Files
RP412/MUNGA/GAUGE.cpp
T
CydandClaude Opus 5 b7b2c3b148 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>
2026-08-09 13:12:34 -05:00

797 lines
17 KiB
C++

#include "munga.h"
#pragma hdrstop
#include "gauge.h"
#include "gaugrend.h"
extern void
indent(int level); // in graph2d.cc
//#########################################################################
//############################# GaugeBase #################################
//#########################################################################
GaugeBase::GaugeBase(
ModeMask mode_mask,
GaugeRenderer *new_renderer,
unsigned int owner_ID,
const char *identification_string
) :
Node(Gauge::GaugeClassID),
alreadyActivatedFlag(False)
{
Check_Pointer(this);
modeMask = mode_mask;
ownerID = owner_ID;
renderer = new_renderer;
identificationString = identification_string;
stayInactive = True; // used by GaugeRenderer during activation
//---------------------------------------------------
// If renderer exists, add to object list
// (Not having a renderer allows for independent testing)
//---------------------------------------------------
if (renderer != NULL)
{
Check(renderer);
renderer->Add(this);
}
Check_Fpu();
}
GaugeBase::~GaugeBase()
{
Check(this);
Check_Fpu();
}
Logical
GaugeBase::TestInstance() const
{
return True;
}
void
GaugeBase::BecameActive()
{
Check(this);
//------------------------------------------------------
// Default method immediately inactivates the gaugeBase
//------------------------------------------------------
DEBUG_STREAM <<
"BecameActive() has not been defined for '" << identificationString <<
"'. About to call Inactivate()\n";
Inactivate();
Check_Fpu();
}
void
GaugeBase::BecameInactive()
{
Check(this);
Check_Fpu();
}
void
GaugeBase::Inactivate()
{
Check(this);
if (renderer != NULL)
{
Check(renderer);
renderer->Inactivate(this);
}
Check_Fpu();
}
Logical
GaugeBase::Update(GaugeRate /*rate_mask*/)
{
Check(this);
//------------------------------------------------------
// Default method immediately inactivates the gaugeBase
//------------------------------------------------------
DEBUG_STREAM <<
"Update() has not been defined for '" << identificationString <<
"'. About to call Inactivate()\n";
Inactivate();
Check_Fpu();
return False;
}
void
GaugeBase::UpdateProfile(Scalar /*frame_percentage*/)
{
Check(this);
Check_Fpu();
}
void
GaugeBase::ReportProfile()
{
Check(this);
Check_Fpu();
}
void
GaugeBase::LinkToEntity(Entity */*entity*/)
{
Check(this);
Check_Fpu();
}
void
GaugeBase::NotifyOfNewInterestingEntity(Entity */*entity*/)
{
Check(this);
Check_Fpu();
}
void
GaugeBase::NotifyOfBecomingUninterestingEntity(Entity */*entity*/)
{
Check(this);
Check_Fpu();
}
int
GaugeBase::DiscernTier()
{
Check(this);
Check_Fpu();
return 0;
}
//#########################################################################
//########################## GaugeBackground ##############################
//#########################################################################
GaugeBackground::GaugeBackground(
ModeMask mode_mask,
GaugeRenderer *new_renderer,
unsigned int owner_ID,
const char *identification_string
) :
GaugeBase(mode_mask, new_renderer, owner_ID, identification_string)
{
Check_Pointer(this);
Check_Fpu();
}
//#########################################################################
//######################## GraphicGaugeBackground #########################
//#########################################################################
GraphicGaugeBackground::GraphicGaugeBackground(
ModeMask mode_mask,
GaugeRenderer *renderer,
unsigned int owner_ID,
int graphics_port_number,
const char *identification_string
) :
localView(
renderer == NULL ?
NULL :
renderer->GetGraphicsPort(graphics_port_number)
),
GaugeBackground(mode_mask, renderer, owner_ID, identification_string)
{
Check_Fpu();
}
//#########################################################################
//########################## GaugeConnection ##############################
//#########################################################################
GaugeConnection::GaugeConnection(int parameter_id)
{
Check_Pointer(this);
parameterID = parameter_id;
Check_Fpu();
}
GaugeConnection::~GaugeConnection()
{
Check_Fpu();
}
Logical
GaugeConnection::TestInstance() const
{
Check_Pointer(this);
return True;
}
void GaugeConnection::Update()
{
Fail("GaugeConnection::Update should not be called!\n");
}
void
GaugeConnection::ShowInstance(char *indent)
{
Check(this);
std::cout << indent << "GaugeConnection:\n";
char
temp[80];
Str_Copy(temp,indent, 80);
Str_Cat(temp,"...", 80);
std::cout << temp << "parameterID=" << parameterID << "\n" << std::flush;
Check_Fpu();
}
//#########################################################################
//################################# Gauge #################################
//#########################################################################
//----------------------------------------------------------------------------
// GaugeRate masks are used to spread out workload in a priority-based
// manner. Each GaugeRate refers to the branch of a binary tree as shown:
//
// A = every frame
// /--------------^--------------\ :
// FIRST TIER: B C = 1/2
// /------^-------\ /------^-------\ :
// 2ND: D E F G = 1/4
// /--^---\ /--^---\ /--^---\ /--^---\ :
// H I J K L M N O = 1/8
// / \ / \ / \ / \ / \ / \ / \ / \ :
// P Q R S T U V W X Y Z Z0 Z1 Z2 Z3 Z4 = 1/16
//
// Each 'tier' down indicates an update rate half that of the one above it.
//
// Note that rates Z0..Z4 cannot be specified through the gauge renderer's
// interpreter. They are available to automatically generated gauges, though.
//----------------------------------------------------------------------------
GaugeRate
Gauge::rateTable[31] =
{
gaugeRate_A, // every frame
gaugeRate_B, // every other frame
gaugeRate_C,
gaugeRate_D, // every 4th frame
gaugeRate_E,
gaugeRate_F,
gaugeRate_G,
gaugeRate_H, // every 8th frame
gaugeRate_I,
gaugeRate_J,
gaugeRate_K,
gaugeRate_L,
gaugeRate_M,
gaugeRate_N,
gaugeRate_O,
gaugeRate_P, // every 16th frame
gaugeRate_Q,
gaugeRate_R,
gaugeRate_S,
gaugeRate_T,
gaugeRate_U,
gaugeRate_V,
gaugeRate_W,
gaugeRate_X,
gaugeRate_Y,
gaugeRate_Z,
gaugeRate_Z0,
gaugeRate_Z1,
gaugeRate_Z2,
gaugeRate_Z3,
gaugeRate_Z4
};
//-------------------------------------------------------------------
// Gauge::NextXXXTierGaugeRate()
// - These functions attempt to spead out workload by returning the
// next available 'GaugeRate' in a given 'tier'.
//
// The higher level tiers bump the lower level tier indices to
// attempt to avoid placing too much load on the same higher-order
// branch of the tree.
//-------------------------------------------------------------------
static int
first_index = -1,
second_index = -1,
third_index = -1,
fourth_index = -1;
GaugeRate
Gauge::NextFirstTierGaugeRate()
{
//----------------------------------------
// Generate first index
//----------------------------------------
first_index = (++first_index) & 1;
//----------------------------------------
// Bump other indices to avoid same branch
//----------------------------------------
second_index += 2;
third_index += 4;
fourth_index += 8;
//----------------------------------------
// Return rate mask
//----------------------------------------
return rateTable[first_index + 1];
}
GaugeRate
Gauge::NextSecondTierGaugeRate()
{
//----------------------------------------
// Generate first index
//----------------------------------------
second_index = (++second_index) & 3;
//----------------------------------------
// Bump other indices to avoid same branch
//----------------------------------------
third_index += 2;
fourth_index += 4;
//----------------------------------------
// Return rate mask
//----------------------------------------
return rateTable[second_index + 3];
}
GaugeRate
Gauge::NextThirdTierGaugeRate()
{
//----------------------------------------
// Generate first index
//----------------------------------------
third_index = (++third_index) & 7;
//----------------------------------------
// Bump other indices to avoid same branch
//----------------------------------------
fourth_index += 2;
//----------------------------------------
// Return rate mask
//----------------------------------------
return rateTable[third_index + 7];
}
GaugeRate
Gauge::NextFourthTierGaugeRate()
{
//----------------------------------------
// Generate first index
//----------------------------------------
fourth_index = (++fourth_index) & 15;
//----------------------------------------
// Return rate mask
//----------------------------------------
return rateTable[fourth_index + 15];
}
GaugeRate
Gauge::ConvertIndexToRate(int index)
{
Verify(index >= 0);
Verify(index < 32);
return rateTable[index];
}
Gauge::Gauge(
GaugeRate new_rate,
ModeMask mode_mask,
GaugeRenderer *renderer,
unsigned int owner_ID,
const char *identification_string
):
GaugeBase(mode_mask, renderer, owner_ID, identification_string),
instanceList(this)
{
Check_Pointer(this);
rate = new_rate;
oldRate = new_rate;
stayInactive = False;
profileFramePercentage = (Scalar) 0;
profileMaxFramePercentage = (Scalar) 0;
profileCycles = 0;
Check_Fpu();
}
Gauge::~Gauge()
{
Check(this);
RemoveAllConnections();
Check_Fpu();
}
Logical
Gauge::TestInstance() const
{
Check(&instanceList);
# if 0
//--------------------------------------------------------------------
// Check all owned GaugeConnections
//--------------------------------------------------------------------
ChainIteratorOf<GaugeConnection*>
i(instanceList);
GaugeConnection
*the_connection;
while ((the_connection=i.ReadAndNext()) != NULL)
{
Check(the_connection);
}
# endif
//--------------------------------------------------------------------
// Check the base object
//--------------------------------------------------------------------
return GaugeBase::TestInstance();
}
void
Gauge::ShowInstance(char *indent)
{
Check(this);
ChainIteratorOf<GaugeConnection*>
i(instanceList);
GaugeConnection
*the_connection;
std::cout << indent << "Gauge:\n";
char
temp[80];
Str_Copy(temp,indent, 80);
Str_Cat(temp,"...", 80);
std::cout << temp << "modeMask =" << modeMask << "\n";
std::cout << temp << "renderer =" << renderer << "\n";
std::cout << temp << "ownerID =" << ownerID << "\n";
std::cout << temp << "InstanceList -\n" << std::flush;
Str_Cat(temp,"...", 80);
while ((the_connection=i.ReadAndNext()) != NULL)
{
Check(the_connection);
the_connection->ShowInstance(temp);
}
Check_Fpu();
}
void
Gauge::AddConnection(GaugeConnection *connection)
{
Check(this);
Check(connection);
instanceList.Add(connection);
Check_Fpu();
}
void
Gauge::RemoveConnection(int parameter_ID)
{
Check(this);
ChainIteratorOf<GaugeConnection*>
i(instanceList);
GaugeConnection
*gauge_connection;
while ((gauge_connection=i.ReadAndNext()) != NULL)
{
Check(gauge_connection);
if (gauge_connection->parameterID == parameter_ID)
{
Unregister_Object(gauge_connection);
delete gauge_connection;
}
}
Check_Fpu();
}
void
Gauge::RemoveAllConnections()
{
Check(this);
ChainIteratorOf<GaugeConnection*>
i(instanceList);
GaugeConnection
*gauge_connection;
while ((gauge_connection=i.ReadAndNext()) != NULL)
{
Check(gauge_connection);
Unregister_Object(gauge_connection);
delete gauge_connection;
}
Check_Fpu();
}
void
Gauge::Disable(Logical disable_state)
{
Check(this);
if (disable_state)
{
rate = 0;
BecameInactive();
}
else
{
rate = oldRate;
BecameActive();
}
Check_Fpu();
}
Logical
Gauge::Update(GaugeRate rate_mask)
{
Check(this);
Logical
actually_ran = False;
//-----------------------------------------
// Update only if rate mask allows it
//-----------------------------------------
if (rate & rate_mask)
{
//-----------------------------------------
// Update parameters
//-----------------------------------------
ChainIteratorOf<GaugeConnection*>
i(instanceList);
GaugeConnection
*gauge_connection;
while ((gauge_connection=i.ReadAndNext()) != NULL)
{
Check(gauge_connection);
gauge_connection->Update();
}
//-----------------------------------------
// Execute gauge code
//-----------------------------------------
Execute();
actually_ran = True;
}
Check_Fpu();
return actually_ran;
}
void
Gauge::Execute()
{
Fail("Gauge::Execute not overridden");
}
struct TierLookup
{
GaugeRate rate;
int tier;
};
int
Gauge::DiscernTier()
{
Check(this);
TierLookup
*tier_pointer;
static TierLookup
tier_lookup[] =
{
{gaugeRate_A, 0 },
{gaugeRate_B, 1 },
{gaugeRate_C, 1 },
{gaugeRate_D, 2 },
{gaugeRate_E, 2 },
{gaugeRate_F, 2 },
{gaugeRate_G, 2 },
{gaugeRate_H, 3 },
{gaugeRate_I, 3 },
{gaugeRate_J, 3 },
{gaugeRate_K, 3 },
{gaugeRate_L, 3 },
{gaugeRate_M, 3 },
{gaugeRate_N, 3 },
{gaugeRate_O, 3 },
{gaugeRate_P, 4 },
{gaugeRate_Q, 4 },
{gaugeRate_R, 4 },
{gaugeRate_S, 4 },
{gaugeRate_T, 4 },
{gaugeRate_U, 4 },
{gaugeRate_V, 4 },
{gaugeRate_W, 4 },
{gaugeRate_X, 4 },
{gaugeRate_Y, 4 },
{gaugeRate_Z, 4 },
{gaugeRate_Z0,4 },
{gaugeRate_Z1,4 },
{gaugeRate_Z2,4 },
{gaugeRate_Z3,4 },
{gaugeRate_Z4,4 },
{0,0}
};
for (tier_pointer=tier_lookup; tier_pointer->rate != 0; ++tier_pointer)
{
if (tier_pointer->rate == rate)
{
Check_Fpu();
return tier_pointer->tier;
}
}
Check_Fpu();
return 0;
}
void
Gauge::UpdateProfile(Scalar frame_percentage)
{
Check(this);
//---------------------------------------------
// Keep worst case
//---------------------------------------------
if (profileMaxFramePercentage < frame_percentage)
{
profileMaxFramePercentage = frame_percentage;
}
//---------------------------------------------
// Keep running total
//---------------------------------------------
profileFramePercentage += frame_percentage;
//---------------------------------------------
// Prevent overflow
//---------------------------------------------
if (++profileCycles > 16384)
{
profileFramePercentage /= (Scalar) 2;
profileCycles >>= 1;
}
Check_Fpu();
}
void
Gauge::ReportProfile()
{
Check(this);
int
i;
DEBUG_STREAM << identificationString << std::flush;
for(i=strlen(identificationString); i<32; ++i)
{
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
average = profileFramePercentage / (Scalar)profileCycles;
char
buffer[80];
sprintf(buffer, "%6d %6.4f %6.4f\n",
profileCycles,
average,
profileMaxFramePercentage
);
DEBUG_STREAM << buffer << std::flush;
}
else
{
DEBUG_STREAM << "never ran.\n" << std::flush;
}
//----------------------------------------
// Reset maximum percentage
//----------------------------------------
profileMaxFramePercentage = (Scalar) 0;
Check_Fpu();
}
//#########################################################################
//############################# GraphicGauge #############################
//#########################################################################
GraphicGauge::GraphicGauge(
GaugeRate new_rate,
ModeMask mode_mask,
GaugeRenderer *renderer,
unsigned int owner_ID,
int graphics_port_number,
const char *identification_string
) :
Gauge(new_rate, mode_mask, renderer, owner_ID, identification_string),
localView(
renderer == NULL ?
NULL :
renderer->GetGraphicsPort(graphics_port_number)
)
{
Check_Fpu();
}
GraphicGauge::~GraphicGauge()
{
Check(this);
Check_Fpu();
}
Logical
GraphicGauge::TestInstance() const
{
Check(&localView);
Check_Fpu();
return Gauge::TestInstance();
}
void
GraphicGauge::ShowInstance(char *indent)
{
Check(this);
std::cout << indent << "GraphicGauge:\n";
char
temp[80];
Str_Copy(temp,indent, 80);
Str_Cat(temp,"...", 80);
localView.ShowInstance(temp);
Gauge::ShowInstance(temp);
Check_Fpu();
}
#if defined(TEST_CLASS)
# include "gauge.tcp"
#endif