First work with vulkano

This commit is contained in:
Florian RICHER 2024-12-08 18:19:37 +01:00
parent cbadffc41f
commit 0597579115
Signed by: florian.richer
GPG key ID: C73D37CBED7BFC77
36 changed files with 1059 additions and 1847 deletions

339
Cargo.lock generated
View file

@ -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"

View file

@ -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"

View file

@ -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();
}
}

View file

@ -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);
}

View file

@ -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]; }

View file

@ -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);
}

View file

@ -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() { #extension GL_ARB_separate_shader_objects: enable #extension GL_ARB_separate_shader_objects: enable , 0.0, 1.0); fragColor = color; }
#version 450 #extension GL_ARB_separate_shader_objects: enable layout (location = 1) in vec3 color; layout (location = 0) out vec3 fragColor; void main() { #extension GL_ARB_separate_shader_objects: enable // NOTE: names must match the `Vertex` struct in Rust , 0.0, 1.0); fragColor = color; }

View file

@ -1,93 +0,0 @@
use crate::display::window::Window;
use crate::renderer::{vulkan::VkRenderContext, Renderable};
use winit::application::ApplicationHandler;
use winit::event::WindowEvent;
use winit::event_loop::ActiveEventLoop;
use winit::window::WindowId;
use crate::scene::TriangleScene;
pub struct App {
window: Window,
render_context: Option<VkRenderContext>,
scene: Option<Box<dyn Renderable>>,
}
impl App {
pub fn new(window: Window) -> Self {
Self {
window,
render_context: None,
scene: None,
}
}
pub fn set_scene(&mut self, mut scene: Box<dyn Renderable>) {
let result = self.render_context.as_mut()
.ok_or_else(|| anyhow::anyhow!("No render context"))
.and_then(|render_context| render_context.init_scene(&mut scene));
match result {
Ok(_) => self.scene = Some(scene),
Err(err) => log::warn!("{err}"),
}
}
}
impl ApplicationHandler for App {
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
self.window
.create_window(event_loop)
.map_err(|err| format!("Failed to create window: {}", err))
.unwrap();
self.render_context = VkRenderContext::init(&self.window).ok();
let scene = TriangleScene::new();
self.set_scene(Box::new(scene));
}
fn window_event(&mut self, event_loop: &ActiveEventLoop, _id: WindowId, event: WindowEvent) {
match event {
WindowEvent::CloseRequested => {
match self.render_context.as_ref() {
Some(render_context) => render_context.exit(),
None => log::warn!("Window closed but no render context found"),
};
log::debug!("The close button was pressed; stopping");
event_loop.exit();
}
WindowEvent::Resized(size) => {
match self.render_context.as_mut() {
Some(render_context) => {
if let Err(error) =
render_context.update_resolution(size.width, size.height)
{
log::error!(
"Failed to update resolution of render context : {}",
error
);
}
}
None => log::warn!("Window resized but no render context found"),
};
}
WindowEvent::RedrawRequested => {
if !event_loop.exiting() {
match self.render_context.as_mut() {
Some(render_context) => {
if let Err(error) = render_context.render(self.scene.as_ref()) {
log::error!("Failed to render with render context : {}", error);
event_loop.exit();
}
}
None => log::warn!("Window resized but no render context found"),
};
}
self.window.request_redraw();
}
_ => {}
}
}
}

View file

@ -1,5 +0,0 @@
mod app;
mod window;
pub use app::App;
pub use window::Window;

View file

@ -1,65 +0,0 @@
use std::ffi::c_char;
use winit::dpi::Pixel;
use winit::event_loop::ActiveEventLoop;
use winit::raw_window_handle::HasDisplayHandle;
pub struct Window {
handle: Option<winit::window::Window>,
window_attributes: winit::window::WindowAttributes,
}
impl Window {
pub fn new(window_attributes: winit::window::WindowAttributes) -> Self {
Self {
handle: None,
window_attributes,
}
}
pub fn create_window(&mut self, event_loop: &ActiveEventLoop) -> anyhow::Result<()> {
let window = event_loop.create_window(self.window_attributes.clone())?;
self.handle = Some(window);
Ok(())
}
pub fn required_extensions(&self) -> anyhow::Result<Vec<*const c_char>> {
let display_handle = self
.handle
.as_ref()
.ok_or_else(|| anyhow::anyhow!("Window not found"))?
.display_handle()?;
#[allow(unused_mut)]
let mut extension_names =
ash_window::enumerate_required_extensions(display_handle.as_raw())?.to_vec();
// TODO: Move this because is not related to Window extensions
#[cfg(any(target_os = "macos", target_os = "ios"))]
{
extension_names.push(ash::khr::portability_enumeration::NAME.as_ptr());
// Enabling this extension is a requirement when using `VK_KHR_portability_subset`
extension_names.push(ash::khr::get_physical_device_properties2::NAME.as_ptr());
}
Ok(extension_names)
}
pub fn handle(&self) -> Option<&winit::window::Window> {
self.handle.as_ref()
}
pub fn physical_size<P: Pixel>(&self) -> Option<winit::dpi::PhysicalSize<P>> {
self.window_attributes
.inner_size
.and_then(|size| Some(size.to_physical::<P>(1.0)))
}
pub fn request_redraw(&self) {
match self.handle.as_ref() {
Some(window) => window.request_redraw(),
None => log::warn!("Redraw requested but no window found"),
}
}
}

View file

@ -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)
}

421
src/renderer/app.rs Normal file
View file

@ -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<Instance>,
device: Arc<Device>,
queue: Arc<Queue>,
memory_allocator: Arc<StandardMemoryAllocator>,
command_buffer_allocator: Arc<StandardCommandBufferAllocator>,
rcx: Option<RenderContext>,
scene: Option<Scene>,
}
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();
}
}

View file

@ -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<vulkan::VkDevice>, render_pass: &Arc<vulkan::VkRenderPass>) -> anyhow::Result<()>;
fn render(&self, device: &vulkan::VkDevice, swapchain: &vulkan::VkSwapchain, command_buffer: &vk::CommandBuffer) -> anyhow::Result<()>;
}
/// This function is called once during initialization, then again whenever the window is resized.
fn window_size_dependent_setup(images: &[Arc<Image>]) -> Vec<Arc<ImageView>> {
images
.iter()
.map(|image| ImageView::new_default(image.clone()).unwrap())
.collect::<Vec<_>>()
}

View file

@ -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<Window>,
pub(super) swapchain: Arc<Swapchain>,
pub(super) attachment_image_views: Vec<Arc<ImageView>>,
pub(super) viewport: Viewport,
pub(super) recreate_swapchain: bool,
pub(super) previous_frame_end: Option<Box<dyn GpuFuture>>,
}
impl RenderContext {
pub fn new(window: Arc<Window>, surface: Arc<Surface>, device: &Arc<Device>) -> 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,
}
}
}

