use std::cell::{Cell, RefCell};
use std::path::Path;
use std::rc::Rc;
use std::slice;
use hashbrown::HashMap;
use crate::error::Result;
use crate::fs;
use crate::graphics::{Color, Texture};
use crate::math::{Mat2, Mat3, Mat4, Vec2, Vec3, Vec4};
use crate::platform::{GraphicsDevice, RawShader};
use crate::Context;
pub const DEFAULT_VERTEX_SHADER: &str = include_str!("../resources/shader.vert");
pub const DEFAULT_FRAGMENT_SHADER: &str = include_str!("../resources/shader.frag");
#[derive(Debug)]
pub(crate) struct Sampler {
pub(crate) texture: Texture,
pub(crate) unit: u32,
}
#[derive(Debug)]
pub(crate) struct ShaderSharedData {
pub(crate) handle: RawShader,
pub(crate) samplers: RefCell<HashMap<String, Sampler>>,
pub(crate) next_unit: Cell<u32>,
}
impl PartialEq for ShaderSharedData {
fn eq(&self, other: &ShaderSharedData) -> bool {
self.handle.eq(&other.handle)
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Shader {
pub(crate) data: Rc<ShaderSharedData>,
}
impl Shader {
pub fn new<P>(ctx: &mut Context, vertex_path: P, fragment_path: P) -> Result<Shader>
where
P: AsRef<Path>,
{
Shader::with_device(
&mut ctx.device,
&fs::read_to_string(vertex_path)?,
&fs::read_to_string(fragment_path)?,
)
}
pub fn from_vertex_file<P>(ctx: &mut Context, path: P) -> Result<Shader>
where
P: AsRef<Path>,
{
Shader::with_device(
&mut ctx.device,
&fs::read_to_string(path)?,
DEFAULT_FRAGMENT_SHADER,
)
}
pub fn from_fragment_file<P>(ctx: &mut Context, path: P) -> Result<Shader>
where
P: AsRef<Path>,
{
Shader::with_device(
&mut ctx.device,
DEFAULT_VERTEX_SHADER,
&fs::read_to_string(path)?,
)
}
pub fn from_string(
ctx: &mut Context,
vertex_shader: &str,
fragment_shader: &str,
) -> Result<Shader> {
Shader::with_device(&mut ctx.device, vertex_shader, fragment_shader)
}
pub fn from_vertex_string(ctx: &mut Context, shader: &str) -> Result<Shader> {
Shader::with_device(&mut ctx.device, shader, DEFAULT_FRAGMENT_SHADER)
}
pub fn from_fragment_string(ctx: &mut Context, shader: &str) -> Result<Shader> {
Shader::with_device(&mut ctx.device, DEFAULT_VERTEX_SHADER, shader)
}
pub(crate) fn with_device(
device: &mut GraphicsDevice,
vertex_shader: &str,
fragment_shader: &str,
) -> Result<Shader> {
let handle = device.new_shader(vertex_shader, fragment_shader)?;
Ok(Shader {
data: Rc::new(ShaderSharedData {
handle,
samplers: RefCell::new(HashMap::new()),
next_unit: Cell::new(1),
}),
})
}
pub fn set_uniform<V>(&self, ctx: &mut Context, name: &str, value: V)
where
V: UniformValue,
{
value.set_uniform(ctx, self, name)
}
pub(crate) fn set_default_uniforms(
&self,
device: &mut GraphicsDevice,
projection: Mat4<f32>,
diffuse: Color,
) -> Result {
let samplers = self.data.samplers.borrow();
for sampler in samplers.values() {
device.attach_texture_to_sampler(&sampler.texture.data.handle, sampler.unit)?;
}
let projection_location = device.get_uniform_location(&self.data.handle, "u_projection");
device.set_uniform_mat4(
&self.data.handle,
projection_location.as_ref(),
&[projection],
);
let diffuse_location = device.get_uniform_location(&self.data.handle, "u_diffuse");
device.set_uniform_vec4(
&self.data.handle,
diffuse_location.as_ref(),
&[diffuse.into()],
);
Ok(())
}
}
pub trait UniformValue {
#[doc(hidden)]
fn set_uniform(&self, ctx: &mut Context, shader: &Shader, name: &str);
}
macro_rules! simple_uniforms {
($($t:ty => $f:ident, $doc:expr, $arraydoc:expr),* $(,)?) => {
$(
#[doc = $doc]
impl UniformValue for $t {
#[doc(hidden)]
fn set_uniform(
&self,
ctx: &mut Context,
shader: &Shader,
name: &str,
) {
let location = ctx.device.get_uniform_location(&shader.data.handle, name);
ctx.device.$f(&shader.data.handle, location.as_ref(), slice::from_ref(self));
}
}
#[doc = $arraydoc]
impl UniformValue for &[$t] {
#[doc(hidden)]
fn set_uniform(
&self,
ctx: &mut Context,
shader: &Shader,
name: &str,
) {
let location = ctx.device.get_uniform_location(&shader.data.handle, name);
ctx.device.$f(&shader.data.handle, location.as_ref(), self);
}
}
#[doc = $arraydoc]
impl<const N: usize> UniformValue for [$t; N] {
#[doc(hidden)]
fn set_uniform(
&self,
ctx: &mut Context,
shader: &Shader,
name: &str,
) {
let location = ctx.device.get_uniform_location(&shader.data.handle, name);
ctx.device.$f(&shader.data.handle, location.as_ref(), self);
}
}
)*
};
}
simple_uniforms! {
i32 => set_uniform_i32, "Can be accessed as an `int` in your shader.", "Can be accessed as an array of `int`s in your shader.",
u32 => set_uniform_u32, "Can be accessed as a `uint` in your shader.", "Can be accessed as an array of `uint`s in your shader.",
f32 => set_uniform_f32, "Can be accessed as a `float` in your shader.", "Can be accessed as an array of `float`s in your shader.",
Vec2<f32> => set_uniform_vec2, "Can be accessed as a `vec2` in your shader.", "Can be accessed as an array of `vec2`s in your shader.",
Vec3<f32> => set_uniform_vec3, "Can be accessed as a `vec3` in your shader.", "Can be accessed as an array of `vec3`s in your shader.",
Vec4<f32> => set_uniform_vec4, "Can be accessed as a `vec4` in your shader.", "Can be accessed as an array of `vec4`s in your shader.",
Mat2<f32> => set_uniform_mat2, "Can be accessed as a `mat2` in your shader.", "Can be accessed as an array of `mat2`s in your shader.",
Mat3<f32> => set_uniform_mat3, "Can be accessed as a `mat3` in your shader.", "Can be accessed as an array of `mat3`s in your shader.",
Mat4<f32> => set_uniform_mat4, "Can be accessed as a `mat4` in your shader.", "Can be accessed as an array of `mat4`s in your shader.",
Color => set_uniform_color, "Can be accessed as a `vec4` in your shader.", "Can be accessed as an array of `vec4`s in your shader.",
}
impl UniformValue for Texture {
#[doc(hidden)]
fn set_uniform(&self, ctx: &mut Context, shader: &Shader, name: &str) {
let mut samplers = shader.data.samplers.borrow_mut();
if let Some(sampler) = samplers.get_mut(name) {
if sampler.texture != *self {
sampler.texture = self.clone();
}
} else {
let next_unit = shader.data.next_unit.get();
samplers.insert(
name.to_owned(),
Sampler {
texture: self.clone(),
unit: next_unit,
},
);
(next_unit as i32).set_uniform(ctx, shader, name);
shader.data.next_unit.set(next_unit + 1);
}
}
}
impl<T> UniformValue for &T
where
T: UniformValue,
{
#[doc(hidden)]
fn set_uniform(&self, ctx: &mut Context, shader: &Shader, name: &str) {
{
let inner = *self;
inner.set_uniform(ctx, shader, name);
}
}
}