#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]; const I32 GL_WINDOW_MIN_W = 800; const I32 GL_WINDOW_MIN_H = 600; const I32 GL_WINDOW_ASPECT_W = 4; const I32 GL_WINDOW_ASPECT_H = 3; GL_DATA* gl_inst; GL_DATA* gl_instance() { return gl_inst; } static void gl_apply_canvas_size( GL_DATA* gl, I32 width, I32 height ); static void gl_handle_window_size_change( GL_DATA* gl, I32 width, I32 height ); static void gl_update_screen_ratio_uniforms( GL_DATA* gl, I32 width, I32 height ); static void gl_query_canvas_size( GL_DATA* gl, I32* width, I32* height ) { I32 w = 0; I32 h = 0; if( gl && gl->window ) { SDL_GL_GetDrawableSize( gl->window, &w, &h ); if( w <= 0 || h <= 0 ) SDL_GetWindowSize( gl->window, &w, &h ); } if( width ) *width = max( 1, w ); if( height ) *height = max( 1, h ); } static void gl_constrain_window_size( I32* width, I32* height ) { if( !width || !height ) return; I32 in_w = max( GL_WINDOW_MIN_W, *width ); I32 in_h = max( GL_WINDOW_MIN_H, *height ); I32 keep_w_h = (I32)( ( (I64)in_w * GL_WINDOW_ASPECT_H + GL_WINDOW_ASPECT_W / 2 ) / GL_WINDOW_ASPECT_W ); keep_w_h = max( GL_WINDOW_MIN_H, keep_w_h ); I32 keep_h_w = (I32)( ( (I64)in_h * GL_WINDOW_ASPECT_W + GL_WINDOW_ASPECT_H / 2 ) / GL_WINDOW_ASPECT_H ); keep_h_w = max( GL_WINDOW_MIN_W, keep_h_w ); I32 d_keep_w = abs( keep_w_h - in_h ); I32 d_keep_h = abs( keep_h_w - in_w ); if( d_keep_w <= d_keep_h ) { *width = in_w; *height = keep_w_h; } else { *width = keep_h_w; *height = in_h; } } static void gl_handle_window_size_change( GL_DATA* gl, I32 width, I32 height ) { if( !gl || !gl->window ) return; I32 target_w = width; I32 target_h = height; if( target_w <= 0 || target_h <= 0 ) SDL_GetWindowSize( gl->window, &target_w, &target_h ); gl_constrain_window_size( &target_w, &target_h ); I32 wnd_w, wnd_h; SDL_GetWindowSize( gl->window, &wnd_w, &wnd_h ); if( ( target_w != wnd_w || target_h != wnd_h ) && !gl->window_constraint_active ) { gl->window_constraint_active = 1; SDL_SetWindowSize( gl->window, target_w, target_h ); gl->window_constraint_active = 0; } I32 draw_w, draw_h; gl_query_canvas_size( gl, &draw_w, &draw_h ); gl_apply_canvas_size( gl, draw_w, draw_h ); } static I32 SDLCALL gl_event_filter( void* userdata, SDL_Event* e ) { GL_DATA* gl = (GL_DATA*)userdata; if( !gl || !e ) return 1; if( e->type == SDL_WINDOWEVENT ) { if( e->window.event == SDL_WINDOWEVENT_SIZE_CHANGED || e->window.event == SDL_WINDOWEVENT_RESIZED ) { gl_handle_window_size_change( gl, e->window.data1, e->window.data2 ); if( gl->resize_repaint_cb && !gl->resize_repaint_active ) { gl->resize_repaint_active = 1; gl->resize_repaint_cb( gl->resize_repaint_userdata ); gl->resize_repaint_active = 0; } } } return 1; } static void gl_apply_canvas_size( GL_DATA* gl, I32 width, I32 height ) { width = max( 1, width ); height = max( 1, height ); gl->canvas_size[0] = width; gl->canvas_size[1] = height; if( canvas ) { canvas[0] = width; canvas[1] = height; } gl->clip_start = { 0.f, 0.f }; gl->clip_dim = { (F32)width, (F32)height }; gl_update_screen_ratio_uniforms( gl, width, height ); glViewport( 0, 0, width, height ); glUseProgram( 0 ); } static void gl_update_screen_ratio_uniforms( GL_DATA* gl, I32 width, I32 height ) { F32 screen_ratio[] = { 2.f / (F32)max( 1, width ), 2.f / (F32)max( 1, height ), 1.f, 1.f }; gl->programs.each( fn( GL_SHADER_PROGRAM** it ) { glUseProgram( (*it)->id ); I32 ratio_location = glGetUniformLocation( (*it)->id, "g_screenratio" ); glUniform4fv( ratio_location, 1, &screen_ratio[0] ); } ); } 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 ); SDL_SetHint( SDL_HINT_WINDOWS_ENABLE_MESSAGELOOP, "1" ); SDL_SetHint( SDL_HINT_WINDOWS_DPI_SCALING, "0" ); SDL_SetHint( SDL_HINT_VIDEO_HIGHDPI_DISABLED, "1" ); 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_CORE ); 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 ); I32 start_w = _canvas[0]; I32 start_h = _canvas[1]; gl_constrain_window_size( &start_w, &start_h ); GL_DATA* gl = new GL_DATA; gl->window = SDL_CreateWindow( "game", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, start_w, start_h, SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE ); if( !gl->window ) { dlog( "gl_init() could not create window: %s\n", SDL_GetError() ); delete gl; return 0; } SDL_SetWindowMinimumSize( gl->window, GL_WINDOW_MIN_W, GL_WINDOW_MIN_H ); gl->ctx = SDL_GL_CreateContext( gl->window ); if( !gl->ctx ) { dlog( "gl_init() could not create context: %s\n", SDL_GetError() ); SDL_DestroyWindow( gl->window ); delete gl; return 0; } SDL_GL_SetSwapInterval(0); SDL_GL_MakeCurrent( gl->window, gl->ctx ); GLenum glewError = glewInit(); if( glewError != GLEW_OK ) { dlog( "gl_create() : Failed to initialize GLEW: %s\n", glewGetErrorString(glewError) ); SDL_GL_DeleteContext( gl->ctx ); SDL_DestroyWindow( gl->window ); delete gl; return 0; } while( glGetError() != GL_NO_ERROR ); // clear errors // 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(); gl_handle_window_size_change( gl, start_w, start_h ); SDL_SetEventFilter( gl_event_filter, gl ); gl_inst = gl; return gl; } void gl_gen_buffers( GL_DATA* gl ) { glGenVertexArrays( 1, &gl->vao ); glBindVertexArray( gl->vao ); 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 ) { SDL_SetEventFilter( 0, 0 ); 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; I32 width = size ? size[0] : 0; I32 height = size ? size[1] : 0; gl_constrain_window_size( &width, &height ); SDL_SetWindowSize( gl->window, width, height ); // this giga breaks on linux, is it needed? // SDL_SetWindowPosition( gl->window, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED ); gl_handle_window_size_change( gl, width, height ); } void gl_sync_window_size( GL_DATA* gl ) { if( !gl || !gl->window ) return; SDL_PumpEvents(); I32 w, h; SDL_GetWindowSize( gl->window, &w, &h ); gl_handle_window_size_change( gl, w, h ); } void gl_set_resize_repaint_callback( GL_DATA* gl, GL_RESIZE_REPAINT_CB cb, void* userdata ) { if( !gl ) return; gl->resize_repaint_cb = cb; gl->resize_repaint_userdata = userdata; } STAT gl_shader_compile( GL_DATA* gl, GL_SHADER_DEF* 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_DEF* 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 w, U32 h ) { 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 ); glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, bitmap ); glGenerateMipmap( GL_TEXTURE_2D ); glBindTexture( GL_TEXTURE_2D, 0 ); tex->width = w; tex->height = h; 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 ) { glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); gl->last_tick = u_tick(); return STAT_OK; } STAT gl_poll_events( GL_DATA* gl ) { SDL_Event e; while( SDL_PollEvent( &e ) ) { input_on_event( &e ); switch( e.type ) { case SDL_QUIT: return STAT_BREAK; case SDL_WINDOWEVENT: { if( e.window.event == SDL_WINDOWEVENT_SIZE_CHANGED || e.window.event == SDL_WINDOWEVENT_RESIZED ) { gl_handle_window_size_change( gl, e.window.data1, e.window.data2 ); } } break; case SDL_KEYDOWN: return e.key.keysym.sym == SDLK_ESCAPE ? STAT_BREAK : STAT_OK; default: break; } } 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(); 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_update_screen_ratio_uniforms( gl, (I32)dim.x, (I32)dim.y ); 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; }