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 / client / src / gpu / device.rs

use std::ffi::{CStr, c_char, c_void};
use std::mem::ManuallyDrop;
use std::ptr::{null, null_mut};
use std::sync::Arc;

use volcanic::loader::DeviceCommands;
use volcanic::*;
use volcanic_mem::{Allocator, AllocatorConfig};

use super::{Error, Instance, MAX_PUSH_SIZE, Result, Surface, check, create, zeroed};

const OPTIONAL_EXTENSIONS: [&CStr; 2] = [
    VK_EXT_DESCRIPTOR_HEAP_EXTENSION_NAME,
    VK_EXT_MEMORY_BUDGET_EXTENSION_NAME,
];

/// `VK_EXT_present_timing` and the device extensions it depends on.
const PRESENT_TIMING_EXTENSIONS: [&CStr; 3] = [
    VK_EXT_PRESENT_TIMING_EXTENSION_NAME,
    VK_KHR_PRESENT_ID_2_EXTENSION_NAME,
    VK_KHR_CALIBRATED_TIMESTAMPS_EXTENSION_NAME,
];

/// `VK_KHR_present_mode_fifo_latest_ready` and the EXT it was promoted from, preferred first.
const FIFO_LATEST_READY_EXTENSIONS: [&CStr; 2] = [
    VK_KHR_PRESENT_MODE_FIFO_LATEST_READY_EXTENSION_NAME,
    VK_EXT_PRESENT_MODE_FIFO_LATEST_READY_EXTENSION_NAME,
];

pub struct Device {
    pub(crate) handle: VkDevice,
    pub(crate) commands: DeviceCommands,
    pub(crate) physical: VkPhysicalDevice,
    pub(crate) queue: VkQueue,
    pub(crate) queue_family: u32,
    pub(crate) allocator: ManuallyDrop<Allocator>,
    /// Whether `VK_EXT_descriptor_heap` is enabled. `heap_properties` is zeroed otherwise.
    pub descriptor_heap: bool,
    pub(crate) heap_properties: VkPhysicalDeviceDescriptorHeapPropertiesEXT,
    /// Whether `VK_EXT_present_timing` is enabled with `presentTiming` and `presentId2`.
    pub(crate) present_timing: bool,
    pub(crate) present_at_absolute_time: bool,
    /// Whether `VK_PRESENT_MODE_FIFO_LATEST_READY_KHR` may be used.
    pub(crate) fifo_latest_ready: bool,
    pub(crate) instance: Arc<Instance>,
    pub(crate) name: String,
}

// SAFETY: Vulkan device handles may be used from any thread. The queue is externally
// synchronized, which holds because only the render thread submits.
unsafe impl Send for Device {}
unsafe impl Sync for Device {}

