use crate::{
error::{
draw::{ShaderError, VulkanError},
textures::*,
},
prelude::{InstanceData, Texture, Vertex as GameVertex},
};
use anyhow::{Error, Result};
use derive_builder::Builder;
use std::sync::Arc;
use vulkano::{
descriptor_set::{PersistentDescriptorSet, WriteDescriptorSet},
pipeline::{
graphics::{
color_blend::{AttachmentBlend, ColorBlendAttachmentState, ColorBlendState},
input_assembly::{InputAssemblyState, PrimitiveTopology},
multisample::MultisampleState,
rasterization::RasterizationState,
vertex_input::{Vertex, VertexDefinition},
viewport::ViewportState,
GraphicsPipelineCreateInfo,
},
layout::PipelineDescriptorSetLayoutCreateInfo,
DynamicState, GraphicsPipeline, Pipeline, PipelineLayout, PipelineShaderStageCreateInfo,
},
render_pass::Subpass,
shader::{spirv::bytes_to_words, ShaderModule, ShaderModuleCreateInfo},
};
use super::RESOURCES;
#[derive(Debug, Clone, Copy)]
pub enum Topology {
TriangleList,
TriangleStrip,
LineList,
LineStrip,
PointList,
}
#[derive(Clone, PartialEq)]
pub struct Material {
pub(crate) pipeline: Arc<GraphicsPipeline>,
pub(crate) instanced: bool,
pub(crate) descriptor: Option<Arc<PersistentDescriptorSet>>,
pub(crate) texture: Option<Texture>,
pub(crate) layer: u32,
}
impl std::fmt::Debug for Material {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Material")
.field("instanced", &self.instanced)
.field("texture", &self.texture)
.field("layer", &self.layer)
.finish()
}
}
impl Material {
pub fn new_with_shaders(
settings: MaterialSettings,
shaders: &Shaders,
instanced: bool,
writes: Vec<WriteDescriptorSet>,
) -> Result<Self, VulkanError> {
let vs = &shaders.vertex;
let fs = &shaders.fragment;
let vertex = vs
.entry_point(&shaders.entry_point)
.ok_or(VulkanError::ShaderError)?;
let fragment = fs
.entry_point(&shaders.entry_point)
.ok_or(VulkanError::ShaderError)?;
let topology: PrimitiveTopology = match settings.topology {
Topology::TriangleList => PrimitiveTopology::TriangleList,
Topology::TriangleStrip => PrimitiveTopology::TriangleStrip,
Topology::LineList => PrimitiveTopology::LineList,
Topology::LineStrip => PrimitiveTopology::LineStrip,
Topology::PointList => PrimitiveTopology::PointList,
};
let resources = &RESOURCES;
let loader = resources.loader().lock();
let vulkan = resources.vulkan();
let pipeline_cache = loader.pipeline_cache.clone();
let subpass = Subpass::from(vulkan.render_pass.clone(), 0)
.ok_or(VulkanError::Other(Error::msg("Failed to make subpass.")))?;
let allocator = &loader.descriptor_set_allocator;
let input_assembly = InputAssemblyState {
topology,
..Default::default()
};
let stages = [
PipelineShaderStageCreateInfo::new(vertex.clone()),
PipelineShaderStageCreateInfo::new(fragment.clone()),
];
let layout = PipelineLayout::new(
vulkan.device.clone(),
PipelineDescriptorSetLayoutCreateInfo::from_stages(&stages)
.into_pipeline_layout_create_info(vulkan.device.clone())
.map_err(|e| VulkanError::Other(e.into()))?,
)?;
let vertex_input_state = if instanced {
[GameVertex::per_vertex(), InstanceData::per_instance()]
.definition(&vertex.info().input_interface)
.map_err(|e| VulkanError::Validated(e.into()))?
} else {
GameVertex::per_vertex()
.definition(&vertex.info().input_interface)
.map_err(|e| VulkanError::Validated(e.into()))?
};
let pipeline = GraphicsPipeline::new(
vulkan.device.clone(),
Some(pipeline_cache.clone()),
GraphicsPipelineCreateInfo {
stages: stages.into_iter().collect(),
vertex_input_state: Some(vertex_input_state),
input_assembly_state: Some(input_assembly),
viewport_state: Some(ViewportState::default()),
rasterization_state: Some(RasterizationState {
line_width: settings.line_width,
..RasterizationState::default()
}),
multisample_state: Some(MultisampleState::default()),
color_blend_state: Some(ColorBlendState::with_attachment_states(
subpass.num_color_attachments(),
ColorBlendAttachmentState {
blend: Some(AttachmentBlend::alpha()),
..Default::default()
},
)),
dynamic_state: [DynamicState::Viewport].into_iter().collect(),
subpass: Some(subpass.into()),
..GraphicsPipelineCreateInfo::layout(layout)
},
)
.map_err(VulkanError::Validated)?;
let descriptor = if !writes.is_empty() {
Some(PersistentDescriptorSet::new(
allocator,
pipeline
.layout()
.set_layouts()
.get(2) .ok_or(VulkanError::Other(Error::msg(
"Failed to get the second set of the pipeline layout.",
)))?
.clone(),
writes,
[],
)?)
} else {
None
};
Ok(Self {
pipeline,
descriptor,
instanced,
layer: settings.initial_layer,
texture: settings.texture,
})
}
pub fn new(settings: MaterialSettings) -> Result<Material, VulkanError> {
let resources = &RESOURCES;
let shaders = resources.vulkan().clone().default_shaders;
Self::new_with_shaders(settings, &shaders, false, vec![])
}
pub fn new_instanced(settings: MaterialSettings) -> Result<Material, VulkanError> {
let resources = &RESOURCES;
let shaders = resources.vulkan().clone().default_instance_shaders;
Self::new_with_shaders(settings, &shaders, true, vec![])
}
pub fn new_default_textured(texture: &Texture) -> Material {
let resources = &RESOURCES;
let default = if texture.layers() == 1 {
resources.vulkan().textured_material.clone()
} else {
resources.vulkan().texture_array_material.clone()
};
Material {
texture: Some(texture.clone()),
..default
}
}
pub fn new_default_textured_instance(texture: &Texture) -> Material {
let resources = &RESOURCES;
let default = if texture.layers() == 1 {
resources.vulkan().textured_instance_material.clone()
} else {
resources.vulkan().texture_array_instance_material.clone()
};
Material {
texture: Some(texture.clone()),
..default
}
}
}
impl Material {
pub unsafe fn write(&mut self, descriptor: Vec<WriteDescriptorSet>) -> Result<()> {
let resources = &RESOURCES;
let loader = resources.loader().lock();
self.descriptor = Some(PersistentDescriptorSet::new(
&loader.descriptor_set_allocator,
self.pipeline
.layout()
.set_layouts()
.get(1)
.ok_or(Error::msg(
"Could not obtain the second set layout of this write.",
))?
.clone(),
descriptor,
[],
)?);
Ok(())
}
pub fn set_layer(&mut self, id: u32) -> Result<(), TextureError> {
if let Some(texture) = &self.texture {
if id > texture.layers() - 1 {
return Err(TextureError::Layer(format!(
"Given: {}, Highest: {}",
id,
texture.layers() - 1
)));
}
} else {
return Err(TextureError::NoTexture);
}
self.layer = id;
Ok(())
}
pub fn layer(&self) -> u32 {
self.layer
}
pub fn next_frame(&mut self) -> Result<(), TextureError> {
if let Some(texture) = &self.texture {
if texture.layers() <= self.layer + 1 {
return Err(TextureError::Layer(
"You are already at the last frame.".to_string(),
));
}
} else {
return Err(TextureError::NoTexture);
}
self.layer += 1;
Ok(())
}
pub fn last_frame(&mut self) -> Result<(), TextureError> {
if self.texture.is_some() {
if self.layer == 0 {
return Err(TextureError::Layer(
"You are already on the first frame".to_string(),
));
}
} else {
return Err(TextureError::NoTexture);
}
self.layer -= 1;
Ok(())
}
pub fn texture(&self) -> Option<Texture> {
self.texture.clone()
}
pub fn set_texture(&mut self, texture: Option<Texture>) {
self.texture = texture;
}
}
#[derive(Builder, Clone, Debug)]
pub struct MaterialSettings {
#[builder(setter(into), default = "Topology::TriangleList")]
pub topology: Topology,
#[builder(setter(into), default = "1.0")]
pub line_width: f32,
#[builder(setter(into), default = "None")]
pub texture: Option<Texture>,
#[builder(setter(into), default = "0")]
pub initial_layer: u32,
}
#[derive(Clone, Debug, PartialEq)]
pub struct Shaders {
pub(crate) vertex: Arc<ShaderModule>,
pub(crate) fragment: Arc<ShaderModule>,
entry_point: Box<str>,
}
impl Shaders {
pub unsafe fn from_bytes(
vertex_bytes: &[u8],
fragment_bytes: &[u8],
entry_point: &str,
) -> Result<Self, ShaderError> {
let resources = &RESOURCES;
let device = resources.vulkan().clone().device;
let vertex_words = bytes_to_words(vertex_bytes)?;
let fragment_words = bytes_to_words(fragment_bytes)?;
let vertex: Arc<ShaderModule> = unsafe {
ShaderModule::new(device.clone(), ShaderModuleCreateInfo::new(&vertex_words))?
};
let fragment: Arc<ShaderModule> = unsafe {
ShaderModule::new(device.clone(), ShaderModuleCreateInfo::new(&fragment_words))?
};
vertex
.entry_point(entry_point)
.ok_or(ShaderError::ShaderEntryPoint)?;
fragment
.entry_point(entry_point)
.ok_or(ShaderError::ShaderEntryPoint)?;
Ok(Self {
vertex,
fragment,
entry_point: entry_point.into(),
})
}
pub fn from_modules(
vertex: Arc<ShaderModule>,
fragment: Arc<ShaderModule>,
entry_point: impl Into<Box<str>>,
) -> Self {
Self {
vertex,
fragment,
entry_point: entry_point.into(),
}
}
}