jkxr/Projects/Android/jni/OpenJK/code/rd-vanilla/tr_draw.cpp
Simon 4597b03873 Initial Commit
Opens in Android Studio but haven't even tried to build it yet (it won't.. I know that much!)
2022-09-18 16:37:21 +01:00

1024 lines
31 KiB
C++

/*
===========================================================================
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 <http://www.gnu.org/licenses/>.
===========================================================================
*/
// 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
qglTexImage2D( GL_TEXTURE_2D, 0, GL_RGB8, cols, rows, 0, GL_RGBA, GL_UNSIGNED_BYTE, data );
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 );
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 ();
}
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;
qglTexImage2D( GL_TEXTURE_2D, 0, GL_RGB8, cols, rows, 0, GL_RGBA, GL_UNSIGNED_BYTE, data );
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<<iShift;
}
return iArg;
}
Dissolve_t Dissolve={0};
#define fDISSOLVE_SECONDS 0.75f
// leave the UV stuff in for now as comments in case I ever need to do some sneaky stuff, but for now...
//
static void RE_Blit(float fX0, float fY0, float fX1, float fY1, float fX2, float fY2, float fX3, float fY3,
//float fU0, float fV0, float fU1, float fV1, float fU2, float fV2, float fU3, float fV3,
image_t *pImage, int iGLState
)
{
//
// some junk they had at the top of other StretchRaw code...
//
R_IssuePendingRenderCommands();
// qglFinish();
GL_Bind( pImage );
GL_State(iGLState);
GL_Cull( CT_TWO_SIDED ) ;
qglColor3f( 1.0f, 1.0f, 1.0f );
qglBegin (GL_QUADS);
{
// TL...
//
// qglTexCoord2f( fU0 / (float)pImage->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<iBufferBytes/4; i++, pPix += 4)
{
pPix[3] = 255;
}
// work out what res we're capable of storing/xfading this "screen sprite"...
//
Dissolve.iWidth = glConfig.vidWidth;
Dissolve.iHeight = glConfig.vidHeight;
Dissolve.iUploadWidth = iPow2VidWidth;
Dissolve.iUploadHeight = iPow2VidHeight;
int iTexSize = glConfig.maxTextureSize;
if ( glConfig.maxTextureSize < 256 ) // jic the driver sucks
{
iTexSize = 256;
}
if (Dissolve.iUploadWidth > 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;
}