//! This example shows how you might use an `Indexmap` in OpenGL. #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] extern crate gl; use gl::types::*; extern crate glutin; use glutin::*; extern crate retro_pixel; use retro_pixel::palettes::NES_PALETTE; 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 nes_width = 256u32; let nes_height = 240u32; let default_upscale = 3; let (start_width, start_height) = (nes_width * default_upscale, nes_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("Indexmap 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 Palette Texture let mut nes_palette_texture = 0; gl::GenTextures(1, &mut nes_palette_texture); gl::ActiveTexture(gl::TEXTURE0); let palette_texture_loc = shader_program.get_uniform_location("palette_texture").unwrap(); gl::Uniform1i(palette_texture_loc, 0); gl::BindTexture(gl::TEXTURE_1D, nes_palette_texture); gl::TexParameteri(gl::TEXTURE_1D, gl::TEXTURE_WRAP_S, gl::REPEAT as i32); gl::TexParameteri(gl::TEXTURE_1D, gl::TEXTURE_WRAP_T, gl::REPEAT as i32); gl::TexParameteri(gl::TEXTURE_1D, gl::TEXTURE_MIN_FILTER, gl::NEAREST as i32); gl::TexParameteri(gl::TEXTURE_1D, gl::TEXTURE_MAG_FILTER, gl::NEAREST as i32); // immediately upload the NES palette texture, it never changes. gl::TexImage1D( gl::TEXTURE_1D, 0, gl::RGBA as i32, NES_PALETTE.len() as i32, 0, gl::RGBA, gl::UNSIGNED_BYTE, NES_PALETTE.as_ptr() as *const _, ); gl::GenerateMipmap(gl::TEXTURE_1D); // The Indexmap Texture let mut indexmap_texture = 0; gl::GenTextures(1, &mut indexmap_texture); gl::ActiveTexture(gl::TEXTURE1); shader_program .get_uniform_location("indexmap_texture") .map(|loc| gl::Uniform1i(loc, 1)) .ok(); gl::BindTexture(gl::TEXTURE_2D, indexmap_texture); gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_S, gl::CLAMP_TO_EDGE as i32); gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_T, gl::CLAMP_TO_EDGE 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 indexmap texture, it changes from frame to frame. // Main loop let mut alien = load_alien_data(); let mut indexmap = VecImage::new(nes_width as usize, nes_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(nes_width, nes_height); } VirtualKeyCode::F2 => { window.set_inner_size(nes_width * 2, nes_height * 2); } VirtualKeyCode::F3 => { window.set_inner_size(nes_width * 3, nes_height * 3); } VirtualKeyCode::F4 => { window.set_inner_size(nes_width * 4, nes_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(); } _ => {} } } // indexmap rendering indexmap.set_all(0x30); indexmap.direct_copy(&alien, alien_position); indexmap[(8, 8)] = 0x1D; // upload the latest texture data, we pretend it's all red gl::PixelStorei(gl::UNPACK_ALIGNMENT, 1); gl::TexImage2D( gl::TEXTURE_2D, 0, gl::RED as i32, indexmap.width() as i32, indexmap.height() as i32, 0, gl::RED, gl::UNSIGNED_BYTE, indexmap.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 im = VecImage::new(8, 8); let (white, red, green, blue, other_blue) = (0x30, 0x05, 0x1A, 0x02, 0x2C); im.set_all(white); // Some eyes im[(3, 5)] = blue; im[(4, 5)] = other_blue; im[(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() { im[p] = green; } // some feet im[(2, 1)] = red; im[(2, 2)] = red; im[(5, 1)] = red; im[(5, 2)] = red; im.upscale(2) } // // SHADER STUFF // static VERT_SRC: &'static str = "#version 330 core layout (location = 0) in vec2 vPositionNDC; out vec2 screen_pixel_position; void main() { gl_Position = vec4(vPositionNDC, 1.0, 1.0); screen_pixel_position = vPositionNDC; }"; static FRAG_SRC: &'static str = "#version 330 core in vec2 screen_pixel_position; uniform sampler2D indexmap_texture; uniform sampler1D palette_texture; out vec4 FragColor; void main() { vec2 indexmap_position = (screen_pixel_position + 1.0) / 2.0; // the texture upload turns 0-255 into 0.0 to 1.0, so reverse that float index_value = texture(indexmap_texture, indexmap_position).r * 255.0; // turn the Xth index (out of 64 NES colors) into a 0.0-1.0 texture position float index_pixel = index_value / 64.0; FragColor = texture(palette_texture, index_pixel); }"; // // 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) } } } }