1
2#![allow(dead_code)]
3#![allow(clippy::too_many_arguments)]
4
5pub mod common;
7
8pub mod init;
10
11pub mod basics;
13
14pub mod device;
16
17pub mod surface;
19
20pub mod renderpass;
22
23pub mod framebuffer;
25
26pub mod rendertarget;
28
29pub mod swapchain;
31
32pub mod cmdpool;
34
35pub mod shader;
37
38pub mod descpool;
40
41pub mod context;
43
44pub mod buffer;
46
47pub mod buffervec;
49
50pub mod texture;
52
53pub mod material;
55
56pub mod mesh;
58
59pub mod descprops;
61
62pub mod pipeline;
64
65pub mod wavefrontobj;
67
68extern crate nalgebra_glm as glm;
69
70pub mod prelude {
72 pub use vkcore_rs::*;
73 pub use glm::*;
74 pub use half::f16;
75 pub use struct_iterable::Iterable;
76 pub use crate::common::*;
77 pub use crate::init::*;
78 pub use crate::basics::*;
79 pub use crate::device::*;
80 pub use crate::surface::*;
81 pub use crate::renderpass::*;
82 pub use crate::framebuffer::*;
83 pub use crate::rendertarget::*;
84 pub use crate::swapchain::*;
85 pub use crate::cmdpool::*;
86 pub use crate::shader::*;
87 pub use crate::descpool::*;
88 pub use crate::context::*;
89 pub use crate::buffer::*;
90 pub use crate::buffervec::*;
91 pub use crate::texture::*;
92 pub use crate::material::*;
93 pub use crate::mesh::*;
94 pub use crate::descprops::*;
95 pub use crate::pipeline::*;
96 pub use crate::wavefrontobj::*;
97 pub use crate::derive_vertex_type;
98 pub use crate::derive_uniform_buffer_type;
99 pub use crate::derive_storage_buffer_type;
100
101 unsafe extern "C" {
102 fn glfwGetTime() -> f64;
103 fn glfwSetTime(time: f64);
104 }
105
106 pub fn glfw_get_time() -> f64 {
107 unsafe {glfwGetTime()}
108 }
109
110 pub fn glfw_set_time(time: f64) {
111 unsafe {glfwSetTime(time)}
112 }
113}
114
115#[cfg(test)]
116mod tests {
117 use glfw::*;
118 use crate::prelude::*;
119 use std::{
120 collections::HashMap,
121 ffi::CStr,
122 path::PathBuf,
123 slice::from_raw_parts_mut,
124 sync::{
125 Arc,
126 Mutex,
127 RwLock,
128 atomic::{
129 AtomicBool,
130 Ordering,
131 }
132 },
133 thread,
134 time::Duration,
135 };
136
137 const TEST_TIME: f64 = 10.0;
138
139 #[derive(Debug)]
140 pub struct TestInstance {
141 pub ctx: Arc<RwLock<VulkanContext>>,
142 pub window: PWindow,
143 pub events: GlfwReceiver<(f64, WindowEvent)>,
144 pub glfw: Glfw,
145 }
146
147 impl TestInstance {
148 pub fn new(width: u32, height: u32, title: &str, window_mode: glfw::WindowMode) -> Result<Self, VulkanError> {
149 static GLFW_LOCK: Mutex<u32> = Mutex::new(0);
150 let glfw_lock = GLFW_LOCK.lock().unwrap();
151 let mut glfw = glfw::init(glfw::fail_on_errors).unwrap();
152 glfw.window_hint(WindowHint::ClientApi(ClientApiHint::NoApi));
153 let (mut window, events) = glfw.create_window(width, height, title, window_mode).expect("Failed to create GLFW window.");
154 drop(glfw_lock);
155 window.set_key_polling(true);
156 let device_requirement = DeviceRequirement {
157 can_graphics: true,
158 can_compute: false,
159 name_subtring: "",
160 };
161 let ctx = Arc::new(RwLock::new(create_vulkan_context(&window, device_requirement, PresentInterval::VSync, 1, false)?));
162 let ctx_lock = ctx.read().unwrap();
163 for gpu in VulkanGpuInfo::get_gpu_info(&ctx_lock.vkcore)?.iter() {
164 println!("Found GPU: {}", unsafe{CStr::from_ptr(gpu.properties.deviceName.as_ptr())}.to_str().unwrap());
165 }
166 println!("Chosen GPU name: {}", unsafe{CStr::from_ptr(ctx_lock.device.get_gpu().properties.deviceName.as_ptr())}.to_str().unwrap());
167 println!("Chosen GPU type: {:?}", ctx_lock.device.get_gpu().properties.deviceType);
168 drop(ctx_lock);
169 Ok(Self {
170 glfw,
171 window,
172 events,
173 ctx,
174 })
175 }
176
177 pub fn get_time(&self) -> f64 {
178 glfw_get_time()
179 }
180
181 pub fn set_time(&self, time: f64) {
182 glfw_set_time(time)
183 }
184
185 pub fn run(&mut self,
186 test_time: Option<f64>,
187 mut on_render: impl FnMut(&VulkanContext, f64) -> Result<(), VulkanError> + Send + 'static
188 ) -> Result<(), VulkanError> {
189 let exit_flag = Arc::new(AtomicBool::new(false));
190 let exit_flag_cloned = exit_flag.clone();
191 let start_time = self.glfw.get_time();
192 let ctx = self.ctx.clone();
193 let renderer_thread = thread::spawn(move || {
194 let mut num_frames = 0;
195 let mut time_in_sec: u64 = 0;
196 let mut num_frames_prev: u64 = 0;
197 while !exit_flag_cloned.load(Ordering::Relaxed) {
198 let cur_frame_time = glfw_get_time();
199 let run_time = cur_frame_time - start_time;
200 on_render(&mut ctx.write().unwrap(), run_time).unwrap();
201 num_frames += 1;
202 let new_time_in_sec = run_time.floor() as u64;
203 if new_time_in_sec > time_in_sec {
204 let fps = num_frames - num_frames_prev;
205 println!("FPS: {fps}\tat {new_time_in_sec}s");
206 time_in_sec = new_time_in_sec;
207 num_frames_prev = num_frames;
208 }
209 }
210 });
211 while !self.window.should_close() {
212 let run_time = glfw_get_time() - start_time;
213 thread::sleep(Duration::from_millis(1));
214 self.glfw.poll_events();
215 for (_, event) in glfw::flush_messages(&self.events) {
216 match event {
217 glfw::WindowEvent::Key(Key::Escape, _, Action::Press, _) => {
218 self.window.set_should_close(true);
219 }
220 _ => {}
221 }
222 }
223 if let Some(test_time) = test_time {
224 if run_time >= test_time {
225 self.window.set_should_close(true);
226 }
227 }
228 }
229 exit_flag.store(true, Ordering::Relaxed);
230 renderer_thread.join().unwrap();
231 println!("End of the test");
232 Ok(())
233 }
234 }
235
236 unsafe impl Send for TestInstance {}
237 unsafe impl Sync for TestInstance {}
238
239 #[test]
240 fn basic_test() {
241 derive_vertex_type! {
242 pub struct VertexType {
243 pub position: Vec2,
244 }
245 }
246 derive_uniform_buffer_type! {
247 pub struct UniformInput {
248 resolution: Vec3,
249 time: f32,
250 }
251 }
252 struct Resources {
253 uniform_input: Arc<dyn GenericUniformBuffer>,
254 pipeline: Pipeline,
255 }
256
257 impl Resources {
258 pub fn new(ctx: &VulkanContext) -> Result<Self, VulkanError> {
259 let device = ctx.device.clone();
260 let draw_shaders = Arc::new(DrawShaders::new(
261 Arc::new(VulkanShader::new_from_source_file_or_cache(device.clone(), ShaderSourcePath::VertexShader(PathBuf::from("shaders/test.vsh")), false, "main", OptimizationLevel::Performance, false)?),
262 None,
263 None,
264 None,
265 Arc::new(VulkanShader::new_from_source_file_or_cache(device.clone(), ShaderSourcePath::FragmentShader(PathBuf::from("shaders/test.fsh")), false, "main", OptimizationLevel::Performance, false)?),
266 ));
267 let uniform_input: Arc<dyn GenericUniformBuffer> = Arc::new(UniformBuffer::<UniformInput>::new(device.clone())?);
268 let desc_props = Arc::new(DescriptorProps::default());
269 desc_props.new_uniform_buffer(0, 0, uniform_input.clone());
270 let pool_in_use = ctx.cmdpools[0].use_pool(None)?;
271 let vertices_data = vec![
272 VertexType {
273 position: Vec2::new(-1.0, -1.0),
274 },
275 VertexType {
276 position: Vec2::new( 1.0, -1.0),
277 },
278 VertexType {
279 position: Vec2::new(-1.0, 1.0),
280 },
281 VertexType {
282 position: Vec2::new( 1.0, 1.0),
283 },
284 ];
285 let vertices = Arc::new(RwLock::new(BufferWithType::new(device.clone(), &vertices_data, pool_in_use.cmdbuf, VkBufferUsageFlagBits::VK_BUFFER_USAGE_VERTEX_BUFFER_BIT as VkBufferUsageFlags)?));
286 let mesh = Arc::new(GenericMeshWithMaterial::new(Arc::new(Mesh::new(VkPrimitiveTopology::VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP, vertices, buffer_unused(), buffer_unused(), buffer_unused())), "", None));
287 mesh.geometry.flush(pool_in_use.cmdbuf)?;
288 drop(pool_in_use);
289 ctx.cmdpools[0].wait_for_submit(u64::MAX)?;
290 mesh.geometry.discard_staging_buffers();
291 let pipeline = ctx.create_pipeline_builder(mesh, draw_shaders, desc_props)?
292 .set_cull_mode(VkCullModeFlagBits::VK_CULL_MODE_NONE as VkCullModeFlags)
293 .set_depth_test(false)
294 .set_depth_write(false)
295 .build()?;
296 Ok(Self {
297 uniform_input,
298 pipeline,
299 })
300 }
301
302 pub fn draw(&self, ctx: &VulkanContext, run_time: f64) -> Result<(), VulkanError> {
303 let scene = ctx.begin_scene(0, None)?;
304 let cmdbuf = scene.get_cmdbuf();
305 let extent = scene.get_rendertarget_extent();
306
307 let ui_data = unsafe {from_raw_parts_mut(self.uniform_input.get_staging_buffer_address()? as *mut UniformInput, 1)};
308 ui_data[0] = UniformInput {
309 resolution: Vec3::new(extent.width as f32, extent.height as f32, 1.0),
310 time: run_time as f32,
311 };
312 self.uniform_input.flush(cmdbuf)?;
313
314 scene.set_viewport_swapchain(0.0, 1.0)?;
315 scene.set_scissor_swapchain()?;
316 scene.begin_renderpass(Vec4::new(0.0, 0.0, 0.2, 1.0), 1.0, 0)?;
317 self.pipeline.draw(cmdbuf)?;
318 scene.end_renderpass()?;
319 scene.finish();
320 Ok(())
321 }
322 }
323
324 let mut inst = Box::new(TestInstance::new(1024, 768, "Vulkan test", glfw::WindowMode::Windowed).unwrap());
325 let resources = Resources::new(&inst.ctx.write().unwrap()).unwrap();
326 inst.run(Some(TEST_TIME),
327 move |ctx: &VulkanContext, run_time: f64| -> Result<(), VulkanError> {
328 resources.draw(ctx, run_time)
329 }).unwrap();
330 }
331
332 #[test]
333 fn avocado() {
334 derive_vertex_type! {
335 pub struct InstanceType {
336 pub transform: Mat4,
337 }
338 }
339 derive_uniform_buffer_type! {
340 pub struct UniformInputScene {
341 view_matrix: Mat4,
342 proj_matrix: Mat4,
343 light_dir: Vec3,
344 _pad1: f32,
345 light_color: Vec3,
346 _pad2: f32,
347 ambient_color: Vec3,
348 _pad3: f32,
349 }
350 }
351 struct Resources {
352 uniform_input_scene: Arc<dyn GenericUniformBuffer>,
353 object: GenericMeshSet<InstanceType>,
354 pipelines: HashMap<String, Pipeline>,
355 }
356
357 impl Resources {
358 const OBJ_ROWS: usize = 4;
359 const OBJ_COLS: usize = 4;
360
361 pub fn new(ctx: &VulkanContext) -> Result<Self, VulkanError> {
362 let device = ctx.device.clone();
363 let draw_shaders = Arc::new(DrawShaders::new(
364 Arc::new(VulkanShader::new_from_source_file_or_cache(device.clone(), ShaderSourcePath::VertexShader(PathBuf::from("shaders/objdisp.vsh")), false, "main", OptimizationLevel::Performance, false)?),
365 None,
366 None,
367 None,
368 Arc::new(VulkanShader::new_from_source_file_or_cache(device.clone(), ShaderSourcePath::FragmentShader(PathBuf::from("shaders/objdisp.fsh")), false, "main", OptimizationLevel::Performance, false)?),
369 ));
370 let pool_in_use = ctx.cmdpools[0].use_pool(None)?;
371 let object = GenericMeshSet::create_meshset_from_obj_file::<f32, ObjVertPositionTexcoord2DNormalTangent, _>(device.clone(), "assets/testobj/avocado.obj", pool_in_use.cmdbuf, Some(&[InstanceType {transform: Mat4::identity()}; Self::OBJ_ROWS * Self::OBJ_COLS]))?;
372 let uniform_input_scene: Arc<dyn GenericUniformBuffer> = Arc::new(UniformBuffer::<UniformInputScene>::new(device.clone())?);
373 let desc_props = Arc::new(DescriptorProps::default());
374 desc_props.new_uniform_buffer(0, 0, uniform_input_scene.clone());
375 let mut pipelines: HashMap<String, Pipeline> = HashMap::with_capacity(object.meshset.len());
376 for mesh in object.meshset.values() {
377 if let Some(material) = &mesh.material {
378 if let Some(albedo) = material.get_albedo() {
379 if let MaterialComponent::Texture(texture) = albedo {
380 texture.prepare_for_sample(pool_in_use.cmdbuf)?;
381 let texture_input_albedo = TextureForSample {
382 texture: texture.clone(),
383 sampler: Arc::new(VulkanSampler::new_linear(device.clone(), true, false)?),
384 };
385 desc_props.new_texture(0, 1, texture_input_albedo);
386 }
387 }
388 if let Some(normal) = material.get_normal() {
389 if let MaterialComponent::Texture(texture) = normal {
390 texture.prepare_for_sample(pool_in_use.cmdbuf)?;
391 let texture_input_normal = TextureForSample {
392 texture: texture.clone(),
393 sampler: Arc::new(VulkanSampler::new_linear(device.clone(), true, false)?),
394 };
395 desc_props.new_texture(0, 2, texture_input_normal);
396 }
397 }
398 }
399 }
400 drop(pool_in_use);
401 ctx.cmdpools[0].wait_for_submit(u64::MAX)?;
402 object.discard_staging_buffers();
403 for (matname, mesh) in object.meshset.iter() {
404 let pipeline = ctx.create_pipeline_builder(mesh.clone(), draw_shaders.clone(), desc_props.clone())?
405 .set_depth_test(true)
406 .set_depth_write(true)
407 .build()?;
408 pipelines.insert(matname.clone(), pipeline);
409 }
410 Ok(Self {
411 uniform_input_scene,
412 object,
413 pipelines,
414 })
415 }
416
417 pub fn draw(&self, ctx: &VulkanContext, run_time: f64) -> Result<(), VulkanError> {
418 let scene = ctx.begin_scene(0, None)?;
419 let cmdbuf = scene.get_cmdbuf();
420 let extent = scene.get_rendertarget_extent();
421
422 let view_matrix = {
423 let eye = glm::vec3(25.0, 25.0, 25.0);
424 let center = glm::vec3(0.0, 0.0, 0.0);
425 let up = glm::vec3(0.0, 1.0, 0.0);
426 glm::look_at(&eye, ¢er, &up)
427 };
428
429 let mut proj_matrix = {
430 let fovy = pi::<f32>() / 3.0;
431 let aspect = extent.width as f32 / extent.height as f32;
432 perspective(aspect, fovy, 0.1, 1000.0)
433 };
434 proj_matrix[(1, 1)] *= -1.0;
435
436 let ui_data = unsafe {from_raw_parts_mut(self.uniform_input_scene.get_staging_buffer_address()? as *mut UniformInputScene, 1)};
437 ui_data[0].view_matrix = view_matrix;
438 ui_data[0].proj_matrix = proj_matrix;
439 ui_data[0].light_dir = normalize(&Vec3::new(-1.2, -1.0, -1.0));
440 ui_data[0].light_color = Vec3::new(1.0, 1.0, 1.0);
441 ui_data[0].ambient_color = Vec3::new(0.1, 0.2, 0.1);
442 self.uniform_input_scene.flush(cmdbuf)?;
443 let mut lock = self.object.edit_instances().unwrap();
444 for (i, x, y) in (0..Self::OBJ_ROWS).flat_map(|y| (0..Self::OBJ_COLS).map(move |x| (y * Self::OBJ_COLS + x, x, y))) {
445 let x = x as f32 - Self::OBJ_ROWS as f32 * 0.5;
446 let y = y as f32 - Self::OBJ_COLS as f32 * 0.5;
447 lock[i] = InstanceType {
448 transform: glm::rotate(&glm::translate(&Mat4::identity(), &Vec3::new(-x * 8.0, -5.0, -y * 8.0)), run_time as f32 + i as f32, &normalize(&glm::vec3(0.0, 1.0, i as f32))),
449 };
450 }
451 drop(lock);
452
453 scene.set_viewport_swapchain(0.0, 1.0)?;
454 scene.set_scissor_swapchain()?;
455 for pipeline in self.pipelines.values() {
456 pipeline.prepare_data(cmdbuf)?;
457 scene.begin_renderpass(Vec4::new(0.0, 0.2, 0.3, 1.0), 1.0, 0)?;
458 pipeline.draw(cmdbuf)?;
459 scene.end_renderpass()?;
460 }
461 scene.finish();
462 Ok(())
463 }
464 }
465
466 let mut inst = Box::new(TestInstance::new(1024, 768, "Vulkan avocado test", glfw::WindowMode::Windowed).unwrap());
467 let resources = Resources::new(&inst.ctx.write().unwrap()).unwrap();
468 inst.run(Some(TEST_TIME),
469 move |ctx: &VulkanContext, run_time: f64| -> Result<(), VulkanError> {
470 resources.draw(ctx, run_time)
471 }).unwrap();
472 }
473}