//! This example shows how you might use `u32` color in OpenGL. #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] extern crate gl; use gl::types::*; extern crate glutin; use glutin::*; #[macro_use] extern crate retro_pixel; use retro_pixel::*; // std use std::collections::HashSet; use std::ffi::CString; use std::mem::{size_of, size_of_val}; use std::ptr::null; fn main() { unsafe { let bitmap_width = 320u32; let bitmap_height = 240u32; let default_upscale = 2; let (start_width, start_height) = (bitmap_width * default_upscale, bitmap_height * default_upscale); // Window and OpenGL setup let mut events_loop = glutin::EventsLoop::new(); let window_builder = glutin::WindowBuilder::new() .with_dimensions(start_width, start_height) .with_title("Bitmap Demo (Use arrow keys to move)"); let context = glutin::ContextBuilder::new() .with_gl(GlRequest::Specific(Api::OpenGl, (3, 3))) .with_gl_profile(GlProfile::Core) .with_vsync(true); let window = glutin::GlWindow::new(window_builder, context, &events_loop) .map_err(|e| eprintln!("==Window Creation Error==\n{}", e)) .unwrap(); window.make_current().expect("Could not make the OpenGL context current"); window.set_position(100, 15); gl::load_with(|symbol| window.get_proc_address(symbol) as *const _); gl::Viewport(0, 0, start_width as i32, start_height as i32); gl::ClearColor(0.1, 0.1, 0.1, 1.0); // get our shader program ready let shader_program = ready_program(VERT_SRC, FRAG_SRC).map_err(|s| eprintln!("{}", s)).unwrap(); shader_program.use_program(); let vertices: [f32; 12] = [-1.0, -1.0, -1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, -1.0, -1.0, -1.0]; // Vertex Array Object let mut vao = 0; gl::GenVertexArrays(1, &mut vao); gl::BindVertexArray(vao); // Vertex buffer object let mut vbo = 0; gl::GenBuffers(1, &mut vbo); gl::BindBuffer(gl::ARRAY_BUFFER, vbo); gl::BufferData( gl::ARRAY_BUFFER, size_of_val(&vertices) as GLsizeiptr, vertices.as_ptr() as *const _, gl::STATIC_DRAW, ); // Position attribute #[allow(non_snake_case)] let vPositionNDC: GLuint = 0; let vertex_stride: GLint = (size_of::() * 2) as GLint; gl::VertexAttribPointer(vPositionNDC, 2, gl::FLOAT, gl::FALSE, vertex_stride, null()); gl::EnableVertexAttribArray(vPositionNDC); // The Bitmap Texture let mut bitmap_texture = 0; gl::GenTextures(1, &mut bitmap_texture); gl::ActiveTexture(gl::TEXTURE0); shader_program .get_uniform_location("bitmap_texture") .map(|loc| gl::Uniform1i(loc, 0)) .ok(); gl::BindTexture(gl::TEXTURE_2D, bitmap_texture); gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_S, gl::REPEAT as i32); gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_T, gl::REPEAT as i32); gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::NEAREST as i32); gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::NEAREST as i32); // hold off on uploading the bitmap texture, it changes from frame to frame. // Main loop let mut alien = load_alien_data(); let mut bitmap: VecImage = VecImage::new(bitmap_width as usize, bitmap_height as usize); let mut alien_position: (isize, isize) = (0, 0); let mut held_keys = HashSet::new(); let mut running = true; while running { // Handle Input events_loop.poll_events(|event| match event { Event::WindowEvent { event: win_event, .. } => match win_event { WindowEvent::Resized(_, _) => { window.get_inner_size().map(|(px, py)| { println!("RESIZING: ({},{})", px, py); gl::Viewport(0, 0, px as i32, py as i32); }); } WindowEvent::CloseRequested | WindowEvent::KeyboardInput { input: KeyboardInput { state: ElementState::Pressed, virtual_keycode: Some(VirtualKeyCode::Escape), .. }, .. } => { running = false; } WindowEvent::KeyboardInput { input: KeyboardInput { state: ElementState::Pressed, virtual_keycode: Some(key), .. }, .. } => { held_keys.insert(key); match key { VirtualKeyCode::F1 => { window.set_inner_size(bitmap_width, bitmap_height); } VirtualKeyCode::F2 => { window.set_inner_size(bitmap_width * 2, bitmap_height * 2); } VirtualKeyCode::F3 => { window.set_inner_size(bitmap_width * 3, bitmap_height * 3); } VirtualKeyCode::F4 => { window.set_inner_size(bitmap_width * 4, bitmap_height * 4); } VirtualKeyCode::Return => { println!("Start"); } VirtualKeyCode::RShift => { println!("Select"); } _ => {} } } WindowEvent::KeyboardInput { input: KeyboardInput { state: ElementState::Released, virtual_keycode: Some(key), .. }, .. } => { held_keys.remove(&key); } _ => {} }, _ => {} }); for &key in held_keys.iter() { match key { VirtualKeyCode::Up => alien_position.1 += 1, VirtualKeyCode::Down => alien_position.1 -= 1, VirtualKeyCode::Left => alien_position.0 -= 1, VirtualKeyCode::Right => alien_position.0 += 1, VirtualKeyCode::X => { alien.inplace_counterclockwise90_square(); alien.inplace_counterclockwise90_square(); alien.inplace_counterclockwise90_square(); } VirtualKeyCode::Z => { alien.inplace_counterclockwise90_square(); } _ => {} } } // bitmap rendering bitmap.set_all(rgb32!(255, 200, 200)); bitmap.slice_mut((20, 30)..(50, 100)).set_all(rgb32!(10, 30, 50)); bitmap.slice_mut((50, 30)..(100, 100)).set_all(rgb32!(255, 255, 255)); bitmap.slice_mut((100, 30)..(150, 100)).set_all(rgb32!(100, 30, 50)); bitmap.slice_mut((150, 30)..(200, 100)).set_all(rgb32!(1, 100, 0)); println!("alien_position: {:?}", alien_position); bitmap.blit_blend_rectilinear(&alien, alien_position); // upload the latest texture data gl::TexImage2D( gl::TEXTURE_2D, 0, gl::RGBA as i32, bitmap.width() as i32, bitmap.height() as i32, 0, gl::RGBA, gl::UNSIGNED_BYTE, bitmap.as_ptr() as *const _, ); gl::GenerateMipmap(gl::TEXTURE_2D); // Draw gl::Clear(gl::COLOR_BUFFER_BIT); gl::DrawArrays(gl::TRIANGLES, 0, 6); // Flip window.swap_buffers().unwrap(); // Error check loop { match gl::GetError() { gl::NO_ERROR => break, gl::INVALID_ENUM => eprintln!("ERR: InvalidEnum"), gl::INVALID_VALUE => eprintln!("ERR: InvalidValue"), gl::INVALID_OPERATION => eprintln!("ERR: InvalidOperation"), gl::INVALID_FRAMEBUFFER_OPERATION => eprintln!("ERR: InvalidFramebufferOperation"), gl::OUT_OF_MEMORY => eprintln!("ERR: OutOfMemory"), gl::STACK_UNDERFLOW => eprintln!("ERR: StackUnderflow"), gl::STACK_OVERFLOW => eprintln!("ERR: StackOverflow"), e => eprintln!("ERR: Unknown: {}", e), }; let _break_point = 5; } } } } fn load_alien_data() -> VecImage { // /* let mut alien = VecImage::new(8, 8); let (empty, red, green, blue, other_blue) = ( 0, linear_to_u32([1.0, 0.0, 0.0, 0.9]), linear_to_u32([0.0, 0.5, 0.0, 0.5]), linear_to_u32([0.0, 0.0, 0.9, 0.8]), linear_to_u32([0.0, 0.7, 0.7, 0.6]), ); alien.set_all(empty); // Some eyes alien[(3, 5)] = blue; alien[(4, 5)] = other_blue; alien[(5, 5)] = blue; // a body for &p in [ (1, 4), (1, 5), (2, 4), (2, 5), (2, 6), (3, 3), (3, 4), (3, 6), (4, 4), (4, 6), (5, 3), (5, 4), (5, 6), (6, 4), (6, 5), (6, 6), ].iter() { alien[p] = green; } // some feet alien[(2, 1)] = red; alien[(2, 2)] = red; alien[(5, 1)] = red; alien[(5, 2)] = red; // feed the alien; alien = alien.upscale(2); // orientation markers alien[(0, 0)] = linear_to_u32([1.0, 1.0, 1.0, 1.0]); // white alien[(1, 0)] = linear_to_u32([1.0, 0.0, 0.0, 1.0]); // red alien[(2, 0)] = linear_to_u32([0.0, 1.0, 0.0, 1.0]); // green alien[(3, 0)] = linear_to_u32([0.0, 0.0, 1.0, 0.5]); // half-blue alien[(4, 0)] = linear_to_u32([0.0, 0.0, 0.0, 1.0]); // black alien[(4, 0)] = linear_to_u32([1.0, 1.0, 1.0, 0.5]); // 50% white alien[(15, 15)] = linear_to_u32([1.0, 1.0, 1.0, 1.0]); alien } // // SHADER STUFF // static VERT_SRC: &'static str = "#version 330 core layout (location = 0) in vec2 vPositionNDC; out vec2 screen_ndc; void main() { gl_Position = vec4(vPositionNDC, 1.0, 1.0); screen_ndc = vPositionNDC; }"; static FRAG_SRC: &'static str = "#version 330 core in vec2 screen_ndc; uniform sampler2D bitmap_texture; out vec4 FragColor; void main() { vec2 bitmap_position = (screen_ndc + 1.0) / 2.0; FragColor = texture(bitmap_texture, bitmap_position); }"; // // SUPPORT CODE BEGINS HERE // /// Makes ready a program from two source strings. /// /// Will attempt to make a complete shader program using the two source strings /// provided as the vertex shader source and fragment shader source. If there's /// an error at any point in the process you get that error message back /// instead. pub fn ready_program(vert: VS, frag: FS) -> Result where VS: AsRef, FS: AsRef, { // Load the vertex shader let mut vertex_shader = ManagedShader::create_shader(gl::VERTEX_SHADER)?; vertex_shader.shader_source(vert)?; vertex_shader.compile_shader(); if !vertex_shader.get_compile_status() { return Err(format!(">>Vertex Shader Error:\n{}", vertex_shader.get_shader_info_log())); } // Load the fragment shader let mut fragment_shader = ManagedShader::create_shader(gl::FRAGMENT_SHADER)?; fragment_shader.shader_source(frag)?; fragment_shader.compile_shader(); if !fragment_shader.get_compile_status() { return Err(format!(">>Fragment Shader Error:\n{}", fragment_shader.get_shader_info_log())); } // Link the program together let mut shader_program = ManagedProgram::create_program()?; shader_program.attach_shader(&vertex_shader); shader_program.attach_shader(&fragment_shader); shader_program.link_program(); if !shader_program.get_link_status() { return Err(format!(">>Program Link Error:\n{}", shader_program.get_program_info_log())); } Ok(shader_program) } /// A newtype around a program name that deletes itself when dropped. #[derive(Debug, PartialEq, Eq)] pub struct ManagedProgram(GLuint); impl ManagedProgram { /// Attempts to creates a new `ManagedProgram`. /// /// See [glCreateProgram](https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glCreateProgram.xhtml) pub fn create_program() -> Result { unsafe { let name = gl::CreateProgram(); if name > 0 { Ok(ManagedProgram(name)) } else { Err("Problem creating the program.".to_string()) } } } /// Returns the program name held by this `ManagedShader`, in case you want /// to do something with it that this library doesn't cover. pub fn get_program_name(&self) -> GLuint { self.0 } /// Attaches the given `ManagedShader` to this program. /// /// See [glAttachShader](https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glAttachShader.xhtml) pub fn attach_shader(&mut self, shader: &ManagedShader) { unsafe { gl::AttachShader(self.0, shader.0); } } /// Links the program shader stages together. /// /// See [glLinkProgram](https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glLinkProgram.xhtml) pub fn link_program(&mut self) { unsafe { gl::LinkProgram(self.0); } } /// Returns `true` if the last link operation of this program was successful, and `false` otherwise. /// /// See [glGetProgramiv](https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glGetProgram.xhtml) pub fn get_link_status(&self) -> bool { unsafe { let mut param = 0; gl::GetProgramiv(self.0, gl::LINK_STATUS, &mut param); param == GLint::from(gl::TRUE) } } /// Gets the info log of this program. /// /// See [glGetProgramInfoLog](https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glGetProgramInfoLog.xhtml) pub fn get_program_info_log(&self) -> String { unsafe { // How many bytes will the info log be? Including the null byte. let mut info_log_length: GLint = 0; gl::GetProgramiv(self.0, gl::INFO_LOG_LENGTH, &mut info_log_length); if info_log_length > 0 { // If there is an info log, get it. let mut info_log: Vec = Vec::with_capacity(info_log_length as usize); let mut info_log_char_count: GLsizei = 0; gl::GetProgramInfoLog( self.0, info_log_length as GLsizei, &mut info_log_char_count as *mut GLsizei, info_log.as_mut_ptr() as *mut GLchar, ); info_log.set_len(info_log_char_count as usize); debug_assert!(info_log_char_count == info_log_length - 1); String::from_utf8_lossy(&info_log).to_string() } else { // If there's no info log, return the empty string. "".to_string() } } } /// Installs the program object as part of the current rendering state. /// /// See [glUseProgram](https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glUseProgram.xhtml) pub fn use_program(&self) { // This is an IO/Global Affecting operation, but conceptually we're not // affecting the `ManagedProgram` _itself_, so we'll only require &T not // &mut T. unsafe { gl::UseProgram(self.0); } } /// Obtains the location of the uniform named within this program. The /// location returned remains valid until you re-link the program. /// /// See /// [glGetUniformLocation](https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glGetUniformLocation.xhtml) pub fn get_uniform_location>(&self, name: S) -> Result { unsafe { match CString::new(name.as_ref()) { Ok(name_c) => match gl::GetUniformLocation(self.0, name_c.as_ptr()) { -1 => Err(format!("Could not find the specified location: {:?}", name.as_ref())), out => Ok(out), }, Err(_) => Err("get_uniform_location: Could not convert the input to a CString.".to_string()), } } } } impl Drop for ManagedProgram { /// A `ManagedShader` calls /// [glDeleteProgram](https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glDeleteProgram.xhtml) /// on itself when dropped. fn drop(&mut self) { unsafe { if gl::IsProgram(self.0) == gl::TRUE { gl::DeleteProgram(self.0); } } } } /// A newtype around a shader name that will mark itself for deletion when /// dropped. #[derive(Debug, PartialEq, Eq)] pub struct ManagedShader(GLuint); impl ManagedShader { /// Makes a new `ManagedShader` value of the `ShaderType` specified. This can /// potentially fail. /// /// The `String` returned in the `Err` case always simply reports that the /// attempt to create the shader (and the attempted type) failed, not anything /// more useful than that. It's only a `String` so that this function's `Result` /// type matches up with the `Result` type of the other functions related to /// shader program creation. /// /// See /// [glCreateShader](https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glCreateShader.xhtml) pub fn create_shader(kind: GLenum) -> Result { unsafe { let shader_id = gl::CreateShader(kind); if shader_id > 0 { Ok(ManagedShader(shader_id)) } else { Err("create_shader: could not create shader".to_string()) } } } /// Gets the shader name held, in case you need to do something with a /// shader that isn't covered by this library. pub fn get_shader_name(&self) -> GLuint { self.0 } /// Assigns the given string to be the shader's source, replacing the /// existing source, if any. This requires that the input string be /// converted into a `CString`, and so it fails if the input contains a null /// byte anywhere within it. /// /// See /// [glShaderSource](https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glShaderSource.xhtml) pub fn shader_source>(&mut self, source: S) -> Result<(), String> { let source_c = CString::new(source.as_ref()).map_err(|_| "shader_source: Null byte found in source string.".to_string())?; unsafe { Ok(gl::ShaderSource(self.0, 1, &source_c.as_ptr(), null())) } } /// Instructs OpenGL to compile the shader. /// /// See /// [glCompileShader](https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glCompileShader.xhtml) pub fn compile_shader(&mut self) { unsafe { gl::CompileShader(self.0) } } /// Returns `true` if the last compile on this shader was successful, /// `false` otherwise. /// /// See /// [glGetShaderiv](https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glGetShader.xhtml) pub fn get_compile_status(&self) -> bool { unsafe { let mut param = 0; gl::GetShaderiv(self.0, gl::COMPILE_STATUS, &mut param); param == GLint::from(gl::TRUE) } } /// Obtains the info log of this shader. /// /// See [glGetShaderInfoLog](https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glGetShaderInfoLog.xhtml) pub fn get_shader_info_log(&self) -> String { unsafe { // How many bytes will the info log be? Including the null byte. let mut info_log_length: GLint = 0; gl::GetShaderiv(self.0, gl::INFO_LOG_LENGTH, &mut info_log_length); if info_log_length > 0 { // If there is an info log, get it. let mut info_log: Vec = Vec::with_capacity(info_log_length as usize); let mut info_log_char_count: GLsizei = 0; gl::GetShaderInfoLog( self.0, info_log_length as GLsizei, &mut info_log_char_count as *mut GLsizei, info_log.as_mut_ptr() as *mut GLchar, ); // This SHOULD trim away the null byte from the length of the // vector, assuming that OpenGL is behaving. info_log.set_len(info_log_length as usize); debug_assert!(info_log_char_count == info_log_length - 1); String::from_utf8_lossy(&info_log).to_string() } else { // If there's no info log, return the empty string. "".to_string() } } } } impl Drop for ManagedShader { /// Uses /// [glDeleteShader](https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glDeleteShader.xhtml) /// if the shader name held is a shader at the time of the drop. fn drop(&mut self) { unsafe { if gl::IsShader(self.0) == gl::TRUE { gl::DeleteShader(self.0) } } } }