medfall

A super great game engine
Log | Files | Refs

commit 9042f6a3b35143a27bc75ceba2fa49890153d803
parent 6262536eb1dd3d9c4d51a370bee2c30d2e1eb421
Author: Michael Savage <mikejsavage@gmail.com>
Date:   Wed Aug 30 18:07:33 +0100

WIP renderer overhaul

- Explicitly begin/end frames/render passes
- Specify what to clear in renderer_begin_pass
- Build a list of draw calls and defer submission until the end of the frame
- Map a single large UBO for the whole frame and use glBindBufferRange
- Update shadow map/BSP code

Diffstat:
bsp.cc | 15++++++++-------
main.cc | 2++
renderer.cc | 288++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------
renderer.h | 41++++++++++++++++++++++++++++++++++++++---
shadow_map.cc | 39++++++++++++++++-----------------------
text_renderer.cc | 8+-------
6 files changed, 314 insertions(+), 79 deletions(-)
diff --git a/bsp.cc b/bsp.cc @@ -247,8 +247,6 @@ BSP_Leaf & BSP::position_to_leaf( v3 pos ) const { return leaves[ -( node_idx + 1 ) ]; } -static UB ub_view; - GAME_INIT( game_init ) { bsp_init( &game->bsp, "acidwdm2.bsp" ); @@ -259,8 +257,6 @@ GAME_INIT( game_init ) { game->yaw = 0; bspr_init( &game->bspr, &arena, &game->bsp ); - - ub_view = renderer_new_ub(); } GAME_FRAME( game_frame ) { @@ -290,11 +286,13 @@ GAME_FRAME( game_frame ) { m4 V = m4_view( forward, right, up, game->pos ); renderer_begin_frame(); - renderer_ub_easy( ub_view, V, P ); + renderer_begin_pass( RENDERER_CLEAR_COLOUR_DONT, RENDERER_CLEAR_DEPTH_DO ); + + UniformBinding view_uniforms = renderer_uniforms( V, P ); RenderState render_state; render_state.shader = get_shader( SHADER_FLAT_VERTEX_COLOURS ); - render_state.ubs[ UB_VIEW ] = ub_view; + render_state.uniforms[ UB_VIEW ] = view_uniforms; bspr_render( &game->bspr, game->pos, render_state ); immediate_init( &imm, triangles, ARRAY_COUNT( triangles ) ); @@ -317,7 +315,7 @@ GAME_FRAME( game_frame ) { RenderState immediate_render_state; render_state.shader = get_shader( SHADER_FLAT_VERTEX_COLOURS ); - immediate_render_state.ubs[ UB_VIEW ] = ub_view; + immediate_render_state.uniforms[ UB_VIEW ] = view_uniforms; immediate_render( &imm, immediate_render_state ); char buf[ 256 ]; @@ -327,4 +325,7 @@ GAME_FRAME( game_frame ) { up.x, up.y, up.z ); draw_text( buf, 2, 2, 16 ); + + renderer_end_pass(); + renderer_end_frame(); } diff --git a/main.cc b/main.cc @@ -44,6 +44,7 @@ int main( int argc, char ** argv ) { profiler_init(); GLFWwindow * window = gl_init( WINDOW_GAME ); + renderer_init(); shaders_init(); text_renderer_init(); workqueue_init( &state.background_tasks, &mem.persistent_arena, 4 ); @@ -150,6 +151,7 @@ int main( int argc, char ** argv ) { workqueue_term( &state.background_tasks ); text_renderer_term(); shaders_term(); + renderer_term(); gl_term(); profiler_write_summary(); diff --git a/renderer.cc b/renderer.cc @@ -23,6 +23,230 @@ static const GLuint ATTR_TEX_COORD1 = 3; static const GLuint ATTR_COLOUR = 4; static const GLuint ATTR_MODEL_TO_WORLD_COL0 = 5; +static const u32 UNIFORM_BUFFER_SIZE = megabytes( 1 ); + +struct DrawCall { + RenderState render_state; + Mesh mesh; +}; + +enum DeleteCommandType { + DELETE_VB, + DELETE_IB, + DELETE_TB, + DELETE_TEXTURE, + DELETE_MESH, +}; + +struct DeleteCommand { + DeleteCommandType type; + union { + VB vb; + IB ib; + TB tb; + FB fb; + Shader shader; + Texture texture; + Mesh mesh; + }; +}; + +struct RenderPass { + size_t begin, one_past_end; + FB render_target; + GLbitfield clear_mask; +}; + +static DynamicArray< DrawCall > draw_calls; +static DynamicArray< RenderPass > render_passes; +static DynamicArray< DeleteCommand > deletes; + +static RenderPass current_render_pass; +static bool in_frame; +static bool in_pass; + +// double buffered UBO. TODO: maybe just orphan +static StaticArray< UB, 2 > uniforms; +static u32 uniforms_idx; +static u32 ubo_offset_alignment; +static u8 * uniforms_buffer; +static u32 uniforms_offset; + +static RenderState previous_render_state; +static u32 previous_viewport_width, previous_viewport_height; + +static void bind_fb( FB fb ) { + if( fb.fbo != previous_render_state.fb.fbo ) { + glBindFramebuffer( GL_DRAW_FRAMEBUFFER, fb.fbo ); + previous_render_state.fb = fb; + + u32 viewport_width, viewport_height; + if( fb.fbo == 0 ) { + v2u32 window_size = get_window_size(); + viewport_width = window_size.x; + viewport_height = window_size.y; + } + else { + viewport_width = fb.width; + viewport_height = fb.height; + } + + if( viewport_width != previous_viewport_width || viewport_height != previous_viewport_height ) { + previous_viewport_width = viewport_width; + previous_viewport_height = viewport_height; + glViewport( 0, 0, viewport_width, viewport_height ); + } + } +} + +void renderer_init() { + glGenBuffers( uniforms.size(), uniforms.ptr() ); + for( UB ub : uniforms ) { + glBindBuffer( GL_UNIFORM_BUFFER, ub ); + glBufferData( GL_UNIFORM_BUFFER, UNIFORM_BUFFER_SIZE, NULL, GL_DYNAMIC_DRAW ); + } + + uniforms_idx = 0; + in_frame = false; + in_pass = false; + + GLint alignment; + glGetIntegerv( GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT, &alignment ); + ubo_offset_alignment = checked_cast< u32 >( alignment ); +} + +void renderer_term() { + glDeleteBuffers( uniforms.size(), uniforms.ptr() ); +} + +void renderer_begin_frame() { + ASSERT( !in_frame ); + in_frame = true; + + draw_calls.clear(); + deletes.clear(); + render_passes.clear(); + + glBindBuffer( GL_UNIFORM_BUFFER, uniforms[ uniforms_idx ] ); + uniforms_buffer = ( u8 * ) glMapBuffer( GL_UNIFORM_BUFFER, GL_WRITE_ONLY ); + ASSERT( uniforms_buffer != NULL ); + uniforms_offset = 0; +} + +static void renderer_draw_mesh_( const Mesh & mesh, const RenderState & state ); + +void renderer_end_frame() { + ASSERT( !in_pass ); + ASSERT( in_frame ); + in_frame = false; + + glBindBuffer( GL_UNIFORM_BUFFER, uniforms[ uniforms_idx ] ); // TODO: not necessary + glUnmapBuffer( GL_UNIFORM_BUFFER ); + uniforms_buffer = NULL; + uniforms_idx = ( uniforms_idx + 1 ) % uniforms.size(); + + for( const RenderPass & pass : render_passes ) { + bind_fb( pass.render_target ); + + if( pass.clear_mask != 0 ) { + glClear( pass.clear_mask ); + } + + // TODO: sort draw calls + + for( const DrawCall & dc : draw_calls.slice( pass.begin, pass.one_past_end ) ) { + renderer_draw_mesh_( dc.mesh, dc.render_state ); + } + } + + for( const DeleteCommand & del : deletes ) { + switch( del.type ) { + case DELETE_VB: + glDeleteBuffers( 1, &del.vb ); + break; + + case DELETE_IB: + glDeleteBuffers( 1, &del.ib ); + break; + + case DELETE_TB: + glDeleteBuffers( 1, &del.tb.tbo ); + glDeleteTextures( 1, &del.tb.texture ); + break; + + case DELETE_TEXTURE: + glDeleteTextures( 1, &del.texture ); + break; + + case DELETE_MESH: + if( del.mesh.positions != 0 ) + renderer_delete_vb( del.mesh.positions ); + if( del.mesh.normals != 0 ) + renderer_delete_vb( del.mesh.normals ); + if( del.mesh.tex_coords0 != 0 ) + renderer_delete_vb( del.mesh.tex_coords0 ); + if( del.mesh.tex_coords1 != 0 ) + renderer_delete_vb( del.mesh.tex_coords1 ); + if( del.mesh.colours != 0 ) + renderer_delete_vb( del.mesh.colours ); + if( del.mesh.indices != 0 ) + renderer_delete_ib( del.mesh.indices ); + + glDeleteVertexArrays( 1, &del.mesh.vao ); + break; + } + } +} + +UniformBinding renderer_ub_data( const void * data, u32 size, u32 alignment ) { + alignment = max( alignment, ubo_offset_alignment ); + + UniformBinding binding; + binding.offset = align_power_of_2( uniforms_offset, alignment ); + binding.size = size; + + u32 new_uniforms_offset = binding.offset + binding.size; + ASSERT( new_uniforms_offset < UNIFORM_BUFFER_SIZE ); + + // memset so we don't leave any gaps. good for write combined memory! + memset( uniforms_buffer + uniforms_offset, 0, binding.offset - uniforms_offset ); + memcpy( uniforms_buffer + binding.offset, data, size ); + + uniforms_offset = new_uniforms_offset; + + return binding; +} + +void renderer_begin_pass( FB render_target, ClearColourBool clear_colour, ClearDepthBool clear_depth ) { + ASSERT( !in_pass ); + in_pass = true; + + GLbitfield clear_mask = 0; + if( clear_colour == RENDERER_CLEAR_COLOUR_DO ) { + clear_mask |= GL_COLOR_BUFFER_BIT; + } + if( clear_depth == RENDERER_CLEAR_DEPTH_DO ) { + clear_mask |= GL_DEPTH_BUFFER_BIT; + } + + current_render_pass.begin = draw_calls.size(); + current_render_pass.render_target = render_target; + current_render_pass.clear_mask = clear_mask; +} + +void renderer_begin_pass( ClearColourBool clear_colour, ClearDepthBool clear_depth ) { + FB render_target = { }; + renderer_begin_pass( render_target, clear_colour, clear_depth ); +} + +void renderer_end_pass() { + ASSERT( in_pass ); + in_pass = false; + + current_render_pass.one_past_end = draw_calls.size(); + render_passes.append( current_render_pass ); +} + void renderer_begin_frame( ClearColourBool clear_colour, ClearDepthBool clear_depth ) { GLbitfield clear_mask = 0; if( clear_colour == RENDERER_CLEAR_COLOUR_DO ) { @@ -598,14 +822,10 @@ Mesh renderer_new_mesh( MeshConfig config ) { } void renderer_delete_mesh( const Mesh & mesh ) { - if( mesh.positions ) renderer_delete_vb( mesh.positions ); - if( mesh.normals ) renderer_delete_vb( mesh.normals ); - if( mesh.tex_coords0 ) renderer_delete_vb( mesh.tex_coords0 ); - if( mesh.tex_coords1 ) renderer_delete_vb( mesh.tex_coords1 ); - if( mesh.colours ) renderer_delete_vb( mesh.colours ); - if( mesh.indices ) renderer_delete_ib( mesh.indices ); - - glDeleteVertexArrays( 1, &mesh.vao ); + DeleteCommand command; + command.type = DELETE_MESH; + command.mesh = mesh; + deletes.append( command ); } static GLenum depthfunc_to_glenum( DepthFunc depth_func ) { @@ -632,47 +852,30 @@ static GLenum primitivetype_to_glenum( PrimitiveType primitive_type ) { return GL_INVALID_ENUM; } -static RenderState previous_render_state; -static u32 previous_viewport_width, previous_viewport_height; - -static void bind_fb( FB fb ) { - if( fb.fbo != previous_render_state.fb.fbo ) { - glBindFramebuffer( GL_DRAW_FRAMEBUFFER, fb.fbo ); - previous_render_state.fb = fb; - - u32 viewport_width, viewport_height; - if( fb.fbo == 0 ) { - v2u32 window_size = get_window_size(); - viewport_width = window_size.x; - viewport_height = window_size.y; - } - else { - viewport_width = fb.width; - viewport_height = fb.height; - } - - if( viewport_width != previous_viewport_width || viewport_height != previous_viewport_height ) { - previous_viewport_width = viewport_width; - previous_viewport_height = viewport_height; - glViewport( 0, 0, viewport_width, viewport_height ); - } - } -} - void renderer_clear_fb( FB fb ) { bind_fb( fb ); glClear( fb.attachment == FB_COLOUR ? GL_COLOR_BUFFER_BIT : GL_DEPTH_BUFFER_BIT ); } +static bool operator!=( UniformBinding a, UniformBinding b ) { + return a.offset != b.offset || a.size != b.size; +} + static void set_render_state( const RenderState & state ) { if( state.shader != previous_render_state.shader ) { glUseProgram( state.shader ); } // uniforms - for( GLuint i = 0; i < RENDERER_MAX_UBS; i++ ) { - if( state.ubs[ i ] != previous_render_state.ubs[ i ] ) { - glBindBufferBase( GL_UNIFORM_BUFFER, i, state.ubs[ i ] ); + for( GLuint i = 0; i < RENDERER_MAX_UNIFORMS; i++ ) { + if( state.uniforms[ i ] != previous_render_state.uniforms[ i ] ) { + UniformBinding binding = state.uniforms[ i ]; + if( binding.size == 0 ) { + glBindBufferBase( GL_UNIFORM_BUFFER, i, 0 ); + } + else { + glBindBufferRange( GL_UNIFORM_BUFFER, i, uniforms[ uniforms_idx ], binding.offset, binding.size ); + } } } @@ -754,7 +957,14 @@ static void set_render_state( const RenderState & state ) { previous_render_state = state; } -void renderer_draw_mesh( const Mesh & mesh, const RenderState & state ) { +void renderer_draw_mesh( const Mesh & mesh, const RenderState & render_state ) { + DrawCall dc; + dc.mesh = mesh; + dc.render_state = render_state; + draw_calls.append( dc ); +} + +static void renderer_draw_mesh_( const Mesh & mesh, const RenderState & state ) { set_render_state( state ); glBindVertexArray( mesh.vao ); diff --git a/renderer.h b/renderer.h @@ -25,7 +25,7 @@ const u32 UB_SUN = 5; #define RENDERER_MAX_TEXTURES 4 #define RENDERER_MAX_TEXTURE_BUFFERS 4 -#define RENDERER_MAX_UBS 8 +#define RENDERER_MAX_UNIFORMS 8 const u32 INVALID_SHADER = 0; @@ -100,8 +100,13 @@ struct FB { u32 width, height; }; +struct UniformBinding { + u32 offset; + u32 size; +}; + struct RenderState { - StaticArray< UB, RENDERER_MAX_UBS > ubs = { }; + StaticArray< UniformBinding, RENDERER_MAX_UNIFORMS > uniforms = { }; StaticArray< Texture, RENDERER_MAX_TEXTURES > textures = { }; StaticArray< TB, RENDERER_MAX_TEXTURE_BUFFERS > tbs = { }; FB fb = { }; @@ -183,7 +188,16 @@ struct TextureConfig { enum ClearColourBool { RENDERER_CLEAR_COLOUR_DONT, RENDERER_CLEAR_COLOUR_DO }; enum ClearDepthBool { RENDERER_CLEAR_DEPTH_DONT, RENDERER_CLEAR_DEPTH_DO }; -void renderer_begin_frame( ClearColourBool clear_colour = RENDERER_CLEAR_COLOUR_DO, ClearDepthBool clear_depth = RENDERER_CLEAR_DEPTH_DO ); +void renderer_init(); +void renderer_term(); + +void renderer_begin_frame(); +void renderer_begin_pass( FB render_target, ClearColourBool clear_colour, ClearDepthBool clear_depth ); +void renderer_begin_pass( ClearColourBool clear_colour, ClearDepthBool clear_depth ); +void renderer_end_pass(); +void renderer_end_frame(); + +// void renderer_begin_frame( ClearColourBool clear_colour = RENDERER_CLEAR_COLOUR_DO, ClearDepthBool clear_depth = RENDERER_CLEAR_DEPTH_DO ); // TODO: define render passes that have clear/undefined input framebuffers #define renderer_clear renderer_begin_frame @@ -195,6 +209,7 @@ void renderer_delete_ib( IB ib ); UB renderer_new_ub( const void * data = NULL, u32 len = 0 ); void renderer_ub_data( UB ub, const void * data, u32 len ); +UniformBinding renderer_ub_data( const void * data, u32 size, u32 alignment ); void renderer_delete_ub( UB ub ); TB renderer_new_tb( TextureFormat format, const void * data = NULL, u32 len = 0, BufferUsage usage = BUFFERUSAGE_DYNAMIC ); @@ -246,6 +261,16 @@ constexpr size_t renderer_ubo_alignment< v3 >() { } template< typename T > +constexpr size_t renderer_ub_alignment() { + return renderer_ubo_alignment< T >(); +} + +template< typename S, typename T, typename... Rest > +constexpr size_t renderer_ub_alignment() { + return max( renderer_ub_alignment< T, Rest... >(), renderer_ubo_alignment< S >() ); +} + +template< typename T > constexpr size_t renderer_ub_size( size_t size ) { return sizeof( T ) + align_power_of_2( size, renderer_ubo_alignment< T >() ); } @@ -272,3 +297,13 @@ inline void renderer_ub_easy( UB ub, Rest... rest ) { renderer_ub_easy_helper( buf, 0, rest... ); renderer_ub_data( ub, buf, sizeof( buf ) ); } + +template< typename... Rest > +inline UniformBinding renderer_uniforms( Rest... rest ) { + constexpr size_t buf_size = renderer_ub_size< Rest... >( 0 ); + constexpr u32 alignment = renderer_ub_alignment< Rest... >(); + char buf[ buf_size ]; + memset( buf, 0, sizeof( buf ) ); + renderer_ub_easy_helper( buf, 0, rest... ); + return renderer_ub_data( buf, sizeof( buf ), alignment ); +} diff --git a/shadow_map.cc b/shadow_map.cc @@ -14,10 +14,6 @@ static ImmediateTriangle triangles[ 512000 ]; static ImmediateContext imm; static Mesh square; -static UB ub_model; -static UB ub_light_view; -static UB ub_view; -static UB ub_light; static FB shadow_fb; static Mesh tree_mesh; static Texture blue_noise; @@ -31,7 +27,7 @@ static void draw_scene( RenderState render_state, bool draw_light = false ) { immediate_init( &imm, triangles, ARRAY_COUNT( triangles ) ); m4 I = m4_identity(); - renderer_ub_easy( render_state.ubs[ UB_MODEL ], I ); + render_state.uniforms[ UB_MODEL ] = renderer_uniforms( I ); immediate_sphere( &imm, v3( 0, 0, 2 ), 2, v4( 1, 0, 0, 1 ) ); immediate_sphere( &imm, v3( -3, 7, 2 ), 2, v4( 0, 1, 0, 1 ) ); @@ -56,18 +52,12 @@ static void draw_scene( RenderState render_state, bool draw_light = false ) { immediate_render( &imm, render_state ); m4 M = m4_translation( v3( -7, -2, 0 ) ) * m4_rotx( deg_to_rad( 90 ) ); - renderer_ub_easy( render_state.ubs[ UB_MODEL ], M ); - + render_state.uniforms[ UB_MODEL ] = renderer_uniforms( M ); render_state.cull_face = CULLFACE_FRONT; renderer_draw_mesh( tree_mesh, render_state ); } GAME_INIT( game_init ) { - ub_model = renderer_new_ub(); - ub_light_view = renderer_new_ub(); - ub_view = renderer_new_ub(); - ub_light = renderer_new_ub(); - { TextureConfig config; config.width = SHADOW_SIZE; @@ -133,7 +123,7 @@ GAME_FRAME( game_frame ) { renderer_begin_frame(); - renderer_ub_easy( ub_view, V, P, game->pos ); + UniformBinding light_view_uniforms; { m4 lightVP; @@ -142,22 +132,25 @@ GAME_FRAME( game_frame ) { const m4 lightP = m4_perspective( 90, 1, NEAR_PLANE_DEPTH, 500.0f ); m4 lightV = m4_lookat( light_pos, target, world_up ); - renderer_ub_easy( ub_light_view, lightV, lightP ); + light_view_uniforms = renderer_uniforms( lightV, lightP ); } // fill shadow map { - renderer_clear_fb( shadow_fb ); + renderer_begin_pass( shadow_fb, RENDERER_CLEAR_COLOUR_DONT, RENDERER_CLEAR_DEPTH_DO ); RenderState render_state; render_state.shader = get_shader( SHADER_WRITE_SHADOW_MAP ); - render_state.ubs[ UB_MODEL ] = ub_model; - render_state.ubs[ UB_LIGHT_VIEW ] = ub_light_view; + render_state.uniforms[ UB_LIGHT_VIEW ] = light_view_uniforms; render_state.fb = shadow_fb; draw_scene( render_state ); + + renderer_end_pass(); } + renderer_begin_pass( RENDERER_CLEAR_COLOUR_DO, RENDERER_CLEAR_DEPTH_DO ); + // render shadow map { RenderState state; @@ -168,14 +161,11 @@ GAME_FRAME( game_frame ) { // draw world { - renderer_ub_easy( ub_light, light_pos ); - RenderState render_state; render_state.shader = get_shader( SHADER_SHADOWED_VERTEX_COLOURS ); - render_state.ubs[ UB_VS_COLD ] = ub_light; - render_state.ubs[ UB_MODEL ] = ub_model; - render_state.ubs[ UB_VIEW ] = ub_view; - render_state.ubs[ UB_LIGHT_VIEW ] = ub_light_view; + render_state.uniforms[ UB_VS_COLD ] = renderer_uniforms( light_pos ); + render_state.uniforms[ UB_VIEW ] = renderer_uniforms( V, P, game->pos ); + render_state.uniforms[ UB_LIGHT_VIEW ] = light_view_uniforms; render_state.textures[ 0 ] = shadow_fb.texture; render_state.textures[ 1 ] = blue_noise; @@ -186,4 +176,7 @@ GAME_FRAME( game_frame ) { const str< 128 > status( "Frame time: {.1}ms FPS: {.1}", dt * 1000.0f, 1.0f / dt ); draw_text( status.c_str(), 2.0f, 2.0f, 16.0f ); } + + renderer_end_pass(); + renderer_end_frame(); } diff --git a/text_renderer.cc b/text_renderer.cc @@ -21,7 +21,6 @@ static const float pixel_sizes[] = { 16.0f, 32.0f, 48.0f }; static FontSize sizes[ ARRAY_COUNT( pixel_sizes ) ]; static ImmediateTriangle triangles[ 4096 ]; -static UB ub_window; void text_renderer_init() { ttf = file_get_contents( "LiberationSans-Regular.ttf" ); @@ -57,8 +56,6 @@ void text_renderer_init() { sizes[ i ].ascent = ascent * scale; sizes[ i ].atlas = renderer_new_texture( texture_config ); } - - ub_window = renderer_new_ub(); } void draw_text( const char * str, int x, int y, float pixel_size ) { @@ -121,13 +118,11 @@ void draw_text( const char * str, int x, int y, float pixel_size ) { RenderState render_state; render_state.shader = get_shader( SHADER_TEXT ); render_state.textures[ 0 ] = sizes[ size_idx ].atlas; - render_state.ubs[ UB_WINDOW ] = ub_window; + render_state.uniforms[ UB_WINDOW ] = renderer_uniforms( get_window_size() ); render_state.depth_func = DEPTHFUNC_DISABLED; render_state.enable_alpha_blending = true; // render_state.disable_depth_writes = true; - renderer_ub_easy( ub_window, get_window_size() ); - immediate_render( &imm, render_state ); } @@ -136,5 +131,4 @@ void text_renderer_term() { for( size_t i = 0; i < ARRAY_COUNT( sizes ); i++ ) { renderer_delete_texture( sizes[ i ].atlas ); } - renderer_delete_ub( ub_window ); }