Compare commits

...

10 Commits

Author SHA1 Message Date
Jérôme Bousquié 252db88980 feat(lib): expose Camera and Entity with Transform (data foundation)
Étape 1 du plan 3D+Phong : fondations de données sans toucher au rendu.

- resources: exporte Camera (fichier auparavant orphelin), migre les fonctions
  glam dépréciées look_at_rh/perspective_rh_gl vers glam::camera::rh::* (induit
  par la compilation de camera.rs, sinon 2 warnings).
- scene: nouveau type Entity { mesh_id, material_id, transform } (scene/entity.rs);
  Scene::entities passe de HashMap<String,(String,String)> à HashMap<String,Entity>.
- API: add_entity conserve sa signature (transform identité par défaut), ajout de
  add_entity_with_transform, entity_transform, set_entity_transform; iter_entities
  rend désormais aussi le &Transform. renderer et examples inchangés a posteriori.
- Validation: cargo check --workspace et examples 0 warning, cargo doc 0 warning, fmt OK.
2026-09-16 15:26:16 +02:00
Jérôme Bousquié 91007853d9 docs: note pour mémoire sur le couplage au runtime async (pollster)
La lib n'a qu'un seul point de couplage au runtime (block_on dans app.rs) ; on ne crée volontairement
pas d'abstraction à ce stade. Consigné dans PLAN.md et ARCHI_APP.md : isoler derrière un module-pivot
unique si la lib acquiert d'autres appels async. Corrige aussi l'item check-list pollster devenu faux
dev-dependencies -> dependencies depuis la migration winit 0.30.
2026-09-16 14:58:51 +02:00
Jérôme Bousquié 8eec38e55c fix(lib): migrate to winit 0.30 ApplicationHandler model
winit 0.30.13 removed WindowBuilder and deprecated EventLoop::run. Move
window/GPU creation into ApplicationHandler::resumed, expose AppHandler::setup
hook, switch App::run to run_app, and migrate both examples. pollster becomes a
regular dependency (used by app.rs).
2026-09-16 14:17:11 +02:00
Jérôme Bousquié bb7fab4911 docs(plan): lock uniform scheme (2 bind groups) and uniform types location (resources/uniform.rs) 2026-09-16 13:40:30 +02:00
Jérôme Bousquié 14e18cda06 docs(plan): unify on a single pipeline layout (2D as degenerate 3D) 2026-09-16 12:06:56 +02:00
Jérôme Bousquié f81144918a docs: detailed plan for 3D+Phong step in DRAFT.md 2026-09-16 11:46:33 +02:00
Jérôme Bousquié b764bbc83d docs: complete README coherence for scene auto-render step 2026-09-16 11:23:58 +02:00
Jérôme Bousquié 9d631b686a docs: clarify PipelineCache item in plan after scene auto-render 2026-09-16 11:18:08 +02:00
Jérôme Bousquié 0a7ebf62ad docs: erase DRAFT scratchpad content for next step 2026-09-16 11:16:48 +02:00
Jérôme Bousquié 0560c1897f docs: record completed scene auto-render step in plan, roadmap, README 2026-09-16 11:16:48 +02:00
17 changed files with 1047 additions and 634 deletions
Generated
+345 -246
View File
@@ -39,23 +39,21 @@ checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
[[package]] [[package]]
name = "android-activity" name = "android-activity"
version = "0.5.2" version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee91c0c2905bae44f84bfa4e044536541df26b7703fd0888deeb9060fcc44289" checksum = "0f2a1bb052857d5dd49572219344a7332b31b76405648eabac5bc68978251bcd"
dependencies = [ dependencies = [
"android-properties", "android-properties",
"bitflags 2.13.0", "bitflags 2.13.0",
"cc", "cc",
"cesu8",
"jni", "jni",
"jni-sys 0.3.1",
"libc", "libc",
"log", "log",
"ndk", "ndk",
"ndk-context", "ndk-context",
"ndk-sys 0.5.0+25.2.9519653", "ndk-sys",
"num_enum", "num_enum",
"thiserror 1.0.69", "thiserror 2.0.18",
] ]
[[package]] [[package]]
@@ -139,23 +137,13 @@ version = "2.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8"
[[package]]
name = "block-sys"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae85a0696e7ea3b835a453750bf002770776609115e6d25c6d2ff28a8200f7e7"
dependencies = [
"objc-sys",
]
[[package]] [[package]]
name = "block2" name = "block2"
version = "0.3.0" version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15b55663a85f33501257357e6421bb33e769d5c9ffb5ba0921c975a123e35e68" checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f"
dependencies = [ dependencies = [
"block-sys", "objc2 0.5.2",
"objc2 0.4.1",
] ]
[[package]] [[package]]
@@ -201,9 +189,9 @@ checksum = "8ae3f5d315924270530207e2a68396c3cc547f6dca3fbdca317cfb1a51edb593"
[[package]] [[package]]
name = "calloop" name = "calloop"
version = "0.12.4" version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fba7adb4dd5aa98e5553510223000e7148f621165ec5f9acd7113f6ca4995298" checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec"
dependencies = [ dependencies = [
"bitflags 2.13.0", "bitflags 2.13.0",
"log", "log",
@@ -215,9 +203,9 @@ dependencies = [
[[package]] [[package]]
name = "calloop-wayland-source" name = "calloop-wayland-source"
version = "0.2.0" version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f0ea9b9476c7fad82841a8dbb380e2eae480c21910feba80725b46931ed8f02" checksum = "95a66a987056935f7efce4ab5668920b5d0dac4a7c99991a67395f13702ddd20"
dependencies = [ dependencies = [
"calloop", "calloop",
"rustix 0.38.44", "rustix 0.38.44",
@@ -237,24 +225,12 @@ dependencies = [
"shlex", "shlex",
] ]
[[package]]
name = "cesu8"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c"
[[package]] [[package]]
name = "cfg-if" name = "cfg-if"
version = "1.0.4" version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "cfg_aliases"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e"
[[package]] [[package]]
name = "cfg_aliases" name = "cfg_aliases"
version = "0.2.1" version = "0.2.1"
@@ -389,6 +365,12 @@ version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2"
[[package]]
name = "dpi"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76"
[[package]] [[package]]
name = "equivalent" name = "equivalent"
version = "1.0.2" version = "1.0.2"
@@ -580,17 +562,6 @@ version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c"
[[package]]
name = "icrate"
version = "0.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99d3aaff8a54577104bafdf686ff18565c3b6903ca5782a2026ef06e2c7aa319"
dependencies = [
"block2 0.3.0",
"dispatch",
"objc2 0.4.1",
]
[[package]] [[package]]
name = "indexmap" name = "indexmap"
version = "2.14.0" version = "2.14.0"
@@ -603,18 +574,32 @@ dependencies = [
[[package]] [[package]]
name = "jni" name = "jni"
version = "0.21.1" version = "0.22.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498"
dependencies = [ dependencies = [
"cesu8",
"cfg-if", "cfg-if",
"combine", "combine",
"jni-sys 0.3.1", "jni-macros",
"jni-sys 0.4.1",
"log", "log",
"thiserror 1.0.69", "simd_cesu8",
"thiserror 2.0.18",
"walkdir", "walkdir",
"windows-sys 0.45.0", "windows-link",
]
[[package]]
name = "jni-macros"
version = "0.22.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3"
dependencies = [
"proc-macro2",
"quote",
"rustc_version",
"simd_cesu8",
"syn",
] ]
[[package]] [[package]]
@@ -775,7 +760,7 @@ dependencies = [
"bit-set", "bit-set",
"bitflags 2.13.0", "bitflags 2.13.0",
"cfg-if", "cfg-if",
"cfg_aliases 0.2.1", "cfg_aliases",
"codespan-reporting", "codespan-reporting",
"half", "half",
"hashbrown 0.17.1", "hashbrown 0.17.1",
@@ -805,14 +790,14 @@ dependencies = [
[[package]] [[package]]
name = "ndk" name = "ndk"
version = "0.8.0" version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2076a31b7010b17a38c01907c45b945e8f11495ee4dd588309718901b1f7a5b7" checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4"
dependencies = [ dependencies = [
"bitflags 2.13.0", "bitflags 2.13.0",
"jni-sys 0.3.1", "jni-sys 0.3.1",
"log", "log",
"ndk-sys 0.5.0+25.2.9519653", "ndk-sys",
"num_enum", "num_enum",
"raw-window-handle", "raw-window-handle",
"thiserror 1.0.69", "thiserror 1.0.69",
@@ -824,15 +809,6 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b"
[[package]]
name = "ndk-sys"
version = "0.5.0+25.2.9519653"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c196769dd60fd4f363e11d948139556a344e79d451aeb2fa2fd040738ef7691"
dependencies = [
"jni-sys 0.3.1",
]
[[package]] [[package]]
name = "ndk-sys" name = "ndk-sys"
version = "0.6.0+11769913" version = "0.6.0+11769913"
@@ -882,12 +858,12 @@ checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310"
[[package]] [[package]]
name = "objc2" name = "objc2"
version = "0.4.1" version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "559c5a40fdd30eb5e344fbceacf7595a81e242529fb4e21cf5f43fb4f11ff98d" checksum = "46a785d4eeff09c14c487497c162e92766fbb3e4059a71840cecc03d9a50b804"
dependencies = [ dependencies = [
"objc-sys", "objc-sys",
"objc2-encode 3.0.0", "objc2-encode",
] ]
[[package]] [[package]]
@@ -896,7 +872,59 @@ version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f" checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f"
dependencies = [ dependencies = [
"objc2-encode 4.1.0", "objc2-encode",
]
[[package]]
name = "objc2-app-kit"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff"
dependencies = [
"bitflags 2.13.0",
"block2 0.5.1",
"libc",
"objc2 0.5.2",
"objc2-core-data",
"objc2-core-image",
"objc2-foundation 0.2.2",
"objc2-quartz-core 0.2.2",
]
[[package]]
name = "objc2-cloud-kit"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009"
dependencies = [
"bitflags 2.13.0",
"block2 0.5.1",
"objc2 0.5.2",
"objc2-core-location",
"objc2-foundation 0.2.2",
]
[[package]]
name = "objc2-contacts"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5ff520e9c33812fd374d8deecef01d4a840e7b41862d849513de77e44aa4889"
dependencies = [
"block2 0.5.1",
"objc2 0.5.2",
"objc2-foundation 0.2.2",
]
[[package]]
name = "objc2-core-data"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef"
dependencies = [
"bitflags 2.13.0",
"block2 0.5.1",
"objc2 0.5.2",
"objc2-foundation 0.2.2",
] ]
[[package]] [[package]]
@@ -923,10 +951,28 @@ dependencies = [
] ]
[[package]] [[package]]
name = "objc2-encode" name = "objc2-core-image"
version = "3.0.0" version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d079845b37af429bfe5dfa76e6d087d788031045b25cfc6fd898486fd9847666" checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80"
dependencies = [
"block2 0.5.1",
"objc2 0.5.2",
"objc2-foundation 0.2.2",
"objc2-metal 0.2.2",
]
[[package]]
name = "objc2-core-location"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "000cfee34e683244f284252ee206a27953279d370e309649dc3ee317b37e5781"
dependencies = [
"block2 0.5.1",
"objc2 0.5.2",
"objc2-contacts",
"objc2-foundation 0.2.2",
]
[[package]] [[package]]
name = "objc2-encode" name = "objc2-encode"
@@ -934,6 +980,19 @@ version = "4.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33"
[[package]]
name = "objc2-foundation"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8"
dependencies = [
"bitflags 2.13.0",
"block2 0.5.1",
"dispatch",
"libc",
"objc2 0.5.2",
]
[[package]] [[package]]
name = "objc2-foundation" name = "objc2-foundation"
version = "0.3.2" version = "0.3.2"
@@ -956,6 +1015,30 @@ dependencies = [
"objc2-core-foundation", "objc2-core-foundation",
] ]
[[package]]
name = "objc2-link-presentation"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398"
dependencies = [
"block2 0.5.1",
"objc2 0.5.2",
"objc2-app-kit",
"objc2-foundation 0.2.2",
]
[[package]]
name = "objc2-metal"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6"
dependencies = [
"bitflags 2.13.0",
"block2 0.5.1",
"objc2 0.5.2",
"objc2-foundation 0.2.2",
]
[[package]] [[package]]
name = "objc2-metal" name = "objc2-metal"
version = "0.3.2" version = "0.3.2"
@@ -965,7 +1048,20 @@ dependencies = [
"bitflags 2.13.0", "bitflags 2.13.0",
"block2 0.6.2", "block2 0.6.2",
"objc2 0.6.4", "objc2 0.6.4",
"objc2-foundation", "objc2-foundation 0.3.2",
]
[[package]]
name = "objc2-quartz-core"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a"
dependencies = [
"bitflags 2.13.0",
"block2 0.5.1",
"objc2 0.5.2",
"objc2-foundation 0.2.2",
"objc2-metal 0.2.2",
] ]
[[package]] [[package]]
@@ -978,8 +1074,63 @@ dependencies = [
"objc2 0.6.4", "objc2 0.6.4",
"objc2-core-foundation", "objc2-core-foundation",
"objc2-core-graphics", "objc2-core-graphics",
"objc2-foundation", "objc2-foundation 0.3.2",
"objc2-metal", "objc2-metal 0.3.2",
]
[[package]]
name = "objc2-symbols"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a684efe3dec1b305badae1a28f6555f6ddd3bb2c2267896782858d5a78404dc"
dependencies = [
"objc2 0.5.2",
"objc2-foundation 0.2.2",
]
[[package]]
name = "objc2-ui-kit"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f"
dependencies = [
"bitflags 2.13.0",
"block2 0.5.1",
"objc2 0.5.2",
"objc2-cloud-kit",
"objc2-core-data",
"objc2-core-image",
"objc2-core-location",
"objc2-foundation 0.2.2",
"objc2-link-presentation",
"objc2-quartz-core 0.2.2",
"objc2-symbols",
"objc2-uniform-type-identifiers",
"objc2-user-notifications",
]
[[package]]
name = "objc2-uniform-type-identifiers"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44fa5f9748dbfe1ca6c0b79ad20725a11eca7c2218bceb4b005cb1be26273bfe"
dependencies = [
"block2 0.5.1",
"objc2 0.5.2",
"objc2-foundation 0.2.2",
]
[[package]]
name = "objc2-user-notifications"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3"
dependencies = [
"bitflags 2.13.0",
"block2 0.5.1",
"objc2 0.5.2",
"objc2-core-location",
"objc2-foundation 0.2.2",
] ]
[[package]] [[package]]
@@ -1045,6 +1196,26 @@ version = "2.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
[[package]]
name = "pin-project"
version = "1.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924"
dependencies = [
"pin-project-internal",
]
[[package]]
name = "pin-project-internal"
version = "1.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "pin-project-lite" name = "pin-project-lite"
version = "0.2.17" version = "0.2.17"
@@ -1079,18 +1250,18 @@ dependencies = [
[[package]] [[package]]
name = "pollster" name = "pollster"
version = "0.4.0" version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3" checksum = "bc6355899e1c9462875b6757c79f3caa011a1fdae12bbb1a2e72dd1f234f8336"
dependencies = [ dependencies = [
"pollster-macro", "pollster-macro",
] ]
[[package]] [[package]]
name = "pollster-macro" name = "pollster-macro"
version = "0.4.0" version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac5da421106a50887c5b51d20806867db377fbb86bacf478ee0500a912e0c113" checksum = "79af046f3496a2db85e4c2fb04f2e9ff77e6b82f29bd43012b398290ce2c811c"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -1186,15 +1357,15 @@ checksum = "40d213455a5f1dc59214213c7330e074ddf8114c9a42411eb890c767357ce135"
dependencies = [ dependencies = [
"objc2 0.6.4", "objc2 0.6.4",
"objc2-core-foundation", "objc2-core-foundation",
"objc2-foundation", "objc2-foundation 0.3.2",
"objc2-quartz-core", "objc2-quartz-core 0.3.2",
] ]
[[package]] [[package]]
name = "redox_syscall" name = "redox_syscall"
version = "0.3.5" version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa"
dependencies = [ dependencies = [
"bitflags 1.3.2", "bitflags 1.3.2",
] ]
@@ -1229,6 +1400,15 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]]
name = "rustc_version"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
dependencies = [
"semver",
]
[[package]] [[package]]
name = "rustix" name = "rustix"
version = "0.38.44" version = "0.38.44"
@@ -1284,9 +1464,9 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]] [[package]]
name = "sctk-adwaita" name = "sctk-adwaita"
version = "0.8.3" version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70b31447ca297092c5a9916fc3b955203157b37c19ca8edde4f52e9843e602c7" checksum = "b6277f0217056f77f1d8f49f2950ac6c278c0d607c45f5ee99328d792ede24ec"
dependencies = [ dependencies = [
"ab_glyph", "ab_glyph",
"log", "log",
@@ -1295,6 +1475,12 @@ dependencies = [
"tiny-skia", "tiny-skia",
] ]
[[package]]
name = "semver"
version = "1.0.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd"
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.228" version = "1.0.228"
@@ -1331,6 +1517,22 @@ version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
[[package]]
name = "simd_cesu8"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11031e251abf8611c80f460e19dbdeb54a66db918e49c65a7065b46ac7aec520"
dependencies = [
"rustc_version",
"simdutf8",
]
[[package]]
name = "simdutf8"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e"
[[package]] [[package]]
name = "slab" name = "slab"
version = "0.4.12" version = "0.4.12"
@@ -1354,9 +1556,9 @@ checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90"
[[package]] [[package]]
name = "smithay-client-toolkit" name = "smithay-client-toolkit"
version = "0.18.1" version = "0.19.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "922fd3eeab3bd820d76537ce8f582b1cf951eceb5475c28500c7457d9d17f53a" checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016"
dependencies = [ dependencies = [
"bitflags 2.13.0", "bitflags 2.13.0",
"calloop", "calloop",
@@ -1522,6 +1724,22 @@ dependencies = [
"winnow", "winnow",
] ]
[[package]]
name = "tracing"
version = "0.1.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
dependencies = [
"pin-project-lite",
"tracing-core",
]
[[package]]
name = "tracing-core"
version = "0.1.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
[[package]] [[package]]
name = "ttf-parser" name = "ttf-parser"
version = "0.25.1" version = "0.25.1"
@@ -1676,9 +1894,9 @@ dependencies = [
[[package]] [[package]]
name = "wayland-protocols" name = "wayland-protocols"
version = "0.31.2" version = "0.32.13"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f81f365b8b4a97f422ac0e8737c438024b5951734506b0e1d775c73030561f4" checksum = "23d0c813de3daa2ed6520af85a3bd49b0e722a3078506899aa9686fea58dc4b6"
dependencies = [ dependencies = [
"bitflags 2.13.0", "bitflags 2.13.0",
"wayland-backend", "wayland-backend",
@@ -1688,9 +1906,9 @@ dependencies = [
[[package]] [[package]]
name = "wayland-protocols-plasma" name = "wayland-protocols-plasma"
version = "0.2.0" version = "0.3.12"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23803551115ff9ea9bce586860c5c5a971e360825a0309264102a9495a5ff479" checksum = "2b6d8cf1eb2c1c31ed1f5643c88a6e53538129d4af80030c8cabd1f9fa884d91"
dependencies = [ dependencies = [
"bitflags 2.13.0", "bitflags 2.13.0",
"wayland-backend", "wayland-backend",
@@ -1701,9 +1919,9 @@ dependencies = [
[[package]] [[package]]
name = "wayland-protocols-wlr" name = "wayland-protocols-wlr"
version = "0.2.0" version = "0.3.12"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad1f61b76b6c2d8742e10f9ba5c3737f6530b4c243132c2a2ccc8aa96fe25cd6" checksum = "eb04e52f7836d7c7976c78ca0250d61e33873c34156a2a1fc9474828ec268234"
dependencies = [ dependencies = [
"bitflags 2.13.0", "bitflags 2.13.0",
"wayland-backend", "wayland-backend",
@@ -1747,9 +1965,9 @@ dependencies = [
[[package]] [[package]]
name = "web-time" name = "web-time"
version = "0.2.4" version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa30049b1c872b72c89866d458eae9f20380ab280ffd1b1e18df2d3e2d98cfe0" checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb"
dependencies = [ dependencies = [
"js-sys", "js-sys",
"wasm-bindgen", "wasm-bindgen",
@@ -1765,7 +1983,7 @@ dependencies = [
"bitflags 2.13.0", "bitflags 2.13.0",
"bytemuck", "bytemuck",
"cfg-if", "cfg-if",
"cfg_aliases 0.2.1", "cfg_aliases",
"document-features", "document-features",
"hashbrown 0.17.1", "hashbrown 0.17.1",
"js-sys", "js-sys",
@@ -1796,7 +2014,7 @@ dependencies = [
"bit-vec", "bit-vec",
"bitflags 2.13.0", "bitflags 2.13.0",
"bytemuck", "bytemuck",
"cfg_aliases 0.2.1", "cfg_aliases",
"document-features", "document-features",
"hashbrown 0.17.1", "hashbrown 0.17.1",
"indexmap", "indexmap",
@@ -1860,7 +2078,7 @@ dependencies = [
"block2 0.6.2", "block2 0.6.2",
"bytemuck", "bytemuck",
"cfg-if", "cfg-if",
"cfg_aliases 0.2.1", "cfg_aliases",
"glow", "glow",
"glutin_wgl_sys", "glutin_wgl_sys",
"gpu-allocator", "gpu-allocator",
@@ -1872,13 +2090,13 @@ dependencies = [
"log", "log",
"naga", "naga",
"naga-types", "naga-types",
"ndk-sys 0.6.0+11769913", "ndk-sys",
"objc2 0.6.4", "objc2 0.6.4",
"objc2-core-foundation", "objc2-core-foundation",
"objc2-core-graphics", "objc2-core-graphics",
"objc2-foundation", "objc2-foundation 0.3.2",
"objc2-metal", "objc2-metal 0.3.2",
"objc2-quartz-core", "objc2-quartz-core 0.3.2",
"once_cell", "once_cell",
"ordered-float", "ordered-float",
"parking_lot", "parking_lot",
@@ -2040,20 +2258,11 @@ dependencies = [
[[package]] [[package]]
name = "windows-sys" name = "windows-sys"
version = "0.45.0" version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [ dependencies = [
"windows-targets 0.42.2", "windows-targets",
]
[[package]]
name = "windows-sys"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
dependencies = [
"windows-targets 0.48.5",
] ]
[[package]] [[package]]
@@ -2062,7 +2271,7 @@ version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
dependencies = [ dependencies = [
"windows-targets 0.52.6", "windows-targets",
] ]
[[package]] [[package]]
@@ -2074,50 +2283,20 @@ dependencies = [
"windows-link", "windows-link",
] ]
[[package]]
name = "windows-targets"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
dependencies = [
"windows_aarch64_gnullvm 0.42.2",
"windows_aarch64_msvc 0.42.2",
"windows_i686_gnu 0.42.2",
"windows_i686_msvc 0.42.2",
"windows_x86_64_gnu 0.42.2",
"windows_x86_64_gnullvm 0.42.2",
"windows_x86_64_msvc 0.42.2",
]
[[package]]
name = "windows-targets"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
dependencies = [
"windows_aarch64_gnullvm 0.48.5",
"windows_aarch64_msvc 0.48.5",
"windows_i686_gnu 0.48.5",
"windows_i686_msvc 0.48.5",
"windows_x86_64_gnu 0.48.5",
"windows_x86_64_gnullvm 0.48.5",
"windows_x86_64_msvc 0.48.5",
]
[[package]] [[package]]
name = "windows-targets" name = "windows-targets"
version = "0.52.6" version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [ dependencies = [
"windows_aarch64_gnullvm 0.52.6", "windows_aarch64_gnullvm",
"windows_aarch64_msvc 0.52.6", "windows_aarch64_msvc",
"windows_i686_gnu 0.52.6", "windows_i686_gnu",
"windows_i686_gnullvm", "windows_i686_gnullvm",
"windows_i686_msvc 0.52.6", "windows_i686_msvc",
"windows_x86_64_gnu 0.52.6", "windows_x86_64_gnu",
"windows_x86_64_gnullvm 0.52.6", "windows_x86_64_gnullvm",
"windows_x86_64_msvc 0.52.6", "windows_x86_64_msvc",
] ]
[[package]] [[package]]
@@ -2129,54 +2308,18 @@ dependencies = [
"windows-link", "windows-link",
] ]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
[[package]] [[package]]
name = "windows_aarch64_gnullvm" name = "windows_aarch64_gnullvm"
version = "0.52.6" version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
[[package]]
name = "windows_aarch64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
[[package]] [[package]]
name = "windows_aarch64_msvc" name = "windows_aarch64_msvc"
version = "0.52.6" version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
[[package]]
name = "windows_i686_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
[[package]] [[package]]
name = "windows_i686_gnu" name = "windows_i686_gnu"
version = "0.52.6" version = "0.52.6"
@@ -2189,72 +2332,24 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
[[package]]
name = "windows_i686_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
[[package]] [[package]]
name = "windows_i686_msvc" name = "windows_i686_msvc"
version = "0.52.6" version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
[[package]]
name = "windows_x86_64_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
[[package]] [[package]]
name = "windows_x86_64_gnu" name = "windows_x86_64_gnu"
version = "0.52.6" version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
[[package]] [[package]]
name = "windows_x86_64_gnullvm" name = "windows_x86_64_gnullvm"
version = "0.52.6" version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
[[package]]
name = "windows_x86_64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
[[package]] [[package]]
name = "windows_x86_64_msvc" name = "windows_x86_64_msvc"
version = "0.52.6" version = "0.52.6"
@@ -2263,37 +2358,41 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]] [[package]]
name = "winit" name = "winit"
version = "0.29.15" version = "0.30.13"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d59ad965a635657faf09c8f062badd885748428933dad8e8bdd64064d92e5ca" checksum = "a6755fa58a9f8350bd1e472d4c3fcc25f824ec358933bba33306d0b63df5978d"
dependencies = [ dependencies = [
"ahash", "ahash",
"android-activity", "android-activity",
"atomic-waker", "atomic-waker",
"bitflags 2.13.0", "bitflags 2.13.0",
"block2 0.5.1",
"bytemuck", "bytemuck",
"calloop", "calloop",
"cfg_aliases 0.1.1", "cfg_aliases",
"concurrent-queue",
"core-foundation", "core-foundation",
"core-graphics", "core-graphics",
"cursor-icon", "cursor-icon",
"icrate", "dpi",
"js-sys", "js-sys",
"libc", "libc",
"log",
"memmap2", "memmap2",
"ndk", "ndk",
"ndk-sys 0.5.0+25.2.9519653", "objc2 0.5.2",
"objc2 0.4.1", "objc2-app-kit",
"once_cell", "objc2-foundation 0.2.2",
"objc2-ui-kit",
"orbclient", "orbclient",
"percent-encoding", "percent-encoding",
"pin-project",
"raw-window-handle", "raw-window-handle",
"redox_syscall 0.3.5", "redox_syscall 0.4.1",
"rustix 0.38.44", "rustix 0.38.44",
"sctk-adwaita", "sctk-adwaita",
"smithay-client-toolkit", "smithay-client-toolkit",
"smol_str", "smol_str",
"tracing",
"unicode-segmentation", "unicode-segmentation",
"wasm-bindgen", "wasm-bindgen",
"wasm-bindgen-futures", "wasm-bindgen-futures",
@@ -2303,7 +2402,7 @@ dependencies = [
"wayland-protocols-plasma", "wayland-protocols-plasma",
"web-sys", "web-sys",
"web-time", "web-time",
"windows-sys 0.48.0", "windows-sys 0.52.0",
"x11-dl", "x11-dl",
"x11rb", "x11rb",
"xkbcommon-dl", "xkbcommon-dl",
+15 -17
View File
@@ -2,15 +2,15 @@
WSG is a Rust library that wraps [wgpu](https://github.com/gfx-rs/wgpu) and [winit](https://crates.io/crates/winit) for simple GPU drawing. It groups the five core wgpu objects (Instance, Surface, Adapter, Device, Queue) behind a single `Context`, adds small building blocks (`Mesh`, `Material`, `PipelineCache`, `Frame`), and exposes the low-level primitives for advanced users. WSG is a Rust library that wraps [wgpu](https://github.com/gfx-rs/wgpu) and [winit](https://crates.io/crates/winit) for simple GPU drawing. It groups the five core wgpu objects (Instance, Surface, Adapter, Device, Queue) behind a single `Context`, adds small building blocks (`Mesh`, `Material`, `PipelineCache`, `Frame`), and exposes the low-level primitives for advanced users.
> **Status: unstable development version.** The manual workflow below is fully working. The high-level "declarative" workflow and the GPU-driven two-pass pipeline described in the architecture docs are **not implemented yet** — see [Status](#status) and [Roadmap](#roadmap). > **Status: unstable development version.** The manual workflow below is fully working, and the high-level "declarative" workflow (automatic `App` scene rendering) works for flat/NDC drawing. The GPU-driven two-pass pipeline described in the architecture docs is **not implemented yet** — see [Status](#status) and [Roadmap](#roadmap).
## Status ## Status
| Area | State | | Area | State |
|------|-------| |------|-------|
| Manual workflow (`Context` + `Renderer` + `PipelineCache`) | ✅ Working | | Manual workflow (`Context` + `Renderer` + `PipelineCache`) | ✅ Working |
| `App` / `AppBuilder` / `AppHandler` event-loop facade | 🚧 Scaffold — window, events and frame presentation work, but the `render()` callback cannot draw yet (the per-frame view is not exposed to it) | | `App` / `AppBuilder` / `AppHandler` event-loop facade | ✅ Working — window, events, frame presentation, and **automatic scene rendering** (the per-frame view is exposed via `Frame::view()`) |
| `Scene` resource/entity registry | 🚧 Registration API works; the engine does not render the scene yet | | `Scene` resource/entity registry | ✅ Working — the engine renders every registered entity automatically in one batched render pass (`App::render_scene`) |
| GPU-driven two-pass pipeline (Compute → indirect draw) | 📋 Roadmap — spec in [docs/tech/ARCHI_CPU_GPU.md](docs/tech/ARCHI_CPU_GPU.md) | | GPU-driven two-pass pipeline (Compute → indirect draw) | 📋 Roadmap — spec in [docs/tech/ARCHI_CPU_GPU.md](docs/tech/ARCHI_CPU_GPU.md) |
| 3D transforms (MVP uniforms, camera in the pipeline) | 📋 Roadmap — the bundled shader draws positions straight to NDC today | | 3D transforms (MVP uniforms, camera in the pipeline) | 📋 Roadmap — the bundled shader draws positions straight to NDC today |
@@ -83,21 +83,19 @@ struct MyGame;
impl AppHandler for MyGame { impl AppHandler for MyGame {
// update() has an empty default — implement it to mutate scene state each frame. // update() has an empty default — implement it to mutate scene state each frame.
fn render(&mut self, _app: &mut App) { // render(app, frame) has a default that draws the whole scene automatically via
// The engine acquires and presents the frame around this callback, // app.render_scene(frame.view()). You don't need to implement it for the common case.
// but scene rendering is not automated yet — see Roadmap.
}
} }
#[pollster::main] #[pollster::main]
async fn main() -> Result<(), wsg_lib::utils::WsgError> { async fn main() -> Result<(), wsg_lib::utils::WsgError> {
let app = AppBuilder::new().build().await?; let app = AppBuilder::new().build().await?;
// Scene registration is available (string IDs): // Register your scene once (string IDs), then App renders it automatically each frame:
// app.cache.register_shader("basic", wsg_lib::utils::BASIC_SHADER_PATH)?;
// app.scene.add_mesh("quad", Arc::new(mesh))?; // app.scene.add_mesh("quad", Arc::new(mesh))?;
// app.scene.add_material("mat", Arc::new(material))?; // app.scene.add_material("mat", Arc::new(Material::new(app.renderer.format(), "basic", &mut app.cache)))?;
// app.scene.add_entity("my_quad", "quad", "mat")?; // app.scene.add_entity("my_quad", "quad", "mat")?;
// ...but the engine will not draw them until the declarative pipeline lands.
app.run(MyGame) app.run(MyGame)
} }
@@ -109,7 +107,7 @@ async fn main() -> Result<(), wsg_lib::utils::WsgError> {
## Architecture overview ## Architecture overview
- **Manager layer (`Context`)** — owns the GPU hardware lifecycle (Instance → Surface → Adapter → Device → Queue). Created once at startup; `configure()` sets up the swapchain, `Frame` wraps each frame's surface texture + view. - **Manager layer (`Context`)** — owns the GPU hardware lifecycle (Instance → Surface → Adapter → Device → Queue). Created once at startup; `configure()` sets up the swapchain, `Frame` wraps each frame's surface texture + view.
- **Executor layer (`Renderer`)** — binds a `Material` pipeline + `Mesh` buffers into a RenderPass and submits the commands. Today this is one encoder + one submit **per object**. - **Executor layer (`Renderer`)** — binds a `Material` pipeline + `Mesh` buffers into a RenderPass and submits the commands. Rendering a whole `Scene` (`render_scene`) batches all entities into **one encoder + one submit per frame**; the low-level `render` still allocates one per object.
- **Supporting pieces** — `PipelineCache` (shader → compiled RenderPipeline, `Arc`-shared), `Material`, `Mesh`/`Vertex`, `Scene` (string-ID registry), `Camera`/`Transform` (types only, not yet used by the pipeline). - **Supporting pieces** — `PipelineCache` (shader → compiled RenderPipeline, `Arc`-shared), `Material`, `Mesh`/`Vertex`, `Scene` (string-ID registry), `Camera`/`Transform` (types only, not yet used by the pipeline).
The planned target architecture — a GPU-driven two-pass pipeline (Compute Pass: world matrices + frustum culling → Indirect Draw Buffer, then a single `draw_indexed_indirect` per frame) — is specified in [docs/tech/ARCHI_APP.md](docs/tech/ARCHI_APP.md) and [docs/tech/ARCHI_CPU_GPU.md](docs/tech/ARCHI_CPU_GPU.md) but is **not implemented yet**. The planned target architecture — a GPU-driven two-pass pipeline (Compute Pass: world matrices + frustum culling → Indirect Draw Buffer, then a single `draw_indexed_indirect` per frame) — is specified in [docs/tech/ARCHI_APP.md](docs/tech/ARCHI_APP.md) and [docs/tech/ARCHI_CPU_GPU.md](docs/tech/ARCHI_CPU_GPU.md) but is **not implemented yet**.
@@ -118,11 +116,11 @@ The planned target architecture — a GPU-driven two-pass pipeline (Compute Pass
| Concept | Type | Responsibility | Status | | Concept | Type | Responsibility | Status |
|---------|------|---------------|--------| |---------|------|---------------|--------|
| App / AppBuilder | Facade | Window lifecycle + winit event loop + frame presentation | 🚧 Scaffold (no scene rendering) | | App / AppBuilder | Facade | Window lifecycle + winit event loop + frame presentation | ✅ (auto scene rendering via `App::render_scene`) |
| AppHandler | Trait | User-defined `update()` / `render()` callbacks | ✅ (render() has no frame access yet) | | AppHandler | Trait | User-defined `update()` / `render()` callbacks | ✅ (`Frame::view()` exposed; default `render` draws the scene) |
| Scene | Struct | String-ID registry: meshes, materials, entities | 🚧 Registration only | | Scene | Struct | String-ID registry: meshes, materials, entities | ✅ (registry auto-rendered by the facade) |
| Context | Struct | GPU hardware lifecycle (Instance, Surface, Adapter, Device, Queue) | ✅ | | Context | Struct | GPU hardware lifecycle (Instance, Surface, Adapter, Device, Queue) | ✅ |
| Renderer | Struct | Binds Material + Mesh into a RenderPass, submits | ✅ (one submit per object) | | Renderer | Struct | Binds Material + Mesh into a RenderPass, submits | ✅ (`render_scene` batches one pass/frame) |
| PipelineCache | Struct | Shader → compiled RenderPipeline cache | ✅ | | PipelineCache | Struct | Shader → compiled RenderPipeline cache | ✅ |
| Material | Struct | Shader ID → RenderPipeline | ✅ | | Material | Struct | Shader ID → RenderPipeline | ✅ |
| Mesh / Vertex | Struct | GPU geometry container / CPU-side vertex tuple | ✅ | | Mesh / Vertex | Struct | GPU geometry container / CPU-side vertex tuple | ✅ |
@@ -145,7 +143,7 @@ pollster = "0.4" # only if you use the async AppBuilder
| Run the working example | `cargo run -p wsg-lib --example manual` | | Run the working example | `cargo run -p wsg-lib --example manual` |
| Check everything (incl. examples) | `cargo check --all-targets` | | Check everything (incl. examples) | `cargo check --all-targets` |
The `manual` example is the reference for the working, pixel-rendering workflow. The `simple` example (App facade) now compiles and opens a window with a running update → render → present loop, but it does not draw a scene yet (see [Roadmap](#roadmap)). The `manual` example is the reference for the low-level workflow. The `simple` example (App facade) registers a colored quad and renders it automatically through the declarative path — it draws a scene without importing wgpu.
## Documentation ## Documentation
@@ -159,7 +157,7 @@ The architecture docs live in `docs/tech/` and are written in **French**. Each d
## Roadmap ## Roadmap
1. **Scene auto-rendering** — `App`/`Renderer` iterate registered entities and draw them in one encoder/submit per frame; expose the frame view to `AppHandler::render` for custom draws. 1. ✅ **Scene auto-rendering** — `App::render_scene` iterates registered entities and draws them in one encoder/submit per frame; the frame view is exposed to `AppHandler::render` for custom draws. (Done 2026-09-16.)
2. **GPU-driven two-pass pipeline** — Compute Pass (world matrices + frustum culling) filling an indirect draw buffer, single `draw_indexed_indirect` (see ARCHI_CPU_GPU). 2. **GPU-driven two-pass pipeline** — Compute Pass (world matrices + frustum culling) filling an indirect draw buffer, single `draw_indexed_indirect` (see ARCHI_CPU_GPU).
3. **CPU→GPU transform sync** — persistent transform buffers with ring (triple) buffering. 3. **CPU→GPU transform sync** — persistent transform buffers with ring (triple) buffering.
4. **Real 3D pipeline** — MVP uniforms + camera support in the vertex shader. 4. **Real 3D pipeline** — MVP uniforms + camera support in the vertex shader.
+103 -130
View File
@@ -1,154 +1,127 @@
# DRAFT — Brouillon d'implémentation # DRAFT — Plan d'implémentation : « 3D + éclairage Phong »
> **Usage.** Ce fichier (dans `docs/`) sert de brouillon pour noter les idées et le plan détaillé de l'étape > **Usage.** Ce fichier (dans `docs/`) sert de brouillon pour le plan détaillé de l'étape en cours.
> en cours. **Son contenu est effacé au début de chaque nouvelle étape.** La source de vérité de l'état est > **Son contenu est effacé au début de chaque nouvelle étape.** La source de vérité de l'état est
> le code + README.md ; les autres docs `docs/*` restent stables. > le code + README.md ; les autres docs `docs/*` restent stables.
>
> **Étape.** 3D + éclairage Phong (ROADMAP 1.3 + 1.5). Objectif MVP : **un mesh 3D éclairé à l'écran**,
> rendu automatiquement par la boucle `App` (Scene auto-render déjà en place).
>
> **État de départ vérifié.**
> - Rendu automatique fonctionnel mais **plat** : `basic_shader.wgsl` pose les positions telles quelles
> (`vec4(position, 1.0)`), aucune matrice, aucune uniform, aucun éclairage.
> - `Renderer::render_scene` parcourt `iter_entities()` en une passe (`&self`, `&Scene`), sans transform.
> - `Scene::entities` : `HashMap<String, (mesh_id, material_id)>` — pas de `Transform` par entité.
> - `Camera` (`resources/camera.rs`) : **fichier orphelin, non exporté** (absent de `resources/mod.rs`) ;
> `Transform`/`Geometry` exportés via `math`.
> - `PipelineCache::build_pipeline` : `bind_group_layouts: &[]`, `immediate_size: 0` — aucun binding.
> - Défaut latente : `basic_shader.wgsl` déclare `@location(1) uv`, `(2) color` alors que le
> `VertexBufferLayout` réel expose `(1) normal`, `(2) uv`, `(3) color`.
--- ---
# Étape : Rendu automatique de la scène + vue de frame exposée ## Étape 1 — Fondations data : Transform + Camera exposées
## 1. Contexte (état réel au 2026-09-16) **But** : donner à chaque entité un `Transform` et rendre `Camera` utilisable via l'API publique, **sans**
toucher au rendu (pure façade de données, validable par compilation).
- `App::run()` : acquiert `Frame` via `context.get_next_frame()`, appelle `handler.render(&mut self)`, - [X] 1.1 **Exporter `Camera`** : dans `lib/src/resources/mod.rs`, ajouter
puis `renderer.present(frame)`. `pub mod camera;` et `pub use camera::Camera;` (aujourd'hui fichier orphelin non compilé). *(fait — 2026-09-16)*
- ⚠️ `handler.render()` ne reçoit **pas** la frame : le callback ne peut rien dessiner. C'est précisément - [X] 1.2 **Type `Entity` + transform** : nouvelle struct
le point bloquant signalé par `docs/PLAN.md` (§ Phase 2 intégra Scene, check-list) et `docs/ROADMAP.md` `Entity { mesh_id: String, material_id: String, transform: Transform }` (module `scene` ou `resources`).
(point de départ : « render() ne peut pas encore dessiner — vue de frame non exposée »). Remplacer `Scene::entities: HashMap<String, (String, String)>` par
- `Renderer::render(view, mesh, material)` existe et fonctionne (usage bas-niveau dans `manual.rs`) : `HashMap<String, Entity>`. Sérialiser `iter_entities()` pour rendre le `&Transform`.
il ouvre 1 encoder + 1 render pass par objet, dessine, soumet. *(fait — `lib/src/scene/entity.rs`)*
- `Scene` a déjà : `add_mesh`, `add_material`, `add_entity`, `iter_entities() -> (label, mesh, mat)`, - [X] 1.3 **Compat API** : garder `add_entity(label, mesh_id, material_id)` (transform identité par défaut)
`get_mesh`, `get_material`, `remove_entity`. + ajouter `add_entity_with_transform(label, mesh_id, material_id, transform)`.
- `Material` porte déjà sa `Arc<RenderPipeline>` (compilée via `PipelineCache`). La scène stocke des Ajouter `entity_transform(label) -> Option<&Transform>` et `set_entity_transform(label, transform)`.
`Arc<Material>`. Donc pour dessiner une scène, le code n'a **pas** besoin de consulter le cache : *(fait — 2026-09-16)*
chaque matériau détient sa pipeline. Le « lien PipelineCache → Scene » du PLAN est donc **conceptuel**, - [X] **Validation** : `cargo check --workspace` 0 warning ; `cargo doc --no-deps` 0 warning ; les exemples
pas indispensable côté rendu pour cette étape. `simple`/`manual` compilent inchangés (défaut : identité ⇒ même rendu). *(fait — 0 warning. Au passage,
`camera.rs` étant désormais compilée, les fonctions glam dépréciées `look_at_rh`/`perspective_rh_gl` ont été
migrées vers `glam::camera::rh::view::look_at_mat4` / `glam::camera::rh::proj::opengl::perspective`.)*
## 2. Objectif ## Étape 2 — Shader Phong `standard_shader.wgsl`
1. Que `AppHandler::render()` reçoive la vue/frame courante. **But** : produire un rendu 3D éclairé via un nouveau shader, sans encore le brancher.
2. Que la scène se rende automatiquement (`app.render(scene)`), sans que `simple.rs` touche à wgpu.
3. Que `simple.rs` affiche le quad (4 sommets, 6 indices, matériau `basic`), en gardant ~15 lignes.
## 3. Plan d'implémentation (détail, dans l'ordre) - [ ] 2.1 **Créer `lib/src/shaders/standard_shader.wgsl`** avec le **contrat vertex correct** :
`@location(0) position : vec3`, `(1) normal : vec3`, `(2) uv : vec2`, `(3) color : vec4`.
- `@group(0) @binding(0)` : `FrameUniforms { view: mat4, proj: mat4, cam_pos: vec4, light_dir: vec4, light_color: vec4 }`
- `@group(1) @binding(0)` : `ObjectUniform { model: mat4 }`
- `vs_main` : `clip_position = proj * view * model * vec4(position,1)` ; passe `normal`/`color` en espace monde.
- `fs_main` : éclairage hémisphérique (ambient) + diffuse directionnel (max(dot(N,L),0)), sortie `vec4(color*light, 1)`.
- **Mode unlit** : un flag dans `FrameUniforms` (ou `light_color` nul) **neutralise la directionnelle** → couleur
plate. Ainsi « 2D » = `standard` non-éclairé, **cas particulier de la 3D** (décision actée).
- [ ] 2.2 **Constantes** : ajouter `STANDARD_SHADER_PATH = "assets/shaders/standard_shader.wgsl"` et
`STANDARD_SHADER: &str = include_str!("../shaders/standard_shader.wgsl")` dans `lib/src/utils/conf.rs`.
- [ ] 2.3 **Migrer `basic` vers le mode unlit de `standard`** (défaut latente réglée) : plus de pipeline au
**layout vide séparé**. Le rendu plat = `standard` non-éclairé (identité/ortho + ambiance) sous le **même
layout uniformisé**. Le fallback embarqué (`BASIC_SHADER`) devient la variante unlit de `standard`.
- [ ] **Validation** : nouveau `shaders/mod.rs` si include_str le requiert ; `cargo check` OK (le shader n'est
pas encore compilé par un pipeline tant que l'Étape 3 ne le charge pas).
### Étape 3.1 — Exposer la vue de frame au callback ## Étape 3 — Infrastructure uniforms dans le `PipelineCache`
**Fichier** : `lib/src/handler.rs` (+ `app.rs`). **But** : permettre aux pipelines de recevoir des uniforms (bind groups) au lieu de `bind_group_layouts: &[]`.
Changer la signature : - [ ] 3.1 **Types bytemuck `Pod`** (nouveau `lib/src/resources/uniform.rs`, ou `math/uniform.rs`) :
```rust - `#[repr(C)] #[derive(Pod, Zeroable, Copy, Clone)] FrameUniforms` (voir 2.1)
fn render(&mut self, app: &mut App, frame: &Frame); - `#[repr(C)] #[derive(...)] ObjectUniform { model: Mat4 }`
``` - (alignement 16 octets : utiliser `Vec4`/tableaux pour éviter le padding). Exporter via le `mod.rs` concerné.
- `Frame` est un type de bibliothèque (`core::Frame`) qui expose `frame.view()` → `&wgpu::TextureView`. - [ ] 3.2 **Bind group layouts** : dans `build_pipeline`, créer 2 `BindGroupLayout`
C'est plus riche et plus stable que de passer le `TextureView` brut : on garde une API bibliothèque. (frame @0 + object @1, chacun avec un buffer uniform `Vertex`/`Fragment`/`Vertex|Fragment` selon usage) et les
- Adapter `handler.rs` docs (consignes `docs/DOCUMENTATION.md` : backticks, description ≤3 lignes, passer dans `PipelineLayoutDescriptor.bind_group_layouts`. `immediate_size` reste 0 (pas de `var<immediate>`).
ce que/qui/quand). - [ ] 3.3 **Acté : un seul layout pour tous** (option A). `build_pipeline` attache **toujours** les 2 bind groups
(frame @0 + object @1). Plus de famille `basic` au layout vide : tout matériau partage le même layout
uniformisé. `manual`/quad plat migrent (Étape 5).
- [ ] **Validation** : `cargo check` 0 warning ; `cargo doc` 0 warning (types documentés, `missing_docs` actif).
**Fichier** : `lib/src/app.rs`, dans `App::run`, branche `RedrawRequested` : ## Étape 4 — Rendu 3D dans le `Renderer`
```rust
let frame = self.context.get_next_frame();
handler.render(&mut self, &frame); // frame est owned (valeur locale) → pas de conflit de borrow avec &mut self
self.renderer.present(frame);
```
> Point d'attention borrow : `frame` est une valeur *owned* détachée de `self.context` une fois acquise,
> on peut donc la passer par référence en même temps que `&mut self` sans erreur du borrow checker.
### Étape 3.2 — Méthode de rendu de scène groupé **But** : `render_scene` applique matrices + éclairage par entité.
**Fichier** : `lib/src/core/renderer.rs`. - [ ] 4.1 **Buffers frame partagés** : créer le `wgpu::Buffer` `FrameUniforms` + `BindGroup(0)` dans
`Renderer::new` (ou à la 1re frame). Écrire chaque frame : view/proj (caméra active) + lumière.
- [ ] 4.2 **Buffers object par entité** : `Renderer` maintient un cache
`RefCell<HashMap<String, (wgpu::Buffer, wgpu::BindGroup)>>` clefé par label d'entité (créé à la 1re rencontre),
car `render_scene(&self, &Scene)` est immuable. Chaque frame : écrire `ObjectUniform.world = entity.transform.to_matrix()` + `set_bind_group(1, ...)`.
- [ ] 4.3 **Caméra active** : ajouter `scene.set_active_camera(Camera)` / `scene.active_camera() -> Option<&Camera>`.
Calcul du `proj` avec l'aspect de la fenêtre (`window.inner_size()` accessible via `App.window`).
- [ ] 4.4 **`draw_entity` étendu** : `set_bind_group(0, frame_bg)` + `set_bind_group(1, object_bg)` avant le draw,
pour **tout** matériau (layout unique). Le chemin bas-niveau `Renderer::render` pose aussi les 2 bind groups
(frame partagé + object du mesh appelant).
- [ ] **Validation** : `cargo check` 0 warning ; exécution `simple` (sans panique, boucle active) ;
`manual` non-régressif (chemin bas-niveau).
Le `Renderer::render(view, mesh, material)` actuel ouvre un encoder+pass **par objet** (N submits par frame ## Étape 5 — Exemple 3D (cube éclairé)
si appelé en boucle). Pour rendre une scène entière proprement, ajouter un rendu **batch** :
```rust **But** : démontrer l'objectif MVP à l'écran et **migrer** les exemples sur le pipeline unifié.
pub fn render_scene(&self, view: &wgpu::TextureView, scene: &Scene) {
let mut encoder = self.device.create_command_encoder(...);
{
let mut pass = encoder.begin_render_pass(/* color attachment: view */);
for (_label, mesh, material) in scene.iter_entities() {
pass.set_pipeline(&material.pipeline);
pass.set_vertex_buffer(0, mesh.vertex_buffer.slice(..));
if let Some(ib) = &mesh.index_buffer {
pass.set_index_buffer(ib.slice(..), wgpu::IndexFormat::Uint16);
pass.draw_indexed(0..mesh.num_indices, 0, 0..1);
} else {
pass.draw(0..mesh.num_vertices, 0..1);
}
}
}
self.queue.submit(once(encoder.finish()));
}
```
- **Batching** : un seul pass pour toutes les entités (aligné sur le principe « batching par matériau »
évoqué dans renderer.rs / README Phase 4.3). On évite N submits/encoder alloués à la volée.
- Conserver `render(view, mesh, material)` (API bas-niveau utilisée par `manual.rs`). Le battle placer du code commun (layout pass / draw d'un mesh) dans un petit helper privé pour éviter la duplication.
- Import `crate::scene::Scene`.
- Documenter selon `docs/DOCUMENTATION.md`.
### Étape 3.3 — Automatisation côté App - [ ] 5.1 **Nouvel exemple `lib/examples/cube.rs`** : cube unitaire (positions + normales), matériau
`standard` éclairé, `Transform` non-identique, camera + lumière directionnelle, rotation dans `AppHandler::update`.
Toujours via `AppBuilder`/scène automatique, **sans importer wgpu** (comme `simple`).
- [ ] 5.2 **Migrer `simple.rs`** (quad plat → `standard` **unlit**, transform identité) et **`manual.rs`** (bas niveau
→ bind groups frame+object posés, unlit). `basic` disparaît comme famille séparée.
- [ ] **Validation** : compile + tourne sans panique ; rotation/éclairage visibles (à confirmer sur GPU/fenêtre).
**Fichier** : `lib/src/app.rs`. ## Étape 6 — Validation globale & docs
Ajouter sur `App` : - [ ] 6.1 `cargo check --workspace` 0 warning ; `cargo doc --no-deps` 0 warning ; `cargo fmt --all`.
```rust - [ ] 6.2 Cas limites (comme à l'étape précédente) : scène vide, mesh non indexé, mesh 0-vertex.
pub fn render_scene(&self, view: &wgpu::TextureView) { - [ ] 6.3 Mettre à jour `README.md` (statut 3D) + `docs/PLAN.md`/`docs/ROADMAP.md` (cases 1.3/1.5 actées).
self.renderer.render_scene(view, &self.scene); - [ ] 6.4 Commits conventionnels (`feat:`, `docs:`), diffs ciblés.
}
```
Le rendu automatique est branché par **défaut** dans le trait (**Option A, décidée**) :
```rust
fn render(&mut self, app: &mut App, frame: &Frame) {
app.render_scene(frame.view());
}
```
→ `simple.rs` **n'implémente même pas `render`** : la scène se rend toute seule, exactement l'esprit
« scene auto-render ». L'utilisateur avancé peut surcharger `render` pour contrôler le dessin.
### Étape 3.4 — Remplir `simple.rs` ---
**Fichier** : `lib/examples/simple.rs`. ## Décisions actées (verrouillées avant l'implémentation)
- Créer le quad (mêmes 4 sommets + 6 indices que dans `manual.rs`, mais sans toucher à wgpu : tout se fait | Décision | Option proposée | Justification |
via `Scene` + `AppBuilder`). |----------|-----------------|---------------|
- Enregistrer le shader : `app.cache.register_shader("basic", utils::BASIC_SHADER_PATH)` | Schéma uniforms | **Acté : 2 bind groups** — frame partagé (@0) + object par entité (@1) | Étendu, portable sur tous backends (Metal/DX12/Vulkan) ; `var<immediate>` neuf, limites de taille et hazard d'écriture par objet ; 2 binds/draw seulement, trivialement « pipeline bind-less » plus tard |
(le shader_id `"basic"` fonctionne déjà en fallback sur `BASIC_SHADER`, cf. `pipeline_cache.rs`). | Emplacement types uniforms | **Acté : `resources/uniform.rs`** (`FrameUniforms`, `ObjectUniform`, types `Pod` bytemuck) | Couche de données GPU (avec Camera/Mesh/Material/Vertex) ; préserve `math/` pur (sans bytemuck ni couplage wgpu) |
- Créer le matériau avec `Material::new(app.renderer.format(), "basic", &mut app.cache)`. | Cache object buffer | `RefCell<HashMap<label, (Buffer, BindGroup)>>` dans `Renderer` | `render_scene(&self)` immuable ; MVP petit nombre d'entités |
- Créer le mesh avec `Mesh::new(app.renderer.device(), &vertices, Some(&indices))`. | Transform dans l'entité | `Entity { mesh_id, material_id, transform }` + `add_entity_with_transform` | `add_entity` garde sa signature (transform identité) |
- Enregistrer dans `app.scene` : `add_mesh`, `add_material`, `add_entity`. | Layout pipeline | **Acté : un seul layout pour tous** (frame @0 + object @1) ; `basic` unlit = variante de `standard` | 2D = cas particulier 3D (décision utilisateur) ; supprime la fourchette à deux layouts pour toujours |
- Tout ce remplissage se fait dans `AppHandler::update()` (ou dans `run()` avant `app.run(...)` — à voir | Exemple démo | Nouvel exemple `cube.rs` (éclairé) ; `simple.rs` et `manual.rs` **migrés** vers le pipeline unifié (unlit) | Démontre le 3D sans dédoubler ; cohérent avec « un seul layout pour tous » |
selon où `app` est constructible ; le plus simple : dans `update(&mut self, app)` une fois). | Correction `basic_shader.wgsl` | **Supprimer** `basic` comme pipeline séparé ; le quad plat devient `standard` unlit | 2D ⊂ 3D : pas de famille de pipeline dédiée |
> ⚠️ Les vertex passent par `Mesh::new(device, ...)` qui exige `wgpu::Device`. **Décision prise** : pour
> l'étape 1, utiliser `app.renderer.device()`/`app.renderer.format()` (accès bibliothèque — l'utilisateur
> n'importe pas wgpu). Un helper haut niveau `Scene::add_quad_entity` pourra être ajouté plus tard.
### Étape 3.5 — Validation
```bash
cargo check --workspace
cargo doc -p wsg-lib --no-deps # exigence : "generated 0 warnings"
cargo run -p wsg-lib --example simple # le quad doit s'afficher
cargo run -p wsg-lib --example manual # le workflow manuel doit rester fonctionnel
cargo fmt --all
```
- Vérifier docs (`docs/DOCUMENTATION.md`) : zéro warning, backticks, chaque item public documenté.
- Committer proprement (conventional commits, ex. `feat(app): expose frame view and auto-render scene`).
## 4. Décisions (actées)
| Sujet | Décision |
|-------|----------|
| Signature de `render` | Ajouter `&Frame` en paramètre (Option A : default → auto-render) |
| Où dessiner la scène | `Renderer::render_scene(view, &Scene)` en **batch** (1 pass unique) |
| PipelineCache dans Scene | **Non bougé pour cette étape** : les `Material` portent déjà leur pipeline ; le lien conceptuel cache↔scene est reporté |
| wgpu dans `simple.rs` | Via `app.renderer.device()`/`format()` : l'utilisateur n'importe pas wgpu |
| Helper quad haut niveau | Reporté (éventuel `Scene::add_quad_entity`) |
## 5. Notes ouvertes / idées
- Le "label" d'entité n'est pour l'instant pas utilisé au rendu (juste itéré). OK pour le MVP.
- `num_indices == 0` dans le cas non indexé : bien gérer le branchement indexé/non indexé (copié depuis
`Renderer::render` actuel).
- Après cette étape, l'ajout de lumières/textures/caméras = simple ajout de données à la Scene
(voir `docs/ROADMAP.md` Phases 2-4 et `docs/PLAN.md` Phase 4).
+32 -10
View File
@@ -11,11 +11,11 @@ generated: { by: human:jerome, at: 2026-07-31T00:00:00Z }
Ce plan définit les étapes prioritaires pour finaliser l'architecture actuelle. L'objectif est de rendre l'API intuitive pour l'utilisateur standard tout en conservant la puissance de contrôle pour l'utilisateur avancé. Ce plan définit les étapes prioritaires pour finaliser l'architecture actuelle. L'objectif est de rendre l'API intuitive pour l'utilisateur standard tout en conservant la puissance de contrôle pour l'utilisateur avancé.
> **Statut réel (à jour au 2026-09-14).** Ce plan couvre la phase de *consolidation* passée ; la source > **Statut réel (à jour au 2026-09-16).** Ce plan couvre la phase de *consolidation* passée ; la source
> de vérité sur l'état actuel est **README.md** et le code. Plusieurs cases `[X]` ci-dessous ont été > de vérité sur l'état actuel est **README.md** et le code. Depuis la révision du 2026-09-14, l'étape
> re-corrigées car elles ne reflétaient plus la réalité : notamment le rendu de la `Scene` n'est > **« Scene auto-render »** a été réalisée : le rendu de la `Scene` est **automatisé** en une seule
> **pas automatisé** (items Phase 2 et Check-list concernés). Depuis, `simple.rs` a été mis en > passe groupée via `App::render_scene(frame.view())` (appelée par défaut dans `AppHandler::render`),
> conformité (API `AppBuilder`, ~15 lignes, compilation sans importer `winit`/`wgpu`). > et `simple.rs` (API `AppBuilder`, sans `winit`/`wgpu`) déclare un quad rendu automatiquement.
## Phase 1 : Finalisation et Nettoyage de l'Existant (Priorité Absolue) ## Phase 1 : Finalisation et Nettoyage de l'Existant (Priorité Absolue)
@@ -45,8 +45,12 @@ Une fois la plomberie encapsulée, nous devons rendre l'assemblage des objets co
### Intégration de la Scene ### Intégration de la Scene
- [X] Formaliser la structure `Scene` : un conteneur qui liste les Entities. - [X] Formaliser la structure `Scene` : un conteneur qui liste les Entities.
- [ ] Associer le `PipelineCache` à la Scene pour que le rendu des matériaux soit automatique (actuellement le cache est porté par `App`, indépendant de la Scene ; le rendu n'est pas automatisé). - [ ] Associer le `PipelineCache` à la Scene pour que la gestion des matériaux soit entièrement portée par la scène (actuellement le cache est porté par `App`, indépendant de la Scene — le rendu de la scène est, lui, déjà automatisé depuis 2026-09-16).
- [ ] Implémenter la logique `app.render(scene)` : cette méthode doit parcourir la scène, récupérer les matériaux, gérer les pipelines via le cache, et soumettre les draw calls (non implémenté — cf. README, étape 1 du Roadmap : scene auto-rendering). - [X] Implémenter la logique de rendu de la scène : `App::render_scene(view)` parcourt la scène,
récupère les matériaux et soumet tous les draw calls en **une seule passe groupée**
(`Renderer::render_scene`), appelée automatiquement chaque frame par l'implémentation par défaut
de `AppHandler::render` (Scene auto-render — réalisé 2026-09-16). Reste à brancher : associé au
`PipelineCache` porté par la `Scene` (cf. ligne précédente).
### Gestion des Matériaux et Shaders ### Gestion des Matériaux et Shaders
@@ -75,8 +79,26 @@ Une fois les phases 1 à 3 validées, nous pourrons introduire :
## Check-list de Vérification pour le LLM d'Assistance ## Check-list de Vérification pour le LLM d'Assistance
- [X] Est-ce que `simple.rs` compile sans importer `winit` ou `wgpu` ? (oui — modèle 15 lignes, API `AppBuilder`) - [X] Est-ce que `simple.rs` compile sans importer `winit` ou `wgpu` ? (oui — modèle 15 lignes, API `AppBuilder`)
- [ ] Est-ce que `App::run` gère bien le cycle update → render → present ? (boucle + présentation OK, mais `render()` ne peut pas encore dessiner — vue de frame non exposée) - [X] Est-ce que `App::run` gère bien le cycle update → render → present ? (oui — la vue de frame est
exposée via `Frame::view()`, `render()` dessine la scène automatiquement en une passe via
`App::render_scene(frame.view())`, la présentation est faite par `App::run`)
- [X] Les modules sont-ils bien exposés via `lib.rs` ? - [X] Les modules sont-ils bien exposés via `lib.rs` ?
- [X] `pollster` est-il uniquement en dev-dependencies ? - [X] `pollster` est-il uniquement en dev-dependencies ? — **obsolète** : depuis la migration
winit 0.30 (2026-09-16), `pollster` est en `dependencies` de la lib (le `block_on` d'init GPU
est désormais appelé dans le code de la lib, `app.rs`, cf. note pour mémoire ci-dessous).
Ce plan garantit que les fondations sont saines. Une fois la Scene rendue automatiquement par `app.render()`, l'ajout de toute nouvelle fonctionnalité (lumières, textures) deviendra une simple question d'ajout de données dans la structure de scène, sans modification de la boucle de rendu. ## Note pour mémoire : couplage au runtime async (pollster)
Depuis la migration winit 0.30, la lib embarque un runtime async pour l'init GPU. Le point de
couplage actuel est **unique** : `pollster::block_on(Context::new(...))` dans `lib/src/app.rs`
(`resumed()`), plus le macro `#[pollster::main]` dans les exemples (crates séparées, hors lib).
À ce stade (un seul appel), **on ne crée volontairement PAS d'abstraction** : ce serait du
sur-engineering pour un seul point d'appel. Mais si la lib acquiert d'autres appels async
(chargements / uploads GPU, etc.), il faudra isoler le runtime derrière un **module-pivot unique**
(`lib/src/exec.rs`, une fonction `block_on`), seul fichier à modifier pour basculer de pollster
vers tokio/futures-executor — le reste du code appelant `crate::exec::block_on(...)`.
Rappel : pollster et tokio sont des runtimes indépendants qui coexistent sans conflit dans un
même binaire ; la seule contre-indication est de faire un `pollster::block_on` **à l'intérieur**
d'un contexte async tokio (blocage imbriqué / deadlock possible).
+13 -2
View File
@@ -12,14 +12,25 @@ generated: { by: human:jerome, at: 2026-07-31T00:00:00Z }
> Basé sur l'architecture existante (ARCHI_APP, ARCHI_ARENES, ARCHI_CPU_GPU, ARCHI_RENDU). > Basé sur l'architecture existante (ARCHI_APP, ARCHI_ARENES, ARCHI_CPU_GPU, ARCHI_RENDU).
> Objectif : prototype fonctionnel d'abord, enrichissement progressif ensuite. > Objectif : prototype fonctionnel d'abord, enrichissement progressif ensuite.
> >
> **Point de départ (état réel au 2026-09-14 — la source de vérité est README.md).** > **Point de départ (état réel au 2026-09-16 — la source de vérité est README.md).**
> Les fondations suivantes existent et fonctionnent déjà ; cette roadmap décrit la **trajectoire à > Les fondations suivantes existent et fonctionnent déjà ; cette roadmap décrit la **trajectoire à
> venir** à partir de cet état (elle reprend les étapes 1-4 du README avant la montée GPU-driven) : > venir** à partir de cet état (elle reprend les étapes 1-4 du README avant la montée GPU-driven) :
> - Workflow manuel (`Context` + `Renderer` + `PipelineCache`) : ✅ fonctionnel (exemple `manual`). > - Workflow manuel (`Context` + `Renderer` + `PipelineCache`) : ✅ fonctionnel (exemple `manual`).
> - Façade `App` / `AppBuilder` / `AppHandler` : 🚧 scaffold — boucle et présentation OK, mais `render()` ne peut pas encore dessiner (vue de frame non exposée) et le rendu de la scène n'est pas automatisé. > - Façade `App` / `AppBuilder` / `AppHandler` : ✅ **Scene auto-render** (2026-09-16) — la vue de frame
> est exposée (`Frame::view()`), `render()` dessine la scène en une passe groupée
> (`App::render_scene`) et la présentation est automatique dans `App::run` (exemple `simple`).
> - `Scene` avec identifiants **String** (décision prise — voir tableau Notes de Décision) : 🚧 enregistrement seul. > - `Scene` avec identifiants **String** (décision prise — voir tableau Notes de Décision) : 🚧 enregistrement seul.
> - `Camera` / `Transform` et `glam` : types et mathématiques présents (`math/`, `resources/camera.rs`), non branchés au pipeline. > - `Camera` / `Transform` et `glam` : types et mathématiques présents (`math/`, `resources/camera.rs`), non branchés au pipeline.
> **Étape suivante (prochaine itération) — « 3D + éclairage Phong » (ROADMAP 1.3 + 1.5).**
> Le rendu automatique est aujourd'hui **plat** : le `basic_shader.wgsl` interprète les positions comme
> déjà en NDC, sans matrice monde/vue/projection ni lumière. L'étape suivante rend la scène réellement
> 3D et éclairée : créer `standard_shader.wgsl` (Phong : matrice `projection * view * world` + lumière
> directionnelle), ajouter les uniform buffers (frame : view/proj/light ; par mesh : world matrix dérivée
> du `Transform`) et les brancher dans `Renderer::render_scene` et `Material`, puis exposer `Camera`/
> `Transform` à la `Scene` (caméra active) et ajouter un mesh de test (cube) à l'exemple. Objectif MVP :
> **un mesh 3D éclairé à l'écran**.
--- ---
## Phase 1️⃣ — Prototype MVP : Un Mesh 3D éclairé à l'écran ## Phase 1️⃣ — Prototype MVP : Un Mesh 3D éclairé à l'écran
+7
View File
@@ -72,6 +72,13 @@ pub trait AppHandler {
- **`update()`** : appelé en premier. L'utilisateur peut modifier librement la scène (transformations, ajout/suppression d'entités). Ces modifications sont synchronisées vers le GPU via un **single buffer** Transform avant la passe de calcul. - **`update()`** : appelé en premier. L'utilisateur peut modifier librement la scène (transformations, ajout/suppression d'entités). Ces modifications sont synchronisées vers le GPU via un **single buffer** Transform avant la passe de calcul.
- **`render()`** : appelé après. Il ne sert qu'à injecter du rendu personnalisé (debug, HUD, etc.). La Scene reste immuable : aucune mutation d'état métier. - **`render()`** : appelé après. Il ne sert qu'à injecter du rendu personnalisé (debug, HUD, etc.). La Scene reste immuable : aucune mutation d'état métier.
> **Note pour mémoire (init GPU / runtime async)** : depuis la migration winit 0.30, l'init GPU
> se fait dans le callback synchrone `resumed()`, donc via `pollster::block_on(Context::new(...))`
> dans `app.rs`. C'est actuellement le **seul** point de couplage de la lib à un runtime async.
> On ne crée volontairement pas d'abstraction tant qu'il n'y a qu'un appel ; si la lib acquiert
> d'autres appels async, isoler le runtime derrière un module-pivot unique (`exec::block_on`),
> seul fichier à modifier pour basculer vers tokio/futures-executor. Voir PLAN.md (note pour mémoire).
## 4. Workflow et Cycle de Vie ## 4. Workflow et Cycle de Vie
### A. Initialisation (Configuration) ### A. Initialisation (Configuration)
+2 -4
View File
@@ -8,10 +8,8 @@ path = "src/lib.rs"
[dependencies] [dependencies]
wgpu = "30.0.0" # Vérifiez la version la plus récente wgpu = "30.0.0" # Vérifiez la version la plus récente
winit = "0.29" # For window management — pinned to match examples winit = "0.30.13" # For window management — pinned to match examples
thiserror = "2" thiserror = "2"
bytemuck = { version = "1.25.0", features = ["derive"] } bytemuck = { version = "1.25.0", features = ["derive"] }
glam = "0.33" glam = "0.33"
pollster = { version="1.0.1", features = ["macro"] }
[dev-dependencies]
pollster = { version="0.4.0", features = ["macro"] }
+143 -84
View File
@@ -1,6 +1,13 @@
//! Workflow bas-niveau : utilisation directe du `Context`, `Renderer`, `PipelineCache`, `Mesh` et
//! `Material`, contournant la façade `App`. Rendu d'un quad plat éclairé via la boucle winit 0.30
//! (`EventLoop::run_app` + `ApplicationHandler`). La fenêtre et le GPU sont créés dans `resumed()`,
//! comme l'exigent winit 0.30 et la migration faite dans `app.rs`.
use std::sync::Arc; use std::sync::Arc;
use winit::event_loop::EventLoop; use winit::application::ApplicationHandler;
use winit::window::WindowBuilder; use winit::dpi::LogicalSize;
use winit::event::WindowEvent;
use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop};
use winit::window::{Window, WindowAttributes};
use wsg_lib::core::Context; use wsg_lib::core::Context;
use wsg_lib::core::Frame; use wsg_lib::core::Frame;
use wsg_lib::core::Renderer; use wsg_lib::core::Renderer;
@@ -10,92 +17,144 @@ use wsg_lib::resources::Mesh;
use wsg_lib::resources::Vertex; use wsg_lib::resources::Vertex;
use wsg_lib::utils; use wsg_lib::utils;
/// Application bas-niveau : détient les objets GPU + window, tous créés dans `resumed`.
struct App {
/// Fenêtre système, partagée via Arc (comme dans app.rs).
window: Option<Arc<Window>>,
/// Contexte GPU (Instance, Surface, Adapter, Device, Queue).
context: Option<Context>,
/// Couche d'exécution qui soumet les draw calls.
renderer: Option<Renderer>,
/// Cache de shaders/pipelines.
cache: Option<PipelineCache>,
/// Matériau (pipeline) du quad.
material: Option<Material>,
/// Mesh du quad (sommets + indices).
mesh: Option<Mesh>,
}
impl ApplicationHandler for App {
/// Crée la fenêtre puis le GPU, et construit le mesh/matériau. Exécuté une fois au démarrage.
/// Redondant `resumed` pour créer à nouveau ? double protection par `self.context.is_some()`.
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
if self.context.is_some() {
return;
}
event_loop.set_control_flow(ControlFlow::Poll);
let attrs = WindowAttributes::default()
.with_title("WSG Manual")
.with_inner_size(LogicalSize::new(800.0, 600.0));
let window = Arc::new(event_loop.create_window(attrs).unwrap());
// 1. Initialisation
let context = pollster::block_on(Context::new(window.clone())).expect("Échec init GPU");
// Configuration de la surface et récupération du format
let format = context
.configure(&context.adapter, 800, 600)
.expect("Échec configuration");
// 2. Initialisation du Renderer (Il récupère tout ce dont il a besoin)
let device = Arc::new(context.device.clone());
let mut cache = PipelineCache::new(device);
cache
.register_shader("basic", utils::BASIC_SHADER_PATH)
.unwrap();
let renderer = Renderer::new(&context, format);
// 3. Material : On utilise renderer.device() et renderer.format()
let material = Material::new(renderer.format(), "basic", &mut cache);
// Mesh : On utilise le device du renderer
let vertices = [
// Position (x,y,z) | Normale (x,y,z) | UV (u,v) | Couleur (r,g,b,a)
Vertex {
position: [-0.5, 0.5, 0.0],
normal: [0.0, 0.0, 1.0],
uv: [0.0, 0.0],
color: [1.0, 0.0, 0.0, 1.0],
}, // Haut-Gauche (Rouge)
Vertex {
position: [0.5, 0.5, 0.0],
normal: [0.0, 0.0, 1.0],
uv: [1.0, 0.0],
color: [0.0, 1.0, 0.0, 1.0],
}, // Haut-Droite (Vert)
Vertex {
position: [0.5, -0.5, 0.0],
normal: [0.0, 0.0, 1.0],
uv: [1.0, 1.0],
color: [0.0, 0.0, 1.0, 1.0],
}, // Bas-Droite (Bleu)
Vertex {
position: [-0.5, -0.5, 0.0],
normal: [0.0, 0.0, 1.0],
uv: [0.0, 1.0],
color: [1.0, 1.0, 0.0, 1.0],
}, // Bas-Gauche (Jaune)
];
let indices: [u16; 6] = [0, 1, 2, 0, 2, 3];
let mesh = Mesh::new(renderer.device(), &vertices, Some(&indices));
self.window = Some(window);
self.context = Some(context);
self.renderer = Some(renderer);
self.cache = Some(cache);
self.material = Some(material);
self.mesh = Some(mesh);
}
/// À chaque frame, demande un redessin pour un rendu continu (animation).
fn about_to_wait(&mut self, _event_loop: &ActiveEventLoop) {
if let Some(window) = &self.window {
window.request_redraw();
}
}
/// Dispatch des événements de fenêtre : RedrawRequested rend puis présente, CloseRequested quitte.
fn window_event(
&mut self,
event_loop: &ActiveEventLoop,
_window_id: winit::window::WindowId,
event: WindowEvent,
) {
match event {
winit::event::WindowEvent::RedrawRequested => {
if let (Some(context), Some(renderer), Some(mesh), Some(material)) =
(&self.context, &self.renderer, &self.mesh, &self.material)
{
if let Some(frame) = Frame::try_new(&context.surface) {
// 1. Rendu (plus d'arguments device/queue inutiles)
renderer.render(frame.view(), mesh, material);
// 2. Présentation
renderer.present(frame);
}
}
}
winit::event::WindowEvent::CloseRequested => {
event_loop.exit(); // C'est ici que tu demandes à la boucle de s'arrêter
}
_ => (),
}
}
}
fn main() { fn main() {
println!( println!(
"Répertoire courant : {:?}", "Répertoire courant : {:?}",
std::env::current_dir().unwrap() std::env::current_dir().unwrap()
); );
let event_loop = EventLoop::new().unwrap(); let event_loop = EventLoop::new().unwrap();
let window = Arc::new(WindowBuilder::new().build(&event_loop).unwrap()); let mut app = App {
window: None,
// 1. Initialisation context: None,
let context = pollster::block_on(Context::new(window.clone())).expect("Échec init GPU"); renderer: None,
cache: None,
// Configuration de la surface et récupération du format material: None,
let format = context mesh: None,
.configure(&context.adapter, 800, 600) };
.expect("Échec configuration"); event_loop.run_app(&mut app).unwrap();
// 2. Initialisation du Renderer (Il récupère tout ce dont il a besoin)
let device = Arc::new(context.device.clone());
let mut cache = PipelineCache::new(device);
cache
.register_shader("basic", utils::BASIC_SHADER_PATH)
.unwrap();
let renderer = Renderer::new(&context, format);
// 3. Material : On utilise renderer.device() et renderer.format()
let material = Material::new(renderer.format(), "basic", &mut cache);
// Mesh : On utilise le device du renderer
let vertices = [
// Position (x,y,z) | Normale (x,y,z) | UV (u,v) | Couleur (r,g,b,a)
Vertex {
position: [-0.5, 0.5, 0.0],
normal: [0.0, 0.0, 1.0],
uv: [0.0, 0.0],
color: [1.0, 0.0, 0.0, 1.0],
}, // Haut-Gauche (Rouge)
Vertex {
position: [0.5, 0.5, 0.0],
normal: [0.0, 0.0, 1.0],
uv: [1.0, 0.0],
color: [0.0, 1.0, 0.0, 1.0],
}, // Haut-Droite (Vert)
Vertex {
position: [0.5, -0.5, 0.0],
normal: [0.0, 0.0, 1.0],
uv: [1.0, 1.0],
color: [0.0, 0.0, 1.0, 1.0],
}, // Bas-Droite (Bleu)
Vertex {
position: [-0.5, -0.5, 0.0],
normal: [0.0, 0.0, 1.0],
uv: [0.0, 1.0],
color: [1.0, 1.0, 0.0, 1.0],
}, // Bas-Gauche (Jaune)
];
let indices: [u16; 6] = [0, 1, 2, 0, 2, 3];
let mesh = Mesh::new(renderer.device(), &vertices, Some(&indices));
// Render loop
event_loop
.run(|event, elwt| {
match event {
winit::event::Event::AboutToWait => {
window.request_redraw();
}
winit::event::Event::WindowEvent {
event: winit::event::WindowEvent::RedrawRequested,
..
} => {
if let Some(frame) = Frame::try_new(&context.surface) {
// 1. Rendu (plus d'arguments device/queue inutiles)
renderer.render(frame.view(), &mesh, &material);
// 2. Présentation
renderer.present(frame);
}
}
winit::event::Event::WindowEvent {
event: winit::event::WindowEvent::CloseRequested,
..
} => {
elwt.exit(); // C'est ici que tu demandes à la boucle de s'arrêter
}
_ => (),
}
})
.unwrap();
} }
+56 -52
View File
@@ -1,8 +1,9 @@
//! Workflow déclaratif minimal, sans manipulation WGPU explicite dans ce fichier. //! Workflow déclaratif minimal, sans manipulation WGPU explicite dans ce fichier.
//! `AppBuilder` ouvre la fenêtre, construit le `Context`/`Renderer` et fait tourner la boucle //! `AppBuilder` crée l'event loop puis `App::run` ouvre la fenêtre, construit le `Context`/`Renderer`
//! update → render → present. La scène se rend automatiquement : la méthode `render()` par défaut //! et fait tourner la boucle update → render → present. Depuis la migration winit 0.30, le GPU n'existe
//! du trait `AppHandler` appelle `app.render_scene(frame.view())`, donc l'utilisateur n'implémente //! qu'après `resumed` : c'est pourquoi l'enregistrement shader + la création mesh/matériau/entité vivent
//! même pas `render` ici — il ne fait que remplir `app.scene` avec un mesh, un matériau et une entité. //! dans le hook `AppHandler::setup`, appelé une fois le contexte prêt. La scène se rend automatiquement :
//! la méthode `render()` par défaut appelle `app.render_scene(frame.view())`.
use std::sync::Arc; use std::sync::Arc;
use wsg_lib::AppHandler; use wsg_lib::AppHandler;
use wsg_lib::app::AppBuilder; use wsg_lib::app::AppBuilder;
@@ -11,56 +12,59 @@ use wsg_lib::utils::WsgError;
struct MonQuad; struct MonQuad;
impl AppHandler for MonQuad {} impl AppHandler for MonQuad {
fn setup(&mut self, app: &mut wsg_lib::App) {
let format = app.renderer().format();
// Enregistrement du shader, création du matériau et du mesh du quad (sans importer wgpu).
app.cache()
.register_shader("basic", wsg_lib::utils::BASIC_SHADER_PATH)
.unwrap();
let vertices = [
Vertex {
position: [-0.5, 0.5, 0.0],
normal: [0.0, 0.0, 1.0],
uv: [0.0, 0.0],
color: [1.0, 0.0, 0.0, 1.0],
}, // Haut-Gauche (Rouge)
Vertex {
position: [0.5, 0.5, 0.0],
normal: [0.0, 0.0, 1.0],
uv: [1.0, 0.0],
color: [0.0, 1.0, 0.0, 1.0],
}, // Haut-Droite (Vert)
Vertex {
position: [0.5, -0.5, 0.0],
normal: [0.0, 0.0, 1.0],
uv: [1.0, 1.0],
color: [0.0, 0.0, 1.0, 1.0],
}, // Bas-Droite (Bleu)
Vertex {
position: [-0.5, -0.5, 0.0],
normal: [0.0, 0.0, 1.0],
uv: [0.0, 1.0],
color: [1.0, 1.0, 0.0, 1.0],
}, // Bas-Gauche (Jaune)
];
let indices: [u16; 6] = [0, 1, 2, 0, 2, 3];
let mesh = Arc::new(Mesh::new(
app.renderer().device(),
&vertices,
Some(&indices),
));
let material = Arc::new(Material::new(format, "basic", app.cache()));
app.scene.add_mesh("quad_mesh", mesh).unwrap();
app.scene.add_material("basic_material", material).unwrap();
app.scene
.add_entity("quad", "quad_mesh", "basic_material")
.unwrap();
}
}
#[pollster::main] #[pollster::main]
async fn main() -> Result<(), WsgError> { async fn main() -> Result<(), WsgError> {
let mut app = AppBuilder::new().title("WSG Simple").build().await?; let app = AppBuilder::new().title("WSG Simple").build().await?;
// Enregistrement du shader, création du matériau et du mesh du quad (sans importer wgpu).
app.cache
.register_shader("basic", wsg_lib::utils::BASIC_SHADER_PATH)
.unwrap();
let vertices = [
Vertex {
position: [-0.5, 0.5, 0.0],
normal: [0.0, 0.0, 1.0],
uv: [0.0, 0.0],
color: [1.0, 0.0, 0.0, 1.0],
}, // Haut-Gauche (Rouge)
Vertex {
position: [0.5, 0.5, 0.0],
normal: [0.0, 0.0, 1.0],
uv: [1.0, 0.0],
color: [0.0, 1.0, 0.0, 1.0],
}, // Haut-Droite (Vert)
Vertex {
position: [0.5, -0.5, 0.0],
normal: [0.0, 0.0, 1.0],
uv: [1.0, 1.0],
color: [0.0, 0.0, 1.0, 1.0],
}, // Bas-Droite (Bleu)
Vertex {
position: [-0.5, -0.5, 0.0],
normal: [0.0, 0.0, 1.0],
uv: [0.0, 1.0],
color: [1.0, 1.0, 0.0, 1.0],
}, // Bas-Gauche (Jaune)
];
let indices: [u16; 6] = [0, 1, 2, 0, 2, 3];
let mesh = Arc::new(Mesh::new(app.renderer.device(), &vertices, Some(&indices)));
let material = Arc::new(Material::new(
app.renderer.format(),
"basic",
&mut app.cache,
));
app.scene.add_mesh("quad_mesh", mesh).unwrap();
app.scene.add_material("basic_material", material).unwrap();
app.scene
.add_entity("quad", "quad_mesh", "basic_material")
.unwrap();
app.run(MonQuad) app.run(MonQuad)
} }
+196 -71
View File
@@ -12,6 +12,14 @@
//! - **scene::scene**: Exposes Scene as mutable field so users can register resources and entities. //! - **scene::scene**: Exposes Scene as mutable field so users can register resources and entities.
//! - **utils::conf**: Provides default window title, dimensions, and embedded WGSL source. //! - **utils::conf**: Provides default window title, dimensions, and embedded WGSL source.
//! - **handler**: Defines the AppHandler trait that users implement for custom logic. //! - **handler**: Defines the AppHandler trait that users implement for custom logic.
//!
//! ## Architecture Note (winit 0.30)
//! winit 0.30 removed the synchronous window-creation API (`WindowBuilder`) and the closure-based
//! `EventLoop::run`, replacing them with the [`ApplicationHandler`] model driven by `EventLoop::run_app`.
//! Windows can only be created inside `ApplicationHandler::resumed()`. Consequently this module builds
//! the window and GPU context lazily inside `AppRunner`'s `resumed()` callback, and exposes the
//! user-facing `AppBuilder::build → App::run` flow over that model. `AppHandler::setup()` is invoked
//! once right after GPU initialization so users can register shaders/meshes/materials/entities.
use crate::AppHandler; use crate::AppHandler;
use crate::core::{Context, Renderer}; use crate::core::{Context, Renderer};
@@ -20,68 +28,96 @@ use crate::scene::Scene;
use crate::utils::WsgError; use crate::utils::WsgError;
use crate::utils::conf::{APP_DEFAULT_HEIGHT, APP_DEFAULT_TITLE, APP_DEFAULT_WIDTH}; use crate::utils::conf::{APP_DEFAULT_HEIGHT, APP_DEFAULT_TITLE, APP_DEFAULT_WIDTH};
use std::sync::Arc; use std::sync::Arc;
use winit::event_loop::EventLoop; use winit::application::ApplicationHandler;
use winit::window::Window; use winit::dpi::LogicalSize;
use winit::event::WindowEvent;
use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop};
use winit::window::{Window, WindowAttributes};
/// High-level application facade that orchestrates window lifecycle, event loop, and rendering automation. /// High-level application facade that orchestrates window lifecycle, event loop, and rendering automation.
/// Encapsulates all five WGPU objects (Instance, Surface, Adapter, Device, Queue) plus the render loop. /// Encapsulates all five WGPU objects (Instance, Surface, Adapter, Device, Queue) plus the render loop.
/// Users create an App via AppBuilder, then run it with their implementation of AppHandler. /// Users create an App via AppBuilder, then run it with their implementation of AppHandler.
///
/// The GPU-facing fields (`context`, `renderer`, `window`, `cache`) are created lazily when the
/// application is resumed (see `AppRunner`); they are only populated after `App::run` has started.
/// Access them through the `context()`, `renderer()`, `window()` and `cache()` accessors, which is
/// guaranteed to work inside `AppHandler::setup`, `update` and `render`.
pub struct App { pub struct App {
/// GPU hardware context — owns Instance, Surface, Adapter, Device, Queue lifecycle. /// Resource depot and entity graph — users register Meshes/Materials here during `AppHandler::setup`.
pub context: Context,
/// Executor layer — binds Materials and Meshes into RenderPasses during draw calls.
pub renderer: Renderer,
/// Winit event loop for window management. Set to None after run() consumes it.
pub event_loop: Option<EventLoop<()>>, // On met en Option pour pouvoir faire .take() facilement
/// Shader compilation cache — manages RenderPipelines keyed by shader_id.
pub cache: PipelineCache,
/// Resource depot and entity graph — users register Meshes/Materials here before the render loop begins.
pub scene: Scene, pub scene: Scene,
/// Window title, read by the runner when the window is created in `resumed`.
pub(crate) title: String,
/// Window width, read by the runner when the window is created in `resumed`.
pub(crate) width: u32,
/// Window height, read by the runner when the window is created in `resumed`.
pub(crate) height: u32,
/// Winit event loop for window management. Set to None after run() consumes it.
event_loop: Option<EventLoop<()>>, // On met en Option pour pouvoir faire .take() facilement
/// GPU hardware context — owns Instance, Surface, Adapter, Device, Queue lifecycle.
context: Option<Context>,
/// Executor layer — binds Materials and Meshes into RenderPasses during draw calls.
renderer: Option<Renderer>,
/// The OS-level window backing this application. Shared via Arc for multi-owner access. /// The OS-level window backing this application. Shared via Arc for multi-owner access.
pub window: Arc<Window>, window: Option<Arc<Window>>,
/// Shader compilation cache — manages RenderPipelines keyed by shader_id.
cache: Option<PipelineCache>,
} }
impl App { impl App {
/// Returns a reference to the GPU renderer.
/// Panics if called before `App::run` has created the renderer (i.e. before `resumed` fires).
pub fn renderer(&self) -> &Renderer {
self.renderer
.as_ref()
.expect("renderer not initialized yet — call app.run(handler) first")
}
/// Returns a reference to the GPU hardware context.
/// Panics if called before `App::run` has created the context (i.e. before `resumed` fires).
pub fn context(&self) -> &Context {
self.context
.as_ref()
.expect("context not initialized yet — call app.run(handler) first")
}
/// Returns a mutable reference to the shader compilation cache.
/// Panics if called before `App::run` has created the cache (i.e. before `resumed` fires).
pub fn cache(&mut self) -> &mut PipelineCache {
self.cache
.as_mut()
.expect("cache not initialized yet — call app.run(handler) first")
}
/// Returns a reference to the window backing this application.
/// Panics if called before `App::run` has created the window (i.e. before `resumed` fires).
pub fn window(&self) -> &Window {
self.window
.as_ref()
.expect("window not initialized yet — call app.run(handler) first")
.as_ref()
}
/// Runs the application's main loop: processes events, updates logic per frame, renders, and presents. /// Runs the application's main loop: processes events, updates logic per frame, renders, and presents.
/// Inputs: handler — user-provided AppHandler implementation containing game logic. /// Inputs: handler — user-provided AppHandler implementation containing game logic.
/// Returns Ok(()) on success or Err(WsgError::WindowSystem) if the event loop exits abnormally. /// Returns Ok(()) on success or Err(WsgError::WindowSystem) if the event loop exits abnormally.
/// Called once at application entry point; runs until the window is closed or an error occurs. /// Called once at application entry point; runs until the window is closed or an error occurs.
/// Internal steps: 1) take EventLoop from Option → 2) enter winit event loop → /// Internal steps: 1) take EventLoop from Option → 2) build an `AppRunner` around the handler →
/// 3a) on AboutToWait: call handler.update() + request_redraw → /// 3) on resumed: create window/context/renderer/cache and call handler.setup() →
/// 3b) on RedrawRequested: acquire frame → call handler.render() → present frame → /// 4) on about_to_wait: call handler.update() + request_redraw →
/// 3c) on CloseRequested: exit event loop. /// 5) on RedrawRequested: acquire frame → call handler.render() → present frame →
pub fn run<H: AppHandler + 'static>(mut self, mut handler: H) -> Result<(), WsgError> { /// 6) on CloseRequested: exit the event loop.
pub fn run<H: AppHandler + 'static>(mut self, handler: H) -> Result<(), WsgError> {
// On extrait l'event_loop de manière sûre grâce au Option // On extrait l'event_loop de manière sûre grâce au Option
let event_loop = self.event_loop.take().ok_or(WsgError::WindowSystem)?; // Erreur si déjà pris let event_loop = self.event_loop.take().ok_or(WsgError::WindowSystem)?; // Erreur si déjà pris
let mut runner = AppRunner {
title: self.title.clone(),
width: self.width,
height: self.height,
handler,
app: None,
};
event_loop event_loop
.run(move |event, elwt| { .run_app(&mut runner)
match event {
winit::event::Event::AboutToWait => {
// update logic
handler.update(&mut self);
self.window.request_redraw();
}
winit::event::Event::WindowEvent {
event: winit::event::WindowEvent::RedrawRequested,
..
} => {
// Rendering logic
let frame = self.context.get_next_frame();
// On appelle le render() de l'utilisateur (reçoit la frame courante)
handler.render(&mut self, &frame);
// On présente automatiquement
self.renderer.present(frame);
}
winit::event::Event::WindowEvent {
event: winit::event::WindowEvent::CloseRequested,
..
} => {
elwt.exit();
}
_ => {}
}
})
.map_err(|_| WsgError::WindowSystem) .map_err(|_| WsgError::WindowSystem)
} }
@@ -90,7 +126,7 @@ impl App {
/// who override `render` to control drawing themselves. /// who override `render` to control drawing themselves.
/// Inputs: view — the frame's texture view acting as the color attachment target. /// Inputs: view — the frame's texture view acting as the color attachment target.
pub fn render_scene(&self, view: &wgpu::TextureView) { pub fn render_scene(&self, view: &wgpu::TextureView) {
self.renderer.render_scene(view, &self.scene); self.renderer().render_scene(view, &self.scene);
} }
} }
@@ -128,34 +164,123 @@ impl AppBuilder {
self.height = height; self.height = height;
self self
} }
/// Builds the configured `App` instance by creating all required components in order: /// Builds the configured `App` instance: creates the event loop and stores the window
/// EventLoop → Window → Context → Renderer → PipelineCache → Scene. /// configuration. The GPU context, window and renderer are created later, when the event loop
/// Returns Ok(App) on success or Err(WsgError) if any component fails during creation. /// is resumed (inside `App::run`), because winit 0.30 only allows window creation in that phase.
/// Called after setting desired properties via the builder pattern; triggers async GPU initialization. /// Returns Ok(App) on success or Err(WsgError) if the event loop cannot be created.
/// Called after setting desired properties via the builder pattern before `App::run`.
pub async fn build(self) -> Result<App, WsgError> { pub async fn build(self) -> Result<App, WsgError> {
let event_loop = EventLoop::new().unwrap(); let event_loop = EventLoop::new().map_err(|_| WsgError::WindowSystem)?;
let window = Arc::new(
winit::window::WindowBuilder::new()
.with_title(&self.title)
.build(&event_loop)
.map_err(|_| WsgError::WindowSystem)?,
);
let context = Context::new(window.clone()).await?;
let device = Arc::new(context.device.clone());
let format = context
.configure(&context.adapter, self.width, self.height)
.map_err(|_| WsgError::SurfaceIncompatible)?;
let renderer = Renderer::new(&context, format);
let cache = PipelineCache::new(device);
let scene = Scene::new();
Ok(App { Ok(App {
context, scene: Scene::new(),
renderer, title: self.title,
cache, width: self.width,
scene, height: self.height,
event_loop: Some(event_loop), event_loop: Some(event_loop),
window, context: None,
renderer: None,
window: None,
cache: None,
}) })
} }
} }
/// Internal runner that adapts a user `AppHandler` to winit's 0.30 `ApplicationHandler` model.
/// It owns the window/GPU lifecycle: everything is created lazily inside `resumed()`, then the
/// user's `setup`, `update` and `render` hooks are driven from the corresponding winit events.
struct AppRunner<H: AppHandler> {
/// Window title, applied when the window is created in `resumed`.
title: String,
/// Window width in pixels, applied when the window is created in `resumed`.
width: u32,
/// Window height in pixels, applied when the window is created in `resumed`.
height: u32,
/// The user-provided game logic.
handler: H,
/// The fully-built App facade, populated on the first `resumed` event.
app: Option<App>,
}
impl<H: AppHandler> ApplicationHandler for AppRunner<H> {
/// Builds the window, GPU context, renderer and shader cache, then invokes the user's `setup`.
/// Guarded so redundant back-to-back `resumed` events do not re-initialize the GPU.
/// Inputs: event_loop — the active event loop used to create the window and control redrawing.
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
if self.app.is_some() {
return;
}
event_loop.set_control_flow(ControlFlow::Poll);
let attrs = WindowAttributes::default()
.with_title(&self.title)
.with_inner_size(LogicalSize::new(self.width as f64, self.height as f64));
let window = Arc::new(
event_loop
.create_window(attrs)
.map_err(|_| WsgError::WindowSystem)
.expect("failed to create window"),
);
// Initialization GPU (bloquant, simplifié au max)
let context = pollster::block_on(Context::new(window.clone())).expect("Échec init GPU");
let format = context
.configure(&context.adapter, self.width, self.height)
.expect("Échec configuration surface");
let device = Arc::new(context.device.clone());
let cache = PipelineCache::new(device);
let renderer = Renderer::new(&context, format);
let mut app = App {
scene: Scene::new(),
title: self.title.clone(),
width: self.width,
height: self.height,
event_loop: None,
context: Some(context),
renderer: Some(renderer),
window: Some(window),
cache: Some(cache),
};
// On laisse l'utilisateur enregistrer shaders/meshes/matériaux/entités une fois le GPU prêt.
self.handler.setup(&mut app);
self.app = Some(app);
}
/// Drives the user's per-frame update and requests a redraw so the window renders continuously.
/// Inputs: _event_loop — active event loop (unused here).
fn about_to_wait(&mut self, _event_loop: &ActiveEventLoop) {
let Some(app) = self.app.as_mut() else {
return;
};
self.handler.update(app);
app.window().request_redraw();
}
/// Dispatches window events: RedrawRequested renders/presents a frame, CloseRequested exits.
/// Inputs: event_loop — active event loop, used to exit on close; event — the window event.
fn window_event(
&mut self,
event_loop: &ActiveEventLoop,
_window_id: winit::window::WindowId,
event: WindowEvent,
) {
let Some(app) = self.app.as_mut() else {
return;
};
match event {
WindowEvent::RedrawRequested => {
// Rendering logic
let frame = app.context().get_next_frame();
// On appelle le render() de l'utilisateur (reçoit la frame courante)
self.handler.render(app, &frame);
// On présente automatiquement
app.renderer().present(frame);
}
WindowEvent::CloseRequested => {
event_loop.exit();
}
_ => {}
}
}
}
+1 -1
View File
@@ -113,7 +113,7 @@ impl Renderer {
..Default::default() ..Default::default()
}); });
for (_label, mesh, material) in scene.iter_entities() { for (_label, mesh, material, _transform) in scene.iter_entities() {
draw_entity(&mut render_pass, mesh, material); draw_entity(&mut render_pass, mesh, material);
} }
} }
+6
View File
@@ -24,6 +24,12 @@ use crate::core::Frame;
/// render (draw call execution). Default implementations provide empty update and automatic /// render (draw call execution). Default implementations provide empty update and automatic
/// scene rendering for convenience. /// scene rendering for convenience.
pub trait AppHandler { pub trait AppHandler {
/// Called once by `App::run`, right after the window/GPU context are created (winit `resumed`).
/// Use it to register shaders, build Meshes/Materials, and populate `app.scene` before the loop
/// starts. This replaces the pre-`run` setup that was possible before the winit 0.30 migration.
/// Default implementation does nothing.
/// Inputs: app — mutable reference to the fully-initialized App facade.
fn setup(&mut self, _app: &mut App) {}
/// Called once per frame before rendering begins. Used for physics updates, input processing, /// Called once per frame before rendering begins. Used for physics updates, input processing,
/// entity management, and any other pre-render logic. Default implementation does nothing. /// entity management, and any other pre-render logic. Default implementation does nothing.
/// Inputs: _app — mutable reference to the App facade providing access to all subsystems. /// Inputs: _app — mutable reference to the App facade providing access to all subsystems.
+9 -5
View File
@@ -13,7 +13,7 @@
//! - `view_matrix()`: Computes the view matrix //! - `view_matrix()`: Computes the view matrix
//! - `projection_matrix()`: Computes the projection matrix //! - `projection_matrix()`: Computes the projection matrix
use glam::{Vec3, Mat4}; use glam::{Mat4, Vec3};
/// Represents a 3D camera for viewing the scene. /// Represents a 3D camera for viewing the scene.
/// ///
@@ -31,7 +31,11 @@ pub struct Camera {
impl Camera { impl Camera {
/// Creates a new camera with specified position, target, and up vector. /// Creates a new camera with specified position, target, and up vector.
pub fn new(position: Vec3, target: Vec3, up: Vec3) -> Self { pub fn new(position: Vec3, target: Vec3, up: Vec3) -> Self {
Self { position, target, up } Self {
position,
target,
up,
}
} }
/// Computes the view matrix for this camera. /// Computes the view matrix for this camera.
@@ -39,7 +43,7 @@ impl Camera {
/// # Returns /// # Returns
/// A `Mat4` representing the view transformation matrix /// A `Mat4` representing the view transformation matrix
pub fn view_matrix(&self) -> Mat4 { pub fn view_matrix(&self) -> Mat4 {
Mat4::look_at_rh(self.position, self.target, self.up) glam::camera::rh::view::look_at_mat4(self.position, self.target, self.up)
} }
/// Computes the projection matrix for this camera. /// Computes the projection matrix for this camera.
@@ -53,6 +57,6 @@ impl Camera {
/// # Returns /// # Returns
/// A `Mat4` representing the projection transformation matrix /// A `Mat4` representing the projection transformation matrix
pub fn projection_matrix(&self, fov: f32, aspect: f32, near: f32, far: f32) -> Mat4 { pub fn projection_matrix(&self, fov: f32, aspect: f32, near: f32, far: f32) -> Mat4 {
Mat4::perspective_rh_gl(fov, aspect, near, far) glam::camera::rh::proj::opengl::perspective(fov, aspect, near, far)
} }
} }
+2
View File
@@ -10,11 +10,13 @@
//! - `mesh::new()` uploads Vertex arrays from CPU memory into GPU vertex buffers via DeviceExt::create_buffer_init(). //! - `mesh::new()` uploads Vertex arrays from CPU memory into GPU vertex buffers via DeviceExt::create_buffer_init().
//! - `material::new()` requests RenderPipelines from PipelineCache during scene initialization. //! - `material::new()` requests RenderPipelines from PipelineCache during scene initialization.
pub mod camera;
pub mod material; pub mod material;
pub mod mesh; pub mod mesh;
pub mod vertex; pub mod vertex;
// Re-exports // Re-exports
pub use camera::Camera;
pub use material::Material; pub use material::Material;
pub use mesh::Mesh; pub use mesh::Mesh;
pub use vertex::Vertex; pub use vertex::Vertex;
+64
View File
@@ -0,0 +1,64 @@
//! # Entity Module
//!
//! Defines `Entity`, the renderable association between a Mesh and a Material together with its
//! own world-space `Transform`. Each entry of `Scene::entities` is an `Entity`: it references the
//! resource by identifier while carrying the per-entity placement data.
//!
//! ## Interaction with Other Modules
//! - `scene::Scene` stores entities in a `HashMap<String, Entity>` keyed by label.
//! - `math::Transform` provides the placement (translation / rotation / scale) converted to a
//! matrix during rendering.
//! - `resources::{Mesh, Material}` are the referenced render resources, resolved by `Scene`.
use crate::math::Transform;
/// A renderable entity: a mesh + material pair with its own world-space transform.
///
/// Entities are created through [`crate::scene::Scene::add_entity`] (identity transform) or
/// [`crate::scene::Scene::add_entity_with_transform`]. Fields are exposed via accessors.
#[derive(Debug, Clone)]
pub struct Entity {
/// Identifier of the referenced Mesh resource.
mesh_id: String,
/// Identifier of the referenced Material resource.
material_id: String,
/// World-space placement of this entity.
transform: Transform,
}
impl Entity {
/// Creates a new entity associating a mesh and a material under the given transform.
/// Called internally by `Scene::add_entity*` after resource existence is validated.
pub fn new(
mesh_id: impl Into<String>,
material_id: impl Into<String>,
transform: Transform,
) -> Self {
Self {
mesh_id: mesh_id.into(),
material_id: material_id.into(),
transform,
}
}
/// Returns the identifier of the referenced Mesh resource.
pub fn mesh_id(&self) -> &str {
&self.mesh_id
}
/// Returns the identifier of the referenced Material resource.
pub fn material_id(&self) -> &str {
&self.material_id
}
/// Returns a reference to this entity's world-space transform.
pub fn transform(&self) -> &Transform {
&self.transform
}
/// Sets this entity's world-space transform.
/// Called by `Scene::set_entity_transform` during dynamic updates.
pub fn set_transform(&mut self, transform: Transform) {
self.transform = transform;
}
}
+2
View File
@@ -16,7 +16,9 @@
//! all resources are declared before the render loop begins, while keeping the freedom to build the engine //! all resources are declared before the render loop begins, while keeping the freedom to build the engine
//! "brick by brick" through direct Context/PipelineCache/Renderer manipulation. //! "brick by brick" through direct Context/PipelineCache/Renderer manipulation.
pub mod entity;
pub mod scene; pub mod scene;
// Re-export // Re-export
pub use entity::Entity;
pub use scene::Scene; pub use scene::Scene;
+51 -12
View File
@@ -10,20 +10,22 @@
//! and avoiding borrow checker issues during dynamic updates. //! and avoiding borrow checker issues during dynamic updates.
//! - **Ergonomie**: Users interact only with entity-level operations (add/remove/get) rather than wgpu buffers/pipelines directly. //! - **Ergonomie**: Users interact only with entity-level operations (add/remove/get) rather than wgpu buffers/pipelines directly.
use crate::math::Transform;
use crate::resources::{Material, Mesh}; use crate::resources::{Material, Mesh};
use crate::scene::Entity;
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::Arc; use std::sync::Arc;
/// Resource depot and entity graph. Stores Meshes and Materials keyed by identifier strings, /// Resource depot and entity graph. Stores Meshes and Materials keyed by identifier strings,
/// and maps entity labels to their associated mesh+material pairs for rendering iteration. /// and maps entity labels to their associated `Entity` (mesh + material + transform) for rendering iteration.
/// Created once during application setup; entities are added before the render loop starts. /// Created once during application setup; entities are added before the render loop starts.
pub struct Scene { pub struct Scene {
/// Map of mesh identifiers to owned `Arc<Mesh>` instances. Populated via `add_mesh()`. /// Map of mesh identifiers to owned `Arc<Mesh>` instances. Populated via `add_mesh()`.
meshes: HashMap<String, Arc<Mesh>>, meshes: HashMap<String, Arc<Mesh>>,
/// Map of material identifiers to owned `Arc<Material>` instances. Populated via `add_material()`. /// Map of material identifiers to owned `Arc<Material>` instances. Populated via `add_material()`.
materials: HashMap<String, Arc<Material>>, materials: HashMap<String, Arc<Material>>,
/// Map of entity labels to (mesh_id, material_id) associations. Populated via `add_entity()`. /// Map of entity labels to `Entity` associations. Populated via `add_entity()` / `add_entity_with_transform()`.
entities: HashMap<String, (String, String)>, entities: HashMap<String, Entity>,
} }
impl Scene { impl Scene {
@@ -59,17 +61,33 @@ impl Scene {
Ok(id.to_string()) Ok(id.to_string())
} }
/// Associates an entity label with a mesh and material pair for rendering iteration. /// Associates an entity label with a mesh and material pair for rendering iteration, using an identity transform.
/// Inputs: label (entity identifier string), mesh_id (key into meshes map), material_id (key into materials map). /// Inputs: label (entity identifier string), mesh_id (key into meshes map), material_id (key into materials map).
/// Returns Ok(label) on success or Err(String) if either referenced resource does not exist. /// Returns Ok(label) on success or Err(String) if either referenced resource does not exist.
/// Called during scene initialization to build the renderable entity graph. /// Called during scene initialization to build the renderable entity graph.
/// Internal steps: 1) validate mesh_id exists → 2) validate material_id exists → /// Internal steps: 1) validate mesh_id exists → 2) validate material_id exists →
/// 3) insert association into entities HashMap. /// 3) insert an `Entity` with identity transform into the entities HashMap.
pub fn add_entity( pub fn add_entity(
&mut self, &mut self,
label: &str, label: &str,
mesh_id: &str, mesh_id: &str,
material_id: &str, material_id: &str,
) -> Result<String, String> {
self.add_entity_with_transform(label, mesh_id, material_id, Transform::identity())
}
/// Associates an entity label with a mesh and material pair together with an explicit world-space transform.
/// Inputs: label (entity identifier string), mesh_id (key into meshes map), material_id (key into materials map),
/// transform (world-space placement). Returns Ok(label) on success or Err(String) if either referenced resource does not exist.
/// Called during scene initialization to build the renderable entity graph.
/// Internal steps: 1) validate mesh_id exists → 2) validate material_id exists →
/// 3) insert the `Entity` into the entities HashMap.
pub fn add_entity_with_transform(
&mut self,
label: &str,
mesh_id: &str,
material_id: &str,
transform: Transform,
) -> Result<String, String> { ) -> Result<String, String> {
if !self.meshes.contains_key(mesh_id) { if !self.meshes.contains_key(mesh_id) {
return Err(format!("Mesh '{}' does not exist.", mesh_id)); return Err(format!("Mesh '{}' does not exist.", mesh_id));
@@ -79,7 +97,7 @@ impl Scene {
} }
self.entities.insert( self.entities.insert(
label.to_string(), label.to_string(),
(mesh_id.to_string(), material_id.to_string()), Entity::new(mesh_id, material_id, transform),
); );
Ok(label.to_string()) Ok(label.to_string())
} }
@@ -96,16 +114,37 @@ impl Scene {
self.materials.get(id) self.materials.get(id)
} }
/// Iterates all entity associations, yielding (label, mesh_ref, material_ref) triples. /// Iterates all entity associations, yielding (label, mesh_ref, material_ref, transform_ref) tuples.
/// Called by the orchestrator during each render pass to draw every entity in order. /// Called by the orchestrator during each render pass to draw every entity in order.
pub fn iter_entities(&self) -> impl Iterator<Item = (&str, &Arc<Mesh>, &Arc<Material>)> + '_ { pub fn iter_entities(
self.entities.iter().map(|(label, (mesh_id, mat_id))| { &self,
let mesh = self.meshes.get(mesh_id).unwrap(); // safe: add_entity validates existence ) -> impl Iterator<Item = (&str, &Arc<Mesh>, &Arc<Material>, &Transform)> + '_ {
let mat = self.materials.get(mat_id).unwrap(); // same invariant self.entities.iter().map(|(label, entity)| {
(label.as_str(), mesh, mat) let mesh = self.meshes.get(entity.mesh_id()).unwrap(); // safe: add_entity validates existence
let mat = self.materials.get(entity.material_id()).unwrap(); // same invariant
(label.as_str(), mesh, mat, entity.transform())
}) })
} }
/// Returns a reference to the transform of the entity with the given label, if it exists.
/// Called by the user to read an entity's current placement during updates.
pub fn entity_transform(&self, label: &str) -> Option<&Transform> {
self.entities.get(label).map(|e| e.transform())
}
/// Overwrites the transform of the entity with the given label.
/// Returns true if the entity existed and was updated, false otherwise.
/// Called by the user to move/rotate/scale an entity during `AppHandler::update`.
pub fn set_entity_transform(&mut self, label: &str, transform: Transform) -> bool {
match self.entities.get_mut(label) {
Some(entity) => {
entity.set_transform(transform);
true
}
None => false,
}
}
/// Removes an entity from the graph without freeing its underlying resources. /// Removes an entity from the graph without freeing its underlying resources.
/// The referenced Mesh and Material remain registered; only the association is dropped. /// The referenced Mesh and Material remain registered; only the association is dropped.
/// Called during dynamic updates when an entity should be hidden or removed temporarily. /// Called during dynamic updates when an entity should be hidden or removed temporarily.