Theme

alex/beta-minecraft-rust

Minecraft Beta 1.7.3 Rewrite

BRANCH main
main
SNAPSHOT Draw terrain GPU-driven from packed per-section buffers with GPU occlusion culling, add a render distance slider (2-128) with a quad/disk shape, cut chunk bookkeeping at high render distances, and fix a light underflow at y=0
DOWNLOAD
Download file

root / crates / volcanic / src / loader / library.rs

use super::{CmdsDevice, CmdsGlobal, CmdsInstance, LoadError};
use crate::{VkDevice, VkInstance};
use std::{ffi::OsStr, ops::Deref, path::PathBuf, sync::Arc};

#[derive(Clone)]
pub struct Loader {
    library: Arc<libloading::Library>,
    commands: CmdsGlobal,
}

impl Loader {
    /// # Safety
    /// The system Vulkan library and its initialization routines must be safe to load
    pub unsafe fn new() -> Result<Self, LoadError> {
        let path = if cfg!(target_os = "windows") {
            "vulkan-1.dll"
        } else if cfg!(target_os = "macos") {
            "libvulkan.1.dylib"
        } else {
            "libvulkan.so.1"
        };
        unsafe { Self::from_path(path) }
    }

    /// # Safety
    /// The library must implement the Vulkan loader ABI and be safe to load
    pub unsafe fn from_path(path: impl AsRef<OsStr>) -> Result<Self, LoadError> {
        let path = path.as_ref();
        let library =
            unsafe { libloading::Library::new(path) }.map_err(|source| LoadError::Library {
                path: PathBuf::from(path),
                source,
            })?;
        let commands = unsafe { CmdsGlobal::load(&library)? };
        for (name, loaded) in [
            (c"vkCreateInstance", commands.vkCreateInstance.is_some()),
            (
                c"vkEnumerateInstanceLayerProperties",
                commands.vkEnumerateInstanceLayerProperties.is_some(),
            ),
            (
                c"vkEnumerateInstanceExtensionProperties",
                commands.vkEnumerateInstanceExtensionProperties.is_some(),
            ),
        ] {
            if !loaded {
                return Err(LoadError::MissingBootstrap { name });
            }
        }
        Ok(Self {
            library: Arc::new(library),
            commands,
        })
    }

    /// # Safety
    /// The instance must belong to this loader and remain live while loading its commands
    pub unsafe fn load_instance(
        &self,
        instance: VkInstance,
    ) -> Result<InstanceCommands, LoadError> {
        let commands = unsafe { CmdsInstance::load(&self.library, instance)? };
        Ok(InstanceCommands {
            library: self.library.clone(),
            commands,
        })
    }
}

impl Deref for Loader {
    type Target = CmdsGlobal;

    fn deref(&self) -> &Self::Target {
        &self.commands
    }
}

#[derive(Clone)]
pub struct InstanceCommands {
    library: Arc<libloading::Library>,
    commands: CmdsInstance,
}

impl InstanceCommands {
    /// # Safety
    /// The device must belong to this instance and remain live while loading its commands
    pub unsafe fn load_device(&self, device: VkDevice) -> Result<DeviceCommands, LoadError> {
        let get_device_proc_addr =
            self.commands
                .vkGetDeviceProcAddr
                .ok_or(LoadError::MissingBootstrap {
                    name: c"vkGetDeviceProcAddr",
                })?;
        let commands = unsafe { CmdsDevice::from_proc_addr(get_device_proc_addr, device) };
        Ok(DeviceCommands {
            _library: self.library.clone(),
            commands,
        })
    }
}

impl Deref for InstanceCommands {
    type Target = CmdsInstance;

    fn deref(&self) -> &Self::Target {
        &self.commands
    }
}

#[derive(Clone)]
pub struct DeviceCommands {
    _library: Arc<libloading::Library>,
    commands: CmdsDevice,
}

impl Deref for DeviceCommands {
    type Target = CmdsDevice;

    fn deref(&self) -> &Self::Target {
        &self.commands
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::error::Error;

    #[test]
    fn library_errors_preserve_the_path_and_cause() {
        let path = std::env::temp_dir()
            .join(format!("volcanic-missing-{}", std::process::id()))
            .join("loader");
        let error = match unsafe { Loader::from_path(&path) } {
            Ok(_) => panic!("unexpected loader at missing path"),
            Err(error) => error,
        };
        assert!(matches!(&error, LoadError::Library { path: actual, .. } if actual == &path));
        assert!(error.to_string().contains(&path.display().to_string()));
        assert!(error.source().is_some());
    }

    #[test]
    fn owned_commands_can_be_shared_across_threads() {
        fn shared<T: Send + Sync + Clone>() {}
        shared::<Loader>();
        shared::<InstanceCommands>();
        shared::<DeviceCommands>();
    }

    #[test]
    #[ignore = "requires a Vulkan loader"]
    fn instance_commands_keep_the_library_loaded() {
        use crate::{
            VK_API_VERSION_1_0, VkApplicationInfo, VkInstanceCreateFlags, VkInstanceCreateInfo,
            VkResult, VkStructureType,
        };
        use std::ptr::{null, null_mut};

        unsafe {
            let loader = Loader::new().unwrap();
            let library = Arc::downgrade(&loader.library);
            let application = VkApplicationInfo {
                sType: VkStructureType::ApplicationInfo,
                pNext: null(),
                pApplicationName: c"Volcanic loader test".as_ptr(),
                applicationVersion: 0,
                pEngineName: null(),
                engineVersion: 0,
                apiVersion: VK_API_VERSION_1_0,
            };
            let info = VkInstanceCreateInfo {
                sType: VkStructureType::InstanceCreateInfo,
                pNext: null(),
                flags: VkInstanceCreateFlags::empty(),
                pApplicationInfo: &application,
                enabledLayerCount: 0,
                ppEnabledLayerNames: null(),
                enabledExtensionCount: 0,
                ppEnabledExtensionNames: null(),
            };
            let mut instance = null_mut();
            assert_eq!(
                loader.vkCreateInstance(&info, null(), &mut instance),
                VkResult::Success
            );
            let commands = loader.load_instance(instance).unwrap();
            let clone = commands.clone();
            drop(loader);
            drop(commands);
            assert!(library.upgrade().is_some());
            clone.vkDestroyInstance(instance, null());
            drop(clone);
            assert!(library.upgrade().is_none());
        }
    }
}