use crate::*;
use std::mem::size_of;
use std::ops::Range;
pub struct ShaderDescription<'a> {
pub vertex_input: &'a [Attribute],
pub fragment_input: &'a [Attribute],
pub uniforms: &'a [Uniform],
pub vertex_shader: &'a str,
pub fragment_shader: &'a str,
}
pub struct ShaderProgram {
ctx: crate::Context,
id: GlProgram,
vertex: GlShader,
fragment: GlShader,
input: Vec<Attribute>,
}
fn generate_shader_text(
is_vertex: bool,
body: &str,
inputs: &[Attribute],
outputs: &[Attribute],
uniforms: &[Uniform],
) -> String {
let mut shader = String::new();
#[cfg(not(target_arch = "wasm32"))]
shader.push_str("#version 150\n");
shader.push_str("precision mediump float;\n");
for attr in inputs.iter() {
attr.as_glsl(is_vertex, Position::Input, &mut shader);
}
for attr in outputs.iter() {
attr.as_glsl(is_vertex, Position::Output, &mut shader);
}
for uniform in uniforms.iter() {
uniform.as_glsl(&mut shader);
}
shader.push_str(body);
shader
}
impl ShaderProgram {
pub fn new(ctx: &Context, desc: ShaderDescription) -> Result<ShaderProgram, GolemError> {
let gl = &ctx.0.gl;
unsafe {
let vertex = gl.create_shader(glow::VERTEX_SHADER)?;
let vertex_source = generate_shader_text(
true,
desc.vertex_shader,
desc.vertex_input,
desc.fragment_input,
desc.uniforms,
);
log::debug!("Vertex shader source: {}", vertex_source);
gl.shader_source(vertex, &vertex_source);
gl.compile_shader(vertex);
if !gl.get_shader_compile_status(vertex) {
let info = gl.get_shader_info_log(vertex);
log::error!("Failed to compile vertex shader: {}", info);
return Err(GolemError::ShaderCompilationError(info));
}
log::trace!("Compiled vertex shader succesfully");
let fragment = gl.create_shader(glow::FRAGMENT_SHADER)?;
#[cfg(target_arch = "wasm32")]
let (fragment_output, fragment_body) = { (&[], desc.fragment_shader) };
#[cfg(not(target_arch = "wasm32"))]
let (fragment_output, fragment_body) = {
(
&[Attribute::new(
"outputColor",
AttributeType::Vector(Dimension::D4),
)],
&desc.fragment_shader.replace("gl_FragColor", "outputColor"),
)
};
let fragment_source = generate_shader_text(
false,
fragment_body,
desc.fragment_input,
fragment_output,
desc.uniforms,
);
log::debug!("Fragment shader source: {}", vertex_source);
gl.shader_source(fragment, &fragment_source);
gl.compile_shader(fragment);
if !gl.get_shader_compile_status(fragment) {
let info = gl.get_shader_info_log(fragment);
log::error!("Failed to compile vertex shader: {}", info);
return Err(GolemError::ShaderCompilationError(info));
}
log::trace!("Compiled fragment shader succesfully");
let id = gl.create_program()?;
gl.attach_shader(id, vertex);
gl.attach_shader(id, fragment);
#[cfg(not(target_arch = "wasm32"))]
gl.bind_frag_data_location(id, 0, "outputColor");
for (index, attr) in desc.vertex_input.iter().enumerate() {
gl.bind_attrib_location(id, index as u32, attr.name());
}
gl.link_program(id);
if !gl.get_program_link_status(id) {
let info = gl.get_program_info_log(id);
log::error!("Failed to link the shader program: {}", info);
return Err(GolemError::ShaderCompilationError(info));
}
log::trace!("Linked shader program succesfully");
Ok(ShaderProgram {
ctx: Context(ctx.0.clone()),
id,
vertex,
fragment,
input: desc.vertex_input.to_vec(),
})
}
}
pub fn is_bound(&self) -> bool {
match *self.ctx.0.current_program.borrow() {
Some(program) => self.id == program,
None => false,
}
}
pub fn set_uniform(&self, name: &str, uniform: UniformValue) -> Result<(), GolemError> {
if self.is_bound() {
let gl = &self.ctx.0.gl;
let location = unsafe { gl.get_uniform_location(self.id, name) };
use UniformValue::*;
unsafe {
match uniform {
Int(x) => gl.uniform_1_i32(location, x),
IVector2([x, y]) => gl.uniform_2_i32(location, x, y),
IVector3([x, y, z]) => gl.uniform_3_i32(location, x, y, z),
IVector4([x, y, z, w]) => gl.uniform_4_i32(location, x, y, z, w),
Float(x) => gl.uniform_1_f32(location, x),
Vector2([x, y]) => gl.uniform_2_f32(location, x, y),
Vector3([x, y, z]) => gl.uniform_3_f32(location, x, y, z),
Vector4([x, y, z, w]) => gl.uniform_4_f32(location, x, y, z, w),
Matrix2(mat) => gl.uniform_matrix_2_f32_slice(location, false, &mat),
Matrix3(mat) => gl.uniform_matrix_3_f32_slice(location, false, &mat),
Matrix4(mat) => gl.uniform_matrix_4_f32_slice(location, false, &mat),
}
}
Ok(())
} else {
Err(GolemError::NotCurrentProgram)
}
}
pub fn bind(&mut self) {
let gl = &self.ctx.0.gl;
log::trace!("Binding the shader and buffers");
unsafe {
gl.use_program(Some(self.id));
}
*self.ctx.0.current_program.borrow_mut() = Some(self.id);
}
pub unsafe fn draw(
&self,
vb: &VertexBuffer,
eb: &ElementBuffer,
range: Range<usize>,
geometry: GeometryMode,
) -> Result<(), GolemError> {
let gl = &self.ctx.0.gl;
if !self.is_bound() {
Err(GolemError::NotCurrentProgram)
} else {
assert!(range.end <= eb.size());
self.bind_vertex(vb);
eb.bind();
log::trace!("Dispatching draw command");
let length = range.end - range.start;
gl.draw_elements(
ShaderProgram::shape_type(geometry),
length as i32,
glow::UNSIGNED_INT,
range.start as i32,
);
Ok(())
}
}
fn bind_vertex(&self, vb: &VertexBuffer) {
vb.bind();
let stride: i32 = self.input.iter().map(|attr| attr.size()).sum();
let stride = stride * size_of::<f32>() as i32;
let mut offset = 0;
log::trace!("Binding the attributes to draw");
let gl = &self.ctx.0.gl;
for (index, attr) in self.input.iter().enumerate() {
let size = attr.size();
unsafe {
let pos_attrib = index as u32;
gl.enable_vertex_attrib_array(pos_attrib);
gl.vertex_attrib_pointer_f32(pos_attrib, size, glow::FLOAT, false, stride, offset);
}
offset += size * size_of::<f32>() as i32;
}
}
fn shape_type(geometry: GeometryMode) -> u32 {
use GeometryMode::*;
match geometry {
Points => glow::POINTS,
Lines => glow::LINES,
LineStrip => glow::LINE_STRIP,
LineLoop => glow::LINE_LOOP,
TriangleStrip => glow::TRIANGLE_STRIP,
TriangleFan => glow::TRIANGLE_FAN,
Triangles => glow::TRIANGLES,
}
}
}
impl Drop for ShaderProgram {
fn drop(&mut self) {
let gl = &self.ctx.0.gl;
unsafe {
gl.delete_program(self.id);
gl.delete_shader(self.fragment);
gl.delete_shader(self.vertex);
}
}
}