Files
RP412/MUNGA_L4/L4VIDEO.h
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

654 lines
21 KiB
C++

#pragma once
#include "..\munga\vidrend.h"
#include "..\munga\rotation.h"
#include "..\munga\reticle.h"
#include "..\munga\graph2d.h"
#include "..\munga\simulate.h"
#include "l4vidrnd.h"
#include "..\munga\tree.h"
#include "..\munga\table.h"
#include "l4d3d.h"
#include <string>
#include <hash_map>
#include <vector>
using namespace std;
using namespace stdext;
//STUBBED: DPL RB 1/14/07
//once the stubs are removed, the following can be removed
#define char8 unsigned char
// RB 1/14/07
//#include <dpl\dpl.h>
//#include <dpl\dpl_2d.h>
#include <D3DX9.h>
#include <D3DX9math.h>
class NotationFile;
// Single-window cockpit (L4MFDSPLIT): when non-NULL, the main 3D scene
// presents into this child "viewscreen" window instead of stretching to
// the device window. Set by SVGA16 when it assembles the cockpit.
extern HWND gMainPresentWindow;
#define INTERSECT_ALL (0xFFFFFFFF)
//##########################################################################
// Declaration of dpl callback function that performs in-place text
// substitution on all material names before they are looked up.
// There must be at least MATERIAL_NAME_LENGTH bytes available at source!
//
#define MATERIAL_NAME_BUFFER_LENGTH 256
char*
substituteMaterial(char *source);
void loadTables();
const char* opMaterialName(const char *fileName, int opId);
struct ReplacementMaterialData
{
float r, g, b;
string texName;
};
hash_map<string, hash_map<int, string>> *gOpNames;
hash_map<string, ReplacementMaterialData> *gReplacementData;
//##########################################################################
// This structure is attached to DPL nodes so we can put information in them
// on what entity the node is part of and what damage zone it belongs to.
//
#define MAX_DZ_NAME_LENGTH 25
struct dpl_tracker
{
Entity
*This_Entity; // The entity this is part of
char
dz_name[MAX_DZ_NAME_LENGTH]; // this is temporary, to make testing easier
int
Damage_Zone_Number; // number of this damage zone
};
//-----------------------------------------------------------------------------
//--------------------------DPL video resource object--------------------------
//-----------------------------------------------------------------------------
//##########################################################################
//########################## L4VideoObject ###########################
//##########################################################################
#define MAX_OBJECT_FILENAME_LENGTH 15
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// This class gets stored in a resource so it is derived from nothing.
//
class L4VideoObject
{
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Construction, Destruction and testing
//
public:
enum ResourceType
{
Unknown,
Skeleton,
Object,
Rubble,
VidFile
};
enum RendererModes // bit flags
{
Normal = 0x000,
BillboardXAxis = 0x001,
BillboardYAxis = 0x002,
BillboardZAxis = 0x004,
BillboardObject = (BillboardXAxis | BillboardYAxis | BillboardZAxis),
BlinkObject = 0x008,
IntersectImmune = 0x010
};
// L4VideoObject();
L4VideoObject(
const char *filename,
ResourceType resource_type,
Enumeration renderer_modes, // RendererModes
float blink_period = 0.0f,
float percent_time_on = 0.0f
);
~L4VideoObject();
const char*
GetObjectFilename() const
{ return objectFilename; }
char*
GetObjectFilename()
{ return objectFilename; }
ResourceType
GetResourceType() const
{ return resourceType; }
Enumeration // RendererModes
GetRendererModes() const
{ return rendererModes; }
Logical
TestInstance() const;
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Implementations
//
// private:
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Private data
//
private:
char
objectFilename[MAX_OBJECT_FILENAME_LENGTH];
ResourceType
resourceType;
Enumeration // RendererModes
rendererModes; // bit flags (see RendererModes)
float
blinkPeriod, // cycle length in seconds
percentTimeOn; // percent of cycle on
};
//##########################################################################
//####################### L4VideoObjectWrapper #######################
//##########################################################################
class L4VideoObjectWrapper:
public Plug
{
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Construction, Destruction and testing
//
public:
// L4VideoObjectWrapper();
L4VideoObjectWrapper(
L4VideoObject *video_object,
Logical delete_object = False
);
~L4VideoObjectWrapper();
L4VideoObject*
GetVideoObject() const
{ Check(this); return videoObject; }
Logical
TestInstance() const;
static int
BuildVideoObjectChainFromResource(
ChainOf<L4VideoObjectWrapper*> *video_chain,
ResourceDescription *video_resource
);
static void
DeleteVideoObjectChain(
ChainOf<L4VideoObjectWrapper*> *video_chain
);
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Implementations
//
// public:
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Private data
//
private:
L4VideoObject
*videoObject;
Logical
deleteObject;
};
//
//-----------------------------------------------------------------------------
//--------------Actual DPL video renderer class starts below-------------------
//-----------------------------------------------------------------------------
//
class DPLObjectCacheLine:
public Plug
{
public:
DPLObjectCacheLine(
const CString &object_name,
dpl_OBJECT *object_ptr)
{
objectName = object_name;
objectPointer = object_ptr;
};
~DPLObjectCacheLine(){};
Logical
TestInstance() const
{
Check(&objectName);
Check_Pointer(objectPointer);
return True;
};
CString
objectName;
dpl_OBJECT
*objectPointer;
};
class DPLJointToDCSTranslator:
public Plug
{
public:
DPLJointToDCSTranslator(
Entity *entity, // The entity to translate
dpl_DCS *dcs_array[]); // Array of DCS's to translate
~DPLJointToDCSTranslator();
Logical
TestInstance() const;
int
DCSCount;
dpl_DCS
**translation_array; // array to translate from joint # to DCS*
};
//##########################################################################
//########################### DPLRenderer ############################
//##########################################################################
#define DELAY_DCS_FLUSH_ARRAY_SIZE 100
#define MAX_PSFX_COUNT 40
#define MAX_INDIE_EMITTERS 32
class DPLRenderer :
public VideoRenderer
{
public:
//
// Construction, Destruction and test instance declarations
//
DPLRenderer(HWND hWnd, unsigned int screenWidth, unsigned int screenHeight, bool fullscreen, InterestType interest_type, InterestDepth depth_calibration);
~DPLRenderer();
virtual vector<MONITORINFO> MonitorsCreateAll(int &monitorCount);
Logical TestInstance() const;
//
// These routines are the renderer side of the culling system, they are
// used to setup the stored data on the eyepoint's transformation matrices
//
inline Scalar GetCurrentFrameTime() { return(currentFrameTime); } // Returns the time at the start of the frame
inline LinearMatrix* GetWorldToEyeMatrix() { return(&worldToEyeMatrix); } // Returns the world to eye matrix (for culling)
inline Scalar GetViewRatio() { return(viewRatio); } // Returns tan(viewAngle/2.0)
void SetupCull(); // Sets up the WorldToEyeMatrix each frame
//
// These routines manage the renderer's list of renderables that need to be
// executed each frame. For the moment these routines just blindly add and
// remove the items from the dplRenderableSocket
//
void AddDynamicRenderable(Component *my_renderable);
void RemoveDynamicRenderable(Component *my_renderable);
//
// These routines are used for tracking target frame time
//
void ResetStatistics();
void ReportStatistics();
//
// Other renderer stuff
//
dpl_ZONE* MakeNewZone();
void MarkDCSHiearchy(dpl_DCS *root_DCS, Entity *entity);
void FlushBitSliceTexture(unsigned int *local_storage);
void LoadBitSliceTexture(BitMap *bitmap_to_load, LPDIRECT3DTEXTURE9 local_storage);
void SetViewAngle(Degree new_angle);
//------------------------------------------------------------------
// Winners Circle presentation camera.
//
// The eye normally rides the viewpoint entity - your own vehicle -
// and DPLEyeRenderable writes D3DTS_VIEW from it each frame. For the
// podium the shot has to come from off the stand looking back, with
// every racer including you in frame, so this overrides that view
// for as long as it is set. Culling still runs from the viewpoint
// entity, which is standing on the podium, so the stand and everyone
// on it stay resident.
//
// Eye and target are world space. ClearPresentationCamera() hands
// the view back to the entity.
//------------------------------------------------------------------
void SetPresentationCamera(const Point3D &eye, const Point3D &look_at);
void ClearPresentationCamera();
//------------------------------------------------------------------
// True once the Winners Circle has taken the screen. The end-of-
// mission fade-to-black checks this and stands down: it multiplies
// the fog to black over FADE_OUT_TIME, which would otherwise black
// out the podium no matter what is drawn.
//------------------------------------------------------------------
Logical InPresentation() const { return mPresentationFog; }
//------------------------------------------------------------------
// Bring the presentation up out of black over the given seconds. The
// race's own fade-to-black has already run by this point; this is
// its mirror image, ramping the fog colour and both fog distances
// back up from nothing to what the Winners Circle asked for.
//------------------------------------------------------------------
void StartPresentationFadeIn(Scalar seconds);
//------------------------------------------------------------------
// Rebuild the viewpoint entity - the pilot's own vehicle - as an
// exterior. It is normally built insideEntity, which is a cockpit
// and no hull, so from any camera that is not in it you see nothing
// where your own vehicle should be. Disconnected_Eye is the engine's
// own switch for this: "so higher level renderers can fix the eye in
// one spot and watch the viewpoint entity drive around."
//------------------------------------------------------------------
void ShowViewpointFromOutside();
unsigned int* MakeBitSliceStorage();
void SortAndReloadNameBitmaps();
// re-point the Winners Circle sign faces at the current callsign
// textures; the plates live in the consolidated static mesh
void BindNamePlates();
void LoadNameBitmaps();
void LoadOrdinalBitmaps();
void DPLIndependantEffect(Point3D location, // Location of the effect in world space
int effect_number, // DPL effect number to trigger at this location
dpl_DCS *my_dcs = NULL,
int subid = 0); // inserted into third byte of effect id.
void DPLIndependantPFX(Point3D location, // Location in space to trigger the effect
dpl_PARTICLESTART_EFFECT_INFO *psfx_definition, // Description of the pfx
dpl_DCS *my_dcs = NULL, // optional DCS to issue from
int subid = 0); // inserted into third byte of effect id.
void DPLDelayDCSFlush(dpl_DCS *my_dcs); // The DCS we want to remember for later
void DPLDoDCSBatchFlush(); // Flush the dcs's remembered by DPLDelayDCSFlush
void DPLReportFreeMemory(std::ostream &output); // Report current free memory in card
void DPLReportPerfStats(std::ostream &output); // Report performance statistics
void DPLToggleWireframe(); // Toggle state of dpl wireframe
void DPLTogglePVision(); // Toggle state of dpl pvision
void DPLFrameDump(Logical antialias); // Write framedump to targa file
InnerProjectileRenderable* GetProjectile(
d3d_OBJECT *graphical_object, // object to hang on the DCS, may be a list later <NULL>
bool isDeathZone); // DPL Zone this stuff will live in (for culling)
void ReleaseProjectile(InnerProjectileRenderable *inner_projectile);
int GetUniqueID();
dpl_PARTICLESTART_EFFECT_INFO* ReadPSFX(const char *file_name); // Name of the file containing the PSFX description
dpl_OBJECT* GetCachedObject(const CString &object_name); // Name of the object we want to get
void PutCachedObject(
const CString &object_name, // Name of the object we will cache
dpl_OBJECT *object_pointer); // pointer to the object being cached
static ResourceDescription::ResourceID CreateModelVideoStreamResource(ResourceFile *resource_file, const char *model_name, NotationFile *model_file, const ResourceDirectories *directories);
void CacheExplosionScripts(int script_select); // The script to be cached
enum FogStyle
{
updateFogSetting,
noUpdateFogSetting,
searchLightOnFogStyle,
searchLightOffFogStyle,
winnersCircleFogStyle
};
void SetFogStyle(FogStyle my_fog);
void GetCurrentFogSettings(float *fogRed, float *fogGreen, float *fogBlue, float *fogNear, float *fogFar);
void SetCurrentFogLimits(float fogNear, float fogFar);
LPDIRECT3DTEXTURE9 GetNameTexture(int playerIndex) { return mNameTextures[playerIndex]; }
inline LPDIRECT3DDEVICE9 GetDevice() { return mDevice; }
inline LPD3DXMATRIXSTACK GetMatrixStack() { return m_MatrixStack; }
inline void AddRenderable(HierarchicalDrawComponent *component) { mRenderables.Add(component); }
void AddToPassList(d3d_OBJECT *object, int pass);
void AddStaticObject(d3d_OBJECT *object);
void RecurseStaticObject(HierarchicalDrawComponent *obj);
virtual void ConsolidateStaticObjects();
inline unsigned int GetWidth() { return x_size; }
inline unsigned int GetHeight() { return y_size; }
inline int *GetSecondaryIndex() { return mSecondaryIndex; }
inline int *GetAux1Index() { return mAux1Index; }
inline int *GetAux2Index() { return mAux2Index; }
private:
d3d_OBJECT *ConsolidateSingleObject(LPD3DXMESH *meshes, D3DXMATRIX *transforms, UINT startMesh, UINT meshCount, hash_map<DWORD, hyper> subsetHash, hash_map<hyper, DWORD> hashToOp, vector<L4DRAWOP*> finalOps);
void ExplosionScripts(Entity *entity, // The entity we are dealing with
ResourceDescription *model_resource, // Pointer to the video resource
ViewFrom view_type, // Type of reference (inside/outside...etc.)
int script_select);
void DPLRenderer::DPLReadINIPage(NotationFile *master_notation_file, const char *starting_page_name, Mission *mission, Logical debug_print);
void DPLRenderer::DPLReadEnvironment(Mission *mission);
void ShutdownImplementation();
void SuspendImplementation();
void ResumeImplementation();
void ExecuteImplementation(RendererComplexity complexity_update, RendererOrigin::InterestingEntityIterator *iterator);
void ExecuteIdle();
hyper HashAdd(hyper input, char data);
hyper HashDrawOp(L4DRAWOP *op);
//Damn eye renderable is our camera and I hate it
public:
DPLEyeRenderable *mCamera;
float GetCloudRed() { return mCloudRed;}
float GetCloudGreen() { return mCloudGreen;}
float GetCloudBlue() { return mCloudBlue;}
float GetCloudEmitRed() { return mCloudEmitRed; }
float GetCloudEmitGreen() { return mCloudEmitGreen; }
float GetCloudEmitBlue() { return mCloudEmitBlue; }
void SetCoreRenderStates();
private:
//
// Direct3D Required Variables
//
LPDIRECT3DDEVICE9 mDevice;
D3DPRESENT_PARAMETERS mPresentParams;
unsigned int x_size;
unsigned int y_size;
SChainOf<HierarchicalDrawComponent *> mRenderables;
d3d_OBJECT *mRenderLists[PASS_TOTAL_COUNT];
LPD3DXMATRIXSTACK m_MatrixStack;
D3DXMATRIX mProjectionMatrix;
D3DXMATRIX mDecalProjectionMatrix;
float mDecalEpsilon;
// Winners Circle: overrides D3DTS_VIEW while set (see
// SetPresentationCamera)
D3DXMATRIX mPresentationView;
Logical mPresentationCamera;
Logical mPresentationFog;
// >0 pillarboxes the scene to this aspect. The Winners Circle was
// built to be looked at from a 4:3 pod monitor and its platform runs
// out at the sides of a 16:9 canvas, so the shot is cropped to the
// shape it was composed for and the surround left black.
float mPresentationAspect;
// fade-in: end time, duration, and the fog it is ramping toward
Time mPresentationFadeEnd;
Scalar mPresentationFadeTime;
float mPresentationFogRed, mPresentationFogGreen, mPresentationFogBlue;
float mPresentationFogNear, mPresentationFogFar;
void FindBestAdapterIndices(bool isWindowed);
float mCloudRed, mCloudGreen, mCloudBlue;
float mCloudEmitRed, mCloudEmitGreen, mCloudEmitBlue;
int *mPrimaryIndex, *mSecondaryIndex, *mAux1Index, *mAux2Index;
// these are used for the map rendering
d3d_OBJECT *mStaticObjectsHead;
int mStaticObjectsCount;
std::list<d3d_OBJECT *> mConsolidatedStaticObjects;
// name and ordinal texture maps
LPDIRECT3DTEXTURE9 mNameTextures[MAX_PLAYER_NAMES];
LPDIRECT3DTEXTURE9 mOrdinalTextures[MAX_PLAYER_NAMES];
protected:
ReticleRenderable *mReticle;
CameraShipHUDRenderable *mCamShipHUD;
void LoadMissionImplementation(Mission *mission);
//
// These variables hold some culling related information
//
LinearMatrix worldToEyeMatrix; // the current world to eye transform for our linked entity
Scalar currentFrameTime; // the time at the start of renderable execution
//
// These variables are used for tracking target frame time
//
unsigned long total_cull,
total_draw,
total_pixelplanes,
total_frame_time,
target_frame_time,
frame_count,
target_frame_count;
Logical statistics_started;
Scalar report_time;
//
// The rest of the renderer's variables.
//
Time StartSample; // For generating a framerate printout
Logical fogUpdating,
eyeRelative, // True if the eye will be relative to the linked entity origin
completeCycleNeeded; // True when we are waiting for DPL to be ready for the next frame
int myUniqueID, // Generates a unique 16 bit id value for PSFX use
lastAppState; // For keeping track of what the application is doing
float aspectRatio, // aspect ratio of the screen
FrameCount, // For keeping track of framerate
// Fog is setup as standard fog used for vehicles without simulated lights
// and used when a vehicle with lights has them switched on. This setting
// is replicated into the "noSearchLight" setting so vehicles with lights
// will always get this setting if the environment doesn't specifically say
// that headlight simulation will work.
fogRed, // Fog setting to use when vehicle lights are on
fogBlue, // This is the default for vehicles with no lights
fogGreen,
fogNear,
fogFar,
currentFogNear,
currentFogFar,
searchLightFogRed, // Fog setting to use when vehicle lights are on
searchLightFogBlue,
searchLightFogGreen,
searchLightFogNear,
searchLightFogFar,
noSearchLightFogRed, // Fog setting to use when vehicle lights are off
noSearchLightFogBlue,
noSearchLightFogGreen,
noSearchLightFogNear,
noSearchLightFogFar,
clipNear, // Near clipping plane (from INI file)
clipFar, // Far clipping plane (from INI file)
backgroundRed, // Background color (from INI file)
backgroundGreen,
backgroundBlue,
viewAngle, // Viewing angle (from INI file)
viewRatio; // This is tan(viewAngle/2.0f) (handy for culling)
dpl_DCS *dplTestEyeDCS; // A DCS we temporarily hook our eye on (for testing)
void MakeEntityRenderables(Entity *this_entity, // The entity we are dealing with
ResourceDescription *model_resource, // Pointer to the video resource
ViewFrom type); // Type of reference (inside/outside...etc.)
dpl_VIEW *dplMainView; // The DPL view we create at the beginning and use for our eye
dpl_ZONE *dplDeathZone, // The DPL zone that holds our vehicle (for death effect)
*dplMainZone; // The DPL zone that holds the rest of the world
Reticle *vehicleReticle; // Pointer to our vehicle's reticle if one exists.
dpl_DCS *delayDCSFlushArray[DELAY_DCS_FLUSH_ARRAY_SIZE+1];
int delayedDCSCount;
INDIE_EFFECT myPSFXDescriptons[MAX_PSFX_COUNT];
ParticleEmitter myPSFXEmitters[MAX_INDIE_EMITTERS];
//
// This information is captured from the pickpoint when it is enabled
//
dpl_INSTANCE *dplHitInstance;
dpl_DCS *dplHitDCS;
dpl_GEOGROUP *dplHitGeoGroup;
dpl_GEOMETRY *dplHitGeometry;
//
// This information holds the list of projectile renderables
//
SChainOf<InnerProjectileRenderable*> projectile_list;
//
// This holds the list of cached objects
//
TreeOf<DPLObjectCacheLine*, CString> dplObjectCacheSocket;
//
// This holds the list of jointed mover DCS tables
//
TableOf<DPLJointToDCSTranslator*, RegisteredClassMemoryAddress> dplJointToDCSTranslatorSocket;
//
// This chain contains the list of active renderables
//
SChainOf<Component*> dplRenderableSocket;
public:
//
// Store Lights for Later Revision
//
// dpl_LIGHT *ambientLight;
D3DLIGHT9 *sceneLight; // Array of all the lights
dpl_DCS **sceneLightDCS; // Array of DCS's for Lights
int sceneLightCount;
//static UserHeap *DPLHeap;
};
extern LPDIRECT3D9 gD3D;
//
// The presentation interval every device is created with. RP412VSYNC=0
// makes it IMMEDIATE, so Present returns instead of waiting for the
// panel's retrace - see the definition in L4VIDEO.cpp for why that
// matters far more here than tearing does.
//
DWORD RPPresentationInterval();