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:
+133
-3
@@ -31,6 +31,54 @@ using namespace std;
|
||||
|
||||
LPDIRECT3D9 gD3D = NULL;
|
||||
|
||||
//
|
||||
//#############################################################################
|
||||
// RPPresentationInterval
|
||||
//#############################################################################
|
||||
//
|
||||
// Every device in this game is created vsync-locked, and on a machine with
|
||||
// a spare 23 cores that is the most expensive line in the build.
|
||||
//
|
||||
// The game is one thread: simulation, 3D, gauge drawing and the display
|
||||
// copies all take turns on it. The frame loop runs the foreground, then
|
||||
// spends whatever is LEFT of the frame on the background gauge work. A
|
||||
// Present that blocks until the panel's next retrace spends that remainder
|
||||
// doing nothing at all - and the gauge loop, guaranteed only a single step
|
||||
// per frame, gets exactly that single step. A pass over the gauge list
|
||||
// needs about twenty, so the cockpit falls to two passes a second and every
|
||||
// slow-tier instrument sits seconds behind.
|
||||
//
|
||||
// That is why lowering TARGETFPS "fixed" the instruments: it did not make
|
||||
// anything faster, it just made the frame long enough that there was time
|
||||
// left over after the wait.
|
||||
//
|
||||
// So this is a knob. 0 = IMMEDIATE, Present returns and the leftover frame
|
||||
// time goes to the gauges where it belongs. Tearing is the cost, and on a
|
||||
// pod cockpit whose instruments are the point, it is a cheap one.
|
||||
//
|
||||
DWORD
|
||||
RPPresentationInterval()
|
||||
{
|
||||
static DWORD
|
||||
interval = 0xFFFFFFFF;
|
||||
|
||||
if (interval == 0xFFFFFFFF)
|
||||
{
|
||||
const char
|
||||
*setting = getenv("RP412VSYNC");
|
||||
|
||||
interval = (setting != NULL && atoi(setting) == 0)
|
||||
? D3DPRESENT_INTERVAL_IMMEDIATE
|
||||
: D3DPRESENT_INTERVAL_DEFAULT;
|
||||
|
||||
DEBUG_STREAM << "Video: presentation interval "
|
||||
<< ((interval == D3DPRESENT_INTERVAL_IMMEDIATE)
|
||||
? "IMMEDIATE (RP412VSYNC=0)" : "vsync")
|
||||
<< "\n" << std::flush;
|
||||
}
|
||||
return interval;
|
||||
}
|
||||
|
||||
// Single-window cockpit: viewscreen child window the scene presents into
|
||||
// (NULL = present to the device window as always).
|
||||
HWND gMainPresentWindow = NULL;
|
||||
@@ -1690,7 +1738,7 @@ DPLRenderer::DPLRenderer(
|
||||
mPresentParams.hDeviceWindow = hWnd;
|
||||
mPresentParams.Flags = 0;
|
||||
mPresentParams.FullScreen_RefreshRateInHz = (fullscreen)?60:D3DPRESENT_RATE_DEFAULT;
|
||||
mPresentParams.PresentationInterval = D3DPRESENT_RATE_DEFAULT;
|
||||
mPresentParams.PresentationInterval = RPPresentationInterval();
|
||||
mPresentParams.BackBufferFormat = D3DFMT_X8R8G8B8;
|
||||
mPresentParams.EnableAutoDepthStencil = TRUE;
|
||||
mPresentParams.AutoDepthStencilFormat = D3DFMT_D24X8;
|
||||
@@ -1762,10 +1810,50 @@ DPLRenderer::DPLRenderer(
|
||||
return;
|
||||
}
|
||||
|
||||
V(gD3D->CreateDevice(*mPrimaryIndex, D3DDEVTYPE_HAL, hWnd, D3DCREATE_SOFTWARE_VERTEXPROCESSING, &mPresentParams, &mDevice));
|
||||
//
|
||||
// RP412VERTEXPROC=hw asks the GPU to transform vertices instead of
|
||||
// this thread.
|
||||
//
|
||||
// The device has always been created 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. There is now, and
|
||||
// the foreground is spending 17 ms of an 18 ms frame while the gauge
|
||||
// loop starves on the 1 ms left over.
|
||||
//
|
||||
// On by default, and sw is the way back. Fixed-function T&L is 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.
|
||||
//
|
||||
DWORD vertex_processing = D3DCREATE_SOFTWARE_VERTEXPROCESSING;
|
||||
{
|
||||
const char *setting = getenv("RP412VERTEXPROC");
|
||||
if (setting == NULL || (*setting != 's' && *setting != 'S'))
|
||||
{
|
||||
D3DCAPS9 caps;
|
||||
if (SUCCEEDED(gD3D->GetDeviceCaps(*mPrimaryIndex, D3DDEVTYPE_HAL, &caps))
|
||||
&& (caps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT) != 0)
|
||||
{
|
||||
vertex_processing = D3DCREATE_HARDWARE_VERTEXPROCESSING;
|
||||
}
|
||||
else
|
||||
{
|
||||
DEBUG_STREAM << "Video: adapter has no hardware T&L - "
|
||||
<< "staying on software vertex processing\n" << std::flush;
|
||||
}
|
||||
}
|
||||
DEBUG_STREAM << "Video: vertex processing "
|
||||
<< ((vertex_processing == D3DCREATE_HARDWARE_VERTEXPROCESSING)
|
||||
? "HARDWARE" : "software (RP412VERTEXPROC=sw)")
|
||||
<< "\n" << std::flush;
|
||||
}
|
||||
|
||||
V(gD3D->CreateDevice(*mPrimaryIndex, D3DDEVTYPE_HAL, hWnd, vertex_processing, &mPresentParams, &mDevice));
|
||||
if (FAILED(hr))
|
||||
{
|
||||
DEBUG_STREAM<<"Couldn't create HARDWARE_VERTEXPROCESSING device."<<std::endl<<std::flush;
|
||||
DEBUG_STREAM<<"Couldn't create the requested device - falling back to software vertex processing."<<std::endl<<std::flush;
|
||||
|
||||
V(gD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd, D3DCREATE_SOFTWARE_VERTEXPROCESSING, &mPresentParams, &mDevice));
|
||||
}
|
||||
@@ -6472,6 +6560,48 @@ void DPLRenderer::ExecuteImplementation(RendererComplexity, RendererOrigin::Inte
|
||||
|
||||
hr = mDevice->Present(NULL, NULL, gMainPresentWindow, NULL);
|
||||
|
||||
//
|
||||
// RP412GAUGEDIAG=1: frames per second, on the same 2-second window the
|
||||
// display-sweep line uses so the two read side by side.
|
||||
//
|
||||
// Without this the gauge rate has to be argued about rather than
|
||||
// measured. A sweep rate far below the frame rate means the gauges are
|
||||
// STARVED - the 3D is fine and the background loop is not getting
|
||||
// through its cycle. A sweep rate that tracks the frame rate means
|
||||
// there is nothing wrong with the gauges at all and the frame itself
|
||||
// is the problem. Those two want opposite fixes, and the sweep line
|
||||
// alone cannot tell them apart.
|
||||
//
|
||||
{
|
||||
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 frames = 0;
|
||||
unsigned long now = GetTickCount();
|
||||
++frames;
|
||||
if (window_start == 0)
|
||||
{
|
||||
window_start = now;
|
||||
}
|
||||
else if (now - window_start >= 2000)
|
||||
{
|
||||
int tenths = frames * 10000 / (int)(now - window_start);
|
||||
DEBUG_STREAM << "FrameDiag: " << frames << " frame(s) in "
|
||||
<< (now - window_start) << " ms ("
|
||||
<< (tenths / 10) << '.' << (tenths % 10) << "/s)\n"
|
||||
<< std::flush;
|
||||
window_start = now;
|
||||
frames = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// hand the whole target back
|
||||
if (mPresentationAspect > 0.0f)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user