diff --git a/CLAUDE.md b/CLAUDE.md index eccbff5..14657b2 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -675,6 +675,27 @@ method table (BT gauge classes: `PlayerStatus` + ~16 recovered `methodDescriptio game-state DATA BINDINGS (why some gauges resolve NULL) — that is what the dev skip/guards stand in for, and what the POD needs. Then Step 2 (RP↔BT MFD port-name reconcile → the 5 MFDs) + Step 3 (2-window layout). +**✅✅ GAUGE MILESTONE C — ALL SIX instrument surfaces in a SEPARATE dev window (default under +`BT_DEV_GAUGES`; `BT_DEV_GAUGES_DOCK=1` = docked-in-main-window instead; full map: `docs/GAUGE_COMPOSITE.md`).** +Steps 2+3 done for the dev path. A 960×384 top-level window tiles all 6 pod screens with authentic content: +**Heat** (COOLANT/BALANCE/RES + condenser/temp-leak gauges), **Comm** (KILLS/DEATHS/SELECT TARGET), **Mfd1/2/3** +(DISPLAY/PROGRAM/NEAREST/TEMP-STATUS frames), and the color **radar** (SCALE grid + dials + ARMOR DAMAGE). Main +800×600 3D view is un-occluded; default DEV un-regressed (TARGET DESTROYED, 0 crashes). **Architecture (from the +`mfd-multisurface-map` workflow):** all 6 surfaces are bit-plane MASKS over the ONE shared `SVGA16`/`pixelBuffer` +(`sec`=palette low byte; `Heat`=0x4000 UL, `Mfd2`=0x0400 UC, `Comm`=0x8000 UR, `Mfd1`=0x0100 LL, `Mfd3`=0x1000 LR; +`Eng1-3` = engineering-mode alt planes, not extra monitors), so the compositor reaches the SVGA16 ONCE and +extracts each by mask. **No port-name reconcile needed on the dev path** — we fetch the BT port names directly via +`GetGraphicsPort` (the RP `auxUL2/…` names only matter to the POD's own `SVGA16::Update` demux — a deferred +pod-only fallback). `SVGA16::DrawDevSurface` has two kernels: palette-LUT (sec, == Update case 0) + mono bit-plane→ +tint (`(word&mask)?tint:0`, the reduced core of Update cases 1/2); `BTDrawGaugeSurfaces` iterates a 6-entry table. +The **separate window = one `CreateAdditionalSwapChain` on the EXISTING device** (no 2nd D3D device); rendered +AFTER the main `EndScene` (`BTGaugeWindowRenderAndPresent`, L4VIDEO): SetRenderTarget→**SetDepthStencilSurface(NULL)** +→Clear→BeginScene→6 tiles→EndScene→restore→`swap->Present`. **⚠ KEY BUG (cost a cycle): depth-stencil size +mismatch** — the main 800×600 depth stays bound when you SetRenderTarget to the 960×384 gauge backbuffer; a bound +depth SMALLER than the RT is INVALID → all draws silently fail (window shows the Clear color, no geometry). Fix: +unbind depth (Z is off anyway), restore after. Pod `BuildWindows`/`Update` path byte-unchanged. REMAINING = the +widget recon (above) + the pod MFD port-name fallback. + **Alternate route considered:** run original `btrel410.exe` under DOSBox — **blocked**: it needs the VPX/IG board DOSBox can't emulate; bypassing it = the shim work anyway. diff --git a/docs/GAUGE_COMPOSITE.md b/docs/GAUGE_COMPOSITE.md index 50fa734..2efff28 100644 --- a/docs/GAUGE_COMPOSITE.md +++ b/docs/GAUGE_COMPOSITE.md @@ -172,10 +172,51 @@ accommodations (skip/guard) stand in for. Files touched: `L4VB16.{h,cpp}` (Draw `L4VIDEO.cpp` (the EndScene hook), `btl4app.{cpp,hpp}` (the override fix), `GAUGREND.cpp` (tolerant skip), `gauge.h` + `GAUGE.{h,cpp}` (NULL-source + SEH guards). -## Follow-ups (after Milestone B) +## Milestone C DONE (2026-07) — ALL SIX instrument surfaces in a SEPARATE dev window -- **Widget recon (the real fix):** reconstruct `BTL4MethodDescription` (BT gauge classes) + the gauge→mech - data bindings so the skipped/guarded gauges show real data and the POD path (strict `Fail`, no guards) works. -- **Step 2** — reconcile the MFD port names (`auxUL2/auxC/...` in `SVGA16::Update` vs `Heat/Comm/Mfd1-3` in - `L4GAUGE.CFG`) → the 5 MFD surfaces render (only `sec`/radar works today). -- **Step 3** — the 2-window layout (3D-view window + an instrument-panel window tiling radar + the 5 MFDs). +The beachhead's single-`sec`-inset is now a full **6-surface compositor in its own top-level window** (the +default under `BT_DEV_GAUGES`; `BT_DEV_GAUGES_DOCK=1` docks the panel into the main window instead). The +960×384 gauge window tiles all six pod instrument screens with authentic content: **Heat** (COOLANT/BALANCE/ +RES + condenser & temp-leak gauges), **Comm** (KILLS/DEATHS/SELECT TARGET scoreboard), **Mfd1/Mfd2/Mfd3** +(DISPLAY/PROGRAM/NEAREST/TEMP-STATUS frames), and the color **radar** (SCALE grid + speed/heading dials + +ARMOR DAMAGE schematic). Verified live: gauge window renders all 6; main 800×600 3D view is un-occluded; +default DEV (no gauges) un-regressed (TARGET DESTROYED after 8 hits, 0 crashes). + +**Architecture (mapped by the `mfd-multisurface-map` workflow, then implemented):** +- **One buffer, six bit-plane views.** All gauge ports share ONE `SVGA16`/`pixelBuffer`; a "surface" is a + MASK over that one 640×480×16 buffer. The compositor reaches the `SVGA16` once (via any port) and extracts + each surface by its own plane mask. BT plane map (`L4GAUGE.CFG` MechInit @4395): `sec`=radar (palette, + low byte) · `Heat`=UL (0x4000) · `Mfd2`=UC (0x0400) · `Comm`=UR (0x8000) · `Mfd1`=LL (0x0100) · + `Mfd3`=LR (0x1000). `Eng1/2/3` are the engineering-mode alt planes of UC/LL/LR, not extra monitors. +- **No port-name reconcile needed on the dev path.** The RP names `auxUL2/auxC/…` hardcoded in + `SVGA16::Update` only matter to the POD demux (a deferred pod-only follow-up); our compositor fetches the + BT names (`Heat/Comm/Mfd1/Mfd2/Mfd3`) directly via `GetGraphicsPort` (`stricmp`). +- **Two extract kernels** in `SVGA16::DrawDevSurface`: palette-LUT (sec/radar, == `Update` case 0) and mono + bit-plane → tint (`(word & mask) ? tint : 0`, the reduced core of `Update` cases 1/2; MFD masks are single + bits so no DWORD SIMD/pack). `BTDrawGaugeSurfaces` iterates a `{name,tint,cellRect}` table over all 6. +- **Separate window = one ADDITIONAL SWAP CHAIN on the existing device** (no 2nd D3D device — textures/verts + are shared). `BTGaugeWindowRenderAndPresent` (called AFTER the main `EndScene`, before the main Present): + `GetBackBuffer` → `SetRenderTarget` → **`SetDepthStencilSurface(NULL)`** → Clear → BeginScene → 6 tiles → + EndScene → restore RT+DS → `swap->Present`. The pod's own per-surface `BuildWindows`/`Update` path is + byte-unchanged (dev mode already NULLs those objects). +- **⚠ THE KEY BUG (cost a cycle): depth-stencil size mismatch.** The main 800×600 depth surface stays bound + when you `SetRenderTarget` to the 960×384 gauge backbuffer; a bound depth surface SMALLER than the render + target is INVALID → every draw silently fails (window showed the Clear color but no tiles). Fix: + `SetDepthStencilSurface(NULL)` before the gauge draws (Z is disabled anyway), restore after. Symptom to + remember: render-to-a-second-swap-chain shows the clear color but no geometry ⇒ check the bound depth size. + +**Content reality (unchanged from Milestone B):** the surfaces show the authored cockpit FRAME art + a few +base-table gauges (heat numerics, radar) — the live animated MFD widgets are BT-specific gauge classes not +yet reconstructed (parse-skipped). The separate window is now the **live viewer** for that widget-recon work. + +## Follow-ups (after Milestone C) + +- **Widget recon (the real fix, the big remaining workstream):** populate `BTL4MethodDescription` (give each + BT gauge class a `methodDescription`) + wire the gauge→game-state `Execute()` data bindings, so the + skipped/guarded widgets (`map`/`GeneratorCluster`/`vehicleSubSystems`/`pilotList`/`cmArmor`/heat clusters) + render live data. The Milestone-C window shows them coming online as they're reconstructed. +- **Pod MFD port-name reconcile (pod-only):** a ~5-line positional dual-name fallback in `SVGA16::Update` + (`UL = GetGraphicsPort("auxUL2"); if(!UL) UL = GetGraphicsPort("Heat"); …`) so BT MFDs reach the pod's real + monitors. NOT a CFG rename (the CFG uses BT names pervasively + `MUNGA_L4` is shared with RP's aux names). +- **Polish:** overlay-plane compositing over `sec`; a pod-accurate RGB-packing toggle; texture atlas + (one lock/upload); device-reset recreate hardening for the additional swap chain. diff --git a/engine/MUNGA_L4/L4VB16.cpp b/engine/MUNGA_L4/L4VB16.cpp index c401255..9c7874e 100644 --- a/engine/MUNGA_L4/L4VB16.cpp +++ b/engine/MUNGA_L4/L4VB16.cpp @@ -109,6 +109,15 @@ static bool DevGaugeComposite() return v != 0; } +// DEV-COMPOSITE: BT_DEV_GAUGES_DOCK=1 docks the 6-surface panel INTO the main 800x600 +// window (occludes the 3D view); default (unset) presents them in a SEPARATE window. +static bool DevGaugeDocked() +{ + static int v = -1; + if (v < 0) v = (getenv("BT_DEV_GAUGES_DOCK") != NULL) ? 1 : 0; + 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 @@ -117,50 +126,70 @@ static bool DevGaugeComposite() // 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) +void SVGA16::DrawDevSurface(LPDIRECT3DDEVICE9 device, int slot, int mask, int paletteID, + int monoTint, float dstX, float dstY, float dstW, float dstH) { int w = pixelBuffer.Data.Size.x; // 640 int h = pixelBuffer.Data.Size.y; // 480 - if (device == NULL || w <= 0 || h <= 0) return; + if (device == NULL || slot < 0 || slot >= 8 || w <= 0 || h <= 0) return; - // Lazily create the inset texture on the MAIN device (MANAGED -> lockable + + // Lazily create this surface's 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; + if (mDevSurfaceTex[slot] == NULL) + device->CreateTexture(w, h, 1, 0, D3DFMT_R5G6B5, D3DPOOL_MANAGED, &mDevSurfaceTex[slot], NULL); + LPDIRECT3DTEXTURE9 tex = mDevSurfaceTex[slot]; + if (tex == NULL) return; - // Palette-expand the secondary plane into the texture (== SVGA16::Update case 0). - SVGA16Palette *pal = &palette[paletteID]; + // Extract this surface's bit-plane from the ONE shared pixelBuffer into the texture. D3DLOCKED_RECT rect; - if (SUCCEEDED(mDevInsetTex->LockRect(0, &rect, NULL, 0))) + if (SUCCEEDED(tex->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++) + if (monoTint < 0) { - for (int x = 0; x < w; x++) + // PALETTE surface (sec/radar) -- palette-LUT expand (== SVGA16::Update case 0). + SVGA16Palette *pal = &palette[paletteID]; + for (int y = 0; y < h; y++) { - PaletteTriplet *pe = &(pal->paletteData.Color[*source & secMask]); - *dest = ((pe->Red >> 3) << 11) | ((pe->Green >> 2) << 5) | (pe->Blue >> 3); - dest++; source++; + for (int x = 0; x < w; x++) + { + PaletteTriplet *pe = &(pal->paletteData.Color[*source & mask]); + *dest = ((pe->Red >> 3) << 11) | ((pe->Green >> 2) << 5) | (pe->Blue >> 3); + dest++; source++; + } + dest += postRowIncrement; } - dest += postRowIncrement; } - mDevInsetTex->UnlockRect(0); + else + { + // MONO MFD surface -- single-bit-plane expand to a tint (the reduced core of + // SVGA16::Update cases 1/2; MFD masks are single bits, so no DWORD SIMD/pack). + Word tint = (Word) monoTint; + for (int y = 0; y < h; y++) + { + for (int x = 0; x < w; x++) + { + *dest = (*source & mask) ? tint : 0; + dest++; source++; + } + dest += postRowIncrement; + } + } + tex->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; + // Draw the surface quad at (dstX,dstY,dstW,dstH) in the current render target. 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 }, + { dstX, dstY, 0.0f, 1.0f, 0.0f, 0.0f }, + { dstX + dstW, dstY, 0.0f, 1.0f, 1.0f, 0.0f }, + { dstX + dstW, dstY + dstH, 0.0f, 1.0f, 1.0f, 1.0f }, + { dstX, dstY + dstH, 0.0f, 1.0f, 0.0f, 1.0f }, }; - device->SetTexture(0, mDevInsetTex); + device->SetTexture(0, tex); device->SetFVF(D3DFVF_XYZRHW | D3DFVF_TEX1); device->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_SELECTARG1); device->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE); @@ -173,23 +202,205 @@ void SVGA16::DrawDevInset(LPDIRECT3DDEVICE9 device, int secMask, int paletteID) device->DrawPrimitiveUP(D3DPT_TRIANGLEFAN, 2, quad, sizeof(InsetVert)); } +// +// The pod's SIX instrument surfaces, all bit-plane views of the ONE shared 640x480 +// gauge buffer (masks/positions from content/GAUGE/L4GAUGE.CFG MechInit @4395): +// sec = radar/secondary (palette) Heat = UL Mfd2 = UC Comm = UR +// Mfd1 = LL Mfd3 = LR +// monoTint < 0 -> palette-expand; else the mono tint for that MFD plane. The MFD +// content today is mostly the authored cockpit frame art (btquad/btcomm/btheat) -- +// the live widgets are BT-specific gauge classes not yet reconstructed (parse-skipped). +// +struct BTGaugeSurfaceDesc +{ + const char *portName; + int monoTint; // -1 = palette (sec); else R5G6B5 tint + float cellX, cellY; // normalized cell origin within the panel (y down) + float cellW, cellH; // normalized cell size +}; + +static const BTGaugeSurfaceDesc kBTGaugeSurfaces[6] = +{ + // top row: Heat (UL) | Mfd2 (UC) | Comm (UR) + { "Heat", 0xFFFF, 0.0f / 3, 0.0f, 1.0f / 3, 0.5f }, + { "Mfd2", 0xFFFF, 1.0f / 3, 0.0f, 1.0f / 3, 0.5f }, + { "Comm", 0xFFFF, 2.0f / 3, 0.0f, 1.0f / 3, 0.5f }, + // bottom row: Mfd1 (LL) | sec/radar (center) | Mfd3 (LR) + { "Mfd1", 0xFFFF, 0.0f / 3, 0.5f, 1.0f / 3, 0.5f }, + { "sec", -1, 1.0f / 3, 0.5f, 1.0f / 3, 0.5f }, + { "Mfd3", 0xFFFF, 2.0f / 3, 0.5f, 1.0f / 3, 0.5f }, +}; + +// +// Composite ALL SIX gauge surfaces into the CURRENT render target, tiled within the +// panel rect (px,py,pw,ph). Reaches the ONE shared SVGA16 via any resolvable port and +// extracts each surface by its own bit-plane mask. Ports that don't resolve are skipped. +// +void BTDrawGaugeSurfaces(LPDIRECT3DDEVICE9 device, float px, float py, float pw, float ph) +{ + if (device == NULL) return; + GaugeRenderer *gr = application ? application->GetGaugeRenderer() : NULL; + if (gr == NULL) return; + + // Reach the shared display once (any present port shares the same SVGA16/pixelBuffer). + SVGA16 *svga = NULL; + for (int i = 0; i < 6 && svga == NULL; i++) + { + L4GraphicsPort *p = static_cast(gr->GetGraphicsPort(kBTGaugeSurfaces[i].portName)); + if (p != NULL) svga = (SVGA16*) p->graphicsDisplay; + } + if (svga == NULL) return; + + for (int i = 0; i < 6; i++) + { + const BTGaugeSurfaceDesc &d = kBTGaugeSurfaces[i]; + L4GraphicsPort *port = static_cast(gr->GetGraphicsPort(d.portName)); + if (port == NULL) continue; // this surface's port isn't configured -> skip + svga->DrawDevSurface( + device, i, port->GetBitMask(), port->paletteID, d.monoTint, + px + d.cellX * pw, py + d.cellY * ph, d.cellW * pw, d.cellH * ph); + } +} + // // 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. +// just before EndScene). No-op unless BT_DEV_GAUGES is set. Docks the 6-surface panel +// in the bottom-left of the 800x600 main window (a second dedicated window is the goal; +// see BTGaugeWindow* below). // 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(gr->GetGraphicsPort("sec")); - if (secPort == NULL) return; - SVGA16 *svga = (SVGA16*)secPort->graphicsDisplay; - if (svga == NULL) return; - svga->DrawDevInset(device, secPort->GetBitMask(), secPort->paletteID); + if (!DevGaugeComposite() || !DevGaugeDocked() || device == NULL) return; + // DOCKED mode only: draw into the main backbuffer (this runs before EndScene). + BTDrawGaugeSurfaces(device, 2.0f, 300.0f, 540.0f, 296.0f); +} + +//===========================================================================// +// DEV-COMPOSITE -- the SEPARATE cockpit-MFD window (the default under BT_DEV_GAUGES). +// A second top-level window fed by an ADDITIONAL SWAP CHAIN on the existing main +// device (no second D3D device needed -- textures/vertex data are shared). The pod's +// own per-surface fullscreen-device path (SVGA16::BuildWindows) is untouched; this is +// the dev-box analog of the pod's separate instrument monitors, collapsed into one +// tiled window. BTGaugeWindowRenderAndPresent is called AFTER the main EndScene. +//===========================================================================// +static HWND s_gaugeHwnd = NULL; +static IDirect3DSwapChain9 *s_gaugeSwap = NULL; +static const int s_gaugeW = 960; +static const int s_gaugeH = 384; + +static LRESULT CALLBACK BTGaugeWndProc(HWND h, UINT m, WPARAM w, LPARAM l) +{ + if (m == WM_CLOSE) { ShowWindow(h, SW_HIDE); return 0; } // hide, never quit the app + return DefWindowProc(h, m, w, l); +} + +// Lazily create the window + its additional swap chain (on the MAIN device). +static bool BTGaugeWindowEnsure(LPDIRECT3DDEVICE9 device) +{ + if (s_gaugeSwap != NULL) return true; + if (device == NULL) return false; + + HINSTANCE hInst = GetModuleHandle(NULL); + static bool s_classReg = false; + if (!s_classReg) + { + WNDCLASSA wc; + memset(&wc, 0, sizeof(wc)); + wc.lpfnWndProc = BTGaugeWndProc; + wc.hInstance = hInst; + wc.hCursor = LoadCursor(NULL, IDC_ARROW); + wc.hbrBackground = (HBRUSH) GetStockObject(BLACK_BRUSH); + wc.lpszClassName = "BTGaugeWnd"; + RegisterClassA(&wc); + s_classReg = true; + } + if (s_gaugeHwnd == NULL) + { + RECT r = { 0, 0, s_gaugeW, s_gaugeH }; + AdjustWindowRect(&r, WS_OVERLAPPEDWINDOW, FALSE); + s_gaugeHwnd = CreateWindowExA( + 0, "BTGaugeWnd", "BattleTech - Cockpit MFDs", WS_OVERLAPPEDWINDOW, + 40, 40, r.right - r.left, r.bottom - r.top, NULL, NULL, hInst, NULL); + if (s_gaugeHwnd == NULL) return false; + ShowWindow(s_gaugeHwnd, SW_SHOWNOACTIVATE); + } + + // Match the main swap chain's backbuffer format so the additional chain is compatible. + D3DFORMAT bbFormat = D3DFMT_X8R8G8B8; + IDirect3DSwapChain9 *mainSwap = NULL; + if (SUCCEEDED(device->GetSwapChain(0, &mainSwap)) && mainSwap != NULL) + { + D3DPRESENT_PARAMETERS mpp; + if (SUCCEEDED(mainSwap->GetPresentParameters(&mpp))) + bbFormat = mpp.BackBufferFormat; + mainSwap->Release(); + } + + D3DPRESENT_PARAMETERS pp; + memset(&pp, 0, sizeof(pp)); + pp.Windowed = TRUE; + pp.SwapEffect = D3DSWAPEFFECT_DISCARD; + pp.hDeviceWindow = s_gaugeHwnd; + pp.BackBufferFormat = bbFormat; + pp.BackBufferWidth = s_gaugeW; + pp.BackBufferHeight = s_gaugeH; + pp.BackBufferCount = 1; + if (FAILED(device->CreateAdditionalSwapChain(&pp, &s_gaugeSwap))) + { + s_gaugeSwap = NULL; + return false; + } + return true; +} + +// Render the 6 surfaces into the gauge window's swap chain and present it. Called AFTER +// the main EndScene, BEFORE the main Present (so it can BeginScene/EndScene on its own). +void BTGaugeWindowRenderAndPresent(LPDIRECT3DDEVICE9 device) +{ + if (!DevGaugeComposite() || DevGaugeDocked() || device == NULL) return; + if (!BTGaugeWindowEnsure(device)) return; + + IDirect3DSurface9 *gaugeBB = NULL; + if (FAILED(s_gaugeSwap->GetBackBuffer(0, D3DBACKBUFFER_TYPE_MONO, &gaugeBB)) || gaugeBB == NULL) + { + // device likely lost -> drop the chain so it recreates next frame + s_gaugeSwap->Release(); + s_gaugeSwap = NULL; + return; + } + + IDirect3DSurface9 *mainRT = NULL; + device->GetRenderTarget(0, &mainRT); + IDirect3DSurface9 *mainDS = NULL; + device->GetDepthStencilSurface(&mainDS); // may be NULL + + device->SetRenderTarget(0, gaugeBB); + // Unbind the main depth-stencil: it is 800x600 but the gauge backbuffer is 960x384 + // (wider) -- a bound depth surface smaller than the render target is INVALID and makes + // every draw silently fail. We don't need depth here (Z is disabled per surface quad). + device->SetDepthStencilSurface(NULL); + device->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(12, 12, 16), 1.0f, 0); + if (SUCCEEDED(device->BeginScene())) + { + BTDrawGaugeSurfaces(device, 0.0f, 0.0f, (float) s_gaugeW, (float) s_gaugeH); + device->EndScene(); + } + + if (mainRT != NULL) // restore the main render target + depth for the next frame + { + device->SetRenderTarget(0, mainRT); + mainRT->Release(); + } + device->SetDepthStencilSurface(mainDS); // NULL is valid (restores "no depth") + if (mainDS != NULL) + mainDS->Release(); + gaugeBB->Release(); + + if (s_gaugeSwap->Present(NULL, NULL, s_gaugeHwnd, NULL, 0) == D3DERR_DEVICELOST) + { + s_gaugeSwap->Release(); + s_gaugeSwap = NULL; + } } void SVGA16::BuildWindows(unsigned int width, unsigned int height, bool windowed, int *secondaryIndex, int *aux1Index, int *aux2Index) @@ -3927,7 +4138,8 @@ 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 + for (int _i = 0; _i < 8; _i++) // DEV-COMPOSITE: lazily created on first surface draw + mDevSurfaceTex[_i] = NULL; //STUBBED: VIDEO RB 1/15/07 # if defined(DEBUG) Tell("SVGA16::SVGA16()\n"); diff --git a/engine/MUNGA_L4/L4VB16.h b/engine/MUNGA_L4/L4VB16.h index 2e33371..a7ee22b 100644 --- a/engine/MUNGA_L4/L4VB16.h +++ b/engine/MUNGA_L4/L4VB16.h @@ -292,12 +292,16 @@ private: int mDisplayToUpdate; - LPDIRECT3DTEXTURE9 mDevInsetTex; // DEV-COMPOSITE (option B): the secondary/radar - // plane uploaded to a texture on the MAIN device. + LPDIRECT3DTEXTURE9 mDevSurfaceTex[8]; // DEV-COMPOSITE: one texture per gauge surface + // (sec/radar + the 5 MFD bit-planes), 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); + // DEV-COMPOSITE: extract ONE gauge surface from the shared pixelBuffer into + // mDevSurfaceTex[slot] on the given (main) device + draw it as a quad at (dstX,dstY, + // dstW,dstH). monoTint < 0 -> palette-expand (sec/radar, case-0 LUT); monoTint >= 0 + // -> mono bit-plane expand ((word & mask) ? tint : 0) for an MFD plane. See + // BTDrawGaugeSurfaces (L4VB16.cpp). + void DrawDevSurface(LPDIRECT3DDEVICE9 device, int slot, int mask, int paletteID, + int monoTint, float dstX, float dstY, float dstW, float dstH); }; //######################################################################## diff --git a/engine/MUNGA_L4/L4VIDEO.cpp b/engine/MUNGA_L4/L4VIDEO.cpp index 08182e6..6ba45f4 100644 --- a/engine/MUNGA_L4/L4VIDEO.cpp +++ b/engine/MUNGA_L4/L4VIDEO.cpp @@ -6963,13 +6963,19 @@ 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). + // DEV-COMPOSITE: in DOCKED mode (BT_DEV_GAUGES_DOCK) blit the 6-surface gauge panel + // into this window as the LAST draw before EndScene (no-op otherwise / off pod). extern void BTDrawGaugeInset(LPDIRECT3DDEVICE9 device); BTDrawGaugeInset(mDevice); hr = mDevice->EndScene(); + // DEV-COMPOSITE: default mode -- render the 6 cockpit surfaces into a SEPARATE window + // (its own additional swap chain on this device) + present it, between the main + // EndScene and the main Present. No-op unless BT_DEV_GAUGES (and not docked mode). + extern void BTGaugeWindowRenderAndPresent(LPDIRECT3DDEVICE9 device); + BTGaugeWindowRenderAndPresent(mDevice); + // DIAG (turn-hitch hunt): draw CPU is _rt0..here; Present blocks on the GPU. LARGE_INTEGER _rt1; QueryPerformanceCounter(&_rt1); hr = mDevice->Present(NULL, NULL, NULL, NULL);