Skip to content

Commit

Permalink
Merge pull request #75 from 17cupsofcoffee/custom_shaders
Browse files Browse the repository at this point in the history
Custom shaders
  • Loading branch information
17cupsofcoffee committed Jan 6, 2019
2 parents ab9e2e1 + bf43c82 commit 186f3aa
Show file tree
Hide file tree
Showing 9 changed files with 254 additions and 37 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ The full list of examples available are:
* [`gamepad`](examples/gamepad.rs) - Displays the input from a connected gamepad.
* [`text_input`](examples/text_input.rs) - Displays text as it is typed in by the player.
* [`scaling`](examples/scaling.rs) - Demonstrates the different screen scaling algorithms.
* [`shaders`](examples/shaders.rs) - Uses a custom shader to render a texture.
* [`tetras`](examples/tetras.rs) - A full example game (which is entirely legally distinct from a certain other block-based puzzle game *cough*).

## Support/Feedback
Expand Down
15 changes: 15 additions & 0 deletions examples/resources/disco.frag
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#version 130

in vec2 v_uv;
in vec4 v_color;

uniform sampler2D u_texture;
uniform float u_red;
uniform float u_green;
uniform float u_blue;

out vec4 o_color;

void main() {
o_color = v_color * texture(u_texture, v_uv) * vec4(u_red, u_green, u_blue, 1.0);
}
81 changes: 81 additions & 0 deletions examples/shaders.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
use tetra::graphics::{self, Color, DrawParams, Font, Shader, Text, Texture, Vec2};
use tetra::{Context, ContextBuilder, State};

struct GameState {
texture: Texture,
shader: Shader,
text: Text,

timer: f32,
red: f32,
green: f32,
blue: f32,
}

impl GameState {
fn new(ctx: &mut Context) -> tetra::Result<GameState> {
Ok(GameState {
texture: Texture::new(ctx, "./examples/resources/player.png")?,
shader: Shader::fragment(ctx, "./examples/resources/disco.frag")?,
text: Text::new("", Font::default(), 32.0),

timer: 0.0,
red: 0.0,
green: 0.0,
blue: 0.0,
})
}
}

impl State for GameState {
fn update(&mut self, _ctx: &mut Context) -> tetra::Result {
self.timer += 1.0;

self.red = ((self.timer / 10.0).sin() + 1.0) / 2.0;
self.green = ((self.timer / 100.0).sin() + 1.0) / 2.0;
self.blue = ((self.timer / 1000.0).sin() + 1.0) / 2.0;

self.text.set_content(format!(
"Red: {:.2}\nGreen: {:.2}\nBlue: {:.2}",
self.red, self.green, self.blue
));

Ok(())
}

fn draw(&mut self, ctx: &mut Context, _dt: f64) -> tetra::Result {
graphics::clear(ctx, Color::rgb(0.392, 0.584, 0.929));

graphics::set_shader(ctx, &self.shader);

self.shader.set_uniform(ctx, "u_red", self.red);

self.shader.set_uniform(ctx, "u_green", self.green);

self.shader.set_uniform(ctx, "u_blue", self.blue);

graphics::draw(
ctx,
&self.texture,
DrawParams::new()
.position(Vec2::new(640.0, 360.0))
.origin(Vec2::new(8.0, 8.0))
.scale(Vec2::new(24.0, 24.0)),
);

graphics::reset_shader(ctx);

graphics::draw(ctx, &self.text, Vec2::new(16.0, 16.0));

Ok(())
}
}

fn main() -> tetra::Result {
ContextBuilder::new("Custom Shaders", 1280, 720)
.maximized(true)
.resizable(true)
.quit_on_escape(true)
.build()?
.run_with(GameState::new)
}
55 changes: 40 additions & 15 deletions src/graphics/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,6 @@ const MAX_VERTICES: usize = MAX_SPRITES * 4;
const MAX_INDICES: usize = MAX_SPRITES * 6;
const VERTEX_STRIDE: usize = 8;
const INDEX_ARRAY: [u32; 6] = [0, 1, 2, 2, 3, 0];
const DEFAULT_VERTEX_SHADER: &str = include_str!("../resources/shader.vert");
const DEFAULT_FRAGMENT_SHADER: &str = include_str!("../resources/shader.frag");
const DEFAULT_FONT: &[u8] = include_bytes!("../resources/DejaVuSansMono.ttf");

