diff --git a/Cargo.toml b/Cargo.toml index 607e12c..1137b84 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,11 +9,18 @@ panic = "abort" opt-level = "z" codegen-units = 1 +[features] +default = ["wayland","egl"] +egl = ["glutin-winit/egl"] +wayland = ["glutin-winit/wayland", "winit/wayland-dlopen"] + [dependencies] -gl = "0.14.0" -glfw = { version = "0.57.0", features = ["wayland"] } -image = { version = "0.25.2", default-features = false, features = ["rayon", "avif-native"] } -#jxl-oxide = { version = "0.8.1" } -#bytemuck = "1" -#ogl33 = { version = "0.2.0", features = ["debug_error_checks"]} -#beryllium = "0.13.3" +jxl-oxide = { version = "0.8.1" } +glutin = { version = "0.32.0", default-features = false} +glutin-winit = { version = "0.5.0", default-features = false} +raw-window-handle = "0.6" +winit = { version = "0.30.0", default-features = false, features = ["rwh_06"] } + +[build-dependencies] +gl_generator = "0.14" +cfg_aliases = "0.2.1" diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..2fbd6cc --- /dev/null +++ b/build.rs @@ -0,0 +1,40 @@ +use std::env; +use std::fs::File; +use std::path::PathBuf; + +use cfg_aliases::cfg_aliases; +use gl_generator::{Api, Fallbacks, Profile, Registry, StructGenerator}; + +fn main() { + // XXX this is taken from glutin/build.rs. + + // Setup alias to reduce `cfg` boilerplate. + cfg_aliases! { + // Systems. + android_platform: { target_os = "android" }, + wasm_platform: { target_family = "wasm" }, + macos_platform: { target_os = "macos" }, + ios_platform: { target_os = "ios" }, + apple: { any(ios_platform, macos_platform) }, + free_unix: { all(unix, not(apple), not(android_platform)) }, + + // Native displays. + x11_platform: { all(feature = "x11", free_unix, not(wasm_platform)) }, + wayland_platform: { all(feature = "wayland", free_unix, not(wasm_platform)) }, + + // Backends. + egl_backend: { all(feature = "egl", any(windows, unix), not(apple), not(wasm_platform)) }, + glx_backend: { all(feature = "glx", x11_platform, not(wasm_platform)) }, + wgl_backend: { all(feature = "wgl", windows, not(wasm_platform)) }, + cgl_backend: { all(macos_platform, not(wasm_platform)) }, + } + + let dest = PathBuf::from(&env::var("OUT_DIR").unwrap()); + + println!("cargo:rerun-if-changed=build.rs"); + + let mut file = File::create(dest.join("gl_bindings.rs")).unwrap(); + Registry::new(Api::Gles2, (3, 0), Profile::Core, Fallbacks::All, []) + .write_bindings(StructGenerator, &mut file) + .unwrap(); +} diff --git a/src/main.rs b/src/main.rs index 6c56391..b365dd0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,280 +1,10 @@ -use std::convert::TryInto; -// use jxl_oxide::JxlImage; -use image::ImageReader; -use glfw; -use glfw::Context; -use gl; +use std::error::Error; +mod mywin; +use winit::event_loop::EventLoop; +use jxl_oxide::JxlImage; -const WIDTH: u32 = 480; -const HEIGHT: u32 = 320; -const TITLE: &str = "Hello From OpenGL World!"; - -fn main() { - let img = ImageReader::open("combined.avif").unwrap().decode(); - println!("{:?}", img); - // let image = JxlImage::builder().open("combined.jxl").expect("Failed to read image header"); - // println!("{:?}", image.image_header()); // Prints the image header - //let image = dav1d::rav1d_open("combined.avif"); - - use glfw::fail_on_errors; - let mut glfw = glfw::init(fail_on_errors!()).unwrap(); - glfw.window_hint(glfw::WindowHint::ContextVersion(3, 3)); - glfw.window_hint(glfw::WindowHint::OpenGlProfile(glfw::OpenGlProfileHint::Core)); - glfw.window_hint(glfw::WindowHint::OpenGlForwardCompat(true)); - glfw.window_hint(glfw::WindowHint::Resizable(true)); - - let (mut window, events) = glfw.create_window(WIDTH, HEIGHT, TITLE, glfw::WindowMode::Windowed).unwrap(); - let (screen_width, screen_height) = window.get_framebuffer_size(); - - window.make_current(); - window.set_key_polling(true); - window.set_pos_polling(true); - window.set_all_polling(true); - window.set_size_polling(true); - window.set_close_polling(true); - window.set_refresh_polling(true); - window.set_focus_polling(true); - window.set_iconify_polling(true); - window.set_framebuffer_size_polling(true); - window.set_char_polling(true); - window.set_char_mods_polling(true); - window.set_mouse_button_polling(true); - window.set_cursor_pos_polling(true); - window.set_cursor_enter_polling(true); - window.set_scroll_polling(true); - window.set_maximize_polling(true); - window.set_content_scale_polling(true); - gl::load_with(|ptr| window.get_proc_address(ptr) as *const _); - - unsafe { - gl::Viewport(0, 0, screen_width, screen_height); - clear_color(Color(0.4, 0.4, 0.4, 1.0)); - } - // ------------------------------------------- - - const VERT_SHADER: &str = "#version 330 core - layout (location = 0) in vec3 position; - - void main() - { - gl_Position = vec4(position, 1.0); - // gl_Position = vec4(position.xyz, 1.0); - // gl_Position = vec4(position.x, position.y, position.z, 1.0); - }"; - - const FRAG_SHADER: &str = "#version 330 core - out vec4 Color; - void main() - { - Color = vec4(0.9, 0.5, 0.2, 1.0); - }"; - - let vertex_shader = unsafe { gl::CreateShader(gl::VERTEX_SHADER) }; - unsafe { - gl::ShaderSource(vertex_shader, 1, &VERT_SHADER.as_bytes().as_ptr().cast(), &VERT_SHADER.len().try_into().unwrap()); - gl::CompileShader(vertex_shader); - - let mut success = 0; - gl::GetShaderiv(vertex_shader, gl::COMPILE_STATUS, &mut success); - if success == 0 { - let mut log_len = 0_i32; - // gl::GetShaderiv(vertex_shader, gl::INFO_LOG_LENGTH, &mut log_len); - // let mut v: Vec = Vec::with_capacity(log_len as usize); - // gl::GetShaderInfoLog(vertex_shader, log_len, &mut log_len, v.as_mut_ptr().cast()); - let mut v: Vec = Vec::with_capacity(1024); - gl::GetShaderInfoLog(vertex_shader, 1024, &mut log_len, v.as_mut_ptr().cast()); - v.set_len(log_len.try_into().unwrap()); - panic!("Vertex Shader Compile Error: {}", String::from_utf8_lossy(&v)); - } - } - - let fragment_shader = unsafe { gl::CreateShader(gl::FRAGMENT_SHADER) }; - unsafe { - gl::ShaderSource(fragment_shader, 1, &FRAG_SHADER.as_bytes().as_ptr().cast(), &FRAG_SHADER.len().try_into().unwrap()); - gl::CompileShader(fragment_shader); - - let mut success = 0; - gl::GetShaderiv(fragment_shader, gl::COMPILE_STATUS, &mut success); - if success == 0 { - let mut v: Vec = Vec::with_capacity(1024); - let mut log_len = 0_i32; - gl::GetShaderInfoLog(fragment_shader, 1024, &mut log_len, v.as_mut_ptr().cast()); - v.set_len(log_len.try_into().unwrap()); - panic!("Fragment Shader Compile Error: {}", String::from_utf8_lossy(&v)); - } - } - - let shader_program = unsafe { gl::CreateProgram() }; - unsafe { - gl::AttachShader(shader_program, vertex_shader); - gl::AttachShader(shader_program, fragment_shader); - gl::LinkProgram(shader_program); - - let mut success = 0; - gl::GetProgramiv(shader_program, gl::LINK_STATUS, &mut success); - if success == 0 { - let mut v: Vec = Vec::with_capacity(1024); - let mut log_len = 0_i32; - gl::GetProgramInfoLog(shader_program, 1024, &mut log_len, v.as_mut_ptr().cast()); - v.set_len(log_len.try_into().unwrap()); - panic!("Program Link Error: {}", String::from_utf8_lossy(&v)); - } - - gl::DetachShader(shader_program, vertex_shader); - gl::DetachShader(shader_program, fragment_shader); - gl::DeleteShader(vertex_shader); - gl::DeleteShader(fragment_shader); - } - - let vertecies = [ - -0.5f32, -0.5, 0.0, - 0.5, -0.5, 0.0, - 0.0, 0.5, 0.0, - ]; - - let mut vao = 0; - unsafe { gl::GenVertexArrays(1, &mut vao) }; - - let mut vbo = 0; - unsafe { gl::GenBuffers(1, &mut vbo) }; - - unsafe { - gl::BindVertexArray(vao); - - gl::BindBuffer(gl::ARRAY_BUFFER, vbo); - gl::BufferData(gl::ARRAY_BUFFER, std::mem::size_of_val(&vertecies) as isize, vertecies.as_ptr().cast(), gl::STATIC_DRAW); - - gl::VertexAttribPointer(0, 3, gl::FLOAT, gl::FALSE, 3 * std::mem::size_of::() as i32, 0 as *const _); - gl::EnableVertexAttribArray(0); - - gl::BindBuffer(gl::ARRAY_BUFFER, 0); - gl::BindVertexArray(0); - } - - // ------------------------------------------- - println!("OpenGL version: {}", gl_get_string(gl::VERSION)); - println!("GLSL version: {}", gl_get_string(gl::SHADING_LANGUAGE_VERSION)); - - - while !window.should_close() { - glfw.poll_events(); - for (time, event) in glfw::flush_messages(&events) { - glfw_handle_event(&mut window, time, event); - } - - clear_color(Color(0.3, 0.4, 0.6, 1.0)); - - unsafe { - gl::Clear(gl::COLOR_BUFFER_BIT); - } - - unsafe { - gl::UseProgram(shader_program); - gl::BindVertexArray(vao); - - gl::DrawArrays(gl::TRIANGLES, 0, 3); - - gl::BindVertexArray(0); - } - - window.swap_buffers(); - } +fn main() -> Result<(), Box> { + let image = JxlImage::builder().open("combined.jxl").expect("Failed to read image header"); + println!("{:?}", image.image_header()); // Prints the image header + mywin::main(EventLoop::new().unwrap()) } - -pub struct Color(f32, f32, f32, f32); - -pub fn clear_color(c: Color) { - unsafe { gl::ClearColor(c.0, c.1, c.2, c.3) } -} - -pub fn gl_get_string<'a>(name: gl::types::GLenum) -> &'a str { - let v = unsafe { gl::GetString(name) }; - let v: &std::ffi::CStr = unsafe { std::ffi::CStr::from_ptr(v as *const i8) }; - v.to_str().unwrap() -} - -fn glfw_handle_event(window: &mut glfw::Window, time: f64, event: glfw::WindowEvent) { - use glfw::WindowEvent as Event; - use glfw::Key; - use glfw::Action; - - match event { - Event::Pos(x, y) => { - window.set_title(&format!("Time: {:?}, Window pos: ({:?}, {:?})", time, x, y)) - } - Event::Size(w, h) => window.set_title(&format!( - "Time: {:?}, Window size: ({:?}, {:?})", - time, w, h - )), - Event::Close => println!("Time: {:?}, Window close requested.", time), - Event::Refresh => { - println!("Time: {:?}, Window refresh callback triggered.", time) - } - Event::Focus(true) => println!("Time: {:?}, Window focus gained.", time), - Event::Focus(false) => println!("Time: {:?}, Window focus lost.", time), - Event::Iconify(true) => println!("Time: {:?}, Window was minimised", time), - Event::Iconify(false) => println!("Time: {:?}, Window was maximised.", time), - Event::FramebufferSize(w, h) => { - unsafe { - gl::Viewport(0, 0, w, h); - } - println!("Time: {:?}, Framebuffer size: ({:?}, {:?})", time, w, h) - } - Event::Char(character) => { - println!("Time: {:?}, Character: {:?}", time, character) - } - Event::CharModifiers(character, mods) => println!( - "Time: {:?}, Character: {:?}, Modifiers: [{:?}]", - time, character, mods - ), - Event::MouseButton(btn, action, mods) => println!( - "Time: {:?}, Button: {:?}, Action: {:?}, Modifiers: [{:?}]", - time, - glfw::DebugAliases(btn), - action, - mods - ), - Event::CursorPos(xpos, ypos) => window.set_title(&format!( - "Time: {:?}, Cursor position: ({:?}, {:?})", - time, xpos, ypos - )), - Event::CursorEnter(true) => { - println!("Time: {:?}, Cursor entered window.", time) - } - Event::CursorEnter(false) => println!("Time: {:?}, Cursor left window.", time), - Event::Scroll(x, y) => window.set_title(&format!( - "Time: {:?}, Scroll offset: ({:?}, {:?})", - time, x, y - )), - /*Event::Key(key, scancode, action, mods) => { - println!( - "Time: {:?}, Key: {:?}, ScanCode: {:?}, Action: {:?}, Modifiers: [{:?}]", - time, key, scancode, action, mods - ); - match (key, action) { - (Key::Escape, Action::Press) => window.set_should_close(true), - (Key::R, Action::Press) => { - // Resize should cause the window to "refresh" - let (window_width, window_height) = window.get_size(); - window.set_size(window_width + 1, window_height); - window.set_size(window_width, window_height); - } - _ => {} - } - }*/ - Event::FileDrop(paths) => { - println!("Time: {:?}, Files dropped: {:?}", time, paths) - } - Event::Maximize(maximized) => { - println!("Time: {:?}, Window maximized: {:?}.", time, maximized) - } - Event::ContentScale(xscale, yscale) => println!( - "Time: {:?}, Content scale x: {:?}, Content scale y: {:?}", - time, xscale, yscale - ), - Event::Key(Key::Escape, _, Action::Press, _) => { - window.set_should_close(true); - }, - _ => {}, - } -} \ No newline at end of file diff --git a/src/mywin.rs b/src/mywin.rs new file mode 100644 index 0000000..f36e311 --- /dev/null +++ b/src/mywin.rs @@ -0,0 +1,422 @@ +use std::error::Error; +use std::ffi::{CStr, CString}; +use std::num::NonZeroU32; +use std::ops::Deref; + +use gl::types::GLfloat; +use raw_window_handle::HasWindowHandle; +use winit::application::ApplicationHandler; +use winit::event::{KeyEvent, WindowEvent}; +use winit::keyboard::{Key, NamedKey}; +use winit::window::Window; + +use glutin::config::{Config, ConfigTemplateBuilder}; +use glutin::context::{ + ContextApi, ContextAttributesBuilder, NotCurrentContext, PossiblyCurrentContext, Version, +}; +use glutin::display::GetGlDisplay; +use glutin::prelude::*; +use glutin::surface::{Surface, SwapInterval, WindowSurface}; + +use glutin_winit::{DisplayBuilder, GlWindow}; + +pub mod gl { + #![allow(clippy::all)] + include!(concat!(env!("OUT_DIR"), "/gl_bindings.rs")); + + pub use Gles2 as Gl; +} + +pub fn main(event_loop: winit::event_loop::EventLoop<()>) -> Result<(), Box> { + let window_attributes = Window::default_attributes() + .with_transparent(true) + .with_title("Glutin triangle gradient example (press Escape to exit)"); + + // The template will match only the configurations supporting rendering + // to windows. + // + // XXX We force transparency only on macOS, given that EGL on X11 doesn't + // have it, but we still want to show window. The macOS situation is like + // that, because we can query only one config at a time on it, but all + // normal platforms will return multiple configs, so we can find the config + // with transparency ourselves inside the `reduce`. + let template = + ConfigTemplateBuilder::new().with_alpha_size(8).with_transparency(cfg!(cgl_backend)); + + let display_builder = DisplayBuilder::new().with_window_attributes(Some(window_attributes)); + + let mut app = App::new(template, display_builder); + event_loop.run_app(&mut app)?; + + app.exit_state +} + +impl ApplicationHandler for App { + fn resumed(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) { + let (mut window, gl_config) = match self.display_builder.clone().build( + event_loop, + self.template.clone(), + gl_config_picker, + ) { + Ok(ok) => ok, + Err(e) => { + self.exit_state = Err(e); + event_loop.exit(); + return; + }, + }; + + println!("Picked a config with {} samples", gl_config.num_samples()); + + let raw_window_handle = window + .as_ref() + .and_then(|window| window.window_handle().ok()) + .map(|handle| handle.as_raw()); + + // XXX The display could be obtained from any object created by it, so we can + // query it from the config. + let gl_display = gl_config.display(); + + // The context creation part. + let context_attributes = ContextAttributesBuilder::new().build(raw_window_handle); + + // Since glutin by default tries to create OpenGL core context, which may not be + // present we should try gles. + let fallback_context_attributes = ContextAttributesBuilder::new() + .with_context_api(ContextApi::Gles(None)) + .build(raw_window_handle); + + // There are also some old devices that support neither modern OpenGL nor GLES. + // To support these we can try and create a 2.1 context. + let legacy_context_attributes = ContextAttributesBuilder::new() + .with_context_api(ContextApi::OpenGl(Some(Version::new(2, 1)))) + .build(raw_window_handle); + + // Reuse the uncurrented context from a suspended() call if it exists, otherwise + // this is the first time resumed() is called, where the context still + // has to be created. + let not_current_gl_context = self.not_current_gl_context.take().unwrap_or_else(|| unsafe { + gl_display.create_context(&gl_config, &context_attributes).unwrap_or_else(|_| { + gl_display.create_context(&gl_config, &fallback_context_attributes).unwrap_or_else( + |_| { + gl_display + .create_context(&gl_config, &legacy_context_attributes) + .expect("failed to create context") + }, + ) + }) + }); + + let window = window.take().unwrap_or_else(|| { + let window_attributes = Window::default_attributes() + .with_transparent(true) + .with_title("Glutin triangle gradient example (press Escape to exit)"); + glutin_winit::finalize_window(event_loop, window_attributes, &gl_config).unwrap() + }); + + let attrs = window + .build_surface_attributes(Default::default()) + .expect("Failed to build surface attributes"); + let gl_surface = + unsafe { gl_config.display().create_window_surface(&gl_config, &attrs).unwrap() }; + + // Make it current. + let gl_context = not_current_gl_context.make_current(&gl_surface).unwrap(); + + // The context needs to be current for the Renderer to set up shaders and + // buffers. It also performs function loading, which needs a current context on + // WGL. + self.renderer.get_or_insert_with(|| Renderer::new(&gl_display)); + + // Try setting vsync. + if let Err(res) = gl_surface + .set_swap_interval(&gl_context, SwapInterval::Wait(NonZeroU32::new(1).unwrap())) + { + eprintln!("Error setting vsync: {res:?}"); + } + + assert!(self.state.replace(AppState { gl_context, gl_surface, window }).is_none()); + } + + fn suspended(&mut self, _event_loop: &winit::event_loop::ActiveEventLoop) { + // This event is only raised on Android, where the backing NativeWindow for a GL + // Surface can appear and disappear at any moment. + println!("Android window removed"); + + // Destroy the GL Surface and un-current the GL Context before ndk-glue releases + // the window back to the system. + let gl_context = self.state.take().unwrap().gl_context; + assert!(self + .not_current_gl_context + .replace(gl_context.make_not_current().unwrap()) + .is_none()); + } + + fn window_event( + &mut self, + event_loop: &winit::event_loop::ActiveEventLoop, + _window_id: winit::window::WindowId, + event: WindowEvent, + ) { + match event { + WindowEvent::Resized(size) if size.width != 0 && size.height != 0 => { + // Some platforms like EGL require resizing GL surface to update the size + // Notable platforms here are Wayland and macOS, other don't require it + // and the function is no-op, but it's wise to resize it for portability + // reasons. + if let Some(AppState { gl_context, gl_surface, window: _ }) = self.state.as_ref() { + gl_surface.resize( + gl_context, + NonZeroU32::new(size.width).unwrap(), + NonZeroU32::new(size.height).unwrap(), + ); + let renderer = self.renderer.as_ref().unwrap(); + renderer.resize(size.width as i32, size.height as i32); + } + }, + WindowEvent::CloseRequested + | WindowEvent::KeyboardInput { + event: KeyEvent { logical_key: Key::Named(NamedKey::Escape), .. }, + .. + } => event_loop.exit(), + _ => (), + } + } + + fn about_to_wait(&mut self, _event_loop: &winit::event_loop::ActiveEventLoop) { + if let Some(AppState { gl_context, gl_surface, window }) = self.state.as_ref() { + let renderer = self.renderer.as_ref().unwrap(); + renderer.draw(); + window.request_redraw(); + + gl_surface.swap_buffers(gl_context).unwrap(); + } + } +} + +struct App { + template: ConfigTemplateBuilder, + display_builder: DisplayBuilder, + exit_state: Result<(), Box>, + not_current_gl_context: Option, + renderer: Option, + // NOTE: `AppState` carries the `Window`, thus it should be dropped after everything else. + state: Option, +} + +impl App { + fn new(template: ConfigTemplateBuilder, display_builder: DisplayBuilder) -> Self { + Self { + template, + display_builder, + exit_state: Ok(()), + not_current_gl_context: None, + state: None, + renderer: None, + } + } +} + +struct AppState { + gl_context: PossiblyCurrentContext, + gl_surface: Surface, + // NOTE: Window should be dropped after all resources created using its + // raw-window-handle. + window: Window, +} + +// Find the config with the maximum number of samples, so our triangle will be +// smooth. +pub fn gl_config_picker(configs: Box + '_>) -> Config { + configs + .reduce(|accum, config| { + let transparency_check = config.supports_transparency().unwrap_or(false) + & !accum.supports_transparency().unwrap_or(false); + + if transparency_check || config.num_samples() > accum.num_samples() { + config + } else { + accum + } + }) + .unwrap() +} + +pub struct Renderer { + program: gl::types::GLuint, + vao: gl::types::GLuint, + vbo: gl::types::GLuint, + gl: gl::Gl, +} + +impl Renderer { + pub fn new(gl_display: &D) -> Self { + unsafe { + let gl = gl::Gl::load_with(|symbol| { + let symbol = CString::new(symbol).unwrap(); + gl_display.get_proc_address(symbol.as_c_str()).cast() + }); + + if let Some(renderer) = get_gl_string(&gl, gl::RENDERER) { + println!("Running on {}", renderer.to_string_lossy()); + } + if let Some(version) = get_gl_string(&gl, gl::VERSION) { + println!("OpenGL Version {}", version.to_string_lossy()); + } + + if let Some(shaders_version) = get_gl_string(&gl, gl::SHADING_LANGUAGE_VERSION) { + println!("Shaders version on {}", shaders_version.to_string_lossy()); + } + + let vertex_shader = create_shader(&gl, gl::VERTEX_SHADER, VERTEX_SHADER_SOURCE); + let fragment_shader = create_shader(&gl, gl::FRAGMENT_SHADER, FRAGMENT_SHADER_SOURCE); + + let program = gl.CreateProgram(); + + gl.AttachShader(program, vertex_shader); + gl.AttachShader(program, fragment_shader); + + gl.LinkProgram(program); + + gl.UseProgram(program); + + gl.DeleteShader(vertex_shader); + gl.DeleteShader(fragment_shader); + + let mut vao = std::mem::zeroed(); + gl.GenVertexArrays(1, &mut vao); + gl.BindVertexArray(vao); + + let mut vbo = std::mem::zeroed(); + gl.GenBuffers(1, &mut vbo); + gl.BindBuffer(gl::ARRAY_BUFFER, vbo); + gl.BufferData( + gl::ARRAY_BUFFER, + (VERTEX_DATA.len() * std::mem::size_of::()) as gl::types::GLsizeiptr, + VERTEX_DATA.as_ptr() as *const _, + gl::STATIC_DRAW, + ); + + let pos_attrib = gl.GetAttribLocation(program, b"position\0".as_ptr() as *const _); + let color_attrib = gl.GetAttribLocation(program, b"color\0".as_ptr() as *const _); + gl.VertexAttribPointer( + pos_attrib as gl::types::GLuint, + 2, + gl::FLOAT, + 0, + 5 * std::mem::size_of::() as gl::types::GLsizei, + std::ptr::null(), + ); + gl.VertexAttribPointer( + color_attrib as gl::types::GLuint, + 3, + gl::FLOAT, + 0, + 5 * std::mem::size_of::() as gl::types::GLsizei, + (2 * std::mem::size_of::()) as *const () as *const _, + ); + gl.EnableVertexAttribArray(pos_attrib as gl::types::GLuint); + gl.EnableVertexAttribArray(color_attrib as gl::types::GLuint); + + Self { program, vao, vbo, gl } + } + } + + pub fn draw(&self) { + self.draw_with_clear_color(0.1, 0.1, 0.1, 0.9) + } + + pub fn draw_with_clear_color( + &self, + red: GLfloat, + green: GLfloat, + blue: GLfloat, + alpha: GLfloat, + ) { + unsafe { + self.gl.UseProgram(self.program); + + self.gl.BindVertexArray(self.vao); + self.gl.BindBuffer(gl::ARRAY_BUFFER, self.vbo); + + self.gl.ClearColor(red, green, blue, alpha); + self.gl.Clear(gl::COLOR_BUFFER_BIT); + self.gl.DrawArrays(gl::TRIANGLES, 0, 3); + } + } + + pub fn resize(&self, width: i32, height: i32) { + unsafe { + self.gl.Viewport(0, 0, width, height); + } + } +} + +impl Deref for Renderer { + type Target = gl::Gl; + + fn deref(&self) -> &Self::Target { + &self.gl + } +} + +impl Drop for Renderer { + fn drop(&mut self) { + unsafe { + self.gl.DeleteProgram(self.program); + self.gl.DeleteBuffers(1, &self.vbo); + self.gl.DeleteVertexArrays(1, &self.vao); + } + } +} + +unsafe fn create_shader( + gl: &gl::Gl, + shader: gl::types::GLenum, + source: &[u8], +) -> gl::types::GLuint { + let shader = gl.CreateShader(shader); + gl.ShaderSource(shader, 1, [source.as_ptr().cast()].as_ptr(), std::ptr::null()); + gl.CompileShader(shader); + shader +} + +fn get_gl_string(gl: &gl::Gl, variant: gl::types::GLenum) -> Option<&'static CStr> { + unsafe { + let s = gl.GetString(variant); + (!s.is_null()).then(|| CStr::from_ptr(s.cast())) + } +} + +#[rustfmt::skip] +static VERTEX_DATA: [f32; 15] = [ + -0.5, -0.5, 1.0, 0.0, 0.0, + 0.0, 0.5, 0.0, 1.0, 0.0, + 0.5, -0.5, 0.0, 0.0, 1.0, +]; + +const VERTEX_SHADER_SOURCE: &[u8] = b" +#version 100 +precision mediump float; + +attribute vec2 position; +attribute vec3 color; + +varying vec3 v_color; + +void main() { + gl_Position = vec4(position, 0.0, 1.0); + v_color = color; +} +\0"; + +const FRAGMENT_SHADER_SOURCE: &[u8] = b" +#version 100 +precision mediump float; + +varying vec3 v_color; + +void main() { + gl_FragColor = vec4(v_color, 1.0); +} +\0";