Migration Guide
How to port old DirectDraw / DirectX 3 2D code to free-direct, common patterns, and known adaptation points.
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/Unlockdirectly into the pixel buffer. - Palette creation and
SetPaletteon 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
-
Remove DirectX SDK / Windows SDK from the include path.
Replace with the free-directinclude/directory and the free-api include directory. -
Remove DirectX libs from the linker.
Removeddraw.lib,dsound.lib,dplayx.lib,dinput.lib,dinput8.lib. Linkfree-directinstead. -
Replace WinAPI entry point if needed.
free-api provides aFreeApiRunWinMainbridge:int main(int argc, char** argv) { return FreeApiRunWinMain(&WinMain, argc, argv); } -
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:
- Create surface with
DDPF_PALETTEINDEXED8. - Create and attach a palette with
CreatePalette+SetPalette. - Write palette indices via
Lock/Unlock. - Blit to the primary surface (8-bit to 8-bit or 8-bit to 32-bit mix is handled).
- On present, the primary surface's 8-bit indices are expanded to RGBA32 using the attached palette.
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
QueryInterfaceto newer interface versions - Hardware acceleration (all rendering is software/CPU)
- Overlays
- Accurate stereo panning (
SetPanis 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.