Compare commits

...

36 Commits

Author SHA1 Message Date
Vlad Zahorodnii
3e536ede68 wayland: Add support for wl_fixes.ack_global_remove
The wl_fixes.ack_global_remove request signals the compositor that the
client will not bind the removed global. It can be used by the
compositor to decide when it is safe to actually destroy the
corresponding global. If a global is destroyed too soon, some clients may
get disconnected.

See also https://gitlab.freedesktop.org/wayland/wayland/-/merge_requests/533

(cherry picked from commit 6eba2f2efe)
2026-07-16 12:32:29 -04:00
Anonymous Maarten
29d07d3ed1 ci: treat CMake deprecated and development warnings as errors
[sdl-ci-filter *]

(cherry picked from commit 64067c11ac)
2026-07-16 03:55:55 +02:00
Frank Praznik
7626a9c313 x11: Don't block when withdrawing unmapped windows
Some window managers will mark minimized or offscreen windows as unmapped. When hiding a window, unconditionally call XWithdrawWindow, and don't wait for an UnmapNotify event if the window is already in the unmapped state, or it will block indefinitely waiting for an event that never arrives.

(cherry picked from commit e442a9a5e1)
2026-07-15 12:05:33 -04:00
Frank Praznik
d8b18a7e18 cocoa: Clear state on failure to enter fullscreen
A fullscreen exit event doesn't always accompany an entry error, so ensure that all relevant state is cleared if the window fails to enter fullscreen.

(cherry picked from commit 1a9801a16f)
2026-07-15 11:32:24 -04:00
Sam Lantinga
558f62eb07 Don't accidentally switch the Horipad Mini into Switch report mode
The PS3 probing switches the HORI Horipad Mini into Nintendo Switch report mode, which we don't recognize since it's still using the Xbox 360 endpoint.

(cherry picked from commit 2d7f300783)
2026-07-14 15:37:05 -07:00
Sam Clegg
4ad0d1f7eb [emscripten] Remove support for legacy webkitGetUserMedia
This modern navigator.mediaDevices.getUserMedia has been
available since Chrome 53 / Firefox 38 / Safari 11.

See https://github.com/emscripten-core/emscripten/pull/27347

(cherry picked from commit 31bd4ff77a)
2026-07-14 12:31:37 -07:00
Frank Praznik
432719facc wayland: Refine window raising for new windows
Generate activation tokens for new windows when necessary, and check the hint for activating windows when shown. This will allow new windows to correctly take focus in environments with elevated focus stealing prevention when possible, and it will try to prevent new windows from being activated if the hint is set to false.

(cherry picked from commit 8586f63d2a)
2026-07-14 13:37:09 -04:00
Ryan C. Gordon
5b5713b955 properties: Fixed a typo in a comment. 2026-07-13 18:38:30 -04:00
Ryan C. Gordon
ecd15e9535 assert: make disabled asserts work with bitfield conditions.
We don't care _what_ the `sizeof` results are, we just want it to vaporize
the condition's code out of the build. So we check it with a `? 1 : 0`, so it
always boils down to a `sizeof (int)`, no matter what data type the condition
actually is.

This works with bitfields, pointers, anything that can be "truthy." If it
isn't truthy, it would fail when assertions are enabled anyhow.

Fixes #15990.

(cherry picked from commit daf519ae05)
2026-07-13 18:23:46 -04:00
Tim Stahlhut
758e323b93 Rewrite five SDL_DIRECTX header checks
Co-authored-by: Anonymous Maarten <madebr@users.noreply.github.com>

[sdl-ci-filter msvc-*]
[sdl-ci-filter msys2-*]
[sdl-ci-filter cygwin]
[sdl-ci-filter ngage]

(cherry picked from commit c97d9feb13)
2026-07-13 09:07:58 -07:00
Nintorch
5311486a9a Emscripten: Convert joystick axis 9 to a hat
(cherry picked from commit d11e3e087a)
2026-07-11 08:01:54 -07:00
Sam Lantinga
6ebd9cf86b testffmpeg_vulkan: enable the VK_KHR_video_maintenance1 extension
This is used by ffmpeg, so we'll enable it if it's available

(cherry picked from commit 9149eca207)
2026-07-10 11:41:14 -07:00
Sam Lantinga
1453f46178 testffmpeg_vulkan: fixed goto jump past variable initialization
(cherry picked from commit 094213b3be)
2026-07-10 10:09:06 -07:00
Sam Lantinga
da8614ff0f testffmpeg: added a --nodelay command line option
(cherry picked from commit 4a00b98c91)
2026-07-10 10:09:06 -07:00
Sam Lantinga
99ca7097d2 testffmpeg_vulkan: use C++ compatible code structure
(cherry picked from commit 573fc51938)
2026-07-10 10:09:06 -07:00
Sam Lantinga
480ab44f9e testffmpeg_vulkan: fixed usage of timeline semaphores
(cherry picked from commit 6ce3d843fc)
2026-07-10 10:09:06 -07:00
Sam Lantinga
5671b0f3ce Fixed Vulkan validation errors when using ffmpeg video decode
(cherry picked from commit c1d4376d3c)
2026-07-10 08:21:02 -07:00
Sam Lantinga
dcb3da5489 Revert "Cygwin: Reduce warning"
This reverts commit 3eb9b5e49a.
2026-07-09 09:14:38 -07:00
Tim Stahlhut
3eb9b5e49a Cygwin: Reduce warning
[sdl-ci-filter cygwin]
[sdl-ci-filter msys2-*]
[sdl-ci-filter msvc-*]

(cherry picked from commit bb74b618ac)
2026-07-09 09:04:00 -07:00
Epifanov Ivan
e5648d4437 VITA: invalidate texture in render state on update (#15957)
(cherry picked from commit 425c6a2695)
2026-07-08 07:11:33 -07:00
meta-legend
af5b015b21 Avoid unnescessarily updating fullscreen display modes
(cherry picked from commit e90bdfb5ea)
2026-07-07 21:37:53 -07:00
ExtremelyLazyCat
4f629701fb Add Mayflash N64 Controller Adapter mapping
(cherry picked from commit 5403934fd3)
2026-07-07 14:05:17 -07:00
Sam Lantinga
ffc7381337 Only skip the Flydigi driver when checking to see if a device is handled by HIDAPI
(cherry picked from commit cf6a235a41)
2026-07-07 09:30:49 -07:00
Isabella Basso
27e6b7a119 cocoa: restore discrete mouse wheel ticks
(cherry picked from commit 1f3f714393)
2026-07-07 11:08:20 -04:00
Sam Lantinga
b18591f4a0 Fixed the 8BitDo Pro 3 controller showing up twice on macOS
Fixes https://github.com/libsdl-org/SDL/issues/15890

(cherry picked from commit 03f81ab4aa)
2026-07-06 17:04:42 -07:00
8BitDo
1ac361c3fe support 8bitdo ultimate 3 (#15927)
(cherry picked from commit efa0e2d5ed)
2026-07-06 05:51:06 -07:00
Caleb Cornett
6c10cdb6eb gpu: Metal fence fixes
(cherry picked from commit 3561f81400)
2026-07-06 05:44:50 -07:00
Alex Tselousov
7e593a2b4f Removed busyloop from SDL_GPUFence on MacOS
Before, MetalFence was implemented as simply a busy loop on an atomic
int on metal, meaning the cpu would busy wait on the gpu to finish
taking power from it and decreasing battery life. This was the only kind
of cpu-gpu syncing (apart from requesting a swapchain)

(cherry picked from commit 514b26e4c4)
2026-07-06 05:44:50 -07:00
Jacul
a19040c84d metal: check MTLBuffer allocations for nil before using their contents.
(cherry picked from commit e1b3c8afd5)
2026-07-06 05:44:20 -07:00
Ozkan Sezer
c42843c389 ci: bump freebsd and cross-platform-actions 2026-07-05 23:36:32 +03:00
Dimitriy Ryazantcev
1cb193a414 Win32: Swap maskColor assignment based on alpha value
Looks like mask pixels was inverted.

(cherry picked from commit 362165ed9b)
2026-07-05 12:37:46 -07:00
Tim Stahlhut
64f058d050 Use SDL_PROCESS_WINDOWS instead of SDL_PLATFORM_WINDOWS
in SDL_process.c

[sdl-ci-filter cygwin]
[sdl-ci-filter msys2-*]
[sdl-ci-filter msvc-*]

(cherry picked from commit 655c8166a1)
2026-07-04 11:19:44 -07:00
Can H. Tartanoglu
c715180e01 testprocess: handle async exec failure for missing executables
(cherry picked from commit bb6f24e542)
2026-07-04 11:17:33 -07:00
Max Seidenstücker
3348fc6466 SDL_camera.h: remove comma at end of enum
GCC: comma at end of enumerator list [-Wpedantic]

(cherry picked from commit bf58ff81d8)
2026-07-04 04:37:32 +03:00
Zana Domán
81c39b0cc5 gpu: validate transfer buffer usage (#15923)
(cherry picked from commit 095e899973)
2026-07-03 17:11:54 -07:00
Sam Lantinga
932f36590f Updated to version 3.4.13 for development 2026-07-01 09:55:50 -07:00
39 changed files with 1952 additions and 1639 deletions

View File

@@ -781,7 +781,7 @@ def spec_to_job(spec: JobSpec, key: str, trackmem_symbol_names: bool) -> JobDeta
match spec.platform: match spec.platform:
case SdlPlatform.FreeBSD: case SdlPlatform.FreeBSD:
job.cpactions_os = "freebsd" job.cpactions_os = "freebsd"
job.cpactions_version = "14.3" job.cpactions_version = "14.4"
job.cpactions_arch = "x86-64" job.cpactions_arch = "x86-64"
job.cpactions_setup_cmd = "sudo pkg update" job.cpactions_setup_cmd = "sudo pkg update"
job.cpactions_install_cmd = "sudo pkg install -y cmake ninja pkgconf libXcursor libXext libXinerama libXi libXfixes libXrandr libXScrnSaver libXxf86vm wayland wayland-protocols libxkbcommon mesa-libs libglvnd evdev-proto libinotify alsa-lib jackit pipewire pulseaudio sndio dbus zh-fcitx ibus libudev-devd" job.cpactions_install_cmd = "sudo pkg install -y cmake ninja pkgconf libXcursor libXext libXinerama libXi libXfixes libXrandr libXScrnSaver libXxf86vm wayland wayland-protocols libxkbcommon mesa-libs libglvnd evdev-proto libinotify alsa-lib jackit pipewire pulseaudio sndio dbus zh-fcitx ibus libudev-devd"

View File

@@ -200,7 +200,7 @@ jobs:
run: | run: |
${{ matrix.platform.source-cmd }} ${{ matrix.platform.source-cmd }}
${{ matrix.platform.cmake-config-emulator }} cmake -S . -B build -G "${{ matrix.platform.cmake-generator }}" \ ${{ matrix.platform.cmake-config-emulator }} cmake -S . -B build -G "${{ matrix.platform.cmake-generator }}" \
-Wdeprecated -Wdev -Werror \ -Wdeprecated -Wdev -Werror=dev -Werror=deprecated \
${{ matrix.platform.cmake-toolchain-file != '' && format('-DCMAKE_TOOLCHAIN_FILE={0}', matrix.platform.cmake-toolchain-file) || '' }} \ ${{ matrix.platform.cmake-toolchain-file != '' && format('-DCMAKE_TOOLCHAIN_FILE={0}', matrix.platform.cmake-toolchain-file) || '' }} \
-DSDL_WERROR=${{ matrix.platform.werror }} \ -DSDL_WERROR=${{ matrix.platform.werror }} \
-DSDL_EXAMPLES=${{ matrix.platform.build-tests }} \ -DSDL_EXAMPLES=${{ matrix.platform.build-tests }} \
@@ -319,7 +319,7 @@ jobs:
- name: 'Build (cross-platform-actions, BSD)' - name: 'Build (cross-platform-actions, BSD)'
id: cpactions id: cpactions
if: ${{ matrix.platform.cpactions }} if: ${{ matrix.platform.cpactions }}
uses: cross-platform-actions/action@v0.32.0 uses: cross-platform-actions/action@v1
with: with:
operating_system: '${{ matrix.platform.cpactions-os }}' operating_system: '${{ matrix.platform.cpactions-os }}'
architecture: '${{ matrix.platform.cpactions-arch }}' architecture: '${{ matrix.platform.cpactions-arch }}'

View File

@@ -5,7 +5,7 @@ if(NOT DEFINED CMAKE_BUILD_TYPE)
endif() endif()
# See docs/release_checklist.md # See docs/release_checklist.md
project(SDL3 LANGUAGES C VERSION "3.4.12") project(SDL3 LANGUAGES C VERSION "3.4.13")
if(CMAKE_SOURCE_DIR STREQUAL PROJECT_SOURCE_DIR) if(CMAKE_SOURCE_DIR STREQUAL PROJECT_SOURCE_DIR)
set(SDL3_MAINPROJECT ON) set(SDL3_MAINPROJECT ON)
@@ -2203,11 +2203,32 @@ elseif(WINDOWS)
string(APPEND CMAKE_REQUIRED_FLAGS " /I\"$ENV{DXSDK_DIR}\\Include\"") string(APPEND CMAKE_REQUIRED_FLAGS " /I\"$ENV{DXSDK_DIR}\\Include\"")
endif() endif()
check_include_file(d3d9.h HAVE_D3D9_H) # d3d9.h may need windows.h, but does not include it itself.
check_include_file(d3d11_1.h HAVE_D3D11_H) check_c_source_compiles("
check_include_file(ddraw.h HAVE_DDRAW_H) #include <windows.h>
check_include_file(dsound.h HAVE_DSOUND_H) #include <d3d9.h>
check_include_file(dinput.h HAVE_DINPUT_H) int main(int argc, char **argv) { (void)argc; (void)argv; return 0; }" HAVE_D3D9_H
)
check_c_source_compiles("
#include <d3d11_1.h>
int main(int argc, char **argv) { (void)argc; (void)argv; return 0; }" HAVE_D3D11_H
)
# ddraw.h, dsound.h, and dinput.h may need windows.h, but does not include it itself.
check_c_source_compiles("
#include <windows.h>
#include <ddraw.h>
int main(int argc, char **argv) { (void)argc; (void)argv; return 0; }" HAVE_DDRAW_H
)
check_c_source_compiles("
#include <windows.h>
#include <dsound.h>
int main(int argc, char **argv) { (void)argc; (void)argv; return 0; }" HAVE_DSOUND_H
)
check_c_source_compiles("
#include <windows.h>
#include <dinput.h>
int main(int argc, char **argv) { (void)argc; (void)argv; return 0; }" HAVE_DINPUT_H
)
if(SDL_CPU_ARM32) # !!! FIXME: this should probably check if we're !(x86 or x86-64) instead of arm. if(SDL_CPU_ARM32) # !!! FIXME: this should probably check if we're !(x86 or x86-64) instead of arm.
set(HAVE_DINPUT_H 0) set(HAVE_DINPUT_H 0)
endif() endif()

View File

@@ -19,10 +19,10 @@
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>FMWK</string> <string>FMWK</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>3.4.12</string> <string>3.4.13</string>
<key>CFBundleSignature</key> <key>CFBundleSignature</key>
<string>SDLX</string> <string>SDLX</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>3.4.12</string> <string>3.4.13</string>
</dict> </dict>
</plist> </plist>

View File

@@ -3162,7 +3162,7 @@
CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_ARC = YES;
DEPLOYMENT_POSTPROCESSING = YES; DEPLOYMENT_POSTPROCESSING = YES;
DYLIB_COMPATIBILITY_VERSION = 401.0.0; DYLIB_COMPATIBILITY_VERSION = 401.0.0;
DYLIB_CURRENT_VERSION = 401.12.0; DYLIB_CURRENT_VERSION = 401.13.0;
DYLIB_INSTALL_NAME_BASE = "@rpath"; DYLIB_INSTALL_NAME_BASE = "@rpath";
ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_ALTIVEC_EXTENSIONS = YES; GCC_ALTIVEC_EXTENSIONS = YES;
@@ -3197,7 +3197,7 @@
"@loader_path/Frameworks", "@loader_path/Frameworks",
); );
MACOSX_DEPLOYMENT_TARGET = 10.13; MACOSX_DEPLOYMENT_TARGET = 10.13;
MARKETING_VERSION = 3.4.12; MARKETING_VERSION = 3.4.13;
OTHER_LDFLAGS = "$(CONFIG_FRAMEWORK_LDFLAGS)"; OTHER_LDFLAGS = "$(CONFIG_FRAMEWORK_LDFLAGS)";
PRODUCT_BUNDLE_IDENTIFIER = org.libsdl.SDL3; PRODUCT_BUNDLE_IDENTIFIER = org.libsdl.SDL3;
PRODUCT_NAME = SDL3; PRODUCT_NAME = SDL3;
@@ -3228,7 +3228,7 @@
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_ARC = YES;
DYLIB_COMPATIBILITY_VERSION = 401.0.0; DYLIB_COMPATIBILITY_VERSION = 401.0.0;
DYLIB_CURRENT_VERSION = 401.12.0; DYLIB_CURRENT_VERSION = 401.13.0;
DYLIB_INSTALL_NAME_BASE = "@rpath"; DYLIB_INSTALL_NAME_BASE = "@rpath";
ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES; ENABLE_TESTABILITY = YES;
@@ -3260,7 +3260,7 @@
"@loader_path/Frameworks", "@loader_path/Frameworks",
); );
MACOSX_DEPLOYMENT_TARGET = 10.13; MACOSX_DEPLOYMENT_TARGET = 10.13;
MARKETING_VERSION = 3.4.12; MARKETING_VERSION = 3.4.13;
ONLY_ACTIVE_ARCH = YES; ONLY_ACTIVE_ARCH = YES;
OTHER_LDFLAGS = "$(CONFIG_FRAMEWORK_LDFLAGS)"; OTHER_LDFLAGS = "$(CONFIG_FRAMEWORK_LDFLAGS)";
PRODUCT_BUNDLE_IDENTIFIER = org.libsdl.SDL3; PRODUCT_BUNDLE_IDENTIFIER = org.libsdl.SDL3;

