Skip to content

feat(input): Implement SDL3 input and window management#2639

Open
githubawn wants to merge 27 commits into
TheSuperHackers:mainfrom
githubawn:feature/sdl3-input-backport
Open

feat(input): Implement SDL3 input and window management#2639
githubawn wants to merge 27 commits into
TheSuperHackers:mainfrom
githubawn:feature/sdl3-input-backport

Conversation

@githubawn

@githubawn githubawn commented Apr 20, 2026

Copy link
Copy Markdown

SDL3 Input Backend
This PR implements an SDL3-based input and windowing backend as a modern alternative to DirectInput and Win32 window creation. By utilizing SDL3, we bypass DirectInput emulation layers on modern systems, providing a lower-latency pipeline for Windows 11 and Wine/Linux users.

Input handling
Unified event manager that centralizes keyboard, mouse, and gamepad events into a thread-safe buffer

Gamepad Support (v0.1)
This is an initial baseline implementation focused on providing functional out-of-the-box playability. Feedback is encouraged regarding the ergonomics and logic of these default mappings.

Input Action
Left Stick Virtual mouse cursor
Right Stick Camera pan (arrow keys)
LT (hold) Precision Mode
RT (hold) Force Attack (LCTRL)
LB Select All (Q)
RB Queue Orders (LSHIFT)
A Left click
B Right click
X Attack Move (A)
Y Stop (S)
D-Pad Control groups 1–4
Start Menu (ESC)
Back Last radar event (SPACE)
L3 Next idle worker
R3 Snap to command center

Scope Note: This implementation provides a hardcoded default layout to establish core functionality. Advanced features, such as input remapping, radial menus, adjustable deadzones, are currently out of scope for this PR and may be addressed in future iterations.

The foundation of this backend was built using the SDL3 input work from the generalsX fork by fbraz3.

Todo: replicate to generals

@githubawn githubawn force-pushed the feature/sdl3-input-backport branch from a785545 to 7cc0861 Compare April 20, 2026 15:00
@greptile-apps

greptile-apps Bot commented Apr 20, 2026

Copy link
Copy Markdown

Greptile Summary

This PR introduces a new SDL3-based input and window management backend as an opt-in alternative to the existing Win32/DirectInput path, enabled by default for modern (non-VS6) builds via RTS_SDL3_ENABLE. The implementation covers unified keyboard/mouse/gamepad event handling, animated cursor loading via SDL3_image, UTF-8 text input forwarding, and a letterbox-aware coordinate scaling layer.

  • SDL3InputManager centralises all SDL event polling into fixed-size ring buffers, with synthetic motion and button events for gamepad stick/button input; windowID is correctly populated on both paths.
  • SDL3CursorManager delegates ANI animation to IMG_LoadAnimation_IO, eliminating the prior m_frameCount out-of-bounds risk; SDL3GameEngine::init() always calls GameEngine::init() regardless of window state, resolving the previously noted headless-mode skip.
  • vcpkg-lock.json is regenerated to match the vcpkg.json overrides (sdl3@3.4.10, sdl3-image@3.4.4), and a FetchContent fallback pins the SDL_image commit that carries the ANI RIFF word-alignment fix.

Confidence Score: 5/5

Safe to merge; the SDL3 backend is opt-in and does not touch the existing Win32/DirectInput path.

All previously raised compile-time and runtime correctness concerns have been addressed in this revision. Remaining findings are style-rule violations only and do not affect correctness.

No files require special attention; the two style comments in WinMain.cpp and SDL3Input.cpp are the only open items.

Important Files Changed

Filename Overview
Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Input.cpp Core input manager, mouse, and keyboard implementations. Gamepad motion/button events now correctly set windowID from m_window. Inline if (d) bodies inside lambdas violate the project's debuggability style rule.
GeneralsMD/Code/Main/WinMain.cpp SDL3 window/init path added with proper SDL_Init failure return. Two NULL → nullptr rule violations, and one inline-if style violation in the SDL3 block.
Core/GameEngineDevice/Source/SDL3GameEngine.cpp SDL3GameEngine implements window integration, UTF-8 text input forwarding, and minimized-window throttle loop. GameEngine::init() is always called regardless of headless state. No issues found.
Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Cursor.cpp ANI cursor loading delegated to SDL3_image's IMG_LoadAnimation_IO; prior m_frameCount out-of-bounds concern is no longer applicable. Bounds checks present in getCursor(). No issues found.
cmake/sdl3.cmake Adds vcpkg/FetchContent fallback for SDL3 and SDL3_image with pinned commit for the ANI RIFF word-alignment fix. Static library alias and Windows system library linkage handled correctly.
vcpkg.json Adds sdl3 and sdl3-image dependencies with version overrides matching vcpkg-lock.json (3.4.10 / 3.4.4). Lock file now updated to match.

