/* ======================================================================= This file is part of Redman's RT. Redman's RT is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Redman's RT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Redman's RT. If not, see . ======================================================================= */ #include "common.h" //#define NO_THREADING typedef struct { size_t x0, y0, x1, y1; } R_tile_t; typedef struct { bool_t done; uint8_t *pixels; size_t width, height; int pitch; uint32_t format, bpp; size_t tileSize; SDL_mutex *tilesLock; R_tile_t *tiles; size_t tileCount, tileNext; } R_frame_t; typedef struct { bool_t shaded; bool_t overlay; //crosshair size_t samples; bool_t phong; bool_t phong_specular; bool_t phong_shadows; bool_t monteCarlo; bool_t globalIllumination; bool_t fresnel; } R_quality_t; typedef enum { R_WORKER_QUIT, R_WORKER_IDLE, R_WORKER_RENDERING, R_WORKER_DONE } R_workerstate_t; typedef struct { vec3_t origin; vec3_t angles; float fov, projdist; m3_t matrix; } R_view_t; typedef struct { size_t id; R_workerstate_t state; R_frame_t *fb; size_t x0, y0, x1, y1; SDL_mutex *sync, *sync2; R_quality_t quality; size_t pixelsDone; W_world_t *world; R_view_t *view; } R_workerdata_t; typedef struct { SDL_Thread *thread; R_workerdata_t data; } R_worker_t; typedef struct { size_t depth; } R_workerlocal_t; #define R_FRAMETIME_SAMPLES 8 typedef struct { bool_t ready; struct { size_t resw, resh; float supersampling; int scaleQuality; size_t workerCount; size_t tileSize; } cfg; SDL_Window *window; SDL_Renderer *renderer; SDL_Texture *frameTexture; R_frame_t frame; R_quality_t quality; //R_world_t world; R_view_t view; R_worker_t *workers; size_t workerCount; size_t frameCount; size_t frameTimes[ R_FRAMETIME_SAMPLES ]; size_t nextFrameTime; float fps; } R_state_t; typedef struct { bool_t transparent; vec3_t color; vec_t mapScale; //Phong vec_t diffuseCoeff; vec_t specularCoeff; vec_t specularExponent; //Snell, Fresnel vec_t refractiveIndex; } R_material_t; static const R_material_t _Materials[ ] = { { }, // BLOCK_NONE // R G B uv dif spe exp ir { false, { 0.7, 0.3, 0.0 }, 0.15, 0.9, 0.1, 1.0, 1.5 }, // BLOCK_GROUND { false, { 0.8, 0.8, 0.8 }, 0.15, 0.8, 0.2, 1.5, 1.5 }, // BLOCK_CONCRETE { false, { 1.0, 1.0, 1.0 }, 1.0, 0.3, 0.7, 12, 1.5 }, // BLOCK_METAL { false, { 0.0, 1.0, 0.0 }, 1.0, 0.0, 1.0, INFINITY, 1.5 }, // BLOCK_MIRROR { false, { 25.0, 25.0, 25.0 }, 1.0, 0.0, 0.0, 0.0, 0.0 }, // BLOCK_LIGHT { false, { 0.7, 0.3, 0.0 }, 1.0, 0.8, 0.2, 3.0, 1.5 } // BLOCK_BANNER }; const size_t _Material_count = sizeof( _Materials ) / sizeof( R_material_t ); typedef struct { SDL_Surface *diffuse; } R_mapset_t; R_mapset_t _Material_mapsets[ sizeof( _Materials ) / sizeof( R_material_t ) ]; int R_Init( R_state_t *rs ) { size_t i; R_mapset_t *mapset; //TODO: error handling IMG_Init( IMG_INIT_JPG | IMG_INIT_PNG ); memset( rs, 0, sizeof( R_state_t ) ); memset( &_Material_mapsets, 0, sizeof( _Material_mapsets ) ); for( i = 1; i < _Material_count; i++ ) { mapset = _Material_mapsets + i; mapset->diffuse = IMG_Load( va( "tex/%i.jpg", i ) ); if( !mapset->diffuse ) fprintf( stderr, "IMG_Load: %s\n", IMG_GetError( ) ); } return 0; } int R_Configure( R_state_t *rs ) { rs->cfg.resw = 1280; rs->cfg.resh = 720; rs->cfg.supersampling = 0.2f; rs->cfg.scaleQuality = 1; rs->cfg.workerCount = 8; rs->cfg.tileSize = 64; rs->view.fov = 0.5 * M_PI; rs->quality.shaded = false; return 0; } int R_InitScreen(R_state_t *rs) { //todo: error catching printf("R_InitScreen: %zux%zu\n",rs->cfg.resw,rs->cfg.resh); if( rs->frameTexture ) SDL_DestroyTexture(rs->frameTexture); if( rs->window ) SDL_DestroyWindow(rs->window); if( rs->renderer ) SDL_DestroyRenderer(rs->renderer); rs->window = SDL_CreateWindow( PROGRAM_FULLNAME, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, rs->cfg.resw, rs->cfg.resh, SDL_WINDOW_OPENGL | SDL_WINDOW_INPUT_GRABBED ); if( !rs->window ) { printf( "SDL_CreateWindow: %s\n", SDL_GetError( ) ); abort( ); } rs->renderer=SDL_CreateRenderer( rs->window,-1, SDL_RENDERER_ACCELERATED ); if( !rs->renderer ) { printf( "SDL_CreateRenderer: %s\n", SDL_GetError( ) ); abort( ); } rs->frame.width = rs->cfg.resw * rs->cfg.supersampling; rs->frame.height = rs->cfg.resh * rs->cfg.supersampling; rs->frameTexture=SDL_CreateTexture( rs->renderer, SDL_PIXELFORMAT_BGR888, SDL_TEXTUREACCESS_STREAMING, rs->frame.width, rs->frame.height ); rs->frame.tileSize = rs->cfg.tileSize; { int w, h; SDL_QueryTexture( rs->frameTexture, &rs->frame.format, NULL, &w, &h ); rs->frame.width = w; rs->frame.height = h; } if( rs->frame.format != SDL_PIXELFORMAT_BGR888 ) printf( "R_InitScreen: wrong pixel format %i\n", rs->frame.format ); rs->frame.bpp = 4; rs->frame.pitch = rs->frame.width * rs->frame.bpp; rs->frame.tilesLock = SDL_CreateMutex( ); rs->ready = true; return 0; } //R_InitWorkers: set up the configured worker count int R_Worker( R_workerdata_t *w ); int R_InitWorkers( R_state_t *rs ) { #ifndef NO_THREADING size_t i; R_worker_t *w; printf( "R_InitWorkers: %zu thread%s\n", rs->cfg.workerCount, ( rs->cfg.workerCount == 1 ? "" : "s" ) ); //nothing to do if( rs->cfg.workerCount == rs->workerCount ) return 0; //remove some workers if( rs->cfg.workerCount < rs->workerCount ) { for( i = rs->cfg.workerCount; i < rs->workerCount; i++ ) { w = rs->workers + i; w->data.state = R_WORKER_QUIT; SDL_UnlockMutex( w->data.sync ); SDL_WaitThread( w->thread, NULL ); } } rs->workers = realloc( rs->workers, rs->cfg.workerCount * sizeof( R_worker_t ) ); //allocate more workers if( rs->cfg.workerCount > rs->workerCount ) { for( i = rs->workerCount; i < rs->cfg.workerCount; i++ ) { w = rs->workers + i; w->data.id = i; w->data.state = R_WORKER_IDLE; w->data.sync = SDL_CreateMutex(); SDL_LockMutex( w->data.sync ); w->data.sync2 = SDL_CreateMutex(); w->thread = SDL_CreateThread( ( SDL_ThreadFunction )R_Worker, va( "render worker %i", i ), &w->data ); } } rs->workerCount = rs->cfg.workerCount; return 0; #endif } void R_GenerateTiles( R_frame_t *fb ) { /* R_tile_t *t; size_t x, y, th, tv; th = fb->width / fb->tileSize; if( fb->width % fb->tileSize ) th++; tv = fb->height / fb->tileSize; if( fb->height % fb->tileSize ) tv++; fb->tileCount = th * tv; fb->tiles = realloc( fb->tiles, sizeof( R_tile_t ) * fb->tileCount ); for( t = fb->tiles, y = 0; y < tv; y++ ) for( x = 0; x < th; x++, t++ ) { t->x0 = x * fb->tileSize; t->y0 = y * fb->tileSize; t->x1 = ( x + 1 ) * fb->tileSize - 1; if( t->x1 > fb->width - 1 ) t->x1 = fb->width - 1; t->y1 = ( y + 1 ) * fb->tileSize - 1; if( t->y1 > fb->height - 1 ) t->y1 = fb->height - 1; } fb->tileNext = 0; */ R_tile_t *t; size_t y; fb->tileCount = fb->height; fb->tiles = realloc( fb->tiles, sizeof( R_tile_t ) * fb->tileCount ); for( t = fb->tiles, y = 0; y < fb->height; y++, t++ ) { t->x0 = 0; t->x1 = fb->width - 1; t->y0 = y; t->y1 = y; } fb->tileNext = 0; }; inline bool_t R_Worker_FindTile( R_workerdata_t *w ) { R_tile_t *t; SDL_LockMutex( w->fb->tilesLock ); if( w->fb->tileNext >= w->fb->tileCount ) { SDL_UnlockMutex( w->fb->tilesLock ); return false; } t = w->fb->tiles + w->fb->tileNext++; SDL_UnlockMutex( w->fb->tilesLock ); w->x0 = t->x0; w->y0 = t->y0; w->x1 = t->x1; w->y1 = t->y1; return true; } W_world_t world; static void R_Sky( vec3_t out, const vec3_t dir, const R_quality_t *quality ) { vec_t dot; dot = V3Dot( dir, world.lights[ 0 ].direction ); //sun if( dot > 0.9999 ) { V3Set( out, 3, 3, 3 ); return; } if( !quality->globalIllumination ) { V3Set( out, 0, 0, 0 ); return; } if( dot < 0 ) dot = 0; dot *= dot * dot * dot * 1.6; dot += 0.4; V3Set( out, dot, dot, dot ); } static void R_MapTexture( vec3_t out, const vec3_t point, const vec3_t normal, vec_t scale, const SDL_Surface *surf ) { vec_t u = 0.0, v = 0.0; size_t x, y; if( surf == _Material_mapsets[ BLOCK_BANNER ].diffuse ) { u = ( point[ 1 ] + 9.0 ) / 16.0; v = ( point[ 2 ] - 24.0 ) / 16.0; } else { if( normal[ 2 ] == -1.0 || normal[ 2 ] == 1.0 ) { u = point[ 0 ]; v = point[ 1 ]; } else if( normal[ 0 ] == -1.0 || normal[ 0 ] == 1.0 ) { u = point[ 1 ]; v = point[ 2 ]; } else if( normal[ 1 ] == -1.0 || normal[ 1 ] == 1.0 ) { u = point[ 2 ]; v = point[ 0 ]; } } //FIXME: lame negative number handling u = fmod( u + 500, 1.0 / scale ) * scale; v = fmod( v + 500, 1.0 / scale ) * scale; x = u * surf->w; y = v * surf->h; x %= surf->w; y %= surf->h; //*(uint32_t*)(w->fb->pixels+y*w->fb->pitch+x*4)=r_|(g_<<8)|(b_<<16); out[ 0 ] = 0.00390625 * *(uint8_t*)(surf->pixels + y * surf->pitch + x * 3 ); out[ 1 ] = 0.00390625 * *(uint8_t*)(surf->pixels + y * surf->pitch + x * 3 + 1 ); out[ 2 ] = 0.00390625 * *(uint8_t*)(surf->pixels + y * surf->pitch + x * 3 + 2 ); } inline static void R_Phong( vec3_t out, const vec3_t point, const vec3_t viewdir, const vec3_t normal, const W_otnode_t *ignore, const R_material_t *mat, const R_quality_t *quality ) { int i; ray_t shray; rtres_t shrt; vec_t dot, dist, lightMod; vec3_t pass; bool_t shadowed; V3Set( out, 0, 0, 0 ); //iterate through all dynamic lights for( i = 0; i < MAX_DLIGHTS; i++ ) { W_light_t *light = world.lights + i; V3Set( pass, 0, 0, 0 ); if( !light->inuse ) continue; V3Copy( shray.org, point ); if( !light->sun ) { V3Sub( shray.dir, light->origin, point ); dist = V3Normalize( shray.dir ); } else { V3Copy( shray.dir, light->direction ); dist = INFINITY; } if( light->flashlight ) { if( !light->flashlight_enabled ) continue; dot = V3Dot( light->direction, shray.dir ); if( dot > -0.6 ) continue; dot = -dot; dot -= 0.6; dot /= 0.4; lightMod = sqrt( dot ); } else lightMod = 1.0; dot = V3Dot( normal, shray.dir ); if( dot < 0 ) continue; shadowed = false; if( quality->phong_shadows ) { U_BuildRay( NULL, NULL, dist, &shray, &shrt ); shadowed = ( W_Trace( &world, ignore, &shray, &shrt ) != NULL ); } if( shadowed ) continue; if( light->sun ) V3Mul( pass, light->light, dot ); else V3Mul( pass, light->light, dot / dist / dist * lightMod ); V3Mul( pass, pass, mat->diffuseCoeff ); if( mat->specularCoeff > 0 ) { vec_t specular; vec3_t ref; V3Reflect( ref, shray.dir, normal ); dot = V3Dot( ref, viewdir ); if( dot > 0 ) { specular = pow( dot, mat->specularExponent ) * mat->specularCoeff; V3MA( pass, pass, specular, light->light ); } } V3Add( out, out, pass ); } } #define R_RVP_SIZE 123456 typedef struct { vec3_t table[ R_RVP_SIZE ]; size_t i; } R_randvecpool_t; void R_GenerateRVP( R_randvecpool_t *rvp ) { size_t i; vec_t p, y; m3_t M, My, Mz; for( i = 0; i < R_RVP_SIZE; i++ ) { p = ( (vec_t)rand( ) / RAND_MAX ) * 2.0 * M_PI; y = ( (vec_t)rand( ) / RAND_MAX ) * 2.0 * M_PI; M3RotY( My, p ); M3RotZ( Mz, y ); M3Product( M, My, Mz ); V3Copy( rvp->table[ i ], M ); } } void R_v3rand( vec3_t out, R_randvecpool_t *rvp, const vec3_t normal ) { rvp->i++; rvp->i %= R_RVP_SIZE; V3Copy( out, rvp->table[ rvp->i ] ); if( !normal ) return; if( V1Epscmp( normal[ 0 ], 1, 1e-5 ) ) out[ 0 ] = fabs( out[ 0 ] ); else if( V1Epscmp( normal[ 0 ], -1, 1e-5 ) ) out[ 0 ] = -fabs( out[ 0 ] ); else if( V1Epscmp( normal[ 1 ], 1, 1e-5 ) ) out[ 1 ] = fabs( out[ 1 ] ); else if( V1Epscmp( normal[ 1 ], -1, 1e-5 ) ) out[ 1 ] = -fabs( out[ 1 ] ); else if( V1Epscmp( normal[ 2 ], 1, 1e-5 ) ) out[ 2 ] = fabs( out[ 2 ] ); else if( V1Epscmp( normal[ 2 ], -1, 1e-5 ) ) out[ 2 ] = -fabs( out[ 2 ] ); } void R_PT_Trace_R( vec3_t out, vec3_t in_mask, const vec3_t org, const vec3_t dir, const W_otnode_t *ignore, const R_quality_t *quality, R_randvecpool_t *rvp, size_t *depth ) { vec3_t mask; W_otnode_t *node; ray_t ray; rtres_t rt; const R_material_t *mat; bool_t reflection = false; const R_mapset_t *mapset; (*depth)++; U_BuildRay( org, dir, INFINITY, &ray, &rt ); node = W_Trace( &world, ignore, &ray, &rt ); if( !node ) { vec3_t sky; R_Sky( sky, ray.dir, quality ); V3VMA( out, out, in_mask, sky ); goto out; } V3MA( rt.hit, ray.org, rt.dist, ray.dir ); mat = _Materials + node->data; mapset = _Material_mapsets + node->data; if( node->data == BLOCK_LIGHT ) { V3VMA( out, out, in_mask, mat->color ); goto out; } if( mapset->diffuse ) { vec3_t color; R_MapTexture( color, rt.hit, rt.normal, mat->mapScale, mapset->diffuse ); V3VMul( mask, in_mask, color ); } else V3VMul( mask, in_mask, mat->color ); //Phong shading if( !mat->transparent ) { if( quality->phong ) { vec3_t phong; R_Phong( phong, rt.hit, ray.dir, rt.normal, node, mat, quality ); V3VMA( out, out, mask, phong ); } } if( (*depth) > 3 ) goto out; reflection = false; if( mat->specularExponent == INFINITY ) reflection = true; else if( quality->monteCarlo ) { vec_t R; R = (vec_t)rand() / RAND_MAX; reflection = ( R < mat->specularCoeff ); } if( !reflection ) { //global illumination if( !mat->transparent ) { if( quality->monteCarlo && quality->globalIllumination ) { vec3_t r; vec3_t mask_gi; R_v3rand( r, rvp, rt.normal ); V3Mul( mask_gi, mask, mat->diffuseCoeff ); R_PT_Trace_R( out, mask_gi, rt.hit, r, node, quality, rvp, depth ); } } //pass through (DOES NOT WORK) /*else { vec_t R, C, B; vec3_t rfa; ray_t rfaray; rtres_t rfart; R = 1.0 / mat->refractiveIndex; C = -V3Dot( rt.normal, ray.dir ); B = R * C - sqrt( 1 - R * R * ( 1 - C * C ) ); V3Mul2( rfa, ray.dir, R, rt.normal, B ); V3MA( rfaray.org, rt.hit, 150, rfa ); V3Mul( rfaray.dir, rfa, -1 ); U_BuildRay( NULL, NULL, INFINITY, &rfaray, &rfart ); if( !U_RT_AABB( (const vec3_t*)node->aabb, &ray, &rt ) ) printf( "wtf\n" ); V3MA( rfart.hit, rfaray.org, rfart.dist, rfaray.dir ); R_PT_Trace_R( out, mask, rfart.hit, rfa, node, quality, rvp, depth ); goto out; }*/ } else { vec3_t ref; V3Reflect( ref, ray.dir, rt.normal ); if( mat->specularExponent == INFINITY ) { V3VMul( mask, mask, mat->color ); R_PT_Trace_R( out, mask, rt.hit, ref, node, quality, rvp, depth ); } else if( quality->monteCarlo ) { vec3_t rnd; R_v3rand( rnd, rvp, NULL ); V3MA( ref, ref, 1.0 / mat->specularExponent, rnd ); V3Normalize( ref ); if( V3Dot( ref, rt.normal ) < 0 ) goto out; V3VMul( mask, mask, mat->color ); R_PT_Trace_R( out, mask, rt.hit, ref, node, quality, rvp, depth ); } } out: (*depth)--; } void R_PT_Trace( vec3_t out, const ray_t *ray, const R_quality_t *quality, R_randvecpool_t *rvp ) { vec3_t mask; size_t i, depth; V3Set( out, 0, 0, 0 ); V3Set( mask, 1, 1, 1 ); depth = 0; for( i = 0; i < quality->samples; i++ ) R_PT_Trace_R( out, mask, ray->org, ray->dir, NULL, quality, rvp, &depth ); V3Mul( out, out, 1.0 / quality->samples ); if( quality->phong && quality->globalIllumination ) V3Mul( out, out, 0.5 ); } bool_t R_Lightbulbs( vec3_t out, const ray_t *ray ) { size_t i; W_light_t *light; vec3_t delta; vec_t dist; for( i = 0; i < MAX_DLIGHTS; i++ ) { light = world.lights + i; if( !light->inuse || light->sun || light->flashlight ) continue; V3Sub( delta, light->origin, ray->org ); dist = V3Normalize( delta ); if( V3Dot( delta, ray->dir ) > ( 1 - 1 / ( dist * 500 ) ) ) { V3Copy( out, light->light ); return true; } } return false; } void R_SimpleTrace( vec3_t out, const ray_t *ray ) { rtres_t rt; memset( &rt, 0, sizeof( rtres_t ) ); if( W_Trace( &world, NULL, ray, &rt ) ) V3Set( out, 4.0 / rt.dist, 0, 0 ); else V3Set( out, 0, 0, 1 ); } int R_Worker( R_workerdata_t *w ) { size_t x, y; R_randvecpool_t rvp; #ifdef NO_THREADING { static int init = 0; if( !init ) R_GenerateRVP( &rvp ); init = 1; } #else R_GenerateRVP( &rvp ); #endif #ifndef NO_THREADING while( 1 ) { if( w->state == R_WORKER_IDLE ) { //lock the sync2 to indicate that work is not complete (since the //worker hasn't even started computing) SDL_LockMutex( w->sync2 ); //sleep until the main thread wakes the worker up by releasing the //sync mutex SDL_LockMutex( w->sync ); SDL_UnlockMutex( w->sync ); } else if( w->state==R_WORKER_QUIT ) break; else if( w->state==R_WORKER_RENDERING ) { #endif ray_t ray; vec3_t color; uint8_t r_, g_, b_; float _x,_y; float _x_min; float d_x, d_y; int cx, cy; w->pixelsDone = 0; cx = w->fb->width / 2; cy = w->fb->height / 2; V3Copy( ray.org, w->view->origin ); d_x = 2.0 / w->fb->height; d_y = 2.0 / w->fb->height; while( R_Worker_FindTile( w ) ) { _x = _x_min = (float)( (int)w->x0 + ( (int)w->fb->height - (int)w->fb->width ) / 2 ) / w->fb->height * 2.0 - 1.0; _y = (float)w->y0 / w->fb->height * 2.0 - 1.0; for( y = w->y0; y <= w->y1; y++, _x = _x_min, _y += d_y ) for( x = w->x0; x <= w->x1; x++, _x += d_x ) { //GETRIDOFITLATER: crosshair if( w->quality.overlay ) { int dx, dy; dx = abs( cx - x ); dy = abs( cy - y ); if( dx + dy <= 1 ) { *(uint32_t*)(w->fb->pixels+y*w->fb->pitch+x*4)=0xFFFFFFFF; w->pixelsDone++; continue; } } V3Mul( ray.dir, w->view->matrix, w->view->projdist ); V3MA2( ray.dir, ray.dir, _x, w->view->matrix + 3, _y, w->view->matrix + 6 ); V3Normalize( ray.dir ); U_BuildRay( NULL, NULL, INFINITY, &ray, NULL ); if( !R_Lightbulbs( color, &ray ) ) { if( w->quality.shaded ) { R_PT_Trace( color, &ray, &w->quality, &rvp ); } else R_SimpleTrace( color, &ray ); } r_ = (uint8_t)( V1Clamp( color[ 0 ], 0.0, 1.0) * 255.0 ); g_ = (uint8_t)( V1Clamp( color[ 1 ], 0.0, 1.0) * 255.0 ); b_ = (uint8_t)( V1Clamp( color[ 2 ], 0.0, 1.0) * 255.0 ); *(uint32_t*)(w->fb->pixels+y*w->fb->pitch+x*4)=r_|(g_<<8)|(b_<<16); w->pixelsDone++; } } #ifndef NO_THREADING w->state = R_WORKER_DONE; //work is done, unlock sync2 so the main thread can synchronize //and display the frame SDL_UnlockMutex( w->sync2 ); } else if( w->state == R_WORKER_DONE ) SDL_Delay( 0 ); //FIXME: get rid of this } SDL_DestroyMutex( w->sync ); SDL_DestroyMutex( w->sync2 ); #endif return 0; } void R_CalculateView( R_view_t *view ) { V3AnglesToMatrix( view->matrix, view->angles ); view->projdist = 1.0 / ( tan ( 0.5 * view->fov ) ); } void R_ViewFromPlayer( R_state_t *rs, const G_player_t *p ) { V3Copy( rs->view.origin, p->origin ); V3Copy( rs->view.angles, p->angles ); M3Copy( rs->view.matrix, p->matrix ); } void R_Render( R_state_t *rs ) { uint64_t t_begin, t_delta; size_t i; R_workerdata_t *w; //start measuring render time t_begin = U_Microclock(); //reset tiles rs->frame.tileNext = 0; //calculate the view matrix R_CalculateView( &rs->view ); SDL_LockTexture( rs->frameTexture, NULL, (void**)&rs->frame.pixels, &rs->frame.pitch ); #ifndef NO_THREADING //update & resume all workers for( i = 0; i < rs->workerCount; i++ ) { w = &rs->workers[ i ].data; w->fb = &rs->frame; w->view = &rs->view; memcpy( &w->quality, &rs->quality, sizeof( R_quality_t ) ); //start the thread by changing the state to RENDERING and unlocking //the sync mutex w->state = R_WORKER_RENDERING; SDL_UnlockMutex( w->sync ); } //wait for all workers for( i = 0; i < rs->workerCount; i++ ) { w = &rs->workers[ i ].data; //wait for the thread to finish computing SDL_LockMutex( w->sync2 ); SDL_UnlockMutex( w->sync2 ); //lock the sync mutex THEN change the worker's state back to idle SDL_LockMutex( w->sync ); w->state = R_WORKER_IDLE; } #else { /* size_t id; R_workerstate_t state; R_frame_t *fb; size_t x0, y0, x1, y1; SDL_mutex *sync, *sync2; R_quality_t quality; size_t pixelsDone; W_world_t *world; R_view_t *view;*/ R_workerdata_t worker; worker.fb = &rs->frame; worker.quality = rs->quality; worker.world = &world; worker.view = &rs->view; R_Worker( &worker ); } #endif //display the render result SDL_UnlockTexture( rs->frameTexture ); SDL_RenderCopy( rs->renderer, rs->frameTexture, NULL, NULL ); SDL_RenderPresent( rs->renderer ); //finish measuring render time t_delta = U_Microclock() - t_begin; rs->frameCount++; rs->frameTimes[ rs->nextFrameTime++ ] = t_delta; rs->nextFrameTime %= R_FRAMETIME_SAMPLES; rs->fps = 0.0f; if(rs->frameCount >= R_FRAMETIME_SAMPLES) { for( i = 0; i < R_FRAMETIME_SAMPLES; i++ ) rs->fps += 1.0e+6 / rs->frameTimes[ i ]; rs->fps /= R_FRAMETIME_SAMPLES; } } void R_OfflineRender( R_state_t *rs ) { #ifndef NO_THREADING uint64_t t_begin, t_delta; size_t i, totalPixels; R_workerdata_t *w; R_frame_t frame; R_quality_t offq; //TODO: move out of here SDL_Surface *image; t_begin = U_Microclock(); R_CalculateView( &rs->view ); frame.width = 1280; frame.height = 720; frame.tileSize = 64; totalPixels = frame.width * frame.height; memcpy( &offq, &rs->quality, sizeof( R_quality_t ) ); offq.overlay = false; if( offq.monteCarlo ) offq.samples *= 10; R_GenerateTiles( &frame ); frame.tilesLock = SDL_CreateMutex( ); image = SDL_CreateRGBSurface( SDL_SWSURFACE, frame.width, frame.height, 32, 0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000 ); SDL_LockSurface( image ); frame.pixels = image->pixels; frame.pitch = image->pitch; //resume all workers for( i = 0; i < rs->workerCount; i++ ) { w=&rs->workers[i].data; w->fb = &frame; w->view = &rs->view; memcpy( &w->quality, &offq, sizeof( R_quality_t ) ); //start the thread by changing the state to RENDERING and unlocking //the sync mutex w->state = R_WORKER_RENDERING; SDL_UnlockMutex( w->sync ); } printf( "\nRendering: ---%%") ; while( 1 ) { size_t i, pixelsDone = 0; for( i = 0; i < rs->workerCount; i++ ) pixelsDone += rs->workers[ i ].data.pixelsDone; printf( "\b\b\b\b%3i%%", (int)( 100.0 * (float)pixelsDone / totalPixels ) ); fflush( stdout ); if( pixelsDone == totalPixels ) break; SDL_Delay( 50 ); } //wait for all workers for( i = 0; i < rs->workerCount; i++ ) { w = &rs->workers[ i ].data; //wait for the thread to finish computing SDL_LockMutex( w->sync2 ); SDL_UnlockMutex( w->sync2 ); //lock the sync mutex THEN change the worker's state back to idle SDL_LockMutex( w->sync ); w->state = R_WORKER_IDLE; } //display the render result SDL_UnlockSurface( image ); //finish measuring render time t_delta = U_Microclock() - t_begin; printf("\b\b\b\b100%% (%.1fs)\n", (float)t_delta * 1.0e-6 ); SDL_SaveBMP( image, "offline.bmp" ); SDL_DestroyMutex( frame.tilesLock ); free( frame.tiles ); SDL_FreeSurface( image ); #endif } void R_Quit( R_state_t *rs ) { size_t i; //workers rs->cfg.workerCount = 0; R_InitWorkers( rs ); //tiles free( rs->frame.tiles ); SDL_DestroyMutex( rs->frame.tilesLock ); //mapsets for( i = 1; i < _Material_count; i++ ) SDL_FreeSurface( _Material_mapsets[ i ].diffuse ); //SDL stuff SDL_DestroyTexture( rs->frameTexture ); SDL_DestroyRenderer( rs->renderer ); SDL_DestroyWindow( rs->window ); rs->ready = false; } G_player_t player; R_state_t rs; int main( int argc, char **argv ) { SDL_Event event; W_blockdata_t blockSelection = BLOCK_CONCRETE; bool_t grabbed = true; SDL_Thread *evthd; uint64_t phystime = 0; R_Init( &rs ); R_Configure( &rs ); R_InitScreen( &rs ); R_InitWorkers( &rs ); R_GenerateTiles( &rs.frame ); rs.quality.overlay = true; SDL_SetRelativeMouseMode( SDL_TRUE ); W_InitWorld( &world ); world.otroot = W_NewOTNode( NULL, 0 ); V3Set( world.otroot->aabb[ 0 ], -32, -32, -32 ); V3Set( world.otroot->aabb[ 1 ], 32, 32, 32 ); if( W_Load( &world, "world" ) ) { W_DeleteWorld( &world ); W_CreateEmptyWorld( &world ); } V3Copy( player.origin, world.saved_origin ); V3Copy( player.angles, world.saved_angles ); V3Set( player.velocity, 0, 0, 0 ); player.lastPhysicsTime = U_Clock( ); V3Set( player.aabb[ 0 ], -0.4, -0.4, -1 ); V3Set( player.aabb[ 1 ], 0.4, 0.4, 0.5 ); world.lights[ 0 ].inuse = world.lights[ 0 ].sun = true; V3Set( world.lights[ 0 ].light, 1.6, 1.6, 1.6 ); world.lights[ 1 ].inuse = world.lights[ 1 ].flashlight = true; phystime = U_Clock( ); while( true ) { while( SDL_PollEvent( &event ) ) switch( event.type ) { case SDL_QUIT: goto main_loop_break; case SDL_KEYDOWN: { switch( event.key.keysym.sym ) { case SDLK_F1: grabbed ^= true; SDL_SetRelativeMouseMode( ( grabbed ? SDL_TRUE : SDL_FALSE ) ); break; case SDLK_F4: V3Set( player.origin, 0, 0, -10 ); V3Set( player.angles, 0, 0, 0 ); V3Set( player.velocity, 0, 0, 0 ); break; case SDLK_f: world.lights[ 1 ].flashlight_enabled ^= 1; break; case SDLK_m: G_RecolorBlock( &world, rs.view.origin, rs.view.matrix ); break; case SDLK_o: if( world.otroot ) W_Optimize( world.otroot ); break; case SDLK_p: W_Dump( &world ); break; //throw a light case SDLK_q: { int i; W_light_t *light; for( i = 0; i < MAX_DLIGHTS; i++ ) { light = world.lights + i; if( light->inuse ) continue; light->timestamp = U_Clock( ); light->inuse = true; light->sun = false; V3Copy( light->origin, rs.view.origin ); V3Mul( light->velocity, rs.view.matrix, 3.0 ); switch( rand( ) % 6 ) { case 0: V3Set( light->light, 1, 0, 0 ); break; case 1: V3Set( light->light, 1, 1, 0 ); break; case 2: V3Set( light->light, 0, 1, 0 ); break; case 3: V3Set( light->light, 0, 1, 1 ); break; case 4: V3Set( light->light, 0, 0, 1 ); break; case 5: V3Set( light->light, 1, 0, 1 ); break; } V3Mul( light->light, light->light, 10 ); break; } } break; case SDLK_F11: R_OfflineRender( &rs ); break; //not shaded case SDLK_1: rs.quality.shaded = false; break; //Phong case SDLK_2: rs.quality.shaded = true; rs.quality.samples = 1; rs.quality.phong = true; rs.quality.phong_shadows = false; rs.quality.monteCarlo = false; rs.quality.globalIllumination = false; rs.quality.fresnel = false; break; //shadowed Phong case SDLK_3: rs.quality.shaded = true; rs.quality.samples = 1; rs.quality.phong = true; rs.quality.phong_shadows = true; rs.quality.monteCarlo = false; rs.quality.globalIllumination = false; rs.quality.fresnel = false; break; //full w/o GI case SDLK_4: rs.quality.shaded = true; rs.quality.samples = 1; rs.quality.phong = true; rs.quality.phong_shadows = true; rs.quality.monteCarlo = true; rs.quality.globalIllumination = false; break; //GI only case SDLK_5: rs.quality.shaded = true; rs.quality.samples = 1; rs.quality.phong = false; rs.quality.monteCarlo = true; rs.quality.globalIllumination = true; break; //full case SDLK_6: rs.quality.shaded = true; rs.quality.samples = 1; rs.quality.phong = true; rs.quality.phong_shadows = true; rs.quality.monteCarlo = true; rs.quality.globalIllumination = true; break; //full @ 5 samples case SDLK_7: rs.quality.shaded = true; rs.quality.samples = 5; rs.quality.phong = true; rs.quality.phong_shadows = true; rs.quality.monteCarlo = true; rs.quality.globalIllumination = true; break; case SDLK_z: blockSelection = BLOCK_CONCRETE; break; case SDLK_x: blockSelection = BLOCK_GROUND; break; case SDLK_r: blockSelection = BLOCK_METAL; break; case SDLK_t: blockSelection = BLOCK_MIRROR; break; case SDLK_y: blockSelection = BLOCK_LIGHT; break; case SDLK_u: blockSelection = BLOCK_BANNER; break; } } break; case SDL_MOUSEBUTTONDOWN: switch( event.button.button ) { case SDL_BUTTON_LEFT: G_RemoveBlock( &world, rs.view.origin, rs.view.matrix, 6 ); break; case SDL_BUTTON_RIGHT: G_PlaceBlock( &world, rs.view.origin, rs.view.matrix, blockSelection, 6 ); break; } break; case SDL_MOUSEMOTION: { float x_, y_; if( !rs.ready ) break; x_ = -0.005f * event.motion.xrel / rs.view.projdist; y_ = 0.005f * event.motion.yrel / rs.view.projdist; player.angles[ 2 ] += x_; player.angles[ 1 ] += y_; player.angles[ 1 ] = V1Clamp( player.angles[ 1 ], -0.5 * M_PI, 0.5 * M_PI ); } break; case SDL_MOUSEWHEEL: { static int level = 0; level -= event.wheel.y; if( level > 0 ) level = 0; rs.view.fov = 0.6 * M_PI * pow( 1.1, level ); } break; } //input { const uint8_t *keys; keys = SDL_GetKeyboardState( NULL ); V2Set( player.cmd_move, 0, 0 ); player.cmd_jump = false; player.cmd_down = false; player.cmd_sprint = false; player.cmd_noclip = false; if( keys[ SDL_SCANCODE_A ] ) player.cmd_move[ 0 ] -= 1.0; if( keys[ SDL_SCANCODE_D ] ) player.cmd_move[ 0 ] += 1.0; if( keys[ SDL_SCANCODE_W ] ) player.cmd_move[ 1 ] += 1.0; if( keys[ SDL_SCANCODE_S ] ) player.cmd_move[ 1 ] -= 1.0; if( keys[ SDL_SCANCODE_SPACE ] ) player.cmd_jump = true; if( keys[ SDL_SCANCODE_C] ) player.cmd_down = true; if( keys[ SDL_SCANCODE_LSHIFT ] ) player.cmd_sprint = true; if( keys[ SDL_SCANCODE_LCTRL ] ) player.cmd_noclip = true; } G_RunPlayer( &world, &player ); R_ViewFromPlayer( &rs, &player ); //physics { G_time_t frametime; frametime = U_Clock( ); while( phystime + 10 < frametime ) { size_t i; const vec_t delta = 0.01; for( i = 0; i < MAX_DLIGHTS; i++ ) { W_light_t *light = world.lights + i; if( !light->inuse ) continue; if( light->sun ) { vec_t t; t = 1.0e-6 * U_Microclock( ) * 0.3; V3Set( light->direction, cos( t ), sin( t ), -0.5 ); V3Normalize( light->direction ); continue; } if( light->flashlight ) { vec_t brightness; V3MA2( light->origin, player.origin, 0.1, player.matrix, 0.1, player.matrix + 3 ); V3Copy( light->direction, player.matrix ); brightness = (vec_t)rand() / RAND_MAX * 0.1 + 1.0 * 5.0; V3Set( light->light, 1.0, 0.8, 0.5 ); V3Mul( light->light, light->light, brightness ); continue; } light->velocity[ 2 ] += 5 * delta; { G_player_t fake; V3Copy( fake.origin, light->origin ); V3Copy( fake.velocity, light->velocity ); V3Set( fake.aabb[ 0 ], -0.1, -0.1, -0.1 ); V3Set( fake.aabb[ 1 ], 0.1, 0.1, 0.1 ); G_MovePlayer( &world, &fake, delta ); V3Copy( light->origin, fake.origin ); V3Copy( light->velocity, fake.velocity ); } if( frametime - light->timestamp > 8000 || light->origin[ 2 ] > 32 ) light->inuse = false; } phystime += 10; } } R_Render( &rs ); { //static uint64_t last = 0; //if( U_Microclock() > last + 500000 ) { size_t nodes, leaves; W_Stats( &world, &nodes, &leaves ); printf( "\r \r%.1f %zu(%zu)", rs.fps, nodes, leaves ); fflush( stdout ); //last = U_Microclock(); } } } main_loop_break: V3Copy( world.saved_origin, player.origin ); V3Copy( world.saved_angles, player.angles ); W_Save( &world, "world" ); W_DeleteWorld( &world ); R_Quit( &rs ); SDL_WaitThread( evthd, 0 ); SDL_Quit(); return 0; }