medfall

A super great game engine
Log | Files | Refs

commit 4025cf32a4e4d65d1235bf987c005c447537343f
parent b82bc4149f4e6bfa70ae20f9ddef928fdf44d904
Author: Michael Savage <mikejsavage@gmail.com>
Date:   Mon,  5 Aug 2019 20:28:32 +0300

Add multisample framebuffers and MSAA resolve pass

Diffstat:
Mclipmap.cc | 40+++++++++++++++++++++++++++++++++++++---
Mrenderer.cc | 383++++++++++++++++++++++++++++++++++++++++++-------------------------------------
Mrenderer.h | 10+++++++---
3 files changed, 246 insertions(+), 187 deletions(-)

diff --git a/clipmap.cc b/clipmap.cc @@ -56,6 +56,8 @@ struct Player { v3 pos; }; +static FB msaa_fb; + static UDPSocket sock; static NetAddress server_addr; static u64 sid; @@ -123,6 +125,8 @@ struct RGBA { GAME_INIT( game_init ) { net_init(); + msaa_fb = { }; + // load quadtree { clipmap.heightmap = alloc_array2d< u16 >( &mem->persistent_arena, 4096, 4096 ); @@ -561,7 +565,36 @@ static bool intervals_overlap( float a0, float a1, float b0, float b1 ) { return a0 <= b1 && b0 <= a1; } +static void recreate_framebuffers() { + renderer_delete_fb( msaa_fb ); + + v2u32 window_size = get_window_size(); + + TextureConfig texture_config; + texture_config.width = window_size.x; + texture_config.height = window_size.y; + texture_config.wrap = TEXWRAP_CLAMP; + + FramebufferConfig fb_config; + + texture_config.format = TEXFMT_RGB_U8_SRGB; + fb_config.textures[ OUTPUT_ALBEDO ].config = texture_config; + fb_config.textures[ OUTPUT_ALBEDO ].attachment = FB_COLOUR; + + texture_config.format = TEXFMT_DEPTH; + fb_config.textures[ OUTPUT_DEPTH ].config = texture_config; + fb_config.textures[ OUTPUT_DEPTH ].attachment = FB_DEPTH; + + fb_config.msaa_samples = 0; + + msaa_fb = renderer_new_fb( fb_config ); +} + GAME_FRAME( game_frame ) { + if( input->resized ) { + recreate_framebuffers(); + } + float fb = float( input->keys[ KEY_W ] - input->keys[ KEY_S ] ); float lr = float( input->keys[ KEY_D ] - input->keys[ KEY_A ] ); float dz = float( input->keys[ KEY_SPACE ] - input->keys[ KEY_LEFTSHIFT ] ); @@ -756,9 +789,10 @@ GAME_FRAME( game_frame ) { renderer_begin_frame(); - u8 world_pass = renderer_add_pass( "Render world", RENDERER_CLEAR_COLOUR_DO, RENDERER_CLEAR_DEPTH_DO ); - u8 ents_pass = renderer_add_pass( "Render entities" ); - u8 sky_pass = renderer_add_pass( "Render sky" ); + u8 world_pass = renderer_add_pass( "Render world", msaa_fb, RENDERER_CLEAR_COLOUR_DO, RENDERER_CLEAR_DEPTH_DO ); + u8 ents_pass = renderer_add_pass( "Render entities", msaa_fb ); + u8 sky_pass = renderer_add_pass( "Render sky", msaa_fb ); + renderer_resolve_msaa( msaa_fb ); u8 ui_pass = renderer_add_pass( "Render UI" ); UniformBinding view_uniforms = renderer_uniforms( V, P, game->pos ); diff --git a/renderer.cc b/renderer.cc @@ -377,7 +377,14 @@ void renderer_end_frame() { pass_idx = dc.render_state.pass; } - if( dc.num_instances == 0 ) { + if( dc.render_state.shader == NULL ) { + FB fb = render_passes[ dc.render_state.pass ].msaa_source; + v2u32 window_size = get_window_size(); + ASSERT( fb.width == window_size.x && fb.height == window_size.y ); + glBindFramebuffer( GL_READ_FRAMEBUFFER, fb.fbo ); + glBlitFramebuffer( 0, 0, fb.width, fb.height, 0, 0, fb.width, fb.height, GL_COLOR_BUFFER_BIT, GL_NEAREST ); + } + else if( dc.num_instances == 0 ) { drawcall_single( dc.mesh, dc.render_state ); } else { @@ -444,6 +451,20 @@ u8 renderer_add_pass( const char * name, ClearColourBool clear_colour, ClearDept return renderer_add_pass( name, target, clear_colour, clear_depth ); } +void renderer_resolve_msaa( FB fb ) { + RenderPass pass; + pass.name = "Resolve MSAA"; + pass.msaa_source = fb; + + RenderState dummy; + dummy.pass = renderer_add_pass( pass ); + dummy.shader = NULL; + + DrawCall dc; + dc.render_state = dummy; + draw_calls.add( dc ); +} + u32 renderer_num_draw_calls() { return draw_calls_this_frame; } @@ -617,6 +638,178 @@ void renderer_delete_ib( IB ib ) { glDeleteBuffers( 1, &ib ); } +static u32 mipmap_dim( u32 dim, u32 level ) { + return max( dim >> level, u32( 1 ) ); +} + +// TODO: use clz +static u32 mipmap_levels( u32 width, u32 height ) { + u32 levels = 1; + u32 dim = max( width, height ); + while( dim > 1 ) { + dim >>= 1; + levels++; + } + return levels; +} + +static u32 texture_byte_size( u32 width, u32 height, TextureFormat format, bool mipmaps ) { + u32 total_dim = width * height; + if( mipmaps ) { + for( u32 i = 1; i < mipmap_levels( width, height ); i++ ) { + total_dim += mipmap_dim( width, i ) * mipmap_dim( height, i ); + } + } + + switch( format ) { + case TEXFMT_INVALID: + case TEXFMT_DEPTH: + break; + + case TEXFMT_R_U8: + case TEXFMT_R_U8NORM: + return total_dim * 1 * sizeof( u8 ); + case TEXFMT_R_U16: + return total_dim * 1 * sizeof( u16 ); + case TEXFMT_R_FLOAT: + return total_dim * 1 * sizeof( float ); + + case TEXFMT_RGB_U8: + return total_dim * 3 * sizeof( u8 ); + case TEXFMT_RGB_U8_SRGB: + return total_dim * 3 * sizeof( u8 ); + case TEXFMT_RGB_HALF: + return total_dim * 3 * sizeof( u16 ); + case TEXFMT_RGB_FLOAT: + return total_dim * 3 * sizeof( float ); + + case TEXFMT_RGBA_U8: + return total_dim * 4 * sizeof( u8 ); + case TEXFMT_RGBA_U8_SRGB: + return total_dim * 4 * sizeof( u8 ); + case TEXFMT_RGBA_FLOAT: + return total_dim * 4 * sizeof( float ); + + case TEXFMT_BC1: + case TEXFMT_BC1_SRGB: + return total_dim / 2; + case TEXFMT_BC4: + return total_dim / 2; + case TEXFMT_BC5: + return total_dim; + } + + FATAL( "unsupported TextureFormat: {}", format ); + return GL_INVALID_ENUM; +} + +static Texture new_texture( TextureConfig config, int msaa_samples ) { + ASSERT( config.format != TEXFMT_INVALID ); + ASSERT( !is_compressed( config.format ) || msaa_samples == 0 ); + ASSERT( !config.has_mipmaps ); // TODO + + GLenum target = msaa_samples == 0 ? GL_TEXTURE_2D : GL_TEXTURE_2D_MULTISAMPLE; + + GLuint texture; + glGenTextures( 1, &texture ); + // TODO: should probably update the gl state since we bind a new texture + glBindTexture( target, texture ); + + if( msaa_samples == 0 ) { + glTexParameteri( target, GL_TEXTURE_WRAP_S, texturewrap_to_glenum( config.wrap ) ); + glTexParameteri( target, GL_TEXTURE_WRAP_T, texturewrap_to_glenum( config.wrap ) ); + + GLenum min_filter, mag_filter; + texturefilter_to_glenum( config.filter, config.has_mipmaps, &min_filter, &mag_filter ); + glTexParameteri( target, GL_TEXTURE_MIN_FILTER, min_filter ); + glTexParameteri( target, GL_TEXTURE_MAG_FILTER, mag_filter ); + + if( config.wrap == TEXWRAP_BORDER ) { + glTexParameterfv( target, GL_TEXTURE_BORDER_COLOR, ( GLfloat * ) &config.border_colour ); + } + } + + GLenum internal_format = texfmt_to_internal_format( config.format ); + + if( is_compressed( config.format ) ) { + u32 dxt_size = texture_byte_size( config.width, config.height, config.format, false ); + glCompressedTexImage2D( GL_TEXTURE_2D, 0, internal_format, + config.width, config.height, 0, dxt_size, config.data ); + } + else { + GLenum channels = GL_INVALID_ENUM; + GLenum type = GL_INVALID_ENUM; + + switch( config.format ) { + case TEXFMT_R_U8: + case TEXFMT_R_U8NORM: + channels = GL_RED; + type = GL_UNSIGNED_BYTE; + break; + case TEXFMT_R_U16: + channels = GL_RED; + type = GL_UNSIGNED_SHORT; + break; + case TEXFMT_R_FLOAT: + channels = GL_RED; + type = GL_FLOAT; + break; + + case TEXFMT_RGB_U8: + case TEXFMT_RGB_U8_SRGB: + channels = GL_RGB; + type = GL_UNSIGNED_BYTE; + break; + case TEXFMT_RGB_HALF: + channels = GL_RGB; + type = GL_HALF_FLOAT; + break; + case TEXFMT_RGB_FLOAT: + channels = GL_RGB; + type = GL_FLOAT; + break; + + case TEXFMT_RGBA_U8: + case TEXFMT_RGBA_U8_SRGB: + channels = GL_RGBA; + type = GL_UNSIGNED_BYTE; + break; + case TEXFMT_RGBA_FLOAT: + channels = GL_RGBA; + type = GL_FLOAT; + break; + + case TEXFMT_DEPTH: + channels = GL_DEPTH_COMPONENT; + type = GL_FLOAT; + break; + + default: + FATAL( "implement channel/type selection for {}", config.format ); + break; + } + + if( msaa_samples == 0 ) { + glTexImage2D( GL_TEXTURE_2D, 0, internal_format, + config.width, config.height, 0, channels, type, config.data ); + } + else { + glTexImage2DMultisample( GL_TEXTURE_2D_MULTISAMPLE, msaa_samples, + internal_format, config.width, config.height, GL_TRUE ); + } + } + + return texture; +} + +Texture renderer_new_texture( const TextureConfig & config ) { + return new_texture( config, 0 ); +} + +void renderer_delete_texture( Texture texture ) { + glDeleteTextures( 1, &texture ); +} + static GLenum framebufferattachment_to_glenum( FramebufferAttachment attachment ) { switch( attachment ) { case FB_COLOUR: return GL_COLOR_ATTACHMENT0; @@ -650,8 +843,10 @@ FB renderer_new_fb( const FramebufferConfig & config ) { ASSERT( !is_compressed( texture_config.format ) ); GLenum attachment = framebufferattachment_to_glenum( config.textures[ i ].attachment ); - Texture texture = renderer_new_texture( texture_config ); - glFramebufferTexture2D( GL_FRAMEBUFFER, attachment, GL_TEXTURE_2D, texture, 0 ); + Texture texture = new_texture( texture_config, config.msaa_samples ); + + GLenum target = config.msaa_samples == 0 ? GL_TEXTURE_2D : GL_TEXTURE_2D_MULTISAMPLE; + glFramebufferTexture2D( GL_FRAMEBUFFER, attachment, target, texture, 0 ); width = texture_config.width; height = texture_config.height; @@ -673,28 +868,10 @@ FB renderer_new_fb( const FramebufferConfig & config ) { } FB renderer_new_fb( TextureConfig texture_config, FramebufferAttachment attachment ) { - ASSERT( !is_compressed( texture_config.format ) ); - - Texture texture = renderer_new_texture( texture_config ); - GLuint fbo; - - glGenFramebuffers( 1, &fbo ); - glBindFramebuffer( GL_FRAMEBUFFER, fbo ); - glFramebufferTexture2D( GL_FRAMEBUFFER, framebufferattachment_to_glenum( attachment ), GL_TEXTURE_2D, texture, 0 ); - - if( attachment == FB_DEPTH ) { - glDrawBuffer( GL_NONE ); - } - - ASSERT( glCheckFramebufferStatus( GL_FRAMEBUFFER ) == GL_FRAMEBUFFER_COMPLETE ); - glBindFramebuffer( GL_FRAMEBUFFER, 0 ); - - FB fb; - fb.fbo = fbo; - fb.textures[ attachment == FB_COLOUR ? OUTPUT_ALBEDO : OUTPUT_DEPTH ] = texture; - fb.width = texture_config.width; - fb.height = texture_config.height; - return fb; + FramebufferConfig config; + config.textures[ 0 ].config = texture_config; + config.textures[ 0 ].attachment = attachment; + return renderer_new_fb( config ); } void renderer_delete_fb( FB fb ) { @@ -829,162 +1006,6 @@ void renderer_delete_shader( Shader shader ) { glDeleteProgram( shader.program ); } -static u32 mipmap_dim( u32 dim, u32 level ) { - return max( dim >> level, u32( 1 ) ); -} - -// TODO: use clz -static u32 mipmap_levels( u32 width, u32 height ) { - u32 levels = 1; - u32 dim = max( width, height ); - while( dim > 1 ) { - dim >>= 1; - levels++; - } - return levels; -} - -static u32 texture_byte_size( u32 width, u32 height, TextureFormat format, bool mipmaps ) { - u32 total_dim = width * height; - if( mipmaps ) { - for( u32 i = 1; i < mipmap_levels( width, height ); i++ ) { - total_dim += mipmap_dim( width, i ) * mipmap_dim( height, i ); - } - } - - switch( format ) { - case TEXFMT_INVALID: - case TEXFMT_DEPTH: - break; - - case TEXFMT_R_U8: - case TEXFMT_R_U8NORM: - return total_dim * 1 * sizeof( u8 ); - case TEXFMT_R_U16: - return total_dim * 1 * sizeof( u16 ); - case TEXFMT_R_FLOAT: - return total_dim * 1 * sizeof( float ); - - case TEXFMT_RGB_U8: - return total_dim * 3 * sizeof( u8 ); - case TEXFMT_RGB_U8_SRGB: - return total_dim * 3 * sizeof( u8 ); - case TEXFMT_RGB_HALF: - return total_dim * 3 * sizeof( u16 ); - case TEXFMT_RGB_FLOAT: - return total_dim * 3 * sizeof( float ); - - case TEXFMT_RGBA_U8: - return total_dim * 4 * sizeof( u8 ); - case TEXFMT_RGBA_U8_SRGB: - return total_dim * 4 * sizeof( u8 ); - case TEXFMT_RGBA_FLOAT: - return total_dim * 4 * sizeof( float ); - - case TEXFMT_BC1: - case TEXFMT_BC1_SRGB: - return total_dim / 2; - case TEXFMT_BC4: - return total_dim / 2; - case TEXFMT_BC5: - return total_dim; - } - - FATAL( "unsupported TextureFormat: {}", format ); - return GL_INVALID_ENUM; -} - -Texture renderer_new_texture( TextureConfig config ) { - ASSERT( config.format != TEXFMT_INVALID ); - ASSERT( !config.has_mipmaps ); // TODO - - GLuint texture; - glGenTextures( 1, &texture ); - // TODO: should probably update the gl state since we bind a new texture - glBindTexture( GL_TEXTURE_2D, texture ); - glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, texturewrap_to_glenum( config.wrap ) ); - glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, texturewrap_to_glenum( config.wrap ) ); - - GLenum min_filter, mag_filter; - texturefilter_to_glenum( config.filter, config.has_mipmaps, &min_filter, &mag_filter ); - glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, min_filter ); - glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, mag_filter ); - - if( config.wrap == TEXWRAP_BORDER ) { - glTexParameterfv( GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, ( GLfloat * ) &config.border_colour ); - } - - GLenum internal_format = texfmt_to_internal_format( config.format ); - - if( is_compressed( config.format ) ) { - u32 dxt_size = texture_byte_size( config.width, config.height, config.format, false ); - glCompressedTexImage2D( GL_TEXTURE_2D, 0, internal_format, - config.width, config.height, 0, dxt_size, config.data ); - } - else { - GLenum channels = GL_INVALID_ENUM; - GLenum type = GL_INVALID_ENUM; - - switch( config.format ) { - case TEXFMT_R_U8: - case TEXFMT_R_U8NORM: - channels = GL_RED; - type = GL_UNSIGNED_BYTE; - break; - case TEXFMT_R_U16: - channels = GL_RED; - type = GL_UNSIGNED_SHORT; - break; - case TEXFMT_R_FLOAT: - channels = GL_RED; - type = GL_FLOAT; - break; - - case TEXFMT_RGB_U8: - case TEXFMT_RGB_U8_SRGB: - channels = GL_RGB; - type = GL_UNSIGNED_BYTE; - break; - case TEXFMT_RGB_HALF: - channels = GL_RGB; - type = GL_HALF_FLOAT; - break; - case TEXFMT_RGB_FLOAT: - channels = GL_RGB; - type = GL_FLOAT; - break; - - case TEXFMT_RGBA_U8: - case TEXFMT_RGBA_U8_SRGB: - channels = GL_RGBA; - type = GL_UNSIGNED_BYTE; - break; - case TEXFMT_RGBA_FLOAT: - channels = GL_RGBA; - type = GL_FLOAT; - break; - - case TEXFMT_DEPTH: - channels = GL_DEPTH_COMPONENT; - type = GL_FLOAT; - break; - - default: - FATAL( "implement channel/type selection for {}", config.format ); - break; - } - - glTexImage2D( GL_TEXTURE_2D, 0, internal_format, - config.width, config.height, 0, channels, type, config.data ); - } - - return texture; -} - -void renderer_delete_texture( Texture texture ) { - glDeleteTextures( 1, &texture ); -} - static void vertexfmt_to_glenum( VertexFormat format, GLenum * type, int * components ) { switch( format ) { case VERTEXFMT_U8x3: diff --git a/renderer.h b/renderer.h @@ -275,6 +275,8 @@ struct RenderPass { bool clear_depth = false; float depth = 1.0f; + + FB msaa_source = { }; }; struct FramebufferConfig { @@ -284,6 +286,7 @@ struct FramebufferConfig { }; StaticArray< FramebufferTexture, OUTPUTS_COUNT > textures = { }; + int msaa_samples = 0; }; enum ClearColourBool { RENDERER_CLEAR_COLOUR_DONT, RENDERER_CLEAR_COLOUR_DO }; @@ -298,6 +301,7 @@ void renderer_end_frame(); u8 renderer_add_pass( const RenderPass & config ); u8 renderer_add_pass( const char * name, ClearColourBool clear_colour = RENDERER_CLEAR_COLOUR_DONT, ClearDepthBool clear_depth = RENDERER_CLEAR_DEPTH_DONT ); u8 renderer_add_pass( const char * name, FB target, ClearColourBool clear_colour = RENDERER_CLEAR_COLOUR_DONT, ClearDepthBool clear_depth = RENDERER_CLEAR_DEPTH_DONT ); +void renderer_resolve_msaa( FB fb ); u32 renderer_num_draw_calls(); u32 renderer_num_vertices(); @@ -312,6 +316,9 @@ void renderer_delete_vb( VB vb ); IB renderer_new_ib( const void * data = NULL, u32 len = 0, BufferUsage usage = BUFFERUSAGE_STATIC ); void renderer_delete_ib( IB ib ); +Texture renderer_new_texture( const TextureConfig & config ); +void renderer_delete_texture( Texture texture ); + FB renderer_new_fb( TextureConfig texture_format, FramebufferAttachment attachment ); FB renderer_new_fb( const FramebufferConfig & config ); void renderer_delete_fb( FB fb ); @@ -319,9 +326,6 @@ void renderer_delete_fb( FB fb ); Shader renderer_new_shader( array< const char * > srcs ); void renderer_delete_shader( Shader shader ); -Texture renderer_new_texture( TextureConfig config ); -void renderer_delete_texture( Texture texture ); - Mesh renderer_new_mesh( MeshConfig config ); void renderer_draw_mesh( const Mesh & mesh, const RenderState & state ); void renderer_draw_instances( const Mesh & mesh, const RenderState & state, u32 num_instances, VB instace_data );