commit 46cf69c3629346d405a27dae2714f42aed869ed6 parent f872c8f752a93cf3ba54e8347ba78cfb0c8bab1e Author: Michael Savage <mikejsavage@gmail.com> Date: Sat Sep 10 18:23:25 -0700 First effort towards async tile loading Diffstat:
terrain_manager.cc | | | 138 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------- |
terrain_manager.h | | | 20 | +++++++++++++++++--- |
diff --git a/terrain_manager.cc b/terrain_manager.cc @@ -97,42 +97,93 @@ static const GLchar * frag_src = GLSL( } ); -static void terrain_load_tile( TerrainManager * tm, s32 tx, s32 ty ) { - Heightmap * hm = &tm->tiles[ tx ][ ty ]; - - if( hm->vbo_verts != 0 ) { - heightmap_destroy( hm ); - gpubtt_destroy( &tm->btt_tiles[ tx ][ ty ] ); - } - - // if( tx >= WORLD_SIZE || ty >= WORLD_SIZE ) return; - if( tx < 0 || ty < 0 ) return; - if( ( u32 ) tx * TILE_SIZE >= tm->width || ( u32 ) ty * TILE_SIZE >= tm->height ) return; +struct AsyncLoadTile { + TerrainManager * tm; + s32 tx, ty; +}; + +static WORK_QUEUE_CALLBACK( async_load_tile ) { + AsyncLoadTile * alt = ( AsyncLoadTile * ) data; + TerrainManager * tm = alt->tm; + s32 tx = alt->tx; + s32 ty = alt->ty; + free( alt ); CompressedTile ct = tm->compressed_tiles[ tx ][ ty ]; - int width, height; + u32 width, height; size_t out_size = ( TILE_SIZE + 1 ) * ( TILE_SIZE + 1 ); // TODO: width/height might not be TILE_SIZE on the edges of the map width = height = TILE_SIZE + 1; + u8 * pixels = ( u8 * ) malloc( out_size ); + assert( pixels != NULL ); int ok = LZ4_decompress_safe( ( const char * ) ct.data, ( char * ) pixels, ct.len, out_size ); assert( ok > 0 ); - heightmap_init( hm, tm->arena, pixels, width, height, - tx * TILE_SIZE, ty * TILE_SIZE, - tm->at_position, tm->at_normal, tm->at_lit ); - MEMARENA_SCOPED_CHECKPOINT( tm->arena ); - BTTs btts = btt_from_heightmap( hm, tm->arena ); - const OffsetHeightmap ohm = { *hm, tx * TILE_SIZE, ty * TILE_SIZE }; - gpubtt_init( tm->arena, &tm->btt_tiles[ tx ][ ty ], &ohm, btts, tm->at_position ); + Heightmap * hm = &tm->tiles[ tx ][ ty ]; + heightmap_init( hm, pixels, width, height ); + MEMARENA_SCOPED_CHECKPOINT( arena ); + uptr used = arena->used; + BTTs btt = btt_from_heightmap( hm, arena ); + size_t size_for_btt = arena->used - used; + + printf( "uploading tile %d %d. arena memory jumped %zu bytes from %zu to %zu\n", tx, ty, size_for_btt, used, arena->used ); + void * btt_memory = malloc( size_for_btt ); + assert( btt_memory != NULL ); + memcpy( btt_memory, arena->memory + used, size_for_btt ); + + tm->btts[ tx ][ ty ] = btt; + tm->btt_memory[ tx ][ ty ] = btt_memory; + + // TODO: this doesn't seem to be acting as release. it + // looks like btt_memory is not being fully written before + // gpu_init is called + store_release( &tm->tile_states[ tx ][ ty ], TILE_READY_FOR_UPLOAD ); +} + +static bool terrain_tile_inside_world( const TerrainManager * tm, s32 tx, s32 ty ) { + if( tx < 0 || ty < 0 ) return false; + if( ( u32 ) tx * TILE_SIZE >= tm->width ) return false; + if( ( u32 ) ty * TILE_SIZE >= tm->height ) return false; + return true; +} + +static void terrain_load_tile( TerrainManager * tm, s32 tx, s32 ty ) { + if( !terrain_tile_inside_world( tm, tx, ty ) ) return; + + if( load_acquire( &tm->tile_states[ tx ][ ty ] ) != TILE_EMPTY ) { + return; + } + + AsyncLoadTile * alt = ( AsyncLoadTile * ) malloc( sizeof( AsyncLoadTile ) ); + alt->tm = tm; + alt->tx = tx; + alt->ty = ty; + workqueue_enqueue( tm->background_tasks, async_load_tile, alt ); +} + +static void terrain_unload_tile( TerrainManager * tm, s32 tx, s32 ty ) { + if( !terrain_tile_inside_world( tm, tx, ty ) ) return; + + if( load_acquire( &tm->tile_states[ tx ][ ty ] ) != TILE_ON_GPU ) { + return; + } + + store_release( &tm->tile_states[ tx ][ ty ], TILE_FREEING ); + printf( "destroyed %d %d\n", tx, ty ); + Heightmap * hm = &tm->tiles[ tx ][ ty ]; + heightmap_destroy( hm ); + gpubtt_destroy( &tm->btt_tiles[ tx ][ ty ] ); + store_release( &tm->tile_states[ tx ][ ty ], TILE_EMPTY ); } void terrain_init( TerrainManager * tm, const char * tiles_dir, - MemoryArena * arena + MemoryArena * arena, WorkQueue * background_tasks ) { tm->arena = arena; + tm->background_tasks = background_tasks; char dims_path[ 256 ]; sprintf( dims_path, "%s/dims.txt", tiles_dir ); @@ -152,6 +203,8 @@ void terrain_init( size_t len; u8 * data = file_get_contents( tile_path, &len ); tm->compressed_tiles[ tx ][ ty ] = { data, len }; + + store_release( &tm->tile_states[ tx ][ ty ], TILE_EMPTY ); } } @@ -231,6 +284,10 @@ void terrain_update( TerrainManager * tm, glm::vec3 position ) { if( player_tile_x > tm->tile_x ) { // +x boundary for( u32 vy = 0; vy < VIEW_SIZE; vy++ ) { + terrain_unload_tile( tm, + tm->tile_x - VIEW_HALF, + tm->tile_y + vy - VIEW_HALF ); + // TODO: this should enqueue a tile load because this can fail now terrain_load_tile( tm, tm->tile_x + VIEW_HALF + 1, tm->tile_y + vy - VIEW_HALF ); @@ -240,6 +297,9 @@ void terrain_update( TerrainManager * tm, glm::vec3 position ) { else { // -x boundary for( u32 vy = 0; vy < VIEW_SIZE; vy++ ) { + terrain_unload_tile( tm, + tm->tile_x + VIEW_HALF, + tm->tile_y + vy - VIEW_HALF ); terrain_load_tile( tm, tm->tile_x - VIEW_HALF - 1, tm->tile_y + vy - VIEW_HALF ); @@ -253,6 +313,9 @@ void terrain_update( TerrainManager * tm, glm::vec3 position ) { if( player_tile_y > tm->tile_y ) { // +y boundary for( u32 vx = 0; vx < VIEW_SIZE; vx++ ) { + terrain_unload_tile( tm, + tm->tile_x + vx - VIEW_HALF, + tm->tile_y - VIEW_HALF ); terrain_load_tile( tm, tm->tile_x + vx - VIEW_HALF, tm->tile_y + VIEW_HALF + 1 ); @@ -262,6 +325,9 @@ void terrain_update( TerrainManager * tm, glm::vec3 position ) { else { // -y boundary for( u32 vx = 0; vx < VIEW_SIZE; vx++ ) { + terrain_unload_tile( tm, + tm->tile_x + vx - VIEW_HALF, + tm->tile_y + VIEW_HALF ); terrain_load_tile( tm, tm->tile_x + vx - VIEW_HALF, tm->tile_y - VIEW_HALF - 1 ); @@ -273,7 +339,7 @@ void terrain_update( TerrainManager * tm, glm::vec3 position ) { } } -void terrain_render( const TerrainManager * tm, glm::mat4 V, glm::mat4 VP, float sun_slope ) { +void terrain_render( TerrainManager * tm, glm::mat4 V, glm::mat4 VP, float sun_slope ) { glm::vec3 sun = glm::normalize( glm::vec3( 1, 0, -sun_slope ) ); glUseProgram( tm->shader ); @@ -315,12 +381,40 @@ void terrain_render( const TerrainManager * tm, glm::mat4 V, glm::mat4 VP, float glUniform1i( tm->un_point_light_colours, 2 ); /* end lighting */ + u32 inited = 0; + for( u16 vy = 0; vy < VIEW_SIZE; vy++ ) { + for( u16 vx = 0; vx < VIEW_SIZE; vx++ ) { + s32 tx = vx + tm->tile_x - VIEW_HALF; + s32 ty = vy + tm->tile_y - VIEW_HALF; + + if( tx < 0 || ty < 0 ) continue; + + u32 state = load_acquire( &tm->tile_states[ tx ][ ty ] ); + if( state == TILE_READY_FOR_UPLOAD ) { + if( inited > 0 ) continue; + // TODO: move this into terrain_prepare_render? + const Heightmap * hm = &tm->tiles[ tx ][ ty ]; + const OffsetHeightmap ohm = { *hm, tx * TILE_SIZE, ty * TILE_SIZE }; + // printf( "gpubtt_init at %d %d %p %p\n", tx, ty, tm->btts[ tx ][ ty ].left_root, tm->btts[ tx ][ ty ].right_root ); + gpubtt_init( tm->arena, &tm->btt_tiles[ tx ][ ty ], &ohm, tm->btts[ tx ][ ty ], tm->at_position ); + free( tm->btt_memory[ tx ][ ty ] ); + store_release( &tm->tile_states[ tx ][ ty ], TILE_ON_GPU ); + inited++; + } + } + } + for( u16 vy = 0; vy < VIEW_SIZE; vy++ ) { for( u16 vx = 0; vx < VIEW_SIZE; vx++ ) { s32 tx = vx + tm->tile_x - VIEW_HALF; s32 ty = vy + tm->tile_y - VIEW_HALF; + if( tx < 0 || ty < 0 ) continue; - gpubtt_render( &tm->btt_tiles[ tx ][ ty ], tm->un_normals ); + + u32 state = load_acquire( &tm->tile_states[ tx ][ ty ] ); + if( state == TILE_ON_GPU ) { + gpubtt_render( &tm->btt_tiles[ tx ][ ty ], tm->un_normals ); + } } } diff --git a/terrain_manager.h b/terrain_manager.h @@ -8,11 +8,13 @@ #include "memory_arena.h" #include "heightmap.h" #include "gpubtt.h" +#include "work_queue.h" +#include "platform_atomic.h" static const u16 TILE_SIZE = 128; static const u16 WORLD_SIZE = 170; -static const u16 VIEW_SIZE = 45; +static const u16 VIEW_SIZE = 25; static const u16 VIEW_HALF = VIEW_SIZE / 2; struct CompressedTile { @@ -20,8 +22,17 @@ struct CompressedTile { size_t len; }; +enum TileState { + TILE_EMPTY, + TILE_LOADING, + TILE_READY_FOR_UPLOAD, + TILE_ON_GPU, + TILE_FREEING, +}; + struct TerrainManager { MemoryArena * arena; + WorkQueue * background_tasks; u32 width, height; @@ -55,17 +66,20 @@ struct TerrainManager { bool first_teleport; CompressedTile compressed_tiles[ WORLD_SIZE ][ WORLD_SIZE ]; + atomic_u32 tile_states[ WORLD_SIZE ][ WORLD_SIZE ]; Heightmap tiles[ WORLD_SIZE ][ WORLD_SIZE ]; + BTTs btts[ WORLD_SIZE ][ WORLD_SIZE ]; + void * btt_memory[ WORLD_SIZE ][ WORLD_SIZE ]; GPUBTT btt_tiles[ WORLD_SIZE ][ WORLD_SIZE ]; u8 lods[ WORLD_SIZE ][ WORLD_SIZE ]; // tile_x and tile_y are the coordinates of the tile we are centered on s32 tile_x, tile_y; }; -void terrain_init( TerrainManager * tm, const char * tiles_dir, MemoryArena * arena ); +void terrain_init( TerrainManager * tm, const char * tiles_dir, MemoryArena * arena, WorkQueue * background_tasks ); void terrain_teleport( TerrainManager * tm, glm::vec3 position ); void terrain_update( TerrainManager * tm, glm::vec3 position ); -void terrain_render( const TerrainManager * tm, glm::mat4 V, glm::mat4 VP, float sun_slope ); +void terrain_render( TerrainManager * tm, glm::mat4 V, glm::mat4 VP, float sun_slope ); float terrain_height( const TerrainManager * tm, glm::vec3 position ); #endif // _TERRAIN_MANAGER_H_