Main Page | Class Hierarchy | Alphabetical List | Data Structures | Directories | File List | Data Fields | Globals

tr_shade.c

Go to the documentation of this file.
00001 /*
00002 ===========================================================================
00003 Copyright (C) 1999-2005 Id Software, Inc.
00004 
00005 This file is part of Quake III Arena source code.
00006 
00007 Quake III Arena source code is free software; you can redistribute it
00008 and/or modify it under the terms of the GNU General Public License as
00009 published by the Free Software Foundation; either version 2 of the License,
00010 or (at your option) any later version.
00011 
00012 Quake III Arena source code is distributed in the hope that it will be
00013 useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
00014 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00015 GNU General Public License for more details.
00016 
00017 You should have received a copy of the GNU General Public License
00018 along with Foobar; if not, write to the Free Software
00019 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
00020 ===========================================================================
00021 */
00022 // tr_shade.c
00023 
00024 #include "tr_local.h"
00025 
00026 /*
00027 
00028   THIS ENTIRE FILE IS BACK END
00029 
00030   This file deals with applying shaders to surface data in the tess struct.
00031 */
00032 
00033 /*
00034 ================
00035 R_ArrayElementDiscrete
00036 
00037 This is just for OpenGL conformance testing, it should never be the fastest
00038 ================
00039 */
00040 static void APIENTRY R_ArrayElementDiscrete( GLint index ) {
00041     qglColor4ubv( tess.svars.colors[ index ] );
00042     if ( glState.currenttmu ) {
00043         qglMultiTexCoord2fARB( 0, tess.svars.texcoords[ 0 ][ index ][0], tess.svars.texcoords[ 0 ][ index ][1] );
00044         qglMultiTexCoord2fARB( 1, tess.svars.texcoords[ 1 ][ index ][0], tess.svars.texcoords[ 1 ][ index ][1] );
00045     } else {
00046         qglTexCoord2fv( tess.svars.texcoords[ 0 ][ index ] );
00047     }
00048     qglVertex3fv( tess.xyz[ index ] );
00049 }
00050 
00051 /*
00052 ===================
00053 R_DrawStripElements
00054 
00055 ===================
00056 */
00057 static int      c_vertexes;     // for seeing how long our average strips are
00058 static int      c_begins;
00059 static void R_DrawStripElements( int numIndexes, const glIndex_t *indexes, void ( APIENTRY *element )(GLint) ) {
00060     int i;
00061     int last[3] = { -1, -1, -1 };
00062     qboolean even;
00063 
00064     c_begins++;
00065 
00066     if ( numIndexes <= 0 ) {
00067         return;
00068     }
00069 
00070     qglBegin( GL_TRIANGLE_STRIP );
00071 
00072     // prime the strip
00073     element( indexes[0] );
00074     element( indexes[1] );
00075     element( indexes[2] );
00076     c_vertexes += 3;
00077 
00078     last[0] = indexes[0];
00079     last[1] = indexes[1];
00080     last[2] = indexes[2];
00081 
00082     even = qfalse;
00083 
00084     for ( i = 3; i < numIndexes; i += 3 )
00085     {
00086         // odd numbered triangle in potential strip
00087         if ( !even )
00088         {
00089             // check previous triangle to see if we're continuing a strip
00090             if ( ( indexes[i+0] == last[2] ) && ( indexes[i+1] == last[1] ) )
00091             {
00092                 element( indexes[i+2] );
00093                 c_vertexes++;
00094                 assert( indexes[i+2] < tess.numVertexes );
00095                 even = qtrue;
00096             }
00097             // otherwise we're done with this strip so finish it and start
00098             // a new one
00099             else
00100             {
00101                 qglEnd();
00102 
00103                 qglBegin( GL_TRIANGLE_STRIP );
00104                 c_begins++;
00105 
00106                 element( indexes[i+0] );
00107                 element( indexes[i+1] );
00108                 element( indexes[i+2] );
00109 
00110                 c_vertexes += 3;
00111 
00112                 even = qfalse;
00113             }
00114         }
00115         else
00116         {
00117             // check previous triangle to see if we're continuing a strip
00118             if ( ( last[2] == indexes[i+1] ) && ( last[0] == indexes[i+0] ) )
00119             {
00120                 element( indexes[i+2] );
00121                 c_vertexes++;
00122 
00123                 even = qfalse;
00124             }
00125             // otherwise we're done with this strip so finish it and start
00126             // a new one
00127             else
00128             {
00129                 qglEnd();
00130 
00131                 qglBegin( GL_TRIANGLE_STRIP );
00132                 c_begins++;
00133 
00134                 element( indexes[i+0] );
00135                 element( indexes[i+1] );
00136                 element( indexes[i+2] );
00137                 c_vertexes += 3;
00138 
00139                 even = qfalse;
00140             }
00141         }
00142 
00143         // cache the last three vertices
00144         last[0] = indexes[i+0];
00145         last[1] = indexes[i+1];
00146         last[2] = indexes[i+2];
00147     }
00148 
00149     qglEnd();
00150 }
00151 
00152 
00153 
00154 /*
00155 ==================
00156 R_DrawElements
00157 
00158 Optionally performs our own glDrawElements that looks for strip conditions
00159 instead of using the single glDrawElements call that may be inefficient
00160 without compiled vertex arrays.
00161 ==================
00162 */
00163 static void R_DrawElements( int numIndexes, const glIndex_t *indexes ) {
00164     int     primitives;
00165 
00166     primitives = r_primitives->integer;
00167 
00168     // default is to use triangles if compiled vertex arrays are present
00169     if ( primitives == 0 ) {
00170         if ( qglLockArraysEXT ) {
00171             primitives = 2;
00172         } else {
00173             primitives = 1;
00174         }
00175     }
00176 
00177 
00178     if ( primitives == 2 ) {
00179         qglDrawElements( GL_TRIANGLES, 
00180                         numIndexes,
00181                         GL_INDEX_TYPE,
00182                         indexes );
00183         return;
00184     }
00185 
00186     if ( primitives == 1 ) {
00187         R_DrawStripElements( numIndexes,  indexes, qglArrayElement );
00188         return;
00189     }
00190     
00191     if ( primitives == 3 ) {
00192         R_DrawStripElements( numIndexes,  indexes, R_ArrayElementDiscrete );
00193         return;
00194     }
00195 
00196     // anything else will cause no drawing
00197 }
00198 
00199 
00200 /*
00201 =============================================================
00202 
00203 SURFACE SHADERS
00204 
00205 =============================================================
00206 */
00207 
00208 shaderCommands_t    tess;
00209 static qboolean setArraysOnce;
00210 
00211 /*
00212 =================
00213 R_BindAnimatedImage
00214 
00215 =================
00216 */
00217 static void R_BindAnimatedImage( textureBundle_t *bundle ) {
00218     int     index;
00219 
00220     if ( bundle->isVideoMap ) {
00221         ri.CIN_RunCinematic(bundle->videoMapHandle);
00222         ri.CIN_UploadCinematic(bundle->videoMapHandle);
00223         return;
00224     }
00225 
00226     if ( bundle->numImageAnimations <= 1 ) {
00227         GL_Bind( bundle->image[0] );
00228         return;
00229     }
00230 
00231     // it is necessary to do this messy calc to make sure animations line up
00232     // exactly with waveforms of the same frequency
00233     index = myftol( tess.shaderTime * bundle->imageAnimationSpeed * FUNCTABLE_SIZE );
00234     index >>= FUNCTABLE_SIZE2;
00235 
00236     if ( index < 0 ) {
00237         index = 0;  // may happen with shader time offsets
00238     }
00239     index %= bundle->numImageAnimations;
00240 
00241     GL_Bind( bundle->image[ index ] );
00242 }
00243 
00244 /*
00245 ================
00246 DrawTris
00247 
00248 Draws triangle outlines for debugging
00249 ================
00250 */
00251 static void DrawTris (shaderCommands_t *input) {
00252     GL_Bind( tr.whiteImage );
00253     qglColor3f (1,1,1);
00254 
00255     GL_State( GLS_POLYMODE_LINE | GLS_DEPTHMASK_TRUE );
00256     qglDepthRange( 0, 0 );
00257 
00258     qglDisableClientState (GL_COLOR_ARRAY);
00259     qglDisableClientState (GL_TEXTURE_COORD_ARRAY);
00260 
00261     qglVertexPointer (3, GL_FLOAT, 16, input->xyz); // padded for SIMD
00262 
00263     if (qglLockArraysEXT) {
00264         qglLockArraysEXT(0, input->numVertexes);
00265         GLimp_LogComment( "glLockArraysEXT\n" );
00266     }
00267 
00268     R_DrawElements( input->numIndexes, input->indexes );
00269 
00270     if (qglUnlockArraysEXT) {
00271         qglUnlockArraysEXT();
00272         GLimp_LogComment( "glUnlockArraysEXT\n" );
00273     }
00274     qglDepthRange( 0, 1 );
00275 }
00276 
00277 
00278 /*
00279 ================
00280 DrawNormals
00281 
00282 Draws vertex normals for debugging
00283 ================
00284 */
00285 static void DrawNormals (shaderCommands_t *input) {
00286     int     i;
00287     vec3_t  temp;
00288 
00289     GL_Bind( tr.whiteImage );
00290     qglColor3f (1,1,1);
00291     qglDepthRange( 0, 0 );  // never occluded
00292     GL_State( GLS_POLYMODE_LINE | GLS_DEPTHMASK_TRUE );
00293 
00294     qglBegin (GL_LINES);
00295     for (i = 0 ; i < input->numVertexes ; i++) {
00296         qglVertex3fv (input->xyz[i]);
00297         VectorMA (input->xyz[i], 2, input->normal[i], temp);
00298         qglVertex3fv (temp);
00299     }
00300     qglEnd ();
00301 
00302     qglDepthRange( 0, 1 );
00303 }
00304 
00305 /*
00306 ==============
00307 RB_BeginSurface
00308 
00309 We must set some things up before beginning any tesselation,
00310 because a surface may be forced to perform a RB_End due
00311 to overflow.
00312 ==============
00313 */
00314 void RB_BeginSurface( shader_t *shader, int fogNum ) {
00315 
00316     shader_t *state = (shader->remappedShader) ? shader->remappedShader : shader;
00317 
00318     tess.numIndexes = 0;
00319     tess.numVertexes = 0;
00320     tess.shader = state;
00321     tess.fogNum = fogNum;
00322     tess.dlightBits = 0;        // will be OR'd in by surface functions
00323     tess.xstages = state->stages;
00324     tess.numPasses = state->numUnfoggedPasses;
00325     tess.currentStageIteratorFunc = state->optimalStageIteratorFunc;
00326 
00327     tess.shaderTime = backEnd.refdef.floatTime - tess.shader->timeOffset;
00328     if (tess.shader->clampTime && tess.shaderTime >= tess.shader->clampTime) {
00329         tess.shaderTime = tess.shader->clampTime;
00330     }
00331 
00332 
00333 }
00334 
00335 /*
00336 ===================
00337 DrawMultitextured
00338 
00339 output = t0 * t1 or t0 + t1
00340 
00341 t0 = most upstream according to spec
00342 t1 = most downstream according to spec
00343 ===================
00344 */
00345 static void DrawMultitextured( shaderCommands_t *input, int stage ) {
00346     shaderStage_t   *pStage;
00347 
00348     pStage = tess.xstages[stage];
00349 
00350     GL_State( pStage->stateBits );
00351 
00352     // this is an ugly hack to work around a GeForce driver
00353     // bug with multitexture and clip planes
00354     if ( backEnd.viewParms.isPortal ) {
00355         qglPolygonMode( GL_FRONT_AND_BACK, GL_FILL );
00356     }
00357 
00358     //
00359     // base
00360     //
00361     GL_SelectTexture( 0 );
00362     qglTexCoordPointer( 2, GL_FLOAT, 0, input->svars.texcoords[0] );
00363     R_BindAnimatedImage( &pStage->bundle[0] );
00364 
00365     //
00366     // lightmap/secondary pass
00367     //
00368     GL_SelectTexture( 1 );
00369     qglEnable( GL_TEXTURE_2D );
00370     qglEnableClientState( GL_TEXTURE_COORD_ARRAY );
00371 
00372     if ( r_lightmap->integer ) {
00373         GL_TexEnv( GL_REPLACE );
00374     } else {
00375         GL_TexEnv( tess.shader->multitextureEnv );
00376     }
00377 
00378     qglTexCoordPointer( 2, GL_FLOAT, 0, input->svars.texcoords[1] );
00379 
00380     R_BindAnimatedImage( &pStage->bundle[1] );
00381 
00382     R_DrawElements( input->numIndexes, input->indexes );
00383 
00384     //
00385     // disable texturing on TEXTURE1, then select TEXTURE0
00386     //
00387     //qglDisableClientState( GL_TEXTURE_COORD_ARRAY );
00388     qglDisable( GL_TEXTURE_2D );
00389 
00390     GL_SelectTexture( 0 );
00391 }
00392 
00393 
00394 
00395 /*
00396 ===================
00397 ProjectDlightTexture
00398 
00399 Perform dynamic lighting with another rendering pass
00400 ===================
00401 */
00402 static void ProjectDlightTexture( void ) {
00403     int     i, l;
00404 #if idppc_altivec
00405     vec_t   origin0, origin1, origin2;
00406     float   texCoords0, texCoords1;
00407     vector float floatColorVec0, floatColorVec1;
00408     vector float modulateVec, colorVec, zero;
00409     vector short colorShort;
00410     vector signed int colorInt;
00411     vector unsigned char floatColorVecPerm, modulatePerm, colorChar;
00412     vector unsigned char vSel = (vector unsigned char)(0x00, 0x00, 0x00, 0xff,
00413                                0x00, 0x00, 0x00, 0xff,
00414                                0x00, 0x00, 0x00, 0xff,
00415                                0x00, 0x00, 0x00, 0xff);
00416 #else
00417     vec3_t  origin;
00418 #endif
00419     float   *texCoords;
00420     byte    *colors;
00421     byte    clipBits[SHADER_MAX_VERTEXES];
00422     MAC_STATIC float    texCoordsArray[SHADER_MAX_VERTEXES][2];
00423     byte    colorArray[SHADER_MAX_VERTEXES][4];
00424     unsigned    hitIndexes[SHADER_MAX_INDEXES];
00425     int     numIndexes;
00426     float   scale;
00427     float   radius;
00428     vec3_t  floatColor;
00429     float   modulate;
00430 
00431     if ( !backEnd.refdef.num_dlights ) {
00432         return;
00433     }
00434 
00435 #if idppc_altivec
00436     // There has to be a better way to do this so that floatColor 
00437     // and/or modulate are already 16-byte aligned.
00438     floatColorVecPerm = vec_lvsl(0,(float *)floatColor);
00439     modulatePerm = vec_lvsl(0,(float *)&modulate);
00440     modulatePerm = (vector unsigned char)vec_splat((vector unsigned int)modulatePerm,0);
00441     zero = (vector float)vec_splat_s8(0);
00442 #endif
00443 
00444     for ( l = 0 ; l < backEnd.refdef.num_dlights ; l++ ) {
00445         dlight_t    *dl;
00446 
00447         if ( !( tess.dlightBits & ( 1 << l ) ) ) {
00448             continue;   // this surface definately doesn't have any of this light
00449         }
00450         texCoords = texCoordsArray[0];
00451         colors = colorArray[0];
00452 
00453         dl = &backEnd.refdef.dlights[l];
00454 #if idppc_altivec
00455         origin0 = dl->transformed[0];
00456         origin1 = dl->transformed[1];
00457         origin2 = dl->transformed[2];
00458 #else
00459         VectorCopy( dl->transformed, origin );
00460 #endif
00461         radius = dl->radius;
00462         scale = 1.0f / radius;
00463 
00464         floatColor[0] = dl->color[0] * 255.0f;
00465         floatColor[1] = dl->color[1] * 255.0f;
00466         floatColor[2] = dl->color[2] * 255.0f;
00467 #if idppc_altivec
00468         floatColorVec0 = vec_ld(0, floatColor);
00469         floatColorVec1 = vec_ld(11, floatColor);
00470         floatColorVec0 = vec_perm(floatColorVec0,floatColorVec0,floatColorVecPerm);
00471 #endif
00472         for ( i = 0 ; i < tess.numVertexes ; i++, texCoords += 2, colors += 4 ) {
00473 #if idppc_altivec
00474             vec_t dist0, dist1, dist2;
00475 #else
00476             vec3_t  dist;
00477 #endif
00478             int     clip;
00479 
00480             backEnd.pc.c_dlightVertexes++;
00481 
00482 #if idppc_altivec
00483             //VectorSubtract( origin, tess.xyz[i], dist );
00484             dist0 = origin0 - tess.xyz[i][0];
00485             dist1 = origin1 - tess.xyz[i][1];
00486             dist2 = origin2 - tess.xyz[i][2];
00487             texCoords0 = 0.5f + dist0 * scale;
00488             texCoords1 = 0.5f + dist1 * scale;
00489 
00490             clip = 0;
00491             if ( texCoords0 < 0.0f ) {
00492                 clip |= 1;
00493             } else if ( texCoords0 > 1.0f ) {
00494                 clip |= 2;
00495             }
00496             if ( texCoords1 < 0.0f ) {
00497                 clip |= 4;
00498             } else if ( texCoords1 > 1.0f ) {
00499                 clip |= 8;
00500             }
00501             texCoords[0] = texCoords0;
00502             texCoords[1] = texCoords1;
00503             
00504             // modulate the strength based on the height and color
00505             if ( dist2 > radius ) {
00506                 clip |= 16;
00507                 modulate = 0.0f;
00508             } else if ( dist2 < -radius ) {
00509                 clip |= 32;
00510                 modulate = 0.0f;
00511             } else {
00512                 dist2 = Q_fabs(dist2);
00513                 if ( dist2 < radius * 0.5f ) {
00514                     modulate = 1.0f;
00515                 } else {
00516                     modulate = 2.0f * (radius - dist2) * scale;
00517                 }
00518             }
00519             clipBits[i] = clip;
00520 
00521             modulateVec = vec_ld(0,(float *)&modulate);
00522             modulateVec = vec_perm(modulateVec,modulateVec,modulatePerm);
00523             colorVec = vec_madd(floatColorVec0,modulateVec,zero);
00524             colorInt = vec_cts(colorVec,0); // RGBx
00525             colorShort = vec_pack(colorInt,colorInt);       // RGBxRGBx
00526             colorChar = vec_packsu(colorShort,colorShort);  // RGBxRGBxRGBxRGBx
00527             colorChar = vec_sel(colorChar,vSel,vSel);       // RGBARGBARGBARGBA replace alpha with 255
00528             vec_ste((vector unsigned int)colorChar,0,(unsigned int *)colors);   // store color
00529 #else
00530             VectorSubtract( origin, tess.xyz[i], dist );
00531             texCoords[0] = 0.5f + dist[0] * scale;
00532             texCoords[1] = 0.5f + dist[1] * scale;
00533 
00534             clip = 0;
00535             if ( texCoords[0] < 0.0f ) {
00536                 clip |= 1;
00537             } else if ( texCoords[0] > 1.0f ) {
00538                 clip |= 2;
00539             }
00540             if ( texCoords[1] < 0.0f ) {
00541                 clip |= 4;
00542             } else if ( texCoords[1] > 1.0f ) {
00543                 clip |= 8;
00544             }
00545             // modulate the strength based on the height and color
00546             if ( dist[2] > radius ) {
00547                 clip |= 16;
00548                 modulate = 0.0f;
00549             } else if ( dist[2] < -radius ) {
00550                 clip |= 32;
00551                 modulate = 0.0f;
00552             } else {
00553                 dist[2] = Q_fabs(dist[2]);
00554                 if ( dist[2] < radius * 0.5f ) {
00555                     modulate = 1.0f;
00556                 } else {
00557                     modulate = 2.0f * (radius - dist[2]) * scale;
00558                 }
00559             }
00560             clipBits[i] = clip;
00561 
00562             colors[0] = myftol(floatColor[0] * modulate);
00563             colors[1] = myftol(floatColor[1] * modulate);
00564             colors[2] = myftol(floatColor[2] * modulate);
00565             colors[3] = 255;
00566 #endif
00567         }
00568 
00569         // build a list of triangles that need light
00570         numIndexes = 0;
00571         for ( i = 0 ; i < tess.numIndexes ; i += 3 ) {
00572             int     a, b, c;
00573 
00574             a = tess.indexes[i];
00575             b = tess.indexes[i+1];
00576             c = tess.indexes[i+2];
00577             if ( clipBits[a] & clipBits[b] & clipBits[c] ) {
00578                 continue;   // not lighted
00579             }
00580             hitIndexes[numIndexes] = a;
00581             hitIndexes[numIndexes+1] = b;
00582             hitIndexes[numIndexes+2] = c;
00583             numIndexes += 3;
00584         }
00585 
00586         if ( !numIndexes ) {
00587             continue;
00588         }
00589 
00590         qglEnableClientState( GL_TEXTURE_COORD_ARRAY );
00591         qglTexCoordPointer( 2, GL_FLOAT, 0, texCoordsArray[0] );
00592 
00593         qglEnableClientState( GL_COLOR_ARRAY );
00594         qglColorPointer( 4, GL_UNSIGNED_BYTE, 0, colorArray );
00595 
00596         GL_Bind( tr.dlightImage );
00597         // include GLS_DEPTHFUNC_EQUAL so alpha tested surfaces don't add light
00598         // where they aren't rendered
00599         if ( dl->additive ) {
00600             GL_State( GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE | GLS_DEPTHFUNC_EQUAL );
00601         }
00602         else {
00603             GL_State( GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_ONE | GLS_DEPTHFUNC_EQUAL );
00604         }
00605         R_DrawElements( numIndexes, hitIndexes );
00606         backEnd.pc.c_totalIndexes += numIndexes;
00607         backEnd.pc.c_dlightIndexes += numIndexes;
00608     }
00609 }
00610 
00611 
00612 /*
00613 ===================
00614 RB_FogPass
00615 
00616 Blends a fog texture on top of everything else
00617 ===================
00618 */
00619 static void RB_FogPass( void ) {
00620     fog_t       *fog;
00621     int         i;
00622 
00623     qglEnableClientState( GL_COLOR_ARRAY );
00624     qglColorPointer( 4, GL_UNSIGNED_BYTE, 0, tess.svars.colors );
00625 
00626     qglEnableClientState( GL_TEXTURE_COORD_ARRAY);
00627     qglTexCoordPointer( 2, GL_FLOAT, 0, tess.svars.texcoords[0] );
00628 
00629     fog = tr.world->fogs + tess.fogNum;
00630 
00631     for ( i = 0; i < tess.numVertexes; i++ ) {
00632         * ( int * )&tess.svars.colors[i] = fog->colorInt;
00633     }
00634 
00635     RB_CalcFogTexCoords( ( float * ) tess.svars.texcoords[0] );
00636 
00637     GL_Bind( tr.fogImage );
00638 
00639     if ( tess.shader->fogPass == FP_EQUAL ) {
00640         GL_State( GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA | GLS_DEPTHFUNC_EQUAL );
00641     } else {
00642         GL_State( GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA );
00643     }
00644 
00645     R_DrawElements( tess.numIndexes, tess.indexes );
00646 }
00647 
00648 /*
00649 ===============
00650 ComputeColors
00651 ===============
00652 */
00653 static void ComputeColors( shaderStage_t *pStage )
00654 {
00655     int     i;
00656 
00657     //
00658     // rgbGen
00659     //
00660     switch ( pStage->rgbGen )
00661     {
00662         case CGEN_IDENTITY:
00663             Com_Memset( tess.svars.colors, 0xff, tess.numVertexes * 4 );
00664             break;
00665         default:
00666         case CGEN_IDENTITY_LIGHTING:
00667             Com_Memset( tess.svars.colors, tr.identityLightByte, tess.numVertexes * 4 );
00668             break;
00669         case CGEN_LIGHTING_DIFFUSE:
00670             RB_CalcDiffuseColor( ( unsigned char * ) tess.svars.colors );
00671             break;
00672         case CGEN_EXACT_VERTEX:
00673             Com_Memcpy( tess.svars.colors, tess.vertexColors, tess.numVertexes * sizeof( tess.vertexColors[0] ) );
00674             break;
00675         case CGEN_CONST:
00676             for ( i = 0; i < tess.numVertexes; i++ ) {
00677                 *(int *)tess.svars.colors[i] = *(int *)pStage->constantColor;
00678             }
00679             break;
00680         case CGEN_VERTEX:
00681             if ( tr.identityLight == 1 )
00682             {
00683                 Com_Memcpy( tess.svars.colors, tess.vertexColors, tess.numVertexes * sizeof( tess.vertexColors[0] ) );
00684             }
00685             else
00686             {
00687                 for ( i = 0; i < tess.numVertexes; i++ )
00688                 {
00689                     tess.svars.colors[i][0] = tess.vertexColors[i][0] * tr.identityLight;
00690                     tess.svars.colors[i][1] = tess.vertexColors[i][1] * tr.identityLight;
00691                     tess.svars.colors[i][2] = tess.vertexColors[i][2] * tr.identityLight;
00692                     tess.svars.colors[i][3] = tess.vertexColors[i][3];
00693                 }
00694             }
00695             break;
00696         case CGEN_ONE_MINUS_VERTEX:
00697             if ( tr.identityLight == 1 )
00698             {
00699                 for ( i = 0; i < tess.numVertexes; i++ )
00700                 {
00701                     tess.svars.colors[i][0] = 255 - tess.vertexColors[i][0];
00702                     tess.svars.colors[i][1] = 255 - tess.vertexColors[i][1];
00703                     tess.svars.colors[i][2] = 255 - tess.vertexColors[i][2];
00704                 }
00705             }
00706             else
00707             {
00708                 for ( i = 0; i < tess.numVertexes; i++ )
00709                 {
00710                     tess.svars.colors[i][0] = ( 255 - tess.vertexColors[i][0] ) * tr.identityLight;
00711                     tess.svars.colors[i][1] = ( 255 - tess.vertexColors[i][1] ) * tr.identityLight;
00712                     tess.svars.colors[i][2] = ( 255 - tess.vertexColors[i][2] ) * tr.identityLight;
00713                 }
00714             }
00715             break;
00716         case CGEN_FOG:
00717             {
00718                 fog_t       *fog;
00719 
00720                 fog = tr.world->fogs + tess.fogNum;
00721 
00722                 for ( i = 0; i < tess.numVertexes; i++ ) {
00723                     * ( int * )&tess.svars.colors[i] = fog->colorInt;
00724                 }
00725             }
00726             break;
00727         case CGEN_WAVEFORM:
00728             RB_CalcWaveColor( &pStage->rgbWave, ( unsigned char * ) tess.svars.colors );
00729             break;
00730         case CGEN_ENTITY:
00731             RB_CalcColorFromEntity( ( unsigned char * ) tess.svars.colors );
00732             break;
00733         case CGEN_ONE_MINUS_ENTITY:
00734             RB_CalcColorFromOneMinusEntity( ( unsigned char * ) tess.svars.colors );
00735             break;
00736     }
00737 
00738     //
00739     // alphaGen
00740     //
00741     switch ( pStage->alphaGen )
00742     {
00743     case AGEN_SKIP:
00744         break;
00745     case AGEN_IDENTITY:
00746         if ( pStage->rgbGen != CGEN_IDENTITY ) {
00747             if ( ( pStage->rgbGen == CGEN_VERTEX && tr.identityLight != 1 ) ||
00748                  pStage->rgbGen != CGEN_VERTEX ) {
00749                 for ( i = 0; i < tess.numVertexes; i++ ) {
00750                     tess.svars.colors[i][3] = 0xff;
00751                 }
00752             }
00753         }
00754         break;
00755     case AGEN_CONST:
00756         if ( pStage->rgbGen != CGEN_CONST ) {
00757             for ( i = 0; i < tess.numVertexes; i++ ) {
00758                 tess.svars.colors[i][3] = pStage->constantColor[3];
00759             }
00760         }
00761         break;
00762     case AGEN_WAVEFORM:
00763         RB_CalcWaveAlpha( &pStage->alphaWave, ( unsigned char * ) tess.svars.colors );
00764         break;
00765     case AGEN_LIGHTING_SPECULAR:
00766         RB_CalcSpecularAlpha( ( unsigned char * ) tess.svars.colors );
00767         break;
00768     case AGEN_ENTITY:
00769         RB_CalcAlphaFromEntity( ( unsigned char * ) tess.svars.colors );
00770         break;
00771     case AGEN_ONE_MINUS_ENTITY:
00772         RB_CalcAlphaFromOneMinusEntity( ( unsigned char * ) tess.svars.colors );
00773         break;
00774     case AGEN_VERTEX:
00775         if ( pStage->rgbGen != CGEN_VERTEX ) {
00776             for ( i = 0; i < tess.numVertexes; i++ ) {
00777                 tess.svars.colors[i][3] = tess.vertexColors[i][3];
00778             }
00779         }
00780         break;
00781     case AGEN_ONE_MINUS_VERTEX:
00782         for ( i = 0; i < tess.numVertexes; i++ )
00783         {
00784             tess.svars.colors[i][3] = 255 - tess.vertexColors[i][3];
00785         }
00786         break;
00787     case AGEN_PORTAL:
00788         {
00789             unsigned char alpha;
00790 
00791             for ( i = 0; i < tess.numVertexes; i++ )
00792             {
00793                 float len;
00794                 vec3_t v;
00795 
00796                 VectorSubtract( tess.xyz[i], backEnd.viewParms.or.origin, v );
00797                 len = VectorLength( v );
00798 
00799                 len /= tess.shader->portalRange;
00800 
00801                 if ( len < 0 )
00802                 {
00803                     alpha = 0;
00804                 }
00805                 else if ( len > 1 )
00806                 {
00807                     alpha = 0xff;
00808                 }
00809                 else
00810                 {
00811                     alpha = len * 0xff;
00812                 }
00813 
00814                 tess.svars.colors[i][3] = alpha;
00815             }
00816         }
00817         break;
00818     }
00819 
00820     //
00821     // fog adjustment for colors to fade out as fog increases
00822     //
00823     if ( tess.fogNum )
00824     {
00825         switch ( pStage->adjustColorsForFog )
00826         {
00827         case ACFF_MODULATE_RGB:
00828             RB_CalcModulateColorsByFog( ( unsigned char * ) tess.svars.colors );
00829             break;
00830         case ACFF_MODULATE_ALPHA:
00831             RB_CalcModulateAlphasByFog( ( unsigned char * ) tess.svars.colors );
00832             break;
00833         case ACFF_MODULATE_RGBA:
00834             RB_CalcModulateRGBAsByFog( ( unsigned char * ) tess.svars.colors );
00835             break;
00836         case ACFF_NONE:
00837             break;
00838         }
00839     }
00840 }
00841 
00842 /*
00843 ===============
00844 ComputeTexCoords
00845 ===============
00846 */
00847 static void ComputeTexCoords( shaderStage_t *pStage ) {
00848     int     i;
00849     int     b;
00850 
00851     for ( b = 0; b < NUM_TEXTURE_BUNDLES; b++ ) {
00852         int tm;
00853 
00854         //
00855         // generate the texture coordinates
00856         //
00857         switch ( pStage->bundle[b].tcGen )
00858         {
00859         case TCGEN_IDENTITY:
00860             Com_Memset( tess.svars.texcoords[b], 0, sizeof( float ) * 2 * tess.numVertexes );
00861             break;
00862         case TCGEN_TEXTURE:
00863             for ( i = 0 ; i < tess.numVertexes ; i++ ) {
00864                 tess.svars.texcoords[b][i][0] = tess.texCoords[i][0][0];
00865                 tess.svars.texcoords[b][i][1] = tess.texCoords[i][0][1];
00866             }
00867             break;
00868         case TCGEN_LIGHTMAP:
00869             for ( i = 0 ; i < tess.numVertexes ; i++ ) {
00870                 tess.svars.texcoords[b][i][0] = tess.texCoords[i][1][0];
00871                 tess.svars.texcoords[b][i][1] = tess.texCoords[i][1][1];
00872             }
00873             break;
00874         case TCGEN_VECTOR:
00875             for ( i = 0 ; i < tess.numVertexes ; i++ ) {
00876                 tess.svars.texcoords[b][i][0] = DotProduct( tess.xyz[i], pStage->bundle[b].tcGenVectors[0] );
00877                 tess.svars.texcoords[b][i][1] = DotProduct( tess.xyz[i], pStage->bundle[b].tcGenVectors[1] );
00878             }
00879             break;
00880         case TCGEN_FOG:
00881             RB_CalcFogTexCoords( ( float * ) tess.svars.texcoords[b] );
00882             break;
00883         case TCGEN_ENVIRONMENT_MAPPED:
00884             RB_CalcEnvironmentTexCoords( ( float * ) tess.svars.texcoords[b] );
00885             break;
00886         case TCGEN_BAD:
00887             return;
00888         }
00889 
00890         //
00891         // alter texture coordinates
00892         //
00893         for ( tm = 0; tm < pStage->bundle[b].numTexMods ; tm++ ) {
00894             switch ( pStage->bundle[b].texMods[tm].type )
00895             {
00896             case TMOD_NONE:
00897                 tm = TR_MAX_TEXMODS;        // break out of for loop
00898                 break;
00899 
00900             case TMOD_TURBULENT:
00901                 RB_CalcTurbulentTexCoords( &pStage->bundle[b].texMods[tm].wave, 
00902                                          ( float * ) tess.svars.texcoords[b] );
00903                 break;
00904 
00905             case TMOD_ENTITY_TRANSLATE:
00906                 RB_CalcScrollTexCoords( backEnd.currentEntity->e.shaderTexCoord,
00907                                      ( float * ) tess.svars.texcoords[b] );
00908                 break;
00909 
00910             case TMOD_SCROLL:
00911                 RB_CalcScrollTexCoords( pStage->bundle[b].texMods[tm].scroll,
00912                                          ( float * ) tess.svars.texcoords[b] );
00913                 break;
00914 
00915             case TMOD_SCALE:
00916                 RB_CalcScaleTexCoords( pStage->bundle[b].texMods[tm].scale,
00917                                      ( float * ) tess.svars.texcoords[b] );
00918                 break;
00919             
00920             case TMOD_STRETCH:
00921                 RB_CalcStretchTexCoords( &pStage->bundle[b].texMods[tm].wave, 
00922                                        ( float * ) tess.svars.texcoords[b] );
00923                 break;
00924 
00925             case TMOD_TRANSFORM:
00926                 RB_CalcTransformTexCoords( &pStage->bundle[b].texMods[tm],
00927                                          ( float * ) tess.svars.texcoords[b] );
00928                 break;
00929 
00930             case TMOD_ROTATE:
00931                 RB_CalcRotateTexCoords( pStage->bundle[b].texMods[tm].rotateSpeed,
00932                                         ( float * ) tess.svars.texcoords[b] );
00933                 break;
00934 
00935             default:
00936                 ri.Error( ERR_DROP, "ERROR: unknown texmod '%d' in shader '%s'\n", pStage->bundle[b].texMods[tm].type, tess.shader->name );
00937                 break;
00938             }
00939         }
00940     }
00941 }
00942 
00943 /*
00944 ** RB_IterateStagesGeneric
00945 */
00946 static void RB_IterateStagesGeneric( shaderCommands_t *input )
00947 {
00948     int stage;
00949 
00950     for ( stage = 0; stage < MAX_SHADER_STAGES; stage++ )
00951     {
00952         shaderStage_t *pStage = tess.xstages[stage];
00953 
00954         if ( !pStage )
00955         {
00956             break;
00957         }
00958 
00959         ComputeColors( pStage );
00960         ComputeTexCoords( pStage );
00961 
00962         if ( !setArraysOnce )
00963         {
00964             qglEnableClientState( GL_COLOR_ARRAY );
00965             qglColorPointer( 4, GL_UNSIGNED_BYTE, 0, input->svars.colors );
00966         }
00967 
00968         //
00969         // do multitexture
00970         //
00971         if ( pStage->bundle[1].image[0] != 0 )
00972         {
00973             DrawMultitextured( input, stage );
00974         }
00975         else
00976         {
00977             if ( !setArraysOnce )
00978             {
00979                 qglTexCoordPointer( 2, GL_FLOAT, 0, input->svars.texcoords[0] );
00980             }
00981 
00982             //
00983             // set state
00984             //
00985             if ( pStage->bundle[0].vertexLightmap && ( (r_vertexLight->integer && !r_uiFullScreen->integer) || glConfig.hardwareType == GLHW_PERMEDIA2 ) && r_lightmap->integer )
00986             {
00987                 GL_Bind( tr.whiteImage );
00988             }
00989             else 
00990                 R_BindAnimatedImage( &pStage->bundle[0] );
00991 
00992             GL_State( pStage->stateBits );
00993 
00994             //
00995             // draw
00996             //
00997             R_DrawElements( input->numIndexes, input->indexes );
00998         }
00999         // allow skipping out to show just lightmaps during development
01000         if ( r_lightmap->integer && ( pStage->bundle[0].isLightmap || pStage->bundle[1].isLightmap || pStage->bundle[0].vertexLightmap ) )
01001         {
01002             break;
01003         }
01004     }
01005 }
01006 
01007 
01008 /*
01009 ** RB_StageIteratorGeneric
01010 */
01011 void RB_StageIteratorGeneric( void )
01012 {
01013     shaderCommands_t *input;
01014 
01015     input = &tess;
01016 
01017     RB_DeformTessGeometry();
01018 
01019     //
01020     // log this call
01021     //
01022     if ( r_logFile->integer ) 
01023     {
01024         // don't just call LogComment, or we will get
01025         // a call to va() every frame!
01026         GLimp_LogComment( va("--- RB_StageIteratorGeneric( %s ) ---\n", tess.shader->name) );
01027     }
01028 
01029     //
01030     // set face culling appropriately
01031     //
01032     GL_Cull( input->shader->cullType );
01033 
01034     // set polygon offset if necessary
01035     if ( input->shader->polygonOffset )
01036     {
01037         qglEnable( GL_POLYGON_OFFSET_FILL );
01038         qglPolygonOffset( r_offsetFactor->value, r_offsetUnits->value );
01039     }
01040 
01041     //
01042     // if there is only a single pass then we can enable color
01043     // and texture arrays before we compile, otherwise we need
01044     // to avoid compiling those arrays since they will change
01045     // during multipass rendering
01046     //
01047     if ( tess.numPasses > 1 || input->shader->multitextureEnv )
01048     {
01049         setArraysOnce = qfalse;
01050         qglDisableClientState (GL_COLOR_ARRAY);
01051         qglDisableClientState (GL_TEXTURE_COORD_ARRAY);
01052     }
01053     else
01054     {
01055         setArraysOnce = qtrue;
01056 
01057         qglEnableClientState( GL_COLOR_ARRAY);
01058         qglColorPointer( 4, GL_UNSIGNED_BYTE, 0, tess.svars.colors );
01059 
01060         qglEnableClientState( GL_TEXTURE_COORD_ARRAY);
01061         qglTexCoordPointer( 2, GL_FLOAT, 0, tess.svars.texcoords[0] );
01062     }
01063 
01064     //
01065     // lock XYZ
01066     //
01067     qglVertexPointer (3, GL_FLOAT, 16, input->xyz); // padded for SIMD
01068     if (qglLockArraysEXT)
01069     {
01070         qglLockArraysEXT(0, input->numVertexes);
01071         GLimp_LogComment( "glLockArraysEXT\n" );
01072     }
01073 
01074     //
01075     // enable color and texcoord arrays after the lock if necessary
01076     //
01077     if ( !setArraysOnce )
01078     {
01079         qglEnableClientState( GL_TEXTURE_COORD_ARRAY );
01080         qglEnableClientState( GL_COLOR_ARRAY );
01081     }
01082 
01083     //
01084     // call shader function
01085     //
01086     RB_IterateStagesGeneric( input );
01087 
01088     // 
01089     // now do any dynamic lighting needed
01090     //
01091     if ( tess.dlightBits && tess.shader->sort <= SS_OPAQUE
01092         && !(tess.shader->surfaceFlags & (SURF_NODLIGHT | SURF_SKY) ) ) {
01093         ProjectDlightTexture();
01094     }
01095 
01096     //
01097     // now do fog
01098     //
01099     if ( tess.fogNum && tess.shader->fogPass ) {
01100         RB_FogPass();
01101     }
01102 
01103     // 
01104     // unlock arrays
01105     //
01106     if (qglUnlockArraysEXT) 
01107     {
01108         qglUnlockArraysEXT();
01109         GLimp_LogComment( "glUnlockArraysEXT\n" );
01110     }
01111 
01112     //
01113     // reset polygon offset
01114     //
01115     if ( input->shader->polygonOffset )
01116     {
01117         qglDisable( GL_POLYGON_OFFSET_FILL );
01118     }
01119 }
01120 
01121 
01122 /*
01123 ** RB_StageIteratorVertexLitTexture
01124 */
01125 void RB_StageIteratorVertexLitTexture( void )
01126 {
01127     shaderCommands_t *input;
01128     shader_t        *shader;
01129 
01130     input = &tess;
01131 
01132     shader = input->shader;
01133 
01134     //
01135     // compute colors
01136     //
01137     RB_CalcDiffuseColor( ( unsigned char * ) tess.svars.colors );
01138 
01139     //
01140     // log this call
01141     //
01142     if ( r_logFile->integer ) 
01143     {
01144         // don't just call LogComment, or we will get
01145         // a call to va() every frame!
01146         GLimp_LogComment( va("--- RB_StageIteratorVertexLitTexturedUnfogged( %s ) ---\n", tess.shader->name) );
01147     }
01148 
01149     //
01150     // set face culling appropriately
01151     //
01152     GL_Cull( input->shader->cullType );
01153 
01154     //
01155     // set arrays and lock
01156     //
01157     qglEnableClientState( GL_COLOR_ARRAY);
01158     qglEnableClientState( GL_TEXTURE_COORD_ARRAY);
01159 
01160     qglColorPointer( 4, GL_UNSIGNED_BYTE, 0, tess.svars.colors );
01161     qglTexCoordPointer( 2, GL_FLOAT, 16, tess.texCoords[0][0] );
01162     qglVertexPointer (3, GL_FLOAT, 16, input->xyz);
01163 
01164     if ( qglLockArraysEXT )
01165     {
01166         qglLockArraysEXT(0, input->numVertexes);
01167         GLimp_LogComment( "glLockArraysEXT\n" );
01168     }
01169 
01170     //
01171     // call special shade routine
01172     //
01173     R_BindAnimatedImage( &tess.xstages[0]->bundle[0] );
01174     GL_State( tess.xstages[0]->stateBits );
01175     R_DrawElements( input->numIndexes, input->indexes );
01176 
01177     // 
01178     // now do any dynamic lighting needed
01179     //
01180     if ( tess.dlightBits && tess.shader->sort <= SS_OPAQUE ) {
01181         ProjectDlightTexture();
01182     }
01183 
01184     //
01185     // now do fog
01186     //
01187     if ( tess.fogNum && tess.shader->fogPass ) {
01188         RB_FogPass();
01189     }
01190 
01191     // 
01192     // unlock arrays
01193     //
01194     if (qglUnlockArraysEXT) 
01195     {
01196         qglUnlockArraysEXT();
01197         GLimp_LogComment( "glUnlockArraysEXT\n" );
01198     }
01199 }
01200 
01201 //define    REPLACE_MODE
01202 
01203 void RB_StageIteratorLightmappedMultitexture( void ) {
01204     shaderCommands_t *input;
01205 
01206     input = &tess;
01207 
01208     //
01209     // log this call
01210     //
01211     if ( r_logFile->integer ) {
01212         // don't just call LogComment, or we will get
01213         // a call to va() every frame!
01214         GLimp_LogComment( va("--- RB_StageIteratorLightmappedMultitexture( %s ) ---\n", tess.shader->name) );
01215     }
01216 
01217     //
01218     // set face culling appropriately
01219     //
01220     GL_Cull( input->shader->cullType );
01221 
01222     //
01223     // set color, pointers, and lock
01224     //
01225     GL_State( GLS_DEFAULT );
01226     qglVertexPointer( 3, GL_FLOAT, 16, input->xyz );
01227 
01228 #ifdef REPLACE_MODE
01229     qglDisableClientState( GL_COLOR_ARRAY );
01230     qglColor3f( 1, 1, 1 );
01231     qglShadeModel( GL_FLAT );
01232 #else
01233     qglEnableClientState( GL_COLOR_ARRAY );
01234     qglColorPointer( 4, GL_UNSIGNED_BYTE, 0, tess.constantColor255 );
01235 #endif
01236 
01237     //
01238     // select base stage
01239     //
01240     GL_SelectTexture( 0 );
01241 
01242     qglEnableClientState( GL_TEXTURE_COORD_ARRAY );
01243     R_BindAnimatedImage( &tess.xstages[0]->bundle[0] );
01244     qglTexCoordPointer( 2, GL_FLOAT, 16, tess.texCoords[0][0] );
01245 
01246     //
01247     // configure second stage
01248     //
01249     GL_SelectTexture( 1 );
01250     qglEnable( GL_TEXTURE_2D );
01251     if ( r_lightmap->integer ) {
01252         GL_TexEnv( GL_REPLACE );
01253     } else {
01254         GL_TexEnv( GL_MODULATE );
01255     }
01256     R_BindAnimatedImage( &tess.xstages[0]->bundle[1] );
01257     qglEnableClientState( GL_TEXTURE_COORD_ARRAY );
01258     qglTexCoordPointer( 2, GL_FLOAT, 16, tess.texCoords[0][1] );
01259 
01260     //
01261     // lock arrays
01262     //
01263     if ( qglLockArraysEXT ) {
01264         qglLockArraysEXT(0, input->numVertexes);
01265         GLimp_LogComment( "glLockArraysEXT\n" );
01266     }
01267 
01268     R_DrawElements( input->numIndexes, input->indexes );
01269 
01270     //
01271     // disable texturing on TEXTURE1, then select TEXTURE0
01272     //
01273     qglDisable( GL_TEXTURE_2D );
01274     qglDisableClientState( GL_TEXTURE_COORD_ARRAY );
01275 
01276     GL_SelectTexture( 0 );
01277 #ifdef REPLACE_MODE
01278     GL_TexEnv( GL_MODULATE );
01279     qglShadeModel( GL_SMOOTH );
01280 #endif
01281 
01282     // 
01283     // now do any dynamic lighting needed
01284     //
01285     if ( tess.dlightBits && tess.shader->sort <= SS_OPAQUE ) {
01286         ProjectDlightTexture();
01287     }
01288 
01289     //
01290     // now do fog
01291     //
01292     if ( tess.fogNum && tess.shader->fogPass ) {
01293         RB_FogPass();
01294     }
01295 
01296     //
01297     // unlock arrays
01298     //
01299     if ( qglUnlockArraysEXT ) {
01300         qglUnlockArraysEXT();
01301         GLimp_LogComment( "glUnlockArraysEXT\n" );
01302     }
01303 }
01304 
01305 /*
01306 ** RB_EndSurface
01307 */
01308 void RB_EndSurface( void ) {
01309     shaderCommands_t *input;
01310 
01311     input = &tess;
01312 
01313     if (input->numIndexes == 0) {
01314         return;
01315     }
01316 
01317     if (input->indexes[SHADER_MAX_INDEXES-1] != 0) {
01318         ri.Error (ERR_DROP, "RB_EndSurface() - SHADER_MAX_INDEXES hit");
01319     }   
01320     if (input->xyz[SHADER_MAX_VERTEXES-1][0] != 0) {
01321         ri.Error (ERR_DROP, "RB_EndSurface() - SHADER_MAX_VERTEXES hit");
01322     }
01323 
01324     if ( tess.shader == tr.shadowShader ) {
01325         RB_ShadowTessEnd();
01326         return;
01327     }
01328 
01329     // for debugging of sort order issues, stop rendering after a given sort value
01330     if ( r_debugSort->integer && r_debugSort->integer < tess.shader->sort ) {
01331         return;
01332     }
01333 
01334     //
01335     // update performance counters
01336     //
01337     backEnd.pc.c_shaders++;
01338     backEnd.pc.c_vertexes += tess.numVertexes;
01339     backEnd.pc.c_indexes += tess.numIndexes;
01340     backEnd.pc.c_totalIndexes += tess.numIndexes * tess.numPasses;
01341 
01342     //
01343     // call off to shader specific tess end function
01344     //
01345     tess.currentStageIteratorFunc();
01346 
01347     //
01348     // draw debugging stuff
01349     //
01350     if ( r_showtris->integer ) {
01351         DrawTris (input);
01352     }
01353     if ( r_shownormals->integer ) {
01354         DrawNormals (input);
01355     }
01356     // clear shader so we can tell we don't have any unclosed surfaces
01357     tess.numIndexes = 0;
01358 
01359     GLimp_LogComment( "----------\n" );
01360 }
01361 

Generated on Thu Aug 25 12:37:53 2005 for Quake III Arena by  doxygen 1.3.9.1