vox_engine 0.3.0

voxel game engine
use std::{sync::Arc, time::Instant};

use vulkano::{
    VulkanLibrary, VulkanObject,
    command_buffer::allocator::StandardCommandBufferAllocator,
    descriptor_set::allocator::StandardDescriptorSetAllocator,
    device::{
        Device, DeviceCreateInfo, DeviceExtensions, DeviceFeatures, Queue, QueueCreateInfo,
        QueueFlags, physical::PhysicalDeviceType,
    },
    format::Format,
    image::{Image, ImageType, ImageUsage, view::ImageView},
    instance::{Instance, InstanceCreateFlags, InstanceCreateInfo, InstanceExtensions},
    memory::allocator::{MemoryTypeFilter, StandardMemoryAllocator},
    swapchain::{
        PresentMode, Surface, SurfaceApi, Swapchain, SwapchainAcquireFuture, SwapchainCreateInfo,
    },
    sync::{GpuFuture, now, semaphore::Semaphore},
};

use sdl3::{EventPump, Sdl, VideoSubsystem, event::Event, video::Window};

use getset::{CloneGetters, CopyGetters, Getters, MutGetters};

use super::{Flags, helpers};

#[derive(CopyGetters, CloneGetters, Getters, MutGetters)]
pub struct Engine {
    #[getset(get_clone = "pub with_prefix")]
    pub(super) library: Arc<VulkanLibrary>,
    #[getset(get_clone = "pub with_prefix")]
    pub(super) instance: Arc<Instance>,

    pub(super) surface: Arc<Surface>,
    #[getset(get_clone = "pub with_prefix")]
    pub(super) device: Arc<Device>,
    #[getset(get_clone = "pub with_prefix")]
    pub(super) queue: Arc<Queue>,
    #[getset(get_clone = "pub with_prefix")]
    pub(super) swapchain: Arc<Swapchain>,
    #[getset(get_clone = "pub with_prefix")]
    pub(super) images: Vec<Arc<Image>>,

    #[getset(get_clone = "pub with_prefix")]
    pub(super) memory_allocator: Arc<StandardMemoryAllocator>,
    #[getset(get_clone = "pub with_prefix")]
    pub(super) command_buffer_allocator: Arc<StandardCommandBufferAllocator>,
    #[getset(get_clone = "pub with_prefix")]
    pub(super) descriptor_set_allocator: Arc<StandardDescriptorSetAllocator>,

    #[getset(get = "pub with_prefix")]
    pub(super) image_format: Format,

    #[getset(get_clone = "pub with_prefix")]
    pub(super) image: Arc<Image>,
    #[getset(get_clone = "pub with_prefix")]
    pub(super) view: Arc<ImageView>,

    pub(super) previous_future: Option<Box<dyn GpuFuture + Send + Sync>>,
    #[getset(get_copy = "pub with_prefix")]
    pub(super) current_image_index: u32,
    pub(super) current_acquire_future: Option<SwapchainAcquireFuture>,

    #[getset(get_clone = "pub with_prefix")]
    pub(super) render_complete_semaphore: Arc<Semaphore>,

    #[getset(get = "pub with_prefix")]
    pub(super) event: EventPump,

    #[getset(get = "pub with_prefix")]
    pub(super) collected_events: Vec<Event>,

    #[getset(get_copy = "pub with_prefix")]
    pub(super) delta_time: f64,
    pub(super) last_frame: Instant,
    pub(super) start: Instant,

    #[getset(get = "pub with_prefix")]
    pub(super) render_scale: u16,

    #[getset(get = "pub with_prefix")]
    pub(super) width: u16,
    #[getset(get = "pub with_prefix")]
    pub(super) height: u16,

    #[getset(get_copy = "pub with_prefix")]
    pub(super) x_offset: f32,
    #[getset(get_copy = "pub with_prefix")]
    pub(super) y_offset: f32,

    pub(super) accum_x: f32,
    pub(super) accum_y: f32,

    pub(super) mouse_x: f32,
    pub(super) mouse_y: f32,

    pub(super) last_x: f32,
    pub(super) last_y: f32,

    pub(super) scale: f32,

    pub(super) x: f32,
    pub(super) y: f32,

    #[getset(get_copy = "pub with_prefix", get_mut = "pub with_prefix")]
    pub(super) flags: Flags,

    pub(super) context: Sdl,
    pub(super) video: VideoSubsystem,

    #[getset(get = "pub with_prefix")]
    pub(super) window: Window,
}

