From f8b92ce3aa08b1445c9f956d8166830946562d12 Mon Sep 17 00:00:00 2001 From: navewindre Date: Wed, 3 Sep 2025 20:10:09 +0200 Subject: a --- src/render/gl.cpp | 394 ++++++++++++++++++++++++++++++++++++++++++++++ src/render/gl.h | 117 ++++++++++++++ src/render/gl_2d.cpp | 314 ++++++++++++++++++++++++++++++++++++ src/render/gl_2d.h | 27 ++++ src/render/gl_2d_font.cpp | 366 ++++++++++++++++++++++++++++++++++++++++++ src/render/gl_2d_font.h | 52 ++++++ src/render/gl_3d.cpp | 204 ++++++++++++++++++++++++ src/render/gl_3d.h | 69 ++++++++ src/render/gl_batch.h | 193 +++++++++++++++++++++++ 9 files changed, 1736 insertions(+) create mode 100644 src/render/gl.cpp create mode 100644 src/render/gl.h create mode 100644 src/render/gl_2d.cpp create mode 100644 src/render/gl_2d.h create mode 100644 src/render/gl_2d_font.cpp create mode 100644 src/render/gl_2d_font.h create mode 100644 src/render/gl_3d.cpp create mode 100644 src/render/gl_3d.h create mode 100644 src/render/gl_batch.h (limited to 'src/render') 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 +#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 +#include + +#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 programs; + LIST textures; + LIST 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 +LIST triangle_fan_to_list( VERTEX* vertices, U32 vert_count ) { + LIST 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 +#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 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; +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 +struct GL_BATCH; + +// func for setting sampler idx inside of vertex data +template +using GL_BATCH_VERTEX_SAMPLER_IDX_FN = void (*)(VERTEX* vertex, U8 idx); + +template +using GL_BATCH_SETUP_FN = void (*)(GL_BATCH* batch); + +template +struct GL_BATCH_CALL { + LIST vertices{}; + VEC2 clip_start; + VEC2 clip_dim; + + VEC2 viewport_start; + VEC2 viewport_dim; + + U16 primitive; + LIST textures{}; +}; + +template +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> calls{}; + + // func for setting up vertex attribs + GL_BATCH_SETUP_FN 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 +inline GL_BATCH* gl_batch_create( + GL_DATA* gl, + GL_SHADER_PROGRAM* shader, + GL_BATCH_SETUP_FN setup_cb +) { + GL_BATCH* batch = new GL_BATCH; + 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 +inline void gl_batch_destroy( GL_BATCH* batch ) { + glDeleteBuffers( &batch->vbuffer ); + delete batch; +} + +template +inline void gl_batch_empty( GL_BATCH* batch ) { + batch->calls.clear(); +} + +template +inline void gl_batch_draw( GL_BATCH* 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* 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 +inline GL_BATCH_CALL* gl_batch_get_call( GL_BATCH* 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* 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 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 +inline void gl_batch_insert( + GL_BATCH* batch, + VERTEX* vertices, + U32 count, + GL_TEX2D* tex, + U16 primitive = GL_TRIANGLES +) { + if( !count ) + return; + + GL_BATCH_CALL* 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] ); + } +} -- cgit v1.2.3