Created
July 8, 2018 10:33
-
-
Save Jacajack/d87c0dc18affd8458eb4763e272a6426 to your computer and use it in GitHub Desktop.
Commonly Used OpenGL Stuff (CUOS)
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
#include "cuos.hpp" | |
#include <libpng16/png.h> | |
#include <GL/glew.h> | |
#include <dirent.h> | |
#ifdef __cplusplus | |
#include <cstdio> | |
#include <cstdlib> | |
#include <vector> | |
#include <fstream> | |
#include <string> | |
#include <glm/glm.hpp> | |
#else | |
#include <stdio.h> | |
#include <stdlib.h> | |
#endif | |
/* | |
Load whole text file into string | |
*/ | |
char *slurp_text( const char *filename ) | |
{ | |
FILE *f = fopen( filename, "rt" ); | |
if ( f == NULL ) | |
{ | |
fprintf( stderr, "cuos: cannot open `%s'\n", filename ); | |
return NULL; | |
} | |
//Get length | |
fseek( f, 0, SEEK_END ); | |
long len = ftell( f ); | |
rewind( f ); | |
//Allocate buffer and read data | |
char *buf; | |
#ifdef __cplusplus | |
buf = new char[len + 1]; | |
#else | |
buf = (char*) calloc( len + 1, sizeof( char ) ); | |
#endif | |
assert( fread( buf, 1, len, f ) != -1 ); | |
buf[len] = 0; | |
fclose( f ); | |
return buf; | |
} | |
/* | |
Load whole PNG file into buffer | |
*/ | |
unsigned char *slurp_png( const char *filename, int *width, int *height, GLenum *format ) | |
{ | |
FILE *f = fopen( filename, "rb" ); | |
if ( f == NULL ) | |
{ | |
fprintf( stderr, "cuos: cannot open `%s'\n", filename ); | |
return NULL; | |
} | |
//Init libpng | |
png_structp png = png_create_read_struct( PNG_LIBPNG_VER_STRING, NULL, NULL, NULL ); | |
if ( png == NULL ) | |
{ | |
fprintf( stderr, "cuos: cannot create libpng read struct\n" ); | |
fclose( f ); | |
return NULL; | |
} | |
//Read PNG info | |
png_infop info = png_create_info_struct( png ); | |
if ( info == NULL ) | |
{ | |
fprintf( stderr, "cuos: cannot create libpng info struct\n" ); | |
fclose( f ); | |
return NULL; | |
} | |
//Libpng error handler | |
if ( setjmp( png_jmpbuf( png ) ) ) | |
{ | |
fprintf( stderr, "cuos: libpng error\n" ); | |
png_destroy_read_struct( &png, &info, NULL ); | |
fclose( f ); | |
return NULL; | |
} | |
//Read PNG info | |
png_init_io( png, f ); | |
png_read_info( png, info ); | |
*width = png_get_image_width( png, info ); | |
*height = png_get_image_height( png, info ); | |
//tRNS to alpha | |
if ( png_get_valid( png, info, PNG_INFO_tRNS ) ) | |
png_set_tRNS_to_alpha( png ); | |
//Determine and convert format | |
switch ( png_get_color_type( png, info ) ) | |
{ | |
//Grayscale | |
case PNG_COLOR_TYPE_GRAY: | |
*format = GL_RGB; | |
png_set_gray_to_rgb( png ); | |
break; | |
//Grayscale + alpha | |
case PNG_COLOR_TYPE_GRAY_ALPHA: | |
*format = GL_RGBA; | |
png_set_gray_to_rgb( png ); | |
break; | |
//Palette | |
case PNG_COLOR_TYPE_PALETTE: | |
*format = GL_RGB; | |
png_set_expand( png ); | |
break; | |
//RGB | |
case PNG_COLOR_TYPE_RGB: | |
*format = GL_RGB; | |
break; | |
//RGBA | |
case PNG_COLOR_TYPE_RGBA: | |
*format = GL_RGBA; | |
break; | |
//Unhandled PNG | |
default: | |
fprintf( stderr, "cuos: unhandled PNG format\n" ); | |
png_destroy_info_struct( png, &info ); | |
png_destroy_read_struct( &png, &info , NULL ); | |
fclose( f ); | |
break; | |
} | |
png_set_interlace_handling( png ); | |
png_read_update_info( png, info ); | |
//Linear texture buffer | |
int bpp = *format == GL_RGB ? 3 : 4; | |
unsigned char *image_data; | |
#ifdef __cplusplus | |
image_data = new unsigned char[*width * *height * bpp]; | |
#else | |
image_data = calloc( *width * *height * bpp, sizeof( unsigned char ) ); | |
#endif | |
//And this tricks libpng to read data into my linear bufer | |
//Note: BMPs are upside down while PNGs are not | |
png_bytep *rows = (png_bytep*) calloc( *height, sizeof( png_bytep ) ); | |
uint8_t *p = image_data; | |
for ( int i = *height - 1; i >= 0; i-- ) | |
{ | |
rows[i] = p; | |
p += *width * bpp; | |
} | |
//Read data | |
png_read_image( png, rows ); | |
//Cleanup | |
free( rows ); | |
png_read_end( png, NULL ); | |
png_destroy_info_struct( png, &info ); | |
png_destroy_read_struct( &png, &info, NULL ); | |
fclose( f ); | |
fprintf( stderr, "cuos: reading %dx%d PNG image done\n", *width, *height ); | |
return image_data; | |
} | |
/* | |
Only available with C++, cause I'm lazy | |
Reads whole obj file into an array | |
Reads vertices positions, UVs and normals | |
Array layout: | |
- vx | |
- vy | |
- vz | |
- vnx | |
- vny | |
- vnz | |
- vu | |
- vv | |
*/ | |
#ifdef __cplusplus | |
GLfloat *slurp_obj( const char *filename, int *vertex_count ) | |
{ | |
//Open OBJ file | |
std::ifstream f( filename, std::ios::in ); | |
//The data | |
std::vector <glm::vec3> vertices, normals; | |
std::vector <glm::vec2> uvs; | |
//IDs | |
std::vector <int> vertex_ids, normal_ids, uv_ids; | |
if ( f.is_open( ) ) | |
{ | |
std::string line; | |
int linecnt = 0; | |
while ( std::getline( f, line ) ) | |
{ | |
linecnt++; | |
//Skip comments | |
if ( line[0] == '#' ) continue; | |
//Split | |
size_t sep = line.find( " " ); | |
if ( sep == std::string::npos ) continue; | |
std::string token_type = line.substr( 0, sep ); | |
std::string token_data = line.substr( sep + 1 ); | |
if ( token_type == "v" ) //Vertex data | |
{ | |
glm::vec3 v; | |
std::sscanf( token_data.c_str( ), "%f %f %f", &v.x, &v.y, &v.z ); | |
vertices.push_back( v ); | |
} | |
else if ( token_type == "vn" ) //Normal data | |
{ | |
glm::vec3 n; | |
std::sscanf( token_data.c_str( ), "%f %f %f", &n.x, &n.y, &n.z ); | |
normals.push_back( n ); | |
} | |
else if ( token_type == "vt" ) //UV data | |
{ | |
glm::vec2 uv; | |
std::sscanf( token_data.c_str( ), "%f %f", &uv.x, &uv.y ); | |
uvs.push_back( uv ); | |
} | |
else if ( token_type == "f" ) //Face data | |
{ | |
int ids[9]; | |
char *linebuf = strdup( token_data.c_str( ) ); | |
assert( linebuf != NULL ); | |
const char *token; | |
int token_major = 0, token_minor = 0; | |
while ( ( token = strsep( &linebuf, " " ) ) != NULL && token_major < 3 ) | |
{ | |
char *tokendup = strdup( token ); | |
assert( tokendup ); | |
char *subtoken; | |
token_minor = 0; | |
while ( ( subtoken = strsep( &tokendup, "/" ) ) != NULL && token_minor < 3 ) | |
{ | |
ids[token_major * 3 + token_minor] = std::atoi( subtoken ); | |
token_minor++; | |
} | |
free( tokendup ); | |
token_major++; | |
} | |
free( linebuf ); | |
//Push the stuff | |
vertex_ids.push_back( ids[0] ); | |
vertex_ids.push_back( ids[3] ); | |
vertex_ids.push_back( ids[6] ); | |
uv_ids.push_back( ids[1] ); | |
uv_ids.push_back( ids[4] ); | |
uv_ids.push_back( ids[7] ); | |
normal_ids.push_back( ids[2] ); | |
normal_ids.push_back( ids[5] ); | |
normal_ids.push_back( ids[8] ); | |
} | |
else | |
{ | |
fprintf( stderr, "cuos: unhandled token at line %d in %s\n", linecnt, filename ); | |
} | |
} | |
} | |
else | |
return NULL; | |
//Unpack the data | |
std::vector <glm::vec3> vertices_unpacked, normals_unpacked; | |
std::vector <glm::vec2> uvs_unpacked; | |
fprintf( stderr, "cuos: unpacking model %s: %ld vertices, %ld normals and %ld uvs\n", filename, vertices.size( ), normals.size( ), uvs.size( ) ); | |
int missing_vertices = 0, missing_normals = 0, missing_uvs = 0; | |
for ( int i = 0; i < vertex_ids.size( ); i++ ) | |
{ | |
int vertex_id = vertex_ids[i] - 1; | |
int normal_id = normal_ids[i] - 1; | |
int uv_id = uv_ids[i] - 1; | |
if ( vertex_id >= 0 && vertex_id < vertices.size( ) ) | |
vertices_unpacked.push_back( vertices[vertex_id] ); | |
else | |
{ | |
missing_vertices++; | |
vertices_unpacked.push_back( glm::vec3( 0.0 ) ); | |
} | |
if ( normal_id >= 0 && normal_id < normals.size( ) ) | |
normals_unpacked.push_back( normals[normal_id] ); | |
else | |
{ | |
missing_normals++; | |
normals_unpacked.push_back( glm::vec3( 0.0 ) ); | |
} | |
if ( uv_id >= 0 && uv_id < uvs.size( ) ) | |
uvs_unpacked.push_back( uvs[uv_id] ); | |
else | |
{ | |
missing_uvs++; | |
uvs_unpacked.push_back( glm::vec3( 0.0 ) ); | |
} | |
} | |
if ( missing_vertices + missing_uvs + missing_normals ) | |
fprintf( stderr, "cuos: there were %d vertices, %d normals and %d uvs missing\n", missing_vertices, missing_normals, missing_uvs ); | |
int pack_length = 3 + 3 + 2; | |
GLfloat *buffer = new GLfloat[pack_length * vertex_ids.size( )]; | |
for ( int i = 0; i < vertex_ids.size( ); i++ ) | |
{ | |
buffer[i * pack_length + 0] = vertices_unpacked[i].x; | |
buffer[i * pack_length + 1] = vertices_unpacked[i].y; | |
buffer[i * pack_length + 2] = vertices_unpacked[i].z; | |
buffer[i * pack_length + 3] = normals_unpacked[i].x; | |
buffer[i * pack_length + 4] = normals_unpacked[i].y; | |
buffer[i * pack_length + 5] = normals_unpacked[i].z; | |
buffer[i * pack_length + 6] = uvs_unpacked[i].x; | |
buffer[i * pack_length + 7] = uvs_unpacked[i].y; | |
} | |
*vertex_count = vertex_ids.size( ); | |
return buffer; | |
} | |
#endif | |
/* | |
Print out shader compilation log | |
*/ | |
int shader_log( GLuint id ) | |
{ | |
GLint result, length; | |
glGetShaderiv( id, GL_COMPILE_STATUS, &result ); | |
glGetShaderiv( id, GL_INFO_LOG_LENGTH, &length ); | |
if ( length > 0 ) | |
{ | |
char *buf = (char*) calloc( length + 1, sizeof( char ) ); | |
glGetShaderInfoLog( id, length, NULL, buf ); | |
fprintf( stderr, "%s\n", buf ); | |
free( buf ); | |
} | |
return result; | |
} | |
/* | |
Load and compile shader | |
*/ | |
int shader_load( GLenum type, GLuint *id, const char *filename ) | |
{ | |
//Load text | |
char *buf = slurp_text( filename ); | |
if ( buf == NULL ) return 0; | |
//Create shader and load source code | |
GLuint shader = glCreateShader( type ); | |
glShaderSource( shader, 1, &buf, NULL ); | |
//Free src buffer | |
#ifdef __cplusplus | |
delete buf; | |
#else | |
free( buf ); | |
#endif | |
//Compile shader | |
glCompileShader( shader ); | |
if ( shader_log( shader ) == GL_FALSE ) | |
{ | |
glDeleteShader( shader ); | |
return 0; | |
} | |
*id = shader; | |
return 1; | |
} | |
/* | |
Print out program linking log | |
*/ | |
int link_log( GLuint id ) | |
{ | |
GLint result, length; | |
glGetProgramiv( id, GL_LINK_STATUS, &result ); | |
glGetProgramiv( id, GL_INFO_LOG_LENGTH, &length ); | |
if ( length > 0 ) | |
{ | |
char *buf = (char*) calloc( length + 1, sizeof( char ) ); | |
glGetProgramInfoLog( id, length, NULL, buf ); | |
fprintf( stderr, "%s\n", buf ); | |
free( buf ); | |
} | |
return result; | |
} | |
/* | |
Load and link all shaders in the directory | |
*/ | |
int load_shader_dir( const char *directory, GLuint *program ) | |
{ | |
assert( program != NULL ); | |
DIR *d = opendir( directory ); | |
assert( d != NULL ); | |
struct dirent *ent; | |
//Shaders go here | |
GLuint shaders[16]; | |
int shader_count = 0; | |
//List files | |
while ( ( ent = readdir( d ) ) != NULL && shader_count < 16 ) | |
{ | |
//Duplicate file name | |
char *fname = strdup( ent->d_name ); | |
assert( fname != NULL ); | |
const char *name = NULL; | |
const char *type = NULL; | |
const char *ext = NULL; | |
char *strtok_buf; | |
//Tokenize | |
name = strtok_r( fname, ".", &strtok_buf ); | |
type = strtok_r( NULL, ".", &strtok_buf ); | |
ext = strtok_r( NULL, ".", &strtok_buf ); | |
//Name has to be valid | |
if ( name == NULL || type == NULL || ext == NULL || strcmp( ext, "glsl" ) ) | |
{ | |
free( fname ); | |
continue; | |
} | |
fprintf( stderr, "cuos: loading `%s' from shader directory `%s'\n", ent->d_name, directory ); | |
char fullpath[1024]; | |
snprintf( fullpath, 1024, "%s/%s", directory, ent->d_name ); | |
//Load shader of given type | |
if ( !strcmp( type, "vs" ) ) assert( shader_load( GL_VERTEX_SHADER, shaders + shader_count++, fullpath ) ); | |
if ( !strcmp( type, "gs" ) ) assert( shader_load( GL_GEOMETRY_SHADER, shaders + shader_count++, fullpath ) ); | |
if ( !strcmp( type, "tcs" ) ) assert( shader_load( GL_TESS_CONTROL_SHADER, shaders + shader_count++, fullpath ) ); | |
if ( !strcmp( type, "tes" ) ) assert( shader_load( GL_TESS_EVALUATION_SHADER, shaders + shader_count++, fullpath ) ); | |
if ( !strcmp( type, "fs" ) ) assert( shader_load( GL_FRAGMENT_SHADER, shaders + shader_count++, fullpath ) ); | |
if ( !strcmp( type, "cs" ) ) assert( shader_load( GL_COMPUTE_SHADER, shaders + shader_count++, fullpath ) ); | |
free( fname ); | |
} | |
closedir( d ); | |
//Link | |
GLuint prog = glCreateProgram( ); | |
for ( int i = 0; i < shader_count; i++ ) | |
glAttachShader( prog, shaders[i] ); | |
glLinkProgram( prog ); | |
assert( link_log( prog ) ); | |
*program = prog; | |
return 0; | |
} | |
/* | |
C++ only | |
Return shader uniform string->int map | |
*/ | |
#ifdef __cplusplus | |
extern UniformList get_uniforms( GLuint prog, std::initializer_list <std::string> names ) | |
{ | |
UniformList uniforms; | |
for ( std::string name : names ) | |
uniforms[name] = glGetUniformLocation( prog, name.c_str( ) ); | |
return uniforms; | |
} | |
#endif | |
/* | |
Prints out GL errors | |
*/ | |
void list_gl_errors( const char *file, int line ) | |
{ | |
GLenum err = glGetError( ); | |
if ( err != GL_NO_ERROR ) | |
{ | |
fprintf( stderr, "%s:%d: ", file, line ); | |
while ( err != GL_NO_ERROR ) | |
{ | |
switch ( err ) | |
{ | |
case GL_INVALID_ENUM: | |
fprintf( stderr, "GL_INVALID_ENUM, " ); | |
break; | |
case GL_INVALID_VALUE: | |
fprintf( stderr, "GL_INVALID_VALUE, " ); | |
break; | |
case GL_INVALID_OPERATION: | |
fprintf( stderr, "GL_INVALID_OPERATION, " ); | |
break; | |
case GL_INVALID_FRAMEBUFFER_OPERATION: | |
fprintf( stderr, "GL_INVALID_FRAMEBUFFER_OPERATION, " ); | |
break; | |
case GL_OUT_OF_MEMORY: | |
fprintf( stderr, "GL_OUT_OF_MEMORY, " ); | |
break; | |
} | |
err = glGetError( ); | |
} | |
fprintf( stderr, "\n" ); | |
} | |
} |
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
#ifndef CUOS_HPP | |
#define CUOS_HPP | |
#include <GL/glew.h> | |
#include <GLFW/glfw3.h> | |
#ifdef __cplusplus | |
#include <cstdio> | |
#include <initializer_list> | |
#include <map> | |
#else | |
#include <stdio.h> | |
#endif | |
extern char *slurp_text( const char *filename ); | |
extern unsigned char *slurp_png( const char *filename, int *width, int *height, GLenum *format ); | |
extern GLfloat *slurp_obj( const char *filename, int *vertex_count ); | |
extern int shader_log( GLuint id ); | |
extern int shader_load( GLenum type, GLuint *id, const char *filename ); | |
extern int link_log( GLuint id ); | |
extern int load_shader_dir( const char *directory, GLuint *program ); | |
extern void list_gl_errors( const char *file, int line ); | |
#ifdef __cplusplus | |
typedef std::map <std::string, GLint> UniformList; | |
extern UniformList get_uniforms( GLuint prog, std::initializer_list <std::string> names ); | |
#endif | |
#define CHECK_GL_ERR list_gl_errors( __FILE__, __LINE__ ); | |
#ifdef CUOS_PROFILING | |
static double CUOS_PROF_TIME; | |
static int CUOS_PROF_LINE; | |
#define CUOS_PROF_BEGIN {CUOS_PROF_TIME = glfwGetTime( ); CUOS_PROF_LINE = __LINE__;} | |
#define CUOS_PROF_MEAS {fprintf( stderr, "cuos: profiling `%s': lines %d - %d in %lfms\n", __FILE__, CUOS_PROF_LINE, __LINE__, 1000 * ( glfwGetTime( ) - CUOS_PROF_TIME ) ) ;} | |
#else | |
#define CUOS_PROF_BEGIN (void)0; | |
#define CUOS_PROF_MEAS (void)0; | |
#endif | |
#endif |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment