/* ============ R_InitFBOs ============ */ void R_InitFBOs( void ) { int i; int width, height; ri.Printf( PRINT_DEVELOPER, "------- R_InitFBOs -------\n" ); if ( !glConfig2.framebufferObjectAvailable ) { return; } tr.numFBOs = 0; GL_CheckErrors(); // make sure the render thread is stopped R_SyncRenderThread(); if ( glConfig2.textureNPOTAvailable ) { width = glConfig.vidWidth; height = glConfig.vidHeight; } else { width = NearestPowerOfTwo( glConfig.vidWidth ); height = NearestPowerOfTwo( glConfig.vidHeight ); } 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 ( glConfig.hardwareType == GLHW_ATI_DX10 ) { R_CreateFBODepthBuffer( tr.occlusionRenderFBO, GL_DEPTH_COMPONENT16 ); } else if ( glConfig.hardwareType == GLHW_NV_DX10 ) { R_CreateFBODepthBuffer( tr.occlusionRenderFBO, GL_DEPTH_COMPONENT24 ); } else if ( glConfig2.framebufferPackedDepthStencilAvailable ) { R_CreateFBOPackedDepthStencilBuffer( tr.occlusionRenderFBO, GL_DEPTH24_STENCIL8_EXT ); } else { R_CreateFBODepthBuffer( tr.occlusionRenderFBO, GL_DEPTH_COMPONENT24 ); } 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 ] ); R_AttachFBOTexture2D( GL_TEXTURE_2D, tr.shadowMapFBOImage[ i ]->texnum, 0 ); R_CreateFBODepthBuffer( tr.shadowMapFBO[ i ], GL_DEPTH_COMPONENT24 ); 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 ] ); R_AttachFBOTexture2D( GL_TEXTURE_2D, tr.sunShadowMapFBOImage[ i ]->texnum, 0 ); R_CreateFBODepthBuffer( tr.sunShadowMapFBO[ i ], GL_DEPTH_COMPONENT24 ); 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 ); 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 ); 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 ); R_AttachFBOTexture2D( GL_TEXTURE_2D, tr.downScaleFBOImage_64x64->texnum, 0 ); R_CheckFBO( tr.downScaleFBO_64x64 ); 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 ); 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 ] ); R_AttachFBOTexture2D( GL_TEXTURE_2D, tr.bloomRenderFBOImage[ i ]->texnum, 0 ); R_CheckFBO( tr.bloomRenderFBO[ i ] ); } } GL_CheckErrors(); R_BindNullFBO(); }
/* ============ 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); qglClearColor( 1, 0, 0.5, 1 ); 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); }
/* ============ R_InitFBOs ============ */ void R_InitFBOs( void ) { int i; int width, height; ri.Printf( PRINT_DEVELOPER, "------- R_InitFBOs -------\n" ); if ( !glConfig2.framebufferObjectAvailable ) { return; } tr.numFBOs = 0; #if !defined( USE_D3D10 ) GL_CheckErrors(); #endif // make sure the render thread is stopped R_SyncRenderThread(); #if defined( USE_D3D10 ) // TODO #else if ( DS_STANDARD_ENABLED() ) { // geometricRender FBO as G-Buffer for deferred shading ri.Printf( PRINT_ALL, "Deferred Shading enabled\n" ); if ( glConfig2.textureNPOTAvailable ) { width = glConfig.vidWidth; height = glConfig.vidHeight; } else { width = NearestPowerOfTwo( glConfig.vidWidth ); height = NearestPowerOfTwo( glConfig.vidHeight ); } tr.geometricRenderFBO = R_CreateFBO( "_geometricRender", width, height ); R_BindFBO( tr.geometricRenderFBO ); #if 0 if ( glConfig2.framebufferPackedDepthStencilAvailable ) { R_AttachFBOTexturePackedDepthStencil( tr.depthRenderImage->texnum ); } else if ( glConfig.hardwareType == GLHW_ATI || glConfig.hardwareType == GLHW_ATI_DX10 ) // || glConfig.hardwareType == GLHW_NV_DX10) { R_AttachFBOTextureDepth( tr.depthRenderImage->texnum ); } else #endif { R_AttachFBOTextureDepth( tr.depthRenderImage->texnum ); } // enable all attachments as draw buffers //glDrawBuffers(4, geometricRenderTargets); R_AttachFBOTexture2D( GL_TEXTURE_2D, tr.deferredRenderFBOImage->texnum, 0 ); R_AttachFBOTexture2D( GL_TEXTURE_2D, tr.deferredDiffuseFBOImage->texnum, 1 ); R_AttachFBOTexture2D( GL_TEXTURE_2D, tr.deferredNormalFBOImage->texnum, 2 ); R_AttachFBOTexture2D( GL_TEXTURE_2D, tr.deferredSpecularFBOImage->texnum, 3 ); R_CheckFBO( tr.geometricRenderFBO ); } else { // 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 ); R_AttachFBOTexture2D( GL_TEXTURE_2D, tr.deferredRenderFBOImage->texnum, 0 ); #if 0 if ( glConfig2.framebufferPackedDepthStencilAvailable ) { R_AttachFBOTexturePackedDepthStencil( tr.depthRenderImage->texnum ); } else if ( glConfig.hardwareType == GLHW_ATI || glConfig.hardwareType == GLHW_ATI_DX10 ) // || glConfig.hardwareType == GLHW_NV_DX10) { R_AttachFBOTextureDepth( tr.depthRenderImage->texnum ); } else #endif { 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 ( glConfig.hardwareType == GLHW_ATI_DX10 ) { //R_CreateFBOColorBuffer(tr.occlusionRenderFBO, GL_ALPHA16F, 0); R_CreateFBODepthBuffer( tr.occlusionRenderFBO, GL_DEPTH_COMPONENT16 ); } else if ( glConfig.hardwareType == GLHW_NV_DX10 ) { //R_CreateFBOColorBuffer(tr.occlusionRenderFBO, GL_ALPHA32F, 0); R_CreateFBODepthBuffer( tr.occlusionRenderFBO, GL_DEPTH_COMPONENT24 ); } else if ( glConfig2.framebufferPackedDepthStencilAvailable ) { //R_CreateFBOColorBuffer(tr.occlusionRenderFBO, GL_ALPHA32F, 0); R_CreateFBOPackedDepthStencilBuffer( tr.occlusionRenderFBO, GL_DEPTH24_STENCIL8_EXT ); } else { //R_CreateFBOColorBuffer(tr.occlusionRenderFBO, GL_RGBA, 0); R_CreateFBODepthBuffer( tr.occlusionRenderFBO, GL_DEPTH_COMPONENT24 ); } 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 ( ( glConfig.driverType == GLDRV_OPENGL3 ) || ( glConfig.hardwareType == GLHW_NV_DX10 || glConfig.hardwareType == GLHW_ATI_DX10 ) ) { 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, 0 ); } } else { R_CreateFBOColorBuffer( tr.shadowMapFBO[ i ], GL_RGBA16F, 0 ); } } else { if ( r_shadows->integer == SHADOWING_ESM16 ) { R_CreateFBOColorBuffer( tr.shadowMapFBO[ i ], GL_ALPHA16F_ARB, 0 ); } else if ( r_shadows->integer == SHADOWING_VSM16 ) { R_CreateFBOColorBuffer( tr.shadowMapFBO[ i ], GL_LUMINANCE_ALPHA16F_ARB, 0 ); } else { R_CreateFBOColorBuffer( tr.shadowMapFBO[ i ], GL_RGBA16F, 0 ); } } R_CreateFBODepthBuffer( tr.shadowMapFBO[ i ], GL_DEPTH_COMPONENT24 ); 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 ( ( glConfig.driverType == GLDRV_OPENGL3 ) || ( glConfig.hardwareType == GLHW_NV_DX10 || glConfig.hardwareType == GLHW_ATI_DX10 ) ) { 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, 0 ); } } else { R_CreateFBOColorBuffer( tr.sunShadowMapFBO[ i ], GL_RGBA16F, 0 ); } } else { if ( r_shadows->integer == SHADOWING_ESM16 ) { R_CreateFBOColorBuffer( tr.sunShadowMapFBO[ i ], GL_ALPHA16F_ARB, 0 ); } else if ( r_shadows->integer == SHADOWING_VSM16 ) { R_CreateFBOColorBuffer( tr.sunShadowMapFBO[ i ], GL_LUMINANCE_ALPHA16F_ARB, 0 ); } else { R_CreateFBOColorBuffer( tr.sunShadowMapFBO[ i ], GL_RGBA16F, 0 ); } } R_CreateFBODepthBuffer( tr.sunShadowMapFBO[ i ], GL_DEPTH_COMPONENT24 ); 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 ); 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 ); 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 ); 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 ); 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 ); 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 ); 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 ); 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 ] ); R_AttachFBOTexture2D( GL_TEXTURE_2D, tr.bloomRenderFBOImage[ i ]->texnum, 0 ); R_CheckFBO( tr.bloomRenderFBO[ i ] ); } } GL_CheckErrors(); #endif // defined(USE_D3D10) R_BindNullFBO(); }
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(); }
/* ============ FBO_Init ============ */ void FBO_Init(void) { int i; int hdrFormat, multisample = 0; ri.Printf(PRINT_ALL, "------- FBO_Init -------\n"); if(!glRefConfig.framebufferObject) return; tr.numFBOs = 0; GL_CheckErrors(); R_IssuePendingRenderCommands(); hdrFormat = GL_RGBA8; if (r_hdr->integer && glRefConfig.textureFloat) hdrFormat = GL_RGBA16F_ARB; if (glRefConfig.framebufferMultisample) qglGetIntegerv(GL_MAX_SAMPLES, &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_CreateBuffer(tr.renderFbo, hdrFormat, 0, multisample); FBO_CreateBuffer(tr.renderFbo, GL_DEPTH_COMPONENT24, 0, multisample); R_CheckFBO(tr.renderFbo); tr.msaaResolveFbo = FBO_Create("_msaaResolve", tr.renderDepthImage->width, tr.renderDepthImage->height); FBO_AttachImage(tr.msaaResolveFbo, tr.renderImage, GL_COLOR_ATTACHMENT0, 0); FBO_AttachImage(tr.msaaResolveFbo, tr.renderDepthImage, GL_DEPTH_ATTACHMENT, 0); R_CheckFBO(tr.msaaResolveFbo); } else if (r_hdr->integer) { tr.renderFbo = FBO_Create("_render", tr.renderDepthImage->width, tr.renderDepthImage->height); FBO_AttachImage(tr.renderFbo, tr.renderImage, GL_COLOR_ATTACHMENT0, 0); FBO_AttachImage(tr.renderFbo, tr.renderDepthImage, GL_DEPTH_ATTACHMENT, 0); R_CheckFBO(tr.renderFbo); } // clear render buffer // this fixes the corrupt screen bug with r_hdr 1 on older hardware if (tr.renderFbo) { GL_BindFramebuffer(GL_FRAMEBUFFER, tr.renderFbo->frameBuffer); qglClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); } if (tr.screenScratchImage) { tr.screenScratchFbo = FBO_Create("screenScratch", tr.screenScratchImage->width, tr.screenScratchImage->height); FBO_AttachImage(tr.screenScratchFbo, tr.screenScratchImage, GL_COLOR_ATTACHMENT0, 0); FBO_AttachImage(tr.screenScratchFbo, tr.renderDepthImage, GL_DEPTH_ATTACHMENT, 0); R_CheckFBO(tr.screenScratchFbo); } if (tr.sunRaysImage) { tr.sunRaysFbo = FBO_Create("_sunRays", tr.renderDepthImage->width, tr.renderDepthImage->height); FBO_AttachImage(tr.sunRaysFbo, tr.sunRaysImage, GL_COLOR_ATTACHMENT0, 0); FBO_AttachImage(tr.sunRaysFbo, tr.renderDepthImage, GL_DEPTH_ATTACHMENT, 0); R_CheckFBO(tr.sunRaysFbo); } 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); // FIXME: this next line wastes 16mb with 16x512x512 sun shadow maps, skip if OpenGL 4.3+ or ARB_framebuffer_no_attachments FBO_CreateBuffer(tr.pshadowFbos[i], GL_RGBA8, 0, 0); FBO_AttachImage(tr.pshadowFbos[i], tr.pshadowMaps[i], GL_DEPTH_ATTACHMENT, 0); 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); // FIXME: this next line wastes 16mb with 4x1024x1024 sun shadow maps, skip if OpenGL 4.3+ or ARB_framebuffer_no_attachments // This at least gets sun shadows working on older GPUs (Intel) FBO_CreateBuffer(tr.sunShadowFbo[i], GL_RGBA8, 0, 0); FBO_AttachImage(tr.sunShadowFbo[i], tr.sunShadowDepthImage[i], GL_DEPTH_ATTACHMENT, 0); R_CheckFBO(tr.sunShadowFbo[i]); } } if (tr.screenShadowImage) { tr.screenShadowFbo = FBO_Create("_screenshadow", tr.screenShadowImage->width, tr.screenShadowImage->height); FBO_AttachImage(tr.screenShadowFbo, tr.screenShadowImage, GL_COLOR_ATTACHMENT0, 0); R_CheckFBO(tr.screenShadowFbo); } if (tr.textureScratchImage[0]) { for (i = 0; i < 2; i++) { tr.textureScratchFbo[i] = FBO_Create(va("_texturescratch%d", i), tr.textureScratchImage[i]->width, tr.textureScratchImage[i]->height); FBO_AttachImage(tr.textureScratchFbo[i], tr.textureScratchImage[i], GL_COLOR_ATTACHMENT0, 0); R_CheckFBO(tr.textureScratchFbo[i]); } } if (tr.calcLevelsImage) { tr.calcLevelsFbo = FBO_Create("_calclevels", tr.calcLevelsImage->width, tr.calcLevelsImage->height); FBO_AttachImage(tr.calcLevelsFbo, tr.calcLevelsImage, GL_COLOR_ATTACHMENT0, 0); R_CheckFBO(tr.calcLevelsFbo); } if (tr.targetLevelsImage) { tr.targetLevelsFbo = FBO_Create("_targetlevels", tr.targetLevelsImage->width, tr.targetLevelsImage->height); FBO_AttachImage(tr.targetLevelsFbo, tr.targetLevelsImage, GL_COLOR_ATTACHMENT0, 0); R_CheckFBO(tr.targetLevelsFbo); } if (tr.quarterImage[0]) { for (i = 0; i < 2; i++) { tr.quarterFbo[i] = FBO_Create(va("_quarter%d", i), tr.quarterImage[i]->width, tr.quarterImage[i]->height); FBO_AttachImage(tr.quarterFbo[i], tr.quarterImage[i], GL_COLOR_ATTACHMENT0, 0); R_CheckFBO(tr.quarterFbo[i]); } } if (tr.hdrDepthImage) { tr.hdrDepthFbo = FBO_Create("_hdrDepth", tr.hdrDepthImage->width, tr.hdrDepthImage->height); FBO_AttachImage(tr.hdrDepthFbo, tr.hdrDepthImage, GL_COLOR_ATTACHMENT0, 0); R_CheckFBO(tr.hdrDepthFbo); } if (tr.screenSsaoImage) { tr.screenSsaoFbo = FBO_Create("_screenssao", tr.screenSsaoImage->width, tr.screenSsaoImage->height); FBO_AttachImage(tr.screenSsaoFbo, tr.screenSsaoImage, GL_COLOR_ATTACHMENT0, 0); R_CheckFBO(tr.screenSsaoFbo); } if (tr.renderCubeImage) { tr.renderCubeFbo = FBO_Create("_renderCubeFbo", tr.renderCubeImage->width, tr.renderCubeImage->height); FBO_AttachImage(tr.renderCubeFbo, tr.renderCubeImage, GL_COLOR_ATTACHMENT0, 0); FBO_CreateBuffer(tr.renderCubeFbo, GL_DEPTH_COMPONENT24_ARB, 0, 0); R_CheckFBO(tr.renderCubeFbo); } GL_CheckErrors(); GL_BindFramebuffer(GL_FRAMEBUFFER, 0); glState.currentFBO = NULL; }