Skip to content

Software Based Strobing Implementation (Black Frame Insertion) #343

Merged
merged 2 commits into from
Jan 24, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 21 additions & 2 deletions engine/client/cl_scrn.c
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ void SCR_DrawFPS( void )
double newtime;
char fpsstring[64];
int offset;
int strobeInterval = r_strobe->integer; //cvar: r_strobe
int eFPS; //Effective FPS (strobing effect)

if( cls.state != ca_active ) return;
if( !cl_showfps->integer || cl.background ) return;
Expand Down Expand Up @@ -101,6 +103,16 @@ void SCR_DrawFPS( void )
/*if( !avgrate ) avgrate = ( maxfps - minfps ) / 2.0f;
else */avgrate += ( calc - avgrate ) / host.framecount;

if (strobeInterval > 0)
{
eFPS = (int)((curfps) / (strobeInterval + 1));
}
else if (strobeInterval < 0)
{
strobeInterval = abs(strobeInterval);
eFPS = (int)((curfps * strobeInterval) / (strobeInterval + 1));
}

switch( cl_showfps->integer )
{
case 3:
Expand All @@ -111,14 +123,21 @@ void SCR_DrawFPS( void )
break;
case 1:
default:
Q_snprintf( fpsstring, sizeof( fpsstring ), "%4i fps", curfps );
if (strobeInterval == 0)
{
Q_snprintf(fpsstring, sizeof(fpsstring), "%4i fps", curfps);
}
else
{
Q_snprintf(fpsstring, sizeof(fpsstring), "%4i FPS\n%3i eFPS", curfps, eFPS);
}
}

MakeRGBA( color, 255, 255, 255, 255 );
}

Con_DrawStringLen( fpsstring, &offset, NULL );
Con_DrawString( scr_width->integer - offset - 2, 4, fpsstring, color );
Con_DrawString( scr_width->integer - offset - 5, 4, fpsstring, color );
}

