From 480f3fbcfb57828c7d8b3e4b500897bcdb836bff Mon Sep 17 00:00:00 2001 From: Thomas Debesse Date: Sun, 6 Jul 2025 05:17:31 +0200 Subject: [PATCH] engine: load worldspawn separately from other map data --- src/dummygame/cgame.cpp | 3 +- src/engine/client/cg_msgdef.h | 15 +++- src/engine/client/cl_cgame.cpp | 14 +++- src/engine/null/null_renderer.cpp | 6 +- src/engine/renderer/tr_bsp.cpp | 124 +++++++++++++++--------------- src/engine/renderer/tr_init.cpp | 3 +- src/engine/renderer/tr_local.h | 3 +- src/engine/renderer/tr_public.h | 3 +- src/shared/client/cg_api.cpp | 11 ++- src/shared/client/cg_api.h | 3 +- 10 files changed, 109 insertions(+), 76 deletions(-) diff --git a/src/dummygame/cgame.cpp b/src/dummygame/cgame.cpp index 275d226455..56660c639e 100644 --- a/src/dummygame/cgame.cpp +++ b/src/dummygame/cgame.cpp @@ -155,7 +155,8 @@ class LoadMapCmd : public Cmd::StaticCmd // the renderer starts (though there is no way to get it back later) refdef.gradingWeights[0] = 1.0f; - trap_R_LoadWorldMap(("maps/" + args.Argv(2) + ".bsp").c_str()); + trap_R_LoadWorldSpawn(("maps/" + args.Argv(2)).c_str()); + trap_R_LoadWorldData(); // This is done by EndRegistration but there is no way to invoke that bool reflection; diff --git a/src/engine/client/cg_msgdef.h b/src/engine/client/cg_msgdef.h index 9918f5b3d3..0eace5ad65 100644 --- a/src/engine/client/cg_msgdef.h +++ b/src/engine/client/cg_msgdef.h @@ -159,7 +159,8 @@ enum cgameImport_t CG_R_GETSHADERNAMEFROMHANDLE, CG_R_SCISSOR_ENABLE, CG_R_SCISSOR_SET, - CG_R_LOADWORLDMAP, + CG_R_LOADWORLDSPAWN, + CG_R_LOADWORLDDATA, CG_R_REGISTERMODEL, CG_R_REGISTERSKIN, CG_R_REGISTERSHADER, @@ -301,8 +302,16 @@ namespace Render { IPC::Message, int>, IPC::Reply >; - // TODO is it really async? - using LoadWorldMapMsg = IPC::Message, std::string>; + // It better be sync even if it doesn't return anything. + using LoadWorldSpawnMsg = IPC::SyncMessage< + IPC::Message, std::string>, + IPC::Reply + >; + // It better be sync even if it doesn't return anything. + using LoadWorldDataMsg = IPC::SyncMessage< + IPC::Message>, + IPC::Reply + >; using RegisterModelMsg = IPC::SyncMessage< IPC::Message, std::string>, IPC::Reply diff --git a/src/engine/client/cl_cgame.cpp b/src/engine/client/cl_cgame.cpp index b1e4791324..2e62d6c311 100644 --- a/src/engine/client/cl_cgame.cpp +++ b/src/engine/client/cl_cgame.cpp @@ -1185,10 +1185,18 @@ void CGameVM::QVMSyscall(int syscallNum, Util::Reader& reader, IPC::Channel& cha }); break; - case CG_R_LOADWORLDMAP: - IPC::HandleMsg(channel, std::move(reader), [this] (const std::string& mapName) { + case CG_R_LOADWORLDSPAWN: + IPC::HandleMsg(channel, std::move(reader), [this] (const std::string& mapName, bool &done) { re.SetWorldVisData(CM_ClusterPVS(-1)); - re.LoadWorld(mapName.c_str()); + re.LoadWorldSpawn(mapName); + done = true; + }); + break; + + case CG_R_LOADWORLDDATA: + IPC::HandleMsg(channel, std::move(reader), [this] (bool &done) { + re.LoadWorldData(); + done = true; }); break; diff --git a/src/engine/null/null_renderer.cpp b/src/engine/null/null_renderer.cpp index 4e2c3b325e..04d886181a 100644 --- a/src/engine/null/null_renderer.cpp +++ b/src/engine/null/null_renderer.cpp @@ -75,7 +75,8 @@ void RE_GlyphChar( fontInfo_t *font, int, glyphInfo_t *glyph ) RE_Glyph( font, nullptr, glyph ); } void RE_UnregisterFont( fontInfo_t* ) { } -void RE_LoadWorldMap( const char * ) { } +void RE_LoadWorldSpawn( const std::string ) { } +void RE_LoadWorldData() { } void RE_SetWorldVisData( const byte * ) { } void RE_EndRegistration() { } void RE_ClearScene() { } @@ -208,7 +209,8 @@ refexport_t *GetRefAPI( int, refimport_t* ) re.Glyph = RE_Glyph; re.GlyphChar = RE_GlyphChar; re.UnregisterFont = RE_UnregisterFont; - re.LoadWorld = RE_LoadWorldMap; + re.LoadWorldSpawn = RE_LoadWorldSpawn; + re.LoadWorldData = RE_LoadWorldData; re.SetWorldVisData = RE_SetWorldVisData; re.EndRegistration = RE_EndRegistration; diff --git a/src/engine/renderer/tr_bsp.cpp b/src/engine/renderer/tr_bsp.cpp index 3cd0c9b9be..954e7f5679 100644 --- a/src/engine/renderer/tr_bsp.cpp +++ b/src/engine/renderer/tr_bsp.cpp @@ -31,8 +31,9 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA ======================================================== Loads and prepares a map file for scene rendering. -A single entry point: -RE_LoadWorldMap(const char *name); +Two entry points, to be called in this order: +RE_LoadWorldSpawn(const std::string worldName); +RE_LoadWorldData(); ======================================================== */ @@ -411,11 +412,11 @@ static void LoadRGBEToBytes( const char *name, byte **ldrImage, int *width, int Z_Free( hdrImage ); } -static std::vector R_LoadExternalLightmaps( const char *mapName ) +static std::vector R_LoadExternalLightmaps( std::string worldName ) { const char *const extensions[] {".png", ".tga", ".webp", ".crn", ".jpg", ".jpeg"}; std::vector files[ ARRAY_LEN( extensions ) ]; - for ( const std::string& filename : FS::PakPath::ListFiles( mapName ) ) { + for ( const std::string& filename : FS::PakPath::ListFiles( worldName ) ) { for ( size_t i = 0; i < ARRAY_LEN( extensions ); i++ ) { if ( Str::IsISuffix( extensions[ i ], filename ) ) @@ -440,7 +441,7 @@ static std::vector R_LoadExternalLightmaps( const char *mapName ) R_LoadLightmaps =============== */ -static void R_LoadLightmaps( lump_t *l, const char *bspName ) +static void R_LoadLightmaps( lump_t *l, std::string worldName ) { tr.worldLightMapping = r_precomputedLighting->integer && tr.lightMode == lightMode_t::MAP; @@ -454,11 +455,6 @@ static void R_LoadLightmaps( lump_t *l, const char *bspName ) int len = l->filelen; if ( !len ) { - char mapName[ MAX_QPATH ]; - - Q_strncpyz( mapName, bspName, sizeof( mapName ) ); - COM_StripExtension3( mapName, mapName, sizeof( mapName ) ); - if ( tr.worldHDR_RGBE ) { // we are about to upload textures @@ -469,7 +465,7 @@ static void R_LoadLightmaps( lump_t *l, const char *bspName ) byte *ldrImage; std::vector hdrFiles; - for ( const std::string& filename : FS::PakPath::ListFiles( mapName ) ) + for ( const std::string& filename : FS::PakPath::ListFiles( worldName ) ) { if ( Str::IsISuffix( ".hdr", filename ) ) { @@ -487,17 +483,17 @@ static void R_LoadLightmaps( lump_t *l, const char *bspName ) for ( const std::string& filename : hdrFiles ) { - Log::Debug("...loading external lightmap as RGB8 LDR '%s/%s'", mapName, filename ); + Log::Debug("...loading external lightmap as RGB8 LDR '%s/%s'", worldName, filename ); width = height = 0; - LoadRGBEToBytes( va( "%s/%s", mapName, filename.c_str() ), &ldrImage, &width, &height ); + LoadRGBEToBytes( va( "%s/%s", worldName.c_str(), filename.c_str() ), &ldrImage, &width, &height ); imageParams_t imageParams = {}; imageParams.bits = IF_NOPICMIP | IF_LIGHTMAP; imageParams.filterType = filterType_t::FT_DEFAULT; imageParams.wrapType = wrapTypeEnum_t::WT_CLAMP; - auto image = R_CreateImage( va( "%s/%s", mapName, filename.c_str() ), (const byte **)&ldrImage, width, height, 1, imageParams ); + auto image = R_CreateImage( va( "%s/%s", worldName.c_str(), filename.c_str() ), (const byte **)&ldrImage, width, height, 1, imageParams ); tr.lightmaps.push_back( image ); @@ -506,7 +502,7 @@ static void R_LoadLightmaps( lump_t *l, const char *bspName ) if (tr.worldDeluxeMapping) { // load deluxemaps - std::vector lightmapFiles = R_LoadExternalLightmaps(mapName); + std::vector lightmapFiles = R_LoadExternalLightmaps( worldName ); if (lightmapFiles.empty()) { Log::Warn("no lightmap files found"); tr.worldLightMapping = false; @@ -517,21 +513,21 @@ static void R_LoadLightmaps( lump_t *l, const char *bspName ) Log::Debug("...loading %i deluxemaps", lightmapFiles.size()); for (const std::string& filename : lightmapFiles) { - Log::Debug("...loading external lightmap '%s/%s'", mapName, filename); + Log::Debug("...loading external lightmap '%s/%s'", worldName.c_str(), filename); imageParams_t imageParams = {}; imageParams.bits = IF_NOPICMIP | IF_NORMALMAP; imageParams.filterType = filterType_t::FT_DEFAULT; imageParams.wrapType = wrapTypeEnum_t::WT_CLAMP; - auto image = R_FindImageFile(va("%s/%s", mapName, filename.c_str()), imageParams); + auto image = R_FindImageFile(va("%s/%s", worldName.c_str(), filename.c_str()), imageParams); tr.deluxemaps.push_back(image); } } } else { - std::vector lightmapFiles = R_LoadExternalLightmaps(mapName); + std::vector lightmapFiles = R_LoadExternalLightmaps( worldName ); if (lightmapFiles.empty()) { Log::Warn("no lightmap files found"); tr.worldLightMapping = false; @@ -545,7 +541,7 @@ static void R_LoadLightmaps( lump_t *l, const char *bspName ) R_SyncRenderThread(); for (size_t i = 0; i < lightmapFiles.size(); i++) { - Log::Debug("...loading external lightmap '%s/%s'", mapName, lightmapFiles[i]); + Log::Debug("...loading external lightmap '%s/%s'", worldName.c_str(), lightmapFiles[i]); if (!tr.worldDeluxeMapping || i % 2 == 0) { imageParams_t imageParams = {}; @@ -553,7 +549,7 @@ static void R_LoadLightmaps( lump_t *l, const char *bspName ) imageParams.filterType = filterType_t::FT_LINEAR; imageParams.wrapType = wrapTypeEnum_t::WT_CLAMP; - auto image = R_FindImageFile(va("%s/%s", mapName, lightmapFiles[i].c_str()), imageParams); + auto image = R_FindImageFile(va("%s/%s", worldName.c_str(), lightmapFiles[i].c_str()), imageParams); tr.lightmaps.push_back(image); } else if (tr.worldDeluxeMapping) @@ -563,7 +559,7 @@ static void R_LoadLightmaps( lump_t *l, const char *bspName ) imageParams.filterType = filterType_t::FT_LINEAR; imageParams.wrapType = wrapTypeEnum_t::WT_CLAMP; - auto image = R_FindImageFile(va("%s/%s", mapName, lightmapFiles[i].c_str()), imageParams); + auto image = R_FindImageFile(va("%s/%s", worldName.c_str(), lightmapFiles[i].c_str()), imageParams); tr.deluxemaps.push_back( image ); } } @@ -4465,25 +4461,24 @@ static void SetWorldLight() { } } -/* -================= -RE_LoadWorldMap +std::string worldName; +std::string bspBuffer; +dheader_t *bspHeader; +byte *bspStartMarker; -Called directly from cgame -================= -*/ -void RE_LoadWorldMap( const char *name ) +// Called directly from cgame. +void RE_LoadWorldSpawn( const std::string name ) { - int i; - dheader_t *header; - byte *startMarker; + worldName = name; + + Log::Debug("----- RE_LoadWorldSpawn( %s ) -----", worldName ); if ( tr.worldMapLoaded ) { Sys::Drop( "ERROR: attempted to redundantly load world map" ); } - Log::Debug("----- RE_LoadWorldMap( %s ) -----", name ); + tr.worldMapLoaded = true; // set default sun direction to be used if it isn't // overridden by a shader @@ -4493,17 +4488,19 @@ void RE_LoadWorldMap( const char *name ) VectorNormalize( tr.sunDirection ); - tr.worldMapLoaded = true; + std::string bspName = name + ".bsp"; // load it std::error_code err; - std::string buffer = FS::PakPath::ReadFile( name, err ); + bspBuffer = FS::PakPath::ReadFile( bspName, err ); if ( err ) { - Sys::Drop( "RE_LoadWorldMap: %s not found", name ); + Sys::Drop( "RE_LoadWorldSpawn: %s not found", bspName ); } + Log::Debug( "Loading %s…", bspName ); + // clear tr.world so if the level fails to load, the next // try will not look at the partially loaded version tr.world = nullptr; @@ -4514,45 +4511,45 @@ void RE_LoadWorldMap( const char *name ) tr.worldDeluxeMapping = false; // set by R_LoadEntities tr.worldHDR_RGBE = false; // set by R_LoadEntities tr.mapOverBrightBits = r_overbrightDefaultExponent.Get(); // maybe set by R_LoadEntities - tr.overbrightBits = std::min( tr.mapOverBrightBits, r_overbrightBits.Get() ); // set by RE_LoadWorldMap - tr.mapLightFactor = 1.0f; // set by RE_LoadWorldMap - tr.identityLight = 1.0f; // set by RE_LoadWorldMap + tr.overbrightBits = std::min( tr.mapOverBrightBits, r_overbrightBits.Get() ); // set by RE_LoadWorldSpawn + tr.mapLightFactor = 1.0f; // set by RE_LoadWorldSpawn + tr.identityLight = 1.0f; // set by RE_LoadWorldSpawn s_worldData = {}; - Q_strncpyz( s_worldData.name, name, sizeof( s_worldData.name ) ); + Q_strncpyz( s_worldData.name, bspName.c_str(), sizeof( s_worldData.name ) ); Q_strncpyz( s_worldData.baseName, COM_SkipPath( s_worldData.name ), sizeof( s_worldData.name ) ); COM_StripExtension3( s_worldData.baseName, s_worldData.baseName, sizeof( s_worldData.baseName ) ); tr.loadingMap = s_worldData.baseName; - startMarker = (byte*) ri.Hunk_Alloc( 0, ha_pref::h_low ); + bspStartMarker = (byte*) ri.Hunk_Alloc( 0, ha_pref::h_low ); - header = ( dheader_t * ) buffer.data(); - fileBase = ( byte * ) header; + bspHeader = ( dheader_t * ) bspBuffer.data(); + fileBase = ( byte * ) bspHeader; - i = LittleLong( header->version ); + int i = LittleLong( bspHeader->version ); if ( i != BSP_VERSION && i != BSP_VERSION_Q3 ) { - Sys::Drop( "RE_LoadWorldMap: %s has wrong version number (%i should be %i for ET or %i for Q3)", - name, i, BSP_VERSION, BSP_VERSION_Q3 ); + Sys::Drop( "RE_LoadWorldSpawn: %s has wrong version number (%i should be %i for ET or %i for Q3)", + bspName, i, BSP_VERSION, BSP_VERSION_Q3 ); } // swap all the lumps for ( unsigned j = 0; j < sizeof( dheader_t ) / 4; j++ ) { - ( ( int * ) header ) [ j ] = LittleLong( ( ( int * ) header ) [ j ] ); + ( ( int * ) bspHeader ) [ j ] = LittleLong( ( ( int * ) bspHeader ) [ j ] ); } if ( glConfig2.reflectionMappingAvailable ) { // TODO: Take into account potential shader changes - headerString = Str::Format( "%i %i %i %i %i", header->lumps[LUMP_PLANES].filelen, header->lumps[LUMP_NODES].filelen, - header->lumps[LUMP_LEAFS].filelen, header->lumps[LUMP_BRUSHES].filelen, header->lumps[LUMP_SURFACES].filelen ); + headerString = Str::Format( "%i %i %i %i %i", bspHeader->lumps[LUMP_PLANES].filelen, bspHeader->lumps[LUMP_NODES].filelen, + bspHeader->lumps[LUMP_LEAFS].filelen, bspHeader->lumps[LUMP_BRUSHES].filelen, bspHeader->lumps[LUMP_SURFACES].filelen ); } // load into heap - std::string externalEntitiesFileName = FS::Path::StripExtension( name ) + ".ent"; + std::string externalEntitiesFileName = worldName + ".ent"; std::string externalEntities = FS::PakPath::ReadFile( externalEntitiesFileName, err ); if ( err ) { @@ -4563,31 +4560,36 @@ void RE_LoadWorldMap( const char *name ) } externalEntities = ""; } - R_LoadEntities( &header->lumps[ LUMP_ENTITIES ], externalEntities ); + R_LoadEntities( &bspHeader->lumps[ LUMP_ENTITIES ], externalEntities ); // Now we can set this after checking a possible worldspawn value for mapOverbrightBits tr.overbrightBits = std::min( tr.mapOverBrightBits, r_overbrightBits.Get() ); +} - R_LoadShaders( &header->lumps[ LUMP_SHADERS ] ); +// Called directly from cgame. +// Should be called after RE_LoadWorldSpawn(). +void RE_LoadWorldData() +{ + R_LoadShaders( &bspHeader->lumps[ LUMP_SHADERS ] ); - R_LoadLightmaps( &header->lumps[ LUMP_LIGHTMAPS ], name ); + R_LoadLightmaps( &bspHeader->lumps[ LUMP_LIGHTMAPS ], worldName ); - R_LoadPlanes( &header->lumps[ LUMP_PLANES ] ); + R_LoadPlanes( &bspHeader->lumps[ LUMP_PLANES ] ); - R_LoadSurfaces( &header->lumps[ LUMP_SURFACES ], &header->lumps[ LUMP_DRAWVERTS ], &header->lumps[ LUMP_DRAWINDEXES ] ); + R_LoadSurfaces( &bspHeader->lumps[ LUMP_SURFACES ], &bspHeader->lumps[ LUMP_DRAWVERTS ], &bspHeader->lumps[ LUMP_DRAWINDEXES ] ); - R_LoadMarksurfaces( &header->lumps[ LUMP_LEAFSURFACES ] ); + R_LoadMarksurfaces( &bspHeader->lumps[ LUMP_LEAFSURFACES ] ); - R_LoadNodesAndLeafs( &header->lumps[ LUMP_NODES ], &header->lumps[ LUMP_LEAFS ] ); + R_LoadNodesAndLeafs( &bspHeader->lumps[ LUMP_NODES ], &bspHeader->lumps[ LUMP_LEAFS ] ); - R_LoadSubmodels( &header->lumps[ LUMP_MODELS ] ); + R_LoadSubmodels( &bspHeader->lumps[ LUMP_MODELS ] ); // moved fog lump loading here, so fogs can be tagged with a model num - R_LoadFogs( &header->lumps[ LUMP_FOGS ], &header->lumps[ LUMP_BRUSHES ], &header->lumps[ LUMP_BRUSHSIDES ] ); + R_LoadFogs( &bspHeader->lumps[ LUMP_FOGS ], &bspHeader->lumps[ LUMP_BRUSHES ], &bspHeader->lumps[ LUMP_BRUSHSIDES ] ); - R_LoadVisibility( &header->lumps[ LUMP_VISIBILITY ] ); + R_LoadVisibility( &bspHeader->lumps[ LUMP_VISIBILITY ] ); - R_LoadLightGrid( &header->lumps[ LUMP_LIGHTGRID ] ); + R_LoadLightGrid( &bspHeader->lumps[ LUMP_LIGHTGRID ] ); // create a static vbo for the world // Do SetWorldLight() before R_CreateWorldVBO(), because the latter will use the world light values to generate materials @@ -4600,7 +4602,7 @@ void RE_LoadWorldMap( const char *name ) FinishSkybox(); } - s_worldData.dataSize = ( byte * ) ri.Hunk_Alloc( 0, ha_pref::h_low ) - startMarker; + s_worldData.dataSize = ( byte * ) ri.Hunk_Alloc( 0, ha_pref::h_low ) - bspStartMarker; // only set tr.world now that we know the entire level has loaded properly tr.world = &s_worldData; diff --git a/src/engine/renderer/tr_init.cpp b/src/engine/renderer/tr_init.cpp index a8cad82695..c7e6092a89 100644 --- a/src/engine/renderer/tr_init.cpp +++ b/src/engine/renderer/tr_init.cpp @@ -1591,7 +1591,8 @@ ScreenshotCmd screenshotPNGRegistration("screenshotPNG", ssFormat_t::SSF_PNG, "p re.RegisterSkin = RE_RegisterSkin; re.RegisterShader = RE_RegisterShader; - re.LoadWorld = RE_LoadWorldMap; + re.LoadWorldSpawn = RE_LoadWorldSpawn; + re.LoadWorldData = RE_LoadWorldData; re.SetWorldVisData = RE_SetWorldVisData; re.EndRegistration = RE_EndRegistration; diff --git a/src/engine/renderer/tr_local.h b/src/engine/renderer/tr_local.h index 059bc0ec7c..9daac4f059 100644 --- a/src/engine/renderer/tr_local.h +++ b/src/engine/renderer/tr_local.h @@ -3033,7 +3033,8 @@ inline bool checkGLErrors() void RE_BeginFrame(); bool RE_BeginRegistration( glconfig_t *glconfig, glconfig2_t *glconfig2 ); - void RE_LoadWorldMap( const char *mapname ); + void RE_LoadWorldSpawn( const std::string worldName ); + void RE_LoadWorldData(); void RE_SetWorldVisData( const byte *vis ); qhandle_t RE_RegisterModel( const char *name ); qhandle_t RE_RegisterSkin( const char *name ); diff --git a/src/engine/renderer/tr_public.h b/src/engine/renderer/tr_public.h index 65b5031f00..8e434ca349 100644 --- a/src/engine/renderer/tr_public.h +++ b/src/engine/renderer/tr_public.h @@ -184,7 +184,8 @@ struct refexport_t void ( *Glyph )( fontInfo_t *font, const char *str, glyphInfo_t *glyph ); void ( *GlyphChar )( fontInfo_t *font, int ch, glyphInfo_t *glyph ); - void ( *LoadWorld )( const char *name ); + void ( *LoadWorldSpawn )( std::string worldName ); + void ( *LoadWorldData )(); // the vis data is a large enough block of data that we go to the trouble // of sharing it with the clipmodel subsystem diff --git a/src/shared/client/cg_api.cpp b/src/shared/client/cg_api.cpp index 9eea1f66be..fc6a904099 100644 --- a/src/shared/client/cg_api.cpp +++ b/src/shared/client/cg_api.cpp @@ -257,9 +257,16 @@ void trap_R_ScissorSet( int x, int y, int w, int h ) cmdBuffer.SendMsg(x, y, w, h); } -void trap_R_LoadWorldMap( const char *mapname ) +void trap_R_LoadWorldSpawn( std::string worldName ) { - VM::SendMsg(mapname); + bool done; + VM::SendMsg(worldName, done); +} + +void trap_R_LoadWorldData() +{ + bool done; + VM::SendMsg(done); } qhandle_t trap_R_RegisterModel( const char *name ) diff --git a/src/shared/client/cg_api.h b/src/shared/client/cg_api.h index 4e2781c3d7..1314bb86c5 100644 --- a/src/shared/client/cg_api.h +++ b/src/shared/client/cg_api.h @@ -60,7 +60,8 @@ void trap_S_UpdateEntityPosition( int entityNum, const vec3_t origin void trap_S_Respatialize( int entityNum, const vec3_t origin, vec3_t axis[ 3 ], int inwater ); sfxHandle_t trap_S_RegisterSound( const char *sample, bool compressed ); void trap_S_StartBackgroundTrack( const char *intro, const char *loop ); -void trap_R_LoadWorldMap( const char *mapname ); +void trap_R_LoadWorldSpawn( std::string worldName ); +void trap_R_LoadWorldData(); qhandle_t trap_R_RegisterModel( const char *name ); qhandle_t trap_R_RegisterSkin( const char *name ); qhandle_t trap_R_RegisterShader( const char *name, int flags );