210
src/renderer/scene.rs Normal file
View file

@ -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<GraphicsPipeline>,
vertex_buffer: Subbuffer<[MyVertex]>,
}
impl Scene {
pub fn initialize(device: &Arc<Device>, swapchain: &Arc<Swapchain>, memory_allocator: &Arc<StandardMemoryAllocator>) -> 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<PrimaryAutoCommandBuffer>) {
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();
}
}

View file

@ -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;

View file

@ -1,51 +0,0 @@
use std::ffi::CString;
pub enum LayersSelector<'a> {
Nothing,
SpecificLayers(Vec<&'a str>),
All,
}
pub fn use_layers(entry: &ash::Entry, selector: LayersSelector) -> Vec<CString> {
let layers_available = get_layers_available(entry)
.iter()
.filter_map(|layer| {
layer
.layer_name_as_c_str()
.and_then(|layer_name| Ok(CString::from(layer_name)))
.ok()
})
.collect::<Vec<_>>();
match selector {
LayersSelector::Nothing => Vec::new(),
LayersSelector::SpecificLayers(layers) => select_layers(&layers_available, &layers),
LayersSelector::All => layers_available,
}
}
fn get_layers_available(entry: &ash::Entry) -> Vec<ash::vk::LayerProperties> {
unsafe {
entry
.enumerate_instance_layer_properties()
.unwrap_or_default()
}
}
fn select_layers(layers_available: &Vec<CString>, layers_to_select: &[&str]) -> Vec<CString> {
layers_to_select
.iter()
.filter_map(|layer_name| {
if layers_available.iter().any(|layer_available| {
layer_available
.to_str()
.and_then(|layer_available| Ok(layer_available.eq(*layer_name)))
.unwrap_or(false)
}) {
CString::new(*layer_name).ok()
} else {
None
}
})
.collect::<Vec<_>>()
}

View file

@ -1 +0,0 @@
pub mod layers;

View file

@ -1,37 +0,0 @@
use ash::vk;
use std::mem::offset_of;
#[derive(Default)]
pub struct Vertex {
pub position: [f32; 2],
pub color: [f32; 3],
}
impl Vertex {
pub fn new(position: [f32; 2], color: [f32; 3]) -> Self {
Self { position, color }
}
pub fn get_binding_description() -> vk::VertexInputBindingDescription {
vk::VertexInputBindingDescription::default()
.binding(0)
.stride(size_of::<Self>() as u32)
.input_rate(vk::VertexInputRate::VERTEX)
}
pub fn get_attribute_descriptions() -> [vk::VertexInputAttributeDescription; 2] {
let position_attribute = vk::VertexInputAttributeDescription::default()
.binding(0)
.location(0)
.format(vk::Format::R32G32_SFLOAT)
.offset(offset_of!(Vertex, position) as u32);
let color_attribute = vk::VertexInputAttributeDescription::default()
.binding(0)
.location(1)
.format(vk::Format::R32G32B32_SFLOAT)
.offset(offset_of!(Vertex, color) as u32);
[position_attribute, color_attribute]
}
}

View file

@ -1,31 +0,0 @@
use std::sync::Arc;
use ash::prelude::VkResult;
use ash::vk;
use crate::renderer::vulkan::VkDevice;
pub struct VkBuffer {
device: Arc<VkDevice>,
handle: vk::Buffer,
}
impl VkBuffer {
pub fn new(device: &Arc<VkDevice>, info: &vk::BufferCreateInfo) -> VkResult<Self> {
let buffer = unsafe { device.handle.create_buffer(info, None)? };
Ok(VkBuffer { device: Arc::clone(device), handle: buffer })
}
pub fn mem_requirements(&self) -> vk::MemoryRequirements {
unsafe { self.device.handle.get_buffer_memory_requirements(self.handle) }
}
}
impl Drop for VkBuffer {
fn drop(&mut self) {
unsafe {
self.device.handle.destroy_buffer(self.handle, None);
}
}
}

View file

@ -1,52 +0,0 @@
use super::VkDevice;
use ash::prelude::VkResult;
use ash::vk;
use std::sync::Arc;
pub struct VkCommandPool {
device: Arc<VkDevice>,
pub handle: vk::CommandPool,
}
impl VkCommandPool {
pub fn new(device: &Arc<VkDevice>) -> VkResult<Self> {
let command_pool_info =
vk::CommandPoolCreateInfo::default()
.flags(vk::CommandPoolCreateFlags::RESET_COMMAND_BUFFER)
.queue_family_index(device.queue_family_index);
let command_pool = unsafe {
device
.handle
.create_command_pool(&command_pool_info, None)?
};
log::debug!("Command pool created ({command_pool:?})");
Ok(Self {
device: device.clone(),
handle: command_pool,
})
}
pub fn allocate_command_buffers_for_framebuffers(&self, framebuffers_count: u32) -> VkResult<Vec<vk::CommandBuffer>> {
let command_buffer_info = vk::CommandBufferAllocateInfo::default()
.command_pool(self.handle)
.level(vk::CommandBufferLevel::PRIMARY)
.command_buffer_count(framebuffers_count);
let command_buffers = unsafe {
self.device
.handle
.allocate_command_buffers(&command_buffer_info)?
};
Ok(command_buffers)
}
}
impl Drop for VkCommandPool {
fn drop(&mut self) {
unsafe { self.device.handle.destroy_command_pool(self.handle, None) };
log::debug!("Command pool destroyed ({:?})", self.handle);
}
}

View file

