diff --git a/engine/client/cl_custom.c b/engine/client/cl_custom.c index a152fc25..d743fc60 100644 --- a/engine/client/cl_custom.c +++ b/engine/client/cl_custom.c @@ -70,6 +70,13 @@ qboolean CL_CheckFile( sizebuf_t *msg, resource_t *pResource ) return true; } + if( cl.downloadUrl[0] ) + { + HTTP_AddDownload( filepath, pResource->nDownloadSize, true ); + host.downloadcount++; + return false; + } + MSG_BeginClientCmd( msg, clc_stringcmd ); MSG_WriteString( msg, va( "dlfile %s", filepath )); host.downloadcount++; @@ -140,4 +147,4 @@ void CL_ClearResourceLists( void ) { CL_ClearResourceList( &cl.resourcesneeded ); CL_ClearResourceList( &cl.resourcesonhand ); -} \ No newline at end of file +} diff --git a/engine/client/cl_game.c b/engine/client/cl_game.c index a1041a43..7494c8e0 100644 --- a/engine/client/cl_game.c +++ b/engine/client/cl_game.c @@ -1049,6 +1049,15 @@ void CL_DrawHUD( int state ) } } +static void CL_ClearUserMessage( char *pszName, int svc_num ) +{ + int i; + + for( i = 0; i < MAX_USER_MESSAGES && clgame.msg[i].name[0]; i++ ) + if( ( clgame.msg[i].number == svc_num ) && Q_strcmp( clgame.msg[i].name, pszName ) ) + clgame.msg[i].number = 0; +} + void CL_LinkUserMessage( char *pszName, const int svc_num, int iSize ) { int i; @@ -1067,6 +1076,7 @@ void CL_LinkUserMessage( char *pszName, const int svc_num, int iSize ) { clgame.msg[i].number = svc_num; clgame.msg[i].size = iSize; + CL_ClearUserMessage( pszName, svc_num ); return; } } @@ -1081,6 +1091,7 @@ void CL_LinkUserMessage( char *pszName, const int svc_num, int iSize ) Q_strncpy( clgame.msg[i].name, pszName, sizeof( clgame.msg[i].name )); clgame.msg[i].number = svc_num; clgame.msg[i].size = iSize; + CL_ClearUserMessage( pszName, svc_num ); } void CL_FreeEntity( cl_entity_t *pEdict ) @@ -1215,7 +1226,7 @@ static qboolean CL_LoadHudSprite( const char *szSpriteName, model_t *m_pSprite, } else { - Con_Reportf( S_ERROR "%s couldn't load\n", szSpriteName ); + Con_Reportf( S_ERROR "Could not load HUD sprite %s\n", szSpriteName ); Mod_UnloadSpriteModel( m_pSprite ); return false; } @@ -2341,7 +2352,7 @@ int CL_FindModelIndex( const char *m ) if( lasttimewarn < host.realtime ) { // tell user about problem (but don't spam console) - Con_Printf( S_ERROR "%s not precached\n", filepath ); + Con_Printf( S_ERROR "Could not find index for model %s: not precached\n", filepath ); lasttimewarn = host.realtime + 1.0f; } diff --git a/engine/client/cl_gameui.c b/engine/client/cl_gameui.c index eaf9a890..c288d7a9 100644 --- a/engine/client/cl_gameui.c +++ b/engine/client/cl_gameui.c @@ -382,7 +382,7 @@ static HIMAGE pfnPIC_Load( const char *szPicName, const byte *image_buf, int ima if( !szPicName || !*szPicName ) { - Con_Reportf( S_ERROR "CL_LoadImage: bad name!\n" ); + Con_Reportf( S_ERROR "CL_LoadImage: refusing to load image with empty name\n" ); return 0; } diff --git a/engine/client/cl_main.c b/engine/client/cl_main.c index d5f6337e..ddb05055 100644 --- a/engine/client/cl_main.c +++ b/engine/client/cl_main.c @@ -1351,6 +1351,7 @@ void CL_ClearState( void ) Cvar_SetValue( "scr_download", -1.0f ); Cvar_SetValue( "scr_loading", 0.0f ); host.allow_console = host.allow_console_init; + HTTP_ClearCustomServers(); } /* @@ -1534,6 +1535,7 @@ void CL_LocalServers_f( void ) Con_Printf( "Scanning for servers on the local network area...\n" ); NET_Config( true ); // allow remote + cls.legacyservercount = 0; // send a broadcast packet adr.type = NA_BROADCAST; @@ -1561,6 +1563,7 @@ void CL_InternetServers_f( void ) Info_SetValueForKey( info, "gamedir", GI->gamefolder, remaining ); Info_SetValueForKey( info, "clver", XASH_VERSION, remaining ); // let master know about client version // Info_SetValueForKey( info, "nat", cl_nat->string, remaining ); + cls.legacyservercount = 0; cls.internetservers_wait = NET_SendToMasters( NS_CLIENT, sizeof( MS_SCAN_REQUEST ) + Q_strlen( info ), fullquery ); cls.internetservers_pending = true; @@ -1683,6 +1686,7 @@ void CL_ParseStatusMessage( netadr_t from, sizebuf_t *msg ) { static char infostring[MAX_INFO_STRING+8]; char *s = MSG_ReadString( msg ); + int i; CL_FixupColorStringsForInfoString( s, infostring ); @@ -1690,6 +1694,8 @@ void CL_ParseStatusMessage( netadr_t from, sizebuf_t *msg ) { Netchan_OutOfBandPrint( NS_CLIENT, from, "info %i", PROTOCOL_LEGACY_VERSION ); Con_Printf( "^1Server^7: %s, Info: %s\n", NET_AdrToString( from ), infostring ); + if( cls.legacyservercount < MAX_LEGACY_SERVERS ) + cls.legacyservers[cls.legacyservercount++] = from; return; } @@ -1699,6 +1705,16 @@ void CL_ParseStatusMessage( netadr_t from, sizebuf_t *msg ) return; // unsupported proto } + for( i = 0; i < cls.legacyservercount; i++ ) + { + if( NET_CompareAdr( cls.legacyservers[i], from ) ) + { + Info_SetValueForKey( infostring, "legacy", "1", sizeof( infostring ) ); + Con_Print("Legacy: "); + break; + } + } + // more info about servers Con_Printf( "^2Server^7: %s, Game: %s\n", NET_AdrToString( from ), Info_ValueForKey( infostring, "gamedir" )); @@ -2309,6 +2325,17 @@ void CL_ProcessFile( qboolean successfully_received, const char *filename ) { Con_Printf( S_ERROR "server failed to transmit file '%s'\n", CL_CleanFileName( filename )); } + if( cls.legacymode ) + { + if( host.downloadcount > 0 ) + host.downloadcount--; + if( !host.downloadcount ) + { + MSG_WriteByte( &cls.netchan.message, clc_stringcmd ); + MSG_WriteString( &cls.netchan.message, "continueloading" ); + } + return; + } pfilename = filename; @@ -2358,7 +2385,7 @@ void CL_ProcessFile( qboolean successfully_received, const char *filename ) } else { - Con_Printf( "Downloaded %i bytes for purported %i byte file, ignoring download\n", + Con_Printf( "Downloaded %i bytes for purported %i byte file, ignoring download\n", cls.netchan.tempbuffersize, p->nDownloadSize ); } @@ -2553,7 +2580,7 @@ qboolean CL_PrecacheResources( void ) { if( FBitSet( pRes->ucFlags, RES_WASMISSING )) { - Con_Printf( S_ERROR "%s%s couldn't load\n", DEFAULT_SOUNDPATH, pRes->szFileName ); + Con_Printf( S_ERROR "Could not load sound %s%s\n", DEFAULT_SOUNDPATH, pRes->szFileName ); cl.sound_precache[pRes->nIndex][0] = 0; cl.sound_index[pRes->nIndex] = 0; } diff --git a/engine/client/cl_parse.c b/engine/client/cl_parse.c index db23748d..ac6321c3 100644 --- a/engine/client/cl_parse.c +++ b/engine/client/cl_parse.c @@ -534,7 +534,11 @@ void CL_BatchResourceRequest( qboolean initialize ) if( cls.state != ca_disconnected ) { - if( !MSG_GetNumBytesWritten( &msg ) && CL_PrecacheResources( )) + if( !cl.downloadUrl[0] && !MSG_GetNumBytesWritten( &msg ) && CL_PrecacheResources( )) + { + CL_RegisterResources( &msg ); + } + if( cl.downloadUrl[0] && host.downloadcount == 0 && CL_PrecacheResources( ) ) { CL_RegisterResources( &msg ); } @@ -1702,16 +1706,23 @@ CL_ParseResLocation */ void CL_ParseResLocation( sizebuf_t *msg ) { - const char *url = MSG_ReadString( msg ); + const char *data = MSG_ReadString( msg ); + char token[256]; - if( url && ( !Q_strnicmp( "http://", url, 7 ) || !Q_strnicmp( "https://", url, 8 ))) + if( Q_strlen( data ) > 256 ) { - const char *lastSlash = Q_strrchr( url, '/' ); + Con_Printf( S_ERROR "Resource location too long!\n" ); + return; + } - if( lastSlash && lastSlash[1] == '\0' ) - Q_strncpy( cl.downloadUrl, url, sizeof( cl.downloadUrl )); - else Q_snprintf( cl.downloadUrl, sizeof( cl.downloadUrl ), "%s/", url ); - Con_Reportf( "Using %s as primary download location\n", cl.downloadUrl ); + while( ( data = COM_ParseFile( data, token ) ) ) + { + Con_Reportf( "Adding %s as download location\n", token ); + + if( !cl.downloadUrl[0] ) + Q_strncpy( cl.downloadUrl, token, sizeof( token ) ); + + HTTP_AddCustomServer( token ); } } @@ -2716,6 +2727,61 @@ void CL_LegacyUpdateUserinfo( sizebuf_t *msg ) else memset( player, 0, sizeof( *player )); } +/* +============== +CL_ParseResourceList + +============== +*/ +void CL_LegacyParseResourceList( sizebuf_t *msg ) +{ + int i = 0; + + static struct + { + int rescount; + int restype[MAX_RESOURCES]; + char resnames[MAX_RESOURCES][CS_SIZE]; + } reslist; + memset( &reslist, 0, sizeof( reslist )); + + reslist.rescount = MSG_ReadWord( msg ) - 1; + + for( i = 0; i < reslist.rescount; i++ ) + { + reslist.restype[i] = MSG_ReadWord( msg ); + Q_strncpy( reslist.resnames[i], MSG_ReadString( msg ), CS_SIZE ); + } + + if( CL_IsPlaybackDemo() ) + { + return; + } + + host.downloadcount = 0; + + for( i = 0; i < reslist.rescount; i++ ) + { + const char *path; + + if( reslist.restype[i] == t_sound ) + path = va( "sound/%s", reslist.resnames[i] ); + else path = reslist.resnames[i]; + + if( FS_FileExists( path, false )) + continue; // already exists + + host.downloadcount++; + HTTP_AddDownload( path, -1, true ); + } + + if( !host.downloadcount ) + { + MSG_WriteByte( &cls.netchan.message, clc_stringcmd ); + MSG_WriteString( &cls.netchan.message, "continueloading" ); + } +} + /* ===================== CL_ParseLegacyServerMessage @@ -2845,13 +2911,9 @@ void CL_ParseLegacyServerMessage( sizebuf_t *msg, qboolean normal_message ) if( !Q_strnicmp( s, "disconnect", 10 ) && cls.signon != SIGNONS ) break; // too early #endif - if( !Q_strcmp(s, "cmd getresourcelist\n") ) - Cbuf_AddText("cmd continueloading\n"); - else - { - Con_Reportf( "Stufftext: %s", s ); - Cbuf_AddText( s ); - } + + Con_Reportf( "Stufftext: %s", s ); + Cbuf_AddText( s ); break; case svc_setangle: CL_ParseSetAngle( msg ); @@ -2981,7 +3043,7 @@ void CL_ParseLegacyServerMessage( sizebuf_t *msg, qboolean normal_message ) //cl.frames[cls.netchan.incoming_sequence & CL_UPDATE_MASK].receivedtime = -2.0; break; case svc_resourcelist: - CL_ParseResourceList( msg ); + CL_LegacyParseResourceList( msg ); break; case svc_deltamovevars: CL_ParseMovevars( msg ); diff --git a/engine/client/client.h b/engine/client/client.h index b924dfb4..ebdeae01 100644 --- a/engine/client/client.h +++ b/engine/client/client.h @@ -663,6 +663,8 @@ typedef struct qboolean internetservers_pending; // internetservers is waiting for dns request qboolean legacymode; // one-way 48 protocol compatibility netadr_t legacyserver; + netadr_t legacyservers[MAX_LEGACY_SERVERS]; + int legacyservercount; int extensions; } client_static_t; diff --git a/engine/client/gl_decals.c b/engine/client/gl_decals.c index 97c79ad6..0c7ea2c6 100644 --- a/engine/client/gl_decals.c +++ b/engine/client/gl_decals.c @@ -54,7 +54,7 @@ typedef struct static float g_DecalClipVerts[MAX_DECALCLIPVERT][VERTEXSIZE]; static float g_DecalClipVerts2[MAX_DECALCLIPVERT][VERTEXSIZE]; -static decal_t gDecalPool[MAX_RENDER_DECALS]; +decal_t gDecalPool[MAX_RENDER_DECALS]; static int gDecalCount; void R_ClearDecals( void ) @@ -570,6 +570,7 @@ static void R_AddDecalToSurface( decal_t *pdecal, msurface_t *surf, decalinfo_t // alloc clipped poly for decal R_DecalCreatePoly( decalinfo, pdecal, surf ); + R_AddDecalVBO( pdecal, surf ); } static void R_DecalCreate( decalinfo_t *decalinfo, msurface_t *surf, float x, float y ) @@ -1282,4 +1283,4 @@ void R_ClearAllDecals( void ) { clgame.drawFuncs.R_ClearStudioDecals(); } -} \ No newline at end of file +} diff --git a/engine/client/gl_local.h b/engine/client/gl_local.h index fcc77848..b938295f 100644 --- a/engine/client/gl_local.h +++ b/engine/client/gl_local.h @@ -408,6 +408,9 @@ void GL_RebuildLightmaps( void ); void GL_InitRandomTable( void ); void GL_BuildLightmaps( void ); void GL_ResetFogColor( void ); +void R_GenerateVBO(); +void R_ClearVBO(); +void R_AddDecalVBO( decal_t *pdecal, msurface_t *surf ); // // gl_sprite.c @@ -523,6 +526,7 @@ enum GL_EXT_GPU_SHADER4, // shaders only GL_DEPTH_TEXTURE, GL_DEBUG_OUTPUT, + GL_ARB_VERTEX_BUFFER_OBJECT_EXT, GL_EXTCOUNT, // must be last }; @@ -678,6 +682,8 @@ extern convar_t *r_lockfrustum; extern convar_t *r_traceglow; extern convar_t *r_dynamic; extern convar_t *r_lightmap; +extern convar_t *r_vbo; +extern convar_t *r_vbo_dlightmode; extern convar_t *vid_displayfrequency; extern convar_t *vid_fullscreen; diff --git a/engine/client/gl_rmisc.c b/engine/client/gl_rmisc.c index a7d55c4e..87631756 100644 --- a/engine/client/gl_rmisc.c +++ b/engine/client/gl_rmisc.c @@ -199,4 +199,5 @@ void R_NewMap( void ) R_SetupSky( clgame.movevars.skyName ); GL_BuildLightmaps (); -} \ No newline at end of file + R_GenerateVBO(); +} diff --git a/engine/client/gl_rsurf.c b/engine/client/gl_rsurf.c index 7fff89a1..f3358346 100644 --- a/engine/client/gl_rsurf.c +++ b/engine/client/gl_rsurf.c @@ -42,6 +42,8 @@ static msurface_t *skychain = NULL; static gllightmapstate_t gl_lms; static void LM_UploadBlock( qboolean dynamic ); +static qboolean R_AddSurfToVBO( msurface_t *surf, qboolean buildlightmaps ); +static void R_DrawVBO( qboolean drawlightmaps, qboolean drawtextures ); byte *Mod_GetCurrentVis( void ) { @@ -391,11 +393,62 @@ void GL_BuildPolygonFromSurface( model_t *mod, msurface_t *fa ) poly->numverts = lnumverts; } + +/* +=============== +R_TextureAnim + +Returns the proper texture for a given time and base texture, do not process random tiling +=============== +*/ +texture_t *R_TextureAnim( texture_t *b ) +{ + texture_t *base = b; + int count, reletive; + + if( RI.currententity->curstate.frame ) + { + if( base->alternate_anims ) + base = base->alternate_anims; + } + + if( !base->anim_total ) + return base; + if( base->name[0] == '-' ) + { + return b; // already tiled + } + else + { + int speed; + + // Quake1 textures uses 10 frames per second + if( FBitSet( R_GetTexture( base->gl_texturenum )->flags, TF_QUAKEPAL )) + speed = 10; + else speed = 20; + + reletive = (int)(cl.time * speed) % base->anim_total; + } + + + count = 0; + + while( base->anim_min > reletive || base->anim_max <= reletive ) + { + base = base->anim_next; + + if( !base || ++count > MOD_FRAMES ) + return b; + } + + return base; +} + /* =============== R_TextureAnimation -Returns the proper texture for a given time and base texture +Returns the proper texture for a given time and surface =============== */ texture_t *R_TextureAnimation( msurface_t *s ) @@ -403,7 +456,7 @@ texture_t *R_TextureAnimation( msurface_t *s ) texture_t *base = s->texinfo->texture; int count, reletive; - if( RI.currententity->curstate.frame ) + if( RI.currententity && RI.currententity->curstate.frame ) { if( base->alternate_anims ) base = base->alternate_anims; @@ -600,6 +653,19 @@ static int LM_AllocBlock( int w, int h, int *x, int *y ) return true; } +static void LM_UploadDynamicBlock() +{ + int height = 0, i; + + for( i = 0; i < BLOCK_SIZE; i++ ) + { + if( gl_lms.allocated[i] > height ) + height = gl_lms.allocated[i]; + } + + pglTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, BLOCK_SIZE, height, GL_RGBA, GL_UNSIGNED_BYTE, gl_lms.lightmap_buffer ); +} + static void LM_UploadBlock( qboolean dynamic ) { int i; @@ -811,23 +877,15 @@ void DrawGLPolyChain( glpoly_t *p, float soffset, float toffset ) } } -/* -================ -R_BlendLightmaps -================ -*/ -void R_BlendLightmaps( void ) +_inline qboolean R_HasLightmap( void ) { - msurface_t *surf, *newsurf = NULL; - int i; - if( CVAR_TO_BOOL( r_fullbright ) || !cl.worldmodel->lightdata ) - return; + return false; if( RI.currententity ) { if( RI.currententity->curstate.effects & EF_FULLBRIGHT ) - return; // disabled by user + return false; // disabled by user // check for rendermode switch( RI.currententity->curstate.rendermode ) @@ -836,10 +894,26 @@ void R_BlendLightmaps( void ) case kRenderTransColor: case kRenderTransAdd: case kRenderGlow: - return; // no lightmaps + return false; // no lightmaps } } + return true; +} + +/* +================ +R_BlendLightmaps +================ +*/ +void R_BlendLightmaps( void ) +{ + msurface_t *surf, *newsurf = NULL; + int i; + + if( !R_HasLightmap() ) + return; + GL_SetupFogColorForSurfaces (); if( !CVAR_TO_BOOL( r_lightmap )) @@ -1448,11 +1522,16 @@ void R_DrawBrushModel( cl_entity_t *e ) model_t *clmodel; qboolean rotated; dlight_t *l; + qboolean allow_vbo = CVAR_TO_BOOL( r_vbo ); if( !RI.drawWorld ) return; clmodel = e->model; + // external models not loaded to VBO + if( clmodel->surfaces != cl.worldmodel->surfaces ) + allow_vbo = false; + if( !VectorIsNull( e->angles )) { for( i = 0; i < 3; i++ ) @@ -1505,7 +1584,13 @@ void R_DrawBrushModel( cl_entity_t *e ) GL_SetupFogColorForSurfaces (); if( e->curstate.rendermode == kRenderTransAdd ) + { R_AllowFog( false ); + allow_vbo = false; + } + + if( e->curstate.rendermode == kRenderTransColor || e->curstate.rendermode == kRenderTransTexture ) + allow_vbo = false; psurf = &clmodel->surfaces[clmodel->firstmodelsurface]; num_sorted = 0; @@ -1545,7 +1630,9 @@ void R_DrawBrushModel( cl_entity_t *e ) // draw sorted translucent surfaces for( i = 0; i < num_sorted; i++ ) - R_RenderBrushPoly( world.draw_surfaces[i].surf, world.draw_surfaces[i].cull ); + if( !allow_vbo || !R_AddSurfToVBO( world.draw_surfaces[i].surf, true ) ) + R_RenderBrushPoly( world.draw_surfaces[i].surf, world.draw_surfaces[i].cull ); + R_DrawVBO( R_HasLightmap(), true ); if( e->curstate.rendermode == kRenderTransColor ) pglEnable( GL_TEXTURE_2D ); @@ -1569,163 +1656,1520 @@ void R_DrawBrushModel( cl_entity_t *e ) R_LoadIdentity(); // restore worldmatrix } + /* -============================================================= +============================== - WORLD MODEL +VBO -============================================================= +============================== */ /* -================ -R_RecursiveWorldNode -================ +Bulld arrays (vboarray_t) for all map geometry on map load. +Store index base for every surface (vbosurfdata_t) to build index arrays +For each texture build index arrays (vbotexture_t) every frame. */ -void R_RecursiveWorldNode( mnode_t *node, uint clipflags ) +// vertex attribs +//#define NO_TEXTURE_MATRIX // need debug +typedef struct vbovertex_s { - int i, clipped; - msurface_t *surf, **mark; - mleaf_t *pleaf; - int c, side; - float dot; -loc0: - if( node->contents == CONTENTS_SOLID ) - return; // hit a solid leaf + vec3_t pos; + vec2_t gl_tc; + vec2_t lm_tc; +#ifdef NO_TEXTURE_MATRIX + vec2_t dt_tc; +#endif +} vbovertex_t; + +// store indexes for each texture +typedef struct vbotexture_s +{ + unsigned short *indexarray; // index array (generated instead of texture chains) + uint curindex; // counter for index array + uint len; // maximum index array length + struct vbotexture_s *next; // if cannot fit into one array, allocate new one, as every array has own index space + msurface_t *dlightchain; // list of dlight surfaces + struct vboarray_s *vboarray; // debug + uint lightmaptexturenum; +} vbotexture_t; + +// array list +typedef struct vboarray_s +{ + uint glindex; // glGenBuffers + int array_len; // allocation length + vbovertex_t *array; // vertex attrib array + struct vboarray_s *next; // split by 65536 vertices +} vboarray_t; + +// every surface is linked to vbo texture +typedef struct vbosurfdata_s +{ + vbotexture_t *vbotexture; + uint texturenum; + uint startindex; +} vbosurfdata_t; - if( node->visframe != tr.visframecount ) +typedef struct vbodecal_s +{ + int numVerts; +} vbodecal_t; + +#define DECAL_VERTS_MAX 32 +#define DECAL_VERTS_CUT 8 + +typedef struct vbodecaldata_s +{ + vbodecal_t decals[MAX_RENDER_DECALS]; + vbovertex_t decalarray[MAX_RENDER_DECALS * DECAL_VERTS_CUT]; + uint decalvbo; + msurface_t **lm; +} vbodecaldata_t; + +// gl_decals.c +extern decal_t gDecalPool[MAX_RENDER_DECALS]; + +struct vbo_static_s +{ + // quickly free all allocations on map change + byte *mempool; + + // arays + vbodecaldata_t *decaldata; // array + vbotexture_t *textures; // array + vbosurfdata_t *surfdata; // array + vboarray_t *arraylist; // linked list + + // separate areay for dlights (build during draw) + unsigned short *dlight_index; // array + vec2_t *dlight_tc; // array + unsigned int dlight_vbo; + vbovertex_t decal_dlight[MAX_RENDER_DECALS * DECAL_VERTS_MAX]; + unsigned int decal_dlight_vbo; + int decal_numverts[MAX_RENDER_DECALS * DECAL_VERTS_MAX]; + + // prevent draining cpu on empty cycles + int minlightmap; + int maxlightmap; + int mintexture; + int maxtexture; + + // never skip array splits + int minarraysplit_tex; + int maxarraysplit_tex; + int minarraysplit_lm; + int maxarraysplit_lm; +} vbos; + +struct multitexturestate_s +{ + int tmu_gl; // texture tmu + int tmu_dt; // detail tmu + int tmu_lm; // lightmap tmu + qboolean details_enabled; // current texture has details + int lm; // current lightmap texture + qboolean skiptexture; + gl_texture_t *glt; // details scale +} mtst; + +/* +=================== +R_GenerateVBO + +Allocate memory for arrays, fill it with vertex attribs and upload to GPU +=================== +*/ +void R_GenerateVBO() +{ + int numtextures = cl.worldmodel->numtextures; + int numlightmaps = gl_lms.current_lightmap_texture; + int k, len = 0; + vboarray_t *vbo; + uint maxindex = 0; + + R_ClearVBO(); + + // we do not want to write vbo code that does not use multitexture + if( !GL_Support( GL_ARB_VERTEX_BUFFER_OBJECT_EXT ) || !GL_Support( GL_ARB_MULTITEXTURE ) || glConfig.max_texture_units < 2 ) + { + Cvar_FullSet( "r_vbo", "0", FCVAR_READ_ONLY ); return; + } - if( clipflags && !CVAR_TO_BOOL( r_nocull )) + // save in config if enabled manually + if( CVAR_TO_BOOL( r_vbo ) ) + r_vbo->flags |= FCVAR_ARCHIVE; + + vbos.mempool = Mem_AllocPool("Render VBO Zone"); + + vbos.minarraysplit_tex = INT_MAX; + vbos.maxarraysplit_tex = 0; + vbos.minarraysplit_lm = MAXLIGHTMAPS; + vbos.maxarraysplit_lm = 0; + vbos.minlightmap = MAX_LIGHTMAPS; + vbos.maxlightmap = 0; + vbos.mintexture = INT_MAX; + vbos.maxtexture = 0; + + vbos.textures = Mem_Calloc( vbos.mempool, numtextures * numlightmaps * sizeof( vbotexture_t ) ); + vbos.surfdata = Mem_Calloc( vbos.mempool, cl.worldmodel->numsurfaces * sizeof( vbosurfdata_t ) ); + vbos.arraylist = vbo = Mem_Calloc( vbos.mempool, sizeof( vboarray_t ) ); + vbos.decaldata = Mem_Calloc( vbos.mempool, sizeof( vbodecaldata_t ) ); + vbos.decaldata->lm = Mem_Calloc( vbos.mempool, sizeof( msurface_t* ) * numlightmaps ); + + // count array lengths + for( k = 0; k < numlightmaps; k++ ) { - for( i = 0; i < 6; i++ ) + int j; + + for( j = 0; j < numtextures; j++ ) { - const mplane_t *p = &RI.frustum.planes[i]; + int i; + vbotexture_t *vbotex = &vbos.textures[k * numtextures + j]; - if( !FBitSet( clipflags, BIT( i ))) - continue; + for( i = 0; i < cl.worldmodel->numsurfaces; i++ ) + { + msurface_t *surf = &cl.worldmodel->surfaces[i]; - clipped = BoxOnPlaneSide( node->minmaxs, node->minmaxs + 3, p ); - if( clipped == 2 ) return; - if( clipped == 1 ) ClearBits( clipflags, BIT( i )); + if( surf->flags & ( SURF_DRAWSKY | SURF_DRAWTURB | SURF_CONVEYOR | SURF_DRAWTURB_QUADS ) ) + continue; + + if( surf->lightmaptexturenum != k ) + continue; + + if( R_TextureAnimation( surf ) != cl.worldmodel->textures[j] ) + continue; + + if( vbo->array_len + surf->polys->numverts > USHRT_MAX ) + { + // generate new array and new vbotexture node + vbo->array = Mem_Calloc( vbos.mempool, sizeof( vbovertex_t ) * vbo->array_len ); + Msg( "R_GenerateVBOs: allocated array of %d verts, texture %d\n", vbo->array_len, j ); + vbo->next = Mem_Calloc( vbos.mempool, sizeof( vboarray_t ) ); + vbo = vbo->next; + vbotex->next = Mem_Calloc( vbos.mempool, sizeof( vbotexture_t ) ); + vbotex = vbotex->next; + + // never skip this textures and lightmaps + if( vbos.minarraysplit_tex > j ) + vbos.minarraysplit_tex = j; + if( vbos.minarraysplit_lm > k ) + vbos.minarraysplit_lm = k; + if( vbos.maxarraysplit_tex < j + 1 ) + vbos.maxarraysplit_tex = j + 1; + if( vbos.maxarraysplit_lm < k + 1 ) + vbos.maxarraysplit_lm = k + 1; + } + vbos.surfdata[i].vbotexture = vbotex; + vbos.surfdata[i].startindex = vbo->array_len; + vbos.surfdata[i].texturenum = j; + vbo->array_len += surf->polys->numverts; + vbotex->len += surf->polys->numverts; + vbotex->vboarray = vbo; + } } } - // if a leaf node, draw stuff - if( node->contents < 0 ) - { - pleaf = (mleaf_t *)node; + // allocate last array + vbo->array = Mem_Calloc( vbos.mempool, sizeof( vbovertex_t ) * vbo->array_len ); + Msg( "R_GenerateVBOs: allocated array of %d verts\n", vbo->array_len ); - mark = pleaf->firstmarksurface; - c = pleaf->nummarksurfaces; + // switch to list begin + vbo = vbos.arraylist; - if( c ) + // fill and upload + for( k = 0; k < numlightmaps; k++ ) + { + int j; + + for( j = 0; j < numtextures; j++ ) { - do + int i; + vbotexture_t *vbotex = &vbos.textures[k * numtextures + j]; + + // preallocate index arrays + vbotex->indexarray = Mem_Calloc( vbos.mempool, sizeof( unsigned short ) * 6 * vbotex->len ); + vbotex->lightmaptexturenum = k; + + if( maxindex < vbotex->len ) + maxindex = vbotex->len; + + for( i = 0; i < cl.worldmodel->numsurfaces; i++ ) { - (*mark)->visframe = tr.framecount; - mark++; - } while( --c ); - } + msurface_t *surf = &cl.worldmodel->surfaces[i]; + int l; - // deal with model fragments in this leaf - if( pleaf->efrags ) - R_StoreEfrags( &pleaf->efrags, tr.realframecount ); + if( surf->flags & ( SURF_DRAWSKY | SURF_DRAWTURB | SURF_CONVEYOR | SURF_DRAWTURB_QUADS ) ) + continue; - r_stats.c_world_leafs++; - return; - } + if( surf->lightmaptexturenum != k ) + continue; - // node is just a decision point, so go down the apropriate sides + if( R_TextureAnimation( surf ) != cl.worldmodel->textures[j] ) + continue; - // find which side of the node we are on - dot = PlaneDiff( tr.modelorg, node->plane ); - side = (dot >= 0.0f) ? 0 : 1; + // switch to next array + if( len + surf->polys->numverts > USHRT_MAX ) + { + // upload last generated array + pglGenBuffersARB( 1, &vbo->glindex ); + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, vbo->glindex ); + pglBufferDataARB( GL_ARRAY_BUFFER_ARB, vbo->array_len * sizeof( vbovertex_t ), vbo->array, GL_STATIC_DRAW_ARB ); - // recurse down the children, front side first - R_RecursiveWorldNode( node->children[side], clipflags ); + ASSERT( len == vbo->array_len ); - // draw stuff - for( c = node->numsurfaces, surf = cl.worldmodel->surfaces + node->firstsurface; c; c--, surf++ ) - { - if( R_CullSurface( surf, &RI.frustum, clipflags )) - continue; + vbo = vbo->next; + vbotex = vbotex->next; + vbotex->indexarray = Mem_Calloc( vbos.mempool, sizeof( unsigned short ) * 6 * vbotex->len ); + vbotex->lightmaptexturenum = k; - if( surf->flags & SURF_DRAWSKY ) - { - // make sky chain to right clip the skybox - surf->texturechain = skychain; - skychain = surf; - } - else - { - surf->texturechain = surf->texinfo->texture->texturechain; - surf->texinfo->texture->texturechain = surf; + // calculate limits for dlights + if( maxindex < vbotex->len ) + maxindex = vbotex->len; + + len = 0; + } + + // fill vbovertex_t + for( l = 0; l < surf->polys->numverts; l++ ) + { + float *v = surf->polys->verts[l]; + + VectorCopy( v, vbo->array[len + l].pos ); + vbo->array[len + l].gl_tc[0] = v[3]; + vbo->array[len + l].gl_tc[1] = v[4]; + vbo->array[len + l].lm_tc[0] = v[5]; + vbo->array[len + l].lm_tc[1] = v[6]; +#ifdef NO_TEXTURE_MATRIX + if( cl.worldmodel->textures[j]->dt_texturenum ) + { + gl_texture_t *glt = R_GetTexture( cl.worldmodel->textures[j]->gl_texturenum ); + vbo->array[len + l].dt_tc[0] = v[3] * glt->xscale; + vbo->array[len + l].dt_tc[1] = v[4] * glt->yscale; + } +#endif + } + + len += surf->polys->numverts; + + } } } + ASSERT( len == vbo->array_len ); - // recurse down the back side - node = node->children[!side]; - goto loc0; -} + // upload last array + pglGenBuffersARB( 1, &vbo->glindex ); + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, vbo->glindex ); + pglBufferDataARB( GL_ARRAY_BUFFER_ARB, vbo->array_len * sizeof( vbovertex_t ), vbo->array, GL_STATIC_DRAW_ARB ); -/* -================ -R_CullNodeTopView + // prepare decal array + pglGenBuffersARB( 1, &vbos.decaldata->decalvbo ); + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, vbos.decaldata->decalvbo ); + pglBufferDataARB( GL_ARRAY_BUFFER_ARB, sizeof( vbovertex_t ) * DECAL_VERTS_CUT * MAX_RENDER_DECALS, vbos.decaldata->decalarray, GL_DYNAMIC_DRAW_ARB ); -cull node by user rectangle (simple scissor) -================ -*/ -qboolean R_CullNodeTopView( mnode_t *node ) -{ - vec2_t delta, size; - vec3_t center, half; + // preallocate dlight arrays + vbos.dlight_index = Mem_Calloc( vbos.mempool, maxindex * sizeof( unsigned short ) * 6 ); - // build the node center and half-diagonal - VectorAverage( node->minmaxs, node->minmaxs + 3, center ); - VectorSubtract( node->minmaxs + 3, center, half ); + // select maximum possible length for dlight + vbos.dlight_tc = Mem_Calloc( vbos.mempool, sizeof( vec2_t ) * (int)(vbos.arraylist->next?USHRT_MAX + 1:vbos.arraylist->array_len + 1) ); - // cull against the screen frustum or the appropriate area's frustum. - Vector2Subtract( center, world_orthocenter, delta ); - Vector2Add( half, world_orthohalf, size ); + if( CVAR_TO_BOOL(r_vbo_dlightmode) ) + { + pglGenBuffersARB( 1, &vbos.dlight_vbo ); + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, vbos.dlight_vbo ); + pglBufferDataARB( GL_ARRAY_BUFFER_ARB, sizeof( vec2_t ) * (int)(vbos.arraylist->next?USHRT_MAX + 1:vbos.arraylist->array_len + 1) , vbos.dlight_tc, GL_STREAM_DRAW_ARB ); + pglGenBuffersARB( 1, &vbos.decal_dlight_vbo ); + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, vbos.decal_dlight_vbo ); + pglBufferDataARB( GL_ARRAY_BUFFER_ARB, sizeof( vbos.decal_dlight ), vbos.decal_dlight, GL_STREAM_DRAW_ARB ); + } - return ( fabs( delta[0] ) > size[0] ) || ( fabs( delta[1] ) > size[1] ); + + // reset state + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, 0 ); + mtst.tmu_gl = XASH_TEXTURE0; } /* -================ -R_DrawTopViewLeaf -================ +============== +R_AddDecalVBO + +generate decal mesh and put it to array +============== */ -static void R_DrawTopViewLeaf( mleaf_t *pleaf, uint clipflags ) +void R_AddDecalVBO( decal_t *pdecal, msurface_t *surf ) { - msurface_t **mark, *surf; - int i; - - for( i = 0, mark = pleaf->firstmarksurface; i < pleaf->nummarksurfaces; i++, mark++ ) - { - surf = *mark; - - // don't process the same surface twice - if( surf->visframe == tr.framecount ) - continue; + int numVerts, i; + float *v; + int decalindex = pdecal - &gDecalPool[0]; - surf->visframe = tr.framecount; + if( !vbos.decaldata ) + return; - if( R_CullSurface( surf, &RI.frustum, clipflags )) - continue; + v = R_DecalSetupVerts( pdecal, surf, pdecal->texture, &numVerts ); - if(!( surf->flags & SURF_DRAWSKY )) - { - surf->texturechain = surf->texinfo->texture->texturechain; - surf->texinfo->texture->texturechain = surf; - } + if( numVerts > DECAL_VERTS_CUT ) + { + // use client arrays + vbos.decaldata->decals[decalindex].numVerts = -1; + return; } - // deal with model fragments in this leaf - if( pleaf->efrags ) - R_StoreEfrags( &pleaf->efrags, tr.realframecount ); + for( i = 0; i < numVerts; i++ ) + memcpy( &vbos.decaldata->decalarray[decalindex * DECAL_VERTS_CUT + i], v + i * VERTEXSIZE, VERTEXSIZE * 4 ); - r_stats.c_world_leafs++; + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, vbos.decaldata->decalvbo ); + pglBufferSubDataARB( GL_ARRAY_BUFFER_ARB, decalindex * sizeof( vbovertex_t ) * DECAL_VERTS_CUT, sizeof( vbovertex_t ) * numVerts, &vbos.decaldata->decalarray[decalindex * DECAL_VERTS_CUT] ); + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, 0 ); + + vbos.decaldata->decals[decalindex].numVerts = numVerts; +} + +/* +============= +R_ClearVBO + +free all vbo data +============= +*/ +void R_ClearVBO() +{ + vboarray_t *vbo; + + for( vbo = vbos.arraylist; vbo; vbo = vbo->next ) + pglDeleteBuffersARB( 1, &vbo->glindex ); + + vbos.arraylist = NULL; + + if( vbos.decaldata ) + pglDeleteBuffersARB( 1, &vbos.decaldata->decalvbo ); + + if( vbos.dlight_vbo ) + pglDeleteBuffersARB( 1, &vbos.dlight_vbo ); + + if( vbos.decal_dlight_vbo ) + pglDeleteBuffersARB( 1, &vbos.decal_dlight_vbo ); + vbos.decal_dlight_vbo = vbos.dlight_vbo = 0; + + vbos.decaldata = NULL; + Mem_FreePool( &vbos.mempool ); +} + + +/* +=================== +R_DisableDetail + +disable detail tmu +=================== +*/ +static void R_DisableDetail( void ) +{ + if( mtst.details_enabled && mtst.tmu_dt != -1 ) + { + GL_SelectTexture( mtst.tmu_dt ); + pglDisableClientState( GL_TEXTURE_COORD_ARRAY ); + pglDisable( GL_TEXTURE_2D ); + pglLoadIdentity(); + } +} + +/* +=================== +R_EnableDetail + +enable detail tmu if availiable +=================== +*/ +static void R_EnableDetail( void ) +{ + if( mtst.details_enabled && mtst.tmu_dt != -1 ) + { + GL_SelectTexture( mtst.tmu_dt ); + pglEnableClientState( GL_TEXTURE_COORD_ARRAY ); + pglEnable( GL_TEXTURE_2D ); + pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_ARB ); + pglTexEnvi( GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_MODULATE ); + pglTexEnvi( GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_PREVIOUS_ARB ); + pglTexEnvi( GL_TEXTURE_ENV, GL_SOURCE1_RGB_ARB, GL_TEXTURE ); + pglTexEnvi( GL_TEXTURE_ENV, GL_RGB_SCALE_ARB, 2 ); + + // use transform matrix for details (undone) +#ifndef NO_TEXTURE_MATRIX + pglTexCoordPointer( 2, GL_FLOAT, sizeof( vbovertex_t ), (void*)offsetof(vbovertex_t, gl_tc ) ); + pglMatrixMode( GL_TEXTURE ); + pglLoadIdentity(); + pglScalef( mtst.glt->xscale, mtst.glt->yscale, 1 ); +#else + pglTexCoordPointer( 2, GL_FLOAT, sizeof( vbovertex_t ), (void*)offsetof(vbovertex_t, dt_tc ) ); +#endif + } +} + +/* +============== +R_SetLightmap + +enable lightmap on current tmu +============== +*/ +static void R_SetLightmap( void ) +{ + if( mtst.skiptexture ) + return; + + /*if( gl_overbright->integer ) + { + pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_ARB ); + pglTexEnvi( GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_MODULATE ); + pglTexEnvi( GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_PREVIOUS_ARB ); + pglTexEnvi( GL_TEXTURE_ENV, GL_SOURCE1_RGB_ARB, GL_TEXTURE ); + pglTexEnvi( GL_TEXTURE_ENV, GL_RGB_SCALE_ARB, 2 ); + + } + else*/ + pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); + pglTexCoordPointer( 2, GL_FLOAT, sizeof( vbovertex_t ), (void*)offsetof(vbovertex_t, lm_tc ) ); +} + +/* +============== +R_SetDecalMode + +When drawing decal, disable or restore bump and details +set tmu to lightmap when enabled +============== +*/ +static void R_SetDecalMode( qboolean enable ) +{ + // order is important to correctly rearrange TMUs + if( enable ) + { + // disable detail texture if enabled + R_DisableDetail(); + } + else + { + R_EnableDetail(); + } + +} + +/* +============== +R_SetupVBOTexture + +setup multitexture mode before drawing VBOs +if tex is NULL, load texture by number +============== +*/ +static texture_t *R_SetupVBOTexture( texture_t *tex, int number ) +{ + if( mtst.skiptexture ) + return tex; + + if( !tex ) + tex = R_TextureAnim( cl.worldmodel->textures[number] ); + + if( CVAR_TO_BOOL( r_detailtextures ) && tex->dt_texturenum && mtst.tmu_dt != -1 ) + { + mtst.details_enabled = true; + GL_Bind( mtst.tmu_dt, tex->dt_texturenum ); + mtst.glt = R_GetTexture( tex->gl_texturenum ); + R_EnableDetail(); + } + else R_DisableDetail(); + + GL_Bind( mtst.tmu_gl, CVAR_TO_BOOL( r_lightmap )?tr.whiteTexture:tex->gl_texturenum ); + + return tex; +} + +/* +=================== +R_AdditionalPasses + +draw details when not enough tmus +=================== +*/ +static void R_AdditionalPasses( vboarray_t *vbo, int indexlen, void *indexarray, texture_t *tex, qboolean resetvbo ) +{ + // draw details in additional pass + if( r_detailtextures->value && mtst.tmu_dt == -1 && tex->dt_texturenum ) + { + gl_texture_t *glt = R_GetTexture( tex->gl_texturenum ); + + GL_SelectTexture( XASH_TEXTURE1 ); + pglDisable( GL_TEXTURE_2D ); + + // setup detail + GL_Bind( XASH_TEXTURE0, tex->dt_texturenum ); + pglEnable( GL_BLEND ); + pglBlendFunc( GL_DST_COLOR, GL_SRC_COLOR ); + pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL ); + + // when drawing dlights, we need to bind array and unbind it again + if( resetvbo ) + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, vbo->glindex ); + + pglTexCoordPointer( 2, GL_FLOAT, sizeof( vbovertex_t ), (void*)offsetof( vbovertex_t, gl_tc ) ); + + // apply scale + pglMatrixMode( GL_TEXTURE ); + pglLoadIdentity(); + pglScalef( glt->xscale, glt->yscale, 1 ); + + // draw +#if !defined XASH_NANOGL || defined XASH_WES && defined __EMSCRIPTEN__ // WebGL need to know array sizes + if( pglDrawRangeElements ) + pglDrawRangeElements( GL_TRIANGLES, 0, vbo->array_len, indexlen, GL_UNSIGNED_SHORT, indexarray ); + else +#endif + pglDrawElements( GL_TRIANGLES, indexlen, GL_UNSIGNED_SHORT, indexarray ); + + + // restore state + pglLoadIdentity(); + pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE ); + pglDisable( GL_BLEND ); + GL_Bind( XASH_TEXTURE1, mtst.lm ); + pglTexCoordPointer( 2, GL_FLOAT, sizeof( vbovertex_t ), (void*)offsetof( vbovertex_t, lm_tc ) ); + + GL_SelectTexture( XASH_TEXTURE1 ); + pglEnable( GL_TEXTURE_2D ); + if( resetvbo ) + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, 0 ); + } +} + +/* +===================== +R_DrawLightmappedVBO + +Draw array for given vbotexture_t. build and draw dynamic lightmaps if present +===================== +*/ +static void R_DrawLightmappedVBO( vboarray_t *vbo, vbotexture_t *vbotex, texture_t *texture, int lightmap, qboolean skiplighting ) +{ +#if !defined XASH_NANOGL || defined XASH_WES && defined __EMSCRIPTEN__ // WebGL need to know array sizes + if( pglDrawRangeElements ) + pglDrawRangeElements( GL_TRIANGLES, 0, vbo->array_len, vbotex->curindex, GL_UNSIGNED_SHORT, vbotex->indexarray ); + else +#endif + pglDrawElements( GL_TRIANGLES, vbotex->curindex, GL_UNSIGNED_SHORT, vbotex->indexarray ); + + R_AdditionalPasses( vbo, vbotex->curindex, vbotex->indexarray, texture, false ); + + // draw debug lines + if( CVAR_TO_BOOL(gl_wireframe) && !skiplighting ) + { + R_SetDecalMode( true ); + pglDisable( GL_TEXTURE_2D ); + GL_SelectTexture( XASH_TEXTURE0 ); + pglDisable( GL_TEXTURE_2D ); + pglDisable( GL_DEPTH_TEST ); +#if !defined XASH_NANOGL || defined XASH_WES && defined __EMSCRIPTEN__ // WebGL need to know array sizes + if( pglDrawRangeElements ) + pglDrawRangeElements( GL_LINES, 0, vbo->array_len, vbotex->curindex, GL_UNSIGNED_SHORT, vbotex->indexarray ); + else +#endif + pglDrawElements( GL_LINES, vbotex->curindex, GL_UNSIGNED_SHORT, vbotex->indexarray ); + pglEnable( GL_DEPTH_TEST ); + pglEnable( GL_TEXTURE_2D ); + GL_SelectTexture( XASH_TEXTURE1 ); + pglEnable( GL_TEXTURE_2D ); + R_SetDecalMode( false ); + } + //Msg( "%d %d %d\n", vbo->array_len, vbotex->len, lightmap ); + if( skiplighting ) + { + vbotex->curindex = 0; + vbotex->dlightchain = NULL; + return; + } + + // draw dlights and dlighted decals + if( vbotex->dlightchain ) + { + unsigned short *dlightarray = vbos.dlight_index; // preallocated array + unsigned int dlightindex = 0; + msurface_t *surf, *newsurf; + int decalcount = 0; + + + GL_Bind( mtst.tmu_lm, tr.dlightTexture ); + + // replace lightmap texcoord array by dlight array + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, vbos.dlight_vbo ); + if( vbos.dlight_vbo ) + pglTexCoordPointer( 2, GL_FLOAT, sizeof( float ) * 2, 0 ); + else + pglTexCoordPointer( 2, GL_FLOAT, sizeof( float ) * 2, vbos.dlight_tc ); + + // clear the block + LM_InitBlock(); + + // accumulate indexes for every dlighted surface until dlight block full + for( surf = newsurf = vbotex->dlightchain; surf; surf = surf->info->lightmapchain ) + { + int smax, tmax; + byte *base; + uint indexbase = vbos.surfdata[((char*)surf - (char*)cl.worldmodel->surfaces) / sizeof( *surf )].startindex; + uint index; + mextrasurf_t *info; // this stores current dlight offset + decal_t *pdecal; + int sample_size; + + info = surf->info; + sample_size = Mod_SampleSizeForFace( surf ); + smax = ( info->lightextents[0] / sample_size ) + 1; + tmax = ( info->lightextents[1] / sample_size ) + 1; + + + // find space for this surface and get offsets + if( LM_AllocBlock( smax, tmax, &info->dlight_s, &info->dlight_t )) + { + base = gl_lms.lightmap_buffer; + base += ( info->dlight_t * BLOCK_SIZE + info->dlight_s ) * 4; + + R_BuildLightMap( surf, base, BLOCK_SIZE * 4, true ); + } + else + { + // out of free block space. Draw all generated index array and clear it + // upload already generated block + LM_UploadDynamicBlock(); +#if !defined XASH_NANOGL || defined XASH_WES && defined __EMSCRIPTEN__ // WebGL need to know array sizes + if( pglDrawRangeElements ) + pglDrawRangeElements( GL_TRIANGLES, 0, vbo->array_len, dlightindex, GL_UNSIGNED_SHORT, dlightarray ); + else +#endif + pglDrawElements( GL_TRIANGLES, dlightindex, GL_UNSIGNED_SHORT, dlightarray ); + + // draw decals that lighted with this lightmap + if( decalcount ) + { + msurface_t *decalsurf; + int decali = 0; + + pglDepthMask( GL_FALSE ); + pglEnable( GL_BLEND ); + pglEnable( GL_POLYGON_OFFSET_FILL ); + if( RI.currententity->curstate.rendermode == kRenderTransAlpha ) + pglDisable( GL_ALPHA_TEST ); + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, vbos.decal_dlight_vbo ); + R_SetDecalMode( true ); + if( vbos.decal_dlight_vbo ) + { + pglTexCoordPointer( 2, GL_FLOAT, sizeof( vbovertex_t ), offsetof( vbovertex_t, lm_tc ) ); + GL_SelectTexture( mtst.tmu_gl ); + pglTexCoordPointer( 2, GL_FLOAT, sizeof( vbovertex_t ), offsetof( vbovertex_t, gl_tc ) ); + pglVertexPointer( 3, GL_FLOAT, sizeof( vbovertex_t ), offsetof( vbovertex_t, pos ) ); + } + else + { + pglTexCoordPointer( 2, GL_FLOAT, sizeof( vbovertex_t ), &vbos.decal_dlight[0].lm_tc ); + GL_SelectTexture( mtst.tmu_gl ); + pglTexCoordPointer( 2, GL_FLOAT, sizeof( vbovertex_t ), &vbos.decal_dlight[0].gl_tc ); + pglVertexPointer( 3, GL_FLOAT, sizeof( vbovertex_t ), &vbos.decal_dlight[0].pos); + } + + for( decalsurf = newsurf; ( decali < decalcount ) && ( decalsurf != surf ); decalsurf = decalsurf->info->lightmapchain ) + { + for( pdecal = decalsurf->pdecals; pdecal; pdecal = pdecal->pnext ) + { + gl_texture_t *glt; + + if( !pdecal->texture ) + continue; + + glt = R_GetTexture( pdecal->texture ); + + GL_Bind( mtst.tmu_gl, pdecal->texture ); + + // normal HL decal with alpha-channel + if( glt->flags & TF_HAS_ALPHA ) + { + // draw transparent decals with GL_MODULATE + if( glt->fogParams[3] > 230 ) + pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); + else pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE ); + pglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); + } + else + { + // color decal like detail texture. Base color is 127 127 127 + pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE ); + pglBlendFunc( GL_DST_COLOR, GL_SRC_COLOR ); + } + + pglDrawArrays( GL_TRIANGLE_FAN, decali * DECAL_VERTS_MAX, vbos.decal_numverts[decali] ); + decali++; + } + newsurf = surf; + + } + + // restore states pointers for next dynamic lightmap + pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE ); + pglDepthMask( GL_TRUE ); + pglDisable( GL_BLEND ); + pglDisable( GL_POLYGON_OFFSET_FILL ); + if( RI.currententity->curstate.rendermode == kRenderTransAlpha ) + pglEnable( GL_ALPHA_TEST ); + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, vbos.dlight_vbo ); + R_SetDecalMode( false ); + GL_SelectTexture( mtst.tmu_lm ); + if( vbos.dlight_vbo ) + pglTexCoordPointer( 2, GL_FLOAT, sizeof( float ) * 2, 0 ); + else + pglTexCoordPointer( 2, GL_FLOAT, sizeof( float ) * 2, vbos.dlight_tc ); + + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, vbo->glindex ); + pglVertexPointer( 3, GL_FLOAT, sizeof( vbovertex_t ), (void*)offsetof(vbovertex_t,pos) ); + R_SetupVBOTexture( texture, 0 ); + pglTexCoordPointer( 2, GL_FLOAT, sizeof( vbovertex_t ), (void*)offsetof(vbovertex_t, gl_tc ) ); + + decalcount = 0; + } + + // clear the block + LM_InitBlock(); + dlightindex = 0; + + // try upload the block now + if( !LM_AllocBlock( smax, tmax, &info->dlight_s, &info->dlight_t )) + Host_Error( "AllocBlock: full\n" ); + + base = gl_lms.lightmap_buffer; + base += ( info->dlight_t * BLOCK_SIZE + info->dlight_s ) * 4; + + R_BuildLightMap( surf, base, BLOCK_SIZE * 4, true ); + } + + // build index and texcoords arrays + vbos.dlight_tc[indexbase][0] = surf->polys->verts[0][5] - ( surf->light_s - info->dlight_s ) * ( 1.0f / (float)BLOCK_SIZE ); + vbos.dlight_tc[indexbase][1] = surf->polys->verts[0][6] - ( surf->light_t - info->dlight_t ) * ( 1.0f / (float)BLOCK_SIZE ); + vbos.dlight_tc[indexbase + 1][0] = surf->polys->verts[1][5] - ( surf->light_s - info->dlight_s ) * ( 1.0f / (float)BLOCK_SIZE ); + vbos.dlight_tc[indexbase + 1][1] = surf->polys->verts[1][6] - ( surf->light_t - info->dlight_t ) * ( 1.0f / (float)BLOCK_SIZE ); + + for( index = indexbase + 2; index < indexbase + surf->polys->numverts; index++ ) + { + dlightarray[dlightindex++] = indexbase; + dlightarray[dlightindex++] = index - 1; + dlightarray[dlightindex++] = index; + vbos.dlight_tc[index][0] = surf->polys->verts[index - indexbase][5] - ( surf->light_s - info->dlight_s ) * ( 1.0f / (float)BLOCK_SIZE ); + vbos.dlight_tc[index][1] = surf->polys->verts[index - indexbase][6] - ( surf->light_t - info->dlight_t ) * ( 1.0f / (float)BLOCK_SIZE ); + } + + if( vbos.dlight_vbo ) + { + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, vbos.dlight_vbo ); + pglBufferSubDataARB( GL_ARRAY_BUFFER_ARB, sizeof( vec2_t ) * indexbase, sizeof( vec2_t )* surf->polys->numverts, vbos.dlight_tc + indexbase ); + } + + // if surface has decals, build decal array + for( pdecal = surf->pdecals; pdecal; pdecal = pdecal->pnext ) + { + int decalindex = pdecal - &gDecalPool[0]; + int numVerts = vbos.decaldata->decals[decalindex].numVerts; + int i; + + if( numVerts == -1 ) + { + // build decal array + float *v = R_DecalSetupVerts( pdecal, surf, pdecal->texture, &numVerts ); + + for( i = 0; i < numVerts; i++, v += VERTEXSIZE ) + { + VectorCopy( v, vbos.decal_dlight[decalcount * DECAL_VERTS_MAX + i].pos ); + vbos.decal_dlight[decalcount * DECAL_VERTS_MAX + i].gl_tc[0] = v[3]; + vbos.decal_dlight[decalcount * DECAL_VERTS_MAX + i].gl_tc[1] = v[4]; + vbos.decal_dlight[decalcount * DECAL_VERTS_MAX + i].lm_tc[0] = v[5] - ( surf->light_s - info->dlight_s ) * ( 1.0f / (float)BLOCK_SIZE ); + vbos.decal_dlight[decalcount * DECAL_VERTS_MAX + i].lm_tc[1] = v[6] - ( surf->light_t - info->dlight_t ) * ( 1.0f / (float)BLOCK_SIZE ); + } + } + else + { + // copy from vbo + for( i = 0; i < numVerts; i++ ) + { + VectorCopy( vbos.decaldata->decalarray[decalindex * DECAL_VERTS_CUT + i].pos, vbos.decal_dlight[decalcount * DECAL_VERTS_MAX + i].pos ); + vbos.decal_dlight[decalcount * DECAL_VERTS_MAX + i].gl_tc[0] = vbos.decaldata->decalarray[decalindex * DECAL_VERTS_CUT + i].gl_tc[0]; + vbos.decal_dlight[decalcount * DECAL_VERTS_MAX + i].gl_tc[1] = vbos.decaldata->decalarray[decalindex * DECAL_VERTS_CUT + i].gl_tc[1]; + vbos.decal_dlight[decalcount * DECAL_VERTS_MAX + i].lm_tc[0] = vbos.decaldata->decalarray[decalindex * DECAL_VERTS_CUT + i].lm_tc[0] - ( surf->light_s - info->dlight_s ) * ( 1.0f / (float)BLOCK_SIZE ); + vbos.decal_dlight[decalcount * DECAL_VERTS_MAX + i].lm_tc[1] = vbos.decaldata->decalarray[decalindex * DECAL_VERTS_CUT + i].lm_tc[1] - ( surf->light_t - info->dlight_t ) * ( 1.0f / (float)BLOCK_SIZE ); + } + } + if( vbos.dlight_vbo ) + { + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, vbos.decal_dlight_vbo ); + pglBufferSubDataARB( GL_ARRAY_BUFFER_ARB, sizeof( vbovertex_t ) * decalcount * DECAL_VERTS_MAX, sizeof( vbovertex_t )* numVerts, vbos.decal_dlight + decalcount * DECAL_VERTS_MAX ); + } + + vbos.decal_numverts[decalcount] = numVerts; + decalcount++; + } + //info->dlight_s = info->dlight_t = 0; + } + + if( dlightindex ) + { + // update block + LM_UploadDynamicBlock(); + + // draw remaining array +#if !defined XASH_NANOGL || defined XASH_WES && defined __EMSCRIPTEN__ // WebGL need to know array sizes + if( pglDrawRangeElements ) + pglDrawRangeElements( GL_TRIANGLES, 0, vbo->array_len, dlightindex, GL_UNSIGNED_SHORT, dlightarray ); + else +#endif + pglDrawElements( GL_TRIANGLES, dlightindex, GL_UNSIGNED_SHORT, dlightarray ); + + R_AdditionalPasses( vbo, dlightindex, dlightarray, texture, true ); + + // draw remaining decals + if( decalcount ) + { + msurface_t *decalsurf; + int decali = 0; + + pglDepthMask( GL_FALSE ); + pglEnable( GL_BLEND ); + pglEnable( GL_POLYGON_OFFSET_FILL ); + if( RI.currententity->curstate.rendermode == kRenderTransAlpha ) + pglDisable( GL_ALPHA_TEST ); + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, vbos.decal_dlight_vbo ); + R_SetDecalMode( true ); + if( vbos.decal_dlight_vbo ) + { + pglTexCoordPointer( 2, GL_FLOAT, sizeof( vbovertex_t ), offsetof( vbovertex_t, lm_tc ) ); + GL_SelectTexture( mtst.tmu_gl ); + pglTexCoordPointer( 2, GL_FLOAT, sizeof( vbovertex_t ), offsetof( vbovertex_t, gl_tc ) ); + pglVertexPointer( 3, GL_FLOAT, sizeof( vbovertex_t ), offsetof( vbovertex_t, pos ) ); + } + else + { + pglTexCoordPointer( 2, GL_FLOAT, sizeof( vbovertex_t ), &vbos.decal_dlight[0].lm_tc ); + GL_SelectTexture( mtst.tmu_gl ); + pglTexCoordPointer( 2, GL_FLOAT, sizeof( vbovertex_t ), &vbos.decal_dlight[0].gl_tc ); + pglVertexPointer( 3, GL_FLOAT, sizeof( vbovertex_t ), &vbos.decal_dlight[0].pos); + } + for( decalsurf = newsurf; decali < decalcount && decalsurf; decalsurf = decalsurf->info->lightmapchain ) + { + decal_t *pdecal; + + for( pdecal = decalsurf->pdecals; pdecal; pdecal = pdecal->pnext ) + { + gl_texture_t *glt; + + if( !pdecal->texture ) + continue; + + glt = R_GetTexture( pdecal->texture ); + + GL_Bind( mtst.tmu_gl, pdecal->texture ); + + // normal HL decal with alpha-channel + if( glt->flags & TF_HAS_ALPHA ) + { + // draw transparent decals with GL_MODULATE + if( glt->fogParams[3] > 230 ) + pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); + else pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE ); + pglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); + } + else + { + // color decal like detail texture. Base color is 127 127 127 + pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE ); + pglBlendFunc( GL_DST_COLOR, GL_SRC_COLOR ); + } + + pglDrawArrays( GL_TRIANGLE_FAN, decali * DECAL_VERTS_MAX, vbos.decal_numverts[decali] ); + + decali++; + } + + } + + // reset states + pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE ); + pglDepthMask( GL_TRUE ); + pglDisable( GL_BLEND ); + pglDisable( GL_POLYGON_OFFSET_FILL ); + if( RI.currententity->curstate.rendermode == kRenderTransAlpha ) + pglEnable( GL_ALPHA_TEST ); + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, vbo->glindex ); + R_SetDecalMode( false ); + pglVertexPointer( 3, GL_FLOAT, sizeof( vbovertex_t ), (void*)offsetof(vbovertex_t,pos) ); + R_SetupVBOTexture( texture, 0 ); + pglTexCoordPointer( 2, GL_FLOAT, sizeof( vbovertex_t ), (void*)offsetof(vbovertex_t, gl_tc ) ); + } + } + + // restore static lightmap + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, vbo->glindex ); + GL_Bind( mtst.tmu_lm, tr.lightmapTextures[lightmap] ); + pglTexCoordPointer( 2, GL_FLOAT, sizeof( vbovertex_t ), (void*)offsetof(vbovertex_t, lm_tc ) ); + + // prepare to next frame + vbotex->dlightchain = NULL; + } + + // prepare to next frame + vbotex->curindex = 0; +} + +/* +===================== +R_DrawVBO + +Draw generated index arrays +===================== +*/ +void R_DrawVBO( qboolean drawlightmap, qboolean drawtextures ) +{ + int numtextures = cl.worldmodel->numtextures; + int numlightmaps = gl_lms.current_lightmap_texture; + int k; + vboarray_t *vbo = vbos.arraylist; + + if( !CVAR_TO_BOOL( r_vbo ) ) + return; + + // bind array + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, vbo->glindex ); + pglEnableClientState( GL_VERTEX_ARRAY ); + pglVertexPointer( 3, GL_FLOAT, sizeof( vbovertex_t ), (void*)offsetof(vbovertex_t,pos) ); + + // setup multitexture + if( drawtextures ) + { + GL_SelectTexture( mtst.tmu_gl = XASH_TEXTURE0 ); + pglEnable( GL_TEXTURE_2D ); + pglEnableClientState( GL_TEXTURE_COORD_ARRAY ); + pglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE ); + pglTexCoordPointer( 2, GL_FLOAT, sizeof( vbovertex_t ), (void*)offsetof(vbovertex_t, gl_tc ) ); + } + + if( drawlightmap ) + { + // set lightmap texenv + GL_SelectTexture( mtst.tmu_lm = XASH_TEXTURE1 ); + pglEnable( GL_TEXTURE_2D ); + pglEnableClientState( GL_TEXTURE_COORD_ARRAY ); + R_SetLightmap(); + } + + mtst.skiptexture = !drawtextures; + mtst.tmu_dt = glConfig.max_texture_units > 2? XASH_TEXTURE2:-1; + + // setup limits + if( vbos.minlightmap > vbos.minarraysplit_lm ) + vbos.minlightmap = vbos.minarraysplit_lm; + if( vbos.maxlightmap < vbos.maxarraysplit_lm ) + vbos.maxlightmap = vbos.maxarraysplit_lm; + if( vbos.maxlightmap > numlightmaps ) + vbos.maxlightmap = numlightmaps; + if( vbos.mintexture > vbos.minarraysplit_tex ) + vbos.mintexture = vbos.minarraysplit_tex; + if( vbos.maxtexture < vbos.maxarraysplit_tex ) + vbos.maxtexture = vbos.maxarraysplit_tex; + if( vbos.maxtexture > numtextures ) + vbos.maxtexture = numtextures; + + for( k = vbos.minlightmap; k < vbos.maxlightmap; k++ ) + { + int j; + msurface_t *lightmapchain; + + if( drawlightmap ) + { + GL_Bind( mtst.tmu_lm, mtst.lm = tr.lightmapTextures[k] ); + } + + for( j = vbos.mintexture; j < vbos.maxtexture; j++ ) + { + vbotexture_t *vbotex = &vbos.textures[k * numtextures + j]; + texture_t *tex = NULL; + if( !vbotex->vboarray ) + continue; + + ASSERT( vbotex->vboarray == vbo ); + + if( vbotex->curindex || vbotex->dlightchain ) + { + // draw textures static lightmap first + if( drawtextures ) + tex = R_SetupVBOTexture( NULL, j ); + + R_DrawLightmappedVBO( vbo, vbotex, tex, k, !drawlightmap ); + } + + // if we need to switch to next array (only if map has >65536 vertices) + while( vbotex->next ) + { + + vbotex = vbotex->next; + vbo = vbo->next; + + // bind new vertex and index arrays + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, vbo->glindex ); + pglVertexPointer( 3, GL_FLOAT, sizeof( vbovertex_t ), (void*)offsetof(vbovertex_t,pos) ); + + // update texcoord pointers + if( drawtextures ) + { + tex = R_SetupVBOTexture( tex, 0 ); + pglTexCoordPointer( 2, GL_FLOAT, sizeof( vbovertex_t ), (void*)offsetof(vbovertex_t, gl_tc ) ); + } + + if( drawlightmap ) + { + GL_Bind( mtst.tmu_lm, tr.lightmapTextures[k] ); + pglTexCoordPointer( 2, GL_FLOAT, sizeof( vbovertex_t ), (void*)offsetof(vbovertex_t, lm_tc ) ); + } + + // draw new array + if( (vbotex->curindex || vbotex->dlightchain) ) + R_DrawLightmappedVBO( vbo, vbotex, tex, k, !drawlightmap ); + } + } + + if( drawtextures && drawlightmap && vbos.decaldata->lm[k] ) + { + // prepare for decal draw + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, vbos.decaldata->decalvbo ); + pglDepthMask( GL_FALSE ); + pglEnable( GL_BLEND ); + pglEnable( GL_POLYGON_OFFSET_FILL ); + + if( RI.currententity->curstate.rendermode == kRenderTransAlpha ) + pglDisable( GL_ALPHA_TEST ); + + R_SetDecalMode( true ); + + // Set pointers to vbodecaldata->decalvbo + if( drawlightmap ) + { + GL_SelectTexture( mtst.tmu_lm ); + pglTexCoordPointer( 2, GL_FLOAT, sizeof( vbovertex_t ), (void*)offsetof(vbovertex_t, lm_tc ) ); + GL_SelectTexture( mtst.tmu_gl ); + pglTexCoordPointer( 2, GL_FLOAT, sizeof( vbovertex_t ), (void*)offsetof(vbovertex_t, gl_tc ) ); + pglVertexPointer( 3, GL_FLOAT, sizeof( vbovertex_t ), (void*)offsetof(vbovertex_t, pos ) ); + } + + // all surfaces having decals and this lightmap + for( lightmapchain = vbos.decaldata->lm[k]; lightmapchain; lightmapchain = lightmapchain->info->lightmapchain ) + { + decal_t *pdecal; + + // all decals of surface + for( pdecal = lightmapchain->pdecals; pdecal; pdecal = pdecal->pnext ) + { + gl_texture_t *glt; + int decalindex = pdecal - &gDecalPool[0]; + + if( !pdecal->texture ) + continue; + + glt = R_GetTexture( pdecal->texture ); + + GL_Bind( mtst.tmu_gl, pdecal->texture ); + + // normal HL decal with alpha-channel + if( glt->flags & TF_HAS_ALPHA ) + { + // draw transparent decals with GL_MODULATE + if( glt->fogParams[3] > 230 ) + pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); + else pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE ); + pglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); + } + else + { + // color decal like detail texture. Base color is 127 127 127 + pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE ); + pglBlendFunc( GL_DST_COLOR, GL_SRC_COLOR ); + } + + if( vbos.decaldata->decals[decalindex].numVerts == -1 ) + { + int numVerts; + float *v; + + v = R_DecalSetupVerts( pdecal, lightmapchain, pdecal->texture, &numVerts ); + + // to many verts to keep in sparse array, so build it now + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, 0 ); + pglVertexPointer( 3, GL_FLOAT, VERTEXSIZE * 4, v ); + pglTexCoordPointer( 2, GL_FLOAT, VERTEXSIZE * 4, v + 3 ); + if( drawlightmap ) + { + GL_SelectTexture( mtst.tmu_lm ); + pglTexCoordPointer( 2, GL_FLOAT, VERTEXSIZE * 4, v + 5 ); + } + + pglDrawArrays( GL_TRIANGLE_FAN, 0, numVerts ); + + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, vbos.decaldata->decalvbo ); + pglTexCoordPointer( 2, GL_FLOAT, sizeof( vbovertex_t ), (void*)offsetof(vbovertex_t, lm_tc ) ); + GL_SelectTexture( mtst.tmu_gl ); + pglTexCoordPointer( 2, GL_FLOAT, sizeof( vbovertex_t ), (void*)offsetof(vbovertex_t, gl_tc ) ); + pglVertexPointer( 3, GL_FLOAT, sizeof( vbovertex_t ), (void*)offsetof(vbovertex_t, pos ) ); + } + else // just draw VBO + pglDrawArrays( GL_TRIANGLE_FAN, decalindex * DECAL_VERTS_CUT, vbos.decaldata->decals[decalindex].numVerts ); + } + } + + // prepare for next frame + vbos.decaldata->lm[k] = NULL; + + // prepare for next texture + pglDepthMask( GL_TRUE ); + pglDisable( GL_BLEND ); + pglDisable( GL_POLYGON_OFFSET_FILL ); + + // restore vbo + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, vbo->glindex ); + pglVertexPointer( 3, GL_FLOAT, sizeof( vbovertex_t ), (void*)offsetof(vbovertex_t,pos) ); + + // restore bump if needed + R_SetDecalMode( false ); + + // restore texture + GL_SelectTexture( mtst.tmu_gl ); + pglEnableClientState( GL_TEXTURE_COORD_ARRAY ); + pglTexCoordPointer( 2, GL_FLOAT, sizeof( vbovertex_t ), (void*)offsetof(vbovertex_t, gl_tc ) ); + + // restore lightmap + GL_SelectTexture( mtst.tmu_lm ); + pglTexCoordPointer( 2, GL_FLOAT, sizeof( vbovertex_t ), (void*)offsetof(vbovertex_t, lm_tc ) ); + + if( RI.currententity->curstate.rendermode == kRenderTransAlpha ) + pglEnable( GL_ALPHA_TEST ); + } + if( !drawtextures || !drawlightmap ) + vbos.decaldata->lm[k] = NULL; + } + ASSERT( !vbo->next ); + + // restore states + R_DisableDetail(); + + if( drawlightmap ) + { + // reset states + GL_SelectTexture( XASH_TEXTURE1 ); + pglDisableClientState( GL_TEXTURE_COORD_ARRAY ); + pglDisable( GL_TEXTURE_2D ); + if( drawtextures ) + { + GL_SelectTexture( XASH_TEXTURE0 ); + pglEnable( GL_TEXTURE_2D ); + } + } + + if( drawtextures ) + pglDisableClientState( GL_TEXTURE_COORD_ARRAY ); + + mtst.details_enabled = false; + + vbos.minlightmap = MAX_LIGHTMAPS; + vbos.maxlightmap = 0; + vbos.mintexture = INT_MAX; + vbos.maxtexture = 0; + + pglDisableClientState( GL_VERTEX_ARRAY ); + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, 0 ); +} + +/* +================ +R_CheckLightMap + +update surface's lightmap if needed and return true if it is dynamic +================ +*/ +static qboolean R_CheckLightMap( msurface_t *fa ) +{ + int maps; + qboolean is_dynamic = false; + + // check for lightmap modification + for( maps = 0; maps < MAXLIGHTMAPS && fa->styles[maps] != 255; maps++ ) + { + if( tr.lightstylevalue[fa->styles[maps]] != fa->cached_light[maps] ) + { + is_dynamic = true; + break; + } + } + + // already up to date + if( !is_dynamic && ( fa->dlightframe != tr.framecount || maps == MAX_LIGHTMAPS ) ) + return false; + + // build lightmap + if(( fa->styles[maps] >= 32 || fa->styles[maps] == 0 ) && ( fa->dlightframe != tr.framecount )) + { + byte temp[132*132*4]; + int smax, tmax; + int sample_size; + mextrasurf_t *info; + + info = fa->info; + sample_size = Mod_SampleSizeForFace( fa ); + smax = ( info->lightextents[0] / sample_size ) + 1; + tmax = ( info->lightextents[1] / sample_size ) + 1; + + if( smax < 132 && tmax < 132 ) + { + R_BuildLightMap( fa, temp, smax * 4, true ); + } + else + { + smax = min( smax, 132 ); + tmax = min( tmax, 132 ); + //Host_MapDesignError( "R_RenderBrushPoly: bad surface extents: %d %d", fa->extents[0], fa->extents[1] ); + memset( temp, 255, sizeof( temp ) ); + } + + R_SetCacheState( fa ); +#ifdef XASH_WES + GL_Bind( XASH_TEXTURE1, tr.lightmapTextures[fa->lightmaptexturenum] ); + + pglTexParameteri( GL_TEXTURE_2D, GL_GENERATE_MIPMAP_SGIS, GL_TRUE ); +#else + GL_Bind( XASH_TEXTURE0, tr.lightmapTextures[fa->lightmaptexturenum] ); +#endif + + pglTexSubImage2D( GL_TEXTURE_2D, 0, fa->light_s, fa->light_t, smax, tmax, + GL_RGBA, GL_UNSIGNED_BYTE, temp ); +#ifdef XASH_WES + GL_SelectTexture( XASH_TEXTURE0 ); +#endif + } + // add to dynamic chain + else + return true; + + // updated + return false; +} + +qboolean R_AddSurfToVBO( msurface_t *surf, qboolean buildlightmap ) +{ + if( CVAR_TO_BOOL(r_vbo) && vbos.surfdata[surf - cl.worldmodel->surfaces].vbotexture ) + { + // find vbotexture_t assotiated with this surface + int idx = surf - cl.worldmodel->surfaces; + vbotexture_t *vbotex = vbos.surfdata[idx].vbotexture; + int texturenum = vbos.surfdata[idx].texturenum; + + if( !surf->polys ) + return true; + + if( vbos.maxlightmap < surf->lightmaptexturenum + 1 ) + vbos.maxlightmap = surf->lightmaptexturenum + 1; + if( vbos.minlightmap > surf->lightmaptexturenum ) + vbos.minlightmap = surf->lightmaptexturenum; + if( vbos.maxtexture < texturenum + 1 ) + vbos.maxtexture = texturenum + 1; + if( vbos.mintexture > texturenum ) + vbos.mintexture = texturenum; + + buildlightmap &= !CVAR_TO_BOOL( r_fullbright ) && !!cl.worldmodel->lightdata; + + if( buildlightmap && R_CheckLightMap( surf ) ) + { + // every vbotex has own lightmap chain (as we sorted if by textures to use multitexture) + surf->info->lightmapchain = vbotex->dlightchain; + vbotex->dlightchain = surf; + } + else + { + uint indexbase = vbos.surfdata[idx].startindex; + uint index; + + // GL_TRIANGLE_FAN: 0 1 2 0 2 3 0 3 4 ... + for( index = indexbase + 2; index < indexbase + surf->polys->numverts; index++ ) + { + vbotex->indexarray[vbotex->curindex++] = indexbase; + vbotex->indexarray[vbotex->curindex++] = index - 1; + vbotex->indexarray[vbotex->curindex++] = index; + } + + // if surface has decals, add it to decal lightmapchain + if( surf->pdecals ) + { + surf->info->lightmapchain = vbos.decaldata->lm[vbotex->lightmaptexturenum]; + vbos.decaldata->lm[vbotex->lightmaptexturenum] = surf; + } + } + return true; + } + return false; +} + +/* +============================================================= + + WORLD MODEL + +============================================================= +*/ +/* +================ +R_RecursiveWorldNode +================ +*/ +void R_RecursiveWorldNode( mnode_t *node, uint clipflags ) +{ + int i, clipped; + msurface_t *surf, **mark; + mleaf_t *pleaf; + int c, side; + float dot; +loc0: + if( node->contents == CONTENTS_SOLID ) + return; // hit a solid leaf + + if( node->visframe != tr.visframecount ) + return; + + if( clipflags && !CVAR_TO_BOOL( r_nocull )) + { + for( i = 0; i < 6; i++ ) + { + const mplane_t *p = &RI.frustum.planes[i]; + + if( !FBitSet( clipflags, BIT( i ))) + continue; + + clipped = BoxOnPlaneSide( node->minmaxs, node->minmaxs + 3, p ); + if( clipped == 2 ) return; + if( clipped == 1 ) ClearBits( clipflags, BIT( i )); + } + } + + // if a leaf node, draw stuff + if( node->contents < 0 ) + { + pleaf = (mleaf_t *)node; + + mark = pleaf->firstmarksurface; + c = pleaf->nummarksurfaces; + + if( c ) + { + do + { + (*mark)->visframe = tr.framecount; + mark++; + } while( --c ); + } + + // deal with model fragments in this leaf + if( pleaf->efrags ) + R_StoreEfrags( &pleaf->efrags, tr.realframecount ); + + r_stats.c_world_leafs++; + return; + } + + // node is just a decision point, so go down the apropriate sides + + // find which side of the node we are on + dot = PlaneDiff( tr.modelorg, node->plane ); + side = (dot >= 0.0f) ? 0 : 1; + + // recurse down the children, front side first + R_RecursiveWorldNode( node->children[side], clipflags ); + + // draw stuff + for( c = node->numsurfaces, surf = cl.worldmodel->surfaces + node->firstsurface; c; c--, surf++ ) + { + if( R_CullSurface( surf, &RI.frustum, clipflags )) + continue; + + if( surf->flags & SURF_DRAWSKY ) + { + // make sky chain to right clip the skybox + surf->texturechain = skychain; + skychain = surf; + } + else if( !R_AddSurfToVBO( surf, true ) ) + { + surf->texturechain = surf->texinfo->texture->texturechain; + surf->texinfo->texture->texturechain = surf; + } + } + + // recurse down the back side + node = node->children[!side]; + goto loc0; +} + +/* +================ +R_CullNodeTopView + +cull node by user rectangle (simple scissor) +================ +*/ +qboolean R_CullNodeTopView( mnode_t *node ) +{ + vec2_t delta, size; + vec3_t center, half; + + // build the node center and half-diagonal + VectorAverage( node->minmaxs, node->minmaxs + 3, center ); + VectorSubtract( node->minmaxs + 3, center, half ); + + // cull against the screen frustum or the appropriate area's frustum. + Vector2Subtract( center, world_orthocenter, delta ); + Vector2Add( half, world_orthohalf, size ); + + return ( fabs( delta[0] ) > size[0] ) || ( fabs( delta[1] ) > size[1] ); +} + +/* +================ +R_DrawTopViewLeaf +================ +*/ +static void R_DrawTopViewLeaf( mleaf_t *pleaf, uint clipflags ) +{ + msurface_t **mark, *surf; + int i; + + for( i = 0, mark = pleaf->firstmarksurface; i < pleaf->nummarksurfaces; i++, mark++ ) + { + surf = *mark; + + // don't process the same surface twice + if( surf->visframe == tr.framecount ) + continue; + + surf->visframe = tr.framecount; + + if( R_CullSurface( surf, &RI.frustum, clipflags )) + continue; + + if(!( surf->flags & SURF_DRAWSKY )) + { + surf->texturechain = surf->texinfo->texture->texturechain; + surf->texinfo->texture->texturechain = surf; + } + } + + // deal with model fragments in this leaf + if( pleaf->efrags ) + R_StoreEfrags( &pleaf->efrags, tr.realframecount ); + + r_stats.c_world_leafs++; } /* @@ -1893,6 +3337,8 @@ void R_DrawWorld( void ) r_stats.t_world_node = end - start; start = Sys_DoubleTime(); + R_DrawVBO( !CVAR_TO_BOOL(r_fullbright) && !!cl.worldmodel->lightdata, true ); + R_DrawTextureChains(); if( !CL_IsDevOverviewMode( )) diff --git a/engine/client/gl_studio.c b/engine/client/gl_studio.c index ed0ceff4..814a3ad0 100644 --- a/engine/client/gl_studio.c +++ b/engine/client/gl_studio.c @@ -102,13 +102,22 @@ typedef struct vec4_t lightpos[MAXSTUDIOVERTS][MAX_LOCALLIGHTS]; vec3_t lightbonepos[MAXSTUDIOBONES][MAX_LOCALLIGHTS]; float locallightR2[MAX_LOCALLIGHTS]; + + // drawelements renderer + vec3_t arrayverts[MAXSTUDIOVERTS]; + vec2_t arraycoord[MAXSTUDIOVERTS]; + unsigned short arrayelems[MAXSTUDIOVERTS*6]; + GLubyte arraycolor[MAXSTUDIOVERTS][4]; + uint numverts; + uint numelems; } studio_draw_state_t; // studio-related cvars -convar_t *r_studio_sort_textures; -convar_t *r_drawviewmodel; +static convar_t *r_studio_sort_textures; +static convar_t *r_drawviewmodel; convar_t *cl_righthand = NULL; -convar_t *cl_himodels; +static convar_t *cl_himodels; +static convar_t *r_studio_drawelements; static r_studio_interface_t *pStudioDraw; static studio_draw_state_t g_studio; // global studio state @@ -135,6 +144,7 @@ void R_StudioInit( void ) cl_himodels = Cvar_Get( "cl_himodels", "1", FCVAR_ARCHIVE, "draw high-resolution player models in multiplayer" ); r_studio_sort_textures = Cvar_Get( "r_studio_sort_textures", "0", FCVAR_ARCHIVE, "change draw order for additive meshes" ); r_drawviewmodel = Cvar_Get( "r_drawviewmodel", "1", 0, "draw firstperson weapon model" ); + r_studio_drawelements = Cvar_Get( "r_studio_drawelements", "1", FCVAR_ARCHIVE, "use glDrawElements for studiomodels" ); Matrix3x4_LoadIdentity( g_studio.rotationmatrix ); Cvar_RegisterVariable( &r_glowshellfreq ); @@ -1723,7 +1733,7 @@ R_LightLambert ==================== */ -void R_LightLambert( vec4_t light[MAX_LOCALLIGHTS], vec3_t normal, vec3_t color ) +void R_LightLambert( vec4_t light[MAX_LOCALLIGHTS], vec3_t normal, vec3_t color, byte *out ) { vec3_t finalLight; vec3_t localLight; @@ -1761,7 +1771,53 @@ void R_LightLambert( vec4_t light[MAX_LOCALLIGHTS], vec3_t normal, vec3_t color } } - pglColor4f( finalLight[0], finalLight[1], finalLight[2], tr.blend ); + out[0] = finalLight[0] * 255; + out[1] = finalLight[1] * 255; + out[2] = finalLight[2] * 255; +} + +static void R_StudioSetColorBegin(short *ptricmds, vec3_t *pstudionorms ) +{ + float *lv = (float *)g_studio.lightvalues[ptricmds[1]]; + rgba_t color; + + if( g_studio.numlocallights ) + { + color[3] = tr.blend * 255; + R_LightLambert( g_studio.lightpos[ptricmds[0]], pstudionorms[ptricmds[1]], lv, color ); + pglColor4ubv( color ); + } + else + { + if( RI.currententity->curstate.rendermode == kRenderTransColor ) + { + color[3] = tr.blend * 255; + VectorCopy( (byte*)&RI.currententity->curstate.rendercolor, color ); + pglColor4ubv( color ); + } + else pglColor4f( lv[0], lv[1], lv[2], tr.blend ); + } +} + +static void R_StudioSetColorArray(short *ptricmds, vec3_t *pstudionorms, byte *color ) +{ + float *lv = (float *)g_studio.lightvalues[ptricmds[1]]; + + color[3] = tr.blend * 255; + + if( g_studio.numlocallights ) + R_LightLambert( g_studio.lightpos[ptricmds[0]], pstudionorms[ptricmds[1]], lv, color ); + else + { + if( RI.currententity->curstate.rendermode == kRenderTransColor ) + VectorCopy( (byte*)&RI.currententity->curstate.rendercolor, color ); + else + { + color[0] = lv[0] * 255; + color[1] = lv[1] * 255; + color[2] = lv[2] * 255; + } + } } /* @@ -1934,11 +1990,8 @@ _inline void R_StudioDrawNormalMesh( short *ptricmds, vec3_t *pstudionorms, floa for( ; i > 0; i--, ptricmds += 4 ) { - lv = (float *)g_studio.lightvalues[ptricmds[1]]; + R_StudioSetColorBegin( ptricmds, pstudionorms ); - if( g_studio.numlocallights ) - R_LightLambert( g_studio.lightpos[ptricmds[0]], pstudionorms[ptricmds[1]], lv ); - else pglColor4f( lv[0], lv[1], lv[2], tr.blend ); pglTexCoord2f( ptricmds[2] * s, ptricmds[3] * t ); pglVertex3fv( g_studio.verts[ptricmds[0]] ); } @@ -1970,10 +2023,7 @@ _inline void R_StudioDrawFloatMesh( short *ptricmds, vec3_t *pstudionorms ) for( ; i > 0; i--, ptricmds += 4 ) { - lv = (float *)g_studio.lightvalues[ptricmds[1]]; - if( g_studio.numlocallights ) - R_LightLambert( g_studio.lightpos[ptricmds[0]], pstudionorms[ptricmds[1]], lv ); - else pglColor4f( lv[0], lv[1], lv[2], tr.blend ); + R_StudioSetColorBegin( ptricmds, pstudionorms ); pglTexCoord2f( HalfToFloat( ptricmds[2] ), HalfToFloat( ptricmds[3] )); pglVertex3fv( g_studio.verts[ptricmds[0]] ); } @@ -2009,10 +2059,13 @@ _inline void R_StudioDrawChromeMesh( short *ptricmds, vec3_t *pstudionorms, floa { if( glowShell ) { + color24 *clr = &RI.currententity->curstate.rendercolor; + idx = g_studio.normaltable[ptricmds[0]]; av = g_studio.verts[ptricmds[0]]; lv = g_studio.norms[ptricmds[0]]; VectorMA( av, scale, lv, vert ); + pglColor4ub( clr->r, clr->g, clr->b, 255 ); pglTexCoord2f( g_studio.chrome[idx][0] * s, g_studio.chrome[idx][1] * t ); pglVertex3fv( vert ); } @@ -2020,9 +2073,7 @@ _inline void R_StudioDrawChromeMesh( short *ptricmds, vec3_t *pstudionorms, floa { idx = ptricmds[1]; lv = (float *)g_studio.lightvalues[ptricmds[1]]; - if( g_studio.numlocallights ) - R_LightLambert( g_studio.lightpos[ptricmds[0]], pstudionorms[ptricmds[1]], lv ); - else pglColor4f( lv[0], lv[1], lv[2], tr.blend ); + R_StudioSetColorBegin( ptricmds, pstudionorms ); pglTexCoord2f( g_studio.chrome[idx][0] * s, g_studio.chrome[idx][1] * t ); pglVertex3fv( g_studio.verts[ptricmds[0]] ); } @@ -2032,6 +2083,220 @@ _inline void R_StudioDrawChromeMesh( short *ptricmds, vec3_t *pstudionorms, floa } } + +_inline int R_StudioBuildIndices( qboolean tri_strip, int vertexState ) +{ + // build in indices + if( vertexState++ < 3 ) + { + g_studio.arrayelems[g_studio.numelems++] = g_studio.numverts; + } + else if( tri_strip ) + { + // flip triangles between clockwise and counter clockwise + if( vertexState & 1 ) + { + // draw triangle [n-2 n-1 n] + g_studio.arrayelems[g_studio.numelems++] = g_studio.numverts - 2; + g_studio.arrayelems[g_studio.numelems++] = g_studio.numverts - 1; + g_studio.arrayelems[g_studio.numelems++] = g_studio.numverts; + } + else + { + // draw triangle [n-1 n-2 n] + g_studio.arrayelems[g_studio.numelems++] = g_studio.numverts - 1; + g_studio.arrayelems[g_studio.numelems++] = g_studio.numverts - 2; + g_studio.arrayelems[g_studio.numelems++] = g_studio.numverts; + } + } + else + { + // draw triangle fan [0 n-1 n] + g_studio.arrayelems[g_studio.numelems++] = g_studio.numverts - ( vertexState - 1 ); + g_studio.arrayelems[g_studio.numelems++] = g_studio.numverts - 1; + g_studio.arrayelems[g_studio.numelems++] = g_studio.numverts; + } + + return vertexState; +} + +/* +=============== +R_StudioDrawNormalMesh + +generic path +=============== +*/ +_inline void R_StudioBuildArrayNormalMesh( short *ptricmds, vec3_t *pstudionorms, float s, float t ) +{ + float *lv; + int i; + float alpha = tr.blend; + + while( i = *( ptricmds++ )) + { + int vertexState = 0; + qboolean tri_strip = true; + + if( i < 0 ) + { + tri_strip = false; + i = -i; + } + + for( ; i > 0; i--, ptricmds += 4 ) + { + GLubyte *cl; + cl = g_studio.arraycolor[g_studio.numverts]; + lv = (float *)g_studio.lightvalues[ptricmds[1]]; + + vertexState = R_StudioBuildIndices( tri_strip, vertexState ); + + R_StudioSetColorArray( ptricmds, pstudionorms, cl ); + + g_studio.arraycoord[g_studio.numverts][0] = ptricmds[2] * s; + g_studio.arraycoord[g_studio.numverts][1] = ptricmds[3] * t; + + VectorCopy( g_studio.verts[ptricmds[0]], g_studio.arrayverts[g_studio.numverts] ); + g_studio.numverts++; + } + } +} + +/* +=============== +R_StudioDrawNormalMesh + +generic path +=============== +*/ +_inline void R_StudioBuildArrayFloatMesh( short *ptricmds, vec3_t *pstudionorms ) +{ + float *lv; + int i; + float alpha = tr.blend; + + while( i = *( ptricmds++ )) + { + int vertexState = 0; + qboolean tri_strip = true; + + if( i < 0 ) + { + tri_strip = false; + i = -i; + } + + for( ; i > 0; i--, ptricmds += 4 ) + { + GLubyte *cl; + cl = g_studio.arraycolor[g_studio.numverts]; + lv = (float *)g_studio.lightvalues[ptricmds[1]]; + + vertexState = R_StudioBuildIndices( tri_strip, vertexState ); + + R_StudioSetColorArray( ptricmds, pstudionorms, cl ); + + g_studio.arraycoord[g_studio.numverts][0] = HalfToFloat( ptricmds[2] ); + g_studio.arraycoord[g_studio.numverts][1] = HalfToFloat( ptricmds[3] ); + + VectorCopy( g_studio.verts[ptricmds[0]], g_studio.arrayverts[g_studio.numverts] ); + g_studio.numverts++; + } + } +} + +/* +=============== +R_StudioDrawNormalMesh + +generic path +=============== +*/ +_inline void R_StudioBuildArrayChromeMesh( short *ptricmds, vec3_t *pstudionorms, float s, float t, float scale ) +{ + float *lv, *av; + int i, idx; + qboolean glowShell = (scale > 0.0f) ? true : false; + vec3_t vert; + float alpha = tr.blend; + + while( i = *( ptricmds++ )) + { + int vertexState = 0; + qboolean tri_strip = true; + + if( i < 0 ) + { + tri_strip = false; + i = -i; + } + + for( ; i > 0; i--, ptricmds += 4 ) + { + GLubyte *cl; + cl = g_studio.arraycolor[g_studio.numverts]; + lv = (float *)g_studio.lightvalues[ptricmds[1]]; + + vertexState = R_StudioBuildIndices( tri_strip, vertexState ); + + if( glowShell ) + { + idx = g_studio.normaltable[ptricmds[0]]; + av = g_studio.verts[ptricmds[0]]; + lv = g_studio.norms[ptricmds[0]]; + + cl[0] = RI.currententity->curstate.rendercolor.r; + cl[1] = RI.currententity->curstate.rendercolor.g; + cl[2] = RI.currententity->curstate.rendercolor.b; + cl[3] = 255; + + VectorMA( av, scale, lv, vert ); + VectorCopy( vert, g_studio.arrayverts[g_studio.numverts] ); + } + else + { + idx = ptricmds[1]; + R_StudioSetColorArray( ptricmds, pstudionorms, cl ); + + VectorCopy( g_studio.verts[ptricmds[0]], g_studio.arrayverts[g_studio.numverts] ); + } + + g_studio.arraycoord[g_studio.numverts][0] = g_studio.chrome[idx][0] * s; + g_studio.arraycoord[g_studio.numverts][1] = g_studio.chrome[idx][1] * t; + + g_studio.numverts++; + } + } +} + +_inline void R_StudioDrawArrays( uint startverts, uint startelems ) +{ + pglEnableClientState( GL_VERTEX_ARRAY ); + pglVertexPointer( 3, GL_FLOAT, 12, g_studio.arrayverts ); + + pglEnableClientState( GL_TEXTURE_COORD_ARRAY ); + pglTexCoordPointer( 2, GL_FLOAT, 0, g_studio.arraycoord ); + + if( !( g_nForceFaceFlags & STUDIO_NF_CHROME ) ) + { + pglEnableClientState( GL_COLOR_ARRAY ); + pglColorPointer( 4, GL_UNSIGNED_BYTE, 0, g_studio.arraycolor ); + } + +#if !defined XASH_NANOGL || defined XASH_WES && defined __EMSCRIPTEN__ // WebGL need to know array sizes + if( pglDrawRangeElements ) + pglDrawRangeElements( GL_TRIANGLES, startverts, g_studio.numverts, + g_studio.numelems - startelems, GL_UNSIGNED_SHORT, &g_studio.arrayelems[startelems] ); + else +#endif + pglDrawElements( GL_TRIANGLES, g_studio.numelems - startelems, GL_UNSIGNED_SHORT, &g_studio.arrayelems[startelems] ); + pglDisableClientState( GL_VERTEX_ARRAY ); + pglDisableClientState( GL_TEXTURE_COORD_ARRAY ); + if( !( g_nForceFaceFlags & STUDIO_NF_CHROME ) ) + pglDisableClientState( GL_COLOR_ARRAY ); +} + /* =============== R_StudioDrawPoints @@ -2054,6 +2319,9 @@ static void R_StudioDrawPoints( void ) if( !m_pStudioHeader ) return; + + g_studio.numverts = g_studio.numelems = 0; + // safety bounding the skinnum m_skinnum = bound( 0, RI.currententity->curstate.skin, ( m_pStudioHeader->numskinfamilies - 1 )); ptexture = (mstudiotexture_t *)((byte *)m_pStudioHeader + m_pStudioHeader->textureindex); @@ -2151,6 +2419,8 @@ static void R_StudioDrawPoints( void ) for( j = 0; j < m_pSubModel->nummesh; j++ ) { float oldblend = tr.blend; + uint startArrayVerts = g_studio.numverts; + uint startArrayElems = g_studio.numelems; short *ptricmds; float s, t; @@ -2184,11 +2454,24 @@ static void R_StudioDrawPoints( void ) R_StudioSetupSkin( m_pStudioHeader, pskinref[pmesh->skinref] ); - if( FBitSet( g_nFaceFlags, STUDIO_NF_CHROME )) - R_StudioDrawChromeMesh( ptricmds, pstudionorms, s, t, shellscale ); - else if( FBitSet( g_nFaceFlags, STUDIO_NF_UV_COORDS )) - R_StudioDrawFloatMesh( ptricmds, pstudionorms ); - else R_StudioDrawNormalMesh( ptricmds, pstudionorms, s, t ); + if( CVAR_TO_BOOL(r_studio_drawelements) ) + { + if( FBitSet( g_nFaceFlags, STUDIO_NF_CHROME )) + R_StudioBuildArrayChromeMesh( ptricmds, pstudionorms, s, t, shellscale ); + else if( FBitSet( g_nFaceFlags, STUDIO_NF_UV_COORDS )) + R_StudioBuildArrayFloatMesh( ptricmds, pstudionorms ); + else R_StudioBuildArrayNormalMesh( ptricmds, pstudionorms, s, t ); + + R_StudioDrawArrays( startArrayVerts, startArrayElems ); + } + else + { + if( FBitSet( g_nFaceFlags, STUDIO_NF_CHROME )) + R_StudioDrawChromeMesh( ptricmds, pstudionorms, s, t, shellscale ); + else if( FBitSet( g_nFaceFlags, STUDIO_NF_UV_COORDS )) + R_StudioDrawFloatMesh( ptricmds, pstudionorms ); + else R_StudioDrawNormalMesh( ptricmds, pstudionorms, s, t ); + } if( FBitSet( g_nFaceFlags, STUDIO_NF_MASKED )) { diff --git a/engine/client/keys.c b/engine/client/keys.c index bc7fccf7..66f0e6c0 100644 --- a/engine/client/keys.c +++ b/engine/client/keys.c @@ -527,8 +527,11 @@ Key_IsAllowedAutoRepeat List of keys that allows auto-repeat =================== */ -qboolean Key_IsAllowedAutoRepeat( int key ) +static qboolean Key_IsAllowedAutoRepeat( int key ) { + if( cls.key_dest != key_game ) + return true; + switch( key ) { case K_BACKSPACE: diff --git a/engine/client/s_vox.c b/engine/client/s_vox.c index 39b0e1d7..5955e344 100644 --- a/engine/client/s_vox.c +++ b/engine/client/s_vox.c @@ -545,7 +545,7 @@ void VOX_ParseLineCommands( char *pSentenceData, int sentenceIndex ) length = pNext - pSentenceData; if( tempBufferPos + length > sizeof( tempBuffer )) { - Con_Printf( S_ERROR "sentence too long!\n" ); + Con_Printf( S_ERROR "Sentence too long (max length %d characters)\n", sizeof(tempBuffer) - 1 ); return; } @@ -619,7 +619,7 @@ void VOX_ReadSentenceFile( const char *psentenceFileName ) { if( g_numSentences >= MAX_SENTENCES ) { - Con_Printf( S_ERROR "VOX_Init: too many sentences specified\n" ); + Con_Printf( S_ERROR "VOX_Init: too many sentences specified, max is %d\n", MAX_SENTENCES ); break; } diff --git a/engine/client/vid_common.c b/engine/client/vid_common.c index ad942cec..b7a6188d 100644 --- a/engine/client/vid_common.c +++ b/engine/client/vid_common.c @@ -65,6 +65,8 @@ convar_t *r_traceglow; convar_t *r_dynamic; convar_t *r_lightmap; convar_t *gl_round_down; +convar_t *r_vbo; +convar_t *r_vbo_dlightmode; convar_t *vid_displayfrequency; convar_t *vid_fullscreen; @@ -546,6 +548,51 @@ static void SetFullscreenModeFromCommandLine( ) #endif } + +/* +=============== +R_CheckVBO + +register VBO cvars and get default value +=============== +*/ +static void R_CheckVBO( void ) +{ + const char *def = "1"; + const char *dlightmode = "1"; + int flags = FCVAR_ARCHIVE; + qboolean disable = false; + + // some bad GLES1 implementations breaks dlights completely + if( glConfig.max_texture_units < 3 ) + disable = true; + +#ifdef XASH_MOBILE_PLATFORM + // VideoCore4 drivers have a problem with mixing VBO and client arrays + // Disable it, as there is no suitable workaround here + if( Q_stristr( glConfig.renderer_string, "VideoCore IV" ) || Q_stristr( glConfig.renderer_string, "vc4" ) ) + disable = true; + + // dlightmode 1 is not too much tested on android + // so better to left it off + dlightmode = "0"; +#endif + + if( disable ) + { + // do not keep in config unless dev > 3 and enabled + flags = 0; + def = "0"; + } + + r_vbo = Cvar_Get( "r_vbo", def, flags, "draw world using VBO" ); + r_vbo_dlightmode = Cvar_Get( "r_vbo_dlightmode", dlightmode, FCVAR_ARCHIVE, "vbo dlight rendering mode(0-1)" ); + + // check if enabled manually + if( CVAR_TO_BOOL(r_vbo) ) + r_vbo->flags |= FCVAR_ARCHIVE; +} + /* =============== R_Init @@ -580,6 +627,7 @@ qboolean R_Init( void ) r_temppool = Mem_AllocPool( "Render Zone" ); GL_SetDefaults(); + R_CheckVBO(); R_InitImages(); R_SpriteInit(); R_StudioInit(); diff --git a/engine/common/common.c b/engine/common/common.c index 286bccb0..2c09cf18 100644 --- a/engine/common/common.c +++ b/engine/common/common.c @@ -20,18 +20,18 @@ GNU General Public License for more details. #include "client.h" #include "library.h" -const char *file_exts[10] = -{ - ".cfg", - ".lst", - ".exe", - ".vbs", - ".com", - ".bat", - ".dll", - ".ini", - ".log", - ".sys", +static const char *file_exts[] = +{ + "cfg", + "lst", + "exe", + "vbs", + "com", + "bat", + "dll", + "ini", + "log", + "sys", }; #ifdef _DEBUG diff --git a/engine/common/common.h b/engine/common/common.h index 0ee4dfd2..6a728b1b 100644 --- a/engine/common/common.h +++ b/engine/common/common.h @@ -1053,6 +1053,7 @@ qboolean CL_IsBackgroundDemo( void ); qboolean CL_IsBackgroundMap( void ); qboolean SV_Initialized( void ); qboolean CL_LoadProgs( const char *name ); +void CL_ProcessFile( qboolean successfully_received, const char *filename ); int SV_GetSaveComment( const char *savename, char *comment ); qboolean SV_NewGame( const char *mapName, qboolean loadGame ); void SV_ClipPMoveToEntity( struct physent_s *pe, const vec3_t start, vec3_t mins, vec3_t maxs, const vec3_t end, struct pmtrace_s *tr ); diff --git a/engine/common/dedicated.c b/engine/common/dedicated.c index 6e573ae2..db647c76 100644 --- a/engine/common/dedicated.c +++ b/engine/common/dedicated.c @@ -84,6 +84,11 @@ const char *svc_strings[256] = "svc_unused63", }; +void CL_ProcessFile( qboolean successfully_received, const char *filename ) +{ + +} + int CL_Active( void ) { return false; diff --git a/engine/common/host.c b/engine/common/host.c index 24f968cc..e117abb5 100644 --- a/engine/common/host.c +++ b/engine/common/host.c @@ -542,6 +542,7 @@ void Host_Frame( float time ) Host_GetCommands (); // dedicated in Host_ServerFrame (); // server frame Host_ClientFrame (); // client frame + HTTP_Run(); // both server and client host.framecount++; } @@ -978,6 +979,7 @@ int EXPORT Host_Main( int argc, char **argv, const char *progname, int bChangeGa SV_Init(); CL_Init(); + HTTP_Init(); ID_Init(); if( Host_IsDedicated() ) @@ -1060,6 +1062,7 @@ void EXPORT Host_Shutdown( void ) Mod_Shutdown(); NET_Shutdown(); + HTTP_Shutdown(); Host_FreeCommon(); #ifdef _WIN32 Wcon_DestroyConsole(); diff --git a/engine/common/mod_bmodel.c b/engine/common/mod_bmodel.c index 9e62218a..dad9490c 100644 --- a/engine/common/mod_bmodel.c +++ b/engine/common/mod_bmodel.c @@ -309,7 +309,7 @@ static void Mod_LoadLump( const byte *in, mlumpinfo_t *info, mlumpstat_t *stat, if( l->filelen % real_entrysize ) { if( !FBitSet( flags, LUMP_SILENT )) - Con_DPrintf( S_ERROR "Mod_Load%s: funny lump size\n", msg2 ); + Con_DPrintf( S_ERROR "Mod_Load%s: Lump size %d was not a multiple of %u bytes\n", msg2, l->filelen, real_entrysize ); loadstat.numerrors++; return; } diff --git a/engine/common/model.c b/engine/common/model.c index 177b3b21..d80cb199 100644 --- a/engine/common/model.c +++ b/engine/common/model.c @@ -288,8 +288,8 @@ model_t *Mod_LoadModel( model_t *mod, qboolean crash ) { memset( mod, 0, sizeof( model_t )); - if( crash ) Host_Error( "%s couldn't load\n", tempname ); - else Con_Printf( S_ERROR "%s couldn't load\n", tempname ); + if( crash ) Host_Error( "Could not load model %s from disk\n", tempname ); + else Con_Printf( S_ERROR "Could not load model %s from disk\n", tempname ); return NULL; } @@ -331,8 +331,8 @@ model_t *Mod_LoadModel( model_t *mod, qboolean crash ) Mod_FreeModel( mod ); Mem_Free( buf ); - if( crash ) Host_Error( "%s couldn't load\n", tempname ); - else Con_Printf( S_ERROR "%s couldn't load\n", tempname ); + if( crash ) Host_Error( "Could not load model %s\n", tempname ); + else Con_Printf( S_ERROR "Could not load model %s\n", tempname ); return NULL; } diff --git a/engine/common/net_ws.c b/engine/common/net_ws.c index 8130d916..4a811398 100644 --- a/engine/common/net_ws.c +++ b/engine/common/net_ws.c @@ -1877,3 +1877,827 @@ void NET_Shutdown( void ) #endif net.initialized = false; } + + +/* +================================================= + +HTTP downloader + +================================================= +*/ + +typedef struct httpserver_s +{ + char host[256]; + int port; + char path[PATH_MAX]; + qboolean needfree; + struct httpserver_s *next; + +} httpserver_t; + +enum connectionstate +{ + HTTP_QUEUE = 0, + HTTP_OPENED, + HTTP_SOCKET, + HTTP_NS_RESOLVED, + HTTP_CONNECTED, + HTTP_REQUEST, + HTTP_REQUEST_SENT, + HTTP_RESPONSE_RECEIVED, + HTTP_FREE +}; + +typedef struct httpfile_s +{ + struct httpfile_s *next; + httpserver_t *server; + char path[PATH_MAX]; + file_t *file; + int socket; + int size; + int downloaded; + int lastchecksize; + float checktime; + float blocktime; + int id; + enum connectionstate state; + qboolean process; + + // query or response + char buf[BUFSIZ]; + int header_size, query_length, bytes_sent; +} httpfile_t; + +static struct http_static_s +{ + // file and server lists + httpfile_t *first_file, *last_file; + httpserver_t *first_server, *last_server; +} http; + + +static convar_t *http_useragent; +static convar_t *http_autoremove; +static convar_t *http_timeout; +static convar_t *http_maxconnections; + +/* +======================== +HTTP_ClearCustomServers +======================== +*/ +void HTTP_ClearCustomServers( void ) +{ + if( http.first_file ) + return; // may be referenced + + while( http.first_server && http.first_server->needfree ) + { + httpserver_t *tmp = http.first_server; + + http.first_server = http.first_server->next; + Mem_Free( tmp ); + } +} + +/* +============== +HTTP_FreeFile + +Skip to next server/file +============== +*/ +static void HTTP_FreeFile( httpfile_t *file, qboolean error ) +{ + char incname[256]; + + // Allways close file and socket + if( file->file ) + FS_Close( file->file ); + + file->file = NULL; + + if( file->socket != -1 ) + pCloseSocket( file->socket ); + + file->socket = -1; + + Q_snprintf( incname, 256, "%s.incomplete", file->path ); + if( error ) + { + // Switch to next fastdl server if present + if( file->server && ( file->state > HTTP_QUEUE ) && (file->state != HTTP_FREE ) ) + { + file->server = file->server->next; + file->state = HTTP_QUEUE; // Reset download state, HTTP_Run() will open file again + return; + } + + // Called because there was no servers to download, free file now + if( http_autoremove->value == 1 ) // remove broken file + FS_Delete( incname ); + else // autoremove disabled, keep file + Con_Printf( "cannot download %s from any server. " + "You may remove %s now\n", file->path, incname ); // Warn about trash file + + if( file->process ) + CL_ProcessFile( false, file->path ); // Process file, increase counter + } + else + { + // Success, rename and process file + char name[256]; + + Q_snprintf( name, 256, "%s", file->path ); + FS_Rename( incname, name ); + + if( file->process ) + CL_ProcessFile( true, name ); + else + Con_Printf( "successfully downloaded %s, processing disabled!\n", name ); + } + + file->state = HTTP_FREE; +} + +/* +=================== +HTTP_AutoClean + +remove files with HTTP_FREE state from list +=================== +*/ +static void HTTP_AutoClean( void ) +{ + httpfile_t *curfile, *prevfile = 0; + + // clean all files marked to free + for( curfile = http.first_file; curfile; curfile = curfile->next ) + { + if( curfile->state != HTTP_FREE ) + { + prevfile = curfile; + continue; + } + + if( curfile == http.first_file ) + { + http.first_file = http.first_file->next; + Mem_Free( curfile ); + curfile = http.first_file; + if( !curfile ) + break; + continue; + } + + if( prevfile ) + prevfile->next = curfile->next; + Mem_Free( curfile ); + curfile = prevfile; + if( !curfile ) + break; + } + http.last_file = prevfile; +} + +/* +=================== +HTTP_ProcessStream + +process incoming data +=================== +*/ +static qboolean HTTP_ProcessStream( httpfile_t *curfile ) +{ + char buf[BUFSIZ+1]; + char *begin = 0; + int res; + + while( ( res = pRecv( curfile->socket, buf, BUFSIZ, 0 ) ) > 0) // if we got there, we are receiving data + { + curfile->blocktime = 0; + + if( curfile->state < HTTP_RESPONSE_RECEIVED ) // Response still not received + { + buf[res] = 0; // string break to search \r\n\r\n + memcpy( curfile->buf + curfile->header_size, buf, res ); + begin = Q_strstr( curfile->buf, "\r\n\r\n" ); + + if( begin ) // Got full header + { + int cutheadersize = begin - curfile->buf + 4; // after that begin of data + char *length; + + Con_Reportf( "HTTP: Got response!\n" ); + + if( !Q_strstr( curfile->buf, "200 OK" ) ) + { + *begin = 0; // cut string to print out response + begin = Q_strchr( curfile->buf, '\r' ); + + if( !begin ) begin = Q_strchr( curfile->buf, '\n' ); + if( begin ) + *begin = 0; + + Con_Printf( S_ERROR "bad response: %s\n", curfile->buf ); + HTTP_FreeFile( curfile, true ); + return false; + } + + // print size + length = Q_stristr( curfile->buf, "Content-Length: " ); + if( length ) + { + int size = Q_atoi( length += 16 ); + + Con_Reportf( "HTTP: File size is %d\n", size ); + + if( ( curfile->size != -1 ) && ( curfile->size != size ) ) // check size if specified, not used + Con_Reportf( S_WARN "Server reports wrong file size!\n" ); + + curfile->size = size; + } + + if( curfile->size == -1 ) + { + // Usually fastdl's reports file size if link is correct + Con_Printf( S_ERROR "file size is unknown, refusing download!\n" ); + HTTP_FreeFile( curfile, true ); + return false; + } + + curfile->state = HTTP_RESPONSE_RECEIVED; // got response, let's start download + begin += 4; + + // Write remaining message part + if( res - cutheadersize - curfile->header_size > 0 ) + { + int ret = FS_Write( curfile->file, begin, res - cutheadersize - curfile->header_size ); + + if( ret != res - cutheadersize - curfile->header_size ) // could not write file + { + // close it and go to next + Con_Printf( S_ERROR "write failed for %s!\n", curfile->path ); + HTTP_FreeFile( curfile, true ); + return false; + } + curfile->downloaded += ret; + } + } + curfile->header_size += res; + } + else if( res > 0 ) + { + // data download + int ret = FS_Write( curfile->file, buf, res ); + + if ( ret != res ) + { + // close it and go to next + Con_Printf( S_ERROR "write failed for %s!\n", curfile->path ); + curfile->state = HTTP_FREE; + HTTP_FreeFile( curfile, true ); + return false; + } + + curfile->downloaded += ret; + curfile->lastchecksize += ret; + + // as after it will run in same frame + if( curfile->checktime > 5 ) + { + curfile->checktime = 0; + Con_Reportf( "download speed %f KB/s\n", (float)curfile->lastchecksize / ( 5.0 * 1024 ) ); + curfile->lastchecksize = 0; + } + } + } + curfile->checktime += host.frametime; + + return true; +} + +/* +============== +HTTP_Run + +Download next file block of each active file +Call every frame +============== +*/ +void HTTP_Run( void ) +{ + httpfile_t *curfile; + int iActiveCount = 0; + int iProgressCount = 0; + float flProgress = 0; + qboolean fResolving = false; + + for( curfile = http.first_file; curfile; curfile = curfile->next ) + { + int res; + struct sockaddr addr; + + if( curfile->state == HTTP_FREE ) + continue; + + if( curfile->state == HTTP_QUEUE ) + { + char name[PATH_MAX]; + + if( iActiveCount > http_maxconnections->value ) + continue; + + if( !curfile->server ) + { + Con_Printf( S_ERROR "no servers to download %s!\n", curfile->path ); + HTTP_FreeFile( curfile, true ); + continue; + } + + Con_Reportf( "HTTP: Starting download %s from %s\n", curfile->path, curfile->server->host ); + Q_snprintf( name, PATH_MAX, "%s.incomplete", curfile->path ); + + curfile->file = FS_Open( name, "wb", true ); + + if( !curfile->file ) + { + Con_Printf( S_ERROR "cannot open %s!\n", name ); + HTTP_FreeFile( curfile, true ); + continue; + } + + curfile->state = HTTP_OPENED; + curfile->blocktime = 0; + curfile->downloaded = 0; + curfile->lastchecksize = 0; + curfile->checktime = 0; + } + + iActiveCount++; + + if( curfile->state < HTTP_SOCKET ) // Socket is not created + { + dword mode; + + curfile->socket = pSocket( AF_INET, SOCK_STREAM, IPPROTO_TCP ); + + // Now set non-blocking mode + // You may skip this if not supported by system, + // but download will lock engine, maybe you will need to add manual returns + mode = 1; + pIoctlSocket( curfile->socket, FIONBIO, &mode ); + #ifdef __linux__ + // SOCK_NONBLOCK is not portable, so use fcntl + fcntl( curfile->socket, F_SETFL, fcntl( curfile->socket, F_GETFL, 0 ) | O_NONBLOCK ); + #endif + curfile->state = HTTP_SOCKET; + } + + if( curfile->state < HTTP_NS_RESOLVED ) + { + if( fResolving ) + continue; + + res = NET_StringToSockaddr( va( "%s:%d", curfile->server->host, curfile->server->port ), &addr, true ); + + if( res == 2 ) + { + fResolving = true; + continue; + } + + if( !res ) + { + Con_Printf( S_ERROR "failed to resolve server address for %s!\n", curfile->server->host ); + HTTP_FreeFile( curfile, true ); // Cannot connect + continue; + } + curfile->state = HTTP_NS_RESOLVED; + } + + if( curfile->state < HTTP_CONNECTED ) // Connection not enstabilished + { + res = pConnect( curfile->socket, &addr, sizeof( struct sockaddr ) ); + + if( res ) + { + if( pWSAGetLastError() == WSAEINPROGRESS || pWSAGetLastError() == WSAEWOULDBLOCK ) // Should give EWOOLDBLOCK if try recv too soon + curfile->state = HTTP_CONNECTED; + else + { + Con_Printf( S_ERROR "cannot connect to server: %s\n", NET_ErrorString( ) ); + HTTP_FreeFile( curfile, true ); // Cannot connect + continue; + } + continue; // skip to next file + } + curfile->state = HTTP_CONNECTED; + } + + if( curfile->state < HTTP_REQUEST ) // Request not formatted + { + curfile->query_length = Q_snprintf( curfile->buf, sizeof( curfile->buf ), + "GET %s%s HTTP/1.0\r\n" + "Host: %s\r\n" + "User-Agent: %s\r\n\r\n", curfile->server->path, + curfile->path, curfile->server->host, http_useragent->string ); + curfile->header_size = 0; + curfile->bytes_sent = 0; + curfile->state = HTTP_REQUEST; + } + + if( curfile->state < HTTP_REQUEST_SENT ) // Request not sent + { + qboolean wait = false; + + while( curfile->bytes_sent < curfile->query_length ) + { + res = pSend( curfile->socket, curfile->buf + curfile->bytes_sent, curfile->query_length - curfile->bytes_sent, 0 ); + + + if( res < 0 ) + { + if( pWSAGetLastError() != WSAEWOULDBLOCK && pWSAGetLastError() != WSAENOTCONN ) + { + Con_Printf( S_ERROR "failed to send request: %s\n", NET_ErrorString() ); + HTTP_FreeFile( curfile, true ); + wait = true; + break; + } + // blocking while waiting connection + // increase counter when blocking + curfile->blocktime += host.frametime; + wait = true; + + if( curfile->blocktime > http_timeout->value ) + { + Con_Printf( S_ERROR "timeout on request send:\n%s\n", curfile->buf ); + HTTP_FreeFile( curfile, true ); + break; + } + break; + } + else + { + curfile->bytes_sent += res; + curfile->blocktime = 0; + } + } + + if( wait ) + continue; + + Con_Reportf( "HTTP: Request sent!\n"); + memset( curfile->buf, 0, sizeof( curfile->buf ) ); + curfile->state = HTTP_REQUEST_SENT; + } + + if( !HTTP_ProcessStream( curfile ) ) + continue; + + if( curfile->size > 0 ) + { + flProgress += (float)curfile->downloaded / curfile->size; + iProgressCount++; + } + + if( curfile->size > 0 && curfile->downloaded >= curfile->size ) + { + HTTP_FreeFile( curfile, false ); // success + continue; + } + else if( pWSAGetLastError() != WSAEWOULDBLOCK ) + Con_Reportf( "problem downloading %s:\n%s\n", curfile->path, NET_ErrorString() ); + else + curfile->blocktime += host.frametime; + + if( curfile->blocktime > http_timeout->value ) + { + Con_Printf( S_ERROR "timeout on receiving data!\n"); + HTTP_FreeFile( curfile, true ); + continue; + } + } + + // update progress + Cvar_SetValue( "scr_download", flProgress/iProgressCount * 100 ); + + HTTP_AutoClean(); +} + +/* +=================== +HTTP_AddDownload + +Add new download to end of queue +=================== +*/ +void HTTP_AddDownload( const char *path, int size, qboolean process ) +{ + httpfile_t *httpfile = Z_Calloc( sizeof( httpfile_t ) ); + + Con_Reportf( "File %s queued to download\n", path ); + + httpfile->size = size; + httpfile->downloaded = 0; + httpfile->socket = -1; + Q_strncpy ( httpfile->path, path, sizeof( httpfile->path ) ); + + if( http.last_file ) + { + // Add next to last download + httpfile->id = http.last_file->id + 1; + http.last_file->next= httpfile; + http.last_file = httpfile; + } + else + { + // It will be the only download + httpfile->id = 0; + http.last_file = http.first_file = httpfile; + } + + httpfile->file = NULL; + httpfile->next = NULL; + httpfile->state = HTTP_QUEUE; + httpfile->server = http.first_server; + httpfile->process = process; +} + +/* +=============== +HTTP_Download_f + +Console wrapper +=============== +*/ +static void HTTP_Download_f( void ) +{ + if( Cmd_Argc() < 2 ) + { + Con_Printf( S_USAGE "download \n"); + return; + } + + HTTP_AddDownload( Cmd_Argv( 1 ), -1, false ); +} + +/* +============== +HTTP_ParseURL +============== +*/ +static httpserver_t *HTTP_ParseURL( const char *url ) +{ + httpserver_t *server; + int i; + + url = Q_strstr( url, "http://" ); + + if( !url ) + return NULL; + + url += 7; + server = Z_Calloc( sizeof( httpserver_t ) ); + i = 0; + + while( *url && ( *url != ':' ) && ( *url != '/' ) && ( *url != '\r' ) && ( *url != '\n' ) ) + { + if( i > sizeof( server->host ) ) + return NULL; + + server->host[i++] = *url++; + } + + server->host[i] = 0; + + if( *url == ':' ) + { + server->port = Q_atoi( ++url ); + + while( *url && ( *url != '/' ) && ( *url != '\r' ) && ( *url != '\n' ) ) + url++; + } + else + server->port = 80; + + i = 0; + + while( *url && ( *url != '\r' ) && ( *url != '\n' ) ) + { + if( i > sizeof( server->path ) ) + return NULL; + + server->path[i++] = *url++; + } + + server->path[i] = 0; + server->next = NULL; + server->needfree = false; + + return server; +} + +/* +======================= +HTTP_AddCustomServer +======================= +*/ +void HTTP_AddCustomServer( const char *url ) +{ + httpserver_t *server = HTTP_ParseURL( url ); + + if( !server ) + { + Con_Printf( S_ERROR "\"%s\" is not valid url!\n", url ); + return; + } + + server->needfree = true; + server->next = http.first_server; + http.first_server = server; +} + +/* +======================= +HTTP_AddCustomServer_f +======================= +*/ +static void HTTP_AddCustomServer_f( void ) +{ + if( Cmd_Argc() == 2 ) + { + HTTP_AddCustomServer( Cmd_Argv( 1 ) ); + } +} + +/* +============ +HTTP_Clear_f + +Clear all queue +============ +*/ +static void HTTP_Clear_f( void ) +{ + http.last_file = NULL; + + while( http.first_file ) + { + httpfile_t *file = http.first_file; + + http.first_file = http.first_file->next; + + if( file->file ) + FS_Close( file->file ); + + if( file->socket != -1 ) + pCloseSocket ( file->socket ); + + Mem_Free( file ); + } +} + +/* +============== +HTTP_Cancel_f + +Stop current download, skip to next file +============== +*/ +static void HTTP_Cancel_f( void ) +{ + if( !http.first_file ) + return; + + http.first_file->state = HTTP_FREE; + HTTP_FreeFile( http.first_file, true ); +} + +/* +============= +HTTP_Skip_f + +Stop current download, skip to next server +============= +*/ +static void HTTP_Skip_f( void ) +{ + if( http.first_file ) + HTTP_FreeFile( http.first_file, true ); +} + +/* +============= +HTTP_List_f + +Print all pending downloads to console +============= +*/ +static void HTTP_List_f( void ) +{ + httpfile_t *file = http.first_file; + + while( file ) + { + if ( file->server ) + Con_Printf ( "\t%d %d http://%s:%d/%s%s %d\n", file->id, file->state, + file->server->host, file->server->port, file->server->path, + file->path, file->downloaded ); + else + Con_Printf ( "\t%d %d (no server) %s\n", file->id, file->state, file->path ); + + file = file->next; + } +} + +/* +================ +HTTP_ResetProcessState + +When connected to new server, all old files should not increase counter +================ +*/ +void HTTP_ResetProcessState( void ) +{ + httpfile_t *file = http.first_file; + + while( file ) + { + file->process = false; + file = file->next; + } +} + +/* +============= +HTTP_Init +============= +*/ +void HTTP_Init( void ) +{ + char *serverfile, *line, token[1024]; + + http.last_server = NULL; + + http.first_file = http.last_file = NULL; + + Cmd_AddCommand("http_download", &HTTP_Download_f, "add file to download queue"); + Cmd_AddCommand("http_skip", &HTTP_Skip_f, "skip current download server"); + Cmd_AddCommand("http_cancel", &HTTP_Cancel_f, "cancel current download"); + Cmd_AddCommand("http_clear", &HTTP_Clear_f, "cancel all downloads"); + Cmd_AddCommand("http_list", &HTTP_List_f, "list all queued downloads"); + Cmd_AddCommand("http_addcustomserver", &HTTP_AddCustomServer_f, "add custom fastdl server"); + http_useragent = Cvar_Get( "http_useragent", "xash3d", FCVAR_ARCHIVE, "User-Agent string" ); + http_autoremove = Cvar_Get( "http_autoremove", "1", FCVAR_ARCHIVE, "remove broken files" ); + http_timeout = Cvar_Get( "http_timeout", "45", FCVAR_ARCHIVE, "timeout for http downloader" ); + http_maxconnections = Cvar_Get( "http_maxconnections", "4", FCVAR_ARCHIVE, "maximum http connection number" ); + + // Read servers from fastdl.txt + line = serverfile = (char *)FS_LoadFile( "fastdl.txt", 0, false ); + + if( serverfile ) + { + while( ( line = COM_ParseFile( line, token ) ) ) + { + httpserver_t *server = HTTP_ParseURL( token ); + + if( !server ) + continue; + + if( !http.last_server ) + http.last_server = http.first_server = server; + else + { + http.last_server->next = server; + http.last_server = server; + } + } + + Mem_Free( serverfile ); + } +} + +/* +==================== +HTTP_Shutdown +==================== +*/ +void HTTP_Shutdown( void ) +{ + HTTP_Clear_f(); + + while( http.first_server ) + { + httpserver_t *tmp = http.first_server; + + http.first_server = http.first_server->next; + Mem_Free( tmp ); + } + + http.last_server = 0; +} diff --git a/engine/common/net_ws.h b/engine/common/net_ws.h index ad4f7b8d..af5680e1 100644 --- a/engine/common/net_ws.h +++ b/engine/common/net_ws.h @@ -69,4 +69,11 @@ qboolean CL_LegacyMode( void ); int CL_GetSplitSize( void ); #endif +void HTTP_AddCustomServer( const char *url ); +void HTTP_AddDownload( const char *path, int size, qboolean process ); +void HTTP_ClearCustomServers( void ); +void HTTP_Shutdown( void ); +void HTTP_Init( void ); +void HTTP_Run( void ); + #endif//NET_WS_H diff --git a/engine/common/protocol.h b/engine/common/protocol.h index 7ea02024..9b9a5914 100644 --- a/engine/common/protocol.h +++ b/engine/common/protocol.h @@ -262,4 +262,6 @@ extern const char *clc_strings[clc_lastmsg+1]; #define MAX_LEGACY_ENTITY_BITS 12 #define MAX_LEGACY_WEAPON_BITS 5 #define MAX_LEGACY_MODEL_BITS 11 +#define MAX_LEGACY_SERVERS 32 + #endif//NET_PROTOCOL_H diff --git a/engine/platform/sdl/vid_sdl.c b/engine/platform/sdl/vid_sdl.c index 4c62d37b..80ba4e05 100644 --- a/engine/platform/sdl/vid_sdl.c +++ b/engine/platform/sdl/vid_sdl.c @@ -25,176 +25,189 @@ GNU General Public License for more details. static vidmode_t *vidmodes = NULL; static int num_vidmodes = 0; static int context_flags = 0; - +#define GL_CALL( x ) #x, (void**)&p##x static dllfunc_t opengl_110funcs[] = { -{ "glClearColor" , (void **)&pglClearColor }, -{ "glClear" , (void **)&pglClear }, -{ "glAlphaFunc" , (void **)&pglAlphaFunc }, -{ "glBlendFunc" , (void **)&pglBlendFunc }, -{ "glCullFace" , (void **)&pglCullFace }, -{ "glDrawBuffer" , (void **)&pglDrawBuffer }, -{ "glReadBuffer" , (void **)&pglReadBuffer }, -{ "glAccum" , (void **)&pglAccum }, -{ "glEnable" , (void **)&pglEnable }, -{ "glDisable" , (void **)&pglDisable }, -{ "glEnableClientState" , (void **)&pglEnableClientState }, -{ "glDisableClientState" , (void **)&pglDisableClientState }, -{ "glGetBooleanv" , (void **)&pglGetBooleanv }, -{ "glGetDoublev" , (void **)&pglGetDoublev }, -{ "glGetFloatv" , (void **)&pglGetFloatv }, -{ "glGetIntegerv" , (void **)&pglGetIntegerv }, -{ "glGetError" , (void **)&pglGetError }, -{ "glGetString" , (void **)&pglGetString }, -{ "glFinish" , (void **)&pglFinish }, -{ "glFlush" , (void **)&pglFlush }, -{ "glClearDepth" , (void **)&pglClearDepth }, -{ "glDepthFunc" , (void **)&pglDepthFunc }, -{ "glDepthMask" , (void **)&pglDepthMask }, -{ "glDepthRange" , (void **)&pglDepthRange }, -{ "glFrontFace" , (void **)&pglFrontFace }, -{ "glDrawElements" , (void **)&pglDrawElements }, -{ "glDrawArrays" , (void **)&pglDrawArrays }, -{ "glColorMask" , (void **)&pglColorMask }, -{ "glIndexPointer" , (void **)&pglIndexPointer }, -{ "glVertexPointer" , (void **)&pglVertexPointer }, -{ "glNormalPointer" , (void **)&pglNormalPointer }, -{ "glColorPointer" , (void **)&pglColorPointer }, -{ "glTexCoordPointer" , (void **)&pglTexCoordPointer }, -{ "glArrayElement" , (void **)&pglArrayElement }, -{ "glColor3f" , (void **)&pglColor3f }, -{ "glColor3fv" , (void **)&pglColor3fv }, -{ "glColor4f" , (void **)&pglColor4f }, -{ "glColor4fv" , (void **)&pglColor4fv }, -{ "glColor3ub" , (void **)&pglColor3ub }, -{ "glColor4ub" , (void **)&pglColor4ub }, -{ "glColor4ubv" , (void **)&pglColor4ubv }, -{ "glTexCoord1f" , (void **)&pglTexCoord1f }, -{ "glTexCoord2f" , (void **)&pglTexCoord2f }, -{ "glTexCoord3f" , (void **)&pglTexCoord3f }, -{ "glTexCoord4f" , (void **)&pglTexCoord4f }, -{ "glTexCoord1fv" , (void **)&pglTexCoord1fv }, -{ "glTexCoord2fv" , (void **)&pglTexCoord2fv }, -{ "glTexCoord3fv" , (void **)&pglTexCoord3fv }, -{ "glTexCoord4fv" , (void **)&pglTexCoord4fv }, -{ "glTexGenf" , (void **)&pglTexGenf }, -{ "glTexGenfv" , (void **)&pglTexGenfv }, -{ "glTexGeni" , (void **)&pglTexGeni }, -{ "glVertex2f" , (void **)&pglVertex2f }, -{ "glVertex3f" , (void **)&pglVertex3f }, -{ "glVertex3fv" , (void **)&pglVertex3fv }, -{ "glNormal3f" , (void **)&pglNormal3f }, -{ "glNormal3fv" , (void **)&pglNormal3fv }, -{ "glBegin" , (void **)&pglBegin }, -{ "glEnd" , (void **)&pglEnd }, -{ "glLineWidth" , (void**)&pglLineWidth }, -{ "glPointSize" , (void**)&pglPointSize }, -{ "glMatrixMode" , (void **)&pglMatrixMode }, -{ "glOrtho" , (void **)&pglOrtho }, -{ "glRasterPos2f" , (void **) &pglRasterPos2f }, -{ "glFrustum" , (void **)&pglFrustum }, -{ "glViewport" , (void **)&pglViewport }, -{ "glPushMatrix" , (void **)&pglPushMatrix }, -{ "glPopMatrix" , (void **)&pglPopMatrix }, -{ "glPushAttrib" , (void **)&pglPushAttrib }, -{ "glPopAttrib" , (void **)&pglPopAttrib }, -{ "glLoadIdentity" , (void **)&pglLoadIdentity }, -{ "glLoadMatrixd" , (void **)&pglLoadMatrixd }, -{ "glLoadMatrixf" , (void **)&pglLoadMatrixf }, -{ "glMultMatrixd" , (void **)&pglMultMatrixd }, -{ "glMultMatrixf" , (void **)&pglMultMatrixf }, -{ "glRotated" , (void **)&pglRotated }, -{ "glRotatef" , (void **)&pglRotatef }, -{ "glScaled" , (void **)&pglScaled }, -{ "glScalef" , (void **)&pglScalef }, -{ "glTranslated" , (void **)&pglTranslated }, -{ "glTranslatef" , (void **)&pglTranslatef }, -{ "glReadPixels" , (void **)&pglReadPixels }, -{ "glDrawPixels" , (void **)&pglDrawPixels }, -{ "glStencilFunc" , (void **)&pglStencilFunc }, -{ "glStencilMask" , (void **)&pglStencilMask }, -{ "glStencilOp" , (void **)&pglStencilOp }, -{ "glClearStencil" , (void **)&pglClearStencil }, -{ "glIsEnabled" , (void **)&pglIsEnabled }, -{ "glIsList" , (void **)&pglIsList }, -{ "glIsTexture" , (void **)&pglIsTexture }, -{ "glTexEnvf" , (void **)&pglTexEnvf }, -{ "glTexEnvfv" , (void **)&pglTexEnvfv }, -{ "glTexEnvi" , (void **)&pglTexEnvi }, -{ "glTexParameterf" , (void **)&pglTexParameterf }, -{ "glTexParameterfv" , (void **)&pglTexParameterfv }, -{ "glTexParameteri" , (void **)&pglTexParameteri }, -{ "glHint" , (void **)&pglHint }, -{ "glPixelStoref" , (void **)&pglPixelStoref }, -{ "glPixelStorei" , (void **)&pglPixelStorei }, -{ "glGenTextures" , (void **)&pglGenTextures }, -{ "glDeleteTextures" , (void **)&pglDeleteTextures }, -{ "glBindTexture" , (void **)&pglBindTexture }, -{ "glTexImage1D" , (void **)&pglTexImage1D }, -{ "glTexImage2D" , (void **)&pglTexImage2D }, -{ "glTexSubImage1D" , (void **)&pglTexSubImage1D }, -{ "glTexSubImage2D" , (void **)&pglTexSubImage2D }, -{ "glCopyTexImage1D" , (void **)&pglCopyTexImage1D }, -{ "glCopyTexImage2D" , (void **)&pglCopyTexImage2D }, -{ "glCopyTexSubImage1D" , (void **)&pglCopyTexSubImage1D }, -{ "glCopyTexSubImage2D" , (void **)&pglCopyTexSubImage2D }, -{ "glScissor" , (void **)&pglScissor }, -{ "glGetTexImage" , (void **)&pglGetTexImage }, -{ "glGetTexEnviv" , (void **)&pglGetTexEnviv }, -{ "glPolygonOffset" , (void **)&pglPolygonOffset }, -{ "glPolygonMode" , (void **)&pglPolygonMode }, -{ "glPolygonStipple" , (void **)&pglPolygonStipple }, -{ "glClipPlane" , (void **)&pglClipPlane }, -{ "glGetClipPlane" , (void **)&pglGetClipPlane }, -{ "glShadeModel" , (void **)&pglShadeModel }, -{ "glGetTexLevelParameteriv" , (void **)&pglGetTexLevelParameteriv }, -{ "glGetTexLevelParameterfv" , (void **)&pglGetTexLevelParameterfv }, -{ "glFogfv" , (void **)&pglFogfv }, -{ "glFogf" , (void **)&pglFogf }, -{ "glFogi" , (void **)&pglFogi }, -{ NULL , NULL } + { GL_CALL( glClearColor ) }, + { GL_CALL( glClear ) }, + { GL_CALL( glAlphaFunc ) }, + { GL_CALL( glBlendFunc ) }, + { GL_CALL( glCullFace ) }, + { GL_CALL( glDrawBuffer ) }, + { GL_CALL( glReadBuffer ) }, + { GL_CALL( glAccum ) }, + { GL_CALL( glEnable ) }, + { GL_CALL( glDisable ) }, + { GL_CALL( glEnableClientState ) }, + { GL_CALL( glDisableClientState ) }, + { GL_CALL( glGetBooleanv ) }, + { GL_CALL( glGetDoublev ) }, + { GL_CALL( glGetFloatv ) }, + { GL_CALL( glGetIntegerv ) }, + { GL_CALL( glGetError ) }, + { GL_CALL( glGetString ) }, + { GL_CALL( glFinish ) }, + { GL_CALL( glFlush ) }, + { GL_CALL( glClearDepth ) }, + { GL_CALL( glDepthFunc ) }, + { GL_CALL( glDepthMask ) }, + { GL_CALL( glDepthRange ) }, + { GL_CALL( glFrontFace ) }, + { GL_CALL( glDrawElements ) }, + { GL_CALL( glDrawArrays ) }, + { GL_CALL( glColorMask ) }, + { GL_CALL( glIndexPointer ) }, + { GL_CALL( glVertexPointer ) }, + { GL_CALL( glNormalPointer ) }, + { GL_CALL( glColorPointer ) }, + { GL_CALL( glTexCoordPointer ) }, + { GL_CALL( glArrayElement ) }, + { GL_CALL( glColor3f ) }, + { GL_CALL( glColor3fv ) }, + { GL_CALL( glColor4f ) }, + { GL_CALL( glColor4fv ) }, + { GL_CALL( glColor3ub ) }, + { GL_CALL( glColor4ub ) }, + { GL_CALL( glColor4ubv ) }, + { GL_CALL( glTexCoord1f ) }, + { GL_CALL( glTexCoord2f ) }, + { GL_CALL( glTexCoord3f ) }, + { GL_CALL( glTexCoord4f ) }, + { GL_CALL( glTexCoord1fv ) }, + { GL_CALL( glTexCoord2fv ) }, + { GL_CALL( glTexCoord3fv ) }, + { GL_CALL( glTexCoord4fv ) }, + { GL_CALL( glTexGenf ) }, + { GL_CALL( glTexGenfv ) }, + { GL_CALL( glTexGeni ) }, + { GL_CALL( glVertex2f ) }, + { GL_CALL( glVertex3f ) }, + { GL_CALL( glVertex3fv ) }, + { GL_CALL( glNormal3f ) }, + { GL_CALL( glNormal3fv ) }, + { GL_CALL( glBegin ) }, + { GL_CALL( glEnd ) }, + { GL_CALL( glLineWidth ) }, + { GL_CALL( glPointSize ) }, + { GL_CALL( glMatrixMode ) }, + { GL_CALL( glOrtho ) }, + { GL_CALL( glRasterPos2f ) }, + { GL_CALL( glFrustum ) }, + { GL_CALL( glViewport ) }, + { GL_CALL( glPushMatrix ) }, + { GL_CALL( glPopMatrix ) }, + { GL_CALL( glPushAttrib ) }, + { GL_CALL( glPopAttrib ) }, + { GL_CALL( glLoadIdentity ) }, + { GL_CALL( glLoadMatrixd ) }, + { GL_CALL( glLoadMatrixf ) }, + { GL_CALL( glMultMatrixd ) }, + { GL_CALL( glMultMatrixf ) }, + { GL_CALL( glRotated ) }, + { GL_CALL( glRotatef ) }, + { GL_CALL( glScaled ) }, + { GL_CALL( glScalef ) }, + { GL_CALL( glTranslated ) }, + { GL_CALL( glTranslatef ) }, + { GL_CALL( glReadPixels ) }, + { GL_CALL( glDrawPixels ) }, + { GL_CALL( glStencilFunc ) }, + { GL_CALL( glStencilMask ) }, + { GL_CALL( glStencilOp ) }, + { GL_CALL( glClearStencil ) }, + { GL_CALL( glIsEnabled ) }, + { GL_CALL( glIsList ) }, + { GL_CALL( glIsTexture ) }, + { GL_CALL( glTexEnvf ) }, + { GL_CALL( glTexEnvfv ) }, + { GL_CALL( glTexEnvi ) }, + { GL_CALL( glTexParameterf ) }, + { GL_CALL( glTexParameterfv ) }, + { GL_CALL( glTexParameteri ) }, + { GL_CALL( glHint ) }, + { GL_CALL( glPixelStoref ) }, + { GL_CALL( glPixelStorei ) }, + { GL_CALL( glGenTextures ) }, + { GL_CALL( glDeleteTextures ) }, + { GL_CALL( glBindTexture ) }, + { GL_CALL( glTexImage1D ) }, + { GL_CALL( glTexImage2D ) }, + { GL_CALL( glTexSubImage1D ) }, + { GL_CALL( glTexSubImage2D ) }, + { GL_CALL( glCopyTexImage1D ) }, + { GL_CALL( glCopyTexImage2D ) }, + { GL_CALL( glCopyTexSubImage1D ) }, + { GL_CALL( glCopyTexSubImage2D ) }, + { GL_CALL( glScissor ) }, + { GL_CALL( glGetTexImage ) }, + { GL_CALL( glGetTexEnviv ) }, + { GL_CALL( glPolygonOffset ) }, + { GL_CALL( glPolygonMode ) }, + { GL_CALL( glPolygonStipple ) }, + { GL_CALL( glClipPlane ) }, + { GL_CALL( glGetClipPlane ) }, + { GL_CALL( glShadeModel ) }, + { GL_CALL( glGetTexLevelParameteriv ) }, + { GL_CALL( glGetTexLevelParameterfv ) }, + { GL_CALL( glFogfv ) }, + { GL_CALL( glFogf ) }, + { GL_CALL( glFogi ) }, + { NULL , NULL } }; static dllfunc_t debugoutputfuncs[] = { -{ "glDebugMessageControlARB" , (void **)&pglDebugMessageControlARB }, -{ "glDebugMessageInsertARB" , (void **)&pglDebugMessageInsertARB }, -{ "glDebugMessageCallbackARB" , (void **)&pglDebugMessageCallbackARB }, -{ "glGetDebugMessageLogARB" , (void **)&pglGetDebugMessageLogARB }, -{ NULL , NULL } + { GL_CALL( glDebugMessageControlARB ) }, + { GL_CALL( glDebugMessageInsertARB ) }, + { GL_CALL( glDebugMessageCallbackARB ) }, + { GL_CALL( glGetDebugMessageLogARB ) }, + { NULL , NULL } }; static dllfunc_t multitexturefuncs[] = { -{ "glMultiTexCoord1fARB" , (void **)&pglMultiTexCoord1f }, -{ "glMultiTexCoord2fARB" , (void **)&pglMultiTexCoord2f }, -{ "glMultiTexCoord3fARB" , (void **)&pglMultiTexCoord3f }, -{ "glMultiTexCoord4fARB" , (void **)&pglMultiTexCoord4f }, -{ "glActiveTextureARB" , (void **)&pglActiveTexture }, -{ "glActiveTextureARB" , (void **)&pglActiveTextureARB }, -{ "glClientActiveTextureARB" , (void **)&pglClientActiveTexture }, -{ "glClientActiveTextureARB" , (void **)&pglClientActiveTextureARB }, -{ NULL , NULL } + { GL_CALL( glMultiTexCoord1f ) }, + { GL_CALL( glMultiTexCoord2f ) }, + { GL_CALL( glMultiTexCoord3f ) }, + { GL_CALL( glMultiTexCoord4f ) }, + { GL_CALL( glActiveTexture ) }, + { GL_CALL( glActiveTextureARB ) }, + { GL_CALL( glClientActiveTexture ) }, + { GL_CALL( glClientActiveTextureARB ) }, + { NULL , NULL } }; static dllfunc_t texture3dextfuncs[] = { -{ "glTexImage3DEXT" , (void **)&pglTexImage3D }, -{ "glTexSubImage3DEXT" , (void **)&pglTexSubImage3D }, -{ "glCopyTexSubImage3DEXT" , (void **)&pglCopyTexSubImage3D }, -{ NULL , NULL } + { GL_CALL( glTexImage3D ) }, + { GL_CALL( glTexSubImage3D ) }, + { GL_CALL( glCopyTexSubImage3D ) }, + { NULL , NULL } }; static dllfunc_t texturecompressionfuncs[] = { -{ "glCompressedTexImage3DARB" , (void **)&pglCompressedTexImage3DARB }, -{ "glCompressedTexImage2DARB" , (void **)&pglCompressedTexImage2DARB }, -{ "glCompressedTexImage1DARB" , (void **)&pglCompressedTexImage1DARB }, -{ "glCompressedTexSubImage3DARB" , (void **)&pglCompressedTexSubImage3DARB }, -{ "glCompressedTexSubImage2DARB" , (void **)&pglCompressedTexSubImage2DARB }, -{ "glCompressedTexSubImage1DARB" , (void **)&pglCompressedTexSubImage1DARB }, -{ "glGetCompressedTexImageARB" , (void **)&pglGetCompressedTexImage }, -{ NULL , NULL } + { GL_CALL( glCompressedTexImage3DARB ) }, + { GL_CALL( glCompressedTexImage2DARB ) }, + { GL_CALL( glCompressedTexImage1DARB ) }, + { GL_CALL( glCompressedTexSubImage3DARB ) }, + { GL_CALL( glCompressedTexSubImage2DARB ) }, + { GL_CALL( glCompressedTexSubImage1DARB ) }, + { GL_CALL( glGetCompressedTexImage ) }, + { NULL , NULL } +}; + +static dllfunc_t vbofuncs[] = +{ + { GL_CALL( glBindBufferARB ) }, + { GL_CALL( glDeleteBuffersARB ) }, + { GL_CALL( glGenBuffersARB ) }, + { GL_CALL( glIsBufferARB ) }, + { GL_CALL( glMapBufferARB ) }, + { GL_CALL( glUnmapBufferARB ) }, // , + { GL_CALL( glBufferDataARB ) }, + { GL_CALL( glBufferSubDataARB ) }, + { NULL, NULL} }; static void GL_SetupAttributes( void ); @@ -884,7 +897,6 @@ qboolean R_Init_Video( void ) return true; } - #ifdef XASH_GLES void GL_InitExtensionsGLES( void ) { @@ -1039,6 +1051,8 @@ void GL_InitExtensionsBigGL() GL_CheckExtension( "GL_ARB_depth_buffer_float", NULL, "gl_arb_depth_float", GL_ARB_DEPTH_FLOAT_EXT ); GL_CheckExtension( "GL_EXT_gpu_shader4", NULL, NULL, GL_EXT_GPU_SHADER4 ); // don't confuse users GL_CheckExtension( "GL_ARB_shading_language_100", NULL, "gl_glslprogram", GL_SHADER_GLSL100_EXT ); + GL_CheckExtension( "GL_ARB_vertex_buffer_object", vbofuncs, "gl_vertex_buffer_object", GL_ARB_VERTEX_BUFFER_OBJECT_EXT ); + // rectangle textures support GL_CheckExtension( "GL_ARB_texture_rectangle", NULL, "gl_texture_rectangle", GL_TEXTURE_2D_RECT_EXT ); diff --git a/engine/server/sv_client.c b/engine/server/sv_client.c index 8e72b24b..3a37d1de 100644 --- a/engine/server/sv_client.c +++ b/engine/server/sv_client.c @@ -375,6 +375,7 @@ void SV_ConnectClient( netadr_t from ) sv.current_client = newcl; newcl->edict = EDICT_NUM( (newcl - svs.clients) + 1 ); newcl->challenge = challenge; // save challenge for checksumming + if( newcl->frames ) Mem_Free( newcl->frames ); newcl->frames = (client_frame_t *)Z_Calloc( sizeof( client_frame_t ) * SV_UPDATE_BACKUP ); newcl->userid = g_userid++; // create unique userid newcl->state = cs_connected; @@ -1875,6 +1876,26 @@ static qboolean SV_DownloadFile_f( sv_client_t *cl ) { if( sv_send_resources.value ) { + int i; + + // security: allow download only precached resources + for( i = 0; i < sv.num_resources; i++ ) + { + const char *cmpname = name; + + if( sv.resources[i].type == t_sound ) + cmpname += sizeof( DEFAULT_SOUNDPATH ) - 1; // cut "sound/" off + + if( !Q_strncmp( sv.resources[i].szFileName, cmpname, 64 ) ) + break; + } + + if( i == sv.num_resources ) + { + SV_FailDownload( cl, name ); + return true; + } + // also check the model textures if( !Q_stricmp( COM_FileExtension( name ), "mdl" )) { diff --git a/engine/server/sv_frame.c b/engine/server/sv_frame.c index 98e19aac..9544ee76 100644 --- a/engine/server/sv_frame.c +++ b/engine/server/sv_frame.c @@ -998,9 +998,15 @@ void SV_InactivateClients( void ) if( !cl->state || !cl->edict ) continue; - if( !cl->edict || FBitSet( cl->edict->v.flags, FL_FAKECLIENT )) + if( !cl->edict ) continue; + if( FBitSet( cl->edict->v.flags, FL_FAKECLIENT )) + { + SV_DropClient( cl, false ); + continue; + } + if( cl->state > cs_connected ) cl->state = cs_connected; @@ -1015,4 +1021,4 @@ void SV_InactivateClients( void ) MSG_Clear( &cl->netchan.message ); MSG_Clear( &cl->datagram ); } -} \ No newline at end of file +} diff --git a/engine/server/sv_game.c b/engine/server/sv_game.c index 179cb121..3c9cac80 100644 --- a/engine/server/sv_game.c +++ b/engine/server/sv_game.c @@ -1294,16 +1294,21 @@ void pfnSetModel( edict_t *e, const char *m ) if( COM_CheckString( name )) { + qboolean notfound = true; + // check to see if model was properly precached for( i = 1; i < MAX_MODELS && sv.model_precache[i][0]; i++ ) { if( !Q_stricmp( sv.model_precache[i], name )) + { + notfound = false; break; + } } - if( i == MAX_MODELS ) + if( notfound ) { - Con_Printf( S_ERROR "no precache: %s\n", name ); + Con_Printf( S_ERROR "Failed to set model %s: was not precached\n", name ); return; } } @@ -1311,7 +1316,7 @@ void pfnSetModel( edict_t *e, const char *m ) if( e == svgame.edicts ) { if( sv.state == ss_active ) - Con_Printf( S_ERROR "world model can't be changed\n" ); + Con_Printf( S_ERROR "Failed to set model %s: world model cannot be changed\n", name ); return; } @@ -1358,8 +1363,8 @@ int pfnModelIndex( const char *m ) return i; } - Con_Printf( S_ERROR "no precache: %s\n", name ); - return 0; + Con_Printf( S_ERROR "Cannot get index for model %s: not precached\n", name ); + return 0; } /* @@ -4410,7 +4415,7 @@ void pfnForceUnmodified( FORCE_TYPE type, float *mins, float *maxs, const char * if( !Q_strcmp( filename, pc->filename )) return; } - Con_Printf( S_ERROR "no precache: %s\n", filename ); + Con_Printf( S_ERROR "Failed to enforce consistency for %s: was not precached\n", filename ); } } diff --git a/engine/server/sv_init.c b/engine/server/sv_init.c index affe4bff..727c7e06 100644 --- a/engine/server/sv_init.c +++ b/engine/server/sv_init.c @@ -274,15 +274,11 @@ model_t *SV_ModelHandle( int modelindex ) return sv.models[modelindex]; } -void SV_CreateGenericResources( void ) +void SV_ReadResourceList( const char *filename ) { - string filename, token; + string token; char *afile, *pfile; - Q_strncpy( filename, sv.model_precache[1], sizeof( filename )); - COM_ReplaceExtension( filename, ".res" ); - COM_FixSlashes( filename ); - afile = FS_LoadFile( filename, NULL, false ); if( !afile ) return; @@ -304,6 +300,18 @@ void SV_CreateGenericResources( void ) Mem_Free( afile ); } +void SV_CreateGenericResources( void ) +{ + string filename; + + Q_strncpy( filename, sv.model_precache[1], sizeof( filename )); + COM_ReplaceExtension( filename, ".res" ); + COM_FixSlashes( filename ); + + SV_ReadResourceList( filename ); + SV_ReadResourceList( "reslist.txt" ); +} + void SV_CreateResourceList( void ) { qboolean ffirstsent = false; diff --git a/engine/server/sv_save.c b/engine/server/sv_save.c index 5fb9e478..25dc0d3c 100644 --- a/engine/server/sv_save.c +++ b/engine/server/sv_save.c @@ -842,7 +842,7 @@ static SAVERESTOREDATA *LoadSaveData( const char *level ) if(( pFile = FS_Open( name, "rb", true )) == NULL ) { - Con_Printf( S_ERROR "couldn't open.\n" ); + Con_Printf( S_ERROR "Couldn't open save data file %s.\n", name ); return NULL; } diff --git a/mainui b/mainui index 995ed39b..33e6e585 160000 --- a/mainui +++ b/mainui @@ -1 +1 @@ -Subproject commit 995ed39b9a88a20c650df624c309f7cee3ecfdee +Subproject commit 33e6e585e875d91ae44b22d5425f1c38f2413741