use crate::{
asset::{define_new_resource, Resource, ResourceData, ResourceState},
core::{
algebra::{Matrix2, Matrix3, Matrix4, Vector2, Vector3, Vector4},
io::{self, FileLoadError},
sparse::AtomicIndex,
visitor::prelude::*,
},
lazy_static::lazy_static,
renderer::{
cache::{shader::ShaderSet, CacheEntry},
framework::framebuffer::DrawParameters,
},
};
use ron::Error;
use serde::Deserialize;
use std::{
borrow::Cow,
io::Cursor,
path::{Path, PathBuf},
};
pub const STANDARD_SHADER_SRC: &str = include_str!("standard/standard.shader");
pub const STANDARD_TERRAIN_SHADER_SRC: &str = include_str!("standard/terrain.shader");
#[derive(Default, Debug)]
pub struct ShaderState {
path: PathBuf,
pub definition: ShaderDefinition,
pub(in crate) cache_index: AtomicIndex<CacheEntry<ShaderSet>>,
}
impl Visit for ShaderState {
fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult {
visitor.enter_region(name)?;
self.path.visit("Path", visitor)?;
if visitor.is_reading() {
if self.path == Path::new("Standard") {
self.definition = ShaderDefinition::from_str(STANDARD_SHADER_SRC).unwrap();
} else if self.path == Path::new("StandardTerrain") {
self.definition = ShaderDefinition::from_str(STANDARD_TERRAIN_SHADER_SRC).unwrap();
}
}
visitor.leave_region()
}
}
#[derive(Deserialize, Debug, PartialEq, Clone, Copy, Visit)]
pub enum SamplerFallback {
White,
Normal,
Black,
}
impl Default for SamplerFallback {
fn default() -> Self {
Self::White
}
}
#[derive(Deserialize, Debug, PartialEq)]
pub enum PropertyKind {
Float(f32),
FloatArray(Vec<f32>),
Int(i32),
IntArray(Vec<i32>),
UInt(u32),
UIntArray(Vec<u32>),
Bool(bool),
Vector2(Vector2<f32>),
Vector2Array(Vec<Vector2<f32>>),
Vector3(Vector3<f32>),
Vector3Array(Vec<Vector3<f32>>),
Vector4(Vector4<f32>),
Vector4Array(Vec<Vector4<f32>>),
Matrix2(Matrix2<f32>),
Matrix2Array(Vec<Matrix2<f32>>),
Matrix3(Matrix3<f32>),
Matrix3Array(Vec<Matrix3<f32>>),
Matrix4(Matrix4<f32>),
Matrix4Array(Vec<Matrix4<f32>>),
Color {
r: u8,
g: u8,
b: u8,
a: u8,
},
Sampler {
default: Option<PathBuf>,
fallback: SamplerFallback,
},
}
impl Default for PropertyKind {
fn default() -> Self {
Self::Float(0.0)
}
}
#[derive(Default, Deserialize, Debug, PartialEq)]
pub struct PropertyDefinition {
pub name: String,
pub kind: PropertyKind,
}
#[derive(Default, Deserialize, Debug, PartialEq)]
pub struct RenderPassDefinition {
pub name: String,
pub draw_parameters: DrawParameters,
pub vertex_shader: String,
pub fragment_shader: String,
}
#[derive(Default, Deserialize, Debug, PartialEq)]
pub struct ShaderDefinition {
pub name: String,
pub passes: Vec<RenderPassDefinition>,
pub properties: Vec<PropertyDefinition>,
}
impl ShaderDefinition {
fn from_buf(buf: Vec<u8>) -> Result<Self, ShaderError> {
Ok(ron::de::from_reader(Cursor::new(buf))?)
}
fn from_str(str: &str) -> Result<Self, ShaderError> {
Ok(ron::de::from_str(str)?)
}
}
impl ShaderState {
pub(in crate) async fn from_file<P: AsRef<Path>>(path: P) -> Result<Self, ShaderError> {
let content = io::load_file(path.as_ref()).await?;
Ok(Self {
path: path.as_ref().to_owned(),
definition: ShaderDefinition::from_buf(content)?,
cache_index: Default::default(),
})
}
pub(in crate) fn from_str<P: AsRef<Path>>(str: &str, path: P) -> Result<Self, ShaderError> {
Ok(Self {
path: path.as_ref().to_owned(),
definition: ShaderDefinition::from_str(str)?,
cache_index: Default::default(),
})
}
}
impl ResourceData for ShaderState {
fn path(&self) -> Cow<Path> {
Cow::from(&self.path)
}
fn set_path(&mut self, path: PathBuf) {
self.path = path;
}
}
#[derive(Debug, thiserror::Error)]
pub enum ShaderError {
#[error("A file load error has occurred {0:?}")]
Io(FileLoadError),
#[error("A parsing error has occurred {0:?}")]
ParseError(ron::Error),
}
impl From<ron::Error> for ShaderError {
fn from(e: Error) -> Self {
Self::ParseError(e)
}
}
impl From<FileLoadError> for ShaderError {
fn from(e: FileLoadError) -> Self {
Self::Io(e)
}
}
define_new_resource!(
Shader<ShaderState, ShaderError>
);
impl Shader {
pub fn from_str<P: AsRef<Path>>(str: &str, path: P) -> Result<Self, ShaderError> {
Ok(Self(Resource::new(ResourceState::Ok(
ShaderState::from_str(str, path.as_ref())?,
))))
}
pub fn standard() -> Self {
STANDARD.clone()
}
pub fn standard_terrain() -> Self {
STANDARD_TERRAIN.clone()
}
pub fn standard_shaders() -> Vec<Shader> {
vec![Self::standard(), Self::standard_terrain()]
}
}
lazy_static! {
static ref STANDARD: Shader = Shader(Resource::new(ResourceState::Ok(
ShaderState::from_str(STANDARD_SHADER_SRC, "Standard").unwrap(),
)));
}
lazy_static! {
static ref STANDARD_TERRAIN: Shader = Shader(Resource::new(ResourceState::Ok(
ShaderState::from_str(STANDARD_TERRAIN_SHADER_SRC, "StandardTerrain").unwrap(),
)));
}
#[cfg(test)]
mod test {
use crate::material::shader::{
PropertyDefinition, PropertyKind, RenderPassDefinition, SamplerFallback, Shader,
ShaderDefinition,
};
#[test]
fn test_shader_load() {
let code = r##"
(
name: "TestShader",
properties: [
(
name: "diffuseTexture",
kind: Sampler(value: None, fallback: White),
),
],
passes: [
(
name: "GBuffer",
draw_parameters: DrawParameters(
cull_face: Some(Back),
color_write: ColorMask(
red: true,
green: true,
blue: true,
alpha: true,
),
depth_write: true,
stencil_test: None,
depth_test: true,
blend: None,
stencil_op: StencilOp(
fail: Keep,
zfail: Keep,
zpass: Keep,
write_mask: 0xFFFF_FFFF,
),
),
vertex_shader: "<CODE>",
fragment_shader: "<CODE>",
),
],
)
"##;
let shader = Shader::from_str(code, "test").unwrap();
let data = shader.data_ref();
let reference_definition = ShaderDefinition {
name: "TestShader".to_owned(),
properties: vec![PropertyDefinition {
name: "diffuseTexture".to_string(),
kind: PropertyKind::Sampler {
default: None,
fallback: SamplerFallback::White,
},
}],
passes: vec![RenderPassDefinition {
name: "GBuffer".to_string(),
draw_parameters: Default::default(),
vertex_shader: "<CODE>".to_string(),
fragment_shader: "<CODE>".to_string(),
}],
};
assert_eq!(data.definition, reference_definition);
}
}