gauges: secondary/radar MFD composites live on a dev box (Milestone B)
The pod's secondary/radar cockpit surface now renders as an inset in the 800x600 dev window under BT_DEV_GAUGES: radar/tactical grid, SPEED/HEADING/ MAGNETIC/PROP dials, and the color-coded ARMOR DAMAGE mech schematic -- real gauge content (nzSec=27247), composited from the shared CPU pixelBuffer. Composite pass (engine): SVGA16::DrawDevInset palette-expands the secondary plane into a MANAGED texture on the MAIN device and draws an XYZRHW inset quad; BTDrawGaugeInset() reaches the gauge renderer's 'sec' port and is called from DPLRenderer::ExecuteImplementation as the last draw before EndScene. Three reconstruction bugs fixed to get real content: 1. BTL4Application::MakeGaugeRenderer signature mismatch (REAL fix): the reconstructed no-arg override only HID the 2007-engine's widened 3-arg virtual MakeGaugeRenderer(int*,int*,int*), so the engine built a base L4GaugeRenderer whose ctor never parsed gauge/l4gauge.cfg -> empty symbol table -> "undefined label 'bhk1Init'" -> no ports/gauges. Matched the 3-arg signature so it truly overrides (args ignored; BT is fullscreen). Same bug class as the BTL4GaugeRenderer(false,NULL,NULL,NULL) ctor fix. 2. Parse hung on undefined primitives (gated dev accommodation): the BT gauge primitive table (BTL4MethodDescription) is still a stub, so an unknown primitive -> ReportParsingError -> Fail() -> a MODAL dialog freezing the parse. Under BT_DEV_GAUGES, skip the unknown primitive's params so labels register and the base "configure" primitive builds the ports. 3. Gauge widgets AV on NULL data bindings (gated): NumericDisplayScalar's NULL value_pointer -> GaugeConnectionDirectOf ctor deref (bind NULL->static zero); RankAndScore::Execute derefs unreconstructed game state (Gauge::GuardedExecute SEH wrapper -> Disable(True) on first fault). All guards gated on BT_DEV_GAUGES: default DEV un-regressed (TARGET DESTROYED, 0 crashes) and the pod path is byte-unchanged (strict Fail, no guards). Remaining (docs/GAUGE_COMPOSITE.md): the real gauge WIDGET reconstruction (BTL4MethodDescription method table + gauge->game-state data bindings), then Step 2 (RP<->BT MFD port-name reconcile) + Step 3 (2-window layout). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
c13e72d0ba
commit
a256813395
+39
-1
@@ -568,7 +568,24 @@ Logical
|
||||
//-----------------------------------------
|
||||
// Execute gauge code
|
||||
//-----------------------------------------
|
||||
Execute();
|
||||
{
|
||||
// BT DEV-GAUGES bring-up: run Execute() under SEH so a gauge whose
|
||||
// game-state binding isn't reconstructed yet is DISABLED (rate=0)
|
||||
// after its first fault instead of AV'ing every frame. Gated on
|
||||
// BT_DEV_GAUGES; the pod build calls Execute() directly.
|
||||
static int s_devGaugesUpd = -1;
|
||||
if (s_devGaugesUpd < 0)
|
||||
s_devGaugesUpd = (getenv("BT_DEV_GAUGES") != NULL) ? 1 : 0;
|
||||
if (s_devGaugesUpd)
|
||||
{
|
||||
if (!GuardedExecute())
|
||||
Disable(True);
|
||||
}
|
||||
else
|
||||
{
|
||||
Execute();
|
||||
}
|
||||
}
|
||||
actually_ran = True;
|
||||
}
|
||||
Check_Fpu();
|
||||
@@ -581,6 +598,27 @@ void
|
||||
Fail("Gauge::Execute not overridden");
|
||||
}
|
||||
|
||||
//
|
||||
// GuardedExecute -- BT DEV-GAUGES bring-up. Run Execute() under a structured
|
||||
// exception handler so a gauge whose game-state data binding is not yet
|
||||
// reconstructed is skipped instead of AV'ing the whole game. (A separate
|
||||
// function because __try/__except may not share a frame with C++ objects that
|
||||
// require unwinding, which Gauge::Update does.) Returns False on fault.
|
||||
//
|
||||
Logical
|
||||
Gauge::GuardedExecute()
|
||||
{
|
||||
__try
|
||||
{
|
||||
Execute();
|
||||
return True;
|
||||
}
|
||||
__except (EXCEPTION_EXECUTE_HANDLER)
|
||||
{
|
||||
return False;
|
||||
}
|
||||
}
|
||||
|
||||
struct TierLookup
|
||||
{
|
||||
GaugeRate rate;
|
||||
|
||||
@@ -193,6 +193,26 @@ template <class T> GaugeConnectionDirectOf<T>::GaugeConnectionDirectOf(
|
||||
{
|
||||
Check_Pointer(this);
|
||||
Check_Pointer(data_destination);
|
||||
|
||||
//
|
||||
// BT DEV-GAUGES bring-up: some gauge widgets bind to mech data sources that
|
||||
// are not reconstructed yet, so the attribute resolves to a NULL data_source
|
||||
// (see the "(Scalar *) is VERY dangerous!" note in L4GAUGE.cpp). Rather than
|
||||
// AV here (and later in Update()), bind to a static zero so the gauge simply
|
||||
// reads 0. Gated on BT_DEV_GAUGES so the POD path is byte-unchanged.
|
||||
//
|
||||
if (data_source == NULL)
|
||||
{
|
||||
static int s_devGauges = -1;
|
||||
if (s_devGauges < 0)
|
||||
s_devGauges = (getenv("BT_DEV_GAUGES") != NULL) ? 1 : 0;
|
||||
if (s_devGauges)
|
||||
{
|
||||
static T s_gaugeNullSource = T();
|
||||
dataSource = &s_gaugeNullSource;
|
||||
data_source = &s_gaugeNullSource;
|
||||
}
|
||||
}
|
||||
Check_Pointer(data_source);
|
||||
|
||||
*dataDestination = *dataSource;
|
||||
@@ -344,6 +364,12 @@ public:
|
||||
protected:
|
||||
virtual void
|
||||
Execute(); // Execute internal processes to display the gauge
|
||||
|
||||
// BT DEV-GAUGES bring-up: SEH wrapper around Execute() so a gauge whose
|
||||
// game-state data binding isn't reconstructed yet is skipped (not crashed).
|
||||
// Returns False if Execute() raised a structured exception (e.g. an AV).
|
||||
Logical
|
||||
GuardedExecute();
|
||||
//----------------------------------------------------------------------
|
||||
// Protected data
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
@@ -1332,7 +1332,42 @@ void
|
||||
) == False
|
||||
)
|
||||
{
|
||||
ReportParsingError("Undefined primitive");
|
||||
//
|
||||
// BT DEV-GAUGES bring-up: the BT-specific gauge primitive
|
||||
// table (BTL4MethodDescription, btl4grnd.cpp) is still a stub
|
||||
// -- the BT gauge widget classes (PlayerStatus, ...) aren't
|
||||
// reconstructed yet, so they don't resolve here. Rather than
|
||||
// Fail() -- which pops a modal assert dialog and FREEZES the
|
||||
// game mid-parse -- SKIP the unknown primitive's parameter
|
||||
// list so the parse can continue: it still registers every
|
||||
// label (e.g. Bhk1Init -> MechInit) and runs the base
|
||||
// "configure" primitives that build the gauge PORTS/surfaces
|
||||
// (the beachhead needs the 'sec'/radar surface, not the
|
||||
// widgets). Gated on BT_DEV_GAUGES so the POD path keeps the
|
||||
// strict Fail (it needs a complete, real method table).
|
||||
//
|
||||
if (getenv("BT_DEV_GAUGES") != NULL)
|
||||
{
|
||||
const char
|
||||
*skip_token;
|
||||
int
|
||||
paren_depth = 1; // the '(' already consumed above
|
||||
while (
|
||||
(paren_depth > 0) &&
|
||||
((skip_token = GetToken()) != NULL)
|
||||
)
|
||||
{
|
||||
if (stricmp(skip_token, "(") == 0)
|
||||
++paren_depth;
|
||||
else if (stricmp(skip_token, ")") == 0)
|
||||
--paren_depth;
|
||||
}
|
||||
UngetPreviousToken(); // leave ')' for the check below
|
||||
}
|
||||
else
|
||||
{
|
||||
ReportParsingError("Undefined primitive");
|
||||
}
|
||||
}
|
||||
if (stricmp(GetToken(), ")") != 0)
|
||||
{
|
||||
|
||||
@@ -109,6 +109,89 @@ static bool DevGaugeComposite()
|
||||
return v != 0;
|
||||
}
|
||||
|
||||
//===========================================================================//
|
||||
// DEV-COMPOSITE (option B) -- draw the woken gauge renderer's SECONDARY/radar
|
||||
// surface as an inset in the MAIN window. The gauge widgets have already
|
||||
// rastered into the shared CPU pixelBuffer; here we palette-expand the secondary
|
||||
// plane into a texture on the MAIN device (mirrors SVGA16::Update case 0) and blit
|
||||
// a quad. Called by the main renderer as the LAST draw before EndScene, so no
|
||||
// render-state save/restore is needed (the next frame resets device state).
|
||||
//===========================================================================//
|
||||
void SVGA16::DrawDevInset(LPDIRECT3DDEVICE9 device, int secMask, int paletteID)
|
||||
{
|
||||
int w = pixelBuffer.Data.Size.x; // 640
|
||||
int h = pixelBuffer.Data.Size.y; // 480
|
||||
if (device == NULL || w <= 0 || h <= 0) return;
|
||||
|
||||
// Lazily create the inset texture on the MAIN device (MANAGED -> lockable +
|
||||
// survives device reset).
|
||||
if (mDevInsetTex == NULL)
|
||||
device->CreateTexture(w, h, 1, 0, D3DFMT_R5G6B5, D3DPOOL_MANAGED, &mDevInsetTex, NULL);
|
||||
if (mDevInsetTex == NULL) return;
|
||||
|
||||
// Palette-expand the secondary plane into the texture (== SVGA16::Update case 0).
|
||||
SVGA16Palette *pal = &palette[paletteID];
|
||||
D3DLOCKED_RECT rect;
|
||||
if (SUCCEEDED(mDevInsetTex->LockRect(0, &rect, NULL, 0)))
|
||||
{
|
||||
Word *source = pixelBuffer.Data.MapPointer;
|
||||
Word *dest = (Word*)rect.pBits;
|
||||
int postRowIncrement = (rect.Pitch / 2) - w;
|
||||
for (int y = 0; y < h; y++)
|
||||
{
|
||||
for (int x = 0; x < w; x++)
|
||||
{
|
||||
PaletteTriplet *pe = &(pal->paletteData.Color[*source & secMask]);
|
||||
*dest = ((pe->Red >> 3) << 11) | ((pe->Green >> 2) << 5) | (pe->Blue >> 3);
|
||||
dest++; source++;
|
||||
}
|
||||
dest += postRowIncrement;
|
||||
}
|
||||
mDevInsetTex->UnlockRect(0);
|
||||
}
|
||||
|
||||
// Draw the inset: a 320x240 panel in the bottom-left of the (800x600) backbuffer.
|
||||
const float iw = 320.0f, ih = 240.0f, ix = 8.0f, iy = 600.0f - ih - 8.0f;
|
||||
struct InsetVert { float x, y, z, rhw, u, v; };
|
||||
InsetVert quad[4] =
|
||||
{
|
||||
{ ix, iy, 0.0f, 1.0f, 0.0f, 0.0f },
|
||||
{ ix + iw, iy, 0.0f, 1.0f, 1.0f, 0.0f },
|
||||
{ ix + iw, iy + ih, 0.0f, 1.0f, 1.0f, 1.0f },
|
||||
{ ix, iy + ih, 0.0f, 1.0f, 0.0f, 1.0f },
|
||||
};
|
||||
device->SetTexture(0, mDevInsetTex);
|
||||
device->SetFVF(D3DFVF_XYZRHW | D3DFVF_TEX1);
|
||||
device->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_SELECTARG1);
|
||||
device->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE);
|
||||
device->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1);
|
||||
device->SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE);
|
||||
device->SetRenderState(D3DRS_ZENABLE, FALSE);
|
||||
device->SetRenderState(D3DRS_LIGHTING, FALSE);
|
||||
device->SetRenderState(D3DRS_ALPHABLENDENABLE, FALSE);
|
||||
device->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE);
|
||||
device->DrawPrimitiveUP(D3DPT_TRIANGLEFAN, 2, quad, sizeof(InsetVert));
|
||||
}
|
||||
|
||||
//
|
||||
// Free entry point the MAIN renderer calls (L4VIDEO DPLRenderer::ExecuteImplementation,
|
||||
// just before EndScene). No-op unless BT_DEV_GAUGES is set + the gauge renderer + its
|
||||
// 'sec' port exist. Reaches the gauge renderer's SVGA16 and blits the secondary inset.
|
||||
//
|
||||
void BTDrawGaugeInset(LPDIRECT3DDEVICE9 device)
|
||||
{
|
||||
if (!DevGaugeComposite() || device == NULL) return;
|
||||
GaugeRenderer *gr = application ? application->GetGaugeRenderer() : NULL;
|
||||
if (gr == NULL) return;
|
||||
// The secondary/radar surface ('sec') is the one port that resolves without
|
||||
// the RP<->BT MFD port-name reconciliation (Step 2), so it is the beachhead.
|
||||
L4GraphicsPort *secPort = static_cast<L4GraphicsPort*>(gr->GetGraphicsPort("sec"));
|
||||
if (secPort == NULL) return;
|
||||
SVGA16 *svga = (SVGA16*)secPort->graphicsDisplay;
|
||||
if (svga == NULL) return;
|
||||
svga->DrawDevInset(device, secPort->GetBitMask(), secPort->paletteID);
|
||||
}
|
||||
|
||||
void SVGA16::BuildWindows(unsigned int width, unsigned int height, bool windowed, int *secondaryIndex, int *aux1Index, int *aux2Index)
|
||||
{
|
||||
NUMGAUGEWINDOWS = 0;
|
||||
@@ -3844,6 +3927,7 @@ SVGA16::SVGA16(
|
||||
):Video16BitBuffered(init_width, init_height)
|
||||
{
|
||||
BuildWindows(init_width,init_height,windowed, secondaryIndex, aux1Index, aux2Index);
|
||||
mDevInsetTex = NULL; // DEV-COMPOSITE (option B): lazily created on first inset draw
|
||||
//STUBBED: VIDEO RB 1/15/07
|
||||
# if defined(DEBUG)
|
||||
Tell("SVGA16::SVGA16()\n");
|
||||
|
||||
@@ -291,6 +291,13 @@ private:
|
||||
RECT *mSurfaceRects;
|
||||
|
||||
int mDisplayToUpdate;
|
||||
|
||||
LPDIRECT3DTEXTURE9 mDevInsetTex; // DEV-COMPOSITE (option B): the secondary/radar
|
||||
// plane uploaded to a texture on the MAIN device.
|
||||
public:
|
||||
// DEV-COMPOSITE (option B): palette-expand the secondary plane into mDevInsetTex on
|
||||
// the given (main) device + draw it as an inset quad. See BTDrawGaugeInset (L4VB16.cpp).
|
||||
void DrawDevInset(LPDIRECT3DDEVICE9 device, int secMask, int paletteID);
|
||||
};
|
||||
|
||||
//########################################################################
|
||||
|
||||
@@ -6963,6 +6963,11 @@ void DPLRenderer::ExecuteImplementation(RendererComplexity, RendererOrigin::Inte
|
||||
if (mCamShipHUD)
|
||||
mCamShipHUD->Render(0, &viewTransform);
|
||||
|
||||
// DEV-COMPOSITE (option B): blit the woken gauge renderer's secondary/radar surface
|
||||
// as an inset in this window (LAST draw before EndScene; no-op unless BT_DEV_GAUGES).
|
||||
extern void BTDrawGaugeInset(LPDIRECT3DDEVICE9 device);
|
||||
BTDrawGaugeInset(mDevice);
|
||||
|
||||
hr = mDevice->EndScene();
|
||||
|
||||
// DIAG (turn-hitch hunt): draw CPU is _rt0..here; Present blocks on the GPU.
|
||||
|
||||
Reference in New Issue
Block a user