Skip to content
Draft
Show file tree
Hide file tree
Changes from 29 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
e406fca
SDL3 Input: Complete backport with Gamepad support and hardening
githubawn Apr 20, 2026
17f1816
SDL3 Input: Complete backport with Gamepad support and hardening
githubawn Apr 20, 2026
bd9921f
added build guards
githubawn Apr 20, 2026
86f996b
greptile feedback
githubawn Apr 20, 2026
43beb41
greptile feedback
githubawn Apr 20, 2026
96d0b3d
fixed build error
githubawn Apr 20, 2026
47b934c
greptile feedback
githubawn Apr 20, 2026
8cfcef4
added vcpkg
githubawn Apr 20, 2026
ebab585
vcpkg fix
githubawn Apr 20, 2026
2774a78
changed dpad group numbers
githubawn Apr 20, 2026
186eaf7
switch back to static linking
githubawn Apr 20, 2026
6b3bec1
move SDL3_image out SDL3input
githubawn Apr 21, 2026
d775816
properly fail on SDL_INIT failure
githubawn Apr 21, 2026
40d480c
removed custom ICO decoder
githubawn Apr 21, 2026
a959ecb
fixes SDL_WarpMouseInWindow mismatch
githubawn Apr 21, 2026
ea63a09
removed some dead declarations
githubawn Apr 21, 2026
13f1f66
temporary re-added custom decoder
githubawn Apr 21, 2026
94d3f4b
dont early return for headless mode
githubawn Apr 22, 2026
c75d632
applied clang to files new in this PR
githubawn May 14, 2026
4b87f56
updated SDL to latest version
githubawn Jun 1, 2026
2a55d23
renamed SAGE_USE_SDL3 to RTS_SDL3_ENABLE
githubawn Jun 1, 2026
8533760
cleaned up comments
githubawn Jun 1, 2026
25d8a42
build: upgrade SDL_image and clean up SDL3Cursor loadANI
githubawn Jun 9, 2026
57bcdf9
style: remove legacy empty parameter list style (void) -> ()
githubawn Jun 9, 2026
5e4c4d7
build: update vcpkg dependencies and fix SDL3 input scancode translation
githubawn Jun 9, 2026
808f8d0
move cmake to RTS_BUILD_OPTION_SDL3, fix sorting
githubawn Jun 10, 2026
81bf8f9
Move SDL3GameEngine files to Common directory.
githubawn Jun 12, 2026
785d213
style/fix: address PR review feedback on SDL3 input and engine device
githubawn Jul 23, 2026
78f7e15
Refactor entry points: extract AppMain lifecycle and isolate SDL3Main
githubawn Jul 23, 2026
6640a47
fix(build): correct include ordering in AppMain and SDL3Main for VC6 …
githubawn Jul 23, 2026
a19098f
fix(sdl3): address PR review feedback on SDL3 input and app lifecycle
githubawn Jul 23, 2026
3d7a346
feat(sdl3): backport gamepad stick acceleration and camera modulation
githubawn Jul 23, 2026
826c951
fix(sdl3): use logical window points in scaleMouseCoordinates
githubawn Jul 23, 2026
3bd8573
feat(sdl3): gamepad camera scrolling and double-click Guard mode
githubawn Jul 23, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ include(cmake/gamespy.cmake)
include(cmake/lzhl.cmake)
include(cmake/stb.cmake)

if(RTS_BUILD_OPTION_SDL3 AND NOT IS_VS6_BUILD)
include(cmake/sdl3.cmake)
endif()

if (IS_VS6_BUILD)
# The original max sdk does not compile against a modern compiler.
# If there is a desire to make this work, then a fixed max sdk needs to be created.
Expand Down
5 changes: 5 additions & 0 deletions Core/GameEngine/Include/GameClient/Display.h
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,11 @@ class Display : public SubsystemInterface
virtual UnsignedInt getBitDepth() { return m_bitDepth; }
virtual void setWindowed( Bool windowed ) { m_windowed = windowed; } ///< set windowed/fullscreen flag
virtual Bool getWindowed() { return m_windowed; } ///< return widowed/fullscreen flag

#if RTS_SDL3_ENABLE
virtual Bool getViewportRect( Int& x, Int& y, Int& width, Int& height ) const { return FALSE; }
#endif

virtual Bool setDisplayMode( UnsignedInt xres, UnsignedInt yres, UnsignedInt bitdepth, Bool windowed ); ///<sets screen resolution/mode
virtual Int getDisplayModeCount() {return 0;} ///<return number of display modes/resolutions supported by video card.
virtual void getDisplayModeDescription(Int modeIndex, Int *xres, Int *yres, Int *bitDepth) {} ///<return description of mode
Expand Down
20 changes: 20 additions & 0 deletions Core/GameEngineDevice/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,18 @@ set(GAMEENGINEDEVICE_SRC
Source/Win32Device/GameClient/Win32Mouse.cpp
)

