diff --git a/Cargo.toml b/Cargo.toml index c0fbfc7..f7c21ee 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,4 +9,3 @@ gl = "0.10.0" [dependencies.rustic_gl] git = "https://github.com/shivshank/rustic_gl.git" -rev = "f849057" diff --git a/README.md b/README.md index 5c24f95..51c357e 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ screen ASAP! extern crate mini_gl_fb; fn main() { - let mut fb = mini_gl_fb::gotta_go_fast("Hello world!", 800, 600); + let mut fb = mini_gl_fb::gotta_go_fast("Hello world!", 800.0, 600.0); let buffer = vec![[128u8, 0, 0, 255]; 800 * 600]; fb.update_buffer(&buffer); fb.persist(); diff --git a/examples/custom_shaders.rs b/examples/custom_shaders.rs index c5dff5c..da12a44 100644 --- a/examples/custom_shaders.rs +++ b/examples/custom_shaders.rs @@ -3,7 +3,7 @@ extern crate mini_gl_fb; /// Geometry shaders allow you to procedurally generate new geometry from the vertex data. /// /// This shader takes the two triangles submitted by mini_gl_fb and turns them into a circle! -const geometry_source: &str = r" +const GEOMETRY_SOURCE: &str = r" #version 330 core layout (triangles) in; @@ -73,7 +73,7 @@ const geometry_source: &str = r" } "; -const fragment_source: &str = r" +const FRAGMENT_SOURCE: &str = r" #version 330 core in vec2 g_uv; @@ -99,23 +99,23 @@ const fragment_source: &str = r" extern crate gl; fn main() { - let width = 800; - let height = 600; + let width = 800.0; + let height = 600.0; let mut fb = mini_gl_fb::gotta_go_fast("Hello shaders!", width, height); let mut buffer = vec![[128u8, 0, 0, 255]; (width * height) as usize]; // let's write a red line into the buffer roughly along the diagonal (misses many pixels) for i in 0..100 { - let j = i as f32 / 100.0; - let index = ((width as f32) * j * ((height as f32) + 1.0)).floor() as usize; + let j = i as f64 / 100.0; + let index = (width * j * (height + 1.0)).floor() as usize; buffer[index] = [255, 0, 0, 255]; } // Let's keep using the default vertex shader - // fb.use_vertex_shader(...); - fb.use_geometry_shader(geometry_source); - fb.use_fragment_shader(fragment_source); + // fb.internal.use_vertex_shader(...); + fb.internal.fb.use_geometry_shader(GEOMETRY_SOURCE); + fb.internal.fb.use_fragment_shader(FRAGMENT_SOURCE); fb.update_buffer(&buffer); diff --git a/examples/window.rs b/examples/window.rs index 017266b..27d1979 100644 --- a/examples/window.rs +++ b/examples/window.rs @@ -1,7 +1,7 @@ extern crate mini_gl_fb; fn main() { - let mut fb = mini_gl_fb::gotta_go_fast("Hello world!", 800, 600); + let mut fb = mini_gl_fb::gotta_go_fast("Hello world!", 800.0, 600.0); let buffer = vec![[128u8, 0, 0, 255]; 800 * 600]; fb.update_buffer(&buffer); fb.persist(); diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..edf9282 --- /dev/null +++ b/src/config.rs @@ -0,0 +1,36 @@ +/// Configuration for "advanced" use cases, when `gotta_go_fast` isn't doing what you need. +/// +/// The following pattern is reccomended when creating a config: +/// +/// ```rust +/// let config = Config { +/// /* specify whichever fields you need to set, for example: */ +/// window_size: (100.0, 100.0), +/// resizable: false, +/// .. Default::default() +/// } +/// ``` +/// +/// To streamline this pattern and save you imports, see the `get_fancy!` macro. +/// +/// If there's a config option you want to see or think is missing, please open an issue! +pub struct Config { + /// Sets the scale of the buffer. The buffer will automatically scale to the size of the + /// window. By default this will be the same size as the window_size. + pub buffer_size: (u32, u32), + pub resizable: bool, + pub window_title: S, + pub window_size: (f64, f64), +} + +impl Default for Config { + fn default() -> Self { + Config { + buffer_size: (0, 0), + resizable: false, + // :^) + window_title: "Super Mini GL Framebufferer 3!".to_string(), + window_size: (600.0, 480.0), + } + } +} diff --git a/src/core.rs b/src/core.rs new file mode 100644 index 0000000..640726f --- /dev/null +++ b/src/core.rs @@ -0,0 +1,344 @@ +use config::Config; + +use rustic_gl; + +use glutin::{ + EventsLoop, + WindowBuilder, + ContextBuilder, + GlWindow, + GlContext, + Event, + WindowEvent, +}; +use glutin::dpi::LogicalSize; + +use gl; +use gl::types::*; + +use std::ptr::null; + +/// Create a context using glutin given a configuration. +pub fn init_glutin_context(config: &Config) -> (EventsLoop, GlWindow) { + let window_size = LogicalSize::new(config.window_size.0, config.window_size.1); + + let events_loop = EventsLoop::new(); + let window = WindowBuilder::new() + .with_title(config.window_title.to_string()) + .with_dimensions(window_size) + .with_resizable(config.resizable); + + let context = ContextBuilder::new(); + let gl_window = GlWindow::new(window, context, &events_loop).unwrap(); + + unsafe { + gl_window.make_current().unwrap(); + gl::load_with(|symbol| gl_window.get_proc_address(symbol) as *const _); + } + + (events_loop, gl_window) +} + +type VertexFormat = buffer_layout!([f32; 2], [f32; 2]); + +pub fn init_framebuffer(config: &Config) -> Framebuffer { + // The config takes the size in u32 because that's all that actually makes sense but since + // OpenGL is from the Land of C where a Working Type System doesn't exist, we work with i32s + let buffer_width = if config.buffer_size.0 == 0 { config.window_size.0.round() as _ } + else { config.buffer_size.0 as _ }; + let buffer_height = if config.buffer_size.1 == 0 { config.window_size.1.round() as _ } + else { config.buffer_size.1 as _ }; + + let vertex_shader = rustic_gl::raw::create_shader( + gl::VERTEX_SHADER, + include_str!("./default_vertex_shader.glsl"), + ).unwrap(); + let fragment_shader = rustic_gl::raw::create_shader( + gl::FRAGMENT_SHADER, + include_str!("./default_fragment_shader.glsl"), + ).unwrap(); + + let program = unsafe { + build_program(&[ + Some(vertex_shader), + Some(fragment_shader), + ]) + }; + + let sampler_location = unsafe { + let location = gl::GetUniformLocation(program, b"u_tex0\0".as_ptr() as *const _); + gl::UseProgram(program); + gl::Uniform1i(location, 0); + gl::UseProgram(0); + location + }; + + let texture_format = (BufferFormat::RGBA, gl::UNSIGNED_BYTE); + let texture = create_texture(buffer_width, buffer_height, texture_format.0, texture_format.1); + + let vao = rustic_gl::raw::create_vao().unwrap(); + let vbo = rustic_gl::raw::create_buffer().unwrap(); + + unsafe { + gl::BindVertexArray(vao); + gl::BindBuffer(gl::ARRAY_BUFFER, vbo); + VertexFormat::declare(0); + + let verts: [[f32; 2]; 12] = [ + [-1., 1.], [0., 0.], // top left + [-1., -1.], [0., 1.], // bottom left + [1., -1.], [1., 1.], // bottom right + [1., -1.], [1., 1.], // bottom right + [1., 1.], [1., 0.], // top right + [-1., 1.], [0., 0.], // top left + ]; + use std::mem::size_of_val; + gl::BufferData(gl::ARRAY_BUFFER, + size_of_val(&verts) as _, + verts.as_ptr() as *const _, + gl::STATIC_DRAW + ); + gl::BindBuffer(gl::ARRAY_BUFFER, 0); + gl::BindVertexArray(0); + } + + Framebuffer { + buffer_width: buffer_width, + buffer_height: buffer_height, + program, + sampler_location, + vertex_shader: Some(vertex_shader), + geometry_shader: None, + fragment_shader: Some(fragment_shader), + texture, + vao, + vbo, + texture_format, + } +} + +pub struct Internal { + pub events_loop: EventsLoop, + pub gl_window: GlWindow, + pub fb: Framebuffer, +} + +impl Internal { + pub fn update_buffer(&mut self, image_data: &[T]) { + self.fb.update_buffer(image_data); + self.gl_window.swap_buffers().unwrap(); + } + + pub fn persist(&mut self) { + self.persist_and_redraw(false); + } + + pub fn persist_and_redraw(&mut self, redraw: bool) { + let mut running = true; + while running { + self.events_loop.poll_events(|event| { + match event { + Event::WindowEvent { event, .. } => match event { + WindowEvent::CloseRequested => running = false, + _ => {}, + }, + _ => {}, + } + }); + if redraw { + self.fb.draw(|_| {}); + self.gl_window.swap_buffers().unwrap(); + } + } + } +} + +/// Provides the drawing functionality. +/// +/// You can get direct access by using a breakout function, such as breakout_glutin. +/// +/// # Disclaimer: +/// +/// Accessing fields directly is not the intended usage. If a feature is missing please open an +/// issue. The fields are public, however, so that while you are waiting for a feature to be +/// exposed, if you need something in a pinch you can dig in easily and make it happen. +/// +/// The internal fields may change. +pub struct Framebuffer { + pub buffer_width: i32, + pub buffer_height: i32, + pub program: GLuint, + pub sampler_location: GLint, + pub vertex_shader: Option, + pub geometry_shader: Option, + pub fragment_shader: Option, + pub texture: GLuint, + pub vao: GLuint, + pub vbo: GLuint, + pub texture_format: (BufferFormat, GLenum), +} + +impl Framebuffer { + pub fn update_buffer(&mut self, image_data: &[T]) { + // TODO: Safety check on the length of the passed slice so this is actually a safe method + self.draw(|fb| { + unsafe { + let (format, kind) = fb.texture_format; + gl::TexImage2D( + gl::TEXTURE_2D, + 0, + gl::RGBA as _, + fb.buffer_width, + fb.buffer_height, + 0, + format as GLenum, + kind, + image_data.as_ptr() as *const _, + ); + } + }) + } + + pub fn use_vertex_shader(&mut self, source: &str) { + rebuild_shader(&mut self.vertex_shader, gl::VERTEX_SHADER, source); + self.relink_program(); + } + + pub fn use_fragment_shader(&mut self, source: &str) { + rebuild_shader(&mut self.fragment_shader, gl::FRAGMENT_SHADER, source); + self.relink_program(); + } + + pub fn use_geometry_shader(&mut self, source: &str) { + rebuild_shader(&mut self.geometry_shader, gl::GEOMETRY_SHADER, source); + self.relink_program(); + } + + // TODO: require passing new image data + pub fn change_buffer_format(&mut self, format: BufferFormat) { + self.texture_format = (format, T::to_gl_enum()); + } + + // TODO: resize_buffer + + fn draw(&mut self, f: F) { + unsafe { + gl::UseProgram(self.program); + gl::BindVertexArray(self.vao); + gl::ActiveTexture(0); + gl::BindTexture(gl::TEXTURE_2D, self.texture); + f(self); + gl::DrawArrays(gl::TRIANGLES, 0, 6); + gl::BindTexture(gl::TEXTURE_2D, 0); + gl::BindVertexArray(0); + gl::UseProgram(0); + } + } + + fn relink_program(&mut self) { + unsafe { + gl::DeleteProgram(self.program); + self.program = build_program(&[ + self.vertex_shader, + self.fragment_shader, + self.geometry_shader, + ]); + } + } +} + +#[derive(Copy, Clone, PartialEq, Eq)] +#[repr(u32)] +pub enum BufferFormat { + R = gl::RED, + RG = gl::RG, + RGB = gl::RGB, + BGR = gl::BGR, + RGBA = gl::RGBA, + BGRA = gl::BGRA, +} + +pub trait ToGlType { + fn to_gl_enum() -> GLenum; +} + +macro_rules! impl_ToGlType { + ( + $( + $t:ty, $gl_type:expr + ),+, + ) => { + $( + impl ToGlType for $t { + fn to_gl_enum() -> GLenum { + $gl_type + } + } + )+ + } +} + +impl_ToGlType!( + u8, gl::UNSIGNED_BYTE, + i8, gl::BYTE, +); + +fn create_texture(width: i32, height: i32, format: BufferFormat, buffer_kind: GLenum) -> GLuint { + unsafe { + let mut tex = 0; + gl::GenTextures(1, &mut tex); + if tex == 0 { + // TODO + panic!(); + } + gl::BindTexture(gl::TEXTURE_2D, tex); + gl::TexImage2D(gl::TEXTURE_2D, 0, gl::RGBA as _, width, height, 0, format as GLenum, buffer_kind, null()); + gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::NEAREST as _); + gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::NEAREST as _); + gl::BindTexture(gl::TEXTURE_2D, 0); + tex + } +} + +fn rebuild_shader(shader: &mut Option, kind: GLenum, source: &str) { + if let Some(shader) = *shader { + unsafe { + gl::DeleteShader(shader); + } + } + let compilation_result = rustic_gl::raw::create_shader(kind, source); + match compilation_result { + Ok(gl_id) => { + *shader = Some(gl_id); + }, + Err(rustic_gl::error::GlError::ShaderCompilation(info)) => { + if let Some(log) = info { + panic!("Shader compilation failed with the following information: {}", log); + } else { + panic!("Shader compilation failed without any information.") + } + }, + Err(err) => { + panic!("An error occured while compiling shader: {}", err); + } + } +} + +unsafe fn build_program(shaders: &[Option]) -> GLuint { + let program = rustic_gl::raw::create_program() + .unwrap(); + for shader in shaders.iter() { + if let &Some(shader) = shader { + gl::AttachShader(program, shader); + } + } + gl::LinkProgram(program); + rustic_gl::raw::get_link_status(program) + .unwrap(); + for shader in shaders { + if let &Some(shader) = shader { + gl::DetachShader(program, shader); + } + } + program +} diff --git a/src/lib.rs b/src/lib.rs index 51c92ed..9efd803 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,322 +9,112 @@ //! setup does not support the newest OpenGL. This bug needs to be verified and is be fixable. //! OpenGL ~3 is currently required, but OpenGL 2.1 support should be feasible if requested. -extern crate glutin; #[macro_use] -extern crate rustic_gl; -extern crate gl; +pub extern crate rustic_gl; -use glutin::GlContext; -use glutin::dpi::LogicalSize; +pub extern crate glutin; +pub extern crate gl; -use gl::types::*; +mod config; +mod core; +// mod breakout; -use std::ptr::null; +pub use config::Config; +pub use core::{Internal, BufferFormat}; -type VertexFormat = buffer_layout!([f32; 2], [f32; 2]); +/* +// TODO: Support mixed { prop, prop: value, .. } for creating configs through the macro -pub fn gotta_go_fast(window_title: S, window_width: i32, window_height: i32) -> Framebuffer { - let events_loop = glutin::EventsLoop::new(); - let window = glutin::WindowBuilder::new() - .with_title(window_title.to_string()) - .with_dimensions(LogicalSize::new(window_width as _, window_height as _)); - let context = glutin::ContextBuilder::new(); - let gl_window = glutin::GlWindow::new(window, context, &events_loop).unwrap(); - - unsafe { - gl_window.make_current().unwrap(); - gl::load_with(|symbol| gl_window.get_proc_address(symbol) as *const _); - } - - let vertex_shader = rustic_gl::raw::create_shader( - gl::VERTEX_SHADER, - include_str!("./default_vertex_shader.glsl"), - ).unwrap(); - let fragment_shader = rustic_gl::raw::create_shader( - gl::FRAGMENT_SHADER, - include_str!("./default_fragment_shader.glsl"), - ).unwrap(); - let program = unsafe { - build_program(&[ - Some(vertex_shader), - Some(fragment_shader), - ]) +#[macro_export] +macro_rules! get_fancy { + ( + $($setting:ident: $setting_value:expr),* + ) => { + // Support both no trailing comma and trailing comma + // (The core macro impl assumes trailing comma) + get_fancy!($($setting: $setting_value),*,) }; - let sampler_location = unsafe { - let location = gl::GetUniformLocation(program, b"u_tex0\0".as_ptr() as *const _); - gl::UseProgram(program); - gl::Uniform1i(location, 0); - gl::UseProgram(0); - location + ( + $($setting:ident),* + ) => { + // Support both no trailing comma and trailing comma + // (The core macro impl assumes trailing comma) + get_fancy!($($setting),*,) }; - let texture_format = (BufferFormat::RGBA, gl::UNSIGNED_BYTE); - let texture = create_texture(window_width, window_height, texture_format.0, texture_format.1); + ( + $($setting:ident),*, + ) => { + get_fancy!($($setting: $setting),*,) + }; - let vao = create_vao().unwrap(); - let vbo = create_gl_buffer().unwrap(); + ( + $($setting:ident: $setting_value:expr),*, + ) => {{ + let config = $crate::Config { + $( + $setting: $setting_value + ),*, + .. Default::default() + }; + $crate::get_fancy(config) + }}; +}*/ - unsafe { - gl::BindVertexArray(vao); - gl::BindBuffer(gl::ARRAY_BUFFER, vbo); - VertexFormat::declare(0); +pub fn gotta_go_fast( + window_title: S, + window_width: f64, + window_height: f64 +) -> MiniGlFb { + let config = Config { + window_title: window_title.to_string(), + window_size: (window_width, window_height), + .. Default::default() + }; + get_fancy(config) +} - let verts: [[f32; 2]; 12] = [ - [-1., 1.], [0., 0.], // top left - [-1., -1.], [0., 1.], // bottom left - [1., -1.], [1., 1.], // bottom right - [1., -1.], [1., 1.], // bottom right - [1., 1.], [1., 0.], // top right - [-1., 1.], [0., 0.], // top left - ]; - use std::mem::size_of_val; - gl::BufferData(gl::ARRAY_BUFFER, size_of_val(&verts) as _, verts.as_ptr() as *const _, gl::STATIC_DRAW); - gl::BindBuffer(gl::ARRAY_BUFFER, 0); - gl::BindVertexArray(0); - } +pub fn get_fancy(config: Config) -> MiniGlFb { + let (events_loop, gl_window) = core::init_glutin_context(&config); + let fb = core::init_framebuffer(&config); - Framebuffer { - width: window_width, - height: window_height, - events_loop, - gl_window, - program, - sampler_location, - vertex_shader: Some(vertex_shader), - geometry_shader: None, - fragment_shader: Some(fragment_shader), - texture, - vao, - vbo, - texture_format, + MiniGlFb { + internal: Internal { + events_loop, + gl_window, + fb, + } } } -pub struct Framebuffer { - width: i32, - height: i32, - events_loop: glutin::EventsLoop, - gl_window: glutin::GlWindow, - program: GLuint, - sampler_location: GLint, - vertex_shader: Option, - geometry_shader: Option, - fragment_shader: Option, - texture: GLuint, - vao: GLuint, - vbo: GLuint, - texture_format: (BufferFormat, GLenum), +pub struct MiniGlFb { + /// All fields are exposed for your convienience, but use at your own risk. + /// + /// Anything accessed through `internal` is not considered a public API and may be subject to + /// breaking API changes. Only access this field as a last resort if the MiniGlFb API fails + /// to fit your exact use case. + pub internal: Internal, } -impl Framebuffer { +impl MiniGlFb { pub fn update_buffer(&mut self, image_data: &[T]) { - // TODO: Safety check on the length of the passed slice so this is actually a safe method - self.draw(|fb| { - unsafe { - let (format, kind) = fb.texture_format; - gl::TexImage2D( - gl::TEXTURE_2D, - 0, - gl::RGBA as _, - fb.width, - fb.height, - 0, - format as GLenum, - kind, - image_data.as_ptr() as *const _, - ); - } - }) - } - - pub fn use_vertex_shader(&mut self, source: &str) { - rebuild_shader(&mut self.vertex_shader, gl::VERTEX_SHADER, source); - self.relink_program(); - } - - pub fn use_fragment_shader(&mut self, source: &str) { - rebuild_shader(&mut self.fragment_shader, gl::FRAGMENT_SHADER, source); - self.relink_program(); - } - - pub fn use_geometry_shader(&mut self, source: &str) { - rebuild_shader(&mut self.geometry_shader, gl::GEOMETRY_SHADER, source); - self.relink_program(); - } - - pub fn change_buffer_format(&mut self, format: BufferFormat) { - self.texture_format = (format, T::to_gl_enum()); + self.internal.update_buffer(image_data); } /// Keeps the window open until the user closes it. pub fn persist(&mut self) { - self.persist_and_redraw(false); + self.internal.persist(); } - /// Persist implementation. + /// `persist` implementation. /// /// When redraw is true, redraws as fast as possible. This function is primarily for debugging. pub fn persist_and_redraw(&mut self, redraw: bool) { - let mut running = true; - while running { - self.events_loop.poll_events(|event| { - match event { - glutin::Event::WindowEvent{ event, .. } => match event { - glutin::WindowEvent::CloseRequested => running = false, - _ => {}, - }, - _ => {}, - } - }); - if redraw { - self.draw(|_| {}); - } - } + self.internal.persist_and_redraw(redraw); } - fn draw(&mut self, f: F) { - unsafe { - gl::UseProgram(self.program); - gl::BindVertexArray(self.vao); - gl::ActiveTexture(0); - gl::BindTexture(gl::TEXTURE_2D, self.texture); - f(self); - gl::DrawArrays(gl::TRIANGLES, 0, 6); - gl::BindTexture(gl::TEXTURE_2D, 0); - gl::BindVertexArray(0); - gl::UseProgram(0); - - self.gl_window.swap_buffers().unwrap(); - } - } - - fn relink_program(&mut self) { - unsafe { - gl::DeleteProgram(self.program); - self.program = build_program(&[ - self.vertex_shader, - self.fragment_shader, - self.geometry_shader, - ]); - } - } -} - -#[derive(Copy, Clone, PartialEq, Eq)] -#[repr(u32)] -pub enum BufferFormat { - R = gl::RED, - RG = gl::RG, - RGB = gl::RGB, - BGR = gl::BGR, - RGBA = gl::RGBA, - BGRA = gl::BGRA, -} - -pub trait ToGlType { - fn to_gl_enum() -> GLenum; -} - -macro_rules! impl_ToGlType { - ( - $( - $t:ty, $gl_type:expr - ),+, - ) => { - $( - impl ToGlType for $t { - fn to_gl_enum() -> GLenum { - $gl_type - } - } - )+ - } -} - -impl_ToGlType!( - u8, gl::UNSIGNED_BYTE, - i8, gl::BYTE, -); - -fn create_texture(width: i32, height: i32, format: BufferFormat, buffer_kind: GLenum) -> GLuint { - unsafe { - let mut tex = 0; - gl::GenTextures(1, &mut tex); - if tex == 0 { - // TODO - panic!(); - } - gl::BindTexture(gl::TEXTURE_2D, tex); - gl::TexImage2D(gl::TEXTURE_2D, 0, gl::RGBA as _, width, height, 0, format as GLenum, buffer_kind, null()); - gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::NEAREST as _); - gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::NEAREST as _); - gl::BindTexture(gl::TEXTURE_2D, 0); - tex - } -} - -fn create_vao() -> Option { - unsafe { - let mut vao = 0; - gl::GenVertexArrays(1, &mut vao); - if vao == 0 { - return None; - } - Some(vao) - } -} - -fn create_gl_buffer() -> Option { - unsafe { - let mut b = 0; - gl::GenBuffers(1, &mut b); - if b == 0 { - return None; - } - Some(b) - } -} - -fn rebuild_shader(shader: &mut Option, kind: GLenum, source: &str) { - if let Some(shader) = *shader { - unsafe { - gl::DeleteShader(shader); - } - } - let compilation_result = rustic_gl::raw::create_shader(kind, source); - match compilation_result { - Ok(gl_id) => { - *shader = Some(gl_id); - }, - Err(rustic_gl::error::GlError::ShaderCompilation(info)) => { - if let Some(log) = info { - panic!("Shader compilation failed with the following information: {}", log); - } else { - panic!("Shader compilation failed without any information.") - } - }, - Err(err) => { - panic!("An error occured while compiling shader: {}", err); - } - } -} - -unsafe fn build_program(shaders: &[Option]) -> GLuint { - let program = rustic_gl::raw::create_program() - .unwrap(); - for shader in shaders.iter() { - if let &Some(shader) = shader { - gl::AttachShader(program, shader); - } - } - gl::LinkProgram(program); - rustic_gl::raw::get_link_status(program) - .unwrap(); - for shader in shaders { - if let &Some(shader) = shader { - gl::DetachShader(program, shader); - } - } - program + // TODO: resize_buffer + // TODO: set_resizable + // TODO: change_buffer_format }