/* =========================================================================== Copyright (C) 1999 - 2005, Id Software, Inc. Copyright (C) 2000 - 2013, Raven Software, Inc. Copyright (C) 2001 - 2013, Activision, Inc. Copyright (C) 2005 - 2015, ioquake3 contributors Copyright (C) 2013 - 2015, OpenJK contributors This file is part of the OpenJK source code. OpenJK is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. 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, see . =========================================================================== */ // tr_draw.c #include "../server/exe_headers.h" #include "tr_common.h" #include "tr_local.h" /* ============= RE_StretchRaw Stretches a raw 32 bit power of 2 bitmap image over the given screen rectangle. Used for cinematics. ============= */ // param 'bDirty' should be true 99% of the time void RE_StretchRaw (int x, int y, int w, int h, int cols, int rows, const byte *data, int iClient, qboolean bDirty ) { if ( !tr.registered ) { return; } R_IssuePendingRenderCommands(); if ( tess.numIndexes ) { RB_EndSurface(); } // we definately want to sync every frame for the cinematics qglFinish(); #ifdef TIMEBIND int start, end; start = end = 0; // only to stop compiler whining, don't need to be initialised #endif // make sure rows and cols are powers of 2 if ( (cols&(cols-1)) || (rows&(rows-1)) ) { Com_Error (ERR_DROP, "Draw_StretchRaw: size not a power of 2: %i by %i", cols, rows); } GL_Bind( tr.scratchImage[iClient] ); // if the scratchImage isn't in the format we want, specify it as a new texture... // if ( cols != tr.scratchImage[iClient]->width || rows != tr.scratchImage[iClient]->height ) { tr.scratchImage[iClient]->width = cols; tr.scratchImage[iClient]->height = rows; #ifdef TIMEBIND if ( r_ignore->integer ) { start = ri.Milliseconds(); } #endif #ifdef HAVE_GLES //don't do qglTexImage2D as this may end up doing a compressed image //on which we are not allowed to do further sub images glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, cols, rows, 0, GL_RGBA, GL_UNSIGNED_BYTE, data ); #else qglTexImage2D( GL_TEXTURE_2D, 0, GL_RGB8, cols, rows, 0, GL_RGBA, GL_UNSIGNED_BYTE, data ); #endif qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, glConfig.clampToEdgeAvailable ? GL_CLAMP_TO_EDGE : GL_CLAMP ); qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, glConfig.clampToEdgeAvailable ? GL_CLAMP_TO_EDGE : GL_CLAMP ); #ifdef TIMEBIND if ( r_ignore->integer ) { end = ri.Milliseconds(); ri.Printf( PRINT_ALL, "qglTexImage2D %i, %i: %i msec\n", cols, rows, end - start ); } #endif } else { if (bDirty) // FIXME: some TA addition or other, not sure why, yet. Should probably be true 99% of the time? { // 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 #ifdef TIMEBIND if ( r_ignore->integer ) { start = ri.Milliseconds(); } #endif qglTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, cols, rows, GL_RGBA, GL_UNSIGNED_BYTE, data ); #ifdef TIMEBIND if ( r_ignore->integer ) { end = ri.Milliseconds(); ri.Printf( PRINT_ALL, "qglTexSubImage2D %i, %i: %i msec\n", cols, rows, end - start ); } #endif } } extern void RB_SetGL2D (void); if (!backEnd.projection2D) { RB_SetGL2D(); } qglColor3f( tr.identityLight, tr.identityLight, tr.identityLight ); #ifdef HAVE_GLES qglColor4f( tr.identityLight, tr.identityLight, tr.identityLight, 1.0f ); GLfloat tex[] = { 0.5 / cols, 0.5 / rows, ( cols - 0.5 ) / cols , 0.5 / rows, ( cols - 0.5 ) / cols, ( rows - 0.5 ) / rows, 0.5 / cols, ( rows - 0.5 ) / rows }; GLfloat vtx[] = { x, y, x + w, y, x + w, y + h, x, y + h }; GLboolean text = qglIsEnabled(GL_TEXTURE_COORD_ARRAY); GLboolean glcol = qglIsEnabled(GL_COLOR_ARRAY); if (glcol) qglDisableClientState(GL_COLOR_ARRAY); if (!text) qglEnableClientState( GL_TEXTURE_COORD_ARRAY ); qglTexCoordPointer( 2, GL_FLOAT, 0, tex ); qglVertexPointer ( 2, GL_FLOAT, 0, vtx ); qglDrawArrays( GL_TRIANGLE_FAN, 0, 4 ); if (glcol) qglEnableClientState(GL_COLOR_ARRAY); if (!text) qglDisableClientState( GL_TEXTURE_COORD_ARRAY ); #else qglBegin (GL_QUADS); qglTexCoord2f ( 0.5f / cols, 0.5f / rows ); qglVertex2f (x, y); qglTexCoord2f ( ( cols - 0.5f ) / cols , 0.5f / rows ); qglVertex2f (x+w, y); qglTexCoord2f ( ( cols - 0.5f ) / cols, ( rows - 0.5f ) / rows ); qglVertex2f (x+w, y+h); qglTexCoord2f ( 0.5f / cols, ( rows - 0.5f ) / rows ); qglVertex2f (x, y+h); qglEnd (); #endif } void RE_UploadCinematic (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 = cols; tr.scratchImage[client]->height = rows; #ifdef HAVE_GLES qglTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, cols, rows, 0, GL_RGBA, GL_UNSIGNED_BYTE, data ); #else qglTexImage2D( GL_TEXTURE_2D, 0, GL_RGB8, cols, rows, 0, GL_RGBA, GL_UNSIGNED_BYTE, data ); #endif qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, glConfig.clampToEdgeAvailable ? GL_CLAMP_TO_EDGE : GL_CLAMP ); qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, glConfig.clampToEdgeAvailable ? GL_CLAMP_TO_EDGE : GL_CLAMP ); } 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 qglTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, cols, rows, GL_RGBA, GL_UNSIGNED_BYTE, data ); } } } extern byte *RB_ReadPixels(int x, int y, int width, int height, size_t *offset, int *padlen); void RE_GetScreenShot(byte *buffer, int w, int h) { byte *source; byte *src, *dst; size_t offset = 0, memcount; int padlen; int x, y; int r, g, b; float xScale, yScale; int xx, yy; source = RB_ReadPixels(0, 0, glConfig.vidWidth, glConfig.vidHeight, &offset, &padlen); memcount = (glConfig.vidWidth * 3 + padlen) * glConfig.vidHeight; // gamma correct if(glConfig.deviceSupportsGamma) R_GammaCorrect(source + offset, memcount); // resample from source xScale = glConfig.vidWidth / (4.0*w); yScale = glConfig.vidHeight / (3.0*h); for ( y = 0 ; y < h ; y++ ) { for ( x = 0 ; x < w ; x++ ) { r = g = b = 0; for ( yy = 0 ; yy < 3 ; yy++ ) { for ( xx = 0 ; xx < 4 ; xx++ ) { src = source + offset + 3 * ( glConfig.vidWidth * (int)( (y*3+yy)*yScale ) + (int)( (x*4+xx)*xScale ) ); r += src[0]; g += src[1]; b += src[2]; } } dst = buffer + 3 * ( y * w + x ); dst[0] = r / 12; dst[1] = g / 12; dst[2] = b / 12; } } R_Free(source); } // this is just a chunk of code from RE_TempRawImage_ReadFromFile() below, subroutinised so I can call it // from the screen dissolve code as well... // static byte *RE_ReSample(byte *pbLoadedPic, int iLoadedWidth, int iLoadedHeight, byte *pbReSampleBuffer, int *piWidth, int *piHeight ) { byte *pbReturn = NULL; // if not resampling, just return some values and return... // if ( pbReSampleBuffer == NULL || (iLoadedWidth == *piWidth && iLoadedHeight == *piHeight) ) { // if not resampling, we're done, just return the loaded size... // *piWidth = iLoadedWidth; *piHeight= iLoadedHeight; pbReturn = pbLoadedPic; } else { // resample from pbLoadedPic to pbReSampledBuffer... // float fXStep = (float)iLoadedWidth / (float)*piWidth; float fYStep = (float)iLoadedHeight/ (float)*piHeight; int iTotPixelsPerDownSample = (int)ceil(fXStep) * (int)ceil(fYStep); int r,g,b; byte *pbDst = pbReSampleBuffer; for ( int y=0; y<*piHeight; y++ ) { for ( int x=0; x<*piWidth; x++ ) { r=g=b=0; for ( float yy = (float)y*fYStep; yy < (float)(y+1)*fYStep ; yy+=1 ) { for ( float xx = (float)x*fXStep; xx < (float)(x+1)*fXStep ; xx+=1 ) { byte *pbSrc = pbLoadedPic + 4 * ( ((int)yy * iLoadedWidth) + (int)xx ); assert(pbSrc < pbLoadedPic + (iLoadedWidth * iLoadedHeight * 4) ); r += pbSrc[0]; g += pbSrc[1]; b += pbSrc[2]; } } assert(pbDst < pbReSampleBuffer + (*piWidth * *piHeight * 4)); pbDst[0] = r / iTotPixelsPerDownSample; pbDst[1] = g / iTotPixelsPerDownSample; pbDst[2] = b / iTotPixelsPerDownSample; pbDst[3] = 255; pbDst += 4; } } // set return value... // pbReturn = pbReSampleBuffer; } return pbReturn; } // this is so the server (or anyone else) can get access to raw pixels if they really need to, // currently it's only used by the server so that savegames can embed a graphic in the auto-save files // (which can't do a screenshot since they're saved out before the level is drawn). // // by default, the pic will be returned as the original dims, but if pbReSampleBuffer != NULL then it's assumed to // be a big enough buffer to hold the resampled image, which also means that the width and height params are read as // inputs (as well as still being inherently outputs) and the pic is scaled to that size, and to that buffer. // // the return value is either NULL, or a pointer to the pixels to use (which may be either the pbReSampleBuffer param, // or the local ptr below). // // In either case, you MUST call the free-up function afterwards ( RE_TempRawImage_CleanUp() ) to get rid of any temp // memory after you've finished with the pic. // // Note: ALWAYS use the return value if != NULL, even if you passed in a declared resample buffer. This is because the // resample will get skipped if the values you want are the same size as the pic that it loaded, so it'll return a // different buffer. // // the vertflip param is used for those functions that expect things in OpenGL's upside-down pixel-read format (sigh) // // (not brilliantly fast, but it's only used for weird stuff anyway) // byte* pbLoadedPic = NULL; byte* RE_TempRawImage_ReadFromFile(const char *psLocalFilename, int *piWidth, int *piHeight, byte *pbReSampleBuffer, qboolean qbVertFlip) { RE_TempRawImage_CleanUp(); // jic byte *pbReturn = NULL; if (psLocalFilename && piWidth && piHeight) { int iLoadedWidth, iLoadedHeight; R_LoadImage( psLocalFilename, &pbLoadedPic, &iLoadedWidth, &iLoadedHeight); if ( pbLoadedPic ) { pbReturn = RE_ReSample( pbLoadedPic, iLoadedWidth, iLoadedHeight, pbReSampleBuffer, piWidth, piHeight); } } if (pbReturn && qbVertFlip) { unsigned int *pSrcLine = (unsigned int *) pbReturn; unsigned int *pDstLine = (unsigned int *) pbReturn + (*piHeight * *piWidth ); // *4 done by compiler (longs) pDstLine-= *piWidth; // point at start of last line, not first after buffer for (int iLineCount=0; iLineCount<*piHeight/2; iLineCount++) { for (int x=0; x<*piWidth; x++) { unsigned int l = pSrcLine[x]; pSrcLine[x] = pDstLine[x]; pDstLine[x] = l; } pSrcLine += *piWidth; pDstLine -= *piWidth; } } return pbReturn; } void RE_TempRawImage_CleanUp(void) { if ( pbLoadedPic ) { R_Free( pbLoadedPic ); pbLoadedPic = NULL; } } typedef enum { eDISSOLVE_RT_TO_LT = 0, eDISSOLVE_LT_TO_RT, eDISSOLVE_TP_TO_BT, eDISSOLVE_BT_TO_TP, eDISSOLVE_CIRCULAR_OUT, // new image comes out from centre // eDISSOLVE_RAND_LIMIT, // label only, not valid to select // // any others... // eDISSOLVE_CIRCULAR_IN, // new image comes in from edges // eDISSOLVE_NUMBEROF } Dissolve_e; typedef struct { int iWidth; int iHeight; int iUploadWidth; int iUploadHeight; int iScratchPadNumber; image_t *pImage; // old image screen image_t *pDissolve; // fuzzy thing image_t *pBlack; // small black image for clearing int iStartTime; // 0 = not processing Dissolve_e eDissolveType; qboolean bTouchNeeded; } Dissolve_t; static int PowerOf2(int iArg) { if ( (iArg & (iArg-1)) != 0) { int iShift=0; while (iArg) { iArg>>=1; iShift++; } iArg = 1<width, fV0 / (float)pImage->height ); qglTexCoord2f( 0,0 ); qglVertex2f( fX0, fY0 ); // TR... // // qglTexCoord2f( fU1 / (float)pImage->width, fV1 / (float)pImage->height ); qglTexCoord2f( 1,0 ); qglVertex2f( fX1, fY1 ); // BR... // // qglTexCoord2f( fU2 / (float)pImage->width, fV2 / (float)pImage->height ); qglTexCoord2f( 1,1 ); qglVertex2f( fX2, fY2); // BL... // // qglTexCoord2f( fU3 / (float)pImage->width, fV3 / (float)pImage->height ); qglTexCoord2f( 0,1 ); qglVertex2f( fX3, fY3); } qglEnd (); } static void RE_KillDissolve(void) { Dissolve.iStartTime = 0; if (Dissolve.pImage) { R_Images_DeleteImage( Dissolve.pImage ); Dissolve.pImage = NULL; } } // Draw the dissolve pic to the screen, over the top of what's already been rendered. // // return = qtrue while still processing, for those interested... // #define iSAFETY_SPRITE_OVERLAP 2 // #pixels to overlap blit region by, in case some drivers leave onscreen seams qboolean RE_ProcessDissolve(void) { if (Dissolve.iStartTime) { if (Dissolve.bTouchNeeded) { // Stuff to avoid music stutter... // // The problem is, that if I call RE_InitDissolve() then call RestartMusic, then by the time the music // has loaded in if it took longer than one second the dissolve would think that it had finished, // even if it had never actually drawn up. However, if I called RE_InitDissolve() AFTER the music had // restarted, then the music would stutter on slow video cards or CPUs while I did the binding/resampling. // // This way, I restart the millisecond counter the first time we actually get as far as rendering, which // should let things work properly... // Dissolve.bTouchNeeded = qfalse; Dissolve.iStartTime = ri.Milliseconds(); } int iDissolvePercentage = ((ri.Milliseconds() - Dissolve.iStartTime)*100) / (1000.0f * fDISSOLVE_SECONDS); // ri.Printf(PRINT_ALL,"iDissolvePercentage %d\n",iDissolvePercentage); if (iDissolvePercentage <= 100) { extern void RB_SetGL2D (void); RB_SetGL2D(); // GLdouble glD; // qglGetDoublev(GL_DEPTH_CLEAR_VALUE,&glD); // qglClearColor(0,0,0,1); qglClearDepth(1.0f); qglClear( GL_DEPTH_BUFFER_BIT ); float fXScaleFactor = (float)SCREEN_WIDTH / (float)Dissolve.iWidth; float fYScaleFactor = (float)SCREEN_HEIGHT/ (float)Dissolve.iHeight; float x0,y0, x1,y1, x2,y2, x3,y3; switch (Dissolve.eDissolveType) { case eDISSOLVE_RT_TO_LT: { float fXboundary = (float) Dissolve.iWidth - (((float)(Dissolve.iWidth+Dissolve.pDissolve->width)*(float)iDissolvePercentage)/100.0f); // blit the fuzzy-dissolve sprite... // x0 = fXScaleFactor * fXboundary; y0 = 0.0f; x1 = fXScaleFactor * (fXboundary + Dissolve.pDissolve->width); y1 = 0.0f; x2 = x1; y2 = fYScaleFactor * Dissolve.iHeight; x3 = x0; y3 = y2; RE_Blit(x0,y0,x1,y1,x2,y2,x3,y3, Dissolve.pDissolve, GLS_DEPTHMASK_TRUE | GLS_SRCBLEND_ZERO | GLS_DSTBLEND_ONE | GLS_ATEST_LT_80); // blit a blank thing over the area the old screen is to be displayed on to enable screen-writing... // (to the left of fXboundary) // x0 = 0.0f; y0 = 0.0f; x1 = fXScaleFactor * (fXboundary + iSAFETY_SPRITE_OVERLAP); y1 = 0.0f; x2 = x1; y2 = fYScaleFactor * Dissolve.iHeight; x3 = x0; y3 = y2; RE_Blit(x0,y0,x1,y1,x2,y2,x3,y3, Dissolve.pBlack, GLS_DEPTHMASK_TRUE | GLS_SRCBLEND_ZERO | GLS_DSTBLEND_ONE); } break; case eDISSOLVE_LT_TO_RT: { float fXboundary = (((float)(Dissolve.iWidth+(2*Dissolve.pDissolve->width))*(float)iDissolvePercentage)/100.0f) - Dissolve.pDissolve->width; // blit the fuzzy-dissolve sprite... // x0 = fXScaleFactor * (fXboundary + Dissolve.pDissolve->width); y0 = 0.0f; x1 = fXScaleFactor * fXboundary; y1 = 0.0f; x2 = x1; y2 = fYScaleFactor * Dissolve.iHeight; x3 = x0; y3 = y2; RE_Blit(x0,y0,x1,y1,x2,y2,x3,y3, Dissolve.pDissolve, GLS_DEPTHMASK_TRUE | GLS_SRCBLEND_ZERO | GLS_DSTBLEND_ONE | GLS_ATEST_LT_80); // blit a blank thing over the area the old screen is to be displayed on to enable screen-writing... // (to the right of fXboundary) // x0 = fXScaleFactor * (( fXboundary + Dissolve.pDissolve->width) - iSAFETY_SPRITE_OVERLAP); y0 = 0.0f; x1 = fXScaleFactor * Dissolve.iWidth; y0 = 0.0f; x2 = x1; y2 = fYScaleFactor * Dissolve.iHeight; x3 = x0; y3 = y2; RE_Blit(x0,y0,x1,y1,x2,y2,x3,y3, Dissolve.pBlack, GLS_DEPTHMASK_TRUE | GLS_SRCBLEND_ZERO | GLS_DSTBLEND_ONE); } break; case eDISSOLVE_TP_TO_BT: { float fYboundary = (((float)(Dissolve.iHeight+(2*Dissolve.pDissolve->width))*(float)iDissolvePercentage)/100.0f) - Dissolve.pDissolve->width; // blit the fuzzy-dissolve sprite... // x0 = 0.0f; y0 = fYScaleFactor * (fYboundary + Dissolve.pDissolve->width); x1 = x0; y1 = fYScaleFactor * fYboundary; x2 = fXScaleFactor * Dissolve.iWidth; y2 = y1; x3 = x2; y3 = y0; RE_Blit(x0,y0,x1,y1,x2,y2,x3,y3, Dissolve.pDissolve, GLS_DEPTHMASK_TRUE | GLS_SRCBLEND_ZERO | GLS_DSTBLEND_ONE | GLS_ATEST_LT_80); // blit a blank thing over the area the old screen is to be displayed on to enable screen-writing... // (underneath fYboundary) // x0 = 0.0f; y0 = fYScaleFactor * ( (fYboundary + Dissolve.pDissolve->width) - iSAFETY_SPRITE_OVERLAP); x1 = fXScaleFactor * Dissolve.iWidth; y1 = y0; x2 = x1; y2 = fYScaleFactor * Dissolve.iHeight; x3 = x0; y3 = y2; RE_Blit(x0,y0,x1,y1,x2,y2,x3,y3, Dissolve.pBlack, GLS_DEPTHMASK_TRUE | GLS_SRCBLEND_ZERO | GLS_DSTBLEND_ONE); } break; case eDISSOLVE_BT_TO_TP: { float fYboundary = Dissolve.iHeight - (((float)(Dissolve.iHeight+Dissolve.pDissolve->width)*(float)iDissolvePercentage)/100.0f); // blit the fuzzy-dissolve sprite... // x0 = 0.0f; y0 = fYScaleFactor * fYboundary; x1 = x0; y1 = fYScaleFactor * (fYboundary + Dissolve.pDissolve->width); x2 = fXScaleFactor * Dissolve.iWidth; y2 = y1; x3 = x2; y3 = y0; RE_Blit(x0,y0,x1,y1,x2,y2,x3,y3, Dissolve.pDissolve, GLS_DEPTHMASK_TRUE | GLS_SRCBLEND_ZERO | GLS_DSTBLEND_ONE | GLS_ATEST_LT_80); // blit a blank thing over the area the old screen is to be displayed on to enable screen-writing... // (above fYboundary) // x0 = 0.0f; y0 = 0.0f; x1 = fXScaleFactor * Dissolve.iWidth; y1 = y0; x2 = x1; y2 = fYScaleFactor * (fYboundary + iSAFETY_SPRITE_OVERLAP); x3 = x0; y3 = y2; RE_Blit(x0,y0,x1,y1,x2,y2,x3,y3, Dissolve.pBlack, GLS_DEPTHMASK_TRUE | GLS_SRCBLEND_ZERO | GLS_DSTBLEND_ONE); } break; case eDISSOLVE_CIRCULAR_IN: { float fDiagZoom = ( ((float)Dissolve.iWidth*0.8) * (100-iDissolvePercentage))/100.0f; // // blit circular graphic... // x0 = fXScaleFactor * ((Dissolve.iWidth/2) - fDiagZoom); y0 = fYScaleFactor * ((Dissolve.iHeight/2)- fDiagZoom); x1 = fXScaleFactor * ((Dissolve.iWidth/2) + fDiagZoom); y1 = y0; x2 = x1; y2 = fYScaleFactor * ((Dissolve.iHeight/2)+ fDiagZoom); x3 = x0; y3 = y2; RE_Blit(x0,y0,x1,y1,x2,y2,x3,y3, Dissolve.pDissolve, GLS_DEPTHMASK_TRUE | GLS_SRCBLEND_ZERO | GLS_DSTBLEND_ONE | GLS_ATEST_LT_80); } break; case eDISSOLVE_CIRCULAR_OUT: { float fDiagZoom = ( ((float)Dissolve.iWidth*0.8) * iDissolvePercentage)/100.0f; // // blit circular graphic... // x0 = fXScaleFactor * ((Dissolve.iWidth/2) - fDiagZoom); y0 = fYScaleFactor * ((Dissolve.iHeight/2)- fDiagZoom); x1 = fXScaleFactor * ((Dissolve.iWidth/2) + fDiagZoom); y1 = y0; x2 = x1; y2 = fYScaleFactor * ((Dissolve.iHeight/2)+ fDiagZoom); x3 = x0; y3 = y2; RE_Blit(x0,y0,x1,y1,x2,y2,x3,y3, Dissolve.pDissolve, GLS_DEPTHMASK_TRUE | GLS_SRCBLEND_ZERO | GLS_DSTBLEND_ONE | GLS_ATEST_LT_80); // now blit the 4 black squares around it to mask off the rest of the screen... // // LHS, top to bottom... // RE_Blit(0,0, // x0,y0 x0+iSAFETY_SPRITE_OVERLAP,0, // x1,y1 x0+iSAFETY_SPRITE_OVERLAP,(fYScaleFactor * Dissolve.iHeight),// x2,y2 0,(fYScaleFactor * Dissolve.iHeight), // x3,y3, Dissolve.pBlack, GLS_DEPTHMASK_TRUE | GLS_SRCBLEND_ZERO | GLS_DSTBLEND_ONE ); // RHS top to bottom... // RE_Blit(x1-iSAFETY_SPRITE_OVERLAP,0, // x0,y0 (fXScaleFactor * Dissolve.iWidth),0, // x1,y1 (fXScaleFactor * Dissolve.iWidth),(fYScaleFactor * Dissolve.iHeight),// x2,y2 x1-iSAFETY_SPRITE_OVERLAP,(fYScaleFactor * Dissolve.iHeight), // x3,y3, Dissolve.pBlack, GLS_DEPTHMASK_TRUE | GLS_SRCBLEND_ZERO | GLS_DSTBLEND_ONE ); // top... // RE_Blit(x0-iSAFETY_SPRITE_OVERLAP,0, // x0,y0 x1+iSAFETY_SPRITE_OVERLAP,0, // x1,y1 x1+iSAFETY_SPRITE_OVERLAP,y0 + iSAFETY_SPRITE_OVERLAP, // x2,y2 x0-iSAFETY_SPRITE_OVERLAP,y0 + iSAFETY_SPRITE_OVERLAP, // x3,y3 Dissolve.pBlack, GLS_DEPTHMASK_TRUE | GLS_SRCBLEND_ZERO | GLS_DSTBLEND_ONE ); // bottom... // RE_Blit(x0-iSAFETY_SPRITE_OVERLAP,y3-iSAFETY_SPRITE_OVERLAP, // x0,y0 x1+iSAFETY_SPRITE_OVERLAP,y2-iSAFETY_SPRITE_OVERLAP, // x1,y1 x1+iSAFETY_SPRITE_OVERLAP,(fYScaleFactor * Dissolve.iHeight), // x2,y2 x0-iSAFETY_SPRITE_OVERLAP,(fYScaleFactor * Dissolve.iHeight), // x3,y3 Dissolve.pBlack, GLS_DEPTHMASK_TRUE | GLS_SRCBLEND_ZERO | GLS_DSTBLEND_ONE ); } break; default: { assert(0); iDissolvePercentage = 101; // force a dissolve-kill break; } } // re-check in case we hit the default case above... // if (iDissolvePercentage <= 100) { // still dissolving, so now (finally), blit old image over top... // x0 = 0.0f; y0 = 0.0f; x1 = fXScaleFactor * Dissolve.pImage->width; y1 = y0; x2 = x1; y2 = fYScaleFactor * Dissolve.pImage->height; x3 = x0; y3 = y2; RE_Blit(x0,y0,x1,y1,x2,y2,x3,y3, Dissolve.pImage,GLS_DEPTHFUNC_EQUAL); } } if (iDissolvePercentage > 100) { RE_KillDissolve(); } } return qfalse; } // return = qtrue(success) else fail, for those interested... // qboolean RE_InitDissolve(qboolean bForceCircularExtroWipe) { R_IssuePendingRenderCommands(); // ri.Printf( PRINT_ALL, "RE_InitDissolve()\n"); qboolean bReturn = qfalse; if (//Dissolve.iStartTime == 0 // no point in interruping an existing one //&& tr.registered == qtrue // ... stops it crashing during first cinematic before the menus... :-) ) { RE_KillDissolve(); // kill any that are already running int iPow2VidWidth = PowerOf2( glConfig.vidWidth ); int iPow2VidHeight = PowerOf2( glConfig.vidHeight); int iBufferBytes = iPow2VidWidth * iPow2VidHeight * 4; byte *pBuffer = (byte *) R_Malloc( iBufferBytes, TAG_TEMP_WORKSPACE, qfalse); if (pBuffer) { // read current screen image... (GL_RGBA should work even on 3DFX in that the RGB parts will be valid at least) // qglReadPixels (0, 0, glConfig.vidWidth, glConfig.vidHeight, GL_RGBA, GL_UNSIGNED_BYTE, pBuffer ); // // now expand the pic over the top of itself so that it has a stride value of {PowerOf2(glConfig.vidWidth)} // (for GL power-of-2 rules) // byte *pbSrc = &pBuffer[ glConfig.vidWidth * glConfig.vidHeight * 4]; byte *pbDst = &pBuffer[ iPow2VidWidth * glConfig.vidHeight * 4]; // // ( clear to end, since we've got pbDst nicely setup here) // int iClearBytes = &pBuffer[iBufferBytes] - pbDst; memset(pbDst, 0, iClearBytes); // // work out copy/stride vals... // iClearBytes = ( iPow2VidWidth - glConfig.vidWidth ) * 4; int iCopyBytes = glConfig.vidWidth * 4; // // do it... // for (int y = 0; y < glConfig.vidHeight; y++) { pbDst -= iClearBytes; memset(pbDst,0,iClearBytes); pbDst -= iCopyBytes; pbSrc -= iCopyBytes; memmove(pbDst, pbSrc, iCopyBytes); } // // ok, now we've got the screen image in the top left of the power-of-2 texture square, // but of course the damn thing's upside down (thanks, GL), so invert it, but only within // the picture pixels, NOT the upload texture as a whole... // byte *pbSwapLineBuffer = (byte *)R_Malloc( iCopyBytes, TAG_TEMP_WORKSPACE, qfalse); pbSrc = &pBuffer[0]; pbDst = &pBuffer[(glConfig.vidHeight-1) * iPow2VidWidth * 4]; for (int y = 0; y < glConfig.vidHeight/2; y++) { memcpy(pbSwapLineBuffer, pbDst, iCopyBytes); memcpy(pbDst, pbSrc, iCopyBytes); memcpy(pbSrc, pbSwapLineBuffer, iCopyBytes); pbDst -= iPow2VidWidth*4; pbSrc += iPow2VidWidth*4; } R_Free(pbSwapLineBuffer); // // Now, in case of busted drivers, 3DFX cards, etc etc we stomp the alphas to 255... // byte *pPix = pBuffer; for (int i=0; i iTexSize) { Dissolve.iUploadWidth = iTexSize; } if (Dissolve.iUploadHeight > iTexSize) { Dissolve.iUploadHeight = iTexSize; } // alloc resample buffer... (note slight optimisation to avoid spurious alloc) // byte *pbReSampleBuffer = ( iPow2VidWidth == Dissolve.iUploadWidth && iPow2VidHeight == Dissolve.iUploadHeight )? NULL : (byte*) R_Malloc( iPow2VidWidth * iPow2VidHeight * 4, TAG_TEMP_WORKSPACE, qfalse); // re-sample screen... // byte *pbScreenSprite = RE_ReSample( pBuffer, // byte *pbLoadedPic iPow2VidWidth, // int iLoadedWidth iPow2VidHeight, // int iLoadedHeight // pbReSampleBuffer, // byte *pbReSampleBuffer &Dissolve.iUploadWidth, // int *piWidth &Dissolve.iUploadHeight // int *piHeight ); Dissolve.pImage = R_CreateImage("*DissolveImage", // const char *name pbScreenSprite, // const byte *pic Dissolve.iUploadWidth, // int width Dissolve.iUploadHeight, // int height GL_RGBA, qfalse, // qboolean mipmap qfalse, // qboolean allowPicmip qfalse, // qboolean allowTC GL_CLAMP // int glWrapClampMode ); static byte bBlack[8*8*4]={0}; for (int j=0; j<8*8*4; j+=4) // itu? bBlack[j+3]=255; // Dissolve.pBlack = R_CreateImage( "*DissolveBlack", // const char *name bBlack, // const byte *pic 8, // int width 8, // int height GL_RGBA, qfalse, // qboolean mipmap qfalse, // qboolean allowPicmip qfalse, // qboolean allowTC GL_CLAMP // int glWrapClampMode ); if (pbReSampleBuffer) { R_Free(pbReSampleBuffer); } R_Free(pBuffer); // pick dissolve type... // #if 0 // cycles through every dissolve type, for testing... // static Dissolve_e eDissolve = (Dissolve_e) 0; Dissolve.eDissolveType = eDissolve; eDissolve = (Dissolve_e) (eDissolve+1); if (eDissolve == eDISSOLVE_RAND_LIMIT) eDissolve = (Dissolve_e) (eDissolve+1); if (eDissolve >= eDISSOLVE_NUMBEROF) eDissolve = (Dissolve_e) 0; #else // final (& random) version... // Dissolve.eDissolveType = (Dissolve_e) Q_irand( 0, eDISSOLVE_RAND_LIMIT-1); #endif if (bForceCircularExtroWipe) { Dissolve.eDissolveType = eDISSOLVE_CIRCULAR_IN; } // ... and load appropriate graphics... // // special tweak, although this code is normally called just before client spawns into world (and // is therefore pretty much immune to precache issues) I also need to make sure that the inverse // iris graphic is loaded so for the special case of doing a circular wipe at the end of the last // level doesn't stall on loading the image. So I'll load it here anyway - to prime the image - // then allow the random wiper to overwrite the ptr if needed. This way the end of level call // will be instant. Downside: every level has one extra 256x256 texture. // Trying to decipher these comments - looks like no problem taking this out. I want the RAM. { Dissolve.pDissolve = R_FindImageFile( "gfx/2d/iris_mono_rev", // const char *name qfalse, // qboolean mipmap qfalse, // qboolean allowPicmip qfalse, // qboolean allowTC GL_CLAMP // int glWrapClampMode ); } extern cvar_t *com_buildScript; if (com_buildScript->integer) { // register any/all of the possible CASE statements below... // Dissolve.pDissolve = R_FindImageFile( "gfx/2d/iris_mono", // const char *name qfalse, // qboolean mipmap qfalse, // qboolean allowPicmip qfalse, // qboolean allowTC GL_CLAMP // int glWrapClampMode ); Dissolve.pDissolve = R_FindImageFile( "textures/common/dissolve", // const char *name qfalse, // qboolean mipmap qfalse, // qboolean allowPicmip qfalse, // qboolean allowTC GL_REPEAT // int glWrapClampMode ); } switch (Dissolve.eDissolveType) { case eDISSOLVE_CIRCULAR_IN: { Dissolve.pDissolve = R_FindImageFile( "gfx/2d/iris_mono_rev", // const char *name qfalse, // qboolean mipmap qfalse, // qboolean allowPicmip qfalse, // qboolean allowTC GL_CLAMP // int glWrapClampMode ); } break; case eDISSOLVE_CIRCULAR_OUT: { Dissolve.pDissolve = R_FindImageFile( "gfx/2d/iris_mono", // const char *name qfalse, // qboolean mipmap qfalse, // qboolean allowPicmip qfalse, // qboolean allowTC GL_CLAMP // int glWrapClampMode ); } break; default: { Dissolve.pDissolve = R_FindImageFile( "textures/common/dissolve", // const char *name qfalse, // qboolean mipmap qfalse, // qboolean allowPicmip qfalse, // qboolean allowTC GL_REPEAT // int glWrapClampMode ); } break; } // all good?... // if (Dissolve.pDissolve) // test if image was found, if not, don't do dissolves { Dissolve.iStartTime = ri.Milliseconds(); // gets overwritten first time, but MUST be set to NZ Dissolve.bTouchNeeded = qtrue; bReturn = qtrue; } else { RE_KillDissolve(); } } } return bReturn; } #endif