diff --git a/Cargo.toml b/Cargo.toml index 1228202..db51fc6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,14 +10,16 @@ travis-ci = { repository = "MarkMcCaskey/rusty-boy", branch = "master" } maintainence = {status = "experimental"} [features] -default = [] +default = ["opengl"] asm = ["nom"] debugger = ["ncurses", "nom"] vulkan = [] +opengl = ["gl", "rusty-boy-derive"] development = ["debugger"] [dependencies] app_dirs = "^1.1.1" +bmp = "0.4" clap = "^2.31" gameboy-rom = "0.2" lazy_static = "^1.0" @@ -29,11 +31,14 @@ serde = "^1.0.0" serde_derive = "^1.0.0" time = "^0.1" +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} vulkano-win = {version = "0.6.2", optional = true} +gl = {version = "^0.12", optional = true} + nom = {version = "^2.2", optional = true} ncurses = {version = "^5.85.0", 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/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 583e387..58d702a 100644 --- a/src/io/arguments.rs +++ b/src/io/arguments.rs @@ -66,5 +66,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/.#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 new file mode 100644 index 0000000..e03ab40 --- /dev/null +++ b/src/io/graphics/gl/mod.rs @@ -0,0 +1,494 @@ +pub mod util; + +use sdl2::{ + self, keyboard::Keycode, pixels::PixelFormatEnum, rect::Point, surface::Surface, + video::GLProfile, *, +}; + +use super::renderer::{self, EventResponse}; +use crate::cpu::Cpu; +use crate::io::{ + applicationsettings::ApplicationSettings, + constants::*, + graphics::{renderer::Renderer, sdl2::input::*}, + sound::*, +}; + +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: Program, + controller: Option, // storing to keep alive + num_tiles: i32, + 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 { + 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_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 = { + 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 _); + 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 = + 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 gb_program = unsafe { + gl::Viewport(0, 0, window_width as i32, window_height as i32); + gl::ClearColor(0f32, 0f32, 0f32, 1f32); + + 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 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 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 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, + window, + gb_program, + ctx, + num_tiles, + gb_vao: vao, + background_texture: texture, + }) + } + + /// 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) { + info!("In draw"); + // Use shader program + self.gb_program.use_program(); + self.gb_vao.bind(); + self.background_texture.bind(); + unsafe { + 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(); + } + + 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) +} + +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 new file mode 100644 index 0000000..883a597 --- /dev/null +++ b/src/io/graphics/gl/shaders/gameboy.frag @@ -0,0 +1,14 @@ +#version 430 core + +in VS_OUTPUT { + vec3 Color; + vec2 Texcoord; +} IN; + +out vec4 Color; + +uniform sampler2D tex; + +void main() { + 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 new file mode 100644 index 0000000..8eb298a --- /dev/null +++ b/src/io/graphics/gl/shaders/gameboy.vert @@ -0,0 +1,16 @@ +#version 430 core + +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/shader.rs b/src/io/graphics/gl/util/shader.rs new file mode 100644 index 0000000..8a908d6 --- /dev/null +++ b/src/io/graphics/gl/util/shader.rs @@ -0,0 +1,128 @@ +//! 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 + } + + pub fn use_program(&self) { + unsafe { + gl::UseProgram(self.id); + } + } +} + +impl Drop for Program { + fn drop(&mut self) { + unsafe { + gl::DeleteProgram(self.id); + } + } +} 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, + ); + } + } +} diff --git a/src/io/graphics/mod.rs b/src/io/graphics/mod.rs index d7c33d0..e845542 100644 --- a/src/io/graphics/mod.rs +++ b/src/io/graphics/mod.rs @@ -1,3 +1,5 @@ +#[cfg(feature = "opengl")] +pub mod gl; pub mod renderer; pub mod sdl2; #[cfg(feature = "vulkan")] diff --git a/src/main.rs b/src/main.rs index 806ab21..0c257b1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -34,6 +34,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;