Files
firestorm/Gameleap
0a657b5998 Fix constant rhythmic stutter in MFD mode 4 (split dual 640x480)
-tmfds 4 splits the 1280x480 MFD span into two independent 640x480 monitors so
the span hardware is no longer required. It rendered correctly but stuttered
rhythmically and constantly, making the game unplayable. -tmfds 1 on the same
binary was flawless.

Root cause: a cross-DirectDraw-object texture read, twice every 7-frame cycle.

CHSH_Device::InitFirst with no pOtherHSHD peer creates its OWN IDirectDraw7 (via
wDirectDrawCreateEx on that monitor's device GUID) and InitSecond creates its OWN
primary flip chain and IDirect3DDevice7. CMFDRight_Device::InitFirst passes no
peer, so the right MFD is an entirely separate DirectDraw object.

EndChannel then did, for channels 3-4:

    target->pD3DDevice->SetTexture(0, pDDSTarget);

where target->pD3DDevice belonged to the RIGHT device but pDDSTarget was the LEFT
device's render-target texture. The old code comment asserted "both devices are
on the same GPU so VRAM textures are mutually accessible" -- that premise is
wrong. In DirectDraw 7 a surface belongs to the IDirectDraw7 that created it, not
to the physical GPU, so it is not a valid texture on another object's D3D device.

The MFDs still displayed, which means the runtime was emulating the access with a
VRAM -> system-memory readback and re-upload of the 1024x512 16-bit render target.
That forces a full GPU pipeline stall, and it happened on channels 3 and 4 (that
is, sh_step 5 and 6) -- twice per 7-frame cycle, on the same GPU drawing the main
view. Hence a fixed-period hitch in the whole game, forever.

This also explains why the earlier stagger work (eaa5fd3, BeginSceneRight) did not
help: it only moved a Flip from sh_step 0 to 1, and the flips already used
DDFLIP_DONOTWAIT|DDFLIP_NOVSYNC and never blocked. The flips were never the
problem.

Fix: give the right device everything it draws with, and render channels 3-4
entirely on it.

- New HSH_CreateMFDTextures() builds the mech image atlas and the MFD sprite atlas
  on a caller-supplied IDirectDraw7. Both devices now call it, so each owns a
  complete independent texture set. CMFD_Device::InitSecond was refactored onto it.

- CMFDRight_Device gained its own pDDSMechTexture / pDDSDamageTexture /
  pDDSTargetTexture plus a Release() override, and its InitSecond now sets up
  tw/th and the material/render state exactly like the left device.

- New CMFD_Device::SwapRightState() exchanges this object's DATA members with the
  right device's. BeginChannel swaps in when channel >= 3 in mode 4; EndChannel
  swaps back. This routes all existing drawing to the correct monitor without
  touching the ~233 mfd_device.* call sites in hudchat/huddamage/hudweapon/
  GUIRadarManager. The vtable pointer is deliberately never swapped, so virtual
  dispatch is unaffected; CHSHFont has no virtual functions so its array is
  swapped bytewise to avoid ctor/dtor side effects on a temporary.

- EndChannel's composite is now a single path for all modes. Mode 4 composites
  full 640 width at x=0 (each device is a standalone panel); modes 1-3 keep the
  half-width (ch/3)*w packing into one backbuffer.

Side effects: startup builds the 65-bitmap mech atlas twice (once per device), and
VRAM use rises by a few MB. Modes 0-3 are behaviourally unchanged.

Known cosmetic leftover, deliberately not changed: huddamage.cpp lines ~1319 and
~1901 call LoadTargetTexture outside the channel-3 block, so those loads land on
the left device and go unused. The in-channel call at ~2209 runs every frame in
that branch and correctly populates the right device's copy, so behaviour is
correct -- it is just a redundant load on target change.

Requires rebuild: MW4.exe (GameOS changes recompile the engine library).
Verified: compiles clean, console launches. Two-monitor testing pending.

Co-authored-by: Claude Opus 5 (Anthropic) <noreply@anthropic.com>
Co-authored-by: GitHub Copilot <copilot@github.com>
2026-07-24 23:39:42 -05:00
..