2016-12-18 04:43:04 +00:00
|
|
|
/*
|
|
|
|
===========================================================================
|
|
|
|
Copyright (C) 1999-2005 Id Software, Inc.
|
|
|
|
|
|
|
|
This file is part of Quake III Arena source code.
|
|
|
|
|
|
|
|
Quake III Arena source code is free software; you can redistribute it
|
|
|
|
and/or modify it under the terms of the GNU General Public License as
|
|
|
|
published by the Free Software Foundation; either version 2 of the License,
|
|
|
|
or (at your option) any later version.
|
|
|
|
|
|
|
|
Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
|
|
|
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
|
|
===========================================================================
|
|
|
|
*/
|
|
|
|
#include "tr_local.h"
|
|
|
|
|
|
|
|
|
2022-12-28 19:49:18 +00:00
|
|
|
#define RC(Enum, Type) (int)sizeof(Type),
|
|
|
|
const int renderCommandSizes[RC_COUNT + 1] =
|
|
|
|
{
|
|
|
|
RENDER_COMMAND_LIST(RC)
|
|
|
|
0
|
|
|
|
};
|
|
|
|
#undef RC
|
|
|
|
|
|
|
|
|
|
|
|
// we reserve space for frame ending commands as well as transition commands (begin/end 2D/3D rendering)
|
|
|
|
static const int CmdListReservedBytes = (int)(sizeof(swapBuffersCommand_t) + sizeof(screenshotCommand_t) + 16 * sizeof(void*));
|
|
|
|
|
|
|
|
|
2019-09-25 03:25:59 +00:00
|
|
|
void R_IssueRenderCommands()
|
2016-12-18 04:43:04 +00:00
|
|
|
{
|
2017-11-03 18:34:32 +00:00
|
|
|
renderCommandList_t* cmdList = &backEndData->commands;
|
2016-12-18 04:43:04 +00:00
|
|
|
|
|
|
|
// add an end-of-list command
|
2022-12-28 19:49:18 +00:00
|
|
|
((renderCommandBase_t*)(cmdList->cmds + cmdList->used))->commandId = RC_END_OF_LIST;
|
2016-12-18 04:43:04 +00:00
|
|
|
|
|
|
|
// clear it out, in case this is a sync and not a buffer flip
|
|
|
|
cmdList->used = 0;
|
|
|
|
|
2022-12-28 19:49:18 +00:00
|
|
|
renderPipeline->ExecuteRenderCommands( cmdList->cmds );
|
2016-12-18 04:43:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2022-12-28 19:49:18 +00:00
|
|
|
byte* R_FindRenderCommand( renderCommand_t type )
|
2019-09-25 03:25:59 +00:00
|
|
|
{
|
|
|
|
renderCommandList_t* cmdList = &backEndData->commands;
|
2022-12-28 19:49:18 +00:00
|
|
|
byte* data = cmdList->cmds;
|
|
|
|
byte* end = cmdList->cmds + cmdList->used;
|
2019-09-25 03:25:59 +00:00
|
|
|
|
2022-12-28 19:49:18 +00:00
|
|
|
for ( ;; ) {
|
|
|
|
const int commandId = ((const renderCommandBase_t*)data)->commandId;
|
2019-09-25 03:25:59 +00:00
|
|
|
|
2022-12-28 19:49:18 +00:00
|
|
|
if( commandId == type )
|
|
|
|
return (byte*)data;
|
2019-09-25 03:25:59 +00:00
|
|
|
|
|
|
|
if ( data >= end )
|
|
|
|
return NULL;
|
|
|
|
|
2022-12-28 19:49:18 +00:00
|
|
|
if ( commandId < 0 || commandId >= RC_COUNT ) {
|
|
|
|
Q_assert( !"Invalid render command type" );
|
2019-09-25 03:25:59 +00:00
|
|
|
return NULL;
|
|
|
|
}
|
2022-12-28 19:49:18 +00:00
|
|
|
|
|
|
|
if ( commandId == RC_END_OF_LIST )
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
data += renderCommandSizes[commandId];
|
2016-12-18 04:43:04 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-25 03:25:59 +00:00
|
|
|
|
2022-12-28 19:49:18 +00:00
|
|
|
static void RemoveRenderCommand( byte* cmd, int cmdSize )
|
2019-09-25 03:25:59 +00:00
|
|
|
{
|
|
|
|
renderCommandList_t* cmdList = &backEndData->commands;
|
2022-12-28 19:49:18 +00:00
|
|
|
const int endOffset = (cmd + cmdSize) - cmdList->cmds;
|
2019-09-25 03:25:59 +00:00
|
|
|
const int endBytes = cmdList->used - endOffset;
|
2022-12-28 19:49:18 +00:00
|
|
|
Q_assert( cmd >= cmdList->cmds );
|
|
|
|
Q_assert( cmd + cmdSize <= cmdList->cmds + cmdList->used );
|
2019-09-25 03:25:59 +00:00
|
|
|
|
2022-12-28 19:49:18 +00:00
|
|
|
memmove( cmd, cmd + cmdSize, endBytes );
|
2019-09-25 03:25:59 +00:00
|
|
|
cmdList->used -= cmdSize;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2022-12-28 19:49:18 +00:00
|
|
|
static qbool CanAllocateRenderCommand( int bytes, qbool endFrame )
|
|
|
|
{
|
|
|
|
const int endOffset = endFrame ? 0 : CmdListReservedBytes;
|
|
|
|
const renderCommandList_t* const cmdList = &backEndData->commands;
|
|
|
|
if ( cmdList->used + bytes + endOffset > MAX_RENDER_COMMANDS ) {
|
|
|
|
return qfalse;
|
|
|
|
}
|
2016-12-18 04:43:04 +00:00
|
|
|
|
2022-12-28 19:49:18 +00:00
|
|
|
return qtrue;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
template<typename T>
|
|
|
|
static qbool CanAllocateRenderCommand( qbool endFrame = qfalse )
|
|
|
|
{
|
|
|
|
return CanAllocateRenderCommand( sizeof(T), endFrame );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
byte* R_AllocateRenderCommand( int bytes, int commandId, qbool endFrame )
|
|
|
|
{
|
|
|
|
Q_assert( bytes % 8 == 0 );
|
|
|
|
|
|
|
|
const int endOffset = endFrame ? 0 : CmdListReservedBytes;
|
2020-05-08 01:38:26 +00:00
|
|
|
renderCommandList_t* const cmdList = &backEndData->commands;
|
2016-12-18 04:43:04 +00:00
|
|
|
|
|
|
|
// always leave room for the end of list command
|
2020-05-08 01:38:26 +00:00
|
|
|
if ( cmdList->used + bytes + endOffset > MAX_RENDER_COMMANDS ) {
|
|
|
|
if ( bytes > MAX_RENDER_COMMANDS - endOffset ) {
|
2016-12-18 04:43:04 +00:00
|
|
|
ri.Error( ERR_FATAL, "R_GetCommandBuffer: bad size %i", bytes );
|
|
|
|
}
|
2020-05-08 01:38:26 +00:00
|
|
|
if ( endFrame ) {
|
|
|
|
// we reserved memory specifically for this case
|
|
|
|
// so this really shouldn't ever happen
|
|
|
|
ri.Error( ERR_FATAL, "R_GetCommandBuffer: can't allocate %i bytes to end the frame", bytes );
|
|
|
|
}
|
2016-12-18 04:43:04 +00:00
|
|
|
// if we run out of room, just start dropping commands
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
cmdList->used += bytes;
|
|
|
|
|
2022-12-28 19:49:18 +00:00
|
|
|
byte* const cmd = cmdList->cmds + cmdList->used - bytes;
|
|
|
|
((renderCommandBase_t*)cmd)->commandId = commandId;
|
|
|
|
|
|
|
|
return cmd;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
template<typename T>
|
|
|
|
static T* AllocateRenderCommand( int commandId, qbool endFrame = qfalse )
|
|
|
|
{
|
|
|
|
Q_assert( commandId >= 0 );
|
|
|
|
Q_assert( commandId < RC_COUNT );
|
|
|
|
Q_assert( (int)sizeof(T) == renderCommandSizes[commandId] );
|
|
|
|
|
|
|
|
return (T*)R_AllocateRenderCommand( sizeof(T), commandId, endFrame );
|
2016-12-18 04:43:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2022-12-28 19:49:18 +00:00
|
|
|
static void Begin2D()
|
|
|
|
{
|
|
|
|
if ( tr.renderMode != RM_UI ) {
|
|
|
|
tr.renderMode = RM_UI;
|
|
|
|
AllocateRenderCommand<beginUICommand_t>( RC_BEGIN_UI );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void End2D( qbool endFrame = qfalse )
|
|
|
|
{
|
|
|
|
if ( tr.renderMode == RM_UI ) {
|
|
|
|
tr.renderMode = RM_NONE;
|
|
|
|
AllocateRenderCommand<endUICommand_t>( RC_END_UI, endFrame );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void Begin3D()
|
|
|
|
{
|
|
|
|
if ( tr.renderMode != RM_3D ) {
|
|
|
|
tr.renderMode = RM_3D;
|
|
|
|
AllocateRenderCommand<begin3DCommand_t>( RC_BEGIN_3D );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void End3D( qbool endFrame = qfalse )
|
|
|
|
{
|
|
|
|
if ( tr.renderMode == RM_3D ) {
|
|
|
|
tr.renderMode = RM_NONE;
|
|
|
|
AllocateRenderCommand<end3DCommand_t>( RC_END_3D, endFrame );
|
|
|
|
}
|
|
|
|
}
|
2016-12-18 04:43:04 +00:00
|
|
|
|
|
|
|
|
2019-09-25 03:25:59 +00:00
|
|
|
void R_AddDrawSurfCmd( drawSurf_t* drawSurfs, int numDrawSurfs, int numTranspSurfs )
|
2016-12-18 04:43:04 +00:00
|
|
|
{
|
2022-12-28 19:49:18 +00:00
|
|
|
if ( !CanAllocateRenderCommand<drawSceneViewCommand_t>() )
|
|
|
|
return;
|
|
|
|
|
|
|
|
End2D();
|
|
|
|
Begin3D();
|
|
|
|
|
|
|
|
drawSceneViewCommand_t* const cmd = AllocateRenderCommand<drawSceneViewCommand_t>(RC_DRAW_SCENE_VIEW);
|
|
|
|
Q_assert( cmd );
|
|
|
|
|
|
|
|
qbool shouldClearColor = qfalse;
|
|
|
|
vec4_t clearColor = { 0.0f, 0.0f, 0.0f, 1.0f };
|
|
|
|
if ( tr.refdef.rdflags & RDF_HYPERSPACE ) {
|
|
|
|
const float c = RB_HyperspaceColor();
|
|
|
|
clearColor[0] = c;
|
|
|
|
clearColor[1] = c;
|
|
|
|
clearColor[2] = c;
|
|
|
|
shouldClearColor = qtrue;
|
|
|
|
} else if ( r_fastsky->integer && !(tr.refdef.rdflags & RDF_NOWORLDMODEL) ) {
|
|
|
|
shouldClearColor = qtrue;
|
|
|
|
} else if ( r_clear->integer ) {
|
|
|
|
clearColor[0] = 1.0f;
|
|
|
|
clearColor[1] = 0.0f;
|
|
|
|
clearColor[2] = 1.0f;
|
|
|
|
shouldClearColor = qtrue;
|
|
|
|
}
|
2016-12-18 04:43:04 +00:00
|
|
|
|
|
|
|
cmd->drawSurfs = drawSurfs;
|
|
|
|
cmd->numDrawSurfs = numDrawSurfs;
|
2019-09-25 03:25:59 +00:00
|
|
|
cmd->numTranspSurfs = numTranspSurfs;
|
2016-12-18 04:43:04 +00:00
|
|
|
cmd->refdef = tr.refdef;
|
|
|
|
cmd->viewParms = tr.viewParms;
|
2022-12-28 19:49:18 +00:00
|
|
|
cmd->shouldClearColor = shouldClearColor;
|
|
|
|
Vector4Copy( clearColor, cmd->clearColor );
|
2016-12-18 04:43:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// passing NULL will set the color to white
|
|
|
|
|
|
|
|
void RE_SetColor( const float* rgba )
|
|
|
|
{
|
2022-12-28 19:49:18 +00:00
|
|
|
if ( !CanAllocateRenderCommand<uiSetColorCommand_t>() )
|
|
|
|
return;
|
|
|
|
|
|
|
|
uiSetColorCommand_t* const cmd = AllocateRenderCommand<uiSetColorCommand_t>(RC_UI_SET_COLOR);
|
|
|
|
Q_assert( cmd );
|
2016-12-18 04:43:04 +00:00
|
|
|
|
|
|
|
if ( !rgba )
|
|
|
|
rgba = colorWhite;
|
|
|
|
|
|
|
|
cmd->color[0] = rgba[0];
|
|
|
|
cmd->color[1] = rgba[1];
|
|
|
|
cmd->color[2] = rgba[2];
|
|
|
|
cmd->color[3] = rgba[3];
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void RE_StretchPic( float x, float y, float w, float h, float s1, float t1, float s2, float t2, qhandle_t hShader )
|
|
|
|
{
|
2022-12-28 19:49:18 +00:00
|
|
|
if ( !CanAllocateRenderCommand<uiDrawQuadCommand_t>() )
|
|
|
|
return;
|
|
|
|
|
|
|
|
End3D();
|
|
|
|
Begin2D();
|
|
|
|
|
|
|
|
uiDrawQuadCommand_t* const cmd = AllocateRenderCommand<uiDrawQuadCommand_t>(RC_UI_DRAW_QUAD);
|
|
|
|
Q_assert( cmd );
|
2016-12-18 04:43:04 +00:00
|
|
|
|
|
|
|
cmd->shader = R_GetShaderByHandle( hShader );
|
|
|
|
cmd->x = x;
|
|
|
|
cmd->y = y;
|
|
|
|
cmd->w = w;
|
|
|
|
cmd->h = h;
|
|
|
|
cmd->s1 = s1;
|
|
|
|
cmd->t1 = t1;
|
|
|
|
cmd->s2 = s2;
|
|
|
|
cmd->t2 = t2;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2017-12-27 04:13:33 +00:00
|
|
|
void RE_DrawTriangle( float x0, float y0, float x1, float y1, float x2, float y2, float s0, float t0, float s1, float t1, float s2, float t2, qhandle_t hShader )
|
|
|
|
{
|
2022-12-28 19:49:18 +00:00
|
|
|
if ( !CanAllocateRenderCommand<uiDrawTriangleCommand_t>() )
|
|
|
|
return;
|
|
|
|
|
|
|
|
End3D();
|
|
|
|
Begin2D();
|
|
|
|
|
|
|
|
uiDrawTriangleCommand_t* const cmd = AllocateRenderCommand<uiDrawTriangleCommand_t>(RC_UI_DRAW_TRIANGLE);
|
|
|
|
Q_assert( cmd );
|
2017-12-27 04:13:33 +00:00
|
|
|
|
|
|
|
cmd->shader = R_GetShaderByHandle( hShader );
|
|
|
|
cmd->x0 = x0;
|
|
|
|
cmd->y0 = y0;
|
|
|
|
cmd->x1 = x1;
|
|
|
|
cmd->y1 = y1;
|
|
|
|
cmd->x2 = x2;
|
|
|
|
cmd->y2 = y2;
|
|
|
|
cmd->s0 = s0;
|
|
|
|
cmd->t0 = t0;
|
|
|
|
cmd->s1 = s1;
|
|
|
|
cmd->t1 = t1;
|
|
|
|
cmd->s2 = s2;
|
|
|
|
cmd->t2 = t2;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2016-12-18 04:43:04 +00:00
|
|
|
// if running in stereo, RE_BeginFrame will be called twice for each RE_EndFrame
|
|
|
|
|
|
|
|
void RE_BeginFrame( stereoFrame_t stereoFrame )
|
|
|
|
{
|
|
|
|
if (!tr.registered)
|
|
|
|
return;
|
|
|
|
|
|
|
|
tr.frameCount++;
|
|
|
|
tr.frameSceneNum = 0;
|
2022-12-28 19:49:18 +00:00
|
|
|
tr.renderMode = RM_NONE;
|
2016-12-18 04:43:04 +00:00
|
|
|
|
2017-11-05 21:40:32 +00:00
|
|
|
// delayed screenshot
|
|
|
|
if ( r_delayedScreenshotPending ) {
|
|
|
|
r_delayedScreenshotFrame++;
|
|
|
|
if ( r_delayedScreenshotFrame >= 2 ) {
|
2022-12-28 19:49:18 +00:00
|
|
|
screenshotCommand_t* const cmd = AllocateRenderCommand<screenshotCommand_t>( RC_SCREENSHOT );
|
|
|
|
if ( cmd ) {
|
|
|
|
*cmd = r_delayedScreenshot;
|
|
|
|
r_delayedScreenshotPending = qfalse;
|
|
|
|
r_delayedScreenshotFrame = 0;
|
|
|
|
}
|
2017-11-05 21:40:32 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-12-18 04:43:04 +00:00
|
|
|
//
|
|
|
|
// draw buffer stuff
|
|
|
|
//
|
2022-12-28 19:49:18 +00:00
|
|
|
AllocateRenderCommand<beginFrameCommand_t>( RC_BEGIN_FRAME );
|
2016-12-18 04:43:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2018-01-06 23:03:42 +00:00
|
|
|
void RE_EndFrame( int* pcFE, int* pc2D, int* pc3D, qbool render )
|
2016-12-18 04:43:04 +00:00
|
|
|
{
|
|
|
|
if (!tr.registered)
|
|
|
|
return;
|
|
|
|
|
2018-01-06 23:03:42 +00:00
|
|
|
qbool delayScreenshot = qfalse;
|
|
|
|
if ( !render && r_delayedScreenshotPending )
|
|
|
|
render = qtrue;
|
|
|
|
|
|
|
|
if ( !render ) {
|
2019-09-25 03:25:59 +00:00
|
|
|
screenshotCommand_t* ssCmd = (screenshotCommand_t*)R_FindRenderCommand( RC_SCREENSHOT );
|
2018-01-06 23:03:42 +00:00
|
|
|
|
|
|
|
if ( ssCmd )
|
|
|
|
render = qtrue;
|
|
|
|
|
|
|
|
if ( ssCmd && !ssCmd->delayed ) {
|
|
|
|
// save and remove the command so we can push it back after the frame's done
|
|
|
|
r_delayedScreenshot = *ssCmd;
|
|
|
|
r_delayedScreenshot.delayed = qtrue;
|
2022-12-28 19:49:18 +00:00
|
|
|
RemoveRenderCommand( (byte*)ssCmd, sizeof(screenshotCommand_t) );
|
2018-01-06 23:03:42 +00:00
|
|
|
delayScreenshot = qtrue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-28 19:49:18 +00:00
|
|
|
// @TODO:
|
|
|
|
backEnd.renderFrame = render;
|
|
|
|
|
|
|
|
End2D( qtrue );
|
|
|
|
End3D( qtrue );
|
|
|
|
|
2018-01-06 23:03:42 +00:00
|
|
|
if ( render ) {
|
2022-12-28 19:49:18 +00:00
|
|
|
AllocateRenderCommand<swapBuffersCommand_t>( RC_SWAP_BUFFERS, qtrue );
|
2018-01-06 23:03:42 +00:00
|
|
|
if ( delayScreenshot ) {
|
2022-12-28 19:49:18 +00:00
|
|
|
screenshotCommand_t* const cmd = AllocateRenderCommand<screenshotCommand_t>( RC_SCREENSHOT, qtrue );
|
2018-01-06 23:03:42 +00:00
|
|
|
*cmd = r_delayedScreenshot;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
R_ClearFrame();
|
|
|
|
}
|
2016-12-18 04:43:04 +00:00
|
|
|
|
|
|
|
R_IssueRenderCommands();
|
|
|
|
|
2017-11-03 18:34:32 +00:00
|
|
|
R_ClearFrame();
|
2016-12-18 04:43:04 +00:00
|
|
|
|
|
|
|
if (pcFE)
|
|
|
|
Com_Memcpy( pcFE, &tr.pc, sizeof( tr.pc ) );
|
|
|
|
|
|
|
|
if (pc2D)
|
|
|
|
Com_Memcpy( pc2D, &backEnd.pc2D, sizeof( backEnd.pc2D ) );
|
|
|
|
|
|
|
|
if (pc3D)
|
|
|
|
Com_Memcpy( pc3D, &backEnd.pc3D, sizeof( backEnd.pc3D ) );
|
|
|
|
|
|
|
|
Com_Memset( &tr.pc, 0, sizeof( tr.pc ) );
|
|
|
|
Com_Memset( &backEnd.pc2D, 0, sizeof( backEnd.pc2D ) );
|
|
|
|
Com_Memset( &backEnd.pc3D, 0, sizeof( backEnd.pc3D ) );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void RE_TakeVideoFrame( int width, int height, byte *captureBuffer, byte *encodeBuffer, qbool motionJpeg )
|
|
|
|
{
|
2022-12-28 19:49:18 +00:00
|
|
|
if ( !CanAllocateRenderCommand<videoFrameCommand_t>() )
|
|
|
|
return;
|
|
|
|
|
|
|
|
End2D();
|
|
|
|
End3D();
|
|
|
|
|
|
|
|
videoFrameCommand_t* const cmd = AllocateRenderCommand<videoFrameCommand_t>( RC_VIDEOFRAME );
|
|
|
|
Q_assert( cmd );
|
2016-12-18 04:43:04 +00:00
|
|
|
|
|
|
|
cmd->width = width;
|
|
|
|
cmd->height = height;
|
|
|
|
cmd->captureBuffer = captureBuffer;
|
|
|
|
cmd->encodeBuffer = encodeBuffer;
|
|
|
|
cmd->motionJpeg = motionJpeg;
|
|
|
|
}
|
2022-12-28 19:49:18 +00:00
|
|
|
|
|
|
|
|
|
|
|
void R_EndScene( const viewParms_t* viewParms )
|
|
|
|
{
|
|
|
|
if ( !CanAllocateRenderCommand<videoFrameCommand_t>() )
|
|
|
|
return;
|
|
|
|
|
|
|
|
Q_assert( tr.renderMode == RM_3D );
|
|
|
|
|
|
|
|
endSceneCommand_t* const cmd = AllocateRenderCommand<endSceneCommand_t>( RC_END_SCENE );
|
|
|
|
Q_assert( cmd );
|
|
|
|
|
|
|
|
cmd->viewParms = *viewParms;
|
|
|
|
}
|