Sequence Diagram

sequenceDiagram
    participant WinMain
    participant SDL3GameEngine
    participant SDL3InputManager
    participant SDL3Mouse
    participant SDL3Keyboard
    participant GameEngine

    WinMain->>SDL3GameEngine: CreateGameEngine()
    WinMain->>WinMain: SDL_Init + SDL_CreateWindow → TheSDL3Window
    SDL3GameEngine->>SDL3GameEngine: init()
    SDL3GameEngine->>SDL3InputManager: new SDL3InputManager(m_SDLWindow)
    SDL3GameEngine->>GameEngine: GameEngine::init()
    GameEngine->>SDL3Mouse: createMouse() → new SDL3Mouse(TheSDL3Window)
    GameEngine->>SDL3Keyboard: createKeyboard() → new SDL3Keyboard()

    loop Game Loop
        SDL3GameEngine->>SDL3GameEngine: update() → pollSDL3Events()
        SDL3GameEngine->>SDL3InputManager: update()
        SDL3InputManager->>SDL3InputManager: SDL_PollEvent (keyboard/mouse/window/gamepad)
        SDL3InputManager->>SDL3InputManager: processGamepadInput()
        Note over SDL3InputManager: synthetic mouse/key events injected with windowID set
        SDL3InputManager-->>SDL3Mouse: addMouseSDLEvent (ring buffer)
        SDL3InputManager-->>SDL3Keyboard: addKeyboardSDLEvent (ring buffer)
        SDL3Mouse->>SDL3Mouse: getMouseEvent → translateEvent → scaleMouseCoordinates
        SDL3Keyboard->>SDL3Keyboard: getKey → translateScanCodeToKeyVal
    end
Loading
Prompt To Fix All With AI
Fix the following 2 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 2
GeneralsMD/Code/Main/WinMain.cpp:899
**Inline `if` body on same line as condition**

`if (!TheGlobalData->m_windowed) flags |= SDL_WINDOW_FULLSCREEN;` places the body on the same line as the condition, which prevents targeted breakpoint placement. Per the project style, the body should be on its own line. The same pattern appears in `SDL3Input.cpp` inside the `processGamepadInput` lambdas: `if (d) TheMessageStream->appendMessage(...)` (three occurrences for NORTH, LEFT_STICK, and RIGHT_STICK buttons).

### Issue 2 of 2
GeneralsMD/Code/Main/WinMain.cpp:912-915
**`NULL` used instead of `nullptr` in SDL3 API calls**

Two SDL3 calls in this block pass `NULL` for pointer parameters: `SDL_GetPointerProperty(props, SDL_PROP_WINDOW_WIN32_HWND_POINTER, NULL)` and `SDL_BlitSurfaceScaled(gLoadScreenSurface, NULL, screen, ...)`. Both `NULL` arguments are pointer-typed parameters and should be `nullptr` per the project's C++ coding standard.

Reviews (28): Last reviewed commit: "build: update vcpkg dependencies and fix..." | Re-trigger Greptile

Comment thread Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Input.cpp Outdated
Comment thread Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Input.cpp Outdated
Comment thread Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Input.cpp Outdated
Comment thread Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Input.cpp
Comment thread cmake/sdl3.cmake Outdated
Comment thread cmake/sdl3.cmake
Comment thread GeneralsMD/Code/Main/WinMain.cpp
Comment thread cmake/config-build.cmake Outdated
Comment thread cmake/config-build.cmake Outdated
@@ -9,6 +9,16 @@ option(RTS_BUILD_OPTION_ASAN "Build code with Address Sanitizer." OFF)
option(RTS_BUILD_OPTION_VC6_FULL_DEBUG "Build VC6 with full debug info." OFF)
option(RTS_BUILD_OPTION_FFMPEG "Enable FFmpeg support" OFF)

# Enable SDL3 by default for modern builds
if(NOT IS_VS6_BUILD)
option(SAGE_USE_SDL3 "Enable SDL3 input/window backend" ON)