impl Device {
    pub fn new(instance: &Arc<Instance>, surface: &Surface) -> Result<Arc<Self>> {
        let commands = &instance.commands;
        let physical_devices = super::enumerate(|count, items| unsafe {
            commands.vkEnumeratePhysicalDevices(instance.handle, count, items)
        })?;

        let (physical, queue_family, properties, mut extensions) = physical_devices
            .into_iter()
            .filter_map(|physical| {
                let properties = unsafe {
                    let mut properties = zeroed();
                    commands.vkGetPhysicalDeviceProperties(physical, &mut properties);
                    properties
                };
                let available = super::enumerate(|count, items| unsafe {
                    commands.vkEnumerateDeviceExtensionProperties(physical, null(), count, items)
                })
                .ok()?;
                let supports = |wanted: &CStr| {
                    available
                        .iter()
                        .any(|e| unsafe { CStr::from_ptr(e.extensionName.as_ptr()) } == wanted)
                };
                let maintenance5 = (properties.apiVersion < VK_API_VERSION_1_4)
                    .then_some(VK_KHR_MAINTENANCE_5_EXTENSION_NAME);
                let required = [VK_KHR_SWAPCHAIN_EXTENSION_NAME]
                    .into_iter()
                    .chain(maintenance5);
                if properties.apiVersion < VK_API_VERSION_1_3
                    || properties.limits.maxPushConstantsSize < MAX_PUSH_SIZE
                    || !required.clone().all(supports)
                    || !required_features(instance, physical)
                {
                    return None;
                }
                let family = graphics_present_family(instance, physical, surface)?;
                let optional = OPTIONAL_EXTENSIONS.into_iter().filter(|e| supports(e));
                let timing = instance.surface_capabilities2
                    && PRESENT_TIMING_EXTENSIONS.into_iter().all(supports);
                let timing = PRESENT_TIMING_EXTENSIONS.into_iter().filter(|_| timing);
                let latest_ready = FIFO_LATEST_READY_EXTENSIONS
                    .into_iter()
                    .find(|e| supports(e));
                let extensions: Vec<&CStr> = (required.chain(optional).chain(timing))
                    .chain(latest_ready)
                    .collect();
                Some((physical, family, properties, extensions))
            })
            .max_by_key(|(_, _, properties, extensions)| {
                (
                    properties.deviceType == VkPhysicalDeviceType::DiscreteGpu,
                    extensions.contains(&VK_EXT_DESCRIPTOR_HEAP_EXTENSION_NAME),
                )
            })
            .ok_or(Error::NoSuitableDevice)?;
        let supported = optional_features(instance, physical, &extensions);
        extensions.retain(|e| {
            (supported.present_timing || !PRESENT_TIMING_EXTENSIONS.contains(e))
                && (supported.fifo_latest_ready || !FIFO_LATEST_READY_EXTENSIONS.contains(e))
        });
        let OptionalFeatures {
            present_timing,
            present_at_absolute_time,
            fifo_latest_ready,
        } = supported;
        let core_1_4 = properties.apiVersion >= VK_API_VERSION_1_4;
        let descriptor_heap = extensions.contains(&VK_EXT_DESCRIPTOR_HEAP_EXTENSION_NAME);
        let memory_budget = extensions.contains(&VK_EXT_MEMORY_BUDGET_EXTENSION_NAME);

        let name = unsafe { CStr::from_ptr(properties.deviceName.as_ptr()) }
            .to_string_lossy()
            .into_owned();

        let mut heap_features = VkPhysicalDeviceDescriptorHeapFeaturesEXT {
            sType: VkStructureType::PhysicalDeviceDescriptorHeapFeaturesExt,
            descriptorHeap: VK_TRUE,
            ..zeroed()
        };
        let heap_next = if descriptor_heap {
            (&raw mut heap_features).cast()
        } else {
            null_mut()
        };
        let mut features14 = VkPhysicalDeviceVulkan14Features {
            sType: VkStructureType::PhysicalDeviceVulkan14Features,
            pNext: heap_next,
            maintenance5: VK_TRUE,
            maintenance6: VK_TRUE,
            ..zeroed()
        };
        let mut maintenance5 = VkPhysicalDeviceMaintenance5Features {
            sType: VkStructureType::PhysicalDeviceMaintenance5Features,
            pNext: heap_next,
            maintenance5: VK_TRUE,
        };
        let mut features13 = VkPhysicalDeviceVulkan13Features {
            sType: VkStructureType::PhysicalDeviceVulkan13Features,
            pNext: if core_1_4 {
                (&raw mut features14).cast()
            } else {
                (&raw mut maintenance5).cast()
            },
            dynamicRendering: VK_TRUE,
            synchronization2: VK_TRUE,
            maintenance4: VK_TRUE,
            shaderDemoteToHelperInvocation: VK_TRUE,
            ..zeroed()
        };
        let mut features12 = VkPhysicalDeviceVulkan12Features {
            sType: VkStructureType::PhysicalDeviceVulkan12Features,
            pNext: (&raw mut features13).cast(),
            bufferDeviceAddress: VK_TRUE,
            drawIndirectCount: VK_TRUE,
            timelineSemaphore: VK_TRUE,
            runtimeDescriptorArray: VK_TRUE,
            shaderSampledImageArrayNonUniformIndexing: VK_TRUE,
            scalarBlockLayout: VK_TRUE,
            ..zeroed()
        };
        let mut features11 = VkPhysicalDeviceVulkan11Features {
            sType: VkStructureType::PhysicalDeviceVulkan11Features,
            pNext: (&raw mut features12).cast(),
            shaderDrawParameters: VK_TRUE,
            ..zeroed()
        };
        let mut next: *mut c_void = (&raw mut features11).cast();
        let mut timing_features = VkPhysicalDevicePresentTimingFeaturesEXT {
            sType: VkStructureType::PhysicalDevicePresentTimingFeaturesExt,
            pNext: next,
            presentTiming: VK_TRUE,
            presentAtAbsoluteTime: present_at_absolute_time.into(),
            ..zeroed()
        };
        let mut id2_features = VkPhysicalDevicePresentId2FeaturesKHR {
            sType: VkStructureType::PhysicalDevicePresentId2FeaturesKhr,
            pNext: (&raw mut timing_features).cast(),
            presentId2: VK_TRUE,
        };
        if present_timing {
            next = (&raw mut id2_features).cast();
        }
        let mut latest_ready_features = VkPhysicalDevicePresentModeFifoLatestReadyFeaturesKHR {
            sType: VkStructureType::PhysicalDevicePresentModeFifoLatestReadyFeaturesKhr,
            pNext: next,
            presentModeFifoLatestReady: VK_TRUE,
        };
        if fifo_latest_ready {
            next = (&raw mut latest_ready_features).cast();
        }
        let features = VkPhysicalDeviceFeatures2 {
            sType: VkStructureType::PhysicalDeviceFeatures2,
            pNext: next,
            features: VkPhysicalDeviceFeatures {
                multiDrawIndirect: VK_TRUE,
                drawIndirectFirstInstance: VK_TRUE,
                fragmentStoresAndAtomics: VK_TRUE,
                ..zeroed()
            },
        };

        let priority = 1.0_f32;
        let queue_info = VkDeviceQueueCreateInfo {
            sType: VkStructureType::DeviceQueueCreateInfo,
            queueFamilyIndex: queue_family,
            queueCount: 1,
            pQueuePriorities: &priority,
            ..zeroed()
        };
        let extensions: Vec<*const c_char> = extensions.iter().map(|e| e.as_ptr()).collect();
        let info = VkDeviceCreateInfo {
            sType: VkStructureType::DeviceCreateInfo,
            pNext: (&raw const features).cast(),
            queueCreateInfoCount: 1,
            pQueueCreateInfos: &queue_info,
            enabledExtensionCount: extensions.len() as u32,
            ppEnabledExtensionNames: extensions.as_ptr(),
            ..zeroed()
        };
        let handle =
            create(|handle| unsafe { commands.vkCreateDevice(physical, &info, null(), handle) })?;
        let device_commands = unsafe { instance.commands.load_device(handle)? };

        let mut queue = null_mut();
        unsafe { device_commands.vkGetDeviceQueue(handle, queue_family, 0, &mut queue) };

        let config = AllocatorConfig {
            memory_budget,
            descriptor_heap,
            ..AllocatorConfig::default()
        };
        // SAFETY: the device enables Vulkan 1.3, `bufferDeviceAddress` and `maintenance5`, plus
        // `VK_EXT_descriptor_heap` and `descriptorHeap` with `descriptor_heap`, and `Drop`
        // destroys the allocator first.
        let allocator =
            unsafe { Allocator::new(commands, physical, &device_commands, handle, &config) }
                .inspect_err(|_| unsafe { device_commands.vkDestroyDevice(handle, null()) })?;

        let mut heap_properties = VkPhysicalDeviceDescriptorHeapPropertiesEXT {
            sType: VkStructureType::PhysicalDeviceDescriptorHeapPropertiesExt,
            ..zeroed()
        };
        if descriptor_heap {
            let mut properties2 = VkPhysicalDeviceProperties2 {
                sType: VkStructureType::PhysicalDeviceProperties2,
                pNext: (&raw mut heap_properties).cast::<c_void>(),
                ..zeroed()
            };
            unsafe { commands.vkGetPhysicalDeviceProperties2(physical, &mut properties2) };
            heap_properties.pNext = null_mut();
        }

        tracing::info!(
            "using {name}, descriptor heaps supported: {descriptor_heap}, present timing supported: {present_timing}"
        );
        Ok(Arc::new(Self {
            handle,
            commands: device_commands,
            physical,
            queue,
            queue_family,
            allocator: ManuallyDrop::new(allocator),
            descriptor_heap,
            heap_properties,
            present_timing,
            present_at_absolute_time,
            fifo_latest_ready,
            instance: instance.clone(),
            name,
        }))
    }

