Simpler render and state update scheduling.
1) If the game doesn't need to update yet (10 updates per second needed), render it. Else goto 2. 2) If the game has been rendered since it was last updated, update it. Else goto 3. 3) Either render or update the game, such that rendering takes 2/5 of the total time. Hopefully, this means more stable framerates, while still running the game at full speed, assuming the CPU is fast enough. And on a super-fast GPU where rendering takes only 2/3 of the time taken to update, there should be at least one rendered frame per game-state update (same as previous behaviour from before the render and state updates were untangled).master
parent
79d8d2251e
commit
059758dff0
|
@ -64,6 +64,8 @@ struct Rational
|
|||
Rational &operator -=(Rational const &b) { return *this = *this - b; }
|
||||
Rational &operator *=(Rational const &b) { return *this = *this * b; }
|
||||
Rational &operator /=(Rational const &b) { return *this = *this / b; }
|
||||
int floor() const { return n >= 0? n/d : (n - (d - 1))/d; }
|
||||
int ceil() const { return n >= 0? (n + (d - 1))/d : n/d; }
|
||||
|
||||
// If int16_t isn't big enough, the comparison operations might overflow.
|
||||
int16_t n;
|
||||
|
|
|
@ -149,7 +149,7 @@ UDWORD getModularScaledRealTime(UDWORD timePeriod, UDWORD requiredRange)
|
|||
}
|
||||
|
||||
/* Call this each loop to update the game timer */
|
||||
void gameTimeUpdate(unsigned renderAverage, unsigned stateAverage)
|
||||
void gameTimeUpdate(bool mayUpdate)
|
||||
{
|
||||
deltaGameTime = 0;
|
||||
deltaGraphicsTime = 0;
|
||||
|
@ -171,20 +171,18 @@ void gameTimeUpdate(unsigned renderAverage, unsigned stateAverage)
|
|||
return;
|
||||
}
|
||||
|
||||
(void)stateAverage; // Don't need to know the average state update time.
|
||||
// A bit arbitrary formula, but if the average time to render a frame is high compared to the time to update the
|
||||
// state, then update multiple times per frame. Otherwise, update at least once per frame.
|
||||
unsigned maximumTicksPerFrame = renderAverage*2 + GAME_TICKS_PER_UPDATE*modifier.d/std::max<int>(modifier.n, 1) + 1;
|
||||
|
||||
// Make sure graphics updates fast enough.
|
||||
prevRealTime = std::max(prevRealTime + maximumTicksPerFrame, currTime) - maximumTicksPerFrame; // Written this way to avoid unsigned underflow.
|
||||
|
||||
// Calculate the new game time
|
||||
int newDeltaGraphicsTime = quantiseFraction(modifier.n, modifier.d, currTime, prevRealTime);
|
||||
ASSERT(newDeltaGraphicsTime >= 0, "Something very wrong.");
|
||||
|
||||
uint32_t newGraphicsTime = graphicsTime + newDeltaGraphicsTime;
|
||||
|
||||
if (newGraphicsTime > gameTime && !mayUpdate)
|
||||
{
|
||||
newGraphicsTime = gameTime;
|
||||
newDeltaGraphicsTime = newGraphicsTime - graphicsTime;
|
||||
}
|
||||
|
||||
if (updateWantedTime == 0 && newGraphicsTime >= gameTime)
|
||||
{
|
||||
updateWantedTime = currTime; // This is the time that we wanted to tick.
|
||||
|
@ -192,15 +190,13 @@ void gameTimeUpdate(unsigned renderAverage, unsigned stateAverage)
|
|||
|
||||
if (newGraphicsTime > gameTime && !checkPlayerGameTime(NET_ALL_PLAYERS))
|
||||
{
|
||||
unsigned player;
|
||||
|
||||
// Pause time at current game time, since we are waiting GAME_GAME_TIME from other players.
|
||||
newGraphicsTime = gameTime;
|
||||
newDeltaGraphicsTime = newGraphicsTime - graphicsTime;
|
||||
|
||||
debug(LOG_SYNC, "Waiting for other players. gameTime = %u, player times are {%s}", gameTime, listToString("%u", ", ", gameQueueTime, gameQueueTime + game.maxPlayers).c_str());
|
||||
|
||||
for (player = 0; player < game.maxPlayers; ++player)
|
||||
for (unsigned player = 0; player < game.maxPlayers; ++player)
|
||||
{
|
||||
if (!checkPlayerGameTime(player))
|
||||
{
|
||||
|
|
|
@ -71,7 +71,7 @@ extern void setGameTime(uint32_t newGameTime);
|
|||
* The game time increases in GAME_UNITS_PER_TICK increments, and deltaGameTime is either 0 or GAME_UNITS_PER_TICK.
|
||||
* @returns true iff the game time ticked.
|
||||
*/
|
||||
void gameTimeUpdate(unsigned renderAverage, unsigned stateAverage);
|
||||
void gameTimeUpdate(bool mayUpdate);
|
||||
/// Call after updating the state, and before processing any net messages that use deltaGameTime. (Sets deltaGameTime = 0.)
|
||||
void gameTimeUpdateEnd(void);
|
||||
|
||||
|
|
25
src/loop.cpp
25
src/loop.cpp
|
@ -27,6 +27,7 @@
|
|||
#include "lib/framework/input.h"
|
||||
#include "lib/framework/strres.h"
|
||||
#include "lib/framework/wzapp.h"
|
||||
#include "lib/framework/rational.h"
|
||||
|
||||
#include "lib/ivis_opengl/pieblitfunc.h"
|
||||
#include "lib/ivis_opengl/piestate.h" //ivis render code
|
||||
|
@ -665,10 +666,11 @@ static void gameStateUpdate()
|
|||
GAMECODE gameLoop(void)
|
||||
{
|
||||
static uint32_t lastFlushTime = 0;
|
||||
static unsigned stateTimes[8];
|
||||
static unsigned renderTimes[8];
|
||||
static int stateTimeIndex = 0;
|
||||
static int renderTimeIndex = 0;
|
||||
|
||||
static int renderBudget = 0; // Scaled time spent rendering minus scaled time spent updating.
|
||||
static bool previousUpdateWasRender = false;
|
||||
const Rational renderFraction(2, 5); // Minimum fraction of time spent rendering.
|
||||
const Rational updateFraction = Rational(1) - renderFraction;
|
||||
|
||||
bool didTick = false;
|
||||
while (true)
|
||||
|
@ -677,11 +679,8 @@ GAMECODE gameLoop(void)
|
|||
// Receive GAME_BLAH messages, and if it's time, process exactly as many GAME_BLAH messages as required to be able to tick the gameTime.
|
||||
recvMessage();
|
||||
|
||||
unsigned renderAverage = std::accumulate(renderTimes, renderTimes + ARRAY_SIZE(renderTimes), 0) / ARRAY_SIZE(renderTimes);
|
||||
unsigned stateAverage = std::accumulate(stateTimes, stateTimes + ARRAY_SIZE(stateTimes), 0) / ARRAY_SIZE(stateTimes);
|
||||
|
||||
// Update gameTime and graphicsTime, and corresponding deltas. Note that gameTime and graphicsTime pause, if we aren't getting our GAME_GAME_TIME messages.
|
||||
gameTimeUpdate(renderAverage, stateAverage);
|
||||
gameTimeUpdate(renderBudget > 0 || previousUpdateWasRender);
|
||||
|
||||
if (deltaGameTime == 0)
|
||||
{
|
||||
|
@ -697,8 +696,9 @@ GAMECODE gameLoop(void)
|
|||
syncDebug("End game state update, gameTime = %d", gameTime);
|
||||
unsigned after = wzGetTicks();
|
||||
|
||||
stateTimes[stateTimeIndex] = after - before;
|
||||
stateTimeIndex = (stateTimeIndex + 1)%ARRAY_SIZE(stateTimes);
|
||||
renderBudget -= (after - before) * renderFraction.n;
|
||||
renderBudget = std::max(renderBudget, (-updateFraction*500).floor());
|
||||
previousUpdateWasRender = false;
|
||||
|
||||
ASSERT(deltaGraphicsTime == 0, "Shouldn't update graphics and game state at once.");
|
||||
}
|
||||
|
@ -713,8 +713,9 @@ GAMECODE gameLoop(void)
|
|||
GAMECODE renderReturn = renderLoop();
|
||||
unsigned after = wzGetTicks();
|
||||
|
||||
renderTimes[renderTimeIndex] = after - before;
|
||||
renderTimeIndex = (renderTimeIndex + 1)%ARRAY_SIZE(renderTimes);
|
||||
renderBudget += (after - before) * updateFraction.n;
|
||||
renderBudget = std::min(renderBudget, (renderFraction*500).floor());
|
||||
previousUpdateWasRender = true;
|
||||
|
||||
return renderReturn;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue