Chapter 60 free-direct and free-api
CNA’s FREEDIRECT renderer sits on two compatibility libraries. free-direct reimplements a narrow DirectX 3-era DirectDraw, DirectSound, and DirectPlay surface; free-api supplies the Win32-shaped types and services below it using SDL3. Neither is a general emulator, and CNA’s native DIRECTX1 through DIRECTX7 renderers do not use either library.
60.1 A game-driven boundary
Free-direct’s original scope is derived from calls made by two games: Speedy Blupi through free-eggbert, and Planet Blupi. A third game cannot assume that a familiar DirectX symbol exists merely because the header name does. It needs its own call-site audit and an explicit decision about widening the library.
The audited public headers make that boundary measurable. Across ddraw.h, dplay.h, and dsound.h, their documentation contains 95 explicit Status: annotation lines. Count that tagged spelling, not every bare status word: dsound.h’s opening overview repeats twelve of the later classifications without the tag. The tagged lines cover functions and interfaces but also some supporting structures and constant groups, so 95 is a documentation-marker census, not a public-method count:
| Header | Implemented | Partial | Stub |
|---|---|---|---|
| ddraw.h | 24 | 12 | 7 |
| dplay.h | 12 | 7 | 7 |
| dsound.h | 20 | 4 | 2 |
| Total | 56 | 23 | 16 |
The table is stronger than a narrative “mostly implemented” claim because it can be regenerated from the headers, provided the unit is not mislabeled as methods. A status still needs semantic reading: DirectDraw’s IsLost() and Restore() are honest stubs returning DD_OK, whereas a partial operation may implement the game-used path and reject other flags.
60.2 Three DirectX subsystems, no Direct3D
DirectDraw owns CPU pixel buffers, primary and offscreen surfaces, clipping, source color keys, palettes, Blt/BltFast, Lock/Unlock, and SDL texture upload on Flip. Surface registration allows renderer invalidation. It is the deepest and most directly exercised part of CNA’s 2D compositor.
DirectSound opens a real SDL3 audio device and stream behind a shared, mutex-protected device. Static PCM buffers and volume are implemented; volume uses . Pan is a sharper boundary than its helper name suggests: SetPan() clamps and stores the value, and a constant-power helper computes left/right gains for mono, but applyGain() then discards those gains because an SDL stream channel map cannot express them. Pan is therefore not audibly applied yet. The tests lock in success and no-crash behavior rather than samples. The subsystem also enforces a 64 MiB buffer ceiling and contains a deliberate PCMWAVEFORMAT versus WAVEFORMATEX layout correction.
DirectPlay implements sessions, players, unicast, broadcast, and host-side relay behind a transport interface. A default build has only in-process loopback. Real UDP peers and LAN discovery require FREE_DIRECT_ENABLE_ENET, which is OFF by default and adds ENet privately. Its packets are FreeDirect-to-FreeDirect, not Microsoft DirectPlay wire compatibility.
There is no Direct3D source or public interface at all. This library is “DirectX 3” only in the era-bundle sense; its graphics path is DirectDraw 2D.
60.3 The two libraries form a one-way dependency
Free-direct adds its sibling ../free-api when no free-api target already exists, disables free-api’s tests, and links free-api::free-api publicly. Free-api does not depend on free-direct. It supplies roughly 80 extern "C" WINAPI declarations across flat Win32-named headers and a real windows.h umbrella.
The public types deliberately do not expose SDL. Internally an HWND represents an SDL_Window* cast through void*; message translation, GDI-lite buffers, files and paths, timers, MCI/MIDI playback, joystick input, string resources, and diagnostics all translate to SDL-backed machinery. MIDI uses a shared audio stream, a mixing thread, and vendored TinySoundFont/TinyMidiLoader. Free-api itself links SDL3, SDL3_image, and SDL3_mixer, but neither ENet nor sharp-runtime.
Only three bridge functions are explicitly designed for free-direct: FreeApiCreateSurfaceDC, FreeApiDestroySurfaceDC, and FreeApiSetWindowFullscreen. Keeping those in free_api_bridge.h names the scope exception instead of letting free-direct reach arbitrarily into private implementation.
60.4 CNA is the odd consumer
Free-eggbert and Planet Blupi add free-api first and then free-direct. CNA adds only free-direct when CNA_GRAPHICS_RENDERER=FREEDIRECT; the public link edge pulls free-api into the final closure transitively. CNA is therefore a real third consumer of free-api even though its own build file never calls add_subdirectory for it.
CNA’s renderer is not a thin adapter. Its 1,374-line implementation creates DirectDraw surfaces, performs locked pixel write/read/clear operations, composites quads on the CPU, and maintains a shadow-backbuffer resize transaction with failure injection. Twenty-one registered CNA test binaries now exercise its renderer-specific and shared graphics contracts under SDL’s dummy video driver, including shadow-buffer pixels, resize transactions, sampling, 3D refusal, resource-transfer and render-pass ordering boundaries.
60.5 The fopen macro is a real containment cost
Free-api’s windows.h defines fopen as free_api_fopen. Because free-direct’s ddraw.h includes that umbrella, exposing ddraw.h from a CNA public header would rewrite every later fopen(...) token in the translation unit. CNA contains the hazard with pimpl: FreeDirectRenderer.hpp does not include ddraw.h; the implementation file owns all DirectDraw types.
This is more than “keep headers small.” It is a preprocessor quarantine around a second-level dependency whose public compatibility contract intentionally includes global Win32 macros.
60.6 FREEDIRECT is not DIRECTX3
CNA’s DIRECTX1, DIRECTX2, DIRECTX3, DIRECTX5, DIRECTX6, and DIRECTX7 renderers use the real Microsoft/MinGW/Wine DirectDraw SDK and link ddraw/dxguid; they deliberately do not use free-direct. The SDL-backed renderer was itself named DIRECTX3 until 2026-08-04, when it became FREEDIRECT precisely to remove this ambiguity.
Selector spelling therefore determines two different dependency graphs:
The same public ddraw.h spelling can resolve to different providers under those graphs, which is why source-path and target evidence matter more than API-name resemblance.
60.7 Verification boundaries
Free-direct has nine default CTest registrations, ten with ENet, using a hand-rolled CHECK mechanism. Compile-only header tests and a shell hygiene gate prevent SDL or ENet identifiers from leaking into public headers. Tests default OFF. The DirectPlay suite also records that enabling ENet makes 29 of 61 loopback-shaped checks fail by design because the timing model changes; a transport toggle is not a free strengthening of the same oracle.
Free-api has 29 CTest registrations: 20 C++ programs and nine CMake-level tests, including negative controls for string-table extraction and public-surface scans. They are ON standalone and OFF when embedded. The suite exercises SDL-to-WM_* translation, message filtering, timers, GDI bounds, resource strings, MCI/MIDI, joystick, and quiet logging.
Test volume did not prevent a cautionary 2026-07-18 sequence. A nine-finding re-audit was immediately followed by two regressions introduced by its changes: a startup abort from reentrant static initialization and real memory growth from bitmap pooling. Users found both, not the 29-test suite. The episode is a useful reminder that an audit patch changes the system under audit; regression risk belongs inside the review, not after it.
The correct scope statement is thus precise. These libraries reproduce the calls their games and CNA currently need, with unusually visible status metadata and meaningful tests. They are not Wine, a complete Win32 SDK, hardware-accurate DirectX emulation, or a general foundation for arbitrary DirectX 3 software.