    pub fn wait_idle(&self) -> Result<()> {
        check(unsafe { self.commands.vkDeviceWaitIdle(self.handle) })
    }

    pub fn create_semaphore(&self) -> Result<VkSemaphore> {
        let info = VkSemaphoreCreateInfo {
            sType: VkStructureType::SemaphoreCreateInfo,
            ..zeroed()
        };
        create(|semaphore| unsafe {
            self.commands
                .vkCreateSemaphore(self.handle, &info, null(), semaphore)
        })
    }

    pub fn create_fence(&self, signaled: bool) -> Result<VkFence> {
        let info = VkFenceCreateInfo {
            sType: VkStructureType::FenceCreateInfo,
            flags: if signaled {
                VkFenceCreateFlags::SIGNALED
            } else {
                VkFenceCreateFlags::empty()
            },
            ..zeroed()
        };
        create(|fence| unsafe {
            self.commands
                .vkCreateFence(self.handle, &info, null(), fence)
        })
    }

    pub fn create_command_pool(&self, transient: bool) -> Result<VkCommandPool> {
        let info = VkCommandPoolCreateInfo {
            sType: VkStructureType::CommandPoolCreateInfo,
            flags: if transient {
                VkCommandPoolCreateFlags::TRANSIENT
            } else {
                VkCommandPoolCreateFlags::empty()
            },
            queueFamilyIndex: self.queue_family,
            ..zeroed()
        };
        create(|pool| unsafe {
            self.commands
                .vkCreateCommandPool(self.handle, &info, null(), pool)
        })
    }

