summaryrefslogtreecommitdiff
path: root/src/render
diff options
context:
space:
mode:
Diffstat (limited to 'src/render')
-rw-r--r--src/render/gl.cpp394
-rw-r--r--src/render/gl.h117
-rw-r--r--src/render/gl_2d.cpp314
-rw-r--r--src/render/gl_2d.h27
-rw-r--r--src/render/gl_2d_font.cpp366
-rw-r--r--src/render/gl_2d_font.h52
-rw-r--r--src/render/gl_3d.cpp204
-rw-r--r--src/render/gl_3d.h69
-rw-r--r--src/render/gl_batch.h193
9 files changed, 1736 insertions, 0 deletions
diff --git a/src/render/gl.cpp b/src/render/gl.cpp
new file mode 100644
index 0000000..915722e
--- /dev/null
+++ b/src/render/gl.cpp
@@ -0,0 +1,394 @@
+#include "SDL_video.h"
+#include <unistd.h>
+#define STB_IMAGE_IMPLEMENTATION
+#include "../util/stb_image.h"
+
+#include "gl.h"
+#include "gl_2d_font.h"
+#include "../util.h"
+
+I32 SAMPLER_INDICES[255];
+
+GL_DATA* gl_inst;
+
+GL_DATA* gl_instance() {
+ return gl_inst;
+}
+
+GL_DATA* gl_create( I32* _canvas ) {
+ if( gl_inst ) {
+ dlog( "gl_create() : fatal: gl instance already exists\n" );
+ abort();
+ }
+
+ if( !font_mutex.align )
+ thread_mutex_init( &font_mutex );
+
+ if( !!SDL_Init( SDL_INIT_VIDEO | SDL_VIDEO_OPENGL ) ) {
+ dlog( "gl_create() could not init SDL: %s\n", SDL_GetError() );
+ return 0;
+ }
+
+ SDL_GL_SetAttribute( SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES );
+ SDL_GL_SetAttribute( SDL_GL_CONTEXT_MAJOR_VERSION, 3 );
+ SDL_GL_SetAttribute( SDL_GL_CONTEXT_MINOR_VERSION, 2 );
+ SDL_GL_SetAttribute( SDL_GL_DOUBLEBUFFER, 1 );
+ SDL_GL_SetAttribute( SDL_GL_ACCELERATED_VISUAL, 1 );
+ SDL_GL_SetAttribute( SDL_GL_DEPTH_SIZE, 24 );
+ SDL_GL_SetAttribute( SDL_GL_RED_SIZE, 8 );
+ SDL_GL_SetAttribute( SDL_GL_GREEN_SIZE, 8 );
+ SDL_GL_SetAttribute( SDL_GL_BLUE_SIZE, 8 );
+ SDL_GL_SetAttribute( SDL_GL_ALPHA_SIZE, 8 );
+
+ GL_DATA* gl = new GL_DATA;
+ gl->window = SDL_CreateWindow(
+ "game",
+ 0,
+ 0,
+ _canvas[0],
+ _canvas[1],
+ SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN
+ );
+
+ if( !gl->window ) {
+ dlog( "gl_init() could not create window: %s\n", SDL_GetError() );
+ return 0;
+ }
+
+ U32 renderer_flags = SDL_RENDERER_ACCELERATED | SDL_RENDERER_TARGETTEXTURE;
+ gl->renderer = SDL_CreateRenderer( gl->window, -1, renderer_flags );
+ if( !gl->renderer ) {
+ dlog( "gl_init() could not create renderer: %s\n", SDL_GetError() );
+ return 0;
+ }
+
+ gl->ctx = SDL_GL_CreateContext( gl->window );
+ if( !gl->ctx ) {
+ dlog( "gl_init() could not create context: %s\n", SDL_GetError() );
+ return 0;
+ }
+
+ glGetIntegerv( GL_MAX_TEXTURE_IMAGE_UNITS, &gl->shader_texture_limit );
+ if( gl->shader_texture_limit > 255 )
+ gl->shader_texture_limit = 255;
+ for( U32 i = 0; i < 255; ++i )
+ SAMPLER_INDICES[i] = i;
+
+ gl->programs.clear();
+ memcpy( gl->canvas_size, _canvas, sizeof(I32) * 2 );
+ gl->clip_start = { 0, 0 };
+ gl->clip_dim = { (F32)gl->canvas_size[0], (F32)gl->canvas_size[1] };
+
+ gl_inst = gl;
+ return gl;
+}
+
+void gl_gen_buffers( GL_DATA* gl ) {
+ glGenBuffers( 1, &gl->vbuffer );
+
+ glBindBuffer( GL_ARRAY_BUFFER, gl->vbuffer );
+ glBufferData( GL_ARRAY_BUFFER, 8192, 0, GL_STATIC_DRAW );
+
+ glBindBuffer( GL_ARRAY_BUFFER, 0 );
+}
+
+void gl_destroy( GL_DATA *gl ) {
+ gl->programs.each( fn( GL_SHADER_PROGRAM** it ) { gl_program_destroy( gl, *it ); } );
+ gl->fonts.each( fn( GL_FONT** it ) { gl_font_destroy( gl, *it ); } );
+ gl->textures.each( fn( GL_TEX2D** it ) { gl_texture_destroy( gl, *it ); } );
+
+ if( gl->ctx )
+ SDL_GL_DeleteContext( gl->ctx );
+ if( gl->window )
+ SDL_DestroyWindow( gl->window );
+
+ free( gl );
+}
+
+void gl_update_window( GL_DATA* gl, I32* size ) {
+ if( !gl->window )
+ return;
+ SDL_SetWindowSize( gl->window, size[0], size[1] );
+ SDL_SetWindowPosition( gl->window, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED );
+
+ gl->canvas_size[0] = size[0];
+ gl->canvas_size[1] = size[1];
+
+ gl->programs.each( fn( GL_SHADER_PROGRAM** it ) {
+ F32 screen_ratio[] = {
+ 2.f / (F32)gl->canvas_size[0],
+ 2.f / (F32)gl->canvas_size[1],
+ 1.f,
+ 1.f
+ };
+
+ glUseProgram( (*it)->id );
+ I32 ratio_location = glGetUniformLocation( (*it)->id, "g_screenratio" );
+ glUniform4fv( ratio_location, 1, &screen_ratio[0] );
+ } );
+
+ glViewport( 0, 0, gl->canvas_size[0], gl->canvas_size[1] );
+ glUseProgram( 0 );
+}
+
+STAT gl_shader_compile( GL_DATA* gl, GL_SHADER* shader ) {
+ static char* log_buf = 0;
+ if( !log_buf )
+ log_buf = (char*)malloc( 8192 );
+
+ I32 res;
+ shader->id = glCreateShader( (GLenum)shader->type );
+ glShaderSource( shader->id, 1, &shader->code, 0 );
+ glCompileShader( shader->id );
+ glGetShaderiv( shader->id, GL_COMPILE_STATUS, &res );
+
+ if( !res ) {
+ glGetShaderInfoLog( shader->id, 8192, 0, log_buf );
+ dlog( "gl_shader_compile() : error compiling shader %s. log: \n%s\n%s", shader->name, log_buf, shader->code );
+
+ glDeleteShader( shader->id );
+ return STAT_ERR;
+ }
+
+ shader->compiled = 1;
+ return STAT_OK;
+}
+
+void gl_shader_destroy( GL_DATA* gl, GL_SHADER* shader ) {
+ if( shader->code )
+ free( (void*)shader->code );
+}
+
+GL_SHADER_PROGRAM* gl_program_create( GL_DATA* gl, const char* name, U32 size ) {
+ GL_SHADER_PROGRAM* program = (GL_SHADER_PROGRAM*)malloc( size );
+ char shader_string[256];
+ char* shader_code;
+
+ sprintf( shader_string, "../assets/shaders/%s.vsh", name );
+ shader_code = (char*)file_read( shader_string );
+ if( !shader_code ) {
+ dlog( "gl_program_create() : could not read shader file %s\n", shader_string );
+ return 0;
+ }
+
+ program->vsh.name = name;
+ program->vsh.type = GL_VERTEX_SHADER;
+ program->vsh.code = shader_code;
+
+ sprintf( shader_string, "../assets/shaders/%s.fsh", name );
+ shader_code = (char*)file_read( shader_string );
+ if( !shader_code ) {
+ dlog( "gl_program_create() : could not read shader file %s\n", shader_string );
+ return 0;
+ }
+
+ program->fsh.name = name;
+ program->fsh.type = GL_FRAGMENT_SHADER;
+ program->fsh.code = shader_code;
+
+ program->name = name;
+ program->gl = gl;
+
+ gl->programs.push( program );
+ return program;
+}
+
+STAT gl_program_compile( GL_DATA* gl, GL_SHADER_PROGRAM* program ) {
+ static char* log_buf = 0;
+ if( !log_buf )
+ log_buf = (char*)malloc( 8192 );
+
+ program->id = glCreateProgram();
+ if( !OK( gl_shader_compile( gl, &program->fsh ) )
+ || !OK( gl_shader_compile( gl, &program->vsh ) ) )
+ return STAT_ERR;
+
+ glAttachShader( program->id, program->fsh.id );
+ glAttachShader( program->id, program->vsh.id );
+
+ I32 status;
+ glLinkProgram( program->id );
+ glGetProgramiv( program->id, GL_LINK_STATUS, &status );
+ if( !status ) {
+ glGetProgramInfoLog( program->id, 8192, 0, log_buf );
+ dlog( "gl_program_compile() : error compiling program %s. log: \n%s\n",
+ program->name,
+ log_buf
+ );
+
+ return STAT_ERR;
+ }
+
+ glGenBuffers( 1, &program->vbuffer );
+ return STAT_OK;
+}
+
+void gl_program_destroy( GL_DATA* gl, GL_SHADER_PROGRAM* program ) {
+ if( program->fsh.compiled )
+ gl_shader_destroy( gl, &program->fsh );
+ if( program->vsh.compiled )
+ gl_shader_destroy( gl, &program->vsh );
+
+ I32 idx = gl->programs.idx_of( program );
+ if( idx != -1 )
+ gl->programs.erase( idx );
+}
+
+GL_TEX2D* gl_texture_from_file( GL_DATA* gl, const char* name ) {
+ char filename[256];
+ sprintf( filename, "../assets/%s", name );
+
+ I32 w, h, n;
+ U8* data = stbi_load( filename, &w, &h, &n, STBI_rgb_alpha );
+ if( !data ) {
+ dlog( "gl_texture_create() : could not load image %s\n", filename );
+ return 0;
+ }
+
+ GL_TEX2D* tex = gl_texture_create( gl, filename );
+ glBindTexture( GL_TEXTURE_2D, tex->id );
+ glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT );
+ glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT );
+ glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
+ glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
+
+ glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, data );
+ glGenerateMipmap( GL_TEXTURE_2D );
+ glBindTexture( GL_TEXTURE_2D, 0 );
+
+ tex->width = w;
+ tex->height = h;
+
+ stbi_image_free( data );
+
+ return tex;
+}
+
+GL_TEX2D* gl_texture_from_bitmap( GL_DATA* gl, const char* name, U8* bitmap, U32 width, U32 height ) {
+ GL_TEX2D* tex = gl_texture_create( gl, name );
+ glBindTexture( GL_TEXTURE_2D, tex->id );
+ glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT );
+ glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT );
+ glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
+ glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
+
+ tex->width = width;
+ tex->height = height;
+
+ glTexImage2D(
+ GL_TEXTURE_2D,
+ 0, GL_RGBA,
+ (I32)width, (I32)height, 0,
+ GL_RGBA, GL_UNSIGNED_BYTE, bitmap
+ );
+
+ glBindTexture( GL_TEXTURE_2D, 0 );
+
+ return tex;
+}
+
+
+GL_TEX2D* gl_texture_create( GL_DATA* gl, const char* name ) {
+ GL_TEX2D* tex = (GL_TEX2D*)malloc( sizeof(GL_TEX2D) );
+ strcpy( tex->name, name );
+ glGenTextures( 1, &tex->id );
+
+ gl->textures.push( tex );
+ return tex;
+}
+
+void gl_texture_destroy( GL_DATA* gl, GL_TEX2D* tex ) {
+ glDeleteTextures( 1, &tex->id );
+ I32 idx = gl->textures.idx_of( tex );
+ if( idx != -1 )
+ gl->textures.erase( idx );
+
+ free( tex );
+}
+
+STAT gl_beginframe( GL_DATA* gl ) {
+ SDL_SetRenderTarget( gl->renderer, 0 );
+ SDL_SetRenderDrawColor( gl->renderer, 0, 0, 0, 255 );
+ SDL_RenderClear( gl->renderer );
+
+ glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
+ gl->last_tick = u_tick();
+ return STAT_OK;
+}
+
+STAT gl_endframe( GL_DATA* gl ) {
+ SDL_GL_SwapWindow( gl->window );
+
+ // 1000 fps max be real
+ while( true ) {
+ U64 diff = u_tick() - gl->last_tick;
+ if( diff < 10 )
+ usleep( 10 );
+ else break;
+ }
+
+ gl->frametime = (F32)(u_tick() - gl->last_tick) / 10000.0f;
+ gl->fps = 1.0f / gl->frametime;
+
+ input_frame_end();
+
+ SDL_Event e;
+ while( SDL_PollEvent( &e ) ) {
+ input_on_event( &e );
+
+ switch( e.type ) {
+ case SDL_QUIT:
+ return STAT_BREAK;
+ case SDL_KEYDOWN:
+ return e.key.keysym.sym == SDLK_ESCAPE ? STAT_BREAK : STAT_OK;
+ default: break;
+ }
+ }
+
+ return STAT_OK;
+}
+
+void gl_set_clip( GL_DATA* gl, VEC2 start, VEC2 dim ) {
+ gl->clip_start = start;
+ gl->clip_dim = dim;
+
+ glEnable( GL_SCISSOR_TEST );
+ glScissor( start.x, gl->canvas_size[1] - (I32)(dim.y + start.y), dim.x, dim.y );
+}
+
+void gl_get_clip( GL_DATA* gl, VEC2* start, VEC2* dim ) {
+ *start = gl->clip_start;
+ *dim = gl->clip_dim;
+}
+
+void gl_reset_clip( GL_DATA* gl ) {
+ glDisable( GL_SCISSOR_TEST );
+ gl->clip_start = {};
+ gl->clip_dim = { (F32)gl->canvas_size[0], (F32)gl->canvas_size[1] };
+}
+
+void gl_set_viewport( GL_DATA* gl, VEC2 start, VEC2 dim ) {
+ I32 vpy = (I32)gl->canvas_size[1] - start.y - dim.y;
+ glViewport( (I32)start.x, vpy, (I32)dim.x, (I32)dim.y );
+
+ gl->programs.each( fn( GL_SHADER_PROGRAM** it ) {
+ F32 screen_ratio[] = {
+ 2.f / (F32)dim.x,
+ 2.f / (F32)dim.y,
+ 1.f,
+ 1.f
+ };
+
+ glUseProgram( (*it)->id );
+ I32 ratio_location = glGetUniformLocation( (*it)->id, "g_screenratio" );
+ glUniform4fv( ratio_location, 1, &screen_ratio[0] );
+ } );
+
+ gl->viewport_start = start;
+ gl->viewport_dim = dim;
+}
+
+void gl_get_viewport( GL_DATA* gl, VEC2* start, VEC2* dim ) {
+ *start = gl->viewport_start;
+ *dim = gl->viewport_dim;
+}
diff --git a/src/render/gl.h b/src/render/gl.h
new file mode 100644
index 0000000..cf2b5dd
--- /dev/null
+++ b/src/render/gl.h
@@ -0,0 +1,117 @@
+#pragma once
+
+#include <SDL.h>
+#include <GLES2/gl2.h>
+
+#include "../util.h"
+#include "../util/matrix.h"
+
+typedef struct GL_FONT *PGL_FONT;
+typedef struct GL_DATA *PGL_DATA;
+
+struct VERTEX {
+ VEC2 pos;
+ VEC2 uv;
+ CLR clr;
+ U8 sampler;
+};
+
+struct GL_SHADER_DEF {
+ const char* name;
+ const char* code;
+ I32 type;
+ U32 id;
+ U8 compiled;
+};
+
+struct GL_SHADER_PROGRAM {
+ const char* name;
+ GL_SHADER_DEF fsh;
+ GL_SHADER_DEF vsh;
+ U32 id;
+ U32 vbuffer;
+
+ GL_DATA* gl;
+};
+
+struct GL_TEX2D {
+ GLuint id;
+ char name[256];
+ U32 width;
+ U32 height;
+ U32 channels;
+ U8* data;
+};
+
+typedef struct GL_DATA {
+ SDL_Window* window;
+ SDL_GLContext ctx;
+ SDL_Renderer* renderer;
+
+ I32 canvas_size[2];
+
+ LIST<GL_SHADER_PROGRAM*> programs;
+ LIST<GL_TEX2D*> textures;
+ LIST<GL_FONT*> fonts;
+
+ I32 shader_texture_limit;
+ U64 last_tick;
+ F32 frametime;
+ F32 fps;
+
+ GLuint vbuffer;
+
+ VEC2 clip_start;
+ VEC2 clip_dim;
+
+ VEC2 viewport_start;
+ VEC2 viewport_dim;
+
+ MAT4* proj_matrix;
+} *PGL_DATA;
+
+GL_DATA* gl_instance();
+GL_DATA* gl_create( I32* _canvas );
+void gl_destroy( GL_DATA* gl );
+void gl_gen_buffers( GL_DATA* gl );
+STAT gl_beginframe( GL_DATA* gl );
+STAT gl_endframe( GL_DATA* gl );
+STAT gl_shader_compile( GL_DATA* gl, GL_SHADER_DEF* shader );
+void gl_shader_destroy( GL_DATA* gl, GL_SHADER_DEF* shader );
+GL_SHADER_PROGRAM* gl_program_create( GL_DATA* gl, const char* name, U32 size = sizeof(GL_SHADER_PROGRAM) );
+STAT gl_program_compile( GL_DATA* gl, GL_SHADER_PROGRAM* program );
+void gl_program_destroy( GL_DATA* gl, GL_SHADER_PROGRAM* program );
+GL_TEX2D* gl_texture_create( GL_DATA* gl, const char* name );
+GL_TEX2D* gl_texture_from_file( GL_DATA* gl, const char* name );
+GL_TEX2D* gl_texture_from_bitmap( GL_DATA* gl, const char* name, U8* bitmap, U32 width, U32 height );
+void gl_texture_destroy( GL_DATA* gl, GL_TEX2D* tex );
+void gl_update_window( GL_DATA* gl, I32* size );
+void gl_set_clip( GL_DATA* gl, VEC2 start, VEC2 dim );
+void gl_get_clip( GL_DATA* gl, VEC2* start, VEC2* dim );
+void gl_set_viewport( GL_DATA* gl, VEC2 start, VEC2 dim );
+void gl_get_viewport( GL_DATA* gl, VEC2* start, VEC2* dim );
+void gl_reset_clip( GL_DATA* gl );
+
+// special sampler id for no texture
+const U8 SAMPLER_ID_NONE = 255;
+extern I32 SAMPLER_INDICES[255];
+
+template <typename VERTEX>
+LIST<VERTEX> triangle_fan_to_list( VERTEX* vertices, U32 vert_count ) {
+ LIST<VERTEX> ret{};
+ if( vert_count < 3 ) {
+ return ret;
+ }
+
+ ret.reserve( (vert_count - 2) * 3 );
+ VERTEX* start = &vertices[0];
+ VERTEX* last = &vertices[1];
+ for( U32 i = 2; i < vert_count; ++i ) {
+ ret.push( *start );
+ ret.push( *last );
+ ret.push( vertices[i] );
+ last = &vertices[i];
+ }
+
+ return ret;
+}
diff --git a/src/render/gl_2d.cpp b/src/render/gl_2d.cpp
new file mode 100644
index 0000000..44e7e00
--- /dev/null
+++ b/src/render/gl_2d.cpp
@@ -0,0 +1,314 @@
+#include "gl_2d.h"
+#include "../util.h"
+
+GL_PROGRAM* gl_2d_init( GL_DATA* gl, VEC2 screensize, const char* shadername ) {
+ GL_PROGRAM* program = gl_program_create( gl, shadername );
+ if( !OK( gl_program_compile( gl, program ) ) )
+ dlog( "gl_2d_init() : error compiling shader %s\n", shadername );
+
+ F32 screen_ratio[] = {
+ 2.f / screensize.x,
+ 2.f / screensize.y,
+ 1.f,
+ 1.f
+ };
+
+ glUseProgram( program->id );
+ glEnable( GL_BLEND );
+ glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
+
+ I32 ratio_location = glGetUniformLocation( program->id, "g_screenratio" );
+ glUniform4fv( ratio_location, 1, &screen_ratio[0] );
+
+ I32 samplers_location = glGetUniformLocation( program->id, "g_samplers" );
+ glUniform1iv( samplers_location, 255, SAMPLER_INDICES );
+
+ return program;
+}
+
+void gl_2d_line( GL_PROGRAM* gl2d, VEC2 start, VEC2 end, CLR col ) {
+ static const U16 order[] = { 0, 1 };
+
+ glUseProgram( gl2d->id );
+
+ VERTEX vertices[] = {
+ { .pos = { start.x, start.y }, .clr = col },
+ { .pos = { end.x, end.y }, .clr = col },
+ };
+
+ glBindBuffer( GL_ARRAY_BUFFER, gl2d->gl->vbuffer );
+ glBufferData( GL_ARRAY_BUFFER, sizeof(vertices), &vertices[0], GL_DYNAMIC_DRAW );
+ I32 position = glGetAttribLocation( gl2d->id, "in_pos" );
+ glEnableVertexAttribArray( position );
+ glVertexAttribPointer( position, 2, GL_FLOAT, 0, sizeof(VERTEX), 0 );
+ I32 color = glGetAttribLocation( gl2d->id, "in_col" );
+ glEnableVertexAttribArray( color );
+ glVertexAttribPointer( color, 4, GL_FLOAT, 0, sizeof(VERTEX), &( (VERTEX*)nullptr)->clr );
+
+ glBindBuffer( GL_ARRAY_BUFFER, 0 );
+ glDrawElements( GL_LINES, 2, GL_UNSIGNED_SHORT, order );
+}
+
+void gl_2d_rect( GL_PROGRAM* gl2d, VEC2 origin, VEC2 dim, CLR col ) {
+ static const U16 order[] = { 0, 1, 2, 3, 4 };
+
+ glUseProgram( gl2d->id );
+
+ VERTEX vertices[] = {
+ { .pos = { origin.x , origin.y }, .clr = col },
+ { .pos = { origin.x + dim.x, origin.y }, .clr = col },
+ { .pos = { origin.x + dim.x, origin.y + dim.y }, .clr = col },
+ { .pos = { origin.x , origin.y + dim.y }, .clr = col },
+ { .pos = { origin.x , origin.y }, .clr = col },
+ };
+
+ glBindBuffer( GL_ARRAY_BUFFER, gl2d->gl->vbuffer );
+ glBufferData( GL_ARRAY_BUFFER, sizeof(vertices), &vertices[0], GL_DYNAMIC_DRAW );
+ I32 position = glGetAttribLocation( gl2d->id, "in_pos" );
+ glEnableVertexAttribArray( position );
+ glVertexAttribPointer( position, 2, GL_FLOAT, 0, sizeof(VERTEX), 0 );
+ I32 color = glGetAttribLocation( gl2d->id, "in_col" );
+ glEnableVertexAttribArray( color );
+ glVertexAttribPointer( color, 4, GL_FLOAT, 0, sizeof(VERTEX), &( (VERTEX*)nullptr)->clr );
+
+ glBindBuffer( GL_ARRAY_BUFFER, 0 );
+ glDrawElements( GL_LINE_STRIP, 5, GL_UNSIGNED_SHORT, order );
+}
+
+void gl_2d_textured_frect( GL_PROGRAM* gl2d, VEC2 origin, VEC2 dim, GL_TEX2D* texture, CLR col, VEC2* uv, F32 rotation ) {
+ static const U16 order[] = { 0, 1, 2, 3 };
+
+ glUseProgram( gl2d->id );
+ VERTEX vertices[] = {
+ { { origin.x, origin.y }, uv? uv[0] : VEC2{ 0.f, 0.f }, col, 0 },
+ { { origin.x + dim.x, origin.y }, uv? uv[1] : VEC2{ 1.f, 0.f }, col, 0 },
+ { { origin.x, origin.y + dim.y }, uv? uv[2] : VEC2{ 0.f, 1.f }, col, 0 },
+ { { origin.x + dim.x, origin.y + dim.y }, uv? uv[3] : VEC2{ 1.f, 1.f }, col, 0 }
+ };
+
+ rotation = remainderf( rotation, 360.f );
+ if( rotation != 0.f ) {
+ F32 rad2dg = rotation * (M_PI / 180.f);
+
+ // rotate texture coordinates
+ for( U32 i = 0; i < 4; ++i ) {
+ F32 x = vertices[i].uv.x - 0.5f;
+ F32 y = vertices[i].uv.y - 0.5f;
+ vertices[i].uv.x = x * cosf( rad2dg ) - y * sinf( rad2dg ) + 0.5f;
+ vertices[i].uv.y = x * sinf( rad2dg ) + y * cosf( rad2dg ) + 0.5f;
+ if( vertices[i].uv.x < 0.f ) vertices[i].uv.x = 0.f;
+ if( vertices[i].uv.x > 1.f ) vertices[i].uv.x = 1.f;
+ }
+ }
+
+ glBindBuffer( GL_ARRAY_BUFFER, gl2d->gl->vbuffer );
+ glBufferData( GL_ARRAY_BUFFER, sizeof(vertices), &vertices[0], GL_DYNAMIC_DRAW );
+ I32 position = glGetAttribLocation( gl2d->id, "in_pos" );
+ glEnableVertexAttribArray( position );
+ glVertexAttribPointer( position, 2, GL_FLOAT, 0, sizeof(VERTEX), 0 );
+ I32 color = glGetAttribLocation( gl2d->id, "in_col" );
+ glEnableVertexAttribArray( color );
+ glVertexAttribPointer( color, 4, GL_FLOAT, 0, sizeof(VERTEX), &( (VERTEX*)nullptr)->clr );
+ I32 texcoord = glGetAttribLocation( gl2d->id, "in_texcoord" );
+ glEnableVertexAttribArray( texcoord );
+ glVertexAttribPointer( texcoord, 2, GL_FLOAT, 0, sizeof(VERTEX), &( (VERTEX*)nullptr)->uv );
+ I32 sampler = glGetAttribLocation( gl2d->id, "in_sampler" );
+ glEnableVertexAttribArray( sampler );
+ glVertexAttribPointer( sampler, 1, GL_UNSIGNED_BYTE, 1, sizeof(VERTEX), &( (VERTEX*)nullptr)->sampler );
+
+ glBindBuffer( GL_ARRAY_BUFFER, 0 );
+
+ glActiveTexture( GL_TEXTURE0 );
+ glBindTexture( GL_TEXTURE_2D, texture->id );
+
+ glUniform1f( glGetUniformLocation( gl2d->id, "in_time" ), u_time() );
+
+ glDrawElements( GL_TRIANGLE_STRIP, 4, GL_UNSIGNED_SHORT, order );
+ glBindTexture( GL_TEXTURE_2D, 0 );
+}
+
+void gl_2d_frect( GL_PROGRAM* gl2d, VEC2 origin, VEC2 dim, CLR col ) {
+ static const U16 order[] = { 0, 1, 2, 3 };
+
+ glUseProgram( gl2d->id );
+ VERTEX vertices[] = {
+ { .pos = { origin.x, origin.y }, .uv = { 0.f, 0.f }, .clr = col },
+ { .pos = { origin.x + dim.x, origin.y }, .uv = { 1.f, 0.f }, .clr = col },
+ { .pos = { origin.x, origin.y + dim.y }, .uv = { 0.f, 1.f }, .clr = col },
+ { .pos = { origin.x + dim.x, origin.y + dim.y }, .uv = { 1.f, 1.f }, .clr = col }
+ };
+
+ glBindBuffer( GL_ARRAY_BUFFER, gl2d->gl->vbuffer );
+ glBufferData( GL_ARRAY_BUFFER, sizeof(vertices), &vertices[0], GL_DYNAMIC_DRAW );
+ I32 position = glGetAttribLocation( gl2d->id, "in_pos" );
+ glEnableVertexAttribArray( position );
+ glVertexAttribPointer( position, 2, GL_FLOAT, 0, sizeof(VERTEX), 0 );
+ I32 color = glGetAttribLocation( gl2d->id, "in_col" );
+ glEnableVertexAttribArray( color );
+ glVertexAttribPointer( color, 4, GL_FLOAT, 0, sizeof(VERTEX), &( (VERTEX*)nullptr)->clr );
+ I32 texcoord = glGetAttribLocation( gl2d->id, "in_texcoord" );
+ glEnableVertexAttribArray( texcoord );
+ glVertexAttribPointer( texcoord, 2, GL_FLOAT, 0, sizeof(VERTEX), &( (VERTEX*)nullptr)->uv );
+
+ glBindBuffer( GL_ARRAY_BUFFER, 0 );
+ glDrawElements( GL_TRIANGLE_STRIP, 4, GL_UNSIGNED_SHORT, order );
+}
+
+void gl_2d_circle( GL_PROGRAM* gl2d, VEC2 origin, F32 radius, CLR col, U32 res ) {
+ static U16* order = 0;
+ const F32 step = 360.f / (F32)res;
+
+ glUseProgram( gl2d->id );
+
+ if( !order ) {
+ order = (U16*)malloc( sizeof( U16 ) * (res + 1) );
+ for( U32 i = 0; i < res + 1; ++i )
+ order[i] = i;
+ }
+
+ VERTEX* vertices = (VERTEX*)malloc( sizeof( VERTEX ) * (res + 1) );
+ for( U32 i = 0; i < res + 1; ++i ) {
+ VEC2 offset = m_radial_offset( step * ( i == res? 0 : i ), radius );
+
+ vertices[i] = (VERTEX){
+ .pos = {
+ origin.x + offset.x,
+ origin.y + offset.y,
+ },
+ .uv = {},
+ .clr = col
+ };
+ }
+
+ glBindBuffer( GL_ARRAY_BUFFER, gl2d->gl->vbuffer );
+ glBufferData( GL_ARRAY_BUFFER, sizeof(VERTEX) * (res + 1), &vertices[0], GL_STATIC_DRAW );
+ I32 position = glGetAttribLocation( gl2d->id, "in_pos" );
+ glEnableVertexAttribArray( position );
+ glVertexAttribPointer( position, 2, GL_FLOAT, 0, sizeof(VERTEX), 0 );
+ I32 color = glGetAttribLocation( gl2d->id, "in_col" );
+ glEnableVertexAttribArray( color );
+ glVertexAttribPointer( color, 4, GL_FLOAT, 0, sizeof(VERTEX), &( (VERTEX*)nullptr)->clr );
+
+ glBindBuffer( GL_ARRAY_BUFFER, 0 );
+
+ glDrawElements( GL_LINE_STRIP, res + 1, GL_UNSIGNED_SHORT, order );
+ free( vertices );
+}
+
+void gl_2d_fcircle( GL_PROGRAM* gl2d, VEC2 origin, F32 radius, CLR col, U32 res ) {
+ static U16* order = 0;
+ const F32 step = 360.f / (F32)res;
+
+ glUseProgram( gl2d->id );
+
+ if( !order ) {
+ order = (U16*)malloc( sizeof( U16 ) * (res * 2) );
+ for( U32 i = 0; i < res * 2; ++i )
+ order[i] = i;
+ }
+
+ VERTEX* vertices = (VERTEX*)malloc( sizeof(VERTEX) * (res * 2) );
+ for( U32 i = 0; i < res * 2; i += 2 ) {
+ VEC2 offset = m_radial_offset( step * i, radius );
+ vertices[i].pos = (VEC2){
+ origin.x,
+ origin.y,
+ };
+
+ vertices[i + 1].pos = (VEC2){
+ origin.x + offset.x,
+ origin.y + offset.y,
+ };
+
+ vertices[i].uv = (VEC2){ 0.5f, 0.5f };
+ vertices[i + 1].uv = (VEC2){
+ 0.5f + ( offset.x * 0.5f ) / radius,
+ 0.5f + ( offset.y * 0.5f ) / radius
+ };
+
+ vertices[i].clr = col;
+ vertices[i + 1].clr = col;
+ };
+
+ glBindBuffer( GL_ARRAY_BUFFER, gl2d->gl->vbuffer );
+ glBufferData( GL_ARRAY_BUFFER, sizeof(VERTEX) * res * 2, &vertices[0], GL_STATIC_DRAW );
+ I32 position = glGetAttribLocation( gl2d->id, "in_pos" );
+ glEnableVertexAttribArray( position );
+ glVertexAttribPointer( position, 2, GL_FLOAT, 0, sizeof(VERTEX), 0 );
+ I32 color = glGetAttribLocation( gl2d->id, "in_col" );
+ glEnableVertexAttribArray( color );
+ glVertexAttribPointer( color, 4, GL_FLOAT, 0, sizeof(VERTEX), &( (VERTEX*)nullptr)->clr );
+ I32 texcoord = glGetAttribLocation( gl2d->id, "in_texcoord" );
+ glEnableVertexAttribArray( texcoord );
+ glVertexAttribPointer( texcoord, 2, GL_FLOAT, 0, sizeof(VERTEX), &( (VERTEX*)nullptr)->uv );
+
+ glBindBuffer( GL_ARRAY_BUFFER, 0 );
+
+ glDrawElements( GL_TRIANGLE_STRIP, res + 2, GL_UNSIGNED_SHORT, order );
+ free( vertices );
+}
+
+void gl_polygon( GL_PROGRAM* gl2d, VERTEX* vertices, U32 vertices_count ) {
+ glUseProgram( gl2d->id );
+
+ glBindBuffer( GL_ARRAY_BUFFER, gl2d->gl->vbuffer );
+ glBufferData( GL_ARRAY_BUFFER, sizeof(VERTEX) * vertices_count, vertices, GL_STATIC_DRAW );
+
+ I32 position = glGetAttribLocation( gl2d->id, "in_pos" );
+ glEnableVertexAttribArray( position );
+ glVertexAttribPointer( position, 2, GL_FLOAT, 0, sizeof(VERTEX), 0 );
+ I32 color = glGetAttribLocation( gl2d->id, "in_col" );
+ glEnableVertexAttribArray( color );
+ glVertexAttribPointer( color, 4, GL_FLOAT, 0, sizeof(VERTEX), &( (VERTEX*)nullptr)->clr );
+ I32 texcoord = glGetAttribLocation( gl2d->id, "in_texcoord" );
+ glEnableVertexAttribArray( texcoord );
+ glVertexAttribPointer( texcoord, 2, GL_FLOAT, 0, sizeof(VERTEX), &( (VERTEX*)nullptr)->uv );
+
+ glBindBuffer( GL_ARRAY_BUFFER, 0 );
+
+ U16* order = (U16*)alloca( sizeof(U16) * vertices_count );
+ for( U32 i = 0; i < vertices_count; i++ )
+ order[i] = i;
+
+ glDrawElements( GL_TRIANGLE_FAN, vertices_count, GL_UNSIGNED_SHORT, order );
+}
+
+void gl_textured_polygon(
+ GL_PROGRAM *gl2d,
+ VERTEX *vertices,
+ U32 vertices_count,
+ GL_TEX2D *tex
+) {
+ glUseProgram( gl2d->id );
+
+ for( U32 i = 0; i < vertices_count; ++i )
+ vertices[i].sampler = 0;
+
+ glBindBuffer( GL_ARRAY_BUFFER, gl2d->gl->vbuffer );
+ glBufferData( GL_ARRAY_BUFFER, sizeof(VERTEX) * vertices_count, vertices, GL_STATIC_DRAW );
+ I32 position = glGetAttribLocation( gl2d->id, "in_pos" );
+ glEnableVertexAttribArray( position );
+ glVertexAttribPointer( position, 2, GL_FLOAT, 0, sizeof(VERTEX), 0 );
+ I32 color = glGetAttribLocation( gl2d->id, "in_col" );
+ glEnableVertexAttribArray( color );
+ glVertexAttribPointer( color, 4, GL_FLOAT, 0, sizeof(VERTEX), &( (VERTEX*)nullptr)->clr );
+ I32 texcoord = glGetAttribLocation( gl2d->id, "in_texcoord" );
+ glEnableVertexAttribArray( texcoord );
+ glVertexAttribPointer( texcoord, 2, GL_FLOAT, 0, sizeof(VERTEX), &( (VERTEX*)nullptr)->uv );
+ I32 sampler = glGetAttribLocation( gl2d->id, "in_sampler" );
+ glEnableVertexAttribArray( sampler );
+ glVertexAttribPointer( sampler, 1, GL_UNSIGNED_BYTE, 1, sizeof(VERTEX), &( (VERTEX*)nullptr)->sampler );
+
+ glBindBuffer( GL_ARRAY_BUFFER, 0 );
+
+ glActiveTexture( GL_TEXTURE0 );
+ glBindTexture( GL_TEXTURE_2D, tex->id );
+
+ glUniform1f( glGetUniformLocation( gl2d->id, "in_time" ), u_time() );
+
+ U16* order = (U16*)alloca( sizeof(U16) * vertices_count );
+ for( U32 i = 0; i < vertices_count; i++ )
+ order[i] = i;
+
+ glDrawElements( GL_TRIANGLE_FAN, vertices_count, GL_UNSIGNED_SHORT, order );
+}
diff --git a/src/render/gl_2d.h b/src/render/gl_2d.h
new file mode 100644
index 0000000..9830516
--- /dev/null
+++ b/src/render/gl_2d.h
@@ -0,0 +1,27 @@
+#pragma once
+#include "gl.h"
+
+extern GL_SHADER_PROGRAM* gl_2d_init( GL_DATA* gl, VEC2 screensize, const char* shadername );
+
+void gl_polygon( GL_SHADER_PROGRAM* gl2d, VERTEX* vertices, U32 vertices_count );
+void gl_textured_polygon(
+ GL_SHADER_PROGRAM* gl2d,
+ VERTEX* vertices,
+ U32 vertices_count,
+ GL_TEX2D* tex
+);
+
+extern void gl_2d_line( GL_SHADER_PROGRAM* gl2d, VEC2 start, VEC2 end, CLR col );
+extern void gl_2d_rect( GL_SHADER_PROGRAM* gl2d, VEC2 origin, VEC2 dimensions, CLR col );
+extern void gl_2d_frect( GL_SHADER_PROGRAM* gl2d, VEC2 origin, VEC2 dimensions, CLR col );
+extern void gl_2d_circle( GL_SHADER_PROGRAM* gl2d, VEC2 origin, F32 radius, CLR col, U32 res = 48 );
+extern void gl_2d_fcircle( GL_SHADER_PROGRAM* gl2d, VEC2 origin, F32 radius, CLR col, U32 res = 48 );
+extern void gl_2d_textured_frect(
+ GL_SHADER_PROGRAM* gl2d,
+ VEC2 origin,
+ VEC2 dim,
+ GL_TEX2D* texture,
+ CLR col = { 1.f, 1.f, 1.f, 1.f },
+ VEC2* uv = 0,
+ F32 rotation = 0.F
+);
diff --git a/src/render/gl_2d_font.cpp b/src/render/gl_2d_font.cpp
new file mode 100644
index 0000000..1dc498b
--- /dev/null
+++ b/src/render/gl_2d_font.cpp
@@ -0,0 +1,366 @@
+#include "gl_2d_font.h"
+#include "gl.h"
+
+FT_Library libft = NULL;
+
+THREAD_MUTEX font_mutex;
+
+void freetype_init() {
+ U32 err;
+
+ if( !libft ) {
+ err = FT_Init_FreeType( &libft );
+
+ if( err )
+ dlog( "gl_font_create() : FT_Init_FreeType() failed\n" );
+ }
+}
+
+void gl_font_create_bitmap( GL_FONT* font ) {
+ U32 char_width = (font->face->size->metrics.max_advance >> 6) + 1;
+ U32 char_height = (font->face->size->metrics.height >> 6) + 1;
+ U32 max_width = char_width * 16;
+ U32 max_height = char_height * 16;
+
+ if( !max_width || !max_height ) {
+ dlog( "gl_font_create() : invalid font size\n" );
+ return;
+ }
+
+ font->bitmap = (U32*)malloc( sizeof(U32) * max_width * max_height );
+ memset( font->bitmap, 0, sizeof(U32) * max_width * max_height );
+ font->bitmap_width = max_width;
+ font->bitmap_height = max_height;
+ font->char_width = char_width;
+ font->char_height = char_height;
+
+ U32 cur_x = 0, cur_y = 1;
+ for( I32 c = 0; c < GL_FONT_MAX_GLYPHS; ++c ) {
+ U32 err = FT_Load_Char( font->face, c, FT_LOAD_RENDER );
+ if( !err ) {
+ FT_Bitmap* bitmap = &font->face->glyph->bitmap;
+ for( U32 y = 0; y < bitmap->rows; ++y ) {
+ for( U32 x = 0; x < bitmap->width; ++x ) {
+ U32 dest_x = x + cur_x;
+ U32 dest_y = y + cur_y;
+
+ U8 pixel = bitmap->buffer[x + bitmap->width * y];
+ U32 out_pixel = 0x00FFFFFF;
+ out_pixel |= ( pixel << 24 );
+ font->bitmap[dest_x + max_width * dest_y] = out_pixel;
+ }
+ }
+
+ font->glyphs[c].offset_x = (F32)cur_x;
+ font->glyphs[c].offset_y = (F32)cur_y;
+ font->glyphs[c].width = bitmap->width;
+ font->glyphs[c].height = bitmap->rows;
+ font->glyphs[c].advance_x = (F32)(font->face->glyph->metrics.horiAdvance >> 6);
+ font->glyphs[c].advance_y = (F32)(char_height - (font->face->glyph->metrics.horiBearingY >> 6));
+ font->glyphs[c].bearing = (F32)(font->face->glyph->metrics.horiBearingX >> 6);
+ }
+ else {
+ dlog( "gl_font_create() : FT_Load_Char() failed (%s: %c) (%x)\n", font->name, c, err );
+ }
+
+ if( cur_x + char_width >= max_width ) {
+ cur_x = 0;
+ cur_y += char_height;
+ } else {
+ cur_x += char_width;
+ }
+ }
+}
+
+void gl_font_create_atlas( GL_DATA* gl, GL_FONT* font ) {
+ if( !font->bitmap ) {
+ dlog( "gl_font_create_atlas() : no bitmap\n" );
+ return;
+ }
+
+ char texture_name[256];
+ sprintf( texture_name, "%s_%d_atlas", font->name, font->size );
+
+ font->atlas = gl_texture_from_bitmap( gl,
+ texture_name,
+ (U8*)font->bitmap,
+ font->bitmap_width,
+ font->bitmap_height
+ );
+
+ free( font->bitmap );
+}
+
+GL_FONT* gl_font_create( GL_DATA* gl, const char* path, I32 size ) {
+ freetype_init();
+
+ if( size <= 2 ) {
+ dlog( "gl_font_create() : size too small.\n" );
+ return 0;
+ }
+
+ char full_path[256];
+ GL_FONT* font = (GL_FONT*)malloc( sizeof( GL_FONT ) );
+ font->size = size;
+ font->name = path;
+
+ sprintf( full_path, "../assets/fonts/%s", path );
+ U32 err = FT_New_Face( libft, full_path, 0, &font->face );
+
+ if( err ) {
+ dlog( "gl_font_create() : FT_New_Face() failed (%x)\n", err );
+ free( font );
+ return 0;
+ }
+
+ err = FT_Set_Pixel_Sizes( font->face, 0, size );
+ if( err ) {
+ dlog( "gl_font_create() : FT_Set_Char_Size() failed\n" );
+ free( font );
+ return 0;
+ }
+
+ gl_font_create_bitmap( font );
+ FT_Done_Face( font->face );
+
+ gl_font_create_atlas( gl, font );
+ gl->fonts.push( font );
+ return font;
+}
+
+void gl_font_destroy( GL_DATA* gl, GL_FONT* font ) {
+ gl_texture_destroy( gl, font->atlas );
+ I32 idx = gl->fonts.idx_of( font );
+ if( idx != -1 )
+ gl->fonts.erase( idx );
+
+ free( font );
+}
+
+
+void gl_font_calc_vertices_uvs(
+ GL_FONT* font,
+ VEC2 origin,
+ const char* text,
+ F32 _scale,
+ VERTEX* vertices,
+ U16* indices,
+ VEC2* coords,
+ CLR clr
+) {
+ U32 len = (U32)strlen( text );
+
+ F32 cur_x = origin.x;
+ F32 cur_y = origin.y;
+
+ for( U32 i = 0; i < len; ++i ) {
+ VERTEX* v = &vertices[i * 6];
+ U16* idx = &indices[i * 6];
+
+ if( text[i] == '\n' ) {
+ v[0] = v[1] = v[2] = v[3] = v[4] = v[5] = { { cur_x, cur_y }, {} };
+ idx[0] = idx[1] = idx[2] = idx[3] = idx[4] = idx[5] = i * 6;
+
+ cur_x = origin.x;
+ cur_y += (F32)font->char_height * _scale;
+ continue;
+ }
+
+ U32 c = (U8)text[i];
+ FONT_GLYPH* glyph = &font->glyphs[c];
+ F32 final_y = cur_y + glyph->advance_y * _scale;
+ F32 final_x = cur_x + (F32)glyph->bearing * _scale;
+
+ v[0].pos = { final_x, final_y };
+ v[1].pos = { final_x + glyph->width * _scale, final_y };
+ v[2].pos = { final_x, final_y + glyph->height * _scale };
+ v[3].pos = { final_x + glyph->width * _scale, final_y + glyph->height * _scale };
+ v[4].pos = { final_x, final_y + glyph->height * _scale };
+ v[5].pos = { final_x + glyph->width * _scale, final_y };
+
+ idx[0] = i * 6;
+ idx[1] = i * 6 + 1;
+ idx[2] = i * 6 + 2;
+ idx[3] = i * 6 + 3;
+ idx[4] = i * 6 + 4;
+ idx[5] = i * 6 + 5;
+
+ v[0].uv = { glyph->offset_x / (F32)font->bitmap_width, glyph->offset_y / font->bitmap_height };
+ v[1].uv = { (glyph->offset_x + glyph->width) / (F32)font->bitmap_width, glyph->offset_y / font->bitmap_height };
+ v[2].uv = { glyph->offset_x / (F32)font->bitmap_width, (glyph->offset_y + glyph->height) / font->bitmap_height };
+ v[3].uv = { (glyph->offset_x + glyph->width) / (F32)font->bitmap_width, (glyph->offset_y + glyph->height) / font->bitmap_height };
+ v[4].uv = { glyph->offset_x / (F32)font->bitmap_width, (glyph->offset_y + glyph->height) / font->bitmap_height };
+ v[5].uv = { (glyph->offset_x + glyph->width) / (F32)font->bitmap_width, glyph->offset_y / font->bitmap_height };
+
+ v[0].clr = v[1].clr = v[2].clr = v[3].clr = v[4].clr = v[5].clr = clr;
+ v[0].sampler = v[1].sampler = v[2].sampler = v[3].sampler = v[4].sampler = v[5].sampler = 0;
+
+ cur_x += glyph->advance_x * _scale;
+ }
+}
+
+void gl_font_draw( GL_FONT* font, GL_SHADER_PROGRAM* shader, VEC2 origin, const char* text, CLR clr, F32 _scale ) {
+ U32 len = strlen( text );
+ VERTEX* vertices = (VERTEX*)malloc( sizeof(VERTEX) * 6 * len );
+ U16* indices = (U16*)malloc( sizeof(U16) * 6 * len );
+ VEC2* coords = (VEC2*)malloc( sizeof(VEC2) * 6 * len );
+
+ gl_font_calc_vertices_uvs( font, origin, text, _scale, vertices, indices, coords, clr );
+ glUseProgram( shader->id );
+
+ glBindBuffer( GL_ARRAY_BUFFER, shader->gl->vbuffer );
+ glBufferData( GL_ARRAY_BUFFER, sizeof(VERTEX) * 6 * len, vertices, GL_STATIC_DRAW );
+ I32 position = glGetAttribLocation( shader->id, "in_pos" );
+ glEnableVertexAttribArray( position );
+ glVertexAttribPointer( position, 2, GL_FLOAT, 0, sizeof(VERTEX), &( (VERTEX*)nullptr)->pos );
+
+ I32 color = glGetAttribLocation( shader->id, "in_clr" );
+ glEnableVertexAttribArray( color );
+ glVertexAttribPointer( color, 4, GL_FLOAT, 0, sizeof(VERTEX), &( (VERTEX*)nullptr)->clr );
+
+ I32 texcoord = glGetAttribLocation( shader->id, "in_texcoord" );
+ glEnableVertexAttribArray( texcoord );
+ glVertexAttribPointer( texcoord, 2, GL_FLOAT, 0, sizeof(VERTEX), &( (VERTEX*)nullptr)->uv );
+
+ I32 sampler = glGetAttribLocation( shader->id, "in_sampler" );
+ glEnableVertexAttribArray( sampler );
+ glVertexAttribPointer( sampler, 1, GL_UNSIGNED_BYTE, 1, sizeof(VERTEX), &( (VERTEX*)nullptr)->sampler );
+
+ glBindBuffer( GL_ARRAY_BUFFER, 0 );
+
+ glActiveTexture( GL_TEXTURE0 );
+
+ glBindTexture( GL_TEXTURE_2D, font->atlas->id );
+
+ glDrawElements( GL_TRIANGLES, len * 6, GL_UNSIGNED_SHORT, indices );
+ glBindTexture( GL_TEXTURE_2D, 0 );
+
+ free( vertices );
+ free( indices );
+ free( coords );
+}
+
+void gl_font_textured(
+ GL_FONT* font,
+ GL_SHADER_PROGRAM* shader,
+ VEC2 origin,
+ const char* text,
+ GL_TEX2D* tex,
+ CLR clr,
+ F32 _scale
+) {
+ U32 len = strlen( text );
+ struct FONT_CUSTOM_VERTEX {
+ VERTEX v;
+ VEC2 uv2;
+ };
+
+ FONT_CUSTOM_VERTEX* custom_vertices = (FONT_CUSTOM_VERTEX*)malloc( sizeof(FONT_CUSTOM_VERTEX) * 6 * len );
+ VERTEX* vertices = (VERTEX*)malloc( sizeof(VERTEX) * 6 * len );
+ U16* indices = (U16*)malloc( sizeof(U16) * 6 * len );
+ VEC2* coords = (VEC2*)malloc( sizeof(VEC2) * 6 * len );
+
+ gl_font_calc_vertices_uvs( font, origin, text, _scale, vertices, indices, coords, clr );
+ VEC2 dim = gl_font_dim( font, text, _scale );
+
+ for( U32 i = 0; i < len * 6; ++i ) {
+ custom_vertices[i].v = vertices[i];
+ custom_vertices[i].uv2 = {
+ (vertices[i].pos.x - origin.x) / dim.x,
+ (vertices[i].pos.y - origin.y - vertices[0].pos.y) / dim.y
+ };
+ }
+
+ glUseProgram( shader->id );
+
+ glBindBuffer( GL_ARRAY_BUFFER, shader->gl->vbuffer );
+ glBufferData( GL_ARRAY_BUFFER, sizeof(FONT_CUSTOM_VERTEX) * 6 * len, custom_vertices, GL_STATIC_DRAW );
+ I32 position = glGetAttribLocation( shader->id, "in_pos" );
+ glEnableVertexAttribArray( position );
+ glVertexAttribPointer( position, 2, GL_FLOAT, 0, sizeof(FONT_CUSTOM_VERTEX), 0 );
+
+ I32 texcoord = glGetAttribLocation( shader->id, "in_texcoord" );
+ glEnableVertexAttribArray( texcoord );
+ glVertexAttribPointer( texcoord, 2, GL_FLOAT, 0, sizeof(FONT_CUSTOM_VERTEX), (void*)(sizeof(VEC2)) );
+
+ I32 texcoord2 = glGetAttribLocation( shader->id, "in_texcoord2" );
+ glEnableVertexAttribArray( texcoord2 );
+ glVertexAttribPointer( texcoord2, 2, GL_FLOAT, 0, sizeof(FONT_CUSTOM_VERTEX), (void*)(sizeof(VERTEX)) );
+
+ glBindBuffer( GL_ARRAY_BUFFER, 0 );
+
+ I32 color = glGetUniformLocation( shader->id, "in_color" );
+ glUniform4fv( color, 1, (F32*)&clr );
+
+ glActiveTexture( GL_TEXTURE0 );
+ glUniform1iv( glGetUniformLocation( shader->id, "in_sampler" ), 0, 0 );
+ glBindTexture( GL_TEXTURE_2D, font->atlas->id );
+
+ glDrawElements( GL_TRIANGLES, len * 6, GL_UNSIGNED_SHORT, indices );
+ glBindTexture( GL_TEXTURE_2D, 0 );
+
+ free( vertices );
+ free( indices );
+ free( coords );
+ free( custom_vertices );
+}
+
+
+VEC2 gl_font_dim( GL_FONT* font, const char* text, F32 _scale ) {
+ U32 len = strlen( text );
+
+ F32 cur_x = 0.f;
+ F32 cur_y = font->char_height * _scale;
+ F32 max_x = 0.f;
+
+ for( U32 i = 0; i < len; ++i ) {
+ if( text[i] == '\n' ) {
+ cur_x = 0;
+ cur_y += font->char_height * _scale;
+ continue;
+ }
+
+ U32 c = (U8)text[i];
+ FONT_GLYPH* glyph = &font->glyphs[c];
+ cur_x += glyph->advance_x * _scale;
+ if( cur_x > max_x )
+ max_x = cur_x;
+ }
+
+ return { max_x, cur_y };
+}
+
+void gl_font_fit_into_box( GL_FONT* font, const char* source, char* out, F32 box_width, F32 box_height, F32 scale ) {
+ U32 len = strlen( source );
+ U32 last_space = 0;
+
+ F32 cur_x = 0.f;
+ for( U32 i = 0; i < len; ++i ) {
+ if( source[i] == ' ' )
+ last_space = i;
+
+ if( source[i] == '\n' ) {
+ cur_x = 0;
+ continue;
+ }
+
+ FONT_GLYPH* glyph = &font->glyphs[(U8)source[i]];
+ cur_x += glyph->advance_x * scale;
+
+ out[i] = source[i];
+ if( cur_x > box_width ) {
+ if( last_space == 0 ) {
+ out[i] = '\n';
+ cur_x = 0;
+ } else {
+ out[last_space] = '\n';
+ cur_x = 0;
+ i = last_space;
+ last_space = 0;
+ }
+ }
+
+ }
+
+ out[len] = '\0';
+}
diff --git a/src/render/gl_2d_font.h b/src/render/gl_2d_font.h
new file mode 100644
index 0000000..71154c0
--- /dev/null
+++ b/src/render/gl_2d_font.h
@@ -0,0 +1,52 @@
+#pragma once
+#include <ft2build.h>
+#include FT_FREETYPE_H
+
+#include "gl.h"
+#include "../util/thread.h"
+
+extern THREAD_MUTEX font_mutex;
+
+typedef struct {
+ F32 offset_x;
+ F32 offset_y;
+ U32 width;
+ U32 height;
+ F32 advance_x;
+ F32 advance_y;
+ U32 bearing;
+} FONT_GLYPH;
+
+const I32 GL_FONT_MAX_GLYPHS = 256;
+typedef struct GL_FONT {
+ FT_Face face;
+ I32 size;
+
+ U32* bitmap;
+ U32 bitmap_width;
+ U32 bitmap_height;
+ U32 char_width;
+ U32 char_height;
+
+ GL_TEX2D* atlas;
+ const char* name;
+
+ FONT_GLYPH glyphs[GL_FONT_MAX_GLYPHS];
+} *PGL_FONT;
+
+GL_FONT* gl_font_create( GL_DATA* gl, const char* path, I32 size );
+void gl_font_destroy( GL_DATA* gl, GL_FONT* font );
+
+void gl_font_draw( GL_FONT* font, GL_SHADER_PROGRAM* shader, VEC2 pos, const char* text, CLR color, F32 scale = 1.0f );
+void gl_font_textured(
+ GL_FONT* font,
+ GL_SHADER_PROGRAM* shader,
+ VEC2 pos,
+ const char* text,
+ GL_TEX2D* tex,
+ CLR color = (CLR){1.f, 1.f, 1.f, 1.f},
+ F32 scale = 1.0f
+);
+
+VEC2 gl_font_dim( GL_FONT* font, const char* text, F32 scale = 1.0f );
+void gl_font_fit_into_box( GL_FONT* font, const char* source, char* out, F32 box_width, F32 box_height, F32 scale );
diff --git a/src/render/gl_3d.cpp b/src/render/gl_3d.cpp
new file mode 100644
index 0000000..78e7f5e
--- /dev/null
+++ b/src/render/gl_3d.cpp
@@ -0,0 +1,204 @@
+#include "gl_3d.h"
+#include "../util/matrix.h"
+#include "gl.h"
+#include "gl_batch.h"
+
+GL_3D* gl_3d_init( GL_DATA* gl, VEC2 screensize, const char* shadername ) {
+ GL_3D *gl3d = (GL_3D*)gl_program_create( gl, shadername, sizeof(GL_3D) );
+ if( !OK( gl_program_compile( gl, gl3d ) ) )
+ dlog( "gl_2d_init() : error compiling shader %s\n", shadername );
+
+ gl3d->winsize = screensize;
+ gl3d->aspect = screensize.x / screensize.y;
+
+ glUseProgram( gl3d->id );
+ I32 samplers_location = glGetUniformLocation( gl3d->id, "g_samplers" );
+ glUniform1iv( samplers_location, gl->shader_texture_limit, SAMPLER_INDICES );
+ return gl3d;
+}
+
+void gl_3d_projection_setup( GL_PROGRAM *_gl3d, VEC3 pos, F32 fov_deg, F32 yaw, F32 pitch, F32 near, F32 far, VEC2 winsize ) {
+ GL_3D* gl3d = (GL_3D*)_gl3d;
+ MAT4 proj, yaw_rot, pitch_rot, view, transl, tmp;
+
+ F32 fov_rad = m_deg2rad( fov_deg );
+ F32 yaw_rad = m_deg2rad( remainderf( yaw + 90.f, 360.f ) );
+ F32 pitch_rad = m_deg2rad( remainderf( pitch , 360.f ) );
+ gl3d->aspect = winsize.x / winsize.y;
+ gl3d->winsize = winsize;
+
+ mat4_perspective( &proj, fov_rad, gl3d->aspect, near, far );
+ mat4_rotation_y( &yaw_rot, -yaw_rad );
+ mat4_rotation_x( &pitch_rot, -pitch_rad );
+ mat4_translation( &transl, -pos.x, -pos.z, -pos.y );
+
+ mat4_mul( &yaw_rot, &transl, &tmp );
+ mat4_mul( &pitch_rot, &tmp, &view );
+ mat4_mul( &proj, &view, &gl3d->projmat );
+ gl3d->gl->proj_matrix = &gl3d->projmat;
+
+ glUseProgram( gl3d->id );
+ I32 location = glGetUniformLocation( gl3d->id, "in_proj_matrix" );
+ glUniformMatrix4fv( location, 1, 1, (GLfloat*)&gl3d->projmat );
+
+ glUniform1f( glGetUniformLocation( gl3d->id, "in_time" ), u_time() );
+}
+
+void gl_3d_batch_setup( GL_BATCH3D *batch ) {
+ glUseProgram( batch->shader->id );
+ glBindBuffer( GL_ARRAY_BUFFER, batch->vbuffer );
+ I32 position = glGetAttribLocation( batch->shader->id, "in_pos" );
+ glEnableVertexAttribArray( position );
+ glVertexAttribPointer( position, 3, GL_FLOAT, 0, sizeof(VERTEX3D), 0 );
+ I32 color = glGetAttribLocation( batch->shader->id, "in_clr" );
+ glEnableVertexAttribArray( color );
+ glVertexAttribPointer( color, 3, GL_FLOAT, 0, sizeof(VERTEX3D), &( (VERTEX3D*)nullptr )->clr );
+ I32 texcoord = glGetAttribLocation( batch->shader->id, "in_texcoord" );
+ glEnableVertexAttribArray( texcoord );
+ glVertexAttribPointer( texcoord, 2, GL_FLOAT, 0, sizeof(VERTEX3D), &( (VERTEX3D*)nullptr )->uv );
+ I32 sampler = glGetAttribLocation( batch->shader->id, "in_sampler" );
+ glEnableVertexAttribArray( sampler );
+ glVertexAttribPointer( sampler, 1, GL_UNSIGNED_BYTE, 1, sizeof(VERTEX3D), &( (VERTEX3D*)nullptr )->sampler );
+ glBindBuffer( GL_ARRAY_BUFFER, 0 );
+}
+
+void gl_3d_polygon(
+ GL_PROGRAM* gl3d,
+ VERTEX3D* vertices,
+ U32 vertices_count,
+ GL_TEX2D* tex
+) {
+ glUseProgram( gl3d->id );
+
+ if( !!tex ) {
+ glActiveTexture( GL_TEXTURE0 );
+ glBindTexture( GL_TEXTURE_2D, tex->id );
+ } else {
+ for( U32 i = 0; i < vertices_count; ++i )
+ vertices[i].sampler = SAMPLER_ID_NONE;
+ }
+
+ glBindBuffer( GL_ARRAY_BUFFER, gl3d->gl->vbuffer );
+ glBufferData( GL_ARRAY_BUFFER, sizeof(VERTEX3D) * vertices_count, vertices, GL_STATIC_DRAW );
+ I32 position = glGetAttribLocation( gl3d->id, "in_pos" );
+ glEnableVertexAttribArray( position );
+ glVertexAttribPointer( position, 3, GL_FLOAT, 0, sizeof(VERTEX3D), 0 );
+ I32 color = glGetAttribLocation( gl3d->id, "in_clr" );
+ glEnableVertexAttribArray( color );
+ glVertexAttribPointer( color, 3, GL_FLOAT, 0, sizeof(VERTEX3D), &( (VERTEX3D*)nullptr )->clr );
+ I32 texcoord = glGetAttribLocation( gl3d->id, "in_texcoord" );
+ glEnableVertexAttribArray( texcoord );
+ glVertexAttribPointer( texcoord, 2, GL_FLOAT, 0, sizeof(VERTEX3D), &( (VERTEX3D*)nullptr )->uv );
+ I32 sampler = glGetAttribLocation( gl3d->id, "in_sampler" );
+ glEnableVertexAttribArray( sampler );
+ glVertexAttribPointer( sampler, 1, GL_UNSIGNED_BYTE, 1, sizeof(VERTEX3D), &( (VERTEX3D*)nullptr )->sampler );
+
+ U16* order = (U16*)alloca( sizeof(U16) * vertices_count );
+ for( U32 i = 0; i < vertices_count; i++ )
+ order[i] = i;
+
+ glBindBuffer( GL_ARRAY_BUFFER, 0 );
+ glDrawElements( GL_TRIANGLE_FAN, vertices_count, GL_UNSIGNED_SHORT, order );
+}
+
+void gl_3d_polygon_fan(
+ GL_BATCH3D* batch,
+ VERTEX3D* vertices,
+ U32 vertices_count,
+ GL_TEX2D* tex
+) {
+ if( vertices_count < 3 )
+ return;
+
+ LIST<VERTEX3D> list = triangle_fan_to_list( vertices, vertices_count );
+ gl_batch_insert(
+ batch,
+ list.data,
+ list.size,
+ tex
+ );
+}
+
+void gl_3d_polygon(
+ GL_BATCH3D* batch,
+ VERTEX3D* vertices,
+ U32 vertices_count,
+ GL_TEX2D* tex
+) {
+ gl_batch_insert(
+ batch,
+ vertices,
+ vertices_count,
+ tex
+ );
+}
+
+void build_plane( VERTEX3D* v, VEC3 pos, VEC2 size, VEC2 rot, CLR col ) {
+ F32 half_width = size.x * 0.5f;
+ F32 half_height = size.y * 0.5f;
+
+ VEC3 vertices[4] = {
+ { -half_width, -half_height, 0 },
+ { half_width, -half_height, 0 },
+ { half_width, half_height, 0 },
+ { -half_width, half_height, 0 }
+ };
+
+ F32 sin_pitch = sinf( m_deg2rad( rot.x ) );
+ F32 cos_pitch = cosf( m_deg2rad( rot.x ) );
+ F32 sin_yaw = sinf( m_deg2rad( rot.y ) );
+ F32 cos_yaw = cosf( m_deg2rad( rot.y ) );
+
+ for( I32 i = 0; i < 4; i++) {
+ F32 x = vertices[i].x;
+ F32 y = vertices[i].y;
+ F32 z = vertices[i].z;
+ vertices[i].y = y * cos_pitch - z * sin_pitch;
+ vertices[i].z = y * sin_pitch + z * cos_pitch;
+ y = vertices[i].y;
+ vertices[i].x = x * cos_yaw - y * sin_yaw;
+ vertices[i].y = x * sin_yaw + y * cos_yaw;
+
+ vertices[i].x += pos.x;
+ vertices[i].y += pos.y;
+ vertices[i].z += pos.z;
+ }
+
+ VEC2 uvs[4] = {
+ { 0.0f, 1.0f },
+ { 1.0f, 1.0f },
+ { 1.0f, 0.0f },
+ { 0.0f, 0.0f }
+ };
+
+ v[0] = { .pos = { vertices[0].x, vertices[0].z, vertices[0].y }, .uv = uvs[0], .clr = col, .sampler = 0 };
+ v[1] = { .pos = { vertices[1].x, vertices[1].z, vertices[1].y }, .uv = uvs[1], .clr = col, .sampler = 0 };
+ v[2] = { .pos = { vertices[2].x, vertices[2].z, vertices[2].y }, .uv = uvs[2], .clr = col, .sampler = 0 };
+ v[3] = { .pos = { vertices[3].x, vertices[3].z, vertices[3].y }, .uv = uvs[3], .clr = col, .sampler = 0 };
+}
+
+void gl_3d_plane(
+ GL_SHADER_PROGRAM* gl3d,
+ VEC3 pos,
+ VEC2 size,
+ VEC2 rot,
+ GL_TEX2D* tex,
+ CLR col
+) {
+ VERTEX3D v[4];
+ build_plane( v, pos, size, rot, col );
+ gl_3d_polygon( gl3d, v, 4, tex );
+}
+
+void gl_3d_plane(
+ GL_BATCH3D* batch,
+ VEC3 pos,
+ VEC2 size,
+ VEC2 rot,
+ GL_TEX2D* tex,
+ CLR col
+) {
+ VERTEX3D v[4];
+ build_plane( v, pos, size, rot, col );
+ gl_3d_polygon_fan( batch, v, 4, tex );
+}
diff --git a/src/render/gl_3d.h b/src/render/gl_3d.h
new file mode 100644
index 0000000..531c3a3
--- /dev/null
+++ b/src/render/gl_3d.h
@@ -0,0 +1,69 @@
+#pragma once
+#include "gl_batch.h"
+#include "../util/matrix.h"
+
+// magic number sue me. yes this float is representable exactly.
+const F32 SCREEN_INVALID_POS = -2137000.f;
+
+struct VERTEX3D {
+ VEC3 pos;
+ VEC2 uv;
+ CLR clr;
+ U8 sampler;
+};
+
+using GL_BATCH3D = GL_BATCH<VERTEX3D>;
+struct GL_3D : GL_SHADER_PROGRAM {
+ F32 aspect;
+ MAT4 projmat;
+
+ VEC2 winsize;
+};
+
+// shader init -----------------------------------------------------------
+extern GL_3D* gl_3d_init( GL_DATA* gl, VEC2 screensize, const char* shadername );
+// sets up projection matrix and sends it to the gpu ------------------------------------------------------------
+extern void gl_3d_projection_setup( GL_SHADER_PROGRAM* gl3d, VEC3 pos, F32 fov_deg, F32 yaw, F32 pitch, F32 near, F32 far, VEC2 winsize );
+extern void gl_3d_batch_setup( GL_BATCH3D* batch );
+
+// takes a triangle fan not set of triangles
+extern void gl_3d_polygon(
+ GL_SHADER_PROGRAM* gl3d,
+ VERTEX3D* vertices,
+ U32 vertices_count,
+ GL_TEX2D* tex
+);
+
+// calculates triangle set from triangle fan before sending to batch
+extern void gl_3d_polygon_fan(
+ GL_BATCH3D* batch,
+ VERTEX3D* vertices,
+ U32 vertices_count,
+ GL_TEX2D* tex
+);
+
+// draws a set of triangles
+extern void gl_3d_polygon(
+ GL_BATCH3D* batch,
+ VERTEX3D* vertices,
+ U32 vertices_count,
+ GL_TEX2D* tex
+);
+
+extern void gl_3d_plane(
+ GL_SHADER_PROGRAM* gl3d,
+ VEC3 pos,
+ VEC2 size,
+ VEC2 rot,
+ GL_TEX2D* tex,
+ CLR col = CLR::WHITE()
+);
+
+extern void gl_3d_plane(
+ GL_BATCH3D* batch,
+ VEC3 pos,
+ VEC2 size,
+ VEC2 rot,
+ GL_TEX2D* tex,
+ CLR col = CLR::WHITE()
+);
diff --git a/src/render/gl_batch.h b/src/render/gl_batch.h
new file mode 100644
index 0000000..944521f
--- /dev/null
+++ b/src/render/gl_batch.h
@@ -0,0 +1,193 @@
+#pragma once
+#include "gl.h"
+
+// this is maybe a bit too much c++ voodoo for my liking
+
+const U32 BATCH_VERTICES_MAX = 16384;
+
+template <typename VERTEX>
+struct GL_BATCH;
+
+// func for setting sampler idx inside of vertex data
+template <typename VERTEX>
+using GL_BATCH_VERTEX_SAMPLER_IDX_FN = void (*)(VERTEX* vertex, U8 idx);
+
+template <typename VERTEX>
+using GL_BATCH_SETUP_FN = void (*)(GL_BATCH<VERTEX>* batch);
+
+template <typename VERTEX>
+struct GL_BATCH_CALL {
+ LIST<VERTEX> vertices{};
+ VEC2 clip_start;
+ VEC2 clip_dim;
+
+ VEC2 viewport_start;
+ VEC2 viewport_dim;
+
+ U16 primitive;
+ LIST<GL_TEX2D*> textures{};
+};
+
+template <typename VERTEX>
+struct GL_BATCH {
+ GL_DATA* gl;
+ GL_SHADER_PROGRAM* shader;
+
+ VEC2 clip_start;
+ VEC2 clip_dim;
+
+ VEC2 viewport_start;
+ VEC2 viewport_dim;
+
+ GLuint vbuffer;
+
+ LIST<GL_BATCH_CALL<VERTEX>> calls{};
+
+ // func for setting up vertex attribs
+ GL_BATCH_SETUP_FN<VERTEX> setup_cb;
+};
+
+/*
+ * takes in a callback func that gets called before the batch is drawn
+ *
+ * should set up vertex attribs correctly according to vertex fmt
+ */
+template <typename VERTEX>
+inline GL_BATCH<VERTEX>* gl_batch_create(
+ GL_DATA* gl,
+ GL_SHADER_PROGRAM* shader,
+ GL_BATCH_SETUP_FN<VERTEX> setup_cb
+) {
+ GL_BATCH<VERTEX>* batch = new GL_BATCH<VERTEX>;
+ batch->gl = gl;
+ batch->shader = shader;
+
+ batch->clip_start = gl->clip_start;
+ batch->clip_dim = gl->clip_dim;
+
+ batch->viewport_start = gl->viewport_start;
+ batch->viewport_dim = gl->viewport_dim;
+
+ batch->setup_cb = setup_cb;
+
+ glGenBuffers( 1, &batch->vbuffer );
+ glBindBuffer( GL_ARRAY_BUFFER, batch->vbuffer );
+ // make sure we have enough vram
+ glBufferData( GL_ARRAY_BUFFER, sizeof( VERTEX ) * BATCH_VERTICES_MAX, 0, GL_DYNAMIC_DRAW );
+ return batch;
+}
+
+template <typename VERTEX>
+inline void gl_batch_destroy( GL_BATCH<VERTEX>* batch ) {
+ glDeleteBuffers( &batch->vbuffer );
+ delete batch;
+}
+
+template <typename VERTEX>
+inline void gl_batch_empty( GL_BATCH<VERTEX>* batch ) {
+ batch->calls.clear();
+}
+
+template <typename VERTEX>
+inline void gl_batch_draw( GL_BATCH<VERTEX>* batch ) {
+ glUseProgram( batch->shader->id );
+
+ VEC2 vp_start, vp_dim, clip_start, clip_dim;
+ gl_get_viewport( batch->gl, &vp_start, &vp_dim );
+ gl_get_clip( batch->gl, &clip_start, &clip_dim );
+
+ batch->setup_cb( batch );
+ batch->calls.each( fn( GL_BATCH_CALL<VERTEX>* call ) {
+ for( U32 i = 0; i < call->textures.size; ++i ) {
+ glActiveTexture( GL_TEXTURE0 + i );
+ glBindTexture( GL_TEXTURE_2D, call->textures.data[i] ? call->textures.data[i]->id : 0 );
+ }
+
+ glBindBuffer( GL_ARRAY_BUFFER, batch->vbuffer );
+ glBufferData(
+ GL_ARRAY_BUFFER,
+ sizeof(VERTEX) * call->vertices.size,
+ call->vertices.data,
+ GL_DYNAMIC_DRAW
+ );
+
+ glBindBuffer( GL_ARRAY_BUFFER, 0 );
+ glDrawArrays( call->primitive, 0, call->vertices.size );
+ } );
+
+ gl_set_viewport( batch->gl, vp_start, vp_dim );
+ gl_set_clip( batch->gl, clip_start, clip_dim );
+}
+
+template <typename VERTEX>
+inline GL_BATCH_CALL<VERTEX>* gl_batch_get_call( GL_BATCH<VERTEX>* batch, GL_TEX2D* tex, U16 primitive, I32* texid ) {
+ U32 n = batch->calls.size;
+ VEC2 vp_start = batch->viewport_start;
+ VEC2 vp_dim = batch->viewport_dim;
+ VEC2 clip_start = batch->clip_start;
+ VEC2 clip_dim = batch->clip_dim;
+
+ if( n ) {
+ GL_BATCH_CALL<VERTEX>* last = &batch->calls.data[n - 1];
+ if( last->primitive == primitive
+ && vp_start == last->clip_start && vp_dim == last->viewport_dim
+ && clip_start == last->clip_start && clip_dim == last->clip_dim
+ ) {
+ I32 idx = last->textures.idx_of( tex );
+ if( idx != -1 ) {
+ *texid = idx;
+ return last;
+ }
+ else if( last->textures.size < batch->gl->shader_texture_limit ) {
+ *texid = last->textures.size;
+ last->textures.push( tex );
+ return last;
+ }
+ }
+ }
+
+
+ GL_BATCH_CALL<VERTEX> call{
+ .vertices = {},
+ .clip_start = batch->clip_start,
+ .clip_dim = batch->clip_dim,
+ .viewport_start = batch->viewport_start,
+ .viewport_dim = batch->viewport_dim,
+ .primitive = primitive,
+ .textures = {},
+ };
+
+ *texid = 0;
+ call.textures.push( tex );
+ return batch->calls.push( call );
+}
+
+template <typename VERTEX>
+inline void gl_batch_insert(
+ GL_BATCH<VERTEX>* batch,
+ VERTEX* vertices,
+ U32 count,
+ GL_TEX2D* tex,
+ U16 primitive = GL_TRIANGLES
+) {
+ if( !count )
+ return;
+
+ GL_BATCH_CALL<VERTEX>* call;
+ I32 texid;
+ VEC2 vp_start, vp_dim;
+ VEC2 clip_start, clip_dim;
+
+ gl_get_viewport( batch->gl, &vp_start, &vp_dim );
+ gl_get_clip( batch->gl, &clip_start, &clip_dim );
+
+ batch->viewport_start = vp_start; batch->viewport_dim = vp_dim;
+ batch->clip_start = clip_start; batch->clip_dim = clip_dim;
+
+ call = gl_batch_get_call( batch, tex, primitive, &texid );
+
+ for( U32 i = 0; i < count; ++i ) {
+ vertices[i].sampler = (!!tex)? texid : SAMPLER_ID_NONE;
+ call->vertices.push( vertices[i] );
+ }
+}