#[derive(PartialEq)]
Expand All @@ -52,6 +50,7 @@ pub(crate) enum ActiveTexture {
#[derive(PartialEq)]
pub(crate) enum ActiveShader {
Default,
User(Shader),
}

#[derive(PartialEq)]
Expand Down Expand Up @@ -143,16 +142,18 @@ impl GraphicsContext {
BufferUsage::DynamicDraw,
);

device.set_vertex_buffer_attribute(&vertex_buffer, 0, 4, 0);
device.set_vertex_buffer_attribute(&vertex_buffer, 1, 3, 4);
device.set_vertex_buffer_attribute(&vertex_buffer, 0, 2, 0);
device.set_vertex_buffer_attribute(&vertex_buffer, 1, 2, 2);
device.set_vertex_buffer_attribute(&vertex_buffer, 2, 4, 4);

let index_buffer = device.new_index_buffer(MAX_INDICES, BufferUsage::StaticDraw);

device.set_index_buffer_data(&index_buffer, &indices, 0);

let default_shader = Shader::from_handle(
device.compile_program(DEFAULT_VERTEX_SHADER, DEFAULT_FRAGMENT_SHADER)?,
);
let default_shader = Shader::from_handle(device.compile_program(
shader::DEFAULT_VERTEX_SHADER,
shader::DEFAULT_FRAGMENT_SHADER,
)?);

let font_cache = GlyphBrushBuilder::using_font_bytes(DEFAULT_FONT).build();
let (width, height) = font_cache.texture_dimensions();
Expand Down Expand Up @@ -518,14 +519,32 @@ pub(crate) fn set_texture_ex(ctx: &mut Context, texture: ActiveTexture) {
}
}

// TODO: This will need to come back once custom shaders are a thing
/// Sets the shader that is currently being used for rendering.
///
/// If the shader is different from the one that is currently in use, this will trigger a
/// [`flush`](fn.flush.html) to the graphics hardware - try to avoid shader swapping as
/// much as you can.
pub fn set_shader(ctx: &mut Context, shader: &Shader) {
set_shader_ex(ctx, ActiveShader::User(shader.clone()));
}

/// Sets the renderer back to using the default shader.
pub fn reset_shader(ctx: &mut Context) {
set_shader_ex(ctx, ActiveShader::Default);
}

pub(crate) fn set_shader_ex(ctx: &mut Context, shader: ActiveShader) -> Option<Shader> {
if shader != ctx.graphics.shader {
flush(ctx);
let old_shader = std::mem::replace(&mut ctx.graphics.shader, shader);

// pub(crate) fn set_shader_ex(ctx: &mut Context, shader: ActiveShader) {
// if shader != ctx.graphics.shader {
// flush(ctx);
// ctx.graphics.shader = shader;
// }
// }
if let ActiveShader::User(s) = old_shader {
return Some(s);
}
}

None
}