@ -1,109 +0,0 @@
use super::{VkInstance, VkPhysicalDevice};
use ash::prelude::VkResult;
use ash::vk;
use std::sync::Arc;
pub struct VkDevice {
instance: Arc<VkInstance>,
physical_device: Arc<VkPhysicalDevice>,
pub handle: ash::Device,
pub swapchain_loader: ash::khr::swapchain::Device,
pub queue_family_index: u32,
// Arc not used because vk::Queue is destroyed with Device automatically
// so any references of vk::Queue must be destroyed with VkDevice
queues: Vec<vk::Queue>,
}
impl VkDevice {
pub fn new_graphics_device(
instance: &Arc<VkInstance>,
physical_device: &Arc<VkPhysicalDevice>,
queue_family_index: u32,
) -> anyhow::Result<Self> {
let device_extension_names_raw = [
ash::khr::swapchain::NAME.as_ptr(),
#[cfg(any(target_os = "macos", target_os = "ios"))]
ash::khr::portability_subset::NAME.as_ptr(),
];
let features = vk::PhysicalDeviceFeatures {
shader_clip_distance: 1,
..Default::default()
};
let queues_priorities = [1.0];
let queue_info = vk::DeviceQueueCreateInfo::default()
.queue_family_index(queue_family_index)
.queue_priorities(&queues_priorities);
let device_create_info = vk::DeviceCreateInfo::default()
.queue_create_infos(std::slice::from_ref(&queue_info))
.enabled_extension_names(&device_extension_names_raw)
.enabled_features(&features);
let device = unsafe {
instance
.handle
.create_device(physical_device.handle, &device_create_info, None)?
};
log::debug!("Device created ({:?})", device.handle());
let queues = queues_priorities
.iter()
.enumerate()
.map(|(index, _)| unsafe { device.get_device_queue(queue_family_index, index as u32) })
.collect::<Vec<_>>();
let swapchain_loader = ash::khr::swapchain::Device::new(&instance.handle, &device);
Ok(Self {
instance: Arc::clone(instance),
physical_device: Arc::clone(&physical_device),
handle: device,
swapchain_loader,
queue_family_index,
queues,
})
}
pub fn get_device_queue(&self, queue_index: u32) -> Option<&vk::Queue> {
self.queues.get(queue_index as usize)
}
pub fn create_command_pool(
&self,
info: &vk::CommandPoolCreateInfo,
) -> VkResult<vk::CommandPool> {
let info = info.queue_family_index(self.queue_family_index);
unsafe { self.handle.create_command_pool(&info, None) }
}
pub fn allocate_command_buffers(
&self,
info: &vk::CommandBufferAllocateInfo,
) -> VkResult<Vec<vk::CommandBuffer>> {
unsafe { self.handle.allocate_command_buffers(&info) }
}
pub fn create_fence(&self, info: &vk::FenceCreateInfo) -> VkResult<vk::Fence> {
unsafe { self.handle.create_fence(&info, None) }
}
pub fn create_semaphore(
&self,
info: &vk::SemaphoreCreateInfo,
) -> VkResult<vk::Semaphore> {
unsafe { self.handle.create_semaphore(&info, None) }
}
}
impl Drop for VkDevice {
fn drop(&mut self) {
unsafe {
self.handle.destroy_device(None);
log::debug!("Device destroyed ({:?})", self.handle.handle());
}
}
}

View file

@ -1,30 +0,0 @@
use super::VkDevice;
use ash::vk;
use std::sync::Arc;
pub struct VkFence {
device: Arc<VkDevice>,
pub handle: vk::Fence,
}
impl VkFence {
pub fn new(device: &Arc<VkDevice>) -> anyhow::Result<Self> {
let fence_info = vk::FenceCreateInfo::default()
.flags(vk::FenceCreateFlags::SIGNALED);
let fence = unsafe { device.handle.create_fence(&fence_info, None)? };
log::debug!("Fence created ({fence:?})");
Ok(Self {
device: device.clone(),
handle: fence,
})
}
}
impl Drop for VkFence {
fn drop(&mut self) {
unsafe { self.device.handle.destroy_fence(self.handle, None) };
log::debug!("Fence destroyed ({:?})", self.handle);
}
}

View file

@ -1,44 +0,0 @@
use super::{VkDevice, VkRenderPass, VkSwapchain};
use ash::vk;
use std::sync::Arc;
pub struct VkFramebuffer {
device: Arc<VkDevice>,
image_view: Arc<vk::ImageView>,
render_pass: Arc<VkRenderPass>,
pub handle: vk::Framebuffer,
}
impl VkFramebuffer {
pub fn from_swapchain_image_view(
device: &Arc<VkDevice>,
render_pass: &Arc<VkRenderPass>,
image_view: &Arc<vk::ImageView>,
swapchain: &VkSwapchain,
) -> anyhow::Result<Self> {
let attachments = [*image_view.as_ref()];
let framebuffer_info = vk::FramebufferCreateInfo::default()
.render_pass(render_pass.handle)
.width(swapchain.surface_resolution.width)
.height(swapchain.surface_resolution.height)
.attachments(&attachments)
.layers(1);
let framebuffer = unsafe { device.handle.create_framebuffer(&framebuffer_info, None)? };
Ok(Self {
device: device.clone(),
render_pass: render_pass.clone(),
image_view: image_view.clone(),
handle: framebuffer,
})
}
}
impl Drop for VkFramebuffer {
fn drop(&mut self) {
unsafe { self.device.handle.destroy_framebuffer(self.handle, None) };
}
}

View file

@ -1,120 +0,0 @@
use super::{VkDevice, VkRenderPass, VkShaderModule, VkSwapchain};
use ash::vk;
use std::ffi::CStr;
use std::sync::Arc;
pub struct VkGraphicsPipeline {
device: Arc<VkDevice>,
render_pass: Arc<VkRenderPass>,
pub pipeline_layout: vk::PipelineLayout,
pub pipeline: vk::Pipeline,
vertex_shader: VkShaderModule,
fragment_shader: VkShaderModule,
}
impl VkGraphicsPipeline {
pub fn new(
device: &Arc<VkDevice>,
render_pass: &Arc<VkRenderPass>,
) -> anyhow::Result<Self> {
let shader_entry_name = CStr::from_bytes_with_nul(b"main\0")?;
let vert_shader_module =
VkShaderModule::from_spv_file(device, "res/shaders/main.vert.spv")?;
let vert_shader_info = vk::PipelineShaderStageCreateInfo::default()
.module(vert_shader_module.handle)
.name(shader_entry_name)
.stage(vk::ShaderStageFlags::VERTEX);
let frag_shader_module =
VkShaderModule::from_spv_file(device, "res/shaders/main.frag.spv")?;
let frag_shader_info = vk::PipelineShaderStageCreateInfo::default()
.module(frag_shader_module.handle)
.name(shader_entry_name)
.stage(vk::ShaderStageFlags::FRAGMENT);
let shader_stage_create_infos = [vert_shader_info, frag_shader_info];
let vertex_input_info = vk::PipelineVertexInputStateCreateInfo::default();
let input_assembly = vk::PipelineInputAssemblyStateCreateInfo::default()
.topology(vk::PrimitiveTopology::TRIANGLE_LIST);
let viewport_state = vk::PipelineViewportStateCreateInfo::default()
.viewport_count(1)
.scissor_count(1);
let rasterizer = vk::PipelineRasterizationStateCreateInfo::default()
.polygon_mode(vk::PolygonMode::FILL)
.cull_mode(vk::CullModeFlags::BACK)
.front_face(vk::FrontFace::CLOCKWISE)
.line_width(1.0);
let multisampling = vk::PipelineMultisampleStateCreateInfo::default()
.rasterization_samples(vk::SampleCountFlags::TYPE_1)
.min_sample_shading(1.0);
let color_blend_attachment = vk::PipelineColorBlendAttachmentState::default()
.color_write_mask(vk::ColorComponentFlags::RGBA);
let attachments = [color_blend_attachment];
let color_blending =
vk::PipelineColorBlendStateCreateInfo::default().attachments(&attachments);
let dynamic_state = vk::PipelineDynamicStateCreateInfo::default()
.dynamic_states(&[vk::DynamicState::VIEWPORT, vk::DynamicState::SCISSOR]);
let pipeline_layout_info = vk::PipelineLayoutCreateInfo::default();
let pipeline_layout = unsafe {
device
.handle
.create_pipeline_layout(&pipeline_layout_info, None)?
};
log::debug!("Pipeline layout created ({pipeline_layout:?})");
let pipeline_info = vk::GraphicsPipelineCreateInfo::default()
.stages(&shader_stage_create_infos)
.vertex_input_state(&vertex_input_info)
.input_assembly_state(&input_assembly)
.viewport_state(&viewport_state)
.rasterization_state(&rasterizer)
.multisample_state(&multisampling)
.color_blend_state(&color_blending)
.dynamic_state(&dynamic_state)
.layout(pipeline_layout)
.render_pass(render_pass.handle);
let pipeline = unsafe {
device
.handle
.create_graphics_pipelines(vk::PipelineCache::null(), &[pipeline_info], None)
.map_err(|(_, error)| error)?[0]
};
log::debug!("Pipeline created ({pipeline_layout:?})");
Ok(Self {
device: device.clone(),
render_pass: render_pass.clone(),
pipeline_layout,
pipeline,
vertex_shader: vert_shader_module,
fragment_shader: frag_shader_module,
})
}
}
impl Drop for VkGraphicsPipeline {
fn drop(&mut self) {
unsafe {
self.device.handle.destroy_pipeline(self.pipeline, None);
log::debug!("Pipeline destroyed ({:?})", self.pipeline);
self.device
.handle
.destroy_pipeline_layout(self.pipeline_layout, None);
log::debug!("Pipeline layout destroyed ({:?})", self.pipeline_layout);
}
}
}

