Switch to SDL for joypad input

SDL is loaded in the same way other Linux system libraries are loaded by using a wrapped and dlopen.

Optionally, SDL can be dynamically linked into the binary.

Currently for Linux only since that platform direly needs it, but should be easy to make work on Windows once stable.

Proposal at: godotengine/godot-proposals#9000
This commit is contained in:
Álex Román Núñez
2024-02-04 05:33:50 +01:00
parent b17884061f
commit 7f1b789678
69 changed files with 30109 additions and 6 deletions
+5
View File
@@ -476,6 +476,11 @@ Comment: RVO2
Copyright: 2016, University of North Carolina at Chapel Hill
License: Apache-2.0
Files: ./thirdparty/sdl_headers/
Comment: SDL
Copyright: 1997-2024 Sam Lantinga
License: Zlib
Files: ./thirdparty/spirv-cross/
Comment: SPIRV-Cross
Copyright: 2015-2021, Arm Limited
+1
View File
@@ -224,6 +224,7 @@ opts.Add(BoolVariable("vulkan", "Enable the vulkan rendering driver", True))
opts.Add(BoolVariable("opengl3", "Enable the OpenGL/GLES3 rendering driver", True))
opts.Add(BoolVariable("d3d12", "Enable the Direct3D 12 rendering driver on supported platforms", False))
opts.Add(BoolVariable("metal", "Enable the Metal rendering driver on supported platforms (Apple arm64 only)", False))
opts.Add(BoolVariable("sdl", "Enable the SDL input driver (Windows/Linux only)", False))
opts.Add(BoolVariable("openxr", "Enable the OpenXR driver", True))
opts.Add(BoolVariable("use_volk", "Use the volk library to load the Vulkan loader dynamically", True))
opts.Add(BoolVariable("disable_exceptions", "Force disabling exception handling code", True))
+4
View File
@@ -49,6 +49,10 @@ if env["metal"]:
Exit(255)
SConscript("metal/SCsub")
# Input drivers
if env["sdl"]:
SConscript("sdl/SCsub")
# Core dependencies
SConscript("png/SCsub")
+9
View File
@@ -0,0 +1,9 @@
#!/usr/bin/env python
Import("env")
if "sdl" in env and env["sdl"] and env["platform"] == "linuxbsd":
if env["use_sowrap"]:
env.add_source_files(env.drivers_sources, "SDL2-so_wrap.c")
env.add_source_files(env.drivers_sources, "*.cpp")
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+398
View File
@@ -0,0 +1,398 @@
/**************************************************************************/
/* joypad_sdl.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "joypad_sdl.h"
#ifdef SDL_ENABLED
#include "core/error/error_macros.h"
#ifdef SOWRAP_ENABLED
#include "SDL2-so_wrap.h"
#else
#include "SDL2/SDL.h"
#include "SDL2/SDL_error.h"
#include "SDL2/SDL_events.h"
#include "SDL2/SDL_gamecontroller.h"
#include "SDL2/SDL_joystick.h"
#include "SDL2/SDL_main.h"
#include "SDL2/SDL_rwops.h"
#endif
#include "core/input/default_controller_mappings.h"
#include "thirdparty/sdl_headers/SDL.h"
void JoypadSDL::process_inputs_thread_func(void *p_userdata) {
JoypadSDL *joy = static_cast<JoypadSDL *>(p_userdata);
joy->process_inputs_run();
}
#define HANDLE_SDL_ERROR(call) \
error = call; \
ERR_FAIL_COND_V_MSG(error != 0, FAILED, SDL_GetError())
void JoypadSDL::process_inputs_run() {
while (!process_inputs_exit.is_set()) {
for (int i = 0; i < Input::JOYPADS_MAX; i++) {
float ff_weak;
float ff_strong;
SDL_Joystick *joy = nullptr;
uint32_t ff_duration_ms;
joypads_lock[i].lock();
if (joypads[i].attached && joypads[i].supports_force_feedback && joypads[i].needs_ff_update) {
joy = SDL_JoystickFromInstanceID(joypads[i].sdl_instance_idx);
ff_weak = joypads[i].ff_weak;
ff_strong = joypads[i].ff_strong;
ff_duration_ms = joypads[i].ff_duration_ms;
joypads[i].needs_ff_update = false;
}
joypads_lock[i].unlock();
// It may be that we've closed the joystick but the main thread isn't aware of this fact yet
// because the event queue hasn't been processed
if (joy == nullptr) {
continue;
}
uint16_t weak = ff_weak * UINT16_MAX;
uint16_t strong = ff_strong * UINT16_MAX;
SDL_JoystickRumble(joy, strong, weak, ff_duration_ms);
}
SDL_Event e;
int has_event = SDL_WaitEventTimeout(&e, 16);
if (has_event != 0) {
switch (e.type) {
case SDL_JOYDEVICEADDED: {
JoypadEvent joypad_event = {};
joypad_event.type = JoypadEventType::DEVICE_ADDED;
SDL_Joystick *joy = nullptr;
SDL_GameController *game_controller = nullptr;
// Game controllers must be opened with GameControllerOpen to get their special remapped events
if (SDL_IsGameController(e.jdevice.which) == SDL_TRUE) {
joypad_event.device_type = JoypadType::GAME_CONTROLLER;
game_controller = SDL_GameControllerOpen(e.jdevice.which);
ERR_CONTINUE_MSG(!game_controller, vformat("Error opening game controller at index %d", e.jdevice.which));
joypad_event.device_name = SDL_GameControllerName(game_controller);
joy = SDL_GameControllerGetJoystick(game_controller);
if (is_print_verbose_enabled()) {
print_line(vformat("SDL: Game controller %s connected", SDL_GameControllerName(game_controller)));
}
} else {
joypad_event.device_type = JoypadType::JOYSTICK;
joy = SDL_JoystickOpen(e.jdevice.which);
ERR_CONTINUE_MSG(!joy, vformat("Error opening joy device %d: %s", SDL_GetError()));
if (is_print_verbose_enabled()) {
print_line(vformat("SDL: Joystick %s connected", SDL_JoystickName(joy)));
}
}
joypad_event.sdl_joystick_instance_id = SDL_JoystickInstanceID(joy);
joypad_event.device_name = String(SDL_JoystickName(joy));
const int MAX_GUID_SIZE = 64;
char guid[MAX_GUID_SIZE] = {};
SDL_JoystickGetGUIDString(SDL_JoystickGetGUID(joy), guid, MAX_GUID_SIZE);
joypad_event.device_guid = String(guid);
joypad_event.device_supports_force_feedback = SDL_JoystickHasRumble(joy);
MutexLock lock(joypad_event_queue_lock);
joypad_event_queue.push_back(joypad_event);
} break;
case SDL_JOYDEVICEREMOVED: {
JoypadEvent joypad_event = {};
joypad_event.type = JoypadEventType::DEVICE_REMOVED;
joypad_event.sdl_joystick_instance_id = e.jdevice.which;
SDL_GameController *game_controller = SDL_GameControllerFromInstanceID(e.jdevice.which);
if (game_controller != nullptr) {
SDL_GameControllerClose(game_controller);
} else {
SDL_JoystickClose(SDL_JoystickFromInstanceID(e.jdevice.which));
}
MutexLock lock(joypad_event_queue_lock);
joypad_event_queue.push_back(joypad_event);
} break;
case SDL_JOYAXISMOTION: {
if (SDL_GameControllerFromInstanceID(e.jbutton.which) != nullptr) {
continue;
}
JoypadEvent joypad_event = {};
joypad_event.type = JoypadEventType::AXIS;
// Godot joy axis constants are already intentionally the same as SDL's
joypad_event.axis = static_cast<JoyAxis>(e.jaxis.axis);
joypad_event.sdl_joystick_instance_id = e.jaxis.which;
joypad_event.value = (e.jaxis.value - SDL_JOYSTICK_AXIS_MIN) / (float)(SDL_JOYSTICK_AXIS_MAX - SDL_JOYSTICK_AXIS_MIN);
joypad_event.value -= 0.5f;
joypad_event.value *= 2.0f;
MutexLock lock(joypad_event_queue_lock);
joypad_event_queue.push_back(joypad_event);
} break;
case SDL_JOYBUTTONUP:
case SDL_JOYBUTTONDOWN: {
if (SDL_GameControllerFromInstanceID(e.jbutton.which) != nullptr) {
continue;
}
JoypadEvent joypad_event = {};
joypad_event.type = JoypadEventType::BUTTON;
joypad_event.sdl_joystick_instance_id = e.jbutton.which;
joypad_event.pressed = e.jbutton.state == SDL_PRESSED;
// Godot button constants are intentionally the same as SDL's, so we can just straight up use them
joypad_event.button = static_cast<JoyButton>(e.jbutton.button);
MutexLock lock(joypad_event_queue_lock);
joypad_event_queue.push_back(joypad_event);
} break;
case SDL_JOYHATMOTION: {
if (SDL_GameControllerFromInstanceID(e.jbutton.which) != nullptr) {
continue;
}
// Godot hat masks are identical to SDL hat masks, so we can just use them as-is.
JoypadEvent joypad_event = {};
joypad_event.type = JoypadEventType::HAT;
joypad_event.hat_mask = e.jhat.value;
joypad_event.sdl_joystick_instance_id = e.jhat.which;
MutexLock lock(joypad_event_queue_lock);
joypad_event_queue.push_back(joypad_event);
} break;
case SDL_CONTROLLERAXISMOTION: {
JoypadEvent joypad_event = {};
joypad_event.type = JoypadEventType::AXIS;
// Godot joy axis constants are already intentionally the same as SDL's
joypad_event.axis = static_cast<JoyAxis>(e.caxis.axis);
joypad_event.sdl_joystick_instance_id = e.caxis.which;
if (e.caxis.axis == SDL_CONTROLLER_AXIS_TRIGGERLEFT || e.caxis.axis == SDL_CONTROLLER_AXIS_TRIGGERRIGHT) {
// Game controller triggers go from 0 to SDL_JOYSTICK_AXIS_MAX
joypad_event.value = e.caxis.value / (float)SDL_JOYSTICK_AXIS_MAX;
} else {
// Other axis go from SDL_JOYSTICK_AXIS_MIN to SDL_JOYSTICK_AXIS_MAX
joypad_event.value = (e.caxis.value - SDL_JOYSTICK_AXIS_MIN) / (float)(SDL_JOYSTICK_AXIS_MAX - SDL_JOYSTICK_AXIS_MIN);
joypad_event.value -= 0.5f;
joypad_event.value *= 2.0f;
}
MutexLock lock(joypad_event_queue_lock);
joypad_event_queue.push_back(joypad_event);
} break;
// Do note SDL game controllers do not have separate events for the dpad
case SDL_CONTROLLERBUTTONUP:
case SDL_CONTROLLERBUTTONDOWN: {
JoypadEvent joypad_event = {};
joypad_event.type = JoypadEventType::BUTTON;
joypad_event.sdl_joystick_instance_id = e.cbutton.which;
joypad_event.pressed = e.cbutton.state == SDL_PRESSED;
// Godot button constants are intentionally the same as SDL's, so we can just straight up use them
joypad_event.button = static_cast<JoyButton>(e.cbutton.button);
MutexLock lock(joypad_event_queue_lock);
joypad_event_queue.push_back(joypad_event);
} break;
}
}
}
}
void JoypadSDL::joypad_vibration_start(int p_pad_idx, float p_weak, float p_strong, float p_duration, uint64_t p_timestamp) {
Joypad &pad = joypads[p_pad_idx];
uint32_t duration_msec = p_duration * 1000;
MutexLock lock(joypads_lock[p_pad_idx]);
pad.needs_ff_update = true;
pad.ff_duration_ms = duration_msec;
pad.ff_weak = p_weak;
pad.ff_strong = p_strong;
pad.ff_effect_timestamp = p_timestamp;
}
void JoypadSDL::joypad_vibration_stop(int p_pad_idx, uint64_t p_timestamp) {
Joypad &pad = joypads[p_pad_idx];
MutexLock lock(joypads_lock[p_pad_idx]);
pad.needs_ff_update = true;
pad.ff_duration_ms = 0;
pad.ff_weak = 0;
pad.ff_strong = 0;
pad.ff_effect_timestamp = p_timestamp;
}
JoypadSDL::JoypadSDL(Input *in) {
input = in;
}
JoypadSDL::~JoypadSDL() {
if (process_inputs_thread.is_started()) {
process_inputs_exit.set();
process_inputs_thread.wait_to_finish();
// Process any remaining input events
process_events();
for (int i = 0; i < Input::JOYPADS_MAX; i++) {
if (joypads[i].attached) {
if (joypads[i].type == JoypadType::GAME_CONTROLLER) {
SDL_GameController *controller = SDL_GameControllerFromInstanceID(joypads[i].sdl_instance_idx);
SDL_GameControllerClose(controller);
} else {
SDL_Joystick *joy = SDL_JoystickFromInstanceID(joypads[i].sdl_instance_idx);
SDL_JoystickClose(joy);
}
}
}
SDL_Quit();
}
}
Error JoypadSDL::initialize() {
#ifdef SOWRAP_ENABLED
#ifdef DEBUG_ENABLED
int dylibloader_verbose = 1;
#else
int dylibloader_verbose = 0;
#endif
if (initialize_SDL2(dylibloader_verbose)) {
print_verbose("SDL: Failed to open, probably not present in the system.");
return ERR_CANT_OPEN;
}
#endif
SDL_SetHint(SDL_HINT_NO_SIGNAL_HANDLERS, "1");
int error;
SDL_SetHint(SDL_HINT_JOYSTICK_THREAD, "1");
HANDLE_SDL_ERROR(SDL_Init(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER));
// Add godot's mapping database from memory
int i = 0;
while (DefaultControllerMappings::mappings[i]) {
String mapping_string = DefaultControllerMappings::mappings[i++];
CharString data = mapping_string.utf8();
SDL_RWops *rw = SDL_RWFromMem((void *)data.ptr(), data.size());
SDL_GameControllerAddMappingsFromRW(rw, 1);
}
print_verbose("SDL: Init OK!");
process_inputs_thread.start(&JoypadSDL::process_inputs_thread_func, this);
return OK;
}
void JoypadSDL::process_events() {
Vector<JoypadEvent> events;
joypad_event_queue_lock.lock();
events = joypad_event_queue;
joypad_event_queue.clear();
joypad_event_queue_lock.unlock();
for (int i = 0; i < events.size(); i++) {
JoypadEvent event = events[i];
switch (event.type) {
case DEVICE_ADDED: {
int joy_id = Input::get_singleton()->get_unused_joy_id();
if (joy_id == -1) {
// There ain't no space for more joypads...
print_error("Joypad limit reached!");
}
joypads[joy_id].attached = true;
joypads[joy_id].sdl_instance_idx = event.sdl_joystick_instance_id;
joypads[joy_id].supports_force_feedback = event.device_supports_force_feedback;
joypads[joy_id].type = event.device_type;
sdl_instance_id_to_joypad_id.insert(event.sdl_joystick_instance_id, joy_id);
// Don't give joysticks of type GAME_CONTROLLER a GUID to prevent godot from messing us up with its own remapping logic
if (event.device_type == JoypadType::GAME_CONTROLLER) {
input->joy_connection_changed(joy_id, true, event.device_name, "");
} else {
input->joy_connection_changed(joy_id, true, event.device_name, event.device_guid);
}
} break;
case DEVICE_REMOVED: {
if (sdl_instance_id_to_joypad_id.has(event.sdl_joystick_instance_id)) {
int joy_id = sdl_instance_id_to_joypad_id.get(event.sdl_joystick_instance_id);
MutexLock lock(joypads_lock[joy_id]);
joypads[joy_id].attached = false;
sdl_instance_id_to_joypad_id.erase(event.sdl_joystick_instance_id);
input->joy_connection_changed(joy_id, false, "");
joypads[joy_id].needs_ff_update = false;
}
} break;
case AXIS: {
if (sdl_instance_id_to_joypad_id.has(event.sdl_joystick_instance_id)) {
int joy_id = sdl_instance_id_to_joypad_id.get(event.sdl_joystick_instance_id);
input->joy_axis(joy_id, event.axis, event.value);
}
} break;
case BUTTON: {
if (sdl_instance_id_to_joypad_id.has(event.sdl_joystick_instance_id)) {
int joy_id = sdl_instance_id_to_joypad_id.get(event.sdl_joystick_instance_id);
input->joy_button(joy_id, event.button, event.pressed);
}
} break;
case HAT: {
if (sdl_instance_id_to_joypad_id.has(event.sdl_joystick_instance_id)) {
int joy_id = sdl_instance_id_to_joypad_id.get(event.sdl_joystick_instance_id);
input->joy_hat(joy_id, event.hat_mask);
}
} break;
}
}
for (int i = 0; i < Input::JOYPADS_MAX; i++) {
Joypad &joy = joypads[i];
if (joy.attached && joy.supports_force_feedback) {
uint64_t timestamp = input->get_joy_vibration_timestamp(i);
if (timestamp > joy.ff_effect_timestamp) {
Vector2 strength = input->get_joy_vibration_strength(i);
float duration = input->get_joy_vibration_duration(i);
if (strength.x == 0 && strength.y == 0) {
joypad_vibration_stop(i, timestamp);
} else {
joypad_vibration_start(i, strength.x, strength.y, duration, timestamp);
}
}
}
}
}
#endif
+114
View File
@@ -0,0 +1,114 @@
/**************************************************************************/
/* joypad_sdl.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef JOYPAD_SDL_H
#define JOYPAD_SDL_H
#ifdef SDL_ENABLED
#include "core/input/input.h"
#include "core/os/thread.h"
typedef int32_t SDL_JoystickID;
class JoypadSDL {
// SDL differentiates between game controllers and generic joysticks
// game controllers refer to playstation/xbox style controllers
enum JoypadType {
GAME_CONTROLLER,
JOYSTICK
};
struct Joypad {
bool attached = false;
JoypadType type;
SDL_JoystickID sdl_instance_idx;
bool supports_force_feedback = false;
uint64_t ff_effect_timestamp;
bool needs_ff_update = false;
float ff_weak = 0.0f;
float ff_strong = 0.0f;
int ff_duration_ms = 0;
};
Joypad joypads[Input::JOYPADS_MAX];
HashMap<SDL_JoystickID, int> sdl_instance_id_to_joypad_id;
Mutex joypads_lock[Input::JOYPADS_MAX];
Input *input;
enum JoypadEventType {
DEVICE_ADDED,
DEVICE_REMOVED,
AXIS,
BUTTON,
HAT
};
struct JoypadEvent {
String device_name;
String device_guid;
JoypadEventType type;
SDL_JoystickID sdl_joystick_instance_id;
union {
JoyAxis axis;
JoyButton button;
JoypadType device_type;
};
BitField<HatMask> hat_mask;
union {
float value = 0.0f;
bool pressed;
bool device_supports_force_feedback;
};
};
Vector<JoypadEvent> joypad_event_queue;
Mutex joypad_event_queue_lock;
SafeFlag process_inputs_exit;
Thread process_inputs_thread;
static void process_inputs_thread_func(void *p_userdata);
void process_inputs_run();
void joypad_vibration_start(int p_pad_idx, float p_weak, float p_strong, float p_duration, uint64_t timestamp);
void joypad_vibration_stop(int p_pad_idx, uint64_t timestamp);
public:
JoypadSDL(Input *in);
~JoypadSDL();
Error initialize();
void process_events();
};
#endif // SDL_ENABLED
#endif // JOYPAD_SDL_H
+11
View File
@@ -340,6 +340,17 @@ def configure(env: "SConsEnvironment"):
else:
env.Append(CPPDEFINES=["PULSEAUDIO_ENABLED", "_REENTRANT"])
if env["sdl"]:
if not env["use_sowrap"]:
if os.system("pkg-config --exists sdl2") == 0: # 0 means found
env.ParseConfig("pkg-config sdl2 --cflags --libs")
env.Append(CPPDEFINES=["SDL_ENABLED"])
else:
print("Warning: SDL development libraries not found. Disabling the SDL input driver.")
env["sdl"] = False
else:
env.Append(CPPDEFINES=["SDL_ENABLED"])
if env["dbus"]:
if not env["use_sowrap"]:
if os.system("pkg-config --exists dbus-1") == 0: # 0 means found
+24 -1
View File
@@ -32,6 +32,7 @@
#include "core/io/certs_compressed.gen.h"
#include "core/io/dir_access.h"
#include "drivers/sdl/joypad_sdl.h"
#include "main/main.h"
#include "servers/display_server.h"
#include "servers/rendering_server.h"
@@ -141,6 +142,15 @@ void OS_LinuxBSD::initialize() {
}
void OS_LinuxBSD::initialize_joypads() {
#ifdef SDL_ENABLED
joypad_sdl = memnew(JoypadSDL(Input::get_singleton()));
if (joypad_sdl->initialize() == OK) {
return;
}
// SDL init failed, fallback to the native driver
memdelete(joypad_sdl);
joypad_sdl = nullptr;
#endif
#ifdef JOYDEV_ENABLED
joypad = memnew(JoypadLinux(Input::get_singleton()));
#endif
@@ -229,6 +239,12 @@ void OS_LinuxBSD::finalize() {
memdelete(joypad);
}
#endif
#ifdef SDL_ENABLED
if (joypad_sdl) {
memdelete(joypad_sdl);
}
#endif
}
MainLoop *OS_LinuxBSD::get_main_loop() const {
@@ -956,8 +972,15 @@ void OS_LinuxBSD::run() {
while (true) {
DisplayServer::get_singleton()->process_events(); // get rid of pending events
#ifdef SDL_ENABLED
if (joypad_sdl) {
joypad_sdl->process_events();
}
#endif
#ifdef JOYDEV_ENABLED
joypad->process_joypads();
if (joypad) {
joypad->process_joypads();
}
#endif
if (Main::iteration()) {
break;
+5 -1
View File
@@ -48,7 +48,7 @@
#include <fontconfig/fontconfig.h>
#endif
#endif
class JoypadSDL;
class OS_LinuxBSD : public OS_Unix {
virtual void delete_main_loop() override;
@@ -65,6 +65,10 @@ class OS_LinuxBSD : public OS_Unix {
JoypadLinux *joypad = nullptr;
#endif
#ifdef SDL_ENABLED
JoypadSDL *joypad_sdl = nullptr;
#endif
#ifdef ALSA_ENABLED
AudioDriverALSA driver_alsa;
#endif
+16
View File
@@ -129,6 +129,22 @@ if env["d3d12"]:
env["pix_path"] + "/bin/" + dxc_arch_subdir + "/" + pix_dll,
Copy("$TARGET", "$SOURCE"),
)
# SDL
if env["sdl"]:
if env["sdl_path"] != "" and os.path.exists(env["sdl_path"]):
sdl_dll = "SDL2.dll"
sdl_dll_path = ""
if env.msvc:
arch_subdir = "x64" if env["arch"].endswith("64") else "x86"
sdl_dll_path = env["sdl_path"] + "/lib/" + arch_subdir + sdl_dll
else:
sdl_dll_path = env["sdl_path"] + "/bin/" + sdl_dll
env.Command(
"#bin/" + sdl_dll,
sdl_dll_path,
Copy("$TARGET", "$SOURCE"),
)
if not os.getenv("VCINSTALLDIR"):
if env["debug_symbols"]:
+31
View File
@@ -223,6 +223,12 @@ def get_opts():
"Path to the PIX runtime distribution (optional for D3D12)",
os.path.join(d3d12_deps_folder, "pix"),
),
# SDL
(
"sdl_path",
"Path to the SDL development libraries",
None,
),
]
@@ -627,6 +633,21 @@ def configure_msvc(env: "SConsEnvironment", vcvars_msvc_config):
env["BUILDERS"]["ProgramOriginal"] = env["BUILDERS"]["Program"]
env["BUILDERS"]["Program"] = methods.precious_program
if env["sdl"]:
if not os.path.exists(env["sdl_path"]):
print("The SDL input driver requires dependencies to be installed.")
sys.exit(255)
if env["arch"] not in ["x86_64", "x86_32"]:
print("The SDL subsystem is only supported on x86")
sys.exit(255)
arch_subdir = "x64" if env["arch"].endswith("64") else "x86"
env.Append(LIBPATH=[env["sdl_path"] + "/lib/" + arch_subdir])
env.Append(CPPPATH=[env["sdl_path"] + "/include"])
env.AppendUnique(CPPDEFINES=["SDL_ENABLED"])
env.Append(LIBS=["SDL2"])
env.Append(LINKFLAGS=["/NATVIS:platform\\windows\\godot.natvis"])
if env["use_asan"]:
@@ -922,6 +943,16 @@ def configure_mingw(env: "SConsEnvironment"):
env.Append(LIBS=["dxgi", "d3d9", "d3d11"])
env.Prepend(CPPPATH=["#thirdparty/angle/include"])
if env["sdl"]:
if not os.path.exists(env["sdl_path"]):
print("The SDL input driver requires dependencies to be installed.")
sys.exit(255)
env.Append(LIBPATH=[env["sdl_path"] + "/lib"])
env.Append(CPPPATH=[env["sdl_path"] + "/include"])
env.AppendUnique(CPPDEFINES=["SDL_ENABLED"])
env.Append(LIBS=["libSDL2.dll"])
env.Append(CPPDEFINES=["MINGW_ENABLED", ("MINGW_HAS_SECURE_API", 1)])
# resrc
+37 -4
View File
@@ -50,6 +50,10 @@
#include "drivers/gles3/rasterizer_gles3.h"
#endif
#ifdef SDL_ENABLED
#include "drivers/sdl/joypad_sdl.h"
#endif
#include <avrt.h>
#include <dwmapi.h>
#include <propkey.h>
@@ -3217,18 +3221,25 @@ String DisplayServerWindows::keyboard_get_layout_name(int p_index) const {
void DisplayServerWindows::process_events() {
ERR_FAIL_COND(!Thread::is_main_thread());
if (!drop_events) {
if (!drop_events && joypad) {
joypad->process_joypads();
}
_THREAD_SAFE_LOCK_
MSG msg = {};
while (PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE)) {
TranslateMessage(&msg);
DispatchMessageW(&msg);
}
_THREAD_SAFE_UNLOCK_
#ifdef SDL_ENABLED
if (!drop_events && joypad_sdl) {
joypad_sdl->process_events();
}
#endif
if (!drop_events) {
_process_key_events();
Input::get_singleton()->flush_buffered_events();
@@ -5291,7 +5302,9 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA
} break;
case WM_DEVICECHANGE: {
joypad->probe_joypads();
if (joypad) {
joypad->probe_joypads();
}
} break;
case WM_DESTROY: {
Input::get_singleton()->flush_buffered_events();
@@ -6412,13 +6425,25 @@ DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, Win
}
WindowID main_window = _create_window(p_mode, p_vsync_mode, p_flags, Rect2i(window_position, p_resolution), false, INVALID_WINDOW_ID);
if (main_window == INVALID_WINDOW_ID) {
r_error = ERR_UNAVAILABLE;
ERR_FAIL_MSG("Failed to create main window.");
}
#ifdef SDL_ENABLED
joypad_sdl = memnew(JoypadSDL(Input::get_singleton()));
if (joypad_sdl->initialize() != OK) {
// SDL init failed, fallback to the native driver
memdelete(joypad_sdl);
joypad_sdl = nullptr;
}
if (!joypad_sdl) {
joypad = new JoypadWindows(&windows[MAIN_WINDOW_ID].hWnd);
}
#else
joypad = new JoypadWindows(&windows[MAIN_WINDOW_ID].hWnd);
#endif
for (int i = 0; i < WINDOW_FLAG_MAX; i++) {
if (p_flags & (1 << i)) {
window_set_flag(WindowFlags(i), true, main_window);
@@ -6551,7 +6576,15 @@ DisplayServerWindows::~DisplayServerWindows() {
E->erase();
}
delete joypad;
#ifdef SDL_ENABLED
if (joypad_sdl) {
memdelete(joypad_sdl);
}
#endif
if (joypad) {
delete joypad;
}
touch_state.clear();
cursors_cache.clear();
@@ -357,6 +357,8 @@ typedef enum _SHC_PROCESS_DPI_AWARENESS {
SHC_PROCESS_PER_MONITOR_DPI_AWARE = 2,
} SHC_PROCESS_DPI_AWARENESS;
class JoypadSDL;
class DisplayServerWindows : public DisplayServer {
// No need to register with GDCLASS, it's platform-specific and nothing is added.
@@ -531,6 +533,7 @@ class DisplayServerWindows : public DisplayServer {
};
JoypadWindows *joypad = nullptr;
JoypadSDL *joypad_sdl = nullptr;
HHOOK mouse_monitor = nullptr;
List<WindowID> popup_list;
uint64_t time_since_popup = 0;
+49
View File
@@ -841,6 +841,55 @@ and solve conflicts and also enrich the feature set originally
proposed by these libraries and better integrate them with Godot.
## sdl_headers
- Upstream: https://github.com/libsdl-org/SDL
- Version: 2.31.0 (8ce6fb25131912c7e83267bff03a5d1797545d5c, 2024)
- License: Zlib
Files extracted from upstream source:
- `LICENSE.txt`
- All `.h` files in the `include/` folder except:
- `SDL_config_android.h`
- `SDL_syswm.h`
- `SDL_opengles2.h`
- `SDL_config_ngage.h`
- `SDL_test_fuzzer.h`
- `SDL_test_compare.h`
- `SDL_config_emscripten.h`
- `SDL_config_macosx.h`
- `SDL_test_random.h`
- `SDL_config_windows.h`
- `SDL_test_log.h`
- `SDL_config_xbox.h`
- `SDL_test_md5.h`
- `SDL_test.h`
- `SDL_test_memory.h`
- `SDL_revision.h`
- `SDL_opengles.h`
- `SDL_egl.h`
- `SDL_test_common.h`
- `SDL_test_assert.h`
- `SDL_opengles2_gl2.h`
- `SDL_copying.h`
- `SDL_config_winrt.h`
- `SDL_name.h`
- `SDL_config_iphoneos.h`
- `SDL_types.h`
- `SDL_config_wingdk.h`
- `SDL_opengles2_khrplatform.h`
- `SDL_test_crc32.h`
- `SDL_opengles2_gl2platform.h`
- `SDL_test_font.h`
- `SDL_opengl.h`
- `SDL_opengl_glext.h`
- `SDL_opengles2_gl2ext.h`
- `SDL_config_pandora.h`
- `SDL_test_images.h`
- `SDL_config_os2.h`
- `SDL_vulkan.h`
## spirv-cross
- Upstream: https://github.com/KhronosGroup/SPIRV-Cross
+17
View File
@@ -0,0 +1,17 @@
Copyright (C) 1997-2024 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
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
+233
View File
@@ -0,0 +1,233 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 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
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
/**
* \file SDL.h
*
* Main include header for the SDL library
*/
#ifndef SDL_h_
#define SDL_h_
#include "SDL_main.h"
#include "SDL_stdinc.h"
#include "SDL_assert.h"
#include "SDL_atomic.h"
#include "SDL_audio.h"
#include "SDL_clipboard.h"
#include "SDL_cpuinfo.h"
#include "SDL_endian.h"
#include "SDL_error.h"
#include "SDL_events.h"
#include "SDL_filesystem.h"
#include "SDL_gamecontroller.h"
#include "SDL_guid.h"
#include "SDL_haptic.h"
#include "SDL_hidapi.h"
#include "SDL_hints.h"
#include "SDL_joystick.h"
#include "SDL_loadso.h"
#include "SDL_log.h"
#include "SDL_messagebox.h"
#include "SDL_metal.h"
#include "SDL_mutex.h"
#include "SDL_power.h"
#include "SDL_render.h"
#include "SDL_rwops.h"
#include "SDL_sensor.h"
#include "SDL_shape.h"
#include "SDL_system.h"
#include "SDL_thread.h"
#include "SDL_timer.h"
#include "SDL_version.h"
#include "SDL_video.h"
#include "SDL_locale.h"
#include "SDL_misc.h"
#include "begin_code.h"
/* Set up for C function definitions, even when using C++ */
#ifdef __cplusplus
extern "C" {
#endif
/* As of version 0.5, SDL is loaded dynamically into the application */
/**
* \name SDL_INIT_*
*
* These are the flags which may be passed to SDL_Init(). You should
* specify the subsystems which you will be using in your application.
*/
/* @{ */
#define SDL_INIT_TIMER 0x00000001u
#define SDL_INIT_AUDIO 0x00000010u
#define SDL_INIT_VIDEO 0x00000020u /**< SDL_INIT_VIDEO implies SDL_INIT_EVENTS */
#define SDL_INIT_JOYSTICK 0x00000200u /**< SDL_INIT_JOYSTICK implies SDL_INIT_EVENTS */
#define SDL_INIT_HAPTIC 0x00001000u
#define SDL_INIT_GAMECONTROLLER 0x00002000u /**< SDL_INIT_GAMECONTROLLER implies SDL_INIT_JOYSTICK */
#define SDL_INIT_EVENTS 0x00004000u
#define SDL_INIT_SENSOR 0x00008000u
#define SDL_INIT_NOPARACHUTE 0x00100000u /**< compatibility; this flag is ignored. */
#define SDL_INIT_EVERYTHING ( \
SDL_INIT_TIMER | SDL_INIT_AUDIO | SDL_INIT_VIDEO | SDL_INIT_EVENTS | \
SDL_INIT_JOYSTICK | SDL_INIT_HAPTIC | SDL_INIT_GAMECONTROLLER | SDL_INIT_SENSOR \
)
/* @} */
/**
* Initialize the SDL library.
*
* SDL_Init() simply forwards to calling SDL_InitSubSystem(). Therefore, the
* two may be used interchangeably. Though for readability of your code
* SDL_InitSubSystem() might be preferred.
*
* The file I/O (for example: SDL_RWFromFile) and threading (SDL_CreateThread)
* subsystems are initialized by default. Message boxes
* (SDL_ShowSimpleMessageBox) also attempt to work without initializing the
* video subsystem, in hopes of being useful in showing an error dialog when
* SDL_Init fails. You must specifically initialize other subsystems if you
* use them in your application.
*
* Logging (such as SDL_Log) works without initialization, too.
*
* `flags` may be any of the following OR'd together:
*
* - `SDL_INIT_TIMER`: timer subsystem
* - `SDL_INIT_AUDIO`: audio subsystem
* - `SDL_INIT_VIDEO`: video subsystem; automatically initializes the events
* subsystem
* - `SDL_INIT_JOYSTICK`: joystick subsystem; automatically initializes the
* events subsystem
* - `SDL_INIT_HAPTIC`: haptic (force feedback) subsystem
* - `SDL_INIT_GAMECONTROLLER`: controller subsystem; automatically
* initializes the joystick subsystem
* - `SDL_INIT_EVENTS`: events subsystem
* - `SDL_INIT_EVERYTHING`: all of the above subsystems
* - `SDL_INIT_NOPARACHUTE`: compatibility; this flag is ignored
*
* Subsystem initialization is ref-counted, you must call SDL_QuitSubSystem()
* for each SDL_InitSubSystem() to correctly shutdown a subsystem manually (or
* call SDL_Quit() to force shutdown). If a subsystem is already loaded then
* this call will increase the ref-count and return.
*
* \param flags subsystem initialization flags
* \returns 0 on success or a negative error code on failure; call
* SDL_GetError() for more information.
*
* \since This function is available since SDL 2.0.0.
*
* \sa SDL_InitSubSystem
* \sa SDL_Quit
* \sa SDL_SetMainReady
* \sa SDL_WasInit
*/
extern DECLSPEC int SDLCALL SDL_Init(Uint32 flags);
/**
* Compatibility function to initialize the SDL library.
*
* In SDL2, this function and SDL_Init() are interchangeable.
*
* \param flags any of the flags used by SDL_Init(); see SDL_Init for details.
* \returns 0 on success or a negative error code on failure; call
* SDL_GetError() for more information.
*
* \since This function is available since SDL 2.0.0.
*
* \sa SDL_Init
* \sa SDL_Quit
* \sa SDL_QuitSubSystem
*/
extern DECLSPEC int SDLCALL SDL_InitSubSystem(Uint32 flags);
/**
* Shut down specific SDL subsystems.
*
* If you start a subsystem using a call to that subsystem's init function
* (for example SDL_VideoInit()) instead of SDL_Init() or SDL_InitSubSystem(),
* SDL_QuitSubSystem() and SDL_WasInit() will not work. You will need to use
* that subsystem's quit function (SDL_VideoQuit()) directly instead. But
* generally, you should not be using those functions directly anyhow; use
* SDL_Init() instead.
*
* You still need to call SDL_Quit() even if you close all open subsystems
* with SDL_QuitSubSystem().
*
* \param flags any of the flags used by SDL_Init(); see SDL_Init for details.
*
* \since This function is available since SDL 2.0.0.
*
* \sa SDL_InitSubSystem
* \sa SDL_Quit
*/
extern DECLSPEC void SDLCALL SDL_QuitSubSystem(Uint32 flags);
/**
* Get a mask of the specified subsystems which are currently initialized.
*
* \param flags any of the flags used by SDL_Init(); see SDL_Init for details.
* \returns a mask of all initialized subsystems if `flags` is 0, otherwise it
* returns the initialization status of the specified subsystems.
*
* The return value does not include SDL_INIT_NOPARACHUTE.
*
* \since This function is available since SDL 2.0.0.
*
* \sa SDL_Init
* \sa SDL_InitSubSystem
*/
extern DECLSPEC Uint32 SDLCALL SDL_WasInit(Uint32 flags);
/**
* Clean up all initialized subsystems.
*
* You should call this function even if you have already shutdown each
* initialized subsystem with SDL_QuitSubSystem(). It is safe to call this
* function even in the case of errors in initialization.
*
* If you start a subsystem using a call to that subsystem's init function
* (for example SDL_VideoInit()) instead of SDL_Init() or SDL_InitSubSystem(),
* then you must use that subsystem's quit function (SDL_VideoQuit()) to shut
* it down before calling SDL_Quit(). But generally, you should not be using
* those functions directly anyhow; use SDL_Init() instead.
*
* You can use this function with atexit() to ensure that it is run when your
* application is shutdown, but it is not wise to do this from a library or
* other dynamically loaded code.
*
* \since This function is available since SDL 2.0.0.
*
* \sa SDL_Init
* \sa SDL_QuitSubSystem
*/
extern DECLSPEC void SDLCALL SDL_Quit(void);
/* Ends C function definitions when using C++ */
#ifdef __cplusplus
}
#endif
#include "close_code.h"
#endif /* SDL_h_ */
/* vi: set ts=4 sw=4 expandtab: */
+322
View File
@@ -0,0 +1,322 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 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
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#ifndef SDL_assert_h_
#define SDL_assert_h_
#include "SDL_stdinc.h"
#include "begin_code.h"
/* Set up for C function definitions, even when using C++ */
#ifdef __cplusplus
extern "C" {
#endif
#ifndef SDL_ASSERT_LEVEL
#ifdef SDL_DEFAULT_ASSERT_LEVEL
#define SDL_ASSERT_LEVEL SDL_DEFAULT_ASSERT_LEVEL
#elif defined(_DEBUG) || defined(DEBUG) || \
(defined(__GNUC__) && !defined(__OPTIMIZE__))
#define SDL_ASSERT_LEVEL 2
#else
#define SDL_ASSERT_LEVEL 1
#endif
#endif /* SDL_ASSERT_LEVEL */
/*
These are macros and not first class functions so that the debugger breaks
on the assertion line and not in some random guts of SDL, and so each
assert can have unique static variables associated with it.
*/
#if defined(_MSC_VER)
/* Don't include intrin.h here because it contains C++ code */
extern void __cdecl __debugbreak(void);
#define SDL_TriggerBreakpoint() __debugbreak()
#elif _SDL_HAS_BUILTIN(__builtin_debugtrap)
#define SDL_TriggerBreakpoint() __builtin_debugtrap()
#elif ( (!defined(__NACL__)) && ((defined(__GNUC__) || defined(__clang__)) && (defined(__i386__) || defined(__x86_64__))) )
#define SDL_TriggerBreakpoint() __asm__ __volatile__ ( "int $3\n\t" )
#elif (defined(__GNUC__) || defined(__clang__)) && defined(__riscv)
#define SDL_TriggerBreakpoint() __asm__ __volatile__ ( "ebreak\n\t" )
#elif ( defined(__APPLE__) && (defined(__arm64__) || defined(__aarch64__)) ) /* this might work on other ARM targets, but this is a known quantity... */
#define SDL_TriggerBreakpoint() __asm__ __volatile__ ( "brk #22\n\t" )
#elif defined(__APPLE__) && defined(__arm__)
#define SDL_TriggerBreakpoint() __asm__ __volatile__ ( "bkpt #22\n\t" )
#elif defined(__386__) && defined(__WATCOMC__)
#define SDL_TriggerBreakpoint() { _asm { int 0x03 } }
#elif defined(HAVE_SIGNAL_H) && !defined(__WATCOMC__)
#include <signal.h>
#define SDL_TriggerBreakpoint() raise(SIGTRAP)
#else
/* How do we trigger breakpoints on this platform? */
#define SDL_TriggerBreakpoint()
#endif
#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 supports __func__ as a standard. */
# define SDL_FUNCTION __func__
#elif ((defined(__GNUC__) && (__GNUC__ >= 2)) || defined(_MSC_VER) || defined (__WATCOMC__))
# define SDL_FUNCTION __FUNCTION__
#else
# define SDL_FUNCTION "???"
#endif
#define SDL_FILE __FILE__
#define SDL_LINE __LINE__
/*
sizeof (x) makes the compiler still parse the expression even without
assertions enabled, so the code is always checked at compile time, but
doesn't actually generate code for it, so there are no side effects or
expensive checks at run time, just the constant size of what x WOULD be,
which presumably gets optimized out as unused.
This also solves the problem of...
int somevalue = blah();
SDL_assert(somevalue == 1);
...which would cause compiles to complain that somevalue is unused if we
disable assertions.
*/
/* "while (0,0)" fools Microsoft's compiler's /W4 warning level into thinking
this condition isn't constant. And looks like an owl's face! */
#ifdef _MSC_VER /* stupid /W4 warnings. */
#define SDL_NULL_WHILE_LOOP_CONDITION (0,0)
#else
#define SDL_NULL_WHILE_LOOP_CONDITION (0)
#endif
#define SDL_disabled_assert(condition) \
do { (void) sizeof ((condition)); } while (SDL_NULL_WHILE_LOOP_CONDITION)
typedef enum
{
SDL_ASSERTION_RETRY, /**< Retry the assert immediately. */
SDL_ASSERTION_BREAK, /**< Make the debugger trigger a breakpoint. */
SDL_ASSERTION_ABORT, /**< Terminate the program. */
SDL_ASSERTION_IGNORE, /**< Ignore the assert. */
SDL_ASSERTION_ALWAYS_IGNORE /**< Ignore the assert from now on. */
} SDL_AssertState;
typedef struct SDL_AssertData
{
int always_ignore;
unsigned int trigger_count;
const char *condition;
const char *filename;
int linenum;
const char *function;
const struct SDL_AssertData *next;
} SDL_AssertData;
/* Never call this directly. Use the SDL_assert* macros. */
extern DECLSPEC SDL_AssertState SDLCALL SDL_ReportAssertion(SDL_AssertData *,
const char *,
const char *, int)
#if defined(__clang__)
#if __has_feature(attribute_analyzer_noreturn)
/* this tells Clang's static analysis that we're a custom assert function,
and that the analyzer should assume the condition was always true past this
SDL_assert test. */
__attribute__((analyzer_noreturn))
#endif
#endif
;
/* the do {} while(0) avoids dangling else problems:
if (x) SDL_assert(y); else blah();
... without the do/while, the "else" could attach to this macro's "if".
We try to handle just the minimum we need here in a macro...the loop,
the static vars, and break points. The heavy lifting is handled in
SDL_ReportAssertion(), in SDL_assert.c.
*/
#define SDL_enabled_assert(condition) \
do { \
while ( !(condition) ) { \
static struct SDL_AssertData sdl_assert_data = { 0, 0, #condition, 0, 0, 0, 0 }; \
const SDL_AssertState sdl_assert_state = SDL_ReportAssertion(&sdl_assert_data, SDL_FUNCTION, SDL_FILE, SDL_LINE); \
if (sdl_assert_state == SDL_ASSERTION_RETRY) { \
continue; /* go again. */ \
} else if (sdl_assert_state == SDL_ASSERTION_BREAK) { \
SDL_TriggerBreakpoint(); \
} \
break; /* not retrying. */ \
} \
} while (SDL_NULL_WHILE_LOOP_CONDITION)
/* Enable various levels of assertions. */
#if SDL_ASSERT_LEVEL == 0 /* assertions disabled */
# define SDL_assert(condition) SDL_disabled_assert(condition)
# define SDL_assert_release(condition) SDL_disabled_assert(condition)
# define SDL_assert_paranoid(condition) SDL_disabled_assert(condition)
#elif SDL_ASSERT_LEVEL == 1 /* release settings. */
# define SDL_assert(condition) SDL_disabled_assert(condition)
# define SDL_assert_release(condition) SDL_enabled_assert(condition)
# define SDL_assert_paranoid(condition) SDL_disabled_assert(condition)
#elif SDL_ASSERT_LEVEL == 2 /* normal settings. */
# define SDL_assert(condition) SDL_enabled_assert(condition)
# define SDL_assert_release(condition) SDL_enabled_assert(condition)
# define SDL_assert_paranoid(condition) SDL_disabled_assert(condition)
#elif SDL_ASSERT_LEVEL == 3 /* paranoid settings. */
# define SDL_assert(condition) SDL_enabled_assert(condition)
# define SDL_assert_release(condition) SDL_enabled_assert(condition)
# define SDL_assert_paranoid(condition) SDL_enabled_assert(condition)
#else
# error Unknown assertion level.
#endif
/* this assertion is never disabled at any level. */
#define SDL_assert_always(condition) SDL_enabled_assert(condition)
/**
* A callback that fires when an SDL assertion fails.
*
* \param data a pointer to the SDL_AssertData structure corresponding to the
* current assertion
* \param userdata what was passed as `userdata` to SDL_SetAssertionHandler()
* \returns an SDL_AssertState value indicating how to handle the failure.
*/
typedef SDL_AssertState (SDLCALL *SDL_AssertionHandler)(
const SDL_AssertData* data, void* userdata);
/**
* Set an application-defined assertion handler.
*
* This function allows an application to show its own assertion UI and/or
* force the response to an assertion failure. If the application doesn't
* provide this, SDL will try to do the right thing, popping up a
* system-specific GUI dialog, and probably minimizing any fullscreen windows.
*
* This callback may fire from any thread, but it runs wrapped in a mutex, so
* it will only fire from one thread at a time.
*
* This callback is NOT reset to SDL's internal handler upon SDL_Quit()!
*
* \param handler the SDL_AssertionHandler function to call when an assertion
* fails or NULL for the default handler
* \param userdata a pointer that is passed to `handler`
*
* \since This function is available since SDL 2.0.0.
*
* \sa SDL_GetAssertionHandler
*/
extern DECLSPEC void SDLCALL SDL_SetAssertionHandler(
SDL_AssertionHandler handler,
void *userdata);
/**
* Get the default assertion handler.
*
* This returns the function pointer that is called by default when an
* assertion is triggered. This is an internal function provided by SDL, that
* is used for assertions when SDL_SetAssertionHandler() hasn't been used to
* provide a different function.
*
* \returns the default SDL_AssertionHandler that is called when an assert
* triggers.
*
* \since This function is available since SDL 2.0.2.
*
* \sa SDL_GetAssertionHandler
*/
extern DECLSPEC SDL_AssertionHandler SDLCALL SDL_GetDefaultAssertionHandler(void);
/**
* Get the current assertion handler.
*
* This returns the function pointer that is called when an assertion is
* triggered. This is either the value last passed to
* SDL_SetAssertionHandler(), or if no application-specified function is set,
* is equivalent to calling SDL_GetDefaultAssertionHandler().
*
* The parameter `puserdata` is a pointer to a void*, which will store the
* "userdata" pointer that was passed to SDL_SetAssertionHandler(). This value
* will always be NULL for the default handler. If you don't care about this
* data, it is safe to pass a NULL pointer to this function to ignore it.
*
* \param puserdata pointer which is filled with the "userdata" pointer that
* was passed to SDL_SetAssertionHandler()
* \returns the SDL_AssertionHandler that is called when an assert triggers.
*
* \since This function is available since SDL 2.0.2.
*
* \sa SDL_SetAssertionHandler
*/
extern DECLSPEC SDL_AssertionHandler SDLCALL SDL_GetAssertionHandler(void **puserdata);
/**
* Get a list of all assertion failures.
*
* This function gets all assertions triggered since the last call to
* SDL_ResetAssertionReport(), or the start of the program.
*
* The proper way to examine this data looks something like this:
*
* ```c
* const SDL_AssertData *item = SDL_GetAssertionReport();
* while (item) {
* printf("'%s', %s (%s:%d), triggered %u times, always ignore: %s.\\n",
* item->condition, item->function, item->filename,
* item->linenum, item->trigger_count,
* item->always_ignore ? "yes" : "no");
* item = item->next;
* }
* ```
*
* \returns a list of all failed assertions or NULL if the list is empty. This
* memory should not be modified or freed by the application.
*
* \since This function is available since SDL 2.0.0.
*
* \sa SDL_ResetAssertionReport
*/
extern DECLSPEC const SDL_AssertData * SDLCALL SDL_GetAssertionReport(void);
/**
* Clear the list of all assertion failures.
*
* This function will clear the list of all assertions triggered up to that
* point. Immediately following this call, SDL_GetAssertionReport will return
* no items. In addition, any previously-triggered assertions will be reset to
* a trigger_count of zero, and their always_ignore state will be false.
*
* \since This function is available since SDL 2.0.0.
*
* \sa SDL_GetAssertionReport
*/
extern DECLSPEC void SDLCALL SDL_ResetAssertionReport(void);
/* these had wrong naming conventions until 2.0.4. Please update your app! */
#define SDL_assert_state SDL_AssertState
#define SDL_assert_data SDL_AssertData
/* Ends C function definitions when using C++ */
#ifdef __cplusplus
}
#endif
#include "close_code.h"
#endif /* SDL_assert_h_ */
/* vi: set ts=4 sw=4 expandtab: */
+414
View File
@@ -0,0 +1,414 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 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
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
/**
* \file SDL_atomic.h
*
* Atomic operations.
*
* IMPORTANT:
* If you are not an expert in concurrent lockless programming, you should
* only be using the atomic lock and reference counting functions in this
* file. In all other cases you should be protecting your data structures
* with full mutexes.
*
* The list of "safe" functions to use are:
* SDL_AtomicLock()
* SDL_AtomicUnlock()
* SDL_AtomicIncRef()
* SDL_AtomicDecRef()
*
* Seriously, here be dragons!
* ^^^^^^^^^^^^^^^^^^^^^^^^^^^
*
* You can find out a little more about lockless programming and the
* subtle issues that can arise here:
* http://msdn.microsoft.com/en-us/library/ee418650%28v=vs.85%29.aspx
*
* There's also lots of good information here:
* http://www.1024cores.net/home/lock-free-algorithms
* http://preshing.com/
*
* These operations may or may not actually be implemented using
* processor specific atomic operations. When possible they are
* implemented as true processor specific atomic operations. When that
* is not possible the are implemented using locks that *do* use the
* available atomic operations.
*
* All of the atomic operations that modify memory are full memory barriers.
*/
#ifndef SDL_atomic_h_
#define SDL_atomic_h_
#include "SDL_stdinc.h"
#include "SDL_platform.h"
#include "begin_code.h"
/* Set up for C function definitions, even when using C++ */
#ifdef __cplusplus
extern "C" {
#endif
/**
* \name SDL AtomicLock
*
* The atomic locks are efficient spinlocks using CPU instructions,
* but are vulnerable to starvation and can spin forever if a thread
* holding a lock has been terminated. For this reason you should
* minimize the code executed inside an atomic lock and never do
* expensive things like API or system calls while holding them.
*
* The atomic locks are not safe to lock recursively.
*
* Porting Note:
* The spin lock functions and type are required and can not be
* emulated because they are used in the atomic emulation code.
*/
/* @{ */
typedef int SDL_SpinLock;
/**
* Try to lock a spin lock by setting it to a non-zero value.
*
* ***Please note that spinlocks are dangerous if you don't know what you're
* doing. Please be careful using any sort of spinlock!***
*
* \param lock a pointer to a lock variable
* \returns SDL_TRUE if the lock succeeded, SDL_FALSE if the lock is already
* held.
*
* \since This function is available since SDL 2.0.0.
*
* \sa SDL_AtomicLock
* \sa SDL_AtomicUnlock
*/
extern DECLSPEC SDL_bool SDLCALL SDL_AtomicTryLock(SDL_SpinLock *lock);
/**
* Lock a spin lock by setting it to a non-zero value.
*
* ***Please note that spinlocks are dangerous if you don't know what you're
* doing. Please be careful using any sort of spinlock!***
*
* \param lock a pointer to a lock variable
*
* \since This function is available since SDL 2.0.0.
*
* \sa SDL_AtomicTryLock
* \sa SDL_AtomicUnlock
*/
extern DECLSPEC void SDLCALL SDL_AtomicLock(SDL_SpinLock *lock);
/**
* Unlock a spin lock by setting it to 0.
*
* Always returns immediately.
*
* ***Please note that spinlocks are dangerous if you don't know what you're
* doing. Please be careful using any sort of spinlock!***
*
* \param lock a pointer to a lock variable
*
* \since This function is available since SDL 2.0.0.
*
* \sa SDL_AtomicLock
* \sa SDL_AtomicTryLock
*/
extern DECLSPEC void SDLCALL SDL_AtomicUnlock(SDL_SpinLock *lock);
/* @} *//* SDL AtomicLock */
/**
* The compiler barrier prevents the compiler from reordering
* reads and writes to globally visible variables across the call.
*/
#if defined(_MSC_VER) && (_MSC_VER > 1200) && !defined(__clang__)
void _ReadWriteBarrier(void);
#pragma intrinsic(_ReadWriteBarrier)
#define SDL_CompilerBarrier() _ReadWriteBarrier()
#elif (defined(__GNUC__) && !defined(__EMSCRIPTEN__)) || (defined(__SUNPRO_C) && (__SUNPRO_C >= 0x5120))
/* This is correct for all CPUs when using GCC or Solaris Studio 12.1+. */
#define SDL_CompilerBarrier() __asm__ __volatile__ ("" : : : "memory")
#elif defined(__WATCOMC__)
extern __inline void SDL_CompilerBarrier(void);
#pragma aux SDL_CompilerBarrier = "" parm [] modify exact [];
#else
#define SDL_CompilerBarrier() \
{ SDL_SpinLock _tmp = 0; SDL_AtomicLock(&_tmp); SDL_AtomicUnlock(&_tmp); }
#endif
/**
* Memory barriers are designed to prevent reads and writes from being
* reordered by the compiler and being seen out of order on multi-core CPUs.
*
* A typical pattern would be for thread A to write some data and a flag, and
* for thread B to read the flag and get the data. In this case you would
* insert a release barrier between writing the data and the flag,
* guaranteeing that the data write completes no later than the flag is
* written, and you would insert an acquire barrier between reading the flag
* and reading the data, to ensure that all the reads associated with the flag
* have completed.
*
* In this pattern you should always see a release barrier paired with an
* acquire barrier and you should gate the data reads/writes with a single
* flag variable.
*
* For more information on these semantics, take a look at the blog post:
* http://preshing.com/20120913/acquire-and-release-semantics
*
* \since This function is available since SDL 2.0.6.
*/
extern DECLSPEC void SDLCALL SDL_MemoryBarrierReleaseFunction(void);
extern DECLSPEC void SDLCALL SDL_MemoryBarrierAcquireFunction(void);
#if defined(__GNUC__) && (defined(__powerpc__) || defined(__ppc__))
#define SDL_MemoryBarrierRelease() __asm__ __volatile__ ("lwsync" : : : "memory")
#define SDL_MemoryBarrierAcquire() __asm__ __volatile__ ("lwsync" : : : "memory")
#elif defined(__GNUC__) && defined(__aarch64__)
#define SDL_MemoryBarrierRelease() __asm__ __volatile__ ("dmb ish" : : : "memory")
#define SDL_MemoryBarrierAcquire() __asm__ __volatile__ ("dmb ish" : : : "memory")
#elif defined(__GNUC__) && defined(__arm__)
#if 0 /* defined(__LINUX__) || defined(__ANDROID__) */
/* Information from:
https://chromium.googlesource.com/chromium/chromium/+/trunk/base/atomicops_internals_arm_gcc.h#19
The Linux kernel provides a helper function which provides the right code for a memory barrier,
hard-coded at address 0xffff0fa0
*/
typedef void (*SDL_KernelMemoryBarrierFunc)();
#define SDL_MemoryBarrierRelease() ((SDL_KernelMemoryBarrierFunc)0xffff0fa0)()
#define SDL_MemoryBarrierAcquire() ((SDL_KernelMemoryBarrierFunc)0xffff0fa0)()
#elif 0 /* defined(__QNXNTO__) */
#include <sys/cpuinline.h>
#define SDL_MemoryBarrierRelease() __cpu_membarrier()
#define SDL_MemoryBarrierAcquire() __cpu_membarrier()
#else
#if defined(__ARM_ARCH_7__) || defined(__ARM_ARCH_7A__) || defined(__ARM_ARCH_7EM__) || defined(__ARM_ARCH_7R__) || defined(__ARM_ARCH_7M__) || defined(__ARM_ARCH_7S__) || defined(__ARM_ARCH_8A__)
#define SDL_MemoryBarrierRelease() __asm__ __volatile__ ("dmb ish" : : : "memory")
#define SDL_MemoryBarrierAcquire() __asm__ __volatile__ ("dmb ish" : : : "memory")
#elif defined(__ARM_ARCH_6__) || defined(__ARM_ARCH_6J__) || defined(__ARM_ARCH_6K__) || defined(__ARM_ARCH_6T2__) || defined(__ARM_ARCH_6Z__) || defined(__ARM_ARCH_6ZK__)
#ifdef __thumb__
/* The mcr instruction isn't available in thumb mode, use real functions */
#define SDL_MEMORY_BARRIER_USES_FUNCTION
#define SDL_MemoryBarrierRelease() SDL_MemoryBarrierReleaseFunction()
#define SDL_MemoryBarrierAcquire() SDL_MemoryBarrierAcquireFunction()
#else
#define SDL_MemoryBarrierRelease() __asm__ __volatile__ ("mcr p15, 0, %0, c7, c10, 5" : : "r"(0) : "memory")
#define SDL_MemoryBarrierAcquire() __asm__ __volatile__ ("mcr p15, 0, %0, c7, c10, 5" : : "r"(0) : "memory")
#endif /* __thumb__ */
#else
#define SDL_MemoryBarrierRelease() __asm__ __volatile__ ("" : : : "memory")
#define SDL_MemoryBarrierAcquire() __asm__ __volatile__ ("" : : : "memory")
#endif /* __LINUX__ || __ANDROID__ */
#endif /* __GNUC__ && __arm__ */
#else
#if (defined(__SUNPRO_C) && (__SUNPRO_C >= 0x5120))
/* This is correct for all CPUs on Solaris when using Solaris Studio 12.1+. */
#include <mbarrier.h>
#define SDL_MemoryBarrierRelease() __machine_rel_barrier()
#define SDL_MemoryBarrierAcquire() __machine_acq_barrier()
#else
/* This is correct for the x86 and x64 CPUs, and we'll expand this over time. */
#define SDL_MemoryBarrierRelease() SDL_CompilerBarrier()
#define SDL_MemoryBarrierAcquire() SDL_CompilerBarrier()
#endif
#endif
/* "REP NOP" is PAUSE, coded for tools that don't know it by that name. */
#if (defined(__GNUC__) || defined(__clang__)) && (defined(__i386__) || defined(__x86_64__))
#define SDL_CPUPauseInstruction() __asm__ __volatile__("pause\n") /* Some assemblers can't do REP NOP, so go with PAUSE. */
#elif (defined(__arm__) && defined(__ARM_ARCH) && __ARM_ARCH >= 7) || defined(__aarch64__)
#define SDL_CPUPauseInstruction() __asm__ __volatile__("yield" ::: "memory")
#elif (defined(__powerpc__) || defined(__powerpc64__))
#define SDL_CPUPauseInstruction() __asm__ __volatile__("or 27,27,27");
#elif defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_X64))
#define SDL_CPUPauseInstruction() _mm_pause() /* this is actually "rep nop" and not a SIMD instruction. No inline asm in MSVC x86-64! */
#elif defined(_MSC_VER) && (defined(_M_ARM) || defined(_M_ARM64))
#define SDL_CPUPauseInstruction() __yield()
#elif defined(__WATCOMC__) && defined(__386__)
extern __inline void SDL_CPUPauseInstruction(void);
#pragma aux SDL_CPUPauseInstruction = ".686p" ".xmm2" "pause"
#else
#define SDL_CPUPauseInstruction()
#endif
/**
* \brief A type representing an atomic integer value. It is a struct
* so people don't accidentally use numeric operations on it.
*/
typedef struct { int value; } SDL_atomic_t;
/**
* Set an atomic variable to a new value if it is currently an old value.
*
* ***Note: If you don't know what this function is for, you shouldn't use
* it!***
*
* \param a a pointer to an SDL_atomic_t variable to be modified
* \param oldval the old value
* \param newval the new value
* \returns SDL_TRUE if the atomic variable was set, SDL_FALSE otherwise.
*
* \since This function is available since SDL 2.0.0.
*
* \sa SDL_AtomicCASPtr
* \sa SDL_AtomicGet
* \sa SDL_AtomicSet
*/
extern DECLSPEC SDL_bool SDLCALL SDL_AtomicCAS(SDL_atomic_t *a, int oldval, int newval);
/**
* Set an atomic variable to a value.
*
* This function also acts as a full memory barrier.
*
* ***Note: If you don't know what this function is for, you shouldn't use
* it!***
*
* \param a a pointer to an SDL_atomic_t variable to be modified
* \param v the desired value
* \returns the previous value of the atomic variable.
*
* \since This function is available since SDL 2.0.2.
*
* \sa SDL_AtomicGet
*/
extern DECLSPEC int SDLCALL SDL_AtomicSet(SDL_atomic_t *a, int v);
/**
* Get the value of an atomic variable.
*
* ***Note: If you don't know what this function is for, you shouldn't use
* it!***
*
* \param a a pointer to an SDL_atomic_t variable
* \returns the current value of an atomic variable.
*
* \since This function is available since SDL 2.0.2.
*
* \sa SDL_AtomicSet
*/
extern DECLSPEC int SDLCALL SDL_AtomicGet(SDL_atomic_t *a);
/**
* Add to an atomic variable.
*
* This function also acts as a full memory barrier.
*
* ***Note: If you don't know what this function is for, you shouldn't use
* it!***
*
* \param a a pointer to an SDL_atomic_t variable to be modified
* \param v the desired value to add
* \returns the previous value of the atomic variable.
*
* \since This function is available since SDL 2.0.2.
*
* \sa SDL_AtomicDecRef
* \sa SDL_AtomicIncRef
*/
extern DECLSPEC int SDLCALL SDL_AtomicAdd(SDL_atomic_t *a, int v);
/**
* \brief Increment an atomic variable used as a reference count.
*/
#ifndef SDL_AtomicIncRef
#define SDL_AtomicIncRef(a) SDL_AtomicAdd(a, 1)
#endif
/**
* \brief Decrement an atomic variable used as a reference count.
*
* \return SDL_TRUE if the variable reached zero after decrementing,
* SDL_FALSE otherwise
*/
#ifndef SDL_AtomicDecRef
#define SDL_AtomicDecRef(a) (SDL_AtomicAdd(a, -1) == 1)
#endif
/**
* Set a pointer to a new value if it is currently an old value.
*
* ***Note: If you don't know what this function is for, you shouldn't use
* it!***
*
* \param a a pointer to a pointer
* \param oldval the old pointer value
* \param newval the new pointer value
* \returns SDL_TRUE if the pointer was set, SDL_FALSE otherwise.
*
* \since This function is available since SDL 2.0.0.
*
* \sa SDL_AtomicCAS
* \sa SDL_AtomicGetPtr
* \sa SDL_AtomicSetPtr
*/
extern DECLSPEC SDL_bool SDLCALL SDL_AtomicCASPtr(void **a, void *oldval, void *newval);
/**
* Set a pointer to a value atomically.
*
* ***Note: If you don't know what this function is for, you shouldn't use
* it!***
*
* \param a a pointer to a pointer
* \param v the desired pointer value
* \returns the previous value of the pointer.
*
* \since This function is available since SDL 2.0.2.
*
* \sa SDL_AtomicCASPtr
* \sa SDL_AtomicGetPtr
*/
extern DECLSPEC void* SDLCALL SDL_AtomicSetPtr(void **a, void* v);
/**
* Get the value of a pointer atomically.
*
* ***Note: If you don't know what this function is for, you shouldn't use
* it!***
*
* \param a a pointer to a pointer
* \returns the current value of a pointer.
*
* \since This function is available since SDL 2.0.2.
*
* \sa SDL_AtomicCASPtr
* \sa SDL_AtomicSetPtr
*/
extern DECLSPEC void* SDLCALL SDL_AtomicGetPtr(void **a);
/* Ends C function definitions when using C++ */
#ifdef __cplusplus
}
#endif
#include "close_code.h"
#endif /* SDL_atomic_h_ */
/* vi: set ts=4 sw=4 expandtab: */

Some files were not shown because too many files have changed in this diff Show More