diff --git a/MUNGA/GAUGREND.cpp b/MUNGA/GAUGREND.cpp index 1daa82e..4275313 100644 --- a/MUNGA/GAUGREND.cpp +++ b/MUNGA/GAUGREND.cpp @@ -21,6 +21,31 @@ BitTrace Gauge_Renderer("Gauge Renderer"); #endif +// +// How long a single background pass may spend drawing gauges, in +// milliseconds. RP412GAUGESLICE tunes it; 0 restores the original +// behaviour of exactly one gauge per pass. +// +static long + GaugeSliceMs() +{ + static long + slice = -1L; + + if (slice < 0L) + { + const char + *setting = getenv("RP412GAUGESLICE"); + + slice = (setting != NULL) ? atol(setting) : 2L; + if (slice < 0L) + { + slice = 0L; + } + } + return slice; +} + //####################################################################### // Miscellaneous utilities //####################################################################### @@ -3683,7 +3708,27 @@ Logical case background: { - result = ProcessOneActiveGauge(); + //----------------------------------------------------------- + // Draw gauges until the slice is spent, rather than exactly + // one per pass. + // + // The background loop is only guaranteed a single pass per + // frame; it gets more only while time remains before the + // frame is due. On a busy map the 3D foreground eats the + // whole budget, so a cycle of ninety-odd gauges takes + // ninety-odd frames to come round and the displays sit + // frozen for seconds. Working to a slice makes the refresh + // rate depend on elapsed time instead of on how much spare + // frame there happened to be. + //----------------------------------------------------------- + Time slice_end = Now(); + slice_end += GaugeSliceMs(); + + do + { + result = ProcessOneActiveGauge(); + } + while (result && taskMode == background && Now() < slice_end); break; } diff --git a/MUNGA_L4/L4VB16.cpp b/MUNGA_L4/L4VB16.cpp index 89c9ebb..fad4f98 100644 --- a/MUNGA_L4/L4VB16.cpp +++ b/MUNGA_L4/L4VB16.cpp @@ -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(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 } diff --git a/MUNGA_L4/L4VB16.h b/MUNGA_L4/L4VB16.h index 1394ef6..56eb1fe 100644 --- a/MUNGA_L4/L4VB16.h +++ b/MUNGA_L4/L4VB16.h @@ -293,6 +293,17 @@ private: int mDisplayToUpdate; + //------------------------------------------------------------------ + // How many displays this copy pass has refreshed. + // + // The gauge renderer's copy phase ends the moment Update() reports it + // has finished, and Update() reported that after ONE display - so a + // whole gauge sweep refreshed a single display, and the map, one of + // three, came round only every third sweep. Counting them out means + // one sweep refreshes all of them. + //------------------------------------------------------------------ + int mDisplaysCopiedThisPass; + //------------------------------------------------------------------ // Split-view mode (L4MFDSPLIT=1): the five channel-packed MFDs and // the rotated map render as their own desktop windows; the packed diff --git a/RP_L4/RPL4ENVIRON.cpp b/RP_L4/RPL4ENVIRON.cpp index 24b6ee6..724cea9 100644 --- a/RP_L4/RPL4ENVIRON.cpp +++ b/RP_L4/RPL4ENVIRON.cpp @@ -185,6 +185,22 @@ namespace "# default is 60; the arcade pods shipped at 25.\n" "TARGETFPS=60\n" "\n" +"# How long one background pass may spend drawing cockpit gauges, in\n" +"# milliseconds. The gauges and the MFD/map displays are redrawn in the\n" +"# time left over after the 3D view; on a big, busy map there is none\n" +"# left, and at the original one-gauge-per-pass the map and the countdown\n" +"# clock could sit frozen for seconds at a time - until something (a\n" +"# death, say) lightened the 3D view enough for the backlog to drain.\n" +"# Working to a slice ties the refresh rate to elapsed time instead. Set\n" +"# 0 for the old behaviour; raise it to favour the displays over frame\n" +"# rate.\n" +"RP412GAUGESLICE=2\n" +"\n" +"# 1 = log how many times a second every cockpit display is actually\n" +"# refreshed, to rpl4.log. Watching the screen cannot tell a display that\n" +"# has stopped refreshing from one whose picture simply is not changing.\n" +"#RP412GAUGEDIAG=1\n" +"\n" "# 1 = Steam networking (lobbies, FakeIP mesh). Needs the Steam client\n" "# running and steam_appid.txt beside the exe; without them the game\n" "# logs the reason and falls back to plain TCP. 0 = TCP only.\n"