View File

@@ -1,4 +1,4 @@
Title SDL 3.4.12 Title SDL 3.4.13
Version 1 Version 1
Description SDL Library for macOS (http://www.libsdl.org) Description SDL Library for macOS (http://www.libsdl.org)
DefaultLocation /Library/Frameworks DefaultLocation /Library/Frameworks

View File

@@ -61,7 +61,7 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh
private static final String TAG = "SDL"; private static final String TAG = "SDL";
private static final int SDL_MAJOR_VERSION = 3; private static final int SDL_MAJOR_VERSION = 3;
private static final int SDL_MINOR_VERSION = 4; private static final int SDL_MINOR_VERSION = 4;
private static final int SDL_MICRO_VERSION = 12; private static final int SDL_MICRO_VERSION = 13;
/* /*
// Display InputType.SOURCE/CLASS of events and devices // Display InputType.SOURCE/CLASS of events and devices
// //

View File

@@ -20,7 +20,7 @@
*/ */
/** /**
* Main include header for the SDL library, version 3.4.12 * Main include header for the SDL library, version 3.4.13
* *
* It is almost always best to include just this one header instead of * It is almost always best to include just this one header instead of
* picking out individual headers included here. There are exceptions to * picking out individual headers included here. There are exceptions to

View File

@@ -232,11 +232,16 @@ extern "C" {
#define SDL_LINE __LINE__ #define SDL_LINE __LINE__
/* /*
sizeof (x) makes the compiler still parse the expression even without sizeof ((x) ? 1 : 0) makes the compiler still parse the expression even
assertions enabled, so the code is always checked at compile time, but 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 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, expensive checks at run time, just the constant size of what int would be,
which presumably gets optimized out as unused. which presumably gets optimized out as unused.
(The `? 1 : 0` is so this always dithers down to `sizeof (int)`...otherwise
you'll have problems when bitfields are used as the condition, since the
compiler will error out on the sizeof.)
This also solves the problem of... This also solves the problem of...
int somevalue = blah(); int somevalue = blah();
@@ -284,14 +289,14 @@ disable assertions.
* *
* The code does nothing, but wraps `condition` in a sizeof operator, which * The code does nothing, but wraps `condition` in a sizeof operator, which
* generates no code and has no side effects, but avoid compiler warnings * generates no code and has no side effects, but avoid compiler warnings
* about unused variables. * about unused variables, and still checks syntax even when disabled.
* *
* \param condition the condition to assert (but not actually run here). * \param condition the condition to assert (but not actually run here).
* *
* \since This macro is available since SDL 3.2.0. * \since This macro is available since SDL 3.2.0.
*/ */
#define SDL_disabled_assert(condition) \ #define SDL_disabled_assert(condition) \
do { (void) sizeof ((condition)); } while (SDL_NULL_WHILE_LOOP_CONDITION) do { (void) sizeof ((condition) ? 1 : 0); } while (SDL_NULL_WHILE_LOOP_CONDITION)
/** /**
* Possible outcomes from a triggered assertion. * Possible outcomes from a triggered assertion.

View File

@@ -147,7 +147,7 @@ typedef enum SDL_CameraPermissionState
{ {
SDL_CAMERA_PERMISSION_STATE_DENIED = -1, SDL_CAMERA_PERMISSION_STATE_DENIED = -1,
SDL_CAMERA_PERMISSION_STATE_PENDING, SDL_CAMERA_PERMISSION_STATE_PENDING,
SDL_CAMERA_PERMISSION_STATE_APPROVED, SDL_CAMERA_PERMISSION_STATE_APPROVED
} SDL_CameraPermissionState; } SDL_CameraPermissionState;

View File

@@ -62,7 +62,7 @@ extern "C" {
* *
* \since This macro is available since SDL 3.2.0. * \since This macro is available since SDL 3.2.0.
*/ */
#define SDL_MICRO_VERSION 12 #define SDL_MICRO_VERSION 13
/** /**
* This macro turns the version numbers into a numeric value. * This macro turns the version numbers into a numeric value.

View File

@@ -813,7 +813,7 @@ void SDL_DestroyProperties(SDL_PropertiesID props)
{ {
if (props) { if (props) {
// this can't just use RemoveFromHashTable with SDL_FreeProperties as the destructor, because // this can't just use RemoveFromHashTable with SDL_FreeProperties as the destructor, because
// other destructors under this might cause use to attempt a recursive lock on SDL_properties, // other destructors under this might cause us to attempt a recursive lock on SDL_properties,
// which isn't allowed with rwlocks. So manually look it up and remove/free it. // which isn't allowed with rwlocks. So manually look it up and remove/free it.
SDL_Properties *properties = NULL; SDL_Properties *properties = NULL;
if (SDL_FindInHashTable(SDL_properties, (const void *)(uintptr_t)props, (const void **)&properties)) { if (SDL_FindInHashTable(SDL_properties, (const void *)(uintptr_t)props, (const void **)&properties)) {

View File

@@ -250,8 +250,6 @@ static bool EMSCRIPTENAUDIO_OpenDevice(SDL_AudioDevice *device)
if ((navigator.mediaDevices !== undefined) && (navigator.mediaDevices.getUserMedia !== undefined)) { if ((navigator.mediaDevices !== undefined) && (navigator.mediaDevices.getUserMedia !== undefined)) {
navigator.mediaDevices.getUserMedia({ audio: true, video: false }).then(have_microphone).catch(no_microphone); navigator.mediaDevices.getUserMedia({ audio: true, video: false }).then(have_microphone).catch(no_microphone);
} else if (navigator.webkitGetUserMedia !== undefined) {
navigator.webkitGetUserMedia({ audio: true, video: false }, have_microphone, no_microphone);
} }
}, device->spec.channels, device->sample_frames, SDL_RecordingAudioThreadIterate, device); }, device->spec.channels, device->sample_frames, SDL_RecordingAudioThreadIterate, device);
} else { } else {
@@ -331,8 +329,6 @@ static bool EMSCRIPTENAUDIO_Init(SDL_AudioDriverImpl *impl)
recording_available = available && MAIN_THREAD_EM_ASM_INT({ recording_available = available && MAIN_THREAD_EM_ASM_INT({
if ((typeof(navigator.mediaDevices) !== 'undefined') && (typeof(navigator.mediaDevices.getUserMedia) !== 'undefined')) { if ((typeof(navigator.mediaDevices) !== 'undefined') && (typeof(navigator.mediaDevices.getUserMedia) !== 'undefined')) {
return true; return true;
} else if (typeof(navigator.webkitGetUserMedia) !== 'undefined') {
return true;
} }
return false; return false;
}); });

View File

@@ -600,7 +600,7 @@ HICON WIN_CreateIconFromSurface(SDL_Surface *surface)
for (int x = 0; x < width; x++) { for (int x = 0; x < width; x++) {
BYTE* pixel = (BYTE*)pBits + (y * width + x) * 4; BYTE* pixel = (BYTE*)pBits + (y * width + x) * 4;
BYTE alpha = pixel[3]; BYTE alpha = pixel[3];
COLORREF maskColor = (alpha == 0) ? RGB(0, 0, 0) : RGB(255, 255, 255); COLORREF maskColor = (alpha == 0) ? RGB(255, 255, 255) : RGB(0, 0, 0);
SetPixel(hdcMem, x, y, maskColor); SetPixel(hdcMem, x, y, maskColor);
} }
} }

View File

@@ -9,8 +9,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
// //
VS_VERSION_INFO VERSIONINFO VS_VERSION_INFO VERSIONINFO
FILEVERSION 3,4,12,0 FILEVERSION 3,4,13,0
PRODUCTVERSION 3,4,12,0 PRODUCTVERSION 3,4,13,0
FILEFLAGSMASK 0x3fL FILEFLAGSMASK 0x3fL
FILEFLAGS 0x0L FILEFLAGS 0x0L
FILEOS 0x40004L FILEOS 0x40004L
@@ -23,12 +23,12 @@ BEGIN
BEGIN BEGIN
VALUE "CompanyName", "\0" VALUE "CompanyName", "\0"
VALUE "FileDescription", "SDL\0" VALUE "FileDescription", "SDL\0"
VALUE "FileVersion", "3, 4, 12, 0\0" VALUE "FileVersion", "3, 4, 13, 0\0"
VALUE "InternalName", "SDL\0" VALUE "InternalName", "SDL\0"
VALUE "LegalCopyright", "Copyright (C) 2026 Sam Lantinga\0" VALUE "LegalCopyright", "Copyright (C) 2026 Sam Lantinga\0"
VALUE "OriginalFilename", "SDL3.dll\0" VALUE "OriginalFilename", "SDL3.dll\0"
VALUE "ProductName", "Simple DirectMedia Layer\0" VALUE "ProductName", "Simple DirectMedia Layer\0"
VALUE "ProductVersion", "3, 4, 12, 0\0" VALUE "ProductVersion", "3, 4, 13, 0\0"
END END
END END
BLOCK "VarFileInfo" BLOCK "VarFileInfo"

View File

@@ -1411,6 +1411,13 @@ SDL_GPUTransferBuffer *SDL_CreateGPUTransferBuffer(
return NULL; return NULL;
} }
if (device->debug_mode) {
if (createinfo->usage != SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD &&
createinfo->usage != SDL_GPU_TRANSFERBUFFERUSAGE_DOWNLOAD) {
SDL_assert_release(!"Invalid transfer buffer usage!");
}
}
const char *debugName = SDL_GetStringProperty(createinfo->props, SDL_PROP_GPU_TRANSFERBUFFER_CREATE_NAME_STRING, NULL); const char *debugName = SDL_GetStringProperty(createinfo->props, SDL_PROP_GPU_TRANSFERBUFFER_CREATE_NAME_STRING, NULL);
return device->CreateTransferBuffer( return device->CreateTransferBuffer(

View File

@@ -424,6 +424,7 @@ static MTLDepthClipMode SDLToMetal_DepthClipMode(
// Structs // Structs
typedef struct MetalRenderer MetalRenderer; typedef struct MetalRenderer MetalRenderer;
typedef struct MetalCommandBuffer MetalCommandBuffer;
typedef struct MetalTexture typedef struct MetalTexture
{ {
@@ -447,7 +448,7 @@ typedef struct MetalTextureContainer
typedef struct MetalFence typedef struct MetalFence
{ {
SDL_AtomicInt complete; id<MTLCommandBuffer> commandBuffer;
SDL_AtomicInt referenceCount; SDL_AtomicInt referenceCount;
} MetalFence; } MetalFence;
@@ -2087,7 +2088,6 @@ static Uint8 METAL_INTERNAL_CreateFence(
MetalFence *fence; MetalFence *fence;
fence = SDL_calloc(1, sizeof(MetalFence)); fence = SDL_calloc(1, sizeof(MetalFence));
SDL_SetAtomicInt(&fence->complete, 0);
SDL_SetAtomicInt(&fence->referenceCount, 0); SDL_SetAtomicInt(&fence->referenceCount, 0);
// Add it to the available pool // Add it to the available pool
@@ -2130,7 +2130,7 @@ static bool METAL_INTERNAL_AcquireFence(
// Associate the fence with the command buffer // Associate the fence with the command buffer
commandBuffer->fence = fence; commandBuffer->fence = fence;
SDL_SetAtomicInt(&fence->complete, 0); // FIXME: Is this right? fence->commandBuffer = commandBuffer->handle;
(void)SDL_AtomicIncRef(&commandBuffer->fence->referenceCount); (void)SDL_AtomicIncRef(&commandBuffer->fence->referenceCount);
return true; return true;
@@ -3400,6 +3400,7 @@ static void METAL_ReleaseFence(
SDL_GPUFence *fence) SDL_GPUFence *fence)
{ {
MetalFence *metalFence = (MetalFence *)fence; MetalFence *metalFence = (MetalFence *)fence;
metalFence->commandBuffer = nil;
if (SDL_AtomicDecRef(&metalFence->referenceCount)) { if (SDL_AtomicDecRef(&metalFence->referenceCount)) {
METAL_INTERNAL_ReleaseFenceToPool( METAL_INTERNAL_ReleaseFenceToPool(
(MetalRenderer *)driverData, (MetalRenderer *)driverData,
@@ -3581,6 +3582,12 @@ static void METAL_INTERNAL_PerformPendingDestroys(
} }
// Fences // Fences
static bool METAL_INTERNAL_IsFenceBusy(
MetalFence *fence
) {
MTLCommandBufferStatus status = fence->commandBuffer.status;
return status == MTLCommandBufferStatusCommitted || status == MTLCommandBufferStatusScheduled;
}
static bool METAL_WaitForFences( static bool METAL_WaitForFences(
SDL_GPURenderer *driverData, SDL_GPURenderer *driverData,
@@ -3590,20 +3597,19 @@ static bool METAL_WaitForFences(
{ {
@autoreleasepool { @autoreleasepool {
MetalRenderer *renderer = (MetalRenderer *)driverData; MetalRenderer *renderer = (MetalRenderer *)driverData;
bool waiting;
if (waitAll) { if (waitAll) {
for (Uint32 i = 0; i < numFences; i += 1) { for (Uint32 i = 0; i < numFences; i += 1) {
while (!SDL_GetAtomicInt(&((MetalFence *)fences[i])->complete)) { MetalFence *fence = (MetalFence *)fences[i];
// Spin! [fence->commandBuffer waitUntilCompleted];
}
} }
} else { } else {
waiting = 1; bool waiting = true;
while (waiting) { while (waiting) {
for (Uint32 i = 0; i < numFences; i += 1) { for (Uint32 i = 0; i < numFences; i += 1) {
if (SDL_GetAtomicInt(&((MetalFence *)fences[i])->complete) > 0) { MetalFence *fence = (MetalFence *)fences[i];
waiting = 0; if (!METAL_INTERNAL_IsFenceBusy(fence)) {
waiting = false;
break; break;
} }
} }
@@ -3621,7 +3627,7 @@ static bool METAL_QueryFence(
SDL_GPUFence *fence) SDL_GPUFence *fence)
{ {
MetalFence *metalFence = (MetalFence *)fence; MetalFence *metalFence = (MetalFence *)fence;
return SDL_GetAtomicInt(&metalFence->complete) == 1; return METAL_INTERNAL_IsFenceBusy(metalFence);
} }
// Window and Swapchain Management // Window and Swapchain Management
@@ -4084,11 +4090,6 @@ static bool METAL_Submit(
windowData->frameCounter = (windowData->frameCounter + 1) % renderer->allowedFramesInFlight; windowData->frameCounter = (windowData->frameCounter + 1) % renderer->allowedFramesInFlight;
} }
// Notify the fence when the command buffer has completed
[metalCommandBuffer->handle addCompletedHandler:^(id<MTLCommandBuffer> buffer) {
SDL_AtomicIncRef(&metalCommandBuffer->fence->complete);
}];
// Submit the command buffer // Submit the command buffer
[metalCommandBuffer->handle commit]; [metalCommandBuffer->handle commit];
metalCommandBuffer->handle = nil; metalCommandBuffer->handle = nil;
@@ -4106,7 +4107,7 @@ static bool METAL_Submit(
// Check if we can perform any cleanups // Check if we can perform any cleanups
for (Sint32 i = renderer->submittedCommandBufferCount - 1; i >= 0; i -= 1) { for (Sint32 i = renderer->submittedCommandBufferCount - 1; i >= 0; i -= 1) {
if (SDL_GetAtomicInt(&renderer->submittedCommandBuffers[i]->fence->complete)) { if (!METAL_INTERNAL_IsFenceBusy(renderer->submittedCommandBuffers[i]->fence)) {
METAL_INTERNAL_CleanCommandBuffer( METAL_INTERNAL_CleanCommandBuffer(
renderer, renderer,
renderer->submittedCommandBuffers[i], renderer->submittedCommandBuffers[i],
@@ -4159,9 +4160,8 @@ static bool METAL_Wait(
* Sort of equivalent to vkDeviceWaitIdle. * Sort of equivalent to vkDeviceWaitIdle.
*/ */
for (Uint32 i = 0; i < renderer->submittedCommandBufferCount; i += 1) { for (Uint32 i = 0; i < renderer->submittedCommandBufferCount; i += 1) {
while (!SDL_GetAtomicInt(&renderer->submittedCommandBuffers[i]->fence->complete)) { SDL_GPUFence *opaqueFence = (SDL_GPUFence *)renderer->submittedCommandBuffers[i]->fence;
// Spin! METAL_WaitForFences(driverData, true, &opaqueFence, 1);
}
} }
SDL_LockMutex(renderer->submitLock); SDL_LockMutex(renderer->submitLock);

View File

@@ -198,6 +198,7 @@ static const char *s_GamepadMappings[] = {
"03000000380700003888000000000000,Madcatz Arcade Fightstick TE S+ PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", "03000000380700003888000000000000,Madcatz Arcade Fightstick TE S+ PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
"030000002a0600001024000000000000,Matricom,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a4,start:b9,x:b2,y:b3,", "030000002a0600001024000000000000,Matricom,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a4,start:b9,x:b2,y:b3,",
"03000000250900000128000000000000,Mayflash Arcade Stick,a:b1,b:b2,back:b8,leftshoulder:b0,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b3,righttrigger:b7,start:b9,x:b5,y:b6,", "03000000250900000128000000000000,Mayflash Arcade Stick,a:b1,b:b2,back:b8,leftshoulder:b0,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b3,righttrigger:b7,start:b9,x:b5,y:b6,",
"030000008f0e00001330000000000000,Mayflash N64 Controller Adapter,crc:226d,a:b1,b:b2,back:b8,start:b9,leftshoulder:b6,rightshoulder:b7,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,leftx:a0,lefty:a1,rightx:a3~,righty:a2,",
"030000008f0e00001030000000000000,Mayflash USB Adapter for original Sega Saturn controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b5,rightshoulder:b2,righttrigger:b7,start:b9,x:b3,y:b4,", "030000008f0e00001030000000000000,Mayflash USB Adapter for original Sega Saturn controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b5,rightshoulder:b2,righttrigger:b7,start:b9,x:b3,y:b4,",
"0300000025090000e803000000000000,Mayflash Wii Classic Controller,a:b1,b:b0,back:b8,dpdown:b13,dpleft:b12,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b2,", "0300000025090000e803000000000000,Mayflash Wii Classic Controller,a:b1,b:b0,back:b8,dpdown:b13,dpleft:b12,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b2,",
"03000000790000000018000000000000,Mayflash WiiU Pro Game Controller Adapter (DInput),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", "03000000790000000018000000000000,Mayflash WiiU Pro Game Controller Adapter (DInput),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",

View File

@@ -3086,7 +3086,7 @@ SDL_GamepadType SDL_GetGamepadTypeFromVIDPID(Uint16 vendor, Uint16 product, cons
if (forUI) { if (forUI) {
type = SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO; type = SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO;
} else { } else {
type = SDL_GAMEPAD_TYPE_STANDARD; type = SDL_GAMEPAD_TYPE_XBOX360;
} }
break; break;
default: default:

View File

@@ -386,6 +386,7 @@ static bool IOS_AddMFIJoystickDevice(SDL_JoystickDeviceItem *device, GCControlle
HIDAPI_IsDevicePresent(USB_VENDOR_DRAGONRISE, USB_PRODUCT_EVORETRO_GAMECUBE_ADAPTER3, 0, ""))) || HIDAPI_IsDevicePresent(USB_VENDOR_DRAGONRISE, USB_PRODUCT_EVORETRO_GAMECUBE_ADAPTER3, 0, ""))) ||
(SDL_strcmp(name, "8Bitdo SN30 Pro") == 0 && (HIDAPI_IsDevicePresent(USB_VENDOR_8BITDO, USB_PRODUCT_8BITDO_SN30_PRO, 0, "") || HIDAPI_IsDevicePresent(USB_VENDOR_8BITDO, USB_PRODUCT_8BITDO_SN30_PRO_BT, 0, ""))) || (SDL_strcmp(name, "8Bitdo SN30 Pro") == 0 && (HIDAPI_IsDevicePresent(USB_VENDOR_8BITDO, USB_PRODUCT_8BITDO_SN30_PRO, 0, "") || HIDAPI_IsDevicePresent(USB_VENDOR_8BITDO, USB_PRODUCT_8BITDO_SN30_PRO_BT, 0, ""))) ||
(SDL_strcmp(name, "8BitDo Pro 2") == 0 && (HIDAPI_IsDevicePresent(USB_VENDOR_8BITDO, USB_PRODUCT_8BITDO_PRO_2, 0, "") || HIDAPI_IsDevicePresent(USB_VENDOR_8BITDO, USB_PRODUCT_8BITDO_PRO_2_BT, 0, ""))) || (SDL_strcmp(name, "8BitDo Pro 2") == 0 && (HIDAPI_IsDevicePresent(USB_VENDOR_8BITDO, USB_PRODUCT_8BITDO_PRO_2, 0, "") || HIDAPI_IsDevicePresent(USB_VENDOR_8BITDO, USB_PRODUCT_8BITDO_PRO_2_BT, 0, ""))) ||
(SDL_strcmp(name, "8BitDo Pro 3") == 0 && (HIDAPI_IsDevicePresent(USB_VENDOR_8BITDO, USB_PRODUCT_8BITDO_PRO_3, 0, ""))) ||
(SDL_startswith(name, "8BitDo Ultimate 2 Wireless") && HIDAPI_IsDevicePresent(USB_VENDOR_8BITDO, USB_PRODUCT_8BITDO_ULTIMATE2_WIRELESS, 0, ""))) { (SDL_startswith(name, "8BitDo Ultimate 2 Wireless") && HIDAPI_IsDevicePresent(USB_VENDOR_8BITDO, USB_PRODUCT_8BITDO_ULTIMATE2_WIRELESS, 0, ""))) {
// The HIDAPI driver is taking care of this device // The HIDAPI driver is taking care of this device
return false; return false;

View File

@@ -155,13 +155,38 @@ static void SDL_GetEmscriptenNormalizedName(int device_index, char *out, int len
}, device_index, out, length); }, device_index, out, length);
} }
#define IS_EQUAL_APPROX(a, b) (((a)-(b)) < 0.00001 && ((a)-(b)) > -0.00001)
static Uint8 SDL_GetHatFromEmscriptenAxis(float hat_axis)
{
if (IS_EQUAL_APPROX(hat_axis, -7.0/7)) {
return SDL_HAT_UP;
} else if (IS_EQUAL_APPROX(hat_axis, -5.0/7)) {
return SDL_HAT_UP | SDL_HAT_RIGHT;
} else if (IS_EQUAL_APPROX(hat_axis, -3.0/7)) {
return SDL_HAT_RIGHT;
} else if (IS_EQUAL_APPROX(hat_axis, -1.0/7)) {
return SDL_HAT_DOWN | SDL_HAT_RIGHT;
} else if (IS_EQUAL_APPROX(hat_axis, 1.0/7)) {
return SDL_HAT_DOWN;
} else if (IS_EQUAL_APPROX(hat_axis, 3.0/7)) {
return SDL_HAT_DOWN | SDL_HAT_LEFT;
} else if (IS_EQUAL_APPROX(hat_axis, 5.0/7)) {
return SDL_HAT_LEFT;
} else if (IS_EQUAL_APPROX(hat_axis, 7.0/7)) {
return SDL_HAT_UP | SDL_HAT_LEFT;
} else {
return SDL_HAT_CENTERED;
}
}
#undef IS_EQUAL_APPROX
static EM_BOOL Emscripten_JoyStickConnected(int eventType, const EmscriptenGamepadEvent *gamepadEvent, void *userData) static EM_BOOL Emscripten_JoyStickConnected(int eventType, const EmscriptenGamepadEvent *gamepadEvent, void *userData)
{ {
SDL_joylist_item *item; SDL_joylist_item *item;
int i; int i;
Uint16 bus; Uint16 bus;
Uint16 vendor, product; Uint16 vendor, product;
Uint8 os_id; Uint8 os_id, subtype;
bool is_xinput; bool is_xinput;
char name[128]; char name[128];
@@ -185,6 +210,7 @@ static EM_BOOL Emscripten_JoyStickConnected(int eventType, const EmscriptenGamep
is_xinput = SDL_IsEmscriptenJoystickXInput(gamepadEvent->index); is_xinput = SDL_IsEmscriptenJoystickXInput(gamepadEvent->index);
os_id = SDL_GetEmscriptenOSID(); os_id = SDL_GetEmscriptenOSID();
subtype = os_id;
item->trigger_rumble_available = MAIN_THREAD_EM_ASM_INT({ item->trigger_rumble_available = MAIN_THREAD_EM_ASM_INT({
let gamepad = navigator['getGamepads']()[$0]; let gamepad = navigator['getGamepads']()[$0];
@@ -220,7 +246,7 @@ static EM_BOOL Emscripten_JoyStickConnected(int eventType, const EmscriptenGamep
if (SDL_strcmp(gamepadEvent->mapping, "standard") == 0) { if (SDL_strcmp(gamepadEvent->mapping, "standard") == 0) {
// We should differentiate between devices that are mapped or unmapped by the browser. // We should differentiate between devices that are mapped or unmapped by the browser.
os_id += 0x80; subtype += 0x80;
} }
SDL_GetEmscriptenNormalizedName(gamepadEvent->index, name, sizeof(name)); SDL_GetEmscriptenNormalizedName(gamepadEvent->index, name, sizeof(name));
@@ -231,10 +257,10 @@ static EM_BOOL Emscripten_JoyStickConnected(int eventType, const EmscriptenGamep
} }
if (vendor && product) { if (vendor && product) {
item->guid = SDL_CreateJoystickGUID(bus, vendor, product, 0, NULL, name, 0, os_id); item->guid = SDL_CreateJoystickGUID(bus, vendor, product, 0, NULL, name, 0, subtype);
} else { } else {
item->guid = SDL_CreateJoystickGUIDForName(item->name); item->guid = SDL_CreateJoystickGUIDForName(item->name);
item->guid.data[15] = os_id; item->guid.data[15] = subtype;
} }
item->mapping = SDL_strdup(gamepadEvent->mapping); item->mapping = SDL_strdup(gamepadEvent->mapping);
@@ -263,12 +289,16 @@ static EM_BOOL Emscripten_JoyStickConnected(int eventType, const EmscriptenGamep
// dump the digital trigger buttons in any case. // dump the digital trigger buttons in any case.
first_trigger_button = 6; first_trigger_button = 6;
num_buttons -= 2; num_buttons -= 2;
} else if ((SDL_strcmp(gamepadEvent->mapping, "standard") != 0) && (num_axes >= 10) && (os_id == 5)) {
// The browsers (Chromium and Firefox) on Windows usually convert the hat on unmapped controllers to axis 9
// We can't know for sure that this axis is a hat, but most of the time it is
item->hat_is_axis = true;
} }
item->first_hat_button = first_hat_button; item->first_hat_button = first_hat_button;
item->first_trigger_button = first_trigger_button; item->first_trigger_button = first_trigger_button;
item->triggers_are_buttons = triggers_are_buttons; item->triggers_are_buttons = triggers_are_buttons;
item->nhats = (first_hat_button >= 0) ? 1 : 0; item->nhats = (first_hat_button >= 0 || item->hat_is_axis) ? 1 : 0;
item->naxes = num_axes; item->naxes = num_axes;
item->nbuttons = num_buttons; item->nbuttons = num_buttons;
item->device_instance = SDL_GetNextObjectID(); item->device_instance = SDL_GetNextObjectID();
@@ -295,7 +325,7 @@ static EM_BOOL Emscripten_JoyStickConnected(int eventType, const EmscriptenGamep
item->axis[real_axis_count+1] = (gamepadEvent->analogButton[first_trigger_button+1] * 2.0f) - 1.0f; item->axis[real_axis_count+1] = (gamepadEvent->analogButton[first_trigger_button+1] * 2.0f) - 1.0f;
} }
SDL_assert(item->nhats <= 1); // there is (currently) only ever one of these, faked from the d-pad buttons. SDL_assert(item->nhats <= 1); // there is (currently) only ever one of these, faked from the d-pad buttons or the axis 9.
if (first_hat_button != -1) { if (first_hat_button != -1) {
Uint8 value = SDL_HAT_CENTERED; Uint8 value = SDL_HAT_CENTERED;
// this currently expects the first button to be up, then down, then left, then right. // this currently expects the first button to be up, then down, then left, then right.
@@ -312,6 +342,9 @@ static EM_BOOL Emscripten_JoyStickConnected(int eventType, const EmscriptenGamep
value |= SDL_HAT_RIGHT; value |= SDL_HAT_RIGHT;
} }
item->hat = value; item->hat = value;
} else if (item->hat_is_axis) {
item->hat = SDL_GetHatFromEmscriptenAxis(gamepadEvent->axis[9]);
item->axis[9] = 0.0;
} }
if (!SDL_joylist_tail) { if (!SDL_joylist_tail) {
@@ -617,6 +650,9 @@ static void EMSCRIPTEN_JoystickUpdate(SDL_Joystick *joystick)
} }
for (i = 0; i < real_axis_count; i++) { for (i = 0; i < real_axis_count; i++) {
if (item->hat_is_axis && i == 9) {
continue; // this axis is a hat
}
if (item->axis[i] != gamepadState.axis[i]) { if (item->axis[i] != gamepadState.axis[i]) {
SDL_SendJoystickAxis(timestamp, item->joystick, i, (Sint16)(32767.0f * gamepadState.axis[i])); SDL_SendJoystickAxis(timestamp, item->joystick, i, (Sint16)(32767.0f * gamepadState.axis[i]));
item->axis[i] = gamepadState.axis[i]; item->axis[i] = gamepadState.axis[i];
@@ -635,16 +671,20 @@ static void EMSCRIPTEN_JoystickUpdate(SDL_Joystick *joystick)
SDL_assert(item->nhats <= 1); // there is (currently) only ever one of these, faked from the d-pad buttons. SDL_assert(item->nhats <= 1); // there is (currently) only ever one of these, faked from the d-pad buttons.
if (item->nhats) { if (item->nhats) {
Uint8 value = SDL_HAT_CENTERED; Uint8 value = SDL_HAT_CENTERED;
// this currently expects the first button to be up, then down, then left, then right. if (first_hat_button >= 0) {
if (gamepadState.digitalButton[first_hat_button + 0]) { // this currently expects the first button to be up, then down, then left, then right.
value |= SDL_HAT_UP; if (gamepadState.digitalButton[first_hat_button + 0]) {
} else if (gamepadState.digitalButton[first_hat_button + 1]) { value |= SDL_HAT_UP;
value |= SDL_HAT_DOWN; } else if (gamepadState.digitalButton[first_hat_button + 1]) {
} value |= SDL_HAT_DOWN;
if (gamepadState.digitalButton[first_hat_button + 2]) { }
value |= SDL_HAT_LEFT; if (gamepadState.digitalButton[first_hat_button + 2]) {
} else if (gamepadState.digitalButton[first_hat_button + 3]) { value |= SDL_HAT_LEFT;
value |= SDL_HAT_RIGHT; } else if (gamepadState.digitalButton[first_hat_button + 3]) {
value |= SDL_HAT_RIGHT;
}
} else if (item->hat_is_axis) {
value = SDL_GetHatFromEmscriptenAxis(gamepadState.axis[9]);
} }
if (item->hat != value) { if (item->hat != value) {
item->hat = value; item->hat = value;
@@ -652,7 +692,6 @@ static void EMSCRIPTEN_JoystickUpdate(SDL_Joystick *joystick)
} }
} }
item->timestamp = gamepadState.timestamp; item->timestamp = gamepadState.timestamp;
} }
} }

View File

@@ -37,6 +37,7 @@ typedef struct SDL_joylist_item
int first_hat_button; int first_hat_button;
int first_trigger_button; int first_trigger_button;
bool triggers_are_buttons; bool triggers_are_buttons;
bool hat_is_axis;
int nhats; int nhats;
SDL_GUID guid; SDL_GUID guid;
int nbuttons; int nbuttons;
@@ -45,7 +46,7 @@ typedef struct SDL_joylist_item
double axis[64]; // !!! FIXME: don't hardcode 64 on all of these. double axis[64]; // !!! FIXME: don't hardcode 64 on all of these.
double analogButton[64]; double analogButton[64];
EM_BOOL digitalButton[64]; EM_BOOL digitalButton[64];
Uint8 hat; // there is (currently) only ever one of these, faked from the d-pad buttons. Uint8 hat; // there is (currently) only ever one of these, faked from the d-pad buttons or the axis 9.
bool rumble_available; bool rumble_available;
bool trigger_rumble_available; bool trigger_rumble_available;
Uint16 weak_magnitude_rumble; Uint16 weak_magnitude_rumble;

View File

@@ -42,6 +42,13 @@ enum
SDL_GAMEPAD_NUM_8BITDO_BUTTONS, SDL_GAMEPAD_NUM_8BITDO_BUTTONS,
}; };
enum
{
SDL_GAMEPAD_BUTTON_8BITDO_SHARE = 15,
SDL_GAMEPAD_NUM_8BITDO_ULTIMATE3_BUTTONS,
};
#define SDL_8BITDO_FEATURE_REPORTID 0x30
#define SDL_8BITDO_FEATURE_REPORTID_ENABLE_SDL_REPORTID 0x06 #define SDL_8BITDO_FEATURE_REPORTID_ENABLE_SDL_REPORTID 0x06
#define SDL_8BITDO_REPORTID_SDL_REPORTID 0x04 #define SDL_8BITDO_REPORTID_SDL_REPORTID 0x04
#define SDL_8BITDO_REPORTID_NOT_SUPPORTED_SDL_REPORTID 0x03 #define SDL_8BITDO_REPORTID_NOT_SUPPORTED_SDL_REPORTID 0x03
@@ -65,6 +72,7 @@ typedef struct
bool touchpad_01_supported; bool touchpad_01_supported;
bool touchpad_02_supported; bool touchpad_02_supported;
bool rumble_supported; bool rumble_supported;
bool trigger_rumble_supported;
bool rumble_type; bool rumble_type;
bool rgb_supported; bool rgb_supported;
bool player_led_supported; bool player_led_supported;
@@ -78,6 +86,10 @@ typedef struct
Uint8 last_state[USB_PACKET_LENGTH]; Uint8 last_state[USB_PACKET_LENGTH];
Uint64 sensor_timestamp; // Nanoseconds. Simulate onboard clock. Different models have different rates vs different connection styles. Uint64 sensor_timestamp; // Nanoseconds. Simulate onboard clock. Different models have different rates vs different connection styles.
Uint64 sensor_timestamp_interval; Uint64 sensor_timestamp_interval;
Uint8 rumble_left_value;
Uint8 rumble_right_value;
Uint8 trigger_rumble_left_value;
Uint8 trigger_rumble_right_value;
Uint32 last_tick; Uint32 last_tick;
} SDL_Driver8BitDo_Context; } SDL_Driver8BitDo_Context;
@@ -147,6 +159,7 @@ static bool HIDAPI_Driver8BitDo_IsSupportedDevice(SDL_HIDAPI_Device *device, con
case USB_PRODUCT_8BITDO_PRO_2_BT: case USB_PRODUCT_8BITDO_PRO_2_BT:
case USB_PRODUCT_8BITDO_PRO_3: case USB_PRODUCT_8BITDO_PRO_3:
case USB_PRODUCT_8BITDO_ULTIMATE2_WIRELESS: case USB_PRODUCT_8BITDO_ULTIMATE2_WIRELESS:
case USB_PRODUCT_8BITDO_ULTIMATE3:
return true; return true;
default: default:
break; break;
@@ -182,7 +195,41 @@ static bool HIDAPI_Driver8BitDo_InitDevice(SDL_HIDAPI_Device *device)
} }
break; break;
} }
} else { } else if (device->product_id == USB_PRODUCT_8BITDO_ULTIMATE3) {
// Supported by default
ctx->sensors_supported = true;
ctx->rumble_supported = true;
ctx->powerstate_supported = true;
ctx->sensor_timestamp_supported = true;
Uint8 data[USB_PACKET_LENGTH];
const int MAX_ATTEMPTS = 5;
for (int attempt = 0; attempt < MAX_ATTEMPTS; ++attempt) {
int size = ReadFeatureReport(device->dev, SDL_8BITDO_FEATURE_REPORTID, data, sizeof(data));
if (size > 0) {
if (data[3] == 0) {
ctx->rumble_supported = false;
ctx->trigger_rumble_supported = false;
}
if (data[3] & 0x02) {
ctx->trigger_rumble_supported = true;
}
if (data[4] == 0) {
ctx->sensors_supported = false;
ctx->sensor_timestamp_supported = false;
}
if (size >= 17 && data[11] != 0) {
char serial[18];
(void)SDL_snprintf(serial, sizeof(serial), "%.2x-%.2x-%.2x-%.2x-%.2x-%.2x",
data[16], data[15], data[14], data[13], data[12], data[11]);
HIDAPI_SetDeviceSerial(device, serial);
}
break;
}
SDL_Delay(10);
}
}
else {
Uint8 data[USB_PACKET_LENGTH]; Uint8 data[USB_PACKET_LENGTH];
const int MAX_ATTEMPTS = 5; const int MAX_ATTEMPTS = 5;
for (int attempt = 0; attempt < MAX_ATTEMPTS; ++attempt) { for (int attempt = 0; attempt < MAX_ATTEMPTS; ++attempt) {
@@ -277,6 +324,8 @@ static Uint64 HIDAPI_Driver8BitDo_GetIMURateForProductID(SDL_HIDAPI_Device *devi
// This firmware appears to update at 1000 Hz over USB dongle // This firmware appears to update at 1000 Hz over USB dongle
return 1000; return 1000;
} }
case USB_PRODUCT_8BITDO_ULTIMATE3:
return 120;
default: default:
return 120; return 120;
} }
@@ -299,8 +348,10 @@ static bool HIDAPI_Driver8BitDo_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joys
device->product_id == USB_PRODUCT_8BITDO_PRO_2_BT || device->product_id == USB_PRODUCT_8BITDO_PRO_2_BT ||
device->product_id == USB_PRODUCT_8BITDO_PRO_3 || device->product_id == USB_PRODUCT_8BITDO_PRO_3 ||
device->product_id == USB_PRODUCT_8BITDO_ULTIMATE2_WIRELESS) { device->product_id == USB_PRODUCT_8BITDO_ULTIMATE2_WIRELESS) {
// This controller has additional buttons // This controller has additional buttons
joystick->nbuttons = SDL_GAMEPAD_NUM_8BITDO_BUTTONS; joystick->nbuttons = SDL_GAMEPAD_NUM_8BITDO_BUTTONS;
} else if (device->product_id == USB_PRODUCT_8BITDO_ULTIMATE3) {
joystick->nbuttons = SDL_GAMEPAD_NUM_8BITDO_ULTIMATE3_BUTTONS;
} else { } else {
joystick->nbuttons = 11; joystick->nbuttons = 11;
} }
@@ -331,6 +382,12 @@ static bool HIDAPI_Driver8BitDo_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Jo
Uint8 rumble_packet[5] = { 0x05, 0x00, 0x00, 0x00, 0x00 }; Uint8 rumble_packet[5] = { 0x05, 0x00, 0x00, 0x00, 0x00 };
rumble_packet[1] = low_frequency_rumble >> 8; rumble_packet[1] = low_frequency_rumble >> 8;
rumble_packet[2] = high_frequency_rumble >> 8; rumble_packet[2] = high_frequency_rumble >> 8;
if (ctx->trigger_rumble_supported) {
rumble_packet[3] = ctx->trigger_rumble_left_value;
rumble_packet[4] = ctx->trigger_rumble_right_value;
ctx->rumble_left_value = rumble_packet[1];
ctx->rumble_right_value = rumble_packet[2];
}
if (SDL_HIDAPI_SendRumble(device, rumble_packet, sizeof(rumble_packet)) != sizeof(rumble_packet)) { if (SDL_HIDAPI_SendRumble(device, rumble_packet, sizeof(rumble_packet)) != sizeof(rumble_packet)) {
return SDL_SetError("Couldn't send rumble packet"); return SDL_SetError("Couldn't send rumble packet");
@@ -343,7 +400,25 @@ static bool HIDAPI_Driver8BitDo_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Jo
static bool HIDAPI_Driver8BitDo_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble) static bool HIDAPI_Driver8BitDo_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
{ {
return SDL_Unsupported(); SDL_Driver8BitDo_Context* ctx = (SDL_Driver8BitDo_Context*)device->context;
if (ctx->trigger_rumble_supported) {
Uint8 rumble_packet[5] = { 0x05, 0x00, 0x00, 0x00, 0x00 };
rumble_packet[3] = left_rumble >> 8;
rumble_packet[4] = right_rumble >> 8;
if (ctx->rumble_supported) {
rumble_packet[1] = ctx->rumble_left_value;
rumble_packet[2] = ctx->rumble_right_value;
ctx->trigger_rumble_left_value = rumble_packet[3];
ctx->trigger_rumble_right_value = rumble_packet[4];
}
if (SDL_HIDAPI_SendRumble(device, rumble_packet, sizeof(rumble_packet)) != sizeof(rumble_packet)) {
return SDL_SetError("Couldn't send rumble packet");
}
return true;
} else {
return SDL_Unsupported();
}
} }
static Uint32 HIDAPI_Driver8BitDo_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) static Uint32 HIDAPI_Driver8BitDo_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
@@ -525,6 +600,9 @@ static void HIDAPI_Driver8BitDo_HandleStatePacket(SDL_Joystick *joystick, SDL_Dr
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data[9] & 0x08) != 0)); SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data[9] & 0x08) != 0));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data[9] & 0x20) != 0)); SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data[9] & 0x20) != 0));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data[9] & 0x40) != 0)); SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data[9] & 0x40) != 0));
if (joystick->nbuttons >= SDL_GAMEPAD_NUM_8BITDO_ULTIMATE3_BUTTONS) {
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_8BITDO_SHARE, ((data[9] & 0x80) != 0));
}
} }
if (size > 10 && ctx->last_state[10] != data[10]) { if (size > 10 && ctx->last_state[10] != data[10]) {

View File

@@ -1332,7 +1332,7 @@ bool HIDAPI_IsDevicePresent(Uint16 vendor_id, Uint16 product_id, Uint16 version,
// changes when that happens, we'll pretend the driver isn't available so the XInput // changes when that happens, we'll pretend the driver isn't available so the XInput
// interface will always show up (but won't have any input when the controller is in // interface will always show up (but won't have any input when the controller is in
// enhanced mode) // enhanced mode)
if (device->vendor_id == USB_VENDOR_FLYDIGI_V2) { if (device->vendor_id == USB_VENDOR_FLYDIGI_V2 && device->driver == &SDL_HIDAPI_DriverFlydigi) {
continue; continue;
} }

View File

@@ -78,6 +78,7 @@
#define USB_PRODUCT_8BITDO_PRO_2_BT 0x6006 // mode switch to D #define USB_PRODUCT_8BITDO_PRO_2_BT 0x6006 // mode switch to D
#define USB_PRODUCT_8BITDO_PRO_3 0x6009 // mode switch to D #define USB_PRODUCT_8BITDO_PRO_3 0x6009 // mode switch to D
#define USB_PRODUCT_8BITDO_ULTIMATE2_WIRELESS 0x6012 // mode switch to BT #define USB_PRODUCT_8BITDO_ULTIMATE2_WIRELESS 0x6012 // mode switch to BT
#define USB_PRODUCT_8BITDO_ULTIMATE3 0x202f // mode switch to BT
#define USB_PRODUCT_AMAZON_LUNA_CONTROLLER 0x0419 #define USB_PRODUCT_AMAZON_LUNA_CONTROLLER 0x0419
#define USB_PRODUCT_ASTRO_C40_XBOX360 0x0024 #define USB_PRODUCT_ASTRO_C40_XBOX360 0x0024
#define USB_PRODUCT_BACKBONE_ONE_IOS 0x0103 #define USB_PRODUCT_BACKBONE_ONE_IOS 0x0103

View File

@@ -45,7 +45,7 @@ SDL_Process *SDL_CreateProcess(const char * const *args, bool pipe_stdio)
SDL_Process *SDL_CreateProcessWithProperties(SDL_PropertiesID props) SDL_Process *SDL_CreateProcessWithProperties(SDL_PropertiesID props)
{ {
const char * const *args = SDL_GetPointerProperty(props, SDL_PROP_PROCESS_CREATE_ARGS_POINTER, NULL); const char * const *args = SDL_GetPointerProperty(props, SDL_PROP_PROCESS_CREATE_ARGS_POINTER, NULL);
#if defined(SDL_PLATFORM_WINDOWS) #if defined(SDL_PROCESS_WINDOWS)
const char *cmdline = SDL_GetStringProperty(props, SDL_PROP_PROCESS_CREATE_CMDLINE_STRING, NULL); const char *cmdline = SDL_GetStringProperty(props, SDL_PROP_PROCESS_CREATE_CMDLINE_STRING, NULL);
CHECK_PARAM((!args || !args[0] || !args[0][0]) && (!cmdline || !cmdline[0])) { CHECK_PARAM((!args || !args[0] || !args[0][0]) && (!cmdline || !cmdline[0])) {
SDL_SetError("Either SDL_PROP_PROCESS_CREATE_ARGS_POINTER or SDL_PROP_PROCESS_CREATE_CMDLINE_STRING must be valid"); SDL_SetError("Either SDL_PROP_PROCESS_CREATE_ARGS_POINTER or SDL_PROP_PROCESS_CREATE_CMDLINE_STRING must be valid");

View File

@@ -1564,6 +1564,9 @@ static bool SetDrawState(SDL_Renderer *renderer, const SDL_RenderCommand *cmd, c
if (statecache->shader_constants_dirty || if (statecache->shader_constants_dirty ||
SDL_memcmp(shader_constants, &statecache->shader_constants, sizeof(*shader_constants)) != 0) { SDL_memcmp(shader_constants, &statecache->shader_constants, sizeof(*shader_constants)) != 0) {
id<MTLBuffer> mtlbufconstants = [data.mtldevice newBufferWithLength:sizeof(*shader_constants) options:MTLResourceStorageModeShared]; id<MTLBuffer> mtlbufconstants = [data.mtldevice newBufferWithLength:sizeof(*shader_constants) options:MTLResourceStorageModeShared];
if (mtlbufconstants == nil) {
return SDL_OutOfMemory();
}
mtlbufconstants.label = @"SDL shader constants data"; mtlbufconstants.label = @"SDL shader constants data";
SDL_memcpy([mtlbufconstants contents], shader_constants, sizeof(*shader_constants)); SDL_memcpy([mtlbufconstants contents], shader_constants, sizeof(*shader_constants));
[data.mtlcmdencoder setFragmentBuffer:mtlbufconstants offset:0 atIndex:0]; [data.mtlcmdencoder setFragmentBuffer:mtlbufconstants offset:0 atIndex:0];
@@ -1734,6 +1737,9 @@ static bool METAL_RunCommandQueue(SDL_Renderer *renderer, SDL_RenderCommand *cmd
* practices guide recommends this approach for streamed vertex data. * practices guide recommends this approach for streamed vertex data.
*/ */
mtlbufvertex = [data.mtldevice newBufferWithLength:vertsize options:MTLResourceStorageModeShared]; mtlbufvertex = [data.mtldevice newBufferWithLength:vertsize options:MTLResourceStorageModeShared];
if (mtlbufvertex == nil) {
return SDL_OutOfMemory();
}
mtlbufvertex.label = @"SDL vertex data"; mtlbufvertex.label = @"SDL vertex data";
SDL_memcpy([mtlbufvertex contents], vertices, vertsize); SDL_memcpy([mtlbufvertex contents], vertices, vertsize);

View File

@@ -336,6 +336,7 @@ static bool VITA_GXM_UpdateTexture(SDL_Renderer *renderer, SDL_Texture *texture,
const SDL_Rect *rect, const void *pixels, int pitch) const SDL_Rect *rect, const void *pixels, int pitch)
{ {
VITA_GXM_TextureData *vita_texture = (VITA_GXM_TextureData *)texture->internal; VITA_GXM_TextureData *vita_texture = (VITA_GXM_TextureData *)texture->internal;
VITA_GXM_RenderData *data = (VITA_GXM_RenderData *)renderer->internal;
Uint8 *dst; Uint8 *dst;
int row, length, dpitch; int row, length, dpitch;
@@ -422,6 +423,7 @@ static bool VITA_GXM_UpdateTexture(SDL_Renderer *renderer, SDL_Texture *texture,
} }
#endif #endif
data->drawstate.texture = NULL;
return true; return true;
} }
@@ -435,6 +437,7 @@ static bool VITA_GXM_UpdateTextureYUV(SDL_Renderer *renderer, SDL_Texture *textu
Uint8 *dst; Uint8 *dst;
int row, length, dpitch; int row, length, dpitch;
SDL_Rect UVrect = { rect->x / 2, rect->y / 2, (rect->w + 1) / 2, (rect->h + 1) / 2 }; SDL_Rect UVrect = { rect->x / 2, rect->y / 2, (rect->w + 1) / 2, (rect->h + 1) / 2 };
VITA_GXM_RenderData *data = (VITA_GXM_RenderData *)renderer->internal;
VITA_GXM_SetYUVProfile(renderer, texture); VITA_GXM_SetYUVProfile(renderer, texture);
@@ -497,6 +500,7 @@ static bool VITA_GXM_UpdateTextureYUV(SDL_Renderer *renderer, SDL_Texture *textu
} }
} }
data->drawstate.texture = NULL;
return true; return true;
} }
@@ -509,6 +513,7 @@ static bool VITA_GXM_UpdateTextureNV(SDL_Renderer *renderer, SDL_Texture *textur
Uint8 *dst; Uint8 *dst;
int row, length, dpitch; int row, length, dpitch;
SDL_Rect UVrect = { rect->x / 2, rect->y / 2, (rect->w + 1) / 2, (rect->h + 1) / 2 }; SDL_Rect UVrect = { rect->x / 2, rect->y / 2, (rect->w + 1) / 2, (rect->h + 1) / 2 };
VITA_GXM_RenderData *data = (VITA_GXM_RenderData *)renderer->internal;
VITA_GXM_SetYUVProfile(renderer, texture); VITA_GXM_SetYUVProfile(renderer, texture);
@@ -552,6 +557,7 @@ static bool VITA_GXM_UpdateTextureNV(SDL_Renderer *renderer, SDL_Texture *textur
} }
} }
data->drawstate.texture = NULL;
return true; return true;
} }
@@ -580,6 +586,8 @@ static void VITA_GXM_UnlockTexture(SDL_Renderer *renderer, SDL_Texture *texture)
// No need to update texture data on ps vita. // No need to update texture data on ps vita.
// VITA_GXM_LockTexture already returns a pointer to the texture pixels buffer. // VITA_GXM_LockTexture already returns a pointer to the texture pixels buffer.
// This really improves framerate when using lock/unlock. // This really improves framerate when using lock/unlock.
VITA_GXM_RenderData *data = (VITA_GXM_RenderData *)renderer->internal;
data->drawstate.texture = NULL;
} }
static bool VITA_GXM_SetRenderTarget(SDL_Renderer *renderer, SDL_Texture *texture) static bool VITA_GXM_SetRenderTarget(SDL_Renderer *renderer, SDL_Texture *texture)
@@ -1259,6 +1267,12 @@ static void VITA_GXM_DestroyTexture(SDL_Renderer *renderer, SDL_Texture *texture
SDL_free(vita_texture); SDL_free(vita_texture);
texture->internal = NULL; texture->internal = NULL;
if (data->drawstate.texture == texture) {
data->drawstate.texture = NULL;
}
if (data->drawstate.target == texture) {
data->drawstate.target = NULL;
}
} }
static void VITA_GXM_DestroyRenderer(SDL_Renderer *renderer) static void VITA_GXM_DestroyRenderer(SDL_Renderer *renderer)