impl Engine {
    pub fn new(title: &str, start: Instant, render_scale: u16, flags: Flags) -> Self {
        let context = sdl3::init().expect("could not initialize SDL3");
        let video = context.video().expect("could not create video subsystem");
        let event = context.event_pump().expect("could not get event pump");

        let display = video
            .get_primary_display()
            .expect("could not get primary display");

        let (width, height) = (
            display.get_mode().unwrap().w as u16,
            display.get_mode().unwrap().h as u16,
        );

        let window = video
            .window(title, width.into(), height.into())
            .borderless()
            .position(0, 0)
            .vulkan()
            .build()
            .expect("could not build window");

        let extensions = window
            .vulkan_instance_extensions()
            .expect("could not get vulkan instance extensions from SDL3 window");

        let mut enabled_extensions = InstanceExtensions::empty();

        for ext in &extensions {
            match ext.as_str() {
                "VK_KHR_wayland_surface" => enabled_extensions.khr_wayland_surface = true,
                "VK_KHR_xlib_surface" => enabled_extensions.khr_xlib_surface = true,
                "VK_KHR_xcb_surface" => enabled_extensions.khr_xcb_surface = true,
                "VK_KHR_win32_surface" => enabled_extensions.khr_win32_surface = true,
                "VK_KHR_surface" => enabled_extensions.khr_surface = true,
                _ => {
                    eprintln!("Unknown Vulkan instance extension: {}", ext)
                }
            }
        }

        let library = VulkanLibrary::new().expect("no vulkan library or dll available");

        let instance = Instance::new(
            library.clone(),
            InstanceCreateInfo {
                flags: InstanceCreateFlags::ENUMERATE_PORTABILITY,
                enabled_extensions,
                ..Default::default()
            },
        )
        .expect("could not create vulkan instance");

        let surface;

        unsafe {
            let sdl3_surface = window
                .vulkan_create_surface(instance.handle())
                .expect("could not create vulkan surface from SDL3 window");
            surface =
                Surface::from_handle(instance.clone(), sdl3_surface, SurfaceApi::Headless, None);
        }

        let surface = Arc::new(surface);

        let device_extensions = DeviceExtensions {
            khr_swapchain: true,
            ..Default::default()
        };

        let device_features = DeviceFeatures {
            sampler_anisotropy: true,
            ..Default::default()
        };

        let (physical_device, queue_family_index) = instance
            .enumerate_physical_devices()
            .expect("could not find physical devices")
            .filter(|p| p.supported_extensions().contains(&device_extensions))
            .filter(|p| p.supported_features().contains(&device_features))
            .filter_map(|p| {
                p.queue_family_properties()
                    .iter()
                    .enumerate()
                    .position(|(i, q)| {
                        q.queue_flags.contains(QueueFlags::GRAPHICS)
                            && p.surface_support(i as u32, &surface).unwrap_or(false)
                    })
                    .map(|q| (p, q as u32))
            })
            .min_by_key(|(p, _)| match p.properties().device_type {
                PhysicalDeviceType::DiscreteGpu => 0,
                PhysicalDeviceType::IntegratedGpu => 1,
                PhysicalDeviceType::VirtualGpu => 2,
                PhysicalDeviceType::Cpu => 3,
                _ => 4,
            })
            .expect("no device available");

        let (device, mut queues) = Device::new(
            physical_device.clone(),
            DeviceCreateInfo {
                queue_create_infos: vec![QueueCreateInfo {
                    queue_family_index: queue_family_index as u32,
                    ..Default::default()
                }],
                enabled_extensions: device_extensions,
                enabled_features: device_features,
                ..Default::default()
            },
        )
        .expect("could not create logical device");

        let queue = queues.next().unwrap();

        let caps = physical_device
            .surface_capabilities(&surface, Default::default())
            .expect("failed to get surface capabilities");

        let composite_alpha = caps.supported_composite_alpha.into_iter().next().unwrap();

        let image_format = physical_device
            .surface_formats(&surface, Default::default())
            .unwrap()
            .iter()
            .find(|(f, _)| *f == Format::R8G8B8A8_UNORM)
            .map(|(f, _)| *f)
            .unwrap_or_else(|| {
                physical_device
                    .surface_formats(&surface, Default::default())
                    .unwrap()[0]
                    .0
            });

        let (swapchain, images) = Swapchain::new(
            device.clone(),
            surface.clone(),
            SwapchainCreateInfo {
                min_image_count: caps.min_image_count.max(3),
                image_format,
                image_extent: [width as u32, height as u32],
                image_usage: ImageUsage::COLOR_ATTACHMENT
                    | ImageUsage::TRANSFER_DST
                    | ImageUsage::STORAGE,
                composite_alpha,
                present_mode: PresentMode::Immediate,
                ..Default::default()
            },
        )
        .unwrap();

        let memory_allocator = Arc::new(StandardMemoryAllocator::new_default(device.clone()));

        let command_buffer_allocator = Arc::new(StandardCommandBufferAllocator::new(
            device.clone(),
            Default::default(),
        ));

        let descriptor_set_allocator = Arc::new(StandardDescriptorSetAllocator::new(
            device.clone(),
            Default::default(),
        ));

        let previous_future =
            Some(Box::new(now(device.clone())) as Box<dyn GpuFuture + Send + Sync>);

        let render_complete_semaphore =
            Arc::new(Semaphore::new(device.clone(), Default::default()).unwrap());

        let (image, view) = helpers::create_image(
            ImageType::Dim2d,
            [
                (width / render_scale) as u32,
                (height / render_scale) as u32,
                1,
            ],
            image_format,
            ImageUsage::STORAGE | ImageUsage::TRANSFER_SRC | ImageUsage::TRANSFER_DST,
            MemoryTypeFilter::PREFER_DEVICE,
            memory_allocator.clone(),
        );

        Self {
            library,
            instance,

            surface,
            device,
            queue,
            swapchain,
            images,

            memory_allocator,
            command_buffer_allocator,
            descriptor_set_allocator,

            image_format,

            image,
            view,

            previous_future,
            current_image_index: 0,
            current_acquire_future: None,

            render_complete_semaphore,

            event,
            collected_events: Vec::new(),

            delta_time: 0f64,
            last_frame: start,
            start,

            render_scale,

            width,
            height,

            x_offset: 0f32,
            y_offset: 0f32,

            accum_x: 0f32,
            accum_y: 0f32,

            mouse_x: 0f32,
            mouse_y: 0f32,

            last_x: 0f32,
            last_y: 0f32,

            scale: 1f32,

            x: 0f32,
            y: 0f32,

            flags,

            context,
            video,

            window,
        }
    }

    pub fn get_dimensions(&self) -> (u16, u16) {
        (self.width, self.height)
    }

    pub fn get_hardware_info(&self) -> String {
        let props = self.device.physical_device().properties();
        format!("{}\n{}", props.device_name, props.api_version)
    }

    pub fn get_frame_rate(&self) -> u64 {
        (1000.0 / self.delta_time as f64).round() as u64
    }
}