From 22495d7245ac00346917c2c69eb75651e2a2313a Mon Sep 17 00:00:00 2001 From: RT Date: Wed, 5 Aug 2026 17:31:09 -0500 Subject: [PATCH] Docs: research notes on making -window work for all display modes Research only. Nothing implemented, nothing scheduled. Captured now so the investigation does not have to be repeated when we come back to it. New file: WINDOWED-MODE.md (repo root), indexed from README.md and the CLAUDE.md next-steps list. Goal being assessed ------------------- Make -window work for every pod configuration, so a full pod (main + radar + MFDs, or a cameraship pair) can be tested on a single monitor as a set of moveable windows. Target order: console (already works), cameraship, -tmfds 1, -tmfds 4. -tmfds 3 deliberately out of scope. Headline finding ---------------- -window does NOT fail for the MFD and cameraship modes - it silently DISABLES them, and the reason is a single line: MW4Application.cpp:1373 use_shgui = Environment.fullScreen ? 1 : 0; use_shgui is the master switch for the entire secondary-panel subsystem and is derived from fullscreen, so with -window it is 0 and DXRasterizer.cpp:1131 never calls HSH_EnterFullScreen2(). No panel is ever created. Console mode only appears to work windowed because it has no panels to lose. What the document covers ------------------------ * Why it behaves the way it does today, with the exact gating code. * What already exists in our favour: the main display is a working windowed reference implementation (primary + clipper + offscreen backbuffer, presented with Blt); the clipper wrappers are already in use; and none of the panel DRAWING code would change, because panels render to an offscreen target and composite - they are indifferent to whether the present is a Flip or a Blt. * The six things that must change, with quoted code: decouple use_shgui; windowed variants of CHSH_Device::InitFirst (DDSCL_NORMAL, no SetDisplayMode, per-panel HWND) and InitSecond (plain primary + clipper + offscreen backbuffer, D3D device on the offscreen); one window per panel; 8 Flip->Blt sites in WinMain.cpp; and explicit frame pacing. * Frame pacing is called out as the item that will actually bite: sh_step advances once per frame and drives MFD channel cycling, so an uncapped windowed rig would not faithfully represent pod behaviour. Same root cause as the known mechlab fast-spin bug. * Reference tables: every panel class with its InitFirst location, device slot and resolution; the SwapRightState member-swap mechanism that any windowed work must keep intact; and the full lifecycle/gating map. * Suggested phasing, with radar-only as the decisive Phase 1 experiment. The risk that decides it ------------------------ Windowed D3D7 device creation is per-GPU, and this would need four windowed devices in one process. Precedent runs both ways: the mission editor hit DDERR_INVALIDOBJECT creating a windowed D3D device on Win11 and needed DDrawCompat (STEP 8), while the game's own windowed main display succeeds on the W4100. Nothing has proven four. If it fails, the fallback means rewriting the panel RENDER path, not just its present - a substantially bigger job. Why this may be worth more than a test convenience -------------------------------------------------- Windowed mode removes exclusive-mode contention entirely, which is the whole reason dgVoodoo2 is currently mandatory for every multi-display configuration on Windows 10/11 (STEP 10). If windowed panels work natively, that is a route to dropping the dgVoodoo2 prerequisite WITHOUT breaking the XP pods, since it needs no external DLL on either OS. Same groundwork as the borderless-windowed migration already parked in STEP 10. Note on citations ----------------- All 22 file:line references were derived directly and verified to resolve to the expected symbol. An exploratory pass had produced line numbers that were substantially wrong (CHSH_Device::InitFirst reported at 717, actually 627; IsMultimonitorAvaliable at 2178, actually 2961), and a claim that the radar renders at 480x640 rotated - the code passes 640,480 like the others. Worth knowing before trusting generated citations in a document intended to outlive the session. Co-authored-by: Claude Opus 5 (Anthropic) Co-authored-by: GitHub Copilot --- CLAUDE.md | 9 ++ README.md | 1 + WINDOWED-MODE.md | 254 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 264 insertions(+) create mode 100644 WINDOWED-MODE.md diff --git a/CLAUDE.md b/CLAUDE.md index 6c0485cb..7835246f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1253,6 +1253,15 @@ consistency check explicitly printed *"No duplicate device assignments"*. The do summary. **Instrument, don't infer.** ## Next steps (proposed) +- [ ] (Researched, NOT started) Make `-window` work for **all** modes — full findings in + **`WINDOWED-MODE.md`** (repo root, 2026-08-05). Headline: `-window` does not fail for MFD/ + cameraship modes, it **silently disables** them, because `use_shgui` is derived from + `Environment.fullScreen` (`MW4Application.cpp:1373`). Beyond that one line the work is a windowed + variant of `CHSH_Device::InitFirst`/`InitSecond` (DDSCL_NORMAL + clipper + offscreen backbuffer, + no SetDisplayMode), one HWND per panel, and 8 `Flip`→`Blt` sites in `WinMain.cpp`. The deciding + risk is whether **four** windowed D3D7 devices can coexist in one process — the editor failed + this on Win11 and needed DDrawCompat (STEP 8), while the game's own windowed main display + succeeds on the W4100. Overlaps heavily with the borderless-windowed assessment below. - [ ] (Decision pending) Borderless-windowed migration — see the staged assessment in STEP 10. Step 2 (one panel) is the cheap, decisive experiment. Note this is the only route that would remove the dgVoodoo2 prerequisite **without** breaking the XP pods, since it needs no external diff --git a/README.md b/README.md index 06b40d82..358c8ae2 100644 --- a/README.md +++ b/README.md @@ -139,6 +139,7 @@ C:\VWE\firestorm\ ├─ RECOVERY.md Disaster-recovery notes ├─ ADDING-A-MECH.md Workflow: add a new 'Mech chassis └─ ADDING-A-MAP.md Workflow: add a new map/mission +└─ WINDOWED-MODE.md Research: making -window work for MFD/cameraship modes ``` ## The folders that matter (build inputs) diff --git a/WINDOWED-MODE.md b/WINDOWED-MODE.md new file mode 100644 index 00000000..f70a4dc2 --- /dev/null +++ b/WINDOWED-MODE.md @@ -0,0 +1,254 @@ +# Making `-window` work for all display modes ? research notes + +**Status: RESEARCH ONLY. Nothing implemented. Parked 2026-08-05.** + +Goal: make `-window` work for every pod configuration, so a full pod (main + radar + MFDs, or a +cameraship pair) can be tested on a single monitor as a set of moveable windows. + +Target configurations, in the order they'd be tackled: + +| Configuration | Windows needed | Status today | +|---|---|---| +| Console (`-tmfds 0`, CTCL console) | 1 (main) | **Already works** | +| Cameraship | 2 (main + Map/Armor) | Panels silently disabled | +| `-tmfds 1` (spanned MFD) | 3 (main + radar + one 1280x480 MFD) | Panels silently disabled | +| `-tmfds 4` (split MFD) | 4 (main + radar + 2x 640x480 MFD) | Panels silently disabled | + +`-tmfds 3` deliberately out of scope for now. + +Companion reading: **CLAUDE.md STEP 10** (the borderless-windowed assessment and the +exclusive-mode wall), **STEP 8** (the editor's windowed D3D7 failure), **STEP 12** (panel +lifecycle and the re-entry fix). + +--- + +## 1. Why `-window` only works in console mode today + +It is not a rendering limitation. It is one line, in +[MW4Application.cpp:1373](Gameleap/code/mw4/Code/MW4Application/MW4Application.cpp#L1373): + +```cpp +Environment.fullScreen = (strstr(all_lower, "-window") == NULL); +use_shgui = Environment.fullScreen ? 1 : 0; +``` + +`use_shgui` is the master switch for the entire secondary-panel subsystem, and it is *derived +from* fullscreen. With `-window` it becomes 0, so +[DXRasterizer.cpp:1131](Gameleap/code/CoreTech/Libraries/GameOS/DXRasterizer.cpp#L1131) never calls +`HSH_EnterFullScreen2()`: + +```cpp +//sanghoon begin +if(use_shgui){ + HSH_EnterFullScreen2(); +} +//sanghoon end +``` + +No panel is ever created. Console mode appears to "work" windowed only because it has no panels +to lose. **`-window` does not fail for the other modes ? it silently disables them.** + +The teardown is gated the same way at +[DXRasterizer.cpp:1407](Gameleap/code/CoreTech/Libraries/GameOS/DXRasterizer.cpp#L1407). + +--- + +## 2. What already exists that helps + +The job is smaller than it sounds, because a working windowed reference implementation is already +in the tree ? the main display does exactly what each panel would need to do. + +- **Windowed main display**: primary surface + clipper + offscreen backbuffer, in `SetupMode()` + ([DXRasterizer.cpp:1623](Gameleap/code/CoreTech/Libraries/GameOS/DXRasterizer.cpp#L1623), windowed + branch from the `if( FullScreen )` at + [:1718](Gameleap/code/CoreTech/Libraries/GameOS/DXRasterizer.cpp#L1718)). +- **Clipper wrappers exist and are already used**: + [DXRasterizer.cpp:1868-1870](Gameleap/code/CoreTech/Libraries/GameOS/DXRasterizer.cpp#L1868) + ```cpp + wCreateClipper( DDobject,0, &ClipperObject, NULL ); + wSetHWnd( ClipperObject, 0, hWindow ); + wSetClipper( FrontBufferSurface, ClipperObject ); + ``` + Wrapper definitions in `DirectDraw.cpp` (`wCreateClipper` / `wSetHWnd` / `wSetClipper`). +- **The windowed present already exists**: + [DXRasterizer.cpp:1287](Gameleap/code/CoreTech/Libraries/GameOS/DXRasterizer.cpp#L1287) blits the + backbuffer to a screen rect, versus the fullscreen `wFlip` at + [:1357](Gameleap/code/CoreTech/Libraries/GameOS/DXRasterizer.cpp#L1357). `Blt` stretches when the + destination rect differs in size, so aspect-preserving output is rect maths, not new machinery. +- **No panel drawing code changes.** Panels render into an offscreen `pDDSTarget` and composite it + onto their own backbuffer with `DrawPrimitive` (`CMFD_Device::EndChannel`). `DrawQuad`, + `DrawTexture`, the fonts and the mode-4 `SwapRightState` juggling are all indifferent to whether + the present is a Flip or a Blt. +- **Panel init already fails gracefully** (the `[panelcoop]` work in STEP 10), so a half-migrated + state degrades to "no panels" rather than dereferencing null surfaces. + +--- + +## 3. What has to change + +### 3.1 Decouple `use_shgui` from fullscreen +[MW4Application.cpp:1373](Gameleap/code/mw4/Code/MW4Application/MW4Application.cpp#L1373). +`use_shgui` should mean *"this configuration has panels"* ? derived from `g_nTypeOfMFDs` and the +CTCL type ? not *"we are fullscreen"*. One line, but nothing else can be tested until it changes. + +### 3.2 `CHSH_Device::InitFirst` is hard-wired to exclusive fullscreen +[render.cpp:627](Gameleap/code/CoreTech/Libraries/GameOS/render.cpp#L627). Default path (`-tcoop 0`): + +```cpp +const DWORD dwFlags = DDSCL_SETFOCUSWINDOW | DDSCL_CREATEDEVICEWINDOW | + DDSCL_ALLOWREBOOT | DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN; +hr=pDD->SetCooperativeLevel( hWindow, dwFlags ); +... +hr=pDD->SetDisplayMode( resx, resy, 16, 60, 0 ); +``` + +Windowed needs `DDSCL_NORMAL`, **no** `SetDisplayMode` at all, and **each panel's own `HWND`** +rather than the single shared global `hWindow`. + +### 3.3 `CHSH_Device::InitSecond` creates a fullscreen flip chain +[render.cpp:832](Gameleap/code/CoreTech/Libraries/GameOS/render.cpp#L832): + +```cpp +ddsd.dwFlags = DDSD_BACKBUFFERCOUNT | DDSD_CAPS; +ddsd.dwBackBufferCount = 1; +ddsd.ddsCaps.dwCaps = DDSCAPS_COMPLEX | DDSCAPS_FLIP | + DDSCAPS_PRIMARYSURFACE | DDSCAPS_3DDEVICE; +hr=pDD->CreateSurface( &ddsd, &pDDSFront, NULL ); +hr=pDDSFront->GetAttachedSurface( &ddsCaps, &pDDSBack ); +... +hr=pD3D->CreateDevice(IID_IDirect3DHALDevice,pDDSBack,&pD3DDevice); +``` + +Windowed needs: plain `DDSCAPS_PRIMARYSURFACE`, a clipper bound to that panel's window, a separate +`DDSCAPS_OFFSCREENPLAIN|DDSCAPS_3DDEVICE` backbuffer, and the D3D device created on the **offscreen +backbuffer**. Structurally identical to the main display's windowed branch. + +### 3.4 No panel windows exist +Only one window is created, at +[Windows.cpp:261/273](Gameleap/code/CoreTech/Libraries/GameOS/Windows.cpp#L261). Each windowed +panel needs its own `CreateWindowEx`, sized to the panel resolution, with a message loop that +survives being dragged. + +**Watch the topmost style**: [Windows.cpp:271](Gameleap/code/CoreTech/Libraries/GameOS/Windows.cpp#L271) +applies `WS_EX_TOPMOST` for every CTCL type except console/none. Left as-is, the main window would +sit on top of the panel windows. + +### 3.5 Panel present: 8 `Flip` sites to convert +All in [WinMain.cpp](Gameleap/code/CoreTech/Libraries/GameOS/WinMain.cpp), in the +`if(hsh_initialized)` block around line 1347: + +| Line | Device | Mode | +|---|---|---| +| 1355 | `radar_device` | `-tmfds 1` | +| 1357 | `mfd_device` | `-tmfds 1` | +| 1365 | *(commented out)* | `-tmfds 2` | +| 1369 | `mfd_device` | `-tmfds 2` | +| 1385 | `radar_device` | `-tmfds 4` | +| 1387 | `mfd_device` (left) | `-tmfds 4` | +| 1390 | `mfd_device_right` | `-tmfds 4` | +| 1396 | `mr_device` | cameraship | + +All of the form: +```cpp +device.pDDSFront->Flip(0,DDFLIP_DONOTWAIT|DDFLIP_NOVSYNC); +``` +Each becomes a `Blt` from that panel's backbuffer to its window's client rect in screen +coordinates. Mechanical ? **but the mode-4 stagger logic around them must be preserved exactly** +(that is the `eaa5fd3` fix; see CLAUDE.md "MFD mode 4: right device stagger fix"). + +### 3.6 Frame pacing ? the one that will actually bite +Fullscreen `Flip` paces the panels. A windowed `Blt` has no vsync whatsoever. This is the same root +cause as the known mechlab fast-spin bug +([DXRasterizer.cpp:1252](Gameleap/code/CoreTech/Libraries/GameOS/DXRasterizer.cpp#L1252) +`DisplayBackBuffer`, focused-windowed uses the unsynced Blt path). + +It matters more than usual here because **`sh_step` advances once per frame** and drives the MFD +channel cycling. At uncapped FPS the channels cycle far faster than on a real pod, so a windowed +test rig would not faithfully represent pod behaviour without an explicit frame cap. + +Options: scale by frame time, cap explicitly, or use `gos_Set_LoseFocusBehavior` mode 3 (the +built-in 60 Hz cap, `Windows.cpp`). + +--- + +## 4. Reference: how the panels are currently wired + +### Panel classes (all derive from `CHSH_Device`) + +| Class | `InitFirst` | Device slot | Resolution | +|---|---|---|---| +| `CMR_Device` (cameraship Map/Armor) | [render.cpp:1213](Gameleap/code/CoreTech/Libraries/GameOS/render.cpp#L1213) | `g_nNonDualHead`, else `g_nDualHead` | 640x480 | +| `CMFDRight_Device` | [render.cpp:1385](Gameleap/code/CoreTech/Libraries/GameOS/render.cpp#L1385) | `g_nMFD2` | 640x480 | +| `CRadar_Device` | [render.cpp:1456](Gameleap/code/CoreTech/Libraries/GameOS/render.cpp#L1456) | `g_nNonDualHead` | 640x480 | +| `CMFD_Device` | [render.cpp:1719](Gameleap/code/CoreTech/Libraries/GameOS/render.cpp#L1719) | `g_nMFD1` (mode 4) or `g_nDualHead` | 640x480 (mode 4) / 1280x480 (spanned) | + +Shared-device case: `CRadar_Device` passes `&mfd_device` as `pOtherHSHD` when `g_nTypeOfMFDs == 2`, +so the two panels share one `IDirectDraw7`. `InitFirst`/`InitSecond` both branch on that. + +`CMFD_Device::SwapRightState()` ([render.cpp:1774](Gameleap/code/CoreTech/Libraries/GameOS/render.cpp#L1774)) +swaps every device member (surfaces, D3D device, fonts, sizes) between the left and right devices +so mode 4 can route a channel to the right panel without duplicating the drawing code. Any windowed +work must keep this intact ? it swaps `pDD`, `pDDSFront`, `pDDSBack`, `pDDSTarget`, `pD3DDevice` +and more, so a per-panel `HWND` would need to be swapped alongside them (or deliberately not). + +### Lifecycle + +- `HSH_EnterFullScreen2()` ? [render.cpp:2999](Gameleap/code/CoreTech/Libraries/GameOS/render.cpp#L2999). + Chooses between the multi-monitor path, the single-secondary (`mr_device`) path, and none. +- `HSH_DirectDrawRelease2()` ? [render.cpp:3097](Gameleap/code/CoreTech/Libraries/GameOS/render.cpp#L3097). +- `IsMultimonitorAvaliable()` ? [render.cpp:2961](Gameleap/code/CoreTech/Libraries/GameOS/render.cpp#L2961). + Returns **false for cameraship**, so cameraship falls through to `IsSecondaryMonitorAvaliable()` + ([render.cpp:2989](Gameleap/code/CoreTech/Libraries/GameOS/render.cpp#L2989)) and the single + `mr_device`. Both would need windowed-aware equivalents. +- `hsh_initialized` / `hsh_mrdev_initialized` gate all per-frame panel work + (`WinMain.cpp` ~1174 and ~1347, `hudchat.cpp` 168/398/913). + +--- + +## 5. Suggested phasing + +| Phase | Work | Risk | +|---|---|---| +| **0** | Decouple `use_shgui` from `Environment.fullScreen`; gate panels on configuration instead | Trivial | +| **1** | Windowed `InitFirst`/`InitSecond` + one panel window. **Radar only.** | Medium ? decisive | +| **2** | Convert that one panel's `Flip` to `Blt` | Low | +| **3** | Explicit frame cap for windowed mode | Low | +| **4** | Cameraship (`mr_device`) ? one extra panel, same path | Low | +| **5** | `-tmfds 1` (one 1280x480 window), then `-tmfds 4` (two 640x480 + `SwapRightState`) | Medium | + +**Phase 1 with the radar alone is the make-or-break experiment.** If a `DDSCL_NORMAL` panel with a +clipper and a D3D device on an offscreen backbuffer comes up in a window, the rest is repetition. + +--- + +## 6. The risk that decides the whole thing + +**Windowed D3D7 device creation is per-GPU, and this would need four of them in one process.** + +Precedent in both directions: + +- **Against**: the mission editor hit exactly this. `IDirect3D7::CreateDevice` returned + `DDERR_INVALIDOBJECT` for a windowed offscreen backbuffer on Win11, for both the hardware HAL and + the Blade software device, and only worked once DDrawCompat was dropped in (CLAUDE.md STEP 8, + "3D viewport FIXED via DDrawCompat"). +- **For**: the game's main display *does* create a windowed D3D device successfully on the W4100 + bench ? console mode windowed is confirmed working. + +Nothing has yet proven **four** windowed D3D7 devices in one process on any hardware. If that +fails, the fallback is rendering panels without D3D (GDI/Blt only), which means rewriting the panel +*render* path rather than just its *present* ? a substantially bigger job. + +--- + +## 7. Two decisions to make before starting + +**Is this test-only, or shippable?** A bench-only tool can skip window chrome, dragging, resize +handling and pacing polish, and the job shrinks a lot. If pods might ever run windowed, it grows. + +**Does it need to work without dgVoodoo2?** This is the reason the work might be worth more than +test convenience. Windowed mode removes exclusive-mode contention entirely ? which is the *whole +reason* dgVoodoo2 is currently mandatory for every multi-display configuration on Windows 10/11 +(STEP 10). If windowed panels work natively, that is a genuine route to dropping the dgVoodoo2 +prerequisite **without** breaking the XP pods, since it needs no external DLL on either OS. That is +the "borderless windowed" migration already parked in STEP 10's assessment, and this research is +the same groundwork.