View File

@@ -891,11 +891,16 @@ static VkResult VULKAN_AllocateImage(VULKAN_RenderData *rendererData, SDL_Proper
imageViewCreateInfo.subresourceRange.baseArrayLayer = 0; imageViewCreateInfo.subresourceRange.baseArrayLayer = 0;
imageViewCreateInfo.subresourceRange.layerCount = 1; imageViewCreateInfo.subresourceRange.layerCount = 1;
VkImageViewUsageCreateInfo imageViewUsageCreateInfo = { 0 };
imageViewUsageCreateInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_USAGE_CREATE_INFO;
imageViewUsageCreateInfo.usage = imageUsage;
imageViewCreateInfo.pNext = &imageViewUsageCreateInfo;
// If it's a YCbCr image, we need to pass the conversion info to the VkImageView (and the VkSampler) // If it's a YCbCr image, we need to pass the conversion info to the VkImageView (and the VkSampler)
if (samplerYcbcrConversion != VK_NULL_HANDLE) { if (samplerYcbcrConversion != VK_NULL_HANDLE) {
samplerYcbcrConversionInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_INFO_KHR; samplerYcbcrConversionInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_INFO_KHR;
samplerYcbcrConversionInfo.conversion = samplerYcbcrConversion; samplerYcbcrConversionInfo.conversion = samplerYcbcrConversion;
imageViewCreateInfo.pNext = &samplerYcbcrConversionInfo; imageViewUsageCreateInfo.pNext = &samplerYcbcrConversionInfo;
} }
result = vkCreateImageView(rendererData->device, &imageViewCreateInfo, NULL, &imageOut->imageView); result = vkCreateImageView(rendererData->device, &imageViewCreateInfo, NULL, &imageOut->imageView);
@@ -2735,8 +2740,8 @@ static bool VULKAN_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, S
// Also create VkSampler object which we will need to pass to the PSO as an immutable sampler // Also create VkSampler object which we will need to pass to the PSO as an immutable sampler
VkSamplerCreateInfo samplerCreateInfo = { 0 }; VkSamplerCreateInfo samplerCreateInfo = { 0 };
samplerCreateInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; samplerCreateInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
samplerCreateInfo.magFilter = VK_FILTER_NEAREST; samplerCreateInfo.magFilter = VK_FILTER_LINEAR;
samplerCreateInfo.minFilter = VK_FILTER_NEAREST; samplerCreateInfo.minFilter = VK_FILTER_LINEAR;
samplerCreateInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST; samplerCreateInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST;
samplerCreateInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; samplerCreateInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
samplerCreateInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; samplerCreateInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
@@ -4039,6 +4044,9 @@ static void VULKAN_InvalidateCachedState(SDL_Renderer *renderer)
VULKAN_RenderData *rendererData = (VULKAN_RenderData *)renderer->internal; VULKAN_RenderData *rendererData = (VULKAN_RenderData *)renderer->internal;
rendererData->currentPipelineState = NULL; rendererData->currentPipelineState = NULL;
rendererData->cliprectDirty = true; rendererData->cliprectDirty = true;
// Make sure pending drawing is submitted to the GPU
VULKAN_IssueBatch(rendererData);
} }
static bool VULKAN_RunCommandQueue(SDL_Renderer *renderer, SDL_RenderCommand *cmd, void *vertices, size_t vertsize) static bool VULKAN_RunCommandQueue(SDL_Renderer *renderer, SDL_RenderCommand *cmd, void *vertices, size_t vertsize)