View file

@ -1,128 +0,0 @@
use crate::renderer::vulkan::{
utils::layers::{use_layers, LayersSelector},
VkPhysicalDevice,
};
use ash::khr::surface;
use ash::{vk, Entry, Instance};
use std::ffi::{c_char, CStr, CString};
use std::sync::Arc;
pub struct VkInstance {
pub entry: Entry,
pub handle: Instance,
pub surface_loader: surface::Instance,
}
impl VkInstance {
pub fn new(required_extensions: &Vec<*const c_char>) -> Self {
let entry = Entry::linked();
log::debug!("Initializing Vulkan instance");
if log::log_enabled!(log::Level::Debug) {
let layer_properties =
unsafe { entry.enumerate_instance_layer_properties() }.unwrap_or_default();
for layer_property in layer_properties {
let layer_extensions = unsafe {
entry.enumerate_instance_extension_properties(
layer_property.layer_name_as_c_str().ok(),
)
}
.unwrap_or_default();
log::debug!("{layer_property:#?} {layer_extensions:#?}");
}
}
{
let required_extensions = required_extensions
.iter()
.map(|str| unsafe { CStr::from_ptr(*str) })
.map(|cstr| cstr.to_string_lossy())
.collect::<Vec<_>>();
log::debug!(
"Required instance extensions: {}",
required_extensions.join(", ")
);
}
// Layers
#[allow(unused)]
let mut layer_selector = LayersSelector::Nothing;
#[cfg(debug_assertions)]
{
layer_selector = LayersSelector::SpecificLayers(vec![
"VK_LAYER_KHRONOS_validation",
"VK_LAYER_MANGOHUD_overlay_x86_64",
"VK_LAYER_NV_optimus",
]);
}
let layers = use_layers(&entry, layer_selector);
{
let layers = layers
.iter()
.map(|layer| layer.to_string_lossy())
.collect::<Vec<_>>();
log::debug!("Selected debug layers : {}", layers.join(", "))
}
let layers_raw = layers.iter().map(|s| s.as_ptr()).collect::<Vec<_>>();
// App Info
let app_name = CString::new("VulkanTriangle").unwrap();
let appinfo = vk::ApplicationInfo::default()
.application_name(app_name.as_c_str())
.application_version(0)
.engine_name(app_name.as_c_str())
.engine_version(0)
.api_version(vk::make_api_version(0, 1, 0, 0));
let create_flags = if cfg!(any(target_os = "macos", target_os = "ios")) {
vk::InstanceCreateFlags::ENUMERATE_PORTABILITY_KHR
} else {
vk::InstanceCreateFlags::default()
};
// Instance Info
let create_info = vk::InstanceCreateInfo::default()
.application_info(&appinfo)
.enabled_layer_names(&layers_raw)
.enabled_extension_names(&required_extensions)
.flags(create_flags);
let instance: Instance = unsafe {
entry
.create_instance(&create_info, None)
.expect("Instance creation error")
};
let surface_loader = surface::Instance::new(&entry, &instance);
log::debug!("Vulkan instance created ({:?})", instance.handle());
Self {
entry,
handle: instance,
surface_loader,
}
}
pub fn get_physical_devices(instance: &Arc<Self>) -> Vec<VkPhysicalDevice> {
let physical_devices = unsafe { instance.handle.enumerate_physical_devices() };
physical_devices
.unwrap_or_default()
.iter()
.map(|physical_device| VkPhysicalDevice::new(&instance, *physical_device))
.collect()
}
}
impl Drop for VkInstance {
fn drop(&mut self) {
unsafe {
self.handle.destroy_instance(None);
}
log::debug!("Vulkan instance destroyed ({:?})", self.handle.handle());
}
}

View file