/*
Expand Down
14 changes: 10 additions & 4 deletions engine/client/console.c
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ convar_t *con_fontscale;
convar_t *con_fontnum;
convar_t *vgui_utf8;

conrect_t con_rect;

static int g_codepage = 0;
static qboolean g_utf8 = false;

Expand Down Expand Up @@ -1698,18 +1700,22 @@ void Con_DrawSolidConsole( float frac, qboolean fill )
y *= frac;
if( y >= 1 )
{
con_rect.x = 0;
con_rect.y = y - scr_width->value * 3 / 4;
con_rect.w = scr_width->value;
con_rect.h = scr_width->value * 3 / 4;
if( fill )
{
GL_SetRenderMode( kRenderNormal );
if( con_black->integer )
{
pglColor4ub( 0, 0, 0, 255 );
R_DrawStretchPic( 0, y - scr_width->value * 3 / 4, scr_width->value, scr_width->value * 3 / 4, 0, 0, 1, 1, cls.fillImage );
R_DrawStretchPic( con_rect.x, con_rect.y, con_rect.w, con_rect.h, 0, 0, 1, 1, cls.fillImage );
}
else
{
pglColor4ub( 255, 255, 255, 255 );
R_DrawStretchPic( 0, y - scr_width->value * 3 / 4, scr_width->value, scr_width->value * 3 / 4, 0, 0, 1, 1, con.background );
R_DrawStretchPic( con_rect.x, con_rect.y, con_rect.w, con_rect.h, 0, 0, 1, 1, con.background );
}
}
else
Expand All @@ -1718,12 +1724,12 @@ void Con_DrawSolidConsole( float frac, qboolean fill )
if( con_black->value )
{
pglColor4ub( 0, 0, 0, 255 * con_alpha->value );
R_DrawStretchPic( 0, y - scr_width->value * 3 / 4, scr_width->value, scr_width->value * 3 / 4, 0, 0, 1, 1, cls.fillImage );
R_DrawStretchPic( con_rect.x, con_rect.y, con_rect.w, con_rect.h, 0, 0, 1, 1, cls.fillImage );
}
else
{
pglColor4ub( 255, 255, 255, 255 * con_alpha->value );
R_DrawStretchPic( 0, y - scr_width->value * 3 / 4, scr_width->value, scr_width->value * 3 / 4, 0, 0, 1, 1, con.background );
R_DrawStretchPic( con_rect.x, con_rect.y, con_rect.w, con_rect.h, 0, 0, 1, 1, con.background );
}
}
pglColor4ub( 255, 255, 255, 255 );
Expand Down
4 changes: 3 additions & 1 deletion engine/client/gl_local.h
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,7 @@ qboolean R_InitRenderAPI( void );
void R_SetupFrustum( void );
void R_FindViewLeaf( void );
void R_DrawFog( void );
void R_Strobe( void );

#define cmatrix3x4 vec4_t *const
#define cmatrix4x4 vec4_t *const
Expand Down Expand Up @@ -699,6 +700,7 @@ extern convar_t *r_lightmap;
extern convar_t *r_fastsky;
extern convar_t *r_vbo;
extern convar_t *r_bump;
extern convar_t *r_strobe;

extern convar_t *mp_decals;

Expand All @@ -709,4 +711,4 @@ extern convar_t *vid_texgamma;
extern convar_t *vid_mode;
extern convar_t *vid_highdpi;

#endif//GL_LOCAL_H
#endif //GL_LOCAL_H
83 changes: 81 additions & 2 deletions engine/client/gl_rmain.c
Original file line number Diff line number Diff line change
Expand Up @@ -1326,15 +1326,94 @@ void R_RenderFrame( const ref_params_t *fd, qboolean drawWorld )
GL_BackendEndFrame();
}

inline static void gl_sBlackFrame( void )
{
if (CL_IsInConsole()) // No strobing on the console
{
pglEnable(GL_SCISSOR_TEST);
pglScissor(con_rect.x, (-con_rect.y) - (con_rect.h*1.25), con_rect.w, con_rect.h); // Preview strobe setting on static
pglClearColor(0.0f, 0.0f, 0.0f, 1.0f);
pglClear(GL_COLOR_BUFFER_BIT);
pglDisable(GL_SCISSOR_TEST);
}
else
{
pglClearColor(0.0f, 0.0f, 0.0f, 1.0f);
pglClear(GL_COLOR_BUFFER_BIT);
}
}

/*
===============
R_Strobe

TODO: Consider vsync timings and do not render the supposed black frame at all.
===============
*/
void R_Strobe( void )
{
static int sCounter = 0;
int getInterval = r_strobe->integer; // Check through modified tag first?
int swapInterval = gl_swapInterval->integer;
if ( (getInterval == 0) || ((swapInterval == 0) && (getInterval != 0)) )
{
if (getInterval != 0) //If v-sync is off, turn off strobing
{
Cvar_Set("r_strobe", "0");
MsgDev(D_WARN, "Strobing (Black Frame Replacement) requires V-SYNC not being turned off! (gl_swapInterval != 0) \n");
Msg("Strobing (Black Frame Insertion) requires Vertical Sync to be enabled!\n");
}
else if (sCounter != 0)
sCounter = 0;

// flush any remaining 2D bits
R_Set2DMode(false);
return;
}

// If interval is positive, insert (replace with) black frames.
// For example result of interval = 3 will be: "black-black-black-normal-black-black-black-normal-black-black-black-normal"
if (getInterval > 0)
{
if (sCounter < getInterval)
{
gl_sBlackFrame();
++sCounter;
}
else
{
sCounter = 0;
R_Set2DMode(false);
}
}
// If interval is negative, the procedure will be the opposite reverse.
// For example result of interval = -4 will be: "normal-normal-normal-normal-black-normal-normal-normal-normal-black"
else
{
getInterval = abs(getInterval);
if (sCounter < getInterval)
{
++sCounter;
R_Set2DMode(false);
}
else
{
gl_sBlackFrame();
sCounter = 0;
}
}
}

/*
===============
R_EndFrame
===============
*/
void R_EndFrame( void )
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Before implementing frame skip, it should be tested is frame skip compatible with mod's custom renderers, like Paranoia, HLFX, Trinity or others.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know a convenient and efficient way to check if this is compatible with custom renderers. I first thought testing it and checking gl errors but I don't think this is a good way. Maybe it is better to disable this completely for custom renderers (except VBO? ) ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Frame skipping itself may break something even in the engine, so engine side must be completely ready for frame skipping. World VBO is now a part of engine renderer. :)

