/* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. Copyright (C) 2006-2011 Robert Beckebans This file is part of XreaL source code. XreaL source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. XreaL source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with XreaL source code; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // tr_backend.c #include "tr_local.h" #include "gl_shader.h" backEndData_t *backEndData[SMP_FRAMES]; backEndState_t backEnd; void GL_Bind(image_t * image) { int texnum; if(!image) { ri.Printf(PRINT_WARNING, "GL_Bind: NULL image\n"); image = tr.defaultImage; } else { if(r_logFile->integer) { // don't just call LogComment, or we will get a call to va() every frame! GLimp_LogComment(va("--- GL_Bind( %s ) ---\n", image->name)); } } texnum = image->texnum; if(r_nobind->integer && tr.blackImage) { // performance evaluation option texnum = tr.blackImage->texnum; } if(glState.currenttextures[glState.currenttmu] != texnum) { image->frameUsed = tr.frameCount; glState.currenttextures[glState.currenttmu] = texnum; glBindTexture(image->type, texnum); } } void GL_Unbind() { GLimp_LogComment("--- GL_Unbind() ---\n"); glBindTexture(GL_TEXTURE_2D, 0); } void BindAnimatedImage(textureBundle_t * bundle) { int index; if(bundle->isVideoMap) { ri.CIN_RunCinematic(bundle->videoMapHandle); ri.CIN_UploadCinematic(bundle->videoMapHandle); return; } if(bundle->numImages <= 1) { GL_Bind(bundle->image[0]); return; } // it is necessary to do this messy calc to make sure animations line up // exactly with waveforms of the same frequency index = Q_ftol(backEnd.refdef.floatTime * bundle->imageAnimationSpeed * FUNCTABLE_SIZE); index >>= FUNCTABLE_SIZE2; if(index < 0) { index = 0; // may happen with shader time offsets } index %= bundle->numImages; GL_Bind(bundle->image[index]); } void GL_TextureFilter(image_t * image, filterType_t filterType) { if(!image) { ri.Printf(PRINT_WARNING, "GL_TextureFilter: NULL image\n"); } else { if(r_logFile->integer) { // don't just call LogComment, or we will get a call to va() every frame! GLimp_LogComment(va("--- GL_TextureFilter( %s ) ---\n", image->name)); } } if(image->filterType == filterType) return; // set filter type switch (image->filterType) { /* case FT_DEFAULT: glTexParameterf(image->type, GL_TEXTURE_MIN_FILTER, gl_filter_min); glTexParameterf(image->type, GL_TEXTURE_MAG_FILTER, gl_filter_max); // set texture anisotropy if(glConfig2.textureAnisotropyAvailable) glTexParameterf(image->type, GL_TEXTURE_MAX_ANISOTROPY_EXT, r_ext_texture_filter_anisotropic->value); break; */ case FT_LINEAR: glTexParameterf(image->type, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameterf(image->type, GL_TEXTURE_MAG_FILTER, GL_LINEAR); break; case FT_NEAREST: glTexParameterf(image->type, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameterf(image->type, GL_TEXTURE_MAG_FILTER, GL_NEAREST); break; default: break; } } void GL_BindProgram(shaderProgram_t * program) { if(!program) { GL_BindNullProgram(); return; } if(r_logFile->integer) { // don't just call LogComment, or we will get a call to va() every frame! GLimp_LogComment(va("--- GL_BindProgram( name = '%s', macros = '%s' ) ---\n", program->name, program->compileMacros)); } if(glState.currentProgram != program) { glUseProgramObjectARB(program->program); glState.currentProgram = program; } } void GL_BindNullProgram(void) { if(r_logFile->integer) { GLimp_LogComment("--- GL_BindNullProgram ---\n"); } if(glState.currentProgram) { glUseProgramObjectARB(0); glState.currentProgram = NULL; } } void GL_SelectTexture(int unit) { if(glState.currenttmu == unit) { return; } if(unit >= 0 && unit <= 31) { glActiveTextureARB(GL_TEXTURE0_ARB + unit); if(r_logFile->integer) { GLimp_LogComment(va("glActiveTextureARB( GL_TEXTURE%i_ARB )\n", unit)); } } else { ri.Error(ERR_DROP, "GL_SelectTexture: unit = %i", unit); } glState.currenttmu = unit; } void GL_BlendFunc(GLenum sfactor, GLenum dfactor) { if(glState.blendSrc != sfactor || glState.blendDst != dfactor) { glState.blendSrc = sfactor; glState.blendDst = dfactor; glBlendFunc(sfactor, dfactor); } } void GL_ClearColor(GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha) { if(glState.clearColorRed != red || glState.clearColorGreen != green || glState.clearColorBlue != blue || glState.clearColorAlpha != alpha) { glState.clearColorRed = red; glState.clearColorGreen = green; glState.clearColorBlue = blue; glState.clearColorAlpha = alpha; glClearColor(red, green, blue, alpha); } } void GL_ClearDepth(GLclampd depth) { if(glState.clearDepth != depth) { glState.clearDepth = depth; glClearDepth(depth); } } void GL_ClearStencil(GLint s) { if(glState.clearStencil != s) { glState.clearStencil = s; glClearStencil(s); } } void GL_ColorMask(GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha) { if(glState.colorMaskRed != red || glState.colorMaskGreen != green || glState.colorMaskBlue != blue || glState.colorMaskAlpha != alpha) { glState.colorMaskRed = red; glState.colorMaskGreen = green; glState.colorMaskBlue = blue; glState.colorMaskAlpha = alpha; glColorMask(red, green, blue, alpha); } } void GL_CullFace(GLenum mode) { if(glState.cullFace != mode) { glState.cullFace = mode; glCullFace(mode); } } void GL_DepthFunc(GLenum func) { if(glState.depthFunc != func) { glState.depthFunc = func; glDepthFunc(func); } } void GL_DepthMask(GLboolean flag) { if(glState.depthMask != flag) { glState.depthMask = flag; glDepthMask(flag); } } void GL_DrawBuffer(GLenum mode) { if(glState.drawBuffer != mode) { glState.drawBuffer = mode; glDrawBuffer(mode); } } void GL_FrontFace(GLenum mode) { if(glState.frontFace != mode) { glState.frontFace = mode; glFrontFace(mode); } } void GL_LoadModelViewMatrix(const matrix_t m) { #if 1 if(MatrixCompare(glState.modelViewMatrix[glState.stackIndex], m)) { return; } #endif MatrixCopy(m, glState.modelViewMatrix[glState.stackIndex]); Matrix4x4Multiply(glState.projectionMatrix[glState.stackIndex], glState.modelViewMatrix[glState.stackIndex], glState.modelViewProjectionMatrix[glState.stackIndex]); } void GL_LoadProjectionMatrix(const matrix_t m) { #if 1 if(MatrixCompare(glState.projectionMatrix[glState.stackIndex], m)) { return; } #endif MatrixCopy(m, glState.projectionMatrix[glState.stackIndex]); Matrix4x4Multiply(glState.projectionMatrix[glState.stackIndex], glState.modelViewMatrix[glState.stackIndex], glState.modelViewProjectionMatrix[glState.stackIndex]); } void GL_PushMatrix() { glState.stackIndex++; if(glState.stackIndex >= MAX_GLSTACK) { glState.stackIndex = MAX_GLSTACK - 1; ri.Error(ERR_DROP, "GL_PushMatrix: stack overflow = %i", glState.stackIndex); } } void GL_PopMatrix() { glState.stackIndex--; if(glState.stackIndex < 0) { glState.stackIndex = 0; ri.Error(ERR_DROP, "GL_PushMatrix: stack underflow"); } } void GL_PolygonMode(GLenum face, GLenum mode) { if(glState.polygonFace != face || glState.polygonMode != mode) { glState.polygonFace = face; glState.polygonMode = mode; glPolygonMode(face, mode); } } void GL_Scissor(GLint x, GLint y, GLsizei width, GLsizei height) { if(glState.scissorX != x || glState.scissorY != y || glState.scissorWidth != width || glState.scissorHeight != height) { glState.scissorX = x; glState.scissorY = y; glState.scissorWidth = width; glState.scissorHeight = height; glScissor(x, y, width, height); } } void GL_Viewport(GLint x, GLint y, GLsizei width, GLsizei height) { if(glState.viewportX != x || glState.viewportY != y || glState.viewportWidth != width || glState.viewportHeight != height) { glState.viewportX = x; glState.viewportY = y; glState.viewportWidth = width; glState.viewportHeight = height; glViewport(x, y, width, height); } } void GL_PolygonOffset(float factor, float units) { if(glState.polygonOffsetFactor != factor || glState.polygonOffsetUnits != units) { glState.polygonOffsetFactor = factor; glState.polygonOffsetUnits = units; glPolygonOffset(factor, units); } } void GL_Cull(int cullType) { if(glState.faceCulling == cullType) { return; } #if 1 glState.faceCulling = cullType; if(cullType == CT_TWO_SIDED) { glDisable(GL_CULL_FACE); } else { glEnable(GL_CULL_FACE); if(cullType == CT_BACK_SIDED) { GL_CullFace(GL_BACK); if(backEnd.viewParms.isMirror) { GL_FrontFace(GL_CW); } else { GL_FrontFace(GL_CCW); } } else { GL_CullFace(GL_FRONT); if(backEnd.viewParms.isMirror) { GL_FrontFace(GL_CW); } else { GL_FrontFace(GL_CCW); } } } #else glState.faceCulling = CT_TWO_SIDED; glDisable(GL_CULL_FACE); #endif } /* GL_State This routine is responsible for setting the most commonly changed state in Q3. */ void GL_State(uint32_t stateBits) { uint32_t diff = stateBits ^ glState.glStateBits; if(!diff) { return; } // check depthFunc bits if(diff & GLS_DEPTHFUNC_BITS) { switch (stateBits & GLS_DEPTHFUNC_BITS) { default: GL_DepthFunc(GL_LEQUAL); break; case GLS_DEPTHFUNC_LESS: GL_DepthFunc(GL_LESS); break; case GLS_DEPTHFUNC_EQUAL: GL_DepthFunc(GL_EQUAL); break; } } // check blend bits if(diff & (GLS_SRCBLEND_BITS | GLS_DSTBLEND_BITS)) { GLenum srcFactor, dstFactor; if(stateBits & (GLS_SRCBLEND_BITS | GLS_DSTBLEND_BITS)) { switch (stateBits & GLS_SRCBLEND_BITS) { case GLS_SRCBLEND_ZERO: srcFactor = GL_ZERO; break; case GLS_SRCBLEND_ONE: srcFactor = GL_ONE; break; case GLS_SRCBLEND_DST_COLOR: srcFactor = GL_DST_COLOR; break; case GLS_SRCBLEND_ONE_MINUS_DST_COLOR: srcFactor = GL_ONE_MINUS_DST_COLOR; break; case GLS_SRCBLEND_SRC_ALPHA: srcFactor = GL_SRC_ALPHA; break; case GLS_SRCBLEND_ONE_MINUS_SRC_ALPHA: srcFactor = GL_ONE_MINUS_SRC_ALPHA; break; case GLS_SRCBLEND_DST_ALPHA: srcFactor = GL_DST_ALPHA; break; case GLS_SRCBLEND_ONE_MINUS_DST_ALPHA: srcFactor = GL_ONE_MINUS_DST_ALPHA; break; case GLS_SRCBLEND_ALPHA_SATURATE: srcFactor = GL_SRC_ALPHA_SATURATE; break; default: srcFactor = GL_ONE; // to get warning to shut up ri.Error(ERR_DROP, "GL_State: invalid src blend state bits\n"); break; } switch (stateBits & GLS_DSTBLEND_BITS) { case GLS_DSTBLEND_ZERO: dstFactor = GL_ZERO; break; case GLS_DSTBLEND_ONE: dstFactor = GL_ONE; break; case GLS_DSTBLEND_SRC_COLOR: dstFactor = GL_SRC_COLOR; break; case GLS_DSTBLEND_ONE_MINUS_SRC_COLOR: dstFactor = GL_ONE_MINUS_SRC_COLOR; break; case GLS_DSTBLEND_SRC_ALPHA: dstFactor = GL_SRC_ALPHA; break; case GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA: dstFactor = GL_ONE_MINUS_SRC_ALPHA; break; case GLS_DSTBLEND_DST_ALPHA: dstFactor = GL_DST_ALPHA; break; case GLS_DSTBLEND_ONE_MINUS_DST_ALPHA: dstFactor = GL_ONE_MINUS_DST_ALPHA; break; default: dstFactor = GL_ONE; // to get warning to shut up ri.Error(ERR_DROP, "GL_State: invalid dst blend state bits\n"); break; } glEnable(GL_BLEND); GL_BlendFunc(srcFactor, dstFactor); } else { glDisable(GL_BLEND); } } // check colormask if(diff & GLS_COLORMASK_BITS) { if(stateBits & GLS_COLORMASK_BITS) { GL_ColorMask((stateBits & GLS_REDMASK_FALSE) ? GL_FALSE : GL_TRUE, (stateBits & GLS_GREENMASK_FALSE) ? GL_FALSE : GL_TRUE, (stateBits & GLS_BLUEMASK_FALSE) ? GL_FALSE : GL_TRUE, (stateBits & GLS_ALPHAMASK_FALSE) ? GL_FALSE : GL_TRUE); } else { GL_ColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); } } // check depthmask if(diff & GLS_DEPTHMASK_TRUE) { if(stateBits & GLS_DEPTHMASK_TRUE) { GL_DepthMask(GL_TRUE); } else { GL_DepthMask(GL_FALSE); } } // fill/line mode if(diff & GLS_POLYMODE_LINE) { if(stateBits & GLS_POLYMODE_LINE) { GL_PolygonMode(GL_FRONT_AND_BACK, GL_LINE); } else { GL_PolygonMode(GL_FRONT_AND_BACK, GL_FILL); } } // depthtest if(diff & GLS_DEPTHTEST_DISABLE) { if(stateBits & GLS_DEPTHTEST_DISABLE) { glDisable(GL_DEPTH_TEST); } else { glEnable(GL_DEPTH_TEST); } } // alpha test - deprecated in OpenGL 3.0 #if 0 if(diff & GLS_ATEST_BITS) { switch (stateBits & GLS_ATEST_BITS) { case GLS_ATEST_GT_0: case GLS_ATEST_LT_128: case GLS_ATEST_GE_128: //case GLS_ATEST_GT_CUSTOM: glEnable(GL_SAMPLE_ALPHA_TO_COVERAGE_ARB); break; default: case 0: glDisable(GL_SAMPLE_ALPHA_TO_COVERAGE_ARB); break; } } #endif /* if(diff & GLS_ATEST_BITS) { switch (stateBits & GLS_ATEST_BITS) { case 0: glDisable(GL_ALPHA_TEST); break; case GLS_ATEST_GT_0: glEnable(GL_ALPHA_TEST); glAlphaFunc(GL_GREATER, 0.0f); break; case GLS_ATEST_LT_80: glEnable(GL_ALPHA_TEST); glAlphaFunc(GL_LESS, 0.5f); break; case GLS_ATEST_GE_80: glEnable(GL_ALPHA_TEST); glAlphaFunc(GL_GEQUAL, 0.5f); break; case GLS_ATEST_GT_CUSTOM: // FIXME glEnable(GL_ALPHA_TEST); glAlphaFunc(GL_GREATER, 0.5f); break; default: assert(0); break; } } */ // stenciltest if(diff & GLS_STENCILTEST_ENABLE) { if(stateBits & GLS_STENCILTEST_ENABLE) { glEnable(GL_STENCIL_TEST); } else { glDisable(GL_STENCIL_TEST); } } glState.glStateBits = stateBits; } void GL_VertexAttribsState(uint32_t stateBits) { uint32_t diff; if(glConfig2.vboVertexSkinningAvailable && tess.vboVertexSkinning) stateBits |= (ATTR_BONE_INDEXES | ATTR_BONE_WEIGHTS); GL_VertexAttribPointers(stateBits); diff = stateBits ^ glState.vertexAttribsState; if(!diff) { return; } if(diff & ATTR_POSITION) { if(stateBits & ATTR_POSITION) { if(r_logFile->integer) { GLimp_LogComment("glEnableVertexAttribArrayARB( ATTR_INDEX_POSITION )\n"); } glEnableVertexAttribArrayARB(ATTR_INDEX_POSITION); } else { if(r_logFile->integer) { GLimp_LogComment("glDisableVertexAttribArrayARB( ATTR_INDEX_POSITION )\n"); } glDisableVertexAttribArrayARB(ATTR_INDEX_POSITION); } } if(diff & ATTR_TEXCOORD) { if(stateBits & ATTR_TEXCOORD) { if(r_logFile->integer) { GLimp_LogComment("glEnableVertexAttribArrayARB( ATTR_INDEX_TEXCOORD )\n"); } glEnableVertexAttribArrayARB(ATTR_INDEX_TEXCOORD0); } else { if(r_logFile->integer) { GLimp_LogComment("glDisableVertexAttribArrayARB( ATTR_INDEX_TEXCOORD )\n"); } glDisableVertexAttribArrayARB(ATTR_INDEX_TEXCOORD0); } } if(diff & ATTR_LIGHTCOORD) { if(stateBits & ATTR_LIGHTCOORD) { if(r_logFile->integer) { GLimp_LogComment("glEnableVertexAttribArrayARB( ATTR_INDEX_LIGHTCOORD )\n"); } glEnableVertexAttribArrayARB(ATTR_INDEX_TEXCOORD1); } else { if(r_logFile->integer) { GLimp_LogComment("glDisableVertexAttribArrayARB( ATTR_INDEX_LIGHTCOORD )\n"); } glDisableVertexAttribArrayARB(ATTR_INDEX_TEXCOORD1); } } if(diff & ATTR_TANGENT) { if(stateBits & ATTR_TANGENT) { if(r_logFile->integer) { GLimp_LogComment("glEnableVertexAttribArrayARB( ATTR_INDEX_TANGENT )\n"); } glEnableVertexAttribArrayARB(ATTR_INDEX_TANGENT); } else { if(r_logFile->integer) { GLimp_LogComment("glDisableVertexAttribArrayARB( ATTR_INDEX_TANGENT )\n"); } glDisableVertexAttribArrayARB(ATTR_INDEX_TANGENT); } } if(diff & ATTR_BINORMAL) { if(stateBits & ATTR_BINORMAL) { if(r_logFile->integer) { GLimp_LogComment("glEnableVertexAttribArrayARB( ATTR_INDEX_BINORMAL )\n"); } glEnableVertexAttribArrayARB(ATTR_INDEX_BINORMAL); } else { if(r_logFile->integer) { GLimp_LogComment("glDisableVertexAttribArrayARB( ATTR_INDEX_BINORMAL )\n"); } glDisableVertexAttribArrayARB(ATTR_INDEX_BINORMAL); } } if(diff & ATTR_NORMAL) { if(stateBits & ATTR_NORMAL) { if(r_logFile->integer) { GLimp_LogComment("glEnableVertexAttribArrayARB( ATTR_INDEX_NORMAL )\n"); } glEnableVertexAttribArrayARB(ATTR_INDEX_NORMAL); } else { if(r_logFile->integer) { GLimp_LogComment("glDisableVertexAttribArrayARB( ATTR_INDEX_NORMAL )\n"); } glDisableVertexAttribArrayARB(ATTR_INDEX_NORMAL); } } if(diff & ATTR_COLOR) { if(stateBits & ATTR_COLOR) { if(r_logFile->integer) { GLimp_LogComment("glEnableVertexAttribArrayARB( ATTR_INDEX_COLOR )\n"); } glEnableVertexAttribArrayARB(ATTR_INDEX_COLOR); } else { if(r_logFile->integer) { GLimp_LogComment("glDisableVertexAttribArrayARB( ATTR_INDEX_COLOR )\n"); } glDisableVertexAttribArrayARB(ATTR_INDEX_COLOR); } } #if !defined(COMPAT_Q3A) && !defined(COMPAT_ET) if(diff & ATTR_PAINTCOLOR) { if(stateBits & ATTR_PAINTCOLOR) { if(r_logFile->integer) { GLimp_LogComment("glEnableVertexAttribArrayARB( ATTR_INDEX_PAINTCOLOR )\n"); } glEnableVertexAttribArrayARB(ATTR_INDEX_PAINTCOLOR); } else { if(r_logFile->integer) { GLimp_LogComment("glDisableVertexAttribArrayARB( ATTR_INDEX_PAINTCOLOR )\n"); } glDisableVertexAttribArrayARB(ATTR_INDEX_PAINTCOLOR); } } if(diff & ATTR_LIGHTDIRECTION) { if(stateBits & ATTR_LIGHTDIRECTION) { if(r_logFile->integer) { GLimp_LogComment("glEnableVertexAttribArrayARB( ATTR_INDEX_LIGHTDIRECTION )\n"); } glEnableVertexAttribArrayARB(ATTR_INDEX_LIGHTDIRECTION); } else { if(r_logFile->integer) { GLimp_LogComment("glDisableVertexAttribArrayARB( ATTR_INDEX_LIGHTDIRECTION )\n"); } glDisableVertexAttribArrayARB(ATTR_INDEX_LIGHTDIRECTION); } } #endif if(diff & ATTR_BONE_INDEXES) { if(stateBits & ATTR_BONE_INDEXES) { if(r_logFile->integer) { GLimp_LogComment("glEnableVertexAttribArrayARB( ATTR_INDEX_BONE_INDEXES )\n"); } glEnableVertexAttribArrayARB(ATTR_INDEX_BONE_INDEXES); } else { if(r_logFile->integer) { GLimp_LogComment("glDisableVertexAttribArrayARB( ATTR_INDEX_BONE_INDEXES )\n"); } glDisableVertexAttribArrayARB(ATTR_INDEX_BONE_INDEXES); } } if(diff & ATTR_BONE_WEIGHTS) { if(stateBits & ATTR_BONE_WEIGHTS) { if(r_logFile->integer) { GLimp_LogComment("glEnableVertexAttribArrayARB( ATTR_INDEX_BONE_WEIGHTS )\n"); } glEnableVertexAttribArrayARB(ATTR_INDEX_BONE_WEIGHTS); } else { if(r_logFile->integer) { GLimp_LogComment("glDisableVertexAttribArrayARB( ATTR_INDEX_BONE_WEIGHTS )\n"); } glDisableVertexAttribArrayARB(ATTR_INDEX_BONE_WEIGHTS); } } if(diff & ATTR_POSITION2) { if(stateBits & ATTR_POSITION2) { if(r_logFile->integer) { GLimp_LogComment("glEnableVertexAttribArrayARB( ATTR_INDEX_POSITION2 )\n"); } glEnableVertexAttribArrayARB(ATTR_INDEX_POSITION2); } else { if(r_logFile->integer) { GLimp_LogComment("glDisableVertexAttribArrayARB( ATTR_INDEX_POSITION2 )\n"); } glDisableVertexAttribArrayARB(ATTR_INDEX_POSITION2); } } if(diff & ATTR_TANGENT2) { if(stateBits & ATTR_TANGENT2) { if(r_logFile->integer) { GLimp_LogComment("glEnableVertexAttribArrayARB( ATTR_INDEX_TANGENT2 )\n"); } glEnableVertexAttribArrayARB(ATTR_INDEX_TANGENT2); } else { if(r_logFile->integer) { GLimp_LogComment("glDisableVertexAttribArrayARB( ATTR_INDEX_TANGENT2 )\n"); } glDisableVertexAttribArrayARB(ATTR_INDEX_TANGENT2); } } if(diff & ATTR_BINORMAL2) { if(stateBits & ATTR_BINORMAL2) { if(r_logFile->integer) { GLimp_LogComment("glEnableVertexAttribArrayARB( ATTR_INDEX_BINORMAL2 )\n"); } glEnableVertexAttribArrayARB(ATTR_INDEX_BINORMAL2); } else { if(r_logFile->integer) { GLimp_LogComment("glDisableVertexAttribArrayARB( ATTR_INDEX_BINORMAL2 )\n"); } glDisableVertexAttribArrayARB(ATTR_INDEX_BINORMAL2); } } if(diff & ATTR_NORMAL2) { if(stateBits & ATTR_NORMAL2) { if(r_logFile->integer) { GLimp_LogComment("glEnableVertexAttribArrayARB( ATTR_INDEX_NORMAL2 )\n"); } glEnableVertexAttribArrayARB(ATTR_INDEX_NORMAL2); } else { if(r_logFile->integer) { GLimp_LogComment("glDisableVertexAttribArrayARB( ATTR_INDEX_NORMAL2 )\n"); } glDisableVertexAttribArrayARB(ATTR_INDEX_NORMAL2); } } glState.vertexAttribsState = stateBits; } void GL_VertexAttribPointers(uint32_t attribBits) { if(!glState.currentVBO) { ri.Error(ERR_FATAL, "GL_VertexAttribPointers: no VBO bound"); return; } if(r_logFile->integer) { // don't just call LogComment, or we will get a call to va() every frame! GLimp_LogComment(va("--- GL_VertexAttribPointers( %s ) ---\n", glState.currentVBO->name)); } if(glConfig2.vboVertexSkinningAvailable && tess.vboVertexSkinning) attribBits |= (ATTR_BONE_INDEXES | ATTR_BONE_WEIGHTS); if((attribBits & ATTR_POSITION) && !(glState.vertexAttribPointersSet & ATTR_POSITION)) { if(r_logFile->integer) { GLimp_LogComment("glVertexAttribPointerARB( ATTR_INDEX_POSITION )\n"); } glVertexAttribPointerARB(ATTR_INDEX_POSITION, 4, GL_FLOAT, 0, 0, BUFFER_OFFSET(glState.currentVBO->ofsXYZ + (glState.vertexAttribsOldFrame * glState.currentVBO->sizeXYZ))); glState.vertexAttribPointersSet |= ATTR_POSITION; } if((attribBits & ATTR_TEXCOORD) && !(glState.vertexAttribPointersSet & ATTR_TEXCOORD)) { if(r_logFile->integer) { GLimp_LogComment("glVertexAttribPointerARB( ATTR_INDEX_TEXCOORD )\n"); } glVertexAttribPointerARB(ATTR_INDEX_TEXCOORD0, 4, GL_FLOAT, 0, 0, BUFFER_OFFSET(glState.currentVBO->ofsTexCoords)); glState.vertexAttribPointersSet |= ATTR_TEXCOORD; } if((attribBits & ATTR_LIGHTCOORD) && !(glState.vertexAttribPointersSet & ATTR_LIGHTCOORD)) { if(r_logFile->integer) { GLimp_LogComment("glVertexAttribPointerARB( ATTR_INDEX_LIGHTCOORD )\n"); } glVertexAttribPointerARB(ATTR_INDEX_TEXCOORD1, 4, GL_FLOAT, 0, 0, BUFFER_OFFSET(glState.currentVBO->ofsLightCoords)); glState.vertexAttribPointersSet |= ATTR_LIGHTCOORD; } if((attribBits & ATTR_TANGENT) && !(glState.vertexAttribPointersSet & ATTR_TANGENT)) { if(r_logFile->integer) { GLimp_LogComment("glVertexAttribPointerARB( ATTR_INDEX_TANGENT )\n"); } glVertexAttribPointerARB(ATTR_INDEX_TANGENT, 3, GL_FLOAT, 0, 16, BUFFER_OFFSET(glState.currentVBO->ofsTangents + (glState.vertexAttribsOldFrame * glState.currentVBO->sizeTangents))); glState.vertexAttribPointersSet |= ATTR_TANGENT; } if((attribBits & ATTR_BINORMAL) && !(glState.vertexAttribPointersSet & ATTR_BINORMAL)) { if(r_logFile->integer) { GLimp_LogComment("glVertexAttribPointerARB( ATTR_INDEX_BINORMAL )\n"); } glVertexAttribPointerARB(ATTR_INDEX_BINORMAL, 3, GL_FLOAT, 0, 16, BUFFER_OFFSET(glState.currentVBO->ofsBinormals + (glState.vertexAttribsOldFrame * glState.currentVBO->sizeBinormals))); glState.vertexAttribPointersSet |= ATTR_BINORMAL; } if((attribBits & ATTR_NORMAL) && !(glState.vertexAttribPointersSet & ATTR_NORMAL)) { if(r_logFile->integer) { GLimp_LogComment("glVertexAttribPointerARB( ATTR_INDEX_NORMAL )\n"); } glVertexAttribPointerARB(ATTR_INDEX_NORMAL, 3, GL_FLOAT, 0, 16, BUFFER_OFFSET(glState.currentVBO->ofsNormals + (glState.vertexAttribsOldFrame * glState.currentVBO->sizeNormals))); glState.vertexAttribPointersSet |= ATTR_NORMAL; } if((attribBits & ATTR_COLOR) && !(glState.vertexAttribPointersSet & ATTR_COLOR)) { if(r_logFile->integer) { GLimp_LogComment("glVertexAttribPointerARB( ATTR_INDEX_COLOR )\n"); } glVertexAttribPointerARB(ATTR_INDEX_COLOR, 4, GL_FLOAT, 0, 0, BUFFER_OFFSET(glState.currentVBO->ofsColors)); glState.vertexAttribPointersSet |= ATTR_COLOR; } #if !defined(COMPAT_Q3A) && !defined(COMPAT_ET) if((attribBits & ATTR_PAINTCOLOR) && !(glState.vertexAttribPointersSet & ATTR_PAINTCOLOR)) { if(r_logFile->integer) { GLimp_LogComment("glVertexAttribPointerARB( ATTR_INDEX_PAINTCOLOR )\n"); } glVertexAttribPointerARB(ATTR_INDEX_PAINTCOLOR, 4, GL_FLOAT, 0, 0, BUFFER_OFFSET(glState.currentVBO->ofsPaintColors)); glState.vertexAttribPointersSet |= ATTR_PAINTCOLOR; } if((attribBits & ATTR_LIGHTDIRECTION) && !(glState.vertexAttribPointersSet & ATTR_LIGHTDIRECTION)) { if(r_logFile->integer) { GLimp_LogComment("glVertexAttribPointerARB( ATTR_INDEX_LIGHTDIRECTION )\n"); } glVertexAttribPointerARB(ATTR_INDEX_LIGHTDIRECTION, 3, GL_FLOAT, 0, 16, BUFFER_OFFSET(glState.currentVBO->ofsLightDirections)); glState.vertexAttribPointersSet |= ATTR_LIGHTDIRECTION; } #endif // #if !defined(COMPAT_Q3A) && !defined(COMPAT_ET) if((attribBits & ATTR_BONE_INDEXES) && !(glState.vertexAttribPointersSet & ATTR_BONE_INDEXES)) { if(r_logFile->integer) { GLimp_LogComment("glVertexAttribPointerARB( ATTR_INDEX_BONE_INDEXES )\n"); } glVertexAttribPointerARB(ATTR_INDEX_BONE_INDEXES, 4, GL_INT, 0, 0, BUFFER_OFFSET(glState.currentVBO->ofsBoneIndexes)); glState.vertexAttribPointersSet |= ATTR_BONE_INDEXES; } if((attribBits & ATTR_BONE_WEIGHTS) && !(glState.vertexAttribPointersSet & ATTR_BONE_WEIGHTS)) { if(r_logFile->integer) { GLimp_LogComment("glVertexAttribPointerARB( ATTR_INDEX_BONE_WEIGHTS )\n"); } glVertexAttribPointerARB(ATTR_INDEX_BONE_WEIGHTS, 4, GL_FLOAT, 0, 0, BUFFER_OFFSET(glState.currentVBO->ofsBoneWeights)); glState.vertexAttribPointersSet |= ATTR_BONE_WEIGHTS; } if(glState.vertexAttribsInterpolation > 0) { if((attribBits & ATTR_POSITION2) && !(glState.vertexAttribPointersSet & ATTR_POSITION2)) { if(r_logFile->integer) { GLimp_LogComment("glVertexAttribPointerARB( ATTR_INDEX_POSITION2 )\n"); } glVertexAttribPointerARB(ATTR_INDEX_POSITION2, 4, GL_FLOAT, 0, 0, BUFFER_OFFSET(glState.currentVBO->ofsXYZ + (glState.vertexAttribsNewFrame * glState.currentVBO->sizeXYZ))); glState.vertexAttribPointersSet |= ATTR_POSITION2; } if((attribBits & ATTR_TANGENT2) && !(glState.vertexAttribPointersSet & ATTR_TANGENT2)) { if(r_logFile->integer) { GLimp_LogComment("glVertexAttribPointerARB( ATTR_INDEX_TANGENT2 )\n"); } glVertexAttribPointerARB(ATTR_INDEX_TANGENT2, 3, GL_FLOAT, 0, 16, BUFFER_OFFSET(glState.currentVBO->ofsTangents + (glState.vertexAttribsNewFrame * glState.currentVBO->sizeTangents))); glState.vertexAttribPointersSet |= ATTR_TANGENT2; } if((attribBits & ATTR_BINORMAL2) && !(glState.vertexAttribPointersSet & ATTR_BINORMAL2)) { if(r_logFile->integer) { GLimp_LogComment("glVertexAttribPointerARB( ATTR_INDEX_BINORMAL2 )\n"); } glVertexAttribPointerARB(ATTR_INDEX_BINORMAL2, 3, GL_FLOAT, 0, 16, BUFFER_OFFSET(glState.currentVBO->ofsBinormals + (glState.vertexAttribsNewFrame * glState.currentVBO->sizeBinormals))); glState.vertexAttribPointersSet |= ATTR_BINORMAL2; } if((attribBits & ATTR_NORMAL2) && !(glState.vertexAttribPointersSet & ATTR_NORMAL2)) { if(r_logFile->integer) { GLimp_LogComment("glVertexAttribPointerARB( ATTR_INDEX_NORMAL2 )\n"); } glVertexAttribPointerARB(ATTR_INDEX_NORMAL2, 3, GL_FLOAT, 0, 16, BUFFER_OFFSET(glState.currentVBO->ofsNormals + (glState.vertexAttribsNewFrame * glState.currentVBO->sizeNormals))); glState.vertexAttribPointersSet |= ATTR_NORMAL2; } } } /* ================ RB_Hyperspace A player has predicted a teleport, but hasn't arrived yet ================ */ static void RB_Hyperspace(void) { float c; if(!backEnd.isHyperspace) { // do initialization shit } c = (backEnd.refdef.time & 255) / 255.0f; GL_ClearColor(c, c, c, 1); glClear(GL_COLOR_BUFFER_BIT); backEnd.isHyperspace = qtrue; } static void SetViewportAndScissor(void) { GL_LoadProjectionMatrix(backEnd.viewParms.projectionMatrix); // set the window clipping GL_Viewport(backEnd.viewParms.viewportX, backEnd.viewParms.viewportY, backEnd.viewParms.viewportWidth, backEnd.viewParms.viewportHeight); GL_Scissor(backEnd.viewParms.viewportX, backEnd.viewParms.viewportY, backEnd.viewParms.viewportWidth, backEnd.viewParms.viewportHeight); } /* ================ RB_SetGL2D ================ */ static void RB_SetGL2D(void) { matrix_t proj; GLimp_LogComment("--- RB_SetGL2D ---\n"); // disable offscreen rendering if(glConfig2.framebufferObjectAvailable) { R_BindNullFBO(); } backEnd.projection2D = qtrue; // set 2D virtual screen size GL_Viewport(0, 0, glConfig.vidWidth, glConfig.vidHeight); GL_Scissor(0, 0, glConfig.vidWidth, glConfig.vidHeight); MatrixOrthogonalProjection(proj, 0, glConfig.vidWidth, glConfig.vidHeight, 0, 0, 1); GL_LoadProjectionMatrix(proj); GL_LoadModelViewMatrix(matrixIdentity); GL_State(GLS_DEPTHTEST_DISABLE | GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA); GL_Cull(CT_TWO_SIDED); // set time for 2D shaders backEnd.refdef.time = ri.Milliseconds(); backEnd.refdef.floatTime = backEnd.refdef.time * 0.001f; } enum renderDrawSurfaces_e { DRAWSURFACES_WORLD_ONLY, DRAWSURFACES_ENTITIES_ONLY, DRAWSURFACES_ALL }; static void RB_RenderDrawSurfaces(bool opaque, bool depthFill, renderDrawSurfaces_e drawSurfFilter) { trRefEntity_t *entity, *oldEntity; shader_t *shader, *oldShader; int lightmapNum, oldLightmapNum; int fogNum, oldFogNum; qboolean depthRange, oldDepthRange; int i; drawSurf_t *drawSurf; GLimp_LogComment("--- RB_RenderDrawSurfaces ---\n"); // draw everything oldEntity = NULL; oldShader = NULL; oldLightmapNum = -1; oldFogNum = -1; oldDepthRange = qfalse; depthRange = qfalse; backEnd.currentLight = NULL; for(i = 0, drawSurf = backEnd.viewParms.drawSurfs; i < backEnd.viewParms.numDrawSurfs; i++, drawSurf++) { // update locals entity = drawSurf->entity; shader = tr.sortedShaders[drawSurf->shaderNum]; lightmapNum = drawSurf->lightmapNum; fogNum = drawSurf->fogNum; switch (drawSurfFilter) { case DRAWSURFACES_WORLD_ONLY: if(entity != &tr.worldEntity) continue; break; case DRAWSURFACES_ENTITIES_ONLY: if(entity == &tr.worldEntity) continue; break; case DRAWSURFACES_ALL: break; }; if(glConfig2.occlusionQueryBits && glConfig.driverType != GLDRV_MESA && r_dynamicEntityOcclusionCulling->integer && !entity->occlusionQuerySamples) { continue; } if(opaque) { // skip all translucent surfaces that don't matter for this pass if(shader->sort > SS_OPAQUE) { break; } } else { // skip all opaque surfaces that don't matter for this pass if(shader->sort <= SS_OPAQUE) { continue; } } if(entity == oldEntity && shader == oldShader && lightmapNum == oldLightmapNum && fogNum == oldFogNum) { // fast path, same as previous sort rb_surfaceTable[*drawSurf->surface] (drawSurf->surface); continue; } // change the tess parameters if needed // a "entityMergable" shader is a shader that can have surfaces from seperate // entities merged into a single batch, like smoke and blood puff sprites if(shader != oldShader || lightmapNum != oldLightmapNum || fogNum != oldFogNum || (entity != oldEntity && !shader->entityMergable)) { if(oldShader != NULL) { Tess_End(); } if(depthFill) Tess_Begin(Tess_StageIteratorGBuffer, NULL, shader, NULL, qtrue, qfalse, lightmapNum, fogNum); else Tess_Begin(Tess_StageIteratorGeneric, NULL, shader, NULL, qfalse, qfalse, lightmapNum, fogNum); oldShader = shader; oldLightmapNum = lightmapNum; oldFogNum = fogNum; } // change the modelview matrix if needed if(entity != oldEntity) { depthRange = qfalse; if(entity != &tr.worldEntity) { backEnd.currentEntity = entity; // set up the transformation matrix R_RotateEntityForViewParms(backEnd.currentEntity, &backEnd.viewParms, &backEnd.orientation); if(backEnd.currentEntity->e.renderfx & RF_DEPTHHACK) { // hack the depth range to prevent view model from poking into walls depthRange = qtrue; } } else { backEnd.currentEntity = &tr.worldEntity; backEnd.orientation = backEnd.viewParms.world; } GL_LoadModelViewMatrix(backEnd.orientation.modelViewMatrix); // change depthrange if needed if(oldDepthRange != depthRange) { if(depthRange) { glDepthRange(0, 0.3); } else { glDepthRange(0, 1); } oldDepthRange = depthRange; } oldEntity = entity; } // add the triangles for this surface rb_surfaceTable[*drawSurf->surface] (drawSurf->surface); } // draw the contents of the last shader batch if(oldShader != NULL) { Tess_End(); } // go back to the world modelview matrix GL_LoadModelViewMatrix(backEnd.viewParms.world.modelViewMatrix); if(depthRange) { glDepthRange(0, 1); } GL_CheckErrors(); } static void RB_RenderOpaqueSurfacesIntoDepth(bool onlyWorld) { trRefEntity_t *entity, *oldEntity; shader_t *shader, *oldShader; qboolean depthRange, oldDepthRange; qboolean alphaTest, oldAlphaTest; deformType_t deformType, oldDeformType; int i; drawSurf_t *drawSurf; GLimp_LogComment("--- RB_RenderOpaqueSurfacesIntoDepth ---\n"); // draw everything oldEntity = NULL; oldShader = NULL; oldDepthRange = depthRange = qfalse; oldAlphaTest = alphaTest = qfalse; oldDeformType = deformType = DEFORM_TYPE_NONE; backEnd.currentLight = NULL; for(i = 0, drawSurf = backEnd.viewParms.drawSurfs; i < backEnd.viewParms.numDrawSurfs; i++, drawSurf++) { // update locals entity = drawSurf->entity; shader = tr.sortedShaders[drawSurf->shaderNum]; alphaTest = shader->alphaTest; #if 0 if(onlyWorld && (entity != &tr.worldEntity)) { continue; } #endif // skip all translucent surfaces that don't matter for this pass if(shader->sort > SS_OPAQUE) { break; } if(shader->numDeforms) { deformType = ShaderRequiresCPUDeforms(shader) ? DEFORM_TYPE_CPU : DEFORM_TYPE_GPU; } else { deformType = DEFORM_TYPE_NONE; } // change the tess parameters if needed // a "entityMergable" shader is a shader that can have surfaces from seperate // entities merged into a single batch, like smoke and blood puff sprites //if(shader != oldShader || lightmapNum != oldLightmapNum || (entity != oldEntity && !shader->entityMergable)) if(entity == oldEntity && (alphaTest ? shader == oldShader : alphaTest == oldAlphaTest) && deformType == oldDeformType) { // fast path, same as previous sort rb_surfaceTable[*drawSurf->surface] (drawSurf->surface); continue; } else { if(oldShader != NULL) { Tess_End(); } Tess_Begin(Tess_StageIteratorDepthFill, NULL, shader, NULL, qtrue, qfalse, -1, 0); oldShader = shader; oldAlphaTest = alphaTest; oldDeformType = deformType; } // change the modelview matrix if needed if(entity != oldEntity) { depthRange = qfalse; if(entity != &tr.worldEntity) { backEnd.currentEntity = entity; // set up the transformation matrix R_RotateEntityForViewParms(backEnd.currentEntity, &backEnd.viewParms, &backEnd.orientation); if(backEnd.currentEntity->e.renderfx & RF_DEPTHHACK) { // hack the depth range to prevent view model from poking into walls depthRange = qtrue; } } else { backEnd.currentEntity = &tr.worldEntity; backEnd.orientation = backEnd.viewParms.world; } GL_LoadModelViewMatrix(backEnd.orientation.modelViewMatrix); // change depthrange if needed if(oldDepthRange != depthRange) { if(depthRange) { glDepthRange(0, 0.3); } else { glDepthRange(0, 1); } oldDepthRange = depthRange; } oldEntity = entity; } // add the triangles for this surface rb_surfaceTable[*drawSurf->surface] (drawSurf->surface); } // draw the contents of the last shader batch if(oldShader != NULL) { Tess_End(); } // go back to the world modelview matrix GL_LoadModelViewMatrix(backEnd.viewParms.world.modelViewMatrix); if(depthRange) { glDepthRange(0, 1); } GL_CheckErrors(); } // *INDENT-OFF* #ifdef VOLUMETRIC_LIGHTING static void Render_lightVolume(interaction_t * ia) { int j; trRefLight_t *light; shader_t *lightShader; shaderStage_t *attenuationXYStage; shaderStage_t *attenuationZStage; matrix_t ortho; vec4_t quadVerts[4]; light = ia->light; // set the window clipping GL_Viewport(backEnd.viewParms.viewportX, backEnd.viewParms.viewportY, backEnd.viewParms.viewportWidth, backEnd.viewParms.viewportHeight); // set light scissor to reduce fillrate GL_Scissor(ia->scissorX, ia->scissorY, ia->scissorWidth, ia->scissorHeight); // set 2D virtual screen size GL_PushMatrix(); MatrixOrthogonalProjection(ortho, backEnd.viewParms.viewportX, backEnd.viewParms.viewportX + backEnd.viewParms.viewportWidth, backEnd.viewParms.viewportY, backEnd.viewParms.viewportY + backEnd.viewParms.viewportHeight, -99999, 99999); GL_LoadProjectionMatrix(ortho); GL_LoadModelViewMatrix(matrixIdentity); switch (light->l.rlType) { case RL_PROJ: { MatrixSetupTranslation(light->attenuationMatrix, 0.5, 0.5, 0.0); // bias MatrixMultiplyScale(light->attenuationMatrix, 0.5, 0.5, 1.0 / Q_min(light->falloffLength, 1.0)); // scale break; } case RL_OMNI: default: { MatrixSetupTranslation(light->attenuationMatrix, 0.5, 0.5, 0.5); // bias MatrixMultiplyScale(light->attenuationMatrix, 0.5, 0.5, 0.5); // scale break; } } MatrixMultiply2(light->attenuationMatrix, light->projectionMatrix); // light projection (frustum) MatrixMultiply2(light->attenuationMatrix, light->viewMatrix); lightShader = light->shader; attenuationZStage = lightShader->stages[0]; for(j = 1; j < MAX_SHADER_STAGES; j++) { attenuationXYStage = lightShader->stages[j]; if(!attenuationXYStage) { break; } if(attenuationXYStage->type != ST_ATTENUATIONMAP_XY) { continue; } if(!RB_EvalExpression(&attenuationXYStage->ifExp, 1.0)) { continue; } Tess_ComputeColor(attenuationXYStage); R_ComputeFinalAttenuation(attenuationXYStage, light); if(light->l.rlType == RL_OMNI) { vec3_t viewOrigin; vec3_t lightOrigin; vec4_t lightColor; qboolean shadowCompare; GLimp_LogComment("--- Render_lightVolume_omni ---\n"); // enable shader, set arrays GL_BindProgram(&tr.lightVolumeShader_omni); //GL_VertexAttribsState(tr.lightVolumeShader_omni.attribs); GL_Cull(CT_TWO_SIDED); GL_State(GLS_DEPTHTEST_DISABLE | GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE); //GL_State(GLS_DEPTHFUNC_LESS | GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE); //GL_State(GLS_DEPTHTEST_DISABLE | GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA); //GL_State(attenuationXYStage->stateBits & ~(GLS_DEPTHMASK_TRUE | GLS_DEPTHTEST_DISABLE)); // set uniforms VectorCopy(backEnd.viewParms.orientation.origin, viewOrigin); // in world space VectorCopy(light->origin, lightOrigin); VectorCopy(tess.svars.color, lightColor); shadowCompare = r_shadows->integer >= SHADOWING_ESM16 && !light->l.noShadows && light->shadowLOD >= 0; GLSL_SetUniform_ViewOrigin(&tr.lightVolumeShader_omni, viewOrigin); GLSL_SetUniform_LightOrigin(&tr.lightVolumeShader_omni, lightOrigin); GLSL_SetUniform_LightColor(&tr.lightVolumeShader_omni, lightColor); GLSL_SetUniform_LightRadius(&tr.lightVolumeShader_omni, light->sphereRadius); GLSL_SetUniform_LightScale(&tr.lightVolumeShader_omni, light->l.scale); GLSL_SetUniform_LightAttenuationMatrix(&tr.lightVolumeShader_omni, light->attenuationMatrix2); // FIXME GLSL_SetUniform_ShadowMatrix(&tr.lightVolumeShader_omni, light->attenuationMatrix); GLSL_SetUniform_ShadowCompare(&tr.lightVolumeShader_omni, shadowCompare); GLSL_SetUniform_ModelViewProjectionMatrix(&tr.lightVolumeShader_omni, glState.modelViewProjectionMatrix[glState.stackIndex]); GLSL_SetUniform_UnprojectMatrix(&tr.lightVolumeShader_omni, backEnd.viewParms.unprojectionMatrix); //GLSL_SetUniform_PortalClipping(&tr.lightVolumeShader_omni, backEnd.viewParms.isPortal); // bind u_DepthMap GL_SelectTexture(0); if(r_deferredShading->integer && glConfig2.framebufferObjectAvailable && glConfig2.textureFloatAvailable && glConfig2.drawBuffersAvailable && glConfig2.maxDrawBuffers >= 4) { GL_Bind(tr.depthRenderImage); } else if(r_hdrRendering->integer && glConfig2.framebufferObjectAvailable && glConfig2.textureFloatAvailable) { GL_Bind(tr.depthRenderImage); } else { // depth texture is not bound to a FBO GL_Bind(tr.depthRenderImage); glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, tr.depthRenderImage->uploadWidth, tr.depthRenderImage->uploadHeight); } // bind u_AttenuationMapXY GL_SelectTexture(1); BindAnimatedImage(&attenuationXYStage->bundle[TB_COLORMAP]); // bind u_AttenuationMapZ GL_SelectTexture(2); BindAnimatedImage(&attenuationZStage->bundle[TB_COLORMAP]); // bind u_ShadowMap if(shadowCompare) { GL_SelectTexture(3); GL_Bind(tr.shadowCubeFBOImage[light->shadowLOD]); } // draw light scissor rectangle VectorSet4(quadVerts[0], ia->scissorX, ia->scissorY, 0, 1); VectorSet4(quadVerts[1], ia->scissorX + ia->scissorWidth - 1, ia->scissorY, 0, 1); VectorSet4(quadVerts[2], ia->scissorX + ia->scissorWidth - 1, ia->scissorY + ia->scissorHeight - 1, 0, 1); VectorSet4(quadVerts[3], ia->scissorX, ia->scissorY + ia->scissorHeight - 1, 0, 1); Tess_InstantQuad(quadVerts); GL_CheckErrors(); } } GL_PopMatrix(); } #endif // *INDENT-ON* /* * helper function for parallel split shadow mapping */ static int MergeInteractionBounds(const matrix_t lightViewProjectionMatrix, interaction_t * ia, int iaCount, vec3_t bounds[2], bool shadowCasters) { int i; int j; surfaceType_t *surface; vec4_t point; vec4_t transf; vec3_t worldBounds[2]; //vec3_t viewBounds[2]; //vec3_t center; //float radius; int numCasters; frustum_t frustum; cplane_t *clipPlane; int r; numCasters = 0; ClearBounds(bounds[0], bounds[1]); // calculate frustum planes using the modelview projection matrix R_SetupFrustum2(frustum, lightViewProjectionMatrix); while(iaCount < backEnd.viewParms.numInteractions) { surface = ia->surface; if(shadowCasters) { if(ia->type == IA_LIGHTONLY) { goto skipInteraction; } } else { // we only merge shadow receivers if(ia->type == IA_SHADOWONLY) { goto skipInteraction; } } if(*surface == SF_FACE || *surface == SF_GRID || *surface == SF_TRIANGLES) { srfGeneric_t *gen = (srfGeneric_t *) surface; VectorCopy(gen->bounds[0], worldBounds[0]); VectorCopy(gen->bounds[1], worldBounds[1]); } else if(*surface == SF_VBO_MESH) { srfVBOMesh_t *srf = (srfVBOMesh_t *) surface; //ri.Printf(PRINT_ALL, "merging vbo mesh bounds\n"); VectorCopy(srf->bounds[0], worldBounds[0]); VectorCopy(srf->bounds[1], worldBounds[1]); } else if(*surface == SF_MDV) { //Tess_AddCube(vec3_origin, entity->localBounds[0], entity->localBounds[1], lightColor); goto skipInteraction; } else { goto skipInteraction; } #if 1 // use the frustum planes to cut off shadow casters beyond the split frustum for(i = 0; i < 6; i++) { clipPlane = &frustum[i]; r = BoxOnPlaneSide(worldBounds[0], worldBounds[1], clipPlane); if(r == 2) { goto skipInteraction; } } #endif if(shadowCasters && ia->type != IA_LIGHTONLY) { numCasters++; } #if 1 for(j = 0; j < 8; j++) { point[0] = worldBounds[j & 1][0]; point[1] = worldBounds[(j >> 1) & 1][1]; point[2] = worldBounds[(j >> 2) & 1][2]; point[3] = 1; MatrixTransform4(lightViewProjectionMatrix, point, transf); transf[0] /= transf[3]; transf[1] /= transf[3]; transf[2] /= transf[3]; AddPointToBounds(transf, bounds[0], bounds[1]); } #elif 0 ClearBounds(viewBounds[0], viewBounds[1]); for(j = 0; j < 8; j++) { point[0] = worldBounds[j & 1][0]; point[1] = worldBounds[(j >> 1) & 1][1]; point[2] = worldBounds[(j >> 2) & 1][2]; point[3] = 1; MatrixTransform4(lightViewProjectionMatrix, point, transf); transf[0] /= transf[3]; transf[1] /= transf[3]; transf[2] /= transf[3]; AddPointToBounds(transf, viewBounds[0], viewBounds[1]); } // get sphere of AABB VectorAdd(viewBounds[0], viewBounds[1], center); VectorScale(center, 0.5, center); radius = RadiusFromBounds(viewBounds[0], viewBounds[1]); for(j = 0; j < 3; j++) { if((transf[j] - radius) < bounds[0][j]) { bounds[0][j] = transf[i] - radius; } if((transf[j] + radius) > bounds[1][j]) { bounds[1][j] = transf[i] + radius; } } #else ClearBounds(viewBounds[0], viewBounds[1]); for(j = 0; j < 8; j++) { point[0] = worldBounds[j & 1][0]; point[1] = worldBounds[(j >> 1) & 1][1]; point[2] = worldBounds[(j >> 2) & 1][2]; point[3] = 1; MatrixTransform4(lightViewProjectionMatrix, point, transf); //transf[0] /= transf[3]; //transf[1] /= transf[3]; //transf[2] /= transf[3]; AddPointToBounds(transf, viewBounds[0], viewBounds[1]); } // get sphere of AABB VectorAdd(viewBounds[0], viewBounds[1], center); VectorScale(center, 0.5, center); //MatrixTransform4(lightViewProjectionMatrix, center, transf); //transf[0] /= transf[3]; //transf[1] /= transf[3]; //transf[2] /= transf[3]; radius = RadiusFromBounds(viewBounds[0], viewBounds[1]); if((transf[2] + radius) > bounds[1][2]) { bounds[1][2] = transf[2] + radius; } #endif skipInteraction: if(!ia->next) { // this is the last interaction of the current light break; } else { // just continue ia = ia->next; iaCount++; } } return numCasters; } /* ================= RB_RenderInteractions ================= */ static void RB_RenderInteractions() { shader_t *shader, *oldShader; trRefEntity_t *entity, *oldEntity; trRefLight_t *light, *oldLight; interaction_t *ia; qboolean depthRange, oldDepthRange; int iaCount; surfaceType_t *surface; vec3_t tmp; matrix_t modelToLight; int startTime = 0, endTime = 0; GLimp_LogComment("--- RB_RenderInteractions ---\n"); if(r_speeds->integer == RSPEEDS_SHADING_TIMES) { glFinish(); startTime = ri.Milliseconds(); } // draw everything oldLight = NULL; oldEntity = NULL; oldShader = NULL; oldDepthRange = qfalse; depthRange = qfalse; // render interactions for(iaCount = 0, ia = &backEnd.viewParms.interactions[0]; iaCount < backEnd.viewParms.numInteractions;) { backEnd.currentLight = light = ia->light; backEnd.currentEntity = entity = ia->entity; surface = ia->surface; shader = ia->surfaceShader; if(glConfig2.occlusionQueryBits && glConfig.driverType != GLDRV_MESA) { // skip all interactions of this light because it failed the occlusion query if(r_dynamicLightOcclusionCulling->integer && !ia->occlusionQuerySamples) goto skipInteraction; if(r_dynamicEntityOcclusionCulling->integer && !entity->occlusionQuerySamples) goto skipInteraction; } if(!shader->interactLight) { // skip this interaction because the surface shader has no ability to interact with light // this will save texcoords and matrix calculations goto skipInteraction; } if(ia->type == IA_SHADOWONLY) { // skip this interaction because the interaction is meant for shadowing only goto skipInteraction; } if(light != oldLight) { GLimp_LogComment("----- Rendering new light -----\n"); // set light scissor to reduce fillrate GL_Scissor(ia->scissorX, ia->scissorY, ia->scissorWidth, ia->scissorHeight); } // Tr3B: this should never happen in the first iteration if(light == oldLight && entity == oldEntity && shader == oldShader) { // fast path, same as previous rb_surfaceTable[*surface] (surface); goto nextInteraction; } // draw the contents of the last shader batch Tess_End(); // begin a new batch Tess_Begin(Tess_StageIteratorLighting, NULL, shader, light->shader, qfalse, qfalse, -1, 0); // change the modelview matrix if needed if(entity != oldEntity) { depthRange = qfalse; if(entity != &tr.worldEntity) { // set up the transformation matrix R_RotateEntityForViewParms(backEnd.currentEntity, &backEnd.viewParms, &backEnd.orientation); if(backEnd.currentEntity->e.renderfx & RF_DEPTHHACK) { // hack the depth range to prevent view model from poking into walls depthRange = qtrue; } } else { backEnd.orientation = backEnd.viewParms.world; } GL_LoadModelViewMatrix(backEnd.orientation.modelViewMatrix); // change depthrange if needed if(oldDepthRange != depthRange) { if(depthRange) { glDepthRange(0, 0.3); } else { glDepthRange(0, 1); } oldDepthRange = depthRange; } } // change the attenuation matrix if needed if(light != oldLight || entity != oldEntity) { // transform light origin into model space for u_LightOrigin parameter if(entity != &tr.worldEntity) { VectorSubtract(light->origin, backEnd.orientation.origin, tmp); light->transformed[0] = DotProduct(tmp, backEnd.orientation.axis[0]); light->transformed[1] = DotProduct(tmp, backEnd.orientation.axis[1]); light->transformed[2] = DotProduct(tmp, backEnd.orientation.axis[2]); } else { VectorCopy(light->origin, light->transformed); } // build the attenuation matrix using the entity transform Matrix4x4Multiply(light->viewMatrix, backEnd.orientation.transformMatrix, modelToLight); switch (light->l.rlType) { case RL_PROJ: { MatrixSetupTranslation(light->attenuationMatrix, 0.5, 0.5, 0.0); // bias MatrixMultiplyScale(light->attenuationMatrix, 0.5, 0.5, 1.0 / Q_min(light->falloffLength, 1.0)); // scale break; } case RL_OMNI: default: { MatrixSetupTranslation(light->attenuationMatrix, 0.5, 0.5, 0.5); // bias MatrixMultiplyScale(light->attenuationMatrix, 0.5, 0.5, 0.5); // scale break; } } MatrixMultiply2(light->attenuationMatrix, light->projectionMatrix); // light projection (frustum) MatrixMultiply2(light->attenuationMatrix, modelToLight); } // add the triangles for this surface rb_surfaceTable[*surface] (surface); nextInteraction: // remember values oldLight = light; oldEntity = entity; oldShader = shader; skipInteraction: if(!ia->next) { // draw the contents of the last shader batch Tess_End(); #ifdef VOLUMETRIC_LIGHTING // draw the light volume if needed if(light->shader->volumetricLight) { Render_lightVolume(ia); } #endif if(iaCount < (backEnd.viewParms.numInteractions - 1)) { // jump to next interaction and continue ia++; iaCount++; } else { // increase last time to leave for loop iaCount++; } // force updates oldLight = NULL; oldEntity = NULL; oldShader = NULL; } else { // just continue ia = ia->next; iaCount++; } } // go back to the world modelview matrix GL_LoadModelViewMatrix(backEnd.viewParms.world.modelViewMatrix); if(depthRange) { glDepthRange(0, 1); } // reset scissor GL_Scissor(backEnd.viewParms.viewportX, backEnd.viewParms.viewportY, backEnd.viewParms.viewportWidth, backEnd.viewParms.viewportHeight); GL_CheckErrors(); if(r_speeds->integer == RSPEEDS_SHADING_TIMES) { glFinish(); endTime = ri.Milliseconds(); backEnd.pc.c_forwardLightingTime = endTime - startTime; } } /* ================= RB_RenderInteractionsShadowMapped ================= */ static void RB_RenderInteractionsShadowMapped() { shader_t *shader, *oldShader; trRefEntity_t *entity, *oldEntity; trRefLight_t *light, *oldLight; interaction_t *ia; int iaCount; int iaFirst; surfaceType_t *surface; qboolean depthRange, oldDepthRange; qboolean alphaTest, oldAlphaTest; deformType_t deformType, oldDeformType; vec3_t tmp; matrix_t modelToLight; qboolean drawShadows; int cubeSide; int splitFrustumIndex; int startTime = 0, endTime = 0; const matrix_t bias = { 0.5, 0.0, 0.0, 0.0, 0.0, 0.5, 0.0, 0.0, 0.0, 0.0, 0.5, 0.0, 0.5, 0.5, 0.5, 1.0}; if(!glConfig2.framebufferObjectAvailable || !glConfig2.textureFloatAvailable) { RB_RenderInteractions(); return; } GLimp_LogComment("--- RB_RenderInteractionsShadowMapped ---\n"); if(r_speeds->integer == RSPEEDS_SHADING_TIMES) { glFinish(); startTime = ri.Milliseconds(); } // draw everything oldLight = NULL; oldEntity = NULL; oldShader = NULL; oldDepthRange = depthRange = qfalse; oldAlphaTest = alphaTest = qfalse; oldDeformType = deformType = DEFORM_TYPE_NONE; drawShadows = qtrue; cubeSide = 0; splitFrustumIndex = 0; // if we need to clear the FBO color buffers then it should be white GL_ClearColor(1.0f, 1.0f, 1.0f, 1.0f); // render interactions for(iaCount = 0, iaFirst = 0, ia = &backEnd.viewParms.interactions[0]; iaCount < backEnd.viewParms.numInteractions;) { backEnd.currentLight = light = ia->light; backEnd.currentEntity = entity = ia->entity; surface = ia->surface; shader = ia->surfaceShader; alphaTest = shader->alphaTest; if(shader->numDeforms) { deformType = ShaderRequiresCPUDeforms(shader) ? DEFORM_TYPE_CPU : DEFORM_TYPE_GPU; } else { deformType = DEFORM_TYPE_NONE; } if(glConfig2.occlusionQueryBits && glConfig.driverType != GLDRV_MESA && r_dynamicLightOcclusionCulling->integer && !ia->occlusionQuerySamples) { // skip all interactions of this light because it failed the occlusion query goto skipInteraction; } if(light->l.inverseShadows) { // handle those lights in RB_RenderInteractionsDeferredInverseShadows goto skipInteraction; } // only iaCount == iaFirst if first iteration or counters were reset if(iaCount == iaFirst) { if(drawShadows) { // HACK: bring OpenGL into a safe state or strange FBO update problems will occur GL_BindProgram(NULL); GL_State(GLS_DEFAULT); //GL_VertexAttribsState(ATTR_POSITION); GL_SelectTexture(0); GL_Bind(tr.whiteImage); if(light->l.noShadows || light->shadowLOD < 0) { if(r_logFile->integer) { // don't just call LogComment, or we will get // a call to va() every frame! GLimp_LogComment(va("----- Skipping shadowCube side: %i -----\n", cubeSide)); } goto skipInteraction; } else { switch (light->l.rlType) { case RL_OMNI: { //float xMin, xMax, yMin, yMax; //float width, height, depth; float zNear, zFar; float fovX, fovY; qboolean flipX, flipY; //float *proj; vec3_t angles; matrix_t rotationMatrix, transformMatrix, viewMatrix; if(r_logFile->integer) { // don't just call LogComment, or we will get // a call to va() every frame! GLimp_LogComment(va("----- Rendering shadowCube side: %i -----\n", cubeSide)); } R_BindFBO(tr.shadowMapFBO[light->shadowLOD]); R_AttachFBOTexture2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB + cubeSide, tr.shadowCubeFBOImage[light->shadowLOD]->texnum, 0); if(!r_ignoreGLErrors->integer) { R_CheckFBO(tr.shadowMapFBO[light->shadowLOD]); } // set the window clipping GL_Viewport(0, 0, shadowMapResolutions[light->shadowLOD], shadowMapResolutions[light->shadowLOD]); GL_Scissor(0, 0, shadowMapResolutions[light->shadowLOD], shadowMapResolutions[light->shadowLOD]); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); switch (cubeSide) { case 0: { // view parameters VectorSet(angles, 0, 0, 90); // projection parameters flipX = qfalse; flipY = qfalse; break; } case 1: { VectorSet(angles, 0, 180, 90); flipX = qtrue; flipY = qtrue; break; } case 2: { VectorSet(angles, 0, 90, 0); flipX = qfalse; flipY = qfalse; break; } case 3: { VectorSet(angles, 0, -90, 0); flipX = qtrue; flipY = qtrue; break; } case 4: { VectorSet(angles, -90, 90, 0); flipX = qfalse; flipY = qfalse; break; } case 5: { VectorSet(angles, 90, 90, 0); flipX = qtrue; flipY = qtrue; break; } default: { // shut up compiler VectorSet(angles, 0, 0, 0); flipX = qfalse; flipY = qfalse; break; } } // Quake -> OpenGL view matrix from light perspective MatrixFromAngles(rotationMatrix, angles[PITCH], angles[YAW], angles[ROLL]); MatrixSetupTransformFromRotation(transformMatrix, rotationMatrix, light->origin); MatrixAffineInverse(transformMatrix, viewMatrix); // convert from our coordinate system (looking down X) // to OpenGL's coordinate system (looking down -Z) Matrix4x4Multiply(quakeToOpenGLMatrix, viewMatrix, light->viewMatrix); // OpenGL projection matrix fovX = 90; fovY = 90; zNear = 1.0; zFar = light->sphereRadius; if(flipX) { fovX = -fovX; } if(flipY) { fovY = -fovY; } MatrixPerspectiveProjectionFovXYRH(light->projectionMatrix, fovX, fovY, zNear, zFar); GL_LoadProjectionMatrix(light->projectionMatrix); break; } case RL_PROJ: { GLimp_LogComment("--- Rendering projective shadowMap ---\n"); R_BindFBO(tr.shadowMapFBO[light->shadowLOD]); R_AttachFBOTexture2D(GL_TEXTURE_2D, tr.shadowMapFBOImage[light->shadowLOD]->texnum, 0); if(!r_ignoreGLErrors->integer) { R_CheckFBO(tr.shadowMapFBO[light->shadowLOD]); } // set the window clipping GL_Viewport(0, 0, shadowMapResolutions[light->shadowLOD], shadowMapResolutions[light->shadowLOD]); GL_Scissor(0, 0, shadowMapResolutions[light->shadowLOD], shadowMapResolutions[light->shadowLOD]); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); GL_LoadProjectionMatrix(light->projectionMatrix); break; } case RL_DIRECTIONAL: { int j; vec3_t angles; vec4_t forward, side, up; vec3_t lightDirection; vec3_t viewOrigin, viewDirection; matrix_t rotationMatrix, transformMatrix, viewMatrix, projectionMatrix, viewProjectionMatrix; matrix_t cropMatrix; vec4_t splitFrustum[6]; vec3_t splitFrustumCorners[8]; vec3_t splitFrustumBounds[2]; vec3_t splitFrustumViewBounds[2]; vec3_t splitFrustumClipBounds[2]; float splitFrustumRadius; int numCasters; vec3_t casterBounds[2]; vec3_t receiverBounds[2]; vec3_t cropBounds[2]; vec4_t point; vec4_t transf; GLimp_LogComment("--- Rendering directional shadowMap ---\n"); R_BindFBO(tr.sunShadowMapFBO[splitFrustumIndex]); if(!r_evsmPostProcess->integer) { R_AttachFBOTexture2D(GL_TEXTURE_2D, tr.sunShadowMapFBOImage[splitFrustumIndex]->texnum, 0); } else { R_AttachFBOTextureDepth(tr.sunShadowMapFBOImage[splitFrustumIndex]->texnum); } if(!r_ignoreGLErrors->integer) { R_CheckFBO(tr.sunShadowMapFBO[splitFrustumIndex]); } // set the window clipping GL_Viewport(0, 0, sunShadowMapResolutions[splitFrustumIndex], sunShadowMapResolutions[splitFrustumIndex]); GL_Scissor(0, 0, sunShadowMapResolutions[splitFrustumIndex], sunShadowMapResolutions[splitFrustumIndex]); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); #if 1 VectorCopy(tr.sunDirection, lightDirection); #else VectorCopy(light->direction, lightDirection); #endif if(r_parallelShadowSplits->integer) { // original light direction is from surface to light VectorInverse(lightDirection); VectorNormalize(lightDirection); VectorCopy(backEnd.viewParms.orientation.origin, viewOrigin); VectorCopy(backEnd.viewParms.orientation.axis[0], viewDirection); VectorNormalize(viewDirection); #if 1 // calculate new up dir CrossProduct(lightDirection, viewDirection, side); VectorNormalize(side); CrossProduct(side, lightDirection, up); VectorNormalize(up); vectoangles(lightDirection, angles); MatrixFromAngles(rotationMatrix, angles[PITCH], angles[YAW], angles[ROLL]); AngleVectors(angles, forward, side, up); MatrixLookAtRH(light->viewMatrix, viewOrigin, lightDirection, up); #else MatrixLookAtRH(light->viewMatrix, viewOrigin, lightDirection, viewDirection); #endif for(j = 0; j < 6; j++) { VectorCopy(backEnd.viewParms.frustums[1 + splitFrustumIndex][j].normal, splitFrustum[j]); splitFrustum[j][3] = backEnd.viewParms.frustums[1 + splitFrustumIndex][j].dist; } // calculate split frustum corner points PlanesGetIntersectionPoint(splitFrustum[FRUSTUM_LEFT], splitFrustum[FRUSTUM_TOP], splitFrustum[FRUSTUM_NEAR], splitFrustumCorners[0]); PlanesGetIntersectionPoint(splitFrustum[FRUSTUM_RIGHT], splitFrustum[FRUSTUM_TOP], splitFrustum[FRUSTUM_NEAR], splitFrustumCorners[1]); PlanesGetIntersectionPoint(splitFrustum[FRUSTUM_RIGHT], splitFrustum[FRUSTUM_BOTTOM], splitFrustum[FRUSTUM_NEAR], splitFrustumCorners[2]); PlanesGetIntersectionPoint(splitFrustum[FRUSTUM_LEFT], splitFrustum[FRUSTUM_BOTTOM], splitFrustum[FRUSTUM_NEAR], splitFrustumCorners[3]); PlanesGetIntersectionPoint(splitFrustum[FRUSTUM_LEFT], splitFrustum[FRUSTUM_TOP], splitFrustum[FRUSTUM_FAR], splitFrustumCorners[4]); PlanesGetIntersectionPoint(splitFrustum[FRUSTUM_RIGHT], splitFrustum[FRUSTUM_TOP], splitFrustum[FRUSTUM_FAR], splitFrustumCorners[5]); PlanesGetIntersectionPoint(splitFrustum[FRUSTUM_RIGHT], splitFrustum[FRUSTUM_BOTTOM], splitFrustum[FRUSTUM_FAR], splitFrustumCorners[6]); PlanesGetIntersectionPoint(splitFrustum[FRUSTUM_LEFT], splitFrustum[FRUSTUM_BOTTOM], splitFrustum[FRUSTUM_FAR], splitFrustumCorners[7]); if(r_logFile->integer) { vec3_t rayIntersectionNear, rayIntersectionFar; float zNear, zFar; // don't just call LogComment, or we will get // a call to va() every frame! //GLimp_LogComment(va("----- Skipping shadowCube side: %i -----\n", cubeSide)); PlaneIntersectRay(viewOrigin, viewDirection, splitFrustum[FRUSTUM_FAR], rayIntersectionFar); zFar = Distance(viewOrigin, rayIntersectionFar); VectorInverse(viewDirection); PlaneIntersectRay(rayIntersectionFar, viewDirection,splitFrustum[FRUSTUM_NEAR], rayIntersectionNear); zNear = Distance(viewOrigin, rayIntersectionNear); VectorInverse(viewDirection); GLimp_LogComment(va("split frustum %i: near = %5.3f, far = %5.3f\n", splitFrustumIndex, zNear, zFar)); GLimp_LogComment(va("pyramid nearCorners\n")); for(j = 0; j < 4; j++) { GLimp_LogComment(va("(%5.3f, %5.3f, %5.3f)\n", splitFrustumCorners[j][0], splitFrustumCorners[j][1], splitFrustumCorners[j][2])); } GLimp_LogComment(va("pyramid farCorners\n")); for(j = 4; j < 8; j++) { GLimp_LogComment(va("(%5.3f, %5.3f, %5.3f)\n", splitFrustumCorners[j][0], splitFrustumCorners[j][1], splitFrustumCorners[j][2])); } } ClearBounds(splitFrustumBounds[0], splitFrustumBounds[1]); for(j = 0; j < 8; j++) { AddPointToBounds(splitFrustumCorners[j], splitFrustumBounds[0], splitFrustumBounds[1]); } #if 0 // // Scene-Independent Projection // // find the bounding box of the current split in the light's view space ClearBounds(splitFrustumViewBounds[0], splitFrustumViewBounds[1]); //numCasters = MergeInteractionBounds(light->viewMatrix, ia, iaCount, splitFrustumViewBounds, qtrue); for(j = 0; j < 8; j++) { VectorCopy(splitFrustumCorners[j], point); point[3] = 1; MatrixTransform4(light->viewMatrix, point, transf); transf[0] /= transf[3]; transf[1] /= transf[3]; transf[2] /= transf[3]; AddPointToBounds(transf, splitFrustumViewBounds[0], splitFrustumViewBounds[1]); } //MatrixScaleTranslateToUnitCube(projectionMatrix, splitFrustumViewBounds[0], splitFrustumViewBounds[1]); //MatrixOrthogonalProjectionRH(projectionMatrix, -1, 1, -1, 1, -splitFrustumViewBounds[1][2], -splitFrustumViewBounds[0][2]); #if 1 MatrixOrthogonalProjectionRH(projectionMatrix, splitFrustumViewBounds[0][0], splitFrustumViewBounds[1][0], splitFrustumViewBounds[0][1], splitFrustumViewBounds[1][1], -splitFrustumViewBounds[1][2], -splitFrustumViewBounds[0][2]); #endif Matrix4x4Multiply(projectionMatrix, light->viewMatrix, viewProjectionMatrix); // find the bounding box of the current split in the light's clip space ClearBounds(splitFrustumClipBounds[0], splitFrustumClipBounds[1]); for(j = 0; j < 8; j++) { VectorCopy(splitFrustumCorners[j], point); point[3] = 1; MatrixTransform4(viewProjectionMatrix, point, transf); transf[0] /= transf[3]; transf[1] /= transf[3]; transf[2] /= transf[3]; AddPointToBounds(transf, splitFrustumClipBounds[0], splitFrustumClipBounds[1]); } splitFrustumClipBounds[0][2] = 0; splitFrustumClipBounds[1][2] = 1; MatrixCrop(cropMatrix, splitFrustumClipBounds[0], splitFrustumClipBounds[1]); //MatrixIdentity(cropMatrix); if(r_logFile->integer) { GLimp_LogComment(va("split frustum light view space bounds (%5.3f, %5.3f, %5.3f) (%5.3f, %5.3f, %5.3f)\n", splitFrustumViewBounds[0][0], splitFrustumViewBounds[0][1], splitFrustumViewBounds[0][2], splitFrustumViewBounds[1][0], splitFrustumViewBounds[1][1], splitFrustumViewBounds[1][2])); GLimp_LogComment(va("split frustum light clip space bounds (%5.3f, %5.3f, %5.3f) (%5.3f, %5.3f, %5.3f)\n", splitFrustumClipBounds[0][0], splitFrustumClipBounds[0][1], splitFrustumClipBounds[0][2], splitFrustumClipBounds[1][0], splitFrustumClipBounds[1][1], splitFrustumClipBounds[1][2])); } #else // // Scene-Dependent Projection // // find the bounding box of the current split in the light's view space ClearBounds(cropBounds[0], cropBounds[1]); for(j = 0; j < 8; j++) { VectorCopy(splitFrustumCorners[j], point); point[3] = 1; #if 1 MatrixTransform4(light->viewMatrix, point, transf); transf[0] /= transf[3]; transf[1] /= transf[3]; transf[2] /= transf[3]; #else MatrixTransformPoint(light->viewMatrix, point, transf); #endif AddPointToBounds(transf, cropBounds[0], cropBounds[1]); } MatrixOrthogonalProjectionRH(projectionMatrix, cropBounds[0][0], cropBounds[1][0], cropBounds[0][1], cropBounds[1][1], -cropBounds[1][2], -cropBounds[0][2]); Matrix4x4Multiply(projectionMatrix, light->viewMatrix, viewProjectionMatrix); numCasters = MergeInteractionBounds(viewProjectionMatrix, ia, iaCount, casterBounds, qtrue); MergeInteractionBounds(viewProjectionMatrix, ia, iaCount, receiverBounds, qfalse); // find the bounding box of the current split in the light's clip space ClearBounds(splitFrustumClipBounds[0], splitFrustumClipBounds[1]); for(j = 0; j < 8; j++) { VectorCopy(splitFrustumCorners[j], point); point[3] = 1; MatrixTransform4(viewProjectionMatrix, point, transf); transf[0] /= transf[3]; transf[1] /= transf[3]; transf[2] /= transf[3]; AddPointToBounds(transf, splitFrustumClipBounds[0], splitFrustumClipBounds[1]); } if(r_logFile->integer) { GLimp_LogComment(va("shadow casters = %i\n", numCasters)); GLimp_LogComment(va("split frustum light space clip bounds (%5.3f, %5.3f, %5.3f) (%5.3f, %5.3f, %5.3f)\n", splitFrustumClipBounds[0][0], splitFrustumClipBounds[0][1], splitFrustumClipBounds[0][2], splitFrustumClipBounds[1][0], splitFrustumClipBounds[1][1], splitFrustumClipBounds[1][2])); GLimp_LogComment(va("shadow caster light space clip bounds (%5.3f, %5.3f, %5.3f) (%5.3f, %5.3f, %5.3f)\n", casterBounds[0][0], casterBounds[0][1], casterBounds[0][2], casterBounds[1][0], casterBounds[1][1], casterBounds[1][2])); GLimp_LogComment(va("light receiver light space clip bounds (%5.3f, %5.3f, %5.3f) (%5.3f, %5.3f, %5.3f)\n", receiverBounds[0][0], receiverBounds[0][1], receiverBounds[0][2], receiverBounds[1][0], receiverBounds[1][1], receiverBounds[1][2])); } // scene-dependent bounding volume cropBounds[0][0] = Q_max(Q_max(casterBounds[0][0], receiverBounds[0][0]), splitFrustumClipBounds[0][0]); cropBounds[0][1] = Q_max(Q_max(casterBounds[0][1], receiverBounds[0][1]), splitFrustumClipBounds[0][1]); cropBounds[1][0] = Q_min(Q_min(casterBounds[1][0], receiverBounds[1][0]), splitFrustumClipBounds[1][0]); cropBounds[1][1] = Q_min(Q_min(casterBounds[1][1], receiverBounds[1][1]), splitFrustumClipBounds[1][1]); cropBounds[0][2] = Q_min(casterBounds[0][2], splitFrustumClipBounds[0][2]); //cropBounds[0][2] = casterBounds[0][2]; //cropBounds[0][2] = splitFrustumClipBounds[0][2]; cropBounds[1][2] = Q_min(receiverBounds[1][2], splitFrustumClipBounds[1][2]); //cropBounds[1][2] = splitFrustumClipBounds[1][2]; if(numCasters == 0) { VectorCopy(splitFrustumClipBounds[0], cropBounds[0]); VectorCopy(splitFrustumClipBounds[1], cropBounds[1]); } MatrixCrop(cropMatrix, cropBounds[0], cropBounds[1]); #endif Matrix4x4Multiply(cropMatrix, projectionMatrix, light->projectionMatrix); GL_LoadProjectionMatrix(light->projectionMatrix); } else { // original light direction is from surface to light VectorInverse(lightDirection); // Quake -> OpenGL view matrix from light perspective #if 1 vectoangles(lightDirection, angles); MatrixFromAngles(rotationMatrix, angles[PITCH], angles[YAW], angles[ROLL]); MatrixSetupTransformFromRotation(transformMatrix, rotationMatrix, backEnd.viewParms.orientation.origin); MatrixAffineInverse(transformMatrix, viewMatrix); Matrix4x4Multiply(quakeToOpenGLMatrix, viewMatrix, light->viewMatrix); #else MatrixLookAtRH(light->viewMatrix, backEnd.viewParms.orientation.origin, lightDirection, backEnd.viewParms.orientation.axis[0]); #endif ClearBounds(splitFrustumBounds[0], splitFrustumBounds[1]); //BoundsAdd(splitFrustumBounds[0], splitFrustumBounds[1], backEnd.viewParms.visBounds[0], backEnd.viewParms.visBounds[1]); BoundsAdd(splitFrustumBounds[0], splitFrustumBounds[1], light->worldBounds[0], light->worldBounds[1]); ClearBounds(cropBounds[0], cropBounds[1]); for(j = 0; j < 8; j++) { point[0] = splitFrustumBounds[j & 1][0]; point[1] = splitFrustumBounds[(j >> 1) & 1][1]; point[2] = splitFrustumBounds[(j >> 2) & 1][2]; point[3] = 1; MatrixTransform4(light->viewMatrix, point, transf); transf[0] /= transf[3]; transf[1] /= transf[3]; transf[2] /= transf[3]; AddPointToBounds(transf, cropBounds[0], cropBounds[1]); } // transform from OpenGL's right handed into D3D's left handed coordinate system #if 0 MatrixScaleTranslateToUnitCube(projectionMatrix, cropBounds[0], cropBounds[1]); Matrix4x4Multiply(flipZMatrix, projectionMatrix, light->projectionMatrix); #else MatrixOrthogonalProjectionRH(light->projectionMatrix, cropBounds[0][0], cropBounds[1][0], cropBounds[0][1], cropBounds[1][1], -cropBounds[1][2], -cropBounds[0][2]); #endif GL_LoadProjectionMatrix(light->projectionMatrix); } break; } default: break; } } if(r_logFile->integer) { // don't just call LogComment, or we will get // a call to va() every frame! GLimp_LogComment(va("----- First Shadow Interaction: %i -----\n", iaCount)); } } else { GLimp_LogComment("--- Rendering lighting ---\n"); if(r_logFile->integer) { // don't just call LogComment, or we will get // a call to va() every frame! GLimp_LogComment(va("----- First Light Interaction: %i -----\n", iaCount)); } if(r_hdrRendering->integer) R_BindFBO(tr.deferredRenderFBO); else R_BindNullFBO(); // set the window clipping GL_Viewport(backEnd.viewParms.viewportX, backEnd.viewParms.viewportY, backEnd.viewParms.viewportWidth, backEnd.viewParms.viewportHeight); GL_Scissor(backEnd.viewParms.viewportX, backEnd.viewParms.viewportY, backEnd.viewParms.viewportWidth, backEnd.viewParms.viewportHeight); // restore camera matrices GL_LoadProjectionMatrix(backEnd.viewParms.projectionMatrix); GL_LoadModelViewMatrix(backEnd.orientation.modelViewMatrix); // reset light view and projection matrices switch (light->l.rlType) { case RL_OMNI: { MatrixAffineInverse(light->transformMatrix, light->viewMatrix); MatrixSetupScale(light->projectionMatrix, 1.0 / light->l.radius[0], 1.0 / light->l.radius[1], 1.0 / light->l.radius[2]); break; } case RL_DIRECTIONAL: { // draw split frustum shadow maps if(r_showShadowMaps->integer) { int frustumIndex; float x, y, w, h; matrix_t ortho; vec4_t quadVerts[4]; // set 2D virtual screen size GL_PushMatrix(); MatrixOrthogonalProjection(ortho, backEnd.viewParms.viewportX, backEnd.viewParms.viewportX + backEnd.viewParms.viewportWidth, backEnd.viewParms.viewportY, backEnd.viewParms.viewportY + backEnd.viewParms.viewportHeight, -99999, 99999); GL_LoadProjectionMatrix(ortho); GL_LoadModelViewMatrix(matrixIdentity); for(frustumIndex = 0; frustumIndex <= r_parallelShadowSplits->integer; frustumIndex++) { GL_Cull(CT_TWO_SIDED); GL_State(GLS_DEPTHTEST_DISABLE); gl_debugShadowMapShader->BindProgram(); gl_debugShadowMapShader->SetUniform_ModelViewProjectionMatrix(glState.modelViewProjectionMatrix[glState.stackIndex]); GL_SelectTexture(0); GL_Bind(tr.sunShadowMapFBOImage[frustumIndex]); w = 200; h = 200; x = 205 * frustumIndex; y = 70; VectorSet4(quadVerts[0], x, y, 0, 1); VectorSet4(quadVerts[1], x + w, y, 0, 1); VectorSet4(quadVerts[2], x + w, y + h, 0, 1); VectorSet4(quadVerts[3], x, y + h, 0, 1); Tess_InstantQuad(quadVerts); { int j; vec4_t splitFrustum[6]; vec3_t farCorners[4]; vec3_t nearCorners[4]; GL_Viewport(x, y, w, h); GL_Scissor(x, y, w, h); GL_PushMatrix(); gl_genericShader->DisableAlphaTesting(); gl_genericShader->DisablePortalClipping(); gl_genericShader->DisableVertexSkinning(); gl_genericShader->DisableVertexAnimation(); gl_genericShader->DisableDeformVertexes(); gl_genericShader->DisableTCGenEnvironment(); gl_genericShader->BindProgram(); // set uniforms gl_genericShader->SetUniform_ColorModulate(CGEN_VERTEX, AGEN_VERTEX); gl_genericShader->SetUniform_Color(colorBlack); GL_State(GLS_POLYMODE_LINE | GLS_DEPTHTEST_DISABLE); GL_Cull(CT_TWO_SIDED); // bind u_ColorMap GL_SelectTexture(0); GL_Bind(tr.whiteImage); gl_genericShader->SetUniform_ColorTextureMatrix(matrixIdentity); gl_genericShader->SetUniform_ModelViewProjectionMatrix(light->shadowMatrices[frustumIndex]); tess.multiDrawPrimitives = 0; tess.numIndexes = 0; tess.numVertexes = 0; for(j = 0; j < 6; j++) { VectorCopy(backEnd.viewParms.frustums[1 + frustumIndex][j].normal, splitFrustum[j]); splitFrustum[j][3] = backEnd.viewParms.frustums[1 + frustumIndex][j].dist; } // calculate split frustum corner points PlanesGetIntersectionPoint(splitFrustum[FRUSTUM_LEFT], splitFrustum[FRUSTUM_TOP], splitFrustum[FRUSTUM_NEAR], nearCorners[0]); PlanesGetIntersectionPoint(splitFrustum[FRUSTUM_RIGHT], splitFrustum[FRUSTUM_TOP], splitFrustum[FRUSTUM_NEAR], nearCorners[1]); PlanesGetIntersectionPoint(splitFrustum[FRUSTUM_RIGHT], splitFrustum[FRUSTUM_BOTTOM], splitFrustum[FRUSTUM_NEAR], nearCorners[2]); PlanesGetIntersectionPoint(splitFrustum[FRUSTUM_LEFT], splitFrustum[FRUSTUM_BOTTOM], splitFrustum[FRUSTUM_NEAR], nearCorners[3]); PlanesGetIntersectionPoint(splitFrustum[FRUSTUM_LEFT], splitFrustum[FRUSTUM_TOP], splitFrustum[FRUSTUM_FAR], farCorners[0]); PlanesGetIntersectionPoint(splitFrustum[FRUSTUM_RIGHT], splitFrustum[FRUSTUM_TOP], splitFrustum[FRUSTUM_FAR], farCorners[1]); PlanesGetIntersectionPoint(splitFrustum[FRUSTUM_RIGHT], splitFrustum[FRUSTUM_BOTTOM], splitFrustum[FRUSTUM_FAR], farCorners[2]); PlanesGetIntersectionPoint(splitFrustum[FRUSTUM_LEFT], splitFrustum[FRUSTUM_BOTTOM], splitFrustum[FRUSTUM_FAR], farCorners[3]); // draw outer surfaces for(j = 0; j < 4; j++) { VectorSet4(quadVerts[0], nearCorners[j][0], nearCorners[j][1], nearCorners[j][2], 1); VectorSet4(quadVerts[1], farCorners[j][0], farCorners[j][1], farCorners[j][2], 1); VectorSet4(quadVerts[2], farCorners[(j + 1) % 4][0], farCorners[(j + 1) % 4][1], farCorners[(j + 1) % 4][2], 1); VectorSet4(quadVerts[3], nearCorners[(j + 1) % 4][0], nearCorners[(j + 1) % 4][1], nearCorners[(j + 1) % 4][2], 1); Tess_AddQuadStamp2(quadVerts, colorCyan); } // draw far cap VectorSet4(quadVerts[0], farCorners[3][0], farCorners[3][1], farCorners[3][2], 1); VectorSet4(quadVerts[1], farCorners[2][0], farCorners[2][1], farCorners[2][2], 1); VectorSet4(quadVerts[2], farCorners[1][0], farCorners[1][1], farCorners[1][2], 1); VectorSet4(quadVerts[3], farCorners[0][0], farCorners[0][1], farCorners[0][2], 1); Tess_AddQuadStamp2(quadVerts, colorBlue); // draw near cap VectorSet4(quadVerts[0], nearCorners[0][0], nearCorners[0][1], nearCorners[0][2], 1); VectorSet4(quadVerts[1], nearCorners[1][0], nearCorners[1][1], nearCorners[1][2], 1); VectorSet4(quadVerts[2], nearCorners[2][0], nearCorners[2][1], nearCorners[2][2], 1); VectorSet4(quadVerts[3], nearCorners[3][0], nearCorners[3][1], nearCorners[3][2], 1); Tess_AddQuadStamp2(quadVerts, colorGreen); Tess_UpdateVBOs(ATTR_POSITION | ATTR_COLOR); Tess_DrawElements(); // draw light volume if(light->isStatic && light->frustumVBO && light->frustumIBO) { gl_genericShader->SetUniform_ColorModulate(CGEN_CUSTOM_RGB, AGEN_CUSTOM); gl_genericShader->SetUniform_Color(colorYellow); R_BindVBO(light->frustumVBO); R_BindIBO(light->frustumIBO); GL_VertexAttribsState(ATTR_POSITION); tess.numVertexes = light->frustumVerts; tess.numIndexes = light->frustumIndexes; Tess_DrawElements(); } tess.multiDrawPrimitives = 0; tess.numIndexes = 0; tess.numVertexes = 0; GL_PopMatrix(); GL_Viewport(backEnd.viewParms.viewportX, backEnd.viewParms.viewportY, backEnd.viewParms.viewportWidth, backEnd.viewParms.viewportHeight); GL_Scissor(backEnd.viewParms.viewportX, backEnd.viewParms.viewportY, backEnd.viewParms.viewportWidth, backEnd.viewParms.viewportHeight); } } GL_PopMatrix(); } } default: break; } } } // end if(iaCount == iaFirst) if(drawShadows) { if(entity->e.renderfx & (RF_DEPTHHACK)) { goto skipInteraction; } if(shader->isSky) { goto skipInteraction; } if(shader->sort > SS_OPAQUE) { goto skipInteraction; } if(shader->noShadows || light->l.noShadows || light->shadowLOD < 0) { goto skipInteraction; } /* if(light->l.inverseShadows && (entity == &tr.worldEntity)) { // this light only casts shadows by its player and their items goto skipInteraction; } */ if(ia->type == IA_LIGHTONLY) { goto skipInteraction; } if(light->l.rlType == RL_OMNI && !(ia->cubeSideBits & (1 << cubeSide))) { goto skipInteraction; } switch (light->l.rlType) { case RL_OMNI: case RL_PROJ: case RL_DIRECTIONAL: { if(light == oldLight && entity == oldEntity && (alphaTest ? shader == oldShader : alphaTest == oldAlphaTest) && deformType == oldDeformType) { if(r_logFile->integer) { // don't just call LogComment, or we will get // a call to va() every frame! GLimp_LogComment(va("----- Batching Shadow Interaction: %i -----\n", iaCount)); } // fast path, same as previous rb_surfaceTable[*surface] (surface); goto nextInteraction; } else { if(oldLight) { // draw the contents of the last shader batch Tess_End(); } if(r_logFile->integer) { // don't just call LogComment, or we will get // a call to va() every frame! GLimp_LogComment(va("----- Beginning Shadow Interaction: %i -----\n", iaCount)); } // we don't need tangent space calculations here Tess_Begin(Tess_StageIteratorShadowFill, NULL, shader, light->shader, qtrue, qfalse, -1, 0); } break; } default: break; } } else { if(!shader->interactLight) { goto skipInteraction; } if(ia->type == IA_SHADOWONLY) { goto skipInteraction; } if(glConfig2.occlusionQueryBits && glConfig.driverType != GLDRV_MESA && r_dynamicEntityOcclusionCulling->integer && !entity->occlusionQuerySamples) { goto skipInteraction; } if(light == oldLight && entity == oldEntity && shader == oldShader) { if(r_logFile->integer) { // don't just call LogComment, or we will get // a call to va() every frame! GLimp_LogComment(va("----- Batching Light Interaction: %i -----\n", iaCount)); } // fast path, same as previous rb_surfaceTable[*surface] (surface); goto nextInteraction; } else { if(oldLight) { // draw the contents of the last shader batch Tess_End(); } if(r_logFile->integer) { // don't just call LogComment, or we will get // a call to va() every frame! GLimp_LogComment(va("----- Beginning Light Interaction: %i -----\n", iaCount)); } // begin a new batch Tess_Begin(Tess_StageIteratorLighting, NULL, shader, light->shader, light->l.inverseShadows, qfalse, -1, 0); } } // change the modelview matrix if needed if(entity != oldEntity) { depthRange = qfalse; if(entity != &tr.worldEntity) { // set up the transformation matrix if(drawShadows) { R_RotateEntityForLight(entity, light, &backEnd.orientation); } else { R_RotateEntityForViewParms(entity, &backEnd.viewParms, &backEnd.orientation); } if(entity->e.renderfx & RF_DEPTHHACK) { // hack the depth range to prevent view model from poking into walls depthRange = qtrue; } } else { // set up the transformation matrix if(drawShadows) { Com_Memset(&backEnd.orientation, 0, sizeof(backEnd.orientation)); backEnd.orientation.axis[0][0] = 1; backEnd.orientation.axis[1][1] = 1; backEnd.orientation.axis[2][2] = 1; VectorCopy(light->l.origin, backEnd.orientation.viewOrigin); MatrixIdentity(backEnd.orientation.transformMatrix); //MatrixAffineInverse(backEnd.orientation.transformMatrix, backEnd.orientation.viewMatrix); Matrix4x4Multiply(light->viewMatrix, backEnd.orientation.transformMatrix, backEnd.orientation.viewMatrix); MatrixCopy(backEnd.orientation.viewMatrix, backEnd.orientation.modelViewMatrix); } else { // transform by the camera placement backEnd.orientation = backEnd.viewParms.world; } } GL_LoadModelViewMatrix(backEnd.orientation.modelViewMatrix); // change depthrange if needed if(oldDepthRange != depthRange) { if(depthRange) { glDepthRange(0, 0.3); } else { glDepthRange(0, 1); } oldDepthRange = depthRange; } } // change the attenuation matrix if needed if(light != oldLight || entity != oldEntity) { // transform light origin into model space for u_LightOrigin parameter if(entity != &tr.worldEntity) { VectorSubtract(light->origin, backEnd.orientation.origin, tmp); light->transformed[0] = DotProduct(tmp, backEnd.orientation.axis[0]); light->transformed[1] = DotProduct(tmp, backEnd.orientation.axis[1]); light->transformed[2] = DotProduct(tmp, backEnd.orientation.axis[2]); } else { VectorCopy(light->origin, light->transformed); } Matrix4x4Multiply(light->viewMatrix, backEnd.orientation.transformMatrix, modelToLight); // build the attenuation matrix using the entity transform switch (light->l.rlType) { case RL_OMNI: { MatrixSetupTranslation(light->attenuationMatrix, 0.5, 0.5, 0.5); // bias MatrixMultiplyScale(light->attenuationMatrix, 0.5, 0.5, 0.5); // scale MatrixMultiply2(light->attenuationMatrix, light->projectionMatrix); MatrixMultiply2(light->attenuationMatrix, modelToLight); MatrixCopy(light->attenuationMatrix, light->shadowMatrices[0]); break; } case RL_PROJ: { MatrixSetupTranslation(light->attenuationMatrix, 0.5, 0.5, 0.0); // bias MatrixMultiplyScale(light->attenuationMatrix, 0.5, 0.5, 1.0 / Q_min(light->falloffLength, 1.0)); // scale MatrixMultiply2(light->attenuationMatrix, light->projectionMatrix); MatrixMultiply2(light->attenuationMatrix, modelToLight); MatrixCopy(light->attenuationMatrix, light->shadowMatrices[0]); break; } case RL_DIRECTIONAL: { MatrixSetupTranslation(light->attenuationMatrix, 0.5, 0.5, 0.5); // bias MatrixMultiplyScale(light->attenuationMatrix, 0.5, 0.5, 0.5); // scale MatrixMultiply2(light->attenuationMatrix, light->projectionMatrix); MatrixMultiply2(light->attenuationMatrix, modelToLight); break; } } } if(drawShadows) { switch (light->l.rlType) { case RL_OMNI: case RL_PROJ: case RL_DIRECTIONAL: { // add the triangles for this surface rb_surfaceTable[*surface] (surface); break; } default: break; } } else { // add the triangles for this surface rb_surfaceTable[*surface] (surface); } nextInteraction: // remember values oldLight = light; oldEntity = entity; oldShader = shader; oldAlphaTest = alphaTest; oldDeformType = deformType; skipInteraction: if(!ia->next) { // if ia->next does not point to any other interaction then // this is the last interaction of the current light if(r_logFile->integer) { // don't just call LogComment, or we will get // a call to va() every frame! GLimp_LogComment(va("----- Last Interaction: %i -----\n", iaCount)); } // draw the contents of the last shader batch Tess_End(); if(drawShadows) { switch (light->l.rlType) { case RL_OMNI: { if(cubeSide == 5) { cubeSide = 0; drawShadows = qfalse; } else { cubeSide++; } // jump back to first interaction of this light ia = &backEnd.viewParms.interactions[iaFirst]; iaCount = iaFirst; break; } case RL_PROJ: { // jump back to first interaction of this light and start lighting ia = &backEnd.viewParms.interactions[iaFirst]; iaCount = iaFirst; drawShadows = qfalse; break; } case RL_DIRECTIONAL: { // set shadow matrix including scale + offset MatrixCopy(bias, light->shadowMatricesBiased[splitFrustumIndex]); MatrixMultiply2(light->shadowMatricesBiased[splitFrustumIndex], light->projectionMatrix); MatrixMultiply2(light->shadowMatricesBiased[splitFrustumIndex], light->viewMatrix); Matrix4x4Multiply(light->projectionMatrix, light->viewMatrix, light->shadowMatrices[splitFrustumIndex]); if(r_parallelShadowSplits->integer) { if(splitFrustumIndex == r_parallelShadowSplits->integer) { splitFrustumIndex = 0; drawShadows = qfalse; } else { splitFrustumIndex++; } // jump back to first interaction of this light ia = &backEnd.viewParms.interactions[iaFirst]; iaCount = iaFirst; } else { // jump back to first interaction of this light and start lighting ia = &backEnd.viewParms.interactions[iaFirst]; iaCount = iaFirst; drawShadows = qfalse; } break; } default: break; } } else { #ifdef VOLUMETRIC_LIGHTING // draw the light volume if needed if(light->shader->volumetricLight) { Render_lightVolume(ia); } #endif if(iaCount < (backEnd.viewParms.numInteractions - 1)) { // jump to next interaction and start shadowing ia++; iaCount++; iaFirst = iaCount; drawShadows = qtrue; } else { // increase last time to leave for loop iaCount++; } } // force updates oldLight = NULL; oldEntity = NULL; oldShader = NULL; } else { // just continue ia = ia->next; iaCount++; } } // go back to the world modelview matrix GL_LoadModelViewMatrix(backEnd.viewParms.world.modelViewMatrix); if(depthRange) { glDepthRange(0, 1); } // reset scissor clamping GL_Scissor(backEnd.viewParms.viewportX, backEnd.viewParms.viewportY, backEnd.viewParms.viewportWidth, backEnd.viewParms.viewportHeight); // reset clear color GL_ClearColor(0.0f, 0.0f, 0.0f, 1.0f); GL_CheckErrors(); if(r_speeds->integer == RSPEEDS_SHADING_TIMES) { glFinish(); endTime = ri.Milliseconds(); backEnd.pc.c_forwardLightingTime = endTime - startTime; } } static void RB_RenderDrawSurfacesIntoGeometricBuffer() { trRefEntity_t *entity, *oldEntity; shader_t *shader, *oldShader; int lightmapNum, oldLightmapNum; qboolean depthRange, oldDepthRange; int i; drawSurf_t *drawSurf; int startTime = 0, endTime = 0; GLimp_LogComment("--- RB_RenderDrawSurfacesIntoGeometricBuffer ---\n"); if(r_speeds->integer == RSPEEDS_SHADING_TIMES) { glFinish(); startTime = ri.Milliseconds(); } // draw everything oldEntity = NULL; oldShader = NULL; oldLightmapNum = -1; oldDepthRange = qfalse; depthRange = qfalse; backEnd.currentLight = NULL; GL_CheckErrors(); for(i = 0, drawSurf = backEnd.viewParms.drawSurfs; i < backEnd.viewParms.numDrawSurfs; i++, drawSurf++) { // update locals entity = drawSurf->entity; shader = tr.sortedShaders[drawSurf->shaderNum]; lightmapNum = drawSurf->lightmapNum; // skip all translucent surfaces that don't matter for this pass if(shader->sort > SS_OPAQUE) { break; } /* if(DS_PREPASS_LIGHTING_ENABLED()) { if(entity == oldEntity && shader == oldShader) { // fast path, same as previous sort rb_surfaceTable[*drawSurf->surface] (drawSurf->surface); continue; } // change the tess parameters if needed // a "entityMergable" shader is a shader that can have surfaces from seperate // entities merged into a single batch, like smoke and blood puff sprites if(shader != oldShader || (entity != oldEntity && !shader->entityMergable)) { if(oldShader != NULL) { Tess_End(); } Tess_Begin(Tess_StageIteratorGBufferNormalsOnly, NULL, shader, NULL, qfalse, qfalse, -1, 0); oldShader = shader; oldLightmapNum = lightmapNum; } } else */ { if(entity == oldEntity && shader == oldShader && lightmapNum == oldLightmapNum) { // fast path, same as previous sort rb_surfaceTable[*drawSurf->surface] (drawSurf->surface); continue; } // change the tess parameters if needed // a "entityMergable" shader is a shader that can have surfaces from seperate // entities merged into a single batch, like smoke and blood puff sprites if(shader != oldShader || (entity != oldEntity && !shader->entityMergable)) { if(oldShader != NULL) { Tess_End(); } Tess_Begin(Tess_StageIteratorGBuffer, NULL, shader, NULL, qfalse, qfalse, lightmapNum, 0); oldShader = shader; oldLightmapNum = lightmapNum; } } // change the modelview matrix if needed if(entity != oldEntity) { depthRange = qfalse; if(entity != &tr.worldEntity) { backEnd.currentEntity = entity; // set up the transformation matrix R_RotateEntityForViewParms(backEnd.currentEntity, &backEnd.viewParms, &backEnd.orientation); if(backEnd.currentEntity->e.renderfx & RF_DEPTHHACK) { // hack the depth range to prevent view model from poking into walls depthRange = qtrue; } } else { backEnd.currentEntity = &tr.worldEntity; backEnd.orientation = backEnd.viewParms.world; } GL_LoadModelViewMatrix(backEnd.orientation.modelViewMatrix); // change depthrange if needed if(oldDepthRange != depthRange) { if(depthRange) { glDepthRange(0, 0.3); } else { glDepthRange(0, 1); } oldDepthRange = depthRange; } oldEntity = entity; } // add the triangles for this surface rb_surfaceTable[*drawSurf->surface] (drawSurf->surface); } // draw the contents of the last shader batch if(oldShader != NULL) { Tess_End(); } // go back to the world modelview matrix GL_LoadModelViewMatrix(backEnd.viewParms.world.modelViewMatrix); if(depthRange) { glDepthRange(0, 1); } // disable offscreen rendering R_BindNullFBO(); GL_CheckErrors(); if(r_speeds->integer == RSPEEDS_SHADING_TIMES) { glFinish(); endTime = ri.Milliseconds(); backEnd.pc.c_deferredGBufferTime = endTime - startTime; } } void RB_RenderInteractionsDeferred() { interaction_t *ia; int iaCount; trRefLight_t *light, *oldLight = NULL; shader_t *lightShader; shaderStage_t *attenuationXYStage; shaderStage_t *attenuationZStage; // int i; int j; //vec3_t viewOrigin; //vec3_t lightOrigin; vec4_t lightColor; matrix_t ortho; vec4_t quadVerts[4]; vec4_t lightFrustum[6]; int startTime = 0, endTime = 0; GLimp_LogComment("--- RB_RenderInteractionsDeferred ---\n"); if(r_skipLightBuffer->integer) return; if(r_speeds->integer == RSPEEDS_SHADING_TIMES) { glFinish(); startTime = ri.Milliseconds(); } GL_State(GLS_DEFAULT); // set the window clipping GL_Viewport(backEnd.viewParms.viewportX, backEnd.viewParms.viewportY, backEnd.viewParms.viewportWidth, backEnd.viewParms.viewportHeight); GL_Scissor(backEnd.viewParms.viewportX, backEnd.viewParms.viewportY, backEnd.viewParms.viewportWidth, backEnd.viewParms.viewportHeight); tess.multiDrawPrimitives = 0; tess.numIndexes = 0; tess.numVertexes = 0; R_BindNullVBO(); R_BindNullIBO(); R_BindFBO(tr.geometricRenderFBO); glDrawBuffers(1, geometricRenderTargets); // helper matrix for 2D rendering MatrixOrthogonalProjection(ortho, backEnd.viewParms.viewportX, backEnd.viewParms.viewportX + backEnd.viewParms.viewportWidth, backEnd.viewParms.viewportY, backEnd.viewParms.viewportY + backEnd.viewParms.viewportHeight, -99999, 99999); // loop trough all light interactions and render the light quad for each last interaction for(iaCount = 0, ia = &backEnd.viewParms.interactions[0]; iaCount < backEnd.viewParms.numInteractions;) { backEnd.currentLight = light = ia->light; if(glConfig2.occlusionQueryBits && glConfig.driverType != GLDRV_MESA && r_dynamicLightOcclusionCulling->integer && !ia->occlusionQuerySamples) { // skip all interactions of this light because it failed the occlusion query goto skipInteraction; } skipInteraction: if(!ia->next) { if((glConfig2.occlusionQueryBits && glConfig.driverType != GLDRV_MESA && r_dynamicLightOcclusionCulling->integer && ia->occlusionQuerySamples) || !r_dynamicLightOcclusionCulling->integer) { GL_CheckErrors(); GLimp_LogComment("--- Rendering lighting ---\n"); // build world to light space matrix switch (light->l.rlType) { case RL_OMNI: case RL_DIRECTIONAL: { // build the attenuation matrix MatrixSetupTranslation(light->attenuationMatrix, 0.5, 0.5, 0.5); // bias MatrixMultiplyScale(light->attenuationMatrix, 0.5, 0.5, 0.5); // scale MatrixMultiply2(light->attenuationMatrix, light->projectionMatrix); // light projection (frustum) MatrixMultiply2(light->attenuationMatrix, light->viewMatrix); break; } case RL_PROJ: { // build the attenuation matrix MatrixSetupTranslation(light->attenuationMatrix, 0.5, 0.5, 0.0); // bias MatrixMultiplyScale(light->attenuationMatrix, 0.5, 0.5, Q_min(light->falloffLength, 1.0)); // scale MatrixMultiply2(light->attenuationMatrix, light->projectionMatrix); MatrixMultiply2(light->attenuationMatrix, light->viewMatrix); break; } default: break; } if(light->clipsNearPlane) { // set 2D virtual screen size GL_PushMatrix(); GL_LoadProjectionMatrix(ortho); GL_LoadModelViewMatrix(matrixIdentity); for(j = 0; j < 6; j++) { VectorCopy(light->frustum[j].normal, lightFrustum[j]); lightFrustum[j][3] = light->frustum[j].dist; } } else { // prepare rendering of the light volume // either bind VBO or setup the tess struct #if 0 // FIXME check why this does not work here if(light->isStatic && light->frustumVBO && light->frustumIBO) { // render in world space backEnd.orientation = backEnd.viewParms.world; GL_LoadProjectionMatrix(backEnd.viewParms.projectionMatrix); GL_LoadModelViewMatrix(backEnd.viewParms.world.modelViewMatrix); R_BindVBO(light->frustumVBO); R_BindIBO(light->frustumIBO); GL_VertexAttribsState(ATTR_POSITION); tess.numVertexes = light->frustumVerts; tess.numIndexes = light->frustumIndexes; } else #endif { tess.multiDrawPrimitives = 0; tess.numIndexes = 0; tess.numVertexes = 0; switch (light->l.rlType) { case RL_OMNI: case RL_DIRECTIONAL: { #if 1 // render in light space R_RotateLightForViewParms(light, &backEnd.viewParms, &backEnd.orientation); GL_LoadProjectionMatrix(backEnd.viewParms.projectionMatrix); GL_LoadModelViewMatrix(backEnd.orientation.modelViewMatrix); Tess_AddCube(vec3_origin, light->localBounds[0], light->localBounds[1], colorWhite); Tess_UpdateVBOs(ATTR_POSITION | ATTR_COLOR); #else matrix_t transform, scale, rot; axis_t axis; MatrixSetupScale(scale, light->l.radius[0], light->l.radius[1], light->l.radius[2]); Matrix4x4Multiply(scale, light->transformMatrix, transform); GL_LoadModelViewMatrix(transform); GL_LoadProjectionMatrix(backEnd.viewParms.projectionMatrix); R_BindVBO(tr.unitCubeVBO); R_BindIBO(tr.unitCubeIBO); GL_VertexAttribsState(ATTR_POSITION); tess.multiDrawPrimitives = 0; tess.numVertexes = tr.unitCubeVBO->vertexesNum; tess.numIndexes = tr.unitCubeIBO->indexesNum; #endif break; } case RL_PROJ: { vec3_t farCorners[4]; vec4_t *frustum = light->localFrustum; // render in light space R_RotateLightForViewParms(light, &backEnd.viewParms, &backEnd.orientation); GL_LoadProjectionMatrix(backEnd.viewParms.projectionMatrix); GL_LoadModelViewMatrix(backEnd.orientation.modelViewMatrix); PlanesGetIntersectionPoint(frustum[FRUSTUM_LEFT], frustum[FRUSTUM_TOP], frustum[FRUSTUM_FAR], farCorners[0]); PlanesGetIntersectionPoint(frustum[FRUSTUM_RIGHT], frustum[FRUSTUM_TOP], frustum[FRUSTUM_FAR], farCorners[1]); PlanesGetIntersectionPoint(frustum[FRUSTUM_RIGHT], frustum[FRUSTUM_BOTTOM], frustum[FRUSTUM_FAR], farCorners[2]); PlanesGetIntersectionPoint(frustum[FRUSTUM_LEFT], frustum[FRUSTUM_BOTTOM], frustum[FRUSTUM_FAR], farCorners[3]); if(!VectorCompare(light->l.projStart, vec3_origin)) { vec3_t nearCorners[4]; // calculate the vertices defining the top area PlanesGetIntersectionPoint(frustum[FRUSTUM_LEFT], frustum[FRUSTUM_TOP], frustum[FRUSTUM_NEAR], nearCorners[0]); PlanesGetIntersectionPoint(frustum[FRUSTUM_RIGHT], frustum[FRUSTUM_TOP], frustum[FRUSTUM_NEAR], nearCorners[1]); PlanesGetIntersectionPoint(frustum[FRUSTUM_RIGHT], frustum[FRUSTUM_BOTTOM], frustum[FRUSTUM_NEAR], nearCorners[2]); PlanesGetIntersectionPoint(frustum[FRUSTUM_LEFT], frustum[FRUSTUM_BOTTOM], frustum[FRUSTUM_NEAR], nearCorners[3]); // draw outer surfaces for(j = 0; j < 4; j++) { VectorSet4(quadVerts[0], nearCorners[j][0], nearCorners[j][1], nearCorners[j][2], 1); VectorSet4(quadVerts[1], farCorners[j][0], farCorners[j][1], farCorners[j][2], 1); VectorSet4(quadVerts[2], farCorners[(j + 1) % 4][0], farCorners[(j + 1) % 4][1], farCorners[(j + 1) % 4][2], 1); VectorSet4(quadVerts[3], nearCorners[(j + 1) % 4][0], nearCorners[(j + 1) % 4][1], nearCorners[(j + 1) % 4][2], 1); Tess_AddQuadStamp2(quadVerts, colorCyan); } // draw far cap VectorSet4(quadVerts[0], farCorners[3][0], farCorners[3][1], farCorners[3][2], 1); VectorSet4(quadVerts[1], farCorners[2][0], farCorners[2][1], farCorners[2][2], 1); VectorSet4(quadVerts[2], farCorners[1][0], farCorners[1][1], farCorners[1][2], 1); VectorSet4(quadVerts[3], farCorners[0][0], farCorners[0][1], farCorners[0][2], 1); Tess_AddQuadStamp2(quadVerts, colorRed); // draw near cap VectorSet4(quadVerts[0], nearCorners[0][0], nearCorners[0][1], nearCorners[0][2], 1); VectorSet4(quadVerts[1], nearCorners[1][0], nearCorners[1][1], nearCorners[1][2], 1); VectorSet4(quadVerts[2], nearCorners[2][0], nearCorners[2][1], nearCorners[2][2], 1); VectorSet4(quadVerts[3], nearCorners[3][0], nearCorners[3][1], nearCorners[3][2], 1); Tess_AddQuadStamp2(quadVerts, colorGreen); } else { vec3_t top; // no light_start, just use the top vertex (doesn't need to be mirrored) PlanesGetIntersectionPoint(frustum[FRUSTUM_LEFT], frustum[FRUSTUM_RIGHT], frustum[FRUSTUM_TOP], top); // draw pyramid for(j = 0; j < 4; j++) { VectorCopy(top, tess.xyz[tess.numVertexes]); Vector4Copy(colorCyan, tess.colors[tess.numVertexes]); tess.indexes[tess.numIndexes++] = tess.numVertexes; tess.numVertexes++; VectorCopy(farCorners[(j + 1) % 4], tess.xyz[tess.numVertexes]); Vector4Copy(colorCyan, tess.colors[tess.numVertexes]); tess.indexes[tess.numIndexes++] = tess.numVertexes; tess.numVertexes++; VectorCopy(farCorners[j], tess.xyz[tess.numVertexes]); Vector4Copy(colorCyan, tess.colors[tess.numVertexes]); tess.indexes[tess.numIndexes++] = tess.numVertexes; tess.numVertexes++; } VectorSet4(quadVerts[0], farCorners[0][0], farCorners[0][1], farCorners[0][2], 1); VectorSet4(quadVerts[1], farCorners[1][0], farCorners[1][1], farCorners[1][2], 1); VectorSet4(quadVerts[2], farCorners[2][0], farCorners[2][1], farCorners[2][2], 1); VectorSet4(quadVerts[3], farCorners[3][0], farCorners[3][1], farCorners[3][2], 1); Tess_AddQuadStamp2(quadVerts, colorRed); } Tess_UpdateVBOs(ATTR_POSITION | ATTR_COLOR); break; } default: break; } } } // last interaction of current light lightShader = light->shader; attenuationZStage = lightShader->stages[0]; for(j = 1; j < MAX_SHADER_STAGES; j++) { attenuationXYStage = lightShader->stages[j]; if(!attenuationXYStage) { break; } if(attenuationXYStage->type != ST_ATTENUATIONMAP_XY) { continue; } if(!RB_EvalExpression(&attenuationXYStage->ifExp, 1.0)) { continue; } Tess_ComputeColor(attenuationXYStage); if(VectorLength(tess.svars.color) < 0.01) { // don't render black lights } R_ComputeFinalAttenuation(attenuationXYStage, light); // set OpenGL state for additive lighting GL_State(GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE); if(light->clipsNearPlane) { GL_Cull(CT_TWO_SIDED); } else { GL_Cull(CT_FRONT_SIDED); } if(light->l.rlType == RL_OMNI) { // choose right shader program ---------------------------------- gl_deferredLightingShader_omniXYZ->SetPortalClipping(backEnd.viewParms.isPortal); gl_deferredLightingShader_omniXYZ->SetNormalMapping(r_normalMapping->integer); gl_deferredLightingShader_omniXYZ->SetShadowing(false); gl_deferredLightingShader_omniXYZ->SetFrustumClipping(light->clipsNearPlane); gl_deferredLightingShader_omniXYZ->BindProgram(); // end choose right shader program ------------------------------ gl_deferredLightingShader_omniXYZ->SetUniform_ViewOrigin(backEnd.viewParms.orientation.origin); // in world space gl_deferredLightingShader_omniXYZ->SetUniform_LightOrigin(light->origin); gl_deferredLightingShader_omniXYZ->SetUniform_LightColor(tess.svars.color); gl_deferredLightingShader_omniXYZ->SetUniform_LightRadius(light->sphereRadius); gl_deferredLightingShader_omniXYZ->SetUniform_LightScale(light->l.scale); gl_deferredLightingShader_omniXYZ->SetUniform_LightAttenuationMatrix(light->attenuationMatrix2); gl_deferredLightingShader_omniXYZ->SetUniform_LightFrustum(lightFrustum); gl_deferredLightingShader_omniXYZ->SetUniform_ModelViewProjectionMatrix(glState.modelViewProjectionMatrix[glState.stackIndex]); gl_deferredLightingShader_omniXYZ->SetUniform_UnprojectMatrix(backEnd.viewParms.unprojectionMatrix); if(backEnd.viewParms.isPortal) { float plane[4]; // clipping plane in world space plane[0] = backEnd.viewParms.portalPlane.normal[0]; plane[1] = backEnd.viewParms.portalPlane.normal[1]; plane[2] = backEnd.viewParms.portalPlane.normal[2]; plane[3] = backEnd.viewParms.portalPlane.dist; gl_deferredLightingShader_omniXYZ->SetUniform_PortalPlane(plane); } // bind u_DiffuseMap GL_SelectTexture(0); GL_Bind(tr.deferredDiffuseFBOImage); // bind u_NormalMap GL_SelectTexture(1); GL_Bind(tr.deferredNormalFBOImage); if(r_normalMapping->integer) { // bind u_SpecularMap GL_SelectTexture(2); GL_Bind(tr.deferredSpecularFBOImage); } // bind u_DepthMap GL_SelectTexture(3); GL_Bind(tr.depthRenderImage); // bind u_AttenuationMapXY GL_SelectTexture(4); BindAnimatedImage(&attenuationXYStage->bundle[TB_COLORMAP]); // bind u_AttenuationMapZ GL_SelectTexture(5); BindAnimatedImage(&attenuationZStage->bundle[TB_COLORMAP]); if(light->clipsNearPlane) { // draw lighting with a fullscreen quad Tess_InstantQuad(backEnd.viewParms.viewportVerts); /* VectorSet4(quadVerts[0], ia->scissorX, ia->scissorY, 0, 1); VectorSet4(quadVerts[1], ia->scissorX + ia->scissorWidth - 1, ia->scissorY, 0, 1); VectorSet4(quadVerts[2], ia->scissorX + ia->scissorWidth - 1, ia->scissorY + ia->scissorHeight - 1, 0, 1); VectorSet4(quadVerts[3], ia->scissorX, ia->scissorY + ia->scissorHeight - 1, 0, 1); Tess_InstantQuad(quadVerts); */ } else { // draw the volume Tess_DrawElements(); } } else if(light->l.rlType == RL_PROJ) { // choose right shader program ---------------------------------- gl_deferredLightingShader_projXYZ->SetPortalClipping(backEnd.viewParms.isPortal); gl_deferredLightingShader_projXYZ->SetNormalMapping(r_normalMapping->integer); gl_deferredLightingShader_projXYZ->SetShadowing(false); gl_deferredLightingShader_projXYZ->SetFrustumClipping(light->clipsNearPlane); gl_deferredLightingShader_projXYZ->BindProgram(); // end choose right shader program ------------------------------ gl_deferredLightingShader_projXYZ->SetUniform_ViewOrigin(backEnd.viewParms.orientation.origin); // in world space gl_deferredLightingShader_projXYZ->SetUniform_LightOrigin(light->origin); gl_deferredLightingShader_projXYZ->SetUniform_LightColor(tess.svars.color); gl_deferredLightingShader_projXYZ->SetUniform_LightRadius(light->sphereRadius); gl_deferredLightingShader_projXYZ->SetUniform_LightScale(light->l.scale); gl_deferredLightingShader_projXYZ->SetUniform_LightAttenuationMatrix(light->attenuationMatrix2); gl_deferredLightingShader_projXYZ->SetUniform_LightFrustum(lightFrustum); gl_deferredLightingShader_projXYZ->SetUniform_ModelViewProjectionMatrix(glState.modelViewProjectionMatrix[glState.stackIndex]); gl_deferredLightingShader_projXYZ->SetUniform_UnprojectMatrix(backEnd.viewParms.unprojectionMatrix); if(backEnd.viewParms.isPortal) { float plane[4]; // clipping plane in world space plane[0] = backEnd.viewParms.portalPlane.normal[0]; plane[1] = backEnd.viewParms.portalPlane.normal[1]; plane[2] = backEnd.viewParms.portalPlane.normal[2]; plane[3] = backEnd.viewParms.portalPlane.dist; gl_deferredLightingShader_projXYZ->SetUniform_PortalPlane(plane); } // bind u_DiffuseMap GL_SelectTexture(0); GL_Bind(tr.deferredDiffuseFBOImage); // bind u_NormalMap GL_SelectTexture(1); GL_Bind(tr.deferredNormalFBOImage); if(r_normalMapping->integer) { // bind u_SpecularMap GL_SelectTexture(2); GL_Bind(tr.deferredSpecularFBOImage); } // bind u_DepthMap GL_SelectTexture(3); GL_Bind(tr.depthRenderImage); // bind u_AttenuationMapXY GL_SelectTexture(4); BindAnimatedImage(&attenuationXYStage->bundle[TB_COLORMAP]); // bind u_AttenuationMapZ GL_SelectTexture(5); BindAnimatedImage(&attenuationZStage->bundle[TB_COLORMAP]); if(light->clipsNearPlane) { // draw lighting with a fullscreen quad Tess_InstantQuad(backEnd.viewParms.viewportVerts); /* VectorSet4(quadVerts[0], ia->scissorX, ia->scissorY, 0, 1); VectorSet4(quadVerts[1], ia->scissorX + ia->scissorWidth - 1, ia->scissorY, 0, 1); VectorSet4(quadVerts[2], ia->scissorX + ia->scissorWidth - 1, ia->scissorY + ia->scissorHeight - 1, 0, 1); VectorSet4(quadVerts[3], ia->scissorX, ia->scissorY + ia->scissorHeight - 1, 0, 1); Tess_InstantQuad(quadVerts); */ } else { // draw the volume Tess_DrawElements(); } } else if(light->l.rlType == RL_DIRECTIONAL) { // choose right shader program ---------------------------------- gl_deferredLightingShader_directionalSun->SetPortalClipping(backEnd.viewParms.isPortal); gl_deferredLightingShader_directionalSun->SetNormalMapping(r_normalMapping->integer); gl_deferredLightingShader_directionalSun->SetShadowing(false); gl_deferredLightingShader_directionalSun->SetFrustumClipping(light->clipsNearPlane); gl_deferredLightingShader_directionalSun->BindProgram(); // end choose right shader program ------------------------------ gl_deferredLightingShader_directionalSun->SetUniform_ViewOrigin(backEnd.viewParms.orientation.origin); // in world space gl_deferredLightingShader_directionalSun->SetUniform_LightDir(tr.sunDirection); gl_deferredLightingShader_directionalSun->SetUniform_LightColor(tess.svars.color); gl_deferredLightingShader_directionalSun->SetUniform_LightRadius(light->sphereRadius); gl_deferredLightingShader_directionalSun->SetUniform_LightScale(light->l.scale); gl_deferredLightingShader_directionalSun->SetUniform_LightAttenuationMatrix(light->attenuationMatrix2); gl_deferredLightingShader_directionalSun->SetUniform_LightFrustum(lightFrustum); gl_deferredLightingShader_directionalSun->SetUniform_ModelViewProjectionMatrix(glState.modelViewProjectionMatrix[glState.stackIndex]); gl_deferredLightingShader_directionalSun->SetUniform_UnprojectMatrix(backEnd.viewParms.unprojectionMatrix); if(backEnd.viewParms.isPortal) { float plane[4]; // clipping plane in world space plane[0] = backEnd.viewParms.portalPlane.normal[0]; plane[1] = backEnd.viewParms.portalPlane.normal[1]; plane[2] = backEnd.viewParms.portalPlane.normal[2]; plane[3] = backEnd.viewParms.portalPlane.dist; gl_deferredLightingShader_directionalSun->SetUniform_PortalPlane(plane); } // bind u_DiffuseMap GL_SelectTexture(0); GL_Bind(tr.deferredDiffuseFBOImage); // bind u_NormalMap GL_SelectTexture(1); GL_Bind(tr.deferredNormalFBOImage); if(r_normalMapping->integer) { // bind u_SpecularMap GL_SelectTexture(2); GL_Bind(tr.deferredSpecularFBOImage); } // bind u_DepthMap GL_SelectTexture(3); GL_Bind(tr.depthRenderImage); // bind u_AttenuationMapXY //GL_SelectTexture(4); //BindAnimatedImage(&attenuationXYStage->bundle[TB_COLORMAP]); // bind u_AttenuationMapZ //GL_SelectTexture(5); //BindAnimatedImage(&attenuationZStage->bundle[TB_COLORMAP]); if(light->clipsNearPlane) { // draw lighting with a fullscreen quad Tess_InstantQuad(backEnd.viewParms.viewportVerts); /* VectorSet4(quadVerts[0], ia->scissorX, ia->scissorY, 0, 1); VectorSet4(quadVerts[1], ia->scissorX + ia->scissorWidth - 1, ia->scissorY, 0, 1); VectorSet4(quadVerts[2], ia->scissorX + ia->scissorWidth - 1, ia->scissorY + ia->scissorHeight - 1, 0, 1); VectorSet4(quadVerts[3], ia->scissorX, ia->scissorY + ia->scissorHeight - 1, 0, 1); Tess_InstantQuad(quadVerts); */ } else { // draw the volume Tess_DrawElements(); } } } if(light->clipsNearPlane) { GL_PopMatrix(); } } if(iaCount < (backEnd.viewParms.numInteractions - 1)) { // jump to next interaction and continue ia++; iaCount++; } else { // increase last time to leave for loop iaCount++; } } else { // just continue ia = ia->next; iaCount++; } oldLight = light; } // clear shader so we can tell we don't have any unclosed surfaces tess.multiDrawPrimitives = 0; tess.numIndexes = 0; tess.numVertexes = 0; R_BindNullVBO(); R_BindNullIBO(); // go back to the world modelview matrix backEnd.orientation = backEnd.viewParms.world; GL_LoadProjectionMatrix(backEnd.viewParms.projectionMatrix); GL_LoadModelViewMatrix(backEnd.viewParms.world.modelViewMatrix); // reset scissor GL_Scissor(backEnd.viewParms.viewportX, backEnd.viewParms.viewportY, backEnd.viewParms.viewportWidth, backEnd.viewParms.viewportHeight); GL_CheckErrors(); if(r_speeds->integer == RSPEEDS_SHADING_TIMES) { glFinish(); endTime = ri.Milliseconds(); backEnd.pc.c_deferredLightingTime = endTime - startTime; } } static void RB_RenderInteractionsDeferredShadowMapped() { interaction_t *ia; int iaCount; int iaFirst; shader_t *shader, *oldShader; trRefEntity_t *entity, *oldEntity; trRefLight_t *light, *oldLight; surfaceType_t *surface; qboolean depthRange, oldDepthRange; qboolean alphaTest, oldAlphaTest; deformType_t deformType, oldDeformType; qboolean drawShadows; int cubeSide; int splitFrustumIndex; const matrix_t bias = { 0.5, 0.0, 0.0, 0.0, 0.0, 0.5, 0.0, 0.0, 0.0, 0.0, 0.5, 0.0, 0.5, 0.5, 0.5, 1.0}; shader_t *lightShader; shaderStage_t *attenuationXYStage; shaderStage_t *attenuationZStage; int i, j; //vec3_t viewOrigin; //vec3_t lightOrigin; vec3_t lightDirection; vec4_t lightColor; qboolean shadowCompare; matrix_t ortho; vec4_t quadVerts[4]; vec4_t lightFrustum[6]; int startTime = 0, endTime = 0; GLimp_LogComment("--- RB_RenderInteractionsDeferredShadowMapped ---\n"); if(r_skipLightBuffer->integer) return; if(r_speeds->integer == RSPEEDS_SHADING_TIMES) { glFinish(); startTime = ri.Milliseconds(); } oldLight = NULL; oldEntity = NULL; oldShader = NULL; oldDepthRange = depthRange = qfalse; oldAlphaTest = alphaTest = qfalse; oldDeformType = deformType = DEFORM_TYPE_NONE; drawShadows = qtrue; cubeSide = 0; splitFrustumIndex = 0; GL_State(GLS_DEFAULT); // set the window clipping GL_Viewport(backEnd.viewParms.viewportX, backEnd.viewParms.viewportY, backEnd.viewParms.viewportWidth, backEnd.viewParms.viewportHeight); GL_Scissor(backEnd.viewParms.viewportX, backEnd.viewParms.viewportY, backEnd.viewParms.viewportWidth, backEnd.viewParms.viewportHeight); // helper matrix for 2D rendering MatrixOrthogonalProjection(ortho, backEnd.viewParms.viewportX, backEnd.viewParms.viewportX + backEnd.viewParms.viewportWidth, backEnd.viewParms.viewportY, backEnd.viewParms.viewportY + backEnd.viewParms.viewportHeight, -99999, 99999); // if we need to clear the FBO color buffers then it should be white GL_ClearColor(1.0f, 1.0f, 1.0f, 1.0f); // render interactions for(iaCount = 0, iaFirst = 0, ia = &backEnd.viewParms.interactions[0]; iaCount < backEnd.viewParms.numInteractions;) { backEnd.currentLight = light = ia->light; backEnd.currentEntity = entity = ia->entity; surface = ia->surface; shader = ia->surfaceShader; alphaTest = shader->alphaTest; if(shader->numDeforms) { deformType = ShaderRequiresCPUDeforms(shader) ? DEFORM_TYPE_CPU : DEFORM_TYPE_GPU; } else { deformType = DEFORM_TYPE_NONE; } if(glConfig2.occlusionQueryBits && glConfig.driverType != GLDRV_MESA && r_dynamicLightOcclusionCulling->integer && !ia->occlusionQuerySamples) { // skip all interactions of this light because it failed the occlusion query goto skipInteraction; } // only iaCount == iaFirst if first iteration or counters were reset if(iaCount == iaFirst) { if(drawShadows) { // HACK: bring OpenGL into a safe state or strange FBO update problems will occur GL_BindProgram(NULL); GL_State(GLS_DEFAULT); //GL_VertexAttribsState(ATTR_POSITION); GL_SelectTexture(0); GL_Bind(tr.whiteImage); if(light->l.noShadows || light->shadowLOD < 0) { if(r_logFile->integer) { // don't just call LogComment, or we will get // a call to va() every frame! GLimp_LogComment(va("----- Skipping shadowCube side: %i -----\n", cubeSide)); } goto skipInteraction; } else { switch (light->l.rlType) { case RL_OMNI: { float zNear, zFar; float fovX, fovY; qboolean flipX, flipY; //float *proj; vec3_t angles; matrix_t rotationMatrix, transformMatrix, viewMatrix; if(r_logFile->integer) { // don't just call LogComment, or we will get // a call to va() every frame! GLimp_LogComment(va("----- Rendering shadowCube side: %i -----\n", cubeSide)); } R_BindFBO(tr.shadowMapFBO[light->shadowLOD]); R_AttachFBOTexture2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB + cubeSide, tr.shadowCubeFBOImage[light->shadowLOD]->texnum, 0); if(!r_ignoreGLErrors->integer) { R_CheckFBO(tr.shadowMapFBO[light->shadowLOD]); } // set the window clipping GL_Viewport(0, 0, shadowMapResolutions[light->shadowLOD], shadowMapResolutions[light->shadowLOD]); GL_Scissor(0, 0, shadowMapResolutions[light->shadowLOD], shadowMapResolutions[light->shadowLOD]); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); switch (cubeSide) { case 0: { // view parameters VectorSet(angles, 0, 0, 90); // projection parameters flipX = qfalse; flipY = qfalse; break; } case 1: { VectorSet(angles, 0, 180, 90); flipX = qtrue; flipY = qtrue; break; } case 2: { VectorSet(angles, 0, 90, 0); flipX = qfalse; flipY = qfalse; break; } case 3: { VectorSet(angles, 0, -90, 0); flipX = qtrue; flipY = qtrue; break; } case 4: { VectorSet(angles, -90, 90, 0); flipX = qfalse; flipY = qfalse; break; } case 5: { VectorSet(angles, 90, 90, 0); flipX = qtrue; flipY = qtrue; break; } default: { // shut up compiler VectorSet(angles, 0, 0, 0); flipX = qfalse; flipY = qfalse; break; } } // Quake -> OpenGL view matrix from light perspective MatrixFromAngles(rotationMatrix, angles[PITCH], angles[YAW], angles[ROLL]); MatrixSetupTransformFromRotation(transformMatrix, rotationMatrix, light->origin); MatrixAffineInverse(transformMatrix, viewMatrix); // convert from our coordinate system (looking down X) // to OpenGL's coordinate system (looking down -Z) Matrix4x4Multiply(quakeToOpenGLMatrix, viewMatrix, light->viewMatrix); // OpenGL projection matrix fovX = 90; fovY = 90; zNear = 1.0; zFar = light->sphereRadius; if(flipX) { fovX = -fovX; } if(flipY) { fovY = -fovY; } MatrixPerspectiveProjectionFovXYRH(light->projectionMatrix, fovX, fovY, zNear, zFar); GL_LoadProjectionMatrix(light->projectionMatrix); break; } case RL_PROJ: { GLimp_LogComment("--- Rendering projective shadowMap ---\n"); R_BindFBO(tr.shadowMapFBO[light->shadowLOD]); R_AttachFBOTexture2D(GL_TEXTURE_2D, tr.shadowMapFBOImage[light->shadowLOD]->texnum, 0); if(!r_ignoreGLErrors->integer) { R_CheckFBO(tr.shadowMapFBO[light->shadowLOD]); } // set the window clipping GL_Viewport(0, 0, shadowMapResolutions[light->shadowLOD], shadowMapResolutions[light->shadowLOD]); GL_Scissor(0, 0, shadowMapResolutions[light->shadowLOD], shadowMapResolutions[light->shadowLOD]); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); GL_LoadProjectionMatrix(light->projectionMatrix); break; } case RL_DIRECTIONAL: { vec3_t angles; vec4_t forward, side, up; vec3_t viewOrigin, viewDirection; matrix_t rotationMatrix, transformMatrix, viewMatrix, projectionMatrix, viewProjectionMatrix; matrix_t cropMatrix; vec4_t splitFrustum[6]; vec3_t splitFrustumCorners[8]; vec3_t splitFrustumBounds[2]; vec3_t splitFrustumViewBounds[2]; vec3_t splitFrustumClipBounds[2]; //float splitFrustumRadius; int numCasters; vec3_t casterBounds[2]; vec3_t receiverBounds[2]; vec3_t cropBounds[2]; vec4_t point; vec4_t transf; GLimp_LogComment("--- Rendering directional shadowMap ---\n"); R_BindFBO(tr.sunShadowMapFBO[splitFrustumIndex]); R_AttachFBOTexture2D(GL_TEXTURE_2D, tr.sunShadowMapFBOImage[splitFrustumIndex]->texnum, 0); if(!r_ignoreGLErrors->integer) { R_CheckFBO(tr.sunShadowMapFBO[splitFrustumIndex]); } // set the window clipping GL_Viewport(0, 0, sunShadowMapResolutions[splitFrustumIndex], sunShadowMapResolutions[splitFrustumIndex]); GL_Scissor(0, 0, sunShadowMapResolutions[splitFrustumIndex], sunShadowMapResolutions[splitFrustumIndex]); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); #if 1 VectorCopy(tr.sunDirection, lightDirection); #else VectorCopy(light->direction, lightDirection); #endif #if 0 if(r_lightSpacePerspectiveWarping->integer) { vec3_t viewOrigin, viewDirection; vec4_t forward, side, up; matrix_t lispMatrix; matrix_t postMatrix; matrix_t projectionCenter; const matrix_t switchToArticle = { 1, 0, 0, 0, 0, 0, 1, 0, 0, -1, 0, 0, 0, 0, 0, 1 }; const matrix_t switchToGL = { 1, 0, 0, 0, 0, 0, -1, 0, 0, 1, 0, 0, 0, 0, 0, 1 }; // original light direction is from surface to light VectorInverse(lightDirection); VectorNormalize(lightDirection); VectorCopy(backEnd.viewParms.orientation.origin, viewOrigin); VectorCopy(backEnd.viewParms.orientation.axis[0], viewDirection); VectorNormalize(viewDirection); // calculate new up dir CrossProduct(lightDirection, viewDirection, side); VectorNormalize(side); CrossProduct(side, lightDirection, up); VectorNormalize(up); #if 0 vectoangles(lightDirection, angles); MatrixFromAngles(rotationMatrix, angles[PITCH], angles[YAW], angles[ROLL]); AngleVectors(angles, forward, side, up); #endif MatrixLookAtRH(light->viewMatrix, viewOrigin, lightDirection, up); #if 0 ri.Printf(PRINT_ALL, "light = (%5.3f, %5.3f, %5.3f)\n", lightDirection[0], lightDirection[1], lightDirection[2]); ri.Printf(PRINT_ALL, "side = (%5.3f, %5.3f, %5.3f)\n", side[0], side[1], side[2]); ri.Printf(PRINT_ALL, "up = (%5.3f, %5.3f, %5.3f)\n", up[0], up[1], up[2]); #endif #if 0 for(j = 0; j < 6; j++) { VectorCopy(backEnd.viewParms.frustums[splitFrustumIndex][j].normal, splitFrustum[j]); splitFrustum[j][3] = backEnd.viewParms.frustums[splitFrustumIndex][j].dist; } PlanesGetIntersectionPoint(splitFrustum[FRUSTUM_LEFT], splitFrustum[FRUSTUM_TOP], splitFrustum[FRUSTUM_NEAR], splitFrustumCorners[0]); PlanesGetIntersectionPoint(splitFrustum[FRUSTUM_RIGHT], splitFrustum[FRUSTUM_TOP], splitFrustum[FRUSTUM_NEAR], splitFrustumCorners[1]); PlanesGetIntersectionPoint(splitFrustum[FRUSTUM_RIGHT], splitFrustum[FRUSTUM_BOTTOM], splitFrustum[FRUSTUM_NEAR], splitFrustumCorners[2]); PlanesGetIntersectionPoint(splitFrustum[FRUSTUM_LEFT], splitFrustum[FRUSTUM_BOTTOM], splitFrustum[FRUSTUM_NEAR], splitFrustumCorners[3]); #if 0 ri.Printf(PRINT_ALL, "split frustum %i\n", splitFrustumIndex); ri.Printf(PRINT_ALL, "pyramid nearCorners\n"); for(j = 0; j < 4; j++) { ri.Printf(PRINT_ALL, "(%5.3f, %5.3f, %5.3f)\n", splitFrustumCorners[j][0], splitFrustumCorners[j][1], splitFrustumCorners[j][2]); } #endif PlanesGetIntersectionPoint(splitFrustum[FRUSTUM_LEFT], splitFrustum[FRUSTUM_TOP], splitFrustum[FRUSTUM_FAR], splitFrustumCorners[4]); PlanesGetIntersectionPoint(splitFrustum[FRUSTUM_RIGHT], splitFrustum[FRUSTUM_TOP], splitFrustum[FRUSTUM_FAR], splitFrustumCorners[5]); PlanesGetIntersectionPoint(splitFrustum[FRUSTUM_RIGHT], splitFrustum[FRUSTUM_BOTTOM], splitFrustum[FRUSTUM_FAR], splitFrustumCorners[6]); PlanesGetIntersectionPoint(splitFrustum[FRUSTUM_LEFT], splitFrustum[FRUSTUM_BOTTOM], splitFrustum[FRUSTUM_FAR], splitFrustumCorners[7]); #if 0 ri.Printf(PRINT_ALL, "pyramid farCorners\n"); for(j = 4; j < 8; j++) { ri.Printf(PRINT_ALL, "(%5.3f, %5.3f, %5.3f)\n", splitFrustumCorners[j][0], splitFrustumCorners[j][1], splitFrustumCorners[j][2]); } #endif #endif ClearBounds(splitFrustumBounds[0], splitFrustumBounds[1]); #if 0 for(i = 0; i < 8; i++) { AddPointToBounds(splitFrustumCorners[i], splitFrustumBounds[0], splitFrustumBounds[1]); } #endif //BoundsAdd(splitFrustumBounds[0], splitFrustumBounds[1], backEnd.viewParms.visBounds[0], backEnd.viewParms.visBounds[1]); BoundsAdd(splitFrustumBounds[0], splitFrustumBounds[1], light->worldBounds[0], light->worldBounds[1]); ClearBounds(cropBounds[0], cropBounds[1]); for(j = 0; j < 8; j++) { point[0] = splitFrustumBounds[j & 1][0]; point[1] = splitFrustumBounds[(j >> 1) & 1][1]; point[2] = splitFrustumBounds[(j >> 2) & 1][2]; point[3] = 1; #if 1 MatrixTransform4(light->viewMatrix, point, transf); transf[0] /= transf[3]; transf[1] /= transf[3]; transf[2] /= transf[3]; #else MatrixTransformPoint(light->viewMatrix, point, transf); #endif AddPointToBounds(transf, cropBounds[0], cropBounds[1]); } #if 0 MatrixOrthogonalProjection(projectionMatrix, cropBounds[0][0], cropBounds[1][0], cropBounds[0][1], cropBounds[1][1], cropBounds[0][2], cropBounds[1][2]); Matrix4x4Multiply(projectionMatrix, light->viewMatrix, viewProjectionMatrix); Matrix4x4Multiply(viewProjectionMatrix, backEnd.viewParms.world.viewMatrix, postMatrix); VectorSet(viewOrigin, 0, 0, 0); MatrixTransformPoint2(viewOrigin); VectorSet(viewDirection, 0, 0, -1); MatrixTransformPoint2(viewDirection); VectorSet(up, 0, 1, 0); MatrixTransformPoint2(viewDirection); #endif #if 0 ri.Printf(PRINT_ALL, "light space crop bounds (%5.3f, %5.3f, %5.3f) (%5.3f, %5.3f, %5.3f)\n", cropBounds[0][0], cropBounds[0][1], cropBounds[0][2], cropBounds[1][0], cropBounds[1][1], cropBounds[1][2]); #endif #if 0 ri.Printf(PRINT_ALL, "cropMatrix =\n(%5.3f, %5.3f, %5.3f, %5.3f)\n" "(%5.3f, %5.3f, %5.3f, %5.3f)\n" "(%5.3f, %5.3f, %5.3f, %5.3f)\n" "(%5.3f, %5.3f, %5.3f, %5.3f)\n\n", cropMatrix[0], cropMatrix[4], cropMatrix[8], cropMatrix[12], cropMatrix[1], cropMatrix[5], cropMatrix[9], cropMatrix[13], cropMatrix[2], cropMatrix[6], cropMatrix[10], cropMatrix[14], cropMatrix[3], cropMatrix[7], cropMatrix[11], cropMatrix[15]); #endif { float gamma; float cosGamma; float sinGamma; float zNear, zFar; float depth; float n, f; vec3_t viewOriginLS, Cstart_lp, C; // use the formulas of the paper to get n (and f) #if 0 cosGamma = DotProduct(viewDirection, lightDirection); sinGamma = sqrt(1.0f - cosGamma * cosGamma); #else gamma = AngleBetweenVectors(viewDirection, lightDirection); sinGamma = sin(DEG2RAD(gamma)); #endif depth = fabs(cropBounds[1][1] - cropBounds[0][1]); //perspective transform depth //light space y extents //depth = fabs(cropBounds[0][2]) + fabs(cropBounds[1][2]); #if 1 zNear = backEnd.viewParms.zNear / sinGamma; zFar = zNear + depth * sinGamma; n = (zNear + sqrt(zFar * zNear)) / sinGamma; #elif 0 zNear = backEnd.viewParms.zNear; zFar = backEnd.viewParms.zFar; n = (zNear + sqrt(zFar * zNear)) / sinGamma; #else zNear = backEnd.viewParms.zNear; zFar = zNear + depth * sinGamma; n = (zNear + sqrt(zFar * zNear)) / sinGamma; #endif f = n + depth; ri.Printf(PRINT_ALL, "gamma = %5.3f, sin(gamma) = %5.3f, n = %5.3f, f = %5.3f\n", gamma, sinGamma, n, f); // new observer point n-1 behind eye position: pos = eyePos-up*(n-nearDist) #if 1 VectorMA(viewOrigin, -(n - zNear), up, C); //VectorMA(C, depth * 0.5f, lightDirection, C); #else // get the coordinates of the near camera point in light space MatrixTransformPoint(light->viewMatrix, viewOrigin, viewOriginLS); // c start has the x and y coordinate of e, the z coord of B.min() VectorSet(Cstart_lp, viewOriginLS[0], viewOriginLS[1], depth * 0.5f); // calc C the projection center // new projection center C, n behind the near plane of P VectorMA(Cstart_lp, -n, axisDefault[1], C); MatrixAffineInverse(light->viewMatrix, transformMatrix); MatrixTransformPoint2(transformMatrix, C); #endif #if 1 MatrixLookAtRH(light->viewMatrix, C, lightDirection, up); #else VectorInverse(up); MatrixLookAtRH(light->viewMatrix, C, up, lightDirection); VectorInverse(up); //MatrixLookAtRH(light->viewMatrix, viewOrigin, viewDirection, backEnd.viewParms.orientation.axis[2]); #endif #if 0 if(n >= FLT_MAX) { // if n is infinite than we should do uniform shadow mapping MatrixIdentity(lispMatrix); } else #endif { // one possibility for a simple perspective transformation matrix // with the two parameters n(near) and f(far) in y direction float a, b; float *m = lispMatrix; a = (f + n) / (f - n); b = -2 * f * n / (f - n); //a = f / (n - f); //b = (n * f) / (n - f); m[0] = 1; m[4] = 0; m[8] = 0; m[12] = 0; m[1] = 0; m[5] = a; m[9] = 0; m[13] = b; m[2] = 0; m[6] = 0; m[10] = 1; m[14] = 0; m[3] = 0; m[7] = 1; m[11] = 0; m[15] = 0; //MatrixPerspectiveProjectionRH(lispMatrix, -1, 1, n, f, -1, 1); //MatrixPerspectiveProjection(lispMatrix, -1, 1, -1, 1, n, f); //MatrixInverse(lispMatrix); //MatrixPerspectiveProjectionLH(lispMatrix, cropBounds[0][0], cropBounds[1][0], cropBounds[0][1], cropBounds[1][1], cropBounds[0][2], cropBounds[1][2]); //MatrixPerspectiveProjectionRH(lispMatrix, cropBounds[0][0], cropBounds[1][0], cropBounds[0][1], cropBounds[1][1], -f, -n); #if 0 ri.Printf(PRINT_ALL, "lispMatrix =\n(%5.3f, %5.3f, %5.3f, %5.3f)\n" "(%5.3f, %5.3f, %5.3f, %5.3f)\n" "(%5.3f, %5.3f, %5.3f, %5.3f)\n" "(%5.3f, %5.3f, %5.3f, %5.3f)\n\n", m[0], m[4], m[8], m[12], m[1], m[5], m[9], m[13], m[2], m[6], m[10], m[14], m[3], m[7], m[11], m[15]); #endif } //MatrixIdentity(lispMatrix); // temporal arrangement for the transformation of the points to post-perspective space #if 0 MatrixCopy(flipZMatrix, viewProjectionMatrix); //MatrixMultiply2(viewProjectionMatrix, switchToGL); MatrixMultiply2(viewProjectionMatrix, lispMatrix); //MatrixMultiply2(viewProjectionMatrix, switchToGL); //MatrixMultiplyScale(viewProjectionMatrix, 1, 1, -1); //Matrix4x4Multiply(flipZMatrix, lispMatrix, viewProjectionMatrix); //Matrix4x4Multiply(lispMatrix, light->viewMatrix, viewProjectionMatrix); //MatrixMultiply2(viewProjectionMatrix, cropMatrix); MatrixMultiply2(viewProjectionMatrix, light->viewMatrix); //MatrixMultiply2(viewProjectionMatrix, projectionCenter); //MatrixMultiply2(viewProjectionMatrix, transformMatrix); #else Matrix4x4Multiply(lispMatrix, light->viewMatrix, viewProjectionMatrix); //Matrix4x4Multiply(flipZMatrix, lispMatrix, viewProjectionMatrix); //MatrixMultiply2(viewProjectionMatrix, light->viewMatrix); #endif //Matrix4x4Multiply(lispMatrix, light->viewMatrix, viewProjectionMatrix); //Matrix4x4Multiply(light->viewMatrix, lispMatrix, viewProjectionMatrix); //transform the light volume points from world into the distorted light space //transformVecPoint(&Bcopy,lightProjection); //calculate the cubic hull (an AABB) //of the light space extents of the intersection body B //and save the two extreme points min and max //calcCubicHull(min,max,Bcopy.points,Bcopy.size); #if 0 VectorSet(forward, 1, 0, 0); VectorSet(side, 0, 1, 0); VectorSet(up, 0, 0, 1); MatrixTransformNormal2(viewProjectionMatrix, forward); MatrixTransformNormal2(viewProjectionMatrix, side); MatrixTransformNormal2(viewProjectionMatrix, up); ri.Printf(PRINT_ALL, "forward = (%5.3f, %5.3f, %5.3f)\n", forward[0], forward[1], forward[2]); ri.Printf(PRINT_ALL, "side = (%5.3f, %5.3f, %5.3f)\n", side[0], side[1], side[2]); ri.Printf(PRINT_ALL, "up = (%5.3f, %5.3f, %5.3f)\n", up[0], up[1], up[2]); #endif #if 1 ClearBounds(cropBounds[0], cropBounds[1]); for(j = 0; j < 8; j++) { point[0] = splitFrustumBounds[j & 1][0]; point[1] = splitFrustumBounds[(j >> 1) & 1][1]; point[2] = splitFrustumBounds[(j >> 2) & 1][2]; point[3] = 1; MatrixTransform4(viewProjectionMatrix, point, transf); transf[0] /= transf[3]; transf[1] /= transf[3]; transf[2] /= transf[3]; AddPointToBounds(transf, cropBounds[0], cropBounds[1]); } MatrixScaleTranslateToUnitCube(cropMatrix, cropBounds[0], cropBounds[1]); //MatrixOrthogonalProjection(cropMatrix, cropBounds[0][0], cropBounds[1][0], cropBounds[0][1], cropBounds[1][1], cropBounds[0][2], cropBounds[1][2]); #endif } #if 0 ri.Printf(PRINT_ALL, "light space post crop bounds (%5.3f, %5.3f, %5.3f) (%5.3f, %5.3f, %5.3f)\n", cropBounds[0][0], cropBounds[0][1], cropBounds[0][2], cropBounds[1][0], cropBounds[1][1], cropBounds[1][2]); #endif // //MatrixInverse(cropMatrix); #if 0 ri.Printf(PRINT_ALL, "cropMatrix =\n(%5.3f, %5.3f, %5.3f, %5.3f)\n" "(%5.3f, %5.3f, %5.3f, %5.3f)\n" "(%5.3f, %5.3f, %5.3f, %5.3f)\n" "(%5.3f, %5.3f, %5.3f, %5.3f)\n\n", cropMatrix[0], cropMatrix[4], cropMatrix[8], cropMatrix[12], cropMatrix[1], cropMatrix[5], cropMatrix[9], cropMatrix[13], cropMatrix[2], cropMatrix[6], cropMatrix[10], cropMatrix[14], cropMatrix[3], cropMatrix[7], cropMatrix[11], cropMatrix[15]); #endif //MatrixOrthogonalProjectionLH(cropMatrix, -1024, 1024, -1024, 1024, cropBounds[0][2], cropBounds[1][2]); //MatrixIdentity(light->projectionMatrix); MatrixCopy(flipZMatrix, light->projectionMatrix); //MatrixMultiply2(light->projectionMatrix, switchToArticle); MatrixMultiply2(light->projectionMatrix, cropMatrix); MatrixMultiply2(light->projectionMatrix, lispMatrix); GL_LoadProjectionMatrix(light->projectionMatrix); } else // if(r_lightSpacePerspectiveWarping->integer) #endif if(r_parallelShadowSplits->integer) { // original light direction is from surface to light VectorInverse(lightDirection); VectorNormalize(lightDirection); VectorCopy(backEnd.viewParms.orientation.origin, viewOrigin); VectorCopy(backEnd.viewParms.orientation.axis[0], viewDirection); VectorNormalize(viewDirection); #if 1 // calculate new up dir CrossProduct(lightDirection, viewDirection, side); VectorNormalize(side); CrossProduct(side, lightDirection, up); VectorNormalize(up); vectoangles(lightDirection, angles); MatrixFromAngles(rotationMatrix, angles[PITCH], angles[YAW], angles[ROLL]); AngleVectors(angles, forward, side, up); MatrixLookAtRH(light->viewMatrix, viewOrigin, lightDirection, up); #else MatrixLookAtRH(light->viewMatrix, viewOrigin, lightDirection, viewDirection); #endif for(j = 0; j < 6; j++) { VectorCopy(backEnd.viewParms.frustums[1 + splitFrustumIndex][j].normal, splitFrustum[j]); splitFrustum[j][3] = backEnd.viewParms.frustums[1 + splitFrustumIndex][j].dist; } // calculate split frustum corner points PlanesGetIntersectionPoint(splitFrustum[FRUSTUM_LEFT], splitFrustum[FRUSTUM_TOP], splitFrustum[FRUSTUM_NEAR], splitFrustumCorners[0]); PlanesGetIntersectionPoint(splitFrustum[FRUSTUM_RIGHT], splitFrustum[FRUSTUM_TOP], splitFrustum[FRUSTUM_NEAR], splitFrustumCorners[1]); PlanesGetIntersectionPoint(splitFrustum[FRUSTUM_RIGHT], splitFrustum[FRUSTUM_BOTTOM], splitFrustum[FRUSTUM_NEAR], splitFrustumCorners[2]); PlanesGetIntersectionPoint(splitFrustum[FRUSTUM_LEFT], splitFrustum[FRUSTUM_BOTTOM], splitFrustum[FRUSTUM_NEAR], splitFrustumCorners[3]); PlanesGetIntersectionPoint(splitFrustum[FRUSTUM_LEFT], splitFrustum[FRUSTUM_TOP], splitFrustum[FRUSTUM_FAR], splitFrustumCorners[4]); PlanesGetIntersectionPoint(splitFrustum[FRUSTUM_RIGHT], splitFrustum[FRUSTUM_TOP], splitFrustum[FRUSTUM_FAR], splitFrustumCorners[5]); PlanesGetIntersectionPoint(splitFrustum[FRUSTUM_RIGHT], splitFrustum[FRUSTUM_BOTTOM], splitFrustum[FRUSTUM_FAR], splitFrustumCorners[6]); PlanesGetIntersectionPoint(splitFrustum[FRUSTUM_LEFT], splitFrustum[FRUSTUM_BOTTOM], splitFrustum[FRUSTUM_FAR], splitFrustumCorners[7]); if(r_logFile->integer) { vec3_t rayIntersectionNear, rayIntersectionFar; float zNear, zFar; // don't just call LogComment, or we will get // a call to va() every frame! //GLimp_LogComment(va("----- Skipping shadowCube side: %i -----\n", cubeSide)); PlaneIntersectRay(viewOrigin, viewDirection, splitFrustum[FRUSTUM_FAR], rayIntersectionFar); zFar = Distance(viewOrigin, rayIntersectionFar); VectorInverse(viewDirection); PlaneIntersectRay(rayIntersectionFar, viewDirection,splitFrustum[FRUSTUM_NEAR], rayIntersectionNear); zNear = Distance(viewOrigin, rayIntersectionNear); VectorInverse(viewDirection); GLimp_LogComment(va("split frustum %i: near = %5.3f, far = %5.3f\n", splitFrustumIndex, zNear, zFar)); GLimp_LogComment(va("pyramid nearCorners\n")); for(j = 0; j < 4; j++) { GLimp_LogComment(va("(%5.3f, %5.3f, %5.3f)\n", splitFrustumCorners[j][0], splitFrustumCorners[j][1], splitFrustumCorners[j][2])); } GLimp_LogComment(va("pyramid farCorners\n")); for(j = 4; j < 8; j++) { GLimp_LogComment(va("(%5.3f, %5.3f, %5.3f)\n", splitFrustumCorners[j][0], splitFrustumCorners[j][1], splitFrustumCorners[j][2])); } } ClearBounds(splitFrustumBounds[0], splitFrustumBounds[1]); for(i = 0; i < 8; i++) { AddPointToBounds(splitFrustumCorners[i], splitFrustumBounds[0], splitFrustumBounds[1]); } #if 0 // find the bounding box of the current split in the light's view space ClearBounds(splitFrustumViewBounds[0], splitFrustumViewBounds[1]); numCasters = MergeInteractionBounds(light->viewMatrix, ia, iaCount, splitFrustumViewBounds, qtrue); for(j = 0; j < 8; j++) { VectorCopy(splitFrustumCorners[j], point); point[3] = 1; MatrixTransform4(light->viewMatrix, point, transf); transf[0] /= transf[3]; transf[1] /= transf[3]; transf[2] /= transf[3]; AddPointToBounds(transf, splitFrustumViewBounds[0], splitFrustumViewBounds[1]); } //MatrixScaleTranslateToUnitCube(projectionMatrix, splitFrustumViewBounds[0], splitFrustumViewBounds[1]); //MatrixOrthogonalProjectionRH(projectionMatrix, -1, 1, -1, 1, -splitFrustumViewBounds[1][2], -splitFrustumViewBounds[0][2]); MatrixOrthogonalProjectionRH(projectionMatrix, splitFrustumViewBounds[0][0], splitFrustumViewBounds[1][0], splitFrustumViewBounds[0][1], splitFrustumViewBounds[1][1], -splitFrustumViewBounds[1][2], -splitFrustumViewBounds[0][2]); Matrix4x4Multiply(projectionMatrix, light->viewMatrix, viewProjectionMatrix); // find the bounding box of the current split in the light's clip space ClearBounds(splitFrustumClipBounds[0], splitFrustumClipBounds[1]); for(j = 0; j < 8; j++) { VectorCopy(splitFrustumCorners[j], point); point[3] = 1; MatrixTransform4(viewProjectionMatrix, point, transf); transf[0] /= transf[3]; transf[1] /= transf[3]; transf[2] /= transf[3]; AddPointToBounds(transf, splitFrustumClipBounds[0], splitFrustumClipBounds[1]); } splitFrustumClipBounds[0][2] = 0; splitFrustumClipBounds[1][2] = 1; MatrixCrop(cropMatrix, splitFrustumClipBounds[0], splitFrustumClipBounds[1]); //MatrixIdentity(cropMatrix); if(r_logFile->integer) { GLimp_LogComment(va("split frustum light view space bounds (%5.3f, %5.3f, %5.3f) (%5.3f, %5.3f, %5.3f)\n", splitFrustumViewBounds[0][0], splitFrustumViewBounds[0][1], splitFrustumViewBounds[0][2], splitFrustumViewBounds[1][0], splitFrustumViewBounds[1][1], splitFrustumViewBounds[1][2])); GLimp_LogComment(va("split frustum light clip space bounds (%5.3f, %5.3f, %5.3f) (%5.3f, %5.3f, %5.3f)\n", splitFrustumClipBounds[0][0], splitFrustumClipBounds[0][1], splitFrustumClipBounds[0][2], splitFrustumClipBounds[1][0], splitFrustumClipBounds[1][1], splitFrustumClipBounds[1][2])); } #else // find the bounding box of the current split in the light's view space ClearBounds(cropBounds[0], cropBounds[1]); for(j = 0; j < 8; j++) { VectorCopy(splitFrustumCorners[j], point); point[3] = 1; MatrixTransform4(light->viewMatrix, point, transf); transf[0] /= transf[3]; transf[1] /= transf[3]; transf[2] /= transf[3]; AddPointToBounds(transf, cropBounds[0], cropBounds[1]); } MatrixOrthogonalProjectionRH(projectionMatrix, cropBounds[0][0], cropBounds[1][0], cropBounds[0][1], cropBounds[1][1], -cropBounds[1][2], -cropBounds[0][2]); Matrix4x4Multiply(projectionMatrix, light->viewMatrix, viewProjectionMatrix); numCasters = MergeInteractionBounds(viewProjectionMatrix, ia, iaCount, casterBounds, true); MergeInteractionBounds(viewProjectionMatrix, ia, iaCount, receiverBounds, false); // find the bounding box of the current split in the light's clip space ClearBounds(splitFrustumClipBounds[0], splitFrustumClipBounds[1]); for(j = 0; j < 8; j++) { VectorCopy(splitFrustumCorners[j], point); point[3] = 1; MatrixTransform4(viewProjectionMatrix, point, transf); transf[0] /= transf[3]; transf[1] /= transf[3]; transf[2] /= transf[3]; AddPointToBounds(transf, splitFrustumClipBounds[0], splitFrustumClipBounds[1]); } //ri.Printf(PRINT_ALL, "shadow casters = %i\n", numCasters); if(r_logFile->integer) { GLimp_LogComment(va("shadow casters = %i\n", numCasters)); GLimp_LogComment(va("split frustum light space clip bounds (%5.3f, %5.3f, %5.3f) (%5.3f, %5.3f, %5.3f)\n", splitFrustumClipBounds[0][0], splitFrustumClipBounds[0][1], splitFrustumClipBounds[0][2], splitFrustumClipBounds[1][0], splitFrustumClipBounds[1][1], splitFrustumClipBounds[1][2])); GLimp_LogComment(va("shadow caster light space clip bounds (%5.3f, %5.3f, %5.3f) (%5.3f, %5.3f, %5.3f)\n", casterBounds[0][0], casterBounds[0][1], casterBounds[0][2], casterBounds[1][0], casterBounds[1][1], casterBounds[1][2])); GLimp_LogComment(va("light receiver light space clip bounds (%5.3f, %5.3f, %5.3f) (%5.3f, %5.3f, %5.3f)\n", receiverBounds[0][0], receiverBounds[0][1], receiverBounds[0][2], receiverBounds[1][0], receiverBounds[1][1], receiverBounds[1][2])); } // scene-dependent bounding volume cropBounds[0][0] = Q_max(Q_max(casterBounds[0][0], receiverBounds[0][0]), splitFrustumClipBounds[0][0]); cropBounds[0][1] = Q_max(Q_max(casterBounds[0][1], receiverBounds[0][1]), splitFrustumClipBounds[0][1]); cropBounds[1][0] = Q_min(Q_min(casterBounds[1][0], receiverBounds[1][0]), splitFrustumClipBounds[1][0]); cropBounds[1][1] = Q_min(Q_min(casterBounds[1][1], receiverBounds[1][1]), splitFrustumClipBounds[1][1]); cropBounds[0][2] = Q_min(casterBounds[0][2], splitFrustumClipBounds[0][2]); //cropBounds[0][2] = casterBounds[0][2]; //cropBounds[0][2] = splitFrustumClipBounds[0][2]; cropBounds[1][2] = Q_min(receiverBounds[1][2], splitFrustumClipBounds[1][2]); //cropBounds[1][2] = splitFrustumClipBounds[1][2]; if(numCasters == 0) { VectorCopy(splitFrustumClipBounds[0], cropBounds[0]); VectorCopy(splitFrustumClipBounds[1], cropBounds[1]); } MatrixCrop(cropMatrix, cropBounds[0], cropBounds[1]); #endif Matrix4x4Multiply(cropMatrix, projectionMatrix, light->projectionMatrix); GL_LoadProjectionMatrix(light->projectionMatrix); } else { // original light direction is from surface to light VectorInverse(lightDirection); // Quake -> OpenGL view matrix from light perspective #if 1 vectoangles(lightDirection, angles); MatrixFromAngles(rotationMatrix, angles[PITCH], angles[YAW], angles[ROLL]); MatrixSetupTransformFromRotation(transformMatrix, rotationMatrix, backEnd.viewParms.orientation.origin); MatrixAffineInverse(transformMatrix, viewMatrix); Matrix4x4Multiply(quakeToOpenGLMatrix, viewMatrix, light->viewMatrix); #else MatrixLookAtRH(light->viewMatrix, backEnd.viewParms.orientation.origin, lightDirection, backEnd.viewParms.orientation.axis[0]); #endif ClearBounds(splitFrustumBounds[0], splitFrustumBounds[1]); //BoundsAdd(splitFrustumBounds[0], splitFrustumBounds[1], backEnd.viewParms.visBounds[0], backEnd.viewParms.visBounds[1]); BoundsAdd(splitFrustumBounds[0], splitFrustumBounds[1], light->worldBounds[0], light->worldBounds[1]); ClearBounds(cropBounds[0], cropBounds[1]); for(j = 0; j < 8; j++) { point[0] = splitFrustumBounds[j & 1][0]; point[1] = splitFrustumBounds[(j >> 1) & 1][1]; point[2] = splitFrustumBounds[(j >> 2) & 1][2]; point[3] = 1; #if 1 MatrixTransform4(light->viewMatrix, point, transf); transf[0] /= transf[3]; transf[1] /= transf[3]; transf[2] /= transf[3]; #else MatrixTransformPoint(light->viewMatrix, point, transf); #endif AddPointToBounds(transf, cropBounds[0], cropBounds[1]); } // transform from OpenGL's right handed into D3D's left handed coordinate system #if 0 MatrixScaleTranslateToUnitCube(projectionMatrix, cropBounds[0], cropBounds[1]); Matrix4x4Multiply(flipZMatrix, projectionMatrix, light->projectionMatrix); #else MatrixOrthogonalProjectionRH(light->projectionMatrix, cropBounds[0][0], cropBounds[1][0], cropBounds[0][1], cropBounds[1][1], -cropBounds[1][2], -cropBounds[0][2]); #endif GL_LoadProjectionMatrix(light->projectionMatrix); } break; } default: { R_BindFBO(tr.shadowMapFBO[light->shadowLOD]); break; } } } if(r_logFile->integer) { // don't just call LogComment, or we will get // a call to va() every frame! GLimp_LogComment(va("----- First Shadow Interaction: %i -----\n", iaCount)); } } else { GLimp_LogComment("--- Rendering lighting ---\n"); if(r_logFile->integer) { // don't just call LogComment, or we will get // a call to va() every frame! GLimp_LogComment(va("----- First Light Interaction: %i -----\n", iaCount)); } // finally draw light R_BindFBO(tr.geometricRenderFBO); glDrawBuffers(1, geometricRenderTargets); // restore camera matrices GL_LoadProjectionMatrix(backEnd.viewParms.projectionMatrix); GL_LoadModelViewMatrix(backEnd.orientation.modelViewMatrix); // set the window clipping GL_Viewport(backEnd.viewParms.viewportX, backEnd.viewParms.viewportY, backEnd.viewParms.viewportWidth, backEnd.viewParms.viewportHeight); GL_Scissor(backEnd.viewParms.viewportX, backEnd.viewParms.viewportY, backEnd.viewParms.viewportWidth, backEnd.viewParms.viewportHeight); GL_CheckErrors(); GLimp_LogComment("--- Rendering lighting ---\n"); switch (light->l.rlType) { case RL_OMNI: { // reset light view and projection matrices MatrixAffineInverse(light->transformMatrix, light->viewMatrix); MatrixSetupScale(light->projectionMatrix, 1.0 / light->l.radius[0], 1.0 / light->l.radius[1], 1.0 / light->l.radius[2]); // build the attenuation matrix MatrixSetupTranslation(light->attenuationMatrix, 0.5, 0.5, 0.5); // bias MatrixMultiplyScale(light->attenuationMatrix, 0.5, 0.5, 0.5); // scale MatrixMultiply2(light->attenuationMatrix, light->projectionMatrix); // light projection (frustum) MatrixMultiply2(light->attenuationMatrix, light->viewMatrix); MatrixCopy(light->attenuationMatrix, light->shadowMatrices[0]); break; } case RL_PROJ: { // build the attenuation matrix MatrixSetupTranslation(light->attenuationMatrix, 0.5, 0.5, 0.0); MatrixMultiplyScale(light->attenuationMatrix, 0.5, 0.5, 1.0 / Q_min(light->falloffLength, 1.0)); MatrixMultiply2(light->attenuationMatrix, light->projectionMatrix); MatrixMultiply2(light->attenuationMatrix, light->viewMatrix); MatrixCopy(light->attenuationMatrix, light->shadowMatrices[0]); break; } case RL_DIRECTIONAL: { matrix_t viewMatrix, projectionMatrix; // build same attenuation matrix as for box lights so we can clip pixels outside of the light volume MatrixAffineInverse(light->transformMatrix, viewMatrix); MatrixSetupScale(projectionMatrix, 1.0 / light->l.radius[0], 1.0 / light->l.radius[1], 1.0 / light->l.radius[2]); MatrixCopy(bias, light->attenuationMatrix); MatrixMultiply2(light->attenuationMatrix, projectionMatrix); MatrixMultiply2(light->attenuationMatrix, viewMatrix); break; } default: break; } if(light->clipsNearPlane) { // set 2D virtual screen size GL_PushMatrix(); GL_LoadProjectionMatrix(ortho); GL_LoadModelViewMatrix(matrixIdentity); for(j = 0; j < 6; j++) { VectorCopy(light->frustum[j].normal, lightFrustum[j]); lightFrustum[j][3] = light->frustum[j].dist; } } else { // prepare rendering of the light volume // either bind VBO or setup the tess struct #if 0 // FIXME check why this does not work here if(light->isStatic && light->frustumVBO && light->frustumIBO) { // render in world space backEnd.orientation = backEnd.viewParms.world; GL_LoadProjectionMatrix(backEnd.viewParms.projectionMatrix); GL_LoadModelViewMatrix(backEnd.viewParms.world.modelViewMatrix); R_BindVBO(light->frustumVBO); R_BindIBO(light->frustumIBO); GL_VertexAttribsState(ATTR_POSITION); tess.numVertexes = light->frustumVerts; tess.numIndexes = light->frustumIndexes; } else #endif { tess.multiDrawPrimitives = 0; tess.numIndexes = 0; tess.numVertexes = 0; switch (light->l.rlType) { case RL_OMNI: case RL_DIRECTIONAL: { #if 1 // render in light space R_RotateLightForViewParms(light, &backEnd.viewParms, &backEnd.orientation); GL_LoadProjectionMatrix(backEnd.viewParms.projectionMatrix); GL_LoadModelViewMatrix(backEnd.orientation.modelViewMatrix); Tess_AddCube(vec3_origin, light->localBounds[0], light->localBounds[1], colorWhite); Tess_UpdateVBOs(ATTR_POSITION | ATTR_COLOR); #else matrix_t transform, scale, rot; axis_t axis; MatrixSetupScale(scale, light->l.radius[0], light->l.radius[1], light->l.radius[2]); Matrix4x4Multiply(scale, light->transformMatrix, transform); GL_LoadModelViewMatrix(transform); GL_LoadProjectionMatrix(backEnd.viewParms.projectionMatrix); R_BindVBO(tr.unitCubeVBO); R_BindIBO(tr.unitCubeIBO); GL_VertexAttribsState(ATTR_POSITION); tess.multiDrawPrimitives = 0; tess.numVertexes = tr.unitCubeVBO->vertexesNum; tess.numIndexes = tr.unitCubeIBO->indexesNum; #endif break; } case RL_PROJ: { vec3_t farCorners[4]; vec4_t *frustum = light->localFrustum; // render in light space R_RotateLightForViewParms(light, &backEnd.viewParms, &backEnd.orientation); GL_LoadProjectionMatrix(backEnd.viewParms.projectionMatrix); GL_LoadModelViewMatrix(backEnd.orientation.modelViewMatrix); PlanesGetIntersectionPoint(frustum[FRUSTUM_LEFT], frustum[FRUSTUM_TOP], frustum[FRUSTUM_FAR], farCorners[0]); PlanesGetIntersectionPoint(frustum[FRUSTUM_RIGHT], frustum[FRUSTUM_TOP], frustum[FRUSTUM_FAR], farCorners[1]); PlanesGetIntersectionPoint(frustum[FRUSTUM_RIGHT], frustum[FRUSTUM_BOTTOM], frustum[FRUSTUM_FAR], farCorners[2]); PlanesGetIntersectionPoint(frustum[FRUSTUM_LEFT], frustum[FRUSTUM_BOTTOM], frustum[FRUSTUM_FAR], farCorners[3]); if(!VectorCompare(light->l.projStart, vec3_origin)) { vec3_t nearCorners[4]; // calculate the vertices defining the top area PlanesGetIntersectionPoint(frustum[FRUSTUM_LEFT], frustum[FRUSTUM_TOP], frustum[FRUSTUM_NEAR], nearCorners[0]); PlanesGetIntersectionPoint(frustum[FRUSTUM_RIGHT], frustum[FRUSTUM_TOP], frustum[FRUSTUM_NEAR], nearCorners[1]); PlanesGetIntersectionPoint(frustum[FRUSTUM_RIGHT], frustum[FRUSTUM_BOTTOM], frustum[FRUSTUM_NEAR], nearCorners[2]); PlanesGetIntersectionPoint(frustum[FRUSTUM_LEFT], frustum[FRUSTUM_BOTTOM], frustum[FRUSTUM_NEAR], nearCorners[3]); // draw outer surfaces for(j = 0; j < 4; j++) { VectorSet4(quadVerts[0], nearCorners[j][0], nearCorners[j][1], nearCorners[j][2], 1); VectorSet4(quadVerts[1], farCorners[j][0], farCorners[j][1], farCorners[j][2], 1); VectorSet4(quadVerts[2], farCorners[(j + 1) % 4][0], farCorners[(j + 1) % 4][1], farCorners[(j + 1) % 4][2], 1); VectorSet4(quadVerts[3], nearCorners[(j + 1) % 4][0], nearCorners[(j + 1) % 4][1], nearCorners[(j + 1) % 4][2], 1); Tess_AddQuadStamp2(quadVerts, colorCyan); } // draw far cap VectorSet4(quadVerts[0], farCorners[3][0], farCorners[3][1], farCorners[3][2], 1); VectorSet4(quadVerts[1], farCorners[2][0], farCorners[2][1], farCorners[2][2], 1); VectorSet4(quadVerts[2], farCorners[1][0], farCorners[1][1], farCorners[1][2], 1); VectorSet4(quadVerts[3], farCorners[0][0], farCorners[0][1], farCorners[0][2], 1); Tess_AddQuadStamp2(quadVerts, colorRed); // draw near cap VectorSet4(quadVerts[0], nearCorners[0][0], nearCorners[0][1], nearCorners[0][2], 1); VectorSet4(quadVerts[1], nearCorners[1][0], nearCorners[1][1], nearCorners[1][2], 1); VectorSet4(quadVerts[2], nearCorners[2][0], nearCorners[2][1], nearCorners[2][2], 1); VectorSet4(quadVerts[3], nearCorners[3][0], nearCorners[3][1], nearCorners[3][2], 1); Tess_AddQuadStamp2(quadVerts, colorGreen); } else { vec3_t top; // no light_start, just use the top vertex (doesn't need to be mirrored) PlanesGetIntersectionPoint(frustum[FRUSTUM_LEFT], frustum[FRUSTUM_RIGHT], frustum[FRUSTUM_TOP], top); // draw pyramid for(j = 0; j < 4; j++) { VectorCopy(top, tess.xyz[tess.numVertexes]); Vector4Copy(colorCyan, tess.colors[tess.numVertexes]); tess.indexes[tess.numIndexes++] = tess.numVertexes; tess.numVertexes++; VectorCopy(farCorners[(j + 1) % 4], tess.xyz[tess.numVertexes]); Vector4Copy(colorCyan, tess.colors[tess.numVertexes]); tess.indexes[tess.numIndexes++] = tess.numVertexes; tess.numVertexes++; VectorCopy(farCorners[j], tess.xyz[tess.numVertexes]); Vector4Copy(colorCyan, tess.colors[tess.numVertexes]); tess.indexes[tess.numIndexes++] = tess.numVertexes; tess.numVertexes++; } VectorSet4(quadVerts[0], farCorners[0][0], farCorners[0][1], farCorners[0][2], 1); VectorSet4(quadVerts[1], farCorners[1][0], farCorners[1][1], farCorners[1][2], 1); VectorSet4(quadVerts[2], farCorners[2][0], farCorners[2][1], farCorners[2][2], 1); VectorSet4(quadVerts[3], farCorners[3][0], farCorners[3][1], farCorners[3][2], 1); Tess_AddQuadStamp2(quadVerts, colorRed); } Tess_UpdateVBOs(ATTR_POSITION | ATTR_COLOR); break; } default: break; } } } // last interaction of current light lightShader = light->shader; attenuationZStage = lightShader->stages[0]; for(j = 1; j < MAX_SHADER_STAGES; j++) { attenuationXYStage = lightShader->stages[j]; if(!attenuationXYStage) { break; } if(attenuationXYStage->type != ST_ATTENUATIONMAP_XY) { continue; } if(!RB_EvalExpression(&attenuationXYStage->ifExp, 1.0)) { continue; } Tess_ComputeColor(attenuationXYStage); if(VectorLength(tess.svars.color) < 0.01) { // don't render black lights } R_ComputeFinalAttenuation(attenuationXYStage, light); #if 0 // FIXME support darkening shadows as in HL2 if(light->l.inverseShadows) { GL_State(GLS_SRCBLEND_ZERO | GLS_DSTBLEND_ONE_MINUS_SRC_COLOR); } else #endif { // set OpenGL state for additive lighting GL_State(GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE); } if(light->clipsNearPlane) { GL_Cull(CT_TWO_SIDED); } else { GL_Cull(CT_FRONT_SIDED); } bool shadowCompare = (r_shadows->integer >= SHADOWING_ESM16 && !light->l.noShadows && light->shadowLOD >= 0); float shadowTexelSize = 1.0f; if(shadowCompare) { shadowTexelSize = 1.0f / shadowMapResolutions[light->shadowLOD]; } if(light->l.rlType == RL_OMNI) { // choose right shader program ---------------------------------- gl_deferredLightingShader_omniXYZ->SetPortalClipping(backEnd.viewParms.isPortal); gl_deferredLightingShader_omniXYZ->SetNormalMapping(r_normalMapping->integer); gl_deferredLightingShader_omniXYZ->SetShadowing(shadowCompare); gl_deferredLightingShader_omniXYZ->SetFrustumClipping(light->clipsNearPlane); gl_deferredLightingShader_omniXYZ->BindProgram(); // end choose right shader program ------------------------------ gl_deferredLightingShader_omniXYZ->SetUniform_ViewOrigin(backEnd.viewParms.orientation.origin); // in world space gl_deferredLightingShader_omniXYZ->SetUniform_LightOrigin(light->origin); gl_deferredLightingShader_omniXYZ->SetUniform_LightColor(tess.svars.color); gl_deferredLightingShader_omniXYZ->SetUniform_LightRadius(light->sphereRadius); gl_deferredLightingShader_omniXYZ->SetUniform_LightScale(light->l.scale); gl_deferredLightingShader_omniXYZ->SetUniform_LightAttenuationMatrix(light->attenuationMatrix2); gl_deferredLightingShader_omniXYZ->SetUniform_LightFrustum(lightFrustum); gl_deferredLightingShader_omniXYZ->SetUniform_ModelViewProjectionMatrix(glState.modelViewProjectionMatrix[glState.stackIndex]); gl_deferredLightingShader_omniXYZ->SetUniform_UnprojectMatrix(backEnd.viewParms.unprojectionMatrix); if(backEnd.viewParms.isPortal) { float plane[4]; // clipping plane in world space plane[0] = backEnd.viewParms.portalPlane.normal[0]; plane[1] = backEnd.viewParms.portalPlane.normal[1]; plane[2] = backEnd.viewParms.portalPlane.normal[2]; plane[3] = backEnd.viewParms.portalPlane.dist; gl_deferredLightingShader_omniXYZ->SetUniform_PortalPlane(plane); } // bind u_DiffuseMap GL_SelectTexture(0); GL_Bind(tr.deferredDiffuseFBOImage); // bind u_NormalMap GL_SelectTexture(1); GL_Bind(tr.deferredNormalFBOImage); if(r_normalMapping->integer) { // bind u_SpecularMap GL_SelectTexture(2); GL_Bind(tr.deferredSpecularFBOImage); } // bind u_DepthMap GL_SelectTexture(3); GL_Bind(tr.depthRenderImage); // bind u_AttenuationMapXY GL_SelectTexture(4); BindAnimatedImage(&attenuationXYStage->bundle[TB_COLORMAP]); // bind u_AttenuationMapZ GL_SelectTexture(5); BindAnimatedImage(&attenuationZStage->bundle[TB_COLORMAP]); // bind u_ShadowMap if(shadowCompare) { GL_SelectTexture(6); GL_Bind(tr.shadowCubeFBOImage[light->shadowLOD]); } if(light->clipsNearPlane) { // draw lighting with a fullscreen quad Tess_InstantQuad(backEnd.viewParms.viewportVerts); } else { // draw the volume Tess_DrawElements(); } } else if(light->l.rlType == RL_PROJ) { // choose right shader program ---------------------------------- gl_deferredLightingShader_projXYZ->SetPortalClipping(backEnd.viewParms.isPortal); gl_deferredLightingShader_projXYZ->SetNormalMapping(r_normalMapping->integer); gl_deferredLightingShader_projXYZ->SetShadowing(shadowCompare); gl_deferredLightingShader_projXYZ->SetFrustumClipping(light->clipsNearPlane); gl_deferredLightingShader_projXYZ->BindProgram(); // end choose right shader program ------------------------------ gl_deferredLightingShader_projXYZ->SetUniform_ViewOrigin(backEnd.viewParms.orientation.origin); // in world space gl_deferredLightingShader_projXYZ->SetUniform_LightOrigin(light->origin); gl_deferredLightingShader_projXYZ->SetUniform_LightColor(tess.svars.color); gl_deferredLightingShader_projXYZ->SetUniform_LightRadius(light->sphereRadius); gl_deferredLightingShader_projXYZ->SetUniform_LightScale(light->l.scale); gl_deferredLightingShader_projXYZ->SetUniform_LightAttenuationMatrix(light->attenuationMatrix2); gl_deferredLightingShader_projXYZ->SetUniform_LightFrustum(lightFrustum); if(shadowCompare) { gl_deferredLightingShader_projXYZ->SetUniform_ShadowTexelSize(shadowTexelSize); gl_deferredLightingShader_projXYZ->SetUniform_ShadowBlur(r_shadowBlur->value); gl_deferredLightingShader_projXYZ->SetUniform_ShadowMatrix(light->shadowMatrices); } gl_deferredLightingShader_projXYZ->SetUniform_ModelViewProjectionMatrix(glState.modelViewProjectionMatrix[glState.stackIndex]); gl_deferredLightingShader_projXYZ->SetUniform_UnprojectMatrix(backEnd.viewParms.unprojectionMatrix); if(backEnd.viewParms.isPortal) { float plane[4]; // clipping plane in world space plane[0] = backEnd.viewParms.portalPlane.normal[0]; plane[1] = backEnd.viewParms.portalPlane.normal[1]; plane[2] = backEnd.viewParms.portalPlane.normal[2]; plane[3] = backEnd.viewParms.portalPlane.dist; gl_deferredLightingShader_projXYZ->SetUniform_PortalPlane(plane); } // bind u_DiffuseMap GL_SelectTexture(0); GL_Bind(tr.deferredDiffuseFBOImage); // bind u_NormalMap GL_SelectTexture(1); GL_Bind(tr.deferredNormalFBOImage); if(r_normalMapping->integer) { // bind u_SpecularMap GL_SelectTexture(2); GL_Bind(tr.deferredSpecularFBOImage); } // bind u_DepthMap GL_SelectTexture(3); GL_Bind(tr.depthRenderImage); // bind u_AttenuationMapXY GL_SelectTexture(4); BindAnimatedImage(&attenuationXYStage->bundle[TB_COLORMAP]); // bind u_AttenuationMapZ GL_SelectTexture(5); BindAnimatedImage(&attenuationZStage->bundle[TB_COLORMAP]); // bind u_ShadowMap if(shadowCompare) { GL_SelectTexture(6); GL_Bind(tr.shadowMapFBOImage[light->shadowLOD]); } if(light->clipsNearPlane) { // draw lighting with a fullscreen quad Tess_InstantQuad(backEnd.viewParms.viewportVerts); } else { // draw the volume Tess_DrawElements(); } } else if(light->l.rlType == RL_DIRECTIONAL) { shadowCompare = (r_shadows->integer >= SHADOWING_ESM16 && !light->l.noShadows);// && light->shadowLOD >= 0); // choose right shader program ---------------------------------- gl_deferredLightingShader_directionalSun->SetPortalClipping(backEnd.viewParms.isPortal); gl_deferredLightingShader_directionalSun->SetNormalMapping(r_normalMapping->integer); gl_deferredLightingShader_directionalSun->SetShadowing(shadowCompare); gl_deferredLightingShader_directionalSun->SetFrustumClipping(light->clipsNearPlane); gl_deferredLightingShader_directionalSun->BindProgram(); // end choose right shader program ------------------------------ gl_deferredLightingShader_directionalSun->SetUniform_ViewOrigin(backEnd.viewParms.orientation.origin); // in world space gl_deferredLightingShader_directionalSun->SetUniform_LightDir(tr.sunDirection); gl_deferredLightingShader_directionalSun->SetUniform_LightColor(tess.svars.color); gl_deferredLightingShader_directionalSun->SetUniform_LightRadius(light->sphereRadius); gl_deferredLightingShader_directionalSun->SetUniform_LightScale(light->l.scale); gl_deferredLightingShader_directionalSun->SetUniform_LightAttenuationMatrix(light->attenuationMatrix2); gl_deferredLightingShader_directionalSun->SetUniform_LightFrustum(lightFrustum); if(shadowCompare) { gl_deferredLightingShader_directionalSun->SetUniform_ShadowMatrix(light->shadowMatricesBiased); gl_deferredLightingShader_directionalSun->SetUniform_ShadowParallelSplitDistances(backEnd.viewParms.parallelSplitDistances); gl_deferredLightingShader_directionalSun->SetUniform_ShadowTexelSize(shadowTexelSize); gl_deferredLightingShader_directionalSun->SetUniform_ShadowBlur(r_shadowBlur->value); } gl_deferredLightingShader_directionalSun->SetUniform_ModelViewProjectionMatrix(glState.modelViewProjectionMatrix[glState.stackIndex]); gl_deferredLightingShader_directionalSun->SetUniform_UnprojectMatrix(backEnd.viewParms.unprojectionMatrix); gl_deferredLightingShader_directionalSun->SetUniform_ViewMatrix(backEnd.viewParms.world.viewMatrix); if(backEnd.viewParms.isPortal) { float plane[4]; // clipping plane in world space plane[0] = backEnd.viewParms.portalPlane.normal[0]; plane[1] = backEnd.viewParms.portalPlane.normal[1]; plane[2] = backEnd.viewParms.portalPlane.normal[2]; plane[3] = backEnd.viewParms.portalPlane.dist; gl_deferredLightingShader_directionalSun->SetUniform_PortalPlane(plane); } // bind u_DiffuseMap GL_SelectTexture(0); GL_Bind(tr.deferredDiffuseFBOImage); // bind u_NormalMap GL_SelectTexture(1); GL_Bind(tr.deferredNormalFBOImage); if(r_normalMapping->integer) { // bind u_SpecularMap GL_SelectTexture(2); GL_Bind(tr.deferredSpecularFBOImage); } // bind u_DepthMap GL_SelectTexture(3); GL_Bind(tr.depthRenderImage); // bind u_AttenuationMapXY //GL_SelectTexture(4); //BindAnimatedImage(&attenuationXYStage->bundle[TB_COLORMAP]); // bind u_AttenuationMapZ //GL_SelectTexture(5); //BindAnimatedImage(&attenuationZStage->bundle[TB_COLORMAP]); // bind shadow maps if(shadowCompare) { GL_SelectTexture(6); GL_Bind(tr.sunShadowMapFBOImage[0]); if(r_parallelShadowSplits->integer >= 1) { GL_SelectTexture(7); GL_Bind(tr.sunShadowMapFBOImage[1]); } if(r_parallelShadowSplits->integer >= 2) { GL_SelectTexture(8); GL_Bind(tr.sunShadowMapFBOImage[2]); } if(r_parallelShadowSplits->integer >= 3) { GL_SelectTexture(9); GL_Bind(tr.sunShadowMapFBOImage[3]); } if(r_parallelShadowSplits->integer >= 4) { GL_SelectTexture(10); GL_Bind(tr.sunShadowMapFBOImage[4]); } } if(light->clipsNearPlane) { // draw lighting with a fullscreen quad Tess_InstantQuad(backEnd.viewParms.viewportVerts); } else { // draw the volume Tess_DrawElements(); } } } if(light->clipsNearPlane) { GL_PopMatrix(); } // done with light post processing // clean up tess.multiDrawPrimitives = 0; tess.numIndexes = 0; tess.numVertexes = 0; // draw split frustum shadow maps if(r_showShadowMaps->integer && light->l.rlType == RL_DIRECTIONAL) { int frustumIndex; float x, y, w, h; GL_Viewport(backEnd.viewParms.viewportX, backEnd.viewParms.viewportY, backEnd.viewParms.viewportWidth, backEnd.viewParms.viewportHeight); GL_Scissor(backEnd.viewParms.viewportX, backEnd.viewParms.viewportY, backEnd.viewParms.viewportWidth, backEnd.viewParms.viewportHeight); // set 2D virtual screen size GL_PushMatrix(); GL_LoadProjectionMatrix(ortho); GL_LoadModelViewMatrix(matrixIdentity); for(frustumIndex = 0; frustumIndex <= r_parallelShadowSplits->integer; frustumIndex++) { GL_Cull(CT_TWO_SIDED); GL_State(GLS_DEPTHTEST_DISABLE); gl_debugShadowMapShader->BindProgram(); gl_debugShadowMapShader->SetUniform_ModelViewProjectionMatrix(glState.modelViewProjectionMatrix[glState.stackIndex]); // bind u_ColorMap GL_SelectTexture(0); GL_Bind(tr.sunShadowMapFBOImage[frustumIndex]); w = 200; h = 200; x = 205 * frustumIndex; y = 70; VectorSet4(quadVerts[0], x, y, 0, 1); VectorSet4(quadVerts[1], x + w, y, 0, 1); VectorSet4(quadVerts[2], x + w, y + h, 0, 1); VectorSet4(quadVerts[3], x, y + h, 0, 1); Tess_InstantQuad(quadVerts); { int j; vec4_t splitFrustum[6]; vec3_t farCorners[4]; vec3_t nearCorners[4]; GL_Viewport(x, y, w, h); GL_Scissor(x, y, w, h); GL_PushMatrix(); GL_State(GLS_POLYMODE_LINE | GLS_DEPTHTEST_DISABLE); GL_Cull(CT_TWO_SIDED); gl_genericShader->DisableAlphaTesting(); gl_genericShader->DisablePortalClipping(); gl_genericShader->DisableVertexSkinning(); gl_genericShader->DisableVertexAnimation(); gl_genericShader->DisableDeformVertexes(); gl_genericShader->DisableTCGenEnvironment(); gl_genericShader->BindProgram(); gl_genericShader->SetUniform_ColorModulate(CGEN_VERTEX, AGEN_VERTEX); gl_genericShader->SetUniform_Color(colorBlack); gl_genericShader->SetUniform_ModelViewProjectionMatrix(light->shadowMatrices[frustumIndex]); // bind u_ColorMap GL_SelectTexture(0); GL_Bind(tr.whiteImage); gl_genericShader->SetUniform_ColorTextureMatrix(matrixIdentity); tess.multiDrawPrimitives = 0; tess.numIndexes = 0; tess.numVertexes = 0; #if 1 for(j = 0; j < 6; j++) { VectorCopy(backEnd.viewParms.frustums[1 + frustumIndex][j].normal, splitFrustum[j]); splitFrustum[j][3] = backEnd.viewParms.frustums[1 + frustumIndex][j].dist; } #else for(j = 0; j < 6; j++) { VectorCopy(backEnd.viewParms.frustums[0][j].normal, splitFrustum[j]); splitFrustum[j][3] = backEnd.viewParms.frustums[0][j].dist; } #endif // calculate split frustum corner points PlanesGetIntersectionPoint(splitFrustum[FRUSTUM_LEFT], splitFrustum[FRUSTUM_TOP], splitFrustum[FRUSTUM_NEAR], nearCorners[0]); PlanesGetIntersectionPoint(splitFrustum[FRUSTUM_RIGHT], splitFrustum[FRUSTUM_TOP], splitFrustum[FRUSTUM_NEAR], nearCorners[1]); PlanesGetIntersectionPoint(splitFrustum[FRUSTUM_RIGHT], splitFrustum[FRUSTUM_BOTTOM], splitFrustum[FRUSTUM_NEAR], nearCorners[2]); PlanesGetIntersectionPoint(splitFrustum[FRUSTUM_LEFT], splitFrustum[FRUSTUM_BOTTOM], splitFrustum[FRUSTUM_NEAR], nearCorners[3]); PlanesGetIntersectionPoint(splitFrustum[FRUSTUM_LEFT], splitFrustum[FRUSTUM_TOP], splitFrustum[FRUSTUM_FAR], farCorners[0]); PlanesGetIntersectionPoint(splitFrustum[FRUSTUM_RIGHT], splitFrustum[FRUSTUM_TOP], splitFrustum[FRUSTUM_FAR], farCorners[1]); PlanesGetIntersectionPoint(splitFrustum[FRUSTUM_RIGHT], splitFrustum[FRUSTUM_BOTTOM], splitFrustum[FRUSTUM_FAR], farCorners[2]); PlanesGetIntersectionPoint(splitFrustum[FRUSTUM_LEFT], splitFrustum[FRUSTUM_BOTTOM], splitFrustum[FRUSTUM_FAR], farCorners[3]); // draw outer surfaces #if 1 for(j = 0; j < 4; j++) { VectorSet4(quadVerts[0], nearCorners[j][0], nearCorners[j][1], nearCorners[j][2], 1); VectorSet4(quadVerts[1], farCorners[j][0], farCorners[j][1], farCorners[j][2], 1); VectorSet4(quadVerts[2], farCorners[(j + 1) % 4][0], farCorners[(j + 1) % 4][1], farCorners[(j + 1) % 4][2], 1); VectorSet4(quadVerts[3], nearCorners[(j + 1) % 4][0], nearCorners[(j + 1) % 4][1], nearCorners[(j + 1) % 4][2], 1); Tess_AddQuadStamp2(quadVerts, colorCyan); } // draw far cap VectorSet4(quadVerts[0], farCorners[3][0], farCorners[3][1], farCorners[3][2], 1); VectorSet4(quadVerts[1], farCorners[2][0], farCorners[2][1], farCorners[2][2], 1); VectorSet4(quadVerts[2], farCorners[1][0], farCorners[1][1], farCorners[1][2], 1); VectorSet4(quadVerts[3], farCorners[0][0], farCorners[0][1], farCorners[0][2], 1); Tess_AddQuadStamp2(quadVerts, colorBlue); // draw near cap VectorSet4(quadVerts[0], nearCorners[0][0], nearCorners[0][1], nearCorners[0][2], 1); VectorSet4(quadVerts[1], nearCorners[1][0], nearCorners[1][1], nearCorners[1][2], 1); VectorSet4(quadVerts[2], nearCorners[2][0], nearCorners[2][1], nearCorners[2][2], 1); VectorSet4(quadVerts[3], nearCorners[3][0], nearCorners[3][1], nearCorners[3][2], 1); Tess_AddQuadStamp2(quadVerts, colorGreen); #else // draw pyramid for(j = 0; j < 4; j++) { VectorCopy(farCorners[j], tess.xyz[tess.numVertexes]); Vector4Copy(colorCyan, tess.colors[tess.numVertexes]); tess.indexes[tess.numIndexes++] = tess.numVertexes; tess.numVertexes++; VectorCopy(farCorners[(j + 1) % 4], tess.xyz[tess.numVertexes]); Vector4Copy(colorCyan, tess.colors[tess.numVertexes]); tess.indexes[tess.numIndexes++] = tess.numVertexes; tess.numVertexes++; VectorCopy(backEnd.viewParms.orientation.origin, tess.xyz[tess.numVertexes]); Vector4Copy(colorCyan, tess.colors[tess.numVertexes]); tess.indexes[tess.numIndexes++] = tess.numVertexes; tess.numVertexes++; } VectorSet4(quadVerts[0], farCorners[3][0], farCorners[3][1], farCorners[3][2], 1); VectorSet4(quadVerts[1], farCorners[2][0], farCorners[2][1], farCorners[2][2], 1); VectorSet4(quadVerts[2], farCorners[1][0], farCorners[1][1], farCorners[1][2], 1); VectorSet4(quadVerts[3], farCorners[0][0], farCorners[0][1], farCorners[0][2], 1); Tess_AddQuadStamp2(quadVerts, colorRed); #endif Tess_UpdateVBOs(ATTR_POSITION | ATTR_COLOR); Tess_DrawElements(); // draw light volume if(light->isStatic && light->frustumVBO && light->frustumIBO) { gl_genericShader->SetUniform_ColorModulate(CGEN_CUSTOM_RGB, AGEN_CUSTOM); gl_genericShader->SetUniform_Color(colorYellow); R_BindVBO(light->frustumVBO); R_BindIBO(light->frustumIBO); GL_VertexAttribsState(ATTR_POSITION); tess.numVertexes = light->frustumVerts; tess.numIndexes = light->frustumIndexes; Tess_DrawElements(); } tess.multiDrawPrimitives = 0; tess.numIndexes = 0; tess.numVertexes = 0; GL_PopMatrix(); GL_Viewport(backEnd.viewParms.viewportX, backEnd.viewParms.viewportY, backEnd.viewParms.viewportWidth, backEnd.viewParms.viewportHeight); GL_Scissor(backEnd.viewParms.viewportX, backEnd.viewParms.viewportY, backEnd.viewParms.viewportWidth, backEnd.viewParms.viewportHeight); } } // end for // back to 3D or whatever GL_PopMatrix(); } } } // end if(iaCount == iaFirst) if(drawShadows) { if(entity->e.renderfx & (RF_DEPTHHACK)) { goto skipInteraction; } if(shader->isSky) { goto skipInteraction; } if(shader->sort > SS_OPAQUE) { goto skipInteraction; } if(shader->noShadows || light->l.noShadows || light->shadowLOD < 0) { goto skipInteraction; } if(light->l.inverseShadows && (entity == &tr.worldEntity)) { // this light only casts shadows by its player and their items goto skipInteraction; } if(ia->type == IA_LIGHTONLY) { goto skipInteraction; } if(light->l.rlType == RL_OMNI && !(ia->cubeSideBits & (1 << cubeSide))) { goto skipInteraction; } switch (light->l.rlType) { case RL_OMNI: case RL_PROJ: case RL_DIRECTIONAL: { #if 1 if((iaCount != iaFirst) && light == oldLight && entity == oldEntity && (alphaTest ? shader == oldShader : alphaTest == oldAlphaTest) && (deformType == oldDeformType)) { if(r_logFile->integer) { // don't just call LogComment, or we will get // a call to va() every frame! GLimp_LogComment(va("----- Batching Shadow Interaction: %i -----\n", iaCount)); } // fast path, same as previous rb_surfaceTable[*surface] (surface); goto nextInteraction; } else #endif { if(oldLight) { // draw the contents of the last shader batch Tess_End(); } if(r_logFile->integer) { // don't just call LogComment, or we will get // a call to va() every frame! GLimp_LogComment(va("----- Beginning Shadow Interaction: %i -----\n", iaCount)); } // we don't need tangent space calculations here Tess_Begin(Tess_StageIteratorShadowFill, NULL, shader, light->shader, qtrue, qfalse, -1, 0); } break; } default: break; } } else { // jump to !ia->next goto nextInteraction; } // change the modelview matrix if needed if(entity != oldEntity) { depthRange = qfalse; if(entity != &tr.worldEntity) { // set up the transformation matrix if(drawShadows) { R_RotateEntityForLight(entity, light, &backEnd.orientation); } else { R_RotateEntityForViewParms(entity, &backEnd.viewParms, &backEnd.orientation); } if(entity->e.renderfx & RF_DEPTHHACK) { // hack the depth range to prevent view model from poking into walls depthRange = qtrue; } } else { // set up the transformation matrix if(drawShadows) { Com_Memset(&backEnd.orientation, 0, sizeof(backEnd.orientation)); backEnd.orientation.axis[0][0] = 1; backEnd.orientation.axis[1][1] = 1; backEnd.orientation.axis[2][2] = 1; VectorCopy(light->l.origin, backEnd.orientation.viewOrigin); MatrixIdentity(backEnd.orientation.transformMatrix); //MatrixAffineInverse(backEnd.orientation.transformMatrix, backEnd.orientation.viewMatrix); Matrix4x4Multiply(light->viewMatrix, backEnd.orientation.transformMatrix, backEnd.orientation.viewMatrix); MatrixCopy(backEnd.orientation.viewMatrix, backEnd.orientation.modelViewMatrix); } else { // transform by the camera placement backEnd.orientation = backEnd.viewParms.world; } } GL_LoadModelViewMatrix(backEnd.orientation.modelViewMatrix); // change depthrange if needed if(oldDepthRange != depthRange) { if(depthRange) { glDepthRange(0, 0.3); } else { glDepthRange(0, 1); } oldDepthRange = depthRange; } } if(drawShadows) { switch (light->l.rlType) { case RL_OMNI: case RL_PROJ: case RL_DIRECTIONAL: { // add the triangles for this surface rb_surfaceTable[*surface] (surface); break; } default: break; } } else { // DO NOTHING //rb_surfaceTable[*surface] (surface, ia->numLightIndexes, ia->lightIndexes, 0, NULL); } nextInteraction: // remember values oldLight = light; oldEntity = entity; oldShader = shader; oldAlphaTest = alphaTest; oldDeformType = deformType; skipInteraction: if(!ia->next) { // if ia->next does not point to any other interaction then // this is the last interaction of the current light if(r_logFile->integer) { // don't just call LogComment, or we will get // a call to va() every frame! GLimp_LogComment(va("----- Last Interaction: %i -----\n", iaCount)); } if(drawShadows) { // draw the contents of the last shader batch Tess_End(); switch (light->l.rlType) { case RL_OMNI: { if(cubeSide == 5) { cubeSide = 0; drawShadows = qfalse; } else { cubeSide++; } // jump back to first interaction of this light ia = &backEnd.viewParms.interactions[iaFirst]; iaCount = iaFirst; break; } case RL_PROJ: { // jump back to first interaction of this light and start lighting ia = &backEnd.viewParms.interactions[iaFirst]; iaCount = iaFirst; drawShadows = qfalse; break; } case RL_DIRECTIONAL: { // set shadow matrix including scale + offset MatrixCopy(bias, light->shadowMatricesBiased[splitFrustumIndex]); MatrixMultiply2(light->shadowMatricesBiased[splitFrustumIndex], light->projectionMatrix); MatrixMultiply2(light->shadowMatricesBiased[splitFrustumIndex], light->viewMatrix); Matrix4x4Multiply(light->projectionMatrix, light->viewMatrix, light->shadowMatrices[splitFrustumIndex]); if(r_parallelShadowSplits->integer) { if(splitFrustumIndex == r_parallelShadowSplits->integer) { splitFrustumIndex = 0; drawShadows = qfalse; } else { splitFrustumIndex++; } // jump back to first interaction of this light ia = &backEnd.viewParms.interactions[iaFirst]; iaCount = iaFirst; } else { // jump back to first interaction of this light and start lighting ia = &backEnd.viewParms.interactions[iaFirst]; iaCount = iaFirst; drawShadows = qfalse; } break; } default: break; } } else { #ifdef VOLUMETRIC_LIGHTING // draw the light volume if needed if(light->shader->volumetricLight) { Render_lightVolume(ia); } #endif if(iaCount < (backEnd.viewParms.numInteractions - 1)) { // jump to next interaction and start shadowing ia++; iaCount++; iaFirst = iaCount; drawShadows = qtrue; } else { // increase last time to leave for loop iaCount++; } } // force updates oldLight = NULL; oldEntity = NULL; oldShader = NULL; } else { // just continue ia = ia->next; iaCount++; } } // go back to the world modelview matrix GL_LoadModelViewMatrix(backEnd.viewParms.world.modelViewMatrix); if(depthRange) { glDepthRange(0, 1); } // reset scissor clamping GL_Scissor(backEnd.viewParms.viewportX, backEnd.viewParms.viewportY, backEnd.viewParms.viewportWidth, backEnd.viewParms.viewportHeight); // reset clear color GL_ClearColor(0.0f, 0.0f, 0.0f, 1.0f); GL_CheckErrors(); if(r_speeds->integer == RSPEEDS_SHADING_TIMES) { glFinish(); endTime = ri.Milliseconds(); backEnd.pc.c_deferredLightingTime = endTime - startTime; } } #ifdef EXPERIMENTAL void RB_RenderScreenSpaceAmbientOcclusion(qboolean deferred) { #if 0 // int i; // vec3_t viewOrigin; // static vec3_t jitter[32]; // static qboolean jitterInit = qfalse; // matrix_t projectMatrix; matrix_t ortho; GLimp_LogComment("--- RB_RenderScreenSpaceAmbientOcclusion ---\n"); if(backEnd.refdef.rdflags & RDF_NOWORLDMODEL) return; if(!r_screenSpaceAmbientOcclusion->integer) return; // enable shader, set arrays GL_BindProgram(&tr.screenSpaceAmbientOcclusionShader); GL_State(GLS_DEPTHTEST_DISABLE); // | GLS_DEPTHMASK_TRUE); GL_Cull(CT_TWO_SIDED); glVertexAttrib4fvARB(ATTR_INDEX_COLOR, colorWhite); // set uniforms /* VectorCopy(backEnd.viewParms.orientation.origin, viewOrigin); // in world space if(!jitterInit) { for(i = 0; i < 32; i++) { float *jit = &jitter[i][0]; float rad = crandom() * 1024.0f; // FIXME radius; float a = crandom() * M_PI * 2; float b = crandom() * M_PI * 2; jit[0] = rad * sin(a) * cos(b); jit[1] = rad * sin(a) * sin(b); jit[2] = rad * cos(a); } jitterInit = qtrue; } MatrixCopy(backEnd.viewParms.projectionMatrix, projectMatrix); MatrixInverse(projectMatrix); glUniform3fARB(tr.screenSpaceAmbientOcclusionShader.u_ViewOrigin, viewOrigin[0], viewOrigin[1], viewOrigin[2]); glUniform3fvARB(tr.screenSpaceAmbientOcclusionShader.u_SSAOJitter, 32, &jitter[0][0]); glUniform1fARB(tr.screenSpaceAmbientOcclusionShader.u_SSAORadius, r_screenSpaceAmbientOcclusionRadius->value); glUniformMatrix4fvARB(tr.screenSpaceAmbientOcclusionShader.u_UnprojectMatrix, 1, GL_FALSE, backEnd.viewParms.unprojectionMatrix); glUniformMatrix4fvARB(tr.screenSpaceAmbientOcclusionShader.u_ProjectMatrix, 1, GL_FALSE, projectMatrix); */ // capture current color buffer for u_CurrentMap GL_SelectTexture(0); GL_Bind(tr.currentRenderImage); glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, tr.currentRenderImage->uploadWidth, tr.currentRenderImage->uploadHeight); // bind u_DepthMap GL_SelectTexture(1); if(deferred) { GL_Bind(tr.deferredPositionFBOImage); } else { GL_Bind(tr.depthRenderImage); glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, tr.depthRenderImage->uploadWidth, tr.depthRenderImage->uploadHeight); } // set 2D virtual screen size GL_PushMatrix(); MatrixOrthogonalProjection(ortho, backEnd.viewParms.viewportX, backEnd.viewParms.viewportX + backEnd.viewParms.viewportWidth, backEnd.viewParms.viewportY, backEnd.viewParms.viewportY + backEnd.viewParms.viewportHeight, -99999, 99999); GL_LoadProjectionMatrix(ortho); GL_LoadModelViewMatrix(matrixIdentity); GLSL_SetUniform_ModelViewProjectionMatrix(&tr.screenSpaceAmbientOcclusionShader, glState.modelViewProjectionMatrix[glState.stackIndex]); // draw viewport Tess_InstantQuad(backEnd.viewParms.viewportVerts); // go back to 3D GL_PopMatrix(); GL_CheckErrors(); #endif } #endif #ifdef EXPERIMENTAL void RB_RenderDepthOfField() { matrix_t ortho; GLimp_LogComment("--- RB_RenderDepthOfField ---\n"); if(backEnd.refdef.rdflags & RDF_NOWORLDMODEL) return; if(!r_depthOfField->integer) return; // enable shader, set arrays GL_BindProgram(&tr.depthOfFieldShader); GL_State(GLS_DEPTHTEST_DISABLE); // | GLS_DEPTHMASK_TRUE); GL_Cull(CT_TWO_SIDED); glVertexAttrib4fvARB(ATTR_INDEX_COLOR, colorWhite); // set uniforms // capture current color buffer for u_CurrentMap GL_SelectTexture(0); if(r_deferredShading->integer && glConfig2.framebufferObjectAvailable && glConfig2.textureFloatAvailable && glConfig2.drawBuffersAvailable && glConfig2.maxDrawBuffers >= 4) { GL_Bind(tr.deferredRenderFBOImage); } else if(r_hdrRendering->integer && glConfig2.framebufferObjectAvailable && glConfig2.textureFloatAvailable) { GL_Bind(tr.deferredRenderFBOImage); } else { GL_Bind(tr.currentRenderImage); glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, tr.currentRenderImage->uploadWidth, tr.currentRenderImage->uploadHeight); } // bind u_DepthMap GL_SelectTexture(1); if(r_deferredShading->integer && glConfig2.framebufferObjectAvailable && glConfig2.textureFloatAvailable && glConfig2.drawBuffersAvailable && glConfig2.maxDrawBuffers >= 4) { GL_Bind(tr.depthRenderImage); } else if(r_hdrRendering->integer && glConfig2.framebufferObjectAvailable && glConfig2.textureFloatAvailable) { GL_Bind(tr.depthRenderImage); } else { // depth texture is not bound to a FBO GL_Bind(tr.depthRenderImage); glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, tr.depthRenderImage->uploadWidth, tr.depthRenderImage->uploadHeight); } // set 2D virtual screen size GL_PushMatrix(); MatrixOrthogonalProjection(ortho, backEnd.viewParms.viewportX, backEnd.viewParms.viewportX + backEnd.viewParms.viewportWidth, backEnd.viewParms.viewportY, backEnd.viewParms.viewportY + backEnd.viewParms.viewportHeight, -99999, 99999); GL_LoadProjectionMatrix(ortho); GL_LoadModelViewMatrix(matrixIdentity); GLSL_SetUniform_ModelViewProjectionMatrix(&tr.depthOfFieldShader, glState.modelViewProjectionMatrix[glState.stackIndex]); // draw viewport Tess_InstantQuad(backEnd.viewParms.viewportVerts); // go back to 3D GL_PopMatrix(); GL_CheckErrors(); } #endif void RB_RenderGlobalFog() { vec3_t local; vec4_t fogDistanceVector, fogDepthVector; vec4_t fogColor; matrix_t ortho; GLimp_LogComment("--- RB_RenderGlobalFog ---\n"); if(backEnd.refdef.rdflags & RDF_NOWORLDMODEL) return; if(r_noFog->integer) return; #if defined(COMPAT_ET) if(!tr.world || tr.world->globalFog < 0) return; #else if(r_forceFog->value <= 0 && VectorLength(tr.fogColor) <= 0) return; if(r_forceFog->value <= 0 && tr.fogDensity <= 0) return; #endif GL_Cull(CT_TWO_SIDED); gl_fogGlobalShader->BindProgram(); // go back to the world modelview matrix backEnd.orientation = backEnd.viewParms.world; gl_fogGlobalShader->SetUniform_ViewOrigin(backEnd.viewParms.orientation.origin); // world space #if defined(COMPAT_ET) { fog_t *fog; fog = &tr.world->fogs[tr.world->globalFog]; if(r_logFile->integer) { GLimp_LogComment(va("--- RB_RenderGlobalFog( fogNum = %i, originalBrushNumber = %i ) ---\n", tr.world->globalFog, fog->originalBrushNumber)); } GL_State(GLS_DEPTHTEST_DISABLE | GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA); // all fogging distance is based on world Z units VectorSubtract(backEnd.orientation.origin, backEnd.viewParms.orientation.origin, local); fogDistanceVector[0] = -backEnd.orientation.modelViewMatrix[2]; fogDistanceVector[1] = -backEnd.orientation.modelViewMatrix[6]; fogDistanceVector[2] = -backEnd.orientation.modelViewMatrix[10]; fogDistanceVector[3] = DotProduct(local, backEnd.viewParms.orientation.axis[0]); // scale the fog vectors based on the fog's thickness fogDistanceVector[0] *= fog->tcScale; fogDistanceVector[1] *= fog->tcScale; fogDistanceVector[2] *= fog->tcScale; fogDistanceVector[3] *= fog->tcScale; gl_fogGlobalShader->SetUniform_FogDistanceVector(fogDistanceVector); gl_fogGlobalShader->SetUniform_Color(fog->color); } #else GL_State(GLS_DEPTHTEST_DISABLE | GLS_SRCBLEND_ONE_MINUS_SRC_ALPHA | GLS_DSTBLEND_SRC_ALPHA); if(r_forceFog->value) { VectorSet4(fogDepthVector, r_forceFog->value, 0, 0, 0); VectorCopy(colorMdGrey, fogColor); } else { VectorSet4(fogDepthVector, tr.fogDensity, 0, 0, 0); VectorCopy(tr.fogColor, fogColor); } gl_fogGlobalShader->SetUniform_FogDepthVector(fogDepthVector); gl_fogGlobalShader->SetUniform_Color(fogColor); #endif gl_fogGlobalShader->SetUniform_ViewMatrix(backEnd.viewParms.world.viewMatrix); gl_fogGlobalShader->SetUniform_UnprojectMatrix(backEnd.viewParms.unprojectionMatrix); // bind u_ColorMap GL_SelectTexture(0); GL_Bind(tr.fogImage); // bind u_DepthMap GL_SelectTexture(1); if(DS_STANDARD_ENABLED() || HDR_ENABLED()) { GL_Bind(tr.depthRenderImage); } else { // depth texture is not bound to a FBO GL_Bind(tr.depthRenderImage); glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, tr.depthRenderImage->uploadWidth, tr.depthRenderImage->uploadHeight); } // set 2D virtual screen size GL_PushMatrix(); MatrixOrthogonalProjection(ortho, backEnd.viewParms.viewportX, backEnd.viewParms.viewportX + backEnd.viewParms.viewportWidth, backEnd.viewParms.viewportY, backEnd.viewParms.viewportY + backEnd.viewParms.viewportHeight, -99999, 99999); GL_LoadProjectionMatrix(ortho); GL_LoadModelViewMatrix(matrixIdentity); gl_fogGlobalShader->SetUniform_ModelViewProjectionMatrix(glState.modelViewProjectionMatrix[glState.stackIndex]); // draw viewport Tess_InstantQuad(backEnd.viewParms.viewportVerts); // go back to 3D GL_PopMatrix(); GL_CheckErrors(); } void RB_RenderBloom() { int i, j; matrix_t ortho; GLimp_LogComment("--- RB_RenderBloom ---\n"); if((backEnd.refdef.rdflags & (RDF_NOWORLDMODEL | RDF_NOBLOOM)) || !r_bloom->integer || backEnd.viewParms.isPortal || !glConfig2.framebufferObjectAvailable) return; // set 2D virtual screen size GL_PushMatrix(); MatrixOrthogonalProjection(ortho, backEnd.viewParms.viewportX, backEnd.viewParms.viewportX + backEnd.viewParms.viewportWidth, backEnd.viewParms.viewportY, backEnd.viewParms.viewportY + backEnd.viewParms.viewportHeight, -99999, 99999); GL_LoadProjectionMatrix(ortho); GL_LoadModelViewMatrix(matrixIdentity); // FIXME //if(glConfig.hardwareType != GLHW_ATI && glConfig.hardwareType != GLHW_ATI_DX10) { GL_State(GLS_DEPTHTEST_DISABLE); GL_Cull(CT_TWO_SIDED); GL_PushMatrix(); GL_LoadModelViewMatrix(matrixIdentity); #if 1 MatrixOrthogonalProjection(ortho, 0, tr.contrastRenderFBO->width, 0, tr.contrastRenderFBO->height, -99999, 99999); GL_LoadProjectionMatrix(ortho); #endif if(DS_STANDARD_ENABLED()) { if(HDR_ENABLED()) { gl_toneMappingShader->EnableMacro_BRIGHTPASS_FILTER(); gl_toneMappingShader->BindProgram(); gl_toneMappingShader->SetUniform_HDRKey(backEnd.hdrKey); gl_toneMappingShader->SetUniform_HDRAverageLuminance(backEnd.hdrAverageLuminance); gl_toneMappingShader->SetUniform_HDRMaxLuminance(backEnd.hdrMaxLuminance); gl_toneMappingShader->SetUniform_ModelViewProjectionMatrix(glState.modelViewProjectionMatrix[glState.stackIndex]); } else { gl_contrastShader->BindProgram(); gl_contrastShader->SetUniform_ModelViewProjectionMatrix(glState.modelViewProjectionMatrix[glState.stackIndex]); } GL_SelectTexture(0); GL_Bind(tr.downScaleFBOImage_quarter); } else if(HDR_ENABLED()) { gl_toneMappingShader->EnableMacro_BRIGHTPASS_FILTER(); gl_toneMappingShader->BindProgram(); gl_toneMappingShader->SetUniform_HDRKey(backEnd.hdrKey); gl_toneMappingShader->SetUniform_HDRAverageLuminance(backEnd.hdrAverageLuminance); gl_toneMappingShader->SetUniform_HDRMaxLuminance(backEnd.hdrMaxLuminance); gl_toneMappingShader->SetUniform_ModelViewProjectionMatrix(glState.modelViewProjectionMatrix[glState.stackIndex]); GL_SelectTexture(0); GL_Bind(tr.downScaleFBOImage_quarter); } else { // render contrast downscaled to 1/4th of the screen gl_contrastShader->BindProgram(); gl_contrastShader->SetUniform_ModelViewProjectionMatrix(glState.modelViewProjectionMatrix[glState.stackIndex]); GL_SelectTexture(0); //GL_Bind(tr.downScaleFBOImage_quarter); GL_Bind(tr.currentRenderImage); glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, tr.currentRenderImage->uploadWidth, tr.currentRenderImage->uploadHeight); } GL_PopMatrix(); // special 1/4th of the screen contrastRenderFBO ortho R_BindFBO(tr.contrastRenderFBO); GL_ClearColor(0.0f, 0.0f, 0.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); // draw viewport Tess_InstantQuad(backEnd.viewParms.viewportVerts); // render bloom in multiple passes for(i = 0; i < 2; i++) { for(j = 0; j < r_bloomPasses->integer; j++) { R_BindFBO(tr.bloomRenderFBO[(j + 1) % 2]); GL_ClearColor(0.0f, 0.0f, 0.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); GL_State(GLS_DEPTHTEST_DISABLE); GL_SelectTexture(0); if(j == 0) GL_Bind(tr.contrastRenderFBOImage); else GL_Bind(tr.bloomRenderFBOImage[j % 2]); GL_PushMatrix(); GL_LoadModelViewMatrix(matrixIdentity); MatrixOrthogonalProjection(ortho, 0, tr.bloomRenderFBO[0]->width, 0, tr.bloomRenderFBO[0]->height, -99999, 99999); GL_LoadProjectionMatrix(ortho); if(i == 0) { gl_blurXShader->BindProgram(); gl_blurXShader->SetUniform_DeformMagnitude(r_bloomBlur->value); gl_blurXShader->SetUniform_ModelViewProjectionMatrix(glState.modelViewProjectionMatrix[glState.stackIndex]); } else { gl_blurYShader->BindProgram(); gl_blurYShader->SetUniform_DeformMagnitude(r_bloomBlur->value); gl_blurYShader->SetUniform_ModelViewProjectionMatrix(glState.modelViewProjectionMatrix[glState.stackIndex]); } GL_PopMatrix(); Tess_InstantQuad(backEnd.viewParms.viewportVerts); } // add offscreen processed bloom to screen if(DS_STANDARD_ENABLED()) { R_BindFBO(tr.geometricRenderFBO); glDrawBuffers(1, geometricRenderTargets); gl_screenShader->BindProgram(); GL_State(GLS_DEPTHTEST_DISABLE | GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE); glVertexAttrib4fvARB(ATTR_INDEX_COLOR, colorWhite); gl_screenShader->SetUniform_ModelViewProjectionMatrix(glState.modelViewProjectionMatrix[glState.stackIndex]); GL_SelectTexture(0); GL_Bind(tr.bloomRenderFBOImage[j % 2]); } else if(HDR_ENABLED()) { R_BindFBO(tr.deferredRenderFBO); gl_screenShader->BindProgram(); GL_State(GLS_DEPTHTEST_DISABLE | GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE); glVertexAttrib4fvARB(ATTR_INDEX_COLOR, colorWhite); gl_screenShader->SetUniform_ModelViewProjectionMatrix(glState.modelViewProjectionMatrix[glState.stackIndex]); GL_SelectTexture(0); GL_Bind(tr.bloomRenderFBOImage[j % 2]); //GL_Bind(tr.contrastRenderFBOImage); } else { R_BindNullFBO(); gl_screenShader->BindProgram(); GL_State(GLS_DEPTHTEST_DISABLE | GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE); glVertexAttrib4fvARB(ATTR_INDEX_COLOR, colorWhite); gl_screenShader->SetUniform_ModelViewProjectionMatrix(glState.modelViewProjectionMatrix[glState.stackIndex]); GL_SelectTexture(0); GL_Bind(tr.bloomRenderFBOImage[j % 2]); //GL_Bind(tr.contrastRenderFBOImage); } Tess_InstantQuad(backEnd.viewParms.viewportVerts); } } // go back to 3D GL_PopMatrix(); GL_CheckErrors(); } void RB_RenderRotoscope(void) { #if 0 //!defined(GLSL_COMPILE_STARTUP_ONLY) matrix_t ortho; GLimp_LogComment("--- RB_CameraPostFX ---\n"); if((backEnd.refdef.rdflags & RDF_NOWORLDMODEL) || !r_rotoscope->integer || backEnd.viewParms.isPortal) return; // set 2D virtual screen size GL_PushMatrix(); MatrixOrthogonalProjection(ortho, backEnd.viewParms.viewportX, backEnd.viewParms.viewportX + backEnd.viewParms.viewportWidth, backEnd.viewParms.viewportY, backEnd.viewParms.viewportY + backEnd.viewParms.viewportHeight, -99999, 99999); GL_LoadProjectionMatrix(ortho); GL_LoadModelViewMatrix(matrixIdentity); GL_State(GLS_DEPTHTEST_DISABLE); GL_Cull(CT_TWO_SIDED); // enable shader, set arrays GL_BindProgram(&tr.rotoscopeShader); GLSL_SetUniform_ModelViewProjectionMatrix(&tr.rotoscopeShader, glState.modelViewProjectionMatrix[glState.stackIndex]); glUniform1fARB(tr.rotoscopeShader.u_BlurMagnitude, r_bloomBlur->value); GL_SelectTexture(0); GL_Bind(tr.currentRenderImage); glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, tr.currentRenderImage->uploadWidth, tr.currentRenderImage->uploadHeight); // draw viewport Tess_InstantQuad(backEnd.viewParms.viewportVerts); // go back to 3D GL_PopMatrix(); GL_CheckErrors(); #endif } void RB_CameraPostFX(void) { matrix_t ortho; matrix_t grain; GLimp_LogComment("--- RB_CameraPostFX ---\n"); if((backEnd.refdef.rdflags & RDF_NOWORLDMODEL) || !r_cameraPostFX->integer || backEnd.viewParms.isPortal || !tr.grainImage || !tr.vignetteImage) return; // set 2D virtual screen size GL_PushMatrix(); MatrixOrthogonalProjection(ortho, backEnd.viewParms.viewportX, backEnd.viewParms.viewportX + backEnd.viewParms.viewportWidth, backEnd.viewParms.viewportY, backEnd.viewParms.viewportY + backEnd.viewParms.viewportHeight, -99999, 99999); GL_LoadProjectionMatrix(ortho); GL_LoadModelViewMatrix(matrixIdentity); GL_State(GLS_DEPTHTEST_DISABLE); GL_Cull(CT_TWO_SIDED); // enable shader, set arrays gl_cameraEffectsShader->BindProgram(); gl_cameraEffectsShader->SetUniform_ModelViewProjectionMatrix(glState.modelViewProjectionMatrix[glState.stackIndex]); //glUniform1fARB(tr.cameraEffectsShader.u_BlurMagnitude, r_bloomBlur->value); MatrixIdentity(grain); MatrixMultiplyScale(grain, r_cameraFilmGrainScale->value, r_cameraFilmGrainScale->value, 0); MatrixMultiplyTranslation(grain, backEnd.refdef.floatTime * 10, backEnd.refdef.floatTime * 10, 0); MatrixMultiplyTranslation(grain, 0.5, 0.5, 0.0); MatrixMultiplyZRotation(grain, backEnd.refdef.floatTime * (random() * 7)); MatrixMultiplyTranslation(grain, -0.5, -0.5, 0.0); gl_cameraEffectsShader->SetUniform_ColorTextureMatrix(grain); // bind u_CurrentMap GL_SelectTexture(0); GL_Bind(tr.occlusionRenderFBOImage); /* if(glConfig.framebufferObjectAvailable && glConfig.textureFloatAvailable) { // copy depth of the main context to deferredRenderFBO glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, 0); glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, tr.occlusionRenderFBO->frameBuffer); glBlitFramebufferEXT(0, 0, glConfig.vidWidth, glConfig.vidHeight, 0, 0, glConfig.vidWidth, glConfig.vidHeight, GL_COLOR_BUFFER_BIT, GL_NEAREST); } else */ { glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, tr.occlusionRenderFBOImage->uploadWidth, tr.occlusionRenderFBOImage->uploadHeight); } // bind u_GrainMap GL_SelectTexture(1); GL_Bind(tr.grainImage); //GL_Bind(tr.defaultImage); // bind u_VignetteMap GL_SelectTexture(2); if(r_cameraVignette->integer) { GL_Bind(tr.vignetteImage); } else { GL_Bind(tr.whiteImage); } // draw viewport Tess_InstantQuad(backEnd.viewParms.viewportVerts); // go back to 3D GL_PopMatrix(); GL_CheckErrors(); } static void RB_CalculateAdaptation() { int i; static float image[64 * 64 * 4]; float curTime; float deltaTime; float luminance; float avgLuminance; float maxLuminance; double sum; const vec3_t LUMINANCE_VECTOR = {0.2125f, 0.7154f, 0.0721f}; vec4_t color; float newAdaptation; float newMaximum; curTime = ri.Milliseconds() / 1000.0f; // calculate the average scene luminance R_BindFBO(tr.downScaleFBO_64x64); // read back the contents // glFinish(); glReadPixels(0, 0, 64, 64, GL_RGBA, GL_FLOAT, image); sum = 0.0f; maxLuminance = 0.0f; for(i = 0; i < (64 * 64 * 4); i += 4) { color[0] = image[i + 0]; color[1] = image[i + 1]; color[2] = image[i + 2]; color[3] = image[i + 3]; luminance = DotProduct(color, LUMINANCE_VECTOR) + 0.0001f; if(luminance > maxLuminance) maxLuminance = luminance; sum += log(luminance); } sum /= (64.0f * 64.0f); avgLuminance = exp(sum); // the user's adapted luminance level is simulated by closing the gap between // adapted luminance and current luminance by 2% every frame, based on a // 30 fps rate. This is not an accurate model of human adaptation, which can // take longer than half an hour. if(backEnd.hdrTime > curTime) backEnd.hdrTime = curTime; deltaTime = curTime - backEnd.hdrTime; //if(r_hdrMaxLuminance->value) { Q_clamp(backEnd.hdrAverageLuminance, r_hdrMinLuminance->value, r_hdrMaxLuminance->value); Q_clamp(avgLuminance, r_hdrMinLuminance->value, r_hdrMaxLuminance->value); Q_clamp(backEnd.hdrMaxLuminance, r_hdrMinLuminance->value, r_hdrMaxLuminance->value); Q_clamp(maxLuminance, r_hdrMinLuminance->value, r_hdrMaxLuminance->value); } newAdaptation = backEnd.hdrAverageLuminance + (avgLuminance - backEnd.hdrAverageLuminance) * (1.0f - powf(0.98f, 30.0f * deltaTime)); newMaximum = backEnd.hdrMaxLuminance + (maxLuminance - backEnd.hdrMaxLuminance) * (1.0f - powf(0.98f, 30.0f * deltaTime)); if(!Q_isnan(newAdaptation) && !Q_isnan(newMaximum)) { #if 1 backEnd.hdrAverageLuminance = newAdaptation; backEnd.hdrMaxLuminance = newMaximum; #else backEnd.hdrAverageLuminance = avgLuminance; backEnd.hdrMaxLuminance = maxLuminance; #endif } backEnd.hdrTime = curTime; // calculate HDR image key if(r_hdrKey->value <= 0) { // calculation from: Perceptual Effects in Real-time Tone Mapping - Krawczyk et al. backEnd.hdrKey = 1.03 - 2.0 / (2.0 + log10f(backEnd.hdrAverageLuminance + 1.0f)); } else { backEnd.hdrKey = r_hdrKey->value; } if(r_hdrDebug->integer) { ri.Printf(PRINT_ALL, "HDR luminance avg = %f, max = %f, key = %f\n", backEnd.hdrAverageLuminance, backEnd.hdrMaxLuminance, backEnd.hdrKey); } GL_CheckErrors(); } void RB_RenderDeferredShadingResultToFrameBuffer() { matrix_t ortho; GLimp_LogComment("--- RB_RenderDeferredShadingResultToFrameBuffer ---\n"); R_BindNullFBO(); /* if(backEnd.refdef.rdflags & RDF_NOWORLDMODEL) { GL_State(GLS_DEPTHTEST_DISABLE | GLS_DEPTHMASK_TRUE | GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE); } else */ { GL_State(GLS_DEPTHTEST_DISABLE); // | GLS_DEPTHMASK_TRUE); } GL_Cull(CT_TWO_SIDED); // set uniforms // set 2D virtual screen size GL_PushMatrix(); MatrixOrthogonalProjection(ortho, backEnd.viewParms.viewportX, backEnd.viewParms.viewportX + backEnd.viewParms.viewportWidth, backEnd.viewParms.viewportY, backEnd.viewParms.viewportY + backEnd.viewParms.viewportHeight, -99999, 99999); GL_LoadProjectionMatrix(ortho); GL_LoadModelViewMatrix(matrixIdentity); if(!(backEnd.refdef.rdflags & RDF_NOWORLDMODEL) && r_hdrRendering->integer) { R_BindNullFBO(); gl_toneMappingShader->DisableMacro_BRIGHTPASS_FILTER(); gl_toneMappingShader->BindProgram(); gl_toneMappingShader->SetUniform_HDRKey(backEnd.hdrKey); gl_toneMappingShader->SetUniform_HDRAverageLuminance(backEnd.hdrAverageLuminance); gl_toneMappingShader->SetUniform_HDRMaxLuminance(backEnd.hdrMaxLuminance); gl_toneMappingShader->SetUniform_ModelViewProjectionMatrix(glState.modelViewProjectionMatrix[glState.stackIndex]); // bind u_ColorMap GL_SelectTexture(0); GL_Bind(tr.deferredRenderFBOImage); } else { gl_screenShader->BindProgram(); glVertexAttrib4fvARB(ATTR_INDEX_COLOR, colorWhite); gl_screenShader->SetUniform_ModelViewProjectionMatrix(glState.modelViewProjectionMatrix[glState.stackIndex]); // bind u_ColorMap GL_SelectTexture(0); if(r_showDeferredDiffuse->integer) { GL_Bind(tr.deferredDiffuseFBOImage); } else if(r_showDeferredNormal->integer) { GL_Bind(tr.deferredNormalFBOImage); } else if(r_showDeferredSpecular->integer) { GL_Bind(tr.deferredSpecularFBOImage); } else if(r_showDeferredPosition->integer) { GL_Bind(tr.depthRenderImage); } else if(r_showDeferredLight->integer) { GL_Bind(tr.lightRenderFBOImage); } else { GL_Bind(tr.deferredRenderFBOImage); } } GL_CheckErrors(); Tess_InstantQuad(backEnd.viewParms.viewportVerts); GL_PopMatrix(); } void RB_RenderDeferredHDRResultToFrameBuffer() { matrix_t ortho; GLimp_LogComment("--- RB_RenderDeferredHDRResultToFrameBuffer ---\n"); if(!r_hdrRendering->integer || !glConfig2.framebufferObjectAvailable || !glConfig2.textureFloatAvailable) return; GL_CheckErrors(); R_BindNullFBO(); // bind u_CurrentMap GL_SelectTexture(0); GL_Bind(tr.deferredRenderFBOImage); GL_State(GLS_DEPTHTEST_DISABLE); GL_Cull(CT_TWO_SIDED); // set uniforms // set 2D virtual screen size GL_PushMatrix(); MatrixOrthogonalProjection(ortho, backEnd.viewParms.viewportX, backEnd.viewParms.viewportX + backEnd.viewParms.viewportWidth, backEnd.viewParms.viewportY, backEnd.viewParms.viewportY + backEnd.viewParms.viewportHeight, -99999, 99999); GL_LoadProjectionMatrix(ortho); GL_LoadModelViewMatrix(matrixIdentity); if(backEnd.refdef.rdflags & RDF_NOWORLDMODEL) { gl_screenShader->BindProgram(); glVertexAttrib4fvARB(ATTR_INDEX_COLOR, colorWhite); gl_screenShader->SetUniform_ModelViewProjectionMatrix(glState.modelViewProjectionMatrix[glState.stackIndex]); } else { gl_toneMappingShader->DisableMacro_BRIGHTPASS_FILTER(); gl_toneMappingShader->BindProgram(); gl_toneMappingShader->SetUniform_HDRKey(backEnd.hdrKey); gl_toneMappingShader->SetUniform_HDRAverageLuminance(backEnd.hdrAverageLuminance); gl_toneMappingShader->SetUniform_HDRMaxLuminance(backEnd.hdrMaxLuminance); gl_toneMappingShader->SetUniform_ModelViewProjectionMatrix(glState.modelViewProjectionMatrix[glState.stackIndex]); } GL_CheckErrors(); Tess_InstantQuad(backEnd.viewParms.viewportVerts); GL_PopMatrix(); } // ================================================================================================ // // LIGHTS OCCLUSION CULLING // // ================================================================================================ static void RenderLightOcclusionVolume( trRefLight_t * light) { int j; vec4_t quadVerts[4]; GL_CheckErrors(); #if 1 if(light->isStatic && light->frustumVBO && light->frustumIBO) { // render in world space backEnd.orientation = backEnd.viewParms.world; GL_LoadModelViewMatrix(backEnd.viewParms.world.modelViewMatrix); gl_genericShader->SetUniform_ModelViewProjectionMatrix(glState.modelViewProjectionMatrix[glState.stackIndex]); R_BindVBO(light->frustumVBO); R_BindIBO(light->frustumIBO); GL_VertexAttribsState(ATTR_POSITION); tess.numVertexes = light->frustumVerts; tess.numIndexes = light->frustumIndexes; Tess_DrawElements(); } else #endif { // render in light space R_RotateLightForViewParms(light, &backEnd.viewParms, &backEnd.orientation); GL_LoadModelViewMatrix(backEnd.orientation.modelViewMatrix); gl_genericShader->SetUniform_ModelViewProjectionMatrix(glState.modelViewProjectionMatrix[glState.stackIndex]); tess.multiDrawPrimitives = 0; tess.numIndexes = 0; tess.numVertexes = 0; switch (light->l.rlType) { case RL_OMNI: { Tess_AddCube(vec3_origin, light->localBounds[0], light->localBounds[1], colorWhite); Tess_UpdateVBOs(ATTR_POSITION | ATTR_COLOR); Tess_DrawElements(); break; } case RL_PROJ: { vec3_t farCorners[4]; vec4_t *frustum = light->localFrustum; PlanesGetIntersectionPoint(frustum[FRUSTUM_LEFT], frustum[FRUSTUM_TOP], frustum[FRUSTUM_FAR], farCorners[0]); PlanesGetIntersectionPoint(frustum[FRUSTUM_RIGHT], frustum[FRUSTUM_TOP], frustum[FRUSTUM_FAR], farCorners[1]); PlanesGetIntersectionPoint(frustum[FRUSTUM_RIGHT], frustum[FRUSTUM_BOTTOM], frustum[FRUSTUM_FAR], farCorners[2]); PlanesGetIntersectionPoint(frustum[FRUSTUM_LEFT], frustum[FRUSTUM_BOTTOM], frustum[FRUSTUM_FAR], farCorners[3]); tess.multiDrawPrimitives = 0; tess.numVertexes = 0; tess.numIndexes = 0; if(!VectorCompare(light->l.projStart, vec3_origin)) { vec3_t nearCorners[4]; // calculate the vertices defining the top area PlanesGetIntersectionPoint(frustum[FRUSTUM_LEFT], frustum[FRUSTUM_TOP], frustum[FRUSTUM_NEAR], nearCorners[0]); PlanesGetIntersectionPoint(frustum[FRUSTUM_RIGHT], frustum[FRUSTUM_TOP], frustum[FRUSTUM_NEAR], nearCorners[1]); PlanesGetIntersectionPoint(frustum[FRUSTUM_RIGHT], frustum[FRUSTUM_BOTTOM], frustum[FRUSTUM_NEAR], nearCorners[2]); PlanesGetIntersectionPoint(frustum[FRUSTUM_LEFT], frustum[FRUSTUM_BOTTOM], frustum[FRUSTUM_NEAR], nearCorners[3]); // draw outer surfaces for(j = 0; j < 4; j++) { VectorSet4(quadVerts[0], nearCorners[j][0], nearCorners[j][1], nearCorners[j][2], 1); VectorSet4(quadVerts[1], farCorners[j][0], farCorners[j][1], farCorners[j][2], 1); VectorSet4(quadVerts[2], farCorners[(j + 1) % 4][0], farCorners[(j + 1) % 4][1], farCorners[(j + 1) % 4][2], 1); VectorSet4(quadVerts[3], nearCorners[(j + 1) % 4][0], nearCorners[(j + 1) % 4][1], nearCorners[(j + 1) % 4][2], 1); Tess_AddQuadStamp2(quadVerts, colorCyan); } // draw far cap VectorSet4(quadVerts[0], farCorners[3][0], farCorners[3][1], farCorners[3][2], 1); VectorSet4(quadVerts[1], farCorners[2][0], farCorners[2][1], farCorners[2][2], 1); VectorSet4(quadVerts[2], farCorners[1][0], farCorners[1][1], farCorners[1][2], 1); VectorSet4(quadVerts[3], farCorners[0][0], farCorners[0][1], farCorners[0][2], 1); Tess_AddQuadStamp2(quadVerts, colorRed); // draw near cap VectorSet4(quadVerts[0], nearCorners[0][0], nearCorners[0][1], nearCorners[0][2], 1); VectorSet4(quadVerts[1], nearCorners[1][0], nearCorners[1][1], nearCorners[1][2], 1); VectorSet4(quadVerts[2], nearCorners[2][0], nearCorners[2][1], nearCorners[2][2], 1); VectorSet4(quadVerts[3], nearCorners[3][0], nearCorners[3][1], nearCorners[3][2], 1); Tess_AddQuadStamp2(quadVerts, colorGreen); } else { vec3_t top; // no light_start, just use the top vertex (doesn't need to be mirrored) PlanesGetIntersectionPoint(frustum[FRUSTUM_LEFT], frustum[FRUSTUM_RIGHT], frustum[FRUSTUM_TOP], top); // draw pyramid for(j = 0; j < 4; j++) { VectorCopy(farCorners[j], tess.xyz[tess.numVertexes]); Vector4Copy(colorCyan, tess.colors[tess.numVertexes]); tess.indexes[tess.numIndexes++] = tess.numVertexes; tess.numVertexes++; VectorCopy(farCorners[(j + 1) % 4], tess.xyz[tess.numVertexes]); Vector4Copy(colorCyan, tess.colors[tess.numVertexes]); tess.indexes[tess.numIndexes++] = tess.numVertexes; tess.numVertexes++; VectorCopy(top, tess.xyz[tess.numVertexes]); Vector4Copy(colorCyan, tess.colors[tess.numVertexes]); tess.indexes[tess.numIndexes++] = tess.numVertexes; tess.numVertexes++; } VectorSet4(quadVerts[0], farCorners[3][0], farCorners[3][1], farCorners[3][2], 1); VectorSet4(quadVerts[1], farCorners[2][0], farCorners[2][1], farCorners[2][2], 1); VectorSet4(quadVerts[2], farCorners[1][0], farCorners[1][1], farCorners[1][2], 1); VectorSet4(quadVerts[3], farCorners[0][0], farCorners[0][1], farCorners[0][2], 1); Tess_AddQuadStamp2(quadVerts, colorRed); } Tess_UpdateVBOs(ATTR_POSITION | ATTR_COLOR); Tess_DrawElements(); break; } default: break; } } tess.multiDrawPrimitives = 0; tess.numIndexes = 0; tess.numVertexes = 0; GL_CheckErrors(); } static void IssueLightOcclusionQuery(link_t * queue, trRefLight_t * light, qboolean resetMultiQueryLink) { GLimp_LogComment("--- IssueLightOcclusionQuery ---\n"); //ri.Printf(PRINT_ALL, "--- IssueOcclusionQuery(%i) ---\n", node - tr.world->nodes); if(tr.numUsedOcclusionQueryObjects < (MAX_OCCLUSION_QUERIES -1)) { light->occlusionQueryObject = tr.occlusionQueryObjects[tr.numUsedOcclusionQueryObjects++]; } else { light->occlusionQueryObject = 0; } EnQueue(queue, light); // tell GetOcclusionQueryResult that this is not a multi query if(resetMultiQueryLink) { QueueInit(&light->multiQuery); } if(light->occlusionQueryObject > 0) { GL_CheckErrors(); // begin the occlusion query glBeginQueryARB(GL_SAMPLES_PASSED, light->occlusionQueryObject); GL_CheckErrors(); RenderLightOcclusionVolume(light); // end the query glEndQueryARB(GL_SAMPLES_PASSED); #if 1 if(!glIsQueryARB(light->occlusionQueryObject)) { ri.Error(ERR_FATAL, "IssueLightOcclusionQuery: light %i has no occlusion query object in slot %i: %i", light - tr.world->lights, backEnd.viewParms.viewCount, light->occlusionQueryObject); } #endif //light->occlusionQueryNumbers[backEnd.viewParms.viewCount] = backEnd.pc.c_occlusionQueries; backEnd.pc.c_occlusionQueries++; } GL_CheckErrors(); } static void IssueLightMultiOcclusionQueries(link_t * multiQueue, link_t * individualQueue) { trRefLight_t *light; trRefLight_t *multiQueryLight; link_t *l; GLimp_LogComment("--- IssueLightMultiOcclusionQueries ---\n"); #if 0 ri.Printf(PRINT_ALL, "IssueLightMultiOcclusionQueries("); for(l = multiQueue->prev; l != multiQueue; l = l->prev) { light = (trRefLight_t *) l->data; ri.Printf(PRINT_ALL, "%i, ", light - backEnd.refdef.lights); } ri.Printf(PRINT_ALL, ")\n"); #endif if(QueueEmpty(multiQueue)) return; multiQueryLight = (trRefLight_t *) QueueFront(multiQueue)->data; if(tr.numUsedOcclusionQueryObjects < (MAX_OCCLUSION_QUERIES -1)) { multiQueryLight->occlusionQueryObject = tr.occlusionQueryObjects[tr.numUsedOcclusionQueryObjects++]; } else { multiQueryLight->occlusionQueryObject = 0; } if(multiQueryLight->occlusionQueryObject > 0) { // begin the occlusion query GL_CheckErrors(); glBeginQueryARB(GL_SAMPLES_PASSED, multiQueryLight->occlusionQueryObject); GL_CheckErrors(); //ri.Printf(PRINT_ALL, "rendering nodes:["); for(l = multiQueue->prev; l != multiQueue; l = l->prev) { light = (trRefLight_t *) l->data; //ri.Printf(PRINT_ALL, "%i, ", light - backEnd.refdef.lights); RenderLightOcclusionVolume(light); } //ri.Printf(PRINT_ALL, "]\n"); backEnd.pc.c_occlusionQueries++; backEnd.pc.c_occlusionQueriesMulti++; // end the query glEndQueryARB(GL_SAMPLES_PASSED); GL_CheckErrors(); #if 0 if(!glIsQueryARB(multiQueryNode->occlusionQueryObjects[backEnd.viewParms.viewCount])) { ri.Error(ERR_FATAL, "IssueMultiOcclusionQueries: node %i has no occlusion query object in slot %i: %i", multiQueryNode - tr.world->nodes, backEnd.viewParms.viewCount, multiQueryNode->occlusionQueryObjects[backEnd.viewParms.viewCount]); } #endif } // move queue to node->multiQuery queue QueueInit(&multiQueryLight->multiQuery); DeQueue(multiQueue); while(!QueueEmpty(multiQueue)) { light = (trRefLight_t *) DeQueue(multiQueue); EnQueue(&multiQueryLight->multiQuery, light); } EnQueue(individualQueue, multiQueryLight); //ri.Printf(PRINT_ALL, "--- IssueMultiOcclusionQueries end ---\n"); } static qboolean LightOcclusionResultAvailable(trRefLight_t *light) { GLint available; if(light->occlusionQueryObject > 0) { glFinish(); available = 0; //if(glIsQueryARB(light->occlusionQueryObjects[backEnd.viewParms.viewCount])) { glGetQueryObjectivARB(light->occlusionQueryObject, GL_QUERY_RESULT_AVAILABLE_ARB, &available); GL_CheckErrors(); } return (qboolean) available; } return qtrue; } static void GetLightOcclusionQueryResult(trRefLight_t *light) { link_t *l, *sentinel; int ocSamples; GLint available; GLimp_LogComment("--- GetLightOcclusionQueryResult ---\n"); if(light->occlusionQueryObject > 0) { glFinish(); #if 0 if(!glIsQueryARB(node->occlusionQueryObjects[backEnd.viewParms.viewCount])) { ri.Error(ERR_FATAL, "GetOcclusionQueryResult: node %i has no occlusion query object in slot %i: %i", node - tr.world->nodes, backEnd.viewParms.viewCount, node->occlusionQueryObjects[backEnd.viewParms.viewCount]); } #endif available = 0; while(!available) { //if(glIsQueryARB(node->occlusionQueryObjects[backEnd.viewParms.viewCount])) { glGetQueryObjectivARB(light->occlusionQueryObject, GL_QUERY_RESULT_AVAILABLE_ARB, &available); //GL_CheckErrors(); } } backEnd.pc.c_occlusionQueriesAvailable++; glGetQueryObjectivARB(light->occlusionQueryObject, GL_QUERY_RESULT, &ocSamples); //ri.Printf(PRINT_ALL, "GetOcclusionQueryResult(%i): available = %i, samples = %i\n", node - tr.world->nodes, available, ocSamples); GL_CheckErrors(); } else { ocSamples = 1; } light->occlusionQuerySamples = ocSamples; // copy result to all nodes that were linked to this multi query node sentinel = &light->multiQuery; for(l = sentinel->prev; l != sentinel; l = l->prev) { light = (trRefLight_t *) l->data; light->occlusionQuerySamples = ocSamples; } } static int LightCompare(const void *a, const void *b) { trRefLight_t *l1, *l2; float d1, d2; l1 = (trRefLight_t *) *(void **)a; l2 = (trRefLight_t *) *(void **)b; d1 = DistanceSquared(backEnd.viewParms.orientation.origin, l1->l.origin); d2 = DistanceSquared(backEnd.viewParms.orientation.origin, l2->l.origin); if(d1 < d2) { return -1; } if(d1 > d2) { return 1; } return 0; } void RB_RenderLightOcclusionQueries() { GLimp_LogComment("--- RB_RenderLightOcclusionQueries ---\n"); if(glConfig2.occlusionQueryBits && glConfig.driverType != GLDRV_MESA && r_dynamicLightOcclusionCulling->integer && !(backEnd.refdef.rdflags & RDF_NOWORLDMODEL)) { int i; interaction_t *ia; int iaCount; int iaFirst; trRefLight_t *light, *oldLight, *multiQueryLight; GLint ocSamples = 0; qboolean queryObjects; link_t occlusionQueryQueue; link_t invisibleQueue; growList_t invisibleList; int startTime = 0, endTime = 0; glVertexAttrib4fARB(ATTR_INDEX_COLOR, 1.0f, 0.0f, 0.0f, 0.05f); if(r_speeds->integer == RSPEEDS_OCCLUSION_QUERIES) { glFinish(); startTime = ri.Milliseconds(); } gl_genericShader->DisableAlphaTesting(); gl_genericShader->DisablePortalClipping(); gl_genericShader->DisableVertexSkinning(); gl_genericShader->DisableVertexAnimation(); gl_genericShader->DisableDeformVertexes(); gl_genericShader->DisableTCGenEnvironment(); gl_genericShader->BindProgram(); gl_genericShader->SetRequiredVertexPointers(); GL_Cull(CT_TWO_SIDED); GL_LoadProjectionMatrix(backEnd.viewParms.projectionMatrix); // set uniforms gl_genericShader->SetUniform_ColorModulate(CGEN_VERTEX, AGEN_VERTEX); gl_genericShader->SetUniform_Color(colorBlack); // bind u_ColorMap GL_SelectTexture(0); GL_Bind(tr.whiteImage); gl_genericShader->SetUniform_ColorTextureMatrix(matrixIdentity); // don't write to the color buffer or depth buffer if(r_showOcclusionQueries->integer) { GL_State(GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE); } else { GL_State(GLS_COLORMASK_BITS); } tr.numUsedOcclusionQueryObjects = 0; QueueInit(&occlusionQueryQueue); QueueInit(&invisibleQueue); Com_InitGrowList(&invisibleList, 1000); // loop trough all light interactions and render the light OBB for each last interaction for(iaCount = 0, ia = &backEnd.viewParms.interactions[0]; iaCount < backEnd.viewParms.numInteractions;) { backEnd.currentLight = light = ia->light; ia->occlusionQuerySamples = 1; if(!ia->next) { // last interaction of current light if(!ia->noOcclusionQueries) { Com_AddToGrowList(&invisibleList, light); } if(iaCount < (backEnd.viewParms.numInteractions - 1)) { // jump to next interaction and continue ia++; iaCount++; } else { // increase last time to leave for loop iaCount++; } } else { // just continue ia = ia->next; iaCount++; } } // sort lights by distance qsort(invisibleList.elements, invisibleList.currentElements, sizeof(void *), LightCompare); for(i = 0; i < invisibleList.currentElements; i++) { light = (trRefLight_t *) Com_GrowListElement(&invisibleList, i); EnQueue(&invisibleQueue, light); if((invisibleList.currentElements - i) <= 100) { if(QueueSize(&invisibleQueue) >= 10) IssueLightMultiOcclusionQueries(&invisibleQueue, &occlusionQueryQueue); } else { if(QueueSize(&invisibleQueue) >= 50) IssueLightMultiOcclusionQueries(&invisibleQueue, &occlusionQueryQueue); } } Com_DestroyGrowList(&invisibleList); if(!QueueEmpty(&invisibleQueue)) { // remaining previously invisible node queries IssueLightMultiOcclusionQueries(&invisibleQueue, &occlusionQueryQueue); //ri.Printf(PRINT_ALL, "occlusionQueryQueue.empty() = %i\n", QueueEmpty(&occlusionQueryQueue)); } // go back to the world modelview matrix backEnd.orientation = backEnd.viewParms.world; GL_LoadModelViewMatrix(backEnd.viewParms.world.modelViewMatrix); while(!QueueEmpty(&occlusionQueryQueue)) { if(LightOcclusionResultAvailable((trRefLight_t *) QueueFront(&occlusionQueryQueue)->data)) { light = (trRefLight_t *) DeQueue(&occlusionQueryQueue); // wait if result not available GetLightOcclusionQueryResult(light); if(light->occlusionQuerySamples > r_chcVisibilityThreshold->integer) { // if a query of multiple previously invisible objects became visible, we need to // test all the individual objects ... if(!QueueEmpty(&light->multiQuery)) { multiQueryLight = light; IssueLightOcclusionQuery(&occlusionQueryQueue, multiQueryLight, qfalse); while(!QueueEmpty(&multiQueryLight->multiQuery)) { light = (trRefLight_t *) DeQueue(&multiQueryLight->multiQuery); IssueLightOcclusionQuery(&occlusionQueryQueue, light, qtrue); } } } else { if(!QueueEmpty(&light->multiQuery)) { backEnd.pc.c_occlusionQueriesLightsCulled++; multiQueryLight = light; while(!QueueEmpty(&multiQueryLight->multiQuery)) { light = (trRefLight_t *) DeQueue(&multiQueryLight->multiQuery); backEnd.pc.c_occlusionQueriesLightsCulled++; backEnd.pc.c_occlusionQueriesSaved++; } } else { backEnd.pc.c_occlusionQueriesLightsCulled++; } } } } if(r_speeds->integer == RSPEEDS_OCCLUSION_QUERIES) { glFinish(); endTime = ri.Milliseconds(); backEnd.pc.c_occlusionQueriesResponseTime = endTime - startTime; startTime = ri.Milliseconds(); } // go back to the world modelview matrix backEnd.orientation = backEnd.viewParms.world; GL_LoadModelViewMatrix(backEnd.viewParms.world.modelViewMatrix); // reenable writes to depth and color buffers GL_State(GLS_DEPTHMASK_TRUE); // loop trough all light interactions and fetch results for each last interaction // then copy result to all other interactions that belong to the same light iaFirst = 0; queryObjects = qtrue; oldLight = NULL; for(iaCount = 0, ia = &backEnd.viewParms.interactions[0]; iaCount < backEnd.viewParms.numInteractions;) { backEnd.currentLight = light = ia->light; if(light != oldLight) { iaFirst = iaCount; } if(!queryObjects) { ia->occlusionQuerySamples = ocSamples; if(ocSamples <= 0) { backEnd.pc.c_occlusionQueriesInteractionsCulled++; } } if(!ia->next) { if(queryObjects) { if(!ia->noOcclusionQueries) { ocSamples = light->occlusionQuerySamples > r_chcVisibilityThreshold->integer; } else { ocSamples = 1; } // jump back to first interaction of this light copy query result ia = &backEnd.viewParms.interactions[iaFirst]; iaCount = iaFirst; queryObjects = qfalse; } else { if(iaCount < (backEnd.viewParms.numInteractions - 1)) { // jump to next interaction and start querying ia++; iaCount++; queryObjects = qtrue; } else { // increase last time to leave for loop iaCount++; } } } else { // just continue ia = ia->next; iaCount++; } oldLight = light; } if(r_speeds->integer == RSPEEDS_OCCLUSION_QUERIES) { glFinish(); endTime = ri.Milliseconds(); backEnd.pc.c_occlusionQueriesFetchTime = endTime - startTime; } } GL_CheckErrors(); } // ================================================================================================ // // ENTITY OCCLUSION CULLING // // ================================================================================================ static void RenderEntityOcclusionVolume(trRefEntity_t * entity) { GL_CheckErrors(); #if 0 // render in entity space R_RotateEntityForViewParms(entity, &backEnd.viewParms, &backEnd.orientation); GL_LoadModelViewMatrix(backEnd.orientation.modelViewMatrix); gl_genericShader->SetUniform_ModelViewProjectionMatrix(glState.modelViewProjectionMatrix[glState.stackIndex]); tess.multiDrawPrimitives = 0; tess.numIndexes = 0; tess.numVertexes = 0; Tess_AddCube(vec3_origin, entity->localBounds[0], entity->localBounds[1], colorBlue); Tess_UpdateVBOs(ATTR_POSITION | ATTR_COLOR); Tess_DrawElements(); tess.multiDrawPrimitives = 0; tess.numIndexes = 0; tess.numVertexes = 0; #else vec3_t boundsCenter; vec3_t boundsSize; matrix_t transform, scale, rot; axis_t axis; #if 0 VectorSubtract(entity->localBounds[1], entity->localBounds[0], boundsSize); #else boundsSize[0] = Q_fabs(entity->localBounds[0][0]) + Q_fabs(entity->localBounds[1][0]); boundsSize[1] = Q_fabs(entity->localBounds[0][1]) + Q_fabs(entity->localBounds[1][1]); boundsSize[2] = Q_fabs(entity->localBounds[0][2]) + Q_fabs(entity->localBounds[1][2]); #endif VectorScale(entity->e.axis[0], boundsSize[0] * 0.5f, axis[0]); VectorScale(entity->e.axis[1], boundsSize[1] * 0.5f, axis[1]); VectorScale(entity->e.axis[2], boundsSize[2] * 0.5f, axis[2]); VectorAdd(entity->localBounds[0], entity->localBounds[1], boundsCenter); VectorScale(boundsCenter, 0.5f, boundsCenter); MatrixFromVectorsFLU(rot, entity->e.axis[0], entity->e.axis[1], entity->e.axis[2]); MatrixTransformNormal2(rot, boundsCenter); VectorAdd(entity->e.origin, boundsCenter, boundsCenter); MatrixSetupTransformFromVectorsFLU(backEnd.orientation.transformMatrix, axis[0], axis[1], axis[2], boundsCenter); MatrixAffineInverse(backEnd.orientation.transformMatrix, backEnd.orientation.viewMatrix); Matrix4x4Multiply(backEnd.viewParms.world.viewMatrix, backEnd.orientation.transformMatrix, backEnd.orientation.modelViewMatrix); GL_LoadModelViewMatrix(backEnd.orientation.modelViewMatrix); gl_genericShader->SetUniform_ModelViewProjectionMatrix(glState.modelViewProjectionMatrix[glState.stackIndex]); R_BindVBO(tr.unitCubeVBO); R_BindIBO(tr.unitCubeIBO); GL_VertexAttribsState(ATTR_POSITION); tess.multiDrawPrimitives = 0; tess.numVertexes = tr.unitCubeVBO->vertexesNum; tess.numIndexes = tr.unitCubeIBO->indexesNum; Tess_DrawElements(); tess.multiDrawPrimitives = 0; tess.numIndexes = 0; tess.numVertexes = 0; #endif GL_CheckErrors(); } static void IssueEntityOcclusionQuery(link_t * queue, trRefEntity_t * entity, qboolean resetMultiQueryLink) { GLimp_LogComment("--- IssueEntityOcclusionQuery ---\n"); //ri.Printf(PRINT_ALL, "--- IssueEntityOcclusionQuery(%i) ---\n", light - backEnd.refdef.lights); if(tr.numUsedOcclusionQueryObjects < (MAX_OCCLUSION_QUERIES -1)) { entity->occlusionQueryObject = tr.occlusionQueryObjects[tr.numUsedOcclusionQueryObjects++]; } else { entity->occlusionQueryObject = 0; } EnQueue(queue, entity); // tell GetOcclusionQueryResult that this is not a multi query if(resetMultiQueryLink) { QueueInit(&entity->multiQuery); } if(entity->occlusionQueryObject > 0) { GL_CheckErrors(); // begin the occlusion query glBeginQueryARB(GL_SAMPLES_PASSED, entity->occlusionQueryObject); GL_CheckErrors(); RenderEntityOcclusionVolume(entity); // end the query glEndQueryARB(GL_SAMPLES_PASSED); #if 0 if(!glIsQueryARB(entity->occlusionQueryObject)) { ri.Error(ERR_FATAL, "IssueOcclusionQuery: entity %i has no occlusion query object in slot %i: %i", light - tr.world->lights, backEnd.viewParms.viewCount, light->occlusionQueryObject); } #endif backEnd.pc.c_occlusionQueries++; } GL_CheckErrors(); } static void IssueEntityMultiOcclusionQueries(link_t * multiQueue, link_t * individualQueue) { trRefEntity_t *entity; trRefEntity_t *multiQueryEntity; link_t *l; GLimp_LogComment("--- IssueEntityMultiOcclusionQueries ---\n"); #if 0 ri.Printf(PRINT_ALL, "IssueEntityMultiOcclusionQueries("); for(l = multiQueue->prev; l != multiQueue; l = l->prev) { light = (trRefEntity_t *) l->data; ri.Printf(PRINT_ALL, "%i, ", light - backEnd.refdef.entities); } ri.Printf(PRINT_ALL, ")\n"); #endif if(QueueEmpty(multiQueue)) return; multiQueryEntity = (trRefEntity_t *) QueueFront(multiQueue)->data; if(tr.numUsedOcclusionQueryObjects < (MAX_OCCLUSION_QUERIES -1)) { multiQueryEntity->occlusionQueryObject = tr.occlusionQueryObjects[tr.numUsedOcclusionQueryObjects++]; } else { multiQueryEntity->occlusionQueryObject = 0; } if(multiQueryEntity->occlusionQueryObject > 0) { // begin the occlusion query GL_CheckErrors(); glBeginQueryARB(GL_SAMPLES_PASSED, multiQueryEntity->occlusionQueryObject); GL_CheckErrors(); //ri.Printf(PRINT_ALL, "rendering nodes:["); for(l = multiQueue->prev; l != multiQueue; l = l->prev) { entity = (trRefEntity_t *) l->data; //ri.Printf(PRINT_ALL, "%i, ", light - backEnd.refdef.lights); RenderEntityOcclusionVolume(entity); } //ri.Printf(PRINT_ALL, "]\n"); backEnd.pc.c_occlusionQueries++; backEnd.pc.c_occlusionQueriesMulti++; // end the query glEndQueryARB(GL_SAMPLES_PASSED); GL_CheckErrors(); #if 0 if(!glIsQueryARB(multiQueryNode->occlusionQueryObjects[backEnd.viewParms.viewCount])) { ri.Error(ERR_FATAL, "IssueEntityMultiOcclusionQueries: node %i has no occlusion query object in slot %i: %i", multiQueryNode - tr.world->nodes, backEnd.viewParms.viewCount, multiQueryNode->occlusionQueryObjects[backEnd.viewParms.viewCount]); } #endif } // move queue to node->multiQuery queue QueueInit(&multiQueryEntity->multiQuery); DeQueue(multiQueue); while(!QueueEmpty(multiQueue)) { entity = (trRefEntity_t *) DeQueue(multiQueue); EnQueue(&multiQueryEntity->multiQuery, entity); } EnQueue(individualQueue, multiQueryEntity); //ri.Printf(PRINT_ALL, "--- IssueMultiOcclusionQueries end ---\n"); } static qboolean EntityOcclusionResultAvailable(trRefEntity_t *entity) { GLint available; if(entity->occlusionQueryObject > 0) { glFinish(); available = 0; //if(glIsQueryARB(light->occlusionQueryObjects[backEnd.viewParms.viewCount])) { glGetQueryObjectivARB(entity->occlusionQueryObject, GL_QUERY_RESULT_AVAILABLE_ARB, &available); GL_CheckErrors(); } return (qboolean) available; } return qtrue; } static void GetEntityOcclusionQueryResult(trRefEntity_t *entity) { link_t *l, *sentinel; int ocSamples; GLint available; GLimp_LogComment("--- GetEntityOcclusionQueryResult ---\n"); if(entity->occlusionQueryObject > 0) { glFinish(); available = 0; while(!available) { //if(glIsQueryARB(node->occlusionQueryObjects[backEnd.viewParms.viewCount])) { glGetQueryObjectivARB(entity->occlusionQueryObject, GL_QUERY_RESULT_AVAILABLE_ARB, &available); //GL_CheckErrors(); } } backEnd.pc.c_occlusionQueriesAvailable++; glGetQueryObjectivARB(entity->occlusionQueryObject, GL_QUERY_RESULT, &ocSamples); //ri.Printf(PRINT_ALL, "GetOcclusionQueryResult(%i): available = %i, samples = %i\n", node - tr.world->nodes, available, ocSamples); GL_CheckErrors(); } else { ocSamples = 1; } entity->occlusionQuerySamples = ocSamples; // copy result to all nodes that were linked to this multi query node sentinel = &entity->multiQuery; for(l = sentinel->prev; l != sentinel; l = l->prev) { entity = (trRefEntity_t *) l->data; entity->occlusionQuerySamples = ocSamples; } } static int EntityCompare(const void *a, const void *b) { trRefEntity_t *e1, *e2; float d1, d2; e1 = (trRefEntity_t *) *(void **)a; e2 = (trRefEntity_t *) *(void **)b; d1 = DistanceSquared(backEnd.viewParms.orientation.origin, e1->e.origin); d2 = DistanceSquared(backEnd.viewParms.orientation.origin, e2->e.origin); if(d1 < d2) { return -1; } if(d1 > d2) { return 1; } return 0; } void RB_RenderEntityOcclusionQueries() { GLimp_LogComment("--- RB_RenderEntityOcclusionQueries ---\n"); if(glConfig2.occlusionQueryBits && glConfig.driverType != GLDRV_MESA && !(backEnd.refdef.rdflags & RDF_NOWORLDMODEL)) { int i; trRefEntity_t *entity, *multiQueryEntity; link_t occlusionQueryQueue; link_t invisibleQueue; growList_t invisibleList; int startTime = 0, endTime = 0; glVertexAttrib4fARB(ATTR_INDEX_COLOR, 1.0f, 0.0f, 0.0f, 0.05f); if(r_speeds->integer == RSPEEDS_OCCLUSION_QUERIES) { glFinish(); startTime = ri.Milliseconds(); } gl_genericShader->DisableAlphaTesting(); gl_genericShader->DisablePortalClipping(); gl_genericShader->DisableVertexSkinning(); gl_genericShader->DisableVertexAnimation(); gl_genericShader->DisableDeformVertexes(); gl_genericShader->DisableTCGenEnvironment(); gl_genericShader->BindProgram(); gl_genericShader->SetRequiredVertexPointers(); GL_Cull(CT_TWO_SIDED); GL_LoadProjectionMatrix(backEnd.viewParms.projectionMatrix); // set uniforms gl_genericShader->SetUniform_ColorModulate(CGEN_CONST, AGEN_CONST); gl_genericShader->SetUniform_Color(colorBlue); // bind u_ColorMap GL_SelectTexture(0); GL_Bind(tr.whiteImage); gl_genericShader->SetUniform_ColorTextureMatrix(matrixIdentity); // don't write to the color buffer or depth buffer if(r_showOcclusionQueries->integer) { GL_State(GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE); } else { GL_State(GLS_COLORMASK_BITS); } tr.numUsedOcclusionQueryObjects = 0; QueueInit(&occlusionQueryQueue); QueueInit(&invisibleQueue); Com_InitGrowList(&invisibleList, 1000); // loop trough all entities and render the entity OBB for(i = 0, entity = backEnd.refdef.entities; i < backEnd.refdef.numEntities; i++, entity++) { if((entity->e.renderfx & RF_THIRD_PERSON) && !backEnd.viewParms.isPortal) continue; if(entity->cull == CULL_OUT) continue; backEnd.currentEntity = entity; entity->occlusionQuerySamples = 1; entity->noOcclusionQueries = qfalse; // check if the entity volume clips against the near plane if(BoxOnPlaneSide(entity->worldBounds[0], entity->worldBounds[1], &backEnd.viewParms.frustums[0][FRUSTUM_NEAR]) == 3) { entity->noOcclusionQueries = qtrue; } else { Com_AddToGrowList(&invisibleList, entity); } } // sort entities by distance qsort(invisibleList.elements, invisibleList.currentElements, sizeof(void *), EntityCompare); for(i = 0; i < invisibleList.currentElements; i++) { entity = (trRefEntity_t *) Com_GrowListElement(&invisibleList, i); EnQueue(&invisibleQueue, entity); if((invisibleList.currentElements - i) <= 100) { if(QueueSize(&invisibleQueue) >= 10) IssueEntityMultiOcclusionQueries(&invisibleQueue, &occlusionQueryQueue); } else { if(QueueSize(&invisibleQueue) >= 50) IssueEntityMultiOcclusionQueries(&invisibleQueue, &occlusionQueryQueue); } } Com_DestroyGrowList(&invisibleList); if(!QueueEmpty(&invisibleQueue)) { // remaining previously invisible node queries IssueEntityMultiOcclusionQueries(&invisibleQueue, &occlusionQueryQueue); //ri.Printf(PRINT_ALL, "occlusionQueryQueue.empty() = %i\n", QueueEmpty(&occlusionQueryQueue)); } // go back to the world modelview matrix backEnd.orientation = backEnd.viewParms.world; GL_LoadModelViewMatrix(backEnd.viewParms.world.modelViewMatrix); while(!QueueEmpty(&occlusionQueryQueue)) { if(EntityOcclusionResultAvailable((trRefEntity_t *) QueueFront(&occlusionQueryQueue)->data)) { entity = (trRefEntity_t *) DeQueue(&occlusionQueryQueue); // wait if result not available GetEntityOcclusionQueryResult(entity); if(entity->occlusionQuerySamples > r_chcVisibilityThreshold->integer) { // if a query of multiple previously invisible objects became visible, we need to // test all the individual objects ... if(!QueueEmpty(&entity->multiQuery)) { multiQueryEntity = entity; IssueEntityOcclusionQuery(&occlusionQueryQueue, multiQueryEntity, qfalse); while(!QueueEmpty(&multiQueryEntity->multiQuery)) { entity = (trRefEntity_t *) DeQueue(&multiQueryEntity->multiQuery); IssueEntityOcclusionQuery(&occlusionQueryQueue, entity, qtrue); } } } else { if(!QueueEmpty(&entity->multiQuery)) { backEnd.pc.c_occlusionQueriesEntitiesCulled++; multiQueryEntity = entity; while(!QueueEmpty(&multiQueryEntity->multiQuery)) { entity = (trRefEntity_t *) DeQueue(&multiQueryEntity->multiQuery); backEnd.pc.c_occlusionQueriesEntitiesCulled++; backEnd.pc.c_occlusionQueriesSaved++; } } else { backEnd.pc.c_occlusionQueriesEntitiesCulled++; } } } } if(r_speeds->integer == RSPEEDS_OCCLUSION_QUERIES) { glFinish(); endTime = ri.Milliseconds(); backEnd.pc.c_occlusionQueriesResponseTime = endTime - startTime; startTime = ri.Milliseconds(); } // go back to the world modelview matrix backEnd.orientation = backEnd.viewParms.world; GL_LoadModelViewMatrix(backEnd.viewParms.world.modelViewMatrix); // reenable writes to depth and color buffers GL_State(GLS_DEPTHMASK_TRUE); } GL_CheckErrors(); } // ================================================================================================ // // BSP OCCLUSION CULLING // // ================================================================================================ #if 0 void RB_RenderBspOcclusionQueries() { GLimp_LogComment("--- RB_RenderBspOcclusionQueries ---\n"); if(glConfig2.occlusionQueryBits && glConfig.driverType != GLDRV_MESA && r_dynamicBspOcclusionCulling->integer) { //int j; bspNode_t *node; link_t *l, *next, *sentinel; GL_BindProgram(&tr.genericShader); GL_Cull(CT_TWO_SIDED); GL_LoadProjectionMatrix(backEnd.viewParms.projectionMatrix); // set uniforms GLSL_SetUniform_TCGen_Environment(&tr.genericShader, qfalse); GLSL_SetUniform_ColorGen(&tr.genericShader, CGEN_VERTEX); GLSL_SetUniform_AlphaGen(&tr.genericShader, AGEN_VERTEX); if(glConfig2.vboVertexSkinningAvailable) { GLSL_SetUniform_VertexSkinning(&tr.genericShader, qfalse); } GLSL_SetUniform_DeformGen(&tr.genericShader, DGEN_NONE); GLSL_SetUniform_AlphaTest(&tr.genericShader, 0); // set up the transformation matrix backEnd.orientation = backEnd.viewParms.world; GL_LoadModelViewMatrix(backEnd.orientation.modelViewMatrix); GLSL_SetUniform_ModelViewProjectionMatrix(&tr.genericShader, glState.modelViewProjectionMatrix[glState.stackIndex]); // bind u_ColorMap GL_SelectTexture(0); GL_Bind(tr.whiteImage); GLSL_SetUniform_ColorTextureMatrix(&tr.genericShader, matrixIdentity); // don't write to the color buffer or depth buffer GL_State(GLS_COLORMASK_BITS); sentinel = &tr.occlusionQueryList; for(l = sentinel->next; l != sentinel; l = next) { next = l->next; node = (bspNode_t *) l->data; // begin the occlusion query glBeginQueryARB(GL_SAMPLES_PASSED, node->occlusionQueryObjects[backEnd.viewParms.viewCount]); R_BindVBO(node->volumeVBO); R_BindIBO(node->volumeIBO); GL_VertexAttribsState(ATTR_POSITION); tess.numVertexes = node->volumeVerts; tess.numIndexes = node->volumeIndexes; Tess_DrawElements(); // end the query // don't read back immediately so that we give the query time to be ready glEndQueryARB(GL_SAMPLES_PASSED); #if 0 if(!glIsQueryARB(node->occlusionQueryObjects[backEnd.viewParms.viewCount])) { ri.Error(ERR_FATAL, "node %i has no occlusion query object in slot %i: %i", j, 0, node->occlusionQueryObjects[backEnd.viewParms.viewCount]); } #endif backEnd.pc.c_occlusionQueries++; tess.multiDrawPrimitives = 0; tess.numIndexes = 0; tess.numVertexes = 0; } } GL_CheckErrors(); } void RB_CollectBspOcclusionQueries() { GLimp_LogComment("--- RB_CollectBspOcclusionQueries ---\n"); if(glConfig2.occlusionQueryBits && glConfig.driverType != GLDRV_MESA && r_dynamicBspOcclusionCulling->integer) { //int j; bspNode_t *node; link_t *l, *next, *sentinel; int ocCount; int avCount; GLint available; glFinish(); ocCount = 0; sentinel = &tr.occlusionQueryList; for(l = sentinel->next; l != sentinel; l = l->next) { node = (bspNode_t *) l->data; if(glIsQueryARB(node->occlusionQueryObjects[backEnd.viewParms.viewCount])) { ocCount++; } } //ri.Printf(PRINT_ALL, "waiting for %i queries...\n", ocCount); avCount = 0; do { for(l = sentinel->next; l != sentinel; l = l->next) { node = (bspNode_t *) l->data; if(node->issueOcclusionQuery) { available = 0; if(glIsQueryARB(node->occlusionQueryObjects[backEnd.viewParms.viewCount])) { glGetQueryObjectivARB(node->occlusionQueryObjects[backEnd.viewParms.viewCount], GL_QUERY_RESULT_AVAILABLE_ARB, &available); GL_CheckErrors(); } if(available) { node->issueOcclusionQuery = qfalse; avCount++; //if(//avCount % oc) //ri.Printf(PRINT_ALL, "%i queries...\n", avCount); } } } } while(avCount < ocCount); for(l = sentinel->next; l != sentinel; l = l->next) { node = (bspNode_t *) l->data; available = 0; if(glIsQueryARB(node->occlusionQueryObjects[backEnd.viewParms.viewCount])) { glGetQueryObjectivARB(node->occlusionQueryObjects[backEnd.viewParms.viewCount], GL_QUERY_RESULT_AVAILABLE_ARB, &available); GL_CheckErrors(); } if(available) { backEnd.pc.c_occlusionQueriesAvailable++; // get the object and store it in the occlusion bits for the light glGetQueryObjectivARB(node->occlusionQueryObjects[backEnd.viewParms.viewCount], GL_QUERY_RESULT, &node->occlusionQuerySamples[backEnd.viewParms.viewCount]); if(node->occlusionQuerySamples[backEnd.viewParms.viewCount] <= 0) { backEnd.pc.c_occlusionQueriesLeafsCulled++; } } else { node->occlusionQuerySamples[backEnd.viewParms.viewCount] = 1; } GL_CheckErrors(); } //ri.Printf(PRINT_ALL, "done\n"); } } #endif static void RB_RenderDebugUtils() { GLimp_LogComment("--- RB_RenderDebugUtils ---\n"); if(r_showLightTransforms->integer || r_showShadowLod->integer) { interaction_t *ia; int iaCount, j; trRefLight_t *light; vec3_t forward, left, up; vec4_t lightColor; vec4_t quadVerts[4]; vec3_t minSize = {-2, -2, -2}; vec3_t maxSize = { 2, 2, 2}; gl_genericShader->DisableAlphaTesting(); gl_genericShader->DisablePortalClipping(); gl_genericShader->DisableVertexSkinning(); gl_genericShader->DisableVertexAnimation(); gl_genericShader->DisableDeformVertexes(); gl_genericShader->DisableTCGenEnvironment(); gl_genericShader->BindProgram(); //GL_State(GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA); GL_State(GLS_POLYMODE_LINE | GLS_DEPTHTEST_DISABLE); GL_Cull(CT_TWO_SIDED); // set uniforms gl_genericShader->SetUniform_ColorModulate(CGEN_CUSTOM_RGB, AGEN_CUSTOM); // bind u_ColorMap GL_SelectTexture(0); GL_Bind(tr.whiteImage); gl_genericShader->SetUniform_ColorTextureMatrix(matrixIdentity); for(iaCount = 0, ia = &backEnd.viewParms.interactions[0]; iaCount < backEnd.viewParms.numInteractions;) { light = ia->light; if(!ia->next) { if(r_showShadowLod->integer) { if(light->shadowLOD == 0) { Vector4Copy(colorRed, lightColor); } else if(light->shadowLOD == 1) { Vector4Copy(colorGreen, lightColor); } else if(light->shadowLOD == 2) { Vector4Copy(colorBlue, lightColor); } else if(light->shadowLOD == 3) { Vector4Copy(colorYellow, lightColor); } else if(light->shadowLOD == 4) { Vector4Copy(colorMagenta, lightColor); } else if(light->shadowLOD == 5) { Vector4Copy(colorCyan, lightColor); } else { Vector4Copy(colorMdGrey, lightColor); } } else if(r_dynamicLightOcclusionCulling->integer) { if(!ia->occlusionQuerySamples) { Vector4Copy(colorRed, lightColor); } else { Vector4Copy(colorGreen, lightColor); } } else { //Vector4Copy(g_color_table[iaCount % 8], lightColor); Vector4Copy(colorBlue, lightColor); } lightColor[3] = 0.2; gl_genericShader->SetUniform_Color(lightColor); MatrixToVectorsFLU(matrixIdentity, forward, left, up); VectorMA(vec3_origin, 16, forward, forward); VectorMA(vec3_origin, 16, left, left); VectorMA(vec3_origin, 16, up, up); /* // draw axis glBegin(GL_LINES); // draw orientation glVertexAttrib4fvARB(ATTR_INDEX_COLOR, colorRed); glVertex3fv(vec3_origin); glVertex3fv(forward); glVertexAttrib4fvARB(ATTR_INDEX_COLOR, colorGreen); glVertex3fv(vec3_origin); glVertex3fv(left); glVertexAttrib4fvARB(ATTR_INDEX_COLOR, colorBlue); glVertex3fv(vec3_origin); glVertex3fv(up); // draw special vectors glVertexAttrib4fvARB(ATTR_INDEX_COLOR, colorYellow); glVertex3fv(vec3_origin); VectorSubtract(light->origin, backEnd.orientation.origin, tmp); light->transformed[0] = DotProduct(tmp, backEnd.orientation.axis[0]); light->transformed[1] = DotProduct(tmp, backEnd.orientation.axis[1]); light->transformed[2] = DotProduct(tmp, backEnd.orientation.axis[2]); glVertex3fv(light->transformed); glEnd(); */ #if 1 if(light->isStatic && light->frustumVBO && light->frustumIBO) { // go back to the world modelview matrix backEnd.orientation = backEnd.viewParms.world; GL_LoadModelViewMatrix(backEnd.viewParms.world.modelViewMatrix); gl_genericShader->SetUniform_ModelViewProjectionMatrix(glState.modelViewProjectionMatrix[glState.stackIndex]); R_BindVBO(light->frustumVBO); R_BindIBO(light->frustumIBO); GL_VertexAttribsState(ATTR_POSITION); tess.numVertexes = light->frustumVerts; tess.numIndexes = light->frustumIndexes; Tess_DrawElements(); tess.multiDrawPrimitives = 0; tess.numIndexes = 0; tess.numVertexes = 0; } else #endif { tess.multiDrawPrimitives = 0; tess.numIndexes = 0; tess.numVertexes = 0; switch (light->l.rlType) { case RL_OMNI: case RL_DIRECTIONAL: { #if 1 // set up the transformation matrix R_RotateLightForViewParms(light, &backEnd.viewParms, &backEnd.orientation); GL_LoadModelViewMatrix(backEnd.orientation.modelViewMatrix); gl_genericShader->SetUniform_ModelViewProjectionMatrix(glState.modelViewProjectionMatrix[glState.stackIndex]); Tess_AddCube(vec3_origin, light->localBounds[0], light->localBounds[1], lightColor); if(!VectorCompare(light->l.center, vec3_origin)) Tess_AddCube(light->l.center, minSize, maxSize, colorYellow); Tess_UpdateVBOs(ATTR_POSITION | ATTR_COLOR); Tess_DrawElements(); #else matrix_t transform, scale, rot; MatrixSetupScale(scale, light->l.radius[0], light->l.radius[1], light->l.radius[2]); Matrix4x4Multiply(light->transformMatrix, scale, transform); GL_LoadModelViewMatrix(transform); //GL_LoadProjectionMatrix(backEnd.viewParms.projectionMatrix); gl_genericShader->SetUniform_ModelViewProjectionMatrix(glState.modelViewProjectionMatrix[glState.stackIndex]); R_BindVBO(tr.unitCubeVBO); R_BindIBO(tr.unitCubeIBO); GL_VertexAttribsState(ATTR_POSITION); tess.multiDrawPrimitives = 0; tess.numVertexes = tr.unitCubeVBO->vertexesNum; tess.numIndexes = tr.unitCubeIBO->indexesNum; Tess_DrawElements(); #endif break; } case RL_PROJ: { vec3_t farCorners[4]; //vec4_t frustum[6]; vec4_t *frustum = light->localFrustum; // set up the transformation matrix R_RotateLightForViewParms(light, &backEnd.viewParms, &backEnd.orientation); GL_LoadModelViewMatrix(backEnd.orientation.modelViewMatrix); gl_genericShader->SetUniform_ModelViewProjectionMatrix(glState.modelViewProjectionMatrix[glState.stackIndex]); #if 0 // transform frustum from world space to local space for(j = 0; j < 6; j++) { MatrixTransformPlane(light->transformMatrix, light->localFrustum[j], frustum[j]); //Vector4Copy(light->localFrustum[j], frustum[j]); //MatrixTransformPlane2(light->viewMatrix, frustum[j]); } // go back to the world modelview matrix backEnd.orientation = backEnd.viewParms.world; GL_LoadModelViewMatrix(backEnd.viewParms.world.modelViewMatrix); GLSL_SetUniform_ModelViewProjectionMatrix(&tr.genericShader, glState.modelViewProjectionMatrix[glState.stackIndex]); #endif PlanesGetIntersectionPoint(frustum[FRUSTUM_LEFT], frustum[FRUSTUM_TOP], frustum[FRUSTUM_FAR], farCorners[0]); PlanesGetIntersectionPoint(frustum[FRUSTUM_RIGHT], frustum[FRUSTUM_TOP], frustum[FRUSTUM_FAR], farCorners[1]); PlanesGetIntersectionPoint(frustum[FRUSTUM_RIGHT], frustum[FRUSTUM_BOTTOM], frustum[FRUSTUM_FAR], farCorners[2]); PlanesGetIntersectionPoint(frustum[FRUSTUM_LEFT], frustum[FRUSTUM_BOTTOM], frustum[FRUSTUM_FAR], farCorners[3]); // the planes of the frustum are measured at world 0,0,0 so we have to position the intersection points relative to the light origin #if 0 ri.Printf(PRINT_ALL, "pyramid farCorners\n"); for(j = 0; j < 4; j++) { ri.Printf(PRINT_ALL, "(%5.3f, %5.3f, %5.3f)\n", farCorners[j][0], farCorners[j][1], farCorners[j][2]); } #endif tess.numVertexes = 0; tess.numIndexes = 0; tess.multiDrawPrimitives = 0; if(!VectorCompare(light->l.projStart, vec3_origin)) { vec3_t nearCorners[4]; // calculate the vertices defining the top area PlanesGetIntersectionPoint(frustum[FRUSTUM_LEFT], frustum[FRUSTUM_TOP], frustum[FRUSTUM_NEAR], nearCorners[0]); PlanesGetIntersectionPoint(frustum[FRUSTUM_RIGHT], frustum[FRUSTUM_TOP], frustum[FRUSTUM_NEAR], nearCorners[1]); PlanesGetIntersectionPoint(frustum[FRUSTUM_RIGHT], frustum[FRUSTUM_BOTTOM], frustum[FRUSTUM_NEAR], nearCorners[2]); PlanesGetIntersectionPoint(frustum[FRUSTUM_LEFT], frustum[FRUSTUM_BOTTOM], frustum[FRUSTUM_NEAR], nearCorners[3]); #if 0 ri.Printf(PRINT_ALL, "pyramid nearCorners\n"); for(j = 0; j < 4; j++) { ri.Printf(PRINT_ALL, "(%5.3f, %5.3f, %5.3f)\n", nearCorners[j][0], nearCorners[j][1], nearCorners[j][2]); } #endif // draw outer surfaces for(j = 0; j < 4; j++) { VectorSet4(quadVerts[3], nearCorners[j][0], nearCorners[j][1], nearCorners[j][2], 1); VectorSet4(quadVerts[2], farCorners[j][0], farCorners[j][1], farCorners[j][2], 1); VectorSet4(quadVerts[1], farCorners[(j + 1) % 4][0], farCorners[(j + 1) % 4][1], farCorners[(j + 1) % 4][2], 1); VectorSet4(quadVerts[0], nearCorners[(j + 1) % 4][0], nearCorners[(j + 1) % 4][1], nearCorners[(j + 1) % 4][2], 1); Tess_AddQuadStamp2(quadVerts, lightColor); } // draw far cap VectorSet4(quadVerts[0], farCorners[0][0], farCorners[0][1], farCorners[0][2], 1); VectorSet4(quadVerts[1], farCorners[1][0], farCorners[1][1], farCorners[1][2], 1); VectorSet4(quadVerts[2], farCorners[2][0], farCorners[2][1], farCorners[2][2], 1); VectorSet4(quadVerts[3], farCorners[3][0], farCorners[3][1], farCorners[3][2], 1); Tess_AddQuadStamp2(quadVerts, lightColor); // draw near cap VectorSet4(quadVerts[3], nearCorners[0][0], nearCorners[0][1], nearCorners[0][2], 1); VectorSet4(quadVerts[2], nearCorners[1][0], nearCorners[1][1], nearCorners[1][2], 1); VectorSet4(quadVerts[1], nearCorners[2][0], nearCorners[2][1], nearCorners[2][2], 1); VectorSet4(quadVerts[0], nearCorners[3][0], nearCorners[3][1], nearCorners[3][2], 1); Tess_AddQuadStamp2(quadVerts, lightColor); } else { vec3_t top; // no light_start, just use the top vertex (doesn't need to be mirrored) PlanesGetIntersectionPoint(frustum[FRUSTUM_LEFT], frustum[FRUSTUM_RIGHT], frustum[FRUSTUM_TOP], top); // draw pyramid for(j = 0; j < 4; j++) { VectorCopy(top, tess.xyz[tess.numVertexes]); Vector4Copy(lightColor, tess.colors[tess.numVertexes]); tess.indexes[tess.numIndexes++] = tess.numVertexes; tess.numVertexes++; VectorCopy(farCorners[(j + 1) % 4], tess.xyz[tess.numVertexes]); Vector4Copy(lightColor, tess.colors[tess.numVertexes]); tess.indexes[tess.numIndexes++] = tess.numVertexes; tess.numVertexes++; VectorCopy(farCorners[j], tess.xyz[tess.numVertexes]); Vector4Copy(lightColor, tess.colors[tess.numVertexes]); tess.indexes[tess.numIndexes++] = tess.numVertexes; tess.numVertexes++; } // draw far cap VectorSet4(quadVerts[0], farCorners[0][0], farCorners[0][1], farCorners[0][2], 1); VectorSet4(quadVerts[1], farCorners[1][0], farCorners[1][1], farCorners[1][2], 1); VectorSet4(quadVerts[2], farCorners[2][0], farCorners[2][1], farCorners[2][2], 1); VectorSet4(quadVerts[3], farCorners[3][0], farCorners[3][1], farCorners[3][2], 1); Tess_AddQuadStamp2(quadVerts, lightColor); } // draw light_target Tess_AddCube(light->l.projTarget, minSize, maxSize, colorRed); Tess_AddCube(light->l.projRight, minSize, maxSize, colorGreen); Tess_AddCube(light->l.projUp, minSize, maxSize, colorBlue); if(!VectorCompare(light->l.projStart, vec3_origin)) Tess_AddCube(light->l.projStart, minSize, maxSize, colorYellow); if(!VectorCompare(light->l.projEnd, vec3_origin)) Tess_AddCube(light->l.projEnd, minSize, maxSize, colorMagenta); Tess_UpdateVBOs(ATTR_POSITION | ATTR_COLOR); Tess_DrawElements(); break; } default: break; } tess.multiDrawPrimitives = 0; tess.numIndexes = 0; tess.numVertexes = 0; } if(iaCount < (backEnd.viewParms.numInteractions - 1)) { // jump to next interaction and continue ia++; iaCount++; } else { // increase last time to leave for loop iaCount++; } } else { // just continue ia = ia->next; iaCount++; } } // go back to the world modelview matrix backEnd.orientation = backEnd.viewParms.world; GL_LoadModelViewMatrix(backEnd.viewParms.world.modelViewMatrix); } if(r_showLightInteractions->integer) { int i; int cubeSides; interaction_t *ia; int iaCount; trRefLight_t *light; trRefEntity_t *entity; surfaceType_t *surface; vec4_t lightColor; vec3_t mins = {-1,-1,-1}; vec3_t maxs = { 1, 1, 1}; gl_genericShader->DisableAlphaTesting(); gl_genericShader->DisablePortalClipping(); gl_genericShader->DisableVertexSkinning(); gl_genericShader->DisableVertexAnimation(); gl_genericShader->DisableDeformVertexes(); gl_genericShader->DisableTCGenEnvironment(); gl_genericShader->BindProgram(); GL_State(GLS_POLYMODE_LINE | GLS_DEPTHTEST_DISABLE); GL_Cull(CT_TWO_SIDED); // set uniforms gl_genericShader->SetUniform_ColorModulate(CGEN_VERTEX, AGEN_VERTEX); gl_genericShader->SetUniform_Color(colorBlack); // bind u_ColorMap GL_SelectTexture(0); GL_Bind(tr.whiteImage); gl_genericShader->SetUniform_ColorTextureMatrix(matrixIdentity); for(iaCount = 0, ia = &backEnd.viewParms.interactions[0]; iaCount < backEnd.viewParms.numInteractions;) { backEnd.currentEntity = entity = ia->entity; light = ia->light; surface = ia->surface; if(entity != &tr.worldEntity) { // set up the transformation matrix R_RotateEntityForViewParms(backEnd.currentEntity, &backEnd.viewParms, &backEnd.orientation); } else { backEnd.orientation = backEnd.viewParms.world; } GL_LoadModelViewMatrix(backEnd.orientation.modelViewMatrix); gl_genericShader->SetUniform_ModelViewProjectionMatrix(glState.modelViewProjectionMatrix[glState.stackIndex]); if(r_shadows->integer >= SHADOWING_ESM16 && light->l.rlType == RL_OMNI) { #if 0 Vector4Copy(colorMdGrey, lightColor); if(ia->cubeSideBits & CUBESIDE_PX) { Vector4Copy(colorBlack, lightColor); } if(ia->cubeSideBits & CUBESIDE_PY) { Vector4Copy(colorRed, lightColor); } if(ia->cubeSideBits & CUBESIDE_PZ) { Vector4Copy(colorGreen, lightColor); } if(ia->cubeSideBits & CUBESIDE_NX) { Vector4Copy(colorYellow, lightColor); } if(ia->cubeSideBits & CUBESIDE_NY) { Vector4Copy(colorBlue, lightColor); } if(ia->cubeSideBits & CUBESIDE_NZ) { Vector4Copy(colorCyan, lightColor); } if(ia->cubeSideBits == CUBESIDE_CLIPALL) { Vector4Copy(colorMagenta, lightColor); } #else // count how many cube sides are in use for this interaction cubeSides = 0; for(i = 0; i < 6; i++) { if(ia->cubeSideBits & (1 << i)) { cubeSides++; } } Vector4Copy(g_color_table[cubeSides], lightColor); #endif } else { Vector4Copy(colorMdGrey, lightColor); } lightColor[0] *= 0.5f; lightColor[1] *= 0.5f; lightColor[2] *= 0.5f; //lightColor[3] *= 0.2f; Vector4Copy(colorWhite, lightColor); tess.numVertexes = 0; tess.numIndexes = 0; tess.multiDrawPrimitives = 0; if(*surface == SF_FACE || *surface == SF_GRID || *surface == SF_TRIANGLES) { srfGeneric_t *gen; gen = (srfGeneric_t *) surface; if(*surface == SF_FACE) { Vector4Copy(colorMdGrey, lightColor); } else if(*surface == SF_GRID) { Vector4Copy(colorCyan, lightColor); } else if(*surface == SF_TRIANGLES) { Vector4Copy(colorMagenta, lightColor); } else { Vector4Copy(colorMdGrey, lightColor); } Tess_AddCube(vec3_origin, gen->bounds[0], gen->bounds[1], lightColor); Tess_AddCube(gen->origin, mins, maxs, colorWhite); } else if(*surface == SF_VBO_MESH) { srfVBOMesh_t *srf = (srfVBOMesh_t *) surface; Tess_AddCube(vec3_origin, srf->bounds[0], srf->bounds[1], lightColor); } else if(*surface == SF_MDV) { Tess_AddCube(vec3_origin, entity->localBounds[0], entity->localBounds[1], lightColor); } Tess_UpdateVBOs(ATTR_POSITION | ATTR_COLOR); Tess_DrawElements(); tess.multiDrawPrimitives = 0; tess.numIndexes = 0; tess.numVertexes = 0; if(!ia->next) { if(iaCount < (backEnd.viewParms.numInteractions - 1)) { // jump to next interaction and continue ia++; iaCount++; } else { // increase last time to leave for loop iaCount++; } } else { // just continue ia = ia->next; iaCount++; } } // go back to the world modelview matrix backEnd.orientation = backEnd.viewParms.world; GL_LoadModelViewMatrix(backEnd.viewParms.world.modelViewMatrix); } if(r_showEntityTransforms->integer) { trRefEntity_t *ent; int i; vec3_t mins = {-1,-1,-1}; vec3_t maxs = { 1, 1, 1}; gl_genericShader->DisableAlphaTesting(); gl_genericShader->DisablePortalClipping(); gl_genericShader->DisableVertexSkinning(); gl_genericShader->DisableVertexAnimation(); gl_genericShader->DisableDeformVertexes(); gl_genericShader->DisableTCGenEnvironment(); gl_genericShader->BindProgram(); GL_State(GLS_POLYMODE_LINE | GLS_DEPTHTEST_DISABLE); GL_Cull(CT_TWO_SIDED); // set uniforms gl_genericShader->SetUniform_ColorModulate(CGEN_VERTEX, AGEN_VERTEX); gl_genericShader->SetUniform_Color(colorBlack); // bind u_ColorMap GL_SelectTexture(0); GL_Bind(tr.whiteImage); gl_genericShader->SetUniform_ColorTextureMatrix(matrixIdentity); ent = backEnd.refdef.entities; for(i = 0; i < backEnd.refdef.numEntities; i++, ent++) { if((ent->e.renderfx & RF_THIRD_PERSON) && !backEnd.viewParms.isPortal) continue; if(ent->cull == CULL_OUT) continue; // set up the transformation matrix R_RotateEntityForViewParms(ent, &backEnd.viewParms, &backEnd.orientation); GL_LoadModelViewMatrix(backEnd.orientation.modelViewMatrix); gl_genericShader->SetUniform_ModelViewProjectionMatrix(glState.modelViewProjectionMatrix[glState.stackIndex]); //R_DebugAxis(vec3_origin, matrixIdentity); //R_DebugBoundingBox(vec3_origin, ent->localBounds[0], ent->localBounds[1], colorMagenta); tess.multiDrawPrimitives = 0; tess.numIndexes = 0; tess.numVertexes = 0; if(r_dynamicEntityOcclusionCulling->integer) { if(!ent->occlusionQuerySamples) { Tess_AddCube(vec3_origin, ent->localBounds[0], ent->localBounds[1], colorRed); } else { Tess_AddCube(vec3_origin, ent->localBounds[0], ent->localBounds[1], colorGreen); } } else { Tess_AddCube(vec3_origin, ent->localBounds[0], ent->localBounds[1], colorBlue); } Tess_AddCube(vec3_origin, mins, maxs, colorWhite); Tess_UpdateVBOs(ATTR_POSITION | ATTR_COLOR); Tess_DrawElements(); tess.multiDrawPrimitives = 0; tess.numIndexes = 0; tess.numVertexes = 0; // go back to the world modelview matrix //backEnd.orientation = backEnd.viewParms.world; //GL_LoadModelViewMatrix(backEnd.viewParms.world.modelViewMatrix); //R_DebugBoundingBox(vec3_origin, ent->worldBounds[0], ent->worldBounds[1], colorCyan); } // go back to the world modelview matrix backEnd.orientation = backEnd.viewParms.world; GL_LoadModelViewMatrix(backEnd.viewParms.world.modelViewMatrix); } #if defined(USE_REFENTITY_ANIMATIONSYSTEM) if(r_showSkeleton->integer) { int i, j, k, parentIndex; trRefEntity_t *ent; vec3_t origin, offset; vec3_t forward, right, up; vec3_t diff, tmp, tmp2, tmp3; vec_t length; vec4_t tetraVerts[4]; static refSkeleton_t skeleton; refSkeleton_t *skel; gl_genericShader->DisableAlphaTesting(); gl_genericShader->DisablePortalClipping(); gl_genericShader->DisableVertexSkinning(); gl_genericShader->DisableVertexAnimation(); gl_genericShader->DisableDeformVertexes(); gl_genericShader->DisableTCGenEnvironment(); gl_genericShader->BindProgram(); GL_Cull(CT_TWO_SIDED); // set uniforms gl_genericShader->SetUniform_ColorModulate(CGEN_VERTEX, AGEN_VERTEX); gl_genericShader->SetUniform_Color(colorBlack); // bind u_ColorMap GL_SelectTexture(0); GL_Bind(tr.charsetImage); gl_genericShader->SetUniform_ColorTextureMatrix(matrixIdentity); ent = backEnd.refdef.entities; for(i = 0; i < backEnd.refdef.numEntities; i++, ent++) { if((ent->e.renderfx & RF_THIRD_PERSON) && !backEnd.viewParms.isPortal) continue; // set up the transformation matrix R_RotateEntityForViewParms(ent, &backEnd.viewParms, &backEnd.orientation); GL_LoadModelViewMatrix(backEnd.orientation.modelViewMatrix); gl_genericShader->SetUniform_ModelViewProjectionMatrix(glState.modelViewProjectionMatrix[glState.stackIndex]); tess.multiDrawPrimitives = 0; tess.numVertexes = 0; tess.numIndexes = 0; skel = NULL; if(ent->e.skeleton.type == SK_ABSOLUTE) { skel = &ent->e.skeleton; } else { model_t *model; refBone_t *bone; model = R_GetModelByHandle(ent->e.hModel); if(model) { switch (model->type) { case MOD_MD5: { // copy absolute bones skeleton.numBones = model->md5->numBones; for(j = 0, bone = &skeleton.bones[0]; j < skeleton.numBones; j++, bone++) { #if defined(REFBONE_NAMES) Q_strncpyz(bone->name, model->md5->bones[j].name, sizeof(bone->name)); #endif bone->parentIndex = model->md5->bones[j].parentIndex; VectorCopy(model->md5->bones[j].origin, bone->origin); VectorCopy(model->md5->bones[j].rotation, bone->rotation); } skel = &skeleton; break; } default: break; } } } if(skel) { static vec3_t worldOrigins[MAX_BONES]; GL_State(GLS_POLYMODE_LINE | GLS_DEPTHTEST_DISABLE); for(j = 0; j < skel->numBones; j++) { parentIndex = skel->bones[j].parentIndex; if(parentIndex < 0) { VectorClear(origin); } else { VectorCopy(skel->bones[parentIndex].origin, origin); } VectorCopy(skel->bones[j].origin, offset); QuatToVectorsFRU(skel->bones[j].rotation, forward, right, up); VectorSubtract(offset, origin, diff); if((length = VectorNormalize(diff))) { PerpendicularVector(tmp, diff); //VectorCopy(up, tmp); VectorScale(tmp, length * 0.1, tmp2); VectorMA(tmp2, length * 0.2, diff, tmp2); for(k = 0; k < 3; k++) { RotatePointAroundVector(tmp3, diff, tmp2, k * 120); VectorAdd(tmp3, origin, tmp3); VectorCopy(tmp3, tetraVerts[k]); tetraVerts[k][3] = 1; } VectorCopy(origin, tetraVerts[3]); tetraVerts[3][3] = 1; Tess_AddTetrahedron(tetraVerts, g_color_table[ColorIndex(j)]); VectorCopy(offset, tetraVerts[3]); tetraVerts[3][3] = 1; Tess_AddTetrahedron(tetraVerts, g_color_table[ColorIndex(j)]); } MatrixTransformPoint(backEnd.orientation.transformMatrix, skel->bones[j].origin, worldOrigins[j]); } Tess_UpdateVBOs(ATTR_POSITION | ATTR_TEXCOORD | ATTR_COLOR); Tess_DrawElements(); tess.multiDrawPrimitives = 0; tess.numVertexes = 0; tess.numIndexes = 0; #if defined(REFBONE_NAMES) { GL_State(GLS_DEPTHTEST_DISABLE | GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA); // go back to the world modelview matrix backEnd.orientation = backEnd.viewParms.world; GL_LoadModelViewMatrix(backEnd.viewParms.world.modelViewMatrix); gl_genericShader->SetUniform_ModelViewProjectionMatrix(glState.modelViewProjectionMatrix[glState.stackIndex]); // draw names for(j = 0; j < skel->numBones; j++) { vec3_t left, up; float radius; vec3_t origin; // calculate the xyz locations for the four corners radius = 0.4; VectorScale(backEnd.viewParms.orientation.axis[1], radius, left); VectorScale(backEnd.viewParms.orientation.axis[2], radius, up); if(backEnd.viewParms.isMirror) { VectorSubtract(vec3_origin, left, left); } for(k = 0; k < strlen(skel->bones[j].name); k++) { int ch; int row, col; float frow, fcol; float size; ch = skel->bones[j].name[k]; ch &= 255; if(ch == ' ') { break; } row = ch >> 4; col = ch & 15; frow = row * 0.0625; fcol = col * 0.0625; size = 0.0625; VectorMA(worldOrigins[j], -(k + 2.0f), left, origin); Tess_AddQuadStampExt(origin, left, up, colorWhite, fcol, frow, fcol + size, frow + size); } Tess_UpdateVBOs(ATTR_POSITION | ATTR_TEXCOORD | ATTR_COLOR); Tess_DrawElements(); tess.multiDrawPrimitives = 0; tess.numVertexes = 0; tess.numIndexes = 0; } } #endif // REFBONE_NAMES } tess.multiDrawPrimitives = 0; tess.numVertexes = 0; tess.numIndexes = 0; } } #endif if(r_showLightScissors->integer) { interaction_t *ia; int iaCount; matrix_t ortho; vec4_t quadVerts[4]; gl_genericShader->DisableAlphaTesting(); gl_genericShader->DisablePortalClipping(); gl_genericShader->DisableVertexSkinning(); gl_genericShader->DisableVertexAnimation(); gl_genericShader->DisableDeformVertexes(); gl_genericShader->DisableTCGenEnvironment(); gl_genericShader->BindProgram(); GL_State(GLS_POLYMODE_LINE | GLS_DEPTHTEST_DISABLE); GL_Cull(CT_TWO_SIDED); // set uniforms gl_genericShader->SetUniform_ColorModulate(CGEN_CUSTOM_RGB, AGEN_CUSTOM); // bind u_ColorMap GL_SelectTexture(0); GL_Bind(tr.whiteImage); gl_genericShader->SetUniform_ColorTextureMatrix(matrixIdentity); // set 2D virtual screen size GL_PushMatrix(); MatrixOrthogonalProjection(ortho, backEnd.viewParms.viewportX, backEnd.viewParms.viewportX + backEnd.viewParms.viewportWidth, backEnd.viewParms.viewportY, backEnd.viewParms.viewportY + backEnd.viewParms.viewportHeight, -99999, 99999); GL_LoadProjectionMatrix(ortho); GL_LoadModelViewMatrix(matrixIdentity); gl_genericShader->SetUniform_ModelViewProjectionMatrix(glState.modelViewProjectionMatrix[glState.stackIndex]); for(iaCount = 0, ia = &backEnd.viewParms.interactions[0]; iaCount < backEnd.viewParms.numInteractions;) { if(glConfig2.occlusionQueryBits && glConfig.driverType != GLDRV_MESA) { if(!ia->occlusionQuerySamples) { gl_genericShader->SetUniform_Color(colorRed); } else { gl_genericShader->SetUniform_Color(colorGreen); } VectorSet4(quadVerts[0], ia->scissorX, ia->scissorY, 0, 1); VectorSet4(quadVerts[1], ia->scissorX + ia->scissorWidth - 1, ia->scissorY, 0, 1); VectorSet4(quadVerts[2], ia->scissorX + ia->scissorWidth - 1, ia->scissorY + ia->scissorHeight - 1, 0, 1); VectorSet4(quadVerts[3], ia->scissorX, ia->scissorY + ia->scissorHeight - 1, 0, 1); Tess_InstantQuad(quadVerts); } else { gl_genericShader->SetUniform_Color(colorWhite); VectorSet4(quadVerts[0], ia->scissorX, ia->scissorY, 0, 1); VectorSet4(quadVerts[1], ia->scissorX + ia->scissorWidth - 1, ia->scissorY, 0, 1); VectorSet4(quadVerts[2], ia->scissorX + ia->scissorWidth - 1, ia->scissorY + ia->scissorHeight - 1, 0, 1); VectorSet4(quadVerts[3], ia->scissorX, ia->scissorY + ia->scissorHeight - 1, 0, 1); Tess_InstantQuad(quadVerts); } if(!ia->next) { if(iaCount < (backEnd.viewParms.numInteractions - 1)) { // jump to next interaction and continue ia++; iaCount++; } else { // increase last time to leave for loop iaCount++; } } else { // just continue ia = ia->next; iaCount++; } } GL_PopMatrix(); } if(r_showCubeProbes->integer) { cubemapProbe_t *cubeProbe; int j; vec4_t quadVerts[4]; vec3_t mins = {-8, -8, -8}; vec3_t maxs = { 8, 8, 8}; //vec3_t viewOrigin; if(backEnd.refdef.rdflags & (RDF_NOWORLDMODEL | RDF_NOCUBEMAP)) { return; } // choose right shader program ---------------------------------- gl_reflectionShader->SetPortalClipping(backEnd.viewParms.isPortal); // gl_reflectionShader->SetAlphaTesting((pStage->stateBits & GLS_ATEST_BITS) != 0); gl_reflectionShader->SetVertexSkinning(false); gl_reflectionShader->SetVertexAnimation(false); gl_reflectionShader->SetDeformVertexes(false); gl_reflectionShader->SetNormalMapping(false); // gl_reflectionShader->DisableMacro_TWOSIDED(); gl_reflectionShader->BindProgram(); // end choose right shader program ------------------------------ gl_reflectionShader->SetUniform_ViewOrigin(backEnd.viewParms.orientation.origin); // in world space GL_State(0); GL_Cull(CT_FRONT_SIDED); // set up the transformation matrix backEnd.orientation = backEnd.viewParms.world; GL_LoadModelViewMatrix(backEnd.orientation.modelViewMatrix); gl_reflectionShader->SetUniform_ModelMatrix(backEnd.orientation.transformMatrix); gl_reflectionShader->SetUniform_ModelViewProjectionMatrix(glState.modelViewProjectionMatrix[glState.stackIndex]); Tess_Begin(Tess_StageIteratorDebug, NULL, NULL, NULL, qtrue, qfalse, -1, 0); for(j = 0; j < tr.cubeProbes.currentElements; j++) { cubeProbe = (cubemapProbe_t *) Com_GrowListElement(&tr.cubeProbes, j); // bind u_ColorMap GL_SelectTexture(0); GL_Bind(cubeProbe->cubemap); Tess_AddCubeWithNormals(cubeProbe->origin, mins, maxs, colorWhite); } Tess_End(); { cubemapProbe_t *cubeProbeNearest; cubemapProbe_t *cubeProbeSecondNearest; gl_genericShader->DisableAlphaTesting(); gl_genericShader->DisablePortalClipping(); gl_genericShader->DisableVertexSkinning(); gl_genericShader->DisableVertexAnimation(); gl_genericShader->DisableDeformVertexes(); gl_genericShader->DisableTCGenEnvironment(); gl_genericShader->BindProgram(); gl_genericShader->SetUniform_ColorModulate(CGEN_VERTEX, AGEN_VERTEX); gl_genericShader->SetUniform_Color(colorBlack); gl_genericShader->SetRequiredVertexPointers(); GL_State(GLS_DEFAULT); GL_Cull(CT_TWO_SIDED); // set uniforms // set up the transformation matrix backEnd.orientation = backEnd.viewParms.world; GL_LoadModelViewMatrix(backEnd.orientation.modelViewMatrix); gl_genericShader->SetUniform_ModelViewProjectionMatrix(glState.modelViewProjectionMatrix[glState.stackIndex]); // bind u_ColorMap GL_SelectTexture(0); GL_Bind(tr.whiteImage); gl_genericShader->SetUniform_ColorTextureMatrix(matrixIdentity); GL_CheckErrors(); R_FindTwoNearestCubeMaps(backEnd.viewParms.orientation.origin, &cubeProbeNearest, &cubeProbeSecondNearest); Tess_Begin(Tess_StageIteratorDebug, NULL, NULL, NULL, qtrue, qfalse, -1, 0); if(cubeProbeNearest == NULL && cubeProbeSecondNearest == NULL) { // bad } else if(cubeProbeNearest == NULL) { Tess_AddCubeWithNormals(cubeProbeSecondNearest->origin, mins, maxs, colorBlue); } else if(cubeProbeSecondNearest == NULL) { Tess_AddCubeWithNormals(cubeProbeNearest->origin, mins, maxs, colorYellow); } else { Tess_AddCubeWithNormals(cubeProbeNearest->origin, mins, maxs, colorGreen); Tess_AddCubeWithNormals(cubeProbeSecondNearest->origin, mins, maxs, colorRed); } Tess_End(); } // go back to the world modelview matrix backEnd.orientation = backEnd.viewParms.world; GL_LoadModelViewMatrix(backEnd.viewParms.world.modelViewMatrix); } if(r_showLightGrid->integer) { bspGridPoint_t *gridPoint; int j, k; vec3_t offset; vec3_t lightDirection; vec3_t tmp, tmp2, tmp3; vec_t length; vec4_t tetraVerts[4]; if(backEnd.refdef.rdflags & (RDF_NOWORLDMODEL | RDF_NOCUBEMAP)) { return; } GLimp_LogComment("--- r_showLightGrid > 0: Rendering light grid\n"); gl_genericShader->DisableAlphaTesting(); gl_genericShader->DisablePortalClipping(); gl_genericShader->DisableVertexSkinning(); gl_genericShader->DisableVertexAnimation(); gl_genericShader->DisableDeformVertexes(); gl_genericShader->DisableTCGenEnvironment(); gl_genericShader->BindProgram(); gl_genericShader->SetUniform_ColorModulate(CGEN_VERTEX, AGEN_VERTEX); gl_genericShader->SetUniform_Color(colorBlack); gl_genericShader->SetRequiredVertexPointers(); GL_State(GLS_DEFAULT); GL_Cull(CT_TWO_SIDED); // set uniforms // set up the transformation matrix backEnd.orientation = backEnd.viewParms.world; GL_LoadModelViewMatrix(backEnd.orientation.modelViewMatrix); gl_genericShader->SetUniform_ModelViewProjectionMatrix(glState.modelViewProjectionMatrix[glState.stackIndex]); // bind u_ColorMap GL_SelectTexture(0); GL_Bind(tr.whiteImage); gl_genericShader->SetUniform_ColorTextureMatrix(matrixIdentity); GL_CheckErrors(); Tess_Begin(Tess_StageIteratorDebug, NULL, NULL, NULL, qtrue, qfalse, -1, 0); for(j = 0; j < tr.world->numLightGridPoints; j++) { gridPoint = &tr.world->lightGridData[j]; if(DistanceSquared(gridPoint->origin, backEnd.viewParms.orientation.origin) > Square(1024)) continue; VectorNegate(gridPoint->direction, lightDirection); length = 8; VectorMA(gridPoint->origin, 8, lightDirection, offset); PerpendicularVector(tmp, lightDirection); //VectorCopy(up, tmp); VectorScale(tmp, length * 0.1, tmp2); VectorMA(tmp2, length * 0.2, lightDirection, tmp2); for(k = 0; k < 3; k++) { RotatePointAroundVector(tmp3, lightDirection, tmp2, k * 120); VectorAdd(tmp3, gridPoint->origin, tmp3); VectorCopy(tmp3, tetraVerts[k]); tetraVerts[k][3] = 1; } VectorCopy(gridPoint->origin, tetraVerts[3]); tetraVerts[3][3] = 1; Tess_AddTetrahedron(tetraVerts, gridPoint->directedColor); VectorCopy(offset, tetraVerts[3]); tetraVerts[3][3] = 1; Tess_AddTetrahedron(tetraVerts, gridPoint->directedColor); } Tess_End(); // go back to the world modelview matrix backEnd.orientation = backEnd.viewParms.world; GL_LoadModelViewMatrix(backEnd.viewParms.world.modelViewMatrix); } if(r_showBspNodes->integer) { bspNode_t *node; link_t *l, *sentinel; if((backEnd.refdef.rdflags & (RDF_NOWORLDMODEL)) || !tr.world) { return; } gl_genericShader->DisableAlphaTesting(); gl_genericShader->DisablePortalClipping(); gl_genericShader->DisableVertexSkinning(); gl_genericShader->DisableVertexAnimation(); gl_genericShader->DisableDeformVertexes(); gl_genericShader->DisableTCGenEnvironment(); gl_genericShader->BindProgram(); // set uniforms gl_genericShader->SetUniform_ColorModulate(CGEN_CUSTOM_RGB, AGEN_CUSTOM); // bind u_ColorMap GL_SelectTexture(0); GL_Bind(tr.whiteImage); gl_genericShader->SetUniform_ColorTextureMatrix(matrixIdentity); GL_CheckErrors(); for(int i = 0; i < 2; i++) { float x, y, w, h; matrix_t ortho; vec4_t quadVerts[4]; if(i == 1) { // set 2D virtual screen size GL_PushMatrix(); MatrixOrthogonalProjection(ortho, backEnd.viewParms.viewportX, backEnd.viewParms.viewportX + backEnd.viewParms.viewportWidth, backEnd.viewParms.viewportY, backEnd.viewParms.viewportY + backEnd.viewParms.viewportHeight, -99999, 99999); GL_LoadProjectionMatrix(ortho); GL_LoadModelViewMatrix(matrixIdentity); GL_Cull(CT_TWO_SIDED); GL_State(GLS_DEPTHTEST_DISABLE); gl_genericShader->SetUniform_ModelViewProjectionMatrix(glState.modelViewProjectionMatrix[glState.stackIndex]); gl_genericShader->SetUniform_Color(colorBlack); w = 300; h = 300; x = 20; y = 90; VectorSet4(quadVerts[0], x, y, 0, 1); VectorSet4(quadVerts[1], x + w, y, 0, 1); VectorSet4(quadVerts[2], x + w, y + h, 0, 1); VectorSet4(quadVerts[3], x, y + h, 0, 1); Tess_InstantQuad(quadVerts); { int j; vec4_t splitFrustum[6]; vec3_t farCorners[4]; vec3_t nearCorners[4]; vec3_t cropBounds[2]; vec4_t point, transf; GL_Viewport(x, y, w, h); GL_Scissor(x, y, w, h); GL_PushMatrix(); // calculate top down view projection matrix { vec3_t forward = {0, 0, -1}; vec3_t up = {1, 0, 0}; matrix_t rotationMatrix, transformMatrix, viewMatrix, projectionMatrix; // Quake -> OpenGL view matrix from light perspective #if 0 vectoangles(lightDirection, angles); MatrixFromAngles(rotationMatrix, angles[PITCH], angles[YAW], angles[ROLL]); MatrixSetupTransformFromRotation(transformMatrix, rotationMatrix, backEnd.viewParms.orientation.origin); MatrixAffineInverse(transformMatrix, viewMatrix); Matrix4x4Multiply(quakeToOpenGLMatrix, viewMatrix, light->viewMatrix); #else MatrixLookAtRH(viewMatrix, backEnd.viewParms.orientation.origin, forward, up); #endif //ClearBounds(splitFrustumBounds[0], splitFrustumBounds[1]); //BoundsAdd(splitFrustumBounds[0], splitFrustumBounds[1], backEnd.viewParms.visBounds[0], backEnd.viewParms.visBounds[1]); //BoundsAdd(splitFrustumBounds[0], splitFrustumBounds[1], light->worldBounds[0], light->worldBounds[1]); ClearBounds(cropBounds[0], cropBounds[1]); for(j = 0; j < 8; j++) { point[0] = tr.world->models[0].bounds[j & 1][0]; point[1] = tr.world->models[0].bounds[(j >> 1) & 1][1]; point[2] = tr.world->models[0].bounds[(j >> 2) & 1][2]; point[3] = 1; MatrixTransform4(viewMatrix, point, transf); transf[0] /= transf[3]; transf[1] /= transf[3]; transf[2] /= transf[3]; AddPointToBounds(transf, cropBounds[0], cropBounds[1]); } MatrixOrthogonalProjectionRH(projectionMatrix, cropBounds[0][0], cropBounds[1][0], cropBounds[0][1], cropBounds[1][1], -cropBounds[1][2], -cropBounds[0][2]); GL_LoadModelViewMatrix(viewMatrix); GL_LoadProjectionMatrix(projectionMatrix); } // set uniforms gl_genericShader->SetUniform_ColorModulate(CGEN_VERTEX, AGEN_VERTEX); gl_genericShader->SetUniform_Color(colorBlack); GL_State(GLS_POLYMODE_LINE | GLS_DEPTHTEST_DISABLE); GL_Cull(CT_TWO_SIDED); // bind u_ColorMap GL_SelectTexture(0); GL_Bind(tr.whiteImage); gl_genericShader->SetUniform_ColorTextureMatrix(matrixIdentity); gl_genericShader->SetUniform_ModelViewProjectionMatrix(glState.modelViewProjectionMatrix[glState.stackIndex]); tess.multiDrawPrimitives = 0; tess.numIndexes = 0; tess.numVertexes = 0; for(j = 0; j < 6; j++) { VectorCopy(backEnd.viewParms.frustums[0][j].normal, splitFrustum[j]); splitFrustum[j][3] = backEnd.viewParms.frustums[0][j].dist; } // calculate split frustum corner points PlanesGetIntersectionPoint(splitFrustum[FRUSTUM_LEFT], splitFrustum[FRUSTUM_TOP], splitFrustum[FRUSTUM_NEAR], nearCorners[0]); PlanesGetIntersectionPoint(splitFrustum[FRUSTUM_RIGHT], splitFrustum[FRUSTUM_TOP], splitFrustum[FRUSTUM_NEAR], nearCorners[1]); PlanesGetIntersectionPoint(splitFrustum[FRUSTUM_RIGHT], splitFrustum[FRUSTUM_BOTTOM], splitFrustum[FRUSTUM_NEAR], nearCorners[2]); PlanesGetIntersectionPoint(splitFrustum[FRUSTUM_LEFT], splitFrustum[FRUSTUM_BOTTOM], splitFrustum[FRUSTUM_NEAR], nearCorners[3]); PlanesGetIntersectionPoint(splitFrustum[FRUSTUM_LEFT], splitFrustum[FRUSTUM_TOP], splitFrustum[FRUSTUM_FAR], farCorners[0]); PlanesGetIntersectionPoint(splitFrustum[FRUSTUM_RIGHT], splitFrustum[FRUSTUM_TOP], splitFrustum[FRUSTUM_FAR], farCorners[1]); PlanesGetIntersectionPoint(splitFrustum[FRUSTUM_RIGHT], splitFrustum[FRUSTUM_BOTTOM], splitFrustum[FRUSTUM_FAR], farCorners[2]); PlanesGetIntersectionPoint(splitFrustum[FRUSTUM_LEFT], splitFrustum[FRUSTUM_BOTTOM], splitFrustum[FRUSTUM_FAR], farCorners[3]); // draw outer surfaces for(j = 0; j < 4; j++) { VectorSet4(quadVerts[0], nearCorners[j][0], nearCorners[j][1], nearCorners[j][2], 1); VectorSet4(quadVerts[1], farCorners[j][0], farCorners[j][1], farCorners[j][2], 1); VectorSet4(quadVerts[2], farCorners[(j + 1) % 4][0], farCorners[(j + 1) % 4][1], farCorners[(j + 1) % 4][2], 1); VectorSet4(quadVerts[3], nearCorners[(j + 1) % 4][0], nearCorners[(j + 1) % 4][1], nearCorners[(j + 1) % 4][2], 1); Tess_AddQuadStamp2(quadVerts, colorCyan); } // draw far cap VectorSet4(quadVerts[0], farCorners[3][0], farCorners[3][1], farCorners[3][2], 1); VectorSet4(quadVerts[1], farCorners[2][0], farCorners[2][1], farCorners[2][2], 1); VectorSet4(quadVerts[2], farCorners[1][0], farCorners[1][1], farCorners[1][2], 1); VectorSet4(quadVerts[3], farCorners[0][0], farCorners[0][1], farCorners[0][2], 1); Tess_AddQuadStamp2(quadVerts, colorBlue); // draw near cap VectorSet4(quadVerts[0], nearCorners[0][0], nearCorners[0][1], nearCorners[0][2], 1); VectorSet4(quadVerts[1], nearCorners[1][0], nearCorners[1][1], nearCorners[1][2], 1); VectorSet4(quadVerts[2], nearCorners[2][0], nearCorners[2][1], nearCorners[2][2], 1); VectorSet4(quadVerts[3], nearCorners[3][0], nearCorners[3][1], nearCorners[3][2], 1); Tess_AddQuadStamp2(quadVerts, colorGreen); Tess_UpdateVBOs(ATTR_POSITION | ATTR_COLOR); Tess_DrawElements(); gl_genericShader->SetUniform_ColorModulate(CGEN_CUSTOM_RGB, AGEN_CUSTOM); } } // i == 1 else { GL_State(GLS_POLYMODE_LINE | GLS_DEPTHTEST_DISABLE); GL_Cull(CT_TWO_SIDED); // render in world space backEnd.orientation = backEnd.viewParms.world; GL_LoadProjectionMatrix(backEnd.viewParms.projectionMatrix); GL_LoadModelViewMatrix(backEnd.viewParms.world.modelViewMatrix); gl_genericShader->SetUniform_ModelViewProjectionMatrix(glState.modelViewProjectionMatrix[glState.stackIndex]); } // draw BSP nodes sentinel = &tr.traversalStack; for(l = sentinel->next; l != sentinel; l = l->next) { node = (bspNode_t *) l->data; if(!r_dynamicBspOcclusionCulling->integer) { if(node->contents != -1) { if(r_showBspNodes->integer == 3) continue; if(node->numMarkSurfaces <= 0) continue; //if(node->shrinkedAABB) // gl_genericShader->SetUniform_Color(colorBlue); //else if(node->visCounts[tr.visIndex] == tr.visCounts[tr.visIndex]) gl_genericShader->SetUniform_Color(colorGreen); else gl_genericShader->SetUniform_Color(colorRed); } else { if(r_showBspNodes->integer == 2) continue; if(node->visCounts[tr.visIndex] == tr.visCounts[tr.visIndex]) gl_genericShader->SetUniform_Color(colorYellow); else gl_genericShader->SetUniform_Color(colorBlue); } } else { if(node->lastVisited[backEnd.viewParms.viewCount] != backEnd.viewParms.frameCount) continue; if(r_showBspNodes->integer == 5 && node->lastQueried[backEnd.viewParms.viewCount] != backEnd.viewParms.frameCount) continue; if(node->contents != -1) { if(r_showBspNodes->integer == 3) continue; //if(node->occlusionQuerySamples[backEnd.viewParms.viewCount] > 0) if(node->visible[backEnd.viewParms.viewCount]) gl_genericShader->SetUniform_Color(colorGreen); else gl_genericShader->SetUniform_Color(colorRed); } else { if(r_showBspNodes->integer == 2) continue; //if(node->occlusionQuerySamples[backEnd.viewParms.viewCount] > 0) if(node->visible[backEnd.viewParms.viewCount]) gl_genericShader->SetUniform_Color(colorYellow); else gl_genericShader->SetUniform_Color(colorBlue); } if(r_showBspNodes->integer == 4) { gl_genericShader->SetUniform_Color(g_color_table[ColorIndex(node->occlusionQueryNumbers[backEnd.viewParms.viewCount])]); } GL_CheckErrors(); } if(node->contents != -1) { glEnable(GL_POLYGON_OFFSET_FILL); GL_PolygonOffset(r_offsetFactor->value, r_offsetUnits->value); } R_BindVBO(node->volumeVBO); R_BindIBO(node->volumeIBO); GL_VertexAttribsState(ATTR_POSITION); tess.multiDrawPrimitives = 0; tess.numVertexes = node->volumeVerts; tess.numIndexes = node->volumeIndexes; Tess_DrawElements(); tess.numIndexes = 0; tess.numVertexes = 0; if(node->contents != -1) { glDisable(GL_POLYGON_OFFSET_FILL); } } if(i == 1) { tess.multiDrawPrimitives = 0; tess.numIndexes = 0; tess.numVertexes = 0; GL_PopMatrix(); GL_Viewport(backEnd.viewParms.viewportX, backEnd.viewParms.viewportY, backEnd.viewParms.viewportWidth, backEnd.viewParms.viewportHeight); GL_Scissor(backEnd.viewParms.viewportX, backEnd.viewParms.viewportY, backEnd.viewParms.viewportWidth, backEnd.viewParms.viewportHeight); GL_PopMatrix(); } } // go back to the world modelview matrix backEnd.orientation = backEnd.viewParms.world; GL_LoadProjectionMatrix(backEnd.viewParms.projectionMatrix); GL_LoadModelViewMatrix(backEnd.viewParms.world.modelViewMatrix); } if(r_showDecalProjectors->integer) { int i; decalProjector_t *dp; srfDecal_t *srfDecal; vec3_t mins = {-1,-1,-1}; vec3_t maxs = { 1, 1, 1}; if(backEnd.refdef.rdflags & (RDF_NOWORLDMODEL)) { return; } gl_genericShader->DisableAlphaTesting(); gl_genericShader->DisablePortalClipping(); gl_genericShader->DisableVertexSkinning(); gl_genericShader->DisableVertexAnimation(); gl_genericShader->DisableDeformVertexes(); gl_genericShader->DisableTCGenEnvironment(); gl_genericShader->BindProgram(); GL_State(GLS_POLYMODE_LINE | GLS_DEPTHTEST_DISABLE); GL_Cull(CT_TWO_SIDED); // set uniforms gl_genericShader->SetUniform_ColorModulate(CGEN_VERTEX, AGEN_VERTEX); gl_genericShader->SetUniform_Color(colorBlack); // set up the transformation matrix backEnd.orientation = backEnd.viewParms.world; GL_LoadModelViewMatrix(backEnd.orientation.modelViewMatrix); gl_genericShader->SetUniform_ModelViewProjectionMatrix(glState.modelViewProjectionMatrix[glState.stackIndex]); // bind u_ColorMap GL_SelectTexture(0); GL_Bind(tr.whiteImage); gl_genericShader->SetUniform_ColorTextureMatrix(matrixIdentity); GL_CheckErrors(); Tess_Begin(Tess_StageIteratorDebug, NULL, NULL, NULL, qtrue, qfalse, -1, 0); for(i = 0, dp = backEnd.refdef.decalProjectors; i < backEnd.refdef.numDecalProjectors; i++, dp++) { if(DistanceSquared(dp->center, backEnd.viewParms.orientation.origin) > Square(1024)) continue; Tess_AddCube(dp->center, mins, maxs, colorRed); Tess_AddCube(vec3_origin, dp->mins, dp->maxs, colorBlue); } glEnable(GL_POLYGON_OFFSET_FILL); GL_PolygonOffset(r_offsetFactor->value, r_offsetUnits->value); for(i = 0, srfDecal = backEnd.refdef.decals; i < backEnd.refdef.numDecals; i++, srfDecal++) { rb_surfaceTable[SF_DECAL] (srfDecal); } glDisable(GL_POLYGON_OFFSET_FILL); Tess_End(); // go back to the world modelview matrix //backEnd.orientation = backEnd.viewParms.world; //GL_LoadModelViewMatrix(backEnd.viewParms.world.modelViewMatrix); } GL_CheckErrors(); } /* ================== RB_RenderView ================== */ static void RB_RenderView(void) { if(r_logFile->integer) { // don't just call LogComment, or we will get a call to va() every frame! GLimp_LogComment(va ("--- RB_RenderView( %i surfaces, %i interactions ) ---\n", backEnd.viewParms.numDrawSurfs, backEnd.viewParms.numInteractions)); } //ri.Error(ERR_FATAL, "test"); GL_CheckErrors(); backEnd.pc.c_surfaces += backEnd.viewParms.numDrawSurfs; if(DS_STANDARD_ENABLED()) { // // Deferred shading path // int clearBits = 0; int startTime = 0, endTime = 0; // sync with gl if needed if(r_finish->integer == 1 && !glState.finishCalled) { glFinish(); glState.finishCalled = qtrue; } if(r_finish->integer == 0) { glState.finishCalled = qtrue; } // we will need to change the projection matrix before drawing // 2D images again backEnd.projection2D = qfalse; // set the modelview matrix for the viewer SetViewportAndScissor(); // ensures that depth writes are enabled for the depth clear GL_State(GLS_DEFAULT); // clear frame buffer objects R_BindNullFBO(); //R_BindFBO(tr.deferredRenderFBO); //glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); clearBits = GL_DEPTH_BUFFER_BIT; /* if(r_measureOverdraw->integer || r_shadows->integer == SHADOWING_STENCIL) { clearBits |= GL_STENCIL_BUFFER_BIT; } */ if(!(backEnd.refdef.rdflags & RDF_NOWORLDMODEL)) { clearBits |= GL_COLOR_BUFFER_BIT; GL_ClearColor(0.0f, 0.0f, 0.0f, 1.0f); // FIXME: get color of sky } glClear(clearBits); R_BindFBO(tr.geometricRenderFBO); if(!(backEnd.refdef.rdflags & RDF_NOWORLDMODEL)) { //clearBits = GL_COLOR_BUFFER_BIT; GL_ClearColor(0.0f, 0.0f, 0.0f, 1.0f); // FIXME: get color of sky } else { /* if(glConfig2.framebufferBlitAvailable) { glDrawBuffers(1, geometricRenderTargets); // copy color of the main context to geometricRenderFBO glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, 0); glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, tr.deferredRenderFBO->frameBuffer); glBlitFramebufferEXT(0, 0, glConfig.vidWidth, glConfig.vidHeight, 0, 0, glConfig.vidWidth, glConfig.vidHeight, GL_COLOR_BUFFER_BIT, GL_NEAREST); } */ } glClear(clearBits); if((backEnd.refdef.rdflags & RDF_HYPERSPACE)) { RB_Hyperspace(); return; } else { backEnd.isHyperspace = qfalse; } glState.faceCulling = -1; // force face culling to set next time // we will only draw a sun if there was sky rendered in this view backEnd.skyRenderedThisView = qfalse; GL_CheckErrors(); //RB_RenderDrawSurfacesIntoGeometricBuffer(); if(r_speeds->integer == RSPEEDS_SHADING_TIMES) { glFinish(); startTime = ri.Milliseconds(); } // draw everything that is opaque R_BindFBO(tr.geometricRenderFBO); RB_RenderDrawSurfaces(true, true, DRAWSURFACES_ALL); if(r_speeds->integer == RSPEEDS_SHADING_TIMES) { glFinish(); endTime = ri.Milliseconds(); backEnd.pc.c_deferredGBufferTime = endTime - startTime; } // try to cull lights using hardware occlusion queries R_BindFBO(tr.geometricRenderFBO); RB_RenderLightOcclusionQueries(); if(r_speeds->integer == RSPEEDS_SHADING_TIMES) { glFinish(); startTime = ri.Milliseconds(); } if(!r_showDeferredRender->integer) { if(r_shadows->integer >= SHADOWING_ESM16) { // render dynamic shadowing and lighting using shadow mapping RB_RenderInteractionsDeferredShadowMapped(); } else { // render dynamic lighting RB_RenderInteractionsDeferred(); } } if(r_speeds->integer == RSPEEDS_SHADING_TIMES) { glFinish(); endTime = ri.Milliseconds(); backEnd.pc.c_deferredLightingTime = endTime - startTime; } R_BindFBO(tr.geometricRenderFBO); glDrawBuffers(1, geometricRenderTargets); // render global fog RB_RenderGlobalFog(); if(r_speeds->integer == RSPEEDS_SHADING_TIMES) { glFinish(); startTime = ri.Milliseconds(); } // draw everything that is translucent R_BindFBO(tr.geometricRenderFBO); RB_RenderDrawSurfaces(false, false, DRAWSURFACES_ALL); if(r_speeds->integer == RSPEEDS_SHADING_TIMES) { glFinish(); endTime = ri.Milliseconds(); backEnd.pc.c_forwardTranslucentTime = endTime - startTime; } // render debug information R_BindFBO(tr.geometricRenderFBO); RB_RenderDebugUtils(); // scale down rendered HDR scene to 1 / 4th if(r_hdrRendering->integer) { // FIXME /* if(glConfig2.framebufferBlitAvailable) { glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, tr.geometricRenderFBO->frameBuffer); glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, tr.downScaleFBO_quarter->frameBuffer); glBlitFramebufferEXT(0, 0, glConfig.vidWidth, glConfig.vidHeight, 0, 0, glConfig.vidWidth * 0.25f, glConfig.vidHeight * 0.25f, GL_COLOR_BUFFER_BIT, GL_LINEAR); glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, tr.geometricRenderFBO->frameBuffer); glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, tr.downScaleFBO_64x64->frameBuffer); glBlitFramebufferEXT(0, 0, glConfig.vidWidth, glConfig.vidHeight, 0, 0, 64, 64, GL_COLOR_BUFFER_BIT, GL_LINEAR); } else { // FIXME add non EXT_framebuffer_blit code } */ RB_CalculateAdaptation(); } else { /* if(glConfig2.framebufferBlitAvailable) { // copy deferredRenderFBO to downScaleFBO_quarter glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, tr.deferredRenderFBO->frameBuffer); glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, tr.downScaleFBO_quarter->frameBuffer); glBlitFramebufferEXT(0, 0, glConfig.vidWidth, glConfig.vidHeight, 0, 0, glConfig.vidWidth * 0.25f, glConfig.vidHeight * 0.25f, GL_COLOR_BUFFER_BIT, GL_LINEAR); } else { // FIXME add non EXT_framebuffer_blit code } */ } GL_CheckErrors(); // render bloom post process effect //RB_RenderBloom(); // copy offscreen rendered scene to the current OpenGL context RB_RenderDeferredShadingResultToFrameBuffer(); if(backEnd.viewParms.isPortal) { /* if(glConfig2.framebufferBlitAvailable) { // copy deferredRenderFBO to portalRenderFBO glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, tr.deferredRenderFBO->frameBuffer); glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, tr.portalRenderFBO->frameBuffer); glBlitFramebufferEXT(0, 0, tr.deferredRenderFBO->width, tr.deferredRenderFBO->height, 0, 0, tr.portalRenderFBO->width, tr.portalRenderFBO->height, GL_COLOR_BUFFER_BIT, GL_NEAREST); } else { // capture current color buffer GL_SelectTexture(0); GL_Bind(tr.portalRenderImage); glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, tr.portalRenderImage->uploadWidth, tr.portalRenderImage->uploadHeight); } */ backEnd.pc.c_portals++; } } else { // // Forward shading path // int clearBits = 0; int startTime = 0, endTime = 0; // sync with gl if needed if(r_finish->integer == 1 && !glState.finishCalled) { glFinish(); glState.finishCalled = qtrue; } if(r_finish->integer == 0) { glState.finishCalled = qtrue; } // disable offscreen rendering if(glConfig2.framebufferObjectAvailable) { if(r_hdrRendering->integer && glConfig2.textureFloatAvailable) R_BindFBO(tr.deferredRenderFBO); else R_BindNullFBO(); } // we will need to change the projection matrix before drawing // 2D images again backEnd.projection2D = qfalse; // set the modelview matrix for the viewer SetViewportAndScissor(); // ensures that depth writes are enabled for the depth clear GL_State(GLS_DEFAULT); // clear relevant buffers clearBits = GL_DEPTH_BUFFER_BIT; if(r_measureOverdraw->integer) { clearBits |= GL_STENCIL_BUFFER_BIT; } #if defined(COMPAT_ET) // ydnar: global q3 fog volume else if(tr.world && tr.world->globalFog >= 0) { clearBits |= GL_DEPTH_BUFFER_BIT; if(!(backEnd.refdef.rdflags & RDF_NOWORLDMODEL)) { clearBits |= GL_COLOR_BUFFER_BIT; GL_ClearColor(tr.world->fogs[tr.world->globalFog].color[0], tr.world->fogs[tr.world->globalFog].color[1], tr.world->fogs[tr.world->globalFog].color[2], 1.0); } } else if(tr.world && tr.world->hasSkyboxPortal) { if(backEnd.refdef.rdflags & RDF_SKYBOXPORTAL) { // portal scene, clear whatever is necessary clearBits |= GL_DEPTH_BUFFER_BIT; if(r_fastsky->integer || backEnd.refdef.rdflags & RDF_NOWORLDMODEL) { // fastsky: clear color // try clearing first with the portal sky fog color, then the world fog color, then finally a default clearBits |= GL_COLOR_BUFFER_BIT; if(tr.glfogsettings[FOG_PORTALVIEW].registered) { GL_ClearColor(tr.glfogsettings[FOG_PORTALVIEW].color[0], tr.glfogsettings[FOG_PORTALVIEW].color[1], tr.glfogsettings[FOG_PORTALVIEW].color[2], tr.glfogsettings[FOG_PORTALVIEW].color[3]); } else if(tr.glfogNum > FOG_NONE && tr.glfogsettings[FOG_CURRENT].registered) { GL_ClearColor(tr.glfogsettings[FOG_CURRENT].color[0], tr.glfogsettings[FOG_CURRENT].color[1], tr.glfogsettings[FOG_CURRENT].color[2], tr.glfogsettings[FOG_CURRENT].color[3]); } else { // GL_ClearColor ( 1.0, 0.0, 0.0, 1.0 ); // red clear for testing portal sky clear GL_ClearColor(0.5, 0.5, 0.5, 1.0); } } else { // rendered sky (either clear color or draw quake sky) if(tr.glfogsettings[FOG_PORTALVIEW].registered) { GL_ClearColor(tr.glfogsettings[FOG_PORTALVIEW].color[0], tr.glfogsettings[FOG_PORTALVIEW].color[1], tr.glfogsettings[FOG_PORTALVIEW].color[2], tr.glfogsettings[FOG_PORTALVIEW].color[3]); if(tr.glfogsettings[FOG_PORTALVIEW].clearscreen) { // portal fog requests a screen clear (distance fog rather than quake sky) clearBits |= GL_COLOR_BUFFER_BIT; } } } } else { // world scene with portal sky, don't clear any buffers, just set the fog color if there is one clearBits |= GL_DEPTH_BUFFER_BIT; // this will go when I get the portal sky rendering way out in the zbuffer (or not writing to zbuffer at all) if(tr.glfogNum > FOG_NONE && tr.glfogsettings[FOG_CURRENT].registered) { if(backEnd.refdef.rdflags & RDF_UNDERWATER) { if(tr.glfogsettings[FOG_CURRENT].mode == GL_LINEAR) { clearBits |= GL_COLOR_BUFFER_BIT; } } else if(!r_portalSky->integer) { // portal skies have been manually turned off, clear bg color clearBits |= GL_COLOR_BUFFER_BIT; } GL_ClearColor(tr.glfogsettings[FOG_CURRENT].color[0], tr.glfogsettings[FOG_CURRENT].color[1], tr.glfogsettings[FOG_CURRENT].color[2], tr.glfogsettings[FOG_CURRENT].color[3]); } else if(!r_portalSky->integer) { // ydnar: portal skies have been manually turned off, clear bg color clearBits |= GL_COLOR_BUFFER_BIT; GL_ClearColor(0.5, 0.5, 0.5, 1.0); } } } else { // world scene with no portal sky clearBits |= GL_DEPTH_BUFFER_BIT; // NERVE - SMF - we don't want to clear the buffer when no world model is specified if(backEnd.refdef.rdflags & RDF_NOWORLDMODEL) { clearBits &= ~GL_COLOR_BUFFER_BIT; } // -NERVE - SMF else if(r_fastsky->integer || backEnd.refdef.rdflags & RDF_NOWORLDMODEL) { clearBits |= GL_COLOR_BUFFER_BIT; if(tr.glfogsettings[FOG_CURRENT].registered) { // try to clear fastsky with current fog color GL_ClearColor(tr.glfogsettings[FOG_CURRENT].color[0], tr.glfogsettings[FOG_CURRENT].color[1], tr.glfogsettings[FOG_CURRENT].color[2], tr.glfogsettings[FOG_CURRENT].color[3]); } else { // GL_ClearColor ( 0.0, 0.0, 1.0, 1.0 ); // blue clear for testing world sky clear GL_ClearColor(0.05, 0.05, 0.05, 1.0); // JPW NERVE changed per id req was 0.5s } } else { // world scene, no portal sky, not fastsky, clear color if fog says to, otherwise, just set the clearcolor if(tr.glfogsettings[FOG_CURRENT].registered) { // try to clear fastsky with current fog color GL_ClearColor(tr.glfogsettings[FOG_CURRENT].color[0], tr.glfogsettings[FOG_CURRENT].color[1], tr.glfogsettings[FOG_CURRENT].color[2], tr.glfogsettings[FOG_CURRENT].color[3]); if(tr.glfogsettings[FOG_CURRENT].clearscreen) { // world fog requests a screen clear (distance fog rather than quake sky) clearBits |= GL_COLOR_BUFFER_BIT; } } } if(HDR_ENABLED()) { // copy color of the main context to deferredRenderFBO glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, 0); glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, tr.deferredRenderFBO->frameBuffer); glBlitFramebufferEXT(0, 0, glConfig.vidWidth, glConfig.vidHeight, 0, 0, glConfig.vidWidth, glConfig.vidHeight, GL_COLOR_BUFFER_BIT, GL_NEAREST); } } #else if(!(backEnd.refdef.rdflags & RDF_NOWORLDMODEL)) { clearBits |= GL_COLOR_BUFFER_BIT; // FIXME: only if sky shaders have been used GL_ClearColor(0.0f, 0.0f, 0.0f, 1.0f); // FIXME: get color of sky } else { if(HDR_ENABLED()) { // copy color of the main context to deferredRenderFBO glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, 0); glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, tr.deferredRenderFBO->frameBuffer); glBlitFramebufferEXT(0, 0, glConfig.vidWidth, glConfig.vidHeight, 0, 0, glConfig.vidWidth, glConfig.vidHeight, GL_COLOR_BUFFER_BIT, GL_NEAREST); } } #endif glClear(clearBits); if((backEnd.refdef.rdflags & RDF_HYPERSPACE)) { RB_Hyperspace(); return; } else { backEnd.isHyperspace = qfalse; } glState.faceCulling = -1; // force face culling to set next time // we will only draw a sun if there was sky rendered in this view backEnd.skyRenderedThisView = qfalse; GL_CheckErrors(); if(r_speeds->integer == RSPEEDS_SHADING_TIMES) { glFinish(); startTime = ri.Milliseconds(); } if(r_dynamicEntityOcclusionCulling->integer) { // draw everything from world that is opaque into black so we can benefit from early-z rejections later //RB_RenderOpaqueSurfacesIntoDepth(true); RB_RenderDrawSurfaces(true, false, DRAWSURFACES_WORLD_ONLY); // try to cull entities using hardware occlusion queries RB_RenderEntityOcclusionQueries(); // draw everything that is opaque RB_RenderDrawSurfaces(true, false, DRAWSURFACES_ENTITIES_ONLY); } else { // draw everything that is opaque RB_RenderDrawSurfaces(true, false, DRAWSURFACES_ALL); // try to cull entities using hardware occlusion queries //RB_RenderEntityOcclusionQueries(); } // try to cull bsp nodes for the next frame using hardware occlusion queries //RB_RenderBspOcclusionQueries(); if(r_speeds->integer == RSPEEDS_SHADING_TIMES) { glFinish(); endTime = ri.Milliseconds(); backEnd.pc.c_forwardAmbientTime = endTime - startTime; } // try to cull lights using hardware occlusion queries RB_RenderLightOcclusionQueries(); if(r_shadows->integer >= SHADOWING_ESM16) { // render dynamic shadowing and lighting using shadow mapping RB_RenderInteractionsShadowMapped(); // render player shadows if any //RB_RenderInteractionsDeferredInverseShadows(); } else { // render dynamic lighting RB_RenderInteractions(); } // render ambient occlusion process effect // Tr3B: needs way more work RB_RenderScreenSpaceAmbientOcclusion(qfalse); if(HDR_ENABLED()) R_BindFBO(tr.deferredRenderFBO); // render global fog post process effect RB_RenderGlobalFog(); // draw everything that is translucent RB_RenderDrawSurfaces(false, false, DRAWSURFACES_ALL); // scale down rendered HDR scene to 1 / 4th if(HDR_ENABLED()) { if(glConfig2.framebufferBlitAvailable) { glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, tr.deferredRenderFBO->frameBuffer); glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, tr.downScaleFBO_quarter->frameBuffer); glBlitFramebufferEXT(0, 0, glConfig.vidWidth, glConfig.vidHeight, 0, 0, glConfig.vidWidth * 0.25f, glConfig.vidHeight * 0.25f, GL_COLOR_BUFFER_BIT, GL_LINEAR); glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, tr.deferredRenderFBO->frameBuffer); glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, tr.downScaleFBO_64x64->frameBuffer); glBlitFramebufferEXT(0, 0, glConfig.vidWidth, glConfig.vidHeight, 0, 0, 64, 64, GL_COLOR_BUFFER_BIT, GL_LINEAR); } else { // FIXME add non EXT_framebuffer_blit code } RB_CalculateAdaptation(); } else { /* Tr3B: FIXME this causes: caught OpenGL error: GL_INVALID_OPERATION in file code/renderer/tr_backend.c line 6479 if(glConfig2.framebufferBlitAvailable) { // copy deferredRenderFBO to downScaleFBO_quarter glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, 0); glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, tr.downScaleFBO_quarter->frameBuffer); glBlitFramebufferEXT(0, 0, glConfig.vidWidth, glConfig.vidHeight, 0, 0, glConfig.vidWidth * 0.25f, glConfig.vidHeight * 0.25f, GL_COLOR_BUFFER_BIT, GL_NEAREST); } else { // FIXME add non EXT_framebuffer_blit code } */ } GL_CheckErrors(); #ifdef EXPERIMENTAL // render depth of field post process effect RB_RenderDepthOfField(qfalse); #endif // render bloom post process effect RB_RenderBloom(); // copy offscreen rendered HDR scene to the current OpenGL context RB_RenderDeferredHDRResultToFrameBuffer(); // render rotoscope post process effect RB_RenderRotoscope(); #if 0 // add the sun flare RB_DrawSun(); #endif #if 0 // add light flares on lights that aren't obscured RB_RenderFlares(); #endif // wait until all bsp node occlusion queries are back //RB_CollectBspOcclusionQueries(); // render debug information RB_RenderDebugUtils(); if(backEnd.viewParms.isPortal) { #if 0 if(r_hdrRendering->integer && glConfig.textureFloatAvailable && glConfig.framebufferObjectAvailable && glConfig.framebufferBlitAvailable) { // copy deferredRenderFBO to portalRenderFBO glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, tr.deferredRenderFBO->frameBuffer); glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, tr.portalRenderFBO->frameBuffer); glBlitFramebufferEXT(0, 0, tr.deferredRenderFBO->width, tr.deferredRenderFBO->height, 0, 0, tr.portalRenderFBO->width, tr.portalRenderFBO->height, GL_COLOR_BUFFER_BIT, GL_NEAREST); } #endif #if 0 // FIXME: this trashes the OpenGL context for an unknown reason if(glConfig2.framebufferObjectAvailable && glConfig2.framebufferBlitAvailable) { // copy main context to portalRenderFBO glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, 0); glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, tr.portalRenderFBO->frameBuffer); glBlitFramebufferEXT(0, 0, glConfig.vidWidth, glConfig.vidHeight, 0, 0, glConfig.vidWidth, glConfig.vidHeight, GL_COLOR_BUFFER_BIT, GL_NEAREST); } #endif //else { // capture current color buffer GL_SelectTexture(0); GL_Bind(tr.portalRenderImage); glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, tr.portalRenderImage->uploadWidth, tr.portalRenderImage->uploadHeight); } backEnd.pc.c_portals++; } #if 0 if(r_dynamicBspOcclusionCulling->integer) { // copy depth of the main context to deferredRenderFBO glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, 0); glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, tr.occlusionRenderFBO->frameBuffer); glBlitFramebufferEXT(0, 0, glConfig.vidWidth, glConfig.vidHeight, 0, 0, glConfig.vidWidth, glConfig.vidHeight, GL_DEPTH_BUFFER_BIT, GL_NEAREST); } #endif } // render chromatric aberration RB_CameraPostFX(); // copy to given byte buffer that is NOT a FBO if(tr.refdef.pixelTarget != NULL) { int i; // need to convert Y axis #if 0 glReadPixels(0, 0, tr.refdef.pixelTargetWidth, tr.refdef.pixelTargetHeight, GL_RGBA, GL_UNSIGNED_BYTE, tr.refdef.pixelTarget); #else // Bugfix: drivers absolutely hate running in high res and using glReadPixels near the top or bottom edge. // Soo.. lets do it in the middle. glReadPixels(glConfig.vidWidth / 2, glConfig.vidHeight / 2, tr.refdef.pixelTargetWidth, tr.refdef.pixelTargetHeight, GL_RGBA, GL_UNSIGNED_BYTE, tr.refdef.pixelTarget); #endif for(i = 0; i < tr.refdef.pixelTargetWidth * tr.refdef.pixelTargetHeight; i++) { tr.refdef.pixelTarget[(i * 4) + 3] = 255; //set the alpha pure white } } GL_CheckErrors(); backEnd.pc.c_views++; } /* ============================================================================ RENDER BACK END THREAD FUNCTIONS ============================================================================ */ /* ============= 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_SyncRenderThread(); // we definately want to sync every frame for the cinematics glFinish(); start = end = 0; if(r_speeds->integer) { glFinish(); 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); } RB_SetGL2D(); glVertexAttrib4fARB(ATTR_INDEX_NORMAL, 0, 0, 1, 1); glVertexAttrib4fARB(ATTR_INDEX_COLOR, tr.identityLight, tr.identityLight, tr.identityLight, 1); gl_genericShader->DisableAlphaTesting(); gl_genericShader->DisablePortalClipping(); gl_genericShader->DisableVertexSkinning(); gl_genericShader->DisableVertexAnimation(); gl_genericShader->DisableDeformVertexes(); gl_genericShader->DisableTCGenEnvironment(); gl_genericShader->BindProgram(); // set uniforms gl_genericShader->SetUniform_ColorModulate(CGEN_VERTEX, AGEN_VERTEX); gl_genericShader->SetUniform_Color(colorBlack); gl_genericShader->SetUniform_ModelViewProjectionMatrix(glState.modelViewProjectionMatrix[glState.stackIndex]); // bind u_ColorMap GL_SelectTexture(0); GL_Bind(tr.scratchImage[client]); gl_genericShader->SetUniform_ColorTextureMatrix(matrixIdentity); // 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; glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, cols, rows, 0, GL_RGBA, GL_UNSIGNED_BYTE, data); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameterf(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 glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, cols, rows, GL_RGBA, GL_UNSIGNED_BYTE, data); } } if(r_speeds->integer) { glFinish(); end = ri.Milliseconds(); ri.Printf(PRINT_ALL, "glTexSubImage2D %i, %i: %i msec\n", cols, rows, end - start); } /* glBegin(GL_QUADS); glVertexAttrib4fARB(ATTR_INDEX_TEXCOORD0, 0.5f / cols, 0.5f / rows, 0, 1); glVertexAttrib4fARB(ATTR_INDEX_POSITION, x, y, 0, 1); glVertexAttrib4fARB(ATTR_INDEX_TEXCOORD0, (cols - 0.5f) / cols, 0.5f / rows, 0, 1); glVertexAttrib4fARB(ATTR_INDEX_POSITION, x + w, y, 0, 1); glVertexAttrib4fARB(ATTR_INDEX_TEXCOORD0, (cols - 0.5f) / cols, (rows - 0.5f) / rows, 0, 1); glVertexAttrib4fARB(ATTR_INDEX_POSITION, x + w, y + h, 0, 1); glVertexAttrib4fARB(ATTR_INDEX_TEXCOORD0, 0.5f / cols, (rows - 0.5f) / rows, 0, 1); glVertexAttrib4fARB(ATTR_INDEX_POSITION, x, y + h, 0, 1); glEnd(); */ tess.multiDrawPrimitives = 0; tess.numVertexes = 0; tess.numIndexes = 0; tess.xyz[tess.numVertexes][0] = x; tess.xyz[tess.numVertexes][1] = y; tess.xyz[tess.numVertexes][2] = 0; tess.xyz[tess.numVertexes][3] = 1; tess.texCoords[tess.numVertexes][0] = 0.5f / cols; tess.texCoords[tess.numVertexes][1] = 0.5f / rows; tess.texCoords[tess.numVertexes][2] = 0; tess.texCoords[tess.numVertexes][3] = 1; tess.numVertexes++; tess.xyz[tess.numVertexes][0] = x + w; tess.xyz[tess.numVertexes][1] = y; tess.xyz[tess.numVertexes][2] = 0; tess.xyz[tess.numVertexes][3] = 1; tess.texCoords[tess.numVertexes][0] = (cols - 0.5f) / cols; tess.texCoords[tess.numVertexes][1] = 0.5f / rows; tess.texCoords[tess.numVertexes][2] = 0; tess.texCoords[tess.numVertexes][3] = 1; tess.numVertexes++; tess.xyz[tess.numVertexes][0] = x + w; tess.xyz[tess.numVertexes][1] = y + h; tess.xyz[tess.numVertexes][2] = 0; tess.xyz[tess.numVertexes][3] = 1; tess.texCoords[tess.numVertexes][0] = (cols - 0.5f) / cols; tess.texCoords[tess.numVertexes][1] = (rows - 0.5f) / rows; tess.texCoords[tess.numVertexes][2] = 0; tess.texCoords[tess.numVertexes][3] = 1; tess.numVertexes++; tess.xyz[tess.numVertexes][0] = x; tess.xyz[tess.numVertexes][1] = y + h; tess.xyz[tess.numVertexes][2] = 0; tess.xyz[tess.numVertexes][3] = 1; tess.texCoords[tess.numVertexes][0] = 0.5f / cols; tess.texCoords[tess.numVertexes][1] = (rows - 0.5f) / rows; tess.texCoords[tess.numVertexes][2] = 0; tess.texCoords[tess.numVertexes][3] = 1; tess.numVertexes++; tess.indexes[tess.numIndexes++] = 0; tess.indexes[tess.numIndexes++] = 1; tess.indexes[tess.numIndexes++] = 2; tess.indexes[tess.numIndexes++] = 0; tess.indexes[tess.numIndexes++] = 2; tess.indexes[tess.numIndexes++] = 3; Tess_UpdateVBOs(ATTR_POSITION | ATTR_TEXCOORD); Tess_DrawElements(); tess.multiDrawPrimitives = 0; tess.numVertexes = 0; tess.numIndexes = 0; GL_CheckErrors(); } void RE_UploadCinematic(int w, int h, int cols, int rows, const byte * data, int client, qboolean dirty) { 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; glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, cols, rows, 0, GL_RGBA, GL_UNSIGNED_BYTE, data); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, colorBlack); } 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 glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, cols, rows, GL_RGBA, GL_UNSIGNED_BYTE, data); } } GL_CheckErrors(); } /* ============= RB_SetColor ============= */ const void *RB_SetColor(const void *data) { const setColorCommand_t *cmd; GLimp_LogComment("--- RB_SetColor ---\n"); cmd = (const setColorCommand_t *)data; backEnd.color2D[0] = cmd->color[0]; backEnd.color2D[1] = cmd->color[1]; backEnd.color2D[2] = cmd->color[2]; backEnd.color2D[3] = cmd->color[3]; return (const void *)(cmd + 1); } /* ============= RB_StretchPic ============= */ const void *RB_StretchPic(const void *data) { int i; const stretchPicCommand_t *cmd; shader_t *shader; int numVerts, numIndexes; GLimp_LogComment("--- RB_StretchPic ---\n"); cmd = (const stretchPicCommand_t *)data; if(!backEnd.projection2D) { RB_SetGL2D(); } shader = cmd->shader; if(shader != tess.surfaceShader) { if(tess.numIndexes) { Tess_End(); } backEnd.currentEntity = &backEnd.entity2D; Tess_Begin(Tess_StageIteratorGeneric, NULL, shader, NULL, qfalse, qfalse, -1, 0); } Tess_CheckOverflow(4, 6); numVerts = tess.numVertexes; numIndexes = tess.numIndexes; tess.numVertexes += 4; tess.numIndexes += 6; tess.indexes[numIndexes] = numVerts + 3; tess.indexes[numIndexes + 1] = numVerts + 0; tess.indexes[numIndexes + 2] = numVerts + 2; tess.indexes[numIndexes + 3] = numVerts + 2; tess.indexes[numIndexes + 4] = numVerts + 0; tess.indexes[numIndexes + 5] = numVerts + 1; for(i = 0; i < 4; i++) { tess.colors[numVerts + i][0] = backEnd.color2D[0]; tess.colors[numVerts + i][1] = backEnd.color2D[1]; tess.colors[numVerts + i][2] = backEnd.color2D[2]; tess.colors[numVerts + i][3] = backEnd.color2D[3]; } tess.xyz[numVerts][0] = cmd->x; tess.xyz[numVerts][1] = cmd->y; tess.xyz[numVerts][2] = 0; tess.xyz[numVerts][3] = 1; tess.texCoords[numVerts][0] = cmd->s1; tess.texCoords[numVerts][1] = cmd->t1; tess.texCoords[numVerts][2] = 0; tess.texCoords[numVerts][3] = 1; tess.xyz[numVerts + 1][0] = cmd->x + cmd->w; tess.xyz[numVerts + 1][1] = cmd->y; tess.xyz[numVerts + 1][2] = 0; tess.xyz[numVerts + 1][3] = 1; tess.texCoords[numVerts + 1][0] = cmd->s2; tess.texCoords[numVerts + 1][1] = cmd->t1; tess.texCoords[numVerts + 1][2] = 0; tess.texCoords[numVerts + 1][3] = 1; tess.xyz[numVerts + 2][0] = cmd->x + cmd->w; tess.xyz[numVerts + 2][1] = cmd->y + cmd->h; tess.xyz[numVerts + 2][2] = 0; tess.xyz[numVerts + 2][3] = 1; tess.texCoords[numVerts + 2][0] = cmd->s2; tess.texCoords[numVerts + 2][1] = cmd->t2; tess.texCoords[numVerts + 2][2] = 0; tess.texCoords[numVerts + 2][3] = 1; tess.xyz[numVerts + 3][0] = cmd->x; tess.xyz[numVerts + 3][1] = cmd->y + cmd->h; tess.xyz[numVerts + 3][2] = 0; tess.xyz[numVerts + 3][3] = 1; tess.texCoords[numVerts + 3][0] = cmd->s1; tess.texCoords[numVerts + 3][1] = cmd->t2; tess.texCoords[numVerts + 3][2] = 0; tess.texCoords[numVerts + 3][3] = 1; return (const void *)(cmd + 1); } const void *RB_Draw2dPolys(const void *data) { const poly2dCommand_t *cmd; shader_t *shader; int i; cmd = (const poly2dCommand_t *)data; if(!backEnd.projection2D) { RB_SetGL2D(); } shader = cmd->shader; if(shader != tess.surfaceShader) { if(tess.numIndexes) { Tess_End(); } backEnd.currentEntity = &backEnd.entity2D; Tess_Begin(Tess_StageIteratorGeneric, NULL, shader, NULL, qfalse, qfalse, -1, 0); } Tess_CheckOverflow(cmd->numverts, (cmd->numverts - 2) * 3); for(i = 0; i < cmd->numverts - 2; i++) { tess.indexes[tess.numIndexes + 0] = tess.numVertexes; tess.indexes[tess.numIndexes + 1] = tess.numVertexes + i + 1; tess.indexes[tess.numIndexes + 2] = tess.numVertexes + i + 2; tess.numIndexes += 3; } for(i = 0; i < cmd->numverts; i++) { tess.xyz[tess.numVertexes][0] = cmd->verts[i].xyz[0]; tess.xyz[tess.numVertexes][1] = cmd->verts[i].xyz[1]; tess.xyz[tess.numVertexes][2] = 0; tess.xyz[tess.numVertexes][3] = 1; tess.texCoords[tess.numVertexes][0] = cmd->verts[i].st[0]; tess.texCoords[tess.numVertexes][1] = cmd->verts[i].st[1]; tess.colors[tess.numVertexes][0] = cmd->verts[i].modulate[0]; tess.colors[tess.numVertexes][1] = cmd->verts[i].modulate[1]; tess.colors[tess.numVertexes][2] = cmd->verts[i].modulate[2]; tess.colors[tess.numVertexes][3] = cmd->verts[i].modulate[3]; tess.numVertexes++; } return (const void *)(cmd + 1); } // NERVE - SMF /* ============= RB_RotatedPic ============= */ const void *RB_RotatedPic(const void *data) { const stretchPicCommand_t *cmd; shader_t *shader; int numVerts, numIndexes; float angle; float pi2 = M_PI * 2; cmd = (const stretchPicCommand_t *)data; if(!backEnd.projection2D) { RB_SetGL2D(); } shader = cmd->shader; if(shader != tess.surfaceShader) { if(tess.numIndexes) { Tess_End(); } backEnd.currentEntity = &backEnd.entity2D; Tess_Begin(Tess_StageIteratorGeneric, NULL, shader, NULL, qfalse, qfalse, -1, 0); } Tess_CheckOverflow(4, 6); numVerts = tess.numVertexes; numIndexes = tess.numIndexes; tess.numVertexes += 4; tess.numIndexes += 6; tess.indexes[numIndexes] = numVerts + 3; tess.indexes[numIndexes + 1] = numVerts + 0; tess.indexes[numIndexes + 2] = numVerts + 2; tess.indexes[numIndexes + 3] = numVerts + 2; tess.indexes[numIndexes + 4] = numVerts + 0; tess.indexes[numIndexes + 5] = numVerts + 1; Vector4Copy(backEnd.color2D, tess.colors[numVerts + 0]); Vector4Copy(backEnd.color2D, tess.colors[numVerts + 1]); Vector4Copy(backEnd.color2D, tess.colors[numVerts + 2]); Vector4Copy(backEnd.color2D, tess.colors[numVerts + 3]); angle = cmd->angle * pi2; tess.xyz[numVerts][0] = cmd->x + (cos(angle) * cmd->w); tess.xyz[numVerts][1] = cmd->y + (sin(angle) * cmd->h); tess.xyz[numVerts][2] = 0; tess.xyz[numVerts][3] = 1; tess.texCoords[numVerts][0] = cmd->s1; tess.texCoords[numVerts][1] = cmd->t1; angle = cmd->angle * pi2 + 0.25 * pi2; tess.xyz[numVerts + 1][0] = cmd->x + (cos(angle) * cmd->w); tess.xyz[numVerts + 1][1] = cmd->y + (sin(angle) * cmd->h); tess.xyz[numVerts + 1][2] = 0; tess.xyz[numVerts + 1][3] = 1; tess.texCoords[numVerts + 1][0] = cmd->s2; tess.texCoords[numVerts + 1][1] = cmd->t1; angle = cmd->angle * pi2 + 0.50 * pi2; tess.xyz[numVerts + 2][0] = cmd->x + (cos(angle) * cmd->w); tess.xyz[numVerts + 2][1] = cmd->y + (sin(angle) * cmd->h); tess.xyz[numVerts + 2][2] = 0; tess.xyz[numVerts + 2][3] = 1; tess.texCoords[numVerts + 2][0] = cmd->s2; tess.texCoords[numVerts + 2][1] = cmd->t2; angle = cmd->angle * pi2 + 0.75 * pi2; tess.xyz[numVerts + 3][0] = cmd->x + (cos(angle) * cmd->w); tess.xyz[numVerts + 3][1] = cmd->y + (sin(angle) * cmd->h); tess.xyz[numVerts + 3][2] = 0; tess.xyz[numVerts + 3][3] = 1; tess.texCoords[numVerts + 3][0] = cmd->s1; tess.texCoords[numVerts + 3][1] = cmd->t2; return (const void *)(cmd + 1); } // -NERVE - SMF /* ============== RB_StretchPicGradient ============== */ const void *RB_StretchPicGradient(const void *data) { const stretchPicCommand_t *cmd; shader_t *shader; int numVerts, numIndexes; int i; cmd = (const stretchPicCommand_t *)data; if(!backEnd.projection2D) { RB_SetGL2D(); } shader = cmd->shader; if(shader != tess.surfaceShader) { if(tess.numIndexes) { Tess_End(); } backEnd.currentEntity = &backEnd.entity2D; Tess_Begin(Tess_StageIteratorGeneric, NULL, shader, NULL, qfalse, qfalse, -1, 0); } Tess_CheckOverflow(4, 6); numVerts = tess.numVertexes; numIndexes = tess.numIndexes; tess.numVertexes += 4; tess.numIndexes += 6; tess.indexes[numIndexes] = numVerts + 3; tess.indexes[numIndexes + 1] = numVerts + 0; tess.indexes[numIndexes + 2] = numVerts + 2; tess.indexes[numIndexes + 3] = numVerts + 2; tess.indexes[numIndexes + 4] = numVerts + 0; tess.indexes[numIndexes + 5] = numVerts + 1; //*(int *)tess.vertexColors[numVerts].v = *(int *)tess.vertexColors[numVerts + 1].v = *(int *)backEnd.color2D; //*(int *)tess.vertexColors[numVerts + 2].v = *(int *)tess.vertexColors[numVerts + 3].v = *(int *)cmd->gradientColor; Vector4Copy(backEnd.color2D, tess.colors[numVerts + 0]); Vector4Copy(backEnd.color2D, tess.colors[numVerts + 1]); for(i = 0; i < 4; i++) { tess.colors[numVerts + 2][0] = cmd->gradientColor[0] * (1.0f / 255.0f); tess.colors[numVerts + 2][1] = cmd->gradientColor[0] * (1.0f / 255.0f); tess.colors[numVerts + 2][2] = cmd->gradientColor[0] * (1.0f / 255.0f); tess.colors[numVerts + 2][3] = cmd->gradientColor[0] * (1.0f / 255.0f); tess.colors[numVerts + 3][0] = cmd->gradientColor[0] * (1.0f / 255.0f); tess.colors[numVerts + 3][1] = cmd->gradientColor[0] * (1.0f / 255.0f); tess.colors[numVerts + 3][2] = cmd->gradientColor[0] * (1.0f / 255.0f); tess.colors[numVerts + 3][3] = cmd->gradientColor[0] * (1.0f / 255.0f); } tess.xyz[numVerts][0] = cmd->x; tess.xyz[numVerts][1] = cmd->y; tess.xyz[numVerts][2] = 0; tess.xyz[numVerts][3] = 1; tess.texCoords[numVerts][0] = cmd->s1; tess.texCoords[numVerts][1] = cmd->t1; tess.xyz[numVerts + 1][0] = cmd->x + cmd->w; tess.xyz[numVerts + 1][1] = cmd->y; tess.xyz[numVerts + 1][2] = 0; tess.xyz[numVerts + 1][3] = 1; tess.texCoords[numVerts + 1][0] = cmd->s2; tess.texCoords[numVerts + 1][1] = cmd->t1; tess.xyz[numVerts + 2][0] = cmd->x + cmd->w; tess.xyz[numVerts + 2][1] = cmd->y + cmd->h; tess.xyz[numVerts + 2][2] = 0; tess.xyz[numVerts + 2][3] = 1; tess.texCoords[numVerts + 2][0] = cmd->s2; tess.texCoords[numVerts + 2][1] = cmd->t2; tess.xyz[numVerts + 3][0] = cmd->x; tess.xyz[numVerts + 3][1] = cmd->y + cmd->h; tess.xyz[numVerts + 3][2] = 0; tess.xyz[numVerts + 3][3] = 1; tess.texCoords[numVerts + 3][0] = cmd->s1; tess.texCoords[numVerts + 3][1] = cmd->t2; return (const void *)(cmd + 1); } /* ============= RB_DrawView ============= */ const void *RB_DrawView(const void *data) { const drawViewCommand_t *cmd; GLimp_LogComment("--- RB_DrawView ---\n"); // finish any 2D drawing if needed if(tess.numIndexes) { Tess_End(); } cmd = (const drawViewCommand_t *)data; backEnd.refdef = cmd->refdef; backEnd.viewParms = cmd->viewParms; RB_RenderView(); return (const void *)(cmd + 1); } /* ============= RB_DrawBuffer ============= */ const void *RB_DrawBuffer(const void *data) { const drawBufferCommand_t *cmd; GLimp_LogComment("--- RB_DrawBuffer ---\n"); cmd = (const drawBufferCommand_t *)data; GL_DrawBuffer(cmd->buffer); // clear screen for debugging if(r_clear->integer) { // GL_ClearColor(1, 0, 0.5, 1); GL_ClearColor(0, 0, 0, 1); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); } return (const void *)(cmd + 1); } /* =============== RB_ShowImages Draw all the images to the screen, on top of whatever was there. This is used to test for texture thrashing. Also called by RE_EndRegistration =============== */ void RB_ShowImages(void) { int i; image_t *image; float x, y, w, h; vec4_t quadVerts[4]; int start, end; GLimp_LogComment("--- RB_ShowImages ---\n"); if(!backEnd.projection2D) { RB_SetGL2D(); } glClear(GL_COLOR_BUFFER_BIT); glFinish(); gl_genericShader->DisableAlphaTesting(); gl_genericShader->DisablePortalClipping(); gl_genericShader->DisableVertexSkinning(); gl_genericShader->DisableVertexAnimation(); gl_genericShader->DisableDeformVertexes(); gl_genericShader->DisableTCGenEnvironment(); gl_genericShader->BindProgram(); GL_Cull(CT_TWO_SIDED); // set uniforms gl_genericShader->SetUniform_ColorModulate(CGEN_VERTEX, AGEN_VERTEX); gl_genericShader->SetUniform_ColorTextureMatrix(matrixIdentity); GL_SelectTexture(0); start = ri.Milliseconds(); for(i = 0; i < tr.images.currentElements; i++) { image = (image_t *) Com_GrowListElement(&tr.images, i); /* if(image->bits & (IF_RGBA16F | IF_RGBA32F | IF_LA16F | IF_LA32F)) { // don't render float textures using FFP continue; } */ w = glConfig.vidWidth / 20; h = glConfig.vidHeight / 15; x = i % 20 * w; y = i / 20 * h; // show in proportional size in mode 2 if(r_showImages->integer == 2) { w *= image->uploadWidth / 512.0f; h *= image->uploadHeight / 512.0f; } // bind u_ColorMap GL_Bind(image); VectorSet4(quadVerts[0], x, y, 0, 1); VectorSet4(quadVerts[1], x + w, y, 0, 1); VectorSet4(quadVerts[2], x + w, y + h, 0, 1); VectorSet4(quadVerts[3], x, y + h, 0, 1); Tess_InstantQuad(quadVerts); /* glBegin(GL_QUADS); glVertexAttrib4fARB(ATTR_INDEX_TEXCOORD0, 0, 0, 0, 1); glVertexAttrib4fARB(ATTR_INDEX_POSITION, x, y, 0, 1); glVertexAttrib4fARB(ATTR_INDEX_TEXCOORD0, 1, 0, 0, 1); glVertexAttrib4fARB(ATTR_INDEX_POSITION, x + w, y, 0, 1); glVertexAttrib4fARB(ATTR_INDEX_TEXCOORD0, 1, 1, 0, 1); glVertexAttrib4fARB(ATTR_INDEX_POSITION, x + w, y + h, 0, 1); glVertexAttrib4fARB(ATTR_INDEX_TEXCOORD0, 0, 1, 0, 1); glVertexAttrib4fARB(ATTR_INDEX_POSITION, x, y + h, 0, 1); glEnd(); */ } glFinish(); end = ri.Milliseconds(); ri.Printf(PRINT_ALL, "%i msec to draw all images\n", end - start); GL_CheckErrors(); } /* ============= RB_SwapBuffers ============= */ const void *RB_SwapBuffers(const void *data) { const swapBuffersCommand_t *cmd; // finish any 2D drawing if needed if(tess.numIndexes) { Tess_End(); } // texture swapping test if(r_showImages->integer) { RB_ShowImages(); } cmd = (const swapBuffersCommand_t *)data; // we measure overdraw by reading back the stencil buffer and // counting up the number of increments that have happened if(r_measureOverdraw->integer) { int i; long sum = 0; unsigned char *stencilReadback; stencilReadback = (unsigned char *) ri.Malloc(glConfig.vidWidth * glConfig.vidHeight); glReadPixels(0, 0, glConfig.vidWidth, glConfig.vidHeight, GL_STENCIL_INDEX, GL_UNSIGNED_BYTE, stencilReadback); for(i = 0; i < glConfig.vidWidth * glConfig.vidHeight; i++) { sum += stencilReadback[i]; } backEnd.pc.c_overDraw += sum; ri.Free(stencilReadback); } if(!glState.finishCalled) { glFinish(); } GLimp_LogComment("***************** RB_SwapBuffers *****************\n\n\n"); GLimp_EndFrame(); backEnd.projection2D = qfalse; return (const void *)(cmd + 1); } //bani /* ============= RB_RenderToTexture ============= */ const void *RB_RenderToTexture(const void *data) { const renderToTextureCommand_t *cmd; // ri.Printf( PRINT_ALL, "RB_RenderToTexture\n" ); cmd = (const renderToTextureCommand_t *)data; GL_Bind(cmd->image); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP_SGIS, GL_TRUE); glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, cmd->x, cmd->y, cmd->w, cmd->h, 0); // glCopyTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, cmd->x, cmd->y, cmd->w, cmd->h ); return (const void *)(cmd + 1); } //bani /* ============= RB_Finish ============= */ const void *RB_Finish(const void *data) { const renderFinishCommand_t *cmd; // ri.Printf( PRINT_ALL, "RB_Finish\n" ); cmd = (const renderFinishCommand_t *)data; glFinish(); return (const void *)(cmd + 1); } /* ==================== RB_ExecuteRenderCommands This function will be called synchronously if running without smp extensions, or asynchronously by another thread. ==================== */ void RB_ExecuteRenderCommands(const void *data) { int t1, t2; GLimp_LogComment("--- RB_ExecuteRenderCommands ---\n"); t1 = ri.Milliseconds(); if(!r_smp->integer || data == backEndData[0]->commands.cmds) { backEnd.smpFrame = 0; } else { backEnd.smpFrame = 1; } while(1) { switch (*(const int *)data) { case RC_SET_COLOR: data = RB_SetColor(data); break; case RC_STRETCH_PIC: data = RB_StretchPic(data); break; case RC_2DPOLYS: data = RB_Draw2dPolys(data); break; case RC_ROTATED_PIC: data = RB_RotatedPic(data); break; case RC_STRETCH_PIC_GRADIENT: data = RB_StretchPicGradient(data); break; case RC_DRAW_VIEW: data = RB_DrawView(data); break; case RC_DRAW_BUFFER: data = RB_DrawBuffer(data); break; case RC_SWAP_BUFFERS: data = RB_SwapBuffers(data); break; case RC_SCREENSHOT: data = RB_TakeScreenshotCmd(data); break; case RC_VIDEOFRAME: data = RB_TakeVideoFrameCmd(data); break; case RC_RENDERTOTEXTURE: data = RB_RenderToTexture(data); break; case RC_FINISH: data = RB_Finish(data); break; case RC_END_OF_LIST: default: // stop rendering on this thread t2 = ri.Milliseconds(); backEnd.pc.msec = t2 - t1; return; } } } /* ================ RB_RenderThread ================ */ void RB_RenderThread(void) { const void *data; // wait for either a rendering command or a quit command while(1) { // sleep until we have work to do data = GLimp_RendererSleep(); if(!data) { return; // all done, renderer is shutting down } renderThreadActive = qtrue; RB_ExecuteRenderCommands(data); renderThreadActive = qfalse; } }