diff --git a/src/consts.rs b/src/consts.rs index d5628e4..f068edd 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -1,20 +1,35 @@ - // settings pub const SCR_WIDTH: u32 = 1280; pub const SCR_HEIGHT: u32 = 720; pub const VERTEX_SHADER_SRC: &str = r#" - #version 330 core - layout (location = 0) in vec3 aPos; - void main() { - gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0); - } +#version 330 core +layout (location = 0) in vec3 aPos; +layout (location = 1) in vec2 aTexCoord; + +out vec2 TexCoord; + +uniform mat4 transform; + +void main() +{ + gl_Position = transform * vec4(aPos, 1.0); + TexCoord = vec2(aTexCoord.x, aTexCoord.y); +} "#; pub const FRAGMENT_SHADER_SRC: &str = r#" - #version 330 core - out vec4 FragColor; - void main() { - FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f); - } -"#; \ No newline at end of file +#version 330 core +out vec4 FragColor; + +in vec3 ourColor; +in vec2 TexCoord; + +// texture sampler +uniform sampler2D texture1; + +void main() +{ + FragColor = texture(texture1, TexCoord); +} +"#; diff --git a/src/macros.rs b/src/macros.rs new file mode 100644 index 0000000..6de3dba --- /dev/null +++ b/src/macros.rs @@ -0,0 +1,17 @@ +#![macro_use] + +/// Macro to get c strings from literals without runtime overhead +/// Literal must not contain any interior nul bytes! +macro_rules! c_str { + ($literal:expr) => { + std::ffi::CStr::from_bytes_with_nul_unchecked(concat!($literal, "\0").as_bytes()) + }; +} + +/// Get offset to struct member, similar to `offset_of` in C/C++ +/// From https://stackoverflow.com/questions/40310483/how-to-get-pointer-offset-in-bytes/40310851#40310851 +macro_rules! offset_of { + ($ty:ty, $field:ident) => { + &(*(ptr::null() as *const $ty)).$field as *const _ as usize + }; +} diff --git a/src/main.rs b/src/main.rs index 4982992..faae8da 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,17 +1,26 @@ extern crate glfw; -use self::glfw::{Context, Key, Action}; +use self::glfw::{Action, Context, Key}; extern crate gl; use self::gl::types::*; -use std::sync::mpsc::Receiver; use std::ffi::CString; -use std::ptr; -use std::str; use std::mem; use std::os::raw::c_void; +use std::ptr; +use std::str; +use std::sync::mpsc::Receiver; + +use std::path::Path; + +extern crate image; +use image::GenericImage; + +use cgmath::prelude::*; +use cgmath::{vec3, Matrix4, Rad}; mod consts; +mod macros; mod shader; pub fn main() { @@ -19,13 +28,21 @@ pub fn main() { // ------------------------------ let mut glfw = glfw::init(glfw::FAIL_ON_ERRORS).unwrap(); glfw.window_hint(glfw::WindowHint::ContextVersion(3, 3)); - glfw.window_hint(glfw::WindowHint::OpenGlProfile(glfw::OpenGlProfileHint::Core)); + glfw.window_hint(glfw::WindowHint::OpenGlProfile( + glfw::OpenGlProfileHint::Core, + )); #[cfg(target_os = "macos")] glfw.window_hint(glfw::WindowHint::OpenGlForwardCompat(true)); // glfw window creation // -------------------- - let (mut window, events) = glfw.create_window(consts::SCR_WIDTH, consts::SCR_HEIGHT, "LearnOpenGL", glfw::WindowMode::Windowed) + let (mut window, events) = glfw + .create_window( + consts::SCR_WIDTH, + consts::SCR_HEIGHT, + "chRustedGL", + glfw::WindowMode::Windowed, + ) .expect("Failed to create GLFW window"); window.make_current(); @@ -36,46 +53,63 @@ pub fn main() { // --------------------------------------- gl::load_with(|symbol| window.get_proc_address(symbol) as *const _); - - let (shaderObject, VAO) = unsafe { - let vShaderCode = CString::new(consts::VERTEX_SHADER_SRC.as_bytes()).unwrap(); - let fShaderCode = CString::new(consts::FRAGMENT_SHADER_SRC.as_bytes()).unwrap(); - let shader = crate::shader::Shader::new(vShaderCode, fShaderCode); + let (shader_object, vao, vbo, ebo, texture) = unsafe { + let v_shader = CString::new(consts::VERTEX_SHADER_SRC.as_bytes()).unwrap(); + let f_shader = CString::new(consts::FRAGMENT_SHADER_SRC.as_bytes()).unwrap(); + let shader = crate::shader::Shader::new(v_shader, f_shader); // set up vertex data (and buffer(s)) and configure vertex attributes // ------------------------------------------------------------------ // HINT: type annotation is crucial since default for float literals is f64 - let vertices: [f32; 12] = [ - 0.5, 0.5, 0.0, // top right - 0.5, -0.5, 0.0, // bottom right - -0.5, -0.5, 0.0, // bottom left - -0.5, 0.5, 0.0 // top left + let vertices: [f32; 20] = [ + // positions // texture coords + 0.5, 0.5, 0.0, 1.0, 1.0, // top right + 0.5, -0.5, 0.0, 1.0, 0.0, // bottom right + -0.5, -0.5, 0.0, 0.0, 0.0, // bottom left + -0.5, 0.5, 0.0, 0.0, 1.0, // top left ]; - let indices = [ // note that we start from 0! - 0, 1, 3, // first Triangle - 1, 2, 3 // second Triangle + let indices = [ + // note that we start from 0! + 0, 1, 3, // first Triangle + 1, 2, 3, // second Triangle ]; - let (mut VBO, mut VAO, mut EBO) = (0, 0, 0); - gl::GenVertexArrays(1, &mut VAO); - gl::GenBuffers(1, &mut VBO); - gl::GenBuffers(1, &mut EBO); + let (mut vbo, mut vao, mut ebo) = (0, 0, 0); + gl::GenVertexArrays(1, &mut vao); + gl::GenBuffers(1, &mut vbo); + gl::GenBuffers(1, &mut ebo); // bind the Vertex Array Object first, then bind and set vertex buffer(s), and then configure vertex attributes(s). - gl::BindVertexArray(VAO); + gl::BindVertexArray(vao); - gl::BindBuffer(gl::ARRAY_BUFFER, VBO); - gl::BufferData(gl::ARRAY_BUFFER, - (vertices.len() * mem::size_of::()) as GLsizeiptr, - &vertices[0] as *const f32 as *const c_void, - gl::STATIC_DRAW); + gl::BindBuffer(gl::ARRAY_BUFFER, vbo); + gl::BufferData( + gl::ARRAY_BUFFER, + (vertices.len() * mem::size_of::()) as GLsizeiptr, + &vertices[0] as *const f32 as *const c_void, + gl::STATIC_DRAW, + ); - gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, EBO); - gl::BufferData(gl::ELEMENT_ARRAY_BUFFER, - (indices.len() * mem::size_of::()) as GLsizeiptr, - &indices[0] as *const i32 as *const c_void, - gl::STATIC_DRAW); + gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, ebo); + gl::BufferData( + gl::ELEMENT_ARRAY_BUFFER, + (indices.len() * mem::size_of::()) as GLsizeiptr, + &indices[0] as *const i32 as *const c_void, + gl::STATIC_DRAW, + ); - gl::VertexAttribPointer(0, 3, gl::FLOAT, gl::FALSE, 3 * mem::size_of::() as GLsizei, ptr::null()); + let stride = 5 * mem::size_of::() as GLsizei; + // position attribute + gl::VertexAttribPointer(0, 3, gl::FLOAT, gl::FALSE, stride, ptr::null()); gl::EnableVertexAttribArray(0); + // texture coord attribute + gl::VertexAttribPointer( + 1, + 2, + gl::FLOAT, + gl::FALSE, + stride, + (3 * mem::size_of::()) as *const c_void, + ); + gl::EnableVertexAttribArray(1); // note that this is allowed, the call to gl::VertexAttribPointer registered VBO as the vertex attribute's bound vertex buffer object so afterwards we can safely unbind gl::BindBuffer(gl::ARRAY_BUFFER, 0); @@ -90,14 +124,41 @@ pub fn main() { // uncomment this call to draw in wireframe polygons. // gl::PolygonMode(gl::FRONT_AND_BACK, gl::LINE); - (shader, VAO) + // ------------------------- + let mut texture = 0; + gl::GenTextures(1, &mut texture); + gl::BindTexture(gl::TEXTURE_2D, texture); // all upcoming GL_TEXTURE_2D operations now have effect on this texture object + // set the texture wrapping parameters + gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_S, gl::REPEAT as i32); // set texture wrapping to gl::REPEAT (default wrapping method) + gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_T, gl::REPEAT as i32); + // set texture filtering parameters + gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::LINEAR as i32); + gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::LINEAR as i32); + // load image, create texture and generate mipmaps + let img = image::open(&Path::new("resources/garlic_dog_space.jpg")) + .expect("Failed to load texture"); + let flipped = img.flipv(); + let data = flipped.raw_pixels(); + gl::TexImage2D( + gl::TEXTURE_2D, + 0, + gl::RGB as i32, + img.width() as i32, + img.height() as i32, + 0, + gl::RGB, + gl::UNSIGNED_BYTE, + &data[0] as *const u8 as *const c_void, + ); + gl::GenerateMipmap(gl::TEXTURE_2D); + (shader, vao, vbo, ebo, texture) }; // render loop // ----------- - let color_r = 0.3; - let color_g = 0.3; - let color_b = 0.6; + let color_r = 0.188; + let color_g = 0.22; + let color_b = 0.235; while !window.should_close() { // events // ----- @@ -109,9 +170,19 @@ pub fn main() { gl::ClearColor(color_r, color_g, color_b, 1.0); gl::Clear(gl::COLOR_BUFFER_BIT); - // draw our first triangle - shaderObject.useProgram(); - gl::BindVertexArray(VAO); // seeing as we only have a single VAO there's no need to bind it every time, but we'll do so to keep things a bit more organized + gl::BindTexture(gl::TEXTURE_2D, texture); + // create transformations + let mut transform: Matrix4 = Matrix4::identity(); + transform = transform * Matrix4::::from_translation(vec3(0.5, -0.5, 0.0)); + transform = transform * Matrix4::::from_angle_z(Rad(glfw.get_time() as f32)); + + // get matrix's uniform location and set matrix + shader_object.useProgram(); + let transform_loc = + gl::GetUniformLocation(shader_object.ID, c_str!("transform").as_ptr()); + gl::UniformMatrix4fv(transform_loc, 1, gl::FALSE, transform.as_ptr()); + + gl::BindVertexArray(vao); // seeing as we only have a single VAO there's no need to bind it every time, but we'll do so to keep things a bit more organized gl::DrawElements(gl::TRIANGLES, 6, gl::UNSIGNED_INT, ptr::null()); } @@ -131,7 +202,9 @@ fn process_events(window: &mut glfw::Window, events: &Receiver<(f64, glfw::Windo // height will be significantly larger than specified on retina displays. unsafe { gl::Viewport(0, 0, width, height) } } - glfw::WindowEvent::Key(Key::Escape, _, Action::Press, _) => window.set_should_close(true), + glfw::WindowEvent::Key(Key::Escape, _, Action::Press, _) => { + window.set_should_close(true) + } glfw::WindowEvent::Key(_, _, Action::Press, _) => println!("Other key pressed"), _ => {} } diff --git a/src/shader.rs b/src/shader.rs index 3fd2005..6ec5d32 100644 --- a/src/shader.rs +++ b/src/shader.rs @@ -1,5 +1,5 @@ #![allow(non_snake_case)] -use std::ffi::{CString, CStr}; +use std::ffi::{CStr, CString}; use std::fs::File; use std::io::Read; use std::ptr; @@ -8,8 +8,8 @@ use std::str; use gl; use gl::types::*; -use cgmath::{Matrix, Matrix4, Vector3}; use cgmath::prelude::*; +use cgmath::{Matrix, Matrix4, Vector3}; pub struct Shader { pub ID: u32, @@ -19,7 +19,6 @@ pub struct Shader { /// a few more setters for uniforms) #[allow(dead_code)] impl Shader { - pub fn new(vShaderCode: CString, fShaderCode: CString) -> Shader { let mut shader = Shader { ID: 0 }; @@ -52,10 +51,10 @@ impl Shader { pub fn from_file(vertexPath: &str, fragmentPath: &str) -> Shader { // 1. retrieve the vertex/fragment source code from filesystem - let mut vShaderFile = File::open(vertexPath) - .unwrap_or_else(|_| panic!("Failed to open {}", vertexPath)); - let mut fShaderFile = File::open(fragmentPath) - .unwrap_or_else(|_| panic!("Failed to open {}", fragmentPath)); + let mut vShaderFile = + File::open(vertexPath).unwrap_or_else(|_| panic!("Failed to open {}", vertexPath)); + let mut fShaderFile = + File::open(fragmentPath).unwrap_or_else(|_| panic!("Failed to open {}", fragmentPath)); let mut vertexCode = String::new(); let mut fragmentCode = String::new(); vShaderFile @@ -92,7 +91,11 @@ impl Shader { } /// ------------------------------------------------------------------------ pub unsafe fn setVector3(&self, name: &CStr, value: &Vector3) { - gl::Uniform3fv(gl::GetUniformLocation(self.ID, name.as_ptr()), 1, value.as_ptr()); + gl::Uniform3fv( + gl::GetUniformLocation(self.ID, name.as_ptr()), + 1, + value.as_ptr(), + ); } /// ------------------------------------------------------------------------ pub unsafe fn setVec3(&self, name: &CStr, x: f32, y: f32, z: f32) { @@ -100,7 +103,12 @@ impl Shader { } /// ------------------------------------------------------------------------ pub unsafe fn setMat4(&self, name: &CStr, mat: &Matrix4) { - gl::UniformMatrix4fv(gl::GetUniformLocation(self.ID, name.as_ptr()), 1, gl::FALSE, mat.as_ptr()); + gl::UniformMatrix4fv( + gl::GetUniformLocation(self.ID, name.as_ptr()), + 1, + gl::FALSE, + mat.as_ptr(), + ); } /// utility function for checking shader compilation/linking errors. @@ -112,36 +120,48 @@ impl Shader { if type_ != "PROGRAM" { gl::GetShaderiv(shader, gl::COMPILE_STATUS, &mut success); if success != gl::TRUE as GLint { - gl::GetShaderInfoLog(shader, 1024, ptr::null_mut(), infoLog.as_mut_ptr() as *mut GLchar); - println!("ERROR::SHADER_COMPILATION_ERROR of type: {}\n{}\n \ + gl::GetShaderInfoLog( + shader, + 1024, + ptr::null_mut(), + infoLog.as_mut_ptr() as *mut GLchar, + ); + println!( + "ERROR::SHADER_COMPILATION_ERROR of type: {}\n{}\n \ -- --------------------------------------------------- -- ", - type_, - str::from_utf8(&infoLog).unwrap()); + type_, + str::from_utf8(&infoLog).unwrap() + ); } - } else { gl::GetProgramiv(shader, gl::LINK_STATUS, &mut success); if success != gl::TRUE as GLint { - gl::GetProgramInfoLog(shader, 1024, ptr::null_mut(), infoLog.as_mut_ptr() as *mut GLchar); - println!("ERROR::PROGRAM_LINKING_ERROR of type: {}\n{}\n \ + gl::GetProgramInfoLog( + shader, + 1024, + ptr::null_mut(), + infoLog.as_mut_ptr() as *mut GLchar, + ); + println!( + "ERROR::PROGRAM_LINKING_ERROR of type: {}\n{}\n \ -- --------------------------------------------------- -- ", - type_, - str::from_utf8(&infoLog).unwrap()); + type_, + str::from_utf8(&infoLog).unwrap() + ); } } - } /// Only used in 4.9 Geometry shaders - ignore until then (shader.h in original C++) pub fn with_geometry_shader(vertexPath: &str, fragmentPath: &str, geometryPath: &str) -> Self { let mut shader = Shader { ID: 0 }; // 1. retrieve the vertex/fragment source code from filesystem - let mut vShaderFile = File::open(vertexPath) - .unwrap_or_else(|_| panic!("Failed to open {}", vertexPath)); - let mut fShaderFile = File::open(fragmentPath) - .unwrap_or_else(|_| panic!("Failed to open {}", fragmentPath)); - let mut gShaderFile = File::open(geometryPath) - .unwrap_or_else(|_| panic!("Failed to open {}", geometryPath)); + let mut vShaderFile = + File::open(vertexPath).unwrap_or_else(|_| panic!("Failed to open {}", vertexPath)); + let mut fShaderFile = + File::open(fragmentPath).unwrap_or_else(|_| panic!("Failed to open {}", fragmentPath)); + let mut gShaderFile = + File::open(geometryPath).unwrap_or_else(|_| panic!("Failed to open {}", geometryPath)); let mut vertexCode = String::new(); let mut fragmentCode = String::new(); let mut geometryCode = String::new();