From d4b2d14fb7676820523d265143cb1e00fbd5cdfb Mon Sep 17 00:00:00 2001 From: deepCurse Date: Tue, 28 Jan 2025 18:39:39 -0400 Subject: [PATCH] dev sync --- Cargo.lock | 2 + Cargo.toml | 9 +- assets/common/archlogo.512.png | Bin 0 -> 25275 bytes shaders/f_default.glsl | 3 +- shaders/v_default.glsl | 27 +- src/audio_engines/mod.rs | 0 src/cataclysm/mod.rs | 16 + src/graphics_engines/directx/mod.rs | 1 + src/graphics_engines/gxm/mod.rs | 1 + src/graphics_engines/metal/mod.rs | 1 + src/graphics_engines/mod.rs | 13 + src/graphics_engines/opengl/mod.rs | 1 + src/graphics_engines/vulkan/mod.rs | 1422 +++++++++++++++++++++++++++ src/graphics_engines/webgpu/mod.rs | 1 + src/linux.rs | 848 +--------------- src/mac.rs | 96 ++ src/macos/metal_bindgen/metal.rs | 0 src/main.rs | 53 +- src/vita/mod.rs | 172 +--- src/wasm.rs | 6 + src/windows.rs | 95 ++ thanks.txt | 12 +- 22 files changed, 1775 insertions(+), 1004 deletions(-) create mode 100644 assets/common/archlogo.512.png create mode 100644 src/audio_engines/mod.rs create mode 100644 src/cataclysm/mod.rs create mode 100644 src/graphics_engines/directx/mod.rs create mode 100644 src/graphics_engines/gxm/mod.rs create mode 100644 src/graphics_engines/metal/mod.rs create mode 100644 src/graphics_engines/mod.rs create mode 100644 src/graphics_engines/opengl/mod.rs create mode 100644 src/graphics_engines/vulkan/mod.rs create mode 100644 src/graphics_engines/webgpu/mod.rs create mode 100644 src/mac.rs create mode 100644 src/macos/metal_bindgen/metal.rs create mode 100644 src/wasm.rs diff --git a/Cargo.lock b/Cargo.lock index baee0ba..784fd19 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -910,7 +910,9 @@ dependencies = [ "jobserver", "libc", "log", + "metal 0.31.0", "num_cpus", + "objc", "png", "rand", "shaderc", diff --git a/Cargo.toml b/Cargo.toml index 0c4f69e..063a712 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,9 +12,14 @@ title_name = "MineMod" opt-level = 2 lto = true codegen-units = 1 -panic = "abort" +# panic = "abort" # Vita platform doesnt support abort currently strip = "debuginfo" +# this should be working but isnt +# [target.'cfg(target_os = "vita")'] +# [target.'armv7-sony-vita-newlibeabihf'] +# rustflags = ["-C","panic=unwind"] + [profile.release.package."*"] opt-level = 3 codegen-units = 1 @@ -90,3 +95,5 @@ tokio = { version = "1.36.0", features = [ [target.'cfg(target_os = "linux")'.dependencies] [target.'cfg(target_os = "macos")'.dependencies] +metal = "0.31.0" +objc = "0.2.7" diff --git a/assets/common/archlogo.512.png b/assets/common/archlogo.512.png new file mode 100644 index 0000000000000000000000000000000000000000..26e46a7ced93cfb25e112182cb3f9fed3dbe1e16 GIT binary patch literal 25275 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 literal 0 HcmV?d00001 diff --git a/shaders/f_default.glsl b/shaders/f_default.glsl index c53f80f..13009da 100644 --- a/shaders/f_default.glsl +++ b/shaders/f_default.glsl @@ -1,4 +1,3 @@ - #version 450 layout(location = 0) in vec3 fragColor; @@ -7,4 +6,4 @@ layout(location = 0) out vec4 outColor; void main() { outColor = vec4(fragColor, 1.0); -} +} \ No newline at end of file diff --git a/shaders/v_default.glsl b/shaders/v_default.glsl index 66d6766..dec0210 100644 --- a/shaders/v_default.glsl +++ b/shaders/v_default.glsl @@ -1,20 +1,17 @@ #version 450 +layout(binding = 0) uniform UniformBufferObject { + mat4 model; + mat4 view; + mat4 proj; +} ubo; + +layout(location = 0) in vec3 inPosition; +layout(location = 1) in vec3 inColor; + layout(location = 0) out vec3 fragColor; -vec2 positions[3] = vec2[]( - vec2(0.0, -0.5), - vec2(0.5, 0.5), - vec2(-0.5, 0.5) -); - -vec3 colors[3] = vec3[]( - vec3(1.0, 0.0, 0.0), - vec3(0.0, 1.0, 0.0), - vec3(0.0, 0.0, 1.0) -); - void main() { - gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0); - fragColor = colors[gl_VertexIndex]; -} \ No newline at end of file + gl_Position = ubo.proj * ubo.view * ubo.model * vec4(inPosition, 1.0); + fragColor = inColor; +} diff --git a/src/audio_engines/mod.rs b/src/audio_engines/mod.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/cataclysm/mod.rs b/src/cataclysm/mod.rs new file mode 100644 index 0000000..83fde03 --- /dev/null +++ b/src/cataclysm/mod.rs @@ -0,0 +1,16 @@ +pub(crate) trait GraphicsCommander {} + +pub struct Cataclysm { + game_objects:Vec, +} + +pub struct GameObject { + pos:(f64, f64, f64), +} + +pub enum ModelKind { + Cube, + Complex(), +} + +pub struct ResourceReference {} diff --git a/src/graphics_engines/directx/mod.rs b/src/graphics_engines/directx/mod.rs new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/graphics_engines/directx/mod.rs @@ -0,0 +1 @@ + diff --git a/src/graphics_engines/gxm/mod.rs b/src/graphics_engines/gxm/mod.rs new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/graphics_engines/gxm/mod.rs @@ -0,0 +1 @@ + diff --git a/src/graphics_engines/metal/mod.rs b/src/graphics_engines/metal/mod.rs new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/graphics_engines/metal/mod.rs @@ -0,0 +1 @@ + diff --git a/src/graphics_engines/mod.rs b/src/graphics_engines/mod.rs new file mode 100644 index 0000000..2337c02 --- /dev/null +++ b/src/graphics_engines/mod.rs @@ -0,0 +1,13 @@ +#[cfg(any(target_os = "linux", target_os = "windows", target_os = "macos"))] +pub mod vulkan; + +#[cfg(any(target_os = "linux", target_os = "windows", target_os = "macos"))] +pub mod opengl; + +#[cfg(target_family = "wasm")] pub mod webgpu; + +#[cfg(target_os = "macos")] pub mod metal; + +#[cfg(target_os = "windows")] pub mod directx; + +#[cfg(target_os = "vita")] pub mod gxm; diff --git a/src/graphics_engines/opengl/mod.rs b/src/graphics_engines/opengl/mod.rs new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/graphics_engines/opengl/mod.rs @@ -0,0 +1 @@ + diff --git a/src/graphics_engines/vulkan/mod.rs b/src/graphics_engines/vulkan/mod.rs new file mode 100644 index 0000000..aba203c --- /dev/null +++ b/src/graphics_engines/vulkan/mod.rs @@ -0,0 +1,1422 @@ +use anyhow::Result; +use anyhow::anyhow; + +use cgmath::point3; +use cgmath::vec2; +use cgmath::vec3; + +use cgmath::Deg; +use log::*; + +use std::collections::HashSet; +use std::ffi::CStr; +use std::fs::File; +use std::mem::size_of; +use std::os::raw::c_void; +use std::ptr::copy_nonoverlapping as memcpy; +use std::time::Instant; + +use thiserror::Error; + +use vulkanalia::Version; +use vulkanalia::bytecode::Bytecode; +use vulkanalia::loader::LIBRARY; +use vulkanalia::loader::LibloadingLoader; +use vulkanalia::prelude::v1_0::*; +use vulkanalia::window as vk_window; + +use vulkanalia::vk::ExtDebugUtilsExtension; +use vulkanalia::vk::KhrSurfaceExtension; +use vulkanalia::vk::KhrSwapchainExtension; + +use winit::window::Window; + +type Vec2f = cgmath::Vector2; +type Vec3f = cgmath::Vector3; +type Vec4f = cgmath::Vector4; + +type Vec2d = cgmath::Vector2; +type Vec3d = cgmath::Vector3; +type Vec4d = cgmath::Vector4; + +type Mat2f = cgmath::Matrix2; +type Mat3f = cgmath::Matrix3; +type Mat4f = cgmath::Matrix4; + +type Mat2d = cgmath::Matrix2; +type Mat3d = cgmath::Matrix3; +type Mat4d = cgmath::Matrix4; + +const VALIDATION_ENABLED:bool = cfg!(debug_assertions); // add env support? +const VALIDATION_LAYER:vk::ExtensionName = vk::ExtensionName::from_bytes(b"VK_LAYER_KHRONOS_validation"); + +const DEVICE_EXTENSIONS:&[vk::ExtensionName] = &[vk::KHR_SWAPCHAIN_EXTENSION.name]; +const PORTABILITY_MACOS_VERSION:Version = Version::new(1, 3, 216); + +const VK_APPLICATION_NAME:&'static [u8; 8] = b"minemod\0"; +const VK_ENGINE_NAME:&'static [u8; 10] = b"cataclysm\0"; +const MAX_FRAMES_IN_FLIGHT:usize = 2; + +// static VERTICES:[Vertex; 3] = [ +// Vertex::new(vec2(0.0, -0.5), vec3(0.3, 0.0, 1.0)), +// Vertex::new(vec2(0.5, 0.5), vec3(0.3, 0.0, 1.0)), +// Vertex::new(vec2(-0.5, 0.5), vec3(0.3, 0.0, 1.0)), +// ]; + +static VERTICES:[Vertex; 4] = [ + Vertex::new(vec2(-0.5, -0.5), vec3(0.3, 0.0, 1.0)), + Vertex::new(vec2(0.5, -0.5), vec3(0.3, 0.0, 1.0)), + Vertex::new(vec2(0.5, 0.5), vec3(0.3, 0.0, 1.0)), + Vertex::new(vec2(-0.5, 0.5), vec3(0.3, 0.0, 1.0)), + // Vertex::new(vec2(-1.0, -1.0),vec3(0.3, 0.0, 1.0)), + // Vertex::new(vec2(1.0, -1.0), vec3(0.3, 0.0, 1.0)), + // Vertex::new(vec2(1.0, 1.0), vec3(0.3, 0.0, 1.0)), + // Vertex::new(vec2(-1.0, 1.0), vec3(0.3, 0.0, 1.0)), +]; +const INDICES:&[u16] = &[0, 1, 2, 2, 3, 0]; + +macro_rules! const_shaders { + { $($vis:vis $identifier:ident = $string:literal; )* } => { + $( + #[allow(non_upper_case_globals)] + $vis static $identifier: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/shaders/", $string)); + )* + }; +} + +/// Our Vulkan app. +#[derive(Clone, Debug)] +pub(crate) struct App { + /// Vulkan entrypoint + entry: Entry, + instance: Instance, + data: AppData, + pub device: Device, + frame: usize, // current frame + pub resized:bool, + start: Instant, +} + +impl App { + /// Creates our Vulkan app. + pub unsafe fn create(window:&Window) -> Result { + let loader = LibloadingLoader::new(LIBRARY)?; + let entry = Entry::new(loader).map_err(|b| anyhow!("{}", b))?; + let mut data = AppData::default(); + let instance = create_instance(window, &entry, &mut data)?; + data.surface = vk_window::create_surface(&instance, &window, &window)?; + pick_physical_device(&instance, &mut data)?; + let device = create_logical_device(&entry, &instance, &mut data)?; + create_swapchain(window, &instance, &device, &mut data)?; + create_swapchain_image_views(&device, &mut data)?; + create_render_pass(&instance, &device, &mut data)?; + create_descriptor_set_layout(&device, &mut data)?; + create_pipeline(&device, &mut data)?; + create_framebuffers(&device, &mut data)?; + create_command_pool(&instance, &device, &mut data)?; + create_texture_image(&instance, &device, &mut data)?; + create_texture_image_view(&device, &mut data)?; + create_texture_sampler(&device, &mut data)?; + create_vertex_buffer(&instance, &device, &mut data)?; + create_index_buffer(&instance, &device, &mut data)?; + create_uniform_buffers(&instance, &device, &mut data)?; + create_descriptor_pool(&device, &mut data)?; + create_descriptor_sets(&device, &mut data)?; + create_command_buffers(&device, &mut data)?; + create_sync_objects(&device, &mut data)?; + Ok(Self { + entry, + instance, + data, + device, + frame:0, + resized:false, + start:Instant::now(), + }) + } + + /// Renders a frame for our Vulkan app. + pub unsafe fn render(&mut self, window:&Window) -> Result<()> { + self.device.wait_for_fences(&[self.data.in_flight_fences[self.frame]], true, u64::MAX)?; + + let result = self.device.acquire_next_image_khr( + self.data.swapchain, + u64::MAX, + self.data.image_available_semaphores[self.frame], + vk::Fence::null(), + ); + + let image_index = match result { + Ok((image_index, _)) => image_index as usize, + Err(vk::ErrorCode::OUT_OF_DATE_KHR) => return self.recreate_swapchain(window), + Err(e) => return Err(anyhow!(e)), + }; + + if !self.data.images_in_flight[image_index as usize].is_null() { + self.device + .wait_for_fences(&[self.data.images_in_flight[image_index as usize]], true, u64::MAX)?; + } + + self.data.images_in_flight[image_index as usize] = self.data.in_flight_fences[self.frame]; + + self.update_uniform_buffer(image_index)?; + + let wait_semaphores = &[self.data.image_available_semaphores[self.frame]]; + let wait_stages = &[vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT]; + let command_buffers = &[self.data.command_buffers[image_index as usize]]; + let signal_semaphores = &[self.data.render_finished_semaphores[self.frame]]; + let submit_info = vk::SubmitInfo::builder() + .wait_semaphores(wait_semaphores) + .wait_dst_stage_mask(wait_stages) + .command_buffers(command_buffers) + .signal_semaphores(signal_semaphores); + + self.device.reset_fences(&[self.data.in_flight_fences[self.frame]])?; + + self.device + .queue_submit(self.data.graphics_queue, &[submit_info], self.data.in_flight_fences[self.frame])?; + + let swapchains = &[self.data.swapchain]; + let image_indices = &[image_index as u32]; + let present_info = vk::PresentInfoKHR::builder() + .wait_semaphores(signal_semaphores) + .swapchains(swapchains) + .image_indices(image_indices); + + let result = self.device.queue_present_khr(self.data.present_queue, &present_info); + + let changed = result == Ok(vk::SuccessCode::SUBOPTIMAL_KHR) || result == Err(vk::ErrorCode::OUT_OF_DATE_KHR); + + if self.resized || changed { + self.resized = false; + self.recreate_swapchain(window)?; + } else if let Err(e) = result { + return Err(anyhow!(e)); + } + + // self.device.queue_wait_idle(self.data.present_queue)?; + + self.frame = (self.frame + 1) % MAX_FRAMES_IN_FLIGHT; + + Ok(()) + } + + unsafe fn update_uniform_buffer(&self, image_index:usize) -> Result<()> { + let time = self.start.elapsed().as_secs_f32(); + + let model = Mat4f::from_axis_angle(vec3(0.0, 0.0, 1.0), Deg(45.0) * time); + + let view = Mat4f::look_at_rh(point3(2.0, 5.0, 2.0), point3(0.0, 0.0, 0.0), vec3(0.0, 0.0, 1.0)); + + let mut proj = cgmath::perspective( + Deg(45.0), + self.data.swapchain_extent.width as f32 / self.data.swapchain_extent.height as f32, + 0.1, + 10.0, + ); + + // cgmath was made with opengl in mind, this fixes it to work with vulkan + proj[1][1] *= -1.0; + + let ubo = UniformBufferObject { model, view, proj }; + + let memory = self.device.map_memory( + self.data.uniform_buffers_memory[image_index], + 0, + size_of::() as u64, + vk::MemoryMapFlags::empty(), + )?; + + memcpy(&ubo, memory.cast(), 1); + + self.device.unmap_memory(self.data.uniform_buffers_memory[image_index]); + + Ok(()) + } + + unsafe fn recreate_swapchain(&mut self, window:&Window) -> Result<()> { + self.device.device_wait_idle()?; + self.destroy_swapchain(); + create_swapchain(window, &self.instance, &self.device, &mut self.data)?; + create_swapchain_image_views(&self.device, &mut self.data)?; + create_render_pass(&self.instance, &self.device, &mut self.data)?; + create_pipeline(&self.device, &mut self.data)?; + create_framebuffers(&self.device, &mut self.data)?; + create_uniform_buffers(&self.instance, &self.device, &mut self.data)?; + create_descriptor_pool(&self.device, &mut self.data)?; + create_descriptor_sets(&self.device, &mut self.data)?; + create_command_buffers(&self.device, &mut self.data)?; + self.data.images_in_flight.resize(self.data.swapchain_images.len(), vk::Fence::null()); + + Ok(()) + } + + /// Destroys our Vulkan app. + pub unsafe fn destroy(&mut self) { + self.device.device_wait_idle().unwrap(); + + self.destroy_swapchain(); + + self.data.in_flight_fences.iter().for_each(|f| self.device.destroy_fence(*f, None)); + self.data + .render_finished_semaphores + .iter() + .for_each(|s| self.device.destroy_semaphore(*s, None)); + self.data + .image_available_semaphores + .iter() + .for_each(|s| self.device.destroy_semaphore(*s, None)); + + self.device.free_memory(self.data.index_buffer_memory, None); + self.device.destroy_buffer(self.data.index_buffer, None); + + self.device.free_memory(self.data.vertex_buffer_memory, None); + self.device.destroy_buffer(self.data.vertex_buffer, None); + + self.device.free_memory(self.data.texture_image_memory, None); + self.device.destroy_image(self.data.texture_image, None); + self.device.destroy_image_view(self.data.texture_image_view, None); + + self.device.destroy_command_pool(self.data.command_pool, None); + + self.device.destroy_descriptor_set_layout(self.data.descriptor_set_layout, None); + + self.device.destroy_device(None); + + self.instance.destroy_surface_khr(self.data.surface, None); + + if VALIDATION_ENABLED { + self.instance.destroy_debug_utils_messenger_ext(self.data.messenger, None); + } + + self.instance.destroy_instance(None); + } + + unsafe fn destroy_swapchain(&mut self) { + self.device.free_command_buffers(self.data.command_pool, &self.data.command_buffers); + self.device.destroy_descriptor_pool(self.data.descriptor_pool, None); + + self.data.uniform_buffers_memory.iter().for_each(|m| self.device.free_memory(*m, None)); + self.data.uniform_buffers.iter().for_each(|b| self.device.destroy_buffer(*b, None)); + self.data.framebuffers.iter().for_each(|f| self.device.destroy_framebuffer(*f, None)); + self.device.destroy_pipeline(self.data.pipeline, None); + self.device.destroy_pipeline_layout(self.data.pipeline_layout, None); + self.device.destroy_render_pass(self.data.render_pass, None); + self.data.swapchain_image_views.iter().for_each(|v| self.device.destroy_image_view(*v, None)); + self.device.destroy_swapchain_khr(self.data.swapchain, None); + } +} + +/// The Vulkan handles and associated properties used by our Vulkan app. +#[derive(Clone, Debug, Default)] +struct AppData { + messenger: vk::DebugUtilsMessengerEXT, + surface: vk::SurfaceKHR, + physical_device: vk::PhysicalDevice, + graphics_queue: vk::Queue, + present_queue: vk::Queue, + swapchain_format: vk::Format, + swapchain_extent: vk::Extent2D, + swapchain: vk::SwapchainKHR, + swapchain_images: Vec, + swapchain_image_views: Vec, + render_pass: vk::RenderPass, + descriptor_set_layout: vk::DescriptorSetLayout, + pipeline_layout: vk::PipelineLayout, + pipeline: vk::Pipeline, + framebuffers: Vec, + command_pool: vk::CommandPool, + /// Vec of command buffers, one per in flight frame, this way they can be separated and you wont draw on the wrong frame + command_buffers: Vec, + image_available_semaphores:Vec, + render_finished_semaphores:Vec, + in_flight_fences: Vec, + images_in_flight: Vec, + vertex_buffer: vk::Buffer, + vertex_buffer_memory: vk::DeviceMemory, + index_buffer: vk::Buffer, + index_buffer_memory: vk::DeviceMemory, + uniform_buffers: Vec, + uniform_buffers_memory: Vec, + descriptor_pool: vk::DescriptorPool, + descriptor_sets: Vec, + texture_image: vk::Image, + texture_image_memory: vk::DeviceMemory, + texture_image_view: vk::ImageView, +} + +unsafe fn create_instance(window:&Window, entry:&Entry, data:&mut AppData) -> Result { + // TODO learn why we give this information to the vulkan driver, im assuming its for data training purposes? + // This way the driver can be shipped with pre trained paths for popular games allowing it to run better? + // Lets hope one day we will be on this list + + let application_info = vk::ApplicationInfo::builder() + // Our application information + .application_name(VK_APPLICATION_NAME) + .application_version(vk::make_version(0, 1, 0)) + // Our engine information + .engine_name(VK_ENGINE_NAME) + .engine_version(vk::make_version(0, 1, 0)) + // Vulkan api version target + .api_version(vk::make_version(1, 0, 0)); + + // Get the names of available layers + let available_layers = entry + .enumerate_instance_layer_properties()? + .iter() + .map(|l| l.layer_name) + .collect::>(); + + if VALIDATION_ENABLED && !available_layers.contains(&VALIDATION_LAYER) { + return Err(anyhow!("Validation layer requested but not supported.")); + } + + let layers = if VALIDATION_ENABLED { vec![VALIDATION_LAYER.as_ptr()] } else { Vec::new() }; + + // Generate a list of required extensions for creating a window instance via winit + let mut extensions = vk_window::get_required_instance_extensions(window) + .iter() + .map(|e| e.as_ptr()) + .collect::>(); + + // needed for semaphores somehow + extensions.push(vk::KHR_GET_PHYSICAL_DEVICE_PROPERTIES2_EXTENSION.name.as_ptr()); + + // Required by Vulkan SDK on macOS since 1.3.216. + let flags = if cfg!(target_os = "macos") && entry.version()? >= PORTABILITY_MACOS_VERSION { + info!("Enabling extensions for macOS portability."); + // extensions.push( + // // already present above + // vk::KHR_GET_PHYSICAL_DEVICE_PROPERTIES2_EXTENSION.name.as_ptr(), + // ); + extensions.push(vk::KHR_PORTABILITY_ENUMERATION_EXTENSION.name.as_ptr()); + vk::InstanceCreateFlags::ENUMERATE_PORTABILITY_KHR + } else { + vk::InstanceCreateFlags::empty() + }; + if VALIDATION_ENABLED { + extensions.push(vk::EXT_DEBUG_UTILS_EXTENSION.name.as_ptr()); + } + + let mut info = vk::InstanceCreateInfo::builder() + .application_info(&application_info) + .enabled_layer_names(&layers) + .enabled_extension_names(&extensions) + .flags(flags); + + let mut debug_info = vk::DebugUtilsMessengerCreateInfoEXT::builder() + .message_severity(vk::DebugUtilsMessageSeverityFlagsEXT::all()) + .message_type( + vk::DebugUtilsMessageTypeFlagsEXT::GENERAL | vk::DebugUtilsMessageTypeFlagsEXT::VALIDATION | vk::DebugUtilsMessageTypeFlagsEXT::PERFORMANCE, + ) + .user_callback(Some(debug_callback)); + + if VALIDATION_ENABLED { + info = info.push_next(&mut debug_info); + } + + let instance = entry.create_instance(&info, None)?; + + if VALIDATION_ENABLED { + data.messenger = instance.create_debug_utils_messenger_ext(&debug_info, None)?; + } + + Ok(instance) +} + +extern "system" fn debug_callback( + severity:vk::DebugUtilsMessageSeverityFlagsEXT, type_:vk::DebugUtilsMessageTypeFlagsEXT, data:*const vk::DebugUtilsMessengerCallbackDataEXT, _:*mut c_void, +) -> vk::Bool32 { + let data = unsafe { *data }; + let message = unsafe { CStr::from_ptr(data.message) }.to_string_lossy(); + + if severity >= vk::DebugUtilsMessageSeverityFlagsEXT::ERROR { + error!("({:?}) {}", type_, message); + } else if severity >= vk::DebugUtilsMessageSeverityFlagsEXT::WARNING { + warn!("({:?}) {}", type_, message); + } else if severity >= vk::DebugUtilsMessageSeverityFlagsEXT::INFO { + debug!("({:?}) {}", type_, message); + } else { + trace!("({:?}) {}", type_, message); + } + + vk::FALSE +} + +#[derive(Debug, Error)] +#[error("{0}")] +pub struct SuitabilityError(pub &'static str); + +unsafe fn pick_physical_device(instance:&Instance, data:&mut AppData) -> Result<()> { + for physical_device in instance.enumerate_physical_devices()? { + let properties = instance.get_physical_device_properties(physical_device); + + if let Err(error) = check_physical_device(instance, data, physical_device) { + warn!("Skipping physical device (`{}`): {}", properties.device_name, error); + } else { + info!("Selected physical device (`{}`).", properties.device_name); + data.physical_device = physical_device; + return Ok(()); + } + } + + Err(anyhow!("Failed to find suitable physical device.")) +} +unsafe fn check_physical_device(instance:&Instance, data:&AppData, physical_device:vk::PhysicalDevice) -> Result<()> { + QueueFamilyIndices::get(instance, data, physical_device)?; + check_physical_device_extensions(instance, physical_device)?; + + let support = SwapchainSupport::get(instance, data, physical_device)?; + if support.formats.is_empty() || support.present_modes.is_empty() { + return Err(anyhow!(SuitabilityError("Insufficient swapchain support."))); + } + + // // TODO handle this like the other one? + // let properties = instance.get_physical_device_properties(physical_device); + // if properties.device_type != vk::PhysicalDeviceType::DISCRETE_GPU { + // return Err(anyhow!(SuitabilityError( + // "Only discrete GPUs are supported." + // ))); + // } + + // let features = instance.get_physical_device_features(physical_device); + // let required_features = [ + // (features.geometry_shader, "Missing geometry shader support."), + // // ( // needed for vr + // // features.multi_viewport, + // // "Missing support for multiple viewports.", + // // ), + // ]; + + // for (feature, string) in required_features { + // if feature != vk::TRUE { + // return Err(anyhow!(SuitabilityError(string))); + // } + // } + + Ok(()) +} +unsafe fn check_physical_device_extensions(instance:&Instance, physical_device:vk::PhysicalDevice) -> Result<()> { + let extensions = instance + .enumerate_device_extension_properties(physical_device, None)? + .iter() + .map(|e| e.extension_name) + .collect::>(); + if DEVICE_EXTENSIONS.iter().all(|e| extensions.contains(e)) { + Ok(()) + } else { + Err(anyhow!(SuitabilityError("Missing required device extensions."))) + } +} + +unsafe fn create_logical_device(entry:&Entry, instance:&Instance, data:&mut AppData) -> Result { + let indices = QueueFamilyIndices::get(instance, data, data.physical_device)?; + + let mut unique_indices = HashSet::new(); + unique_indices.insert(indices.graphics); + unique_indices.insert(indices.present); + + let queue_priorities = &[1.0]; + let queue_infos = unique_indices + .iter() + .map(|i| vk::DeviceQueueCreateInfo::builder().queue_family_index(*i).queue_priorities(queue_priorities)) + .collect::>(); + + let layers = if VALIDATION_ENABLED { vec![VALIDATION_LAYER.as_ptr()] } else { vec![] }; + + let mut extensions = DEVICE_EXTENSIONS.iter().map(|n| n.as_ptr()).collect::>(); + + // Required by Vulkan SDK on macOS since 1.3.216. + if cfg!(target_os = "macos") && entry.version()? >= PORTABILITY_MACOS_VERSION { + extensions.push(vk::KHR_PORTABILITY_SUBSET_EXTENSION.name.as_ptr()); + } + + let features = vk::PhysicalDeviceFeatures::builder(); + + let info = vk::DeviceCreateInfo::builder() + .queue_create_infos(&queue_infos) + .enabled_layer_names(&layers) + .enabled_extension_names(&extensions) + .enabled_features(&features); + + let device = instance.create_device(data.physical_device, &info, None)?; + + data.graphics_queue = device.get_device_queue(indices.graphics, 0); + data.present_queue = device.get_device_queue(indices.present, 0); + + Ok(device) +} + +unsafe fn create_swapchain( + window:&Window, + instance:&Instance, + device:&Device, + data:&mut AppData, + // old_swapchain: Option +) -> Result<()> { + let indices = QueueFamilyIndices::get(instance, data, data.physical_device)?; + let support = SwapchainSupport::get(instance, data, data.physical_device)?; + + let surface_format = get_swapchain_surface_format(&support.formats); + let present_mode = get_swapchain_present_mode(&support.present_modes); + let extent = get_swapchain_extent(window, support.capabilities); + + data.swapchain_format = surface_format.format; + data.swapchain_extent = extent; + + let mut image_count = support.capabilities.min_image_count + 1; + if support.capabilities.max_image_count != 0 && image_count > support.capabilities.max_image_count { + image_count = support.capabilities.max_image_count; + } + + let mut queue_family_indices = vec![]; + let image_sharing_mode = if indices.graphics != indices.present { + queue_family_indices.push(indices.graphics); + queue_family_indices.push(indices.present); + vk::SharingMode::CONCURRENT + } else { + vk::SharingMode::EXCLUSIVE + }; + + let info = vk::SwapchainCreateInfoKHR::builder() + .surface(data.surface) + .min_image_count(image_count) + .image_format(surface_format.format) + .image_color_space(surface_format.color_space) + .image_extent(extent) + .image_array_layers(1) + .image_usage(vk::ImageUsageFlags::COLOR_ATTACHMENT) + .image_sharing_mode(image_sharing_mode) + .queue_family_indices(&queue_family_indices) + .pre_transform(support.capabilities.current_transform) + .composite_alpha(vk::CompositeAlphaFlagsKHR::OPAQUE) + .present_mode(present_mode) + .clipped(true) + .old_swapchain(vk::SwapchainKHR::null()); + // .old_swapchain(data.swapchain); // TODO if experiencing issues replace with vk::SwapchainKHR::null() + + data.swapchain = device.create_swapchain_khr(&info, None)?; + + data.swapchain_images = device.get_swapchain_images_khr(data.swapchain)?; + + Ok(()) +} + +fn get_swapchain_surface_format(formats:&[vk::SurfaceFormatKHR]) -> vk::SurfaceFormatKHR { + formats + .iter() + .cloned() + .find(|f| f.format == vk::Format::B8G8R8A8_SRGB && f.color_space == vk::ColorSpaceKHR::SRGB_NONLINEAR) + .unwrap_or_else(|| formats[0]) +} + +fn get_swapchain_present_mode(present_modes:&[vk::PresentModeKHR]) -> vk::PresentModeKHR { + present_modes + .iter() + .cloned() + .find(|m| *m == vk::PresentModeKHR::MAILBOX) + .unwrap_or(vk::PresentModeKHR::FIFO) +} + +fn get_swapchain_extent(window:&Window, capabilities:vk::SurfaceCapabilitiesKHR) -> vk::Extent2D { + if capabilities.current_extent.width != u32::MAX { + capabilities.current_extent + } else { + vk::Extent2D::builder() + .width( + window + .inner_size() + .width + .clamp(capabilities.min_image_extent.width, capabilities.max_image_extent.width), + ) + .height( + window + .inner_size() + .height + .clamp(capabilities.min_image_extent.height, capabilities.max_image_extent.height), + ) + .build() + } +} + +unsafe fn create_swapchain_image_views(device:&Device, data:&mut AppData) -> Result<()> { + data.swapchain_image_views = data + .swapchain_images + .iter() + .map(|i| create_image_view(device, *i, data.swapchain_format)) + .collect::, _>>()?; + + Ok(()) +} + +unsafe fn create_render_pass(instance:&Instance, device:&Device, data:&mut AppData) -> Result<()> { + let color_attachment = vk::AttachmentDescription::builder() + .format(data.swapchain_format) + .samples(vk::SampleCountFlags::_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::builder() + .attachment(0) + .layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL); + + let color_attachments = &[color_attachment_ref]; + let subpass = vk::SubpassDescription::builder() + .pipeline_bind_point(vk::PipelineBindPoint::GRAPHICS) + .color_attachments(color_attachments); + + let dependency = vk::SubpassDependency::builder() + .src_subpass(vk::SUBPASS_EXTERNAL) + .dst_subpass(0) + .src_stage_mask(vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT) + .src_access_mask(vk::AccessFlags::empty()) + .dst_stage_mask(vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT) + .dst_access_mask(vk::AccessFlags::COLOR_ATTACHMENT_WRITE); + + let attachments = &[color_attachment]; + let subpasses = &[subpass]; + let dependencies = &[dependency]; + let info = vk::RenderPassCreateInfo::builder() + .attachments(attachments) + .subpasses(subpasses) + .dependencies(dependencies); + + data.render_pass = device.create_render_pass(&info, None)?; + + Ok(()) +} + +unsafe fn create_descriptor_set_layout(device:&Device, data:&mut AppData) -> Result<()> { + let ubo_binding = vk::DescriptorSetLayoutBinding::builder() + .binding(0) + .descriptor_type(vk::DescriptorType::UNIFORM_BUFFER) + .descriptor_count(1) + .stage_flags(vk::ShaderStageFlags::VERTEX); + + let bindings = &[ubo_binding]; + let info = vk::DescriptorSetLayoutCreateInfo::builder().bindings(bindings); + + data.descriptor_set_layout = device.create_descriptor_set_layout(&info, None)?; + + Ok(()) +} + +unsafe fn create_pipeline(device:&Device, data:&mut AppData) -> Result<()> { + const_shaders! { + frag = "f_default.spv"; + vert = "v_default.spv"; + } + + let vert_shader_module = create_shader_module(device, &vert[..])?; + let frag_shader_module = create_shader_module(device, &frag[..])?; + + let vert_stage = vk::PipelineShaderStageCreateInfo::builder() + .stage(vk::ShaderStageFlags::VERTEX) + .module(vert_shader_module) + .name(b"main\0"); // keep specialization_info in mind for pipeline creation optimizations that dont happen at render time + + let frag_stage = vk::PipelineShaderStageCreateInfo::builder() + .stage(vk::ShaderStageFlags::FRAGMENT) + .module(frag_shader_module) + .name(b"main\0"); + + let binding_descriptions = &[Vertex::binding_description()]; + let attribute_descriptions = Vertex::attribute_descriptions(); + let vertex_input_state = vk::PipelineVertexInputStateCreateInfo::builder() + .vertex_binding_descriptions(binding_descriptions) + .vertex_attribute_descriptions(&attribute_descriptions); + + let input_assembly_state = vk::PipelineInputAssemblyStateCreateInfo::builder() + .topology(vk::PrimitiveTopology::TRIANGLE_LIST) + .primitive_restart_enable(false); + + let viewport = vk::Viewport::builder() + .x(0.0) + .y(0.0) + .width(data.swapchain_extent.width as f32) + .height(data.swapchain_extent.height as f32) + .min_depth(0.0) + .max_depth(1.0); + + let scissor = vk::Rect2D::builder().offset(vk::Offset2D { x:0, y:0 }).extent(data.swapchain_extent); + + let viewports = &[viewport]; + let scissors = &[scissor]; + let viewport_state = vk::PipelineViewportStateCreateInfo::builder().viewports(viewports).scissors(scissors); + + let rasterization_state = vk::PipelineRasterizationStateCreateInfo::builder() + .depth_clamp_enable(false) + .rasterizer_discard_enable(false) + .polygon_mode(vk::PolygonMode::FILL) + .line_width(1.0) + .cull_mode(vk::CullModeFlags::BACK) + .front_face(vk::FrontFace::COUNTER_CLOCKWISE) + .depth_bias_enable(false); + + let multisample_state = vk::PipelineMultisampleStateCreateInfo::builder() + .sample_shading_enable(false) + .rasterization_samples(vk::SampleCountFlags::_1); + + let attachment = vk::PipelineColorBlendAttachmentState::builder() + .color_write_mask(vk::ColorComponentFlags::all()) + .blend_enable(false); + // .blend_enable(true) + // .src_color_blend_factor(vk::BlendFactor::SRC_ALPHA) + // .dst_color_blend_factor(vk::BlendFactor::ONE_MINUS_SRC_ALPHA) + // .color_blend_op(vk::BlendOp::ADD) + // .src_alpha_blend_factor(vk::BlendFactor::ONE) + // .dst_alpha_blend_factor(vk::BlendFactor::ZERO) + // .alpha_blend_op(vk::BlendOp::ADD); + + let attachments = &[attachment]; + let color_blend_state = vk::PipelineColorBlendStateCreateInfo::builder() + .logic_op_enable(false) + .logic_op(vk::LogicOp::COPY) + .attachments(attachments) + .blend_constants([0.0, 0.0, 0.0, 0.0]); + + let set_layouts = &[data.descriptor_set_layout]; + let layout_info = vk::PipelineLayoutCreateInfo::builder().set_layouts(set_layouts); + + data.pipeline_layout = device.create_pipeline_layout(&layout_info, None)?; + + let stages = &[vert_stage, frag_stage]; + let info = vk::GraphicsPipelineCreateInfo::builder() + .stages(stages) + .vertex_input_state(&vertex_input_state) + .input_assembly_state(&input_assembly_state) + .viewport_state(&viewport_state) + .rasterization_state(&rasterization_state) + .multisample_state(&multisample_state) + .color_blend_state(&color_blend_state) + .layout(data.pipeline_layout) + .render_pass(data.render_pass) + .subpass(0) + .base_pipeline_handle(vk::Pipeline::null()) // Optional. + .base_pipeline_index(-1); // Optional. + + data.pipeline = device.create_graphics_pipelines(vk::PipelineCache::null(), &[info], None)?.0[0]; + + device.destroy_shader_module(vert_shader_module, None); + device.destroy_shader_module(frag_shader_module, None); + Ok(()) +} + +unsafe fn create_shader_module(device:&Device, bytecode:&[u8]) -> Result { + let bytecode = Bytecode::new(bytecode).unwrap(); + + let info = vk::ShaderModuleCreateInfo::builder().code_size(bytecode.code_size()).code(bytecode.code()); + + Ok(device.create_shader_module(&info, None)?) +} + +unsafe fn create_framebuffers(device:&Device, data:&mut AppData) -> Result<()> { + data.framebuffers = data + .swapchain_image_views + .iter() + .map(|i| { + let attachments = &[*i]; + let create_info = vk::FramebufferCreateInfo::builder() + .render_pass(data.render_pass) + .attachments(attachments) + .width(data.swapchain_extent.width) + .height(data.swapchain_extent.height) + .layers(1); + + device.create_framebuffer(&create_info, None) + }) + .collect::, _>>()?; + + Ok(()) +} +unsafe fn create_command_pool(instance:&Instance, device:&Device, data:&mut AppData) -> Result<()> { + let indices = QueueFamilyIndices::get(instance, data, data.physical_device)?; + + let info = vk::CommandPoolCreateInfo::builder() + .flags(vk::CommandPoolCreateFlags::empty()) // Optional. + .queue_family_index(indices.graphics); + + data.command_pool = device.create_command_pool(&info, None)?; + + Ok(()) +} + +unsafe fn create_texture_image(instance:&Instance, device:&Device, data:&mut AppData) -> Result<()> { + let image = File::open("assets/common/archlogo.512.png")?; + + let decoder = png::Decoder::new(image); + let mut reader = decoder.read_info()?; + + let mut pixels = vec![0; reader.info().raw_bytes()]; + reader.next_frame(&mut pixels)?; // what format is this reading to? + + let size = reader.info().raw_bytes() as u64; + let (width, height) = reader.info().size(); + + let (staging_buffer, staging_buffer_memory) = create_buffer( + instance, + device, + data, + size, + vk::BufferUsageFlags::TRANSFER_SRC, + vk::MemoryPropertyFlags::HOST_COHERENT | vk::MemoryPropertyFlags::HOST_VISIBLE, + )?; + + let memory = device.map_memory(staging_buffer_memory, 0, size, vk::MemoryMapFlags::empty())?; + + memcpy(pixels.as_ptr(), memory.cast(), pixels.len()); + + device.unmap_memory(staging_buffer_memory); + + let (texture_image, texture_image_memory) = create_image( + instance, + device, + data, + width, + height, + vk::Format::R8G8B8A8_SRGB, // BCn compression can be used here, its also possible that these formats are not available everywhere and this must be handled separately + vk::ImageTiling::OPTIMAL, // can be linear if we are planning to read from it on the cpu later? + vk::ImageUsageFlags::SAMPLED | vk::ImageUsageFlags::TRANSFER_DST, + vk::MemoryPropertyFlags::DEVICE_LOCAL, + )?; + + data.texture_image = texture_image; + data.texture_image_memory = texture_image_memory; + + transition_image_layout( + device, + data, + data.texture_image, + vk::Format::R8G8B8A8_SRGB, + vk::ImageLayout::UNDEFINED, + vk::ImageLayout::TRANSFER_DST_OPTIMAL, + )?; + + copy_buffer_to_image(device, data, staging_buffer, data.texture_image, width, height)?; + + transition_image_layout( + device, + data, + data.texture_image, + vk::Format::R8G8B8A8_SRGB, + vk::ImageLayout::TRANSFER_DST_OPTIMAL, + vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL, + )?; + device.destroy_buffer(staging_buffer, None); + device.free_memory(staging_buffer_memory, None); + + Ok(()) +} +unsafe fn create_command_buffers(device:&Device, data:&mut AppData) -> Result<()> { + let allocate_info = vk::CommandBufferAllocateInfo::builder() + .command_pool(data.command_pool) + .level(vk::CommandBufferLevel::PRIMARY) + .command_buffer_count(data.framebuffers.len() as u32); + + data.command_buffers = device.allocate_command_buffers(&allocate_info)?; + + for (i, command_buffer) in data.command_buffers.iter().enumerate() { + let inheritance = vk::CommandBufferInheritanceInfo::builder(); + + let info = vk::CommandBufferBeginInfo::builder() + .flags(vk::CommandBufferUsageFlags::empty()) // Optional. + .inheritance_info(&inheritance); // Optional. + + device.begin_command_buffer(*command_buffer, &info)?; + + let render_area = vk::Rect2D::builder().offset(vk::Offset2D::default()).extent(data.swapchain_extent); + + let color_clear_value = vk::ClearValue { + color:vk::ClearColorValue { + float32:[0.0125, 0.0094, 0.0071, 1.0], + }, + }; + + let clear_values = &[color_clear_value]; + let info = vk::RenderPassBeginInfo::builder() + .render_pass(data.render_pass) + .framebuffer(data.framebuffers[i]) + .render_area(render_area) + .clear_values(clear_values); + + device.cmd_begin_render_pass(*command_buffer, &info, vk::SubpassContents::INLINE); + device.cmd_bind_pipeline(*command_buffer, vk::PipelineBindPoint::GRAPHICS, data.pipeline); + + device.cmd_bind_vertex_buffers(*command_buffer, 0, &[data.vertex_buffer], &[0]); + device.cmd_bind_index_buffer(*command_buffer, data.index_buffer, 0, vk::IndexType::UINT16); + device.cmd_bind_descriptor_sets( + *command_buffer, + vk::PipelineBindPoint::GRAPHICS, + data.pipeline_layout, + 0, + &[data.descriptor_sets[i]], + &[], + ); + + device.cmd_draw_indexed(*command_buffer, INDICES.len() as u32, 1, 0, 0, 0); + + device.cmd_end_render_pass(*command_buffer); + device.end_command_buffer(*command_buffer)?; + } + + Ok(()) +} + +unsafe fn create_sync_objects(device:&Device, data:&mut AppData) -> Result<()> { + let semaphore_info = vk::SemaphoreCreateInfo::builder(); + let fence_info = vk::FenceCreateInfo::builder().flags(vk::FenceCreateFlags::SIGNALED); + + for _ in 0..MAX_FRAMES_IN_FLIGHT { + data.image_available_semaphores.push(device.create_semaphore(&semaphore_info, None)?); + data.render_finished_semaphores.push(device.create_semaphore(&semaphore_info, None)?); + + data.in_flight_fences.push(device.create_fence(&fence_info, None)?); + } + + data.images_in_flight = data.swapchain_images.iter().map(|_| vk::Fence::null()).collect(); + + Ok(()) +} + +#[derive(Copy, Clone, Debug)] +struct QueueFamilyIndices { + graphics:u32, + present: u32, +} + +impl QueueFamilyIndices { + unsafe fn get(instance:&Instance, data:&AppData, physical_device:vk::PhysicalDevice) -> Result { + let properties = instance.get_physical_device_queue_family_properties(physical_device); + + let graphics = properties + .iter() + .position(|p| p.queue_flags.contains(vk::QueueFlags::GRAPHICS)) + .map(|i| i as u32); + + let mut present = None; + for (index, properties) in properties.iter().enumerate() { + if instance.get_physical_device_surface_support_khr(physical_device, index as u32, data.surface)? { + present = Some(index as u32); + break; + } + } + + // Note that it's very likely that these end up being the same queue family after all, but throughout the program we will treat them as if they were separate queues for a uniform approach. Nevertheless, you could add logic to explicitly prefer a physical device that supports drawing and presentation in the same queue for improved performance. + if let (Some(graphics), Some(present)) = (graphics, present) { + Ok(Self { graphics, present }) + } else { + Err(anyhow!(SuitabilityError("Missing required queue families."))) + } + } +} + +#[derive(Clone, Debug)] +struct SwapchainSupport { + capabilities: vk::SurfaceCapabilitiesKHR, + formats: Vec, + present_modes:Vec, +} + +impl SwapchainSupport { + unsafe fn get(instance:&Instance, data:&AppData, physical_device:vk::PhysicalDevice) -> Result { + Ok(Self { + capabilities: instance.get_physical_device_surface_capabilities_khr(physical_device, data.surface)?, + formats: instance.get_physical_device_surface_formats_khr(physical_device, data.surface)?, + present_modes:instance.get_physical_device_surface_present_modes_khr(physical_device, data.surface)?, + }) + } +} + +#[repr(C)] +#[derive(Copy, Clone, Debug)] +struct Vertex { + pos: Vec2f, + color:Vec3f, +} + +impl Vertex { + const fn new(pos:Vec2f, color:Vec3f) -> Self { Self { pos, color } } + + fn binding_description() -> vk::VertexInputBindingDescription { + vk::VertexInputBindingDescription::builder() + .binding(0) + .stride(size_of::() as u32) + .input_rate(vk::VertexInputRate::VERTEX) + .build() + } + + fn attribute_descriptions() -> [vk::VertexInputAttributeDescription; 2] { + let pos = vk::VertexInputAttributeDescription::builder() + .binding(0) + .location(0) + .format(vk::Format::R32G32_SFLOAT) + .offset(0) + .build(); + + let color = vk::VertexInputAttributeDescription::builder() + .binding(0) + .location(1) + .format(vk::Format::R32G32B32_SFLOAT) + .offset(size_of::() as u32) + .build(); + + [pos, color] + } +} + +unsafe fn create_vertex_buffer(instance:&Instance, device:&Device, data:&mut AppData) -> Result<()> { + let size = (size_of::() * VERTICES.len()) as u64; + + let (staging_buffer, staging_buffer_memory) = create_buffer( + instance, + device, + data, + size, + vk::BufferUsageFlags::TRANSFER_SRC, + vk::MemoryPropertyFlags::HOST_COHERENT | vk::MemoryPropertyFlags::HOST_VISIBLE, + )?; + + let memory = device.map_memory(staging_buffer_memory, 0, size, vk::MemoryMapFlags::empty())?; + + memcpy(VERTICES.as_ptr(), memory.cast(), VERTICES.len()); + + device.unmap_memory(staging_buffer_memory); + + let (vertex_buffer, vertex_buffer_memory) = create_buffer( + instance, + device, + data, + size, + vk::BufferUsageFlags::TRANSFER_DST | vk::BufferUsageFlags::VERTEX_BUFFER, + vk::MemoryPropertyFlags::DEVICE_LOCAL, + )?; + + data.vertex_buffer = vertex_buffer; + data.vertex_buffer_memory = vertex_buffer_memory; + + copy_buffer(device, data, staging_buffer, vertex_buffer, size)?; + + device.destroy_buffer(staging_buffer, None); + device.free_memory(staging_buffer_memory, None); + + Ok(()) +} + +unsafe fn copy_buffer(device:&Device, data:&AppData, source:vk::Buffer, destination:vk::Buffer, size:vk::DeviceSize) -> Result<()> { + let command_buffer = begin_single_time_commands(device, data)?; + + let regions = vk::BufferCopy::builder().size(size); + device.cmd_copy_buffer(command_buffer, source, destination, &[regions]); + + end_single_time_commands(device, data, command_buffer)?; + + Ok(()) +} + +unsafe fn get_memory_type_index(instance:&Instance, data:&AppData, properties:vk::MemoryPropertyFlags, requirements:vk::MemoryRequirements) -> Result { + let memory = instance.get_physical_device_memory_properties(data.physical_device); + + (0..memory.memory_type_count) + .find(|i| { + let suitable = (requirements.memory_type_bits & (1 << i)) != 0; + let memory_type = memory.memory_types[*i as usize]; + suitable && memory_type.property_flags.contains(properties) + }) + .ok_or_else(|| anyhow!("Failed to find suitable memory type.")) +} + +unsafe fn create_buffer( + instance:&Instance, device:&Device, data:&AppData, size:vk::DeviceSize, usage:vk::BufferUsageFlags, properties:vk::MemoryPropertyFlags, +) -> Result<(vk::Buffer, vk::DeviceMemory)> { + let buffer_info = vk::BufferCreateInfo::builder().size(size).usage(usage).sharing_mode(vk::SharingMode::EXCLUSIVE); + + let buffer = device.create_buffer(&buffer_info, None)?; + + let requirements = device.get_buffer_memory_requirements(buffer); + + let memory_info = vk::MemoryAllocateInfo::builder() + .allocation_size(requirements.size) + .memory_type_index(get_memory_type_index(instance, data, properties, requirements)?); + + let buffer_memory = device.allocate_memory(&memory_info, None)?; + + device.bind_buffer_memory(buffer, buffer_memory, 0)?; + + Ok((buffer, buffer_memory)) +} + +unsafe fn create_index_buffer(instance:&Instance, device:&Device, data:&mut AppData) -> Result<()> { + let size = (size_of::() * INDICES.len()) as u64; + + let (staging_buffer, staging_buffer_memory) = create_buffer( + instance, + device, + data, + size, + vk::BufferUsageFlags::TRANSFER_SRC, + vk::MemoryPropertyFlags::HOST_COHERENT | vk::MemoryPropertyFlags::HOST_VISIBLE, + )?; + + let memory = device.map_memory(staging_buffer_memory, 0, size, vk::MemoryMapFlags::empty())?; + + memcpy(INDICES.as_ptr(), memory.cast(), INDICES.len()); + + device.unmap_memory(staging_buffer_memory); + + let (index_buffer, index_buffer_memory) = create_buffer( + instance, + device, + data, + size, + vk::BufferUsageFlags::TRANSFER_DST | vk::BufferUsageFlags::INDEX_BUFFER, + vk::MemoryPropertyFlags::DEVICE_LOCAL, + )?; + + data.index_buffer = index_buffer; + data.index_buffer_memory = index_buffer_memory; + + copy_buffer(device, data, staging_buffer, index_buffer, size)?; + + device.destroy_buffer(staging_buffer, None); + device.free_memory(staging_buffer_memory, None); + + Ok(()) +} + +#[repr(C)] +#[derive(Copy, Clone, Debug)] +struct UniformBufferObject { + model:Mat4f, + view: Mat4f, + proj: Mat4f, +} + +unsafe fn create_uniform_buffers(instance:&Instance, device:&Device, data:&mut AppData) -> Result<()> { + data.uniform_buffers.clear(); + data.uniform_buffers_memory.clear(); + + for _ in 0..data.swapchain_images.len() { + let (uniform_buffer, uniform_buffer_memory) = create_buffer( + instance, + device, + data, + size_of::() as u64, + vk::BufferUsageFlags::UNIFORM_BUFFER, + vk::MemoryPropertyFlags::HOST_COHERENT | vk::MemoryPropertyFlags::HOST_VISIBLE, + )?; + + data.uniform_buffers.push(uniform_buffer); + data.uniform_buffers_memory.push(uniform_buffer_memory); + } + + Ok(()) +} + +unsafe fn create_descriptor_pool(device:&Device, data:&mut AppData) -> Result<()> { + let ubo_size = vk::DescriptorPoolSize::builder() + .type_(vk::DescriptorType::UNIFORM_BUFFER) + .descriptor_count(data.swapchain_images.len() as u32); + + let pool_sizes = &[ubo_size]; + let info = vk::DescriptorPoolCreateInfo::builder() + .pool_sizes(pool_sizes) + .max_sets(data.swapchain_images.len() as u32); + + data.descriptor_pool = device.create_descriptor_pool(&info, None)?; + + Ok(()) +} + +unsafe fn create_descriptor_sets(device:&Device, data:&mut AppData) -> Result<()> { + let layouts = vec![data.descriptor_set_layout; data.swapchain_images.len()]; + let info = vk::DescriptorSetAllocateInfo::builder() + .descriptor_pool(data.descriptor_pool) + .set_layouts(&layouts); + + data.descriptor_sets = device.allocate_descriptor_sets(&info)?; + + for i in 0..data.swapchain_images.len() { + let info = vk::DescriptorBufferInfo::builder() + .buffer(data.uniform_buffers[i]) + .offset(0) + .range(size_of::() as u64); + + let buffer_info = &[info]; + let ubo_write = vk::WriteDescriptorSet::builder() + .dst_set(data.descriptor_sets[i]) + .dst_binding(0) + .dst_array_element(0) + .descriptor_type(vk::DescriptorType::UNIFORM_BUFFER) + .buffer_info(buffer_info); + + device.update_descriptor_sets(&[ubo_write], &[] as &[vk::CopyDescriptorSet]); + } + + Ok(()) +} + +unsafe fn create_image( + instance:&Instance, device:&Device, data:&AppData, width:u32, height:u32, format:vk::Format, tiling:vk::ImageTiling, usage:vk::ImageUsageFlags, + properties:vk::MemoryPropertyFlags, +) -> Result<(vk::Image, vk::DeviceMemory)> { + let info = vk::ImageCreateInfo::builder() + .image_type(vk::ImageType::_2D) + .extent(vk::Extent3D { width, height, depth:1 }) + .mip_levels(1) + .array_layers(1) + .format(format) + .tiling(tiling) + .initial_layout(vk::ImageLayout::UNDEFINED) + .usage(usage) + .samples(vk::SampleCountFlags::_1) + .sharing_mode(vk::SharingMode::EXCLUSIVE); + + let image = device.create_image(&info, None)?; + + let requirements = device.get_image_memory_requirements(image); + + let info = vk::MemoryAllocateInfo::builder() + .allocation_size(requirements.size) + .memory_type_index(get_memory_type_index(instance, data, properties, requirements)?); + + let image_memory = device.allocate_memory(&info, None)?; + + device.bind_image_memory(image, image_memory, 0)?; + + Ok((image, image_memory)) +} + +unsafe fn begin_single_time_commands(device:&Device, data:&AppData) -> Result { + let info = vk::CommandBufferAllocateInfo::builder() + .level(vk::CommandBufferLevel::PRIMARY) + .command_pool(data.command_pool) + .command_buffer_count(1); + + let command_buffer = device.allocate_command_buffers(&info)?[0]; + + let info = vk::CommandBufferBeginInfo::builder().flags(vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT); + + device.begin_command_buffer(command_buffer, &info)?; + + Ok(command_buffer) +} + +unsafe fn end_single_time_commands(device:&Device, data:&AppData, command_buffer:vk::CommandBuffer) -> Result<()> { + device.end_command_buffer(command_buffer)?; + + let command_buffers = &[command_buffer]; + let info = vk::SubmitInfo::builder().command_buffers(command_buffers); + + device.queue_submit(data.graphics_queue, &[info], vk::Fence::null())?; + device.queue_wait_idle(data.graphics_queue)?; + + device.free_command_buffers(data.command_pool, &[command_buffer]); + + Ok(()) +} + +unsafe fn transition_image_layout( + device:&Device, data:&AppData, image:vk::Image, format:vk::Format, old_layout:vk::ImageLayout, new_layout:vk::ImageLayout, +) -> Result<()> { + let command_buffer = begin_single_time_commands(device, data)?; + + let subresource = vk::ImageSubresourceRange::builder() + .aspect_mask(vk::ImageAspectFlags::COLOR) + .base_mip_level(0) + .level_count(1) + .base_array_layer(0) + .layer_count(1); + + let (src_access_mask, dst_access_mask, src_stage_mask, dst_stage_mask) = match (old_layout, new_layout) { + (vk::ImageLayout::UNDEFINED, vk::ImageLayout::TRANSFER_DST_OPTIMAL) => ( + vk::AccessFlags::empty(), + vk::AccessFlags::TRANSFER_WRITE, + vk::PipelineStageFlags::TOP_OF_PIPE, + vk::PipelineStageFlags::TRANSFER, + ), + (vk::ImageLayout::TRANSFER_DST_OPTIMAL, vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL) => ( + vk::AccessFlags::TRANSFER_WRITE, + vk::AccessFlags::SHADER_READ, + vk::PipelineStageFlags::TRANSFER, + vk::PipelineStageFlags::FRAGMENT_SHADER, + ), + _ => return Err(anyhow!("Unsupported image layout transition!")), + }; + + let barrier = vk::ImageMemoryBarrier::builder() + .old_layout(old_layout) + .new_layout(new_layout) + .src_queue_family_index(vk::QUEUE_FAMILY_IGNORED) + .dst_queue_family_index(vk::QUEUE_FAMILY_IGNORED) + .image(image) + .subresource_range(subresource) + .src_access_mask(src_access_mask) + .dst_access_mask(dst_access_mask); + + device.cmd_pipeline_barrier( + command_buffer, + src_stage_mask, + dst_stage_mask, + vk::DependencyFlags::empty(), + &[] as &[vk::MemoryBarrier], + &[] as &[vk::BufferMemoryBarrier], + &[barrier], + ); + + end_single_time_commands(device, data, command_buffer)?; + + Ok(()) +} + +unsafe fn copy_buffer_to_image(device:&Device, data:&AppData, buffer:vk::Buffer, image:vk::Image, width:u32, height:u32) -> Result<()> { + let command_buffer = begin_single_time_commands(device, data)?; + + let subresource = vk::ImageSubresourceLayers::builder() + .aspect_mask(vk::ImageAspectFlags::COLOR) + .mip_level(0) + .base_array_layer(0) + .layer_count(1); + + let region = vk::BufferImageCopy::builder() + .buffer_offset(0) + .buffer_row_length(0) + .buffer_image_height(0) + .image_subresource(subresource) + .image_offset(vk::Offset3D { x:0, y:0, z:0 }) + .image_extent(vk::Extent3D { width, height, depth:1 }); + + device.cmd_copy_buffer_to_image(command_buffer, buffer, image, vk::ImageLayout::TRANSFER_DST_OPTIMAL, &[region]); + + end_single_time_commands(device, data, command_buffer)?; + + Ok(()) +} + +unsafe fn create_texture_image_view(device:&Device, data:&mut AppData) -> Result<()> { + data.texture_image_view = create_image_view(device, data.texture_image, vk::Format::R8G8B8A8_SRGB)?; + + Ok(()) +} + +unsafe fn create_image_view(device:&Device, image:vk::Image, format:vk::Format) -> Result { + let subresource_range = vk::ImageSubresourceRange::builder() + .aspect_mask(vk::ImageAspectFlags::COLOR) + .base_mip_level(0) + .level_count(1) + .base_array_layer(0) + .layer_count(1); + + let info = vk::ImageViewCreateInfo::builder() + .image(image) + .view_type(vk::ImageViewType::_2D) + .format(format) + .subresource_range(subresource_range); + + Ok(device.create_image_view(&info, None)?) +} + +unsafe fn create_texture_sampler(device:&Device, data:&mut AppData) -> Result<()> { Ok(()) } diff --git a/src/graphics_engines/webgpu/mod.rs b/src/graphics_engines/webgpu/mod.rs new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/graphics_engines/webgpu/mod.rs @@ -0,0 +1 @@ + diff --git a/src/linux.rs b/src/linux.rs index d34f554..be6d4d8 100644 --- a/src/linux.rs +++ b/src/linux.rs @@ -1,87 +1,21 @@ use anyhow::Result; -use anyhow::anyhow; - -use cgmath::vec2; -use cgmath::vec3; use log::*; -use std::collections::HashSet; -use std::ffi::CStr; -use std::mem::size_of; -use std::os::raw::c_void; - -use thiserror::Error; - -use vulkanalia::Version; -use vulkanalia::bytecode::Bytecode; -use vulkanalia::loader::LIBRARY; -use vulkanalia::loader::LibloadingLoader; use vulkanalia::prelude::v1_0::*; -use vulkanalia::window as vk_window; - -use vulkanalia::vk::ExtDebugUtilsExtension; -use vulkanalia::vk::KhrSurfaceExtension; -use vulkanalia::vk::KhrSwapchainExtension; use winit::dpi::LogicalSize; use winit::event::Event; use winit::event::WindowEvent; use winit::event_loop::EventLoop; -use winit::window::Window; use winit::window::WindowBuilder; -type Vec2f = cgmath::Vector2; -type Vec3f = cgmath::Vector3; -type Vec4f = cgmath::Vector4; - -type Vec2d = cgmath::Vector2; -type Vec3d = cgmath::Vector3; -type Vec4d = cgmath::Vector4; - -type Mat2f = cgmath::Matrix2; -type Mat3f = cgmath::Matrix3; -type Mat4f = cgmath::Matrix4; - -type Mat2d = cgmath::Matrix2; -type Mat3d = cgmath::Matrix3; -type Mat4d = cgmath::Matrix4; - -const VALIDATION_ENABLED:bool = cfg!(debug_assertions); // add env support? -const VALIDATION_LAYER:vk::ExtensionName = vk::ExtensionName::from_bytes(b"VK_LAYER_KHRONOS_validation"); - -const DEVICE_EXTENSIONS:&[vk::ExtensionName] = &[vk::KHR_SWAPCHAIN_EXTENSION.name]; -const PORTABILITY_MACOS_VERSION:Version = Version::new(1, 3, 216); +use crate::graphics_engines::vulkan::App; const WINDOW_TITLE:&'static str = "MineMod"; -const VK_APPLICATION_NAME:&'static [u8; 8] = b"minemod\0"; -const VK_ENGINE_NAME:&'static [u8; 10] = b"cataclysm\0"; -const MAX_FRAMES_IN_FLIGHT:usize = 2; - -macro_rules! const_shaders { - { $($vis:vis $identifier:ident = $string:literal; )* } => { - $( - #[allow(non_upper_case_globals)] - $vis static $identifier: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/shaders/", $string)); - )* - }; -} pub fn main() -> Result<()> { - tracing_subscriber::fmt() - .compact() - .with_timer(tracing_subscriber::fmt::time::uptime()) - .with_ansi(true) - .with_level(true) - // .with_thread_ids(true) - .with_thread_names(true) - .with_max_level(if cfg!(debug_assertions) { - tracing::level_filters::LevelFilter::DEBUG - } else { - tracing::level_filters::LevelFilter::INFO - }) - .init(); - // pretty_env_logger::init_timed(); + super::init_logging(); info!("Registering CTRLC hook."); @@ -160,781 +94,3 @@ pub fn main() -> Result<()> { Ok(()) } - -/// Our Vulkan app. -#[derive(Clone, Debug)] -struct App { - entry: Entry, - instance:Instance, - data: AppData, - device: Device, - frame: usize, // current frame - resized: bool, -} - -impl App { - /// Creates our Vulkan app. - unsafe fn create(window:&Window) -> Result { - let loader = LibloadingLoader::new(LIBRARY)?; - let entry = Entry::new(loader).map_err(|b| anyhow!("{}", b))?; - let mut data = AppData::default(); - let instance = create_instance(window, &entry, &mut data)?; - data.surface = vk_window::create_surface(&instance, &window, &window)?; - pick_physical_device(&instance, &mut data)?; - let device = create_logical_device(&entry, &instance, &mut data)?; - create_swapchain(window, &instance, &device, &mut data)?; - create_swapchain_image_views(&device, &mut data)?; - create_render_pass(&instance, &device, &mut data)?; - create_pipeline(&device, &mut data)?; - create_framebuffers(&device, &mut data)?; - create_command_pool(&instance, &device, &mut data)?; - create_command_buffers(&device, &mut data)?; - create_sync_objects(&device, &mut data)?; - Ok(Self { - entry, - instance, - data, - device, - frame:0, - resized:false, - }) - } - - /// Renders a frame for our Vulkan app. - unsafe fn render(&mut self, window:&Window) -> Result<()> { - self.device.wait_for_fences(&[self.data.in_flight_fences[self.frame]], true, u64::MAX)?; - - let result = self.device.acquire_next_image_khr( - self.data.swapchain, - u64::MAX, - self.data.image_available_semaphores[self.frame], - vk::Fence::null(), - ); - - let image_index = match result { - Ok((image_index, _)) => image_index as usize, - Err(vk::ErrorCode::OUT_OF_DATE_KHR) => return self.recreate_swapchain(window), - Err(e) => return Err(anyhow!(e)), - }; - - if !self.data.images_in_flight[image_index as usize].is_null() { - self.device - .wait_for_fences(&[self.data.images_in_flight[image_index as usize]], true, u64::MAX)?; - } - - self.data.images_in_flight[image_index as usize] = self.data.in_flight_fences[self.frame]; - - let wait_semaphores = &[self.data.image_available_semaphores[self.frame]]; - let wait_stages = &[vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT]; - let command_buffers = &[self.data.command_buffers[image_index as usize]]; - let signal_semaphores = &[self.data.render_finished_semaphores[self.frame]]; - let submit_info = vk::SubmitInfo::builder() - .wait_semaphores(wait_semaphores) - .wait_dst_stage_mask(wait_stages) - .command_buffers(command_buffers) - .signal_semaphores(signal_semaphores); - - self.device.reset_fences(&[self.data.in_flight_fences[self.frame]])?; - - self.device - .queue_submit(self.data.graphics_queue, &[submit_info], self.data.in_flight_fences[self.frame])?; - - let swapchains = &[self.data.swapchain]; - let image_indices = &[image_index as u32]; - let present_info = vk::PresentInfoKHR::builder() - .wait_semaphores(signal_semaphores) - .swapchains(swapchains) - .image_indices(image_indices); - - let result = self.device.queue_present_khr(self.data.present_queue, &present_info); - - let changed = result == Ok(vk::SuccessCode::SUBOPTIMAL_KHR) || result == Err(vk::ErrorCode::OUT_OF_DATE_KHR); - - if self.resized || changed { - self.resized = false; - self.recreate_swapchain(window)?; - } else if let Err(e) = result { - return Err(anyhow!(e)); - } - - // self.device.queue_wait_idle(self.data.present_queue)?; - - self.frame = (self.frame + 1) % MAX_FRAMES_IN_FLIGHT; - - Ok(()) - } - - unsafe fn recreate_swapchain(&mut self, window:&Window) -> Result<()> { - self.device.device_wait_idle()?; - self.destroy_swapchain(); - create_swapchain(window, &self.instance, &self.device, &mut self.data)?; - create_swapchain_image_views(&self.device, &mut self.data)?; - create_render_pass(&self.instance, &self.device, &mut self.data)?; - create_pipeline(&self.device, &mut self.data)?; - create_framebuffers(&self.device, &mut self.data)?; - create_command_buffers(&self.device, &mut self.data)?; - self.data.images_in_flight.resize(self.data.swapchain_images.len(), vk::Fence::null()); - - Ok(()) - } - - unsafe fn destroy_swapchain(&mut self) { - self.data.framebuffers.iter().for_each(|f| self.device.destroy_framebuffer(*f, None)); - self.device.free_command_buffers(self.data.command_pool, &self.data.command_buffers); - self.device.destroy_pipeline(self.data.pipeline, None); - self.device.destroy_pipeline_layout(self.data.pipeline_layout, None); - self.device.destroy_render_pass(self.data.render_pass, None); - self.data.swapchain_image_views.iter().for_each(|v| self.device.destroy_image_view(*v, None)); - self.device.destroy_swapchain_khr(self.data.swapchain, None); - } - - /// Destroys our Vulkan app. - unsafe fn destroy(&mut self) { - self.destroy_swapchain(); - - self.data.in_flight_fences.iter().for_each(|f| self.device.destroy_fence(*f, None)); - self.data - .render_finished_semaphores - .iter() - .for_each(|s| self.device.destroy_semaphore(*s, None)); - self.data - .image_available_semaphores - .iter() - .for_each(|s| self.device.destroy_semaphore(*s, None)); - - self.device.destroy_command_pool(self.data.command_pool, None); - self.device.destroy_device(None); - self.instance.destroy_surface_khr(self.data.surface, None); - - if VALIDATION_ENABLED { - self.instance.destroy_debug_utils_messenger_ext(self.data.messenger, None); - } - - self.instance.destroy_instance(None); - } -} - -/// The Vulkan handles and associated properties used by our Vulkan app. -#[derive(Clone, Debug, Default)] -struct AppData { - messenger: vk::DebugUtilsMessengerEXT, - surface: vk::SurfaceKHR, - physical_device: vk::PhysicalDevice, - graphics_queue: vk::Queue, - present_queue: vk::Queue, - swapchain_format: vk::Format, - swapchain_extent: vk::Extent2D, - swapchain: vk::SwapchainKHR, - swapchain_images: Vec, - swapchain_image_views: Vec, - render_pass: vk::RenderPass, - pipeline_layout: vk::PipelineLayout, - pipeline: vk::Pipeline, - framebuffers: Vec, - command_pool: vk::CommandPool, - command_buffers: Vec, - image_available_semaphores:Vec, - render_finished_semaphores:Vec, - in_flight_fences: Vec, - images_in_flight: Vec, -} - -unsafe fn create_instance(window:&Window, entry:&Entry, data:&mut AppData) -> Result { - let application_info = vk::ApplicationInfo::builder() - .application_name(VK_APPLICATION_NAME) - .application_version(vk::make_version(0, 1, 0)) - .engine_name(VK_ENGINE_NAME) - .engine_version(vk::make_version(0, 1, 0)) - .api_version(vk::make_version(1, 0, 0)); - - let available_layers = entry - .enumerate_instance_layer_properties()? - .iter() - .map(|l| l.layer_name) - .collect::>(); - - if VALIDATION_ENABLED && !available_layers.contains(&VALIDATION_LAYER) { - return Err(anyhow!("Validation layer requested but not supported.")); - } - - let layers = if VALIDATION_ENABLED { vec![VALIDATION_LAYER.as_ptr()] } else { Vec::new() }; - - let mut extensions = vk_window::get_required_instance_extensions(window) - .iter() - .map(|e| e.as_ptr()) - .collect::>(); - - // needed for semaphores, unix only? - extensions.push(vk::KHR_GET_PHYSICAL_DEVICE_PROPERTIES2_EXTENSION.name.as_ptr()); - - // Required by Vulkan SDK on macOS since 1.3.216. - let flags = if cfg!(target_os = "macos") && entry.version()? >= PORTABILITY_MACOS_VERSION { - info!("Enabling extensions for macOS portability."); - // extensions.push( // already present above - // vk::KHR_GET_PHYSICAL_DEVICE_PROPERTIES2_EXTENSION - // .name - // .as_ptr(), - // ); - extensions.push(vk::KHR_PORTABILITY_ENUMERATION_EXTENSION.name.as_ptr()); - vk::InstanceCreateFlags::ENUMERATE_PORTABILITY_KHR - } else { - vk::InstanceCreateFlags::empty() - }; - if VALIDATION_ENABLED { - extensions.push(vk::EXT_DEBUG_UTILS_EXTENSION.name.as_ptr()); - } - - let mut info = vk::InstanceCreateInfo::builder() - .application_info(&application_info) - .enabled_layer_names(&layers) - .enabled_extension_names(&extensions) - .flags(flags); - - let mut debug_info = vk::DebugUtilsMessengerCreateInfoEXT::builder() - .message_severity(vk::DebugUtilsMessageSeverityFlagsEXT::all()) - .message_type( - vk::DebugUtilsMessageTypeFlagsEXT::GENERAL | vk::DebugUtilsMessageTypeFlagsEXT::VALIDATION | vk::DebugUtilsMessageTypeFlagsEXT::PERFORMANCE, - ) - .user_callback(Some(debug_callback)); - - if VALIDATION_ENABLED { - info = info.push_next(&mut debug_info); - } - - let instance = entry.create_instance(&info, None)?; - - if VALIDATION_ENABLED { - data.messenger = instance.create_debug_utils_messenger_ext(&debug_info, None)?; - } - - Ok(instance) -} - -extern "system" fn debug_callback( - severity:vk::DebugUtilsMessageSeverityFlagsEXT, type_:vk::DebugUtilsMessageTypeFlagsEXT, data:*const vk::DebugUtilsMessengerCallbackDataEXT, _:*mut c_void, -) -> vk::Bool32 { - let data = unsafe { *data }; - let message = unsafe { CStr::from_ptr(data.message) }.to_string_lossy(); - - if severity >= vk::DebugUtilsMessageSeverityFlagsEXT::ERROR { - error!("({:?}) {}", type_, message); - } else if severity >= vk::DebugUtilsMessageSeverityFlagsEXT::WARNING { - warn!("({:?}) {}", type_, message); - } else if severity >= vk::DebugUtilsMessageSeverityFlagsEXT::INFO { - debug!("({:?}) {}", type_, message); - } else { - trace!("({:?}) {}", type_, message); - } - - vk::FALSE -} - -#[derive(Debug, Error)] -#[error("{0}")] -pub struct SuitabilityError(pub &'static str); - -unsafe fn pick_physical_device(instance:&Instance, data:&mut AppData) -> Result<()> { - for physical_device in instance.enumerate_physical_devices()? { - let properties = instance.get_physical_device_properties(physical_device); - - if let Err(error) = check_physical_device(instance, data, physical_device) { - warn!("Skipping physical device (`{}`): {}", properties.device_name, error); - } else { - info!("Selected physical device (`{}`).", properties.device_name); - data.physical_device = physical_device; - return Ok(()); - } - } - - Err(anyhow!("Failed to find suitable physical device.")) -} -unsafe fn check_physical_device(instance:&Instance, data:&AppData, physical_device:vk::PhysicalDevice) -> Result<()> { - QueueFamilyIndices::get(instance, data, physical_device)?; - check_physical_device_extensions(instance, physical_device)?; - - let support = SwapchainSupport::get(instance, data, physical_device)?; - if support.formats.is_empty() || support.present_modes.is_empty() { - return Err(anyhow!(SuitabilityError("Insufficient swapchain support."))); - } - - // // TODO handle this like the other one? - // let properties = instance.get_physical_device_properties(physical_device); - // if properties.device_type != vk::PhysicalDeviceType::DISCRETE_GPU { - // return Err(anyhow!(SuitabilityError( - // "Only discrete GPUs are supported." - // ))); - // } - - // let features = instance.get_physical_device_features(physical_device); - // let required_features = [ - // (features.geometry_shader, "Missing geometry shader support."), - // // ( // needed for vr - // // features.multi_viewport, - // // "Missing support for multiple viewports.", - // // ), - // ]; - - // for (feature, string) in required_features { - // if feature != vk::TRUE { - // return Err(anyhow!(SuitabilityError(string))); - // } - // } - - Ok(()) -} -unsafe fn check_physical_device_extensions(instance:&Instance, physical_device:vk::PhysicalDevice) -> Result<()> { - let extensions = instance - .enumerate_device_extension_properties(physical_device, None)? - .iter() - .map(|e| e.extension_name) - .collect::>(); - if DEVICE_EXTENSIONS.iter().all(|e| extensions.contains(e)) { - Ok(()) - } else { - Err(anyhow!(SuitabilityError("Missing required device extensions."))) - } -} - -unsafe fn create_logical_device(entry:&Entry, instance:&Instance, data:&mut AppData) -> Result { - let indices = QueueFamilyIndices::get(instance, data, data.physical_device)?; - - let mut unique_indices = HashSet::new(); - unique_indices.insert(indices.graphics); - unique_indices.insert(indices.present); - - let queue_priorities = &[1.0]; - let queue_infos = unique_indices - .iter() - .map(|i| vk::DeviceQueueCreateInfo::builder().queue_family_index(*i).queue_priorities(queue_priorities)) - .collect::>(); - - let layers = if VALIDATION_ENABLED { vec![VALIDATION_LAYER.as_ptr()] } else { vec![] }; - - let mut extensions = DEVICE_EXTENSIONS.iter().map(|n| n.as_ptr()).collect::>(); - - // Required by Vulkan SDK on macOS since 1.3.216. - if cfg!(target_os = "macos") && entry.version()? >= PORTABILITY_MACOS_VERSION { - extensions.push(vk::KHR_PORTABILITY_SUBSET_EXTENSION.name.as_ptr()); - } - - let features = vk::PhysicalDeviceFeatures::builder(); - - let info = vk::DeviceCreateInfo::builder() - .queue_create_infos(&queue_infos) - .enabled_layer_names(&layers) - .enabled_extension_names(&extensions) - .enabled_features(&features); - - let device = instance.create_device(data.physical_device, &info, None)?; - - data.graphics_queue = device.get_device_queue(indices.graphics, 0); - data.present_queue = device.get_device_queue(indices.present, 0); - - Ok(device) -} - -unsafe fn create_swapchain( - window:&Window, - instance:&Instance, - device:&Device, - data:&mut AppData, - // old_swapchain: Option -) -> Result<()> { - let indices = QueueFamilyIndices::get(instance, data, data.physical_device)?; - let support = SwapchainSupport::get(instance, data, data.physical_device)?; - - let surface_format = get_swapchain_surface_format(&support.formats); - let present_mode = get_swapchain_present_mode(&support.present_modes); - let extent = get_swapchain_extent(window, support.capabilities); - - data.swapchain_format = surface_format.format; - data.swapchain_extent = extent; - - let mut image_count = support.capabilities.min_image_count + 1; - if support.capabilities.max_image_count != 0 && image_count > support.capabilities.max_image_count { - image_count = support.capabilities.max_image_count; - } - - let mut queue_family_indices = vec![]; - let image_sharing_mode = if indices.graphics != indices.present { - queue_family_indices.push(indices.graphics); - queue_family_indices.push(indices.present); - vk::SharingMode::CONCURRENT - } else { - vk::SharingMode::EXCLUSIVE - }; - - let info = vk::SwapchainCreateInfoKHR::builder() - .surface(data.surface) - .min_image_count(image_count) - .image_format(surface_format.format) - .image_color_space(surface_format.color_space) - .image_extent(extent) - .image_array_layers(1) - .image_usage(vk::ImageUsageFlags::COLOR_ATTACHMENT) - .image_sharing_mode(image_sharing_mode) - .queue_family_indices(&queue_family_indices) - .pre_transform(support.capabilities.current_transform) - .composite_alpha(vk::CompositeAlphaFlagsKHR::OPAQUE) - .present_mode(present_mode) - .clipped(true) - .old_swapchain(vk::SwapchainKHR::null()); - // .old_swapchain(data.swapchain); // TODO if experiencing issues replace with vk::SwapchainKHR::null() - - data.swapchain = device.create_swapchain_khr(&info, None)?; - - data.swapchain_images = device.get_swapchain_images_khr(data.swapchain)?; - - Ok(()) -} - -fn get_swapchain_surface_format(formats:&[vk::SurfaceFormatKHR]) -> vk::SurfaceFormatKHR { - formats - .iter() - .cloned() - .find(|f| f.format == vk::Format::B8G8R8A8_SRGB && f.color_space == vk::ColorSpaceKHR::SRGB_NONLINEAR) - .unwrap_or_else(|| formats[0]) -} - -fn get_swapchain_present_mode(present_modes:&[vk::PresentModeKHR]) -> vk::PresentModeKHR { - present_modes - .iter() - .cloned() - .find(|m| *m == vk::PresentModeKHR::MAILBOX) - .unwrap_or(vk::PresentModeKHR::FIFO) -} - -fn get_swapchain_extent(window:&Window, capabilities:vk::SurfaceCapabilitiesKHR) -> vk::Extent2D { - if capabilities.current_extent.width != u32::MAX { - capabilities.current_extent - } else { - vk::Extent2D::builder() - .width( - window - .inner_size() - .width - .clamp(capabilities.min_image_extent.width, capabilities.max_image_extent.width), - ) - .height( - window - .inner_size() - .height - .clamp(capabilities.min_image_extent.height, capabilities.max_image_extent.height), - ) - .build() - } -} - -unsafe fn create_swapchain_image_views(device:&Device, data:&mut AppData) -> Result<()> { - data.swapchain_image_views = data - .swapchain_images - .iter() - .map(|i| { - let components = vk::ComponentMapping::builder() - .r(vk::ComponentSwizzle::IDENTITY) - .g(vk::ComponentSwizzle::IDENTITY) - .b(vk::ComponentSwizzle::IDENTITY) - .a(vk::ComponentSwizzle::IDENTITY); - - let subresource_range = vk::ImageSubresourceRange::builder() - .aspect_mask(vk::ImageAspectFlags::COLOR) - .base_mip_level(0) - .level_count(1) - .base_array_layer(0) - .layer_count(1); - - let info = vk::ImageViewCreateInfo::builder() - .image(*i) - .view_type(vk::ImageViewType::_2D) - .format(data.swapchain_format) - .components(components) - .subresource_range(subresource_range); - - device.create_image_view(&info, None) - }) - .collect::, _>>()?; - - Ok(()) -} - -unsafe fn create_render_pass(instance:&Instance, device:&Device, data:&mut AppData) -> Result<()> { - let color_attachment = vk::AttachmentDescription::builder() - .format(data.swapchain_format) - .samples(vk::SampleCountFlags::_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::builder() - .attachment(0) - .layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL); - - let color_attachments = &[color_attachment_ref]; - let subpass = vk::SubpassDescription::builder() - .pipeline_bind_point(vk::PipelineBindPoint::GRAPHICS) - .color_attachments(color_attachments); - - let dependency = vk::SubpassDependency::builder() - .src_subpass(vk::SUBPASS_EXTERNAL) - .dst_subpass(0) - .src_stage_mask(vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT) - .src_access_mask(vk::AccessFlags::empty()) - .dst_stage_mask(vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT) - .dst_access_mask(vk::AccessFlags::COLOR_ATTACHMENT_WRITE); - - let attachments = &[color_attachment]; - let subpasses = &[subpass]; - let dependencies = &[dependency]; - let info = vk::RenderPassCreateInfo::builder() - .attachments(attachments) - .subpasses(subpasses) - .dependencies(dependencies); - - data.render_pass = device.create_render_pass(&info, None)?; - - Ok(()) -} - -unsafe fn create_pipeline(device:&Device, data:&mut AppData) -> Result<()> { - const_shaders! { - frag = "f_default.spv"; - vert = "v_default.spv"; - } - - let vert_shader_module = create_shader_module(device, &vert[..])?; - let frag_shader_module = create_shader_module(device, &frag[..])?; - - let vert_stage = vk::PipelineShaderStageCreateInfo::builder() - .stage(vk::ShaderStageFlags::VERTEX) - .module(vert_shader_module) - .name(b"main\0"); // keep specialization_info in mind for pipeline creation optimizations that dont happen at render time - - let frag_stage = vk::PipelineShaderStageCreateInfo::builder() - .stage(vk::ShaderStageFlags::FRAGMENT) - .module(frag_shader_module) - .name(b"main\0"); - - let vertex_input_state = vk::PipelineVertexInputStateCreateInfo::builder(); - - let input_assembly_state = vk::PipelineInputAssemblyStateCreateInfo::builder() - .topology(vk::PrimitiveTopology::TRIANGLE_LIST) - .primitive_restart_enable(false); - - let viewport = vk::Viewport::builder() - .x(0.0) - .y(0.0) - .width(data.swapchain_extent.width as f32) - .height(data.swapchain_extent.height as f32) - .min_depth(0.0) - .max_depth(1.0); - - let scissor = vk::Rect2D::builder().offset(vk::Offset2D { x:0, y:0 }).extent(data.swapchain_extent); - - let viewports = &[viewport]; - let scissors = &[scissor]; - let viewport_state = vk::PipelineViewportStateCreateInfo::builder().viewports(viewports).scissors(scissors); - - let rasterization_state = vk::PipelineRasterizationStateCreateInfo::builder() - .depth_clamp_enable(false) - .rasterizer_discard_enable(false) - .polygon_mode(vk::PolygonMode::FILL) - .line_width(1.0) - .cull_mode(vk::CullModeFlags::BACK) - .front_face(vk::FrontFace::CLOCKWISE) - .depth_bias_enable(false); - - let multisample_state = vk::PipelineMultisampleStateCreateInfo::builder() - .sample_shading_enable(false) - .rasterization_samples(vk::SampleCountFlags::_1); - - let attachment = vk::PipelineColorBlendAttachmentState::builder() - .color_write_mask(vk::ColorComponentFlags::all()) - // .blend_enable(false); - .blend_enable(true) - .src_color_blend_factor(vk::BlendFactor::SRC_ALPHA) - .dst_color_blend_factor(vk::BlendFactor::ONE_MINUS_SRC_ALPHA) - .color_blend_op(vk::BlendOp::ADD) - .src_alpha_blend_factor(vk::BlendFactor::ONE) - .dst_alpha_blend_factor(vk::BlendFactor::ZERO) - .alpha_blend_op(vk::BlendOp::ADD); - - let attachments = &[attachment]; - let color_blend_state = vk::PipelineColorBlendStateCreateInfo::builder() - .logic_op_enable(false) - .logic_op(vk::LogicOp::COPY) - .attachments(attachments) - .blend_constants([0.0, 0.0, 0.0, 0.0]); - - let layout_info = vk::PipelineLayoutCreateInfo::builder(); - - data.pipeline_layout = device.create_pipeline_layout(&layout_info, None)?; - - let stages = &[vert_stage, frag_stage]; - let info = vk::GraphicsPipelineCreateInfo::builder() - .stages(stages) - .vertex_input_state(&vertex_input_state) - .input_assembly_state(&input_assembly_state) - .viewport_state(&viewport_state) - .rasterization_state(&rasterization_state) - .multisample_state(&multisample_state) - .color_blend_state(&color_blend_state) - .layout(data.pipeline_layout) - .render_pass(data.render_pass) - .subpass(0) - .base_pipeline_handle(vk::Pipeline::null()) // Optional. - .base_pipeline_index(-1); // Optional. - - data.pipeline = device.create_graphics_pipelines(vk::PipelineCache::null(), &[info], None)?.0[0]; - - device.destroy_shader_module(vert_shader_module, None); - device.destroy_shader_module(frag_shader_module, None); - Ok(()) -} - -unsafe fn create_shader_module(device:&Device, bytecode:&[u8]) -> Result { - let bytecode = Bytecode::new(bytecode).unwrap(); - - let info = vk::ShaderModuleCreateInfo::builder().code_size(bytecode.code_size()).code(bytecode.code()); - - Ok(device.create_shader_module(&info, None)?) -} - -unsafe fn create_framebuffers(device:&Device, data:&mut AppData) -> Result<()> { - data.framebuffers = data - .swapchain_image_views - .iter() - .map(|i| { - let attachments = &[*i]; - let create_info = vk::FramebufferCreateInfo::builder() - .render_pass(data.render_pass) - .attachments(attachments) - .width(data.swapchain_extent.width) - .height(data.swapchain_extent.height) - .layers(1); - - device.create_framebuffer(&create_info, None) - }) - .collect::, _>>()?; - - Ok(()) -} -unsafe fn create_command_pool(instance:&Instance, device:&Device, data:&mut AppData) -> Result<()> { - let indices = QueueFamilyIndices::get(instance, data, data.physical_device)?; - - let info = vk::CommandPoolCreateInfo::builder() - .flags(vk::CommandPoolCreateFlags::empty()) // Optional. - .queue_family_index(indices.graphics); - - data.command_pool = device.create_command_pool(&info, None)?; - - Ok(()) -} - -unsafe fn create_command_buffers(device:&Device, data:&mut AppData) -> Result<()> { - let allocate_info = vk::CommandBufferAllocateInfo::builder() - .command_pool(data.command_pool) - .level(vk::CommandBufferLevel::PRIMARY) - .command_buffer_count(data.framebuffers.len() as u32); - - data.command_buffers = device.allocate_command_buffers(&allocate_info)?; - - for (i, command_buffer) in data.command_buffers.iter().enumerate() { - let inheritance = vk::CommandBufferInheritanceInfo::builder(); - - let info = vk::CommandBufferBeginInfo::builder() - .flags(vk::CommandBufferUsageFlags::empty()) // Optional. - .inheritance_info(&inheritance); // Optional. - - device.begin_command_buffer(*command_buffer, &info)?; - - let render_area = vk::Rect2D::builder().offset(vk::Offset2D::default()).extent(data.swapchain_extent); - - let color_clear_value = vk::ClearValue { - color:vk::ClearColorValue { float32:[0.0, 0.0, 0.0, 1.0] }, - }; - - let clear_values = &[color_clear_value]; - let info = vk::RenderPassBeginInfo::builder() - .render_pass(data.render_pass) - .framebuffer(data.framebuffers[i]) - .render_area(render_area) - .clear_values(clear_values); - - device.cmd_begin_render_pass(*command_buffer, &info, vk::SubpassContents::INLINE); - device.cmd_bind_pipeline(*command_buffer, vk::PipelineBindPoint::GRAPHICS, data.pipeline); - device.cmd_draw(*command_buffer, 3, 1, 0, 0); - - device.cmd_end_render_pass(*command_buffer); - device.end_command_buffer(*command_buffer)?; - } - - Ok(()) -} - -unsafe fn create_sync_objects(device:&Device, data:&mut AppData) -> Result<()> { - let semaphore_info = vk::SemaphoreCreateInfo::builder(); - let fence_info = vk::FenceCreateInfo::builder().flags(vk::FenceCreateFlags::SIGNALED); - - for _ in 0..MAX_FRAMES_IN_FLIGHT { - data.image_available_semaphores.push(device.create_semaphore(&semaphore_info, None)?); - data.render_finished_semaphores.push(device.create_semaphore(&semaphore_info, None)?); - - data.in_flight_fences.push(device.create_fence(&fence_info, None)?); - } - - data.images_in_flight = data.swapchain_images.iter().map(|_| vk::Fence::null()).collect(); - - Ok(()) -} - -#[derive(Copy, Clone, Debug)] -struct QueueFamilyIndices { - graphics:u32, - present: u32, -} - -impl QueueFamilyIndices { - unsafe fn get(instance:&Instance, data:&AppData, physical_device:vk::PhysicalDevice) -> Result { - let properties = instance.get_physical_device_queue_family_properties(physical_device); - - let graphics = properties - .iter() - .position(|p| p.queue_flags.contains(vk::QueueFlags::GRAPHICS)) - .map(|i| i as u32); - - let mut present = None; - for (index, properties) in properties.iter().enumerate() { - if instance.get_physical_device_surface_support_khr(physical_device, index as u32, data.surface)? { - present = Some(index as u32); - break; - } - } - - // Note that it's very likely that these end up being the same queue family after all, but throughout the program we will treat them as if they were separate queues for a uniform approach. Nevertheless, you could add logic to explicitly prefer a physical device that supports drawing and presentation in the same queue for improved performance. - if let (Some(graphics), Some(present)) = (graphics, present) { - Ok(Self { graphics, present }) - } else { - Err(anyhow!(SuitabilityError("Missing required queue families."))) - } - } -} - -#[derive(Clone, Debug)] -struct SwapchainSupport { - capabilities: vk::SurfaceCapabilitiesKHR, - formats: Vec, - present_modes:Vec, -} - -impl SwapchainSupport { - unsafe fn get(instance:&Instance, data:&AppData, physical_device:vk::PhysicalDevice) -> Result { - Ok(Self { - capabilities: instance.get_physical_device_surface_capabilities_khr(physical_device, data.surface)?, - formats: instance.get_physical_device_surface_formats_khr(physical_device, data.surface)?, - present_modes:instance.get_physical_device_surface_present_modes_khr(physical_device, data.surface)?, - }) - } -} diff --git a/src/mac.rs b/src/mac.rs new file mode 100644 index 0000000..be6d4d8 --- /dev/null +++ b/src/mac.rs @@ -0,0 +1,96 @@ +use anyhow::Result; + +use log::*; + +use vulkanalia::prelude::v1_0::*; + +use winit::dpi::LogicalSize; +use winit::event::Event; +use winit::event::WindowEvent; +use winit::event_loop::EventLoop; +use winit::window::WindowBuilder; + +use crate::graphics_engines::vulkan::App; + +const WINDOW_TITLE:&'static str = "MineMod"; + +pub fn main() -> Result<()> { + super::init_logging(); + + info!("Registering CTRLC hook."); + + let (shutdown_tx, shutdown_rx) = std::sync::mpsc::channel(); + + ctrlc::set_handler(move || { + shutdown_tx.send(()).expect("Failed to send shutdown signal"); + }) + .expect("Error setting Ctrl-C handler"); + + info!("Initializing event loop and winit window instance."); + + // Window + + let event_loop = EventLoop::new()?; + let window = WindowBuilder::new() + .with_title(WINDOW_TITLE) + .with_inner_size(LogicalSize::new(1024, 768)) + .build(&event_loop)?; + + info!("Creating app and starting event loop."); + + // App + + let mut app = unsafe { App::create(&window)? }; + let mut minimized = false; + + let shutdown_rx = std::sync::Arc::new(std::sync::Mutex::new(Some(shutdown_rx))); + + event_loop.run(move |event, elwt| { + let mut shutdown_rx_guard = shutdown_rx.lock().unwrap(); + + if let Some(receiver) = shutdown_rx_guard.as_mut() { + if receiver.try_recv().is_ok() { + info!("Closing event loop and destroying Vulkan instance."); + elwt.exit(); + unsafe { + app.device.device_wait_idle().unwrap(); + app.destroy(); + } + return; + } + } + + match event { + // Request a redraw when all events were processed. + Event::AboutToWait => window.request_redraw(), + Event::WindowEvent { event, .. } => match event { + // Render a frame if our Vulkan app is not being destroyed. + WindowEvent::RedrawRequested if !elwt.exiting() && !minimized => { + unsafe { app.render(&window) }.unwrap(); + }, + WindowEvent::Resized(size) => + if size.width == 0 || size.height == 0 { + minimized = true; + } else { + minimized = false; + app.resized = true; + }, + // Destroy our Vulkan app. + WindowEvent::CloseRequested => { + info!("Closing event loop and destroying Vulkan instance."); + elwt.exit(); + unsafe { + app.device.device_wait_idle().unwrap(); + app.destroy(); + } + }, + _ => {}, + }, + _ => {}, + } + })?; + + info!("Exiting program."); + + Ok(()) +} diff --git a/src/macos/metal_bindgen/metal.rs b/src/macos/metal_bindgen/metal.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/main.rs b/src/main.rs index 306049d..0c239f0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,4 @@ +#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] #![deny(clippy::unwrap_used)] #![allow( // dead_code, @@ -7,8 +8,38 @@ unsafe_op_in_unsafe_fn )] -// planned system targets +mod cataclysm; +mod graphics_engines; +// NOTE use crate:: for things in engine that depend on the target platform +// this allows an incredibly easy way to handle gui and others per platform +// without needing a ton of conditional compile targets in the engine code + +// NOTE player save data will behave like so: +// playerA +// playerA/worldA +// playerB +// playerB/worldB +// playerB/worldC +// playerB/world... +// playerC +// playerC/worldD + +// Make sure to date save data and potentially version it so changes may be rolled back to a set date + +// quotes from the first link in thanks.txt +// +// It should be noted that in a real world application, you're not supposed to actually call allocate_memory for every individual buffer. The maximum number of simultaneous memory allocations is limited by the max_memory_allocation_count physical device limit, which may be as low as 4096 even on high end hardware like an NVIDIA GTX 1080. The right way to allocate memory for a large number of objects at the same time is to create a custom allocator that splits up a single allocation among many different objects by using the offset parameters that we've seen in many functions. +// The previous chapter already mentioned that you should allocate multiple resources like buffers from a single memory allocation, but in fact you should go a step further. Driver developers recommend that you also store multiple buffers, like the vertex and index buffer, into a single vk::Buffer and use offsets in commands like cmd_bind_vertex_buffers. The advantage is that your data is more cache friendly in that case, because it's closer together. It is even possible to reuse the same chunk of memory for multiple resources if they are not used during the same render operations, provided that their data is refreshed, of course. This is known as aliasing and some Vulkan functions have explicit flags to specify that you want to do this. +// +// https://docs.vulkan.org/spec/latest/chapters/descriptorsets.html#interfaces-resources-layout +// + +// test BCn vs raw formats + +// research why texture atlases exist, it seems like an easy way to reduce the allocation calls since they are limited, and it seems to provide better usage of memory overall + +// Planned system targets #[cfg(target_os = "linux")] mod linux; #[cfg(target_os = "linux")] pub use linux::*; #[cfg(target_os = "windows")] mod windows; @@ -20,7 +51,7 @@ #[cfg(target_os = "vita")] mod vita; #[cfg(target_os = "vita")] pub use vita::*; -// unconfirmed system targets +// Potential system targets // bindgen, cc, https://github.com/rust-console @@ -44,3 +75,21 @@ // mod wii; // #[cfg(target_os = "wii")] // pub use wii::*; + +fn init_logging() { + tracing_subscriber::fmt() + .compact() + .with_timer(tracing_subscriber::fmt::time::uptime()) + .with_ansi(true) + .with_level(true) + // .with_thread_ids(true) + .with_thread_names(true) + .with_max_level(if cfg!(debug_assertions) { + tracing::level_filters::LevelFilter::DEBUG + } else { + tracing::level_filters::LevelFilter::INFO + }) + .init(); + + // pretty_env_logger::init_timed(); +} diff --git a/src/vita/mod.rs b/src/vita/mod.rs index c31a448..7712921 100644 --- a/src/vita/mod.rs +++ b/src/vita/mod.rs @@ -17,166 +17,66 @@ pub const NOT_CDRAM_ALIGNMENT:u32 = (4 * 1024) - 1; #[inline] pub const fn align(data:u32, alignment:u32) -> u32 { (data + alignment) & !alignment } -// these should probably be macros? -#[inline] -pub fn abs(x:i8) -> i8 { if x > 0 { x } else { x * -1 } } +// #[inline] +// pub fn abs(x:i8) -> i8 { if x > 0 { x } else { x * -1 } } -// tracing based logger +// fn rainbow_color(t:f32) -> (u32, u32, u32) { +// let hue = t * 360.0; // Convert time to hue (0-360 degrees) +// let saturation = 1.0; // Maximum saturation +// let value = 1.0; // Maximum value (brightness) -fn rainbow_color(t:f32) -> (u32, u32, u32) { - let hue = t * 360.0; // Convert time to hue (0-360 degrees) - let saturation = 1.0; // Maximum saturation - let value = 1.0; // Maximum value (brightness) +// // Convert HSV to RGB +// let c = value * saturation; +// let x = c * (1.0 - (((hue / 60.0) % 2.0) - 1.0).abs()); +// let m = value - c; +// let (r, g, b) = match hue { +// 0.0..=60.0 => (c, x, 0.0), +// 60.0..=120.0 => (x, c, 0.0), +// 120.0..=180.0 => (0.0, c, x), +// 180.0..=240.0 => (0.0, x, c), +// 240.0..=300.0 => (x, 0.0, c), +// _ => (c, 0.0, x), +// }; - // Convert HSV to RGB - let c = value * saturation; - let x = c * (1.0 - (((hue / 60.0) % 2.0) - 1.0).abs()); - let m = value - c; - let (r, g, b) = match hue { - 0.0..=60.0 => (c, x, 0.0), - 60.0..=120.0 => (x, c, 0.0), - 120.0..=180.0 => (0.0, c, x), - 180.0..=240.0 => (0.0, x, c), - 240.0..=300.0 => (x, 0.0, c), - _ => (c, 0.0, x), - }; +// // Scale RGB values to 8-bit integers +// let r = (255.0 * (r + m)) as u32; +// let g = (255.0 * (g + m)) as u32; +// let b = (255.0 * (b + m)) as u32; - // Scale RGB values to 8-bit integers - let r = (255.0 * (r + m)) as u32; - let g = (255.0 * (g + m)) as u32; - let b = (255.0 * (b + m)) as u32; +// (r, g, b) +// } - (r, g, b) -} - -fn ilerp(min:f32, max:f32, input:f32) -> f32 { (input - min) / (max - min) } +// fn ilerp(min:f32, max:f32, input:f32) -> f32 { (input - min) / (max - min) } // #[tokio::main] pub fn main() { std::panic::set_hook(Box::new(custom_panic_hook)); + super::init_logging(); - println!("Panic hook set"); - - // SCE_GXM_ERROR_INVALID_POINTER - - // const thing1: u32 = 0x80024b05; - // const thing2: u32 = 0x80024309; - - // unsafe { sceClibPrintf("whar?".as_ptr() as *const i8); } - - // text_screen(); - - // std::thread::sleep(Duration::from_secs(3)); + panic!("Vista system target is not supported currently."); // for _ in 0..3 { // raw_rainbow(); // } - // std::thread::sleep(Duration::from_secs(3)); - - // new_text_screen(); - - // std::thread::sleep(Duration::from_secs(3)); - - // unsafe { - // cube::cube(); - // cube2::cube(); - // cube3::cube(); - // } - - std::thread::sleep(Duration::from_secs(3)); - - panic!("Test panic!"); - - // loop { - // std::thread::sleep(Duration::from_secs(10)); - // } - unsafe { sceGxmTerminate() }; } -// fn new_text_screen() { -// let mut screen = gui::TextScreen::predef(); -// screen.enable(); +// fn raw_rainbow() { +// let mut raw_buffer = gui::framebuffer::Framebuffer::new(); +// raw_buffer.set_display(); -// // for i in 336..446 { -// // screen.framebuffer.set(i, 0xFF_FF_FF_FF); -// // } +// for index_width in 0..raw_buffer.screen_width { +// thread::sleep(Duration::from_micros(20)); -// // screen.set_cursor(4, 0); -// // write!(screen, "\n").ok(); -// // screen.update_screen_raw(); -// write!(screen, "Goodbye Void!\n").ok(); -// screen.update_screen_raw(); -// thread::sleep(Duration::from_secs(3)); -// write!(screen, "Goodbye Void!\n").ok(); -// screen.update_screen_raw(); -// thread::sleep(Duration::from_secs(3)); -// write!(screen, "Goodbye Void!\n").ok(); -// screen.update_screen_raw(); -// thread::sleep(Duration::from_secs(3)); +// let (r, g, b) = rainbow_color(ilerp(0.0, raw_buffer.screen_width as f32, index_width as f32)); -// screen.set_cursor(0, 3); -// for _ in 0..25 { -// write!(screen, "Goodbye Void!\n").ok(); -// // screen.update_screen_raw(); +// for index_height in 0..raw_buffer.screen_height { +// raw_buffer.set(index_width + (index_height * raw_buffer.screen_width), 0 as u32 | b << 16 | g << 8 | r); // VITA USES ABGR +// } // } - -// thread::sleep(Duration::from_secs(3)); -// screen.clear(); -// screen.update_screen_raw(); - -// // screen.set_foreground(255,255,0); - -// // for x in 0..24 { -// // for y in 0..24 { -// // mono_screen.set_cursor(24 + x, 24 + y); -// // write!((219 as char).to_string()); -// // thread::sleep(Duration::from_millis(16)); -// // } -// // } -// // mono_screen.set_cursor(0,1); -// // mono_screen.set_foreground(0,0,0); - -// // drop(screen); - -// // let mut screen = gui::TextScreen::new(); -// // screen.set_display(); -// // loop { -// // screen.clear(); -// // screen.update_screen_raw(); -// // std::thread::sleep(Duration::from_millis(100)); -// // } // } -// fn text_screen() { -// let mut screen = debug::framebuffer::DebugScreen::new(); -// writeln!(screen, "crusty").ok(); -// // thread::sleep(Duration::from_secs(2)); -// writeln!(screen, "newline").ok(); -// // thread::sleep(Duration::from_secs(2)); - -// let random_numbers: Vec = (0..8).map(|_i| rand::random::()).collect(); -// writeln!(screen, "random numbers: {:?}", random_numbers).ok(); - -// // writeln!(screen, "I know where you sleep :3").ok(); -// } - -fn raw_rainbow() { - let mut raw_buffer = gui::framebuffer::Framebuffer::new(); - raw_buffer.set_display(); - - for index_width in 0..raw_buffer.screen_width { - thread::sleep(Duration::from_micros(20)); - - let (r, g, b) = rainbow_color(ilerp(0.0, raw_buffer.screen_width as f32, index_width as f32)); - - for index_height in 0..raw_buffer.screen_height { - raw_buffer.set(index_width + (index_height * raw_buffer.screen_width), 0 as u32 | b << 16 | g << 8 | r); // VITA USES ABGR - } - } -} - fn custom_panic_hook(info:&PanicHookInfo<'_>) { // The current implementation always returns `Some`. let location = info.location().unwrap(); diff --git a/src/wasm.rs b/src/wasm.rs new file mode 100644 index 0000000..567144f --- /dev/null +++ b/src/wasm.rs @@ -0,0 +1,6 @@ +use log::*; + +pub fn main() { + super::init_logging(); + error!("Sorry, but wasm currently isnt setup in this engine!"); +} diff --git a/src/windows.rs b/src/windows.rs index 8b13789..2b92962 100644 --- a/src/windows.rs +++ b/src/windows.rs @@ -1 +1,96 @@ +use anyhow::Result; +use log::*; + +use vulkanalia::prelude::v1_0::*; + +use winit::dpi::LogicalSize; +use winit::event::Event; +use winit::event::WindowEvent; +use winit::event_loop::EventLoop; +use winit::window::WindowBuilder; + +use crate::graphics_engines::vulkan::App; + +const WINDOW_TITLE:&'static str = "MineMod"; + +pub fn main() -> Result<()> { + super::init_logging(); + + info!("Registering CTRLC hook."); + + let (shutdown_tx, shutdown_rx) = std::sync::mpsc::channel::<()>(); + + ctrlc::set_handler(move || { + shutdown_tx.send(()).expect("Failed to send shutdown signal"); + }) + .expect("Error setting Ctrl-C handler"); + + info!("Initializing event loop and winit window instance."); + + // Window + + let event_loop = EventLoop::new()?; + let window = WindowBuilder::new() + .with_title(WINDOW_TITLE) + .with_inner_size(LogicalSize::new(1024, 768)) + .build(&event_loop)?; + + info!("Creating app and starting event loop."); + + // App + + let mut app = unsafe { App::create(&window)? }; + let mut minimized = false; + + let shutdown_rx = std::sync::Arc::new(std::sync::Mutex::new(Some(shutdown_rx))); + + event_loop.run(move |event, elwt| { + let mut shutdown_rx_guard = shutdown_rx.lock().unwrap(); + + if let Some(receiver) = shutdown_rx_guard.as_mut() { + if receiver.try_recv().is_ok() { + info!("Closing event loop and destroying Vulkan instance."); + elwt.exit(); + unsafe { + app.device.device_wait_idle().unwrap(); + app.destroy(); + } + return; + } + } + + match event { + // Request a redraw when all events were processed. + Event::AboutToWait => window.request_redraw(), + Event::WindowEvent { event, .. } => match event { + // Render a frame if our Vulkan app is not being destroyed. + WindowEvent::RedrawRequested if !elwt.exiting() && !minimized => { + unsafe { app.render(&window) }.unwrap(); + }, + WindowEvent::Resized(size) => + if size.width == 0 || size.height == 0 { + minimized = true; + } else { + minimized = false; + app.resized = true; + }, + // Destroy our Vulkan app. + WindowEvent::CloseRequested => { + info!("Closing event loop and destroying Vulkan instance."); + elwt.exit(); + unsafe { + app.device.device_wait_idle().unwrap(); + app.destroy(); + } + }, + _ => {}, + }, + _ => {}, + } + })?; + + info!("Exiting program."); + + Ok(()) +} diff --git a/thanks.txt b/thanks.txt index 367a8cc..9daed35 100644 --- a/thanks.txt +++ b/thanks.txt @@ -1,10 +1,18 @@ -# list of resources ive used in the making of this project -# thank you to everyone here for allowing me to learn what was required to make this project +# List of resources ive used in the making of this project +# Thank you to everyone here for allowing me to learn what was required to make this project +# Certain items may not be immediately related to the project but are still listed because they helped in another way + +# The entire vulkan 1.0 implementation is based off of this guide, currently just copied and pasted with slight modification https://kylemayes.github.io/vulkanalia/introduction.html # javidx9 code it yourself console game engine tutorial +# I like this series because it explains much of the math behind 3d rendering https://www.youtube.com/watch?v=ih20l3pJoeU https://www.youtube.com/watch?v=XgMWc6LumG4 https://www.youtube.com/watch?v=HXSuNxpCzdM https://www.youtube.com/watch?v=nBzCS-Y0FcY + +# Very interesting shader project i will be using to test my engines shader capabilities later on +https://www.youtube.com/watch?v=HPqGaIMVuLs +https://github.com/runevision/Dither3D