The map keeps drawing when the view gets busy

Two testers reported the map and the countdown clock freezing, one of
them only on larger, more complex maps, and one of them until a death.
Both details point at the same place.

The gauges and the cockpit displays are redrawn in whatever time is left
after the 3D view. The background loop is guaranteed a single pass per
frame and gets more only while time remains before the frame is due, and
one pass drew exactly one gauge. So a full sweep of ninety-odd gauges
needed ninety-odd passes - free when there is spare frame, but on a busy
map the 3D view eats all of it, the loop drops to its one guaranteed
pass, and a sweep takes ninety-odd FRAMES. Seconds. A death makes the
renderer skip every static object, the budget frees up, and the backlog
drains at once: the display appears to come back to life.

Worse, the copy phase that follows ended after a SINGLE display, so the
map - one of three - came round only every third sweep.

So: draw gauges to a 2ms slice rather than one per pass, which ties the
refresh rate to elapsed time instead of to how much spare frame there
happened to be; and copy every display before reporting the sweep done.

Measured on a deliberately starved frame budget, which reproduces the
reported symptom: 0.7 sweeps/s before, 3.1 after. At a normal budget
20/s, against 18-19 before - no cost to the healthy case. RP412GAUGESLICE
tunes the slice and 0 restores the old behaviour, which reproduces the
0.7 exactly. RP412GAUGEDIAG=1 logs the rate; watching the screen cannot
tell a display that has stopped refreshing from one whose picture simply
is not changing, which is what made this hard to see.

Also fixes the constructor calling Update() three lines before it
initialised mDisplayToUpdate, so the first pass indexed the D3D device
and surface arrays with whatever was on the stack.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Cyd
2026-08-05 22:36:45 -05:00
co-authored by Claude Opus 5
parent eb17220dd5
commit f4fef29428
4 changed files with 167 additions and 5 deletions
+94 -4
View File
@@ -4358,6 +4358,15 @@ SVGA16::SVGA16(
// Split-view mode: decide before BuildWindows so the packed gauge
// windows can stay hidden.
//------------------------------------------------------------------
//
// Before anything else: the constructor calls Update() below, and
// both of these are read in there. mDisplayToUpdate was only being
// set at the END of the constructor, so that first pass indexed the
// display arrays with whatever was on the stack.
//
mDisplayToUpdate = 0;
mDisplaysCopiedThisPass = 0;
splitViews = False;
cockpitViewscreen = NULL;
Logical explodedViews = False;
@@ -4997,12 +5006,16 @@ Logical SVGA16::Update(Logical forceAll)
GaugeRenderer *renderer = application->GetGaugeRenderer();
if (!valid || renderer == NULL)
{
mDisplaysCopiedThisPass = 0;
CLEAR_SCREEN_COPY();
return False; // Do no more!
}
if (++mDisplayToUpdate >= NUMGAUGEWINDOWS)
// one display per call; the rotation steps at the end of the function
if (mDisplayToUpdate >= NUMGAUGEWINDOWS)
{
mDisplayToUpdate = 0;
}
//Top MFD's
L4GraphicsPort *UL = static_cast<L4GraphicsPort*>(renderer->GetGraphicsPort("auxUL2"));
@@ -5045,7 +5058,10 @@ Logical SVGA16::Update(Logical forceAll)
lrMask |= (lrMask << 16);
} else
{
//No MFDs to draw, break out early
//No MFDs to draw, break out early. The sweep counter resets
//too: leaving it part-used would keep the renderer in its
//copy phase, and it never draws another gauge while there.
mDisplaysCopiedThisPass = 0;
return False;
}
} else
@@ -5057,7 +5073,8 @@ Logical SVGA16::Update(Logical forceAll)
secPalette = &((SVGA16 *) secPort->graphicsDisplay)->palette[secPort->paletteID];
} else
{
//No secondary, skip
//No secondary, skip - and end the sweep, as above.
mDisplaysCopiedThisPass = 0;
return False;
}
}
@@ -5227,7 +5244,80 @@ Logical SVGA16::Update(Logical forceAll)
// if (end.ticks - start.ticks > 100)
// end = start;
return False; // True == 'more to do'
//
//------------------------------------------------------------------
// Step the rotation, and say "more to do" until every display has
// had its turn.
//
// This used to return False unconditionally, which told the gauge
// renderer its copy phase was over after a SINGLE display. A full
// gauge sweep - one gauge per background pass, so as many passes as
// there are active gauges - therefore refreshed one display, and the
// map, one of three, came round only every third sweep.
//
// That is invisible with frame time to spare, because the background
// loop keeps running until the frame budget is used up and gets
// through several sweeps. On a big map the 3D foreground eats the
// whole budget, the loop drops to the one pass per frame it is
// guaranteed, and the map goes seconds between refreshes - which is
// what the field reports describe, on exactly those maps. A death
// makes the renderer skip every static object, the budget frees up,
// and the backlog drains at once: the display appears to come back
// to life, which is the tell that led here.
//------------------------------------------------------------------
//
mDisplayToUpdate++;
if (mDisplayToUpdate >= NUMGAUGEWINDOWS)
{
mDisplayToUpdate = 0;
}
if (++mDisplaysCopiedThisPass < NUMGAUGEWINDOWS)
{
return True; // call again - there are displays waiting
}
mDisplaysCopiedThisPass = 0;
//
// RP412GAUGEDIAG=1 reports how often the displays are actually being
// refreshed. Pixel-watching from outside cannot tell a display that
// is not refreshing from one whose picture simply is not changing,
// and that ambiguity is exactly what makes "my map froze" hard to
// pin down. This counts the real thing.
//
{
static int diagnostics = -1;
if (diagnostics < 0)
{
const char *setting = getenv("RP412GAUGEDIAG");
diagnostics = (setting != NULL && atoi(setting) != 0) ? 1 : 0;
}
if (diagnostics)
{
static unsigned long window_start = 0;
static int sweeps = 0;
unsigned long now = GetTickCount();
++sweeps;
if (window_start == 0)
{
window_start = now;
}
else if (now - window_start >= 2000)
{
// tenths, by hand: whole sweeps per second rounds the
// interesting cases - a starved pipeline managing two
// thirds of a sweep a second reads as a flat "0/s".
int tenths = sweeps * 10000 / (int)(now - window_start);
DEBUG_STREAM << "GaugeDiag: " << sweeps << " display sweep(s) in "
<< (now - window_start) << " ms ("
<< (tenths / 10) << '.' << (tenths % 10) << "/s, "
<< NUMGAUGEWINDOWS << " displays each)\n" << std::flush;
window_start = now;
sweeps = 0;
}
}
}
return False; // the sweep is complete
}