diff --git a/.vscode/extensions.json b/.vscode/extensions.json
new file mode 100644
index 0000000..a6bd770
--- /dev/null
+++ b/.vscode/extensions.json
@@ -0,0 +1,7 @@
+{
+    "recommendations": [
+        "pinage404.rust-extension-pack",
+        "vadimcn.vscode-lldb",
+        "jnoortheen.nix-ide"
+    ]
+}
\ No newline at end of file
diff --git a/.vscode/launch.json b/.vscode/launch.json
new file mode 100644
index 0000000..c0caf03
--- /dev/null
+++ b/.vscode/launch.json
@@ -0,0 +1,45 @@
+{
+    // Use IntelliSense to learn about possible attributes.
+    // Hover to view descriptions of existing attributes.
+    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
+    "version": "0.2.0",
+    "configurations": [
+        {
+            "type": "lldb",
+            "request": "launch",
+            "name": "Debug executable 'rust_vulkan_test'",
+            "cargo": {
+                "args": [
+                    "build",
+                    "--bin=rust_vulkan_test",
+                    "--package=rust_vulkan_test"
+                ],
+                "filter": {
+                    "name": "rust_vulkan_test",
+                    "kind": "bin"
+                }
+            },
+            "args": [],
+            "cwd": "${workspaceFolder}"
+        },
+        {
+            "type": "lldb",
+            "request": "launch",
+            "name": "Debug unit tests in executable 'rust_vulkan_test'",
+            "cargo": {
+                "args": [
+                    "test",
+                    "--no-run",
+                    "--bin=rust_vulkan_test",
+                    "--package=rust_vulkan_test"
+                ],
+                "filter": {
+                    "name": "rust_vulkan_test",
+                    "kind": "bin"
+                }
+            },
+            "args": [],
+            "cwd": "${workspaceFolder}"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000..875f10e
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,11 @@
+{
+    "editor.formatOnSave": true,
+    "editor.codeActionsOnSave": {
+        "source.organizeImports": "always",
+    },
+    "[rust]": {
+        "editor.defaultFormatter": "rust-lang.rust-analyzer",
+    },
+    "files.insertFinalNewline": true,
+    "files.trimTrailingWhitespace": true
+}
diff --git a/Cargo.lock b/Cargo.lock
index 85ed865..ffb6291 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1,6 +1,6 @@
 # This file is automatically @generated by Cargo.
 # It is not intended for manual editing.
-version = 3
+version = 4
 
 [[package]]
 name = "ab_glyph"
@@ -19,13 +19,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "c71b1793ee61086797f5c80b6efa2b8ffa6d5dd703f118545808a7f2e27f7046"
 
 [[package]]
-name = "ahash"
-version = "0.8.11"
+name = "adler2"
+version = "2.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011"
+checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
+
+[[package]]
+name = "ahash"
+version = "0.8.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75"
 dependencies = [
  "cfg-if",
- "getrandom",
+ "getrandom 0.3.3",
  "once_cell",
  "version_check",
  "zerocopy",
@@ -40,6 +46,12 @@ dependencies = [
  "memchr",
 ]
 
+[[package]]
+name = "aligned-vec"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4aa90d7ce82d4be67b64039a3d588d38dbcc6736577de4a847025ce5b0c468d1"
+
 [[package]]
 name = "android-activity"
 version = "0.6.0"
@@ -47,7 +59,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ef6978589202a00cd7e118380c448a08b6ed394c3a8df3a430d0898e3a42d046"
 dependencies = [
  "android-properties",
- "bitflags 2.6.0",
+ "bitflags 2.9.1",
  "cc",
  "cesu8",
  "jni",
@@ -58,7 +70,7 @@ dependencies = [
  "ndk-context",
  "ndk-sys",
  "num_enum",
- "thiserror",
+ "thiserror 1.0.69",
 ]
 
 [[package]]
@@ -67,60 +79,48 @@ version = "0.2.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04"
 
-[[package]]
-name = "anstream"
-version = "0.6.18"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b"
-dependencies = [
- "anstyle",
- "anstyle-parse",
- "anstyle-query",
- "anstyle-wincon",
- "colorchoice",
- "is_terminal_polyfill",
- "utf8parse",
-]
-
-[[package]]
-name = "anstyle"
-version = "1.0.10"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9"
-
-[[package]]
-name = "anstyle-parse"
-version = "0.2.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9"
-dependencies = [
- "utf8parse",
-]
-
-[[package]]
-name = "anstyle-query"
-version = "1.1.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c"
-dependencies = [
- "windows-sys 0.59.0",
-]
-
-[[package]]
-name = "anstyle-wincon"
-version = "3.0.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125"
-dependencies = [
- "anstyle",
- "windows-sys 0.59.0",
-]
-
 [[package]]
 name = "anyhow"
-version = "1.0.93"
+version = "1.0.98"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775"
+checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487"
+
+[[package]]
+name = "arbitrary"
+version = "1.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223"
+
+[[package]]
+name = "arboard"
+version = "3.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c1df21f715862ede32a0c525ce2ca4d52626bb0007f8c18b87a384503ac33e70"
+dependencies = [
+ "clipboard-win",
+ "image",
+ "log",
+ "objc2 0.6.1",
+ "objc2-app-kit 0.3.1",
+ "objc2-core-foundation",
+ "objc2-core-graphics",
+ "objc2-foundation 0.3.1",
+ "parking_lot",
+ "percent-encoding",
+ "windows-sys 0.59.0",
+ "x11rb",
+]
+
+[[package]]
+name = "arg_enum_proc_macro"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
 
 [[package]]
 name = "arrayref"
@@ -145,16 +145,8 @@ name = "ash"
 version = "0.38.0+1.3.281"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0bb44936d800fea8f016d7f2311c6a4f97aebd5dc86f09906139ec848cf3a46f"
-
-[[package]]
-name = "ash-window"
-version = "0.13.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "52bca67b61cb81e5553babde81b8211f713cb6db79766f80168f3e5f40ea6c82"
 dependencies = [
- "ash",
- "raw-window-handle",
- "raw-window-metal",
+ "libloading",
 ]
 
 [[package]]
@@ -169,6 +161,35 @@ version = "1.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
 
+[[package]]
+name = "av1-grain"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4f3efb2ca85bc610acfa917b5aaa36f3fcbebed5b3182d7f877b02531c4b80c8"
+dependencies = [
+ "anyhow",
+ "arrayvec",
+ "log",
+ "nom",
+ "num-rational",
+ "v_frame",
+]
+
+[[package]]
+name = "avif-serialize"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "98922d6a4cfbcb08820c69d8eeccc05bb1f29bfa06b4f5b1dbfe9a868bd7608e"
+dependencies = [
+ "arrayvec",
+]
+
+[[package]]
+name = "bit_field"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61"
+
 [[package]]
 name = "bitflags"
 version = "1.3.2"
@@ -177,15 +198,15 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
 
 [[package]]
 name = "bitflags"
-version = "2.6.0"
+version = "2.9.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
+checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967"
 
 [[package]]
-name = "block"
-version = "0.1.6"
+name = "bitstream-io"
+version = "2.6.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a"
+checksum = "6099cdc01846bc367c4e7dd630dc5966dccf36b652fae7a74e17b640411a91b2"
 
 [[package]]
 name = "block2"
@@ -193,26 +214,52 @@ version = "0.5.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f"
 dependencies = [
- "objc2",
+ "objc2 0.5.2",
 ]
 
 [[package]]
-name = "bumpalo"
-version = "3.16.0"
+name = "built"
+version = "0.7.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
+checksum = "56ed6191a7e78c36abdb16ab65341eefd73d64d303fffccdbb00d51e4205967b"
+
+[[package]]
+name = "bumpalo"
+version = "3.17.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf"
 
 [[package]]
 name = "bytemuck"
-version = "1.19.0"
+version = "1.23.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8334215b81e418a0a7bdb8ef0849474f40bb10c8b71f1c4ed315cff49f32494d"
+checksum = "9134a6ef01ce4b366b50689c94f82c14bc72bc5d0386829828a2e2752ef7958c"
+dependencies = [
+ "bytemuck_derive",
+]
+
+[[package]]
+name = "bytemuck_derive"
+version = "1.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7ecc273b49b3205b83d648f0690daa588925572cc5063745bfe547fe7ec8e1a1"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "byteorder-lite"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
 
 [[package]]
 name = "bytes"
-version = "1.8.0"
+version = "1.10.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da"
+checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
 
 [[package]]
 name = "calloop"
@@ -220,12 +267,12 @@ version = "0.13.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec"
 dependencies = [
- "bitflags 2.6.0",
+ "bitflags 2.9.1",
  "log",
  "polling",
  "rustix",
  "slab",
- "thiserror",
+ "thiserror 1.0.69",
 ]
 
 [[package]]
@@ -242,9 +289,9 @@ dependencies = [
 
 [[package]]
 name = "cc"
-version = "1.2.1"
+version = "1.2.23"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fd9de9f2205d5ef3fd67e685b0df337994ddd4495e2a28d185500d0e1edfea47"
+checksum = "5f4ac86a9e5bc1e2b3449ab9d7d3a6a405e3d1bb28d7b9be8614f55846ae3766"
 dependencies = [
  "jobserver",
  "libc",
@@ -257,6 +304,16 @@ version = "1.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c"
 
+[[package]]
+name = "cfg-expr"
+version = "0.15.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02"
+dependencies = [
+ "smallvec",
+ "target-lexicon",
+]
+
 [[package]]
 name = "cfg-if"
 version = "1.0.0"
@@ -270,40 +327,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
 
 [[package]]
-name = "cocoa"
-version = "0.25.0"
+name = "clipboard-win"
+version = "5.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f6140449f97a6e97f9511815c5632d84c8aacf8ac271ad77c559218161a1373c"
+checksum = "15efe7a882b08f34e38556b14f2fb3daa98769d06c7f0c1b076dfd0d983bc892"
 dependencies = [
- "bitflags 1.3.2",
- "block",
- "cocoa-foundation",
- "core-foundation",
- "core-graphics",
- "foreign-types",
- "libc",
- "objc",
+ "error-code",
 ]
 
 [[package]]
-name = "cocoa-foundation"
-version = "0.1.2"
+name = "cmake"
+version = "0.1.54"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8c6234cbb2e4c785b456c0644748b1ac416dd045799740356f8363dfe00c93f7"
+checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0"
 dependencies = [
- "bitflags 1.3.2",
- "block",
- "core-foundation",
- "core-graphics-types",
- "libc",
- "objc",
+ "cc",
 ]
 
 [[package]]
-name = "colorchoice"
-version = "1.0.3"
+name = "color_quant"
+version = "1.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
+checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
 
 [[package]]
 name = "combine"
@@ -334,6 +379,16 @@ dependencies = [
  "libc",
 ]
 
+[[package]]
+name = "core-foundation"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b55271e5c8c478ad3f38ad24ef34923091e0548492a266d19b3c0b4d82574c63"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
 [[package]]
 name = "core-foundation-sys"
 version = "0.8.7"
@@ -347,7 +402,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081"
 dependencies = [
  "bitflags 1.3.2",
- "core-foundation",
+ "core-foundation 0.9.4",
  "core-graphics-types",
  "foreign-types",
  "libc",
@@ -360,15 +415,58 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf"
 dependencies = [
  "bitflags 1.3.2",
- "core-foundation",
+ "core-foundation 0.9.4",
  "libc",
 ]
 
 [[package]]
-name = "crossbeam-utils"
-version = "0.8.20"
+name = "crc32fast"
+version = "1.4.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80"
+checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "crossbeam-deque"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
+dependencies = [
+ "crossbeam-epoch",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-epoch"
+version = "0.9.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
+dependencies = [
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-queue"
+version = "0.3.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115"
+dependencies = [
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-utils"
+version = "0.8.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
+
+[[package]]
+name = "crunchy"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929"
 
 [[package]]
 name = "cursor-icon"
@@ -382,6 +480,27 @@ version = "0.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b"
 
+[[package]]
+name = "dispatch2"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec"
+dependencies = [
+ "bitflags 2.9.1",
+ "objc2 0.6.1",
+]
+
+[[package]]
+name = "displaydoc"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
 [[package]]
 name = "dlib"
 version = "0.5.2"
@@ -399,49 +518,170 @@ checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2"
 
 [[package]]
 name = "dpi"
-version = "0.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f25c0e292a7ca6d6498557ff1df68f32c99850012b6ea401cf8daf771f22ff53"
-
-[[package]]
-name = "env_filter"
 version = "0.1.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4f2c92ceda6ceec50f43169f9ee8424fe2db276791afde7b2cd8bc084cb376ab"
+checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76"
+
+[[package]]
+name = "ecolor"
+version = "0.31.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bc4feb366740ded31a004a0e4452fbf84e80ef432ecf8314c485210229672fd1"
 dependencies = [
- "log",
- "regex",
+ "bytemuck",
+ "emath",
 ]
 
 [[package]]
-name = "env_logger"
-version = "0.11.5"
+name = "egui"
+version = "0.31.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e13fa619b91fb2381732789fc5de83b45675e882f66623b7d8cb4f643017018d"
+checksum = "25dd34cec49ab55d85ebf70139cb1ccd29c977ef6b6ba4fe85489d6877ee9ef3"
 dependencies = [
- "anstream",
- "anstyle",
- "env_filter",
- "humantime",
+ "ahash",
+ "bitflags 2.9.1",
+ "emath",
+ "epaint",
  "log",
+ "nohash-hasher",
+ "profiling",
 ]
 
+[[package]]
+name = "egui-winit"
+version = "0.31.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7d9dfbb78fe4eb9c3a39ad528b90ee5915c252e77bbab9d4ebc576541ab67e13"
+dependencies = [
+ "ahash",
+ "arboard",
+ "bytemuck",
+ "egui",
+ "log",
+ "profiling",
+ "raw-window-handle",
+ "smithay-clipboard",
+ "web-time",
+ "webbrowser",
+ "winit",
+]
+
+[[package]]
+name = "egui_winit_vulkano"
+version = "0.28.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6abd5e69939cd416853fc3ec69c0e08721b175d03b508d5574849885c7e85a6"
+dependencies = [
+ "ahash",
+ "egui",
+ "egui-winit",
+ "image",
+ "vulkano",
+ "vulkano-shaders",
+ "winit",
+]
+
+[[package]]
+name = "either"
+version = "1.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
+
+[[package]]
+name = "emath"
+version = "0.31.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e4cadcff7a5353ba72b7fea76bf2122b5ebdbc68e8155aa56dfdea90083fe1b"
+dependencies = [
+ "bytemuck",
+]
+
+[[package]]
+name = "epaint"
+version = "0.31.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41fcc0f5a7c613afd2dee5e4b30c3e6acafb8ad6f0edb06068811f708a67c562"
+dependencies = [
+ "ab_glyph",
+ "ahash",
+ "bytemuck",
+ "ecolor",
+ "emath",
+ "epaint_default_fonts",
+ "log",
+ "nohash-hasher",
+ "parking_lot",
+ "profiling",
+]
+
+[[package]]
+name = "epaint_default_fonts"
+version = "0.31.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc7e7a64c02cf7a5b51e745a9e45f60660a286f151c238b9d397b3e923f5082f"
+
 [[package]]
 name = "equivalent"
-version = "1.0.1"
+version = "1.0.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
+checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
 
 [[package]]
 name = "errno"
-version = "0.3.9"
+version = "0.3.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba"
+checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18"
 dependencies = [
  "libc",
- "windows-sys 0.52.0",
+ "windows-sys 0.59.0",
 ]
 
+[[package]]
+name = "error-code"
+version = "3.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59"
+
+[[package]]
+name = "exr"
+version = "1.73.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f83197f59927b46c04a183a619b7c29df34e63e63c7869320862268c0ef687e0"
+dependencies = [
+ "bit_field",
+ "half",
+ "lebe",
+ "miniz_oxide",
+ "rayon-core",
+ "smallvec",
+ "zune-inflate",
+]
+
+[[package]]
+name = "fdeflate"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c"
+dependencies = [
+ "simd-adler32",
+]
+
+[[package]]
+name = "flate2"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7ced92e76e966ca2fd84c8f7aa01a4aea65b0eb6648d72f7c8f3e2764a67fece"
+dependencies = [
+ "crc32fast",
+ "miniz_oxide",
+]
+
+[[package]]
+name = "foldhash"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
+
 [[package]]
 name = "foreign-types"
 version = "0.5.0"
@@ -469,6 +709,29 @@ version = "0.3.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b"
 
+[[package]]
+name = "form_urlencoded"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456"
+dependencies = [
+ "percent-encoding",
+]
+
+[[package]]
+name = "generator"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d18470a76cb7f8ff746cf1f7470914f900252ec36bbc40b569d74b1258446827"
+dependencies = [
+ "cc",
+ "cfg-if",
+ "libc",
+ "log",
+ "rustversion",
+ "windows",
+]
+
 [[package]]
 name = "gethostname"
 version = "0.4.3"
@@ -481,26 +744,71 @@ dependencies = [
 
 [[package]]
 name = "getrandom"
-version = "0.2.15"
+version = "0.2.16"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
+checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
 dependencies = [
  "cfg-if",
  "libc",
- "wasi",
+ "wasi 0.11.0+wasi-snapshot-preview1",
 ]
 
 [[package]]
-name = "glob"
-version = "0.3.1"
+name = "getrandom"
+version = "0.3.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
+checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "r-efi",
+ "wasi 0.14.2+wasi-0.2.4",
+]
+
+[[package]]
+name = "gif"
+version = "0.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3fb2d69b19215e18bb912fa30f7ce15846e301408695e44e0ef719f1da9e19f2"
+dependencies = [
+ "color_quant",
+ "weezl",
+]
+
+[[package]]
+name = "glam"
+version = "0.30.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6b46b9ca4690308844c644e7c634d68792467260e051c8543e0c7871662b3ba7"
+
+[[package]]
+name = "half"
+version = "2.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9"
+dependencies = [
+ "bytemuck",
+ "cfg-if",
+ "crunchy",
+]
 
 [[package]]
 name = "hashbrown"
-version = "0.15.1"
+version = "0.15.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3"
+checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3"
+
+[[package]]
+name = "heck"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
+
+[[package]]
+name = "heck"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
 
 [[package]]
 name = "hermit-abi"
@@ -509,26 +817,195 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc"
 
 [[package]]
-name = "humantime"
-version = "2.1.0"
+name = "home"
+version = "0.5.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
+checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf"
+dependencies = [
+ "windows-sys 0.59.0",
+]
+
+[[package]]
+name = "icu_collections"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47"
+dependencies = [
+ "displaydoc",
+ "potential_utf",
+ "yoke",
+ "zerofrom",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_locale_core"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a"
+dependencies = [
+ "displaydoc",
+ "litemap",
+ "tinystr",
+ "writeable",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_normalizer"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979"
+dependencies = [
+ "displaydoc",
+ "icu_collections",
+ "icu_normalizer_data",
+ "icu_properties",
+ "icu_provider",
+ "smallvec",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_normalizer_data"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3"
+
+[[package]]
+name = "icu_properties"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b"
+dependencies = [
+ "displaydoc",
+ "icu_collections",
+ "icu_locale_core",
+ "icu_properties_data",
+ "icu_provider",
+ "potential_utf",
+ "zerotrie",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_properties_data"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632"
+
+[[package]]
+name = "icu_provider"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af"
+dependencies = [
+ "displaydoc",
+ "icu_locale_core",
+ "stable_deref_trait",
+ "tinystr",
+ "writeable",
+ "yoke",
+ "zerofrom",
+ "zerotrie",
+ "zerovec",
+]
+
+[[package]]
+name = "idna"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e"
+dependencies = [
+ "idna_adapter",
+ "smallvec",
+ "utf8_iter",
+]
+
+[[package]]
+name = "idna_adapter"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344"
+dependencies = [
+ "icu_normalizer",
+ "icu_properties",
+]
+
+[[package]]
+name = "image"
+version = "0.25.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "db35664ce6b9810857a38a906215e75a9c879f0696556a39f59c62829710251a"
+dependencies = [
+ "bytemuck",
+ "byteorder-lite",
+ "color_quant",
+ "exr",
+ "gif",
+ "image-webp",
+ "num-traits",
+ "png",
+ "qoi",
+ "ravif",
+ "rayon",
+ "rgb",
+ "tiff",
+ "zune-core",
+ "zune-jpeg",
+]
+
+[[package]]
+name = "image-webp"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b77d01e822461baa8409e156015a1d91735549f0f2c17691bd2d996bef238f7f"
+dependencies = [
+ "byteorder-lite",
+ "quick-error",
+]
+
+[[package]]
+name = "imgref"
+version = "1.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d0263a3d970d5c054ed9312c0057b4f3bde9c0b33836d3637361d4a9e6e7a408"
 
 [[package]]
 name = "indexmap"
-version = "2.6.0"
+version = "2.9.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da"
+checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e"
 dependencies = [
  "equivalent",
  "hashbrown",
 ]
 
 [[package]]
-name = "is_terminal_polyfill"
-version = "1.70.1"
+name = "interpolate_name"
+version = "0.2.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
+checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "itertools"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569"
+dependencies = [
+ "either",
+]
+
+[[package]]
+name = "itoa"
+version = "1.0.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
 
 [[package]]
 name = "jni"
@@ -541,7 +1018,7 @@ dependencies = [
  "combine",
  "jni-sys",
  "log",
- "thiserror",
+ "thiserror 1.0.69",
  "walkdir",
  "windows-sys 0.45.0",
 ]
@@ -554,33 +1031,63 @@ checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130"
 
 [[package]]
 name = "jobserver"
-version = "0.1.32"
+version = "0.1.33"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0"
+checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a"
 dependencies = [
+ "getrandom 0.3.3",
  "libc",
 ]
 
 [[package]]
-name = "js-sys"
-version = "0.3.72"
+name = "jpeg-decoder"
+version = "0.3.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9"
+checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0"
+
+[[package]]
+name = "js-sys"
+version = "0.3.77"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f"
 dependencies = [
+ "once_cell",
  "wasm-bindgen",
 ]
 
 [[package]]
-name = "libc"
-version = "0.2.164"
+name = "lazy_static"
+version = "1.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "433bfe06b8c75da9b2e3fbea6e5329ff87748f0b144ef75306e674c3f6f7c13f"
+checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
+
+[[package]]
+name = "lebe"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8"
+
+[[package]]
+name = "libc"
+version = "0.2.172"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa"
+
+[[package]]
+name = "libfuzzer-sys"
+version = "0.4.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf78f52d400cf2d84a3a973a78a592b4adc535739e0a5597a0da6f0c357adc75"
+dependencies = [
+ "arbitrary",
+ "cc",
+]
 
 [[package]]
 name = "libloading"
-version = "0.8.5"
+version = "0.8.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4"
+checksum = "6a793df0d7afeac54f95b471d3af7f0d4fb975699f972341a4b76988d49cdf0c"
 dependencies = [
  "cfg-if",
  "windows-targets 0.52.6",
@@ -592,30 +1099,78 @@ version = "0.1.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d"
 dependencies = [
- "bitflags 2.6.0",
+ "bitflags 2.9.1",
  "libc",
- "redox_syscall 0.5.7",
+ "redox_syscall 0.5.12",
 ]
 
 [[package]]
 name = "linux-raw-sys"
-version = "0.4.14"
+version = "0.4.15"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89"
+checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab"
+
+[[package]]
+name = "litemap"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956"
+
+[[package]]
+name = "lock_api"
+version = "0.4.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17"
+dependencies = [
+ "autocfg",
+ "scopeguard",
+]
 
 [[package]]
 name = "log"
-version = "0.4.22"
+version = "0.4.27"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
+checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
 
 [[package]]
-name = "malloc_buf"
-version = "0.0.6"
+name = "loom"
+version = "0.7.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb"
+checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca"
 dependencies = [
- "libc",
+ "cfg-if",
+ "generator",
+ "scoped-tls",
+ "tracing",
+ "tracing-subscriber",
+]
+
+[[package]]
+name = "loop9"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fae87c125b03c1d2c0150c90365d7d6bcc53fb73a9acaef207d2d065860f062"
+dependencies = [
+ "imgref",
+]
+
+[[package]]
+name = "matchers"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558"
+dependencies = [
+ "regex-automata 0.1.10",
+]
+
+[[package]]
+name = "maybe-rayon"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519"
+dependencies = [
+ "cfg-if",
+ "rayon",
 ]
 
 [[package]]
@@ -633,19 +1188,35 @@ dependencies = [
  "libc",
 ]
 
+[[package]]
+name = "minimal-lexical"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
+
+[[package]]
+name = "miniz_oxide"
+version = "0.8.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a"
+dependencies = [
+ "adler2",
+ "simd-adler32",
+]
+
 [[package]]
 name = "ndk"
 version = "0.9.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4"
 dependencies = [
- "bitflags 2.6.0",
+ "bitflags 2.9.1",
  "jni-sys",
  "log",
  "ndk-sys",
  "num_enum",
  "raw-window-handle",
- "thiserror",
+ "thiserror 1.0.69",
 ]
 
 [[package]]
@@ -663,6 +1234,94 @@ dependencies = [
  "jni-sys",
 ]
 
+[[package]]
+name = "new_debug_unreachable"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086"
+
+[[package]]
+name = "nohash-hasher"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451"
+
+[[package]]
+name = "nom"
+version = "7.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
+dependencies = [
+ "memchr",
+ "minimal-lexical",
+]
+
+[[package]]
+name = "noop_proc_macro"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8"
+
+[[package]]
+name = "nu-ansi-term"
+version = "0.46.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84"
+dependencies = [
+ "overload",
+ "winapi",
+]
+
+[[package]]
+name = "num-bigint"
+version = "0.4.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9"
+dependencies = [
+ "num-integer",
+ "num-traits",
+]
+
+[[package]]
+name = "num-derive"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "num-integer"
+version = "0.1.46"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "num-rational"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824"
+dependencies = [
+ "num-bigint",
+ "num-integer",
+ "num-traits",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
+dependencies = [
+ "autocfg",
+]
+
 [[package]]
 name = "num_enum"
 version = "0.7.3"
@@ -684,15 +1343,6 @@ dependencies = [
  "syn",
 ]
 
-[[package]]
-name = "objc"
-version = "0.2.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1"
-dependencies = [
- "malloc_buf",
-]
-
 [[package]]
 name = "objc-sys"
 version = "0.3.5"
@@ -709,20 +1359,41 @@ dependencies = [
  "objc2-encode",
 ]
 
+[[package]]
+name = "objc2"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "88c6597e14493ab2e44ce58f2fdecf095a51f12ca57bec060a11c57332520551"
+dependencies = [
+ "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.6.0",
+ "bitflags 2.9.1",
  "block2",
  "libc",
- "objc2",
+ "objc2 0.5.2",
  "objc2-core-data",
  "objc2-core-image",
- "objc2-foundation",
- "objc2-quartz-core",
+ "objc2-foundation 0.2.2",
+ "objc2-quartz-core 0.2.2",
+]
+
+[[package]]
+name = "objc2-app-kit"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6f29f568bec459b0ddff777cec4fe3fd8666d82d5a40ebd0ff7e66134f89bcc"
+dependencies = [
+ "bitflags 2.9.1",
+ "objc2 0.6.1",
+ "objc2-core-graphics",
+ "objc2-foundation 0.3.1",
 ]
 
 [[package]]
@@ -731,11 +1402,11 @@ version = "0.2.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009"
 dependencies = [
- "bitflags 2.6.0",
+ "bitflags 2.9.1",
  "block2",
- "objc2",
+ "objc2 0.5.2",
  "objc2-core-location",
- "objc2-foundation",
+ "objc2-foundation 0.2.2",
 ]
 
 [[package]]
@@ -745,8 +1416,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a5ff520e9c33812fd374d8deecef01d4a840e7b41862d849513de77e44aa4889"
 dependencies = [
  "block2",
- "objc2",
- "objc2-foundation",
+ "objc2 0.5.2",
+ "objc2-foundation 0.2.2",
 ]
 
 [[package]]
@@ -755,10 +1426,34 @@ version = "0.2.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef"
 dependencies = [
- "bitflags 2.6.0",
+ "bitflags 2.9.1",
  "block2",
- "objc2",
- "objc2-foundation",
+ "objc2 0.5.2",
+ "objc2-foundation 0.2.2",
+]
+
+[[package]]
+name = "objc2-core-foundation"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1c10c2894a6fed806ade6027bcd50662746363a9589d3ec9d9bef30a4e4bc166"
+dependencies = [
+ "bitflags 2.9.1",
+ "dispatch2",
+ "objc2 0.6.1",
+]
+
+[[package]]
+name = "objc2-core-graphics"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "989c6c68c13021b5c2d6b71456ebb0f9dc78d752e86a98da7c716f4f9470f5a4"
+dependencies = [
+ "bitflags 2.9.1",
+ "dispatch2",
+ "objc2 0.6.1",
+ "objc2-core-foundation",
+ "objc2-io-surface",
 ]
 
 [[package]]
@@ -768,9 +1463,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80"
 dependencies = [
  "block2",
- "objc2",
- "objc2-foundation",
- "objc2-metal",
+ "objc2 0.5.2",
+ "objc2-foundation 0.2.2",
+ "objc2-metal 0.2.2",
 ]
 
 [[package]]
@@ -780,16 +1475,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "000cfee34e683244f284252ee206a27953279d370e309649dc3ee317b37e5781"
 dependencies = [
  "block2",
- "objc2",
+ "objc2 0.5.2",
  "objc2-contacts",
- "objc2-foundation",
+ "objc2-foundation 0.2.2",
 ]
 
 [[package]]
 name = "objc2-encode"
-version = "4.0.3"
+version = "4.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7891e71393cd1f227313c9379a26a584ff3d7e6e7159e988851f0934c993f0f8"
+checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33"
 
 [[package]]
 name = "objc2-foundation"
@@ -797,11 +1492,33 @@ version = "0.2.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8"
 dependencies = [
- "bitflags 2.6.0",
+ "bitflags 2.9.1",
  "block2",
  "dispatch",
  "libc",
- "objc2",
+ "objc2 0.5.2",
+]
+
+[[package]]
+name = "objc2-foundation"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "900831247d2fe1a09a683278e5384cfb8c80c79fe6b166f9d14bfdde0ea1b03c"
+dependencies = [
+ "bitflags 2.9.1",
+ "objc2 0.6.1",
+ "objc2-core-foundation",
+]
+
+[[package]]
+name = "objc2-io-surface"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7282e9ac92529fa3457ce90ebb15f4ecbc383e8338060960760fa2cf75420c3c"
+dependencies = [
+ "bitflags 2.9.1",
+ "objc2 0.6.1",
+ "objc2-core-foundation",
 ]
 
 [[package]]
@@ -811,9 +1528,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398"
 dependencies = [
  "block2",
- "objc2",
- "objc2-app-kit",
- "objc2-foundation",
+ "objc2 0.5.2",
+ "objc2-app-kit 0.2.2",
+ "objc2-foundation 0.2.2",
 ]
 
 [[package]]
@@ -822,10 +1539,21 @@ version = "0.2.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6"
 dependencies = [
- "bitflags 2.6.0",
+ "bitflags 2.9.1",
  "block2",
- "objc2",
- "objc2-foundation",
+ "objc2 0.5.2",
+ "objc2-foundation 0.2.2",
+]
+
+[[package]]
+name = "objc2-metal"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f246c183239540aab1782457b35ab2040d4259175bd1d0c58e46ada7b47a874"
+dependencies = [
+ "bitflags 2.9.1",
+ "objc2 0.6.1",
+ "objc2-foundation 0.3.1",
 ]
 
 [[package]]
@@ -834,11 +1562,24 @@ version = "0.2.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a"
 dependencies = [
- "bitflags 2.6.0",
+ "bitflags 2.9.1",
  "block2",
- "objc2",
- "objc2-foundation",
- "objc2-metal",
+ "objc2 0.5.2",
+ "objc2-foundation 0.2.2",
+ "objc2-metal 0.2.2",
+]
+
+[[package]]
+name = "objc2-quartz-core"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90ffb6a0cd5f182dc964334388560b12a57f7b74b3e2dec5e2722aa2dfb2ccd5"
+dependencies = [
+ "bitflags 2.9.1",
+ "objc2 0.6.1",
+ "objc2-core-foundation",
+ "objc2-foundation 0.3.1",
+ "objc2-metal 0.3.1",
 ]
 
 [[package]]
@@ -847,8 +1588,8 @@ version = "0.2.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0a684efe3dec1b305badae1a28f6555f6ddd3bb2c2267896782858d5a78404dc"
 dependencies = [
- "objc2",
- "objc2-foundation",
+ "objc2 0.5.2",
+ "objc2-foundation 0.2.2",
 ]
 
 [[package]]
@@ -857,16 +1598,16 @@ version = "0.2.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f"
 dependencies = [
- "bitflags 2.6.0",
+ "bitflags 2.9.1",
  "block2",
- "objc2",
+ "objc2 0.5.2",
  "objc2-cloud-kit",
  "objc2-core-data",
  "objc2-core-image",
  "objc2-core-location",
- "objc2-foundation",
+ "objc2-foundation 0.2.2",
  "objc2-link-presentation",
- "objc2-quartz-core",
+ "objc2-quartz-core 0.2.2",
  "objc2-symbols",
  "objc2-uniform-type-identifiers",
  "objc2-user-notifications",
@@ -879,8 +1620,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "44fa5f9748dbfe1ca6c0b79ad20725a11eca7c2218bceb4b005cb1be26273bfe"
 dependencies = [
  "block2",
- "objc2",
- "objc2-foundation",
+ "objc2 0.5.2",
+ "objc2-foundation 0.2.2",
 ]
 
 [[package]]
@@ -889,18 +1630,18 @@ version = "0.2.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3"
 dependencies = [
- "bitflags 2.6.0",
+ "bitflags 2.9.1",
  "block2",
- "objc2",
+ "objc2 0.5.2",
  "objc2-core-location",
- "objc2-foundation",
+ "objc2-foundation 0.2.2",
 ]
 
 [[package]]
 name = "once_cell"
-version = "1.20.2"
+version = "1.21.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
+checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
 
 [[package]]
 name = "orbclient"
@@ -911,6 +1652,12 @@ dependencies = [
  "libredox",
 ]
 
+[[package]]
+name = "overload"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
+
 [[package]]
 name = "owned_ttf_parser"
 version = "0.25.0"
@@ -920,6 +1667,35 @@ dependencies = [
  "ttf-parser",
 ]
 
+[[package]]
+name = "parking_lot"
+version = "0.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27"
+dependencies = [
+ "lock_api",
+ "parking_lot_core",
+]
+
+[[package]]
+name = "parking_lot_core"
+version = "0.9.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "redox_syscall 0.5.12",
+ "smallvec",
+ "windows-targets 0.52.6",
+]
+
+[[package]]
+name = "paste"
+version = "1.0.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
+
 [[package]]
 name = "percent-encoding"
 version = "2.3.1"
@@ -928,18 +1704,18 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
 
 [[package]]
 name = "pin-project"
-version = "1.1.7"
+version = "1.1.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "be57f64e946e500c8ee36ef6331845d40a93055567ec57e8fae13efd33759b95"
+checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a"
 dependencies = [
  "pin-project-internal",
 ]
 
 [[package]]
 name = "pin-project-internal"
-version = "1.1.7"
+version = "1.1.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3c0f5fad0874fc7abcd4d750e76917eaebbecaa2c20bde22e1dbeeba8beb758c"
+checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -948,15 +1724,28 @@ dependencies = [
 
 [[package]]
 name = "pin-project-lite"
-version = "0.2.15"
+version = "0.2.16"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff"
+checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
 
 [[package]]
 name = "pkg-config"
-version = "0.3.31"
+version = "0.3.32"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2"
+checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
+
+[[package]]
+name = "png"
+version = "0.17.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526"
+dependencies = [
+ "bitflags 1.3.2",
+ "crc32fast",
+ "fdeflate",
+ "flate2",
+ "miniz_oxide",
+]
 
 [[package]]
 name = "polling"
@@ -974,41 +1763,208 @@ dependencies = [
 ]
 
 [[package]]
-name = "proc-macro-crate"
-version = "3.2.0"
+name = "potential_utf"
+version = "0.1.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b"
+checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585"
+dependencies = [
+ "zerovec",
+]
+
+[[package]]
+name = "ppv-lite86"
+version = "0.2.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
+dependencies = [
+ "zerocopy",
+]
+
+[[package]]
+name = "proc-macro-crate"
+version = "3.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35"
 dependencies = [
  "toml_edit",
 ]
 
 [[package]]
 name = "proc-macro2"
-version = "1.0.89"
+version = "1.0.95"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e"
+checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
 dependencies = [
  "unicode-ident",
 ]
 
 [[package]]
-name = "quick-xml"
-version = "0.36.2"
+name = "profiling"
+version = "1.0.16"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f7649a7b4df05aed9ea7ec6f628c67c9953a43869b8bc50929569b2999d443fe"
+checksum = "afbdc74edc00b6f6a218ca6a5364d6226a259d4b8ea1af4a0ea063f27e179f4d"
+dependencies = [
+ "profiling-procmacros",
+]
+
+[[package]]
+name = "profiling-procmacros"
+version = "1.0.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a65f2e60fbf1063868558d69c6beacf412dc755f9fc020f514b7955fc914fe30"
+dependencies = [
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "qoi"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001"
+dependencies = [
+ "bytemuck",
+]
+
+[[package]]
+name = "quick-error"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
+
+[[package]]
+name = "quick-xml"
+version = "0.37.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "331e97a1af0bf59823e6eadffe373d7b27f485be8748f71471c662c1f269b7fb"
 dependencies = [
  "memchr",
 ]
 
 [[package]]
 name = "quote"
-version = "1.0.37"
+version = "1.0.40"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
+checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
 dependencies = [
  "proc-macro2",
 ]
 
+[[package]]
+name = "r-efi"
+version = "5.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5"
+
+[[package]]
+name = "rand"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
+dependencies = [
+ "libc",
+ "rand_chacha 0.3.1",
+ "rand_core 0.6.4",
+]
+
+[[package]]
+name = "rand"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97"
+dependencies = [
+ "rand_chacha 0.9.0",
+ "rand_core 0.9.3",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
+dependencies = [
+ "ppv-lite86",
+ "rand_core 0.6.4",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
+dependencies = [
+ "ppv-lite86",
+ "rand_core 0.9.3",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
+dependencies = [
+ "getrandom 0.2.16",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
+dependencies = [
+ "getrandom 0.3.3",
+]
+
+[[package]]
+name = "rav1e"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cd87ce80a7665b1cce111f8a16c1f3929f6547ce91ade6addf4ec86a8dda5ce9"
+dependencies = [
+ "arbitrary",
+ "arg_enum_proc_macro",
+ "arrayvec",
+ "av1-grain",
+ "bitstream-io",
+ "built",
+ "cfg-if",
+ "interpolate_name",
+ "itertools",
+ "libc",
+ "libfuzzer-sys",
+ "log",
+ "maybe-rayon",
+ "new_debug_unreachable",
+ "noop_proc_macro",
+ "num-derive",
+ "num-traits",
+ "once_cell",
+ "paste",
+ "profiling",
+ "rand 0.8.5",
+ "rand_chacha 0.3.1",
+ "simd_helpers",
+ "system-deps",
+ "thiserror 1.0.69",
+ "v_frame",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "ravif"
+version = "0.11.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d6a5f31fcf7500f9401fea858ea4ab5525c99f2322cfcee732c0e6c74208c0c6"
+dependencies = [
+ "avif-serialize",
+ "imgref",
+ "loop9",
+ "quick-error",
+ "rav1e",
+ "rayon",
+ "rgb",
+]
+
 [[package]]
 name = "raw-window-handle"
 version = "0.6.2"
@@ -1017,14 +1973,34 @@ checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539"
 
 [[package]]
 name = "raw-window-metal"
-version = "0.4.0"
+version = "1.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "76e8caa82e31bb98fee12fa8f051c94a6aa36b07cddb03f0d4fc558988360ff1"
+checksum = "40d213455a5f1dc59214213c7330e074ddf8114c9a42411eb890c767357ce135"
 dependencies = [
- "cocoa",
- "core-graphics",
- "objc",
- "raw-window-handle",
+ "objc2 0.6.1",
+ "objc2-core-foundation",
+ "objc2-foundation 0.3.1",
+ "objc2-quartz-core 0.3.1",
+]
+
+[[package]]
+name = "rayon"
+version = "1.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa"
+dependencies = [
+ "either",
+ "rayon-core",
+]
+
+[[package]]
+name = "rayon-core"
+version = "1.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2"
+dependencies = [
+ "crossbeam-deque",
+ "crossbeam-utils",
 ]
 
 [[package]]
@@ -1038,11 +2014,11 @@ dependencies = [
 
 [[package]]
 name = "redox_syscall"
-version = "0.5.7"
+version = "0.5.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f"
+checksum = "928fca9cf2aa042393a8325b9ead81d2f0df4cb12e1e24cef072922ccd99c5af"
 dependencies = [
- "bitflags 2.6.0",
+ "bitflags 2.9.1",
 ]
 
 [[package]]
@@ -1053,8 +2029,17 @@ checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
 dependencies = [
  "aho-corasick",
  "memchr",
- "regex-automata",
- "regex-syntax",
+ "regex-automata 0.4.9",
+ "regex-syntax 0.8.5",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
+dependencies = [
+ "regex-syntax 0.6.29",
 ]
 
 [[package]]
@@ -1065,41 +2050,81 @@ checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
 dependencies = [
  "aho-corasick",
  "memchr",
- "regex-syntax",
+ "regex-syntax 0.8.5",
 ]
 
+[[package]]
+name = "regex-syntax"
+version = "0.6.29"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
+
 [[package]]
 name = "regex-syntax"
 version = "0.8.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
 
+[[package]]
+name = "rgb"
+version = "0.8.50"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "57397d16646700483b67d2dd6511d79318f9d057fdbd21a4066aeac8b41d310a"
+
+[[package]]
+name = "roxmltree"
+version = "0.14.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "921904a62e410e37e215c40381b7117f830d9d89ba60ab5236170541dd25646b"
+dependencies = [
+ "xmlparser",
+]
+
 [[package]]
 name = "rust_vulkan_test"
 version = "0.1.0"
 dependencies = [
  "anyhow",
- "ash",
- "ash-window",
- "env_logger",
- "glob",
- "log",
+ "egui_winit_vulkano",
+ "glam",
+ "image",
+ "rand 0.9.1",
+ "thiserror 2.0.12",
+ "tracing",
+ "tracing-log",
+ "tracing-subscriber",
+ "tracing-tracy",
+ "vulkano",
+ "vulkano-shaders",
+ "vulkano-util",
  "winit",
 ]
 
 [[package]]
 name = "rustix"
-version = "0.38.40"
+version = "0.38.44"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "99e4ea3e1cdc4b559b8e5650f9c8e5998e3e5c1343b4eaf034565f32318d63c0"
+checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154"
 dependencies = [
- "bitflags 2.6.0",
+ "bitflags 2.9.1",
  "errno",
  "libc",
  "linux-raw-sys",
- "windows-sys 0.52.0",
+ "windows-sys 0.59.0",
 ]
 
+[[package]]
+name = "rustversion"
+version = "1.0.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2"
+
+[[package]]
+name = "ryu"
+version = "1.0.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
+
 [[package]]
 name = "same-file"
 version = "1.0.6"
@@ -1115,6 +2140,12 @@ version = "1.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294"
 
+[[package]]
+name = "scopeguard"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
+
 [[package]]
 name = "sctk-adwaita"
 version = "0.10.1"
@@ -1130,30 +2161,96 @@ dependencies = [
 
 [[package]]
 name = "serde"
-version = "1.0.215"
+version = "1.0.219"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f"
+checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
 dependencies = [
  "serde_derive",
 ]
 
 [[package]]
 name = "serde_derive"
-version = "1.0.215"
+version = "1.0.219"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0"
+checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
 dependencies = [
  "proc-macro2",
  "quote",
  "syn",
 ]
 
+[[package]]
+name = "serde_json"
+version = "1.0.140"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373"
+dependencies = [
+ "itoa",
+ "memchr",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "serde_spanned"
+version = "0.6.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "shaderc"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "27e07913ada18607bb60d12431cbe3358d3bbebbe95948e1618851dc01e63b7b"
+dependencies = [
+ "libc",
+ "shaderc-sys",
+]
+
+[[package]]
+name = "shaderc-sys"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "73120d240fe22196300f39ca8547ca2d014960f27b19b47b21288b396272f7f7"
+dependencies = [
+ "cmake",
+ "libc",
+ "roxmltree",
+]
+
+[[package]]
+name = "sharded-slab"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6"
+dependencies = [
+ "lazy_static",
+]
+
 [[package]]
 name = "shlex"
 version = "1.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
 
+[[package]]
+name = "simd-adler32"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
+
+[[package]]
+name = "simd_helpers"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "95890f873bec569a0362c235787f3aca6e1e887302ba4840839bcc6459c42da6"
+dependencies = [
+ "quote",
+]
+
 [[package]]
 name = "slab"
 version = "0.4.9"
@@ -1164,10 +2261,16 @@ dependencies = [
 ]
 
 [[package]]
-name = "smallvec"
-version = "1.13.2"
+name = "slabbin"
+version = "1.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
+checksum = "9db491c0d4152a069911a0fbdaca959691bf0b9d7110d98a7ed1c8e59b79ab30"
+
+[[package]]
+name = "smallvec"
+version = "1.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9"
 
 [[package]]
 name = "smithay-client-toolkit"
@@ -1175,7 +2278,7 @@ version = "0.19.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016"
 dependencies = [
- "bitflags 2.6.0",
+ "bitflags 2.9.1",
  "calloop",
  "calloop-wayland-source",
  "cursor-icon",
@@ -1183,7 +2286,7 @@ dependencies = [
  "log",
  "memmap2",
  "rustix",
- "thiserror",
+ "thiserror 1.0.69",
  "wayland-backend",
  "wayland-client",
  "wayland-csd-frame",
@@ -1194,6 +2297,17 @@ dependencies = [
  "xkeysym",
 ]
 
+[[package]]
+name = "smithay-clipboard"
+version = "0.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cc8216eec463674a0e90f29e0ae41a4db573ec5b56b1c6c1c71615d249b6d846"
+dependencies = [
+ "libc",
+ "smithay-client-toolkit",
+ "wayland-backend",
+]
+
 [[package]]
 name = "smol_str"
 version = "0.2.2"
@@ -1203,6 +2317,12 @@ dependencies = [
  "serde",
 ]
 
+[[package]]
+name = "stable_deref_trait"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
+
 [[package]]
 name = "strict-num"
 version = "0.1.1"
@@ -1211,22 +2331,61 @@ checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731"
 
 [[package]]
 name = "syn"
-version = "2.0.87"
+version = "2.0.101"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d"
+checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf"
 dependencies = [
  "proc-macro2",
  "quote",
  "unicode-ident",
 ]
 
+[[package]]
+name = "synstructure"
+version = "0.13.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "system-deps"
+version = "6.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349"
+dependencies = [
+ "cfg-expr",
+ "heck 0.5.0",
+ "pkg-config",
+ "toml",
+ "version-compare",
+]
+
+[[package]]
+name = "target-lexicon"
+version = "0.12.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
+
 [[package]]
 name = "thiserror"
 version = "1.0.69"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
 dependencies = [
- "thiserror-impl",
+ "thiserror-impl 1.0.69",
+]
+
+[[package]]
+name = "thiserror"
+version = "2.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
+dependencies = [
+ "thiserror-impl 2.0.12",
 ]
 
 [[package]]
@@ -1240,6 +2399,38 @@ dependencies = [
  "syn",
 ]
 
+[[package]]
+name = "thiserror-impl"
+version = "2.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "thread_local"
+version = "1.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c"
+dependencies = [
+ "cfg-if",
+ "once_cell",
+]
+
+[[package]]
+name = "tiff"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba1310fcea54c6a9a4fd1aad794ecc02c31682f6bfbecdf460bf19533eed1e3e"
+dependencies = [
+ "flate2",
+ "jpeg-decoder",
+ "weezl",
+]
+
 [[package]]
 name = "tiny-skia"
 version = "0.11.4"
@@ -1266,49 +2457,153 @@ dependencies = [
 ]
 
 [[package]]
-name = "toml_datetime"
-version = "0.6.8"
+name = "tinystr"
+version = "0.8.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41"
+checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b"
+dependencies = [
+ "displaydoc",
+ "zerovec",
+]
+
+[[package]]
+name = "toml"
+version = "0.8.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05ae329d1f08c4d17a59bed7ff5b5a769d062e64a62d34a3261b219e62cd5aae"
+dependencies = [
+ "serde",
+ "serde_spanned",
+ "toml_datetime",
+ "toml_edit",
+]
+
+[[package]]
+name = "toml_datetime"
+version = "0.6.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3da5db5a963e24bc68be8b17b6fa82814bb22ee8660f192bb182771d498f09a3"
+dependencies = [
+ "serde",
+]
 
 [[package]]
 name = "toml_edit"
-version = "0.22.22"
+version = "0.22.26"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5"
+checksum = "310068873db2c5b3e7659d2cc35d21855dbafa50d1ce336397c666e3cb08137e"
 dependencies = [
  "indexmap",
+ "serde",
+ "serde_spanned",
  "toml_datetime",
  "winnow",
 ]
 
 [[package]]
 name = "tracing"
-version = "0.1.40"
+version = "0.1.41"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef"
+checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0"
 dependencies = [
  "pin-project-lite",
+ "tracing-attributes",
  "tracing-core",
 ]
 
 [[package]]
-name = "tracing-core"
-version = "0.1.32"
+name = "tracing-attributes"
+version = "0.1.28"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54"
+checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "tracing-core"
+version = "0.1.33"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c"
+dependencies = [
+ "once_cell",
+ "valuable",
+]
+
+[[package]]
+name = "tracing-log"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
+dependencies = [
+ "log",
+ "once_cell",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-subscriber"
+version = "0.3.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008"
+dependencies = [
+ "matchers",
+ "nu-ansi-term",
+ "once_cell",
+ "regex",
+ "sharded-slab",
+ "smallvec",
+ "thread_local",
+ "tracing",
+ "tracing-core",
+ "tracing-log",
+]
+
+[[package]]
+name = "tracing-tracy"
+version = "0.11.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0eaa1852afa96e0fe9e44caa53dc0bd2d9d05e0f2611ce09f97f8677af56e4ba"
+dependencies = [
+ "tracing-core",
+ "tracing-subscriber",
+ "tracy-client",
+]
+
+[[package]]
+name = "tracy-client"
+version = "0.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d90a2c01305b02b76fdd89ac8608bae27e173c829a35f7d76a345ab5d33836db"
+dependencies = [
+ "loom",
+ "once_cell",
+ "tracy-client-sys",
+]
+
+[[package]]
+name = "tracy-client-sys"
+version = "0.24.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "69fff37da548239c3bf9e64a12193d261e8b22b660991c6fd2df057c168f435f"
+dependencies = [
+ "cc",
+ "windows-targets 0.52.6",
+]
 
 [[package]]
 name = "ttf-parser"
-version = "0.25.0"
+version = "0.25.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5902c5d130972a0000f60860bfbf46f7ca3db5391eddfedd1b8728bd9dc96c0e"
+checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31"
 
 [[package]]
 name = "unicode-ident"
-version = "1.0.13"
+version = "1.0.18"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe"
+checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
 
 [[package]]
 name = "unicode-segmentation"
@@ -1317,10 +2612,44 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
 
 [[package]]
-name = "utf8parse"
-version = "0.2.2"
+name = "url"
+version = "2.5.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
+checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60"
+dependencies = [
+ "form_urlencoded",
+ "idna",
+ "percent-encoding",
+]
+
+[[package]]
+name = "utf8_iter"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
+
+[[package]]
+name = "v_frame"
+version = "0.3.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d6f32aaa24bacd11e488aa9ba66369c7cd514885742c9fe08cfe85884db3e92b"
+dependencies = [
+ "aligned-vec",
+ "num-traits",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "valuable"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
+
+[[package]]
+name = "version-compare"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b"
 
 [[package]]
 name = "version_check"
@@ -1328,6 +2657,85 @@ version = "0.9.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
 
+[[package]]
+name = "vk-parse"
+version = "0.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3859da4d7b98bec73e68fb65815d47a263819c415c90eed42b80440a02cbce8c"
+dependencies = [
+ "xml-rs",
+]
+
+[[package]]
+name = "vulkano"
+version = "0.35.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08840c2b51759a6f88f26f5ea378bc8b5c199a5b4760ddda292304be087249c4"
+dependencies = [
+ "ash",
+ "bytemuck",
+ "crossbeam-queue",
+ "foldhash",
+ "half",
+ "heck 0.4.1",
+ "indexmap",
+ "libloading",
+ "nom",
+ "once_cell",
+ "parking_lot",
+ "proc-macro2",
+ "quote",
+ "raw-window-handle",
+ "raw-window-metal",
+ "serde",
+ "serde_json",
+ "slabbin",
+ "smallvec",
+ "thread_local",
+ "vk-parse",
+ "vulkano-macros",
+ "x11-dl",
+ "x11rb",
+]
+
+[[package]]
+name = "vulkano-macros"
+version = "0.35.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1dc929c42c9336fd082079ac3ea30126e4a0dfe36fd2e2b3581303f7d140d20f"
+dependencies = [
+ "proc-macro-crate",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "vulkano-shaders"
+version = "0.35.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3bf501461be7cef2893c0e62c50945add9763cc482051d29053f6157089d5ea9"
+dependencies = [
+ "foldhash",
+ "heck 0.4.1",
+ "proc-macro2",
+ "quote",
+ "shaderc",
+ "syn",
+ "vulkano",
+]
+
+[[package]]
+name = "vulkano-util"
+version = "0.35.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e25dc54fd5e14a0e01c7282f9b5d9c6e133745e1df3228b0352366e34c83bb6b"
+dependencies = [
+ "foldhash",
+ "vulkano",
+ "winit",
+]
+
 [[package]]
 name = "walkdir"
 version = "2.5.0"
@@ -1345,25 +2753,34 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
 
 [[package]]
-name = "wasm-bindgen"
-version = "0.2.95"
+name = "wasi"
+version = "0.14.2+wasi-0.2.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e"
+checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3"
+dependencies = [
+ "wit-bindgen-rt",
+]
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.100"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5"
 dependencies = [
  "cfg-if",
  "once_cell",
+ "rustversion",
  "wasm-bindgen-macro",
 ]
 
 [[package]]
 name = "wasm-bindgen-backend"
-version = "0.2.95"
+version = "0.2.100"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358"
+checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6"
 dependencies = [
  "bumpalo",
  "log",
- "once_cell",
  "proc-macro2",
  "quote",
  "syn",
@@ -1372,21 +2789,22 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-futures"
-version = "0.4.45"
+version = "0.4.50"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cc7ec4f8827a71586374db3e87abdb5a2bb3a15afed140221307c3ec06b1f63b"
+checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61"
 dependencies = [
  "cfg-if",
  "js-sys",
+ "once_cell",
  "wasm-bindgen",
  "web-sys",
 ]
 
 [[package]]
 name = "wasm-bindgen-macro"
-version = "0.2.95"
+version = "0.2.100"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56"
+checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407"
 dependencies = [
  "quote",
  "wasm-bindgen-macro-support",
@@ -1394,9 +2812,9 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-macro-support"
-version = "0.2.95"
+version = "0.2.100"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68"
+checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -1407,15 +2825,18 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-shared"
-version = "0.2.95"
+version = "0.2.100"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d"
+checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d"
+dependencies = [
+ "unicode-ident",
+]
 
 [[package]]
 name = "wayland-backend"
-version = "0.3.7"
+version = "0.3.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "056535ced7a150d45159d3a8dc30f91a2e2d588ca0b23f70e56033622b8016f6"
+checksum = "fe770181423e5fc79d3e2a7f4410b7799d5aab1de4372853de3c6aa13ca24121"
 dependencies = [
  "cc",
  "downcast-rs",
@@ -1427,11 +2848,11 @@ dependencies = [
 
 [[package]]
 name = "wayland-client"
-version = "0.31.7"
+version = "0.31.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b66249d3fc69f76fd74c82cc319300faa554e9d865dab1f7cd66cc20db10b280"
+checksum = "978fa7c67b0847dbd6a9f350ca2569174974cd4082737054dbb7fbb79d7d9a61"
 dependencies = [
- "bitflags 2.6.0",
+ "bitflags 2.9.1",
  "rustix",
  "wayland-backend",
  "wayland-scanner",
@@ -1443,16 +2864,16 @@ version = "0.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e"
 dependencies = [
- "bitflags 2.6.0",
+ "bitflags 2.9.1",
  "cursor-icon",
  "wayland-backend",
 ]
 
 [[package]]
 name = "wayland-cursor"
-version = "0.31.7"
+version = "0.31.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "32b08bc3aafdb0035e7fe0fdf17ba0c09c268732707dca4ae098f60cb28c9e4c"
+checksum = "a65317158dec28d00416cb16705934070aef4f8393353d41126c54264ae0f182"
 dependencies = [
  "rustix",
  "wayland-client",
@@ -1461,11 +2882,11 @@ dependencies = [
 
 [[package]]
 name = "wayland-protocols"
-version = "0.32.5"
+version = "0.32.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7cd0ade57c4e6e9a8952741325c30bf82f4246885dca8bf561898b86d0c1f58e"
+checksum = "779075454e1e9a521794fed15886323ea0feda3f8b0fc1390f5398141310422a"
 dependencies = [
- "bitflags 2.6.0",
+ "bitflags 2.9.1",
  "wayland-backend",
  "wayland-client",
  "wayland-scanner",
@@ -1473,11 +2894,11 @@ dependencies = [
 
 [[package]]
 name = "wayland-protocols-plasma"
-version = "0.3.5"
+version = "0.3.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9b31cab548ee68c7eb155517f2212049dc151f7cd7910c2b66abfd31c3ee12bd"
+checksum = "4fd38cdad69b56ace413c6bcc1fbf5acc5e2ef4af9d5f8f1f9570c0c83eae175"
 dependencies = [
- "bitflags 2.6.0",
+ "bitflags 2.9.1",
  "wayland-backend",
  "wayland-client",
  "wayland-protocols",
@@ -1486,11 +2907,11 @@ dependencies = [
 
 [[package]]
 name = "wayland-protocols-wlr"
-version = "0.3.5"
+version = "0.3.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "782e12f6cd923c3c316130d56205ebab53f55d6666b7faddfad36cecaeeb4022"
+checksum = "1cb6cdc73399c0e06504c437fe3cf886f25568dd5454473d565085b36d6a8bbf"
 dependencies = [
- "bitflags 2.6.0",
+ "bitflags 2.9.1",
  "wayland-backend",
  "wayland-client",
  "wayland-protocols",
@@ -1499,9 +2920,9 @@ dependencies = [
 
 [[package]]
 name = "wayland-scanner"
-version = "0.31.5"
+version = "0.31.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "597f2001b2e5fc1121e3d5b9791d3e78f05ba6bfa4641053846248e3a13661c3"
+checksum = "896fdafd5d28145fce7958917d69f2fd44469b1d4e861cb5961bcbeebc6d1484"
 dependencies = [
  "proc-macro2",
  "quick-xml",
@@ -1510,9 +2931,9 @@ dependencies = [
 
 [[package]]
 name = "wayland-sys"
-version = "0.31.5"
+version = "0.31.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "efa8ac0d8e8ed3e3b5c9fc92c7881406a268e11555abe36493efabe649a29e09"
+checksum = "dbcebb399c77d5aa9fa5db874806ee7b4eba4e73650948e8f93963f128896615"
 dependencies = [
  "dlib",
  "log",
@@ -1522,9 +2943,9 @@ dependencies = [
 
 [[package]]
 name = "web-sys"
-version = "0.3.72"
+version = "0.3.77"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112"
+checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2"
 dependencies = [
  "js-sys",
  "wasm-bindgen",
@@ -1540,6 +2961,45 @@ dependencies = [
  "wasm-bindgen",
 ]
 
+[[package]]
+name = "webbrowser"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d5df295f8451142f1856b1bd86a606dfe9587d439bc036e319c827700dbd555e"
+dependencies = [
+ "core-foundation 0.10.0",
+ "home",
+ "jni",
+ "log",
+ "ndk-context",
+ "objc2 0.6.1",
+ "objc2-foundation 0.3.1",
+ "url",
+ "web-sys",
+]
+
+[[package]]
+name = "weezl"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a751b3277700db47d3e574514de2eced5e54dc8a5436a3bf7a0b248b2cee16f3"
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
 [[package]]
 name = "winapi-util"
 version = "0.1.9"
@@ -1549,6 +3009,114 @@ dependencies = [
  "windows-sys 0.59.0",
 ]
 
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
+name = "windows"
+version = "0.61.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c5ee8f3d025738cb02bad7868bbb5f8a6327501e870bf51f1b455b0a2454a419"
+dependencies = [
+ "windows-collections",
+ "windows-core",
+ "windows-future",
+ "windows-link",
+ "windows-numerics",
+]
+
+[[package]]
+name = "windows-collections"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8"
+dependencies = [
+ "windows-core",
+]
+
+[[package]]
+name = "windows-core"
+version = "0.61.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3"
+dependencies = [
+ "windows-implement",
+ "windows-interface",
+ "windows-link",
+ "windows-result",
+ "windows-strings",
+]
+
+[[package]]
+name = "windows-future"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e"
+dependencies = [
+ "windows-core",
+ "windows-link",
+ "windows-threading",
+]
+
+[[package]]
+name = "windows-implement"
+version = "0.60.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "windows-interface"
+version = "0.59.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "windows-link"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38"
+
+[[package]]
+name = "windows-numerics"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1"
+dependencies = [
+ "windows-core",
+ "windows-link",
+]
+
+[[package]]
+name = "windows-result"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6"
+dependencies = [
+ "windows-link",
+]
+
+[[package]]
+name = "windows-strings"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57"
+dependencies = [
+ "windows-link",
+]
+
 [[package]]
 name = "windows-sys"
 version = "0.45.0"
@@ -1622,6 +3190,15 @@ dependencies = [
  "windows_x86_64_msvc 0.52.6",
 ]
 
+[[package]]
+name = "windows-threading"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6"
+dependencies = [
+ "windows-link",
+]
+
 [[package]]
 name = "windows_aarch64_gnullvm"
 version = "0.42.2"
@@ -1756,20 +3333,20 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
 
 [[package]]
 name = "winit"
-version = "0.30.5"
+version = "0.30.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0be9e76a1f1077e04a411f0b989cbd3c93339e1771cb41e71ac4aee95bfd2c67"
+checksum = "b0d05bd8908e14618c9609471db04007e644fd9cce6529756046cfc577f9155e"
 dependencies = [
  "ahash",
  "android-activity",
  "atomic-waker",
- "bitflags 2.6.0",
+ "bitflags 2.9.1",
  "block2",
  "bytemuck",
  "calloop",
  "cfg_aliases",
  "concurrent-queue",
- "core-foundation",
+ "core-foundation 0.9.4",
  "core-graphics",
  "cursor-icon",
  "dpi",
@@ -1777,9 +3354,9 @@ dependencies = [
  "libc",
  "memmap2",
  "ndk",
- "objc2",
- "objc2-app-kit",
- "objc2-foundation",
+ "objc2 0.5.2",
+ "objc2-app-kit 0.2.2",
+ "objc2-foundation 0.2.2",
  "objc2-ui-kit",
  "orbclient",
  "percent-encoding",
@@ -1808,13 +3385,28 @@ dependencies = [
 
 [[package]]
 name = "winnow"
-version = "0.6.20"
+version = "0.7.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b"
+checksum = "c06928c8748d81b05c9be96aad92e1b6ff01833332f281e8cfca3be4b35fc9ec"
 dependencies = [
  "memchr",
 ]
 
+[[package]]
+name = "wit-bindgen-rt"
+version = "0.39.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
+dependencies = [
+ "bitflags 2.9.1",
+]
+
+[[package]]
+name = "writeable"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb"
+
 [[package]]
 name = "x11-dl"
 version = "2.21.0"
@@ -1859,7 +3451,7 @@ version = "0.4.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d039de8032a9a8856a6be89cea3e5d12fdd82306ab7c94d74e6deab2460651c5"
 dependencies = [
- "bitflags 2.6.0",
+ "bitflags 2.9.1",
  "dlib",
  "log",
  "once_cell",
@@ -1873,21 +3465,135 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56"
 
 [[package]]
-name = "zerocopy"
-version = "0.7.35"
+name = "xml-rs"
+version = "0.8.26"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
+checksum = "a62ce76d9b56901b19a74f19431b0d8b3bc7ca4ad685a746dfd78ca8f4fc6bda"
+
+[[package]]
+name = "xmlparser"
+version = "0.13.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "66fee0b777b0f5ac1c69bb06d361268faafa61cd4682ae064a171c16c433e9e4"
+
+[[package]]
+name = "yoke"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc"
+dependencies = [
+ "serde",
+ "stable_deref_trait",
+ "yoke-derive",
+ "zerofrom",
+]
+
+[[package]]
+name = "yoke-derive"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "synstructure",
+]
+
+[[package]]
+name = "zerocopy"
+version = "0.8.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb"
 dependencies = [
  "zerocopy-derive",
 ]
 
 [[package]]
 name = "zerocopy-derive"
-version = "0.7.35"
+version = "0.8.25"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
+checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef"
 dependencies = [
  "proc-macro2",
  "quote",
  "syn",
 ]
+
+[[package]]
+name = "zerofrom"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5"
+dependencies = [
+ "zerofrom-derive",
+]
+
+[[package]]
+name = "zerofrom-derive"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "synstructure",
+]
+
+[[package]]
+name = "zerotrie"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595"
+dependencies = [
+ "displaydoc",
+ "yoke",
+ "zerofrom",
+]
+
+[[package]]
+name = "zerovec"
+version = "0.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428"
+dependencies = [
+ "yoke",
+ "zerofrom",
+ "zerovec-derive",
+]
+
+[[package]]
+name = "zerovec-derive"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "zune-core"
+version = "0.4.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a"
+
+[[package]]
+name = "zune-inflate"
+version = "0.2.54"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02"
+dependencies = [
+ "simd-adler32",
+]
+
+[[package]]
+name = "zune-jpeg"
+version = "0.4.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "99a5bab8d7dedf81405c4bb1f2b83ea057643d9cb28778cea9eecddeedd2e028"
+dependencies = [
+ "zune-core",
+]
diff --git a/Cargo.toml b/Cargo.toml
index 3865769..af26232 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,19 +1,30 @@
 [package]
 name = "rust_vulkan_test"
 version = "0.1.0"
-edition = "2021"
+edition = "2024"
 authors = ["Florian RICHER <florian.richer@protonmail.com>"]
 publish = false
 
 [dependencies]
 anyhow = "1.0"
+thiserror = "2.0"
 winit = { version = "0.30", features = ["rwh_06"] }
-ash = { version = "0.38", default-features = false, features = ["linked", "debug", "std"] }
-ash-window = "0.13"
+
+vulkano = "0.35"
+vulkano-shaders = "0.35"
+vulkano-util = "0.35"
+egui_winit_vulkano = { version = "0.28" }
+
+image = { version = "0.25", features = ["png", "jpeg"] }
+
+# Math
+glam = { version = "0.30" }
 
 # Log and tracing
-log = "0.4"
-env_logger = "0.11.5"
+tracing = "0.1"
+tracing-subscriber = { version = "0.3", features = ["env-filter", "fmt"] }
+tracing-log = "0.2"
+tracing-tracy = "0.11"
 
-[build-dependencies]
-glob = "0.3"
\ No newline at end of file
+# Random
+rand = "0.9"
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..c271fd8
--- /dev/null
+++ b/README.md
@@ -0,0 +1,17 @@
+# Project
+
+## Notes
+
+1. Run renderdoc on wayland:
+
+```console
+WAYLAND_DISPLAY= QT_QPA_PLATFORM=xcb qrenderdoc
+```
+> Not supported yet https://github.com/baldurk/renderdoc/issues/853
+
+2. [Difference Between OpenGL and Vulkan](./docs/OPENGL_VULKAN_DIFF.md)
+
+## Usefull links
+
+- https://vulkan-tutorial.com/fr/Introduction
+- https://github.com/bwasty/vulkan-tutorial-rs
diff --git a/build.rs b/build.rs
deleted file mode 100644
index be949e6..0000000
--- a/build.rs
+++ /dev/null
@@ -1,24 +0,0 @@
-use std::process::Command;
-
-fn main() {
-    for shader in glob::glob("res/shaders/*").unwrap().filter_map(Result::ok) {
-        if !shader.is_file() {
-            continue;
-        }
-
-        let shader_file_name = shader.to_str().unwrap();
-
-        let mut command = Command::new("glslc");
-        command.arg(&shader);
-
-        let out_file = match shader.extension().unwrap().to_str().unwrap() {
-            "vert" => shader_file_name.replace(".vert", ".vert.spv"),
-            "frag" => shader_file_name.replace(".frag", ".frag.spv"),
-            _ => continue,
-        };
-
-        command.arg("-o");
-        command.arg(out_file);
-        command.output().unwrap();
-    }
-}
diff --git a/docs/OPENGL_VULKAN_DIFF.md b/docs/OPENGL_VULKAN_DIFF.md
new file mode 100644
index 0000000..93d0855
--- /dev/null
+++ b/docs/OPENGL_VULKAN_DIFF.md
@@ -0,0 +1,11 @@
+# Difference between Vulkan and OpenGL
+
+Viewport:
+
+- Y axis is flipped like D3D
+- Clipped Z axis is not [-1; 1] but [0; 1]
+
+![normalized viewport coordinates](./images/normalized_device_coordinates.svg)
+![coord_sys](./images/coord_sys.png)
+
+See: [Vulkan Tutorial (Vertex step)](https://vulkan-tutorial.com/Drawing_a_triangle/Graphics_pipeline_basics/Shader_modules) and [VK_KHR_maintenance1 (Allow negative height)](https://registry.khronos.org/vulkan/specs/latest/man/html/VK_KHR_maintenance1.html#_description)
diff --git a/docs/images/coord_sys.png b/docs/images/coord_sys.png
new file mode 100644
index 0000000..6d195f6
Binary files /dev/null and b/docs/images/coord_sys.png differ
diff --git a/docs/images/normalized_device_coordinates.svg b/docs/images/normalized_device_coordinates.svg
new file mode 100644
index 0000000..970c9f4
--- /dev/null
+++ b/docs/images/normalized_device_coordinates.svg
@@ -0,0 +1,219 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="168.23553mm"
+   height="76.127022mm"
+   viewBox="0 0 596.11016 269.74141"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.91 r13725"
+   sodipodi:docname="clip_coordinates.svg">
+  <defs
+     id="defs4" />
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="0.98994949"
+     inkscape:cx="140.75091"
+     inkscape:cy="-3.0732866"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="false"
+     inkscape:window-width="1600"
+     inkscape:window-height="837"
+     inkscape:window-x="-8"
+     inkscape:window-y="-8"
+     inkscape:window-maximized="1"
+     fit-margin-top="10"
+     fit-margin-left="10"
+     fit-margin-right="10"
+     fit-margin-bottom="10" />
+  <metadata
+     id="metadata7">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(-68.169789,-67.73013)">
+    <rect
+       style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:2.37949777;stroke-opacity:1"
+       id="rect4136"
+       width="185.26089"
+       height="129.17273"
+       x="127.66544"
+       y="152.46893" />
+    <text
+       xml:space="preserve"
+       style="font-style:normal;font-weight:normal;font-size:12.5px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       x="142.5"
+       y="114.50506"
+       id="text4153"
+       sodipodi:linespacing="125%"><tspan
+         sodipodi:role="line"
+         id="tspan4155"
+         x="142.5"
+         y="114.50506">Framebuffer coordinates</tspan></text>
+    <text
+       xml:space="preserve"
+       style="font-style:normal;font-weight:normal;font-size:12.5px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#4d4d4d;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       x="108.08633"
+       y="144.23506"
+       id="text4157"
+       sodipodi:linespacing="125%"><tspan
+         sodipodi:role="line"
+         id="tspan4159"
+         x="108.08633"
+         y="144.23506">(0, 0)</tspan></text>
+    <text
+       xml:space="preserve"
+       style="font-style:normal;font-weight:normal;font-size:12.5px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#4d4d4d;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       x="289.4823"
+       y="143.68567"
+       id="text4157-1"
+       sodipodi:linespacing="125%"><tspan
+         sodipodi:role="line"
+         id="tspan4159-7"
+         x="289.4823"
+         y="143.68567">(1920, 0)</tspan></text>
+    <text
+       xml:space="preserve"
+       style="font-style:normal;font-weight:normal;font-size:12.5px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#4d4d4d;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       x="102.49812"
+       y="299.52383"
+       id="text4157-0"
+       sodipodi:linespacing="125%"><tspan
+         sodipodi:role="line"
+         id="tspan4159-3"
+         x="102.49812"
+         y="299.52383">(0, 1080)</tspan></text>
+    <text
+       xml:space="preserve"
+       style="font-style:normal;font-weight:normal;font-size:12.5px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#4d4d4d;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       x="277.83316"
+       y="298.46939"
+       id="text4157-1-3"
+       sodipodi:linespacing="125%"><tspan
+         sodipodi:role="line"
+         id="tspan4159-7-2"
+         x="277.83316"
+         y="298.46939">(1920, 1080)</tspan></text>
+    <circle
+       style="fill:#000000;fill-opacity:1;stroke:none;stroke-opacity:1"
+       id="path4229"
+       cx="220.46579"
+       cy="218.48128"
+       r="1.767767" />
+    <text
+       xml:space="preserve"
+       style="font-style:normal;font-weight:normal;font-size:12.5px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#4d4d4d;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       x="187.29964"
+       y="232.99626"
+       id="text4157-1-3-3"
+       sodipodi:linespacing="125%"><tspan
+         sodipodi:role="line"
+         id="tspan4159-7-2-3"
+         x="187.29964"
+         y="232.99626">(960, 540)</tspan></text>
+    <rect
+       style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:2.37949777;stroke-opacity:1"
+       id="rect4136-0"
+       width="185.26089"
+       height="129.17273"
+       x="426.228"
+       y="150.62413" />
+    <text
+       xml:space="preserve"
+       style="font-style:normal;font-weight:normal;font-size:12.5px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       x="465.34827"
+       y="112.66027"
+       id="text4153-2"
+       sodipodi:linespacing="125%"><tspan
+         sodipodi:role="line"
+         id="tspan4155-2"
+         x="435.34827"
+         y="112.66027">Normalized device coordinates</tspan></text>
+    <text
+       xml:space="preserve"
+       style="font-style:normal;font-weight:normal;font-size:12.5px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#4d4d4d;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       x="406.6489"
+       y="142.39026"
+       id="text4157-9"
+       sodipodi:linespacing="125%"><tspan
+         sodipodi:role="line"
+         id="tspan4159-0"
+         x="406.6489"
+         y="142.39026">(-1, -1)</tspan></text>
+    <text
+       xml:space="preserve"
+       style="font-style:normal;font-weight:normal;font-size:12.5px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#4d4d4d;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       x="588.04486"
+       y="141.84087"
+       id="text4157-1-4"
+       sodipodi:linespacing="125%"><tspan
+         sodipodi:role="line"
+         id="tspan4159-7-21"
+         x="588.04486"
+         y="141.84087">(1, -1)</tspan></text>
+    <text
+       xml:space="preserve"
+       style="font-style:normal;font-weight:normal;font-size:12.5px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#4d4d4d;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       x="401.0607"
+       y="297.67902"
+       id="text4157-0-6"
+       sodipodi:linespacing="125%"><tspan
+         sodipodi:role="line"
+         id="tspan4159-3-5"
+         x="401.0607"
+         y="297.67902">(-1, 1)</tspan></text>
+    <text
+       xml:space="preserve"
+       style="font-style:normal;font-weight:normal;font-size:12.5px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#4d4d4d;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       x="592.82428"
+       y="296.62457"
+       id="text4157-1-3-7"
+       sodipodi:linespacing="125%"><tspan
+         sodipodi:role="line"
+         id="tspan4159-7-2-6"
+         x="592.82428"
+         y="296.62457">(1, 1)</tspan></text>
+    <circle
+       style="fill:#000000;fill-opacity:1;stroke:none;stroke-opacity:1"
+       id="path4229-5"
+       cx="519.02832"
+       cy="216.63647"
+       r="1.767767" />
+    <text
+       xml:space="preserve"
+       style="font-style:normal;font-weight:normal;font-size:12.5px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#4d4d4d;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       x="500.14792"
+       y="231.15146"
+       id="text4157-1-3-3-8"
+       sodipodi:linespacing="125%"><tspan
+         sodipodi:role="line"
+         id="tspan4159-7-2-3-0"
+         x="500.14792"
+         y="231.15146">(0, 0)</tspan></text>
+  </g>
+</svg>
diff --git a/flake.lock b/flake.lock
index 9ff2d00..4f4cf7e 100644
--- a/flake.lock
+++ b/flake.lock
@@ -5,11 +5,11 @@
         "systems": "systems"
       },
       "locked": {
-        "lastModified": 1726560853,
-        "narHash": "sha256-X6rJYSESBVr3hBoH0WbKE5KvhPU5bloyZ2L4K60/fPQ=",
+        "lastModified": 1731533236,
+        "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
         "owner": "numtide",
         "repo": "flake-utils",
-        "rev": "c1dfcf08411b08f6b8615f7d8971a2bfa81d5e8a",
+        "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
         "type": "github"
       },
       "original": {
@@ -28,26 +28,27 @@
         ]
       },
       "locked": {
-        "lastModified": 1713543440,
-        "narHash": "sha256-lnzZQYG0+EXl/6NkGpyIz+FEOc/DSEG57AP1VsdeNrM=",
-        "owner": "nix-community",
+        "lastModified": 1735283791,
+        "narHash": "sha256-JlT4VFs8aVlW+l151HZIZumfFsccZXcO/k5WpbYF09Y=",
+        "owner": "phirsch",
         "repo": "nixGL",
-        "rev": "310f8e49a149e4c9ea52f1adf70cdc768ec53f8a",
+        "rev": "ea8baea3b9d854bf9cf5c834a805c50948dd2603",
         "type": "github"
       },
       "original": {
-        "owner": "nix-community",
+        "owner": "phirsch",
+        "ref": "fix-versionMatch",
         "repo": "nixGL",
         "type": "github"
       }
     },
     "nixpkgs": {
       "locked": {
-        "lastModified": 1730831018,
-        "narHash": "sha256-2S0HwIFRxYp+afuoFORcZA9TjryAf512GmE0MTfEOPU=",
+        "lastModified": 1747312588,
+        "narHash": "sha256-MmJvj6mlWzeRwKGLcwmZpKaOPZ5nJb/6al5CXqJsgjo=",
         "owner": "nixos",
         "repo": "nixpkgs",
-        "rev": "8c4dc69b9732f6bbe826b5fbb32184987520ff26",
+        "rev": "b1bebd0fe266bbd1820019612ead889e96a8fa2d",
         "type": "github"
       },
       "original": {
@@ -72,11 +73,11 @@
         ]
       },
       "locked": {
-        "lastModified": 1730860036,
-        "narHash": "sha256-u0sfA4B65Q9cRO3xpIkQ4nldB8isfdIb3rWtsnRZ+Iw=",
+        "lastModified": 1747363019,
+        "narHash": "sha256-N4dwkRBmpOosa4gfFkFf/LTD8oOcNkAyvZ07JvRDEf0=",
         "owner": "oxalica",
         "repo": "rust-overlay",
-        "rev": "b8eb3aeb21629cbe14968a5e3b1cbaefb0d1b260",
+        "rev": "0e624f2b1972a34be1a9b35290ed18ea4b419b6f",
         "type": "github"
       },
       "original": {
diff --git a/flake.nix b/flake.nix
index 624590d..645ade4 100644
--- a/flake.nix
+++ b/flake.nix
@@ -9,7 +9,8 @@
       inputs.nixpkgs.follows = "nixpkgs";
     };
     nixgl = {
-      url = "github:nix-community/nixGL";
+      # Revert this to community version when https://github.com/nix-community/nixGL/pull/187 is merged
+      url = "github:phirsch/nixGL/fix-versionMatch";
       inputs.nixpkgs.follows = "nixpkgs";
       inputs.flake-utils.follows = "flake-utils";
     };
@@ -30,35 +31,58 @@
           cargo = rust;
         });
 
-        libs = with pkgs; [ vulkan-headers vulkan-loader vulkan-validation-layers ]
-          ++ pkgs.lib.optionals pkgs.stdenv.hostPlatform.isLinux (with pkgs; [ libxkbcommon wayland libGL ])
+        buildInputs = with pkgs; [ vulkan-headers vulkan-loader vulkan-validation-layers renderdoc tracy ]
+          ++ pkgs.lib.optionals pkgs.stdenv.hostPlatform.isLinux (with pkgs; [
+          stdenv.cc.cc.lib
+
+          # Wayland
+          libxkbcommon
+          wayland
+          libGL
+
+          # Xorg
+          xorg.libX11
+          xorg.libXcursor
+          xorg.libXi
+          xorg.libxcb
+          xorg.libxshmfence
+        ])
           ++ pkgs.lib.optionals pkgs.stdenv.hostPlatform.isDarwin (with pkgs; [ darwin.apple_sdk.frameworks.SystemConfiguration ]);
+
+        nativeBuildInputs = with pkgs; [
+          pkg-config
+          cmake
+          python312
+        ];
+
+        mkCustomShell = { packages ? [ ] }: pkgs.mkShell {
+          nativeBuildInputs = [
+            pkgs.renderdoc
+            (rust.override { extensions = [ "rust-src" "rust-analyzer" ]; })
+          ] ++ nativeBuildInputs;
+
+          buildInputs = buildInputs
+            ++ packages;
+
+          LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath buildInputs;
+          VK_LAYER_PATH = "${pkgs.vulkan-validation-layers}/share/vulkan/explicit_layer.d";
+          RUST_LOG = "debug,rust_vulkan_test=trace";
+        };
       in
       {
         devShells = {
-          default = pkgs.mkShell {
-            nativeBuildInputs = with pkgs; [
-              (rust.override { extensions = ["rust-src" "rust-analyzer"]; })
-              pkg-config
-            ];
-
-            buildInputs = libs ++ [
-              pkgs.nixgl.auto.nixVulkanNvidia pkgs.nixgl.nixVulkanIntel
-            ];
-
-            LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath (with pkgs; [ libxkbcommon wayland libGL ]);
-          };
+          default = mkCustomShell { packages = [ pkgs.nixgl.auto.nixVulkanNvidia pkgs.nixgl.nixVulkanMesa ]; };
+          nixos = mkCustomShell { };
         };
 
         packages = {
           default = rustPlatform.buildRustPackage {
-            pname = "rust_ash_test";
+            pname = "vulkan_test";
             version = "0.1.0";
 
             src = self;
 
-            nativeBuildInputs = with pkgs; [ pkg-config shaderc ];
-            buildInputs = libs;
+            inherit nativeBuildInputs buildInputs;
 
             cargoLock = {
               lockFile = ./Cargo.lock;
diff --git a/res/shaders/main.frag b/res/shaders/main.frag
deleted file mode 100644
index 33ca4af..0000000
--- a/res/shaders/main.frag
+++ /dev/null
@@ -1,8 +0,0 @@
-#version 450
-
-layout (location = 0) in vec3 fragColor;
-layout (location = 0) out vec4 outColor;
-
-void main() {
-    outColor = vec4(fragColor, 1.0);
-}
\ No newline at end of file
diff --git a/res/shaders/main.vert b/res/shaders/main.vert
deleted file mode 100644
index d141c1d..0000000
--- a/res/shaders/main.vert
+++ /dev/null
@@ -1 +0,0 @@
-#version 450

out gl_PerVertex {
    vec4 gl_Position;
};

layout (location = 0) out vec3 fragColor;

vec2 positions[3] = vec2[](
vec2(0.0, -0.5),
vec2(0.5, 0.5),
vec2(-0.5, 0.5)
);

vec3 colors[3] = vec3[](
vec3(1.0, 0.0, 0.0),
vec3(0.0, 1.0, 0.0),
vec3(0.0, 0.0, 1.0)
);

void main() {
    gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0);
    fragColor = colors[gl_VertexIndex];
}
\ No newline at end of file
diff --git a/res/shaders/vertex.frag b/res/shaders/vertex.frag
index 04f88bd..67831a9 100644
--- a/res/shaders/vertex.frag
+++ b/res/shaders/vertex.frag
@@ -1,10 +1,12 @@
 #version 450
-#extension GL_ARB_separate_shader_objects: enable
 
-layout (location = 0) in vec3 fragColor;
+layout (location = 0) in vec2 tex_coords;
 
-layout (location = 0) out vec4 outColor;
+layout (location = 0) out vec4 f_color;
+
+layout(set = 1, binding = 0) uniform sampler mySampler;
+layout(set = 1, binding = 1) uniform texture2D myTexture;
 
 void main() {
-    outColor = vec4(fragColor, 1.0);
-}
\ No newline at end of file
+    f_color = texture(sampler2D(myTexture, mySampler), tex_coords);
+}
diff --git a/res/shaders/vertex.vert b/res/shaders/vertex.vert
index d7b1aac..8c1df7c 100644
--- a/res/shaders/vertex.vert
+++ b/res/shaders/vertex.vert
@@ -1 +1,20 @@
-#version 450
#extension GL_ARB_separate_shader_objects: enable

// NOTE: names must match the `Vertex` struct in Rust
layout (location = 0) in vec2 pos;
layout (location = 1) in vec3 color;

layout (location = 0) out vec3 fragColor;

out gl_PerVertex {
    vec4 gl_Position;
};

void main() {
    gl_Position = vec4(pos, 0.0, 1.0);
    fragColor = color;
}
\ No newline at end of file
+#version 450
+
+layout (location = 0) in vec3 position;
+layout (location = 1) in vec2 uv;
+layout (location = 2) in mat4 model;
+
+layout (location = 0) out vec2 fragUv;
+
+layout (set = 0, binding = 0) uniform MVP {
+    mat4 world;
+    mat4 view;
+    mat4 projection;
+} uniforms;
+
+void main() {
+    mat4 worldview = uniforms.view * uniforms.world;
+    vec4 modelPosition = model * vec4(position, 1.0);
+    gl_Position = uniforms.projection * worldview * modelPosition;
+    fragUv = uv;
+}
diff --git a/res/textures/wooden-crate.jpg b/res/textures/wooden-crate.jpg
new file mode 100644
index 0000000..d1c8734
Binary files /dev/null and b/res/textures/wooden-crate.jpg differ
diff --git a/rust-toolchain.toml b/rust-toolchain.toml
index 2e2b8c8..b8889a3 100644
--- a/rust-toolchain.toml
+++ b/rust-toolchain.toml
@@ -1,2 +1,2 @@
 [toolchain]
-channel = "1.82.0"
+channel = "1.87.0"
diff --git a/src/core/app/context.rs b/src/core/app/context.rs
new file mode 100644
index 0000000..80b602e
--- /dev/null
+++ b/src/core/app/context.rs
@@ -0,0 +1,186 @@
+use std::{
+    cell::RefCell,
+    rc::Rc,
+    sync::{Arc, RwLock},
+};
+
+use egui_winit_vulkano::Gui;
+use vulkano::{
+    command_buffer::allocator::StandardCommandBufferAllocator,
+    descriptor_set::allocator::StandardDescriptorSetAllocator,
+    device::{Device, Queue},
+    instance::Instance,
+    memory::allocator::StandardMemoryAllocator,
+};
+use vulkano_util::{renderer::VulkanoWindowRenderer, window::VulkanoWindows};
+use winit::{event_loop::EventLoopProxy, monitor::MonitorHandle, window::WindowId};
+
+use crate::core::{input::InputManager, render::vulkan_context::VulkanContext, timer::Timer};
+
+use super::user_event::UserEvent;
+
+/// Contexte d'application unifié avec Arc<Mutex<>> pour la mutabilité partagée
+#[derive(Clone)]
+pub struct WindowContext {
+    // Données Vulkan (immutables)
+    pub vulkan_context: Arc<VulkanContext>,
+    pub device: Arc<Device>,
+    pub instance: Arc<Instance>,
+    pub graphics_queue: Arc<Queue>,
+    pub compute_queue: Arc<Queue>,
+    pub transfer_queue: Option<Arc<Queue>>,
+    pub memory_allocator: Arc<StandardMemoryAllocator>,
+    pub command_buffer_allocator: Arc<StandardCommandBufferAllocator>,
+    pub descriptor_set_allocator: Arc<StandardDescriptorSetAllocator>,
+    pub event_loop_proxy: EventLoopProxy<UserEvent>,
+    pub window_id: WindowId,
+
+    // Données mutables partagées avec Arc<Mutex<>>
+    pub vulkano_windows: Rc<RefCell<VulkanoWindows>>,
+    pub input_manager: Arc<RwLock<InputManager>>,
+    pub timer: Arc<RwLock<Timer>>,
+    pub gui: Rc<RefCell<Gui>>,
+}
+
+impl WindowContext {
+    pub fn new(
+        vulkan_context: Arc<VulkanContext>,
+        vulkano_windows: Rc<RefCell<VulkanoWindows>>,
+        input_manager: Arc<RwLock<InputManager>>,
+        timer: Arc<RwLock<Timer>>,
+        gui: Rc<RefCell<Gui>>,
+        event_loop_proxy: EventLoopProxy<UserEvent>,
+        window_id: WindowId,
+    ) -> Self {
+        let vulkano_context_inner = vulkan_context.vulkano_context();
+
+        Self {
+            // Données Vulkan
+            vulkan_context: vulkan_context.clone(),
+            device: vulkano_context_inner.device().clone(),
+            instance: vulkano_context_inner.instance().clone(),
+            graphics_queue: vulkano_context_inner.graphics_queue().clone(),
+            compute_queue: vulkano_context_inner.compute_queue().clone(),
+            transfer_queue: vulkano_context_inner.transfer_queue().cloned(),
+            memory_allocator: vulkano_context_inner.memory_allocator().clone(),
+            command_buffer_allocator: vulkan_context.command_buffer_allocator().clone(),
+            descriptor_set_allocator: vulkan_context.descriptor_set_allocator().clone(),
+            event_loop_proxy,
+            window_id,
+
+            // Données mutables partagées
+            vulkano_windows,
+            input_manager,
+            timer,
+            gui,
+        }
+    }
+
+    /// Extrait les résolutions d'un moniteur donné
+    fn extract_resolutions_from_monitor(monitor: MonitorHandle) -> Vec<(u32, u32)> {
+        let video_modes: Vec<_> = monitor.video_modes().collect();
+
+        let resolutions: Vec<(u32, u32)> = video_modes
+            .into_iter()
+            .map(|mode| {
+                let size = mode.size();
+                (size.width, size.height)
+            })
+            .collect();
+
+        tracing::trace!(
+            "Modes vidéo trouvés pour {:?}: {:?}",
+            monitor.name(),
+            resolutions
+        );
+        resolutions
+    }
+
+    /// Récupère les résolutions disponibles
+    pub fn get_available_resolutions(&self) -> Vec<(u32, u32)> {
+        self.with_renderer(|renderer| {
+            renderer
+                .window()
+                .current_monitor()
+                .map(Self::extract_resolutions_from_monitor)
+                .unwrap_or_default()
+        })
+    }
+
+    /// Récupère le delta time actuel depuis le timer
+    pub fn get_delta_time(&self) -> f32 {
+        self.with_timer(|timer| timer.delta_time())
+    }
+
+    /// Récupère la taille de la fenêtre depuis le renderer
+    pub fn get_window_size(&self) -> [f32; 2] {
+        self.with_renderer(|renderer| renderer.window_size())
+    }
+
+    /// Récupère l'aspect ratio depuis le renderer
+    pub fn get_aspect_ratio(&self) -> f32 {
+        self.with_renderer(|renderer| renderer.aspect_ratio())
+    }
+
+    pub fn with_renderer<T, F>(&self, f: F) -> T
+    where
+        F: FnOnce(&VulkanoWindowRenderer) -> T,
+    {
+        let vulkano_windows = self.vulkano_windows.borrow_mut();
+        let renderer = vulkano_windows
+            .get_renderer(self.window_id)
+            .expect("Failed to get renderer");
+        f(renderer)
+    }
+
+    /// Méthode utilitaire pour accéder au renderer de manière thread-safe
+    pub fn with_renderer_mut<T, F>(&mut self, f: F) -> T
+    where
+        F: FnOnce(&mut VulkanoWindowRenderer) -> T,
+    {
+        let mut vulkano_windows = self.vulkano_windows.borrow_mut();
+        let renderer = vulkano_windows
+            .get_renderer_mut(self.window_id)
+            .expect("Failed to get renderer");
+        f(renderer)
+    }
+
+    /// Méthode utilitaire pour accéder au gui de manière thread-safe
+    pub fn with_gui<T, F>(&self, f: F) -> T
+    where
+        F: FnOnce(&Gui) -> T,
+    {
+        let gui = self.gui.borrow();
+        f(&gui)
+    }
+
+    /// Méthode utilitaire pour accéder au gui de manière thread-safe
+    pub fn with_gui_mut<T, F>(&mut self, f: F) -> T
+    where
+        F: FnOnce(&mut Gui) -> T,
+    {
+        let mut gui = self.gui.borrow_mut();
+        f(&mut gui)
+    }
+
+    /// Méthode utilitaire pour accéder à l'input manager de manière thread-safe
+    pub fn with_input_manager<T, F>(&self, f: F) -> T
+    where
+        F: FnOnce(&InputManager) -> T,
+    {
+        let input_manager = self
+            .input_manager
+            .read()
+            .expect("Failed to lock input_manager");
+        f(&input_manager)
+    }
+
+    /// Méthode utilitaire pour accéder au timer de manière thread-safe
+    pub fn with_timer<T, F>(&self, f: F) -> T
+    where
+        F: FnOnce(&Timer) -> T,
+    {
+        let timer = self.timer.read().expect("Failed to lock timer");
+        f(&timer)
+    }
+}
diff --git a/src/core/app/mod.rs b/src/core/app/mod.rs
new file mode 100644
index 0000000..81fb0a3
--- /dev/null
+++ b/src/core/app/mod.rs
@@ -0,0 +1,260 @@
+use std::cell::RefCell;
+use std::collections::HashMap;
+use std::rc::Rc;
+use std::sync::{Arc, RwLock};
+
+use super::render::vulkan_context::VulkanContext;
+use crate::core::input::InputManager;
+use crate::core::scene::manager::SceneManager;
+use crate::core::timer::Timer;
+use crate::game::scenes::main_scene::MainScene;
+use egui_winit_vulkano::{Gui, GuiConfig};
+use user_event::UserEvent;
+use vulkano::format::Format;
+use vulkano::image::ImageUsage;
+use vulkano::swapchain::PresentMode;
+use vulkano_util::context::VulkanoContext;
+use vulkano_util::window::{VulkanoWindows, WindowDescriptor};
+use winit::application::ApplicationHandler;
+use winit::event::WindowEvent;
+use winit::event_loop::{ActiveEventLoop, EventLoopProxy};
+use winit::window::WindowId;
+
+use self::context::WindowContext;
+
+pub mod context;
+pub mod user_event;
+
+pub const DEPTH_IMAGE_ID: usize = 0;
+
+pub struct App {
+    vulkan_context: Arc<VulkanContext>,
+    vulkano_windows: Rc<RefCell<VulkanoWindows>>,
+    gui: HashMap<WindowId, Rc<RefCell<Gui>>>,
+    scene_manager: HashMap<WindowId, SceneManager>,
+    input_manager: Arc<RwLock<InputManager>>,
+    timer: Arc<RwLock<Timer>>,
+    event_loop_proxy: EventLoopProxy<UserEvent>,
+
+    // Context d'application partagé par fenêtre - architecture unifiée
+    app_contexts: HashMap<WindowId, Rc<RefCell<WindowContext>>>,
+}
+
+impl App {
+    pub fn new(
+        vulkano_context: VulkanoContext,
+        input_manager: InputManager,
+        event_loop_proxy: EventLoopProxy<UserEvent>,
+    ) -> Self {
+        Self {
+            vulkan_context: Arc::new(VulkanContext::new(vulkano_context)),
+            vulkano_windows: Rc::new(RefCell::new(VulkanoWindows::default())),
+            gui: HashMap::new(),
+            input_manager: Arc::new(RwLock::new(input_manager)),
+            scene_manager: HashMap::new(),
+            timer: Arc::new(RwLock::new(Timer::new())),
+            event_loop_proxy,
+            app_contexts: HashMap::new(),
+        }
+    }
+}
+
+impl ApplicationHandler<UserEvent> for App {
+    fn resumed(&mut self, event_loop: &ActiveEventLoop) {
+        let mut vulkano_windows = self.vulkano_windows.borrow_mut();
+        let window_id = vulkano_windows.create_window(
+            event_loop,
+            self.vulkan_context.vulkano_context(),
+            &WindowDescriptor {
+                title: "Rust ASH Test".to_string(),
+                width: 800.0,
+                height: 600.0,
+                present_mode: PresentMode::Fifo,
+                cursor_visible: false,
+                cursor_locked: true,
+                ..Default::default()
+            },
+            |_| {},
+        );
+
+        let renderer = vulkano_windows.get_renderer_mut(window_id).unwrap();
+        renderer.add_additional_image_view(
+            DEPTH_IMAGE_ID,
+            Format::D16_UNORM,
+            ImageUsage::DEPTH_STENCIL_ATTACHMENT,
+        );
+
+        let gui = {
+            Gui::new(
+                event_loop,
+                renderer.surface(),
+                renderer.graphics_queue(),
+                renderer.swapchain_format(),
+                GuiConfig {
+                    is_overlay: true,
+                    allow_srgb_render_target: true,
+                    ..Default::default()
+                },
+            )
+        };
+        self.gui.insert(window_id, Rc::new(RefCell::new(gui)));
+
+        let mut scene_manager = SceneManager::new();
+        scene_manager.load_scene(Box::new(MainScene::default()));
+
+        self.scene_manager.insert(window_id, scene_manager);
+
+        let app_context = Rc::new(RefCell::new(WindowContext::new(
+            self.vulkan_context.clone(),
+            self.vulkano_windows.clone(),
+            self.input_manager.clone(),
+            self.timer.clone(),
+            self.gui.get(&window_id).unwrap().clone(),
+            self.event_loop_proxy.clone(),
+            window_id,
+        )));
+        self.app_contexts.insert(window_id, app_context);
+    }
+
+    fn device_event(
+        &mut self,
+        _event_loop: &ActiveEventLoop,
+        _device_id: winit::event::DeviceId,
+        event: winit::event::DeviceEvent,
+    ) {
+        let mut input_manager = self.input_manager.write().unwrap();
+        input_manager.process_device_event(&event);
+    }
+
+    fn window_event(&mut self, event_loop: &ActiveEventLoop, id: WindowId, event: WindowEvent) {
+        {
+            let gui = self.gui.get_mut(&id).unwrap();
+            let mut gui = gui.borrow_mut();
+            if !gui.update(&event) {
+                let mut input_manager = self.input_manager.write().unwrap();
+                input_manager.process_window_event(&event);
+            }
+        }
+
+        match event {
+            WindowEvent::CloseRequested => {
+                tracing::debug!("The close button was pressed; stopping");
+                event_loop.exit();
+            }
+            WindowEvent::Resized(_) | WindowEvent::ScaleFactorChanged { .. } => {
+                let mut vulkano_windows = self.vulkano_windows.borrow_mut();
+                vulkano_windows.get_renderer_mut(id).unwrap().resize();
+            }
+            WindowEvent::RedrawRequested => {
+                let _frame_span = tracing::info_span!("frame").entered();
+
+                {
+                    let _input_span = tracing::debug_span!("input_update").entered();
+                    let mut input_manager = self
+                        .input_manager
+                        .write()
+                        .expect("Failed to lock input manager");
+                    input_manager.update();
+                }
+                {
+                    let _timer_span = tracing::debug_span!("timer_update").entered();
+                    let mut timer = self.timer.write().expect("Failed to lock timer");
+                    timer.update();
+                }
+
+                // Créer ou mettre à jour le contexte d'application
+                let window_context = self.app_contexts.get(&id).unwrap().clone();
+                let scene_manager = self.scene_manager.get_mut(&id).unwrap();
+
+                // Utiliser le contexte partagé pour les scènes
+                {
+                    let mut context = window_context.borrow_mut();
+
+                    {
+                        let _scene_span =
+                            tracing::info_span!("scene_loading_if_not_loaded").entered();
+                        scene_manager
+                            .load_scene_if_not_loaded(&mut context)
+                            .unwrap();
+                    }
+
+                    if let Some(scene) = scene_manager.current_scene_mut() {
+                        {
+                            let _update_span = tracing::debug_span!("scene_update").entered();
+                            scene.update(&mut context).unwrap();
+                        }
+
+                        let acquire_future = {
+                            let _acquire_span = tracing::debug_span!("acquire_swapchain").entered();
+                            context.with_renderer_mut(|renderer| {
+                                renderer.acquire(None, |_| {}).unwrap()
+                            })
+                        };
+
+                        let acquire_future = {
+                            let _render_span = tracing::debug_span!("scene_render").entered();
+                            scene.render(acquire_future, &mut context).unwrap()
+                        };
+
+                        {
+                            let _present_span = tracing::debug_span!("present_frame").entered();
+                            context.with_renderer_mut(|renderer| {
+                                renderer.present(acquire_future, true);
+                            });
+                        }
+                    } else {
+                        tracing::warn!("No current scene found for update!");
+                    }
+                }
+
+                {
+                    let _gui_span = tracing::debug_span!("request_redraw").entered();
+                    let mut window_context = window_context.borrow_mut();
+                    window_context.with_renderer_mut(|renderer| {
+                        renderer.window().request_redraw();
+                    });
+                }
+            }
+            _ => {}
+        }
+    }
+
+    fn user_event(&mut self, event_loop: &ActiveEventLoop, event: UserEvent) {
+        match event {
+            UserEvent::CursorGrabMode(window_id, grab) => {
+                let vulkano_windows = self.vulkano_windows.borrow();
+                let window = vulkano_windows.get_window(window_id).unwrap();
+                if let Err(e) = window.set_cursor_grab(grab) {
+                    tracing::error!("Failed to set cursor grab: {}", e);
+                }
+            }
+            UserEvent::CursorVisible(window_id, visible) => {
+                let vulkano_windows = self.vulkano_windows.borrow();
+                let window = vulkano_windows.get_window(window_id).unwrap();
+                window.set_cursor_visible(visible);
+            }
+            UserEvent::ChangeScene(window_id, scene) => {
+                if let Some(scene_manager) = self.scene_manager.get_mut(&window_id) {
+                    scene_manager.load_scene(scene);
+                }
+            }
+            UserEvent::ChangeResolution(window_id, width, height) => {
+                let mut vulkano_windows = self.vulkano_windows.borrow_mut();
+                let window = vulkano_windows.get_window(window_id).unwrap();
+                let _ = window.request_inner_size(winit::dpi::LogicalSize::new(width, height));
+                let renderer = vulkano_windows.get_renderer_mut(window_id).unwrap();
+                renderer.resize();
+                tracing::trace!(
+                    "Resolution changed to {}x{} for window {:?}",
+                    width,
+                    height,
+                    window_id
+                );
+            }
+            UserEvent::Exit(window_id) => {
+                tracing::trace!("Exit requested for window {:?}", window_id);
+                event_loop.exit();
+            }
+        }
+    }
+}
diff --git a/src/core/app/user_event.rs b/src/core/app/user_event.rs
new file mode 100644
index 0000000..21b70f1
--- /dev/null
+++ b/src/core/app/user_event.rs
@@ -0,0 +1,11 @@
+use winit::window::{CursorGrabMode, WindowId};
+
+use crate::core::scene::Scene;
+
+pub enum UserEvent {
+    CursorGrabMode(WindowId, CursorGrabMode),
+    CursorVisible(WindowId, bool),
+    ChangeScene(WindowId, Box<dyn Scene>),
+    ChangeResolution(WindowId, f32, f32),
+    Exit(WindowId),
+}
diff --git a/src/core/input/cache.rs b/src/core/input/cache.rs
new file mode 100644
index 0000000..b9d562c
--- /dev/null
+++ b/src/core/input/cache.rs
@@ -0,0 +1,86 @@
+use std::{
+    collections::HashMap,
+    hash::Hash,
+    ops::{Add, AddAssign, Sub},
+};
+
+use winit::event::ElementState;
+
+pub struct CachedElementState<K: Eq + Hash> {
+    cache: HashMap<K, ElementState>,
+}
+
+impl<K: Eq + Hash> Default for CachedElementState<K> {
+    fn default() -> Self {
+        Self {
+            cache: HashMap::new(),
+        }
+    }
+}
+
+impl<K: Eq + Hash> CachedElementState<K> {
+    pub fn set_key_state(&mut self, key: K, state: ElementState) -> Option<ElementState> {
+        let key_state = self.cache.get(&key);
+        let new_key_state = match key_state {
+            Some(old) => match state {
+                ElementState::Pressed => match old {
+                    ElementState::Released => Some(ElementState::Pressed),
+                    ElementState::Pressed => None,
+                },
+                ElementState::Released => match old {
+                    ElementState::Released => None,
+                    ElementState::Pressed => Some(ElementState::Released),
+                },
+            },
+            None => match state {
+                ElementState::Pressed => Some(ElementState::Pressed),
+                ElementState::Released => Some(ElementState::Released),
+            },
+        };
+        if let Some(new_key_state) = new_key_state {
+            self.cache.insert(key, new_key_state);
+        }
+        new_key_state
+    }
+}
+
+#[derive(Default)]
+pub struct CachedMovement<T>
+where
+    T: Sub<Output = T> + Add<Output = T> + Default + Copy,
+{
+    pub old_value: Option<T>,
+    pub value: T,
+}
+
+impl<T> CachedMovement<T>
+where
+    T: Sub<Output = T> + Add<Output = T> + Default + Copy,
+{
+    pub fn set_value(&mut self, value: T) {
+        self.value = value;
+    }
+
+    pub fn reset(&mut self) -> T {
+        match self.old_value.as_ref() {
+            Some(old_value) => {
+                let diff = self.value - *old_value;
+                self.old_value = Some(self.value);
+                diff
+            }
+            None => {
+                self.old_value = Some(self.value);
+                T::default()
+            }
+        }
+    }
+}
+
+impl<T> AddAssign<T> for CachedMovement<T>
+where
+    T: Add<Output = T> + Sub<Output = T> + Default + Copy,
+{
+    fn add_assign(&mut self, rhs: T) {
+        self.value = self.value + rhs;
+    }
+}
diff --git a/src/core/input/mod.rs b/src/core/input/mod.rs
new file mode 100644
index 0000000..f4a1642
--- /dev/null
+++ b/src/core/input/mod.rs
@@ -0,0 +1,97 @@
+use std::collections::HashMap;
+
+use cache::{CachedElementState, CachedMovement};
+use virtual_input::VirtualInput;
+use winit::{
+    event::{DeviceEvent, MouseButton, MouseScrollDelta, WindowEvent},
+    keyboard::PhysicalKey,
+};
+
+mod cache;
+mod virtual_binding;
+mod virtual_input;
+mod virtual_state;
+pub use virtual_binding::{AxisDirection, VirtualBinding};
+
+#[derive(Default)]
+pub struct InputManager {
+    keys_state: CachedElementState<PhysicalKey>,
+    mouse_buttons_state: CachedElementState<MouseButton>,
+    mouse_position_delta: CachedMovement<glam::Vec2>,
+    mouse_wheel_delta: CachedMovement<glam::Vec2>,
+    virtual_input: VirtualInput,
+}
+
+impl std::fmt::Debug for InputManager {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        f.debug_struct("InputManager")
+            .field("virtual_input", &self.virtual_input)
+            .finish()
+    }
+}
+
+impl InputManager {
+    pub fn new(input_mapping: HashMap<String, Vec<VirtualBinding>>) -> Self {
+        let mut input_manager = InputManager::default();
+        for (value_name, bindings) in input_mapping {
+            input_manager.add_virtual_bindings(value_name, bindings);
+        }
+        input_manager
+    }
+
+    pub fn process_device_event(&mut self, event: &DeviceEvent) {
+        if let DeviceEvent::MouseMotion { delta, .. } = event {
+            self.mouse_position_delta += glam::Vec2::new(delta.0 as f32, delta.1 as f32);
+        }
+    }
+
+    pub fn process_window_event(&mut self, event: &WindowEvent) {
+        match event {
+            WindowEvent::AxisMotion { axis, value, .. } => {
+                self.virtual_input.update_axis_binding(*axis, *value as f32);
+            }
+            WindowEvent::KeyboardInput { event, .. } => {
+                let new_key_state = self
+                    .keys_state
+                    .set_key_state(event.physical_key, event.state);
+                if let Some(new_key_state) = new_key_state {
+                    self.virtual_input
+                        .update_key_binding(event.physical_key, new_key_state);
+                }
+            }
+            WindowEvent::MouseInput { button, state, .. } => {
+                let new_mouse_button_state =
+                    self.mouse_buttons_state.set_key_state(*button, *state);
+                if let Some(new_mouse_button_state) = new_mouse_button_state {
+                    self.virtual_input
+                        .update_mouse_button_binding(*button, new_mouse_button_state);
+                }
+            }
+            WindowEvent::MouseWheel { delta, .. } => {
+                self.mouse_wheel_delta += match delta {
+                    MouseScrollDelta::PixelDelta(position) => {
+                        glam::Vec2::new(position.x as f32, position.y as f32)
+                    }
+                    MouseScrollDelta::LineDelta(x, y) => glam::Vec2::new(*x, *y),
+                };
+            }
+            _ => {}
+        }
+    }
+
+    /// Updates deltas before running update
+    pub fn update(&mut self) {
+        self.virtual_input
+            .update_mouse_move_binding(&self.mouse_position_delta.reset());
+        self.virtual_input
+            .update_mouse_wheel_binding(&self.mouse_wheel_delta.reset());
+    }
+
+    pub fn get_virtual_input_state(&self, value_name: &str) -> f32 {
+        self.virtual_input.get_state(value_name)
+    }
+
+    fn add_virtual_bindings(&mut self, value_name: String, bindings: Vec<VirtualBinding>) {
+        self.virtual_input.add_bindings(value_name, bindings);
+    }
+}
diff --git a/src/core/input/virtual_binding.rs b/src/core/input/virtual_binding.rs
new file mode 100644
index 0000000..c11dd48
--- /dev/null
+++ b/src/core/input/virtual_binding.rs
@@ -0,0 +1,30 @@
+use winit::{
+    event::{AxisId, MouseButton},
+    keyboard::PhysicalKey,
+};
+
+#[derive(Clone)]
+pub enum AxisDirection {
+    Normal,
+    Invert,
+}
+
+impl From<&AxisDirection> for f32 {
+    fn from(direction: &AxisDirection) -> Self {
+        match direction {
+            AxisDirection::Normal => 1.0,
+            AxisDirection::Invert => -1.0,
+        }
+    }
+}
+
+#[derive(Clone)]
+pub enum VirtualBinding {
+    Keyboard(PhysicalKey, AxisDirection),
+    Axis(AxisId, AxisDirection, f32), // f32 deadzone
+    MouseX(AxisDirection),
+    MouseY(AxisDirection),
+    MouseWheelX(AxisDirection),
+    MouseWheelY(AxisDirection),
+    MouseButton(MouseButton, AxisDirection),
+}
diff --git a/src/core/input/virtual_input.rs b/src/core/input/virtual_input.rs
new file mode 100644
index 0000000..999baed
--- /dev/null
+++ b/src/core/input/virtual_input.rs
@@ -0,0 +1,150 @@
+use std::{
+    collections::HashMap,
+    sync::{Arc, RwLock},
+};
+
+use winit::{
+    event::{AxisId, ElementState, MouseButton},
+    keyboard::PhysicalKey,
+};
+
+use super::{
+    virtual_binding::VirtualBinding,
+    virtual_state::{VirtualBindingState, VirtualInputState},
+};
+
+#[derive(Default)]
+pub struct VirtualInput {
+    // Global states
+    states: HashMap<String, Arc<RwLock<VirtualInputState>>>,
+
+    // Per kind of input states to keep complexity low during state updates
+    states_by_key: HashMap<PhysicalKey, Vec<Arc<RwLock<VirtualInputState>>>>,
+    mouse_move_states: Vec<Arc<RwLock<VirtualInputState>>>,
+    mouse_wheel_states: Vec<Arc<RwLock<VirtualInputState>>>,
+    mouse_button_states: HashMap<MouseButton, Vec<Arc<RwLock<VirtualInputState>>>>,
+    axis_states: HashMap<AxisId, Vec<Arc<RwLock<VirtualInputState>>>>,
+}
+
+impl std::fmt::Debug for VirtualInput {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        let mut debug = f.debug_struct("VirtualInput");
+
+        for (name, state) in &self.states {
+            let value = state.read().expect("Poisoned lock for debug").value;
+            debug.field(name, &value);
+        }
+
+        debug.finish()
+    }
+}
+
+impl VirtualInput {
+    pub fn get_state(&self, value_name: &str) -> f32 {
+        self.states
+            .get(value_name)
+            .map(|state| state.read().expect("Poisoned lock for get state").value)
+            .unwrap_or(0.0)
+    }
+
+    pub fn add_bindings(&mut self, value_name: String, new_bindings: Vec<VirtualBinding>) {
+        let state = self
+            .states
+            .entry(value_name)
+            .or_insert(Arc::new(RwLock::new(VirtualInputState {
+                value: 0.0,
+                bindings: Vec::new(),
+            })));
+
+        for binding in &new_bindings {
+            match binding {
+                VirtualBinding::Keyboard(key, _) => {
+                    self.states_by_key
+                        .entry(*key)
+                        .or_default()
+                        .push(state.clone());
+                }
+                VirtualBinding::MouseX(_) | VirtualBinding::MouseY(_) => {
+                    self.mouse_move_states.push(state.clone());
+                }
+                VirtualBinding::MouseButton(button, _) => {
+                    self.mouse_button_states
+                        .entry(*button)
+                        .or_default()
+                        .push(state.clone());
+                }
+                VirtualBinding::MouseWheelX(_) | VirtualBinding::MouseWheelY(_) => {
+                    self.mouse_wheel_states.push(state.clone());
+                }
+                VirtualBinding::Axis(axis, _, _) => {
+                    self.axis_states
+                        .entry(*axis)
+                        .or_default()
+                        .push(state.clone());
+                }
+            }
+        }
+
+        state
+            .write()
+            .expect("Poisoned lock for add bindings")
+            .bindings
+            .extend(new_bindings.iter().map(|b| VirtualBindingState {
+                value: 0.0,
+                binding: b.clone(),
+            }));
+    }
+
+    pub(super) fn update_key_binding(&mut self, key: PhysicalKey, key_state: ElementState) {
+        let states = self.states_by_key.get_mut(&key);
+
+        if let Some(states) = states {
+            for state in states {
+                let mut state = state.write().expect("Poisoned lock for key update");
+                state.update_from_key(key, key_state);
+            }
+        }
+    }
+
+    pub(super) fn update_mouse_move_binding(&mut self, delta: &glam::Vec2) {
+        for state in &mut self.mouse_move_states {
+            let mut state = state.write().expect("Poisoned lock for mouse move update");
+            state.update_from_mouse(delta);
+        }
+    }
+
+    pub(super) fn update_mouse_wheel_binding(&mut self, delta: &glam::Vec2) {
+        for state in &mut self.mouse_wheel_states {
+            let mut state = state.write().expect("Poisoned lock for mouse wheel update");
+            state.update_from_mouse_wheel(delta);
+        }
+    }
+
+    pub(super) fn update_mouse_button_binding(
+        &mut self,
+        button: MouseButton,
+        button_state: ElementState,
+    ) {
+        let states = self.mouse_button_states.get_mut(&button);
+
+        if let Some(states) = states {
+            for state in states {
+                let mut state = state
+                    .write()
+                    .expect("Poisoned lock for mouse button update");
+                state.update_from_mouse_button(button, button_state);
+            }
+        }
+    }
+
+    pub(super) fn update_axis_binding(&mut self, axis: AxisId, axis_state: f32) {
+        let states = self.axis_states.get_mut(&axis);
+
+        if let Some(states) = states {
+            for state in states {
+                let mut state = state.write().expect("Poisoned lock for axis update");
+                state.update_from_axis(axis, axis_state);
+            }
+        }
+    }
+}
diff --git a/src/core/input/virtual_state.rs b/src/core/input/virtual_state.rs
new file mode 100644
index 0000000..2cfd098
--- /dev/null
+++ b/src/core/input/virtual_state.rs
@@ -0,0 +1,105 @@
+use winit::{
+    event::{AxisId, ElementState, MouseButton},
+    keyboard::PhysicalKey,
+};
+
+use super::virtual_binding::VirtualBinding;
+
+pub struct VirtualBindingState {
+    pub value: f32,
+    pub binding: VirtualBinding,
+}
+
+pub struct VirtualInputState {
+    pub value: f32,
+    pub bindings: Vec<VirtualBindingState>,
+}
+
+impl VirtualInputState {
+    pub fn update_from_key(&mut self, key: PhysicalKey, key_state: ElementState) {
+        let mut new_value = 0.0;
+        for binding in &mut self.bindings {
+            if let VirtualBinding::Keyboard(binding_key, direction) = &binding.binding {
+                if binding_key == &key {
+                    if key_state == ElementState::Pressed {
+                        binding.value += f32::from(direction);
+                    } else {
+                        binding.value = 0.0;
+                    }
+                }
+            }
+            new_value += binding.value;
+        }
+        self.value = new_value;
+    }
+
+    pub fn update_from_mouse(&mut self, delta: &glam::Vec2) {
+        let mut new_value = 0.0;
+        for binding in &mut self.bindings {
+            match &binding.binding {
+                VirtualBinding::MouseX(direction) => {
+                    binding.value = f32::from(direction) * delta.x;
+                }
+                VirtualBinding::MouseY(direction) => {
+                    binding.value = f32::from(direction) * delta.y;
+                }
+                _ => {}
+            }
+            new_value += binding.value;
+        }
+        self.value = new_value;
+    }
+
+    pub fn update_from_mouse_wheel(&mut self, delta: &glam::Vec2) {
+        let mut new_value = 0.0;
+        for binding in &mut self.bindings {
+            match &binding.binding {
+                VirtualBinding::MouseWheelX(direction) => {
+                    binding.value = f32::from(direction) * delta.x;
+                }
+                VirtualBinding::MouseWheelY(direction) => {
+                    binding.value = f32::from(direction) * delta.y;
+                }
+                _ => {}
+            }
+            new_value += binding.value;
+        }
+        self.value = new_value;
+    }
+
+    pub fn update_from_mouse_button(&mut self, button: MouseButton, button_state: ElementState) {
+        let mut new_value = 0.0;
+        for binding in &mut self.bindings {
+            if let VirtualBinding::MouseButton(binding_button, direction) = &binding.binding {
+                if binding_button == &button {
+                    if button_state == ElementState::Pressed {
+                        binding.value = f32::from(direction);
+                    } else {
+                        binding.value = 0.0;
+                    }
+                }
+            }
+            new_value += binding.value;
+        }
+        self.value = new_value;
+    }
+
+    pub fn update_from_axis(&mut self, axis: AxisId, axis_state: f32) {
+        let mut new_value = 0.0;
+        for binding in &mut self.bindings {
+            if let VirtualBinding::Axis(binding_axis, direction, deadzone) = &binding.binding {
+                if binding_axis == &axis {
+                    binding.value =
+                        f32::from(direction) * process_axis_deadzone(axis_state, *deadzone);
+                }
+            }
+            new_value += binding.value;
+        }
+        self.value = new_value;
+    }
+}
+
+#[inline]
+fn process_axis_deadzone(value: f32, deadzone: f32) -> f32 {
+    if value.abs() < deadzone { 0.0 } else { value }
+}
diff --git a/src/core/mod.rs b/src/core/mod.rs
new file mode 100644
index 0000000..237496d
--- /dev/null
+++ b/src/core/mod.rs
@@ -0,0 +1,5 @@
+pub mod app;
+pub mod input;
+pub mod render;
+pub mod scene;
+pub mod timer;
diff --git a/src/core/render/material_manager.rs b/src/core/render/material_manager.rs
new file mode 100644
index 0000000..008ad07
--- /dev/null
+++ b/src/core/render/material_manager.rs
@@ -0,0 +1,246 @@
+use std::{
+    any::TypeId,
+    error::Error,
+    sync::{Arc, RwLock, RwLockReadGuard},
+};
+
+use vulkano::{device::Device, format::Format, memory::allocator::StandardMemoryAllocator};
+
+pub trait Pipeline {
+    fn load(
+        &mut self,
+        device: &Device,
+        memory_allocator: &StandardMemoryAllocator,
+        swapchain_format: Format,
+        depth_format: Format,
+    ) -> Result<(), Box<dyn Error>>;
+}
+
+pub trait Material {
+    fn pipeline_type_id() -> TypeId
+    where
+        Self: Sized;
+
+    fn load(
+        &mut self,
+        device: &Device,
+        memory_allocator: &StandardMemoryAllocator,
+    ) -> Result<(), Box<dyn Error>>;
+}
+
+pub enum MaterialState {
+    Loading,
+    Loaded,
+}
+
+pub enum MaterialError {
+    PipelineNotFound,
+}
+
+pub struct MaterialManager {
+    device: Arc<Device>,
+    memory_allocator: Arc<StandardMemoryAllocator>,
+    swapchain_format: Format,
+    depth_format: Format,
+
+    pipelines_id: Arc<RwLock<Vec<TypeId>>>,
+    pipelines_state: Arc<RwLock<Vec<Arc<RwLock<MaterialState>>>>>,
+    pipelines: Arc<RwLock<Vec<Arc<RwLock<dyn Pipeline>>>>>,
+
+    materials_id: Arc<RwLock<Vec<TypeId>>>,
+    materials_pipeline_id: Arc<RwLock<Vec<TypeId>>>,
+    materials_pipeline: Arc<RwLock<Vec<Arc<RwLock<dyn Pipeline>>>>>,
+    materials_pipeline_state: Arc<RwLock<Vec<Arc<RwLock<MaterialState>>>>>,
+    materials_state: Arc<RwLock<Vec<Arc<RwLock<MaterialState>>>>>,
+    materials: Arc<RwLock<Vec<Arc<RwLock<dyn Material>>>>>,
+}
+
+impl MaterialManager {
+    pub fn new(
+        device: Arc<Device>,
+        memory_allocator: Arc<StandardMemoryAllocator>,
+        swapchain_format: Format,
+        depth_format: Format,
+    ) -> Self {
+        Self {
+            device,
+            memory_allocator,
+            swapchain_format,
+            depth_format,
+            pipelines_id: Arc::new(RwLock::new(Vec::new())),
+            pipelines_state: Arc::new(RwLock::new(Vec::new())),
+            pipelines: Arc::new(RwLock::new(Vec::new())),
+            materials_id: Arc::new(RwLock::new(Vec::new())),
+            materials_pipeline_id: Arc::new(RwLock::new(Vec::new())),
+            materials_pipeline: Arc::new(RwLock::new(Vec::new())),
+            materials_pipeline_state: Arc::new(RwLock::new(Vec::new())),
+            materials_state: Arc::new(RwLock::new(Vec::new())),
+            materials: Arc::new(RwLock::new(Vec::new())),
+        }
+    }
+
+    pub fn add_pipeline<P: Pipeline + Default + 'static>(&self) {
+        let type_id = TypeId::of::<P>();
+        let pipeline = Arc::new(RwLock::new(P::default()));
+
+        let mut pipelines_id = self.pipelines_id.write().unwrap();
+        let mut pipelines_state = self.pipelines_state.write().unwrap();
+        let mut pipelines = self.pipelines.write().unwrap();
+
+        pipelines_id.push(type_id);
+        pipelines_state.push(Arc::new(RwLock::new(MaterialState::Loading)));
+        pipelines.push(pipeline.clone());
+    }
+
+    pub fn add_material<M: Material + Default + 'static>(&self) -> Result<(), MaterialError> {
+        let pipeline_id = M::pipeline_type_id();
+
+        let pipeline_result = {
+            let pipelines_id = self.pipelines_id.read().unwrap();
+            let pipelines_state = self.pipelines_state.read().unwrap();
+            let pipelines = self.pipelines.read().unwrap();
+
+            pipelines_id
+                .iter()
+                .zip(pipelines.iter())
+                .zip(pipelines_state.iter())
+                .find(|((id, _), _)| *id == &pipeline_id)
+                .map(|((_, pipeline), state)| (pipeline.clone(), state.clone()))
+        };
+
+        let (pipeline, pipeline_state) = match pipeline_result {
+            Some(pipeline) => pipeline,
+            None => {
+                tracing::error!(
+                    "Pipeline with id {pipeline_id:?} not found, please add it before adding a material"
+                );
+                return Err(MaterialError::PipelineNotFound);
+            }
+        };
+
+        let type_id = TypeId::of::<M>();
+
+        let mut materials_id = self.materials_id.write().unwrap();
+        let mut materials_pipeline_id = self.materials_pipeline_id.write().unwrap();
+        let mut materials_pipeline = self.materials_pipeline.write().unwrap();
+        let mut materials_pipeline_state = self.materials_pipeline_state.write().unwrap();
+        let mut materials_state = self.materials_state.write().unwrap();
+        let mut materials = self.materials.write().unwrap();
+
+        materials_id.push(type_id);
+        materials_pipeline_id.push(pipeline_id);
+        materials_pipeline.push(pipeline);
+        materials_pipeline_state.push(pipeline_state);
+        materials_state.push(Arc::new(RwLock::new(MaterialState::Loading)));
+        materials.push(Arc::new(RwLock::new(M::default())));
+
+        Ok(())
+    }
+
+    pub fn update_swapchain_format(&mut self, swapchain_format: Format) {
+        if self.swapchain_format == swapchain_format {
+            return;
+        }
+
+        self.swapchain_format = swapchain_format;
+        self.mark_all_pipelines_as_loading();
+    }
+
+    fn mark_all_pipelines_as_loading(&self) {
+        let pipelines_state = self.pipelines_state.write().unwrap();
+
+        for state in pipelines_state.iter() {
+            let mut state = state.write().unwrap();
+            *state = MaterialState::Loading;
+        }
+    }
+
+    fn load_pipelines(&self) {
+        let pipelines_state = self.pipelines_state.read().unwrap();
+        let pipelines = self.pipelines.read().unwrap();
+
+        let iter = pipelines_state
+            .iter()
+            .zip(pipelines.iter())
+            .filter(|(state, _)| {
+                let state = state.read().unwrap();
+                matches!(*state, MaterialState::Loading)
+            });
+
+        for (state, pipeline) in iter {
+            let mut pipeline = pipeline.write().unwrap();
+            let result = pipeline.load(
+                &self.device,
+                &self.memory_allocator,
+                self.swapchain_format,
+                self.depth_format,
+            );
+
+            match result {
+                Ok(_) => {
+                    let mut state = state.write().unwrap();
+                    *state = MaterialState::Loaded;
+                }
+                Err(e) => {
+                    tracing::error!("Failed to load pipeline: {e}");
+                }
+            }
+        }
+    }
+
+    fn load_materials(&self) {
+        let materials_state = self.materials_state.read().unwrap();
+        let materials = self.materials.read().unwrap();
+
+        let iter = materials_state
+            .iter()
+            .zip(materials.iter())
+            .filter(|(state, _)| {
+                let state = state.read().unwrap();
+                matches!(*state, MaterialState::Loading)
+            });
+
+        for (state, material) in iter {
+            let mut material = material.write().unwrap();
+            let result = material.load(&self.device, &self.memory_allocator);
+
+            match result {
+                Ok(_) => {
+                    let mut state = state.write().unwrap();
+                    *state = MaterialState::Loaded;
+                }
+                Err(e) => {
+                    tracing::error!("Failed to load material: {e}");
+                }
+            }
+        }
+    }
+
+    fn render_materials<F>(&self, f: F)
+    where
+        F: Fn(RwLockReadGuard<'_, dyn Material>, RwLockReadGuard<'_, dyn Pipeline>),
+    {
+        let materials = self.materials.read().unwrap();
+        let materials_state = self.materials_state.read().unwrap();
+        let materials_pipeline = self.materials_pipeline.read().unwrap();
+        let materials_pipeline_state = self.materials_pipeline_state.read().unwrap();
+
+        materials
+            .iter()
+            .zip(materials_state.iter())
+            .zip(materials_pipeline.iter())
+            .zip(materials_pipeline_state.iter())
+            .filter(|(((_, material_state), _), pipeline_state)| {
+                let material_state = material_state.read().unwrap();
+                let pipeline_state = pipeline_state.read().unwrap();
+                matches!(*material_state, MaterialState::Loaded)
+                    && matches!(*pipeline_state, MaterialState::Loaded)
+            })
+            .for_each(|(((material, _), pipeline), _)| {
+                let material = material.read().unwrap();
+                let pipeline = pipeline.read().unwrap();
+
+                f(material, pipeline);
+            });
+    }
+}
diff --git a/src/core/render/mod.rs b/src/core/render/mod.rs
new file mode 100644
index 0000000..ec7ac54
--- /dev/null
+++ b/src/core/render/mod.rs
@@ -0,0 +1,5 @@
+pub mod material_manager;
+pub mod primitives;
+pub mod render_pass_manager;
+pub mod texture;
+pub mod vulkan_context;
diff --git a/src/core/render/primitives/camera.rs b/src/core/render/primitives/camera.rs
new file mode 100644
index 0000000..f27622a
--- /dev/null
+++ b/src/core/render/primitives/camera.rs
@@ -0,0 +1,122 @@
+use std::{f32::consts::FRAC_PI_2, sync::Arc};
+
+use glam::{Mat4, Vec3, Vec4};
+use vulkano::{
+    Validated,
+    buffer::{AllocateBufferError, Subbuffer},
+    memory::allocator::StandardMemoryAllocator,
+};
+
+use crate::core::{input::InputManager, timer::Timer};
+
+use super::mvp::Mvp;
+
+// See docs/OPENGL_VULKAN_DIFF.md
+const OPENGL_TO_VULKAN_Y_AXIS_FLIP: Mat4 = Mat4 {
+    x_axis: Vec4::new(1.0, 0.0, 0.0, 0.0),
+    y_axis: Vec4::new(0.0, -1.0, 0.0, 0.0),
+    z_axis: Vec4::new(0.0, 0.0, 1.0, 0.0),
+    w_axis: Vec4::new(0.0, 0.0, 0.0, 1.0),
+};
+
+#[derive(Default)]
+pub struct Camera3D {
+    projection: Mat4,
+
+    position: Vec3,
+    rotation: Vec3,
+    aspect_ratio: f32,
+    fov: f32,
+    near: f32,
+    far: f32,
+}
+
+impl Camera3D {
+    pub fn new(aspect_ratio: f32, fov: f32, near: f32, far: f32) -> Self {
+        Self {
+            projection: Mat4::perspective_rh(fov, aspect_ratio, near, far),
+            position: Vec3::ZERO,
+            rotation: Vec3::ZERO,
+            aspect_ratio,
+            fov,
+            near,
+            far,
+        }
+    }
+
+    pub fn update(
+        &mut self,
+        input_manager: &InputManager,
+        timer: &Timer,
+        movement_speed: f32,
+        camera_sensitivity: f32,
+        window_aspect_ratio: f32,
+    ) {
+        // Process camera rotation
+        let camera_delta = camera_sensitivity * timer.delta_time();
+        self.rotation += Vec3::new(
+            (input_manager.get_virtual_input_state("mouse_y") * camera_delta).to_radians(),
+            (input_manager.get_virtual_input_state("mouse_x") * camera_delta).to_radians(),
+            0.0,
+        );
+
+        if self.rotation.x > FRAC_PI_2 {
+            self.rotation = Vec3::new(FRAC_PI_2, self.rotation.y, 0.0);
+        }
+
+        if self.rotation.x < -FRAC_PI_2 {
+            self.rotation = Vec3::new(-FRAC_PI_2, self.rotation.y, 0.0);
+        }
+
+        let movement_delta = movement_speed * timer.delta_time();
+
+        let (yaw_sin, yaw_cos) = self.rotation.y.sin_cos();
+        let forward = Vec3::new(yaw_cos, 0.0, yaw_sin).normalize();
+        let right = Vec3::new(-yaw_sin, 0.0, yaw_cos).normalize();
+
+        let tx = input_manager.get_virtual_input_state("move_right") * movement_delta;
+        self.position += tx * right;
+
+        let tz = input_manager.get_virtual_input_state("move_forward") * movement_delta;
+        self.position += tz * forward;
+
+        if self.aspect_ratio != window_aspect_ratio {
+            self.aspect_ratio = window_aspect_ratio;
+            self.projection =
+                Mat4::perspective_rh(self.fov, self.aspect_ratio, self.near, self.far);
+        }
+    }
+
+    pub fn set_projection(&mut self, projection: Mat4) {
+        self.projection = projection;
+    }
+
+    pub fn get_position(&self) -> Vec3 {
+        self.position
+    }
+
+    pub fn get_rotation(&self) -> Vec3 {
+        self.rotation
+    }
+
+    pub fn create_buffer(
+        &self,
+        memory_allocator: &Arc<StandardMemoryAllocator>,
+    ) -> Result<Subbuffer<[Mvp]>, Validated<AllocateBufferError>> {
+        let (sin_pitch, cos_pitch) = self.rotation.x.sin_cos();
+        let (sin_yaw, cos_yaw) = self.rotation.y.sin_cos();
+
+        let view_matrix = Mat4::look_to_rh(
+            self.position,
+            Vec3::new(cos_pitch * cos_yaw, sin_pitch, cos_pitch * sin_yaw).normalize(),
+            Vec3::Y,
+        );
+
+        Mvp {
+            model: OPENGL_TO_VULKAN_Y_AXIS_FLIP.to_cols_array_2d(),
+            view: view_matrix.to_cols_array_2d(),
+            projection: self.projection.to_cols_array_2d(),
+        }
+        .into_buffer(memory_allocator)
+    }
+}
diff --git a/src/core/render/primitives/mod.rs b/src/core/render/primitives/mod.rs
new file mode 100644
index 0000000..606c12d
--- /dev/null
+++ b/src/core/render/primitives/mod.rs
@@ -0,0 +1,4 @@
+pub mod camera;
+pub mod mvp;
+pub mod transform;
+pub mod vertex;
diff --git a/src/core/render/primitives/mvp.rs b/src/core/render/primitives/mvp.rs
new file mode 100644
index 0000000..192c67b
--- /dev/null
+++ b/src/core/render/primitives/mvp.rs
@@ -0,0 +1,36 @@
+use std::sync::Arc;
+
+use vulkano::Validated;
+use vulkano::buffer::{
+    AllocateBufferError, Buffer, BufferContents, BufferCreateInfo, BufferUsage, Subbuffer,
+};
+use vulkano::memory::allocator::{AllocationCreateInfo, MemoryTypeFilter, StandardMemoryAllocator};
+
+#[derive(BufferContents, Clone, Copy)]
+#[repr(C)]
+pub struct Mvp {
+    pub model: [[f32; 4]; 4],
+    pub view: [[f32; 4]; 4],
+    pub projection: [[f32; 4]; 4],
+}
+
+impl Mvp {
+    pub fn into_buffer(
+        self,
+        memory_allocator: &Arc<StandardMemoryAllocator>,
+    ) -> Result<Subbuffer<[Mvp]>, Validated<AllocateBufferError>> {
+        Buffer::from_iter(
+            memory_allocator.clone(),
+            BufferCreateInfo {
+                usage: BufferUsage::UNIFORM_BUFFER,
+                ..Default::default()
+            },
+            AllocationCreateInfo {
+                memory_type_filter: MemoryTypeFilter::PREFER_DEVICE
+                    | MemoryTypeFilter::HOST_SEQUENTIAL_WRITE,
+                ..Default::default()
+            },
+            [self],
+        )
+    }
+}
diff --git a/src/core/render/primitives/transform.rs b/src/core/render/primitives/transform.rs
new file mode 100644
index 0000000..4d870b0
--- /dev/null
+++ b/src/core/render/primitives/transform.rs
@@ -0,0 +1,81 @@
+use std::sync::Arc;
+
+use glam::{Mat4, Quat, Vec3};
+use vulkano::{
+    Validated,
+    buffer::{
+        AllocateBufferError, Buffer, BufferContents, BufferCreateInfo, BufferUsage, Subbuffer,
+    },
+    memory::allocator::{AllocationCreateInfo, MemoryTypeFilter, StandardMemoryAllocator},
+    pipeline::graphics::vertex_input::Vertex,
+};
+
+pub struct Transform {
+    pub position: Vec3,
+    pub rotation: Quat,
+    pub scale: Vec3,
+}
+
+#[derive(BufferContents, Vertex)]
+#[repr(C)]
+pub struct TransformRaw {
+    #[format(R32G32B32A32_SFLOAT)]
+    pub model: [[f32; 4]; 4],
+}
+
+impl Default for Transform {
+    fn default() -> Self {
+        Self {
+            position: Vec3::default(),
+            rotation: Quat::default(),
+            scale: Vec3::ONE,
+        }
+    }
+}
+
+impl Transform {
+    pub fn rotate(&mut self, rotation: Quat) {
+        self.rotation *= rotation;
+    }
+
+    pub fn translate(&mut self, translation: Vec3) {
+        self.position += translation;
+    }
+
+    pub fn scale(&mut self, scale: Vec3) {
+        self.scale *= scale;
+    }
+
+    pub fn to_raw_tranform(&self) -> TransformRaw {
+        TransformRaw {
+            model: (Mat4::from_translation(self.position)
+                * Mat4::from_quat(self.rotation)
+                * Mat4::from_scale(self.scale))
+            .to_cols_array_2d(),
+        }
+    }
+
+    pub fn create_buffer(
+        memory_allocator: &Arc<StandardMemoryAllocator>,
+        transforms: &[Transform],
+    ) -> Result<Subbuffer<[TransformRaw]>, Validated<AllocateBufferError>> {
+        let transform_raws: Vec<TransformRaw> =
+            transforms.iter().map(|t| t.to_raw_tranform()).collect();
+
+        let buffer = Buffer::from_iter(
+            memory_allocator.clone(),
+            BufferCreateInfo {
+                usage: BufferUsage::VERTEX_BUFFER,
+                ..Default::default()
+            },
+            AllocationCreateInfo {
+                memory_type_filter: MemoryTypeFilter::PREFER_DEVICE
+                    | MemoryTypeFilter::HOST_SEQUENTIAL_WRITE,
+                ..Default::default()
+            },
+            transform_raws,
+        )?;
+
+        Ok(buffer)
+    }
+}
diff --git a/src/core/render/primitives/vertex.rs b/src/core/render/primitives/vertex.rs
new file mode 100644
index 0000000..166ac44
--- /dev/null
+++ b/src/core/render/primitives/vertex.rs
@@ -0,0 +1,22 @@
+use vulkano::buffer::BufferContents;
+use vulkano::pipeline::graphics::vertex_input::Vertex;
+
+#[derive(BufferContents, Vertex)]
+#[repr(C)]
+pub struct Vertex2D {
+    #[format(R32G32_SFLOAT)]
+    pub position: [f32; 2],
+
+    #[format(R32G32_SFLOAT)]
+    pub uv: [f32; 2],
+}
+
+#[derive(BufferContents, Vertex)]
+#[repr(C)]
+pub struct Vertex3D {
+    #[format(R32G32B32_SFLOAT)]
+    pub position: [f32; 3],
+
+    #[format(R32G32_SFLOAT)]
+    pub uv: [f32; 2],
+}
diff --git a/src/core/render/render_pass_manager.rs b/src/core/render/render_pass_manager.rs
new file mode 100644
index 0000000..02c3d7a
--- /dev/null
+++ b/src/core/render/render_pass_manager.rs
@@ -0,0 +1,113 @@
+use std::sync::Arc;
+use vulkano::{
+    command_buffer::{AutoCommandBufferBuilder, RenderingAttachmentInfo, RenderingInfo},
+    image::view::ImageView,
+    pipeline::graphics::viewport::Viewport,
+    render_pass::{AttachmentLoadOp, AttachmentStoreOp},
+};
+
+/// Types de render passes disponibles
+#[derive(Debug, Clone)]
+pub enum RenderPassType {
+    Standard,
+    ShadowMap,
+    PostProcess,
+}
+
+/// Configuration pour un render pass
+#[derive(Debug, Clone)]
+pub struct RenderPassConfig {
+    pub pass_type: RenderPassType,
+    pub clear_color: Option<[f32; 4]>,
+    pub clear_depth: Option<f32>,
+    pub load_op: AttachmentLoadOp,
+    pub store_op: AttachmentStoreOp,
+}
+
+impl Default for RenderPassConfig {
+    fn default() -> Self {
+        Self {
+            pass_type: RenderPassType::Standard,
+            clear_color: Some([0.0, 0.0, 0.0, 1.0]),
+            clear_depth: Some(1.0),
+            load_op: AttachmentLoadOp::Clear,
+            store_op: AttachmentStoreOp::Store,
+        }
+    }
+}
+
+/// Gestionnaire de render passes réutilisable
+pub struct RenderPassManager;
+
+impl RenderPassManager {
+    /// Commence un render pass standard avec les paramètres donnés
+    pub fn begin_standard_rendering(
+        builder: &mut AutoCommandBufferBuilder<vulkano::command_buffer::PrimaryAutoCommandBuffer>,
+        config: &RenderPassConfig,
+        color_attachment: Arc<ImageView>,
+        depth_attachment: Option<Arc<ImageView>>,
+        window_size: [f32; 2],
+    ) -> Result<(), Box<dyn std::error::Error>> {
+        let viewport = Viewport {
+            offset: [0.0, 0.0],
+            extent: window_size,
+            depth_range: 0.0..=1.0,
+        };
+
+        let mut rendering_info = RenderingInfo {
+            color_attachments: vec![Some(RenderingAttachmentInfo {
+                load_op: config.load_op,
+                store_op: config.store_op,
+                clear_value: config.clear_color.map(|c| c.into()),
+                ..RenderingAttachmentInfo::image_view(color_attachment)
+            })],
+            depth_attachment: None,
+            ..Default::default()
+        };
+
+        if let Some(depth_view) = depth_attachment {
+            rendering_info.depth_attachment = Some(RenderingAttachmentInfo {
+                load_op: AttachmentLoadOp::Clear,
+                store_op: AttachmentStoreOp::DontCare,
+                clear_value: config.clear_depth.map(|d| [d].into()),
+                ..RenderingAttachmentInfo::image_view(depth_view)
+            });
+        }
+
+        builder
+            .begin_rendering(rendering_info)?
+            .set_viewport(0, [viewport].into_iter().collect())?;
+
+        Ok(())
+    }
+
+    /// Termine le render pass actuel
+    pub fn end_rendering(
+        builder: &mut AutoCommandBufferBuilder<vulkano::command_buffer::PrimaryAutoCommandBuffer>,
+    ) -> Result<(), Box<dyn std::error::Error>> {
+        builder.end_rendering()?;
+        Ok(())
+    }
+
+    /// Crée une configuration pour un render pass shadow map
+    pub fn shadow_map_config() -> RenderPassConfig {
+        RenderPassConfig {
+            pass_type: RenderPassType::ShadowMap,
+            clear_color: None,
+            clear_depth: Some(1.0),
+            load_op: AttachmentLoadOp::Clear,
+            store_op: AttachmentStoreOp::Store,
+        }
+    }
+
+    /// Crée une configuration pour un render pass de post-processing
+    pub fn post_process_config() -> RenderPassConfig {
+        RenderPassConfig {
+            pass_type: RenderPassType::PostProcess,
+            clear_color: Some([0.0, 0.0, 0.0, 1.0]),
+            clear_depth: None,
+            load_op: AttachmentLoadOp::Load,
+            store_op: AttachmentStoreOp::Store,
+        }
+    }
+}
diff --git a/src/core/render/texture.rs b/src/core/render/texture.rs
new file mode 100644
index 0000000..8b7f63c
--- /dev/null
+++ b/src/core/render/texture.rs
@@ -0,0 +1,123 @@
+use std::{path::Path, sync::Arc};
+
+use anyhow::Error;
+use image::{DynamicImage, EncodableLayout};
+use vulkano::{
+    buffer::{Buffer, BufferCreateInfo, BufferUsage},
+    command_buffer::{AutoCommandBufferBuilder, CopyBufferToImageInfo, PrimaryAutoCommandBuffer},
+    device::Device,
+    format::Format,
+    image::{
+        Image, ImageCreateInfo, ImageType, ImageUsage,
+        sampler::{Filter, Sampler, SamplerAddressMode, SamplerCreateInfo},
+        view::ImageView,
+    },
+    memory::allocator::{AllocationCreateInfo, MemoryTypeFilter, StandardMemoryAllocator},
+};
+
+pub struct Texture {
+    texture: Arc<ImageView>,
+    sampler: Arc<Sampler>,
+}
+
+impl Texture {
+    fn new(texture: Arc<ImageView>, sampler: Arc<Sampler>) -> Self {
+        Self { texture, sampler }
+    }
+
+    pub fn from_file(
+        device: &Arc<Device>,
+        memory_allocator: &Arc<StandardMemoryAllocator>,
+        builder: &mut AutoCommandBufferBuilder<PrimaryAutoCommandBuffer>,
+        path: &str,
+    ) -> Result<Self, Error> {
+        let _span = tracing::info_span!("texture_load_from_file", path = path);
+
+        let bytes = std::fs::read(path)?;
+        Self::from_bytes(device, memory_allocator, builder, &bytes)
+    }
+
+    pub fn from_bytes(
+        device: &Arc<Device>,
+        memory_allocator: &Arc<StandardMemoryAllocator>,
+        builder: &mut AutoCommandBufferBuilder<PrimaryAutoCommandBuffer>,
+        bytes: &[u8],
+    ) -> Result<Self, Error> {
+        let image = image::load_from_memory(bytes)?;
+        Self::from_dynamic_image(device, memory_allocator, builder, image)
+    }
+
+    pub fn from_dynamic_image(
+        device: &Arc<Device>,
+        memory_allocator: &Arc<StandardMemoryAllocator>,
+        builder: &mut AutoCommandBufferBuilder<PrimaryAutoCommandBuffer>,
+        image: DynamicImage,
+    ) -> Result<Self, Error> {
+        let _span = tracing::info_span!("texture_from_dynamic_image");
+
+        let image_data = image.to_rgba8();
+        let image_dimensions = image_data.dimensions();
+        let image_data = image_data.into_raw();
+
+        let upload_buffer = Buffer::new_slice(
+            memory_allocator.clone(),
+            BufferCreateInfo {
+                usage: BufferUsage::TRANSFER_SRC,
+                ..Default::default()
+            },
+            AllocationCreateInfo {
+                memory_type_filter: MemoryTypeFilter::PREFER_HOST
+                    | MemoryTypeFilter::HOST_SEQUENTIAL_WRITE,
+                ..Default::default()
+            },
+            image_data.len() as u64,
+        )?;
+
+        {
+            let buffer_data = &mut *upload_buffer.write()?;
+            buffer_data.copy_from_slice(&image_data);
+        }
+
+        let image = Image::new(
+            memory_allocator.clone(),
+            ImageCreateInfo {
+                image_type: ImageType::Dim2d,
+                format: Format::R8G8B8A8_SRGB,
+                extent: [image_dimensions.0, image_dimensions.1, 1],
+                array_layers: 1,
+                usage: ImageUsage::TRANSFER_DST | ImageUsage::SAMPLED,
+                ..Default::default()
+            },
+            AllocationCreateInfo::default(),
+        )?;
+
+        builder.copy_buffer_to_image(CopyBufferToImageInfo::buffer_image(
+            upload_buffer,
+            image.clone(),
+        ))?;
+
+        let sampler = Sampler::new(
+            device.clone(),
+            SamplerCreateInfo {
+                mag_filter: Filter::Linear,
+                min_filter: Filter::Linear,
+                address_mode: [SamplerAddressMode::Repeat; 3],
+                ..Default::default()
+            },
+        )?;
+
+        let image_view = ImageView::new_default(image)?;
+
+        tracing::trace!("Texture loaded with dimensions {:?}", image_dimensions);
+
+        Ok(Self::new(image_view, sampler))
+    }
+
+    pub fn get_texture(&self) -> &Arc<ImageView> {
+        &self.texture
+    }
+
+    pub fn get_sampler(&self) -> &Arc<Sampler> {
+        &self.sampler
+    }
+}
diff --git a/src/core/render/vulkan_context.rs b/src/core/render/vulkan_context.rs
new file mode 100644
index 0000000..3d91ec3
--- /dev/null
+++ b/src/core/render/vulkan_context.rs
@@ -0,0 +1,45 @@
+use std::sync::Arc;
+
+use vulkano::{
+    command_buffer::allocator::StandardCommandBufferAllocator,
+    descriptor_set::allocator::StandardDescriptorSetAllocator,
+};
+use vulkano_util::context::VulkanoContext;
+
+pub struct VulkanContext {
+    vulkano_context: VulkanoContext,
+    command_buffer_allocator: Arc<StandardCommandBufferAllocator>,
+    descriptor_set_allocator: Arc<StandardDescriptorSetAllocator>,
+}
+
+impl VulkanContext {
+    pub fn new(vulkano_context: VulkanoContext) -> Self {
+        let command_buffer_allocator = Arc::new(StandardCommandBufferAllocator::new(
+            vulkano_context.device().clone(),
+            Default::default(),
+        ));
+
+        let descriptor_set_allocator = Arc::new(StandardDescriptorSetAllocator::new(
+            vulkano_context.device().clone(),
+            Default::default(),
+        ));
+
+        Self {
+            vulkano_context,
+            command_buffer_allocator,
+            descriptor_set_allocator,
+        }
+    }
+
+    pub fn vulkano_context(&self) -> &VulkanoContext {
+        &self.vulkano_context
+    }
+
+    pub fn command_buffer_allocator(&self) -> &Arc<StandardCommandBufferAllocator> {
+        &self.command_buffer_allocator
+    }
+
+    pub fn descriptor_set_allocator(&self) -> &Arc<StandardDescriptorSetAllocator> {
+        &self.descriptor_set_allocator
+    }
+}
diff --git a/src/core/scene/manager.rs b/src/core/scene/manager.rs
new file mode 100644
index 0000000..cf07ebc
--- /dev/null
+++ b/src/core/scene/manager.rs
@@ -0,0 +1,65 @@
+use std::error::Error;
+
+use crate::core::app::context::WindowContext;
+
+use super::Scene;
+
+pub struct SceneManager {
+    scenes: Vec<Box<dyn Scene>>,
+    current_scene_index: Option<usize>,
+}
+
+impl SceneManager {
+    pub fn new() -> Self {
+        Self {
+            scenes: Vec::new(),
+            current_scene_index: None,
+        }
+    }
+
+    pub fn load_scene(&mut self, scene: Box<dyn Scene>) {
+        self.scenes.push(scene);
+        self.current_scene_index = Some(self.scenes.len() - 1);
+    }
+
+    pub fn replace_current_scene(&mut self, scene: Box<dyn Scene>) {
+        if let Some(index) = self.current_scene_index {
+            if index < self.scenes.len() {
+                self.scenes[index].unload();
+                self.scenes[index] = scene;
+            }
+        } else {
+            self.load_scene(scene);
+        }
+    }
+
+    pub fn current_scene(&self) -> Option<&Box<dyn Scene>> {
+        if let Some(index) = self.current_scene_index {
+            self.scenes.get(index)
+        } else {
+            None
+        }
+    }
+
+    pub fn current_scene_mut(&mut self) -> Option<&mut Box<dyn Scene>> {
+        if let Some(index) = self.current_scene_index {
+            self.scenes.get_mut(index)
+        } else {
+            None
+        }
+    }
+
+    pub fn load_scene_if_not_loaded(
+        &mut self,
+        app_context: &mut WindowContext,
+    ) -> Result<(), Box<dyn Error>> {
+        if let Some(scene) = self.current_scene_mut() {
+            if !scene.loaded() {
+                scene.load(app_context)?;
+            }
+        } else {
+            tracing::warn!("No scene found in SceneManager!");
+        }
+        Ok(())
+    }
+}
diff --git a/src/core/scene/mod.rs b/src/core/scene/mod.rs
new file mode 100644
index 0000000..5dd8a57
--- /dev/null
+++ b/src/core/scene/mod.rs
@@ -0,0 +1,19 @@
+use std::error::Error;
+
+use vulkano::sync::GpuFuture;
+
+use crate::core::app::context::WindowContext;
+
+pub mod manager;
+
+pub trait Scene {
+    fn loaded(&self) -> bool;
+    fn load(&mut self, app_context: &mut WindowContext) -> Result<(), Box<dyn Error>>;
+    fn update(&mut self, app_context: &mut WindowContext) -> Result<(), Box<dyn Error>>;
+    fn render(
+        &mut self,
+        acquire_future: Box<dyn GpuFuture>,
+        app_context: &mut WindowContext,
+    ) -> Result<Box<dyn GpuFuture>, Box<dyn Error>>;
+    fn unload(&mut self);
+}
diff --git a/src/core/timer.rs b/src/core/timer.rs
new file mode 100644
index 0000000..3245a4c
--- /dev/null
+++ b/src/core/timer.rs
@@ -0,0 +1,34 @@
+pub struct Timer {
+    start_time: std::time::Instant,
+    last_time: std::time::Instant,
+    current_time: std::time::Instant,
+    delta_time: f32,
+}
+
+impl Timer {
+    pub fn new() -> Self {
+        Self {
+            start_time: std::time::Instant::now(),
+            last_time: std::time::Instant::now(),
+            current_time: std::time::Instant::now(),
+            delta_time: 0.0,
+        }
+    }
+
+    pub fn update(&mut self) {
+        self.current_time = std::time::Instant::now();
+        self.delta_time = self
+            .current_time
+            .duration_since(self.last_time)
+            .as_secs_f32();
+        self.last_time = self.current_time;
+    }
+
+    pub fn delta_time(&self) -> f32 {
+        self.delta_time
+    }
+
+    pub fn start_time(&self) -> std::time::Instant {
+        self.start_time
+    }
+}
diff --git a/src/display/app.rs b/src/display/app.rs
deleted file mode 100644
index 0d5fb1d..0000000
--- a/src/display/app.rs
+++ /dev/null
@@ -1,93 +0,0 @@
-use crate::display::window::Window;
-use crate::renderer::{vulkan::VkRenderContext, Renderable};
-use winit::application::ApplicationHandler;
-use winit::event::WindowEvent;
-use winit::event_loop::ActiveEventLoop;
-use winit::window::WindowId;
-use crate::scene::TriangleScene;
-
-pub struct App {
-    window: Window,
-    render_context: Option<VkRenderContext>,
-    scene: Option<Box<dyn Renderable>>,
-}
-
-impl App {
-    pub fn new(window: Window) -> Self {
-        Self {
-            window,
-            render_context: None,
-            scene: None,
-        }
-    }
-
-    pub fn set_scene(&mut self, mut scene: Box<dyn Renderable>) {
-        let result = self.render_context.as_mut()
-            .ok_or_else(|| anyhow::anyhow!("No render context"))
-            .and_then(|render_context| render_context.init_scene(&mut scene));
-
-        match result {
-            Ok(_) => self.scene = Some(scene),
-            Err(err) => log::warn!("{err}"),
-        }
-    }
-}
-
-impl ApplicationHandler for App {
-    fn resumed(&mut self, event_loop: &ActiveEventLoop) {
-        self.window
-            .create_window(event_loop)
-            .map_err(|err| format!("Failed to create window: {}", err))
-            .unwrap();
-
-        self.render_context = VkRenderContext::init(&self.window).ok();
-
-        let scene = TriangleScene::new();
-        self.set_scene(Box::new(scene));
-    }
-
-    fn window_event(&mut self, event_loop: &ActiveEventLoop, _id: WindowId, event: WindowEvent) {
-        match event {
-            WindowEvent::CloseRequested => {
-                match self.render_context.as_ref() {
-                    Some(render_context) => render_context.exit(),
-                    None => log::warn!("Window closed but no render context found"),
-                };
-
-                log::debug!("The close button was pressed; stopping");
-                event_loop.exit();
-            }
-            WindowEvent::Resized(size) => {
-                match self.render_context.as_mut() {
-                    Some(render_context) => {
-                        if let Err(error) =
-                            render_context.update_resolution(size.width, size.height)
-                        {
-                            log::error!(
-                                "Failed to update resolution of render context : {}",
-                                error
-                            );
-                        }
-                    }
-                    None => log::warn!("Window resized but no render context found"),
-                };
-            }
-            WindowEvent::RedrawRequested => {
-                if !event_loop.exiting() {
-                    match self.render_context.as_mut() {
-                        Some(render_context) => {
-                            if let Err(error) = render_context.render(self.scene.as_ref()) {
-                                log::error!("Failed to render with render context : {}", error);
-                                event_loop.exit();
-                            }
-                        }
-                        None => log::warn!("Window resized but no render context found"),
-                    };
-                }
-
-                self.window.request_redraw();
-            }
-            _ => {}
-        }
-    }
-}
diff --git a/src/display/mod.rs b/src/display/mod.rs
deleted file mode 100644
index f7758ab..0000000
--- a/src/display/mod.rs
+++ /dev/null
@@ -1,5 +0,0 @@
-mod app;
-mod window;
-
-pub use app::App;
-pub use window::Window;
diff --git a/src/display/window.rs b/src/display/window.rs
deleted file mode 100644
index 803fd35..0000000
--- a/src/display/window.rs
+++ /dev/null
@@ -1,65 +0,0 @@
-use std::ffi::c_char;
-use winit::dpi::Pixel;
-use winit::event_loop::ActiveEventLoop;
-use winit::raw_window_handle::HasDisplayHandle;
-
-pub struct Window {
-    handle: Option<winit::window::Window>,
-    window_attributes: winit::window::WindowAttributes,
-}
-
-impl Window {
-    pub fn new(window_attributes: winit::window::WindowAttributes) -> Self {
-        Self {
-            handle: None,
-            window_attributes,
-        }
-    }
-
-    pub fn create_window(&mut self, event_loop: &ActiveEventLoop) -> anyhow::Result<()> {
-        let window = event_loop.create_window(self.window_attributes.clone())?;
-
-        self.handle = Some(window);
-
-        Ok(())
-    }
-
-    pub fn required_extensions(&self) -> anyhow::Result<Vec<*const c_char>> {
-        let display_handle = self
-            .handle
-            .as_ref()
-            .ok_or_else(|| anyhow::anyhow!("Window not found"))?
-            .display_handle()?;
-
-        #[allow(unused_mut)]
-        let mut extension_names =
-            ash_window::enumerate_required_extensions(display_handle.as_raw())?.to_vec();
-
-        // TODO: Move this because is not related to Window extensions
-        #[cfg(any(target_os = "macos", target_os = "ios"))]
-        {
-            extension_names.push(ash::khr::portability_enumeration::NAME.as_ptr());
-            // Enabling this extension is a requirement when using `VK_KHR_portability_subset`
-            extension_names.push(ash::khr::get_physical_device_properties2::NAME.as_ptr());
-        }
-
-        Ok(extension_names)
-    }
-
-    pub fn handle(&self) -> Option<&winit::window::Window> {
-        self.handle.as_ref()
-    }
-
-    pub fn physical_size<P: Pixel>(&self) -> Option<winit::dpi::PhysicalSize<P>> {
-        self.window_attributes
-            .inner_size
-            .and_then(|size| Some(size.to_physical::<P>(1.0)))
-    }
-
-    pub fn request_redraw(&self) {
-        match self.handle.as_ref() {
-            Some(window) => window.request_redraw(),
-            None => log::warn!("Redraw requested but no window found"),
-        }
-    }
-}
diff --git a/src/game/assets/mod.rs b/src/game/assets/mod.rs
new file mode 100644
index 0000000..d793a66
--- /dev/null
+++ b/src/game/assets/mod.rs
@@ -0,0 +1 @@
+pub mod square;
diff --git a/src/game/assets/square.rs b/src/game/assets/square.rs
new file mode 100644
index 0000000..7219157
--- /dev/null
+++ b/src/game/assets/square.rs
@@ -0,0 +1,263 @@
+use std::{collections::BTreeMap, error::Error, sync::Arc};
+
+use vulkano::{
+    buffer::{Buffer, BufferCreateInfo, BufferUsage, Subbuffer},
+    command_buffer::{AutoCommandBufferBuilder, PrimaryAutoCommandBuffer},
+    descriptor_set::{
+        DescriptorSet, WriteDescriptorSet,
+        allocator::StandardDescriptorSetAllocator,
+        layout::{DescriptorSetLayoutBinding, DescriptorSetLayoutCreateInfo, DescriptorType},
+    },
+    device::Device,
+    format::Format,
+    memory::allocator::{AllocationCreateInfo, MemoryTypeFilter, StandardMemoryAllocator},
+    pipeline::{
+        DynamicState, GraphicsPipeline, Pipeline, PipelineBindPoint, PipelineLayout,
+        PipelineShaderStageCreateInfo,
+        graphics::{
+            GraphicsPipelineCreateInfo,
+            color_blend::{ColorBlendAttachmentState, ColorBlendState},
+            depth_stencil::{DepthState, DepthStencilState},
+            input_assembly::InputAssemblyState,
+            multisample::MultisampleState,
+            rasterization::RasterizationState,
+            subpass::PipelineRenderingCreateInfo,
+            vertex_input::{Vertex, VertexDefinition},
+            viewport::ViewportState,
+        },
+        layout::{PipelineDescriptorSetLayoutCreateInfo, PipelineLayoutCreateFlags},
+    },
+    shader::ShaderStages,
+};
+
+use crate::core::render::{
+    primitives::{mvp::Mvp, transform::TransformRaw, vertex::Vertex3D},
+    texture::Texture,
+};
+
+const VERTICES: [Vertex3D; 4] = [
+    Vertex3D {
+        position: [-0.5, -0.5, 0.0],
+        uv: [0.0, 0.0],
+    },
+    Vertex3D {
+        position: [-0.5, 0.5, 0.0],
+        uv: [0.0, 1.0],
+    },
+    Vertex3D {
+        position: [0.5, -0.5, 0.0],
+        uv: [1.0, 0.0],
+    },
+    Vertex3D {
+        position: [0.5, 0.5, 0.0],
+        uv: [1.0, 1.0],
+    },
+];
+
+const INDICES: [u32; 6] = [0, 2, 1, 2, 3, 1];
+
+pub mod shaders {
+    pub mod vs {
+        vulkano_shaders::shader! {
+            ty: "vertex",
+            path: r"res/shaders/vertex.vert",
+            generate_structs: false,
+        }
+    }
+
+    pub mod fs {
+        vulkano_shaders::shader! {
+            ty: "fragment",
+            path: r"res/shaders/vertex.frag",
+            generate_structs: false,
+        }
+    }
+}
+
+pub struct Square {
+    vertex_buffer: Subbuffer<[Vertex3D]>,
+    index_buffer: Subbuffer<[u32]>,
+    pipeline: Arc<GraphicsPipeline>,
+}
+
+impl Square {
+    pub fn new(
+        device: &Arc<Device>,
+        memory_allocator: &Arc<StandardMemoryAllocator>,
+        swapchain_format: Format,
+        depth_format: Format,
+    ) -> Result<Self, Box<dyn Error>> {
+        let vertex_buffer = Buffer::from_iter(
+            memory_allocator.clone(),
+            BufferCreateInfo {
+                usage: BufferUsage::VERTEX_BUFFER,
+                ..Default::default()
+            },
+            AllocationCreateInfo {
+                memory_type_filter: MemoryTypeFilter::PREFER_DEVICE
+                    | MemoryTypeFilter::HOST_SEQUENTIAL_WRITE,
+                ..Default::default()
+            },
+            Vec::from_iter(VERTICES),
+        )?;
+        let index_buffer = Buffer::from_iter(
+            memory_allocator.clone(),
+            BufferCreateInfo {
+                usage: BufferUsage::INDEX_BUFFER,
+                ..Default::default()
+            },
+            AllocationCreateInfo {
+                memory_type_filter: MemoryTypeFilter::PREFER_DEVICE
+                    | MemoryTypeFilter::HOST_SEQUENTIAL_WRITE,
+                ..Default::default()
+            },
+            Vec::from_iter(INDICES),
+        )?;
+
+        let vs = shaders::vs::load(device.clone())?
+            .entry_point("main")
+            .ok_or("Failed find main entry point of vertex shader".to_string())?;
+
+        let fs = shaders::fs::load(device.clone())?
+            .entry_point("main")
+            .ok_or("Failed find main entry point of fragment shader".to_string())?;
+
+        let vertex_input_state =
+            [Vertex3D::per_vertex(), TransformRaw::per_instance()].definition(&vs)?;
+
+        let stages = [
+            PipelineShaderStageCreateInfo::new(vs),
+            PipelineShaderStageCreateInfo::new(fs),
+        ];
+
+        let vertex_bindings = BTreeMap::<u32, DescriptorSetLayoutBinding>::from_iter([(
+            0,
+            DescriptorSetLayoutBinding {
+                stages: ShaderStages::VERTEX,
+                ..DescriptorSetLayoutBinding::descriptor_type(DescriptorType::UniformBuffer)
+            },
+        )]);
+        let fragment_bindings = BTreeMap::<u32, DescriptorSetLayoutBinding>::from_iter([
+            (
+                0,
+                DescriptorSetLayoutBinding {
+                    stages: ShaderStages::FRAGMENT,
+                    ..DescriptorSetLayoutBinding::descriptor_type(DescriptorType::Sampler)
+                },
+            ),
+            (
+                1,
+                DescriptorSetLayoutBinding {
+                    stages: ShaderStages::FRAGMENT,
+                    ..DescriptorSetLayoutBinding::descriptor_type(DescriptorType::SampledImage)
+                },
+            ),
+        ]);
+
+        let vertex_descriptor_set_layout = DescriptorSetLayoutCreateInfo {
+            bindings: vertex_bindings,
+            ..Default::default()
+        };
+
+        let fragment_descriptor_set_layout = DescriptorSetLayoutCreateInfo {
+            bindings: fragment_bindings,
+            ..Default::default()
+        };
+
+        let create_info = PipelineDescriptorSetLayoutCreateInfo {
+            set_layouts: vec![vertex_descriptor_set_layout, fragment_descriptor_set_layout],
+            flags: PipelineLayoutCreateFlags::default(),
+            push_constant_ranges: vec![],
+        }
+        .into_pipeline_layout_create_info(device.clone())?;
+
+        let layout = PipelineLayout::new(device.clone(), create_info)?;
+
+        let subpass = PipelineRenderingCreateInfo {
+            color_attachment_formats: vec![Some(swapchain_format)],
+            depth_attachment_format: Some(depth_format),
+            ..Default::default()
+        };
+
+        let pipeline = GraphicsPipeline::new(
+            device.clone(),
+            None,
+            GraphicsPipelineCreateInfo {
+                stages: stages.into_iter().collect(),
+                vertex_input_state: Some(vertex_input_state),
+                input_assembly_state: Some(InputAssemblyState::default()),
+                viewport_state: Some(ViewportState::default()),
+                rasterization_state: Some(RasterizationState::default()),
+                multisample_state: Some(MultisampleState::default()),
+                color_blend_state: Some(ColorBlendState::with_attachment_states(
+                    subpass.color_attachment_formats.len() as u32,
+                    ColorBlendAttachmentState::default(),
+                )),
+                depth_stencil_state: Some(DepthStencilState {
+                    depth: Some(DepthState::simple()),
+                    ..Default::default()
+                }),
+                dynamic_state: [DynamicState::Viewport].into_iter().collect(),
+                subpass: Some(subpass.into()),
+                ..GraphicsPipelineCreateInfo::layout(layout)
+            },
+        )?;
+
+        Ok(Self {
+            vertex_buffer,
+            index_buffer,
+            pipeline,
+        })
+    }
+
+    pub fn render(
+        &self,
+        command_buffer: &mut AutoCommandBufferBuilder<PrimaryAutoCommandBuffer>,
+        descriptor_set_allocator: &Arc<StandardDescriptorSetAllocator>,
+        mvp_uniform: &Subbuffer<[Mvp]>,
+        transform_uniform: &Subbuffer<[TransformRaw]>,
+        texture: &Texture,
+    ) -> Result<(), Box<dyn Error>> {
+        let layouts = self.pipeline.layout().set_layouts();
+
+        let uniform_descriptor_set = DescriptorSet::new(
+            descriptor_set_allocator.clone(),
+            layouts[0].clone(),
+            [WriteDescriptorSet::buffer(0, mvp_uniform.clone())],
+            [],
+        )?;
+
+        let texture_descriptor_set = DescriptorSet::new(
+            descriptor_set_allocator.clone(),
+            layouts[1].clone(),
+            [
+                WriteDescriptorSet::sampler(0, texture.get_sampler().clone()),
+                WriteDescriptorSet::image_view(1, texture.get_texture().clone()),
+            ],
+            [],
+        )?;
+
+        command_buffer.bind_pipeline_graphics(self.pipeline.clone())?;
+        command_buffer.bind_descriptor_sets(
+            PipelineBindPoint::Graphics,
+            self.pipeline.layout().clone(),
+            0,
+            vec![uniform_descriptor_set, texture_descriptor_set],
+        )?;
+        command_buffer
+            .bind_vertex_buffers(0, (self.vertex_buffer.clone(), transform_uniform.clone()))?;
+        command_buffer.bind_index_buffer(self.index_buffer.clone())?;
+
+        unsafe {
+            command_buffer.draw_indexed(
+                INDICES.len() as u32,
+                transform_uniform.len() as u32,
+                0,
+                0,
+                0,
+            )?;
+        }
+
+        Ok(())
+    }
+}
diff --git a/src/game/mod.rs b/src/game/mod.rs
new file mode 100644
index 0000000..065e8e4
--- /dev/null
+++ b/src/game/mod.rs
@@ -0,0 +1,2 @@
+pub mod assets;
+pub mod scenes;
diff --git a/src/game/scenes/main_scene.rs b/src/game/scenes/main_scene.rs
new file mode 100644
index 0000000..37fd4ed
--- /dev/null
+++ b/src/game/scenes/main_scene.rs
@@ -0,0 +1,285 @@
+use std::error::Error;
+
+use super::settings_scene::SettingsScene;
+use crate::core::app::DEPTH_IMAGE_ID;
+use crate::core::app::context::WindowContext;
+use crate::core::app::user_event::UserEvent;
+use crate::core::render::primitives::camera::Camera3D;
+use crate::core::render::primitives::transform::Transform;
+use crate::core::render::render_pass_manager::{RenderPassConfig, RenderPassManager};
+use crate::core::render::texture::Texture;
+use crate::core::scene::Scene;
+use crate::game::assets::square::Square;
+use egui_winit_vulkano::egui;
+use glam::EulerRot;
+use glam::Quat;
+use glam::Vec3;
+use vulkano::{
+    command_buffer::{AutoCommandBufferBuilder, CommandBufferUsage, PrimaryCommandBufferAbstract},
+    sync::GpuFuture,
+};
+use winit::window::CursorGrabMode;
+
+pub struct MainSceneState {
+    square: Square,
+    instances: Vec<Transform>,
+    camera: Camera3D,
+    texture: Texture,
+    speed: f32,
+}
+
+#[derive(Default)]
+pub struct MainScene {
+    state: Option<MainSceneState>,
+}
+
+impl Scene for MainScene {
+    fn loaded(&self) -> bool {
+        self.state.is_some()
+    }
+
+    fn load(&mut self, app_context: &mut WindowContext) -> Result<(), Box<dyn std::error::Error>> {
+        let depth_image_view = app_context.with_renderer_mut(|renderer| {
+            renderer.get_additional_image_view(DEPTH_IMAGE_ID).clone()
+        });
+
+        let swapchain_image_view =
+            app_context.with_renderer(|renderer| renderer.swapchain_image_view().clone());
+
+        let square = Square::new(
+            &app_context.device,
+            &app_context.memory_allocator,
+            swapchain_image_view.format(),
+            depth_image_view.format(),
+        )?;
+
+        let num_instances = 100;
+        let instance_size = 10.0;
+        let instance_spacing = 10.0;
+        let num_instances_per_row = (num_instances as f32 / instance_spacing).ceil() as u32;
+        let instances: Vec<Transform> = (0..num_instances)
+            .map(|i| Transform {
+                position: Vec3::new(
+                    (i % num_instances_per_row) as f32 * (instance_spacing + instance_size),
+                    0.0,
+                    (i / num_instances_per_row) as f32 * (instance_spacing + instance_size),
+                ),
+                rotation: Quat::from_euler(
+                    EulerRot::XYZ,
+                    0.0,
+                    rand::random_range(0.0..=360.0),
+                    0.0,
+                ),
+                scale: Vec3::new(instance_size, instance_size, instance_size),
+            })
+            .collect();
+
+        let texture = {
+            let mut uploads = AutoCommandBufferBuilder::primary(
+                app_context.command_buffer_allocator.clone(),
+                app_context.graphics_queue.queue_family_index(),
+                CommandBufferUsage::OneTimeSubmit,
+            )?;
+
+            let texture = Texture::from_file(
+                &app_context.device,
+                &app_context.memory_allocator,
+                &mut uploads,
+                "res/textures/wooden-crate.jpg",
+            )?;
+
+            let _ = uploads
+                .build()?
+                .execute(app_context.graphics_queue.clone())?;
+
+            texture
+        };
+
+        let camera = app_context.with_renderer(|renderer| {
+            Camera3D::new(
+                renderer.aspect_ratio(),
+                std::f32::consts::FRAC_PI_2,
+                0.01,
+                1000.0,
+            )
+        });
+
+        self.state = Some(MainSceneState {
+            square,
+            instances,
+            camera,
+            texture,
+            speed: 50.0,
+        });
+
+        Ok(())
+    }
+
+    fn update(&mut self, app_context: &mut WindowContext) -> Result<(), Box<dyn Error>> {
+        let state = self.state.as_mut().unwrap();
+        app_context.with_input_manager(|input_manager| {
+            app_context.with_timer(|timer| {
+                state.camera.update(
+                    input_manager,
+                    timer,
+                    state.speed,
+                    10.0,
+                    app_context.get_aspect_ratio(),
+                );
+            });
+        });
+
+        if app_context
+            .with_input_manager(|input_manager| input_manager.get_virtual_input_state("mouse_left"))
+            > 0.0
+        {
+            let _ = app_context
+                .event_loop_proxy
+                .send_event(UserEvent::CursorVisible(app_context.window_id, false));
+            let _ = app_context
+                .event_loop_proxy
+                .send_event(UserEvent::CursorGrabMode(
+                    app_context.window_id,
+                    CursorGrabMode::Locked,
+                ));
+        }
+
+        if app_context.with_input_manager(|input_manager| {
+            input_manager.get_virtual_input_state("mouse_right")
+        }) > 0.0
+        {
+            let _ = app_context
+                .event_loop_proxy
+                .send_event(UserEvent::CursorVisible(app_context.window_id, true));
+            let _ = app_context
+                .event_loop_proxy
+                .send_event(UserEvent::CursorGrabMode(
+                    app_context.window_id,
+                    CursorGrabMode::None,
+                ));
+        }
+
+        Ok(())
+    }
+
+    fn render(
+        &mut self,
+        before_future: Box<dyn GpuFuture>,
+        app_context: &mut WindowContext,
+    ) -> Result<Box<dyn GpuFuture>, Box<dyn Error>> {
+        let state = self.state.as_ref().ok_or("State not loaded")?;
+
+        let mut builder = AutoCommandBufferBuilder::primary(
+            app_context.command_buffer_allocator.clone(),
+            app_context.graphics_queue.queue_family_index(),
+            CommandBufferUsage::OneTimeSubmit,
+        )?;
+
+        {
+            let swapchain_image_view =
+                app_context.with_renderer(|renderer| renderer.swapchain_image_view().clone());
+            let depth_image_view = app_context.with_renderer_mut(|renderer| {
+                renderer.get_additional_image_view(DEPTH_IMAGE_ID).clone()
+            });
+            let config = RenderPassConfig::default();
+            RenderPassManager::begin_standard_rendering(
+                &mut builder,
+                &config,
+                swapchain_image_view,
+                Some(depth_image_view),
+                app_context.get_window_size(),
+            )?;
+        }
+
+        // Create camera uniform using the actual camera
+        let camera_uniform = state.camera.create_buffer(&app_context.memory_allocator)?;
+
+        let transform_uniform =
+            Transform::create_buffer(&app_context.memory_allocator, &state.instances)?;
+
+        state
+            .square
+            .render(
+                &mut builder,
+                &app_context.descriptor_set_allocator,
+                &camera_uniform,
+                &transform_uniform,
+                &state.texture,
+            )
+            .unwrap();
+
+        RenderPassManager::end_rendering(&mut builder)?;
+
+        let command_buffer = builder.build()?;
+
+        let render_future =
+            before_future.then_execute(app_context.graphics_queue.clone(), command_buffer)?;
+
+        let swapchain_image_view =
+            app_context.with_renderer(|renderer| renderer.swapchain_image_view().clone());
+        let input_manager_status =
+            app_context.with_input_manager(|input_manager| format!("{:#?}", input_manager));
+        let event_loop_proxy = app_context.event_loop_proxy.clone();
+        let delta_time = app_context.get_delta_time();
+        let window_id = app_context.window_id;
+        let window_size = app_context.get_window_size();
+
+        let render_future = app_context.with_gui_mut(|gui| {
+            gui.immediate_ui(|gui| {
+                let ctx = gui.context();
+                egui::TopBottomPanel::top("top_panel").show(&ctx, |ui| {
+                    ui.horizontal(|ui| {
+                        ui.heading("Vulkan Test - Moteur 3D");
+                        ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
+                            if ui.button("Paramètres").clicked() {
+                                let _ = event_loop_proxy.send_event(UserEvent::ChangeScene(
+                                    window_id,
+                                    Box::new(SettingsScene::default()),
+                                ));
+                            }
+                            if ui.button("Quitter").clicked() {
+                                let _ = event_loop_proxy.send_event(UserEvent::Exit(window_id));
+                            }
+                        });
+                    });
+                });
+
+                egui::SidePanel::left("side_panel").show(&ctx, |ui| {
+                    ui.heading("Informations");
+
+                    ui.separator();
+
+                    ui.label(format!("Résolution: {:?}", window_size));
+                    ui.label(format!("Delta Time: {:.2}ms", delta_time * 1000.0));
+
+                    ui.separator();
+
+                    ui.label("Position caméra:");
+                    let position = state.camera.get_position();
+                    ui.label(format!("  X: {:.2}", position[0]));
+                    ui.label(format!("  Y: {:.2}", position[1]));
+                    ui.label(format!("  Z: {:.2}", position[2]));
+
+                    ui.separator();
+
+                    ui.label("Rotation caméra:");
+                    let rotation = state.camera.get_rotation();
+                    ui.label(format!("  Yaw: {:.2}°", rotation.y.to_degrees()));
+                    ui.label(format!("  Pitch: {:.2}°", rotation.x.to_degrees()));
+
+                    ui.separator();
+
+                    ui.label(input_manager_status);
+                });
+            });
+
+            gui.draw_on_image(render_future, swapchain_image_view.clone())
+        });
+
+        Ok(render_future)
+    }
+
+    fn unload(&mut self) {
+        self.state = None;
+    }
+}
diff --git a/src/game/scenes/mod.rs b/src/game/scenes/mod.rs
new file mode 100644
index 0000000..516fa6f
--- /dev/null
+++ b/src/game/scenes/mod.rs
@@ -0,0 +1,2 @@
+pub mod main_scene;
+pub mod settings_scene;
diff --git a/src/game/scenes/settings_scene.rs b/src/game/scenes/settings_scene.rs
new file mode 100644
index 0000000..466652a
--- /dev/null
+++ b/src/game/scenes/settings_scene.rs
@@ -0,0 +1,136 @@
+use std::error::Error;
+
+use crate::core::app::DEPTH_IMAGE_ID;
+use crate::core::app::context::WindowContext;
+use crate::core::app::user_event::UserEvent;
+use crate::core::render::render_pass_manager::{RenderPassConfig, RenderPassManager};
+use crate::core::scene::Scene;
+use egui_winit_vulkano::egui;
+use vulkano::{
+    command_buffer::AutoCommandBufferBuilder, command_buffer::CommandBufferUsage, sync::GpuFuture,
+};
+
+use super::main_scene::MainScene;
+
+pub struct SettingsSceneState {
+    current_resolution: [f32; 2],
+    available_resolutions: Vec<(u32, u32)>,
+}
+
+#[derive(Default)]
+pub struct SettingsScene {
+    state: Option<SettingsSceneState>,
+}
+
+impl Scene for SettingsScene {
+    fn loaded(&self) -> bool {
+        self.state.is_some()
+    }
+
+    fn load(&mut self, app_context: &mut WindowContext) -> Result<(), Box<dyn Error>> {
+        let current_resolution = app_context.get_window_size();
+        let available_resolutions = app_context.get_available_resolutions();
+
+        self.state = Some(SettingsSceneState {
+            current_resolution,
+            available_resolutions,
+        });
+
+        Ok(())
+    }
+
+    fn update(&mut self, _app_context: &mut WindowContext) -> Result<(), Box<dyn Error>> {
+        Ok(())
+    }
+
+    fn render(
+        &mut self,
+        before_future: Box<dyn GpuFuture>,
+        app_context: &mut WindowContext,
+    ) -> Result<Box<dyn GpuFuture>, Box<dyn Error>> {
+        let state = self.state.as_ref().ok_or("State not found")?;
+
+        let mut builder = AutoCommandBufferBuilder::primary(
+            app_context.command_buffer_allocator.clone(),
+            app_context.graphics_queue.queue_family_index(),
+            CommandBufferUsage::OneTimeSubmit,
+        )?;
+
+        // Utiliser le RenderPassManager
+        {
+            let swapchain_image_view =
+                app_context.with_renderer(|renderer| renderer.swapchain_image_view().clone());
+            let depth_stencil_image_view = app_context.with_renderer_mut(|renderer| {
+                renderer.get_additional_image_view(DEPTH_IMAGE_ID).clone()
+            });
+
+            let config = RenderPassConfig::default();
+            RenderPassManager::begin_standard_rendering(
+                &mut builder,
+                &config,
+                swapchain_image_view,
+                Some(depth_stencil_image_view),
+                app_context.get_window_size(),
+            )?;
+        }
+
+        // Pas de géométrie dans cette scène - juste un écran de paramètres
+        RenderPassManager::end_rendering(&mut builder)?;
+
+        let command_buffer = builder.build()?;
+
+        let render_future =
+            before_future.then_execute(app_context.graphics_queue.clone(), command_buffer)?;
+
+        let swapchain_image_view =
+            app_context.with_renderer(|renderer| renderer.swapchain_image_view().clone());
+
+        let event_loop_proxy = app_context.event_loop_proxy.clone();
+        let window_id = app_context.window_id;
+
+        let render_future = app_context.with_gui_mut(|gui| {
+            gui.immediate_ui(|gui| {
+                let ctx = gui.context();
+
+                egui::CentralPanel::default().show(&ctx, |ui| {
+                    ui.heading("Paramètres");
+
+                    ui.separator();
+
+                    ui.label(format!(
+                        "Résolution actuelle: {:?}",
+                        state.current_resolution
+                    ));
+
+                    ui.separator();
+                    ui.label("Changer la résolution:");
+
+                    for &(width, height) in &state.available_resolutions {
+                        if ui.button(format!("{}x{}", width, height)).clicked() {
+                            let _ = event_loop_proxy.send_event(UserEvent::ChangeResolution(
+                                window_id,
+                                width as f32,
+                                height as f32,
+                            ));
+                        }
+                    }
+
+                    ui.separator();
+
+                    if ui.button("Retour au jeu").clicked() {
+                        let _ = event_loop_proxy.send_event(UserEvent::ChangeScene(
+                            window_id,
+                            Box::new(MainScene::default()),
+                        ));
+                    }
+                });
+            });
+
+            gui.draw_on_image(render_future, swapchain_image_view.clone())
+        });
+
+        Ok(render_future)
+    }
+
+    fn unload(&mut self) {}
+}
diff --git a/src/main.rs b/src/main.rs
index 2f48cfe..998f725 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,24 +1,108 @@
-use winit::event_loop::{ControlFlow, EventLoop};
+use core::input::{AxisDirection, InputManager, VirtualBinding};
+use std::collections::HashMap;
 
-mod display;
-mod renderer;
-mod scene;
+use vulkano::device::{DeviceExtensions, DeviceFeatures};
+use vulkano_util::context::{VulkanoConfig, VulkanoContext};
+use winit::{
+    event::MouseButton,
+    event_loop::{ControlFlow, EventLoop},
+    keyboard::{KeyCode, PhysicalKey},
+};
+
+mod core;
+mod game;
 
 fn main() {
-    env_logger::init();
+    use tracing_subscriber::layer::SubscriberExt;
+    use tracing_subscriber::util::SubscriberInitExt;
 
-    let event_loop = EventLoop::new().unwrap();
+    tracing_subscriber::registry()
+        .with(
+            tracing_subscriber::fmt::layer()
+                .with_target(true)
+                .with_thread_ids(true)
+                .with_file(true)
+                .with_line_number(true),
+        )
+        .with(tracing_tracy::TracyLayer::default())
+        .with(
+            tracing_subscriber::EnvFilter::try_from_default_env()
+                .unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info")),
+        )
+        .init();
+
+    let input_manager = InputManager::new(HashMap::from([
+        (
+            "move_forward".to_string(),
+            vec![
+                VirtualBinding::Keyboard(PhysicalKey::Code(KeyCode::KeyW), AxisDirection::Normal),
+                VirtualBinding::Keyboard(PhysicalKey::Code(KeyCode::KeyS), AxisDirection::Invert),
+            ],
+        ),
+        (
+            "move_right".to_string(),
+            vec![
+                VirtualBinding::Keyboard(PhysicalKey::Code(KeyCode::KeyD), AxisDirection::Normal),
+                VirtualBinding::Keyboard(PhysicalKey::Code(KeyCode::KeyA), AxisDirection::Invert),
+            ],
+        ),
+        (
+            "mouse_x".to_string(),
+            vec![VirtualBinding::MouseX(AxisDirection::Normal)],
+        ),
+        (
+            "mouse_y".to_string(),
+            vec![VirtualBinding::MouseY(AxisDirection::Normal)],
+        ),
+        (
+            "mouse_wheel".to_string(),
+            vec![VirtualBinding::MouseWheelY(AxisDirection::Normal)],
+        ),
+        (
+            "mouse_left".to_string(),
+            vec![VirtualBinding::MouseButton(
+                MouseButton::Left,
+                AxisDirection::Normal,
+            )],
+        ),
+        (
+            "mouse_right".to_string(),
+            vec![VirtualBinding::MouseButton(
+                MouseButton::Right,
+                AxisDirection::Normal,
+            )],
+        ),
+    ]));
+
+    let device_extensions = DeviceExtensions {
+        khr_swapchain: true,
+        ..Default::default()
+    };
+
+    let device_features = DeviceFeatures {
+        dynamic_rendering: true,
+        ..Default::default()
+    };
+
+    let vulkano_config = VulkanoConfig {
+        print_device_name: true,
+        device_extensions,
+        device_features,
+        ..Default::default()
+    };
+
+    let vulkano_context = VulkanoContext::new(vulkano_config);
+
+    let event_loop = EventLoop::with_user_event().build().unwrap();
     event_loop.set_control_flow(ControlFlow::Poll);
+    let proxy = event_loop.create_proxy();
 
-    let window_attributes = winit::window::Window::default_attributes()
-        .with_title("Rust ASH Test")
-        .with_inner_size(winit::dpi::PhysicalSize::new(
-            f64::from(800),
-            f64::from(600),
-        ));
+    let mut app = core::app::App::new(vulkano_context, input_manager, proxy);
 
-    let window = display::Window::new(window_attributes);
-    let mut app = display::App::new(window);
-
-    event_loop.run_app(&mut app).unwrap();
+    match event_loop.run_app(&mut app) {
+        Ok(_) => {}
+        Err(e) => {
+            tracing::error!("Error running old app: {e}");
+        }
+    }
 }
diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs
deleted file mode 100644
index cca608a..0000000
--- a/src/renderer/mod.rs
+++ /dev/null
@@ -1,9 +0,0 @@
-use std::sync::Arc;
-use ash::vk;
-
-pub mod vulkan;
-
-pub trait Renderable {
-    fn init(&mut self, device: &Arc<vulkan::VkDevice>, render_pass: &Arc<vulkan::VkRenderPass>) -> anyhow::Result<()>;
-    fn render(&self, device: &vulkan::VkDevice, swapchain: &vulkan::VkSwapchain, command_buffer: &vk::CommandBuffer) -> anyhow::Result<()>;
-}
\ No newline at end of file
diff --git a/src/renderer/vulkan/mod.rs b/src/renderer/vulkan/mod.rs
deleted file mode 100644
index a00ef58..0000000
--- a/src/renderer/vulkan/mod.rs
+++ /dev/null
@@ -1,45 +0,0 @@
-mod vk_render_context;
-pub use vk_render_context::VkRenderContext;
-
-mod vk_instance;
-pub use vk_instance::VkInstance;
-
-mod vk_surface;
-pub use vk_surface::{SwapchainSupportDetails, VkSurface};
-
-mod vk_physical_device;
-pub use vk_physical_device::VkPhysicalDevice;
-
-mod vk_device;
-pub use vk_device::VkDevice;
-
-mod vk_swapchain;
-pub use vk_swapchain::VkSwapchain;
-
-mod vk_shader_module;
-pub use vk_shader_module::VkShaderModule;
-
-mod vk_graphics_pipeline;
-pub use vk_graphics_pipeline::VkGraphicsPipeline;
-
-mod vk_render_pass;
-pub use vk_render_pass::VkRenderPass;
-
-mod vk_semaphore;
-pub use vk_semaphore::VkSemaphore;
-
-mod vk_command_pool;
-pub use vk_command_pool::VkCommandPool;
-
-mod vk_framebuffer;
-pub use vk_framebuffer::VkFramebuffer;
-
-mod vk_fence;
-pub use vk_fence::VkFence;
-
-mod utils;
-mod vertex;
-mod vk_buffer;
-pub use vk_buffer::VkBuffer;
-
-pub use vertex::Vertex;
diff --git a/src/renderer/vulkan/utils/layers.rs b/src/renderer/vulkan/utils/layers.rs
deleted file mode 100644
index 9d901fb..0000000
--- a/src/renderer/vulkan/utils/layers.rs
+++ /dev/null
@@ -1,51 +0,0 @@
-use std::ffi::CString;
-
-pub enum LayersSelector<'a> {
-    Nothing,
-    SpecificLayers(Vec<&'a str>),
-    All,
-}
-
-pub fn use_layers(entry: &ash::Entry, selector: LayersSelector) -> Vec<CString> {
-    let layers_available = get_layers_available(entry)
-        .iter()
-        .filter_map(|layer| {
-            layer
-                .layer_name_as_c_str()
-                .and_then(|layer_name| Ok(CString::from(layer_name)))
-                .ok()
-        })
-        .collect::<Vec<_>>();
-
-    match selector {
-        LayersSelector::Nothing => Vec::new(),
-        LayersSelector::SpecificLayers(layers) => select_layers(&layers_available, &layers),
-        LayersSelector::All => layers_available,
-    }
-}
-
-fn get_layers_available(entry: &ash::Entry) -> Vec<ash::vk::LayerProperties> {
-    unsafe {
-        entry
-            .enumerate_instance_layer_properties()
-            .unwrap_or_default()
-    }
-}
-
-fn select_layers(layers_available: &Vec<CString>, layers_to_select: &[&str]) -> Vec<CString> {
-    layers_to_select
-        .iter()
-        .filter_map(|layer_name| {
-            if layers_available.iter().any(|layer_available| {
-                layer_available
-                    .to_str()
-                    .and_then(|layer_available| Ok(layer_available.eq(*layer_name)))
-                    .unwrap_or(false)
-            }) {
-                CString::new(*layer_name).ok()
-            } else {
-                None
-            }
-        })
-        .collect::<Vec<_>>()
-}
diff --git a/src/renderer/vulkan/utils/mod.rs b/src/renderer/vulkan/utils/mod.rs
deleted file mode 100644
index 759c25e..0000000
--- a/src/renderer/vulkan/utils/mod.rs
+++ /dev/null
@@ -1 +0,0 @@
-pub mod layers;
diff --git a/src/renderer/vulkan/vertex.rs b/src/renderer/vulkan/vertex.rs
deleted file mode 100644
index 5d97213..0000000
--- a/src/renderer/vulkan/vertex.rs
+++ /dev/null
@@ -1,37 +0,0 @@
-use ash::vk;
-use std::mem::offset_of;
-
-#[derive(Default)]
-pub struct Vertex {
-    pub position: [f32; 2],
-    pub color: [f32; 3],
-}
-
-impl Vertex {
-    pub fn new(position: [f32; 2], color: [f32; 3]) -> Self {
-        Self { position, color }
-    }
-
-    pub fn get_binding_description() -> vk::VertexInputBindingDescription {
-        vk::VertexInputBindingDescription::default()
-            .binding(0)
-            .stride(size_of::<Self>() as u32)
-            .input_rate(vk::VertexInputRate::VERTEX)
-    }
-
-    pub fn get_attribute_descriptions() -> [vk::VertexInputAttributeDescription; 2] {
-        let position_attribute = vk::VertexInputAttributeDescription::default()
-            .binding(0)
-            .location(0)
-            .format(vk::Format::R32G32_SFLOAT)
-            .offset(offset_of!(Vertex, position) as u32);
-
-        let color_attribute = vk::VertexInputAttributeDescription::default()
-            .binding(0)
-            .location(1)
-            .format(vk::Format::R32G32B32_SFLOAT)
-            .offset(offset_of!(Vertex, color) as u32);
-
-        [position_attribute, color_attribute]
-    }
-}
\ No newline at end of file
diff --git a/src/renderer/vulkan/vk_buffer.rs b/src/renderer/vulkan/vk_buffer.rs
deleted file mode 100644
index b03dea4..0000000
--- a/src/renderer/vulkan/vk_buffer.rs
+++ /dev/null
@@ -1,31 +0,0 @@
-use std::sync::Arc;
-use ash::prelude::VkResult;
-use ash::vk;
-use crate::renderer::vulkan::VkDevice;
-
-pub struct VkBuffer {
-    device: Arc<VkDevice>,
-
-    handle: vk::Buffer,
-}
-
-impl VkBuffer {
-    pub fn new(device: &Arc<VkDevice>, info: &vk::BufferCreateInfo) -> VkResult<Self> {
-        let buffer = unsafe { device.handle.create_buffer(info, None)? };
-
-        Ok(VkBuffer { device: Arc::clone(device), handle: buffer })
-    }
-
-
-    pub fn mem_requirements(&self) -> vk::MemoryRequirements {
-        unsafe { self.device.handle.get_buffer_memory_requirements(self.handle) }
-    }
-}
-
-impl Drop for VkBuffer {
-    fn drop(&mut self) {
-        unsafe {
-            self.device.handle.destroy_buffer(self.handle, None);
-        }
-    }
-}
\ No newline at end of file
diff --git a/src/renderer/vulkan/vk_command_pool.rs b/src/renderer/vulkan/vk_command_pool.rs
deleted file mode 100644
index c657e97..0000000
--- a/src/renderer/vulkan/vk_command_pool.rs
+++ /dev/null
@@ -1,52 +0,0 @@
-use super::VkDevice;
-use ash::prelude::VkResult;
-use ash::vk;
-use std::sync::Arc;
-
-pub struct VkCommandPool {
-    device: Arc<VkDevice>,
-
-    pub handle: vk::CommandPool,
-}
-
-impl VkCommandPool {
-    pub fn new(device: &Arc<VkDevice>) -> VkResult<Self> {
-        let command_pool_info =
-            vk::CommandPoolCreateInfo::default()
-                .flags(vk::CommandPoolCreateFlags::RESET_COMMAND_BUFFER)
-                .queue_family_index(device.queue_family_index);
-        let command_pool = unsafe {
-            device
-                .handle
-                .create_command_pool(&command_pool_info, None)?
-        };
-        log::debug!("Command pool created ({command_pool:?})");
-
-        Ok(Self {
-            device: device.clone(),
-            handle: command_pool,
-        })
-    }
-
-    pub fn allocate_command_buffers_for_framebuffers(&self, framebuffers_count: u32) -> VkResult<Vec<vk::CommandBuffer>> {
-        let command_buffer_info = vk::CommandBufferAllocateInfo::default()
-            .command_pool(self.handle)
-            .level(vk::CommandBufferLevel::PRIMARY)
-            .command_buffer_count(framebuffers_count);
-
-        let command_buffers = unsafe {
-            self.device
-                .handle
-                .allocate_command_buffers(&command_buffer_info)?
-        };
-
-        Ok(command_buffers)
-    }
-}
-
-impl Drop for VkCommandPool {
-    fn drop(&mut self) {
-        unsafe { self.device.handle.destroy_command_pool(self.handle, None) };
-        log::debug!("Command pool destroyed ({:?})", self.handle);
-    }
-}
diff --git a/src/renderer/vulkan/vk_device.rs b/src/renderer/vulkan/vk_device.rs
deleted file mode 100644
index 9f14630..0000000
--- a/src/renderer/vulkan/vk_device.rs
+++ /dev/null
@@ -1,109 +0,0 @@
-use super::{VkInstance, VkPhysicalDevice};
-use ash::prelude::VkResult;
-use ash::vk;
-use std::sync::Arc;
-
-pub struct VkDevice {
-    instance: Arc<VkInstance>,
-    physical_device: Arc<VkPhysicalDevice>,
-
-    pub handle: ash::Device,
-    pub swapchain_loader: ash::khr::swapchain::Device,
-    pub queue_family_index: u32,
-
-    // Arc not used because vk::Queue is destroyed with Device automatically
-    // so any references of vk::Queue must be destroyed with VkDevice
-    queues: Vec<vk::Queue>,
-}
-
-impl VkDevice {
-    pub fn new_graphics_device(
-        instance: &Arc<VkInstance>,
-        physical_device: &Arc<VkPhysicalDevice>,
-        queue_family_index: u32,
-    ) -> anyhow::Result<Self> {
-        let device_extension_names_raw = [
-            ash::khr::swapchain::NAME.as_ptr(),
-            #[cfg(any(target_os = "macos", target_os = "ios"))]
-            ash::khr::portability_subset::NAME.as_ptr(),
-        ];
-        let features = vk::PhysicalDeviceFeatures {
-            shader_clip_distance: 1,
-            ..Default::default()
-        };
-
-        let queues_priorities = [1.0];
-        let queue_info = vk::DeviceQueueCreateInfo::default()
-            .queue_family_index(queue_family_index)
-            .queue_priorities(&queues_priorities);
-
-        let device_create_info = vk::DeviceCreateInfo::default()
-            .queue_create_infos(std::slice::from_ref(&queue_info))
-            .enabled_extension_names(&device_extension_names_raw)
-            .enabled_features(&features);
-
-        let device = unsafe {
-            instance
-                .handle
-                .create_device(physical_device.handle, &device_create_info, None)?
-        };
-        log::debug!("Device created ({:?})", device.handle());
-
-        let queues = queues_priorities
-            .iter()
-            .enumerate()
-            .map(|(index, _)| unsafe { device.get_device_queue(queue_family_index, index as u32) })
-            .collect::<Vec<_>>();
-
-        let swapchain_loader = ash::khr::swapchain::Device::new(&instance.handle, &device);
-
-        Ok(Self {
-            instance: Arc::clone(instance),
-            physical_device: Arc::clone(&physical_device),
-            handle: device,
-            swapchain_loader,
-            queue_family_index,
-            queues,
-        })
-    }
-
-    pub fn get_device_queue(&self, queue_index: u32) -> Option<&vk::Queue> {
-        self.queues.get(queue_index as usize)
-    }
-
-    pub fn create_command_pool(
-        &self,
-        info: &vk::CommandPoolCreateInfo,
-    ) -> VkResult<vk::CommandPool> {
-        let info = info.queue_family_index(self.queue_family_index);
-
-        unsafe { self.handle.create_command_pool(&info, None) }
-    }
-
-    pub fn allocate_command_buffers(
-        &self,
-        info: &vk::CommandBufferAllocateInfo,
-    ) -> VkResult<Vec<vk::CommandBuffer>> {
-        unsafe { self.handle.allocate_command_buffers(&info) }
-    }
-
-    pub fn create_fence(&self, info: &vk::FenceCreateInfo) -> VkResult<vk::Fence> {
-        unsafe { self.handle.create_fence(&info, None) }
-    }
-
-    pub fn create_semaphore(
-        &self,
-        info: &vk::SemaphoreCreateInfo,
-    ) -> VkResult<vk::Semaphore> {
-        unsafe { self.handle.create_semaphore(&info, None) }
-    }
-}
-
-impl Drop for VkDevice {
-    fn drop(&mut self) {
-        unsafe {
-            self.handle.destroy_device(None);
-            log::debug!("Device destroyed ({:?})", self.handle.handle());
-        }
-    }
-}
diff --git a/src/renderer/vulkan/vk_fence.rs b/src/renderer/vulkan/vk_fence.rs
deleted file mode 100644
index 9fb71ff..0000000
--- a/src/renderer/vulkan/vk_fence.rs
+++ /dev/null
@@ -1,30 +0,0 @@
-use super::VkDevice;
-use ash::vk;
-use std::sync::Arc;
-
-pub struct VkFence {
-    device: Arc<VkDevice>,
-
-    pub handle: vk::Fence,
-}
-
-impl VkFence {
-    pub fn new(device: &Arc<VkDevice>) -> anyhow::Result<Self> {
-        let fence_info = vk::FenceCreateInfo::default()
-            .flags(vk::FenceCreateFlags::SIGNALED);
-        let fence = unsafe { device.handle.create_fence(&fence_info, None)? };
-        log::debug!("Fence created ({fence:?})");
-
-        Ok(Self {
-            device: device.clone(),
-            handle: fence,
-        })
-    }
-}
-
-impl Drop for VkFence {
-    fn drop(&mut self) {
-        unsafe { self.device.handle.destroy_fence(self.handle, None) };
-        log::debug!("Fence destroyed ({:?})", self.handle);
-    }
-}
\ No newline at end of file
diff --git a/src/renderer/vulkan/vk_framebuffer.rs b/src/renderer/vulkan/vk_framebuffer.rs
deleted file mode 100644
index 8486fb5..0000000
--- a/src/renderer/vulkan/vk_framebuffer.rs
+++ /dev/null
@@ -1,44 +0,0 @@
-use super::{VkDevice, VkRenderPass, VkSwapchain};
-use ash::vk;
-use std::sync::Arc;
-
-pub struct VkFramebuffer {
-    device: Arc<VkDevice>,
-    image_view: Arc<vk::ImageView>,
-    render_pass: Arc<VkRenderPass>,
-
-    pub handle: vk::Framebuffer,
-}
-
-impl VkFramebuffer {
-    pub fn from_swapchain_image_view(
-        device: &Arc<VkDevice>,
-        render_pass: &Arc<VkRenderPass>,
-        image_view: &Arc<vk::ImageView>,
-        swapchain: &VkSwapchain,
-    ) -> anyhow::Result<Self> {
-        let attachments = [*image_view.as_ref()];
-        let framebuffer_info = vk::FramebufferCreateInfo::default()
-            .render_pass(render_pass.handle)
-            .width(swapchain.surface_resolution.width)
-            .height(swapchain.surface_resolution.height)
-            .attachments(&attachments)
-            .layers(1);
-
-        let framebuffer = unsafe { device.handle.create_framebuffer(&framebuffer_info, None)? };
-
-        Ok(Self {
-            device: device.clone(),
-            render_pass: render_pass.clone(),
-            image_view: image_view.clone(),
-
-            handle: framebuffer,
-        })
-    }
-}
-
-impl Drop for VkFramebuffer {
-    fn drop(&mut self) {
-        unsafe { self.device.handle.destroy_framebuffer(self.handle, None) };
-    }
-}
diff --git a/src/renderer/vulkan/vk_graphics_pipeline.rs b/src/renderer/vulkan/vk_graphics_pipeline.rs
deleted file mode 100644
index a9af298..0000000
--- a/src/renderer/vulkan/vk_graphics_pipeline.rs
+++ /dev/null
@@ -1,120 +0,0 @@
-use super::{VkDevice, VkRenderPass, VkShaderModule, VkSwapchain};
-use ash::vk;
-use std::ffi::CStr;
-use std::sync::Arc;
-
-pub struct VkGraphicsPipeline {
-    device: Arc<VkDevice>,
-    render_pass: Arc<VkRenderPass>,
-
-    pub pipeline_layout: vk::PipelineLayout,
-    pub pipeline: vk::Pipeline,
-    vertex_shader: VkShaderModule,
-    fragment_shader: VkShaderModule,
-}
-
-impl VkGraphicsPipeline {
-    pub fn new(
-        device: &Arc<VkDevice>,
-        render_pass: &Arc<VkRenderPass>,
-    ) -> anyhow::Result<Self> {
-        let shader_entry_name = CStr::from_bytes_with_nul(b"main\0")?;
-
-        let vert_shader_module =
-            VkShaderModule::from_spv_file(device, "res/shaders/main.vert.spv")?;
-
-        let vert_shader_info = vk::PipelineShaderStageCreateInfo::default()
-            .module(vert_shader_module.handle)
-            .name(shader_entry_name)
-            .stage(vk::ShaderStageFlags::VERTEX);
-
-        let frag_shader_module =
-            VkShaderModule::from_spv_file(device, "res/shaders/main.frag.spv")?;
-
-        let frag_shader_info = vk::PipelineShaderStageCreateInfo::default()
-            .module(frag_shader_module.handle)
-            .name(shader_entry_name)
-            .stage(vk::ShaderStageFlags::FRAGMENT);
-
-        let shader_stage_create_infos = [vert_shader_info, frag_shader_info];
-
-        let vertex_input_info = vk::PipelineVertexInputStateCreateInfo::default();
-
-        let input_assembly = vk::PipelineInputAssemblyStateCreateInfo::default()
-            .topology(vk::PrimitiveTopology::TRIANGLE_LIST);
-
-        let viewport_state = vk::PipelineViewportStateCreateInfo::default()
-            .viewport_count(1)
-            .scissor_count(1);
-
-        let rasterizer = vk::PipelineRasterizationStateCreateInfo::default()
-            .polygon_mode(vk::PolygonMode::FILL)
-            .cull_mode(vk::CullModeFlags::BACK)
-            .front_face(vk::FrontFace::CLOCKWISE)
-            .line_width(1.0);
-
-        let multisampling = vk::PipelineMultisampleStateCreateInfo::default()
-            .rasterization_samples(vk::SampleCountFlags::TYPE_1)
-            .min_sample_shading(1.0);
-
-        let color_blend_attachment = vk::PipelineColorBlendAttachmentState::default()
-            .color_write_mask(vk::ColorComponentFlags::RGBA);
-
-        let attachments = [color_blend_attachment];
-        let color_blending =
-            vk::PipelineColorBlendStateCreateInfo::default().attachments(&attachments);
-
-        let dynamic_state = vk::PipelineDynamicStateCreateInfo::default()
-            .dynamic_states(&[vk::DynamicState::VIEWPORT, vk::DynamicState::SCISSOR]);
-
-        let pipeline_layout_info = vk::PipelineLayoutCreateInfo::default();
-        let pipeline_layout = unsafe {
-            device
-                .handle
-                .create_pipeline_layout(&pipeline_layout_info, None)?
-        };
-        log::debug!("Pipeline layout created ({pipeline_layout:?})");
-
-        let pipeline_info = vk::GraphicsPipelineCreateInfo::default()
-            .stages(&shader_stage_create_infos)
-            .vertex_input_state(&vertex_input_info)
-            .input_assembly_state(&input_assembly)
-            .viewport_state(&viewport_state)
-            .rasterization_state(&rasterizer)
-            .multisample_state(&multisampling)
-            .color_blend_state(&color_blending)
-            .dynamic_state(&dynamic_state)
-            .layout(pipeline_layout)
-            .render_pass(render_pass.handle);
-        let pipeline = unsafe {
-            device
-                .handle
-                .create_graphics_pipelines(vk::PipelineCache::null(), &[pipeline_info], None)
-                .map_err(|(_, error)| error)?[0]
-        };
-        log::debug!("Pipeline created ({pipeline_layout:?})");
-
-        Ok(Self {
-            device: device.clone(),
-            render_pass: render_pass.clone(),
-            pipeline_layout,
-            pipeline,
-            vertex_shader: vert_shader_module,
-            fragment_shader: frag_shader_module,
-        })
-    }
-}
-
-impl Drop for VkGraphicsPipeline {
-    fn drop(&mut self) {
-        unsafe {
-            self.device.handle.destroy_pipeline(self.pipeline, None);
-            log::debug!("Pipeline destroyed ({:?})", self.pipeline);
-
-            self.device
-                .handle
-                .destroy_pipeline_layout(self.pipeline_layout, None);
-            log::debug!("Pipeline layout destroyed ({:?})", self.pipeline_layout);
-        }
-    }
-}
diff --git a/src/renderer/vulkan/vk_instance.rs b/src/renderer/vulkan/vk_instance.rs
deleted file mode 100644
index 101dec2..0000000
--- a/src/renderer/vulkan/vk_instance.rs
+++ /dev/null
@@ -1,128 +0,0 @@
-use crate::renderer::vulkan::{
-    utils::layers::{use_layers, LayersSelector},
-    VkPhysicalDevice,
-};
-use ash::khr::surface;
-use ash::{vk, Entry, Instance};
-use std::ffi::{c_char, CStr, CString};
-use std::sync::Arc;
-
-pub struct VkInstance {
-    pub entry: Entry,
-    pub handle: Instance,
-    pub surface_loader: surface::Instance,
-}
-
-impl VkInstance {
-    pub fn new(required_extensions: &Vec<*const c_char>) -> Self {
-        let entry = Entry::linked();
-
-        log::debug!("Initializing Vulkan instance");
-
-        if log::log_enabled!(log::Level::Debug) {
-            let layer_properties =
-                unsafe { entry.enumerate_instance_layer_properties() }.unwrap_or_default();
-
-            for layer_property in layer_properties {
-                let layer_extensions = unsafe {
-                    entry.enumerate_instance_extension_properties(
-                        layer_property.layer_name_as_c_str().ok(),
-                    )
-                }
-                    .unwrap_or_default();
-                log::debug!("{layer_property:#?} {layer_extensions:#?}");
-            }
-        }
-
-        {
-            let required_extensions = required_extensions
-                .iter()
-                .map(|str| unsafe { CStr::from_ptr(*str) })
-                .map(|cstr| cstr.to_string_lossy())
-                .collect::<Vec<_>>();
-            log::debug!(
-                "Required instance extensions: {}",
-                required_extensions.join(", ")
-            );
-        }
-
-        // Layers
-        #[allow(unused)]
-        let mut layer_selector = LayersSelector::Nothing;
-        #[cfg(debug_assertions)]
-        {
-            layer_selector = LayersSelector::SpecificLayers(vec![
-                "VK_LAYER_KHRONOS_validation",
-                "VK_LAYER_MANGOHUD_overlay_x86_64",
-                "VK_LAYER_NV_optimus",
-            ]);
-        }
-        let layers = use_layers(&entry, layer_selector);
-
-        {
-            let layers = layers
-                .iter()
-                .map(|layer| layer.to_string_lossy())
-                .collect::<Vec<_>>();
-            log::debug!("Selected debug layers : {}", layers.join(", "))
-        }
-
-        let layers_raw = layers.iter().map(|s| s.as_ptr()).collect::<Vec<_>>();
-
-        // App Info
-        let app_name = CString::new("VulkanTriangle").unwrap();
-        let appinfo = vk::ApplicationInfo::default()
-            .application_name(app_name.as_c_str())
-            .application_version(0)
-            .engine_name(app_name.as_c_str())
-            .engine_version(0)
-            .api_version(vk::make_api_version(0, 1, 0, 0));
-
-        let create_flags = if cfg!(any(target_os = "macos", target_os = "ios")) {
-            vk::InstanceCreateFlags::ENUMERATE_PORTABILITY_KHR
-        } else {
-            vk::InstanceCreateFlags::default()
-        };
-
-        // Instance Info
-        let create_info = vk::InstanceCreateInfo::default()
-            .application_info(&appinfo)
-            .enabled_layer_names(&layers_raw)
-            .enabled_extension_names(&required_extensions)
-            .flags(create_flags);
-
-        let instance: Instance = unsafe {
-            entry
-                .create_instance(&create_info, None)
-                .expect("Instance creation error")
-        };
-
-        let surface_loader = surface::Instance::new(&entry, &instance);
-
-        log::debug!("Vulkan instance created ({:?})", instance.handle());
-
-        Self {
-            entry,
-            handle: instance,
-            surface_loader,
-        }
-    }
-
-    pub fn get_physical_devices(instance: &Arc<Self>) -> Vec<VkPhysicalDevice> {
-        let physical_devices = unsafe { instance.handle.enumerate_physical_devices() };
-        physical_devices
-            .unwrap_or_default()
-            .iter()
-            .map(|physical_device| VkPhysicalDevice::new(&instance, *physical_device))
-            .collect()
-    }
-}
-
-impl Drop for VkInstance {
-    fn drop(&mut self) {
-        unsafe {
-            self.handle.destroy_instance(None);
-        }
-        log::debug!("Vulkan instance destroyed ({:?})", self.handle.handle());
-    }
-}
diff --git a/src/renderer/vulkan/vk_physical_device.rs b/src/renderer/vulkan/vk_physical_device.rs
deleted file mode 100644
index 3534f24..0000000
--- a/src/renderer/vulkan/vk_physical_device.rs
+++ /dev/null
@@ -1,85 +0,0 @@
-use std::sync::Arc;
-use super::{VkInstance, VkSurface};
-use ash::vk;
-
-pub struct VkPhysicalDevice {
-    instance: Arc<VkInstance>,
-    pub handle: vk::PhysicalDevice,
-
-    pub properties: vk::PhysicalDeviceProperties,
-    pub features: vk::PhysicalDeviceFeatures,
-    pub queue_family_properties: Vec<vk::QueueFamilyProperties>,
-}
-
-impl VkPhysicalDevice {
-    pub fn new(instance: &Arc<VkInstance>, physical_device: vk::PhysicalDevice) -> Self {
-        log::debug!("New physical device");
-        let device_properties = unsafe { instance.handle.get_physical_device_properties(physical_device) };
-        log::debug!("{device_properties:#?}");
-        let device_features = unsafe { instance.handle.get_physical_device_features(physical_device) };
-        log::debug!("{device_features:#?}");
-        let device_queue_families =
-            unsafe { instance.handle.get_physical_device_queue_family_properties(physical_device) };
-        log::debug!("{device_queue_families:#?}");
-
-        Self {
-            instance: Arc::clone(instance),
-            handle: physical_device,
-            properties: device_properties,
-            features: device_features,
-            queue_family_properties: device_queue_families,
-        }
-    }
-
-    pub fn find_queue_family_by(
-        &self,
-        queue_flags: Option<vk::QueueFlags>,
-        surface: Option<&VkSurface>,
-    ) -> Option<(u32, &vk::QueueFamilyProperties)> {
-        self.queue_family_properties.iter().enumerate().find_map(
-            |(index, queue_family_property)| {
-                let surface_check_passed = match surface {
-                    Some(surface) => surface
-                        .physical_device_queue_supported(self, index as u32)
-                        .unwrap_or(false),
-                    None => true,
-                };
-
-                let queue_flags_check_passed = match queue_flags {
-                    Some(queue_flags) => queue_family_property.queue_flags.contains(queue_flags),
-                    None => true,
-                };
-
-                if surface_check_passed && queue_flags_check_passed {
-                    Some((index as u32, queue_family_property))
-                } else {
-                    None
-                }
-            },
-        )
-    }
-
-    pub fn pick_physical_device_and_queue_by<'a>(
-        physical_devices: &'a Vec<VkPhysicalDevice>,
-        queue_flags: Option<vk::QueueFlags>,
-        surface: Option<&VkSurface>,
-    ) -> Option<(&'a VkPhysicalDevice, u32, &'a vk::QueueFamilyProperties)> {
-        physical_devices.iter().find_map(|physical_device| {
-            physical_device
-                .find_queue_family_by(queue_flags, surface)
-                .and_then(|(queue_index, queue_family_properties)| {
-                    Some((physical_device, queue_index, queue_family_properties))
-                })
-        })
-    }
-
-    pub fn priority(&self) -> usize {
-        match self.properties.device_type {
-            vk::PhysicalDeviceType::CPU => 1,
-            vk::PhysicalDeviceType::VIRTUAL_GPU => 2,
-            vk::PhysicalDeviceType::INTEGRATED_GPU => 3,
-            vk::PhysicalDeviceType::DISCRETE_GPU => 4,
-            _ => 0,
-        }
-    }
-}
diff --git a/src/renderer/vulkan/vk_render_context.rs b/src/renderer/vulkan/vk_render_context.rs
deleted file mode 100644
index 68dd304..0000000
--- a/src/renderer/vulkan/vk_render_context.rs
+++ /dev/null
@@ -1,206 +0,0 @@
-use super::{
-    VkCommandPool, VkDevice, VkFence, VkFramebuffer, VkGraphicsPipeline, VkInstance, VkPhysicalDevice,
-    VkRenderPass, VkSemaphore, VkSurface, VkSwapchain,
-};
-use ash::vk;
-use std::sync::Arc;
-use crate::renderer::Renderable;
-
-pub struct VkRenderContext {
-    instance: Arc<VkInstance>,
-    surface: Arc<VkSurface>,
-    device: Arc<VkDevice>,
-
-    swapchain: Arc<VkSwapchain>,
-    render_pass: Arc<VkRenderPass>,
-    framebuffers: Vec<Arc<VkFramebuffer>>,
-
-    command_pool: VkCommandPool,
-    command_buffers: Vec<vk::CommandBuffer>,
-    image_available_semaphore: VkSemaphore,
-    render_finished_semaphore: VkSemaphore,
-    in_flight_fence: VkFence,
-}
-
-impl VkRenderContext {
-    pub fn init(window: &crate::display::Window) -> anyhow::Result<Self> {
-        let required_extensions = window.required_extensions()?;
-
-        let instance = Arc::new(VkInstance::new(&required_extensions));
-        let surface = Arc::new(VkSurface::new(&window, instance.clone())?);
-
-        let mut physical_devices = VkInstance::get_physical_devices(&instance);
-        physical_devices.sort_by(|a, b| b.priority().cmp(&a.priority()));
-
-        let (physical_device, queue_family_index, properties) =
-            VkPhysicalDevice::pick_physical_device_and_queue_by(
-                &physical_devices,
-                Some(vk::QueueFlags::GRAPHICS),
-                Some(&surface),
-            )
-                .ok_or_else(|| anyhow::anyhow!("Unable to find physical device"))?;
-        log::debug!(
-            "Selected queue {properties:#?} for physical device {:?}",
-            physical_device.properties.device_name_as_c_str()
-        );
-
-        let device = Arc::new(VkDevice::new_graphics_device(
-            &instance,
-            &physical_device,
-            queue_family_index,
-        )?);
-
-        let swapchain = Arc::new(VkSwapchain::new(
-            &window,
-            &surface,
-            &device,
-            &physical_device,
-        )?);
-
-        let render_pass = Arc::new(VkRenderPass::new(&device, &swapchain)?);
-
-        let framebuffers = swapchain
-            .create_framebuffers(&render_pass)
-            .ok_or_else(|| anyhow::anyhow!("Failed to get framebuffers"))?;
-
-        let command_pool = VkCommandPool::new(&device)?;
-
-        // Destroyed with command pool
-        let command_buffers = command_pool
-            .allocate_command_buffers_for_framebuffers(framebuffers.len() as u32)?;
-
-        let image_available_semaphore = VkSemaphore::new(&device)?;
-        let render_finished_semaphore = VkSemaphore::new(&device)?;
-        let in_flight_fence = VkFence::new(&device)?;
-
-        Ok(Self {
-            instance,
-            surface,
-            device,
-
-            swapchain,
-            render_pass,
-            framebuffers,
-
-            command_pool,
-            command_buffers,
-
-            image_available_semaphore,
-            render_finished_semaphore,
-            in_flight_fence,
-        })
-    }
-
-    pub fn render(&mut self, scene: Option<&Box<dyn Renderable>>) -> anyhow::Result<()> {
-        unsafe { self.device.handle.wait_for_fences(&[self.in_flight_fence.handle], true, u64::MAX)? };
-        unsafe { self.device.handle.reset_fences(&[self.in_flight_fence.handle])? };
-
-        let (index, _) = self
-            .swapchain
-            .acquire_next_image(&self.image_available_semaphore)?;
-
-        // if self.swapchain.is_dirty() {
-        //     self.update_swapchain()?
-        // }
-
-        let command_buffer = self.command_buffers[index as usize];
-        unsafe { self.device.handle.reset_command_buffer(command_buffer, vk::CommandBufferResetFlags::default())? };
-
-        let render_area = vk::Rect2D::default().extent(self.swapchain.surface_resolution);
-        let clear_value = vk::ClearValue::default();
-        let command_buffer_begin_info = vk::CommandBufferBeginInfo::default();
-        unsafe {
-            self.device
-                .handle
-                .begin_command_buffer(command_buffer, &command_buffer_begin_info)?
-        };
-
-        let clear_values = [clear_value];
-        let framebuffer = self.framebuffers[index as usize].as_ref();
-        let render_pass_begin_info = vk::RenderPassBeginInfo::default()
-            .render_pass(self.render_pass.handle)
-            .framebuffer(framebuffer.handle)
-            .render_area(render_area)
-            .clear_values(&clear_values);
-
-        unsafe {
-            self.device.handle.cmd_begin_render_pass(
-                command_buffer,
-                &render_pass_begin_info,
-                vk::SubpassContents::INLINE,
-            );
-        };
-
-        if let Some(scene) = scene {
-            scene.render(&self.device, &self.swapchain, &command_buffer)?;
-        }
-
-        unsafe { self.device.handle.cmd_end_render_pass(command_buffer) };
-
-        unsafe { self.device.handle.end_command_buffer(command_buffer)? };
-
-        let wait_semaphores = [self.image_available_semaphore.handle];
-        let signal_semaphores = [self.render_finished_semaphore.handle];
-        let wait_stages = [vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT];
-        let command_buffers_to_submit = [command_buffer];
-        let submit_info = vk::SubmitInfo::default()
-            .wait_semaphores(&wait_semaphores)
-            .wait_dst_stage_mask(&wait_stages)
-            .command_buffers(&command_buffers_to_submit)
-            .signal_semaphores(&signal_semaphores);
-
-        let queue = self
-            .device
-            .get_device_queue(0)
-            .ok_or_else(|| anyhow::anyhow!("Failed to get a queue"))?;
-
-        unsafe {
-            self.device
-                .handle
-                .queue_submit(*queue, &[submit_info], self.in_flight_fence.handle)?
-        };
-
-        let swapchains = [self.swapchain.handle.unwrap()];
-        let indices = [index];
-        let present_info = vk::PresentInfoKHR::default()
-            .wait_semaphores(&signal_semaphores)
-            .swapchains(&swapchains)
-            .image_indices(&indices);
-
-        unsafe { self.device.swapchain_loader.queue_present(*queue, &present_info)? };
-
-        Ok(())
-    }
-
-    pub fn update_resolution(&mut self, width: u32, height: u32) -> anyhow::Result<()> {
-        match Arc::get_mut(&mut self.swapchain) {
-            Some(swapchain) => swapchain.update_resolution(width, height)?,
-            None => log::warn!("Impossible to get mutable swapchain"),
-        }
-
-        Ok(())
-    }
-
-    pub fn exit(&self) {
-        unsafe { self.device.handle.device_wait_idle().unwrap() }
-    }
-
-    pub fn init_scene(&self, scene: &mut Box<dyn Renderable>) -> anyhow::Result<()> {
-        scene.init(&self.device, &self.render_pass)
-    }
-
-    fn update_swapchain(&mut self) -> anyhow::Result<()> {
-        match Arc::get_mut(&mut self.swapchain) {
-            Some(swapchain) => {
-                swapchain.create_swapchain()?;
-
-                self.framebuffers = self.swapchain
-                    .create_framebuffers(&self.render_pass)
-                    .ok_or_else(|| anyhow::anyhow!("Failed to get framebuffers"))?;
-            }
-            None => log::warn!("Impossible to get mutable swapchain"),
-        }
-
-        Ok(())
-    }
-}
diff --git a/src/renderer/vulkan/vk_render_pass.rs b/src/renderer/vulkan/vk_render_pass.rs
deleted file mode 100644
index e9819fe..0000000
--- a/src/renderer/vulkan/vk_render_pass.rs
+++ /dev/null
@@ -1,56 +0,0 @@
-use super::{VkDevice, VkSwapchain};
-use ash::prelude::VkResult;
-use ash::vk;
-use std::sync::Arc;
-
-pub struct VkRenderPass {
-    device: Arc<VkDevice>,
-
-    pub handle: vk::RenderPass,
-}
-
-impl VkRenderPass {
-    pub fn new(device: &Arc<VkDevice>, swapchain: &Arc<VkSwapchain>) -> VkResult<Self> {
-        let color_attachment = vk::AttachmentDescription::default()
-            .format(swapchain.surface_format.format)
-            .samples(vk::SampleCountFlags::TYPE_1)
-            .load_op(vk::AttachmentLoadOp::CLEAR)
-            .store_op(vk::AttachmentStoreOp::STORE)
-            .stencil_load_op(vk::AttachmentLoadOp::DONT_CARE)
-            .stencil_store_op(vk::AttachmentStoreOp::DONT_CARE)
-            .initial_layout(vk::ImageLayout::UNDEFINED)
-            .final_layout(vk::ImageLayout::PRESENT_SRC_KHR);
-
-        let color_attachment_ref = vk::AttachmentReference::default()
-            .attachment(0)
-            .layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL);
-
-        let color_attachments = [color_attachment_ref];
-        let subpass = vk::SubpassDescription::default()
-            .pipeline_bind_point(vk::PipelineBindPoint::GRAPHICS)
-            .color_attachments(&color_attachments);
-
-        let attachments = [color_attachment];
-        let subpasses = [subpass];
-        let render_pass_info = vk::RenderPassCreateInfo::default()
-            .attachments(&attachments)
-            .subpasses(&subpasses);
-
-        let render_pass = unsafe { device.handle.create_render_pass(&render_pass_info, None)? };
-        log::debug!("Render pass created ({render_pass:?})");
-
-        Ok(Self {
-            device: device.clone(),
-            handle: render_pass,
-        })
-    }
-}
-
-impl Drop for VkRenderPass {
-    fn drop(&mut self) {
-        unsafe {
-            self.device.handle.destroy_render_pass(self.handle, None);
-            log::debug!("Render pass destroyed ({:?})", self.handle);
-        }
-    }
-}
diff --git a/src/renderer/vulkan/vk_semaphore.rs b/src/renderer/vulkan/vk_semaphore.rs
deleted file mode 100644
index facaf4e..0000000
--- a/src/renderer/vulkan/vk_semaphore.rs
+++ /dev/null
@@ -1,29 +0,0 @@
-use super::VkDevice;
-use ash::vk;
-use std::sync::Arc;
-
-pub struct VkSemaphore {
-    device: Arc<VkDevice>,
-
-    pub handle: vk::Semaphore,
-}
-
-impl VkSemaphore {
-    pub fn new(device: &Arc<VkDevice>) -> anyhow::Result<Self> {
-        let semaphore_info = vk::SemaphoreCreateInfo::default();
-        let semaphore = unsafe { device.handle.create_semaphore(&semaphore_info, None)? };
-        log::debug!("Semaphore created ({semaphore:?})");
-
-        Ok(Self {
-            device: device.clone(),
-            handle: semaphore,
-        })
-    }
-}
-
-impl Drop for VkSemaphore {
-    fn drop(&mut self) {
-        unsafe { self.device.handle.destroy_semaphore(self.handle, None) };
-        log::debug!("Semaphore destroyed ({:?})", self.handle);
-    }
-}
\ No newline at end of file
diff --git a/src/renderer/vulkan/vk_shader_module.rs b/src/renderer/vulkan/vk_shader_module.rs
deleted file mode 100644
index 96ddcc5..0000000
--- a/src/renderer/vulkan/vk_shader_module.rs
+++ /dev/null
@@ -1,42 +0,0 @@
-use super::VkDevice;
-use ash::vk;
-use std::path::Path;
-use std::sync::Arc;
-
-pub struct VkShaderModule {
-    device: Arc<VkDevice>,
-
-    pub handle: vk::ShaderModule,
-}
-
-impl VkShaderModule {
-    pub fn from_spv_file<P: AsRef<Path>>(device: &Arc<VkDevice>, path: P) -> anyhow::Result<Self> {
-        let mut file = std::fs::File::open(&path)?;
-        let frag_shader_str = ash::util::read_spv(&mut file)?;
-
-        let shader_create_info = vk::ShaderModuleCreateInfo::default().code(&frag_shader_str);
-        let shader_module = unsafe {
-            device
-                .handle
-                .create_shader_module(&shader_create_info, None)?
-        };
-        log::debug!(
-            "Shader module created ({shader_module:?}) from {:?}",
-            path.as_ref()
-        );
-
-        Ok(Self {
-            device: device.clone(),
-            handle: shader_module,
-        })
-    }
-}
-
-impl Drop for VkShaderModule {
-    fn drop(&mut self) {
-        unsafe {
-            self.device.handle.destroy_shader_module(self.handle, None);
-            log::debug!("Shader module destroyed ({:?})", self.handle);
-        }
-    }
-}
diff --git a/src/renderer/vulkan/vk_surface.rs b/src/renderer/vulkan/vk_surface.rs
deleted file mode 100644
index 9aace7c..0000000
--- a/src/renderer/vulkan/vk_surface.rs
+++ /dev/null
@@ -1,94 +0,0 @@
-use super::{VkInstance, VkPhysicalDevice};
-use ash::prelude::VkResult;
-use ash::vk;
-use std::sync::Arc;
-use winit::raw_window_handle::{HasDisplayHandle, HasWindowHandle};
-
-pub struct SwapchainSupportDetails(
-    pub Vec<vk::SurfaceFormatKHR>,
-    pub vk::SurfaceCapabilitiesKHR,
-    pub Vec<vk::PresentModeKHR>,
-);
-
-pub struct VkSurface {
-    instance: Arc<VkInstance>,
-
-    pub handle: vk::SurfaceKHR,
-}
-
-impl VkSurface {
-    pub fn new(window: &crate::display::Window, instance: Arc<VkInstance>) -> anyhow::Result<Self> {
-        let window_handle = window
-            .handle()
-            .ok_or_else(|| anyhow::anyhow!("Window handle is not available."))?;
-
-        let surface = unsafe {
-            ash_window::create_surface(
-                &instance.entry,
-                &instance.handle,
-                window_handle.display_handle()?.as_raw(),
-                window_handle.window_handle()?.as_raw(),
-                None,
-            )?
-        };
-
-        log::debug!("Surface created ({:?})", surface);
-
-        Ok(Self { instance, handle: surface })
-    }
-
-    pub fn physical_device_queue_supported(
-        &self,
-        physical_device: &VkPhysicalDevice,
-        queue_index: u32,
-    ) -> VkResult<bool> {
-        unsafe {
-            self.instance
-                .surface_loader
-                .get_physical_device_surface_support(
-                    physical_device.handle,
-                    queue_index,
-                    self.handle,
-                )
-        }
-    }
-
-    pub fn get_physical_device_swapchain_support_details(
-        &self,
-        physical_device: &VkPhysicalDevice,
-    ) -> VkResult<SwapchainSupportDetails> {
-        unsafe {
-            let formats = self
-                .instance
-                .surface_loader
-                .get_physical_device_surface_formats(physical_device.handle, self.handle)?;
-
-            let capabilities = self
-                .instance
-                .surface_loader
-                .get_physical_device_surface_capabilities(physical_device.handle, self.handle)?;
-
-            let present_modes = self
-                .instance
-                .surface_loader
-                .get_physical_device_surface_present_modes(physical_device.handle, self.handle)?;
-
-            Ok(SwapchainSupportDetails(
-                formats,
-                capabilities,
-                present_modes,
-            ))
-        }
-    }
-}
-
-impl Drop for VkSurface {
-    fn drop(&mut self) {
-        unsafe {
-            self.instance
-                .surface_loader
-                .destroy_surface(self.handle, None);
-        }
-        log::debug!("Surface destroyed ({:?})", self.handle);
-    }
-}
diff --git a/src/renderer/vulkan/vk_swapchain.rs b/src/renderer/vulkan/vk_swapchain.rs
deleted file mode 100644
index 758574a..0000000
--- a/src/renderer/vulkan/vk_swapchain.rs
+++ /dev/null
@@ -1,297 +0,0 @@
-use super::{SwapchainSupportDetails, VkDevice, VkFramebuffer, VkPhysicalDevice, VkRenderPass, VkSemaphore, VkSurface};
-use crate::display::Window;
-use ash::prelude::VkResult;
-use ash::vk;
-use std::sync::Arc;
-
-pub struct VkSwapchain {
-    surface: Arc<VkSurface>,
-    device: Arc<VkDevice>,
-
-    pub handle: Option<vk::SwapchainKHR>,
-    swapchain_support_details: SwapchainSupportDetails,
-
-    pub desired_image_count: u32,
-    pub surface_format: vk::SurfaceFormatKHR,
-    pub surface_resolution: vk::Extent2D,
-    pub new_requested_surface_resolution: Option<vk::Extent2D>,
-    pub present_mode: vk::PresentModeKHR,
-    pub pre_transform: vk::SurfaceTransformFlagsKHR,
-
-    pub present_images: Option<Vec<vk::Image>>,
-    pub present_image_views: Option<Vec<Arc<vk::ImageView>>>,
-}
-
-impl VkSwapchain {
-    pub fn new(
-        window: &Window,
-        surface: &Arc<VkSurface>,
-        device: &Arc<VkDevice>,
-        physical_device: &VkPhysicalDevice,
-    ) -> anyhow::Result<Self> {
-        log::debug!("Creating swapchain");
-
-        let window_size = window
-            .physical_size::<u32>()
-            .and_then(|size| {
-                Some(vk::Extent2D {
-                    width: size.width,
-                    height: size.height,
-                })
-            })
-            .ok_or_else(|| anyhow::anyhow!("Failed to get swapchain extent"))?;
-        log::debug!("Window size ({}x{})", window_size.width, window_size.height);
-
-        let swapchain_support_details =
-            surface.get_physical_device_swapchain_support_details(physical_device)?;
-        let SwapchainSupportDetails(surface_formats, surface_capabilities, present_modes) =
-            &swapchain_support_details;
-        log::debug!("Supported surface formats by physical device: {surface_formats:#?}");
-        log::debug!("Surface capabilities: {surface_capabilities:#?}");
-        log::debug!("Present modes: {present_modes:#?}");
-
-        let surface_format = Self::choose_surface_format(surface_formats)
-            .ok_or_else(|| anyhow::anyhow!("No available surface formats"))?;
-        let desired_image_count = Self::choose_desired_image_count(surface_capabilities);
-        let swapchain_extent = Self::choose_swapchain_extent(window_size, surface_capabilities);
-        let pre_transform = Self::choose_pre_transform(surface_capabilities);
-        let present_mode = Self::choose_present_mode(present_modes);
-
-        let mut swapchain = Self {
-            surface: surface.clone(),
-            device: device.clone(),
-
-            handle: None,
-            new_requested_surface_resolution: None,
-            swapchain_support_details,
-            desired_image_count,
-            surface_format,
-            surface_resolution: swapchain_extent,
-            present_mode,
-            pre_transform,
-            present_images: None,
-            present_image_views: None,
-        };
-
-        swapchain.create_swapchain()?;
-
-        Ok(swapchain)
-    }
-
-    pub fn create_swapchain(&mut self) -> VkResult<()> {
-        if let Some(new_requested_surface_resolution) = self.new_requested_surface_resolution {
-            self.surface_resolution = new_requested_surface_resolution;
-            self.new_requested_surface_resolution = None;
-        }
-
-        let mut swapchain_create_info = self.create_swapchain_info(&self.surface);
-
-        if let Some(old_swapchain) = self.handle {
-            swapchain_create_info.old_swapchain = old_swapchain;
-        }
-
-        let swapchain = unsafe {
-            self.device
-                .swapchain_loader
-                .create_swapchain(&swapchain_create_info, None)?
-        };
-
-        let present_images = unsafe {
-            self.device
-                .swapchain_loader
-                .get_swapchain_images(swapchain)?
-        };
-        let present_images_view = present_images
-            .iter()
-            .map(|i| {
-                self.create_present_image_view(*i)
-                    .expect("Failed to create image view")
-            })
-            .map(|i| Arc::new(i))
-            .collect::<Vec<_>>();
-
-        if log::log_enabled!(log::Level::Debug) {
-            let label = match self.handle {
-                None => "Swapchain created",
-                Some(_) => "Swapchain updated",
-            };
-            log::debug!("{label} ({swapchain:?}) : {swapchain_create_info:#?}");
-        }
-
-        self.handle = Some(swapchain);
-        self.present_image_views = Some(present_images_view);
-        self.present_images = Some(present_images);
-
-        Ok(())
-    }
-
-    pub fn create_framebuffers(
-        &self,
-        render_pass: &Arc<VkRenderPass>,
-    ) -> Option<Vec<Arc<VkFramebuffer>>> {
-        let present_image_views = self.present_image_views.as_ref()?;
-
-        Some(
-            present_image_views
-                .iter()
-                .map(|image_view| {
-                    VkFramebuffer::from_swapchain_image_view(
-                        &self.device,
-                        &render_pass,
-                        &image_view,
-                        &self,
-                    )
-                        .unwrap()
-                })
-                .map(|framebuffer| Arc::new(framebuffer))
-                .collect::<Vec<_>>(),
-        )
-    }
-
-    pub fn update_resolution(&mut self, width: u32, height: u32) -> VkResult<()> {
-        log::debug!("New resolution requested ({width}x{height})");
-
-        let chosen_extent = Self::choose_swapchain_extent(
-            vk::Extent2D { width, height },
-            &self.swapchain_support_details.1,
-        );
-        if chosen_extent.width != self.surface_resolution.width
-            || chosen_extent.height != self.surface_resolution.height
-        {
-            self.new_requested_surface_resolution = Some(chosen_extent);
-            log::debug!(
-                "New resolution submitted ({}x{})",
-                chosen_extent.width,
-                chosen_extent.height
-            );
-        } else {
-            log::debug!("New resolution skipped ({width}x{height}) : Same resolution");
-        }
-
-        Ok(())
-    }
-
-    pub fn acquire_next_image(&self, semaphore: &VkSemaphore) -> VkResult<(u32, bool)> {
-        unsafe {
-            self.device.swapchain_loader.acquire_next_image(
-                self.handle.unwrap(),
-                u64::MAX,
-                semaphore.handle,
-                vk::Fence::null(),
-            )
-        }
-    }
-
-    pub fn is_dirty(&self) -> bool {
-        self.new_requested_surface_resolution.is_some()
-    }
-
-    fn create_swapchain_info(&self, surface: &VkSurface) -> vk::SwapchainCreateInfoKHR {
-        vk::SwapchainCreateInfoKHR::default()
-            .surface(surface.handle)
-            .min_image_count(self.desired_image_count)
-            .image_color_space(self.surface_format.color_space)
-            .image_format(self.surface_format.format)
-            .image_extent(self.surface_resolution)
-            .image_usage(vk::ImageUsageFlags::COLOR_ATTACHMENT)
-            .image_sharing_mode(vk::SharingMode::EXCLUSIVE)
-            .pre_transform(self.pre_transform)
-            .composite_alpha(vk::CompositeAlphaFlagsKHR::OPAQUE)
-            .present_mode(self.present_mode)
-            .clipped(true)
-            .image_array_layers(1)
-    }
-
-    fn choose_swapchain_extent(
-        window_size: vk::Extent2D,
-        surface_capabilities: &vk::SurfaceCapabilitiesKHR,
-    ) -> vk::Extent2D {
-        vk::Extent2D {
-            width: window_size
-                .width
-                .max(surface_capabilities.min_image_extent.width)
-                .min(surface_capabilities.max_image_extent.width),
-            height: window_size
-                .height
-                .max(surface_capabilities.min_image_extent.height)
-                .min(surface_capabilities.max_image_extent.height),
-        }
-    }
-
-    fn choose_surface_format(
-        surface_formats: &Vec<vk::SurfaceFormatKHR>,
-    ) -> Option<vk::SurfaceFormatKHR> {
-        surface_formats.first().and_then(|f| Some(*f))
-    }
-
-    fn choose_desired_image_count(surface_capabilities: &vk::SurfaceCapabilitiesKHR) -> u32 {
-        let mut desired_image_count = surface_capabilities.min_image_count + 1;
-        if surface_capabilities.max_image_count > 0
-            && desired_image_count > surface_capabilities.max_image_count
-        {
-            desired_image_count = surface_capabilities.max_image_count;
-        }
-        desired_image_count
-    }
-
-    fn choose_pre_transform(
-        surface_capabilities: &vk::SurfaceCapabilitiesKHR,
-    ) -> vk::SurfaceTransformFlagsKHR {
-        if surface_capabilities
-            .supported_transforms
-            .contains(vk::SurfaceTransformFlagsKHR::IDENTITY)
-        {
-            vk::SurfaceTransformFlagsKHR::IDENTITY
-        } else {
-            surface_capabilities.current_transform
-        }
-    }
-
-    fn choose_present_mode(present_modes: &Vec<vk::PresentModeKHR>) -> vk::PresentModeKHR {
-        present_modes
-            .iter()
-            .cloned()
-            .find(|&mode| mode == vk::PresentModeKHR::MAILBOX)
-            .unwrap_or(vk::PresentModeKHR::FIFO)
-    }
-
-    fn create_present_image_view(&self, image: vk::Image) -> VkResult<vk::ImageView> {
-        let create_view_info = vk::ImageViewCreateInfo::default()
-            .view_type(vk::ImageViewType::TYPE_2D)
-            .format(self.surface_format.format)
-            .components(vk::ComponentMapping {
-                r: vk::ComponentSwizzle::IDENTITY,
-                g: vk::ComponentSwizzle::IDENTITY,
-                b: vk::ComponentSwizzle::IDENTITY,
-                a: vk::ComponentSwizzle::IDENTITY,
-            })
-            .subresource_range(vk::ImageSubresourceRange {
-                aspect_mask: vk::ImageAspectFlags::COLOR,
-                base_mip_level: 0,
-                level_count: 1,
-                base_array_layer: 0,
-                layer_count: 1,
-            })
-            .image(image);
-
-        unsafe {
-            self.device
-                .handle
-                .create_image_view(&create_view_info, None)
-        }
-    }
-}
-
-impl Drop for VkSwapchain {
-    fn drop(&mut self) {
-        if let Some(swapchain) = self.handle {
-            unsafe {
-                self.device
-                    .swapchain_loader
-                    .destroy_swapchain(swapchain, None);
-            }
-            self.handle = None;
-            log::debug!("Swapchain destroyed ({swapchain:?})");
-        }
-    }
-}
diff --git a/src/scene/mod.rs b/src/scene/mod.rs
deleted file mode 100644
index 46be2e9..0000000
--- a/src/scene/mod.rs
+++ /dev/null
@@ -1,4 +0,0 @@
-mod triangle;
-mod vertex;
-
-pub use triangle::TriangleScene;
diff --git a/src/scene/triangle.rs b/src/scene/triangle.rs
deleted file mode 100644
index f699930..0000000
--- a/src/scene/triangle.rs
+++ /dev/null
@@ -1,49 +0,0 @@
-use crate::renderer::vulkan::{VkDevice, VkGraphicsPipeline, VkRenderPass, VkSwapchain};
-use crate::renderer::Renderable;
-use ash::vk;
-use ash::vk::CommandBuffer;
-use std::sync::Arc;
-
-pub struct TriangleScene {
-    pipeline: Option<VkGraphicsPipeline>,
-}
-
-impl TriangleScene {
-    pub fn new() -> Self {
-        Self { pipeline: None }
-    }
-}
-
-impl Renderable for TriangleScene {
-    fn init(&mut self, device: &Arc<VkDevice>, render_pass: &Arc<VkRenderPass>) -> anyhow::Result<()> {
-        let pipeline = VkGraphicsPipeline::new(&device, &render_pass)?;
-        self.pipeline = Some(pipeline);
-
-        Ok(())
-    }
-
-    fn render(&self, device: &VkDevice, swapchain: &VkSwapchain, command_buffer: &CommandBuffer) -> anyhow::Result<()> {
-        unsafe {
-            device.handle.cmd_bind_pipeline(
-                *command_buffer,
-                vk::PipelineBindPoint::GRAPHICS,
-                self.pipeline.as_ref().unwrap().pipeline,
-            )
-        };
-
-        let viewport = vk::Viewport::default()
-            .width(swapchain.surface_resolution.width as f32)
-            .height(swapchain.surface_resolution.height as f32)
-            .max_depth(1.0);
-
-        unsafe { device.handle.cmd_set_viewport(*command_buffer, 0, &[viewport]) }
-
-        let scissor = swapchain.surface_resolution.into();
-
-        unsafe { device.handle.cmd_set_scissor(*command_buffer, 0, &[scissor]) }
-
-        unsafe { device.handle.cmd_draw(*command_buffer, 3, 1, 0, 0) };
-
-        Ok(())
-    }
-}
\ No newline at end of file
diff --git a/src/scene/vertex.rs b/src/scene/vertex.rs
deleted file mode 100644
index 0384d0c..0000000
--- a/src/scene/vertex.rs
+++ /dev/null
@@ -1,39 +0,0 @@
-use std::sync::Arc;
-use ash::prelude::VkResult;
-use ash::vk;
-use crate::renderer::vulkan::{Vertex, VkBuffer, VkDevice};
-
-#[derive(Default)]
-struct VertexScene {
-    vertices: Vec<Vertex>,
-
-    vertices_buffer: Option<VkBuffer>,
-}
-
-impl VertexScene {
-    pub fn new() -> Self {
-        let vertices = vec![
-            Vertex::new([0.0, -0.5], [1.0, 0.0, 0.0]),
-            Vertex::new([0.5, 0.5], [0.0, 1.0, 0.0]),
-            Vertex::new([-0.5, 0.5], [0.0, 0.0, 1.0]),
-        ];
-
-        Self {
-            vertices,
-            ..Default::default()
-        }
-    }
-
-
-    fn create_buffer(&mut self, device: &Arc<VkDevice>) -> VkResult<()> {
-        let buffer_info = vk::BufferCreateInfo::default()
-            .usage(vk::BufferUsageFlags::VERTEX_BUFFER)
-            .sharing_mode(vk::SharingMode::EXCLUSIVE)
-            .size(self.vertices.len() as u64 * size_of::<Vertex>() as u64);
-
-        let buffer = VkBuffer::new(device, &buffer_info)?;
-        self.vertices_buffer = Some(buffer);
-
-        Ok(())
-    }
-}
\ No newline at end of file