    pub fn allocate_command_buffer(&self, pool: VkCommandPool) -> Result<VkCommandBuffer> {
        let info = VkCommandBufferAllocateInfo {
            sType: VkStructureType::CommandBufferAllocateInfo,
            commandPool: pool,
            level: VkCommandBufferLevel::Primary,
            commandBufferCount: 1,
            ..zeroed()
        };
        create(|buffer| unsafe {
            self.commands
                .vkAllocateCommandBuffers(self.handle, &info, buffer)
        })
    }

    /// Records and synchronously executes one-off commands, such as initial uploads.
    pub fn submit_now(&self, record: impl FnOnce(VkCommandBuffer)) -> Result<()> {
        let pool = self.create_command_pool(true)?;
        let result = (|| {
            let command_buffer = self.allocate_command_buffer(pool)?;
            let begin = VkCommandBufferBeginInfo {
                sType: VkStructureType::CommandBufferBeginInfo,
                flags: VkCommandBufferUsageFlags::ONE_TIME_SUBMIT,
                ..zeroed()
            };
            check(unsafe { self.commands.vkBeginCommandBuffer(command_buffer, &begin) })?;
            record(command_buffer);
            check(unsafe { self.commands.vkEndCommandBuffer(command_buffer) })?;
            let command_info = VkCommandBufferSubmitInfo {
                sType: VkStructureType::CommandBufferSubmitInfo,
                commandBuffer: command_buffer,
                ..zeroed()
            };
            let submit = VkSubmitInfo2 {
                sType: VkStructureType::SubmitInfo2,
                commandBufferInfoCount: 1,
                pCommandBufferInfos: &command_info,
                ..zeroed()
            };
            check(unsafe {
                self.commands
                    .vkQueueSubmit2(self.queue, 1, &submit, VK_NULL_HANDLE)
            })?;
            check(unsafe { self.commands.vkQueueWaitIdle(self.queue) })
        })();
        unsafe {
            self.commands
                .vkDestroyCommandPool(self.handle, pool, null())
        };
        result
    }
}

