mirror of
https://github.com/DrBeef/JKXR.git
synced 2024-11-23 12:32:26 +00:00
1024 lines
31 KiB
C++
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;
|
|
}
|
|
|