Integration Guide
How to add free-direct to an existing C++ project, configure CMake, and initialize the library.
Prerequisites
Before adding free-direct, ensure your project can provide the following CMake targets:
SDL3::SDL3— SDL 3 base librarySDL3_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).
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>
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:
- Remove the original
ddraw.lib/dsound.lib/dplayx.libfrom your linker input. - Replace the Windows SDK include path with free-direct's
include/. - Link
free-directinstead.
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 option | Default | Effect |
|---|---|---|
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:
| Variable | Value | Effect |
|---|---|---|
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.