It’s a work in progress, but it does what it’s supposed to do.
Multisample blits are currently inverted on NVIDIA’s drivers (95 and 100 series)
namespace {
GLenum GetValidFBODepthFormat() {
GLint internalDepthFormat = GL_DEPTH_COMPONENT24_ARB;
std::string vendor = reinterpret_cast< const char * >( glGetString(GL_VENDOR) );
if( vendor.find("ATI Technologies Inc.") != std::string::npos ) {
common->RSafePrintf("Using ATI-safe DEPTH_COMPONENT16
");
internalDepthFormat = GL_DEPTH_COMPONENT16_ARB;
}
return internalDepthFormat;
}
void R_ComputeFBODimensions( renderTarget_t &rt ) {
float supersamples = cvarSystem->GetFloat("r_postProcessSuperSamples");
supersamples = NxMath::clamp( supersamples, 16.0f, 0.025f );
common->Printf("Samples: %4.4f
", supersamples );
const int reqBufferWidth = static_cast< int >( float(appWindow.width) * supersamples );
const int reqBufferHeight = static_cast< int >( float(appWindow.height) * supersamples );
if( cvarSystem->GetBool("r_postProcessAllowNPOT") ) {
rt.width = reqBufferWidth;
rt.height = reqBufferHeight;
}
else if( cvarSystem->GetInteger("r_backBufferWidth") ) {
rt.width = cvarSystem->GetInteger("r_backBufferWidth");
rt.height = cvarSystem->GetInteger("r_backBufferHeight");
}
else {
rt.width = PowerOf2Ceil( reqBufferWidth );
rt.height = PowerOf2Ceil( reqBufferHeight );
}
// rt.width = std::min( PowerOf2Ceil(cvarSystem->GetInteger("r_rtt_maxWidth") ), rt.width );
// rt.height = std::min( PowerOf2Ceil(cvarSystem->GetInteger("r_rtt_maxHeight")), rt.height );
}
}
void R_BindFBODepth( renderTarget_t &rt, int multisamplesRequested, int colorSamplesRequested = 0 ) {
// Detach depth _renderBuffer_, if there was one
//
glFramebufferRenderbufferEXT( GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, 0 );
// Detach depth _texture_ if there was one
//
glFramebufferTexture2DEXT( GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, rt.textureTargetType, 0, 0 );
const int depthType = cvarSystem->GetInteger("r_postProcessDepthTexture") ;
// TODO: faster to use depth RENDER BUFFER?
const GLint internalDepthFormat = GetValidFBODepthFormat();
const GLenum type = GL_UNSIGNED_BYTE;
if( depthType == 1 ) {
g_rgl->BindTexture( rt.textureTargetType, rt.fb_depthTex->_handle );
CHECKGL;
g_rgl->UploadTextureData2D( rt.textureTargetType, 0, internalDepthFormat, rt.width, rt.height, 0, GL_DEPTH_COMPONENT, type, NULL );
CHECKGL;
glTexParameteri( rt.textureTargetType, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
glTexParameteri( rt.textureTargetType, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
CHECKGL;
glFramebufferTexture2DEXT( GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, rt.textureTargetType, rt.fb_depthTex->_handle, 0 );
g_rgl->BindTexture( rt.textureTargetType, 0);
CHECKGL;
}
else if( depthType == 2 ) {
// TODO: move this and color rbs into renderTarget structure
//
static GLuint depth_rb = 0;
glGenRenderbuffersEXT( 1, &depth_rb );
// initialize depth renderbuffer
glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, depth_rb);
//////////////////////////////////////////////////////////////////////////
//
if( glrimp.exts.NV_framebuffer_multisample_coverage ) {
int coverageSamplesRequested = multisamplesRequested;
glRenderbufferStorageMultisampleCoverageNV( GL_RENDERBUFFER_EXT, coverageSamplesRequested, colorSamplesRequested, internalDepthFormat, rt.width, rt.height );
CHECKGL;
GLint givenCoverageSamples = -1;
glGetRenderbufferParameterivEXT( GL_RENDERBUFFER_EXT, GL_RENDERBUFFER_COVERAGE_SAMPLES_NV, &givenCoverageSamples );
GLint givenColorSamples = -1;
glGetRenderbufferParameterivEXT( GL_RENDERBUFFER_EXT, GL_RENDERBUFFER_COLOR_SAMPLES_NV, &givenColorSamples );
CHECKGL;
if( givenCoverageSamples == multisamplesRequested ) {
common->Printf( COM_GREEN("%d multisample coverage format given
"), givenCoverageSamples );
}
else {
common->Printf( COM_YELLOW("%d multisample coverage format given, different from request
"), givenCoverageSamples );
}
if( givenColorSamples == colorSamplesRequested ) {
common->Printf( COM_GREEN("%d multisample color format given
"), givenColorSamples );
}
else {
common->Printf( COM_YELLOW("%d multisample color format given, different from request
"), givenColorSamples );
}
}
else if( glrimp.exts.EXT_framebuffer_multisample ) {
glRenderbufferStorageMultisampleEXT( GL_RENDERBUFFER_EXT, multisamplesRequested, internalDepthFormat, rt.width, rt.height );
}
else {
glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, internalDepthFormat, rt.width, rt.height );
}
//////////////////////////////////////////////////////////////////////////
// attach renderbufferto framebufferdepth buffer
glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, depth_rb);
}
common->Printf( COM_GREEN(" RenderTarget( %d, %d )
"), rt.width, rt.height);
if( depthType == 1 ) {
common->Printf( COM_GREEN(" with depth texture
") );
}
else if( depthType == 2) {
common->Printf( COM_GREEN(" with depth render buffer
") );
}
else {
common->Printf( COM_GREEN(" with no depth information
") );
}
}
void R_BindFBOColor( renderTarget_t &rt, int multisamplesRequested, int colorSamplesRequested = 0 ) {
R_ComputeFBODimensions( rt );
// Detach color _renderBuffer_, if there was one
//
glFramebufferRenderbufferEXT( GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_RENDERBUFFER_EXT, 0 );
CHECKGL;
// Detach color _texture_ if there was one
//
glFramebufferTexture2DEXT( GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, rt.textureTargetType, 0, 0 );
CHECKGL;
const int colorType = cvarSystem->GetInteger("r_postProcessColorTexture");
if( colorType == 1 ) {
common->Printf("Allocating video memory for color texture
");
g_rgl->BindTexture( rt.textureTargetType, rt.fb_colorTex->_handle );
CHECKGL;
#if 1
const GLenum internalFormat = GL_RGBA8;
const GLenum channelType = GL_UNSIGNED_BYTE;
#else
#pragma message("Using floating point FBO!")
const GLenum internalFormat = GL_RGBA32F_ARB;
const GLenum channelType = GL_FLOAT;
#endif
Renderer::GetSingleton()->UploadTextureData2D( rt.textureTargetType, 0, internalFormat, rt.width, rt.height, 0, GL_RGBA, channelType, NULL );
CHECKGL;
if( cvarSystem->GetBool("r_postProcessGenerateMipmaps") ) {
// Establish the mipmap chain for the color texture
glGenerateMipmapEXT( rt.textureTargetType );
CHECKGL;
// TODO: read about how texture biasing works in conjunction with with filtering
//
glTexParameteri( rt.textureTargetType, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR );
glTexParameteri( rt.textureTargetType, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
}
else {
/*
glTexParameteri( rt.textureTargetType, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
glTexParameteri( rt.textureTargetType, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
*/
glTexParameteri( rt.textureTargetType, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
glTexParameteri( rt.textureTargetType, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
}
CHECKGL;
g_rgl->BindTexture( rt.textureTargetType, 0 );
CHECKGL;
glFramebufferTexture2DEXT( GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, rt.textureTargetType, rt.fb_colorTex->_handle, 0 );
CHECKGL;
}
else if( colorType == 2 ) {
rt.fb_colorRB = 0;
glGenRenderbuffersEXT( 1, &rt.fb_colorRB );
// initialize color renderbuffer
glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, rt.fb_colorRB );
//////////////////////////////////////////////////////////////////////////
//
if( glrimp.exts.NV_framebuffer_multisample_coverage ) {
int coverageSamplesRequested = multisamplesRequested;
glRenderbufferStorageMultisampleCoverageNV( GL_RENDERBUFFER_EXT, coverageSamplesRequested, colorSamplesRequested, GL_RGBA, rt.width, rt.height );
CHECKGL;
GLint givenCoverageSamples = -1;
glGetRenderbufferParameterivEXT( GL_RENDERBUFFER_EXT, GL_RENDERBUFFER_COVERAGE_SAMPLES_NV, &givenCoverageSamples );
GLint givenColorSamples = -1;
glGetRenderbufferParameterivEXT( GL_RENDERBUFFER_EXT, GL_RENDERBUFFER_COLOR_SAMPLES_NV, &givenColorSamples );
CHECKGL;
if( givenCoverageSamples == multisamplesRequested ) {
common->Printf( COM_GREEN("%d multisample coverage samples given
"), givenCoverageSamples );
}
else {
common->Printf( COM_YELLOW("%d multisample coverage samples given, different from requested
"), givenCoverageSamples, coverageSamplesRequested );
}
if( givenColorSamples == colorSamplesRequested ) {
common->Printf( COM_GREEN("%d multisample color samples given
"), givenColorSamples );
}
else {
common->Printf( COM_YELLOW("%d multisample color samples given, different from requested %d
"), givenColorSamples, colorSamplesRequested );
}
}
else if( glrimp.exts.EXT_framebuffer_multisample ) {
glRenderbufferStorageMultisampleEXT( GL_RENDERBUFFER_EXT, multisamplesRequested, GL_RGBA, rt.width, rt.height );
CHECKGL;
GLint givenSamples = -1;
glGetRenderbufferParameterivEXT( GL_RENDERBUFFER_EXT, GL_RENDERBUFFER_SAMPLES_EXT, &givenSamples );
CHECKGL;
if( givenSamples == multisamplesRequested ) {
common->Printf( COM_GREEN("%d multisample color format given
"), givenSamples );
}
else {
common->Printf( COM_YELLOW("%d multisample color format given, different from request
"), givenSamples );
}
}
else {
// glRenderbufferStorageMultisampleEXT( GL_RENDERBUFFER_EXT, /*NOTE ZERO*/ 0, internalDepthFormat, rt.width, rt.height );
// Is the same thing as
//
glRenderbufferStorageEXT( GL_RENDERBUFFER_EXT, GL_RGBA, rt.width, rt.height );
CHECKGL;
//////////////////////////////////////////////////////////////////////////
}
// attach renderbuffer to framebuffer color buffer
glFramebufferRenderbufferEXT( GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_RENDERBUFFER_EXT, rt.fb_colorRB );
CHECKGL;
}
else {
// nothing
}
if( colorType == 1 ) {
common->Printf( COM_GREEN(" with color texture
") );
}
else if( colorType == 2) {
common->Printf( COM_GREEN(" with color render buffer
") );
}
else {
common->Printf( COM_GREEN(" with no color information
") );
}
}
void R_BindFBO( renderTarget_t &rt, bool regenerate, bool disableMultisample = false ) {
if( !glrimp.exts.EXT_framebuffer_object ) {
return;
}
// TODO: performance implication, should be moved into renderTarget structure
//
const int coverageSamples = glrimp.exts.EXT_framebuffer_multisample && !disableMultisample
? cvarSystem->GetInteger("r_postProcessMultisamples") : 0;
const int colorSamples = glrimp.exts.NV_framebuffer_multisample_coverage && !disableMultisample
? cvarSystem->GetInteger("r_postProcessColorSamples") : 0;
if( regenerate ) {
common->Printf("Reparameterizing render target
");
if( !rt.fbo || !rt.fbo->_handle ) {
common->Printf(" Generating FBO
");
rt.fbo = Renderer::GetSingleton()->GenFrameBufferObject();
}
CHECKGL;
if( !rt.fb_colorTex2D || !rt.fb_colorTex2D->_handle ) {
common->Printf(" Generating FBO's color texture2D
");
rt.fb_colorTex2D = Renderer::GetSingleton()->GenTexture();
}
if( !rt.fb_depthTex2D || !rt.fb_depthTex2D->_handle) {
common->Printf(" Generating FBO's depth texture2D
");
rt.fb_depthTex2D = Renderer::GetSingleton()->GenTexture();
}
if( !rt.fb_colorTexRect || !rt.fb_colorTexRect->_handle ) {
common->Printf(" Generating FBO's color textureRect
");
rt.fb_colorTexRect = Renderer::GetSingleton()->GenTexture();
}
if( !rt.fb_depthTexRect || !rt.fb_depthTexRect->_handle) {
common->Printf(" Generating FBO's depth textureRect
");
rt.fb_depthTexRect = Renderer::GetSingleton()->GenTexture();
}
CHECKGL;
if( cvarSystem->GetBool("r_postProcessUseNVRECT") ) {
rt.textureTargetType = GL_TEXTURE_RECTANGLE_NV;
rt.fb_colorTex = rt.fb_colorTexRect;
rt.fb_depthTex = rt.fb_depthTexRect;
}
else {
rt.textureTargetType = GL_TEXTURE_2D;
rt.fb_colorTex = rt.fb_colorTex2D;
rt.fb_depthTex = rt.fb_depthTex2D;
}
if( false && coverageSamples > 0 ) {
glBindFramebufferEXT( GL_DRAW_FRAMEBUFFER_EXT, rt.fbo->_handle );
CHECKGL;
printf("Using DRAW_FRAMEBUFFER (with regen)
");
}
else {
glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, rt.fbo->_handle );
CHECKGL;
}
R_BindFBOColor( rt, coverageSamples, colorSamples );
CHECKGL;
if( !disableMultisample ) {
R_BindFBODepth( rt, coverageSamples, colorSamples );
}
CHECKGL;
if( coverageSamples ) {
// common->DPrintf( COM_YELLOW(" %d multisample framebuffer requested
"), multisamples );
}
}
else {
//Set of state that can change on a framebuffer bind
// – AUX_BUFFERS, MAX_DRAW_BUFFERS, STEREO, AUX_BUFFERS, MAX_DRAW_BUFFERS, STEREO,
// SAMPLES, X_BITS, DOUBLE_BUFFER and a few others
if( false && coverageSamples > 0 ) {
glBindFramebufferEXT( GL_DRAW_FRAMEBUFFER_EXT, rt.fbo->_handle );
CHECKGL;
}
else {
glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, rt.fbo->_handle );
CHECKGL;
}
}
if( cvarSystem->GetBool("r_postProcessScissorClip") ) {
glEnable( GL_SCISSOR_TEST );
glScissor( 0, 0, appWindow.width, appWindow.height );
}
else {
glDisable( GL_SCISSOR_TEST );
}
glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, rt.fbo->_handle );
GL_CHECK_FRAMEBUFFER_STATUS(GL_FRAMEBUFFER_EXT);
const GLenum status = glCheckFramebufferStatusEXT( GL_FRAMEBUFFER_EXT );
if( status != GL_FRAMEBUFFER_COMPLETE_EXT ) {
cvarSystem->SetInteger("r_postProcessEnable", 0);
}
glBindFramebufferEXT( GL_DRAW_FRAMEBUFFER_EXT, rt.fbo->_handle );
GL_CHECK_FRAMEBUFFER_STATUS(GL_DRAW_FRAMEBUFFER_EXT);
glBindFramebufferEXT( GL_READ_FRAMEBUFFER_EXT, rt.fbo->_handle );
GL_CHECK_FRAMEBUFFER_STATUS(GL_READ_FRAMEBUFFER_EXT);
glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, 0 );
GL_CHECK_FRAMEBUFFER_STATUS(GL_FRAMEBUFFER_EXT);
glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, rt.fbo->_handle );
GL_CHECK_FRAMEBUFFER_STATUS(GL_FRAMEBUFFER_EXT);
CHECKGL;
}
void R_UnbindFBO( const renderTarget_t &rt ) {
if( !glrimp.exts.EXT_framebuffer_object || !cvarSystem->GetBool("r_postProcessEnable") ) {
return;
}
CHECKGL;
LuaManager * const svm = LuaManager::GetSingleton();
const int samples = cvarSystem->GetInteger("r_postProcessMultisamples");
const int blitType = cvarSystem->GetInteger("r_postProcessAllowFBBlit");
const int srcWidth = appWindow.width;
const int srcHeight = appWindow.height;
const bool flip = cvarSystem->GetBool("r_postProcessFlipFBBlit") ? true : false;
const float scale = cvarSystem->GetFloat("r_postProcessScaleFBBlit");
const GLenum filtering = cvarSystem->GetBool("r_postProcessFilterFBBlit") ? GL_LINEAR : GL_NEAREST;
// const GLenum mask = svm->GetGlobalInt("g_testFBO") > 0 ? GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT : GL_COLOR_BUFFER_BIT;
const GLenum mask = GL_COLOR_BUFFER_BIT;
#pragma warning( disable : 4244 )
const int dstWidth = (float)appWindow.width * scale;
const int dstHeight = (float)appWindow.height * scale;
#pragma warning( default : 4244 )
const GLuint defaultWindow = 0;
if( blitType == 2 ) {
if( !rt.fbo ) {
printf("FBO structure not allocated!
");
exit(-1);
}
glBindFramebufferEXT( GL_READ_FRAMEBUFFER_EXT, rt.fbo->_handle );
CHECKGL;
glBindFramebufferEXT( GL_DRAW_FRAMEBUFFER_EXT, defaultWindow );
CHECKGL;
GL_CHECK_FRAMEBUFFER_STATUS( GL_FRAMEBUFFER_EXT );
CHECKGL;
GL_CHECK_FRAMEBUFFER_STATUS( GL_DRAW_FRAMEBUFFER_EXT );
CHECKGL;
GL_CHECK_FRAMEBUFFER_STATUS( GL_READ_FRAMEBUFFER_EXT );
CHECKGL;
#if 0
if( false ) {
int readSamples = -1;
glBindRenderbufferEXT( GL_RENDERBUFFER_EXT, rt.fb_colorRB );
CHECKGL;
glGetRenderbufferParameterivEXT( GL_RENDERBUFFER_EXT, GL_RENDERBUFFER_SAMPLES_EXT, &readSamples );
CHECKGL;
int drawSamples = -1;
// glGetRenderbufferParameterivEXT( GL_RENDERBUFFER_EXT, GL_RENDERBUFFER_SAMPLES_EXT, &drawSamples );
// CHECKGL;
printf("readSamples: %d, drawSamples: %d
", readSamples, drawSamples );
GLint drawBuffer = -1;
glGetIntegerv( GL_DRAW_FRAMEBUFFER_BINDING_EXT, &drawBuffer );
CHECKGL;
GLint readBuffer = -1;
glGetIntegerv( GL_READ_FRAMEBUFFER_BINDING_EXT, &readBuffer );
CHECKGL;
printf("rb: %d, db: %d
", readBuffer, drawBuffer );
}
#endif
if( !cvarSystem->GetBool("r_postProcessSkipFBBlit") ) {
glBlitFramebufferEXT( 0, 0, srcWidth, srcHeight,
0, 0, dstWidth, dstHeight,
mask, filtering );
CHECKGL;
if( flip && g_renderTarget1.fbo && g_renderTarget1.fbo->_handle ){
glBindFramebufferEXT( GL_READ_FRAMEBUFFER_EXT, defaultWindow );
CHECKGL;
glBindFramebufferEXT( GL_DRAW_FRAMEBUFFER_EXT, g_renderTarget1.fbo->_handle );
CHECKGL;
glBlitFramebufferEXT( 0, srcHeight, srcWidth, 0, // reverse Y
0, 0, dstWidth, dstHeight,
mask, filtering );
glBindFramebufferEXT( GL_READ_FRAMEBUFFER_EXT, g_renderTarget1.fbo->_handle );
CHECKGL;
glBindFramebufferEXT( GL_DRAW_FRAMEBUFFER_EXT, defaultWindow );
CHECKGL;
glBlitFramebufferEXT( 0, 0, srcWidth, srcHeight,
0, 0, dstWidth, dstHeight,
mask, filtering );
CHECKGL;
}
else if( false ) { // slow software? implementation
glBindFramebufferEXT( GL_READ_FRAMEBUFFER_EXT, 0 );
CHECKGL;
glBindFramebufferEXT( GL_DRAW_FRAMEBUFFER_EXT, 0 );
CHECKGL;
glDepthFunc( GL_ALWAYS );
glPixelZoom( 1.0, -1.0 );
orthoSet( appWindow.width, appWindow.height );
glRasterPos2f( 0, dstHeight * 0.5f );
glCopyPixels( 0, 0, dstWidth, dstHeight, GL_COLOR );
glPixelZoom( 1.0, 1.0 );
glDepthFunc( GL_LEQUAL );
}
}
CHECKGL;
glBindFramebufferEXT( GL_READ_FRAMEBUFFER_EXT, defaultWindow );
CHECKGL;
glBindFramebufferEXT( GL_DRAW_FRAMEBUFFER_EXT, defaultWindow );
CHECKGL;
}
else if( blitType == 3 ) {
glBindFramebufferEXT( GL_READ_FRAMEBUFFER_EXT, rt.fbo->_handle );
CHECKGL;
glBindFramebufferEXT( GL_DRAW_FRAMEBUFFER_EXT, g_renderTarget1.fbo->_handle );
glBlitFramebufferEXT( 0, 0, srcWidth, srcHeight,
0, 0, dstWidth, dstHeight,
mask, filtering );
glBindFramebufferEXT( GL_READ_FRAMEBUFFER_EXT, g_renderTarget1.fbo->_handle );
glBindFramebufferEXT( GL_DRAW_FRAMEBUFFER_EXT, defaultWindow );
glBlitFramebufferEXT( 0, 0, srcWidth, srcHeight,
0, 0, dstWidth, dstHeight,
mask, filtering );
glBindFramebufferEXT( GL_READ_FRAMEBUFFER_EXT, defaultWindow );
}
else {
if( glrimp.exts.EXT_framebuffer_blit ) {
glBindFramebufferEXT( GL_DRAW_FRAMEBUFFER_EXT, 0 );
glBindFramebufferEXT( GL_READ_FRAMEBUFFER_EXT, 0 );
}
else {
glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, 0 );
}
CHECKGL;
}
}