Prerequisites

Before adding free-direct, ensure your project can provide the following CMake targets:

  • SDL3::SDL3 — SDL 3 base library
  • SDL3_image::SDL3_image — SDL3_image (used for image loading in the demo; may not be strictly needed by your game)
  • SDL3_mixer::SDL3_mixer — SDL3_mixer (linked by the demo; optional if you only use DirectSound)
  • free-api::free-api — companion WinAPI compatibility layer (sibling repository)

The parent project free-eggbert provides all SDL targets through cmake/ThirdPartySDL.cmake. If you are integrating free-direct into your own project, you must supply these targets yourself (e.g. via find_package or FetchContent).

Important

free-direct's CMakeLists.txt calls fatal_error if the SDL targets are missing at configure time. Ensure they are available before add_subdirectory is called.

Adding free-direct via CMake

free-direct is consumed as an add_subdirectory source dependency. Place it (or a submodule pointing to it) next to free-api in your repository:

your-project/
├── free-api/        ← sibling repository / submodule
├── free-direct/     ← sibling repository / submodule
└── CMakeLists.txt

In your root CMakeLists.txt:

cmake_minimum_required(VERSION 3.20)
project(YourGame LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# ── 1. Provide SDL3 targets (example: system installation) ──
find_package(SDL3 REQUIRED)
find_package(SDL3_image REQUIRED)
find_package(SDL3_mixer REQUIRED)

# ── 2. Add free-api first (free-direct depends on it) ──
add_subdirectory(free-api)

# ── 3. Add free-direct ──
add_subdirectory(free-direct)

# ── 4. Link your game target ──
add_executable(YourGame src/main.cpp)
target_link_libraries(YourGame
    PRIVATE
    free-direct        # brings in free-api transitively
    SDL3::SDL3
    SDL3_image::SDL3_image
    SDL3_mixer::SDL3_mixer
)

The free-direct target's public include directories expose include/, so after linking, your code can use:

#include <ddraw.h>
#include <dsound.h>
#include <dplay.h>
Include path note

The include path is intentionally set up to mirror the legacy DirectX SDK layout. #include <ddraw.h> resolves to free-direct/include/ddraw.h, not to any system or SDK header. Make sure you do not have a conflicting system ddraw.h earlier in the include search path.

Build instructions (standalone demo)

To build the included demo application:

git clone https://github.com/openeggbert/free-direct.git
cd free-direct

# SDL3 targets must be provided — see the parent project free-eggbert
# for the recommended ThirdPartySDL.cmake approach.
cmake -B build
cmake --build build

./build/FREE_DIRECT

Initialization sequence

A typical initialization follows the same sequence as original DirectX 3 game code. No extra free-direct-specific setup is needed; just call the standard DirectDraw entry points.

#include <windows.h>
#include <ddraw.h>

// 1. Create the DirectDraw object
LPDIRECTDRAW dd = nullptr;
if (FAILED(DirectDrawCreate(nullptr, &dd, nullptr))) {
    // handle error
}

// 2. Set cooperative level — provide the HWND from your window
//    DDSCL_NORMAL for windowed, DDSCL_FULLSCREEN|DDSCL_EXCLUSIVE for fullscreen
if (FAILED(dd->SetCooperativeLevel(hwnd, DDSCL_NORMAL))) {
    dd->Release();
    // handle error
}

// 3. (Optional) set the display mode for fullscreen
//    This stores the logical resolution used by CreateSurface for the primary surface.
dd->SetDisplayMode(640, 480, 8);  // width, height, bits-per-pixel

// 4. Create the primary surface
DDSURFACEDESC desc = {};
desc.dwSize  = sizeof(DDSURFACEDESC);
desc.dwFlags = DDSD_CAPS;
desc.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;

LPDIRECTDRAWSURFACE primary = nullptr;
dd->CreateSurface(&desc, &primary, nullptr);

// 5. Create an offscreen back buffer
desc = {};
desc.dwSize   = sizeof(DDSURFACEDESC);
desc.dwFlags  = DDSD_CAPS | DDSD_WIDTH | DDSD_HEIGHT;
desc.dwWidth  = 640;
desc.dwHeight = 480;
desc.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN;

LPDIRECTDRAWSURFACE back = nullptr;
dd->CreateSurface(&desc, &back, nullptr);

// 6. Create and attach a clipper (required for windowed Blt-to-primary)
LPDIRECTDRAWCLIPPER clipper = nullptr;
dd->CreateClipper(0, &clipper, nullptr);
clipper->SetHWnd(0, hwnd);
primary->SetClipper(clipper);
clipper->Release();

// ... render, blit, present ...

// 7. Shutdown
back->Release();
primary->Release();
dd->Release();

Replacing old DirectX 3 calls

In most cases you only need to:

  1. Remove the original ddraw.lib / dsound.lib / dplayx.lib from your linker input.
  2. Replace the Windows SDK include path with free-direct's include/.
  3. Link free-direct instead.

Game source code that only uses the supported subset will compile and link without modification. See the Compatibility Table for the list of supported features and the Migration Guide for patterns that require adaptation.

Optional CMake options

CMake optionDefaultEffect
FREE_DIRECT_DIAGNOSTICS OFF Enables atomic diagnostic counters (live object counts, blit/present totals). See Developer Notes.

Enable from the parent project:

set(FREE_DIRECT_DIAGNOSTICS ON CACHE BOOL "Enable Free Direct diagnostic counters")

Runtime environment variables

The following environment variables control runtime behavior without recompilation:

VariableValueEffect
FREE_DIRECT_TARGET_FPS integer, e.g. 60 Sets the frame-throttle target. Default 60. Set to a higher value (e.g. 120) to reduce throttling.
FREE_DIRECT_ENABLE_VSYNC 0 to disable VSync is enabled by default. Set to 0 to disable SDL_SetRenderVSync.
FREE_DIRECT_DEBUG_DDRAW 1 Verbose DirectDraw operation logging via SDL_Log.
FREE_DIRECT_DEBUG_DSOUND 1 Verbose DirectSound operation logging.
FREE_DIRECT_DEBUG_DSOUND_FORMAT 1 Detailed format/stream information for each sound buffer.
FREE_DIRECT_DEBUG_PRESENTATION 1 Logs every PresentPrimary call including throttle and dirty checks.
FREE_DIRECT_DEBUG_COLORKEY 1 Logs color key comparisons and per-blit copied/skipped pixel counts.
FREE_DIRECT_DEBUG_PERF 1 Prints per-second performance summary (presents/s, uploads/s, blts/s).
FREE_DIRECT_DEBUG_PRIMARY_CLEAR 1 Clears the window to a solid blue on startup to confirm the renderer is working.

Minimal example application

See Code Examples for a complete annotated minimal application showing initialization, surface creation, blitting, and shutdown.