diff --git a/src/client/refresh/gl3/gl3_draw.c b/src/client/refresh/gl3/gl3_draw.c index e390d89c..043cc56b 100644 --- a/src/client/refresh/gl3/gl3_draw.c +++ b/src/client/refresh/gl3/gl3_draw.c @@ -249,6 +249,27 @@ GL3_Draw_TileClear(int x, int y, int w, int h, char *pic) drawTexturedRectangle(x, y, w, h, x/64.0f, y/64.0f, (x+w)/64.0f, (y+h)/64.0f); } +void +GL3_DrawFrameBufferObject(int x, int y, int w, int h, GLuint fboTexture, const float v_blend[4]) +{ + qboolean underwater = (gl3_newrefdef.rdflags & RDF_UNDERWATER) != 0; + gl3ShaderInfo_t* shader = underwater ? &gl3state.si2DpostProcessWater + : &gl3state.si2DpostProcess; + GL3_UseProgram(shader->shaderProgram); + GL3_Bind(fboTexture); + + if(underwater && shader->uniLmScalesOrTime != -1) + { + glUniform1f(shader->uniLmScalesOrTime, gl3_newrefdef.time); + } + if(shader->uniVblend != -1) + { + glUniform4fv(shader->uniVblend, 1, v_blend); + } + + drawTexturedRectangle(x, y, w, h, 0, 1, 1, 0); +} + /* * Fills a box of pixels with a single color */ diff --git a/src/client/refresh/gl3/gl3_main.c b/src/client/refresh/gl3/gl3_main.c index f99ea674..2ba131ba 100644 --- a/src/client/refresh/gl3/gl3_main.c +++ b/src/client/refresh/gl3/gl3_main.c @@ -126,6 +126,7 @@ cvar_t *gl_shadows; cvar_t *gl3_debugcontext; cvar_t *gl3_usebigvbo; cvar_t *r_fixsurfsky; +cvar_t *gl3_usefbo; // Yaw-Pitch-Roll // equivalent to R_z * R_y * R_x where R_x is the trans matrix for rotating around X axis for aroundXdeg @@ -258,6 +259,8 @@ GL3_Register(void) r_speeds = ri.Cvar_Get("r_speeds", "0", 0); gl_finish = ri.Cvar_Get("gl_finish", "0", CVAR_ARCHIVE); + gl3_usefbo = ri.Cvar_Get("gl3_usefbo", "1", CVAR_ARCHIVE); // use framebuffer object for postprocess effects (water) + #if 0 // TODO! //gl_lefthand = ri.Cvar_Get("hand", "0", CVAR_USERINFO | CVAR_ARCHIVE); //gl_farsee = ri.Cvar_Get("gl_farsee", "0", CVAR_LATCH | CVAR_ARCHIVE); @@ -601,6 +604,11 @@ GL3_Init(void) GL3_SurfInit(); + glGenFramebuffers(1, &gl3state.ppFBO); + // the rest for the FBO is done dynamically in GL3_RenderView() so it can + // take the viewsize into account (enforce that by setting invalid size) + gl3state.ppFBtexWidth = gl3state.ppFBtexHeight = -1; + R_Printf(PRINT_ALL, "\n"); return true; } @@ -623,6 +631,17 @@ GL3_Shutdown(void) GL3_SurfShutdown(); GL3_Draw_ShutdownLocal(); GL3_ShutdownShaders(); + + // free the postprocessing FBO and its renderbuffer and texture + if(gl3state.ppFBrbo != 0) + glDeleteRenderbuffers(1, &gl3state.ppFBrbo); + if(gl3state.ppFBtex != 0) + glDeleteTextures(1, &gl3state.ppFBtex); + if(gl3state.ppFBO != 0) + glDeleteFramebuffers(1, &gl3state.ppFBO); + gl3state.ppFBrbo = gl3state.ppFBtex = gl3state.ppFBO = 0; + gl3state.ppFBObound = false; + gl3state.ppFBtexWidth = gl3state.ppFBtexHeight = -1; } /* shutdown OS specific OpenGL stuff like contexts, etc. */ @@ -1302,6 +1321,8 @@ GL3_MYgluPerspective(GLdouble fovy, GLdouble aspect, GLdouble zNear, GLdouble zF return ret; } +static void GL3_Clear(void); + static void SetupGL(void) { @@ -1332,7 +1353,67 @@ SetupGL(void) } #endif // 0 - glViewport(x, y2, w, h); + + + // set up the FBO accordingly, but only if actually rendering the world + // (=> don't use FBO when rendering the playermodel in the player menu) + if (gl3_usefbo->value && (gl3_newrefdef.rdflags & RDF_NOWORLDMODEL) == 0 && gl3state.ppFBO != 0) + { + glBindFramebuffer(GL_FRAMEBUFFER, gl3state.ppFBO); + gl3state.ppFBObound = true; + if(gl3state.ppFBtex == 0) + { + gl3state.ppFBtexWidth = -1; // make sure we generate the texture storage below + glGenTextures(1, &gl3state.ppFBtex); + } + + if(gl3state.ppFBrbo == 0) + { + gl3state.ppFBtexWidth = -1; // make sure we generate the RBO storage below + glGenRenderbuffers(1, &gl3state.ppFBrbo); + } + + // even if the FBO already has a texture and RBO, the viewport size + // might have changed so they need to be regenerated with the correct sizes + if(gl3state.ppFBtexWidth != w || gl3state.ppFBtexHeight != h) + { + gl3state.ppFBtexWidth = w; + gl3state.ppFBtexHeight = h; + GL3_Bind(gl3state.ppFBtex); + // create texture for FBO with size of the viewport + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, w, h, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + GL3_Bind(0); + // attach it to currently bound FBO + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, gl3state.ppFBtex, 0); + + // also create a renderbuffer object so the FBO has a stencil- and depth-buffer + glBindRenderbuffer(GL_RENDERBUFFER, gl3state.ppFBrbo); + glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, w, h); + glBindRenderbuffer(GL_RENDERBUFFER, 0); + // attach it to the FBO + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, + GL_RENDERBUFFER, gl3state.ppFBrbo); + + GLenum fbState = glCheckFramebufferStatus(GL_FRAMEBUFFER); + if(fbState != GL_FRAMEBUFFER_COMPLETE) + { + R_Printf(PRINT_ALL, "GL3 SetupGL(): WARNING: FBO is not complete, status = 0x%x\n", fbState); + gl3state.ppFBtexWidth = -1; // to try again next frame; TODO: maybe give up? + gl3state.ppFBObound = false; + glBindFramebuffer(GL_FRAMEBUFFER, 0); + } + } + + GL3_Clear(); // clear the FBO that's bound now + + glViewport(0, 0, w, h); // this will be moved to the center later, so no x/y offset + } + else // rendering directly (not to FBO for postprocessing) + { + glViewport(x, y2, w, h); + } /* set up projection matrix (eye coordinates -> clip coordinates) */ { @@ -1637,13 +1718,23 @@ GL3_RenderFrame(refdef_t *fd) { GL3_RenderView(fd); GL3_SetLightLevel(NULL); + qboolean usedFBO = gl3state.ppFBObound; // if it was/is used this frame + if(usedFBO) + { + glBindFramebuffer(GL_FRAMEBUFFER, 0); // now render to default framebuffer + gl3state.ppFBObound = false; + } GL3_SetGL2D(); - if(v_blend[3] != 0.0f) + int x = (vid.width - gl3_newrefdef.width)/2; + int y = (vid.height - gl3_newrefdef.height)/2; + if (usedFBO) + { + // if we're actually drawing the world and using an FBO, render the FBO's texture + GL3_DrawFrameBufferObject(x, y, gl3_newrefdef.width, gl3_newrefdef.height, gl3state.ppFBtex, v_blend); + } + else if(v_blend[3] != 0.0f) { - int x = (vid.width - gl3_newrefdef.width)/2; - int y = (vid.height - gl3_newrefdef.height)/2; - GL3_Draw_Flash(v_blend, x, y, gl3_newrefdef.width, gl3_newrefdef.height); } } diff --git a/src/client/refresh/gl3/gl3_shaders.c b/src/client/refresh/gl3/gl3_shaders.c index f045b9d1..7f52b8d2 100644 --- a/src/client/refresh/gl3/gl3_shaders.c +++ b/src/client/refresh/gl3/gl3_shaders.c @@ -217,6 +217,87 @@ static const char* fragmentSrc2D = MULTILINE_STRING(#version 150\n } ); +static const char* fragmentSrc2Dpostprocess = MULTILINE_STRING(#version 150\n + in vec2 passTexCoord; + + // for UBO shared between all shaders (incl. 2D) + // TODO: not needed here, remove? + layout (std140) uniform uniCommon + { + float gamma; + float intensity; + float intensity2D; // for HUD, menu etc + + vec4 color; + }; + + uniform sampler2D tex; + uniform vec4 v_blend; + + out vec4 outColor; + + void main() + { + // no gamma or intensity here, it has been applied before + // (this is just for postprocessing) + vec4 res = texture(tex, passTexCoord); + // apply the v_blend, usually blended as a colored quad with: + // glBlendEquation(GL_FUNC_ADD); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + res.rgb = v_blend.a * v_blend.rgb + (1.0 - v_blend.a)*res.rgb; + outColor = res; + } +); + +static const char* fragmentSrc2DpostprocessWater = MULTILINE_STRING(#version 150\n + in vec2 passTexCoord; + + // for UBO shared between all shaders (incl. 2D) + // TODO: not needed here, remove? + layout (std140) uniform uniCommon + { + float gamma; + float intensity; + float intensity2D; // for HUD, menu etc + + vec4 color; + }; + + const float PI = 3.14159265358979323846; + + uniform sampler2D tex; + + uniform float time; + uniform vec4 v_blend; + + out vec4 outColor; + + void main() + { + vec2 uv = passTexCoord; + + // warping based on vkquake2 + // here uv is always between 0 and 1 so ignore all that scrWidth and gl_FragCoord stuff + //float sx = pc.scale - abs(pc.scrWidth / 2.0 - gl_FragCoord.x) * 2.0 / pc.scrWidth; + //float sy = pc.scale - abs(pc.scrHeight / 2.0 - gl_FragCoord.y) * 2.0 / pc.scrHeight; + float sx = 1.0 - abs(0.5-uv.x)*2.0; + float sy = 1.0 - abs(0.5-uv.y)*2.0; + float xShift = 2.0 * time + uv.y * PI * 10; + float yShift = 2.0 * time + uv.x * PI * 10; + vec2 distortion = vec2(sin(xShift) * sx, sin(yShift) * sy) * 0.00666; + + uv += distortion; + uv = clamp(uv, vec2(0.0, 0.0), vec2(1.0, 1.0)); + + // no gamma or intensity here, it has been applied before + // (this is just for postprocessing) + vec4 res = texture(tex, uv); + // apply the v_blend, usually blended as a colored quad with: + // glBlendEquation(GL_FUNC_ADD); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + res.rgb = v_blend.a * v_blend.rgb + (1.0 - v_blend.a)*res.rgb; + outColor = res; + } +); + // 2D color only rendering, GL3_Draw_Fill(), GL3_Draw_FadeScreen() static const char* vertexSrc2Dcolor = MULTILINE_STRING(#version 150\n @@ -827,7 +908,8 @@ initShader2D(gl3ShaderInfo_t* shaderInfo, const char* vertSrc, const char* fragS //shaderInfo->uniColor = shaderInfo->uniProjMatrix = shaderInfo->uniModelViewMatrix = -1; shaderInfo->shaderProgram = 0; - shaderInfo->uniLmScales = -1; + shaderInfo->uniLmScalesOrTime = -1; + shaderInfo->uniVblend = -1; shaders2D[0] = CompileShader(GL_VERTEX_SHADER, vertSrc, NULL); if(shaders2D[0] == 0) return false; @@ -894,6 +976,18 @@ initShader2D(gl3ShaderInfo_t* shaderInfo, const char* vertSrc, const char* fragS goto err_cleanup; } + shaderInfo->uniLmScalesOrTime = glGetUniformLocation(prog, "time"); + if(shaderInfo->uniLmScalesOrTime != -1) + { + glUniform1f(shaderInfo->uniLmScalesOrTime, 0.0f); + } + + shaderInfo->uniVblend = glGetUniformLocation(prog, "v_blend"); + if(shaderInfo->uniVblend != -1) + { + glUniform4f(shaderInfo->uniVblend, 0, 0, 0, 0); + } + return true; err_cleanup: @@ -917,7 +1011,8 @@ initShader3D(gl3ShaderInfo_t* shaderInfo, const char* vertSrc, const char* fragS } shaderInfo->shaderProgram = 0; - shaderInfo->uniLmScales = -1; + shaderInfo->uniLmScalesOrTime = -1; + shaderInfo->uniVblend = -1; shaders3D[0] = CompileShader(GL_VERTEX_SHADER, vertexCommon3D, vertSrc); if(shaders3D[0] == 0) return false; @@ -1017,7 +1112,7 @@ initShader3D(gl3ShaderInfo_t* shaderInfo, const char* vertSrc, const char* fragS } GLint lmScalesLoc = glGetUniformLocation(prog, "lmScales"); - shaderInfo->uniLmScales = lmScalesLoc; + shaderInfo->uniLmScalesOrTime = lmScalesLoc; if(lmScalesLoc != -1) { shaderInfo->lmScales[0] = HMM_Vec4(1.0f, 1.0f, 1.0f, 1.0f); @@ -1102,6 +1197,17 @@ static qboolean createShaders(void) return false; } + if(!initShader2D(&gl3state.si2DpostProcess, vertexSrc2D, fragmentSrc2Dpostprocess)) + { + R_Printf(PRINT_ALL, "WARNING: Failed to create shader program to render framebuffer object!\n"); + return false; + } + if(!initShader2D(&gl3state.si2DpostProcessWater, vertexSrc2D, fragmentSrc2DpostprocessWater)) + { + R_Printf(PRINT_ALL, "WARNING: Failed to create shader program to render framebuffer object under water!\n"); + return false; + } + const char* lightmappedFrag = (gl3_colorlight->value == 0.0f) ? fragmentSrc3DlmNoColor : fragmentSrc3Dlm; diff --git a/src/client/refresh/gl3/gl3_surf.c b/src/client/refresh/gl3/gl3_surf.c index 0ebf9bd8..360547a8 100644 --- a/src/client/refresh/gl3/gl3_surf.c +++ b/src/client/refresh/gl3/gl3_surf.c @@ -332,7 +332,7 @@ UpdateLMscales(const hmm_vec4 lmScales[MAX_LIGHTMAPS_PER_SURFACE], gl3ShaderInfo if(hasChanged) { - glUniform4fv(si->uniLmScales, MAX_LIGHTMAPS_PER_SURFACE, si->lmScales[0].Elements); + glUniform4fv(si->uniLmScalesOrTime, MAX_LIGHTMAPS_PER_SURFACE, si->lmScales[0].Elements); } } diff --git a/src/client/refresh/gl3/header/local.h b/src/client/refresh/gl3/header/local.h index 4f594ddf..39976312 100644 --- a/src/client/refresh/gl3/header/local.h +++ b/src/client/refresh/gl3/header/local.h @@ -116,7 +116,8 @@ typedef struct typedef struct { GLuint shaderProgram; - GLint uniLmScales; + GLint uniVblend; + GLint uniLmScalesOrTime; // for 3D it's lmScales, for 2D underwater PP it's time hmm_vec4 lmScales[4]; } gl3ShaderInfo_t; @@ -196,6 +197,13 @@ typedef struct int currentlightmap; // lightmap_textureIDs[currentlightmap] bound to GL_TEXTURE1 GLuint currenttmu; // GL_TEXTURE0 or GL_TEXTURE1 + // FBO for postprocess effects (like under-water-warping) + GLuint ppFBO; + GLuint ppFBtex; // ppFBO's texture for color buffer + int ppFBtexWidth, ppFBtexHeight; + GLuint ppFBrbo; // ppFBO's renderbuffer object for depth and stencil buffer + qboolean ppFBObound; // is it currently bound (rendered to)? + //float camera_separation; //enum stereo_modes stereo_mode; @@ -208,6 +216,9 @@ typedef struct // NOTE: make sure si2D is always the first shaderInfo (or adapt GL3_ShutdownShaders()) gl3ShaderInfo_t si2D; // shader for rendering 2D with textures gl3ShaderInfo_t si2Dcolor; // shader for rendering 2D with flat colors + gl3ShaderInfo_t si2DpostProcess; // shader to render postprocess FBO, when *not* underwater + gl3ShaderInfo_t si2DpostProcessWater; // shader to apply water-warp postprocess effect + gl3ShaderInfo_t si3Dlm; // a regular opaque face (e.g. from brush) with lightmap // TODO: lm-only variants for gl_lightmap 1 gl3ShaderInfo_t si3Dtrans; // transparent is always w/o lightmap @@ -393,6 +404,7 @@ extern void GL3_Draw_PicScaled(int x, int y, char *pic, float factor); extern void GL3_Draw_StretchPic(int x, int y, int w, int h, char *pic); extern void GL3_Draw_CharScaled(int x, int y, int num, float scale); extern void GL3_Draw_TileClear(int x, int y, int w, int h, char *pic); +extern void GL3_DrawFrameBufferObject(int x, int y, int w, int h, GLuint fboTexture, const float v_blend[4]); extern void GL3_Draw_Fill(int x, int y, int w, int h, int c); extern void GL3_Draw_FadeScreen(void); extern void GL3_Draw_Flash(const float color[4], float x, float y, float w, float h);