@ -1,85 +0,0 @@
use std::sync::Arc;
use super::{VkInstance, VkSurface};
use ash::vk;
pub struct VkPhysicalDevice {
instance: Arc<VkInstance>,
pub handle: vk::PhysicalDevice,
pub properties: vk::PhysicalDeviceProperties,
pub features: vk::PhysicalDeviceFeatures,
pub queue_family_properties: Vec<vk::QueueFamilyProperties>,
}
impl VkPhysicalDevice {
pub fn new(instance: &Arc<VkInstance>, physical_device: vk::PhysicalDevice) -> Self {
log::debug!("New physical device");
let device_properties = unsafe { instance.handle.get_physical_device_properties(physical_device) };
log::debug!("{device_properties:#?}");
let device_features = unsafe { instance.handle.get_physical_device_features(physical_device) };
log::debug!("{device_features:#?}");
let device_queue_families =
unsafe { instance.handle.get_physical_device_queue_family_properties(physical_device) };
log::debug!("{device_queue_families:#?}");
Self {
instance: Arc::clone(instance),
handle: physical_device,
properties: device_properties,
features: device_features,
queue_family_properties: device_queue_families,
}
}
pub fn find_queue_family_by(
&self,
queue_flags: Option<vk::QueueFlags>,
surface: Option<&VkSurface>,
) -> Option<(u32, &vk::QueueFamilyProperties)> {
self.queue_family_properties.iter().enumerate().find_map(
|(index, queue_family_property)| {
let surface_check_passed = match surface {
Some(surface) => surface
.physical_device_queue_supported(self, index as u32)
.unwrap_or(false),
None => true,
};
let queue_flags_check_passed = match queue_flags {
Some(queue_flags) => queue_family_property.queue_flags.contains(queue_flags),
None => true,
};
if surface_check_passed && queue_flags_check_passed {
Some((index as u32, queue_family_property))
} else {
None
}
},
)
}
pub fn pick_physical_device_and_queue_by<'a>(
physical_devices: &'a Vec<VkPhysicalDevice>,
queue_flags: Option<vk::QueueFlags>,
surface: Option<&VkSurface>,
) -> Option<(&'a VkPhysicalDevice, u32, &'a vk::QueueFamilyProperties)> {
physical_devices.iter().find_map(|physical_device| {
physical_device
.find_queue_family_by(queue_flags, surface)
.and_then(|(queue_index, queue_family_properties)| {
Some((physical_device, queue_index, queue_family_properties))
})
})
}
pub fn priority(&self) -> usize {
match self.properties.device_type {
vk::PhysicalDeviceType::CPU => 1,
vk::PhysicalDeviceType::VIRTUAL_GPU => 2,
vk::PhysicalDeviceType::INTEGRATED_GPU => 3,
vk::PhysicalDeviceType::DISCRETE_GPU => 4,
_ => 0,
}
}
}

View file

@ -1,206 +0,0 @@
use super::{
VkCommandPool, VkDevice, VkFence, VkFramebuffer, VkGraphicsPipeline, VkInstance, VkPhysicalDevice,
VkRenderPass, VkSemaphore, VkSurface, VkSwapchain,
};
use ash::vk;
use std::sync::Arc;
use crate::renderer::Renderable;
pub struct VkRenderContext {
instance: Arc<VkInstance>,
surface: Arc<VkSurface>,
device: Arc<VkDevice>,
swapchain: Arc<VkSwapchain>,
render_pass: Arc<VkRenderPass>,
framebuffers: Vec<Arc<VkFramebuffer>>,
command_pool: VkCommandPool,
command_buffers: Vec<vk::CommandBuffer>,
image_available_semaphore: VkSemaphore,
render_finished_semaphore: VkSemaphore,
in_flight_fence: VkFence,
}
impl VkRenderContext {
pub fn init(window: &crate::display::Window) -> anyhow::Result<Self> {
let required_extensions = window.required_extensions()?;
let instance = Arc::new(VkInstance::new(&required_extensions));
let surface = Arc::new(VkSurface::new(&window, instance.clone())?);
let mut physical_devices = VkInstance::get_physical_devices(&instance);
physical_devices.sort_by(|a, b| b.priority().cmp(&a.priority()));
let (physical_device, queue_family_index, properties) =
VkPhysicalDevice::pick_physical_device_and_queue_by(
&physical_devices,
Some(vk::QueueFlags::GRAPHICS),
Some(&surface),
)
.ok_or_else(|| anyhow::anyhow!("Unable to find physical device"))?;
log::debug!(
"Selected queue {properties:#?} for physical device {:?}",
physical_device.properties.device_name_as_c_str()
);
let device = Arc::new(VkDevice::new_graphics_device(
&instance,
&physical_device,
queue_family_index,
)?);
let swapchain = Arc::new(VkSwapchain::new(
&window,
&surface,
&device,
&physical_device,
)?);
let render_pass = Arc::new(VkRenderPass::new(&device, &swapchain)?);
let framebuffers = swapchain
.create_framebuffers(&render_pass)
.ok_or_else(|| anyhow::anyhow!("Failed to get framebuffers"))?;
let command_pool = VkCommandPool::new(&device)?;
// Destroyed with command pool
let command_buffers = command_pool
.allocate_command_buffers_for_framebuffers(framebuffers.len() as u32)?;
let image_available_semaphore = VkSemaphore::new(&device)?;
let render_finished_semaphore = VkSemaphore::new(&device)?;
let in_flight_fence = VkFence::new(&device)?;
Ok(Self {
instance,
surface,
device,
swapchain,
render_pass,
framebuffers,
command_pool,
command_buffers,
image_available_semaphore,
render_finished_semaphore,
in_flight_fence,
})
}
pub fn render(&mut self, scene: Option<&Box<dyn Renderable>>) -> anyhow::Result<()> {
unsafe { self.device.handle.wait_for_fences(&[self.in_flight_fence.handle], true, u64::MAX)? };
unsafe { self.device.handle.reset_fences(&[self.in_flight_fence.handle])? };
let (index, _) = self
.swapchain
.acquire_next_image(&self.image_available_semaphore)?;
// if self.swapchain.is_dirty() {
// self.update_swapchain()?
// }
let command_buffer = self.command_buffers[index as usize];
unsafe { self.device.handle.reset_command_buffer(command_buffer, vk::CommandBufferResetFlags::default())? };
let render_area = vk::Rect2D::default().extent(self.swapchain.surface_resolution);
let clear_value = vk::ClearValue::default();
let command_buffer_begin_info = vk::CommandBufferBeginInfo::default();
unsafe {
self.device
.handle
.begin_command_buffer(command_buffer, &command_buffer_begin_info)?
};
let clear_values = [clear_value];
let framebuffer = self.framebuffers[index as usize].as_ref();
let render_pass_begin_info = vk::RenderPassBeginInfo::default()
.render_pass(self.render_pass.handle)
.framebuffer(framebuffer.handle)
.render_area(render_area)
.clear_values(&clear_values);
unsafe {
self.device.handle.cmd_begin_render_pass(
command_buffer,
&render_pass_begin_info,
vk::SubpassContents::INLINE,
);
};
if let Some(scene) = scene {
scene.render(&self.device, &self.swapchain, &command_buffer)?;
}
unsafe { self.device.handle.cmd_end_render_pass(command_buffer) };
unsafe { self.device.handle.end_command_buffer(command_buffer)? };
let wait_semaphores = [self.image_available_semaphore.handle];
let signal_semaphores = [self.render_finished_semaphore.handle];
let wait_stages = [vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT];
let command_buffers_to_submit = [command_buffer];
let submit_info = vk::SubmitInfo::default()
.wait_semaphores(&wait_semaphores)
.wait_dst_stage_mask(&wait_stages)
.command_buffers(&command_buffers_to_submit)
.signal_semaphores(&signal_semaphores);
let queue = self
.device
.get_device_queue(0)
.ok_or_else(|| anyhow::anyhow!("Failed to get a queue"))?;
unsafe {
self.device
.handle
.queue_submit(*queue, &[submit_info], self.in_flight_fence.handle)?
};
let swapchains = [self.swapchain.handle.unwrap()];
let indices = [index];
let present_info = vk::PresentInfoKHR::default()
.wait_semaphores(&signal_semaphores)
.swapchains(&swapchains)
.image_indices(&indices);
unsafe { self.device.swapchain_loader.queue_present(*queue, &present_info)? };
Ok(())
}
pub fn update_resolution(&mut self, width: u32, height: u32) -> anyhow::Result<()> {
match Arc::get_mut(&mut self.swapchain) {
Some(swapchain) => swapchain.update_resolution(width, height)?,
None => log::warn!("Impossible to get mutable swapchain"),
}
Ok(())
}
pub fn exit(&self) {
unsafe { self.device.handle.device_wait_idle().unwrap() }
}
pub fn init_scene(&self, scene: &mut Box<dyn Renderable>) -> anyhow::Result<()> {
scene.init(&self.device, &self.render_pass)
}
fn update_swapchain(&mut self) -> anyhow::Result<()> {
match Arc::get_mut(&mut self.swapchain) {
Some(swapchain) => {
swapchain.create_swapchain()?;
self.framebuffers = self.swapchain
.create_framebuffers(&self.render_pass)
.ok_or_else(|| anyhow::anyhow!("Failed to get framebuffers"))?;
}
None => log::warn!("Impossible to get mutable swapchain"),
}
Ok(())
}
}

