/* ============= RE_StretchRaw FIXME: not exactly backend Stretches a raw 32 bit power of 2 bitmap image over the given screen rectangle. Used for cinematics. ============= */ void RE_StretchRaw( int x, int y, int w, int h, int cols, int rows, const byte *data, int client, qboolean dirty ) { int i, j; int start, end; if ( !tr.registered ) { return; } R_IssuePendingRenderCommands(); if ( tess.numIndexes ) { RB_EndSurface(); } // we definately want to sync every frame for the cinematics qglFinish(); start = 0; if ( r_speeds->integer ) { start = ri.Milliseconds(); } // make sure rows and cols are powers of 2 for ( i = 0 ; ( 1 << i ) < cols ; i++ ) { } for ( j = 0 ; ( 1 << j ) < rows ; j++ ) { } if ( ( 1 << i ) != cols || ( 1 << j ) != rows ) { ri.Error( ERR_DROP, "Draw_StretchRaw: size not a power of 2: %i by %i", cols, rows ); } GL_Bind( tr.scratchImage[client] ); // if the scratchImage isn't in the format we want, specify it as a new texture if ( cols != tr.scratchImage[client]->width || rows != tr.scratchImage[client]->height ) { tr.scratchImage[client]->width = tr.scratchImage[client]->uploadWidth = cols; tr.scratchImage[client]->height = tr.scratchImage[client]->uploadHeight = rows; #ifdef USE_OPENGLES qglTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, cols, rows, 0, GL_RGBA, GL_UNSIGNED_BYTE, data ); #else qglTexImage2D( GL_TEXTURE_2D, 0, 3, cols, rows, 0, GL_RGBA, GL_UNSIGNED_BYTE, data ); #endif qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE ); qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE ); } else { if ( dirty ) { // otherwise, just subimage upload it so that drivers can tell we are going to be changing // it and don't try and do a texture compression qglTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, cols, rows, GL_RGBA, GL_UNSIGNED_BYTE, data ); } } if ( r_speeds->integer ) { end = ri.Milliseconds(); ri.Printf( PRINT_ALL, "qglTexSubImage2D %i, %i: %i msec\n", cols, rows, end - start ); } RB_SetGL2D(); qglColor3f( tr.identityLight, tr.identityLight, tr.identityLight ); #ifdef USE_OPENGLES GLfloat tex[] = { 0.5f / cols, 0.5f / rows, ( cols - 0.5f ) / cols , 0.5f / rows, ( cols - 0.5f ) / cols, ( rows - 0.5f ) / rows, 0.5f / cols, ( rows - 0.5f ) / rows }; GLfloat vtx[] = { x, y, x+w, y, x+w, y+h, x, y+h }; GLboolean text = qglIsEnabled(GL_TEXTURE_COORD_ARRAY); GLboolean glcol = qglIsEnabled(GL_COLOR_ARRAY); if (glcol) qglDisableClientState(GL_COLOR_ARRAY); if (!text) qglEnableClientState( GL_TEXTURE_COORD_ARRAY ); qglTexCoordPointer( 2, GL_FLOAT, 0, tex ); qglVertexPointer ( 2, GL_FLOAT, 0, vtx ); qglDrawArrays( GL_TRIANGLE_FAN, 0, 4 ); if (!text) qglDisableClientState( GL_TEXTURE_COORD_ARRAY ); if (glcol) qglEnableClientState(GL_COLOR_ARRAY); #else qglBegin( GL_QUADS ); qglTexCoord2f( 0.5f / cols, 0.5f / rows ); qglVertex2f( x, y ); qglTexCoord2f( ( cols - 0.5f ) / cols, 0.5f / rows ); qglVertex2f( x + w, y ); qglTexCoord2f( ( cols - 0.5f ) / cols, ( rows - 0.5f ) / rows ); qglVertex2f( x + w, y + h ); qglTexCoord2f( 0.5f / cols, ( rows - 0.5f ) / rows ); qglVertex2f( x, y + h ); qglEnd(); #endif }
qhandle_t RE_RegisterSkin( const char *name ) { qhandle_t hSkin; skin_t *skin; if ( !name || !name[0] ) { ri->Printf( PRINT_ALL, "Empty name passed to RE_RegisterSkin\n" ); return 0; } if ( strlen( name ) >= MAX_QPATH ) { ri->Printf( PRINT_ALL, "Skin name exceeds MAX_QPATH\n" ); return 0; } // see if the skin is already loaded for ( hSkin = 1; hSkin < tr.numSkins ; hSkin++ ) { skin = tr.skins[hSkin]; if ( !Q_stricmp( skin->name, name ) ) { if( skin->numSurfaces == 0 ) { return 0; // default skin } return hSkin; } } // allocate a new skin if ( tr.numSkins == MAX_SKINS ) { ri->Printf( PRINT_ALL, "WARNING: RE_RegisterSkin( '%s' ) MAX_SKINS hit\n", name ); return 0; } tr.numSkins++; skin = (struct skin_s *)Hunk_Alloc( sizeof( skin_t ), h_low ); tr.skins[hSkin] = skin; Q_strncpyz( skin->name, name, sizeof( skin->name ) ); skin->numSurfaces = 0; // make sure the render thread is stopped R_IssuePendingRenderCommands(); // If not a .skin file, load as a single shader if ( strcmp( name + strlen( name ) - 5, ".skin" ) ) { /* skin->numSurfaces = 1; skin->surfaces[0] = (skinSurface_t *)Hunk_Alloc( sizeof(skin->surfaces[0]), h_low ); skin->surfaces[0]->shader = R_FindShader( name, lightmapsNone, stylesDefault, qtrue ); return hSkin; */ } char skinhead[MAX_QPATH]={0}; char skintorso[MAX_QPATH]={0}; char skinlower[MAX_QPATH]={0}; if ( RE_SplitSkins(name, (char*)&skinhead, (char*)&skintorso, (char*)&skinlower ) ) {//three part hSkin = RE_RegisterIndividualSkin(skinhead, hSkin); if (hSkin && strcmp(skinhead, skintorso)) { hSkin = RE_RegisterIndividualSkin(skintorso, hSkin); } if (hSkin && strcmp(skinhead, skinlower) && strcmp(skintorso, skinlower)) { hSkin = RE_RegisterIndividualSkin(skinlower, hSkin); } } else {//single skin hSkin = RE_RegisterIndividualSkin(name, hSkin); } return(hSkin); }
/* ==================== RE_BeginFrame If running in stereo, RE_BeginFrame will be called twice for each RE_EndFrame ==================== */ void RE_BeginFrame( stereoFrame_t stereoFrame ) { drawBufferCommand_t *cmd = NULL; colorMaskCommand_t *colcmd = NULL; if ( !tr.registered ) { return; } glState.finishCalled = qfalse; tr.frameCount++; tr.frameSceneNum = 0; // // do overdraw measurement // if ( r_measureOverdraw->integer ) { if ( glConfig.stencilBits < 4 ) { ri.Printf( PRINT_ALL, "Warning: not enough stencil bits to measure overdraw: %d\n", glConfig.stencilBits ); ri.Cvar_Set( "r_measureOverdraw", "0" ); r_measureOverdraw->modified = qfalse; } else if ( r_shadows->integer == 2 ) { ri.Printf( PRINT_ALL, "Warning: stencil shadows and overdraw measurement are mutually exclusive\n" ); ri.Cvar_Set( "r_measureOverdraw", "0" ); r_measureOverdraw->modified = qfalse; } else { R_IssuePendingRenderCommands(); qglEnable( GL_STENCIL_TEST ); qglStencilMask( ~0U ); qglClearStencil( 0U ); qglStencilFunc( GL_ALWAYS, 0U, ~0U ); qglStencilOp( GL_KEEP, GL_INCR, GL_INCR ); } r_measureOverdraw->modified = qfalse; } else { // this is only reached if it was on and is now off if ( r_measureOverdraw->modified ) { R_IssuePendingRenderCommands(); qglDisable( GL_STENCIL_TEST ); } r_measureOverdraw->modified = qfalse; } // // texturemode stuff // if ( r_textureMode->modified ) { R_IssuePendingRenderCommands(); GL_TextureMode( r_textureMode->string ); r_textureMode->modified = qfalse; } // // gamma stuff // if ( r_gamma->modified ) { r_gamma->modified = qfalse; R_IssuePendingRenderCommands(); R_SetColorMappings(); } // check for errors if ( !r_ignoreGLErrors->integer ) { int err; R_IssuePendingRenderCommands(); if ((err = qglGetError()) != GL_NO_ERROR) ri.Error(ERR_FATAL, "RE_BeginFrame() - glGetError() failed (0x%x)!", err); } if (glConfig.stereoEnabled) { if( !(cmd = R_GetCommandBuffer(sizeof(*cmd))) ) return; cmd->commandId = RC_DRAW_BUFFER; if ( stereoFrame == STEREO_LEFT ) { cmd->buffer = (int)GL_BACK_LEFT; } else if ( stereoFrame == STEREO_RIGHT ) { cmd->buffer = (int)GL_BACK_RIGHT; } else { ri.Error( ERR_FATAL, "RE_BeginFrame: Stereo is enabled, but stereoFrame was %i", stereoFrame ); } } else { if(r_anaglyphMode->integer) { if(r_anaglyphMode->modified) { // clear both, front and backbuffer. qglColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); backEnd.colorMask[0] = GL_FALSE; backEnd.colorMask[1] = GL_FALSE; backEnd.colorMask[2] = GL_FALSE; backEnd.colorMask[3] = GL_FALSE; if (glRefConfig.framebufferObject) { // clear all framebuffers if (tr.msaaResolveFbo) { FBO_Bind(tr.msaaResolveFbo); qglClear(GL_COLOR_BUFFER_BIT); } if (tr.renderFbo) { FBO_Bind(tr.renderFbo); qglClear(GL_COLOR_BUFFER_BIT); } FBO_Bind(NULL); } qglDrawBuffer(GL_FRONT); qglClear(GL_COLOR_BUFFER_BIT); qglDrawBuffer(GL_BACK); qglClear(GL_COLOR_BUFFER_BIT); r_anaglyphMode->modified = qfalse; } if(stereoFrame == STEREO_LEFT) { if( !(cmd = R_GetCommandBuffer(sizeof(*cmd))) ) return; if( !(colcmd = R_GetCommandBuffer(sizeof(*colcmd))) ) return; } else if(stereoFrame == STEREO_RIGHT) { clearDepthCommand_t *cldcmd; if( !(cldcmd = R_GetCommandBuffer(sizeof(*cldcmd))) ) return; cldcmd->commandId = RC_CLEARDEPTH; if( !(colcmd = R_GetCommandBuffer(sizeof(*colcmd))) ) return; } else ri.Error( ERR_FATAL, "RE_BeginFrame: Stereo is enabled, but stereoFrame was %i", stereoFrame ); R_SetColorMode(colcmd->rgba, stereoFrame, r_anaglyphMode->integer); colcmd->commandId = RC_COLORMASK; } else { if(stereoFrame != STEREO_CENTER) ri.Error( ERR_FATAL, "RE_BeginFrame: Stereo is disabled, but stereoFrame was %i", stereoFrame ); if( !(cmd = R_GetCommandBuffer(sizeof(*cmd))) ) return; } if(cmd) { cmd->commandId = RC_DRAW_BUFFER; if(r_anaglyphMode->modified) { qglColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); backEnd.colorMask[0] = 0; backEnd.colorMask[1] = 0; backEnd.colorMask[2] = 0; backEnd.colorMask[3] = 0; r_anaglyphMode->modified = qfalse; } if (!Q_stricmp(r_drawBuffer->string, "GL_FRONT")) cmd->buffer = (int)GL_FRONT; else cmd->buffer = (int)GL_BACK; } } tr.refdef.stereoFrame = stereoFrame; }
/* ============= RE_EndRegistration Touch all images to make sure they are resident ============= */ void RE_EndRegistration( void ) { R_IssuePendingRenderCommands(); if (!ri.Sys_LowPhysicalMemory()) { RB_ShowImages(); } }
/* ==================== RE_RegisterModel Loads in a model for the given name Zero will be returned if the model fails to load. An entry will be retained for failed models as an optimization to prevent disk rescanning if they are asked for again. ==================== */ qhandle_t RE_RegisterModel(const char *name) { model_t *mod; byte *buffer; int bufferLen = 0; int lod; int ident; qboolean loaded; qhandle_t hModel; int numLoaded; if (!name || !name[0]) { Ren_Developer("RE_RegisterModel: NULL name\n"); return 0; } else { Ren_Developer("RE_RegisterModel model: %s\n", name); } if (strlen(name) >= MAX_QPATH) { Ren_Print("Model name exceeds MAX_QPATH\n"); return 0; } // search the currently loaded models for (hModel = 1; hModel < tr.numModels; hModel++) { mod = tr.models[hModel]; if (!strcmp(mod->name, name)) { if (mod->type == MOD_BAD) { return 0; } return hModel; } } // allocate a new model_t if ((mod = R_AllocModel()) == NULL) { Ren_Warning("RE_RegisterModel: R_AllocModel() failed for '%s'\n", name); return 0; } // only set the name after the model has been successfully loaded Q_strncpyz(mod->name, name, sizeof(mod->name)); // make sure the render thread is stopped R_IssuePendingRenderCommands(); mod->numLods = 0; // load the files numLoaded = 0; if (strstr(name, ".mds") || strstr(name, ".mdm") || strstr(name, ".mdx") || strstr(name, ".md5mesh") || strstr(name, ".psk")) { // try loading skeletal file loaded = qfalse; bufferLen = ri.FS_ReadFile(name, (void **)&buffer); if (buffer) { loadmodel = mod; ident = LittleLong(*(unsigned *)buffer); #if 0 if (ident == MDS_IDENT) { loaded = R_LoadMDS(mod, buffer, name); } else #endif if (ident == MDM_IDENT) { loaded = R_LoadMDM(mod, buffer, name); } else if (ident == MDX_IDENT) { loaded = R_LoadMDX(mod, buffer, name); } #if defined(USE_REFENTITY_ANIMATIONSYSTEM) if (!Q_stricmpn((const char *)buffer, "MD5Version", 10)) { loaded = R_LoadMD5(mod, buffer, bufferLen, name); } else if (!Q_stricmpn((const char *)buffer, PSK_IDENTSTRING, PSK_IDENTLEN)) { loaded = R_LoadPSK(mod, buffer, bufferLen, name); } #endif ri.FS_FreeFile(buffer); } if (loaded) { // make sure the VBO glState entries are save R_BindNullVBO(); R_BindNullIBO(); return mod->index; } } for (lod = MD3_MAX_LODS - 1; lod >= 0; lod--) { char filename[1024]; buffer = NULL; strcpy(filename, name); if (lod != 0) { char namebuf[80]; if (strrchr(filename, '.')) { *strrchr(filename, '.') = 0; } sprintf(namebuf, "_%d.md3", lod); strcat(filename, namebuf); } filename[strlen(filename) - 1] = 'c'; // try MDC first if (ri.FS_FOpenFileRead(filename, NULL, qfalse)) { ri.FS_ReadFile(filename, (void **)&buffer); } if (!buffer) { filename[strlen(filename) - 1] = '3'; // try MD3 second if (ri.FS_FOpenFileRead(filename, NULL, qfalse)) { ri.FS_ReadFile(filename, (void **)&buffer); } if (!buffer) { continue; } } loadmodel = mod; ident = LittleLong(*(unsigned *)buffer); if (ident != MD3_IDENT && ident != MDC_IDENT) { Ren_Warning("RE_RegisterModel: unknown fileid for %s\n", name); ri.FS_FreeFile(buffer); goto fail; } if (ident == MD3_IDENT) { loaded = R_LoadMD3(mod, lod, buffer, bufferLen, name); } else if (ident == MDC_IDENT) { loaded = R_LoadMDC(mod, lod, buffer, bufferLen, name); } ri.FS_FreeFile(buffer); if (!loaded) { if (lod == 0) { goto fail; } else { break; } } else { // make sure the VBO glState entries are save R_BindNullVBO(); R_BindNullIBO(); mod->numLods++; numLoaded++; // if we have a valid model and are biased // so that we won't see any higher detail ones, // stop loading them //if ( lod <= r_lodbias->integer ) { //break; //} } } // make sure the VBO glState entries are save R_BindNullVBO(); R_BindNullIBO(); if (numLoaded) { // duplicate into higher lod spots that weren't // loaded, in case the user changes r_lodbias on the fly for (lod--; lod >= 0; lod--) { mod->numLods++; mod->mdv[lod] = mod->mdv[lod + 1]; } return mod->index; } #ifdef LEGACY_DEBUG else { Ren_Warning("couldn't load '%s'\n", name); } #endif fail: // we still keep the model_t around, so if the model name is asked for // again, we won't bother scanning the filesystem mod->type = MOD_BAD; // make sure the VBO glState entries are save R_BindNullVBO(); R_BindNullIBO(); return 0; }
/* ============= RE_StretchRaw FIXME: not exactly backend Stretches a raw 32 bit power of 2 bitmap image over the given screen rectangle. Used for cinematics. ============= */ void RE_StretchRaw(int x, int y, int w, int h, int cols, int rows, const byte *data, int client, qboolean dirty) { int i, j; int start; if (!tr.registered) { return; } R_IssuePendingRenderCommands(); // we definately want to sync every frame for the cinematics qglFinish(); start = 0; if (r_speeds->integer) { start = ri.Milliseconds(); } // make sure rows and cols are powers of 2 for (i = 0 ; (1 << i) < cols ; i++) { } for (j = 0 ; (1 << j) < rows ; j++) { } if ((1 << i) != cols || (1 << j) != rows) { Ren_Drop("Draw_StretchRaw: size not a power of 2: %i by %i", cols, rows); } GL_Bind(tr.scratchImage[client]); // if the scratchImage isn't in the format we want, specify it as a new texture if (cols != tr.scratchImage[client]->width || rows != tr.scratchImage[client]->height) { tr.scratchImage[client]->width = tr.scratchImage[client]->uploadWidth = cols; tr.scratchImage[client]->height = tr.scratchImage[client]->uploadHeight = rows; qglTexImage2D(GL_TEXTURE_2D, 0, 3, cols, rows, 0, GL_RGBA, GL_UNSIGNED_BYTE, data); qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); } else { if (dirty) { // otherwise, just subimage upload it so that drivers can tell we are going to be changing // it and don't try and do a texture compression qglTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, cols, rows, GL_RGBA, GL_UNSIGNED_BYTE, data); } } if (r_speeds->integer) { int end = ri.Milliseconds(); Ren_Print("qglTexSubImage2D %i, %i: %i msec\n", cols, rows, end - start); } RB_SetGL2D(); qglColor3f(tr.identityLight, tr.identityLight, tr.identityLight); qglBegin(GL_QUADS); qglTexCoord2f(0.5f / cols, 0.5f / rows); qglVertex2f(x, y); qglTexCoord2f((cols - 0.5f) / cols, 0.5f / rows); qglVertex2f(x + w, y); qglTexCoord2f((cols - 0.5f) / cols, (rows - 0.5f) / rows); qglVertex2f(x + w, y + h); qglTexCoord2f(0.5f / cols, (rows - 0.5f) / rows); qglVertex2f(x, y + h); qglEnd(); }
/* ============ FBO_Init ============ */ void FBO_Init(void) { int i; // int width, height, hdrFormat, multisample; int hdrFormat, multisample; ri.Printf(PRINT_ALL, "------- FBO_Init -------\n"); if(!glRefConfig.framebufferObject) return; tr.numFBOs = 0; GL_CheckErrors(); R_IssuePendingRenderCommands(); /* if(glRefConfig.textureNonPowerOfTwo) { width = glConfig.vidWidth; height = glConfig.vidHeight; } else { width = NextPowerOfTwo(glConfig.vidWidth); height = NextPowerOfTwo(glConfig.vidHeight); } */ hdrFormat = GL_RGBA8; if (r_hdr->integer && glRefConfig.framebufferObject && glRefConfig.textureFloat) { hdrFormat = GL_RGBA16F_ARB; } qglGetIntegerv(GL_MAX_SAMPLES_EXT, &multisample); if (r_ext_framebuffer_multisample->integer < multisample) { multisample = r_ext_framebuffer_multisample->integer; } if (multisample < 2 || !glRefConfig.framebufferBlit) multisample = 0; if (multisample != r_ext_framebuffer_multisample->integer) { ri.Cvar_SetValue("r_ext_framebuffer_multisample", (float)multisample); } // only create a render FBO if we need to resolve MSAA or do HDR // otherwise just render straight to the screen (tr.renderFbo = NULL) if (multisample && glRefConfig.framebufferMultisample) { tr.renderFbo = FBO_Create("_render", tr.renderDepthImage->width, tr.renderDepthImage->height); FBO_Bind(tr.renderFbo); FBO_CreateBuffer(tr.renderFbo, hdrFormat, 0, multisample); FBO_CreateBuffer(tr.renderFbo, GL_DEPTH_COMPONENT24_ARB, 0, multisample); R_CheckFBO(tr.renderFbo); tr.msaaResolveFbo = FBO_Create("_msaaResolve", tr.renderDepthImage->width, tr.renderDepthImage->height); FBO_Bind(tr.msaaResolveFbo); //FBO_CreateBuffer(tr.msaaResolveFbo, hdrFormat, 0, 0); FBO_AttachTextureImage(tr.renderImage, 0); //FBO_CreateBuffer(tr.msaaResolveFbo, GL_DEPTH_COMPONENT24_ARB, 0, 0); R_AttachFBOTextureDepth(tr.renderDepthImage->texnum); R_CheckFBO(tr.msaaResolveFbo); } else if (r_hdr->integer) { tr.renderFbo = FBO_Create("_render", tr.renderDepthImage->width, tr.renderDepthImage->height); FBO_Bind(tr.renderFbo); //FBO_CreateBuffer(tr.renderFbo, hdrFormat, 0, 0); FBO_AttachTextureImage(tr.renderImage, 0); //FBO_CreateBuffer(tr.renderFbo, GL_DEPTH_COMPONENT24_ARB, 0, 0); R_AttachFBOTextureDepth(tr.renderDepthImage->texnum); R_CheckFBO(tr.renderFbo); } // clear render buffer // this fixes the corrupt screen bug with r_hdr 1 on older hardware if (tr.renderFbo) { FBO_Bind(tr.renderFbo); qglClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); FBO_Bind(NULL); } if (r_drawSunRays->integer) { tr.sunRaysFbo = FBO_Create("_sunRays", tr.renderDepthImage->width, tr.renderDepthImage->height); FBO_Bind(tr.sunRaysFbo); FBO_AttachTextureImage(tr.sunRaysImage, 0); R_AttachFBOTextureDepth(tr.renderDepthImage->texnum); R_CheckFBO(tr.sunRaysFbo); } // FIXME: Don't use separate color/depth buffers for a shadow buffer if (MAX_DRAWN_PSHADOWS && tr.pshadowMaps[0]) { for( i = 0; i < MAX_DRAWN_PSHADOWS; i++) { tr.pshadowFbos[i] = FBO_Create(va("_shadowmap%d", i), tr.pshadowMaps[i]->width, tr.pshadowMaps[i]->height); FBO_Bind(tr.pshadowFbos[i]); //FBO_CreateBuffer(tr.pshadowFbos[i], GL_RGBA8, 0, 0); FBO_AttachTextureImage(tr.pshadowMaps[i], 0); FBO_CreateBuffer(tr.pshadowFbos[i], GL_DEPTH_COMPONENT24_ARB, 0, 0); //R_AttachFBOTextureDepth(tr.textureDepthImage->texnum); R_CheckFBO(tr.pshadowFbos[i]); } } if (tr.sunShadowDepthImage[0]) { for ( i = 0; i < 4; i++) { tr.sunShadowFbo[i] = FBO_Create("_sunshadowmap", tr.sunShadowDepthImage[i]->width, tr.sunShadowDepthImage[i]->height); FBO_Bind(tr.sunShadowFbo[i]); //FBO_CreateBuffer(tr.sunShadowFbo[i], GL_RGBA8, 0, 0); //FBO_AttachTextureImage(tr.sunShadowImage, 0); qglDrawBuffer(GL_NONE); qglReadBuffer(GL_NONE); //FBO_CreateBuffer(tr.sunShadowFbo, GL_DEPTH_COMPONENT24_ARB, 0, 0); R_AttachFBOTextureDepth(tr.sunShadowDepthImage[i]->texnum); R_CheckFBO(tr.sunShadowFbo[i]); } tr.screenShadowFbo = FBO_Create("_screenshadow", tr.screenShadowImage->width, tr.screenShadowImage->height); FBO_Bind(tr.screenShadowFbo); FBO_AttachTextureImage(tr.screenShadowImage, 0); R_CheckFBO(tr.screenShadowFbo); } for (i = 0; i < 2; i++) { tr.textureScratchFbo[i] = FBO_Create(va("_texturescratch%d", i), tr.textureScratchImage[i]->width, tr.textureScratchImage[i]->height); FBO_Bind(tr.textureScratchFbo[i]); //FBO_CreateBuffer(tr.textureScratchFbo[i], GL_RGBA8, 0, 0); FBO_AttachTextureImage(tr.textureScratchImage[i], 0); R_CheckFBO(tr.textureScratchFbo[i]); } { tr.calcLevelsFbo = FBO_Create("_calclevels", tr.calcLevelsImage->width, tr.calcLevelsImage->height); FBO_Bind(tr.calcLevelsFbo); //FBO_CreateBuffer(tr.calcLevelsFbo, hdrFormat, 0, 0); FBO_AttachTextureImage(tr.calcLevelsImage, 0); R_CheckFBO(tr.calcLevelsFbo); } { tr.targetLevelsFbo = FBO_Create("_targetlevels", tr.targetLevelsImage->width, tr.targetLevelsImage->height); FBO_Bind(tr.targetLevelsFbo); //FBO_CreateBuffer(tr.targetLevelsFbo, hdrFormat, 0, 0); FBO_AttachTextureImage(tr.targetLevelsImage, 0); R_CheckFBO(tr.targetLevelsFbo); } for (i = 0; i < 2; i++) { tr.quarterFbo[i] = FBO_Create(va("_quarter%d", i), tr.quarterImage[i]->width, tr.quarterImage[i]->height); FBO_Bind(tr.quarterFbo[i]); //FBO_CreateBuffer(tr.quarterFbo[i], hdrFormat, 0, 0); FBO_AttachTextureImage(tr.quarterImage[i], 0); R_CheckFBO(tr.quarterFbo[i]); } if (r_ssao->integer) { tr.hdrDepthFbo = FBO_Create("_hdrDepth", tr.hdrDepthImage->width, tr.hdrDepthImage->height); FBO_Bind(tr.hdrDepthFbo); FBO_AttachTextureImage(tr.hdrDepthImage, 0); R_CheckFBO(tr.hdrDepthFbo); tr.screenSsaoFbo = FBO_Create("_screenssao", tr.screenSsaoImage->width, tr.screenSsaoImage->height); FBO_Bind(tr.screenSsaoFbo); FBO_AttachTextureImage(tr.screenSsaoImage, 0); R_CheckFBO(tr.screenSsaoFbo); } if (tr.renderCubeImage) { tr.renderCubeFbo = FBO_Create("_renderCubeFbo", tr.renderCubeImage->width, tr.renderCubeImage->height); FBO_Bind(tr.renderCubeFbo); //FBO_AttachTextureImage(tr.renderCubeImage, 0); R_AttachFBOTexture2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB, tr.renderCubeImage->texnum, 0); glState.currentFBO->colorImage[0] = tr.renderCubeImage; FBO_CreateBuffer(tr.renderCubeFbo, GL_DEPTH_COMPONENT24_ARB, 0, 0); R_CheckFBO(tr.renderCubeFbo); } GL_CheckErrors(); FBO_Bind(NULL); }
/* ==================== RE_RegisterModel Loads in a model for the given name Zero will be returned if the model fails to load. An entry will be retained for failed models as an optimization to prevent disk rescanning if they are asked for again. ==================== */ qhandle_t RE_RegisterModel( const char *name ) { model_t *mod; qhandle_t hModel; qboolean orgNameFailed = qfalse; int orgLoader = -1; int i; char localName[ MAX_QPATH ]; const char *ext; char altName[ MAX_QPATH ]; if ( !name || !name[0] ) { ri.Printf( PRINT_ALL, "RE_RegisterModel: NULL name\n" ); return 0; } if ( strlen( name ) >= MAX_QPATH ) { ri.Printf( PRINT_ALL, "Model name exceeds MAX_QPATH\n" ); return 0; } // // search the currently loaded models // for ( hModel = 1 ; hModel < tr.numModels; hModel++ ) { mod = tr.models[hModel]; if ( !strcmp( mod->name, name ) ) { if( mod->type == MOD_BAD ) { return 0; } return hModel; } } // allocate a new model_t if ( ( mod = R_AllocModel() ) == NULL ) { ri.Printf( PRINT_WARNING, "RE_RegisterModel: R_AllocModel() failed for '%s'\n", name); return 0; } // only set the name after the model has been successfully loaded Q_strncpyz( mod->name, name, sizeof( mod->name ) ); R_IssuePendingRenderCommands(); mod->type = MOD_BAD; mod->numLods = 0; // // load the files // Q_strncpyz( localName, name, MAX_QPATH ); ext = COM_GetExtension( localName ); if( *ext ) { // Look for the correct loader and use it for( i = 0; i < numModelLoaders; i++ ) { if( !Q_stricmp( ext, modelLoaders[ i ].ext ) ) { // Load hModel = modelLoaders[ i ].ModelLoader( localName, mod ); break; } } // A loader was found if( i < numModelLoaders ) { if( !hModel ) { // Loader failed, most likely because the file isn't there; // try again without the extension orgNameFailed = qtrue; orgLoader = i; COM_StripExtension( name, localName, MAX_QPATH ); } else { // Something loaded return mod->index; } } } // Try and find a suitable match using all // the model formats supported for( i = 0; i < numModelLoaders; i++ ) { if (i == orgLoader) continue; Com_sprintf( altName, sizeof (altName), "%s.%s", localName, modelLoaders[ i ].ext ); // Load hModel = modelLoaders[ i ].ModelLoader( altName, mod ); if( hModel ) { if( orgNameFailed ) { ri.Printf( PRINT_DEVELOPER, "WARNING: %s not present, using %s instead\n", name, altName ); } break; } } return hModel; }
/** * @brief Will be called once for each RE_EndFrame */ void RE_BeginFrame() { drawBufferCommand_t *cmd; if (!tr.registered) { return; } Ren_LogComment("--- RE_BeginFrame ---\n"); glState.finishCalled = qfalse; tr.frameCount++; tr.frameSceneNum = 0; tr.viewCount = 0; // do overdraw measurement if (r_measureOverdraw->integer) { if (glConfig.stencilBits < 4) { Ren_Print("Warning: not enough stencil bits to measure overdraw: %d\n", glConfig.stencilBits); ri.Cvar_Set("r_measureOverdraw", "0"); } else if (r_shadows->integer == 2) { Ren_Print("Warning: stencil shadows and overdraw measurement are mutually exclusive\n"); ri.Cvar_Set("r_measureOverdraw", "0"); } else { R_IssuePendingRenderCommands(); glEnable(GL_STENCIL_TEST); glStencilMask(~0U); GL_ClearStencil(0U); glStencilFunc(GL_ALWAYS, 0U, ~0U); glStencilOp(GL_KEEP, GL_INCR, GL_INCR); } r_measureOverdraw->modified = qfalse; } else { // this is only reached if it was on and is now off if (r_measureOverdraw->modified) { R_IssuePendingRenderCommands(); glDisable(GL_STENCIL_TEST); } r_measureOverdraw->modified = qfalse; } // texturemode stuff if (r_textureMode->modified) { R_IssuePendingRenderCommands(); GL_TextureMode(r_textureMode->string); r_textureMode->modified = qfalse; } // gamma stuff if (r_gamma->modified) { r_gamma->modified = qfalse; R_IssuePendingRenderCommands(); R_SetColorMappings(); } // check for errors if (!r_ignoreGLErrors->integer) { int err; char s[128]; R_IssuePendingRenderCommands(); if ((err = glGetError()) != GL_NO_ERROR) { switch (err) { case GL_INVALID_ENUM: Q_strcpy(s, "GL_INVALID_ENUM"); break; case GL_INVALID_VALUE: Q_strcpy(s, "GL_INVALID_VALUE"); break; case GL_INVALID_OPERATION: Q_strcpy(s, "GL_INVALID_OPERATION"); break; case GL_STACK_OVERFLOW: Q_strcpy(s, "GL_STACK_OVERFLOW"); break; case GL_STACK_UNDERFLOW: Q_strcpy(s, "GL_STACK_UNDERFLOW"); break; case GL_OUT_OF_MEMORY: Q_strcpy(s, "GL_OUT_OF_MEMORY"); break; case GL_TABLE_TOO_LARGE: Q_strcpy(s, "GL_TABLE_TOO_LARGE"); break; case GL_INVALID_FRAMEBUFFER_OPERATION_EXT: Q_strcpy(s, "GL_INVALID_FRAMEBUFFER_OPERATION_EXT"); break; default: Com_sprintf(s, sizeof(s), "0x%X", err); break; } //Ren_Fatal( "caught OpenGL error: %s in file %s line %i", s, filename, line); Ren_Fatal("RE_BeginFrame() - glGetError() failed (%s)!\n", s); } } // draw buffer stuff cmd = (drawBufferCommand_t *)R_GetCommandBuffer(sizeof(*cmd)); if (!cmd) { return; } cmd->commandId = RC_DRAW_BUFFER; if (!Q_stricmp(r_drawBuffer->string, "GL_FRONT")) { cmd->buffer = (int)GL_FRONT; } else { cmd->buffer = (int)GL_BACK; } }
/* ============= RE_StretchRaw FIXME: not exactly backend Stretches a raw 32 bit power of 2 bitmap image over the given screen rectangle. Used for cinematics. ============= */ void RE_StretchRaw (int x, int y, int w, int h, int cols, int rows, const byte *data, int client, qboolean dirty) { int i, j; int start, end; vec4_t quadVerts[4]; vec2_t texCoords[4]; if ( !tr.registered ) { return; } R_IssuePendingRenderCommands(); // we definately want to sync every frame for the cinematics qglFinish(); start = 0; if ( r_speeds->integer ) { start = ri.Milliseconds(); } // make sure rows and cols are powers of 2 for ( i = 0 ; ( 1 << i ) < cols ; i++ ) { } for ( j = 0 ; ( 1 << j ) < rows ; j++ ) { } if ( ( 1 << i ) != cols || ( 1 << j ) != rows) { ri.Error (ERR_DROP, "Draw_StretchRaw: size not a power of 2: %i by %i", cols, rows); } RE_UploadCinematic (w, h, cols, rows, data, client, dirty); if ( r_speeds->integer ) { end = ri.Milliseconds(); ri.Printf( PRINT_ALL, "qglTexSubImage2D %i, %i: %i msec\n", cols, rows, end - start ); } // FIXME: HUGE hack if (glRefConfig.framebufferObject) { if (!tr.renderFbo || backEnd.framePostProcessed) { FBO_Bind(tr.screenScratchFbo); } else { FBO_Bind(tr.renderFbo); } } RB_SetGL2D(); VectorSet4(quadVerts[0], x, y, 0.0f, 1.0f); VectorSet4(quadVerts[1], x + w, y, 0.0f, 1.0f); VectorSet4(quadVerts[2], x + w, y + h, 0.0f, 1.0f); VectorSet4(quadVerts[3], x, y + h, 0.0f, 1.0f); VectorSet2(texCoords[0], 0.5f / cols, 0.5f / rows); VectorSet2(texCoords[1], (cols - 0.5f) / cols, 0.5f / rows); VectorSet2(texCoords[2], (cols - 0.5f) / cols, (rows - 0.5f) / rows); VectorSet2(texCoords[3], 0.5f / cols, (rows - 0.5f) / rows); GLSL_BindProgram(&tr.textureColorShader); GLSL_SetUniformMatrix16(&tr.textureColorShader, UNIFORM_MODELVIEWPROJECTIONMATRIX, glState.modelviewProjection); GLSL_SetUniformVec4(&tr.textureColorShader, UNIFORM_COLOR, colorWhite); RB_InstantQuad2(quadVerts, texCoords); }
void R_InitFBOs(void) { int i; int width, height; Ren_Developer("------- R_InitFBOs -------\n"); if (!glConfig2.framebufferObjectAvailable) { return; } R_CheckDefaultBuffer(); tr.numFBOs = 0; GL_CheckErrors(); // make sure the render thread is stopped R_IssuePendingRenderCommands(); { // forward shading if (glConfig2.textureNPOTAvailable) { width = glConfig.vidWidth; height = glConfig.vidHeight; } else { width = NearestPowerOfTwo(glConfig.vidWidth); height = NearestPowerOfTwo(glConfig.vidHeight); } // deferredRender FBO for the HDR or LDR context tr.deferredRenderFBO = R_CreateFBO("_deferredRender", width, height); R_BindFBO(tr.deferredRenderFBO); if (r_hdrRendering->integer && glConfig2.textureFloatAvailable) { R_CreateFBOColorBuffer(tr.deferredRenderFBO, GL_RGBA16F_ARB, 0); } else { R_CreateFBOColorBuffer(tr.deferredRenderFBO, GL_RGBA, 0); } R_AttachFBOTexture2D(GL_TEXTURE_2D, tr.deferredRenderFBOImage->texnum, 0); R_CreateFBODepthBuffer(tr.deferredRenderFBO, GL_DEPTH_COMPONENT24_ARB); R_AttachFBOTextureDepth(tr.depthRenderImage->texnum); R_CheckFBO(tr.deferredRenderFBO); } if (glConfig2.framebufferBlitAvailable) { if (glConfig2.textureNPOTAvailable) { width = glConfig.vidWidth; height = glConfig.vidHeight; } else { width = NearestPowerOfTwo(glConfig.vidWidth); height = NearestPowerOfTwo(glConfig.vidHeight); } tr.occlusionRenderFBO = R_CreateFBO("_occlusionRender", width, height); R_BindFBO(tr.occlusionRenderFBO); #if 0 if (glConfig2.framebufferPackedDepthStencilAvailable) { //R_CreateFBOColorBuffer(tr.occlusionRenderFBO, GL_ALPHA32F_ARB, 0); R_CreateFBOPackedDepthStencilBuffer(tr.occlusionRenderFBO, GL_DEPTH24_STENCIL8); } else { //R_CreateFBOColorBuffer(tr.occlusionRenderFBO, GL_RGBA, 0); R_CreateFBODepthBuffer(tr.occlusionRenderFBO, GL_DEPTH_COMPONENT24); } #else R_CreateFBODepthBuffer(tr.occlusionRenderFBO, GL_DEPTH_COMPONENT24); #endif R_CreateFBOColorBuffer(tr.occlusionRenderFBO, GL_RGBA, 0); R_AttachFBOTexture2D(GL_TEXTURE_2D, tr.occlusionRenderFBOImage->texnum, 0); R_CheckFBO(tr.occlusionRenderFBO); } if (r_shadows->integer >= SHADOWING_ESM16 && glConfig2.textureFloatAvailable) { // shadowMap FBOs for shadow mapping offscreen rendering for (i = 0; i < MAX_SHADOWMAPS; i++) { width = height = shadowMapResolutions[i]; tr.shadowMapFBO[i] = R_CreateFBO(va("_shadowMap%d", i), width, height); R_BindFBO(tr.shadowMapFBO[i]); if (r_shadows->integer == SHADOWING_ESM32) { R_CreateFBOColorBuffer(tr.shadowMapFBO[i], GL_ALPHA32F_ARB, 0); } else if (r_shadows->integer == SHADOWING_VSM32) { R_CreateFBOColorBuffer(tr.shadowMapFBO[i], GL_LUMINANCE_ALPHA32F_ARB, 0); } else if (r_shadows->integer == SHADOWING_EVSM32) { if (r_evsmPostProcess->integer) { R_CreateFBOColorBuffer(tr.shadowMapFBO[i], GL_ALPHA32F_ARB, 0); } else { R_CreateFBOColorBuffer(tr.shadowMapFBO[i], GL_RGBA32F_ARB, 0); } } else { R_CreateFBOColorBuffer(tr.shadowMapFBO[i], GL_RGBA16F_ARB, 0); } R_CreateFBODepthBuffer(tr.shadowMapFBO[i], GL_DEPTH_COMPONENT24_ARB); R_CheckFBO(tr.shadowMapFBO[i]); } // sun requires different resolutions for (i = 0; i < MAX_SHADOWMAPS; i++) { width = height = sunShadowMapResolutions[i]; tr.sunShadowMapFBO[i] = R_CreateFBO(va("_sunShadowMap%d", i), width, height); R_BindFBO(tr.sunShadowMapFBO[i]); if (r_shadows->integer == SHADOWING_ESM32) { R_CreateFBOColorBuffer(tr.sunShadowMapFBO[i], GL_ALPHA32F_ARB, 0); } else if (r_shadows->integer == SHADOWING_VSM32) { R_CreateFBOColorBuffer(tr.sunShadowMapFBO[i], GL_LUMINANCE_ALPHA32F_ARB, 0); } else if (r_shadows->integer == SHADOWING_EVSM32) { if (!r_evsmPostProcess->integer) { R_CreateFBOColorBuffer(tr.sunShadowMapFBO[i], GL_RGBA32F_ARB, 0); } } else { R_CreateFBOColorBuffer(tr.sunShadowMapFBO[i], GL_RGBA16F_ARB, 0); } R_CreateFBODepthBuffer(tr.sunShadowMapFBO[i], GL_DEPTH_COMPONENT24_ARB); if (r_shadows->integer == SHADOWING_EVSM32 && r_evsmPostProcess->integer) { R_AttachFBOTextureDepth(tr.sunShadowMapFBOImage[i]->texnum); /* Since we don't have a color attachment the framebuffer will be considered incomplete. Consequently, we must inform the driver that we do not wish to render to the color buffer. We do this with a call to set the draw-buffer and read-buffer to GL_NONE: */ glDrawBuffer(GL_NONE); glReadBuffer(GL_NONE); } R_CheckFBO(tr.sunShadowMapFBO[i]); } } { if (glConfig2.textureNPOTAvailable) { width = glConfig.vidWidth; height = glConfig.vidHeight; } else { width = NearestPowerOfTwo(glConfig.vidWidth); height = NearestPowerOfTwo(glConfig.vidHeight); } // portalRender FBO for portals, mirrors, water, cameras et cetera tr.portalRenderFBO = R_CreateFBO("_portalRender", width, height); R_BindFBO(tr.portalRenderFBO); if (r_hdrRendering->integer && glConfig2.textureFloatAvailable) { R_CreateFBOColorBuffer(tr.portalRenderFBO, GL_RGBA16F_ARB, 0); } else { R_CreateFBOColorBuffer(tr.portalRenderFBO, GL_RGBA, 0); } R_AttachFBOTexture2D(GL_TEXTURE_2D, tr.portalRenderImage->texnum, 0); R_CheckFBO(tr.portalRenderFBO); } { if (glConfig2.textureNPOTAvailable) { width = glConfig.vidWidth * 0.25f; height = glConfig.vidHeight * 0.25f; } else { width = NearestPowerOfTwo(glConfig.vidWidth * 0.25f); height = NearestPowerOfTwo(glConfig.vidHeight * 0.25f); } tr.downScaleFBO_quarter = R_CreateFBO("_downScale_quarter", width, height); R_BindFBO(tr.downScaleFBO_quarter); if (r_hdrRendering->integer && glConfig2.textureFloatAvailable) { R_CreateFBOColorBuffer(tr.downScaleFBO_quarter, GL_RGBA16F_ARB, 0); } else { R_CreateFBOColorBuffer(tr.downScaleFBO_quarter, GL_RGBA, 0); } R_AttachFBOTexture2D(GL_TEXTURE_2D, tr.downScaleFBOImage_quarter->texnum, 0); R_CheckFBO(tr.downScaleFBO_quarter); tr.downScaleFBO_64x64 = R_CreateFBO("_downScale_64x64", 64, 64); R_BindFBO(tr.downScaleFBO_64x64); if (r_hdrRendering->integer && glConfig2.textureFloatAvailable) { R_CreateFBOColorBuffer(tr.downScaleFBO_64x64, GL_RGBA16F_ARB, 0); } else { R_CreateFBOColorBuffer(tr.downScaleFBO_64x64, GL_RGBA, 0); } R_AttachFBOTexture2D(GL_TEXTURE_2D, tr.downScaleFBOImage_64x64->texnum, 0); R_CheckFBO(tr.downScaleFBO_64x64); #if 0 tr.downScaleFBO_16x16 = R_CreateFBO("_downScale_16x16", 16, 16); R_BindFBO(tr.downScaleFBO_16x16); if (r_hdrRendering->integer && glConfig2.textureFloatAvailable) { R_CreateFBOColorBuffer(tr.downScaleFBO_16x16, GL_RGBA16F_ARB, 0); } else { R_CreateFBOColorBuffer(tr.downScaleFBO_16x16, GL_RGBA, 0); } R_AttachFBOTexture2D(GL_TEXTURE_2D, tr.downScaleFBOImage_16x16->texnum, 0); R_CheckFBO(tr.downScaleFBO_16x16); tr.downScaleFBO_4x4 = R_CreateFBO("_downScale_4x4", 4, 4); R_BindFBO(tr.downScaleFBO_4x4); if (r_hdrRendering->integer && glConfig2.textureFloatAvailable) { R_CreateFBOColorBuffer(tr.downScaleFBO_4x4, GL_RGBA16F_ARB, 0); } else { R_CreateFBOColorBuffer(tr.downScaleFBO_4x4, GL_RGBA, 0); } R_AttachFBOTexture2D(GL_TEXTURE_2D, tr.downScaleFBOImage_4x4->texnum, 0); R_CheckFBO(tr.downScaleFBO_4x4); tr.downScaleFBO_1x1 = R_CreateFBO("_downScale_1x1", 1, 1); R_BindFBO(tr.downScaleFBO_1x1); if (r_hdrRendering->integer && glConfig2.textureFloatAvailable) { R_CreateFBOColorBuffer(tr.downScaleFBO_1x1, GL_RGBA16F_ARB, 0); } else { R_CreateFBOColorBuffer(tr.downScaleFBO_1x1, GL_RGBA, 0); } R_AttachFBOTexture2D(GL_TEXTURE_2D, tr.downScaleFBOImage_1x1->texnum, 0); R_CheckFBO(tr.downScaleFBO_1x1); #endif if (glConfig2.textureNPOTAvailable) { width = glConfig.vidWidth * 0.25f; height = glConfig.vidHeight * 0.25f; } else { width = NearestPowerOfTwo(glConfig.vidWidth * 0.25f); height = NearestPowerOfTwo(glConfig.vidHeight * 0.25f); } tr.contrastRenderFBO = R_CreateFBO("_contrastRender", width, height); R_BindFBO(tr.contrastRenderFBO); if (r_hdrRendering->integer && glConfig2.textureFloatAvailable) { R_CreateFBOColorBuffer(tr.contrastRenderFBO, GL_RGBA16F_ARB, 0); } else { R_CreateFBOColorBuffer(tr.contrastRenderFBO, GL_RGBA, 0); } R_AttachFBOTexture2D(GL_TEXTURE_2D, tr.contrastRenderFBOImage->texnum, 0); R_CheckFBO(tr.contrastRenderFBO); for (i = 0; i < 2; i++) { tr.bloomRenderFBO[i] = R_CreateFBO(va("_bloomRender%d", i), width, height); R_BindFBO(tr.bloomRenderFBO[i]); if (r_hdrRendering->integer && glConfig2.textureFloatAvailable) { R_CreateFBOColorBuffer(tr.bloomRenderFBO[i], GL_RGBA16F_ARB, 0); } else { R_CreateFBOColorBuffer(tr.bloomRenderFBO[i], GL_RGBA, 0); } R_AttachFBOTexture2D(GL_TEXTURE_2D, tr.bloomRenderFBOImage[i]->texnum, 0); R_CheckFBO(tr.bloomRenderFBO[i]); } } GL_CheckErrors(); R_BindNullFBO(); }
void GLSL_InitGPUShaders(void) { int startTime, endTime; int i; char extradefines[1024]; int attribs; int numGenShaders = 0, numLightShaders = 0, numEtcShaders = 0; ri.Printf(PRINT_ALL, "------- GLSL_InitGPUShaders -------\n"); R_IssuePendingRenderCommands(); startTime = ri.Milliseconds(); for (i = 0; i < GENERICDEF_COUNT; i++) { attribs = ATTR_POSITION | ATTR_TEXCOORD | ATTR_LIGHTCOORD | ATTR_NORMAL | ATTR_COLOR; extradefines[0] = '\0'; if (i & GENERICDEF_USE_DEFORM_VERTEXES) Q_strcat(extradefines, 1024, "#define USE_DEFORM_VERTEXES\n"); if (i & GENERICDEF_USE_TCGEN_AND_TCMOD) { Q_strcat(extradefines, 1024, "#define USE_TCGEN\n"); Q_strcat(extradefines, 1024, "#define USE_TCMOD\n"); } if (i & GENERICDEF_USE_VERTEX_ANIMATION) { Q_strcat(extradefines, 1024, "#define USE_VERTEX_ANIMATION\n"); attribs |= ATTR_POSITION2 | ATTR_NORMAL2; } if (i & GENERICDEF_USE_FOG) Q_strcat(extradefines, 1024, "#define USE_FOG\n"); if (i & GENERICDEF_USE_RGBAGEN) Q_strcat(extradefines, 1024, "#define USE_RGBAGEN\n"); if (i & GENERICDEF_USE_LIGHTMAP) Q_strcat(extradefines, 1024, "#define USE_LIGHTMAP\n"); if (r_hdr->integer && !glRefConfig.floatLightmap) Q_strcat(extradefines, 1024, "#define RGBM_LIGHTMAP\n"); if (!GLSL_InitGPUShader(&tr.genericShader[i], "generic", attribs, qtrue, extradefines, qtrue, fallbackShader_generic_vp, fallbackShader_generic_fp)) { ri.Error(ERR_FATAL, "Could not load generic shader!"); } GLSL_InitUniforms(&tr.genericShader[i]); qglUseProgramObjectARB(tr.genericShader[i].program); GLSL_SetUniformInt(&tr.genericShader[i], UNIFORM_DIFFUSEMAP, TB_DIFFUSEMAP); GLSL_SetUniformInt(&tr.genericShader[i], UNIFORM_LIGHTMAP, TB_LIGHTMAP); qglUseProgramObjectARB(0); GLSL_FinishGPUShader(&tr.genericShader[i]); numGenShaders++; } attribs = ATTR_POSITION | ATTR_TEXCOORD; if (!GLSL_InitGPUShader(&tr.textureColorShader, "texturecolor", attribs, qtrue, NULL, qfalse, fallbackShader_texturecolor_vp, fallbackShader_texturecolor_fp)) { ri.Error(ERR_FATAL, "Could not load texturecolor shader!"); } GLSL_InitUniforms(&tr.textureColorShader); qglUseProgramObjectARB(tr.textureColorShader.program); GLSL_SetUniformInt(&tr.textureColorShader, UNIFORM_TEXTUREMAP, TB_DIFFUSEMAP); qglUseProgramObjectARB(0); GLSL_FinishGPUShader(&tr.textureColorShader); numEtcShaders++; for (i = 0; i < FOGDEF_COUNT; i++) { attribs = ATTR_POSITION | ATTR_POSITION2 | ATTR_NORMAL | ATTR_NORMAL2 | ATTR_TEXCOORD; extradefines[0] = '\0'; if (i & FOGDEF_USE_DEFORM_VERTEXES) Q_strcat(extradefines, 1024, "#define USE_DEFORM_VERTEXES\n"); if (i & FOGDEF_USE_VERTEX_ANIMATION) Q_strcat(extradefines, 1024, "#define USE_VERTEX_ANIMATION\n"); if (!GLSL_InitGPUShader(&tr.fogShader[i], "fogpass", attribs, qtrue, extradefines, qtrue, fallbackShader_fogpass_vp, fallbackShader_fogpass_fp)) { ri.Error(ERR_FATAL, "Could not load fogpass shader!"); } GLSL_InitUniforms(&tr.fogShader[i]); GLSL_FinishGPUShader(&tr.fogShader[i]); numEtcShaders++; } for (i = 0; i < DLIGHTDEF_COUNT; i++) { attribs = ATTR_POSITION | ATTR_NORMAL | ATTR_TEXCOORD; extradefines[0] = '\0'; if (i & DLIGHTDEF_USE_DEFORM_VERTEXES) { Q_strcat(extradefines, 1024, "#define USE_DEFORM_VERTEXES\n"); } if (!GLSL_InitGPUShader(&tr.dlightShader[i], "dlight", attribs, qtrue, extradefines, qtrue, fallbackShader_dlight_vp, fallbackShader_dlight_fp)) { ri.Error(ERR_FATAL, "Could not load dlight shader!"); } GLSL_InitUniforms(&tr.dlightShader[i]); qglUseProgramObjectARB(tr.dlightShader[i].program); GLSL_SetUniformInt(&tr.dlightShader[i], UNIFORM_DIFFUSEMAP, TB_DIFFUSEMAP); qglUseProgramObjectARB(0); GLSL_FinishGPUShader(&tr.dlightShader[i]); numEtcShaders++; } for (i = 0; i < LIGHTDEF_COUNT; i++) { int lightType = i & LIGHTDEF_LIGHTTYPE_MASK; qboolean fastLight = !(r_normalMapping->integer || r_specularMapping->integer); // skip impossible combos if ((i & LIGHTDEF_USE_PARALLAXMAP) && !r_parallaxMapping->integer) continue; if (!lightType && (i & LIGHTDEF_USE_PARALLAXMAP)) continue; if (!lightType && (i & LIGHTDEF_USE_SHADOWMAP)) continue; attribs = ATTR_POSITION | ATTR_TEXCOORD | ATTR_COLOR | ATTR_NORMAL; extradefines[0] = '\0'; if (r_deluxeSpecular->value > 0.000001f) Q_strcat(extradefines, 1024, va("#define r_deluxeSpecular %f\n", r_deluxeSpecular->value)); if (r_specularIsMetallic->value) Q_strcat(extradefines, 1024, "#define SPECULAR_IS_METALLIC\n"); if (r_dlightMode->integer >= 2) Q_strcat(extradefines, 1024, "#define USE_SHADOWMAP\n"); if (1) Q_strcat(extradefines, 1024, "#define SWIZZLE_NORMALMAP\n"); if (r_hdr->integer && !glRefConfig.floatLightmap) Q_strcat(extradefines, 1024, "#define RGBM_LIGHTMAP\n"); if (lightType) { Q_strcat(extradefines, 1024, "#define USE_LIGHT\n"); if (fastLight) Q_strcat(extradefines, 1024, "#define USE_FAST_LIGHT\n"); switch (lightType) { case LIGHTDEF_USE_LIGHTMAP: Q_strcat(extradefines, 1024, "#define USE_LIGHTMAP\n"); if (r_deluxeMapping->integer && !fastLight) Q_strcat(extradefines, 1024, "#define USE_DELUXEMAP\n"); attribs |= ATTR_LIGHTCOORD | ATTR_LIGHTDIRECTION; break; case LIGHTDEF_USE_LIGHT_VECTOR: Q_strcat(extradefines, 1024, "#define USE_LIGHT_VECTOR\n"); break; case LIGHTDEF_USE_LIGHT_VERTEX: Q_strcat(extradefines, 1024, "#define USE_LIGHT_VERTEX\n"); attribs |= ATTR_LIGHTDIRECTION; break; default: break; } if (r_normalMapping->integer) { Q_strcat(extradefines, 1024, "#define USE_NORMALMAP\n"); if (r_normalMapping->integer == 2) Q_strcat(extradefines, 1024, "#define USE_OREN_NAYAR\n"); if (r_normalMapping->integer == 3) Q_strcat(extradefines, 1024, "#define USE_TRIACE_OREN_NAYAR\n"); #ifdef USE_VERT_TANGENT_SPACE Q_strcat(extradefines, 1024, "#define USE_VERT_TANGENT_SPACE\n"); attribs |= ATTR_TANGENT; #endif if ((i & LIGHTDEF_USE_PARALLAXMAP) && !(i & LIGHTDEF_ENTITY) && r_parallaxMapping->integer) Q_strcat(extradefines, 1024, "#define USE_PARALLAXMAP\n"); } if (r_specularMapping->integer) { Q_strcat(extradefines, 1024, "#define USE_SPECULARMAP\n"); switch (r_specularMapping->integer) { case 1: default: Q_strcat(extradefines, 1024, "#define USE_BLINN\n"); break; case 2: Q_strcat(extradefines, 1024, "#define USE_BLINN_FRESNEL\n"); break; case 3: Q_strcat(extradefines, 1024, "#define USE_MCAULEY\n"); break; case 4: Q_strcat(extradefines, 1024, "#define USE_GOTANDA\n"); break; case 5: Q_strcat(extradefines, 1024, "#define USE_LAZAROV\n"); break; } } if (r_cubeMapping->integer) Q_strcat(extradefines, 1024, "#define USE_CUBEMAP\n"); } if (i & LIGHTDEF_USE_SHADOWMAP) { Q_strcat(extradefines, 1024, "#define USE_SHADOWMAP\n"); if (r_sunlightMode->integer == 1) Q_strcat(extradefines, 1024, "#define SHADOWMAP_MODULATE\n"); else if (r_sunlightMode->integer == 2) Q_strcat(extradefines, 1024, "#define USE_PRIMARY_LIGHT\n"); } if (i & LIGHTDEF_USE_TCGEN_AND_TCMOD) { Q_strcat(extradefines, 1024, "#define USE_TCGEN\n"); Q_strcat(extradefines, 1024, "#define USE_TCMOD\n"); } if (i & LIGHTDEF_ENTITY) { Q_strcat(extradefines, 1024, "#define USE_VERTEX_ANIMATION\n#define USE_MODELMATRIX\n"); attribs |= ATTR_POSITION2 | ATTR_NORMAL2; #ifdef USE_VERT_TANGENT_SPACE if (r_normalMapping->integer) { attribs |= ATTR_TANGENT2; } #endif } if (!GLSL_InitGPUShader(&tr.lightallShader[i], "lightall", attribs, qtrue, extradefines, qtrue, fallbackShader_lightall_vp, fallbackShader_lightall_fp)) { ri.Error(ERR_FATAL, "Could not load lightall shader!"); } GLSL_InitUniforms(&tr.lightallShader[i]); qglUseProgramObjectARB(tr.lightallShader[i].program); GLSL_SetUniformInt(&tr.lightallShader[i], UNIFORM_DIFFUSEMAP, TB_DIFFUSEMAP); GLSL_SetUniformInt(&tr.lightallShader[i], UNIFORM_LIGHTMAP, TB_LIGHTMAP); GLSL_SetUniformInt(&tr.lightallShader[i], UNIFORM_NORMALMAP, TB_NORMALMAP); GLSL_SetUniformInt(&tr.lightallShader[i], UNIFORM_DELUXEMAP, TB_DELUXEMAP); GLSL_SetUniformInt(&tr.lightallShader[i], UNIFORM_SPECULARMAP, TB_SPECULARMAP); GLSL_SetUniformInt(&tr.lightallShader[i], UNIFORM_SHADOWMAP, TB_SHADOWMAP); GLSL_SetUniformInt(&tr.lightallShader[i], UNIFORM_CUBEMAP, TB_CUBEMAP); qglUseProgramObjectARB(0); GLSL_FinishGPUShader(&tr.lightallShader[i]); numLightShaders++; } attribs = ATTR_POSITION | ATTR_POSITION2 | ATTR_NORMAL | ATTR_NORMAL2 | ATTR_TEXCOORD; extradefines[0] = '\0'; if (!GLSL_InitGPUShader(&tr.shadowmapShader, "shadowfill", attribs, qtrue, extradefines, qtrue, fallbackShader_shadowfill_vp, fallbackShader_shadowfill_fp)) { ri.Error(ERR_FATAL, "Could not load shadowfill shader!"); } GLSL_InitUniforms(&tr.shadowmapShader); GLSL_FinishGPUShader(&tr.shadowmapShader); numEtcShaders++; attribs = ATTR_POSITION | ATTR_NORMAL; extradefines[0] = '\0'; Q_strcat(extradefines, 1024, "#define USE_PCF\n#define USE_DISCARD\n"); if (!GLSL_InitGPUShader(&tr.pshadowShader, "pshadow", attribs, qtrue, extradefines, qtrue, fallbackShader_pshadow_vp, fallbackShader_pshadow_fp)) { ri.Error(ERR_FATAL, "Could not load pshadow shader!"); } GLSL_InitUniforms(&tr.pshadowShader); qglUseProgramObjectARB(tr.pshadowShader.program); GLSL_SetUniformInt(&tr.pshadowShader, UNIFORM_SHADOWMAP, TB_DIFFUSEMAP); qglUseProgramObjectARB(0); GLSL_FinishGPUShader(&tr.pshadowShader); numEtcShaders++; attribs = ATTR_POSITION | ATTR_TEXCOORD; extradefines[0] = '\0'; if (!GLSL_InitGPUShader(&tr.down4xShader, "down4x", attribs, qtrue, extradefines, qtrue, fallbackShader_down4x_vp, fallbackShader_down4x_fp)) { ri.Error(ERR_FATAL, "Could not load down4x shader!"); } GLSL_InitUniforms(&tr.down4xShader); qglUseProgramObjectARB(tr.down4xShader.program); GLSL_SetUniformInt(&tr.down4xShader, UNIFORM_TEXTUREMAP, TB_DIFFUSEMAP); qglUseProgramObjectARB(0); GLSL_FinishGPUShader(&tr.down4xShader); numEtcShaders++; attribs = ATTR_POSITION | ATTR_TEXCOORD; extradefines[0] = '\0'; if (!GLSL_InitGPUShader(&tr.bokehShader, "bokeh", attribs, qtrue, extradefines, qtrue, fallbackShader_bokeh_vp, fallbackShader_bokeh_fp)) { ri.Error(ERR_FATAL, "Could not load bokeh shader!"); } GLSL_InitUniforms(&tr.bokehShader); qglUseProgramObjectARB(tr.bokehShader.program); GLSL_SetUniformInt(&tr.bokehShader, UNIFORM_TEXTUREMAP, TB_DIFFUSEMAP); qglUseProgramObjectARB(0); GLSL_FinishGPUShader(&tr.bokehShader); numEtcShaders++; attribs = ATTR_POSITION | ATTR_TEXCOORD; extradefines[0] = '\0'; if (!GLSL_InitGPUShader(&tr.tonemapShader, "tonemap", attribs, qtrue, extradefines, qtrue, fallbackShader_tonemap_vp, fallbackShader_tonemap_fp)) { ri.Error(ERR_FATAL, "Could not load tonemap shader!"); } GLSL_InitUniforms(&tr.tonemapShader); qglUseProgramObjectARB(tr.tonemapShader.program); GLSL_SetUniformInt(&tr.tonemapShader, UNIFORM_TEXTUREMAP, TB_COLORMAP); GLSL_SetUniformInt(&tr.tonemapShader, UNIFORM_LEVELSMAP, TB_LEVELSMAP); qglUseProgramObjectARB(0); GLSL_FinishGPUShader(&tr.tonemapShader); numEtcShaders++; for (i = 0; i < 2; i++) { attribs = ATTR_POSITION | ATTR_TEXCOORD; extradefines[0] = '\0'; if (!i) Q_strcat(extradefines, 1024, "#define FIRST_PASS\n"); if (!GLSL_InitGPUShader(&tr.calclevels4xShader[i], "calclevels4x", attribs, qtrue, extradefines, qtrue, fallbackShader_calclevels4x_vp, fallbackShader_calclevels4x_fp)) { ri.Error(ERR_FATAL, "Could not load calclevels4x shader!"); } GLSL_InitUniforms(&tr.calclevels4xShader[i]); qglUseProgramObjectARB(tr.calclevels4xShader[i].program); GLSL_SetUniformInt(&tr.calclevels4xShader[i], UNIFORM_TEXTUREMAP, TB_DIFFUSEMAP); qglUseProgramObjectARB(0); GLSL_FinishGPUShader(&tr.calclevels4xShader[i]); numEtcShaders++; } attribs = ATTR_POSITION | ATTR_TEXCOORD; extradefines[0] = '\0'; if (r_shadowFilter->integer >= 1) Q_strcat(extradefines, 1024, "#define USE_SHADOW_FILTER\n"); if (r_shadowFilter->integer >= 2) Q_strcat(extradefines, 1024, "#define USE_SHADOW_FILTER2\n"); if (r_shadowCascadeZFar->integer != 0) Q_strcat(extradefines, 1024, "#define USE_SHADOW_CASCADE\n"); Q_strcat(extradefines, 1024, va("#define r_shadowMapSize %f\n", r_shadowMapSize->value)); Q_strcat(extradefines, 1024, va("#define r_shadowCascadeZFar %f\n", r_shadowCascadeZFar->value)); if (!GLSL_InitGPUShader(&tr.shadowmaskShader, "shadowmask", attribs, qtrue, extradefines, qtrue, fallbackShader_shadowmask_vp, fallbackShader_shadowmask_fp)) { ri.Error(ERR_FATAL, "Could not load shadowmask shader!"); } GLSL_InitUniforms(&tr.shadowmaskShader); qglUseProgramObjectARB(tr.shadowmaskShader.program); GLSL_SetUniformInt(&tr.shadowmaskShader, UNIFORM_SCREENDEPTHMAP, TB_COLORMAP); GLSL_SetUniformInt(&tr.shadowmaskShader, UNIFORM_SHADOWMAP, TB_SHADOWMAP); GLSL_SetUniformInt(&tr.shadowmaskShader, UNIFORM_SHADOWMAP2, TB_SHADOWMAP2); GLSL_SetUniformInt(&tr.shadowmaskShader, UNIFORM_SHADOWMAP3, TB_SHADOWMAP3); GLSL_SetUniformInt(&tr.shadowmaskShader, UNIFORM_SHADOWMAP4, TB_SHADOWMAP4); qglUseProgramObjectARB(0); GLSL_FinishGPUShader(&tr.shadowmaskShader); numEtcShaders++; attribs = ATTR_POSITION | ATTR_TEXCOORD; extradefines[0] = '\0'; if (!GLSL_InitGPUShader(&tr.ssaoShader, "ssao", attribs, qtrue, extradefines, qtrue, fallbackShader_ssao_vp, fallbackShader_ssao_fp)) { ri.Error(ERR_FATAL, "Could not load ssao shader!"); } GLSL_InitUniforms(&tr.ssaoShader); qglUseProgramObjectARB(tr.ssaoShader.program); GLSL_SetUniformInt(&tr.ssaoShader, UNIFORM_SCREENDEPTHMAP, TB_COLORMAP); qglUseProgramObjectARB(0); GLSL_FinishGPUShader(&tr.ssaoShader); numEtcShaders++; for (i = 0; i < 2; i++) { attribs = ATTR_POSITION | ATTR_TEXCOORD; extradefines[0] = '\0'; if (i & 1) Q_strcat(extradefines, 1024, "#define USE_VERTICAL_BLUR\n"); else Q_strcat(extradefines, 1024, "#define USE_HORIZONTAL_BLUR\n"); if (!GLSL_InitGPUShader(&tr.depthBlurShader[i], "depthBlur", attribs, qtrue, extradefines, qtrue, fallbackShader_depthblur_vp, fallbackShader_depthblur_fp)) { ri.Error(ERR_FATAL, "Could not load depthBlur shader!"); } GLSL_InitUniforms(&tr.depthBlurShader[i]); qglUseProgramObjectARB(tr.depthBlurShader[i].program); GLSL_SetUniformInt(&tr.depthBlurShader[i], UNIFORM_SCREENIMAGEMAP, TB_COLORMAP); GLSL_SetUniformInt(&tr.depthBlurShader[i], UNIFORM_SCREENDEPTHMAP, TB_LIGHTMAP); qglUseProgramObjectARB(0); GLSL_FinishGPUShader(&tr.depthBlurShader[i]); numEtcShaders++; } #if 0 attribs = ATTR_POSITION | ATTR_TEXCOORD; extradefines[0] = '\0'; if (!GLSL_InitGPUShader(&tr.testcubeShader, "testcube", attribs, qtrue, extradefines, qtrue, NULL, NULL)) { ri.Error(ERR_FATAL, "Could not load testcube shader!"); } GLSL_InitUniforms(&tr.testcubeShader); qglUseProgramObjectARB(tr.testcubeShader.program); GLSL_SetUniformInt(&tr.testcubeShader, UNIFORM_TEXTUREMAP, TB_COLORMAP); qglUseProgramObjectARB(0); GLSL_FinishGPUShader(&tr.testcubeShader); numEtcShaders++; #endif endTime = ri.Milliseconds(); ri.Printf(PRINT_ALL, "loaded %i GLSL shaders (%i gen %i light %i etc) in %5.2f seconds\n", numGenShaders + numLightShaders + numEtcShaders, numGenShaders, numLightShaders, numEtcShaders, (endTime - startTime) / 1000.0); }
qhandle_t RE_RegisterSkin(const char *name) { qhandle_t hSkin; skin_t *skin; skinSurface_t *surf; skinModel_t *model; char *text, *text_p; char *token; char surfName[MAX_QPATH]; if (!name || !name[0]) { Ren_Print("Empty name passed to RE_RegisterSkin\n"); return 0; } if (strlen(name) >= MAX_QPATH) { Ren_Print("Skin name exceeds MAX_QPATH\n"); return 0; } // see if the skin is already loaded for (hSkin = 1; hSkin < tr.numSkins; hSkin++) { skin = tr.skins[hSkin]; if (!Q_stricmp(skin->name, name)) { if (skin->numSurfaces == 0) { return 0; // default skin } return hSkin; } } // allocate a new skin if (tr.numSkins == MAX_SKINS) { Ren_Warning("WARNING: RE_RegisterSkin( '%s' ) MAX_SKINS hit\n", name); return 0; } // - moved things around slightly to fix the problem where you restart // a map that has ai characters who had invalid skin names entered // in thier "skin" or "head" field // make sure the render thread is stopped R_IssuePendingRenderCommands(); #if 0 // If not a .skin file, load as a single shader if (strcmp(name + strlen(name) - 5, ".skin")) { skin->numSurfaces = 1; skin->surfaces[0] = ri.Hunk_Alloc(sizeof(skin->surfaces[0]), h_low); skin->surfaces[0]->shader = R_FindShader(name, SHADER_3D_DYNAMIC, qtrue); return hSkin; } #endif // load and parse the skin file ri.FS_ReadFile(name, (void **)&text); if (!text) { return 0; } tr.numSkins++; skin = (skin_t *)ri.Hunk_Alloc(sizeof(skin_t), h_low); tr.skins[hSkin] = skin; Q_strncpyz(skin->name, name, sizeof(skin->name)); skin->numSurfaces = 0; skin->numModels = 0; text_p = text; while (text_p && *text_p) { // get surface name token = CommaParse(&text_p); Q_strncpyz(surfName, token, sizeof(surfName)); if (!token[0]) { break; } // lowercase the surface name so skin compares are faster Q_strlwr(surfName); if (*text_p == ',') { text_p++; } if (!Q_stricmpn(token, "tag_", 4)) { continue; } if (!Q_stricmpn(token, "md3_", 4)) { if (skin->numModels >= MAX_PART_MODELS) { Ren_Warning("WARNING: Ignoring models in '%s', the max is %d!\n", name, MAX_PART_MODELS); break; } // this is specifying a model model = skin->models[skin->numModels] = (skinModel_t *)ri.Hunk_Alloc(sizeof(*skin->models[0]), h_low); Q_strncpyz(model->type, token, sizeof(model->type)); model->hash = Com_HashKey(model->type, sizeof(model->type)); // get the model name token = CommaParse(&text_p); Q_strncpyz(model->model, token, sizeof(model->model)); skin->numModels++; continue; } // parse the shader name token = CommaParse(&text_p); if (skin->numSurfaces >= MD3_MAX_SURFACES) { Ren_Warning("WARNING: Ignoring surfaces in '%s', the max is %d surfaces!\n", name, MD3_MAX_SURFACES); break; } surf = skin->surfaces[skin->numSurfaces] = (skinSurface_t *)ri.Hunk_Alloc(sizeof(*skin->surfaces[0]), h_low); Q_strncpyz(surf->name, surfName, sizeof(surf->name)); // FIXME: bspSurface not not have ::hash yet //surf->hash = Com_HashKey(surf->name, sizeof(surf->name)); surf->shader = R_FindShader(token, SHADER_3D_DYNAMIC, qtrue); skin->numSurfaces++; } ri.FS_FreeFile(text); // never let a skin have 0 shaders if (skin->numSurfaces == 0) { return 0; // use default skin } return hSkin; }