reaction/code/renderer/tr_fbo.c
2013-01-04 13:27:22 +00:00

499 lines
11 KiB
C

#include "tr_local.h"
#define MAX_FBO_COLOR_BUFFERS 8
/*
===============
Render buffer
Can be either a zbuffer, a stencil buffer or a multisampled color buffer
===============
*/
struct fboRenderBuffer_s {
GLuint id;
int internalFormat;
int width;
int height;
qboolean msaa;
};
typedef fboRenderBuffer_t fboZBuffer_t;
typedef fboRenderBuffer_t fboStencilBuffer_t;
/*
===============
Color buffer
If anti-aliased we'll have a texture, a render buffer
and two frame buffers needed for the MSAA resolve.
Otherwise, only the texture is initialized.
===============
*/
struct fboColorBuffer_s {
fboRenderBuffer_t *buf;
image_t *tex;
GLuint fboResolve[2];
qboolean dirty;
};
/*
===============
Frame buffer
Has one or more color buffers, an optional zbuffer
and an optional stencil buffer
===============
*/
struct fbo_s {
GLuint id;
int numColorBufs;
fboColorBuffer_t* color[MAX_FBO_COLOR_BUFFERS];
fboZBuffer_t* depth;
fboStencilBuffer_t* stencil;
char name[MAX_QPATH];
};
qboolean R_FBO_CheckStatus(void)
{
GLenum status = qglCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
const char* msg = "unknown error";
switch (status)
{
case GL_FRAMEBUFFER_COMPLETE_EXT:
return qtrue;
case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT:
msg = "incomplete attachment";
break;
case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_EXT:
msg = "missing attachment";
break;
//case GL_FRAMEBUFFER_INCOMPLETE_DUPLICATE_ATTACHMENT_EXT:
// msg = "duplicate attachment";
// break;
case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT:
msg = "mismatched dimensions";
break;
case GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT:
msg = "mismatched formats";
break;
case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT:
msg = "incomplete draw buffer";
break;
case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT:
msg = "incomplete read buffer";
break;
case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE_EXT:
msg = "mismatched multisample settings";
break;
case GL_FRAMEBUFFER_UNSUPPORTED_EXT:
msg = "unsupported";
break;
default:
msg = va("0x%x", status);
break;
}
ri.Error(ERR_FATAL, "Framebuffer setup error: %s\n", msg);
return qfalse;
}
void R_FBO_InitRenderBuffer(fboRenderBuffer_t* buf)
{
buf->id = 0;
qglGenRenderbuffersEXT(1, &buf->id);
qglBindRenderbufferEXT(GL_RENDERBUFFER_EXT, buf->id);
if (buf->msaa)
{
qglRenderbufferStorageMultisampleEXT(GL_RENDERBUFFER_EXT, tr.fbo.samples, buf->internalFormat, buf->width, buf->height);
}
else
{
qglRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, buf->internalFormat, buf->width, buf->height);
}
qglBindRenderbufferEXT(GL_RENDERBUFFER_EXT, 0);
}
void R_FBO_ReleaseRenderBuffer(fboRenderBuffer_t* buf)
{
if (buf->id == 0)
return;
qglDeleteRenderbuffersEXT(1, &buf->id);
buf->id = 0;
}
fboRenderBuffer_t *R_FBO_CreateRenderBuffer(int width, int height, int pixelformat, qboolean msaa)
{
fboRenderBuffer_t* ret = NULL;
if (tr.fbo.numRenderBuffers >= ARRAY_SIZE(tr.fbo.renderBuffers))
{
ri.Error(ERR_FATAL, "Too many FBO render buffers\n");
return NULL;
}
ret = ri.Hunk_Alloc(sizeof(*ret), h_low);
memset(ret, 0, sizeof(*ret));
ret->width = width;
ret->height = height;
ret->internalFormat = pixelformat;
ret->msaa = (msaa && tr.fbo.samples > 1);
R_FBO_InitRenderBuffer(ret);
tr.fbo.renderBuffers[tr.fbo.numRenderBuffers++] = ret;
return ret;
}
fboColorBuffer_t *R_FBO_CreateColorBuffer(const char* name, int width, int height, qboolean msaa, qboolean mipmap, int clamp)
{
if (tr.fbo.numColorBuffers>= ARRAY_SIZE(tr.fbo.colorBuffers))
{
ri.Error(ERR_FATAL, "Too many FBO color buffers\n");
return NULL;
}
else
{
qboolean realmsaa = msaa && tr.fbo.samples > 1;
image_t *texture = R_CreateRenderTarget(name, width, height, mipmap, clamp);
fboRenderBuffer_t *buf = realmsaa ? R_FBO_CreateRenderBuffer(width, height, texture->internalFormat, qtrue) : NULL;
fboColorBuffer_t *ret = ri.Hunk_Alloc(sizeof(*ret), h_low);
Com_Memset(ret, 0, sizeof(*ret));
ret->buf = buf;
ret->tex = texture;
ret->dirty = qfalse;
if (realmsaa)
{
qglGenFramebuffersEXT(2, ret->fboResolve);
qglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, ret->fboResolve[0]);
qglFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_RENDERBUFFER_EXT, ret->buf->id);
R_FBO_CheckStatus();
qglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, ret->fboResolve[1]);
qglFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, ret->tex->texnum, 0);
R_FBO_CheckStatus();
qglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
}
else
{
ret->fboResolve[0] = ret->fboResolve[1] = 0;
}
tr.fbo.colorBuffers[tr.fbo.numColorBuffers++] = ret;
return ret;
}
}
void R_FBO_ReleaseColorBuffer(fboColorBuffer_t* color)
{
if (color->buf)
{
qglDeleteFramebuffersEXT(2, color->fboResolve);
}
}
fboZBuffer_t *R_FBO_CreateZBuffer(int width, int height, qboolean msaa)
{
return R_FBO_CreateRenderBuffer(width, height, GL_DEPTH_COMPONENT24, msaa);
}
void R_FBO_Init(fbo_t* fbo)
{
memset(fbo, 0, sizeof(*fbo));
qglGenFramebuffersEXT(1, &fbo->id);
}
void R_FBO_Release(fbo_t* fbo)
{
if (fbo->id == 0)
return;
qglDeleteFramebuffersEXT(1, &fbo->id);
fbo->id = 0;
}
void R_FBO_AddColorBuffer(fbo_t* fbo, fboColorBuffer_t* color)
{
GLenum attach;
if (fbo->numColorBufs >= ARRAY_SIZE(fbo->color))
{
ri.Error(ERR_FATAL, "Max FBO color buffers exceeded.\n");
return;
}
fbo->color[fbo->numColorBufs++] = color;
qglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo->id);
attach = GL_COLOR_ATTACHMENT0_EXT + fbo->numColorBufs - 1;
if (color->buf)
{
qglFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, attach, GL_RENDERBUFFER_EXT, color->buf->id);
}
else
{
qglFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, attach, GL_TEXTURE_2D, color->tex->texnum, 0);
}
}
/*
===============
R_FBO_Bind
===============
*/
fbo_t* R_FBO_Bind(fbo_t* fbo)
{
fbo_t* old = glState.currentFBO;
GLuint id = 0;
if (!glRefConfig.framebufferObject)
{
glState.currentFBO = NULL;
return NULL;
}
if (old == fbo)
return old;
glState.currentFBO = fbo;
if (fbo)
id = fbo->id;
qglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, id);
if (fbo)
{
int i;
for (i=0; i<fbo->numColorBufs; ++i)
{
fboColorBuffer_t* color = fbo->color[i];
color->dirty = (color->buf != NULL);
}
if (1)
R_FBO_CheckStatus();
}
return old;
}
/*
===============
R_FBO_BindColorBuffer
===============
*/
void R_FBO_BindColorBuffer(fbo_t* fbo, int index)
{
fboColorBuffer_t* color = fbo->color[index];
if (color->dirty)
{
color->dirty = qfalse;
// resolve
if (color->buf)
{
int id;
int width = color->buf->width;
int height = color->buf->height;
qglBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, color->fboResolve[0]);
qglBindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, color->fboResolve[1]);
qglBlitFramebufferEXT(0, 0, width, height, 0, 0, width, height, GL_COLOR_BUFFER_BIT, GL_LINEAR);
if (glState.currentFBO)
id = glState.currentFBO->id;
else
id = 0;
qglBindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, id);
qglBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, id);
}
}
GL_Bind(color->tex);
}
/*
===============
R_FBO_CreateEx
===============
*/
fbo_t* R_FBO_CreateEx(const char* name, int numColorBuffers, fboColorBuffer_t** colorBuffers, fboZBuffer_t* depth, fboStencilBuffer_t* stencil)
{
fbo_t* fbo = NULL;
if (tr.fbo.numFBOs >= ARRAY_SIZE(tr.fbo.fbos))
{
ri.Error(ERR_FATAL, "Too many FBO's\n");
return NULL;
}
ri.Printf(PRINT_DEVELOPER, "Creating FBO %s: %d color/%d depth/%d stencil\n", name, numColorBuffers, depth != NULL, stencil != NULL);
fbo = ri.Hunk_Alloc(sizeof(*fbo), h_low);
R_FBO_Init(fbo);
Q_strncpyz(fbo->name, name, ARRAY_SIZE(fbo->name));
while (numColorBuffers-- > 0)
R_FBO_AddColorBuffer(fbo, *colorBuffers++);
fbo->depth = depth;
fbo->stencil = stencil;
qglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo->id);
if (fbo->depth)
qglFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, fbo->depth->id);
if (fbo->stencil)
qglFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_STENCIL_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, fbo->stencil->id);
qglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
tr.fbo.fbos[tr.fbo.numFBOs++] = fbo;
return fbo;
}
fbo_t* R_FBO_CreateSimple(const char* name, fboColorBuffer_t* color, fboZBuffer_t* depth, fboStencilBuffer_t* stencil)
{
return R_FBO_CreateEx(name, color != NULL, &color, depth, stencil);
}
/*
===============
R_FBO_CreateDefaultBuffers
===============
*/
static void R_FBO_CreateDefaultBuffers(void)
{
qboolean msaa = tr.fbo.samples > 1;
tr.fbo.full[0] = R_FBO_CreateSimple(
"main",
R_FBO_CreateColorBuffer("*framebuffer0", glConfig.vidWidth, glConfig.vidHeight, msaa, qfalse, GL_CLAMP_TO_EDGE),
R_FBO_CreateZBuffer(glConfig.vidWidth, glConfig.vidHeight, msaa),
NULL);
tr.fbo.full[1] = R_FBO_CreateSimple(
"postprocess",
R_FBO_CreateColorBuffer("*framebuffer1", glConfig.vidWidth, glConfig.vidHeight, msaa, qfalse, GL_CLAMP_TO_EDGE),
tr.fbo.full[0]->depth,
NULL);
tr.fbo.quarter[0] = R_FBO_CreateSimple(
"quarter0",
R_FBO_CreateColorBuffer("*quarterBuffer0", glConfig.vidWidth/2, glConfig.vidHeight/2, qfalse, qfalse, GL_CLAMP_TO_EDGE),
NULL,
NULL);
tr.fbo.quarter[1] = R_FBO_CreateSimple(
"quarter1",
R_FBO_CreateColorBuffer("*quarterBuffer1", glConfig.vidWidth/2, glConfig.vidHeight/2, qfalse, qfalse, GL_CLAMP_TO_EDGE),
NULL,
NULL);
tr.fbo.tiny[0] = R_FBO_CreateSimple(
"tiny0",
R_FBO_CreateColorBuffer("*tinyBuffer0", glConfig.vidWidth/4, glConfig.vidHeight/4, qfalse, qfalse, GL_CLAMP_TO_EDGE),
NULL,
NULL);
tr.fbo.tiny[1] = R_FBO_CreateSimple(
"tiny1",
R_FBO_CreateColorBuffer("*tinyBuffer1", glConfig.vidWidth/4, glConfig.vidHeight/4, qfalse, qfalse, GL_CLAMP_TO_EDGE),
NULL,
NULL);
ri.Printf(PRINT_DEVELOPER, "...created %d FBOs\n", tr.fbo.numFBOs);
}
/*
===============
R_InitFBOs
===============
*/
void R_InitFBOs(void)
{
GLint maxSamples = 0;
if (!glRefConfig.framebufferObject)
return;
ri.Printf(PRINT_ALL, "------- R_InitFBOs -------\n");
if (glRefConfig.framebufferMultisample && glRefConfig.framebufferBlit)
{
qglGetIntegerv(GL_MAX_SAMPLES_EXT, &maxSamples);
tr.fbo.samples = maxSamples < r_ext_multisample->integer ? maxSamples : r_ext_multisample->integer;
if (tr.fbo.samples < 2)
tr.fbo.samples = 0;
}
else
{
tr.fbo.samples = 0;
}
if (tr.fbo.samples > 1)
ri.Printf(PRINT_ALL, "MSAA enabled with %d samples (max %d)\n", tr.fbo.samples, maxSamples);
R_FBO_CreateDefaultBuffers();
R_FBO_Bind(tr.fbo.full[0]);
}
/*
===============
R_ShutDownFBOs
===============
*/
void R_ShutDownFBOs(void)
{
if (!glRefConfig.framebufferObject)
return;
ri.Printf(PRINT_ALL, "------- R_ShutdownFBOs -------\n");
R_FBO_Bind(NULL);
while (tr.fbo.numRenderBuffers>0)
R_FBO_ReleaseRenderBuffer(tr.fbo.renderBuffers[--tr.fbo.numRenderBuffers]);
while (tr.fbo.numColorBuffers>0)
R_FBO_ReleaseColorBuffer(tr.fbo.colorBuffers[--tr.fbo.numColorBuffers]);
while (tr.fbo.numFBOs>0)
R_FBO_Release(tr.fbo.fbos[--tr.fbo.numFBOs]);
}