View File

@@ -1509,9 +1509,6 @@ const SDL_DisplayMode *SDL_GetCurrentDisplayMode(SDL_DisplayID displayID)
CHECK_DISPLAY_MAGIC(display, NULL); CHECK_DISPLAY_MAGIC(display, NULL);
// Make sure our mode list is updated
SDL_UpdateFullscreenDisplayModes(display);
return display->current_mode; return display->current_mode;
} }

View File

@@ -618,17 +618,18 @@ void Cocoa_HandleMouseWheel(SDL_Window *window, NSEvent *event)
SDL_MouseWheelDirection direction; SDL_MouseWheelDirection direction;
CGFloat x, y; CGFloat x, y;
x = -[event deltaX];
y = [event deltaY];
direction = SDL_MOUSEWHEEL_NORMAL; direction = SDL_MOUSEWHEEL_NORMAL;
if ([event isDirectionInvertedFromDevice] == YES) { if ([event isDirectionInvertedFromDevice] == YES) {
direction = SDL_MOUSEWHEEL_FLIPPED; direction = SDL_MOUSEWHEEL_FLIPPED;
} }
/* For discrete scroll events from conventional mice, always send a full tick. if ([event hasPreciseScrollingDeltas]) {
For continuous scroll events from trackpads, send fractional deltas for smoother scrolling. */ x = -[event scrollingDeltaX] * 0.1f;
if (![event hasPreciseScrollingDeltas]) { y = [event scrollingDeltaY] * 0.1f;
} else {
x = -[event deltaX];
y = [event deltaY];
if (x > 0) { if (x > 0) {
x = SDL_ceil(x); x = SDL_ceil(x);
} else if (x < 0) { } else if (x < 0) {

View File

@@ -1430,12 +1430,18 @@ static NSCursor *Cocoa_GetDesiredCursor(void)
} }
/* This is usually sent after an unexpected windowDidExitFullscreen if the window /* This is usually sent after an unexpected windowDidExitFullscreen if the window
* failed to become fullscreen. * failed to become fullscreen, but not always, so ensure that the state variables
* are cleared.
* *
* Since something went wrong and the current state is unknown, dump any pending events. * Since something went wrong and the current state is unknown, dump any pending events.
*
* For testing purposes, this error can usually be induced by starting something that
* immedately enters a fullscreen space from a terminal that is in a fullscreen space.
*/ */
- (void)windowDidFailToEnterFullScreen:(NSNotification *)aNotification - (void)windowDidFailToEnterFullScreen:(NSNotification *)aNotification
{ {
inFullscreenTransition = NO;
isFullscreenSpace = NO;
[self clearAllPendingWindowOperations]; [self clearAllPendingWindowOperations];
} }

View File

@@ -110,7 +110,9 @@
#define SDL_WL_DATA_DEVICE_VERSION 3 #define SDL_WL_DATA_DEVICE_VERSION 3
// wl_fixes was introduced in 1.24.0 // wl_fixes was introduced in 1.24.0
#if SDL_WAYLAND_CHECK_VERSION(1, 24, 0) #if SDL_WAYLAND_CHECK_VERSION(1, 26, 0)
#define SDL_WL_FIXES_VERSION 2
#elif SDL_WAYLAND_CHECK_VERSION(1, 24, 0)
#define SDL_WL_FIXES_VERSION 1 #define SDL_WL_FIXES_VERSION 1
#endif #endif
@@ -506,7 +508,11 @@ static void wayland_preferred_check_handle_global(void *data, struct wl_registry
static void wayland_preferred_check_remove_global(void *data, struct wl_registry *registry, uint32_t id) static void wayland_preferred_check_remove_global(void *data, struct wl_registry *registry, uint32_t id)
{ {
// No need to do anything here. SDL_WaylandPreferredData *d = (SDL_WaylandPreferredData *)data;
if (d->wl_fixes && wl_fixes_get_version(d->wl_fixes) >= WL_FIXES_ACK_GLOBAL_REMOVE_SINCE_VERSION) {
wl_fixes_ack_global_remove(d->wl_fixes, registry, id);
}
} }
static const struct wl_registry_listener preferred_registry_listener = { static const struct wl_registry_listener preferred_registry_listener = {
@@ -1370,18 +1376,22 @@ static void handle_registry_remove_global(void *data, struct wl_registry *regist
} }
d->output_count--; d->output_count--;
return; goto ack_remove;
} }
} }
SDL_WaylandSeat *seat, *temp; SDL_WaylandSeat *seat, *temp;
wl_list_for_each_safe (seat, temp, &d->seat_list, link) wl_list_for_each_safe (seat, temp, &d->seat_list, link) {
{
if (seat->registry_id == id) { if (seat->registry_id == id) {
Wayland_SeatDestroy(seat, false); Wayland_SeatDestroy(seat, false);
return; goto ack_remove;
} }
} }
ack_remove:
if (d->wl_fixes && wl_fixes_get_version(d->wl_fixes) >= WL_FIXES_ACK_GLOBAL_REMOVE_SINCE_VERSION) {
wl_fixes_ack_global_remove(d->wl_fixes, registry, id);
}
} }
static const struct wl_registry_listener registry_listener = { static const struct wl_registry_listener registry_listener = {

View File

@@ -2136,8 +2136,11 @@ void Wayland_ShowWindow(SDL_VideoDevice *_this, SDL_Window *window)
Wayland_SetWindowResizable(_this, window, !!(window->flags & SDL_WINDOW_RESIZABLE)); Wayland_SetWindowResizable(_this, window, !!(window->flags & SDL_WINDOW_RESIZABLE));
// We're finally done putting the window together, raise if possible // We're finally done putting the window together, raise if possible
if (c->activation_manager) { if (c->activation_manager && SDL_GetHintBoolean(SDL_HINT_WINDOW_ACTIVATE_WHEN_SHOWN, true)) {
/* Note that we don't check for empty strings, as that is still /* if the process was passed an activation token, use it when showing
* the initial window.
*
* Note that we don't check for empty strings, as that is still
* considered a valid activation token! * considered a valid activation token!
*/ */
const char *activation_token = SDL_getenv("XDG_ACTIVATION_TOKEN"); const char *activation_token = SDL_getenv("XDG_ACTIVATION_TOKEN");
@@ -2148,7 +2151,13 @@ void Wayland_ShowWindow(SDL_VideoDevice *_this, SDL_Window *window)
// Clear this variable, per the protocol's request // Clear this variable, per the protocol's request
SDL_unsetenv_unsafe("XDG_ACTIVATION_TOKEN"); SDL_unsetenv_unsafe("XDG_ACTIVATION_TOKEN");
} else {
// Try to generate an activation token for this window.
Wayland_RaiseWindow(_this, window);
} }
} else {
// Clear the ignored activation token, as it won't be used.
SDL_unsetenv_unsafe("XDG_ACTIVATION_TOKEN");
} }
// No frame callback on an external surface, as it may already have one attached. // No frame callback on an external surface, as it may already have one attached.

