rotex-vulkan 0.1.1

A Vulkan backend for rotex_core
Documentation
use std::collections::HashMap;
use std::ffi::CString;

use ash::vk;

use super::VulkanBridge;
use crate::backend::vulkan::{
    CommandPool, DescriptorPool, DescriptorSetLayout, DeviceDescriptor, Fence,
    QueueCategory as BackendQueueCategory, QueueRequest as BackendQueueRequest, VulkanInstance,
};
use crate::core::InstanceOptions;
use crate::error::{Error, ErrorKind};
use rotex_types::{
    DeviceDescriptor as FrontendDeviceDescriptor, DeviceFeatures as FrontendDeviceFeatures,
    Extent2D as FrontendExtent2D, InstanceDescriptor as FrontendInstanceDescriptor,
    QueueCategory,
};

impl VulkanBridge {
    pub fn new(
        instance_descriptor: FrontendInstanceDescriptor,
        device_descriptor: FrontendDeviceDescriptor,
    ) -> Result<Self, Error> {
        let options = InstanceOptions {
            enable_validation: instance_descriptor.enable_validation,
            enable_debug_utils: instance_descriptor.enable_validation,
            ..Default::default()
        };
        let extension_names = instance_descriptor
            .required_instance_extensions
            .iter()
            .map(|name| {
                CString::new(name.as_str()).map_err(|_| {
                    Error::fatal(ErrorKind::Unsupported(
                        "Instance extension contains interior NUL byte",
                    ))
                })
            })
            .collect::<Result<Vec<_>, _>>()?;
        let extension_ptrs = extension_names
            .iter()
            .map(|name| name.as_ptr())
            .collect::<Vec<_>>();
        let instance = VulkanInstance::new(&options, &extension_ptrs)?;
        let backend_desc = DeviceDescriptor {
            enable_swapchain: device_descriptor.enable_swapchain,
            queues: device_descriptor.queues.into_iter().map(map_queue_request).collect(),
            required_features: map_device_features(device_descriptor.required_features),
        };
        let device = instance.request_device(backend_desc)?;
        let command_pool = CommandPool::new(device.raw())?;
        let mut command_buffers = command_pool.allocate_buffers(device.raw(), 1)?;
        let command_buffer = command_buffers.pop().expect("one command buffer");
        let in_flight_fence = Fence::new(device.raw(), true)?;
        let texture_layout_bindings = [vk::DescriptorSetLayoutBinding::default()
            .binding(0)
            .descriptor_type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER)
            .descriptor_count(1)
            .stage_flags(vk::ShaderStageFlags::FRAGMENT)];
        let texture_set_layout = DescriptorSetLayout::new(device.raw(), &texture_layout_bindings)?;
        let texture_pool_sizes = [vk::DescriptorPoolSize {
            ty: vk::DescriptorType::COMBINED_IMAGE_SAMPLER,
            descriptor_count: 4096,
        }];
        let texture_descriptor_pool = DescriptorPool::new(device.raw(), 4096, &texture_pool_sizes)?;
        let graphics_queue_index = device
            .raw()
            .queues()
            .iter()
            .find(|q| q.category == BackendQueueCategory::Graphics)
            .ok_or(Error::fatal(ErrorKind::NoCompatibleDevice))?
            .family_index;
        Ok(Self {
            instance,
            device,
            command_pool,
            command_buffer,
            in_flight_fence,
            graphics_queue_index,
            surface_state: None,
            meshes: HashMap::new(),
            materials: HashMap::new(),
            textures: HashMap::new(),
            default_texture: None,
            texture_descriptor_pool,
            texture_set_layout,
            material_pipelines: HashMap::new(),
            pipelines_by_material: HashMap::new(),
            vertex_layouts: HashMap::new(),
            next_mesh_id: 1,
            next_material_id: 1,
            next_texture_id: 1,
        })
    }
}

fn map_queue_request(req: rotex_types::QueueRequest) -> BackendQueueRequest {
    BackendQueueRequest {
        category: match req.category {
            QueueCategory::Graphics => BackendQueueCategory::Graphics,
            QueueCategory::Compute => BackendQueueCategory::Compute,
            QueueCategory::Transfer => BackendQueueCategory::Transfer,
        },
        count: req.count,
    }
}

fn map_device_features(features: FrontendDeviceFeatures) -> vk::PhysicalDeviceFeatures {
    let mut mapped = vk::PhysicalDeviceFeatures::default();
    mapped.sampler_anisotropy = features.sampler_anisotropy as u32;
    mapped.fill_mode_non_solid = features.fill_mode_non_solid as u32;
    mapped.wide_lines = features.wide_lines as u32;
    mapped
}

pub(super) fn to_vk_extent(extent: FrontendExtent2D) -> vk::Extent2D {
    let extent = extent.clamped();
    vk::Extent2D {
        width: extent.width,
        height: extent.height,
    }
}