What usually works without change

If your code uses the following patterns, it will typically compile and run correctly under free-direct with no source changes:

  • Creating primary and offscreen surfaces.
  • Clearing surfaces with Blt(DDBLT_COLORFILL).
  • Blitting sprites with BltFast(DDBLTFAST_SRCCOLORKEY).
  • Drawing with Lock / Unlock directly into the pixel buffer.
  • Palette creation and SetPalette on 8-bit surfaces.
  • Color key setup with SetColorKey(DDCKEY_SRCBLT, ...).
  • Presenting with Flip(NULL, 0) on the primary surface.
  • Calling IsLost() / Restore() in a loop (both are harmless stubs).
  • Creating and releasing DirectSound PCM buffers with Lock/Unlock/Play.
  • Calling DirectPlay functions in a game that degrades gracefully to single-player.

Build migration steps

  1. Remove DirectX SDK / Windows SDK from the include path.
    Replace with the free-direct include/ directory and the free-api include directory.
  2. Remove DirectX libs from the linker.
    Remove ddraw.lib, dsound.lib, dplayx.lib, dinput.lib, dinput8.lib. Link free-direct instead.
  3. Replace WinAPI entry point if needed.
    free-api provides a FreeApiRunWinMain bridge:
    int main(int argc, char** argv) {
        return FreeApiRunWinMain(&WinMain, argc, argv);
    }
  4. Set C++20 standard.
    free-direct requires C++20. If your game uses an older standard, you may need to wrap it or update the standard in your CMake project.

Common patterns and adaptations

DDColorMatch — identifying a color key from a surface pixel

Some old games use a helper function that calls GetDC, SetPixel, GetPixel, then ReleaseDC and Lock to discover the palette index that corresponds to a given RGB color. This pattern is supported in free-direct's GetDC implementation for both 32-bit and 8-bit surfaces. No changes are needed.

Windowed mode — screen vs. client coordinates

In original DirectDraw windowed mode, the primary surface represents the entire desktop. Games often convert window-local (client) coordinates to screen coordinates before blitting. free-direct automatically corrects this: when blitting to a primary surface that has a clipper attached, it subtracts the window position from the destination rectangle.

If your game does not convert to screen coordinates, it will blit at the correct position anyway (no correction applied when no clipper is present).

SetDisplayMode for fullscreen

SetDisplayMode(640, 480, 8) stores the logical resolution used when creating the primary surface and triggers fullscreen via free-api. It does not change the physical display mode. On modern monitors SDL3's letterbox scaling handles the rest. Your code can call SetDisplayMode as before and it will work correctly.

8-bit palette rendering

8-bit paletted surface rendering works end-to-end:

  1. Create surface with DDPF_PALETTEINDEXED8.
  2. Create and attach a palette with CreatePalette + SetPalette.
  3. Write palette indices via Lock / Unlock.
  4. Blit to the primary surface (8-bit to 8-bit or 8-bit to 32-bit mix is handled).
  5. On present, the primary surface's 8-bit indices are expanded to RGBA32 using the attached palette.
Mixed BPP blits

The blit engine handles 8→8 and 32→32 correctly. Mixed-depth blits (8→32 or 32→8) are not supported — the blit returns DD_OK but copies no pixels in that case. Ensure source and destination have the same bit depth.

Rotation — DDBLT_ROTATIONANGLE

The DDBLT_ROTATIONANGLE flag and DDBLTFX.dwRotationAngle field are accepted but rotation is not performed. The blit proceeds as a normal non-rotated copy. If your game uses rotation, you will need to apply it manually (pre-rotate into a temporary surface via pixel manipulation or use SDL3 directly in a custom code path).

DirectSound looping audio

DSBPLAY_LOOPING is accepted but the buffer plays only once. If your game relies on looping audio (background music, ambient sound), you will need to re-call Play() after the buffer finishes, or implement a looping wrapper. See Developer Notes for the recommended approach.

DirectInput

free-direct has no DirectInput implementation. Legacy game code that uses DirectInput for keyboard or mouse input cannot be ported by replacing headers alone; the input code must be adapted to use the Win32 message queue (which is provided by free-api) or another input system.

IDirectDraw2 / IDirectDrawSurface2 / IDirectDrawSurface4

free-direct only implements IDirectDraw and IDirectDrawSurface (DirectX 3 shapes). Calls to QueryInterface for newer interface versions return DDERR_UNSUPPORTED. If your code requires IDirectDraw2 or IDirectDrawSurface2, you cannot use free-direct without adding those interfaces.

What is known NOT to work

  • DirectInput (no header, no implementation)
  • Direct3D (3D pipeline)
  • Real DirectPlay networking
  • Looping audio (DSBPLAY_LOOPING)
  • Rotation blits (DDBLT_ROTATIONANGLE)
  • Mixed-depth blits (8→32 or 32→8)
  • COM QueryInterface to newer interface versions
  • Hardware acceleration (all rendering is software/CPU)
  • Overlays
  • Accurate stereo panning (SetPan is stored but not applied)

Debugging a ported game

Enable verbose logging to trace what is happening during port testing:

# On Linux/macOS
export FREE_DIRECT_DEBUG_DDRAW=1
export FREE_DIRECT_DEBUG_PRESENTATION=1
export FREE_DIRECT_DEBUG_COLORKEY=1
./your-game

# On Windows (cmd)
set FREE_DIRECT_DEBUG_DDRAW=1
your-game.exe

SDL_Log output goes to the terminal / stdout. Watch for surface IDs, blit flags, color key values, and present call counts to identify problems.