mirror of
https://github.com/italicsjenga/mini_gl_fb.git
synced 2025-02-17 07:07:42 +11:00
Completely refactor to support breakout and exposed internals
This commit is contained in:
parent
86570bd141
commit
03f4f50d28
7 changed files with 468 additions and 299 deletions
|
@ -9,4 +9,3 @@ gl = "0.10.0"
|
||||||
|
|
||||||
[dependencies.rustic_gl]
|
[dependencies.rustic_gl]
|
||||||
git = "https://github.com/shivshank/rustic_gl.git"
|
git = "https://github.com/shivshank/rustic_gl.git"
|
||||||
rev = "f849057"
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ screen ASAP!
|
||||||
extern crate mini_gl_fb;
|
extern crate mini_gl_fb;
|
||||||
|
|
||||||
fn main() {
|
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];
|
let buffer = vec![[128u8, 0, 0, 255]; 800 * 600];
|
||||||
fb.update_buffer(&buffer);
|
fb.update_buffer(&buffer);
|
||||||
fb.persist();
|
fb.persist();
|
||||||
|
|
|
@ -3,7 +3,7 @@ extern crate mini_gl_fb;
|
||||||
/// Geometry shaders allow you to procedurally generate new geometry from the vertex data.
|
/// 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!
|
/// 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
|
#version 330 core
|
||||||
|
|
||||||
layout (triangles) in;
|
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
|
#version 330 core
|
||||||
|
|
||||||
in vec2 g_uv;
|
in vec2 g_uv;
|
||||||
|
@ -99,23 +99,23 @@ const fragment_source: &str = r"
|
||||||
extern crate gl;
|
extern crate gl;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let width = 800;
|
let width = 800.0;
|
||||||
let height = 600;
|
let height = 600.0;
|
||||||
|
|
||||||
let mut fb = mini_gl_fb::gotta_go_fast("Hello shaders!", width, height);
|
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 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)
|
// let's write a red line into the buffer roughly along the diagonal (misses many pixels)
|
||||||
for i in 0..100 {
|
for i in 0..100 {
|
||||||
let j = i as f32 / 100.0;
|
let j = i as f64 / 100.0;
|
||||||
let index = ((width as f32) * j * ((height as f32) + 1.0)).floor() as usize;
|
let index = (width * j * (height + 1.0)).floor() as usize;
|
||||||
buffer[index] = [255, 0, 0, 255];
|
buffer[index] = [255, 0, 0, 255];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Let's keep using the default vertex shader
|
// Let's keep using the default vertex shader
|
||||||
// fb.use_vertex_shader(...);
|
// fb.internal.use_vertex_shader(...);
|
||||||
fb.use_geometry_shader(geometry_source);
|
fb.internal.fb.use_geometry_shader(GEOMETRY_SOURCE);
|
||||||
fb.use_fragment_shader(fragment_source);
|
fb.internal.fb.use_fragment_shader(FRAGMENT_SOURCE);
|
||||||
|
|
||||||
fb.update_buffer(&buffer);
|
fb.update_buffer(&buffer);
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
extern crate mini_gl_fb;
|
extern crate mini_gl_fb;
|
||||||
|
|
||||||
fn main() {
|
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];
|
let buffer = vec![[128u8, 0, 0, 255]; 800 * 600];
|
||||||
fb.update_buffer(&buffer);
|
fb.update_buffer(&buffer);
|
||||||
fb.persist();
|
fb.persist();
|
||||||
|
|
36
src/config.rs
Normal file
36
src/config.rs
Normal file
|
@ -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<S: ToString> {
|
||||||
|
/// 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<String> {
|
||||||
|
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),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
344
src/core.rs
Normal file
344
src/core.rs
Normal file
|
@ -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<S: ToString>(config: &Config<S>) -> (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<S: ToString>(config: &Config<S>) -> 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<T>(&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<GLuint>,
|
||||||
|
pub geometry_shader: Option<GLuint>,
|
||||||
|
pub fragment_shader: Option<GLuint>,
|
||||||
|
pub texture: GLuint,
|
||||||
|
pub vao: GLuint,
|
||||||
|
pub vbo: GLuint,
|
||||||
|
pub texture_format: (BufferFormat, GLenum),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Framebuffer {
|
||||||
|
pub fn update_buffer<T>(&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<T: ToGlType>(&mut self, format: BufferFormat) {
|
||||||
|
self.texture_format = (format, T::to_gl_enum());
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: resize_buffer
|
||||||
|
|
||||||
|
fn draw<F: FnOnce(&Framebuffer)>(&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<GLuint>, 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>]) -> 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
|
||||||
|
}
|
364
src/lib.rs
364
src/lib.rs
|
@ -9,322 +9,112 @@
|
||||||
//! setup does not support the newest OpenGL. This bug needs to be verified and is be fixable.
|
//! 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.
|
//! OpenGL ~3 is currently required, but OpenGL 2.1 support should be feasible if requested.
|
||||||
|
|
||||||
extern crate glutin;
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate rustic_gl;
|
pub extern crate rustic_gl;
|
||||||
extern crate gl;
|
|
||||||
|
|
||||||
use glutin::GlContext;
|
pub extern crate glutin;
|
||||||
use glutin::dpi::LogicalSize;
|
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<S: ToString>(window_title: S, window_width: i32, window_height: i32) -> Framebuffer {
|
#[macro_export]
|
||||||
let events_loop = glutin::EventsLoop::new();
|
macro_rules! get_fancy {
|
||||||
let window = glutin::WindowBuilder::new()
|
(
|
||||||
.with_title(window_title.to_string())
|
$($setting:ident: $setting_value:expr),*
|
||||||
.with_dimensions(LogicalSize::new(window_width as _, window_height as _));
|
) => {
|
||||||
let context = glutin::ContextBuilder::new();
|
// Support both no trailing comma and trailing comma
|
||||||
let gl_window = glutin::GlWindow::new(window, context, &events_loop).unwrap();
|
// (The core macro impl assumes trailing comma)
|
||||||
|
get_fancy!($($setting: $setting_value),*,)
|
||||||
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),
|
|
||||||
])
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let sampler_location = unsafe {
|
(
|
||||||
let location = gl::GetUniformLocation(program, b"u_tex0\0".as_ptr() as *const _);
|
$($setting:ident),*
|
||||||
gl::UseProgram(program);
|
) => {
|
||||||
gl::Uniform1i(location, 0);
|
// Support both no trailing comma and trailing comma
|
||||||
gl::UseProgram(0);
|
// (The core macro impl assumes trailing comma)
|
||||||
location
|
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 {
|
pub fn gotta_go_fast<S: ToString>(
|
||||||
gl::BindVertexArray(vao);
|
window_title: S,
|
||||||
gl::BindBuffer(gl::ARRAY_BUFFER, vbo);
|
window_width: f64,
|
||||||
VertexFormat::declare(0);
|
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] = [
|
pub fn get_fancy<S: ToString>(config: Config<S>) -> MiniGlFb {
|
||||||
[-1., 1.], [0., 0.], // top left
|
let (events_loop, gl_window) = core::init_glutin_context(&config);
|
||||||
[-1., -1.], [0., 1.], // bottom left
|
let fb = core::init_framebuffer(&config);
|
||||||
[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 {
|
MiniGlFb {
|
||||||
width: window_width,
|
internal: Internal {
|
||||||
height: window_height,
|
events_loop,
|
||||||
events_loop,
|
gl_window,
|
||||||
gl_window,
|
fb,
|
||||||
program,
|
}
|
||||||
sampler_location,
|
|
||||||
vertex_shader: Some(vertex_shader),
|
|
||||||
geometry_shader: None,
|
|
||||||
fragment_shader: Some(fragment_shader),
|
|
||||||
texture,
|
|
||||||
vao,
|
|
||||||
vbo,
|
|
||||||
texture_format,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Framebuffer {
|
pub struct MiniGlFb {
|
||||||
width: i32,
|
/// All fields are exposed for your convienience, but use at your own risk.
|
||||||
height: i32,
|
///
|
||||||
events_loop: glutin::EventsLoop,
|
/// Anything accessed through `internal` is not considered a public API and may be subject to
|
||||||
gl_window: glutin::GlWindow,
|
/// breaking API changes. Only access this field as a last resort if the MiniGlFb API fails
|
||||||
program: GLuint,
|
/// to fit your exact use case.
|
||||||
sampler_location: GLint,
|
pub internal: Internal,
|
||||||
vertex_shader: Option<GLuint>,
|
|
||||||
geometry_shader: Option<GLuint>,
|
|
||||||
fragment_shader: Option<GLuint>,
|
|
||||||
texture: GLuint,
|
|
||||||
vao: GLuint,
|
|
||||||
vbo: GLuint,
|
|
||||||
texture_format: (BufferFormat, GLenum),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Framebuffer {
|
impl MiniGlFb {
|
||||||
pub fn update_buffer<T>(&mut self, image_data: &[T]) {
|
pub fn update_buffer<T>(&mut self, image_data: &[T]) {
|
||||||
// TODO: Safety check on the length of the passed slice so this is actually a safe method
|
self.internal.update_buffer(image_data);
|
||||||
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<T: ToGlType>(&mut self, format: BufferFormat) {
|
|
||||||
self.texture_format = (format, T::to_gl_enum());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Keeps the window open until the user closes it.
|
/// Keeps the window open until the user closes it.
|
||||||
pub fn persist(&mut self) {
|
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.
|
/// When redraw is true, redraws as fast as possible. This function is primarily for debugging.
|
||||||
pub fn persist_and_redraw(&mut self, redraw: bool) {
|
pub fn persist_and_redraw(&mut self, redraw: bool) {
|
||||||
let mut running = true;
|
self.internal.persist_and_redraw(redraw);
|
||||||
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(|_| {});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw<F: FnOnce(&Framebuffer)>(&mut self, f: F) {
|
// TODO: resize_buffer
|
||||||
unsafe {
|
// TODO: set_resizable
|
||||||
gl::UseProgram(self.program);
|
// TODO: change_buffer_format
|
||||||
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<GLuint> {
|
|
||||||
unsafe {
|
|
||||||
let mut vao = 0;
|
|
||||||
gl::GenVertexArrays(1, &mut vao);
|
|
||||||
if vao == 0 {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
Some(vao)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_gl_buffer() -> Option<GLuint> {
|
|
||||||
unsafe {
|
|
||||||
let mut b = 0;
|
|
||||||
gl::GenBuffers(1, &mut b);
|
|
||||||
if b == 0 {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
Some(b)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn rebuild_shader(shader: &mut Option<GLuint>, 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>]) -> 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
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue