From 6bc3dbd53df570995c9145bd4daaa9773e56d76f Mon Sep 17 00:00:00 2001 From: Florian RICHER Date: Tue, 1 Apr 2025 23:49:40 +0200 Subject: [PATCH 001/105] Migrate to bevy_ecs for more up to date ECS crates --- Cargo.lock | 550 ++++++++++++++++++++++++++++++++--------------------- Cargo.toml | 2 +- 2 files changed, 331 insertions(+), 221 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e69aec8..e35561c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -25,6 +25,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ "cfg-if", + "const-random", "getrandom", "once_cell", "version_check", @@ -40,6 +41,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + [[package]] name = "android-activity" version = "0.6.0" @@ -117,48 +124,12 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "any_vec" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f6ac04794a7749710e3c7f3c93222e3d04692993b69876d69393efd2565401a" - [[package]] name = "anyhow" version = "1.0.97" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f" -[[package]] -name = "apecs" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6df7760d4baebb17003dcf99134d8e3a63f487e146d58911f0bcd27afb185d1c" -dependencies = [ - "any_vec", - "apecs-derive", - "async-channel", - "itertools", - "log", - "moongraph", - "parking_lot", - "rayon", - "smallvec", - "snafu 0.8.5", -] - -[[package]] -name = "apecs-derive" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc0f3ddfd31fd5276fb8039b75dc4d284c21213757a969e480c6ef8fde494f3b" -dependencies = [ - "moongraph-macros-syntax", - "proc-macro2", - "quote", - "syn 2.0.100", -] - [[package]] name = "arrayref" version = "0.3.9" @@ -187,16 +158,35 @@ dependencies = [ ] [[package]] -name = "async-channel" -version = "1.9.0" +name = "assert_type_match" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" +checksum = "f548ad2c4031f2902e3edc1f29c29e835829437de49562d8eb5dc5584d3a1043" dependencies = [ - "concurrent-queue", - "event-listener", - "futures-core", + "proc-macro2", + "quote", + "syn", ] +[[package]] +name = "async-executor" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30ca9a001c1e8ba5149f91a74362376cc6bc5b919d92d988668657bd570bdcec" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "slab", +] + +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" + [[package]] name = "atomic-waker" version = "1.1.2" @@ -209,6 +199,127 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +[[package]] +name = "bevy_ecs" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1597106cc01e62e6217ccb662e0748b2ce330893f27c7dc17bac33e0bb99bca9" +dependencies = [ + "bevy_ecs_macros", + "bevy_ptr", + "bevy_reflect", + "bevy_tasks", + "bevy_utils", + "bitflags 2.9.0", + "concurrent-queue", + "derive_more", + "disqualified", + "fixedbitset 0.5.7", + "nonmax", + "petgraph", + "smallvec", +] + +[[package]] +name = "bevy_ecs_macros" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f453adf07712b39826bc5845e5b0887ce03204ee8359bbe6b40a9afda60564a1" +dependencies = [ + "bevy_macro_utils", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "bevy_macro_utils" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bb6ded1ddc124ea214f6a2140e47a78d1fe79b0638dad39419cdeef2e1133f1" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "toml_edit", +] + +[[package]] +name = "bevy_ptr" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89fe0b0b919146939481a3a7c38864face2c6d0fd2c73ab3d430dc693ecd9b11" + +[[package]] +name = "bevy_reflect" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ddbca0a39e88eff2c301dc794ee9d73a53f4b08d47b2c9b5a6aac182fae6217" +dependencies = [ + "assert_type_match", + "bevy_ptr", + "bevy_reflect_derive", + "bevy_utils", + "derive_more", + "disqualified", + "downcast-rs", + "erased-serde", + "serde", + "smallvec", +] + +[[package]] +name = "bevy_reflect_derive" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d62affb769db17d34ad0b75ff27eca94867e2acc8ea350c5eca97d102bd98709" +dependencies = [ + "bevy_macro_utils", + "proc-macro2", + "quote", + "syn", + "uuid", +] + +[[package]] +name = "bevy_tasks" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "028630ddc355563bd567df1076db3515858aa26715ddf7467d2086f9b40e5ab1" +dependencies = [ + "async-executor", + "futures-channel", + "futures-lite", + "pin-project", + "wasm-bindgen-futures", +] + +[[package]] +name = "bevy_utils" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63c2174d43a0de99f863c98a472370047a2bfa7d1e5cec8d9d647fb500905d9d" +dependencies = [ + "ahash", + "bevy_utils_proc_macros", + "getrandom", + "hashbrown 0.14.5", + "thread_local", + "tracing", + "web-time", +] + +[[package]] +name = "bevy_utils_proc_macros" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94847541f6dd2e28f54a9c2b0e857da5f2631e2201ebc25ce68781cdcb721391" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -230,17 +341,6 @@ dependencies = [ "objc2 0.5.2", ] -[[package]] -name = "broomdog" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ec65645d8167b03c07e049f114a878a11ab889f20c071d6f7b30bf88fbe5af" -dependencies = [ - "log", - "rustc-hash", - "snafu 0.8.5", -] - [[package]] name = "bumpalo" version = "3.17.0" @@ -264,7 +364,7 @@ checksum = "3fa76293b4f7bb636ab88fd78228235b5248b4d05cc589aed610f954af5d7c7a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn", ] [[package]] @@ -362,6 +462,26 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "const-random" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359" +dependencies = [ + "const-random-macro", +] + +[[package]] +name = "const-random-macro" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" +dependencies = [ + "getrandom", + "once_cell", + "tiny-keccak", +] + [[package]] name = "core-foundation" version = "0.9.4" @@ -402,25 +522,6 @@ dependencies = [ "libc", ] -[[package]] -name = "crossbeam-deque" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" -dependencies = [ - "crossbeam-epoch", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.9.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" -dependencies = [ - "crossbeam-utils", -] - [[package]] name = "crossbeam-queue" version = "0.3.12" @@ -449,14 +550,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96a6ac251f4a2aca6b3f91340350eab87ae57c3f127ffeb585e92bd336717991" [[package]] -name = "dagga" -version = "0.2.1" +name = "derive_more" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04cf0d7dcd307c9c5d81277737c35d1faf08af9e2cb262966a01c91021686b68" +checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" dependencies = [ - "log", - "rustc-hash", - "snafu 0.7.5", + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "unicode-xid", ] [[package]] @@ -465,6 +576,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" +[[package]] +name = "disqualified" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9c272297e804878a2a4b707cfcfc6d2328b5bb936944613b4fdf2b9269afdfd" + [[package]] name = "dlib" version = "0.5.2" @@ -474,12 +591,6 @@ dependencies = [ "libloading", ] -[[package]] -name = "doc-comment" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" - [[package]] name = "downcast-rs" version = "1.2.1" @@ -492,12 +603,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f25c0e292a7ca6d6498557ff1df68f32c99850012b6ea401cf8daf771f22ff53" -[[package]] -name = "either" -version = "1.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" - [[package]] name = "env_filter" version = "0.1.3" @@ -527,6 +632,16 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" +[[package]] +name = "erased-serde" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e004d887f51fcb9fef17317a2f3525c887d8aa3f4f50fed920816a688284a5b7" +dependencies = [ + "serde", + "typeid", +] + [[package]] name = "errno" version = "0.3.10" @@ -538,10 +653,22 @@ dependencies = [ ] [[package]] -name = "event-listener" -version = "2.5.3" +name = "fastrand" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" [[package]] name = "foldhash" @@ -567,7 +694,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn", ] [[package]] @@ -576,12 +703,40 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", +] + [[package]] name = "futures-core" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-lite" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5edaec856126859abb19ed65f39e90fea3a9574b9707f13539acf4abf7eb532" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + [[package]] name = "gethostname" version = "0.4.3" @@ -599,8 +754,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi", + "wasm-bindgen", ] [[package]] @@ -620,6 +777,17 @@ dependencies = [ "crunchy", ] +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash", + "allocator-api2", + "serde", +] + [[package]] name = "hashbrown" version = "0.15.2" @@ -645,7 +813,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3954d50fe15b02142bf25d3b8bdadb634ec3948f103d04ffe3031bc8fe9d7058" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.15.2", ] [[package]] @@ -654,15 +822,6 @@ version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" -[[package]] -name = "itertools" -version = "0.10.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" -dependencies = [ - "either", -] - [[package]] name = "itoa" version = "1.0.15" @@ -690,7 +849,7 @@ checksum = "8d16e75759ee0aa64c57a56acbf43916987b20c77373cb7e808979e02b93c9f9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn", ] [[package]] @@ -804,30 +963,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" -[[package]] -name = "moongraph" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5a4b09eb96a84205062b48ec5469c8c35c128167e838aa73dc620c4411af598" -dependencies = [ - "broomdog", - "dagga", - "log", - "rayon", - "snafu 0.8.5", -] - -[[package]] -name = "moongraph-macros-syntax" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08112087acc92cc28fb5d8f7bda1307123ecc9a275ed4835f1c03f1a8dd02c1d" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.100", -] - [[package]] name = "ndk" version = "0.9.0" @@ -868,6 +1003,12 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "nonmax" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "610a5acd306ec67f907abe5567859a3c693fb9886eb1f012ab8f2a47bef3db51" + [[package]] name = "num_enum" version = "0.7.3" @@ -886,7 +1027,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.100", + "syn", ] [[package]] @@ -1170,6 +1311,12 @@ dependencies = [ "ttf-parser", ] +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + [[package]] name = "parking_lot" version = "0.12.3" @@ -1199,6 +1346,16 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +[[package]] +name = "petgraph" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" +dependencies = [ + "fixedbitset 0.4.2", + "indexmap", +] + [[package]] name = "pin-project" version = "1.1.10" @@ -1216,7 +1373,7 @@ checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn", ] [[package]] @@ -1315,26 +1472,6 @@ dependencies = [ "objc2-quartz-core 0.3.0", ] -[[package]] -name = "rayon" -version = "1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" -dependencies = [ - "either", - "rayon-core", -] - -[[package]] -name = "rayon-core" -version = "1.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" -dependencies = [ - "crossbeam-deque", - "crossbeam-utils", -] - [[package]] name = "redox_syscall" version = "0.4.1" @@ -1396,7 +1533,7 @@ name = "rust_vulkan_test" version = "0.1.0" dependencies = [ "anyhow", - "apecs", + "bevy_ecs", "env_logger", "glam", "log", @@ -1405,12 +1542,6 @@ dependencies = [ "winit", ] -[[package]] -name = "rustc-hash" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" - [[package]] name = "rustix" version = "0.38.44" @@ -1487,7 +1618,7 @@ checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn", ] [[package]] @@ -1584,66 +1715,12 @@ dependencies = [ "serde", ] -[[package]] -name = "snafu" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4de37ad025c587a29e8f3f5605c00f70b98715ef90b9061a815b9e59e9042d6" -dependencies = [ - "doc-comment", - "snafu-derive 0.7.5", -] - -[[package]] -name = "snafu" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "223891c85e2a29c3fe8fb900c1fae5e69c2e42415e3177752e8718475efa5019" -dependencies = [ - "snafu-derive 0.8.5", -] - -[[package]] -name = "snafu-derive" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "990079665f075b699031e9c08fd3ab99be5029b96f3b78dc0709e8f77e4efebf" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "snafu-derive" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03c3c6b7927ffe7ecaa769ee0e3994da3b8cafc8f444578982c83ecb161af917" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn 2.0.100", -] - [[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.100" @@ -1672,7 +1749,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn", ] [[package]] @@ -1685,6 +1762,15 @@ dependencies = [ "once_cell", ] +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + [[package]] name = "tiny-skia" version = "0.11.4" @@ -1742,6 +1828,9 @@ name = "tracing-core" version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +dependencies = [ + "once_cell", +] [[package]] name = "ttf-parser" @@ -1749,6 +1838,12 @@ version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31" +[[package]] +name = "typeid" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" + [[package]] name = "unicode-ident" version = "1.0.18" @@ -1761,12 +1856,27 @@ version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" +[[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 = "uuid" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3758f5e68192bb96cc8f9b7e2c2cfdabb435499a28499a42f8f984092adad4b" +dependencies = [ + "getrandom", +] + [[package]] name = "version_check" version = "0.9.5" @@ -1823,7 +1933,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.100", + "syn", ] [[package]] @@ -1837,7 +1947,7 @@ dependencies = [ "proc-macro2", "quote", "shaderc", - "syn 2.0.100", + "syn", "vulkano", ] @@ -1879,7 +1989,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn 2.0.100", + "syn", "wasm-bindgen-shared", ] @@ -1914,7 +2024,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -2418,5 +2528,5 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn", ] diff --git a/Cargo.toml b/Cargo.toml index ba7eb9d..a6ece63 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,7 @@ vulkano-shaders = "0.35" glam = { version = "0.30" } # ECS -apecs = "0.8" +bevy_ecs = "0.15.3" # Log and tracing log = "0.4" From f32db721011cf5ddb12f6e444b916fa62d8eaf0a Mon Sep 17 00:00:00 2001 From: Florian RICHER Date: Thu, 3 Apr 2025 18:38:17 +0200 Subject: [PATCH 002/105] rename renderer to vulkan --- src/main.rs | 4 ++-- src/{renderer => vulkan}/app.rs | 10 +++++----- src/{renderer => vulkan}/mod.rs | 0 src/{renderer => vulkan}/pipelines/mod.rs | 0 .../pipelines/triangle_pipeline.rs | 4 ++-- src/{renderer => vulkan}/render_context.rs | 0 src/{renderer => vulkan}/scene.rs | 4 ++-- src/{renderer => vulkan}/vertex.rs | 0 8 files changed, 11 insertions(+), 11 deletions(-) rename src/{renderer => vulkan}/app.rs (98%) rename src/{renderer => vulkan}/mod.rs (100%) rename src/{renderer => vulkan}/pipelines/mod.rs (100%) rename src/{renderer => vulkan}/pipelines/triangle_pipeline.rs (99%) rename src/{renderer => vulkan}/render_context.rs (100%) rename src/{renderer => vulkan}/scene.rs (96%) rename src/{renderer => vulkan}/vertex.rs (100%) diff --git a/src/main.rs b/src/main.rs index 6c01c40..e0ab6d4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,7 @@ use std::error::Error; use winit::event_loop::{ControlFlow, EventLoop}; -mod renderer; +mod vulkan; fn main() -> Result<(), impl Error> { env_logger::init(); @@ -9,7 +9,7 @@ fn main() -> Result<(), impl Error> { let event_loop = EventLoop::new().unwrap(); event_loop.set_control_flow(ControlFlow::Poll); - let mut app = renderer::App::new(&event_loop); + let mut app = vulkan::App::new(&event_loop); event_loop.run_app(&mut app) } diff --git a/src/renderer/app.rs b/src/vulkan/app.rs similarity index 98% rename from src/renderer/app.rs rename to src/vulkan/app.rs index f791616..8a0397e 100644 --- a/src/renderer/app.rs +++ b/src/vulkan/app.rs @@ -1,8 +1,8 @@ -use crate::renderer::render_context::RenderContext; -use crate::renderer::Scene; +use crate::vulkan::Scene; +use crate::vulkan::render_context::RenderContext; use std::sync::Arc; -use vulkano::buffer::allocator::{SubbufferAllocator, SubbufferAllocatorCreateInfo}; use vulkano::buffer::BufferUsage; +use vulkano::buffer::allocator::{SubbufferAllocator, SubbufferAllocatorCreateInfo}; use vulkano::command_buffer::allocator::StandardCommandBufferAllocator; use vulkano::command_buffer::{ AutoCommandBufferBuilder, CommandBufferUsage, RenderingAttachmentInfo, RenderingInfo, @@ -15,9 +15,9 @@ use vulkano::device::{ use vulkano::instance::{Instance, InstanceCreateFlags, InstanceCreateInfo}; use vulkano::memory::allocator::{MemoryTypeFilter, StandardMemoryAllocator}; use vulkano::render_pass::{AttachmentLoadOp, AttachmentStoreOp}; -use vulkano::swapchain::{acquire_next_image, Surface, SwapchainPresentInfo}; +use vulkano::swapchain::{Surface, SwapchainPresentInfo, acquire_next_image}; use vulkano::sync::GpuFuture; -use vulkano::{sync, Validated, Version, VulkanError, VulkanLibrary}; +use vulkano::{Validated, Version, VulkanError, VulkanLibrary, sync}; use winit::application::ApplicationHandler; use winit::event::WindowEvent; use winit::event_loop::{ActiveEventLoop, EventLoop}; diff --git a/src/renderer/mod.rs b/src/vulkan/mod.rs similarity index 100% rename from src/renderer/mod.rs rename to src/vulkan/mod.rs diff --git a/src/renderer/pipelines/mod.rs b/src/vulkan/pipelines/mod.rs similarity index 100% rename from src/renderer/pipelines/mod.rs rename to src/vulkan/pipelines/mod.rs diff --git a/src/renderer/pipelines/triangle_pipeline.rs b/src/vulkan/pipelines/triangle_pipeline.rs similarity index 99% rename from src/renderer/pipelines/triangle_pipeline.rs rename to src/vulkan/pipelines/triangle_pipeline.rs index f6001f7..0c1554d 100644 --- a/src/renderer/pipelines/triangle_pipeline.rs +++ b/src/vulkan/pipelines/triangle_pipeline.rs @@ -5,6 +5,7 @@ use vulkano::descriptor_set::layout::{ DescriptorSetLayoutBinding, DescriptorSetLayoutCreateInfo, DescriptorType, }; use vulkano::device::Device; +use vulkano::pipeline::graphics::GraphicsPipelineCreateInfo; use vulkano::pipeline::graphics::color_blend::{ColorBlendAttachmentState, ColorBlendState}; use vulkano::pipeline::graphics::input_assembly::InputAssemblyState; use vulkano::pipeline::graphics::multisample::MultisampleState; @@ -12,7 +13,6 @@ use vulkano::pipeline::graphics::rasterization::RasterizationState; use vulkano::pipeline::graphics::subpass::PipelineRenderingCreateInfo; use vulkano::pipeline::graphics::vertex_input::{Vertex, VertexDefinition}; use vulkano::pipeline::graphics::viewport::ViewportState; -use vulkano::pipeline::graphics::GraphicsPipelineCreateInfo; use vulkano::pipeline::layout::{PipelineDescriptorSetLayoutCreateInfo, PipelineLayoutCreateFlags}; use vulkano::pipeline::{ DynamicState, GraphicsPipeline, PipelineLayout, PipelineShaderStageCreateInfo, @@ -20,7 +20,7 @@ use vulkano::pipeline::{ use vulkano::shader::{EntryPoint, ShaderStages}; use vulkano::swapchain::Swapchain; -use crate::renderer::Vertex2D; +use crate::vulkan::Vertex2D; pub mod shaders { pub mod vs { diff --git a/src/renderer/render_context.rs b/src/vulkan/render_context.rs similarity index 100% rename from src/renderer/render_context.rs rename to src/vulkan/render_context.rs diff --git a/src/renderer/scene.rs b/src/vulkan/scene.rs similarity index 96% rename from src/renderer/scene.rs rename to src/vulkan/scene.rs index e3c6b58..83863e2 100644 --- a/src/renderer/scene.rs +++ b/src/vulkan/scene.rs @@ -1,4 +1,4 @@ -use crate::renderer::pipelines::triangle_pipeline::shaders::vs; +use crate::vulkan::pipelines::triangle_pipeline::shaders::vs; use glam::{Mat3, Mat4, Vec3}; use std::error::Error; use std::sync::Arc; @@ -8,7 +8,7 @@ use vulkano::command_buffer::{AutoCommandBufferBuilder, PrimaryAutoCommandBuffer use vulkano::descriptor_set::{DescriptorSet, WriteDescriptorSet}; use vulkano::pipeline::{GraphicsPipeline, Pipeline, PipelineBindPoint}; -use crate::renderer::{pipelines::triangle_pipeline::create_triangle_pipeline, App, Vertex2D}; +use crate::vulkan::{App, Vertex2D, pipelines::triangle_pipeline::create_triangle_pipeline}; const VERTICES: [Vertex2D; 12] = [ // Triangle en haut à gauche diff --git a/src/renderer/vertex.rs b/src/vulkan/vertex.rs similarity index 100% rename from src/renderer/vertex.rs rename to src/vulkan/vertex.rs From 15c273b93d35b0f0cdc7fc3fac987c159f7362c8 Mon Sep 17 00:00:00 2001 From: Florian RICHER Date: Thu, 3 Apr 2025 19:59:10 +0200 Subject: [PATCH 003/105] Split app, window render context and vulkan context --- src/core/app.rs | 178 +++++++++++ src/core/mod.rs | 1 + src/main.rs | 6 +- src/vulkan/app.rs | 295 ------------------ src/vulkan/mod.rs | 13 +- src/vulkan/pipelines/triangle_pipeline.rs | 2 +- src/vulkan/scene.rs | 35 ++- src/vulkan/vulkan_context.rs | 203 ++++++++++++ ...er_context.rs => window_render_context.rs} | 18 +- 9 files changed, 426 insertions(+), 325 deletions(-) create mode 100644 src/core/app.rs create mode 100644 src/core/mod.rs delete mode 100644 src/vulkan/app.rs create mode 100644 src/vulkan/vulkan_context.rs rename src/vulkan/{render_context.rs => window_render_context.rs} (89%) diff --git a/src/core/app.rs b/src/core/app.rs new file mode 100644 index 0000000..93a7d56 --- /dev/null +++ b/src/core/app.rs @@ -0,0 +1,178 @@ +use crate::vulkan::scene::Scene; +use crate::vulkan::vulkan_context::VulkanContext; +use crate::vulkan::window_render_context::WindowRenderContext; +use std::sync::Arc; +use vulkano::command_buffer::{RenderingAttachmentInfo, RenderingInfo}; +use vulkano::render_pass::{AttachmentLoadOp, AttachmentStoreOp}; +use vulkano::swapchain::{SwapchainPresentInfo, acquire_next_image}; +use vulkano::sync::GpuFuture; +use vulkano::{Validated, VulkanError, sync}; +use winit::application::ApplicationHandler; +use winit::event::WindowEvent; +use winit::event_loop::ActiveEventLoop; +use winit::window::WindowId; + +pub struct App { + vulkan_context: VulkanContext, + window_render_context: Option, + scene: Option, +} + +impl From for App { + fn from(vulkan_context: VulkanContext) -> Self { + Self { + vulkan_context, + window_render_context: None, + scene: None, + } + } +} + +impl ApplicationHandler for App { + fn resumed(&mut self, event_loop: &ActiveEventLoop) { + let window_attributes = winit::window::Window::default_attributes() + .with_title("Rust ASH Test") + .with_inner_size(winit::dpi::PhysicalSize::new( + f64::from(800), + f64::from(600), + )); + + let window = Arc::new(event_loop.create_window(window_attributes).unwrap()); + + let surface = self.vulkan_context.create_surface(window.clone()); + + self.window_render_context = Some(WindowRenderContext::new( + window, + surface, + &self.vulkan_context.device, + )); + self.scene = Some( + Scene::load( + &self.vulkan_context, + self.window_render_context.as_ref().unwrap(), + ) + .unwrap(), + ); + } + + fn window_event(&mut self, event_loop: &ActiveEventLoop, _id: WindowId, event: WindowEvent) { + match event { + WindowEvent::CloseRequested => { + log::debug!("The close button was pressed; stopping"); + event_loop.exit(); + } + WindowEvent::Resized(_) => { + let rcx = self.window_render_context.as_mut().unwrap(); + rcx.recreate_swapchain = true; + } + WindowEvent::RedrawRequested => { + let (image_index, acquire_future) = { + let rcx = self.window_render_context.as_mut().unwrap(); + let window_size = rcx.window.inner_size(); + + if window_size.width == 0 || window_size.height == 0 { + return; + } + + rcx.previous_frame_end.as_mut().unwrap().cleanup_finished(); + rcx.update_swapchain().unwrap(); + + let (image_index, suboptimal, acquire_future) = + match acquire_next_image(rcx.swapchain.clone(), None) + .map_err(Validated::unwrap) + { + Ok(r) => r, + Err(VulkanError::OutOfDate) => { + rcx.recreate_swapchain = true; + return; + } + Err(e) => panic!("failed to acquire next image: {e}"), + }; + + if suboptimal { + rcx.recreate_swapchain = true; + } + + (image_index, acquire_future) + }; + + let mut builder = self.vulkan_context.create_render_builder(); + + { + let rcx = self.window_render_context.as_ref().unwrap(); + builder + .begin_rendering(RenderingInfo { + color_attachments: vec![Some(RenderingAttachmentInfo { + load_op: AttachmentLoadOp::Clear, + store_op: AttachmentStoreOp::Store, + clear_value: Some([0.0, 0.0, 0.0, 1.0].into()), + ..RenderingAttachmentInfo::image_view( + rcx.attachment_image_views[image_index as usize].clone(), + ) + })], + ..Default::default() + }) + .unwrap() + .set_viewport(0, [rcx.viewport.clone()].into_iter().collect()) + .unwrap(); + } + + if let Some(scene) = self.scene.as_ref() { + scene + .render( + &self.vulkan_context, + &self.window_render_context.as_ref().unwrap(), + &mut builder, + ) + .unwrap(); + } + + builder.end_rendering().unwrap(); + + let command_buffer = builder.build().unwrap(); + + { + let rcx = self.window_render_context.as_mut().unwrap(); + + let future = rcx + .previous_frame_end + .take() + .unwrap() + .join(acquire_future) + .then_execute(self.vulkan_context.graphics_queue.clone(), command_buffer) + .unwrap() + .then_swapchain_present( + self.vulkan_context.graphics_queue.clone(), + SwapchainPresentInfo::swapchain_image_index( + rcx.swapchain.clone(), + image_index, + ), + ) + .then_signal_fence_and_flush(); + + match future.map_err(Validated::unwrap) { + Ok(future) => { + rcx.previous_frame_end = Some(future.boxed()); + } + Err(VulkanError::OutOfDate) => { + rcx.recreate_swapchain = true; + rcx.previous_frame_end = + Some(sync::now(self.vulkan_context.device.clone()).boxed()); + } + Err(e) => { + println!("failed to flush future: {e}"); + rcx.previous_frame_end = + Some(sync::now(self.vulkan_context.device.clone()).boxed()); + } + } + } + } + _ => {} + } + } + + fn about_to_wait(&mut self, _event_loop: &ActiveEventLoop) { + let rcx = self.window_render_context.as_mut().unwrap(); + rcx.window.request_redraw(); + } +} diff --git a/src/core/mod.rs b/src/core/mod.rs new file mode 100644 index 0000000..309be62 --- /dev/null +++ b/src/core/mod.rs @@ -0,0 +1 @@ +pub mod app; diff --git a/src/main.rs b/src/main.rs index e0ab6d4..54a1427 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,8 @@ use std::error::Error; use winit::event_loop::{ControlFlow, EventLoop}; -mod vulkan; +pub mod core; +pub mod vulkan; fn main() -> Result<(), impl Error> { env_logger::init(); @@ -9,7 +10,8 @@ fn main() -> Result<(), impl Error> { let event_loop = EventLoop::new().unwrap(); event_loop.set_control_flow(ControlFlow::Poll); - let mut app = vulkan::App::new(&event_loop); + let vulkan_context = vulkan::vulkan_context::VulkanContext::from(&event_loop); + let mut app = core::app::App::from(vulkan_context); event_loop.run_app(&mut app) } diff --git a/src/vulkan/app.rs b/src/vulkan/app.rs deleted file mode 100644 index 8a0397e..0000000 --- a/src/vulkan/app.rs +++ /dev/null @@ -1,295 +0,0 @@ -use crate::vulkan::Scene; -use crate::vulkan::render_context::RenderContext; -use std::sync::Arc; -use vulkano::buffer::BufferUsage; -use vulkano::buffer::allocator::{SubbufferAllocator, SubbufferAllocatorCreateInfo}; -use vulkano::command_buffer::allocator::StandardCommandBufferAllocator; -use vulkano::command_buffer::{ - AutoCommandBufferBuilder, CommandBufferUsage, RenderingAttachmentInfo, RenderingInfo, -}; -use vulkano::descriptor_set::allocator::StandardDescriptorSetAllocator; -use vulkano::device::physical::PhysicalDeviceType; -use vulkano::device::{ - Device, DeviceCreateInfo, DeviceExtensions, DeviceFeatures, Queue, QueueCreateInfo, QueueFlags, -}; -use vulkano::instance::{Instance, InstanceCreateFlags, InstanceCreateInfo}; -use vulkano::memory::allocator::{MemoryTypeFilter, StandardMemoryAllocator}; -use vulkano::render_pass::{AttachmentLoadOp, AttachmentStoreOp}; -use vulkano::swapchain::{Surface, SwapchainPresentInfo, acquire_next_image}; -use vulkano::sync::GpuFuture; -use vulkano::{Validated, Version, VulkanError, VulkanLibrary, sync}; -use winit::application::ApplicationHandler; -use winit::event::WindowEvent; -use winit::event_loop::{ActiveEventLoop, EventLoop}; -use winit::window::WindowId; - -pub struct App { - pub instance: Arc, - pub device: Arc, - pub queue: Arc, - - pub memory_allocator: Arc, - pub command_buffer_allocator: Arc, - pub uniform_buffer_allocator: SubbufferAllocator, - pub descriptor_set_allocator: Arc, - - pub rcx: Option, - scene: Option, -} - -impl App { - pub fn new(event_loop: &EventLoop<()>) -> Self { - let library = VulkanLibrary::new().unwrap(); - - for layer in library.layer_properties().unwrap() { - log::debug!("Available layer: {}", layer.name()); - } - - let required_extensions = Surface::required_extensions(event_loop).unwrap(); - - let instance = Instance::new( - library, - InstanceCreateInfo { - // Enable enumerating devices that use non-conformant Vulkan implementations. - // (e.g. MoltenVK) - flags: InstanceCreateFlags::ENUMERATE_PORTABILITY, - enabled_extensions: required_extensions, - enabled_layers: vec![String::from("VK_LAYER_KHRONOS_validation")], - ..Default::default() - }, - ) - .unwrap(); - - let mut device_extensions = DeviceExtensions { - khr_swapchain: true, - ..DeviceExtensions::empty() - }; - - let (physical_device, queue_family_index) = instance - .enumerate_physical_devices() - .unwrap() - .filter(|p| { - p.api_version() >= Version::V1_3 || p.supported_extensions().khr_dynamic_rendering - }) - .filter(|p| p.supported_extensions().contains(&device_extensions)) - .filter_map(|p| { - p.queue_family_properties() - .iter() - .enumerate() - .position(|(i, q)| { - q.queue_flags.intersects(QueueFlags::GRAPHICS) - && p.presentation_support(i as u32, event_loop).unwrap() - }) - .map(|i| (p, i as u32)) - }) - .min_by_key(|(p, _)| match p.properties().device_type { - PhysicalDeviceType::DiscreteGpu => 0, - PhysicalDeviceType::IntegratedGpu => 1, - PhysicalDeviceType::VirtualGpu => 2, - PhysicalDeviceType::Cpu => 3, - PhysicalDeviceType::Other => 4, - _ => 5, - }) - .expect("no suitable physical device found"); - - log::debug!( - "Using device: {} (type: {:?})", - physical_device.properties().device_name, - physical_device.properties().device_type, - ); - - if physical_device.api_version() < Version::V1_3 { - device_extensions.khr_dynamic_rendering = true; - } - - log::debug!("Using device extensions: {:#?}", device_extensions); - - let (device, mut queues) = Device::new( - physical_device, - DeviceCreateInfo { - queue_create_infos: vec![QueueCreateInfo { - queue_family_index, - ..Default::default() - }], - enabled_extensions: device_extensions, - enabled_features: DeviceFeatures { - dynamic_rendering: true, - ..DeviceFeatures::empty() - }, - ..Default::default() - }, - ) - .unwrap(); - - let queue = queues.next().unwrap(); - let memory_allocator = Arc::new(StandardMemoryAllocator::new_default(device.clone())); - - let command_buffer_allocator = Arc::new(StandardCommandBufferAllocator::new( - device.clone(), - Default::default(), - )); - - let uniform_buffer_allocator = SubbufferAllocator::new( - memory_allocator.clone(), - SubbufferAllocatorCreateInfo { - buffer_usage: BufferUsage::UNIFORM_BUFFER, - memory_type_filter: MemoryTypeFilter::PREFER_DEVICE - | MemoryTypeFilter::HOST_SEQUENTIAL_WRITE, - ..Default::default() - }, - ); - - let descriptor_set_allocator = Arc::new(StandardDescriptorSetAllocator::new( - device.clone(), - Default::default(), - )); - - Self { - instance, - device, - queue, - memory_allocator, - command_buffer_allocator, - uniform_buffer_allocator, - descriptor_set_allocator, - rcx: None, - scene: None, - } - } -} - -impl ApplicationHandler for App { - fn resumed(&mut self, event_loop: &ActiveEventLoop) { - let window_attributes = winit::window::Window::default_attributes() - .with_title("Rust ASH Test") - .with_inner_size(winit::dpi::PhysicalSize::new( - f64::from(800), - f64::from(600), - )); - - let window = Arc::new(event_loop.create_window(window_attributes).unwrap()); - - let surface = Surface::from_window(self.instance.clone(), window.clone()).unwrap(); - - self.rcx = Some(RenderContext::new(window, surface, &self.device)); - self.scene = Some(Scene::load(&self).unwrap()); - } - - fn window_event(&mut self, event_loop: &ActiveEventLoop, _id: WindowId, event: WindowEvent) { - match event { - WindowEvent::CloseRequested => { - log::debug!("The close button was pressed; stopping"); - event_loop.exit(); - } - WindowEvent::Resized(_) => { - let rcx = self.rcx.as_mut().unwrap(); - rcx.recreate_swapchain = true; - } - WindowEvent::RedrawRequested => { - let (image_index, acquire_future) = { - let rcx = self.rcx.as_mut().unwrap(); - let window_size = rcx.window.inner_size(); - - if window_size.width == 0 || window_size.height == 0 { - return; - } - - rcx.previous_frame_end.as_mut().unwrap().cleanup_finished(); - rcx.update_swapchain().unwrap(); - - let (image_index, suboptimal, acquire_future) = - match acquire_next_image(rcx.swapchain.clone(), None) - .map_err(Validated::unwrap) - { - Ok(r) => r, - Err(VulkanError::OutOfDate) => { - rcx.recreate_swapchain = true; - return; - } - Err(e) => panic!("failed to acquire next image: {e}"), - }; - - if suboptimal { - rcx.recreate_swapchain = true; - } - - (image_index, acquire_future) - }; - - let mut builder = AutoCommandBufferBuilder::primary( - self.command_buffer_allocator.clone(), - self.queue.queue_family_index(), - CommandBufferUsage::OneTimeSubmit, - ) - .unwrap(); - - { - let rcx = self.rcx.as_ref().unwrap(); - builder - .begin_rendering(RenderingInfo { - color_attachments: vec![Some(RenderingAttachmentInfo { - load_op: AttachmentLoadOp::Clear, - store_op: AttachmentStoreOp::Store, - clear_value: Some([0.0, 0.0, 0.0, 1.0].into()), - ..RenderingAttachmentInfo::image_view( - rcx.attachment_image_views[image_index as usize].clone(), - ) - })], - ..Default::default() - }) - .unwrap() - .set_viewport(0, [rcx.viewport.clone()].into_iter().collect()) - .unwrap(); - } - - if let Some(scene) = self.scene.as_ref() { - scene.render(&self, &mut builder).unwrap(); - } - - builder.end_rendering().unwrap(); - - let command_buffer = builder.build().unwrap(); - - { - let rcx = self.rcx.as_mut().unwrap(); - - let future = rcx - .previous_frame_end - .take() - .unwrap() - .join(acquire_future) - .then_execute(self.queue.clone(), command_buffer) - .unwrap() - .then_swapchain_present( - self.queue.clone(), - SwapchainPresentInfo::swapchain_image_index( - rcx.swapchain.clone(), - image_index, - ), - ) - .then_signal_fence_and_flush(); - - match future.map_err(Validated::unwrap) { - Ok(future) => { - rcx.previous_frame_end = Some(future.boxed()); - } - Err(VulkanError::OutOfDate) => { - rcx.recreate_swapchain = true; - rcx.previous_frame_end = Some(sync::now(self.device.clone()).boxed()); - } - Err(e) => { - println!("failed to flush future: {e}"); - rcx.previous_frame_end = Some(sync::now(self.device.clone()).boxed()); - } - } - } - } - _ => {} - } - } - - fn about_to_wait(&mut self, _event_loop: &ActiveEventLoop) { - let rcx = self.rcx.as_mut().unwrap(); - rcx.window.request_redraw(); - } -} diff --git a/src/vulkan/mod.rs b/src/vulkan/mod.rs index bbcff9a..56d8c07 100644 --- a/src/vulkan/mod.rs +++ b/src/vulkan/mod.rs @@ -1,9 +1,6 @@ -mod app; -mod pipelines; -mod render_context; -mod vertex; -pub use app::App; +pub mod pipelines; +pub mod vertex; +pub mod vulkan_context; +pub mod window_render_context; -mod scene; -pub use scene::Scene; -pub use vertex::Vertex2D; +pub mod scene; diff --git a/src/vulkan/pipelines/triangle_pipeline.rs b/src/vulkan/pipelines/triangle_pipeline.rs index 0c1554d..bb07389 100644 --- a/src/vulkan/pipelines/triangle_pipeline.rs +++ b/src/vulkan/pipelines/triangle_pipeline.rs @@ -20,7 +20,7 @@ use vulkano::pipeline::{ use vulkano::shader::{EntryPoint, ShaderStages}; use vulkano::swapchain::Swapchain; -use crate::vulkan::Vertex2D; +use crate::vulkan::vertex::Vertex2D; pub mod shaders { pub mod vs { diff --git a/src/vulkan/scene.rs b/src/vulkan/scene.rs index 83863e2..1f77e5a 100644 --- a/src/vulkan/scene.rs +++ b/src/vulkan/scene.rs @@ -8,7 +8,10 @@ use vulkano::command_buffer::{AutoCommandBufferBuilder, PrimaryAutoCommandBuffer use vulkano::descriptor_set::{DescriptorSet, WriteDescriptorSet}; use vulkano::pipeline::{GraphicsPipeline, Pipeline, PipelineBindPoint}; -use crate::vulkan::{App, Vertex2D, pipelines::triangle_pipeline::create_triangle_pipeline}; +use crate::vulkan::{pipelines::triangle_pipeline::create_triangle_pipeline, vertex::Vertex2D}; + +use super::vulkan_context::VulkanContext; +use super::window_render_context::WindowRenderContext; const VERTICES: [Vertex2D; 12] = [ // Triangle en haut à gauche @@ -73,10 +76,14 @@ pub struct Scene { } impl Scene { - pub fn load(app: &App) -> Result> { - let pipeline = create_triangle_pipeline(&app.device, &app.rcx.as_ref().unwrap().swapchain)?; + pub fn load( + vulkan_context: &VulkanContext, + window_render_context: &WindowRenderContext, + ) -> Result> { + let pipeline = + create_triangle_pipeline(&vulkan_context.device, &window_render_context.swapchain)?; let vertex_buffer = - Vertex2D::create_buffer(Vec::from_iter(VERTICES), &app.memory_allocator)?; + Vertex2D::create_buffer(Vec::from_iter(VERTICES), &vulkan_context.memory_allocator)?; Ok(Scene { pipeline, @@ -87,16 +94,17 @@ impl Scene { pub fn render( &self, - app: &App, + vulkan_context: &VulkanContext, + window_render_context: &WindowRenderContext, builder: &mut AutoCommandBufferBuilder, ) -> Result<(), Box> { let vertex_count = self.vertex_buffer.len() as u32; let instance_count = vertex_count / 3; - let uniform_buffer = self.get_uniform_buffer(app); + let uniform_buffer = self.get_uniform_buffer(vulkan_context, window_render_context); let layout = &self.pipeline.layout().set_layouts()[0]; let descriptor_set = DescriptorSet::new( - app.descriptor_set_allocator.clone(), + vulkan_context.descriptor_set_allocator.clone(), layout.clone(), [WriteDescriptorSet::buffer(0, uniform_buffer)], [], @@ -119,8 +127,12 @@ impl Scene { Ok(()) } - fn get_uniform_buffer(&self, app: &App) -> Subbuffer { - let swapchain = &app.rcx.as_ref().unwrap().swapchain; + fn get_uniform_buffer( + &self, + vulkan_context: &VulkanContext, + window_render_context: &WindowRenderContext, + ) -> Subbuffer { + let swapchain = &window_render_context.swapchain; let elapsed = self.rotation_start.elapsed(); let rotation = elapsed.as_secs() as f64 + elapsed.subsec_nanos() as f64 / 1_000_000_000.0; let rotation = Mat3::from_rotation_y(rotation as f32); @@ -143,7 +155,10 @@ impl Scene { projection: proj.to_cols_array_2d(), }; - let buffer = app.uniform_buffer_allocator.allocate_sized().unwrap(); + let buffer = vulkan_context + .uniform_buffer_allocator + .allocate_sized() + .unwrap(); *buffer.write().unwrap() = uniform_data; buffer diff --git a/src/vulkan/vulkan_context.rs b/src/vulkan/vulkan_context.rs new file mode 100644 index 0000000..ef2c6f7 --- /dev/null +++ b/src/vulkan/vulkan_context.rs @@ -0,0 +1,203 @@ +use std::{any::Any, sync::Arc}; + +use vulkano::{ + Version, VulkanLibrary, + buffer::{ + BufferUsage, + allocator::{SubbufferAllocator, SubbufferAllocatorCreateInfo}, + }, + command_buffer::{ + AutoCommandBufferBuilder, CommandBufferUsage, PrimaryAutoCommandBuffer, + allocator::StandardCommandBufferAllocator, + }, + descriptor_set::allocator::StandardDescriptorSetAllocator, + device::{ + Device, DeviceCreateInfo, DeviceExtensions, DeviceFeatures, Queue, QueueCreateInfo, + QueueFlags, physical::PhysicalDeviceType, + }, + instance::{Instance, InstanceCreateFlags, InstanceCreateInfo, InstanceExtensions}, + memory::allocator::{MemoryTypeFilter, StandardMemoryAllocator}, + swapchain::Surface, +}; +use winit::{ + event_loop::EventLoop, + raw_window_handle::{HasDisplayHandle, HasWindowHandle}, +}; + +pub struct VulkanContext { + instance: Arc, + pub device: Arc, + pub graphics_queue: Arc, + + pub memory_allocator: Arc, + pub command_buffer_allocator: Arc, + pub uniform_buffer_allocator: Arc, + pub descriptor_set_allocator: Arc, +} + +impl From<&EventLoop<()>> for VulkanContext { + fn from(event_loop: &EventLoop<()>) -> Self { + let library = load_library(); + + let enabled_extensions = Surface::required_extensions(event_loop).unwrap(); + + let instance = create_instance(library.clone(), enabled_extensions); + + let (device, mut queues) = pick_graphics_device(&instance, event_loop); + let graphics_queue = queues.next().unwrap(); + + let memory_allocator = Arc::new(StandardMemoryAllocator::new_default(device.clone())); + + let command_buffer_allocator = Arc::new(StandardCommandBufferAllocator::new( + device.clone(), + Default::default(), + )); + + let uniform_buffer_allocator = Arc::new(SubbufferAllocator::new( + memory_allocator.clone(), + SubbufferAllocatorCreateInfo { + buffer_usage: BufferUsage::UNIFORM_BUFFER, + memory_type_filter: MemoryTypeFilter::PREFER_DEVICE + | MemoryTypeFilter::HOST_SEQUENTIAL_WRITE, + ..Default::default() + }, + )); + + let descriptor_set_allocator = Arc::new(StandardDescriptorSetAllocator::new( + device.clone(), + Default::default(), + )); + + Self { + instance, + device, + graphics_queue, + memory_allocator, + command_buffer_allocator, + uniform_buffer_allocator, + descriptor_set_allocator, + } + } +} + +impl VulkanContext { + pub fn create_surface( + &self, + window: Arc, + ) -> Arc { + Surface::from_window(self.instance.clone(), window).unwrap() + } + + pub fn create_render_builder(&self) -> AutoCommandBufferBuilder { + AutoCommandBufferBuilder::primary( + self.command_buffer_allocator.clone(), + self.graphics_queue.queue_family_index(), + CommandBufferUsage::OneTimeSubmit, + ) + .unwrap() + } +} + +fn load_library() -> Arc { + let library = VulkanLibrary::new().unwrap(); + + log::debug!("Available layer:"); + for layer in library.layer_properties().unwrap() { + log::debug!( + "\t - Layer name: {}, Description: {}, Implementation Version: {}, Vulkan Version: {}", + layer.name(), + layer.description(), + layer.implementation_version(), + layer.vulkan_version() + ); + } + + library +} + +fn create_instance( + library: Arc, + required_extensions: InstanceExtensions, +) -> Arc { + Instance::new( + library, + InstanceCreateInfo { + // Enable enumerating devices that use non-conformant Vulkan implementations. + // (e.g. MoltenVK) + flags: InstanceCreateFlags::ENUMERATE_PORTABILITY, + enabled_extensions: required_extensions, + enabled_layers: vec![String::from("VK_LAYER_KHRONOS_validation")], + ..Default::default() + }, + ) + .unwrap() +} + +fn pick_graphics_device( + instance: &Arc, + event_loop: &EventLoop<()>, +) -> ( + Arc, + impl ExactSizeIterator> + use<>, +) { + let mut device_extensions = DeviceExtensions { + khr_swapchain: true, + ..DeviceExtensions::empty() + }; + + let (physical_device, queue_family_index) = instance + .enumerate_physical_devices() + .unwrap() + .filter(|p| { + p.api_version() >= Version::V1_3 || p.supported_extensions().khr_dynamic_rendering + }) + .filter(|p| p.supported_extensions().contains(&device_extensions)) + .filter_map(|p| { + p.queue_family_properties() + .iter() + .enumerate() + .position(|(i, q)| { + q.queue_flags.intersects(QueueFlags::GRAPHICS) + && p.presentation_support(i as u32, event_loop).unwrap() + }) + .map(|i| (p, i as u32)) + }) + .min_by_key(|(p, _)| match p.properties().device_type { + PhysicalDeviceType::DiscreteGpu => 0, + PhysicalDeviceType::IntegratedGpu => 1, + PhysicalDeviceType::VirtualGpu => 2, + PhysicalDeviceType::Cpu => 3, + PhysicalDeviceType::Other => 4, + _ => 5, + }) + .expect("no suitable physical device found"); + + log::debug!( + "Using device: {} (type: {:?})", + physical_device.properties().device_name, + physical_device.properties().device_type, + ); + + if physical_device.api_version() < Version::V1_3 { + device_extensions.khr_dynamic_rendering = true; + } + + log::debug!("Using device extensions: {:#?}", device_extensions); + + Device::new( + physical_device, + DeviceCreateInfo { + queue_create_infos: vec![QueueCreateInfo { + queue_family_index, + ..Default::default() + }], + enabled_extensions: device_extensions, + enabled_features: DeviceFeatures { + dynamic_rendering: true, + ..DeviceFeatures::empty() + }, + ..Default::default() + }, + ) + .unwrap() +} diff --git a/src/vulkan/render_context.rs b/src/vulkan/window_render_context.rs similarity index 89% rename from src/vulkan/render_context.rs rename to src/vulkan/window_render_context.rs index dd7e840..54120d0 100644 --- a/src/vulkan/render_context.rs +++ b/src/vulkan/window_render_context.rs @@ -5,19 +5,19 @@ use vulkano::image::{Image, ImageUsage}; use vulkano::pipeline::graphics::viewport::Viewport; use vulkano::swapchain::{Surface, Swapchain, SwapchainCreateInfo}; use vulkano::sync::GpuFuture; -use vulkano::{sync, Validated, VulkanError}; +use vulkano::{Validated, VulkanError, sync}; use winit::window::Window; -pub struct RenderContext { - pub(super) window: Arc, - pub(super) swapchain: Arc, - pub(super) attachment_image_views: Vec>, - pub(super) viewport: Viewport, - pub(super) recreate_swapchain: bool, - pub(super) previous_frame_end: Option>, +pub struct WindowRenderContext { + pub window: Arc, + pub swapchain: Arc, + pub attachment_image_views: Vec>, + pub viewport: Viewport, + pub recreate_swapchain: bool, + pub previous_frame_end: Option>, } -impl RenderContext { +impl WindowRenderContext { pub fn new(window: Arc, surface: Arc, device: &Arc) -> Self { let window_size = window.inner_size(); From 2fbf4e6ce2ff41a2234fda87da49439a35f9191b Mon Sep 17 00:00:00 2001 From: Florian RICHER Date: Thu, 3 Apr 2025 21:10:08 +0200 Subject: [PATCH 004/105] Split pick_graphics_device --- src/vulkan/vulkan_context.rs | 81 +++++++++++++++++++++++------------- 1 file changed, 53 insertions(+), 28 deletions(-) diff --git a/src/vulkan/vulkan_context.rs b/src/vulkan/vulkan_context.rs index ef2c6f7..ea1b29b 100644 --- a/src/vulkan/vulkan_context.rs +++ b/src/vulkan/vulkan_context.rs @@ -13,7 +13,8 @@ use vulkano::{ descriptor_set::allocator::StandardDescriptorSetAllocator, device::{ Device, DeviceCreateInfo, DeviceExtensions, DeviceFeatures, Queue, QueueCreateInfo, - QueueFlags, physical::PhysicalDeviceType, + QueueFlags, + physical::{PhysicalDevice, PhysicalDeviceType}, }, instance::{Instance, InstanceCreateFlags, InstanceCreateInfo, InstanceExtensions}, memory::allocator::{MemoryTypeFilter, StandardMemoryAllocator}, @@ -133,6 +134,53 @@ fn create_instance( .unwrap() } +fn find_physical_device_queue_family_indexes( + physical_device: &Arc, + event_loop: &EventLoop<()>, +) -> Option { + let mut graphic_queue_family_index = None; + + for (i, queue_family_property) in physical_device.queue_family_properties().iter().enumerate() { + if queue_family_property + .queue_flags + .intersects(QueueFlags::GRAPHICS) + && physical_device + .presentation_support(i as u32, event_loop) + .unwrap() + { + graphic_queue_family_index = Some(i as u32); + } + } + + graphic_queue_family_index +} + +fn pick_physical_device_and_queue_family_indexes( + instance: &Arc, + event_loop: &EventLoop<()>, + device_extensions: &DeviceExtensions, +) -> Option<(Arc, u32)> { + instance + .enumerate_physical_devices() + .unwrap() + .filter(|p| { + p.api_version() >= Version::V1_3 || p.supported_extensions().khr_dynamic_rendering + }) + .filter(|p| p.supported_extensions().contains(device_extensions)) + .filter_map(|p| { + find_physical_device_queue_family_indexes(&p, event_loop) + .and_then(|indexes| Some((p, indexes))) + }) + .min_by_key(|(p, _)| match p.properties().device_type { + PhysicalDeviceType::DiscreteGpu => 0, + PhysicalDeviceType::IntegratedGpu => 1, + PhysicalDeviceType::VirtualGpu => 2, + PhysicalDeviceType::Cpu => 3, + PhysicalDeviceType::Other => 4, + _ => 5, + }) +} + fn pick_graphics_device( instance: &Arc, event_loop: &EventLoop<()>, @@ -145,32 +193,9 @@ fn pick_graphics_device( ..DeviceExtensions::empty() }; - let (physical_device, queue_family_index) = instance - .enumerate_physical_devices() - .unwrap() - .filter(|p| { - p.api_version() >= Version::V1_3 || p.supported_extensions().khr_dynamic_rendering - }) - .filter(|p| p.supported_extensions().contains(&device_extensions)) - .filter_map(|p| { - p.queue_family_properties() - .iter() - .enumerate() - .position(|(i, q)| { - q.queue_flags.intersects(QueueFlags::GRAPHICS) - && p.presentation_support(i as u32, event_loop).unwrap() - }) - .map(|i| (p, i as u32)) - }) - .min_by_key(|(p, _)| match p.properties().device_type { - PhysicalDeviceType::DiscreteGpu => 0, - PhysicalDeviceType::IntegratedGpu => 1, - PhysicalDeviceType::VirtualGpu => 2, - PhysicalDeviceType::Cpu => 3, - PhysicalDeviceType::Other => 4, - _ => 5, - }) - .expect("no suitable physical device found"); + let (physical_device, graphics_family_index) = + pick_physical_device_and_queue_family_indexes(instance, event_loop, &device_extensions) + .unwrap(); log::debug!( "Using device: {} (type: {:?})", @@ -188,7 +213,7 @@ fn pick_graphics_device( physical_device, DeviceCreateInfo { queue_create_infos: vec![QueueCreateInfo { - queue_family_index, + queue_family_index: graphics_family_index, ..Default::default() }], enabled_extensions: device_extensions, From 852d72d716710ac0e9c30298d26371c7d98bdbf5 Mon Sep 17 00:00:00 2001 From: Florian RICHER Date: Fri, 4 Apr 2025 13:38:27 +0200 Subject: [PATCH 005/105] Begin move mesh + Vertex and Camera into core --- src/core/camera/mod.rs | 19 +++++++++++++++++++ src/core/mod.rs | 3 +++ src/core/render/mesh.rs | 14 ++++++++++++++ src/core/render/mod.rs | 2 ++ src/{vulkan => core/render}/vertex.rs | 0 src/core/scene.rs | 21 +++++++++++++++++++++ src/game/mod.rs | 1 + src/main.rs | 1 + src/vulkan/mod.rs | 1 - src/vulkan/pipelines/triangle_pipeline.rs | 2 +- src/vulkan/scene.rs | 3 ++- 11 files changed, 64 insertions(+), 3 deletions(-) create mode 100644 src/core/camera/mod.rs create mode 100644 src/core/render/mesh.rs create mode 100644 src/core/render/mod.rs rename src/{vulkan => core/render}/vertex.rs (100%) create mode 100644 src/core/scene.rs create mode 100644 src/game/mod.rs diff --git a/src/core/camera/mod.rs b/src/core/camera/mod.rs new file mode 100644 index 0000000..f9b6925 --- /dev/null +++ b/src/core/camera/mod.rs @@ -0,0 +1,19 @@ +use bevy_ecs::component::Component; +use glam::{Mat4, Quat, Vec3}; + +pub trait Camera: Into + Component {} + +#[derive(Component)] +pub struct Camera3D { + pub projection: Mat4, + pub position: Vec3, + pub rotation: Quat, +} + +impl Into for Camera3D { + fn into(self) -> Mat4 { + Mat4::from_rotation_translation(self.rotation, self.position) * self.projection + } +} + +impl Camera for Camera3D {} diff --git a/src/core/mod.rs b/src/core/mod.rs index 309be62..f058532 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -1 +1,4 @@ pub mod app; +pub mod camera; +pub mod render; +pub mod scene; diff --git a/src/core/render/mesh.rs b/src/core/render/mesh.rs new file mode 100644 index 0000000..ca5ec0f --- /dev/null +++ b/src/core/render/mesh.rs @@ -0,0 +1,14 @@ +use bevy_ecs::component::Component; + +use super::vertex::Vertex2D; + +#[derive(Component)] +pub struct Mesh2D { + pub vertices: Vec, +} + +impl Mesh2D { + pub fn new(vertices: Vec) -> Self { + Self { vertices } + } +} diff --git a/src/core/render/mod.rs b/src/core/render/mod.rs new file mode 100644 index 0000000..6f29814 --- /dev/null +++ b/src/core/render/mod.rs @@ -0,0 +1,2 @@ +pub mod mesh; +pub mod vertex; diff --git a/src/vulkan/vertex.rs b/src/core/render/vertex.rs similarity index 100% rename from src/vulkan/vertex.rs rename to src/core/render/vertex.rs diff --git a/src/core/scene.rs b/src/core/scene.rs new file mode 100644 index 0000000..a424da3 --- /dev/null +++ b/src/core/scene.rs @@ -0,0 +1,21 @@ +use bevy_ecs::world::World; + +pub struct Scene { + world: World, +} + +impl Scene { + pub fn new() -> Self { + Self { + world: World::new(), + } + } + + pub fn world(&self) -> &World { + &self.world + } + + pub fn world_mut(&mut self) -> &mut World { + &mut self.world + } +} diff --git a/src/game/mod.rs b/src/game/mod.rs new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/game/mod.rs @@ -0,0 +1 @@ + diff --git a/src/main.rs b/src/main.rs index 54a1427..2b80274 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,6 +2,7 @@ use std::error::Error; use winit::event_loop::{ControlFlow, EventLoop}; pub mod core; +pub mod game; pub mod vulkan; fn main() -> Result<(), impl Error> { diff --git a/src/vulkan/mod.rs b/src/vulkan/mod.rs index 56d8c07..dc3d731 100644 --- a/src/vulkan/mod.rs +++ b/src/vulkan/mod.rs @@ -1,5 +1,4 @@ pub mod pipelines; -pub mod vertex; pub mod vulkan_context; pub mod window_render_context; diff --git a/src/vulkan/pipelines/triangle_pipeline.rs b/src/vulkan/pipelines/triangle_pipeline.rs index bb07389..e573747 100644 --- a/src/vulkan/pipelines/triangle_pipeline.rs +++ b/src/vulkan/pipelines/triangle_pipeline.rs @@ -20,7 +20,7 @@ use vulkano::pipeline::{ use vulkano::shader::{EntryPoint, ShaderStages}; use vulkano::swapchain::Swapchain; -use crate::vulkan::vertex::Vertex2D; +use crate::core::render::vertex::Vertex2D; pub mod shaders { pub mod vs { diff --git a/src/vulkan/scene.rs b/src/vulkan/scene.rs index 1f77e5a..4d1de95 100644 --- a/src/vulkan/scene.rs +++ b/src/vulkan/scene.rs @@ -8,7 +8,8 @@ use vulkano::command_buffer::{AutoCommandBufferBuilder, PrimaryAutoCommandBuffer use vulkano::descriptor_set::{DescriptorSet, WriteDescriptorSet}; use vulkano::pipeline::{GraphicsPipeline, Pipeline, PipelineBindPoint}; -use crate::vulkan::{pipelines::triangle_pipeline::create_triangle_pipeline, vertex::Vertex2D}; +use crate::core::render::vertex::Vertex2D; +use crate::vulkan::pipelines::triangle_pipeline::create_triangle_pipeline; use super::vulkan_context::VulkanContext; use super::window_render_context::WindowRenderContext; From 9664ea754ca4a0cda52a76603391699e9ce9fad7 Mon Sep 17 00:00:00 2001 From: Florian RICHER Date: Mon, 7 Apr 2025 13:11:19 +0200 Subject: [PATCH 006/105] flake: Fix missing libstdc++ --- flake.nix | 2 ++ 1 file changed, 2 insertions(+) diff --git a/flake.nix b/flake.nix index 565976b..4f7cd2e 100644 --- a/flake.nix +++ b/flake.nix @@ -39,6 +39,8 @@ buildInputs = with pkgs; [ vulkan-headers vulkan-loader vulkan-validation-layers renderdoc ] ++ pkgs.lib.optionals pkgs.stdenv.hostPlatform.isLinux (with pkgs; [ + stdenv.cc.cc.lib + # Wayland libxkbcommon wayland From 1d333b633b19e0691a760e504df4cc17185bf392 Mon Sep 17 00:00:00 2001 From: Florian RICHER Date: Mon, 7 Apr 2025 17:03:00 +0200 Subject: [PATCH 007/105] Push lunch break code --- src/core/render/material/mod.rs | 10 ++++++++ src/core/render/material/triangle.rs | 34 ++++++++++++++++++++++++++++ src/core/render/mod.rs | 1 + 3 files changed, 45 insertions(+) create mode 100644 src/core/render/material/mod.rs create mode 100644 src/core/render/material/triangle.rs diff --git a/src/core/render/material/mod.rs b/src/core/render/material/mod.rs new file mode 100644 index 0000000..8cccde2 --- /dev/null +++ b/src/core/render/material/mod.rs @@ -0,0 +1,10 @@ +pub mod triangle; + +use vulkano::command_buffer::CommandBuffer; + +use crate::vulkan::vulkan_context::VulkanContext; + +pub trait Material { + fn load(&self, vulkan_context: &VulkanContext); + fn bind(&self, command_buffer: &mut CommandBuffer); +} diff --git a/src/core/render/material/triangle.rs b/src/core/render/material/triangle.rs new file mode 100644 index 0000000..c3bafaa --- /dev/null +++ b/src/core/render/material/triangle.rs @@ -0,0 +1,34 @@ +use glam::Vec4; +use vulkano::command_buffer::CommandBuffer; + +use super::Material; + +pub mod shaders { + pub mod vs { + vulkano_shaders::shader! { + ty: "vertex", + path: r"res/shaders/vertex.vert", + } + } + + pub mod fs { + vulkano_shaders::shader! { + ty: "fragment", + path: r"res/shaders/vertex.frag", + } + } +} + +pub struct TriangleMaterial { + pub colors: [Vec4; 3], +} + +impl Material for TriangleMaterial { + fn bind(&self, command_buffer: &mut CommandBuffer) { + todo!() + } + + fn load(&self, vulkan_context: &crate::vulkan::vulkan_context::VulkanContext) { + todo!() + } +} diff --git a/src/core/render/mod.rs b/src/core/render/mod.rs index 6f29814..5d003a8 100644 --- a/src/core/render/mod.rs +++ b/src/core/render/mod.rs @@ -1,2 +1,3 @@ +pub mod material; pub mod mesh; pub mod vertex; From b361965033337551f488e39f4bd81b945abf4754 Mon Sep 17 00:00:00 2001 From: Florian RICHER Date: Mon, 7 Apr 2025 22:51:49 +0200 Subject: [PATCH 008/105] Update --- src/core/render/material.rs | 7 ++++++ src/core/render/material/mod.rs | 10 -------- src/core/render/material/triangle.rs | 34 ---------------------------- src/core/render/vertex.rs | 2 +- src/vulkan/scene.rs | 24 +++++++++++++------- src/vulkan/vulkan_context.rs | 18 +-------------- 6 files changed, 25 insertions(+), 70 deletions(-) create mode 100644 src/core/render/material.rs delete mode 100644 src/core/render/material/mod.rs delete mode 100644 src/core/render/material/triangle.rs diff --git a/src/core/render/material.rs b/src/core/render/material.rs new file mode 100644 index 0000000..539d4bc --- /dev/null +++ b/src/core/render/material.rs @@ -0,0 +1,7 @@ +use std::sync::Arc; + +use bevy_ecs::component::Component; +use vulkano::pipeline::GraphicsPipeline; + +#[derive(Component)] +pub struct Material(pub Arc); diff --git a/src/core/render/material/mod.rs b/src/core/render/material/mod.rs deleted file mode 100644 index 8cccde2..0000000 --- a/src/core/render/material/mod.rs +++ /dev/null @@ -1,10 +0,0 @@ -pub mod triangle; - -use vulkano::command_buffer::CommandBuffer; - -use crate::vulkan::vulkan_context::VulkanContext; - -pub trait Material { - fn load(&self, vulkan_context: &VulkanContext); - fn bind(&self, command_buffer: &mut CommandBuffer); -} diff --git a/src/core/render/material/triangle.rs b/src/core/render/material/triangle.rs deleted file mode 100644 index c3bafaa..0000000 --- a/src/core/render/material/triangle.rs +++ /dev/null @@ -1,34 +0,0 @@ -use glam::Vec4; -use vulkano::command_buffer::CommandBuffer; - -use super::Material; - -pub mod shaders { - pub mod vs { - vulkano_shaders::shader! { - ty: "vertex", - path: r"res/shaders/vertex.vert", - } - } - - pub mod fs { - vulkano_shaders::shader! { - ty: "fragment", - path: r"res/shaders/vertex.frag", - } - } -} - -pub struct TriangleMaterial { - pub colors: [Vec4; 3], -} - -impl Material for TriangleMaterial { - fn bind(&self, command_buffer: &mut CommandBuffer) { - todo!() - } - - fn load(&self, vulkan_context: &crate::vulkan::vulkan_context::VulkanContext) { - todo!() - } -} diff --git a/src/core/render/vertex.rs b/src/core/render/vertex.rs index fc2ee21..9bf133e 100644 --- a/src/core/render/vertex.rs +++ b/src/core/render/vertex.rs @@ -1,10 +1,10 @@ use std::sync::Arc; +use vulkano::Validated; use vulkano::buffer::{ AllocateBufferError, Buffer, BufferContents, BufferCreateInfo, BufferUsage, Subbuffer, }; use vulkano::memory::allocator::{AllocationCreateInfo, MemoryTypeFilter, StandardMemoryAllocator}; use vulkano::pipeline::graphics::vertex_input::Vertex; -use vulkano::Validated; #[derive(BufferContents, Vertex)] #[repr(C)] diff --git a/src/vulkan/scene.rs b/src/vulkan/scene.rs index 4d1de95..22986ff 100644 --- a/src/vulkan/scene.rs +++ b/src/vulkan/scene.rs @@ -3,9 +3,10 @@ use glam::{Mat3, Mat4, Vec3}; use std::error::Error; use std::sync::Arc; use std::time::Instant; -use vulkano::buffer::Subbuffer; +use vulkano::buffer::{Buffer, BufferCreateInfo, BufferUsage, Subbuffer}; use vulkano::command_buffer::{AutoCommandBufferBuilder, PrimaryAutoCommandBuffer}; use vulkano::descriptor_set::{DescriptorSet, WriteDescriptorSet}; +use vulkano::memory::allocator::{AllocationCreateInfo, MemoryTypeFilter}; use vulkano::pipeline::{GraphicsPipeline, Pipeline, PipelineBindPoint}; use crate::core::render::vertex::Vertex2D; @@ -156,12 +157,19 @@ impl Scene { projection: proj.to_cols_array_2d(), }; - let buffer = vulkan_context - .uniform_buffer_allocator - .allocate_sized() - .unwrap(); - *buffer.write().unwrap() = uniform_data; - - buffer + Buffer::from_data( + vulkan_context.memory_allocator.clone(), + BufferCreateInfo { + usage: BufferUsage::UNIFORM_BUFFER, + ..Default::default() + }, + AllocationCreateInfo { + memory_type_filter: MemoryTypeFilter::PREFER_DEVICE + | MemoryTypeFilter::HOST_SEQUENTIAL_WRITE, + ..Default::default() + }, + uniform_data, + ) + .unwrap() } } diff --git a/src/vulkan/vulkan_context.rs b/src/vulkan/vulkan_context.rs index ea1b29b..bad47de 100644 --- a/src/vulkan/vulkan_context.rs +++ b/src/vulkan/vulkan_context.rs @@ -2,10 +2,6 @@ use std::{any::Any, sync::Arc}; use vulkano::{ Version, VulkanLibrary, - buffer::{ - BufferUsage, - allocator::{SubbufferAllocator, SubbufferAllocatorCreateInfo}, - }, command_buffer::{ AutoCommandBufferBuilder, CommandBufferUsage, PrimaryAutoCommandBuffer, allocator::StandardCommandBufferAllocator, @@ -17,7 +13,7 @@ use vulkano::{ physical::{PhysicalDevice, PhysicalDeviceType}, }, instance::{Instance, InstanceCreateFlags, InstanceCreateInfo, InstanceExtensions}, - memory::allocator::{MemoryTypeFilter, StandardMemoryAllocator}, + memory::allocator::StandardMemoryAllocator, swapchain::Surface, }; use winit::{ @@ -32,7 +28,6 @@ pub struct VulkanContext { pub memory_allocator: Arc, pub command_buffer_allocator: Arc, - pub uniform_buffer_allocator: Arc, pub descriptor_set_allocator: Arc, } @@ -54,16 +49,6 @@ impl From<&EventLoop<()>> for VulkanContext { Default::default(), )); - let uniform_buffer_allocator = Arc::new(SubbufferAllocator::new( - memory_allocator.clone(), - SubbufferAllocatorCreateInfo { - buffer_usage: BufferUsage::UNIFORM_BUFFER, - memory_type_filter: MemoryTypeFilter::PREFER_DEVICE - | MemoryTypeFilter::HOST_SEQUENTIAL_WRITE, - ..Default::default() - }, - )); - let descriptor_set_allocator = Arc::new(StandardDescriptorSetAllocator::new( device.clone(), Default::default(), @@ -75,7 +60,6 @@ impl From<&EventLoop<()>> for VulkanContext { graphics_queue, memory_allocator, command_buffer_allocator, - uniform_buffer_allocator, descriptor_set_allocator, } } From f4694157ab53d3223089ad0032964dd67ca500da Mon Sep 17 00:00:00 2001 From: Florian RICHER Date: Sun, 13 Apr 2025 16:35:21 +0200 Subject: [PATCH 009/105] Update --- src/core/app/app.rs | 54 +++++++++++++++++++++++++++++++ src/core/app/mod.rs | 4 +++ src/core/app/plugin.rs | 5 +++ src/core/mod.rs | 2 ++ src/core/{app.rs => old_app.rs} | 0 src/core/window/mod.rs | 24 ++++++++++++++ src/core/window/window_handler.rs | 40 +++++++++++++++++++++++ src/main.rs | 17 ++++++++-- 8 files changed, 143 insertions(+), 3 deletions(-) create mode 100644 src/core/app/app.rs create mode 100644 src/core/app/mod.rs create mode 100644 src/core/app/plugin.rs rename src/core/{app.rs => old_app.rs} (100%) create mode 100644 src/core/window/mod.rs create mode 100644 src/core/window/window_handler.rs diff --git a/src/core/app/app.rs b/src/core/app/app.rs new file mode 100644 index 0000000..12b4be7 --- /dev/null +++ b/src/core/app/app.rs @@ -0,0 +1,54 @@ +use std::error::Error; + +use bevy_ecs::{schedule::Schedules, world::World}; + +pub enum AppExit { + Success, + Error(Box), +} + +pub type RunnerFn = Box AppExit>; + +pub struct App { + world: World, + runner: Option, +} + +impl Default for App { + fn default() -> Self { + let mut world = World::new(); + world.init_resource::(); + + Self { + world, + runner: None, + } + } +} + +impl App { + pub fn world_mut(&mut self) -> &mut World { + &mut self.world + } + + pub fn world(&self) -> &World { + &self.world + } + + pub fn run(&mut self) -> Result<(), Box> { + match self.runner.take() { + Some(runner) => match runner() { + AppExit::Success => Ok(()), + AppExit::Error(e) => Err(e), + }, + None => Err(Box::new(std::io::Error::new( + std::io::ErrorKind::Other, + "runner is not set", + ))), + } + } + + pub fn set_runner(&mut self, runner: RunnerFn) { + self.runner = Some(runner); + } +} diff --git a/src/core/app/mod.rs b/src/core/app/mod.rs new file mode 100644 index 0000000..f7320aa --- /dev/null +++ b/src/core/app/mod.rs @@ -0,0 +1,4 @@ +mod app; +pub mod plugin; + +pub use app::App; diff --git a/src/core/app/plugin.rs b/src/core/app/plugin.rs new file mode 100644 index 0000000..987c540 --- /dev/null +++ b/src/core/app/plugin.rs @@ -0,0 +1,5 @@ +use super::app::App; + +pub trait Plugin { + fn build(&self, app: &mut App); +} diff --git a/src/core/mod.rs b/src/core/mod.rs index f058532..07427ac 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -1,4 +1,6 @@ pub mod app; pub mod camera; +pub mod old_app; pub mod render; pub mod scene; +pub mod window; diff --git a/src/core/app.rs b/src/core/old_app.rs similarity index 100% rename from src/core/app.rs rename to src/core/old_app.rs diff --git a/src/core/window/mod.rs b/src/core/window/mod.rs new file mode 100644 index 0000000..55f6a70 --- /dev/null +++ b/src/core/window/mod.rs @@ -0,0 +1,24 @@ +pub mod window_handler; + +use super::app::{App, plugin::Plugin}; +use window_handler::WindowHandler; +use winit::event_loop::EventLoop; +use winit::window::WindowAttributes; + +pub struct WindowPlugin { + window_attributes: WindowAttributes, + event_loop: EventLoop<()>, +} + +impl Plugin for WindowPlugin { + fn build(&self, app: &mut App) { + let world = app.world_mut(); + world.insert_resource(WindowHandler::new(self.window_attributes.clone())); + + let window_handler = world.get_resource_mut::().unwrap(); + + // app.set_runner(Box::new(move || { + // self.event_loop.run_app(&mut window_handler); + // })); + } +} diff --git a/src/core/window/window_handler.rs b/src/core/window/window_handler.rs new file mode 100644 index 0000000..2b87d35 --- /dev/null +++ b/src/core/window/window_handler.rs @@ -0,0 +1,40 @@ +use bevy_ecs::system::Resource; +use winit::{ + application::ApplicationHandler, + event::WindowEvent, + event_loop::ActiveEventLoop, + window::{Window, WindowAttributes, WindowId}, +}; + +#[derive(Resource)] +pub struct WindowHandler { + window_attributes: WindowAttributes, + window: Option>, +} + +impl WindowHandler { + pub fn new(window_attributes: WindowAttributes) -> Self { + Self { + window_attributes, + window: None, + } + } +} +impl ApplicationHandler for WindowHandler { + fn resumed(&mut self, event_loop: &ActiveEventLoop) { + let window = event_loop + .create_window(self.window_attributes.clone()) + .unwrap(); + self.window = Some(Box::new(window)); + } + + fn window_event(&mut self, event_loop: &ActiveEventLoop, _id: WindowId, event: WindowEvent) { + match event { + WindowEvent::CloseRequested => { + log::debug!("The close button was pressed; stopping"); + event_loop.exit(); + } + _ => {} + } + } +} diff --git a/src/main.rs b/src/main.rs index 2b80274..f09236f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,14 +5,25 @@ pub mod core; pub mod game; pub mod vulkan; -fn main() -> Result<(), impl Error> { +fn main() -> Result<(), Box> { env_logger::init(); + run_old_app() +} + +fn run_new_app() -> Result<(), Box> { + let mut app = core::app::App::default(); + app.run() +} + +fn run_old_app() -> Result<(), Box> { let event_loop = EventLoop::new().unwrap(); event_loop.set_control_flow(ControlFlow::Poll); let vulkan_context = vulkan::vulkan_context::VulkanContext::from(&event_loop); - let mut app = core::app::App::from(vulkan_context); + let mut app = core::old_app::App::from(vulkan_context); - event_loop.run_app(&mut app) + event_loop.run_app(&mut app).map_err(Box::new)?; + + Ok(()) } From df99ef3a3f1641f4f753bb67de76f99c288532fc Mon Sep 17 00:00:00 2001 From: Florian RICHER Date: Sun, 13 Apr 2025 16:49:07 +0200 Subject: [PATCH 010/105] Add AppError --- Cargo.lock | 33 +++++++++++++++++++++++++++------ Cargo.toml | 1 + src/core/app/app.rs | 21 +++++++++++++-------- src/main.rs | 14 ++++++-------- 4 files changed, 47 insertions(+), 22 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e35561c..da7454f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -65,7 +65,7 @@ dependencies = [ "ndk-context", "ndk-sys", "num_enum", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -384,7 +384,7 @@ dependencies = [ "polling", "rustix", "slab", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -863,7 +863,7 @@ dependencies = [ "combine", "jni-sys", "log", - "thiserror", + "thiserror 1.0.69", "walkdir", "windows-sys 0.45.0", ] @@ -975,7 +975,7 @@ dependencies = [ "ndk-sys", "num_enum", "raw-window-handle", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -1537,6 +1537,7 @@ dependencies = [ "env_logger", "glam", "log", + "thiserror 2.0.12", "vulkano", "vulkano-shaders", "winit", @@ -1695,7 +1696,7 @@ dependencies = [ "log", "memmap2", "rustix", - "thiserror", + "thiserror 1.0.69", "wayland-backend", "wayland-client", "wayland-csd-frame", @@ -1738,7 +1739,16 @@ version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "thiserror-impl", + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +dependencies = [ + "thiserror-impl 2.0.12", ] [[package]] @@ -1752,6 +1762,17 @@ dependencies = [ "syn", ] +[[package]] +name = "thiserror-impl" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "thread_local" version = "1.1.8" diff --git a/Cargo.toml b/Cargo.toml index a6ece63..a84f681 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,7 @@ publish = false [dependencies] anyhow = "1.0" +thiserror = "2.0" winit = { version = "0.30", features = ["rwh_06"] } vulkano = "0.35" diff --git a/src/core/app/app.rs b/src/core/app/app.rs index 12b4be7..21ebd28 100644 --- a/src/core/app/app.rs +++ b/src/core/app/app.rs @@ -7,7 +7,15 @@ pub enum AppExit { Error(Box), } -pub type RunnerFn = Box AppExit>; +pub type RunnerFn = Box AppExit>; + +#[derive(Debug, thiserror::Error)] +pub enum AppError { + #[error("Runner is not set")] + RunnerNotSet, + #[error("Runner returned an error : {0}")] + RunnerError(Box), +} pub struct App { world: World, @@ -35,16 +43,13 @@ impl App { &self.world } - pub fn run(&mut self) -> Result<(), Box> { + pub fn run(mut self) -> Result<(), AppError> { match self.runner.take() { - Some(runner) => match runner() { + Some(runner) => match runner(self) { AppExit::Success => Ok(()), - AppExit::Error(e) => Err(e), + AppExit::Error(e) => Err(AppError::RunnerError(e)), }, - None => Err(Box::new(std::io::Error::new( - std::io::ErrorKind::Other, - "runner is not set", - ))), + None => Err(AppError::RunnerNotSet), } } diff --git a/src/main.rs b/src/main.rs index f09236f..9412fad 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,25 +5,23 @@ pub mod core; pub mod game; pub mod vulkan; -fn main() -> Result<(), Box> { +fn main() -> Result<(), impl Error> { env_logger::init(); - run_old_app() + run_new_app() } -fn run_new_app() -> Result<(), Box> { - let mut app = core::app::App::default(); +fn run_new_app() -> Result<(), impl Error> { + let app = core::app::App::default(); app.run() } -fn run_old_app() -> Result<(), Box> { +fn run_old_app() -> Result<(), impl Error> { let event_loop = EventLoop::new().unwrap(); event_loop.set_control_flow(ControlFlow::Poll); let vulkan_context = vulkan::vulkan_context::VulkanContext::from(&event_loop); let mut app = core::old_app::App::from(vulkan_context); - event_loop.run_app(&mut app).map_err(Box::new)?; - - Ok(()) + event_loop.run_app(&mut app) } From 4f6216635f7fe88577f6e05b624ceb6a19589a5b Mon Sep 17 00:00:00 2001 From: Florian RICHER Date: Sun, 13 Apr 2025 18:06:18 +0200 Subject: [PATCH 011/105] Update --- src/core/app/mod.rs | 4 +- src/core/app/plugin.rs | 5 --- src/core/mod.rs | 2 - src/core/scene.rs | 21 --------- src/core/window/config.rs | 17 +++++++ src/core/window/mod.rs | 37 ++++++++------- src/core/window/state.rs | 45 +++++++++++++++++++ src/core/window/window_handler.rs | 40 ----------------- src/game/mod.rs | 13 ++++++ src/main.rs | 12 ++--- src/{core/old_app.rs => old_app/app.rs} | 6 +-- src/{vulkan => old_app}/mod.rs | 4 +- src/{vulkan => old_app}/pipelines/mod.rs | 0 .../pipelines/triangle_pipeline.rs | 0 src/{vulkan => old_app}/scene.rs | 4 +- src/{vulkan => old_app}/vulkan_context.rs | 0 .../window_render_context.rs | 0 17 files changed, 110 insertions(+), 100 deletions(-) delete mode 100644 src/core/app/plugin.rs delete mode 100644 src/core/scene.rs create mode 100644 src/core/window/config.rs create mode 100644 src/core/window/state.rs delete mode 100644 src/core/window/window_handler.rs rename src/{core/old_app.rs => old_app/app.rs} (97%) rename src/{vulkan => old_app}/mod.rs (87%) rename src/{vulkan => old_app}/pipelines/mod.rs (100%) rename src/{vulkan => old_app}/pipelines/triangle_pipeline.rs (100%) rename src/{vulkan => old_app}/scene.rs (97%) rename src/{vulkan => old_app}/vulkan_context.rs (100%) rename src/{vulkan => old_app}/window_render_context.rs (100%) diff --git a/src/core/app/mod.rs b/src/core/app/mod.rs index f7320aa..c6c8a20 100644 --- a/src/core/app/mod.rs +++ b/src/core/app/mod.rs @@ -1,4 +1,2 @@ mod app; -pub mod plugin; - -pub use app::App; +pub use app::*; diff --git a/src/core/app/plugin.rs b/src/core/app/plugin.rs deleted file mode 100644 index 987c540..0000000 --- a/src/core/app/plugin.rs +++ /dev/null @@ -1,5 +0,0 @@ -use super::app::App; - -pub trait Plugin { - fn build(&self, app: &mut App); -} diff --git a/src/core/mod.rs b/src/core/mod.rs index 07427ac..bd7ce34 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -1,6 +1,4 @@ pub mod app; pub mod camera; -pub mod old_app; pub mod render; -pub mod scene; pub mod window; diff --git a/src/core/scene.rs b/src/core/scene.rs deleted file mode 100644 index a424da3..0000000 --- a/src/core/scene.rs +++ /dev/null @@ -1,21 +0,0 @@ -use bevy_ecs::world::World; - -pub struct Scene { - world: World, -} - -impl Scene { - pub fn new() -> Self { - Self { - world: World::new(), - } - } - - pub fn world(&self) -> &World { - &self.world - } - - pub fn world_mut(&mut self) -> &mut World { - &mut self.world - } -} diff --git a/src/core/window/config.rs b/src/core/window/config.rs new file mode 100644 index 0000000..8fbef8c --- /dev/null +++ b/src/core/window/config.rs @@ -0,0 +1,17 @@ +use bevy_ecs::system::Resource; +use winit::{dpi::PhysicalSize, window::WindowAttributes}; + +#[derive(Resource, Clone)] +pub struct WindowConfig { + pub title: String, + pub width: u32, + pub height: u32, +} + +impl Into for &WindowConfig { + fn into(self) -> WindowAttributes { + WindowAttributes::default() + .with_title(self.title.clone()) + .with_inner_size(PhysicalSize::new(self.width as f64, self.height as f64)) + } +} diff --git a/src/core/window/mod.rs b/src/core/window/mod.rs index 55f6a70..e600120 100644 --- a/src/core/window/mod.rs +++ b/src/core/window/mod.rs @@ -1,24 +1,27 @@ -pub mod window_handler; - -use super::app::{App, plugin::Plugin}; -use window_handler::WindowHandler; +use config::WindowConfig; +use state::WindowState; use winit::event_loop::EventLoop; -use winit::window::WindowAttributes; -pub struct WindowPlugin { - window_attributes: WindowAttributes, - event_loop: EventLoop<()>, +use super::app::{App, AppExit}; + +pub mod config; +pub mod state; + +pub fn init(app: &mut App, window_config: WindowConfig) { + let world = app.world_mut(); + world.insert_resource(window_config); + + let mut event_loop_builder = EventLoop::with_user_event(); + let event_loop = event_loop_builder.build().unwrap(); + + app.set_runner(Box::new(move |app| runner(app, event_loop))); } -impl Plugin for WindowPlugin { - fn build(&self, app: &mut App) { - let world = app.world_mut(); - world.insert_resource(WindowHandler::new(self.window_attributes.clone())); +fn runner(app: App, event_loop: EventLoop<()>) -> AppExit { + let mut window_state = WindowState::new(app); - let window_handler = world.get_resource_mut::().unwrap(); - - // app.set_runner(Box::new(move || { - // self.event_loop.run_app(&mut window_handler); - // })); + match event_loop.run_app(&mut window_state) { + Ok(_) => AppExit::Success, + Err(e) => AppExit::Error(Box::new(e)), } } diff --git a/src/core/window/state.rs b/src/core/window/state.rs new file mode 100644 index 0000000..94bfcc7 --- /dev/null +++ b/src/core/window/state.rs @@ -0,0 +1,45 @@ +use bevy_ecs::world::World; +use winit::{ + application::ApplicationHandler, + event::WindowEvent, + event_loop::ActiveEventLoop, + window::{Window, WindowId}, +}; + +use crate::core::app::App; + +use super::config::WindowConfig; + +pub struct WindowState { + app: App, + window: Option, +} + +impl WindowState { + pub fn new(app: App) -> Self { + Self { app, window: None } + } + + fn world(&self) -> &World { + self.app.world() + } +} + +impl ApplicationHandler for WindowState { + fn resumed(&mut self, event_loop: &ActiveEventLoop) { + let window_config = self.world().get_resource::().unwrap(); + + let window = event_loop.create_window(window_config.into()).unwrap(); + self.window = Some(window); + } + + fn window_event(&mut self, event_loop: &ActiveEventLoop, _id: WindowId, event: WindowEvent) { + match event { + WindowEvent::CloseRequested => { + log::debug!("The close button was pressed; stopping"); + event_loop.exit(); + } + _ => {} + } + } +} diff --git a/src/core/window/window_handler.rs b/src/core/window/window_handler.rs deleted file mode 100644 index 2b87d35..0000000 --- a/src/core/window/window_handler.rs +++ /dev/null @@ -1,40 +0,0 @@ -use bevy_ecs::system::Resource; -use winit::{ - application::ApplicationHandler, - event::WindowEvent, - event_loop::ActiveEventLoop, - window::{Window, WindowAttributes, WindowId}, -}; - -#[derive(Resource)] -pub struct WindowHandler { - window_attributes: WindowAttributes, - window: Option>, -} - -impl WindowHandler { - pub fn new(window_attributes: WindowAttributes) -> Self { - Self { - window_attributes, - window: None, - } - } -} -impl ApplicationHandler for WindowHandler { - fn resumed(&mut self, event_loop: &ActiveEventLoop) { - let window = event_loop - .create_window(self.window_attributes.clone()) - .unwrap(); - self.window = Some(Box::new(window)); - } - - fn window_event(&mut self, event_loop: &ActiveEventLoop, _id: WindowId, event: WindowEvent) { - match event { - WindowEvent::CloseRequested => { - log::debug!("The close button was pressed; stopping"); - event_loop.exit(); - } - _ => {} - } - } -} diff --git a/src/game/mod.rs b/src/game/mod.rs index 8b13789..ffee10f 100644 --- a/src/game/mod.rs +++ b/src/game/mod.rs @@ -1 +1,14 @@ +use crate::core::{ + app::App, + window::{self, config::WindowConfig}, +}; +pub fn init(app: &mut App) { + let window_config = WindowConfig { + title: "Rust ASH Test".to_string(), + width: 800, + height: 600, + }; + + window::init(app, window_config); +} diff --git a/src/main.rs b/src/main.rs index 9412fad..c981713 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,16 +3,18 @@ use winit::event_loop::{ControlFlow, EventLoop}; pub mod core; pub mod game; -pub mod vulkan; +pub mod old_app; fn main() -> Result<(), impl Error> { env_logger::init(); - run_new_app() + // run_new_app() + run_old_app() } fn run_new_app() -> Result<(), impl Error> { - let app = core::app::App::default(); + let mut app = core::app::App::default(); + game::init(&mut app); app.run() } @@ -20,8 +22,8 @@ fn run_old_app() -> Result<(), impl Error> { let event_loop = EventLoop::new().unwrap(); event_loop.set_control_flow(ControlFlow::Poll); - let vulkan_context = vulkan::vulkan_context::VulkanContext::from(&event_loop); - let mut app = core::old_app::App::from(vulkan_context); + let vulkan_context = old_app::vulkan_context::VulkanContext::from(&event_loop); + let mut app = old_app::app::App::from(vulkan_context); event_loop.run_app(&mut app) } diff --git a/src/core/old_app.rs b/src/old_app/app.rs similarity index 97% rename from src/core/old_app.rs rename to src/old_app/app.rs index 93a7d56..cfcf038 100644 --- a/src/core/old_app.rs +++ b/src/old_app/app.rs @@ -1,6 +1,6 @@ -use crate::vulkan::scene::Scene; -use crate::vulkan::vulkan_context::VulkanContext; -use crate::vulkan::window_render_context::WindowRenderContext; +use crate::old_app::scene::Scene; +use crate::old_app::vulkan_context::VulkanContext; +use crate::old_app::window_render_context::WindowRenderContext; use std::sync::Arc; use vulkano::command_buffer::{RenderingAttachmentInfo, RenderingInfo}; use vulkano::render_pass::{AttachmentLoadOp, AttachmentStoreOp}; diff --git a/src/vulkan/mod.rs b/src/old_app/mod.rs similarity index 87% rename from src/vulkan/mod.rs rename to src/old_app/mod.rs index dc3d731..9fce0a9 100644 --- a/src/vulkan/mod.rs +++ b/src/old_app/mod.rs @@ -1,5 +1,5 @@ +pub mod app; pub mod pipelines; +pub mod scene; pub mod vulkan_context; pub mod window_render_context; - -pub mod scene; diff --git a/src/vulkan/pipelines/mod.rs b/src/old_app/pipelines/mod.rs similarity index 100% rename from src/vulkan/pipelines/mod.rs rename to src/old_app/pipelines/mod.rs diff --git a/src/vulkan/pipelines/triangle_pipeline.rs b/src/old_app/pipelines/triangle_pipeline.rs similarity index 100% rename from src/vulkan/pipelines/triangle_pipeline.rs rename to src/old_app/pipelines/triangle_pipeline.rs diff --git a/src/vulkan/scene.rs b/src/old_app/scene.rs similarity index 97% rename from src/vulkan/scene.rs rename to src/old_app/scene.rs index 22986ff..72e3f48 100644 --- a/src/vulkan/scene.rs +++ b/src/old_app/scene.rs @@ -1,4 +1,4 @@ -use crate::vulkan::pipelines::triangle_pipeline::shaders::vs; +use crate::old_app::pipelines::triangle_pipeline::shaders::vs; use glam::{Mat3, Mat4, Vec3}; use std::error::Error; use std::sync::Arc; @@ -10,7 +10,7 @@ use vulkano::memory::allocator::{AllocationCreateInfo, MemoryTypeFilter}; use vulkano::pipeline::{GraphicsPipeline, Pipeline, PipelineBindPoint}; use crate::core::render::vertex::Vertex2D; -use crate::vulkan::pipelines::triangle_pipeline::create_triangle_pipeline; +use crate::old_app::pipelines::triangle_pipeline::create_triangle_pipeline; use super::vulkan_context::VulkanContext; use super::window_render_context::WindowRenderContext; diff --git a/src/vulkan/vulkan_context.rs b/src/old_app/vulkan_context.rs similarity index 100% rename from src/vulkan/vulkan_context.rs rename to src/old_app/vulkan_context.rs diff --git a/src/vulkan/window_render_context.rs b/src/old_app/window_render_context.rs similarity index 100% rename from src/vulkan/window_render_context.rs rename to src/old_app/window_render_context.rs From e2616a0ef5af2ae8ff0d1e351017976f97c7138a Mon Sep 17 00:00:00 2001 From: Florian RICHER Date: Sun, 13 Apr 2025 18:45:33 +0200 Subject: [PATCH 012/105] Add vulkan creation from resources --- src/core/mod.rs | 1 + src/core/vulkan/context.rs | 65 ++++++++++++++++ src/core/vulkan/mod.rs | 24 ++++++ src/core/vulkan/utils.rs | 137 ++++++++++++++++++++++++++++++++++ src/core/window/mod.rs | 39 +++++++--- src/core/window/raw_handle.rs | 18 +++++ src/game/mod.rs | 6 +- src/main.rs | 4 +- 8 files changed, 281 insertions(+), 13 deletions(-) create mode 100644 src/core/vulkan/context.rs create mode 100644 src/core/vulkan/mod.rs create mode 100644 src/core/vulkan/utils.rs create mode 100644 src/core/window/raw_handle.rs diff --git a/src/core/mod.rs b/src/core/mod.rs index bd7ce34..abaeeae 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -1,4 +1,5 @@ pub mod app; pub mod camera; pub mod render; +pub mod vulkan; pub mod window; diff --git a/src/core/vulkan/context.rs b/src/core/vulkan/context.rs new file mode 100644 index 0000000..b4e9763 --- /dev/null +++ b/src/core/vulkan/context.rs @@ -0,0 +1,65 @@ +use std::sync::Arc; + +use bevy_ecs::system::Resource; +use vulkano::{ + command_buffer::allocator::StandardCommandBufferAllocator, + descriptor_set::allocator::StandardDescriptorSetAllocator, + device::{Device, Queue}, + instance::Instance, + memory::allocator::StandardMemoryAllocator, + swapchain::Surface, +}; + +use crate::core::{app::App, window::raw_handle::DisplayHandleWrapper}; + +use super::utils; + +#[derive(Resource)] +pub struct VulkanContext { + pub instance: Arc, + pub device: Arc, + pub graphics_queue: Arc, + + pub memory_allocator: Arc, + pub command_buffer_allocator: Arc, + pub descriptor_set_allocator: Arc, +} + +impl From<&App> for VulkanContext { + fn from(app: &App) -> Self { + let library = utils::load_library(); + + let world = app.world(); + + let display_handle: &DisplayHandleWrapper = + world.get_resource::().unwrap(); + + let enabled_extensions = Surface::required_extensions(&display_handle.0).unwrap(); + + let instance = utils::create_instance(library.clone(), enabled_extensions); + + let (device, mut queues) = utils::pick_graphics_device(&instance, &display_handle.0); + let graphics_queue = queues.next().unwrap(); + + let memory_allocator = Arc::new(StandardMemoryAllocator::new_default(device.clone())); + + let command_buffer_allocator = Arc::new(StandardCommandBufferAllocator::new( + device.clone(), + Default::default(), + )); + + let descriptor_set_allocator = Arc::new(StandardDescriptorSetAllocator::new( + device.clone(), + Default::default(), + )); + + Self { + instance: instance.clone(), + device, + graphics_queue, + memory_allocator, + command_buffer_allocator, + descriptor_set_allocator, + } + } +} diff --git a/src/core/vulkan/mod.rs b/src/core/vulkan/mod.rs new file mode 100644 index 0000000..e80d56b --- /dev/null +++ b/src/core/vulkan/mod.rs @@ -0,0 +1,24 @@ +use context::VulkanContext; + +use super::app::App; + +mod context; +mod utils; + +#[derive(Debug, thiserror::Error)] +pub enum VulkanError { + #[error("Failed to create vulkan context")] + FailedToCreateVulkanContext, +} + +pub struct Vulkan; + +impl Vulkan { + pub fn new(app: &mut App) -> Result<(), VulkanError> { + let vulkan_context = VulkanContext::from(app as &App); + + app.world_mut().insert_resource(vulkan_context); + + Ok(()) + } +} diff --git a/src/core/vulkan/utils.rs b/src/core/vulkan/utils.rs new file mode 100644 index 0000000..046528f --- /dev/null +++ b/src/core/vulkan/utils.rs @@ -0,0 +1,137 @@ +use std::sync::Arc; + +use vulkano::{ + Version, VulkanLibrary, + device::{ + Device, DeviceCreateInfo, DeviceExtensions, DeviceFeatures, Queue, QueueCreateInfo, + QueueFlags, + physical::{PhysicalDevice, PhysicalDeviceType}, + }, + instance::{Instance, InstanceCreateFlags, InstanceCreateInfo, InstanceExtensions}, +}; +use winit::raw_window_handle::HasDisplayHandle; + +pub(super) fn load_library() -> Arc { + let library = VulkanLibrary::new().unwrap(); + + log::debug!("Available layer:"); + for layer in library.layer_properties().unwrap() { + log::debug!( + "\t - Layer name: {}, Description: {}, Implementation Version: {}, Vulkan Version: {}", + layer.name(), + layer.description(), + layer.implementation_version(), + layer.vulkan_version() + ); + } + + library +} + +pub(super) fn create_instance( + library: Arc, + required_extensions: InstanceExtensions, +) -> Arc { + Instance::new( + library, + InstanceCreateInfo { + // Enable enumerating devices that use non-conformant Vulkan implementations. + // (e.g. MoltenVK) + flags: InstanceCreateFlags::ENUMERATE_PORTABILITY, + enabled_extensions: required_extensions, + enabled_layers: vec![String::from("VK_LAYER_KHRONOS_validation")], + ..Default::default() + }, + ) + .unwrap() +} + +pub(super) fn find_physical_device_queue_family_indexes( + physical_device: &Arc, + display_handle: &impl HasDisplayHandle, +) -> Option { + let mut graphic_queue_family_index = None; + + for (i, queue_family_property) in physical_device.queue_family_properties().iter().enumerate() { + if queue_family_property + .queue_flags + .intersects(QueueFlags::GRAPHICS) + && physical_device + .presentation_support(i as u32, display_handle) + .unwrap() + { + graphic_queue_family_index = Some(i as u32); + } + } + + graphic_queue_family_index +} + +pub(super) fn pick_physical_device_and_queue_family_indexes( + instance: &Arc, + display_handle: &impl HasDisplayHandle, + device_extensions: &DeviceExtensions, +) -> Option<(Arc, u32)> { + instance + .enumerate_physical_devices() + .unwrap() + .filter(|p| { + p.api_version() >= Version::V1_3 || p.supported_extensions().khr_dynamic_rendering + }) + .filter(|p| p.supported_extensions().contains(device_extensions)) + .filter_map(|p| { + find_physical_device_queue_family_indexes(&p, display_handle) + .and_then(|indexes| Some((p, indexes))) + }) + .min_by_key(|(p, _)| match p.properties().device_type { + PhysicalDeviceType::DiscreteGpu => 0, + PhysicalDeviceType::IntegratedGpu => 1, + PhysicalDeviceType::VirtualGpu => 2, + PhysicalDeviceType::Cpu => 3, + PhysicalDeviceType::Other => 4, + _ => 5, + }) +} + +pub(super) fn pick_graphics_device( + instance: &Arc, + display_handle: &impl HasDisplayHandle, +) -> (Arc, impl ExactSizeIterator>) { + let mut device_extensions = DeviceExtensions { + khr_swapchain: true, + ..DeviceExtensions::empty() + }; + + let (physical_device, graphics_family_index) = + pick_physical_device_and_queue_family_indexes(instance, display_handle, &device_extensions) + .unwrap(); + + log::debug!( + "Using device: {} (type: {:?})", + physical_device.properties().device_name, + physical_device.properties().device_type, + ); + + if physical_device.api_version() < Version::V1_3 { + device_extensions.khr_dynamic_rendering = true; + } + + log::debug!("Using device extensions: {:#?}", device_extensions); + + Device::new( + physical_device, + DeviceCreateInfo { + queue_create_infos: vec![QueueCreateInfo { + queue_family_index: graphics_family_index, + ..Default::default() + }], + enabled_extensions: device_extensions, + enabled_features: DeviceFeatures { + dynamic_rendering: true, + ..DeviceFeatures::empty() + }, + ..Default::default() + }, + ) + .unwrap() +} diff --git a/src/core/window/mod.rs b/src/core/window/mod.rs index e600120..4368310 100644 --- a/src/core/window/mod.rs +++ b/src/core/window/mod.rs @@ -1,23 +1,44 @@ use config::WindowConfig; +use raw_handle::{DisplayHandleWrapper, EventLoopProxyWrapper}; use state::WindowState; use winit::event_loop::EventLoop; use super::app::{App, AppExit}; pub mod config; +pub mod raw_handle; pub mod state; -pub fn init(app: &mut App, window_config: WindowConfig) { - let world = app.world_mut(); - world.insert_resource(window_config); - - let mut event_loop_builder = EventLoop::with_user_event(); - let event_loop = event_loop_builder.build().unwrap(); - - app.set_runner(Box::new(move |app| runner(app, event_loop))); +#[derive(Debug, thiserror::Error)] +pub enum WindowError { + #[error("Failed to create event loop")] + FailedToCreateEventLoop, } -fn runner(app: App, event_loop: EventLoop<()>) -> AppExit { +pub struct Window; + +impl Window { + pub fn new(app: &mut App, window_config: WindowConfig) -> Result<(), WindowError> { + let world = app.world_mut(); + world.insert_resource(window_config); + + let mut event_loop_builder = EventLoop::with_user_event(); + let event_loop = event_loop_builder + .build() + .map_err(|_| WindowError::FailedToCreateEventLoop)?; + + world.insert_resource(DisplayHandleWrapper(event_loop.owned_display_handle())); + + app.set_runner(Box::new(move |app| runner(app, event_loop))); + + Ok(()) + } +} + +fn runner(mut app: App, event_loop: EventLoop<()>) -> AppExit { + app.world_mut() + .insert_resource(EventLoopProxyWrapper::new(event_loop.create_proxy())); + let mut window_state = WindowState::new(app); match event_loop.run_app(&mut window_state) { diff --git a/src/core/window/raw_handle.rs b/src/core/window/raw_handle.rs new file mode 100644 index 0000000..16b2178 --- /dev/null +++ b/src/core/window/raw_handle.rs @@ -0,0 +1,18 @@ +use bevy_ecs::system::Resource; +use winit::event_loop::EventLoopProxy; + +#[derive(Resource)] +pub struct EventLoopProxyWrapper(EventLoopProxy); + +impl EventLoopProxyWrapper { + pub fn new(event_loop: EventLoopProxy) -> Self { + Self(event_loop) + } + + pub fn proxy(&self) -> &EventLoopProxy { + &self.0 + } +} + +#[derive(Resource)] +pub struct DisplayHandleWrapper(pub winit::event_loop::OwnedDisplayHandle); diff --git a/src/game/mod.rs b/src/game/mod.rs index ffee10f..0b7787d 100644 --- a/src/game/mod.rs +++ b/src/game/mod.rs @@ -1,6 +1,7 @@ use crate::core::{ app::App, - window::{self, config::WindowConfig}, + vulkan::Vulkan, + window::{Window, config::WindowConfig}, }; pub fn init(app: &mut App) { @@ -10,5 +11,6 @@ pub fn init(app: &mut App) { height: 600, }; - window::init(app, window_config); + Window::new(app, window_config).unwrap(); + Vulkan::new(app).unwrap(); } diff --git a/src/main.rs b/src/main.rs index c981713..8297797 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,8 +8,8 @@ pub mod old_app; fn main() -> Result<(), impl Error> { env_logger::init(); - // run_new_app() - run_old_app() + run_new_app() + // run_old_app() } fn run_new_app() -> Result<(), impl Error> { From a04c7694389af64c1e716f29949aa9b8155b786b Mon Sep 17 00:00:00 2001 From: Florian RICHER Date: Sun, 13 Apr 2025 19:23:05 +0200 Subject: [PATCH 013/105] Begin add Window Render Context --- src/core/vulkan/mod.rs | 10 +- .../vulkan/{context.rs => vulkan_context.rs} | 26 +++- src/core/vulkan/window_render_context.rs | 117 ++++++++++++++++++ src/core/window/raw_handle.rs | 7 +- src/core/window/state.rs | 17 +-- 5 files changed, 163 insertions(+), 14 deletions(-) rename src/core/vulkan/{context.rs => vulkan_context.rs} (69%) create mode 100644 src/core/vulkan/window_render_context.rs diff --git a/src/core/vulkan/mod.rs b/src/core/vulkan/mod.rs index e80d56b..fe22fb3 100644 --- a/src/core/vulkan/mod.rs +++ b/src/core/vulkan/mod.rs @@ -1,9 +1,11 @@ -use context::VulkanContext; +use vulkan_context::VulkanContext; +use window_render_context::WindowRenderContext; use super::app::App; -mod context; mod utils; +mod vulkan_context; +mod window_render_context; #[derive(Debug, thiserror::Error)] pub enum VulkanError { @@ -16,9 +18,11 @@ pub struct Vulkan; impl Vulkan { pub fn new(app: &mut App) -> Result<(), VulkanError> { let vulkan_context = VulkanContext::from(app as &App); - app.world_mut().insert_resource(vulkan_context); + let window_render_context = WindowRenderContext::from(app as &App); + app.world_mut().insert_resource(window_render_context); + Ok(()) } } diff --git a/src/core/vulkan/context.rs b/src/core/vulkan/vulkan_context.rs similarity index 69% rename from src/core/vulkan/context.rs rename to src/core/vulkan/vulkan_context.rs index b4e9763..f59d9a3 100644 --- a/src/core/vulkan/context.rs +++ b/src/core/vulkan/vulkan_context.rs @@ -1,14 +1,18 @@ -use std::sync::Arc; +use std::{any::Any, sync::Arc}; use bevy_ecs::system::Resource; use vulkano::{ - command_buffer::allocator::StandardCommandBufferAllocator, + command_buffer::{ + AutoCommandBufferBuilder, CommandBufferUsage, PrimaryAutoCommandBuffer, + allocator::StandardCommandBufferAllocator, + }, descriptor_set::allocator::StandardDescriptorSetAllocator, device::{Device, Queue}, instance::Instance, memory::allocator::StandardMemoryAllocator, swapchain::Surface, }; +use winit::raw_window_handle::{HasDisplayHandle, HasWindowHandle}; use crate::core::{app::App, window::raw_handle::DisplayHandleWrapper}; @@ -25,6 +29,24 @@ pub struct VulkanContext { pub descriptor_set_allocator: Arc, } +impl VulkanContext { + pub fn create_surface( + &self, + window: Arc, + ) -> Arc { + Surface::from_window(self.instance.clone(), window).unwrap() + } + + pub fn create_render_builder(&self) -> AutoCommandBufferBuilder { + AutoCommandBufferBuilder::primary( + self.command_buffer_allocator.clone(), + self.graphics_queue.queue_family_index(), + CommandBufferUsage::OneTimeSubmit, + ) + .unwrap() + } +} + impl From<&App> for VulkanContext { fn from(app: &App) -> Self { let library = utils::load_library(); diff --git a/src/core/vulkan/window_render_context.rs b/src/core/vulkan/window_render_context.rs new file mode 100644 index 0000000..1c653a7 --- /dev/null +++ b/src/core/vulkan/window_render_context.rs @@ -0,0 +1,117 @@ +use bevy_ecs::system::Resource; +use std::sync::Arc; +use vulkano::image::view::ImageView; +use vulkano::image::{Image, ImageUsage}; +use vulkano::pipeline::graphics::viewport::Viewport; +use vulkano::swapchain::{Swapchain, SwapchainCreateInfo}; +use vulkano::sync::{self, GpuFuture}; +use vulkano::{Validated, VulkanError}; +use winit::window::Window; + +use crate::core::app::App; +use crate::core::window::raw_handle::WindowWrapper; + +use super::vulkan_context::VulkanContext; + +#[derive(Resource)] +pub struct WindowRenderContext { + pub window: Arc, + pub swapchain: Arc, + pub attachment_image_views: Vec>, + pub viewport: Viewport, + pub recreate_swapchain: bool, + pub previous_frame_end: Option>, +} + +impl From<&App> for WindowRenderContext { + fn from(app: &App) -> Self { + let world = app.world(); + let vulkan_context = world.get_resource::().unwrap(); + let window_handle = world.get_resource::().unwrap(); + let window_size = window_handle.0.inner_size(); + + let surface = vulkan_context.create_surface(window_handle.0.clone()); + + let (swapchain, images) = { + let surface_capabilities = vulkan_context + .device + .physical_device() + .surface_capabilities(&surface, Default::default()) + .unwrap(); + + let (image_format, _) = vulkan_context + .device + .physical_device() + .surface_formats(&surface, Default::default()) + .unwrap()[0]; + + Swapchain::new( + vulkan_context.device.clone(), + surface, + SwapchainCreateInfo { + // 2 because with some graphics driver, it crash on fullscreen because fullscreen need to min image to works. + min_image_count: surface_capabilities.min_image_count.max(2), + image_format, + image_extent: window_size.into(), + image_usage: ImageUsage::COLOR_ATTACHMENT, + composite_alpha: surface_capabilities + .supported_composite_alpha + .into_iter() + .next() + .unwrap(), + + ..Default::default() + }, + ) + .unwrap() + }; + + let attachment_image_views = window_size_dependent_setup(&images); + + let viewport = Viewport { + offset: [0.0, 0.0], + extent: window_size.into(), + depth_range: 0.0..=1.0, + }; + + let recreate_swapchain = false; + let previous_frame_end = Some(sync::now(vulkan_context.device.clone()).boxed_send_sync()); + + Self { + window: window_handle.0.clone(), + swapchain, + attachment_image_views, + viewport, + recreate_swapchain, + previous_frame_end, + } + } +} + +impl WindowRenderContext { + pub fn update_swapchain(&mut self) -> Result<(), Validated> { + if !self.recreate_swapchain { + return Ok(()); + } + + let window_size = self.window.inner_size(); + let (new_swapchain, new_images) = self.swapchain.recreate(SwapchainCreateInfo { + image_extent: window_size.into(), + ..self.swapchain.create_info() + })?; + + self.swapchain = new_swapchain; + self.attachment_image_views = window_size_dependent_setup(&new_images); + self.viewport.extent = window_size.into(); + self.recreate_swapchain = false; + + Ok(()) + } +} + +fn window_size_dependent_setup(images: &[Arc]) -> Vec> { + images + .iter() + .map(|image| ImageView::new_default(image.clone()).unwrap()) + .collect::>() +} diff --git a/src/core/window/raw_handle.rs b/src/core/window/raw_handle.rs index 16b2178..ad9c8dd 100644 --- a/src/core/window/raw_handle.rs +++ b/src/core/window/raw_handle.rs @@ -1,5 +1,7 @@ +use std::sync::Arc; + use bevy_ecs::system::Resource; -use winit::event_loop::EventLoopProxy; +use winit::{event_loop::EventLoopProxy, window::Window}; #[derive(Resource)] pub struct EventLoopProxyWrapper(EventLoopProxy); @@ -16,3 +18,6 @@ impl EventLoopProxyWrapper { #[derive(Resource)] pub struct DisplayHandleWrapper(pub winit::event_loop::OwnedDisplayHandle); + +#[derive(Resource)] +pub struct WindowWrapper(pub Arc); diff --git a/src/core/window/state.rs b/src/core/window/state.rs index 94bfcc7..0b0966e 100644 --- a/src/core/window/state.rs +++ b/src/core/window/state.rs @@ -1,23 +1,22 @@ +use std::sync::Arc; + use bevy_ecs::world::World; use winit::{ - application::ApplicationHandler, - event::WindowEvent, - event_loop::ActiveEventLoop, - window::{Window, WindowId}, + application::ApplicationHandler, event::WindowEvent, event_loop::ActiveEventLoop, + window::WindowId, }; use crate::core::app::App; -use super::config::WindowConfig; +use super::{config::WindowConfig, raw_handle::WindowWrapper}; pub struct WindowState { app: App, - window: Option, } impl WindowState { pub fn new(app: App) -> Self { - Self { app, window: None } + Self { app } } fn world(&self) -> &World { @@ -30,7 +29,9 @@ impl ApplicationHandler for WindowState { let window_config = self.world().get_resource::().unwrap(); let window = event_loop.create_window(window_config.into()).unwrap(); - self.window = Some(window); + self.app + .world_mut() + .insert_resource(WindowWrapper(Arc::new(window))); } fn window_event(&mut self, event_loop: &ActiveEventLoop, _id: WindowId, event: WindowEvent) { From 8b0c59f7c0682a0391a9527033e56174640dc57f Mon Sep 17 00:00:00 2001 From: Florian RICHER Date: Sun, 13 Apr 2025 20:05:17 +0200 Subject: [PATCH 014/105] Remove useless schedule (for now) --- src/core/app/app.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/core/app/app.rs b/src/core/app/app.rs index 21ebd28..3821bfc 100644 --- a/src/core/app/app.rs +++ b/src/core/app/app.rs @@ -1,6 +1,6 @@ use std::error::Error; -use bevy_ecs::{schedule::Schedules, world::World}; +use bevy_ecs::world::World; pub enum AppExit { Success, @@ -24,11 +24,8 @@ pub struct App { impl Default for App { fn default() -> Self { - let mut world = World::new(); - world.init_resource::(); - Self { - world, + world: World::new(), runner: None, } } From a295093c97252f86c86aa624f225ab98eb1c9443 Mon Sep 17 00:00:00 2001 From: Florian RICHER Date: Thu, 24 Apr 2025 13:05:38 +0200 Subject: [PATCH 015/105] Use bevy_app instead --- Cargo.lock | 63 ++++++++++++++++++++++++ Cargo.toml | 5 +- src/core/app/app.rs | 56 --------------------- src/core/app/mod.rs | 2 - src/core/mod.rs | 1 - src/core/vulkan/mod.rs | 2 +- src/core/vulkan/vulkan_context.rs | 3 +- src/core/vulkan/window_render_context.rs | 2 +- src/core/window/mod.rs | 8 +-- src/core/window/state.rs | 3 +- src/game/mod.rs | 3 +- src/main.rs | 29 +++++++---- 12 files changed, 98 insertions(+), 79 deletions(-) delete mode 100644 src/core/app/app.rs delete mode 100644 src/core/app/mod.rs diff --git a/Cargo.lock b/Cargo.lock index da7454f..9e91469 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -199,6 +199,36 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +[[package]] +name = "bevy_app" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0ac033a388b8699d241499a43783a09e6a3bab2430f1297c6bd4974095efb3f" +dependencies = [ + "bevy_derive", + "bevy_ecs", + "bevy_reflect", + "bevy_tasks", + "bevy_utils", + "console_error_panic_hook", + "ctrlc", + "derive_more", + "downcast-rs", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "bevy_derive" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57d94761ce947b0a2402fd949fe1e7a5b1535293130ba4cd9893be6295d4680a" +dependencies = [ + "bevy_macro_utils", + "quote", + "syn", +] + [[package]] name = "bevy_ecs" version = "0.15.3" @@ -462,6 +492,16 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "console_error_panic_hook" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" +dependencies = [ + "cfg-if", + "wasm-bindgen", +] + [[package]] name = "const-random" version = "0.1.18" @@ -543,6 +583,16 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" +[[package]] +name = "ctrlc" +version = "3.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "697b5419f348fd5ae2478e8018cb016c00a5881c7f46c717de98ffd135a5651c" +dependencies = [ + "nix", + "windows-sys 0.59.0", +] + [[package]] name = "cursor-icon" version = "1.1.0" @@ -993,6 +1043,18 @@ dependencies = [ "jni-sys", ] +[[package]] +name = "nix" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +dependencies = [ + "bitflags 2.9.0", + "cfg-if", + "cfg_aliases", + "libc", +] + [[package]] name = "nom" version = "7.1.3" @@ -1533,6 +1595,7 @@ name = "rust_vulkan_test" version = "0.1.0" dependencies = [ "anyhow", + "bevy_app", "bevy_ecs", "env_logger", "glam", diff --git a/Cargo.toml b/Cargo.toml index a84f681..4542e45 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,8 +17,9 @@ vulkano-shaders = "0.35" glam = { version = "0.30" } # ECS -bevy_ecs = "0.15.3" +bevy_ecs = "0.15" +bevy_app = "0.15" # Log and tracing log = "0.4" -env_logger = "0.11.5" +env_logger = "0.11" diff --git a/src/core/app/app.rs b/src/core/app/app.rs deleted file mode 100644 index 3821bfc..0000000 --- a/src/core/app/app.rs +++ /dev/null @@ -1,56 +0,0 @@ -use std::error::Error; - -use bevy_ecs::world::World; - -pub enum AppExit { - Success, - Error(Box), -} - -pub type RunnerFn = Box AppExit>; - -#[derive(Debug, thiserror::Error)] -pub enum AppError { - #[error("Runner is not set")] - RunnerNotSet, - #[error("Runner returned an error : {0}")] - RunnerError(Box), -} - -pub struct App { - world: World, - runner: Option, -} - -impl Default for App { - fn default() -> Self { - Self { - world: World::new(), - runner: None, - } - } -} - -impl App { - pub fn world_mut(&mut self) -> &mut World { - &mut self.world - } - - pub fn world(&self) -> &World { - &self.world - } - - pub fn run(mut self) -> Result<(), AppError> { - match self.runner.take() { - Some(runner) => match runner(self) { - AppExit::Success => Ok(()), - AppExit::Error(e) => Err(AppError::RunnerError(e)), - }, - None => Err(AppError::RunnerNotSet), - } - } - - pub fn set_runner(&mut self, runner: RunnerFn) { - self.runner = Some(runner); - } -} diff --git a/src/core/app/mod.rs b/src/core/app/mod.rs deleted file mode 100644 index c6c8a20..0000000 --- a/src/core/app/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -mod app; -pub use app::*; diff --git a/src/core/mod.rs b/src/core/mod.rs index abaeeae..db2061f 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -1,4 +1,3 @@ -pub mod app; pub mod camera; pub mod render; pub mod vulkan; diff --git a/src/core/vulkan/mod.rs b/src/core/vulkan/mod.rs index fe22fb3..29fe224 100644 --- a/src/core/vulkan/mod.rs +++ b/src/core/vulkan/mod.rs @@ -1,7 +1,7 @@ use vulkan_context::VulkanContext; use window_render_context::WindowRenderContext; -use super::app::App; +use bevy_app::App; mod utils; mod vulkan_context; diff --git a/src/core/vulkan/vulkan_context.rs b/src/core/vulkan/vulkan_context.rs index f59d9a3..b8be6ca 100644 --- a/src/core/vulkan/vulkan_context.rs +++ b/src/core/vulkan/vulkan_context.rs @@ -1,5 +1,6 @@ use std::{any::Any, sync::Arc}; +use bevy_app::App; use bevy_ecs::system::Resource; use vulkano::{ command_buffer::{ @@ -14,7 +15,7 @@ use vulkano::{ }; use winit::raw_window_handle::{HasDisplayHandle, HasWindowHandle}; -use crate::core::{app::App, window::raw_handle::DisplayHandleWrapper}; +use crate::core::window::raw_handle::DisplayHandleWrapper; use super::utils; diff --git a/src/core/vulkan/window_render_context.rs b/src/core/vulkan/window_render_context.rs index 1c653a7..5cb2618 100644 --- a/src/core/vulkan/window_render_context.rs +++ b/src/core/vulkan/window_render_context.rs @@ -1,3 +1,4 @@ +use bevy_app::App; use bevy_ecs::system::Resource; use std::sync::Arc; use vulkano::image::view::ImageView; @@ -8,7 +9,6 @@ use vulkano::sync::{self, GpuFuture}; use vulkano::{Validated, VulkanError}; use winit::window::Window; -use crate::core::app::App; use crate::core::window::raw_handle::WindowWrapper; use super::vulkan_context::VulkanContext; diff --git a/src/core/window/mod.rs b/src/core/window/mod.rs index 4368310..d1623b7 100644 --- a/src/core/window/mod.rs +++ b/src/core/window/mod.rs @@ -1,10 +1,9 @@ +use bevy_app::{App, AppExit}; use config::WindowConfig; use raw_handle::{DisplayHandleWrapper, EventLoopProxyWrapper}; use state::WindowState; use winit::event_loop::EventLoop; -use super::app::{App, AppExit}; - pub mod config; pub mod raw_handle; pub mod state; @@ -43,6 +42,9 @@ fn runner(mut app: App, event_loop: EventLoop<()>) -> AppExit { match event_loop.run_app(&mut window_state) { Ok(_) => AppExit::Success, - Err(e) => AppExit::Error(Box::new(e)), + Err(e) => { + log::error!("Error running window state: {e}"); + AppExit::error() + } } } diff --git a/src/core/window/state.rs b/src/core/window/state.rs index 0b0966e..8ffd44b 100644 --- a/src/core/window/state.rs +++ b/src/core/window/state.rs @@ -1,13 +1,12 @@ use std::sync::Arc; +use bevy_app::App; use bevy_ecs::world::World; use winit::{ application::ApplicationHandler, event::WindowEvent, event_loop::ActiveEventLoop, window::WindowId, }; -use crate::core::app::App; - use super::{config::WindowConfig, raw_handle::WindowWrapper}; pub struct WindowState { diff --git a/src/game/mod.rs b/src/game/mod.rs index 0b7787d..6eb9cab 100644 --- a/src/game/mod.rs +++ b/src/game/mod.rs @@ -1,5 +1,6 @@ +use bevy_app::App; + use crate::core::{ - app::App, vulkan::Vulkan, window::{Window, config::WindowConfig}, }; diff --git a/src/main.rs b/src/main.rs index 8297797..34fad4b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,29 +1,40 @@ -use std::error::Error; use winit::event_loop::{ControlFlow, EventLoop}; +use bevy_app::{App, AppExit}; + pub mod core; pub mod game; pub mod old_app; -fn main() -> Result<(), impl Error> { +fn main() { env_logger::init(); - run_new_app() - // run_old_app() + run_new_app(); + // run_old_app(); } -fn run_new_app() -> Result<(), impl Error> { - let mut app = core::app::App::default(); +fn run_new_app() { + let mut app = App::default(); game::init(&mut app); - app.run() + match app.run() { + AppExit::Success => {} + AppExit::Error(e) => { + log::error!("Error running new app: {e}"); + } + } } -fn run_old_app() -> Result<(), impl Error> { +fn run_old_app() { let event_loop = EventLoop::new().unwrap(); event_loop.set_control_flow(ControlFlow::Poll); let vulkan_context = old_app::vulkan_context::VulkanContext::from(&event_loop); let mut app = old_app::app::App::from(vulkan_context); - event_loop.run_app(&mut app) + match event_loop.run_app(&mut app) { + Ok(_) => {} + Err(e) => { + log::error!("Error running old app: {e}"); + } + } } From 285b1942803429e675ece2eb8144bf2b7e858fac Mon Sep 17 00:00:00 2001 From: Florian RICHER Date: Sun, 27 Apr 2025 17:16:50 +0200 Subject: [PATCH 016/105] Surface: Add required extensions --- src/core/vulkan/vulkan_context.rs | 1 + src/old_app/vulkan_context.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/src/core/vulkan/vulkan_context.rs b/src/core/vulkan/vulkan_context.rs index b8be6ca..74df916 100644 --- a/src/core/vulkan/vulkan_context.rs +++ b/src/core/vulkan/vulkan_context.rs @@ -58,6 +58,7 @@ impl From<&App> for VulkanContext { world.get_resource::().unwrap(); let enabled_extensions = Surface::required_extensions(&display_handle.0).unwrap(); + log::debug!("Surface required extensions: {enabled_extensions:?}"); let instance = utils::create_instance(library.clone(), enabled_extensions); diff --git a/src/old_app/vulkan_context.rs b/src/old_app/vulkan_context.rs index bad47de..332dbcf 100644 --- a/src/old_app/vulkan_context.rs +++ b/src/old_app/vulkan_context.rs @@ -36,6 +36,7 @@ impl From<&EventLoop<()>> for VulkanContext { let library = load_library(); let enabled_extensions = Surface::required_extensions(event_loop).unwrap(); + log::debug!("Surface required extensions: {enabled_extensions:?}"); let instance = create_instance(library.clone(), enabled_extensions); From dda368e8023506a090e62e59ee6378bf3e6880da Mon Sep 17 00:00:00 2001 From: Florian RICHER Date: Fri, 16 May 2025 13:47:24 +0200 Subject: [PATCH 017/105] Update all dependencies --- Cargo.lock | 689 ++++++++++++++--------- Cargo.toml | 4 +- flake.lock | 12 +- flake.nix | 10 +- rust-toolchain.toml | 2 +- src/core/vulkan/vulkan_context.rs | 2 +- src/core/vulkan/window_render_context.rs | 2 +- src/core/window/config.rs | 2 +- src/core/window/raw_handle.rs | 2 +- 9 files changed, 426 insertions(+), 299 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9e91469..e9d2554 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -20,12 +20,11 @@ checksum = "c71b1793ee61086797f5c80b6efa2b8ffa6d5dd703f118545808a7f2e27f7046" [[package]] name = "ahash" -version = "0.8.11" +version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" dependencies = [ "cfg-if", - "const-random", "getrandom", "once_cell", "version_check", @@ -41,12 +40,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "allocator-api2" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" - [[package]] name = "android-activity" version = "0.6.0" @@ -54,7 +47,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef6978589202a00cd7e118380c448a08b6ed394c3a8df3a430d0898e3a42d046" dependencies = [ "android-properties", - "bitflags 2.9.0", + "bitflags 2.9.1", "cc", "cesu8", "jni", @@ -126,9 +119,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.97" +version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f" +checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" [[package]] name = "arrayref" @@ -170,14 +163,15 @@ dependencies = [ [[package]] name = "async-executor" -version = "1.13.1" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30ca9a001c1e8ba5149f91a74362376cc6bc5b919d92d988668657bd570bdcec" +checksum = "bb812ffb58524bdd10860d7d974e2f01cc0950c2438a74ee5ec2e2280c6c4ffa" dependencies = [ "async-task", "concurrent-queue", "fastrand", "futures-lite", + "pin-project-lite", "slab", ] @@ -186,12 +180,18 @@ name = "async-task" version = "4.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" +dependencies = [ + "portable-atomic", +] [[package]] name = "atomic-waker" version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" +dependencies = [ + "portable-atomic", +] [[package]] name = "autocfg" @@ -201,28 +201,29 @@ checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "bevy_app" -version = "0.15.3" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0ac033a388b8699d241499a43783a09e6a3bab2430f1297c6bd4974095efb3f" +checksum = "a2b6267ac23a9947d5b2725ff047a1e1add70076d85fa9fb73d044ab9bea1f3c" dependencies = [ "bevy_derive", "bevy_ecs", + "bevy_platform", "bevy_reflect", "bevy_tasks", "bevy_utils", - "console_error_panic_hook", + "cfg-if", "ctrlc", - "derive_more", - "downcast-rs", - "wasm-bindgen", - "web-sys", + "downcast-rs 2.0.1", + "log", + "thiserror 2.0.12", + "variadics_please", ] [[package]] name = "bevy_derive" -version = "0.15.3" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57d94761ce947b0a2402fd949fe1e7a5b1535293130ba4cd9893be6295d4680a" +checksum = "f626531b9c05c25a758ede228727bd11c2c2c8498ecbed9925044386d525a2a3" dependencies = [ "bevy_macro_utils", "quote", @@ -231,30 +232,37 @@ dependencies = [ [[package]] name = "bevy_ecs" -version = "0.15.3" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1597106cc01e62e6217ccb662e0748b2ce330893f27c7dc17bac33e0bb99bca9" +checksum = "d9e807b5d9aab3bb8dfe47e7a44c9ff088bad2ceefe299b80ac77609a87fe9d4" dependencies = [ + "arrayvec", "bevy_ecs_macros", + "bevy_platform", "bevy_ptr", "bevy_reflect", "bevy_tasks", "bevy_utils", - "bitflags 2.9.0", + "bitflags 2.9.1", + "bumpalo", "concurrent-queue", "derive_more", "disqualified", - "fixedbitset 0.5.7", + "fixedbitset", + "indexmap", + "log", "nonmax", - "petgraph", + "serde", "smallvec", + "thiserror 2.0.12", + "variadics_please", ] [[package]] name = "bevy_ecs_macros" -version = "0.15.3" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f453adf07712b39826bc5845e5b0887ce03204ee8359bbe6b40a9afda60564a1" +checksum = "467d7bb98aeb8dd30f36e6a773000c12a891d4f1bee2adc3841ec89cc8eaf54e" dependencies = [ "bevy_macro_utils", "proc-macro2", @@ -264,10 +272,11 @@ dependencies = [ [[package]] name = "bevy_macro_utils" -version = "0.15.3" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bb6ded1ddc124ea214f6a2140e47a78d1fe79b0638dad39419cdeef2e1133f1" +checksum = "7a2473db70d8785b5c75d6dd951a2e51e9be2c2311122db9692c79c9d887517b" dependencies = [ + "parking_lot", "proc-macro2", "quote", "syn", @@ -275,34 +284,58 @@ dependencies = [ ] [[package]] -name = "bevy_ptr" -version = "0.15.3" +name = "bevy_platform" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89fe0b0b919146939481a3a7c38864face2c6d0fd2c73ab3d430dc693ecd9b11" +checksum = "704db2c11b7bc31093df4fbbdd3769f9606a6a5287149f4b51f2680f25834ebc" +dependencies = [ + "cfg-if", + "critical-section", + "foldhash", + "hashbrown", + "portable-atomic", + "portable-atomic-util", + "serde", + "spin", +] + +[[package]] +name = "bevy_ptr" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86f1275dfb4cfef4ffc90c3fa75408964864facf833acc932413d52aa5364ba4" [[package]] name = "bevy_reflect" -version = "0.15.3" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ddbca0a39e88eff2c301dc794ee9d73a53f4b08d47b2c9b5a6aac182fae6217" +checksum = "607ebacc31029cf2f39ac330eabf1d4bc411b159528ec08dbe6b0593eaccfd41" dependencies = [ "assert_type_match", + "bevy_platform", "bevy_ptr", "bevy_reflect_derive", "bevy_utils", "derive_more", "disqualified", - "downcast-rs", + "downcast-rs 2.0.1", "erased-serde", + "foldhash", + "glam 0.29.3", "serde", "smallvec", + "smol_str", + "thiserror 2.0.12", + "uuid", + "variadics_please", + "wgpu-types", ] [[package]] name = "bevy_reflect_derive" -version = "0.15.3" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d62affb769db17d34ad0b75ff27eca94867e2acc8ea350c5eca97d102bd98709" +checksum = "cf35e45e4eb239018369f63f2adc2107a54c329f9276d020e01eee1625b0238b" dependencies = [ "bevy_macro_utils", "proc-macro2", @@ -313,41 +346,29 @@ dependencies = [ [[package]] name = "bevy_tasks" -version = "0.15.3" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "028630ddc355563bd567df1076db3515858aa26715ddf7467d2086f9b40e5ab1" +checksum = "444c450b65e108855f42ecb6db0c041a56ea7d7f10cc6222f0ca95e9536a7d19" dependencies = [ "async-executor", - "futures-channel", + "async-task", + "atomic-waker", + "bevy_platform", + "cfg-if", + "crossbeam-queue", + "derive_more", "futures-lite", - "pin-project", - "wasm-bindgen-futures", + "heapless", ] [[package]] name = "bevy_utils" -version = "0.15.3" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63c2174d43a0de99f863c98a472370047a2bfa7d1e5cec8d9d647fb500905d9d" +checksum = "ac2da3b3c1f94dadefcbe837aaa4aa119fcea37f7bdc5307eb05b4ede1921e24" dependencies = [ - "ahash", - "bevy_utils_proc_macros", - "getrandom", - "hashbrown 0.14.5", + "bevy_platform", "thread_local", - "tracing", - "web-time", -] - -[[package]] -name = "bevy_utils_proc_macros" -version = "0.15.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94847541f6dd2e28f54a9c2b0e857da5f2631e2201ebc25ce68781cdcb721391" -dependencies = [ - "proc-macro2", - "quote", - "syn", ] [[package]] @@ -358,9 +379,12 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.9.0" +version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" +dependencies = [ + "serde", +] [[package]] name = "block2" @@ -379,24 +403,30 @@ checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" [[package]] name = "bytemuck" -version = "1.22.0" +version = "1.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6b1fc10dbac614ebc03540c9dbd60e83887fda27794998c6528f1782047d540" +checksum = "9134a6ef01ce4b366b50689c94f82c14bc72bc5d0386829828a2e2752ef7958c" dependencies = [ "bytemuck_derive", ] [[package]] name = "bytemuck_derive" -version = "1.8.1" +version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fa76293b4f7bb636ab88fd78228235b5248b4d05cc589aed610f954af5d7c7a" +checksum = "7ecc273b49b3205b83d648f0690daa588925572cc5063745bfe547fe7ec8e1a1" dependencies = [ "proc-macro2", "quote", "syn", ] +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "bytes" version = "1.10.1" @@ -409,7 +439,7 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "log", "polling", "rustix", @@ -431,9 +461,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.16" +version = "1.2.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be714c154be609ec7f5dad223a33bf1482fff90472de28f7362806e6d4832b8c" +checksum = "5f4ac86a9e5bc1e2b3449ab9d7d3a6a405e3d1bb28d7b9be8614f55846ae3766" dependencies = [ "jobserver", "libc", @@ -490,36 +520,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" dependencies = [ "crossbeam-utils", -] - -[[package]] -name = "console_error_panic_hook" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" -dependencies = [ - "cfg-if", - "wasm-bindgen", -] - -[[package]] -name = "const-random" -version = "0.1.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359" -dependencies = [ - "const-random-macro", -] - -[[package]] -name = "const-random-macro" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" -dependencies = [ - "getrandom", - "once_cell", - "tiny-keccak", + "portable-atomic", ] [[package]] @@ -562,6 +563,12 @@ dependencies = [ "libc", ] +[[package]] +name = "critical-section" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + [[package]] name = "crossbeam-queue" version = "0.3.12" @@ -585,9 +592,9 @@ checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" [[package]] name = "ctrlc" -version = "3.4.6" +version = "3.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "697b5419f348fd5ae2478e8018cb016c00a5881c7f46c717de98ffd135a5651c" +checksum = "46f93780a459b7d656ef7f071fe699c4d3d2cb201c4b24d085b6ddc505276e73" dependencies = [ "nix", "windows-sys 0.59.0", @@ -626,6 +633,16 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" +[[package]] +name = "dispatch2" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec" +dependencies = [ + "bitflags 2.9.1", + "objc2 0.6.1", +] + [[package]] name = "disqualified" version = "1.0.0" @@ -648,10 +665,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" [[package]] -name = "dpi" -version = "0.1.1" +name = "downcast-rs" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f25c0e292a7ca6d6498557ff1df68f32c99850012b6ea401cf8daf771f22ff53" +checksum = "ea8a8b81cacc08888170eef4d13b775126db426d0b348bee9d18c2c1eaf123cf" + +[[package]] +name = "dpi" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" [[package]] name = "env_filter" @@ -665,9 +688,9 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.11.7" +version = "0.11.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3716d7a920fb4fac5d84e9d4bce8ceb321e9414b4409da61b07b75c1e3d0697" +checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f" dependencies = [ "anstream", "anstyle", @@ -694,9 +717,9 @@ dependencies = [ [[package]] name = "errno" -version = "0.3.10" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" +checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18" dependencies = [ "libc", "windows-sys 0.59.0", @@ -708,12 +731,6 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" -[[package]] -name = "fixedbitset" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" - [[package]] name = "fixedbitset" version = "0.5.7" @@ -722,9 +739,9 @@ checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" [[package]] name = "foldhash" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" [[package]] name = "foreign-types" @@ -753,15 +770,6 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" -[[package]] -name = "futures-channel" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" -dependencies = [ - "futures-core", -] - [[package]] name = "futures-core" version = "0.3.31" @@ -799,28 +807,36 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.15" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" dependencies = [ "cfg-if", - "js-sys", "libc", + "r-efi", "wasi", - "wasm-bindgen", ] [[package]] name = "glam" -version = "0.30.0" +version = "0.29.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17fcdf9683c406c2fc4d124afd29c0d595e22210d633cbdb8695ba9935ab1dc6" +checksum = "8babf46d4c1c9d92deac9f7be466f76dfc4482b6452fc5024b5e8daf6ffeb3ee" +dependencies = [ + "serde", +] + +[[package]] +name = "glam" +version = "0.30.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b46b9ca4690308844c644e7c634d68792467260e051c8543e0c7871662b3ba7" [[package]] name = "half" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7db2ff139bba50379da6aa0766b52fdcb62cb5b263009b09ed58ba604e14bbd1" +checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9" dependencies = [ "bytemuck", "cfg-if", @@ -828,21 +844,34 @@ dependencies = [ ] [[package]] -name = "hashbrown" -version = "0.14.5" +name = "hash32" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" dependencies = [ - "ahash", - "allocator-api2", - "serde", + "byteorder", ] [[package]] name = "hashbrown" -version = "0.15.2" +version = "0.15.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3" +dependencies = [ + "equivalent", + "serde", +] + +[[package]] +name = "heapless" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" +dependencies = [ + "hash32", + "portable-atomic", + "stable_deref_trait", +] [[package]] name = "heck" @@ -858,12 +887,12 @@ checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" [[package]] name = "indexmap" -version = "2.8.0" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3954d50fe15b02142bf25d3b8bdadb634ec3948f103d04ffe3031bc8fe9d7058" +checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" dependencies = [ "equivalent", - "hashbrown 0.15.2", + "hashbrown", ] [[package]] @@ -880,9 +909,9 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "jiff" -version = "0.2.4" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d699bc6dfc879fb1bf9bdff0d4c56f0884fc6f0d0eb0fba397a6d00cd9a6b85e" +checksum = "f02000660d30638906021176af16b17498bd0d12813dbfe7b276d8bc7f3c0806" dependencies = [ "jiff-static", "log", @@ -893,9 +922,9 @@ dependencies = [ [[package]] name = "jiff-static" -version = "0.2.4" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d16e75759ee0aa64c57a56acbf43916987b20c77373cb7e808979e02b93c9f9" +checksum = "f3c30758ddd7188629c6713fc45d1188af4f44c90582311d0c8d8c9907f60c48" dependencies = [ "proc-macro2", "quote", @@ -926,10 +955,11 @@ checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" [[package]] name = "jobserver" -version = "0.1.32" +version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" +checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" dependencies = [ + "getrandom", "libc", ] @@ -945,18 +975,18 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.171" +version = "0.2.172" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" +checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" [[package]] name = "libloading" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" +checksum = "6a793df0d7afeac54f95b471d3af7f0d4fb975699f972341a4b76988d49cdf0c" dependencies = [ "cfg-if", - "windows-targets 0.52.6", + "windows-targets 0.53.0", ] [[package]] @@ -965,9 +995,9 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "libc", - "redox_syscall 0.5.10", + "redox_syscall 0.5.12", ] [[package]] @@ -988,9 +1018,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.26" +version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" [[package]] name = "memchr" @@ -1019,7 +1049,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "jni-sys", "log", "ndk-sys", @@ -1045,11 +1075,11 @@ dependencies = [ [[package]] name = "nix" -version = "0.29.0" +version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "cfg-if", "cfg_aliases", "libc", @@ -1110,9 +1140,9 @@ dependencies = [ [[package]] name = "objc2" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3531f65190d9cff863b77a99857e74c314dd16bf56c538c4b57c7cbc3f3a6e59" +checksum = "88c6597e14493ab2e44ce58f2fdecf095a51f12ca57bec060a11c57332520551" dependencies = [ "objc2-encode", ] @@ -1123,7 +1153,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "block2", "libc", "objc2 0.5.2", @@ -1139,7 +1169,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "block2", "objc2 0.5.2", "objc2-core-location", @@ -1163,7 +1193,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "block2", "objc2 0.5.2", "objc2-foundation 0.2.2", @@ -1171,12 +1201,13 @@ dependencies = [ [[package]] name = "objc2-core-foundation" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daeaf60f25471d26948a1c2f840e3f7d86f4109e3af4e8e4b5cd70c39690d925" +checksum = "1c10c2894a6fed806ade6027bcd50662746363a9589d3ec9d9bef30a4e4bc166" dependencies = [ - "bitflags 2.9.0", - "objc2 0.6.0", + "bitflags 2.9.1", + "dispatch2", + "objc2 0.6.1", ] [[package]] @@ -1215,7 +1246,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "block2", "dispatch", "libc", @@ -1224,12 +1255,12 @@ dependencies = [ [[package]] name = "objc2-foundation" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a21c6c9014b82c39515db5b396f91645182611c97d24637cf56ac01e5f8d998" +checksum = "900831247d2fe1a09a683278e5384cfb8c80c79fe6b166f9d14bfdde0ea1b03c" dependencies = [ - "bitflags 2.9.0", - "objc2 0.6.0", + "bitflags 2.9.1", + "objc2 0.6.1", "objc2-core-foundation", ] @@ -1251,7 +1282,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "block2", "objc2 0.5.2", "objc2-foundation 0.2.2", @@ -1259,13 +1290,13 @@ dependencies = [ [[package]] name = "objc2-metal" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01c41bc8b0e50ea7a5304a56f25e0066f526e99641b46fd7b9ad4421dd35bff6" +checksum = "7f246c183239540aab1782457b35ab2040d4259175bd1d0c58e46ada7b47a874" dependencies = [ - "bitflags 2.9.0", - "objc2 0.6.0", - "objc2-foundation 0.3.0", + "bitflags 2.9.1", + "objc2 0.6.1", + "objc2-foundation 0.3.1", ] [[package]] @@ -1274,7 +1305,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "block2", "objc2 0.5.2", "objc2-foundation 0.2.2", @@ -1283,15 +1314,15 @@ dependencies = [ [[package]] name = "objc2-quartz-core" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fb3794501bb1bee12f08dcad8c61f2a5875791ad1c6f47faa71a0f033f20071" +checksum = "90ffb6a0cd5f182dc964334388560b12a57f7b74b3e2dec5e2722aa2dfb2ccd5" dependencies = [ - "bitflags 2.9.0", - "objc2 0.6.0", + "bitflags 2.9.1", + "objc2 0.6.1", "objc2-core-foundation", - "objc2-foundation 0.3.0", - "objc2-metal 0.3.0", + "objc2-foundation 0.3.1", + "objc2-metal 0.3.1", ] [[package]] @@ -1310,7 +1341,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "block2", "objc2 0.5.2", "objc2-cloud-kit", @@ -1342,7 +1373,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "block2", "objc2 0.5.2", "objc2-core-location", @@ -1351,9 +1382,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.21.1" +version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d75b0bedcc4fe52caa0e03d9f1151a323e4aa5e2d78ba3580400cd3c9e2bc4bc" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "orbclient" @@ -1397,7 +1428,7 @@ checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.10", + "redox_syscall 0.5.12", "smallvec", "windows-targets 0.52.6", ] @@ -1408,16 +1439,6 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" -[[package]] -name = "petgraph" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" -dependencies = [ - "fixedbitset 0.4.2", - "indexmap", -] - [[package]] name = "pin-project" version = "1.1.10" @@ -1491,18 +1512,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.94" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" dependencies = [ "unicode-ident", ] [[package]] name = "quick-xml" -version = "0.37.2" +version = "0.37.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "165859e9e55f79d67b96c5d96f4e88b6f2695a1972849c15a6a3f5c59fc2c003" +checksum = "331e97a1af0bf59823e6eadffe373d7b27f485be8748f71471c662c1f269b7fb" dependencies = [ "memchr", ] @@ -1516,6 +1537,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" + [[package]] name = "raw-window-handle" version = "0.6.2" @@ -1528,10 +1555,10 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40d213455a5f1dc59214213c7330e074ddf8114c9a42411eb890c767357ce135" dependencies = [ - "objc2 0.6.0", + "objc2 0.6.1", "objc2-core-foundation", - "objc2-foundation 0.3.0", - "objc2-quartz-core 0.3.0", + "objc2-foundation 0.3.1", + "objc2-quartz-core 0.3.1", ] [[package]] @@ -1545,11 +1572,11 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.10" +version = "0.5.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b8c0c260b63a8219631167be35e6a988e9554dbd323f8bd08439c8ed1302bd1" +checksum = "928fca9cf2aa042393a8325b9ead81d2f0df4cb12e1e24cef072922ccd99c5af" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", ] [[package]] @@ -1598,7 +1625,7 @@ dependencies = [ "bevy_app", "bevy_ecs", "env_logger", - "glam", + "glam 0.30.3", "log", "thiserror 2.0.12", "vulkano", @@ -1612,7 +1639,7 @@ version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "errno", "libc", "linux-raw-sys", @@ -1741,9 +1768,9 @@ checksum = "9db491c0d4152a069911a0fbdaca959691bf0b9d7110d98a7ed1c8e59b79ab30" [[package]] name = "smallvec" -version = "1.14.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd" +checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" [[package]] name = "smithay-client-toolkit" @@ -1751,7 +1778,7 @@ version = "0.19.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "calloop", "calloop-wayland-source", "cursor-icon", @@ -1779,6 +1806,21 @@ dependencies = [ "serde", ] +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + [[package]] name = "strict-num" version = "0.1.1" @@ -1787,9 +1829,9 @@ checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" [[package]] name = "syn" -version = "2.0.100" +version = "2.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" +checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" dependencies = [ "proc-macro2", "quote", @@ -1846,15 +1888,6 @@ dependencies = [ "once_cell", ] -[[package]] -name = "tiny-keccak" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" -dependencies = [ - "crunchy", -] - [[package]] name = "tiny-skia" version = "0.11.4" @@ -1882,15 +1915,15 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.8" +version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +checksum = "3da5db5a963e24bc68be8b17b6fa82814bb22ee8660f192bb182771d498f09a3" [[package]] name = "toml_edit" -version = "0.22.24" +version = "0.22.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474" +checksum = "310068873db2c5b3e7659d2cc35d21855dbafa50d1ce336397c666e3cb08137e" dependencies = [ "indexmap", "toml_datetime", @@ -1912,9 +1945,6 @@ name = "tracing-core" version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" -dependencies = [ - "once_cell", -] [[package]] name = "ttf-parser" @@ -1954,11 +1984,25 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.12.1" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3758f5e68192bb96cc8f9b7e2c2cfdabb435499a28499a42f8f984092adad4b" +checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9" dependencies = [ "getrandom", + "js-sys", + "serde", + "wasm-bindgen", +] + +[[package]] +name = "variadics_please" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41b6d82be61465f97d42bd1d15bf20f3b0a3a0905018f38f9d6f6962055b0b5c" +dependencies = [ + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -2047,9 +2091,12 @@ dependencies = [ [[package]] name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" +version = "0.14.2+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt", +] [[package]] name = "wasm-bindgen" @@ -2124,12 +2171,12 @@ dependencies = [ [[package]] name = "wayland-backend" -version = "0.3.8" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7208998eaa3870dad37ec8836979581506e0c5c64c20c9e79e9d2a10d6f47bf" +checksum = "fe770181423e5fc79d3e2a7f4410b7799d5aab1de4372853de3c6aa13ca24121" dependencies = [ "cc", - "downcast-rs", + "downcast-rs 1.2.1", "rustix", "scoped-tls", "smallvec", @@ -2138,11 +2185,11 @@ dependencies = [ [[package]] name = "wayland-client" -version = "0.31.8" +version = "0.31.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2120de3d33638aaef5b9f4472bff75f07c56379cf76ea320bd3a3d65ecaf73f" +checksum = "978fa7c67b0847dbd6a9f350ca2569174974cd4082737054dbb7fbb79d7d9a61" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "rustix", "wayland-backend", "wayland-scanner", @@ -2154,16 +2201,16 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "cursor-icon", "wayland-backend", ] [[package]] name = "wayland-cursor" -version = "0.31.8" +version = "0.31.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a93029cbb6650748881a00e4922b076092a6a08c11e7fbdb923f064b23968c5d" +checksum = "a65317158dec28d00416cb16705934070aef4f8393353d41126c54264ae0f182" dependencies = [ "rustix", "wayland-client", @@ -2172,11 +2219,11 @@ dependencies = [ [[package]] name = "wayland-protocols" -version = "0.32.6" +version = "0.32.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0781cf46869b37e36928f7b432273c0995aa8aed9552c556fb18754420541efc" +checksum = "779075454e1e9a521794fed15886323ea0feda3f8b0fc1390f5398141310422a" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "wayland-backend", "wayland-client", "wayland-scanner", @@ -2184,11 +2231,11 @@ dependencies = [ [[package]] name = "wayland-protocols-plasma" -version = "0.3.6" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ccaacc76703fefd6763022ac565b590fcade92202492381c95b2edfdf7d46b3" +checksum = "4fd38cdad69b56ace413c6bcc1fbf5acc5e2ef4af9d5f8f1f9570c0c83eae175" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "wayland-backend", "wayland-client", "wayland-protocols", @@ -2197,11 +2244,11 @@ dependencies = [ [[package]] name = "wayland-protocols-wlr" -version = "0.3.6" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "248a02e6f595aad796561fa82d25601bd2c8c3b145b1c7453fc8f94c1a58f8b2" +checksum = "1cb6cdc73399c0e06504c437fe3cf886f25568dd5454473d565085b36d6a8bbf" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "wayland-backend", "wayland-client", "wayland-protocols", @@ -2251,6 +2298,19 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "wgpu-types" +version = "24.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50ac044c0e76c03a0378e7786ac505d010a873665e2d51383dcff8dd227dc69c" +dependencies = [ + "bitflags 2.9.1", + "js-sys", + "log", + "serde", + "web-sys", +] + [[package]] name = "winapi-util" version = "0.1.9" @@ -2326,13 +2386,29 @@ dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm", + "windows_i686_gnullvm 0.52.6", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", "windows_x86_64_msvc 0.52.6", ] +[[package]] +name = "windows-targets" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1e4c7e8ceaaf9cb7d7507c974735728ab453b67ef8f18febdd7c11fe59dca8b" +dependencies = [ + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.42.2" @@ -2351,6 +2427,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + [[package]] name = "windows_aarch64_msvc" version = "0.42.2" @@ -2369,6 +2451,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + [[package]] name = "windows_i686_gnu" version = "0.42.2" @@ -2387,12 +2475,24 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + [[package]] name = "windows_i686_msvc" version = "0.42.2" @@ -2411,6 +2511,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + [[package]] name = "windows_x86_64_gnu" version = "0.42.2" @@ -2429,6 +2535,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + [[package]] name = "windows_x86_64_gnullvm" version = "0.42.2" @@ -2447,6 +2559,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + [[package]] name = "windows_x86_64_msvc" version = "0.42.2" @@ -2466,15 +2584,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] -name = "winit" -version = "0.30.9" +name = "windows_x86_64_msvc" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a809eacf18c8eca8b6635091543f02a5a06ddf3dad846398795460e6e0ae3cc0" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + +[[package]] +name = "winit" +version = "0.30.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0d05bd8908e14618c9609471db04007e644fd9cce6529756046cfc577f9155e" dependencies = [ "ahash", "android-activity", "atomic-waker", - "bitflags 2.9.0", + "bitflags 2.9.1", "block2", "bytemuck", "calloop", @@ -2519,13 +2643,22 @@ dependencies = [ [[package]] name = "winnow" -version = "0.7.4" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e97b544156e9bebe1a0ffbc03484fc1ffe3100cbce3ffb17eac35f7cdd7ab36" +checksum = "c06928c8748d81b05c9be96aad92e1b6ff01833332f281e8cfca3be4b35fc9ec" dependencies = [ "memchr", ] +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags 2.9.1", +] + [[package]] name = "x11-dl" version = "2.21.0" @@ -2570,7 +2703,7 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d039de8032a9a8856a6be89cea3e5d12fdd82306ab7c94d74e6deab2460651c5" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "dlib", "log", "once_cell", @@ -2585,9 +2718,9 @@ checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" [[package]] name = "xml-rs" -version = "0.8.25" +version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5b940ebc25896e71dd073bad2dbaa2abfe97b0a391415e22ad1326d9c54e3c4" +checksum = "a62ce76d9b56901b19a74f19431b0d8b3bc7ca4ad685a746dfd78ca8f4fc6bda" [[package]] name = "xmlparser" @@ -2597,18 +2730,18 @@ checksum = "66fee0b777b0f5ac1c69bb06d361268faafa61cd4682ae064a171c16c433e9e4" [[package]] name = "zerocopy" -version = "0.7.35" +version = "0.8.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.35" +version = "0.8.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 4542e45..d37056f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,8 +17,8 @@ vulkano-shaders = "0.35" glam = { version = "0.30" } # ECS -bevy_ecs = "0.15" -bevy_app = "0.15" +bevy_ecs = "0.16" +bevy_app = "0.16" # Log and tracing log = "0.4" diff --git a/flake.lock b/flake.lock index 2853bb7..4f4cf7e 100644 --- a/flake.lock +++ b/flake.lock @@ -44,11 +44,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1742546557, - "narHash": "sha256-QyhimDBaDBtMfRc7kyL28vo+HTwXRPq3hz+BgSJDotw=", + "lastModified": 1747312588, + "narHash": "sha256-MmJvj6mlWzeRwKGLcwmZpKaOPZ5nJb/6al5CXqJsgjo=", "owner": "nixos", "repo": "nixpkgs", - "rev": "bfa9810ff7104a17555ab68ebdeafb6705f129b1", + "rev": "b1bebd0fe266bbd1820019612ead889e96a8fa2d", "type": "github" }, "original": { @@ -73,11 +73,11 @@ ] }, "locked": { - "lastModified": 1742524367, - "narHash": "sha256-KzTwk/5ETJavJZYV1DEWdCx05M4duFCxCpRbQSKWpng=", + "lastModified": 1747363019, + "narHash": "sha256-N4dwkRBmpOosa4gfFkFf/LTD8oOcNkAyvZ07JvRDEf0=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "70bf752d176b2ce07417e346d85486acea9040ef", + "rev": "0e624f2b1972a34be1a9b35290ed18ea4b419b6f", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 4f7cd2e..ec86293 100644 --- a/flake.nix +++ b/flake.nix @@ -31,12 +31,6 @@ cargo = rust; }); - renderdoc = pkgs.renderdoc.overrideAttrs (oldAttrs: { - cmakeFlags = oldAttrs.cmakeFlags ++ [ - (pkgs.lib.cmakeBool "ENABLE_UNSUPPORTED_EXPERIMENTAL_POSSIBLY_BROKEN_WAYLAND" true) - ]; - }); - buildInputs = with pkgs; [ vulkan-headers vulkan-loader vulkan-validation-layers renderdoc ] ++ pkgs.lib.optionals pkgs.stdenv.hostPlatform.isLinux (with pkgs; [ stdenv.cc.cc.lib @@ -62,7 +56,7 @@ mkCustomShell = { packages ? [ ] }: pkgs.mkShell { nativeBuildInputs = [ - renderdoc + pkgs.renderdoc (rust.override { extensions = [ "rust-src" "rust-analyzer" ]; }) ] ++ nativeBuildInputs; @@ -70,7 +64,7 @@ ++ packages; LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath buildInputs; - VK_LAYER_PATH = "${pkgs.vulkan-validation-layers}/share/vulkan/explicit_layer.d:${renderdoc}/share/vulkan/implicit_layer.d"; + VK_LAYER_PATH = "${pkgs.vulkan-validation-layers}/share/vulkan/explicit_layer.d:${pkgs.renderdoc}/share/vulkan/implicit_layer.d"; RUST_LOG = "info,rust_vulkan_test=trace"; }; in diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 00822fd..b8889a3 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,2 +1,2 @@ [toolchain] -channel = "1.85.1" +channel = "1.87.0" diff --git a/src/core/vulkan/vulkan_context.rs b/src/core/vulkan/vulkan_context.rs index 74df916..1583df8 100644 --- a/src/core/vulkan/vulkan_context.rs +++ b/src/core/vulkan/vulkan_context.rs @@ -1,7 +1,7 @@ use std::{any::Any, sync::Arc}; use bevy_app::App; -use bevy_ecs::system::Resource; +use bevy_ecs::resource::Resource; use vulkano::{ command_buffer::{ AutoCommandBufferBuilder, CommandBufferUsage, PrimaryAutoCommandBuffer, diff --git a/src/core/vulkan/window_render_context.rs b/src/core/vulkan/window_render_context.rs index 5cb2618..146be52 100644 --- a/src/core/vulkan/window_render_context.rs +++ b/src/core/vulkan/window_render_context.rs @@ -1,5 +1,5 @@ use bevy_app::App; -use bevy_ecs::system::Resource; +use bevy_ecs::resource::Resource; use std::sync::Arc; use vulkano::image::view::ImageView; use vulkano::image::{Image, ImageUsage}; diff --git a/src/core/window/config.rs b/src/core/window/config.rs index 8fbef8c..487140c 100644 --- a/src/core/window/config.rs +++ b/src/core/window/config.rs @@ -1,4 +1,4 @@ -use bevy_ecs::system::Resource; +use bevy_ecs::resource::Resource; use winit::{dpi::PhysicalSize, window::WindowAttributes}; #[derive(Resource, Clone)] diff --git a/src/core/window/raw_handle.rs b/src/core/window/raw_handle.rs index ad9c8dd..c896b56 100644 --- a/src/core/window/raw_handle.rs +++ b/src/core/window/raw_handle.rs @@ -1,6 +1,6 @@ use std::sync::Arc; -use bevy_ecs::system::Resource; +use bevy_ecs::resource::Resource; use winit::{event_loop::EventLoopProxy, window::Window}; #[derive(Resource)] From 6639f0bb1ef04030af41a6ac990bf62445ea6e92 Mon Sep 17 00:00:00 2001 From: Florian RICHER Date: Fri, 16 May 2025 14:22:18 +0200 Subject: [PATCH 018/105] Init plugins + first system --- src/core/window/mod.rs | 7 ++++++- src/core/window/state.rs | 28 +++++++++++++++++++++++++--- src/game/mod.rs | 5 ++++- src/game/test_plugin.rs | 13 +++++++++++++ 4 files changed, 48 insertions(+), 5 deletions(-) create mode 100644 src/game/test_plugin.rs diff --git a/src/core/window/mod.rs b/src/core/window/mod.rs index d1623b7..4a19fee 100644 --- a/src/core/window/mod.rs +++ b/src/core/window/mod.rs @@ -1,4 +1,4 @@ -use bevy_app::{App, AppExit}; +use bevy_app::{App, AppExit, PluginsState}; use config::WindowConfig; use raw_handle::{DisplayHandleWrapper, EventLoopProxyWrapper}; use state::WindowState; @@ -35,6 +35,11 @@ impl Window { } fn runner(mut app: App, event_loop: EventLoop<()>) -> AppExit { + if app.plugins_state() == PluginsState::Ready { + app.finish(); + app.cleanup(); + } + app.world_mut() .insert_resource(EventLoopProxyWrapper::new(event_loop.create_proxy())); diff --git a/src/core/window/state.rs b/src/core/window/state.rs index 8ffd44b..58d78d2 100644 --- a/src/core/window/state.rs +++ b/src/core/window/state.rs @@ -1,13 +1,16 @@ use std::sync::Arc; -use bevy_app::App; -use bevy_ecs::world::World; +use bevy_app::{App, PluginsState}; +use bevy_ecs::{event, world::World}; use winit::{ application::ApplicationHandler, event::WindowEvent, event_loop::ActiveEventLoop, window::WindowId, }; -use super::{config::WindowConfig, raw_handle::WindowWrapper}; +use super::{ + config::WindowConfig, + raw_handle::{DisplayHandleWrapper, WindowWrapper}, +}; pub struct WindowState { app: App, @@ -33,13 +36,32 @@ impl ApplicationHandler for WindowState { .insert_resource(WindowWrapper(Arc::new(window))); } + fn new_events(&mut self, event_loop: &ActiveEventLoop, cause: winit::event::StartCause) { + if self.app.plugins_state() == PluginsState::Ready { + self.app.finish(); + self.app.cleanup(); + } + } + fn window_event(&mut self, event_loop: &ActiveEventLoop, _id: WindowId, event: WindowEvent) { match event { WindowEvent::CloseRequested => { log::debug!("The close button was pressed; stopping"); event_loop.exit(); } + WindowEvent::RedrawRequested => { + log::debug!("The window was requested to be redrawn"); + if self.app.plugins_state() == PluginsState::Cleaned { + self.app.update(); + } + } _ => {} } } + + fn about_to_wait(&mut self, _event_loop: &ActiveEventLoop) { + let window_wrapper = self.app.world().get_resource::().unwrap(); + + window_wrapper.0.request_redraw(); + } } diff --git a/src/game/mod.rs b/src/game/mod.rs index 6eb9cab..903a179 100644 --- a/src/game/mod.rs +++ b/src/game/mod.rs @@ -5,6 +5,8 @@ use crate::core::{ window::{Window, config::WindowConfig}, }; +pub mod test_plugin; + pub fn init(app: &mut App) { let window_config = WindowConfig { title: "Rust ASH Test".to_string(), @@ -12,6 +14,7 @@ pub fn init(app: &mut App) { height: 600, }; + app.add_plugins(test_plugin::TestPlugin); Window::new(app, window_config).unwrap(); - Vulkan::new(app).unwrap(); + // Vulkan::new(app).unwrap(); } diff --git a/src/game/test_plugin.rs b/src/game/test_plugin.rs new file mode 100644 index 0000000..e82e61b --- /dev/null +++ b/src/game/test_plugin.rs @@ -0,0 +1,13 @@ +use bevy_app::{App, Last, Plugin, Startup}; + +pub struct TestPlugin; + +impl Plugin for TestPlugin { + fn build(&self, app: &mut App) { + app.add_systems(Last, setup_system); + } +} + +fn setup_system() { + log::info!("Hello, world!"); +} From 99be029ff879bf16f5782c551b83c574299792fe Mon Sep 17 00:00:00 2001 From: Florian RICHER Date: Sun, 18 May 2025 12:41:25 +0200 Subject: [PATCH 019/105] First vulkan init working --- src/core/vulkan/mod.rs | 18 ++++++++++++------ src/core/window/mod.rs | 17 +++++++++-------- src/core/window/state.rs | 17 +++++++---------- src/game/mod.rs | 12 ++++++++---- 4 files changed, 36 insertions(+), 28 deletions(-) diff --git a/src/core/vulkan/mod.rs b/src/core/vulkan/mod.rs index 29fe224..b4833ac 100644 --- a/src/core/vulkan/mod.rs +++ b/src/core/vulkan/mod.rs @@ -1,7 +1,9 @@ use vulkan_context::VulkanContext; use window_render_context::WindowRenderContext; -use bevy_app::App; +use bevy_app::{App, Plugin}; + +use super::window::raw_handle::WindowWrapper; mod utils; mod vulkan_context; @@ -13,16 +15,20 @@ pub enum VulkanError { FailedToCreateVulkanContext, } -pub struct Vulkan; +pub struct VulkanPlugin; -impl Vulkan { - pub fn new(app: &mut App) -> Result<(), VulkanError> { +impl Plugin for VulkanPlugin { + fn build(&self, app: &mut App) { let vulkan_context = VulkanContext::from(app as &App); app.world_mut().insert_resource(vulkan_context); + } + fn ready(&self, app: &App) -> bool { + app.world().get_resource::().is_some() + } + + fn finish(&self, app: &mut App) { let window_render_context = WindowRenderContext::from(app as &App); app.world_mut().insert_resource(window_render_context); - - Ok(()) } } diff --git a/src/core/window/mod.rs b/src/core/window/mod.rs index 4a19fee..1576e63 100644 --- a/src/core/window/mod.rs +++ b/src/core/window/mod.rs @@ -1,4 +1,4 @@ -use bevy_app::{App, AppExit, PluginsState}; +use bevy_app::{App, AppExit, Plugin, PluginsState}; use config::WindowConfig; use raw_handle::{DisplayHandleWrapper, EventLoopProxyWrapper}; use state::WindowState; @@ -14,23 +14,24 @@ pub enum WindowError { FailedToCreateEventLoop, } -pub struct Window; +pub struct WindowPlugin { + pub window_config: WindowConfig, +} -impl Window { - pub fn new(app: &mut App, window_config: WindowConfig) -> Result<(), WindowError> { +impl Plugin for WindowPlugin { + fn build(&self, app: &mut App) { let world = app.world_mut(); - world.insert_resource(window_config); + world.insert_resource(self.window_config.clone()); let mut event_loop_builder = EventLoop::with_user_event(); let event_loop = event_loop_builder .build() - .map_err(|_| WindowError::FailedToCreateEventLoop)?; + .map_err(|_| WindowError::FailedToCreateEventLoop) + .expect("Failed to create event loop"); world.insert_resource(DisplayHandleWrapper(event_loop.owned_display_handle())); app.set_runner(Box::new(move |app| runner(app, event_loop))); - - Ok(()) } } diff --git a/src/core/window/state.rs b/src/core/window/state.rs index 58d78d2..5654022 100644 --- a/src/core/window/state.rs +++ b/src/core/window/state.rs @@ -1,16 +1,13 @@ use std::sync::Arc; use bevy_app::{App, PluginsState}; -use bevy_ecs::{event, world::World}; +use bevy_ecs::world::World; use winit::{ application::ApplicationHandler, event::WindowEvent, event_loop::ActiveEventLoop, window::WindowId, }; -use super::{ - config::WindowConfig, - raw_handle::{DisplayHandleWrapper, WindowWrapper}, -}; +use super::{config::WindowConfig, raw_handle::WindowWrapper}; pub struct WindowState { app: App, @@ -54,14 +51,14 @@ impl ApplicationHandler for WindowState { if self.app.plugins_state() == PluginsState::Cleaned { self.app.update(); } + + let window_wrapper = self.app.world().get_resource::().unwrap(); + + window_wrapper.0.request_redraw(); } _ => {} } } - fn about_to_wait(&mut self, _event_loop: &ActiveEventLoop) { - let window_wrapper = self.app.world().get_resource::().unwrap(); - - window_wrapper.0.request_redraw(); - } + fn about_to_wait(&mut self, _event_loop: &ActiveEventLoop) {} } diff --git a/src/game/mod.rs b/src/game/mod.rs index 903a179..e141086 100644 --- a/src/game/mod.rs +++ b/src/game/mod.rs @@ -1,8 +1,8 @@ use bevy_app::App; use crate::core::{ - vulkan::Vulkan, - window::{Window, config::WindowConfig}, + vulkan, + window::{self, config::WindowConfig}, }; pub mod test_plugin; @@ -14,7 +14,11 @@ pub fn init(app: &mut App) { height: 600, }; - app.add_plugins(test_plugin::TestPlugin); - Window::new(app, window_config).unwrap(); + app.add_plugins(( + test_plugin::TestPlugin, + window::WindowPlugin { window_config }, + vulkan::VulkanPlugin, + )); + // Window::new(app, window_config).unwrap(); // Vulkan::new(app).unwrap(); } From b977f446d35e9ce861dd33ed9936fc2fe1e803fd Mon Sep 17 00:00:00 2001 From: Florian RICHER Date: Sun, 18 May 2025 13:15:29 +0200 Subject: [PATCH 020/105] Split crates --- Cargo.lock | 104 +++++------------- Cargo.toml | 22 +++- crates/engine_vulkan/Cargo.toml | 14 +++ .../mod.rs => crates/engine_vulkan/src/lib.rs | 3 +- .../engine_vulkan/src}/utils.rs | 0 .../engine_vulkan/src}/vulkan_context.rs | 3 +- .../src}/window_render_context.rs | 3 +- crates/engine_window/Cargo.toml | 12 ++ .../engine_window/src}/config.rs | 0 .../mod.rs => crates/engine_window/src/lib.rs | 0 .../engine_window/src}/raw_handle.rs | 0 .../engine_window/src}/state.rs | 1 - flake.nix | 2 +- src/core/mod.rs | 2 - src/game/mod.rs | 15 +-- src/game/test_plugin.rs | 13 --- 16 files changed, 84 insertions(+), 110 deletions(-) create mode 100644 crates/engine_vulkan/Cargo.toml rename src/core/vulkan/mod.rs => crates/engine_vulkan/src/lib.rs (94%) rename {src/core/vulkan => crates/engine_vulkan/src}/utils.rs (100%) rename {src/core/vulkan => crates/engine_vulkan/src}/vulkan_context.rs (97%) rename {src/core/vulkan => crates/engine_vulkan/src}/window_render_context.rs (98%) create mode 100644 crates/engine_window/Cargo.toml rename {src/core/window => crates/engine_window/src}/config.rs (100%) rename src/core/window/mod.rs => crates/engine_window/src/lib.rs (100%) rename {src/core/window => crates/engine_window/src}/raw_handle.rs (100%) rename {src/core/window => crates/engine_window/src}/state.rs (96%) delete mode 100644 src/game/test_plugin.rs diff --git a/Cargo.lock b/Cargo.lock index e9d2554..d2dda1a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -117,12 +117,6 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "anyhow" -version = "1.0.98" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" - [[package]] name = "arrayref" version = "0.3.9" @@ -676,6 +670,32 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" +[[package]] +name = "engine_vulkan" +version = "0.1.0" +dependencies = [ + "bevy_app", + "bevy_ecs", + "engine_window", + "env_logger", + "log", + "thiserror 2.0.12", + "vulkano", + "winit", +] + +[[package]] +name = "engine_window" +version = "0.1.0" +dependencies = [ + "bevy_app", + "bevy_ecs", + "env_logger", + "log", + "thiserror 2.0.12", + "winit", +] + [[package]] name = "env_filter" version = "0.1.3" @@ -986,7 +1006,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a793df0d7afeac54f95b471d3af7f0d4fb975699f972341a4b76988d49cdf0c" dependencies = [ "cfg-if", - "windows-targets 0.53.0", + "windows-targets 0.52.6", ] [[package]] @@ -1621,13 +1641,13 @@ dependencies = [ name = "rust_vulkan_test" version = "0.1.0" dependencies = [ - "anyhow", "bevy_app", "bevy_ecs", + "engine_vulkan", + "engine_window", "env_logger", "glam 0.30.3", "log", - "thiserror 2.0.12", "vulkano", "vulkano-shaders", "winit", @@ -2386,29 +2406,13 @@ dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm 0.52.6", + "windows_i686_gnullvm", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", "windows_x86_64_msvc 0.52.6", ] -[[package]] -name = "windows-targets" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1e4c7e8ceaaf9cb7d7507c974735728ab453b67ef8f18febdd7c11fe59dca8b" -dependencies = [ - "windows_aarch64_gnullvm 0.53.0", - "windows_aarch64_msvc 0.53.0", - "windows_i686_gnu 0.53.0", - "windows_i686_gnullvm 0.53.0", - "windows_i686_msvc 0.53.0", - "windows_x86_64_gnu 0.53.0", - "windows_x86_64_gnullvm 0.53.0", - "windows_x86_64_msvc 0.53.0", -] - [[package]] name = "windows_aarch64_gnullvm" version = "0.42.2" @@ -2427,12 +2431,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" - [[package]] name = "windows_aarch64_msvc" version = "0.42.2" @@ -2451,12 +2449,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" -[[package]] -name = "windows_aarch64_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" - [[package]] name = "windows_i686_gnu" version = "0.42.2" @@ -2475,24 +2467,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" -[[package]] -name = "windows_i686_gnu" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" - [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" -[[package]] -name = "windows_i686_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" - [[package]] name = "windows_i686_msvc" version = "0.42.2" @@ -2511,12 +2491,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" -[[package]] -name = "windows_i686_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" - [[package]] name = "windows_x86_64_gnu" version = "0.42.2" @@ -2535,12 +2509,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" -[[package]] -name = "windows_x86_64_gnu" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" - [[package]] name = "windows_x86_64_gnullvm" version = "0.42.2" @@ -2559,12 +2527,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" - [[package]] name = "windows_x86_64_msvc" version = "0.42.2" @@ -2583,12 +2545,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" -[[package]] -name = "windows_x86_64_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" - [[package]] name = "winit" version = "0.30.10" diff --git a/Cargo.toml b/Cargo.toml index d37056f..c5f1546 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,11 @@ edition = "2024" authors = ["Florian RICHER "] publish = false -[dependencies] +[workspace] +resolver = "2" +members = ["crates/*"] + +[workspace.dependencies] anyhow = "1.0" thiserror = "2.0" winit = { version = "0.30", features = ["rwh_06"] } @@ -23,3 +27,19 @@ bevy_app = "0.16" # Log and tracing log = "0.4" env_logger = "0.11" + +engine_vulkan = { path = "crates/engine_vulkan" } +engine_window = { path = "crates/engine_window" } + +[dependencies] +log = { workspace = true } +env_logger = { workspace = true } +bevy_app = { workspace = true } +bevy_ecs = { workspace = true } +winit = { workspace = true } +vulkano = { workspace = true } +vulkano-shaders = { workspace = true } +glam = { workspace = true } + +engine_vulkan = { workspace = true } +engine_window = { workspace = true } diff --git a/crates/engine_vulkan/Cargo.toml b/crates/engine_vulkan/Cargo.toml new file mode 100644 index 0000000..c0fe35b --- /dev/null +++ b/crates/engine_vulkan/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "engine_vulkan" +version = "0.1.0" +edition = "2024" + +[dependencies] +thiserror = { workspace = true } +log = { workspace = true } +env_logger = { workspace = true } +bevy_app = { workspace = true } +bevy_ecs = { workspace = true } +winit = { workspace = true } +vulkano = { workspace = true } +engine_window = { workspace = true } diff --git a/src/core/vulkan/mod.rs b/crates/engine_vulkan/src/lib.rs similarity index 94% rename from src/core/vulkan/mod.rs rename to crates/engine_vulkan/src/lib.rs index b4833ac..e36006e 100644 --- a/src/core/vulkan/mod.rs +++ b/crates/engine_vulkan/src/lib.rs @@ -1,10 +1,9 @@ +use engine_window::raw_handle::WindowWrapper; use vulkan_context::VulkanContext; use window_render_context::WindowRenderContext; use bevy_app::{App, Plugin}; -use super::window::raw_handle::WindowWrapper; - mod utils; mod vulkan_context; mod window_render_context; diff --git a/src/core/vulkan/utils.rs b/crates/engine_vulkan/src/utils.rs similarity index 100% rename from src/core/vulkan/utils.rs rename to crates/engine_vulkan/src/utils.rs diff --git a/src/core/vulkan/vulkan_context.rs b/crates/engine_vulkan/src/vulkan_context.rs similarity index 97% rename from src/core/vulkan/vulkan_context.rs rename to crates/engine_vulkan/src/vulkan_context.rs index 1583df8..7bf3b3b 100644 --- a/src/core/vulkan/vulkan_context.rs +++ b/crates/engine_vulkan/src/vulkan_context.rs @@ -2,6 +2,7 @@ use std::{any::Any, sync::Arc}; use bevy_app::App; use bevy_ecs::resource::Resource; +use engine_window::raw_handle::DisplayHandleWrapper; use vulkano::{ command_buffer::{ AutoCommandBufferBuilder, CommandBufferUsage, PrimaryAutoCommandBuffer, @@ -15,8 +16,6 @@ use vulkano::{ }; use winit::raw_window_handle::{HasDisplayHandle, HasWindowHandle}; -use crate::core::window::raw_handle::DisplayHandleWrapper; - use super::utils; #[derive(Resource)] diff --git a/src/core/vulkan/window_render_context.rs b/crates/engine_vulkan/src/window_render_context.rs similarity index 98% rename from src/core/vulkan/window_render_context.rs rename to crates/engine_vulkan/src/window_render_context.rs index 146be52..87ee958 100644 --- a/src/core/vulkan/window_render_context.rs +++ b/crates/engine_vulkan/src/window_render_context.rs @@ -1,5 +1,6 @@ use bevy_app::App; use bevy_ecs::resource::Resource; +use engine_window::raw_handle::WindowWrapper; use std::sync::Arc; use vulkano::image::view::ImageView; use vulkano::image::{Image, ImageUsage}; @@ -9,8 +10,6 @@ use vulkano::sync::{self, GpuFuture}; use vulkano::{Validated, VulkanError}; use winit::window::Window; -use crate::core::window::raw_handle::WindowWrapper; - use super::vulkan_context::VulkanContext; #[derive(Resource)] diff --git a/crates/engine_window/Cargo.toml b/crates/engine_window/Cargo.toml new file mode 100644 index 0000000..dc19716 --- /dev/null +++ b/crates/engine_window/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "engine_window" +version = "0.1.0" +edition = "2024" + +[dependencies] +thiserror = { workspace = true } +log = { workspace = true } +env_logger = { workspace = true } +bevy_app = { workspace = true } +bevy_ecs = { workspace = true } +winit = { workspace = true } diff --git a/src/core/window/config.rs b/crates/engine_window/src/config.rs similarity index 100% rename from src/core/window/config.rs rename to crates/engine_window/src/config.rs diff --git a/src/core/window/mod.rs b/crates/engine_window/src/lib.rs similarity index 100% rename from src/core/window/mod.rs rename to crates/engine_window/src/lib.rs diff --git a/src/core/window/raw_handle.rs b/crates/engine_window/src/raw_handle.rs similarity index 100% rename from src/core/window/raw_handle.rs rename to crates/engine_window/src/raw_handle.rs diff --git a/src/core/window/state.rs b/crates/engine_window/src/state.rs similarity index 96% rename from src/core/window/state.rs rename to crates/engine_window/src/state.rs index 5654022..0f708fd 100644 --- a/src/core/window/state.rs +++ b/crates/engine_window/src/state.rs @@ -47,7 +47,6 @@ impl ApplicationHandler for WindowState { event_loop.exit(); } WindowEvent::RedrawRequested => { - log::debug!("The window was requested to be redrawn"); if self.app.plugins_state() == PluginsState::Cleaned { self.app.update(); } diff --git a/flake.nix b/flake.nix index ec86293..b00f3ab 100644 --- a/flake.nix +++ b/flake.nix @@ -65,7 +65,7 @@ LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath buildInputs; VK_LAYER_PATH = "${pkgs.vulkan-validation-layers}/share/vulkan/explicit_layer.d:${pkgs.renderdoc}/share/vulkan/implicit_layer.d"; - RUST_LOG = "info,rust_vulkan_test=trace"; + RUST_LOG = "trace,rust_vulkan_test=trace"; }; in { diff --git a/src/core/mod.rs b/src/core/mod.rs index db2061f..a8707c6 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -1,4 +1,2 @@ pub mod camera; pub mod render; -pub mod vulkan; -pub mod window; diff --git a/src/game/mod.rs b/src/game/mod.rs index e141086..2078652 100644 --- a/src/game/mod.rs +++ b/src/game/mod.rs @@ -1,11 +1,6 @@ use bevy_app::App; - -use crate::core::{ - vulkan, - window::{self, config::WindowConfig}, -}; - -pub mod test_plugin; +use engine_vulkan::VulkanPlugin; +use engine_window::{WindowPlugin, config::WindowConfig}; pub fn init(app: &mut App) { let window_config = WindowConfig { @@ -14,11 +9,7 @@ pub fn init(app: &mut App) { height: 600, }; - app.add_plugins(( - test_plugin::TestPlugin, - window::WindowPlugin { window_config }, - vulkan::VulkanPlugin, - )); + app.add_plugins((WindowPlugin { window_config }, VulkanPlugin)); // Window::new(app, window_config).unwrap(); // Vulkan::new(app).unwrap(); } diff --git a/src/game/test_plugin.rs b/src/game/test_plugin.rs deleted file mode 100644 index e82e61b..0000000 --- a/src/game/test_plugin.rs +++ /dev/null @@ -1,13 +0,0 @@ -use bevy_app::{App, Last, Plugin, Startup}; - -pub struct TestPlugin; - -impl Plugin for TestPlugin { - fn build(&self, app: &mut App) { - app.add_systems(Last, setup_system); - } -} - -fn setup_system() { - log::info!("Hello, world!"); -} From f585ba78e73ebcd3e64c28a167c61f4320312044 Mon Sep 17 00:00:00 2001 From: Florian RICHER Date: Sun, 18 May 2025 17:06:59 +0200 Subject: [PATCH 021/105] Split vulkan resources --- crates/engine_vulkan/src/lib.rs | 71 ++++- crates/engine_vulkan/src/utils.rs | 137 -------- crates/engine_vulkan/src/utils/device.rs | 301 ++++++++++++++++++ crates/engine_vulkan/src/utils/instance.rs | 70 ++++ crates/engine_vulkan/src/utils/mod.rs | 2 + crates/engine_vulkan/src/vulkan_context.rs | 88 ----- .../src/window_render_context.rs | 31 +- flake.nix | 2 +- src/game/mod.rs | 25 +- 9 files changed, 483 insertions(+), 244 deletions(-) delete mode 100644 crates/engine_vulkan/src/utils.rs create mode 100644 crates/engine_vulkan/src/utils/device.rs create mode 100644 crates/engine_vulkan/src/utils/instance.rs create mode 100644 crates/engine_vulkan/src/utils/mod.rs delete mode 100644 crates/engine_vulkan/src/vulkan_context.rs diff --git a/crates/engine_vulkan/src/lib.rs b/crates/engine_vulkan/src/lib.rs index e36006e..310a1b9 100644 --- a/crates/engine_vulkan/src/lib.rs +++ b/crates/engine_vulkan/src/lib.rs @@ -1,25 +1,86 @@ +use std::sync::Arc; + +use bevy_ecs::resource::Resource; use engine_window::raw_handle::WindowWrapper; -use vulkan_context::VulkanContext; +use utils::{device::create_and_insert_device, instance::create_and_insert_instance}; +use vulkano::{ + command_buffer::allocator::StandardCommandBufferAllocator, + descriptor_set::allocator::StandardDescriptorSetAllocator, + device::{Device, DeviceExtensions, DeviceFeatures, Queue}, + instance::Instance, + memory::allocator::StandardMemoryAllocator, +}; use window_render_context::WindowRenderContext; use bevy_app::{App, Plugin}; mod utils; -mod vulkan_context; mod window_render_context; +#[derive(Resource)] +pub struct VulkanInstance(Arc); + +#[derive(Resource)] +pub struct VulkanDevice(Arc); + +#[derive(Resource)] +pub struct VulkanGraphicsQueue(Arc); + +#[derive(Resource)] +pub struct VulkanComputeQueue(Arc); + +#[derive(Resource)] +pub struct VulkanTransferQueue(Arc); +#[derive(Resource)] +pub struct VulkanMemoryAllocator(Arc); + +#[derive(Resource)] +pub struct VulkanCommandBufferAllocator(Arc); + +#[derive(Resource)] +pub struct VulkanDescriptorSetAllocator(Arc); + #[derive(Debug, thiserror::Error)] pub enum VulkanError { #[error("Failed to create vulkan context")] FailedToCreateVulkanContext, } -pub struct VulkanPlugin; +pub struct VulkanConfig { + pub instance_layers: Vec, + pub device_extensions: DeviceExtensions, + pub device_features: DeviceFeatures, + pub with_window_surface: bool, + pub with_graphics_queue: bool, + pub with_compute_queue: bool, + pub with_transfer_queue: bool, +} + +impl Default for VulkanConfig { + fn default() -> Self { + Self { + instance_layers: Vec::default(), + device_extensions: DeviceExtensions::default(), + device_features: DeviceFeatures::default(), + with_window_surface: true, + with_graphics_queue: true, + with_compute_queue: true, + with_transfer_queue: true, + } + } +} + +#[derive(Default)] +pub struct VulkanPlugin { + pub vulkan_config: VulkanConfig, +} impl Plugin for VulkanPlugin { fn build(&self, app: &mut App) { - let vulkan_context = VulkanContext::from(app as &App); - app.world_mut().insert_resource(vulkan_context); + let world = app.world_mut(); + + create_and_insert_instance(world, &self.vulkan_config); + create_and_insert_device(world, &self.vulkan_config); } fn ready(&self, app: &App) -> bool { diff --git a/crates/engine_vulkan/src/utils.rs b/crates/engine_vulkan/src/utils.rs deleted file mode 100644 index 046528f..0000000 --- a/crates/engine_vulkan/src/utils.rs +++ /dev/null @@ -1,137 +0,0 @@ -use std::sync::Arc; - -use vulkano::{ - Version, VulkanLibrary, - device::{ - Device, DeviceCreateInfo, DeviceExtensions, DeviceFeatures, Queue, QueueCreateInfo, - QueueFlags, - physical::{PhysicalDevice, PhysicalDeviceType}, - }, - instance::{Instance, InstanceCreateFlags, InstanceCreateInfo, InstanceExtensions}, -}; -use winit::raw_window_handle::HasDisplayHandle; - -pub(super) fn load_library() -> Arc { - let library = VulkanLibrary::new().unwrap(); - - log::debug!("Available layer:"); - for layer in library.layer_properties().unwrap() { - log::debug!( - "\t - Layer name: {}, Description: {}, Implementation Version: {}, Vulkan Version: {}", - layer.name(), - layer.description(), - layer.implementation_version(), - layer.vulkan_version() - ); - } - - library -} - -pub(super) fn create_instance( - library: Arc, - required_extensions: InstanceExtensions, -) -> Arc { - Instance::new( - library, - InstanceCreateInfo { - // Enable enumerating devices that use non-conformant Vulkan implementations. - // (e.g. MoltenVK) - flags: InstanceCreateFlags::ENUMERATE_PORTABILITY, - enabled_extensions: required_extensions, - enabled_layers: vec![String::from("VK_LAYER_KHRONOS_validation")], - ..Default::default() - }, - ) - .unwrap() -} - -pub(super) fn find_physical_device_queue_family_indexes( - physical_device: &Arc, - display_handle: &impl HasDisplayHandle, -) -> Option { - let mut graphic_queue_family_index = None; - - for (i, queue_family_property) in physical_device.queue_family_properties().iter().enumerate() { - if queue_family_property - .queue_flags - .intersects(QueueFlags::GRAPHICS) - && physical_device - .presentation_support(i as u32, display_handle) - .unwrap() - { - graphic_queue_family_index = Some(i as u32); - } - } - - graphic_queue_family_index -} - -pub(super) fn pick_physical_device_and_queue_family_indexes( - instance: &Arc, - display_handle: &impl HasDisplayHandle, - device_extensions: &DeviceExtensions, -) -> Option<(Arc, u32)> { - instance - .enumerate_physical_devices() - .unwrap() - .filter(|p| { - p.api_version() >= Version::V1_3 || p.supported_extensions().khr_dynamic_rendering - }) - .filter(|p| p.supported_extensions().contains(device_extensions)) - .filter_map(|p| { - find_physical_device_queue_family_indexes(&p, display_handle) - .and_then(|indexes| Some((p, indexes))) - }) - .min_by_key(|(p, _)| match p.properties().device_type { - PhysicalDeviceType::DiscreteGpu => 0, - PhysicalDeviceType::IntegratedGpu => 1, - PhysicalDeviceType::VirtualGpu => 2, - PhysicalDeviceType::Cpu => 3, - PhysicalDeviceType::Other => 4, - _ => 5, - }) -} - -pub(super) fn pick_graphics_device( - instance: &Arc, - display_handle: &impl HasDisplayHandle, -) -> (Arc, impl ExactSizeIterator>) { - let mut device_extensions = DeviceExtensions { - khr_swapchain: true, - ..DeviceExtensions::empty() - }; - - let (physical_device, graphics_family_index) = - pick_physical_device_and_queue_family_indexes(instance, display_handle, &device_extensions) - .unwrap(); - - log::debug!( - "Using device: {} (type: {:?})", - physical_device.properties().device_name, - physical_device.properties().device_type, - ); - - if physical_device.api_version() < Version::V1_3 { - device_extensions.khr_dynamic_rendering = true; - } - - log::debug!("Using device extensions: {:#?}", device_extensions); - - Device::new( - physical_device, - DeviceCreateInfo { - queue_create_infos: vec![QueueCreateInfo { - queue_family_index: graphics_family_index, - ..Default::default() - }], - enabled_extensions: device_extensions, - enabled_features: DeviceFeatures { - dynamic_rendering: true, - ..DeviceFeatures::empty() - }, - ..Default::default() - }, - ) - .unwrap() -} diff --git a/crates/engine_vulkan/src/utils/device.rs b/crates/engine_vulkan/src/utils/device.rs new file mode 100644 index 0000000..6c4b49f --- /dev/null +++ b/crates/engine_vulkan/src/utils/device.rs @@ -0,0 +1,301 @@ +use std::sync::Arc; + +use bevy_ecs::world::World; +use engine_window::raw_handle::DisplayHandleWrapper; +use vulkano::{ + command_buffer::allocator::StandardCommandBufferAllocator, + descriptor_set::allocator::StandardDescriptorSetAllocator, + device::{ + Device, DeviceCreateInfo, DeviceExtensions, Queue, QueueCreateInfo, QueueFlags, + physical::{PhysicalDevice, PhysicalDeviceType}, + }, + memory::allocator::StandardMemoryAllocator, +}; + +use crate::{ + VulkanCommandBufferAllocator, VulkanComputeQueue, VulkanConfig, VulkanDescriptorSetAllocator, + VulkanDevice, VulkanGraphicsQueue, VulkanInstance, VulkanMemoryAllocator, VulkanTransferQueue, +}; + +pub fn create_and_insert_device(world: &mut World, config: &VulkanConfig) { + let picked_device = + pick_physical_device(world, &config).expect("Failed to pick physical device"); + + let device = picked_device.device; + let physical_device = device.physical_device(); + + log::debug!("Vulkan device created"); + log::debug!( + "\tPhysical device: {:?} ({:?})", + physical_device.properties().device_name, + physical_device.properties().device_type + ); + log::debug!("\tDevice extensions: {:?}", device.enabled_extensions()); + log::debug!("\tDevice features: {:?}", device.enabled_features()); + + world.insert_resource(VulkanDevice(device.clone())); + + log::debug!("\tDevice selected queues:"); + if config.with_graphics_queue { + world.insert_resource(VulkanGraphicsQueue( + picked_device + .graphics_queue + .expect("Failed to get graphics queue"), + )); + log::debug!("\t\t- Graphics queue"); + } + + if config.with_compute_queue { + world.insert_resource(VulkanComputeQueue( + picked_device + .compute_queue + .expect("Failed to get compute queue"), + )); + log::debug!("\t\t- Compute queue"); + } + + if config.with_transfer_queue { + world.insert_resource(VulkanTransferQueue( + picked_device + .transfer_queue + .expect("Failed to get transfer queue"), + )); + log::debug!("\t\t- Transfer queue"); + } + + world.insert_resource(VulkanMemoryAllocator(Arc::new( + StandardMemoryAllocator::new_default(device.clone()), + ))); + world.insert_resource(VulkanCommandBufferAllocator(Arc::new( + StandardCommandBufferAllocator::new(device.clone(), Default::default()), + ))); + world.insert_resource(VulkanDescriptorSetAllocator(Arc::new( + StandardDescriptorSetAllocator::new(device.clone(), Default::default()), + ))); +} + +struct PickedDevice { + pub device: Arc, + pub graphics_queue: Option>, + pub compute_queue: Option>, + pub transfer_queue: Option>, +} + +fn pick_physical_device(world: &World, config: &VulkanConfig) -> Option { + let instance = world + .get_resource::() + .expect("Failed to get VulkanInstance during vulkan plugin initialization"); + + instance + .0 + .enumerate_physical_devices() + .expect("Failed to enumerate physical devices") + .filter_map(|p| check_physical_device(world, &p, config)) + .min_by_key( + |p| match p.device.physical_device().properties().device_type { + PhysicalDeviceType::DiscreteGpu => 0, + PhysicalDeviceType::IntegratedGpu => 1, + PhysicalDeviceType::VirtualGpu => 2, + PhysicalDeviceType::Cpu => 3, + PhysicalDeviceType::Other => 4, + _ => 5, + }, + ) + .take() +} + +fn check_device_extensions_support( + physical_device: &Arc, + config: &VulkanConfig, +) -> Option { + let device_extensions = DeviceExtensions { + khr_swapchain: config.with_window_surface, + ..config.device_extensions + }; + + if physical_device + .supported_extensions() + .contains(&device_extensions) + { + log::debug!( + "\t\t[OK] Device supports required extensions {:?}", + device_extensions + ); + Some(device_extensions) + } else { + log::debug!( + "\t\t[FAILED] Device does not support required extensions {:?}", + device_extensions + ); + None + } +} + +struct PickedQueuesInfo { + graphics_queue_family_index: Option, + compute_queue_family_index: Option, + transfer_queue_family_index: Option, +} + +fn check_queues_support( + world: &World, + physical_device: &Arc, + config: &VulkanConfig, +) -> Option { + let mut graphics_queue_family_index: Option = None; + let mut compute_queue_family_index: Option = None; + let mut transfer_queue_family_index: Option = None; + + for (i, queue_family_property) in physical_device.queue_family_properties().iter().enumerate() { + if config.with_graphics_queue { + let graphics_supported = queue_family_property + .queue_flags + .intersects(QueueFlags::GRAPHICS); + + let presentation_valid = if config.with_window_surface { + let display_handle = world + .get_resource::() + .expect("DisplayHandleWrapper must be added before VulkanPlugin"); + + physical_device + .presentation_support(i as u32, &display_handle.0) + .expect("Failed to check presentation support") + } else { + true + }; + + if graphics_supported && presentation_valid { + graphics_queue_family_index = Some(i as u32); + } + } + + if config.with_compute_queue { + let compute_supported = queue_family_property + .queue_flags + .intersects(QueueFlags::COMPUTE); + + if compute_supported { + compute_queue_family_index = Some(i as u32); + } + } + + if config.with_transfer_queue { + let transfer_supported = queue_family_property + .queue_flags + .intersects(QueueFlags::TRANSFER); + + if transfer_supported { + transfer_queue_family_index = Some(i as u32); + } + } + } + + if !config.with_graphics_queue { + log::debug!("\t\t[SKIPPED] Graphics queue is not required"); + } else if graphics_queue_family_index.is_some() { + log::debug!("\t\t[OK] Graphics queue is supported"); + } else { + log::debug!("\t\t[FAILED] Graphics queue is not supported"); + return None; + } + + if !config.with_compute_queue { + log::debug!("\t\t[SKIPPED] Compute queue is not required"); + } else if compute_queue_family_index.is_some() { + log::debug!("\t\t[OK] Compute queue is supported"); + } else { + log::debug!("\t\t[FAILED] Compute queue is not supported"); + return None; + } + + if !config.with_transfer_queue { + log::debug!("\t\t[SKIPPED] Transfer queue is not required"); + } else if transfer_queue_family_index.is_some() { + log::debug!("\t\t[OK] Transfer queue is supported"); + } else { + log::debug!("\t\t[FAILED] Transfer queue is not supported"); + return None; + } + + Some(PickedQueuesInfo { + graphics_queue_family_index, + compute_queue_family_index, + transfer_queue_family_index, + }) +} + +fn check_physical_device( + world: &World, + physical_device: &Arc, + config: &VulkanConfig, +) -> Option { + log::debug!("Checking physical device"); + log::debug!("\tProperties"); + log::debug!("\t\tName: {}", physical_device.properties().device_name); + log::debug!("\t\tAPI version: {}", physical_device.api_version()); + log::debug!( + "\t\tDevice type: {:?}", + physical_device.properties().device_type + ); + log::debug!("\tRequired supports checking report"); + + let device_extensions = check_device_extensions_support(physical_device, config)?; + let picked_queues_info = check_queues_support(world, physical_device, config)?; + + let mut queue_create_infos = vec![]; + + if config.with_graphics_queue { + queue_create_infos.push(QueueCreateInfo { + queue_family_index: picked_queues_info.graphics_queue_family_index.unwrap(), + ..Default::default() + }); + } + + if config.with_compute_queue { + queue_create_infos.push(QueueCreateInfo { + queue_family_index: picked_queues_info.compute_queue_family_index.unwrap(), + ..Default::default() + }); + } + + if config.with_transfer_queue { + queue_create_infos.push(QueueCreateInfo { + queue_family_index: picked_queues_info.transfer_queue_family_index.unwrap(), + ..Default::default() + }); + } + + let (device, mut queues) = Device::new( + physical_device.clone(), + DeviceCreateInfo { + queue_create_infos, + enabled_extensions: device_extensions, + enabled_features: config.device_features, + ..Default::default() + }, + ) + .expect("Failed to create device"); + + let mut graphics_queue = None; + let mut compute_queue = None; + let mut transfer_queue = None; + + if config.with_graphics_queue { + graphics_queue = queues.next(); + } + + if config.with_compute_queue { + compute_queue = queues.next(); + } + + if config.with_transfer_queue { + transfer_queue = queues.next(); + } + + Some(PickedDevice { + device, + graphics_queue, + compute_queue, + transfer_queue, + }) +} diff --git a/crates/engine_vulkan/src/utils/instance.rs b/crates/engine_vulkan/src/utils/instance.rs new file mode 100644 index 0000000..dcda857 --- /dev/null +++ b/crates/engine_vulkan/src/utils/instance.rs @@ -0,0 +1,70 @@ +use std::sync::Arc; + +use bevy_ecs::world::World; +use engine_window::raw_handle::DisplayHandleWrapper; +use vulkano::{ + VulkanLibrary, + instance::{Instance, InstanceCreateFlags, InstanceCreateInfo, InstanceExtensions}, + swapchain::Surface, +}; + +use crate::{VulkanConfig, VulkanInstance}; + +fn load_library() -> Arc { + let library = VulkanLibrary::new().unwrap(); + + log::debug!("Available Instance layers:"); + for layer in library.layer_properties().unwrap() { + log::debug!( + "\t - Layer name: {}, Description: {}, Implementation Version: {}, Vulkan Version: {}", + layer.name(), + layer.description(), + layer.implementation_version(), + layer.vulkan_version() + ); + } + + library +} + +pub fn create_and_insert_instance(world: &mut World, config: &VulkanConfig) { + let library = load_library(); + + let instance_extensions = { + if config.with_window_surface { + let display_handle = world + .get_resource::() + .expect("DisplayHandleWrapper must be added before VulkanPlugin"); + + Surface::required_extensions(&display_handle.0) + .expect("Failed to get surface required extensions") + } else { + InstanceExtensions::default() + } + }; + + let instance = Instance::new( + library, + InstanceCreateInfo { + // Enable enumerating devices that use non-conformant Vulkan implementations. + // (e.g. MoltenVK) + flags: InstanceCreateFlags::ENUMERATE_PORTABILITY, + enabled_extensions: instance_extensions, + enabled_layers: config.instance_layers.clone(), + ..Default::default() + }, + ) + .expect("Failed to create vulkan instance"); + + log::debug!("Instance created"); + log::debug!( + "\t- Enabled extensions: {:?}", + instance.enabled_extensions() + ); + log::debug!("\t- Enabled layers: {:?}", instance.enabled_layers()); + log::debug!("\t- API version: {:?}", instance.api_version()); + log::debug!("\t- Max API version: {:?}", instance.max_api_version()); + log::debug!("\t- Flags: {:?}", instance.flags()); + + world.insert_resource(VulkanInstance(instance)); +} diff --git a/crates/engine_vulkan/src/utils/mod.rs b/crates/engine_vulkan/src/utils/mod.rs new file mode 100644 index 0000000..09e4b35 --- /dev/null +++ b/crates/engine_vulkan/src/utils/mod.rs @@ -0,0 +1,2 @@ +pub mod device; +pub mod instance; diff --git a/crates/engine_vulkan/src/vulkan_context.rs b/crates/engine_vulkan/src/vulkan_context.rs deleted file mode 100644 index 7bf3b3b..0000000 --- a/crates/engine_vulkan/src/vulkan_context.rs +++ /dev/null @@ -1,88 +0,0 @@ -use std::{any::Any, sync::Arc}; - -use bevy_app::App; -use bevy_ecs::resource::Resource; -use engine_window::raw_handle::DisplayHandleWrapper; -use vulkano::{ - command_buffer::{ - AutoCommandBufferBuilder, CommandBufferUsage, PrimaryAutoCommandBuffer, - allocator::StandardCommandBufferAllocator, - }, - descriptor_set::allocator::StandardDescriptorSetAllocator, - device::{Device, Queue}, - instance::Instance, - memory::allocator::StandardMemoryAllocator, - swapchain::Surface, -}; -use winit::raw_window_handle::{HasDisplayHandle, HasWindowHandle}; - -use super::utils; - -#[derive(Resource)] -pub struct VulkanContext { - pub instance: Arc, - pub device: Arc, - pub graphics_queue: Arc, - - pub memory_allocator: Arc, - pub command_buffer_allocator: Arc, - pub descriptor_set_allocator: Arc, -} - -impl VulkanContext { - pub fn create_surface( - &self, - window: Arc, - ) -> Arc { - Surface::from_window(self.instance.clone(), window).unwrap() - } - - pub fn create_render_builder(&self) -> AutoCommandBufferBuilder { - AutoCommandBufferBuilder::primary( - self.command_buffer_allocator.clone(), - self.graphics_queue.queue_family_index(), - CommandBufferUsage::OneTimeSubmit, - ) - .unwrap() - } -} - -impl From<&App> for VulkanContext { - fn from(app: &App) -> Self { - let library = utils::load_library(); - - let world = app.world(); - - let display_handle: &DisplayHandleWrapper = - world.get_resource::().unwrap(); - - let enabled_extensions = Surface::required_extensions(&display_handle.0).unwrap(); - log::debug!("Surface required extensions: {enabled_extensions:?}"); - - let instance = utils::create_instance(library.clone(), enabled_extensions); - - let (device, mut queues) = utils::pick_graphics_device(&instance, &display_handle.0); - let graphics_queue = queues.next().unwrap(); - - let memory_allocator = Arc::new(StandardMemoryAllocator::new_default(device.clone())); - - let command_buffer_allocator = Arc::new(StandardCommandBufferAllocator::new( - device.clone(), - Default::default(), - )); - - let descriptor_set_allocator = Arc::new(StandardDescriptorSetAllocator::new( - device.clone(), - Default::default(), - )); - - Self { - instance: instance.clone(), - device, - graphics_queue, - memory_allocator, - command_buffer_allocator, - descriptor_set_allocator, - } - } -} diff --git a/crates/engine_vulkan/src/window_render_context.rs b/crates/engine_vulkan/src/window_render_context.rs index 87ee958..95b3d90 100644 --- a/crates/engine_vulkan/src/window_render_context.rs +++ b/crates/engine_vulkan/src/window_render_context.rs @@ -5,12 +5,12 @@ use std::sync::Arc; use vulkano::image::view::ImageView; use vulkano::image::{Image, ImageUsage}; use vulkano::pipeline::graphics::viewport::Viewport; -use vulkano::swapchain::{Swapchain, SwapchainCreateInfo}; +use vulkano::swapchain::{Surface, Swapchain, SwapchainCreateInfo}; use vulkano::sync::{self, GpuFuture}; use vulkano::{Validated, VulkanError}; use winit::window::Window; -use super::vulkan_context::VulkanContext; +use crate::{VulkanDevice, VulkanInstance}; #[derive(Resource)] pub struct WindowRenderContext { @@ -25,27 +25,36 @@ pub struct WindowRenderContext { impl From<&App> for WindowRenderContext { fn from(app: &App) -> Self { let world = app.world(); - let vulkan_context = world.get_resource::().unwrap(); - let window_handle = world.get_resource::().unwrap(); + let window_handle = world + .get_resource::() + .expect("Failed to find window handle"); + let vulkan_instance = world + .get_resource::() + .expect("Failed to find vulkan instance"); + let vulkan_device = world + .get_resource::() + .expect("Failed to find vulkan device"); + let window_size = window_handle.0.inner_size(); - let surface = vulkan_context.create_surface(window_handle.0.clone()); + let surface = Surface::from_window(vulkan_instance.0.clone(), window_handle.0.clone()) + .expect("Failed to create surface"); let (swapchain, images) = { - let surface_capabilities = vulkan_context - .device + let surface_capabilities = vulkan_device + .0 .physical_device() .surface_capabilities(&surface, Default::default()) .unwrap(); - let (image_format, _) = vulkan_context - .device + let (image_format, _) = vulkan_device + .0 .physical_device() .surface_formats(&surface, Default::default()) .unwrap()[0]; Swapchain::new( - vulkan_context.device.clone(), + vulkan_device.0.clone(), surface, SwapchainCreateInfo { // 2 because with some graphics driver, it crash on fullscreen because fullscreen need to min image to works. @@ -74,7 +83,7 @@ impl From<&App> for WindowRenderContext { }; let recreate_swapchain = false; - let previous_frame_end = Some(sync::now(vulkan_context.device.clone()).boxed_send_sync()); + let previous_frame_end = Some(sync::now(vulkan_device.0.clone()).boxed_send_sync()); Self { window: window_handle.0.clone(), diff --git a/flake.nix b/flake.nix index b00f3ab..9cef9c1 100644 --- a/flake.nix +++ b/flake.nix @@ -65,7 +65,7 @@ LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath buildInputs; VK_LAYER_PATH = "${pkgs.vulkan-validation-layers}/share/vulkan/explicit_layer.d:${pkgs.renderdoc}/share/vulkan/implicit_layer.d"; - RUST_LOG = "trace,rust_vulkan_test=trace"; + RUST_LOG = "debug,rust_vulkan_test=trace"; }; in { diff --git a/src/game/mod.rs b/src/game/mod.rs index 2078652..2c30bf8 100644 --- a/src/game/mod.rs +++ b/src/game/mod.rs @@ -1,6 +1,7 @@ use bevy_app::App; -use engine_vulkan::VulkanPlugin; +use engine_vulkan::{VulkanConfig, VulkanPlugin}; use engine_window::{WindowPlugin, config::WindowConfig}; +use vulkano::device::{DeviceExtensions, DeviceFeatures}; pub fn init(app: &mut App) { let window_config = WindowConfig { @@ -9,7 +10,27 @@ pub fn init(app: &mut App) { height: 600, }; - app.add_plugins((WindowPlugin { window_config }, VulkanPlugin)); + let device_extensions = DeviceExtensions { + khr_dynamic_rendering: true, + ..Default::default() + }; + + let device_features = DeviceFeatures { + dynamic_rendering: true, + ..Default::default() + }; + + let vulkan_config = VulkanConfig { + instance_layers: vec![String::from("VK_LAYER_KHRONOS_validation")], + device_extensions, + device_features, + ..Default::default() + }; + + app.add_plugins(( + WindowPlugin { window_config }, + VulkanPlugin { vulkan_config }, + )); // Window::new(app, window_config).unwrap(); // Vulkan::new(app).unwrap(); } From 0ee29a3649c507f451ae7f63e06deacc67664c91 Mon Sep 17 00:00:00 2001 From: Florian RICHER Date: Sun, 18 May 2025 18:02:54 +0200 Subject: [PATCH 022/105] render_plugin: Add first SubApp and default schedules --- Cargo.lock | 11 +++- Cargo.toml | 2 + crates/engine_render/Cargo.toml | 9 +++ crates/engine_render/src/lib.rs | 65 ++++++++++++++++++ crates/engine_vulkan/src/vulkan_context.rs | 76 ++++++++++++++++++++++ crates/engine_window/Cargo.toml | 1 - src/game/mod.rs | 2 + 7 files changed, 164 insertions(+), 2 deletions(-) create mode 100644 crates/engine_render/Cargo.toml create mode 100644 crates/engine_render/src/lib.rs create mode 100644 crates/engine_vulkan/src/vulkan_context.rs diff --git a/Cargo.lock b/Cargo.lock index d2dda1a..6467d38 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -670,6 +670,15 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" +[[package]] +name = "engine_render" +version = "0.1.0" +dependencies = [ + "bevy_app", + "bevy_ecs", + "log", +] + [[package]] name = "engine_vulkan" version = "0.1.0" @@ -690,7 +699,6 @@ version = "0.1.0" dependencies = [ "bevy_app", "bevy_ecs", - "env_logger", "log", "thiserror 2.0.12", "winit", @@ -1643,6 +1651,7 @@ version = "0.1.0" dependencies = [ "bevy_app", "bevy_ecs", + "engine_render", "engine_vulkan", "engine_window", "env_logger", diff --git a/Cargo.toml b/Cargo.toml index c5f1546..d8c8792 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,6 +30,7 @@ env_logger = "0.11" engine_vulkan = { path = "crates/engine_vulkan" } engine_window = { path = "crates/engine_window" } +engine_render = { path = "crates/engine_render" } [dependencies] log = { workspace = true } @@ -43,3 +44,4 @@ glam = { workspace = true } engine_vulkan = { workspace = true } engine_window = { workspace = true } +engine_render = { workspace = true } diff --git a/crates/engine_render/Cargo.toml b/crates/engine_render/Cargo.toml new file mode 100644 index 0000000..bd283f0 --- /dev/null +++ b/crates/engine_render/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "engine_render" +version = "0.1.0" +edition = "2024" + +[dependencies] +log = { workspace = true } +bevy_app = { workspace = true } +bevy_ecs = { workspace = true } diff --git a/crates/engine_render/src/lib.rs b/crates/engine_render/src/lib.rs new file mode 100644 index 0000000..febefbf --- /dev/null +++ b/crates/engine_render/src/lib.rs @@ -0,0 +1,65 @@ +use bevy_app::{App, AppLabel, Last, Plugin, SubApp}; +use bevy_ecs::{ + schedule::{IntoScheduleConfigs, Schedule, ScheduleLabel, SystemSet}, + system::Commands, +}; + +#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)] +pub enum RenderSystems { + Prepare, + Queue, + Render, + Present, +} + +#[derive(ScheduleLabel, Debug, Hash, PartialEq, Eq, Clone, Default)] +pub struct Render; + +impl Render { + pub fn base_schedule() -> Schedule { + use RenderSystems::*; + + let mut schedule = Schedule::new(Self); + + schedule.configure_sets((Prepare, Queue, Render, Present).chain()); + + schedule + } +} + +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, AppLabel)] +pub struct RenderApp; + +pub struct RenderPlugin; + +impl Plugin for RenderPlugin { + fn build(&self, app: &mut App) { + let mut render_app = SubApp::new(); + render_app.update_schedule = Some(Render.intern()); + + render_app.add_schedule(Render::base_schedule()); + + render_app.add_systems(Render, test_prepare.in_set(RenderSystems::Prepare)); + render_app.add_systems(Render, test_queue.in_set(RenderSystems::Queue)); + render_app.add_systems(Render, test_render.in_set(RenderSystems::Render)); + render_app.add_systems(Render, test_present.in_set(RenderSystems::Present)); + + app.insert_sub_app(RenderApp, render_app); + } +} + +fn test_prepare(mut commands: Commands) { + log::info!("test_prepare"); +} + +fn test_queue(mut commands: Commands) { + log::info!("test_queue"); +} + +fn test_render(mut commands: Commands) { + log::info!("test_render"); +} + +fn test_present(mut commands: Commands) { + log::info!("test_present"); +} diff --git a/crates/engine_vulkan/src/vulkan_context.rs b/crates/engine_vulkan/src/vulkan_context.rs new file mode 100644 index 0000000..08ff1ac --- /dev/null +++ b/crates/engine_vulkan/src/vulkan_context.rs @@ -0,0 +1,76 @@ +use std::{any::Any, sync::Arc}; + +use bevy_app::App; +use bevy_ecs::resource::Resource; +use engine_window::raw_handle::DisplayHandleWrapper; +use vulkano::{ + command_buffer::{ + AutoCommandBufferBuilder, CommandBufferUsage, PrimaryAutoCommandBuffer, + allocator::StandardCommandBufferAllocator, + }, + descriptor_set::allocator::StandardDescriptorSetAllocator, + device::{Device, Queue}, + instance::Instance, + memory::allocator::StandardMemoryAllocator, + swapchain::Surface, +}; +use winit::raw_window_handle::{HasDisplayHandle, HasWindowHandle}; + +use super::utils; + +#[derive(Resource)] +pub struct VulkanContext { + pub instance: Arc, + pub device: Arc, + pub graphics_queue: Arc, + + pub memory_allocator: Arc, + pub command_buffer_allocator: Arc, + pub descriptor_set_allocator: Arc, +} + +impl VulkanContext { + pub fn create_surface( + &self, + window: Arc, + ) -> Arc { + Surface::from_window(self.instance.clone(), window).unwrap() + } + + pub fn create_render_builder(&self) -> AutoCommandBufferBuilder { + AutoCommandBufferBuilder::primary( + self.command_buffer_allocator.clone(), + self.graphics_queue.queue_family_index(), + CommandBufferUsage::OneTimeSubmit, + ) + .unwrap() + } +} + +impl From<&App> for VulkanContext { + fn from(app: &App) -> Self { + let (device, mut queues) = utils::pick_graphics_device(&instance, &display_handle.0); + let graphics_queue = queues.next().unwrap(); + + let memory_allocator = Arc::new(StandardMemoryAllocator::new_default(device.clone())); + + let command_buffer_allocator = Arc::new(StandardCommandBufferAllocator::new( + device.clone(), + Default::default(), + )); + + let descriptor_set_allocator = Arc::new(StandardDescriptorSetAllocator::new( + device.clone(), + Default::default(), + )); + + Self { + instance: instance.clone(), + device, + graphics_queue, + memory_allocator, + command_buffer_allocator, + descriptor_set_allocator, + } + } +} diff --git a/crates/engine_window/Cargo.toml b/crates/engine_window/Cargo.toml index dc19716..b5016cc 100644 --- a/crates/engine_window/Cargo.toml +++ b/crates/engine_window/Cargo.toml @@ -6,7 +6,6 @@ edition = "2024" [dependencies] thiserror = { workspace = true } log = { workspace = true } -env_logger = { workspace = true } bevy_app = { workspace = true } bevy_ecs = { workspace = true } winit = { workspace = true } diff --git a/src/game/mod.rs b/src/game/mod.rs index 2c30bf8..ff3df3c 100644 --- a/src/game/mod.rs +++ b/src/game/mod.rs @@ -1,4 +1,5 @@ use bevy_app::App; +use engine_render::RenderPlugin; use engine_vulkan::{VulkanConfig, VulkanPlugin}; use engine_window::{WindowPlugin, config::WindowConfig}; use vulkano::device::{DeviceExtensions, DeviceFeatures}; @@ -30,6 +31,7 @@ pub fn init(app: &mut App) { app.add_plugins(( WindowPlugin { window_config }, VulkanPlugin { vulkan_config }, + RenderPlugin, )); // Window::new(app, window_config).unwrap(); // Vulkan::new(app).unwrap(); From ae0a2be097a0565cb862d7384a292ffec0958227 Mon Sep 17 00:00:00 2001 From: Florian RICHER Date: Sun, 18 May 2025 19:28:34 +0200 Subject: [PATCH 023/105] render_plugin: Begin add window plugin --- Cargo.lock | 3 + crates/engine_render/Cargo.toml | 3 + crates/engine_render/src/lib.rs | 82 ++++++++++++++++++++------ crates/engine_render/src/window/mod.rs | 35 +++++++++++ crates/engine_vulkan/src/lib.rs | 42 ++++++------- crates/engine_window/src/lib.rs | 2 +- crates/engine_window/src/raw_handle.rs | 4 +- src/game/mod.rs | 11 +++- 8 files changed, 134 insertions(+), 48 deletions(-) create mode 100644 crates/engine_render/src/window/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 6467d38..e538714 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -676,7 +676,10 @@ version = "0.1.0" dependencies = [ "bevy_app", "bevy_ecs", + "engine_vulkan", + "engine_window", "log", + "vulkano", ] [[package]] diff --git a/crates/engine_render/Cargo.toml b/crates/engine_render/Cargo.toml index bd283f0..5016279 100644 --- a/crates/engine_render/Cargo.toml +++ b/crates/engine_render/Cargo.toml @@ -7,3 +7,6 @@ edition = "2024" log = { workspace = true } bevy_app = { workspace = true } bevy_ecs = { workspace = true } +vulkano = { workspace = true } +engine_vulkan = { workspace = true } +engine_window = { workspace = true } diff --git a/crates/engine_render/src/lib.rs b/crates/engine_render/src/lib.rs index febefbf..f42013e 100644 --- a/crates/engine_render/src/lib.rs +++ b/crates/engine_render/src/lib.rs @@ -1,11 +1,21 @@ use bevy_app::{App, AppLabel, Last, Plugin, SubApp}; use bevy_ecs::{ schedule::{IntoScheduleConfigs, Schedule, ScheduleLabel, SystemSet}, - system::Commands, + system::{Commands, Res}, + world::World, }; +use engine_vulkan::{ + VulkanCommandBufferAllocator, VulkanDescriptorSetAllocator, VulkanDevice, VulkanGraphicsQueue, + VulkanMemoryAllocator, +}; +use engine_window::raw_handle::WindowWrapper; +use window::WindowRenderPlugin; + +pub mod window; #[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)] pub enum RenderSystems { + ManageViews, Prepare, Queue, Render, @@ -33,33 +43,69 @@ pub struct RenderApp; pub struct RenderPlugin; impl Plugin for RenderPlugin { - fn build(&self, app: &mut App) { + fn build(&self, _app: &mut App) {} + + fn ready(&self, app: &App) -> bool { + let world = app.world(); + + world.get_resource::().is_some() + && world.get_resource::().is_some() + && world.get_resource::().is_some() + && world.get_resource::().is_some() + && world + .get_resource::() + .is_some() + && world + .get_resource::() + .is_some() + } + + fn finish(&self, app: &mut App) { let mut render_app = SubApp::new(); render_app.update_schedule = Some(Render.intern()); - render_app.add_schedule(Render::base_schedule()); - render_app.add_systems(Render, test_prepare.in_set(RenderSystems::Prepare)); - render_app.add_systems(Render, test_queue.in_set(RenderSystems::Queue)); - render_app.add_systems(Render, test_render.in_set(RenderSystems::Render)); - render_app.add_systems(Render, test_present.in_set(RenderSystems::Present)); + extract_app_resources(app.world_mut(), render_app.world_mut()); app.insert_sub_app(RenderApp, render_app); + + app.add_plugins(WindowRenderPlugin); + } + + fn cleanup(&self, app: &mut App) { + app.remove_sub_app(RenderApp); } } -fn test_prepare(mut commands: Commands) { - log::info!("test_prepare"); -} +fn extract_app_resources(world: &mut World, render_world: &mut World) { + let window_wrapper = world + .get_resource::() + .expect("Failed to get WindowWrapper. Check is WindowPlugin is added before RenderPlugin."); -fn test_queue(mut commands: Commands) { - log::info!("test_queue"); -} + let vulkan_device = world + .get_resource::() + .expect("Failed to get Vulkan device. Check is VulkanPlugin is added before RenderPlugin."); -fn test_render(mut commands: Commands) { - log::info!("test_render"); -} + let vulkan_graphics_queue = world.get_resource::().expect( + "Failed to get Vulkan graphics queue. Check is VulkanPlugin is added before RenderPlugin.", + ); -fn test_present(mut commands: Commands) { - log::info!("test_present"); + let vulkan_memory_allocator = world + .get_resource::() + .expect("Failed to get Vulkan memory allocator. Check is VulkanPlugin is added before RenderPlugin."); + + let vulkan_command_buffer_allocator = world + .get_resource::() + .expect("Failed to get Vulkan command buffer allocator. Check is VulkanPlugin is added before RenderPlugin."); + + let vulkan_descriptor_set_allocator = world + .get_resource::() + .expect("Failed to get Vulkan descriptor set allocator. Check is VulkanPlugin is added before RenderPlugin."); + + render_world.insert_resource(vulkan_device.clone()); + render_world.insert_resource(vulkan_graphics_queue.clone()); + render_world.insert_resource(vulkan_memory_allocator.clone()); + render_world.insert_resource(vulkan_command_buffer_allocator.clone()); + render_world.insert_resource(vulkan_descriptor_set_allocator.clone()); + render_world.insert_resource(window_wrapper.clone()); } diff --git a/crates/engine_render/src/window/mod.rs b/crates/engine_render/src/window/mod.rs new file mode 100644 index 0000000..bcb3ea6 --- /dev/null +++ b/crates/engine_render/src/window/mod.rs @@ -0,0 +1,35 @@ +use std::sync::Arc; + +use bevy_app::{App, Plugin}; +use bevy_ecs::resource::Resource; +use vulkano::{ + image::view::ImageView, pipeline::graphics::viewport::Viewport, swapchain::Swapchain, + sync::GpuFuture, +}; + +use crate::RenderApp; + +pub struct WindowSurfaceData { + pub swapchain: Arc, + pub attachment_image_views: Vec>, + pub viewport: Viewport, + pub recreate_swapchain: bool, + pub previous_frame_end: Option>, +} + +#[derive(Resource, Default)] +pub struct WindowSurface { + pub surface: Option, +} + +pub struct WindowRenderPlugin; + +impl Plugin for WindowRenderPlugin { + fn build(&self, app: &mut App) { + let render_app = app + .get_sub_app_mut(RenderApp) + .expect("Failed to get RenderApp. Check is RenderPlugin is added."); + + render_app.init_resource::(); + } +} diff --git a/crates/engine_vulkan/src/lib.rs b/crates/engine_vulkan/src/lib.rs index 310a1b9..65e9349 100644 --- a/crates/engine_vulkan/src/lib.rs +++ b/crates/engine_vulkan/src/lib.rs @@ -17,28 +17,29 @@ use bevy_app::{App, Plugin}; mod utils; mod window_render_context; -#[derive(Resource)] -pub struct VulkanInstance(Arc); +#[derive(Resource, Clone)] +pub struct VulkanInstance(pub Arc); -#[derive(Resource)] -pub struct VulkanDevice(Arc); +#[derive(Resource, Clone)] +pub struct VulkanDevice(pub Arc); -#[derive(Resource)] -pub struct VulkanGraphicsQueue(Arc); +#[derive(Resource, Clone)] +pub struct VulkanGraphicsQueue(pub Arc); -#[derive(Resource)] -pub struct VulkanComputeQueue(Arc); +#[derive(Resource, Clone)] +pub struct VulkanComputeQueue(pub Arc); -#[derive(Resource)] -pub struct VulkanTransferQueue(Arc); -#[derive(Resource)] -pub struct VulkanMemoryAllocator(Arc); +#[derive(Resource, Clone)] +pub struct VulkanTransferQueue(pub Arc); -#[derive(Resource)] -pub struct VulkanCommandBufferAllocator(Arc); +#[derive(Resource, Clone)] +pub struct VulkanMemoryAllocator(pub Arc); -#[derive(Resource)] -pub struct VulkanDescriptorSetAllocator(Arc); +#[derive(Resource, Clone)] +pub struct VulkanCommandBufferAllocator(pub Arc); + +#[derive(Resource, Clone)] +pub struct VulkanDescriptorSetAllocator(pub Arc); #[derive(Debug, thiserror::Error)] pub enum VulkanError { @@ -82,13 +83,4 @@ impl Plugin for VulkanPlugin { create_and_insert_instance(world, &self.vulkan_config); create_and_insert_device(world, &self.vulkan_config); } - - fn ready(&self, app: &App) -> bool { - app.world().get_resource::().is_some() - } - - fn finish(&self, app: &mut App) { - let window_render_context = WindowRenderContext::from(app as &App); - app.world_mut().insert_resource(window_render_context); - } } diff --git a/crates/engine_window/src/lib.rs b/crates/engine_window/src/lib.rs index 1576e63..33e150c 100644 --- a/crates/engine_window/src/lib.rs +++ b/crates/engine_window/src/lib.rs @@ -1,6 +1,6 @@ use bevy_app::{App, AppExit, Plugin, PluginsState}; use config::WindowConfig; -use raw_handle::{DisplayHandleWrapper, EventLoopProxyWrapper}; +use raw_handle::{DisplayHandleWrapper, EventLoopProxyWrapper, WindowWrapper}; use state::WindowState; use winit::event_loop::EventLoop; diff --git a/crates/engine_window/src/raw_handle.rs b/crates/engine_window/src/raw_handle.rs index c896b56..87d5988 100644 --- a/crates/engine_window/src/raw_handle.rs +++ b/crates/engine_window/src/raw_handle.rs @@ -16,8 +16,8 @@ impl EventLoopProxyWrapper { } } -#[derive(Resource)] +#[derive(Resource, Clone)] pub struct DisplayHandleWrapper(pub winit::event_loop::OwnedDisplayHandle); -#[derive(Resource)] +#[derive(Resource, Clone)] pub struct WindowWrapper(pub Arc); diff --git a/src/game/mod.rs b/src/game/mod.rs index ff3df3c..9c5b547 100644 --- a/src/game/mod.rs +++ b/src/game/mod.rs @@ -33,6 +33,13 @@ pub fn init(app: &mut App) { VulkanPlugin { vulkan_config }, RenderPlugin, )); - // Window::new(app, window_config).unwrap(); - // Vulkan::new(app).unwrap(); + + // app.get_sub_app_mut(RenderApp) + // .expect("Failed to get RenderApp. Check is RenderPlugin is added.") + // .add_systems(Render, test_system.in_set(RenderSystems::Prepare)); } + +// fn test_system(vulkan_device: Res, vulkan_graphics_queue: Res) { +// log::trace!("vulkan_device: {:?}", vulkan_device.0); +// log::trace!("vulkan_graphics_queue: {:?}", vulkan_graphics_queue.0); +// } From 62d12f2ab827c5e6a327d10d841362b2fa19484b Mon Sep 17 00:00:00 2001 From: Florian RICHER Date: Sun, 18 May 2025 21:00:22 +0200 Subject: [PATCH 024/105] render_plugin: Autocreate swapchain and update --- crates/engine_render/src/lib.rs | 14 +- crates/engine_render/src/window/mod.rs | 197 +++++++++++++++++- crates/engine_vulkan/src/lib.rs | 3 - .../src/window_render_context.rs | 125 ----------- src/game/mod.rs | 9 - 5 files changed, 201 insertions(+), 147 deletions(-) delete mode 100644 crates/engine_vulkan/src/window_render_context.rs diff --git a/crates/engine_render/src/lib.rs b/crates/engine_render/src/lib.rs index f42013e..ccbc39d 100644 --- a/crates/engine_render/src/lib.rs +++ b/crates/engine_render/src/lib.rs @@ -6,7 +6,7 @@ use bevy_ecs::{ }; use engine_vulkan::{ VulkanCommandBufferAllocator, VulkanDescriptorSetAllocator, VulkanDevice, VulkanGraphicsQueue, - VulkanMemoryAllocator, + VulkanInstance, VulkanMemoryAllocator, }; use engine_window::raw_handle::WindowWrapper; use window::WindowRenderPlugin; @@ -31,7 +31,7 @@ impl Render { let mut schedule = Schedule::new(Self); - schedule.configure_sets((Prepare, Queue, Render, Present).chain()); + schedule.configure_sets((ManageViews, Prepare, Queue, Render, Present).chain()); schedule } @@ -49,6 +49,7 @@ impl Plugin for RenderPlugin { let world = app.world(); world.get_resource::().is_some() + && world.get_resource::().is_some() && world.get_resource::().is_some() && world.get_resource::().is_some() && world.get_resource::().is_some() @@ -71,10 +72,6 @@ impl Plugin for RenderPlugin { app.add_plugins(WindowRenderPlugin); } - - fn cleanup(&self, app: &mut App) { - app.remove_sub_app(RenderApp); - } } fn extract_app_resources(world: &mut World, render_world: &mut World) { @@ -82,6 +79,10 @@ fn extract_app_resources(world: &mut World, render_world: &mut World) { .get_resource::() .expect("Failed to get WindowWrapper. Check is WindowPlugin is added before RenderPlugin."); + let vulkan_instance = world.get_resource::().expect( + "Failed to get Vulkan instance. Check is VulkanPlugin is added before RenderPlugin.", + ); + let vulkan_device = world .get_resource::() .expect("Failed to get Vulkan device. Check is VulkanPlugin is added before RenderPlugin."); @@ -102,6 +103,7 @@ fn extract_app_resources(world: &mut World, render_world: &mut World) { .get_resource::() .expect("Failed to get Vulkan descriptor set allocator. Check is VulkanPlugin is added before RenderPlugin."); + render_world.insert_resource(vulkan_instance.clone()); render_world.insert_resource(vulkan_device.clone()); render_world.insert_resource(vulkan_graphics_queue.clone()); render_world.insert_resource(vulkan_memory_allocator.clone()); diff --git a/crates/engine_render/src/window/mod.rs b/crates/engine_render/src/window/mod.rs index bcb3ea6..9af4184 100644 --- a/crates/engine_render/src/window/mod.rs +++ b/crates/engine_render/src/window/mod.rs @@ -1,13 +1,21 @@ use std::sync::Arc; use bevy_app::{App, Plugin}; -use bevy_ecs::resource::Resource; +use bevy_ecs::{ + resource::Resource, + schedule::IntoScheduleConfigs, + system::{Res, ResMut}, +}; +use engine_vulkan::{VulkanDevice, VulkanInstance}; +use engine_window::raw_handle::WindowWrapper; use vulkano::{ - image::view::ImageView, pipeline::graphics::viewport::Viewport, swapchain::Swapchain, - sync::GpuFuture, + image::{Image, ImageUsage, view::ImageView}, + pipeline::graphics::viewport::Viewport, + swapchain::{Surface, Swapchain, SwapchainCreateInfo}, + sync::{self, GpuFuture}, }; -use crate::RenderApp; +use super::{Render, RenderApp, RenderSystems}; pub struct WindowSurfaceData { pub swapchain: Arc, @@ -31,5 +39,186 @@ impl Plugin for WindowRenderPlugin { .expect("Failed to get RenderApp. Check is RenderPlugin is added."); render_app.init_resource::(); + + render_app.add_systems( + Render, + create_window_surface + .in_set(RenderSystems::ManageViews) + .run_if(need_create_window_surface) + .before(need_update_window_surface), + ); + + render_app.add_systems( + Render, + update_window_surface + .in_set(RenderSystems::ManageViews) + .run_if(need_update_window_surface), + ); } } + +fn need_create_window_surface(window_surface: Res) -> bool { + window_surface.surface.is_none() +} + +fn create_window_surface( + mut window_surface: ResMut, + window_handle: Res, + vulkan_instance: Res, + vulkan_device: Res, +) { + let window_size = window_handle.0.inner_size(); + + let surface = Surface::from_window(vulkan_instance.0.clone(), window_handle.0.clone()) + .expect("Failed to create surface"); + log::debug!("Surface created"); + + let (swapchain, images) = { + let surface_capabilities = vulkan_device + .0 + .physical_device() + .surface_capabilities(&surface, Default::default()) + .unwrap(); + + let (image_format, _) = vulkan_device + .0 + .physical_device() + .surface_formats(&surface, Default::default()) + .unwrap()[0]; + + Swapchain::new( + vulkan_device.0.clone(), + surface, + SwapchainCreateInfo { + // 2 because with some graphics driver, it crash on fullscreen because fullscreen need to min image to works. + min_image_count: surface_capabilities.min_image_count.max(2), + image_format, + image_extent: window_size.into(), + image_usage: ImageUsage::COLOR_ATTACHMENT, + composite_alpha: surface_capabilities + .supported_composite_alpha + .into_iter() + .next() + .unwrap(), + + ..Default::default() + }, + ) + .unwrap() + }; + log_swapchain_info(&swapchain, false); + + let attachment_image_views = window_size_dependent_setup(&images); + + let viewport = Viewport { + offset: [0.0, 0.0], + extent: window_size.into(), + depth_range: 0.0..=1.0, + }; + log_viewport_info(&viewport, false); + + let recreate_swapchain = false; + let previous_frame_end = Some(sync::now(vulkan_device.0.clone()).boxed_send_sync()); + + window_surface.surface = Some(WindowSurfaceData { + swapchain, + attachment_image_views, + viewport, + recreate_swapchain, + previous_frame_end, + }); +} + +fn window_size_dependent_setup(images: &[Arc]) -> Vec> { + images + .iter() + .map(|image| ImageView::new_default(image.clone()).unwrap()) + .collect::>() +} + +fn need_update_window_surface(window_surface: Res) -> bool { + match &window_surface.surface { + Some(surface) => surface.recreate_swapchain, + None => false, + } +} + +fn update_window_surface( + mut window_surface: ResMut, + window_handle: Res, +) { + if window_surface.surface.is_none() { + return; + } + + let window_surface = window_surface.surface.as_mut().unwrap(); + + if !window_surface.recreate_swapchain { + return; + } + + let window_size = window_handle.0.inner_size(); + let (new_swapchain, new_images) = window_surface + .swapchain + .recreate(SwapchainCreateInfo { + image_extent: window_size.into(), + ..window_surface.swapchain.create_info() + }) + .expect("Failed to recreate swapchain"); + + window_surface.swapchain = new_swapchain; + window_surface.attachment_image_views = window_size_dependent_setup(&new_images); + window_surface.viewport.extent = window_size.into(); + window_surface.recreate_swapchain = false; + + log_swapchain_info(&window_surface.swapchain, true); + log_viewport_info(&window_surface.viewport, true); +} + +fn log_swapchain_info(swapchain: &Swapchain, recreate_swapchain: bool) { + if recreate_swapchain { + log::debug!("Swapchain recreated"); + } else { + log::debug!("Swapchain created"); + } + log::debug!( + "\tMin image count: {}", + swapchain.create_info().min_image_count + ); + log::debug!("\tImage format: {:?}", swapchain.create_info().image_format); + log::debug!("\tImage extent: {:?}", swapchain.create_info().image_extent); + log::debug!("\tImage usage: {:?}", swapchain.create_info().image_usage); + log::debug!( + "\tComposite alpha: {:?}", + swapchain.create_info().composite_alpha + ); + log::debug!("\tPresent mode: {:?}", swapchain.create_info().present_mode); + log::debug!( + "\tImage sharing: {:?}", + swapchain.create_info().image_sharing + ); + log::debug!( + "\tPre transform: {:?}", + swapchain.create_info().pre_transform + ); + log::debug!( + "\tComposite alpha: {:?}", + swapchain.create_info().composite_alpha + ); + log::debug!("\tPresent mode: {:?}", swapchain.create_info().present_mode); + log::debug!( + "\tFull screen exclusive: {:?}", + swapchain.create_info().full_screen_exclusive + ); +} + +fn log_viewport_info(viewport: &Viewport, recreate_viewport: bool) { + if recreate_viewport { + log::debug!("Viewport recreated"); + } else { + log::debug!("Viewport created"); + } + log::debug!("\tOffset: {:?}", viewport.offset); + log::debug!("\tExtent: {:?}", viewport.extent); + log::debug!("\tDepth range: {:?}", viewport.depth_range); +} diff --git a/crates/engine_vulkan/src/lib.rs b/crates/engine_vulkan/src/lib.rs index 65e9349..4f5129b 100644 --- a/crates/engine_vulkan/src/lib.rs +++ b/crates/engine_vulkan/src/lib.rs @@ -1,7 +1,6 @@ use std::sync::Arc; use bevy_ecs::resource::Resource; -use engine_window::raw_handle::WindowWrapper; use utils::{device::create_and_insert_device, instance::create_and_insert_instance}; use vulkano::{ command_buffer::allocator::StandardCommandBufferAllocator, @@ -10,12 +9,10 @@ use vulkano::{ instance::Instance, memory::allocator::StandardMemoryAllocator, }; -use window_render_context::WindowRenderContext; use bevy_app::{App, Plugin}; mod utils; -mod window_render_context; #[derive(Resource, Clone)] pub struct VulkanInstance(pub Arc); diff --git a/crates/engine_vulkan/src/window_render_context.rs b/crates/engine_vulkan/src/window_render_context.rs deleted file mode 100644 index 95b3d90..0000000 --- a/crates/engine_vulkan/src/window_render_context.rs +++ /dev/null @@ -1,125 +0,0 @@ -use bevy_app::App; -use bevy_ecs::resource::Resource; -use engine_window::raw_handle::WindowWrapper; -use std::sync::Arc; -use vulkano::image::view::ImageView; -use vulkano::image::{Image, ImageUsage}; -use vulkano::pipeline::graphics::viewport::Viewport; -use vulkano::swapchain::{Surface, Swapchain, SwapchainCreateInfo}; -use vulkano::sync::{self, GpuFuture}; -use vulkano::{Validated, VulkanError}; -use winit::window::Window; - -use crate::{VulkanDevice, VulkanInstance}; - -#[derive(Resource)] -pub struct WindowRenderContext { - pub window: Arc, - pub swapchain: Arc, - pub attachment_image_views: Vec>, - pub viewport: Viewport, - pub recreate_swapchain: bool, - pub previous_frame_end: Option>, -} - -impl From<&App> for WindowRenderContext { - fn from(app: &App) -> Self { - let world = app.world(); - let window_handle = world - .get_resource::() - .expect("Failed to find window handle"); - let vulkan_instance = world - .get_resource::() - .expect("Failed to find vulkan instance"); - let vulkan_device = world - .get_resource::() - .expect("Failed to find vulkan device"); - - let window_size = window_handle.0.inner_size(); - - let surface = Surface::from_window(vulkan_instance.0.clone(), window_handle.0.clone()) - .expect("Failed to create surface"); - - let (swapchain, images) = { - let surface_capabilities = vulkan_device - .0 - .physical_device() - .surface_capabilities(&surface, Default::default()) - .unwrap(); - - let (image_format, _) = vulkan_device - .0 - .physical_device() - .surface_formats(&surface, Default::default()) - .unwrap()[0]; - - Swapchain::new( - vulkan_device.0.clone(), - surface, - SwapchainCreateInfo { - // 2 because with some graphics driver, it crash on fullscreen because fullscreen need to min image to works. - min_image_count: surface_capabilities.min_image_count.max(2), - image_format, - image_extent: window_size.into(), - image_usage: ImageUsage::COLOR_ATTACHMENT, - composite_alpha: surface_capabilities - .supported_composite_alpha - .into_iter() - .next() - .unwrap(), - - ..Default::default() - }, - ) - .unwrap() - }; - - let attachment_image_views = window_size_dependent_setup(&images); - - let viewport = Viewport { - offset: [0.0, 0.0], - extent: window_size.into(), - depth_range: 0.0..=1.0, - }; - - let recreate_swapchain = false; - let previous_frame_end = Some(sync::now(vulkan_device.0.clone()).boxed_send_sync()); - - Self { - window: window_handle.0.clone(), - swapchain, - attachment_image_views, - viewport, - recreate_swapchain, - previous_frame_end, - } - } -} - -impl WindowRenderContext { - pub fn update_swapchain(&mut self) -> Result<(), Validated> { - if !self.recreate_swapchain { - return Ok(()); - } - - let window_size = self.window.inner_size(); - let (new_swapchain, new_images) = self.swapchain.recreate(SwapchainCreateInfo { - image_extent: window_size.into(), - ..self.swapchain.create_info() - })?; - - self.swapchain = new_swapchain; - self.attachment_image_views = window_size_dependent_setup(&new_images); - self.viewport.extent = window_size.into(); - self.recreate_swapchain = false; - - Ok(()) - } -} - -fn window_size_dependent_setup(images: &[Arc]) -> Vec> { - images - .iter() - .map(|image| ImageView::new_default(image.clone()).unwrap()) - .collect::>() -} diff --git a/src/game/mod.rs b/src/game/mod.rs index 9c5b547..e04b2a7 100644 --- a/src/game/mod.rs +++ b/src/game/mod.rs @@ -33,13 +33,4 @@ pub fn init(app: &mut App) { VulkanPlugin { vulkan_config }, RenderPlugin, )); - - // app.get_sub_app_mut(RenderApp) - // .expect("Failed to get RenderApp. Check is RenderPlugin is added.") - // .add_systems(Render, test_system.in_set(RenderSystems::Prepare)); } - -// fn test_system(vulkan_device: Res, vulkan_graphics_queue: Res) { -// log::trace!("vulkan_device: {:?}", vulkan_device.0); -// log::trace!("vulkan_graphics_queue: {:?}", vulkan_graphics_queue.0); -// } From 7951b05ab3641e936faf0d02acd39cfdca48d1eb Mon Sep 17 00:00:00 2001 From: Florian RICHER Date: Mon, 19 May 2025 23:30:35 +0200 Subject: [PATCH 025/105] Add README.md --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..c74b5d4 --- /dev/null +++ b/README.md @@ -0,0 +1,6 @@ +# Project + +## Usefull links + +- https://vulkan-tutorial.com/fr/Introduction +- https://github.com/bwasty/vulkan-tutorial-rs From 18174e42e984de129b22c25473a268a7aea2b9ec Mon Sep 17 00:00:00 2001 From: Florian RICHER Date: Wed, 21 May 2025 13:42:36 +0200 Subject: [PATCH 026/105] render_vulkan: Fixes - Avoid create device for each physical device during support checking - Avoid to select different queue family index and add support of creating multiple queues on same queue family - Log select queue family index for each queue type --- crates/engine_vulkan/src/utils/device.rs | 115 ++++++++++++++------- crates/engine_vulkan/src/vulkan_context.rs | 76 -------------- 2 files changed, 77 insertions(+), 114 deletions(-) delete mode 100644 crates/engine_vulkan/src/vulkan_context.rs diff --git a/crates/engine_vulkan/src/utils/device.rs b/crates/engine_vulkan/src/utils/device.rs index 6c4b49f..e44ac52 100644 --- a/crates/engine_vulkan/src/utils/device.rs +++ b/crates/engine_vulkan/src/utils/device.rs @@ -1,8 +1,9 @@ -use std::sync::Arc; +use std::{collections::HashMap, sync::Arc}; use bevy_ecs::world::World; use engine_window::raw_handle::DisplayHandleWrapper; use vulkano::{ + VulkanError, command_buffer::allocator::StandardCommandBufferAllocator, descriptor_set::allocator::StandardDescriptorSetAllocator, device::{ @@ -86,22 +87,30 @@ fn pick_physical_device(world: &World, config: &VulkanConfig) -> Option() .expect("Failed to get VulkanInstance during vulkan plugin initialization"); - instance + let result = instance .0 .enumerate_physical_devices() .expect("Failed to enumerate physical devices") - .filter_map(|p| check_physical_device(world, &p, config)) - .min_by_key( - |p| match p.device.physical_device().properties().device_type { - PhysicalDeviceType::DiscreteGpu => 0, - PhysicalDeviceType::IntegratedGpu => 1, - PhysicalDeviceType::VirtualGpu => 2, - PhysicalDeviceType::Cpu => 3, - PhysicalDeviceType::Other => 4, - _ => 5, - }, - ) - .take() + .filter_map(|p| check_physical_device_support(world, &p, config).and_then(|r| Some((p, r)))) + .min_by_key(|(p, _)| match p.properties().device_type { + PhysicalDeviceType::DiscreteGpu => 0, + PhysicalDeviceType::IntegratedGpu => 1, + PhysicalDeviceType::VirtualGpu => 2, + PhysicalDeviceType::Cpu => 3, + PhysicalDeviceType::Other => 4, + _ => 5, + }) + .take(); + + match result { + Some((p, (device_extensions, picked_queues_info))) => Some(create_device( + config, + &p, + device_extensions, + &picked_queues_info, + )), + None => None, + } } fn check_device_extensions_support( @@ -147,7 +156,7 @@ fn check_queues_support( let mut transfer_queue_family_index: Option = None; for (i, queue_family_property) in physical_device.queue_family_properties().iter().enumerate() { - if config.with_graphics_queue { + if config.with_graphics_queue && graphics_queue_family_index.is_none() { let graphics_supported = queue_family_property .queue_flags .intersects(QueueFlags::GRAPHICS); @@ -169,7 +178,7 @@ fn check_queues_support( } } - if config.with_compute_queue { + if config.with_compute_queue && compute_queue_family_index.is_none() { let compute_supported = queue_family_property .queue_flags .intersects(QueueFlags::COMPUTE); @@ -179,7 +188,7 @@ fn check_queues_support( } } - if config.with_transfer_queue { + if config.with_transfer_queue && transfer_queue_family_index.is_none() { let transfer_supported = queue_family_property .queue_flags .intersects(QueueFlags::TRANSFER); @@ -193,7 +202,10 @@ fn check_queues_support( if !config.with_graphics_queue { log::debug!("\t\t[SKIPPED] Graphics queue is not required"); } else if graphics_queue_family_index.is_some() { - log::debug!("\t\t[OK] Graphics queue is supported"); + log::debug!( + "\t\t[OK] Graphics queue is supported (family index: {:?})", + graphics_queue_family_index + ); } else { log::debug!("\t\t[FAILED] Graphics queue is not supported"); return None; @@ -202,7 +214,10 @@ fn check_queues_support( if !config.with_compute_queue { log::debug!("\t\t[SKIPPED] Compute queue is not required"); } else if compute_queue_family_index.is_some() { - log::debug!("\t\t[OK] Compute queue is supported"); + log::debug!( + "\t\t[OK] Compute queue is supported (family index: {:?})", + compute_queue_family_index + ); } else { log::debug!("\t\t[FAILED] Compute queue is not supported"); return None; @@ -211,7 +226,10 @@ fn check_queues_support( if !config.with_transfer_queue { log::debug!("\t\t[SKIPPED] Transfer queue is not required"); } else if transfer_queue_family_index.is_some() { - log::debug!("\t\t[OK] Transfer queue is supported"); + log::debug!( + "\t\t[OK] Transfer queue is supported (family index: {:?})", + transfer_queue_family_index + ); } else { log::debug!("\t\t[FAILED] Transfer queue is not supported"); return None; @@ -224,11 +242,11 @@ fn check_queues_support( }) } -fn check_physical_device( +fn check_physical_device_support( world: &World, physical_device: &Arc, config: &VulkanConfig, -) -> Option { +) -> Option<(DeviceExtensions, PickedQueuesInfo)> { log::debug!("Checking physical device"); log::debug!("\tProperties"); log::debug!("\t\tName: {}", physical_device.properties().device_name); @@ -242,33 +260,54 @@ fn check_physical_device( let device_extensions = check_device_extensions_support(physical_device, config)?; let picked_queues_info = check_queues_support(world, physical_device, config)?; - let mut queue_create_infos = vec![]; + Some((device_extensions, picked_queues_info)) +} + +fn create_device( + config: &VulkanConfig, + physical_device: &Arc, + device_extensions: DeviceExtensions, + picked_queues_info: &PickedQueuesInfo, +) -> PickedDevice { + let mut queue_create_infos = HashMap::::new(); if config.with_graphics_queue { - queue_create_infos.push(QueueCreateInfo { - queue_family_index: picked_queues_info.graphics_queue_family_index.unwrap(), - ..Default::default() - }); + let entry = queue_create_infos + .entry(picked_queues_info.graphics_queue_family_index.unwrap()) + .or_insert(QueueCreateInfo { + queue_family_index: picked_queues_info.graphics_queue_family_index.unwrap(), + ..Default::default() + }); + + entry.queues.push(1.0); } if config.with_compute_queue { - queue_create_infos.push(QueueCreateInfo { - queue_family_index: picked_queues_info.compute_queue_family_index.unwrap(), - ..Default::default() - }); + let entry = queue_create_infos + .entry(picked_queues_info.compute_queue_family_index.unwrap()) + .or_insert(QueueCreateInfo { + queue_family_index: picked_queues_info.compute_queue_family_index.unwrap(), + ..Default::default() + }); + + entry.queues.push(1.0); } if config.with_transfer_queue { - queue_create_infos.push(QueueCreateInfo { - queue_family_index: picked_queues_info.transfer_queue_family_index.unwrap(), - ..Default::default() - }); + let entry = queue_create_infos + .entry(picked_queues_info.transfer_queue_family_index.unwrap()) + .or_insert(QueueCreateInfo { + queue_family_index: picked_queues_info.transfer_queue_family_index.unwrap(), + ..Default::default() + }); + + entry.queues.push(1.0); } let (device, mut queues) = Device::new( physical_device.clone(), DeviceCreateInfo { - queue_create_infos, + queue_create_infos: queue_create_infos.values().cloned().collect(), enabled_extensions: device_extensions, enabled_features: config.device_features, ..Default::default() @@ -292,10 +331,10 @@ fn check_physical_device( transfer_queue = queues.next(); } - Some(PickedDevice { + PickedDevice { device, graphics_queue, compute_queue, transfer_queue, - }) + } } diff --git a/crates/engine_vulkan/src/vulkan_context.rs b/crates/engine_vulkan/src/vulkan_context.rs deleted file mode 100644 index 08ff1ac..0000000 --- a/crates/engine_vulkan/src/vulkan_context.rs +++ /dev/null @@ -1,76 +0,0 @@ -use std::{any::Any, sync::Arc}; - -use bevy_app::App; -use bevy_ecs::resource::Resource; -use engine_window::raw_handle::DisplayHandleWrapper; -use vulkano::{ - command_buffer::{ - AutoCommandBufferBuilder, CommandBufferUsage, PrimaryAutoCommandBuffer, - allocator::StandardCommandBufferAllocator, - }, - descriptor_set::allocator::StandardDescriptorSetAllocator, - device::{Device, Queue}, - instance::Instance, - memory::allocator::StandardMemoryAllocator, - swapchain::Surface, -}; -use winit::raw_window_handle::{HasDisplayHandle, HasWindowHandle}; - -use super::utils; - -#[derive(Resource)] -pub struct VulkanContext { - pub instance: Arc, - pub device: Arc, - pub graphics_queue: Arc, - - pub memory_allocator: Arc, - pub command_buffer_allocator: Arc, - pub descriptor_set_allocator: Arc, -} - -impl VulkanContext { - pub fn create_surface( - &self, - window: Arc, - ) -> Arc { - Surface::from_window(self.instance.clone(), window).unwrap() - } - - pub fn create_render_builder(&self) -> AutoCommandBufferBuilder { - AutoCommandBufferBuilder::primary( - self.command_buffer_allocator.clone(), - self.graphics_queue.queue_family_index(), - CommandBufferUsage::OneTimeSubmit, - ) - .unwrap() - } -} - -impl From<&App> for VulkanContext { - fn from(app: &App) -> Self { - let (device, mut queues) = utils::pick_graphics_device(&instance, &display_handle.0); - let graphics_queue = queues.next().unwrap(); - - let memory_allocator = Arc::new(StandardMemoryAllocator::new_default(device.clone())); - - let command_buffer_allocator = Arc::new(StandardCommandBufferAllocator::new( - device.clone(), - Default::default(), - )); - - let descriptor_set_allocator = Arc::new(StandardDescriptorSetAllocator::new( - device.clone(), - Default::default(), - )); - - Self { - instance: instance.clone(), - device, - graphics_queue, - memory_allocator, - command_buffer_allocator, - descriptor_set_allocator, - } - } -} From 9ea8721346c1060f95b5fd7515a814a97221b0ac Mon Sep 17 00:00:00 2001 From: Florian RICHER Date: Wed, 21 May 2025 13:46:13 +0200 Subject: [PATCH 027/105] render_vulkan: Avoid continue loop if all queues is found --- crates/engine_vulkan/src/utils/device.rs | 30 ++++++++++++++---------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/crates/engine_vulkan/src/utils/device.rs b/crates/engine_vulkan/src/utils/device.rs index e44ac52..38bf5fd 100644 --- a/crates/engine_vulkan/src/utils/device.rs +++ b/crates/engine_vulkan/src/utils/device.rs @@ -87,7 +87,7 @@ fn pick_physical_device(world: &World, config: &VulkanConfig) -> Option() .expect("Failed to get VulkanInstance during vulkan plugin initialization"); - let result = instance + instance .0 .enumerate_physical_devices() .expect("Failed to enumerate physical devices") @@ -100,17 +100,15 @@ fn pick_physical_device(world: &World, config: &VulkanConfig) -> Option 4, _ => 5, }) - .take(); - - match result { - Some((p, (device_extensions, picked_queues_info))) => Some(create_device( - config, - &p, - device_extensions, - &picked_queues_info, - )), - None => None, - } + .take() + .and_then(|(p, (device_extensions, picked_queues_info))| { + Some(create_device( + config, + &p, + device_extensions, + &picked_queues_info, + )) + }) } fn check_device_extensions_support( @@ -197,6 +195,14 @@ fn check_queues_support( transfer_queue_family_index = Some(i as u32); } } + + if (!config.with_graphics_queue || graphics_queue_family_index.is_some()) + && (!config.with_compute_queue || compute_queue_family_index.is_some()) + && (!config.with_transfer_queue || transfer_queue_family_index.is_some()) + { + // We found all required queues, no need to continue iterating + break; + } } if !config.with_graphics_queue { From 4676b1b5b8db38ab2b3cddfca20ad96fad034eb2 Mon Sep 17 00:00:00 2001 From: Florian RICHER Date: Fri, 23 May 2025 14:10:51 +0200 Subject: [PATCH 028/105] engine_vulkan: Refactor queue finding --- .../engine_vulkan/src/{utils => }/device.rs | 178 +++--------------- .../engine_vulkan/src/{utils => }/instance.rs | 0 crates/engine_vulkan/src/lib.rs | 8 +- crates/engine_vulkan/src/queues.rs | 174 +++++++++++++++++ crates/engine_vulkan/src/utils/mod.rs | 2 - 5 files changed, 202 insertions(+), 160 deletions(-) rename crates/engine_vulkan/src/{utils => }/device.rs (51%) rename crates/engine_vulkan/src/{utils => }/instance.rs (100%) create mode 100644 crates/engine_vulkan/src/queues.rs delete mode 100644 crates/engine_vulkan/src/utils/mod.rs diff --git a/crates/engine_vulkan/src/utils/device.rs b/crates/engine_vulkan/src/device.rs similarity index 51% rename from crates/engine_vulkan/src/utils/device.rs rename to crates/engine_vulkan/src/device.rs index 38bf5fd..f20335f 100644 --- a/crates/engine_vulkan/src/utils/device.rs +++ b/crates/engine_vulkan/src/device.rs @@ -1,13 +1,12 @@ -use std::{collections::HashMap, sync::Arc}; +use std::sync::Arc; use bevy_ecs::world::World; use engine_window::raw_handle::DisplayHandleWrapper; use vulkano::{ - VulkanError, command_buffer::allocator::StandardCommandBufferAllocator, descriptor_set::allocator::StandardDescriptorSetAllocator, device::{ - Device, DeviceCreateInfo, DeviceExtensions, Queue, QueueCreateInfo, QueueFlags, + Device, DeviceCreateInfo, DeviceExtensions, Queue, physical::{PhysicalDevice, PhysicalDeviceType}, }, memory::allocator::StandardMemoryAllocator, @@ -16,6 +15,7 @@ use vulkano::{ use crate::{ VulkanCommandBufferAllocator, VulkanComputeQueue, VulkanConfig, VulkanDescriptorSetAllocator, VulkanDevice, VulkanGraphicsQueue, VulkanInstance, VulkanMemoryAllocator, VulkanTransferQueue, + queues::{VulkanQueueFamilyIndices, VulkanQueuesQuery, find_queues_family_indices}, }; pub fn create_and_insert_device(world: &mut World, config: &VulkanConfig) { @@ -101,12 +101,12 @@ fn pick_physical_device(world: &World, config: &VulkanConfig) -> Option 5, }) .take() - .and_then(|(p, (device_extensions, picked_queues_info))| { + .and_then(|(p, (device_extensions, queue_family_indices))| { Some(create_device( config, &p, device_extensions, - &picked_queues_info, + queue_family_indices, )) }) } @@ -138,121 +138,11 @@ fn check_device_extensions_support( } } -struct PickedQueuesInfo { - graphics_queue_family_index: Option, - compute_queue_family_index: Option, - transfer_queue_family_index: Option, -} - -fn check_queues_support( - world: &World, - physical_device: &Arc, - config: &VulkanConfig, -) -> Option { - let mut graphics_queue_family_index: Option = None; - let mut compute_queue_family_index: Option = None; - let mut transfer_queue_family_index: Option = None; - - for (i, queue_family_property) in physical_device.queue_family_properties().iter().enumerate() { - if config.with_graphics_queue && graphics_queue_family_index.is_none() { - let graphics_supported = queue_family_property - .queue_flags - .intersects(QueueFlags::GRAPHICS); - - let presentation_valid = if config.with_window_surface { - let display_handle = world - .get_resource::() - .expect("DisplayHandleWrapper must be added before VulkanPlugin"); - - physical_device - .presentation_support(i as u32, &display_handle.0) - .expect("Failed to check presentation support") - } else { - true - }; - - if graphics_supported && presentation_valid { - graphics_queue_family_index = Some(i as u32); - } - } - - if config.with_compute_queue && compute_queue_family_index.is_none() { - let compute_supported = queue_family_property - .queue_flags - .intersects(QueueFlags::COMPUTE); - - if compute_supported { - compute_queue_family_index = Some(i as u32); - } - } - - if config.with_transfer_queue && transfer_queue_family_index.is_none() { - let transfer_supported = queue_family_property - .queue_flags - .intersects(QueueFlags::TRANSFER); - - if transfer_supported { - transfer_queue_family_index = Some(i as u32); - } - } - - if (!config.with_graphics_queue || graphics_queue_family_index.is_some()) - && (!config.with_compute_queue || compute_queue_family_index.is_some()) - && (!config.with_transfer_queue || transfer_queue_family_index.is_some()) - { - // We found all required queues, no need to continue iterating - break; - } - } - - if !config.with_graphics_queue { - log::debug!("\t\t[SKIPPED] Graphics queue is not required"); - } else if graphics_queue_family_index.is_some() { - log::debug!( - "\t\t[OK] Graphics queue is supported (family index: {:?})", - graphics_queue_family_index - ); - } else { - log::debug!("\t\t[FAILED] Graphics queue is not supported"); - return None; - } - - if !config.with_compute_queue { - log::debug!("\t\t[SKIPPED] Compute queue is not required"); - } else if compute_queue_family_index.is_some() { - log::debug!( - "\t\t[OK] Compute queue is supported (family index: {:?})", - compute_queue_family_index - ); - } else { - log::debug!("\t\t[FAILED] Compute queue is not supported"); - return None; - } - - if !config.with_transfer_queue { - log::debug!("\t\t[SKIPPED] Transfer queue is not required"); - } else if transfer_queue_family_index.is_some() { - log::debug!( - "\t\t[OK] Transfer queue is supported (family index: {:?})", - transfer_queue_family_index - ); - } else { - log::debug!("\t\t[FAILED] Transfer queue is not supported"); - return None; - } - - Some(PickedQueuesInfo { - graphics_queue_family_index, - compute_queue_family_index, - transfer_queue_family_index, - }) -} - fn check_physical_device_support( world: &World, physical_device: &Arc, config: &VulkanConfig, -) -> Option<(DeviceExtensions, PickedQueuesInfo)> { +) -> Option<(DeviceExtensions, VulkanQueueFamilyIndices)> { log::debug!("Checking physical device"); log::debug!("\tProperties"); log::debug!("\t\tName: {}", physical_device.properties().device_name); @@ -264,56 +154,34 @@ fn check_physical_device_support( log::debug!("\tRequired supports checking report"); let device_extensions = check_device_extensions_support(physical_device, config)?; - let picked_queues_info = check_queues_support(world, physical_device, config)?; - Some((device_extensions, picked_queues_info)) + let display_handle = world + .get_resource::() + .expect("DisplayHandleWrapper must be added before VulkanPlugin"); + + let queue_support_query = VulkanQueuesQuery { + with_surface: Some(&display_handle), + with_graphics_queue: config.with_graphics_queue, + with_compute_queue: config.with_compute_queue, + with_transfer_queue: config.with_transfer_queue, + }; + + let queue_family_indices = find_queues_family_indices(physical_device, &queue_support_query); + log::debug!("\t\tQueue family indices: {:#?}", queue_family_indices); + + Some((device_extensions, queue_family_indices)) } fn create_device( config: &VulkanConfig, physical_device: &Arc, device_extensions: DeviceExtensions, - picked_queues_info: &PickedQueuesInfo, + queue_family_indices: VulkanQueueFamilyIndices, ) -> PickedDevice { - let mut queue_create_infos = HashMap::::new(); - - if config.with_graphics_queue { - let entry = queue_create_infos - .entry(picked_queues_info.graphics_queue_family_index.unwrap()) - .or_insert(QueueCreateInfo { - queue_family_index: picked_queues_info.graphics_queue_family_index.unwrap(), - ..Default::default() - }); - - entry.queues.push(1.0); - } - - if config.with_compute_queue { - let entry = queue_create_infos - .entry(picked_queues_info.compute_queue_family_index.unwrap()) - .or_insert(QueueCreateInfo { - queue_family_index: picked_queues_info.compute_queue_family_index.unwrap(), - ..Default::default() - }); - - entry.queues.push(1.0); - } - - if config.with_transfer_queue { - let entry = queue_create_infos - .entry(picked_queues_info.transfer_queue_family_index.unwrap()) - .or_insert(QueueCreateInfo { - queue_family_index: picked_queues_info.transfer_queue_family_index.unwrap(), - ..Default::default() - }); - - entry.queues.push(1.0); - } - let (device, mut queues) = Device::new( physical_device.clone(), DeviceCreateInfo { - queue_create_infos: queue_create_infos.values().cloned().collect(), + queue_create_infos: queue_family_indices.into(), enabled_extensions: device_extensions, enabled_features: config.device_features, ..Default::default() diff --git a/crates/engine_vulkan/src/utils/instance.rs b/crates/engine_vulkan/src/instance.rs similarity index 100% rename from crates/engine_vulkan/src/utils/instance.rs rename to crates/engine_vulkan/src/instance.rs diff --git a/crates/engine_vulkan/src/lib.rs b/crates/engine_vulkan/src/lib.rs index 4f5129b..38cb456 100644 --- a/crates/engine_vulkan/src/lib.rs +++ b/crates/engine_vulkan/src/lib.rs @@ -1,7 +1,7 @@ use std::sync::Arc; +use bevy_app::{App, Plugin}; use bevy_ecs::resource::Resource; -use utils::{device::create_and_insert_device, instance::create_and_insert_instance}; use vulkano::{ command_buffer::allocator::StandardCommandBufferAllocator, descriptor_set::allocator::StandardDescriptorSetAllocator, @@ -10,9 +10,11 @@ use vulkano::{ memory::allocator::StandardMemoryAllocator, }; -use bevy_app::{App, Plugin}; +mod device; +mod instance; +mod queues; -mod utils; +use crate::{device::create_and_insert_device, instance::create_and_insert_instance}; #[derive(Resource, Clone)] pub struct VulkanInstance(pub Arc); diff --git a/crates/engine_vulkan/src/queues.rs b/crates/engine_vulkan/src/queues.rs new file mode 100644 index 0000000..fc49dcc --- /dev/null +++ b/crates/engine_vulkan/src/queues.rs @@ -0,0 +1,174 @@ +use std::{collections::HashMap, sync::Arc}; + +use engine_window::raw_handle::DisplayHandleWrapper; +use vulkano::device::{ + QueueCreateInfo, QueueFamilyProperties, QueueFlags, physical::PhysicalDevice, +}; + +#[derive(Debug)] +pub enum VulkanQueueFamilyStatus { + Unused, + NotSupported, + Supported(u32), +} + +impl VulkanQueueFamilyStatus { + pub fn can_be_set(&self) -> bool { + match self { + VulkanQueueFamilyStatus::NotSupported => true, + _ => false, + } + } +} + +/// Convert a boolean to a VulkanQueueFamilyStatus as default value +impl From for VulkanQueueFamilyStatus { + fn from(value: bool) -> Self { + if value { + VulkanQueueFamilyStatus::NotSupported + } else { + VulkanQueueFamilyStatus::Unused + } + } +} + +/// Convert a DisplayHandleWrapper Option to a VulkanQueueFamilyStatus as default value +impl From> for VulkanQueueFamilyStatus { + fn from(value: Option<&DisplayHandleWrapper>) -> Self { + if value.is_some() { + VulkanQueueFamilyStatus::NotSupported + } else { + VulkanQueueFamilyStatus::Unused + } + } +} + +#[derive(Debug)] +pub struct VulkanQueueFamilyIndices { + pub present_queue_family_index: VulkanQueueFamilyStatus, + pub graphics_queue_family_index: VulkanQueueFamilyStatus, + pub compute_queue_family_index: VulkanQueueFamilyStatus, + pub transfer_queue_family_index: VulkanQueueFamilyStatus, +} + +impl From for Vec { + fn from(indices: VulkanQueueFamilyIndices) -> Self { + vec![ + indices.present_queue_family_index, + indices.graphics_queue_family_index, + indices.compute_queue_family_index, + indices.transfer_queue_family_index, + ] + } +} + +impl From for Vec { + fn from(indices: VulkanQueueFamilyIndices) -> Self { + let mut queue_create_infos = HashMap::::new(); + + let statuses: Vec = indices.into(); + + for status in statuses.iter() { + match status { + VulkanQueueFamilyStatus::Supported(index) => { + let entry = queue_create_infos.entry(*index).or_insert(QueueCreateInfo { + queue_family_index: *index, + ..Default::default() + }); + + entry.queues.push(1.0); + } + _ => {} + } + } + + queue_create_infos + .into_iter() + .map(|(_, value)| value) + .collect() + } +} + +/// Convert a VulkanQueuesQuery to a VulkanQueuesFamilyIndices as default value +impl<'a> From<&'a VulkanQueuesQuery<'a>> for VulkanQueueFamilyIndices { + fn from(query: &'a VulkanQueuesQuery<'a>) -> Self { + VulkanQueueFamilyIndices { + present_queue_family_index: query.with_surface.into(), + graphics_queue_family_index: query.with_graphics_queue.into(), + compute_queue_family_index: query.with_compute_queue.into(), + transfer_queue_family_index: query.with_transfer_queue.into(), + } + } +} + +pub struct VulkanQueuesQuery<'a> { + pub with_surface: Option<&'a DisplayHandleWrapper>, + pub with_graphics_queue: bool, + pub with_compute_queue: bool, + pub with_transfer_queue: bool, +} + +pub fn find_queues_family_indices( + physical_device: &Arc, + query: &VulkanQueuesQuery, +) -> VulkanQueueFamilyIndices { + let mut indices: VulkanQueueFamilyIndices = query.into(); + + for (i, queue_family_properties) in physical_device.queue_family_properties().iter().enumerate() + { + if indices.present_queue_family_index.can_be_set() + && check_presentation_support( + physical_device, + i as u32, + &query.with_surface.as_ref().unwrap(), + ) + { + indices.present_queue_family_index = VulkanQueueFamilyStatus::Supported(i as u32); + } + + if indices.graphics_queue_family_index.can_be_set() + && check_queue_support(queue_family_properties, QueueFlags::GRAPHICS) + { + indices.graphics_queue_family_index = VulkanQueueFamilyStatus::Supported(i as u32); + } + + if indices.compute_queue_family_index.can_be_set() + && check_queue_support(queue_family_properties, QueueFlags::COMPUTE) + { + indices.compute_queue_family_index = VulkanQueueFamilyStatus::Supported(i as u32); + } + + if indices.transfer_queue_family_index.can_be_set() + && check_queue_support(queue_family_properties, QueueFlags::TRANSFER) + { + indices.transfer_queue_family_index = VulkanQueueFamilyStatus::Supported(i as u32); + } + + if !indices.present_queue_family_index.can_be_set() + && !indices.graphics_queue_family_index.can_be_set() + && !indices.compute_queue_family_index.can_be_set() + && !indices.transfer_queue_family_index.can_be_set() + { + break; + } + } + + return indices; +} + +fn check_presentation_support( + physical_device: &Arc, + queue_family_index: u32, + surface: &DisplayHandleWrapper, +) -> bool { + physical_device + .presentation_support(queue_family_index, &surface.0) + .unwrap() +} + +fn check_queue_support( + queue_family_property: &QueueFamilyProperties, + queue_flags: QueueFlags, +) -> bool { + queue_family_property.queue_flags.intersects(queue_flags) +} diff --git a/crates/engine_vulkan/src/utils/mod.rs b/crates/engine_vulkan/src/utils/mod.rs deleted file mode 100644 index 09e4b35..0000000 --- a/crates/engine_vulkan/src/utils/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod device; -pub mod instance; From d10212ac3bf492eae34ee79861c02eb9996f15d2 Mon Sep 17 00:00:00 2001 From: Florian RICHER Date: Sat, 24 May 2025 16:44:51 +0200 Subject: [PATCH 029/105] engine_vulkan: Fixes of queue allocation - Avoid to allocate dedicated queue for presentation and use graphics queue for it - Use queueCount of Queue Family Properties for queue support checking --- crates/engine_vulkan/src/queues.rs | 31 +++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/crates/engine_vulkan/src/queues.rs b/crates/engine_vulkan/src/queues.rs index fc49dcc..6b94f76 100644 --- a/crates/engine_vulkan/src/queues.rs +++ b/crates/engine_vulkan/src/queues.rs @@ -45,7 +45,6 @@ impl From> for VulkanQueueFamilyStatus { #[derive(Debug)] pub struct VulkanQueueFamilyIndices { - pub present_queue_family_index: VulkanQueueFamilyStatus, pub graphics_queue_family_index: VulkanQueueFamilyStatus, pub compute_queue_family_index: VulkanQueueFamilyStatus, pub transfer_queue_family_index: VulkanQueueFamilyStatus, @@ -54,7 +53,6 @@ pub struct VulkanQueueFamilyIndices { impl From for Vec { fn from(indices: VulkanQueueFamilyIndices) -> Self { vec![ - indices.present_queue_family_index, indices.graphics_queue_family_index, indices.compute_queue_family_index, indices.transfer_queue_family_index, @@ -93,7 +91,6 @@ impl From for Vec { impl<'a> From<&'a VulkanQueuesQuery<'a>> for VulkanQueueFamilyIndices { fn from(query: &'a VulkanQueuesQuery<'a>) -> Self { VulkanQueueFamilyIndices { - present_queue_family_index: query.with_surface.into(), graphics_queue_family_index: query.with_graphics_queue.into(), compute_queue_family_index: query.with_compute_queue.into(), transfer_queue_family_index: query.with_transfer_queue.into(), @@ -116,36 +113,40 @@ pub fn find_queues_family_indices( for (i, queue_family_properties) in physical_device.queue_family_properties().iter().enumerate() { - if indices.present_queue_family_index.can_be_set() - && check_presentation_support( - physical_device, - i as u32, - &query.with_surface.as_ref().unwrap(), - ) - { - indices.present_queue_family_index = VulkanQueueFamilyStatus::Supported(i as u32); - } + let mut available_queue_count = queue_family_properties.queue_count; if indices.graphics_queue_family_index.can_be_set() && check_queue_support(queue_family_properties, QueueFlags::GRAPHICS) + && available_queue_count > 0 { - indices.graphics_queue_family_index = VulkanQueueFamilyStatus::Supported(i as u32); + if query.with_surface.is_none() + || check_presentation_support( + physical_device, + i as u32, + &query.with_surface.as_ref().unwrap(), + ) + { + indices.graphics_queue_family_index = VulkanQueueFamilyStatus::Supported(i as u32); + available_queue_count -= 1; + } } if indices.compute_queue_family_index.can_be_set() && check_queue_support(queue_family_properties, QueueFlags::COMPUTE) + && available_queue_count > 0 { indices.compute_queue_family_index = VulkanQueueFamilyStatus::Supported(i as u32); + available_queue_count -= 1; } if indices.transfer_queue_family_index.can_be_set() && check_queue_support(queue_family_properties, QueueFlags::TRANSFER) + && available_queue_count > 0 { indices.transfer_queue_family_index = VulkanQueueFamilyStatus::Supported(i as u32); } - if !indices.present_queue_family_index.can_be_set() - && !indices.graphics_queue_family_index.can_be_set() + if !indices.graphics_queue_family_index.can_be_set() && !indices.compute_queue_family_index.can_be_set() && !indices.transfer_queue_family_index.can_be_set() { From 15565d03c1616221b850163efaa5ee7cec697a1a Mon Sep 17 00:00:00 2001 From: Florian RICHER Date: Sat, 24 May 2025 17:02:01 +0200 Subject: [PATCH 030/105] engine_vulkan: Fix allocate more queue as available and add different priorities --- crates/engine_vulkan/src/queues.rs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/crates/engine_vulkan/src/queues.rs b/crates/engine_vulkan/src/queues.rs index 6b94f76..17e6c08 100644 --- a/crates/engine_vulkan/src/queues.rs +++ b/crates/engine_vulkan/src/queues.rs @@ -50,12 +50,12 @@ pub struct VulkanQueueFamilyIndices { pub transfer_queue_family_index: VulkanQueueFamilyStatus, } -impl From for Vec { +impl From for Vec<(VulkanQueueFamilyStatus, f32)> { fn from(indices: VulkanQueueFamilyIndices) -> Self { vec![ - indices.graphics_queue_family_index, - indices.compute_queue_family_index, - indices.transfer_queue_family_index, + (indices.graphics_queue_family_index, 1.0), + (indices.compute_queue_family_index, 0.5), + (indices.transfer_queue_family_index, 0.5), ] } } @@ -64,17 +64,18 @@ impl From for Vec { fn from(indices: VulkanQueueFamilyIndices) -> Self { let mut queue_create_infos = HashMap::::new(); - let statuses: Vec = indices.into(); + let statuses: Vec<(VulkanQueueFamilyStatus, f32)> = indices.into(); - for status in statuses.iter() { + for (status, priority) in statuses.iter() { match status { VulkanQueueFamilyStatus::Supported(index) => { let entry = queue_create_infos.entry(*index).or_insert(QueueCreateInfo { queue_family_index: *index, + queues: Vec::new(), ..Default::default() }); - entry.queues.push(1.0); + entry.queues.push(*priority); } _ => {} } From dbe415d2623388faa1cf91d220e391e75b0c8994 Mon Sep 17 00:00:00 2001 From: Florian RICHER Date: Sat, 24 May 2025 18:04:56 +0200 Subject: [PATCH 031/105] engine_render: Add first render --- crates/engine_render/src/lib.rs | 7 ++ crates/engine_render/src/render.rs | 122 +++++++++++++++++++++++++++++ 2 files changed, 129 insertions(+) create mode 100644 crates/engine_render/src/render.rs diff --git a/crates/engine_render/src/lib.rs b/crates/engine_render/src/lib.rs index ccbc39d..8c869e9 100644 --- a/crates/engine_render/src/lib.rs +++ b/crates/engine_render/src/lib.rs @@ -11,6 +11,7 @@ use engine_vulkan::{ use engine_window::raw_handle::WindowWrapper; use window::WindowRenderPlugin; +pub mod render; pub mod window; #[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)] @@ -65,6 +66,12 @@ impl Plugin for RenderPlugin { let mut render_app = SubApp::new(); render_app.update_schedule = Some(Render.intern()); render_app.add_schedule(Render::base_schedule()); + render_app.add_systems( + Render, + render::render_system + .in_set(RenderSystems::Render) + .run_if(render::can_render), + ); extract_app_resources(app.world_mut(), render_app.world_mut()); diff --git a/crates/engine_render/src/render.rs b/crates/engine_render/src/render.rs new file mode 100644 index 0000000..1a8c95e --- /dev/null +++ b/crates/engine_render/src/render.rs @@ -0,0 +1,122 @@ +use bevy_ecs::system::{Res, ResMut}; +use engine_vulkan::{VulkanCommandBufferAllocator, VulkanDevice, VulkanGraphicsQueue}; +use vulkano::{ + Validated, VulkanError, + command_buffer::{ + AutoCommandBufferBuilder, CommandBufferUsage, RenderingAttachmentInfo, RenderingInfo, + }, + render_pass::{AttachmentLoadOp, AttachmentStoreOp}, + swapchain::{SwapchainAcquireFuture, SwapchainPresentInfo, acquire_next_image}, + sync::{self, GpuFuture}, +}; + +use crate::window::{WindowSurface, WindowSurfaceData}; + +pub fn can_render(window_surface: Res) -> bool { + window_surface.surface.is_some() +} + +pub fn render_system( + mut window_surface: ResMut, + command_buffer_allocator: Res, + graphics_queue: Res, + device: Res, +) { + { + let surface = window_surface.surface.as_ref().unwrap(); + if surface.viewport.extent[0] == 0.0 || surface.viewport.extent[1] == 0.0 { + return; + } + } + + let (image_index, acquire_future) = + match acquire_image(&mut window_surface.surface.as_mut().unwrap()) { + Some(r) => r, + None => return, + }; + + let mut builder = AutoCommandBufferBuilder::primary( + command_buffer_allocator.0.clone(), + graphics_queue.0.queue_family_index(), + CommandBufferUsage::OneTimeSubmit, + ) + .expect("failed to create command buffer builder"); + + { + let surface = window_surface.surface.as_ref().unwrap(); + builder + .begin_rendering(RenderingInfo { + color_attachments: vec![Some(RenderingAttachmentInfo { + load_op: AttachmentLoadOp::Clear, + store_op: AttachmentStoreOp::Store, + clear_value: Some([0.0, 0.0, 0.0, 0.0].into()), + ..RenderingAttachmentInfo::image_view( + surface.attachment_image_views[image_index as usize].clone(), + ) + })], + ..Default::default() + }) + .unwrap() + .set_viewport(0, [surface.viewport.clone()].into_iter().collect()) + .unwrap(); + } + + builder.end_rendering().unwrap(); + + let command_buffer = builder.build().unwrap(); + + { + let surface = window_surface.surface.as_mut().unwrap(); + + let future = surface + .previous_frame_end + .take() + .unwrap() + .join(acquire_future) + .then_execute(graphics_queue.0.clone(), command_buffer) + .unwrap() + .then_swapchain_present( + graphics_queue.0.clone(), + SwapchainPresentInfo::swapchain_image_index(surface.swapchain.clone(), image_index), + ) + .then_signal_fence_and_flush(); + + match future.map_err(Validated::unwrap) { + Ok(future) => { + surface.previous_frame_end = Some(future.boxed_send_sync()); + } + Err(VulkanError::OutOfDate) => { + surface.recreate_swapchain = true; + surface.previous_frame_end = Some(sync::now(device.0.clone()).boxed_send_sync()); + } + Err(e) => { + println!("failed to flush future: {e}"); + surface.previous_frame_end = Some(sync::now(device.0.clone()).boxed_send_sync()); + } + } + } +} + +fn acquire_image(surface: &mut WindowSurfaceData) -> Option<(u32, SwapchainAcquireFuture)> { + surface + .previous_frame_end + .as_mut() + .unwrap() + .cleanup_finished(); + + let (image_index, suboptimal, acquire_future) = + match acquire_next_image(surface.swapchain.clone(), None).map_err(Validated::unwrap) { + Ok(r) => r, + Err(VulkanError::OutOfDate) => { + surface.recreate_swapchain = true; + return None; + } + Err(e) => panic!("failed to acquire next image: {e}"), + }; + + if suboptimal { + surface.recreate_swapchain = true; + } + + Some((image_index, acquire_future)) +} From d232706f6830be5685435feb8126f08aeb7b2c93 Mon Sep 17 00:00:00 2001 From: Florian RICHER Date: Sun, 25 May 2025 18:05:20 +0200 Subject: [PATCH 032/105] engine_render: Resize swapchain --- crates/engine_render/src/window/mod.rs | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/crates/engine_render/src/window/mod.rs b/crates/engine_render/src/window/mod.rs index 9af4184..d019444 100644 --- a/crates/engine_render/src/window/mod.rs +++ b/crates/engine_render/src/window/mod.rs @@ -136,9 +136,15 @@ fn window_size_dependent_setup(images: &[Arc]) -> Vec> { .collect::>() } -fn need_update_window_surface(window_surface: Res) -> bool { +fn need_update_window_surface( + window_surface: Res, + window_handle: Res, +) -> bool { match &window_surface.surface { - Some(surface) => surface.recreate_swapchain, + Some(surface) => { + let window_size: [f32; 2] = window_handle.0.inner_size().into(); + surface.recreate_swapchain || surface.viewport.extent != window_size + } None => false, } } @@ -147,16 +153,8 @@ fn update_window_surface( mut window_surface: ResMut, window_handle: Res, ) { - if window_surface.surface.is_none() { - return; - } - let window_surface = window_surface.surface.as_mut().unwrap(); - if !window_surface.recreate_swapchain { - return; - } - let window_size = window_handle.0.inner_size(); let (new_swapchain, new_images) = window_surface .swapchain From f486486be37a6d4f0ac6eea655373a0d45368fbf Mon Sep 17 00:00:00 2001 From: Florian RICHER Date: Sun, 25 May 2025 18:55:58 +0200 Subject: [PATCH 033/105] Remove ECS pattern: Split into new repo --- Cargo.lock | 489 +----------------- Cargo.toml | 27 +- crates/engine_render/Cargo.toml | 12 - crates/engine_render/src/lib.rs | 120 ----- crates/engine_render/src/render.rs | 122 ----- crates/engine_render/src/window/mod.rs | 222 -------- crates/engine_vulkan/Cargo.toml | 14 - crates/engine_vulkan/src/device.rs | 214 -------- crates/engine_vulkan/src/instance.rs | 70 --- crates/engine_vulkan/src/lib.rs | 85 --- crates/engine_vulkan/src/queues.rs | 176 ------- crates/engine_window/Cargo.toml | 11 - crates/engine_window/src/config.rs | 17 - crates/engine_window/src/lib.rs | 56 -- crates/engine_window/src/raw_handle.rs | 23 - crates/engine_window/src/state.rs | 63 --- flake.nix | 2 +- src/core/camera/mod.rs | 19 - src/core/mod.rs | 2 - src/core/render/material.rs | 7 - src/core/render/mesh.rs | 14 - src/core/render/mod.rs | 3 - src/game/mod.rs | 36 -- src/main.rs | 27 +- src/{old_app => render}/app.rs | 6 +- src/{old_app => render}/mod.rs | 1 + src/{old_app => render}/pipelines/mod.rs | 0 .../pipelines/triangle_pipeline.rs | 2 +- src/{old_app => render}/scene.rs | 6 +- src/{core => }/render/vertex.rs | 0 src/{old_app => render}/vulkan_context.rs | 0 .../window_render_context.rs | 0 32 files changed, 23 insertions(+), 1823 deletions(-) delete mode 100644 crates/engine_render/Cargo.toml delete mode 100644 crates/engine_render/src/lib.rs delete mode 100644 crates/engine_render/src/render.rs delete mode 100644 crates/engine_render/src/window/mod.rs delete mode 100644 crates/engine_vulkan/Cargo.toml delete mode 100644 crates/engine_vulkan/src/device.rs delete mode 100644 crates/engine_vulkan/src/instance.rs delete mode 100644 crates/engine_vulkan/src/lib.rs delete mode 100644 crates/engine_vulkan/src/queues.rs delete mode 100644 crates/engine_window/Cargo.toml delete mode 100644 crates/engine_window/src/config.rs delete mode 100644 crates/engine_window/src/lib.rs delete mode 100644 crates/engine_window/src/raw_handle.rs delete mode 100644 crates/engine_window/src/state.rs delete mode 100644 src/core/camera/mod.rs delete mode 100644 src/core/mod.rs delete mode 100644 src/core/render/material.rs delete mode 100644 src/core/render/mesh.rs delete mode 100644 src/core/render/mod.rs delete mode 100644 src/game/mod.rs rename src/{old_app => render}/app.rs (97%) rename src/{old_app => render}/mod.rs (86%) rename src/{old_app => render}/pipelines/mod.rs (100%) rename src/{old_app => render}/pipelines/triangle_pipeline.rs (98%) rename src/{old_app => render}/scene.rs (96%) rename src/{core => }/render/vertex.rs (100%) rename src/{old_app => render}/vulkan_context.rs (100%) rename src/{old_app => render}/window_render_context.rs (100%) diff --git a/Cargo.lock b/Cargo.lock index e538714..1f58cce 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -117,6 +117,12 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "anyhow" +version = "1.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" + [[package]] name = "arrayref" version = "0.3.9" @@ -144,48 +150,11 @@ dependencies = [ "libloading", ] -[[package]] -name = "assert_type_match" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f548ad2c4031f2902e3edc1f29c29e835829437de49562d8eb5dc5584d3a1043" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "async-executor" -version = "1.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb812ffb58524bdd10860d7d974e2f01cc0950c2438a74ee5ec2e2280c6c4ffa" -dependencies = [ - "async-task", - "concurrent-queue", - "fastrand", - "futures-lite", - "pin-project-lite", - "slab", -] - -[[package]] -name = "async-task" -version = "4.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" -dependencies = [ - "portable-atomic", -] - [[package]] name = "atomic-waker" version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" -dependencies = [ - "portable-atomic", -] [[package]] name = "autocfg" @@ -193,178 +162,6 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" -[[package]] -name = "bevy_app" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2b6267ac23a9947d5b2725ff047a1e1add70076d85fa9fb73d044ab9bea1f3c" -dependencies = [ - "bevy_derive", - "bevy_ecs", - "bevy_platform", - "bevy_reflect", - "bevy_tasks", - "bevy_utils", - "cfg-if", - "ctrlc", - "downcast-rs 2.0.1", - "log", - "thiserror 2.0.12", - "variadics_please", -] - -[[package]] -name = "bevy_derive" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f626531b9c05c25a758ede228727bd11c2c2c8498ecbed9925044386d525a2a3" -dependencies = [ - "bevy_macro_utils", - "quote", - "syn", -] - -[[package]] -name = "bevy_ecs" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9e807b5d9aab3bb8dfe47e7a44c9ff088bad2ceefe299b80ac77609a87fe9d4" -dependencies = [ - "arrayvec", - "bevy_ecs_macros", - "bevy_platform", - "bevy_ptr", - "bevy_reflect", - "bevy_tasks", - "bevy_utils", - "bitflags 2.9.1", - "bumpalo", - "concurrent-queue", - "derive_more", - "disqualified", - "fixedbitset", - "indexmap", - "log", - "nonmax", - "serde", - "smallvec", - "thiserror 2.0.12", - "variadics_please", -] - -[[package]] -name = "bevy_ecs_macros" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "467d7bb98aeb8dd30f36e6a773000c12a891d4f1bee2adc3841ec89cc8eaf54e" -dependencies = [ - "bevy_macro_utils", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "bevy_macro_utils" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2473db70d8785b5c75d6dd951a2e51e9be2c2311122db9692c79c9d887517b" -dependencies = [ - "parking_lot", - "proc-macro2", - "quote", - "syn", - "toml_edit", -] - -[[package]] -name = "bevy_platform" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "704db2c11b7bc31093df4fbbdd3769f9606a6a5287149f4b51f2680f25834ebc" -dependencies = [ - "cfg-if", - "critical-section", - "foldhash", - "hashbrown", - "portable-atomic", - "portable-atomic-util", - "serde", - "spin", -] - -[[package]] -name = "bevy_ptr" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86f1275dfb4cfef4ffc90c3fa75408964864facf833acc932413d52aa5364ba4" - -[[package]] -name = "bevy_reflect" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "607ebacc31029cf2f39ac330eabf1d4bc411b159528ec08dbe6b0593eaccfd41" -dependencies = [ - "assert_type_match", - "bevy_platform", - "bevy_ptr", - "bevy_reflect_derive", - "bevy_utils", - "derive_more", - "disqualified", - "downcast-rs 2.0.1", - "erased-serde", - "foldhash", - "glam 0.29.3", - "serde", - "smallvec", - "smol_str", - "thiserror 2.0.12", - "uuid", - "variadics_please", - "wgpu-types", -] - -[[package]] -name = "bevy_reflect_derive" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf35e45e4eb239018369f63f2adc2107a54c329f9276d020e01eee1625b0238b" -dependencies = [ - "bevy_macro_utils", - "proc-macro2", - "quote", - "syn", - "uuid", -] - -[[package]] -name = "bevy_tasks" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "444c450b65e108855f42ecb6db0c041a56ea7d7f10cc6222f0ca95e9536a7d19" -dependencies = [ - "async-executor", - "async-task", - "atomic-waker", - "bevy_platform", - "cfg-if", - "crossbeam-queue", - "derive_more", - "futures-lite", - "heapless", -] - -[[package]] -name = "bevy_utils" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac2da3b3c1f94dadefcbe837aaa4aa119fcea37f7bdc5307eb05b4ede1921e24" -dependencies = [ - "bevy_platform", - "thread_local", -] - [[package]] name = "bitflags" version = "1.3.2" @@ -376,9 +173,6 @@ name = "bitflags" version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" -dependencies = [ - "serde", -] [[package]] name = "block2" @@ -415,12 +209,6 @@ dependencies = [ "syn", ] -[[package]] -name = "byteorder" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" - [[package]] name = "bytes" version = "1.10.1" @@ -514,7 +302,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" dependencies = [ "crossbeam-utils", - "portable-atomic", ] [[package]] @@ -557,12 +344,6 @@ dependencies = [ "libc", ] -[[package]] -name = "critical-section" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" - [[package]] name = "crossbeam-queue" version = "0.3.12" @@ -584,43 +365,12 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" -[[package]] -name = "ctrlc" -version = "3.4.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46f93780a459b7d656ef7f071fe699c4d3d2cb201c4b24d085b6ddc505276e73" -dependencies = [ - "nix", - "windows-sys 0.59.0", -] - [[package]] name = "cursor-icon" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96a6ac251f4a2aca6b3f91340350eab87ae57c3f127ffeb585e92bd336717991" -[[package]] -name = "derive_more" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" -dependencies = [ - "derive_more-impl", -] - -[[package]] -name = "derive_more-impl" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "unicode-xid", -] - [[package]] name = "dispatch" version = "0.2.0" @@ -637,12 +387,6 @@ dependencies = [ "objc2 0.6.1", ] -[[package]] -name = "disqualified" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9c272297e804878a2a4b707cfcfc6d2328b5bb936944613b4fdf2b9269afdfd" - [[package]] name = "dlib" version = "0.5.2" @@ -658,55 +402,12 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" -[[package]] -name = "downcast-rs" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea8a8b81cacc08888170eef4d13b775126db426d0b348bee9d18c2c1eaf123cf" - [[package]] name = "dpi" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" -[[package]] -name = "engine_render" -version = "0.1.0" -dependencies = [ - "bevy_app", - "bevy_ecs", - "engine_vulkan", - "engine_window", - "log", - "vulkano", -] - -[[package]] -name = "engine_vulkan" -version = "0.1.0" -dependencies = [ - "bevy_app", - "bevy_ecs", - "engine_window", - "env_logger", - "log", - "thiserror 2.0.12", - "vulkano", - "winit", -] - -[[package]] -name = "engine_window" -version = "0.1.0" -dependencies = [ - "bevy_app", - "bevy_ecs", - "log", - "thiserror 2.0.12", - "winit", -] - [[package]] name = "env_filter" version = "0.1.3" @@ -736,16 +437,6 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" -[[package]] -name = "erased-serde" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e004d887f51fcb9fef17317a2f3525c887d8aa3f4f50fed920816a688284a5b7" -dependencies = [ - "serde", - "typeid", -] - [[package]] name = "errno" version = "0.3.12" @@ -756,18 +447,6 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "fastrand" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" - -[[package]] -name = "fixedbitset" -version = "0.5.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" - [[package]] name = "foldhash" version = "0.1.5" @@ -801,31 +480,6 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" -[[package]] -name = "futures-core" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" - -[[package]] -name = "futures-io" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" - -[[package]] -name = "futures-lite" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5edaec856126859abb19ed65f39e90fea3a9574b9707f13539acf4abf7eb532" -dependencies = [ - "fastrand", - "futures-core", - "futures-io", - "parking", - "pin-project-lite", -] - [[package]] name = "gethostname" version = "0.4.3" @@ -848,15 +502,6 @@ dependencies = [ "wasi", ] -[[package]] -name = "glam" -version = "0.29.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8babf46d4c1c9d92deac9f7be466f76dfc4482b6452fc5024b5e8daf6ffeb3ee" -dependencies = [ - "serde", -] - [[package]] name = "glam" version = "0.30.3" @@ -874,35 +519,11 @@ dependencies = [ "crunchy", ] -[[package]] -name = "hash32" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" -dependencies = [ - "byteorder", -] - [[package]] name = "hashbrown" version = "0.15.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3" -dependencies = [ - "equivalent", - "serde", -] - -[[package]] -name = "heapless" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" -dependencies = [ - "hash32", - "portable-atomic", - "stable_deref_trait", -] [[package]] name = "heck" @@ -1104,18 +725,6 @@ dependencies = [ "jni-sys", ] -[[package]] -name = "nix" -version = "0.30.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" -dependencies = [ - "bitflags 2.9.1", - "cfg-if", - "cfg_aliases", - "libc", -] - [[package]] name = "nom" version = "7.1.3" @@ -1126,12 +735,6 @@ dependencies = [ "minimal-lexical", ] -[[package]] -name = "nonmax" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "610a5acd306ec67f907abe5567859a3c693fb9886eb1f012ab8f2a47bef3db51" - [[package]] name = "num_enum" version = "0.7.3" @@ -1435,12 +1038,6 @@ dependencies = [ "ttf-parser", ] -[[package]] -name = "parking" -version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" - [[package]] name = "parking_lot" version = "0.12.3" @@ -1652,14 +1249,11 @@ dependencies = [ name = "rust_vulkan_test" version = "0.1.0" dependencies = [ - "bevy_app", - "bevy_ecs", - "engine_render", - "engine_vulkan", - "engine_window", + "anyhow", "env_logger", - "glam 0.30.3", + "glam", "log", + "thiserror 2.0.12", "vulkano", "vulkano-shaders", "winit", @@ -1838,21 +1432,6 @@ dependencies = [ "serde", ] -[[package]] -name = "spin" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" -dependencies = [ - "portable-atomic", -] - -[[package]] -name = "stable_deref_trait" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" - [[package]] name = "strict-num" version = "0.1.1" @@ -1984,12 +1563,6 @@ version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31" -[[package]] -name = "typeid" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" - [[package]] name = "unicode-ident" version = "1.0.18" @@ -2002,41 +1575,12 @@ version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" -[[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 = "uuid" -version = "1.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9" -dependencies = [ - "getrandom", - "js-sys", - "serde", - "wasm-bindgen", -] - -[[package]] -name = "variadics_please" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41b6d82be61465f97d42bd1d15bf20f3b0a3a0905018f38f9d6f6962055b0b5c" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "version_check" version = "0.9.5" @@ -2208,7 +1752,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe770181423e5fc79d3e2a7f4410b7799d5aab1de4372853de3c6aa13ca24121" dependencies = [ "cc", - "downcast-rs 1.2.1", + "downcast-rs", "rustix", "scoped-tls", "smallvec", @@ -2330,19 +1874,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "wgpu-types" -version = "24.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50ac044c0e76c03a0378e7786ac505d010a873665e2d51383dcff8dd227dc69c" -dependencies = [ - "bitflags 2.9.1", - "js-sys", - "log", - "serde", - "web-sys", -] - [[package]] name = "winapi-util" version = "0.1.9" diff --git a/Cargo.toml b/Cargo.toml index d8c8792..ce36410 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,11 +5,7 @@ edition = "2024" authors = ["Florian RICHER "] publish = false -[workspace] -resolver = "2" -members = ["crates/*"] - -[workspace.dependencies] +[dependencies] anyhow = "1.0" thiserror = "2.0" winit = { version = "0.30", features = ["rwh_06"] } @@ -20,28 +16,7 @@ vulkano-shaders = "0.35" # Math glam = { version = "0.30" } -# ECS -bevy_ecs = "0.16" -bevy_app = "0.16" # Log and tracing log = "0.4" env_logger = "0.11" - -engine_vulkan = { path = "crates/engine_vulkan" } -engine_window = { path = "crates/engine_window" } -engine_render = { path = "crates/engine_render" } - -[dependencies] -log = { workspace = true } -env_logger = { workspace = true } -bevy_app = { workspace = true } -bevy_ecs = { workspace = true } -winit = { workspace = true } -vulkano = { workspace = true } -vulkano-shaders = { workspace = true } -glam = { workspace = true } - -engine_vulkan = { workspace = true } -engine_window = { workspace = true } -engine_render = { workspace = true } diff --git a/crates/engine_render/Cargo.toml b/crates/engine_render/Cargo.toml deleted file mode 100644 index 5016279..0000000 --- a/crates/engine_render/Cargo.toml +++ /dev/null @@ -1,12 +0,0 @@ -[package] -name = "engine_render" -version = "0.1.0" -edition = "2024" - -[dependencies] -log = { workspace = true } -bevy_app = { workspace = true } -bevy_ecs = { workspace = true } -vulkano = { workspace = true } -engine_vulkan = { workspace = true } -engine_window = { workspace = true } diff --git a/crates/engine_render/src/lib.rs b/crates/engine_render/src/lib.rs deleted file mode 100644 index 8c869e9..0000000 --- a/crates/engine_render/src/lib.rs +++ /dev/null @@ -1,120 +0,0 @@ -use bevy_app::{App, AppLabel, Last, Plugin, SubApp}; -use bevy_ecs::{ - schedule::{IntoScheduleConfigs, Schedule, ScheduleLabel, SystemSet}, - system::{Commands, Res}, - world::World, -}; -use engine_vulkan::{ - VulkanCommandBufferAllocator, VulkanDescriptorSetAllocator, VulkanDevice, VulkanGraphicsQueue, - VulkanInstance, VulkanMemoryAllocator, -}; -use engine_window::raw_handle::WindowWrapper; -use window::WindowRenderPlugin; - -pub mod render; -pub mod window; - -#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)] -pub enum RenderSystems { - ManageViews, - Prepare, - Queue, - Render, - Present, -} - -#[derive(ScheduleLabel, Debug, Hash, PartialEq, Eq, Clone, Default)] -pub struct Render; - -impl Render { - pub fn base_schedule() -> Schedule { - use RenderSystems::*; - - let mut schedule = Schedule::new(Self); - - schedule.configure_sets((ManageViews, Prepare, Queue, Render, Present).chain()); - - schedule - } -} - -#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, AppLabel)] -pub struct RenderApp; - -pub struct RenderPlugin; - -impl Plugin for RenderPlugin { - fn build(&self, _app: &mut App) {} - - fn ready(&self, app: &App) -> bool { - let world = app.world(); - - world.get_resource::().is_some() - && world.get_resource::().is_some() - && world.get_resource::().is_some() - && world.get_resource::().is_some() - && world.get_resource::().is_some() - && world - .get_resource::() - .is_some() - && world - .get_resource::() - .is_some() - } - - fn finish(&self, app: &mut App) { - let mut render_app = SubApp::new(); - render_app.update_schedule = Some(Render.intern()); - render_app.add_schedule(Render::base_schedule()); - render_app.add_systems( - Render, - render::render_system - .in_set(RenderSystems::Render) - .run_if(render::can_render), - ); - - extract_app_resources(app.world_mut(), render_app.world_mut()); - - app.insert_sub_app(RenderApp, render_app); - - app.add_plugins(WindowRenderPlugin); - } -} - -fn extract_app_resources(world: &mut World, render_world: &mut World) { - let window_wrapper = world - .get_resource::() - .expect("Failed to get WindowWrapper. Check is WindowPlugin is added before RenderPlugin."); - - let vulkan_instance = world.get_resource::().expect( - "Failed to get Vulkan instance. Check is VulkanPlugin is added before RenderPlugin.", - ); - - let vulkan_device = world - .get_resource::() - .expect("Failed to get Vulkan device. Check is VulkanPlugin is added before RenderPlugin."); - - let vulkan_graphics_queue = world.get_resource::().expect( - "Failed to get Vulkan graphics queue. Check is VulkanPlugin is added before RenderPlugin.", - ); - - let vulkan_memory_allocator = world - .get_resource::() - .expect("Failed to get Vulkan memory allocator. Check is VulkanPlugin is added before RenderPlugin."); - - let vulkan_command_buffer_allocator = world - .get_resource::() - .expect("Failed to get Vulkan command buffer allocator. Check is VulkanPlugin is added before RenderPlugin."); - - let vulkan_descriptor_set_allocator = world - .get_resource::() - .expect("Failed to get Vulkan descriptor set allocator. Check is VulkanPlugin is added before RenderPlugin."); - - render_world.insert_resource(vulkan_instance.clone()); - render_world.insert_resource(vulkan_device.clone()); - render_world.insert_resource(vulkan_graphics_queue.clone()); - render_world.insert_resource(vulkan_memory_allocator.clone()); - render_world.insert_resource(vulkan_command_buffer_allocator.clone()); - render_world.insert_resource(vulkan_descriptor_set_allocator.clone()); - render_world.insert_resource(window_wrapper.clone()); -} diff --git a/crates/engine_render/src/render.rs b/crates/engine_render/src/render.rs deleted file mode 100644 index 1a8c95e..0000000 --- a/crates/engine_render/src/render.rs +++ /dev/null @@ -1,122 +0,0 @@ -use bevy_ecs::system::{Res, ResMut}; -use engine_vulkan::{VulkanCommandBufferAllocator, VulkanDevice, VulkanGraphicsQueue}; -use vulkano::{ - Validated, VulkanError, - command_buffer::{ - AutoCommandBufferBuilder, CommandBufferUsage, RenderingAttachmentInfo, RenderingInfo, - }, - render_pass::{AttachmentLoadOp, AttachmentStoreOp}, - swapchain::{SwapchainAcquireFuture, SwapchainPresentInfo, acquire_next_image}, - sync::{self, GpuFuture}, -}; - -use crate::window::{WindowSurface, WindowSurfaceData}; - -pub fn can_render(window_surface: Res) -> bool { - window_surface.surface.is_some() -} - -pub fn render_system( - mut window_surface: ResMut, - command_buffer_allocator: Res, - graphics_queue: Res, - device: Res, -) { - { - let surface = window_surface.surface.as_ref().unwrap(); - if surface.viewport.extent[0] == 0.0 || surface.viewport.extent[1] == 0.0 { - return; - } - } - - let (image_index, acquire_future) = - match acquire_image(&mut window_surface.surface.as_mut().unwrap()) { - Some(r) => r, - None => return, - }; - - let mut builder = AutoCommandBufferBuilder::primary( - command_buffer_allocator.0.clone(), - graphics_queue.0.queue_family_index(), - CommandBufferUsage::OneTimeSubmit, - ) - .expect("failed to create command buffer builder"); - - { - let surface = window_surface.surface.as_ref().unwrap(); - builder - .begin_rendering(RenderingInfo { - color_attachments: vec![Some(RenderingAttachmentInfo { - load_op: AttachmentLoadOp::Clear, - store_op: AttachmentStoreOp::Store, - clear_value: Some([0.0, 0.0, 0.0, 0.0].into()), - ..RenderingAttachmentInfo::image_view( - surface.attachment_image_views[image_index as usize].clone(), - ) - })], - ..Default::default() - }) - .unwrap() - .set_viewport(0, [surface.viewport.clone()].into_iter().collect()) - .unwrap(); - } - - builder.end_rendering().unwrap(); - - let command_buffer = builder.build().unwrap(); - - { - let surface = window_surface.surface.as_mut().unwrap(); - - let future = surface - .previous_frame_end - .take() - .unwrap() - .join(acquire_future) - .then_execute(graphics_queue.0.clone(), command_buffer) - .unwrap() - .then_swapchain_present( - graphics_queue.0.clone(), - SwapchainPresentInfo::swapchain_image_index(surface.swapchain.clone(), image_index), - ) - .then_signal_fence_and_flush(); - - match future.map_err(Validated::unwrap) { - Ok(future) => { - surface.previous_frame_end = Some(future.boxed_send_sync()); - } - Err(VulkanError::OutOfDate) => { - surface.recreate_swapchain = true; - surface.previous_frame_end = Some(sync::now(device.0.clone()).boxed_send_sync()); - } - Err(e) => { - println!("failed to flush future: {e}"); - surface.previous_frame_end = Some(sync::now(device.0.clone()).boxed_send_sync()); - } - } - } -} - -fn acquire_image(surface: &mut WindowSurfaceData) -> Option<(u32, SwapchainAcquireFuture)> { - surface - .previous_frame_end - .as_mut() - .unwrap() - .cleanup_finished(); - - let (image_index, suboptimal, acquire_future) = - match acquire_next_image(surface.swapchain.clone(), None).map_err(Validated::unwrap) { - Ok(r) => r, - Err(VulkanError::OutOfDate) => { - surface.recreate_swapchain = true; - return None; - } - Err(e) => panic!("failed to acquire next image: {e}"), - }; - - if suboptimal { - surface.recreate_swapchain = true; - } - - Some((image_index, acquire_future)) -} diff --git a/crates/engine_render/src/window/mod.rs b/crates/engine_render/src/window/mod.rs deleted file mode 100644 index d019444..0000000 --- a/crates/engine_render/src/window/mod.rs +++ /dev/null @@ -1,222 +0,0 @@ -use std::sync::Arc; - -use bevy_app::{App, Plugin}; -use bevy_ecs::{ - resource::Resource, - schedule::IntoScheduleConfigs, - system::{Res, ResMut}, -}; -use engine_vulkan::{VulkanDevice, VulkanInstance}; -use engine_window::raw_handle::WindowWrapper; -use vulkano::{ - image::{Image, ImageUsage, view::ImageView}, - pipeline::graphics::viewport::Viewport, - swapchain::{Surface, Swapchain, SwapchainCreateInfo}, - sync::{self, GpuFuture}, -}; - -use super::{Render, RenderApp, RenderSystems}; - -pub struct WindowSurfaceData { - pub swapchain: Arc, - pub attachment_image_views: Vec>, - pub viewport: Viewport, - pub recreate_swapchain: bool, - pub previous_frame_end: Option>, -} - -#[derive(Resource, Default)] -pub struct WindowSurface { - pub surface: Option, -} - -pub struct WindowRenderPlugin; - -impl Plugin for WindowRenderPlugin { - fn build(&self, app: &mut App) { - let render_app = app - .get_sub_app_mut(RenderApp) - .expect("Failed to get RenderApp. Check is RenderPlugin is added."); - - render_app.init_resource::(); - - render_app.add_systems( - Render, - create_window_surface - .in_set(RenderSystems::ManageViews) - .run_if(need_create_window_surface) - .before(need_update_window_surface), - ); - - render_app.add_systems( - Render, - update_window_surface - .in_set(RenderSystems::ManageViews) - .run_if(need_update_window_surface), - ); - } -} - -fn need_create_window_surface(window_surface: Res) -> bool { - window_surface.surface.is_none() -} - -fn create_window_surface( - mut window_surface: ResMut, - window_handle: Res, - vulkan_instance: Res, - vulkan_device: Res, -) { - let window_size = window_handle.0.inner_size(); - - let surface = Surface::from_window(vulkan_instance.0.clone(), window_handle.0.clone()) - .expect("Failed to create surface"); - log::debug!("Surface created"); - - let (swapchain, images) = { - let surface_capabilities = vulkan_device - .0 - .physical_device() - .surface_capabilities(&surface, Default::default()) - .unwrap(); - - let (image_format, _) = vulkan_device - .0 - .physical_device() - .surface_formats(&surface, Default::default()) - .unwrap()[0]; - - Swapchain::new( - vulkan_device.0.clone(), - surface, - SwapchainCreateInfo { - // 2 because with some graphics driver, it crash on fullscreen because fullscreen need to min image to works. - min_image_count: surface_capabilities.min_image_count.max(2), - image_format, - image_extent: window_size.into(), - image_usage: ImageUsage::COLOR_ATTACHMENT, - composite_alpha: surface_capabilities - .supported_composite_alpha - .into_iter() - .next() - .unwrap(), - - ..Default::default() - }, - ) - .unwrap() - }; - log_swapchain_info(&swapchain, false); - - let attachment_image_views = window_size_dependent_setup(&images); - - let viewport = Viewport { - offset: [0.0, 0.0], - extent: window_size.into(), - depth_range: 0.0..=1.0, - }; - log_viewport_info(&viewport, false); - - let recreate_swapchain = false; - let previous_frame_end = Some(sync::now(vulkan_device.0.clone()).boxed_send_sync()); - - window_surface.surface = Some(WindowSurfaceData { - swapchain, - attachment_image_views, - viewport, - recreate_swapchain, - previous_frame_end, - }); -} - -fn window_size_dependent_setup(images: &[Arc]) -> Vec> { - images - .iter() - .map(|image| ImageView::new_default(image.clone()).unwrap()) - .collect::>() -} - -fn need_update_window_surface( - window_surface: Res, - window_handle: Res, -) -> bool { - match &window_surface.surface { - Some(surface) => { - let window_size: [f32; 2] = window_handle.0.inner_size().into(); - surface.recreate_swapchain || surface.viewport.extent != window_size - } - None => false, - } -} - -fn update_window_surface( - mut window_surface: ResMut, - window_handle: Res, -) { - let window_surface = window_surface.surface.as_mut().unwrap(); - - let window_size = window_handle.0.inner_size(); - let (new_swapchain, new_images) = window_surface - .swapchain - .recreate(SwapchainCreateInfo { - image_extent: window_size.into(), - ..window_surface.swapchain.create_info() - }) - .expect("Failed to recreate swapchain"); - - window_surface.swapchain = new_swapchain; - window_surface.attachment_image_views = window_size_dependent_setup(&new_images); - window_surface.viewport.extent = window_size.into(); - window_surface.recreate_swapchain = false; - - log_swapchain_info(&window_surface.swapchain, true); - log_viewport_info(&window_surface.viewport, true); -} - -fn log_swapchain_info(swapchain: &Swapchain, recreate_swapchain: bool) { - if recreate_swapchain { - log::debug!("Swapchain recreated"); - } else { - log::debug!("Swapchain created"); - } - log::debug!( - "\tMin image count: {}", - swapchain.create_info().min_image_count - ); - log::debug!("\tImage format: {:?}", swapchain.create_info().image_format); - log::debug!("\tImage extent: {:?}", swapchain.create_info().image_extent); - log::debug!("\tImage usage: {:?}", swapchain.create_info().image_usage); - log::debug!( - "\tComposite alpha: {:?}", - swapchain.create_info().composite_alpha - ); - log::debug!("\tPresent mode: {:?}", swapchain.create_info().present_mode); - log::debug!( - "\tImage sharing: {:?}", - swapchain.create_info().image_sharing - ); - log::debug!( - "\tPre transform: {:?}", - swapchain.create_info().pre_transform - ); - log::debug!( - "\tComposite alpha: {:?}", - swapchain.create_info().composite_alpha - ); - log::debug!("\tPresent mode: {:?}", swapchain.create_info().present_mode); - log::debug!( - "\tFull screen exclusive: {:?}", - swapchain.create_info().full_screen_exclusive - ); -} - -fn log_viewport_info(viewport: &Viewport, recreate_viewport: bool) { - if recreate_viewport { - log::debug!("Viewport recreated"); - } else { - log::debug!("Viewport created"); - } - log::debug!("\tOffset: {:?}", viewport.offset); - log::debug!("\tExtent: {:?}", viewport.extent); - log::debug!("\tDepth range: {:?}", viewport.depth_range); -} diff --git a/crates/engine_vulkan/Cargo.toml b/crates/engine_vulkan/Cargo.toml deleted file mode 100644 index c0fe35b..0000000 --- a/crates/engine_vulkan/Cargo.toml +++ /dev/null @@ -1,14 +0,0 @@ -[package] -name = "engine_vulkan" -version = "0.1.0" -edition = "2024" - -[dependencies] -thiserror = { workspace = true } -log = { workspace = true } -env_logger = { workspace = true } -bevy_app = { workspace = true } -bevy_ecs = { workspace = true } -winit = { workspace = true } -vulkano = { workspace = true } -engine_window = { workspace = true } diff --git a/crates/engine_vulkan/src/device.rs b/crates/engine_vulkan/src/device.rs deleted file mode 100644 index f20335f..0000000 --- a/crates/engine_vulkan/src/device.rs +++ /dev/null @@ -1,214 +0,0 @@ -use std::sync::Arc; - -use bevy_ecs::world::World; -use engine_window::raw_handle::DisplayHandleWrapper; -use vulkano::{ - command_buffer::allocator::StandardCommandBufferAllocator, - descriptor_set::allocator::StandardDescriptorSetAllocator, - device::{ - Device, DeviceCreateInfo, DeviceExtensions, Queue, - physical::{PhysicalDevice, PhysicalDeviceType}, - }, - memory::allocator::StandardMemoryAllocator, -}; - -use crate::{ - VulkanCommandBufferAllocator, VulkanComputeQueue, VulkanConfig, VulkanDescriptorSetAllocator, - VulkanDevice, VulkanGraphicsQueue, VulkanInstance, VulkanMemoryAllocator, VulkanTransferQueue, - queues::{VulkanQueueFamilyIndices, VulkanQueuesQuery, find_queues_family_indices}, -}; - -pub fn create_and_insert_device(world: &mut World, config: &VulkanConfig) { - let picked_device = - pick_physical_device(world, &config).expect("Failed to pick physical device"); - - let device = picked_device.device; - let physical_device = device.physical_device(); - - log::debug!("Vulkan device created"); - log::debug!( - "\tPhysical device: {:?} ({:?})", - physical_device.properties().device_name, - physical_device.properties().device_type - ); - log::debug!("\tDevice extensions: {:?}", device.enabled_extensions()); - log::debug!("\tDevice features: {:?}", device.enabled_features()); - - world.insert_resource(VulkanDevice(device.clone())); - - log::debug!("\tDevice selected queues:"); - if config.with_graphics_queue { - world.insert_resource(VulkanGraphicsQueue( - picked_device - .graphics_queue - .expect("Failed to get graphics queue"), - )); - log::debug!("\t\t- Graphics queue"); - } - - if config.with_compute_queue { - world.insert_resource(VulkanComputeQueue( - picked_device - .compute_queue - .expect("Failed to get compute queue"), - )); - log::debug!("\t\t- Compute queue"); - } - - if config.with_transfer_queue { - world.insert_resource(VulkanTransferQueue( - picked_device - .transfer_queue - .expect("Failed to get transfer queue"), - )); - log::debug!("\t\t- Transfer queue"); - } - - world.insert_resource(VulkanMemoryAllocator(Arc::new( - StandardMemoryAllocator::new_default(device.clone()), - ))); - world.insert_resource(VulkanCommandBufferAllocator(Arc::new( - StandardCommandBufferAllocator::new(device.clone(), Default::default()), - ))); - world.insert_resource(VulkanDescriptorSetAllocator(Arc::new( - StandardDescriptorSetAllocator::new(device.clone(), Default::default()), - ))); -} - -struct PickedDevice { - pub device: Arc, - pub graphics_queue: Option>, - pub compute_queue: Option>, - pub transfer_queue: Option>, -} - -fn pick_physical_device(world: &World, config: &VulkanConfig) -> Option { - let instance = world - .get_resource::() - .expect("Failed to get VulkanInstance during vulkan plugin initialization"); - - instance - .0 - .enumerate_physical_devices() - .expect("Failed to enumerate physical devices") - .filter_map(|p| check_physical_device_support(world, &p, config).and_then(|r| Some((p, r)))) - .min_by_key(|(p, _)| match p.properties().device_type { - PhysicalDeviceType::DiscreteGpu => 0, - PhysicalDeviceType::IntegratedGpu => 1, - PhysicalDeviceType::VirtualGpu => 2, - PhysicalDeviceType::Cpu => 3, - PhysicalDeviceType::Other => 4, - _ => 5, - }) - .take() - .and_then(|(p, (device_extensions, queue_family_indices))| { - Some(create_device( - config, - &p, - device_extensions, - queue_family_indices, - )) - }) -} - -fn check_device_extensions_support( - physical_device: &Arc, - config: &VulkanConfig, -) -> Option { - let device_extensions = DeviceExtensions { - khr_swapchain: config.with_window_surface, - ..config.device_extensions - }; - - if physical_device - .supported_extensions() - .contains(&device_extensions) - { - log::debug!( - "\t\t[OK] Device supports required extensions {:?}", - device_extensions - ); - Some(device_extensions) - } else { - log::debug!( - "\t\t[FAILED] Device does not support required extensions {:?}", - device_extensions - ); - None - } -} - -fn check_physical_device_support( - world: &World, - physical_device: &Arc, - config: &VulkanConfig, -) -> Option<(DeviceExtensions, VulkanQueueFamilyIndices)> { - log::debug!("Checking physical device"); - log::debug!("\tProperties"); - log::debug!("\t\tName: {}", physical_device.properties().device_name); - log::debug!("\t\tAPI version: {}", physical_device.api_version()); - log::debug!( - "\t\tDevice type: {:?}", - physical_device.properties().device_type - ); - log::debug!("\tRequired supports checking report"); - - let device_extensions = check_device_extensions_support(physical_device, config)?; - - let display_handle = world - .get_resource::() - .expect("DisplayHandleWrapper must be added before VulkanPlugin"); - - let queue_support_query = VulkanQueuesQuery { - with_surface: Some(&display_handle), - with_graphics_queue: config.with_graphics_queue, - with_compute_queue: config.with_compute_queue, - with_transfer_queue: config.with_transfer_queue, - }; - - let queue_family_indices = find_queues_family_indices(physical_device, &queue_support_query); - log::debug!("\t\tQueue family indices: {:#?}", queue_family_indices); - - Some((device_extensions, queue_family_indices)) -} - -fn create_device( - config: &VulkanConfig, - physical_device: &Arc, - device_extensions: DeviceExtensions, - queue_family_indices: VulkanQueueFamilyIndices, -) -> PickedDevice { - let (device, mut queues) = Device::new( - physical_device.clone(), - DeviceCreateInfo { - queue_create_infos: queue_family_indices.into(), - enabled_extensions: device_extensions, - enabled_features: config.device_features, - ..Default::default() - }, - ) - .expect("Failed to create device"); - - let mut graphics_queue = None; - let mut compute_queue = None; - let mut transfer_queue = None; - - if config.with_graphics_queue { - graphics_queue = queues.next(); - } - - if config.with_compute_queue { - compute_queue = queues.next(); - } - - if config.with_transfer_queue { - transfer_queue = queues.next(); - } - - PickedDevice { - device, - graphics_queue, - compute_queue, - transfer_queue, - } -} diff --git a/crates/engine_vulkan/src/instance.rs b/crates/engine_vulkan/src/instance.rs deleted file mode 100644 index dcda857..0000000 --- a/crates/engine_vulkan/src/instance.rs +++ /dev/null @@ -1,70 +0,0 @@ -use std::sync::Arc; - -use bevy_ecs::world::World; -use engine_window::raw_handle::DisplayHandleWrapper; -use vulkano::{ - VulkanLibrary, - instance::{Instance, InstanceCreateFlags, InstanceCreateInfo, InstanceExtensions}, - swapchain::Surface, -}; - -use crate::{VulkanConfig, VulkanInstance}; - -fn load_library() -> Arc { - let library = VulkanLibrary::new().unwrap(); - - log::debug!("Available Instance layers:"); - for layer in library.layer_properties().unwrap() { - log::debug!( - "\t - Layer name: {}, Description: {}, Implementation Version: {}, Vulkan Version: {}", - layer.name(), - layer.description(), - layer.implementation_version(), - layer.vulkan_version() - ); - } - - library -} - -pub fn create_and_insert_instance(world: &mut World, config: &VulkanConfig) { - let library = load_library(); - - let instance_extensions = { - if config.with_window_surface { - let display_handle = world - .get_resource::() - .expect("DisplayHandleWrapper must be added before VulkanPlugin"); - - Surface::required_extensions(&display_handle.0) - .expect("Failed to get surface required extensions") - } else { - InstanceExtensions::default() - } - }; - - let instance = Instance::new( - library, - InstanceCreateInfo { - // Enable enumerating devices that use non-conformant Vulkan implementations. - // (e.g. MoltenVK) - flags: InstanceCreateFlags::ENUMERATE_PORTABILITY, - enabled_extensions: instance_extensions, - enabled_layers: config.instance_layers.clone(), - ..Default::default() - }, - ) - .expect("Failed to create vulkan instance"); - - log::debug!("Instance created"); - log::debug!( - "\t- Enabled extensions: {:?}", - instance.enabled_extensions() - ); - log::debug!("\t- Enabled layers: {:?}", instance.enabled_layers()); - log::debug!("\t- API version: {:?}", instance.api_version()); - log::debug!("\t- Max API version: {:?}", instance.max_api_version()); - log::debug!("\t- Flags: {:?}", instance.flags()); - - world.insert_resource(VulkanInstance(instance)); -} diff --git a/crates/engine_vulkan/src/lib.rs b/crates/engine_vulkan/src/lib.rs deleted file mode 100644 index 38cb456..0000000 --- a/crates/engine_vulkan/src/lib.rs +++ /dev/null @@ -1,85 +0,0 @@ -use std::sync::Arc; - -use bevy_app::{App, Plugin}; -use bevy_ecs::resource::Resource; -use vulkano::{ - command_buffer::allocator::StandardCommandBufferAllocator, - descriptor_set::allocator::StandardDescriptorSetAllocator, - device::{Device, DeviceExtensions, DeviceFeatures, Queue}, - instance::Instance, - memory::allocator::StandardMemoryAllocator, -}; - -mod device; -mod instance; -mod queues; - -use crate::{device::create_and_insert_device, instance::create_and_insert_instance}; - -#[derive(Resource, Clone)] -pub struct VulkanInstance(pub Arc); - -#[derive(Resource, Clone)] -pub struct VulkanDevice(pub Arc); - -#[derive(Resource, Clone)] -pub struct VulkanGraphicsQueue(pub Arc); - -#[derive(Resource, Clone)] -pub struct VulkanComputeQueue(pub Arc); - -#[derive(Resource, Clone)] -pub struct VulkanTransferQueue(pub Arc); - -#[derive(Resource, Clone)] -pub struct VulkanMemoryAllocator(pub Arc); - -#[derive(Resource, Clone)] -pub struct VulkanCommandBufferAllocator(pub Arc); - -#[derive(Resource, Clone)] -pub struct VulkanDescriptorSetAllocator(pub Arc); - -#[derive(Debug, thiserror::Error)] -pub enum VulkanError { - #[error("Failed to create vulkan context")] - FailedToCreateVulkanContext, -} - -pub struct VulkanConfig { - pub instance_layers: Vec, - pub device_extensions: DeviceExtensions, - pub device_features: DeviceFeatures, - pub with_window_surface: bool, - pub with_graphics_queue: bool, - pub with_compute_queue: bool, - pub with_transfer_queue: bool, -} - -impl Default for VulkanConfig { - fn default() -> Self { - Self { - instance_layers: Vec::default(), - device_extensions: DeviceExtensions::default(), - device_features: DeviceFeatures::default(), - with_window_surface: true, - with_graphics_queue: true, - with_compute_queue: true, - with_transfer_queue: true, - } - } -} - -#[derive(Default)] -pub struct VulkanPlugin { - pub vulkan_config: VulkanConfig, -} - -impl Plugin for VulkanPlugin { - fn build(&self, app: &mut App) { - let world = app.world_mut(); - - create_and_insert_instance(world, &self.vulkan_config); - create_and_insert_device(world, &self.vulkan_config); - } -} diff --git a/crates/engine_vulkan/src/queues.rs b/crates/engine_vulkan/src/queues.rs deleted file mode 100644 index 17e6c08..0000000 --- a/crates/engine_vulkan/src/queues.rs +++ /dev/null @@ -1,176 +0,0 @@ -use std::{collections::HashMap, sync::Arc}; - -use engine_window::raw_handle::DisplayHandleWrapper; -use vulkano::device::{ - QueueCreateInfo, QueueFamilyProperties, QueueFlags, physical::PhysicalDevice, -}; - -#[derive(Debug)] -pub enum VulkanQueueFamilyStatus { - Unused, - NotSupported, - Supported(u32), -} - -impl VulkanQueueFamilyStatus { - pub fn can_be_set(&self) -> bool { - match self { - VulkanQueueFamilyStatus::NotSupported => true, - _ => false, - } - } -} - -/// Convert a boolean to a VulkanQueueFamilyStatus as default value -impl From for VulkanQueueFamilyStatus { - fn from(value: bool) -> Self { - if value { - VulkanQueueFamilyStatus::NotSupported - } else { - VulkanQueueFamilyStatus::Unused - } - } -} - -/// Convert a DisplayHandleWrapper Option to a VulkanQueueFamilyStatus as default value -impl From> for VulkanQueueFamilyStatus { - fn from(value: Option<&DisplayHandleWrapper>) -> Self { - if value.is_some() { - VulkanQueueFamilyStatus::NotSupported - } else { - VulkanQueueFamilyStatus::Unused - } - } -} - -#[derive(Debug)] -pub struct VulkanQueueFamilyIndices { - pub graphics_queue_family_index: VulkanQueueFamilyStatus, - pub compute_queue_family_index: VulkanQueueFamilyStatus, - pub transfer_queue_family_index: VulkanQueueFamilyStatus, -} - -impl From for Vec<(VulkanQueueFamilyStatus, f32)> { - fn from(indices: VulkanQueueFamilyIndices) -> Self { - vec![ - (indices.graphics_queue_family_index, 1.0), - (indices.compute_queue_family_index, 0.5), - (indices.transfer_queue_family_index, 0.5), - ] - } -} - -impl From for Vec { - fn from(indices: VulkanQueueFamilyIndices) -> Self { - let mut queue_create_infos = HashMap::::new(); - - let statuses: Vec<(VulkanQueueFamilyStatus, f32)> = indices.into(); - - for (status, priority) in statuses.iter() { - match status { - VulkanQueueFamilyStatus::Supported(index) => { - let entry = queue_create_infos.entry(*index).or_insert(QueueCreateInfo { - queue_family_index: *index, - queues: Vec::new(), - ..Default::default() - }); - - entry.queues.push(*priority); - } - _ => {} - } - } - - queue_create_infos - .into_iter() - .map(|(_, value)| value) - .collect() - } -} - -/// Convert a VulkanQueuesQuery to a VulkanQueuesFamilyIndices as default value -impl<'a> From<&'a VulkanQueuesQuery<'a>> for VulkanQueueFamilyIndices { - fn from(query: &'a VulkanQueuesQuery<'a>) -> Self { - VulkanQueueFamilyIndices { - graphics_queue_family_index: query.with_graphics_queue.into(), - compute_queue_family_index: query.with_compute_queue.into(), - transfer_queue_family_index: query.with_transfer_queue.into(), - } - } -} - -pub struct VulkanQueuesQuery<'a> { - pub with_surface: Option<&'a DisplayHandleWrapper>, - pub with_graphics_queue: bool, - pub with_compute_queue: bool, - pub with_transfer_queue: bool, -} - -pub fn find_queues_family_indices( - physical_device: &Arc, - query: &VulkanQueuesQuery, -) -> VulkanQueueFamilyIndices { - let mut indices: VulkanQueueFamilyIndices = query.into(); - - for (i, queue_family_properties) in physical_device.queue_family_properties().iter().enumerate() - { - let mut available_queue_count = queue_family_properties.queue_count; - - if indices.graphics_queue_family_index.can_be_set() - && check_queue_support(queue_family_properties, QueueFlags::GRAPHICS) - && available_queue_count > 0 - { - if query.with_surface.is_none() - || check_presentation_support( - physical_device, - i as u32, - &query.with_surface.as_ref().unwrap(), - ) - { - indices.graphics_queue_family_index = VulkanQueueFamilyStatus::Supported(i as u32); - available_queue_count -= 1; - } - } - - if indices.compute_queue_family_index.can_be_set() - && check_queue_support(queue_family_properties, QueueFlags::COMPUTE) - && available_queue_count > 0 - { - indices.compute_queue_family_index = VulkanQueueFamilyStatus::Supported(i as u32); - available_queue_count -= 1; - } - - if indices.transfer_queue_family_index.can_be_set() - && check_queue_support(queue_family_properties, QueueFlags::TRANSFER) - && available_queue_count > 0 - { - indices.transfer_queue_family_index = VulkanQueueFamilyStatus::Supported(i as u32); - } - - if !indices.graphics_queue_family_index.can_be_set() - && !indices.compute_queue_family_index.can_be_set() - && !indices.transfer_queue_family_index.can_be_set() - { - break; - } - } - - return indices; -} - -fn check_presentation_support( - physical_device: &Arc, - queue_family_index: u32, - surface: &DisplayHandleWrapper, -) -> bool { - physical_device - .presentation_support(queue_family_index, &surface.0) - .unwrap() -} - -fn check_queue_support( - queue_family_property: &QueueFamilyProperties, - queue_flags: QueueFlags, -) -> bool { - queue_family_property.queue_flags.intersects(queue_flags) -} diff --git a/crates/engine_window/Cargo.toml b/crates/engine_window/Cargo.toml deleted file mode 100644 index b5016cc..0000000 --- a/crates/engine_window/Cargo.toml +++ /dev/null @@ -1,11 +0,0 @@ -[package] -name = "engine_window" -version = "0.1.0" -edition = "2024" - -[dependencies] -thiserror = { workspace = true } -log = { workspace = true } -bevy_app = { workspace = true } -bevy_ecs = { workspace = true } -winit = { workspace = true } diff --git a/crates/engine_window/src/config.rs b/crates/engine_window/src/config.rs deleted file mode 100644 index 487140c..0000000 --- a/crates/engine_window/src/config.rs +++ /dev/null @@ -1,17 +0,0 @@ -use bevy_ecs::resource::Resource; -use winit::{dpi::PhysicalSize, window::WindowAttributes}; - -#[derive(Resource, Clone)] -pub struct WindowConfig { - pub title: String, - pub width: u32, - pub height: u32, -} - -impl Into for &WindowConfig { - fn into(self) -> WindowAttributes { - WindowAttributes::default() - .with_title(self.title.clone()) - .with_inner_size(PhysicalSize::new(self.width as f64, self.height as f64)) - } -} diff --git a/crates/engine_window/src/lib.rs b/crates/engine_window/src/lib.rs deleted file mode 100644 index 33e150c..0000000 --- a/crates/engine_window/src/lib.rs +++ /dev/null @@ -1,56 +0,0 @@ -use bevy_app::{App, AppExit, Plugin, PluginsState}; -use config::WindowConfig; -use raw_handle::{DisplayHandleWrapper, EventLoopProxyWrapper, WindowWrapper}; -use state::WindowState; -use winit::event_loop::EventLoop; - -pub mod config; -pub mod raw_handle; -pub mod state; - -#[derive(Debug, thiserror::Error)] -pub enum WindowError { - #[error("Failed to create event loop")] - FailedToCreateEventLoop, -} - -pub struct WindowPlugin { - pub window_config: WindowConfig, -} - -impl Plugin for WindowPlugin { - fn build(&self, app: &mut App) { - let world = app.world_mut(); - world.insert_resource(self.window_config.clone()); - - let mut event_loop_builder = EventLoop::with_user_event(); - let event_loop = event_loop_builder - .build() - .map_err(|_| WindowError::FailedToCreateEventLoop) - .expect("Failed to create event loop"); - - world.insert_resource(DisplayHandleWrapper(event_loop.owned_display_handle())); - - app.set_runner(Box::new(move |app| runner(app, event_loop))); - } -} - -fn runner(mut app: App, event_loop: EventLoop<()>) -> AppExit { - if app.plugins_state() == PluginsState::Ready { - app.finish(); - app.cleanup(); - } - - app.world_mut() - .insert_resource(EventLoopProxyWrapper::new(event_loop.create_proxy())); - - let mut window_state = WindowState::new(app); - - match event_loop.run_app(&mut window_state) { - Ok(_) => AppExit::Success, - Err(e) => { - log::error!("Error running window state: {e}"); - AppExit::error() - } - } -} diff --git a/crates/engine_window/src/raw_handle.rs b/crates/engine_window/src/raw_handle.rs deleted file mode 100644 index 87d5988..0000000 --- a/crates/engine_window/src/raw_handle.rs +++ /dev/null @@ -1,23 +0,0 @@ -use std::sync::Arc; - -use bevy_ecs::resource::Resource; -use winit::{event_loop::EventLoopProxy, window::Window}; - -#[derive(Resource)] -pub struct EventLoopProxyWrapper(EventLoopProxy); - -impl EventLoopProxyWrapper { - pub fn new(event_loop: EventLoopProxy) -> Self { - Self(event_loop) - } - - pub fn proxy(&self) -> &EventLoopProxy { - &self.0 - } -} - -#[derive(Resource, Clone)] -pub struct DisplayHandleWrapper(pub winit::event_loop::OwnedDisplayHandle); - -#[derive(Resource, Clone)] -pub struct WindowWrapper(pub Arc); diff --git a/crates/engine_window/src/state.rs b/crates/engine_window/src/state.rs deleted file mode 100644 index 0f708fd..0000000 --- a/crates/engine_window/src/state.rs +++ /dev/null @@ -1,63 +0,0 @@ -use std::sync::Arc; - -use bevy_app::{App, PluginsState}; -use bevy_ecs::world::World; -use winit::{ - application::ApplicationHandler, event::WindowEvent, event_loop::ActiveEventLoop, - window::WindowId, -}; - -use super::{config::WindowConfig, raw_handle::WindowWrapper}; - -pub struct WindowState { - app: App, -} - -impl WindowState { - pub fn new(app: App) -> Self { - Self { app } - } - - fn world(&self) -> &World { - self.app.world() - } -} - -impl ApplicationHandler for WindowState { - fn resumed(&mut self, event_loop: &ActiveEventLoop) { - let window_config = self.world().get_resource::().unwrap(); - - let window = event_loop.create_window(window_config.into()).unwrap(); - self.app - .world_mut() - .insert_resource(WindowWrapper(Arc::new(window))); - } - - fn new_events(&mut self, event_loop: &ActiveEventLoop, cause: winit::event::StartCause) { - if self.app.plugins_state() == PluginsState::Ready { - self.app.finish(); - self.app.cleanup(); - } - } - - fn window_event(&mut self, event_loop: &ActiveEventLoop, _id: WindowId, event: WindowEvent) { - match event { - WindowEvent::CloseRequested => { - log::debug!("The close button was pressed; stopping"); - event_loop.exit(); - } - WindowEvent::RedrawRequested => { - if self.app.plugins_state() == PluginsState::Cleaned { - self.app.update(); - } - - let window_wrapper = self.app.world().get_resource::().unwrap(); - - window_wrapper.0.request_redraw(); - } - _ => {} - } - } - - fn about_to_wait(&mut self, _event_loop: &ActiveEventLoop) {} -} diff --git a/flake.nix b/flake.nix index 9cef9c1..dbd8d4c 100644 --- a/flake.nix +++ b/flake.nix @@ -76,7 +76,7 @@ packages = { default = rustPlatform.buildRustPackage { - pname = "rust_ash_test"; + pname = "vulkan_test"; version = "0.1.0"; src = self; diff --git a/src/core/camera/mod.rs b/src/core/camera/mod.rs deleted file mode 100644 index f9b6925..0000000 --- a/src/core/camera/mod.rs +++ /dev/null @@ -1,19 +0,0 @@ -use bevy_ecs::component::Component; -use glam::{Mat4, Quat, Vec3}; - -pub trait Camera: Into + Component {} - -#[derive(Component)] -pub struct Camera3D { - pub projection: Mat4, - pub position: Vec3, - pub rotation: Quat, -} - -impl Into for Camera3D { - fn into(self) -> Mat4 { - Mat4::from_rotation_translation(self.rotation, self.position) * self.projection - } -} - -impl Camera for Camera3D {} diff --git a/src/core/mod.rs b/src/core/mod.rs deleted file mode 100644 index a8707c6..0000000 --- a/src/core/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod camera; -pub mod render; diff --git a/src/core/render/material.rs b/src/core/render/material.rs deleted file mode 100644 index 539d4bc..0000000 --- a/src/core/render/material.rs +++ /dev/null @@ -1,7 +0,0 @@ -use std::sync::Arc; - -use bevy_ecs::component::Component; -use vulkano::pipeline::GraphicsPipeline; - -#[derive(Component)] -pub struct Material(pub Arc); diff --git a/src/core/render/mesh.rs b/src/core/render/mesh.rs deleted file mode 100644 index ca5ec0f..0000000 --- a/src/core/render/mesh.rs +++ /dev/null @@ -1,14 +0,0 @@ -use bevy_ecs::component::Component; - -use super::vertex::Vertex2D; - -#[derive(Component)] -pub struct Mesh2D { - pub vertices: Vec, -} - -impl Mesh2D { - pub fn new(vertices: Vec) -> Self { - Self { vertices } - } -} diff --git a/src/core/render/mod.rs b/src/core/render/mod.rs deleted file mode 100644 index 5d003a8..0000000 --- a/src/core/render/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub mod material; -pub mod mesh; -pub mod vertex; diff --git a/src/game/mod.rs b/src/game/mod.rs deleted file mode 100644 index e04b2a7..0000000 --- a/src/game/mod.rs +++ /dev/null @@ -1,36 +0,0 @@ -use bevy_app::App; -use engine_render::RenderPlugin; -use engine_vulkan::{VulkanConfig, VulkanPlugin}; -use engine_window::{WindowPlugin, config::WindowConfig}; -use vulkano::device::{DeviceExtensions, DeviceFeatures}; - -pub fn init(app: &mut App) { - let window_config = WindowConfig { - title: "Rust ASH Test".to_string(), - width: 800, - height: 600, - }; - - let device_extensions = DeviceExtensions { - khr_dynamic_rendering: true, - ..Default::default() - }; - - let device_features = DeviceFeatures { - dynamic_rendering: true, - ..Default::default() - }; - - let vulkan_config = VulkanConfig { - instance_layers: vec![String::from("VK_LAYER_KHRONOS_validation")], - device_extensions, - device_features, - ..Default::default() - }; - - app.add_plugins(( - WindowPlugin { window_config }, - VulkanPlugin { vulkan_config }, - RenderPlugin, - )); -} diff --git a/src/main.rs b/src/main.rs index 34fad4b..ca3f901 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,35 +1,14 @@ use winit::event_loop::{ControlFlow, EventLoop}; -use bevy_app::{App, AppExit}; - -pub mod core; -pub mod game; -pub mod old_app; - +mod render; fn main() { env_logger::init(); - run_new_app(); - // run_old_app(); -} - -fn run_new_app() { - let mut app = App::default(); - game::init(&mut app); - match app.run() { - AppExit::Success => {} - AppExit::Error(e) => { - log::error!("Error running new app: {e}"); - } - } -} - -fn run_old_app() { let event_loop = EventLoop::new().unwrap(); event_loop.set_control_flow(ControlFlow::Poll); - let vulkan_context = old_app::vulkan_context::VulkanContext::from(&event_loop); - let mut app = old_app::app::App::from(vulkan_context); + let vulkan_context = render::vulkan_context::VulkanContext::from(&event_loop); + let mut app = render::app::App::from(vulkan_context); match event_loop.run_app(&mut app) { Ok(_) => {} diff --git a/src/old_app/app.rs b/src/render/app.rs similarity index 97% rename from src/old_app/app.rs rename to src/render/app.rs index cfcf038..d46757e 100644 --- a/src/old_app/app.rs +++ b/src/render/app.rs @@ -1,6 +1,6 @@ -use crate::old_app::scene::Scene; -use crate::old_app::vulkan_context::VulkanContext; -use crate::old_app::window_render_context::WindowRenderContext; +use crate::render::scene::Scene; +use crate::render::vulkan_context::VulkanContext; +use crate::render::window_render_context::WindowRenderContext; use std::sync::Arc; use vulkano::command_buffer::{RenderingAttachmentInfo, RenderingInfo}; use vulkano::render_pass::{AttachmentLoadOp, AttachmentStoreOp}; diff --git a/src/old_app/mod.rs b/src/render/mod.rs similarity index 86% rename from src/old_app/mod.rs rename to src/render/mod.rs index 9fce0a9..c1bda07 100644 --- a/src/old_app/mod.rs +++ b/src/render/mod.rs @@ -3,3 +3,4 @@ pub mod pipelines; pub mod scene; pub mod vulkan_context; pub mod window_render_context; +pub mod vertex; diff --git a/src/old_app/pipelines/mod.rs b/src/render/pipelines/mod.rs similarity index 100% rename from src/old_app/pipelines/mod.rs rename to src/render/pipelines/mod.rs diff --git a/src/old_app/pipelines/triangle_pipeline.rs b/src/render/pipelines/triangle_pipeline.rs similarity index 98% rename from src/old_app/pipelines/triangle_pipeline.rs rename to src/render/pipelines/triangle_pipeline.rs index e573747..04aef22 100644 --- a/src/old_app/pipelines/triangle_pipeline.rs +++ b/src/render/pipelines/triangle_pipeline.rs @@ -20,7 +20,7 @@ use vulkano::pipeline::{ use vulkano::shader::{EntryPoint, ShaderStages}; use vulkano::swapchain::Swapchain; -use crate::core::render::vertex::Vertex2D; +use crate::render::vertex::Vertex2D; pub mod shaders { pub mod vs { diff --git a/src/old_app/scene.rs b/src/render/scene.rs similarity index 96% rename from src/old_app/scene.rs rename to src/render/scene.rs index 72e3f48..9dc7bf9 100644 --- a/src/old_app/scene.rs +++ b/src/render/scene.rs @@ -1,4 +1,4 @@ -use crate::old_app::pipelines::triangle_pipeline::shaders::vs; +use crate::render::pipelines::triangle_pipeline::shaders::vs; use glam::{Mat3, Mat4, Vec3}; use std::error::Error; use std::sync::Arc; @@ -9,8 +9,8 @@ use vulkano::descriptor_set::{DescriptorSet, WriteDescriptorSet}; use vulkano::memory::allocator::{AllocationCreateInfo, MemoryTypeFilter}; use vulkano::pipeline::{GraphicsPipeline, Pipeline, PipelineBindPoint}; -use crate::core::render::vertex::Vertex2D; -use crate::old_app::pipelines::triangle_pipeline::create_triangle_pipeline; +use crate::render::vertex::Vertex2D; +use crate::render::pipelines::triangle_pipeline::create_triangle_pipeline; use super::vulkan_context::VulkanContext; use super::window_render_context::WindowRenderContext; diff --git a/src/core/render/vertex.rs b/src/render/vertex.rs similarity index 100% rename from src/core/render/vertex.rs rename to src/render/vertex.rs diff --git a/src/old_app/vulkan_context.rs b/src/render/vulkan_context.rs similarity index 100% rename from src/old_app/vulkan_context.rs rename to src/render/vulkan_context.rs diff --git a/src/old_app/window_render_context.rs b/src/render/window_render_context.rs similarity index 100% rename from src/old_app/window_render_context.rs rename to src/render/window_render_context.rs From a4a6c0c60a94109b83e8ef4ef078def157336371 Mon Sep 17 00:00:00 2001 From: Florian RICHER Date: Sun, 25 May 2025 19:48:59 +0200 Subject: [PATCH 034/105] Use vulkano_util instead --- Cargo.lock | 12 ++ Cargo.toml | 1 + src/main.rs | 24 ++- src/render/app.rs | 163 +++++++---------- src/render/mod.rs | 3 +- src/render/pipelines/triangle_pipeline.rs | 5 +- src/render/scene.rs | 40 +++-- src/render/vulkan_context.rs | 202 ++-------------------- src/render/window_render_context.rs | 102 ----------- 9 files changed, 137 insertions(+), 415 deletions(-) delete mode 100644 src/render/window_render_context.rs diff --git a/Cargo.lock b/Cargo.lock index 1f58cce..43de2a6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1256,6 +1256,7 @@ dependencies = [ "thiserror 2.0.12", "vulkano", "vulkano-shaders", + "vulkano-util", "winit", ] @@ -1655,6 +1656,17 @@ dependencies = [ "vulkano", ] +[[package]] +name = "vulkano-util" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e25dc54fd5e14a0e01c7282f9b5d9c6e133745e1df3228b0352366e34c83bb6b" +dependencies = [ + "foldhash", + "vulkano", + "winit", +] + [[package]] name = "walkdir" version = "2.5.0" diff --git a/Cargo.toml b/Cargo.toml index ce36410..a79f536 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,7 @@ winit = { version = "0.30", features = ["rwh_06"] } vulkano = "0.35" vulkano-shaders = "0.35" +vulkano-util = "0.35" # Math glam = { version = "0.30" } diff --git a/src/main.rs b/src/main.rs index ca3f901..4ef9dfb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,14 +1,34 @@ +use vulkano::device::{DeviceExtensions, DeviceFeatures}; +use vulkano_util::context::{VulkanoConfig, VulkanoContext}; use winit::event_loop::{ControlFlow, EventLoop}; mod render; fn main() { env_logger::init(); + let device_extensions = DeviceExtensions { + khr_swapchain: true, + ..Default::default() + }; + + let device_features = DeviceFeatures { + dynamic_rendering: true, + ..Default::default() + }; + + let vulkano_config = VulkanoConfig { + print_device_name: true, + device_extensions, + device_features, + ..Default::default() + }; + + let vulkano_context = VulkanoContext::new(vulkano_config); + let event_loop = EventLoop::new().unwrap(); event_loop.set_control_flow(ControlFlow::Poll); - let vulkan_context = render::vulkan_context::VulkanContext::from(&event_loop); - let mut app = render::app::App::from(vulkan_context); + let mut app = render::app::App::from(vulkano_context); match event_loop.run_app(&mut app) { Ok(_) => {} diff --git a/src/render/app.rs b/src/render/app.rs index d46757e..ebcf8be 100644 --- a/src/render/app.rs +++ b/src/render/app.rs @@ -1,28 +1,32 @@ use crate::render::scene::Scene; -use crate::render::vulkan_context::VulkanContext; -use crate::render::window_render_context::WindowRenderContext; -use std::sync::Arc; -use vulkano::command_buffer::{RenderingAttachmentInfo, RenderingInfo}; +use vulkano::command_buffer::{ + AutoCommandBufferBuilder, CommandBufferUsage, RenderingAttachmentInfo, RenderingInfo, +}; +use vulkano::pipeline::graphics::viewport::Viewport; use vulkano::render_pass::{AttachmentLoadOp, AttachmentStoreOp}; -use vulkano::swapchain::{SwapchainPresentInfo, acquire_next_image}; +use vulkano::swapchain::{PresentMode, SwapchainPresentInfo}; use vulkano::sync::GpuFuture; use vulkano::{Validated, VulkanError, sync}; +use vulkano_util::context::VulkanoContext; +use vulkano_util::window::{VulkanoWindows, WindowDescriptor}; use winit::application::ApplicationHandler; use winit::event::WindowEvent; use winit::event_loop::ActiveEventLoop; use winit::window::WindowId; +use super::vulkan_context::VulkanContext; + pub struct App { vulkan_context: VulkanContext, - window_render_context: Option, + vulkano_windows: VulkanoWindows, scene: Option, } -impl From for App { - fn from(vulkan_context: VulkanContext) -> Self { +impl From for App { + fn from(vulkano_context: VulkanoContext) -> Self { Self { - vulkan_context, - window_render_context: None, + vulkan_context: VulkanContext::new(vulkano_context), + vulkano_windows: VulkanoWindows::default(), scene: None, } } @@ -30,26 +34,23 @@ impl From for App { impl ApplicationHandler for App { fn resumed(&mut self, event_loop: &ActiveEventLoop) { - let window_attributes = winit::window::Window::default_attributes() - .with_title("Rust ASH Test") - .with_inner_size(winit::dpi::PhysicalSize::new( - f64::from(800), - f64::from(600), - )); + self.vulkano_windows.create_window( + event_loop, + self.vulkan_context.vulkano_context(), + &WindowDescriptor { + title: "Rust ASH Test".to_string(), + width: 800.0, + height: 600.0, + present_mode: PresentMode::Fifo, + ..Default::default() + }, + |_| {}, + ); - let window = Arc::new(event_loop.create_window(window_attributes).unwrap()); - - let surface = self.vulkan_context.create_surface(window.clone()); - - self.window_render_context = Some(WindowRenderContext::new( - window, - surface, - &self.vulkan_context.device, - )); self.scene = Some( Scene::load( &self.vulkan_context, - self.window_render_context.as_ref().unwrap(), + &self.vulkano_windows.get_primary_renderer_mut().unwrap(), ) .unwrap(), ); @@ -61,45 +62,27 @@ impl ApplicationHandler for App { log::debug!("The close button was pressed; stopping"); event_loop.exit(); } - WindowEvent::Resized(_) => { - let rcx = self.window_render_context.as_mut().unwrap(); - rcx.recreate_swapchain = true; - } WindowEvent::RedrawRequested => { - let (image_index, acquire_future) = { - let rcx = self.window_render_context.as_mut().unwrap(); - let window_size = rcx.window.inner_size(); + let renderer = self.vulkano_windows.get_primary_renderer_mut().unwrap(); + let acquire_future = renderer.acquire(None, |_| {}).unwrap(); - if window_size.width == 0 || window_size.height == 0 { - return; - } - - rcx.previous_frame_end.as_mut().unwrap().cleanup_finished(); - rcx.update_swapchain().unwrap(); - - let (image_index, suboptimal, acquire_future) = - match acquire_next_image(rcx.swapchain.clone(), None) - .map_err(Validated::unwrap) - { - Ok(r) => r, - Err(VulkanError::OutOfDate) => { - rcx.recreate_swapchain = true; - return; - } - Err(e) => panic!("failed to acquire next image: {e}"), - }; - - if suboptimal { - rcx.recreate_swapchain = true; - } - - (image_index, acquire_future) - }; - - let mut builder = self.vulkan_context.create_render_builder(); + let mut builder = AutoCommandBufferBuilder::primary( + self.vulkan_context.command_buffer_allocator().clone(), + self.vulkan_context + .vulkano_context() + .graphics_queue() + .queue_family_index(), + CommandBufferUsage::OneTimeSubmit, + ) + .unwrap(); { - let rcx = self.window_render_context.as_ref().unwrap(); + let viewport = Viewport { + offset: [0.0, 0.0], + extent: renderer.resolution(), + depth_range: 0.0..=1.0, + }; + builder .begin_rendering(RenderingInfo { color_attachments: vec![Some(RenderingAttachmentInfo { @@ -107,23 +90,19 @@ impl ApplicationHandler for App { store_op: AttachmentStoreOp::Store, clear_value: Some([0.0, 0.0, 0.0, 1.0].into()), ..RenderingAttachmentInfo::image_view( - rcx.attachment_image_views[image_index as usize].clone(), + renderer.swapchain_image_view().clone(), ) })], ..Default::default() }) .unwrap() - .set_viewport(0, [rcx.viewport.clone()].into_iter().collect()) + .set_viewport(0, [viewport].into_iter().collect()) .unwrap(); } if let Some(scene) = self.scene.as_ref() { scene - .render( - &self.vulkan_context, - &self.window_render_context.as_ref().unwrap(), - &mut builder, - ) + .render(&self.vulkan_context, &renderer, &mut builder) .unwrap(); } @@ -131,48 +110,24 @@ impl ApplicationHandler for App { let command_buffer = builder.build().unwrap(); - { - let rcx = self.window_render_context.as_mut().unwrap(); + let future = acquire_future + .then_execute( + self.vulkan_context + .vulkano_context() + .graphics_queue() + .clone(), + command_buffer, + ) + .unwrap(); - let future = rcx - .previous_frame_end - .take() - .unwrap() - .join(acquire_future) - .then_execute(self.vulkan_context.graphics_queue.clone(), command_buffer) - .unwrap() - .then_swapchain_present( - self.vulkan_context.graphics_queue.clone(), - SwapchainPresentInfo::swapchain_image_index( - rcx.swapchain.clone(), - image_index, - ), - ) - .then_signal_fence_and_flush(); - - match future.map_err(Validated::unwrap) { - Ok(future) => { - rcx.previous_frame_end = Some(future.boxed()); - } - Err(VulkanError::OutOfDate) => { - rcx.recreate_swapchain = true; - rcx.previous_frame_end = - Some(sync::now(self.vulkan_context.device.clone()).boxed()); - } - Err(e) => { - println!("failed to flush future: {e}"); - rcx.previous_frame_end = - Some(sync::now(self.vulkan_context.device.clone()).boxed()); - } - } - } + renderer.present(future.boxed(), true); } _ => {} } } fn about_to_wait(&mut self, _event_loop: &ActiveEventLoop) { - let rcx = self.window_render_context.as_mut().unwrap(); - rcx.window.request_redraw(); + let window = self.vulkano_windows.get_primary_window().unwrap(); + window.request_redraw(); } } diff --git a/src/render/mod.rs b/src/render/mod.rs index c1bda07..d93a2e0 100644 --- a/src/render/mod.rs +++ b/src/render/mod.rs @@ -1,6 +1,5 @@ pub mod app; pub mod pipelines; pub mod scene; -pub mod vulkan_context; -pub mod window_render_context; pub mod vertex; +pub mod vulkan_context; diff --git a/src/render/pipelines/triangle_pipeline.rs b/src/render/pipelines/triangle_pipeline.rs index 04aef22..b5f91b2 100644 --- a/src/render/pipelines/triangle_pipeline.rs +++ b/src/render/pipelines/triangle_pipeline.rs @@ -5,6 +5,7 @@ use vulkano::descriptor_set::layout::{ DescriptorSetLayoutBinding, DescriptorSetLayoutCreateInfo, DescriptorType, }; use vulkano::device::Device; +use vulkano::format::Format; use vulkano::pipeline::graphics::GraphicsPipelineCreateInfo; use vulkano::pipeline::graphics::color_blend::{ColorBlendAttachmentState, ColorBlendState}; use vulkano::pipeline::graphics::input_assembly::InputAssemblyState; @@ -40,7 +41,7 @@ pub mod shaders { pub fn create_triangle_pipeline( device: &Arc, - swapchain: &Arc, + swapchain_format: Format, ) -> Result, Box> { let (vs, fs) = load_shaders(device)?; let vertex_input_state = Vertex2D::per_vertex().definition(&vs)?; @@ -71,7 +72,7 @@ pub fn create_triangle_pipeline( let layout = PipelineLayout::new(device.clone(), create_info)?; let subpass = PipelineRenderingCreateInfo { - color_attachment_formats: vec![Some(swapchain.image_format())], + color_attachment_formats: vec![Some(swapchain_format)], ..Default::default() }; diff --git a/src/render/scene.rs b/src/render/scene.rs index 9dc7bf9..82ae981 100644 --- a/src/render/scene.rs +++ b/src/render/scene.rs @@ -6,14 +6,14 @@ use std::time::Instant; use vulkano::buffer::{Buffer, BufferCreateInfo, BufferUsage, Subbuffer}; use vulkano::command_buffer::{AutoCommandBufferBuilder, PrimaryAutoCommandBuffer}; use vulkano::descriptor_set::{DescriptorSet, WriteDescriptorSet}; -use vulkano::memory::allocator::{AllocationCreateInfo, MemoryTypeFilter}; +use vulkano::memory::allocator::{AllocationCreateInfo, MemoryTypeFilter, StandardMemoryAllocator}; use vulkano::pipeline::{GraphicsPipeline, Pipeline, PipelineBindPoint}; +use vulkano_util::renderer::VulkanoWindowRenderer; -use crate::render::vertex::Vertex2D; use crate::render::pipelines::triangle_pipeline::create_triangle_pipeline; +use crate::render::vertex::Vertex2D; use super::vulkan_context::VulkanContext; -use super::window_render_context::WindowRenderContext; const VERTICES: [Vertex2D; 12] = [ // Triangle en haut à gauche @@ -79,13 +79,17 @@ pub struct Scene { impl Scene { pub fn load( - vulkan_context: &VulkanContext, - window_render_context: &WindowRenderContext, + vulkano_context: &VulkanContext, + vulkano_window_renderer: &VulkanoWindowRenderer, ) -> Result> { - let pipeline = - create_triangle_pipeline(&vulkan_context.device, &window_render_context.swapchain)?; - let vertex_buffer = - Vertex2D::create_buffer(Vec::from_iter(VERTICES), &vulkan_context.memory_allocator)?; + let pipeline = create_triangle_pipeline( + &vulkano_context.vulkano_context().device(), + vulkano_window_renderer.swapchain_format(), + )?; + let vertex_buffer = Vertex2D::create_buffer( + Vec::from_iter(VERTICES), + &vulkano_context.vulkano_context().memory_allocator(), + )?; Ok(Scene { pipeline, @@ -97,16 +101,19 @@ impl Scene { pub fn render( &self, vulkan_context: &VulkanContext, - window_render_context: &WindowRenderContext, + vulkano_window_renderer: &VulkanoWindowRenderer, builder: &mut AutoCommandBufferBuilder, ) -> Result<(), Box> { let vertex_count = self.vertex_buffer.len() as u32; let instance_count = vertex_count / 3; - let uniform_buffer = self.get_uniform_buffer(vulkan_context, window_render_context); + let uniform_buffer = self.get_uniform_buffer( + &vulkan_context.vulkano_context().memory_allocator(), + vulkano_window_renderer.aspect_ratio(), + ); let layout = &self.pipeline.layout().set_layouts()[0]; let descriptor_set = DescriptorSet::new( - vulkan_context.descriptor_set_allocator.clone(), + vulkan_context.descriptor_set_allocator().clone(), layout.clone(), [WriteDescriptorSet::buffer(0, uniform_buffer)], [], @@ -131,18 +138,15 @@ impl Scene { fn get_uniform_buffer( &self, - vulkan_context: &VulkanContext, - window_render_context: &WindowRenderContext, + memory_allocator: &Arc, + aspect_ratio: f32, ) -> Subbuffer { - let swapchain = &window_render_context.swapchain; let elapsed = self.rotation_start.elapsed(); let rotation = elapsed.as_secs() as f64 + elapsed.subsec_nanos() as f64 / 1_000_000_000.0; let rotation = Mat3::from_rotation_y(rotation as f32); // NOTE: This teapot was meant for OpenGL where the origin is at the lower left // instead the origin is at the upper left in Vulkan, so we reverse the Y axis. - let aspect_ratio = swapchain.image_extent()[0] as f32 / swapchain.image_extent()[1] as f32; - let proj = Mat4::perspective_rh_gl(std::f32::consts::FRAC_PI_2, aspect_ratio, 0.01, 100.0); let view = Mat4::look_at_rh( Vec3::new(0.3, 0.3, 1.0), @@ -158,7 +162,7 @@ impl Scene { }; Buffer::from_data( - vulkan_context.memory_allocator.clone(), + memory_allocator.clone(), BufferCreateInfo { usage: BufferUsage::UNIFORM_BUFFER, ..Default::default() diff --git a/src/render/vulkan_context.rs b/src/render/vulkan_context.rs index 332dbcf..3d91ec3 100644 --- a/src/render/vulkan_context.rs +++ b/src/render/vulkan_context.rs @@ -1,213 +1,45 @@ -use std::{any::Any, sync::Arc}; +use std::sync::Arc; use vulkano::{ - Version, VulkanLibrary, - command_buffer::{ - AutoCommandBufferBuilder, CommandBufferUsage, PrimaryAutoCommandBuffer, - allocator::StandardCommandBufferAllocator, - }, + command_buffer::allocator::StandardCommandBufferAllocator, descriptor_set::allocator::StandardDescriptorSetAllocator, - device::{ - Device, DeviceCreateInfo, DeviceExtensions, DeviceFeatures, Queue, QueueCreateInfo, - QueueFlags, - physical::{PhysicalDevice, PhysicalDeviceType}, - }, - instance::{Instance, InstanceCreateFlags, InstanceCreateInfo, InstanceExtensions}, - memory::allocator::StandardMemoryAllocator, - swapchain::Surface, -}; -use winit::{ - event_loop::EventLoop, - raw_window_handle::{HasDisplayHandle, HasWindowHandle}, }; +use vulkano_util::context::VulkanoContext; pub struct VulkanContext { - instance: Arc, - pub device: Arc, - pub graphics_queue: Arc, - - pub memory_allocator: Arc, - pub command_buffer_allocator: Arc, - pub descriptor_set_allocator: Arc, + vulkano_context: VulkanoContext, + command_buffer_allocator: Arc, + descriptor_set_allocator: Arc, } -impl From<&EventLoop<()>> for VulkanContext { - fn from(event_loop: &EventLoop<()>) -> Self { - let library = load_library(); - - let enabled_extensions = Surface::required_extensions(event_loop).unwrap(); - log::debug!("Surface required extensions: {enabled_extensions:?}"); - - let instance = create_instance(library.clone(), enabled_extensions); - - let (device, mut queues) = pick_graphics_device(&instance, event_loop); - let graphics_queue = queues.next().unwrap(); - - let memory_allocator = Arc::new(StandardMemoryAllocator::new_default(device.clone())); - +impl VulkanContext { + pub fn new(vulkano_context: VulkanoContext) -> Self { let command_buffer_allocator = Arc::new(StandardCommandBufferAllocator::new( - device.clone(), + vulkano_context.device().clone(), Default::default(), )); let descriptor_set_allocator = Arc::new(StandardDescriptorSetAllocator::new( - device.clone(), + vulkano_context.device().clone(), Default::default(), )); Self { - instance, - device, - graphics_queue, - memory_allocator, + vulkano_context, command_buffer_allocator, descriptor_set_allocator, } } -} -impl VulkanContext { - pub fn create_surface( - &self, - window: Arc, - ) -> Arc { - Surface::from_window(self.instance.clone(), window).unwrap() + pub fn vulkano_context(&self) -> &VulkanoContext { + &self.vulkano_context } - pub fn create_render_builder(&self) -> AutoCommandBufferBuilder { - AutoCommandBufferBuilder::primary( - self.command_buffer_allocator.clone(), - self.graphics_queue.queue_family_index(), - CommandBufferUsage::OneTimeSubmit, - ) - .unwrap() - } -} - -fn load_library() -> Arc { - let library = VulkanLibrary::new().unwrap(); - - log::debug!("Available layer:"); - for layer in library.layer_properties().unwrap() { - log::debug!( - "\t - Layer name: {}, Description: {}, Implementation Version: {}, Vulkan Version: {}", - layer.name(), - layer.description(), - layer.implementation_version(), - layer.vulkan_version() - ); + pub fn command_buffer_allocator(&self) -> &Arc { + &self.command_buffer_allocator } - library -} - -fn create_instance( - library: Arc, - required_extensions: InstanceExtensions, -) -> Arc { - Instance::new( - library, - InstanceCreateInfo { - // Enable enumerating devices that use non-conformant Vulkan implementations. - // (e.g. MoltenVK) - flags: InstanceCreateFlags::ENUMERATE_PORTABILITY, - enabled_extensions: required_extensions, - enabled_layers: vec![String::from("VK_LAYER_KHRONOS_validation")], - ..Default::default() - }, - ) - .unwrap() -} - -fn find_physical_device_queue_family_indexes( - physical_device: &Arc, - event_loop: &EventLoop<()>, -) -> Option { - let mut graphic_queue_family_index = None; - - for (i, queue_family_property) in physical_device.queue_family_properties().iter().enumerate() { - if queue_family_property - .queue_flags - .intersects(QueueFlags::GRAPHICS) - && physical_device - .presentation_support(i as u32, event_loop) - .unwrap() - { - graphic_queue_family_index = Some(i as u32); - } + pub fn descriptor_set_allocator(&self) -> &Arc { + &self.descriptor_set_allocator } - - graphic_queue_family_index -} - -fn pick_physical_device_and_queue_family_indexes( - instance: &Arc, - event_loop: &EventLoop<()>, - device_extensions: &DeviceExtensions, -) -> Option<(Arc, u32)> { - instance - .enumerate_physical_devices() - .unwrap() - .filter(|p| { - p.api_version() >= Version::V1_3 || p.supported_extensions().khr_dynamic_rendering - }) - .filter(|p| p.supported_extensions().contains(device_extensions)) - .filter_map(|p| { - find_physical_device_queue_family_indexes(&p, event_loop) - .and_then(|indexes| Some((p, indexes))) - }) - .min_by_key(|(p, _)| match p.properties().device_type { - PhysicalDeviceType::DiscreteGpu => 0, - PhysicalDeviceType::IntegratedGpu => 1, - PhysicalDeviceType::VirtualGpu => 2, - PhysicalDeviceType::Cpu => 3, - PhysicalDeviceType::Other => 4, - _ => 5, - }) -} - -fn pick_graphics_device( - instance: &Arc, - event_loop: &EventLoop<()>, -) -> ( - Arc, - impl ExactSizeIterator> + use<>, -) { - let mut device_extensions = DeviceExtensions { - khr_swapchain: true, - ..DeviceExtensions::empty() - }; - - let (physical_device, graphics_family_index) = - pick_physical_device_and_queue_family_indexes(instance, event_loop, &device_extensions) - .unwrap(); - - log::debug!( - "Using device: {} (type: {:?})", - physical_device.properties().device_name, - physical_device.properties().device_type, - ); - - if physical_device.api_version() < Version::V1_3 { - device_extensions.khr_dynamic_rendering = true; - } - - log::debug!("Using device extensions: {:#?}", device_extensions); - - Device::new( - physical_device, - DeviceCreateInfo { - queue_create_infos: vec![QueueCreateInfo { - queue_family_index: graphics_family_index, - ..Default::default() - }], - enabled_extensions: device_extensions, - enabled_features: DeviceFeatures { - dynamic_rendering: true, - ..DeviceFeatures::empty() - }, - ..Default::default() - }, - ) - .unwrap() } diff --git a/src/render/window_render_context.rs b/src/render/window_render_context.rs deleted file mode 100644 index 54120d0..0000000 --- a/src/render/window_render_context.rs +++ /dev/null @@ -1,102 +0,0 @@ -use std::sync::Arc; -use vulkano::device::Device; -use vulkano::image::view::ImageView; -use vulkano::image::{Image, ImageUsage}; -use vulkano::pipeline::graphics::viewport::Viewport; -use vulkano::swapchain::{Surface, Swapchain, SwapchainCreateInfo}; -use vulkano::sync::GpuFuture; -use vulkano::{Validated, VulkanError, sync}; -use winit::window::Window; - -pub struct WindowRenderContext { - pub window: Arc, - pub swapchain: Arc, - pub attachment_image_views: Vec>, - pub viewport: Viewport, - pub recreate_swapchain: bool, - pub previous_frame_end: Option>, -} - -impl WindowRenderContext { - pub fn new(window: Arc, surface: Arc, device: &Arc) -> Self { - let window_size = window.inner_size(); - - let (swapchain, images) = { - let surface_capabilities = device - .physical_device() - .surface_capabilities(&surface, Default::default()) - .unwrap(); - - let (image_format, _) = device - .physical_device() - .surface_formats(&surface, Default::default()) - .unwrap()[0]; - - Swapchain::new( - device.clone(), - surface, - SwapchainCreateInfo { - // 2 because with some graphics driver, it crash on fullscreen because fullscreen need to min image to works. - min_image_count: surface_capabilities.min_image_count.max(2), - image_format, - image_extent: window_size.into(), - image_usage: ImageUsage::COLOR_ATTACHMENT, - composite_alpha: surface_capabilities - .supported_composite_alpha - .into_iter() - .next() - .unwrap(), - - ..Default::default() - }, - ) - .unwrap() - }; - - let attachment_image_views = window_size_dependent_setup(&images); - - let viewport = Viewport { - offset: [0.0, 0.0], - extent: window_size.into(), - depth_range: 0.0..=1.0, - }; - - let recreate_swapchain = false; - let previous_frame_end = Some(sync::now(device.clone()).boxed()); - - Self { - window, - swapchain, - attachment_image_views, - viewport, - recreate_swapchain, - previous_frame_end, - } - } - - pub fn update_swapchain(&mut self) -> Result<(), Validated> { - if !self.recreate_swapchain { - return Ok(()); - } - - let window_size = self.window.inner_size(); - let (new_swapchain, new_images) = self.swapchain.recreate(SwapchainCreateInfo { - image_extent: window_size.into(), - ..self.swapchain.create_info() - })?; - - self.swapchain = new_swapchain; - self.attachment_image_views = window_size_dependent_setup(&new_images); - self.viewport.extent = window_size.into(); - self.recreate_swapchain = false; - - Ok(()) - } -} - -fn window_size_dependent_setup(images: &[Arc]) -> Vec> { - images - .iter() - .map(|image| ImageView::new_default(image.clone()).unwrap()) - .collect::>() -} From 6dae0339dbfd97257941ef5b79f81f2ec7a17254 Mon Sep 17 00:00:00 2001 From: Florian RICHER Date: Sun, 25 May 2025 21:10:58 +0200 Subject: [PATCH 035/105] Fix warning --- src/render/app.rs | 7 +++---- src/render/pipelines/triangle_pipeline.rs | 1 - src/render/scene.rs | 6 +++--- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/render/app.rs b/src/render/app.rs index ebcf8be..69db339 100644 --- a/src/render/app.rs +++ b/src/render/app.rs @@ -4,9 +4,8 @@ use vulkano::command_buffer::{ }; use vulkano::pipeline::graphics::viewport::Viewport; use vulkano::render_pass::{AttachmentLoadOp, AttachmentStoreOp}; -use vulkano::swapchain::{PresentMode, SwapchainPresentInfo}; +use vulkano::swapchain::PresentMode; use vulkano::sync::GpuFuture; -use vulkano::{Validated, VulkanError, sync}; use vulkano_util::context::VulkanoContext; use vulkano_util::window::{VulkanoWindows, WindowDescriptor}; use winit::application::ApplicationHandler; @@ -50,7 +49,7 @@ impl ApplicationHandler for App { self.scene = Some( Scene::load( &self.vulkan_context, - &self.vulkano_windows.get_primary_renderer_mut().unwrap(), + self.vulkano_windows.get_primary_renderer_mut().unwrap(), ) .unwrap(), ); @@ -102,7 +101,7 @@ impl ApplicationHandler for App { if let Some(scene) = self.scene.as_ref() { scene - .render(&self.vulkan_context, &renderer, &mut builder) + .render(&self.vulkan_context, renderer, &mut builder) .unwrap(); } diff --git a/src/render/pipelines/triangle_pipeline.rs b/src/render/pipelines/triangle_pipeline.rs index b5f91b2..9fb3629 100644 --- a/src/render/pipelines/triangle_pipeline.rs +++ b/src/render/pipelines/triangle_pipeline.rs @@ -19,7 +19,6 @@ use vulkano::pipeline::{ DynamicState, GraphicsPipeline, PipelineLayout, PipelineShaderStageCreateInfo, }; use vulkano::shader::{EntryPoint, ShaderStages}; -use vulkano::swapchain::Swapchain; use crate::render::vertex::Vertex2D; diff --git a/src/render/scene.rs b/src/render/scene.rs index 82ae981..36ba182 100644 --- a/src/render/scene.rs +++ b/src/render/scene.rs @@ -83,12 +83,12 @@ impl Scene { vulkano_window_renderer: &VulkanoWindowRenderer, ) -> Result> { let pipeline = create_triangle_pipeline( - &vulkano_context.vulkano_context().device(), + vulkano_context.vulkano_context().device(), vulkano_window_renderer.swapchain_format(), )?; let vertex_buffer = Vertex2D::create_buffer( Vec::from_iter(VERTICES), - &vulkano_context.vulkano_context().memory_allocator(), + vulkano_context.vulkano_context().memory_allocator(), )?; Ok(Scene { @@ -108,7 +108,7 @@ impl Scene { let instance_count = vertex_count / 3; let uniform_buffer = self.get_uniform_buffer( - &vulkan_context.vulkano_context().memory_allocator(), + vulkan_context.vulkano_context().memory_allocator(), vulkano_window_renderer.aspect_ratio(), ); let layout = &self.pipeline.layout().set_layouts()[0]; From 2c3392c3ea15f53c5d9932630e55116ab7ed791f Mon Sep 17 00:00:00 2001 From: Florian RICHER Date: Sun, 25 May 2025 22:36:27 +0200 Subject: [PATCH 036/105] add EGUI --- Cargo.lock | 1158 ++++++++++++++++++++++++++++++++++++++++++++- Cargo.toml | 2 +- src/render/app.rs | 58 ++- 3 files changed, 1201 insertions(+), 17 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 43de2a6..d461d0b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -18,6 +18,12 @@ version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c71b1793ee61086797f5c80b6efa2b8ffa6d5dd703f118545808a7f2e27f7046" +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + [[package]] name = "ahash" version = "0.8.12" @@ -25,7 +31,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" dependencies = [ "cfg-if", - "getrandom", + "getrandom 0.3.3", "once_cell", "version_check", "zerocopy", @@ -40,6 +46,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "aligned-vec" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4aa90d7ce82d4be67b64039a3d588d38dbcc6736577de4a847025ce5b0c468d1" + [[package]] name = "android-activity" version = "0.6.0" @@ -123,6 +135,43 @@ version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" +[[package]] +name = "arbitrary" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" + +[[package]] +name = "arboard" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1df21f715862ede32a0c525ce2ca4d52626bb0007f8c18b87a384503ac33e70" +dependencies = [ + "clipboard-win", + "image", + "log", + "objc2 0.6.1", + "objc2-app-kit 0.3.1", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-foundation 0.3.1", + "parking_lot", + "percent-encoding", + "windows-sys 0.59.0", + "x11rb", +] + +[[package]] +name = "arg_enum_proc_macro" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "arrayref" version = "0.3.9" @@ -162,6 +211,35 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +[[package]] +name = "av1-grain" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f3efb2ca85bc610acfa917b5aaa36f3fcbebed5b3182d7f877b02531c4b80c8" +dependencies = [ + "anyhow", + "arrayvec", + "log", + "nom", + "num-rational", + "v_frame", +] + +[[package]] +name = "avif-serialize" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98922d6a4cfbcb08820c69d8eeccc05bb1f29bfa06b4f5b1dbfe9a868bd7608e" +dependencies = [ + "arrayvec", +] + +[[package]] +name = "bit_field" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61" + [[package]] name = "bitflags" version = "1.3.2" @@ -174,6 +252,12 @@ version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" +[[package]] +name = "bitstream-io" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6099cdc01846bc367c4e7dd630dc5966dccf36b652fae7a74e17b640411a91b2" + [[package]] name = "block2" version = "0.5.1" @@ -183,6 +267,12 @@ dependencies = [ "objc2 0.5.2", ] +[[package]] +name = "built" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56ed6191a7e78c36abdb16ab65341eefd73d64d303fffccdbb00d51e4205967b" + [[package]] name = "bumpalo" version = "3.17.0" @@ -209,6 +299,12 @@ dependencies = [ "syn", ] +[[package]] +name = "byteorder-lite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" + [[package]] name = "bytes" version = "1.10.1" @@ -258,6 +354,16 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" +[[package]] +name = "cfg-expr" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" +dependencies = [ + "smallvec", + "target-lexicon", +] + [[package]] name = "cfg-if" version = "1.0.0" @@ -270,6 +376,15 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +[[package]] +name = "clipboard-win" +version = "5.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15efe7a882b08f34e38556b14f2fb3daa98769d06c7f0c1b076dfd0d983bc892" +dependencies = [ + "error-code", +] + [[package]] name = "cmake" version = "0.1.54" @@ -279,6 +394,12 @@ dependencies = [ "cc", ] +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + [[package]] name = "colorchoice" version = "1.0.3" @@ -314,6 +435,16 @@ dependencies = [ "libc", ] +[[package]] +name = "core-foundation" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b55271e5c8c478ad3f38ad24ef34923091e0548492a266d19b3c0b4d82574c63" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -327,7 +458,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" dependencies = [ "bitflags 1.3.2", - "core-foundation", + "core-foundation 0.9.4", "core-graphics-types", "foreign-types", "libc", @@ -340,10 +471,38 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" dependencies = [ "bitflags 1.3.2", - "core-foundation", + "core-foundation 0.9.4", "libc", ] +[[package]] +name = "crc32fast" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-queue" version = "0.3.12" @@ -387,6 +546,17 @@ dependencies = [ "objc2 0.6.1", ] +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "dlib" version = "0.5.2" @@ -408,6 +578,80 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" +[[package]] +name = "ecolor" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc4feb366740ded31a004a0e4452fbf84e80ef432ecf8314c485210229672fd1" +dependencies = [ + "bytemuck", + "emath", +] + +[[package]] +name = "egui" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25dd34cec49ab55d85ebf70139cb1ccd29c977ef6b6ba4fe85489d6877ee9ef3" +dependencies = [ + "ahash", + "bitflags 2.9.1", + "emath", + "epaint", + "log", + "nohash-hasher", + "profiling", +] + +[[package]] +name = "egui-winit" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d9dfbb78fe4eb9c3a39ad528b90ee5915c252e77bbab9d4ebc576541ab67e13" +dependencies = [ + "ahash", + "arboard", + "bytemuck", + "egui", + "log", + "profiling", + "raw-window-handle", + "smithay-clipboard", + "web-time", + "webbrowser", + "winit", +] + +[[package]] +name = "egui_winit_vulkano" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6abd5e69939cd416853fc3ec69c0e08721b175d03b508d5574849885c7e85a6" +dependencies = [ + "ahash", + "egui", + "egui-winit", + "image", + "vulkano", + "vulkano-shaders", + "winit", +] + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "emath" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e4cadcff7a5353ba72b7fea76bf2122b5ebdbc68e8155aa56dfdea90083fe1b" +dependencies = [ + "bytemuck", +] + [[package]] name = "env_filter" version = "0.1.3" @@ -431,6 +675,30 @@ dependencies = [ "log", ] +[[package]] +name = "epaint" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fcc0f5a7c613afd2dee5e4b30c3e6acafb8ad6f0edb06068811f708a67c562" +dependencies = [ + "ab_glyph", + "ahash", + "bytemuck", + "ecolor", + "emath", + "epaint_default_fonts", + "log", + "nohash-hasher", + "parking_lot", + "profiling", +] + +[[package]] +name = "epaint_default_fonts" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7e7a64c02cf7a5b51e745a9e45f60660a286f151c238b9d397b3e923f5082f" + [[package]] name = "equivalent" version = "1.0.2" @@ -447,6 +715,46 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "error-code" +version = "3.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59" + +[[package]] +name = "exr" +version = "1.73.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83197f59927b46c04a183a619b7c29df34e63e63c7869320862268c0ef687e0" +dependencies = [ + "bit_field", + "half", + "lebe", + "miniz_oxide", + "rayon-core", + "smallvec", + "zune-inflate", +] + +[[package]] +name = "fdeflate" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "flate2" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ced92e76e966ca2fd84c8f7aa01a4aea65b0eb6648d72f7c8f3e2764a67fece" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + [[package]] name = "foldhash" version = "0.1.5" @@ -480,6 +788,15 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + [[package]] name = "gethostname" version = "0.4.3" @@ -490,6 +807,17 @@ dependencies = [ "windows-targets 0.48.5", ] +[[package]] +name = "getrandom" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", +] + [[package]] name = "getrandom" version = "0.3.3" @@ -499,7 +827,17 @@ dependencies = [ "cfg-if", "libc", "r-efi", - "wasi", + "wasi 0.14.2+wasi-0.2.4", +] + +[[package]] +name = "gif" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fb2d69b19215e18bb912fa30f7ce15846e301408695e44e0ef719f1da9e19f2" +dependencies = [ + "color_quant", + "weezl", ] [[package]] @@ -531,12 +869,173 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "hermit-abi" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" +[[package]] +name = "home" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "icu_collections" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" + +[[package]] +name = "icu_properties" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "potential_utf", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" + +[[package]] +name = "icu_provider" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" +dependencies = [ + "displaydoc", + "icu_locale_core", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "idna" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "image" +version = "0.25.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db35664ce6b9810857a38a906215e75a9c879f0696556a39f59c62829710251a" +dependencies = [ + "bytemuck", + "byteorder-lite", + "color_quant", + "exr", + "gif", + "image-webp", + "num-traits", + "png", + "qoi", + "ravif", + "rayon", + "rgb", + "tiff", + "zune-core", + "zune-jpeg", +] + +[[package]] +name = "image-webp" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b77d01e822461baa8409e156015a1d91735549f0f2c17691bd2d996bef238f7f" +dependencies = [ + "byteorder-lite", + "quick-error", +] + +[[package]] +name = "imgref" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0263a3d970d5c054ed9312c0057b4f3bde9c0b33836d3637361d4a9e6e7a408" + [[package]] name = "indexmap" version = "2.9.0" @@ -547,12 +1046,32 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "interpolate_name" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.15" @@ -611,10 +1130,16 @@ version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" dependencies = [ - "getrandom", + "getrandom 0.3.3", "libc", ] +[[package]] +name = "jpeg-decoder" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0" + [[package]] name = "js-sys" version = "0.3.77" @@ -625,12 +1150,28 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "lebe" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" + [[package]] name = "libc" version = "0.2.172" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" +[[package]] +name = "libfuzzer-sys" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf78f52d400cf2d84a3a973a78a592b4adc535739e0a5597a0da6f0c357adc75" +dependencies = [ + "arbitrary", + "cc", +] + [[package]] name = "libloading" version = "0.8.7" @@ -658,6 +1199,12 @@ version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" +[[package]] +name = "litemap" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" + [[package]] name = "lock_api" version = "0.4.12" @@ -674,6 +1221,25 @@ version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" +[[package]] +name = "loop9" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fae87c125b03c1d2c0150c90365d7d6bcc53fb73a9acaef207d2d065860f062" +dependencies = [ + "imgref", +] + +[[package]] +name = "maybe-rayon" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519" +dependencies = [ + "cfg-if", + "rayon", +] + [[package]] name = "memchr" version = "2.7.4" @@ -695,6 +1261,16 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" +[[package]] +name = "miniz_oxide" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" +dependencies = [ + "adler2", + "simd-adler32", +] + [[package]] name = "ndk" version = "0.9.0" @@ -725,6 +1301,18 @@ dependencies = [ "jni-sys", ] +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + +[[package]] +name = "nohash-hasher" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" + [[package]] name = "nom" version = "7.1.3" @@ -735,6 +1323,62 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "noop_proc_macro" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + [[package]] name = "num_enum" version = "0.7.3" @@ -797,6 +1441,18 @@ dependencies = [ "objc2-quartz-core 0.2.2", ] +[[package]] +name = "objc2-app-kit" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6f29f568bec459b0ddff777cec4fe3fd8666d82d5a40ebd0ff7e66134f89bcc" +dependencies = [ + "bitflags 2.9.1", + "objc2 0.6.1", + "objc2-core-graphics", + "objc2-foundation 0.3.1", +] + [[package]] name = "objc2-cloud-kit" version = "0.2.2" @@ -844,6 +1500,19 @@ dependencies = [ "objc2 0.6.1", ] +[[package]] +name = "objc2-core-graphics" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "989c6c68c13021b5c2d6b71456ebb0f9dc78d752e86a98da7c716f4f9470f5a4" +dependencies = [ + "bitflags 2.9.1", + "dispatch2", + "objc2 0.6.1", + "objc2-core-foundation", + "objc2-io-surface", +] + [[package]] name = "objc2-core-image" version = "0.2.2" @@ -898,6 +1567,17 @@ dependencies = [ "objc2-core-foundation", ] +[[package]] +name = "objc2-io-surface" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7282e9ac92529fa3457ce90ebb15f4ecbc383e8338060960760fa2cf75420c3c" +dependencies = [ + "bitflags 2.9.1", + "objc2 0.6.1", + "objc2-core-foundation", +] + [[package]] name = "objc2-link-presentation" version = "0.2.2" @@ -906,7 +1586,7 @@ checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398" dependencies = [ "block2", "objc2 0.5.2", - "objc2-app-kit", + "objc2-app-kit 0.2.2", "objc2-foundation 0.2.2", ] @@ -1061,6 +1741,12 @@ dependencies = [ "windows-targets 0.52.6", ] +[[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.1" @@ -1099,6 +1785,19 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +[[package]] +name = "png" +version = "0.17.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + [[package]] name = "polling" version = "3.7.4" @@ -1129,6 +1828,24 @@ dependencies = [ "portable-atomic", ] +[[package]] +name = "potential_utf" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585" +dependencies = [ + "zerovec", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + [[package]] name = "proc-macro-crate" version = "3.3.0" @@ -1147,6 +1864,40 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "profiling" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afbdc74edc00b6f6a218ca6a5364d6226a259d4b8ea1af4a0ea063f27e179f4d" +dependencies = [ + "profiling-procmacros", +] + +[[package]] +name = "profiling-procmacros" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a65f2e60fbf1063868558d69c6beacf412dc755f9fc020f514b7955fc914fe30" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "qoi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "quick-error" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" + [[package]] name = "quick-xml" version = "0.37.5" @@ -1171,6 +1922,86 @@ version = "5.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.16", +] + +[[package]] +name = "rav1e" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd87ce80a7665b1cce111f8a16c1f3929f6547ce91ade6addf4ec86a8dda5ce9" +dependencies = [ + "arbitrary", + "arg_enum_proc_macro", + "arrayvec", + "av1-grain", + "bitstream-io", + "built", + "cfg-if", + "interpolate_name", + "itertools", + "libc", + "libfuzzer-sys", + "log", + "maybe-rayon", + "new_debug_unreachable", + "noop_proc_macro", + "num-derive", + "num-traits", + "once_cell", + "paste", + "profiling", + "rand", + "rand_chacha", + "simd_helpers", + "system-deps", + "thiserror 1.0.69", + "v_frame", + "wasm-bindgen", +] + +[[package]] +name = "ravif" +version = "0.11.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6a5f31fcf7500f9401fea858ea4ab5525c99f2322cfcee732c0e6c74208c0c6" +dependencies = [ + "avif-serialize", + "imgref", + "loop9", + "quick-error", + "rav1e", + "rayon", + "rgb", +] + [[package]] name = "raw-window-handle" version = "0.6.2" @@ -1189,6 +2020,26 @@ dependencies = [ "objc2-quartz-core 0.3.1", ] +[[package]] +name = "rayon" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + [[package]] name = "redox_syscall" version = "0.4.1" @@ -1236,6 +2087,12 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +[[package]] +name = "rgb" +version = "0.8.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57397d16646700483b67d2dd6511d79318f9d057fdbd21a4066aeac8b41d310a" + [[package]] name = "roxmltree" version = "0.14.1" @@ -1250,6 +2107,7 @@ name = "rust_vulkan_test" version = "0.1.0" dependencies = [ "anyhow", + "egui_winit_vulkano", "env_logger", "glam", "log", @@ -1351,6 +2209,15 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_spanned" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" +dependencies = [ + "serde", +] + [[package]] name = "shaderc" version = "0.8.3" @@ -1378,6 +2245,21 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + +[[package]] +name = "simd_helpers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95890f873bec569a0362c235787f3aca6e1e887302ba4840839bcc6459c42da6" +dependencies = [ + "quote", +] + [[package]] name = "slab" version = "0.4.9" @@ -1424,6 +2306,17 @@ dependencies = [ "xkeysym", ] +[[package]] +name = "smithay-clipboard" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc8216eec463674a0e90f29e0ae41a4db573ec5b56b1c6c1c71615d249b6d846" +dependencies = [ + "libc", + "smithay-client-toolkit", + "wayland-backend", +] + [[package]] name = "smol_str" version = "0.2.2" @@ -1433,6 +2326,12 @@ dependencies = [ "serde", ] +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + [[package]] name = "strict-num" version = "0.1.1" @@ -1450,6 +2349,36 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "system-deps" +version = "6.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" +dependencies = [ + "cfg-expr", + "heck 0.5.0", + "pkg-config", + "toml", + "version-compare", +] + +[[package]] +name = "target-lexicon" +version = "0.12.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" + [[package]] name = "thiserror" version = "1.0.69" @@ -1500,6 +2429,17 @@ dependencies = [ "once_cell", ] +[[package]] +name = "tiff" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba1310fcea54c6a9a4fd1aad794ecc02c31682f6bfbecdf460bf19533eed1e3e" +dependencies = [ + "flate2", + "jpeg-decoder", + "weezl", +] + [[package]] name = "tiny-skia" version = "0.11.4" @@ -1525,11 +2465,36 @@ dependencies = [ "strict-num", ] +[[package]] +name = "tinystr" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "toml" +version = "0.8.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05ae329d1f08c4d17a59bed7ff5b5a769d062e64a62d34a3261b219e62cd5aae" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + [[package]] name = "toml_datetime" version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3da5db5a963e24bc68be8b17b6fa82814bb22ee8660f192bb182771d498f09a3" +dependencies = [ + "serde", +] [[package]] name = "toml_edit" @@ -1538,6 +2503,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "310068873db2c5b3e7659d2cc35d21855dbafa50d1ce336397c666e3cb08137e" dependencies = [ "indexmap", + "serde", + "serde_spanned", "toml_datetime", "winnow", ] @@ -1576,12 +2543,46 @@ version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" +[[package]] +name = "url" +version = "2.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + [[package]] name = "utf8parse" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "v_frame" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f32aaa24bacd11e488aa9ba66369c7cd514885742c9fe08cfe85884db3e92b" +dependencies = [ + "aligned-vec", + "num-traits", + "wasm-bindgen", +] + +[[package]] +name = "version-compare" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b" + [[package]] name = "version_check" version = "0.9.5" @@ -1608,7 +2609,7 @@ dependencies = [ "crossbeam-queue", "foldhash", "half", - "heck", + "heck 0.4.1", "indexmap", "libloading", "nom", @@ -1648,7 +2649,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3bf501461be7cef2893c0e62c50945add9763cc482051d29053f6157089d5ea9" dependencies = [ "foldhash", - "heck", + "heck 0.4.1", "proc-macro2", "quote", "shaderc", @@ -1677,6 +2678,12 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + [[package]] name = "wasi" version = "0.14.2+wasi-0.2.4" @@ -1886,6 +2893,29 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "webbrowser" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5df295f8451142f1856b1bd86a606dfe9587d439bc036e319c827700dbd555e" +dependencies = [ + "core-foundation 0.10.0", + "home", + "jni", + "log", + "ndk-context", + "objc2 0.6.1", + "objc2-foundation 0.3.1", + "url", + "web-sys", +] + +[[package]] +name = "weezl" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a751b3277700db47d3e574514de2eced5e54dc8a5436a3bf7a0b248b2cee16f3" + [[package]] name = "winapi-util" version = "0.1.9" @@ -2115,7 +3145,7 @@ dependencies = [ "calloop", "cfg_aliases", "concurrent-queue", - "core-foundation", + "core-foundation 0.9.4", "core-graphics", "cursor-icon", "dpi", @@ -2124,7 +3154,7 @@ dependencies = [ "memmap2", "ndk", "objc2 0.5.2", - "objc2-app-kit", + "objc2-app-kit 0.2.2", "objc2-foundation 0.2.2", "objc2-ui-kit", "orbclient", @@ -2170,6 +3200,12 @@ dependencies = [ "bitflags 2.9.1", ] +[[package]] +name = "writeable" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" + [[package]] name = "x11-dl" version = "2.21.0" @@ -2239,6 +3275,30 @@ version = "0.13.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "66fee0b777b0f5ac1c69bb06d361268faafa61cd4682ae064a171c16c433e9e4" +[[package]] +name = "yoke" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + [[package]] name = "zerocopy" version = "0.8.25" @@ -2258,3 +3318,81 @@ dependencies = [ "quote", "syn", ] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerotrie" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zune-core" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a" + +[[package]] +name = "zune-inflate" +version = "0.2.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "zune-jpeg" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99a5bab8d7dedf81405c4bb1f2b83ea057643d9cb28778cea9eecddeedd2e028" +dependencies = [ + "zune-core", +] diff --git a/Cargo.toml b/Cargo.toml index a79f536..372b1a5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,11 +13,11 @@ winit = { version = "0.30", features = ["rwh_06"] } vulkano = "0.35" vulkano-shaders = "0.35" vulkano-util = "0.35" +egui_winit_vulkano = { version = "0.28" } # Math glam = { version = "0.30" } - # Log and tracing log = "0.4" env_logger = "0.11" diff --git a/src/render/app.rs b/src/render/app.rs index 69db339..ee33768 100644 --- a/src/render/app.rs +++ b/src/render/app.rs @@ -1,4 +1,7 @@ +use std::collections::HashMap; + use crate::render::scene::Scene; +use egui_winit_vulkano::{Gui, GuiConfig, egui}; use vulkano::command_buffer::{ AutoCommandBufferBuilder, CommandBufferUsage, RenderingAttachmentInfo, RenderingInfo, }; @@ -18,7 +21,9 @@ use super::vulkan_context::VulkanContext; pub struct App { vulkan_context: VulkanContext, vulkano_windows: VulkanoWindows, + gui: HashMap, scene: Option, + clear_color: [f32; 3], } impl From for App { @@ -26,14 +31,16 @@ impl From for App { Self { vulkan_context: VulkanContext::new(vulkano_context), vulkano_windows: VulkanoWindows::default(), + gui: HashMap::new(), scene: None, + clear_color: [0.0, 0.0, 0.0], } } } impl ApplicationHandler for App { fn resumed(&mut self, event_loop: &ActiveEventLoop) { - self.vulkano_windows.create_window( + let window_id = self.vulkano_windows.create_window( event_loop, self.vulkan_context.vulkano_context(), &WindowDescriptor { @@ -46,6 +53,21 @@ impl ApplicationHandler for App { |_| {}, ); + let gui = { + let renderer = self.vulkano_windows.get_renderer_mut(window_id).unwrap(); + Gui::new( + event_loop, + renderer.surface(), + renderer.graphics_queue(), + renderer.swapchain_format(), + GuiConfig { + is_overlay: true, + ..Default::default() + }, + ) + }; + self.gui.insert(window_id, gui); + self.scene = Some( Scene::load( &self.vulkan_context, @@ -55,14 +77,23 @@ impl ApplicationHandler for App { ); } - fn window_event(&mut self, event_loop: &ActiveEventLoop, _id: WindowId, event: WindowEvent) { + fn window_event(&mut self, event_loop: &ActiveEventLoop, id: WindowId, event: WindowEvent) { + let renderer = self.vulkano_windows.get_renderer_mut(id).unwrap(); + let gui = self.gui.get_mut(&id).unwrap(); + gui.update(&event); + match event { WindowEvent::CloseRequested => { log::debug!("The close button was pressed; stopping"); event_loop.exit(); } + WindowEvent::Resized(_) => { + renderer.resize(); + } + WindowEvent::ScaleFactorChanged { .. } => { + renderer.resize(); + } WindowEvent::RedrawRequested => { - let renderer = self.vulkano_windows.get_primary_renderer_mut().unwrap(); let acquire_future = renderer.acquire(None, |_| {}).unwrap(); let mut builder = AutoCommandBufferBuilder::primary( @@ -87,7 +118,7 @@ impl ApplicationHandler for App { color_attachments: vec![Some(RenderingAttachmentInfo { load_op: AttachmentLoadOp::Clear, store_op: AttachmentStoreOp::Store, - clear_value: Some([0.0, 0.0, 0.0, 1.0].into()), + clear_value: Some(self.clear_color.into()), ..RenderingAttachmentInfo::image_view( renderer.swapchain_image_view().clone(), ) @@ -109,7 +140,7 @@ impl ApplicationHandler for App { let command_buffer = builder.build().unwrap(); - let future = acquire_future + let render_future = acquire_future .then_execute( self.vulkan_context .vulkano_context() @@ -119,7 +150,22 @@ impl ApplicationHandler for App { ) .unwrap(); - renderer.present(future.boxed(), true); + gui.immediate_ui(|gui| { + let ctx = gui.context(); + + egui::Window::new("Informations") + .vscroll(true) + .show(&ctx, |ui| { + ui.label(format!("Format: {:?}", renderer.swapchain_format())); + ui.label(format!("Resolution: {:?}", renderer.resolution())); + ui.color_edit_button_rgb(&mut self.clear_color); + }); + }); + + let render_future = + gui.draw_on_image(render_future, renderer.swapchain_image_view()); + + renderer.present(render_future.boxed(), true); } _ => {} } From c4c691c4dde0ec5fa4059287bc58ba328e11a37d Mon Sep 17 00:00:00 2001 From: Florian RICHER Date: Sun, 25 May 2025 23:48:02 +0200 Subject: [PATCH 037/105] Begin implement input management --- src/render/app.rs | 10 ++++- src/render/input.rs | 89 +++++++++++++++++++++++++++++++++++++++++++++ src/render/mod.rs | 1 + 3 files changed, 99 insertions(+), 1 deletion(-) create mode 100644 src/render/input.rs diff --git a/src/render/app.rs b/src/render/app.rs index ee33768..bbd4a22 100644 --- a/src/render/app.rs +++ b/src/render/app.rs @@ -16,6 +16,7 @@ use winit::event::WindowEvent; use winit::event_loop::ActiveEventLoop; use winit::window::WindowId; +use super::input::InputState; use super::vulkan_context::VulkanContext; pub struct App { @@ -24,6 +25,7 @@ pub struct App { gui: HashMap, scene: Option, clear_color: [f32; 3], + input_state: InputState, } impl From for App { @@ -34,6 +36,7 @@ impl From for App { gui: HashMap::new(), scene: None, clear_color: [0.0, 0.0, 0.0], + input_state: InputState::default(), } } } @@ -80,7 +83,10 @@ impl ApplicationHandler for App { fn window_event(&mut self, event_loop: &ActiveEventLoop, id: WindowId, event: WindowEvent) { let renderer = self.vulkano_windows.get_renderer_mut(id).unwrap(); let gui = self.gui.get_mut(&id).unwrap(); - gui.update(&event); + + if !gui.update(&event) { + self.input_state.process_event(&event); + } match event { WindowEvent::CloseRequested => { @@ -94,6 +100,8 @@ impl ApplicationHandler for App { renderer.resize(); } WindowEvent::RedrawRequested => { + self.input_state.update(); + let acquire_future = renderer.acquire(None, |_| {}).unwrap(); let mut builder = AutoCommandBufferBuilder::primary( diff --git a/src/render/input.rs b/src/render/input.rs new file mode 100644 index 0000000..2b1075e --- /dev/null +++ b/src/render/input.rs @@ -0,0 +1,89 @@ +use std::collections::HashMap; + +use winit::{ + dpi::PhysicalPosition, + event::{ElementState, KeyEvent, WindowEvent}, + keyboard::PhysicalKey, +}; + +#[derive(Debug, Default)] +pub enum KeyState { + #[default] + Pressed, + Released, + Held, +} + +#[derive(Debug, Default)] +pub struct MouseState { + old_position: glam::Vec2, + pub position: glam::Vec2, + pub delta: glam::Vec2, +} + +#[derive(Debug, Default)] +pub struct InputState { + pub key_states: HashMap, + pub mouse_state: MouseState, +} + +impl InputState { + pub fn process_event(&mut self, event: &WindowEvent) { + match event { + WindowEvent::KeyboardInput { event, .. } => { + self.update_key_state(event.physical_key, event); + } + WindowEvent::CursorMoved { position, .. } => { + self.update_mouse_position(position); + } + _ => {} + } + } + + /// Updates deltas before running update + pub fn update(&mut self) { + self.mouse_state.delta = self.mouse_state.position - self.mouse_state.old_position; + self.mouse_state.old_position = self.mouse_state.position; + } + + pub fn get_key_state(&self, key: PhysicalKey) -> &KeyState { + self.key_states.get(&key).unwrap_or(&KeyState::Released) + } + + pub fn get_mouse_state(&self) -> &MouseState { + &self.mouse_state + } + + fn update_key_state(&mut self, key: PhysicalKey, event: &KeyEvent) { + let key_state = self.key_states.get(&event.physical_key); + let new_key_state = match key_state { + Some(key_state) => match event.state { + ElementState::Pressed => match key_state { + KeyState::Released => Some(KeyState::Pressed), + KeyState::Pressed => Some(KeyState::Held), + KeyState::Held => None, + }, + ElementState::Released => match key_state { + KeyState::Released => None, + _ => Some(KeyState::Released), + }, + }, + None => match event.state { + ElementState::Pressed => Some(KeyState::Pressed), + ElementState::Released => Some(KeyState::Released), + }, + }; + if let Some(new_key_state) = new_key_state { + log::trace!( + "New key state {:?} for key {:?}", + new_key_state, + event.physical_key + ); + self.key_states.insert(key, new_key_state); + } + } + + fn update_mouse_position(&mut self, position: &PhysicalPosition) { + self.mouse_state.position = glam::Vec2::new(position.x as f32, position.y as f32); + } +} diff --git a/src/render/mod.rs b/src/render/mod.rs index d93a2e0..164a07e 100644 --- a/src/render/mod.rs +++ b/src/render/mod.rs @@ -1,4 +1,5 @@ pub mod app; +pub mod input; pub mod pipelines; pub mod scene; pub mod vertex; From 1babc5bfebb97eaa7d85aa4f7ef005d4597d3814 Mon Sep 17 00:00:00 2001 From: Florian RICHER Date: Mon, 26 May 2025 00:04:46 +0200 Subject: [PATCH 038/105] Add debug in gui --- src/render/app.rs | 20 ++++++++++++++++++-- src/render/input.rs | 7 +------ 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/src/render/app.rs b/src/render/app.rs index bbd4a22..5515e97 100644 --- a/src/render/app.rs +++ b/src/render/app.rs @@ -14,9 +14,10 @@ use vulkano_util::window::{VulkanoWindows, WindowDescriptor}; use winit::application::ApplicationHandler; use winit::event::WindowEvent; use winit::event_loop::ActiveEventLoop; +use winit::keyboard::{KeyCode, PhysicalKey}; use winit::window::WindowId; -use super::input::InputState; +use super::input::{InputState, KeyState}; use super::vulkan_context::VulkanContext; pub struct App { @@ -164,9 +165,24 @@ impl ApplicationHandler for App { egui::Window::new("Informations") .vscroll(true) .show(&ctx, |ui| { - ui.label(format!("Format: {:?}", renderer.swapchain_format())); ui.label(format!("Resolution: {:?}", renderer.resolution())); ui.color_edit_button_rgb(&mut self.clear_color); + ui.label(format!( + "Mouse position: {:?}", + self.input_state.get_mouse_state().position + )); + ui.label(format!( + "Mouse delta: {:?}", + self.input_state.get_mouse_state().delta + )); + + for (key, state) in + self.input_state.key_states.iter().filter(|(_, state)| { + *state == &KeyState::Pressed || *state == &KeyState::Held + }) + { + ui.label(format!("{:?} State: {:?}", key, state)); + } }); }); diff --git a/src/render/input.rs b/src/render/input.rs index 2b1075e..04ca813 100644 --- a/src/render/input.rs +++ b/src/render/input.rs @@ -6,7 +6,7 @@ use winit::{ keyboard::PhysicalKey, }; -#[derive(Debug, Default)] +#[derive(Debug, Default, PartialEq)] pub enum KeyState { #[default] Pressed, @@ -74,11 +74,6 @@ impl InputState { }, }; if let Some(new_key_state) = new_key_state { - log::trace!( - "New key state {:?} for key {:?}", - new_key_state, - event.physical_key - ); self.key_states.insert(key, new_key_state); } } From e58c22b531d3d096cb5b2dcc22706bba73a92e0b Mon Sep 17 00:00:00 2001 From: Florian RICHER Date: Mon, 26 May 2025 13:29:38 +0200 Subject: [PATCH 039/105] remove held state in input. - Winit not return event if multiple key as repeat event --- src/render/app.rs | 14 +++++++------- src/render/input.rs | 27 +++++++++------------------ 2 files changed, 16 insertions(+), 25 deletions(-) diff --git a/src/render/app.rs b/src/render/app.rs index 5515e97..ee88731 100644 --- a/src/render/app.rs +++ b/src/render/app.rs @@ -12,12 +12,11 @@ use vulkano::sync::GpuFuture; use vulkano_util::context::VulkanoContext; use vulkano_util::window::{VulkanoWindows, WindowDescriptor}; use winit::application::ApplicationHandler; -use winit::event::WindowEvent; +use winit::event::{ElementState, WindowEvent}; use winit::event_loop::ActiveEventLoop; -use winit::keyboard::{KeyCode, PhysicalKey}; use winit::window::WindowId; -use super::input::{InputState, KeyState}; +use super::input::InputState; use super::vulkan_context::VulkanContext; pub struct App { @@ -176,10 +175,11 @@ impl ApplicationHandler for App { self.input_state.get_mouse_state().delta )); - for (key, state) in - self.input_state.key_states.iter().filter(|(_, state)| { - *state == &KeyState::Pressed || *state == &KeyState::Held - }) + for (key, state) in self + .input_state + .key_states + .iter() + .filter(|(_, state)| *state == &ElementState::Pressed) { ui.label(format!("{:?} State: {:?}", key, state)); } diff --git a/src/render/input.rs b/src/render/input.rs index 04ca813..59e8398 100644 --- a/src/render/input.rs +++ b/src/render/input.rs @@ -6,14 +6,6 @@ use winit::{ keyboard::PhysicalKey, }; -#[derive(Debug, Default, PartialEq)] -pub enum KeyState { - #[default] - Pressed, - Released, - Held, -} - #[derive(Debug, Default)] pub struct MouseState { old_position: glam::Vec2, @@ -23,7 +15,7 @@ pub struct MouseState { #[derive(Debug, Default)] pub struct InputState { - pub key_states: HashMap, + pub key_states: HashMap, pub mouse_state: MouseState, } @@ -46,8 +38,8 @@ impl InputState { self.mouse_state.old_position = self.mouse_state.position; } - pub fn get_key_state(&self, key: PhysicalKey) -> &KeyState { - self.key_states.get(&key).unwrap_or(&KeyState::Released) + pub fn get_key_state(&self, key: PhysicalKey) -> &ElementState { + self.key_states.get(&key).unwrap_or(&ElementState::Released) } pub fn get_mouse_state(&self) -> &MouseState { @@ -59,18 +51,17 @@ impl InputState { let new_key_state = match key_state { Some(key_state) => match event.state { ElementState::Pressed => match key_state { - KeyState::Released => Some(KeyState::Pressed), - KeyState::Pressed => Some(KeyState::Held), - KeyState::Held => None, + ElementState::Released => Some(ElementState::Pressed), + ElementState::Pressed => None, }, ElementState::Released => match key_state { - KeyState::Released => None, - _ => Some(KeyState::Released), + ElementState::Released => None, + ElementState::Pressed => Some(ElementState::Released), }, }, None => match event.state { - ElementState::Pressed => Some(KeyState::Pressed), - ElementState::Released => Some(KeyState::Released), + ElementState::Pressed => Some(ElementState::Pressed), + ElementState::Released => Some(ElementState::Released), }, }; if let Some(new_key_state) = new_key_state { From 5b74eef561bb13edad6b9c3436dbea938c5058b4 Mon Sep 17 00:00:00 2001 From: Florian RICHER Date: Mon, 26 May 2025 16:46:26 +0200 Subject: [PATCH 040/105] Push break job work [not work] --- src/{render => core}/input.rs | 0 src/core/mod.rs | 4 ++ src/{ => core}/render/app.rs | 21 +++++----- src/{ => core}/render/mod.rs | 2 - src/{ => core}/render/pipelines/mod.rs | 0 .../render/pipelines/triangle_pipeline.rs | 2 +- src/{ => core}/render/vertex.rs | 0 src/{ => core}/render/vulkan_context.rs | 0 src/core/scene.rs | 29 +++++++++++++ src/core/timer.rs | 30 ++++++++++++++ src/{render/scene.rs => game/main_scene.rs} | 41 ++++++++++++++++++- src/game/mod.rs | 1 + src/main.rs | 6 ++- 13 files changed, 118 insertions(+), 18 deletions(-) rename src/{render => core}/input.rs (100%) create mode 100644 src/core/mod.rs rename src/{ => core}/render/app.rs (95%) rename src/{ => core}/render/mod.rs (70%) rename src/{ => core}/render/pipelines/mod.rs (100%) rename src/{ => core}/render/pipelines/triangle_pipeline.rs (98%) rename src/{ => core}/render/vertex.rs (100%) rename src/{ => core}/render/vulkan_context.rs (100%) create mode 100644 src/core/scene.rs create mode 100644 src/core/timer.rs rename src/{render/scene.rs => game/main_scene.rs} (86%) create mode 100644 src/game/mod.rs diff --git a/src/render/input.rs b/src/core/input.rs similarity index 100% rename from src/render/input.rs rename to src/core/input.rs diff --git a/src/core/mod.rs b/src/core/mod.rs new file mode 100644 index 0000000..ddaf320 --- /dev/null +++ b/src/core/mod.rs @@ -0,0 +1,4 @@ +pub mod input; +pub mod render; +pub mod scene; +pub mod timer; diff --git a/src/render/app.rs b/src/core/render/app.rs similarity index 95% rename from src/render/app.rs rename to src/core/render/app.rs index ee88731..9553a5a 100644 --- a/src/render/app.rs +++ b/src/core/render/app.rs @@ -1,6 +1,9 @@ use std::collections::HashMap; -use crate::render::scene::Scene; +use crate::core::input::InputState; +use crate::core::scene::SceneManager; +use crate::core::timer::Timer; +use crate::game::main_scene::MainScene; use egui_winit_vulkano::{Gui, GuiConfig, egui}; use vulkano::command_buffer::{ AutoCommandBufferBuilder, CommandBufferUsage, RenderingAttachmentInfo, RenderingInfo, @@ -16,16 +19,16 @@ use winit::event::{ElementState, WindowEvent}; use winit::event_loop::ActiveEventLoop; use winit::window::WindowId; -use super::input::InputState; use super::vulkan_context::VulkanContext; pub struct App { vulkan_context: VulkanContext, vulkano_windows: VulkanoWindows, gui: HashMap, - scene: Option, clear_color: [f32; 3], input_state: InputState, + scene_manager: SceneManager, + timer: Timer, } impl From for App { @@ -34,9 +37,10 @@ impl From for App { vulkan_context: VulkanContext::new(vulkano_context), vulkano_windows: VulkanoWindows::default(), gui: HashMap::new(), - scene: None, clear_color: [0.0, 0.0, 0.0], input_state: InputState::default(), + scene_manager: SceneManager::new(), + timer: Timer::new(), } } } @@ -71,13 +75,8 @@ impl ApplicationHandler for App { }; self.gui.insert(window_id, gui); - self.scene = Some( - Scene::load( - &self.vulkan_context, - self.vulkano_windows.get_primary_renderer_mut().unwrap(), - ) - .unwrap(), - ); + self.scene_manager + .load_scene(Box::new(MainScene::default())); } fn window_event(&mut self, event_loop: &ActiveEventLoop, id: WindowId, event: WindowEvent) { diff --git a/src/render/mod.rs b/src/core/render/mod.rs similarity index 70% rename from src/render/mod.rs rename to src/core/render/mod.rs index 164a07e..d98c419 100644 --- a/src/render/mod.rs +++ b/src/core/render/mod.rs @@ -1,6 +1,4 @@ pub mod app; -pub mod input; pub mod pipelines; -pub mod scene; pub mod vertex; pub mod vulkan_context; diff --git a/src/render/pipelines/mod.rs b/src/core/render/pipelines/mod.rs similarity index 100% rename from src/render/pipelines/mod.rs rename to src/core/render/pipelines/mod.rs diff --git a/src/render/pipelines/triangle_pipeline.rs b/src/core/render/pipelines/triangle_pipeline.rs similarity index 98% rename from src/render/pipelines/triangle_pipeline.rs rename to src/core/render/pipelines/triangle_pipeline.rs index 9fb3629..62bd8b0 100644 --- a/src/render/pipelines/triangle_pipeline.rs +++ b/src/core/render/pipelines/triangle_pipeline.rs @@ -20,7 +20,7 @@ use vulkano::pipeline::{ }; use vulkano::shader::{EntryPoint, ShaderStages}; -use crate::render::vertex::Vertex2D; +use crate::core::render::vertex::Vertex2D; pub mod shaders { pub mod vs { diff --git a/src/render/vertex.rs b/src/core/render/vertex.rs similarity index 100% rename from src/render/vertex.rs rename to src/core/render/vertex.rs diff --git a/src/render/vulkan_context.rs b/src/core/render/vulkan_context.rs similarity index 100% rename from src/render/vulkan_context.rs rename to src/core/render/vulkan_context.rs diff --git a/src/core/scene.rs b/src/core/scene.rs new file mode 100644 index 0000000..2286820 --- /dev/null +++ b/src/core/scene.rs @@ -0,0 +1,29 @@ +use vulkano_util::renderer::VulkanoWindowRenderer; + +use super::{input::InputState, render::vulkan_context::VulkanContext, timer::Timer}; + +pub trait Scene { + fn load(&mut self, app: &mut App); + fn update(&mut self, app: &mut App); + fn render(&self); + fn unload(&mut self); +} + +pub struct SceneManager { + current_scene: Option>, +} + +impl SceneManager { + pub fn new() -> Self { + Self { + current_scene: None, + } + } + + pub fn load_scene(&mut self, scene: Box) { + if let Some(current_scene) = self.current_scene.as_mut() { + current_scene.unload(); + } + self.current_scene = Some(scene); + } +} diff --git a/src/core/timer.rs b/src/core/timer.rs new file mode 100644 index 0000000..eacf423 --- /dev/null +++ b/src/core/timer.rs @@ -0,0 +1,30 @@ +pub struct Timer { + start_time: std::time::Instant, + last_time: std::time::Instant, + current_time: std::time::Instant, + delta_time: f32, +} + +impl Timer { + pub fn new() -> Self { + Self { + start_time: std::time::Instant::now(), + last_time: std::time::Instant::now(), + current_time: std::time::Instant::now(), + delta_time: 0.0, + } + } + + pub fn update(&mut self) { + self.current_time = std::time::Instant::now(); + self.delta_time = self + .current_time + .duration_since(self.last_time) + .as_secs_f32(); + self.last_time = self.current_time; + } + + pub fn get_delta_time(&self) -> f32 { + self.delta_time + } +} diff --git a/src/render/scene.rs b/src/game/main_scene.rs similarity index 86% rename from src/render/scene.rs rename to src/game/main_scene.rs index 36ba182..4135c38 100644 --- a/src/render/scene.rs +++ b/src/game/main_scene.rs @@ -1,3 +1,4 @@ +use crate::core::scene::Scene; use crate::render::pipelines::triangle_pipeline::shaders::vs; use glam::{Mat3, Mat4, Vec3}; use std::error::Error; @@ -70,14 +71,50 @@ const VERTICES: [Vertex2D; 12] = [ }, ]; -pub struct Scene { +pub struct MainSceneState { pipeline: Arc, vertex_buffer: Subbuffer<[Vertex2D]>, rotation_start: Instant, } -impl Scene { +#[derive(Default)] +pub struct MainScene { + state: Option, +} + +impl Scene for MainScene { + fn load(&mut self, app: &mut App) { + let pipeline = create_triangle_pipeline( + app.vulkan_context.vulkano_context().device(), + app.vulkano_windows.swapchain_format(), + )?; + let vertex_buffer = Vertex2D::create_buffer( + Vec::from_iter(VERTICES), + vulkano_context.vulkano_context().memory_allocator(), + )?; + + self.state = Some(MainSceneState { + pipeline, + vertex_buffer, + rotation_start: Instant::now(), + }) + } + + fn update(&mut self, app: &mut App) { + todo!() + } + + fn render(&self) { + todo!() + } + + fn unload(&mut self) { + todo!() + } +} + +impl MainScene { pub fn load( vulkano_context: &VulkanContext, vulkano_window_renderer: &VulkanoWindowRenderer, diff --git a/src/game/mod.rs b/src/game/mod.rs new file mode 100644 index 0000000..53eb070 --- /dev/null +++ b/src/game/mod.rs @@ -0,0 +1 @@ +pub mod main_scene; diff --git a/src/main.rs b/src/main.rs index 4ef9dfb..52ba1f6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,7 +2,9 @@ use vulkano::device::{DeviceExtensions, DeviceFeatures}; use vulkano_util::context::{VulkanoConfig, VulkanoContext}; use winit::event_loop::{ControlFlow, EventLoop}; -mod render; +mod core; +mod game; + fn main() { env_logger::init(); @@ -28,7 +30,7 @@ fn main() { let event_loop = EventLoop::new().unwrap(); event_loop.set_control_flow(ControlFlow::Poll); - let mut app = render::app::App::from(vulkano_context); + let mut app = core::render::app::App::from(vulkano_context); match event_loop.run_app(&mut app) { Ok(_) => {} From 7401a9b5f3ae0375f53a45b89a663cb66222af25 Mon Sep 17 00:00:00 2001 From: Florian RICHER Date: Mon, 26 May 2025 19:55:34 +0200 Subject: [PATCH 041/105] First scene refactor working --- src/core/render/app.rs | 98 +++++++++++++------- src/core/render/mod.rs | 1 + src/core/render/render_context.rs | 70 +++++++++++++++ src/core/scene.rs | 41 +++++++-- src/core/timer.rs | 6 +- src/game/main_scene.rs | 145 +++++++++++++++--------------- 6 files changed, 251 insertions(+), 110 deletions(-) create mode 100644 src/core/render/render_context.rs diff --git a/src/core/render/app.rs b/src/core/render/app.rs index 9553a5a..6e08f72 100644 --- a/src/core/render/app.rs +++ b/src/core/render/app.rs @@ -1,4 +1,5 @@ use std::collections::HashMap; +use std::sync::Arc; use crate::core::input::InputState; use crate::core::scene::SceneManager; @@ -13,17 +14,19 @@ use vulkano::render_pass::{AttachmentLoadOp, AttachmentStoreOp}; use vulkano::swapchain::PresentMode; use vulkano::sync::GpuFuture; use vulkano_util::context::VulkanoContext; +use vulkano_util::renderer::VulkanoWindowRenderer; use vulkano_util::window::{VulkanoWindows, WindowDescriptor}; use winit::application::ApplicationHandler; use winit::event::{ElementState, WindowEvent}; use winit::event_loop::ActiveEventLoop; use winit::window::WindowId; +use super::render_context::RenderContext; use super::vulkan_context::VulkanContext; pub struct App { - vulkan_context: VulkanContext, - vulkano_windows: VulkanoWindows, + vulkan_context: Arc, + vulkano_windows: Arc, gui: HashMap, clear_color: [f32; 3], input_state: InputState, @@ -31,11 +34,29 @@ pub struct App { timer: Timer, } +impl From<(&VulkanContext, &VulkanoWindowRenderer)> for RenderContext { + fn from((vulkan_context, renderer): (&VulkanContext, &VulkanoWindowRenderer)) -> Self { + RenderContext { + window_size: renderer.resolution(), + aspect_ratio: renderer.aspect_ratio(), + instance: vulkan_context.vulkano_context().instance().clone(), + device: vulkan_context.vulkano_context().device().clone(), + graphics_queue: vulkan_context.vulkano_context().graphics_queue().clone(), + compute_queue: vulkan_context.vulkano_context().compute_queue().clone(), + transfer_queue: vulkan_context.vulkano_context().transfer_queue().cloned(), + memory_allocator: vulkan_context.vulkano_context().memory_allocator().clone(), + command_buffer_allocator: vulkan_context.command_buffer_allocator().clone(), + descriptor_set_allocator: vulkan_context.descriptor_set_allocator().clone(), + swapchain_format: renderer.swapchain_format(), + } + } +} + impl From for App { fn from(vulkano_context: VulkanoContext) -> Self { Self { - vulkan_context: VulkanContext::new(vulkano_context), - vulkano_windows: VulkanoWindows::default(), + vulkan_context: Arc::new(VulkanContext::new(vulkano_context)), + vulkano_windows: Arc::new(VulkanoWindows::default()), gui: HashMap::new(), clear_color: [0.0, 0.0, 0.0], input_state: InputState::default(), @@ -47,41 +68,47 @@ impl From for App { impl ApplicationHandler for App { fn resumed(&mut self, event_loop: &ActiveEventLoop) { - let window_id = self.vulkano_windows.create_window( - event_loop, - self.vulkan_context.vulkano_context(), - &WindowDescriptor { - title: "Rust ASH Test".to_string(), - width: 800.0, - height: 600.0, - present_mode: PresentMode::Fifo, - ..Default::default() - }, - |_| {}, - ); - - let gui = { - let renderer = self.vulkano_windows.get_renderer_mut(window_id).unwrap(); - Gui::new( + if let Some(vulkano_windows) = Arc::get_mut(&mut self.vulkano_windows) { + let window_id = vulkano_windows.create_window( event_loop, - renderer.surface(), - renderer.graphics_queue(), - renderer.swapchain_format(), - GuiConfig { - is_overlay: true, + self.vulkan_context.vulkano_context(), + &WindowDescriptor { + title: "Rust ASH Test".to_string(), + width: 800.0, + height: 600.0, + present_mode: PresentMode::Fifo, ..Default::default() }, - ) - }; - self.gui.insert(window_id, gui); + |_| {}, + ); + + let gui = { + let renderer = vulkano_windows.get_renderer_mut(window_id).unwrap(); + Gui::new( + event_loop, + renderer.surface(), + renderer.graphics_queue(), + renderer.swapchain_format(), + GuiConfig { + is_overlay: true, + ..Default::default() + }, + ) + }; + self.gui.insert(window_id, gui); + } self.scene_manager .load_scene(Box::new(MainScene::default())); } fn window_event(&mut self, event_loop: &ActiveEventLoop, id: WindowId, event: WindowEvent) { - let renderer = self.vulkano_windows.get_renderer_mut(id).unwrap(); + let renderer = Arc::get_mut(&mut self.vulkano_windows) + .unwrap() + .get_renderer_mut(id) + .unwrap(); let gui = self.gui.get_mut(&id).unwrap(); + let render_context = RenderContext::from((self.vulkan_context.as_ref(), &*renderer)); if !gui.update(&event) { self.input_state.process_event(&event); @@ -100,6 +127,11 @@ impl ApplicationHandler for App { } WindowEvent::RedrawRequested => { self.input_state.update(); + self.timer.update(); + self.scene_manager.load_scene_if_not_loaded(&render_context); + if let Some(scene) = self.scene_manager.current_scene_mut() { + scene.update(&render_context, &self.input_state, &self.timer); + } let acquire_future = renderer.acquire(None, |_| {}).unwrap(); @@ -137,10 +169,8 @@ impl ApplicationHandler for App { .unwrap(); } - if let Some(scene) = self.scene.as_ref() { - scene - .render(&self.vulkan_context, renderer, &mut builder) - .unwrap(); + if let Some(scene) = self.scene_manager.current_scene() { + scene.render(&render_context, &mut builder); } builder.end_rendering().unwrap(); @@ -174,6 +204,8 @@ impl ApplicationHandler for App { self.input_state.get_mouse_state().delta )); + ui.label(format!("Delta time: {:?}", self.timer.delta_time())); + for (key, state) in self .input_state .key_states diff --git a/src/core/render/mod.rs b/src/core/render/mod.rs index d98c419..a86d640 100644 --- a/src/core/render/mod.rs +++ b/src/core/render/mod.rs @@ -1,4 +1,5 @@ pub mod app; pub mod pipelines; +pub mod render_context; pub mod vertex; pub mod vulkan_context; diff --git a/src/core/render/render_context.rs b/src/core/render/render_context.rs new file mode 100644 index 0000000..d1d2be4 --- /dev/null +++ b/src/core/render/render_context.rs @@ -0,0 +1,70 @@ +use std::sync::Arc; + +use vulkano::{ + command_buffer::allocator::StandardCommandBufferAllocator, + descriptor_set::allocator::StandardDescriptorSetAllocator, + device::{Device, Queue}, + format::Format, + instance::Instance, + memory::allocator::StandardMemoryAllocator, +}; + +pub struct RenderContext { + pub(super) instance: Arc, + pub(super) device: Arc, + pub(super) graphics_queue: Arc, + pub(super) compute_queue: Arc, + pub(super) transfer_queue: Option>, + pub(super) memory_allocator: Arc, + pub(super) command_buffer_allocator: Arc, + pub(super) descriptor_set_allocator: Arc, + pub(super) window_size: [f32; 2], + pub(super) aspect_ratio: f32, + pub(super) swapchain_format: Format, +} + +impl RenderContext { + pub fn instance(&self) -> &Arc { + &self.instance + } + + pub fn device(&self) -> &Arc { + &self.device + } + + pub fn graphics_queue(&self) -> &Arc { + &self.graphics_queue + } + + pub fn compute_queue(&self) -> &Arc { + &self.compute_queue + } + + pub fn transfer_queue(&self) -> Option<&Arc> { + self.transfer_queue.as_ref() + } + + pub fn memory_allocator(&self) -> &Arc { + &self.memory_allocator + } + + pub fn command_buffer_allocator(&self) -> &Arc { + &self.command_buffer_allocator + } + + pub fn descriptor_set_allocator(&self) -> &Arc { + &self.descriptor_set_allocator + } + + pub fn window_size(&self) -> &[f32; 2] { + &self.window_size + } + + pub fn aspect_ratio(&self) -> f32 { + self.aspect_ratio + } + + pub fn swapchain_format(&self) -> Format { + self.swapchain_format + } +} diff --git a/src/core/scene.rs b/src/core/scene.rs index 2286820..e8040f8 100644 --- a/src/core/scene.rs +++ b/src/core/scene.rs @@ -1,11 +1,16 @@ -use vulkano_util::renderer::VulkanoWindowRenderer; +use vulkano::command_buffer::{AutoCommandBufferBuilder, PrimaryAutoCommandBuffer}; -use super::{input::InputState, render::vulkan_context::VulkanContext, timer::Timer}; +use super::{input::InputState, render::render_context::RenderContext, timer::Timer}; pub trait Scene { - fn load(&mut self, app: &mut App); - fn update(&mut self, app: &mut App); - fn render(&self); + fn loaded(&self) -> bool; + fn load(&mut self, render_context: &RenderContext); + fn update(&mut self, render_context: &RenderContext, input_state: &InputState, timer: &Timer); + fn render( + &self, + render_context: &RenderContext, + builder: &mut AutoCommandBufferBuilder, + ); fn unload(&mut self); } @@ -20,10 +25,36 @@ impl SceneManager { } } + pub fn load_scene_if_not_loaded(&mut self, render_context: &RenderContext) { + if let Some(current_scene) = self.current_scene.as_mut() { + if !current_scene.loaded() { + current_scene.load(render_context); + } + } + } + pub fn load_scene(&mut self, scene: Box) { if let Some(current_scene) = self.current_scene.as_mut() { current_scene.unload(); } self.current_scene = Some(scene); } + + pub fn current_scene(&self) -> Option<&Box> { + if let Some(current_scene) = self.current_scene.as_ref() { + if current_scene.loaded() { + return Some(current_scene); + } + } + None + } + + pub fn current_scene_mut(&mut self) -> Option<&mut Box> { + if let Some(current_scene) = self.current_scene.as_mut() { + if current_scene.loaded() { + return Some(current_scene); + } + } + None + } } diff --git a/src/core/timer.rs b/src/core/timer.rs index eacf423..3245a4c 100644 --- a/src/core/timer.rs +++ b/src/core/timer.rs @@ -24,7 +24,11 @@ impl Timer { self.last_time = self.current_time; } - pub fn get_delta_time(&self) -> f32 { + pub fn delta_time(&self) -> f32 { self.delta_time } + + pub fn start_time(&self) -> std::time::Instant { + self.start_time + } } diff --git a/src/game/main_scene.rs b/src/game/main_scene.rs index 4135c38..c0e13be 100644 --- a/src/game/main_scene.rs +++ b/src/game/main_scene.rs @@ -1,5 +1,8 @@ +use crate::core::input::InputState; +use crate::core::render::pipelines::triangle_pipeline::shaders::vs; +use crate::core::render::render_context::RenderContext; use crate::core::scene::Scene; -use crate::render::pipelines::triangle_pipeline::shaders::vs; +use crate::core::timer::Timer; use glam::{Mat3, Mat4, Vec3}; use std::error::Error; use std::sync::Arc; @@ -10,11 +13,13 @@ use vulkano::descriptor_set::{DescriptorSet, WriteDescriptorSet}; use vulkano::memory::allocator::{AllocationCreateInfo, MemoryTypeFilter, StandardMemoryAllocator}; use vulkano::pipeline::{GraphicsPipeline, Pipeline, PipelineBindPoint}; use vulkano_util::renderer::VulkanoWindowRenderer; +use winit::event::ElementState; +use winit::keyboard::{KeyCode, PhysicalKey}; -use crate::render::pipelines::triangle_pipeline::create_triangle_pipeline; -use crate::render::vertex::Vertex2D; +use crate::core::render::pipelines::triangle_pipeline::create_triangle_pipeline; +use crate::core::render::vertex::Vertex2D; -use super::vulkan_context::VulkanContext; +use crate::core::render::vulkan_context::VulkanContext; const VERTICES: [Vertex2D; 12] = [ // Triangle en haut à gauche @@ -74,8 +79,8 @@ const VERTICES: [Vertex2D; 12] = [ pub struct MainSceneState { pipeline: Arc, vertex_buffer: Subbuffer<[Vertex2D]>, - - rotation_start: Instant, + uniform_buffer: Subbuffer, + rotation: f32, } #[derive(Default)] @@ -84,103 +89,101 @@ pub struct MainScene { } impl Scene for MainScene { - fn load(&mut self, app: &mut App) { - let pipeline = create_triangle_pipeline( - app.vulkan_context.vulkano_context().device(), - app.vulkano_windows.swapchain_format(), - )?; - let vertex_buffer = Vertex2D::create_buffer( - Vec::from_iter(VERTICES), - vulkano_context.vulkano_context().memory_allocator(), - )?; + fn loaded(&self) -> bool { + self.state.is_some() + } + + fn load(&mut self, render_context: &RenderContext) { + let pipeline = + create_triangle_pipeline(render_context.device(), render_context.swapchain_format()) + .unwrap(); + let vertex_buffer = + Vertex2D::create_buffer(Vec::from_iter(VERTICES), render_context.memory_allocator()) + .unwrap(); + + let uniform_buffer = MainScene::get_uniform_buffer( + 0.0, + render_context.memory_allocator(), + render_context.aspect_ratio(), + ); self.state = Some(MainSceneState { pipeline, vertex_buffer, - rotation_start: Instant::now(), + uniform_buffer, + rotation: 0.0, }) } - fn update(&mut self, app: &mut App) { - todo!() + fn update(&mut self, render_context: &RenderContext, input_state: &InputState, timer: &Timer) { + let state = self.state.as_mut().unwrap(); + let delta_rotation = if input_state.get_key_state(PhysicalKey::Code(KeyCode::KeyA)) + == &ElementState::Pressed + { + timer.delta_time() * 5.0 + } else if input_state.get_key_state(PhysicalKey::Code(KeyCode::KeyD)) + == &ElementState::Pressed + { + timer.delta_time() * -5.0 + } else { + timer.delta_time() * 0.0 + }; + state.rotation += delta_rotation; + state.uniform_buffer = MainScene::get_uniform_buffer( + state.rotation, + render_context.memory_allocator(), + render_context.aspect_ratio(), + ); } - fn render(&self) { - todo!() - } - - fn unload(&mut self) { - todo!() - } -} - -impl MainScene { - pub fn load( - vulkano_context: &VulkanContext, - vulkano_window_renderer: &VulkanoWindowRenderer, - ) -> Result> { - let pipeline = create_triangle_pipeline( - vulkano_context.vulkano_context().device(), - vulkano_window_renderer.swapchain_format(), - )?; - let vertex_buffer = Vertex2D::create_buffer( - Vec::from_iter(VERTICES), - vulkano_context.vulkano_context().memory_allocator(), - )?; - - Ok(Scene { - pipeline, - vertex_buffer, - rotation_start: Instant::now(), - }) - } - - pub fn render( + fn render( &self, - vulkan_context: &VulkanContext, - vulkano_window_renderer: &VulkanoWindowRenderer, + render_context: &RenderContext, builder: &mut AutoCommandBufferBuilder, - ) -> Result<(), Box> { - let vertex_count = self.vertex_buffer.len() as u32; + ) { + let vertex_count = self.state.as_ref().unwrap().vertex_buffer.len() as u32; let instance_count = vertex_count / 3; - let uniform_buffer = self.get_uniform_buffer( - vulkan_context.vulkano_context().memory_allocator(), - vulkano_window_renderer.aspect_ratio(), - ); - let layout = &self.pipeline.layout().set_layouts()[0]; + let layout = &self.state.as_ref().unwrap().pipeline.layout().set_layouts()[0]; let descriptor_set = DescriptorSet::new( - vulkan_context.descriptor_set_allocator().clone(), + render_context.descriptor_set_allocator().clone(), layout.clone(), - [WriteDescriptorSet::buffer(0, uniform_buffer)], + [WriteDescriptorSet::buffer( + 0, + self.state.as_ref().unwrap().uniform_buffer.clone(), + )], [], ) .unwrap(); unsafe { builder - .bind_pipeline_graphics(self.pipeline.clone())? + .bind_pipeline_graphics(self.state.as_ref().unwrap().pipeline.clone()) + .unwrap() .bind_descriptor_sets( PipelineBindPoint::Graphics, - self.pipeline.layout().clone(), + self.state.as_ref().unwrap().pipeline.layout().clone(), 0, descriptor_set, - )? - .bind_vertex_buffers(0, self.vertex_buffer.clone())? - .draw(vertex_count, instance_count, 0, 0)?; + ) + .unwrap() + .bind_vertex_buffers(0, self.state.as_ref().unwrap().vertex_buffer.clone()) + .unwrap() + .draw(vertex_count, instance_count, 0, 0) + .unwrap(); } - - Ok(()) } + fn unload(&mut self) {} +} + +impl MainScene { fn get_uniform_buffer( - &self, + rotation: f32, memory_allocator: &Arc, aspect_ratio: f32, ) -> Subbuffer { - let elapsed = self.rotation_start.elapsed(); - let rotation = elapsed.as_secs() as f64 + elapsed.subsec_nanos() as f64 / 1_000_000_000.0; - let rotation = Mat3::from_rotation_y(rotation as f32); + let rotation = Mat3::from_rotation_y(rotation); // NOTE: This teapot was meant for OpenGL where the origin is at the lower left // instead the origin is at the upper left in Vulkan, so we reverse the Y axis. From 1976a8b53eb0c40f44dd9a593c40b4dd2018d1b4 Mon Sep 17 00:00:00 2001 From: Florian RICHER Date: Mon, 26 May 2025 22:53:32 +0200 Subject: [PATCH 042/105] Refactor camera code --- res/shaders/vertex.vert | 19 ++- src/core/render/mod.rs | 2 +- .../render/pipelines/triangle_pipeline.rs | 4 +- src/core/render/primitives/camera.rs | 48 ++++++ src/core/render/primitives/mod.rs | 4 + src/core/render/primitives/mvp.rs | 45 ++++++ src/core/render/primitives/transform.rs | 37 +++++ src/core/render/{ => primitives}/vertex.rs | 0 src/game/main_scene.rs | 139 ++++++++---------- 9 files changed, 219 insertions(+), 79 deletions(-) create mode 100644 src/core/render/primitives/camera.rs create mode 100644 src/core/render/primitives/mod.rs create mode 100644 src/core/render/primitives/mvp.rs create mode 100644 src/core/render/primitives/transform.rs rename src/core/render/{ => primitives}/vertex.rs (100%) diff --git a/res/shaders/vertex.vert b/res/shaders/vertex.vert index 65b0acf..bb1c261 100644 --- a/res/shaders/vertex.vert +++ b/res/shaders/vertex.vert @@ -1 +1,18 @@ -#version 450 layout (location = 0) in vec2 position; layout (location = 1) in vec3 color; layout (location = 0) out vec3 fragColor; layout (set = 0, binding = 0) uniform MVPData { mat4 world; mat4 view; mat4 projection; } uniforms; void main() { mat4 worldview = uniforms.view * uniforms.world; gl_Position = uniforms.projection * worldview * vec4(position, 0.0, 1.0); fragColor = color; } \ No newline at end of file +#version 450 + +layout (location = 0) in vec2 position; +layout (location = 1) in vec3 color; + +layout (location = 0) out vec3 fragColor; + +layout (set = 0, binding = 0) uniform MVP { + mat4 world; + mat4 view; + mat4 projection; +} uniforms; + +void main() { + mat4 worldview = uniforms.view * uniforms.world; + gl_Position = uniforms.projection * worldview * vec4(position, 0.0, 1.0); + fragColor = color; +} diff --git a/src/core/render/mod.rs b/src/core/render/mod.rs index a86d640..a074771 100644 --- a/src/core/render/mod.rs +++ b/src/core/render/mod.rs @@ -1,5 +1,5 @@ pub mod app; pub mod pipelines; +pub mod primitives; pub mod render_context; -pub mod vertex; pub mod vulkan_context; diff --git a/src/core/render/pipelines/triangle_pipeline.rs b/src/core/render/pipelines/triangle_pipeline.rs index 62bd8b0..b7ffce4 100644 --- a/src/core/render/pipelines/triangle_pipeline.rs +++ b/src/core/render/pipelines/triangle_pipeline.rs @@ -20,13 +20,14 @@ use vulkano::pipeline::{ }; use vulkano::shader::{EntryPoint, ShaderStages}; -use crate::core::render::vertex::Vertex2D; +use crate::core::render::primitives::vertex::Vertex2D; pub mod shaders { pub mod vs { vulkano_shaders::shader! { ty: "vertex", path: r"res/shaders/vertex.vert", + generate_structs: false, } } @@ -34,6 +35,7 @@ pub mod shaders { vulkano_shaders::shader! { ty: "fragment", path: r"res/shaders/vertex.frag", + generate_structs: false, } } } diff --git a/src/core/render/primitives/camera.rs b/src/core/render/primitives/camera.rs new file mode 100644 index 0000000..efd61e5 --- /dev/null +++ b/src/core/render/primitives/camera.rs @@ -0,0 +1,48 @@ +use std::sync::Arc; + +use glam::Mat4; +use vulkano::{ + Validated, + buffer::{AllocateBufferError, Subbuffer}, + memory::allocator::StandardMemoryAllocator, +}; + +use super::{mvp::MVP, transform::Transform}; + +#[derive(Default)] +pub struct Camera { + view: Mat4, + projection: Mat4, + + transform: Transform, +} + +impl Camera { + pub fn new(view: Mat4, projection: Mat4) -> Self { + Self { + view, + projection, + transform: Transform::default(), + } + } + + pub fn get_transform(&self) -> &Transform { + &self.transform + } + + pub fn get_transform_mut(&mut self) -> &mut Transform { + &mut self.transform + } + + pub fn set_projection(&mut self, projection: Mat4) { + self.projection = projection; + } + + pub fn create_buffer( + &self, + memory_allocator: &Arc, + ) -> Result, Validated> { + MVP::new(&self.transform.get_mat4(), &self.view, &self.projection) + .into_buffer(memory_allocator) + } +} diff --git a/src/core/render/primitives/mod.rs b/src/core/render/primitives/mod.rs new file mode 100644 index 0000000..fd0b762 --- /dev/null +++ b/src/core/render/primitives/mod.rs @@ -0,0 +1,4 @@ +pub mod camera; +mod mvp; +pub mod transform; +pub mod vertex; diff --git a/src/core/render/primitives/mvp.rs b/src/core/render/primitives/mvp.rs new file mode 100644 index 0000000..fb73ad3 --- /dev/null +++ b/src/core/render/primitives/mvp.rs @@ -0,0 +1,45 @@ +use std::sync::Arc; + +use glam::Mat4; +use vulkano::Validated; +use vulkano::buffer::{ + AllocateBufferError, Buffer, BufferContents, BufferCreateInfo, BufferUsage, Subbuffer, +}; +use vulkano::memory::allocator::{AllocationCreateInfo, MemoryTypeFilter, StandardMemoryAllocator}; + +#[derive(BufferContents, Clone, Copy)] +#[repr(C)] +pub struct MVP { + world: [[f32; 4]; 4], + view: [[f32; 4]; 4], + projection: [[f32; 4]; 4], +} + +impl MVP { + pub fn new(world: &Mat4, view: &Mat4, projection: &Mat4) -> Self { + Self { + world: world.to_cols_array_2d(), + view: view.to_cols_array_2d(), + projection: projection.to_cols_array_2d(), + } + } + + pub fn into_buffer( + self, + memory_allocator: &Arc, + ) -> Result, Validated> { + Buffer::from_iter( + memory_allocator.clone(), + BufferCreateInfo { + usage: BufferUsage::UNIFORM_BUFFER, + ..Default::default() + }, + AllocationCreateInfo { + memory_type_filter: MemoryTypeFilter::PREFER_DEVICE + | MemoryTypeFilter::HOST_SEQUENTIAL_WRITE, + ..Default::default() + }, + [self], + ) + } +} diff --git a/src/core/render/primitives/transform.rs b/src/core/render/primitives/transform.rs new file mode 100644 index 0000000..88e6422 --- /dev/null +++ b/src/core/render/primitives/transform.rs @@ -0,0 +1,37 @@ +use glam::{Mat4, Quat, Vec3}; + +pub struct Transform { + position: Vec3, + rotation: Quat, + scale: Vec3, +} + +impl Default for Transform { + fn default() -> Self { + Self { + position: Vec3::default(), + rotation: Quat::default(), + scale: Vec3::ONE, + } + } +} + +impl Transform { + pub fn rotate(&mut self, rotation: Quat) { + self.rotation = self.rotation * rotation; + } + + pub fn translate(&mut self, translation: Vec3) { + self.position += translation; + } + + pub fn scale(&mut self, scale: Vec3) { + self.scale *= scale; + } + + pub fn get_mat4(&self) -> Mat4 { + Mat4::from_translation(self.position) + * Mat4::from_quat(self.rotation) + * Mat4::from_scale(self.scale) + } +} diff --git a/src/core/render/vertex.rs b/src/core/render/primitives/vertex.rs similarity index 100% rename from src/core/render/vertex.rs rename to src/core/render/primitives/vertex.rs diff --git a/src/game/main_scene.rs b/src/game/main_scene.rs index c0e13be..54ca089 100644 --- a/src/game/main_scene.rs +++ b/src/game/main_scene.rs @@ -1,25 +1,19 @@ use crate::core::input::InputState; -use crate::core::render::pipelines::triangle_pipeline::shaders::vs; +use crate::core::render::primitives::camera::Camera; +use crate::core::render::primitives::vertex::Vertex2D; use crate::core::render::render_context::RenderContext; use crate::core::scene::Scene; use crate::core::timer::Timer; -use glam::{Mat3, Mat4, Vec3}; -use std::error::Error; +use glam::{Mat4, Quat, Vec3}; use std::sync::Arc; -use std::time::Instant; -use vulkano::buffer::{Buffer, BufferCreateInfo, BufferUsage, Subbuffer}; +use vulkano::buffer::Subbuffer; use vulkano::command_buffer::{AutoCommandBufferBuilder, PrimaryAutoCommandBuffer}; use vulkano::descriptor_set::{DescriptorSet, WriteDescriptorSet}; -use vulkano::memory::allocator::{AllocationCreateInfo, MemoryTypeFilter, StandardMemoryAllocator}; use vulkano::pipeline::{GraphicsPipeline, Pipeline, PipelineBindPoint}; -use vulkano_util::renderer::VulkanoWindowRenderer; use winit::event::ElementState; use winit::keyboard::{KeyCode, PhysicalKey}; use crate::core::render::pipelines::triangle_pipeline::create_triangle_pipeline; -use crate::core::render::vertex::Vertex2D; - -use crate::core::render::vulkan_context::VulkanContext; const VERTICES: [Vertex2D; 12] = [ // Triangle en haut à gauche @@ -79,8 +73,7 @@ const VERTICES: [Vertex2D; 12] = [ pub struct MainSceneState { pipeline: Arc, vertex_buffer: Subbuffer<[Vertex2D]>, - uniform_buffer: Subbuffer, - rotation: f32, + camera: Camera, } #[derive(Default)] @@ -101,39 +94,72 @@ impl Scene for MainScene { Vertex2D::create_buffer(Vec::from_iter(VERTICES), render_context.memory_allocator()) .unwrap(); - let uniform_buffer = MainScene::get_uniform_buffer( - 0.0, - render_context.memory_allocator(), - render_context.aspect_ratio(), + let camera = Camera::new( + Mat4::look_at_rh( + Vec3::new(0.3, 0.3, 1.0), + Vec3::new(0.0, 0.0, 0.0), + Vec3::new(0.0, -1.0, 0.0), + ), + Mat4::perspective_rh_gl( + std::f32::consts::FRAC_PI_2, + render_context.aspect_ratio(), + 0.01, + 100.0, + ), ); self.state = Some(MainSceneState { pipeline, vertex_buffer, - uniform_buffer, - rotation: 0.0, + camera, }) } fn update(&mut self, render_context: &RenderContext, input_state: &InputState, timer: &Timer) { let state = self.state.as_mut().unwrap(); - let delta_rotation = if input_state.get_key_state(PhysicalKey::Code(KeyCode::KeyA)) + + let speed = 50.0 * timer.delta_time(); + + let mut rot = Quat::default(); + rot *= Quat::from_rotation_y(input_state.mouse_state.delta.x * speed.to_radians()); + rot *= Quat::from_rotation_x(input_state.mouse_state.delta.y * speed.to_radians()); + state.camera.get_transform_mut().rotate(rot); + + let translation_x = if input_state.get_key_state(PhysicalKey::Code(KeyCode::KeyA)) == &ElementState::Pressed { - timer.delta_time() * 5.0 + timer.delta_time() * speed } else if input_state.get_key_state(PhysicalKey::Code(KeyCode::KeyD)) == &ElementState::Pressed { - timer.delta_time() * -5.0 + timer.delta_time() * -speed } else { timer.delta_time() * 0.0 }; - state.rotation += delta_rotation; - state.uniform_buffer = MainScene::get_uniform_buffer( - state.rotation, - render_context.memory_allocator(), + + let translation_z = if input_state.get_key_state(PhysicalKey::Code(KeyCode::KeyW)) + == &ElementState::Pressed + { + timer.delta_time() * speed + } else if input_state.get_key_state(PhysicalKey::Code(KeyCode::KeyS)) + == &ElementState::Pressed + { + timer.delta_time() * -speed + } else { + timer.delta_time() * 0.0 + }; + + state + .camera + .get_transform_mut() + .translate(Vec3::new(translation_x, 0.0, translation_z)); + + state.camera.set_projection(Mat4::perspective_rh_gl( + std::f32::consts::FRAC_PI_2, render_context.aspect_ratio(), - ); + 0.01, + 100.0, + )); } fn render( @@ -141,33 +167,35 @@ impl Scene for MainScene { render_context: &RenderContext, builder: &mut AutoCommandBufferBuilder, ) { - let vertex_count = self.state.as_ref().unwrap().vertex_buffer.len() as u32; + let state = self.state.as_ref().unwrap(); + let vertex_count = state.vertex_buffer.len() as u32; let instance_count = vertex_count / 3; - let layout = &self.state.as_ref().unwrap().pipeline.layout().set_layouts()[0]; + let layout = &state.pipeline.layout().set_layouts()[0]; + let uniform_buffer = state + .camera + .create_buffer(render_context.memory_allocator()) + .unwrap(); let descriptor_set = DescriptorSet::new( render_context.descriptor_set_allocator().clone(), layout.clone(), - [WriteDescriptorSet::buffer( - 0, - self.state.as_ref().unwrap().uniform_buffer.clone(), - )], + [WriteDescriptorSet::buffer(0, uniform_buffer)], [], ) .unwrap(); unsafe { builder - .bind_pipeline_graphics(self.state.as_ref().unwrap().pipeline.clone()) + .bind_pipeline_graphics(state.pipeline.clone()) .unwrap() .bind_descriptor_sets( PipelineBindPoint::Graphics, - self.state.as_ref().unwrap().pipeline.layout().clone(), + state.pipeline.layout().clone(), 0, descriptor_set, ) .unwrap() - .bind_vertex_buffers(0, self.state.as_ref().unwrap().vertex_buffer.clone()) + .bind_vertex_buffers(0, state.vertex_buffer.clone()) .unwrap() .draw(vertex_count, instance_count, 0, 0) .unwrap(); @@ -176,44 +204,3 @@ impl Scene for MainScene { fn unload(&mut self) {} } - -impl MainScene { - fn get_uniform_buffer( - rotation: f32, - memory_allocator: &Arc, - aspect_ratio: f32, - ) -> Subbuffer { - let rotation = Mat3::from_rotation_y(rotation); - - // NOTE: This teapot was meant for OpenGL where the origin is at the lower left - // instead the origin is at the upper left in Vulkan, so we reverse the Y axis. - let proj = Mat4::perspective_rh_gl(std::f32::consts::FRAC_PI_2, aspect_ratio, 0.01, 100.0); - let view = Mat4::look_at_rh( - Vec3::new(0.3, 0.3, 1.0), - Vec3::new(0.0, 0.0, 0.0), - Vec3::new(0.0, -1.0, 0.0), - ); - let scale = Mat4::from_scale(Vec3::splat(1.0)); - - let uniform_data = vs::MVPData { - world: Mat4::from_mat3(rotation).to_cols_array_2d(), - view: (view * scale).to_cols_array_2d(), - projection: proj.to_cols_array_2d(), - }; - - Buffer::from_data( - memory_allocator.clone(), - BufferCreateInfo { - usage: BufferUsage::UNIFORM_BUFFER, - ..Default::default() - }, - AllocationCreateInfo { - memory_type_filter: MemoryTypeFilter::PREFER_DEVICE - | MemoryTypeFilter::HOST_SEQUENTIAL_WRITE, - ..Default::default() - }, - uniform_data, - ) - .unwrap() - } -} From 8c42e7b1399e85ec50e390fed805901e9d802ed3 Mon Sep 17 00:00:00 2001 From: Florian RICHER Date: Tue, 27 May 2025 17:13:22 +0200 Subject: [PATCH 043/105] Refactor input --- src/core/{render => }/app.rs | 68 +++++++----------- src/core/input.rs | 75 -------------------- src/core/input/keyboard_state.rs | 46 +++++++++++++ src/core/input/mod.rs | 64 +++++++++++++++++ src/core/input/mouse_state.rs | 22 ++++++ src/core/input/virtual_binding.rs | 24 +++++++ src/core/input/virtual_input.rs | 110 ++++++++++++++++++++++++++++++ src/core/input/virtual_state.rs | 49 +++++++++++++ src/core/mod.rs | 1 + src/core/render/mod.rs | 1 - src/core/render/render_context.rs | 50 +++++++++++--- src/core/scene.rs | 9 ++- src/game/main_scene.rs | 43 +++++------- src/main.rs | 36 +++++++++- 14 files changed, 439 insertions(+), 159 deletions(-) rename src/core/{render => }/app.rs (76%) delete mode 100644 src/core/input.rs create mode 100644 src/core/input/keyboard_state.rs create mode 100644 src/core/input/mod.rs create mode 100644 src/core/input/mouse_state.rs create mode 100644 src/core/input/virtual_binding.rs create mode 100644 src/core/input/virtual_input.rs create mode 100644 src/core/input/virtual_state.rs diff --git a/src/core/render/app.rs b/src/core/app.rs similarity index 76% rename from src/core/render/app.rs rename to src/core/app.rs index 6e08f72..3a7145b 100644 --- a/src/core/render/app.rs +++ b/src/core/app.rs @@ -1,7 +1,9 @@ use std::collections::HashMap; use std::sync::Arc; -use crate::core::input::InputState; +use super::render::render_context::RenderContext; +use super::render::vulkan_context::VulkanContext; +use crate::core::input::InputManager; use crate::core::scene::SceneManager; use crate::core::timer::Timer; use crate::game::main_scene::MainScene; @@ -17,49 +19,46 @@ use vulkano_util::context::VulkanoContext; use vulkano_util::renderer::VulkanoWindowRenderer; use vulkano_util::window::{VulkanoWindows, WindowDescriptor}; use winit::application::ApplicationHandler; -use winit::event::{ElementState, WindowEvent}; +use winit::event::WindowEvent; use winit::event_loop::ActiveEventLoop; use winit::window::WindowId; -use super::render_context::RenderContext; -use super::vulkan_context::VulkanContext; - pub struct App { vulkan_context: Arc, vulkano_windows: Arc, gui: HashMap, clear_color: [f32; 3], - input_state: InputState, + input_manager: InputManager, scene_manager: SceneManager, timer: Timer, } impl From<(&VulkanContext, &VulkanoWindowRenderer)> for RenderContext { fn from((vulkan_context, renderer): (&VulkanContext, &VulkanoWindowRenderer)) -> Self { - RenderContext { - window_size: renderer.resolution(), - aspect_ratio: renderer.aspect_ratio(), - instance: vulkan_context.vulkano_context().instance().clone(), - device: vulkan_context.vulkano_context().device().clone(), - graphics_queue: vulkan_context.vulkano_context().graphics_queue().clone(), - compute_queue: vulkan_context.vulkano_context().compute_queue().clone(), - transfer_queue: vulkan_context.vulkano_context().transfer_queue().cloned(), - memory_allocator: vulkan_context.vulkano_context().memory_allocator().clone(), - command_buffer_allocator: vulkan_context.command_buffer_allocator().clone(), - descriptor_set_allocator: vulkan_context.descriptor_set_allocator().clone(), - swapchain_format: renderer.swapchain_format(), - } + RenderContext::new( + vulkan_context.vulkano_context().instance().clone(), + vulkan_context.vulkano_context().device().clone(), + vulkan_context.vulkano_context().graphics_queue().clone(), + vulkan_context.vulkano_context().compute_queue().clone(), + vulkan_context.vulkano_context().transfer_queue().cloned(), + vulkan_context.vulkano_context().memory_allocator().clone(), + vulkan_context.command_buffer_allocator().clone(), + vulkan_context.descriptor_set_allocator().clone(), + renderer.resolution(), + renderer.aspect_ratio(), + renderer.swapchain_format(), + ) } } -impl From for App { - fn from(vulkano_context: VulkanoContext) -> Self { +impl App { + pub fn new(vulkano_context: VulkanoContext, input_manager: InputManager) -> Self { Self { vulkan_context: Arc::new(VulkanContext::new(vulkano_context)), vulkano_windows: Arc::new(VulkanoWindows::default()), gui: HashMap::new(), clear_color: [0.0, 0.0, 0.0], - input_state: InputState::default(), + input_manager, scene_manager: SceneManager::new(), timer: Timer::new(), } @@ -111,7 +110,7 @@ impl ApplicationHandler for App { let render_context = RenderContext::from((self.vulkan_context.as_ref(), &*renderer)); if !gui.update(&event) { - self.input_state.process_event(&event); + self.input_manager.process_window_event(&event); } match event { @@ -126,11 +125,11 @@ impl ApplicationHandler for App { renderer.resize(); } WindowEvent::RedrawRequested => { - self.input_state.update(); + self.input_manager.update(); self.timer.update(); self.scene_manager.load_scene_if_not_loaded(&render_context); if let Some(scene) = self.scene_manager.current_scene_mut() { - scene.update(&render_context, &self.input_state, &self.timer); + scene.update(&render_context, &self.input_manager, &self.timer); } let acquire_future = renderer.acquire(None, |_| {}).unwrap(); @@ -195,25 +194,10 @@ impl ApplicationHandler for App { .show(&ctx, |ui| { ui.label(format!("Resolution: {:?}", renderer.resolution())); ui.color_edit_button_rgb(&mut self.clear_color); - ui.label(format!( - "Mouse position: {:?}", - self.input_state.get_mouse_state().position - )); - ui.label(format!( - "Mouse delta: {:?}", - self.input_state.get_mouse_state().delta - )); + + ui.label(format!("{:#?}", self.input_manager.get_virtual_input())); ui.label(format!("Delta time: {:?}", self.timer.delta_time())); - - for (key, state) in self - .input_state - .key_states - .iter() - .filter(|(_, state)| *state == &ElementState::Pressed) - { - ui.label(format!("{:?} State: {:?}", key, state)); - } }); }); diff --git a/src/core/input.rs b/src/core/input.rs deleted file mode 100644 index 59e8398..0000000 --- a/src/core/input.rs +++ /dev/null @@ -1,75 +0,0 @@ -use std::collections::HashMap; - -use winit::{ - dpi::PhysicalPosition, - event::{ElementState, KeyEvent, WindowEvent}, - keyboard::PhysicalKey, -}; - -#[derive(Debug, Default)] -pub struct MouseState { - old_position: glam::Vec2, - pub position: glam::Vec2, - pub delta: glam::Vec2, -} - -#[derive(Debug, Default)] -pub struct InputState { - pub key_states: HashMap, - pub mouse_state: MouseState, -} - -impl InputState { - pub fn process_event(&mut self, event: &WindowEvent) { - match event { - WindowEvent::KeyboardInput { event, .. } => { - self.update_key_state(event.physical_key, event); - } - WindowEvent::CursorMoved { position, .. } => { - self.update_mouse_position(position); - } - _ => {} - } - } - - /// Updates deltas before running update - pub fn update(&mut self) { - self.mouse_state.delta = self.mouse_state.position - self.mouse_state.old_position; - self.mouse_state.old_position = self.mouse_state.position; - } - - pub fn get_key_state(&self, key: PhysicalKey) -> &ElementState { - self.key_states.get(&key).unwrap_or(&ElementState::Released) - } - - pub fn get_mouse_state(&self) -> &MouseState { - &self.mouse_state - } - - fn update_key_state(&mut self, key: PhysicalKey, event: &KeyEvent) { - let key_state = self.key_states.get(&event.physical_key); - let new_key_state = match key_state { - Some(key_state) => match event.state { - ElementState::Pressed => match key_state { - ElementState::Released => Some(ElementState::Pressed), - ElementState::Pressed => None, - }, - ElementState::Released => match key_state { - ElementState::Released => None, - ElementState::Pressed => Some(ElementState::Released), - }, - }, - None => match event.state { - ElementState::Pressed => Some(ElementState::Pressed), - ElementState::Released => Some(ElementState::Released), - }, - }; - if let Some(new_key_state) = new_key_state { - self.key_states.insert(key, new_key_state); - } - } - - fn update_mouse_position(&mut self, position: &PhysicalPosition) { - self.mouse_state.position = glam::Vec2::new(position.x as f32, position.y as f32); - } -} diff --git a/src/core/input/keyboard_state.rs b/src/core/input/keyboard_state.rs new file mode 100644 index 0000000..c7b9b3e --- /dev/null +++ b/src/core/input/keyboard_state.rs @@ -0,0 +1,46 @@ +use egui_winit_vulkano::egui::ahash::HashMap; +use winit::{ + event::{ElementState, KeyEvent}, + keyboard::PhysicalKey, +}; + +use super::virtual_input::VirtualInput; + +#[derive(Debug, Default)] +pub struct KeyboardState { + key_states: HashMap, +} + +impl KeyboardState { + pub fn get_key_states(&self) -> &HashMap { + &self.key_states + } + + pub fn get_key_state(&self, key: PhysicalKey) -> &ElementState { + self.key_states.get(&key).unwrap_or(&ElementState::Released) + } + + pub fn process_window_event(&mut self, event: &KeyEvent, virtual_input: &mut VirtualInput) { + let key_state = self.key_states.get(&event.physical_key); + let new_key_state = match key_state { + Some(key_state) => match event.state { + ElementState::Pressed => match key_state { + ElementState::Released => Some(ElementState::Pressed), + ElementState::Pressed => None, + }, + ElementState::Released => match key_state { + ElementState::Released => None, + ElementState::Pressed => Some(ElementState::Released), + }, + }, + None => match event.state { + ElementState::Pressed => Some(ElementState::Pressed), + ElementState::Released => Some(ElementState::Released), + }, + }; + if let Some(new_key_state) = new_key_state { + self.key_states.insert(event.physical_key, new_key_state); + virtual_input.update_key_binding(event.physical_key, new_key_state); + } + } +} diff --git a/src/core/input/mod.rs b/src/core/input/mod.rs new file mode 100644 index 0000000..f29b963 --- /dev/null +++ b/src/core/input/mod.rs @@ -0,0 +1,64 @@ +use std::collections::HashMap; + +use keyboard_state::KeyboardState; +use mouse_state::MouseState; +use virtual_binding::VirtualBinding; +use virtual_input::VirtualInput; +use winit::event::WindowEvent; + +pub mod keyboard_state; +pub mod mouse_state; +pub mod virtual_binding; +pub mod virtual_input; +pub mod virtual_state; + +#[derive(Default)] +pub struct InputManager { + keyboard_state: KeyboardState, + mouse_state: MouseState, + virtual_input: VirtualInput, +} + +impl InputManager { + pub fn process_window_event(&mut self, event: &WindowEvent) { + match event { + WindowEvent::KeyboardInput { event, .. } => { + self.keyboard_state + .process_window_event(event, &mut self.virtual_input); + } + WindowEvent::CursorMoved { position, .. } => { + self.mouse_state.process_window_event(position); + } + _ => {} + } + } + + /// Updates deltas before running update + pub fn update(&mut self) { + self.mouse_state.update(&mut self.virtual_input); + } + + pub fn get_mouse_state(&self) -> &MouseState { + &self.mouse_state + } + + pub fn get_keyboard_state(&self) -> &KeyboardState { + &self.keyboard_state + } + + pub fn get_virtual_input(&self) -> &VirtualInput { + &self.virtual_input + } + + pub fn add_virtual_binding(&mut self, value_name: String, binding: VirtualBinding) { + self.virtual_input.add_binding(value_name, binding); + } + + pub fn add_virtual_bindings(&mut self, value_name: String, bindings: Vec) { + self.virtual_input.add_bindings(value_name, bindings); + } + + pub fn get_virtual_input_state(&self, value_name: &str) -> f32 { + self.virtual_input.get_state(value_name) + } +} diff --git a/src/core/input/mouse_state.rs b/src/core/input/mouse_state.rs new file mode 100644 index 0000000..8cdce4b --- /dev/null +++ b/src/core/input/mouse_state.rs @@ -0,0 +1,22 @@ +use winit::dpi::PhysicalPosition; + +use super::virtual_input::VirtualInput; + +#[derive(Debug, Default)] +pub struct MouseState { + old_position: glam::Vec2, + pub position: glam::Vec2, + pub delta: glam::Vec2, +} + +impl MouseState { + pub fn process_window_event(&mut self, position: &PhysicalPosition) { + self.position = glam::Vec2::new(position.x as f32, position.y as f32); + } + + pub fn update(&mut self, virtual_input: &mut VirtualInput) { + self.delta = self.position - self.old_position; + self.old_position = self.position; + virtual_input.update_mouse_binding(&self.delta); + } +} diff --git a/src/core/input/virtual_binding.rs b/src/core/input/virtual_binding.rs new file mode 100644 index 0000000..9209a78 --- /dev/null +++ b/src/core/input/virtual_binding.rs @@ -0,0 +1,24 @@ +use winit::{event::AxisId, keyboard::PhysicalKey}; + +#[derive(Clone)] +pub enum AxisDirection { + Positive, + Negative, +} + +impl From<&AxisDirection> for f32 { + fn from(direction: &AxisDirection) -> Self { + match direction { + AxisDirection::Positive => 1.0, + AxisDirection::Negative => -1.0, + } + } +} + +#[derive(Clone)] +pub enum VirtualBinding { + Keyboard(PhysicalKey, AxisDirection), + Axis(AxisId, AxisDirection, f32), // f32 deadzone + MouseX(AxisDirection), + MouseY(AxisDirection), +} diff --git a/src/core/input/virtual_input.rs b/src/core/input/virtual_input.rs new file mode 100644 index 0000000..f1ec4a8 --- /dev/null +++ b/src/core/input/virtual_input.rs @@ -0,0 +1,110 @@ +use std::{collections::HashMap, sync::Arc}; + +use egui_winit_vulkano::egui::mutex::Mutex; +use winit::{event::ElementState, keyboard::PhysicalKey}; + +use super::{ + virtual_binding::VirtualBinding, + virtual_state::{VirtualBindingState, VirtualInputState}, +}; + +#[derive(Default)] +pub struct VirtualInput { + states: HashMap>>, + states_by_key: HashMap>>>, + mouse_states: Vec>>, +} + +impl std::fmt::Debug for VirtualInput { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let mut debug = f.debug_struct("VirtualInput"); + + for (name, state) in &self.states { + debug.field(name, &state.lock().value); + } + + debug.finish() + } +} + +impl VirtualInput { + pub fn get_state(&self, value_name: &str) -> f32 { + self.states + .get(value_name) + .and_then(|state| Some(state.lock().value)) + .unwrap_or(0.0) + } + + pub fn add_bindings(&mut self, value_name: String, new_bindings: Vec) { + add_bindings( + &mut self.states, + &mut self.states_by_key, + &mut self.mouse_states, + value_name, + new_bindings, + ); + } + + pub fn add_binding(&mut self, value_name: String, binding: VirtualBinding) { + self.add_bindings(value_name, vec![binding]); + } + + pub(super) fn update_key_binding(&mut self, key: PhysicalKey, key_state: ElementState) { + let states = self.states_by_key.get_mut(&key); + + if let Some(states) = states { + for state in states { + let mut state = state.lock(); + state.update_from_key(key, key_state); + } + } else { + log::trace!("{}", self.states_by_key.keys().len()); + log::warn!("No states found for key: {key:?}"); + } + } + + pub(super) fn update_mouse_binding(&mut self, delta: &glam::Vec2) { + for state in &mut self.mouse_states { + let mut state = state.lock(); + state.update_from_mouse(delta); + } + } +} + +fn add_bindings( + states: &mut HashMap>>, + states_by_key: &mut HashMap>>>, + mouse_states: &mut Vec>>, + value_name: String, + new_bindings: Vec, +) { + let state = states + .entry(value_name) + .or_insert(Arc::new(Mutex::new(VirtualInputState { + value: 0.0, + bindings: Vec::new(), + }))); + + for binding in &new_bindings { + match binding { + VirtualBinding::Keyboard(key, _) => { + states_by_key + .entry(*key) + .or_insert(Vec::new()) + .push(state.clone()); + } + VirtualBinding::MouseX(_) | VirtualBinding::MouseY(_) => { + mouse_states.push(state.clone()); + } + _ => {} + } + } + + let mut state = state.lock(); + state + .bindings + .extend(new_bindings.iter().map(|b| VirtualBindingState { + value: 0.0, + binding: b.clone(), + })); +} diff --git a/src/core/input/virtual_state.rs b/src/core/input/virtual_state.rs new file mode 100644 index 0000000..47f8b0d --- /dev/null +++ b/src/core/input/virtual_state.rs @@ -0,0 +1,49 @@ +use winit::{event::ElementState, keyboard::PhysicalKey}; + +use super::virtual_binding::VirtualBinding; + +pub struct VirtualBindingState { + pub value: f32, + pub binding: VirtualBinding, +} + +pub struct VirtualInputState { + pub value: f32, + pub bindings: Vec, +} + +impl VirtualInputState { + pub fn update_from_key(&mut self, key: PhysicalKey, key_state: ElementState) { + let mut new_value = 0.0; + for binding in &mut self.bindings { + if let VirtualBinding::Keyboard(binding_key, direction) = &binding.binding { + if binding_key == &key { + if key_state == ElementState::Pressed { + binding.value += f32::from(direction); + } else { + binding.value = 0.0; + } + } + } + new_value += binding.value; + } + self.value = new_value; + } + + pub fn update_from_mouse(&mut self, delta: &glam::Vec2) { + let mut new_value = 0.0; + for binding in &mut self.bindings { + match &binding.binding { + VirtualBinding::MouseX(direction) => { + binding.value = f32::from(direction) * delta.x; + } + VirtualBinding::MouseY(direction) => { + binding.value = f32::from(direction) * delta.y; + } + _ => {} + } + new_value += binding.value; + } + self.value = new_value; + } +} diff --git a/src/core/mod.rs b/src/core/mod.rs index ddaf320..237496d 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -1,3 +1,4 @@ +pub mod app; pub mod input; pub mod render; pub mod scene; diff --git a/src/core/render/mod.rs b/src/core/render/mod.rs index a074771..c550198 100644 --- a/src/core/render/mod.rs +++ b/src/core/render/mod.rs @@ -1,4 +1,3 @@ -pub mod app; pub mod pipelines; pub mod primitives; pub mod render_context; diff --git a/src/core/render/render_context.rs b/src/core/render/render_context.rs index d1d2be4..e9e34a1 100644 --- a/src/core/render/render_context.rs +++ b/src/core/render/render_context.rs @@ -10,20 +10,48 @@ use vulkano::{ }; pub struct RenderContext { - pub(super) instance: Arc, - pub(super) device: Arc, - pub(super) graphics_queue: Arc, - pub(super) compute_queue: Arc, - pub(super) transfer_queue: Option>, - pub(super) memory_allocator: Arc, - pub(super) command_buffer_allocator: Arc, - pub(super) descriptor_set_allocator: Arc, - pub(super) window_size: [f32; 2], - pub(super) aspect_ratio: f32, - pub(super) swapchain_format: Format, + instance: Arc, + device: Arc, + graphics_queue: Arc, + compute_queue: Arc, + transfer_queue: Option>, + memory_allocator: Arc, + command_buffer_allocator: Arc, + descriptor_set_allocator: Arc, + window_size: [f32; 2], + aspect_ratio: f32, + swapchain_format: Format, } impl RenderContext { + pub fn new( + instance: Arc, + device: Arc, + graphics_queue: Arc, + compute_queue: Arc, + transfer_queue: Option>, + memory_allocator: Arc, + command_buffer_allocator: Arc, + descriptor_set_allocator: Arc, + window_size: [f32; 2], + aspect_ratio: f32, + swapchain_format: Format, + ) -> Self { + Self { + instance, + device, + graphics_queue, + compute_queue, + transfer_queue, + memory_allocator, + command_buffer_allocator, + descriptor_set_allocator, + window_size, + aspect_ratio, + swapchain_format, + } + } + pub fn instance(&self) -> &Arc { &self.instance } diff --git a/src/core/scene.rs b/src/core/scene.rs index e8040f8..9717117 100644 --- a/src/core/scene.rs +++ b/src/core/scene.rs @@ -1,11 +1,16 @@ use vulkano::command_buffer::{AutoCommandBufferBuilder, PrimaryAutoCommandBuffer}; -use super::{input::InputState, render::render_context::RenderContext, timer::Timer}; +use super::{input::InputManager, render::render_context::RenderContext, timer::Timer}; pub trait Scene { fn loaded(&self) -> bool; fn load(&mut self, render_context: &RenderContext); - fn update(&mut self, render_context: &RenderContext, input_state: &InputState, timer: &Timer); + fn update( + &mut self, + render_context: &RenderContext, + input_manager: &InputManager, + timer: &Timer, + ); fn render( &self, render_context: &RenderContext, diff --git a/src/game/main_scene.rs b/src/game/main_scene.rs index 54ca089..a759478 100644 --- a/src/game/main_scene.rs +++ b/src/game/main_scene.rs @@ -1,4 +1,4 @@ -use crate::core::input::InputState; +use crate::core::input::InputManager; use crate::core::render::primitives::camera::Camera; use crate::core::render::primitives::vertex::Vertex2D; use crate::core::render::render_context::RenderContext; @@ -115,39 +115,30 @@ impl Scene for MainScene { }) } - fn update(&mut self, render_context: &RenderContext, input_state: &InputState, timer: &Timer) { + fn update( + &mut self, + render_context: &RenderContext, + input_manager: &InputManager, + timer: &Timer, + ) { let state = self.state.as_mut().unwrap(); let speed = 50.0 * timer.delta_time(); let mut rot = Quat::default(); - rot *= Quat::from_rotation_y(input_state.mouse_state.delta.x * speed.to_radians()); - rot *= Quat::from_rotation_x(input_state.mouse_state.delta.y * speed.to_radians()); + rot *= Quat::from_rotation_y( + input_manager.get_virtual_input_state("mouse_x") * speed.to_radians(), + ); + rot *= Quat::from_rotation_x( + input_manager.get_virtual_input_state("mouse_y") * speed.to_radians(), + ); state.camera.get_transform_mut().rotate(rot); - let translation_x = if input_state.get_key_state(PhysicalKey::Code(KeyCode::KeyA)) - == &ElementState::Pressed - { - timer.delta_time() * speed - } else if input_state.get_key_state(PhysicalKey::Code(KeyCode::KeyD)) - == &ElementState::Pressed - { - timer.delta_time() * -speed - } else { - timer.delta_time() * 0.0 - }; + let translation_x = + input_manager.get_virtual_input_state("move_right") * timer.delta_time() * speed; - let translation_z = if input_state.get_key_state(PhysicalKey::Code(KeyCode::KeyW)) - == &ElementState::Pressed - { - timer.delta_time() * speed - } else if input_state.get_key_state(PhysicalKey::Code(KeyCode::KeyS)) - == &ElementState::Pressed - { - timer.delta_time() * -speed - } else { - timer.delta_time() * 0.0 - }; + let translation_z = + input_manager.get_virtual_input_state("move_forward") * timer.delta_time() * speed; state .camera diff --git a/src/main.rs b/src/main.rs index 52ba1f6..d671188 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,14 @@ +use core::input::{ + InputManager, + virtual_binding::{AxisDirection, VirtualBinding}, +}; + use vulkano::device::{DeviceExtensions, DeviceFeatures}; use vulkano_util::context::{VulkanoConfig, VulkanoContext}; -use winit::event_loop::{ControlFlow, EventLoop}; +use winit::{ + event_loop::{ControlFlow, EventLoop}, + keyboard::{KeyCode, PhysicalKey}, +}; mod core; mod game; @@ -8,6 +16,30 @@ mod game; fn main() { env_logger::init(); + let mut input_manager = InputManager::default(); + input_manager.add_virtual_bindings( + "move_forward".to_string(), + vec![ + VirtualBinding::Keyboard(PhysicalKey::Code(KeyCode::KeyW), AxisDirection::Positive), + VirtualBinding::Keyboard(PhysicalKey::Code(KeyCode::KeyS), AxisDirection::Negative), + ], + ); + input_manager.add_virtual_bindings( + "move_right".to_string(), + vec![ + VirtualBinding::Keyboard(PhysicalKey::Code(KeyCode::KeyD), AxisDirection::Positive), + VirtualBinding::Keyboard(PhysicalKey::Code(KeyCode::KeyA), AxisDirection::Negative), + ], + ); + input_manager.add_virtual_bindings( + "mouse_x".to_string(), + vec![VirtualBinding::MouseX(AxisDirection::Positive)], + ); + input_manager.add_virtual_bindings( + "mouse_y".to_string(), + vec![VirtualBinding::MouseY(AxisDirection::Positive)], + ); + let device_extensions = DeviceExtensions { khr_swapchain: true, ..Default::default() @@ -30,7 +62,7 @@ fn main() { let event_loop = EventLoop::new().unwrap(); event_loop.set_control_flow(ControlFlow::Poll); - let mut app = core::render::app::App::from(vulkano_context); + let mut app = core::app::App::new(vulkano_context, input_manager); match event_loop.run_app(&mut app) { Ok(_) => {} From b0f82b071461f6efd9f33c4af4eefde663f653a1 Mon Sep 17 00:00:00 2001 From: Florian RICHER Date: Tue, 27 May 2025 19:11:28 +0200 Subject: [PATCH 044/105] input: Add support for Axis, Mouse Button, MouseWheel --- src/core/app.rs | 2 +- src/core/input/keyboard_state.rs | 41 +++------ src/core/input/mod.rs | 95 ++++++++++++++------- src/core/input/mouse_state.rs | 41 +++++++-- src/core/input/virtual_binding.rs | 16 ++-- src/core/input/virtual_input.rs | 136 +++++++++++++++++++----------- src/core/input/virtual_state.rs | 58 ++++++++++++- src/game/main_scene.rs | 5 +- src/main.rs | 74 ++++++++++------ 9 files changed, 308 insertions(+), 160 deletions(-) diff --git a/src/core/app.rs b/src/core/app.rs index 3a7145b..d14b4c5 100644 --- a/src/core/app.rs +++ b/src/core/app.rs @@ -195,7 +195,7 @@ impl ApplicationHandler for App { ui.label(format!("Resolution: {:?}", renderer.resolution())); ui.color_edit_button_rgb(&mut self.clear_color); - ui.label(format!("{:#?}", self.input_manager.get_virtual_input())); + ui.label(format!("{:#?}", self.input_manager)); ui.label(format!("Delta time: {:?}", self.timer.delta_time())); }); diff --git a/src/core/input/keyboard_state.rs b/src/core/input/keyboard_state.rs index c7b9b3e..f35e448 100644 --- a/src/core/input/keyboard_state.rs +++ b/src/core/input/keyboard_state.rs @@ -1,10 +1,10 @@ use egui_winit_vulkano::egui::ahash::HashMap; use winit::{ - event::{ElementState, KeyEvent}, + event::{ElementState, WindowEvent}, keyboard::PhysicalKey, }; -use super::virtual_input::VirtualInput; +use super::{process_new_element_state, virtual_input::VirtualInput}; #[derive(Debug, Default)] pub struct KeyboardState { @@ -12,35 +12,14 @@ pub struct KeyboardState { } impl KeyboardState { - pub fn get_key_states(&self) -> &HashMap { - &self.key_states - } - - pub fn get_key_state(&self, key: PhysicalKey) -> &ElementState { - self.key_states.get(&key).unwrap_or(&ElementState::Released) - } - - pub fn process_window_event(&mut self, event: &KeyEvent, virtual_input: &mut VirtualInput) { - let key_state = self.key_states.get(&event.physical_key); - let new_key_state = match key_state { - Some(key_state) => match event.state { - ElementState::Pressed => match key_state { - ElementState::Released => Some(ElementState::Pressed), - ElementState::Pressed => None, - }, - ElementState::Released => match key_state { - ElementState::Released => None, - ElementState::Pressed => Some(ElementState::Released), - }, - }, - None => match event.state { - ElementState::Pressed => Some(ElementState::Pressed), - ElementState::Released => Some(ElementState::Released), - }, - }; - if let Some(new_key_state) = new_key_state { - self.key_states.insert(event.physical_key, new_key_state); - virtual_input.update_key_binding(event.physical_key, new_key_state); + pub fn process_window_event(&mut self, event: &WindowEvent, virtual_input: &mut VirtualInput) { + if let WindowEvent::KeyboardInput { event, .. } = event { + let key_state = self.key_states.get(&event.physical_key); + let new_key_state = process_new_element_state(key_state, event.state); + if let Some(new_key_state) = new_key_state { + self.key_states.insert(event.physical_key, new_key_state); + virtual_input.update_key_binding(event.physical_key, new_key_state); + } } } } diff --git a/src/core/input/mod.rs b/src/core/input/mod.rs index f29b963..00ebe18 100644 --- a/src/core/input/mod.rs +++ b/src/core/input/mod.rs @@ -2,15 +2,15 @@ use std::collections::HashMap; use keyboard_state::KeyboardState; use mouse_state::MouseState; -use virtual_binding::VirtualBinding; use virtual_input::VirtualInput; -use winit::event::WindowEvent; +use winit::event::{ElementState, WindowEvent}; -pub mod keyboard_state; -pub mod mouse_state; -pub mod virtual_binding; -pub mod virtual_input; -pub mod virtual_state; +mod keyboard_state; +mod mouse_state; +mod virtual_binding; +mod virtual_input; +mod virtual_state; +pub use virtual_binding::{AxisDirection, VirtualBinding}; #[derive(Default)] pub struct InputManager { @@ -19,17 +19,34 @@ pub struct InputManager { virtual_input: VirtualInput, } +impl std::fmt::Debug for InputManager { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("InputManager") + .field("virtual_input", &self.virtual_input) + .finish() + } +} + impl InputManager { + pub fn new(input_mapping: HashMap>) -> Self { + let mut input_manager = InputManager::default(); + for (value_name, bindings) in input_mapping { + input_manager.add_virtual_bindings(value_name, bindings); + } + input_manager + } + pub fn process_window_event(&mut self, event: &WindowEvent) { match event { - WindowEvent::KeyboardInput { event, .. } => { + WindowEvent::AxisMotion { axis, value, .. } => { + self.virtual_input.update_axis_binding(*axis, *value as f32); + } + _ => { self.keyboard_state .process_window_event(event, &mut self.virtual_input); + self.mouse_state + .process_window_event(event, &mut self.virtual_input); } - WindowEvent::CursorMoved { position, .. } => { - self.mouse_state.process_window_event(position); - } - _ => {} } } @@ -38,27 +55,41 @@ impl InputManager { self.mouse_state.update(&mut self.virtual_input); } - pub fn get_mouse_state(&self) -> &MouseState { - &self.mouse_state - } - - pub fn get_keyboard_state(&self) -> &KeyboardState { - &self.keyboard_state - } - - pub fn get_virtual_input(&self) -> &VirtualInput { - &self.virtual_input - } - - pub fn add_virtual_binding(&mut self, value_name: String, binding: VirtualBinding) { - self.virtual_input.add_binding(value_name, binding); - } - - pub fn add_virtual_bindings(&mut self, value_name: String, bindings: Vec) { - self.virtual_input.add_bindings(value_name, bindings); - } - pub fn get_virtual_input_state(&self, value_name: &str) -> f32 { self.virtual_input.get_state(value_name) } + + fn add_virtual_bindings(&mut self, value_name: String, bindings: Vec) { + self.virtual_input.add_bindings(value_name, bindings); + } +} + +/// Maps the old element state to the new element state. +/// if is changed, returns Some(new_state), otherwise returns None +#[inline] +fn process_new_element_state( + old: Option<&ElementState>, + new: ElementState, +) -> Option { + match old { + Some(old) => match new { + ElementState::Pressed => match old { + ElementState::Released => Some(ElementState::Pressed), + ElementState::Pressed => None, + }, + ElementState::Released => match old { + ElementState::Released => None, + ElementState::Pressed => Some(ElementState::Released), + }, + }, + None => match new { + ElementState::Pressed => Some(ElementState::Pressed), + ElementState::Released => Some(ElementState::Released), + }, + } +} + +#[inline] +fn process_axis_deadzone(value: f32, deadzone: f32) -> f32 { + if value.abs() < deadzone { 0.0 } else { value } } diff --git a/src/core/input/mouse_state.rs b/src/core/input/mouse_state.rs index 8cdce4b..084f43c 100644 --- a/src/core/input/mouse_state.rs +++ b/src/core/input/mouse_state.rs @@ -1,22 +1,49 @@ -use winit::dpi::PhysicalPosition; +use std::collections::HashMap; -use super::virtual_input::VirtualInput; +use winit::event::{ElementState, MouseButton, MouseScrollDelta, WindowEvent}; + +use super::{process_new_element_state, virtual_input::VirtualInput}; #[derive(Debug, Default)] pub struct MouseState { old_position: glam::Vec2, - pub position: glam::Vec2, - pub delta: glam::Vec2, + position: glam::Vec2, + delta: glam::Vec2, + wheel_delta: glam::Vec2, + mouse_button_state: HashMap, } impl MouseState { - pub fn process_window_event(&mut self, position: &PhysicalPosition) { - self.position = glam::Vec2::new(position.x as f32, position.y as f32); + pub fn process_window_event(&mut self, event: &WindowEvent, virtual_input: &mut VirtualInput) { + match event { + WindowEvent::CursorMoved { position, .. } => { + self.position = glam::Vec2::new(position.x as f32, position.y as f32); + } + WindowEvent::MouseWheel { delta, .. } => { + self.wheel_delta += match delta { + MouseScrollDelta::PixelDelta(position) => { + glam::Vec2::new(position.x as f32, position.y as f32) + } + MouseScrollDelta::LineDelta(x, y) => glam::Vec2::new(*x as f32, *y as f32), + }; + } + WindowEvent::MouseInput { button, state, .. } => { + let key_state = self.mouse_button_state.get(button); + let new_key_state = process_new_element_state(key_state, *state); + if let Some(new_key_state) = new_key_state { + self.mouse_button_state.insert(*button, new_key_state); + virtual_input.update_mouse_button_binding(*button, new_key_state); + } + } + _ => {} + } } pub fn update(&mut self, virtual_input: &mut VirtualInput) { self.delta = self.position - self.old_position; self.old_position = self.position; - virtual_input.update_mouse_binding(&self.delta); + virtual_input.update_mouse_move_binding(&self.delta); + virtual_input.update_mouse_wheel_binding(&self.wheel_delta); + self.wheel_delta = glam::Vec2::ZERO; } } diff --git a/src/core/input/virtual_binding.rs b/src/core/input/virtual_binding.rs index 9209a78..c11dd48 100644 --- a/src/core/input/virtual_binding.rs +++ b/src/core/input/virtual_binding.rs @@ -1,16 +1,19 @@ -use winit::{event::AxisId, keyboard::PhysicalKey}; +use winit::{ + event::{AxisId, MouseButton}, + keyboard::PhysicalKey, +}; #[derive(Clone)] pub enum AxisDirection { - Positive, - Negative, + Normal, + Invert, } impl From<&AxisDirection> for f32 { fn from(direction: &AxisDirection) -> Self { match direction { - AxisDirection::Positive => 1.0, - AxisDirection::Negative => -1.0, + AxisDirection::Normal => 1.0, + AxisDirection::Invert => -1.0, } } } @@ -21,4 +24,7 @@ pub enum VirtualBinding { Axis(AxisId, AxisDirection, f32), // f32 deadzone MouseX(AxisDirection), MouseY(AxisDirection), + MouseWheelX(AxisDirection), + MouseWheelY(AxisDirection), + MouseButton(MouseButton, AxisDirection), } diff --git a/src/core/input/virtual_input.rs b/src/core/input/virtual_input.rs index f1ec4a8..875d66c 100644 --- a/src/core/input/virtual_input.rs +++ b/src/core/input/virtual_input.rs @@ -1,7 +1,10 @@ use std::{collections::HashMap, sync::Arc}; use egui_winit_vulkano::egui::mutex::Mutex; -use winit::{event::ElementState, keyboard::PhysicalKey}; +use winit::{ + event::{AxisId, ElementState, MouseButton}, + keyboard::PhysicalKey, +}; use super::{ virtual_binding::VirtualBinding, @@ -10,9 +13,15 @@ use super::{ #[derive(Default)] pub struct VirtualInput { + // Global states states: HashMap>>, + + // Per kind of input states to keep complexity low during state updates states_by_key: HashMap>>>, - mouse_states: Vec>>, + mouse_move_states: Vec>>, + mouse_wheel_states: Vec>>, + mouse_button_states: HashMap>>>, + axis_states: HashMap>>>, } impl std::fmt::Debug for VirtualInput { @@ -36,17 +45,50 @@ impl VirtualInput { } pub fn add_bindings(&mut self, value_name: String, new_bindings: Vec) { - add_bindings( - &mut self.states, - &mut self.states_by_key, - &mut self.mouse_states, - value_name, - new_bindings, - ); - } + let state = + self.states + .entry(value_name) + .or_insert(Arc::new(Mutex::new(VirtualInputState { + value: 0.0, + bindings: Vec::new(), + }))); - pub fn add_binding(&mut self, value_name: String, binding: VirtualBinding) { - self.add_bindings(value_name, vec![binding]); + for binding in &new_bindings { + match binding { + VirtualBinding::Keyboard(key, _) => { + self.states_by_key + .entry(*key) + .or_insert(Vec::new()) + .push(state.clone()); + } + VirtualBinding::MouseX(_) | VirtualBinding::MouseY(_) => { + self.mouse_move_states.push(state.clone()); + } + VirtualBinding::MouseButton(button, _) => { + self.mouse_button_states + .entry(*button) + .or_insert(Vec::new()) + .push(state.clone()); + } + VirtualBinding::MouseWheelX(_) | VirtualBinding::MouseWheelY(_) => { + self.mouse_wheel_states.push(state.clone()); + } + VirtualBinding::Axis(axis, _, _) => { + self.axis_states + .entry(*axis) + .or_insert(Vec::new()) + .push(state.clone()); + } + } + } + + state + .lock() + .bindings + .extend(new_bindings.iter().map(|b| VirtualBindingState { + value: 0.0, + binding: b.clone(), + })); } pub(super) fn update_key_binding(&mut self, key: PhysicalKey, key_state: ElementState) { @@ -57,54 +99,46 @@ impl VirtualInput { let mut state = state.lock(); state.update_from_key(key, key_state); } - } else { - log::trace!("{}", self.states_by_key.keys().len()); - log::warn!("No states found for key: {key:?}"); } } - pub(super) fn update_mouse_binding(&mut self, delta: &glam::Vec2) { - for state in &mut self.mouse_states { + pub(super) fn update_mouse_move_binding(&mut self, delta: &glam::Vec2) { + for state in &mut self.mouse_move_states { let mut state = state.lock(); state.update_from_mouse(delta); } } -} -fn add_bindings( - states: &mut HashMap>>, - states_by_key: &mut HashMap>>>, - mouse_states: &mut Vec>>, - value_name: String, - new_bindings: Vec, -) { - let state = states - .entry(value_name) - .or_insert(Arc::new(Mutex::new(VirtualInputState { - value: 0.0, - bindings: Vec::new(), - }))); - - for binding in &new_bindings { - match binding { - VirtualBinding::Keyboard(key, _) => { - states_by_key - .entry(*key) - .or_insert(Vec::new()) - .push(state.clone()); - } - VirtualBinding::MouseX(_) | VirtualBinding::MouseY(_) => { - mouse_states.push(state.clone()); - } - _ => {} + pub(super) fn update_mouse_wheel_binding(&mut self, delta: &glam::Vec2) { + for state in &mut self.mouse_wheel_states { + let mut state = state.lock(); + state.update_from_mouse_wheel(delta); } } - let mut state = state.lock(); - state - .bindings - .extend(new_bindings.iter().map(|b| VirtualBindingState { - value: 0.0, - binding: b.clone(), - })); + pub(super) fn update_mouse_button_binding( + &mut self, + button: MouseButton, + button_state: ElementState, + ) { + let states = self.mouse_button_states.get_mut(&button); + + if let Some(states) = states { + for state in states { + let mut state = state.lock(); + state.update_from_mouse_button(button, button_state); + } + } + } + + pub(super) fn update_axis_binding(&mut self, axis: AxisId, axis_state: f32) { + let states = self.axis_states.get_mut(&axis); + + if let Some(states) = states { + for state in states { + let mut state = state.lock(); + state.update_from_axis(axis, axis_state); + } + } + } } diff --git a/src/core/input/virtual_state.rs b/src/core/input/virtual_state.rs index 47f8b0d..93cf28f 100644 --- a/src/core/input/virtual_state.rs +++ b/src/core/input/virtual_state.rs @@ -1,6 +1,9 @@ -use winit::{event::ElementState, keyboard::PhysicalKey}; +use winit::{ + event::{AxisId, ElementState, MouseButton}, + keyboard::PhysicalKey, +}; -use super::virtual_binding::VirtualBinding; +use super::{process_axis_deadzone, virtual_binding::VirtualBinding}; pub struct VirtualBindingState { pub value: f32, @@ -46,4 +49,55 @@ impl VirtualInputState { } self.value = new_value; } + + pub fn update_from_mouse_wheel(&mut self, delta: &glam::Vec2) { + let mut new_value = 0.0; + for binding in &mut self.bindings { + match &binding.binding { + VirtualBinding::MouseWheelX(direction) => { + binding.value = f32::from(direction) * delta.x; + } + VirtualBinding::MouseWheelY(direction) => { + binding.value = f32::from(direction) * delta.y; + } + _ => {} + } + new_value += binding.value; + } + self.value = new_value; + } + + pub fn update_from_mouse_button(&mut self, button: MouseButton, button_state: ElementState) { + let mut new_value = 0.0; + for binding in &mut self.bindings { + match &binding.binding { + VirtualBinding::MouseButton(binding_button, direction) => { + if binding_button == &button { + if button_state == ElementState::Pressed { + binding.value = f32::from(direction); + } else { + binding.value = 0.0; + } + } + } + _ => {} + } + new_value += binding.value; + } + self.value = new_value; + } + + pub fn update_from_axis(&mut self, axis: AxisId, axis_state: f32) { + let mut new_value = 0.0; + for binding in &mut self.bindings { + if let VirtualBinding::Axis(binding_axis, direction, deadzone) = &binding.binding { + if binding_axis == &axis { + binding.value = + f32::from(direction) * process_axis_deadzone(axis_state, *deadzone); + } + } + new_value += binding.value; + } + self.value = new_value; + } } diff --git a/src/game/main_scene.rs b/src/game/main_scene.rs index a759478..1f27f0b 100644 --- a/src/game/main_scene.rs +++ b/src/game/main_scene.rs @@ -1,4 +1,5 @@ use crate::core::input::InputManager; +use crate::core::render::pipelines::triangle_pipeline::create_triangle_pipeline; use crate::core::render::primitives::camera::Camera; use crate::core::render::primitives::vertex::Vertex2D; use crate::core::render::render_context::RenderContext; @@ -10,10 +11,6 @@ use vulkano::buffer::Subbuffer; use vulkano::command_buffer::{AutoCommandBufferBuilder, PrimaryAutoCommandBuffer}; use vulkano::descriptor_set::{DescriptorSet, WriteDescriptorSet}; use vulkano::pipeline::{GraphicsPipeline, Pipeline, PipelineBindPoint}; -use winit::event::ElementState; -use winit::keyboard::{KeyCode, PhysicalKey}; - -use crate::core::render::pipelines::triangle_pipeline::create_triangle_pipeline; const VERTICES: [Vertex2D; 12] = [ // Triangle en haut à gauche diff --git a/src/main.rs b/src/main.rs index d671188..6a4984b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,11 +1,10 @@ -use core::input::{ - InputManager, - virtual_binding::{AxisDirection, VirtualBinding}, -}; +use core::input::{AxisDirection, InputManager, VirtualBinding}; +use std::collections::HashMap; use vulkano::device::{DeviceExtensions, DeviceFeatures}; use vulkano_util::context::{VulkanoConfig, VulkanoContext}; use winit::{ + event::MouseButton, event_loop::{ControlFlow, EventLoop}, keyboard::{KeyCode, PhysicalKey}, }; @@ -16,29 +15,50 @@ mod game; fn main() { env_logger::init(); - let mut input_manager = InputManager::default(); - input_manager.add_virtual_bindings( - "move_forward".to_string(), - vec![ - VirtualBinding::Keyboard(PhysicalKey::Code(KeyCode::KeyW), AxisDirection::Positive), - VirtualBinding::Keyboard(PhysicalKey::Code(KeyCode::KeyS), AxisDirection::Negative), - ], - ); - input_manager.add_virtual_bindings( - "move_right".to_string(), - vec![ - VirtualBinding::Keyboard(PhysicalKey::Code(KeyCode::KeyD), AxisDirection::Positive), - VirtualBinding::Keyboard(PhysicalKey::Code(KeyCode::KeyA), AxisDirection::Negative), - ], - ); - input_manager.add_virtual_bindings( - "mouse_x".to_string(), - vec![VirtualBinding::MouseX(AxisDirection::Positive)], - ); - input_manager.add_virtual_bindings( - "mouse_y".to_string(), - vec![VirtualBinding::MouseY(AxisDirection::Positive)], - ); + let input_manager = InputManager::new(HashMap::from([ + ( + "move_forward".to_string(), + vec![ + VirtualBinding::Keyboard(PhysicalKey::Code(KeyCode::KeyW), AxisDirection::Normal), + VirtualBinding::Keyboard(PhysicalKey::Code(KeyCode::KeyS), AxisDirection::Invert), + VirtualBinding::Axis(0, AxisDirection::Normal, 1.0), + ], + ), + ( + "move_right".to_string(), + vec![ + VirtualBinding::Keyboard(PhysicalKey::Code(KeyCode::KeyD), AxisDirection::Normal), + VirtualBinding::Keyboard(PhysicalKey::Code(KeyCode::KeyA), AxisDirection::Invert), + VirtualBinding::Axis(1, AxisDirection::Normal, 1.0), + ], + ), + ( + "mouse_x".to_string(), + vec![VirtualBinding::MouseX(AxisDirection::Normal)], + ), + ( + "mouse_y".to_string(), + vec![VirtualBinding::MouseY(AxisDirection::Normal)], + ), + ( + "mouse_wheel".to_string(), + vec![VirtualBinding::MouseWheelY(AxisDirection::Normal)], + ), + ( + "mouse_left".to_string(), + vec![VirtualBinding::MouseButton( + MouseButton::Left, + AxisDirection::Normal, + )], + ), + ( + "mouse_right".to_string(), + vec![VirtualBinding::MouseButton( + MouseButton::Right, + AxisDirection::Normal, + )], + ), + ])); let device_extensions = DeviceExtensions { khr_swapchain: true, From 5b0ab192078b1357acea720ebc858bcdf5c9eece Mon Sep 17 00:00:00 2001 From: Florian RICHER Date: Tue, 27 May 2025 19:44:21 +0200 Subject: [PATCH 045/105] input: Refactor all previous states --- src/core/input/cache.rs | 86 ++++++++++++++++++++++++++++++++ src/core/input/keyboard_state.rs | 25 ---------- src/core/input/mod.rs | 86 ++++++++++++++++---------------- src/core/input/mouse_state.rs | 49 ------------------ src/core/input/virtual_state.rs | 7 ++- 5 files changed, 135 insertions(+), 118 deletions(-) create mode 100644 src/core/input/cache.rs delete mode 100644 src/core/input/keyboard_state.rs delete mode 100644 src/core/input/mouse_state.rs diff --git a/src/core/input/cache.rs b/src/core/input/cache.rs new file mode 100644 index 0000000..b9d562c --- /dev/null +++ b/src/core/input/cache.rs @@ -0,0 +1,86 @@ +use std::{ + collections::HashMap, + hash::Hash, + ops::{Add, AddAssign, Sub}, +}; + +use winit::event::ElementState; + +pub struct CachedElementState { + cache: HashMap, +} + +impl Default for CachedElementState { + fn default() -> Self { + Self { + cache: HashMap::new(), + } + } +} + +impl CachedElementState { + pub fn set_key_state(&mut self, key: K, state: ElementState) -> Option { + let key_state = self.cache.get(&key); + let new_key_state = match key_state { + Some(old) => match state { + ElementState::Pressed => match old { + ElementState::Released => Some(ElementState::Pressed), + ElementState::Pressed => None, + }, + ElementState::Released => match old { + ElementState::Released => None, + ElementState::Pressed => Some(ElementState::Released), + }, + }, + None => match state { + ElementState::Pressed => Some(ElementState::Pressed), + ElementState::Released => Some(ElementState::Released), + }, + }; + if let Some(new_key_state) = new_key_state { + self.cache.insert(key, new_key_state); + } + new_key_state + } +} + +#[derive(Default)] +pub struct CachedMovement +where + T: Sub + Add + Default + Copy, +{ + pub old_value: Option, + pub value: T, +} + +impl CachedMovement +where + T: Sub + Add + Default + Copy, +{ + pub fn set_value(&mut self, value: T) { + self.value = value; + } + + pub fn reset(&mut self) -> T { + match self.old_value.as_ref() { + Some(old_value) => { + let diff = self.value - *old_value; + self.old_value = Some(self.value); + diff + } + None => { + self.old_value = Some(self.value); + T::default() + } + } + } +} + +impl AddAssign for CachedMovement +where + T: Add + Sub + Default + Copy, +{ + fn add_assign(&mut self, rhs: T) { + self.value = self.value + rhs; + } +} diff --git a/src/core/input/keyboard_state.rs b/src/core/input/keyboard_state.rs deleted file mode 100644 index f35e448..0000000 --- a/src/core/input/keyboard_state.rs +++ /dev/null @@ -1,25 +0,0 @@ -use egui_winit_vulkano::egui::ahash::HashMap; -use winit::{ - event::{ElementState, WindowEvent}, - keyboard::PhysicalKey, -}; - -use super::{process_new_element_state, virtual_input::VirtualInput}; - -#[derive(Debug, Default)] -pub struct KeyboardState { - key_states: HashMap, -} - -impl KeyboardState { - pub fn process_window_event(&mut self, event: &WindowEvent, virtual_input: &mut VirtualInput) { - if let WindowEvent::KeyboardInput { event, .. } = event { - let key_state = self.key_states.get(&event.physical_key); - let new_key_state = process_new_element_state(key_state, event.state); - if let Some(new_key_state) = new_key_state { - self.key_states.insert(event.physical_key, new_key_state); - virtual_input.update_key_binding(event.physical_key, new_key_state); - } - } - } -} diff --git a/src/core/input/mod.rs b/src/core/input/mod.rs index 00ebe18..08c1862 100644 --- a/src/core/input/mod.rs +++ b/src/core/input/mod.rs @@ -1,12 +1,13 @@ use std::collections::HashMap; -use keyboard_state::KeyboardState; -use mouse_state::MouseState; +use cache::{CachedElementState, CachedMovement}; use virtual_input::VirtualInput; -use winit::event::{ElementState, WindowEvent}; +use winit::{ + event::{MouseButton, MouseScrollDelta, WindowEvent}, + keyboard::PhysicalKey, +}; -mod keyboard_state; -mod mouse_state; +mod cache; mod virtual_binding; mod virtual_input; mod virtual_state; @@ -14,8 +15,10 @@ pub use virtual_binding::{AxisDirection, VirtualBinding}; #[derive(Default)] pub struct InputManager { - keyboard_state: KeyboardState, - mouse_state: MouseState, + keys_state: CachedElementState, + mouse_buttons_state: CachedElementState, + mouse_position_delta: CachedMovement, + mouse_wheel_delta: CachedMovement, virtual_input: VirtualInput, } @@ -41,18 +44,45 @@ impl InputManager { WindowEvent::AxisMotion { axis, value, .. } => { self.virtual_input.update_axis_binding(*axis, *value as f32); } - _ => { - self.keyboard_state - .process_window_event(event, &mut self.virtual_input); - self.mouse_state - .process_window_event(event, &mut self.virtual_input); + WindowEvent::KeyboardInput { event, .. } => { + let new_key_state = self + .keys_state + .set_key_state(event.physical_key, event.state); + if let Some(new_key_state) = new_key_state { + self.virtual_input + .update_key_binding(event.physical_key, new_key_state); + } } + WindowEvent::CursorMoved { position, .. } => { + self.mouse_position_delta + .set_value(glam::Vec2::new(position.x as f32, position.y as f32)); + } + WindowEvent::MouseInput { button, state, .. } => { + let new_mouse_button_state = + self.mouse_buttons_state.set_key_state(*button, *state); + if let Some(new_mouse_button_state) = new_mouse_button_state { + self.virtual_input + .update_mouse_button_binding(*button, new_mouse_button_state); + } + } + WindowEvent::MouseWheel { delta, .. } => { + self.mouse_wheel_delta += match delta { + MouseScrollDelta::PixelDelta(position) => { + glam::Vec2::new(position.x as f32, position.y as f32) + } + MouseScrollDelta::LineDelta(x, y) => glam::Vec2::new(*x as f32, *y as f32), + }; + } + _ => {} } } /// Updates deltas before running update pub fn update(&mut self) { - self.mouse_state.update(&mut self.virtual_input); + self.virtual_input + .update_mouse_move_binding(&self.mouse_position_delta.reset()); + self.virtual_input + .update_mouse_wheel_binding(&self.mouse_wheel_delta.reset()); } pub fn get_virtual_input_state(&self, value_name: &str) -> f32 { @@ -63,33 +93,3 @@ impl InputManager { self.virtual_input.add_bindings(value_name, bindings); } } - -/// Maps the old element state to the new element state. -/// if is changed, returns Some(new_state), otherwise returns None -#[inline] -fn process_new_element_state( - old: Option<&ElementState>, - new: ElementState, -) -> Option { - match old { - Some(old) => match new { - ElementState::Pressed => match old { - ElementState::Released => Some(ElementState::Pressed), - ElementState::Pressed => None, - }, - ElementState::Released => match old { - ElementState::Released => None, - ElementState::Pressed => Some(ElementState::Released), - }, - }, - None => match new { - ElementState::Pressed => Some(ElementState::Pressed), - ElementState::Released => Some(ElementState::Released), - }, - } -} - -#[inline] -fn process_axis_deadzone(value: f32, deadzone: f32) -> f32 { - if value.abs() < deadzone { 0.0 } else { value } -} diff --git a/src/core/input/mouse_state.rs b/src/core/input/mouse_state.rs deleted file mode 100644 index 084f43c..0000000 --- a/src/core/input/mouse_state.rs +++ /dev/null @@ -1,49 +0,0 @@ -use std::collections::HashMap; - -use winit::event::{ElementState, MouseButton, MouseScrollDelta, WindowEvent}; - -use super::{process_new_element_state, virtual_input::VirtualInput}; - -#[derive(Debug, Default)] -pub struct MouseState { - old_position: glam::Vec2, - position: glam::Vec2, - delta: glam::Vec2, - wheel_delta: glam::Vec2, - mouse_button_state: HashMap, -} - -impl MouseState { - pub fn process_window_event(&mut self, event: &WindowEvent, virtual_input: &mut VirtualInput) { - match event { - WindowEvent::CursorMoved { position, .. } => { - self.position = glam::Vec2::new(position.x as f32, position.y as f32); - } - WindowEvent::MouseWheel { delta, .. } => { - self.wheel_delta += match delta { - MouseScrollDelta::PixelDelta(position) => { - glam::Vec2::new(position.x as f32, position.y as f32) - } - MouseScrollDelta::LineDelta(x, y) => glam::Vec2::new(*x as f32, *y as f32), - }; - } - WindowEvent::MouseInput { button, state, .. } => { - let key_state = self.mouse_button_state.get(button); - let new_key_state = process_new_element_state(key_state, *state); - if let Some(new_key_state) = new_key_state { - self.mouse_button_state.insert(*button, new_key_state); - virtual_input.update_mouse_button_binding(*button, new_key_state); - } - } - _ => {} - } - } - - pub fn update(&mut self, virtual_input: &mut VirtualInput) { - self.delta = self.position - self.old_position; - self.old_position = self.position; - virtual_input.update_mouse_move_binding(&self.delta); - virtual_input.update_mouse_wheel_binding(&self.wheel_delta); - self.wheel_delta = glam::Vec2::ZERO; - } -} diff --git a/src/core/input/virtual_state.rs b/src/core/input/virtual_state.rs index 93cf28f..779b8ea 100644 --- a/src/core/input/virtual_state.rs +++ b/src/core/input/virtual_state.rs @@ -3,7 +3,7 @@ use winit::{ keyboard::PhysicalKey, }; -use super::{process_axis_deadzone, virtual_binding::VirtualBinding}; +use super::virtual_binding::VirtualBinding; pub struct VirtualBindingState { pub value: f32, @@ -101,3 +101,8 @@ impl VirtualInputState { self.value = new_value; } } + +#[inline] +fn process_axis_deadzone(value: f32, deadzone: f32) -> f32 { + if value.abs() < deadzone { 0.0 } else { value } +} From 29a4da5666c6ef8ea020afa93618af939df4b088 Mon Sep 17 00:00:00 2001 From: Florian RICHER Date: Tue, 27 May 2025 22:25:17 +0200 Subject: [PATCH 046/105] texture: First image load --- Cargo.lock | 1 + Cargo.toml | 2 + res/shaders/vertex.frag | 9 +- res/shaders/vertex.vert | 6 +- res/textures/wooden-crate.jpg | Bin 0 -> 49767 bytes src/core/render/mod.rs | 1 + .../render/pipelines/triangle_pipeline.rs | 46 +++++-- src/core/render/primitives/vertex.rs | 4 +- src/core/render/texture.rs | 113 ++++++++++++++++++ src/game/main_scene.rs | 106 ++++++++-------- src/main.rs | 4 +- 11 files changed, 220 insertions(+), 72 deletions(-) create mode 100644 res/textures/wooden-crate.jpg create mode 100644 src/core/render/texture.rs diff --git a/Cargo.lock b/Cargo.lock index d461d0b..54175c9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2110,6 +2110,7 @@ dependencies = [ "egui_winit_vulkano", "env_logger", "glam", + "image", "log", "thiserror 2.0.12", "vulkano", diff --git a/Cargo.toml b/Cargo.toml index 372b1a5..50be745 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,8 @@ vulkano-shaders = "0.35" vulkano-util = "0.35" egui_winit_vulkano = { version = "0.28" } +image = { version = "0.25", features = ["png", "jpeg"] } + # Math glam = { version = "0.30" } diff --git a/res/shaders/vertex.frag b/res/shaders/vertex.frag index 720d192..67831a9 100644 --- a/res/shaders/vertex.frag +++ b/res/shaders/vertex.frag @@ -1,9 +1,12 @@ #version 450 -layout (location = 0) in vec3 color; +layout (location = 0) in vec2 tex_coords; layout (location = 0) out vec4 f_color; +layout(set = 1, binding = 0) uniform sampler mySampler; +layout(set = 1, binding = 1) uniform texture2D myTexture; + void main() { - f_color = vec4(color, 1.0); -} \ No newline at end of file + f_color = texture(sampler2D(myTexture, mySampler), tex_coords); +} diff --git a/res/shaders/vertex.vert b/res/shaders/vertex.vert index bb1c261..0445e54 100644 --- a/res/shaders/vertex.vert +++ b/res/shaders/vertex.vert @@ -1,9 +1,9 @@ #version 450 layout (location = 0) in vec2 position; -layout (location = 1) in vec3 color; +layout (location = 1) in vec2 uv; -layout (location = 0) out vec3 fragColor; +layout (location = 0) out vec2 fragUv; layout (set = 0, binding = 0) uniform MVP { mat4 world; @@ -14,5 +14,5 @@ layout (set = 0, binding = 0) uniform MVP { void main() { mat4 worldview = uniforms.view * uniforms.world; gl_Position = uniforms.projection * worldview * vec4(position, 0.0, 1.0); - fragColor = color; + fragUv = uv; } diff --git a/res/textures/wooden-crate.jpg b/res/textures/wooden-crate.jpg new file mode 100644 index 0000000000000000000000000000000000000000..d1c87341ca55543742aeefa9145fea732c166468 GIT binary patch literal 49767 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::new(); - let mut descriptor_set_layout_binding = - DescriptorSetLayoutBinding::descriptor_type(DescriptorType::UniformBuffer); - descriptor_set_layout_binding.stages = ShaderStages::VERTEX; - bindings.insert(0, descriptor_set_layout_binding); + let vertex_bindings = BTreeMap::::from_iter([( + 0, + DescriptorSetLayoutBinding { + stages: ShaderStages::VERTEX, + ..DescriptorSetLayoutBinding::descriptor_type(DescriptorType::UniformBuffer) + }, + )]); + let fragment_bindings = BTreeMap::::from_iter([ + ( + 0, + DescriptorSetLayoutBinding { + stages: ShaderStages::FRAGMENT, + ..DescriptorSetLayoutBinding::descriptor_type(DescriptorType::Sampler) + }, + ), + ( + 1, + DescriptorSetLayoutBinding { + stages: ShaderStages::FRAGMENT, + ..DescriptorSetLayoutBinding::descriptor_type(DescriptorType::SampledImage) + }, + ), + ]); - let descriptor_set_layout = DescriptorSetLayoutCreateInfo { - bindings, + let vertex_descriptor_set_layout = DescriptorSetLayoutCreateInfo { + bindings: vertex_bindings, + ..Default::default() + }; + + let fragment_descriptor_set_layout = DescriptorSetLayoutCreateInfo { + bindings: fragment_bindings, ..Default::default() }; let create_info = PipelineDescriptorSetLayoutCreateInfo { - set_layouts: vec![descriptor_set_layout], + set_layouts: vec![vertex_descriptor_set_layout, fragment_descriptor_set_layout], flags: PipelineLayoutCreateFlags::default(), push_constant_ranges: vec![], } @@ -83,7 +106,10 @@ pub fn create_triangle_pipeline( GraphicsPipelineCreateInfo { stages: stages.into_iter().collect(), vertex_input_state: Some(vertex_input_state), - input_assembly_state: Some(InputAssemblyState::default()), + input_assembly_state: Some(InputAssemblyState { + topology: PrimitiveTopology::TriangleStrip, + ..Default::default() + }), viewport_state: Some(ViewportState::default()), rasterization_state: Some(RasterizationState::default()), multisample_state: Some(MultisampleState::default()), diff --git a/src/core/render/primitives/vertex.rs b/src/core/render/primitives/vertex.rs index 9bf133e..b35588c 100644 --- a/src/core/render/primitives/vertex.rs +++ b/src/core/render/primitives/vertex.rs @@ -12,8 +12,8 @@ pub struct Vertex2D { #[format(R32G32_SFLOAT)] pub position: [f32; 2], - #[format(R32G32B32_SFLOAT)] - pub color: [f32; 3], + #[format(R32G32_SFLOAT)] + pub uv: [f32; 2], } impl Vertex2D { diff --git a/src/core/render/texture.rs b/src/core/render/texture.rs new file mode 100644 index 0000000..680ddbf --- /dev/null +++ b/src/core/render/texture.rs @@ -0,0 +1,113 @@ +use std::{path::Path, sync::Arc}; + +use anyhow::Error; +use image::{DynamicImage, EncodableLayout}; +use vulkano::{ + buffer::{Buffer, BufferCreateInfo, BufferUsage}, + command_buffer::{AutoCommandBufferBuilder, CopyBufferToImageInfo, PrimaryAutoCommandBuffer}, + device::Device, + format::Format, + image::{ + Image, ImageCreateInfo, ImageType, ImageUsage, + sampler::{Filter, Sampler, SamplerAddressMode, SamplerCreateInfo}, + view::ImageView, + }, + memory::allocator::{AllocationCreateInfo, MemoryTypeFilter, StandardMemoryAllocator}, +}; + +pub struct Texture { + texture: Arc, + sampler: Arc, +} + +impl Texture { + fn new(texture: Arc, sampler: Arc) -> Self { + Self { texture, sampler } + } + + pub fn from_file( + device: &Arc, + memory_allocator: &Arc, + builder: &mut AutoCommandBufferBuilder, + path: &str, + ) -> Result { + let image = image::open(path)?; + Self::from_dynamic_image(device, memory_allocator, builder, image) + } + + pub fn from_bytes( + device: &Arc, + memory_allocator: &Arc, + builder: &mut AutoCommandBufferBuilder, + bytes: &[u8], + ) -> Result { + let image = image::load_from_memory(bytes)?; + Self::from_dynamic_image(device, memory_allocator, builder, image) + } + + pub fn from_dynamic_image( + device: &Arc, + memory_allocator: &Arc, + builder: &mut AutoCommandBufferBuilder, + image: DynamicImage, + ) -> Result { + let image_data = image.to_rgba8(); + let image_dimensions = image_data.dimensions(); + + let upload_buffer = Buffer::from_iter( + memory_allocator.clone(), + BufferCreateInfo { + usage: BufferUsage::TRANSFER_SRC, + ..Default::default() + }, + AllocationCreateInfo { + memory_type_filter: MemoryTypeFilter::PREFER_HOST + | MemoryTypeFilter::HOST_SEQUENTIAL_WRITE, + ..Default::default() + }, + image_data.to_vec(), + )?; + + let image = Image::new( + memory_allocator.clone(), + ImageCreateInfo { + image_type: ImageType::Dim2d, + format: Format::R8G8B8A8_SRGB, + extent: [image_dimensions.0 as u32, image_dimensions.1 as u32, 1], + array_layers: 1, + usage: ImageUsage::TRANSFER_DST | ImageUsage::SAMPLED, + ..Default::default() + }, + AllocationCreateInfo::default(), + )?; + + builder.copy_buffer_to_image(CopyBufferToImageInfo::buffer_image( + upload_buffer, + image.clone(), + ))?; + + let sampler = Sampler::new( + device.clone(), + SamplerCreateInfo { + mag_filter: Filter::Linear, + min_filter: Filter::Linear, + address_mode: [SamplerAddressMode::Repeat; 3], + ..Default::default() + }, + )?; + + let image_view = ImageView::new_default(image)?; + + log::trace!("Texture loaded with dimensions {:?}", image_dimensions); + + Ok(Self::new(image_view, sampler)) + } + + pub fn get_texture(&self) -> &Arc { + &self.texture + } + + pub fn get_sampler(&self) -> &Arc { + &self.sampler + } +} diff --git a/src/game/main_scene.rs b/src/game/main_scene.rs index 1f27f0b..c5a56a0 100644 --- a/src/game/main_scene.rs +++ b/src/game/main_scene.rs @@ -3,67 +3,35 @@ use crate::core::render::pipelines::triangle_pipeline::create_triangle_pipeline; use crate::core::render::primitives::camera::Camera; use crate::core::render::primitives::vertex::Vertex2D; use crate::core::render::render_context::RenderContext; +use crate::core::render::texture::Texture; use crate::core::scene::Scene; use crate::core::timer::Timer; use glam::{Mat4, Quat, Vec3}; use std::sync::Arc; use vulkano::buffer::Subbuffer; -use vulkano::command_buffer::{AutoCommandBufferBuilder, PrimaryAutoCommandBuffer}; +use vulkano::command_buffer::{ + AutoCommandBufferBuilder, CommandBufferUsage, PrimaryAutoCommandBuffer, + PrimaryCommandBufferAbstract, +}; use vulkano::descriptor_set::{DescriptorSet, WriteDescriptorSet}; use vulkano::pipeline::{GraphicsPipeline, Pipeline, PipelineBindPoint}; -const VERTICES: [Vertex2D; 12] = [ - // Triangle en haut à gauche +const VERTICES: [Vertex2D; 4] = [ Vertex2D { - position: [-0.5, -0.75], - color: [1.0, 0.0, 0.0], + position: [-0.5, -0.5], + uv: [0.0, 0.0], }, Vertex2D { - position: [-0.75, -0.25], - color: [0.0, 1.0, 0.0], + position: [-0.5, 0.5], + uv: [0.0, 1.0], }, Vertex2D { - position: [-0.25, -0.25], - color: [0.0, 0.0, 1.0], - }, - // Triangle en bas à gauche - Vertex2D { - position: [-0.5, 0.25], - color: [0.5, 0.5, 0.5], + position: [0.5, -0.5], + uv: [1.0, 0.0], }, Vertex2D { - position: [-0.75, 0.75], - color: [0.2, 0.8, 0.2], - }, - Vertex2D { - position: [-0.25, 0.75], - color: [0.8, 0.2, 0.2], - }, - // Triangle en haut à droite - Vertex2D { - position: [0.5, -0.75], - color: [1.0, 1.0, 0.0], - }, - Vertex2D { - position: [0.25, -0.25], - color: [0.0, 1.0, 1.0], - }, - Vertex2D { - position: [0.75, -0.25], - color: [1.0, 0.0, 1.0], - }, - // Triangle en bas à droite - Vertex2D { - position: [0.5, 0.25], - color: [0.1, 0.5, 0.8], - }, - Vertex2D { - position: [0.25, 0.75], - color: [0.8, 0.6, 0.1], - }, - Vertex2D { - position: [0.75, 0.75], - color: [0.3, 0.4, 0.6], + position: [0.5, 0.5], + uv: [1.0, 1.0], }, ]; @@ -71,6 +39,7 @@ pub struct MainSceneState { pipeline: Arc, vertex_buffer: Subbuffer<[Vertex2D]>, camera: Camera, + texture: Texture, } #[derive(Default)] @@ -105,11 +74,33 @@ impl Scene for MainScene { ), ); + let mut uploads = AutoCommandBufferBuilder::primary( + render_context.command_buffer_allocator().clone(), + render_context.graphics_queue().queue_family_index(), + CommandBufferUsage::OneTimeSubmit, + ) + .unwrap(); + + let texture = Texture::from_file( + render_context.device(), + render_context.memory_allocator(), + &mut uploads, + "res/textures/wooden-crate.jpg", + ) + .unwrap(); + + let _ = uploads + .build() + .unwrap() + .execute(render_context.graphics_queue().clone()) + .unwrap(); + self.state = Some(MainSceneState { pipeline, vertex_buffer, camera, - }) + texture, + }); } fn update( @@ -157,21 +148,32 @@ impl Scene for MainScene { ) { let state = self.state.as_ref().unwrap(); let vertex_count = state.vertex_buffer.len() as u32; - let instance_count = vertex_count / 3; + let instance_count = vertex_count / 4; - let layout = &state.pipeline.layout().set_layouts()[0]; + let layouts = state.pipeline.layout().set_layouts(); let uniform_buffer = state .camera .create_buffer(render_context.memory_allocator()) .unwrap(); - let descriptor_set = DescriptorSet::new( + let uniform_descriptor_set = DescriptorSet::new( render_context.descriptor_set_allocator().clone(), - layout.clone(), + layouts[0].clone(), [WriteDescriptorSet::buffer(0, uniform_buffer)], [], ) .unwrap(); + let texture_descriptor_set = DescriptorSet::new( + render_context.descriptor_set_allocator().clone(), + layouts[1].clone(), + [ + WriteDescriptorSet::sampler(0, state.texture.get_sampler().clone()), + WriteDescriptorSet::image_view(1, state.texture.get_texture().clone()), + ], + [], + ) + .unwrap(); + unsafe { builder .bind_pipeline_graphics(state.pipeline.clone()) @@ -180,7 +182,7 @@ impl Scene for MainScene { PipelineBindPoint::Graphics, state.pipeline.layout().clone(), 0, - descriptor_set, + vec![uniform_descriptor_set, texture_descriptor_set], ) .unwrap() .bind_vertex_buffers(0, state.vertex_buffer.clone()) diff --git a/src/main.rs b/src/main.rs index 6a4984b..7a4284c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -21,7 +21,7 @@ fn main() { vec![ VirtualBinding::Keyboard(PhysicalKey::Code(KeyCode::KeyW), AxisDirection::Normal), VirtualBinding::Keyboard(PhysicalKey::Code(KeyCode::KeyS), AxisDirection::Invert), - VirtualBinding::Axis(0, AxisDirection::Normal, 1.0), + VirtualBinding::Axis(0, AxisDirection::Normal, 0.0), ], ), ( @@ -29,7 +29,7 @@ fn main() { vec![ VirtualBinding::Keyboard(PhysicalKey::Code(KeyCode::KeyD), AxisDirection::Normal), VirtualBinding::Keyboard(PhysicalKey::Code(KeyCode::KeyA), AxisDirection::Invert), - VirtualBinding::Axis(1, AxisDirection::Normal, 1.0), + VirtualBinding::Axis(1, AxisDirection::Normal, 0.0), ], ), ( From a0fce9c08e9edb0bb92eee078044e54d3556f907 Mon Sep 17 00:00:00 2001 From: Florian RICHER Date: Tue, 27 May 2025 22:32:44 +0200 Subject: [PATCH 047/105] texture: Avoid clone image --- src/core/render/texture.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/core/render/texture.rs b/src/core/render/texture.rs index 680ddbf..17e9247 100644 --- a/src/core/render/texture.rs +++ b/src/core/render/texture.rs @@ -54,7 +54,7 @@ impl Texture { let image_data = image.to_rgba8(); let image_dimensions = image_data.dimensions(); - let upload_buffer = Buffer::from_iter( + let upload_buffer = Buffer::new_slice::( memory_allocator.clone(), BufferCreateInfo { usage: BufferUsage::TRANSFER_SRC, @@ -65,9 +65,14 @@ impl Texture { | MemoryTypeFilter::HOST_SEQUENTIAL_WRITE, ..Default::default() }, - image_data.to_vec(), + image_data.len() as u64, )?; + { + let buffer_data = &mut *upload_buffer.write()?; + buffer_data.copy_from_slice(image_data.as_bytes()); + } + let image = Image::new( memory_allocator.clone(), ImageCreateInfo { From 09bfe6fb488fc35ef70a1293af94d02b44f1ee7c Mon Sep 17 00:00:00 2001 From: Florian RICHER Date: Tue, 27 May 2025 23:31:23 +0200 Subject: [PATCH 048/105] camera: Try fix camera --- src/core/render/primitives/camera.rs | 45 ++++++++++++++---- src/game/main_scene.rs | 68 ++++++++++++++++++---------- 2 files changed, 79 insertions(+), 34 deletions(-) diff --git a/src/core/render/primitives/camera.rs b/src/core/render/primitives/camera.rs index efd61e5..16ae79b 100644 --- a/src/core/render/primitives/camera.rs +++ b/src/core/render/primitives/camera.rs @@ -1,6 +1,6 @@ use std::sync::Arc; -use glam::Mat4; +use glam::{EulerRot, Mat4, Quat, Vec3}; use vulkano::{ Validated, buffer::{AllocateBufferError, Subbuffer}, @@ -14,7 +14,8 @@ pub struct Camera { view: Mat4, projection: Mat4, - transform: Transform, + position: Vec3, + rotation: Vec3, } impl Camera { @@ -22,27 +23,53 @@ impl Camera { Self { view, projection, - transform: Transform::default(), + position: Vec3::ZERO, + rotation: Vec3::ZERO, } } - pub fn get_transform(&self) -> &Transform { - &self.transform + pub fn rotate(&mut self, rotation: Vec3) { + self.rotation += rotation; } - pub fn get_transform_mut(&mut self) -> &mut Transform { - &mut self.transform + pub fn translate(&mut self, translation: Vec3) { + self.position += translation; + } + + pub fn set_view(&mut self, view: Mat4) { + self.view = view; } pub fn set_projection(&mut self, projection: Mat4) { self.projection = projection; } + pub fn get_rotation(&self) -> Vec3 { + self.rotation + } + + pub fn set_rotation(&mut self, rotation: Vec3) { + self.rotation = rotation; + } + + pub fn get_position(&self) -> Vec3 { + self.position + } + + pub fn set_position(&mut self, position: Vec3) { + self.position = position; + } + pub fn create_buffer( &self, memory_allocator: &Arc, ) -> Result, Validated> { - MVP::new(&self.transform.get_mat4(), &self.view, &self.projection) - .into_buffer(memory_allocator) + let mut world_matrix = Mat4::IDENTITY; + world_matrix *= Mat4::from_quat(Quat::from_rotation_z(self.rotation.z)); + world_matrix *= Mat4::from_quat(Quat::from_rotation_y(self.rotation.y)); + world_matrix *= Mat4::from_quat(Quat::from_rotation_x(self.rotation.x)); + world_matrix *= Mat4::from_translation(-self.position); + + MVP::new(&world_matrix, &self.view, &self.projection).into_buffer(memory_allocator) } } diff --git a/src/game/main_scene.rs b/src/game/main_scene.rs index c5a56a0..3b0ed93 100644 --- a/src/game/main_scene.rs +++ b/src/game/main_scene.rs @@ -6,7 +6,7 @@ use crate::core::render::render_context::RenderContext; use crate::core::render::texture::Texture; use crate::core::scene::Scene; use crate::core::timer::Timer; -use glam::{Mat4, Quat, Vec3}; +use glam::{Mat4, Vec3}; use std::sync::Arc; use vulkano::buffer::Subbuffer; use vulkano::command_buffer::{ @@ -18,20 +18,20 @@ use vulkano::pipeline::{GraphicsPipeline, Pipeline, PipelineBindPoint}; const VERTICES: [Vertex2D; 4] = [ Vertex2D { - position: [-0.5, -0.5], + position: [0.0, 0.0], uv: [0.0, 0.0], }, Vertex2D { - position: [-0.5, 0.5], - uv: [0.0, 1.0], + position: [0.0, 5.0], + uv: [0.0, 0.5], }, Vertex2D { - position: [0.5, -0.5], + position: [10.0, 0.0], uv: [1.0, 0.0], }, Vertex2D { - position: [0.5, 0.5], - uv: [1.0, 1.0], + position: [10.0, 5.0], + uv: [1.0, 0.5], }, ]; @@ -40,6 +40,7 @@ pub struct MainSceneState { vertex_buffer: Subbuffer<[Vertex2D]>, camera: Camera, texture: Texture, + speed: f32, } #[derive(Default)] @@ -60,7 +61,7 @@ impl Scene for MainScene { Vertex2D::create_buffer(Vec::from_iter(VERTICES), render_context.memory_allocator()) .unwrap(); - let camera = Camera::new( + let mut camera = Camera::new( Mat4::look_at_rh( Vec3::new(0.3, 0.3, 1.0), Vec3::new(0.0, 0.0, 0.0), @@ -73,6 +74,7 @@ impl Scene for MainScene { 100.0, ), ); + camera.set_position(Vec3::new(-10.0, 0.0, -10.0)); let mut uploads = AutoCommandBufferBuilder::primary( render_context.command_buffer_allocator().clone(), @@ -100,6 +102,7 @@ impl Scene for MainScene { vertex_buffer, camera, texture, + speed: 50.0, }); } @@ -111,27 +114,42 @@ impl Scene for MainScene { ) { let state = self.state.as_mut().unwrap(); - let speed = 50.0 * timer.delta_time(); + state.speed += input_manager.get_virtual_input_state("mouse_wheel") * 10.0; - let mut rot = Quat::default(); - rot *= Quat::from_rotation_y( - input_manager.get_virtual_input_state("mouse_x") * speed.to_radians(), - ); - rot *= Quat::from_rotation_x( - input_manager.get_virtual_input_state("mouse_y") * speed.to_radians(), - ); - state.camera.get_transform_mut().rotate(rot); + let speed = state.speed * timer.delta_time(); - let translation_x = - input_manager.get_virtual_input_state("move_right") * timer.delta_time() * speed; + state.camera.rotate(Vec3::new( + (input_manager.get_virtual_input_state("mouse_y") * 50.0 * timer.delta_time()) + .to_radians(), + (input_manager.get_virtual_input_state("mouse_x") * 50.0 * timer.delta_time()) + .to_radians(), + 0.0, + )); - let translation_z = - input_manager.get_virtual_input_state("move_forward") * timer.delta_time() * speed; + if state.camera.get_rotation().x > 89.0 { + state + .camera + .set_rotation(Vec3::new(89.0, state.camera.get_rotation().y, 0.0)); + } - state - .camera - .get_transform_mut() - .translate(Vec3::new(translation_x, 0.0, translation_z)); + if state.camera.get_rotation().x < -89.0 { + state + .camera + .set_rotation(Vec3::new(-89.0, state.camera.get_rotation().y, 0.0)); + } + + let rotation = state.camera.get_rotation(); + let mut translation = Vec3::ZERO; + + let tx = input_manager.get_virtual_input_state("move_right") * timer.delta_time() * speed; + translation.x += tx * (rotation.y).to_radians().cos(); + translation.z += tx * (rotation.y).to_radians().sin(); + + let ty = input_manager.get_virtual_input_state("move_forward") * timer.delta_time() * speed; + translation.x += ty * (rotation.y + 90.0).to_radians().cos(); + translation.z += ty * (rotation.y + 90.0).to_radians().sin(); + + state.camera.translate(translation); state.camera.set_projection(Mat4::perspective_rh_gl( std::f32::consts::FRAC_PI_2, From fbb1493b45783e73166392034e6a52eff0a9a7a9 Mon Sep 17 00:00:00 2001 From: Florian RICHER Date: Wed, 28 May 2025 13:41:13 +0200 Subject: [PATCH 049/105] Fixes --- src/core/app.rs | 13 +++- src/core/input/mod.rs | 15 ++-- .../render/pipelines/triangle_pipeline.rs | 1 + src/core/render/primitives/camera.rs | 74 +++++++++++-------- src/game/main_scene.rs | 41 +--------- 5 files changed, 68 insertions(+), 76 deletions(-) diff --git a/src/core/app.rs b/src/core/app.rs index d14b4c5..e05d971 100644 --- a/src/core/app.rs +++ b/src/core/app.rs @@ -19,7 +19,7 @@ use vulkano_util::context::VulkanoContext; use vulkano_util::renderer::VulkanoWindowRenderer; use vulkano_util::window::{VulkanoWindows, WindowDescriptor}; use winit::application::ApplicationHandler; -use winit::event::WindowEvent; +use winit::event::{DeviceEvent, WindowEvent}; use winit::event_loop::ActiveEventLoop; use winit::window::WindowId; @@ -76,6 +76,8 @@ impl ApplicationHandler for App { width: 800.0, height: 600.0, present_mode: PresentMode::Fifo, + cursor_visible: false, + cursor_locked: true, ..Default::default() }, |_| {}, @@ -101,6 +103,15 @@ impl ApplicationHandler for App { .load_scene(Box::new(MainScene::default())); } + fn device_event( + &mut self, + _event_loop: &ActiveEventLoop, + _device_id: winit::event::DeviceId, + event: winit::event::DeviceEvent, + ) { + self.input_manager.process_device_event(&event); + } + fn window_event(&mut self, event_loop: &ActiveEventLoop, id: WindowId, event: WindowEvent) { let renderer = Arc::get_mut(&mut self.vulkano_windows) .unwrap() diff --git a/src/core/input/mod.rs b/src/core/input/mod.rs index 08c1862..227a5a1 100644 --- a/src/core/input/mod.rs +++ b/src/core/input/mod.rs @@ -3,7 +3,7 @@ use std::collections::HashMap; use cache::{CachedElementState, CachedMovement}; use virtual_input::VirtualInput; use winit::{ - event::{MouseButton, MouseScrollDelta, WindowEvent}, + event::{DeviceEvent, MouseButton, MouseScrollDelta, WindowEvent}, keyboard::PhysicalKey, }; @@ -39,6 +39,15 @@ impl InputManager { input_manager } + pub fn process_device_event(&mut self, event: &DeviceEvent) { + match event { + DeviceEvent::MouseMotion { delta, .. } => { + self.mouse_position_delta += glam::Vec2::new(delta.0 as f32, delta.1 as f32); + } + _ => {} + } + } + pub fn process_window_event(&mut self, event: &WindowEvent) { match event { WindowEvent::AxisMotion { axis, value, .. } => { @@ -53,10 +62,6 @@ impl InputManager { .update_key_binding(event.physical_key, new_key_state); } } - WindowEvent::CursorMoved { position, .. } => { - self.mouse_position_delta - .set_value(glam::Vec2::new(position.x as f32, position.y as f32)); - } WindowEvent::MouseInput { button, state, .. } => { let new_mouse_button_state = self.mouse_buttons_state.set_key_state(*button, *state); diff --git a/src/core/render/pipelines/triangle_pipeline.rs b/src/core/render/pipelines/triangle_pipeline.rs index 43d00d5..8893f7e 100644 --- a/src/core/render/pipelines/triangle_pipeline.rs +++ b/src/core/render/pipelines/triangle_pipeline.rs @@ -46,6 +46,7 @@ pub fn create_triangle_pipeline( ) -> Result, Box> { let (vs, fs) = load_shaders(device)?; let vertex_input_state = Vertex2D::per_vertex().definition(&vs)?; + log::trace!("vertex_input_state: {:#?}", vertex_input_state); let stages = [ PipelineShaderStageCreateInfo::new(vs), diff --git a/src/core/render/primitives/camera.rs b/src/core/render/primitives/camera.rs index 16ae79b..c322c7c 100644 --- a/src/core/render/primitives/camera.rs +++ b/src/core/render/primitives/camera.rs @@ -1,13 +1,15 @@ -use std::sync::Arc; +use std::{f32::consts::PI, sync::Arc}; -use glam::{EulerRot, Mat4, Quat, Vec3}; +use glam::{Mat4, Quat, Vec3}; use vulkano::{ Validated, buffer::{AllocateBufferError, Subbuffer}, memory::allocator::StandardMemoryAllocator, }; -use super::{mvp::MVP, transform::Transform}; +use crate::core::{input::InputManager, timer::Timer}; + +use super::mvp::MVP; #[derive(Default)] pub struct Camera { @@ -28,47 +30,57 @@ impl Camera { } } - pub fn rotate(&mut self, rotation: Vec3) { - self.rotation += rotation; - } + pub fn update( + &mut self, + input_manager: &InputManager, + timer: &Timer, + movement_speed: f32, + camera_sensitivity: f32, + ) { + // Process camera rotation + let camera_delta = camera_sensitivity * timer.delta_time(); + self.rotation += Vec3::new( + -(input_manager.get_virtual_input_state("mouse_y") * camera_delta).to_radians(), + (input_manager.get_virtual_input_state("mouse_x") * camera_delta).to_radians(), + 0.0, + ); - pub fn translate(&mut self, translation: Vec3) { - self.position += translation; - } + if self.rotation.x > 90.0 { + self.rotation = Vec3::new(90.0, self.rotation.y, 0.0); + } - pub fn set_view(&mut self, view: Mat4) { - self.view = view; + if self.rotation.x < -90.0 { + self.rotation = Vec3::new(-90.0, self.rotation.y, 0.0); + } + + let movement_delta = movement_speed * timer.delta_time(); + + let tx = input_manager.get_virtual_input_state("move_right") * movement_delta; + let tz = input_manager.get_virtual_input_state("move_forward") * movement_delta; + + self.position.x += tx * (self.rotation.y).cos(); + self.position.z += tx * (self.rotation.y).sin(); + + self.position.x += tz * (self.rotation.y + PI / 2.0).cos(); + self.position.z += tz * (self.rotation.y + PI / 2.0).sin(); } pub fn set_projection(&mut self, projection: Mat4) { self.projection = projection; } - pub fn get_rotation(&self) -> Vec3 { - self.rotation - } - - pub fn set_rotation(&mut self, rotation: Vec3) { - self.rotation = rotation; - } - - pub fn get_position(&self) -> Vec3 { - self.position - } - - pub fn set_position(&mut self, position: Vec3) { - self.position = position; - } - pub fn create_buffer( &self, memory_allocator: &Arc, ) -> Result, Validated> { let mut world_matrix = Mat4::IDENTITY; - world_matrix *= Mat4::from_quat(Quat::from_rotation_z(self.rotation.z)); - world_matrix *= Mat4::from_quat(Quat::from_rotation_y(self.rotation.y)); - world_matrix *= Mat4::from_quat(Quat::from_rotation_x(self.rotation.x)); - world_matrix *= Mat4::from_translation(-self.position); + world_matrix *= Mat4::from_quat(Quat::from_euler( + glam::EulerRot::XYX, + self.rotation.x, + self.rotation.y, + self.rotation.z, + )); + world_matrix *= Mat4::from_translation(self.position); MVP::new(&world_matrix, &self.view, &self.projection).into_buffer(memory_allocator) } diff --git a/src/game/main_scene.rs b/src/game/main_scene.rs index 3b0ed93..38c5bc5 100644 --- a/src/game/main_scene.rs +++ b/src/game/main_scene.rs @@ -61,7 +61,7 @@ impl Scene for MainScene { Vertex2D::create_buffer(Vec::from_iter(VERTICES), render_context.memory_allocator()) .unwrap(); - let mut camera = Camera::new( + let camera = Camera::new( Mat4::look_at_rh( Vec3::new(0.3, 0.3, 1.0), Vec3::new(0.0, 0.0, 0.0), @@ -74,7 +74,6 @@ impl Scene for MainScene { 100.0, ), ); - camera.set_position(Vec3::new(-10.0, 0.0, -10.0)); let mut uploads = AutoCommandBufferBuilder::primary( render_context.command_buffer_allocator().clone(), @@ -113,43 +112,7 @@ impl Scene for MainScene { timer: &Timer, ) { let state = self.state.as_mut().unwrap(); - - state.speed += input_manager.get_virtual_input_state("mouse_wheel") * 10.0; - - let speed = state.speed * timer.delta_time(); - - state.camera.rotate(Vec3::new( - (input_manager.get_virtual_input_state("mouse_y") * 50.0 * timer.delta_time()) - .to_radians(), - (input_manager.get_virtual_input_state("mouse_x") * 50.0 * timer.delta_time()) - .to_radians(), - 0.0, - )); - - if state.camera.get_rotation().x > 89.0 { - state - .camera - .set_rotation(Vec3::new(89.0, state.camera.get_rotation().y, 0.0)); - } - - if state.camera.get_rotation().x < -89.0 { - state - .camera - .set_rotation(Vec3::new(-89.0, state.camera.get_rotation().y, 0.0)); - } - - let rotation = state.camera.get_rotation(); - let mut translation = Vec3::ZERO; - - let tx = input_manager.get_virtual_input_state("move_right") * timer.delta_time() * speed; - translation.x += tx * (rotation.y).to_radians().cos(); - translation.z += tx * (rotation.y).to_radians().sin(); - - let ty = input_manager.get_virtual_input_state("move_forward") * timer.delta_time() * speed; - translation.x += ty * (rotation.y + 90.0).to_radians().cos(); - translation.z += ty * (rotation.y + 90.0).to_radians().sin(); - - state.camera.translate(translation); + state.camera.update(input_manager, timer, state.speed, 10.0); state.camera.set_projection(Mat4::perspective_rh_gl( std::f32::consts::FRAC_PI_2, From 122f577a26db1273481171f946cadf63f1757538 Mon Sep 17 00:00:00 2001 From: Florian RICHER Date: Wed, 28 May 2025 13:47:10 +0200 Subject: [PATCH 050/105] texture: Let compiler type inference --- src/core/render/texture.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/render/texture.rs b/src/core/render/texture.rs index 17e9247..ddf88e5 100644 --- a/src/core/render/texture.rs +++ b/src/core/render/texture.rs @@ -54,7 +54,7 @@ impl Texture { let image_data = image.to_rgba8(); let image_dimensions = image_data.dimensions(); - let upload_buffer = Buffer::new_slice::( + let upload_buffer = Buffer::new_slice( memory_allocator.clone(), BufferCreateInfo { usage: BufferUsage::TRANSFER_SRC, @@ -70,7 +70,7 @@ impl Texture { { let buffer_data = &mut *upload_buffer.write()?; - buffer_data.copy_from_slice(image_data.as_bytes()); + buffer_data.copy_from_slice(image_data.as_raw()); } let image = Image::new( From 131811a539723873ab4ff78da75c2f1e53e64b0d Mon Sep 17 00:00:00 2001 From: Florian RICHER Date: Wed, 28 May 2025 22:39:56 +0200 Subject: [PATCH 051/105] pipeline: Refactor square pipeline + add support of indexes --- src/core/app.rs | 2 +- src/core/render/mod.rs | 1 - src/core/render/pipelines/mod.rs | 1 - .../render/pipelines/triangle_pipeline.rs | 140 ---------- src/core/render/primitives/mod.rs | 2 +- src/game/assets/mod.rs | 1 + src/game/assets/square.rs | 247 ++++++++++++++++++ src/game/main_scene.rs | 92 ++----- src/game/mod.rs | 1 + 9 files changed, 270 insertions(+), 217 deletions(-) delete mode 100644 src/core/render/pipelines/mod.rs delete mode 100644 src/core/render/pipelines/triangle_pipeline.rs create mode 100644 src/game/assets/mod.rs create mode 100644 src/game/assets/square.rs diff --git a/src/core/app.rs b/src/core/app.rs index e05d971..bcdb82d 100644 --- a/src/core/app.rs +++ b/src/core/app.rs @@ -19,7 +19,7 @@ use vulkano_util::context::VulkanoContext; use vulkano_util::renderer::VulkanoWindowRenderer; use vulkano_util::window::{VulkanoWindows, WindowDescriptor}; use winit::application::ApplicationHandler; -use winit::event::{DeviceEvent, WindowEvent}; +use winit::event::WindowEvent; use winit::event_loop::ActiveEventLoop; use winit::window::WindowId; diff --git a/src/core/render/mod.rs b/src/core/render/mod.rs index 0a718e4..a50c5da 100644 --- a/src/core/render/mod.rs +++ b/src/core/render/mod.rs @@ -1,4 +1,3 @@ -pub mod pipelines; pub mod primitives; pub mod render_context; pub mod texture; diff --git a/src/core/render/pipelines/mod.rs b/src/core/render/pipelines/mod.rs deleted file mode 100644 index e5f30a7..0000000 --- a/src/core/render/pipelines/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod triangle_pipeline; diff --git a/src/core/render/pipelines/triangle_pipeline.rs b/src/core/render/pipelines/triangle_pipeline.rs deleted file mode 100644 index 8893f7e..0000000 --- a/src/core/render/pipelines/triangle_pipeline.rs +++ /dev/null @@ -1,140 +0,0 @@ -use std::collections::BTreeMap; -use std::error::Error; -use std::sync::Arc; -use vulkano::descriptor_set::layout::{ - DescriptorSetLayoutBinding, DescriptorSetLayoutCreateInfo, DescriptorType, -}; -use vulkano::device::Device; -use vulkano::format::Format; -use vulkano::pipeline::graphics::GraphicsPipelineCreateInfo; -use vulkano::pipeline::graphics::color_blend::{ColorBlendAttachmentState, ColorBlendState}; -use vulkano::pipeline::graphics::input_assembly::{InputAssemblyState, PrimitiveTopology}; -use vulkano::pipeline::graphics::multisample::MultisampleState; -use vulkano::pipeline::graphics::rasterization::RasterizationState; -use vulkano::pipeline::graphics::subpass::PipelineRenderingCreateInfo; -use vulkano::pipeline::graphics::vertex_input::{Vertex, VertexDefinition}; -use vulkano::pipeline::graphics::viewport::ViewportState; -use vulkano::pipeline::layout::{PipelineDescriptorSetLayoutCreateInfo, PipelineLayoutCreateFlags}; -use vulkano::pipeline::{ - DynamicState, GraphicsPipeline, PipelineLayout, PipelineShaderStageCreateInfo, -}; -use vulkano::shader::{EntryPoint, ShaderStages}; - -use crate::core::render::primitives::vertex::Vertex2D; - -pub mod shaders { - pub mod vs { - vulkano_shaders::shader! { - ty: "vertex", - path: r"res/shaders/vertex.vert", - generate_structs: false, - } - } - - pub mod fs { - vulkano_shaders::shader! { - ty: "fragment", - path: r"res/shaders/vertex.frag", - generate_structs: false, - } - } -} - -pub fn create_triangle_pipeline( - device: &Arc, - swapchain_format: Format, -) -> Result, Box> { - let (vs, fs) = load_shaders(device)?; - let vertex_input_state = Vertex2D::per_vertex().definition(&vs)?; - log::trace!("vertex_input_state: {:#?}", vertex_input_state); - - let stages = [ - PipelineShaderStageCreateInfo::new(vs), - PipelineShaderStageCreateInfo::new(fs), - ]; - - let vertex_bindings = BTreeMap::::from_iter([( - 0, - DescriptorSetLayoutBinding { - stages: ShaderStages::VERTEX, - ..DescriptorSetLayoutBinding::descriptor_type(DescriptorType::UniformBuffer) - }, - )]); - let fragment_bindings = BTreeMap::::from_iter([ - ( - 0, - DescriptorSetLayoutBinding { - stages: ShaderStages::FRAGMENT, - ..DescriptorSetLayoutBinding::descriptor_type(DescriptorType::Sampler) - }, - ), - ( - 1, - DescriptorSetLayoutBinding { - stages: ShaderStages::FRAGMENT, - ..DescriptorSetLayoutBinding::descriptor_type(DescriptorType::SampledImage) - }, - ), - ]); - - let vertex_descriptor_set_layout = DescriptorSetLayoutCreateInfo { - bindings: vertex_bindings, - ..Default::default() - }; - - let fragment_descriptor_set_layout = DescriptorSetLayoutCreateInfo { - bindings: fragment_bindings, - ..Default::default() - }; - - let create_info = PipelineDescriptorSetLayoutCreateInfo { - set_layouts: vec![vertex_descriptor_set_layout, fragment_descriptor_set_layout], - flags: PipelineLayoutCreateFlags::default(), - push_constant_ranges: vec![], - } - .into_pipeline_layout_create_info(device.clone())?; - - let layout = PipelineLayout::new(device.clone(), create_info)?; - - let subpass = PipelineRenderingCreateInfo { - color_attachment_formats: vec![Some(swapchain_format)], - ..Default::default() - }; - - let pipeline = GraphicsPipeline::new( - device.clone(), - None, - GraphicsPipelineCreateInfo { - stages: stages.into_iter().collect(), - vertex_input_state: Some(vertex_input_state), - input_assembly_state: Some(InputAssemblyState { - topology: PrimitiveTopology::TriangleStrip, - ..Default::default() - }), - viewport_state: Some(ViewportState::default()), - rasterization_state: Some(RasterizationState::default()), - multisample_state: Some(MultisampleState::default()), - color_blend_state: Some(ColorBlendState::with_attachment_states( - subpass.color_attachment_formats.len() as u32, - ColorBlendAttachmentState::default(), - )), - dynamic_state: [DynamicState::Viewport].into_iter().collect(), - subpass: Some(subpass.into()), - ..GraphicsPipelineCreateInfo::layout(layout) - }, - )?; - - Ok(pipeline) -} - -fn load_shaders(device: &Arc) -> Result<(EntryPoint, EntryPoint), Box> { - let vs = shaders::vs::load(device.clone())? - .entry_point("main") - .ok_or("Failed find main entry point of vertex shader".to_string())?; - - let fs = shaders::fs::load(device.clone())? - .entry_point("main") - .ok_or("Failed find main entry point of fragment shader".to_string())?; - - Ok((vs, fs)) -} diff --git a/src/core/render/primitives/mod.rs b/src/core/render/primitives/mod.rs index fd0b762..606c12d 100644 --- a/src/core/render/primitives/mod.rs +++ b/src/core/render/primitives/mod.rs @@ -1,4 +1,4 @@ pub mod camera; -mod mvp; +pub mod mvp; pub mod transform; pub mod vertex; diff --git a/src/game/assets/mod.rs b/src/game/assets/mod.rs new file mode 100644 index 0000000..d793a66 --- /dev/null +++ b/src/game/assets/mod.rs @@ -0,0 +1 @@ +pub mod square; diff --git a/src/game/assets/square.rs b/src/game/assets/square.rs new file mode 100644 index 0000000..3dba163 --- /dev/null +++ b/src/game/assets/square.rs @@ -0,0 +1,247 @@ +use std::{collections::BTreeMap, error::Error, sync::Arc}; + +use vulkano::{ + buffer::{Buffer, BufferCreateInfo, BufferUsage, Subbuffer}, + command_buffer::{AutoCommandBufferBuilder, PrimaryAutoCommandBuffer}, + descriptor_set::{ + DescriptorSet, WriteDescriptorSet, + allocator::StandardDescriptorSetAllocator, + layout::{DescriptorSetLayoutBinding, DescriptorSetLayoutCreateInfo, DescriptorType}, + }, + device::Device, + format::Format, + memory::allocator::{AllocationCreateInfo, MemoryTypeFilter, StandardMemoryAllocator}, + pipeline::{ + DynamicState, GraphicsPipeline, Pipeline, PipelineBindPoint, PipelineLayout, + PipelineShaderStageCreateInfo, + graphics::{ + GraphicsPipelineCreateInfo, + color_blend::{ColorBlendAttachmentState, ColorBlendState}, + input_assembly::{InputAssemblyState, PrimitiveTopology}, + multisample::MultisampleState, + rasterization::RasterizationState, + subpass::PipelineRenderingCreateInfo, + vertex_input::{Vertex, VertexDefinition}, + viewport::ViewportState, + }, + layout::{PipelineDescriptorSetLayoutCreateInfo, PipelineLayoutCreateFlags}, + }, + shader::ShaderStages, +}; + +use crate::core::render::{ + primitives::{mvp::MVP, vertex::Vertex2D}, + texture::Texture, +}; + +const VERTICES: [Vertex2D; 4] = [ + Vertex2D { + position: [0.0, 0.0], + uv: [0.0, 0.0], + }, + Vertex2D { + position: [0.0, 5.0], + uv: [0.0, 0.5], + }, + Vertex2D { + position: [10.0, 0.0], + uv: [1.0, 0.0], + }, + Vertex2D { + position: [10.0, 5.0], + uv: [1.0, 0.5], + }, +]; + +const INDICES: [u32; 6] = [0, 1, 2, 2, 1, 3]; + +pub mod shaders { + pub mod vs { + vulkano_shaders::shader! { + ty: "vertex", + path: r"res/shaders/vertex.vert", + generate_structs: false, + } + } + + pub mod fs { + vulkano_shaders::shader! { + ty: "fragment", + path: r"res/shaders/vertex.frag", + generate_structs: false, + } + } +} + +pub struct Square { + vertex_buffer: Subbuffer<[Vertex2D]>, + index_buffer: Subbuffer<[u32]>, + pipeline: Arc, +} + +impl Square { + pub fn new( + device: &Arc, + memory_allocator: &Arc, + swapchain_format: Format, + ) -> Result> { + let vertex_buffer = Buffer::from_iter( + memory_allocator.clone(), + BufferCreateInfo { + usage: BufferUsage::VERTEX_BUFFER, + ..Default::default() + }, + AllocationCreateInfo { + memory_type_filter: MemoryTypeFilter::PREFER_DEVICE + | MemoryTypeFilter::HOST_SEQUENTIAL_WRITE, + ..Default::default() + }, + Vec::from_iter(VERTICES), + )?; + let index_buffer = Buffer::from_iter( + memory_allocator.clone(), + BufferCreateInfo { + usage: BufferUsage::INDEX_BUFFER, + ..Default::default() + }, + AllocationCreateInfo { + memory_type_filter: MemoryTypeFilter::PREFER_DEVICE + | MemoryTypeFilter::HOST_SEQUENTIAL_WRITE, + ..Default::default() + }, + Vec::from_iter(INDICES), + )?; + + let vs = shaders::vs::load(device.clone())? + .entry_point("main") + .ok_or("Failed find main entry point of vertex shader".to_string())?; + + let fs = shaders::fs::load(device.clone())? + .entry_point("main") + .ok_or("Failed find main entry point of fragment shader".to_string())?; + + let vertex_input_state = Vertex2D::per_vertex().definition(&vs)?; + + let stages = [ + PipelineShaderStageCreateInfo::new(vs), + PipelineShaderStageCreateInfo::new(fs), + ]; + + let vertex_bindings = BTreeMap::::from_iter([( + 0, + DescriptorSetLayoutBinding { + stages: ShaderStages::VERTEX, + ..DescriptorSetLayoutBinding::descriptor_type(DescriptorType::UniformBuffer) + }, + )]); + let fragment_bindings = BTreeMap::::from_iter([ + ( + 0, + DescriptorSetLayoutBinding { + stages: ShaderStages::FRAGMENT, + ..DescriptorSetLayoutBinding::descriptor_type(DescriptorType::Sampler) + }, + ), + ( + 1, + DescriptorSetLayoutBinding { + stages: ShaderStages::FRAGMENT, + ..DescriptorSetLayoutBinding::descriptor_type(DescriptorType::SampledImage) + }, + ), + ]); + + let vertex_descriptor_set_layout = DescriptorSetLayoutCreateInfo { + bindings: vertex_bindings, + ..Default::default() + }; + + let fragment_descriptor_set_layout = DescriptorSetLayoutCreateInfo { + bindings: fragment_bindings, + ..Default::default() + }; + + let create_info = PipelineDescriptorSetLayoutCreateInfo { + set_layouts: vec![vertex_descriptor_set_layout, fragment_descriptor_set_layout], + flags: PipelineLayoutCreateFlags::default(), + push_constant_ranges: vec![], + } + .into_pipeline_layout_create_info(device.clone())?; + + let layout = PipelineLayout::new(device.clone(), create_info)?; + + let subpass = PipelineRenderingCreateInfo { + color_attachment_formats: vec![Some(swapchain_format)], + ..Default::default() + }; + + let pipeline = GraphicsPipeline::new( + device.clone(), + None, + GraphicsPipelineCreateInfo { + stages: stages.into_iter().collect(), + vertex_input_state: Some(vertex_input_state), + input_assembly_state: Some(InputAssemblyState::default()), + viewport_state: Some(ViewportState::default()), + rasterization_state: Some(RasterizationState::default()), + multisample_state: Some(MultisampleState::default()), + color_blend_state: Some(ColorBlendState::with_attachment_states( + subpass.color_attachment_formats.len() as u32, + ColorBlendAttachmentState::default(), + )), + dynamic_state: [DynamicState::Viewport].into_iter().collect(), + subpass: Some(subpass.into()), + ..GraphicsPipelineCreateInfo::layout(layout) + }, + )?; + + Ok(Self { + vertex_buffer, + index_buffer, + pipeline, + }) + } + + pub fn render( + &self, + command_buffer: &mut AutoCommandBufferBuilder, + descriptor_set_allocator: &Arc, + mvp_uniform: &Subbuffer<[MVP]>, + texture: &Texture, + ) -> Result<(), Box> { + let layouts = self.pipeline.layout().set_layouts(); + + let uniform_descriptor_set = DescriptorSet::new( + descriptor_set_allocator.clone(), + layouts[0].clone(), + [WriteDescriptorSet::buffer(0, mvp_uniform.clone())], + [], + )?; + + let texture_descriptor_set = DescriptorSet::new( + descriptor_set_allocator.clone(), + layouts[1].clone(), + [ + WriteDescriptorSet::sampler(0, texture.get_sampler().clone()), + WriteDescriptorSet::image_view(1, texture.get_texture().clone()), + ], + [], + )?; + + command_buffer.bind_pipeline_graphics(self.pipeline.clone())?; + command_buffer.bind_descriptor_sets( + PipelineBindPoint::Graphics, + self.pipeline.layout().clone(), + 0, + vec![uniform_descriptor_set, texture_descriptor_set], + )?; + command_buffer.bind_vertex_buffers(0, self.vertex_buffer.clone())?; + command_buffer.bind_index_buffer(self.index_buffer.clone())?; + + unsafe { + command_buffer.draw_indexed(INDICES.len() as u32, 1, 0, 0, 0)?; + } + + Ok(()) + } +} diff --git a/src/game/main_scene.rs b/src/game/main_scene.rs index 38c5bc5..c3d08ca 100644 --- a/src/game/main_scene.rs +++ b/src/game/main_scene.rs @@ -1,43 +1,19 @@ use crate::core::input::InputManager; -use crate::core::render::pipelines::triangle_pipeline::create_triangle_pipeline; use crate::core::render::primitives::camera::Camera; -use crate::core::render::primitives::vertex::Vertex2D; use crate::core::render::render_context::RenderContext; use crate::core::render::texture::Texture; use crate::core::scene::Scene; use crate::core::timer::Timer; use glam::{Mat4, Vec3}; -use std::sync::Arc; -use vulkano::buffer::Subbuffer; use vulkano::command_buffer::{ AutoCommandBufferBuilder, CommandBufferUsage, PrimaryAutoCommandBuffer, PrimaryCommandBufferAbstract, }; -use vulkano::descriptor_set::{DescriptorSet, WriteDescriptorSet}; -use vulkano::pipeline::{GraphicsPipeline, Pipeline, PipelineBindPoint}; -const VERTICES: [Vertex2D; 4] = [ - Vertex2D { - position: [0.0, 0.0], - uv: [0.0, 0.0], - }, - Vertex2D { - position: [0.0, 5.0], - uv: [0.0, 0.5], - }, - Vertex2D { - position: [10.0, 0.0], - uv: [1.0, 0.0], - }, - Vertex2D { - position: [10.0, 5.0], - uv: [1.0, 0.5], - }, -]; +use super::assets::square::Square; pub struct MainSceneState { - pipeline: Arc, - vertex_buffer: Subbuffer<[Vertex2D]>, + square: Square, camera: Camera, texture: Texture, speed: f32, @@ -54,12 +30,12 @@ impl Scene for MainScene { } fn load(&mut self, render_context: &RenderContext) { - let pipeline = - create_triangle_pipeline(render_context.device(), render_context.swapchain_format()) - .unwrap(); - let vertex_buffer = - Vertex2D::create_buffer(Vec::from_iter(VERTICES), render_context.memory_allocator()) - .unwrap(); + let square = Square::new( + render_context.device(), + render_context.memory_allocator(), + render_context.swapchain_format(), + ) + .unwrap(); let camera = Camera::new( Mat4::look_at_rh( @@ -97,8 +73,7 @@ impl Scene for MainScene { .unwrap(); self.state = Some(MainSceneState { - pipeline, - vertex_buffer, + square, camera, texture, speed: 50.0, @@ -128,49 +103,20 @@ impl Scene for MainScene { builder: &mut AutoCommandBufferBuilder, ) { let state = self.state.as_ref().unwrap(); - let vertex_count = state.vertex_buffer.len() as u32; - let instance_count = vertex_count / 4; - - let layouts = state.pipeline.layout().set_layouts(); - let uniform_buffer = state + let camera_uniform = state .camera .create_buffer(render_context.memory_allocator()) .unwrap(); - let uniform_descriptor_set = DescriptorSet::new( - render_context.descriptor_set_allocator().clone(), - layouts[0].clone(), - [WriteDescriptorSet::buffer(0, uniform_buffer)], - [], - ) - .unwrap(); - let texture_descriptor_set = DescriptorSet::new( - render_context.descriptor_set_allocator().clone(), - layouts[1].clone(), - [ - WriteDescriptorSet::sampler(0, state.texture.get_sampler().clone()), - WriteDescriptorSet::image_view(1, state.texture.get_texture().clone()), - ], - [], - ) - .unwrap(); - - unsafe { - builder - .bind_pipeline_graphics(state.pipeline.clone()) - .unwrap() - .bind_descriptor_sets( - PipelineBindPoint::Graphics, - state.pipeline.layout().clone(), - 0, - vec![uniform_descriptor_set, texture_descriptor_set], - ) - .unwrap() - .bind_vertex_buffers(0, state.vertex_buffer.clone()) - .unwrap() - .draw(vertex_count, instance_count, 0, 0) - .unwrap(); - } + state + .square + .render( + builder, + render_context.descriptor_set_allocator(), + &camera_uniform, + &state.texture, + ) + .unwrap(); } fn unload(&mut self) {} diff --git a/src/game/mod.rs b/src/game/mod.rs index 53eb070..e8924d3 100644 --- a/src/game/mod.rs +++ b/src/game/mod.rs @@ -1 +1,2 @@ +pub mod assets; pub mod main_scene; From f8359414328f5032edba36efd9cfe62f70acbb63 Mon Sep 17 00:00:00 2001 From: Florian RICHER Date: Thu, 29 May 2025 00:17:21 +0200 Subject: [PATCH 052/105] app: Move render_pass into scene --- src/core/app.rs | 160 +++++++----------------- src/core/render/mod.rs | 1 - src/core/render/render_context.rs | 98 --------------- src/core/scene/context.rs | 82 ++++++++++++ src/core/{scene.rs => scene/manager.rs} | 25 +--- src/core/scene/mod.rs | 24 ++++ src/game/main_scene.rs | 127 ++++++++++++++----- 7 files changed, 250 insertions(+), 267 deletions(-) delete mode 100644 src/core/render/render_context.rs create mode 100644 src/core/scene/context.rs rename src/core/{scene.rs => scene/manager.rs} (59%) create mode 100644 src/core/scene/mod.rs diff --git a/src/core/app.rs b/src/core/app.rs index bcdb82d..5865cb1 100644 --- a/src/core/app.rs +++ b/src/core/app.rs @@ -1,22 +1,15 @@ use std::collections::HashMap; use std::sync::Arc; -use super::render::render_context::RenderContext; use super::render::vulkan_context::VulkanContext; +use super::scene::SceneContext; use crate::core::input::InputManager; use crate::core::scene::SceneManager; use crate::core::timer::Timer; use crate::game::main_scene::MainScene; -use egui_winit_vulkano::{Gui, GuiConfig, egui}; -use vulkano::command_buffer::{ - AutoCommandBufferBuilder, CommandBufferUsage, RenderingAttachmentInfo, RenderingInfo, -}; -use vulkano::pipeline::graphics::viewport::Viewport; -use vulkano::render_pass::{AttachmentLoadOp, AttachmentStoreOp}; +use egui_winit_vulkano::{Gui, GuiConfig}; use vulkano::swapchain::PresentMode; -use vulkano::sync::GpuFuture; use vulkano_util::context::VulkanoContext; -use vulkano_util::renderer::VulkanoWindowRenderer; use vulkano_util::window::{VulkanoWindows, WindowDescriptor}; use winit::application::ApplicationHandler; use winit::event::WindowEvent; @@ -27,28 +20,9 @@ pub struct App { vulkan_context: Arc, vulkano_windows: Arc, gui: HashMap, - clear_color: [f32; 3], - input_manager: InputManager, scene_manager: SceneManager, - timer: Timer, -} - -impl From<(&VulkanContext, &VulkanoWindowRenderer)> for RenderContext { - fn from((vulkan_context, renderer): (&VulkanContext, &VulkanoWindowRenderer)) -> Self { - RenderContext::new( - vulkan_context.vulkano_context().instance().clone(), - vulkan_context.vulkano_context().device().clone(), - vulkan_context.vulkano_context().graphics_queue().clone(), - vulkan_context.vulkano_context().compute_queue().clone(), - vulkan_context.vulkano_context().transfer_queue().cloned(), - vulkan_context.vulkano_context().memory_allocator().clone(), - vulkan_context.command_buffer_allocator().clone(), - vulkan_context.descriptor_set_allocator().clone(), - renderer.resolution(), - renderer.aspect_ratio(), - renderer.swapchain_format(), - ) - } + input_manager: Arc, + timer: Arc, } impl App { @@ -57,10 +31,9 @@ impl App { vulkan_context: Arc::new(VulkanContext::new(vulkano_context)), vulkano_windows: Arc::new(VulkanoWindows::default()), gui: HashMap::new(), - clear_color: [0.0, 0.0, 0.0], - input_manager, + input_manager: Arc::new(input_manager), scene_manager: SceneManager::new(), - timer: Timer::new(), + timer: Arc::new(Timer::new()), } } } @@ -109,7 +82,10 @@ impl ApplicationHandler for App { _device_id: winit::event::DeviceId, event: winit::event::DeviceEvent, ) { - self.input_manager.process_device_event(&event); + match Arc::get_mut(&mut self.input_manager) { + Some(input_manager) => input_manager.process_device_event(&event), + None => log::error!("Failed to get a mutable reference to the input manager"), + } } fn window_event(&mut self, event_loop: &ActiveEventLoop, id: WindowId, event: WindowEvent) { @@ -118,10 +94,12 @@ impl ApplicationHandler for App { .get_renderer_mut(id) .unwrap(); let gui = self.gui.get_mut(&id).unwrap(); - let render_context = RenderContext::from((self.vulkan_context.as_ref(), &*renderer)); if !gui.update(&event) { - self.input_manager.process_window_event(&event); + match Arc::get_mut(&mut self.input_manager) { + Some(input_manager) => input_manager.process_window_event(&event), + None => log::error!("Failed to get a mutable reference to the input manager"), + } } match event { @@ -136,86 +114,40 @@ impl ApplicationHandler for App { renderer.resize(); } WindowEvent::RedrawRequested => { - self.input_manager.update(); - self.timer.update(); - self.scene_manager.load_scene_if_not_loaded(&render_context); + match Arc::get_mut(&mut self.input_manager) { + Some(input_manager) => input_manager.update(), + None => log::error!("Failed to get a mutable reference to the input manager"), + } + match Arc::get_mut(&mut self.timer) { + Some(timer) => timer.update(), + None => log::error!("Failed to get a mutable reference to the timer"), + } + + let scene_context = SceneContext::from(( + &*renderer, + &self.vulkan_context, + &self.input_manager, + &self.timer, + )); + self.scene_manager.load_scene_if_not_loaded(&scene_context); + if let Some(scene) = self.scene_manager.current_scene_mut() { - scene.update(&render_context, &self.input_manager, &self.timer); + scene.update(&scene_context); + + let acquire_future = renderer.acquire(None, |_| {}).unwrap(); + let acquire_future = scene.render( + &renderer.swapchain_image_view(), + acquire_future, + &scene_context, + gui, + ); + match acquire_future { + Ok(future) => renderer.present(future, true), + Err(e) => { + log::error!("Error rendering scene: {}", e); + } + } } - - let acquire_future = renderer.acquire(None, |_| {}).unwrap(); - - let mut builder = AutoCommandBufferBuilder::primary( - self.vulkan_context.command_buffer_allocator().clone(), - self.vulkan_context - .vulkano_context() - .graphics_queue() - .queue_family_index(), - CommandBufferUsage::OneTimeSubmit, - ) - .unwrap(); - - { - let viewport = Viewport { - offset: [0.0, 0.0], - extent: renderer.resolution(), - depth_range: 0.0..=1.0, - }; - - builder - .begin_rendering(RenderingInfo { - color_attachments: vec![Some(RenderingAttachmentInfo { - load_op: AttachmentLoadOp::Clear, - store_op: AttachmentStoreOp::Store, - clear_value: Some(self.clear_color.into()), - ..RenderingAttachmentInfo::image_view( - renderer.swapchain_image_view().clone(), - ) - })], - ..Default::default() - }) - .unwrap() - .set_viewport(0, [viewport].into_iter().collect()) - .unwrap(); - } - - if let Some(scene) = self.scene_manager.current_scene() { - scene.render(&render_context, &mut builder); - } - - builder.end_rendering().unwrap(); - - let command_buffer = builder.build().unwrap(); - - let render_future = acquire_future - .then_execute( - self.vulkan_context - .vulkano_context() - .graphics_queue() - .clone(), - command_buffer, - ) - .unwrap(); - - gui.immediate_ui(|gui| { - let ctx = gui.context(); - - egui::Window::new("Informations") - .vscroll(true) - .show(&ctx, |ui| { - ui.label(format!("Resolution: {:?}", renderer.resolution())); - ui.color_edit_button_rgb(&mut self.clear_color); - - ui.label(format!("{:#?}", self.input_manager)); - - ui.label(format!("Delta time: {:?}", self.timer.delta_time())); - }); - }); - - let render_future = - gui.draw_on_image(render_future, renderer.swapchain_image_view()); - - renderer.present(render_future.boxed(), true); } _ => {} } diff --git a/src/core/render/mod.rs b/src/core/render/mod.rs index a50c5da..07e4134 100644 --- a/src/core/render/mod.rs +++ b/src/core/render/mod.rs @@ -1,4 +1,3 @@ pub mod primitives; -pub mod render_context; pub mod texture; pub mod vulkan_context; diff --git a/src/core/render/render_context.rs b/src/core/render/render_context.rs deleted file mode 100644 index e9e34a1..0000000 --- a/src/core/render/render_context.rs +++ /dev/null @@ -1,98 +0,0 @@ -use std::sync::Arc; - -use vulkano::{ - command_buffer::allocator::StandardCommandBufferAllocator, - descriptor_set::allocator::StandardDescriptorSetAllocator, - device::{Device, Queue}, - format::Format, - instance::Instance, - memory::allocator::StandardMemoryAllocator, -}; - -pub struct RenderContext { - instance: Arc, - device: Arc, - graphics_queue: Arc, - compute_queue: Arc, - transfer_queue: Option>, - memory_allocator: Arc, - command_buffer_allocator: Arc, - descriptor_set_allocator: Arc, - window_size: [f32; 2], - aspect_ratio: f32, - swapchain_format: Format, -} - -impl RenderContext { - pub fn new( - instance: Arc, - device: Arc, - graphics_queue: Arc, - compute_queue: Arc, - transfer_queue: Option>, - memory_allocator: Arc, - command_buffer_allocator: Arc, - descriptor_set_allocator: Arc, - window_size: [f32; 2], - aspect_ratio: f32, - swapchain_format: Format, - ) -> Self { - Self { - instance, - device, - graphics_queue, - compute_queue, - transfer_queue, - memory_allocator, - command_buffer_allocator, - descriptor_set_allocator, - window_size, - aspect_ratio, - swapchain_format, - } - } - - pub fn instance(&self) -> &Arc { - &self.instance - } - - pub fn device(&self) -> &Arc { - &self.device - } - - pub fn graphics_queue(&self) -> &Arc { - &self.graphics_queue - } - - pub fn compute_queue(&self) -> &Arc { - &self.compute_queue - } - - pub fn transfer_queue(&self) -> Option<&Arc> { - self.transfer_queue.as_ref() - } - - pub fn memory_allocator(&self) -> &Arc { - &self.memory_allocator - } - - pub fn command_buffer_allocator(&self) -> &Arc { - &self.command_buffer_allocator - } - - pub fn descriptor_set_allocator(&self) -> &Arc { - &self.descriptor_set_allocator - } - - pub fn window_size(&self) -> &[f32; 2] { - &self.window_size - } - - pub fn aspect_ratio(&self) -> f32 { - self.aspect_ratio - } - - pub fn swapchain_format(&self) -> Format { - self.swapchain_format - } -} diff --git a/src/core/scene/context.rs b/src/core/scene/context.rs new file mode 100644 index 0000000..028d612 --- /dev/null +++ b/src/core/scene/context.rs @@ -0,0 +1,82 @@ +use std::sync::Arc; + +use vulkano::{ + command_buffer::allocator::StandardCommandBufferAllocator, + descriptor_set::allocator::StandardDescriptorSetAllocator, + device::{Device, Queue}, + format::Format, + instance::Instance, + memory::allocator::StandardMemoryAllocator, +}; +use vulkano_util::renderer::VulkanoWindowRenderer; + +use crate::core::{input::InputManager, render::vulkan_context::VulkanContext, timer::Timer}; + +pub struct SceneContext { + pub instance: Arc, + pub device: Arc, + pub graphics_queue: Arc, + pub compute_queue: Arc, + pub transfer_queue: Option>, + pub memory_allocator: Arc, + pub command_buffer_allocator: Arc, + pub descriptor_set_allocator: Arc, + pub window_size: [f32; 2], + pub aspect_ratio: f32, + pub swapchain_format: Format, + pub input_manager: Arc, + pub timer: Arc, +} + +impl + From<( + &VulkanoWindowRenderer, + &Arc, + &Arc, + &Arc, + )> for SceneContext +{ + fn from( + (renderer, vulkan_context, input_manager, timer): ( + &VulkanoWindowRenderer, + &Arc, + &Arc, + &Arc, + ), + ) -> Self { + let (command_buffer_allocator, descriptor_set_allocator) = { + ( + vulkan_context.command_buffer_allocator().clone(), + vulkan_context.descriptor_set_allocator().clone(), + ) + }; + + let (instance, device, graphics_queue, compute_queue, transfer_queue, memory_allocator) = { + let vulkan_context = vulkan_context.vulkano_context(); + ( + vulkan_context.instance().clone(), + vulkan_context.device().clone(), + vulkan_context.graphics_queue().clone(), + vulkan_context.compute_queue().clone(), + vulkan_context.transfer_queue().cloned(), + vulkan_context.memory_allocator().clone(), + ) + }; + + Self { + instance, + device, + graphics_queue, + compute_queue, + transfer_queue, + memory_allocator, + command_buffer_allocator, + descriptor_set_allocator, + window_size: renderer.window_size(), + aspect_ratio: renderer.aspect_ratio(), + swapchain_format: renderer.swapchain_format(), + input_manager: input_manager.clone(), + timer: timer.clone(), + } + } +} diff --git a/src/core/scene.rs b/src/core/scene/manager.rs similarity index 59% rename from src/core/scene.rs rename to src/core/scene/manager.rs index 9717117..8e25a74 100644 --- a/src/core/scene.rs +++ b/src/core/scene/manager.rs @@ -1,23 +1,4 @@ -use vulkano::command_buffer::{AutoCommandBufferBuilder, PrimaryAutoCommandBuffer}; - -use super::{input::InputManager, render::render_context::RenderContext, timer::Timer}; - -pub trait Scene { - fn loaded(&self) -> bool; - fn load(&mut self, render_context: &RenderContext); - fn update( - &mut self, - render_context: &RenderContext, - input_manager: &InputManager, - timer: &Timer, - ); - fn render( - &self, - render_context: &RenderContext, - builder: &mut AutoCommandBufferBuilder, - ); - fn unload(&mut self); -} +use super::{Scene, SceneContext}; pub struct SceneManager { current_scene: Option>, @@ -30,10 +11,10 @@ impl SceneManager { } } - pub fn load_scene_if_not_loaded(&mut self, render_context: &RenderContext) { + pub fn load_scene_if_not_loaded(&mut self, scene_context: &SceneContext) { if let Some(current_scene) = self.current_scene.as_mut() { if !current_scene.loaded() { - current_scene.load(render_context); + current_scene.load(scene_context); } } } diff --git a/src/core/scene/mod.rs b/src/core/scene/mod.rs new file mode 100644 index 0000000..dc97ece --- /dev/null +++ b/src/core/scene/mod.rs @@ -0,0 +1,24 @@ +use std::{error::Error, sync::Arc}; + +use egui_winit_vulkano::Gui; +use vulkano::{image::view::ImageView, sync::GpuFuture}; + +mod context; +pub use context::SceneContext; + +mod manager; +pub use manager::SceneManager; + +pub trait Scene { + fn loaded(&self) -> bool; + fn load(&mut self, scene_context: &SceneContext); + fn update(&mut self, scene_context: &SceneContext); + fn render( + &mut self, + image_view: &Arc, + acquire_future: Box, + scene_context: &SceneContext, + gui: &mut Gui, + ) -> Result, Box>; + fn unload(&mut self); +} diff --git a/src/game/main_scene.rs b/src/game/main_scene.rs index c3d08ca..3000746 100644 --- a/src/game/main_scene.rs +++ b/src/game/main_scene.rs @@ -1,13 +1,20 @@ -use crate::core::input::InputManager; +use std::{error::Error, sync::Arc}; + use crate::core::render::primitives::camera::Camera; -use crate::core::render::render_context::RenderContext; use crate::core::render::texture::Texture; use crate::core::scene::Scene; -use crate::core::timer::Timer; +use crate::core::scene::SceneContext; +use egui_winit_vulkano::{Gui, egui}; use glam::{Mat4, Vec3}; -use vulkano::command_buffer::{ - AutoCommandBufferBuilder, CommandBufferUsage, PrimaryAutoCommandBuffer, - PrimaryCommandBufferAbstract, +use vulkano::{ + command_buffer::{ + AutoCommandBufferBuilder, CommandBufferUsage, PrimaryCommandBufferAbstract, + RenderingAttachmentInfo, RenderingInfo, + }, + image::view::ImageView, + pipeline::graphics::viewport::Viewport, + render_pass::{AttachmentLoadOp, AttachmentStoreOp}, + sync::GpuFuture, }; use super::assets::square::Square; @@ -29,11 +36,11 @@ impl Scene for MainScene { self.state.is_some() } - fn load(&mut self, render_context: &RenderContext) { + fn load(&mut self, scene_context: &SceneContext) { let square = Square::new( - render_context.device(), - render_context.memory_allocator(), - render_context.swapchain_format(), + &scene_context.device, + &scene_context.memory_allocator, + scene_context.swapchain_format, ) .unwrap(); @@ -45,22 +52,22 @@ impl Scene for MainScene { ), Mat4::perspective_rh_gl( std::f32::consts::FRAC_PI_2, - render_context.aspect_ratio(), + scene_context.aspect_ratio, 0.01, 100.0, ), ); let mut uploads = AutoCommandBufferBuilder::primary( - render_context.command_buffer_allocator().clone(), - render_context.graphics_queue().queue_family_index(), + scene_context.command_buffer_allocator.clone(), + scene_context.graphics_queue.queue_family_index(), CommandBufferUsage::OneTimeSubmit, ) .unwrap(); let texture = Texture::from_file( - render_context.device(), - render_context.memory_allocator(), + &scene_context.device, + &scene_context.memory_allocator, &mut uploads, "res/textures/wooden-crate.jpg", ) @@ -69,7 +76,7 @@ impl Scene for MainScene { let _ = uploads .build() .unwrap() - .execute(render_context.graphics_queue().clone()) + .execute(scene_context.graphics_queue.clone()) .unwrap(); self.state = Some(MainSceneState { @@ -80,43 +87,99 @@ impl Scene for MainScene { }); } - fn update( - &mut self, - render_context: &RenderContext, - input_manager: &InputManager, - timer: &Timer, - ) { + fn update(&mut self, scene_context: &SceneContext) { let state = self.state.as_mut().unwrap(); - state.camera.update(input_manager, timer, state.speed, 10.0); + state.camera.update( + &scene_context.input_manager, + &scene_context.timer, + state.speed, + 10.0, + ); state.camera.set_projection(Mat4::perspective_rh_gl( std::f32::consts::FRAC_PI_2, - render_context.aspect_ratio(), + scene_context.aspect_ratio, 0.01, 100.0, )); } fn render( - &self, - render_context: &RenderContext, - builder: &mut AutoCommandBufferBuilder, - ) { + &mut self, + image_view: &Arc, + acquire_future: Box, + scene_context: &SceneContext, + gui: &mut Gui, + ) -> Result, Box> { let state = self.state.as_ref().unwrap(); + + let mut builder = AutoCommandBufferBuilder::primary( + scene_context.command_buffer_allocator.clone(), + scene_context.graphics_queue.queue_family_index(), + CommandBufferUsage::OneTimeSubmit, + )?; + + { + let viewport = Viewport { + offset: [0.0, 0.0], + extent: scene_context.window_size, + depth_range: 0.0..=1.0, + }; + + builder + .begin_rendering(RenderingInfo { + color_attachments: vec![Some(RenderingAttachmentInfo { + load_op: AttachmentLoadOp::Clear, + store_op: AttachmentStoreOp::Store, + clear_value: Some([0.0, 0.0, 0.0, 1.0].into()), + ..RenderingAttachmentInfo::image_view(image_view.clone()) + })], + ..Default::default() + })? + .set_viewport(0, [viewport].into_iter().collect())?; + } + let camera_uniform = state .camera - .create_buffer(render_context.memory_allocator()) - .unwrap(); + .create_buffer(&scene_context.memory_allocator)?; state .square .render( - builder, - render_context.descriptor_set_allocator(), + &mut builder, + &scene_context.descriptor_set_allocator, &camera_uniform, &state.texture, ) .unwrap(); + + builder.end_rendering()?; + + let command_buffer = builder.build()?; + + let render_future = + acquire_future.then_execute(scene_context.graphics_queue.clone(), command_buffer)?; + + gui.immediate_ui(|gui| { + let ctx = gui.context(); + + egui::Window::new("Informations") + .vscroll(true) + .show(&ctx, |ui| { + ui.label(format!("Resolution: {:?}", scene_context.window_size)); + + ui.label(format!("{:#?}", scene_context.input_manager)); + + ui.label(format!( + "Delta time: {:?}", + scene_context.timer.delta_time() + )); + }); + }); + + let render_future = gui.draw_on_image(render_future, image_view.clone()); + + Ok(render_future) } fn unload(&mut self) {} From 998aa68da1321dcfb7a3018353194d3df43c95f4 Mon Sep 17 00:00:00 2001 From: Florian RICHER Date: Thu, 29 May 2025 13:54:00 +0200 Subject: [PATCH 053/105] camera: fix camera movement --- README.md | 7 ++++ flake.nix | 3 +- src/core/app.rs | 5 ++- src/core/render/primitives/camera.rs | 60 +++++++++++++++++----------- src/core/render/primitives/mvp.rs | 14 ++----- src/core/render/primitives/vertex.rs | 28 +------------ src/core/scene/manager.rs | 4 +- src/core/scene/mod.rs | 4 +- src/game/main_scene.rs | 59 ++++++++++++--------------- src/main.rs | 2 - 10 files changed, 83 insertions(+), 103 deletions(-) diff --git a/README.md b/README.md index c74b5d4..6e4bad1 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,12 @@ # Project +Run renderdoc on wayland: + +```console +WAYLAND_DISPLAY= QT_QPA_PLATFORM=xcb qrenderdoc +``` +> Not supported yet https://github.com/baldurk/renderdoc/issues/853 + ## Usefull links - https://vulkan-tutorial.com/fr/Introduction diff --git a/flake.nix b/flake.nix index dbd8d4c..c5bbd9a 100644 --- a/flake.nix +++ b/flake.nix @@ -39,6 +39,7 @@ libxkbcommon wayland libGL + # Xorg xorg.libX11 xorg.libXcursor @@ -64,7 +65,7 @@ ++ packages; LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath buildInputs; - VK_LAYER_PATH = "${pkgs.vulkan-validation-layers}/share/vulkan/explicit_layer.d:${pkgs.renderdoc}/share/vulkan/implicit_layer.d"; + VK_LAYER_PATH = "${pkgs.vulkan-validation-layers}/share/vulkan/explicit_layer.d"; RUST_LOG = "debug,rust_vulkan_test=trace"; }; in diff --git a/src/core/app.rs b/src/core/app.rs index 5865cb1..33a5dc1 100644 --- a/src/core/app.rs +++ b/src/core/app.rs @@ -65,6 +65,7 @@ impl ApplicationHandler for App { renderer.swapchain_format(), GuiConfig { is_overlay: true, + allow_srgb_render_target: true, ..Default::default() }, ) @@ -132,7 +133,9 @@ impl ApplicationHandler for App { self.scene_manager.load_scene_if_not_loaded(&scene_context); if let Some(scene) = self.scene_manager.current_scene_mut() { - scene.update(&scene_context); + if let Err(e) = scene.update(&scene_context) { + log::error!("Error updating scene: {}", e); + } let acquire_future = renderer.acquire(None, |_| {}).unwrap(); let acquire_future = scene.render( diff --git a/src/core/render/primitives/camera.rs b/src/core/render/primitives/camera.rs index c322c7c..34950ce 100644 --- a/src/core/render/primitives/camera.rs +++ b/src/core/render/primitives/camera.rs @@ -1,6 +1,6 @@ -use std::{f32::consts::PI, sync::Arc}; +use std::{f32::consts::FRAC_PI_2, sync::Arc}; -use glam::{Mat4, Quat, Vec3}; +use glam::{Mat4, Vec3, Vec4}; use vulkano::{ Validated, buffer::{AllocateBufferError, Subbuffer}, @@ -13,7 +13,6 @@ use super::mvp::MVP; #[derive(Default)] pub struct Camera { - view: Mat4, projection: Mat4, position: Vec3, @@ -21,9 +20,8 @@ pub struct Camera { } impl Camera { - pub fn new(view: Mat4, projection: Mat4) -> Self { + pub fn new(projection: Mat4) -> Self { Self { - view, projection, position: Vec3::ZERO, rotation: Vec3::ZERO, @@ -45,43 +43,57 @@ impl Camera { 0.0, ); - if self.rotation.x > 90.0 { - self.rotation = Vec3::new(90.0, self.rotation.y, 0.0); + if self.rotation.x > FRAC_PI_2 { + self.rotation = Vec3::new(FRAC_PI_2, self.rotation.y, 0.0); } - if self.rotation.x < -90.0 { - self.rotation = Vec3::new(-90.0, self.rotation.y, 0.0); + if self.rotation.x < -FRAC_PI_2 { + self.rotation = Vec3::new(-FRAC_PI_2, self.rotation.y, 0.0); } let movement_delta = movement_speed * timer.delta_time(); + let (yaw_sin, yaw_cos) = self.rotation.y.sin_cos(); + let forward = Vec3::new(yaw_cos, 0.0, yaw_sin).normalize(); + let right = Vec3::new(-yaw_sin, 0.0, yaw_cos).normalize(); + let tx = input_manager.get_virtual_input_state("move_right") * movement_delta; + self.position += tx * right; + let tz = input_manager.get_virtual_input_state("move_forward") * movement_delta; - - self.position.x += tx * (self.rotation.y).cos(); - self.position.z += tx * (self.rotation.y).sin(); - - self.position.x += tz * (self.rotation.y + PI / 2.0).cos(); - self.position.z += tz * (self.rotation.y + PI / 2.0).sin(); + self.position += tz * forward; } pub fn set_projection(&mut self, projection: Mat4) { self.projection = projection; } + pub fn get_position(&self) -> Vec3 { + self.position + } + + pub fn get_rotation(&self) -> Vec3 { + self.rotation + } + pub fn create_buffer( &self, memory_allocator: &Arc, ) -> Result, Validated> { - let mut world_matrix = Mat4::IDENTITY; - world_matrix *= Mat4::from_quat(Quat::from_euler( - glam::EulerRot::XYX, - self.rotation.x, - self.rotation.y, - self.rotation.z, - )); - world_matrix *= Mat4::from_translation(self.position); + let (sin_pitch, cos_pitch) = self.rotation.x.sin_cos(); + let (sin_yaw, cos_yaw) = self.rotation.y.sin_cos(); - MVP::new(&world_matrix, &self.view, &self.projection).into_buffer(memory_allocator) + let view_matrix = Mat4::look_to_rh( + self.position, + Vec3::new(cos_pitch * cos_yaw, sin_pitch, cos_pitch * sin_yaw).normalize(), + Vec3::Y, + ); + + MVP { + model: Mat4::IDENTITY.to_cols_array_2d(), + view: view_matrix.to_cols_array_2d(), + projection: self.projection.to_cols_array_2d(), + } + .into_buffer(memory_allocator) } } diff --git a/src/core/render/primitives/mvp.rs b/src/core/render/primitives/mvp.rs index fb73ad3..b06b55f 100644 --- a/src/core/render/primitives/mvp.rs +++ b/src/core/render/primitives/mvp.rs @@ -10,20 +10,12 @@ use vulkano::memory::allocator::{AllocationCreateInfo, MemoryTypeFilter, Standar #[derive(BufferContents, Clone, Copy)] #[repr(C)] pub struct MVP { - world: [[f32; 4]; 4], - view: [[f32; 4]; 4], - projection: [[f32; 4]; 4], + pub model: [[f32; 4]; 4], + pub view: [[f32; 4]; 4], + pub projection: [[f32; 4]; 4], } impl MVP { - pub fn new(world: &Mat4, view: &Mat4, projection: &Mat4) -> Self { - Self { - world: world.to_cols_array_2d(), - view: view.to_cols_array_2d(), - projection: projection.to_cols_array_2d(), - } - } - pub fn into_buffer( self, memory_allocator: &Arc, diff --git a/src/core/render/primitives/vertex.rs b/src/core/render/primitives/vertex.rs index b35588c..566df22 100644 --- a/src/core/render/primitives/vertex.rs +++ b/src/core/render/primitives/vertex.rs @@ -1,9 +1,4 @@ -use std::sync::Arc; -use vulkano::Validated; -use vulkano::buffer::{ - AllocateBufferError, Buffer, BufferContents, BufferCreateInfo, BufferUsage, Subbuffer, -}; -use vulkano::memory::allocator::{AllocationCreateInfo, MemoryTypeFilter, StandardMemoryAllocator}; +use vulkano::buffer::BufferContents; use vulkano::pipeline::graphics::vertex_input::Vertex; #[derive(BufferContents, Vertex)] @@ -15,24 +10,3 @@ pub struct Vertex2D { #[format(R32G32_SFLOAT)] pub uv: [f32; 2], } - -impl Vertex2D { - pub fn create_buffer( - vertices: Vec, - memory_allocator: &Arc, - ) -> Result, Validated> { - Buffer::from_iter( - memory_allocator.clone(), - BufferCreateInfo { - usage: BufferUsage::VERTEX_BUFFER, - ..Default::default() - }, - AllocationCreateInfo { - memory_type_filter: MemoryTypeFilter::PREFER_DEVICE - | MemoryTypeFilter::HOST_SEQUENTIAL_WRITE, - ..Default::default() - }, - vertices, - ) - } -} diff --git a/src/core/scene/manager.rs b/src/core/scene/manager.rs index 8e25a74..e2043f2 100644 --- a/src/core/scene/manager.rs +++ b/src/core/scene/manager.rs @@ -14,7 +14,9 @@ impl SceneManager { pub fn load_scene_if_not_loaded(&mut self, scene_context: &SceneContext) { if let Some(current_scene) = self.current_scene.as_mut() { if !current_scene.loaded() { - current_scene.load(scene_context); + if let Err(e) = current_scene.load(scene_context) { + log::error!("Error loading scene: {}", e); + } } } } diff --git a/src/core/scene/mod.rs b/src/core/scene/mod.rs index dc97ece..a71bfbb 100644 --- a/src/core/scene/mod.rs +++ b/src/core/scene/mod.rs @@ -11,8 +11,8 @@ pub use manager::SceneManager; pub trait Scene { fn loaded(&self) -> bool; - fn load(&mut self, scene_context: &SceneContext); - fn update(&mut self, scene_context: &SceneContext); + fn load(&mut self, scene_context: &SceneContext) -> Result<(), Box>; + fn update(&mut self, scene_context: &SceneContext) -> Result<(), Box>; fn render( &mut self, image_view: &Arc, diff --git a/src/game/main_scene.rs b/src/game/main_scene.rs index 3000746..0aa18ac 100644 --- a/src/game/main_scene.rs +++ b/src/game/main_scene.rs @@ -5,7 +5,7 @@ use crate::core::render::texture::Texture; use crate::core::scene::Scene; use crate::core::scene::SceneContext; use egui_winit_vulkano::{Gui, egui}; -use glam::{Mat4, Vec3}; +use glam::Mat4; use vulkano::{ command_buffer::{ AutoCommandBufferBuilder, CommandBufferUsage, PrimaryCommandBufferAbstract, @@ -36,48 +36,36 @@ impl Scene for MainScene { self.state.is_some() } - fn load(&mut self, scene_context: &SceneContext) { + fn load(&mut self, scene_context: &SceneContext) -> Result<(), Box> { let square = Square::new( &scene_context.device, &scene_context.memory_allocator, scene_context.swapchain_format, - ) - .unwrap(); + )?; - let camera = Camera::new( - Mat4::look_at_rh( - Vec3::new(0.3, 0.3, 1.0), - Vec3::new(0.0, 0.0, 0.0), - Vec3::new(0.0, -1.0, 0.0), - ), - Mat4::perspective_rh_gl( - std::f32::consts::FRAC_PI_2, - scene_context.aspect_ratio, - 0.01, - 100.0, - ), - ); + let camera = Camera::new(Mat4::perspective_rh_gl( + std::f32::consts::FRAC_PI_2, + scene_context.aspect_ratio, + 0.01, + 1000.0, + )); let mut uploads = AutoCommandBufferBuilder::primary( scene_context.command_buffer_allocator.clone(), scene_context.graphics_queue.queue_family_index(), CommandBufferUsage::OneTimeSubmit, - ) - .unwrap(); + )?; let texture = Texture::from_file( &scene_context.device, &scene_context.memory_allocator, &mut uploads, "res/textures/wooden-crate.jpg", - ) - .unwrap(); + )?; let _ = uploads - .build() - .unwrap() - .execute(scene_context.graphics_queue.clone()) - .unwrap(); + .build()? + .execute(scene_context.graphics_queue.clone())?; self.state = Some(MainSceneState { square, @@ -85,10 +73,12 @@ impl Scene for MainScene { texture, speed: 50.0, }); + + Ok(()) } - fn update(&mut self, scene_context: &SceneContext) { - let state = self.state.as_mut().unwrap(); + fn update(&mut self, scene_context: &SceneContext) -> Result<(), Box> { + let state = self.state.as_mut().ok_or("State not found")?; state.camera.update( &scene_context.input_manager, &scene_context.timer, @@ -96,12 +86,7 @@ impl Scene for MainScene { 10.0, ); - state.camera.set_projection(Mat4::perspective_rh_gl( - std::f32::consts::FRAC_PI_2, - scene_context.aspect_ratio, - 0.01, - 100.0, - )); + Ok(()) } fn render( @@ -111,7 +96,7 @@ impl Scene for MainScene { scene_context: &SceneContext, gui: &mut Gui, ) -> Result, Box> { - let state = self.state.as_ref().unwrap(); + let state = self.state.as_ref().ok_or("State not found")?; let mut builder = AutoCommandBufferBuilder::primary( scene_context.command_buffer_allocator.clone(), @@ -174,6 +159,12 @@ impl Scene for MainScene { "Delta time: {:?}", scene_context.timer.delta_time() )); + + ui.label(format!( + "Position: {:?}, Rotation: {:?}", + state.camera.get_position(), + state.camera.get_rotation() + )); }); }); diff --git a/src/main.rs b/src/main.rs index 7a4284c..ffe5f72 100644 --- a/src/main.rs +++ b/src/main.rs @@ -21,7 +21,6 @@ fn main() { vec![ VirtualBinding::Keyboard(PhysicalKey::Code(KeyCode::KeyW), AxisDirection::Normal), VirtualBinding::Keyboard(PhysicalKey::Code(KeyCode::KeyS), AxisDirection::Invert), - VirtualBinding::Axis(0, AxisDirection::Normal, 0.0), ], ), ( @@ -29,7 +28,6 @@ fn main() { vec![ VirtualBinding::Keyboard(PhysicalKey::Code(KeyCode::KeyD), AxisDirection::Normal), VirtualBinding::Keyboard(PhysicalKey::Code(KeyCode::KeyA), AxisDirection::Invert), - VirtualBinding::Axis(1, AxisDirection::Normal, 0.0), ], ), ( From 05532756cfad020f98c9f85b09bc4f3f1faf67b3 Mon Sep 17 00:00:00 2001 From: Florian RICHER Date: Thu, 29 May 2025 16:08:41 +0200 Subject: [PATCH 054/105] docs: Add opengl/vulkan diff --- README.md | 6 +- docs/OPENGL_VULKAN_DIFF.md | 11 + docs/images/coord_sys.png | Bin 0 -> 24096 bytes docs/images/normalized_device_coordinates.svg | 219 ++++++++++++++++++ 4 files changed, 235 insertions(+), 1 deletion(-) create mode 100644 docs/OPENGL_VULKAN_DIFF.md create mode 100644 docs/images/coord_sys.png create mode 100644 docs/images/normalized_device_coordinates.svg diff --git a/README.md b/README.md index 6e4bad1..c271fd8 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,16 @@ # Project -Run renderdoc on wayland: +## Notes + +1. Run renderdoc on wayland: ```console WAYLAND_DISPLAY= QT_QPA_PLATFORM=xcb qrenderdoc ``` > Not supported yet https://github.com/baldurk/renderdoc/issues/853 +2. [Difference Between OpenGL and Vulkan](./docs/OPENGL_VULKAN_DIFF.md) + ## Usefull links - https://vulkan-tutorial.com/fr/Introduction diff --git a/docs/OPENGL_VULKAN_DIFF.md b/docs/OPENGL_VULKAN_DIFF.md new file mode 100644 index 0000000..93d0855 --- /dev/null +++ b/docs/OPENGL_VULKAN_DIFF.md @@ -0,0 +1,11 @@ +# Difference between Vulkan and OpenGL + +Viewport: + +- Y axis is flipped like D3D +- Clipped Z axis is not [-1; 1] but [0; 1] + +![normalized viewport coordinates](./images/normalized_device_coordinates.svg) +![coord_sys](./images/coord_sys.png) + +See: [Vulkan Tutorial (Vertex step)](https://vulkan-tutorial.com/Drawing_a_triangle/Graphics_pipeline_basics/Shader_modules) and [VK_KHR_maintenance1 (Allow negative height)](https://registry.khronos.org/vulkan/specs/latest/man/html/VK_KHR_maintenance1.html#_description) diff --git a/docs/images/coord_sys.png b/docs/images/coord_sys.png new file mode 100644 index 0000000000000000000000000000000000000000..6d195f67ca5dbc31bf19457f0d724c1b52dd1597 GIT binary patch literal 24096 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
    XW1uvMN#KLU*8Rt+}~tt-#$T@+2O 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 + + + + + + + + + image/svg+xml + + + + + + + + Framebuffer coordinates + (0, 0) + (1920, 0) + (0, 1080) + (1920, 1080) + + (960, 540) + + Normalized device coordinates + (-1, -1) + (1, -1) + (-1, 1) + (1, 1) + + (0, 0) + + From 8b16def890ea041a0368053fc6d84bc2b26e9404 Mon Sep 17 00:00:00 2001 From: Florian RICHER Date: Thu, 29 May 2025 16:09:04 +0200 Subject: [PATCH 055/105] square: Fix indices --- src/game/assets/square.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/game/assets/square.rs b/src/game/assets/square.rs index 3dba163..acca0b6 100644 --- a/src/game/assets/square.rs +++ b/src/game/assets/square.rs @@ -53,7 +53,7 @@ const VERTICES: [Vertex2D; 4] = [ }, ]; -const INDICES: [u32; 6] = [0, 1, 2, 2, 1, 3]; +const INDICES: [u32; 6] = [0, 2, 1, 2, 3, 1]; pub mod shaders { pub mod vs { From 3a562fb6ebf3ecfc0bfd9583444cac89d9ca9cfe Mon Sep 17 00:00:00 2001 From: Florian RICHER Date: Thu, 29 May 2025 16:38:45 +0200 Subject: [PATCH 056/105] camera: Fix Y inverted --- src/core/render/primitives/camera.rs | 10 +++++++++- src/game/main_scene.rs | 2 +- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/core/render/primitives/camera.rs b/src/core/render/primitives/camera.rs index 34950ce..f1f48d7 100644 --- a/src/core/render/primitives/camera.rs +++ b/src/core/render/primitives/camera.rs @@ -11,6 +11,14 @@ use crate::core::{input::InputManager, timer::Timer}; use super::mvp::MVP; +// See docs/OPENGL_VULKAN_DIFF.md +const OPENGL_TO_VULKAN_Y_AXIS_FLIP: Mat4 = Mat4 { + x_axis: Vec4::new(1.0, 0.0, 0.0, 0.0), + y_axis: Vec4::new(0.0, -1.0, 0.0, 0.0), + z_axis: Vec4::new(0.0, 0.0, 1.0, 0.0), + w_axis: Vec4::new(0.0, 0.0, 0.0, 1.0), +}; + #[derive(Default)] pub struct Camera { projection: Mat4, @@ -90,7 +98,7 @@ impl Camera { ); MVP { - model: Mat4::IDENTITY.to_cols_array_2d(), + model: OPENGL_TO_VULKAN_Y_AXIS_FLIP.to_cols_array_2d(), view: view_matrix.to_cols_array_2d(), projection: self.projection.to_cols_array_2d(), } diff --git a/src/game/main_scene.rs b/src/game/main_scene.rs index 0aa18ac..03f54ea 100644 --- a/src/game/main_scene.rs +++ b/src/game/main_scene.rs @@ -43,7 +43,7 @@ impl Scene for MainScene { scene_context.swapchain_format, )?; - let camera = Camera::new(Mat4::perspective_rh_gl( + let camera = Camera::new(Mat4::perspective_rh( std::f32::consts::FRAC_PI_2, scene_context.aspect_ratio, 0.01, From f8b81f32691a986150a3ed4bb214e06459185d1e Mon Sep 17 00:00:00 2001 From: Florian RICHER Date: Thu, 29 May 2025 16:46:11 +0200 Subject: [PATCH 057/105] camera: Change camera to Camera3D --- src/core/render/primitives/camera.rs | 23 +++++++++++++++++++---- src/game/main_scene.rs | 11 ++++++----- 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/src/core/render/primitives/camera.rs b/src/core/render/primitives/camera.rs index f1f48d7..5ea52e7 100644 --- a/src/core/render/primitives/camera.rs +++ b/src/core/render/primitives/camera.rs @@ -20,19 +20,27 @@ const OPENGL_TO_VULKAN_Y_AXIS_FLIP: Mat4 = Mat4 { }; #[derive(Default)] -pub struct Camera { +pub struct Camera3D { projection: Mat4, position: Vec3, rotation: Vec3, + aspect_ratio: f32, + fov: f32, + near: f32, + far: f32, } -impl Camera { - pub fn new(projection: Mat4) -> Self { +impl Camera3D { + pub fn new(aspect_ratio: f32, fov: f32, near: f32, far: f32) -> Self { Self { - projection, + projection: Mat4::perspective_rh(fov, aspect_ratio, near, far), position: Vec3::ZERO, rotation: Vec3::ZERO, + aspect_ratio, + fov, + near, + far, } } @@ -42,6 +50,7 @@ impl Camera { timer: &Timer, movement_speed: f32, camera_sensitivity: f32, + window_aspect_ratio: f32, ) { // Process camera rotation let camera_delta = camera_sensitivity * timer.delta_time(); @@ -70,6 +79,12 @@ impl Camera { let tz = input_manager.get_virtual_input_state("move_forward") * movement_delta; self.position += tz * forward; + + if self.aspect_ratio != window_aspect_ratio { + self.aspect_ratio = window_aspect_ratio; + self.projection = + Mat4::perspective_rh(self.fov, self.aspect_ratio, self.near, self.far); + } } pub fn set_projection(&mut self, projection: Mat4) { diff --git a/src/game/main_scene.rs b/src/game/main_scene.rs index 03f54ea..7063d58 100644 --- a/src/game/main_scene.rs +++ b/src/game/main_scene.rs @@ -1,6 +1,6 @@ use std::{error::Error, sync::Arc}; -use crate::core::render::primitives::camera::Camera; +use crate::core::render::primitives::camera::Camera3D; use crate::core::render::texture::Texture; use crate::core::scene::Scene; use crate::core::scene::SceneContext; @@ -21,7 +21,7 @@ use super::assets::square::Square; pub struct MainSceneState { square: Square, - camera: Camera, + camera: Camera3D, texture: Texture, speed: f32, } @@ -43,12 +43,12 @@ impl Scene for MainScene { scene_context.swapchain_format, )?; - let camera = Camera::new(Mat4::perspective_rh( - std::f32::consts::FRAC_PI_2, + let camera = Camera3D::new( scene_context.aspect_ratio, + std::f32::consts::FRAC_PI_2, 0.01, 1000.0, - )); + ); let mut uploads = AutoCommandBufferBuilder::primary( scene_context.command_buffer_allocator.clone(), @@ -84,6 +84,7 @@ impl Scene for MainScene { &scene_context.timer, state.speed, 10.0, + scene_context.aspect_ratio, ); Ok(()) From 77c717f90b38f57881d3b3b19e9904fb9165673d Mon Sep 17 00:00:00 2001 From: Florian RICHER Date: Thu, 29 May 2025 17:13:01 +0200 Subject: [PATCH 058/105] Add instances support --- Cargo.lock | 40 ++++++++++++++--- Cargo.toml | 3 ++ res/shaders/vertex.vert | 6 ++- src/core/render/primitives/transform.rs | 57 ++++++++++++++++++++++--- src/core/render/primitives/vertex.rs | 10 +++++ src/game/assets/square.rs | 41 +++++++++++------- src/game/main_scene.rs | 32 +++++++++++++- 7 files changed, 158 insertions(+), 31 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 54175c9..375be30 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1929,8 +1929,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha", - "rand_core", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.3", ] [[package]] @@ -1940,7 +1950,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.3", ] [[package]] @@ -1952,6 +1972,15 @@ dependencies = [ "getrandom 0.2.16", ] +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom 0.3.3", +] + [[package]] name = "rav1e" version = "0.7.1" @@ -1978,8 +2007,8 @@ dependencies = [ "once_cell", "paste", "profiling", - "rand", - "rand_chacha", + "rand 0.8.5", + "rand_chacha 0.3.1", "simd_helpers", "system-deps", "thiserror 1.0.69", @@ -2112,6 +2141,7 @@ dependencies = [ "glam", "image", "log", + "rand 0.9.1", "thiserror 2.0.12", "vulkano", "vulkano-shaders", diff --git a/Cargo.toml b/Cargo.toml index 50be745..feb1bca 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,3 +23,6 @@ glam = { version = "0.30" } # Log and tracing log = "0.4" env_logger = "0.11" + +# Random +rand = "0.9" diff --git a/res/shaders/vertex.vert b/res/shaders/vertex.vert index 0445e54..8c1df7c 100644 --- a/res/shaders/vertex.vert +++ b/res/shaders/vertex.vert @@ -1,7 +1,8 @@ #version 450 -layout (location = 0) in vec2 position; +layout (location = 0) in vec3 position; layout (location = 1) in vec2 uv; +layout (location = 2) in mat4 model; layout (location = 0) out vec2 fragUv; @@ -13,6 +14,7 @@ layout (set = 0, binding = 0) uniform MVP { void main() { mat4 worldview = uniforms.view * uniforms.world; - gl_Position = uniforms.projection * worldview * vec4(position, 0.0, 1.0); + vec4 modelPosition = model * vec4(position, 1.0); + gl_Position = uniforms.projection * worldview * modelPosition; fragUv = uv; } diff --git a/src/core/render/primitives/transform.rs b/src/core/render/primitives/transform.rs index 88e6422..131e6f1 100644 --- a/src/core/render/primitives/transform.rs +++ b/src/core/render/primitives/transform.rs @@ -1,9 +1,26 @@ +use std::sync::Arc; + use glam::{Mat4, Quat, Vec3}; +use vulkano::{ + Validated, + buffer::{ + AllocateBufferError, Buffer, BufferContents, BufferCreateInfo, BufferUsage, Subbuffer, + }, + memory::allocator::{AllocationCreateInfo, MemoryTypeFilter, StandardMemoryAllocator}, + pipeline::graphics::vertex_input::Vertex, +}; pub struct Transform { - position: Vec3, - rotation: Quat, - scale: Vec3, + pub position: Vec3, + pub rotation: Quat, + pub scale: Vec3, +} + +#[derive(BufferContents, Vertex)] +#[repr(C)] +pub struct TransformRaw { + #[format(R32G32B32A32_SFLOAT)] + pub model: [[f32; 4]; 4], } impl Default for Transform { @@ -29,9 +46,35 @@ impl Transform { self.scale *= scale; } - pub fn get_mat4(&self) -> Mat4 { - Mat4::from_translation(self.position) - * Mat4::from_quat(self.rotation) - * Mat4::from_scale(self.scale) + pub fn into_raw(&self) -> TransformRaw { + TransformRaw { + model: (Mat4::from_translation(self.position) + * Mat4::from_quat(self.rotation) + * Mat4::from_scale(self.scale)) + .to_cols_array_2d(), + } + } + + pub fn create_buffer( + memory_allocator: &Arc, + transforms: &[Transform], + ) -> Result, Validated> { + let transform_raws: Vec = transforms.iter().map(|t| t.into_raw()).collect(); + + let buffer = Buffer::from_iter( + memory_allocator.clone(), + BufferCreateInfo { + usage: BufferUsage::VERTEX_BUFFER, + ..Default::default() + }, + AllocationCreateInfo { + memory_type_filter: MemoryTypeFilter::PREFER_DEVICE + | MemoryTypeFilter::HOST_SEQUENTIAL_WRITE, + ..Default::default() + }, + transform_raws, + )?; + + Ok(buffer) } } diff --git a/src/core/render/primitives/vertex.rs b/src/core/render/primitives/vertex.rs index 566df22..166ac44 100644 --- a/src/core/render/primitives/vertex.rs +++ b/src/core/render/primitives/vertex.rs @@ -10,3 +10,13 @@ pub struct Vertex2D { #[format(R32G32_SFLOAT)] pub uv: [f32; 2], } + +#[derive(BufferContents, Vertex)] +#[repr(C)] +pub struct Vertex3D { + #[format(R32G32B32_SFLOAT)] + pub position: [f32; 3], + + #[format(R32G32_SFLOAT)] + pub uv: [f32; 2], +} diff --git a/src/game/assets/square.rs b/src/game/assets/square.rs index acca0b6..ca16b93 100644 --- a/src/game/assets/square.rs +++ b/src/game/assets/square.rs @@ -30,26 +30,26 @@ use vulkano::{ }; use crate::core::render::{ - primitives::{mvp::MVP, vertex::Vertex2D}, + primitives::{mvp::MVP, transform::TransformRaw, vertex::Vertex3D}, texture::Texture, }; -const VERTICES: [Vertex2D; 4] = [ - Vertex2D { - position: [0.0, 0.0], +const VERTICES: [Vertex3D; 4] = [ + Vertex3D { + position: [-0.5, -0.5, 0.0], uv: [0.0, 0.0], }, - Vertex2D { - position: [0.0, 5.0], - uv: [0.0, 0.5], + Vertex3D { + position: [-0.5, 0.5, 0.0], + uv: [0.0, 1.0], }, - Vertex2D { - position: [10.0, 0.0], + Vertex3D { + position: [0.5, -0.5, 0.0], uv: [1.0, 0.0], }, - Vertex2D { - position: [10.0, 5.0], - uv: [1.0, 0.5], + Vertex3D { + position: [0.5, 0.5, 0.0], + uv: [1.0, 1.0], }, ]; @@ -74,7 +74,7 @@ pub mod shaders { } pub struct Square { - vertex_buffer: Subbuffer<[Vertex2D]>, + vertex_buffer: Subbuffer<[Vertex3D]>, index_buffer: Subbuffer<[u32]>, pipeline: Arc, } @@ -120,7 +120,8 @@ impl Square { .entry_point("main") .ok_or("Failed find main entry point of fragment shader".to_string())?; - let vertex_input_state = Vertex2D::per_vertex().definition(&vs)?; + let vertex_input_state = + [Vertex3D::per_vertex(), TransformRaw::per_instance()].definition(&vs)?; let stages = [ PipelineShaderStageCreateInfo::new(vs), @@ -207,6 +208,7 @@ impl Square { command_buffer: &mut AutoCommandBufferBuilder, descriptor_set_allocator: &Arc, mvp_uniform: &Subbuffer<[MVP]>, + transform_uniform: &Subbuffer<[TransformRaw]>, texture: &Texture, ) -> Result<(), Box> { let layouts = self.pipeline.layout().set_layouts(); @@ -235,11 +237,18 @@ impl Square { 0, vec![uniform_descriptor_set, texture_descriptor_set], )?; - command_buffer.bind_vertex_buffers(0, self.vertex_buffer.clone())?; + command_buffer + .bind_vertex_buffers(0, (self.vertex_buffer.clone(), transform_uniform.clone()))?; command_buffer.bind_index_buffer(self.index_buffer.clone())?; unsafe { - command_buffer.draw_indexed(INDICES.len() as u32, 1, 0, 0, 0)?; + command_buffer.draw_indexed( + INDICES.len() as u32, + transform_uniform.len() as u32, + 0, + 0, + 0, + )?; } Ok(()) diff --git a/src/game/main_scene.rs b/src/game/main_scene.rs index 7063d58..5fd757a 100644 --- a/src/game/main_scene.rs +++ b/src/game/main_scene.rs @@ -1,11 +1,14 @@ use std::{error::Error, sync::Arc}; use crate::core::render::primitives::camera::Camera3D; +use crate::core::render::primitives::transform::Transform; use crate::core::render::texture::Texture; use crate::core::scene::Scene; use crate::core::scene::SceneContext; use egui_winit_vulkano::{Gui, egui}; -use glam::Mat4; +use glam::EulerRot; +use glam::Quat; +use glam::Vec3; use vulkano::{ command_buffer::{ AutoCommandBufferBuilder, CommandBufferUsage, PrimaryCommandBufferAbstract, @@ -21,6 +24,7 @@ use super::assets::square::Square; pub struct MainSceneState { square: Square, + instances: Vec, camera: Camera3D, texture: Texture, speed: f32, @@ -43,6 +47,27 @@ impl Scene for MainScene { scene_context.swapchain_format, )?; + let num_instances = 100; + let instance_size = 10.0; + let instance_spacing = 10.0; + let num_instances_per_row = (num_instances as f32 / instance_spacing as f32).ceil() as u32; + let instances: Vec = (0..num_instances) + .map(|i| Transform { + position: Vec3::new( + (i % num_instances_per_row) as f32 * (instance_spacing + instance_size), + 0.0, + (i / num_instances_per_row) as f32 * (instance_spacing + instance_size), + ), + rotation: Quat::from_euler( + EulerRot::XYZ, + 0.0, + rand::random_range(0.0..=360.0), + 0.0, + ), + scale: Vec3::new(instance_size, instance_size, instance_size), + }) + .collect(); + let camera = Camera3D::new( scene_context.aspect_ratio, std::f32::consts::FRAC_PI_2, @@ -69,6 +94,7 @@ impl Scene for MainScene { self.state = Some(MainSceneState { square, + instances, camera, texture, speed: 50.0, @@ -129,12 +155,16 @@ impl Scene for MainScene { .camera .create_buffer(&scene_context.memory_allocator)?; + let transform_uniform = + Transform::create_buffer(&scene_context.memory_allocator, &state.instances)?; + state .square .render( &mut builder, &scene_context.descriptor_set_allocator, &camera_uniform, + &transform_uniform, &state.texture, ) .unwrap(); From 650b61e3ae48437b37063f3cb9f091f087e50c29 Mon Sep 17 00:00:00 2001 From: Florian RICHER Date: Thu, 29 May 2025 17:44:00 +0200 Subject: [PATCH 059/105] render: add depth buffer --- src/core/app.rs | 30 ++++++++++++++---------------- src/core/scene/manager.rs | 12 ++++++++---- src/game/assets/square.rs | 9 ++++++++- src/game/main_scene.rs | 31 +++++++++++++++++++++++++++++++ 4 files changed, 61 insertions(+), 21 deletions(-) diff --git a/src/core/app.rs b/src/core/app.rs index 33a5dc1..d80d5c6 100644 --- a/src/core/app.rs +++ b/src/core/app.rs @@ -130,26 +130,24 @@ impl ApplicationHandler for App { &self.input_manager, &self.timer, )); - self.scene_manager.load_scene_if_not_loaded(&scene_context); + + self.scene_manager + .load_scene_if_not_loaded(&scene_context) + .unwrap(); if let Some(scene) = self.scene_manager.current_scene_mut() { - if let Err(e) = scene.update(&scene_context) { - log::error!("Error updating scene: {}", e); - } + scene.update(&scene_context).unwrap(); let acquire_future = renderer.acquire(None, |_| {}).unwrap(); - let acquire_future = scene.render( - &renderer.swapchain_image_view(), - acquire_future, - &scene_context, - gui, - ); - match acquire_future { - Ok(future) => renderer.present(future, true), - Err(e) => { - log::error!("Error rendering scene: {}", e); - } - } + let acquire_future = scene + .render( + &renderer.swapchain_image_view(), + acquire_future, + &scene_context, + gui, + ) + .unwrap(); + renderer.present(acquire_future, true); } } _ => {} diff --git a/src/core/scene/manager.rs b/src/core/scene/manager.rs index e2043f2..e44765d 100644 --- a/src/core/scene/manager.rs +++ b/src/core/scene/manager.rs @@ -1,3 +1,5 @@ +use std::error::Error; + use super::{Scene, SceneContext}; pub struct SceneManager { @@ -11,14 +13,16 @@ impl SceneManager { } } - pub fn load_scene_if_not_loaded(&mut self, scene_context: &SceneContext) { + pub fn load_scene_if_not_loaded( + &mut self, + scene_context: &SceneContext, + ) -> Result<(), Box> { if let Some(current_scene) = self.current_scene.as_mut() { if !current_scene.loaded() { - if let Err(e) = current_scene.load(scene_context) { - log::error!("Error loading scene: {}", e); - } + current_scene.load(scene_context)?; } } + Ok(()) } pub fn load_scene(&mut self, scene: Box) { diff --git a/src/game/assets/square.rs b/src/game/assets/square.rs index ca16b93..80a24e2 100644 --- a/src/game/assets/square.rs +++ b/src/game/assets/square.rs @@ -17,7 +17,8 @@ use vulkano::{ graphics::{ GraphicsPipelineCreateInfo, color_blend::{ColorBlendAttachmentState, ColorBlendState}, - input_assembly::{InputAssemblyState, PrimitiveTopology}, + depth_stencil::{DepthState, DepthStencilState}, + input_assembly::InputAssemblyState, multisample::MultisampleState, rasterization::RasterizationState, subpass::PipelineRenderingCreateInfo, @@ -84,6 +85,7 @@ impl Square { device: &Arc, memory_allocator: &Arc, swapchain_format: Format, + depth_format: Format, ) -> Result> { let vertex_buffer = Buffer::from_iter( memory_allocator.clone(), @@ -173,6 +175,7 @@ impl Square { let subpass = PipelineRenderingCreateInfo { color_attachment_formats: vec![Some(swapchain_format)], + depth_attachment_format: Some(depth_format), ..Default::default() }; @@ -190,6 +193,10 @@ impl Square { subpass.color_attachment_formats.len() as u32, ColorBlendAttachmentState::default(), )), + depth_stencil_state: Some(DepthStencilState { + depth: Some(DepthState::simple()), + ..Default::default() + }), dynamic_state: [DynamicState::Viewport].into_iter().collect(), subpass: Some(subpass.into()), ..GraphicsPipelineCreateInfo::layout(layout) diff --git a/src/game/main_scene.rs b/src/game/main_scene.rs index 5fd757a..b1638c6 100644 --- a/src/game/main_scene.rs +++ b/src/game/main_scene.rs @@ -9,6 +9,12 @@ use egui_winit_vulkano::{Gui, egui}; use glam::EulerRot; use glam::Quat; use glam::Vec3; +use vulkano::format::Format; +use vulkano::image::Image; +use vulkano::image::ImageCreateInfo; +use vulkano::image::ImageLayout; +use vulkano::image::ImageUsage; +use vulkano::memory::allocator::AllocationCreateInfo; use vulkano::{ command_buffer::{ AutoCommandBufferBuilder, CommandBufferUsage, PrimaryCommandBufferAbstract, @@ -28,6 +34,7 @@ pub struct MainSceneState { camera: Camera3D, texture: Texture, speed: f32, + depth_image_view: Arc, } #[derive(Default)] @@ -41,10 +48,27 @@ impl Scene for MainScene { } fn load(&mut self, scene_context: &SceneContext) -> Result<(), Box> { + let depth_image = Image::new( + scene_context.memory_allocator.clone(), + ImageCreateInfo { + format: Format::D16_UNORM, + extent: [ + scene_context.window_size[0] as u32, + scene_context.window_size[1] as u32, + 1, + ], + usage: ImageUsage::DEPTH_STENCIL_ATTACHMENT, + ..Default::default() + }, + AllocationCreateInfo::default(), + )?; + let depth_image_view = ImageView::new_default(depth_image)?; + let square = Square::new( &scene_context.device, &scene_context.memory_allocator, scene_context.swapchain_format, + Format::D16_UNORM, )?; let num_instances = 100; @@ -98,6 +122,7 @@ impl Scene for MainScene { camera, texture, speed: 50.0, + depth_image_view, }); Ok(()) @@ -146,6 +171,12 @@ impl Scene for MainScene { clear_value: Some([0.0, 0.0, 0.0, 1.0].into()), ..RenderingAttachmentInfo::image_view(image_view.clone()) })], + depth_attachment: Some(RenderingAttachmentInfo { + load_op: AttachmentLoadOp::Clear, + store_op: AttachmentStoreOp::DontCare, + clear_value: Some([1.0].into()), + ..RenderingAttachmentInfo::image_view(state.depth_image_view.clone()) + }), ..Default::default() })? .set_viewport(0, [viewport].into_iter().collect())?; From 6a6b1821a4ee69ee72f0980c60f58c2b8c01615a Mon Sep 17 00:00:00 2001 From: Florian RICHER Date: Thu, 29 May 2025 18:16:26 +0200 Subject: [PATCH 060/105] depth: Fix not resized --- src/core/app.rs | 28 +++++++++++++--------- src/core/render/primitives/camera.rs | 2 +- src/core/scene/context.rs | 16 ++++++++----- src/core/scene/mod.rs | 1 - src/game/main_scene.rs | 35 ++++++++-------------------- 5 files changed, 38 insertions(+), 44 deletions(-) diff --git a/src/core/app.rs b/src/core/app.rs index d80d5c6..93ef9bc 100644 --- a/src/core/app.rs +++ b/src/core/app.rs @@ -8,6 +8,8 @@ use crate::core::scene::SceneManager; use crate::core::timer::Timer; use crate::game::main_scene::MainScene; use egui_winit_vulkano::{Gui, GuiConfig}; +use vulkano::format::Format; +use vulkano::image::ImageUsage; use vulkano::swapchain::PresentMode; use vulkano_util::context::VulkanoContext; use vulkano_util::window::{VulkanoWindows, WindowDescriptor}; @@ -16,6 +18,8 @@ use winit::event::WindowEvent; use winit::event_loop::ActiveEventLoop; use winit::window::WindowId; +pub const DEPTH_IMAGE_ID: usize = 0; + pub struct App { vulkan_context: Arc, vulkano_windows: Arc, @@ -56,8 +60,14 @@ impl ApplicationHandler for App { |_| {}, ); + let renderer = vulkano_windows.get_renderer_mut(window_id).unwrap(); + renderer.add_additional_image_view( + DEPTH_IMAGE_ID, + Format::D16_UNORM, + ImageUsage::DEPTH_STENCIL_ATTACHMENT, + ); + let gui = { - let renderer = vulkano_windows.get_renderer_mut(window_id).unwrap(); Gui::new( event_loop, renderer.surface(), @@ -124,8 +134,8 @@ impl ApplicationHandler for App { None => log::error!("Failed to get a mutable reference to the timer"), } - let scene_context = SceneContext::from(( - &*renderer, + let mut scene_context = SceneContext::from(( + &mut *renderer, &self.vulkan_context, &self.input_manager, &self.timer, @@ -139,14 +149,10 @@ impl ApplicationHandler for App { scene.update(&scene_context).unwrap(); let acquire_future = renderer.acquire(None, |_| {}).unwrap(); - let acquire_future = scene - .render( - &renderer.swapchain_image_view(), - acquire_future, - &scene_context, - gui, - ) - .unwrap(); + // Update the swapchain image view to the current one after acquiring the next image + scene_context.swapchain_image_view = renderer.swapchain_image_view(); + + let acquire_future = scene.render(acquire_future, &scene_context, gui).unwrap(); renderer.present(acquire_future, true); } } diff --git a/src/core/render/primitives/camera.rs b/src/core/render/primitives/camera.rs index 5ea52e7..e8cdf1e 100644 --- a/src/core/render/primitives/camera.rs +++ b/src/core/render/primitives/camera.rs @@ -55,7 +55,7 @@ impl Camera3D { // Process camera rotation let camera_delta = camera_sensitivity * timer.delta_time(); self.rotation += Vec3::new( - -(input_manager.get_virtual_input_state("mouse_y") * camera_delta).to_radians(), + (input_manager.get_virtual_input_state("mouse_y") * camera_delta).to_radians(), (input_manager.get_virtual_input_state("mouse_x") * camera_delta).to_radians(), 0.0, ); diff --git a/src/core/scene/context.rs b/src/core/scene/context.rs index 028d612..545f39c 100644 --- a/src/core/scene/context.rs +++ b/src/core/scene/context.rs @@ -4,13 +4,15 @@ use vulkano::{ command_buffer::allocator::StandardCommandBufferAllocator, descriptor_set::allocator::StandardDescriptorSetAllocator, device::{Device, Queue}, - format::Format, + image::view::ImageView, instance::Instance, memory::allocator::StandardMemoryAllocator, }; use vulkano_util::renderer::VulkanoWindowRenderer; -use crate::core::{input::InputManager, render::vulkan_context::VulkanContext, timer::Timer}; +use crate::core::{ + app::DEPTH_IMAGE_ID, input::InputManager, render::vulkan_context::VulkanContext, timer::Timer, +}; pub struct SceneContext { pub instance: Arc, @@ -23,14 +25,15 @@ pub struct SceneContext { pub descriptor_set_allocator: Arc, pub window_size: [f32; 2], pub aspect_ratio: f32, - pub swapchain_format: Format, + pub swapchain_image_view: Arc, + pub depth_stencil_image_view: Arc, pub input_manager: Arc, pub timer: Arc, } impl From<( - &VulkanoWindowRenderer, + &mut VulkanoWindowRenderer, &Arc, &Arc, &Arc, @@ -38,7 +41,7 @@ impl { fn from( (renderer, vulkan_context, input_manager, timer): ( - &VulkanoWindowRenderer, + &mut VulkanoWindowRenderer, &Arc, &Arc, &Arc, @@ -74,7 +77,8 @@ impl descriptor_set_allocator, window_size: renderer.window_size(), aspect_ratio: renderer.aspect_ratio(), - swapchain_format: renderer.swapchain_format(), + swapchain_image_view: renderer.swapchain_image_view(), + depth_stencil_image_view: renderer.get_additional_image_view(DEPTH_IMAGE_ID), input_manager: input_manager.clone(), timer: timer.clone(), } diff --git a/src/core/scene/mod.rs b/src/core/scene/mod.rs index a71bfbb..8642224 100644 --- a/src/core/scene/mod.rs +++ b/src/core/scene/mod.rs @@ -15,7 +15,6 @@ pub trait Scene { fn update(&mut self, scene_context: &SceneContext) -> Result<(), Box>; fn render( &mut self, - image_view: &Arc, acquire_future: Box, scene_context: &SceneContext, gui: &mut Gui, diff --git a/src/game/main_scene.rs b/src/game/main_scene.rs index b1638c6..22c851b 100644 --- a/src/game/main_scene.rs +++ b/src/game/main_scene.rs @@ -12,7 +12,6 @@ use glam::Vec3; use vulkano::format::Format; use vulkano::image::Image; use vulkano::image::ImageCreateInfo; -use vulkano::image::ImageLayout; use vulkano::image::ImageUsage; use vulkano::memory::allocator::AllocationCreateInfo; use vulkano::{ @@ -34,7 +33,6 @@ pub struct MainSceneState { camera: Camera3D, texture: Texture, speed: f32, - depth_image_view: Arc, } #[derive(Default)] @@ -48,27 +46,11 @@ impl Scene for MainScene { } fn load(&mut self, scene_context: &SceneContext) -> Result<(), Box> { - let depth_image = Image::new( - scene_context.memory_allocator.clone(), - ImageCreateInfo { - format: Format::D16_UNORM, - extent: [ - scene_context.window_size[0] as u32, - scene_context.window_size[1] as u32, - 1, - ], - usage: ImageUsage::DEPTH_STENCIL_ATTACHMENT, - ..Default::default() - }, - AllocationCreateInfo::default(), - )?; - let depth_image_view = ImageView::new_default(depth_image)?; - let square = Square::new( &scene_context.device, &scene_context.memory_allocator, - scene_context.swapchain_format, - Format::D16_UNORM, + scene_context.swapchain_image_view.format(), + scene_context.depth_stencil_image_view.format(), )?; let num_instances = 100; @@ -122,7 +104,6 @@ impl Scene for MainScene { camera, texture, speed: 50.0, - depth_image_view, }); Ok(()) @@ -143,7 +124,6 @@ impl Scene for MainScene { fn render( &mut self, - image_view: &Arc, acquire_future: Box, scene_context: &SceneContext, gui: &mut Gui, @@ -169,13 +149,17 @@ impl Scene for MainScene { load_op: AttachmentLoadOp::Clear, store_op: AttachmentStoreOp::Store, clear_value: Some([0.0, 0.0, 0.0, 1.0].into()), - ..RenderingAttachmentInfo::image_view(image_view.clone()) + ..RenderingAttachmentInfo::image_view( + scene_context.swapchain_image_view.clone(), + ) })], depth_attachment: Some(RenderingAttachmentInfo { load_op: AttachmentLoadOp::Clear, store_op: AttachmentStoreOp::DontCare, clear_value: Some([1.0].into()), - ..RenderingAttachmentInfo::image_view(state.depth_image_view.clone()) + ..RenderingAttachmentInfo::image_view( + scene_context.depth_stencil_image_view.clone(), + ) }), ..Default::default() })? @@ -230,7 +214,8 @@ impl Scene for MainScene { }); }); - let render_future = gui.draw_on_image(render_future, image_view.clone()); + let render_future = + gui.draw_on_image(render_future, scene_context.swapchain_image_view.clone()); Ok(render_future) } From f1ae54bc7383d5499174567a2fc17f80271deb6e Mon Sep 17 00:00:00 2001 From: Florian RICHER Date: Thu, 29 May 2025 21:38:07 +0200 Subject: [PATCH 061/105] winit: Use user event --- src/core/{app.rs => app/mod.rs} | 55 +++++++++++--- src/core/app/user_event.rs | 9 +++ src/core/scene/context.rs | 29 ++++++-- src/game/mod.rs | 2 +- src/game/{ => scenes}/main_scene.rs | 43 +++++++++-- src/game/scenes/mod.rs | 2 + src/game/scenes/test_scene.rs | 111 ++++++++++++++++++++++++++++ src/main.rs | 5 +- 8 files changed, 227 insertions(+), 29 deletions(-) rename src/core/{app.rs => app/mod.rs} (75%) create mode 100644 src/core/app/user_event.rs rename src/game/{ => scenes}/main_scene.rs (85%) create mode 100644 src/game/scenes/mod.rs create mode 100644 src/game/scenes/test_scene.rs diff --git a/src/core/app.rs b/src/core/app/mod.rs similarity index 75% rename from src/core/app.rs rename to src/core/app/mod.rs index 93ef9bc..ab548f8 100644 --- a/src/core/app.rs +++ b/src/core/app/mod.rs @@ -6,8 +6,9 @@ use super::scene::SceneContext; use crate::core::input::InputManager; use crate::core::scene::SceneManager; use crate::core::timer::Timer; -use crate::game::main_scene::MainScene; +use crate::game::scenes::test_scene::TestScene; use egui_winit_vulkano::{Gui, GuiConfig}; +use user_event::UserEvent; use vulkano::format::Format; use vulkano::image::ImageUsage; use vulkano::swapchain::PresentMode; @@ -15,34 +16,42 @@ use vulkano_util::context::VulkanoContext; use vulkano_util::window::{VulkanoWindows, WindowDescriptor}; use winit::application::ApplicationHandler; use winit::event::WindowEvent; -use winit::event_loop::ActiveEventLoop; +use winit::event_loop::{ActiveEventLoop, EventLoopProxy}; use winit::window::WindowId; +pub mod user_event; + pub const DEPTH_IMAGE_ID: usize = 0; pub struct App { vulkan_context: Arc, vulkano_windows: Arc, gui: HashMap, - scene_manager: SceneManager, + scene_manager: HashMap, input_manager: Arc, timer: Arc, + event_loop_proxy: EventLoopProxy, } impl App { - pub fn new(vulkano_context: VulkanoContext, input_manager: InputManager) -> Self { + pub fn new( + vulkano_context: VulkanoContext, + input_manager: InputManager, + event_loop_proxy: EventLoopProxy, + ) -> Self { Self { vulkan_context: Arc::new(VulkanContext::new(vulkano_context)), vulkano_windows: Arc::new(VulkanoWindows::default()), gui: HashMap::new(), input_manager: Arc::new(input_manager), - scene_manager: SceneManager::new(), + scene_manager: HashMap::new(), timer: Arc::new(Timer::new()), + event_loop_proxy, } } } -impl ApplicationHandler for App { +impl ApplicationHandler for App { fn resumed(&mut self, event_loop: &ActiveEventLoop) { if let Some(vulkano_windows) = Arc::get_mut(&mut self.vulkano_windows) { let window_id = vulkano_windows.create_window( @@ -81,10 +90,12 @@ impl ApplicationHandler for App { ) }; self.gui.insert(window_id, gui); - } - self.scene_manager - .load_scene(Box::new(MainScene::default())); + let mut scene_manager = SceneManager::new(); + scene_manager.load_scene(Box::new(TestScene::default())); + + self.scene_manager.insert(window_id, scene_manager); + } } fn device_event( @@ -139,13 +150,16 @@ impl ApplicationHandler for App { &self.vulkan_context, &self.input_manager, &self.timer, + id, + &self.event_loop_proxy, )); - self.scene_manager + let scene_manager = self.scene_manager.get_mut(&id).unwrap(); + scene_manager .load_scene_if_not_loaded(&scene_context) .unwrap(); - if let Some(scene) = self.scene_manager.current_scene_mut() { + if let Some(scene) = scene_manager.current_scene_mut() { scene.update(&scene_context).unwrap(); let acquire_future = renderer.acquire(None, |_| {}).unwrap(); @@ -164,4 +178,23 @@ impl ApplicationHandler for App { let window = self.vulkano_windows.get_primary_window().unwrap(); window.request_redraw(); } + + fn user_event(&mut self, event_loop: &ActiveEventLoop, event: UserEvent) { + match event { + UserEvent::CursorGrabMode(window_id, grab) => { + let window = self.vulkano_windows.get_window(window_id).unwrap(); + if let Err(e) = window.set_cursor_grab(grab) { + log::error!("Failed to set cursor grab: {}", e); + } + } + UserEvent::CursorVisible(window_id, visible) => { + let window = self.vulkano_windows.get_window(window_id).unwrap(); + window.set_cursor_visible(visible); + } + UserEvent::ChangeScene(window_id, scene) => { + let scene_manager = self.scene_manager.get_mut(&window_id).unwrap(); + scene_manager.load_scene(scene); + } + } + } } diff --git a/src/core/app/user_event.rs b/src/core/app/user_event.rs new file mode 100644 index 0000000..316ac4c --- /dev/null +++ b/src/core/app/user_event.rs @@ -0,0 +1,9 @@ +use winit::window::{CursorGrabMode, WindowId}; + +use crate::core::scene::Scene; + +pub enum UserEvent { + CursorGrabMode(WindowId, CursorGrabMode), + CursorVisible(WindowId, bool), + ChangeScene(WindowId, Box), +} diff --git a/src/core/scene/context.rs b/src/core/scene/context.rs index 545f39c..4e0dab3 100644 --- a/src/core/scene/context.rs +++ b/src/core/scene/context.rs @@ -9,12 +9,19 @@ use vulkano::{ memory::allocator::StandardMemoryAllocator, }; use vulkano_util::renderer::VulkanoWindowRenderer; - -use crate::core::{ - app::DEPTH_IMAGE_ID, input::InputManager, render::vulkan_context::VulkanContext, timer::Timer, +use winit::{ + event_loop::EventLoopProxy, + window::{Window, WindowId}, }; -pub struct SceneContext { +use crate::core::{ + app::{DEPTH_IMAGE_ID, user_event::UserEvent}, + input::InputManager, + render::vulkan_context::VulkanContext, + timer::Timer, +}; + +pub struct SceneContext<'a> { pub instance: Arc, pub device: Arc, pub graphics_queue: Arc, @@ -29,22 +36,28 @@ pub struct SceneContext { pub depth_stencil_image_view: Arc, pub input_manager: Arc, pub timer: Arc, + pub event_loop_proxy: &'a EventLoopProxy, + pub window_id: WindowId, } -impl +impl<'a> From<( &mut VulkanoWindowRenderer, &Arc, &Arc, &Arc, - )> for SceneContext + WindowId, + &'a EventLoopProxy, + )> for SceneContext<'a> { fn from( - (renderer, vulkan_context, input_manager, timer): ( + (renderer, vulkan_context, input_manager, timer, window_id, event_loop_proxy): ( &mut VulkanoWindowRenderer, &Arc, &Arc, &Arc, + WindowId, + &'a EventLoopProxy, ), ) -> Self { let (command_buffer_allocator, descriptor_set_allocator) = { @@ -81,6 +94,8 @@ impl depth_stencil_image_view: renderer.get_additional_image_view(DEPTH_IMAGE_ID), input_manager: input_manager.clone(), timer: timer.clone(), + event_loop_proxy, + window_id, } } } diff --git a/src/game/mod.rs b/src/game/mod.rs index e8924d3..065e8e4 100644 --- a/src/game/mod.rs +++ b/src/game/mod.rs @@ -1,2 +1,2 @@ pub mod assets; -pub mod main_scene; +pub mod scenes; diff --git a/src/game/main_scene.rs b/src/game/scenes/main_scene.rs similarity index 85% rename from src/game/main_scene.rs rename to src/game/scenes/main_scene.rs index 22c851b..bad12d0 100644 --- a/src/game/main_scene.rs +++ b/src/game/scenes/main_scene.rs @@ -1,31 +1,26 @@ use std::{error::Error, sync::Arc}; +use crate::core::app::user_event::UserEvent; use crate::core::render::primitives::camera::Camera3D; use crate::core::render::primitives::transform::Transform; use crate::core::render::texture::Texture; use crate::core::scene::Scene; use crate::core::scene::SceneContext; +use crate::game::assets::square::Square; use egui_winit_vulkano::{Gui, egui}; use glam::EulerRot; use glam::Quat; use glam::Vec3; -use vulkano::format::Format; -use vulkano::image::Image; -use vulkano::image::ImageCreateInfo; -use vulkano::image::ImageUsage; -use vulkano::memory::allocator::AllocationCreateInfo; use vulkano::{ command_buffer::{ AutoCommandBufferBuilder, CommandBufferUsage, PrimaryCommandBufferAbstract, RenderingAttachmentInfo, RenderingInfo, }, - image::view::ImageView, pipeline::graphics::viewport::Viewport, render_pass::{AttachmentLoadOp, AttachmentStoreOp}, sync::GpuFuture, }; - -use super::assets::square::Square; +use winit::window::CursorGrabMode; pub struct MainSceneState { square: Square, @@ -119,6 +114,38 @@ impl Scene for MainScene { scene_context.aspect_ratio, ); + if scene_context + .input_manager + .get_virtual_input_state("mouse_left") + > 0.0 + { + let _ = scene_context + .event_loop_proxy + .send_event(UserEvent::CursorVisible(scene_context.window_id, false)); + let _ = scene_context + .event_loop_proxy + .send_event(UserEvent::CursorGrabMode( + scene_context.window_id, + CursorGrabMode::Locked, + )); + } + + if scene_context + .input_manager + .get_virtual_input_state("mouse_right") + > 0.0 + { + let _ = scene_context + .event_loop_proxy + .send_event(UserEvent::CursorVisible(scene_context.window_id, true)); + let _ = scene_context + .event_loop_proxy + .send_event(UserEvent::CursorGrabMode( + scene_context.window_id, + CursorGrabMode::None, + )); + } + Ok(()) } diff --git a/src/game/scenes/mod.rs b/src/game/scenes/mod.rs new file mode 100644 index 0000000..e5c4945 --- /dev/null +++ b/src/game/scenes/mod.rs @@ -0,0 +1,2 @@ +pub mod main_scene; +pub mod test_scene; diff --git a/src/game/scenes/test_scene.rs b/src/game/scenes/test_scene.rs new file mode 100644 index 0000000..9ed8707 --- /dev/null +++ b/src/game/scenes/test_scene.rs @@ -0,0 +1,111 @@ +use std::error::Error; + +use crate::core::app::user_event::UserEvent; +use crate::core::scene::Scene; +use crate::core::scene::SceneContext; +use egui_winit_vulkano::{Gui, egui}; +use vulkano::command_buffer::AutoCommandBufferBuilder; +use vulkano::command_buffer::CommandBufferUsage; +use vulkano::command_buffer::RenderingAttachmentInfo; +use vulkano::command_buffer::RenderingInfo; +use vulkano::pipeline::graphics::viewport::Viewport; +use vulkano::render_pass::AttachmentLoadOp; +use vulkano::render_pass::AttachmentStoreOp; +use vulkano::sync::GpuFuture; + +use super::main_scene::MainScene; + +pub struct MainSceneState {} + +#[derive(Default)] +pub struct TestScene { + state: Option, +} + +impl Scene for TestScene { + fn loaded(&self) -> bool { + self.state.is_some() + } + + fn load(&mut self, scene_context: &SceneContext) -> Result<(), Box> { + self.state = Some(MainSceneState {}); + + Ok(()) + } + + fn update(&mut self, scene_context: &SceneContext) -> Result<(), Box> { + Ok(()) + } + + fn render( + &mut self, + acquire_future: Box, + scene_context: &SceneContext, + gui: &mut Gui, + ) -> Result, Box> { + let mut builder = AutoCommandBufferBuilder::primary( + scene_context.command_buffer_allocator.clone(), + scene_context.graphics_queue.queue_family_index(), + CommandBufferUsage::OneTimeSubmit, + )?; + + { + let viewport = Viewport { + offset: [0.0, 0.0], + extent: scene_context.window_size, + depth_range: 0.0..=1.0, + }; + + builder + .begin_rendering(RenderingInfo { + color_attachments: vec![Some(RenderingAttachmentInfo { + load_op: AttachmentLoadOp::Clear, + store_op: AttachmentStoreOp::Store, + clear_value: Some([0.0, 0.0, 0.0, 1.0].into()), + ..RenderingAttachmentInfo::image_view( + scene_context.swapchain_image_view.clone(), + ) + })], + depth_attachment: Some(RenderingAttachmentInfo { + load_op: AttachmentLoadOp::Clear, + store_op: AttachmentStoreOp::DontCare, + clear_value: Some([1.0].into()), + ..RenderingAttachmentInfo::image_view( + scene_context.depth_stencil_image_view.clone(), + ) + }), + ..Default::default() + })? + .set_viewport(0, [viewport].into_iter().collect())?; + } + + builder.end_rendering()?; + + let command_buffer = builder.build()?; + + let render_future = + acquire_future.then_execute(scene_context.graphics_queue.clone(), command_buffer)?; + + gui.immediate_ui(|gui| { + let ctx = gui.context(); + + egui::CentralPanel::default().show(&ctx, |ui| { + if ui.button("Start Game").clicked() { + let _ = scene_context + .event_loop_proxy + .send_event(UserEvent::ChangeScene( + scene_context.window_id, + Box::new(MainScene::default()), + )); + } + }); + }); + + let render_future = + gui.draw_on_image(render_future, scene_context.swapchain_image_view.clone()); + + Ok(render_future) + } + + fn unload(&mut self) {} +} diff --git a/src/main.rs b/src/main.rs index ffe5f72..1ea49c3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -77,10 +77,11 @@ fn main() { let vulkano_context = VulkanoContext::new(vulkano_config); - let event_loop = EventLoop::new().unwrap(); + let event_loop = EventLoop::with_user_event().build().unwrap(); event_loop.set_control_flow(ControlFlow::Poll); + let proxy = event_loop.create_proxy(); - let mut app = core::app::App::new(vulkano_context, input_manager); + let mut app = core::app::App::new(vulkano_context, input_manager, proxy); match event_loop.run_app(&mut app) { Ok(_) => {} From 2c169548b99a307fa082b0b0a8467cba5274fb39 Mon Sep 17 00:00:00 2001 From: Florian RICHER Date: Fri, 30 May 2025 19:04:46 +0200 Subject: [PATCH 062/105] Refactor app context and render pass manager --- src/core/app/context.rs | 230 +++++++++++++++++++++ src/core/app/mod.rs | 232 ++++++++++++--------- src/core/app/user_event.rs | 2 + src/core/render/mod.rs | 1 + src/core/render/render_pass_manager.rs | 113 +++++++++++ src/core/scene/context.rs | 101 --------- src/core/scene/manager.rs | 96 ++++++--- src/core/scene/mod.rs | 18 +- src/game/scenes/main_scene.rs | 271 ++++++++++++++----------- src/game/scenes/mod.rs | 2 +- src/game/scenes/settings_scene.rs | 135 ++++++++++++ src/game/scenes/test_scene.rs | 111 ---------- 12 files changed, 846 insertions(+), 466 deletions(-) create mode 100644 src/core/app/context.rs create mode 100644 src/core/render/render_pass_manager.rs delete mode 100644 src/core/scene/context.rs create mode 100644 src/game/scenes/settings_scene.rs delete mode 100644 src/game/scenes/test_scene.rs diff --git a/src/core/app/context.rs b/src/core/app/context.rs new file mode 100644 index 0000000..1c0ccb6 --- /dev/null +++ b/src/core/app/context.rs @@ -0,0 +1,230 @@ +use std::sync::{Arc, Mutex}; + +use egui_winit_vulkano::Gui; +use vulkano::{ + command_buffer::allocator::StandardCommandBufferAllocator, + descriptor_set::allocator::StandardDescriptorSetAllocator, + device::{Device, Queue}, + instance::Instance, + memory::allocator::StandardMemoryAllocator, +}; +use vulkano_util::{renderer::VulkanoWindowRenderer, window::VulkanoWindows}; +use winit::{ + event_loop::EventLoopProxy, + window::{Window, WindowId}, +}; + +use crate::core::{input::InputManager, render::vulkan_context::VulkanContext, timer::Timer}; + +use super::user_event::UserEvent; + +/// Contexte d'application unifié avec Arc> pour la mutabilité partagée +#[derive(Clone)] +pub struct ApplicationContext { + // Données Vulkan (immutables) + pub vulkan_context: Arc, + pub device: Arc, + pub instance: Arc, + pub graphics_queue: Arc, + pub compute_queue: Arc, + pub transfer_queue: Option>, + pub memory_allocator: Arc, + pub command_buffer_allocator: Arc, + pub descriptor_set_allocator: Arc, + pub event_loop_proxy: EventLoopProxy, + pub window_id: WindowId, + + // Données mutables partagées avec Arc> + pub vulkano_windows: Arc>, + pub input_manager: Arc>, + pub timer: Arc>, + pub gui: Arc>, +} + +impl ApplicationContext { + pub fn new( + vulkan_context: Arc, + vulkano_windows: Arc>, + input_manager: Arc>, + timer: Arc>, + gui: Arc>, + event_loop_proxy: EventLoopProxy, + window_id: WindowId, + ) -> Self { + let vulkano_context_inner = vulkan_context.vulkano_context(); + + Self { + // Données Vulkan + vulkan_context: vulkan_context.clone(), + device: vulkano_context_inner.device().clone(), + instance: vulkano_context_inner.instance().clone(), + graphics_queue: vulkano_context_inner.graphics_queue().clone(), + compute_queue: vulkano_context_inner.compute_queue().clone(), + transfer_queue: vulkano_context_inner.transfer_queue().cloned(), + memory_allocator: vulkano_context_inner.memory_allocator().clone(), + command_buffer_allocator: vulkan_context.command_buffer_allocator().clone(), + descriptor_set_allocator: vulkan_context.descriptor_set_allocator().clone(), + event_loop_proxy, + window_id, + + // Données mutables partagées + vulkano_windows, + input_manager, + timer, + gui, + } + } + + /// Récupère les résolutions disponibles du moniteur (méthode utilitaire statique) + fn get_monitor_resolutions(window: &Window) -> Vec<(u32, u32)> { + // Première tentative : moniteur actuel + if let Some(monitor) = window.current_monitor() { + let resolutions = Self::extract_resolutions_from_monitor(&monitor); + if !resolutions.is_empty() { + log::debug!( + "Résolutions trouvées via moniteur actuel: {:?}", + resolutions + ); + return resolutions; + } + } + + // Deuxième tentative : tous les moniteurs disponibles + log::debug!("Tentative de récupération via tous les moniteurs disponibles..."); + let mut all_resolutions = Vec::new(); + + for monitor in window.available_monitors() { + let resolutions = Self::extract_resolutions_from_monitor(&monitor); + all_resolutions.extend(resolutions); + } + + if !all_resolutions.is_empty() { + // Supprime les doublons et trie + all_resolutions.sort_unstable(); + all_resolutions.dedup(); + all_resolutions.sort_by(|a, b| (b.0 * b.1).cmp(&(a.0 * a.1))); + + log::debug!( + "Résolutions trouvées via tous les moniteurs: {:?}", + all_resolutions + ); + return all_resolutions; + } + + // Aucune résolution détectée - retourne un vecteur vide + log::warn!("Aucune résolution détectée pour cette fenêtre"); + Vec::new() + } + + /// Extrait les résolutions d'un moniteur donné + fn extract_resolutions_from_monitor( + monitor: &winit::monitor::MonitorHandle, + ) -> Vec<(u32, u32)> { + let video_modes: Vec<_> = monitor.video_modes().collect(); + + if video_modes.is_empty() { + log::debug!( + "Aucun mode vidéo trouvé pour le moniteur {:?}", + monitor.name() + ); + return Vec::new(); + } + + let resolutions: Vec<(u32, u32)> = video_modes + .into_iter() + .map(|mode| { + let size = mode.size(); + (size.width, size.height) + }) + .collect(); + + log::debug!( + "Modes vidéo trouvés pour {:?}: {:?}", + monitor.name(), + resolutions + ); + resolutions + } + + /// Récupère les résolutions disponibles + pub fn get_available_resolutions(&self) -> Vec<(u32, u32)> { + self.with_renderer(|renderer| Self::get_monitor_resolutions(&renderer.window())) + } + + /// Récupère le delta time actuel depuis le timer + pub fn get_delta_time(&self) -> f32 { + self.with_timer(|timer| timer.delta_time()) + } + + /// Récupère la taille de la fenêtre depuis le renderer + pub fn get_window_size(&self) -> [f32; 2] { + self.with_renderer(|renderer| renderer.window_size()) + } + + /// Récupère l'aspect ratio depuis le renderer + pub fn get_aspect_ratio(&self) -> f32 { + self.with_renderer(|renderer| renderer.aspect_ratio()) + } + + pub fn with_renderer(&self, f: F) -> T + where + F: FnOnce(&VulkanoWindowRenderer) -> T, + { + let vulkano_windows = self + .vulkano_windows + .lock() + .expect("Failed to lock vulkano_windows"); + let renderer = vulkano_windows + .get_renderer(self.window_id) + .expect("Failed to get renderer"); + f(&renderer) + } + + pub fn with_renderer_mut(&mut self, f: F) -> T + where + F: FnOnce(&mut VulkanoWindowRenderer) -> T, + { + let mut vulkano_windows = self + .vulkano_windows + .lock() + .expect("Failed to lock vulkano_windows"); + let renderer = vulkano_windows + .get_renderer_mut(self.window_id) + .expect("Failed to get renderer"); + f(renderer) + } + + pub fn with_gui(&self, f: F) -> T + where + F: FnOnce(&Gui) -> T, + { + let gui = self.gui.lock().unwrap(); + f(&gui) + } + + pub fn with_gui_mut(&mut self, f: F) -> T + where + F: FnOnce(&mut Gui) -> T, + { + let mut gui = self.gui.lock().unwrap(); + f(&mut gui) + } + + /// Méthode utilitaire pour accéder à l'input manager de manière thread-safe + pub fn with_input_manager(&self, f: F) -> T + where + F: FnOnce(&InputManager) -> T, + { + let input_manager = self.input_manager.lock().unwrap(); + f(&input_manager) + } + + /// Méthode utilitaire pour accéder au timer de manière thread-safe + pub fn with_timer(&self, f: F) -> T + where + F: FnOnce(&Timer) -> T, + { + let timer = self.timer.lock().unwrap(); + f(&timer) + } +} diff --git a/src/core/app/mod.rs b/src/core/app/mod.rs index ab548f8..243420b 100644 --- a/src/core/app/mod.rs +++ b/src/core/app/mod.rs @@ -1,12 +1,11 @@ use std::collections::HashMap; -use std::sync::Arc; +use std::sync::{Arc, Mutex}; use super::render::vulkan_context::VulkanContext; -use super::scene::SceneContext; use crate::core::input::InputManager; -use crate::core::scene::SceneManager; +use crate::core::scene::manager::SceneManager; use crate::core::timer::Timer; -use crate::game::scenes::test_scene::TestScene; +use crate::game::scenes::main_scene::MainScene; use egui_winit_vulkano::{Gui, GuiConfig}; use user_event::UserEvent; use vulkano::format::Format; @@ -19,18 +18,23 @@ use winit::event::WindowEvent; use winit::event_loop::{ActiveEventLoop, EventLoopProxy}; use winit::window::WindowId; +use self::context::ApplicationContext; + +pub mod context; pub mod user_event; pub const DEPTH_IMAGE_ID: usize = 0; pub struct App { vulkan_context: Arc, - vulkano_windows: Arc, - gui: HashMap, + vulkano_windows: Arc>, + gui: HashMap>>, scene_manager: HashMap, - input_manager: Arc, - timer: Arc, + input_manager: Arc>, + timer: Arc>, event_loop_proxy: EventLoopProxy, + // Context d'application partagé par fenêtre - architecture unifiée + app_contexts: HashMap>>, } impl App { @@ -41,61 +45,61 @@ impl App { ) -> Self { Self { vulkan_context: Arc::new(VulkanContext::new(vulkano_context)), - vulkano_windows: Arc::new(VulkanoWindows::default()), + vulkano_windows: Arc::new(Mutex::new(VulkanoWindows::default())), gui: HashMap::new(), - input_manager: Arc::new(input_manager), + input_manager: Arc::new(Mutex::new(input_manager)), scene_manager: HashMap::new(), - timer: Arc::new(Timer::new()), + timer: Arc::new(Mutex::new(Timer::new())), event_loop_proxy, + app_contexts: HashMap::new(), } } } impl ApplicationHandler for App { fn resumed(&mut self, event_loop: &ActiveEventLoop) { - if let Some(vulkano_windows) = Arc::get_mut(&mut self.vulkano_windows) { - let window_id = vulkano_windows.create_window( + let mut vulkano_windows = self.vulkano_windows.lock().unwrap(); + let window_id = vulkano_windows.create_window( + event_loop, + &self.vulkan_context.vulkano_context(), + &WindowDescriptor { + title: "Rust ASH Test".to_string(), + width: 800.0, + height: 600.0, + present_mode: PresentMode::Fifo, + cursor_visible: false, + cursor_locked: true, + ..Default::default() + }, + |_| {}, + ); + + let renderer = vulkano_windows.get_renderer_mut(window_id).unwrap(); + renderer.add_additional_image_view( + DEPTH_IMAGE_ID, + Format::D16_UNORM, + ImageUsage::DEPTH_STENCIL_ATTACHMENT, + ); + + let gui = { + Gui::new( event_loop, - self.vulkan_context.vulkano_context(), - &WindowDescriptor { - title: "Rust ASH Test".to_string(), - width: 800.0, - height: 600.0, - present_mode: PresentMode::Fifo, - cursor_visible: false, - cursor_locked: true, + renderer.surface(), + renderer.graphics_queue(), + renderer.swapchain_format(), + GuiConfig { + is_overlay: true, + allow_srgb_render_target: true, ..Default::default() }, - |_| {}, - ); + ) + }; + self.gui.insert(window_id, Arc::new(Mutex::new(gui))); - let renderer = vulkano_windows.get_renderer_mut(window_id).unwrap(); - renderer.add_additional_image_view( - DEPTH_IMAGE_ID, - Format::D16_UNORM, - ImageUsage::DEPTH_STENCIL_ATTACHMENT, - ); + let mut scene_manager = SceneManager::new(); + scene_manager.load_scene(Box::new(MainScene::default())); - let gui = { - Gui::new( - event_loop, - renderer.surface(), - renderer.graphics_queue(), - renderer.swapchain_format(), - GuiConfig { - is_overlay: true, - allow_srgb_render_target: true, - ..Default::default() - }, - ) - }; - self.gui.insert(window_id, gui); - - let mut scene_manager = SceneManager::new(); - scene_manager.load_scene(Box::new(TestScene::default())); - - self.scene_manager.insert(window_id, scene_manager); - } + self.scene_manager.insert(window_id, scene_manager); } fn device_event( @@ -104,23 +108,17 @@ impl ApplicationHandler for App { _device_id: winit::event::DeviceId, event: winit::event::DeviceEvent, ) { - match Arc::get_mut(&mut self.input_manager) { - Some(input_manager) => input_manager.process_device_event(&event), - None => log::error!("Failed to get a mutable reference to the input manager"), - } + let mut input_manager = self.input_manager.lock().unwrap(); + input_manager.process_device_event(&event); } fn window_event(&mut self, event_loop: &ActiveEventLoop, id: WindowId, event: WindowEvent) { - let renderer = Arc::get_mut(&mut self.vulkano_windows) - .unwrap() - .get_renderer_mut(id) - .unwrap(); - let gui = self.gui.get_mut(&id).unwrap(); - - if !gui.update(&event) { - match Arc::get_mut(&mut self.input_manager) { - Some(input_manager) => input_manager.process_window_event(&event), - None => log::error!("Failed to get a mutable reference to the input manager"), + { + let gui = self.gui.get_mut(&id).unwrap(); + let mut gui = gui.lock().unwrap(); + if !gui.update(&event) { + let mut input_manager = self.input_manager.lock().unwrap(); + input_manager.process_window_event(&event); } } @@ -129,45 +127,64 @@ impl ApplicationHandler for App { log::debug!("The close button was pressed; stopping"); event_loop.exit(); } - WindowEvent::Resized(_) => { - renderer.resize(); - } - WindowEvent::ScaleFactorChanged { .. } => { - renderer.resize(); + WindowEvent::Resized(_) | WindowEvent::ScaleFactorChanged { .. } => { + let mut vulkano_windows = self.vulkano_windows.lock().unwrap(); + vulkano_windows.get_renderer_mut(id).unwrap().resize(); } WindowEvent::RedrawRequested => { - match Arc::get_mut(&mut self.input_manager) { - Some(input_manager) => input_manager.update(), - None => log::error!("Failed to get a mutable reference to the input manager"), + { + let mut input_manager = self + .input_manager + .lock() + .expect("Failed to lock input manager"); + input_manager.update(); } - match Arc::get_mut(&mut self.timer) { - Some(timer) => timer.update(), - None => log::error!("Failed to get a mutable reference to the timer"), + { + let mut timer = self.timer.lock().expect("Failed to lock timer"); + timer.update(); } - let mut scene_context = SceneContext::from(( - &mut *renderer, - &self.vulkan_context, - &self.input_manager, - &self.timer, - id, - &self.event_loop_proxy, - )); + // Créer ou mettre à jour le contexte d'application + let app_context = self + .app_contexts + .entry(id) + .or_insert_with(|| { + Arc::new(Mutex::new(ApplicationContext::new( + self.vulkan_context.clone(), + self.vulkano_windows.clone(), + self.input_manager.clone(), + self.timer.clone(), + self.gui.get(&id).unwrap().clone(), + self.event_loop_proxy.clone(), + id, + ))) + }) + .clone(); let scene_manager = self.scene_manager.get_mut(&id).unwrap(); - scene_manager - .load_scene_if_not_loaded(&scene_context) - .unwrap(); - if let Some(scene) = scene_manager.current_scene_mut() { - scene.update(&scene_context).unwrap(); + // Utiliser le contexte partagé pour les scènes + { + let mut context = app_context.lock().unwrap(); - let acquire_future = renderer.acquire(None, |_| {}).unwrap(); - // Update the swapchain image view to the current one after acquiring the next image - scene_context.swapchain_image_view = renderer.swapchain_image_view(); + scene_manager + .load_scene_if_not_loaded(&mut context) + .unwrap(); - let acquire_future = scene.render(acquire_future, &scene_context, gui).unwrap(); - renderer.present(acquire_future, true); + if let Some(scene) = scene_manager.current_scene_mut() { + scene.update(&mut context).unwrap(); + + let acquire_future = context + .with_renderer_mut(|renderer| renderer.acquire(None, |_| {}).unwrap()); + + let acquire_future = scene.render(acquire_future, &mut context).unwrap(); + + context.with_renderer_mut(|renderer| { + renderer.present(acquire_future, true); + }); + } else { + log::warn!("No current scene found for update!"); + } } } _ => {} @@ -175,25 +192,46 @@ impl ApplicationHandler for App { } fn about_to_wait(&mut self, _event_loop: &ActiveEventLoop) { - let window = self.vulkano_windows.get_primary_window().unwrap(); + let vulkano_windows = self.vulkano_windows.lock().unwrap(); + let window = vulkano_windows.get_primary_window().unwrap(); window.request_redraw(); } fn user_event(&mut self, event_loop: &ActiveEventLoop, event: UserEvent) { match event { UserEvent::CursorGrabMode(window_id, grab) => { - let window = self.vulkano_windows.get_window(window_id).unwrap(); + let vulkano_windows = self.vulkano_windows.lock().unwrap(); + let window = vulkano_windows.get_window(window_id).unwrap(); if let Err(e) = window.set_cursor_grab(grab) { log::error!("Failed to set cursor grab: {}", e); } } UserEvent::CursorVisible(window_id, visible) => { - let window = self.vulkano_windows.get_window(window_id).unwrap(); + let vulkano_windows = self.vulkano_windows.lock().unwrap(); + let window = vulkano_windows.get_window(window_id).unwrap(); window.set_cursor_visible(visible); } UserEvent::ChangeScene(window_id, scene) => { - let scene_manager = self.scene_manager.get_mut(&window_id).unwrap(); - scene_manager.load_scene(scene); + if let Some(scene_manager) = self.scene_manager.get_mut(&window_id) { + scene_manager.load_scene(scene); + } + } + UserEvent::ChangeResolution(window_id, width, height) => { + let mut vulkano_windows = self.vulkano_windows.lock().unwrap(); + let window = vulkano_windows.get_window(window_id).unwrap(); + let _ = window.request_inner_size(winit::dpi::LogicalSize::new(width, height)); + let renderer = vulkano_windows.get_renderer_mut(window_id).unwrap(); + renderer.resize(); + log::info!( + "Resolution changed to {}x{} for window {:?}", + width, + height, + window_id + ); + } + UserEvent::Exit(window_id) => { + log::info!("Exit requested for window {:?}", window_id); + event_loop.exit(); } } } diff --git a/src/core/app/user_event.rs b/src/core/app/user_event.rs index 316ac4c..21b70f1 100644 --- a/src/core/app/user_event.rs +++ b/src/core/app/user_event.rs @@ -6,4 +6,6 @@ pub enum UserEvent { CursorGrabMode(WindowId, CursorGrabMode), CursorVisible(WindowId, bool), ChangeScene(WindowId, Box), + ChangeResolution(WindowId, f32, f32), + Exit(WindowId), } diff --git a/src/core/render/mod.rs b/src/core/render/mod.rs index 07e4134..f56fdf4 100644 --- a/src/core/render/mod.rs +++ b/src/core/render/mod.rs @@ -1,3 +1,4 @@ pub mod primitives; +pub mod render_pass_manager; pub mod texture; pub mod vulkan_context; diff --git a/src/core/render/render_pass_manager.rs b/src/core/render/render_pass_manager.rs new file mode 100644 index 0000000..02c3d7a --- /dev/null +++ b/src/core/render/render_pass_manager.rs @@ -0,0 +1,113 @@ +use std::sync::Arc; +use vulkano::{ + command_buffer::{AutoCommandBufferBuilder, RenderingAttachmentInfo, RenderingInfo}, + image::view::ImageView, + pipeline::graphics::viewport::Viewport, + render_pass::{AttachmentLoadOp, AttachmentStoreOp}, +}; + +/// Types de render passes disponibles +#[derive(Debug, Clone)] +pub enum RenderPassType { + Standard, + ShadowMap, + PostProcess, +} + +/// Configuration pour un render pass +#[derive(Debug, Clone)] +pub struct RenderPassConfig { + pub pass_type: RenderPassType, + pub clear_color: Option<[f32; 4]>, + pub clear_depth: Option, + pub load_op: AttachmentLoadOp, + pub store_op: AttachmentStoreOp, +} + +impl Default for RenderPassConfig { + fn default() -> Self { + Self { + pass_type: RenderPassType::Standard, + clear_color: Some([0.0, 0.0, 0.0, 1.0]), + clear_depth: Some(1.0), + load_op: AttachmentLoadOp::Clear, + store_op: AttachmentStoreOp::Store, + } + } +} + +/// Gestionnaire de render passes réutilisable +pub struct RenderPassManager; + +impl RenderPassManager { + /// Commence un render pass standard avec les paramètres donnés + pub fn begin_standard_rendering( + builder: &mut AutoCommandBufferBuilder, + config: &RenderPassConfig, + color_attachment: Arc, + depth_attachment: Option>, + window_size: [f32; 2], + ) -> Result<(), Box> { + let viewport = Viewport { + offset: [0.0, 0.0], + extent: window_size, + depth_range: 0.0..=1.0, + }; + + let mut rendering_info = RenderingInfo { + color_attachments: vec![Some(RenderingAttachmentInfo { + load_op: config.load_op, + store_op: config.store_op, + clear_value: config.clear_color.map(|c| c.into()), + ..RenderingAttachmentInfo::image_view(color_attachment) + })], + depth_attachment: None, + ..Default::default() + }; + + if let Some(depth_view) = depth_attachment { + rendering_info.depth_attachment = Some(RenderingAttachmentInfo { + load_op: AttachmentLoadOp::Clear, + store_op: AttachmentStoreOp::DontCare, + clear_value: config.clear_depth.map(|d| [d].into()), + ..RenderingAttachmentInfo::image_view(depth_view) + }); + } + + builder + .begin_rendering(rendering_info)? + .set_viewport(0, [viewport].into_iter().collect())?; + + Ok(()) + } + + /// Termine le render pass actuel + pub fn end_rendering( + builder: &mut AutoCommandBufferBuilder, + ) -> Result<(), Box> { + builder.end_rendering()?; + Ok(()) + } + + /// Crée une configuration pour un render pass shadow map + pub fn shadow_map_config() -> RenderPassConfig { + RenderPassConfig { + pass_type: RenderPassType::ShadowMap, + clear_color: None, + clear_depth: Some(1.0), + load_op: AttachmentLoadOp::Clear, + store_op: AttachmentStoreOp::Store, + } + } + + /// Crée une configuration pour un render pass de post-processing + pub fn post_process_config() -> RenderPassConfig { + RenderPassConfig { + pass_type: RenderPassType::PostProcess, + clear_color: Some([0.0, 0.0, 0.0, 1.0]), + clear_depth: None, + load_op: AttachmentLoadOp::Load, + store_op: AttachmentStoreOp::Store, + } + } +} diff --git a/src/core/scene/context.rs b/src/core/scene/context.rs deleted file mode 100644 index 4e0dab3..0000000 --- a/src/core/scene/context.rs +++ /dev/null @@ -1,101 +0,0 @@ -use std::sync::Arc; - -use vulkano::{ - command_buffer::allocator::StandardCommandBufferAllocator, - descriptor_set::allocator::StandardDescriptorSetAllocator, - device::{Device, Queue}, - image::view::ImageView, - instance::Instance, - memory::allocator::StandardMemoryAllocator, -}; -use vulkano_util::renderer::VulkanoWindowRenderer; -use winit::{ - event_loop::EventLoopProxy, - window::{Window, WindowId}, -}; - -use crate::core::{ - app::{DEPTH_IMAGE_ID, user_event::UserEvent}, - input::InputManager, - render::vulkan_context::VulkanContext, - timer::Timer, -}; - -pub struct SceneContext<'a> { - pub instance: Arc, - pub device: Arc, - pub graphics_queue: Arc, - pub compute_queue: Arc, - pub transfer_queue: Option>, - pub memory_allocator: Arc, - pub command_buffer_allocator: Arc, - pub descriptor_set_allocator: Arc, - pub window_size: [f32; 2], - pub aspect_ratio: f32, - pub swapchain_image_view: Arc, - pub depth_stencil_image_view: Arc, - pub input_manager: Arc, - pub timer: Arc, - pub event_loop_proxy: &'a EventLoopProxy, - pub window_id: WindowId, -} - -impl<'a> - From<( - &mut VulkanoWindowRenderer, - &Arc, - &Arc, - &Arc, - WindowId, - &'a EventLoopProxy, - )> for SceneContext<'a> -{ - fn from( - (renderer, vulkan_context, input_manager, timer, window_id, event_loop_proxy): ( - &mut VulkanoWindowRenderer, - &Arc, - &Arc, - &Arc, - WindowId, - &'a EventLoopProxy, - ), - ) -> Self { - let (command_buffer_allocator, descriptor_set_allocator) = { - ( - vulkan_context.command_buffer_allocator().clone(), - vulkan_context.descriptor_set_allocator().clone(), - ) - }; - - let (instance, device, graphics_queue, compute_queue, transfer_queue, memory_allocator) = { - let vulkan_context = vulkan_context.vulkano_context(); - ( - vulkan_context.instance().clone(), - vulkan_context.device().clone(), - vulkan_context.graphics_queue().clone(), - vulkan_context.compute_queue().clone(), - vulkan_context.transfer_queue().cloned(), - vulkan_context.memory_allocator().clone(), - ) - }; - - Self { - instance, - device, - graphics_queue, - compute_queue, - transfer_queue, - memory_allocator, - command_buffer_allocator, - descriptor_set_allocator, - window_size: renderer.window_size(), - aspect_ratio: renderer.aspect_ratio(), - swapchain_image_view: renderer.swapchain_image_view(), - depth_stencil_image_view: renderer.get_additional_image_view(DEPTH_IMAGE_ID), - input_manager: input_manager.clone(), - timer: timer.clone(), - event_loop_proxy, - window_id, - } - } -} diff --git a/src/core/scene/manager.rs b/src/core/scene/manager.rs index e44765d..9ff65ac 100644 --- a/src/core/scene/manager.rs +++ b/src/core/scene/manager.rs @@ -1,52 +1,84 @@ use std::error::Error; -use super::{Scene, SceneContext}; +use crate::core::app::context::ApplicationContext; + +use super::Scene; pub struct SceneManager { - current_scene: Option>, + scenes: Vec>, + current_scene_index: Option, } impl SceneManager { pub fn new() -> Self { Self { - current_scene: None, + scenes: Vec::new(), + current_scene_index: None, + } + } + + pub fn load_scene(&mut self, scene: Box) { + self.scenes.push(scene); + self.current_scene_index = Some(self.scenes.len() - 1); + } + + pub fn replace_current_scene(&mut self, scene: Box) { + if let Some(index) = self.current_scene_index { + if index < self.scenes.len() { + self.scenes[index].unload(); + self.scenes[index] = scene; + } + } else { + self.load_scene(scene); + } + } + + pub fn current_scene(&self) -> Option<&Box> { + if let Some(index) = self.current_scene_index { + self.scenes.get(index) + } else { + None + } + } + + pub fn current_scene_mut(&mut self) -> Option<&mut Box> { + log::debug!( + "current_scene_mut called - index: {:?}, scenes len: {}", + self.current_scene_index, + self.scenes.len() + ); + if let Some(index) = self.current_scene_index { + log::debug!("Getting scene at index {}", index); + self.scenes.get_mut(index) + } else { + log::debug!("No current scene index set"); + None } } pub fn load_scene_if_not_loaded( &mut self, - scene_context: &SceneContext, + app_context: &mut ApplicationContext, ) -> Result<(), Box> { - if let Some(current_scene) = self.current_scene.as_mut() { - if !current_scene.loaded() { - current_scene.load(scene_context)?; + log::debug!("SceneManager::load_scene_if_not_loaded called"); + log::debug!( + "Current scene index: {:?}, scenes count: {}", + self.current_scene_index, + self.scenes.len() + ); + + if let Some(scene) = self.current_scene_mut() { + log::debug!("Scene found, checking if loaded: {}", scene.loaded()); + if !scene.loaded() { + log::debug!("Scene not loaded, loading..."); + scene.load(app_context)?; + log::debug!("Scene loaded successfully"); + } else { + log::debug!("Scene already loaded"); } + } else { + log::warn!("No scene found in SceneManager!"); } Ok(()) } - - pub fn load_scene(&mut self, scene: Box) { - if let Some(current_scene) = self.current_scene.as_mut() { - current_scene.unload(); - } - self.current_scene = Some(scene); - } - - pub fn current_scene(&self) -> Option<&Box> { - if let Some(current_scene) = self.current_scene.as_ref() { - if current_scene.loaded() { - return Some(current_scene); - } - } - None - } - - pub fn current_scene_mut(&mut self) -> Option<&mut Box> { - if let Some(current_scene) = self.current_scene.as_mut() { - if current_scene.loaded() { - return Some(current_scene); - } - } - None - } } diff --git a/src/core/scene/mod.rs b/src/core/scene/mod.rs index 8642224..58a75df 100644 --- a/src/core/scene/mod.rs +++ b/src/core/scene/mod.rs @@ -1,23 +1,19 @@ -use std::{error::Error, sync::Arc}; +use std::error::Error; -use egui_winit_vulkano::Gui; -use vulkano::{image::view::ImageView, sync::GpuFuture}; +use vulkano::sync::GpuFuture; -mod context; -pub use context::SceneContext; +use crate::core::app::context::ApplicationContext; -mod manager; -pub use manager::SceneManager; +pub mod manager; pub trait Scene { fn loaded(&self) -> bool; - fn load(&mut self, scene_context: &SceneContext) -> Result<(), Box>; - fn update(&mut self, scene_context: &SceneContext) -> Result<(), Box>; + fn load(&mut self, app_context: &mut ApplicationContext) -> Result<(), Box>; + fn update(&mut self, app_context: &mut ApplicationContext) -> Result<(), Box>; fn render( &mut self, acquire_future: Box, - scene_context: &SceneContext, - gui: &mut Gui, + app_context: &mut ApplicationContext, ) -> Result, Box>; fn unload(&mut self); } diff --git a/src/game/scenes/main_scene.rs b/src/game/scenes/main_scene.rs index bad12d0..6461289 100644 --- a/src/game/scenes/main_scene.rs +++ b/src/game/scenes/main_scene.rs @@ -1,23 +1,21 @@ -use std::{error::Error, sync::Arc}; +use std::error::Error; +use super::settings_scene::SettingsScene; +use crate::core::app::DEPTH_IMAGE_ID; +use crate::core::app::context::ApplicationContext; use crate::core::app::user_event::UserEvent; use crate::core::render::primitives::camera::Camera3D; use crate::core::render::primitives::transform::Transform; +use crate::core::render::render_pass_manager::{RenderPassConfig, RenderPassManager}; use crate::core::render::texture::Texture; use crate::core::scene::Scene; -use crate::core::scene::SceneContext; use crate::game::assets::square::Square; -use egui_winit_vulkano::{Gui, egui}; +use egui_winit_vulkano::egui; use glam::EulerRot; use glam::Quat; use glam::Vec3; use vulkano::{ - command_buffer::{ - AutoCommandBufferBuilder, CommandBufferUsage, PrimaryCommandBufferAbstract, - RenderingAttachmentInfo, RenderingInfo, - }, - pipeline::graphics::viewport::Viewport, - render_pass::{AttachmentLoadOp, AttachmentStoreOp}, + command_buffer::{AutoCommandBufferBuilder, CommandBufferUsage, PrimaryCommandBufferAbstract}, sync::GpuFuture, }; use winit::window::CursorGrabMode; @@ -40,12 +38,22 @@ impl Scene for MainScene { self.state.is_some() } - fn load(&mut self, scene_context: &SceneContext) -> Result<(), Box> { + fn load( + &mut self, + app_context: &mut ApplicationContext, + ) -> Result<(), Box> { + let depth_image_view = app_context.with_renderer_mut(|renderer| { + renderer.get_additional_image_view(DEPTH_IMAGE_ID).clone() + }); + + let swapchain_image_view = + app_context.with_renderer_mut(|renderer| renderer.swapchain_image_view().clone()); + let square = Square::new( - &scene_context.device, - &scene_context.memory_allocator, - scene_context.swapchain_image_view.format(), - scene_context.depth_stencil_image_view.format(), + &app_context.device, + &app_context.memory_allocator, + swapchain_image_view.format(), + depth_image_view.format(), )?; let num_instances = 100; @@ -69,29 +77,35 @@ impl Scene for MainScene { }) .collect(); - let camera = Camera3D::new( - scene_context.aspect_ratio, - std::f32::consts::FRAC_PI_2, - 0.01, - 1000.0, - ); + let texture = { + let mut uploads = AutoCommandBufferBuilder::primary( + app_context.command_buffer_allocator.clone(), + app_context.graphics_queue.queue_family_index(), + CommandBufferUsage::OneTimeSubmit, + )?; - let mut uploads = AutoCommandBufferBuilder::primary( - scene_context.command_buffer_allocator.clone(), - scene_context.graphics_queue.queue_family_index(), - CommandBufferUsage::OneTimeSubmit, - )?; + let texture = Texture::from_file( + &app_context.device, + &app_context.memory_allocator, + &mut uploads, + "res/textures/wooden-crate.jpg", + )?; - let texture = Texture::from_file( - &scene_context.device, - &scene_context.memory_allocator, - &mut uploads, - "res/textures/wooden-crate.jpg", - )?; + let _ = uploads + .build()? + .execute(app_context.graphics_queue.clone())?; - let _ = uploads - .build()? - .execute(scene_context.graphics_queue.clone())?; + texture + }; + + let camera = app_context.with_renderer(|renderer| { + Camera3D::new( + renderer.aspect_ratio(), + std::f32::consts::FRAC_PI_2, + 0.01, + 1000.0, + ) + }); self.state = Some(MainSceneState { square, @@ -104,44 +118,46 @@ impl Scene for MainScene { Ok(()) } - fn update(&mut self, scene_context: &SceneContext) -> Result<(), Box> { - let state = self.state.as_mut().ok_or("State not found")?; - state.camera.update( - &scene_context.input_manager, - &scene_context.timer, - state.speed, - 10.0, - scene_context.aspect_ratio, - ); + fn update(&mut self, app_context: &mut ApplicationContext) -> Result<(), Box> { + let state = self.state.as_mut().unwrap(); + app_context.with_input_manager(|input_manager| { + app_context.with_timer(|timer| { + state.camera.update( + input_manager, + timer, + state.speed, + 10.0, + app_context.get_aspect_ratio(), + ); + }); + }); - if scene_context - .input_manager - .get_virtual_input_state("mouse_left") + if app_context + .with_input_manager(|input_manager| input_manager.get_virtual_input_state("mouse_left")) > 0.0 { - let _ = scene_context + let _ = app_context .event_loop_proxy - .send_event(UserEvent::CursorVisible(scene_context.window_id, false)); - let _ = scene_context + .send_event(UserEvent::CursorVisible(app_context.window_id, false)); + let _ = app_context .event_loop_proxy .send_event(UserEvent::CursorGrabMode( - scene_context.window_id, + app_context.window_id, CursorGrabMode::Locked, )); } - if scene_context - .input_manager - .get_virtual_input_state("mouse_right") - > 0.0 + if app_context.with_input_manager(|input_manager| { + input_manager.get_virtual_input_state("mouse_right") + }) > 0.0 { - let _ = scene_context + let _ = app_context .event_loop_proxy - .send_event(UserEvent::CursorVisible(scene_context.window_id, true)); - let _ = scene_context + .send_event(UserEvent::CursorVisible(app_context.window_id, true)); + let _ = app_context .event_loop_proxy .send_event(UserEvent::CursorGrabMode( - scene_context.window_id, + app_context.window_id, CursorGrabMode::None, )); } @@ -151,87 +167,112 @@ impl Scene for MainScene { fn render( &mut self, - acquire_future: Box, - scene_context: &SceneContext, - gui: &mut Gui, + before_future: Box, + app_context: &mut ApplicationContext, ) -> Result, Box> { - let state = self.state.as_ref().ok_or("State not found")?; + let state = self.state.as_ref().ok_or("State not loaded")?; let mut builder = AutoCommandBufferBuilder::primary( - scene_context.command_buffer_allocator.clone(), - scene_context.graphics_queue.queue_family_index(), + app_context.command_buffer_allocator.clone(), + app_context.graphics_queue.queue_family_index(), CommandBufferUsage::OneTimeSubmit, )?; { - let viewport = Viewport { - offset: [0.0, 0.0], - extent: scene_context.window_size, - depth_range: 0.0..=1.0, - }; - - builder - .begin_rendering(RenderingInfo { - color_attachments: vec![Some(RenderingAttachmentInfo { - load_op: AttachmentLoadOp::Clear, - store_op: AttachmentStoreOp::Store, - clear_value: Some([0.0, 0.0, 0.0, 1.0].into()), - ..RenderingAttachmentInfo::image_view( - scene_context.swapchain_image_view.clone(), - ) - })], - depth_attachment: Some(RenderingAttachmentInfo { - load_op: AttachmentLoadOp::Clear, - store_op: AttachmentStoreOp::DontCare, - clear_value: Some([1.0].into()), - ..RenderingAttachmentInfo::image_view( - scene_context.depth_stencil_image_view.clone(), - ) - }), - ..Default::default() - })? - .set_viewport(0, [viewport].into_iter().collect())?; + let swapchain_image_view = + app_context.with_renderer_mut(|renderer| renderer.swapchain_image_view().clone()); + let depth_image_view = app_context.with_renderer_mut(|renderer| { + renderer.get_additional_image_view(DEPTH_IMAGE_ID).clone() + }); + let config = RenderPassConfig::default(); + RenderPassManager::begin_standard_rendering( + &mut builder, + &config, + swapchain_image_view, + Some(depth_image_view), + app_context.get_window_size(), + )?; } - let camera_uniform = state - .camera - .create_buffer(&scene_context.memory_allocator)?; + // Create camera uniform using the actual camera + let camera_uniform = state.camera.create_buffer(&app_context.memory_allocator)?; let transform_uniform = - Transform::create_buffer(&scene_context.memory_allocator, &state.instances)?; + Transform::create_buffer(&app_context.memory_allocator, &state.instances)?; state .square .render( &mut builder, - &scene_context.descriptor_set_allocator, + &app_context.descriptor_set_allocator, &camera_uniform, &transform_uniform, &state.texture, ) .unwrap(); - builder.end_rendering()?; + RenderPassManager::end_rendering(&mut builder)?; let command_buffer = builder.build()?; let render_future = - acquire_future.then_execute(scene_context.graphics_queue.clone(), command_buffer)?; + before_future.then_execute(app_context.graphics_queue.clone(), command_buffer)?; - gui.immediate_ui(|gui| { - let ctx = gui.context(); + let swapchain_image_view = + app_context.with_renderer_mut(|renderer| renderer.swapchain_image_view().clone()); + let input_manager_status = + app_context.with_input_manager(|input_manager| format!("{:#?}", input_manager)); + let event_loop_proxy = app_context.event_loop_proxy.clone(); + let delta_time = app_context.get_delta_time(); + let window_id = app_context.window_id; + let window_size = app_context.get_window_size(); + let render_future = app_context.with_gui_mut(|gui| { + gui.immediate_ui(|gui| { + let ctx = gui.context(); + egui::TopBottomPanel::top("top_panel").show(&ctx, |ui| { + ui.horizontal(|ui| { + ui.heading("Vulkan Test - Moteur 3D"); + ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { + if ui.button("Paramètres").clicked() { + let _ = event_loop_proxy.send_event(UserEvent::ChangeScene( + window_id, + Box::new(SettingsScene::default()), + )); + } + if ui.button("Quitter").clicked() { + let _ = event_loop_proxy.send_event(UserEvent::Exit(window_id)); + } + }); + }); + }); - egui::Window::new("Informations") - .vscroll(true) - .show(&ctx, |ui| { - ui.label(format!("Resolution: {:?}", scene_context.window_size)); + egui::SidePanel::left("side_panel").show(&ctx, |ui| { + ui.heading("Informations"); - ui.label(format!("{:#?}", scene_context.input_manager)); + ui.separator(); - ui.label(format!( - "Delta time: {:?}", - scene_context.timer.delta_time() - )); + ui.label(format!("Résolution: {:?}", window_size)); + ui.label(format!("Delta Time: {:.2}ms", delta_time * 1000.0)); + + ui.separator(); + + ui.label("Position caméra:"); + let position = state.camera.get_position(); + ui.label(format!(" X: {:.2}", position[0])); + ui.label(format!(" Y: {:.2}", position[1])); + ui.label(format!(" Z: {:.2}", position[2])); + + ui.separator(); + + ui.label("Rotation caméra:"); + let rotation = state.camera.get_rotation(); + ui.label(format!(" Yaw: {:.2}°", rotation.y.to_degrees())); + ui.label(format!(" Pitch: {:.2}°", rotation.x.to_degrees())); + + ui.separator(); + ui.label(input_manager_status); + + ui.label(format!("Delta time: {:?}", delta_time)); ui.label(format!( "Position: {:?}, Rotation: {:?}", @@ -239,13 +280,17 @@ impl Scene for MainScene { state.camera.get_rotation() )); }); - }); + }); - let render_future = - gui.draw_on_image(render_future, scene_context.swapchain_image_view.clone()); + let render_future = gui.draw_on_image(render_future, swapchain_image_view.clone()); + + render_future + }); Ok(render_future) } - fn unload(&mut self) {} + fn unload(&mut self) { + self.state = None; + } } diff --git a/src/game/scenes/mod.rs b/src/game/scenes/mod.rs index e5c4945..516fa6f 100644 --- a/src/game/scenes/mod.rs +++ b/src/game/scenes/mod.rs @@ -1,2 +1,2 @@ pub mod main_scene; -pub mod test_scene; +pub mod settings_scene; diff --git a/src/game/scenes/settings_scene.rs b/src/game/scenes/settings_scene.rs new file mode 100644 index 0000000..68e842b --- /dev/null +++ b/src/game/scenes/settings_scene.rs @@ -0,0 +1,135 @@ +use std::error::Error; + +use crate::core::app::DEPTH_IMAGE_ID; +use crate::core::app::context::ApplicationContext; +use crate::core::app::user_event::UserEvent; +use crate::core::render::render_pass_manager::{RenderPassConfig, RenderPassManager}; +use crate::core::scene::Scene; +use egui_winit_vulkano::egui; +use vulkano::{ + command_buffer::AutoCommandBufferBuilder, command_buffer::CommandBufferUsage, sync::GpuFuture, +}; + +use super::main_scene::MainScene; + +pub struct SettingsSceneState { + current_resolution: [f32; 2], + available_resolutions: Vec<(u32, u32)>, +} + +#[derive(Default)] +pub struct SettingsScene { + state: Option, +} + +impl Scene for SettingsScene { + fn loaded(&self) -> bool { + self.state.is_some() + } + + fn load(&mut self, app_context: &mut ApplicationContext) -> Result<(), Box> { + let current_resolution = app_context.get_window_size(); + let available_resolutions = app_context.get_available_resolutions(); + + self.state = Some(SettingsSceneState { + current_resolution, + available_resolutions, + }); + + Ok(()) + } + + fn update(&mut self, _app_context: &mut ApplicationContext) -> Result<(), Box> { + Ok(()) + } + + fn render( + &mut self, + before_future: Box, + app_context: &mut ApplicationContext, + ) -> Result, Box> { + let state = self.state.as_ref().ok_or("State not found")?; + + let mut builder = AutoCommandBufferBuilder::primary( + app_context.command_buffer_allocator.clone(), + app_context.graphics_queue.queue_family_index(), + CommandBufferUsage::OneTimeSubmit, + )?; + + // Utiliser le RenderPassManager + { + let swapchain_image_view = + app_context.with_renderer_mut(|renderer| renderer.swapchain_image_view().clone()); + let depth_stencil_image_view = app_context.with_renderer_mut(|renderer| { + renderer.get_additional_image_view(DEPTH_IMAGE_ID).clone() + }); + let config = RenderPassConfig::default(); + RenderPassManager::begin_standard_rendering( + &mut builder, + &config, + swapchain_image_view, + Some(depth_stencil_image_view), + app_context.get_window_size(), + )?; + } + + // Pas de géométrie dans cette scène - juste un écran de paramètres + RenderPassManager::end_rendering(&mut builder)?; + + let command_buffer = builder.build()?; + + let render_future = + before_future.then_execute(app_context.graphics_queue.clone(), command_buffer)?; + + let swapchain_image_view = + app_context.with_renderer_mut(|renderer| renderer.swapchain_image_view().clone()); + + let event_loop_proxy = app_context.event_loop_proxy.clone(); + let window_id = app_context.window_id; + + let render_future = app_context.with_gui_mut(|gui| { + gui.immediate_ui(|gui| { + let ctx = gui.context(); + + egui::CentralPanel::default().show(&ctx, |ui| { + ui.heading("Paramètres"); + + ui.separator(); + + ui.label(format!( + "Résolution actuelle: {:?}", + state.current_resolution + )); + + ui.separator(); + ui.label("Changer la résolution:"); + + for &(width, height) in &state.available_resolutions { + if ui.button(format!("{}x{}", width, height)).clicked() { + let _ = event_loop_proxy.send_event(UserEvent::ChangeResolution( + window_id, + width as f32, + height as f32, + )); + } + } + + ui.separator(); + + if ui.button("Retour au jeu").clicked() { + let _ = event_loop_proxy.send_event(UserEvent::ChangeScene( + window_id, + Box::new(MainScene::default()), + )); + } + }); + }); + + gui.draw_on_image(render_future, swapchain_image_view.clone()) + }); + + Ok(render_future) + } + + fn unload(&mut self) {} +} diff --git a/src/game/scenes/test_scene.rs b/src/game/scenes/test_scene.rs deleted file mode 100644 index 9ed8707..0000000 --- a/src/game/scenes/test_scene.rs +++ /dev/null @@ -1,111 +0,0 @@ -use std::error::Error; - -use crate::core::app::user_event::UserEvent; -use crate::core::scene::Scene; -use crate::core::scene::SceneContext; -use egui_winit_vulkano::{Gui, egui}; -use vulkano::command_buffer::AutoCommandBufferBuilder; -use vulkano::command_buffer::CommandBufferUsage; -use vulkano::command_buffer::RenderingAttachmentInfo; -use vulkano::command_buffer::RenderingInfo; -use vulkano::pipeline::graphics::viewport::Viewport; -use vulkano::render_pass::AttachmentLoadOp; -use vulkano::render_pass::AttachmentStoreOp; -use vulkano::sync::GpuFuture; - -use super::main_scene::MainScene; - -pub struct MainSceneState {} - -#[derive(Default)] -pub struct TestScene { - state: Option, -} - -impl Scene for TestScene { - fn loaded(&self) -> bool { - self.state.is_some() - } - - fn load(&mut self, scene_context: &SceneContext) -> Result<(), Box> { - self.state = Some(MainSceneState {}); - - Ok(()) - } - - fn update(&mut self, scene_context: &SceneContext) -> Result<(), Box> { - Ok(()) - } - - fn render( - &mut self, - acquire_future: Box, - scene_context: &SceneContext, - gui: &mut Gui, - ) -> Result, Box> { - let mut builder = AutoCommandBufferBuilder::primary( - scene_context.command_buffer_allocator.clone(), - scene_context.graphics_queue.queue_family_index(), - CommandBufferUsage::OneTimeSubmit, - )?; - - { - let viewport = Viewport { - offset: [0.0, 0.0], - extent: scene_context.window_size, - depth_range: 0.0..=1.0, - }; - - builder - .begin_rendering(RenderingInfo { - color_attachments: vec![Some(RenderingAttachmentInfo { - load_op: AttachmentLoadOp::Clear, - store_op: AttachmentStoreOp::Store, - clear_value: Some([0.0, 0.0, 0.0, 1.0].into()), - ..RenderingAttachmentInfo::image_view( - scene_context.swapchain_image_view.clone(), - ) - })], - depth_attachment: Some(RenderingAttachmentInfo { - load_op: AttachmentLoadOp::Clear, - store_op: AttachmentStoreOp::DontCare, - clear_value: Some([1.0].into()), - ..RenderingAttachmentInfo::image_view( - scene_context.depth_stencil_image_view.clone(), - ) - }), - ..Default::default() - })? - .set_viewport(0, [viewport].into_iter().collect())?; - } - - builder.end_rendering()?; - - let command_buffer = builder.build()?; - - let render_future = - acquire_future.then_execute(scene_context.graphics_queue.clone(), command_buffer)?; - - gui.immediate_ui(|gui| { - let ctx = gui.context(); - - egui::CentralPanel::default().show(&ctx, |ui| { - if ui.button("Start Game").clicked() { - let _ = scene_context - .event_loop_proxy - .send_event(UserEvent::ChangeScene( - scene_context.window_id, - Box::new(MainScene::default()), - )); - } - }); - }); - - let render_future = - gui.draw_on_image(render_future, scene_context.swapchain_image_view.clone()); - - Ok(render_future) - } - - fn unload(&mut self) {} -} From bc42892d39f0422da9ac7f63816711c209bfa904 Mon Sep 17 00:00:00 2001 From: Florian RICHER Date: Fri, 30 May 2025 21:54:58 +0200 Subject: [PATCH 063/105] cleanup --- src/core/app/context.rs | 3 +++ src/core/app/mod.rs | 1 + src/core/scene/manager.rs | 19 ------------------- src/game/scenes/main_scene.rs | 16 +++++----------- src/game/scenes/settings_scene.rs | 5 +++-- 5 files changed, 12 insertions(+), 32 deletions(-) diff --git a/src/core/app/context.rs b/src/core/app/context.rs index 1c0ccb6..ce01c7c 100644 --- a/src/core/app/context.rs +++ b/src/core/app/context.rs @@ -180,6 +180,7 @@ impl ApplicationContext { f(&renderer) } + /// Méthode utilitaire pour accéder au renderer de manière thread-safe pub fn with_renderer_mut(&mut self, f: F) -> T where F: FnOnce(&mut VulkanoWindowRenderer) -> T, @@ -194,6 +195,7 @@ impl ApplicationContext { f(renderer) } + /// Méthode utilitaire pour accéder au gui de manière thread-safe pub fn with_gui(&self, f: F) -> T where F: FnOnce(&Gui) -> T, @@ -202,6 +204,7 @@ impl ApplicationContext { f(&gui) } + /// Méthode utilitaire pour accéder au gui de manière thread-safe pub fn with_gui_mut(&mut self, f: F) -> T where F: FnOnce(&mut Gui) -> T, diff --git a/src/core/app/mod.rs b/src/core/app/mod.rs index 243420b..140dee1 100644 --- a/src/core/app/mod.rs +++ b/src/core/app/mod.rs @@ -33,6 +33,7 @@ pub struct App { input_manager: Arc>, timer: Arc>, event_loop_proxy: EventLoopProxy, + // Context d'application partagé par fenêtre - architecture unifiée app_contexts: HashMap>>, } diff --git a/src/core/scene/manager.rs b/src/core/scene/manager.rs index 9ff65ac..a12892e 100644 --- a/src/core/scene/manager.rs +++ b/src/core/scene/manager.rs @@ -42,16 +42,9 @@ impl SceneManager { } pub fn current_scene_mut(&mut self) -> Option<&mut Box> { - log::debug!( - "current_scene_mut called - index: {:?}, scenes len: {}", - self.current_scene_index, - self.scenes.len() - ); if let Some(index) = self.current_scene_index { - log::debug!("Getting scene at index {}", index); self.scenes.get_mut(index) } else { - log::debug!("No current scene index set"); None } } @@ -60,21 +53,9 @@ impl SceneManager { &mut self, app_context: &mut ApplicationContext, ) -> Result<(), Box> { - log::debug!("SceneManager::load_scene_if_not_loaded called"); - log::debug!( - "Current scene index: {:?}, scenes count: {}", - self.current_scene_index, - self.scenes.len() - ); - if let Some(scene) = self.current_scene_mut() { - log::debug!("Scene found, checking if loaded: {}", scene.loaded()); if !scene.loaded() { - log::debug!("Scene not loaded, loading..."); scene.load(app_context)?; - log::debug!("Scene loaded successfully"); - } else { - log::debug!("Scene already loaded"); } } else { log::warn!("No scene found in SceneManager!"); diff --git a/src/game/scenes/main_scene.rs b/src/game/scenes/main_scene.rs index 6461289..26e8631 100644 --- a/src/game/scenes/main_scene.rs +++ b/src/game/scenes/main_scene.rs @@ -47,7 +47,7 @@ impl Scene for MainScene { }); let swapchain_image_view = - app_context.with_renderer_mut(|renderer| renderer.swapchain_image_view().clone()); + app_context.with_renderer(|renderer| renderer.swapchain_image_view().clone()); let square = Square::new( &app_context.device, @@ -180,7 +180,7 @@ impl Scene for MainScene { { let swapchain_image_view = - app_context.with_renderer_mut(|renderer| renderer.swapchain_image_view().clone()); + app_context.with_renderer(|renderer| renderer.swapchain_image_view().clone()); let depth_image_view = app_context.with_renderer_mut(|renderer| { renderer.get_additional_image_view(DEPTH_IMAGE_ID).clone() }); @@ -219,13 +219,14 @@ impl Scene for MainScene { before_future.then_execute(app_context.graphics_queue.clone(), command_buffer)?; let swapchain_image_view = - app_context.with_renderer_mut(|renderer| renderer.swapchain_image_view().clone()); + app_context.with_renderer(|renderer| renderer.swapchain_image_view().clone()); let input_manager_status = app_context.with_input_manager(|input_manager| format!("{:#?}", input_manager)); let event_loop_proxy = app_context.event_loop_proxy.clone(); let delta_time = app_context.get_delta_time(); let window_id = app_context.window_id; let window_size = app_context.get_window_size(); + let render_future = app_context.with_gui_mut(|gui| { gui.immediate_ui(|gui| { let ctx = gui.context(); @@ -270,15 +271,8 @@ impl Scene for MainScene { ui.label(format!(" Pitch: {:.2}°", rotation.x.to_degrees())); ui.separator(); + ui.label(input_manager_status); - - ui.label(format!("Delta time: {:?}", delta_time)); - - ui.label(format!( - "Position: {:?}, Rotation: {:?}", - state.camera.get_position(), - state.camera.get_rotation() - )); }); }); diff --git a/src/game/scenes/settings_scene.rs b/src/game/scenes/settings_scene.rs index 68e842b..4d6fc53 100644 --- a/src/game/scenes/settings_scene.rs +++ b/src/game/scenes/settings_scene.rs @@ -59,10 +59,11 @@ impl Scene for SettingsScene { // Utiliser le RenderPassManager { let swapchain_image_view = - app_context.with_renderer_mut(|renderer| renderer.swapchain_image_view().clone()); + app_context.with_renderer(|renderer| renderer.swapchain_image_view().clone()); let depth_stencil_image_view = app_context.with_renderer_mut(|renderer| { renderer.get_additional_image_view(DEPTH_IMAGE_ID).clone() }); + let config = RenderPassConfig::default(); RenderPassManager::begin_standard_rendering( &mut builder, @@ -82,7 +83,7 @@ impl Scene for SettingsScene { before_future.then_execute(app_context.graphics_queue.clone(), command_buffer)?; let swapchain_image_view = - app_context.with_renderer_mut(|renderer| renderer.swapchain_image_view().clone()); + app_context.with_renderer(|renderer| renderer.swapchain_image_view().clone()); let event_loop_proxy = app_context.event_loop_proxy.clone(); let window_id = app_context.window_id; From b1458785e51efe651b283eaa23f454ca21e23543 Mon Sep 17 00:00:00 2001 From: Florian RICHER Date: Fri, 30 May 2025 22:04:54 +0200 Subject: [PATCH 064/105] Change Mutex by RwLock --- src/core/app/context.rs | 33 ++++++++++++++------------- src/core/app/mod.rs | 49 ++++++++++++++++++++++------------------- 2 files changed, 44 insertions(+), 38 deletions(-) diff --git a/src/core/app/context.rs b/src/core/app/context.rs index ce01c7c..f796563 100644 --- a/src/core/app/context.rs +++ b/src/core/app/context.rs @@ -1,4 +1,4 @@ -use std::sync::{Arc, Mutex}; +use std::sync::{Arc, RwLock}; use egui_winit_vulkano::Gui; use vulkano::{ @@ -35,19 +35,19 @@ pub struct ApplicationContext { pub window_id: WindowId, // Données mutables partagées avec Arc> - pub vulkano_windows: Arc>, - pub input_manager: Arc>, - pub timer: Arc>, - pub gui: Arc>, + pub vulkano_windows: Arc>, + pub input_manager: Arc>, + pub timer: Arc>, + pub gui: Arc>, } impl ApplicationContext { pub fn new( vulkan_context: Arc, - vulkano_windows: Arc>, - input_manager: Arc>, - timer: Arc>, - gui: Arc>, + vulkano_windows: Arc>, + input_manager: Arc>, + timer: Arc>, + gui: Arc>, event_loop_proxy: EventLoopProxy, window_id: WindowId, ) -> Self { @@ -172,7 +172,7 @@ impl ApplicationContext { { let vulkano_windows = self .vulkano_windows - .lock() + .read() .expect("Failed to lock vulkano_windows"); let renderer = vulkano_windows .get_renderer(self.window_id) @@ -187,7 +187,7 @@ impl ApplicationContext { { let mut vulkano_windows = self .vulkano_windows - .lock() + .write() .expect("Failed to lock vulkano_windows"); let renderer = vulkano_windows .get_renderer_mut(self.window_id) @@ -200,7 +200,7 @@ impl ApplicationContext { where F: FnOnce(&Gui) -> T, { - let gui = self.gui.lock().unwrap(); + let gui = self.gui.read().expect("Failed to lock gui"); f(&gui) } @@ -209,7 +209,7 @@ impl ApplicationContext { where F: FnOnce(&mut Gui) -> T, { - let mut gui = self.gui.lock().unwrap(); + let mut gui = self.gui.write().expect("Failed to lock gui"); f(&mut gui) } @@ -218,7 +218,10 @@ impl ApplicationContext { where F: FnOnce(&InputManager) -> T, { - let input_manager = self.input_manager.lock().unwrap(); + let input_manager = self + .input_manager + .read() + .expect("Failed to lock input_manager"); f(&input_manager) } @@ -227,7 +230,7 @@ impl ApplicationContext { where F: FnOnce(&Timer) -> T, { - let timer = self.timer.lock().unwrap(); + let timer = self.timer.read().expect("Failed to lock timer"); f(&timer) } } diff --git a/src/core/app/mod.rs b/src/core/app/mod.rs index 140dee1..d17e84d 100644 --- a/src/core/app/mod.rs +++ b/src/core/app/mod.rs @@ -1,5 +1,5 @@ use std::collections::HashMap; -use std::sync::{Arc, Mutex}; +use std::sync::{Arc, RwLock}; use super::render::vulkan_context::VulkanContext; use crate::core::input::InputManager; @@ -27,15 +27,15 @@ pub const DEPTH_IMAGE_ID: usize = 0; pub struct App { vulkan_context: Arc, - vulkano_windows: Arc>, - gui: HashMap>>, + vulkano_windows: Arc>, + gui: HashMap>>, scene_manager: HashMap, - input_manager: Arc>, - timer: Arc>, + input_manager: Arc>, + timer: Arc>, event_loop_proxy: EventLoopProxy, // Context d'application partagé par fenêtre - architecture unifiée - app_contexts: HashMap>>, + app_contexts: HashMap>>, } impl App { @@ -46,11 +46,11 @@ impl App { ) -> Self { Self { vulkan_context: Arc::new(VulkanContext::new(vulkano_context)), - vulkano_windows: Arc::new(Mutex::new(VulkanoWindows::default())), + vulkano_windows: Arc::new(RwLock::new(VulkanoWindows::default())), gui: HashMap::new(), - input_manager: Arc::new(Mutex::new(input_manager)), + input_manager: Arc::new(RwLock::new(input_manager)), scene_manager: HashMap::new(), - timer: Arc::new(Mutex::new(Timer::new())), + timer: Arc::new(RwLock::new(Timer::new())), event_loop_proxy, app_contexts: HashMap::new(), } @@ -59,7 +59,10 @@ impl App { impl ApplicationHandler for App { fn resumed(&mut self, event_loop: &ActiveEventLoop) { - let mut vulkano_windows = self.vulkano_windows.lock().unwrap(); + let mut vulkano_windows = self + .vulkano_windows + .write() + .expect("Failed to lock vulkano_windows"); let window_id = vulkano_windows.create_window( event_loop, &self.vulkan_context.vulkano_context(), @@ -95,7 +98,7 @@ impl ApplicationHandler for App { }, ) }; - self.gui.insert(window_id, Arc::new(Mutex::new(gui))); + self.gui.insert(window_id, Arc::new(RwLock::new(gui))); let mut scene_manager = SceneManager::new(); scene_manager.load_scene(Box::new(MainScene::default())); @@ -109,16 +112,16 @@ impl ApplicationHandler for App { _device_id: winit::event::DeviceId, event: winit::event::DeviceEvent, ) { - let mut input_manager = self.input_manager.lock().unwrap(); + let mut input_manager = self.input_manager.write().unwrap(); input_manager.process_device_event(&event); } fn window_event(&mut self, event_loop: &ActiveEventLoop, id: WindowId, event: WindowEvent) { { let gui = self.gui.get_mut(&id).unwrap(); - let mut gui = gui.lock().unwrap(); + let mut gui = gui.write().expect("Failed to lock gui"); if !gui.update(&event) { - let mut input_manager = self.input_manager.lock().unwrap(); + let mut input_manager = self.input_manager.write().unwrap(); input_manager.process_window_event(&event); } } @@ -129,19 +132,19 @@ impl ApplicationHandler for App { event_loop.exit(); } WindowEvent::Resized(_) | WindowEvent::ScaleFactorChanged { .. } => { - let mut vulkano_windows = self.vulkano_windows.lock().unwrap(); + let mut vulkano_windows = self.vulkano_windows.write().unwrap(); vulkano_windows.get_renderer_mut(id).unwrap().resize(); } WindowEvent::RedrawRequested => { { let mut input_manager = self .input_manager - .lock() + .write() .expect("Failed to lock input manager"); input_manager.update(); } { - let mut timer = self.timer.lock().expect("Failed to lock timer"); + let mut timer = self.timer.write().expect("Failed to lock timer"); timer.update(); } @@ -150,7 +153,7 @@ impl ApplicationHandler for App { .app_contexts .entry(id) .or_insert_with(|| { - Arc::new(Mutex::new(ApplicationContext::new( + Arc::new(RwLock::new(ApplicationContext::new( self.vulkan_context.clone(), self.vulkano_windows.clone(), self.input_manager.clone(), @@ -166,7 +169,7 @@ impl ApplicationHandler for App { // Utiliser le contexte partagé pour les scènes { - let mut context = app_context.lock().unwrap(); + let mut context = app_context.write().unwrap(); scene_manager .load_scene_if_not_loaded(&mut context) @@ -193,7 +196,7 @@ impl ApplicationHandler for App { } fn about_to_wait(&mut self, _event_loop: &ActiveEventLoop) { - let vulkano_windows = self.vulkano_windows.lock().unwrap(); + let vulkano_windows = self.vulkano_windows.read().unwrap(); let window = vulkano_windows.get_primary_window().unwrap(); window.request_redraw(); } @@ -201,14 +204,14 @@ impl ApplicationHandler for App { fn user_event(&mut self, event_loop: &ActiveEventLoop, event: UserEvent) { match event { UserEvent::CursorGrabMode(window_id, grab) => { - let vulkano_windows = self.vulkano_windows.lock().unwrap(); + let vulkano_windows = self.vulkano_windows.read().unwrap(); let window = vulkano_windows.get_window(window_id).unwrap(); if let Err(e) = window.set_cursor_grab(grab) { log::error!("Failed to set cursor grab: {}", e); } } UserEvent::CursorVisible(window_id, visible) => { - let vulkano_windows = self.vulkano_windows.lock().unwrap(); + let vulkano_windows = self.vulkano_windows.read().unwrap(); let window = vulkano_windows.get_window(window_id).unwrap(); window.set_cursor_visible(visible); } @@ -218,7 +221,7 @@ impl ApplicationHandler for App { } } UserEvent::ChangeResolution(window_id, width, height) => { - let mut vulkano_windows = self.vulkano_windows.lock().unwrap(); + let mut vulkano_windows = self.vulkano_windows.write().unwrap(); let window = vulkano_windows.get_window(window_id).unwrap(); let _ = window.request_inner_size(winit::dpi::LogicalSize::new(width, height)); let renderer = vulkano_windows.get_renderer_mut(window_id).unwrap(); From 1568223b9d8184df9e2134c371ea6453d5d6256c Mon Sep 17 00:00:00 2001 From: Florian RICHER Date: Fri, 30 May 2025 22:21:34 +0200 Subject: [PATCH 065/105] some cleanup from clippy --- src/core/app/context.rs | 4 ++-- src/core/app/mod.rs | 2 +- src/core/input/mod.rs | 9 +++------ src/core/input/virtual_input.rs | 6 +++--- src/core/input/virtual_state.rs | 15 ++++++--------- src/core/render/primitives/camera.rs | 6 +++--- src/core/render/primitives/mvp.rs | 7 +++---- src/core/render/primitives/transform.rs | 7 ++++--- src/core/render/texture.rs | 2 +- src/game/assets/square.rs | 4 ++-- src/game/scenes/main_scene.rs | 6 ++---- 11 files changed, 30 insertions(+), 38 deletions(-) diff --git a/src/core/app/context.rs b/src/core/app/context.rs index f796563..6820613 100644 --- a/src/core/app/context.rs +++ b/src/core/app/context.rs @@ -148,7 +148,7 @@ impl ApplicationContext { /// Récupère les résolutions disponibles pub fn get_available_resolutions(&self) -> Vec<(u32, u32)> { - self.with_renderer(|renderer| Self::get_monitor_resolutions(&renderer.window())) + self.with_renderer(|renderer| Self::get_monitor_resolutions(renderer.window())) } /// Récupère le delta time actuel depuis le timer @@ -177,7 +177,7 @@ impl ApplicationContext { let renderer = vulkano_windows .get_renderer(self.window_id) .expect("Failed to get renderer"); - f(&renderer) + f(renderer) } /// Méthode utilitaire pour accéder au renderer de manière thread-safe diff --git a/src/core/app/mod.rs b/src/core/app/mod.rs index d17e84d..1be0e81 100644 --- a/src/core/app/mod.rs +++ b/src/core/app/mod.rs @@ -65,7 +65,7 @@ impl ApplicationHandler for App { .expect("Failed to lock vulkano_windows"); let window_id = vulkano_windows.create_window( event_loop, - &self.vulkan_context.vulkano_context(), + self.vulkan_context.vulkano_context(), &WindowDescriptor { title: "Rust ASH Test".to_string(), width: 800.0, diff --git a/src/core/input/mod.rs b/src/core/input/mod.rs index 227a5a1..f4a1642 100644 --- a/src/core/input/mod.rs +++ b/src/core/input/mod.rs @@ -40,11 +40,8 @@ impl InputManager { } pub fn process_device_event(&mut self, event: &DeviceEvent) { - match event { - DeviceEvent::MouseMotion { delta, .. } => { - self.mouse_position_delta += glam::Vec2::new(delta.0 as f32, delta.1 as f32); - } - _ => {} + if let DeviceEvent::MouseMotion { delta, .. } = event { + self.mouse_position_delta += glam::Vec2::new(delta.0 as f32, delta.1 as f32); } } @@ -75,7 +72,7 @@ impl InputManager { MouseScrollDelta::PixelDelta(position) => { glam::Vec2::new(position.x as f32, position.y as f32) } - MouseScrollDelta::LineDelta(x, y) => glam::Vec2::new(*x as f32, *y as f32), + MouseScrollDelta::LineDelta(x, y) => glam::Vec2::new(*x, *y), }; } _ => {} diff --git a/src/core/input/virtual_input.rs b/src/core/input/virtual_input.rs index 875d66c..dfab54d 100644 --- a/src/core/input/virtual_input.rs +++ b/src/core/input/virtual_input.rs @@ -58,7 +58,7 @@ impl VirtualInput { VirtualBinding::Keyboard(key, _) => { self.states_by_key .entry(*key) - .or_insert(Vec::new()) + .or_default() .push(state.clone()); } VirtualBinding::MouseX(_) | VirtualBinding::MouseY(_) => { @@ -67,7 +67,7 @@ impl VirtualInput { VirtualBinding::MouseButton(button, _) => { self.mouse_button_states .entry(*button) - .or_insert(Vec::new()) + .or_default() .push(state.clone()); } VirtualBinding::MouseWheelX(_) | VirtualBinding::MouseWheelY(_) => { @@ -76,7 +76,7 @@ impl VirtualInput { VirtualBinding::Axis(axis, _, _) => { self.axis_states .entry(*axis) - .or_insert(Vec::new()) + .or_default() .push(state.clone()); } } diff --git a/src/core/input/virtual_state.rs b/src/core/input/virtual_state.rs index 779b8ea..2cfd098 100644 --- a/src/core/input/virtual_state.rs +++ b/src/core/input/virtual_state.rs @@ -70,17 +70,14 @@ impl VirtualInputState { pub fn update_from_mouse_button(&mut self, button: MouseButton, button_state: ElementState) { let mut new_value = 0.0; for binding in &mut self.bindings { - match &binding.binding { - VirtualBinding::MouseButton(binding_button, direction) => { - if binding_button == &button { - if button_state == ElementState::Pressed { - binding.value = f32::from(direction); - } else { - binding.value = 0.0; - } + if let VirtualBinding::MouseButton(binding_button, direction) = &binding.binding { + if binding_button == &button { + if button_state == ElementState::Pressed { + binding.value = f32::from(direction); + } else { + binding.value = 0.0; } } - _ => {} } new_value += binding.value; } diff --git a/src/core/render/primitives/camera.rs b/src/core/render/primitives/camera.rs index e8cdf1e..f27622a 100644 --- a/src/core/render/primitives/camera.rs +++ b/src/core/render/primitives/camera.rs @@ -9,7 +9,7 @@ use vulkano::{ use crate::core::{input::InputManager, timer::Timer}; -use super::mvp::MVP; +use super::mvp::Mvp; // See docs/OPENGL_VULKAN_DIFF.md const OPENGL_TO_VULKAN_Y_AXIS_FLIP: Mat4 = Mat4 { @@ -102,7 +102,7 @@ impl Camera3D { pub fn create_buffer( &self, memory_allocator: &Arc, - ) -> Result, Validated> { + ) -> Result, Validated> { let (sin_pitch, cos_pitch) = self.rotation.x.sin_cos(); let (sin_yaw, cos_yaw) = self.rotation.y.sin_cos(); @@ -112,7 +112,7 @@ impl Camera3D { Vec3::Y, ); - MVP { + Mvp { model: OPENGL_TO_VULKAN_Y_AXIS_FLIP.to_cols_array_2d(), view: view_matrix.to_cols_array_2d(), projection: self.projection.to_cols_array_2d(), diff --git a/src/core/render/primitives/mvp.rs b/src/core/render/primitives/mvp.rs index b06b55f..192c67b 100644 --- a/src/core/render/primitives/mvp.rs +++ b/src/core/render/primitives/mvp.rs @@ -1,6 +1,5 @@ use std::sync::Arc; -use glam::Mat4; use vulkano::Validated; use vulkano::buffer::{ AllocateBufferError, Buffer, BufferContents, BufferCreateInfo, BufferUsage, Subbuffer, @@ -9,17 +8,17 @@ use vulkano::memory::allocator::{AllocationCreateInfo, MemoryTypeFilter, Standar #[derive(BufferContents, Clone, Copy)] #[repr(C)] -pub struct MVP { +pub struct Mvp { pub model: [[f32; 4]; 4], pub view: [[f32; 4]; 4], pub projection: [[f32; 4]; 4], } -impl MVP { +impl Mvp { pub fn into_buffer( self, memory_allocator: &Arc, - ) -> Result, Validated> { + ) -> Result, Validated> { Buffer::from_iter( memory_allocator.clone(), BufferCreateInfo { diff --git a/src/core/render/primitives/transform.rs b/src/core/render/primitives/transform.rs index 131e6f1..4d870b0 100644 --- a/src/core/render/primitives/transform.rs +++ b/src/core/render/primitives/transform.rs @@ -35,7 +35,7 @@ impl Default for Transform { impl Transform { pub fn rotate(&mut self, rotation: Quat) { - self.rotation = self.rotation * rotation; + self.rotation *= rotation; } pub fn translate(&mut self, translation: Vec3) { @@ -46,7 +46,7 @@ impl Transform { self.scale *= scale; } - pub fn into_raw(&self) -> TransformRaw { + pub fn to_raw_tranform(&self) -> TransformRaw { TransformRaw { model: (Mat4::from_translation(self.position) * Mat4::from_quat(self.rotation) @@ -59,7 +59,8 @@ impl Transform { memory_allocator: &Arc, transforms: &[Transform], ) -> Result, Validated> { - let transform_raws: Vec = transforms.iter().map(|t| t.into_raw()).collect(); + let transform_raws: Vec = + transforms.iter().map(|t| t.to_raw_tranform()).collect(); let buffer = Buffer::from_iter( memory_allocator.clone(), diff --git a/src/core/render/texture.rs b/src/core/render/texture.rs index ddf88e5..b8270da 100644 --- a/src/core/render/texture.rs +++ b/src/core/render/texture.rs @@ -78,7 +78,7 @@ impl Texture { ImageCreateInfo { image_type: ImageType::Dim2d, format: Format::R8G8B8A8_SRGB, - extent: [image_dimensions.0 as u32, image_dimensions.1 as u32, 1], + extent: [image_dimensions.0, image_dimensions.1, 1], array_layers: 1, usage: ImageUsage::TRANSFER_DST | ImageUsage::SAMPLED, ..Default::default() diff --git a/src/game/assets/square.rs b/src/game/assets/square.rs index 80a24e2..7219157 100644 --- a/src/game/assets/square.rs +++ b/src/game/assets/square.rs @@ -31,7 +31,7 @@ use vulkano::{ }; use crate::core::render::{ - primitives::{mvp::MVP, transform::TransformRaw, vertex::Vertex3D}, + primitives::{mvp::Mvp, transform::TransformRaw, vertex::Vertex3D}, texture::Texture, }; @@ -214,7 +214,7 @@ impl Square { &self, command_buffer: &mut AutoCommandBufferBuilder, descriptor_set_allocator: &Arc, - mvp_uniform: &Subbuffer<[MVP]>, + mvp_uniform: &Subbuffer<[Mvp]>, transform_uniform: &Subbuffer<[TransformRaw]>, texture: &Texture, ) -> Result<(), Box> { diff --git a/src/game/scenes/main_scene.rs b/src/game/scenes/main_scene.rs index 26e8631..00232f7 100644 --- a/src/game/scenes/main_scene.rs +++ b/src/game/scenes/main_scene.rs @@ -59,7 +59,7 @@ impl Scene for MainScene { let num_instances = 100; let instance_size = 10.0; let instance_spacing = 10.0; - let num_instances_per_row = (num_instances as f32 / instance_spacing as f32).ceil() as u32; + let num_instances_per_row = (num_instances as f32 / instance_spacing).ceil() as u32; let instances: Vec = (0..num_instances) .map(|i| Transform { position: Vec3::new( @@ -276,9 +276,7 @@ impl Scene for MainScene { }); }); - let render_future = gui.draw_on_image(render_future, swapchain_image_view.clone()); - - render_future + gui.draw_on_image(render_future, swapchain_image_view.clone()) }); Ok(render_future) From 1aa2dcc55d235573d65f4174484ceb0f4eef630c Mon Sep 17 00:00:00 2001 From: Florian RICHER Date: Fri, 30 May 2025 22:33:06 +0200 Subject: [PATCH 066/105] Avoid to use Arc for not Send/Sync reference --- src/core/app/context.rs | 13 ++++++++----- src/core/app/mod.rs | 13 +++++++------ src/core/input/virtual_input.rs | 2 +- 3 files changed, 16 insertions(+), 12 deletions(-) diff --git a/src/core/app/context.rs b/src/core/app/context.rs index 6820613..f7f8ec4 100644 --- a/src/core/app/context.rs +++ b/src/core/app/context.rs @@ -1,4 +1,7 @@ -use std::sync::{Arc, RwLock}; +use std::{ + rc::Rc, + sync::{Arc, RwLock}, +}; use egui_winit_vulkano::Gui; use vulkano::{ @@ -35,19 +38,19 @@ pub struct ApplicationContext { pub window_id: WindowId, // Données mutables partagées avec Arc> - pub vulkano_windows: Arc>, + pub vulkano_windows: Rc>, pub input_manager: Arc>, pub timer: Arc>, - pub gui: Arc>, + pub gui: Rc>, } impl ApplicationContext { pub fn new( vulkan_context: Arc, - vulkano_windows: Arc>, + vulkano_windows: Rc>, input_manager: Arc>, timer: Arc>, - gui: Arc>, + gui: Rc>, event_loop_proxy: EventLoopProxy, window_id: WindowId, ) -> Self { diff --git a/src/core/app/mod.rs b/src/core/app/mod.rs index 1be0e81..0a1b3de 100644 --- a/src/core/app/mod.rs +++ b/src/core/app/mod.rs @@ -1,4 +1,5 @@ use std::collections::HashMap; +use std::rc::Rc; use std::sync::{Arc, RwLock}; use super::render::vulkan_context::VulkanContext; @@ -27,15 +28,15 @@ pub const DEPTH_IMAGE_ID: usize = 0; pub struct App { vulkan_context: Arc, - vulkano_windows: Arc>, - gui: HashMap>>, + vulkano_windows: Rc>, + gui: HashMap>>, scene_manager: HashMap, input_manager: Arc>, timer: Arc>, event_loop_proxy: EventLoopProxy, // Context d'application partagé par fenêtre - architecture unifiée - app_contexts: HashMap>>, + app_contexts: HashMap>>, } impl App { @@ -46,7 +47,7 @@ impl App { ) -> Self { Self { vulkan_context: Arc::new(VulkanContext::new(vulkano_context)), - vulkano_windows: Arc::new(RwLock::new(VulkanoWindows::default())), + vulkano_windows: Rc::new(RwLock::new(VulkanoWindows::default())), gui: HashMap::new(), input_manager: Arc::new(RwLock::new(input_manager)), scene_manager: HashMap::new(), @@ -98,7 +99,7 @@ impl ApplicationHandler for App { }, ) }; - self.gui.insert(window_id, Arc::new(RwLock::new(gui))); + self.gui.insert(window_id, Rc::new(RwLock::new(gui))); let mut scene_manager = SceneManager::new(); scene_manager.load_scene(Box::new(MainScene::default())); @@ -153,7 +154,7 @@ impl ApplicationHandler for App { .app_contexts .entry(id) .or_insert_with(|| { - Arc::new(RwLock::new(ApplicationContext::new( + Rc::new(RwLock::new(ApplicationContext::new( self.vulkan_context.clone(), self.vulkano_windows.clone(), self.input_manager.clone(), diff --git a/src/core/input/virtual_input.rs b/src/core/input/virtual_input.rs index dfab54d..26ac882 100644 --- a/src/core/input/virtual_input.rs +++ b/src/core/input/virtual_input.rs @@ -40,7 +40,7 @@ impl VirtualInput { pub fn get_state(&self, value_name: &str) -> f32 { self.states .get(value_name) - .and_then(|state| Some(state.lock().value)) + .map(|state| state.lock().value) .unwrap_or(0.0) } From 8a57094478829666c00232a5b3aca2255c5fefdd Mon Sep 17 00:00:00 2001 From: Florian RICHER Date: Fri, 30 May 2025 22:49:18 +0200 Subject: [PATCH 067/105] app_context: Remove useless monitors video modes checking --- src/core/app/context.rs | 64 +++++++---------------------------------- 1 file changed, 10 insertions(+), 54 deletions(-) diff --git a/src/core/app/context.rs b/src/core/app/context.rs index f7f8ec4..713a56b 100644 --- a/src/core/app/context.rs +++ b/src/core/app/context.rs @@ -14,6 +14,7 @@ use vulkano::{ use vulkano_util::{renderer::VulkanoWindowRenderer, window::VulkanoWindows}; use winit::{ event_loop::EventLoopProxy, + monitor::MonitorHandle, window::{Window, WindowId}, }; @@ -78,61 +79,10 @@ impl ApplicationContext { } } - /// Récupère les résolutions disponibles du moniteur (méthode utilitaire statique) - fn get_monitor_resolutions(window: &Window) -> Vec<(u32, u32)> { - // Première tentative : moniteur actuel - if let Some(monitor) = window.current_monitor() { - let resolutions = Self::extract_resolutions_from_monitor(&monitor); - if !resolutions.is_empty() { - log::debug!( - "Résolutions trouvées via moniteur actuel: {:?}", - resolutions - ); - return resolutions; - } - } - - // Deuxième tentative : tous les moniteurs disponibles - log::debug!("Tentative de récupération via tous les moniteurs disponibles..."); - let mut all_resolutions = Vec::new(); - - for monitor in window.available_monitors() { - let resolutions = Self::extract_resolutions_from_monitor(&monitor); - all_resolutions.extend(resolutions); - } - - if !all_resolutions.is_empty() { - // Supprime les doublons et trie - all_resolutions.sort_unstable(); - all_resolutions.dedup(); - all_resolutions.sort_by(|a, b| (b.0 * b.1).cmp(&(a.0 * a.1))); - - log::debug!( - "Résolutions trouvées via tous les moniteurs: {:?}", - all_resolutions - ); - return all_resolutions; - } - - // Aucune résolution détectée - retourne un vecteur vide - log::warn!("Aucune résolution détectée pour cette fenêtre"); - Vec::new() - } - /// Extrait les résolutions d'un moniteur donné - fn extract_resolutions_from_monitor( - monitor: &winit::monitor::MonitorHandle, - ) -> Vec<(u32, u32)> { + fn extract_resolutions_from_monitor(monitor: MonitorHandle) -> Vec<(u32, u32)> { let video_modes: Vec<_> = monitor.video_modes().collect(); - if video_modes.is_empty() { - log::debug!( - "Aucun mode vidéo trouvé pour le moniteur {:?}", - monitor.name() - ); - return Vec::new(); - } - let resolutions: Vec<(u32, u32)> = video_modes .into_iter() .map(|mode| { @@ -141,7 +91,7 @@ impl ApplicationContext { }) .collect(); - log::debug!( + log::trace!( "Modes vidéo trouvés pour {:?}: {:?}", monitor.name(), resolutions @@ -151,7 +101,13 @@ impl ApplicationContext { /// Récupère les résolutions disponibles pub fn get_available_resolutions(&self) -> Vec<(u32, u32)> { - self.with_renderer(|renderer| Self::get_monitor_resolutions(renderer.window())) + self.with_renderer(|renderer| { + renderer + .window() + .current_monitor() + .map(Self::extract_resolutions_from_monitor) + .unwrap_or_default() + }) } /// Récupère le delta time actuel depuis le timer From d765e78da40b6bd3a731bc7c9b42f5e9046cb533 Mon Sep 17 00:00:00 2001 From: Florian RICHER Date: Fri, 30 May 2025 23:27:03 +0200 Subject: [PATCH 068/105] Move to tracing with tracy support --- Cargo.lock | 440 +++++++++++++++++++++++++------------ Cargo.toml | 6 +- flake.nix | 2 +- src/core/app/context.rs | 2 +- src/core/app/mod.rs | 42 +++- src/core/render/texture.rs | 13 +- src/core/scene/manager.rs | 2 +- src/main.rs | 20 +- 8 files changed, 369 insertions(+), 158 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 375be30..ffb6291 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -79,56 +79,6 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04" -[[package]] -name = "anstream" -version = "0.6.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" -dependencies = [ - "anstyle", - "anstyle-parse", - "anstyle-query", - "anstyle-wincon", - "colorchoice", - "is_terminal_polyfill", - "utf8parse", -] - -[[package]] -name = "anstyle" -version = "1.0.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" - -[[package]] -name = "anstyle-parse" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" -dependencies = [ - "utf8parse", -] - -[[package]] -name = "anstyle-query" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" -dependencies = [ - "windows-sys 0.59.0", -] - -[[package]] -name = "anstyle-wincon" -version = "3.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" -dependencies = [ - "anstyle", - "once_cell", - "windows-sys 0.59.0", -] - [[package]] name = "anyhow" version = "1.0.98" @@ -400,12 +350,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" -[[package]] -name = "colorchoice" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" - [[package]] name = "combine" version = "4.6.7" @@ -652,29 +596,6 @@ dependencies = [ "bytemuck", ] -[[package]] -name = "env_filter" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" -dependencies = [ - "log", - "regex", -] - -[[package]] -name = "env_logger" -version = "0.11.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f" -dependencies = [ - "anstream", - "anstyle", - "env_filter", - "jiff", - "log", -] - [[package]] name = "epaint" version = "0.31.1" @@ -797,6 +718,20 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "generator" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d18470a76cb7f8ff746cf1f7470914f900252ec36bbc40b569d74b1258446827" +dependencies = [ + "cc", + "cfg-if", + "libc", + "log", + "rustversion", + "windows", +] + [[package]] name = "gethostname" version = "0.4.3" @@ -1057,12 +992,6 @@ dependencies = [ "syn", ] -[[package]] -name = "is_terminal_polyfill" -version = "1.70.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" - [[package]] name = "itertools" version = "0.12.1" @@ -1078,30 +1007,6 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" -[[package]] -name = "jiff" -version = "0.2.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f02000660d30638906021176af16b17498bd0d12813dbfe7b276d8bc7f3c0806" -dependencies = [ - "jiff-static", - "log", - "portable-atomic", - "portable-atomic-util", - "serde", -] - -[[package]] -name = "jiff-static" -version = "0.2.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3c30758ddd7188629c6713fc45d1188af4f44c90582311d0c8d8c9907f60c48" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "jni" version = "0.21.1" @@ -1150,6 +1055,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + [[package]] name = "lebe" version = "0.5.2" @@ -1221,6 +1132,19 @@ version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" +[[package]] +name = "loom" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca" +dependencies = [ + "cfg-if", + "generator", + "scoped-tls", + "tracing", + "tracing-subscriber", +] + [[package]] name = "loop9" version = "0.1.5" @@ -1230,6 +1154,15 @@ dependencies = [ "imgref", ] +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + [[package]] name = "maybe-rayon" version = "0.1.1" @@ -1329,6 +1262,16 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + [[package]] name = "num-bigint" version = "0.4.6" @@ -1709,6 +1652,12 @@ dependencies = [ "libredox", ] +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + [[package]] name = "owned_ttf_parser" version = "0.25.0" @@ -1813,21 +1762,6 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "portable-atomic" -version = "1.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e" - -[[package]] -name = "portable-atomic-util" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" -dependencies = [ - "portable-atomic", -] - [[package]] name = "potential_utf" version = "0.1.2" @@ -2095,8 +2029,17 @@ checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", - "regex-automata", - "regex-syntax", + "regex-automata 0.4.9", + "regex-syntax 0.8.5", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", ] [[package]] @@ -2107,9 +2050,15 @@ checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", - "regex-syntax", + "regex-syntax 0.8.5", ] +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + [[package]] name = "regex-syntax" version = "0.8.5" @@ -2137,12 +2086,14 @@ version = "0.1.0" dependencies = [ "anyhow", "egui_winit_vulkano", - "env_logger", "glam", "image", - "log", "rand 0.9.1", "thiserror 2.0.12", + "tracing", + "tracing-log", + "tracing-subscriber", + "tracing-tracy", "vulkano", "vulkano-shaders", "vulkano-util", @@ -2270,6 +2221,15 @@ dependencies = [ "roxmltree", ] +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + [[package]] name = "shlex" version = "1.3.0" @@ -2547,14 +2507,91 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ "pin-project-lite", + "tracing-attributes", "tracing-core", ] +[[package]] +name = "tracing-attributes" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "tracing-core" version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "tracing-tracy" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eaa1852afa96e0fe9e44caa53dc0bd2d9d05e0f2611ce09f97f8677af56e4ba" +dependencies = [ + "tracing-core", + "tracing-subscriber", + "tracy-client", +] + +[[package]] +name = "tracy-client" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d90a2c01305b02b76fdd89ac8608bae27e173c829a35f7d76a345ab5d33836db" +dependencies = [ + "loom", + "once_cell", + "tracy-client-sys", +] + +[[package]] +name = "tracy-client-sys" +version = "0.24.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69fff37da548239c3bf9e64a12193d261e8b22b660991c6fd2df057c168f435f" +dependencies = [ + "cc", + "windows-targets 0.52.6", +] [[package]] name = "ttf-parser" @@ -2591,12 +2628,6 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" -[[package]] -name = "utf8parse" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" - [[package]] name = "v_frame" version = "0.3.8" @@ -2608,6 +2639,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + [[package]] name = "version-compare" version = "0.2.0" @@ -2947,6 +2984,22 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a751b3277700db47d3e574514de2eced5e54dc8a5436a3bf7a0b248b2cee16f3" +[[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.9" @@ -2956,6 +3009,114 @@ dependencies = [ "windows-sys 0.59.0", ] +[[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.61.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5ee8f3d025738cb02bad7868bbb5f8a6327501e870bf51f1b455b0a2454a419" +dependencies = [ + "windows-collections", + "windows-core", + "windows-future", + "windows-link", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" +dependencies = [ + "windows-core", +] + +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-future" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" +dependencies = [ + "windows-core", + "windows-link", + "windows-threading", +] + +[[package]] +name = "windows-implement" +version = "0.60.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" + +[[package]] +name = "windows-numerics" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" +dependencies = [ + "windows-core", + "windows-link", +] + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-sys" version = "0.45.0" @@ -3029,6 +3190,15 @@ dependencies = [ "windows_x86_64_msvc 0.52.6", ] +[[package]] +name = "windows-threading" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" +dependencies = [ + "windows-link", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.42.2" diff --git a/Cargo.toml b/Cargo.toml index feb1bca..af26232 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,8 +21,10 @@ image = { version = "0.25", features = ["png", "jpeg"] } glam = { version = "0.30" } # Log and tracing -log = "0.4" -env_logger = "0.11" +tracing = "0.1" +tracing-subscriber = { version = "0.3", features = ["env-filter", "fmt"] } +tracing-log = "0.2" +tracing-tracy = "0.11" # Random rand = "0.9" diff --git a/flake.nix b/flake.nix index c5bbd9a..645ade4 100644 --- a/flake.nix +++ b/flake.nix @@ -31,7 +31,7 @@ cargo = rust; }); - buildInputs = with pkgs; [ vulkan-headers vulkan-loader vulkan-validation-layers renderdoc ] + buildInputs = with pkgs; [ vulkan-headers vulkan-loader vulkan-validation-layers renderdoc tracy ] ++ pkgs.lib.optionals pkgs.stdenv.hostPlatform.isLinux (with pkgs; [ stdenv.cc.cc.lib diff --git a/src/core/app/context.rs b/src/core/app/context.rs index 713a56b..cedd65f 100644 --- a/src/core/app/context.rs +++ b/src/core/app/context.rs @@ -91,7 +91,7 @@ impl ApplicationContext { }) .collect(); - log::trace!( + tracing::trace!( "Modes vidéo trouvés pour {:?}: {:?}", monitor.name(), resolutions diff --git a/src/core/app/mod.rs b/src/core/app/mod.rs index 0a1b3de..96fed31 100644 --- a/src/core/app/mod.rs +++ b/src/core/app/mod.rs @@ -129,7 +129,7 @@ impl ApplicationHandler for App { match event { WindowEvent::CloseRequested => { - log::debug!("The close button was pressed; stopping"); + tracing::debug!("The close button was pressed; stopping"); event_loop.exit(); } WindowEvent::Resized(_) | WindowEvent::ScaleFactorChanged { .. } => { @@ -137,7 +137,10 @@ impl ApplicationHandler for App { vulkano_windows.get_renderer_mut(id).unwrap().resize(); } WindowEvent::RedrawRequested => { + let _frame_span = tracing::info_span!("frame").entered(); + { + let _input_span = tracing::debug_span!("input_update").entered(); let mut input_manager = self .input_manager .write() @@ -145,6 +148,7 @@ impl ApplicationHandler for App { input_manager.update(); } { + let _timer_span = tracing::debug_span!("timer_update").entered(); let mut timer = self.timer.write().expect("Failed to lock timer"); timer.update(); } @@ -170,6 +174,7 @@ impl ApplicationHandler for App { // Utiliser le contexte partagé pour les scènes { + let _scene_span = tracing::info_span!("scene_processing").entered(); let mut context = app_context.write().unwrap(); scene_manager @@ -177,18 +182,31 @@ impl ApplicationHandler for App { .unwrap(); if let Some(scene) = scene_manager.current_scene_mut() { - scene.update(&mut context).unwrap(); + { + let _update_span = tracing::debug_span!("scene_update").entered(); + scene.update(&mut context).unwrap(); + } - let acquire_future = context - .with_renderer_mut(|renderer| renderer.acquire(None, |_| {}).unwrap()); + let acquire_future = { + let _acquire_span = tracing::debug_span!("acquire_swapchain").entered(); + context.with_renderer_mut(|renderer| { + renderer.acquire(None, |_| {}).unwrap() + }) + }; - let acquire_future = scene.render(acquire_future, &mut context).unwrap(); + let acquire_future = { + let _render_span = tracing::debug_span!("scene_render").entered(); + scene.render(acquire_future, &mut context).unwrap() + }; - context.with_renderer_mut(|renderer| { - renderer.present(acquire_future, true); - }); + { + let _present_span = tracing::debug_span!("present_frame").entered(); + context.with_renderer_mut(|renderer| { + renderer.present(acquire_future, true); + }); + } } else { - log::warn!("No current scene found for update!"); + tracing::warn!("No current scene found for update!"); } } } @@ -208,7 +226,7 @@ impl ApplicationHandler for App { let vulkano_windows = self.vulkano_windows.read().unwrap(); let window = vulkano_windows.get_window(window_id).unwrap(); if let Err(e) = window.set_cursor_grab(grab) { - log::error!("Failed to set cursor grab: {}", e); + tracing::error!("Failed to set cursor grab: {}", e); } } UserEvent::CursorVisible(window_id, visible) => { @@ -227,7 +245,7 @@ impl ApplicationHandler for App { let _ = window.request_inner_size(winit::dpi::LogicalSize::new(width, height)); let renderer = vulkano_windows.get_renderer_mut(window_id).unwrap(); renderer.resize(); - log::info!( + tracing::info!( "Resolution changed to {}x{} for window {:?}", width, height, @@ -235,7 +253,7 @@ impl ApplicationHandler for App { ); } UserEvent::Exit(window_id) => { - log::info!("Exit requested for window {:?}", window_id); + tracing::info!("Exit requested for window {:?}", window_id); event_loop.exit(); } } diff --git a/src/core/render/texture.rs b/src/core/render/texture.rs index b8270da..8b7f63c 100644 --- a/src/core/render/texture.rs +++ b/src/core/render/texture.rs @@ -31,8 +31,10 @@ impl Texture { builder: &mut AutoCommandBufferBuilder, path: &str, ) -> Result { - let image = image::open(path)?; - Self::from_dynamic_image(device, memory_allocator, builder, image) + let _span = tracing::info_span!("texture_load_from_file", path = path); + + let bytes = std::fs::read(path)?; + Self::from_bytes(device, memory_allocator, builder, &bytes) } pub fn from_bytes( @@ -51,8 +53,11 @@ impl Texture { builder: &mut AutoCommandBufferBuilder, image: DynamicImage, ) -> Result { + let _span = tracing::info_span!("texture_from_dynamic_image"); + let image_data = image.to_rgba8(); let image_dimensions = image_data.dimensions(); + let image_data = image_data.into_raw(); let upload_buffer = Buffer::new_slice( memory_allocator.clone(), @@ -70,7 +75,7 @@ impl Texture { { let buffer_data = &mut *upload_buffer.write()?; - buffer_data.copy_from_slice(image_data.as_raw()); + buffer_data.copy_from_slice(&image_data); } let image = Image::new( @@ -103,7 +108,7 @@ impl Texture { let image_view = ImageView::new_default(image)?; - log::trace!("Texture loaded with dimensions {:?}", image_dimensions); + tracing::trace!("Texture loaded with dimensions {:?}", image_dimensions); Ok(Self::new(image_view, sampler)) } diff --git a/src/core/scene/manager.rs b/src/core/scene/manager.rs index a12892e..f8e5b0f 100644 --- a/src/core/scene/manager.rs +++ b/src/core/scene/manager.rs @@ -58,7 +58,7 @@ impl SceneManager { scene.load(app_context)?; } } else { - log::warn!("No scene found in SceneManager!"); + tracing::warn!("No scene found in SceneManager!"); } Ok(()) } diff --git a/src/main.rs b/src/main.rs index 1ea49c3..998f725 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,7 +13,23 @@ mod core; mod game; fn main() { - env_logger::init(); + use tracing_subscriber::layer::SubscriberExt; + use tracing_subscriber::util::SubscriberInitExt; + + tracing_subscriber::registry() + .with( + tracing_subscriber::fmt::layer() + .with_target(true) + .with_thread_ids(true) + .with_file(true) + .with_line_number(true), + ) + .with(tracing_tracy::TracyLayer::default()) + .with( + tracing_subscriber::EnvFilter::try_from_default_env() + .unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info")), + ) + .init(); let input_manager = InputManager::new(HashMap::from([ ( @@ -86,7 +102,7 @@ fn main() { match event_loop.run_app(&mut app) { Ok(_) => {} Err(e) => { - log::error!("Error running old app: {e}"); + tracing::error!("Error running old app: {e}"); } } } From e5d8dd58f24b3846033fdb99204e12785f923499 Mon Sep 17 00:00:00 2001 From: Florian RICHER Date: Fri, 30 May 2025 23:35:13 +0200 Subject: [PATCH 069/105] Add loading scene if not loaded on tracy --- src/core/app/mod.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/core/app/mod.rs b/src/core/app/mod.rs index 96fed31..c5e7acb 100644 --- a/src/core/app/mod.rs +++ b/src/core/app/mod.rs @@ -177,9 +177,13 @@ impl ApplicationHandler for App { let _scene_span = tracing::info_span!("scene_processing").entered(); let mut context = app_context.write().unwrap(); - scene_manager - .load_scene_if_not_loaded(&mut context) - .unwrap(); + { + let _scene_span = + tracing::info_span!("scene_loading_if_not_loaded").entered(); + scene_manager + .load_scene_if_not_loaded(&mut context) + .unwrap(); + } if let Some(scene) = scene_manager.current_scene_mut() { { From a293b962f7ddc4e2fc0212f6a4cb9d3a4df5b32b Mon Sep 17 00:00:00 2001 From: Florian RICHER Date: Sat, 31 May 2025 12:40:19 +0200 Subject: [PATCH 070/105] use RefCell instead of RwLock - Gui and VulkanoWindows is not thread safe - It's more adapted with association with Rc --- src/core/app/context.rs | 23 +++++++++-------------- src/core/app/mod.rs | 32 +++++++++++++++----------------- 2 files changed, 24 insertions(+), 31 deletions(-) diff --git a/src/core/app/context.rs b/src/core/app/context.rs index cedd65f..d6d1647 100644 --- a/src/core/app/context.rs +++ b/src/core/app/context.rs @@ -1,4 +1,5 @@ use std::{ + cell::RefCell, rc::Rc, sync::{Arc, RwLock}, }; @@ -39,19 +40,19 @@ pub struct ApplicationContext { pub window_id: WindowId, // Données mutables partagées avec Arc> - pub vulkano_windows: Rc>, + pub vulkano_windows: Rc>, pub input_manager: Arc>, pub timer: Arc>, - pub gui: Rc>, + pub gui: Rc>, } impl ApplicationContext { pub fn new( vulkan_context: Arc, - vulkano_windows: Rc>, + vulkano_windows: Rc>, input_manager: Arc>, timer: Arc>, - gui: Rc>, + gui: Rc>, event_loop_proxy: EventLoopProxy, window_id: WindowId, ) -> Self { @@ -129,10 +130,7 @@ impl ApplicationContext { where F: FnOnce(&VulkanoWindowRenderer) -> T, { - let vulkano_windows = self - .vulkano_windows - .read() - .expect("Failed to lock vulkano_windows"); + let vulkano_windows = self.vulkano_windows.borrow_mut(); let renderer = vulkano_windows .get_renderer(self.window_id) .expect("Failed to get renderer"); @@ -144,10 +142,7 @@ impl ApplicationContext { where F: FnOnce(&mut VulkanoWindowRenderer) -> T, { - let mut vulkano_windows = self - .vulkano_windows - .write() - .expect("Failed to lock vulkano_windows"); + let mut vulkano_windows = self.vulkano_windows.borrow_mut(); let renderer = vulkano_windows .get_renderer_mut(self.window_id) .expect("Failed to get renderer"); @@ -159,7 +154,7 @@ impl ApplicationContext { where F: FnOnce(&Gui) -> T, { - let gui = self.gui.read().expect("Failed to lock gui"); + let gui = self.gui.borrow(); f(&gui) } @@ -168,7 +163,7 @@ impl ApplicationContext { where F: FnOnce(&mut Gui) -> T, { - let mut gui = self.gui.write().expect("Failed to lock gui"); + let mut gui = self.gui.borrow_mut(); f(&mut gui) } diff --git a/src/core/app/mod.rs b/src/core/app/mod.rs index c5e7acb..d936f57 100644 --- a/src/core/app/mod.rs +++ b/src/core/app/mod.rs @@ -1,3 +1,4 @@ +use std::cell::RefCell; use std::collections::HashMap; use std::rc::Rc; use std::sync::{Arc, RwLock}; @@ -28,15 +29,15 @@ pub const DEPTH_IMAGE_ID: usize = 0; pub struct App { vulkan_context: Arc, - vulkano_windows: Rc>, - gui: HashMap>>, + vulkano_windows: Rc>, + gui: HashMap>>, scene_manager: HashMap, input_manager: Arc>, timer: Arc>, event_loop_proxy: EventLoopProxy, // Context d'application partagé par fenêtre - architecture unifiée - app_contexts: HashMap>>, + app_contexts: HashMap>>, } impl App { @@ -47,7 +48,7 @@ impl App { ) -> Self { Self { vulkan_context: Arc::new(VulkanContext::new(vulkano_context)), - vulkano_windows: Rc::new(RwLock::new(VulkanoWindows::default())), + vulkano_windows: Rc::new(RefCell::new(VulkanoWindows::default())), gui: HashMap::new(), input_manager: Arc::new(RwLock::new(input_manager)), scene_manager: HashMap::new(), @@ -60,10 +61,7 @@ impl App { impl ApplicationHandler for App { fn resumed(&mut self, event_loop: &ActiveEventLoop) { - let mut vulkano_windows = self - .vulkano_windows - .write() - .expect("Failed to lock vulkano_windows"); + let mut vulkano_windows = self.vulkano_windows.borrow_mut(); let window_id = vulkano_windows.create_window( event_loop, self.vulkan_context.vulkano_context(), @@ -99,7 +97,7 @@ impl ApplicationHandler for App { }, ) }; - self.gui.insert(window_id, Rc::new(RwLock::new(gui))); + self.gui.insert(window_id, Rc::new(RefCell::new(gui))); let mut scene_manager = SceneManager::new(); scene_manager.load_scene(Box::new(MainScene::default())); @@ -120,7 +118,7 @@ impl ApplicationHandler for App { fn window_event(&mut self, event_loop: &ActiveEventLoop, id: WindowId, event: WindowEvent) { { let gui = self.gui.get_mut(&id).unwrap(); - let mut gui = gui.write().expect("Failed to lock gui"); + let mut gui = gui.borrow_mut(); if !gui.update(&event) { let mut input_manager = self.input_manager.write().unwrap(); input_manager.process_window_event(&event); @@ -133,7 +131,7 @@ impl ApplicationHandler for App { event_loop.exit(); } WindowEvent::Resized(_) | WindowEvent::ScaleFactorChanged { .. } => { - let mut vulkano_windows = self.vulkano_windows.write().unwrap(); + let mut vulkano_windows = self.vulkano_windows.borrow_mut(); vulkano_windows.get_renderer_mut(id).unwrap().resize(); } WindowEvent::RedrawRequested => { @@ -158,7 +156,7 @@ impl ApplicationHandler for App { .app_contexts .entry(id) .or_insert_with(|| { - Rc::new(RwLock::new(ApplicationContext::new( + Rc::new(RefCell::new(ApplicationContext::new( self.vulkan_context.clone(), self.vulkano_windows.clone(), self.input_manager.clone(), @@ -175,7 +173,7 @@ impl ApplicationHandler for App { // Utiliser le contexte partagé pour les scènes { let _scene_span = tracing::info_span!("scene_processing").entered(); - let mut context = app_context.write().unwrap(); + let mut context = app_context.borrow_mut(); { let _scene_span = @@ -219,7 +217,7 @@ impl ApplicationHandler for App { } fn about_to_wait(&mut self, _event_loop: &ActiveEventLoop) { - let vulkano_windows = self.vulkano_windows.read().unwrap(); + let vulkano_windows = self.vulkano_windows.borrow(); let window = vulkano_windows.get_primary_window().unwrap(); window.request_redraw(); } @@ -227,14 +225,14 @@ impl ApplicationHandler for App { fn user_event(&mut self, event_loop: &ActiveEventLoop, event: UserEvent) { match event { UserEvent::CursorGrabMode(window_id, grab) => { - let vulkano_windows = self.vulkano_windows.read().unwrap(); + let vulkano_windows = self.vulkano_windows.borrow(); let window = vulkano_windows.get_window(window_id).unwrap(); if let Err(e) = window.set_cursor_grab(grab) { tracing::error!("Failed to set cursor grab: {}", e); } } UserEvent::CursorVisible(window_id, visible) => { - let vulkano_windows = self.vulkano_windows.read().unwrap(); + let vulkano_windows = self.vulkano_windows.borrow(); let window = vulkano_windows.get_window(window_id).unwrap(); window.set_cursor_visible(visible); } @@ -244,7 +242,7 @@ impl ApplicationHandler for App { } } UserEvent::ChangeResolution(window_id, width, height) => { - let mut vulkano_windows = self.vulkano_windows.write().unwrap(); + let mut vulkano_windows = self.vulkano_windows.borrow_mut(); let window = vulkano_windows.get_window(window_id).unwrap(); let _ = window.request_inner_size(winit::dpi::LogicalSize::new(width, height)); let renderer = vulkano_windows.get_renderer_mut(window_id).unwrap(); From 45ccf030f61988d728b2ed8cf14b9ca94a56dff3 Mon Sep 17 00:00:00 2001 From: Florian RICHER Date: Sat, 31 May 2025 12:56:00 +0200 Subject: [PATCH 071/105] app: refactor WindowContext name and creation --- src/core/app/context.rs | 10 ++---- src/core/app/mod.rs | 53 +++++++++++++++---------------- src/core/scene/manager.rs | 4 +-- src/core/scene/mod.rs | 8 ++--- src/game/scenes/main_scene.rs | 11 +++---- src/game/scenes/settings_scene.rs | 8 ++--- 6 files changed, 42 insertions(+), 52 deletions(-) diff --git a/src/core/app/context.rs b/src/core/app/context.rs index d6d1647..80b602e 100644 --- a/src/core/app/context.rs +++ b/src/core/app/context.rs @@ -13,11 +13,7 @@ use vulkano::{ memory::allocator::StandardMemoryAllocator, }; use vulkano_util::{renderer::VulkanoWindowRenderer, window::VulkanoWindows}; -use winit::{ - event_loop::EventLoopProxy, - monitor::MonitorHandle, - window::{Window, WindowId}, -}; +use winit::{event_loop::EventLoopProxy, monitor::MonitorHandle, window::WindowId}; use crate::core::{input::InputManager, render::vulkan_context::VulkanContext, timer::Timer}; @@ -25,7 +21,7 @@ use super::user_event::UserEvent; /// Contexte d'application unifié avec Arc> pour la mutabilité partagée #[derive(Clone)] -pub struct ApplicationContext { +pub struct WindowContext { // Données Vulkan (immutables) pub vulkan_context: Arc, pub device: Arc, @@ -46,7 +42,7 @@ pub struct ApplicationContext { pub gui: Rc>, } -impl ApplicationContext { +impl WindowContext { pub fn new( vulkan_context: Arc, vulkano_windows: Rc>, diff --git a/src/core/app/mod.rs b/src/core/app/mod.rs index d936f57..81fb0a3 100644 --- a/src/core/app/mod.rs +++ b/src/core/app/mod.rs @@ -20,7 +20,7 @@ use winit::event::WindowEvent; use winit::event_loop::{ActiveEventLoop, EventLoopProxy}; use winit::window::WindowId; -use self::context::ApplicationContext; +use self::context::WindowContext; pub mod context; pub mod user_event; @@ -37,7 +37,7 @@ pub struct App { event_loop_proxy: EventLoopProxy, // Context d'application partagé par fenêtre - architecture unifiée - app_contexts: HashMap>>, + app_contexts: HashMap>>, } impl App { @@ -103,6 +103,17 @@ impl ApplicationHandler for App { scene_manager.load_scene(Box::new(MainScene::default())); self.scene_manager.insert(window_id, scene_manager); + + let app_context = Rc::new(RefCell::new(WindowContext::new( + self.vulkan_context.clone(), + self.vulkano_windows.clone(), + self.input_manager.clone(), + self.timer.clone(), + self.gui.get(&window_id).unwrap().clone(), + self.event_loop_proxy.clone(), + window_id, + ))); + self.app_contexts.insert(window_id, app_context); } fn device_event( @@ -152,28 +163,12 @@ impl ApplicationHandler for App { } // Créer ou mettre à jour le contexte d'application - let app_context = self - .app_contexts - .entry(id) - .or_insert_with(|| { - Rc::new(RefCell::new(ApplicationContext::new( - self.vulkan_context.clone(), - self.vulkano_windows.clone(), - self.input_manager.clone(), - self.timer.clone(), - self.gui.get(&id).unwrap().clone(), - self.event_loop_proxy.clone(), - id, - ))) - }) - .clone(); - + let window_context = self.app_contexts.get(&id).unwrap().clone(); let scene_manager = self.scene_manager.get_mut(&id).unwrap(); // Utiliser le contexte partagé pour les scènes { - let _scene_span = tracing::info_span!("scene_processing").entered(); - let mut context = app_context.borrow_mut(); + let mut context = window_context.borrow_mut(); { let _scene_span = @@ -211,17 +206,19 @@ impl ApplicationHandler for App { tracing::warn!("No current scene found for update!"); } } + + { + let _gui_span = tracing::debug_span!("request_redraw").entered(); + let mut window_context = window_context.borrow_mut(); + window_context.with_renderer_mut(|renderer| { + renderer.window().request_redraw(); + }); + } } _ => {} } } - fn about_to_wait(&mut self, _event_loop: &ActiveEventLoop) { - let vulkano_windows = self.vulkano_windows.borrow(); - let window = vulkano_windows.get_primary_window().unwrap(); - window.request_redraw(); - } - fn user_event(&mut self, event_loop: &ActiveEventLoop, event: UserEvent) { match event { UserEvent::CursorGrabMode(window_id, grab) => { @@ -247,7 +244,7 @@ impl ApplicationHandler for App { let _ = window.request_inner_size(winit::dpi::LogicalSize::new(width, height)); let renderer = vulkano_windows.get_renderer_mut(window_id).unwrap(); renderer.resize(); - tracing::info!( + tracing::trace!( "Resolution changed to {}x{} for window {:?}", width, height, @@ -255,7 +252,7 @@ impl ApplicationHandler for App { ); } UserEvent::Exit(window_id) => { - tracing::info!("Exit requested for window {:?}", window_id); + tracing::trace!("Exit requested for window {:?}", window_id); event_loop.exit(); } } diff --git a/src/core/scene/manager.rs b/src/core/scene/manager.rs index f8e5b0f..cf07ebc 100644 --- a/src/core/scene/manager.rs +++ b/src/core/scene/manager.rs @@ -1,6 +1,6 @@ use std::error::Error; -use crate::core::app::context::ApplicationContext; +use crate::core::app::context::WindowContext; use super::Scene; @@ -51,7 +51,7 @@ impl SceneManager { pub fn load_scene_if_not_loaded( &mut self, - app_context: &mut ApplicationContext, + app_context: &mut WindowContext, ) -> Result<(), Box> { if let Some(scene) = self.current_scene_mut() { if !scene.loaded() { diff --git a/src/core/scene/mod.rs b/src/core/scene/mod.rs index 58a75df..5dd8a57 100644 --- a/src/core/scene/mod.rs +++ b/src/core/scene/mod.rs @@ -2,18 +2,18 @@ use std::error::Error; use vulkano::sync::GpuFuture; -use crate::core::app::context::ApplicationContext; +use crate::core::app::context::WindowContext; pub mod manager; pub trait Scene { fn loaded(&self) -> bool; - fn load(&mut self, app_context: &mut ApplicationContext) -> Result<(), Box>; - fn update(&mut self, app_context: &mut ApplicationContext) -> Result<(), Box>; + fn load(&mut self, app_context: &mut WindowContext) -> Result<(), Box>; + fn update(&mut self, app_context: &mut WindowContext) -> Result<(), Box>; fn render( &mut self, acquire_future: Box, - app_context: &mut ApplicationContext, + app_context: &mut WindowContext, ) -> Result, Box>; fn unload(&mut self); } diff --git a/src/game/scenes/main_scene.rs b/src/game/scenes/main_scene.rs index 00232f7..37fd4ed 100644 --- a/src/game/scenes/main_scene.rs +++ b/src/game/scenes/main_scene.rs @@ -2,7 +2,7 @@ use std::error::Error; use super::settings_scene::SettingsScene; use crate::core::app::DEPTH_IMAGE_ID; -use crate::core::app::context::ApplicationContext; +use crate::core::app::context::WindowContext; use crate::core::app::user_event::UserEvent; use crate::core::render::primitives::camera::Camera3D; use crate::core::render::primitives::transform::Transform; @@ -38,10 +38,7 @@ impl Scene for MainScene { self.state.is_some() } - fn load( - &mut self, - app_context: &mut ApplicationContext, - ) -> Result<(), Box> { + fn load(&mut self, app_context: &mut WindowContext) -> Result<(), Box> { let depth_image_view = app_context.with_renderer_mut(|renderer| { renderer.get_additional_image_view(DEPTH_IMAGE_ID).clone() }); @@ -118,7 +115,7 @@ impl Scene for MainScene { Ok(()) } - fn update(&mut self, app_context: &mut ApplicationContext) -> Result<(), Box> { + fn update(&mut self, app_context: &mut WindowContext) -> Result<(), Box> { let state = self.state.as_mut().unwrap(); app_context.with_input_manager(|input_manager| { app_context.with_timer(|timer| { @@ -168,7 +165,7 @@ impl Scene for MainScene { fn render( &mut self, before_future: Box, - app_context: &mut ApplicationContext, + app_context: &mut WindowContext, ) -> Result, Box> { let state = self.state.as_ref().ok_or("State not loaded")?; diff --git a/src/game/scenes/settings_scene.rs b/src/game/scenes/settings_scene.rs index 4d6fc53..466652a 100644 --- a/src/game/scenes/settings_scene.rs +++ b/src/game/scenes/settings_scene.rs @@ -1,7 +1,7 @@ use std::error::Error; use crate::core::app::DEPTH_IMAGE_ID; -use crate::core::app::context::ApplicationContext; +use crate::core::app::context::WindowContext; use crate::core::app::user_event::UserEvent; use crate::core::render::render_pass_manager::{RenderPassConfig, RenderPassManager}; use crate::core::scene::Scene; @@ -27,7 +27,7 @@ impl Scene for SettingsScene { self.state.is_some() } - fn load(&mut self, app_context: &mut ApplicationContext) -> Result<(), Box> { + fn load(&mut self, app_context: &mut WindowContext) -> Result<(), Box> { let current_resolution = app_context.get_window_size(); let available_resolutions = app_context.get_available_resolutions(); @@ -39,14 +39,14 @@ impl Scene for SettingsScene { Ok(()) } - fn update(&mut self, _app_context: &mut ApplicationContext) -> Result<(), Box> { + fn update(&mut self, _app_context: &mut WindowContext) -> Result<(), Box> { Ok(()) } fn render( &mut self, before_future: Box, - app_context: &mut ApplicationContext, + app_context: &mut WindowContext, ) -> Result, Box> { let state = self.state.as_ref().ok_or("State not found")?; From 9d3800c7182fa205070fdc1182c441850c78ec19 Mon Sep 17 00:00:00 2001 From: Florian RICHER Date: Sat, 31 May 2025 12:59:32 +0200 Subject: [PATCH 072/105] virtual_input: Use RwLock instead of Mutex --- src/core/input/virtual_input.rs | 44 ++++++++++++++++----------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/src/core/input/virtual_input.rs b/src/core/input/virtual_input.rs index 26ac882..50dcb11 100644 --- a/src/core/input/virtual_input.rs +++ b/src/core/input/virtual_input.rs @@ -1,6 +1,6 @@ use std::{collections::HashMap, sync::Arc}; -use egui_winit_vulkano::egui::mutex::Mutex; +use egui_winit_vulkano::egui::mutex::RwLock; use winit::{ event::{AxisId, ElementState, MouseButton}, keyboard::PhysicalKey, @@ -14,14 +14,14 @@ use super::{ #[derive(Default)] pub struct VirtualInput { // Global states - states: HashMap>>, + states: HashMap>>, // Per kind of input states to keep complexity low during state updates - states_by_key: HashMap>>>, - mouse_move_states: Vec>>, - mouse_wheel_states: Vec>>, - mouse_button_states: HashMap>>>, - axis_states: HashMap>>>, + states_by_key: HashMap>>>, + mouse_move_states: Vec>>, + mouse_wheel_states: Vec>>, + mouse_button_states: HashMap>>>, + axis_states: HashMap>>>, } impl std::fmt::Debug for VirtualInput { @@ -29,7 +29,7 @@ impl std::fmt::Debug for VirtualInput { let mut debug = f.debug_struct("VirtualInput"); for (name, state) in &self.states { - debug.field(name, &state.lock().value); + debug.field(name, &state.read().value); } debug.finish() @@ -40,18 +40,18 @@ impl VirtualInput { pub fn get_state(&self, value_name: &str) -> f32 { self.states .get(value_name) - .map(|state| state.lock().value) + .map(|state| state.read().value) .unwrap_or(0.0) } pub fn add_bindings(&mut self, value_name: String, new_bindings: Vec) { - let state = - self.states - .entry(value_name) - .or_insert(Arc::new(Mutex::new(VirtualInputState { - value: 0.0, - bindings: Vec::new(), - }))); + let state = self + .states + .entry(value_name) + .or_insert(Arc::new(RwLock::new(VirtualInputState { + value: 0.0, + bindings: Vec::new(), + }))); for binding in &new_bindings { match binding { @@ -83,7 +83,7 @@ impl VirtualInput { } state - .lock() + .write() .bindings .extend(new_bindings.iter().map(|b| VirtualBindingState { value: 0.0, @@ -96,7 +96,7 @@ impl VirtualInput { if let Some(states) = states { for state in states { - let mut state = state.lock(); + let mut state = state.write(); state.update_from_key(key, key_state); } } @@ -104,14 +104,14 @@ impl VirtualInput { pub(super) fn update_mouse_move_binding(&mut self, delta: &glam::Vec2) { for state in &mut self.mouse_move_states { - let mut state = state.lock(); + let mut state = state.write(); state.update_from_mouse(delta); } } pub(super) fn update_mouse_wheel_binding(&mut self, delta: &glam::Vec2) { for state in &mut self.mouse_wheel_states { - let mut state = state.lock(); + let mut state = state.write(); state.update_from_mouse_wheel(delta); } } @@ -125,7 +125,7 @@ impl VirtualInput { if let Some(states) = states { for state in states { - let mut state = state.lock(); + let mut state = state.write(); state.update_from_mouse_button(button, button_state); } } @@ -136,7 +136,7 @@ impl VirtualInput { if let Some(states) = states { for state in states { - let mut state = state.lock(); + let mut state = state.write(); state.update_from_axis(axis, axis_state); } } From 9d2a4410f0593e84a72c4b7a6d693741b25c21e4 Mon Sep 17 00:00:00 2001 From: Florian RICHER Date: Sat, 31 May 2025 13:41:53 +0200 Subject: [PATCH 073/105] input: Move from egui to std RwLock (mistake during use choice) --- src/core/input/virtual_input.rs | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/src/core/input/virtual_input.rs b/src/core/input/virtual_input.rs index 50dcb11..999baed 100644 --- a/src/core/input/virtual_input.rs +++ b/src/core/input/virtual_input.rs @@ -1,6 +1,8 @@ -use std::{collections::HashMap, sync::Arc}; +use std::{ + collections::HashMap, + sync::{Arc, RwLock}, +}; -use egui_winit_vulkano::egui::mutex::RwLock; use winit::{ event::{AxisId, ElementState, MouseButton}, keyboard::PhysicalKey, @@ -29,7 +31,8 @@ impl std::fmt::Debug for VirtualInput { let mut debug = f.debug_struct("VirtualInput"); for (name, state) in &self.states { - debug.field(name, &state.read().value); + let value = state.read().expect("Poisoned lock for debug").value; + debug.field(name, &value); } debug.finish() @@ -40,7 +43,7 @@ impl VirtualInput { pub fn get_state(&self, value_name: &str) -> f32 { self.states .get(value_name) - .map(|state| state.read().value) + .map(|state| state.read().expect("Poisoned lock for get state").value) .unwrap_or(0.0) } @@ -84,6 +87,7 @@ impl VirtualInput { state .write() + .expect("Poisoned lock for add bindings") .bindings .extend(new_bindings.iter().map(|b| VirtualBindingState { value: 0.0, @@ -96,7 +100,7 @@ impl VirtualInput { if let Some(states) = states { for state in states { - let mut state = state.write(); + let mut state = state.write().expect("Poisoned lock for key update"); state.update_from_key(key, key_state); } } @@ -104,14 +108,14 @@ impl VirtualInput { pub(super) fn update_mouse_move_binding(&mut self, delta: &glam::Vec2) { for state in &mut self.mouse_move_states { - let mut state = state.write(); + let mut state = state.write().expect("Poisoned lock for mouse move update"); state.update_from_mouse(delta); } } pub(super) fn update_mouse_wheel_binding(&mut self, delta: &glam::Vec2) { for state in &mut self.mouse_wheel_states { - let mut state = state.write(); + let mut state = state.write().expect("Poisoned lock for mouse wheel update"); state.update_from_mouse_wheel(delta); } } @@ -125,7 +129,9 @@ impl VirtualInput { if let Some(states) = states { for state in states { - let mut state = state.write(); + let mut state = state + .write() + .expect("Poisoned lock for mouse button update"); state.update_from_mouse_button(button, button_state); } } @@ -136,7 +142,7 @@ impl VirtualInput { if let Some(states) = states { for state in states { - let mut state = state.write(); + let mut state = state.write().expect("Poisoned lock for axis update"); state.update_from_axis(axis, axis_state); } } From 5971c8cd5f7c5b6e32efbacf45bfe8594de23386 Mon Sep 17 00:00:00 2001 From: Florian RICHER Date: Sat, 31 May 2025 21:53:20 +0200 Subject: [PATCH 074/105] MaterialManager: first iteration --- src/core/render/material_manager.rs | 218 ++++++++++++++++++++++++++++ src/core/render/mod.rs | 1 + 2 files changed, 219 insertions(+) create mode 100644 src/core/render/material_manager.rs diff --git a/src/core/render/material_manager.rs b/src/core/render/material_manager.rs new file mode 100644 index 0000000..dd8f657 --- /dev/null +++ b/src/core/render/material_manager.rs @@ -0,0 +1,218 @@ +use std::{ + any::TypeId, + collections::HashMap, + error::Error, + sync::{Arc, RwLock}, +}; + +use vulkano::{ + device::Device, format::Format, memory::allocator::StandardMemoryAllocator, + pipeline::Pipeline as VulkanoPipeline, +}; + +pub trait Pipeline { + fn load( + &mut self, + device: &Device, + memory_allocator: &StandardMemoryAllocator, + swapchain_format: Format, + depth_format: Format, + ) -> Result<(), Box>; +} + +pub trait Material { + fn pipeline_type_id() -> TypeId + where + Self: Sized; + + fn load( + &mut self, + device: &Device, + memory_allocator: &StandardMemoryAllocator, + ) -> Result<(), Box>; +} + +pub struct MaterialManager { + device: Arc, + memory_allocator: Arc, + swapchain_format: Format, + depth_format: Format, + + // cached pipelines by Pipeline ID + loading_pipelines: Arc>>>>, + loaded_pipelines: Arc>>>>, + + // cached materials by Material ID + loading_materials: Arc>>>>, + loaded_materials: Arc>>>>, + material_pipeline_map: Arc>>, + + // Store all materials marked as renderable when pipeline and material are loaded + renderable_materials: Arc>>>>, +} + +impl MaterialManager { + pub fn new( + device: Arc, + memory_allocator: Arc, + swapchain_format: Format, + depth_format: Format, + ) -> Self { + Self { + device, + memory_allocator, + swapchain_format, + depth_format, + loading_pipelines: Arc::new(RwLock::new(HashMap::new())), + loaded_pipelines: Arc::new(RwLock::new(HashMap::new())), + loading_materials: Arc::new(RwLock::new(HashMap::new())), + loaded_materials: Arc::new(RwLock::new(HashMap::new())), + material_pipeline_map: Arc::new(RwLock::new(HashMap::new())), + renderable_materials: Arc::new(RwLock::new(HashMap::new())), + } + } + + pub fn add_pipeline(&self) { + let type_id = TypeId::of::

    (); + self.loading_pipelines + .write() + .expect("Failed to get write lock to loading_pipelines") + .insert(type_id, Arc::new(RwLock::new(P::default()))); + } + + pub fn add_material(&self) { + let type_id = TypeId::of::(); + let pipeline_id = M::pipeline_type_id(); + let material = Arc::new(RwLock::new(M::default())); + + self.loading_materials + .write() + .expect("Failed to get write lock to loading_materials") + .insert(type_id, material.clone()); + + self.material_pipeline_map + .write() + .expect("Failed to get write lock to material_pipeline_map") + .insert(type_id, pipeline_id); + } + + pub fn update_swapchain_format(&mut self, swapchain_format: Format) { + if self.swapchain_format == swapchain_format { + return; + } + + self.swapchain_format = swapchain_format; + self.mark_all_pipelines_as_loading(); + } + + fn load_pipelines(&self) { + let mut loaded_pipelines = HashMap::>>::new(); + + { + let loading_pipelines = self.loading_pipelines.read().unwrap(); + + for (type_id, pipeline) in loading_pipelines.iter() { + let result = { + let mut pipeline = pipeline.write().unwrap(); + pipeline.load( + &self.device, + &self.memory_allocator, + self.swapchain_format, + self.depth_format, + ) + }; + match result { + Ok(_) => { + loaded_pipelines.insert(type_id.clone(), pipeline.clone()); + } + Err(e) => { + tracing::error!("Failed to load pipeline: {e}"); + } + } + } + } + + let loaded_pipeline_keys = loaded_pipelines.keys().collect::>(); + + { + let mut loading_pipelines = self.loading_pipelines.write().unwrap(); + let loaded_materials = self.loaded_materials.read().unwrap(); + let mut renderable_materials = self.renderable_materials.write().unwrap(); + + for type_id in loaded_pipeline_keys { + loading_pipelines.remove(type_id); + + if loaded_materials.contains_key(type_id) { + renderable_materials.insert( + type_id.clone(), + loaded_materials.get(type_id).unwrap().clone(), + ); + } + } + } + + self.loaded_pipelines + .write() + .expect("Failed to get write lock to loaded_pipelines") + .extend(loaded_pipelines); + } + + fn load_materials(&self) { + let mut loaded_materials = HashMap::>>::new(); + + { + let loading_materials = self.loading_materials.read().unwrap(); + + for (type_id, material) in loading_materials.iter() { + let result = { + let mut material = material.write().unwrap(); + material.load(&self.device, &self.memory_allocator) + }; + + match result { + Ok(_) => { + loaded_materials.insert(type_id.clone(), material.clone()); + } + Err(e) => { + tracing::error!("Failed to load material: {e}"); + } + } + } + } + + { + let mut loading_materials = self.loading_materials.write().unwrap(); + let loaded_pipelines = self.loaded_pipelines.read().unwrap(); + let material_pipeline_map = self.material_pipeline_map.read().unwrap(); + let mut renderable_materials = self.renderable_materials.write().unwrap(); + + for (type_id, material) in loaded_materials.iter() { + loading_materials.remove(type_id); + + let pipeline_id = material_pipeline_map.get(type_id).unwrap().clone(); + + if loaded_pipelines.contains_key(&pipeline_id) { + renderable_materials.insert(type_id.clone(), material.clone()); + } + } + } + + self.loaded_materials + .write() + .expect("Failed to get write lock to loaded_materials") + .extend(loaded_materials); + } + + fn mark_all_pipelines_as_loading(&self) { + let mut loading_pipelines = self.loading_pipelines.write().unwrap(); + let mut loaded_pipelines = self.loaded_pipelines.write().unwrap(); + let mut renderable_materials = self.renderable_materials.write().unwrap(); + + for (type_id, pipeline) in loaded_pipelines.iter() { + loading_pipelines.insert(type_id.clone(), pipeline.clone()); + } + + loaded_pipelines.clear(); + renderable_materials.clear(); // Mark all materials as loading + } +} diff --git a/src/core/render/mod.rs b/src/core/render/mod.rs index f56fdf4..ec7ac54 100644 --- a/src/core/render/mod.rs +++ b/src/core/render/mod.rs @@ -1,3 +1,4 @@ +pub mod material_manager; pub mod primitives; pub mod render_pass_manager; pub mod texture; From 1a071e44a995a7c4ae5ba91b62cf9a3f247e2105 Mon Sep 17 00:00:00 2001 From: Florian RICHER Date: Sat, 31 May 2025 22:38:38 +0200 Subject: [PATCH 075/105] MaterialManager: with O(n) complexity --- src/core/render/material_manager.rs | 281 +++++++++++++++------------- 1 file changed, 155 insertions(+), 126 deletions(-) diff --git a/src/core/render/material_manager.rs b/src/core/render/material_manager.rs index dd8f657..e522ebf 100644 --- a/src/core/render/material_manager.rs +++ b/src/core/render/material_manager.rs @@ -1,14 +1,10 @@ use std::{ any::TypeId, - collections::HashMap, error::Error, - sync::{Arc, RwLock}, + sync::{Arc, RwLock, RwLockReadGuard}, }; -use vulkano::{ - device::Device, format::Format, memory::allocator::StandardMemoryAllocator, - pipeline::Pipeline as VulkanoPipeline, -}; +use vulkano::{device::Device, format::Format, memory::allocator::StandardMemoryAllocator}; pub trait Pipeline { fn load( @@ -32,23 +28,31 @@ pub trait Material { ) -> Result<(), Box>; } +pub enum MaterialState { + Loading, + Loaded, +} + +pub enum MaterialError { + PipelineNotFound, +} + pub struct MaterialManager { device: Arc, memory_allocator: Arc, swapchain_format: Format, depth_format: Format, - // cached pipelines by Pipeline ID - loading_pipelines: Arc>>>>, - loaded_pipelines: Arc>>>>, + pipelines_id: Arc>>, + pipelines_state: Arc>>>>, + pipelines: Arc>>>>, - // cached materials by Material ID - loading_materials: Arc>>>>, - loaded_materials: Arc>>>>, - material_pipeline_map: Arc>>, - - // Store all materials marked as renderable when pipeline and material are loaded - renderable_materials: Arc>>>>, + materials_id: Arc>>, + materials_pipeline_id: Arc>>, + materials_pipeline: Arc>>>>, + materials_pipeline_state: Arc>>>>, + materials_state: Arc>>>>, + materials: Arc>>>>, } impl MaterialManager { @@ -63,37 +67,75 @@ impl MaterialManager { memory_allocator, swapchain_format, depth_format, - loading_pipelines: Arc::new(RwLock::new(HashMap::new())), - loaded_pipelines: Arc::new(RwLock::new(HashMap::new())), - loading_materials: Arc::new(RwLock::new(HashMap::new())), - loaded_materials: Arc::new(RwLock::new(HashMap::new())), - material_pipeline_map: Arc::new(RwLock::new(HashMap::new())), - renderable_materials: Arc::new(RwLock::new(HashMap::new())), + pipelines_id: Arc::new(RwLock::new(Vec::new())), + pipelines_state: Arc::new(RwLock::new(Vec::new())), + pipelines: Arc::new(RwLock::new(Vec::new())), + materials_id: Arc::new(RwLock::new(Vec::new())), + materials_pipeline_id: Arc::new(RwLock::new(Vec::new())), + materials_pipeline: Arc::new(RwLock::new(Vec::new())), + materials_pipeline_state: Arc::new(RwLock::new(Vec::new())), + materials_state: Arc::new(RwLock::new(Vec::new())), + materials: Arc::new(RwLock::new(Vec::new())), } } pub fn add_pipeline(&self) { let type_id = TypeId::of::

    (); - self.loading_pipelines - .write() - .expect("Failed to get write lock to loading_pipelines") - .insert(type_id, Arc::new(RwLock::new(P::default()))); + let pipeline = Arc::new(RwLock::new(P::default())); + + let mut pipelines_id = self.pipelines_id.write().unwrap(); + let mut pipelines_state = self.pipelines_state.write().unwrap(); + let mut pipelines = self.pipelines.write().unwrap(); + + pipelines_id.push(type_id); + pipelines_state.push(Arc::new(RwLock::new(MaterialState::Loading))); + pipelines.push(pipeline.clone()); } - pub fn add_material(&self) { - let type_id = TypeId::of::(); + pub fn add_material(&self) -> Result<(), MaterialError> { let pipeline_id = M::pipeline_type_id(); + + let pipeline_result = { + let pipelines_id = self.pipelines_id.read().unwrap(); + let pipelines_state = self.pipelines_state.read().unwrap(); + let pipelines = self.pipelines.read().unwrap(); + + pipelines_id + .iter() + .zip(pipelines.iter()) + .zip(pipelines_state.iter()) + .find(|((id, _), _)| *id == &pipeline_id) + .map(|((_, pipeline), state)| (pipeline.clone(), state.clone())) + }; + + let (pipeline, pipeline_state) = match pipeline_result { + Some(pipeline) => pipeline, + None => { + tracing::error!( + "Pipeline with id {pipeline_id:?} not found, please add it before adding a material" + ); + return Err(MaterialError::PipelineNotFound); + } + }; + + let type_id = TypeId::of::(); let material = Arc::new(RwLock::new(M::default())); - self.loading_materials - .write() - .expect("Failed to get write lock to loading_materials") - .insert(type_id, material.clone()); + let mut materials_id = self.materials_id.write().unwrap(); + let mut materials_pipeline_id = self.materials_pipeline_id.write().unwrap(); + let mut materials_pipeline = self.materials_pipeline.write().unwrap(); + let mut materials_pipeline_state = self.materials_pipeline_state.write().unwrap(); + let mut materials_state = self.materials_state.write().unwrap(); + let mut materials = self.materials.write().unwrap(); - self.material_pipeline_map - .write() - .expect("Failed to get write lock to material_pipeline_map") - .insert(type_id, pipeline_id); + materials_id.push(type_id); + materials_pipeline_id.push(pipeline_id); + materials_pipeline.push(pipeline.clone()); + materials_pipeline_state.push(pipeline_state.clone()); + materials_state.push(Arc::new(RwLock::new(MaterialState::Loading))); + materials.push(material.clone()); + + Ok(()) } pub fn update_swapchain_format(&mut self, swapchain_format: Format) { @@ -105,114 +147,101 @@ impl MaterialManager { self.mark_all_pipelines_as_loading(); } + fn mark_all_pipelines_as_loading(&self) { + let pipelines_state = self.pipelines_state.write().unwrap(); + + for state in pipelines_state.iter() { + let mut state = state.write().unwrap(); + *state = MaterialState::Loading; + } + } + fn load_pipelines(&self) { - let mut loaded_pipelines = HashMap::>>::new(); + let pipelines_state = self.pipelines_state.read().unwrap(); + let pipelines = self.pipelines.read().unwrap(); - { - let loading_pipelines = self.loading_pipelines.read().unwrap(); + let iter = pipelines_state + .iter() + .zip(pipelines.iter()) + .filter(|(state, _)| { + let state = state.read().unwrap(); + matches!(*state, MaterialState::Loading) + }); - for (type_id, pipeline) in loading_pipelines.iter() { - let result = { - let mut pipeline = pipeline.write().unwrap(); - pipeline.load( - &self.device, - &self.memory_allocator, - self.swapchain_format, - self.depth_format, - ) - }; - match result { - Ok(_) => { - loaded_pipelines.insert(type_id.clone(), pipeline.clone()); - } - Err(e) => { - tracing::error!("Failed to load pipeline: {e}"); - } + for (state, pipeline) in iter { + let mut pipeline = pipeline.write().unwrap(); + let result = pipeline.load( + &self.device, + &self.memory_allocator, + self.swapchain_format, + self.depth_format, + ); + + match result { + Ok(_) => { + let mut state = state.write().unwrap(); + *state = MaterialState::Loaded; + } + Err(e) => { + tracing::error!("Failed to load pipeline: {e}"); } } } - - let loaded_pipeline_keys = loaded_pipelines.keys().collect::>(); - - { - let mut loading_pipelines = self.loading_pipelines.write().unwrap(); - let loaded_materials = self.loaded_materials.read().unwrap(); - let mut renderable_materials = self.renderable_materials.write().unwrap(); - - for type_id in loaded_pipeline_keys { - loading_pipelines.remove(type_id); - - if loaded_materials.contains_key(type_id) { - renderable_materials.insert( - type_id.clone(), - loaded_materials.get(type_id).unwrap().clone(), - ); - } - } - } - - self.loaded_pipelines - .write() - .expect("Failed to get write lock to loaded_pipelines") - .extend(loaded_pipelines); } fn load_materials(&self) { - let mut loaded_materials = HashMap::>>::new(); + let materials_state = self.materials_state.read().unwrap(); + let materials = self.materials.read().unwrap(); - { - let loading_materials = self.loading_materials.read().unwrap(); + let iter = materials_state + .iter() + .zip(materials.iter()) + .filter(|(state, _)| { + let state = state.read().unwrap(); + matches!(*state, MaterialState::Loading) + }); - for (type_id, material) in loading_materials.iter() { - let result = { - let mut material = material.write().unwrap(); - material.load(&self.device, &self.memory_allocator) - }; + for (state, material) in iter { + let mut material = material.write().unwrap(); + let result = material.load(&self.device, &self.memory_allocator); - match result { - Ok(_) => { - loaded_materials.insert(type_id.clone(), material.clone()); - } - Err(e) => { - tracing::error!("Failed to load material: {e}"); - } + match result { + Ok(_) => { + let mut state = state.write().unwrap(); + *state = MaterialState::Loaded; + } + Err(e) => { + tracing::error!("Failed to load material: {e}"); } } } - - { - let mut loading_materials = self.loading_materials.write().unwrap(); - let loaded_pipelines = self.loaded_pipelines.read().unwrap(); - let material_pipeline_map = self.material_pipeline_map.read().unwrap(); - let mut renderable_materials = self.renderable_materials.write().unwrap(); - - for (type_id, material) in loaded_materials.iter() { - loading_materials.remove(type_id); - - let pipeline_id = material_pipeline_map.get(type_id).unwrap().clone(); - - if loaded_pipelines.contains_key(&pipeline_id) { - renderable_materials.insert(type_id.clone(), material.clone()); - } - } - } - - self.loaded_materials - .write() - .expect("Failed to get write lock to loaded_materials") - .extend(loaded_materials); } - fn mark_all_pipelines_as_loading(&self) { - let mut loading_pipelines = self.loading_pipelines.write().unwrap(); - let mut loaded_pipelines = self.loaded_pipelines.write().unwrap(); - let mut renderable_materials = self.renderable_materials.write().unwrap(); + fn render_materials(&self, f: F) + where + F: Fn(RwLockReadGuard<'_, dyn Material>, RwLockReadGuard<'_, dyn Pipeline>), + { + let materials = self.materials.read().unwrap(); + let materials_state = self.materials_state.read().unwrap(); + let materials_pipeline = self.materials_pipeline.read().unwrap(); + let materials_pipeline_state = self.materials_pipeline_state.read().unwrap(); - for (type_id, pipeline) in loaded_pipelines.iter() { - loading_pipelines.insert(type_id.clone(), pipeline.clone()); - } + materials + .iter() + .zip(materials_state.iter()) + .zip(materials_pipeline.iter()) + .zip(materials_pipeline_state.iter()) + .filter(|(((_, material_state), _), pipeline_state)| { + let material_state = material_state.read().unwrap(); + let pipeline_state = pipeline_state.read().unwrap(); + matches!(*material_state, MaterialState::Loaded) + && matches!(*pipeline_state, MaterialState::Loaded) + }) + .for_each(|(((material, _), pipeline), _)| { + let material = material.read().unwrap(); + let pipeline = pipeline.read().unwrap(); - loaded_pipelines.clear(); - renderable_materials.clear(); // Mark all materials as loading + f(material, pipeline); + }); } } From b7bc6478e24ae00b398d2fc1f9bfcdac4158f383 Mon Sep 17 00:00:00 2001 From: Florian RICHER Date: Sat, 31 May 2025 22:40:56 +0200 Subject: [PATCH 076/105] MaterialManager: Avoid some clone --- src/core/render/material_manager.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/core/render/material_manager.rs b/src/core/render/material_manager.rs index e522ebf..008ad07 100644 --- a/src/core/render/material_manager.rs +++ b/src/core/render/material_manager.rs @@ -119,7 +119,6 @@ impl MaterialManager { }; let type_id = TypeId::of::(); - let material = Arc::new(RwLock::new(M::default())); let mut materials_id = self.materials_id.write().unwrap(); let mut materials_pipeline_id = self.materials_pipeline_id.write().unwrap(); @@ -130,10 +129,10 @@ impl MaterialManager { materials_id.push(type_id); materials_pipeline_id.push(pipeline_id); - materials_pipeline.push(pipeline.clone()); - materials_pipeline_state.push(pipeline_state.clone()); + materials_pipeline.push(pipeline); + materials_pipeline_state.push(pipeline_state); materials_state.push(Arc::new(RwLock::new(MaterialState::Loading))); - materials.push(material.clone()); + materials.push(Arc::new(RwLock::new(M::default()))); Ok(()) } From 5539381f4688b5608c5f6da0c2efdb44add7d401 Mon Sep 17 00:00:00 2001 From: Florian RICHER Date: Thu, 5 Jun 2025 13:30:55 +0200 Subject: [PATCH 077/105] render: Add AsBindableDescriptorSet --- src/core/render/primitives/mod.rs | 21 ++++++++++++ src/core/render/primitives/mvp.rs | 36 ++++++++++++++++++- src/core/render/texture.rs | 57 +++++++++++++++++++++++++++---- src/game/assets/square.rs | 48 +++++--------------------- 4 files changed, 115 insertions(+), 47 deletions(-) diff --git a/src/core/render/primitives/mod.rs b/src/core/render/primitives/mod.rs index 606c12d..5ab8266 100644 --- a/src/core/render/primitives/mod.rs +++ b/src/core/render/primitives/mod.rs @@ -1,4 +1,25 @@ +use std::{collections::BTreeMap, sync::Arc}; + +use vulkano::{ + Validated, VulkanError, + descriptor_set::{ + DescriptorSet, + allocator::StandardDescriptorSetAllocator, + layout::{DescriptorSetLayout, DescriptorSetLayoutBinding}, + }, +}; + pub mod camera; pub mod mvp; pub mod transform; pub mod vertex; + +pub trait AsBindableDescriptorSet { + fn as_descriptor_set_layout_bindings() -> BTreeMap; + + fn as_descriptor_set( + descriptor_set_allocator: &Arc, + layout: &Arc, + data: &T, + ) -> Result, Validated>; +} diff --git a/src/core/render/primitives/mvp.rs b/src/core/render/primitives/mvp.rs index 192c67b..d05307e 100644 --- a/src/core/render/primitives/mvp.rs +++ b/src/core/render/primitives/mvp.rs @@ -1,10 +1,19 @@ +use std::collections::BTreeMap; use std::sync::Arc; -use vulkano::Validated; use vulkano::buffer::{ AllocateBufferError, Buffer, BufferContents, BufferCreateInfo, BufferUsage, Subbuffer, }; +use vulkano::descriptor_set::allocator::StandardDescriptorSetAllocator; +use vulkano::descriptor_set::layout::{ + DescriptorSetLayout, DescriptorSetLayoutBinding, DescriptorType, +}; +use vulkano::descriptor_set::{DescriptorSet, WriteDescriptorSet}; use vulkano::memory::allocator::{AllocationCreateInfo, MemoryTypeFilter, StandardMemoryAllocator}; +use vulkano::shader::ShaderStages; +use vulkano::{Validated, VulkanError}; + +use crate::core::render::primitives::AsBindableDescriptorSet; #[derive(BufferContents, Clone, Copy)] #[repr(C)] @@ -34,3 +43,28 @@ impl Mvp { ) } } + +impl AsBindableDescriptorSet> for Mvp { + fn as_descriptor_set_layout_bindings() -> BTreeMap { + BTreeMap::::from_iter([( + 0, + DescriptorSetLayoutBinding { + stages: ShaderStages::VERTEX, + ..DescriptorSetLayoutBinding::descriptor_type(DescriptorType::UniformBuffer) + }, + )]) + } + + fn as_descriptor_set( + descriptor_set_allocator: &Arc, + layout: &Arc, + data: &Subbuffer<[Mvp]>, + ) -> Result, Validated> { + DescriptorSet::new( + descriptor_set_allocator.clone(), + layout.clone(), + [WriteDescriptorSet::buffer(0, data.clone())], + [], + ) + } +} diff --git a/src/core/render/texture.rs b/src/core/render/texture.rs index 8b7f63c..db0bcc5 100644 --- a/src/core/render/texture.rs +++ b/src/core/render/texture.rs @@ -1,10 +1,15 @@ -use std::{path::Path, sync::Arc}; +use std::{collections::BTreeMap, error::Error, sync::Arc}; -use anyhow::Error; -use image::{DynamicImage, EncodableLayout}; +use image::DynamicImage; use vulkano::{ + Validated, VulkanError, buffer::{Buffer, BufferCreateInfo, BufferUsage}, command_buffer::{AutoCommandBufferBuilder, CopyBufferToImageInfo, PrimaryAutoCommandBuffer}, + descriptor_set::{ + DescriptorSet, WriteDescriptorSet, + allocator::StandardDescriptorSetAllocator, + layout::{DescriptorSetLayout, DescriptorSetLayoutBinding, DescriptorType}, + }, device::Device, format::Format, image::{ @@ -13,8 +18,11 @@ use vulkano::{ view::ImageView, }, memory::allocator::{AllocationCreateInfo, MemoryTypeFilter, StandardMemoryAllocator}, + shader::ShaderStages, }; +use crate::core::render::primitives::AsBindableDescriptorSet; + pub struct Texture { texture: Arc, sampler: Arc, @@ -30,7 +38,7 @@ impl Texture { memory_allocator: &Arc, builder: &mut AutoCommandBufferBuilder, path: &str, - ) -> Result { + ) -> Result> { let _span = tracing::info_span!("texture_load_from_file", path = path); let bytes = std::fs::read(path)?; @@ -42,7 +50,7 @@ impl Texture { memory_allocator: &Arc, builder: &mut AutoCommandBufferBuilder, bytes: &[u8], - ) -> Result { + ) -> Result> { let image = image::load_from_memory(bytes)?; Self::from_dynamic_image(device, memory_allocator, builder, image) } @@ -52,7 +60,7 @@ impl Texture { memory_allocator: &Arc, builder: &mut AutoCommandBufferBuilder, image: DynamicImage, - ) -> Result { + ) -> Result> { let _span = tracing::info_span!("texture_from_dynamic_image"); let image_data = image.to_rgba8(); @@ -121,3 +129,40 @@ impl Texture { &self.sampler } } + +impl AsBindableDescriptorSet for Texture { + fn as_descriptor_set_layout_bindings() -> BTreeMap { + BTreeMap::::from_iter([ + ( + 0, + DescriptorSetLayoutBinding { + stages: ShaderStages::FRAGMENT, + ..DescriptorSetLayoutBinding::descriptor_type(DescriptorType::Sampler) + }, + ), + ( + 1, + DescriptorSetLayoutBinding { + stages: ShaderStages::FRAGMENT, + ..DescriptorSetLayoutBinding::descriptor_type(DescriptorType::SampledImage) + }, + ), + ]) + } + + fn as_descriptor_set( + descriptor_set_allocator: &Arc, + layout: &Arc, + data: &Texture, + ) -> Result, Validated> { + DescriptorSet::new( + descriptor_set_allocator.clone(), + layout.clone(), + [ + WriteDescriptorSet::sampler(0, data.sampler.clone()), + WriteDescriptorSet::image_view(1, data.texture.clone()), + ], + [], + ) + } +} diff --git a/src/game/assets/square.rs b/src/game/assets/square.rs index 7219157..c4a33ad 100644 --- a/src/game/assets/square.rs +++ b/src/game/assets/square.rs @@ -31,7 +31,7 @@ use vulkano::{ }; use crate::core::render::{ - primitives::{mvp::Mvp, transform::TransformRaw, vertex::Vertex3D}, + primitives::{AsBindableDescriptorSet, mvp::Mvp, transform::TransformRaw, vertex::Vertex3D}, texture::Texture, }; @@ -130,29 +130,8 @@ impl Square { PipelineShaderStageCreateInfo::new(fs), ]; - let vertex_bindings = BTreeMap::::from_iter([( - 0, - DescriptorSetLayoutBinding { - stages: ShaderStages::VERTEX, - ..DescriptorSetLayoutBinding::descriptor_type(DescriptorType::UniformBuffer) - }, - )]); - let fragment_bindings = BTreeMap::::from_iter([ - ( - 0, - DescriptorSetLayoutBinding { - stages: ShaderStages::FRAGMENT, - ..DescriptorSetLayoutBinding::descriptor_type(DescriptorType::Sampler) - }, - ), - ( - 1, - DescriptorSetLayoutBinding { - stages: ShaderStages::FRAGMENT, - ..DescriptorSetLayoutBinding::descriptor_type(DescriptorType::SampledImage) - }, - ), - ]); + let vertex_bindings = Mvp::as_descriptor_set_layout_bindings(); + let texture_bindings = Texture::as_descriptor_set_layout_bindings(); let vertex_descriptor_set_layout = DescriptorSetLayoutCreateInfo { bindings: vertex_bindings, @@ -160,7 +139,7 @@ impl Square { }; let fragment_descriptor_set_layout = DescriptorSetLayoutCreateInfo { - bindings: fragment_bindings, + bindings: texture_bindings, ..Default::default() }; @@ -220,22 +199,11 @@ impl Square { ) -> Result<(), Box> { let layouts = self.pipeline.layout().set_layouts(); - let uniform_descriptor_set = DescriptorSet::new( - descriptor_set_allocator.clone(), - layouts[0].clone(), - [WriteDescriptorSet::buffer(0, mvp_uniform.clone())], - [], - )?; + let uniform_descriptor_set = + Mvp::as_descriptor_set(descriptor_set_allocator, &layouts[0], mvp_uniform)?; - let texture_descriptor_set = DescriptorSet::new( - descriptor_set_allocator.clone(), - layouts[1].clone(), - [ - WriteDescriptorSet::sampler(0, texture.get_sampler().clone()), - WriteDescriptorSet::image_view(1, texture.get_texture().clone()), - ], - [], - )?; + let texture_descriptor_set = + Texture::as_descriptor_set(descriptor_set_allocator, &layouts[1], texture)?; command_buffer.bind_pipeline_graphics(self.pipeline.clone())?; command_buffer.bind_descriptor_sets( From 1a61aab218ac956b5c578e418cae2eaf58ac526d Mon Sep 17 00:00:00 2001 From: Florian RICHER Date: Sat, 7 Jun 2025 20:25:45 +0200 Subject: [PATCH 078/105] Add traits --- src/core/render/primitives/buffer.rs | 83 ++++++++++ src/core/render/primitives/camera.rs | 9 +- src/core/render/primitives/command.rs | 110 +++++++++++++ src/core/render/primitives/mod.rs | 7 + src/core/render/primitives/mvp.rs | 43 +++-- src/core/render/primitives/resource.rs | 208 ++++++++++++++++++++++++ src/core/render/primitives/transform.rs | 58 ++++--- src/core/render/primitives/vertex.rs | 83 +++++++++- src/game/assets/square.rs | 156 ++++++++++++------ 9 files changed, 663 insertions(+), 94 deletions(-) create mode 100644 src/core/render/primitives/buffer.rs create mode 100644 src/core/render/primitives/command.rs create mode 100644 src/core/render/primitives/resource.rs diff --git a/src/core/render/primitives/buffer.rs b/src/core/render/primitives/buffer.rs new file mode 100644 index 0000000..e01226f --- /dev/null +++ b/src/core/render/primitives/buffer.rs @@ -0,0 +1,83 @@ +use std::{error::Error, sync::Arc}; + +use vulkano::{ + Validated, + buffer::{AllocateBufferError, Buffer, BufferContents, BufferCreateInfo, Subbuffer}, + memory::allocator::{AllocationCreateInfo, StandardMemoryAllocator}, +}; + +pub trait AsBindableBuffer { + type BufferData: BufferContents + Clone; + + fn buffer_create_info() -> BufferCreateInfo; + + fn allocation_create_info() -> AllocationCreateInfo; + + fn to_buffer_data(data: &T) -> Self::BufferData; + + fn create_buffer( + memory_allocator: &Arc, + data: &T, + ) -> Result, Validated> { + let buffer_data = Self::to_buffer_data(data); + Buffer::from_iter( + memory_allocator.clone(), + Self::buffer_create_info(), + Self::allocation_create_info(), + [buffer_data], + ) + } + + fn update_buffer( + buffer: &Subbuffer<[Self::BufferData]>, + data: &T, + ) -> Result<(), Box> { + let buffer_data = Self::to_buffer_data(data); + let mut write_guard = buffer.write()?; + write_guard[0] = buffer_data; + Ok(()) + } +} + +pub trait AsUniformBuffer: AsBindableBuffer { + fn create_uniform_buffer( + memory_allocator: &Arc, + data: &T, + ) -> Result, Validated> { + Self::create_buffer(memory_allocator, data) + } +} + +pub trait AsVertexBuffer: AsBindableBuffer { + fn create_vertex_buffer( + memory_allocator: &Arc, + vertices: &[T], + ) -> Result, Validated> { + let buffer_data: Vec = + vertices.iter().map(|v| Self::to_buffer_data(v)).collect(); + + Buffer::from_iter( + memory_allocator.clone(), + Self::buffer_create_info(), + Self::allocation_create_info(), + buffer_data, + ) + } +} + +pub trait AsIndexBuffer: AsBindableBuffer { + fn create_index_buffer( + memory_allocator: &Arc, + indices: &[T], + ) -> Result, Validated> { + let buffer_data: Vec = + indices.iter().map(|i| Self::to_buffer_data(i)).collect(); + + Buffer::from_iter( + memory_allocator.clone(), + Self::buffer_create_info(), + Self::allocation_create_info(), + buffer_data, + ) + } +} diff --git a/src/core/render/primitives/camera.rs b/src/core/render/primitives/camera.rs index f27622a..4fdf1ae 100644 --- a/src/core/render/primitives/camera.rs +++ b/src/core/render/primitives/camera.rs @@ -9,7 +9,7 @@ use vulkano::{ use crate::core::{input::InputManager, timer::Timer}; -use super::mvp::Mvp; +use super::{AsUniformBuffer, mvp::Mvp}; // See docs/OPENGL_VULKAN_DIFF.md const OPENGL_TO_VULKAN_Y_AXIS_FLIP: Mat4 = Mat4 { @@ -112,11 +112,12 @@ impl Camera3D { Vec3::Y, ); - Mvp { + let mvp = Mvp { model: OPENGL_TO_VULKAN_Y_AXIS_FLIP.to_cols_array_2d(), view: view_matrix.to_cols_array_2d(), projection: self.projection.to_cols_array_2d(), - } - .into_buffer(memory_allocator) + }; + + Mvp::create_uniform_buffer(memory_allocator, &mvp) } } diff --git a/src/core/render/primitives/command.rs b/src/core/render/primitives/command.rs new file mode 100644 index 0000000..37970a1 --- /dev/null +++ b/src/core/render/primitives/command.rs @@ -0,0 +1,110 @@ +use std::{error::Error, sync::Arc}; + +use vulkano::{ + command_buffer::{ + AutoCommandBufferBuilder, PrimaryAutoCommandBuffer, RenderPassBeginInfo, SubpassBeginInfo, + SubpassEndInfo, + }, + descriptor_set::DescriptorSet, + pipeline::{GraphicsPipeline, Pipeline}, + render_pass::{Framebuffer, RenderPass}, +}; + +pub trait AsRecordable { + fn record_render_commands( + builder: &mut AutoCommandBufferBuilder, + data: &T, + pipeline: &Arc, + descriptor_sets: &[Arc], + ) -> Result<(), Box>; +} + +pub trait AsDrawable { + type VertexBuffer; + type IndexBuffer; + + fn vertex_buffer(data: &T) -> &Self::VertexBuffer; + + fn index_buffer(_data: &T) -> Option<&Self::IndexBuffer> { + None + } + + fn vertex_count(data: &T) -> u32; + + fn index_count(_data: &T) -> u32 { + 0 + } + + fn instance_count(_data: &T) -> u32 { + 1 + } + + fn first_vertex(_data: &T) -> u32 { + 0 + } + + fn first_index(_data: &T) -> u32 { + 0 + } + + fn first_instance(_data: &T) -> u32 { + 0 + } +} + +pub trait AsRenderPassRecordable { + fn begin_render_pass( + builder: &mut AutoCommandBufferBuilder, + _render_pass: &Arc, + framebuffer: &Arc, + clear_values: Vec>, + ) -> Result<(), Box> { + builder.begin_render_pass( + RenderPassBeginInfo { + clear_values, + ..RenderPassBeginInfo::framebuffer(framebuffer.clone()) + }, + SubpassBeginInfo { + contents: vulkano::command_buffer::SubpassContents::Inline, + ..Default::default() + }, + )?; + Ok(()) + } + + fn end_render_pass( + builder: &mut AutoCommandBufferBuilder, + ) -> Result<(), Box> { + builder.end_render_pass(SubpassEndInfo::default())?; + Ok(()) + } + + fn record_render_pass_commands( + builder: &mut AutoCommandBufferBuilder, + data: &T, + ) -> Result<(), Box>; +} + +pub trait AsRenderableObject: AsRecordable + AsDrawable { + fn record_complete_render( + builder: &mut AutoCommandBufferBuilder, + data: &T, + pipeline: &Arc, + descriptor_sets: &[Arc], + ) -> Result<(), Box> { + builder.bind_pipeline_graphics(pipeline.clone())?; + + if !descriptor_sets.is_empty() { + builder.bind_descriptor_sets( + vulkano::pipeline::PipelineBindPoint::Graphics, + pipeline.layout().clone(), + 0, + descriptor_sets.iter().cloned().collect::>(), + )?; + } + + Self::record_render_commands(builder, data, pipeline, descriptor_sets)?; + + Ok(()) + } +} diff --git a/src/core/render/primitives/mod.rs b/src/core/render/primitives/mod.rs index 5ab8266..8ef9dd7 100644 --- a/src/core/render/primitives/mod.rs +++ b/src/core/render/primitives/mod.rs @@ -14,6 +14,13 @@ pub mod mvp; pub mod transform; pub mod vertex; +pub mod buffer; +pub mod command; +pub mod resource; + +pub use buffer::{AsBindableBuffer, AsIndexBuffer, AsUniformBuffer, AsVertexBuffer}; +pub use command::{AsDrawable, AsRecordable}; + pub trait AsBindableDescriptorSet { fn as_descriptor_set_layout_bindings() -> BTreeMap; diff --git a/src/core/render/primitives/mvp.rs b/src/core/render/primitives/mvp.rs index d05307e..0f47ce3 100644 --- a/src/core/render/primitives/mvp.rs +++ b/src/core/render/primitives/mvp.rs @@ -2,7 +2,7 @@ use std::collections::BTreeMap; use std::sync::Arc; use vulkano::buffer::{ - AllocateBufferError, Buffer, BufferContents, BufferCreateInfo, BufferUsage, Subbuffer, + AllocateBufferError, BufferContents, BufferCreateInfo, BufferUsage, Subbuffer, }; use vulkano::descriptor_set::allocator::StandardDescriptorSetAllocator; use vulkano::descriptor_set::layout::{ @@ -13,7 +13,7 @@ use vulkano::memory::allocator::{AllocationCreateInfo, MemoryTypeFilter, Standar use vulkano::shader::ShaderStages; use vulkano::{Validated, VulkanError}; -use crate::core::render::primitives::AsBindableDescriptorSet; +use crate::core::render::primitives::{AsBindableBuffer, AsBindableDescriptorSet, AsUniformBuffer}; #[derive(BufferContents, Clone, Copy)] #[repr(C)] @@ -28,22 +28,35 @@ impl Mvp { self, memory_allocator: &Arc, ) -> Result, Validated> { - Buffer::from_iter( - memory_allocator.clone(), - BufferCreateInfo { - usage: BufferUsage::UNIFORM_BUFFER, - ..Default::default() - }, - AllocationCreateInfo { - memory_type_filter: MemoryTypeFilter::PREFER_DEVICE - | MemoryTypeFilter::HOST_SEQUENTIAL_WRITE, - ..Default::default() - }, - [self], - ) + Self::create_uniform_buffer(memory_allocator, &self) } } +impl AsBindableBuffer for Mvp { + type BufferData = Mvp; + + fn buffer_create_info() -> BufferCreateInfo { + BufferCreateInfo { + usage: BufferUsage::UNIFORM_BUFFER, + ..Default::default() + } + } + + fn allocation_create_info() -> AllocationCreateInfo { + AllocationCreateInfo { + memory_type_filter: MemoryTypeFilter::PREFER_DEVICE + | MemoryTypeFilter::HOST_SEQUENTIAL_WRITE, + ..Default::default() + } + } + + fn to_buffer_data(data: &Mvp) -> Self::BufferData { + *data + } +} + +impl AsUniformBuffer for Mvp {} + impl AsBindableDescriptorSet> for Mvp { fn as_descriptor_set_layout_bindings() -> BTreeMap { BTreeMap::::from_iter([( diff --git a/src/core/render/primitives/resource.rs b/src/core/render/primitives/resource.rs new file mode 100644 index 0000000..7ea75e7 --- /dev/null +++ b/src/core/render/primitives/resource.rs @@ -0,0 +1,208 @@ +use std::{ + collections::HashMap, + error::Error, + fmt::{self, Debug, Display}, + hash::Hash, + sync::Arc, +}; + +#[derive(Debug, Clone)] +pub struct ResourceLoadError { + pub message: String, +} + +impl Display for ResourceLoadError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Resource load error: {}", self.message) + } +} + +impl Error for ResourceLoadError {} + +impl ResourceLoadError { + pub fn new(message: impl Into) -> Self { + Self { + message: message.into(), + } + } +} + +pub trait AsResourceManager +where + THandle: Clone + Eq + Hash + Debug, +{ + /// Type d'erreur lors du chargement + type LoadError: Error; + + /// Charge une ressource avec l'handle donné + fn load(&mut self, handle: THandle, resource: TResource) -> Result<(), Self::LoadError>; + + /// Récupère une ressource par son handle + fn get(&self, handle: &THandle) -> Option<&TResource>; + + /// Récupère une ressource mutable par son handle + fn get_mut(&mut self, handle: &THandle) -> Option<&mut TResource>; + + /// Supprime une ressource + fn unload(&mut self, handle: &THandle) -> Option; + + /// Vérifie si une ressource est chargée + fn is_loaded(&self, handle: &THandle) -> bool; + + /// Retourne tous les handles chargés + fn loaded_handles(&self) -> Vec; + + /// Nettoie toutes les ressources + fn clear(&mut self); +} + +/// Implémentation basique d'un gestionnaire de ressources +pub struct BasicResourceManager +where + THandle: Clone + Eq + Hash + Debug, +{ + resources: HashMap, +} + +impl BasicResourceManager +where + THandle: Clone + Eq + Hash + Debug, +{ + pub fn new() -> Self { + Self { + resources: HashMap::new(), + } + } + + pub fn with_capacity(capacity: usize) -> Self { + Self { + resources: HashMap::with_capacity(capacity), + } + } +} + +impl Default for BasicResourceManager +where + THandle: Clone + Eq + Hash + Debug, +{ + fn default() -> Self { + Self::new() + } +} + +impl AsResourceManager + for BasicResourceManager +where + THandle: Clone + Eq + Hash + Debug, +{ + type LoadError = ResourceLoadError; + + fn load(&mut self, handle: THandle, resource: TResource) -> Result<(), Self::LoadError> { + self.resources.insert(handle, resource); + Ok(()) + } + + fn get(&self, handle: &THandle) -> Option<&TResource> { + self.resources.get(handle) + } + + fn get_mut(&mut self, handle: &THandle) -> Option<&mut TResource> { + self.resources.get_mut(handle) + } + + fn unload(&mut self, handle: &THandle) -> Option { + self.resources.remove(handle) + } + + fn is_loaded(&self, handle: &THandle) -> bool { + self.resources.contains_key(handle) + } + + fn loaded_handles(&self) -> Vec { + self.resources.keys().cloned().collect() + } + + fn clear(&mut self) { + self.resources.clear(); + } +} + +pub struct ThreadSafeResourceManager +where + THandle: Clone + Eq + Hash + Debug, +{ + resources: Arc>>>, +} + +impl ThreadSafeResourceManager +where + THandle: Clone + Eq + Hash + Debug, +{ + pub fn new() -> Self { + Self { + resources: Arc::new(std::sync::RwLock::new(HashMap::new())), + } + } + + pub fn load(&self, handle: THandle, resource: TResource) -> Result<(), ResourceLoadError> { + let mut resources = self + .resources + .write() + .map_err(|_| ResourceLoadError::new("Failed to acquire write lock"))?; + resources.insert(handle, Arc::new(resource)); + Ok(()) + } + + pub fn get(&self, handle: &THandle) -> Option> { + let resources = self.resources.read().ok()?; + resources.get(handle).cloned() + } + + pub fn unload(&self, handle: &THandle) -> Option> { + let mut resources = self.resources.write().ok()?; + resources.remove(handle) + } + + pub fn is_loaded(&self, handle: &THandle) -> bool { + if let Ok(resources) = self.resources.read() { + resources.contains_key(handle) + } else { + false + } + } + + pub fn loaded_handles(&self) -> Vec { + if let Ok(resources) = self.resources.read() { + resources.keys().cloned().collect() + } else { + Vec::new() + } + } + + pub fn clear(&self) { + if let Ok(mut resources) = self.resources.write() { + resources.clear(); + } + } +} + +impl Clone for ThreadSafeResourceManager +where + THandle: Clone + Eq + Hash + Debug, +{ + fn clone(&self) -> Self { + Self { + resources: Arc::clone(&self.resources), + } + } +} + +pub trait AsAsyncLoadable +where + THandle: Clone + Eq + Hash + Debug + Send + Sync, +{ + type Resource: Send + Sync; + type LoadError: Error + Send + Sync; + + async fn load_async(handle: THandle) -> Result; +} diff --git a/src/core/render/primitives/transform.rs b/src/core/render/primitives/transform.rs index 4d870b0..a108d14 100644 --- a/src/core/render/primitives/transform.rs +++ b/src/core/render/primitives/transform.rs @@ -3,20 +3,20 @@ use std::sync::Arc; use glam::{Mat4, Quat, Vec3}; use vulkano::{ Validated, - buffer::{ - AllocateBufferError, Buffer, BufferContents, BufferCreateInfo, BufferUsage, Subbuffer, - }, + buffer::{AllocateBufferError, BufferContents, BufferCreateInfo, BufferUsage, Subbuffer}, memory::allocator::{AllocationCreateInfo, MemoryTypeFilter, StandardMemoryAllocator}, pipeline::graphics::vertex_input::Vertex, }; +use crate::core::render::primitives::{AsBindableBuffer, AsVertexBuffer}; + pub struct Transform { pub position: Vec3, pub rotation: Quat, pub scale: Vec3, } -#[derive(BufferContents, Vertex)] +#[derive(BufferContents, Vertex, Clone, Copy)] #[repr(C)] pub struct TransformRaw { #[format(R32G32B32A32_SFLOAT)] @@ -59,23 +59,37 @@ impl Transform { memory_allocator: &Arc, transforms: &[Transform], ) -> Result, Validated> { - let transform_raws: Vec = - transforms.iter().map(|t| t.to_raw_tranform()).collect(); - - let buffer = Buffer::from_iter( - memory_allocator.clone(), - BufferCreateInfo { - usage: BufferUsage::VERTEX_BUFFER, - ..Default::default() - }, - AllocationCreateInfo { - memory_type_filter: MemoryTypeFilter::PREFER_DEVICE - | MemoryTypeFilter::HOST_SEQUENTIAL_WRITE, - ..Default::default() - }, - transform_raws, - )?; - - Ok(buffer) + TransformRaw::create_vertex_buffer( + memory_allocator, + &transforms + .iter() + .map(|t| t.to_raw_tranform()) + .collect::>(), + ) } } + +impl AsBindableBuffer for TransformRaw { + type BufferData = TransformRaw; + + fn buffer_create_info() -> BufferCreateInfo { + BufferCreateInfo { + usage: BufferUsage::VERTEX_BUFFER, + ..Default::default() + } + } + + fn allocation_create_info() -> AllocationCreateInfo { + AllocationCreateInfo { + memory_type_filter: MemoryTypeFilter::PREFER_DEVICE + | MemoryTypeFilter::HOST_SEQUENTIAL_WRITE, + ..Default::default() + } + } + + fn to_buffer_data(data: &TransformRaw) -> Self::BufferData { + *data + } +} + +impl AsVertexBuffer for TransformRaw {} diff --git a/src/core/render/primitives/vertex.rs b/src/core/render/primitives/vertex.rs index 166ac44..2a27b36 100644 --- a/src/core/render/primitives/vertex.rs +++ b/src/core/render/primitives/vertex.rs @@ -1,7 +1,11 @@ use vulkano::buffer::BufferContents; +use vulkano::buffer::{BufferCreateInfo, BufferUsage}; +use vulkano::memory::allocator::{AllocationCreateInfo, MemoryTypeFilter}; use vulkano::pipeline::graphics::vertex_input::Vertex; -#[derive(BufferContents, Vertex)] +use crate::core::render::primitives::{AsBindableBuffer, AsIndexBuffer, AsVertexBuffer}; + +#[derive(BufferContents, Vertex, Clone, Copy)] #[repr(C)] pub struct Vertex2D { #[format(R32G32_SFLOAT)] @@ -11,7 +15,7 @@ pub struct Vertex2D { pub uv: [f32; 2], } -#[derive(BufferContents, Vertex)] +#[derive(BufferContents, Vertex, Clone, Copy)] #[repr(C)] pub struct Vertex3D { #[format(R32G32B32_SFLOAT)] @@ -20,3 +24,78 @@ pub struct Vertex3D { #[format(R32G32_SFLOAT)] pub uv: [f32; 2], } + +impl AsBindableBuffer for Vertex2D { + type BufferData = Vertex2D; + + fn buffer_create_info() -> BufferCreateInfo { + BufferCreateInfo { + usage: BufferUsage::VERTEX_BUFFER, + ..Default::default() + } + } + + fn allocation_create_info() -> AllocationCreateInfo { + AllocationCreateInfo { + memory_type_filter: MemoryTypeFilter::PREFER_DEVICE + | MemoryTypeFilter::HOST_SEQUENTIAL_WRITE, + ..Default::default() + } + } + + fn to_buffer_data(data: &Vertex2D) -> Self::BufferData { + *data + } +} + +impl AsVertexBuffer for Vertex2D {} + +impl AsBindableBuffer for Vertex3D { + type BufferData = Vertex3D; + + fn buffer_create_info() -> BufferCreateInfo { + BufferCreateInfo { + usage: BufferUsage::VERTEX_BUFFER, + ..Default::default() + } + } + + fn allocation_create_info() -> AllocationCreateInfo { + AllocationCreateInfo { + memory_type_filter: MemoryTypeFilter::PREFER_DEVICE + | MemoryTypeFilter::HOST_SEQUENTIAL_WRITE, + ..Default::default() + } + } + + fn to_buffer_data(data: &Vertex3D) -> Self::BufferData { + *data + } +} + +impl AsVertexBuffer for Vertex3D {} + +impl AsBindableBuffer for u32 { + type BufferData = u32; + + fn buffer_create_info() -> BufferCreateInfo { + BufferCreateInfo { + usage: BufferUsage::INDEX_BUFFER, + ..Default::default() + } + } + + fn allocation_create_info() -> AllocationCreateInfo { + AllocationCreateInfo { + memory_type_filter: MemoryTypeFilter::PREFER_DEVICE + | MemoryTypeFilter::HOST_SEQUENTIAL_WRITE, + ..Default::default() + } + } + + fn to_buffer_data(data: &u32) -> Self::BufferData { + *data + } +} + +impl AsIndexBuffer for u32 {} diff --git a/src/game/assets/square.rs b/src/game/assets/square.rs index c4a33ad..57e4138 100644 --- a/src/game/assets/square.rs +++ b/src/game/assets/square.rs @@ -1,16 +1,15 @@ -use std::{collections::BTreeMap, error::Error, sync::Arc}; +use std::{error::Error, sync::Arc}; use vulkano::{ - buffer::{Buffer, BufferCreateInfo, BufferUsage, Subbuffer}, + buffer::Subbuffer, command_buffer::{AutoCommandBufferBuilder, PrimaryAutoCommandBuffer}, descriptor_set::{ - DescriptorSet, WriteDescriptorSet, - allocator::StandardDescriptorSetAllocator, - layout::{DescriptorSetLayoutBinding, DescriptorSetLayoutCreateInfo, DescriptorType}, + DescriptorSet, allocator::StandardDescriptorSetAllocator, + layout::DescriptorSetLayoutCreateInfo, }, device::Device, format::Format, - memory::allocator::{AllocationCreateInfo, MemoryTypeFilter, StandardMemoryAllocator}, + memory::allocator::StandardMemoryAllocator, pipeline::{ DynamicState, GraphicsPipeline, Pipeline, PipelineBindPoint, PipelineLayout, PipelineShaderStageCreateInfo, @@ -27,11 +26,13 @@ use vulkano::{ }, layout::{PipelineDescriptorSetLayoutCreateInfo, PipelineLayoutCreateFlags}, }, - shader::ShaderStages, }; use crate::core::render::{ - primitives::{AsBindableDescriptorSet, mvp::Mvp, transform::TransformRaw, vertex::Vertex3D}, + primitives::{ + AsBindableDescriptorSet, AsDrawable, AsIndexBuffer, AsRecordable, AsVertexBuffer, mvp::Mvp, + transform::TransformRaw, vertex::Vertex3D, + }, texture::Texture, }; @@ -80,6 +81,14 @@ pub struct Square { pipeline: Arc, } +/// Structure pour encapsuler les données de rendu du Square +pub struct SquareRenderData<'a> { + pub square: &'a Square, + pub mvp_uniform: &'a Subbuffer<[Mvp]>, + pub transform_uniform: &'a Subbuffer<[TransformRaw]>, + pub texture: &'a Texture, +} + impl Square { pub fn new( device: &Arc, @@ -87,32 +96,9 @@ impl Square { swapchain_format: Format, depth_format: Format, ) -> Result> { - let vertex_buffer = Buffer::from_iter( - memory_allocator.clone(), - BufferCreateInfo { - usage: BufferUsage::VERTEX_BUFFER, - ..Default::default() - }, - AllocationCreateInfo { - memory_type_filter: MemoryTypeFilter::PREFER_DEVICE - | MemoryTypeFilter::HOST_SEQUENTIAL_WRITE, - ..Default::default() - }, - Vec::from_iter(VERTICES), - )?; - let index_buffer = Buffer::from_iter( - memory_allocator.clone(), - BufferCreateInfo { - usage: BufferUsage::INDEX_BUFFER, - ..Default::default() - }, - AllocationCreateInfo { - memory_type_filter: MemoryTypeFilter::PREFER_DEVICE - | MemoryTypeFilter::HOST_SEQUENTIAL_WRITE, - ..Default::default() - }, - Vec::from_iter(INDICES), - )?; + let vertex_buffer = Vertex3D::create_vertex_buffer(memory_allocator, &VERTICES)?; + + let index_buffer = u32::create_index_buffer(memory_allocator, &INDICES)?; let vs = shaders::vs::load(device.clone())? .entry_point("main") @@ -197,6 +183,13 @@ impl Square { transform_uniform: &Subbuffer<[TransformRaw]>, texture: &Texture, ) -> Result<(), Box> { + let render_data = SquareRenderData { + square: self, + mvp_uniform, + transform_uniform, + texture, + }; + let layouts = self.pipeline.layout().set_layouts(); let uniform_descriptor_set = @@ -205,25 +198,86 @@ impl Square { let texture_descriptor_set = Texture::as_descriptor_set(descriptor_set_allocator, &layouts[1], texture)?; - command_buffer.bind_pipeline_graphics(self.pipeline.clone())?; - command_buffer.bind_descriptor_sets( - PipelineBindPoint::Graphics, - self.pipeline.layout().clone(), - 0, - vec![uniform_descriptor_set, texture_descriptor_set], + // Utiliser les nouveaux traits pour le rendu + Self::record_render_commands( + command_buffer, + &render_data, + &self.pipeline, + &[uniform_descriptor_set, texture_descriptor_set], )?; - command_buffer - .bind_vertex_buffers(0, (self.vertex_buffer.clone(), transform_uniform.clone()))?; - command_buffer.bind_index_buffer(self.index_buffer.clone())?; - unsafe { - command_buffer.draw_indexed( - INDICES.len() as u32, - transform_uniform.len() as u32, - 0, - 0, - 0, - )?; + Ok(()) + } +} + +// Implémentation des nouveaux traits pour Square +impl<'a> AsDrawable> for Square { + type VertexBuffer = Subbuffer<[Vertex3D]>; + type IndexBuffer = Subbuffer<[u32]>; + + fn vertex_buffer(data: &SquareRenderData<'a>) -> &'a Self::VertexBuffer { + &data.square.vertex_buffer + } + + fn index_buffer(data: &SquareRenderData<'a>) -> Option<&'a Self::IndexBuffer> { + Some(&data.square.index_buffer) + } + + fn vertex_count(_data: &SquareRenderData<'a>) -> u32 { + VERTICES.len() as u32 + } + + fn index_count(_data: &SquareRenderData<'a>) -> u32 { + INDICES.len() as u32 + } + + fn instance_count(data: &SquareRenderData<'a>) -> u32 { + data.transform_uniform.len() as u32 + } +} + +impl<'a> AsRecordable> for Square { + fn record_render_commands( + builder: &mut AutoCommandBufferBuilder, + data: &SquareRenderData<'a>, + pipeline: &Arc, + descriptor_sets: &[Arc], + ) -> Result<(), Box> { + // Bind pipeline + builder.bind_pipeline_graphics(pipeline.clone())?; + + // Bind descriptor sets + builder.bind_descriptor_sets( + PipelineBindPoint::Graphics, + pipeline.layout().clone(), + 0, + descriptor_sets.iter().cloned().collect::>(), + )?; + + // Bind buffers + builder.bind_vertex_buffers( + 0, + ( + Self::vertex_buffer(data).clone(), + data.transform_uniform.clone(), + ), + )?; + + if let Some(index_buffer) = Self::index_buffer(data) { + builder.bind_index_buffer(index_buffer.clone())?; + unsafe { + builder.draw_indexed( + Self::index_count(data), + Self::instance_count(data), + 0, + 0, + 0, + )?; + } + } else { + unsafe { + builder.draw(Self::vertex_count(data), Self::instance_count(data), 0, 0)?; + } } Ok(()) From 4f96a1e4b5c0e2db0e148554f8f197571be178f4 Mon Sep 17 00:00:00 2001 From: Florian RICHER Date: Sat, 7 Jun 2025 21:13:31 +0200 Subject: [PATCH 079/105] Update transform --- src/core/render/primitives/transform.rs | 92 +++++++++++++++++++++---- src/game/scenes/main_scene.rs | 33 +++++---- 2 files changed, 99 insertions(+), 26 deletions(-) diff --git a/src/core/render/primitives/transform.rs b/src/core/render/primitives/transform.rs index a108d14..d7c1249 100644 --- a/src/core/render/primitives/transform.rs +++ b/src/core/render/primitives/transform.rs @@ -10,13 +10,17 @@ use vulkano::{ use crate::core::render::primitives::{AsBindableBuffer, AsVertexBuffer}; +#[derive(Debug, Clone)] pub struct Transform { pub position: Vec3, pub rotation: Quat, pub scale: Vec3, + // Cache to avoid unnecessary recalculations + cached_matrix: Option, + dirty: bool, } -#[derive(BufferContents, Vertex, Clone, Copy)] +#[derive(BufferContents, Vertex, Clone, Copy, Debug)] #[repr(C)] pub struct TransformRaw { #[format(R32G32B32A32_SFLOAT)] @@ -26,49 +30,113 @@ pub struct TransformRaw { impl Default for Transform { fn default() -> Self { Self { - position: Vec3::default(), - rotation: Quat::default(), + position: Vec3::ZERO, + rotation: Quat::IDENTITY, scale: Vec3::ONE, + cached_matrix: None, + dirty: true, } } } impl Transform { + pub fn new(position: Vec3, rotation: Quat, scale: Vec3) -> Self { + Self { + position, + rotation, + scale, + cached_matrix: None, + dirty: true, + } + } + pub fn rotate(&mut self, rotation: Quat) { self.rotation *= rotation; + self.mark_dirty(); } pub fn translate(&mut self, translation: Vec3) { self.position += translation; + self.mark_dirty(); } pub fn scale(&mut self, scale: Vec3) { self.scale *= scale; + self.mark_dirty(); } - pub fn to_raw_tranform(&self) -> TransformRaw { + pub fn set_position(&mut self, position: Vec3) { + self.position = position; + self.mark_dirty(); + } + + pub fn set_rotation(&mut self, rotation: Quat) { + self.rotation = rotation; + self.mark_dirty(); + } + + pub fn set_scale(&mut self, scale: Vec3) { + self.scale = scale; + self.mark_dirty(); + } + + fn mark_dirty(&mut self) { + self.dirty = true; + self.cached_matrix = None; + } + + /// Get the transformation matrix (immutable - recalculates each time) + pub fn matrix(&self) -> Mat4 { + Mat4::from_translation(self.position) + * Mat4::from_quat(self.rotation) + * Mat4::from_scale(self.scale) + } + + /// Convert to GPU-ready format (immutable - recalculates each time) + pub fn to_raw(&self) -> TransformRaw { TransformRaw { - model: (Mat4::from_translation(self.position) - * Mat4::from_quat(self.rotation) - * Mat4::from_scale(self.scale)) - .to_cols_array_2d(), + model: self.matrix().to_cols_array_2d(), } } + /// Create a buffer from transforms (immutable - recalculates each time) pub fn create_buffer( memory_allocator: &Arc, transforms: &[Transform], ) -> Result, Validated> { TransformRaw::create_vertex_buffer( memory_allocator, - &transforms - .iter() - .map(|t| t.to_raw_tranform()) - .collect::>(), + &transforms.iter().map(|t| t.to_raw()).collect::>(), ) } } +impl From<&Transform> for TransformRaw { + fn from(transform: &Transform) -> Self { + transform.to_raw() + } +} + +impl From for TransformRaw { + fn from(matrix: Mat4) -> Self { + Self { + model: matrix.to_cols_array_2d(), + } + } +} + +impl TransformRaw { + pub fn from_matrix(matrix: Mat4) -> Self { + Self { + model: matrix.to_cols_array_2d(), + } + } + + pub fn to_matrix(&self) -> Mat4 { + Mat4::from_cols_array_2d(&self.model) + } +} + impl AsBindableBuffer for TransformRaw { type BufferData = TransformRaw; diff --git a/src/game/scenes/main_scene.rs b/src/game/scenes/main_scene.rs index 37fd4ed..933bccb 100644 --- a/src/game/scenes/main_scene.rs +++ b/src/game/scenes/main_scene.rs @@ -58,19 +58,16 @@ impl Scene for MainScene { let instance_spacing = 10.0; let num_instances_per_row = (num_instances as f32 / instance_spacing).ceil() as u32; let instances: Vec = (0..num_instances) - .map(|i| Transform { - position: Vec3::new( - (i % num_instances_per_row) as f32 * (instance_spacing + instance_size), - 0.0, - (i / num_instances_per_row) as f32 * (instance_spacing + instance_size), - ), - rotation: Quat::from_euler( - EulerRot::XYZ, - 0.0, - rand::random_range(0.0..=360.0), - 0.0, - ), - scale: Vec3::new(instance_size, instance_size, instance_size), + .map(|i| { + Transform::new( + Vec3::new( + (i % num_instances_per_row) as f32 * (instance_spacing + instance_size), + 0.0, + (i / num_instances_per_row) as f32 * (instance_spacing + instance_size), + ), + Quat::from_euler(EulerRot::XYZ, 0.0, rand::random_range(0.0..=360.0), 0.0), + Vec3::new(instance_size, instance_size, instance_size), + ) }) .collect(); @@ -129,6 +126,14 @@ impl Scene for MainScene { }); }); + let delta_time = app_context.get_delta_time(); + for (i, instance) in state.instances.iter_mut().enumerate() { + let rotation_speed = (i % 10) as f32; + let rotation_delta = Quat::from_rotation_y(rotation_speed * delta_time); + + instance.rotate(rotation_delta); + } + if app_context .with_input_manager(|input_manager| input_manager.get_virtual_input_state("mouse_left")) > 0.0 @@ -167,7 +172,7 @@ impl Scene for MainScene { before_future: Box, app_context: &mut WindowContext, ) -> Result, Box> { - let state = self.state.as_ref().ok_or("State not loaded")?; + let state = self.state.as_mut().ok_or("State not loaded")?; let mut builder = AutoCommandBufferBuilder::primary( app_context.command_buffer_allocator.clone(), From 078e9daba955310d5da6e1bc1f9a745180fe3ce8 Mon Sep 17 00:00:00 2001 From: Florian RICHER Date: Sun, 8 Jun 2025 15:41:22 +0200 Subject: [PATCH 080/105] Split Mesh data and Pipeline --- res/shaders/{vertex.frag => simple.frag} | 0 res/shaders/{vertex.vert => simple.vert} | 0 src/core/render/primitives/command.rs | 66 +++----- src/core/render/primitives/mod.rs | 8 +- src/core/render/primitives/mvp.rs | 2 +- src/core/render/texture.rs | 2 +- src/game/assets/meshs/mod.rs | 1 + src/game/assets/meshs/square.rs | 63 +++++++ src/game/assets/mod.rs | 3 +- src/game/assets/pipelines/mod.rs | 1 + src/game/assets/pipelines/simple.rs | 207 +++++++++++++++++++++++ src/game/assets/square.rs | 54 ++---- src/game/scenes/main_scene.rs | 33 ++-- 13 files changed, 331 insertions(+), 109 deletions(-) rename res/shaders/{vertex.frag => simple.frag} (100%) rename res/shaders/{vertex.vert => simple.vert} (100%) create mode 100644 src/game/assets/meshs/mod.rs create mode 100644 src/game/assets/meshs/square.rs create mode 100644 src/game/assets/pipelines/mod.rs create mode 100644 src/game/assets/pipelines/simple.rs diff --git a/res/shaders/vertex.frag b/res/shaders/simple.frag similarity index 100% rename from res/shaders/vertex.frag rename to res/shaders/simple.frag diff --git a/res/shaders/vertex.vert b/res/shaders/simple.vert similarity index 100% rename from res/shaders/vertex.vert rename to res/shaders/simple.vert diff --git a/src/core/render/primitives/command.rs b/src/core/render/primitives/command.rs index 37970a1..f7a20e4 100644 --- a/src/core/render/primitives/command.rs +++ b/src/core/render/primitives/command.rs @@ -1,53 +1,51 @@ use std::{error::Error, sync::Arc}; use vulkano::{ + buffer::Subbuffer, command_buffer::{ AutoCommandBufferBuilder, PrimaryAutoCommandBuffer, RenderPassBeginInfo, SubpassBeginInfo, SubpassEndInfo, }, - descriptor_set::DescriptorSet, - pipeline::{GraphicsPipeline, Pipeline}, + descriptor_set::allocator::StandardDescriptorSetAllocator, + pipeline::GraphicsPipeline, render_pass::{Framebuffer, RenderPass}, }; pub trait AsRecordable { fn record_render_commands( builder: &mut AutoCommandBufferBuilder, - data: &T, + descriptor_set_allocator: &Arc, pipeline: &Arc, - descriptor_sets: &[Arc], + data: &T, ) -> Result<(), Box>; } -pub trait AsDrawable { - type VertexBuffer; - type IndexBuffer; +pub trait AsRenderableMesh { + fn vertex_buffer(&self) -> &Subbuffer<[V]>; - fn vertex_buffer(data: &T) -> &Self::VertexBuffer; + fn vertex_count(&self) -> u32; - fn index_buffer(_data: &T) -> Option<&Self::IndexBuffer> { + fn first_vertex(&self) -> u32 { + 0 + } + + fn vertex_offset(&self) -> i32 { + 0 + } + + fn index_buffer(&self) -> Option<&Subbuffer<[I]>> { None } - fn vertex_count(data: &T) -> u32; - - fn index_count(_data: &T) -> u32 { + fn index_count(&self) -> u32 { 0 } - fn instance_count(_data: &T) -> u32 { - 1 - } - - fn first_vertex(_data: &T) -> u32 { + fn first_index(&self) -> u32 { 0 } - fn first_index(_data: &T) -> u32 { - 0 - } - - fn first_instance(_data: &T) -> u32 { + fn index_offset(&self) -> i32 { 0 } } @@ -84,27 +82,3 @@ pub trait AsRenderPassRecordable { data: &T, ) -> Result<(), Box>; } - -pub trait AsRenderableObject: AsRecordable + AsDrawable { - fn record_complete_render( - builder: &mut AutoCommandBufferBuilder, - data: &T, - pipeline: &Arc, - descriptor_sets: &[Arc], - ) -> Result<(), Box> { - builder.bind_pipeline_graphics(pipeline.clone())?; - - if !descriptor_sets.is_empty() { - builder.bind_descriptor_sets( - vulkano::pipeline::PipelineBindPoint::Graphics, - pipeline.layout().clone(), - 0, - descriptor_sets.iter().cloned().collect::>(), - )?; - } - - Self::record_render_commands(builder, data, pipeline, descriptor_sets)?; - - Ok(()) - } -} diff --git a/src/core/render/primitives/mod.rs b/src/core/render/primitives/mod.rs index 8ef9dd7..3303d41 100644 --- a/src/core/render/primitives/mod.rs +++ b/src/core/render/primitives/mod.rs @@ -9,17 +9,17 @@ use vulkano::{ }, }; +mod buffer; +mod command; + pub mod camera; pub mod mvp; pub mod transform; pub mod vertex; -pub mod buffer; -pub mod command; pub mod resource; - pub use buffer::{AsBindableBuffer, AsIndexBuffer, AsUniformBuffer, AsVertexBuffer}; -pub use command::{AsDrawable, AsRecordable}; +pub use command::{AsRecordable, AsRenderableMesh}; pub trait AsBindableDescriptorSet { fn as_descriptor_set_layout_bindings() -> BTreeMap; diff --git a/src/core/render/primitives/mvp.rs b/src/core/render/primitives/mvp.rs index 0f47ce3..af5db16 100644 --- a/src/core/render/primitives/mvp.rs +++ b/src/core/render/primitives/mvp.rs @@ -4,7 +4,7 @@ use std::sync::Arc; use vulkano::buffer::{ AllocateBufferError, BufferContents, BufferCreateInfo, BufferUsage, Subbuffer, }; -use vulkano::descriptor_set::allocator::StandardDescriptorSetAllocator; +use vulkano::descriptor_set::allocator::{DescriptorSetAllocator, StandardDescriptorSetAllocator}; use vulkano::descriptor_set::layout::{ DescriptorSetLayout, DescriptorSetLayoutBinding, DescriptorType, }; diff --git a/src/core/render/texture.rs b/src/core/render/texture.rs index db0bcc5..38d8147 100644 --- a/src/core/render/texture.rs +++ b/src/core/render/texture.rs @@ -7,7 +7,7 @@ use vulkano::{ command_buffer::{AutoCommandBufferBuilder, CopyBufferToImageInfo, PrimaryAutoCommandBuffer}, descriptor_set::{ DescriptorSet, WriteDescriptorSet, - allocator::StandardDescriptorSetAllocator, + allocator::{DescriptorSetAllocator, StandardDescriptorSetAllocator}, layout::{DescriptorSetLayout, DescriptorSetLayoutBinding, DescriptorType}, }, device::Device, diff --git a/src/game/assets/meshs/mod.rs b/src/game/assets/meshs/mod.rs new file mode 100644 index 0000000..d793a66 --- /dev/null +++ b/src/game/assets/meshs/mod.rs @@ -0,0 +1 @@ +pub mod square; diff --git a/src/game/assets/meshs/square.rs b/src/game/assets/meshs/square.rs new file mode 100644 index 0000000..d1dc524 --- /dev/null +++ b/src/game/assets/meshs/square.rs @@ -0,0 +1,63 @@ +use std::{error::Error, sync::Arc}; + +use vulkano::{buffer::Subbuffer, memory::allocator::StandardMemoryAllocator}; + +use crate::core::render::primitives::{ + AsIndexBuffer, AsRenderableMesh, AsVertexBuffer, vertex::Vertex3D, +}; + +const VERTICES: [Vertex3D; 4] = [ + Vertex3D { + position: [-0.5, -0.5, 0.0], + uv: [0.0, 0.0], + }, + Vertex3D { + position: [-0.5, 0.5, 0.0], + uv: [0.0, 1.0], + }, + Vertex3D { + position: [0.5, -0.5, 0.0], + uv: [1.0, 0.0], + }, + Vertex3D { + position: [0.5, 0.5, 0.0], + uv: [1.0, 1.0], + }, +]; + +const INDICES: [u32; 6] = [0, 2, 1, 2, 3, 1]; + +pub struct Square { + vertex_buffer: Subbuffer<[Vertex3D]>, + index_buffer: Subbuffer<[u32]>, +} + +impl Square { + pub fn new(memory_allocator: &Arc) -> Result> { + let vertex_buffer = Vertex3D::create_vertex_buffer(memory_allocator, &VERTICES)?; + let index_buffer = u32::create_index_buffer(memory_allocator, &INDICES)?; + + Ok(Self { + vertex_buffer, + index_buffer, + }) + } +} + +impl AsRenderableMesh for Square { + fn vertex_buffer(&self) -> &Subbuffer<[Vertex3D]> { + &self.vertex_buffer + } + + fn index_buffer(&self) -> Option<&Subbuffer<[u32]>> { + Some(&self.index_buffer) + } + + fn vertex_count(&self) -> u32 { + VERTICES.len() as u32 + } + + fn index_count(&self) -> u32 { + INDICES.len() as u32 + } +} diff --git a/src/game/assets/mod.rs b/src/game/assets/mod.rs index d793a66..a0cac96 100644 --- a/src/game/assets/mod.rs +++ b/src/game/assets/mod.rs @@ -1 +1,2 @@ -pub mod square; +pub mod meshs; +pub mod pipelines; diff --git a/src/game/assets/pipelines/mod.rs b/src/game/assets/pipelines/mod.rs new file mode 100644 index 0000000..b252f36 --- /dev/null +++ b/src/game/assets/pipelines/mod.rs @@ -0,0 +1 @@ +pub mod simple; diff --git a/src/game/assets/pipelines/simple.rs b/src/game/assets/pipelines/simple.rs new file mode 100644 index 0000000..b46c993 --- /dev/null +++ b/src/game/assets/pipelines/simple.rs @@ -0,0 +1,207 @@ +use std::{error::Error, sync::Arc}; + +use vulkano::{ + buffer::Subbuffer, + command_buffer::{AutoCommandBufferBuilder, PrimaryAutoCommandBuffer}, + descriptor_set::{ + allocator::StandardDescriptorSetAllocator, layout::DescriptorSetLayoutCreateInfo, + }, + device::Device, + format::Format, + pipeline::{ + DynamicState, GraphicsPipeline, Pipeline, PipelineBindPoint, PipelineLayout, + PipelineShaderStageCreateInfo, + graphics::{ + GraphicsPipelineCreateInfo, + color_blend::{ColorBlendAttachmentState, ColorBlendState}, + depth_stencil::{DepthState, DepthStencilState}, + input_assembly::InputAssemblyState, + multisample::MultisampleState, + rasterization::RasterizationState, + subpass::PipelineRenderingCreateInfo, + vertex_input::{Vertex, VertexDefinition}, + viewport::ViewportState, + }, + layout::{PipelineDescriptorSetLayoutCreateInfo, PipelineLayoutCreateFlags}, + }, +}; + +use crate::core::render::{ + primitives::{ + AsBindableDescriptorSet, AsRecordable, AsRenderableMesh, mvp::Mvp, transform::TransformRaw, + vertex::Vertex3D, + }, + texture::Texture, +}; + +pub struct SimplePipelineRenderData<'a, T> +where + T: AsRenderableMesh, +{ + pub mesh: &'a T, + pub mvp_uniform: &'a Subbuffer<[Mvp]>, + pub texture: &'a Texture, + pub instances: &'a Subbuffer<[TransformRaw]>, +} + +pub struct SimplePipeline { + pipeline: Arc, +} + +impl SimplePipeline { + pub fn new( + device: &Arc, + swapchain_format: Format, + depth_format: Format, + ) -> Result> { + let vs = shaders::vs::load(device.clone())? + .entry_point("main") + .ok_or("Failed find main entry point of vertex shader".to_string())?; + + let fs = shaders::fs::load(device.clone())? + .entry_point("main") + .ok_or("Failed find main entry point of fragment shader".to_string())?; + + let vertex_input_state = + [Vertex3D::per_vertex(), TransformRaw::per_instance()].definition(&vs)?; + + let stages = [ + PipelineShaderStageCreateInfo::new(vs), + PipelineShaderStageCreateInfo::new(fs), + ]; + + let mvp_bindings = Mvp::as_descriptor_set_layout_bindings(); + let texture_bindings = Texture::as_descriptor_set_layout_bindings(); + + let vertex_descriptor_set_layout = DescriptorSetLayoutCreateInfo { + bindings: mvp_bindings, + ..Default::default() + }; + + let fragment_descriptor_set_layout = DescriptorSetLayoutCreateInfo { + bindings: texture_bindings, + ..Default::default() + }; + + let create_info = PipelineDescriptorSetLayoutCreateInfo { + set_layouts: vec![vertex_descriptor_set_layout, fragment_descriptor_set_layout], + flags: PipelineLayoutCreateFlags::default(), + push_constant_ranges: vec![], + } + .into_pipeline_layout_create_info(device.clone())?; + + let layout = PipelineLayout::new(device.clone(), create_info)?; + + let subpass = PipelineRenderingCreateInfo { + color_attachment_formats: vec![Some(swapchain_format)], + depth_attachment_format: Some(depth_format), + ..Default::default() + }; + + let pipeline = GraphicsPipeline::new( + device.clone(), + None, + GraphicsPipelineCreateInfo { + stages: stages.into_iter().collect(), + vertex_input_state: Some(vertex_input_state), + input_assembly_state: Some(InputAssemblyState::default()), + viewport_state: Some(ViewportState::default()), + rasterization_state: Some(RasterizationState::default()), + multisample_state: Some(MultisampleState::default()), + color_blend_state: Some(ColorBlendState::with_attachment_states( + subpass.color_attachment_formats.len() as u32, + ColorBlendAttachmentState::default(), + )), + depth_stencil_state: Some(DepthStencilState { + depth: Some(DepthState::simple()), + ..Default::default() + }), + dynamic_state: [DynamicState::Viewport].into_iter().collect(), + subpass: Some(subpass.into()), + ..GraphicsPipelineCreateInfo::layout(layout) + }, + )?; + + Ok(Self { pipeline }) + } + + pub fn pipeline(&self) -> &Arc { + &self.pipeline + } +} + +impl<'a, T: AsRenderableMesh> AsRecordable> + for SimplePipeline +{ + fn record_render_commands( + builder: &mut AutoCommandBufferBuilder, + descriptor_set_allocator: &Arc, + pipeline: &Arc, + data: &SimplePipelineRenderData<'a, T>, + ) -> Result<(), Box> { + let layouts = pipeline.layout().set_layouts(); + + let uniform_descriptor_set = + Mvp::as_descriptor_set(descriptor_set_allocator, &layouts[0], data.mvp_uniform)?; + + let texture_descriptor_set = + Texture::as_descriptor_set(descriptor_set_allocator, &layouts[1], data.texture)?; + + builder.bind_pipeline_graphics(pipeline.clone())?; + + builder.bind_descriptor_sets( + PipelineBindPoint::Graphics, + pipeline.layout().clone(), + 0, + vec![uniform_descriptor_set, texture_descriptor_set], + )?; + + builder.bind_vertex_buffers( + 0, + (data.mesh.vertex_buffer().clone(), data.instances.clone()), + )?; + + match data.mesh.index_buffer() { + Some(index_buffer) => { + builder.bind_index_buffer(index_buffer.clone())?; + unsafe { + builder.draw_indexed( + data.mesh.index_count(), + data.instances.len() as u32, + data.mesh.first_index(), + data.mesh.vertex_offset(), + 0, + )?; + } + } + None => unsafe { + builder.draw( + data.mesh.vertex_count(), + data.instances.len() as u32, + data.mesh.first_vertex(), + 0, + )?; + }, + } + + Ok(()) + } +} + +pub mod shaders { + pub mod vs { + vulkano_shaders::shader! { + ty: "vertex", + path: r"res/shaders/simple.vert", + generate_structs: false, + } + } + + pub mod fs { + vulkano_shaders::shader! { + ty: "fragment", + path: r"res/shaders/simple.frag", + generate_structs: false, + } + } +} diff --git a/src/game/assets/square.rs b/src/game/assets/square.rs index 57e4138..cea1274 100644 --- a/src/game/assets/square.rs +++ b/src/game/assets/square.rs @@ -86,6 +86,7 @@ pub struct SquareRenderData<'a> { pub square: &'a Square, pub mvp_uniform: &'a Subbuffer<[Mvp]>, pub transform_uniform: &'a Subbuffer<[TransformRaw]>, + pub descriptor_sets: &'a [Arc], pub texture: &'a Texture, } @@ -183,13 +184,6 @@ impl Square { transform_uniform: &Subbuffer<[TransformRaw]>, texture: &Texture, ) -> Result<(), Box> { - let render_data = SquareRenderData { - square: self, - mvp_uniform, - transform_uniform, - texture, - }; - let layouts = self.pipeline.layout().set_layouts(); let uniform_descriptor_set = @@ -198,50 +192,26 @@ impl Square { let texture_descriptor_set = Texture::as_descriptor_set(descriptor_set_allocator, &layouts[1], texture)?; + let render_data = SquareRenderData { + square: self, + mvp_uniform, + transform_uniform, + texture, + descriptor_sets: &[uniform_descriptor_set, texture_descriptor_set], + }; + // Utiliser les nouveaux traits pour le rendu - Self::record_render_commands( - command_buffer, - &render_data, - &self.pipeline, - &[uniform_descriptor_set, texture_descriptor_set], - )?; + Self::record_render_commands(command_buffer, &self.pipeline, &render_data)?; Ok(()) } } -// Implémentation des nouveaux traits pour Square -impl<'a> AsDrawable> for Square { - type VertexBuffer = Subbuffer<[Vertex3D]>; - type IndexBuffer = Subbuffer<[u32]>; - - fn vertex_buffer(data: &SquareRenderData<'a>) -> &'a Self::VertexBuffer { - &data.square.vertex_buffer - } - - fn index_buffer(data: &SquareRenderData<'a>) -> Option<&'a Self::IndexBuffer> { - Some(&data.square.index_buffer) - } - - fn vertex_count(_data: &SquareRenderData<'a>) -> u32 { - VERTICES.len() as u32 - } - - fn index_count(_data: &SquareRenderData<'a>) -> u32 { - INDICES.len() as u32 - } - - fn instance_count(data: &SquareRenderData<'a>) -> u32 { - data.transform_uniform.len() as u32 - } -} - impl<'a> AsRecordable> for Square { fn record_render_commands( builder: &mut AutoCommandBufferBuilder, - data: &SquareRenderData<'a>, pipeline: &Arc, - descriptor_sets: &[Arc], + data: &SquareRenderData<'a>, ) -> Result<(), Box> { // Bind pipeline builder.bind_pipeline_graphics(pipeline.clone())?; @@ -251,7 +221,7 @@ impl<'a> AsRecordable> for Square { PipelineBindPoint::Graphics, pipeline.layout().clone(), 0, - descriptor_sets.iter().cloned().collect::>(), + data.descriptor_sets.iter().cloned().collect::>(), )?; // Bind buffers diff --git a/src/game/scenes/main_scene.rs b/src/game/scenes/main_scene.rs index 933bccb..eb3b6bf 100644 --- a/src/game/scenes/main_scene.rs +++ b/src/game/scenes/main_scene.rs @@ -5,11 +5,14 @@ use crate::core::app::DEPTH_IMAGE_ID; use crate::core::app::context::WindowContext; use crate::core::app::user_event::UserEvent; use crate::core::render::primitives::camera::Camera3D; +use crate::core::render::primitives::mvp::Mvp; use crate::core::render::primitives::transform::Transform; +use crate::core::render::primitives::{AsBindableDescriptorSet, AsRecordable}; use crate::core::render::render_pass_manager::{RenderPassConfig, RenderPassManager}; use crate::core::render::texture::Texture; use crate::core::scene::Scene; -use crate::game::assets::square::Square; +use crate::game::assets::meshs::square::Square; +use crate::game::assets::pipelines::simple::{SimplePipeline, SimplePipelineRenderData}; use egui_winit_vulkano::egui; use glam::EulerRot; use glam::Quat; @@ -22,6 +25,7 @@ use winit::window::CursorGrabMode; pub struct MainSceneState { square: Square, + simple_pipeline: SimplePipeline, instances: Vec, camera: Camera3D, texture: Texture, @@ -46,9 +50,9 @@ impl Scene for MainScene { let swapchain_image_view = app_context.with_renderer(|renderer| renderer.swapchain_image_view().clone()); - let square = Square::new( + let square = Square::new(&app_context.memory_allocator)?; + let simple_pipeline = SimplePipeline::new( &app_context.device, - &app_context.memory_allocator, swapchain_image_view.format(), depth_image_view.format(), )?; @@ -103,6 +107,7 @@ impl Scene for MainScene { self.state = Some(MainSceneState { square, + simple_pipeline, instances, camera, texture, @@ -198,20 +203,20 @@ impl Scene for MainScene { // Create camera uniform using the actual camera let camera_uniform = state.camera.create_buffer(&app_context.memory_allocator)?; - let transform_uniform = Transform::create_buffer(&app_context.memory_allocator, &state.instances)?; - state - .square - .render( - &mut builder, - &app_context.descriptor_set_allocator, - &camera_uniform, - &transform_uniform, - &state.texture, - ) - .unwrap(); + SimplePipeline::record_render_commands( + &mut builder, + &app_context.descriptor_set_allocator, + state.simple_pipeline.pipeline(), + &SimplePipelineRenderData { + mesh: &state.square, + mvp_uniform: &camera_uniform, + texture: &state.texture, + instances: &transform_uniform, + }, + )?; RenderPassManager::end_rendering(&mut builder)?; From f91c0792b2e0c0db4c52fe08bfe5dad90b026752 Mon Sep 17 00:00:00 2001 From: Florian RICHER Date: Sun, 8 Jun 2025 16:42:22 +0200 Subject: [PATCH 081/105] Split record_render_commands and bind_commands --- src/core/render/primitives/command.rs | 98 +++++++++++++------------ src/core/render/primitives/mod.rs | 2 +- src/core/render/primitives/transform.rs | 12 +++ src/game/assets/meshs/square.rs | 2 +- src/game/assets/pipelines/simple.rs | 47 +++--------- src/game/scenes/main_scene.rs | 7 +- 6 files changed, 83 insertions(+), 85 deletions(-) diff --git a/src/core/render/primitives/command.rs b/src/core/render/primitives/command.rs index f7a20e4..a71b9d2 100644 --- a/src/core/render/primitives/command.rs +++ b/src/core/render/primitives/command.rs @@ -1,26 +1,61 @@ use std::{error::Error, sync::Arc}; use vulkano::{ - buffer::Subbuffer, - command_buffer::{ - AutoCommandBufferBuilder, PrimaryAutoCommandBuffer, RenderPassBeginInfo, SubpassBeginInfo, - SubpassEndInfo, - }, + buffer::{IndexBuffer, Subbuffer}, + command_buffer::{AutoCommandBufferBuilder, PrimaryAutoCommandBuffer}, descriptor_set::allocator::StandardDescriptorSetAllocator, pipeline::GraphicsPipeline, - render_pass::{Framebuffer, RenderPass}, }; -pub trait AsRecordable { - fn record_render_commands( +pub trait AsRecordable +where + MI: Into + Clone, +{ + fn record_bind_commands( builder: &mut AutoCommandBufferBuilder, descriptor_set_allocator: &Arc, pipeline: &Arc, - data: &T, + mesh: &impl AsRenderableMesh, + instances: &impl AsRenderableMeshInstance, + extra_data: &T, ) -> Result<(), Box>; + + fn record_draw_commands( + builder: &mut AutoCommandBufferBuilder, + mesh: &impl AsRenderableMesh, + instances: &impl AsRenderableMeshInstance, + ) -> Result<(), Box> { + match mesh.index_buffer() { + Some(index_buffer) => { + builder.bind_index_buffer(index_buffer.clone())?; + unsafe { + builder.draw_indexed( + mesh.index_count(), + instances.instance_count(), + mesh.first_index(), + mesh.vertex_offset(), + instances.first_instance(), + )?; + } + } + None => unsafe { + builder.draw( + mesh.vertex_count(), + instances.instance_count(), + mesh.first_vertex(), + instances.first_instance(), + )?; + }, + } + + Ok(()) + } } -pub trait AsRenderableMesh { +pub trait AsRenderableMesh +where + I: Into + Clone, +{ fn vertex_buffer(&self) -> &Subbuffer<[V]>; fn vertex_count(&self) -> u32; @@ -33,7 +68,7 @@ pub trait AsRenderableMesh { 0 } - fn index_buffer(&self) -> Option<&Subbuffer<[I]>> { + fn index_buffer(&self) -> Option<&I> { None } @@ -44,41 +79,14 @@ pub trait AsRenderableMesh { fn first_index(&self) -> u32 { 0 } +} - fn index_offset(&self) -> i32 { +pub trait AsRenderableMeshInstance { + fn instance_buffer(&self) -> &Subbuffer<[T]>; + + fn instance_count(&self) -> u32; + + fn first_instance(&self) -> u32 { 0 } } - -pub trait AsRenderPassRecordable { - fn begin_render_pass( - builder: &mut AutoCommandBufferBuilder, - _render_pass: &Arc, - framebuffer: &Arc, - clear_values: Vec>, - ) -> Result<(), Box> { - builder.begin_render_pass( - RenderPassBeginInfo { - clear_values, - ..RenderPassBeginInfo::framebuffer(framebuffer.clone()) - }, - SubpassBeginInfo { - contents: vulkano::command_buffer::SubpassContents::Inline, - ..Default::default() - }, - )?; - Ok(()) - } - - fn end_render_pass( - builder: &mut AutoCommandBufferBuilder, - ) -> Result<(), Box> { - builder.end_render_pass(SubpassEndInfo::default())?; - Ok(()) - } - - fn record_render_pass_commands( - builder: &mut AutoCommandBufferBuilder, - data: &T, - ) -> Result<(), Box>; -} diff --git a/src/core/render/primitives/mod.rs b/src/core/render/primitives/mod.rs index 3303d41..427b633 100644 --- a/src/core/render/primitives/mod.rs +++ b/src/core/render/primitives/mod.rs @@ -19,7 +19,7 @@ pub mod vertex; pub mod resource; pub use buffer::{AsBindableBuffer, AsIndexBuffer, AsUniformBuffer, AsVertexBuffer}; -pub use command::{AsRecordable, AsRenderableMesh}; +pub use command::{AsRecordable, AsRenderableMesh, AsRenderableMeshInstance}; pub trait AsBindableDescriptorSet { fn as_descriptor_set_layout_bindings() -> BTreeMap; diff --git a/src/core/render/primitives/transform.rs b/src/core/render/primitives/transform.rs index d7c1249..27f24f4 100644 --- a/src/core/render/primitives/transform.rs +++ b/src/core/render/primitives/transform.rs @@ -10,6 +10,8 @@ use vulkano::{ use crate::core::render::primitives::{AsBindableBuffer, AsVertexBuffer}; +use super::command::AsRenderableMeshInstance; + #[derive(Debug, Clone)] pub struct Transform { pub position: Vec3, @@ -161,3 +163,13 @@ impl AsBindableBuffer for TransformRaw { } impl AsVertexBuffer for TransformRaw {} + +impl AsRenderableMeshInstance for Subbuffer<[TransformRaw]> { + fn instance_buffer(&self) -> &Subbuffer<[TransformRaw]> { + self + } + + fn instance_count(&self) -> u32 { + self.len() as u32 + } +} diff --git a/src/game/assets/meshs/square.rs b/src/game/assets/meshs/square.rs index d1dc524..1a67013 100644 --- a/src/game/assets/meshs/square.rs +++ b/src/game/assets/meshs/square.rs @@ -44,7 +44,7 @@ impl Square { } } -impl AsRenderableMesh for Square { +impl AsRenderableMesh> for Square { fn vertex_buffer(&self) -> &Subbuffer<[Vertex3D]> { &self.vertex_buffer } diff --git a/src/game/assets/pipelines/simple.rs b/src/game/assets/pipelines/simple.rs index b46c993..f26bcda 100644 --- a/src/game/assets/pipelines/simple.rs +++ b/src/game/assets/pipelines/simple.rs @@ -28,20 +28,15 @@ use vulkano::{ use crate::core::render::{ primitives::{ - AsBindableDescriptorSet, AsRecordable, AsRenderableMesh, mvp::Mvp, transform::TransformRaw, - vertex::Vertex3D, + AsBindableDescriptorSet, AsRecordable, AsRenderableMesh, AsRenderableMeshInstance, + mvp::Mvp, transform::TransformRaw, vertex::Vertex3D, }, texture::Texture, }; -pub struct SimplePipelineRenderData<'a, T> -where - T: AsRenderableMesh, -{ - pub mesh: &'a T, +pub struct SimplePipelineRenderData<'a> { pub mvp_uniform: &'a Subbuffer<[Mvp]>, pub texture: &'a Texture, - pub instances: &'a Subbuffer<[TransformRaw]>, } pub struct SimplePipeline { @@ -130,14 +125,16 @@ impl SimplePipeline { } } -impl<'a, T: AsRenderableMesh> AsRecordable> +impl<'a> AsRecordable, Vertex3D, Subbuffer<[u32]>, TransformRaw> for SimplePipeline { - fn record_render_commands( + fn record_bind_commands( builder: &mut AutoCommandBufferBuilder, descriptor_set_allocator: &Arc, pipeline: &Arc, - data: &SimplePipelineRenderData<'a, T>, + mesh: &impl AsRenderableMesh>, + instances: &impl AsRenderableMeshInstance, + data: &SimplePipelineRenderData<'a>, ) -> Result<(), Box> { let layouts = pipeline.layout().set_layouts(); @@ -158,32 +155,12 @@ impl<'a, T: AsRenderableMesh> AsRecordable { - builder.bind_index_buffer(index_buffer.clone())?; - unsafe { - builder.draw_indexed( - data.mesh.index_count(), - data.instances.len() as u32, - data.mesh.first_index(), - data.mesh.vertex_offset(), - 0, - )?; - } - } - None => unsafe { - builder.draw( - data.mesh.vertex_count(), - data.instances.len() as u32, - data.mesh.first_vertex(), - 0, - )?; - }, - } - Ok(()) } } diff --git a/src/game/scenes/main_scene.rs b/src/game/scenes/main_scene.rs index eb3b6bf..5f1388c 100644 --- a/src/game/scenes/main_scene.rs +++ b/src/game/scenes/main_scene.rs @@ -206,17 +206,18 @@ impl Scene for MainScene { let transform_uniform = Transform::create_buffer(&app_context.memory_allocator, &state.instances)?; - SimplePipeline::record_render_commands( + SimplePipeline::record_bind_commands( &mut builder, &app_context.descriptor_set_allocator, state.simple_pipeline.pipeline(), + &state.square, + &transform_uniform, &SimplePipelineRenderData { - mesh: &state.square, mvp_uniform: &camera_uniform, texture: &state.texture, - instances: &transform_uniform, }, )?; + SimplePipeline::record_draw_commands(&mut builder, &state.square, &transform_uniform)?; RenderPassManager::end_rendering(&mut builder)?; From a32cf6c747a333d89317ebf9cf45c4a5311baf5f Mon Sep 17 00:00:00 2001 From: Florian RICHER Date: Sun, 8 Jun 2025 18:38:51 +0200 Subject: [PATCH 082/105] Refactor texture loading --- Cargo.lock | 10 + Cargo.toml | 3 + res/objects/cube-diffuse.jpg | Bin 0 -> 25317 bytes res/objects/cube-normal.png | Bin 0 -> 119578 bytes res/objects/cube.obj | 1143 +++++++++++++++++ src/core/render/mod.rs | 2 +- src/core/render/primitives/mod.rs | 3 +- src/core/render/resources/meshes/mod.rs | 5 + src/core/render/resources/meshes/obj.rs | 76 ++ .../render/resources/meshes}/square.rs | 6 +- src/core/render/resources/mod.rs | 2 + src/core/render/resources/texture/loader.rs | 127 ++ src/core/render/resources/texture/mod.rs | 5 + .../render/{ => resources/texture}/texture.rs | 54 +- src/game/assets/meshs/mod.rs | 1 - src/game/assets/mod.rs | 1 - src/game/assets/pipelines/simple.rs | 2 +- src/game/assets/square.rs | 255 ---- src/game/scenes/main_scene.rs | 141 +- 19 files changed, 1499 insertions(+), 337 deletions(-) create mode 100644 res/objects/cube-diffuse.jpg create mode 100644 res/objects/cube-normal.png create mode 100644 res/objects/cube.obj create mode 100644 src/core/render/resources/meshes/mod.rs create mode 100644 src/core/render/resources/meshes/obj.rs rename src/{game/assets/meshs => core/render/resources/meshes}/square.rs (92%) create mode 100644 src/core/render/resources/mod.rs create mode 100644 src/core/render/resources/texture/loader.rs create mode 100644 src/core/render/resources/texture/mod.rs rename src/core/render/{ => resources/texture}/texture.rs (79%) delete mode 100644 src/game/assets/meshs/mod.rs delete mode 100644 src/game/assets/square.rs diff --git a/Cargo.lock b/Cargo.lock index ffb6291..c35674a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2090,6 +2090,7 @@ dependencies = [ "image", "rand 0.9.1", "thiserror 2.0.12", + "tobj", "tracing", "tracing-log", "tracing-subscriber", @@ -2466,6 +2467,15 @@ dependencies = [ "zerovec", ] +[[package]] +name = "tobj" +version = "4.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04aca6092e5978e708ee784e8ab9b5cf3cdb598b28f99a2f257446e7081a7025" +dependencies = [ + "ahash", +] + [[package]] name = "toml" version = "0.8.22" diff --git a/Cargo.toml b/Cargo.toml index af26232..c33debe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,3 +28,6 @@ tracing-tracy = "0.11" # Random rand = "0.9" + +# OBJ loader +tobj = "4.0" diff --git a/res/objects/cube-diffuse.jpg b/res/objects/cube-diffuse.jpg new file mode 100644 index 0000000000000000000000000000000000000000..c89b31d5fc05dccd2800fc129c11348015729b10 GIT binary patch literal 25317 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, + index_buffer: Subbuffer<[u32]>, +} + +impl ObjMesh { + pub fn new( + memory_allocator: &Arc, + file_path: impl Into, + ) -> Result, Box> { + let (models, _) = tobj::load_obj( + &file_path.into(), + &tobj::LoadOptions { + single_index: true, + triangulate: true, + ..Default::default() + }, + )?; + + let meshes = models + .into_iter() + .map(|model| { + let vertices = (0..model.mesh.positions.len() / 3) + .map(|i| Vertex3D { + position: [ + model.mesh.positions[i * 3], + model.mesh.positions[i * 3 + 1], + model.mesh.positions[i * 3 + 2], + ], + uv: [model.mesh.texcoords[i * 2], model.mesh.texcoords[i * 2 + 1]], + }) + .collect::>(); + + let indices = model.mesh.indices; + + let vertex_buffer = Vertex3D::create_vertex_buffer(memory_allocator, &vertices) + .expect("Failed to create vertex buffer"); + let index_buffer = u32::create_index_buffer(memory_allocator, &indices) + .expect("Failed to create index buffer"); + + Self { + vertex_buffer, + index_buffer, + } + }) + .collect::>(); + + Ok(meshes) + } +} + +impl AsRenderableMesh> for ObjMesh { + fn vertex_buffer(&self) -> &Subbuffer<[Vertex3D]> { + &self.vertex_buffer + } + + fn index_buffer(&self) -> Option<&Subbuffer<[u32]>> { + Some(&self.index_buffer) + } + + fn vertex_count(&self) -> u32 { + self.vertex_buffer.len() as u32 + } + + fn index_count(&self) -> u32 { + self.index_buffer.len() as u32 + } +} diff --git a/src/game/assets/meshs/square.rs b/src/core/render/resources/meshes/square.rs similarity index 92% rename from src/game/assets/meshs/square.rs rename to src/core/render/resources/meshes/square.rs index 1a67013..087b1a9 100644 --- a/src/game/assets/meshs/square.rs +++ b/src/core/render/resources/meshes/square.rs @@ -27,12 +27,12 @@ const VERTICES: [Vertex3D; 4] = [ const INDICES: [u32; 6] = [0, 2, 1, 2, 3, 1]; -pub struct Square { +pub struct SquareMesh { vertex_buffer: Subbuffer<[Vertex3D]>, index_buffer: Subbuffer<[u32]>, } -impl Square { +impl SquareMesh { pub fn new(memory_allocator: &Arc) -> Result> { let vertex_buffer = Vertex3D::create_vertex_buffer(memory_allocator, &VERTICES)?; let index_buffer = u32::create_index_buffer(memory_allocator, &INDICES)?; @@ -44,7 +44,7 @@ impl Square { } } -impl AsRenderableMesh> for Square { +impl AsRenderableMesh> for SquareMesh { fn vertex_buffer(&self) -> &Subbuffer<[Vertex3D]> { &self.vertex_buffer } diff --git a/src/core/render/resources/mod.rs b/src/core/render/resources/mod.rs new file mode 100644 index 0000000..26807af --- /dev/null +++ b/src/core/render/resources/mod.rs @@ -0,0 +1,2 @@ +pub mod meshes; +pub mod texture; diff --git a/src/core/render/resources/texture/loader.rs b/src/core/render/resources/texture/loader.rs new file mode 100644 index 0000000..81acdc4 --- /dev/null +++ b/src/core/render/resources/texture/loader.rs @@ -0,0 +1,127 @@ +use std::{collections::HashMap, error::Error, sync::Arc}; + +use vulkano::{ + command_buffer::{ + AutoCommandBufferBuilder, CommandBufferUsage, PrimaryCommandBufferAbstract, + allocator::StandardCommandBufferAllocator, + }, + device::{Device, Queue}, + format::Format, + image::sampler::SamplerCreateInfo, + memory::allocator::StandardMemoryAllocator, +}; + +use crate::core::app::context::WindowContext; + +use super::Texture; + +pub enum TextureSourceKind { + File(String), + Buffer(Vec), +} + +pub struct TextureLoadInfo { + pub source: TextureSourceKind, + pub sampler_create_info: SamplerCreateInfo, + pub image_format: Format, +} + +pub struct TextureLoader { + loaded_textures: HashMap, + pending_textures: HashMap, + device: Arc, + command_buffer_allocator: Arc, + memory_allocator: Arc, + queue: Arc, +} + +impl TextureLoader { + pub fn new(app_context: &WindowContext) -> Self { + Self { + loaded_textures: HashMap::new(), + pending_textures: HashMap::new(), + device: app_context.device.clone(), + command_buffer_allocator: app_context.command_buffer_allocator.clone(), + memory_allocator: app_context.memory_allocator.clone(), + queue: Self::select_best_suitable_queue(app_context), + } + } + + fn select_best_suitable_queue(app_context: &WindowContext) -> Arc { + app_context + .transfer_queue + .as_ref() + .map(|queue| { + tracing::trace!( + "Selected transfer queue for texture loading with family index: {:?}", + queue.queue_family_index() + ); + queue.clone() + }) + .or_else(|| { + tracing::trace!( + "Selected graphics queue for texture loading with family index: {:?}", + app_context.graphics_queue.queue_family_index() + ); + Some(app_context.graphics_queue.clone()) + }) + .unwrap() + } + + pub fn add_texture(&mut self, name: String, load_info: TextureLoadInfo) { + self.pending_textures.insert(name, load_info); + } + + pub fn load_pending_textures(&mut self) -> Result<(), Box> { + let _span = tracing::info_span!("load_pending_textures"); + let mut uploads = AutoCommandBufferBuilder::primary( + self.command_buffer_allocator.clone(), + self.queue.queue_family_index(), + CommandBufferUsage::OneTimeSubmit, + )?; + + let mut loading_textures = HashMap::new(); + + tracing::trace!("Pending textures count: {}", self.pending_textures.len()); + + for (name, info) in self.pending_textures.iter() { + let texture = match &info.source { + TextureSourceKind::File(path) => Texture::from_file( + &self.device, + &self.memory_allocator, + &mut uploads, + path.as_str(), + info, + )?, + TextureSourceKind::Buffer(buffer) => Texture::from_bytes( + &self.device, + &self.memory_allocator, + &mut uploads, + &buffer, + info, + )?, + }; + + loading_textures.insert(name.clone(), texture); + tracing::trace!("Loaded texture: {}", name); + } + + let _ = uploads.build()?.execute(self.queue.clone())?; + + self.loaded_textures.extend(loading_textures); + + Ok(()) + } + + pub fn get_texture(&self, name: &str) -> Option<&Texture> { + self.loaded_textures.get(name) + } + + pub fn pending_textures_count(&self) -> usize { + self.pending_textures.len() + } + + pub fn loaded_textures_count(&self) -> usize { + self.loaded_textures.len() + } +} diff --git a/src/core/render/resources/texture/mod.rs b/src/core/render/resources/texture/mod.rs new file mode 100644 index 0000000..7bd052e --- /dev/null +++ b/src/core/render/resources/texture/mod.rs @@ -0,0 +1,5 @@ +mod texture; +pub use texture::Texture; + +mod loader; +pub use loader::{TextureLoadInfo, TextureLoader, TextureSourceKind}; diff --git a/src/core/render/texture.rs b/src/core/render/resources/texture/texture.rs similarity index 79% rename from src/core/render/texture.rs rename to src/core/render/resources/texture/texture.rs index 38d8147..3147d26 100644 --- a/src/core/render/texture.rs +++ b/src/core/render/resources/texture/texture.rs @@ -7,22 +7,20 @@ use vulkano::{ command_buffer::{AutoCommandBufferBuilder, CopyBufferToImageInfo, PrimaryAutoCommandBuffer}, descriptor_set::{ DescriptorSet, WriteDescriptorSet, - allocator::{DescriptorSetAllocator, StandardDescriptorSetAllocator}, + allocator::StandardDescriptorSetAllocator, layout::{DescriptorSetLayout, DescriptorSetLayoutBinding, DescriptorType}, }, device::Device, format::Format, - image::{ - Image, ImageCreateInfo, ImageType, ImageUsage, - sampler::{Filter, Sampler, SamplerAddressMode, SamplerCreateInfo}, - view::ImageView, - }, + image::{Image, ImageCreateInfo, ImageType, ImageUsage, sampler::Sampler, view::ImageView}, memory::allocator::{AllocationCreateInfo, MemoryTypeFilter, StandardMemoryAllocator}, shader::ShaderStages, }; use crate::core::render::primitives::AsBindableDescriptorSet; +use super::TextureLoadInfo; + pub struct Texture { texture: Arc, sampler: Arc, @@ -33,37 +31,39 @@ impl Texture { Self { texture, sampler } } - pub fn from_file( + pub(super) fn from_file( device: &Arc, memory_allocator: &Arc, builder: &mut AutoCommandBufferBuilder, path: &str, + load_info: &TextureLoadInfo, ) -> Result> { - let _span = tracing::info_span!("texture_load_from_file", path = path); - let bytes = std::fs::read(path)?; - Self::from_bytes(device, memory_allocator, builder, &bytes) + Self::from_bytes(device, memory_allocator, builder, &bytes, load_info) } - pub fn from_bytes( + pub(super) fn from_bytes( device: &Arc, memory_allocator: &Arc, builder: &mut AutoCommandBufferBuilder, bytes: &[u8], + load_info: &TextureLoadInfo, ) -> Result> { let image = image::load_from_memory(bytes)?; - Self::from_dynamic_image(device, memory_allocator, builder, image) + Self::from_dynamic_image(device, memory_allocator, builder, image, load_info) } - pub fn from_dynamic_image( + fn from_dynamic_image( device: &Arc, memory_allocator: &Arc, builder: &mut AutoCommandBufferBuilder, image: DynamicImage, + load_info: &TextureLoadInfo, ) -> Result> { - let _span = tracing::info_span!("texture_from_dynamic_image"); - - let image_data = image.to_rgba8(); + let image_data = match load_info.image_format { + Format::R8G8B8A8_SRGB => image.to_rgba8(), + _ => return Err("Unsupported format".into()), + }; let image_dimensions = image_data.dimensions(); let image_data = image_data.into_raw(); @@ -90,7 +90,7 @@ impl Texture { memory_allocator.clone(), ImageCreateInfo { image_type: ImageType::Dim2d, - format: Format::R8G8B8A8_SRGB, + format: load_info.image_format, extent: [image_dimensions.0, image_dimensions.1, 1], array_layers: 1, usage: ImageUsage::TRANSFER_DST | ImageUsage::SAMPLED, @@ -104,30 +104,12 @@ impl Texture { image.clone(), ))?; - let sampler = Sampler::new( - device.clone(), - SamplerCreateInfo { - mag_filter: Filter::Linear, - min_filter: Filter::Linear, - address_mode: [SamplerAddressMode::Repeat; 3], - ..Default::default() - }, - )?; + let sampler = Sampler::new(device.clone(), load_info.sampler_create_info.clone())?; let image_view = ImageView::new_default(image)?; - tracing::trace!("Texture loaded with dimensions {:?}", image_dimensions); - Ok(Self::new(image_view, sampler)) } - - pub fn get_texture(&self) -> &Arc { - &self.texture - } - - pub fn get_sampler(&self) -> &Arc { - &self.sampler - } } impl AsBindableDescriptorSet for Texture { diff --git a/src/game/assets/meshs/mod.rs b/src/game/assets/meshs/mod.rs deleted file mode 100644 index d793a66..0000000 --- a/src/game/assets/meshs/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod square; diff --git a/src/game/assets/mod.rs b/src/game/assets/mod.rs index a0cac96..81e613f 100644 --- a/src/game/assets/mod.rs +++ b/src/game/assets/mod.rs @@ -1,2 +1 @@ -pub mod meshs; pub mod pipelines; diff --git a/src/game/assets/pipelines/simple.rs b/src/game/assets/pipelines/simple.rs index f26bcda..3c87f9a 100644 --- a/src/game/assets/pipelines/simple.rs +++ b/src/game/assets/pipelines/simple.rs @@ -31,7 +31,7 @@ use crate::core::render::{ AsBindableDescriptorSet, AsRecordable, AsRenderableMesh, AsRenderableMeshInstance, mvp::Mvp, transform::TransformRaw, vertex::Vertex3D, }, - texture::Texture, + resources::texture::Texture, }; pub struct SimplePipelineRenderData<'a> { diff --git a/src/game/assets/square.rs b/src/game/assets/square.rs deleted file mode 100644 index cea1274..0000000 --- a/src/game/assets/square.rs +++ /dev/null @@ -1,255 +0,0 @@ -use std::{error::Error, sync::Arc}; - -use vulkano::{ - buffer::Subbuffer, - command_buffer::{AutoCommandBufferBuilder, PrimaryAutoCommandBuffer}, - descriptor_set::{ - DescriptorSet, allocator::StandardDescriptorSetAllocator, - layout::DescriptorSetLayoutCreateInfo, - }, - device::Device, - format::Format, - memory::allocator::StandardMemoryAllocator, - pipeline::{ - DynamicState, GraphicsPipeline, Pipeline, PipelineBindPoint, PipelineLayout, - PipelineShaderStageCreateInfo, - graphics::{ - GraphicsPipelineCreateInfo, - color_blend::{ColorBlendAttachmentState, ColorBlendState}, - depth_stencil::{DepthState, DepthStencilState}, - input_assembly::InputAssemblyState, - multisample::MultisampleState, - rasterization::RasterizationState, - subpass::PipelineRenderingCreateInfo, - vertex_input::{Vertex, VertexDefinition}, - viewport::ViewportState, - }, - layout::{PipelineDescriptorSetLayoutCreateInfo, PipelineLayoutCreateFlags}, - }, -}; - -use crate::core::render::{ - primitives::{ - AsBindableDescriptorSet, AsDrawable, AsIndexBuffer, AsRecordable, AsVertexBuffer, mvp::Mvp, - transform::TransformRaw, vertex::Vertex3D, - }, - texture::Texture, -}; - -const VERTICES: [Vertex3D; 4] = [ - Vertex3D { - position: [-0.5, -0.5, 0.0], - uv: [0.0, 0.0], - }, - Vertex3D { - position: [-0.5, 0.5, 0.0], - uv: [0.0, 1.0], - }, - Vertex3D { - position: [0.5, -0.5, 0.0], - uv: [1.0, 0.0], - }, - Vertex3D { - position: [0.5, 0.5, 0.0], - uv: [1.0, 1.0], - }, -]; - -const INDICES: [u32; 6] = [0, 2, 1, 2, 3, 1]; - -pub mod shaders { - pub mod vs { - vulkano_shaders::shader! { - ty: "vertex", - path: r"res/shaders/vertex.vert", - generate_structs: false, - } - } - - pub mod fs { - vulkano_shaders::shader! { - ty: "fragment", - path: r"res/shaders/vertex.frag", - generate_structs: false, - } - } -} - -pub struct Square { - vertex_buffer: Subbuffer<[Vertex3D]>, - index_buffer: Subbuffer<[u32]>, - pipeline: Arc, -} - -/// Structure pour encapsuler les données de rendu du Square -pub struct SquareRenderData<'a> { - pub square: &'a Square, - pub mvp_uniform: &'a Subbuffer<[Mvp]>, - pub transform_uniform: &'a Subbuffer<[TransformRaw]>, - pub descriptor_sets: &'a [Arc], - pub texture: &'a Texture, -} - -impl Square { - pub fn new( - device: &Arc, - memory_allocator: &Arc, - swapchain_format: Format, - depth_format: Format, - ) -> Result> { - let vertex_buffer = Vertex3D::create_vertex_buffer(memory_allocator, &VERTICES)?; - - let index_buffer = u32::create_index_buffer(memory_allocator, &INDICES)?; - - let vs = shaders::vs::load(device.clone())? - .entry_point("main") - .ok_or("Failed find main entry point of vertex shader".to_string())?; - - let fs = shaders::fs::load(device.clone())? - .entry_point("main") - .ok_or("Failed find main entry point of fragment shader".to_string())?; - - let vertex_input_state = - [Vertex3D::per_vertex(), TransformRaw::per_instance()].definition(&vs)?; - - let stages = [ - PipelineShaderStageCreateInfo::new(vs), - PipelineShaderStageCreateInfo::new(fs), - ]; - - let vertex_bindings = Mvp::as_descriptor_set_layout_bindings(); - let texture_bindings = Texture::as_descriptor_set_layout_bindings(); - - let vertex_descriptor_set_layout = DescriptorSetLayoutCreateInfo { - bindings: vertex_bindings, - ..Default::default() - }; - - let fragment_descriptor_set_layout = DescriptorSetLayoutCreateInfo { - bindings: texture_bindings, - ..Default::default() - }; - - let create_info = PipelineDescriptorSetLayoutCreateInfo { - set_layouts: vec![vertex_descriptor_set_layout, fragment_descriptor_set_layout], - flags: PipelineLayoutCreateFlags::default(), - push_constant_ranges: vec![], - } - .into_pipeline_layout_create_info(device.clone())?; - - let layout = PipelineLayout::new(device.clone(), create_info)?; - - let subpass = PipelineRenderingCreateInfo { - color_attachment_formats: vec![Some(swapchain_format)], - depth_attachment_format: Some(depth_format), - ..Default::default() - }; - - let pipeline = GraphicsPipeline::new( - device.clone(), - None, - GraphicsPipelineCreateInfo { - stages: stages.into_iter().collect(), - vertex_input_state: Some(vertex_input_state), - input_assembly_state: Some(InputAssemblyState::default()), - viewport_state: Some(ViewportState::default()), - rasterization_state: Some(RasterizationState::default()), - multisample_state: Some(MultisampleState::default()), - color_blend_state: Some(ColorBlendState::with_attachment_states( - subpass.color_attachment_formats.len() as u32, - ColorBlendAttachmentState::default(), - )), - depth_stencil_state: Some(DepthStencilState { - depth: Some(DepthState::simple()), - ..Default::default() - }), - dynamic_state: [DynamicState::Viewport].into_iter().collect(), - subpass: Some(subpass.into()), - ..GraphicsPipelineCreateInfo::layout(layout) - }, - )?; - - Ok(Self { - vertex_buffer, - index_buffer, - pipeline, - }) - } - - pub fn render( - &self, - command_buffer: &mut AutoCommandBufferBuilder, - descriptor_set_allocator: &Arc, - mvp_uniform: &Subbuffer<[Mvp]>, - transform_uniform: &Subbuffer<[TransformRaw]>, - texture: &Texture, - ) -> Result<(), Box> { - let layouts = self.pipeline.layout().set_layouts(); - - let uniform_descriptor_set = - Mvp::as_descriptor_set(descriptor_set_allocator, &layouts[0], mvp_uniform)?; - - let texture_descriptor_set = - Texture::as_descriptor_set(descriptor_set_allocator, &layouts[1], texture)?; - - let render_data = SquareRenderData { - square: self, - mvp_uniform, - transform_uniform, - texture, - descriptor_sets: &[uniform_descriptor_set, texture_descriptor_set], - }; - - // Utiliser les nouveaux traits pour le rendu - Self::record_render_commands(command_buffer, &self.pipeline, &render_data)?; - - Ok(()) - } -} - -impl<'a> AsRecordable> for Square { - fn record_render_commands( - builder: &mut AutoCommandBufferBuilder, - pipeline: &Arc, - data: &SquareRenderData<'a>, - ) -> Result<(), Box> { - // Bind pipeline - builder.bind_pipeline_graphics(pipeline.clone())?; - - // Bind descriptor sets - builder.bind_descriptor_sets( - PipelineBindPoint::Graphics, - pipeline.layout().clone(), - 0, - data.descriptor_sets.iter().cloned().collect::>(), - )?; - - // Bind buffers - builder.bind_vertex_buffers( - 0, - ( - Self::vertex_buffer(data).clone(), - data.transform_uniform.clone(), - ), - )?; - - if let Some(index_buffer) = Self::index_buffer(data) { - builder.bind_index_buffer(index_buffer.clone())?; - unsafe { - builder.draw_indexed( - Self::index_count(data), - Self::instance_count(data), - 0, - 0, - 0, - )?; - } - } else { - unsafe { - builder.draw(Self::vertex_count(data), Self::instance_count(data), 0, 0)?; - } - } - - Ok(()) - } -} diff --git a/src/game/scenes/main_scene.rs b/src/game/scenes/main_scene.rs index 5f1388c..31126ea 100644 --- a/src/game/scenes/main_scene.rs +++ b/src/game/scenes/main_scene.rs @@ -4,19 +4,22 @@ use super::settings_scene::SettingsScene; use crate::core::app::DEPTH_IMAGE_ID; use crate::core::app::context::WindowContext; use crate::core::app::user_event::UserEvent; +use crate::core::render::primitives::AsRecordable; use crate::core::render::primitives::camera::Camera3D; -use crate::core::render::primitives::mvp::Mvp; use crate::core::render::primitives::transform::Transform; -use crate::core::render::primitives::{AsBindableDescriptorSet, AsRecordable}; use crate::core::render::render_pass_manager::{RenderPassConfig, RenderPassManager}; -use crate::core::render::texture::Texture; +use crate::core::render::resources::meshes::{ObjMesh, SquareMesh}; +use crate::core::render::resources::texture::{ + Texture, TextureLoadInfo, TextureLoader, TextureSourceKind, +}; use crate::core::scene::Scene; -use crate::game::assets::meshs::square::Square; use crate::game::assets::pipelines::simple::{SimplePipeline, SimplePipelineRenderData}; use egui_winit_vulkano::egui; use glam::EulerRot; use glam::Quat; use glam::Vec3; +use vulkano::format::Format; +use vulkano::image::sampler::{Filter, SamplerAddressMode, SamplerCreateInfo}; use vulkano::{ command_buffer::{AutoCommandBufferBuilder, CommandBufferUsage, PrimaryCommandBufferAbstract}, sync::GpuFuture, @@ -24,11 +27,13 @@ use vulkano::{ use winit::window::CursorGrabMode; pub struct MainSceneState { - square: Square, + texture_loader: TextureLoader, + square: SquareMesh, + obj: ObjMesh, simple_pipeline: SimplePipeline, - instances: Vec, + square_instances: Vec, + obj_instances: Vec, camera: Camera3D, - texture: Texture, speed: f32, } @@ -50,7 +55,41 @@ impl Scene for MainScene { let swapchain_image_view = app_context.with_renderer(|renderer| renderer.swapchain_image_view().clone()); - let square = Square::new(&app_context.memory_allocator)?; + let mut texture_loader = TextureLoader::new(app_context); + texture_loader.add_texture( + "wooden-crate".to_string(), + TextureLoadInfo { + source: TextureSourceKind::File("res/textures/wooden-crate.jpg".to_string()), + sampler_create_info: SamplerCreateInfo { + mag_filter: Filter::Linear, + min_filter: Filter::Linear, + address_mode: [SamplerAddressMode::Repeat; 3], + ..Default::default() + }, + image_format: Format::R8G8B8A8_SRGB, + }, + ); + texture_loader.add_texture( + "cube-diffuse".to_string(), + TextureLoadInfo { + source: TextureSourceKind::File("res/objects/cube-diffuse.jpg".to_string()), + sampler_create_info: SamplerCreateInfo { + mag_filter: Filter::Linear, + min_filter: Filter::Linear, + address_mode: [SamplerAddressMode::Repeat; 3], + ..Default::default() + }, + image_format: Format::R8G8B8A8_SRGB, + }, + ); + texture_loader.load_pending_textures()?; + + let square = SquareMesh::new(&app_context.memory_allocator)?; + + let obj = { + let obj = ObjMesh::new(&app_context.memory_allocator, "res/objects/cube.obj")?; + obj.into_iter().next().unwrap() + }; let simple_pipeline = SimplePipeline::new( &app_context.device, swapchain_image_view.format(), @@ -61,7 +100,7 @@ impl Scene for MainScene { let instance_size = 10.0; let instance_spacing = 10.0; let num_instances_per_row = (num_instances as f32 / instance_spacing).ceil() as u32; - let instances: Vec = (0..num_instances) + let square_instances: Vec = (0..num_instances) .map(|i| { Transform::new( Vec3::new( @@ -75,26 +114,26 @@ impl Scene for MainScene { }) .collect(); - let texture = { - let mut uploads = AutoCommandBufferBuilder::primary( - app_context.command_buffer_allocator.clone(), - app_context.graphics_queue.queue_family_index(), - CommandBufferUsage::OneTimeSubmit, - )?; - - let texture = Texture::from_file( - &app_context.device, - &app_context.memory_allocator, - &mut uploads, - "res/textures/wooden-crate.jpg", - )?; - - let _ = uploads - .build()? - .execute(app_context.graphics_queue.clone())?; - - texture - }; + let obj_instances: Vec = (0..num_instances) + .map(|i| { + Transform::new( + Vec3::new( + (i % num_instances_per_row) as f32 * (instance_spacing + instance_size), + 0.0, + (i / num_instances_per_row) as f32 + * (instance_spacing + instance_size) + * -1.0 + - instance_spacing * 2.0, + ), + Quat::from_euler(EulerRot::XYZ, 0.0, rand::random_range(0.0..=360.0), 0.0), + Vec3::new( + instance_size * 0.5, + instance_size * 0.5, + instance_size * 0.5, + ), + ) + }) + .collect(); let camera = app_context.with_renderer(|renderer| { Camera3D::new( @@ -107,11 +146,13 @@ impl Scene for MainScene { self.state = Some(MainSceneState { square, + obj, simple_pipeline, - instances, + square_instances, + obj_instances, camera, - texture, speed: 50.0, + texture_loader, }); Ok(()) @@ -132,7 +173,14 @@ impl Scene for MainScene { }); let delta_time = app_context.get_delta_time(); - for (i, instance) in state.instances.iter_mut().enumerate() { + for (i, instance) in state.square_instances.iter_mut().enumerate() { + let rotation_speed = (i % 10) as f32; + let rotation_delta = Quat::from_rotation_y(rotation_speed * delta_time); + + instance.rotate(rotation_delta); + } + + for (i, instance) in state.obj_instances.iter_mut().enumerate() { let rotation_speed = (i % 10) as f32; let rotation_delta = Quat::from_rotation_y(rotation_speed * delta_time); @@ -203,21 +251,40 @@ impl Scene for MainScene { // Create camera uniform using the actual camera let camera_uniform = state.camera.create_buffer(&app_context.memory_allocator)?; - let transform_uniform = - Transform::create_buffer(&app_context.memory_allocator, &state.instances)?; + let square_transform_uniform = + Transform::create_buffer(&app_context.memory_allocator, &state.square_instances)?; + let obj_transform_uniform = + Transform::create_buffer(&app_context.memory_allocator, &state.obj_instances)?; SimplePipeline::record_bind_commands( &mut builder, &app_context.descriptor_set_allocator, state.simple_pipeline.pipeline(), &state.square, - &transform_uniform, + &square_transform_uniform, &SimplePipelineRenderData { mvp_uniform: &camera_uniform, - texture: &state.texture, + texture: &state.texture_loader.get_texture("wooden-crate").unwrap(), }, )?; - SimplePipeline::record_draw_commands(&mut builder, &state.square, &transform_uniform)?; + SimplePipeline::record_draw_commands( + &mut builder, + &state.square, + &square_transform_uniform, + )?; + + SimplePipeline::record_bind_commands( + &mut builder, + &app_context.descriptor_set_allocator, + state.simple_pipeline.pipeline(), + &state.obj, + &obj_transform_uniform, + &SimplePipelineRenderData { + mvp_uniform: &camera_uniform, + texture: &state.texture_loader.get_texture("cube-diffuse").unwrap(), + }, + )?; + SimplePipeline::record_draw_commands(&mut builder, &state.obj, &obj_transform_uniform)?; RenderPassManager::end_rendering(&mut builder)?; From 90a5b5d117214c68330a4d2b958b626234081e71 Mon Sep 17 00:00:00 2001 From: Florian RICHER Date: Sun, 8 Jun 2025 18:48:12 +0200 Subject: [PATCH 083/105] Fix typo --- src/core/render/resources/texture/loader.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/render/resources/texture/loader.rs b/src/core/render/resources/texture/loader.rs index 81acdc4..8d26862 100644 --- a/src/core/render/resources/texture/loader.rs +++ b/src/core/render/resources/texture/loader.rs @@ -53,14 +53,14 @@ impl TextureLoader { .as_ref() .map(|queue| { tracing::trace!( - "Selected transfer queue for texture loading with family index: {:?}", + "Select transfer queue for texture loading with family index: {:?}", queue.queue_family_index() ); queue.clone() }) .or_else(|| { tracing::trace!( - "Selected graphics queue for texture loading with family index: {:?}", + "Select graphics queue for texture loading with family index: {:?}", app_context.graphics_queue.queue_family_index() ); Some(app_context.graphics_queue.clone()) From 0174aeb60e5daba3140871ee4af007f3703e385b Mon Sep 17 00:00:00 2001 From: Florian RICHER Date: Mon, 9 Jun 2025 16:12:21 +0200 Subject: [PATCH 084/105] Reduce Generics in pipeline rendering --- src/core/render/primitives/command.rs | 6 ++- src/core/render/primitives/descriptor_set.rs | 22 +++++++++ src/core/render/primitives/mod.rs | 23 +-------- src/core/render/primitives/mvp.rs | 12 +++-- src/core/render/resources/texture/loader.rs | 6 +-- src/core/render/resources/texture/texture.rs | 12 +++-- src/game/assets/pipelines/simple.rs | 49 +++++++++----------- src/game/scenes/main_scene.rs | 38 +++++++++------ 8 files changed, 92 insertions(+), 76 deletions(-) create mode 100644 src/core/render/primitives/descriptor_set.rs diff --git a/src/core/render/primitives/command.rs b/src/core/render/primitives/command.rs index a71b9d2..6fdca2c 100644 --- a/src/core/render/primitives/command.rs +++ b/src/core/render/primitives/command.rs @@ -7,7 +7,9 @@ use vulkano::{ pipeline::GraphicsPipeline, }; -pub trait AsRecordable +use super::AsDescriptorSet; + +pub trait AsRecordable where MI: Into + Clone, { @@ -17,7 +19,7 @@ where pipeline: &Arc, mesh: &impl AsRenderableMesh, instances: &impl AsRenderableMeshInstance, - extra_data: &T, + descriptor_sets: Vec>, ) -> Result<(), Box>; fn record_draw_commands( diff --git a/src/core/render/primitives/descriptor_set.rs b/src/core/render/primitives/descriptor_set.rs new file mode 100644 index 0000000..964d5e9 --- /dev/null +++ b/src/core/render/primitives/descriptor_set.rs @@ -0,0 +1,22 @@ +use std::{collections::BTreeMap, sync::Arc}; + +use vulkano::{ + Validated, VulkanError, + descriptor_set::{ + DescriptorSet, + allocator::StandardDescriptorSetAllocator, + layout::{DescriptorSetLayout, DescriptorSetLayoutBinding}, + }, +}; + +pub trait AsDescriptorSetLayoutBindings { + fn as_descriptor_set_layout_bindings() -> BTreeMap; +} + +pub trait AsDescriptorSet { + fn as_descriptor_set( + &self, + descriptor_set_allocator: &Arc, + layout: &Arc, + ) -> Result, Validated>; +} diff --git a/src/core/render/primitives/mod.rs b/src/core/render/primitives/mod.rs index 75743cd..26810d6 100644 --- a/src/core/render/primitives/mod.rs +++ b/src/core/render/primitives/mod.rs @@ -1,16 +1,6 @@ -use std::{collections::BTreeMap, sync::Arc}; - -use vulkano::{ - Validated, VulkanError, - descriptor_set::{ - DescriptorSet, - allocator::StandardDescriptorSetAllocator, - layout::{DescriptorSetLayout, DescriptorSetLayoutBinding}, - }, -}; - mod buffer; mod command; +mod descriptor_set; pub mod camera; pub mod mvp; @@ -19,13 +9,4 @@ pub mod transform; pub mod vertex; pub use buffer::{AsBindableBuffer, AsIndexBuffer, AsUniformBuffer, AsVertexBuffer}; pub use command::{AsRecordable, AsRenderableMesh, AsRenderableMeshInstance}; - -pub trait AsBindableDescriptorSet { - fn as_descriptor_set_layout_bindings() -> BTreeMap; - - fn as_descriptor_set( - descriptor_set_allocator: &Arc, - layout: &Arc, - data: &T, - ) -> Result, Validated>; -} +pub use descriptor_set::{AsDescriptorSet, AsDescriptorSetLayoutBindings}; diff --git a/src/core/render/primitives/mvp.rs b/src/core/render/primitives/mvp.rs index af5db16..09c8673 100644 --- a/src/core/render/primitives/mvp.rs +++ b/src/core/render/primitives/mvp.rs @@ -13,7 +13,9 @@ use vulkano::memory::allocator::{AllocationCreateInfo, MemoryTypeFilter, Standar use vulkano::shader::ShaderStages; use vulkano::{Validated, VulkanError}; -use crate::core::render::primitives::{AsBindableBuffer, AsBindableDescriptorSet, AsUniformBuffer}; +use crate::core::render::primitives::{AsBindableBuffer, AsUniformBuffer}; + +use super::{AsDescriptorSet, AsDescriptorSetLayoutBindings}; #[derive(BufferContents, Clone, Copy)] #[repr(C)] @@ -57,7 +59,7 @@ impl AsBindableBuffer for Mvp { impl AsUniformBuffer for Mvp {} -impl AsBindableDescriptorSet> for Mvp { +impl AsDescriptorSetLayoutBindings for Mvp { fn as_descriptor_set_layout_bindings() -> BTreeMap { BTreeMap::::from_iter([( 0, @@ -67,16 +69,18 @@ impl AsBindableDescriptorSet> for Mvp { }, )]) } +} +impl AsDescriptorSet for Subbuffer<[Mvp]> { fn as_descriptor_set( + &self, descriptor_set_allocator: &Arc, layout: &Arc, - data: &Subbuffer<[Mvp]>, ) -> Result, Validated> { DescriptorSet::new( descriptor_set_allocator.clone(), layout.clone(), - [WriteDescriptorSet::buffer(0, data.clone())], + [WriteDescriptorSet::buffer(0, self.clone())], [], ) } diff --git a/src/core/render/resources/texture/loader.rs b/src/core/render/resources/texture/loader.rs index 8d26862..d440291 100644 --- a/src/core/render/resources/texture/loader.rs +++ b/src/core/render/resources/texture/loader.rs @@ -27,7 +27,7 @@ pub struct TextureLoadInfo { } pub struct TextureLoader { - loaded_textures: HashMap, + loaded_textures: HashMap>, pending_textures: HashMap, device: Arc, command_buffer_allocator: Arc, @@ -102,7 +102,7 @@ impl TextureLoader { )?, }; - loading_textures.insert(name.clone(), texture); + loading_textures.insert(name.clone(), Arc::new(texture)); tracing::trace!("Loaded texture: {}", name); } @@ -113,7 +113,7 @@ impl TextureLoader { Ok(()) } - pub fn get_texture(&self, name: &str) -> Option<&Texture> { + pub fn get_texture(&self, name: &str) -> Option<&Arc> { self.loaded_textures.get(name) } diff --git a/src/core/render/resources/texture/texture.rs b/src/core/render/resources/texture/texture.rs index 3147d26..8eaf140 100644 --- a/src/core/render/resources/texture/texture.rs +++ b/src/core/render/resources/texture/texture.rs @@ -17,7 +17,7 @@ use vulkano::{ shader::ShaderStages, }; -use crate::core::render::primitives::AsBindableDescriptorSet; +use crate::core::render::primitives::{AsDescriptorSet, AsDescriptorSetLayoutBindings}; use super::TextureLoadInfo; @@ -112,7 +112,7 @@ impl Texture { } } -impl AsBindableDescriptorSet for Texture { +impl AsDescriptorSetLayoutBindings for Texture { fn as_descriptor_set_layout_bindings() -> BTreeMap { BTreeMap::::from_iter([ ( @@ -131,18 +131,20 @@ impl AsBindableDescriptorSet for Texture { ), ]) } +} +impl AsDescriptorSet for Texture { fn as_descriptor_set( + &self, descriptor_set_allocator: &Arc, layout: &Arc, - data: &Texture, ) -> Result, Validated> { DescriptorSet::new( descriptor_set_allocator.clone(), layout.clone(), [ - WriteDescriptorSet::sampler(0, data.sampler.clone()), - WriteDescriptorSet::image_view(1, data.texture.clone()), + WriteDescriptorSet::sampler(0, self.sampler.clone()), + WriteDescriptorSet::image_view(1, self.texture.clone()), ], [], ) diff --git a/src/game/assets/pipelines/simple.rs b/src/game/assets/pipelines/simple.rs index 3c87f9a..e003276 100644 --- a/src/game/assets/pipelines/simple.rs +++ b/src/game/assets/pipelines/simple.rs @@ -1,4 +1,4 @@ -use std::{error::Error, sync::Arc}; +use std::{collections::HashMap, error::Error, sync::Arc}; use vulkano::{ buffer::Subbuffer, @@ -28,17 +28,12 @@ use vulkano::{ use crate::core::render::{ primitives::{ - AsBindableDescriptorSet, AsRecordable, AsRenderableMesh, AsRenderableMeshInstance, - mvp::Mvp, transform::TransformRaw, vertex::Vertex3D, + AsDescriptorSet, AsDescriptorSetLayoutBindings, AsRecordable, AsRenderableMesh, + AsRenderableMeshInstance, mvp::Mvp, transform::TransformRaw, vertex::Vertex3D, }, resources::texture::Texture, }; -pub struct SimplePipelineRenderData<'a> { - pub mvp_uniform: &'a Subbuffer<[Mvp]>, - pub texture: &'a Texture, -} - pub struct SimplePipeline { pipeline: Arc, } @@ -125,33 +120,35 @@ impl SimplePipeline { } } -impl<'a> AsRecordable, Vertex3D, Subbuffer<[u32]>, TransformRaw> - for SimplePipeline -{ +impl AsRecordable, TransformRaw> for SimplePipeline { fn record_bind_commands( builder: &mut AutoCommandBufferBuilder, descriptor_set_allocator: &Arc, pipeline: &Arc, mesh: &impl AsRenderableMesh>, instances: &impl AsRenderableMeshInstance, - data: &SimplePipelineRenderData<'a>, + descriptor_sets: Vec>, ) -> Result<(), Box> { - let layouts = pipeline.layout().set_layouts(); - - let uniform_descriptor_set = - Mvp::as_descriptor_set(descriptor_set_allocator, &layouts[0], data.mvp_uniform)?; - - let texture_descriptor_set = - Texture::as_descriptor_set(descriptor_set_allocator, &layouts[1], data.texture)?; - builder.bind_pipeline_graphics(pipeline.clone())?; - builder.bind_descriptor_sets( - PipelineBindPoint::Graphics, - pipeline.layout().clone(), - 0, - vec![uniform_descriptor_set, texture_descriptor_set], - )?; + if !descriptor_sets.is_empty() { + let layouts = pipeline.layout().set_layouts(); + + let descriptor_sets = descriptor_sets + .iter() + .enumerate() + .map(|(layout_index, data)| { + data.as_descriptor_set(descriptor_set_allocator, &layouts[layout_index]) + }) + .collect::, _>>()?; + + builder.bind_descriptor_sets( + PipelineBindPoint::Graphics, + pipeline.layout().clone(), + 0, + descriptor_sets, + )?; + } builder.bind_vertex_buffers( 0, diff --git a/src/game/scenes/main_scene.rs b/src/game/scenes/main_scene.rs index 31126ea..e139e90 100644 --- a/src/game/scenes/main_scene.rs +++ b/src/game/scenes/main_scene.rs @@ -1,19 +1,19 @@ +use std::collections::HashMap; use std::error::Error; +use std::sync::Arc; use super::settings_scene::SettingsScene; use crate::core::app::DEPTH_IMAGE_ID; use crate::core::app::context::WindowContext; use crate::core::app::user_event::UserEvent; -use crate::core::render::primitives::AsRecordable; use crate::core::render::primitives::camera::Camera3D; use crate::core::render::primitives::transform::Transform; +use crate::core::render::primitives::{AsDescriptorSet, AsRecordable}; use crate::core::render::render_pass_manager::{RenderPassConfig, RenderPassManager}; use crate::core::render::resources::meshes::{ObjMesh, SquareMesh}; -use crate::core::render::resources::texture::{ - Texture, TextureLoadInfo, TextureLoader, TextureSourceKind, -}; +use crate::core::render::resources::texture::{TextureLoadInfo, TextureLoader, TextureSourceKind}; use crate::core::scene::Scene; -use crate::game::assets::pipelines::simple::{SimplePipeline, SimplePipelineRenderData}; +use crate::game::assets::pipelines::simple::SimplePipeline; use egui_winit_vulkano::egui; use glam::EulerRot; use glam::Quat; @@ -21,7 +21,7 @@ use glam::Vec3; use vulkano::format::Format; use vulkano::image::sampler::{Filter, SamplerAddressMode, SamplerCreateInfo}; use vulkano::{ - command_buffer::{AutoCommandBufferBuilder, CommandBufferUsage, PrimaryCommandBufferAbstract}, + command_buffer::{AutoCommandBufferBuilder, CommandBufferUsage}, sync::GpuFuture, }; use winit::window::CursorGrabMode; @@ -250,7 +250,7 @@ impl Scene for MainScene { } // Create camera uniform using the actual camera - let camera_uniform = state.camera.create_buffer(&app_context.memory_allocator)?; + let camera_uniform = Arc::new(state.camera.create_buffer(&app_context.memory_allocator)?); let square_transform_uniform = Transform::create_buffer(&app_context.memory_allocator, &state.square_instances)?; let obj_transform_uniform = @@ -262,10 +262,14 @@ impl Scene for MainScene { state.simple_pipeline.pipeline(), &state.square, &square_transform_uniform, - &SimplePipelineRenderData { - mvp_uniform: &camera_uniform, - texture: &state.texture_loader.get_texture("wooden-crate").unwrap(), - }, + vec![ + camera_uniform.clone() as Arc, + state + .texture_loader + .get_texture("wooden-crate") + .unwrap() + .clone(), + ], )?; SimplePipeline::record_draw_commands( &mut builder, @@ -279,10 +283,14 @@ impl Scene for MainScene { state.simple_pipeline.pipeline(), &state.obj, &obj_transform_uniform, - &SimplePipelineRenderData { - mvp_uniform: &camera_uniform, - texture: &state.texture_loader.get_texture("cube-diffuse").unwrap(), - }, + vec![ + camera_uniform.clone() as Arc, + state + .texture_loader + .get_texture("cube-diffuse") + .unwrap() + .clone(), + ], )?; SimplePipeline::record_draw_commands(&mut builder, &state.obj, &obj_transform_uniform)?; From 883014998f550d87b12c16aeb8d0410cca9a9459 Mon Sep 17 00:00:00 2001 From: Florian RICHER Date: Mon, 9 Jun 2025 16:13:32 +0200 Subject: [PATCH 085/105] Remove material_manager and resource (Not used) --- src/core/render/material_manager.rs | 246 ------------------------- src/core/render/mod.rs | 1 - src/core/render/primitives/mod.rs | 1 - src/core/render/primitives/resource.rs | 208 --------------------- 4 files changed, 456 deletions(-) delete mode 100644 src/core/render/material_manager.rs delete mode 100644 src/core/render/primitives/resource.rs diff --git a/src/core/render/material_manager.rs b/src/core/render/material_manager.rs deleted file mode 100644 index 008ad07..0000000 --- a/src/core/render/material_manager.rs +++ /dev/null @@ -1,246 +0,0 @@ -use std::{ - any::TypeId, - error::Error, - sync::{Arc, RwLock, RwLockReadGuard}, -}; - -use vulkano::{device::Device, format::Format, memory::allocator::StandardMemoryAllocator}; - -pub trait Pipeline { - fn load( - &mut self, - device: &Device, - memory_allocator: &StandardMemoryAllocator, - swapchain_format: Format, - depth_format: Format, - ) -> Result<(), Box>; -} - -pub trait Material { - fn pipeline_type_id() -> TypeId - where - Self: Sized; - - fn load( - &mut self, - device: &Device, - memory_allocator: &StandardMemoryAllocator, - ) -> Result<(), Box>; -} - -pub enum MaterialState { - Loading, - Loaded, -} - -pub enum MaterialError { - PipelineNotFound, -} - -pub struct MaterialManager { - device: Arc, - memory_allocator: Arc, - swapchain_format: Format, - depth_format: Format, - - pipelines_id: Arc>>, - pipelines_state: Arc>>>>, - pipelines: Arc>>>>, - - materials_id: Arc>>, - materials_pipeline_id: Arc>>, - materials_pipeline: Arc>>>>, - materials_pipeline_state: Arc>>>>, - materials_state: Arc>>>>, - materials: Arc>>>>, -} - -impl MaterialManager { - pub fn new( - device: Arc, - memory_allocator: Arc, - swapchain_format: Format, - depth_format: Format, - ) -> Self { - Self { - device, - memory_allocator, - swapchain_format, - depth_format, - pipelines_id: Arc::new(RwLock::new(Vec::new())), - pipelines_state: Arc::new(RwLock::new(Vec::new())), - pipelines: Arc::new(RwLock::new(Vec::new())), - materials_id: Arc::new(RwLock::new(Vec::new())), - materials_pipeline_id: Arc::new(RwLock::new(Vec::new())), - materials_pipeline: Arc::new(RwLock::new(Vec::new())), - materials_pipeline_state: Arc::new(RwLock::new(Vec::new())), - materials_state: Arc::new(RwLock::new(Vec::new())), - materials: Arc::new(RwLock::new(Vec::new())), - } - } - - pub fn add_pipeline(&self) { - let type_id = TypeId::of::

    (); - let pipeline = Arc::new(RwLock::new(P::default())); - - let mut pipelines_id = self.pipelines_id.write().unwrap(); - let mut pipelines_state = self.pipelines_state.write().unwrap(); - let mut pipelines = self.pipelines.write().unwrap(); - - pipelines_id.push(type_id); - pipelines_state.push(Arc::new(RwLock::new(MaterialState::Loading))); - pipelines.push(pipeline.clone()); - } - - pub fn add_material(&self) -> Result<(), MaterialError> { - let pipeline_id = M::pipeline_type_id(); - - let pipeline_result = { - let pipelines_id = self.pipelines_id.read().unwrap(); - let pipelines_state = self.pipelines_state.read().unwrap(); - let pipelines = self.pipelines.read().unwrap(); - - pipelines_id - .iter() - .zip(pipelines.iter()) - .zip(pipelines_state.iter()) - .find(|((id, _), _)| *id == &pipeline_id) - .map(|((_, pipeline), state)| (pipeline.clone(), state.clone())) - }; - - let (pipeline, pipeline_state) = match pipeline_result { - Some(pipeline) => pipeline, - None => { - tracing::error!( - "Pipeline with id {pipeline_id:?} not found, please add it before adding a material" - ); - return Err(MaterialError::PipelineNotFound); - } - }; - - let type_id = TypeId::of::(); - - let mut materials_id = self.materials_id.write().unwrap(); - let mut materials_pipeline_id = self.materials_pipeline_id.write().unwrap(); - let mut materials_pipeline = self.materials_pipeline.write().unwrap(); - let mut materials_pipeline_state = self.materials_pipeline_state.write().unwrap(); - let mut materials_state = self.materials_state.write().unwrap(); - let mut materials = self.materials.write().unwrap(); - - materials_id.push(type_id); - materials_pipeline_id.push(pipeline_id); - materials_pipeline.push(pipeline); - materials_pipeline_state.push(pipeline_state); - materials_state.push(Arc::new(RwLock::new(MaterialState::Loading))); - materials.push(Arc::new(RwLock::new(M::default()))); - - Ok(()) - } - - pub fn update_swapchain_format(&mut self, swapchain_format: Format) { - if self.swapchain_format == swapchain_format { - return; - } - - self.swapchain_format = swapchain_format; - self.mark_all_pipelines_as_loading(); - } - - fn mark_all_pipelines_as_loading(&self) { - let pipelines_state = self.pipelines_state.write().unwrap(); - - for state in pipelines_state.iter() { - let mut state = state.write().unwrap(); - *state = MaterialState::Loading; - } - } - - fn load_pipelines(&self) { - let pipelines_state = self.pipelines_state.read().unwrap(); - let pipelines = self.pipelines.read().unwrap(); - - let iter = pipelines_state - .iter() - .zip(pipelines.iter()) - .filter(|(state, _)| { - let state = state.read().unwrap(); - matches!(*state, MaterialState::Loading) - }); - - for (state, pipeline) in iter { - let mut pipeline = pipeline.write().unwrap(); - let result = pipeline.load( - &self.device, - &self.memory_allocator, - self.swapchain_format, - self.depth_format, - ); - - match result { - Ok(_) => { - let mut state = state.write().unwrap(); - *state = MaterialState::Loaded; - } - Err(e) => { - tracing::error!("Failed to load pipeline: {e}"); - } - } - } - } - - fn load_materials(&self) { - let materials_state = self.materials_state.read().unwrap(); - let materials = self.materials.read().unwrap(); - - let iter = materials_state - .iter() - .zip(materials.iter()) - .filter(|(state, _)| { - let state = state.read().unwrap(); - matches!(*state, MaterialState::Loading) - }); - - for (state, material) in iter { - let mut material = material.write().unwrap(); - let result = material.load(&self.device, &self.memory_allocator); - - match result { - Ok(_) => { - let mut state = state.write().unwrap(); - *state = MaterialState::Loaded; - } - Err(e) => { - tracing::error!("Failed to load material: {e}"); - } - } - } - } - - fn render_materials(&self, f: F) - where - F: Fn(RwLockReadGuard<'_, dyn Material>, RwLockReadGuard<'_, dyn Pipeline>), - { - let materials = self.materials.read().unwrap(); - let materials_state = self.materials_state.read().unwrap(); - let materials_pipeline = self.materials_pipeline.read().unwrap(); - let materials_pipeline_state = self.materials_pipeline_state.read().unwrap(); - - materials - .iter() - .zip(materials_state.iter()) - .zip(materials_pipeline.iter()) - .zip(materials_pipeline_state.iter()) - .filter(|(((_, material_state), _), pipeline_state)| { - let material_state = material_state.read().unwrap(); - let pipeline_state = pipeline_state.read().unwrap(); - matches!(*material_state, MaterialState::Loaded) - && matches!(*pipeline_state, MaterialState::Loaded) - }) - .for_each(|(((material, _), pipeline), _)| { - let material = material.read().unwrap(); - let pipeline = pipeline.read().unwrap(); - - f(material, pipeline); - }); - } -} diff --git a/src/core/render/mod.rs b/src/core/render/mod.rs index e0de60f..d3d0d42 100644 --- a/src/core/render/mod.rs +++ b/src/core/render/mod.rs @@ -1,4 +1,3 @@ -pub mod material_manager; pub mod primitives; pub mod render_pass_manager; pub mod resources; diff --git a/src/core/render/primitives/mod.rs b/src/core/render/primitives/mod.rs index 26810d6..6f795e4 100644 --- a/src/core/render/primitives/mod.rs +++ b/src/core/render/primitives/mod.rs @@ -4,7 +4,6 @@ mod descriptor_set; pub mod camera; pub mod mvp; -pub mod resource; pub mod transform; pub mod vertex; pub use buffer::{AsBindableBuffer, AsIndexBuffer, AsUniformBuffer, AsVertexBuffer}; diff --git a/src/core/render/primitives/resource.rs b/src/core/render/primitives/resource.rs deleted file mode 100644 index 7ea75e7..0000000 --- a/src/core/render/primitives/resource.rs +++ /dev/null @@ -1,208 +0,0 @@ -use std::{ - collections::HashMap, - error::Error, - fmt::{self, Debug, Display}, - hash::Hash, - sync::Arc, -}; - -#[derive(Debug, Clone)] -pub struct ResourceLoadError { - pub message: String, -} - -impl Display for ResourceLoadError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "Resource load error: {}", self.message) - } -} - -impl Error for ResourceLoadError {} - -impl ResourceLoadError { - pub fn new(message: impl Into) -> Self { - Self { - message: message.into(), - } - } -} - -pub trait AsResourceManager -where - THandle: Clone + Eq + Hash + Debug, -{ - /// Type d'erreur lors du chargement - type LoadError: Error; - - /// Charge une ressource avec l'handle donné - fn load(&mut self, handle: THandle, resource: TResource) -> Result<(), Self::LoadError>; - - /// Récupère une ressource par son handle - fn get(&self, handle: &THandle) -> Option<&TResource>; - - /// Récupère une ressource mutable par son handle - fn get_mut(&mut self, handle: &THandle) -> Option<&mut TResource>; - - /// Supprime une ressource - fn unload(&mut self, handle: &THandle) -> Option; - - /// Vérifie si une ressource est chargée - fn is_loaded(&self, handle: &THandle) -> bool; - - /// Retourne tous les handles chargés - fn loaded_handles(&self) -> Vec; - - /// Nettoie toutes les ressources - fn clear(&mut self); -} - -/// Implémentation basique d'un gestionnaire de ressources -pub struct BasicResourceManager -where - THandle: Clone + Eq + Hash + Debug, -{ - resources: HashMap, -} - -impl BasicResourceManager -where - THandle: Clone + Eq + Hash + Debug, -{ - pub fn new() -> Self { - Self { - resources: HashMap::new(), - } - } - - pub fn with_capacity(capacity: usize) -> Self { - Self { - resources: HashMap::with_capacity(capacity), - } - } -} - -impl Default for BasicResourceManager -where - THandle: Clone + Eq + Hash + Debug, -{ - fn default() -> Self { - Self::new() - } -} - -impl AsResourceManager - for BasicResourceManager -where - THandle: Clone + Eq + Hash + Debug, -{ - type LoadError = ResourceLoadError; - - fn load(&mut self, handle: THandle, resource: TResource) -> Result<(), Self::LoadError> { - self.resources.insert(handle, resource); - Ok(()) - } - - fn get(&self, handle: &THandle) -> Option<&TResource> { - self.resources.get(handle) - } - - fn get_mut(&mut self, handle: &THandle) -> Option<&mut TResource> { - self.resources.get_mut(handle) - } - - fn unload(&mut self, handle: &THandle) -> Option { - self.resources.remove(handle) - } - - fn is_loaded(&self, handle: &THandle) -> bool { - self.resources.contains_key(handle) - } - - fn loaded_handles(&self) -> Vec { - self.resources.keys().cloned().collect() - } - - fn clear(&mut self) { - self.resources.clear(); - } -} - -pub struct ThreadSafeResourceManager -where - THandle: Clone + Eq + Hash + Debug, -{ - resources: Arc>>>, -} - -impl ThreadSafeResourceManager -where - THandle: Clone + Eq + Hash + Debug, -{ - pub fn new() -> Self { - Self { - resources: Arc::new(std::sync::RwLock::new(HashMap::new())), - } - } - - pub fn load(&self, handle: THandle, resource: TResource) -> Result<(), ResourceLoadError> { - let mut resources = self - .resources - .write() - .map_err(|_| ResourceLoadError::new("Failed to acquire write lock"))?; - resources.insert(handle, Arc::new(resource)); - Ok(()) - } - - pub fn get(&self, handle: &THandle) -> Option> { - let resources = self.resources.read().ok()?; - resources.get(handle).cloned() - } - - pub fn unload(&self, handle: &THandle) -> Option> { - let mut resources = self.resources.write().ok()?; - resources.remove(handle) - } - - pub fn is_loaded(&self, handle: &THandle) -> bool { - if let Ok(resources) = self.resources.read() { - resources.contains_key(handle) - } else { - false - } - } - - pub fn loaded_handles(&self) -> Vec { - if let Ok(resources) = self.resources.read() { - resources.keys().cloned().collect() - } else { - Vec::new() - } - } - - pub fn clear(&self) { - if let Ok(mut resources) = self.resources.write() { - resources.clear(); - } - } -} - -impl Clone for ThreadSafeResourceManager -where - THandle: Clone + Eq + Hash + Debug, -{ - fn clone(&self) -> Self { - Self { - resources: Arc::clone(&self.resources), - } - } -} - -pub trait AsAsyncLoadable -where - THandle: Clone + Eq + Hash + Debug + Send + Sync, -{ - type Resource: Send + Sync; - type LoadError: Error + Send + Sync; - - async fn load_async(handle: THandle) -> Result; -} From 50aabaa6ee792db1e056556f6e7e1a3aed36b071 Mon Sep 17 00:00:00 2001 From: Florian RICHER Date: Mon, 9 Jun 2025 16:26:14 +0200 Subject: [PATCH 086/105] Remove all generics in AsRecordable --- src/core/render/primitives/command.rs | 33 +++++++++++----------- src/core/render/primitives/transform.rs | 6 ++-- src/core/render/resources/meshes/obj.rs | 9 ++++-- src/core/render/resources/meshes/square.rs | 9 ++++-- src/game/assets/pipelines/simple.rs | 9 +++--- 5 files changed, 36 insertions(+), 30 deletions(-) diff --git a/src/core/render/primitives/command.rs b/src/core/render/primitives/command.rs index 6fdca2c..16967d7 100644 --- a/src/core/render/primitives/command.rs +++ b/src/core/render/primitives/command.rs @@ -1,7 +1,7 @@ use std::{error::Error, sync::Arc}; use vulkano::{ - buffer::{IndexBuffer, Subbuffer}, + buffer::{BufferContents, IndexBuffer, Subbuffer}, command_buffer::{AutoCommandBufferBuilder, PrimaryAutoCommandBuffer}, descriptor_set::allocator::StandardDescriptorSetAllocator, pipeline::GraphicsPipeline, @@ -9,23 +9,20 @@ use vulkano::{ use super::AsDescriptorSet; -pub trait AsRecordable -where - MI: Into + Clone, -{ +pub trait AsRecordable { fn record_bind_commands( builder: &mut AutoCommandBufferBuilder, descriptor_set_allocator: &Arc, pipeline: &Arc, - mesh: &impl AsRenderableMesh, - instances: &impl AsRenderableMeshInstance, + mesh: &impl AsRenderableMesh, + instances: &impl AsRenderableMeshInstance, descriptor_sets: Vec>, ) -> Result<(), Box>; fn record_draw_commands( builder: &mut AutoCommandBufferBuilder, - mesh: &impl AsRenderableMesh, - instances: &impl AsRenderableMeshInstance, + mesh: &impl AsRenderableMesh, + instances: &impl AsRenderableMeshInstance, ) -> Result<(), Box> { match mesh.index_buffer() { Some(index_buffer) => { @@ -54,11 +51,11 @@ where } } -pub trait AsRenderableMesh -where - I: Into + Clone, -{ - fn vertex_buffer(&self) -> &Subbuffer<[V]>; +pub trait AsRenderableMesh { + type VertexBufferContentsType: BufferContents + Clone; + type IndexBufferType: Into + Clone; + + fn vertex_buffer(&self) -> &Subbuffer<[Self::VertexBufferContentsType]>; fn vertex_count(&self) -> u32; @@ -70,7 +67,7 @@ where 0 } - fn index_buffer(&self) -> Option<&I> { + fn index_buffer(&self) -> Option<&Self::IndexBufferType> { None } @@ -83,8 +80,10 @@ where } } -pub trait AsRenderableMeshInstance { - fn instance_buffer(&self) -> &Subbuffer<[T]>; +pub trait AsRenderableMeshInstance { + type InstanceBufferContents: BufferContents + Clone; + + fn instance_buffer(&self) -> &Subbuffer<[Self::InstanceBufferContents]>; fn instance_count(&self) -> u32; diff --git a/src/core/render/primitives/transform.rs b/src/core/render/primitives/transform.rs index 27f24f4..2645ad9 100644 --- a/src/core/render/primitives/transform.rs +++ b/src/core/render/primitives/transform.rs @@ -164,8 +164,10 @@ impl AsBindableBuffer for TransformRaw { impl AsVertexBuffer for TransformRaw {} -impl AsRenderableMeshInstance for Subbuffer<[TransformRaw]> { - fn instance_buffer(&self) -> &Subbuffer<[TransformRaw]> { +impl AsRenderableMeshInstance for Subbuffer<[TransformRaw]> { + type InstanceBufferContents = TransformRaw; + + fn instance_buffer(&self) -> &Subbuffer<[Self::InstanceBufferContents]> { self } diff --git a/src/core/render/resources/meshes/obj.rs b/src/core/render/resources/meshes/obj.rs index d699d10..fe40db8 100644 --- a/src/core/render/resources/meshes/obj.rs +++ b/src/core/render/resources/meshes/obj.rs @@ -57,12 +57,15 @@ impl ObjMesh { } } -impl AsRenderableMesh> for ObjMesh { - fn vertex_buffer(&self) -> &Subbuffer<[Vertex3D]> { +impl AsRenderableMesh for ObjMesh { + type VertexBufferContentsType = Vertex3D; + type IndexBufferType = Subbuffer<[u32]>; + + fn vertex_buffer(&self) -> &Subbuffer<[Self::VertexBufferContentsType]> { &self.vertex_buffer } - fn index_buffer(&self) -> Option<&Subbuffer<[u32]>> { + fn index_buffer(&self) -> Option<&Self::IndexBufferType> { Some(&self.index_buffer) } diff --git a/src/core/render/resources/meshes/square.rs b/src/core/render/resources/meshes/square.rs index 087b1a9..07b046c 100644 --- a/src/core/render/resources/meshes/square.rs +++ b/src/core/render/resources/meshes/square.rs @@ -44,12 +44,15 @@ impl SquareMesh { } } -impl AsRenderableMesh> for SquareMesh { - fn vertex_buffer(&self) -> &Subbuffer<[Vertex3D]> { +impl AsRenderableMesh for SquareMesh { + type VertexBufferContentsType = Vertex3D; + type IndexBufferType = Subbuffer<[u32]>; + + fn vertex_buffer(&self) -> &Subbuffer<[Self::VertexBufferContentsType]> { &self.vertex_buffer } - fn index_buffer(&self) -> Option<&Subbuffer<[u32]>> { + fn index_buffer(&self) -> Option<&Self::IndexBufferType> { Some(&self.index_buffer) } diff --git a/src/game/assets/pipelines/simple.rs b/src/game/assets/pipelines/simple.rs index e003276..8336a3d 100644 --- a/src/game/assets/pipelines/simple.rs +++ b/src/game/assets/pipelines/simple.rs @@ -1,7 +1,6 @@ -use std::{collections::HashMap, error::Error, sync::Arc}; +use std::{error::Error, sync::Arc}; use vulkano::{ - buffer::Subbuffer, command_buffer::{AutoCommandBufferBuilder, PrimaryAutoCommandBuffer}, descriptor_set::{ allocator::StandardDescriptorSetAllocator, layout::DescriptorSetLayoutCreateInfo, @@ -120,13 +119,13 @@ impl SimplePipeline { } } -impl AsRecordable, TransformRaw> for SimplePipeline { +impl AsRecordable for SimplePipeline { fn record_bind_commands( builder: &mut AutoCommandBufferBuilder, descriptor_set_allocator: &Arc, pipeline: &Arc, - mesh: &impl AsRenderableMesh>, - instances: &impl AsRenderableMeshInstance, + mesh: &impl AsRenderableMesh, + instances: &impl AsRenderableMeshInstance, descriptor_sets: Vec>, ) -> Result<(), Box> { builder.bind_pipeline_graphics(pipeline.clone())?; From cc64efd96f9f8d692ea6ce05a14fd1984d878414 Mon Sep 17 00:00:00 2001 From: Florian RICHER Date: Mon, 9 Jun 2025 16:32:58 +0200 Subject: [PATCH 087/105] Remove useless generics in buffer --- src/core/render/primitives/buffer.rs | 41 +++++++++++++------------ src/core/render/primitives/camera.rs | 2 +- src/core/render/primitives/mvp.rs | 10 +++--- src/core/render/primitives/transform.rs | 8 ++--- src/core/render/primitives/vertex.rs | 24 +++++++-------- 5 files changed, 43 insertions(+), 42 deletions(-) diff --git a/src/core/render/primitives/buffer.rs b/src/core/render/primitives/buffer.rs index e01226f..33284e7 100644 --- a/src/core/render/primitives/buffer.rs +++ b/src/core/render/primitives/buffer.rs @@ -6,55 +6,53 @@ use vulkano::{ memory::allocator::{AllocationCreateInfo, StandardMemoryAllocator}, }; -pub trait AsBindableBuffer { +pub trait AsBindableBuffer { type BufferData: BufferContents + Clone; fn buffer_create_info() -> BufferCreateInfo; fn allocation_create_info() -> AllocationCreateInfo; - fn to_buffer_data(data: &T) -> Self::BufferData; + fn to_buffer_data(&self) -> Self::BufferData; fn create_buffer( + &self, memory_allocator: &Arc, - data: &T, ) -> Result, Validated> { - let buffer_data = Self::to_buffer_data(data); Buffer::from_iter( memory_allocator.clone(), Self::buffer_create_info(), Self::allocation_create_info(), - [buffer_data], + [self.to_buffer_data()], ) } - fn update_buffer( - buffer: &Subbuffer<[Self::BufferData]>, - data: &T, - ) -> Result<(), Box> { - let buffer_data = Self::to_buffer_data(data); + fn update_buffer(&self, buffer: &Subbuffer<[Self::BufferData]>) -> Result<(), Box> { let mut write_guard = buffer.write()?; - write_guard[0] = buffer_data; + write_guard[0] = self.to_buffer_data(); Ok(()) } } -pub trait AsUniformBuffer: AsBindableBuffer { +pub trait AsUniformBuffer: AsBindableBuffer { fn create_uniform_buffer( + &self, memory_allocator: &Arc, - data: &T, ) -> Result, Validated> { - Self::create_buffer(memory_allocator, data) + self.create_buffer(memory_allocator) } } -pub trait AsVertexBuffer: AsBindableBuffer { +pub trait AsVertexBuffer: AsBindableBuffer +where + Self: Sized, +{ fn create_vertex_buffer( memory_allocator: &Arc, - vertices: &[T], + vertices: &[Self], ) -> Result, Validated> { let buffer_data: Vec = - vertices.iter().map(|v| Self::to_buffer_data(v)).collect(); + vertices.iter().map(|v| v.to_buffer_data()).collect(); Buffer::from_iter( memory_allocator.clone(), @@ -65,13 +63,16 @@ pub trait AsVertexBuffer: AsBindableBuffer { } } -pub trait AsIndexBuffer: AsBindableBuffer { +pub trait AsIndexBuffer: AsBindableBuffer +where + Self: Sized, +{ fn create_index_buffer( memory_allocator: &Arc, - indices: &[T], + indices: &[Self], ) -> Result, Validated> { let buffer_data: Vec = - indices.iter().map(|i| Self::to_buffer_data(i)).collect(); + indices.iter().map(|i| i.to_buffer_data()).collect(); Buffer::from_iter( memory_allocator.clone(), diff --git a/src/core/render/primitives/camera.rs b/src/core/render/primitives/camera.rs index 4fdf1ae..1841079 100644 --- a/src/core/render/primitives/camera.rs +++ b/src/core/render/primitives/camera.rs @@ -118,6 +118,6 @@ impl Camera3D { projection: self.projection.to_cols_array_2d(), }; - Mvp::create_uniform_buffer(memory_allocator, &mvp) + mvp.create_uniform_buffer(memory_allocator) } } diff --git a/src/core/render/primitives/mvp.rs b/src/core/render/primitives/mvp.rs index 09c8673..6cf1362 100644 --- a/src/core/render/primitives/mvp.rs +++ b/src/core/render/primitives/mvp.rs @@ -30,11 +30,11 @@ impl Mvp { self, memory_allocator: &Arc, ) -> Result, Validated> { - Self::create_uniform_buffer(memory_allocator, &self) + self.create_uniform_buffer(memory_allocator) } } -impl AsBindableBuffer for Mvp { +impl AsBindableBuffer for Mvp { type BufferData = Mvp; fn buffer_create_info() -> BufferCreateInfo { @@ -52,12 +52,12 @@ impl AsBindableBuffer for Mvp { } } - fn to_buffer_data(data: &Mvp) -> Self::BufferData { - *data + fn to_buffer_data(&self) -> Self::BufferData { + *self } } -impl AsUniformBuffer for Mvp {} +impl AsUniformBuffer for Mvp {} impl AsDescriptorSetLayoutBindings for Mvp { fn as_descriptor_set_layout_bindings() -> BTreeMap { diff --git a/src/core/render/primitives/transform.rs b/src/core/render/primitives/transform.rs index 2645ad9..8818fd3 100644 --- a/src/core/render/primitives/transform.rs +++ b/src/core/render/primitives/transform.rs @@ -139,7 +139,7 @@ impl TransformRaw { } } -impl AsBindableBuffer for TransformRaw { +impl AsBindableBuffer for TransformRaw { type BufferData = TransformRaw; fn buffer_create_info() -> BufferCreateInfo { @@ -157,12 +157,12 @@ impl AsBindableBuffer for TransformRaw { } } - fn to_buffer_data(data: &TransformRaw) -> Self::BufferData { - *data + fn to_buffer_data(&self) -> Self::BufferData { + *self } } -impl AsVertexBuffer for TransformRaw {} +impl AsVertexBuffer for TransformRaw {} impl AsRenderableMeshInstance for Subbuffer<[TransformRaw]> { type InstanceBufferContents = TransformRaw; diff --git a/src/core/render/primitives/vertex.rs b/src/core/render/primitives/vertex.rs index 2a27b36..14571c6 100644 --- a/src/core/render/primitives/vertex.rs +++ b/src/core/render/primitives/vertex.rs @@ -25,7 +25,7 @@ pub struct Vertex3D { pub uv: [f32; 2], } -impl AsBindableBuffer for Vertex2D { +impl AsBindableBuffer for Vertex2D { type BufferData = Vertex2D; fn buffer_create_info() -> BufferCreateInfo { @@ -43,14 +43,14 @@ impl AsBindableBuffer for Vertex2D { } } - fn to_buffer_data(data: &Vertex2D) -> Self::BufferData { - *data + fn to_buffer_data(&self) -> Self::BufferData { + *self } } -impl AsVertexBuffer for Vertex2D {} +impl AsVertexBuffer for Vertex2D {} -impl AsBindableBuffer for Vertex3D { +impl AsBindableBuffer for Vertex3D { type BufferData = Vertex3D; fn buffer_create_info() -> BufferCreateInfo { @@ -68,14 +68,14 @@ impl AsBindableBuffer for Vertex3D { } } - fn to_buffer_data(data: &Vertex3D) -> Self::BufferData { - *data + fn to_buffer_data(&self) -> Self::BufferData { + *self } } -impl AsVertexBuffer for Vertex3D {} +impl AsVertexBuffer for Vertex3D {} -impl AsBindableBuffer for u32 { +impl AsBindableBuffer for u32 { type BufferData = u32; fn buffer_create_info() -> BufferCreateInfo { @@ -93,9 +93,9 @@ impl AsBindableBuffer for u32 { } } - fn to_buffer_data(data: &u32) -> Self::BufferData { - *data + fn to_buffer_data(&self) -> Self::BufferData { + *self } } -impl AsIndexBuffer for u32 {} +impl AsIndexBuffer for u32 {} From c2b9c2363b017434aa6f3a9ab2b8234dd192fcd0 Mon Sep 17 00:00:00 2001 From: Florian RICHER Date: Mon, 9 Jun 2025 16:51:24 +0200 Subject: [PATCH 088/105] Avoid suffixe type by Type --- src/core/render/primitives/command.rs | 8 ++++---- src/core/render/resources/meshes/obj.rs | 8 ++++---- src/core/render/resources/meshes/square.rs | 8 ++++---- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/core/render/primitives/command.rs b/src/core/render/primitives/command.rs index 16967d7..3adc63c 100644 --- a/src/core/render/primitives/command.rs +++ b/src/core/render/primitives/command.rs @@ -52,10 +52,10 @@ pub trait AsRecordable { } pub trait AsRenderableMesh { - type VertexBufferContentsType: BufferContents + Clone; - type IndexBufferType: Into + Clone; + type VertexBufferContents: BufferContents + Clone; + type IndexBuffer: Into + Clone; - fn vertex_buffer(&self) -> &Subbuffer<[Self::VertexBufferContentsType]>; + fn vertex_buffer(&self) -> &Subbuffer<[Self::VertexBufferContents]>; fn vertex_count(&self) -> u32; @@ -67,7 +67,7 @@ pub trait AsRenderableMesh { 0 } - fn index_buffer(&self) -> Option<&Self::IndexBufferType> { + fn index_buffer(&self) -> Option<&Self::IndexBuffer> { None } diff --git a/src/core/render/resources/meshes/obj.rs b/src/core/render/resources/meshes/obj.rs index fe40db8..932ff17 100644 --- a/src/core/render/resources/meshes/obj.rs +++ b/src/core/render/resources/meshes/obj.rs @@ -58,14 +58,14 @@ impl ObjMesh { } impl AsRenderableMesh for ObjMesh { - type VertexBufferContentsType = Vertex3D; - type IndexBufferType = Subbuffer<[u32]>; + type VertexBufferContents = Vertex3D; + type IndexBuffer = Subbuffer<[u32]>; - fn vertex_buffer(&self) -> &Subbuffer<[Self::VertexBufferContentsType]> { + fn vertex_buffer(&self) -> &Subbuffer<[Self::VertexBufferContents]> { &self.vertex_buffer } - fn index_buffer(&self) -> Option<&Self::IndexBufferType> { + fn index_buffer(&self) -> Option<&Self::IndexBuffer> { Some(&self.index_buffer) } diff --git a/src/core/render/resources/meshes/square.rs b/src/core/render/resources/meshes/square.rs index 07b046c..6989eb4 100644 --- a/src/core/render/resources/meshes/square.rs +++ b/src/core/render/resources/meshes/square.rs @@ -45,14 +45,14 @@ impl SquareMesh { } impl AsRenderableMesh for SquareMesh { - type VertexBufferContentsType = Vertex3D; - type IndexBufferType = Subbuffer<[u32]>; + type VertexBufferContents = Vertex3D; + type IndexBuffer = Subbuffer<[u32]>; - fn vertex_buffer(&self) -> &Subbuffer<[Self::VertexBufferContentsType]> { + fn vertex_buffer(&self) -> &Subbuffer<[Self::VertexBufferContents]> { &self.vertex_buffer } - fn index_buffer(&self) -> Option<&Self::IndexBufferType> { + fn index_buffer(&self) -> Option<&Self::IndexBuffer> { Some(&self.index_buffer) } From 8b982ba0895b92d5a305eae08fc2e3fe63bf0bbf Mon Sep 17 00:00:00 2001 From: Florian RICHER Date: Mon, 9 Jun 2025 16:53:59 +0200 Subject: [PATCH 089/105] Cleanup --- src/core/render/primitives/command.rs | 8 ++++---- src/core/render/primitives/transform.rs | 24 ++-------------------- src/core/render/resources/meshes/obj.rs | 4 ++-- src/core/render/resources/meshes/square.rs | 4 ++-- 4 files changed, 10 insertions(+), 30 deletions(-) diff --git a/src/core/render/primitives/command.rs b/src/core/render/primitives/command.rs index 3adc63c..877d55f 100644 --- a/src/core/render/primitives/command.rs +++ b/src/core/render/primitives/command.rs @@ -52,10 +52,10 @@ pub trait AsRecordable { } pub trait AsRenderableMesh { - type VertexBufferContents: BufferContents + Clone; + type VertexBufferData: BufferContents + Clone; type IndexBuffer: Into + Clone; - fn vertex_buffer(&self) -> &Subbuffer<[Self::VertexBufferContents]>; + fn vertex_buffer(&self) -> &Subbuffer<[Self::VertexBufferData]>; fn vertex_count(&self) -> u32; @@ -81,9 +81,9 @@ pub trait AsRenderableMesh { } pub trait AsRenderableMeshInstance { - type InstanceBufferContents: BufferContents + Clone; + type InstanceBufferData: BufferContents + Clone; - fn instance_buffer(&self) -> &Subbuffer<[Self::InstanceBufferContents]>; + fn instance_buffer(&self) -> &Subbuffer<[Self::InstanceBufferData]>; fn instance_count(&self) -> u32; diff --git a/src/core/render/primitives/transform.rs b/src/core/render/primitives/transform.rs index 8818fd3..506158c 100644 --- a/src/core/render/primitives/transform.rs +++ b/src/core/render/primitives/transform.rs @@ -119,26 +119,6 @@ impl From<&Transform> for TransformRaw { } } -impl From for TransformRaw { - fn from(matrix: Mat4) -> Self { - Self { - model: matrix.to_cols_array_2d(), - } - } -} - -impl TransformRaw { - pub fn from_matrix(matrix: Mat4) -> Self { - Self { - model: matrix.to_cols_array_2d(), - } - } - - pub fn to_matrix(&self) -> Mat4 { - Mat4::from_cols_array_2d(&self.model) - } -} - impl AsBindableBuffer for TransformRaw { type BufferData = TransformRaw; @@ -165,9 +145,9 @@ impl AsBindableBuffer for TransformRaw { impl AsVertexBuffer for TransformRaw {} impl AsRenderableMeshInstance for Subbuffer<[TransformRaw]> { - type InstanceBufferContents = TransformRaw; + type InstanceBufferData = TransformRaw; - fn instance_buffer(&self) -> &Subbuffer<[Self::InstanceBufferContents]> { + fn instance_buffer(&self) -> &Subbuffer<[Self::InstanceBufferData]> { self } diff --git a/src/core/render/resources/meshes/obj.rs b/src/core/render/resources/meshes/obj.rs index 932ff17..7ca648f 100644 --- a/src/core/render/resources/meshes/obj.rs +++ b/src/core/render/resources/meshes/obj.rs @@ -58,10 +58,10 @@ impl ObjMesh { } impl AsRenderableMesh for ObjMesh { - type VertexBufferContents = Vertex3D; + type VertexBufferData = Vertex3D; type IndexBuffer = Subbuffer<[u32]>; - fn vertex_buffer(&self) -> &Subbuffer<[Self::VertexBufferContents]> { + fn vertex_buffer(&self) -> &Subbuffer<[Self::VertexBufferData]> { &self.vertex_buffer } diff --git a/src/core/render/resources/meshes/square.rs b/src/core/render/resources/meshes/square.rs index 6989eb4..86f8cf9 100644 --- a/src/core/render/resources/meshes/square.rs +++ b/src/core/render/resources/meshes/square.rs @@ -45,10 +45,10 @@ impl SquareMesh { } impl AsRenderableMesh for SquareMesh { - type VertexBufferContents = Vertex3D; + type VertexBufferData = Vertex3D; type IndexBuffer = Subbuffer<[u32]>; - fn vertex_buffer(&self) -> &Subbuffer<[Self::VertexBufferContents]> { + fn vertex_buffer(&self) -> &Subbuffer<[Self::VertexBufferData]> { &self.vertex_buffer } From 6099a3e27fc8bdeca0b1ec851d713af121dddf8c Mon Sep 17 00:00:00 2001 From: Florian RICHER Date: Mon, 9 Jun 2025 20:42:35 +0200 Subject: [PATCH 090/105] Add pipeline loader --- src/core/render/resources/mod.rs | 1 + src/core/render/resources/pipeline/loader.rs | 112 +++++++++++++++++++ src/core/render/resources/pipeline/mod.rs | 2 + src/game/assets/pipelines/simple.rs | 17 ++- src/game/scenes/main_scene.rs | 100 ++++++++++------- 5 files changed, 179 insertions(+), 53 deletions(-) create mode 100644 src/core/render/resources/pipeline/loader.rs create mode 100644 src/core/render/resources/pipeline/mod.rs diff --git a/src/core/render/resources/mod.rs b/src/core/render/resources/mod.rs index 26807af..d900da7 100644 --- a/src/core/render/resources/mod.rs +++ b/src/core/render/resources/mod.rs @@ -1,2 +1,3 @@ pub mod meshes; +pub mod pipeline; pub mod texture; diff --git a/src/core/render/resources/pipeline/loader.rs b/src/core/render/resources/pipeline/loader.rs new file mode 100644 index 0000000..c20a1de --- /dev/null +++ b/src/core/render/resources/pipeline/loader.rs @@ -0,0 +1,112 @@ +use std::{ + any::TypeId, + collections::HashMap, + error::Error, + sync::{Arc, RwLock}, +}; + +use vulkano::{device::Device, format::Format, pipeline::GraphicsPipeline}; + +#[derive(PartialEq, Eq)] +pub enum PipelineState { + NeedBuild, + Loaded, +} + +pub type GraphicsPipelineLoadFn = + fn(&Arc, Format, Format) -> Result, Box>; + +pub struct PipelineLoader { + device: Arc, + swapchain_image_format: Format, + depth_image_format: Format, + + // Arc is used in internal of vulkano. It's not possible to use Arc>> directly. + pipelines_index: HashMap, + pipelines_id: Vec, + pipelines_load_fn: Vec, + // Only content is protected by Arc and RwLock to avoid push in pipeline_loader in multiple threads and just allow to lock each pipeline when is needed as parallel pipelines loading. + // But only the pipeline loader is allowed to load a pipeline when it's needed. + pipelines: Vec>>>>, + pipelines_state: Vec>>, +} + +impl PipelineLoader { + pub fn new( + device: Arc, + swapchain_image_format: Format, + depth_image_format: Format, + ) -> Self { + Self { + device, + swapchain_image_format, + depth_image_format, + pipelines: Vec::new(), + pipelines_load_fn: Vec::new(), + pipelines_id: Vec::new(), + pipelines_state: Vec::new(), + pipelines_index: HashMap::new(), + } + } + + pub fn register( + &mut self, + load_fn: GraphicsPipelineLoadFn, + ) -> Result<(), Box> { + let id = TypeId::of::(); + self.pipelines_index.insert(id, self.pipelines.len()); + self.pipelines_id.push(id); + self.pipelines_load_fn.push(load_fn); + self.pipelines_state + .push(Arc::new(RwLock::new(PipelineState::NeedBuild))); + self.pipelines.push(Arc::new(RwLock::new(None))); + Ok(()) + } + + pub fn load_pipelines(&self) -> Result<(), Box> { + let iter = self + .pipelines_id + .iter() + .zip(self.pipelines.iter()) + .zip(self.pipelines_load_fn.iter()) + .zip(self.pipelines_state.iter()) + .filter(|(_, state)| { + let state = state.read().unwrap(); + *state == PipelineState::NeedBuild + }); + + for (((id, pipeline), load_fn), state) in iter { + let new_pipeline = load_fn( + &self.device, + self.swapchain_image_format, + self.depth_image_format, + )?; + let mut pipeline = pipeline.write().unwrap(); + *pipeline = Some(new_pipeline); + let mut state = state.write().unwrap(); + *state = PipelineState::Loaded; + tracing::trace!("Pipeline {id:?} loaded"); + } + Ok(()) + } + + fn mark_pipelines_as_need_build(&mut self) { + for state in self.pipelines_state.iter() { + let mut state = state.write().unwrap(); + *state = PipelineState::NeedBuild; + } + } + + pub fn with_pipeline(&self, f: F) -> Result<(), Box> + where + F: FnOnce(&Arc) -> Result<(), Box>, + { + let id = TypeId::of::(); + let index = self.pipelines_index.get(&id).ok_or("Pipeline not found")?; + let pipeline_locker = self.pipelines[*index] + .read() + .map_err(|_| "Failed to lock pipeline")?; + let pipeline = pipeline_locker.as_ref().ok_or("Pipeline not loaded")?; + f(pipeline) + } +} diff --git a/src/core/render/resources/pipeline/mod.rs b/src/core/render/resources/pipeline/mod.rs new file mode 100644 index 0000000..3026927 --- /dev/null +++ b/src/core/render/resources/pipeline/mod.rs @@ -0,0 +1,2 @@ +mod loader; +pub use loader::{GraphicsPipelineLoadFn, PipelineLoader}; diff --git a/src/game/assets/pipelines/simple.rs b/src/game/assets/pipelines/simple.rs index 8336a3d..0e9cfcd 100644 --- a/src/game/assets/pipelines/simple.rs +++ b/src/game/assets/pipelines/simple.rs @@ -1,4 +1,7 @@ -use std::{error::Error, sync::Arc}; +use std::{ + error::Error, + sync::{Arc, RwLock}, +}; use vulkano::{ command_buffer::{AutoCommandBufferBuilder, PrimaryAutoCommandBuffer}, @@ -33,16 +36,14 @@ use crate::core::render::{ resources::texture::Texture, }; -pub struct SimplePipeline { - pipeline: Arc, -} +pub struct SimplePipeline; impl SimplePipeline { pub fn new( device: &Arc, swapchain_format: Format, depth_format: Format, - ) -> Result> { + ) -> Result, Box> { let vs = shaders::vs::load(device.clone())? .entry_point("main") .ok_or("Failed find main entry point of vertex shader".to_string())?; @@ -111,11 +112,7 @@ impl SimplePipeline { }, )?; - Ok(Self { pipeline }) - } - - pub fn pipeline(&self) -> &Arc { - &self.pipeline + Ok(pipeline) } } diff --git a/src/game/scenes/main_scene.rs b/src/game/scenes/main_scene.rs index e139e90..4f0b421 100644 --- a/src/game/scenes/main_scene.rs +++ b/src/game/scenes/main_scene.rs @@ -11,6 +11,7 @@ use crate::core::render::primitives::transform::Transform; use crate::core::render::primitives::{AsDescriptorSet, AsRecordable}; use crate::core::render::render_pass_manager::{RenderPassConfig, RenderPassManager}; use crate::core::render::resources::meshes::{ObjMesh, SquareMesh}; +use crate::core::render::resources::pipeline::PipelineLoader; use crate::core::render::resources::texture::{TextureLoadInfo, TextureLoader, TextureSourceKind}; use crate::core::scene::Scene; use crate::game::assets::pipelines::simple::SimplePipeline; @@ -28,9 +29,9 @@ use winit::window::CursorGrabMode; pub struct MainSceneState { texture_loader: TextureLoader, + pipeline_loader: PipelineLoader, square: SquareMesh, obj: ObjMesh, - simple_pipeline: SimplePipeline, square_instances: Vec, obj_instances: Vec, camera: Camera3D, @@ -55,6 +56,14 @@ impl Scene for MainScene { let swapchain_image_view = app_context.with_renderer(|renderer| renderer.swapchain_image_view().clone()); + let mut pipeline_loader = PipelineLoader::new( + app_context.device.clone(), + swapchain_image_view.format(), + depth_image_view.format(), + ); + pipeline_loader.register::(SimplePipeline::new)?; + pipeline_loader.load_pipelines()?; + let mut texture_loader = TextureLoader::new(app_context); texture_loader.add_texture( "wooden-crate".to_string(), @@ -90,11 +99,6 @@ impl Scene for MainScene { let obj = ObjMesh::new(&app_context.memory_allocator, "res/objects/cube.obj")?; obj.into_iter().next().unwrap() }; - let simple_pipeline = SimplePipeline::new( - &app_context.device, - swapchain_image_view.format(), - depth_image_view.format(), - )?; let num_instances = 100; let instance_size = 10.0; @@ -147,7 +151,7 @@ impl Scene for MainScene { self.state = Some(MainSceneState { square, obj, - simple_pipeline, + pipeline_loader, square_instances, obj_instances, camera, @@ -256,43 +260,53 @@ impl Scene for MainScene { let obj_transform_uniform = Transform::create_buffer(&app_context.memory_allocator, &state.obj_instances)?; - SimplePipeline::record_bind_commands( - &mut builder, - &app_context.descriptor_set_allocator, - state.simple_pipeline.pipeline(), - &state.square, - &square_transform_uniform, - vec![ - camera_uniform.clone() as Arc, - state - .texture_loader - .get_texture("wooden-crate") - .unwrap() - .clone(), - ], - )?; - SimplePipeline::record_draw_commands( - &mut builder, - &state.square, - &square_transform_uniform, - )?; + state + .pipeline_loader + .with_pipeline::(|pipeline| { + SimplePipeline::record_bind_commands( + &mut builder, + &app_context.descriptor_set_allocator, + pipeline, + &state.square, + &square_transform_uniform, + vec![ + camera_uniform.clone() as Arc, + state + .texture_loader + .get_texture("wooden-crate") + .unwrap() + .clone(), + ], + )?; + SimplePipeline::record_draw_commands( + &mut builder, + &state.square, + &square_transform_uniform, + )?; - SimplePipeline::record_bind_commands( - &mut builder, - &app_context.descriptor_set_allocator, - state.simple_pipeline.pipeline(), - &state.obj, - &obj_transform_uniform, - vec![ - camera_uniform.clone() as Arc, - state - .texture_loader - .get_texture("cube-diffuse") - .unwrap() - .clone(), - ], - )?; - SimplePipeline::record_draw_commands(&mut builder, &state.obj, &obj_transform_uniform)?; + SimplePipeline::record_bind_commands( + &mut builder, + &app_context.descriptor_set_allocator, + pipeline, + &state.obj, + &obj_transform_uniform, + vec![ + camera_uniform.clone() as Arc, + state + .texture_loader + .get_texture("cube-diffuse") + .unwrap() + .clone(), + ], + )?; + SimplePipeline::record_draw_commands( + &mut builder, + &state.obj, + &obj_transform_uniform, + )?; + + Ok(()) + })?; RenderPassManager::end_rendering(&mut builder)?; From 2300c256037e859ce0918a6eb422406f973e80c8 Mon Sep 17 00:00:00 2001 From: Florian RICHER Date: Mon, 9 Jun 2025 20:53:22 +0200 Subject: [PATCH 091/105] Use trait instead to get load_fn and add name for pipeline --- src/core/render/resources/pipeline/loader.rs | 19 ++++++++++--------- src/core/render/resources/pipeline/mod.rs | 18 +++++++++++++++++- src/game/assets/pipelines/simple.rs | 10 +++++++--- src/game/scenes/main_scene.rs | 2 +- 4 files changed, 35 insertions(+), 14 deletions(-) diff --git a/src/core/render/resources/pipeline/loader.rs b/src/core/render/resources/pipeline/loader.rs index c20a1de..9719c8c 100644 --- a/src/core/render/resources/pipeline/loader.rs +++ b/src/core/render/resources/pipeline/loader.rs @@ -7,15 +7,14 @@ use std::{ use vulkano::{device::Device, format::Format, pipeline::GraphicsPipeline}; +use super::{GraphicsPipelineLoadFn, LoadableGraphicsPipeline}; + #[derive(PartialEq, Eq)] pub enum PipelineState { NeedBuild, Loaded, } -pub type GraphicsPipelineLoadFn = - fn(&Arc, Format, Format) -> Result, Box>; - pub struct PipelineLoader { device: Arc, swapchain_image_format: Format, @@ -29,6 +28,7 @@ pub struct PipelineLoader { // But only the pipeline loader is allowed to load a pipeline when it's needed. pipelines: Vec>>>>, pipelines_state: Vec>>, + pipelines_name: Vec<&'static str>, } impl PipelineLoader { @@ -43,20 +43,21 @@ impl PipelineLoader { depth_image_format, pipelines: Vec::new(), pipelines_load_fn: Vec::new(), + pipelines_name: Vec::new(), pipelines_id: Vec::new(), pipelines_state: Vec::new(), pipelines_index: HashMap::new(), } } - pub fn register( + pub fn register( &mut self, - load_fn: GraphicsPipelineLoadFn, ) -> Result<(), Box> { let id = TypeId::of::(); self.pipelines_index.insert(id, self.pipelines.len()); self.pipelines_id.push(id); - self.pipelines_load_fn.push(load_fn); + self.pipelines_load_fn.push(T::load); + self.pipelines_name.push(T::pipeline_name()); self.pipelines_state .push(Arc::new(RwLock::new(PipelineState::NeedBuild))); self.pipelines.push(Arc::new(RwLock::new(None))); @@ -65,7 +66,7 @@ impl PipelineLoader { pub fn load_pipelines(&self) -> Result<(), Box> { let iter = self - .pipelines_id + .pipelines_name .iter() .zip(self.pipelines.iter()) .zip(self.pipelines_load_fn.iter()) @@ -75,7 +76,7 @@ impl PipelineLoader { *state == PipelineState::NeedBuild }); - for (((id, pipeline), load_fn), state) in iter { + for (((name, pipeline), load_fn), state) in iter { let new_pipeline = load_fn( &self.device, self.swapchain_image_format, @@ -85,7 +86,7 @@ impl PipelineLoader { *pipeline = Some(new_pipeline); let mut state = state.write().unwrap(); *state = PipelineState::Loaded; - tracing::trace!("Pipeline {id:?} loaded"); + tracing::trace!("Pipeline {name} loaded"); } Ok(()) } diff --git a/src/core/render/resources/pipeline/mod.rs b/src/core/render/resources/pipeline/mod.rs index 3026927..9fa9c79 100644 --- a/src/core/render/resources/pipeline/mod.rs +++ b/src/core/render/resources/pipeline/mod.rs @@ -1,2 +1,18 @@ mod loader; -pub use loader::{GraphicsPipelineLoadFn, PipelineLoader}; +use std::{error::Error, sync::Arc}; + +pub use loader::PipelineLoader; +use vulkano::{device::Device, format::Format, pipeline::GraphicsPipeline}; + +type GraphicsPipelineLoadFn = + fn(&Arc, Format, Format) -> Result, Box>; + +pub trait LoadableGraphicsPipeline { + fn load( + device: &Arc, + swapchain_image_format: Format, + depth_image_format: Format, + ) -> Result, Box>; + + fn pipeline_name() -> &'static str; +} diff --git a/src/game/assets/pipelines/simple.rs b/src/game/assets/pipelines/simple.rs index 0e9cfcd..3f3a4db 100644 --- a/src/game/assets/pipelines/simple.rs +++ b/src/game/assets/pipelines/simple.rs @@ -33,13 +33,13 @@ use crate::core::render::{ AsDescriptorSet, AsDescriptorSetLayoutBindings, AsRecordable, AsRenderableMesh, AsRenderableMeshInstance, mvp::Mvp, transform::TransformRaw, vertex::Vertex3D, }, - resources::texture::Texture, + resources::{pipeline::LoadableGraphicsPipeline, texture::Texture}, }; pub struct SimplePipeline; -impl SimplePipeline { - pub fn new( +impl LoadableGraphicsPipeline for SimplePipeline { + fn load( device: &Arc, swapchain_format: Format, depth_format: Format, @@ -114,6 +114,10 @@ impl SimplePipeline { Ok(pipeline) } + + fn pipeline_name() -> &'static str { + "SimplePipeline" + } } impl AsRecordable for SimplePipeline { diff --git a/src/game/scenes/main_scene.rs b/src/game/scenes/main_scene.rs index 4f0b421..67ea2e0 100644 --- a/src/game/scenes/main_scene.rs +++ b/src/game/scenes/main_scene.rs @@ -61,7 +61,7 @@ impl Scene for MainScene { swapchain_image_view.format(), depth_image_view.format(), ); - pipeline_loader.register::(SimplePipeline::new)?; + pipeline_loader.register::()?; pipeline_loader.load_pipelines()?; let mut texture_loader = TextureLoader::new(app_context); From 1f7bfd142c04dca222567bd3e8a03254a3b6e89f Mon Sep 17 00:00:00 2001 From: Florian RICHER Date: Mon, 9 Jun 2025 21:07:17 +0200 Subject: [PATCH 092/105] Add record_commands --- src/core/render/primitives/command.rs | 20 ++++++++++++++++++++ src/game/scenes/main_scene.rs | 14 ++------------ 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/src/core/render/primitives/command.rs b/src/core/render/primitives/command.rs index 877d55f..36a7956 100644 --- a/src/core/render/primitives/command.rs +++ b/src/core/render/primitives/command.rs @@ -49,6 +49,26 @@ pub trait AsRecordable { Ok(()) } + + fn record_commands( + builder: &mut AutoCommandBufferBuilder, + descriptor_set_allocator: &Arc, + pipeline: &Arc, + mesh: &impl AsRenderableMesh, + instances: &impl AsRenderableMeshInstance, + descriptor_sets: Vec>, + ) -> Result<(), Box> { + Self::record_bind_commands( + builder, + descriptor_set_allocator, + pipeline, + mesh, + instances, + descriptor_sets, + )?; + Self::record_draw_commands(builder, mesh, instances)?; + Ok(()) + } } pub trait AsRenderableMesh { diff --git a/src/game/scenes/main_scene.rs b/src/game/scenes/main_scene.rs index 67ea2e0..efacfc5 100644 --- a/src/game/scenes/main_scene.rs +++ b/src/game/scenes/main_scene.rs @@ -263,7 +263,7 @@ impl Scene for MainScene { state .pipeline_loader .with_pipeline::(|pipeline| { - SimplePipeline::record_bind_commands( + SimplePipeline::record_commands( &mut builder, &app_context.descriptor_set_allocator, pipeline, @@ -278,13 +278,8 @@ impl Scene for MainScene { .clone(), ], )?; - SimplePipeline::record_draw_commands( - &mut builder, - &state.square, - &square_transform_uniform, - )?; - SimplePipeline::record_bind_commands( + SimplePipeline::record_commands( &mut builder, &app_context.descriptor_set_allocator, pipeline, @@ -299,11 +294,6 @@ impl Scene for MainScene { .clone(), ], )?; - SimplePipeline::record_draw_commands( - &mut builder, - &state.obj, - &obj_transform_uniform, - )?; Ok(()) })?; From fc81f65a27647c788cc21dc4ab8ad5c450c4066d Mon Sep 17 00:00:00 2001 From: Florian RICHER Date: Mon, 9 Jun 2025 21:21:55 +0200 Subject: [PATCH 093/105] Rename load_pipelines by load_pending_pipelines --- src/core/render/resources/pipeline/loader.rs | 2 +- src/game/scenes/main_scene.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/render/resources/pipeline/loader.rs b/src/core/render/resources/pipeline/loader.rs index 9719c8c..e5e0aeb 100644 --- a/src/core/render/resources/pipeline/loader.rs +++ b/src/core/render/resources/pipeline/loader.rs @@ -64,7 +64,7 @@ impl PipelineLoader { Ok(()) } - pub fn load_pipelines(&self) -> Result<(), Box> { + pub fn load_pending_pipelines(&self) -> Result<(), Box> { let iter = self .pipelines_name .iter() diff --git a/src/game/scenes/main_scene.rs b/src/game/scenes/main_scene.rs index efacfc5..bb29c35 100644 --- a/src/game/scenes/main_scene.rs +++ b/src/game/scenes/main_scene.rs @@ -62,7 +62,7 @@ impl Scene for MainScene { depth_image_view.format(), ); pipeline_loader.register::()?; - pipeline_loader.load_pipelines()?; + pipeline_loader.load_pending_pipelines()?; let mut texture_loader = TextureLoader::new(app_context); texture_loader.add_texture( From 8ce620a74b77eae10abe20453623f2ecb453a8b4 Mon Sep 17 00:00:00 2001 From: Florian RICHER Date: Wed, 11 Jun 2025 16:05:35 +0200 Subject: [PATCH 094/105] Integration of ECS pattern: Iteration 1 --- Cargo.lock | 396 +++++++++++++++++- Cargo.toml | 3 + src/core/app/context.rs | 34 +- src/core/app/mod.rs | 29 +- src/core/render/primitives/mod.rs | 1 + src/core/render/primitives/mvp.rs | 2 +- src/core/render/primitives/vulkan_resource.rs | 79 ++++ src/core/render/resources/texture/loader.rs | 30 +- src/core/scene/manager.rs | 101 ++++- src/core/scene/mod.rs | 16 +- src/game/assets/pipelines/simple.rs | 5 +- src/game/scenes/main_scene.rs | 119 ++++-- src/game/scenes/settings_scene.rs | 47 ++- 13 files changed, 727 insertions(+), 135 deletions(-) create mode 100644 src/core/render/primitives/vulkan_resource.rs diff --git a/Cargo.lock b/Cargo.lock index c35674a..befb3ec 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -149,11 +149,48 @@ dependencies = [ "libloading", ] +[[package]] +name = "assert_type_match" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f548ad2c4031f2902e3edc1f29c29e835829437de49562d8eb5dc5584d3a1043" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "async-executor" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb812ffb58524bdd10860d7d974e2f01cc0950c2438a74ee5ec2e2280c6c4ffa" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "pin-project-lite", + "slab", +] + +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" +dependencies = [ + "portable-atomic", +] + [[package]] name = "atomic-waker" version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" +dependencies = [ + "portable-atomic", +] [[package]] name = "autocfg" @@ -184,6 +221,147 @@ dependencies = [ "arrayvec", ] +[[package]] +name = "bevy_ecs" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c2bf6521aae57a0ec3487c4bfb59e36c4a378e834b626a4bea6a885af2fdfe7" +dependencies = [ + "arrayvec", + "bevy_ecs_macros", + "bevy_platform", + "bevy_ptr", + "bevy_reflect", + "bevy_tasks", + "bevy_utils", + "bitflags 2.9.1", + "bumpalo", + "concurrent-queue", + "derive_more", + "disqualified", + "fixedbitset", + "indexmap", + "log", + "nonmax", + "serde", + "smallvec", + "thiserror 2.0.12", + "variadics_please", +] + +[[package]] +name = "bevy_ecs_macros" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38748d6f3339175c582d751f410fb60a93baf2286c3deb7efebb0878dce7f413" +dependencies = [ + "bevy_macro_utils", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "bevy_macro_utils" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "052eeebcb8e7e072beea5031b227d9a290f8a7fbbb947573ab6ec81df0fb94be" +dependencies = [ + "parking_lot", + "proc-macro2", + "quote", + "syn", + "toml_edit", +] + +[[package]] +name = "bevy_platform" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7573dc824a1b08b4c93fdbe421c53e1e8188e9ca1dd74a414455fe571facb47" +dependencies = [ + "cfg-if", + "critical-section", + "foldhash", + "hashbrown", + "portable-atomic", + "portable-atomic-util", + "serde", + "spin", +] + +[[package]] +name = "bevy_ptr" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df7370d0e46b60e071917711d0860721f5347bc958bf325975ae6913a5dfcf01" + +[[package]] +name = "bevy_reflect" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daeb91a63a1a4df00aa58da8cc4ddbd4b9f16ab8bb647c5553eb156ce36fa8c2" +dependencies = [ + "assert_type_match", + "bevy_platform", + "bevy_ptr", + "bevy_reflect_derive", + "bevy_utils", + "derive_more", + "disqualified", + "downcast-rs 2.0.1", + "erased-serde", + "foldhash", + "glam 0.29.3", + "serde", + "smallvec", + "smol_str", + "thiserror 2.0.12", + "uuid", + "variadics_please", + "wgpu-types", +] + +[[package]] +name = "bevy_reflect_derive" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40ddadc55fe16b45faaa54ab2f9cb00548013c74812e8b018aa172387103cce6" +dependencies = [ + "bevy_macro_utils", + "proc-macro2", + "quote", + "syn", + "uuid", +] + +[[package]] +name = "bevy_tasks" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b674242641cab680688fc3b850243b351c1af49d4f3417a576debd6cca8dcf5" +dependencies = [ + "async-executor", + "async-task", + "atomic-waker", + "bevy_platform", + "cfg-if", + "crossbeam-queue", + "derive_more", + "futures-lite", + "heapless", +] + +[[package]] +name = "bevy_utils" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94f7a8905a125d2017e8561beefb7f2f5e67e93ff6324f072ad87c5fd6ec3b99" +dependencies = [ + "bevy_platform", + "thread_local", +] + [[package]] name = "bit_field" version = "0.10.2" @@ -201,6 +379,9 @@ name = "bitflags" version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" +dependencies = [ + "serde", +] [[package]] name = "bitstream-io" @@ -249,6 +430,12 @@ dependencies = [ "syn", ] +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "byteorder-lite" version = "0.1.0" @@ -367,6 +554,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" dependencies = [ "crossbeam-utils", + "portable-atomic", ] [[package]] @@ -428,6 +616,12 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "critical-section" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + [[package]] name = "crossbeam-deque" version = "0.8.6" @@ -474,6 +668,27 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96a6ac251f4a2aca6b3f91340350eab87ae57c3f127ffeb585e92bd336717991" +[[package]] +name = "derive_more" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "unicode-xid", +] + [[package]] name = "dispatch" version = "0.2.0" @@ -501,6 +716,12 @@ dependencies = [ "syn", ] +[[package]] +name = "disqualified" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9c272297e804878a2a4b707cfcfc6d2328b5bb936944613b4fdf2b9269afdfd" + [[package]] name = "dlib" version = "0.5.2" @@ -516,6 +737,12 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" +[[package]] +name = "downcast-rs" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea8a8b81cacc08888170eef4d13b775126db426d0b348bee9d18c2c1eaf123cf" + [[package]] name = "dpi" version = "0.1.2" @@ -626,6 +853,16 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" +[[package]] +name = "erased-serde" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e004d887f51fcb9fef17317a2f3525c887d8aa3f4f50fed920816a688284a5b7" +dependencies = [ + "serde", + "typeid", +] + [[package]] name = "errno" version = "0.3.12" @@ -657,6 +894,12 @@ dependencies = [ "zune-inflate", ] +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + [[package]] name = "fdeflate" version = "0.3.7" @@ -666,6 +909,12 @@ dependencies = [ "simd-adler32", ] +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + [[package]] name = "flate2" version = "1.1.1" @@ -718,6 +967,31 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-lite" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5edaec856126859abb19ed65f39e90fea3a9574b9707f13539acf4abf7eb532" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + [[package]] name = "generator" version = "0.8.5" @@ -775,6 +1049,15 @@ dependencies = [ "weezl", ] +[[package]] +name = "glam" +version = "0.29.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8babf46d4c1c9d92deac9f7be466f76dfc4482b6452fc5024b5e8daf6ffeb3ee" +dependencies = [ + "serde", +] + [[package]] name = "glam" version = "0.30.3" @@ -792,11 +1075,35 @@ dependencies = [ "crunchy", ] +[[package]] +name = "hash32" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" +dependencies = [ + "byteorder", +] + [[package]] name = "hashbrown" version = "0.15.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3" +dependencies = [ + "equivalent", + "serde", +] + +[[package]] +name = "heapless" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" +dependencies = [ + "hash32", + "portable-atomic", + "stable_deref_trait", +] [[package]] name = "heck" @@ -1256,6 +1563,12 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "nonmax" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "610a5acd306ec67f907abe5567859a3c693fb9886eb1f012ab8f2a47bef3db51" + [[package]] name = "noop_proc_macro" version = "0.3.0" @@ -1667,6 +1980,12 @@ dependencies = [ "ttf-parser", ] +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + [[package]] name = "parking_lot" version = "0.12.3" @@ -1762,6 +2081,21 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "portable-atomic" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" + +[[package]] +name = "portable-atomic-util" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +dependencies = [ + "portable-atomic", +] + [[package]] name = "potential_utf" version = "0.1.2" @@ -2085,8 +2419,9 @@ name = "rust_vulkan_test" version = "0.1.0" dependencies = [ "anyhow", + "bevy_ecs", "egui_winit_vulkano", - "glam", + "glam 0.30.3", "image", "rand 0.9.1", "thiserror 2.0.12", @@ -2318,6 +2653,15 @@ dependencies = [ "serde", ] +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "portable-atomic", +] + [[package]] name = "stable_deref_trait" version = "1.2.0" @@ -2609,6 +2953,12 @@ version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31" +[[package]] +name = "typeid" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" + [[package]] name = "unicode-ident" version = "1.0.18" @@ -2621,6 +2971,12 @@ version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + [[package]] name = "url" version = "2.5.4" @@ -2638,6 +2994,18 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +[[package]] +name = "uuid" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d" +dependencies = [ + "getrandom 0.3.3", + "js-sys", + "serde", + "wasm-bindgen", +] + [[package]] name = "v_frame" version = "0.3.8" @@ -2655,6 +3023,17 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" +[[package]] +name = "variadics_please" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41b6d82be61465f97d42bd1d15bf20f3b0a3a0905018f38f9d6f6962055b0b5c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "version-compare" version = "0.2.0" @@ -2849,7 +3228,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe770181423e5fc79d3e2a7f4410b7799d5aab1de4372853de3c6aa13ca24121" dependencies = [ "cc", - "downcast-rs", + "downcast-rs 1.2.1", "rustix", "scoped-tls", "smallvec", @@ -2994,6 +3373,19 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a751b3277700db47d3e574514de2eced5e54dc8a5436a3bf7a0b248b2cee16f3" +[[package]] +name = "wgpu-types" +version = "24.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50ac044c0e76c03a0378e7786ac505d010a873665e2d51383dcff8dd227dc69c" +dependencies = [ + "bitflags 2.9.1", + "js-sys", + "log", + "serde", + "web-sys", +] + [[package]] name = "winapi" version = "0.3.9" diff --git a/Cargo.toml b/Cargo.toml index c33debe..6883189 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,3 +31,6 @@ rand = "0.9" # OBJ loader tobj = "4.0" + +# ECS +bevy_ecs = "0.16.1" # Latest version diff --git a/src/core/app/context.rs b/src/core/app/context.rs index 80b602e..a12bb3c 100644 --- a/src/core/app/context.rs +++ b/src/core/app/context.rs @@ -5,13 +5,6 @@ use std::{ }; use egui_winit_vulkano::Gui; -use vulkano::{ - command_buffer::allocator::StandardCommandBufferAllocator, - descriptor_set::allocator::StandardDescriptorSetAllocator, - device::{Device, Queue}, - instance::Instance, - memory::allocator::StandardMemoryAllocator, -}; use vulkano_util::{renderer::VulkanoWindowRenderer, window::VulkanoWindows}; use winit::{event_loop::EventLoopProxy, monitor::MonitorHandle, window::WindowId}; @@ -19,19 +12,10 @@ use crate::core::{input::InputManager, render::vulkan_context::VulkanContext, ti use super::user_event::UserEvent; -/// Contexte d'application unifié avec Arc> pour la mutabilité partagée -#[derive(Clone)] +/// Contexte d'application unifié pour les fonctions liées à la fenêtre pub struct WindowContext { // Données Vulkan (immutables) pub vulkan_context: Arc, - pub device: Arc, - pub instance: Arc, - pub graphics_queue: Arc, - pub compute_queue: Arc, - pub transfer_queue: Option>, - pub memory_allocator: Arc, - pub command_buffer_allocator: Arc, - pub descriptor_set_allocator: Arc, pub event_loop_proxy: EventLoopProxy, pub window_id: WindowId, @@ -52,19 +36,9 @@ impl WindowContext { event_loop_proxy: EventLoopProxy, window_id: WindowId, ) -> Self { - let vulkano_context_inner = vulkan_context.vulkano_context(); - Self { // Données Vulkan - vulkan_context: vulkan_context.clone(), - device: vulkano_context_inner.device().clone(), - instance: vulkano_context_inner.instance().clone(), - graphics_queue: vulkano_context_inner.graphics_queue().clone(), - compute_queue: vulkano_context_inner.compute_queue().clone(), - transfer_queue: vulkano_context_inner.transfer_queue().cloned(), - memory_allocator: vulkano_context_inner.memory_allocator().clone(), - command_buffer_allocator: vulkan_context.command_buffer_allocator().clone(), - descriptor_set_allocator: vulkan_context.descriptor_set_allocator().clone(), + vulkan_context, event_loop_proxy, window_id, @@ -76,6 +50,10 @@ impl WindowContext { } } + pub fn vulkan_context(&self) -> &VulkanContext { + &self.vulkan_context + } + /// Extrait les résolutions d'un moniteur donné fn extract_resolutions_from_monitor(monitor: MonitorHandle) -> Vec<(u32, u32)> { let video_modes: Vec<_> = monitor.video_modes().collect(); diff --git a/src/core/app/mod.rs b/src/core/app/mod.rs index 81fb0a3..8f0ace8 100644 --- a/src/core/app/mod.rs +++ b/src/core/app/mod.rs @@ -99,11 +99,7 @@ impl ApplicationHandler for App { }; self.gui.insert(window_id, Rc::new(RefCell::new(gui))); - let mut scene_manager = SceneManager::new(); - scene_manager.load_scene(Box::new(MainScene::default())); - - self.scene_manager.insert(window_id, scene_manager); - + // Create the WindowContext with simplified arguments let app_context = Rc::new(RefCell::new(WindowContext::new( self.vulkan_context.clone(), self.vulkano_windows.clone(), @@ -113,7 +109,16 @@ impl ApplicationHandler for App { self.event_loop_proxy.clone(), window_id, ))); - self.app_contexts.insert(window_id, app_context); + self.app_contexts.insert(window_id, app_context.clone()); + + // Now use the created context to load the scene + let mut scene_manager = SceneManager::new(); + { + let context = app_context.borrow(); + scene_manager.load_scene(Box::new(MainScene::default()), &context); + } + + self.scene_manager.insert(window_id, scene_manager); } fn device_event( @@ -181,7 +186,7 @@ impl ApplicationHandler for App { if let Some(scene) = scene_manager.current_scene_mut() { { let _update_span = tracing::debug_span!("scene_update").entered(); - scene.update(&mut context).unwrap(); + scene.0.update(&mut scene.1, &context).unwrap(); } let acquire_future = { @@ -193,7 +198,10 @@ impl ApplicationHandler for App { let acquire_future = { let _render_span = tracing::debug_span!("scene_render").entered(); - scene.render(acquire_future, &mut context).unwrap() + scene + .0 + .render(acquire_future, &mut scene.1, &mut context) + .unwrap() }; { @@ -235,7 +243,10 @@ impl ApplicationHandler for App { } UserEvent::ChangeScene(window_id, scene) => { if let Some(scene_manager) = self.scene_manager.get_mut(&window_id) { - scene_manager.load_scene(scene); + if let Some(app_context) = self.app_contexts.get(&window_id) { + let context = app_context.borrow(); + scene_manager.load_scene(scene, &context); + } } } UserEvent::ChangeResolution(window_id, width, height) => { diff --git a/src/core/render/primitives/mod.rs b/src/core/render/primitives/mod.rs index 6f795e4..c7211a6 100644 --- a/src/core/render/primitives/mod.rs +++ b/src/core/render/primitives/mod.rs @@ -1,6 +1,7 @@ mod buffer; mod command; mod descriptor_set; +pub mod vulkan_resource; pub mod camera; pub mod mvp; diff --git a/src/core/render/primitives/mvp.rs b/src/core/render/primitives/mvp.rs index 6cf1362..814a6e6 100644 --- a/src/core/render/primitives/mvp.rs +++ b/src/core/render/primitives/mvp.rs @@ -4,7 +4,7 @@ use std::sync::Arc; use vulkano::buffer::{ AllocateBufferError, BufferContents, BufferCreateInfo, BufferUsage, Subbuffer, }; -use vulkano::descriptor_set::allocator::{DescriptorSetAllocator, StandardDescriptorSetAllocator}; +use vulkano::descriptor_set::allocator::StandardDescriptorSetAllocator; use vulkano::descriptor_set::layout::{ DescriptorSetLayout, DescriptorSetLayoutBinding, DescriptorType, }; diff --git a/src/core/render/primitives/vulkan_resource.rs b/src/core/render/primitives/vulkan_resource.rs new file mode 100644 index 0000000..db59468 --- /dev/null +++ b/src/core/render/primitives/vulkan_resource.rs @@ -0,0 +1,79 @@ +use bevy_ecs::prelude::Resource; +use bevy_ecs::world::World; +use std::sync::Arc; +use vulkano::{ + command_buffer::allocator::StandardCommandBufferAllocator, + descriptor_set::allocator::StandardDescriptorSetAllocator, + device::{Device, Queue}, + instance::Instance, + memory::allocator::StandardMemoryAllocator, +}; + +/// Vulkan Instance resource +#[derive(Resource)] +pub struct VulkanInstance(pub Arc); + +/// Vulkan Device resource +#[derive(Resource)] +pub struct VulkanDevice(pub Arc); + +/// Vulkan Graphics Queue resource +#[derive(Resource)] +pub struct VulkanGraphicsQueue(pub Arc); + +/// Vulkan Compute Queue resource +#[derive(Resource)] +pub struct VulkanComputeQueue(pub Arc); + +/// Vulkan Transfer Queue resource +#[derive(Resource)] +pub struct VulkanTransferQueue(pub Option>); + +/// Vulkan Memory Allocator resource +#[derive(Resource)] +pub struct VulkanMemoryAllocator(pub Arc); + +/// Vulkan Command Buffer Allocator resource +#[derive(Resource)] +pub struct VulkanCommandBufferAllocator(pub Arc); + +/// Vulkan Descriptor Set Allocator resource +#[derive(Resource)] +pub struct VulkanDescriptorSetAllocator(pub Arc); + +/// Helper functions to access vulkan resources from the ECS world +impl VulkanDevice { + pub fn get_from_world(world: &World) -> &Arc { + &world.resource::().0 + } +} + +impl VulkanMemoryAllocator { + pub fn get_from_world(world: &World) -> &Arc { + &world.resource::().0 + } +} + +impl VulkanCommandBufferAllocator { + pub fn get_from_world(world: &World) -> &Arc { + &world.resource::().0 + } +} + +impl VulkanDescriptorSetAllocator { + pub fn get_from_world(world: &World) -> &Arc { + &world.resource::().0 + } +} + +impl VulkanGraphicsQueue { + pub fn get_from_world(world: &World) -> &Arc { + &world.resource::().0 + } +} + +impl VulkanTransferQueue { + pub fn get_from_world(world: &World) -> Option<&Arc> { + world.resource::().0.as_ref() + } +} diff --git a/src/core/render/resources/texture/loader.rs b/src/core/render/resources/texture/loader.rs index d440291..38a365c 100644 --- a/src/core/render/resources/texture/loader.rs +++ b/src/core/render/resources/texture/loader.rs @@ -11,7 +11,11 @@ use vulkano::{ memory::allocator::StandardMemoryAllocator, }; -use crate::core::app::context::WindowContext; +use crate::core::render::primitives::vulkan_resource::{ + VulkanCommandBufferAllocator, VulkanDevice, VulkanGraphicsQueue, VulkanMemoryAllocator, + VulkanTransferQueue, +}; +use bevy_ecs::world::World; use super::Texture; @@ -36,21 +40,19 @@ pub struct TextureLoader { } impl TextureLoader { - pub fn new(app_context: &WindowContext) -> Self { + pub fn new(world: &World) -> Self { Self { loaded_textures: HashMap::new(), pending_textures: HashMap::new(), - device: app_context.device.clone(), - command_buffer_allocator: app_context.command_buffer_allocator.clone(), - memory_allocator: app_context.memory_allocator.clone(), - queue: Self::select_best_suitable_queue(app_context), + device: VulkanDevice::get_from_world(world).clone(), + command_buffer_allocator: VulkanCommandBufferAllocator::get_from_world(world).clone(), + memory_allocator: VulkanMemoryAllocator::get_from_world(world).clone(), + queue: Self::select_best_suitable_queue(world), } } - fn select_best_suitable_queue(app_context: &WindowContext) -> Arc { - app_context - .transfer_queue - .as_ref() + fn select_best_suitable_queue(world: &World) -> Arc { + VulkanTransferQueue::get_from_world(world) .map(|queue| { tracing::trace!( "Select transfer queue for texture loading with family index: {:?}", @@ -58,14 +60,14 @@ impl TextureLoader { ); queue.clone() }) - .or_else(|| { + .unwrap_or_else(|| { + let graphics_queue = VulkanGraphicsQueue::get_from_world(world); tracing::trace!( "Select graphics queue for texture loading with family index: {:?}", - app_context.graphics_queue.queue_family_index() + graphics_queue.queue_family_index() ); - Some(app_context.graphics_queue.clone()) + graphics_queue.clone() }) - .unwrap() } pub fn add_texture(&mut self, name: String, load_info: TextureLoadInfo) { diff --git a/src/core/scene/manager.rs b/src/core/scene/manager.rs index cf07ebc..e8660e8 100644 --- a/src/core/scene/manager.rs +++ b/src/core/scene/manager.rs @@ -1,12 +1,20 @@ +use std::cell::RefCell; use std::error::Error; +use std::rc::Rc; use crate::core::app::context::WindowContext; +use crate::core::render::primitives::vulkan_resource::{ + VulkanCommandBufferAllocator, VulkanComputeQueue, VulkanDescriptorSetAllocator, VulkanDevice, + VulkanGraphicsQueue, VulkanInstance, VulkanMemoryAllocator, VulkanTransferQueue, +}; +use bevy_ecs::world::World; use super::Scene; pub struct SceneManager { - scenes: Vec>, + scenes: Vec<(Box, World)>, current_scene_index: Option, + window_context: Option>>, } impl SceneManager { @@ -14,26 +22,95 @@ impl SceneManager { Self { scenes: Vec::new(), current_scene_index: None, + window_context: None, } } - pub fn load_scene(&mut self, scene: Box) { - self.scenes.push(scene); + pub fn set_window_context(&mut self, window_context: Rc>) { + self.window_context = Some(window_context); + } + + fn create_world_with_resources(window_context: &WindowContext) -> World { + let mut world = World::new(); + + // Add Vulkan resources + world.insert_resource(VulkanInstance( + window_context + .vulkan_context() + .vulkano_context() + .instance() + .clone(), + )); + world.insert_resource(VulkanDevice( + window_context + .vulkan_context() + .vulkano_context() + .device() + .clone(), + )); + world.insert_resource(VulkanGraphicsQueue( + window_context + .vulkan_context() + .vulkano_context() + .graphics_queue() + .clone(), + )); + world.insert_resource(VulkanComputeQueue( + window_context + .vulkan_context() + .vulkano_context() + .compute_queue() + .clone(), + )); + world.insert_resource(VulkanTransferQueue( + window_context + .vulkan_context() + .vulkano_context() + .transfer_queue() + .cloned(), + )); + world.insert_resource(VulkanMemoryAllocator( + window_context + .vulkan_context() + .vulkano_context() + .memory_allocator() + .clone(), + )); + world.insert_resource(VulkanCommandBufferAllocator( + window_context + .vulkan_context() + .command_buffer_allocator() + .clone(), + )); + world.insert_resource(VulkanDescriptorSetAllocator( + window_context + .vulkan_context() + .descriptor_set_allocator() + .clone(), + )); + + world + } + + pub fn load_scene(&mut self, scene: Box, window_context: &WindowContext) { + let world = Self::create_world_with_resources(window_context); + self.scenes.push((scene, world)); self.current_scene_index = Some(self.scenes.len() - 1); } - pub fn replace_current_scene(&mut self, scene: Box) { + pub fn replace_current_scene(&mut self, scene: Box, window_context: &WindowContext) { if let Some(index) = self.current_scene_index { if index < self.scenes.len() { - self.scenes[index].unload(); - self.scenes[index] = scene; + self.scenes[index].0.unload(); + let world = Self::create_world_with_resources(window_context); + self.scenes[index] = (scene, world); } } else { - self.load_scene(scene); + self.load_scene(scene, window_context); } } - pub fn current_scene(&self) -> Option<&Box> { + pub fn current_scene(&self) -> Option<&(Box, World)> { if let Some(index) = self.current_scene_index { self.scenes.get(index) } else { @@ -41,7 +118,7 @@ impl SceneManager { } } - pub fn current_scene_mut(&mut self) -> Option<&mut Box> { + pub fn current_scene_mut(&mut self) -> Option<&mut (Box, World)> { if let Some(index) = self.current_scene_index { self.scenes.get_mut(index) } else { @@ -51,11 +128,11 @@ impl SceneManager { pub fn load_scene_if_not_loaded( &mut self, - app_context: &mut WindowContext, + window_context: &mut WindowContext, ) -> Result<(), Box> { - if let Some(scene) = self.current_scene_mut() { + if let Some((scene, world)) = self.current_scene_mut() { if !scene.loaded() { - scene.load(app_context)?; + scene.load(world, window_context)?; } } else { tracing::warn!("No scene found in SceneManager!"); diff --git a/src/core/scene/mod.rs b/src/core/scene/mod.rs index 5dd8a57..c12f00e 100644 --- a/src/core/scene/mod.rs +++ b/src/core/scene/mod.rs @@ -1,5 +1,6 @@ use std::error::Error; +use bevy_ecs::world::World; use vulkano::sync::GpuFuture; use crate::core::app::context::WindowContext; @@ -8,12 +9,21 @@ pub mod manager; pub trait Scene { fn loaded(&self) -> bool; - fn load(&mut self, app_context: &mut WindowContext) -> Result<(), Box>; - fn update(&mut self, app_context: &mut WindowContext) -> Result<(), Box>; + fn load( + &mut self, + world: &mut World, + window_context: &mut WindowContext, + ) -> Result<(), Box>; + fn update( + &mut self, + world: &mut World, + window_context: &WindowContext, + ) -> Result<(), Box>; fn render( &mut self, acquire_future: Box, - app_context: &mut WindowContext, + world: &mut World, + window_context: &mut WindowContext, ) -> Result, Box>; fn unload(&mut self); } diff --git a/src/game/assets/pipelines/simple.rs b/src/game/assets/pipelines/simple.rs index 3f3a4db..6a114c8 100644 --- a/src/game/assets/pipelines/simple.rs +++ b/src/game/assets/pipelines/simple.rs @@ -1,7 +1,4 @@ -use std::{ - error::Error, - sync::{Arc, RwLock}, -}; +use std::{error::Error, sync::Arc}; use vulkano::{ command_buffer::{AutoCommandBufferBuilder, PrimaryAutoCommandBuffer}, diff --git a/src/game/scenes/main_scene.rs b/src/game/scenes/main_scene.rs index bb29c35..33532f8 100644 --- a/src/game/scenes/main_scene.rs +++ b/src/game/scenes/main_scene.rs @@ -1,4 +1,3 @@ -use std::collections::HashMap; use std::error::Error; use std::sync::Arc; @@ -8,6 +7,10 @@ use crate::core::app::context::WindowContext; use crate::core::app::user_event::UserEvent; use crate::core::render::primitives::camera::Camera3D; use crate::core::render::primitives::transform::Transform; +use crate::core::render::primitives::vulkan_resource::{ + VulkanCommandBufferAllocator, VulkanDescriptorSetAllocator, VulkanDevice, VulkanGraphicsQueue, + VulkanMemoryAllocator, +}; use crate::core::render::primitives::{AsDescriptorSet, AsRecordable}; use crate::core::render::render_pass_manager::{RenderPassConfig, RenderPassManager}; use crate::core::render::resources::meshes::{ObjMesh, SquareMesh}; @@ -15,6 +18,7 @@ use crate::core::render::resources::pipeline::PipelineLoader; use crate::core::render::resources::texture::{TextureLoadInfo, TextureLoader, TextureSourceKind}; use crate::core::scene::Scene; use crate::game::assets::pipelines::simple::SimplePipeline; +use bevy_ecs::world::World; use egui_winit_vulkano::egui; use glam::EulerRot; use glam::Quat; @@ -48,23 +52,27 @@ impl Scene for MainScene { self.state.is_some() } - fn load(&mut self, app_context: &mut WindowContext) -> Result<(), Box> { - let depth_image_view = app_context.with_renderer_mut(|renderer| { + fn load( + &mut self, + world: &mut World, + window_context: &mut WindowContext, + ) -> Result<(), Box> { + let depth_image_view = window_context.with_renderer_mut(|renderer| { renderer.get_additional_image_view(DEPTH_IMAGE_ID).clone() }); let swapchain_image_view = - app_context.with_renderer(|renderer| renderer.swapchain_image_view().clone()); + window_context.with_renderer(|renderer| renderer.swapchain_image_view().clone()); let mut pipeline_loader = PipelineLoader::new( - app_context.device.clone(), + VulkanDevice::get_from_world(world).clone(), swapchain_image_view.format(), depth_image_view.format(), ); pipeline_loader.register::()?; pipeline_loader.load_pending_pipelines()?; - let mut texture_loader = TextureLoader::new(app_context); + let mut texture_loader = TextureLoader::new(world); texture_loader.add_texture( "wooden-crate".to_string(), TextureLoadInfo { @@ -93,10 +101,13 @@ impl Scene for MainScene { ); texture_loader.load_pending_textures()?; - let square = SquareMesh::new(&app_context.memory_allocator)?; + let square = SquareMesh::new(VulkanMemoryAllocator::get_from_world(world))?; let obj = { - let obj = ObjMesh::new(&app_context.memory_allocator, "res/objects/cube.obj")?; + let obj = ObjMesh::new( + VulkanMemoryAllocator::get_from_world(world), + "res/objects/cube.obj", + )?; obj.into_iter().next().unwrap() }; @@ -139,7 +150,7 @@ impl Scene for MainScene { }) .collect(); - let camera = app_context.with_renderer(|renderer| { + let camera = window_context.with_renderer(|renderer| { Camera3D::new( renderer.aspect_ratio(), std::f32::consts::FRAC_PI_2, @@ -162,21 +173,26 @@ impl Scene for MainScene { Ok(()) } - fn update(&mut self, app_context: &mut WindowContext) -> Result<(), Box> { + fn update( + &mut self, + _world: &mut World, + window_context: &WindowContext, + ) -> Result<(), Box> { let state = self.state.as_mut().unwrap(); - app_context.with_input_manager(|input_manager| { - app_context.with_timer(|timer| { + + window_context.with_input_manager(|input_manager| { + window_context.with_timer(|timer| { state.camera.update( input_manager, timer, state.speed, 10.0, - app_context.get_aspect_ratio(), + window_context.get_aspect_ratio(), ); }); }); - let delta_time = app_context.get_delta_time(); + let delta_time = window_context.get_delta_time(); for (i, instance) in state.square_instances.iter_mut().enumerate() { let rotation_speed = (i % 10) as f32; let rotation_delta = Quat::from_rotation_y(rotation_speed * delta_time); @@ -191,32 +207,32 @@ impl Scene for MainScene { instance.rotate(rotation_delta); } - if app_context + if window_context .with_input_manager(|input_manager| input_manager.get_virtual_input_state("mouse_left")) > 0.0 { - let _ = app_context + let _ = window_context .event_loop_proxy - .send_event(UserEvent::CursorVisible(app_context.window_id, false)); - let _ = app_context + .send_event(UserEvent::CursorVisible(window_context.window_id, false)); + let _ = window_context .event_loop_proxy .send_event(UserEvent::CursorGrabMode( - app_context.window_id, + window_context.window_id, CursorGrabMode::Locked, )); } - if app_context.with_input_manager(|input_manager| { + if window_context.with_input_manager(|input_manager| { input_manager.get_virtual_input_state("mouse_right") }) > 0.0 { - let _ = app_context + let _ = window_context .event_loop_proxy - .send_event(UserEvent::CursorVisible(app_context.window_id, true)); - let _ = app_context + .send_event(UserEvent::CursorVisible(window_context.window_id, true)); + let _ = window_context .event_loop_proxy .send_event(UserEvent::CursorGrabMode( - app_context.window_id, + window_context.window_id, CursorGrabMode::None, )); } @@ -227,20 +243,21 @@ impl Scene for MainScene { fn render( &mut self, before_future: Box, - app_context: &mut WindowContext, + world: &mut World, + window_context: &mut WindowContext, ) -> Result, Box> { let state = self.state.as_mut().ok_or("State not loaded")?; let mut builder = AutoCommandBufferBuilder::primary( - app_context.command_buffer_allocator.clone(), - app_context.graphics_queue.queue_family_index(), + VulkanCommandBufferAllocator::get_from_world(world).clone(), + VulkanGraphicsQueue::get_from_world(world).queue_family_index(), CommandBufferUsage::OneTimeSubmit, )?; { let swapchain_image_view = - app_context.with_renderer(|renderer| renderer.swapchain_image_view().clone()); - let depth_image_view = app_context.with_renderer_mut(|renderer| { + window_context.with_renderer(|renderer| renderer.swapchain_image_view().clone()); + let depth_image_view = window_context.with_renderer_mut(|renderer| { renderer.get_additional_image_view(DEPTH_IMAGE_ID).clone() }); let config = RenderPassConfig::default(); @@ -249,23 +266,31 @@ impl Scene for MainScene { &config, swapchain_image_view, Some(depth_image_view), - app_context.get_window_size(), + window_context.get_window_size(), )?; } // Create camera uniform using the actual camera - let camera_uniform = Arc::new(state.camera.create_buffer(&app_context.memory_allocator)?); - let square_transform_uniform = - Transform::create_buffer(&app_context.memory_allocator, &state.square_instances)?; - let obj_transform_uniform = - Transform::create_buffer(&app_context.memory_allocator, &state.obj_instances)?; + let camera_uniform = Arc::new( + state + .camera + .create_buffer(VulkanMemoryAllocator::get_from_world(world))?, + ); + let square_transform_uniform = Transform::create_buffer( + VulkanMemoryAllocator::get_from_world(world), + &state.square_instances, + )?; + let obj_transform_uniform = Transform::create_buffer( + VulkanMemoryAllocator::get_from_world(world), + &state.obj_instances, + )?; state .pipeline_loader .with_pipeline::(|pipeline| { SimplePipeline::record_commands( &mut builder, - &app_context.descriptor_set_allocator, + &VulkanDescriptorSetAllocator::get_from_world(world), pipeline, &state.square, &square_transform_uniform, @@ -281,7 +306,7 @@ impl Scene for MainScene { SimplePipeline::record_commands( &mut builder, - &app_context.descriptor_set_allocator, + &VulkanDescriptorSetAllocator::get_from_world(world), pipeline, &state.obj, &obj_transform_uniform, @@ -302,19 +327,21 @@ impl Scene for MainScene { let command_buffer = builder.build()?; - let render_future = - before_future.then_execute(app_context.graphics_queue.clone(), command_buffer)?; + let render_future = before_future.then_execute( + VulkanGraphicsQueue::get_from_world(world).clone(), + command_buffer, + )?; let swapchain_image_view = - app_context.with_renderer(|renderer| renderer.swapchain_image_view().clone()); + window_context.with_renderer(|renderer| renderer.swapchain_image_view().clone()); let input_manager_status = - app_context.with_input_manager(|input_manager| format!("{:#?}", input_manager)); - let event_loop_proxy = app_context.event_loop_proxy.clone(); - let delta_time = app_context.get_delta_time(); - let window_id = app_context.window_id; - let window_size = app_context.get_window_size(); + window_context.with_input_manager(|input_manager| format!("{:#?}", input_manager)); + let event_loop_proxy = window_context.event_loop_proxy.clone(); + let delta_time = window_context.get_delta_time(); + let window_id = window_context.window_id; + let window_size = window_context.get_window_size(); - let render_future = app_context.with_gui_mut(|gui| { + let render_future = window_context.with_gui_mut(|gui| { gui.immediate_ui(|gui| { let ctx = gui.context(); egui::TopBottomPanel::top("top_panel").show(&ctx, |ui| { diff --git a/src/game/scenes/settings_scene.rs b/src/game/scenes/settings_scene.rs index 466652a..f2e1eff 100644 --- a/src/game/scenes/settings_scene.rs +++ b/src/game/scenes/settings_scene.rs @@ -3,8 +3,12 @@ use std::error::Error; use crate::core::app::DEPTH_IMAGE_ID; use crate::core::app::context::WindowContext; use crate::core::app::user_event::UserEvent; +use crate::core::render::primitives::vulkan_resource::{ + VulkanCommandBufferAllocator, VulkanGraphicsQueue, +}; use crate::core::render::render_pass_manager::{RenderPassConfig, RenderPassManager}; use crate::core::scene::Scene; +use bevy_ecs::world::World; use egui_winit_vulkano::egui; use vulkano::{ command_buffer::AutoCommandBufferBuilder, command_buffer::CommandBufferUsage, sync::GpuFuture, @@ -27,9 +31,13 @@ impl Scene for SettingsScene { self.state.is_some() } - fn load(&mut self, app_context: &mut WindowContext) -> Result<(), Box> { - let current_resolution = app_context.get_window_size(); - let available_resolutions = app_context.get_available_resolutions(); + fn load( + &mut self, + _world: &mut World, + window_context: &mut WindowContext, + ) -> Result<(), Box> { + let current_resolution = window_context.get_window_size(); + let available_resolutions = window_context.get_available_resolutions(); self.state = Some(SettingsSceneState { current_resolution, @@ -39,28 +47,33 @@ impl Scene for SettingsScene { Ok(()) } - fn update(&mut self, _app_context: &mut WindowContext) -> Result<(), Box> { + fn update( + &mut self, + _world: &mut World, + _window_context: &WindowContext, + ) -> Result<(), Box> { Ok(()) } fn render( &mut self, before_future: Box, - app_context: &mut WindowContext, + world: &mut World, + window_context: &mut WindowContext, ) -> Result, Box> { let state = self.state.as_ref().ok_or("State not found")?; let mut builder = AutoCommandBufferBuilder::primary( - app_context.command_buffer_allocator.clone(), - app_context.graphics_queue.queue_family_index(), + VulkanCommandBufferAllocator::get_from_world(world).clone(), + VulkanGraphicsQueue::get_from_world(world).queue_family_index(), CommandBufferUsage::OneTimeSubmit, )?; // Utiliser le RenderPassManager { let swapchain_image_view = - app_context.with_renderer(|renderer| renderer.swapchain_image_view().clone()); - let depth_stencil_image_view = app_context.with_renderer_mut(|renderer| { + window_context.with_renderer(|renderer| renderer.swapchain_image_view().clone()); + let depth_stencil_image_view = window_context.with_renderer_mut(|renderer| { renderer.get_additional_image_view(DEPTH_IMAGE_ID).clone() }); @@ -70,7 +83,7 @@ impl Scene for SettingsScene { &config, swapchain_image_view, Some(depth_stencil_image_view), - app_context.get_window_size(), + window_context.get_window_size(), )?; } @@ -79,16 +92,18 @@ impl Scene for SettingsScene { let command_buffer = builder.build()?; - let render_future = - before_future.then_execute(app_context.graphics_queue.clone(), command_buffer)?; + let render_future = before_future.then_execute( + VulkanGraphicsQueue::get_from_world(world).clone(), + command_buffer, + )?; let swapchain_image_view = - app_context.with_renderer(|renderer| renderer.swapchain_image_view().clone()); + window_context.with_renderer(|renderer| renderer.swapchain_image_view().clone()); - let event_loop_proxy = app_context.event_loop_proxy.clone(); - let window_id = app_context.window_id; + let event_loop_proxy = window_context.event_loop_proxy.clone(); + let window_id = window_context.window_id; - let render_future = app_context.with_gui_mut(|gui| { + let render_future = window_context.with_gui_mut(|gui| { gui.immediate_ui(|gui| { let ctx = gui.context(); From c49457438953fe3c78141ee282daa9bb6c71f8b1 Mon Sep 17 00:00:00 2001 From: Florian RICHER Date: Wed, 11 Jun 2025 22:24:28 +0200 Subject: [PATCH 095/105] Move World into dedicated Scene struct --- src/core/app/mod.rs | 9 +++---- src/core/app/user_event.rs | 4 +-- src/core/scene/manager.rs | 29 ++++++++++++--------- src/core/scene/mod.rs | 43 ++++++++++++++++++++++++++++++- src/game/scenes/main_scene.rs | 6 ++--- src/game/scenes/settings_scene.rs | 10 ++++--- 6 files changed, 73 insertions(+), 28 deletions(-) diff --git a/src/core/app/mod.rs b/src/core/app/mod.rs index 8f0ace8..c62e4d5 100644 --- a/src/core/app/mod.rs +++ b/src/core/app/mod.rs @@ -186,7 +186,7 @@ impl ApplicationHandler for App { if let Some(scene) = scene_manager.current_scene_mut() { { let _update_span = tracing::debug_span!("scene_update").entered(); - scene.0.update(&mut scene.1, &context).unwrap(); + scene.update(&context).unwrap(); } let acquire_future = { @@ -198,10 +198,7 @@ impl ApplicationHandler for App { let acquire_future = { let _render_span = tracing::debug_span!("scene_render").entered(); - scene - .0 - .render(acquire_future, &mut scene.1, &mut context) - .unwrap() + scene.render(acquire_future, &mut context).unwrap() }; { @@ -245,7 +242,7 @@ impl ApplicationHandler for App { if let Some(scene_manager) = self.scene_manager.get_mut(&window_id) { if let Some(app_context) = self.app_contexts.get(&window_id) { let context = app_context.borrow(); - scene_manager.load_scene(scene, &context); + scene_manager.replace_current_scene(scene, &context); } } } diff --git a/src/core/app/user_event.rs b/src/core/app/user_event.rs index 21b70f1..498aac0 100644 --- a/src/core/app/user_event.rs +++ b/src/core/app/user_event.rs @@ -1,11 +1,11 @@ use winit::window::{CursorGrabMode, WindowId}; -use crate::core::scene::Scene; +use crate::core::scene::AsScene; pub enum UserEvent { CursorGrabMode(WindowId, CursorGrabMode), CursorVisible(WindowId, bool), - ChangeScene(WindowId, Box), + ChangeScene(WindowId, Box), ChangeResolution(WindowId, f32, f32), Exit(WindowId), } diff --git a/src/core/scene/manager.rs b/src/core/scene/manager.rs index e8660e8..652e83b 100644 --- a/src/core/scene/manager.rs +++ b/src/core/scene/manager.rs @@ -9,10 +9,10 @@ use crate::core::render::primitives::vulkan_resource::{ }; use bevy_ecs::world::World; -use super::Scene; +use super::{AsScene, Scene}; pub struct SceneManager { - scenes: Vec<(Box, World)>, + scenes: Vec, current_scene_index: Option, window_context: Option>>, } @@ -92,25 +92,30 @@ impl SceneManager { world } - pub fn load_scene(&mut self, scene: Box, window_context: &WindowContext) { + pub fn load_scene(&mut self, scene_impl: Box, window_context: &WindowContext) { let world = Self::create_world_with_resources(window_context); - self.scenes.push((scene, world)); + let scene = Scene::new(scene_impl, world); + self.scenes.push(scene); self.current_scene_index = Some(self.scenes.len() - 1); } - pub fn replace_current_scene(&mut self, scene: Box, window_context: &WindowContext) { + pub fn replace_current_scene( + &mut self, + scene_impl: Box, + window_context: &WindowContext, + ) { if let Some(index) = self.current_scene_index { if index < self.scenes.len() { - self.scenes[index].0.unload(); + self.scenes[index].unload(); let world = Self::create_world_with_resources(window_context); - self.scenes[index] = (scene, world); + self.scenes[index] = Scene::new(scene_impl, world); } } else { - self.load_scene(scene, window_context); + self.load_scene(scene_impl, window_context); } } - pub fn current_scene(&self) -> Option<&(Box, World)> { + pub fn current_scene(&self) -> Option<&Scene> { if let Some(index) = self.current_scene_index { self.scenes.get(index) } else { @@ -118,7 +123,7 @@ impl SceneManager { } } - pub fn current_scene_mut(&mut self) -> Option<&mut (Box, World)> { + pub fn current_scene_mut(&mut self) -> Option<&mut Scene> { if let Some(index) = self.current_scene_index { self.scenes.get_mut(index) } else { @@ -130,9 +135,9 @@ impl SceneManager { &mut self, window_context: &mut WindowContext, ) -> Result<(), Box> { - if let Some((scene, world)) = self.current_scene_mut() { + if let Some(scene) = self.current_scene_mut() { if !scene.loaded() { - scene.load(world, window_context)?; + scene.load(window_context)?; } } else { tracing::warn!("No scene found in SceneManager!"); diff --git a/src/core/scene/mod.rs b/src/core/scene/mod.rs index c12f00e..7abb795 100644 --- a/src/core/scene/mod.rs +++ b/src/core/scene/mod.rs @@ -7,7 +7,48 @@ use crate::core::app::context::WindowContext; pub mod manager; -pub trait Scene { +/// Structure Scene qui contient le world et l'implémentation AsScene +pub struct Scene { + pub world: World, + pub implementation: Box, +} + +impl Scene { + pub fn new(implementation: Box, world: World) -> Self { + Self { + world, + implementation, + } + } + + pub fn loaded(&self) -> bool { + self.implementation.loaded() + } + + pub fn load(&mut self, window_context: &mut WindowContext) -> Result<(), Box> { + self.implementation.load(&mut self.world, window_context) + } + + pub fn update(&mut self, window_context: &WindowContext) -> Result<(), Box> { + self.implementation.update(&mut self.world, window_context) + } + + pub fn render( + &mut self, + acquire_future: Box, + window_context: &mut WindowContext, + ) -> Result, Box> { + self.implementation + .render(acquire_future, &mut self.world, window_context) + } + + pub fn unload(&mut self) { + self.implementation.unload() + } +} + +/// Trait pour les implémentations de scènes +pub trait AsScene { fn loaded(&self) -> bool; fn load( &mut self, diff --git a/src/game/scenes/main_scene.rs b/src/game/scenes/main_scene.rs index 33532f8..0079a5a 100644 --- a/src/game/scenes/main_scene.rs +++ b/src/game/scenes/main_scene.rs @@ -16,7 +16,7 @@ use crate::core::render::render_pass_manager::{RenderPassConfig, RenderPassManag use crate::core::render::resources::meshes::{ObjMesh, SquareMesh}; use crate::core::render::resources::pipeline::PipelineLoader; use crate::core::render::resources::texture::{TextureLoadInfo, TextureLoader, TextureSourceKind}; -use crate::core::scene::Scene; +use crate::core::scene::AsScene; use crate::game::assets::pipelines::simple::SimplePipeline; use bevy_ecs::world::World; use egui_winit_vulkano::egui; @@ -47,7 +47,7 @@ pub struct MainScene { state: Option, } -impl Scene for MainScene { +impl AsScene for MainScene { fn loaded(&self) -> bool { self.state.is_some() } @@ -393,7 +393,7 @@ impl Scene for MainScene { gui.draw_on_image(render_future, swapchain_image_view.clone()) }); - Ok(render_future) + Ok(Box::new(render_future)) } fn unload(&mut self) { diff --git a/src/game/scenes/settings_scene.rs b/src/game/scenes/settings_scene.rs index f2e1eff..38e44b2 100644 --- a/src/game/scenes/settings_scene.rs +++ b/src/game/scenes/settings_scene.rs @@ -7,7 +7,7 @@ use crate::core::render::primitives::vulkan_resource::{ VulkanCommandBufferAllocator, VulkanGraphicsQueue, }; use crate::core::render::render_pass_manager::{RenderPassConfig, RenderPassManager}; -use crate::core::scene::Scene; +use crate::core::scene::AsScene; use bevy_ecs::world::World; use egui_winit_vulkano::egui; use vulkano::{ @@ -26,7 +26,7 @@ pub struct SettingsScene { state: Option, } -impl Scene for SettingsScene { +impl AsScene for SettingsScene { fn loaded(&self) -> bool { self.state.is_some() } @@ -144,8 +144,10 @@ impl Scene for SettingsScene { gui.draw_on_image(render_future, swapchain_image_view.clone()) }); - Ok(render_future) + Ok(Box::new(render_future)) } - fn unload(&mut self) {} + fn unload(&mut self) { + self.state = None; + } } From 9fabacffc97d857263a461596efee65c4757ff5f Mon Sep 17 00:00:00 2001 From: Florian RICHER Date: Wed, 11 Jun 2025 22:34:54 +0200 Subject: [PATCH 096/105] Cleanup + Add velocity --- src/core/render/primitives/mod.rs | 1 + src/core/render/primitives/transform.rs | 21 ++------------------- src/core/render/primitives/velocity.rs | 14 ++++++++++++++ 3 files changed, 17 insertions(+), 19 deletions(-) create mode 100644 src/core/render/primitives/velocity.rs diff --git a/src/core/render/primitives/mod.rs b/src/core/render/primitives/mod.rs index c7211a6..9061f3c 100644 --- a/src/core/render/primitives/mod.rs +++ b/src/core/render/primitives/mod.rs @@ -6,6 +6,7 @@ pub mod vulkan_resource; pub mod camera; pub mod mvp; pub mod transform; +pub mod velocity; pub mod vertex; pub use buffer::{AsBindableBuffer, AsIndexBuffer, AsUniformBuffer, AsVertexBuffer}; pub use command::{AsRecordable, AsRenderableMesh, AsRenderableMeshInstance}; diff --git a/src/core/render/primitives/transform.rs b/src/core/render/primitives/transform.rs index 506158c..c5a6fae 100644 --- a/src/core/render/primitives/transform.rs +++ b/src/core/render/primitives/transform.rs @@ -1,5 +1,6 @@ use std::sync::Arc; +use bevy_ecs::resource::Resource; use glam::{Mat4, Quat, Vec3}; use vulkano::{ Validated, @@ -12,14 +13,11 @@ use crate::core::render::primitives::{AsBindableBuffer, AsVertexBuffer}; use super::command::AsRenderableMeshInstance; -#[derive(Debug, Clone)] +#[derive(Resource, Debug, Clone)] pub struct Transform { pub position: Vec3, pub rotation: Quat, pub scale: Vec3, - // Cache to avoid unnecessary recalculations - cached_matrix: Option, - dirty: bool, } #[derive(BufferContents, Vertex, Clone, Copy, Debug)] @@ -35,8 +33,6 @@ impl Default for Transform { position: Vec3::ZERO, rotation: Quat::IDENTITY, scale: Vec3::ONE, - cached_matrix: None, - dirty: true, } } } @@ -47,44 +43,31 @@ impl Transform { position, rotation, scale, - cached_matrix: None, - dirty: true, } } pub fn rotate(&mut self, rotation: Quat) { self.rotation *= rotation; - self.mark_dirty(); } pub fn translate(&mut self, translation: Vec3) { self.position += translation; - self.mark_dirty(); } pub fn scale(&mut self, scale: Vec3) { self.scale *= scale; - self.mark_dirty(); } pub fn set_position(&mut self, position: Vec3) { self.position = position; - self.mark_dirty(); } pub fn set_rotation(&mut self, rotation: Quat) { self.rotation = rotation; - self.mark_dirty(); } pub fn set_scale(&mut self, scale: Vec3) { self.scale = scale; - self.mark_dirty(); - } - - fn mark_dirty(&mut self) { - self.dirty = true; - self.cached_matrix = None; } /// Get the transformation matrix (immutable - recalculates each time) diff --git a/src/core/render/primitives/velocity.rs b/src/core/render/primitives/velocity.rs new file mode 100644 index 0000000..7663f3e --- /dev/null +++ b/src/core/render/primitives/velocity.rs @@ -0,0 +1,14 @@ +use bevy_ecs::resource::Resource; +use glam::Vec3; + +#[derive(Resource, Debug, Clone)] +pub struct Velocity { + pub linear: Vec3, + pub angular: Vec3, +} + +impl Velocity { + pub fn new(linear: Vec3, angular: Vec3) -> Self { + Self { linear, angular } + } +} From 37467d5066a5e86f326573127daef9e4f3310cda Mon Sep 17 00:00:00 2001 From: Florian RICHER Date: Wed, 11 Jun 2025 23:21:06 +0200 Subject: [PATCH 097/105] First entities render with ecs --- src/core/render/primitives/transform.rs | 4 +- src/core/render/primitives/velocity.rs | 4 +- src/core/scene/manager.rs | 71 +++------ src/core/timer.rs | 3 + src/game/scenes/main_scene.rs | 183 ++++++++++++++++-------- 5 files changed, 148 insertions(+), 117 deletions(-) diff --git a/src/core/render/primitives/transform.rs b/src/core/render/primitives/transform.rs index c5a6fae..85bd01b 100644 --- a/src/core/render/primitives/transform.rs +++ b/src/core/render/primitives/transform.rs @@ -1,6 +1,6 @@ use std::sync::Arc; -use bevy_ecs::resource::Resource; +use bevy_ecs::prelude::*; use glam::{Mat4, Quat, Vec3}; use vulkano::{ Validated, @@ -13,7 +13,7 @@ use crate::core::render::primitives::{AsBindableBuffer, AsVertexBuffer}; use super::command::AsRenderableMeshInstance; -#[derive(Resource, Debug, Clone)] +#[derive(Component, Debug, Clone)] pub struct Transform { pub position: Vec3, pub rotation: Quat, diff --git a/src/core/render/primitives/velocity.rs b/src/core/render/primitives/velocity.rs index 7663f3e..6e16db4 100644 --- a/src/core/render/primitives/velocity.rs +++ b/src/core/render/primitives/velocity.rs @@ -1,7 +1,7 @@ -use bevy_ecs::resource::Resource; +use bevy_ecs::prelude::*; use glam::Vec3; -#[derive(Resource, Debug, Clone)] +#[derive(Component, Debug, Clone)] pub struct Velocity { pub linear: Vec3, pub angular: Vec3, diff --git a/src/core/scene/manager.rs b/src/core/scene/manager.rs index 652e83b..47aa2e9 100644 --- a/src/core/scene/manager.rs +++ b/src/core/scene/manager.rs @@ -33,60 +33,29 @@ impl SceneManager { fn create_world_with_resources(window_context: &WindowContext) -> World { let mut world = World::new(); - // Add Vulkan resources - world.insert_resource(VulkanInstance( - window_context - .vulkan_context() - .vulkano_context() - .instance() - .clone(), - )); - world.insert_resource(VulkanDevice( - window_context - .vulkan_context() - .vulkano_context() - .device() - .clone(), - )); - world.insert_resource(VulkanGraphicsQueue( - window_context - .vulkan_context() - .vulkano_context() - .graphics_queue() - .clone(), - )); - world.insert_resource(VulkanComputeQueue( - window_context - .vulkan_context() - .vulkano_context() - .compute_queue() - .clone(), - )); - world.insert_resource(VulkanTransferQueue( - window_context - .vulkan_context() - .vulkano_context() - .transfer_queue() - .cloned(), - )); - world.insert_resource(VulkanMemoryAllocator( - window_context - .vulkan_context() - .vulkano_context() - .memory_allocator() - .clone(), - )); + let vulkan_context = window_context.vulkan_context(); + let vulkano_context = vulkan_context.vulkano_context(); + + let vulkan_instance = vulkano_context.instance(); + let vulkan_device = vulkano_context.device(); + let vulkan_graphics_queue = vulkano_context.graphics_queue(); + let vulkan_compute_queue = vulkano_context.compute_queue(); + let vulkan_transfer_queue = vulkano_context.transfer_queue(); + let vulkan_memory_allocator = vulkano_context.memory_allocator(); + let vulkan_command_buffer_allocator = vulkan_context.command_buffer_allocator(); + let vulkan_descriptor_set_allocator = vulkan_context.descriptor_set_allocator(); + + world.insert_resource(VulkanInstance(vulkan_instance.clone())); + world.insert_resource(VulkanDevice(vulkan_device.clone())); + world.insert_resource(VulkanGraphicsQueue(vulkan_graphics_queue.clone())); + world.insert_resource(VulkanComputeQueue(vulkan_compute_queue.clone())); + world.insert_resource(VulkanTransferQueue(vulkan_transfer_queue.cloned())); + world.insert_resource(VulkanMemoryAllocator(vulkan_memory_allocator.clone())); world.insert_resource(VulkanCommandBufferAllocator( - window_context - .vulkan_context() - .command_buffer_allocator() - .clone(), + vulkan_command_buffer_allocator.clone(), )); world.insert_resource(VulkanDescriptorSetAllocator( - window_context - .vulkan_context() - .descriptor_set_allocator() - .clone(), + vulkan_descriptor_set_allocator.clone(), )); world diff --git a/src/core/timer.rs b/src/core/timer.rs index 3245a4c..1efb5ad 100644 --- a/src/core/timer.rs +++ b/src/core/timer.rs @@ -1,3 +1,6 @@ +use bevy_ecs::resource::Resource; + +#[derive(Resource)] pub struct Timer { start_time: std::time::Instant, last_time: std::time::Instant, diff --git a/src/game/scenes/main_scene.rs b/src/game/scenes/main_scene.rs index 0079a5a..0ad4d61 100644 --- a/src/game/scenes/main_scene.rs +++ b/src/game/scenes/main_scene.rs @@ -7,6 +7,7 @@ use crate::core::app::context::WindowContext; use crate::core::app::user_event::UserEvent; use crate::core::render::primitives::camera::Camera3D; use crate::core::render::primitives::transform::Transform; +use crate::core::render::primitives::velocity::Velocity; use crate::core::render::primitives::vulkan_resource::{ VulkanCommandBufferAllocator, VulkanDescriptorSetAllocator, VulkanDevice, VulkanGraphicsQueue, VulkanMemoryAllocator, @@ -17,7 +18,10 @@ use crate::core::render::resources::meshes::{ObjMesh, SquareMesh}; use crate::core::render::resources::pipeline::PipelineLoader; use crate::core::render::resources::texture::{TextureLoadInfo, TextureLoader, TextureSourceKind}; use crate::core::scene::AsScene; +use crate::core::timer::Timer; use crate::game::assets::pipelines::simple::SimplePipeline; +use bevy_ecs::prelude::*; +use bevy_ecs::schedule::Schedule; use bevy_ecs::world::World; use egui_winit_vulkano::egui; use glam::EulerRot; @@ -31,15 +35,20 @@ use vulkano::{ }; use winit::window::CursorGrabMode; +#[derive(Component)] +pub struct Square; + +#[derive(Component)] +pub struct Cube; + pub struct MainSceneState { texture_loader: TextureLoader, pipeline_loader: PipelineLoader, square: SquareMesh, obj: ObjMesh, - square_instances: Vec, - obj_instances: Vec, camera: Camera3D, speed: f32, + scheduler: Schedule, } #[derive(Default)] @@ -111,45 +120,6 @@ impl AsScene for MainScene { obj.into_iter().next().unwrap() }; - let num_instances = 100; - let instance_size = 10.0; - let instance_spacing = 10.0; - let num_instances_per_row = (num_instances as f32 / instance_spacing).ceil() as u32; - let square_instances: Vec = (0..num_instances) - .map(|i| { - Transform::new( - Vec3::new( - (i % num_instances_per_row) as f32 * (instance_spacing + instance_size), - 0.0, - (i / num_instances_per_row) as f32 * (instance_spacing + instance_size), - ), - Quat::from_euler(EulerRot::XYZ, 0.0, rand::random_range(0.0..=360.0), 0.0), - Vec3::new(instance_size, instance_size, instance_size), - ) - }) - .collect(); - - let obj_instances: Vec = (0..num_instances) - .map(|i| { - Transform::new( - Vec3::new( - (i % num_instances_per_row) as f32 * (instance_spacing + instance_size), - 0.0, - (i / num_instances_per_row) as f32 - * (instance_spacing + instance_size) - * -1.0 - - instance_spacing * 2.0, - ), - Quat::from_euler(EulerRot::XYZ, 0.0, rand::random_range(0.0..=360.0), 0.0), - Vec3::new( - instance_size * 0.5, - instance_size * 0.5, - instance_size * 0.5, - ), - ) - }) - .collect(); - let camera = window_context.with_renderer(|renderer| { Camera3D::new( renderer.aspect_ratio(), @@ -159,14 +129,20 @@ impl AsScene for MainScene { ) }); + let mut scheduler = Schedule::default(); + scheduler.add_systems(update_velocity_system); + scheduler.add_systems(update_timer_system); + + world.insert_resource(Timer::new()); + Self::create_entities(world, 100, 10.0, 10.0); + self.state = Some(MainSceneState { square, obj, pipeline_loader, - square_instances, - obj_instances, camera, speed: 50.0, + scheduler, texture_loader, }); @@ -175,7 +151,7 @@ impl AsScene for MainScene { fn update( &mut self, - _world: &mut World, + world: &mut World, window_context: &WindowContext, ) -> Result<(), Box> { let state = self.state.as_mut().unwrap(); @@ -192,20 +168,7 @@ impl AsScene for MainScene { }); }); - let delta_time = window_context.get_delta_time(); - for (i, instance) in state.square_instances.iter_mut().enumerate() { - let rotation_speed = (i % 10) as f32; - let rotation_delta = Quat::from_rotation_y(rotation_speed * delta_time); - - instance.rotate(rotation_delta); - } - - for (i, instance) in state.obj_instances.iter_mut().enumerate() { - let rotation_speed = (i % 10) as f32; - let rotation_delta = Quat::from_rotation_y(rotation_speed * delta_time); - - instance.rotate(rotation_delta); - } + state.scheduler.run(world); if window_context .with_input_manager(|input_manager| input_manager.get_virtual_input_state("mouse_left")) @@ -276,13 +239,23 @@ impl AsScene for MainScene { .camera .create_buffer(VulkanMemoryAllocator::get_from_world(world))?, ); + let square_transforms = world + .query_filtered::<&Transform, With>() + .iter(&world) + .cloned() + .collect::>(); let square_transform_uniform = Transform::create_buffer( VulkanMemoryAllocator::get_from_world(world), - &state.square_instances, + &square_transforms, )?; - let obj_transform_uniform = Transform::create_buffer( + let cube_transforms = world + .query_filtered::<&Transform, With>() + .iter(&world) + .cloned() + .collect::>(); + let cube_transform_uniform = Transform::create_buffer( VulkanMemoryAllocator::get_from_world(world), - &state.obj_instances, + &cube_transforms, )?; state @@ -309,7 +282,7 @@ impl AsScene for MainScene { &VulkanDescriptorSetAllocator::get_from_world(world), pipeline, &state.obj, - &obj_transform_uniform, + &cube_transform_uniform, vec![ camera_uniform.clone() as Arc, state @@ -400,3 +373,89 @@ impl AsScene for MainScene { self.state = None; } } + +impl MainScene { + // Function to create entities in the ECS world + fn create_entities( + world: &mut World, + num_instances: u32, + instance_size: f32, + instance_spacing: f32, + ) { + let num_instances_per_row = (num_instances as f32 / instance_spacing).ceil() as u32; + + let square_instances = (0..num_instances) + .map(|i| { + let x_index = i % num_instances_per_row; + let z_index = i / num_instances_per_row; + + let transform = Transform::new( + Vec3::new( + x_index as f32 * (instance_spacing + instance_size), + 0.0, + z_index as f32 * (instance_spacing + instance_size), + ), + Quat::from_euler(EulerRot::XYZ, 0.0, rand::random_range(0.0..=360.0), 0.0), + Vec3::new(instance_size, instance_size, instance_size), + ); + + let velocity = Velocity::new(Vec3::ZERO, Vec3::new(0.0, x_index as f32, 0.0)); + + (Square, transform, velocity) + }) + .collect::>(); + + world.spawn_batch(square_instances); + + let cube_instances = (0..num_instances) + .map(|i| { + let x_index = i % num_instances_per_row; + let z_index = i / num_instances_per_row; + + let transform = Transform::new( + Vec3::new( + x_index as f32 * (instance_spacing + instance_size), + 0.0, + z_index as f32 * (instance_spacing + instance_size) * -1.0 + - instance_spacing * 2.0, + ), + Quat::from_euler(EulerRot::XYZ, 0.0, rand::random_range(0.0..=360.0), 0.0), + Vec3::new( + instance_size * 0.5, + instance_size * 0.5, + instance_size * 0.5, + ), + ); + + let velocity = Velocity::new(Vec3::ZERO, Vec3::new(0.0, x_index as f32, 0.0)); + + (Cube, transform, velocity) + }) + .collect::>(); + + world.spawn_batch(cube_instances); + } +} + +fn update_velocity_system(mut query: Query<(&mut Transform, &Velocity)>, time: Res) { + for (mut transform, velocity) in query.iter_mut() { + let delta_time = time.delta_time(); + + // Update linear position + transform.translate(velocity.linear * delta_time); + + // Update angular rotation + let angular_delta = velocity.angular * delta_time; + let rotation_delta = Quat::from_euler( + EulerRot::XYZ, + angular_delta.x, + angular_delta.y, + angular_delta.z, + ); + transform.rotate(rotation_delta); + } +} + +fn update_timer_system(mut timer: ResMut) { + timer.update(); +} From 6a0491fe51e88d7ad1bff46021a33c0cedcb988c Mon Sep 17 00:00:00 2001 From: Florian RICHER Date: Thu, 12 Jun 2025 12:56:04 +0200 Subject: [PATCH 098/105] Move texture loader in Resource --- src/core/render/resources/texture/loader.rs | 26 ++++++++++++++------- src/core/scene/manager.rs | 15 +++++++----- src/game/scenes/main_scene.rs | 18 ++++---------- 3 files changed, 31 insertions(+), 28 deletions(-) diff --git a/src/core/render/resources/texture/loader.rs b/src/core/render/resources/texture/loader.rs index 38a365c..b408003 100644 --- a/src/core/render/resources/texture/loader.rs +++ b/src/core/render/resources/texture/loader.rs @@ -15,7 +15,7 @@ use crate::core::render::primitives::vulkan_resource::{ VulkanCommandBufferAllocator, VulkanDevice, VulkanGraphicsQueue, VulkanMemoryAllocator, VulkanTransferQueue, }; -use bevy_ecs::world::World; +use bevy_ecs::{resource::Resource, world::World}; use super::Texture; @@ -30,6 +30,7 @@ pub struct TextureLoadInfo { pub image_format: Format, } +#[derive(Resource)] pub struct TextureLoader { loaded_textures: HashMap>, pending_textures: HashMap, @@ -40,19 +41,27 @@ pub struct TextureLoader { } impl TextureLoader { - pub fn new(world: &World) -> Self { + pub fn new( + device: Arc, + command_buffer_allocator: Arc, + memory_allocator: Arc, + queue: Arc, + ) -> Self { Self { loaded_textures: HashMap::new(), pending_textures: HashMap::new(), - device: VulkanDevice::get_from_world(world).clone(), - command_buffer_allocator: VulkanCommandBufferAllocator::get_from_world(world).clone(), - memory_allocator: VulkanMemoryAllocator::get_from_world(world).clone(), - queue: Self::select_best_suitable_queue(world), + device, + command_buffer_allocator, + memory_allocator, + queue, } } - fn select_best_suitable_queue(world: &World) -> Arc { - VulkanTransferQueue::get_from_world(world) + pub fn select_best_suitable_queue( + graphics_queue: &Arc, + transfer_queue: Option<&Arc>, + ) -> Arc { + transfer_queue .map(|queue| { tracing::trace!( "Select transfer queue for texture loading with family index: {:?}", @@ -61,7 +70,6 @@ impl TextureLoader { queue.clone() }) .unwrap_or_else(|| { - let graphics_queue = VulkanGraphicsQueue::get_from_world(world); tracing::trace!( "Select graphics queue for texture loading with family index: {:?}", graphics_queue.queue_family_index() diff --git a/src/core/scene/manager.rs b/src/core/scene/manager.rs index 47aa2e9..e787dcb 100644 --- a/src/core/scene/manager.rs +++ b/src/core/scene/manager.rs @@ -7,6 +7,7 @@ use crate::core::render::primitives::vulkan_resource::{ VulkanCommandBufferAllocator, VulkanComputeQueue, VulkanDescriptorSetAllocator, VulkanDevice, VulkanGraphicsQueue, VulkanInstance, VulkanMemoryAllocator, VulkanTransferQueue, }; +use crate::core::render::resources::texture::TextureLoader; use bevy_ecs::world::World; use super::{AsScene, Scene}; @@ -14,7 +15,6 @@ use super::{AsScene, Scene}; pub struct SceneManager { scenes: Vec, current_scene_index: Option, - window_context: Option>>, } impl SceneManager { @@ -22,14 +22,9 @@ impl SceneManager { Self { scenes: Vec::new(), current_scene_index: None, - window_context: None, } } - pub fn set_window_context(&mut self, window_context: Rc>) { - self.window_context = Some(window_context); - } - fn create_world_with_resources(window_context: &WindowContext) -> World { let mut world = World::new(); @@ -45,6 +40,13 @@ impl SceneManager { let vulkan_command_buffer_allocator = vulkan_context.command_buffer_allocator(); let vulkan_descriptor_set_allocator = vulkan_context.descriptor_set_allocator(); + let texture_loader = TextureLoader::new( + vulkan_device.clone(), + vulkan_command_buffer_allocator.clone(), + vulkan_memory_allocator.clone(), + TextureLoader::select_best_suitable_queue(vulkan_graphics_queue, vulkan_transfer_queue), + ); + world.insert_resource(VulkanInstance(vulkan_instance.clone())); world.insert_resource(VulkanDevice(vulkan_device.clone())); world.insert_resource(VulkanGraphicsQueue(vulkan_graphics_queue.clone())); @@ -57,6 +59,7 @@ impl SceneManager { world.insert_resource(VulkanDescriptorSetAllocator( vulkan_descriptor_set_allocator.clone(), )); + world.insert_resource(texture_loader); world } diff --git a/src/game/scenes/main_scene.rs b/src/game/scenes/main_scene.rs index 0ad4d61..69cb263 100644 --- a/src/game/scenes/main_scene.rs +++ b/src/game/scenes/main_scene.rs @@ -42,7 +42,6 @@ pub struct Square; pub struct Cube; pub struct MainSceneState { - texture_loader: TextureLoader, pipeline_loader: PipelineLoader, square: SquareMesh, obj: ObjMesh, @@ -81,7 +80,7 @@ impl AsScene for MainScene { pipeline_loader.register::()?; pipeline_loader.load_pending_pipelines()?; - let mut texture_loader = TextureLoader::new(world); + let mut texture_loader = world.resource_mut::(); texture_loader.add_texture( "wooden-crate".to_string(), TextureLoadInfo { @@ -143,7 +142,6 @@ impl AsScene for MainScene { camera, speed: 50.0, scheduler, - texture_loader, }); Ok(()) @@ -258,6 +256,8 @@ impl AsScene for MainScene { &cube_transforms, )?; + let texture_loader = world.resource::(); + state .pipeline_loader .with_pipeline::(|pipeline| { @@ -269,11 +269,7 @@ impl AsScene for MainScene { &square_transform_uniform, vec![ camera_uniform.clone() as Arc, - state - .texture_loader - .get_texture("wooden-crate") - .unwrap() - .clone(), + texture_loader.get_texture("wooden-crate").unwrap().clone(), ], )?; @@ -285,11 +281,7 @@ impl AsScene for MainScene { &cube_transform_uniform, vec![ camera_uniform.clone() as Arc, - state - .texture_loader - .get_texture("cube-diffuse") - .unwrap() - .clone(), + texture_loader.get_texture("cube-diffuse").unwrap().clone(), ], )?; From 9c651c5e0ad58eff273f5b38019ae67f2c9fcde7 Mon Sep 17 00:00:00 2001 From: Florian RICHER Date: Thu, 12 Jun 2025 12:56:43 +0200 Subject: [PATCH 099/105] Avoid use borrow_mut in with_renderer --- src/core/app/context.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/app/context.rs b/src/core/app/context.rs index a12bb3c..c247443 100644 --- a/src/core/app/context.rs +++ b/src/core/app/context.rs @@ -104,7 +104,7 @@ impl WindowContext { where F: FnOnce(&VulkanoWindowRenderer) -> T, { - let vulkano_windows = self.vulkano_windows.borrow_mut(); + let vulkano_windows = self.vulkano_windows.borrow(); let renderer = vulkano_windows .get_renderer(self.window_id) .expect("Failed to get renderer"); From 6ba61e040e1018c1a935b30686f667137820a10a Mon Sep 17 00:00:00 2001 From: Florian RICHER Date: Thu, 12 Jun 2025 14:24:11 +0200 Subject: [PATCH 100/105] move pipeline loader in world --- src/core/app/mod.rs | 10 +-- src/core/render/resources/pipeline/loader.rs | 2 + src/core/render/resources/texture/loader.rs | 6 +- src/core/scene/manager.rs | 81 +++++++++----------- src/game/scenes/main_scene.rs | 70 +++++++---------- 5 files changed, 70 insertions(+), 99 deletions(-) diff --git a/src/core/app/mod.rs b/src/core/app/mod.rs index c62e4d5..d951acb 100644 --- a/src/core/app/mod.rs +++ b/src/core/app/mod.rs @@ -113,10 +113,7 @@ impl ApplicationHandler for App { // Now use the created context to load the scene let mut scene_manager = SceneManager::new(); - { - let context = app_context.borrow(); - scene_manager.load_scene(Box::new(MainScene::default()), &context); - } + scene_manager.set_new_scene(Box::new(MainScene::default())); self.scene_manager.insert(window_id, scene_manager); } @@ -240,10 +237,7 @@ impl ApplicationHandler for App { } UserEvent::ChangeScene(window_id, scene) => { if let Some(scene_manager) = self.scene_manager.get_mut(&window_id) { - if let Some(app_context) = self.app_contexts.get(&window_id) { - let context = app_context.borrow(); - scene_manager.replace_current_scene(scene, &context); - } + scene_manager.set_new_scene(scene); } } UserEvent::ChangeResolution(window_id, width, height) => { diff --git a/src/core/render/resources/pipeline/loader.rs b/src/core/render/resources/pipeline/loader.rs index e5e0aeb..92c5f9a 100644 --- a/src/core/render/resources/pipeline/loader.rs +++ b/src/core/render/resources/pipeline/loader.rs @@ -5,6 +5,7 @@ use std::{ sync::{Arc, RwLock}, }; +use bevy_ecs::resource::Resource; use vulkano::{device::Device, format::Format, pipeline::GraphicsPipeline}; use super::{GraphicsPipelineLoadFn, LoadableGraphicsPipeline}; @@ -15,6 +16,7 @@ pub enum PipelineState { Loaded, } +#[derive(Resource)] pub struct PipelineLoader { device: Arc, swapchain_image_format: Format, diff --git a/src/core/render/resources/texture/loader.rs b/src/core/render/resources/texture/loader.rs index b408003..2f34d4b 100644 --- a/src/core/render/resources/texture/loader.rs +++ b/src/core/render/resources/texture/loader.rs @@ -11,11 +11,7 @@ use vulkano::{ memory::allocator::StandardMemoryAllocator, }; -use crate::core::render::primitives::vulkan_resource::{ - VulkanCommandBufferAllocator, VulkanDevice, VulkanGraphicsQueue, VulkanMemoryAllocator, - VulkanTransferQueue, -}; -use bevy_ecs::{resource::Resource, world::World}; +use bevy_ecs::resource::Resource; use super::Texture; diff --git a/src/core/scene/manager.rs b/src/core/scene/manager.rs index e787dcb..5f63f4e 100644 --- a/src/core/scene/manager.rs +++ b/src/core/scene/manager.rs @@ -1,33 +1,42 @@ -use std::cell::RefCell; use std::error::Error; -use std::rc::Rc; +use crate::core::app::DEPTH_IMAGE_ID; use crate::core::app::context::WindowContext; use crate::core::render::primitives::vulkan_resource::{ VulkanCommandBufferAllocator, VulkanComputeQueue, VulkanDescriptorSetAllocator, VulkanDevice, VulkanGraphicsQueue, VulkanInstance, VulkanMemoryAllocator, VulkanTransferQueue, }; +use crate::core::render::resources::pipeline::PipelineLoader; use crate::core::render::resources::texture::TextureLoader; +use crate::core::scene::AsScene; use bevy_ecs::world::World; -use super::{AsScene, Scene}; +use super::Scene; +#[derive(Default)] pub struct SceneManager { - scenes: Vec, - current_scene_index: Option, + current_scene: Option, + new_scene: Option>, } impl SceneManager { pub fn new() -> Self { Self { - scenes: Vec::new(), - current_scene_index: None, + current_scene: None, + new_scene: None, } } - fn create_world_with_resources(window_context: &WindowContext) -> World { + fn create_world_with_resources(window_context: &mut WindowContext) -> World { let mut world = World::new(); + let depth_image_view = window_context.with_renderer_mut(|renderer| { + renderer.get_additional_image_view(DEPTH_IMAGE_ID).clone() + }); + + let swapchain_image_view = + window_context.with_renderer(|renderer| renderer.swapchain_image_view().clone()); + let vulkan_context = window_context.vulkan_context(); let vulkano_context = vulkan_context.vulkano_context(); @@ -47,6 +56,12 @@ impl SceneManager { TextureLoader::select_best_suitable_queue(vulkan_graphics_queue, vulkan_transfer_queue), ); + let pipeline_loader = PipelineLoader::new( + vulkan_device.clone(), + swapchain_image_view.format(), + depth_image_view.format(), + ); + world.insert_resource(VulkanInstance(vulkan_instance.clone())); world.insert_resource(VulkanDevice(vulkan_device.clone())); world.insert_resource(VulkanGraphicsQueue(vulkan_graphics_queue.clone())); @@ -60,59 +75,37 @@ impl SceneManager { vulkan_descriptor_set_allocator.clone(), )); world.insert_resource(texture_loader); + world.insert_resource(pipeline_loader); world } - pub fn load_scene(&mut self, scene_impl: Box, window_context: &WindowContext) { - let world = Self::create_world_with_resources(window_context); - let scene = Scene::new(scene_impl, world); - self.scenes.push(scene); - self.current_scene_index = Some(self.scenes.len() - 1); - } - - pub fn replace_current_scene( - &mut self, - scene_impl: Box, - window_context: &WindowContext, - ) { - if let Some(index) = self.current_scene_index { - if index < self.scenes.len() { - self.scenes[index].unload(); - let world = Self::create_world_with_resources(window_context); - self.scenes[index] = Scene::new(scene_impl, world); - } - } else { - self.load_scene(scene_impl, window_context); - } + pub fn set_new_scene(&mut self, scene_impl: Box) { + self.new_scene = Some(scene_impl); } pub fn current_scene(&self) -> Option<&Scene> { - if let Some(index) = self.current_scene_index { - self.scenes.get(index) - } else { - None - } + self.current_scene.as_ref() } pub fn current_scene_mut(&mut self) -> Option<&mut Scene> { - if let Some(index) = self.current_scene_index { - self.scenes.get_mut(index) - } else { - None - } + self.current_scene.as_mut() } pub fn load_scene_if_not_loaded( &mut self, window_context: &mut WindowContext, ) -> Result<(), Box> { - if let Some(scene) = self.current_scene_mut() { - if !scene.loaded() { - scene.load(window_context)?; + if let Some(new_scene) = self.new_scene.take() { + let world = Self::create_world_with_resources(window_context); + let mut scene = Scene::new(new_scene, world); + scene.load(window_context)?; + + if let Some(mut current_scene) = self.current_scene.take() { + current_scene.unload(); } - } else { - tracing::warn!("No scene found in SceneManager!"); + + self.current_scene = Some(scene); } Ok(()) } diff --git a/src/game/scenes/main_scene.rs b/src/game/scenes/main_scene.rs index 69cb263..6e561bb 100644 --- a/src/game/scenes/main_scene.rs +++ b/src/game/scenes/main_scene.rs @@ -9,7 +9,7 @@ use crate::core::render::primitives::camera::Camera3D; use crate::core::render::primitives::transform::Transform; use crate::core::render::primitives::velocity::Velocity; use crate::core::render::primitives::vulkan_resource::{ - VulkanCommandBufferAllocator, VulkanDescriptorSetAllocator, VulkanDevice, VulkanGraphicsQueue, + VulkanCommandBufferAllocator, VulkanDescriptorSetAllocator, VulkanGraphicsQueue, VulkanMemoryAllocator, }; use crate::core::render::primitives::{AsDescriptorSet, AsRecordable}; @@ -42,7 +42,6 @@ pub struct Square; pub struct Cube; pub struct MainSceneState { - pipeline_loader: PipelineLoader, square: SquareMesh, obj: ObjMesh, camera: Camera3D, @@ -65,18 +64,7 @@ impl AsScene for MainScene { world: &mut World, window_context: &mut WindowContext, ) -> Result<(), Box> { - let depth_image_view = window_context.with_renderer_mut(|renderer| { - renderer.get_additional_image_view(DEPTH_IMAGE_ID).clone() - }); - - let swapchain_image_view = - window_context.with_renderer(|renderer| renderer.swapchain_image_view().clone()); - - let mut pipeline_loader = PipelineLoader::new( - VulkanDevice::get_from_world(world).clone(), - swapchain_image_view.format(), - depth_image_view.format(), - ); + let mut pipeline_loader = world.resource_mut::(); pipeline_loader.register::()?; pipeline_loader.load_pending_pipelines()?; @@ -138,7 +126,6 @@ impl AsScene for MainScene { self.state = Some(MainSceneState { square, obj, - pipeline_loader, camera, speed: 50.0, scheduler, @@ -257,36 +244,35 @@ impl AsScene for MainScene { )?; let texture_loader = world.resource::(); + let pipeline_loader = world.resource::(); - state - .pipeline_loader - .with_pipeline::(|pipeline| { - SimplePipeline::record_commands( - &mut builder, - &VulkanDescriptorSetAllocator::get_from_world(world), - pipeline, - &state.square, - &square_transform_uniform, - vec![ - camera_uniform.clone() as Arc, - texture_loader.get_texture("wooden-crate").unwrap().clone(), - ], - )?; + pipeline_loader.with_pipeline::(|pipeline| { + SimplePipeline::record_commands( + &mut builder, + &VulkanDescriptorSetAllocator::get_from_world(world), + pipeline, + &state.square, + &square_transform_uniform, + vec![ + camera_uniform.clone() as Arc, + texture_loader.get_texture("wooden-crate").unwrap().clone(), + ], + )?; - SimplePipeline::record_commands( - &mut builder, - &VulkanDescriptorSetAllocator::get_from_world(world), - pipeline, - &state.obj, - &cube_transform_uniform, - vec![ - camera_uniform.clone() as Arc, - texture_loader.get_texture("cube-diffuse").unwrap().clone(), - ], - )?; + SimplePipeline::record_commands( + &mut builder, + &VulkanDescriptorSetAllocator::get_from_world(world), + pipeline, + &state.obj, + &cube_transform_uniform, + vec![ + camera_uniform.clone() as Arc, + texture_loader.get_texture("cube-diffuse").unwrap().clone(), + ], + )?; - Ok(()) - })?; + Ok(()) + })?; RenderPassManager::end_rendering(&mut builder)?; From 07056fc0ce7265729179dd98fed45f8a5595dadd Mon Sep 17 00:00:00 2001 From: Florian RICHER Date: Thu, 12 Jun 2025 19:36:49 +0200 Subject: [PATCH 101/105] Move input_manager and camera as resource --- src/core/app/context.rs | 12 --- src/core/input/mod.rs | 9 ++- src/core/render/primitives/camera.rs | 6 +- src/core/scene/manager.rs | 2 + src/game/scenes/main_scene.rs | 111 ++++++++++++++------------- 5 files changed, 72 insertions(+), 68 deletions(-) diff --git a/src/core/app/context.rs b/src/core/app/context.rs index c247443..ca37b65 100644 --- a/src/core/app/context.rs +++ b/src/core/app/context.rs @@ -141,18 +141,6 @@ impl WindowContext { f(&mut gui) } - /// Méthode utilitaire pour accéder à l'input manager de manière thread-safe - pub fn with_input_manager(&self, f: F) -> T - where - F: FnOnce(&InputManager) -> T, - { - let input_manager = self - .input_manager - .read() - .expect("Failed to lock input_manager"); - f(&input_manager) - } - /// Méthode utilitaire pour accéder au timer de manière thread-safe pub fn with_timer(&self, f: F) -> T where diff --git a/src/core/input/mod.rs b/src/core/input/mod.rs index f4a1642..aa3c85f 100644 --- a/src/core/input/mod.rs +++ b/src/core/input/mod.rs @@ -1,5 +1,9 @@ -use std::collections::HashMap; +use std::{ + collections::HashMap, + sync::{Arc, RwLock}, +}; +use bevy_ecs::resource::Resource; use cache::{CachedElementState, CachedMovement}; use virtual_input::VirtualInput; use winit::{ @@ -22,6 +26,9 @@ pub struct InputManager { virtual_input: VirtualInput, } +#[derive(Resource)] +pub struct InputManagerResource(pub Arc>); + impl std::fmt::Debug for InputManager { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("InputManager") diff --git a/src/core/render/primitives/camera.rs b/src/core/render/primitives/camera.rs index 1841079..f25c41c 100644 --- a/src/core/render/primitives/camera.rs +++ b/src/core/render/primitives/camera.rs @@ -1,5 +1,6 @@ use std::{f32::consts::FRAC_PI_2, sync::Arc}; +use bevy_ecs::resource::Resource; use glam::{Mat4, Vec3, Vec4}; use vulkano::{ Validated, @@ -19,7 +20,7 @@ const OPENGL_TO_VULKAN_Y_AXIS_FLIP: Mat4 = Mat4 { w_axis: Vec4::new(0.0, 0.0, 0.0, 1.0), }; -#[derive(Default)] +#[derive(Resource)] pub struct Camera3D { projection: Mat4, @@ -50,7 +51,6 @@ impl Camera3D { timer: &Timer, movement_speed: f32, camera_sensitivity: f32, - window_aspect_ratio: f32, ) { // Process camera rotation let camera_delta = camera_sensitivity * timer.delta_time(); @@ -79,7 +79,9 @@ impl Camera3D { let tz = input_manager.get_virtual_input_state("move_forward") * movement_delta; self.position += tz * forward; + } + pub fn update_projection(&mut self, window_aspect_ratio: f32) { if self.aspect_ratio != window_aspect_ratio { self.aspect_ratio = window_aspect_ratio; self.projection = diff --git a/src/core/scene/manager.rs b/src/core/scene/manager.rs index 5f63f4e..6d59be3 100644 --- a/src/core/scene/manager.rs +++ b/src/core/scene/manager.rs @@ -2,6 +2,7 @@ use std::error::Error; use crate::core::app::DEPTH_IMAGE_ID; use crate::core::app::context::WindowContext; +use crate::core::input::InputManagerResource; use crate::core::render::primitives::vulkan_resource::{ VulkanCommandBufferAllocator, VulkanComputeQueue, VulkanDescriptorSetAllocator, VulkanDevice, VulkanGraphicsQueue, VulkanInstance, VulkanMemoryAllocator, VulkanTransferQueue, @@ -76,6 +77,7 @@ impl SceneManager { )); world.insert_resource(texture_loader); world.insert_resource(pipeline_loader); + world.insert_resource(InputManagerResource(window_context.input_manager.clone())); world } diff --git a/src/game/scenes/main_scene.rs b/src/game/scenes/main_scene.rs index 6e561bb..45e7dfa 100644 --- a/src/game/scenes/main_scene.rs +++ b/src/game/scenes/main_scene.rs @@ -5,6 +5,7 @@ use super::settings_scene::SettingsScene; use crate::core::app::DEPTH_IMAGE_ID; use crate::core::app::context::WindowContext; use crate::core::app::user_event::UserEvent; +use crate::core::input::InputManagerResource; use crate::core::render::primitives::camera::Camera3D; use crate::core::render::primitives::transform::Transform; use crate::core::render::primitives::velocity::Velocity; @@ -44,11 +45,12 @@ pub struct Cube; pub struct MainSceneState { square: SquareMesh, obj: ObjMesh, - camera: Camera3D, - speed: f32, scheduler: Schedule, } +#[derive(Resource)] +pub struct CameraSpeed(f32); + #[derive(Default)] pub struct MainScene { state: Option, @@ -115,19 +117,19 @@ impl AsScene for MainScene { 1000.0, ) }); + world.insert_resource(CameraSpeed(50.0)); + world.insert_resource(camera); let mut scheduler = Schedule::default(); scheduler.add_systems(update_velocity_system); scheduler.add_systems(update_timer_system); - + scheduler.add_systems(update_camera_system); world.insert_resource(Timer::new()); Self::create_entities(world, 100, 10.0, 10.0); self.state = Some(MainSceneState { square, obj, - camera, - speed: 50.0, scheduler, }); @@ -141,50 +143,41 @@ impl AsScene for MainScene { ) -> Result<(), Box> { let state = self.state.as_mut().unwrap(); - window_context.with_input_manager(|input_manager| { - window_context.with_timer(|timer| { - state.camera.update( - input_manager, - timer, - state.speed, - 10.0, - window_context.get_aspect_ratio(), - ); - }); - }); + { + let mut camera = world.resource_mut::(); + camera.update_projection(window_context.get_aspect_ratio()); + } + + { + let input_manager = world.resource::().0.read().unwrap(); + + if input_manager.get_virtual_input_state("mouse_left") > 0.0 { + let _ = window_context + .event_loop_proxy + .send_event(UserEvent::CursorVisible(window_context.window_id, false)); + let _ = window_context + .event_loop_proxy + .send_event(UserEvent::CursorGrabMode( + window_context.window_id, + CursorGrabMode::Locked, + )); + } + + if input_manager.get_virtual_input_state("mouse_right") > 0.0 { + let _ = window_context + .event_loop_proxy + .send_event(UserEvent::CursorVisible(window_context.window_id, true)); + let _ = window_context + .event_loop_proxy + .send_event(UserEvent::CursorGrabMode( + window_context.window_id, + CursorGrabMode::None, + )); + } + } state.scheduler.run(world); - if window_context - .with_input_manager(|input_manager| input_manager.get_virtual_input_state("mouse_left")) - > 0.0 - { - let _ = window_context - .event_loop_proxy - .send_event(UserEvent::CursorVisible(window_context.window_id, false)); - let _ = window_context - .event_loop_proxy - .send_event(UserEvent::CursorGrabMode( - window_context.window_id, - CursorGrabMode::Locked, - )); - } - - if window_context.with_input_manager(|input_manager| { - input_manager.get_virtual_input_state("mouse_right") - }) > 0.0 - { - let _ = window_context - .event_loop_proxy - .send_event(UserEvent::CursorVisible(window_context.window_id, true)); - let _ = window_context - .event_loop_proxy - .send_event(UserEvent::CursorGrabMode( - window_context.window_id, - CursorGrabMode::None, - )); - } - Ok(()) } @@ -220,8 +213,8 @@ impl AsScene for MainScene { // Create camera uniform using the actual camera let camera_uniform = Arc::new( - state - .camera + world + .resource::() .create_buffer(VulkanMemoryAllocator::get_from_world(world))?, ); let square_transforms = world @@ -285,13 +278,15 @@ impl AsScene for MainScene { let swapchain_image_view = window_context.with_renderer(|renderer| renderer.swapchain_image_view().clone()); - let input_manager_status = - window_context.with_input_manager(|input_manager| format!("{:#?}", input_manager)); + let input_manager_status = { + let input_manager = world.resource::().0.read().unwrap(); + format!("{:#?}", input_manager) + }; let event_loop_proxy = window_context.event_loop_proxy.clone(); let delta_time = window_context.get_delta_time(); let window_id = window_context.window_id; let window_size = window_context.get_window_size(); - + let camera = world.resource::(); let render_future = window_context.with_gui_mut(|gui| { gui.immediate_ui(|gui| { let ctx = gui.context(); @@ -323,7 +318,7 @@ impl AsScene for MainScene { ui.separator(); ui.label("Position caméra:"); - let position = state.camera.get_position(); + let position = camera.get_position(); ui.label(format!(" X: {:.2}", position[0])); ui.label(format!(" Y: {:.2}", position[1])); ui.label(format!(" Z: {:.2}", position[2])); @@ -331,7 +326,7 @@ impl AsScene for MainScene { ui.separator(); ui.label("Rotation caméra:"); - let rotation = state.camera.get_rotation(); + let rotation = camera.get_rotation(); ui.label(format!(" Yaw: {:.2}°", rotation.y.to_degrees())); ui.label(format!(" Pitch: {:.2}°", rotation.x.to_degrees())); @@ -437,3 +432,13 @@ fn update_velocity_system(mut query: Query<(&mut Transform, &Velocity)>, time: R fn update_timer_system(mut timer: ResMut) { timer.update(); } + +fn update_camera_system( + mut camera: ResMut, + input_manager: Res, + timer: Res, + camera_speed: Res, +) { + let input_manager = input_manager.0.read().unwrap(); + camera.update(&input_manager, &timer, camera_speed.0, 10.0); +} From 51a3f812fe731f3b593b1cd2f5fda153a7688a7a Mon Sep 17 00:00:00 2001 From: Florian RICHER Date: Thu, 12 Jun 2025 21:07:49 +0200 Subject: [PATCH 102/105] Migrate Camera Resource to Component --- src/core/render/primitives/camera.rs | 70 +++------------- src/core/render/primitives/transform.rs | 24 ------ src/core/render/primitives/velocity.rs | 2 +- src/game/scenes/main_scene.rs | 104 ++++++++++++++++++------ 4 files changed, 92 insertions(+), 108 deletions(-) diff --git a/src/core/render/primitives/camera.rs b/src/core/render/primitives/camera.rs index f25c41c..008ecac 100644 --- a/src/core/render/primitives/camera.rs +++ b/src/core/render/primitives/camera.rs @@ -1,6 +1,6 @@ -use std::{f32::consts::FRAC_PI_2, sync::Arc}; +use std::sync::Arc; -use bevy_ecs::resource::Resource; +use bevy_ecs::component::Component; use glam::{Mat4, Vec3, Vec4}; use vulkano::{ Validated, @@ -8,8 +8,6 @@ use vulkano::{ memory::allocator::StandardMemoryAllocator, }; -use crate::core::{input::InputManager, timer::Timer}; - use super::{AsUniformBuffer, mvp::Mvp}; // See docs/OPENGL_VULKAN_DIFF.md @@ -20,24 +18,25 @@ const OPENGL_TO_VULKAN_Y_AXIS_FLIP: Mat4 = Mat4 { w_axis: Vec4::new(0.0, 0.0, 0.0, 1.0), }; -#[derive(Resource)] +#[derive(Component)] pub struct Camera3D { projection: Mat4, - - position: Vec3, - rotation: Vec3, aspect_ratio: f32, fov: f32, near: f32, far: f32, } +#[derive(Component)] +pub struct Camera3DTransform { + pub position: Vec3, + pub rotation: Vec3, +} + impl Camera3D { pub fn new(aspect_ratio: f32, fov: f32, near: f32, far: f32) -> Self { Self { projection: Mat4::perspective_rh(fov, aspect_ratio, near, far), - position: Vec3::ZERO, - rotation: Vec3::ZERO, aspect_ratio, fov, near, @@ -45,42 +44,6 @@ impl Camera3D { } } - pub fn update( - &mut self, - input_manager: &InputManager, - timer: &Timer, - movement_speed: f32, - camera_sensitivity: f32, - ) { - // Process camera rotation - let camera_delta = camera_sensitivity * timer.delta_time(); - self.rotation += Vec3::new( - (input_manager.get_virtual_input_state("mouse_y") * camera_delta).to_radians(), - (input_manager.get_virtual_input_state("mouse_x") * camera_delta).to_radians(), - 0.0, - ); - - if self.rotation.x > FRAC_PI_2 { - self.rotation = Vec3::new(FRAC_PI_2, self.rotation.y, 0.0); - } - - if self.rotation.x < -FRAC_PI_2 { - self.rotation = Vec3::new(-FRAC_PI_2, self.rotation.y, 0.0); - } - - let movement_delta = movement_speed * timer.delta_time(); - - let (yaw_sin, yaw_cos) = self.rotation.y.sin_cos(); - let forward = Vec3::new(yaw_cos, 0.0, yaw_sin).normalize(); - let right = Vec3::new(-yaw_sin, 0.0, yaw_cos).normalize(); - - let tx = input_manager.get_virtual_input_state("move_right") * movement_delta; - self.position += tx * right; - - let tz = input_manager.get_virtual_input_state("move_forward") * movement_delta; - self.position += tz * forward; - } - pub fn update_projection(&mut self, window_aspect_ratio: f32) { if self.aspect_ratio != window_aspect_ratio { self.aspect_ratio = window_aspect_ratio; @@ -93,23 +56,16 @@ impl Camera3D { self.projection = projection; } - pub fn get_position(&self) -> Vec3 { - self.position - } - - pub fn get_rotation(&self) -> Vec3 { - self.rotation - } - pub fn create_buffer( &self, + transform: &Camera3DTransform, memory_allocator: &Arc, ) -> Result, Validated> { - let (sin_pitch, cos_pitch) = self.rotation.x.sin_cos(); - let (sin_yaw, cos_yaw) = self.rotation.y.sin_cos(); + let (sin_pitch, cos_pitch) = transform.rotation.x.sin_cos(); + let (sin_yaw, cos_yaw) = transform.rotation.y.sin_cos(); let view_matrix = Mat4::look_to_rh( - self.position, + transform.position, Vec3::new(cos_pitch * cos_yaw, sin_pitch, cos_pitch * sin_yaw).normalize(), Vec3::Y, ); diff --git a/src/core/render/primitives/transform.rs b/src/core/render/primitives/transform.rs index 85bd01b..9bbda47 100644 --- a/src/core/render/primitives/transform.rs +++ b/src/core/render/primitives/transform.rs @@ -46,30 +46,6 @@ impl Transform { } } - pub fn rotate(&mut self, rotation: Quat) { - self.rotation *= rotation; - } - - pub fn translate(&mut self, translation: Vec3) { - self.position += translation; - } - - pub fn scale(&mut self, scale: Vec3) { - self.scale *= scale; - } - - pub fn set_position(&mut self, position: Vec3) { - self.position = position; - } - - pub fn set_rotation(&mut self, rotation: Quat) { - self.rotation = rotation; - } - - pub fn set_scale(&mut self, scale: Vec3) { - self.scale = scale; - } - /// Get the transformation matrix (immutable - recalculates each time) pub fn matrix(&self) -> Mat4 { Mat4::from_translation(self.position) diff --git a/src/core/render/primitives/velocity.rs b/src/core/render/primitives/velocity.rs index 6e16db4..19899aa 100644 --- a/src/core/render/primitives/velocity.rs +++ b/src/core/render/primitives/velocity.rs @@ -1,7 +1,7 @@ use bevy_ecs::prelude::*; use glam::Vec3; -#[derive(Component, Debug, Clone)] +#[derive(Component, Debug, Clone, Default)] pub struct Velocity { pub linear: Vec3, pub angular: Vec3, diff --git a/src/game/scenes/main_scene.rs b/src/game/scenes/main_scene.rs index 45e7dfa..fc7f3c9 100644 --- a/src/game/scenes/main_scene.rs +++ b/src/game/scenes/main_scene.rs @@ -6,7 +6,7 @@ use crate::core::app::DEPTH_IMAGE_ID; use crate::core::app::context::WindowContext; use crate::core::app::user_event::UserEvent; use crate::core::input::InputManagerResource; -use crate::core::render::primitives::camera::Camera3D; +use crate::core::render::primitives::camera::{Camera3D, Camera3DTransform}; use crate::core::render::primitives::transform::Transform; use crate::core::render::primitives::velocity::Velocity; use crate::core::render::primitives::vulkan_resource::{ @@ -49,7 +49,10 @@ pub struct MainSceneState { } #[derive(Resource)] -pub struct CameraSpeed(f32); +pub struct MovementSpeed(f32); + +#[derive(Resource)] +pub struct CameraSensitivity(f32); #[derive(Default)] pub struct MainScene { @@ -109,21 +112,31 @@ impl AsScene for MainScene { obj.into_iter().next().unwrap() }; - let camera = window_context.with_renderer(|renderer| { + world.spawn(( Camera3D::new( - renderer.aspect_ratio(), + window_context.get_aspect_ratio(), std::f32::consts::FRAC_PI_2, 0.01, 1000.0, - ) - }); - world.insert_resource(CameraSpeed(50.0)); - world.insert_resource(camera); + ), + Camera3DTransform { + position: Vec3::ZERO, + rotation: Vec3::ZERO, + }, + )); + + world.insert_resource(MovementSpeed(50.0)); + world.insert_resource(CameraSensitivity(10.0)); let mut scheduler = Schedule::default(); - scheduler.add_systems(update_velocity_system); - scheduler.add_systems(update_timer_system); - scheduler.add_systems(update_camera_system); + scheduler.add_systems( + ( + update_camera_system, + update_velocity_system, + update_timer_system, + ) + .chain(), + ); world.insert_resource(Timer::new()); Self::create_entities(world, 100, 10.0, 10.0); @@ -144,7 +157,7 @@ impl AsScene for MainScene { let state = self.state.as_mut().unwrap(); { - let mut camera = world.resource_mut::(); + let mut camera = world.query::<&mut Camera3D>().single_mut(world).unwrap(); camera.update_projection(window_context.get_aspect_ratio()); } @@ -212,11 +225,18 @@ impl AsScene for MainScene { } // Create camera uniform using the actual camera - let camera_uniform = Arc::new( - world - .resource::() - .create_buffer(VulkanMemoryAllocator::get_from_world(world))?, - ); + let camera_uniform = { + let result = world + .query_filtered::<(&Camera3D, &Camera3DTransform), With>() + .single(&world) + .unwrap(); + + Arc::new( + result + .0 + .create_buffer(result.1, VulkanMemoryAllocator::get_from_world(world))?, + ) + }; let square_transforms = world .query_filtered::<&Transform, With>() .iter(&world) @@ -286,7 +306,10 @@ impl AsScene for MainScene { let delta_time = window_context.get_delta_time(); let window_id = window_context.window_id; let window_size = window_context.get_window_size(); - let camera = world.resource::(); + let camera_transform = world + .query_filtered::<&Camera3DTransform, With>() + .single(&world) + .unwrap(); let render_future = window_context.with_gui_mut(|gui| { gui.immediate_ui(|gui| { let ctx = gui.context(); @@ -318,7 +341,7 @@ impl AsScene for MainScene { ui.separator(); ui.label("Position caméra:"); - let position = camera.get_position(); + let position = camera_transform.position; ui.label(format!(" X: {:.2}", position[0])); ui.label(format!(" Y: {:.2}", position[1])); ui.label(format!(" Z: {:.2}", position[2])); @@ -326,7 +349,7 @@ impl AsScene for MainScene { ui.separator(); ui.label("Rotation caméra:"); - let rotation = camera.get_rotation(); + let rotation = camera_transform.rotation; ui.label(format!(" Yaw: {:.2}°", rotation.y.to_degrees())); ui.label(format!(" Pitch: {:.2}°", rotation.x.to_degrees())); @@ -415,7 +438,7 @@ fn update_velocity_system(mut query: Query<(&mut Transform, &Velocity)>, time: R let delta_time = time.delta_time(); // Update linear position - transform.translate(velocity.linear * delta_time); + transform.position += velocity.linear * delta_time; // Update angular rotation let angular_delta = velocity.angular * delta_time; @@ -425,7 +448,7 @@ fn update_velocity_system(mut query: Query<(&mut Transform, &Velocity)>, time: R angular_delta.y, angular_delta.z, ); - transform.rotate(rotation_delta); + transform.rotation *= rotation_delta; } } @@ -434,11 +457,40 @@ fn update_timer_system(mut timer: ResMut) { } fn update_camera_system( - mut camera: ResMut, + mut query: Query<&mut Camera3DTransform, With>, input_manager: Res, - timer: Res, - camera_speed: Res, + camera_sensitivity: Res, + movement_speed: Res, + time: Res, ) { let input_manager = input_manager.0.read().unwrap(); - camera.update(&input_manager, &timer, camera_speed.0, 10.0); + let mut camera_transform = query.single_mut().unwrap(); + + let delta_time = time.delta_time(); + + camera_transform.rotation += Vec3::new( + (input_manager.get_virtual_input_state("mouse_y") * camera_sensitivity.0 * delta_time) + .to_radians(), + (input_manager.get_virtual_input_state("mouse_x") * camera_sensitivity.0 * delta_time) + .to_radians(), + 0.0, + ); + + if camera_transform.rotation.x > std::f32::consts::FRAC_PI_2 { + camera_transform.rotation.x = std::f32::consts::FRAC_PI_2; + } + + if camera_transform.rotation.x < -std::f32::consts::FRAC_PI_2 { + camera_transform.rotation.x = -std::f32::consts::FRAC_PI_2; + } + + let (yaw_sin, yaw_cos) = camera_transform.rotation.y.sin_cos(); + let forward = Vec3::new(yaw_cos, 0.0, yaw_sin).normalize(); + let right = Vec3::new(-yaw_sin, 0.0, yaw_cos).normalize(); + + let tx = input_manager.get_virtual_input_state("move_right") * movement_speed.0 * delta_time; + camera_transform.position += tx * right; + + let tz = input_manager.get_virtual_input_state("move_forward") * movement_speed.0 * delta_time; + camera_transform.position += tz * forward; } From 5d4048d9a7189e5f891c9c1936ab5d4a306b721f Mon Sep 17 00:00:00 2001 From: Florian RICHER Date: Thu, 12 Jun 2025 21:11:17 +0200 Subject: [PATCH 103/105] create_entities: Remove from MainScene struct --- src/game/scenes/main_scene.rs | 101 +++++++++++++++++----------------- 1 file changed, 49 insertions(+), 52 deletions(-) diff --git a/src/game/scenes/main_scene.rs b/src/game/scenes/main_scene.rs index fc7f3c9..4c8aa87 100644 --- a/src/game/scenes/main_scene.rs +++ b/src/game/scenes/main_scene.rs @@ -138,7 +138,7 @@ impl AsScene for MainScene { .chain(), ); world.insert_resource(Timer::new()); - Self::create_entities(world, 100, 10.0, 10.0); + create_entities(world, 100, 10.0, 10.0); self.state = Some(MainSceneState { square, @@ -370,67 +370,64 @@ impl AsScene for MainScene { } } -impl MainScene { - // Function to create entities in the ECS world - fn create_entities( - world: &mut World, - num_instances: u32, - instance_size: f32, - instance_spacing: f32, - ) { - let num_instances_per_row = (num_instances as f32 / instance_spacing).ceil() as u32; +fn create_entities( + world: &mut World, + num_instances: u32, + instance_size: f32, + instance_spacing: f32, +) { + let num_instances_per_row = (num_instances as f32 / instance_spacing).ceil() as u32; - let square_instances = (0..num_instances) - .map(|i| { - let x_index = i % num_instances_per_row; - let z_index = i / num_instances_per_row; + let square_instances = (0..num_instances) + .map(|i| { + let x_index = i % num_instances_per_row; + let z_index = i / num_instances_per_row; - let transform = Transform::new( - Vec3::new( - x_index as f32 * (instance_spacing + instance_size), - 0.0, - z_index as f32 * (instance_spacing + instance_size), - ), - Quat::from_euler(EulerRot::XYZ, 0.0, rand::random_range(0.0..=360.0), 0.0), - Vec3::new(instance_size, instance_size, instance_size), - ); + let transform = Transform::new( + Vec3::new( + x_index as f32 * (instance_spacing + instance_size), + 0.0, + z_index as f32 * (instance_spacing + instance_size), + ), + Quat::from_euler(EulerRot::XYZ, 0.0, rand::random_range(0.0..=360.0), 0.0), + Vec3::new(instance_size, instance_size, instance_size), + ); - let velocity = Velocity::new(Vec3::ZERO, Vec3::new(0.0, x_index as f32, 0.0)); + let velocity = Velocity::new(Vec3::ZERO, Vec3::new(0.0, x_index as f32, 0.0)); - (Square, transform, velocity) - }) - .collect::>(); + (Square, transform, velocity) + }) + .collect::>(); - world.spawn_batch(square_instances); + world.spawn_batch(square_instances); - let cube_instances = (0..num_instances) - .map(|i| { - let x_index = i % num_instances_per_row; - let z_index = i / num_instances_per_row; + let cube_instances = (0..num_instances) + .map(|i| { + let x_index = i % num_instances_per_row; + let z_index = i / num_instances_per_row; - let transform = Transform::new( - Vec3::new( - x_index as f32 * (instance_spacing + instance_size), - 0.0, - z_index as f32 * (instance_spacing + instance_size) * -1.0 - - instance_spacing * 2.0, - ), - Quat::from_euler(EulerRot::XYZ, 0.0, rand::random_range(0.0..=360.0), 0.0), - Vec3::new( - instance_size * 0.5, - instance_size * 0.5, - instance_size * 0.5, - ), - ); + let transform = Transform::new( + Vec3::new( + x_index as f32 * (instance_spacing + instance_size), + 0.0, + z_index as f32 * (instance_spacing + instance_size) * -1.0 + - instance_spacing * 2.0, + ), + Quat::from_euler(EulerRot::XYZ, 0.0, rand::random_range(0.0..=360.0), 0.0), + Vec3::new( + instance_size * 0.5, + instance_size * 0.5, + instance_size * 0.5, + ), + ); - let velocity = Velocity::new(Vec3::ZERO, Vec3::new(0.0, x_index as f32, 0.0)); + let velocity = Velocity::new(Vec3::ZERO, Vec3::new(0.0, x_index as f32, 0.0)); - (Cube, transform, velocity) - }) - .collect::>(); + (Cube, transform, velocity) + }) + .collect::>(); - world.spawn_batch(cube_instances); - } + world.spawn_batch(cube_instances); } fn update_velocity_system(mut query: Query<(&mut Transform, &Velocity)>, time: Res) { From 7b1373620c2e6d5d2665bde4a800291d41d81be1 Mon Sep 17 00:00:00 2001 From: Florian RICHER Date: Fri, 13 Jun 2025 12:20:20 +0200 Subject: [PATCH 104/105] Add docs about how use ash with vulkano --- docs/USE_ASH_METHODS.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 docs/USE_ASH_METHODS.md diff --git a/docs/USE_ASH_METHODS.md b/docs/USE_ASH_METHODS.md new file mode 100644 index 0000000..a69997d --- /dev/null +++ b/docs/USE_ASH_METHODS.md @@ -0,0 +1,15 @@ +# Informations + +```rust +let fns = vulkano_context.instance().fns(); +let mut props = ash::vk::PhysicalDeviceProperties::default(); + +unsafe { + (fns.v1_0.get_physical_device_properties)( + vulkano_context.device().physical_device().handle(), + &mut props, + ); +} + +println!("{:?}", props); +``` From 77affedf93f678c494ebac7ecbf25304f567bfc8 Mon Sep 17 00:00:00 2001 From: Florian RICHER Date: Fri, 13 Jun 2025 13:27:34 +0200 Subject: [PATCH 105/105] Refactor + Begin add SceneSchedule --- src/core/app/context.rs | 19 +-- src/core/app/mod.rs | 9 -- src/core/mod.rs | 1 - src/core/render/primitives/mod.rs | 1 - src/core/render/primitives/vulkan_resource.rs | 79 ---------- src/core/render/resources/mod.rs | 2 + src/core/{ => render/resources}/timer.rs | 0 src/core/render/resources/vulkan.rs | 145 ++++++++++++++++++ src/core/scene/extract.rs | 81 ++++++++++ src/core/scene/manager.rs | 59 +------ src/core/scene/mod.rs | 8 +- src/core/scene/schedule.rs | 24 +++ src/game/scenes/main_scene.rs | 36 ++--- src/game/scenes/settings_scene.rs | 10 +- 14 files changed, 286 insertions(+), 188 deletions(-) delete mode 100644 src/core/render/primitives/vulkan_resource.rs rename src/core/{ => render/resources}/timer.rs (100%) create mode 100644 src/core/render/resources/vulkan.rs create mode 100644 src/core/scene/extract.rs create mode 100644 src/core/scene/schedule.rs diff --git a/src/core/app/context.rs b/src/core/app/context.rs index ca37b65..00e8256 100644 --- a/src/core/app/context.rs +++ b/src/core/app/context.rs @@ -8,7 +8,7 @@ use egui_winit_vulkano::Gui; use vulkano_util::{renderer::VulkanoWindowRenderer, window::VulkanoWindows}; use winit::{event_loop::EventLoopProxy, monitor::MonitorHandle, window::WindowId}; -use crate::core::{input::InputManager, render::vulkan_context::VulkanContext, timer::Timer}; +use crate::core::{input::InputManager, render::vulkan_context::VulkanContext}; use super::user_event::UserEvent; @@ -22,7 +22,6 @@ pub struct WindowContext { // Données mutables partagées avec Arc> pub vulkano_windows: Rc>, pub input_manager: Arc>, - pub timer: Arc>, pub gui: Rc>, } @@ -31,7 +30,6 @@ impl WindowContext { vulkan_context: Arc, vulkano_windows: Rc>, input_manager: Arc>, - timer: Arc>, gui: Rc>, event_loop_proxy: EventLoopProxy, window_id: WindowId, @@ -45,7 +43,6 @@ impl WindowContext { // Données mutables partagées vulkano_windows, input_manager, - timer, gui, } } @@ -85,11 +82,6 @@ impl WindowContext { }) } - /// Récupère le delta time actuel depuis le timer - pub fn get_delta_time(&self) -> f32 { - self.with_timer(|timer| timer.delta_time()) - } - /// Récupère la taille de la fenêtre depuis le renderer pub fn get_window_size(&self) -> [f32; 2] { self.with_renderer(|renderer| renderer.window_size()) @@ -140,13 +132,4 @@ impl WindowContext { let mut gui = self.gui.borrow_mut(); f(&mut gui) } - - /// Méthode utilitaire pour accéder au timer de manière thread-safe - pub fn with_timer(&self, f: F) -> T - where - F: FnOnce(&Timer) -> T, - { - let timer = self.timer.read().expect("Failed to lock timer"); - f(&timer) - } } diff --git a/src/core/app/mod.rs b/src/core/app/mod.rs index d951acb..2d3ecde 100644 --- a/src/core/app/mod.rs +++ b/src/core/app/mod.rs @@ -6,7 +6,6 @@ use std::sync::{Arc, RwLock}; use super::render::vulkan_context::VulkanContext; use crate::core::input::InputManager; use crate::core::scene::manager::SceneManager; -use crate::core::timer::Timer; use crate::game::scenes::main_scene::MainScene; use egui_winit_vulkano::{Gui, GuiConfig}; use user_event::UserEvent; @@ -33,7 +32,6 @@ pub struct App { gui: HashMap>>, scene_manager: HashMap, input_manager: Arc>, - timer: Arc>, event_loop_proxy: EventLoopProxy, // Context d'application partagé par fenêtre - architecture unifiée @@ -52,7 +50,6 @@ impl App { gui: HashMap::new(), input_manager: Arc::new(RwLock::new(input_manager)), scene_manager: HashMap::new(), - timer: Arc::new(RwLock::new(Timer::new())), event_loop_proxy, app_contexts: HashMap::new(), } @@ -104,7 +101,6 @@ impl ApplicationHandler for App { self.vulkan_context.clone(), self.vulkano_windows.clone(), self.input_manager.clone(), - self.timer.clone(), self.gui.get(&window_id).unwrap().clone(), self.event_loop_proxy.clone(), window_id, @@ -158,11 +154,6 @@ impl ApplicationHandler for App { .expect("Failed to lock input manager"); input_manager.update(); } - { - let _timer_span = tracing::debug_span!("timer_update").entered(); - let mut timer = self.timer.write().expect("Failed to lock timer"); - timer.update(); - } // Créer ou mettre à jour le contexte d'application let window_context = self.app_contexts.get(&id).unwrap().clone(); diff --git a/src/core/mod.rs b/src/core/mod.rs index 237496d..b1c8000 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -2,4 +2,3 @@ pub mod app; pub mod input; pub mod render; pub mod scene; -pub mod timer; diff --git a/src/core/render/primitives/mod.rs b/src/core/render/primitives/mod.rs index 9061f3c..28cd729 100644 --- a/src/core/render/primitives/mod.rs +++ b/src/core/render/primitives/mod.rs @@ -1,7 +1,6 @@ mod buffer; mod command; mod descriptor_set; -pub mod vulkan_resource; pub mod camera; pub mod mvp; diff --git a/src/core/render/primitives/vulkan_resource.rs b/src/core/render/primitives/vulkan_resource.rs deleted file mode 100644 index db59468..0000000 --- a/src/core/render/primitives/vulkan_resource.rs +++ /dev/null @@ -1,79 +0,0 @@ -use bevy_ecs::prelude::Resource; -use bevy_ecs::world::World; -use std::sync::Arc; -use vulkano::{ - command_buffer::allocator::StandardCommandBufferAllocator, - descriptor_set::allocator::StandardDescriptorSetAllocator, - device::{Device, Queue}, - instance::Instance, - memory::allocator::StandardMemoryAllocator, -}; - -/// Vulkan Instance resource -#[derive(Resource)] -pub struct VulkanInstance(pub Arc); - -/// Vulkan Device resource -#[derive(Resource)] -pub struct VulkanDevice(pub Arc); - -/// Vulkan Graphics Queue resource -#[derive(Resource)] -pub struct VulkanGraphicsQueue(pub Arc); - -/// Vulkan Compute Queue resource -#[derive(Resource)] -pub struct VulkanComputeQueue(pub Arc); - -/// Vulkan Transfer Queue resource -#[derive(Resource)] -pub struct VulkanTransferQueue(pub Option>); - -/// Vulkan Memory Allocator resource -#[derive(Resource)] -pub struct VulkanMemoryAllocator(pub Arc); - -/// Vulkan Command Buffer Allocator resource -#[derive(Resource)] -pub struct VulkanCommandBufferAllocator(pub Arc); - -/// Vulkan Descriptor Set Allocator resource -#[derive(Resource)] -pub struct VulkanDescriptorSetAllocator(pub Arc); - -/// Helper functions to access vulkan resources from the ECS world -impl VulkanDevice { - pub fn get_from_world(world: &World) -> &Arc { - &world.resource::().0 - } -} - -impl VulkanMemoryAllocator { - pub fn get_from_world(world: &World) -> &Arc { - &world.resource::().0 - } -} - -impl VulkanCommandBufferAllocator { - pub fn get_from_world(world: &World) -> &Arc { - &world.resource::().0 - } -} - -impl VulkanDescriptorSetAllocator { - pub fn get_from_world(world: &World) -> &Arc { - &world.resource::().0 - } -} - -impl VulkanGraphicsQueue { - pub fn get_from_world(world: &World) -> &Arc { - &world.resource::().0 - } -} - -impl VulkanTransferQueue { - pub fn get_from_world(world: &World) -> Option<&Arc> { - world.resource::().0.as_ref() - } -} diff --git a/src/core/render/resources/mod.rs b/src/core/render/resources/mod.rs index d900da7..d60e2d2 100644 --- a/src/core/render/resources/mod.rs +++ b/src/core/render/resources/mod.rs @@ -1,3 +1,5 @@ pub mod meshes; pub mod pipeline; pub mod texture; +pub mod timer; +pub mod vulkan; diff --git a/src/core/timer.rs b/src/core/render/resources/timer.rs similarity index 100% rename from src/core/timer.rs rename to src/core/render/resources/timer.rs diff --git a/src/core/render/resources/vulkan.rs b/src/core/render/resources/vulkan.rs new file mode 100644 index 0000000..47dce68 --- /dev/null +++ b/src/core/render/resources/vulkan.rs @@ -0,0 +1,145 @@ +use bevy_ecs::prelude::Resource; +use std::{ops::Deref, sync::Arc}; +use vulkano::{ + command_buffer::allocator::StandardCommandBufferAllocator, + descriptor_set::allocator::StandardDescriptorSetAllocator, + device::{Device, Queue}, + instance::Instance, + memory::allocator::StandardMemoryAllocator, +}; + +#[derive(Resource)] +pub struct VulkanInstance(Arc); + +impl From<&Arc> for VulkanInstance { + fn from(instance: &Arc) -> Self { + Self(instance.clone()) + } +} + +impl Deref for VulkanInstance { + type Target = Arc; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +#[derive(Resource)] +pub struct VulkanDevice(Arc); + +impl From<&Arc> for VulkanDevice { + fn from(device: &Arc) -> Self { + Self(device.clone()) + } +} + +impl Deref for VulkanDevice { + type Target = Arc; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +#[derive(Resource)] +pub struct VulkanGraphicsQueue(Arc); + +impl From<&Arc> for VulkanGraphicsQueue { + fn from(queue: &Arc) -> Self { + Self(queue.clone()) + } +} + +impl Deref for VulkanGraphicsQueue { + type Target = Arc; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +#[derive(Resource)] +pub struct VulkanComputeQueue(Arc); + +impl From<&Arc> for VulkanComputeQueue { + fn from(queue: &Arc) -> Self { + Self(queue.clone()) + } +} + +impl Deref for VulkanComputeQueue { + type Target = Arc; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +#[derive(Resource)] +pub struct VulkanTransferQueue(Option>); + +impl From>> for VulkanTransferQueue { + fn from(queue: Option<&Arc>) -> Self { + Self(queue.cloned()) + } +} + +impl Deref for VulkanTransferQueue { + type Target = Option>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +#[derive(Resource)] +pub struct VulkanMemoryAllocator(Arc); + +impl From<&Arc> for VulkanMemoryAllocator { + fn from(allocator: &Arc) -> Self { + Self(allocator.clone()) + } +} + +impl Deref for VulkanMemoryAllocator { + type Target = Arc; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +#[derive(Resource)] +pub struct VulkanCommandBufferAllocator(Arc); + +impl From<&Arc> for VulkanCommandBufferAllocator { + fn from(allocator: &Arc) -> Self { + Self(allocator.clone()) + } +} + +impl Deref for VulkanCommandBufferAllocator { + type Target = Arc; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +#[derive(Resource)] +pub struct VulkanDescriptorSetAllocator(Arc); + +impl From<&Arc> for VulkanDescriptorSetAllocator { + fn from(allocator: &Arc) -> Self { + Self(allocator.clone()) + } +} + +impl Deref for VulkanDescriptorSetAllocator { + type Target = Arc; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} diff --git a/src/core/scene/extract.rs b/src/core/scene/extract.rs new file mode 100644 index 0000000..9c61953 --- /dev/null +++ b/src/core/scene/extract.rs @@ -0,0 +1,81 @@ +use bevy_ecs::world::World; + +use crate::core::{ + app::{DEPTH_IMAGE_ID, context::WindowContext}, + render::{ + resources::vulkan::{ + VulkanCommandBufferAllocator, VulkanComputeQueue, VulkanDescriptorSetAllocator, + VulkanDevice, VulkanGraphicsQueue, VulkanInstance, VulkanMemoryAllocator, + VulkanTransferQueue, + }, + resources::{pipeline::PipelineLoader, texture::TextureLoader}, + vulkan_context::VulkanContext, + }, +}; + +pub fn extract_vulkan_ressources(world: &mut World, vulkan_context: &VulkanContext) { + let vulkano_context = vulkan_context.vulkano_context(); + + let vulkan_instance = vulkano_context.instance(); + let vulkan_device = vulkano_context.device(); + let vulkan_graphics_queue = vulkano_context.graphics_queue(); + let vulkan_compute_queue = vulkano_context.compute_queue(); + let vulkan_transfer_queue = vulkano_context.transfer_queue(); + let vulkan_memory_allocator = vulkano_context.memory_allocator(); + let vulkan_command_buffer_allocator = vulkan_context.command_buffer_allocator(); + let vulkan_descriptor_set_allocator = vulkan_context.descriptor_set_allocator(); + + world.insert_resource(VulkanInstance::from(vulkan_instance)); + world.insert_resource(VulkanDevice::from(vulkan_device)); + world.insert_resource(VulkanGraphicsQueue::from(vulkan_graphics_queue)); + world.insert_resource(VulkanComputeQueue::from(vulkan_compute_queue)); + world.insert_resource(VulkanTransferQueue::from(vulkan_transfer_queue)); + world.insert_resource(VulkanMemoryAllocator::from(vulkan_memory_allocator)); + world.insert_resource(VulkanCommandBufferAllocator::from( + vulkan_command_buffer_allocator, + )); + world.insert_resource(VulkanDescriptorSetAllocator::from( + vulkan_descriptor_set_allocator, + )); +} + +pub fn extract_texture_loader(world: &mut World, vulkan_context: &VulkanContext) { + let vulkano_context = vulkan_context.vulkano_context(); + + let vulkan_device = vulkano_context.device(); + let vulkan_command_buffer_allocator = vulkan_context.command_buffer_allocator(); + let vulkan_memory_allocator = vulkano_context.memory_allocator(); + let vulkan_graphics_queue = vulkano_context.graphics_queue(); + let vulkan_transfer_queue = vulkano_context.transfer_queue(); + + let texture_loader = TextureLoader::new( + vulkan_device.clone(), + vulkan_command_buffer_allocator.clone(), + vulkan_memory_allocator.clone(), + TextureLoader::select_best_suitable_queue(vulkan_graphics_queue, vulkan_transfer_queue), + ); + + world.insert_resource(texture_loader); +} + +pub fn extract_pipeline_loader(world: &mut World, window_context: &mut WindowContext) { + let vulkan_device = window_context + .vulkan_context() + .vulkano_context() + .device() + .clone(); + + let depth_image_view = window_context + .with_renderer_mut(|renderer| renderer.get_additional_image_view(DEPTH_IMAGE_ID).clone()); + + let swapchain_image_view = + window_context.with_renderer(|renderer| renderer.swapchain_image_view().clone()); + + let pipeline_loader = PipelineLoader::new( + vulkan_device.clone(), + swapchain_image_view.format(), + depth_image_view.format(), + ); + + world.insert_resource(pipeline_loader); +} diff --git a/src/core/scene/manager.rs b/src/core/scene/manager.rs index 6d59be3..4bc82b3 100644 --- a/src/core/scene/manager.rs +++ b/src/core/scene/manager.rs @@ -1,15 +1,11 @@ use std::error::Error; -use crate::core::app::DEPTH_IMAGE_ID; use crate::core::app::context::WindowContext; use crate::core::input::InputManagerResource; -use crate::core::render::primitives::vulkan_resource::{ - VulkanCommandBufferAllocator, VulkanComputeQueue, VulkanDescriptorSetAllocator, VulkanDevice, - VulkanGraphicsQueue, VulkanInstance, VulkanMemoryAllocator, VulkanTransferQueue, -}; -use crate::core::render::resources::pipeline::PipelineLoader; -use crate::core::render::resources::texture::TextureLoader; use crate::core::scene::AsScene; +use crate::core::scene::extract::{ + extract_pipeline_loader, extract_texture_loader, extract_vulkan_ressources, +}; use bevy_ecs::world::World; use super::Scene; @@ -31,52 +27,9 @@ impl SceneManager { fn create_world_with_resources(window_context: &mut WindowContext) -> World { let mut world = World::new(); - let depth_image_view = window_context.with_renderer_mut(|renderer| { - renderer.get_additional_image_view(DEPTH_IMAGE_ID).clone() - }); - - let swapchain_image_view = - window_context.with_renderer(|renderer| renderer.swapchain_image_view().clone()); - - let vulkan_context = window_context.vulkan_context(); - let vulkano_context = vulkan_context.vulkano_context(); - - let vulkan_instance = vulkano_context.instance(); - let vulkan_device = vulkano_context.device(); - let vulkan_graphics_queue = vulkano_context.graphics_queue(); - let vulkan_compute_queue = vulkano_context.compute_queue(); - let vulkan_transfer_queue = vulkano_context.transfer_queue(); - let vulkan_memory_allocator = vulkano_context.memory_allocator(); - let vulkan_command_buffer_allocator = vulkan_context.command_buffer_allocator(); - let vulkan_descriptor_set_allocator = vulkan_context.descriptor_set_allocator(); - - let texture_loader = TextureLoader::new( - vulkan_device.clone(), - vulkan_command_buffer_allocator.clone(), - vulkan_memory_allocator.clone(), - TextureLoader::select_best_suitable_queue(vulkan_graphics_queue, vulkan_transfer_queue), - ); - - let pipeline_loader = PipelineLoader::new( - vulkan_device.clone(), - swapchain_image_view.format(), - depth_image_view.format(), - ); - - world.insert_resource(VulkanInstance(vulkan_instance.clone())); - world.insert_resource(VulkanDevice(vulkan_device.clone())); - world.insert_resource(VulkanGraphicsQueue(vulkan_graphics_queue.clone())); - world.insert_resource(VulkanComputeQueue(vulkan_compute_queue.clone())); - world.insert_resource(VulkanTransferQueue(vulkan_transfer_queue.cloned())); - world.insert_resource(VulkanMemoryAllocator(vulkan_memory_allocator.clone())); - world.insert_resource(VulkanCommandBufferAllocator( - vulkan_command_buffer_allocator.clone(), - )); - world.insert_resource(VulkanDescriptorSetAllocator( - vulkan_descriptor_set_allocator.clone(), - )); - world.insert_resource(texture_loader); - world.insert_resource(pipeline_loader); + extract_vulkan_ressources(&mut world, window_context.vulkan_context()); + extract_texture_loader(&mut world, window_context.vulkan_context()); + extract_pipeline_loader(&mut world, window_context); world.insert_resource(InputManagerResource(window_context.input_manager.clone())); world diff --git a/src/core/scene/mod.rs b/src/core/scene/mod.rs index 7abb795..9f1c857 100644 --- a/src/core/scene/mod.rs +++ b/src/core/scene/mod.rs @@ -1,15 +1,18 @@ use std::error::Error; -use bevy_ecs::world::World; +use bevy_ecs::{schedule::Schedule, world::World}; use vulkano::sync::GpuFuture; -use crate::core::app::context::WindowContext; +use crate::core::{app::context::WindowContext, scene::schedule::SceneScedule}; +mod extract; pub mod manager; +pub mod schedule; /// Structure Scene qui contient le world et l'implémentation AsScene pub struct Scene { pub world: World, + pub loop_schedule: Schedule, pub implementation: Box, } @@ -17,6 +20,7 @@ impl Scene { pub fn new(implementation: Box, world: World) -> Self { Self { world, + loop_schedule: SceneScedule::base_schedule(), implementation, } } diff --git a/src/core/scene/schedule.rs b/src/core/scene/schedule.rs new file mode 100644 index 0000000..9f1e97e --- /dev/null +++ b/src/core/scene/schedule.rs @@ -0,0 +1,24 @@ +use bevy_ecs::schedule::{IntoScheduleConfigs, Schedule, ScheduleLabel, SystemSet}; + +#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)] +pub enum SceneSystems { + ExtractViews, + Update, + Render, + Present, +} + +#[derive(ScheduleLabel, Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct SceneScedule; + +impl SceneScedule { + pub fn base_schedule() -> Schedule { + use SceneSystems::*; + + let mut schedule = Schedule::new(Self); + + schedule.configure_sets((ExtractViews, Update, Render, Present).chain()); + + schedule + } +} diff --git a/src/game/scenes/main_scene.rs b/src/game/scenes/main_scene.rs index 4c8aa87..c500a82 100644 --- a/src/game/scenes/main_scene.rs +++ b/src/game/scenes/main_scene.rs @@ -9,17 +9,17 @@ use crate::core::input::InputManagerResource; use crate::core::render::primitives::camera::{Camera3D, Camera3DTransform}; use crate::core::render::primitives::transform::Transform; use crate::core::render::primitives::velocity::Velocity; -use crate::core::render::primitives::vulkan_resource::{ - VulkanCommandBufferAllocator, VulkanDescriptorSetAllocator, VulkanGraphicsQueue, - VulkanMemoryAllocator, -}; use crate::core::render::primitives::{AsDescriptorSet, AsRecordable}; use crate::core::render::render_pass_manager::{RenderPassConfig, RenderPassManager}; use crate::core::render::resources::meshes::{ObjMesh, SquareMesh}; use crate::core::render::resources::pipeline::PipelineLoader; use crate::core::render::resources::texture::{TextureLoadInfo, TextureLoader, TextureSourceKind}; +use crate::core::render::resources::timer::Timer; +use crate::core::render::resources::vulkan::{ + VulkanCommandBufferAllocator, VulkanDescriptorSetAllocator, VulkanGraphicsQueue, + VulkanMemoryAllocator, +}; use crate::core::scene::AsScene; -use crate::core::timer::Timer; use crate::game::assets::pipelines::simple::SimplePipeline; use bevy_ecs::prelude::*; use bevy_ecs::schedule::Schedule; @@ -102,11 +102,11 @@ impl AsScene for MainScene { ); texture_loader.load_pending_textures()?; - let square = SquareMesh::new(VulkanMemoryAllocator::get_from_world(world))?; + let square = SquareMesh::new(world.resource::())?; let obj = { let obj = ObjMesh::new( - VulkanMemoryAllocator::get_from_world(world), + world.resource::(), "res/objects/cube.obj", )?; obj.into_iter().next().unwrap() @@ -203,8 +203,8 @@ impl AsScene for MainScene { let state = self.state.as_mut().ok_or("State not loaded")?; let mut builder = AutoCommandBufferBuilder::primary( - VulkanCommandBufferAllocator::get_from_world(world).clone(), - VulkanGraphicsQueue::get_from_world(world).queue_family_index(), + (*world.resource::()).clone(), + world.resource::().queue_family_index(), CommandBufferUsage::OneTimeSubmit, )?; @@ -234,7 +234,7 @@ impl AsScene for MainScene { Arc::new( result .0 - .create_buffer(result.1, VulkanMemoryAllocator::get_from_world(world))?, + .create_buffer(result.1, world.resource::())?, ) }; let square_transforms = world @@ -243,7 +243,7 @@ impl AsScene for MainScene { .cloned() .collect::>(); let square_transform_uniform = Transform::create_buffer( - VulkanMemoryAllocator::get_from_world(world), + world.resource::(), &square_transforms, )?; let cube_transforms = world @@ -251,10 +251,8 @@ impl AsScene for MainScene { .iter(&world) .cloned() .collect::>(); - let cube_transform_uniform = Transform::create_buffer( - VulkanMemoryAllocator::get_from_world(world), - &cube_transforms, - )?; + let cube_transform_uniform = + Transform::create_buffer(world.resource::(), &cube_transforms)?; let texture_loader = world.resource::(); let pipeline_loader = world.resource::(); @@ -262,7 +260,7 @@ impl AsScene for MainScene { pipeline_loader.with_pipeline::(|pipeline| { SimplePipeline::record_commands( &mut builder, - &VulkanDescriptorSetAllocator::get_from_world(world), + world.resource::(), pipeline, &state.square, &square_transform_uniform, @@ -274,7 +272,7 @@ impl AsScene for MainScene { SimplePipeline::record_commands( &mut builder, - &VulkanDescriptorSetAllocator::get_from_world(world), + world.resource::(), pipeline, &state.obj, &cube_transform_uniform, @@ -292,7 +290,7 @@ impl AsScene for MainScene { let command_buffer = builder.build()?; let render_future = before_future.then_execute( - VulkanGraphicsQueue::get_from_world(world).clone(), + (*world.resource::()).clone(), command_buffer, )?; @@ -303,7 +301,7 @@ impl AsScene for MainScene { format!("{:#?}", input_manager) }; let event_loop_proxy = window_context.event_loop_proxy.clone(); - let delta_time = window_context.get_delta_time(); + let delta_time = world.resource::().delta_time(); let window_id = window_context.window_id; let window_size = window_context.get_window_size(); let camera_transform = world diff --git a/src/game/scenes/settings_scene.rs b/src/game/scenes/settings_scene.rs index 38e44b2..24aaccd 100644 --- a/src/game/scenes/settings_scene.rs +++ b/src/game/scenes/settings_scene.rs @@ -3,10 +3,8 @@ use std::error::Error; use crate::core::app::DEPTH_IMAGE_ID; use crate::core::app::context::WindowContext; use crate::core::app::user_event::UserEvent; -use crate::core::render::primitives::vulkan_resource::{ - VulkanCommandBufferAllocator, VulkanGraphicsQueue, -}; use crate::core::render::render_pass_manager::{RenderPassConfig, RenderPassManager}; +use crate::core::render::resources::vulkan::{VulkanCommandBufferAllocator, VulkanGraphicsQueue}; use crate::core::scene::AsScene; use bevy_ecs::world::World; use egui_winit_vulkano::egui; @@ -64,8 +62,8 @@ impl AsScene for SettingsScene { let state = self.state.as_ref().ok_or("State not found")?; let mut builder = AutoCommandBufferBuilder::primary( - VulkanCommandBufferAllocator::get_from_world(world).clone(), - VulkanGraphicsQueue::get_from_world(world).queue_family_index(), + (*world.resource::()).clone(), + world.resource::().queue_family_index(), CommandBufferUsage::OneTimeSubmit, )?; @@ -93,7 +91,7 @@ impl AsScene for SettingsScene { let command_buffer = builder.build()?; let render_future = before_future.then_execute( - VulkanGraphicsQueue::get_from_world(world).clone(), + (*world.resource::()).clone(), command_buffer, )?;