crystal-vk 0.1.1

Graphics wrapper for Vulkan
Documentation
use std::{error::Error, iter::zip, sync::Arc};

use ash::vk;

use crate::{
    pipeline::{
        Pipeline, PipelineInfo, attribute::Attribute, descriptor::layout::PipelineLayout,
        shader::Shader,
    },
    render::RenderTarget,
};

pub struct GraphicsPipelineInfo {
    pub viewports: Vec<vk::Viewport>,
    pub scissors: Vec<vk::Rect2D>,
    pub line_width: f32,
    pub polygon_mode: vk::PolygonMode,
    pub cull_mode: vk::CullModeFlags,
    pub front_face: vk::FrontFace,
    pub vertex_attributes: Vec<Attribute>,
}

impl Default for GraphicsPipelineInfo {
    fn default() -> Self {
        Self {
            viewports: Vec::with_capacity(0),
            scissors: Vec::with_capacity(0),
            line_width: 1.,
            polygon_mode: vk::PolygonMode::FILL,
            cull_mode: vk::CullModeFlags::BACK,
            front_face: vk::FrontFace::COUNTER_CLOCKWISE,
            vertex_attributes: vec![],
        }
    }
}

impl Pipeline {
    pub fn new_graphics(
        pipeline_layout: Arc<PipelineLayout>,
        render_target: Arc<RenderTarget>,
        shaders: Vec<Arc<Shader>>,
        mut pipeline_info: GraphicsPipelineInfo,
        cache: Option<&[u8]>,
    ) -> Result<Arc<Pipeline>, Box<dyn Error>> {
        if shaders.is_empty() {
            return Err("no shaders specified".into());
        }

        let mut stages = Vec::new();

        for shader in shaders.clone() {
            let stage = vk::PipelineShaderStageCreateInfo {
                stage: shader.stage,
                module: shader.handle,
                p_name: shader.entry_point.as_ptr(),
                ..Default::default()
            };

            stages.push(stage);
        }

        let attributes = pipeline_info.vertex_attributes.clone();

        let binding_descriptions = &[vk::VertexInputBindingDescription::default()
            .binding(0)
            .stride(attributes.iter().map(|a| a.size as u32).sum())
            .input_rate(vk::VertexInputRate::VERTEX)];

        let mut attribute_descriptions = vec![];

        for (location, attribute) in zip(0..attributes.len() as u32, attributes) {
            let attribute_description = vk::VertexInputAttributeDescription::default()
                .binding(0)
                .location(location)
                .format(match attribute.size {
                    4 => vk::Format::R32_SFLOAT,
                    8 => vk::Format::R32G32_SFLOAT,
                    12 => vk::Format::R32G32B32_SFLOAT,
                    16 => vk::Format::R32G32B32A32_SFLOAT,
                    _ => vk::Format::R32G32B32_SFLOAT,
                })
                .offset(attribute.offset as u32);
            attribute_descriptions.push(attribute_description);
        }

        let vertex_input_info = vk::PipelineVertexInputStateCreateInfo::default()
            .vertex_binding_descriptions(binding_descriptions)
            .vertex_attribute_descriptions(&attribute_descriptions);

        let input_assembly = vk::PipelineInputAssemblyStateCreateInfo::default()
            .topology(vk::PrimitiveTopology::TRIANGLE_LIST)
            .primitive_restart_enable(false);

        let dynamic_states = &[vk::DynamicState::VIEWPORT, vk::DynamicState::SCISSOR];

        let dynamic_state =
            vk::PipelineDynamicStateCreateInfo::default().dynamic_states(dynamic_states);

        let target = render_target.clone();
        let framebuffer_lock = target.framebuffer.read().unwrap();

        let (viewports, scissors) =
            if pipeline_info.viewports.is_empty() || pipeline_info.scissors.is_empty() {
                let extent = framebuffer_lock.attachments[0].info.extent;
                (
                    vec![
                        vk::Viewport::default()
                            .width(extent[0] as f32)
                            .height(extent[1] as f32),
                    ],
                    vec![vk::Rect2D::default().extent(vk::Extent2D {
                        width: extent[0],
                        height: extent[1],
                    })],
                )
            } else {
                (
                    pipeline_info.viewports.clone(),
                    pipeline_info.scissors.clone(),
                )
            };

        pipeline_info.viewports = viewports;
        pipeline_info.scissors = scissors;

        let viewport_state = vk::PipelineViewportStateCreateInfo::default()
            .viewports(&pipeline_info.viewports)
            .scissors(&pipeline_info.scissors);

        let rasterizer = vk::PipelineRasterizationStateCreateInfo::default()
            .depth_clamp_enable(false)
            .rasterizer_discard_enable(false)
            .polygon_mode(pipeline_info.polygon_mode)
            .line_width(pipeline_info.line_width)
            .cull_mode(pipeline_info.cull_mode)
            .front_face(pipeline_info.front_face)
            .depth_bias_enable(false);

        let multisampling = vk::PipelineMultisampleStateCreateInfo::default()
            .sample_shading_enable(false)
            .rasterization_samples(framebuffer_lock.render_pass.info.samples);

        let color_blend_attachment = vk::PipelineColorBlendAttachmentState::default()
            .color_write_mask(vk::ColorComponentFlags::RGBA)
            .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 = &[color_blend_attachment];

        let color_blending = vk::PipelineColorBlendStateCreateInfo::default()
            .logic_op_enable(false)
            .attachments(attachments);

        let depth_stencil_state = vk::PipelineDepthStencilStateCreateInfo::default()
            .depth_test_enable(true)
            .depth_write_enable(true)
            .depth_compare_op(vk::CompareOp::LESS)
            .depth_bounds_test_enable(false);

        let pipeline_create_info = vk::GraphicsPipelineCreateInfo::default()
            .stages(&stages)
            .vertex_input_state(&vertex_input_info)
            .input_assembly_state(&input_assembly)
            .viewport_state(&viewport_state)
            .rasterization_state(&rasterizer)
            .multisample_state(&multisampling)
            .color_blend_state(&color_blending)
            .dynamic_state(&dynamic_state)
            .depth_stencil_state(&depth_stencil_state)
            .layout(pipeline_layout.handle)
            .render_pass(framebuffer_lock.render_pass.handle);

        let mut cache_create_info = vk::PipelineCacheCreateInfo::default();
        if let Some(cache) = cache {
            cache_create_info = cache_create_info.initial_data(cache);
        }

        let cache = unsafe {
            pipeline_layout
                .device
                .handle
                .create_pipeline_cache(&cache_create_info, None)?
        };

        match unsafe {
            render_target.device.handle.create_graphics_pipelines(
                cache,
                &[pipeline_create_info],
                None,
            )
        } {
            Ok(pipelines) => Ok(Arc::new(Self {
                handle: pipelines[0],
                _info: PipelineInfo::Graphics(pipeline_info),
                bind_point: vk::PipelineBindPoint::GRAPHICS,
                pipeline_layout,
                _shaders: shaders,
                _render_target: Some(render_target),
                cache,
            })),
            Err(e) => Err(e.1.into()),
        }
    }
}