View file

@ -1,56 +0,0 @@
use super::{VkDevice, VkSwapchain};
use ash::prelude::VkResult;
use ash::vk;
use std::sync::Arc;
pub struct VkRenderPass {
device: Arc<VkDevice>,
pub handle: vk::RenderPass,
}
impl VkRenderPass {
pub fn new(device: &Arc<VkDevice>, swapchain: &Arc<VkSwapchain>) -> VkResult<Self> {
let color_attachment = vk::AttachmentDescription::default()
.format(swapchain.surface_format.format)
.samples(vk::SampleCountFlags::TYPE_1)
.load_op(vk::AttachmentLoadOp::CLEAR)
.store_op(vk::AttachmentStoreOp::STORE)
.stencil_load_op(vk::AttachmentLoadOp::DONT_CARE)
.stencil_store_op(vk::AttachmentStoreOp::DONT_CARE)
.initial_layout(vk::ImageLayout::UNDEFINED)
.final_layout(vk::ImageLayout::PRESENT_SRC_KHR);
let color_attachment_ref = vk::AttachmentReference::default()
.attachment(0)
.layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL);
let color_attachments = [color_attachment_ref];
let subpass = vk::SubpassDescription::default()
.pipeline_bind_point(vk::PipelineBindPoint::GRAPHICS)
.color_attachments(&color_attachments);
let attachments = [color_attachment];
let subpasses = [subpass];
let render_pass_info = vk::RenderPassCreateInfo::default()
.attachments(&attachments)
.subpasses(&subpasses);
let render_pass = unsafe { device.handle.create_render_pass(&render_pass_info, None)? };
log::debug!("Render pass created ({render_pass:?})");
Ok(Self {
device: device.clone(),
handle: render_pass,
})
}
}
impl Drop for VkRenderPass {
fn drop(&mut self) {
unsafe {
self.device.handle.destroy_render_pass(self.handle, None);
log::debug!("Render pass destroyed ({:?})", self.handle);
}
}
}

View file

@ -1,29 +0,0 @@
use super::VkDevice;
use ash::vk;
use std::sync::Arc;
pub struct VkSemaphore {
device: Arc<VkDevice>,
pub handle: vk::Semaphore,
}
impl VkSemaphore {
pub fn new(device: &Arc<VkDevice>) -> anyhow::Result<Self> {
let semaphore_info = vk::SemaphoreCreateInfo::default();
let semaphore = unsafe { device.handle.create_semaphore(&semaphore_info, None)? };
log::debug!("Semaphore created ({semaphore:?})");
Ok(Self {
device: device.clone(),
handle: semaphore,
})
}
}
impl Drop for VkSemaphore {
fn drop(&mut self) {
unsafe { self.device.handle.destroy_semaphore(self.handle, None) };
log::debug!("Semaphore destroyed ({:?})", self.handle);
}
}

View file

@ -1,42 +0,0 @@
use super::VkDevice;
use ash::vk;
use std::path::Path;
use std::sync::Arc;
pub struct VkShaderModule {
device: Arc<VkDevice>,
pub handle: vk::ShaderModule,
}
impl VkShaderModule {
pub fn from_spv_file<P: AsRef<Path>>(device: &Arc<VkDevice>, path: P) -> anyhow::Result<Self> {
let mut file = std::fs::File::open(&path)?;
let frag_shader_str = ash::util::read_spv(&mut file)?;
let shader_create_info = vk::ShaderModuleCreateInfo::default().code(&frag_shader_str);
let shader_module = unsafe {
device
.handle
.create_shader_module(&shader_create_info, None)?
};
log::debug!(
"Shader module created ({shader_module:?}) from {:?}",
path.as_ref()
);
Ok(Self {
device: device.clone(),
handle: shader_module,
})
}
}
impl Drop for VkShaderModule {
fn drop(&mut self) {
unsafe {
self.device.handle.destroy_shader_module(self.handle, None);
log::debug!("Shader module destroyed ({:?})", self.handle);
}
}
}

View file

