Skip to content

Instantly share code, notes, and snippets.

@Jacajack
Created July 8, 2018 10:33
Show Gist options
  • Save Jacajack/d87c0dc18affd8458eb4763e272a6426 to your computer and use it in GitHub Desktop.
Save Jacajack/d87c0dc18affd8458eb4763e272a6426 to your computer and use it in GitHub Desktop.
Commonly Used OpenGL Stuff (CUOS)
#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" );
}
}
#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