The GPU transforms the vertices
The cockpit displays were updating every two to three seconds while the 3D view held a perfectly smooth 55 fps. This is why, and it is one line. Every device was created D3DCREATE_SOFTWARE_VERTEXPROCESSING - every vertex on the track transformed and lit on the CPU, on the one core this game uses for everything. That was not a choice when the engine was written; there was no hardware to hand it to. The error message beneath the call still says "Couldn't create HARDWARE_VERTEXPROCESSING device", so the flag was changed at some point and the message left behind. Measured on the biggest track, 1920x1080: software foreground 17.2 ms background 1.2 ms 2.4 gauge passes/s hardware foreground 0.2 ms background 17.9 ms 50.0 gauge passes/s The frame loop runs the foreground and then spends whatever is LEFT on the background gauge work. A foreground costing 17.2 ms of an 18 ms frame leaves nothing, so the gauge loop got the single pass it is guaranteed and no more. A pass needs about twenty steps - eighteen gauges and three display copies - so the cockpit ran at two passes a second, and since the renderer walks a sixteen-step rate wheel, a gauge on one step redrew once per SIXTEEN of those. Three seconds. The map, the clock, the boost gauge and the sim still running after the fade to black were all that one number. Hardware T&L is now the default and sw is the way back. Fixed-function lighting and fog are not bit-identical between the old software path and a driver, so the escape hatch stays - but the picture was checked against both and the difference is not the one worth defending. A cockpit whose instruments update twice a second is. It falls back to software by itself if the adapter has no hardware T&L. The instruments that found it stay in, because nothing about this was visible from outside: - FrameSplit, under RP412GAUGEDIAG, reports foreground against background against whole frame. APPMGR has computed those four timestamps every frame since forever and never reported one of them; it would have pointed here on the first day. - FrameDiag reports frames per second on the same window, so the gauge sweep rate can be read against the frame rate rather than guessed at. - ProfileReport, which already existed and was only reachable through F11 on the RIO controls mapper - not the mapper a desktop player runs, so in practice unreachable - now runs on a timer under RP412GAUGEPROFILE. Its per-gauge line gains the rate mask and tier, which is what names a display as one-in-sixteen rather than merely slow. - The winners' circle logs what its exterior and name-plate rebuilds cost, since nothing else runs while they do. RP412VSYNC is here too, and it is honest about itself: presenting IMMEDIATE was measured and made no difference to the frame budget, because the frame was full of work rather than waiting. It stays as a latency-against-tearing preference, not a fix. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -234,6 +234,74 @@ Background_Loop:
|
||||
}
|
||||
Time endBackground = Now();
|
||||
|
||||
//
|
||||
//---------------------------------------------------------------------
|
||||
// RP412GAUGEDIAG=1: where the frame actually goes.
|
||||
//
|
||||
// These four timestamps have been computed every frame since forever
|
||||
// and never reported. The whole cockpit problem is a question about
|
||||
// this split - the background loop only gets what the foreground
|
||||
// leaves - and it has been measurable all along.
|
||||
//---------------------------------------------------------------------
|
||||
//
|
||||
{
|
||||
static int
|
||||
frameSplitDiag = -1;
|
||||
|
||||
if (frameSplitDiag < 0)
|
||||
{
|
||||
const char
|
||||
*setting = getenv("RP412GAUGEDIAG");
|
||||
|
||||
frameSplitDiag = (setting != NULL && atoi(setting) != 0) ? 1 : 0;
|
||||
}
|
||||
if (frameSplitDiag)
|
||||
{
|
||||
static Scalar
|
||||
foregroundSum = (Scalar) 0,
|
||||
backgroundSum = (Scalar) 0,
|
||||
frameSum = (Scalar) 0;
|
||||
static int
|
||||
splitFrames = 0;
|
||||
static Logical
|
||||
splitStarted = False;
|
||||
static Time
|
||||
splitWindowStart;
|
||||
|
||||
Time
|
||||
splitNow = Now();
|
||||
|
||||
foregroundSum += (Scalar)(endForeground - startForeground);
|
||||
backgroundSum += (Scalar)(endBackground - startBackground);
|
||||
frameSum += (Scalar)(splitNow - beginFrameTimestamp);
|
||||
++splitFrames;
|
||||
|
||||
if (!splitStarted)
|
||||
{
|
||||
splitStarted = True;
|
||||
splitWindowStart = splitNow;
|
||||
}
|
||||
else if ((Scalar)(splitNow - splitWindowStart) >= (Scalar) 2)
|
||||
{
|
||||
char
|
||||
buffer[200];
|
||||
|
||||
sprintf(buffer,
|
||||
"FrameSplit: %d frames | foreground %.2f ms | "
|
||||
"background %.2f ms | whole frame %.2f ms\n",
|
||||
splitFrames,
|
||||
(double)(foregroundSum * 1000.0f / splitFrames),
|
||||
(double)(backgroundSum * 1000.0f / splitFrames),
|
||||
(double)(frameSum * 1000.0f / splitFrames));
|
||||
DEBUG_STREAM << buffer << std::flush;
|
||||
|
||||
foregroundSum = backgroundSum = frameSum = (Scalar) 0;
|
||||
splitFrames = 0;
|
||||
splitWindowStart = splitNow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//char str[256];
|
||||
//Scalar lastFrameLength = Now() - beginFrameTimestamp;
|
||||
//sprintf(str, "RPL4 - %.2f FPS", 1.0f / lastFrameLength);
|
||||
|
||||
@@ -690,6 +690,23 @@ void
|
||||
DEBUG_STREAM << "." << std::flush;
|
||||
}
|
||||
|
||||
//
|
||||
// The rate this gauge runs at, and which tier that puts it in. The
|
||||
// renderer walks a sixteen-step wheel and a gauge draws only on the
|
||||
// steps its rate names, so tier 4 is one turn of the wheel between
|
||||
// redraws - seconds, once a race has the passes down to a handful a
|
||||
// second. Without this the profile says how EXPENSIVE each gauge is
|
||||
// but not how RARELY it runs, and the second one is what makes a
|
||||
// display look stuck.
|
||||
//
|
||||
{
|
||||
char
|
||||
rate_buffer[32];
|
||||
|
||||
sprintf(rate_buffer, "%04x/t%d ", (unsigned) rate, DiscernTier());
|
||||
DEBUG_STREAM << rate_buffer << std::flush;
|
||||
}
|
||||
|
||||
if (profileCycles > 0)
|
||||
{
|
||||
Scalar
|
||||
|
||||
@@ -3697,6 +3697,60 @@ Logical
|
||||
Logical
|
||||
result;
|
||||
|
||||
//
|
||||
// RP412GAUGEPROFILE=<seconds> - dump the gauge profile on that
|
||||
// cadence. Off unless set.
|
||||
//
|
||||
// ProfileReport already exists and PROFILE_GAUGES is already on, so
|
||||
// the numbers are being collected whether anyone looks or not. It was
|
||||
// only reachable from F11 through the RIO controls mapper, which is
|
||||
// not the mapper a desktop player is running - so on PAD;KEYBOARD it
|
||||
// could not be reached at all. This gives it a way out.
|
||||
//
|
||||
// It reports every gauge with its rate, its tier, how many times it
|
||||
// ran and what it cost, then clears - so each dump covers the
|
||||
// interval since the last one rather than all of history.
|
||||
//
|
||||
{
|
||||
static long
|
||||
profileInterval = -1L;
|
||||
|
||||
if (profileInterval < 0L)
|
||||
{
|
||||
const char
|
||||
*setting = getenv("RP412GAUGEPROFILE");
|
||||
|
||||
profileInterval = (setting != NULL) ? atol(setting) : 0L;
|
||||
if (profileInterval < 0L)
|
||||
{
|
||||
profileInterval = 0L;
|
||||
}
|
||||
}
|
||||
if (profileInterval > 0L)
|
||||
{
|
||||
static Logical
|
||||
profileScheduled = False;
|
||||
static Time
|
||||
profileDue;
|
||||
|
||||
Time
|
||||
profileNow = Now();
|
||||
|
||||
if (!profileScheduled)
|
||||
{
|
||||
profileScheduled = True;
|
||||
profileDue = profileNow;
|
||||
profileDue += profileInterval * 1000L;
|
||||
}
|
||||
else if (profileDue < profileNow)
|
||||
{
|
||||
profileDue = profileNow;
|
||||
profileDue += profileInterval * 1000L;
|
||||
ProfileReport();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Time start, end;
|
||||
int oldTaskMode = taskMode;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user