host_sdl is the handwritten native boundary for Flight applications running in an SDL 3 process. It owns SDL
subsystem lifetime, windows, event ingress, decoded-PCM playback, and native graphics surface acquisition. It does
not implement a scene renderer. Generated render-gl, scene2d-gl, render-wgpu, and scene2d-wgpu code remains
responsible for rendering after an adapter supplies its native context or surface.
The installed package is independent of the generated SDK while the compiler contracts settle. Its public API lives
under include/flight/host_sdl/, its implementations live under src/host_sdl/, and none of its targets changes the
dependency-free Flight::Cpp target. The build tree additionally exposes SDK audio and cursor adapters against the
committed preview headers so the exact generated interfaces are continuously compiled and executed before they
become installable.
SDL 3.2 or newer is required. Vulkan headers and a loader are required unless Vulkan support is disabled. On a clean checkout with SDL and Vulkan development packages installed, build and test the complete package with:
cmake -S . -B out/cmake/host-sdl -G Ninja \
-DCMAKE_BUILD_TYPE=Debug \
-DBUILD_TESTING=ON \
-DFLIGHT_CPP_BUILD_HOST_SDL=ON
cmake --build out/cmake/host-sdl
ctest --test-dir out/cmake/host-sdl --output-on-failureThe same option can be layered onto the repository preset:
cmake --preset development -DFLIGHT_CPP_BUILD_HOST_SDL=ON
cmake --build --preset development
ctest --preset developmentWith examples enabled by the preset, the native tween example exercises the GL path end to end:
./out/cmake/development/examples/flight_cpp_tween_sdl_gl_example
./out/cmake/development/examples/flight_cpp_sound_sdl_exampleBazel pins and builds SDL 3.4.10 from source for the SDL, GL, and type-erased WGPU targets. CMake, Ninja, Make, M4,
and pkg-config are the build tools used by rules_foreign_cc; SDL itself does not need to be installed. Build the
same host test and run the GL smoke with:
bazel test --config=local-posix //tests:host_sdl_test
SDL_AUDIODRIVER=dummy \
bazel run --config=local-posix //examples:sound_sdl -- --smoke
SDL_VIDEODRIVER=offscreen SDL_AUDIODRIVER=dummy \
bazel run --config=local-posix //examples:tween_sdl_gl -- --smokeThe public Bazel labels are //:host_sdl, //:host_sdl_image, //:host_sdl_gl, //:host_sdl_sdk_audio,
//:host_sdl_sdk_clipboard, //:host_sdl_sdk_cursor, //:host_sdl_sdk_device, //:host_sdl_sdk_haptics, //:host_sdl_sdk_keyboard, //:host_sdl_sdk_platform, //:host_sdl_sdk_screen, //:host_sdl_sdk_window, //:host_sdl_sdk_preview, and //:host_sdl_wgpu. They and their tests are tagged manual, so a core
bazel test //... does not fetch or build SDL. CMake remains the complete package path for the Vulkan surface
adapter.
Flight::HostSdlSdkPreview and Bazel //:host_sdl_sdk_preview are the single dependency for every exact generated
record adapter listed above. They remain build-tree-only with Flight::SdkPreview; the name does not claim a
generated Host aggregate, which flight-compiler still refuses through its ApplicationWindow dependency.
It animates the fifteen easing curves emitted from examples/tween/source/tween.ts. Rendering uses the copyable
GlCanvas and WebGl2Context host seam exposed by Flight::HostSdlGl. The example calls the context's reusable
texture upload, framebuffer clear/readback, shader/program, draw, viewport, and presentation operations; no
example-private OpenGL dispatch table or SDL renderer is involved.
The sound example transpiles the upstream procedural tone and sweep calculations, narrows their results to Float32
PCM once, and plays three concurrent sources through the generated AudioDeviceBackend record returned by
SdkAudioDeviceBackend. Its --smoke mode runs with SDL's dummy audio driver and verifies application-thread
completion delivery.
Set -DFLIGHT_CPP_BUILD_HOST_SDL_VULKAN=OFF for an SDL and GL/WGPU build without Vulkan development files. Native
dependency discovery and target selection belong to CMake or Bazel, so an npm wrapper would only obscure their
options and is not provided.
The installed build exports five primary host targets through the existing FlightCpp package:
-
Flight::HostSdlinitializes ref-counted SDL subsystems, polls or waits forSDL_Event, exposes the monotonic SDL clock, and owns plain, OpenGL, or Vulkan windows.InputDispatcherconverts keyboard, text/IME, mouse, wheel, and standard-layout gamepad events into records matching Flight's input-ingress data shapes.Windowcontrols SDL text input and relative-pointer mode for the eventual generated ingress adapter.SdlAudioDeviceBackendimplements Flight's decoded-PCM device, buffer, and source lifecycle with live gain, equal-power pan, playback rate, bounded regions, and completion notification. It mixes mono or stereo Float32 sources in SDL's device callback and queues completions for serialized application-thread delivery.SdlCursorBackendmaps Flight's CSS cursor identifiers to SDL system cursors, implementsnonewith SDL visibility, and retains the requested value when a headless driver has no native cursor. Unknown or unsupported CSS cursor images use the SDL default. -
Flight::HostSdlImageowns no graphics context. It is the shared decoded-RGBA image carrier used by GL and WebGPU, with source-kind metadata and weak identity for texture caches. Native image decoders and video providers populate it; neither graphics target owns decoding or frame acquisition. -
Flight::HostSdlGlowns anSDL_GLContext, configures OpenGL or OpenGL ES attributes before window creation, resolves procedure addresses, controls the swap interval, and swaps the window. ItsGlCanvasandWebGl2Contextshare that owner and supply the native types named bybindings/sdl-gl.json. Image uploads consume the shared carrier selected bybindings/sdl-image.json. The context already forwards the operations used by the tween and exposes the anisotropic-filter extension constants and live availability query required by Flight's GL runtime. Buffer, framebuffer, renderbuffer, texture, vertex-array, shader, program, and uniform-location handles share WebGL-style identity. Explicit deletion invalidates every alias, remaining live resources are reclaimed while their context is alive, and cross-context handle use is rejected. Its selected command surface includes 2D/3D and compressed texture upload, framebuffer clear/blit and readback, active-uniform metadata, and the render state, vertex, draw, and uniform calls used by Flight.bindings/sdl-app.jsonlayers a browser-shaped application shell over this target for upstream examples. Its document attachment is intentionally lightweight, while its request/cancel frame queue and SDL input bridge preserve ordered animation turns and route keyboard, pointer, and wheel events into registered listeners onwindowandGlCanvas. SDL window focus, visibility, minimize, and restore events update the document facade and dispatch its focus/page lifecycle callbacks. The input surface also exposes value-owned standard-layout gamepad snapshots throughnavigator.getGamepads(), a common event carrier for Flight's base-event narrowing, and an identity-preserving typedCustomEvent<T>detail carrier.The document facade also covers the concrete button, details, div, heading, input, label, option, paragraph, select, span, and style element types used by the upstream examples.
document.createElement()returns one C++ facade because the compiler retainsautofor its tag-dependent TypeScript result; that facade exposes the DOM value surface directly and lazily creates an SDL GL canvas when a canvas context is requested. It does not implement a Canvas2D renderer. -
Flight::HostSdlVulkancopies the required instance extension names and owns theVkSurfaceKHRreturned by SDL. -
Flight::HostSdlWgpuowns a type-erased native WebGPU surface and typed shared WebGPU object handles through callbacks supplied by a Dawn or wgpu-native adapter. Handle copies and weak cache keys preserve identity, provider releases run exactly once, adapter capabilities remain value-owned metadata, and external image copies use the same decoded source as GL. It intentionally adds no WebGPU implementation dependency.
The composed SDK sweeps also include bindings/web-types.json. That profile maps standard Canvas, WebGPU,
image-smoothing, and permission string-literal domains to flight::String. It also supplies portable
DOMPointInit and CanvasRenderingContext2DSettings value dictionaries, preserving optional-member presence. It
selects no renderer or platform provider and keeps object-handle names available until a concrete backend profile
supplies them.
bindings/sdl-image.json maps the DOM image, video, bitmap, offscreen-canvas, SVG image, video-frame, GL image-source,
and WebGPU external-copy type domains to one shared source and weak-key policy. Browser constructor values remain
unbound until flight-compiler can lower external runtime type tests against the retained source kind.
bindings/sdl-wgpu.json supplies the WebGPU object handles, adapter features/limits,
device/origin/vertex/external-image/sampler descriptors, bind-group resources and layouts, buffer and texture
transfer descriptors, iterable extents,
shared buffer/view sources, blend and stencil pipeline values, and standard buffer, texture, shader, color-write,
and map-mode flags. Optional descriptor fields retain their source presence for the provider, and copied buffers and
views retain their backing identity.
It is a provider ABI rather than a renderer: device methods remain the responsibility of the selected Dawn or
wgpu-native adapter. Run npm run sdk:generate:sdl-wgpu for that profile alone, or npm run sdk:generate:sdl for
the complete GL + WebGPU + application-shell inventory.
An installed consumer selects only the backend it uses:
find_package(FlightCpp 0.1 CONFIG REQUIRED)
target_link_libraries(my_gl_app PRIVATE Flight::HostSdlGl)
# or: Flight::HostSdlVulkan / Flight::HostSdlWgpuSDL video operations must run on the main thread. Construct Host before Window; destroy GL contexts and Vulkan or
WebGPU surfaces before their window; destroy windows before Host. The types are move-only so ownership transfers are
explicit.
#include <flight/host_sdl/audio.hpp>
#include <flight/host_sdl/host.hpp>
#include <flight/host_sdl/window.hpp>
int main() {
flight::host_sdl::Host host;
flight::host_sdl::SdlAudioDeviceBackend audio;
flight::host_sdl::Window window({
.title = "Flight",
.width = 1280,
.height = 720,
.graphics_api = flight::host_sdl::GraphicsApi::vulkan,
});
flight::host_sdl::InputDispatcher input(window.id(), {
.key_down = [](const flight::host_sdl::InputKeyboardData& data) {
// Forward synchronously to the generated Flight InputIngressSink adapter.
},
});
SDL_Event event{};
bool running = true;
while (running) {
while (host.poll_event(event)) {
if (event.type == SDL_EVENT_QUIT) running = false;
input.dispatch(event);
}
host.pump_timers();
static_cast<void>(audio.pump());
// Update Flight, render through render-wgpu or render-gl, then present.
}
}Window::id() lets a future Flight input adapter associate an SDL event with the correct application window.
Window::size() reports logical units and Window::pixel_size() reports the renderable pixel extent for high-density
displays. Host::pump_timers() executes the headless binding profile's due timeout and interval callbacks on the SDL
thread; no background timer thread can race Flight state.
WebPlatformInput composes the persistent InputDispatcher with a GlCanvas, and
pump_animation_frame(timestamp_ms) runs the callbacks that were pending when that frame began. Callbacks scheduled
by another frame callback remain queued for the next turn, matching the browser ordering used by the examples.
Its SDL window-event path also keeps document.hidden and document.hasFocus() current and emits
visibilitychange, pagehide, pageshow, focus, and blur. The event stores accept
flight::Function<Signature>, deduplicate its copy-stable identity with the event type and capture flag, and expose
matching removal operations. The binding remains gated on flight-compiler electing that carrier for JavaScript
function values; plain std::function copies cannot identify the callback captured by Flight's returned unsubscribe
closure.
SdlAudioDeviceBackend::pump() delivers each completed source callback on the pumping thread. Call it once per host
turn, just like Host::pump_timers(). Destroying a source suppresses its pending completion, invalid handles follow
Flight's sentinel/no-op contract, and destroying a buffer does not invalidate sources that already acquired it.
Flight::HostSdlSdkCursor and Bazel //:host_sdl_sdk_cursor populate the committed generated
flight::types::CursorBackend record. Copies of the adapter and emitted record share the selected cursor state, and
the native operations stay on the SDL application thread.
Flight::HostSdlSdkClipboard and Bazel //:host_sdl_sdk_clipboard populate the committed generated
ClipboardTextBackend record. It exposes clear, nonempty-text detection, UTF-8 reading, and UTF-8 writing with the
same resolved-task sentinels as the Web host. SDL clipboard calls must run on the application thread. Rich formats,
images, bookmarks, and change notifications are not advertised by this adapter.
Flight::HostSdlSdkDevice and Bazel //:host_sdl_sdk_device populate the committed generated DeviceBackend.
The snapshot reports SDL's attached keyboard and mouse presence, the desktop display containing its window, safe
area, native architecture, logical CPU count, configured RAM, and canonical platform name. It leaves install id,
available memory, physical DPI, OS version, and hardware/product details at Flight's documented sentinels because
SDL does not expose them. Display and safe-area reads resolve the SDL window id each time and return sentinels after
the native window is destroyed.
Flight::HostSdlSdkHaptics and Bazel //:host_sdl_sdk_haptics populate the committed generated HapticsBackend.
It selects the first connected SDL gamepad whose properties report rumble support, owns the opened reference, and
rediscovers a device after disconnection. Continuous vibration, intensity-scaled impacts, notifications, selection,
prepare, and cancel map to SDL rumble. SDL has no timed multi-step rumble call, so capabilities report patterns and
custom waveforms unavailable and those operations fail without collapsing their timing into one buzz.
Flight::HostSdlSdkPlatform and Bazel //:host_sdl_sdk_platform populate the committed generated
PlatformBackend. It writes into Flight's caller-owned PlatformInfo, using SDL for the OS name, preferred locale,
and touch-device presence and the native compiler target for architecture, byte order, and pointer width. Version,
OS-build, and distribution values stay empty where SDL has no source for them.
Flight::HostSdlSdkScreen and Bazel //:host_sdl_sdk_screen populate the committed generated
ScreenQueryBackend, ScreenDetailsBackend, and ScreenChangeBackend. Query calls enumerate SDL displays into the caller-owned array and
report virtual-desktop bounds, work-area size, density, physical dimensions, refresh and pixel depth, orientation,
HDR state, labels, the primary display, and global pointer position. Native enumeration has no browser-style
permission prompt, so the details record resolves granted and true. Pass SDL display events to
SdkScreenBackend::dispatch() to emit exact added, removed, or metrics-changed records; each release closure removes
its own subscription. SDL does not expose physical DPI, gamut, luminance, or portable internal/touch classification.
Flight::HostSdlSdkKeyboard and Bazel //:host_sdl_sdk_keyboard populate the committed generated
SoftKeyboardInfoBackend, SoftKeyboardVisibilityBackend, and SoftKeyboardChangeBackend. SDL reports whether the
active driver has an on-screen keyboard, whether it is shown for the bound window, starts and stops native text
input, and emits global shown/hidden events. Pass each polled event to SdkSoftKeyboardBackend::dispatch(). Drivers
without on-screen keyboard support return Flight's acquisition/operation failure values. SDL does not expose the
keyboard rectangle, so geometry remains zero; platform-specific style and layout controls are separate capabilities.
Flight::HostSdlSdkWindow and Bazel //:host_sdl_sdk_window bind an SDL window id to the committed generated
ApplicationVisibilityBackend and FullscreenBackend records. Generated fullscreen target handles are registered
weakly, unknown handles return false, and records retained after native window destruction fail closed. The
fullscreen event callbacks remain absent from the optional fields because the current emitted std::function
carrier cannot satisfy callback-identity removal; the same issue gates the non-optional ApplicationExitBackend.
The adapter also creates weakly registered generated InputTargetHandle values and populates
InputTargetBackend, InputFocusBackend, InputDropFileBackend, and InputPointerLockBackend. Pass each polled
event to SdkWindowBackend::dispatch() as well as other host dispatchers to deliver focus and file paths. The
returned release closures remove the exact subscription, and pointer lock maps to SDL relative mouse mode.
The native mechanics are now present. Wiring them to generated Flight contracts remains a narrow integration task:
- Finish compiler emission of the narrowed Flight
GlContextinterface. The current compiler resolves the SDL/GL ambient bindings but leaves its inheritedviewportmember as an unresolved C++ type. - Populate that generated callable interface from
WebGl2Contextand connect compressed-extension enum lookup once the compiler selects the downstream orderedRecord<String, double>carrier. The SDL/GL binding profile, shared object handle identities and lifetime, image-source weak-key policy, anisotropy carrier, context ownership, buffer and texture upload, compressed texture upload, framebuffer clear/blit/readback, shader/program compilation, closed state queries, extension availability, state commands, vertex attributes, draw calls, uniform uploads, procedure lookup, and presentation path are present. A live compiler fixture emits and compiles exact calls through this native context, and the offscreen SDL smoke executes its ordinary texture/readback path. With the anisotropy ambient refusal removed,GlContextRuntimenow reaches the compiler's closed-value proof for one of itsWeakMapfields. Flight::HostSdlSdkAudioand Bazel//:host_sdl_sdk_audioalready populate the emittedflight::types::AudioDeviceBackendrecord and execute it against SDL's dummy driver in the host test. The CMake target andsdk_audio.hppare build-tree preview surfaces untilFlight::Sdkis installable. A compiler module remap must replace the sound example'swebAudioDeviceBackendprovider. Encoded sound bytes still need a native decoder/provider; decoded PCM now uses the portableflight::AudioBuffercarrier.Flight::HostSdlSdkCursorand Bazel//:host_sdl_sdk_cursorpopulate and execute the emittedflight::types::CursorBackendrecord. The compiler source-remap lane must select that native provider in place ofcreateWebCursorBackendfor the interaction and sound examples.Flight::HostSdlSdkWindowand Bazel//:host_sdl_sdk_windowpopulate the generated visibility, fullscreen command, target preparation, focus, file-drop, and pointer-lock records. Compiler adoption offlight::Functionwill unlock exact fullscreen event subscriptions andApplicationExitBackend; no identity approximation is installed in the meantime.- Implement generated
WgpuHostBackendandWgpuRenderSurfaceProviderwith a selected Dawn or wgpu-native adapter.WgpuSurfaceCallbacksis the stable point where that dependency enters. - Adapt
InputDispatcher's normalized records into the generated Flight input types onceInputPointerDataandInputIngressBackendclear their current generated dependency refusals. - Clear the remaining SDK and example compiler refusals, then replace the handwritten tween loop with the generated application module. The repository now selects and compiles all upstream WebGL example sources through a recorded source remap and the SDL application-shell profile.
The corresponding compiler work is recorded in the upstream request. The installed host package does not wait for those compiler changes; the preview adapter is isolated so compiler-driven type changes do not alter SDL ownership or the native playback implementation.
Run npm run sdk:generate:sdl to audit the full maintained host profile. It composes the runtime, headless, SDL/GL,
and SDL application-shell manifests under out/sdk-sdl; npm run sdk:compile:sdl writes its independent-header
report. The narrower sdk:generate:sdl-gl command remains useful when application-shell ambient bindings are not
wanted.