Created
March 18, 2019 00:39
-
-
Save Youka/683df083554ab6a2f4e287fea10147a7 to your computer and use it in GitHub Desktop.
Rust OpenGL Backup - Given up because of slow download speed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use std::env; | |
fn main() { | |
// Multisampling not supported on CI machine, else 8 samples are absolutely enough | |
println!("cargo:rustc-env=SAMPLES={}", if env::var("TRAVIS_RUST_VERSION").is_ok() {1} else {8}); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Imports | |
use std::thread; | |
use std::sync::mpsc::{sync_channel, SyncSender, Receiver}; | |
use glutin::{WindowBuilder, ContextBuilder, GlRequest, Api, GlProfile, EventsLoop, ContextTrait}; | |
use glutin::dpi::LogicalSize; | |
use log::info; | |
use super::error::GlError; | |
use super::safe::{Framebuffer, Renderbuffer, Texture2D, Viewport}; | |
// GL environment by hidden window in separate thread | |
pub struct GlEnvironment<DataTypeIn, DataTypeOut> { | |
worker_thread: Option<thread::JoinHandle<()>>, | |
gl_sender: SyncSender<Option<DataTypeIn>>, | |
user_receiver: Receiver<DataTypeOut> | |
} | |
impl<DataTypeIn, DataTypeOut> GlEnvironment<DataTypeIn, DataTypeOut> { | |
// Constructor | |
pub fn new<WorkerType>(version: (u8, u8), worker: WorkerType) -> Self | |
where WorkerType: (Fn(DataTypeIn) -> DataTypeOut) + std::marker::Send + 'static, | |
DataTypeIn: std::marker::Send + 'static, | |
DataTypeOut: std::marker::Send + 'static { | |
// Channels between user & worker | |
let (gl_sender, gl_receiver) = sync_channel::<Option<DataTypeIn>>(0); | |
let (user_sender, user_receiver) = sync_channel::<DataTypeOut>(0); | |
// Return instance | |
Self{ | |
// Work in new thread for separate context | |
worker_thread: Some(thread::spawn(move ||{ | |
info!("Started GlEnvironment thread."); | |
// Create OpenGL context | |
let gl_window = ContextBuilder::new() | |
.with_gl(GlRequest::Specific(Api::OpenGl, version)) | |
.with_gl_profile(GlProfile::Core) | |
.build_windowed( | |
WindowBuilder::new() | |
.with_dimensions(LogicalSize::new(1.0, 1.0)) | |
.with_visibility(false), | |
&EventsLoop::new() | |
).expect(&format!("Unsupported GL version: {:?}!", &version)); | |
unsafe { | |
gl_window.make_current().expect("GL context binding not possible!"); | |
} | |
gl32::load_with(|symbol| gl_window.get_proc_address(symbol) as *const _); | |
// Process data with worker | |
loop { | |
if let Ok(may_data) = gl_receiver.recv() { | |
// User data to process | |
if let Some(data) = may_data { | |
user_sender.send(worker(data)).ok(); | |
// Stop signal from Drop | |
} else { | |
break; | |
} | |
} | |
} | |
info!("Finished GlEnvironment thread."); | |
})), | |
gl_sender, | |
user_receiver | |
} | |
} | |
// Methods | |
pub fn process(&self, data: DataTypeIn) -> Result<DataTypeOut, GlError> | |
where DataTypeIn: std::marker::Send + 'static, | |
DataTypeOut: std::marker::Send + 'static { | |
// Send data to worker | |
self.gl_sender.send(Some(data))?; | |
// Receive worker result | |
Ok(self.user_receiver.recv()?) | |
} | |
} | |
impl<DataTypeIn, DataTypeOut> Drop for GlEnvironment<DataTypeIn, DataTypeOut> { | |
// Deconstructor | |
fn drop(&mut self) { | |
// Send stop signal into thread | |
self.gl_sender.send(None).ok(); | |
// Wait for thread to finish | |
self.worker_thread | |
.take().expect("Thread join handle should have been reserved for drop!") | |
.join().expect("Thread join failed, unexpected termination!"); | |
} | |
} | |
// Supported color types | |
pub enum ColorType { | |
RGB, | |
BGR, | |
RGBA, | |
BGRA | |
} | |
impl ColorType { | |
pub fn size(&self) -> u8 { | |
match self { | |
ColorType::RGB | ColorType::BGR => 3, | |
ColorType::RGBA | ColorType::BGRA => 4 | |
} | |
} | |
pub fn gl_enum(&self) -> gl32::types::GLenum { | |
match self { | |
ColorType::RGB => gl32::RGB, | |
ColorType::BGR => gl32::BGR, | |
ColorType::RGBA => gl32::RGBA, | |
ColorType::BGRA => gl32::BGRA | |
} | |
} | |
} | |
// GL offscreen resources available in context | |
pub struct OffscreenContext { | |
// Size | |
width: u16, | |
height: u16, | |
color_type: ColorType, | |
samples: u8, | |
// Transfer | |
fb_tex: Framebuffer, | |
tex_color: Texture2D, | |
// Draw | |
fb_render: Framebuffer, | |
_rb_color: Renderbuffer, | |
_rb_depth_stencil: Renderbuffer | |
} | |
impl OffscreenContext { | |
// Constructor | |
pub fn new(width: u16, height: u16, color_type: ColorType, samples: u8) -> Result<Self,GlError> { | |
// Create transfer texture | |
let tex_color = Texture2D::generate(); | |
tex_color.bind(); | |
Texture2D::tex_image_2d(gl32::RGBA, width, height, color_type.gl_enum(), gl32::UNSIGNED_BYTE, None); | |
Texture2D::unbind(); | |
// Create framebuffer for transfer texture | |
let fb_tex = Framebuffer::generate(); | |
fb_tex.bind(); | |
Framebuffer::texture_2d(gl32::COLOR_ATTACHMENT0, &tex_color); | |
if Framebuffer::status() != gl32::FRAMEBUFFER_COMPLETE { | |
Framebuffer::unbind(); | |
return Err(GlError::new("Couldn't create texture framebuffer!")); | |
} | |
Framebuffer::unbind(); | |
// Create multisampled renderbuffer for color | |
let rb_color = Renderbuffer::generate(); | |
rb_color.bind(); | |
Renderbuffer::storage_multisample(samples, gl32::RGBA8, width, height); | |
// Create multisampled renderbuffer for depth & stencil | |
let rb_depth_stencil = Renderbuffer::generate(); | |
rb_depth_stencil.bind(); | |
Renderbuffer::storage_multisample(samples, gl32::DEPTH24_STENCIL8, width, height); | |
Renderbuffer::unbind(); | |
// Create framebuffer for rendering | |
let fb_render = Framebuffer::generate(); | |
fb_render.bind(); | |
Framebuffer::renderbuffer(gl32::COLOR_ATTACHMENT0, &rb_color); | |
Framebuffer::renderbuffer(gl32::DEPTH_STENCIL_ATTACHMENT, &rb_depth_stencil); | |
if Framebuffer::status() != gl32::FRAMEBUFFER_COMPLETE { | |
Framebuffer::unbind(); | |
return Err(GlError::new("Couldn't create rendering framebuffer!")); | |
} | |
Framebuffer::unbind(); | |
// Return resources | |
Ok(Self { | |
width, | |
height, | |
color_type, | |
samples, | |
fb_tex, | |
tex_color, | |
fb_render, | |
_rb_color: rb_color, | |
_rb_depth_stencil: rb_depth_stencil | |
}) | |
} | |
// Getters | |
pub fn width(&self) -> u16 { | |
self.width | |
} | |
pub fn height(&self) -> u16 { | |
self.height | |
} | |
pub fn color_type(&self) -> &ColorType { | |
&self.color_type | |
} | |
pub fn samples(&self) -> u8 { | |
self.samples | |
} | |
// Methods | |
pub fn process<CB>(&self, buffer: &mut [u8], callback: CB) -> Result<(), GlError> | |
where CB: FnOnce() { | |
// Check buffer size enough for context | |
if buffer.len() < self.width as usize * self.height as usize * self.color_type.size() as usize { | |
return Err(GlError::new("Buffer size too small!")); | |
} | |
// Upload image into texture | |
self.tex_color.bind(); | |
Texture2D::tex_sub_image_2d(0, 0, self.width, self.height, self.color_type.gl_enum(), gl32::UNSIGNED_BYTE, buffer); | |
Texture2D::unbind(); | |
// Copy texture into renderbuffer | |
self.fb_tex.bind_target(gl32::READ_FRAMEBUFFER); | |
self.fb_render.bind_target(gl32::DRAW_FRAMEBUFFER); | |
Framebuffer::blit(0, 0, self.width as i32, self.height as i32, 0, 0, self.width as i32, self.height as i32, gl32::COLOR_BUFFER_BIT, gl32::NEAREST); | |
Framebuffer::unbind(); | |
// Setup renderbuffer viewport | |
Viewport(0, 0, self.width, self.height); | |
// Draw on renderbuffer | |
self.fb_render.bind(); | |
callback(); | |
Framebuffer::unbind(); | |
// Copy renderbuffer into texture | |
self.fb_render.bind_target(gl32::READ_FRAMEBUFFER); | |
self.fb_tex.bind_target(gl32::DRAW_FRAMEBUFFER); | |
Framebuffer::blit(0, 0, self.width as i32, self.height as i32, 0, 0, self.width as i32, self.height as i32, gl32::COLOR_BUFFER_BIT, gl32::NEAREST); | |
Framebuffer::unbind(); | |
// Download image from texture | |
self.tex_color.bind(); | |
Texture2D::get_tex_image(self.color_type.gl_enum(), gl32::UNSIGNED_BYTE, buffer); | |
Texture2D::unbind(); | |
// All worked | |
Ok(()) | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Imports | |
use std::error::Error; | |
use std::fmt; | |
use std::sync::mpsc::{SendError, RecvError}; | |
use super::safe::GetError; | |
// Structure | |
#[derive(Debug)] | |
pub struct GlError { | |
message: String, | |
source: Option<Box<Error>> | |
} | |
// Implementation | |
impl GlError { | |
pub fn new(message: &str) -> Self { | |
Self{ message: message.to_string(), source: None } | |
} | |
pub fn new_with_source(message: &str, source: Box<Error>) -> Self { | |
Self{ message: message.to_string(), source: Some(source) } | |
} | |
pub fn from_gl() -> Option<Self> { | |
let error_code = GetError(); | |
if error_code == gl32::NO_ERROR { | |
None | |
} else { | |
Some(Self::new(&format!("Error code: {:#X} (see https://www.khronos.org/opengl/wiki/OpenGL_Error#Meaning_of_errors )", error_code))) | |
} | |
} | |
} | |
// Extensions | |
impl Error for GlError { | |
fn description(&self) -> &str { | |
&self.message | |
} | |
fn source(&self) -> Option<&(dyn Error + 'static)> { | |
if let Some(ref source) = self.source { | |
Some(source.as_ref()) | |
} else { | |
None | |
} | |
} | |
} | |
impl fmt::Display for GlError { | |
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | |
if let Some(source) = self.source() { | |
write!(f, "{} - Source: {}", self.description(), source) | |
} else { | |
write!(f, "{}", self.description()) | |
} | |
} | |
} | |
impl<DataType> From<SendError<DataType>> for GlError | |
where DataType: std::marker::Send + 'static { | |
fn from(error: SendError<DataType>) -> Self { | |
Self::new_with_source("Send error!", Box::new(error)) | |
} | |
} | |
impl From<RecvError> for GlError { | |
fn from(error: RecvError) -> Self { | |
Self::new_with_source("Receive error!", Box::new(error)) | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#[cfg(test)] | |
mod gl_utils_tests { | |
// Imports | |
use ssb_renderer::gl_utils::{error::GlError, safe::*, environment::GlEnvironment}; | |
use std::sync::mpsc::channel; | |
// Test resource | |
fn do_gl_things(data: u8) -> u8 { | |
assert!(data == 42 || data == 9); | |
// Note: GL version string may not contain current profile but newest possible version (f.e. mesa) | |
assert!(GetString(gl32::VERSION).is_some()); | |
// Zero is always an invalid ID in OpenGL | |
assert!(GetString(0).is_none()); | |
println!("{}", GlError::from_gl().expect("Last GL call should have been wrong!")); | |
assert!(GlError::from_gl().is_none()); | |
data + 1 | |
} | |
// Tester | |
#[test] | |
fn test_gl_environment() { | |
let gl_env = GlEnvironment::new((3, 2), do_gl_things); | |
assert!(gl_env.process(42).expect("Simple process didn't work!") == 43); | |
assert!(gl_env.process(9).expect("Another simple process didn't work!") == 10); | |
} | |
// Tester | |
#[test] | |
fn test_gl_error() { | |
// Send error | |
let (sender, _) = channel::<u8>(); | |
let send_err = GlError::from(sender.send(0).expect_err("Sending shouldn't be possible!")); | |
println!("{}", send_err); | |
// Receive error | |
let (_, receiver) = channel::<u8>(); | |
let recv_err = GlError::from(receiver.recv().expect_err("Receiving shouldn't be possible!")); | |
println!("{:?}", recv_err); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Imports | |
use super::safe::*; | |
#[inline] | |
pub fn build_program(vshader_source: &str, fshader_source: &str) -> Program { | |
// Compile shaders | |
let vertex_shader = Shader::create(gl32::VERTEX_SHADER); | |
vertex_shader.source(vshader_source); | |
vertex_shader.compile().expect("Vertex shader couldn't compile!"); | |
let fragment_shader = Shader::create(gl32::FRAGMENT_SHADER); | |
fragment_shader.source(fshader_source); | |
fragment_shader.compile().expect("Fragment shader couldn't compile!"); | |
// Link shader program | |
let shader_program = Program::create(); | |
shader_program.attach(&vertex_shader); | |
shader_program.attach(&fragment_shader); | |
shader_program.link().expect("Shader program couldn't link!"); | |
// Return only program, shaders aren't needed anymore | |
shader_program | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Imports | |
use microbench::{bench, Options}; | |
use std::time::Duration; | |
use ssb_renderer::gl_utils::environment::{GlEnvironment, OffscreenContext, ColorType}; | |
// Benchmark | |
fn main() { | |
GlEnvironment::new((3, 2), |()| { | |
let offscreen_context = OffscreenContext::new(1920, 1080, ColorType::RGBA, 8).unwrap(); | |
let mut buffer = vec![0u8; offscreen_context.width() as usize * offscreen_context.height() as usize * offscreen_context.color_type().size() as usize]; | |
bench(&Options::default().time(Duration::from_secs(2)), "Offscreen context processing overhead", || { | |
offscreen_context.process(&mut buffer, || {}).unwrap(); | |
}); | |
}).process(()).unwrap(); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#[cfg(test)] | |
mod rendering_tests { | |
// Imports | |
use ssb_renderer::gl_utils::{safe::*, macros::build_program, environment::{GlEnvironment, ColorType, OffscreenContext}, error::GlError}; | |
use image::{RgbImage, RgbaImage}; | |
use std::path::Path; | |
// TRIANGLE | |
// Test resources | |
const TRIANGLE_VERTEX_DATA: [f32; 6] = [ | |
0.0, -0.5, | |
-0.5, 0.5, | |
0.5, 0.5, | |
]; | |
const TRIANGLE_VERTEX_SHADER: &str = "#version 150 core | |
in vec2 position; | |
void main() | |
{ | |
gl_Position = vec4(position, 0.0, 1.0); | |
}"; | |
const TRIANGLE_FRAGMENT_SHADER: &str = "#version 150 core | |
out vec4 color; | |
void main() | |
{ | |
color = vec4(1.0, 1.0, 1.0, 1.0); | |
}"; | |
fn draw_triangle(img_size: (u32, u32)) -> RgbImage { | |
// Create empty image | |
let img = RgbImage::new(img_size.0, img_size.1); | |
// Unpack image | |
let width = img.width(); | |
let height = img.height(); | |
let mut buffer: Vec<u8> = img.into_raw(); | |
// Render on image with offscreen rendering context | |
OffscreenContext::new(width as u16, height as u16, ColorType::RGB, env!("SAMPLES").parse::<u8>().expect("SAMPLES environment variable not a number!")) | |
.expect("Offscreen context required!") | |
.process(&mut buffer, || { | |
// Link shader program | |
let shader_program = build_program(&TRIANGLE_VERTEX_SHADER, &TRIANGLE_FRAGMENT_SHADER); | |
shader_program.using(); | |
// Create vertex attribute storage (required by GL core profile!) | |
let vao = VAO::generate(); | |
vao.bind(); | |
// Bind vertex data | |
let vbo = VBO::generate(); | |
vbo.bind(); | |
VBO::data(&TRIANGLE_VERTEX_DATA); | |
// Interpret vertex data | |
let position_location = shader_program.attrib_location("position"); | |
VBO::enable_attrib_array(position_location as u32); | |
VBO::attrib_pointer(position_location as u32, 2, 0, 0); | |
// Draw! | |
VBO::draw_arrays(gl32::TRIANGLES, 0, 3); | |
}).ok(); | |
// Return processed image | |
return RgbImage::from_raw(width, height, buffer).expect("Image repackaging failed!"); | |
} | |
// Tester | |
#[test] | |
fn test_triangle_image() { | |
// Create OpenGL capable environment | |
GlEnvironment::new((3, 2), draw_triangle) | |
// Process new image in environment | |
.process((800, 800)).expect("Drawing failed!") | |
// Save image to disk | |
.save( | |
Path::new(&env!("CARGO_MANIFEST_DIR")) | |
.join("../target/triangle_image.png") | |
).expect("Image saving failed!"); | |
} | |
// QUAD (colored) | |
// Test resources | |
const QUAD_VERTEX_DATA: [f32; 24] = [ | |
// Pos Color | |
-0.5, 0.5, 1.0, 0.0, 0.0, 0.5, | |
0.5, 0.5, 0.0, 1.0, 0.0, 1.0, | |
0.5, -0.5, 0.0, 0.0, 1.0, 0.5, | |
-0.5, -0.5, 1.0, 1.0, 0.0, 1.0 | |
]; | |
const QUAD_INDICES: [u32; 6] = [ | |
0, 1, 2, | |
2, 3, 0 | |
]; | |
const QUAD_VERTEX_SHADER: &str = "#version 150 core | |
in vec2 position; | |
in vec4 color; | |
out vec4 vertex_color; | |
void main() | |
{ | |
gl_Position = vec4(position, 0.0, 1.0); | |
vertex_color = color; | |
}"; | |
const QUAD_FRAGMENT_SHADER: &str = "#version 150 core | |
in vec4 vertex_color; | |
out vec4 fragment_color; | |
void main() | |
{ | |
fragment_color = vertex_color; | |
}"; | |
fn draw_quad(img: RgbaImage) -> RgbaImage { | |
// Unpack image | |
let width = img.width(); | |
let height = img.height(); | |
let mut buffer: Vec<u8> = img.into_raw(); | |
// Render on image with offscreen rendering context | |
OffscreenContext::new(width as u16, height as u16, ColorType::RGBA, env!("SAMPLES").parse::<u8>().expect("SAMPLES environment variable not a number!")) | |
.expect("Offscreen context required!") | |
.process(&mut buffer, || { | |
// Link shader program | |
let shader_program = build_program(&QUAD_VERTEX_SHADER, &QUAD_FRAGMENT_SHADER); | |
shader_program.using(); | |
// Create vertex attribute storage (required by GL core profile!) | |
let vao = VAO::generate(); | |
vao.bind(); | |
// Bind vertex data | |
let vbo = VBO::generate(); | |
vbo.bind(); | |
VBO::data(&QUAD_VERTEX_DATA); | |
// Interpret vertex data | |
let position_location = shader_program.attrib_location("position"); | |
VBO::enable_attrib_array(position_location as u32); | |
VBO::attrib_pointer(position_location as u32, 2/*vec2*/, 6/*skip current position+color for next entry*/, 0/*start with first data value*/); | |
let color_location = shader_program.attrib_location("color"); | |
VBO::enable_attrib_array(color_location as u32); | |
VBO::attrib_pointer(color_location as u32, 4, 6, 2); | |
// Bind indices | |
let ebo = EBO::generate(); | |
ebo.bind(); | |
EBO::data(&QUAD_INDICES); | |
// Enable blending | |
Enable(gl32::BLEND); | |
BlendFuncSeparate(gl32::SRC_ALPHA, gl32::ONE_MINUS_SRC_ALPHA, gl32::ZERO, gl32::ONE); | |
BlendEquation(gl32::FUNC_ADD); | |
// Draw! | |
EBO::draw_elements(gl32::TRIANGLES, 6); | |
if let Some(err) = GlError::from_gl() { | |
panic!("draw_elements: {}", err); | |
} | |
}).ok(); | |
// Return processed image | |
return RgbaImage::from_raw(width, height, buffer).expect("Image repackaging failed!"); | |
} | |
// Tester | |
#[test] | |
fn test_quad_image() { | |
// Get manifest directory | |
let dir = env!("CARGO_MANIFEST_DIR"); | |
// Load image | |
let sample_image = image::open( | |
Path::new(&dir) | |
.join("tests/ayaya.png") | |
).expect("Couldn't load sample image!").to_rgba(); | |
// Draw on image | |
let quad_image = GlEnvironment::new((3, 2), draw_quad).process(sample_image).expect("Drawing failed!"); | |
// Save image | |
quad_image.save( | |
Path::new(&dir) | |
.join("../target/quad_image.png") | |
).expect("Image saving failed!"); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Imports | |
use std::ffi::{CStr, CString}; | |
use std::os::raw::{c_char}; | |
use std::ptr::{null, null_mut}; | |
use std::mem::size_of; | |
use super::error::GlError; | |
// Helper macro | |
macro_rules! check_loaded { | |
($name:ident, $body:expr) => { | |
if gl32::$name::is_loaded() { | |
$body | |
} else { | |
panic!("{} not loaded!", stringify!($name)); | |
} | |
} | |
} | |
// FUNCTIONS | |
// GetString | |
#[allow(non_snake_case)] | |
pub fn GetString(name: gl32::types::GLenum) -> Option<String> { | |
check_loaded!( | |
GetString, | |
unsafe { | |
let gl_string = gl32::GetString(name); | |
if gl_string == null() { | |
None | |
} else { | |
Some(CStr::from_ptr(gl_string as *const c_char).to_string_lossy().to_string()) | |
} | |
} | |
) | |
} | |
// GetError | |
#[allow(non_snake_case)] | |
pub fn GetError() -> gl32::types::GLenum { | |
check_loaded!( | |
GetError, | |
unsafe { | |
gl32::GetError() | |
} | |
) | |
} | |
// ClearColor | |
#[allow(non_snake_case)] | |
pub fn ClearColor(red: f32, green: f32, blue: f32, alpha: f32) { | |
check_loaded!( | |
ClearColor, | |
unsafe { | |
gl32::ClearColor(red, green, blue, alpha); | |
} | |
); | |
} | |
// Clear | |
#[allow(non_snake_case)] | |
pub fn Clear(mask: gl32::types::GLenum) { | |
check_loaded!( | |
Clear, | |
unsafe { | |
gl32::Clear(mask); | |
} | |
); | |
} | |
// Viewport | |
#[allow(non_snake_case)] | |
pub fn Viewport(x: u16, y: u16, width: u16, height: u16) { | |
check_loaded!( | |
Viewport, | |
unsafe { | |
gl32::Viewport( | |
x as gl32::types::GLint, y as gl32::types::GLint, | |
width as gl32::types::GLsizei, height as gl32::types::GLsizei | |
); | |
} | |
); | |
} | |
// Enable / Disable | |
#[allow(non_snake_case)] | |
pub fn Enable(cap: gl32::types::GLenum) { | |
check_loaded!( | |
Enable, | |
unsafe { | |
gl32::Enable(cap); | |
} | |
); | |
} | |
#[allow(non_snake_case)] | |
pub fn Disable(cap: gl32::types::GLenum) { | |
check_loaded!( | |
Disable, | |
unsafe { | |
gl32::Disable(cap); | |
} | |
); | |
} | |
// Blending | |
#[allow(non_snake_case)] | |
pub fn BlendFunc(sfactor: gl32::types::GLenum, dfactor: gl32::types::GLenum) { | |
check_loaded!( | |
BlendFunc, | |
unsafe { | |
gl32::BlendFunc(sfactor, dfactor); | |
} | |
); | |
} | |
#[allow(non_snake_case)] | |
pub fn BlendFuncSeparate(srcRGB: gl32::types::GLenum, dstRGB: gl32::types::GLenum, srcAlpha: gl32::types::GLenum, dstAlpha: gl32::types::GLenum) { | |
check_loaded!( | |
BlendFuncSeparate, | |
unsafe { | |
gl32::BlendFuncSeparate(srcRGB, dstRGB, srcAlpha, dstAlpha); | |
} | |
); | |
} | |
#[allow(non_snake_case)] | |
pub fn BlendEquation(mode: gl32::types::GLenum) { | |
check_loaded!( | |
BlendEquation, | |
unsafe { | |
gl32::BlendEquation(mode); | |
} | |
); | |
} | |
// OBJECTS | |
// Framebuffer | |
pub struct Framebuffer { | |
id: gl32::types::GLuint | |
} | |
impl Framebuffer { | |
// New | |
pub fn generate() -> Self { | |
check_loaded!( | |
GenFramebuffers, | |
{ | |
let mut id: gl32::types::GLuint = 0; | |
unsafe { | |
gl32::GenFramebuffers(1, &mut id); | |
} | |
Self{ | |
id | |
} | |
} | |
) | |
} | |
// Bind | |
pub fn bind(&self) { | |
check_loaded!( | |
BindFramebuffer, | |
unsafe { | |
gl32::BindFramebuffer(gl32::FRAMEBUFFER, self.id); | |
} | |
); | |
} | |
pub fn bind_target(&self, target: gl32::types::GLenum) { | |
check_loaded!( | |
BindFramebuffer, | |
unsafe { | |
gl32::BindFramebuffer(target, self.id); | |
} | |
); | |
} | |
pub fn unbind() { | |
check_loaded!( | |
BindFramebuffer, | |
unsafe { | |
gl32::BindFramebuffer(gl32::FRAMEBUFFER, 0); | |
} | |
); | |
} | |
// Check complete status | |
pub fn status() -> gl32::types::GLenum { | |
check_loaded!( | |
CheckFramebufferStatus, | |
unsafe { | |
gl32::CheckFramebufferStatus(gl32::FRAMEBUFFER) | |
} | |
) | |
} | |
// Link | |
pub fn texture_2d(attachment: gl32::types::GLenum, texture: &Texture2D) { | |
check_loaded!( | |
FramebufferTexture2D, | |
unsafe { | |
gl32::FramebufferTexture2D(gl32::FRAMEBUFFER, attachment, gl32::TEXTURE_2D, texture.id, 0); | |
} | |
); | |
} | |
pub fn renderbuffer(attachment: gl32::types::GLenum, renderbuffer: &Renderbuffer) { | |
check_loaded!( | |
FramebufferRenderbuffer, | |
unsafe { | |
gl32::FramebufferRenderbuffer(gl32::FRAMEBUFFER, attachment, gl32::RENDERBUFFER, renderbuffer.id); | |
} | |
); | |
} | |
// Blit | |
pub fn blit(src_x0: i32, src_y0: i32, src_x1: i32, src_y1: i32, | |
dst_x0: i32, dst_y0: i32, dst_x1: i32, dst_y1: i32, | |
mask: gl32::types::GLbitfield, filter: gl32::types::GLenum) { | |
check_loaded!( | |
BlitFramebuffer, | |
unsafe { | |
gl32::BlitFramebuffer( | |
src_x0, src_y0, src_x1, src_y1, | |
dst_x0, dst_y0, dst_x1, dst_y1, | |
mask, filter | |
); | |
} | |
); | |
} | |
} | |
impl Drop for Framebuffer { | |
// Delete | |
fn drop(&mut self) { | |
check_loaded!( | |
DeleteFramebuffers, | |
unsafe { | |
gl32::DeleteFramebuffers(1, &self.id); | |
} | |
); | |
} | |
} | |
// Texture (2D) | |
pub struct Texture2D { | |
id: gl32::types::GLuint | |
} | |
impl Texture2D { | |
// New | |
pub fn generate() -> Self { | |
check_loaded!( | |
GenTextures, | |
{ | |
let mut id: gl32::types::GLuint = 0; | |
unsafe { | |
gl32::GenTextures(1, &mut id); | |
} | |
Self{ | |
id | |
} | |
} | |
) | |
} | |
// Bind | |
pub fn bind(&self) { | |
check_loaded!( | |
BindTexture, | |
unsafe { | |
gl32::BindTexture(gl32::TEXTURE_2D, self.id); | |
} | |
); | |
} | |
pub fn unbind() { | |
check_loaded!( | |
BindTexture, | |
unsafe { | |
gl32::BindTexture(gl32::TEXTURE_2D, 0); | |
} | |
); | |
} | |
// Memory | |
pub fn tex_image_2d(internalformat: gl32::types::GLenum, width: u16, height: u16, data_format: gl32::types::GLenum, data_type: gl32::types::GLenum, data: Option<&[u8]>) { | |
check_loaded!( | |
TexImage2D, | |
unsafe { | |
gl32::TexImage2D( | |
gl32::TEXTURE_2D, 0, internalformat as gl32::types::GLint, | |
width as gl32::types::GLsizei, height as gl32::types::GLsizei, 0, | |
data_format, data_type, data.map_or(null(), |bytes| bytes.as_ptr() as *const _) | |
); | |
} | |
); | |
} | |
pub fn tex_sub_image_2d(xoffset: i16, yoffset: i16, width: u16, height: u16, data_format: gl32::types::GLenum, data_type: gl32::types::GLenum, data: &[u8]) { | |
check_loaded!( | |
TexSubImage2D, | |
unsafe { | |
gl32::TexSubImage2D( | |
gl32::TEXTURE_2D, 0, | |
xoffset as gl32::types::GLint, yoffset as gl32::types::GLint, width as gl32::types::GLsizei, height as gl32::types::GLsizei, | |
data_format, data_type, data.as_ptr() as *const _ | |
); | |
} | |
); | |
} | |
pub fn get_tex_image(data_format: gl32::types::GLenum, data_type: gl32::types::GLenum, data: &mut [u8]) { | |
check_loaded!( | |
GetTexImage, | |
unsafe { | |
gl32::GetTexImage(gl32::TEXTURE_2D, 0, data_format, data_type, data.as_ptr() as *mut _); | |
} | |
); | |
} | |
} | |
impl Drop for Texture2D { | |
// Delete | |
fn drop(&mut self) { | |
check_loaded!( | |
DeleteTextures, | |
unsafe { | |
gl32::DeleteTextures(1, &self.id); | |
} | |
); | |
} | |
} | |
// Renderbuffer (with multisampling) | |
pub struct Renderbuffer { | |
id: gl32::types::GLuint | |
} | |
impl Renderbuffer { | |
// New | |
pub fn generate() -> Self { | |
check_loaded!( | |
GenRenderbuffers, | |
{ | |
let mut id: gl32::types::GLuint = 0; | |
unsafe { | |
gl32::GenRenderbuffers(1, &mut id); | |
} | |
Self{ | |
id | |
} | |
} | |
) | |
} | |
// Bind | |
pub fn bind(&self) { | |
check_loaded!( | |
BindRenderbuffer, | |
unsafe { | |
gl32::BindRenderbuffer(gl32::RENDERBUFFER, self.id); | |
} | |
); | |
} | |
pub fn unbind() { | |
check_loaded!( | |
BindRenderbuffer, | |
unsafe { | |
gl32::BindRenderbuffer(gl32::RENDERBUFFER, 0); | |
} | |
); | |
} | |
// Memory | |
pub fn storage_multisample(samples: u8, internalformat: gl32::types::GLenum, width: u16, height: u16) { | |
check_loaded!( | |
RenderbufferStorageMultisample, | |
unsafe { | |
gl32::RenderbufferStorageMultisample( | |
gl32::RENDERBUFFER, samples as gl32::types::GLsizei, internalformat, | |
width as gl32::types::GLsizei, height as gl32::types::GLsizei | |
); | |
} | |
); | |
} | |
} | |
impl Drop for Renderbuffer { | |
// Delete | |
fn drop(&mut self) { | |
check_loaded!( | |
DeleteRenderbuffers, | |
unsafe { | |
gl32::DeleteRenderbuffers(1, &self.id); | |
} | |
); | |
} | |
} | |
// VBO (Vertex buffer object) | |
pub struct VBO { | |
id: gl32::types::GLuint | |
} | |
impl VBO { | |
// New | |
pub fn generate() -> Self { | |
check_loaded!( | |
GenBuffers, | |
{ | |
let mut id: gl32::types::GLuint = 0; | |
unsafe { | |
gl32::GenBuffers(1, &mut id); | |
} | |
Self{ | |
id | |
} | |
} | |
) | |
} | |
// Bind | |
pub fn bind(&self) { | |
check_loaded!( | |
BindBuffer, | |
unsafe { | |
gl32::BindBuffer(gl32::ARRAY_BUFFER, self.id); | |
} | |
); | |
} | |
pub fn unbind() { | |
check_loaded!( | |
BindBuffer, | |
unsafe { | |
gl32::BindBuffer(gl32::ARRAY_BUFFER, 0); | |
} | |
); | |
} | |
// Memory | |
pub fn data(data: &[f32]) { | |
check_loaded!( | |
BufferData, | |
unsafe { | |
gl32::BufferData(gl32::ARRAY_BUFFER, (data.len() * size_of::<f32>()) as gl32::types::GLsizeiptr, data.as_ptr() as *const _, gl32::STATIC_DRAW); | |
} | |
); | |
} | |
// Attributes | |
pub fn enable_attrib_array(index: u32) { | |
check_loaded!( | |
EnableVertexAttribArray, | |
unsafe { | |
gl32::EnableVertexAttribArray(index); | |
} | |
); | |
} | |
pub fn disable_attrib_array(index: u32) { | |
check_loaded!( | |
DisableVertexAttribArray, | |
unsafe { | |
gl32::DisableVertexAttribArray(index); | |
} | |
); | |
} | |
pub fn attrib_pointer(index: u32, size: i32, stride: i32, offset: isize) { | |
check_loaded!( | |
VertexAttribPointer, | |
unsafe { | |
gl32::VertexAttribPointer( | |
index, size, gl32::FLOAT, gl32::FALSE, | |
stride * size_of::<f32>() as i32, (offset * size_of::<f32>() as isize) as *const _ | |
); | |
} | |
); | |
} | |
// Draw | |
pub fn draw_arrays(mode: gl32::types::GLenum, first: u16, count: u16) { | |
check_loaded!( | |
DrawArrays, | |
unsafe { | |
gl32::DrawArrays(mode, first as gl32::types::GLint, count as gl32::types::GLsizei); | |
} | |
); | |
} | |
} | |
impl Drop for VBO { | |
// Delete | |
fn drop(&mut self) { | |
check_loaded!( | |
DeleteBuffers, | |
unsafe { | |
gl32::DeleteBuffers(1, &self.id); | |
} | |
); | |
} | |
} | |
// EBO (Element buffer object) | |
pub struct EBO { | |
id: gl32::types::GLuint | |
} | |
impl EBO { | |
// New | |
pub fn generate() -> Self { | |
check_loaded!( | |
GenBuffers, | |
{ | |
let mut id: gl32::types::GLuint = 0; | |
unsafe { | |
gl32::GenBuffers(1, &mut id); | |
} | |
Self{ | |
id | |
} | |
} | |
) | |
} | |
// Bind | |
pub fn bind(&self) { | |
check_loaded!( | |
BindBuffer, | |
unsafe { | |
gl32::BindBuffer(gl32::ELEMENT_ARRAY_BUFFER, self.id); | |
} | |
); | |
} | |
pub fn unbind() { | |
check_loaded!( | |
BindBuffer, | |
unsafe { | |
gl32::BindBuffer(gl32::ELEMENT_ARRAY_BUFFER, 0); | |
} | |
); | |
} | |
// Memory | |
pub fn data(data: &[u32]) { | |
check_loaded!( | |
BufferData, | |
unsafe { | |
gl32::BufferData(gl32::ELEMENT_ARRAY_BUFFER, (data.len() * size_of::<u32>()) as gl32::types::GLsizeiptr, data.as_ptr() as *const _, gl32::STATIC_DRAW); | |
} | |
); | |
} | |
// Draw | |
pub fn draw_elements(mode: gl32::types::GLenum, count: u16) { | |
check_loaded!( | |
DrawElements, | |
unsafe { | |
gl32::DrawElements(mode, count as gl32::types::GLsizei, gl32::UNSIGNED_INT, null() as *const _); | |
} | |
); | |
} | |
} | |
impl Drop for EBO { | |
// Delete | |
fn drop(&mut self) { | |
check_loaded!( | |
DeleteBuffers, | |
unsafe { | |
gl32::DeleteBuffers(1, &self.id); | |
} | |
); | |
} | |
} | |
// VAO (Vertex array object) | |
pub struct VAO { | |
id: gl32::types::GLuint | |
} | |
impl VAO { | |
// New | |
pub fn generate() -> Self { | |
check_loaded!( | |
GenVertexArrays, | |
{ | |
let mut id: gl32::types::GLuint = 0; | |
unsafe { | |
gl32::GenVertexArrays(1, &mut id); | |
} | |
Self{ | |
id | |
} | |
} | |
) | |
} | |
// Bind | |
pub fn bind(&self) { | |
check_loaded!( | |
BindVertexArray, | |
unsafe { | |
gl32::BindVertexArray(self.id); | |
} | |
); | |
} | |
pub fn unbind() { | |
check_loaded!( | |
BindVertexArray, | |
unsafe { | |
gl32::BindVertexArray(0); | |
} | |
); | |
} | |
} | |
impl Drop for VAO { | |
// Delete | |
fn drop(&mut self) { | |
check_loaded!( | |
DeleteVertexArrays, | |
unsafe { | |
gl32::DeleteVertexArrays(1, &self.id); | |
} | |
); | |
} | |
} | |
// Shader | |
pub struct Shader { | |
id: gl32::types::GLuint | |
} | |
impl Shader { | |
// New | |
pub fn create(shader_type: gl32::types::GLenum) -> Self { | |
check_loaded!( | |
CreateShader, | |
unsafe { | |
Self { | |
id: gl32::CreateShader(shader_type) | |
} | |
} | |
) | |
} | |
// Source | |
pub fn source(&self, string: &str) { | |
check_loaded!( | |
ShaderSource, | |
unsafe { | |
let source = CString::new(string).expect("Source string shouldn't contain null bytes!"); | |
gl32::ShaderSource( | |
self.id, 1, | |
&source.as_ptr() as *const *const gl32::types::GLchar, | |
null() | |
); | |
} | |
); | |
} | |
pub fn compile(&self) -> Result<(), GlError> { | |
check_loaded!( | |
CompileShader, | |
unsafe { | |
gl32::CompileShader(self.id); | |
let mut success: gl32::types::GLint = 0; | |
gl32::GetShaderiv(self.id, gl32::COMPILE_STATUS, &mut success); | |
if success == 0 { | |
const BUF_SIZE: gl32::types::GLsizei = 1024; | |
let mut info_log: [gl32::types::GLchar; BUF_SIZE as usize] = [0; BUF_SIZE as usize]; | |
gl32::GetShaderInfoLog(self.id, BUF_SIZE, null_mut(), info_log.as_mut_ptr()); | |
return Err(GlError::new( | |
&CStr::from_ptr(info_log.as_ptr()).to_string_lossy().to_string() | |
)); | |
} | |
Ok(()) | |
} | |
) | |
} | |
} | |
impl Drop for Shader { | |
// Delete | |
fn drop(&mut self) { | |
check_loaded!( | |
DeleteShader, | |
unsafe { | |
gl32::DeleteShader(self.id); | |
} | |
); | |
} | |
} | |
// Program | |
pub struct Program { | |
id: gl32::types::GLuint | |
} | |
impl Program { | |
// New | |
pub fn create() -> Self { | |
check_loaded!( | |
CreateProgram, | |
unsafe { | |
Self { | |
id: gl32::CreateProgram() | |
} | |
} | |
) | |
} | |
// Attach | |
pub fn attach(&self, shader: &Shader) { | |
check_loaded!( | |
AttachShader, | |
unsafe { | |
gl32::AttachShader(self.id, shader.id); | |
} | |
); | |
} | |
// Link | |
pub fn link(&self) -> Result<(), GlError> { | |
check_loaded!( | |
LinkProgram, | |
unsafe { | |
gl32::LinkProgram(self.id); | |
let mut success: gl32::types::GLint = 0; | |
gl32::GetProgramiv(self.id, gl32::LINK_STATUS, &mut success); | |
if success == 0 { | |
const BUF_SIZE: gl32::types::GLsizei = 1024; | |
let mut info_log: [gl32::types::GLchar; BUF_SIZE as usize] = [0; BUF_SIZE as usize]; | |
gl32::GetProgramInfoLog(self.id, BUF_SIZE, null_mut(), info_log.as_mut_ptr()); | |
return Err(GlError::new( | |
&CStr::from_ptr(info_log.as_ptr()).to_string_lossy().to_string() | |
)); | |
} | |
Ok(()) | |
} | |
) | |
} | |
// Use | |
pub fn using(&self) { | |
check_loaded!( | |
UseProgram, | |
unsafe { | |
gl32::UseProgram(self.id); | |
} | |
); | |
} | |
// Attributes | |
pub fn attrib_location(&self, name: &str) -> gl32::types::GLint { | |
check_loaded!( | |
GetAttribLocation, | |
unsafe { | |
gl32::GetAttribLocation(self.id, CString::new(name).expect("Name string shouldn't contain null bytes!").as_ptr()) | |
} | |
) | |
} | |
} | |
impl Drop for Program { | |
// Delete | |
fn drop(&mut self) { | |
check_loaded!( | |
DeleteProgram, | |
unsafe { | |
gl32::DeleteProgram(self.id); | |
} | |
); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#[cfg(test)] | |
mod tessellation_tests { | |
// Imports | |
use lyon::math::point; | |
use lyon::path::Path; | |
use lyon::tessellation::{VertexBuffers, FillVertex, FillTessellator, FillOptions, geometry_builder}; | |
#[test] | |
fn test_simple_shape2triangles() { | |
// Build a path | |
let mut path_builder = Path::builder(); | |
path_builder.move_to(point(0.0, 0.0)); | |
path_builder.line_to(point(1.0, 0.0)); | |
path_builder.quadratic_bezier_to(point(2.0, 0.0), point(2.0, 1.0)); | |
path_builder.cubic_bezier_to(point(1.0, 1.0), point(0.0, 1.0), point(0.0, 0.0)); | |
path_builder.close(); | |
let path = path_builder.build(); | |
// Tessellation output buffers | |
let mut buffers = VertexBuffers::<FillVertex, u16>::new(); | |
assert!( | |
// Tessellate path into buffers | |
FillTessellator::new().tessellate_path( | |
&path, | |
&FillOptions::default(), | |
&mut geometry_builder::simple_builder(&mut buffers) | |
).is_ok() | |
); | |
// Print tessellation output | |
println!( | |
"Vertices:\n{:?}\n\nIndices:\n{:?}", | |
buffers.vertices, | |
buffers.indices | |
); | |
assert!( | |
!buffers.vertices.is_empty() && | |
!buffers.indices.is_empty() | |
); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment