Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
14 changes: 13 additions & 1 deletion .github/workflows/windows_builds.yml
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,18 @@ jobs:
fi
continue-on-error: true

- name: Download GameInput
shell: sh
id: gameinput
run: |
if python ./misc/scripts/install_gameinput_windows.py; then
echo "GAMEINPUT_ENABLED=yes" >> "$GITHUB_OUTPUT"
else
echo "::warning::Windows: GameInput installation failed, building without GameInput support and using XInput instead."
echo "GAMEINPUT_ENABLED=no" >> "$GITHUB_OUTPUT"
fi
continue-on-error: true

- name: Download pre-built ANGLE static libraries
uses: dsaltares/fetch-gh-release-asset@1.1.2
with:
Expand All @@ -109,7 +121,7 @@ jobs:
- name: Compilation
uses: ./.github/actions/godot-build
with:
scons-flags: ${{ env.SCONS_FLAGS }} ${{ matrix.scons-flags }} d3d12=${{ steps.d3d12-sdk.outputs.D3D12_ENABLED }}
scons-flags: ${{ env.SCONS_FLAGS }} ${{ matrix.scons-flags }} d3d12=${{ steps.d3d12-sdk.outputs.D3D12_ENABLED }} gameinput=${{ steps.gameinput.outputs.GAMEINPUT_ENABLED }}
platform: windows
target: ${{ matrix.target }}

Expand Down
1 change: 1 addition & 0 deletions SConstruct
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,7 @@ opts.Add(BoolVariable("use_volk", "Use the volk library to load the Vulkan loade
opts.Add(BoolVariable("accesskit", "Use AccessKit C SDK", True))
opts.Add(("accesskit_sdk_path", "Path to the AccessKit C SDK", ""))
opts.Add(BoolVariable("sdl", "Enable the SDL3 input driver", True))
opts.Add(BoolVariable("gameinput", "Enable the GameInput input driver on supported platforms", False))
opts.Add(
EnumVariable(
"profiler", "Specify the profiler to use", "none", ["none", "tracy", "perfetto", "instruments"], ignorecase=2
Expand Down
16 changes: 13 additions & 3 deletions drivers/sdl/SCsub
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#!/usr/bin/env python
from misc.utility.scons_hints import *

import os

Import("env")

env_sdl = env.Clone()
Expand Down Expand Up @@ -163,7 +165,6 @@ if env["builtin_sdl"]:
elif env["platform"] == "windows":
env_sdl.Append(CPPDEFINES=["SDL_PLATFORM_WINDOWS"])
thirdparty_sources += [
"core/windows/SDL_gameinput.c",
"core/windows/SDL_hid.c",
"core/windows/SDL_immdevice.c",
"core/windows/SDL_windows.c",
Expand All @@ -172,10 +173,9 @@ if env["builtin_sdl"]:
"haptic/windows/SDL_dinputhaptic.c",
"haptic/windows/SDL_windowshaptic.c",
"joystick/windows/SDL_dinputjoystick.c",
"joystick/windows/SDL_rawinputjoystick.c",
"joystick/windows/SDL_windows_gaming_input.c",
"joystick/windows/SDL_windowsjoystick.c",
"joystick/windows/SDL_xinputjoystick.c",
"loadso/windows/SDL_sysloadso.c",
"thread/generic/SDL_syscond.c",
"thread/generic/SDL_sysrwlock.c",
"sensor/windows/SDL_windowssensor.c",
Expand All @@ -188,6 +188,16 @@ if env["builtin_sdl"]:
"timer/windows/SDL_systimer.c",
]

gameinput_path = env["gameinput_path"]
if env["gameinput"] and os.path.exists(gameinput_path):
thirdparty_sources += [
"core/windows/SDL_gameinput.cpp",
"joystick/gdk/SDL_gameinputjoystick.cpp",
]

env_sdl.Prepend(CPPPATH=[os.path.join(gameinput_path, "native", "include", "v2")])
env_sdl.Prepend(CPPDEFINES=["HAVE_GAMEINPUT_H"])

elif env["platform"] in ["ios", "visionos"]:
if env["platform"] == "ios":
env_sdl.Append(CPPDEFINES=["SDL_PLATFORM_IOS"])
Expand Down
14 changes: 9 additions & 5 deletions drivers/sdl/SDL_build_config_private.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,20 +59,24 @@
#define HAVE_LIBC 1
#define HAVE_DINPUT_H 1
#define HAVE_XINPUT_H 1
#if defined(_WIN32_MAXVER) && _WIN32_MAXVER >= 0x0A00 /* Windows 10 SDK */
#define HAVE_WINDOWS_GAMING_INPUT_H 1
#define SDL_JOYSTICK_WGI 1

#ifdef HAVE_GAMEINPUT_H // May be defined in "drivers/sdl/SCsub"
#define SDL_JOYSTICK_GAMEINPUT 1
#endif

#define SDL_JOYSTICK_DINPUT 1
#define SDL_JOYSTICK_HIDAPI 1
#define SDL_JOYSTICK_RAWINPUT 1
#define SDL_JOYSTICK_XINPUT 1
#define SDL_JOYSTICK_HIDAPI 1

#define SDL_HAPTIC_DINPUT 1

#define SDL_THREAD_GENERIC_COND_SUFFIX 1
#define SDL_THREAD_GENERIC_RWLOCK_SUFFIX 1
#define SDL_THREAD_WINDOWS 1

#define SDL_TIMER_WINDOWS 1
#define SDL_SENSOR_WINDOWS 1
#define SDL_LOADSO_WINDOWS 1

// Linux defines
#elif defined(SDL_PLATFORM_LINUX)
Expand Down
7 changes: 4 additions & 3 deletions drivers/sdl/joypad_sdl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ JoypadSDL::~JoypadSDL() {

Error JoypadSDL::initialize() {
SDL_SetHint(SDL_HINT_JOYSTICK_THREAD, "1");
SDL_SetHint(SDL_HINT_JOYSTICK_GAMEINPUT, "1");
SDL_SetHint(SDL_HINT_NO_SIGNAL_HANDLERS, "1");
ERR_FAIL_COND_V_MSG(!SDL_Init(SDL_INIT_JOYSTICK | SDL_INIT_GAMEPAD), FAILED, SDL_GetError());

Expand Down Expand Up @@ -137,15 +138,15 @@ void JoypadSDL::process_events() {
device_name = SDL_GetGamepadName(gamepad);
joy = SDL_GetGamepadJoystick(gamepad);

print_verbose(vformat("SDL: Gamepad %s connected", SDL_GetGamepadName(gamepad)));
print_verbose(vformat("SDL: Gamepad %s connected", device_name));
} else {
joy = SDL_OpenJoystick(sdl_event.jdevice.which);
ERR_CONTINUE_MSG(!joy,
vformat("Error opening joystick at index %d: %s", sdl_event.jdevice.which, SDL_GetError()));

device_name = SDL_GetJoystickName(joy);
device_name = String::utf8(SDL_GetJoystickName(joy));

print_verbose(vformat("SDL: Joystick %s connected", SDL_GetJoystickName(joy)));
print_verbose(vformat("SDL: Joystick %s connected", device_name));
}

const int MAX_GUID_SIZE = 64;
Expand Down
57 changes: 57 additions & 0 deletions misc/scripts/install_gameinput_windows.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#!/usr/bin/env python3

if __name__ != "__main__":
raise SystemExit(f'Utility script "{__file__}" should not be used as a module!')

import argparse
import os
import shutil
import sys
import urllib.request

sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../"))

from misc.utility.color import Ansi, color_print

parser = argparse.ArgumentParser(description="Install GameInput dependencies for Windows platforms.")
parser.add_argument(
"--mingw_prefix",
default=os.getenv("MINGW_PREFIX", ""),
help="Explicitly specify a path containing the MinGW bin folder.",
)
args = parser.parse_args()

# Base Godot dependencies path
# If cross-compiling (no LOCALAPPDATA), we install in `bin`
deps_folder = os.getenv("LOCALAPPDATA")
if deps_folder:
deps_folder = os.path.join(deps_folder, "Godot", "build_deps")
else:
deps_folder = os.path.join("bin", "build_deps")

# Check for latest version: https://www.nuget.org/api/v2/package/Microsoft.GameInput (check downloaded filename)
gameinput_version = "3.2.135"
gameinput_archive = os.path.join(deps_folder, f"microsoft.gameinput.{gameinput_version}.nupkg")
gameinput_folder = os.path.join(deps_folder, "gameinput")

# Create dependencies folder
if not os.path.exists(deps_folder):
os.makedirs(deps_folder)

if os.path.isfile(gameinput_archive):
os.remove(gameinput_archive)
print(f"Downloading GameInput {gameinput_version} ...")
urllib.request.urlretrieve(
f"https://www.nuget.org/api/v2/package/Microsoft.GameInput/{gameinput_version}", gameinput_archive
)
if os.path.exists(gameinput_folder):
print(f"Removing existing local GameInput installation in {gameinput_folder} ...")
shutil.rmtree(gameinput_folder)
print(f"Extracting GameInput {gameinput_version} to {gameinput_folder} ...")
shutil.unpack_archive(gameinput_archive, gameinput_folder, "zip")
os.remove(gameinput_archive)
print(f"GameInput {gameinput_version} installed successfully.\n")

# Complete message
color_print(f'{Ansi.GREEN}All GameInput components were installed to "{deps_folder}" successfully!')
color_print(f'{Ansi.GREEN}You can now build Godot with GameInput support enabled by running "scons gameinput=yes".')
50 changes: 41 additions & 9 deletions platform/windows/detect.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,10 +166,10 @@ def get_opts():

mingw = os.getenv("MINGW_PREFIX", "")

# Direct3D 12 SDK dependencies folder.
d3d12_deps_folder = os.getenv("LOCALAPPDATA")
if d3d12_deps_folder:
d3d12_deps_folder = os.path.join(d3d12_deps_folder, "Godot", "build_deps")
# Direct3D 12 SDK and GameInput dependencies folder.
deps_folder = os.getenv("LOCALAPPDATA")
if deps_folder:
deps_folder = os.path.join(deps_folder, "Godot", "build_deps")
else:
# Cross-compiling, the deps install script puts things in `bin`.
# Getting an absolute path to it is a bit hacky in Python.
Expand All @@ -178,9 +178,9 @@ def get_opts():

caller_frame = inspect.stack()[1]
caller_script_dir = os.path.dirname(os.path.abspath(caller_frame[1]))
d3d12_deps_folder = os.path.join(caller_script_dir, "bin", "build_deps")
deps_folder = os.path.join(caller_script_dir, "bin", "build_deps")
except Exception: # Give up.
d3d12_deps_folder = ""
deps_folder = ""

return [
("mingw_prefix", "MinGW prefix", mingw),
Expand All @@ -199,12 +199,12 @@ def get_opts():
(
"mesa_libs",
"Path to the MESA/NIR static libraries (required for D3D12)",
os.path.join(d3d12_deps_folder, "mesa"),
os.path.join(deps_folder, "mesa"),
),
(
"agility_sdk_path",
"Path to the Agility SDK distribution (optional for D3D12)",
os.path.join(d3d12_deps_folder, "agility_sdk"),
os.path.join(deps_folder, "agility_sdk"),
),
BoolVariable(
"agility_sdk_multiarch",
Expand All @@ -215,7 +215,12 @@ def get_opts():
(
"pix_path",
"Path to the PIX runtime distribution (optional for D3D12)",
os.path.join(d3d12_deps_folder, "pix"),
os.path.join(deps_folder, "pix"),
),
(
"gameinput_path",
"Path to the GameInput libraries",
os.path.join(deps_folder, "gameinput"),
),
]

Expand All @@ -236,6 +241,7 @@ def get_flags():
return {
"arch": arch,
"d3d12": True,
"gameinput": True,
"supported": ["d3d12", "dcomp", "library", "mono", "xaudio2"],
}

Expand Down Expand Up @@ -444,6 +450,14 @@ def spawn_capture(sh, escape, cmd, args, env):
if env["sdl"]:
env.Append(CPPDEFINES=["SDL_ENABLED"])

if env["gameinput"]:
if env["sdl"]:
check_gameinput_installed(env)
# Do nothing else, GameInput is only used inside SDL, so the rest is handled in "drivers/sdl/SCsub".
else:
print("GameInput API is enabled, but SDL was explicitly disabled. Disabling GameInput API.")
env["gameinput"] = False

if env["d3d12"]:
check_d3d12_installed(env, env["arch"] + "-msvc")

Expand Down Expand Up @@ -831,6 +845,14 @@ def configure_mingw(env: "SConsEnvironment"):
if env["sdl"]:
env.Append(CPPDEFINES=["SDL_ENABLED"])

if env["gameinput"]:
if env["sdl"]:
check_gameinput_installed(env)
# Do nothing else, GameInput is only used inside SDL, so the rest is handled in "drivers/sdl/SCsub".
else:
print("GameInput API is enabled, but SDL was explicitly disabled. Disabling GameInput API.")
env["gameinput"] = False

if env["d3d12"]:
if env["use_llvm"]:
check_d3d12_installed(env, env["arch"] + "-llvm")
Expand Down Expand Up @@ -933,3 +955,13 @@ def check_d3d12_installed(env, suffix):
"Alternatively, disable this driver by compiling with `d3d12=no` explicitly."
)
sys.exit(255)


def check_gameinput_installed(env):
if not os.path.exists(env["gameinput_path"]):
print_error(
"The GameInput API dependencies are not installed.\n"
"You can install them by running `python misc\\scripts\\install_gameinput_windows.py`.\n"
"Alternatively, disable this driver by compiling with `gameinput=no` explicitly."
)
sys.exit(255)
1 change: 1 addition & 0 deletions thirdparty/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1015,6 +1015,7 @@ Patches:
- `0005-fix-libudev-dbus.patch` ([GH-108373](https://github.com/godotengine/godot/pull/108373))
- `0006-fix-cs-environ.patch` ([GH-109283](https://github.com/godotengine/godot/pull/109283))
- `0007-shield-duplicate-macos.patch` ([GH-115510](https://github.com/godotengine/godot/pull/115510))
- `0009-gameinput-fixes.patch` ([GH-116055](https://github.com/godotengine/godot/pull/116055))


## spirv-cross
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>

This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
Expand All @@ -25,16 +25,11 @@
#include "SDL_windows.h"
#include "SDL_gameinput.h"

#ifdef SDL_PLATFORM_WIN32
#include <initguid.h>
// {11BE2A7E-4254-445A-9C09-FFC40F006918}
DEFINE_GUID(SDL_IID_GameInput, 0x11BE2A7E, 0x4254, 0x445A, 0x9C, 0x09, 0xFF, 0xC4, 0x0F, 0x00, 0x69, 0x18);
#endif

static SDL_SharedObject *g_hGameInputDLL;
static IGameInput *g_pGameInput;
static int g_nGameInputRefCount;


bool SDL_InitGameInput(IGameInput **ppGameInput)
{
if (g_nGameInputRefCount == 0) {
Expand All @@ -43,30 +38,34 @@ bool SDL_InitGameInput(IGameInput **ppGameInput)
return false;
}

typedef HRESULT (WINAPI *GameInputCreate_t)(IGameInput * *gameInput);
GameInputCreate_t GameInputCreateFunc = (GameInputCreate_t)SDL_LoadFunction(g_hGameInputDLL, "GameInputCreate");
if (!GameInputCreateFunc) {
typedef HRESULT (WINAPI *pfnGameInputCreate)(IGameInput **gameInput);
pfnGameInputCreate pGameInputCreate = (pfnGameInputCreate)SDL_LoadFunction(g_hGameInputDLL, "GameInputCreate");
if (!pGameInputCreate) {
SDL_UnloadObject(g_hGameInputDLL);
return false;
}

IGameInput *pGameInput = NULL;
HRESULT hr = GameInputCreateFunc(&pGameInput);
HRESULT hr = pGameInputCreate(&pGameInput);
if (FAILED(hr)) {
SDL_UnloadObject(g_hGameInputDLL);
return WIN_SetErrorFromHRESULT("GameInputCreate failed", hr);
}

#ifdef SDL_PLATFORM_WIN32
hr = IGameInput_QueryInterface(pGameInput, &SDL_IID_GameInput, (void **)&g_pGameInput);
IGameInput_Release(pGameInput);
#if GAMEINPUT_API_VERSION >= 1
hr = pGameInput->QueryInterface(IID_IGameInput, (void **)&g_pGameInput);
#else
// We require GameInput v1.1 or newer
hr = E_NOINTERFACE;
#endif
pGameInput->Release();
if (FAILED(hr)) {
SDL_UnloadObject(g_hGameInputDLL);
return WIN_SetErrorFromHRESULT("GameInput QueryInterface failed", hr);
}
#else
// Assume that the version we get is compatible with the current SDK
// If that isn't the case, define the correct GUID for SDL_IID_GameInput above
g_pGameInput = pGameInput;
#endif
}
Expand All @@ -85,7 +84,7 @@ void SDL_QuitGameInput(void)
--g_nGameInputRefCount;
if (g_nGameInputRefCount == 0) {
if (g_pGameInput) {
IGameInput_Release(g_pGameInput);
g_pGameInput->Release();
g_pGameInput = NULL;
}
if (g_hGameInputDLL) {
Expand Down
Loading