A crosshair off-centre on the second race, and underneath it every race after the first was a different race. Windowed, BackBufferWidth/Height were left at zero, so D3D sized the back buffer to the device window's client area at the moment the device was created. Everything downstream is built from the size we ASKED for instead - the projection matrix takes its aspect from it, the reticle is centred on it - so a window that was not exactly that size rendered at the wrong shape and got rescaled on the way to the viewscreen pane. -fit decided which window that was, and it decided differently for the first mission than for the rest. Its borderless full-monitor placement lived only in SVGA16's cockpit build, which does not run until a mission starts - just after that mission has built its device. So race one was set up against a still-bordered client and every race after it against the borderless monitor. On a 3440x1440 panel that is a 1.778 image drawn across a 2.389 target, against 1.816 the first time. That is not a cosmetic difference. The simulation advances on wall-clock deltas, so frame cost is physics: two render targets that size and scale differently are two different races from one lobby and one set of settings. A racing sim does not get to do that. So: the back buffer is the requested size windowed as well as full-screen, and -fit takes its shape at startup rather than four screens later. SVGA16 still applies the same rect when it builds the cockpit - that call is now a no-op instead of a change, which is the point. The first lobby also stops being the only one with a title bar. The reticle keeps its own share of the blame and is fixed on its own terms, so it cannot drift again if a target ever does move: - It is measured against the viewport at draw time and rebuilt when that changes, rather than baked once in the constructor from the renderer's requested size. One GetViewport a frame, no rewrite until it moves. - The arms are quads, not lines. D3D9 line rasterisation follows the diamond-exit rule and is free to differ between drivers on a segment running along a pixel boundary, which is how a crosshair loses one pair of arms and keeps the other - and full-screen, where both dimensions are usually even and both pairs sit on boundaries, how it can lose the lot. - Arm thickness follows the target rather than being one pixel whatever the resolution. One pixel is a width the presentation can throw away in a downscale, and it was a hairline at 1440 next to the pod's line at 480. The log names the viewport, the requested size and where the crosshair landed, and says TARGET DISAGREES with both aspects when the first two do not match - so the next report of this arrives with its own diagnosis. Window creation cleaned up while in there: it computed a style and then handed CreateWindowEx a literal WS_OVERLAPPEDWINDOW regardless, so the full-screen path never got the WS_POPUP it thought it was asking for. Borderless modes are now born borderless instead of being restyled a moment after. The requested size also goes through AdjustWindowRect, because -res is a render size and was being used as the OUTER rectangle with the chrome taken out of the middle - which is how -res 640 480 came to present into a 624x441 client and started all of this. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
252 lines
7.2 KiB
C++
252 lines
7.2 KiB
C++
//===========================================================================//
|
|
// File: l4app.hpp //
|
|
// Project: MUNGA Brick: L4Application //
|
|
// Contents: Interface specification for L4Application //
|
|
//---------------------------------------------------------------------------//
|
|
// Date Who Modification //
|
|
// -------- --- ---------------------------------------------------------- //
|
|
// 01/21/95 ECH Initial coding. //
|
|
//---------------------------------------------------------------------------//
|
|
// Copyright (C) 1994, Virtual World Entertainment, Inc. //
|
|
// All Rights reserved worldwide. //
|
|
// This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL //
|
|
//===========================================================================//
|
|
|
|
#pragma once
|
|
|
|
#include "..\munga\app.h"
|
|
#include "..\munga\APPMGR.h"
|
|
|
|
class DPLRenderer;
|
|
class L4AudioRenderer;
|
|
class L4GaugeRenderer;
|
|
class L4NetworkManager;
|
|
class LBE4ControlsManager;
|
|
class L4IcomManager;
|
|
|
|
//##########################################################################
|
|
//######################## L4Application #############################
|
|
//##########################################################################
|
|
|
|
class L4Application:
|
|
public Application
|
|
{
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Construction, Destruction, and Testing
|
|
//
|
|
public:
|
|
L4Application(
|
|
HINSTANCE hInstance,
|
|
HWND hWnd,
|
|
ResourceFile *resource_file,
|
|
ApplicationID application_ID,
|
|
ClassID class_ID=L4ApplicationClassID,
|
|
SharedData &shared_data=DefaultData
|
|
);
|
|
~L4Application();
|
|
|
|
Logical
|
|
TestInstance() const;
|
|
|
|
static Derivation *GetClassDerivations();
|
|
static SharedData DefaultData;
|
|
|
|
bool IsDead() {return mIsDead;}
|
|
void SetIsDead(bool isDead) {mIsDead = isDead;}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Command line parsing
|
|
//
|
|
public:
|
|
typedef Logical
|
|
(*TokenParser)(int *argument, int argc, LPWSTR argv[]);
|
|
static Logical
|
|
ParseToken(
|
|
int *argument,
|
|
int argc,
|
|
LPWSTR argv[]
|
|
);
|
|
static Logical
|
|
ParseCommandLine(
|
|
int argc,
|
|
LPWSTR argv[],
|
|
TokenParser parser = &L4Application::ParseToken
|
|
);
|
|
|
|
static int GetMissionReviewMode() { return missionReviewMode; }
|
|
static HINSTANCE GetAppInstance() { return mhInstance; }
|
|
static HWND GetMainWindow() { return ghWnd; }
|
|
static unsigned int GetScreenWidth() { return mScreenWidth; }
|
|
static unsigned int GetScreenHeight() { return mScreenHeight; }
|
|
static bool GetFullscreen() { return mFullscreen; }
|
|
// -fit: borderless window filling the monitor, with the render size
|
|
// chosen to match the cockpit canvas it will be presented into.
|
|
static bool GetFitDisplay() { return mFitDisplay; }
|
|
// -fit's borderless full-monitor placement. Applied once at startup so
|
|
// the window is in its final shape before ANY mission builds a device
|
|
// against it - see the definition for why the first race differed.
|
|
static void FitWindowToMonitor(HWND window);
|
|
static Logical GetSeeSolids() { return seeSolids; }
|
|
static unsigned long GetNetworkCommonFlatAddress() { return networkCommonFlatAddress; }
|
|
// The front end's multiplayer path turns network mode on at launch
|
|
// time (the owner pod meshes like any other pod); command-line -net
|
|
// keeps working exactly as before.
|
|
static void SetNetworkCommonFlatAddress(unsigned long address) { networkCommonFlatAddress = address; }
|
|
static CString GetEggNotationFileName() { return eggNotationFileName; }
|
|
// the in-game front end builds an egg and injects it here
|
|
static void SetEggNotationFileName(const char *name) { eggNotationFileName = name; }
|
|
static CString GetSpoolFileName() { return spoolFileName; }
|
|
static CString GetRIOPlaybackFileName() { return rioPlaybackFileName; }
|
|
static CString GetRIORecordingFileName() { return rioRecordingFileName; }
|
|
|
|
protected:
|
|
// Pick mScreenWidth/mScreenHeight from the monitor. Runs after the
|
|
// whole command line is parsed, so an explicit -res always wins no
|
|
// matter which side of -fit it appears on.
|
|
static void ChooseFitResolution();
|
|
|
|
static HINSTANCE mhInstance;
|
|
static unsigned int mScreenWidth;
|
|
static unsigned int mScreenHeight;
|
|
static bool mFullscreen;
|
|
static bool mFitDisplay;
|
|
static bool mResExplicit;
|
|
static Logical seeSolids;
|
|
static CString eggNotationFileName;
|
|
static CString spoolFileName;
|
|
static unsigned long networkCommonFlatAddress;
|
|
static int missionReviewMode;
|
|
static CString rioPlaybackFileName;
|
|
static CString rioRecordingFileName;
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Execution control
|
|
//
|
|
public:
|
|
void
|
|
Initialize();
|
|
void
|
|
InitializeTillConsole();
|
|
|
|
Entity*
|
|
MakeAndLinkViewpointEntity(Entity__MakeMessage *message);
|
|
|
|
Entity*
|
|
MakeViewpointEntity(Entity__MakeMessage *message);
|
|
|
|
void
|
|
Terminate();
|
|
|
|
void
|
|
TeslaCoil(Logical lights_on);
|
|
|
|
void
|
|
PilotIllumination(Logical lights_on); // includes 'Tesla coil'
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Module accesors
|
|
//
|
|
public:
|
|
DPLRenderer*
|
|
GetVideoRenderer();
|
|
|
|
L4AudioRenderer*
|
|
GetAudioRenderer();
|
|
|
|
L4GaugeRenderer*
|
|
GetGaugeRenderer();
|
|
|
|
L4NetworkManager*
|
|
GetNetworkManager()
|
|
{return (L4NetworkManager*)Application::GetNetworkManager();}
|
|
LBE4ControlsManager*
|
|
GetControlsManager()
|
|
{return (LBE4ControlsManager*)Application::GetControlsManager();}
|
|
L4IcomManager*
|
|
GetIntercomManager()
|
|
{return (L4IcomManager*)Application::GetIntercomManager();}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Public data
|
|
//
|
|
public:
|
|
char
|
|
*divisionParameters;
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Module Creation
|
|
//
|
|
protected:
|
|
ModeManager*
|
|
MakeModeManager();
|
|
|
|
ControlsManager*
|
|
MakeControlsManager();
|
|
|
|
IcomManager*
|
|
MakeIntercomManager();
|
|
|
|
NetworkManager*
|
|
MakeNetworkManager();
|
|
|
|
VideoRenderer*
|
|
MakeVideoRenderer();
|
|
|
|
AudioRenderer*
|
|
MakeAudioRenderer();
|
|
|
|
GaugeRenderer*
|
|
MakeGaugeRenderer(int *secondaryIndex, int *aux1Index, int *aux2Index);
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Message Support
|
|
//
|
|
public:
|
|
enum
|
|
{
|
|
LightsOutMessageID = Application::NextMessageID,
|
|
NextMessageID
|
|
};
|
|
|
|
static const HandlerEntry
|
|
MessageHandlerEntries[];
|
|
//static MessageHandlerSet MessageHandlers;
|
|
static MessageHandlerSet& GetMessageHandlers();
|
|
|
|
void
|
|
LightsOutMessageHandler(Receiver::Message *message);
|
|
void
|
|
RunMissionMessageHandler(RunMissionMessage *message);
|
|
void
|
|
StopMissionMessageHandler(StopMissionMessage *message);
|
|
void // HACK - the 'int' is actually 'ControlsKey'
|
|
KeyCommandMessageHandler(ReceiverDataMessageOf<int> *message);
|
|
|
|
private:
|
|
bool mIsDead;
|
|
};
|
|
|
|
extern L4Application * &l4_application;
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~ L4Application inlines ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
inline DPLRenderer*
|
|
L4Application::GetVideoRenderer()
|
|
{
|
|
Check(this);
|
|
return (DPLRenderer*)videoRenderer;
|
|
}
|
|
|
|
inline L4AudioRenderer*
|
|
L4Application::GetAudioRenderer()
|
|
{
|
|
Check(this);
|
|
return (L4AudioRenderer*)audioRenderer;
|
|
}
|
|
|
|
inline L4GaugeRenderer*
|
|
L4Application::GetGaugeRenderer()
|
|
{
|
|
Check(this);
|
|
return (L4GaugeRenderer*)gaugeRenderer;
|
|
} |