From 8b1ae0b382ffd8bff975c4c4f7f57b8f6689c409 Mon Sep 17 00:00:00 2001 From: Mark McCaskey Date: Sun, 22 Apr 2018 16:11:11 -0700 Subject: [PATCH 1/5] Add skeleton of GL rendering --- Cargo.toml | 5 +- src/io/graphics/gl/mod.rs | 309 ++++++++++++++++++++++++++++++++++++++ src/io/graphics/mod.rs | 2 + src/main.rs | 2 + 4 files changed, 317 insertions(+), 1 deletion(-) create mode 100644 src/io/graphics/gl/mod.rs diff --git a/Cargo.toml b/Cargo.toml index bd242c2..adf4e4b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,10 +9,11 @@ travis-ci = { repository = "MarkMcCaskey/rusty-boy", branch = "master" } maintainence = {status = "experimental"} [features] -default = [] +default = ["opengl"] asm = [] debugger = [] vulkan = [] +opengl = ["gl"] development = ["debugger"] [dependencies] @@ -32,6 +33,8 @@ vulkano-shader-derive = {version = "0.6.2", optional = true} winit = {version = "0.7.6", optional = true} vulkano-win = {version = "0.6.2", optional = true} +gl = {version = "^0.10", optional = true} + nom = {version = "^2.2", optional = true} ncurses = {version = "5.85.0", optional = true} diff --git a/src/io/graphics/gl/mod.rs b/src/io/graphics/gl/mod.rs new file mode 100644 index 0000000..68bc6be --- /dev/null +++ b/src/io/graphics/gl/mod.rs @@ -0,0 +1,309 @@ +use sdl2; +use sdl2::*; +use sdl2::rect::Point; +use sdl2::keyboard::Keycode; +use sdl2::surface::Surface; +use sdl2::pixels::PixelFormatEnum; +use sdl2::video::GLProfile; + +use cpu::Cpu; +use io::graphics::renderer::Renderer; +use io::constants::*; +use super::renderer; +use io::sound::*; +use io::applicationsettings::ApplicationSettings; +use super::renderer::EventResponse; +use io::graphics::sdl2::input::*; + +use gl; + +pub struct GlRenderer { + sdl_context: Sdl, // sdl_sound: sdl2::audio, + ctx: sdl2::video::GLContext, + window: sdl2::video::Window, + controller: Option, // storing to keep alive +} + +impl GlRenderer { + pub fn new(app_settings: &ApplicationSettings) -> Result { + let sdl_context = sdl2::init()?; + setup_audio(&sdl_context)?; + let controller = setup_controller_subsystem(&sdl_context); + + // Set up graphics and window + trace!("Opening window"); + let video_subsystem = sdl_context.video()?; + + + let gl_attr = video_subsystem.gl_attr(); + gl_attr.set_context_profile(GLProfile::Core); + gl_attr.set_context_version(4, 3); + + let window = { + let (window_width, window_height) = if app_settings.memvis_mode { + (RB_SCREEN_WIDTH, RB_SCREEN_HEIGHT) + } else { + ( + ((GB_SCREEN_WIDTH as f32) * 2.0) as u32, + ((GB_SCREEN_HEIGHT as f32) * 2.0) as u32, + ) + }; + + match video_subsystem + .window( + app_settings.rom_file_name.as_str(), + window_width, + window_height, + ) + .position_centered() + .opengl() + .build() + { + Ok(v) => v, + Err(e) => panic!("Fatal error: {}", e), + } + }; + let ctx = window.gl_create_context()?; + gl::load_with(|name| video_subsystem.gl_get_proc_address(name) as *const _); + + unsafe { + gl::ClearColor(0f32, 0f32, 0f32, 1f32); + } + + + Ok(Self { + sdl_context, + controller, + window, + ctx, + }) + } + + /// Loads a controller to be used as input if there isn't currently an active controller + pub fn load_controller_if_none_exist(&mut self) { + let should_load = if let Some(ref c) = self.controller { + !c.attached() + } else { + true + }; + + if should_load { + self.controller = setup_controller_subsystem(&self.sdl_context); + if let Some(ref c) = self.controller { + info!("Controller {} attached", c.name()); + } else { + //Note: not printing a warning here because this function is + // called every frame now + + //warn!("Could not attach controller!"); + } + } + } +} + +impl Renderer for GlRenderer { + fn draw_gameboy(&mut self, gameboy: &Cpu, app_settings: &ApplicationSettings) { + unimplemented!(); + } + + fn draw_memory_visualization(&mut self, _gameboy: &Cpu, _app_settings: &ApplicationSettings) { + unimplemented!(); + } + + fn handle_events( + &mut self, + gameboy: &mut Cpu, + app_settings: &ApplicationSettings, + ) -> Vec { + let mut ret_vec: Vec = vec![]; + for event in self.sdl_context.event_pump().unwrap().poll_iter() { + use sdl2::event::Event; + + match event { + Event::ControllerAxisMotion { + axis, value: val, .. + } => { + let deadzone = 10000; + trace!("Axis {:?} moved to {}", axis, val); + match axis { + controller::Axis::LeftX if deadzone < (val as i32).abs() => { + if val < 0 { + gameboy.press_left(); + gameboy.unpress_right(); + } else { + gameboy.press_right(); + gameboy.unpress_left(); + }; + } + controller::Axis::LeftX => { + gameboy.unpress_left(); + gameboy.unpress_right(); + } + controller::Axis::LeftY if deadzone < (val as i32).abs() => { + if val < 0 { + gameboy.press_up(); + gameboy.unpress_down(); + } else { + gameboy.press_down(); + gameboy.unpress_up(); + } + } + controller::Axis::LeftY => { + gameboy.unpress_up(); + gameboy.unpress_down(); + } + _ => {} + } + } + Event::ControllerButtonDown { button, .. } => { + trace!("Button {:?} down", button); + match button { + controller::Button::A => { + gameboy.press_a(); + // TODO: sound + // device.resume(); + } + controller::Button::B => gameboy.press_b(), + controller::Button::Back => gameboy.press_select(), + controller::Button::Start => gameboy.press_start(), + _ => (), + } + } + + Event::ControllerButtonUp { button, .. } => { + trace!("Button {:?} up", button); + match button { + controller::Button::A => { + gameboy.unpress_a(); + } + controller::Button::B => gameboy.unpress_b(), + controller::Button::Back => gameboy.unpress_select(), + controller::Button::Start => gameboy.unpress_start(), + _ => (), + } + } + /*Event::JoyDeviceAdded {..} | Event::ControllerDeviceAdded{..} => { + self.load_controller_if_none_exist(); + }*/ + Event::JoyDeviceRemoved { + which: device_id, .. + } + | Event::ControllerDeviceRemoved { + which: device_id, .. + } => { + let should_remove = if let Some(ref controller) = self.controller { + let sr = device_id == controller.instance_id(); + + if sr { + info!("Removing controller {}", controller.name()); + } + + sr + } else { + false + }; + + if should_remove { + self.controller = None; + } + } + Event::AppTerminating { .. } + | Event::Quit { .. } + | Event::KeyDown { + keycode: Some(Keycode::Escape), + .. + } => { + ret_vec.push(EventResponse::ProgramTerminated); + } + Event::KeyDown { + keycode: Some(keycode), + repeat, + .. + } => { + if !repeat { + match keycode { + Keycode::F3 => gameboy.toggle_logger(), + Keycode::R => { + // Reset/reload emu + // TODO Keep previous visualization settings + gameboy.reset(); + ret_vec.push(EventResponse::Reset); + //let gbcopy = self.initial_gameboy_state.clone(); + //gameboy = gbcopy; + gameboy.reinit_logger(); + + // // This way makes it possible to edit rom + // // with external editor and see changes + // // instantly. + // gameboy = Cpu::new(); + // gameboy.load_rom(rom_file); + } + Keycode::A => gameboy.press_a(), + Keycode::S => gameboy.press_b(), + Keycode::D => gameboy.press_select(), + Keycode::F => gameboy.press_start(), + Keycode::Up => gameboy.press_up(), + Keycode::Down => gameboy.press_down(), + Keycode::Left => gameboy.press_left(), + Keycode::Right => gameboy.press_right(), + _ => (), + } + } + } + Event::KeyUp { + keycode: Some(keycode), + repeat, + .. + } => { + if !repeat { + match keycode { + Keycode::A => gameboy.unpress_a(), + Keycode::S => gameboy.unpress_b(), + Keycode::D => gameboy.unpress_select(), + Keycode::F => gameboy.unpress_start(), + Keycode::Up => gameboy.unpress_up(), + Keycode::Down => gameboy.unpress_down(), + Keycode::Left => gameboy.unpress_left(), + Keycode::Right => gameboy.unpress_right(), + + _ => (), + } + } + } + Event::MouseButtonDown { + x, y, mouse_btn, .. + } => { + // Transform screen coordinates in UI coordinates + let click_point = display_coords_to_ui_point(app_settings.ui_scale, x, y); + + // Find clicked widget + /*for widget in &mut self.widgets { + if widget.rect.contains_point(click_point) { + widget.click(mouse_btn, click_point, gameboy); + break; + } + }*/ + } + Event::MouseWheel { y: _y, .. } => { + //self.ui_scale += y as f32; + // self.widgets[0].scale += y as f32; + } + // // Event::MouseMotion { x, y, mousestate, xrel, yrel, .. } => { + // Event::MouseMotion { x, y, .. } => { + // // Test widget position + // let mouse_pos = self.display_coords_to_ui_point(x+5, y+5); + // self.widgets[0].rect.reposition(mouse_pos); + // } + _ => (), + } + } + + return ret_vec; + } +} + +pub fn display_coords_to_ui_point(ui_scale: f32, x: i32, y: i32) -> Point { + let s_x = (x as f32 / ui_scale) as i32; + let s_y = (y as f32 / ui_scale) as i32; + Point::new(s_x, s_y) +} diff --git a/src/io/graphics/mod.rs b/src/io/graphics/mod.rs index 5ef2f92..38d26f4 100644 --- a/src/io/graphics/mod.rs +++ b/src/io/graphics/mod.rs @@ -1,6 +1,8 @@ pub mod sdl2; #[cfg(feature = "vulkan")] pub mod vulkan; +#[cfg(feature = "opengl")] +pub mod gl; pub mod renderer; /* diff --git a/src/main.rs b/src/main.rs index d947ba5..830e38d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -35,6 +35,8 @@ extern crate vulkano_shader_derive; extern crate vulkano_win; #[cfg(feature = "vulkan")] extern crate winit; +#[cfg(feature = "opengl")] +extern crate gl; /// Simple Gameboy-flavored Z80 assembler pub mod assembler; From 9d0231ba79807234f8a17506074b02ec128c906c Mon Sep 17 00:00:00 2001 From: Mark McCaskey Date: Sun, 22 Apr 2018 18:58:18 -0700 Subject: [PATCH 2/5] Further opengl skeleton --- src/io/graphics/.#vulkan.rs | 1 - src/io/graphics/gl/mod.rs | 65 +++++++++++++++++-------- src/io/graphics/gl/shaders/gameboy.frag | 7 +++ src/io/graphics/gl/shaders/gameboy.vert | 8 +++ 4 files changed, 60 insertions(+), 21 deletions(-) delete mode 120000 src/io/graphics/.#vulkan.rs create mode 100644 src/io/graphics/gl/shaders/gameboy.frag create mode 100644 src/io/graphics/gl/shaders/gameboy.vert diff --git a/src/io/graphics/.#vulkan.rs b/src/io/graphics/.#vulkan.rs deleted file mode 120000 index 307b879..0000000 --- a/src/io/graphics/.#vulkan.rs +++ /dev/null @@ -1 +0,0 @@ -mark@mark-desktop.6280:1504816853 \ No newline at end of file diff --git a/src/io/graphics/gl/mod.rs b/src/io/graphics/gl/mod.rs index 68bc6be..bdbecd6 100644 --- a/src/io/graphics/gl/mod.rs +++ b/src/io/graphics/gl/mod.rs @@ -1,21 +1,15 @@ -use sdl2; -use sdl2::*; -use sdl2::rect::Point; -use sdl2::keyboard::Keycode; -use sdl2::surface::Surface; -use sdl2::pixels::PixelFormatEnum; -use sdl2::video::GLProfile; +use sdl2::{self, *, keyboard::Keycode, pixels::PixelFormatEnum, rect::Point, surface::Surface, + video::GLProfile}; use cpu::Cpu; -use io::graphics::renderer::Renderer; -use io::constants::*; -use super::renderer; -use io::sound::*; -use io::applicationsettings::ApplicationSettings; -use super::renderer::EventResponse; -use io::graphics::sdl2::input::*; +use io::{applicationsettings::ApplicationSettings, constants::*, + graphics::{renderer::Renderer, sdl2::input::*}, sound::*}; +use super::renderer::{self, EventResponse}; -use gl; +use gl::{self, *}; + +static GB_VERT_SHADER_SOURCE: &'static str = include_str!("shaders/gameboy.vert"); +static GB_FRAG_SHADER_SOURCE: &'static str = include_str!("shaders/gameboy.frag"); pub struct GlRenderer { sdl_context: Sdl, // sdl_sound: sdl2::audio, @@ -34,7 +28,6 @@ impl GlRenderer { trace!("Opening window"); let video_subsystem = sdl_context.video()?; - let gl_attr = video_subsystem.gl_attr(); gl_attr.set_context_profile(GLProfile::Core); gl_attr.set_context_version(4, 3); @@ -66,10 +59,39 @@ impl GlRenderer { let ctx = window.gl_create_context()?; gl::load_with(|name| video_subsystem.gl_get_proc_address(name) as *const _); + use std::ffi::CString; + let vshader_src: CString = + CString::new(GB_VERT_SHADER_SOURCE).expect("Invalid vertex shader source"); + let fshader_src: CString = + CString::new(GB_FRAG_SHADER_SOURCE).expect("Invalid fragment shader source"); + let vshader_ptr = (&(&GB_VERT_SHADER_SOURCE).as_ptr() as *const *const u8); + let fshader_ptr = (&(&GB_FRAG_SHADER_SOURCE).as_ptr() as *const *const u8); unsafe { gl::ClearColor(0f32, 0f32, 0f32, 1f32); - } + let vshader_id = gl::CreateShader(VERTEX_SHADER); + let fshader_id = gl::CreateShader(FRAGMENT_SHADER); + gl::ShaderSource( + vshader_id, + 1, + vshader_ptr as _, + (&(GB_VERT_SHADER_SOURCE.len()) as *const usize) as _, + ); + gl::ShaderSource( + fshader_id, + 1, + fshader_ptr as _, + (&(GB_FRAG_SHADER_SOURCE.len()) as *const usize) as _, + ); + + gl::CompileShader(vshader_id); + gl::CompileShader(fshader_id); + let gb_program_id = gl::CreateProgram(); + gl::AttachShader(gb_program_id, vshader_id); + gl::AttachShader(gb_program_id, fshader_id); + gl::LinkProgram(gb_program_id); + gl::UseProgram(gb_program_id); + } Ok(Self { sdl_context, @@ -103,7 +125,11 @@ impl GlRenderer { impl Renderer for GlRenderer { fn draw_gameboy(&mut self, gameboy: &Cpu, app_settings: &ApplicationSettings) { - unimplemented!(); + //unsafe { gl::DrawElements(TRIANGLES, , UNSIGNED_SHORT, 0u32 as _) } + self.window.gl_swap_window(); + unsafe { + gl::Clear(COLOR_BUFFER_BIT); + } } fn draw_memory_visualization(&mut self, _gameboy: &Cpu, _app_settings: &ApplicationSettings) { @@ -282,8 +308,7 @@ impl Renderer for GlRenderer { widget.click(mouse_btn, click_point, gameboy); break; } - }*/ - } + }*/ } Event::MouseWheel { y: _y, .. } => { //self.ui_scale += y as f32; // self.widgets[0].scale += y as f32; diff --git a/src/io/graphics/gl/shaders/gameboy.frag b/src/io/graphics/gl/shaders/gameboy.frag new file mode 100644 index 0000000..c4401c8 --- /dev/null +++ b/src/io/graphics/gl/shaders/gameboy.frag @@ -0,0 +1,7 @@ +#version 430 core + +in vec4 frag_rgba; + +void main() { + gl_FragColor = vec(1,0,1,1); +} \ No newline at end of file diff --git a/src/io/graphics/gl/shaders/gameboy.vert b/src/io/graphics/gl/shaders/gameboy.vert new file mode 100644 index 0000000..9da0b99 --- /dev/null +++ b/src/io/graphics/gl/shaders/gameboy.vert @@ -0,0 +1,8 @@ +#version 430 core + +layout(location = 0) in vec2 vert_xy; +out vec4 frag_rgba; + +void main() { + frag_rgba = vec(1,1,0,1); +} From ee7fc3fe593bdc412d4739cfcbd0463a88f4d3a9 Mon Sep 17 00:00:00 2001 From: Mark McCaskey Date: Mon, 4 Mar 2019 11:30:37 -0800 Subject: [PATCH 3/5] update edition --- src/io/graphics/gl/mod.rs | 18 +++++++++--------- src/io/graphics/mod.rs | 5 ++--- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/src/io/graphics/gl/mod.rs b/src/io/graphics/gl/mod.rs index bdbecd6..52afdbd 100644 --- a/src/io/graphics/gl/mod.rs +++ b/src/io/graphics/gl/mod.rs @@ -1,8 +1,8 @@ use sdl2::{self, *, keyboard::Keycode, pixels::PixelFormatEnum, rect::Point, surface::Surface, video::GLProfile}; -use cpu::Cpu; -use io::{applicationsettings::ApplicationSettings, constants::*, +use crate::cpu::Cpu; +use crate::io::{applicationsettings::ApplicationSettings, constants::*, graphics::{renderer::Renderer, sdl2::input::*}, sound::*}; use super::renderer::{self, EventResponse}; @@ -60,12 +60,12 @@ impl GlRenderer { gl::load_with(|name| video_subsystem.gl_get_proc_address(name) as *const _); use std::ffi::CString; - let vshader_src: CString = + let _vshader_src: CString = CString::new(GB_VERT_SHADER_SOURCE).expect("Invalid vertex shader source"); - let fshader_src: CString = + let _fshader_src: CString = CString::new(GB_FRAG_SHADER_SOURCE).expect("Invalid fragment shader source"); - let vshader_ptr = (&(&GB_VERT_SHADER_SOURCE).as_ptr() as *const *const u8); - let fshader_ptr = (&(&GB_FRAG_SHADER_SOURCE).as_ptr() as *const *const u8); + let vshader_ptr = &(&GB_VERT_SHADER_SOURCE).as_ptr() as *const *const u8; + let fshader_ptr = &(&GB_FRAG_SHADER_SOURCE).as_ptr() as *const *const u8; unsafe { gl::ClearColor(0f32, 0f32, 0f32, 1f32); let vshader_id = gl::CreateShader(VERTEX_SHADER); @@ -124,7 +124,7 @@ impl GlRenderer { } impl Renderer for GlRenderer { - fn draw_gameboy(&mut self, gameboy: &Cpu, app_settings: &ApplicationSettings) { + fn draw_gameboy(&mut self, _gameboy: &Cpu, _app_settings: &ApplicationSettings) { //unsafe { gl::DrawElements(TRIANGLES, , UNSIGNED_SHORT, 0u32 as _) } self.window.gl_swap_window(); unsafe { @@ -297,10 +297,10 @@ impl Renderer for GlRenderer { } } Event::MouseButtonDown { - x, y, mouse_btn, .. + x, y, mouse_btn: _, .. } => { // Transform screen coordinates in UI coordinates - let click_point = display_coords_to_ui_point(app_settings.ui_scale, x, y); + let _click_point = display_coords_to_ui_point(app_settings.ui_scale, x, y); // Find clicked widget /*for widget in &mut self.widgets { diff --git a/src/io/graphics/mod.rs b/src/io/graphics/mod.rs index c1824cc..e845542 100644 --- a/src/io/graphics/mod.rs +++ b/src/io/graphics/mod.rs @@ -1,10 +1,9 @@ +#[cfg(feature = "opengl")] +pub mod gl; pub mod renderer; pub mod sdl2; #[cfg(feature = "vulkan")] pub mod vulkan; -#[cfg(feature = "opengl")] -pub mod gl; -pub mod renderer; /* #[cfg(not(feature = "vulkan"))] From 6f802d105995273f1cc18e97823406fbee0248d9 Mon Sep 17 00:00:00 2001 From: Mark McCaskey Date: Sat, 25 May 2019 20:55:30 -0700 Subject: [PATCH 4/5] significantly improve gl skeleton --- Cargo.toml | 2 +- src/io/applicationsettings.rs | 5 +- src/io/applicationstate.rs | 9 +- src/io/arguments.rs | 11 ++ src/io/graphics/gl/mod.rs | 191 +++++++++++++++++++----- src/io/graphics/gl/shaders/gameboy.frag | 8 +- src/io/graphics/gl/shaders/gameboy.vert | 9 +- src/io/graphics/gl/util.rs | 122 +++++++++++++++ 8 files changed, 309 insertions(+), 48 deletions(-) create mode 100644 src/io/graphics/gl/util.rs diff --git a/Cargo.toml b/Cargo.toml index 825e9c2..957a6b2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,7 +34,7 @@ vulkano-shader-derive = {version = "0.6.2", optional = true} winit = {version = "0.7.6", optional = true} vulkano-win = {version = "0.6.2", optional = true} -gl = {version = "^0.10", optional = true} +gl = {version = "^0.12", optional = true} nom = {version = "^2.2", optional = true} ncurses = {version = "^5.85.0", optional = true} diff --git a/src/io/applicationsettings.rs b/src/io/applicationsettings.rs index 930cd6e..fee39c4 100644 --- a/src/io/applicationsettings.rs +++ b/src/io/applicationsettings.rs @@ -1,8 +1,8 @@ //! Stores all settings related to the application from a user perspective +use crate::io::constants::{APP_INFO, SCALE}; use app_dirs::*; use clap::ArgMatches; -use crate::io::constants::{APP_INFO, SCALE}; use std::path::PathBuf; use log::LevelFilter; @@ -19,6 +19,7 @@ pub struct ApplicationSettings { pub memvis_mode: bool, pub debugger_on: bool, pub vulkan_mode: bool, + pub gl_mode: bool, config_path: Option, pub data_path: Option, pub ui_scale: f32, @@ -35,6 +36,7 @@ impl ApplicationSettings { let trace_mode = arguments.is_present("trace"); let memvis_mode = arguments.is_present("visualize"); let vulkan_mode = arguments.is_present("vulkan"); + let gl_mode = arguments.is_present("opengl"); // Set up logging let stdout = ConsoleAppender::builder() @@ -91,6 +93,7 @@ impl ApplicationSettings { trace_mode, memvis_mode, vulkan_mode, + gl_mode, config_path, data_path, debugger_on: should_debugger, diff --git a/src/io/applicationstate.rs b/src/io/applicationstate.rs index 45b445b..bf8d2ef 100644 --- a/src/io/applicationstate.rs +++ b/src/io/applicationstate.rs @@ -64,7 +64,14 @@ impl ApplicationState { Box::new(graphics::sdl2::Sdl2Renderer::new(&app_settings)?) }; - #[cfg(not(feature = "vulkan"))] + #[cfg(feature = "opengl")] + let renderer: Box = if dbg!(app_settings.gl_mode) { + Box::new(graphics::gl::GlRenderer::new(&app_settings)?) + } else { + Box::new(graphics::sdl2::Sdl2Renderer::new(&app_settings)?) + }; + + #[cfg(not(any(feature = "vulkan", feature = "opengl")))] let renderer: Box = Box::new(graphics::sdl2::Sdl2Renderer::new(&app_settings)?); let gbcopy = gameboy.clone(); diff --git a/src/io/arguments.rs b/src/io/arguments.rs index cddd8bd..c2036d8 100644 --- a/src/io/arguments.rs +++ b/src/io/arguments.rs @@ -59,5 +59,16 @@ pub fn read_arguments<'input>() -> ArgMatches<'input> { ); } + #[cfg(feature = "opengl")] + { + app_builder = app_builder.arg( + Arg::with_name("opengl") + .short("g") + .long("opengl") + .help("Runs graphics on the GPU with OpenGL") + .takes_value(false), + ); + } + app_builder.get_matches() } diff --git a/src/io/graphics/gl/mod.rs b/src/io/graphics/gl/mod.rs index 52afdbd..48b0853 100644 --- a/src/io/graphics/gl/mod.rs +++ b/src/io/graphics/gl/mod.rs @@ -1,12 +1,20 @@ -use sdl2::{self, *, keyboard::Keycode, pixels::PixelFormatEnum, rect::Point, surface::Surface, - video::GLProfile}; +pub mod util; + +use sdl2::{ + self, keyboard::Keycode, pixels::PixelFormatEnum, rect::Point, surface::Surface, + video::GLProfile, *, +}; -use crate::cpu::Cpu; -use crate::io::{applicationsettings::ApplicationSettings, constants::*, - graphics::{renderer::Renderer, sdl2::input::*}, sound::*}; use super::renderer::{self, EventResponse}; +use crate::cpu::Cpu; +use crate::io::{ + applicationsettings::ApplicationSettings, + constants::*, + graphics::{renderer::Renderer, sdl2::input::*}, + sound::*, +}; -use gl::{self, *}; +use gl::{self, types::*, *}; static GB_VERT_SHADER_SOURCE: &'static str = include_str!("shaders/gameboy.vert"); static GB_FRAG_SHADER_SOURCE: &'static str = include_str!("shaders/gameboy.frag"); @@ -15,7 +23,10 @@ pub struct GlRenderer { sdl_context: Sdl, // sdl_sound: sdl2::audio, ctx: sdl2::video::GLContext, window: sdl2::video::Window, + gb_program: util::Program, controller: Option, // storing to keep alive + num_tiles: i32, + gb_vao: u32, } impl GlRenderer { @@ -32,16 +43,15 @@ impl GlRenderer { gl_attr.set_context_profile(GLProfile::Core); gl_attr.set_context_version(4, 3); + let (window_width, window_height) = if app_settings.memvis_mode { + (RB_SCREEN_WIDTH, RB_SCREEN_HEIGHT) + } else { + ( + ((GB_SCREEN_WIDTH as f32) * 2.0) as u32, + ((GB_SCREEN_HEIGHT as f32) * 2.0) as u32, + ) + }; let window = { - let (window_width, window_height) = if app_settings.memvis_mode { - (RB_SCREEN_WIDTH, RB_SCREEN_HEIGHT) - } else { - ( - ((GB_SCREEN_WIDTH as f32) * 2.0) as u32, - ((GB_SCREEN_HEIGHT as f32) * 2.0) as u32, - ) - }; - match video_subsystem .window( app_settings.rom_file_name.as_str(), @@ -58,46 +68,118 @@ impl GlRenderer { }; let ctx = window.gl_create_context()?; gl::load_with(|name| video_subsystem.gl_get_proc_address(name) as *const _); + window.gl_set_context_to_current().unwrap(); + unsafe { + gl::Enable(gl::DEBUG_OUTPUT); + gl::DebugMessageCallback(dbg_msg, std::ptr::null_mut()); + } + info!("Here"); use std::ffi::CString; - let _vshader_src: CString = + let vshader_src: CString = CString::new(GB_VERT_SHADER_SOURCE).expect("Invalid vertex shader source"); - let _fshader_src: CString = + let fshader_src: CString = CString::new(GB_FRAG_SHADER_SOURCE).expect("Invalid fragment shader source"); - let vshader_ptr = &(&GB_VERT_SHADER_SOURCE).as_ptr() as *const *const u8; - let fshader_ptr = &(&GB_FRAG_SHADER_SOURCE).as_ptr() as *const *const u8; - unsafe { + let gb_program = unsafe { + gl::Viewport(0, 0, window_width as i32, window_height as i32); gl::ClearColor(0f32, 0f32, 0f32, 1f32); - let vshader_id = gl::CreateShader(VERTEX_SHADER); - let fshader_id = gl::CreateShader(FRAGMENT_SHADER); - gl::ShaderSource( - vshader_id, - 1, - vshader_ptr as _, - (&(GB_VERT_SHADER_SOURCE.len()) as *const usize) as _, + let gb_program = util::Program::from_shaders(&[ + util::Shader::from_vert_source(&vshader_src)?, + util::Shader::from_frag_source(&fshader_src)?, + ])?; + gl::UseProgram(gb_program.id()); + gb_program + }; + + let mut verts: Vec = vec![]; + let num_tiles = 32; + let num_tilesf = num_tiles as f32; + for i in 0..num_tiles { + for j in 0..num_tiles { + let bot_right_x = (((i as f32) / num_tilesf) - 0.5) * 2.; + let bot_right_y = (((j as f32) / num_tilesf) - 0.5) * 2.; + verts.push(dbg!(bot_right_x)); + verts.push(bot_right_y); + verts.push(1.); + verts.push(0.); + verts.push(0.); + + verts.push(dbg!(bot_right_x + (1.0 / num_tilesf * 2.))); + verts.push(bot_right_y); + verts.push(0.); + verts.push(1.); + verts.push(0.); + + verts.push(bot_right_x + (1.0 / num_tilesf)); + verts.push(bot_right_y + (1.0 / num_tilesf * 2.)); + verts.push(0.); + verts.push(0.); + verts.push(1.); + } + } + let mut vao = 0; + let mut vbo = 0; + + unsafe { + // Create a Vertex Buffer Object and copy the vertex data to it + gl::GenBuffers(1, &mut vbo); + gl::BindBuffer(gl::ARRAY_BUFFER, vbo); + gl::BufferData( + gl::ARRAY_BUFFER, + (verts.len() * std::mem::size_of::()) as GLsizeiptr, + verts.as_ptr() as *const GLvoid, + gl::STATIC_DRAW, + ); + gl::BindBuffer(gl::ARRAY_BUFFER, 0); + + // Create Vertex Array Object + gl::GenVertexArrays(1, &mut vao); + gl::BindVertexArray(vao); + gl::BindBuffer(gl::ARRAY_BUFFER, vbo); + + // Specify the layout of the vertex data + /* let pos_attr = gl::GetAttribLocation( + self.gb_program.id(), + CString::new("position").unwrap().as_ptr(), + );*/ + gl::EnableVertexAttribArray(0); //pos_attr as GLuint); + gl::VertexAttribPointer( + 0, //dbg!(pos_attr) as GLuint, + 2, + gl::FLOAT, + gl::FALSE as GLboolean, + (5 * std::mem::size_of::()) as gl::types::GLint, + std::ptr::null(), ); - gl::ShaderSource( - fshader_id, + gl::EnableVertexAttribArray(1); + gl::VertexAttribPointer( 1, - fshader_ptr as _, - (&(GB_FRAG_SHADER_SOURCE.len()) as *const usize) as _, + 3, + gl::FLOAT, + gl::FALSE, + (5 * std::mem::size_of::()) as gl::types::GLint, + (2 * std::mem::size_of::()) as *const gl::types::GLvoid, ); - gl::CompileShader(vshader_id); - gl::CompileShader(fshader_id); - let gb_program_id = gl::CreateProgram(); - gl::AttachShader(gb_program_id, vshader_id); - gl::AttachShader(gb_program_id, fshader_id); - gl::LinkProgram(gb_program_id); - gl::UseProgram(gb_program_id); + gl::BindBuffer(gl::ARRAY_BUFFER, 0); + gl::BindVertexArray(0); + + /*gl::BindFragDataLocation( + self.gb_program.id(), + 0, + CString::new("out_color").unwrap().as_ptr(), + );*/ } Ok(Self { sdl_context, controller, window, + gb_program, ctx, + num_tiles, + gb_vao: vao, }) } @@ -125,11 +207,15 @@ impl GlRenderer { impl Renderer for GlRenderer { fn draw_gameboy(&mut self, _gameboy: &Cpu, _app_settings: &ApplicationSettings) { - //unsafe { gl::DrawElements(TRIANGLES, , UNSIGNED_SHORT, 0u32 as _) } - self.window.gl_swap_window(); + info!("In draw"); unsafe { + // Use shader program + gl::UseProgram(self.gb_program.id()); + gl::BindVertexArray(self.gb_vao); gl::Clear(COLOR_BUFFER_BIT); + gl::DrawArrays(gl::TRIANGLES, 0, self.num_tiles * self.num_tiles * 3); } + self.window.gl_swap_window(); } fn draw_memory_visualization(&mut self, _gameboy: &Cpu, _app_settings: &ApplicationSettings) { @@ -308,7 +394,8 @@ impl Renderer for GlRenderer { widget.click(mouse_btn, click_point, gameboy); break; } - }*/ } + }*/ + } Event::MouseWheel { y: _y, .. } => { //self.ui_scale += y as f32; // self.widgets[0].scale += y as f32; @@ -332,3 +419,25 @@ pub fn display_coords_to_ui_point(ui_scale: f32, x: i32, y: i32) -> Point { let s_y = (y as f32 / ui_scale) as i32; Point::new(s_x, s_y) } + +extern "system" fn dbg_msg( + src: u32, + ty: u32, + id: GLuint, + sev: GLuint, + len: GLsizei, + msg: *const i8, + user_param: *mut std::ffi::c_void, +) { + unsafe { + println!( + "{} {} {} {} {} {}", + src, + ty, + id, + sev, + len, + std::ffi::CStr::from_ptr(msg).to_str().unwrap().to_string() + ); + } +} diff --git a/src/io/graphics/gl/shaders/gameboy.frag b/src/io/graphics/gl/shaders/gameboy.frag index c4401c8..9618559 100644 --- a/src/io/graphics/gl/shaders/gameboy.frag +++ b/src/io/graphics/gl/shaders/gameboy.frag @@ -1,7 +1,11 @@ #version 430 core -in vec4 frag_rgba; +in VS_OUTPUT { + vec3 Color; +} IN; + +out vec4 Color; void main() { - gl_FragColor = vec(1,0,1,1); + Color = vec4(IN.Color, 1.0f); } \ No newline at end of file diff --git a/src/io/graphics/gl/shaders/gameboy.vert b/src/io/graphics/gl/shaders/gameboy.vert index 9da0b99..f24bb6a 100644 --- a/src/io/graphics/gl/shaders/gameboy.vert +++ b/src/io/graphics/gl/shaders/gameboy.vert @@ -1,8 +1,13 @@ #version 430 core layout(location = 0) in vec2 vert_xy; -out vec4 frag_rgba; +layout(location = 1) in vec3 Color; + +out VS_OUTPUT { + vec3 Color; +} OUT; void main() { - frag_rgba = vec(1,1,0,1); + gl_Position = vec4(vert_xy, 0.0, 1.0); + OUT.Color = Color; } diff --git a/src/io/graphics/gl/util.rs b/src/io/graphics/gl/util.rs new file mode 100644 index 0000000..ae7db7f --- /dev/null +++ b/src/io/graphics/gl/util.rs @@ -0,0 +1,122 @@ +//! Code taken from http://nercury.github.io/rust/opengl/tutorial/2018/02/10/opengl-in-rust-from-scratch-03-compiling-shaders.html +//! Thanks! +use gl; +use std; +use std::ffi::{CStr, CString}; + +pub struct Shader { + id: gl::types::GLuint, +} + +impl Shader { + pub fn from_source(source: &CStr, kind: gl::types::GLenum) -> Result { + let id = shader_from_source(source, kind)?; + Ok(Shader { id }) + } + + pub fn from_vert_source(source: &CStr) -> Result { + Shader::from_source(source, gl::VERTEX_SHADER) + } + + pub fn from_frag_source(source: &CStr) -> Result { + Shader::from_source(source, gl::FRAGMENT_SHADER) + } + + pub fn id(&self) -> gl::types::GLuint { + self.id + } +} + +impl Drop for Shader { + fn drop(&mut self) { + unsafe { + gl::DeleteShader(self.id); + } + } +} + +fn shader_from_source(source: &CStr, kind: gl::types::GLenum) -> Result { + let id = unsafe { gl::CreateShader(kind) }; + unsafe { + gl::ShaderSource(id, 1, &source.as_ptr(), std::ptr::null()); + gl::CompileShader(id); + } + + let mut success: gl::types::GLint = 1; + unsafe { + gl::GetShaderiv(id, gl::COMPILE_STATUS, &mut success); + } + + if success == 0 { + let mut len: gl::types::GLint = 0; + unsafe { + gl::GetShaderiv(id, gl::INFO_LOG_LENGTH, &mut len); + } + + let error = create_whitespace_cstring_with_len(len as usize); + + unsafe { + gl::GetShaderInfoLog( + id, + len, + std::ptr::null_mut(), + error.as_ptr() as *mut gl::types::GLchar, + ); + } + + return Err(error.to_string_lossy().into_owned()); + } + + Ok(id) +} + +fn create_whitespace_cstring_with_len(len: usize) -> CString { + // allocate buffer of correct size + let mut buffer: Vec = Vec::with_capacity(len + 1); + // fill it with len spaces + buffer.extend([b' '].iter().cycle().take(len)); + // convert buffer to CString + unsafe { CString::from_vec_unchecked(buffer) } +} + +pub struct Program { + id: gl::types::GLuint, +} + +impl Program { + pub fn from_shaders(shaders: &[Shader]) -> Result { + let program_id = unsafe { gl::CreateProgram() }; + + for shader in shaders { + unsafe { + gl::AttachShader(program_id, shader.id()); + } + } + + unsafe { + gl::LinkProgram(program_id); + } + + // continue with error handling here + + for shader in shaders { + unsafe { + gl::DetachShader(program_id, shader.id()); + } + } + + Ok(Program { id: program_id }) + } + + pub fn id(&self) -> gl::types::GLuint { + self.id + } +} + +impl Drop for Program { + fn drop(&mut self) { + unsafe { + gl::DeleteProgram(self.id); + } + } +} From 18517d4e5c0894f572189155c627a003a70b428f Mon Sep 17 00:00:00 2001 From: Mark McCaskey Date: Sun, 2 Jun 2019 12:58:59 -0700 Subject: [PATCH 5/5] massive changes, pushing debug code --- Cargo.toml | 4 +- lib/rusty-boy-derive/Cargo.toml | 13 ++ lib/rusty-boy-derive/src/lib.rs | 85 +++++++ src/io/graphics/gl/mod.rs | 217 +++++++++++------- src/io/graphics/gl/shaders/gameboy.frag | 5 +- src/io/graphics/gl/shaders/gameboy.vert | 3 + src/io/graphics/gl/util/buffer.rs | 139 +++++++++++ src/io/graphics/gl/util/data.rs | 66 ++++++ src/io/graphics/gl/util/mod.rs | 15 ++ .../graphics/gl/{util.rs => util/shader.rs} | 6 + src/io/graphics/gl/util/texture.rs | 57 +++++ 11 files changed, 525 insertions(+), 85 deletions(-) create mode 100644 lib/rusty-boy-derive/Cargo.toml create mode 100644 lib/rusty-boy-derive/src/lib.rs create mode 100644 src/io/graphics/gl/util/buffer.rs create mode 100644 src/io/graphics/gl/util/data.rs create mode 100644 src/io/graphics/gl/util/mod.rs rename src/io/graphics/gl/{util.rs => util/shader.rs} (96%) create mode 100644 src/io/graphics/gl/util/texture.rs diff --git a/Cargo.toml b/Cargo.toml index 957a6b2..f86c1d1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,7 @@ default = ["opengl"] asm = ["nom"] debugger = ["ncurses", "nom"] vulkan = [] -opengl = ["gl"] +opengl = ["gl", "rusty-boy-derive"] development = ["debugger"] [dependencies] @@ -28,7 +28,9 @@ serde_derive = "^1.0.0" app_dirs = "^1.1.1" time = "^0.1" lazy_static = "^1.0" +bmp = "0.4" +rusty-boy-derive = { path = "lib/rusty-boy-derive", optional = true } vulkano = {version = "0.6.2", optional = true} vulkano-shader-derive = {version = "0.6.2", optional = true} winit = {version = "0.7.6", optional = true} diff --git a/lib/rusty-boy-derive/Cargo.toml b/lib/rusty-boy-derive/Cargo.toml new file mode 100644 index 0000000..be337d1 --- /dev/null +++ b/lib/rusty-boy-derive/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "rusty-boy-derive" +version = "0.1.1" +authors = ["Mark McCaskey"] +edition = "2018" +license = "MIT" + +[dependencies] +quote = "0.6" +syn = "0.15" + +[lib] +proc-macro = true \ No newline at end of file diff --git a/lib/rusty-boy-derive/src/lib.rs b/lib/rusty-boy-derive/src/lib.rs new file mode 100644 index 0000000..18f1984 --- /dev/null +++ b/lib/rusty-boy-derive/src/lib.rs @@ -0,0 +1,85 @@ +#![recursion_limit = "128"] + +// Code from and/or inspired by http://nercury.github.io/rust/opengl/tutorial/2018/07/11/opengl-in-rust-from-scratch-10-procedural-macros.html + +extern crate proc_macro; +extern crate syn; +#[macro_use] +extern crate quote; + +use syn::Token; + +#[proc_macro_derive(VertexAttribPointers, attributes(location))] +pub fn vertex_attrib_pointers_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let ast = syn::parse_macro_input!(input as syn::DeriveInput); + let gen = generate_impl(ast); + gen +} + +fn generate_impl(ast: syn::DeriveInput) -> proc_macro::TokenStream { + let ident = &ast.ident; + let generics = &ast.generics; + let where_clause = &ast.generics.where_clause; + + let fields_vertex_attrib_pointer = generate_vertex_attrib_pointer_calls(ast.data); + let tok_strm2 = quote! { + impl #ident #generics #where_clause { + fn vertex_attrib_pointers() { + let stride = std::mem::size_of::(); + let offset = 0; + + #(#fields_vertex_attrib_pointer)* + } + } + }; + //panic!("generate impl quote: {:#?}", tok_strm2); + tok_strm2.into() +} + +fn generate_vertex_attrib_pointer_calls(body: syn::Data) -> Vec> { + match body { + syn::Data::Enum(_) => panic!("VertexAttribPointers can not be implemented for enums"), + syn::Data::Union(_) => panic!("VertexAttribPointers can not be implemented for unions"), + syn::Data::Struct(syn::DataStruct { + fields: syn::Fields::Unit, + .. + }) => panic!("VertexAttribPointers can not be implemented for Unit structs"), + syn::Data::Struct(syn::DataStruct { + fields: syn::Fields::Unnamed(_), + .. + }) => panic!("VertexAttribPointers can not be implemented for Tuple structs"), + syn::Data::Struct(syn::DataStruct { + fields: syn::Fields::Named(syn::FieldsNamed { named, .. }), + .. + }) => named + .into_iter() + .map(generate_struct_field_vertex_attrib_pointer_call) + .collect(), + } +} + +fn generate_struct_field_vertex_attrib_pointer_call(field: syn::Field) -> Box { + let field_name = field.ident.map(|id| format!("{}", &id)).unwrap_or_default(); + let location_attr = field + .attrs + .into_iter() + .filter(|a| a.path.segments.iter().next().unwrap().ident.to_string() == "location") + .next() + .unwrap_or_else(|| { + panic!( + "Field {:?} is missing #[location = ?] attribute", + field_name + ) + }); + + let location_value_literal = location_attr.tts.into_iter().skip(1).next(); + + let field_ty = &field.ty; + Box::new(quote! { + let location = #location_value_literal; + unsafe { + #field_ty::vertex_attrib_pointer(stride, location, offset); + } + let offset = offset + std::mem::size_of::<#field_ty>(); + }) +} diff --git a/src/io/graphics/gl/mod.rs b/src/io/graphics/gl/mod.rs index 48b0853..e03ab40 100644 --- a/src/io/graphics/gl/mod.rs +++ b/src/io/graphics/gl/mod.rs @@ -14,19 +14,53 @@ use crate::io::{ sound::*, }; -use gl::{self, types::*, *}; +use gl::{self, types::*}; +use rusty_boy_derive::VertexAttribPointers; +use util::*; static GB_VERT_SHADER_SOURCE: &'static str = include_str!("shaders/gameboy.vert"); static GB_FRAG_SHADER_SOURCE: &'static str = include_str!("shaders/gameboy.frag"); +fn hsv_to_rgb(h: u32, s: u32, v: u32) -> u32 { + if s == 0 { + return v | (v << 8) | (v << 16); + } + let region = h / 43; + let remainder = (h - region * 43) * 6; + + let p = (v * (255 - s)) >> 8; + let q = (v * (255 - ((s * remainder) >> 8))) >> 8; + let t = (v * (255 - ((s * (255 - remainder)) >> 8))) >> 8; + match region { + 0 => v << 24 | t << 16 | p << 8, + 1 => q << 24 | v << 16 | p << 8, + 2 => p << 24 | v << 16 | t << 8, + 3 => p << 24 | q << 16 | v << 8, + 4 => t << 24 | p << 16 | v << 8, + _ => v << 24 | p << 16 | q << 8, + } +} + pub struct GlRenderer { sdl_context: Sdl, // sdl_sound: sdl2::audio, ctx: sdl2::video::GLContext, window: sdl2::video::Window, - gb_program: util::Program, + gb_program: Program, controller: Option, // storing to keep alive num_tiles: i32, - gb_vao: u32, + gb_vao: VertexArray, + background_texture: Texture, +} + +#[derive(VertexAttribPointers, Copy, Clone, Debug)] +#[repr(C, packed)] +struct GbScreenVertex { + #[location = 0] + pos: data::f32_f32, + #[location = 1] + color: data::f32_f32_f32, + #[location = 2] + texcoord: data::f32_f32, } impl GlRenderer { @@ -84,94 +118,101 @@ impl GlRenderer { gl::Viewport(0, 0, window_width as i32, window_height as i32); gl::ClearColor(0f32, 0f32, 0f32, 1f32); - let gb_program = util::Program::from_shaders(&[ - util::Shader::from_vert_source(&vshader_src)?, - util::Shader::from_frag_source(&fshader_src)?, + let gb_program = Program::from_shaders(&[ + Shader::from_vert_source(&vshader_src)?, + Shader::from_frag_source(&fshader_src)?, ])?; gl::UseProgram(gb_program.id()); gb_program }; - let mut verts: Vec = vec![]; + let mut verts: Vec = vec![]; + let mut indices: Vec = vec![]; let num_tiles = 32; let num_tilesf = num_tiles as f32; + let mut counter = 0; for i in 0..num_tiles { for j in 0..num_tiles { - let bot_right_x = (((i as f32) / num_tilesf) - 0.5) * 2.; - let bot_right_y = (((j as f32) / num_tilesf) - 0.5) * 2.; - verts.push(dbg!(bot_right_x)); - verts.push(bot_right_y); - verts.push(1.); - verts.push(0.); - verts.push(0.); - - verts.push(dbg!(bot_right_x + (1.0 / num_tilesf * 2.))); - verts.push(bot_right_y); - verts.push(0.); - verts.push(1.); - verts.push(0.); - - verts.push(bot_right_x + (1.0 / num_tilesf)); - verts.push(bot_right_y + (1.0 / num_tilesf * 2.)); - verts.push(0.); - verts.push(0.); - verts.push(1.); + let denormal_br_x = ((i as f32) / num_tilesf); + let denormal_br_y = ((j as f32) / num_tilesf); + let bot_right_x = (denormal_br_x - 0.5) * 2.; + let bot_right_y = (denormal_br_y - 0.5) * 2.; + + verts.push(GbScreenVertex { + pos: (bot_right_x, bot_right_y).into(), + color: (1., 0., 0.).into(), + texcoord: (denormal_br_x, denormal_br_y).into(), + }); + verts.push(GbScreenVertex { + pos: (bot_right_x + (1.0 / num_tilesf * 2.), bot_right_y).into(), + color: (0., 1., 0.).into(), + texcoord: (denormal_br_x + (1.0 / num_tilesf), denormal_br_y).into(), + }); + verts.push(GbScreenVertex { + pos: ( + bot_right_x + (1.0 / num_tilesf * 2.), + bot_right_y + (1.0 / num_tilesf * 2.), + ) + .into(), + color: (0., 0., 1.).into(), + texcoord: ( + denormal_br_x + (1.0 / num_tilesf), + denormal_br_y + (1.0 / num_tilesf), + ) + .into(), + }); + verts.push(GbScreenVertex { + pos: (bot_right_x, bot_right_y + (1.0 / num_tilesf * 2.)).into(), + color: (0., 1., 1.).into(), + texcoord: (denormal_br_x, denormal_br_y + (1.0 / num_tilesf)).into(), + }); + + indices.push(counter); + indices.push(counter + 1); + indices.push(counter + 2); + indices.push(counter); + indices.push(counter + 2); + indices.push(counter + 3); + counter += 4; } } - let mut vao = 0; - let mut vbo = 0; - - unsafe { - // Create a Vertex Buffer Object and copy the vertex data to it - gl::GenBuffers(1, &mut vbo); - gl::BindBuffer(gl::ARRAY_BUFFER, vbo); - gl::BufferData( - gl::ARRAY_BUFFER, - (verts.len() * std::mem::size_of::()) as GLsizeiptr, - verts.as_ptr() as *const GLvoid, - gl::STATIC_DRAW, - ); - gl::BindBuffer(gl::ARRAY_BUFFER, 0); - - // Create Vertex Array Object - gl::GenVertexArrays(1, &mut vao); - gl::BindVertexArray(vao); - gl::BindBuffer(gl::ARRAY_BUFFER, vbo); - - // Specify the layout of the vertex data - /* let pos_attr = gl::GetAttribLocation( - self.gb_program.id(), - CString::new("position").unwrap().as_ptr(), - );*/ - gl::EnableVertexAttribArray(0); //pos_attr as GLuint); - gl::VertexAttribPointer( - 0, //dbg!(pos_attr) as GLuint, - 2, - gl::FLOAT, - gl::FALSE as GLboolean, - (5 * std::mem::size_of::()) as gl::types::GLint, - std::ptr::null(), - ); - gl::EnableVertexAttribArray(1); - gl::VertexAttribPointer( - 1, - 3, - gl::FLOAT, - gl::FALSE, - (5 * std::mem::size_of::()) as gl::types::GLint, - (2 * std::mem::size_of::()) as *const gl::types::GLvoid, - ); - - gl::BindBuffer(gl::ARRAY_BUFFER, 0); - gl::BindVertexArray(0); - - /*gl::BindFragDataLocation( - self.gb_program.id(), - 0, - CString::new("out_color").unwrap().as_ptr(), - );*/ + let vbo = ArrayBuffer::new(); + vbo.bind(); + vbo.static_draw_data(&verts); + vbo.unbind(); + let vao = VertexArray::new(); + vao.bind(); + vbo.bind(); + GbScreenVertex::vertex_attrib_pointers(); + let ibo = ElementArrayBuffer::new(); + ibo.bind(); + ibo.static_draw_data(&indices); + vbo.unbind(); + vao.unbind(); + const TEX_X: i32 = 256; + const TEX_Y: i32 = 256; + let texture = Texture::new(TEX_X, TEX_Y); + texture.bind(); + /*let mut pixels = [0u32; (TEX_X * TEX_Y) as usize]; + for i in 0..TEX_X { + for j in 0..TEX_Y { + pixels[((i * TEX_Y) + j) as usize] = hsv_to_rgb(i as u32, j as u32, 255); //0x00A00000 | (i as u32) | ((j as u32) << 8); //((i as f32) / 255.) / ((j as f32) / 255.); + } + }*/ + let img = bmp::open(concat!(env!("CARGO_MANIFEST_DIR"), "/test.bmp")).unwrap(); + let mut pixels = [0u32; (TEX_X * TEX_Y) as usize]; + + for i in 0..TEX_X { + for j in 0..TEX_Y { + let pix = img.get_pixel(j as u32, i as u32); + let val = (pix.r as u32) << 24 | (pix.g as u32) << 16 | (pix.b as u32) << 8 | 0xFF; + pixels[((i * TEX_Y) + j) as usize] = val; + } } + texture.upload_pixels(&pixels); + texture.unbind(); + Ok(Self { sdl_context, controller, @@ -180,6 +221,7 @@ impl GlRenderer { ctx, num_tiles, gb_vao: vao, + background_texture: texture, }) } @@ -208,13 +250,22 @@ impl GlRenderer { impl Renderer for GlRenderer { fn draw_gameboy(&mut self, _gameboy: &Cpu, _app_settings: &ApplicationSettings) { info!("In draw"); + // Use shader program + self.gb_program.use_program(); + self.gb_vao.bind(); + self.background_texture.bind(); unsafe { - // Use shader program - gl::UseProgram(self.gb_program.id()); - gl::BindVertexArray(self.gb_vao); - gl::Clear(COLOR_BUFFER_BIT); - gl::DrawArrays(gl::TRIANGLES, 0, self.num_tiles * self.num_tiles * 3); + gl::Clear(gl::COLOR_BUFFER_BIT); + gl::DrawElements( + gl::TRIANGLES, + self.num_tiles * self.num_tiles * 6, + gl::UNSIGNED_SHORT, + std::ptr::null(), + ); + //gl::DrawArrays(gl::TRIANGLE_STRIP, 0, self.num_tiles * self.num_tiles * 4); } + self.gb_vao.unbind(); + self.background_texture.unbind(); self.window.gl_swap_window(); } diff --git a/src/io/graphics/gl/shaders/gameboy.frag b/src/io/graphics/gl/shaders/gameboy.frag index 9618559..883a597 100644 --- a/src/io/graphics/gl/shaders/gameboy.frag +++ b/src/io/graphics/gl/shaders/gameboy.frag @@ -2,10 +2,13 @@ in VS_OUTPUT { vec3 Color; + vec2 Texcoord; } IN; out vec4 Color; +uniform sampler2D tex; + void main() { - Color = vec4(IN.Color, 1.0f); + Color = texture(tex, IN.Texcoord); //vec4(IN.Color, 1.0f); } \ No newline at end of file diff --git a/src/io/graphics/gl/shaders/gameboy.vert b/src/io/graphics/gl/shaders/gameboy.vert index f24bb6a..8eb298a 100644 --- a/src/io/graphics/gl/shaders/gameboy.vert +++ b/src/io/graphics/gl/shaders/gameboy.vert @@ -2,12 +2,15 @@ layout(location = 0) in vec2 vert_xy; layout(location = 1) in vec3 Color; +layout(location = 2) in vec2 Texcoord; out VS_OUTPUT { vec3 Color; + vec2 Texcoord; } OUT; void main() { gl_Position = vec4(vert_xy, 0.0, 1.0); OUT.Color = Color; + OUT.Texcoord = Texcoord; } diff --git a/src/io/graphics/gl/util/buffer.rs b/src/io/graphics/gl/util/buffer.rs new file mode 100644 index 0000000..42937d0 --- /dev/null +++ b/src/io/graphics/gl/util/buffer.rs @@ -0,0 +1,139 @@ +use gl::{self, types::*}; + +pub struct ArrayBuffer { + vbo: GLuint, +} + +impl ArrayBuffer { + pub fn new() -> ArrayBuffer { + let mut vbo: GLuint = 0; + unsafe { + gl::GenBuffers(1, &mut vbo); + } + + ArrayBuffer { vbo } + } + + pub fn bind(&self) { + unsafe { + gl::BindBuffer(gl::ARRAY_BUFFER, self.vbo); + } + } + + pub fn unbind(&self) { + unsafe { + gl::BindBuffer(gl::ARRAY_BUFFER, 0); + } + } + + pub fn static_draw_data(&self, data: &[T]) { + unsafe { + gl::BufferData( + gl::ARRAY_BUFFER, + (data.len() * ::std::mem::size_of::()) as gl::types::GLsizeiptr, + data.as_ptr() as *const gl::types::GLvoid, + gl::STATIC_DRAW, + ); + } + } +} + +impl Drop for ArrayBuffer { + fn drop(&mut self) { + unsafe { + gl::DeleteBuffers(1, &mut self.vbo); + } + } +} + +pub struct VertexArray { + vao: GLuint, +} + +impl VertexArray { + pub fn new() -> VertexArray { + let mut vao: GLuint = 0; + unsafe { + gl::GenVertexArrays(1, &mut vao); + } + + Self { vao } + } + + pub fn bind(&self) { + unsafe { + gl::BindVertexArray(self.vao); + } + } + + pub fn unbind(&self) { + unsafe { + gl::BindVertexArray(0); + } + } + + pub fn static_draw_data(&self, data: &[T]) { + unsafe { + gl::BufferData( + gl::ARRAY_BUFFER, + (data.len() * ::std::mem::size_of::()) as gl::types::GLsizeiptr, + data.as_ptr() as *const gl::types::GLvoid, + gl::STATIC_DRAW, + ); + } + } +} + +impl Drop for VertexArray { + fn drop(&mut self) { + unsafe { + gl::DeleteVertexArrays(1, &mut self.vao); + } + } +} + +pub struct ElementArrayBuffer { + ibo: GLuint, +} + +impl ElementArrayBuffer { + pub fn new() -> ElementArrayBuffer { + let mut ibo: GLuint = 0; + unsafe { + gl::GenBuffers(1, &mut ibo); + } + + ElementArrayBuffer { ibo } + } + + pub fn bind(&self) { + unsafe { + gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, self.ibo); + } + } + + pub fn unbind(&self) { + unsafe { + gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, 0); + } + } + + pub fn static_draw_data(&self, data: &[T]) { + unsafe { + gl::BufferData( + gl::ELEMENT_ARRAY_BUFFER, + (data.len() * ::std::mem::size_of::()) as gl::types::GLsizeiptr, + data.as_ptr() as *const gl::types::GLvoid, + gl::STATIC_DRAW, + ); + } + } +} + +impl Drop for ElementArrayBuffer { + fn drop(&mut self) { + unsafe { + gl::DeleteBuffers(1, &mut self.ibo); + } + } +} diff --git a/src/io/graphics/gl/util/data.rs b/src/io/graphics/gl/util/data.rs new file mode 100644 index 0000000..af9904d --- /dev/null +++ b/src/io/graphics/gl/util/data.rs @@ -0,0 +1,66 @@ +use gl::{self, types::*}; + +#[allow(non_camel_case_types)] +#[derive(Copy, Clone, Debug)] +#[repr(C, packed)] +pub struct f32_f32_f32 { + pub d0: f32, + pub d1: f32, + pub d2: f32, +} + +impl f32_f32_f32 { + pub fn new(d0: f32, d1: f32, d2: f32) -> Self { + Self { d0, d1, d2 } + } + + pub unsafe fn vertex_attrib_pointer(stride: usize, location: usize, offset: usize) { + gl::EnableVertexAttribArray(location as GLuint); + gl::VertexAttribPointer( + location as GLuint, + 3, + gl::FLOAT, + gl::FALSE, + stride as GLint, + offset as *const GLvoid, + ); + } +} + +impl From<(f32, f32, f32)> for f32_f32_f32 { + fn from(other: (f32, f32, f32)) -> Self { + f32_f32_f32::new(other.0, other.1, other.2) + } +} + +#[allow(non_camel_case_types)] +#[derive(Copy, Clone, Debug)] +#[repr(C, packed)] +pub struct f32_f32 { + pub d0: f32, + pub d1: f32, +} + +impl f32_f32 { + pub fn new(d0: f32, d1: f32) -> Self { + Self { d0, d1 } + } + + pub unsafe fn vertex_attrib_pointer(stride: usize, location: usize, offset: usize) { + gl::EnableVertexAttribArray(location as GLuint); + gl::VertexAttribPointer( + location as GLuint, + 2, + gl::FLOAT, + gl::FALSE, + stride as GLint, + offset as *const GLvoid, + ); + } +} + +impl From<(f32, f32)> for f32_f32 { + fn from(other: (f32, f32)) -> Self { + f32_f32::new(other.0, other.1) + } +} diff --git a/src/io/graphics/gl/util/mod.rs b/src/io/graphics/gl/util/mod.rs new file mode 100644 index 0000000..d14f404 --- /dev/null +++ b/src/io/graphics/gl/util/mod.rs @@ -0,0 +1,15 @@ +//! Code taken and inspired by http://nercury.github.io/rust/opengl/tutorial/ +//! Thanks! + +mod buffer; +pub mod data; +mod shader; +mod texture; + +pub use self::buffer::{ArrayBuffer, ElementArrayBuffer, VertexArray}; +pub use self::shader::{Program, Shader}; +pub use texture::Texture; + +use gl; +use std; +use std::ffi::{CStr, CString}; diff --git a/src/io/graphics/gl/util.rs b/src/io/graphics/gl/util/shader.rs similarity index 96% rename from src/io/graphics/gl/util.rs rename to src/io/graphics/gl/util/shader.rs index ae7db7f..8a908d6 100644 --- a/src/io/graphics/gl/util.rs +++ b/src/io/graphics/gl/util/shader.rs @@ -111,6 +111,12 @@ impl Program { pub fn id(&self) -> gl::types::GLuint { self.id } + + pub fn use_program(&self) { + unsafe { + gl::UseProgram(self.id); + } + } } impl Drop for Program { diff --git a/src/io/graphics/gl/util/texture.rs b/src/io/graphics/gl/util/texture.rs new file mode 100644 index 0000000..d395caf --- /dev/null +++ b/src/io/graphics/gl/util/texture.rs @@ -0,0 +1,57 @@ +use gl::{self, types::*}; + +pub struct Texture { + tex: GLuint, + x: i32, + y: i32, +} + +impl Texture { + pub fn new(x: i32, y: i32) -> Texture { + let mut tex: GLuint = 0; + unsafe { + gl::GenTextures(1, &mut tex); + } + + let texture = Texture { tex, x, y }; + + texture.bind(); + unsafe { + gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_S, gl::CLAMP_TO_BORDER as _); + gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_T, gl::CLAMP_TO_BORDER as _); + gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::NEAREST as _); + gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::NEAREST as _); + } + texture.unbind(); + + texture + } + + pub fn bind(&self) { + unsafe { + gl::BindTexture(gl::TEXTURE_2D, self.tex); + } + } + + pub fn unbind(&self) { + unsafe { + gl::BindTexture(gl::TEXTURE_2D, 0); + } + } + + pub fn upload_pixels(&self, data: &[T]) { + unsafe { + gl::TexImage2D( + gl::TEXTURE_2D, + 0, + gl::RGB as _, + self.x, + self.y, + 0, + gl::RGB as _, + gl::UNSIGNED_BYTE as _, // TODO: this is probably wrong + data.as_ptr() as *const GLvoid, + ); + } + } +}