View File

@@ -1642,14 +1642,16 @@ void X11_HideWindow(SDL_VideoDevice *_this, SDL_Window *window)
Display *display = data->videodata->display; Display *display = data->videodata->display;
XEvent event; XEvent event;
if (X11_IsWindowMapped(_this, window)) { /* Some window managers will mark minimized or offscreen windows as unmapped, so we
X11_XWithdrawWindow(display, data->xwindow, screen); * must not block while waiting for an UnmapNotify event that will never arrive.
// Blocking wait for "UnmapNotify" event */
if (!(window->flags & SDL_WINDOW_EXTERNAL) && X11_IsDisplayOk(display)) { const bool is_mapped = X11_IsWindowMapped(_this, window);
X11_XIfEvent(display, &event, &isUnmapNotify, (XPointer)&data->xwindow); X11_XWithdrawWindow(display, data->xwindow, screen);
} if (is_mapped && !(window->flags & SDL_WINDOW_EXTERNAL) && X11_IsDisplayOk(display)) {
X11_XFlush(display); // Blocking wait for "UnmapNotify" event.
X11_XIfEvent(display, &event, &isUnmapNotify, (XPointer)&data->xwindow);
} }
X11_XFlush(display);
// Transfer keyboard focus back to the parent // Transfer keyboard focus back to the parent
if ((window->flags & SDL_WINDOW_POPUP_MENU) && !(window->flags & SDL_WINDOW_NOT_FOCUSABLE)) { if ((window->flags & SDL_WINDOW_POPUP_MENU) && !(window->flags & SDL_WINDOW_NOT_FOCUSABLE)) {

View File

@@ -82,6 +82,7 @@ static SDL_Renderer *renderer;
static SDL_AudioStream *audio; static SDL_AudioStream *audio;
static SDL_Texture *video_texture; static SDL_Texture *video_texture;
static Uint64 video_start; static Uint64 video_start;
static bool nodelay;
static bool software_only; static bool software_only;
static bool has_eglCreateImage; static bool has_eglCreateImage;
#ifdef HAVE_EGL #ifdef HAVE_EGL
@@ -1087,6 +1088,10 @@ static void DisplayVideoTexture(AVFrame *frame)
return; return;
} }
if (BeginFrameRendering(frame) < 0) {
return;
}
SDL_FRect src; SDL_FRect src;
src.x = 0.0f; src.x = 0.0f;
src.y = 0.0f; src.y = 0.0f;
@@ -1097,6 +1102,9 @@ static void DisplayVideoTexture(AVFrame *frame)
} else { } else {
SDL_RenderTexture(renderer, video_texture, &src, NULL); SDL_RenderTexture(renderer, video_texture, &src, NULL);
} }
SDL_FlushRenderer(renderer);
FinishFrameRendering(frame);
} }
static void DisplayVideoFrame(AVFrame *frame) static void DisplayVideoFrame(AVFrame *frame)
@@ -1106,17 +1114,15 @@ static void DisplayVideoFrame(AVFrame *frame)
static void HandleVideoFrame(AVFrame *frame, double pts) static void HandleVideoFrame(AVFrame *frame, double pts)
{ {
/* Quick and dirty PTS handling */ if (!nodelay) {
if (!video_start) { /* Quick and dirty PTS handling */
video_start = SDL_GetTicks(); if (!video_start) {
} video_start = SDL_GetTicks();
double now = (double)(SDL_GetTicks() - video_start) / 1000.0; }
if (now < pts) { double now = (double)(SDL_GetTicks() - video_start) / 1000.0;
SDL_DelayPrecise((Uint64)((pts - now) * SDL_NS_PER_SECOND)); if (now < pts) {
} SDL_DelayPrecise((Uint64)((pts - now) * SDL_NS_PER_SECOND));
}
if (BeginFrameRendering(frame) < 0) {
return;
} }
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
@@ -1128,8 +1134,6 @@ static void HandleVideoFrame(AVFrame *frame, double pts)
MoveSprite(); MoveSprite();
SDL_RenderPresent(renderer); SDL_RenderPresent(renderer);
FinishFrameRendering(frame);
} }
static AVCodecContext *OpenAudioStream(AVFormatContext *ic, int stream, const AVCodec *codec) static AVCodecContext *OpenAudioStream(AVFormatContext *ic, int stream, const AVCodec *codec)
@@ -1287,7 +1291,7 @@ static void av_log_callback(void *avcl, int level, const char *fmt, va_list vl)
static void print_usage(SDLTest_CommonState *state, const char *argv0) static void print_usage(SDLTest_CommonState *state, const char *argv0)
{ {
static const char *options[] = { "[--verbose]", "[--sprites N]", "[--audio-codec codec]", "[--video-codec codec]", "[--software]", "video_file", NULL }; static const char *options[] = { "[--verbose]", "[--sprites N]", "[--audio-codec codec]", "[--video-codec codec]", "[--software]", "[--nodelay]", "video_file", NULL };
SDLTest_CommonLogUsage(state, argv0, options); SDLTest_CommonLogUsage(state, argv0, options);
} }
@@ -1345,6 +1349,9 @@ int main(int argc, char *argv[])
} else if (SDL_strcmp(argv[i], "--software") == 0) { } else if (SDL_strcmp(argv[i], "--software") == 0) {
software_only = true; software_only = true;
consumed = 1; consumed = 1;
} else if (SDL_strcmp(argv[i], "--nodelay") == 0) {
nodelay = true;
consumed = 1;
} else if (!file) { } else if (!file) {
/* We'll try to open this as a media file */ /* We'll try to open this as a media file */
file = argv[i]; file = argv[i];
@@ -1566,7 +1573,7 @@ int main(int argc, char *argv[])
} }
if (flushing && !decoded) { if (flushing && !decoded) {
if (SDL_GetAudioStreamQueued(audio) > 0) { if (SDL_GetAudioStreamQueued(audio) > 0 && !nodelay) {
/* Wait a little bit for the audio to finish */ /* Wait a little bit for the audio to finish */
SDL_Delay(10); SDL_Delay(10);
} else { } else {

View File

@@ -78,10 +78,6 @@ struct VulkanVideoContext
VkCommandBuffer *commandBuffers; VkCommandBuffer *commandBuffers;
uint32_t commandBufferCount; uint32_t commandBufferCount;
uint32_t commandBufferIndex; uint32_t commandBufferIndex;
VkSemaphore *waitSemaphores;
uint32_t waitSemaphoreCount;
VkSemaphore *signalSemaphores;
uint32_t signalSemaphoreCount;
const char **instanceExtensions; const char **instanceExtensions;
int instanceExtensionsCount; int instanceExtensionsCount;
@@ -202,8 +198,10 @@ static int createInstance(VulkanVideoContext *context)
VK_EXT_SWAPCHAIN_COLOR_SPACE_EXTENSION_NAME, VK_EXT_SWAPCHAIN_COLOR_SPACE_EXTENSION_NAME,
VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME
}; };
VkApplicationInfo appInfo = { 0 }; VkApplicationInfo appInfo;
VkInstanceCreateInfo instanceCreateInfo = { 0 }; SDL_zero(appInfo);
VkInstanceCreateInfo instanceCreateInfo;
SDL_zero(instanceCreateInfo);
VkResult result; VkResult result;
char const *const *instanceExtensions = SDL_Vulkan_GetInstanceExtensions(&instanceCreateInfo.enabledExtensionCount); char const *const *instanceExtensions = SDL_Vulkan_GetInstanceExtensions(&instanceCreateInfo.enabledExtensionCount);
@@ -212,7 +210,7 @@ static int createInstance(VulkanVideoContext *context)
instanceCreateInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; instanceCreateInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
instanceCreateInfo.pApplicationInfo = &appInfo; instanceCreateInfo.pApplicationInfo = &appInfo;
const char **instanceExtensionsCopy = SDL_calloc(instanceCreateInfo.enabledExtensionCount + SDL_arraysize(optional_extensions), sizeof(const char *)); const char **instanceExtensionsCopy = (const char **)SDL_calloc(instanceCreateInfo.enabledExtensionCount + SDL_arraysize(optional_extensions), sizeof(const char *));
for (uint32_t i = 0; i < instanceCreateInfo.enabledExtensionCount; i++) { for (uint32_t i = 0; i < instanceCreateInfo.enabledExtensionCount; i++) {
instanceExtensionsCopy[i] = instanceExtensions[i]; instanceExtensionsCopy[i] = instanceExtensions[i];
} }
@@ -221,7 +219,7 @@ static int createInstance(VulkanVideoContext *context)
{ {
uint32_t extensionCount; uint32_t extensionCount;
if (context->vkEnumerateInstanceExtensionProperties(NULL, &extensionCount, NULL) == VK_SUCCESS && extensionCount > 0) { if (context->vkEnumerateInstanceExtensionProperties(NULL, &extensionCount, NULL) == VK_SUCCESS && extensionCount > 0) {
VkExtensionProperties *extensionProperties = SDL_calloc(extensionCount, sizeof(VkExtensionProperties)); VkExtensionProperties *extensionProperties = (VkExtensionProperties *)SDL_calloc(extensionCount, sizeof(VkExtensionProperties));
if (context->vkEnumerateInstanceExtensionProperties(NULL, &extensionCount, extensionProperties) == VK_SUCCESS) { if (context->vkEnumerateInstanceExtensionProperties(NULL, &extensionCount, extensionProperties) == VK_SUCCESS) {
for (uint32_t i = 0; i < SDL_arraysize(optional_extensions); ++i) { for (uint32_t i = 0; i < SDL_arraysize(optional_extensions); ++i) {
for (uint32_t j = 0; j < extensionCount; ++j) { for (uint32_t j = 0; j < extensionCount; ++j) {
@@ -261,14 +259,14 @@ static int createSurface(VulkanVideoContext *context, SDL_Window *window)
} }
// Use the same queue scoring algorithm as ffmpeg to make sure we get the same device configuration // Use the same queue scoring algorithm as ffmpeg to make sure we get the same device configuration
static int selectQueueFamily(VkQueueFamilyProperties *queueFamiliesProperties, uint32_t queueFamiliesCount, VkQueueFlagBits flags, int *queueCount) static int selectQueueFamily(VkQueueFamilyProperties *queueFamiliesProperties, uint32_t queueFamiliesCount, VkQueueFlags flags, int *queueCount)
{ {
uint32_t queueFamilyIndex; uint32_t queueFamilyIndex;
uint32_t selectedQueueFamilyIndex = queueFamiliesCount; uint32_t selectedQueueFamilyIndex = queueFamiliesCount;
uint32_t min_score = ~0u; uint32_t min_score = ~0u;
for (queueFamilyIndex = 0; queueFamilyIndex < queueFamiliesCount; ++queueFamilyIndex) { for (queueFamilyIndex = 0; queueFamilyIndex < queueFamiliesCount; ++queueFamilyIndex) {
VkQueueFlagBits current_flags = queueFamiliesProperties[queueFamilyIndex].queueFlags; VkQueueFlags current_flags = queueFamiliesProperties[queueFamilyIndex].queueFlags;
if (current_flags & flags) { if (current_flags & flags) {
uint32_t score = av_popcount(current_flags) + queueFamiliesProperties[queueFamilyIndex].timestampValidBits; uint32_t score = av_popcount(current_flags) + queueFamiliesProperties[queueFamilyIndex].timestampValidBits;
if (score < min_score) { if (score < min_score) {
@@ -408,7 +406,7 @@ static int findPhysicalDevice(VulkanVideoContext *context)
if (deviceExtensionsAllocatedSize < deviceExtensionCount) { if (deviceExtensionsAllocatedSize < deviceExtensionCount) {
SDL_free(deviceExtensions); SDL_free(deviceExtensions);
deviceExtensionsAllocatedSize = deviceExtensionCount; deviceExtensionsAllocatedSize = deviceExtensionCount;
deviceExtensions = SDL_malloc(sizeof(VkExtensionProperties) * deviceExtensionsAllocatedSize); deviceExtensions = (VkExtensionProperties *)SDL_malloc(sizeof(VkExtensionProperties) * deviceExtensionsAllocatedSize);
if (!deviceExtensions) { if (!deviceExtensions) {
SDL_free(physicalDevices); SDL_free(physicalDevices);
SDL_free(queueFamiliesProperties); SDL_free(queueFamiliesProperties);
@@ -558,12 +556,14 @@ static int createDevice(VulkanVideoContext *context)
VK_KHR_VIDEO_DECODE_QUEUE_EXTENSION_NAME, VK_KHR_VIDEO_DECODE_QUEUE_EXTENSION_NAME,
VK_KHR_VIDEO_DECODE_H264_EXTENSION_NAME, VK_KHR_VIDEO_DECODE_H264_EXTENSION_NAME,
VK_KHR_VIDEO_DECODE_H265_EXTENSION_NAME, VK_KHR_VIDEO_DECODE_H265_EXTENSION_NAME,
VK_KHR_VIDEO_DECODE_AV1_EXTENSION_NAME VK_KHR_VIDEO_DECODE_AV1_EXTENSION_NAME,
VK_KHR_VIDEO_MAINTENANCE_1_EXTENSION_NAME
}; };
VkDeviceCreateInfo deviceCreateInfo = { 0 }; VkDeviceCreateInfo deviceCreateInfo;
VkDeviceQueueCreateInfo *queueCreateInfos = NULL; VkDeviceQueueCreateInfo *queueCreateInfos = NULL;
uint32_t queueCreateInfoCount = 0; uint32_t queueCreateInfoCount = 0;
VulkanDeviceFeatures supported_features; VulkanDeviceFeatures supported_features;
const char **deviceExtensionsCopy = NULL;
VkResult result = VK_ERROR_UNKNOWN; VkResult result = VK_ERROR_UNKNOWN;
if (addQueueFamily(&queueCreateInfos, &queueCreateInfoCount, context->presentQueueFamilyIndex, context->presentQueueCount) < 0 || if (addQueueFamily(&queueCreateInfos, &queueCreateInfoCount, context->presentQueueFamilyIndex, context->presentQueueCount) < 0 ||
@@ -579,6 +579,7 @@ static int createDevice(VulkanVideoContext *context)
context->vkGetPhysicalDeviceFeatures2(context->physicalDevice, &supported_features.device_features); context->vkGetPhysicalDeviceFeatures2(context->physicalDevice, &supported_features.device_features);
copyDeviceFeatures(&supported_features, &context->features); copyDeviceFeatures(&supported_features, &context->features);
SDL_zero(deviceCreateInfo);
deviceCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; deviceCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
deviceCreateInfo.queueCreateInfoCount = queueCreateInfoCount; deviceCreateInfo.queueCreateInfoCount = queueCreateInfoCount;
deviceCreateInfo.pQueueCreateInfos = queueCreateInfos; deviceCreateInfo.pQueueCreateInfos = queueCreateInfos;
@@ -586,7 +587,7 @@ static int createDevice(VulkanVideoContext *context)
deviceCreateInfo.enabledExtensionCount = SDL_arraysize(deviceExtensionNames); deviceCreateInfo.enabledExtensionCount = SDL_arraysize(deviceExtensionNames);
deviceCreateInfo.pNext = &context->features.device_features; deviceCreateInfo.pNext = &context->features.device_features;
const char **deviceExtensionsCopy = SDL_calloc(deviceCreateInfo.enabledExtensionCount + SDL_arraysize(optional_extensions), sizeof(const char *)); deviceExtensionsCopy = (const char **)SDL_calloc(deviceCreateInfo.enabledExtensionCount + SDL_arraysize(optional_extensions), sizeof(const char *));
for (uint32_t i = 0; i < deviceCreateInfo.enabledExtensionCount; i++) { for (uint32_t i = 0; i < deviceCreateInfo.enabledExtensionCount; i++) {
deviceExtensionsCopy[i] = deviceExtensionNames[i]; deviceExtensionsCopy[i] = deviceExtensionNames[i];
} }
@@ -595,7 +596,7 @@ static int createDevice(VulkanVideoContext *context)
{ {
uint32_t extensionCount; uint32_t extensionCount;
if (context->vkEnumerateDeviceExtensionProperties(context->physicalDevice, NULL, &extensionCount, NULL) == VK_SUCCESS && extensionCount > 0) { if (context->vkEnumerateDeviceExtensionProperties(context->physicalDevice, NULL, &extensionCount, NULL) == VK_SUCCESS && extensionCount > 0) {
VkExtensionProperties *extensionProperties = SDL_calloc(extensionCount, sizeof(VkExtensionProperties)); VkExtensionProperties *extensionProperties = (VkExtensionProperties *)SDL_calloc(extensionCount, sizeof(VkExtensionProperties));
if (context->vkEnumerateDeviceExtensionProperties(context->physicalDevice, NULL, &extensionCount, extensionProperties) == VK_SUCCESS) { if (context->vkEnumerateDeviceExtensionProperties(context->physicalDevice, NULL, &extensionCount, extensionProperties) == VK_SUCCESS) {
for (uint32_t i = 0; i < SDL_arraysize(optional_extensions); ++i) { for (uint32_t i = 0; i < SDL_arraysize(optional_extensions); ++i) {
for (uint32_t j = 0; j < extensionCount; ++j) { for (uint32_t j = 0; j < extensionCount; ++j) {
@@ -630,7 +631,8 @@ static int createDevice(VulkanVideoContext *context)
context->vkGetDeviceQueue(context->device, context->graphicsQueueFamilyIndex, 0, &context->graphicsQueue); context->vkGetDeviceQueue(context->device, context->graphicsQueueFamilyIndex, 0, &context->graphicsQueue);
// Create a command pool // Create a command pool
VkCommandPoolCreateInfo commandPoolCreateInfo = { 0 }; VkCommandPoolCreateInfo commandPoolCreateInfo;
SDL_zero(commandPoolCreateInfo);
commandPoolCreateInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; commandPoolCreateInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
commandPoolCreateInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; commandPoolCreateInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
commandPoolCreateInfo.queueFamilyIndex = context->graphicsQueueFamilyIndex; commandPoolCreateInfo.queueFamilyIndex = context->graphicsQueueFamilyIndex;
@@ -654,7 +656,7 @@ done:
VulkanVideoContext *CreateVulkanVideoContext(SDL_Window *window) VulkanVideoContext *CreateVulkanVideoContext(SDL_Window *window)
{ {
VulkanVideoContext *context = SDL_calloc(1, sizeof(*context)); VulkanVideoContext *context = (VulkanVideoContext *)SDL_calloc(1, sizeof(*context));
if (!context) { if (!context) {
return NULL; return NULL;
} }
@@ -723,44 +725,6 @@ static int CreateCommandBuffers(VulkanVideoContext *context, SDL_Renderer *rende
{ {
uint32_t commandBufferCount = (uint32_t)SDL_GetNumberProperty(SDL_GetRendererProperties(renderer), SDL_PROP_RENDERER_VULKAN_SWAPCHAIN_IMAGE_COUNT_NUMBER, 1); uint32_t commandBufferCount = (uint32_t)SDL_GetNumberProperty(SDL_GetRendererProperties(renderer), SDL_PROP_RENDERER_VULKAN_SWAPCHAIN_IMAGE_COUNT_NUMBER, 1);
if (commandBufferCount > context->waitSemaphoreCount) {
VkSemaphore *semaphores = (VkSemaphore *)SDL_realloc(context->waitSemaphores, commandBufferCount * sizeof(*semaphores));
if (!semaphores) {
return -1;
}
context->waitSemaphores = semaphores;
VkSemaphoreCreateInfo semaphoreCreateInfo = { 0 };
semaphoreCreateInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
while (context->waitSemaphoreCount < commandBufferCount) {
VkResult result = context->vkCreateSemaphore(context->device, &semaphoreCreateInfo, NULL, &context->waitSemaphores[context->waitSemaphoreCount]);
if (result != VK_SUCCESS) {
SDL_SetError("vkCreateSemaphore(): %s", getVulkanResultString(result));
return -1;
}
++context->waitSemaphoreCount;
}
}
if (commandBufferCount > context->signalSemaphoreCount) {
VkSemaphore *semaphores = (VkSemaphore *)SDL_realloc(context->signalSemaphores, commandBufferCount * sizeof(*semaphores));
if (!semaphores) {
return -1;
}
context->signalSemaphores = semaphores;
VkSemaphoreCreateInfo semaphoreCreateInfo = { 0 };
semaphoreCreateInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
while (context->signalSemaphoreCount < commandBufferCount) {
VkResult result = context->vkCreateSemaphore(context->device, &semaphoreCreateInfo, NULL, &context->signalSemaphores[context->signalSemaphoreCount]);
if (result != VK_SUCCESS) {
SDL_SetError("vkCreateSemaphore(): %s", getVulkanResultString(result));
return -1;
}
++context->signalSemaphoreCount;
}
}
if (commandBufferCount > context->commandBufferCount) { if (commandBufferCount > context->commandBufferCount) {
uint32_t needed = (commandBufferCount - context->commandBufferCount); uint32_t needed = (commandBufferCount - context->commandBufferCount);
VkCommandBuffer *commandBuffers = (VkCommandBuffer *)SDL_realloc(context->commandBuffers, commandBufferCount * sizeof(*commandBuffers)); VkCommandBuffer *commandBuffers = (VkCommandBuffer *)SDL_realloc(context->commandBuffers, commandBufferCount * sizeof(*commandBuffers));
@@ -769,7 +733,8 @@ static int CreateCommandBuffers(VulkanVideoContext *context, SDL_Renderer *rende
} }
context->commandBuffers = commandBuffers; context->commandBuffers = commandBuffers;
VkCommandBufferAllocateInfo commandBufferAllocateInfo = { 0 }; VkCommandBufferAllocateInfo commandBufferAllocateInfo;
SDL_zero(commandBufferAllocateInfo);
commandBufferAllocateInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; commandBufferAllocateInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
commandBufferAllocateInfo.commandPool = context->commandPool; commandBufferAllocateInfo.commandPool = context->commandPool;
commandBufferAllocateInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; commandBufferAllocateInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
@@ -797,30 +762,32 @@ int BeginVulkanFrameRendering(VulkanVideoContext *context, AVFrame *frame, SDL_R
vk->lock_frame(frames, pVkFrame); vk->lock_frame(frames, pVkFrame);
VkTimelineSemaphoreSubmitInfo timeline = { 0 }; VkTimelineSemaphoreSubmitInfo timeline;
SDL_zero(timeline);
timeline.sType = VK_STRUCTURE_TYPE_TIMELINE_SEMAPHORE_SUBMIT_INFO; timeline.sType = VK_STRUCTURE_TYPE_TIMELINE_SEMAPHORE_SUBMIT_INFO;
timeline.waitSemaphoreValueCount = 1; timeline.waitSemaphoreValueCount = 1;
timeline.pWaitSemaphoreValues = pVkFrame->sem_value; timeline.pWaitSemaphoreValues = pVkFrame->sem_value;
VkPipelineStageFlags pipelineStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; VkPipelineStageFlags pipelineStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
VkSubmitInfo submitInfo = { 0 }; VkSubmitInfo submitInfo;
SDL_zero(submitInfo);
submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
submitInfo.waitSemaphoreCount = 1; submitInfo.waitSemaphoreCount = 1;
submitInfo.pWaitSemaphores = pVkFrame->sem; submitInfo.pWaitSemaphores = pVkFrame->sem;
submitInfo.pWaitDstStageMask = &pipelineStageMask; submitInfo.pWaitDstStageMask = &pipelineStageMask;
submitInfo.signalSemaphoreCount = 1;
submitInfo.pSignalSemaphores = &context->waitSemaphores[context->commandBufferIndex];
submitInfo.pNext = &timeline; submitInfo.pNext = &timeline;
if (pVkFrame->layout[0] != VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) { if (pVkFrame->layout[0] != VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) {
VkCommandBuffer commandBuffer = context->commandBuffers[context->commandBufferIndex]; VkCommandBuffer commandBuffer = context->commandBuffers[context->commandBufferIndex];
VkCommandBufferBeginInfo beginInfo = { 0 }; VkCommandBufferBeginInfo beginInfo;
SDL_zero(beginInfo);
beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
beginInfo.flags = 0; beginInfo.flags = 0;
context->vkBeginCommandBuffer(commandBuffer, &beginInfo); context->vkBeginCommandBuffer(commandBuffer, &beginInfo);
VkImageMemoryBarrier2 barrier = { 0 }; VkImageMemoryBarrier2 barrier;
SDL_zero(barrier);
barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2; barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2;
barrier.srcAccessMask = VK_ACCESS_2_NONE; barrier.srcAccessMask = VK_ACCESS_2_NONE;
barrier.dstAccessMask = VK_ACCESS_2_SHADER_SAMPLED_READ_BIT; barrier.dstAccessMask = VK_ACCESS_2_SHADER_SAMPLED_READ_BIT;
@@ -835,7 +802,8 @@ int BeginVulkanFrameRendering(VulkanVideoContext *context, AVFrame *frame, SDL_R
barrier.srcStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; barrier.srcStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
barrier.dstStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; barrier.dstStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
VkDependencyInfo dep = { 0 }; VkDependencyInfo dep;
SDL_zero(dep);
dep.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; dep.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO;
dep.dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT; dep.dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
dep.imageMemoryBarrierCount = 1; dep.imageMemoryBarrierCount = 1;
@@ -858,8 +826,6 @@ int BeginVulkanFrameRendering(VulkanVideoContext *context, AVFrame *frame, SDL_R
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION , "vkQueueSubmit(): %s", getVulkanResultString(result)); SDL_LogError(SDL_LOG_CATEGORY_APPLICATION , "vkQueueSubmit(): %s", getVulkanResultString(result));
} }
SDL_AddVulkanRenderSemaphores(renderer, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, (Sint64)context->waitSemaphores[context->commandBufferIndex], (Sint64)context->signalSemaphores[context->commandBufferIndex]);
return 0; return 0;
} }
@@ -872,17 +838,15 @@ int FinishVulkanFrameRendering(VulkanVideoContext *context, AVFrame *frame, SDL_
// Transition the frame back to ffmpeg // Transition the frame back to ffmpeg
++pVkFrame->sem_value[0]; ++pVkFrame->sem_value[0];
VkTimelineSemaphoreSubmitInfo timeline = { 0 }; VkTimelineSemaphoreSubmitInfo timeline;
SDL_zero(timeline);
timeline.sType = VK_STRUCTURE_TYPE_TIMELINE_SEMAPHORE_SUBMIT_INFO; timeline.sType = VK_STRUCTURE_TYPE_TIMELINE_SEMAPHORE_SUBMIT_INFO;
timeline.signalSemaphoreValueCount = 1; timeline.signalSemaphoreValueCount = 1;
timeline.pSignalSemaphoreValues = pVkFrame->sem_value; timeline.pSignalSemaphoreValues = pVkFrame->sem_value;
VkPipelineStageFlags pipelineStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; VkSubmitInfo submitInfo;
VkSubmitInfo submitInfo = { 0 }; SDL_zero(submitInfo);
submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
submitInfo.waitSemaphoreCount = 1;
submitInfo.pWaitSemaphores = &context->signalSemaphores[context->commandBufferIndex];
submitInfo.pWaitDstStageMask = &pipelineStageMask;
submitInfo.signalSemaphoreCount = 1; submitInfo.signalSemaphoreCount = 1;
submitInfo.pSignalSemaphores = pVkFrame->sem; submitInfo.pSignalSemaphores = pVkFrame->sem;
submitInfo.pNext = &timeline; submitInfo.pNext = &timeline;
@@ -940,20 +904,6 @@ void DestroyVulkanVideoContext(VulkanVideoContext *context)
} }
SDL_free(context->instanceExtensions); SDL_free(context->instanceExtensions);
SDL_free(context->deviceExtensions); SDL_free(context->deviceExtensions);
if (context->waitSemaphores) {
for (uint32_t i = 0; i < context->waitSemaphoreCount; ++i) {
context->vkDestroySemaphore(context->device, context->waitSemaphores[i], NULL);
}
SDL_free(context->waitSemaphores);
context->waitSemaphores = NULL;
}
if (context->signalSemaphores) {
for (uint32_t i = 0; i < context->signalSemaphoreCount; ++i) {
context->vkDestroySemaphore(context->device, context->signalSemaphores[i], NULL);
}
SDL_free(context->signalSemaphores);
context->signalSemaphores = NULL;
}
if (context->commandBuffers) { if (context->commandBuffers) {
context->vkFreeCommandBuffers(context->device, context->commandPool, context->commandBufferCount, context->commandBuffers); context->vkFreeCommandBuffers(context->device, context->commandPool, context->commandBufferCount, context->commandBuffers);
SDL_free(context->commandBuffers); SDL_free(context->commandBuffers);

View File

@@ -789,6 +789,8 @@ static int process_testNonExistingExecutable(void *arg)
char *random_stem; char *random_stem;
char *random_path; char *random_path;
SDL_Process *process = NULL; SDL_Process *process = NULL;
bool result;
int exit_code = 0;
random_stem = SDLTest_RandomAsciiStringOfSize(STEM_LENGTH); random_stem = SDLTest_RandomAsciiStringOfSize(STEM_LENGTH);
random_path = SDL_malloc(STEM_LENGTH + SDL_strlen(EXE) + 1); random_path = SDL_malloc(STEM_LENGTH + SDL_strlen(EXE) + 1);
@@ -801,7 +803,15 @@ static int process_testNonExistingExecutable(void *arg)
SDLTest_AssertPass("About to call SDL_CreateProcess"); SDLTest_AssertPass("About to call SDL_CreateProcess");
process = SDL_CreateProcess((const char * const *)process_args, false); process = SDL_CreateProcess((const char * const *)process_args, false);
SDLTest_AssertCheck(process == NULL, "SDL_CreateProcess() should have failed (%s)", SDL_GetError()); if (process) {
SDLTest_AssertPass("SDL_CreateProcess() returned a process, waiting for exec failure");
result = SDL_WaitProcess(process, true, &exit_code);
SDLTest_AssertCheck(result, "SDL_WaitProcess()");
SDLTest_AssertCheck(exit_code != 0, "Exit code should be non-zero, is %d", exit_code);
SDL_DestroyProcess(process);
} else {
SDLTest_AssertPass("SDL_CreateProcess() failed synchronously (%s)", SDL_GetError());
}
DestroyStringArray(process_args); DestroyStringArray(process_args);
return TEST_COMPLETED; return TEST_COMPLETED;

File diff suppressed because it is too large Load Diff