impl Drop for Device {
    fn drop(&mut self) {
        unsafe {
            self.commands.vkDeviceWaitIdle(self.handle);
            ManuallyDrop::drop(&mut self.allocator);
            self.commands.vkDestroyDevice(self.handle, null());
        }
    }
}

/// Whether the device supports the features GPU-driven terrain culling needs.
fn required_features(instance: &Instance, physical: VkPhysicalDevice) -> bool {
    let mut features12 = VkPhysicalDeviceVulkan12Features {
        sType: VkStructureType::PhysicalDeviceVulkan12Features,
        ..zeroed()
    };
    let mut features = VkPhysicalDeviceFeatures2 {
        sType: VkStructureType::PhysicalDeviceFeatures2,
        pNext: (&raw mut features12).cast(),
        ..zeroed()
    };
    unsafe {
        instance
            .commands
            .vkGetPhysicalDeviceFeatures2(physical, &mut features)
    };
    let core = &features.features;
    [
        core.multiDrawIndirect,
        core.drawIndirectFirstInstance,
        core.fragmentStoresAndAtomics,
        features12.drawIndirectCount,
    ]
    .iter()
    .all(|&supported| supported == VK_TRUE)
}

struct OptionalFeatures {
    /// `presentTiming` with `presentId2`.
    present_timing: bool,
    present_at_absolute_time: bool,
    fifo_latest_ready: bool,
}

/// The optional features the device supports for the chosen `extensions`.
fn optional_features(
    instance: &Instance,
    physical: VkPhysicalDevice,
    extensions: &[&CStr],
) -> OptionalFeatures {
    let mut timing = VkPhysicalDevicePresentTimingFeaturesEXT {
        sType: VkStructureType::PhysicalDevicePresentTimingFeaturesExt,
        ..zeroed()
    };
    let mut id2 = VkPhysicalDevicePresentId2FeaturesKHR {
        sType: VkStructureType::PhysicalDevicePresentId2FeaturesKhr,
        pNext: (&raw mut timing).cast(),
        ..zeroed()
    };
    let mut next: *mut c_void = null_mut();
    if extensions.contains(&VK_EXT_PRESENT_TIMING_EXTENSION_NAME) {
        next = (&raw mut id2).cast();
    }
    let mut latest_ready = VkPhysicalDevicePresentModeFifoLatestReadyFeaturesKHR {
        sType: VkStructureType::PhysicalDevicePresentModeFifoLatestReadyFeaturesKhr,
        pNext: next,
        ..zeroed()
    };
    if FIFO_LATEST_READY_EXTENSIONS
        .iter()
        .any(|e| extensions.contains(e))
    {
        next = (&raw mut latest_ready).cast();
    }
    let mut features = VkPhysicalDeviceFeatures2 {
        sType: VkStructureType::PhysicalDeviceFeatures2,
        pNext: next,
        ..zeroed()
    };
    unsafe {
        instance
            .commands
            .vkGetPhysicalDeviceFeatures2(physical, &mut features)
    };
    let present_timing = timing.presentTiming == VK_TRUE && id2.presentId2 == VK_TRUE;
    OptionalFeatures {
        present_timing,
        present_at_absolute_time: present_timing && timing.presentAtAbsoluteTime == VK_TRUE,
        fifo_latest_ready: latest_ready.presentModeFifoLatestReady == VK_TRUE,
    }
}

fn graphics_present_family(
    instance: &Instance,
    physical: VkPhysicalDevice,
    surface: &Surface,
) -> Option<u32> {
    let commands = &instance.commands;
    let families = super::enumerate(|count, items: *mut VkQueueFamilyProperties| {
        unsafe { commands.vkGetPhysicalDeviceQueueFamilyProperties(physical, count, items) };
        VkResult::Success
    })
    .ok()?;
    families.iter().enumerate().find_map(|(index, family)| {
        let index = index as u32;
        let mut present = VK_FALSE;
        unsafe {
            commands.vkGetPhysicalDeviceSurfaceSupportKHR(
                physical,
                index,
                surface.handle,
                &mut present,
            );
        }
        (family.queueFlags.contains(VkQueueFlags::GRAPHICS) && present == VK_TRUE).then_some(index)
    })
}