medfall

A super great game engine
Log | Files | Refs

renderer.cc (32617B)


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