medfall

A super great game engine
Log | Files | Refs

renderer.cc (32760B)


      1 #include <algorithm>
      2 
      3 #include "intrinsics.h"
      4 #include "game.h"
      5 #include "gl.h"
      6 #include "renderer.h"
      7 #include "log.h"
      8 #include "linear_algebra.h"
      9 #include "blue_noise.h"
     10 #include "obj.h"
     11 #include "fnv.h"
     12 
     13 #include "glad.h"
     14 
     15 STATIC_ASSERT( SAME_TYPE( VB, GLuint ) );
     16 STATIC_ASSERT( SAME_TYPE( IB, GLuint ) );
     17 STATIC_ASSERT( SAME_TYPE( Texture, GLuint ) );
     18 STATIC_ASSERT( SAME_TYPE( FramebufferObject, GLuint ) );
     19 STATIC_ASSERT( SAME_TYPE( u32, GLuint ) );
     20 
     21 static const GLuint ATTR_POSITION = 0;
     22 static const GLuint ATTR_NORMAL = 1;
     23 static const GLuint ATTR_TEX_COORD0 = 2;
     24 static const GLuint ATTR_TEX_COORD1 = 3;
     25 static const GLuint ATTR_COLOUR = 4;
     26 static const GLuint ATTR_JOINTS = 5;
     27 static const GLuint ATTR_WEIGHTS = 6;
     28 static const GLuint ATTR_MODEL_TO_WORLD_COL0 = 7;
     29 
     30 static const u32 UNIFORM_BUFFER_SIZE = kilobytes( 64 );
     31 
     32 struct DrawCall {
     33 	RenderState render_state;
     34 	Mesh mesh;
     35 	u32 num_instances;
     36 	VB instance_data;
     37 };
     38 
     39 enum DeleteCommandType {
     40 	DELETE_VB,
     41 	DELETE_IB,
     42 	DELETE_TEXTURE,
     43 	DELETE_MESH,
     44 };
     45 
     46 struct DeleteCommand {
     47 	DeleteCommandType type;
     48 	union {
     49 		VB vb;
     50 		IB ib;
     51 		Shader shader;
     52 		Texture texture;
     53 		Mesh mesh;
     54 	};
     55 };
     56 
     57 static DynamicArray< DrawCall > draw_calls;
     58 static DynamicArray< RenderPass > render_passes;
     59 static DynamicArray< DeleteCommand > deletes;
     60 
     61 static u32 draw_calls_this_frame;
     62 static u32 vertices_this_frame;
     63 
     64 static bool in_frame;
     65 
     66 struct UBO {
     67 	GLuint ubo;
     68 	u8 * buffer;
     69 	u32 bytes_used;
     70 };
     71 
     72 static UBO ubos[ 8 ];
     73 static size_t ubo_offset_alignment;
     74 
     75 static Texture blue_noise;
     76 static Mesh fullscreen_triangle;
     77 
     78 static RenderState previous_render_state;
     79 static FramebufferObject previous_fbo;
     80 static u32 previous_viewport_width;
     81 static u32 previous_viewport_height;
     82 
     83 void renderer_init() {
     84 	GLint alignment;
     85 	glGetIntegerv( GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT, &alignment );
     86 	ubo_offset_alignment = checked_cast< size_t >( alignment );
     87 
     88 	GLint max_ubo_size;
     89 	glGetIntegerv( GL_MAX_UNIFORM_BLOCK_SIZE, &max_ubo_size );
     90 	ASSERT( max_ubo_size >= UNIFORM_BUFFER_SIZE );
     91 
     92 	for( size_t i = 0; i < ARRAY_COUNT( ubos ); i++ ) {
     93 		glGenBuffers( 1, &ubos[ i ].ubo );
     94 		glBindBuffer( GL_UNIFORM_BUFFER, ubos[ i ].ubo );
     95 		glBufferData( GL_UNIFORM_BUFFER, UNIFORM_BUFFER_SIZE, NULL, GL_DYNAMIC_DRAW );
     96 	}
     97 
     98 	in_frame = false;
     99 
    100 	previous_fbo = 0;
    101 	previous_viewport_width = 0;
    102 	previous_viewport_height = 0;
    103 
    104 	blue_noise = load_png_memory( blue_noise_png, blue_noise_png_len, TEXFMT_R_U8NORM, 1 );
    105 
    106 	v3 tri[] = {
    107 		v3( -1.0f, -1.0f, 0.0f ),
    108 		v3( 3.0f, -1.0f, 0.0f ),
    109 		v3( -1.0f, 3.0f, 0.0f ),
    110 	};
    111 	MeshConfig config;
    112 	config.positions = renderer_new_vb( tri, sizeof( tri ) );
    113 	config.num_vertices = 3;
    114 	config.primitive_type = PRIMITIVETYPE_TRIANGLES;
    115 	fullscreen_triangle = renderer_new_mesh( config );
    116 }
    117 
    118 void renderer_term() {
    119 	for( size_t i = 0; i < ARRAY_COUNT( ubos ); i++ ) {
    120 		glDeleteBuffers( 1, &ubos[ i ].ubo );
    121 	}
    122 
    123 	renderer_delete_texture( blue_noise );
    124 	renderer_delete_mesh( fullscreen_triangle );
    125 }
    126 
    127 void renderer_begin_frame() {
    128 	ASSERT( !in_frame );
    129 	in_frame = true;
    130 
    131 	draw_calls.clear();
    132 	render_passes.clear();
    133 	deletes.clear();
    134 
    135 	draw_calls_this_frame = 0;
    136 	vertices_this_frame = 0;
    137 
    138 	for( size_t i = 0; i < ARRAY_COUNT( ubos ); i++ ) {
    139 		glBindBuffer( GL_UNIFORM_BUFFER, ubos[ i ].ubo );
    140 		ubos[ i ].buffer = ( u8 * ) glMapBufferRange( GL_UNIFORM_BUFFER, 0, UNIFORM_BUFFER_SIZE, GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT );
    141 		ubos[ i ].bytes_used = 0;
    142 		ASSERT( ubos[ i ].buffer != NULL );
    143 	}
    144 
    145 	v2u32 window_size = get_window_size();
    146 	if( window_size.x != previous_viewport_width || window_size.y != previous_viewport_height ) {
    147 		previous_viewport_width = window_size.x;
    148 		previous_viewport_height = window_size.y;
    149 		glViewport( 0, 0, window_size.x, window_size.y );
    150 	}
    151 }
    152 
    153 static GLenum depthfunc_to_glenum( DepthFunc depth_func ) {
    154 	switch( depth_func ) {
    155 		case DEPTHFUNC_LESS: return GL_LESS;
    156 		case DEPTHFUNC_EQUAL: return GL_EQUAL;
    157 		case DEPTHFUNC_ALWAYS: return GL_ALWAYS;
    158 		case DEPTHFUNC_DISABLED: return GL_ALWAYS;
    159 	}
    160 
    161 	FATAL( "unsupported DepthFunc: {}", depth_func );
    162 	return GL_INVALID_ENUM;
    163 }
    164 
    165 static GLenum primitivetype_to_glenum( PrimitiveType primitive_type ) {
    166 	switch( primitive_type ) {
    167 		case PRIMITIVETYPE_TRIANGLES: return GL_TRIANGLES;
    168 		case PRIMITIVETYPE_TRIANGLE_STRIP: return GL_TRIANGLE_STRIP;
    169 		case PRIMITIVETYPE_POINTS: return GL_POINTS;
    170 		case PRIMITIVETYPE_LINES: return GL_LINES;
    171 	}
    172 
    173 	FATAL( "unsupported PrimitiveType: {}", primitive_type );
    174 	return GL_INVALID_ENUM;
    175 }
    176 
    177 static void set_render_state( const RenderState & state ) {
    178 	if( state.shader != previous_render_state.shader ) {
    179 		glUseProgram( state.shader->program );
    180 	}
    181 
    182 	// uniforms
    183 	// TODO: maybe these shouldn't always get rebound
    184 	for( size_t i = 0; i < ARRAY_COUNT( state.shader->uniforms ); i++ ) {
    185 		bool found = false;
    186 		for( size_t j = 0; j < state.num_uniforms; j++ ) {
    187 			UniformBinding binding = state.uniforms[ j ].binding;
    188 			if( state.uniforms[ j ].name_hash == state.shader->uniforms[ i ] && binding.size > 0 ) {
    189 				glBindBufferRange( GL_UNIFORM_BUFFER, i, binding.ubo, binding.offset, binding.size );
    190 				found = true;
    191 				break;
    192 			}
    193 		}
    194 
    195 		if( !found ) {
    196 			glBindBufferBase( GL_UNIFORM_BUFFER, i, 0 );
    197 		}
    198 	}
    199 
    200 	// textures
    201 	for( size_t i = 0; i < ARRAY_COUNT( state.shader->textures ); i++ ) {
    202 		glActiveTexture( GL_TEXTURE0 + i );
    203 
    204 		bool found = false;
    205 		for( size_t j = 0; j < state.num_textures; j++ ) {
    206 			if( state.textures[ j ].name_hash == state.shader->textures[ i ] ) {
    207 				glBindTexture( GL_TEXTURE_2D, state.textures[ j ].texture );
    208 				found = true;
    209 				break;
    210 			}
    211 		}
    212 
    213 		if( !found ) {
    214 			glBindTexture( GL_TEXTURE_2D, 0 );
    215 		}
    216 	}
    217 
    218 	// depth writing
    219 	if( state.disable_depth_writes != previous_render_state.disable_depth_writes ) {
    220 		glDepthMask( state.disable_depth_writes ? GL_FALSE : GL_TRUE );
    221 	}
    222 
    223 	// backface culling
    224 	if( state.cull_face != previous_render_state.cull_face ) {
    225 		if( state.cull_face == CULLFACE_DISABLED ) {
    226 			glDisable( GL_CULL_FACE );
    227 		}
    228 		else {
    229 			if( previous_render_state.cull_face == CULLFACE_DISABLED ) {
    230 				glEnable( GL_CULL_FACE );
    231 			}
    232 			glCullFace( state.cull_face == CULLFACE_FRONT ? GL_FRONT : GL_BACK );
    233 		}
    234 	}
    235 
    236 	// depth testing
    237 	if( state.depth_func != previous_render_state.depth_func ) {
    238 		if( state.depth_func == DEPTHFUNC_DISABLED ) {
    239 			glDisable( GL_DEPTH_TEST );
    240 		}
    241 		else {
    242 			if( previous_render_state.depth_func == DEPTHFUNC_DISABLED ) {
    243 				glEnable( GL_DEPTH_TEST );
    244 			}
    245 			glDepthFunc( depthfunc_to_glenum( state.depth_func ) );
    246 		}
    247 	}
    248 
    249 	// alpha blending
    250 	if( state.enable_alpha_blending != previous_render_state.enable_alpha_blending ) {
    251 		if( state.enable_alpha_blending ) {
    252 			glEnable( GL_BLEND );
    253 			glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
    254 		}
    255 		else {
    256 			glDisable( GL_BLEND );
    257 		}
    258 	}
    259 
    260 	// polygon fill mode
    261 	if( state.wireframe != previous_render_state.wireframe ) {
    262 		if( state.wireframe ) {
    263 			glPolygonMode( GL_FRONT_AND_BACK, GL_LINE );
    264 			glEnable( GL_POLYGON_OFFSET_LINE );
    265 			glPolygonOffset( -1, -1 );
    266 		}
    267 		else {
    268 			glPolygonMode( GL_FRONT_AND_BACK, GL_FILL );
    269 			glDisable( GL_POLYGON_OFFSET_LINE );
    270 		}
    271 	}
    272 
    273 	previous_render_state = state;
    274 }
    275 
    276 static bool compare_drawcall( const DrawCall & a, const DrawCall & b ) {
    277 	if( a.render_state.pass != b.render_state.pass )
    278 		return a.render_state.pass < b.render_state.pass;
    279 	return a.render_state.shader < b.render_state.shader;
    280 }
    281 
    282 static void setup_render_pass( const RenderPass & pass ) {
    283 	if( GLAD_GL_KHR_debug != 0 ) {
    284 		glPushDebugGroup( GL_DEBUG_SOURCE_APPLICATION, 0, -1, pass.name );
    285 	}
    286 
    287 	const FB & fb = pass.target;
    288 	if( fb.fbo != previous_fbo ) {
    289 		glBindFramebuffer( GL_DRAW_FRAMEBUFFER, fb.fbo );
    290 		previous_fbo = fb.fbo;
    291 
    292 		u32 viewport_width, viewport_height;
    293 		if( fb.fbo == 0 ) {
    294 			v2u32 window_size = get_window_size();
    295 			viewport_width = window_size.x;
    296 			viewport_height = window_size.y;
    297 		}
    298 		else {
    299 			viewport_width = fb.width;
    300 			viewport_height = fb.height;
    301 		}
    302 
    303 		if( viewport_width != previous_viewport_width || viewport_height != previous_viewport_height ) {
    304 			previous_viewport_width = viewport_width;
    305 			previous_viewport_height = viewport_height;
    306 			glViewport( 0, 0, viewport_width, viewport_height );
    307 		}
    308 	}
    309 
    310 	GLbitfield clear_mask = 0;
    311 	clear_mask |= pass.clear_colour ? GL_COLOR_BUFFER_BIT : 0;
    312 	clear_mask |= pass.clear_depth ? GL_DEPTH_BUFFER_BIT : 0;
    313 	if( clear_mask != 0 ) {
    314 		if( pass.clear_colour )
    315 			glClearColor( pass.colour.x, pass.colour.y, pass.colour.z, pass.colour.w );
    316 		if( pass.clear_depth )
    317 			glClearDepth( pass.depth );
    318 		glClear( clear_mask );
    319 	}
    320 }
    321 
    322 static void drawcall_single( const Mesh & mesh, const RenderState & state ) {
    323 	set_render_state( state );
    324 
    325 	glBindVertexArray( mesh.vao );
    326 	GLenum primitive = primitivetype_to_glenum( mesh.primitive_type );
    327 	if( mesh.indices != 0 ) {
    328 		glDrawElements( primitive, mesh.num_vertices, mesh.indices_format == INDEXFMT_U16 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, 0 );
    329 	}
    330 	else {
    331 		glDrawArrays( primitive, 0, mesh.num_vertices );
    332 	}
    333 	glBindVertexArray( 0 );
    334 }
    335 
    336 static void drawcall_instanced( const Mesh & mesh, const RenderState & state, u32 num_instances, VB instance_data ) {
    337 	set_render_state( state );
    338 
    339 	glBindVertexArray( mesh.vao );
    340 	glBindBuffer( GL_ARRAY_BUFFER, instance_data );
    341 
    342 	for( int i = 0; i < 4; i++ ) {
    343 		GLuint loc = ATTR_MODEL_TO_WORLD_COL0 + i;
    344 		glEnableVertexAttribArray( loc );
    345 		glVertexAttribPointer( loc, 4, GL_FLOAT, GL_FALSE, sizeof( m4 ), ( GLvoid * ) ( i * sizeof( v4 ) ) );
    346 		glVertexAttribDivisor( loc, 1 );
    347 	}
    348 
    349 	GLenum primitive = primitivetype_to_glenum( mesh.primitive_type );
    350 	if( mesh.indices != 0 ) {
    351 		glDrawElementsInstanced( primitive, mesh.num_vertices, GL_UNSIGNED_INT, 0, checked_cast< s32 >( num_instances ) );
    352 	}
    353 	else {
    354 		glDrawArraysInstanced( primitive, 0, mesh.num_vertices, checked_cast< s32 >( num_instances ) );
    355 	}
    356 
    357 	glBindVertexArray( 0 );
    358 }
    359 
    360 void renderer_end_frame() {
    361 	ASSERT( in_frame );
    362 	in_frame = false;
    363 
    364 	for( size_t i = 0; i < ARRAY_COUNT( ubos ); i++ ) {
    365 		glBindBuffer( GL_UNIFORM_BUFFER, ubos[ i ].ubo );
    366 		glUnmapBuffer( GL_UNIFORM_BUFFER );
    367 	}
    368 
    369 	std::sort( draw_calls.begin(), draw_calls.end(), compare_drawcall );
    370 
    371 	u8 pass_idx = U8_MAX;
    372 	for( const DrawCall & dc : draw_calls ) {
    373 		if( dc.render_state.pass != pass_idx ) {
    374 			if( GLAD_GL_KHR_debug != 0 && pass_idx != U8_MAX )
    375 				glPopDebugGroup();
    376 			setup_render_pass( render_passes[ dc.render_state.pass ] );
    377 			pass_idx = dc.render_state.pass;
    378 		}
    379 
    380 		if( dc.render_state.shader == NULL ) {
    381 			FB fb = render_passes[ dc.render_state.pass ].msaa_source;
    382 			v2u32 window_size = get_window_size();
    383 			ASSERT( fb.width == window_size.x && fb.height == window_size.y );
    384 			glBindFramebuffer( GL_READ_FRAMEBUFFER, fb.fbo );
    385 			glBlitFramebuffer( 0, 0, fb.width, fb.height, 0, 0, fb.width, fb.height, GL_COLOR_BUFFER_BIT, GL_NEAREST );
    386 		}
    387 		else if( dc.num_instances == 0 ) {
    388 			drawcall_single( dc.mesh, dc.render_state );
    389 		}
    390 		else {
    391 			drawcall_instanced( dc.mesh, dc.render_state, dc.num_instances, dc.instance_data );
    392 		}
    393 	}
    394 
    395 	if( GLAD_GL_KHR_debug != 0 && pass_idx != U8_MAX )
    396 		glPopDebugGroup();
    397 
    398 	for( const DeleteCommand & del : deletes ) {
    399 		switch( del.type ) {
    400 			case DELETE_VB:
    401 				glDeleteBuffers( 1, &del.vb );
    402 				break;
    403 
    404 			case DELETE_IB:
    405 				glDeleteBuffers( 1, &del.ib );
    406 				break;
    407 
    408 			case DELETE_TEXTURE:
    409 				glDeleteTextures( 1, &del.texture );
    410 				break;
    411 
    412 			case DELETE_MESH:
    413 				if( del.mesh.positions != 0 )
    414 					renderer_delete_vb( del.mesh.positions );
    415 				if( del.mesh.normals != 0 )
    416 					renderer_delete_vb( del.mesh.normals );
    417 				if( del.mesh.tex_coords0 != 0 )
    418 					renderer_delete_vb( del.mesh.tex_coords0 );
    419 				if( del.mesh.tex_coords1 != 0 )
    420 					renderer_delete_vb( del.mesh.tex_coords1 );
    421 				if( del.mesh.colours != 0 )
    422 					renderer_delete_vb( del.mesh.colours );
    423 				if( del.mesh.indices != 0 )
    424 					renderer_delete_ib( del.mesh.indices );
    425 				if( del.mesh.joints != 0 )
    426 					renderer_delete_vb( del.mesh.joints );
    427 				if( del.mesh.weights != 0 )
    428 					renderer_delete_vb( del.mesh.weights );
    429 
    430 				glDeleteVertexArrays( 1, &del.mesh.vao );
    431 				break;
    432 		}
    433 	}
    434 }
    435 
    436 u8 renderer_add_pass( const RenderPass & pass ) {
    437 	return checked_cast< u8 >( render_passes.add( pass ) );
    438 }
    439 
    440 u8 renderer_add_pass( const char * name, FB target, ClearColourBool clear_colour, ClearDepthBool clear_depth ) {
    441 	RenderPass pass;
    442 	pass.target = target;
    443 	pass.name = name;
    444 	pass.clear_colour = clear_colour == RENDERER_CLEAR_COLOUR_DO;
    445 	pass.clear_depth = clear_depth == RENDERER_CLEAR_DEPTH_DO;
    446 	return renderer_add_pass( pass );
    447 }
    448 
    449 u8 renderer_add_pass( const char * name, ClearColourBool clear_colour, ClearDepthBool clear_depth ) {
    450 	FB target = { };
    451 	return renderer_add_pass( name, target, clear_colour, clear_depth );
    452 }
    453 
    454 void renderer_resolve_msaa( FB fb ) {
    455 	RenderPass pass;
    456 	pass.name = "Resolve MSAA";
    457 	pass.msaa_source = fb;
    458 
    459 	RenderState dummy;
    460 	dummy.pass = renderer_add_pass( pass );
    461 	dummy.shader = NULL;
    462 
    463 	DrawCall dc;
    464 	dc.render_state = dummy;
    465 	draw_calls.add( dc );
    466 }
    467 
    468 u32 renderer_num_draw_calls() {
    469 	return draw_calls_this_frame;
    470 }
    471 
    472 u32 renderer_num_vertices() {
    473 	return vertices_this_frame;
    474 }
    475 
    476 Texture renderer_blue_noise() {
    477 	return blue_noise;
    478 }
    479 
    480 UniformBinding renderer_upload_uniforms( const void * data, size_t size ) {
    481 	ASSERT( in_frame );
    482 
    483 	UBO * ubo = NULL;
    484 	u32 offset;
    485 
    486 	for( size_t i = 0; i < ARRAY_COUNT( ubos ); i++ ) {
    487 		offset = checked_cast< u32 >( align_power_of_2( ubos[ i ].bytes_used, ubo_offset_alignment ) );
    488 		if( UNIFORM_BUFFER_SIZE - offset >= size ) {
    489 			ubo = &ubos[ i ];
    490 			break;
    491 		}
    492 	}
    493 
    494 	if( ubo == NULL )
    495 		FATAL( "no ubo space" );
    496 
    497 	UniformBinding binding;
    498 	binding.ubo = ubo->ubo;
    499 	binding.offset = offset;
    500 	binding.size = checked_cast< u32 >( size );
    501 
    502 	// memset so we don't leave any gaps. good for write combined memory!
    503 	memset( ubo->buffer + ubo->bytes_used, 0, offset - ubo->bytes_used );
    504 	memcpy( ubo->buffer + offset, data, size );
    505 	ubo->bytes_used = offset + size;
    506 
    507 	return binding;
    508 }
    509 
    510 static GLenum bufferusage_to_glenum( BufferUsage usage ) {
    511 	switch( usage ) {
    512 		case BUFFERUSAGE_STATIC: return GL_STATIC_DRAW;
    513 		case BUFFERUSAGE_DYNAMIC: return GL_DYNAMIC_DRAW;
    514 		case BUFFERUSAGE_STREAM: return GL_STREAM_DRAW;
    515 	}
    516 
    517 	FATAL( "unsupported BufferUsage: {}", usage );
    518 	return GL_INVALID_ENUM;
    519 }
    520 
    521 static GLenum texfmt_to_internal_format( TextureFormat format ) {
    522 	switch( format ) {
    523 		case TEXFMT_INVALID:
    524 			break;
    525 
    526 		case TEXFMT_R_U8:
    527 			return GL_R8;
    528 		case TEXFMT_R_U8NORM:
    529 			return GL_R8_SNORM;
    530 		case TEXFMT_R_U16:
    531 			return GL_R16;
    532 		case TEXFMT_R_FLOAT:
    533 			return GL_R32F;
    534 
    535 		case TEXFMT_RGB_U8:
    536 			return GL_RGB8;
    537 		case TEXFMT_RGB_U8_SRGB:
    538 			return GL_SRGB8;
    539 		case TEXFMT_RGB_HALF:
    540 			return GL_RGB16F;
    541 		case TEXFMT_RGB_FLOAT:
    542 			return GL_RGB32F;
    543 
    544 		case TEXFMT_RGBA_U8:
    545 			return GL_RGBA8;
    546 		case TEXFMT_RGBA_U8_SRGB:
    547 			return GL_SRGB8_ALPHA8;
    548 		case TEXFMT_RGBA_FLOAT:
    549 			return GL_RGBA32F;
    550 
    551 		case TEXFMT_DEPTH:
    552 			return GL_DEPTH_COMPONENT24;
    553 
    554 		case TEXFMT_BC1:
    555 			return GL_COMPRESSED_RGB_S3TC_DXT1_EXT;
    556 		case TEXFMT_BC1_SRGB:
    557 			return GL_COMPRESSED_SRGB_S3TC_DXT1_EXT;
    558 		case TEXFMT_BC4:
    559 			return GL_COMPRESSED_RED_RGTC1;
    560 		case TEXFMT_BC5:
    561 			return GL_COMPRESSED_RG_RGTC2;
    562 	}
    563 
    564 	FATAL( "unsupported TextureFormat: {}", format );
    565 	return GL_INVALID_ENUM;
    566 }
    567 
    568 static GLenum texturewrap_to_glenum( TextureWrapMode mode ) {
    569 	switch( mode ) {
    570 		case TEXWRAP_REPEAT:
    571 			return GL_REPEAT;
    572 		case TEXWRAP_CLAMP:
    573 			return GL_CLAMP_TO_EDGE;
    574 		case TEXWRAP_MIRROR:
    575 			return GL_MIRRORED_REPEAT;
    576 		case TEXWRAP_BORDER:
    577 			return GL_CLAMP_TO_BORDER;
    578 	}
    579 
    580 	FATAL( "unsupported TextureWrapMode: {}", mode );
    581 	return GL_INVALID_ENUM;
    582 }
    583 
    584 static void texturefilter_to_glenum( TextureFilterMode mode, bool mipmaps, GLenum * min_filter, GLenum * mag_filter ) {
    585 	switch( mode ) {
    586 		case TEXFILTER_LINEAR:
    587 			*min_filter = mipmaps ? GL_LINEAR_MIPMAP_LINEAR : GL_LINEAR;
    588 			*mag_filter = GL_LINEAR;
    589 			return;
    590 		case TEXFILTER_POINT:
    591 			*min_filter = mipmaps ? GL_NEAREST_MIPMAP_NEAREST : GL_NEAREST;
    592 			*mag_filter = GL_NEAREST;
    593 			return;
    594 	}
    595 
    596 	FATAL( "unsupported TextureFilterMode: {}", mode );
    597 }
    598 
    599 static bool is_compressed( TextureFormat format ) {
    600 	return format == TEXFMT_BC1 || format == TEXFMT_BC4 || format == TEXFMT_BC5;
    601 }
    602 
    603 VB renderer_new_vb( const void * data, u32 len, BufferUsage usage ) {
    604 	// glBufferData's length parameter is GLsizeiptr, so we need to make
    605 	// sure len fits in a signed 32bit int
    606 	ASSERT( len < S32_MAX );
    607 
    608 	GLuint vbo;
    609 	glGenBuffers( 1, &vbo );
    610 
    611 	if( data != NULL ) {
    612 		glBindBuffer( GL_ARRAY_BUFFER, vbo );
    613 		glBufferData( GL_ARRAY_BUFFER, len, data, bufferusage_to_glenum( usage ) );
    614 	}
    615 
    616 	return vbo;
    617 }
    618 
    619 void renderer_delete_vb( VB vb ) {
    620 	glDeleteBuffers( 1, &vb );
    621 }
    622 
    623 IB renderer_new_ib( const void * data, u32 len, BufferUsage usage ) {
    624 	ASSERT( len < S32_MAX );
    625 
    626 	GLuint ebo;
    627 	glGenBuffers( 1, &ebo );
    628 
    629 	if( data != NULL ) {
    630 		glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, ebo );
    631 		glBufferData( GL_ELEMENT_ARRAY_BUFFER, len, data, bufferusage_to_glenum( usage ) );
    632 	}
    633 
    634 	return ebo;
    635 }
    636 
    637 void renderer_delete_ib( IB ib ) {
    638 	glDeleteBuffers( 1, &ib );
    639 }
    640 
    641 static u32 mipmap_dim( u32 dim, u32 level ) {
    642 	return max( dim >> level, u32( 1 ) );
    643 }
    644 
    645 // TODO: use clz
    646 static u32 mipmap_levels( u32 width, u32 height ) {
    647 	u32 levels = 1;
    648 	u32 dim = max( width, height );
    649 	while( dim > 1 ) {
    650 		dim >>= 1;
    651 		levels++;
    652 	}
    653 	return levels;
    654 }
    655 
    656 static u32 texture_byte_size( u32 width, u32 height, TextureFormat format, bool mipmaps ) {
    657 	u32 total_dim = width * height;
    658 	if( mipmaps ) {
    659 		for( u32 i = 1; i < mipmap_levels( width, height ); i++ ) {
    660 			total_dim += mipmap_dim( width, i ) * mipmap_dim( height, i );
    661 		}
    662 	}
    663 
    664 	switch( format ) {
    665 		case TEXFMT_INVALID:
    666 		case TEXFMT_DEPTH:
    667 			break;
    668 
    669 		case TEXFMT_R_U8:
    670 		case TEXFMT_R_U8NORM:
    671 			return total_dim * 1 * sizeof( u8 );
    672 		case TEXFMT_R_U16:
    673 			return total_dim * 1 * sizeof( u16 );
    674 		case TEXFMT_R_FLOAT:
    675 			return total_dim * 1 * sizeof( float );
    676 
    677 		case TEXFMT_RGB_U8:
    678 			return total_dim * 3 * sizeof( u8 );
    679 		case TEXFMT_RGB_U8_SRGB:
    680 			return total_dim * 3 * sizeof( u8 );
    681 		case TEXFMT_RGB_HALF:
    682 			return total_dim * 3 * sizeof( u16 );
    683 		case TEXFMT_RGB_FLOAT:
    684 			return total_dim * 3 * sizeof( float );
    685 
    686 		case TEXFMT_RGBA_U8:
    687 			return total_dim * 4 * sizeof( u8 );
    688 		case TEXFMT_RGBA_U8_SRGB:
    689 			return total_dim * 4 * sizeof( u8 );
    690 		case TEXFMT_RGBA_FLOAT:
    691 			return total_dim * 4 * sizeof( float );
    692 
    693 		case TEXFMT_BC1:
    694 		case TEXFMT_BC1_SRGB:
    695 			return total_dim / 2;
    696 		case TEXFMT_BC4:
    697 			return total_dim / 2;
    698 		case TEXFMT_BC5:
    699 			return total_dim;
    700 	}
    701 
    702 	FATAL( "unsupported TextureFormat: {}", format );
    703 	return GL_INVALID_ENUM;
    704 }
    705 
    706 static Texture new_texture( TextureConfig config, int msaa_samples ) {
    707 	ASSERT( config.format != TEXFMT_INVALID );
    708 	ASSERT( !is_compressed( config.format ) || msaa_samples == 0 );
    709 	ASSERT( !config.has_mipmaps ); // TODO
    710 
    711 	GLenum target = msaa_samples == 0 ? GL_TEXTURE_2D : GL_TEXTURE_2D_MULTISAMPLE;
    712 
    713 	GLuint texture;
    714 	glGenTextures( 1, &texture );
    715 	// TODO: should probably update the gl state since we bind a new texture
    716 	glBindTexture( target, texture );
    717 
    718 	if( msaa_samples == 0 ) {
    719 		glTexParameteri( target, GL_TEXTURE_WRAP_S, texturewrap_to_glenum( config.wrap ) );
    720 		glTexParameteri( target, GL_TEXTURE_WRAP_T, texturewrap_to_glenum( config.wrap ) );
    721 
    722 		GLenum min_filter, mag_filter;
    723 		texturefilter_to_glenum( config.filter, config.has_mipmaps, &min_filter, &mag_filter );
    724 		glTexParameteri( target, GL_TEXTURE_MIN_FILTER, min_filter );
    725 		glTexParameteri( target, GL_TEXTURE_MAG_FILTER, mag_filter );
    726 
    727 		if( config.wrap == TEXWRAP_BORDER ) {
    728 			glTexParameterfv( target, GL_TEXTURE_BORDER_COLOR, ( GLfloat * ) &config.border_colour );
    729 		}
    730 	}
    731 
    732 	GLenum internal_format = texfmt_to_internal_format( config.format );
    733 
    734 	if( is_compressed( config.format ) ) {
    735 		u32 dxt_size = texture_byte_size( config.width, config.height, config.format, false );
    736 		glCompressedTexImage2D( GL_TEXTURE_2D, 0, internal_format,
    737 			config.width, config.height, 0, dxt_size, config.data );
    738 	}
    739 	else {
    740 		GLenum channels = GL_INVALID_ENUM;
    741 		GLenum type = GL_INVALID_ENUM;
    742 
    743 		switch( config.format ) {
    744 			case TEXFMT_R_U8:
    745 			case TEXFMT_R_U8NORM:
    746 				channels = GL_RED;
    747 				type = GL_UNSIGNED_BYTE;
    748 				break;
    749 			case TEXFMT_R_U16:
    750 				channels = GL_RED;
    751 				type = GL_UNSIGNED_SHORT;
    752 				break;
    753 			case TEXFMT_R_FLOAT:
    754 				channels = GL_RED;
    755 				type = GL_FLOAT;
    756 				break;
    757 
    758 			case TEXFMT_RGB_U8:
    759 			case TEXFMT_RGB_U8_SRGB:
    760 				channels = GL_RGB;
    761 				type = GL_UNSIGNED_BYTE;
    762 				break;
    763 			case TEXFMT_RGB_HALF:
    764 				channels = GL_RGB;
    765 				type = GL_HALF_FLOAT;
    766 				break;
    767 			case TEXFMT_RGB_FLOAT:
    768 				channels = GL_RGB;
    769 				type = GL_FLOAT;
    770 				break;
    771 
    772 			case TEXFMT_RGBA_U8:
    773 			case TEXFMT_RGBA_U8_SRGB:
    774 				channels = GL_RGBA;
    775 				type = GL_UNSIGNED_BYTE;
    776 				break;
    777 			case TEXFMT_RGBA_FLOAT:
    778 				channels = GL_RGBA;
    779 				type = GL_FLOAT;
    780 				break;
    781 
    782 			case TEXFMT_DEPTH:
    783 				channels = GL_DEPTH_COMPONENT;
    784 				type = GL_FLOAT;
    785 				break;
    786 
    787 			default:
    788 				FATAL( "implement channel/type selection for {}", config.format );
    789 				break;
    790 		}
    791 
    792 		if( msaa_samples == 0 ) {
    793 			glTexImage2D( GL_TEXTURE_2D, 0, internal_format,
    794 				config.width, config.height, 0, channels, type, config.data );
    795 		}
    796 		else {
    797 			glTexImage2DMultisample( GL_TEXTURE_2D_MULTISAMPLE, msaa_samples,
    798 				internal_format, config.width, config.height, GL_TRUE );
    799 		}
    800 	}
    801 
    802 	return texture;
    803 }
    804 
    805 Texture renderer_new_texture( const TextureConfig & config ) {
    806 	return new_texture( config, 0 );
    807 }
    808 
    809 void renderer_delete_texture( Texture texture ) {
    810 	glDeleteTextures( 1, &texture );
    811 }
    812 
    813 static GLenum framebufferattachment_to_glenum( FramebufferAttachment attachment ) {
    814 	switch( attachment ) {
    815 		case FB_COLOUR: return GL_COLOR_ATTACHMENT0;
    816 		case FB_DEPTH: return GL_DEPTH_ATTACHMENT;
    817 		case FB_NORMAL: return GL_COLOR_ATTACHMENT1;
    818 	}
    819 
    820 	FATAL( "invalid FramebufferAttachment: {}", attachment );
    821 	return GL_INVALID_ENUM;
    822 }
    823 
    824 FB renderer_new_fb( const FramebufferConfig & config ) {
    825 	GLuint fbo;
    826 	glGenFramebuffers( 1, &fbo );
    827 	glBindFramebuffer( GL_FRAMEBUFFER, fbo );
    828 
    829 	FB fb;
    830 	fb.fbo = fbo;
    831 
    832 	u32 width = 0;
    833 	u32 height = 0;
    834 	GLenum bufs[ OUTPUTS_COUNT ];
    835 
    836 	for( size_t i = 0; i < config.textures.size(); i++ ) {
    837 		bufs[ i ] = GL_NONE;
    838 
    839 		const TextureConfig & texture_config = config.textures[ i ].config;
    840 		if( texture_config.format == TEXFMT_INVALID )
    841 			continue;
    842 
    843 		ASSERT( !is_compressed( texture_config.format ) );
    844 
    845 		GLenum attachment = framebufferattachment_to_glenum( config.textures[ i ].attachment );
    846 		Texture texture = new_texture( texture_config, config.msaa_samples );
    847 
    848 		GLenum target = config.msaa_samples == 0 ? GL_TEXTURE_2D : GL_TEXTURE_2D_MULTISAMPLE;
    849 		glFramebufferTexture2D( GL_FRAMEBUFFER, attachment, target, texture, 0 );
    850 
    851 		width = texture_config.width;
    852 		height = texture_config.height;
    853 		fb.textures[ i ] = texture;
    854 		if( config.textures[ i ].attachment != FB_DEPTH )
    855 			bufs[ i ] = attachment;
    856 	}
    857 
    858 	glDrawBuffers( ARRAY_COUNT( bufs ), bufs );
    859 
    860 	ASSERT( glCheckFramebufferStatus( GL_FRAMEBUFFER ) == GL_FRAMEBUFFER_COMPLETE );
    861 	ASSERT( width > 0 && height > 0 );
    862 	glBindFramebuffer( GL_FRAMEBUFFER, 0 );
    863 
    864 	fb.width = width;
    865 	fb.height = height;
    866 
    867 	return fb;
    868 }
    869 
    870 FB renderer_new_fb( TextureConfig texture_config, FramebufferAttachment attachment ) {
    871 	FramebufferConfig config;
    872 	config.textures[ 0 ].config = texture_config;
    873 	config.textures[ 0 ].attachment = attachment;
    874 	return renderer_new_fb( config );
    875 }
    876 
    877 void renderer_delete_fb( FB fb ) {
    878 	if( fb.fbo == 0 )
    879 		return;
    880 
    881 	glDeleteFramebuffers( 1, &fb.fbo );
    882 	for( Texture texture : fb.textures ) {
    883 		if( texture != 0 ) {
    884 			glDeleteTextures( 1, &texture );
    885 		}
    886 	}
    887 }
    888 
    889 static GLuint new_gl_shader( GLenum type, array< const char * > srcs ) {
    890 	StaticArray< const char *, 16 > full_srcs;
    891 	full_srcs[ 0 ] = "#version 330\n";
    892 	full_srcs[ 1 ] = type == GL_VERTEX_SHADER ? "#define VERTEX_SHADER 1\n" : "#define FRAGMENT_SHADER 1\n";
    893 	GLsizei n = 2;
    894 	for( size_t i = 0; i < srcs.n; i++ ) {
    895 		if( srcs[ i ] != NULL ) {
    896 			full_srcs[ n ] = srcs[ i ];
    897 			n++;
    898 		}
    899 	}
    900 
    901 	GLuint shader = glCreateShader( type );
    902 	glShaderSource( shader, n, full_srcs.ptr(), NULL );
    903 	glCompileShader( shader );
    904 
    905 	GLint status;
    906 	glGetShaderiv( shader, GL_COMPILE_STATUS, &status );
    907 
    908 	if( status == GL_FALSE ) {
    909 		char buf[ 1024 ];
    910 		glGetShaderInfoLog( shader, sizeof( buf ), NULL, buf );
    911 		WARN( "shader compilation failed: {}", buf );
    912 		glDeleteShader( shader );
    913 
    914 		// static char src[ 65536 ];
    915 		// glGetShaderSource( shader, sizeof( src ), NULL, src );
    916 		// printf( "%s\n", src );
    917 
    918 		return 0;
    919 	}
    920 
    921 	return shader;
    922 }
    923 
    924 Shader renderer_new_shader( array< const char * > srcs ) {
    925 	Shader shader = { };
    926 
    927 	GLuint vs = new_gl_shader( GL_VERTEX_SHADER, srcs );
    928 	GLuint fs = new_gl_shader( GL_FRAGMENT_SHADER, srcs );
    929 
    930 	if( vs == 0 || fs == 0 ) {
    931 		return shader;
    932 	}
    933 
    934 	GLuint program = glCreateProgram();
    935 	glAttachShader( program, vs );
    936 	glAttachShader( program, fs );
    937 
    938 	glBindAttribLocation( program, ATTR_POSITION, "position" );
    939 	glBindAttribLocation( program, ATTR_NORMAL, "normal" );
    940 	glBindAttribLocation( program, ATTR_TEX_COORD0, "tex_coord0" );
    941 	glBindAttribLocation( program, ATTR_TEX_COORD1, "tex_coord1" );
    942 	glBindAttribLocation( program, ATTR_COLOUR, "colour" );
    943 	glBindAttribLocation( program, ATTR_JOINTS, "joints" );
    944 	glBindAttribLocation( program, ATTR_WEIGHTS, "weights" );
    945 	glBindAttribLocation( program, ATTR_MODEL_TO_WORLD_COL0, "model_to_world" );
    946 
    947 	glBindFragDataLocation( program, 0, "output_albedo" );
    948 	glBindFragDataLocation( program, 1, "output_normal" );
    949 
    950 	glLinkProgram( program );
    951 
    952 	glDeleteShader( vs );
    953 	glDeleteShader( fs );
    954 
    955 	GLint status;
    956 	glGetProgramiv( program, GL_LINK_STATUS, &status );
    957 	if( status == GL_FALSE ) {
    958 		char buf[ 1024 ];
    959 		glGetProgramInfoLog( program, sizeof( buf ), NULL, buf );
    960 		WARN( "shader linking failed: {}", buf );
    961 
    962 		return shader;
    963 	}
    964 
    965 
    966 	glUseProgram( program );
    967 	shader.program = program;
    968 
    969 	GLint count, maxlen;
    970 	glGetProgramiv( program, GL_ACTIVE_UNIFORMS, &count );
    971 	glGetProgramiv( program, GL_ACTIVE_UNIFORM_MAX_LENGTH, &maxlen );
    972 
    973 	size_t numtextures = 0;
    974 	for( GLint i = 0; i < count; i++ ) {
    975 		char name[ 128 ];
    976 		GLint size, len;
    977 		GLenum type;
    978 		glGetActiveUniform( program, i, sizeof( name ), &len, &size, &type, name );
    979 
    980 		if( type == GL_SAMPLER_2D ) {
    981 			glUniform1i( glGetUniformLocation( program, name ), numtextures );
    982 			shader.textures[ numtextures ] = fnv1a32( name, len );
    983 			numtextures++;
    984 		}
    985 	}
    986 
    987 	glGetProgramiv( program, GL_ACTIVE_UNIFORM_BLOCKS, &count );
    988 	glGetProgramiv( program, GL_ACTIVE_UNIFORM_BLOCK_MAX_NAME_LENGTH, &maxlen );
    989 
    990 	size_t numubos = 0;
    991 	for( GLint i = 0; i < count; i++ ) {
    992 		char name[ 128 ];
    993 		GLint len;
    994 		glGetActiveUniformBlockName( program, i, sizeof( name ), &len, name );
    995 		glUniformBlockBinding( program, i, numubos );
    996 		shader.uniforms[ numubos ] = fnv1a32( name, len );
    997 		numubos++;
    998 	}
    999 
   1000 	glUseProgram( 0 );
   1001 
   1002 	return shader;
   1003 }
   1004 
   1005 void renderer_delete_shader( Shader shader ) {
   1006 	glDeleteProgram( shader.program );
   1007 }
   1008 
   1009 static void vertexfmt_to_glenum( VertexFormat format, GLenum * type, int * components ) {
   1010 	switch( format ) {
   1011 		case VERTEXFMT_U8x3:
   1012 			*type = GL_UNSIGNED_BYTE;
   1013 			*components = 3;
   1014 			break;
   1015 		case VERTEXFMT_U8x4:
   1016 			*type = GL_UNSIGNED_BYTE;
   1017 			*components = 4;
   1018 			break;
   1019 
   1020 		case VERTEXFMT_U16x4:
   1021 			*type = GL_UNSIGNED_SHORT;
   1022 			*components = 4;
   1023 			break;
   1024 
   1025 		case VERTEXFMT_HALFx2:
   1026 			*type = GL_HALF_FLOAT;
   1027 			*components = 2;
   1028 			break;
   1029 		case VERTEXFMT_HALFx3:
   1030 			*type = GL_HALF_FLOAT;
   1031 			*components = 3;
   1032 			break;
   1033 		case VERTEXFMT_HALFx4:
   1034 			*type = GL_HALF_FLOAT;
   1035 			*components = 4;
   1036 			break;
   1037 
   1038 		case VERTEXFMT_FLOATx2:
   1039 			*type = GL_FLOAT;
   1040 			*components = 2;
   1041 			break;
   1042 		case VERTEXFMT_FLOATx3:
   1043 			*type = GL_FLOAT;
   1044 			*components = 3;
   1045 			break;
   1046 		case VERTEXFMT_FLOATx4:
   1047 			*type = GL_FLOAT;
   1048 			*components = 4;
   1049 			break;
   1050 	}
   1051 }
   1052 
   1053 static void setup_vertex_attrib( GLuint index, VertexFormat format, u32 stride = 0, u32 offset = 0 ) {
   1054 	GLvoid * gl_offset = checked_cast< GLvoid * >( checked_cast< uptr >( offset ) );
   1055 	GLenum type;
   1056 	int components;
   1057 	vertexfmt_to_glenum( format, &type, &components );
   1058 
   1059 	GLboolean normalized = index == ATTR_WEIGHTS;
   1060 
   1061 	glEnableVertexAttribArray( index );
   1062 	if( index == ATTR_JOINTS )
   1063 		glVertexAttribIPointer( index, components, type, stride, gl_offset );
   1064 	else
   1065 		glVertexAttribPointer( index, components, type, normalized, stride, gl_offset );
   1066 }
   1067 
   1068 Mesh renderer_new_mesh( MeshConfig config ) {
   1069 	ASSERT( config.num_vertices != 0 );
   1070 
   1071 	switch( config.primitive_type ) {
   1072 		case PRIMITIVETYPE_TRIANGLES:
   1073 			ASSERT( config.num_vertices % 3 == 0 );
   1074 			break;
   1075 		case PRIMITIVETYPE_TRIANGLE_STRIP:
   1076 			ASSERT( config.num_vertices >= 3 );
   1077 			break;
   1078 		case PRIMITIVETYPE_POINTS:
   1079 			break;
   1080 		case PRIMITIVETYPE_LINES:
   1081 			ASSERT( config.num_vertices % 2 == 0 );
   1082 			break;
   1083 	}
   1084 
   1085 	GLuint vao;
   1086 	glGenVertexArrays( 1, &vao );
   1087 	glBindVertexArray( vao );
   1088 
   1089 	if( config.unified_buffer == 0 ) {
   1090 		ASSERT( config.positions != 0 );
   1091 
   1092 		glBindBuffer( GL_ARRAY_BUFFER, config.positions );
   1093 		setup_vertex_attrib( ATTR_POSITION, config.positions_format );
   1094 
   1095 		if( config.normals != 0 ) {
   1096 			glBindBuffer( GL_ARRAY_BUFFER, config.normals );
   1097 			setup_vertex_attrib( ATTR_NORMAL, config.normals_format );
   1098 		}
   1099 
   1100 		if( config.tex_coords0 != 0 ) {
   1101 			glBindBuffer( GL_ARRAY_BUFFER, config.tex_coords0 );
   1102 			setup_vertex_attrib( ATTR_TEX_COORD0, config.tex_coords0_format );
   1103 		}
   1104 
   1105 		if( config.tex_coords1 != 0 ) {
   1106 			glBindBuffer( GL_ARRAY_BUFFER, config.tex_coords1 );
   1107 			setup_vertex_attrib( ATTR_TEX_COORD1, config.tex_coords1_format );
   1108 		}
   1109 
   1110 		if( config.colours != 0 ) {
   1111 			glBindBuffer( GL_ARRAY_BUFFER, config.colours );
   1112 			setup_vertex_attrib( ATTR_COLOUR, config.colours_format );
   1113 		}
   1114 
   1115 		if( config.joints != 0 ) {
   1116 			glBindBuffer( GL_ARRAY_BUFFER, config.joints );
   1117 			setup_vertex_attrib( ATTR_JOINTS, config.joints_format );
   1118 		}
   1119 
   1120 		if( config.weights != 0 ) {
   1121 			glBindBuffer( GL_ARRAY_BUFFER, config.weights );
   1122 			setup_vertex_attrib( ATTR_WEIGHTS, config.weights_format );
   1123 		}
   1124 	}
   1125 	else {
   1126 		ASSERT( config.stride != 0 );
   1127 
   1128 		glBindBuffer( GL_ARRAY_BUFFER, config.unified_buffer );
   1129 
   1130 		setup_vertex_attrib( ATTR_POSITION, config.positions_format, config.stride, config.positions_offset );
   1131 
   1132 		if( config.normals_offset != 0 ) {
   1133 			setup_vertex_attrib( ATTR_NORMAL, config.normals_format, config.stride, config.normals_offset );
   1134 		}
   1135 
   1136 		if( config.tex_coords0_offset != 0 ) {
   1137 			setup_vertex_attrib( ATTR_TEX_COORD0, config.tex_coords0_format, config.stride, config.tex_coords0_offset );
   1138 		}
   1139 
   1140 		if( config.tex_coords1_offset != 0 ) {
   1141 			setup_vertex_attrib( ATTR_TEX_COORD1, config.tex_coords1_format, config.stride, config.tex_coords1_offset );
   1142 		}
   1143 
   1144 		if( config.colours_offset != 0 ) {
   1145 			setup_vertex_attrib( ATTR_COLOUR, config.colours_format, config.stride, config.colours_offset );
   1146 		}
   1147 
   1148 		if( config.joints_offset != 0 ) {
   1149 			setup_vertex_attrib( ATTR_JOINTS, config.joints_format, config.stride, config.joints_offset );
   1150 		}
   1151 
   1152 		if( config.weights_offset != 0 ) {
   1153 			setup_vertex_attrib( ATTR_WEIGHTS, config.weights_format, config.stride, config.weights_offset );
   1154 		}
   1155 	}
   1156 
   1157 	if( config.indices != 0 ) {
   1158 		glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, config.indices );
   1159 	}
   1160 
   1161 	glBindVertexArray( 0 );
   1162 
   1163 	Mesh mesh = { };
   1164 	mesh.num_vertices = config.num_vertices;
   1165 	mesh.primitive_type = config.primitive_type;
   1166 	mesh.vao = vao;
   1167 	if( config.unified_buffer == 0 ) {
   1168 		mesh.positions = config.positions;
   1169 		mesh.normals = config.normals;
   1170 		mesh.tex_coords0 = config.tex_coords0;
   1171 		mesh.tex_coords1 = config.tex_coords1;
   1172 		mesh.colours = config.colours;
   1173 		mesh.joints = config.joints;
   1174 		mesh.weights = config.weights;
   1175 	}
   1176 	else {
   1177 		mesh.positions = config.unified_buffer;
   1178 	}
   1179 	mesh.indices = config.indices;
   1180 	mesh.indices_format = config.indices_format;
   1181 
   1182 	return mesh;
   1183 }
   1184 
   1185 void renderer_delete_mesh( const Mesh & mesh ) {
   1186 	DeleteCommand command;
   1187 	command.type = DELETE_MESH;
   1188 	command.mesh = mesh;
   1189 	deletes.add( command );
   1190 }
   1191 
   1192 void renderer_draw_mesh( const Mesh & mesh, const RenderState & render_state ) {
   1193 	ASSERT( in_frame );
   1194 	ASSERT( render_state.pass != U8_MAX );
   1195 	ASSERT( render_state.shader != NULL );
   1196 
   1197 	DrawCall dc;
   1198 	dc.mesh = mesh;
   1199 	dc.render_state = render_state;
   1200 	dc.num_instances = 0;
   1201 	dc.instance_data = 0;
   1202 	draw_calls.add( dc );
   1203 
   1204 	draw_calls_this_frame++;
   1205 	vertices_this_frame += mesh.num_vertices;
   1206 }
   1207 
   1208 void renderer_draw_instances( const Mesh & mesh, const RenderState & render_state, u32 num_instances, VB instance_data ) {
   1209 	ASSERT( in_frame );
   1210 
   1211 	if( num_instances == 0 ) {
   1212 		return;
   1213 	}
   1214 
   1215 	DrawCall dc;
   1216 	dc.mesh = mesh;
   1217 	dc.render_state = render_state;
   1218 	dc.num_instances = num_instances;
   1219 	dc.instance_data = instance_data;
   1220 	draw_calls.add( dc );
   1221 
   1222 	draw_calls_this_frame++;
   1223 	vertices_this_frame += mesh.num_vertices * num_instances;
   1224 }
   1225 
   1226 void renderer_draw_fullscreen_triangle( const RenderState & render_state ) {
   1227 	renderer_draw_mesh( fullscreen_triangle, render_state );
   1228 }