@stephanmeesters stephanmeesters Apr 20, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Options should be on top of the file and be something like RTS_BUILD_OPTION_SDL3


namespace {

Bool DecodeNextUtf8Codepoint(const char* text, size_t length, size_t& offset, UnsignedInt& outCodepoint)

@stephanmeesters stephanmeesters Apr 20, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need this, maybe better placed in UnicodeString? Or some other helper if it's generic stuff

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point.

I looked at #2045 and #2528 which are working on UTF-8 infrastructure.
If one of those lands first, forwardTextInputEvent could use the bulk Utf8_To_Utf16Le conversion and iterate the resulting wchar_t string directly, making this function unnecessary.

Happy to refactor once there's a clear winner between those two. For now it's self-contained here.

@githubawn githubawn force-pushed the feature/sdl3-input-backport branch from e365605 to 627ef23 Compare April 20, 2026 18:24
@githubawn githubawn changed the title feat(input): Implement SDL3 input backend feat(input): Implement SDL3 input and window management Apr 20, 2026
Consolidated all work from the test/sdl3-backport branch into a single atomic commit:
- Centralized input management via SDL3InputManager.
- Hardened Ani/RIFF cursor loading with robust bounds checking.
- Native Gamepad support with analogue stick-to-mouse emulation and custom RTS mappings.
- Modernized focus and capture handling for better stability during Alt-Tab.
- Standardized and secured string operations throughout the SDL3 path.
Consolidated all work from the test/sdl3-backport branch into a single atomic commit:
- Centralized input management via SDL3InputManager.
- Hardened Ani/RIFF cursor loading with robust bounds checking.
- Native Gamepad support with analogue stick-to-mouse emulation and custom RTS mappings.
- Modernized focus and capture handling for better stability during Alt-Tab.
- Standardized and secured string operations throughout the SDL3 path.
- Updated credit attribution (fbraz3).
@githubawn githubawn force-pushed the feature/sdl3-input-backport branch from eb9908a to 534e694 Compare April 21, 2026 01:17
Comment thread GeneralsMD/Code/Main/WinMain.cpp Outdated
Comment thread Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Input.cpp Outdated
Comment thread Core/GameEngineDevice/Source/SDL3GameEngine.cpp Outdated
Comment thread Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Cursor.cpp
@DevGeniusCode DevGeniusCode added this to the Linux support milestone Apr 29, 2026
@xezon xezon added Gen Relates to Generals ZH Relates to Zero Hour Platform Work towards platform support, such as Linux, MacOS Input labels May 12, 2026

@xezon xezon left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs polishing. Not yet reviewed extensively until the obvious style issues are fixed.

Comment thread vcpkg-lock.json
Comment thread CMakeLists.txt Outdated

#if SAGE_USE_SDL3
#include <SDL3/SDL.h>
#include "SDL3GameEngine.h"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This SDL code is added to a file called WinMain (for Windows). Is this intentional?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was intentional. The SDL3 input backend is designed with a future splitscreen skirmish feature in mind, supporting multiple simultaneous mice/keyboards so that 2–8 players can share a single machine rather than needing separate computers, either controlling the same or unique armies.

SDL3 input doesn't function without an SDL window, so creating the window here was the minimum viable approach to make the input backend functional. The Win32/DirectInput path doesn't lend itself to the multi-device model SDL3 enables, which is why SDL3 is the foundation for this direction.

@xezon xezon May 27, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't we have a SDL3Main then? I have seen other forks do that.

Maybe make WinMain and SDL3Main share code if they do. But basically get rid of the ifdef approach.


ParticleSystemManager* SDL3GameEngine::createParticleSystemManager(Bool dummy)
{
(void)dummy;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not create dummy here?


namespace {

Bool DecodeNextUtf8Codepoint(const char* text, size_t length, size_t& offset, UnsignedInt& outCodepoint)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function looks out of place. Bobtista was also working on Utf8 decoding in another change. It would be good to have this as a utility somewhere accessible engine wide, and not specific to this file.

m_gamepad(nullptr),
m_precisionMode(FALSE),
m_lastUpdateTime(0),
m_isQuitting(FALSE)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or use false

Comment thread Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Input.cpp Outdated
handleGamepadButton(SDL_GAMEPAD_BUTTON_DPAD_RIGHT, m_state.buttonState[SDL_GAMEPAD_BUTTON_DPAD_RIGHT], SDL_GetGamepadButton(m_gamepad, SDL_GAMEPAD_BUTTON_DPAD_RIGHT), [&](bool d){ virtualPulseKey(SDL_SCANCODE_3, d); });
handleGamepadButton(SDL_GAMEPAD_BUTTON_DPAD_DOWN, m_state.buttonState[SDL_GAMEPAD_BUTTON_DPAD_DOWN], SDL_GetGamepadButton(m_gamepad, SDL_GAMEPAD_BUTTON_DPAD_DOWN), [&](bool d){ virtualPulseKey(SDL_SCANCODE_4, d); });
handleGamepadButton(SDL_GAMEPAD_BUTTON_LEFT_STICK, m_state.buttonState[SDL_GAMEPAD_BUTTON_LEFT_STICK], SDL_GetGamepadButton(m_gamepad, SDL_GAMEPAD_BUTTON_LEFT_STICK), [&](bool d){ if (d) TheMessageStream->appendMessage(GameMessage::MSG_META_SELECT_NEXT_IDLE_WORKER); });
handleGamepadButton(SDL_GAMEPAD_BUTTON_RIGHT_STICK, m_state.buttonState[SDL_GAMEPAD_BUTTON_RIGHT_STICK], SDL_GetGamepadButton(m_gamepad, SDL_GAMEPAD_BUTTON_RIGHT_STICK), [&](bool d){ if (d) TheMessageStream->appendMessage(GameMessage::MSG_META_VIEW_COMMAND_CENTER); });

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These lines are awfully long.

Comment thread cmake/config-build.cmake Outdated
Comment thread cmake/config-build.cmake Outdated
@xezon

xezon commented May 12, 2026

Copy link
Copy Markdown

Perhaps also check Fighter19's fork for SDL related implementations. As far as I am aware he has it all done.

@githubawn

Copy link
Copy Markdown
Author

I have looked at all existing forks and attributed where possible.

Comment thread Core/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Input.cpp
@bobtista

bobtista commented Jun 3, 2026

Copy link
Copy Markdown

How about changing display-mode? eg getDisplayModeCount() / getDisplayModeDescription() using SDL_GetFullscreenDisplayModes + desktop mode + resolution filtering. EDIT: Also account for mouse position which can sometimes not match clicks to cursor position.

@bobtista

bobtista commented Jun 3, 2026

Copy link
Copy Markdown

Is it better to use SDL3InputManager (+ SDL3CursorManager) or to split out SDL3Keyboard + SDL3Mouse that subclass the engine's existing Keyboard/Mouse base classes?

- Switched SDL3_image to the latest GitHub main branch commit to natively resolve the RIFF/ANI odd-chunk parsing bug.
- Removed the legacy cbSize header patching workaround in SDL3CursorManager::loadANI.
- Kept base SDL3 library pinned to stable release-3.4.10.
- Cleaned up transient code-level header comments.

Co-authored-by: fbraz3 <fbraz3@users.noreply.github.com>
Co-authored-by: Fighter19 <Fighter19@users.noreply.github.com>
Co-authored-by: feliwir <feliwir@users.noreply.github.com>
Comment thread vcpkg-lock.json
githubawn and others added 2 commits June 10, 2026 00:37
- Replaced (void) with empty parentheses () in SDL3Input and SDL3GameEngine declarations and definitions.

Co-authored-by: fbraz3 <fbraz3@users.noreply.github.com>
Co-authored-by: Fighter19 <Fighter19@users.noreply.github.com>
Co-authored-by: feliwir <feliwir@users.noreply.github.com>
- Updated vcpkg.json overrides and regenerated vcpkg-lock.json (sdl3 to 3.4.10, sdl3-image to 3.4.4).
- Normalized formatting on vcpkg.json using the official vcpkg format-manifest tool.
- Changed translateScanCodeToKeyVal parameter type to SDL_Scancode to prevent high-range media keys from wrapping into valid letter keycodes.
- Documented SDL3_image FetchContent Git pin reasoning in cmake/sdl3.cmake.

Co-authored-by: fbraz3 <fbraz3@users.noreply.github.com>
Co-authored-by: Fighter19 <Fighter19@users.noreply.github.com>
Co-authored-by: feliwir <feliwir@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Gen Relates to Generals Input Platform Work towards platform support, such as Linux, MacOS ZH Relates to Zero Hour

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants