/* Copyright (C) 1996-2001 Id Software, Inc. Copyright (C) 2002-2009 John Fitzgibbons and others Copyright (C) 2010-2014 QuakeSpasm developers This program 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. This program 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 this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ // r_main.c #include "quakedef.h" #include "glquake.h" vec3_t modelorg, r_entorigin; static entity_t r_worldentity; //so we can make sure currententity is valid entity_t *currententity; int r_visframecount; // bumped when going to a new PVS int r_framecount; // used for dlight push checking mplane_t frustum[4]; //johnfitz -- rendering statistics int rs_brushpolys, rs_aliaspolys, rs_skypolys; int rs_dynamiclightmaps, rs_brushpasses, rs_aliaspasses, rs_skypasses; // // view origin // vec3_t vup; vec3_t vpn; vec3_t vright; vec3_t r_origin; float r_fovx, r_fovy; //johnfitz -- rendering fov may be different becuase of r_waterwarp and r_stereo // // screen size info // refdef_t r_refdef; mleaf_t *r_viewleaf, *r_oldviewleaf; int d_lightstylevalue[MAX_LIGHTSTYLES]; // 8.8 fraction of base light value cvar_t r_norefresh = {"r_norefresh","0",CVAR_NONE}; cvar_t r_drawentities = {"r_drawentities","1",CVAR_NONE}; cvar_t r_drawviewmodel = {"r_drawviewmodel","1",CVAR_NONE}; cvar_t r_speeds = {"r_speeds","0",CVAR_NONE}; cvar_t r_pos = {"r_pos","0",CVAR_NONE}; cvar_t r_fullbright = {"r_fullbright","0",CVAR_NONE}; cvar_t r_lightmap = {"r_lightmap","0",CVAR_NONE}; cvar_t r_shadows = {"r_shadows","0",CVAR_ARCHIVE}; cvar_t r_wateralpha = {"r_wateralpha","1",CVAR_ARCHIVE}; cvar_t r_dynamic = {"r_dynamic","1",CVAR_ARCHIVE}; cvar_t r_novis = {"r_novis","0",CVAR_ARCHIVE}; cvar_t gl_finish = {"gl_finish","0",CVAR_NONE}; cvar_t gl_clear = {"gl_clear","1",CVAR_NONE}; cvar_t gl_cull = {"gl_cull","1",CVAR_NONE}; cvar_t gl_smoothmodels = {"gl_smoothmodels","1",CVAR_NONE}; cvar_t gl_affinemodels = {"gl_affinemodels","0",CVAR_NONE}; cvar_t gl_polyblend = {"gl_polyblend","1",CVAR_NONE}; cvar_t gl_flashblend = {"gl_flashblend","0",CVAR_ARCHIVE}; cvar_t gl_playermip = {"gl_playermip","0",CVAR_NONE}; cvar_t gl_nocolors = {"gl_nocolors","0",CVAR_NONE}; //johnfitz -- new cvars cvar_t r_stereo = {"r_stereo","0",CVAR_NONE}; cvar_t r_stereodepth = {"r_stereodepth","128",CVAR_NONE}; cvar_t r_clearcolor = {"r_clearcolor","2",CVAR_ARCHIVE}; cvar_t r_drawflat = {"r_drawflat","0",CVAR_NONE}; cvar_t r_flatlightstyles = {"r_flatlightstyles", "0", CVAR_NONE}; cvar_t gl_fullbrights = {"gl_fullbrights", "1", CVAR_ARCHIVE}; cvar_t gl_farclip = {"gl_farclip", "16384", CVAR_ARCHIVE}; cvar_t gl_overbright = {"gl_overbright", "1", CVAR_ARCHIVE}; cvar_t gl_overbright_models = {"gl_overbright_models", "1", CVAR_ARCHIVE}; cvar_t r_oldskyleaf = {"r_oldskyleaf", "0", CVAR_NONE}; cvar_t r_drawworld = {"r_drawworld", "1", CVAR_NONE}; cvar_t r_showtris = {"r_showtris", "0", CVAR_NONE}; cvar_t r_showbboxes = {"r_showbboxes", "0", CVAR_NONE}; cvar_t r_lerpmodels = {"r_lerpmodels", "1", CVAR_ARCHIVE}; cvar_t r_lerpmove = {"r_lerpmove", "1", CVAR_ARCHIVE}; cvar_t r_nolerp_list = {"r_nolerp_list", "progs/flame.mdl,progs/flame2.mdl,progs/braztall.mdl,progs/brazshrt.mdl,progs/longtrch.mdl,progs/flame_pyre.mdl,progs/v_saw.mdl,progs/v_xfist.mdl,progs/h2stuff/newfire.mdl", CVAR_NONE}; cvar_t r_noshadow_list = {"r_noshadow_list", "progs/flame2.mdl,progs/flame.mdl,progs/bolt1.mdl,progs/bolt2.mdl,progs/bolt3.mdl,progs/laser.mdl", CVAR_NONE}; extern cvar_t r_vfog; //johnfitz cvar_t gl_zfix = {"gl_zfix", "0", CVAR_NONE}; // QuakeSpasm z-fighting fix cvar_t r_lavaalpha = {"r_lavaalpha","0",CVAR_NONE}; cvar_t r_telealpha = {"r_telealpha","0",CVAR_NONE}; cvar_t r_slimealpha = {"r_slimealpha","0",CVAR_NONE}; float map_wateralpha, map_lavaalpha, map_telealpha, map_slimealpha; float map_fallbackalpha; qboolean r_drawflat_cheatsafe, r_fullbright_cheatsafe, r_lightmap_cheatsafe, r_drawworld_cheatsafe; //johnfitz cvar_t r_scale = {"r_scale", "1", CVAR_ARCHIVE}; //============================================================================== // // GLSL GAMMA CORRECTION // //============================================================================== static GLuint r_gamma_texture; static GLuint r_gamma_program; static int r_gamma_texture_width, r_gamma_texture_height; // uniforms used in gamma shader static GLuint gammaLoc; static GLuint contrastLoc; static GLuint textureLoc; /* ============= GLSLGamma_DeleteTexture ============= */ void GLSLGamma_DeleteTexture (void) { glDeleteTextures (1, &r_gamma_texture); r_gamma_texture = 0; r_gamma_program = 0; // deleted in R_DeleteShaders } /* ============= GLSLGamma_CreateShaders ============= */ static void GLSLGamma_CreateShaders (void) { const GLchar *vertSource = \ "#version 110\n" "\n" "void main(void) {\n" " gl_Position = vec4(gl_Vertex.xy, 0.0, 1.0);\n" " gl_TexCoord[0] = gl_MultiTexCoord0;\n" "}\n"; const GLchar *fragSource = \ "#version 110\n" "\n" "uniform sampler2D GammaTexture;\n" "uniform float GammaValue;\n" "uniform float ContrastValue;\n" "\n" "void main(void) {\n" " vec4 frag = texture2D(GammaTexture, gl_TexCoord[0].xy);\n" " frag.rgb = frag.rgb * ContrastValue;\n" " gl_FragColor = vec4(pow(frag.rgb, vec3(GammaValue)), 1.0);\n" "}\n"; if (!gl_glsl_gamma_able) return; r_gamma_program = GL_CreateProgram (vertSource, fragSource, 0, NULL); // get uniform locations gammaLoc = GL_GetUniformLocation (&r_gamma_program, "GammaValue"); contrastLoc = GL_GetUniformLocation (&r_gamma_program, "ContrastValue"); textureLoc = GL_GetUniformLocation (&r_gamma_program, "GammaTexture"); } /* ============= GLSLGamma_GammaCorrect ============= */ void GLSLGamma_GammaCorrect (void) { int tw=glwidth,th=glheight; float smax, tmax; if (!gl_glsl_gamma_able) return; if (vid_gamma.value == 1 && vid_contrast.value == 1) return; // create render-to-texture texture if needed if (!r_gamma_texture) { glGenTextures (1, &r_gamma_texture); r_gamma_texture_width = 0; r_gamma_texture_height = 0; } GL_DisableMultitexture(); glBindTexture (GL_TEXTURE_2D, r_gamma_texture); if (!gl_texture_NPOT) { tw = TexMgr_Pad(tw); th = TexMgr_Pad(th); } if (r_gamma_texture_width != tw || r_gamma_texture_height != th) { r_gamma_texture_width = tw; r_gamma_texture_height = th; glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA8, r_gamma_texture_width, r_gamma_texture_height, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, NULL); glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); } // create shader if needed if (!r_gamma_program) { GLSLGamma_CreateShaders (); if (!r_gamma_program) { Sys_Error("GLSLGamma_CreateShaders failed"); } } // copy the framebuffer to the texture glCopyTexSubImage2D (GL_TEXTURE_2D, 0, 0, 0, glx, gly, glwidth, glheight); // draw the texture back to the framebuffer with a fragment shader GL_UseProgramFunc (r_gamma_program); GL_Uniform1fFunc (gammaLoc, vid_gamma.value); GL_Uniform1fFunc (contrastLoc, q_min(2.0f, q_max(1.0f, vid_contrast.value))); GL_Uniform1iFunc (textureLoc, 0); // use texture unit 0 glDisable (GL_ALPHA_TEST); glDisable (GL_DEPTH_TEST); glViewport (glx, gly, glwidth, glheight); smax = glwidth/(float)r_gamma_texture_width; tmax = glheight/(float)r_gamma_texture_height; glBegin (GL_QUADS); glTexCoord2f (0, 0); glVertex2f (-1, -1); glTexCoord2f (smax, 0); glVertex2f (1, -1); glTexCoord2f (smax, tmax); glVertex2f (1, 1); glTexCoord2f (0, tmax); glVertex2f (-1, 1); glEnd (); GL_UseProgramFunc (0); // clear cached binding GL_ClearBindings (); } /* ================= R_CullBox -- johnfitz -- replaced with new function from lordhavoc Returns true if the box is completely outside the frustum ================= */ qboolean R_CullBox (vec3_t emins, vec3_t emaxs) { int i; mplane_t *p; byte signbits; float vec[3]; for (i = 0;i < 4;i++) { p = frustum + i; signbits = p->signbits; vec[0] = ((signbits % 2)<1) ? emaxs[0] : emins[0]; vec[1] = ((signbits % 4)<2) ? emaxs[1] : emins[1]; vec[2] = ((signbits % 8)<4) ? emaxs[2] : emins[2]; if (p->normal[0]*vec[0] + p->normal[1]*vec[1] + p->normal[2]*vec[2] < p->dist) return true; } return false; } /* =============== R_CullModelForEntity -- johnfitz -- uses correct bounds based on rotation =============== */ qboolean R_CullModelForEntity (entity_t *e) { vec3_t mins, maxs; vec_t scalefactor, *minbounds, *maxbounds; if (e->angles[0] || e->angles[2]) //pitch or roll { minbounds = e->model->rmins; maxbounds = e->model->rmaxs; } else if (e->angles[1]) //yaw { minbounds = e->model->ymins; maxbounds = e->model->ymaxs; } else //no rotation { minbounds = e->model->mins; maxbounds = e->model->maxs; } scalefactor = ENTSCALE_DECODE(e->netstate.scale); if (scalefactor != 1.0f) { VectorMA (e->origin, scalefactor, minbounds, mins); VectorMA (e->origin, scalefactor, maxbounds, maxs); } else { VectorAdd (e->origin, minbounds, mins); VectorAdd (e->origin, maxbounds, maxs); } return R_CullBox (mins, maxs); } /* =============== R_RotateForEntity -- johnfitz -- modified to take origin and angles instead of pointer to entity =============== */ void R_RotateForEntity (vec3_t origin, vec3_t angles, unsigned char scale) { glTranslatef (origin[0], origin[1], origin[2]); glRotatef (angles[1], 0, 0, 1); glRotatef (-angles[0], 0, 1, 0); glRotatef (angles[2], 1, 0, 0); if (scale != ENTSCALE_DEFAULT) { float scalefactor = ENTSCALE_DECODE(scale); glScalef(scalefactor, scalefactor, scalefactor); } } /* ============= GL_PolygonOffset -- johnfitz negative offset moves polygon closer to camera ============= */ void GL_PolygonOffset (int offset) { if (offset > 0) { glEnable (GL_POLYGON_OFFSET_FILL); glEnable (GL_POLYGON_OFFSET_LINE); glPolygonOffset(1, offset); } else if (offset < 0) { glEnable (GL_POLYGON_OFFSET_FILL); glEnable (GL_POLYGON_OFFSET_LINE); glPolygonOffset(-1, offset); } else { glDisable (GL_POLYGON_OFFSET_FILL); glDisable (GL_POLYGON_OFFSET_LINE); } } //============================================================================== // // SETUP FRAME // //============================================================================== int SignbitsForPlane (mplane_t *out) { int bits, j; // for fast box on planeside test bits = 0; for (j=0 ; j<3 ; j++) { if (out->normal[j] < 0) bits |= 1<contents; //spike -- FTE_ENT_SKIN_CONTENTS -- added this loop for moving water volumes, to avoid v_cshift etc hacks. for (i = 0; i < cl.num_entities && viewcontents == CONTENTS_EMPTY; i++) { mleaf_t *subleaf; vec3_t relpos; if (cl.entities[i].model && cl.entities[i].model->type==mod_brush) { VectorSubtract(r_origin, cl.entities[i].origin, relpos); if (cl.entities[i].angles[0] || cl.entities[i].angles[1] || cl.entities[i].angles[2]) { //rotate the point, just in case. vec3_t axis[3], t; AngleVectors(cl.entities[i].angles, axis[0], axis[1], axis[2]); VectorCopy(relpos, t); relpos[0] = DotProduct(t, axis[0]); relpos[0] = -DotProduct(t, axis[1]); relpos[0] = DotProduct(t, axis[2]); } subleaf = Mod_PointInLeaf (relpos, cl.entities[i].model); if ((char)cl.entities[i].skinnum < 0) viewcontents = ((subleaf->contents == CONTENTS_SOLID)?(char)cl.entities[i].skinnum:CONTENTS_EMPTY); else viewcontents = subleaf->contents; } } } else viewcontents = CONTENTS_EMPTY; V_SetContentsColor (viewcontents); V_CalcBlend (); //johnfitz -- calculate r_fovx and r_fovy here r_fovx = r_refdef.fov_x; r_fovy = r_refdef.fov_y; if (r_waterwarp.value) { if (viewcontents == CONTENTS_WATER || viewcontents == CONTENTS_SLIME || viewcontents == CONTENTS_LAVA) { //variance is a percentage of width, where width = 2 * tan(fov / 2) otherwise the effect is too dramatic at high FOV and too subtle at low FOV. what a mess! r_fovx = atan(tan(DEG2RAD(r_refdef.fov_x) / 2) * (0.97 + sin(cl.time * 1.5) * 0.03)) * 2 / M_PI_DIV_180; r_fovy = atan(tan(DEG2RAD(r_refdef.fov_y) / 2) * (1.03 - sin(cl.time * 1.5) * 0.03)) * 2 / M_PI_DIV_180; } } //johnfitz R_SetFrustum (r_fovx, r_fovy); //johnfitz -- use r_fov* vars if (r_refdef.drawworld) { currententity = &r_worldentity; R_MarkSurfaces (); //johnfitz -- create texture chains from PVS currententity = NULL; } R_Clear (); //johnfitz -- cheat-protect some draw modes r_drawflat_cheatsafe = r_fullbright_cheatsafe = r_lightmap_cheatsafe = false; r_drawworld_cheatsafe = r_refdef.drawworld; if (cl.maxclients == 1 && r_refdef.drawworld) { if (!r_drawworld.value) r_drawworld_cheatsafe = false; if (r_drawflat.value) r_drawflat_cheatsafe = true; else if (r_fullbright.value || !cl.worldmodel->lightdata) r_fullbright_cheatsafe = true; else if (r_lightmap.value) r_lightmap_cheatsafe = true; } //johnfitz } //============================================================================== // // RENDER VIEW // //============================================================================== /* ============= R_DrawEntitiesOnList ============= */ void R_DrawEntitiesOnList (qboolean alphapass) //johnfitz -- added parameter { int i; if (!r_drawentities.value) return; //johnfitz -- sprites are not a special case for (i=0 ; ialpha) < 1 && !alphapass) || (ENTALPHA_DECODE(currententity->alpha) == 1 && alphapass)) continue; //johnfitz -- chasecam if (currententity == &cl.entities[cl.viewentity]) currententity->angles[0] *= 0.3; //johnfitz //spike -- this would be more efficient elsewhere, but its more correct here. if (currententity->eflags & EFLAGS_EXTERIORMODEL) continue; switch (currententity->model->type) { case mod_alias: R_DrawAliasModel (currententity); break; case mod_brush: R_DrawBrushModel (currententity); break; case mod_sprite: R_DrawSpriteModel (currententity); break; case mod_ext_invalid: //nothing. could draw a blob instead. break; } } } /* ============= R_DrawViewModel -- johnfitz -- gutted ============= */ void R_DrawViewModel (void) { if (!r_drawviewmodel.value || !r_drawentities.value || chase_active.value || skyroom_drawing/*silly depthrange*/) return; if (cl.items & IT_INVISIBILITY || cl.stats[STAT_HEALTH] <= 0) return; currententity = &cl.viewent; if (!currententity->model) return; //johnfitz -- this fixes a crash if (currententity->model->type != mod_alias) return; //johnfitz // hack the depth range to prevent view model from poking into walls glDepthRange (0, 0.3); R_DrawAliasModel (currententity); glDepthRange (0, 1); } /* ================ R_EmitWirePoint -- johnfitz -- draws a wireframe cross shape for point entities ================ */ void R_EmitWirePoint (vec3_t origin) { const int size = 8; glBegin (GL_LINES); glVertex3f (origin[0]-size, origin[1], origin[2]); glVertex3f (origin[0]+size, origin[1], origin[2]); glVertex3f (origin[0], origin[1]-size, origin[2]); glVertex3f (origin[0], origin[1]+size, origin[2]); glVertex3f (origin[0], origin[1], origin[2]-size); glVertex3f (origin[0], origin[1], origin[2]+size); glEnd (); } /* ================ R_EmitWireBox -- johnfitz -- draws one axis aligned bounding box ================ */ void R_EmitWireBox (vec3_t mins, vec3_t maxs) { glBegin (GL_QUAD_STRIP); glVertex3f (mins[0], mins[1], mins[2]); glVertex3f (mins[0], mins[1], maxs[2]); glVertex3f (maxs[0], mins[1], mins[2]); glVertex3f (maxs[0], mins[1], maxs[2]); glVertex3f (maxs[0], maxs[1], mins[2]); glVertex3f (maxs[0], maxs[1], maxs[2]); glVertex3f (mins[0], maxs[1], mins[2]); glVertex3f (mins[0], maxs[1], maxs[2]); glVertex3f (mins[0], mins[1], mins[2]); glVertex3f (mins[0], mins[1], maxs[2]); glEnd (); } /* ================ R_ShowBoundingBoxes -- johnfitz draw bounding boxes -- the server-side boxes, not the renderer cullboxes ================ */ void R_ShowBoundingBoxes (void) { extern edict_t *sv_player; vec3_t mins,maxs; edict_t *ed; int i; qcvm_t *oldvm; //in case we ever draw a scene from within csqc. if (!r_showbboxes.value || cl.maxclients > 1 || !r_drawentities.value || !sv.active) return; glDisable (GL_DEPTH_TEST); glPolygonMode (GL_FRONT_AND_BACK, GL_LINE); GL_PolygonOffset (OFFSET_SHOWTRIS); glDisable (GL_TEXTURE_2D); glDisable (GL_CULL_FACE); glColor3f (1,1,1); oldvm = qcvm; PR_SwitchQCVM(NULL); PR_SwitchQCVM(&sv.qcvm); for (i=1, ed=NEXT_EDICT(qcvm->edicts) ; inum_edicts ; i++, ed=NEXT_EDICT(ed)) { if (ed == sv_player || ed->free) continue; //don't draw player's own bbox or freed edicts // if (r_showbboxes.value != 2) // if (!SV_VisibleToClient (sv_player, ed, sv.worldmodel)) // continue; //don't draw if not in pvs if (ed->v.mins[0] == ed->v.maxs[0] && ed->v.mins[1] == ed->v.maxs[1] && ed->v.mins[2] == ed->v.maxs[2]) { //point entity R_EmitWirePoint (ed->v.origin); } else { //box entity if ((ed->v.solid == SOLID_BSP || ed->v.solid == SOLID_EXT_BSPTRIGGER) && (ed->v.angles[0]||ed->v.angles[1]||ed->v.angles[2]) && pr_checkextension.value) R_EmitWireBox (ed->v.absmin, ed->v.absmax); else { VectorAdd (ed->v.mins, ed->v.origin, mins); VectorAdd (ed->v.maxs, ed->v.origin, maxs); R_EmitWireBox (mins, maxs); } } } PR_SwitchQCVM(NULL); PR_SwitchQCVM(oldvm); glColor3f (1,1,1); glEnable (GL_TEXTURE_2D); glEnable (GL_CULL_FACE); glPolygonMode (GL_FRONT_AND_BACK, GL_FILL); GL_PolygonOffset (OFFSET_NONE); glEnable (GL_DEPTH_TEST); Sbar_Changed (); //so we don't get dots collecting on the statusbar } /* ================ R_ShowTris -- johnfitz ================ */ void R_ShowTris (void) { extern cvar_t r_particles; int i; if (r_showtris.value < 1 || r_showtris.value > 2 || cl.maxclients > 1) return; if (r_showtris.value == 1) glDisable (GL_DEPTH_TEST); glPolygonMode (GL_FRONT_AND_BACK, GL_LINE); GL_PolygonOffset (OFFSET_SHOWTRIS); glDisable (GL_TEXTURE_2D); glColor3f (1,1,1); // glEnable (GL_BLEND); // glBlendFunc (GL_ONE, GL_ONE); if (r_drawworld.value) { R_DrawWorld_ShowTris (); } if (r_drawentities.value) { for (i=0 ; iangles[0] *= 0.3; switch (currententity->model->type) { case mod_brush: R_DrawBrushModel_ShowTris (currententity); break; case mod_alias: R_DrawAliasModel_ShowTris (currententity); break; case mod_sprite: R_DrawSpriteModel (currententity); break; default: break; } } // viewmodel currententity = &cl.viewent; if (r_drawviewmodel.value && !chase_active.value && cl.stats[STAT_HEALTH] > 0 && !(cl.items & IT_INVISIBILITY) && currententity->model && currententity->model->type == mod_alias) { glDepthRange (0, 0.3); R_DrawAliasModel_ShowTris (currententity); glDepthRange (0, 1); } } if (r_particles.value) { R_DrawParticles_ShowTris (); } // glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // glDisable (GL_BLEND); glColor3f (1,1,1); glEnable (GL_TEXTURE_2D); glPolygonMode (GL_FRONT_AND_BACK, GL_FILL); GL_PolygonOffset (OFFSET_NONE); if (r_showtris.value == 1) glEnable (GL_DEPTH_TEST); Sbar_Changed (); //so we don't get dots collecting on the statusbar } /* ================ R_DrawShadows ================ */ void R_DrawShadows (void) { int i; if (!r_shadows.value || !r_drawentities.value || r_drawflat_cheatsafe || r_lightmap_cheatsafe) return; // Use stencil buffer to prevent self-intersecting shadows, from Baker (MarkV) if (gl_stencilbits) { glClear(GL_STENCIL_BUFFER_BIT); glStencilFunc(GL_EQUAL, 0, ~0); glStencilOp(GL_KEEP, GL_KEEP, GL_INCR); glEnable(GL_STENCIL_TEST); } for (i=0 ; imodel->type != mod_alias) continue; if (currententity == &cl.viewent) return; GL_DrawAliasShadow (currententity); } if (gl_stencilbits) { glDisable(GL_STENCIL_TEST); } } /* ================ R_RenderScene ================ */ void R_RenderScene (void) { currententity = &r_worldentity; R_SetupScene (); //johnfitz -- this does everything that should be done once per call to RenderScene Fog_EnableGFog (); //johnfitz Sky_DrawSky (); //johnfitz R_DrawWorld (); currententity = NULL; S_ExtraUpdate (); // don't let sound get messed up if going slow if (r_refdef.drawworld) R_DrawShadows (); //johnfitz -- render entity shadows R_DrawEntitiesOnList (false); //johnfitz -- false means this is the pass for nonalpha entities R_DrawWorld_Water (); //johnfitz -- drawn here since they might have transparency R_DrawEntitiesOnList (true); //johnfitz -- true means this is the pass for alpha entities R_RenderDlights (); //triangle fan dlights -- johnfitz -- moved after water if (r_refdef.drawworld) { R_DrawParticles (); #ifdef PSET_SCRIPT PScript_DrawParticles(); #endif } Fog_DisableGFog (); //johnfitz R_DrawViewModel (); //johnfitz -- moved here from R_RenderView R_ShowTris (); //johnfitz R_ShowBoundingBoxes (); //johnfitz } static GLuint r_scaleview_texture; static int r_scaleview_texture_width, r_scaleview_texture_height; /* ============= R_ScaleView_DeleteTexture ============= */ void R_ScaleView_DeleteTexture (void) { glDeleteTextures (1, &r_scaleview_texture); r_scaleview_texture = 0; } /* ================ R_ScaleView The r_scale cvar allows rendering the 3D view at 1/2, 1/3, or 1/4 resolution. This function scales the reduced resolution 3D view back up to fill r_refdef.vrect. This is for emulating a low-resolution pixellated look, or possibly as a perforance boost on slow graphics cards. ================ */ void R_ScaleView (void) { float smax, tmax; int scale; int srcx, srcy, srcw, srch; // copied from R_SetupGL() scale = CLAMP(1, (int)r_scale.value, 4); srcx = glx + r_refdef.vrect.x; srcy = gly + glheight - r_refdef.vrect.y - r_refdef.vrect.height; srcw = r_refdef.vrect.width / scale; srch = r_refdef.vrect.height / scale; if (scale == 1 || !r_refdef.drawworld) return; // make sure texture unit 0 is selected GL_DisableMultitexture (); // create (if needed) and bind the render-to-texture texture if (!r_scaleview_texture) { glGenTextures (1, &r_scaleview_texture); r_scaleview_texture_width = 0; r_scaleview_texture_height = 0; } glBindTexture (GL_TEXTURE_2D, r_scaleview_texture); // resize render-to-texture texture if needed if (r_scaleview_texture_width < srcw || r_scaleview_texture_height < srch) { r_scaleview_texture_width = srcw; r_scaleview_texture_height = srch; if (!gl_texture_NPOT) { r_scaleview_texture_width = TexMgr_Pad(r_scaleview_texture_width); r_scaleview_texture_height = TexMgr_Pad(r_scaleview_texture_height); } glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA, r_scaleview_texture_width, r_scaleview_texture_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); } // copy the framebuffer to the texture glBindTexture (GL_TEXTURE_2D, r_scaleview_texture); glCopyTexSubImage2D (GL_TEXTURE_2D, 0, 0, 0, srcx, srcy, srcw, srch); // draw the texture back to the framebuffer glDisable (GL_ALPHA_TEST); glDisable (GL_DEPTH_TEST); glDisable (GL_CULL_FACE); glDisable (GL_BLEND); glViewport (srcx, srcy, r_refdef.vrect.width, r_refdef.vrect.height); glMatrixMode(GL_PROJECTION); glLoadIdentity (); glMatrixMode(GL_MODELVIEW); glLoadIdentity (); // correction factor if we lack NPOT textures, normally these are 1.0f smax = srcw/(float)r_scaleview_texture_width; tmax = srch/(float)r_scaleview_texture_height; glBegin (GL_QUADS); glTexCoord2f (0, 0); glVertex2f (-1, -1); glTexCoord2f (smax, 0); glVertex2f (1, -1); glTexCoord2f (smax, tmax); glVertex2f (1, 1); glTexCoord2f (0, tmax); glVertex2f (-1, 1); glEnd (); // clear cached binding GL_ClearBindings (); } static qboolean R_SkyroomWasVisible(void) { qmodel_t *model = cl.worldmodel; texture_t *t; size_t i; extern cvar_t r_fastsky; if (!skyroom_enabled || r_fastsky.value) return false; for (i=0 ; inumtextures ; i++) { t = model->textures[i]; if (t && t->texturechains[chain_world] && t->texturechains[chain_world]->flags & SURF_DRAWSKY) return true; } return false; } /* ================ R_RenderView ================ */ void R_RenderView (void) { static qboolean skyroom_visible; double time1, time2; if (r_norefresh.value) return; if (r_refdef.drawworld && !cl.worldmodel) Sys_Error ("R_RenderView: NULL worldmodel"); time1 = 0; /* avoid compiler warning */ if (r_speeds.value) { glFinish (); time1 = Sys_DoubleTime (); //johnfitz -- rendering statistics rs_brushpolys = rs_aliaspolys = rs_skypolys = rs_dynamiclightmaps = rs_aliaspasses = rs_skypasses = rs_brushpasses = 0; } else if (gl_finish.value) glFinish (); if (lightmaps_latecached) { GL_BuildLightmaps (); GL_BuildBModelVertexBuffer (); lightmaps_latecached=false; } //Spike -- quickly draw the world from the skyroom camera's point of view. skyroom_drawn = false; if (r_refdef.drawworld && skyroom_enabled && skyroom_visible) { vec3_t vieworg; vec3_t viewang; VectorCopy(r_refdef.vieworg, vieworg); VectorCopy(r_refdef.viewangles, viewang); VectorMA(skyroom_origin, skyroom_origin[3],vieworg, r_refdef.vieworg); //allow a little paralax if (skyroom_orientation[3]) { vec3_t axis[3]; float ang = skyroom_orientation[3] * cl.time; if (!skyroom_orientation[0]&&!skyroom_orientation[1]&&!skyroom_orientation[2]) { skyroom_orientation[0] = 0; skyroom_orientation[1] = 0; skyroom_orientation[2] = 1; } VectorNormalize(skyroom_orientation); RotatePointAroundVector(axis[0], skyroom_orientation, vpn, ang); RotatePointAroundVector(axis[1], skyroom_orientation, vright, ang); RotatePointAroundVector(axis[2], skyroom_orientation, vup, ang); VectorAngles(axis[0], axis[2], r_refdef.viewangles); } skyroom_drawing = true; R_SetupView (); //note: sky boxes are generally considered an 'infinite' distance away such that you'd not see paralax. //that's my excuse for not handling r_stereo here, and I'm sticking to it. R_RenderScene (); VectorCopy(vieworg, r_refdef.vieworg); VectorCopy(viewang, r_refdef.viewangles); skyroom_drawn = true; //disable glClear(GL_COLOR_BUFFER_BIT) } skyroom_drawing = false; //skyroom end R_SetupView (); //johnfitz -- this does everything that should be done once per frame //johnfitz -- stereo rendering -- full of hacky goodness if (r_stereo.value) { float eyesep = CLAMP(-8.0f, r_stereo.value, 8.0f); float fdepth = CLAMP(32.0f, r_stereodepth.value, 1024.0f); AngleVectors (r_refdef.viewangles, vpn, vright, vup); //render left eye (red) glColorMask(1, 0, 0, 1); VectorMA (r_refdef.vieworg, -0.5f * eyesep, vright, r_refdef.vieworg); frustum_skew = 0.5 * eyesep * NEARCLIP / fdepth; srand((int) (cl.time * 1000)); //sync random stuff between eyes R_RenderScene (); //render right eye (cyan) glClear (GL_DEPTH_BUFFER_BIT); glColorMask(0, 1, 1, 1); VectorMA (r_refdef.vieworg, 1.0f * eyesep, vright, r_refdef.vieworg); frustum_skew = -frustum_skew; srand((int) (cl.time * 1000)); //sync random stuff between eyes R_RenderScene (); //restore glColorMask(1, 1, 1, 1); VectorMA (r_refdef.vieworg, -0.5f * eyesep, vright, r_refdef.vieworg); frustum_skew = 0.0f; } else { R_RenderScene (); } //johnfitz //Spike: flag whether the skyroom was actually visible, so we don't needlessly draw it when its not (1 frame's lag, hopefully not too noticable) if (r_refdef.drawworld) { if (r_viewleaf->contents == CONTENTS_SOLID || r_drawflat_cheatsafe || r_lightmap_cheatsafe) skyroom_visible = false; //don't do skyrooms when the view is in the void, for framerate reasons while debugging. else skyroom_visible = R_SkyroomWasVisible(); skyroom_drawn = false; } //skyroom end R_ScaleView (); //johnfitz -- modified r_speeds output time2 = Sys_DoubleTime (); if (r_pos.value) Con_Printf ("x %i y %i z %i (pitch %i yaw %i roll %i)\n", (int)cl.entities[cl.viewentity].origin[0], (int)cl.entities[cl.viewentity].origin[1], (int)cl.entities[cl.viewentity].origin[2], (int)cl.viewangles[PITCH], (int)cl.viewangles[YAW], (int)cl.viewangles[ROLL]); else if (r_speeds.value == 2) Con_Printf ("%3i ms %4i/%4i wpoly %4i/%4i epoly %3i lmap %4i/%4i sky %1.1f mtex\n", (int)((time2-time1)*1000), rs_brushpolys, rs_brushpasses, rs_aliaspolys, rs_aliaspasses, rs_dynamiclightmaps, rs_skypolys, rs_skypasses, TexMgr_FrameUsage ()); else if (r_speeds.value) Con_Printf ("%3i ms %4i wpoly %4i epoly %3i lmap\n", (int)((time2-time1)*1000), rs_brushpolys, rs_aliaspolys, rs_dynamiclightmaps); //johnfitz }