Skip to content

Instantly share code, notes, and snippets.

@Youka
Created March 18, 2019 00:39
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Youka/683df083554ab6a2f4e287fea10147a7 to your computer and use it in GitHub Desktop.
Save Youka/683df083554ab6a2f4e287fea10147a7 to your computer and use it in GitHub Desktop.
Rust OpenGL Backup - Given up because of slow download speed
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});
}
// 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(())
}
}
// 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))
}
}
#[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);
}
}
// 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
}
// 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();
}
#[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!");
}
}
// 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);
}
);
}
}
#[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