From 0597579115876943f37795ffe85d1612eaa7ea3f Mon Sep 17 00:00:00 2001 From: Florian RICHER Date: Sun, 8 Dec 2024 18:19:37 +0100 Subject: [PATCH] First work with vulkano --- Cargo.lock | 339 ++++++++++++---- Cargo.toml | 10 +- build.rs | 24 -- res/shaders/main.frag | 8 - res/shaders/main.vert | 1 - res/shaders/vertex.frag | 7 +- res/shaders/vertex.vert | 2 +- src/display/app.rs | 93 ----- src/display/mod.rs | 5 - src/display/window.rs | 65 --- src/main.rs | 17 +- src/renderer/app.rs | 421 ++++++++++++++++++++ src/renderer/mod.rs | 22 +- src/renderer/render_context.rs | 133 +++++++ src/renderer/scene.rs | 210 ++++++++++ src/renderer/vulkan/mod.rs | 45 --- src/renderer/vulkan/utils/layers.rs | 51 --- src/renderer/vulkan/utils/mod.rs | 1 - src/renderer/vulkan/vertex.rs | 37 -- src/renderer/vulkan/vk_buffer.rs | 31 -- src/renderer/vulkan/vk_command_pool.rs | 52 --- src/renderer/vulkan/vk_device.rs | 109 ----- src/renderer/vulkan/vk_fence.rs | 30 -- src/renderer/vulkan/vk_framebuffer.rs | 44 -- src/renderer/vulkan/vk_graphics_pipeline.rs | 120 ------ src/renderer/vulkan/vk_instance.rs | 128 ------ src/renderer/vulkan/vk_physical_device.rs | 85 ---- src/renderer/vulkan/vk_render_context.rs | 206 ---------- src/renderer/vulkan/vk_render_pass.rs | 56 --- src/renderer/vulkan/vk_semaphore.rs | 29 -- src/renderer/vulkan/vk_shader_module.rs | 42 -- src/renderer/vulkan/vk_surface.rs | 94 ----- src/renderer/vulkan/vk_swapchain.rs | 297 -------------- src/scene/mod.rs | 4 - src/scene/triangle.rs | 49 --- src/scene/vertex.rs | 39 -- 36 files changed, 1059 insertions(+), 1847 deletions(-) delete mode 100644 build.rs delete mode 100644 res/shaders/main.frag delete mode 100644 res/shaders/main.vert delete mode 100644 src/display/app.rs delete mode 100644 src/display/mod.rs delete mode 100644 src/display/window.rs create mode 100644 src/renderer/app.rs create mode 100644 src/renderer/render_context.rs create mode 100644 src/renderer/scene.rs delete mode 100644 src/renderer/vulkan/mod.rs delete mode 100644 src/renderer/vulkan/utils/layers.rs delete mode 100644 src/renderer/vulkan/utils/mod.rs delete mode 100644 src/renderer/vulkan/vertex.rs delete mode 100644 src/renderer/vulkan/vk_buffer.rs delete mode 100644 src/renderer/vulkan/vk_command_pool.rs delete mode 100644 src/renderer/vulkan/vk_device.rs delete mode 100644 src/renderer/vulkan/vk_fence.rs delete mode 100644 src/renderer/vulkan/vk_framebuffer.rs delete mode 100644 src/renderer/vulkan/vk_graphics_pipeline.rs delete mode 100644 src/renderer/vulkan/vk_instance.rs delete mode 100644 src/renderer/vulkan/vk_physical_device.rs delete mode 100644 src/renderer/vulkan/vk_render_context.rs delete mode 100644 src/renderer/vulkan/vk_render_pass.rs delete mode 100644 src/renderer/vulkan/vk_semaphore.rs delete mode 100644 src/renderer/vulkan/vk_shader_module.rs delete mode 100644 src/renderer/vulkan/vk_surface.rs delete mode 100644 src/renderer/vulkan/vk_swapchain.rs delete mode 100644 src/scene/mod.rs delete mode 100644 src/scene/triangle.rs delete mode 100644 src/scene/vertex.rs diff --git a/Cargo.lock b/Cargo.lock index 85ed865..7092bb5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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]] @@ -181,12 +173,6 @@ version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" -[[package]] -name = "block" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" - [[package]] name = "block2" version = "0.5.1" @@ -207,6 +193,20 @@ name = "bytemuck" version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8334215b81e418a0a7bdb8ef0849474f40bb10c8b71f1c4ed315cff49f32494d" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcfcc3cd946cb52f0bbfdbbcfa2f4e24f75ebb6c0e1002f7c25904fada18b9ec" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] [[package]] name = "bytes" @@ -270,33 +270,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] -name = "cocoa" -version = "0.25.0" +name = "cmake" +version = "0.1.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6140449f97a6e97f9511815c5632d84c8aacf8ac271ad77c559218161a1373c" +checksum = "c682c223677e0e5b6b7f63a64b9351844c3f1b1678a68b7ee617e30fb082620e" dependencies = [ - "bitflags 1.3.2", - "block", - "cocoa-foundation", - "core-foundation", - "core-graphics", - "foreign-types", - "libc", - "objc", -] - -[[package]] -name = "cocoa-foundation" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c6234cbb2e4c785b456c0644748b1ac416dd045799740356f8363dfe00c93f7" -dependencies = [ - "bitflags 1.3.2", - "block", - "core-foundation", - "core-graphics-types", - "libc", - "objc", + "cc", ] [[package]] @@ -364,12 +343,27 @@ dependencies = [ "libc", ] +[[package]] +name = "crossbeam-queue" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df0346b5d5e76ac2fe4e327c5fd1118d6be7c51dfb18f9b7922923f287471e35" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-utils" version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + [[package]] name = "cursor-icon" version = "1.1.0" @@ -491,10 +485,15 @@ dependencies = [ ] [[package]] -name = "glob" -version = "0.3.1" +name = "half" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" +dependencies = [ + "bytemuck", + "cfg-if", + "crunchy", +] [[package]] name = "hashbrown" @@ -502,6 +501,12 @@ version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3" +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + [[package]] name = "hermit-abi" version = "0.4.0" @@ -530,6 +535,12 @@ version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +[[package]] +name = "itoa" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" + [[package]] name = "jni" version = "0.21.1" @@ -603,21 +614,22 @@ version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +[[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" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" -[[package]] -name = "malloc_buf" -version = "0.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" -dependencies = [ - "libc", -] - [[package]] name = "memchr" version = "2.7.4" @@ -633,6 +645,12 @@ dependencies = [ "libc", ] +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "ndk" version = "0.9.0" @@ -644,7 +662,8 @@ dependencies = [ "log", "ndk-sys", "num_enum", - "raw-window-handle", + "raw-window-handle 0.5.2", + "raw-window-handle 0.6.2", "thiserror", ] @@ -663,6 +682,16 @@ dependencies = [ "jni-sys", ] +[[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 = "num_enum" version = "0.7.3" @@ -684,15 +713,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" @@ -920,6 +940,29 @@ 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.7", + "smallvec", + "windows-targets 0.52.6", +] + [[package]] name = "percent-encoding" version = "2.3.1" @@ -1009,6 +1052,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "raw-window-handle" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2ff9a1f06a88b01621b7ae906ef0211290d1c8a168a15542486a8f61c0833b9" + [[package]] name = "raw-window-handle" version = "0.6.2" @@ -1017,14 +1066,13 @@ checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" [[package]] name = "raw-window-metal" -version = "0.4.0" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76e8caa82e31bb98fee12fa8f051c94a6aa36b07cddb03f0d4fc558988360ff1" +checksum = "b2000e45d7daa9b6d946e88dfa1d7ae330424a81918a6545741821c989eb80a9" dependencies = [ - "cocoa", - "core-graphics", - "objc", - "raw-window-handle", + "objc2", + "objc2-foundation", + "objc2-quartz-core", ] [[package]] @@ -1074,16 +1122,24 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +[[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", + "vulkano", + "vulkano-shaders", "winit", ] @@ -1100,6 +1156,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + [[package]] name = "same-file" version = "1.0.6" @@ -1115,6 +1177,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" @@ -1148,6 +1216,39 @@ dependencies = [ "syn", ] +[[package]] +name = "serde_json" +version = "1.0.133" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" +dependencies = [ + "itoa", + "memchr", + "ryu", + "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 = "shlex" version = "1.3.0" @@ -1163,6 +1264,12 @@ dependencies = [ "autocfg", ] +[[package]] +name = "slabbin" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd33b7a607dbd960b5e78bb4740d1f86e84250eb03a12960ee1482c2a256063" + [[package]] name = "smallvec" version = "1.13.2" @@ -1240,6 +1347,16 @@ dependencies = [ "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 = "tiny-skia" version = "0.11.4" @@ -1328,6 +1445,71 @@ 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.34.0" +source = "git+https://github.com/vulkano-rs/vulkano.git?branch=master#23606f05825adf5212f104ead9e95f9d325db1aa" +dependencies = [ + "ahash", + "ash", + "bytemuck", + "crossbeam-queue", + "half", + "heck", + "indexmap", + "libloading", + "nom", + "once_cell", + "parking_lot", + "proc-macro2", + "quote", + "raw-window-handle 0.6.2", + "raw-window-metal", + "serde", + "serde_json", + "slabbin", + "smallvec", + "thread_local", + "vk-parse", + "vulkano-macros", + "x11-dl", + "x11rb", +] + +[[package]] +name = "vulkano-macros" +version = "0.34.0" +source = "git+https://github.com/vulkano-rs/vulkano.git?branch=master#23606f05825adf5212f104ead9e95f9d325db1aa" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "vulkano-shaders" +version = "0.34.0" +source = "git+https://github.com/vulkano-rs/vulkano.git?branch=master#23606f05825adf5212f104ead9e95f9d325db1aa" +dependencies = [ + "ahash", + "heck", + "proc-macro2", + "quote", + "shaderc", + "syn", + "vulkano", +] + [[package]] name = "walkdir" version = "2.5.0" @@ -1784,7 +1966,8 @@ dependencies = [ "orbclient", "percent-encoding", "pin-project", - "raw-window-handle", + "raw-window-handle 0.5.2", + "raw-window-handle 0.6.2", "redox_syscall 0.4.1", "rustix", "sctk-adwaita", @@ -1872,6 +2055,18 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" +[[package]] +name = "xml-rs" +version = "0.8.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea8b391c9a790b496184c29f7f93b9ed5b16abb306c05415b68bcc16e4d06432" + +[[package]] +name = "xmlparser" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66fee0b777b0f5ac1c69bb06d361268faafa61cd4682ae064a171c16c433e9e4" + [[package]] name = "zerocopy" version = "0.7.35" diff --git a/Cargo.toml b/Cargo.toml index 3865769..3cf3338 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,13 +7,11 @@ publish = false [dependencies] anyhow = "1.0" -winit = { version = "0.30", features = ["rwh_06"] } -ash = { version = "0.38", default-features = false, features = ["linked", "debug", "std"] } -ash-window = "0.13" +winit = { version = "0.30", features = ["rwh_05"] } + +vulkano = { git = "https://github.com/vulkano-rs/vulkano.git", branch = "master" } +vulkano-shaders = { git = "https://github.com/vulkano-rs/vulkano.git", branch = "master" } # Log and tracing log = "0.4" env_logger = "0.11.5" - -[build-dependencies] -glob = "0.3" \ No newline at end of file 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/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..720d192 100644 --- a/res/shaders/vertex.frag +++ b/res/shaders/vertex.frag @@ -1,10 +1,9 @@ #version 450 -#extension GL_ARB_separate_shader_objects: enable -layout (location = 0) in vec3 fragColor; +layout (location = 0) in vec3 color; -layout (location = 0) out vec4 outColor; +layout (location = 0) out vec4 f_color; void main() { - outColor = vec4(fragColor, 1.0); + f_color = vec4(color, 1.0); } \ No newline at end of file diff --git a/res/shaders/vertex.vert b/res/shaders/vertex.vert index d7b1aac..3adb319 100644 --- a/res/shaders/vertex.vert +++ b/res/shaders/vertex.vert @@ -1 +1 @@ -#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 vec2 position; layout (location = 1) in vec3 color; layout (location = 0) out vec3 fragColor; void main() { gl_Position = vec4(position, 0.0, 1.0); fragColor = color; } \ No newline at end of file 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, - scene: Option>, -} - -impl App { - pub fn new(window: Window) -> Self { - Self { - window, - render_context: None, - scene: None, - } - } - - pub fn set_scene(&mut self, mut scene: Box) { - 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, - 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> { - 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(&self) -> Option> { - self.window_attributes - .inner_size - .and_then(|size| Some(size.to_physical::

(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/main.rs b/src/main.rs index 2f48cfe..6c01c40 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,24 +1,15 @@ +use std::error::Error; use winit::event_loop::{ControlFlow, EventLoop}; -mod display; mod renderer; -mod scene; -fn main() { +fn main() -> Result<(), impl Error> { env_logger::init(); let event_loop = EventLoop::new().unwrap(); event_loop.set_control_flow(ControlFlow::Poll); - 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 = renderer::App::new(&event_loop); - let window = display::Window::new(window_attributes); - let mut app = display::App::new(window); - - event_loop.run_app(&mut app).unwrap(); + event_loop.run_app(&mut app) } diff --git a/src/renderer/app.rs b/src/renderer/app.rs new file mode 100644 index 0000000..438f946 --- /dev/null +++ b/src/renderer/app.rs @@ -0,0 +1,421 @@ +use std::sync::Arc; +use vulkano::command_buffer::allocator::StandardCommandBufferAllocator; +use vulkano::device::{Device, DeviceCreateInfo, DeviceExtensions, DeviceFeatures, Queue, QueueCreateInfo, QueueFlags}; +use vulkano::device::physical::PhysicalDeviceType; +use vulkano::instance::{Instance, InstanceCreateFlags, InstanceCreateInfo}; +use vulkano::memory::allocator::StandardMemoryAllocator; +use vulkano::swapchain::{acquire_next_image, Surface, SwapchainCreateInfo, SwapchainPresentInfo}; +use vulkano::{sync, Validated, Version, VulkanError, VulkanLibrary}; +use vulkano::command_buffer::{AutoCommandBufferBuilder, CommandBufferUsage, RenderingAttachmentInfo, RenderingInfo}; +use vulkano::render_pass::{AttachmentLoadOp, AttachmentStoreOp}; +use vulkano::sync::GpuFuture; +use winit::application::ApplicationHandler; +use winit::event::WindowEvent; +use winit::event_loop::{ActiveEventLoop, EventLoop}; +use winit::window::{Window, WindowId}; +use crate::renderer::render_context::RenderContext; +use crate::renderer::{window_size_dependent_setup, Scene}; + +pub struct App { + instance: Arc, + device: Arc, + queue: Arc, + memory_allocator: Arc, + command_buffer_allocator: Arc, + rcx: Option, + scene: Option, +} + +impl App { + pub fn new(event_loop: &EventLoop<()>) -> Self { + let library = VulkanLibrary::new().unwrap(); + + // The first step of any Vulkan program is to create an instance. + // + // When we create an instance, we have to pass a list of extensions that we want to enable. + // + // All the window-drawing functionalities are part of non-core extensions that we need to + // enable manually. To do so, we ask `Surface` for the list of extensions required to draw + // to a window. + let required_extensions = Surface::required_extensions(event_loop).unwrap(); + + // Now creating the instance. + let instance = Instance::new( + library, + InstanceCreateInfo { + // Enable enumerating devices that use non-conformant Vulkan implementations. + // (e.g. MoltenVK) + flags: InstanceCreateFlags::ENUMERATE_PORTABILITY, + enabled_extensions: required_extensions, + enabled_layers: vec![ + String::from("VK_LAYER_KHRONOS_validation"), + String::from("VK_LAYER_MANGOHUD_overlay_x86_64"), + String::from("VK_LAYER_NV_optimus"), + ], + ..Default::default() + }, + ) + .unwrap(); + + // Choose device extensions that we're going to use. In order to present images to a + // surface, we need a `Swapchain`, which is provided by the `khr_swapchain` extension. + let mut device_extensions = DeviceExtensions { + khr_swapchain: true, + ..DeviceExtensions::empty() + }; + + // We then choose which physical device to use. First, we enumerate all the available + // physical devices, then apply filters to narrow them down to those that can support our + // needs. + let (physical_device, queue_family_index) = instance + .enumerate_physical_devices() + .unwrap() + .filter(|p| { + // For this example, we require at least Vulkan 1.3, or a device that has the + // `khr_dynamic_rendering` extension available. + p.api_version() >= Version::V1_3 || p.supported_extensions().khr_dynamic_rendering + }) + .filter(|p| { + // Some devices may not support the extensions or features that your application, + // or report properties and limits that are not sufficient for your application. + // These should be filtered out here. + p.supported_extensions().contains(&device_extensions) + }) + .filter_map(|p| { + // For each physical device, we try to find a suitable queue family that will + // execute our draw commands. + // + // Devices can provide multiple queues to run commands in parallel (for example a + // draw queue and a compute queue), similar to CPU threads. This is something you + // have to have to manage manually in Vulkan. Queues of the same type belong to the + // same queue family. + // + // Here, we look for a single queue family that is suitable for our purposes. In a + // real-world application, you may want to use a separate dedicated transfer queue + // to handle data transfers in parallel with graphics operations. You may also need + // a separate queue for compute operations, if your application uses those. + p.queue_family_properties() + .iter() + .enumerate() + .position(|(i, q)| { + // We select a queue family that supports graphics operations. When drawing + // to a window surface, as we do in this example, we also need to check + // that queues in this queue family are capable of presenting images to the + // surface. + q.queue_flags.intersects(QueueFlags::GRAPHICS) + && p.presentation_support(i as u32, event_loop).unwrap() + }) + // The code here searches for the first queue family that is suitable. If none + // is found, `None` is returned to `filter_map`, which disqualifies this + // physical device. + .map(|i| (p, i as u32)) + }) + // All the physical devices that pass the filters above are suitable for the + // application. However, not every device is equal, some are preferred over others. + // Now, we assign each physical device a score, and pick the device with the lowest + // ("best") score. + // + // In this example, we simply select the best-scoring device to use in the application. + // In a real-world setting, you may want to use the best-scoring device only as a + // "default" or "recommended" device, and let the user choose the device themself. + .min_by_key(|(p, _)| { + // We assign a lower score to device types that are likely to be faster/better. + match p.properties().device_type { + PhysicalDeviceType::DiscreteGpu => 0, + PhysicalDeviceType::IntegratedGpu => 1, + PhysicalDeviceType::VirtualGpu => 2, + PhysicalDeviceType::Cpu => 3, + PhysicalDeviceType::Other => 4, + _ => 5, + } + }) + .expect("no suitable physical device found"); + + // Some little debug infos. + println!( + "Using device: {} (type: {:?})", + physical_device.properties().device_name, + physical_device.properties().device_type, + ); + + // If the selected device doesn't have Vulkan 1.3 available, then we need to enable the + // `khr_dynamic_rendering` extension manually. This extension became a core part of Vulkan + // in version 1.3 and later, so it's always available then and it does not need to be + // enabled. We can be sure that this extension will be available on the selected physical + // device, because we filtered out unsuitable devices in the device selection code above. + if physical_device.api_version() < Version::V1_3 { + device_extensions.khr_dynamic_rendering = true; + } + + // Now initializing the device. This is probably the most important object of Vulkan. + // + // An iterator of created queues is returned by the function alongside the device. + let (device, mut queues) = Device::new( + // Which physical device to connect to. + physical_device, + DeviceCreateInfo { + // The list of queues that we are going to use. Here we only use one queue, from + // the previously chosen queue family. + queue_create_infos: vec![QueueCreateInfo { + queue_family_index, + ..Default::default() + }], + + // A list of optional features and extensions that our program needs to work + // correctly. Some parts of the Vulkan specs are optional and must be enabled + // manually at device creation. In this example the only things we are going to + // need are the `khr_swapchain` extension that allows us to draw to a window, and + // `khr_dynamic_rendering` if we don't have Vulkan 1.3 available. + enabled_extensions: device_extensions, + + // In order to render with Vulkan 1.3's dynamic rendering, we need to enable it + // here. Otherwise, we are only allowed to render with a render pass object, as in + // the standard triangle example. The feature is required to be supported by the + // device if it supports Vulkan 1.3 and higher, or if the `khr_dynamic_rendering` + // extension is available, so we don't need to check for support. + enabled_features: DeviceFeatures { + dynamic_rendering: true, + ..DeviceFeatures::empty() + }, + + ..Default::default() + }, + ) + .unwrap(); + + // Since we can request multiple queues, the `queues` variable is in fact an iterator. We + // only use one queue in this example, so we just retrieve the first and only element of + // the iterator. + let queue = queues.next().unwrap(); + + let memory_allocator = Arc::new(StandardMemoryAllocator::new_default(device.clone())); + + // Before we can start creating and recording command buffers, we need a way of allocating + // them. Vulkano provides a command buffer allocator, which manages raw Vulkan command + // pools underneath and provides a safe interface for them. + let command_buffer_allocator = Arc::new(StandardCommandBufferAllocator::new( + device.clone(), + Default::default(), + )); + + Self { + instance, + device, + queue, + memory_allocator, + command_buffer_allocator, + rcx: None, + scene: None, + } + } +} + +impl ApplicationHandler for App { + fn resumed(&mut self, event_loop: &ActiveEventLoop) { + let window_attributes = winit::window::Window::default_attributes() + .with_title("Rust ASH Test") + .with_inner_size(winit::dpi::PhysicalSize::new( + f64::from(800), + f64::from(600), + )); + + let window = Arc::new( + event_loop + .create_window(window_attributes) + .unwrap(), + ); + + let surface = Surface::from_window(self.instance.clone(), window.clone()).unwrap(); + + self.rcx = Some(RenderContext::new(window, surface, &self.device)); + self.scene = Some(Scene::initialize(&self.device, &self.rcx.as_ref().unwrap().swapchain, &self.memory_allocator)); + } + + fn window_event(&mut self, event_loop: &ActiveEventLoop, _id: WindowId, event: WindowEvent) { + let rcx = self.rcx.as_mut().unwrap(); + + match event { + WindowEvent::CloseRequested => { + log::debug!("The close button was pressed; stopping"); + event_loop.exit(); + } + WindowEvent::Resized(_) => { + rcx.recreate_swapchain = true; + } + WindowEvent::RedrawRequested => { + let window_size = rcx.window.inner_size(); + + // Do not draw the frame when the screen size is zero. On Windows, this can occur + // when minimizing the application. + if window_size.width == 0 || window_size.height == 0 { + return; + } + + // It is important to call this function from time to time, otherwise resources + // will keep accumulating and you will eventually reach an out of memory error. + // Calling this function polls various fences in order to determine what the GPU + // has already processed, and frees the resources that are no longer needed. + rcx.previous_frame_end.as_mut().unwrap().cleanup_finished(); + + // Whenever the window resizes we need to recreate everything dependent on the + // window size. In this example that includes the swapchain, the framebuffers and + // the dynamic state viewport. + if rcx.recreate_swapchain { + let (new_swapchain, new_images) = rcx + .swapchain + .recreate(SwapchainCreateInfo { + image_extent: window_size.into(), + ..rcx.swapchain.create_info() + }) + .expect("failed to recreate swapchain"); + + rcx.swapchain = new_swapchain; + + // Now that we have new swapchain images, we must create new image views from + // them as well. + rcx.attachment_image_views = window_size_dependent_setup(&new_images); + + rcx.viewport.extent = window_size.into(); + + rcx.recreate_swapchain = false; + } + + // Before we can draw on the output, we have to *acquire* an image from the + // swapchain. If no image is available (which happens if you submit draw commands + // too quickly), then the function will block. This operation returns the index of + // the image that we are allowed to draw upon. + // + // This function can block if no image is available. The parameter is an optional + // timeout after which the function call will return an error. + let (image_index, suboptimal, acquire_future) = match acquire_next_image( + rcx.swapchain.clone(), + None, + ) + .map_err(Validated::unwrap) + { + Ok(r) => r, + Err(VulkanError::OutOfDate) => { + rcx.recreate_swapchain = true; + return; + } + Err(e) => panic!("failed to acquire next image: {e}"), + }; + + // `acquire_next_image` can be successful, but suboptimal. This means that the + // swapchain image will still work, but it may not display correctly. With some + // drivers this can be when the window resizes, but it may not cause the swapchain + // to become out of date. + if suboptimal { + rcx.recreate_swapchain = true; + } + + // In order to draw, we have to record a *command buffer*. The command buffer + // object holds the list of commands that are going to be executed. + // + // Recording a command buffer is an expensive operation (usually a few hundred + // microseconds), but it is known to be a hot path in the driver and is expected to + // be optimized. + // + // Note that we have to pass a queue family when we create the command buffer. The + // command buffer will only be executable on that given queue family. + let mut builder = AutoCommandBufferBuilder::primary( + self.command_buffer_allocator.clone(), + self.queue.queue_family_index(), + CommandBufferUsage::OneTimeSubmit, + ) + .unwrap(); + + builder + // Before we can draw, we have to *enter a render pass*. We specify which + // attachments we are going to use for rendering here, which needs to match + // what was previously specified when creating the pipeline. + .begin_rendering(RenderingInfo { + // As before, we specify one color attachment, but now we specify the image + // view to use as well as how it should be used. + color_attachments: vec![Some(RenderingAttachmentInfo { + // `Clear` means that we ask the GPU to clear the content of this + // attachment at the start of rendering. + load_op: AttachmentLoadOp::Clear, + // `Store` means that we ask the GPU to store the rendered output in + // the attachment image. We could also ask it to discard the result. + store_op: AttachmentStoreOp::Store, + // The value to clear the attachment with. Here we clear it with a blue + // color. + // + // Only attachments that have `AttachmentLoadOp::Clear` are provided + // with clear values, any others should use `None` as the clear value. + clear_value: Some([0.0, 0.0, 0.0, 1.0].into()), + ..RenderingAttachmentInfo::image_view( + // We specify image view corresponding to the currently acquired + // swapchain image, to use for this attachment. + rcx.attachment_image_views[image_index as usize].clone(), + ) + })], + ..Default::default() + }) + .unwrap() + // We are now inside the first subpass of the render pass. + // + // TODO: Document state setting and how it affects subsequent draw commands. + .set_viewport(0, [rcx.viewport.clone()].into_iter().collect()) + .unwrap(); + + if let Some(scene) = self.scene.as_ref() { + scene.render(&mut builder); + } + + builder + // We leave the render pass. + .end_rendering() + .unwrap(); + + // Finish recording the command buffer by calling `end`. + let command_buffer = builder.build().unwrap(); + + let future = rcx + .previous_frame_end + .take() + .unwrap() + .join(acquire_future) + .then_execute(self.queue.clone(), command_buffer) + .unwrap() + // The color output is now expected to contain our triangle. But in order to + // show it on the screen, we have to *present* the image by calling + // `then_swapchain_present`. + // + // This function does not actually present the image immediately. Instead it + // submits a present command at the end of the queue. This means that it will + // only be presented once the GPU has finished executing the command buffer + // that draws the triangle. + .then_swapchain_present( + self.queue.clone(), + SwapchainPresentInfo::swapchain_image_index( + rcx.swapchain.clone(), + image_index, + ), + ) + .then_signal_fence_and_flush(); + + match future.map_err(Validated::unwrap) { + Ok(future) => { + rcx.previous_frame_end = Some(future.boxed()); + } + Err(VulkanError::OutOfDate) => { + rcx.recreate_swapchain = true; + rcx.previous_frame_end = Some(sync::now(self.device.clone()).boxed()); + } + Err(e) => { + println!("failed to flush future: {e}"); + rcx.previous_frame_end = Some(sync::now(self.device.clone()).boxed()); + } + } + } + _ => {} + } + } + + fn about_to_wait(&mut self, _event_loop: &ActiveEventLoop) { + let rcx = self.rcx.as_mut().unwrap(); + rcx.window.request_redraw(); + } +} diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index cca608a..e72d412 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -1,9 +1,19 @@ +mod render_context; +mod app; +pub use app::App; + +mod scene; +pub use scene::Scene; + use std::sync::Arc; -use ash::vk; +use vulkano::image::Image; +use vulkano::image::view::ImageView; -pub mod vulkan; -pub trait Renderable { - fn init(&mut self, device: &Arc, render_pass: &Arc) -> anyhow::Result<()>; - fn render(&self, device: &vulkan::VkDevice, swapchain: &vulkan::VkSwapchain, command_buffer: &vk::CommandBuffer) -> anyhow::Result<()>; -} \ No newline at end of file +/// This function is called once during initialization, then again whenever the window is resized. +fn window_size_dependent_setup(images: &[Arc]) -> Vec> { + images + .iter() + .map(|image| ImageView::new_default(image.clone()).unwrap()) + .collect::>() +} diff --git a/src/renderer/render_context.rs b/src/renderer/render_context.rs new file mode 100644 index 0000000..cd2bc24 --- /dev/null +++ b/src/renderer/render_context.rs @@ -0,0 +1,133 @@ +use std::sync::Arc; +use vulkano::device::Device; +use vulkano::image::{Image, ImageUsage}; +use vulkano::image::view::ImageView; +use vulkano::pipeline::graphics::subpass::PipelineRenderingCreateInfo; +use vulkano::pipeline::graphics::viewport::Viewport; +use vulkano::pipeline::GraphicsPipeline; +use vulkano::render_pass::{Framebuffer, RenderPass}; +use vulkano::swapchain::{Surface, Swapchain, SwapchainCreateInfo}; +use vulkano::sync; +use vulkano::sync::GpuFuture; +use winit::window::Window; +use crate::renderer::window_size_dependent_setup; + +pub struct RenderContext { + pub(super) window: Arc, + pub(super) swapchain: Arc, + pub(super) attachment_image_views: Vec>, + pub(super) viewport: Viewport, + pub(super) recreate_swapchain: bool, + pub(super) previous_frame_end: Option>, +} + +impl RenderContext { + pub fn new(window: Arc, surface: Arc, device: &Arc) -> Self { + let window_size = window.inner_size(); + + + // Before we can draw on the surface, we have to create what is called a swapchain. + // Creating a swapchain allocates the color buffers that will contain the image that will + // ultimately be visible on the screen. These images are returned alongside the swapchain. + let (swapchain, images) = { + // Querying the capabilities of the surface. When we create the swapchain we can only + // pass values that are allowed by the capabilities. + let surface_capabilities = device + .physical_device() + .surface_capabilities(&surface, Default::default()) + .unwrap(); + + // Choosing the internal format that the images will have. + let (image_format, _) = device + .physical_device() + .surface_formats(&surface, Default::default()) + .unwrap()[0]; + + // Please take a look at the docs for the meaning of the parameters we didn't mention. + Swapchain::new( + device.clone(), + surface, + SwapchainCreateInfo { + // Some drivers report an `min_image_count` of 1, but fullscreen mode requires + // at least 2. Therefore we must ensure the count is at least 2, otherwise the + // program would crash when entering fullscreen mode on those drivers. + min_image_count: surface_capabilities.min_image_count.max(2), + + image_format, + + // The size of the window, only used to initially setup the swapchain. + // + // NOTE: + // On some drivers the swapchain extent is specified by + // `surface_capabilities.current_extent` and the swapchain size must use this + // extent. This extent is always the same as the window size. + // + // However, other drivers don't specify a value, i.e. + // `surface_capabilities.current_extent` is `None`. These drivers will allow + // anything, but the only sensible value is the window size. + // + // Both of these cases need the swapchain to use the window size, so we just + // use that. + image_extent: window_size.into(), + + image_usage: ImageUsage::COLOR_ATTACHMENT, + + // The alpha mode indicates how the alpha value of the final image will behave. + // For example, you can choose whether the window will be opaque or + // transparent. + composite_alpha: surface_capabilities + .supported_composite_alpha + .into_iter() + .next() + .unwrap(), + + ..Default::default() + }, + ) + .unwrap() + }; + + // When creating the swapchain, we only created plain images. To use them as an attachment + // for rendering, we must wrap then in an image view. + // + // Since we need to draw to multiple images, we are going to create a different image view + // for each image. + let attachment_image_views = window_size_dependent_setup(&images); + + // Dynamic viewports allow us to recreate just the viewport when the window is resized. + // Otherwise we would have to recreate the whole pipeline. + let viewport = Viewport { + offset: [0.0, 0.0], + extent: window_size.into(), + depth_range: 0.0..=1.0, + }; + + // In some situations, the swapchain will become invalid by itself. This includes for + // example when the window is resized (as the images of the swapchain will no longer match + // the window's) or, on Android, when the application went to the background and goes back + // to the foreground. + // + // In this situation, acquiring a swapchain image or presenting it will return an error. + // Rendering to an image of that swapchain will not produce any error, but may or may not + // work. To continue rendering, we need to recreate the swapchain by creating a new + // swapchain. Here, we remember that we need to do this for the next loop iteration. + let recreate_swapchain = false; + + // In the loop below we are going to submit commands to the GPU. Submitting a command + // produces an object that implements the `GpuFuture` trait, which holds the resources for + // as long as they are in use by the GPU. + // + // Destroying the `GpuFuture` blocks until the GPU is finished executing it. In order to + // avoid that, we store the submission of the previous frame here. + let previous_frame_end = Some(sync::now(device.clone()).boxed()); + + Self { + window, + swapchain, + attachment_image_views, + viewport, + recreate_swapchain, + previous_frame_end, + } + } +} diff --git a/src/renderer/scene.rs b/src/renderer/scene.rs new file mode 100644 index 0000000..174ba7e --- /dev/null +++ b/src/renderer/scene.rs @@ -0,0 +1,210 @@ +use std::sync::Arc; +use vulkano::buffer::{Buffer, BufferContents, BufferCreateInfo, BufferUsage, Subbuffer}; +use vulkano::command_buffer::{AutoCommandBufferBuilder, PrimaryAutoCommandBuffer}; +use vulkano::device::Device; +use vulkano::memory::allocator::{AllocationCreateInfo, MemoryTypeFilter, StandardMemoryAllocator}; +use vulkano::pipeline::graphics::vertex_input::{Vertex, VertexDefinition}; +use vulkano::pipeline::{DynamicState, GraphicsPipeline, PipelineLayout, PipelineShaderStageCreateInfo}; +use vulkano::pipeline::graphics::color_blend::{ColorBlendAttachmentState, ColorBlendState}; +use vulkano::pipeline::graphics::GraphicsPipelineCreateInfo; +use vulkano::pipeline::graphics::input_assembly::InputAssemblyState; +use vulkano::pipeline::graphics::multisample::MultisampleState; +use vulkano::pipeline::graphics::rasterization::RasterizationState; +use vulkano::pipeline::graphics::subpass::PipelineRenderingCreateInfo; +use vulkano::pipeline::graphics::viewport::ViewportState; +use vulkano::pipeline::layout::PipelineDescriptorSetLayoutCreateInfo; +use vulkano::swapchain::Swapchain; + +// We use `#[repr(C)]` here to force rustc to use a defined layout for our data, as the default +// representation has *no guarantees*. +#[derive(BufferContents, Vertex)] +#[repr(C)] +struct MyVertex { + #[format(R32G32_SFLOAT)] + position: [f32; 2], + + #[format(R32G32B32_SFLOAT)] + color: [f32; 3], +} + +pub struct Scene { + pipeline: Arc, + vertex_buffer: Subbuffer<[MyVertex]>, +} + +impl Scene { + pub fn initialize(device: &Arc, swapchain: &Arc, memory_allocator: &Arc) -> Scene { + // The next step is to create the shaders. + // + // The raw shader creation API provided by the vulkano library is unsafe for various + // reasons, so The `shader!` macro provides a way to generate a Rust module from GLSL + // source - in the example below, the source is provided as a string input directly to the + // shader, but a path to a source file can be provided as well. Note that the user must + // specify the type of shader (e.g. "vertex", "fragment", etc.) using the `ty` option of + // the macro. + // + // The items generated by the `shader!` macro include a `load` function which loads the + // shader using an input logical device. The module also includes type definitions for + // layout structures defined in the shader source, for example uniforms and push constants. + // + // A more detailed overview of what the `shader!` macro generates can be found in the + // vulkano-shaders crate docs. You can view them at https://docs.rs/vulkano-shaders/ + mod vs { + vulkano_shaders::shader! { + ty: "vertex", + path: r"res/shaders/vertex.vert", + } + } + + mod fs { + vulkano_shaders::shader! { + ty: "fragment", + path: r"res/shaders/vertex.frag", + } + } + + // Before we draw, we have to create what is called a **pipeline**. A pipeline describes + // how a GPU operation is to be performed. It is similar to an OpenGL program, but it also + // contains many settings for customization, all baked into a single object. For drawing, + // we create a **graphics** pipeline, but there are also other types of pipeline. + let pipeline = { + // First, we load the shaders that the pipeline will use: the vertex shader and the + // fragment shader. + // + // A Vulkan shader can in theory contain multiple entry points, so we have to specify + // which one. + let vs = vs::load(device.clone()) + .unwrap() + .entry_point("main") + .unwrap(); + let fs = fs::load(device.clone()) + .unwrap() + .entry_point("main") + .unwrap(); + + // Automatically generate a vertex input state from the vertex shader's input + // interface, that takes a single vertex buffer containing `Vertex` structs. + let vertex_input_state = MyVertex::per_vertex().definition(&vs).unwrap(); + + // Make a list of the shader stages that the pipeline will have. + let stages = [ + PipelineShaderStageCreateInfo::new(vs), + PipelineShaderStageCreateInfo::new(fs), + ]; + + // We must now create a **pipeline layout** object, which describes the locations and + // types of descriptor sets and push constants used by the shaders in the pipeline. + // + // Multiple pipelines can share a common layout object, which is more efficient. The + // shaders in a pipeline must use a subset of the resources described in its pipeline + // layout, but the pipeline layout is allowed to contain resources that are not present + // in the shaders; they can be used by shaders in other pipelines that share the same + // layout. Thus, it is a good idea to design shaders so that many pipelines have common + // resource locations, which allows them to share pipeline layouts. + let layout = PipelineLayout::new( + device.clone(), + // Since we only have one pipeline in this example, and thus one pipeline layout, + // we automatically generate the creation info for it from the resources used in + // the shaders. In a real application, you would specify this information manually + // so that you can re-use one layout in multiple pipelines. + PipelineDescriptorSetLayoutCreateInfo::from_stages(&stages) + .into_pipeline_layout_create_info(device.clone()) + .unwrap(), + ) + .unwrap(); + + // We describe the formats of attachment images where the colors, depth and/or stencil + // information will be written. The pipeline will only be usable with this particular + // configuration of the attachment images. + let subpass = PipelineRenderingCreateInfo { + // We specify a single color attachment that will be rendered to. When we begin + // rendering, we will specify a swapchain image to be used as this attachment, so + // here we set its format to be the same format as the swapchain. + color_attachment_formats: vec![Some(swapchain.image_format())], + ..Default::default() + }; + + // Finally, create the pipeline. + GraphicsPipeline::new( + device.clone(), + None, + GraphicsPipelineCreateInfo { + stages: stages.into_iter().collect(), + // How vertex data is read from the vertex buffers into the vertex shader. + vertex_input_state: Some(vertex_input_state), + // How vertices are arranged into primitive shapes. The default primitive shape + // is a triangle. + input_assembly_state: Some(InputAssemblyState::default()), + // How primitives are transformed and clipped to fit the framebuffer. We use a + // resizable viewport, set to draw over the entire window. + viewport_state: Some(ViewportState::default()), + // How polygons are culled and converted into a raster of pixels. The default + // value does not perform any culling. + rasterization_state: Some(RasterizationState::default()), + // How multiple fragment shader samples are converted to a single pixel value. + // The default value does not perform any multisampling. + multisample_state: Some(MultisampleState::default()), + // How pixel values are combined with the values already present in the + // framebuffer. The default value overwrites the old value with the new one, + // without any blending. + color_blend_state: Some(ColorBlendState::with_attachment_states( + subpass.color_attachment_formats.len() as u32, + ColorBlendAttachmentState::default(), + )), + // Dynamic states allows us to specify parts of the pipeline settings when + // recording the command buffer, before we perform drawing. Here, we specify + // that the viewport should be dynamic. + dynamic_state: [DynamicState::Viewport].into_iter().collect(), + subpass: Some(subpass.into()), + ..GraphicsPipelineCreateInfo::layout(layout) + }, + ) + .unwrap() + }; + + // We now create a buffer that will store the shape of our triangle. + let vertices = [ + MyVertex { + position: [-0.5, -0.25], + color: [1.0, 0.0, 0.0], + }, + MyVertex { + position: [0.0, 0.5], + color: [0.0, 1.0, 0.0], + }, + MyVertex { + position: [0.25, -0.1], + color: [0.0, 0.0, 1.0], + }, + ]; + 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() + }, + vertices, + ) + .unwrap(); + + Self { + pipeline, + vertex_buffer, + } + } + + pub fn render(&self, builder: &mut AutoCommandBufferBuilder) { + builder.bind_pipeline_graphics(self.pipeline.clone()) + .unwrap() + .bind_vertex_buffers(0, self.vertex_buffer.clone()) + .unwrap(); + + // We add a draw command. + unsafe { builder.draw(self.vertex_buffer.len() as u32, 1, 0, 0) }.unwrap(); + } +} \ 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 { - 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::>(); - - 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 { - unsafe { - entry - .enumerate_instance_layer_properties() - .unwrap_or_default() - } -} - -fn select_layers(layers_available: &Vec, layers_to_select: &[&str]) -> Vec { - 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::>() -} 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::() 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, - - handle: vk::Buffer, -} - -impl VkBuffer { - pub fn new(device: &Arc, info: &vk::BufferCreateInfo) -> VkResult { - 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, - - pub handle: vk::CommandPool, -} - -impl VkCommandPool { - pub fn new(device: &Arc) -> VkResult { - 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> { - 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, - physical_device: Arc, - - 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, -} - -impl VkDevice { - pub fn new_graphics_device( - instance: &Arc, - physical_device: &Arc, - queue_family_index: u32, - ) -> anyhow::Result { - 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::>(); - - 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 { - 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> { - unsafe { self.handle.allocate_command_buffers(&info) } - } - - pub fn create_fence(&self, info: &vk::FenceCreateInfo) -> VkResult { - unsafe { self.handle.create_fence(&info, None) } - } - - pub fn create_semaphore( - &self, - info: &vk::SemaphoreCreateInfo, - ) -> VkResult { - 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, - - pub handle: vk::Fence, -} - -impl VkFence { - pub fn new(device: &Arc) -> anyhow::Result { - 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, - image_view: Arc, - render_pass: Arc, - - pub handle: vk::Framebuffer, -} - -impl VkFramebuffer { - pub fn from_swapchain_image_view( - device: &Arc, - render_pass: &Arc, - image_view: &Arc, - swapchain: &VkSwapchain, - ) -> anyhow::Result { - 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, - render_pass: Arc, - - pub pipeline_layout: vk::PipelineLayout, - pub pipeline: vk::Pipeline, - vertex_shader: VkShaderModule, - fragment_shader: VkShaderModule, -} - -impl VkGraphicsPipeline { - pub fn new( - device: &Arc, - render_pass: &Arc, - ) -> anyhow::Result { - 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::>(); - 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::>(); - log::debug!("Selected debug layers : {}", layers.join(", ")) - } - - let layers_raw = layers.iter().map(|s| s.as_ptr()).collect::>(); - - // 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) -> Vec { - 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, - pub handle: vk::PhysicalDevice, - - pub properties: vk::PhysicalDeviceProperties, - pub features: vk::PhysicalDeviceFeatures, - pub queue_family_properties: Vec, -} - -impl VkPhysicalDevice { - pub fn new(instance: &Arc, 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, - 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, - queue_flags: Option, - 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, - surface: Arc, - device: Arc, - - swapchain: Arc, - render_pass: Arc, - framebuffers: Vec>, - - command_pool: VkCommandPool, - command_buffers: Vec, - image_available_semaphore: VkSemaphore, - render_finished_semaphore: VkSemaphore, - in_flight_fence: VkFence, -} - -impl VkRenderContext { - pub fn init(window: &crate::display::Window) -> anyhow::Result { - 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>) -> 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) -> 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, - - pub handle: vk::RenderPass, -} - -impl VkRenderPass { - pub fn new(device: &Arc, swapchain: &Arc) -> VkResult { - 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, - - pub handle: vk::Semaphore, -} - -impl VkSemaphore { - pub fn new(device: &Arc) -> anyhow::Result { - 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, - - pub handle: vk::ShaderModule, -} - -impl VkShaderModule { - pub fn from_spv_file>(device: &Arc, path: P) -> anyhow::Result { - 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, - pub vk::SurfaceCapabilitiesKHR, - pub Vec, -); - -pub struct VkSurface { - instance: Arc, - - pub handle: vk::SurfaceKHR, -} - -impl VkSurface { - pub fn new(window: &crate::display::Window, instance: Arc) -> anyhow::Result { - 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 { - 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 { - 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, - device: Arc, - - pub handle: Option, - 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, - pub present_mode: vk::PresentModeKHR, - pub pre_transform: vk::SurfaceTransformFlagsKHR, - - pub present_images: Option>, - pub present_image_views: Option>>, -} - -impl VkSwapchain { - pub fn new( - window: &Window, - surface: &Arc, - device: &Arc, - physical_device: &VkPhysicalDevice, - ) -> anyhow::Result { - log::debug!("Creating swapchain"); - - let window_size = window - .physical_size::() - .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::>(); - - 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, - ) -> Option>> { - 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::>(), - ) - } - - 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, - ) -> Option { - 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 { - 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 { - 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, -} - -impl TriangleScene { - pub fn new() -> Self { - Self { pipeline: None } - } -} - -impl Renderable for TriangleScene { - fn init(&mut self, device: &Arc, render_pass: &Arc) -> 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, - - vertices_buffer: Option, -} - -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) -> 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::() as u64); - - let buffer = VkBuffer::new(device, &buffer_info)?; - self.vertices_buffer = Some(buffer); - - Ok(()) - } -} \ No newline at end of file