pub(crate) fn set_projection_ex(ctx: &mut Context, projection: ActiveProjection) {
if projection != ctx.graphics.projection {
Expand Down Expand Up @@ -570,6 +589,7 @@ pub fn flush(ctx: &mut Context) {

let shader = match &ctx.graphics.shader {
ActiveShader::Default => &ctx.graphics.default_shader,
ActiveShader::User(s) => &s,
};

let projection = match &ctx.graphics.projection {
Expand All @@ -578,7 +598,7 @@ pub fn flush(ctx: &mut Context) {
};

ctx.gl
.set_uniform(&shader.handle, "projection", &projection);
.set_uniform(&shader.handle, "u_projection", &projection);

ctx.gl
.set_vertex_buffer_data(&ctx.graphics.vertex_buffer, &ctx.graphics.vertex_data, 0);
Expand All @@ -605,6 +625,7 @@ pub fn present(ctx: &mut Context) {
set_framebuffer_ex(ctx, ActiveFramebuffer::Window);
set_projection_ex(ctx, ActiveProjection::Window);
set_texture_ex(ctx, ActiveTexture::Framebuffer);
let user_shader = set_shader_ex(ctx, ActiveShader::Default);

clear(ctx, color::BLACK);

Expand All @@ -627,6 +648,10 @@ pub fn present(ctx: &mut Context) {
set_framebuffer_ex(ctx, ActiveFramebuffer::Backbuffer);
set_projection_ex(ctx, ActiveProjection::Internal);

if let Some(s) = user_shader {
set_shader_ex(ctx, ActiveShader::User(s));
}

ctx.window.gl_swap_window();
}

Expand Down
51 changes: 40 additions & 11 deletions src/graphics/opengl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -204,11 +204,15 @@ impl GLDevice {
let program_id = gl::CreateProgram();

// TODO: IDK if this should be applied to *all* shaders...
let pos_tex_name = CString::new("in_pos_tex").unwrap();
let color_name = CString::new("in_color").unwrap();
let position_name = CString::new("a_position").unwrap();
let uv_name = CString::new("a_uv").unwrap();
let color_name = CString::new("a_color").unwrap();
let out_color_name = CString::new("o_color").unwrap();

gl::BindAttribLocation(program_id, 0, pos_tex_name.as_ptr());
gl::BindAttribLocation(program_id, 1, color_name.as_ptr());
gl::BindAttribLocation(program_id, 0, position_name.as_ptr());
gl::BindAttribLocation(program_id, 1, uv_name.as_ptr());
gl::BindAttribLocation(program_id, 2, color_name.as_ptr());
gl::BindFragDataLocation(program_id, 0, out_color_name.as_ptr());

let vertex_id = gl::CreateShader(gl::VERTEX_SHADER);
gl::ShaderSource(vertex_id, 1, &vertex_buffer.as_ptr(), ptr::null());
Expand Down Expand Up @@ -248,7 +252,7 @@ impl GLDevice {

let program = GLProgram { id: program_id };

self.set_uniform(&program, "sampler1", 0);
self.set_uniform(&program, "u_texture", 0);

Ok(program)
}
Expand Down Expand Up @@ -306,7 +310,7 @@ impl GLDevice {

pub fn set_uniform<T>(&mut self, program: &GLProgram, name: &str, value: T)
where
T: SetUniform,
T: UniformValue,
{
unsafe {
self.bind_program(program);
Expand Down Expand Up @@ -583,26 +587,51 @@ impl Drop for GLProgram {
}
}

pub trait SetUniform {
mod sealed {
use super::*;
pub trait UniformValueTypes {}
impl UniformValueTypes for i32 {}
impl UniformValueTypes for f32 {}
impl UniformValueTypes for Mat4 {}
impl<'a, T> UniformValueTypes for &'a T where T: UniformValueTypes {}
}

/// Represents a type that can be passed as a uniform value to a shader.
///
/// As the implementation of this trait currently interacts directly with the OpenGL layer,
/// it's marked as a [sealed trait](https://rust-lang-nursery.github.io/api-guidelines/future-proofing.html#sealed-traits-protect-against-downstream-implementations-c-sealed),
/// and can't be implemented outside of Tetra. This might change in the future!
pub trait UniformValue: sealed::UniformValueTypes {
#[doc(hidden)]
unsafe fn set_uniform(&self, location: GLint);
}

impl SetUniform for i32 {
impl UniformValue for i32 {
#[doc(hidden)]
unsafe fn set_uniform(&self, location: GLint) {
gl::Uniform1i(location, *self);
}
}

impl SetUniform for Mat4 {
impl UniformValue for f32 {
#[doc(hidden)]
unsafe fn set_uniform(&self, location: GLint) {
gl::Uniform1f(location, *self);
}
}

impl UniformValue for Mat4 {
#[doc(hidden)]
unsafe fn set_uniform(&self, location: GLint) {
gl::UniformMatrix4fv(location, 1, gl::FALSE, self.as_slice().as_ptr());
}
}

impl<'a, T> SetUniform for &'a T
impl<'a, T> UniformValue for &'a T
where
T: SetUniform,
T: UniformValue,
{
#[doc(hidden)]
unsafe fn set_uniform(&self, location: GLint) {
(**self).set_uniform(location);
}
Expand Down
Loading

0 comments on commit 186f3aa

Please sign in to comment.