Yeah, maybe it can be disabled for custom renderers or just filled black at the end of the frame, i.e. no skip.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the current version, I do not think that frame skipping would gain considerable amount of performance increase. So I will not implement it in this pull request and leave that part as is. However I did not check this in any platform other than PC.

My Android build platform is not set. Can you check this in Android and see if it turns out to be bad? Therefore this can be disabled before too late.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@fuzun Android version is built automatically by Travis CI.

There is a link from the end of build log. Script is "travis-upload".
https://transfer.sh/rv8Vy/1024-03-45-xash3d-generic-master-b8bb3c5.apk

Copy link

@blurbusters blurbusters Jan 21, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am the author of TestUFO and Founder of Blur Busters.

I can indeed confirm that this is a useful modification, especially for 100Hz+ monitors. It reduces motion blur in the same way as www.testufo.com/blackframes ....

If frameskipping is an essential feature to accept this pull request - then simply do "frame-ignoring"/"frame-blackout" instead of "frame-skipping". Basically intentionally black-out the rendered frames instead of forcing a frame skip. That mathematically works the same way, even though it wastes more GPU resources. (i.e. rendering frames only to be thrown away). But it could be an optional option as a compatibility mode.

Frameskipping for BFI is more efficient, but frame-blackout for BFI (blacking-out rendered frames) could possibly allow more plugins to work, and in theory might actually end up having better physics/mousefeel (depending on how the game engine works). Could be an option / toggle. A future change, perhaps.

Also, with www.testufo.com/blackframes -- the 120fps Razer Phone works great with 60fps black frame insertion (basically 60fps with half the motion blur of normal 60Hz). I bet this will look great on 120Hz Android phones -- for times where 120fps is impossible but you'd like to keep the low motion blur.

Copy link
Member

@a1batross a1batross Jan 21, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mdrejhon thanks for professional review! :)

I don't have 60+ Hz screens now, but after reading your review, it looks like a really cool feature for high frequency screens. So I think it can be merged. :)

Sadly, engine is not ready for frame skipping now and implementing this is a bit complicated because compability with Half-Life, which doesn't have this feature and hardly depends on frametime(which was mostly fixed by Valve in 2013). As it will be done on engine side, maybe someone will use it for BFI.

{
// flush any remaining 2D bits
R_Set2DMode( false );
if (!CL_IsInMenu())
R_Strobe();

#ifdef XASH_SDL
SDL_GL_SwapWindow( host.hWnd );
#elif defined __ANDROID__ // For direct android backend
Expand Down
14 changes: 14 additions & 0 deletions engine/client/vid_common.c
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ convar_t *r_lightmap;
convar_t *r_fastsky;
convar_t *r_vbo;
convar_t *r_bump;
convar_t *r_strobe;
convar_t *mp_decals;

convar_t *vid_displayfrequency;
Expand Down Expand Up @@ -1005,6 +1006,18 @@ static void R_CheckVBO( void )
r_bump = Cvar_Get( "r_bump", def, flags, "enable bump-mapping (r_vbo required)" );
}

/*
===============
R_initStrobe

register strobe cvar
===============
*/
static inline void R_initStrobe( void )
{
r_strobe = Cvar_Get("r_strobe", "0", CVAR_ARCHIVE, "black frame insertion interval");
}

/*
===============
R_Init
Expand Down Expand Up @@ -1047,6 +1060,7 @@ qboolean R_Init( void )
R_StudioInit();
R_ClearDecals();
R_ClearScene();
R_initStrobe();

// initialize screen
SCR_Init();
Expand Down
14 changes: 14 additions & 0 deletions engine/common/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ extern "C" {

#include "backends.h"
#include "defaults.h"
//#include "wrect.h"

//
// check if selected backend not allowed
//
Expand Down Expand Up @@ -1075,6 +1077,18 @@ void Con_ClearAutoComplete();
//
// console.c
//

//#define SolidConsoleX 0
//#define SolidConsoleY (y - scr_width->value * 3 / 4)
//#define SolidConsoleW (scr_width->value)
//#define SolidConsoleH (scr_width->value * 3 / 4)
//extern wrect_t con_rect; //Float - Int incompatibility
typedef struct conrect_s
{
float x, y, w, h;
}conrect_t;
extern conrect_t con_rect;

void Con_Clear( void );

extern const char *svc_strings[256];
Expand Down