# Add Core-level SDL3 implementation
if(RTS_BUILD_OPTION_SDL3 AND NOT IS_VS6_BUILD)
list(APPEND GAMEENGINEDEVICE_SRC
Include/SDL3Device/Common/SDL3GameEngine.h
Include/SDL3Device/GameClient/SDL3Input.h
Include/SDL3Device/GameClient/SDL3Cursor.h
Source/SDL3Device/Common/SDL3GameEngine.cpp
Source/SDL3Device/GameClient/SDL3Input.cpp
Source/SDL3Device/GameClient/SDL3Cursor.cpp
)
endif()

# Add C++ 17 FileSystem implementation for non-VS6 builds
if(NOT IS_VS6_BUILD)
list(APPEND GAMEENGINEDEVICE_SRC
Expand Down Expand Up @@ -233,6 +245,14 @@ target_link_libraries(corei_gameenginedevice_public INTERFACE
milesstub
)

# Export SDL3 dependencies for modern builds
if(RTS_BUILD_OPTION_SDL3 AND NOT IS_VS6_BUILD)
target_link_libraries(corei_gameenginedevice_public INTERFACE
SDL3::SDL3
SDL3_image::SDL3_image
)
endif()

if(RTS_BUILD_OPTION_FFMPEG)
find_package(FFMPEG REQUIRED)

Expand Down
85 changes: 85 additions & 0 deletions Core/GameEngineDevice/Include/SDL3Device/Common/SDL3GameEngine.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/*
** Command & Conquer Generals Zero Hour(tm)
** Copyright 2026 TheSuperHackers
**
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation, either version 3 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

#pragma once

#include "Lib/BaseType.h"

#include <SDL3/SDL.h>

#include "Common/GameEngine.h"

// SDL3 window typically provided by WinMain integration
extern SDL_Window* TheSDL3Window;

// Forward declarations for base classes
class AudioManager;
class Mouse;
class Keyboard;
class GameWindow;
class LocalFileSystem;
class ArchiveFileSystem;
class ThingFactory;
class ModuleFactory;
class FunctionLexicon;
class Radar;
class WebBrowser;
class ParticleSystemManager;

class SDL3GameEngine : public GameEngine
{
public:
SDL3GameEngine();
virtual ~SDL3GameEngine();

// GameEngine interface
virtual void init() override;
virtual void reset() override;
virtual void update() override;
virtual void serviceWindowsOS() override;
virtual Bool isActive() override;
virtual void setIsActive(Bool isActive) override;

// Factory methods (override GameEngine)
virtual LocalFileSystem* createLocalFileSystem() override;
virtual ArchiveFileSystem* createArchiveFileSystem() override;
virtual GameLogic* createGameLogic() override;
virtual GameClient* createGameClient() override;
virtual ModuleFactory* createModuleFactory() override;
virtual ThingFactory* createThingFactory() override;
virtual FunctionLexicon* createFunctionLexicon() override;
virtual Radar* createRadar(Bool dummy) override;
virtual WebBrowser* createWebBrowser() override;
virtual ParticleSystemManager* createParticleSystemManager(Bool dummy) override;
virtual AudioManager* createAudioManager(Bool dummy) override;

// SDL3 specific
virtual SDL_Window* getSDLWindow() const { return m_SDLWindow; }
virtual void forwardTextInputEvent(const char* utf8Text);

protected:
SDL_Window* m_SDLWindow;
Bool m_IsInitialized;
Bool m_IsActive;
Bool m_IsTextInputActive;
GameWindow* m_TextInputFocusWindow;

// Event processing
void pollSDL3Events();
void updateTextInputState();
};
61 changes: 61 additions & 0 deletions Core/GameEngineDevice/Include/SDL3Device/GameClient/SDL3Cursor.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
** Command & Conquer Generals Zero Hour(tm)
** Copyright 2026 TheSuperHackers
**
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation, either version 3 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

#pragma once

#include "Lib/BaseType.h"

#include <array>
#include <SDL3/SDL.h>

#include "GameClient/Mouse.h"

struct AnimatedCursor
{
SDL_Cursor* m_cursor;

AnimatedCursor()
: m_cursor(nullptr)
{}
~AnimatedCursor()
{
if (m_cursor)
{
SDL_DestroyCursor(m_cursor);
m_cursor = nullptr;
}
}

SDL_Cursor* getCursor() const { return m_cursor; }
};

class SDL3CursorManager
{
public:
static void init();
static void shutdown();

static SDL_Cursor* getCursor(Mouse::MouseCursor cursor, int direction);

// Internal loader used by Mouse implementation
static void initResources(Mouse* mouse);

private:
static AnimatedCursor* loadANI(const char* filepath);
static AnimatedCursor* m_cursorResources[Mouse::NUM_MOUSE_CURSORS][MAX_2D_CURSOR_DIRECTIONS];
};
182 changes: 182 additions & 0 deletions Core/GameEngineDevice/Include/SDL3Device/GameClient/SDL3Input.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
/*
** Command & Conquer Generals Zero Hour(tm)
** Copyright 2026 TheSuperHackers
**
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation, either version 3 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

#pragma once

#include "Lib/BaseType.h"

// SYSTEM INCLUDES
#include <SDL3/SDL.h>
#include <array>
#include <functional>

// USER INCLUDES
#include "GameClient/Mouse.h"
#include "GameClient/Keyboard.h"
#include "GameClient/KeyDefs.h"

// FORWARD REFERENCES
class SDL3InputManager;

extern SDL3InputManager* TheSDL3InputManager;

typedef KeyDefType KeyVal;

class SDL3Mouse : public Mouse
{
public:
SDL3Mouse(SDL_Window* window);
virtual ~SDL3Mouse();

// SubsystemInterface
virtual void init() override;
virtual void reset() override;
virtual void update() override;
virtual void initCursorResources() override;
static void freeCursorResources();

// Mouse interface
virtual void setCursor(MouseCursor cursor) override;
virtual void setVisibility(Bool visible) override;
virtual void loseFocus() override;
virtual void regainFocus() override;

// SDL3-specific methods
void addSDLEvent(SDL_Event* event);

protected:
virtual void capture() override;
virtual void releaseCapture() override;
virtual UnsignedByte getMouseEvent(MouseIO* result, Bool flush) override;

private:
// Event translation from SDL_Event (Clean Slate implementation)
void translateEvent(const SDL_Event& event, MouseIO* result);

// Scale raw SDL window coordinates to game internal resolution
void scaleMouseCoordinates(int rawX, int rawY, Uint32 windowID, int& scaledX, int& scaledY);

SDL_Window* m_Window;
Bool m_IsCaptured;
Bool m_IsVisible;
Bool m_LostFocus;

Int m_directionFrame;

float m_accumulatedDeltaX;
float m_accumulatedDeltaY;

SDL_Cursor* m_activeSDLCursor;
};

class SDL3Keyboard : public Keyboard
{
public:
SDL3Keyboard();
virtual ~SDL3Keyboard();

// SubsystemInterface
virtual void init() override;
virtual void reset() override;
virtual void update() override;

// Keyboard interface
virtual Bool getCapsState() override;

// SDL3-specific methods
void addSDLEvent(SDL_Event* event);

protected:
virtual void getKey(KeyboardIO* key) override;
virtual KeyVal translateScanCodeToKeyVal(SDL_Scancode scan);

private:
void translateKeyEvent(const SDL_KeyboardEvent& event);
};

class SDL3InputManager
{
public:
SDL3InputManager(SDL_Window* window);
virtual ~SDL3InputManager();

void update();

// Buffer access
Bool getNextMouseEvent(SDL_Event& outEvent);
Bool getNextKeyboardEvent(SDL_Event& outEvent);

void addMouseSDLEvent(const SDL_Event& event);
void addKeyboardSDLEvent(const SDL_Event& event);

Bool isQuitting() const { return m_isQuitting; }

// Constants
static constexpr float AXIS_MAX = 32767.0f;
static constexpr int TRIGGER_THRESHOLD = 16384;
static constexpr float DEFAULT_DEADZONE = 0.15f;
static constexpr float DEFAULT_CURSOR_SPEED = 800.0f;

private:
struct GamepadState
{
bool buttonState[SDL_GAMEPAD_BUTTON_COUNT];
bool stickLeft, stickRight, stickUp, stickDown;
bool ltDown, rtDown;

GamepadState()
{
memset(buttonState, 0, sizeof(buttonState));
stickLeft = stickRight = stickUp = stickDown = false;
ltDown = rtDown = false;
}
};

private:
// Gamepad management
void openFirstGamepad();
void closeGamepad();

SDL_Window* m_window;
SDL_Gamepad* m_gamepad;
void processGamepadInput();
void handleGamepadButton(SDL_GamepadButton button, bool& currentState, bool isDown, std::function<void(bool)> action);

// Virtual event injection
void virtualPulseKey(SDL_Scancode scancode, bool down);
void virtualPulseMouse(Uint8 button, bool down);

// Event buffers
static const UnsignedInt MAX_MOUSE_EVENTS = 256;
static const UnsignedInt MAX_KEY_EVENTS = 256;

SDL_Event m_mouseEvents[MAX_MOUSE_EVENTS];
UnsignedInt m_mouseNextFree;
UnsignedInt m_mouseNextGet;

SDL_Event m_keyEvents[MAX_KEY_EVENTS];
UnsignedInt m_keyNextFree;
UnsignedInt m_keyNextGet;

// Gamepad state
GamepadState m_state;

Bool m_precisionMode;
Uint64 m_lastUpdateTime;
Bool m_isQuitting;
};
Loading
Loading