@ -1,94 +0,0 @@
use super::{VkInstance, VkPhysicalDevice};
use ash::prelude::VkResult;
use ash::vk;
use std::sync::Arc;
use winit::raw_window_handle::{HasDisplayHandle, HasWindowHandle};
pub struct SwapchainSupportDetails(
pub Vec<vk::SurfaceFormatKHR>,
pub vk::SurfaceCapabilitiesKHR,
pub Vec<vk::PresentModeKHR>,
);
pub struct VkSurface {
instance: Arc<VkInstance>,
pub handle: vk::SurfaceKHR,
}
impl VkSurface {
pub fn new(window: &crate::display::Window, instance: Arc<VkInstance>) -> anyhow::Result<Self> {
let window_handle = window
.handle()
.ok_or_else(|| anyhow::anyhow!("Window handle is not available."))?;
let surface = unsafe {
ash_window::create_surface(
&instance.entry,
&instance.handle,
window_handle.display_handle()?.as_raw(),
window_handle.window_handle()?.as_raw(),
None,
)?
};
log::debug!("Surface created ({:?})", surface);
Ok(Self { instance, handle: surface })
}
pub fn physical_device_queue_supported(
&self,
physical_device: &VkPhysicalDevice,
queue_index: u32,
) -> VkResult<bool> {
unsafe {
self.instance
.surface_loader
.get_physical_device_surface_support(
physical_device.handle,
queue_index,
self.handle,
)
}
}
pub fn get_physical_device_swapchain_support_details(
&self,
physical_device: &VkPhysicalDevice,
) -> VkResult<SwapchainSupportDetails> {
unsafe {
let formats = self
.instance
.surface_loader
.get_physical_device_surface_formats(physical_device.handle, self.handle)?;
let capabilities = self
.instance
.surface_loader
.get_physical_device_surface_capabilities(physical_device.handle, self.handle)?;
let present_modes = self
.instance
.surface_loader
.get_physical_device_surface_present_modes(physical_device.handle, self.handle)?;
Ok(SwapchainSupportDetails(
formats,
capabilities,
present_modes,
))
}
}
}
impl Drop for VkSurface {
fn drop(&mut self) {
unsafe {
self.instance
.surface_loader
.destroy_surface(self.handle, None);
}
log::debug!("Surface destroyed ({:?})", self.handle);
}
}

View file

@ -1,297 +0,0 @@
use super::{SwapchainSupportDetails, VkDevice, VkFramebuffer, VkPhysicalDevice, VkRenderPass, VkSemaphore, VkSurface};
use crate::display::Window;
use ash::prelude::VkResult;
use ash::vk;
use std::sync::Arc;
pub struct VkSwapchain {
surface: Arc<VkSurface>,
device: Arc<VkDevice>,
pub handle: Option<vk::SwapchainKHR>,
swapchain_support_details: SwapchainSupportDetails,
pub desired_image_count: u32,
pub surface_format: vk::SurfaceFormatKHR,
pub surface_resolution: vk::Extent2D,
pub new_requested_surface_resolution: Option<vk::Extent2D>,
pub present_mode: vk::PresentModeKHR,
pub pre_transform: vk::SurfaceTransformFlagsKHR,
pub present_images: Option<Vec<vk::Image>>,
pub present_image_views: Option<Vec<Arc<vk::ImageView>>>,
}
impl VkSwapchain {
pub fn new(
window: &Window,
surface: &Arc<VkSurface>,
device: &Arc<VkDevice>,
physical_device: &VkPhysicalDevice,
) -> anyhow::Result<Self> {
log::debug!("Creating swapchain");
let window_size = window
.physical_size::<u32>()
.and_then(|size| {
Some(vk::Extent2D {
width: size.width,
height: size.height,
})
})
.ok_or_else(|| anyhow::anyhow!("Failed to get swapchain extent"))?;
log::debug!("Window size ({}x{})", window_size.width, window_size.height);
let swapchain_support_details =
surface.get_physical_device_swapchain_support_details(physical_device)?;
let SwapchainSupportDetails(surface_formats, surface_capabilities, present_modes) =
&swapchain_support_details;
log::debug!("Supported surface formats by physical device: {surface_formats:#?}");
log::debug!("Surface capabilities: {surface_capabilities:#?}");
log::debug!("Present modes: {present_modes:#?}");
let surface_format = Self::choose_surface_format(surface_formats)
.ok_or_else(|| anyhow::anyhow!("No available surface formats"))?;
let desired_image_count = Self::choose_desired_image_count(surface_capabilities);
let swapchain_extent = Self::choose_swapchain_extent(window_size, surface_capabilities);
let pre_transform = Self::choose_pre_transform(surface_capabilities);
let present_mode = Self::choose_present_mode(present_modes);
let mut swapchain = Self {
surface: surface.clone(),
device: device.clone(),
handle: None,
new_requested_surface_resolution: None,
swapchain_support_details,
desired_image_count,
surface_format,
surface_resolution: swapchain_extent,
present_mode,
pre_transform,
present_images: None,
present_image_views: None,
};
swapchain.create_swapchain()?;
Ok(swapchain)
}
pub fn create_swapchain(&mut self) -> VkResult<()> {
if let Some(new_requested_surface_resolution) = self.new_requested_surface_resolution {
self.surface_resolution = new_requested_surface_resolution;
self.new_requested_surface_resolution = None;
}
let mut swapchain_create_info = self.create_swapchain_info(&self.surface);
if let Some(old_swapchain) = self.handle {
swapchain_create_info.old_swapchain = old_swapchain;
}
let swapchain = unsafe {
self.device
.swapchain_loader
.create_swapchain(&swapchain_create_info, None)?
};
let present_images = unsafe {
self.device
.swapchain_loader
.get_swapchain_images(swapchain)?
};
let present_images_view = present_images
.iter()
.map(|i| {
self.create_present_image_view(*i)
.expect("Failed to create image view")
})
.map(|i| Arc::new(i))
.collect::<Vec<_>>();
if log::log_enabled!(log::Level::Debug) {
let label = match self.handle {
None => "Swapchain created",
Some(_) => "Swapchain updated",
};
log::debug!("{label} ({swapchain:?}) : {swapchain_create_info:#?}");
}
self.handle = Some(swapchain);
self.present_image_views = Some(present_images_view);
self.present_images = Some(present_images);
Ok(())
}
pub fn create_framebuffers(
&self,
render_pass: &Arc<VkRenderPass>,
) -> Option<Vec<Arc<VkFramebuffer>>> {
let present_image_views = self.present_image_views.as_ref()?;
Some(
present_image_views
.iter()
.map(|image_view| {
VkFramebuffer::from_swapchain_image_view(
&self.device,
&render_pass,
&image_view,
&self,
)
.unwrap()
})
.map(|framebuffer| Arc::new(framebuffer))
.collect::<Vec<_>>(),
)
}
pub fn update_resolution(&mut self, width: u32, height: u32) -> VkResult<()> {
log::debug!("New resolution requested ({width}x{height})");
let chosen_extent = Self::choose_swapchain_extent(
vk::Extent2D { width, height },
&self.swapchain_support_details.1,
);
if chosen_extent.width != self.surface_resolution.width
|| chosen_extent.height != self.surface_resolution.height
{
self.new_requested_surface_resolution = Some(chosen_extent);
log::debug!(
"New resolution submitted ({}x{})",
chosen_extent.width,
chosen_extent.height
);
} else {
log::debug!("New resolution skipped ({width}x{height}) : Same resolution");
}
Ok(())
}
pub fn acquire_next_image(&self, semaphore: &VkSemaphore) -> VkResult<(u32, bool)> {
unsafe {
self.device.swapchain_loader.acquire_next_image(
self.handle.unwrap(),
u64::MAX,
semaphore.handle,
vk::Fence::null(),
)
}
}
pub fn is_dirty(&self) -> bool {
self.new_requested_surface_resolution.is_some()
}
fn create_swapchain_info(&self, surface: &VkSurface) -> vk::SwapchainCreateInfoKHR {
vk::SwapchainCreateInfoKHR::default()
.surface(surface.handle)
.min_image_count(self.desired_image_count)
.image_color_space(self.surface_format.color_space)
.image_format(self.surface_format.format)
.image_extent(self.surface_resolution)
.image_usage(vk::ImageUsageFlags::COLOR_ATTACHMENT)
.image_sharing_mode(vk::SharingMode::EXCLUSIVE)
.pre_transform(self.pre_transform)
.composite_alpha(vk::CompositeAlphaFlagsKHR::OPAQUE)
.present_mode(self.present_mode)
.clipped(true)
.image_array_layers(1)
}
fn choose_swapchain_extent(
window_size: vk::Extent2D,
surface_capabilities: &vk::SurfaceCapabilitiesKHR,
) -> vk::Extent2D {
vk::Extent2D {
width: window_size
.width
.max(surface_capabilities.min_image_extent.width)
.min(surface_capabilities.max_image_extent.width),
height: window_size
.height
.max(surface_capabilities.min_image_extent.height)
.min(surface_capabilities.max_image_extent.height),
}
}
fn choose_surface_format(
surface_formats: &Vec<vk::SurfaceFormatKHR>,
) -> Option<vk::SurfaceFormatKHR> {
surface_formats.first().and_then(|f| Some(*f))
}
fn choose_desired_image_count(surface_capabilities: &vk::SurfaceCapabilitiesKHR) -> u32 {
let mut desired_image_count = surface_capabilities.min_image_count + 1;
if surface_capabilities.max_image_count > 0
&& desired_image_count > surface_capabilities.max_image_count
{
desired_image_count = surface_capabilities.max_image_count;
}
desired_image_count
}
fn choose_pre_transform(
surface_capabilities: &vk::SurfaceCapabilitiesKHR,
) -> vk::SurfaceTransformFlagsKHR {
if surface_capabilities
.supported_transforms
.contains(vk::SurfaceTransformFlagsKHR::IDENTITY)
{
vk::SurfaceTransformFlagsKHR::IDENTITY
} else {
surface_capabilities.current_transform
}
}
fn choose_present_mode(present_modes: &Vec<vk::PresentModeKHR>) -> vk::PresentModeKHR {
present_modes
.iter()
.cloned()
.find(|&mode| mode == vk::PresentModeKHR::MAILBOX)
.unwrap_or(vk::PresentModeKHR::FIFO)
}
fn create_present_image_view(&self, image: vk::Image) -> VkResult<vk::ImageView> {
let create_view_info = vk::ImageViewCreateInfo::default()
.view_type(vk::ImageViewType::TYPE_2D)
.format(self.surface_format.format)
.components(vk::ComponentMapping {
r: vk::ComponentSwizzle::IDENTITY,
g: vk::ComponentSwizzle::IDENTITY,
b: vk::ComponentSwizzle::IDENTITY,
a: vk::ComponentSwizzle::IDENTITY,
})
.subresource_range(vk::ImageSubresourceRange {
aspect_mask: vk::ImageAspectFlags::COLOR,
base_mip_level: 0,
level_count: 1,
base_array_layer: 0,
layer_count: 1,
})
.image(image);
unsafe {
self.device
.handle
.create_image_view(&create_view_info, None)
}
}
}
impl Drop for VkSwapchain {
fn drop(&mut self) {
if let Some(swapchain) = self.handle {
unsafe {
self.device
.swapchain_loader
.destroy_swapchain(swapchain, None);
}
self.handle = None;
log::debug!("Swapchain destroyed ({swapchain:?})");
}
}
}

View file

@ -1,4 +0,0 @@
mod triangle;
mod vertex;
pub use triangle::TriangleScene;

View file

@ -1,49 +0,0 @@
use crate::renderer::vulkan::{VkDevice, VkGraphicsPipeline, VkRenderPass, VkSwapchain};
use crate::renderer::Renderable;
use ash::vk;
use ash::vk::CommandBuffer;
use std::sync::Arc;
pub struct TriangleScene {
pipeline: Option<VkGraphicsPipeline>,
}
impl TriangleScene {
pub fn new() -> Self {
Self { pipeline: None }
}
}
impl Renderable for TriangleScene {
fn init(&mut self, device: &Arc<VkDevice>, render_pass: &Arc<VkRenderPass>) -> anyhow::Result<()> {
let pipeline = VkGraphicsPipeline::new(&device, &render_pass)?;
self.pipeline = Some(pipeline);
Ok(())
}
fn render(&self, device: &VkDevice, swapchain: &VkSwapchain, command_buffer: &CommandBuffer) -> anyhow::Result<()> {
unsafe {
device.handle.cmd_bind_pipeline(
*command_buffer,
vk::PipelineBindPoint::GRAPHICS,
self.pipeline.as_ref().unwrap().pipeline,
)
};
let viewport = vk::Viewport::default()
.width(swapchain.surface_resolution.width as f32)
.height(swapchain.surface_resolution.height as f32)
.max_depth(1.0);
unsafe { device.handle.cmd_set_viewport(*command_buffer, 0, &[viewport]) }
let scissor = swapchain.surface_resolution.into();
unsafe { device.handle.cmd_set_scissor(*command_buffer, 0, &[scissor]) }
unsafe { device.handle.cmd_draw(*command_buffer, 3, 1, 0, 0) };
Ok(())
}
}

View file

@ -1,39 +0,0 @@
use std::sync::Arc;
use ash::prelude::VkResult;
use ash::vk;
use crate::renderer::vulkan::{Vertex, VkBuffer, VkDevice};
#[derive(Default)]
struct VertexScene {
vertices: Vec<Vertex>,
vertices_buffer: Option<VkBuffer>,
}
impl VertexScene {
pub fn new() -> Self {
let vertices = vec![
Vertex::new([0.0, -0.5], [1.0, 0.0, 0.0]),
Vertex::new([0.5, 0.5], [0.0, 1.0, 0.0]),
Vertex::new([-0.5, 0.5], [0.0, 0.0, 1.0]),
];
Self {
vertices,
..Default::default()
}
}
fn create_buffer(&mut self, device: &Arc<VkDevice>) -> VkResult<()> {
let buffer_info = vk::BufferCreateInfo::default()
.usage(vk::BufferUsageFlags::VERTEX_BUFFER)
.sharing_mode(vk::SharingMode::EXCLUSIVE)
.size(self.vertices.len() as u64 * size_of::<Vertex>() as u64);
let buffer = VkBuffer::new(device, &buffer_info)?;
self.vertices_buffer = Some(buffer);
Ok(())
}
}