From ab332aa6efd5fa0f68f0ca2574eed9ddfdea29d1 Mon Sep 17 00:00:00 2001 From: Sasha Szpakowski Date: Sun, 22 Jun 2025 16:51:22 -0300 Subject: [PATCH] update SDL3 to libsdl-org/SDL@038a380 (3.3 development) --- .../actions/setup-ngage-sdk/action.yml | 102 + .../.github/workflows/create-test-plan.py | 26 +- libs/SDL3/.github/workflows/generic.yml | 5 + libs/SDL3/.github/workflows/release.yml | 3 + libs/SDL3/.wikiheaders-options | 3 +- libs/SDL3/Android.mk | 1 + libs/SDL3/CMakeLists.txt | 125 +- libs/SDL3/VisualC-GDK/SDL/SDL.vcxproj | 18 +- libs/SDL3/VisualC-GDK/SDL/SDL.vcxproj.filters | 14 +- .../VisualC-GDK/tests/testgdk/src/testgdk.cpp | 47 +- libs/SDL3/VisualC/SDL.sln | 74 +- libs/SDL3/VisualC/SDL/SDL.vcxproj | 39 +- libs/SDL3/VisualC/SDL/SDL.vcxproj.filters | 47 +- .../01-load-bitmaps/01-load-bitmaps.vcxproj | 13 + .../04-multiple-streams.vcxproj | 13 + .../demo/04-bytepusher/04-bytepusher.vcxproj | 13 + libs/SDL3/VisualC/examples/generate.py | 3 +- .../01-joystick-polling.vcxproj | 13 + .../02-joystick-events.vcxproj | 13 + .../19-affine-textures.vcxproj | 13 + libs/SDL3/Xcode/SDL/Info-Framework.plist | 4 +- .../Xcode/SDL/SDL.xcodeproj/project.pbxproj | 48 +- libs/SDL3/Xcode/SDL/pkg-support/SDL.info | 2 +- .../share/cmake/SDL3/SDL3Config.cmake | 2 +- .../android-project/app/proguard-rules.pro | 3 +- .../app/HIDDeviceBLESteamController.java | 30 +- .../java/org/libsdl/app/HIDDeviceManager.java | 30 +- .../java/org/libsdl/app/HIDDeviceUSB.java | 4 +- .../app/src/main/java/org/libsdl/app/SDL.java | 12 +- .../main/java/org/libsdl/app/SDLActivity.java | 43 +- .../java/org/libsdl/app/SDLAudioManager.java | 28 +- .../org/libsdl/app/SDLControllerManager.java | 132 +- .../java/org/libsdl/app/SDLDummyEdit.java | 4 +- .../org/libsdl/app/SDLInputConnection.java | 4 +- .../main/java/org/libsdl/app/SDLSurface.java | 15 +- libs/SDL3/build-scripts/build-release.py | 7 +- libs/SDL3/build-scripts/wikiheaders.pl | 135 +- libs/SDL3/cmake/PreseedMSVCCache.cmake | 1 + libs/SDL3/cmake/PreseedNokiaNGageCache.cmake | 189 + libs/SDL3/cmake/macros.cmake | 3 - libs/SDL3/cmake/sdlchecks.cmake | 44 +- .../{sdltargets.cmake => sdlcommands.cmake} | 0 libs/SDL3/cmake/sdlcpu.cmake | 6 +- libs/SDL3/cmake/sdlplatform.cmake | 2 + libs/SDL3/cmake/test/CMakeLists.txt | 14 +- libs/SDL3/cmake/test/main_gui.c | 37 +- libs/SDL3/cmake/test/sdltest.c | 19 +- libs/SDL3/docs/README-android.md | 2 +- libs/SDL3/docs/README-cmake.md | 8 +- libs/SDL3/docs/README-documentation-rules.md | 40 +- libs/SDL3/docs/README-emscripten.md | 4 +- libs/SDL3/docs/README-highdpi.md | 56 +- libs/SDL3/docs/README-linux.md | 5 +- libs/SDL3/docs/README-migration.md | 2 +- libs/SDL3/docs/README-ngage.md | 65 +- libs/SDL3/docs/README-platforms.md | 4 +- libs/SDL3/docs/README-psp.md | 4 +- libs/SDL3/docs/README-versions.md | 68 +- libs/SDL3/docs/README-wayland.md | 9 + libs/SDL3/docs/release_checklist.md | 2 + libs/SDL3/examples/CMakeLists.txt | 6 + .../audio/04-multiple-streams/README.txt | 2 +- .../04-multiple-streams/multiple-streams.c | 2 +- .../examples/audio/05-planar-data/README.txt | 7 + .../audio/05-planar-data/onmouseover.webp | Bin 0 -> 17488 bytes .../audio/05-planar-data/planar-data.c | 366 ++ .../audio/05-planar-data/thumbnail.png | Bin 0 -> 2637 bytes libs/SDL3/examples/demo/01-snake/snake.c | 48 +- .../examples/demo/04-bytepusher/bytepusher.c | 23 +- .../01-joystick-polling/joystick-polling.c | 2 +- .../renderer/19-affine-textures/README.txt | 3 + .../19-affine-textures/affine-textures.c | 145 + .../19-affine-textures/onmouseover.webp | Bin 0 -> 211490 bytes .../renderer/19-affine-textures/thumbnail.png | Bin 0 -> 234754 bytes libs/SDL3/include/SDL3/SDL.h | 2 +- libs/SDL3/include/SDL3/SDL_assert.h | 2 +- libs/SDL3/include/SDL3/SDL_audio.h | 228 +- libs/SDL3/include/SDL3/SDL_begin_code.h | 73 +- libs/SDL3/include/SDL3/SDL_events.h | 39 +- libs/SDL3/include/SDL3/SDL_filesystem.h | 4 +- libs/SDL3/include/SDL3/SDL_gamepad.h | 12 +- libs/SDL3/include/SDL3/SDL_gpu.h | 333 +- libs/SDL3/include/SDL3/SDL_haptic.h | 72 +- libs/SDL3/include/SDL3/SDL_hints.h | 104 +- libs/SDL3/include/SDL3/SDL_init.h | 4 +- libs/SDL3/include/SDL3/SDL_iostream.h | 24 +- libs/SDL3/include/SDL3/SDL_joystick.h | 4 + libs/SDL3/include/SDL3/SDL_log.h | 3 + libs/SDL3/include/SDL3/SDL_main.h | 20 +- libs/SDL3/include/SDL3/SDL_mouse.h | 76 +- libs/SDL3/include/SDL3/SDL_mutex.h | 2 +- libs/SDL3/include/SDL3/SDL_pixels.h | 10 +- libs/SDL3/include/SDL3/SDL_platform_defines.h | 13 +- libs/SDL3/include/SDL3/SDL_process.h | 11 + libs/SDL3/include/SDL3/SDL_rect.h | 2 +- libs/SDL3/include/SDL3/SDL_render.h | 297 +- libs/SDL3/include/SDL3/SDL_stdinc.h | 10 +- libs/SDL3/include/SDL3/SDL_storage.h | 4 + libs/SDL3/include/SDL3/SDL_surface.h | 16 +- libs/SDL3/include/SDL3/SDL_system.h | 4 +- libs/SDL3/include/SDL3/SDL_timer.h | 8 +- libs/SDL3/include/SDL3/SDL_version.h | 11 +- libs/SDL3/include/SDL3/SDL_video.h | 175 +- libs/SDL3/include/SDL3/SDL_vulkan.h | 6 +- .../build_config/SDL_build_config.h.cmake | 62 + .../build_config/SDL_build_config_windows.h | 3 + .../build_config/SDL_build_config_wingdk.h | 1 + .../build_config/SDL_build_config_xbox.h | 1 + libs/SDL3/src/SDL.c | 2 + libs/SDL3/src/SDL_error.c | 2 + libs/SDL3/src/SDL_hints.c | 35 +- libs/SDL3/src/SDL_internal.h | 6 + libs/SDL3/src/SDL_log.c | 24 + libs/SDL3/src/SDL_utils.c | 6 +- libs/SDL3/src/atomic/SDL_atomic.c | 2 +- libs/SDL3/src/audio/SDL_audio.c | 115 +- libs/SDL3/src/audio/SDL_audiocvt.c | 294 +- libs/SDL3/src/audio/SDL_audioqueue.h | 2 +- libs/SDL3/src/audio/SDL_audiotypecvt.c | 98 +- libs/SDL3/src/audio/SDL_sysaudio.h | 16 +- libs/SDL3/src/audio/aaudio/SDL_aaudio.c | 8 +- libs/SDL3/src/audio/aaudio/SDL_aaudiofuncs.h | 2 +- libs/SDL3/src/audio/alsa/SDL_alsa_audio.c | 105 +- libs/SDL3/src/audio/coreaudio/SDL_coreaudio.m | 4 +- libs/SDL3/src/audio/disk/SDL_diskaudio.c | 14 +- .../audio/emscripten/SDL_emscriptenaudio.c | 4 +- libs/SDL3/src/audio/ngage/SDL_ngageaudio.c | 103 + libs/SDL3/src/audio/ngage/SDL_ngageaudio.cpp | 368 ++ .../ngage/SDL_ngageaudio.h} | 27 +- libs/SDL3/src/audio/ngage/SDL_ngageaudio.hpp | 98 + libs/SDL3/src/audio/pipewire/SDL_pipewire.c | 18 +- .../src/audio/pulseaudio/SDL_pulseaudio.c | 3 +- libs/SDL3/src/audio/vita/SDL_vitaaudio.c | 3 +- libs/SDL3/src/audio/wasapi/SDL_wasapi.c | 2 +- .../camera/coremedia/SDL_camera_coremedia.m | 2 +- .../camera/emscripten/SDL_camera_emscripten.c | 2 +- .../SDL_camera_mediafoundation.c | 8 +- .../src/camera/pipewire/SDL_camera_pipewire.c | 28 +- libs/SDL3/src/camera/v4l2/SDL_camera_v4l2.c | 6 +- libs/SDL3/src/camera/vita/SDL_camera_vita.c | 14 +- libs/SDL3/src/core/SDL_core_unsupported.c | 6 +- libs/SDL3/src/core/android/SDL_android.c | 108 +- libs/SDL3/src/core/android/SDL_android.h | 4 +- .../src/core/freebsd/SDL_evdev_kbd_freebsd.c | 2 +- libs/SDL3/src/core/gdk/SDL_gdk.cpp | 2 +- libs/SDL3/src/core/haiku/SDL_BApp.h | 2 +- libs/SDL3/src/core/haiku/SDL_BeApp.cc | 4 +- libs/SDL3/src/core/linux/SDL_dbus.c | 185 + libs/SDL3/src/core/linux/SDL_dbus.h | 10 + libs/SDL3/src/core/linux/SDL_evdev.c | 4 +- libs/SDL3/src/core/linux/SDL_evdev.h | 4 +- libs/SDL3/src/core/linux/SDL_evdev_kbd.c | 4 +- libs/SDL3/src/core/linux/SDL_evdev_kbd.h | 2 +- libs/SDL3/src/core/linux/SDL_progressbar.c | 159 + .../linux/SDL_progressbar.h} | 14 +- libs/SDL3/src/core/linux/SDL_udev.c | 2 +- libs/SDL3/src/core/ngage/SDL_ngage.cpp | 77 + libs/SDL3/src/core/ngage/SDL_ngage.h | 36 + .../{SDL_gameinput.c => SDL_gameinput.cpp} | 21 +- libs/SDL3/src/core/windows/SDL_gameinput.h | 9 +- libs/SDL3/src/core/windows/SDL_windows.c | 16 +- libs/SDL3/src/core/windows/SDL_xinput.c | 12 - libs/SDL3/src/core/windows/SDL_xinput.h | 1 - libs/SDL3/src/core/windows/version.rc | 8 +- libs/SDL3/src/cpuinfo/SDL_cpuinfo.c | 16 +- libs/SDL3/src/dialog/cocoa/SDL_cocoadialog.m | 59 +- libs/SDL3/src/dialog/haiku/SDL_haikudialog.cc | 22 +- libs/SDL3/src/dialog/unix/SDL_portaldialog.c | 6 +- .../src/dialog/windows/SDL_windowsdialog.c | 28 +- libs/SDL3/src/dynapi/SDL_dynapi.h | 2 + libs/SDL3/src/dynapi/SDL_dynapi.sym | 20 + libs/SDL3/src/dynapi/SDL_dynapi_overrides.h | 20 + libs/SDL3/src/dynapi/SDL_dynapi_procs.h | 32 +- libs/SDL3/src/events/SDL_clipboardevents.c | 12 +- libs/SDL3/src/events/SDL_events.c | 102 +- libs/SDL3/src/events/SDL_keyboard.c | 91 +- libs/SDL3/src/events/SDL_keyboard_c.h | 3 + libs/SDL3/src/events/SDL_keymap.c | 89 +- libs/SDL3/src/events/SDL_keymap_c.h | 18 +- libs/SDL3/src/events/SDL_mouse.c | 166 +- libs/SDL3/src/events/SDL_mouse_c.h | 22 +- libs/SDL3/src/events/SDL_pen.c | 13 +- libs/SDL3/src/events/SDL_touch.c | 16 +- libs/SDL3/src/events/SDL_touch_c.h | 3 + libs/SDL3/src/events/SDL_windowevents.c | 1 + libs/SDL3/src/events/scancodes_xfree86.h | 8 +- .../src/filesystem/cocoa/SDL_sysfilesystem.m | 2 +- .../src/filesystem/gdk/SDL_sysfilesystem.cpp | 2 +- .../src/filesystem/ngage/SDL_sysfilesystem.c | 67 + .../filesystem/ngage/SDL_sysfilesystem.cpp | 68 + .../src/filesystem/unix/SDL_sysfilesystem.c | 10 +- .../filesystem/windows/SDL_sysfilesystem.c | 2 +- libs/SDL3/src/gpu/SDL_gpu.c | 500 +- libs/SDL3/src/gpu/SDL_sysgpu.h | 254 +- libs/SDL3/src/gpu/d3d12/SDL_gpu_d3d12.c | 702 ++- libs/SDL3/src/gpu/metal/SDL_gpu_metal.m | 203 +- libs/SDL3/src/gpu/vulkan/SDL_gpu_vulkan.c | 767 ++-- libs/SDL3/src/haptic/SDL_haptic.c | 163 +- libs/SDL3/src/haptic/SDL_syshaptic.h | 2 +- .../SDL3/src/haptic/hidapi/SDL_hidapihaptic.c | 306 ++ .../SDL3/src/haptic/hidapi/SDL_hidapihaptic.h | 48 + .../src/haptic/hidapi/SDL_hidapihaptic_c.h | 70 + .../haptic/hidapi/SDL_hidapihaptic_lg4ff.c | 1264 ++++++ libs/SDL3/src/hidapi/SDL_hidapi.c | 13 +- libs/SDL3/src/hidapi/SDL_hidapi_windows.h | 4 + libs/SDL3/src/hidapi/android/hid.cpp | 14 +- libs/SDL3/src/hidapi/libusb/hid.c | 14 + .../io/windows/SDL_asyncio_windows_ioring.c | 2 +- libs/SDL3/src/joystick/SDL_gamepad.c | 250 +- libs/SDL3/src/joystick/SDL_gamepad_db.h | 27 +- libs/SDL3/src/joystick/SDL_joystick.c | 333 +- libs/SDL3/src/joystick/SDL_joystick_c.h | 6 + libs/SDL3/src/joystick/SDL_sysjoystick.h | 1 + .../SDL3/src/joystick/apple/SDL_mfijoystick.m | 4 +- libs/SDL3/src/joystick/controller_list.h | 10 +- .../src/joystick/darwin/SDL_iokitjoystick.c | 35 +- .../src/joystick/darwin/SDL_iokitjoystick_c.h | 1 + ...utjoystick.c => SDL_gameinputjoystick.cpp} | 119 +- .../src/joystick/hidapi/SDL_hidapi_8bitdo.c | 647 +++ .../src/joystick/hidapi/SDL_hidapi_flydigi.c | 518 +++ .../src/joystick/hidapi/SDL_hidapi_gamecube.c | 109 +- .../SDL3/src/joystick/hidapi/SDL_hidapi_gip.c | 2958 ++++++++++++ .../src/joystick/hidapi/SDL_hidapi_lg4ff.c | 1002 ++++ .../joystick/hidapi/SDL_hidapi_steamdeck.c | 15 + .../src/joystick/hidapi/SDL_hidapi_switch.c | 70 +- .../src/joystick/hidapi/SDL_hidapijoystick.c | 28 +- .../joystick/hidapi/SDL_hidapijoystick_c.h | 8 + libs/SDL3/src/joystick/usb_ids.h | 41 +- .../src/joystick/windows/SDL_dinputjoystick.c | 2 +- .../joystick/windows/SDL_rawinputjoystick.c | 20 +- libs/SDL3/src/locale/ngage/SDL_syslocale.cpp | 307 ++ libs/SDL3/src/main/SDL_main_callbacks.c | 2 +- libs/SDL3/src/main/SDL_runapp.c | 4 +- .../main/emscripten/SDL_sysmain_callbacks.c | 2 +- .../src/main/emscripten/SDL_sysmain_runapp.c | 2 +- libs/SDL3/src/main/gdk/SDL_sysmain_runapp.cpp | 2 +- .../src/main/generic/SDL_sysmain_callbacks.c | 2 +- .../SDL3/src/main/ios/SDL_sysmain_callbacks.m | 2 +- libs/SDL3/src/main/n3ds/SDL_sysmain_runapp.c | 2 +- .../src/main/ngage/SDL_sysmain_callbacks.c | 31 + libs/SDL3/src/main/ngage/SDL_sysmain_main.cpp | 199 + libs/SDL3/src/main/ngage/SDL_sysmain_main.hpp | 46 + libs/SDL3/src/main/ps2/SDL_sysmain_runapp.c | 2 +- libs/SDL3/src/main/psp/SDL_sysmain_runapp.c | 2 +- .../src/main/windows/SDL_sysmain_runapp.c | 2 +- libs/SDL3/src/process/SDL_process.c | 8 + .../SDL3/src/process/posix/SDL_posixprocess.c | 31 + .../src/process/windows/SDL_windowsprocess.c | 49 +- libs/SDL3/src/render/SDL_render.c | 479 +- libs/SDL3/src/render/SDL_sysrender.h | 56 +- .../SDL3/src/render/direct3d/SDL_render_d3d.c | 53 +- .../direct3d11/D3D11_PixelShader_Advanced.h | 2021 +++++---- .../direct3d11/D3D11_PixelShader_Colors.h | 438 +- .../direct3d11/D3D11_PixelShader_Common.hlsli | 23 +- .../direct3d11/D3D11_PixelShader_Textures.h | 510 +-- .../render/direct3d11/D3D11_VertexShader.h | 484 +- .../src/render/direct3d11/SDL_render_d3d11.c | 156 +- .../direct3d12/D3D12_PixelShader_Advanced.h | 2370 +++++----- .../direct3d12/D3D12_PixelShader_Colors.h | 671 +-- .../direct3d12/D3D12_PixelShader_Common.hlsli | 23 +- .../direct3d12/D3D12_PixelShader_Textures.h | 809 ++-- .../direct3d12/D3D12_RootSig_Advanced.h | 6 +- .../render/direct3d12/D3D12_RootSig_Color.h | 6 +- .../render/direct3d12/D3D12_RootSig_Texture.h | 6 +- .../direct3d12/D3D12_Shader_Common.hlsli | 6 +- .../direct3d12/D3D12_VertexShader_Advanced.h | 786 ++-- .../direct3d12/D3D12_VertexShader_Color.h | 754 ++-- .../direct3d12/D3D12_VertexShader_Texture.h | 770 ++-- .../src/render/direct3d12/SDL_render_d3d12.c | 147 +- libs/SDL3/src/render/gpu/SDL_pipeline_gpu.c | 32 +- libs/SDL3/src/render/gpu/SDL_pipeline_gpu.h | 1 + libs/SDL3/src/render/gpu/SDL_render_gpu.c | 248 +- libs/SDL3/src/render/gpu/SDL_shaders_gpu.c | 69 +- libs/SDL3/src/render/gpu/SDL_shaders_gpu.h | 3 + .../src/render/gpu/shaders/build-shaders.sh | 104 +- libs/SDL3/src/render/gpu/shaders/color.frag | 9 - .../src/render/gpu/shaders/color.frag.dxil.h | 238 + .../src/render/gpu/shaders/color.frag.metal.h | 28 - .../src/render/gpu/shaders/color.frag.msl.h | 32 + .../render/gpu/shaders/color.frag.sm50.dxbc.h | 85 - .../render/gpu/shaders/color.frag.sm60.dxil.h | 340 -- .../src/render/gpu/shaders/color.frag.spv.h | 57 +- libs/SDL3/src/render/gpu/shaders/dxbc50.h | 6 - libs/SDL3/src/render/gpu/shaders/dxil.h | 8 + libs/SDL3/src/render/gpu/shaders/dxil60.h | 6 - .../src/render/gpu/shaders/linepoint.vert | 17 - .../render/gpu/shaders/linepoint.vert.dxil.h | 322 ++ .../render/gpu/shaders/linepoint.vert.metal.h | 51 - .../render/gpu/shaders/linepoint.vert.msl.h | 55 + .../gpu/shaders/linepoint.vert.sm50.dxbc.h | 172 - .../gpu/shaders/linepoint.vert.sm60.dxil.h | 496 -- .../render/gpu/shaders/linepoint.vert.spv.h | 175 +- libs/SDL3/src/render/gpu/shaders/metal.h | 6 - libs/SDL3/src/render/gpu/shaders/msl.h | 8 + libs/SDL3/src/render/gpu/shaders/spir-v.h | 8 +- .../gpu/shaders/texture_pixelart.frag.hlsli | 33 + .../src/render/gpu/shaders/texture_rgb.frag | 12 - .../gpu/shaders/texture_rgb.frag.dxil.h | 325 ++ .../gpu/shaders/texture_rgb.frag.metal.h | 41 - .../render/gpu/shaders/texture_rgb.frag.msl.h | 48 + .../gpu/shaders/texture_rgb.frag.sm50.dxbc.h | 123 - .../gpu/shaders/texture_rgb.frag.sm60.dxil.h | 465 -- .../render/gpu/shaders/texture_rgb.frag.spv.h | 132 +- .../shaders/texture_rgb_pixelart.frag.dxil.h | 401 ++ .../shaders/texture_rgb_pixelart.frag.msl.h | 87 + .../shaders/texture_rgb_pixelart.frag.spv.h | 148 + .../src/render/gpu/shaders/texture_rgba.frag | 12 - .../gpu/shaders/texture_rgba.frag.dxil.h | 325 ++ .../gpu/shaders/texture_rgba.frag.metal.h | 40 - .../gpu/shaders/texture_rgba.frag.msl.h | 47 + .../gpu/shaders/texture_rgba.frag.sm50.dxbc.h | 120 - .../gpu/shaders/texture_rgba.frag.spv.h | 116 +- .../shaders/texture_rgba_pixelart.frag.dxil.h | 402 ++ .../shaders/texture_rgba_pixelart.frag.msl.h | 86 + .../shaders/texture_rgba_pixelart.frag.spv.h | 141 + .../src/render/gpu/shaders/tri_color.vert | 17 - .../render/gpu/shaders/tri_color.vert.dxil.h | 334 ++ .../render/gpu/shaders/tri_color.vert.metal.h | 48 - .../render/gpu/shaders/tri_color.vert.msl.h | 54 + .../gpu/shaders/tri_color.vert.sm50.dxbc.h | 178 - .../gpu/shaders/tri_color.vert.sm60.dxil.h | 515 --- .../render/gpu/shaders/tri_color.vert.spv.h | 165 +- .../src/render/gpu/shaders/tri_texture.vert | 20 - .../gpu/shaders/tri_texture.vert.dxil.h | 357 ++ .../gpu/shaders/tri_texture.vert.metal.h | 56 - .../render/gpu/shaders/tri_texture.vert.msl.h | 66 + .../gpu/shaders/tri_texture.vert.sm50.dxbc.h | 195 - .../gpu/shaders/tri_texture.vert.sm60.dxil.h | 558 --- .../render/gpu/shaders/tri_texture.vert.spv.h | 197 +- libs/SDL3/src/render/metal/SDL_render_metal.m | 157 +- .../src/render/metal/SDL_shaders_metal.metal | 33 +- .../src/render/metal/SDL_shaders_metal_ios.h | 3014 +++++++------ .../metal/SDL_shaders_metal_iphonesimulator.h | 3624 ++++++++------- .../render/metal/SDL_shaders_metal_macos.h | 2865 ++++++------ .../src/render/metal/SDL_shaders_metal_tvos.h | 3014 +++++++------ .../metal/SDL_shaders_metal_tvsimulator.h | 4012 +++++++++-------- libs/SDL3/src/render/ngage/SDL_render_ngage.c | 544 +++ .../src/render/ngage/SDL_render_ngage.cpp | 750 +++ .../src/render/ngage/SDL_render_ngage_c.h | 105 + .../src/render/ngage/SDL_render_ngage_c.hpp | 91 + libs/SDL3/src/render/ngage/SDL_render_ops.cpp | 152 + libs/SDL3/src/render/ngage/SDL_render_ops.hpp | 32 + libs/SDL3/src/render/opengl/SDL_render_gl.c | 80 +- libs/SDL3/src/render/opengl/SDL_shaders_gl.c | 151 +- libs/SDL3/src/render/opengl/SDL_shaders_gl.h | 2 + .../src/render/opengles2/SDL_render_gles2.c | 160 +- .../src/render/opengles2/SDL_shaders_gles2.c | 320 +- .../src/render/opengles2/SDL_shaders_gles2.h | 4 + libs/SDL3/src/render/ps2/SDL_render_ps2.c | 3 +- libs/SDL3/src/render/psp/SDL_render_psp.c | 56 +- libs/SDL3/src/render/software/SDL_draw.h | 187 +- libs/SDL3/src/render/software/SDL_drawline.c | 10 +- libs/SDL3/src/render/software/SDL_render_sw.c | 5 +- libs/SDL3/src/render/software/SDL_triangle.c | 71 +- libs/SDL3/src/render/software/SDL_triangle.h | 5 +- .../src/render/vitagxm/SDL_render_vita_gxm.c | 114 +- .../vitagxm/SDL_render_vita_gxm_types.h | 4 +- .../src/render/vulkan/SDL_render_vulkan.c | 148 +- .../vulkan/VULKAN_PixelShader_Advanced.h | 556 +-- .../render/vulkan/VULKAN_PixelShader_Colors.h | 64 +- .../vulkan/VULKAN_PixelShader_Common.hlsli | 22 +- .../vulkan/VULKAN_PixelShader_Textures.h | 82 +- .../src/render/vulkan/VULKAN_VertexShader.h | 12 +- libs/SDL3/src/stdlib/SDL_stdlib.c | 4 +- libs/SDL3/src/stdlib/SDL_string.c | 2 + libs/SDL3/src/stdlib/SDL_vacopy.h | 3 +- libs/SDL3/src/storage/SDL_storage.c | 3 + libs/SDL3/src/storage/SDL_sysstorage.h | 5 +- .../SDL3/src/storage/steam/SDL_steamstorage.c | 12 +- libs/SDL3/src/test/SDL_test_common.c | 51 + libs/SDL3/src/test/SDL_test_memory.c | 6 +- libs/SDL3/src/time/ngage/SDL_systime.cpp | 184 + libs/SDL3/src/timer/ngage/SDL_systimer.cpp | 48 + libs/SDL3/src/tray/unix/SDL_tray.c | 16 +- libs/SDL3/src/tray/windows/SDL_tray.c | 2 +- libs/SDL3/src/video/SDL_blit_0.c | 12 +- libs/SDL3/src/video/SDL_blit_1.c | 8 +- libs/SDL3/src/video/SDL_blit_A.c | 26 +- libs/SDL3/src/video/SDL_blit_N.c | 16 +- libs/SDL3/src/video/SDL_blit_auto.c | 664 +-- libs/SDL3/src/video/SDL_blit_auto.h | 3 +- libs/SDL3/src/video/SDL_blit_slow.c | 46 +- libs/SDL3/src/video/SDL_bmp.c | 12 +- libs/SDL3/src/video/SDL_egl.c | 16 +- libs/SDL3/src/video/SDL_pixels.c | 70 +- libs/SDL3/src/video/SDL_pixels_c.h | 2 +- libs/SDL3/src/video/SDL_stretch.c | 18 +- libs/SDL3/src/video/SDL_surface.c | 26 +- libs/SDL3/src/video/SDL_sysvideo.h | 17 +- libs/SDL3/src/video/SDL_video.c | 205 +- .../src/video/android/SDL_androidvulkan.c | 3 +- .../src/video/android/SDL_androidvulkan.h | 2 +- .../SDL3/src/video/cocoa/SDL_cocoaclipboard.m | 11 + libs/SDL3/src/video/cocoa/SDL_cocoakeyboard.m | 2 +- libs/SDL3/src/video/cocoa/SDL_cocoamouse.m | 10 +- libs/SDL3/src/video/cocoa/SDL_cocoavideo.m | 6 +- libs/SDL3/src/video/cocoa/SDL_cocoavulkan.h | 2 +- libs/SDL3/src/video/cocoa/SDL_cocoavulkan.m | 3 +- libs/SDL3/src/video/cocoa/SDL_cocoawindow.h | 1 - libs/SDL3/src/video/cocoa/SDL_cocoawindow.m | 117 +- .../video/emscripten/SDL_emscriptenevents.c | 157 +- .../video/emscripten/SDL_emscriptenevents.h | 2 + .../emscripten/SDL_emscriptenframebuffer.c | 4 +- .../video/emscripten/SDL_emscriptenmouse.c | 60 + .../video/emscripten/SDL_emscriptenopengles.c | 2 +- .../video/emscripten/SDL_emscriptenvideo.c | 195 +- .../video/emscripten/SDL_emscriptenvideo.h | 1 + libs/SDL3/src/video/haiku/SDL_bclipboard.cc | 2 +- libs/SDL3/src/video/haiku/SDL_bmessagebox.cc | 2 +- libs/SDL3/src/video/haiku/SDL_bmodes.cc | 2 +- libs/SDL3/src/video/haiku/SDL_bopengl.cc | 6 +- libs/SDL3/src/video/kmsdrm/SDL_kmsdrmmouse.c | 2 +- libs/SDL3/src/video/kmsdrm/SDL_kmsdrmvideo.c | 13 +- libs/SDL3/src/video/kmsdrm/SDL_kmsdrmvulkan.c | 3 +- libs/SDL3/src/video/kmsdrm/SDL_kmsdrmvulkan.h | 2 +- libs/SDL3/src/video/ngage/SDL_ngagevideo.c | 175 + libs/SDL3/src/video/ngage/SDL_ngagevideo.h | 39 + libs/SDL3/src/video/openvr/SDL_openvrvideo.c | 38 +- libs/SDL3/src/video/psp/SDL_pspvideo.c | 2 + libs/SDL3/src/video/riscos/SDL_riscosmodes.c | 2 +- libs/SDL3/src/video/sdlgenblit.pl | 102 +- libs/SDL3/src/video/stb_image.h | 27 +- .../src/video/uikit/SDL_uikitappdelegate.m | 2 +- .../src/video/uikit/SDL_uikitmessagebox.m | 33 +- libs/SDL3/src/video/uikit/SDL_uikitview.m | 2 +- libs/SDL3/src/video/uikit/SDL_uikitvulkan.h | 2 +- libs/SDL3/src/video/uikit/SDL_uikitvulkan.m | 3 +- libs/SDL3/src/video/vita/SDL_vitagl_pvr.c | 1 - libs/SDL3/src/video/vita/SDL_vitagles.c | 1 - libs/SDL3/src/video/vita/SDL_vitagles_c.h | 53 - libs/SDL3/src/video/vita/SDL_vitagles_pvr.c | 1 - libs/SDL3/src/video/vita/SDL_vitavideo.c | 10 +- libs/SDL3/src/video/vita/SDL_vitavideo.h | 19 +- .../src/video/vivante/SDL_vivantevulkan.c | 3 +- .../src/video/vivante/SDL_vivantevulkan.h | 2 +- .../src/video/wayland/SDL_waylandclipboard.c | 66 +- .../video/wayland/SDL_waylanddatamanager.c | 154 +- .../video/wayland/SDL_waylanddatamanager.h | 43 +- libs/SDL3/src/video/wayland/SDL_waylanddyn.h | 5 + .../src/video/wayland/SDL_waylandevents.c | 2361 +++++----- .../src/video/wayland/SDL_waylandevents_c.h | 232 +- .../src/video/wayland/SDL_waylandkeyboard.c | 275 +- .../src/video/wayland/SDL_waylandkeyboard.h | 8 +- .../SDL3/src/video/wayland/SDL_waylandmouse.c | 486 +- .../SDL3/src/video/wayland/SDL_waylandmouse.h | 3 +- libs/SDL3/src/video/wayland/SDL_waylandsym.h | 13 +- .../SDL3/src/video/wayland/SDL_waylandvideo.c | 88 +- .../SDL3/src/video/wayland/SDL_waylandvideo.h | 13 +- .../src/video/wayland/SDL_waylandvulkan.c | 2 +- .../src/video/wayland/SDL_waylandvulkan.h | 2 +- .../src/video/wayland/SDL_waylandwindow.c | 155 +- .../src/video/wayland/SDL_waylandwindow.h | 29 +- .../src/video/windows/SDL_surface_utils.c | 2 +- .../src/video/windows/SDL_windowsclipboard.c | 4 +- .../src/video/windows/SDL_windowsevents.c | 175 +- ...wsgameinput.c => SDL_windowsgameinput.cpp} | 125 +- .../src/video/windows/SDL_windowsgameinput.h | 9 + .../src/video/windows/SDL_windowskeyboard.c | 2 +- .../SDL3/src/video/windows/SDL_windowsmodes.c | 10 +- .../SDL3/src/video/windows/SDL_windowsmodes.h | 2 +- .../SDL3/src/video/windows/SDL_windowsmouse.c | 41 +- .../SDL3/src/video/windows/SDL_windowsvideo.c | 25 +- .../SDL3/src/video/windows/SDL_windowsvideo.h | 13 +- .../src/video/windows/SDL_windowsvulkan.c | 3 +- .../src/video/windows/SDL_windowsvulkan.h | 2 +- .../src/video/windows/SDL_windowswindow.c | 242 +- .../src/video/windows/SDL_windowswindow.h | 7 +- libs/SDL3/src/video/x11/SDL_x11dyn.c | 22 +- libs/SDL3/src/video/x11/SDL_x11dyn.h | 17 +- libs/SDL3/src/video/x11/SDL_x11events.c | 139 +- libs/SDL3/src/video/x11/SDL_x11keyboard.c | 6 +- libs/SDL3/src/video/x11/SDL_x11messagebox.c | 85 +- libs/SDL3/src/video/x11/SDL_x11mouse.c | 11 +- libs/SDL3/src/video/x11/SDL_x11opengl.c | 10 +- libs/SDL3/src/video/x11/SDL_x11pen.c | 22 +- libs/SDL3/src/video/x11/SDL_x11sym.h | 481 +- libs/SDL3/src/video/x11/SDL_x11video.c | 47 +- libs/SDL3/src/video/x11/SDL_x11vulkan.c | 3 +- libs/SDL3/src/video/x11/SDL_x11vulkan.h | 2 +- libs/SDL3/src/video/x11/SDL_x11window.c | 237 +- libs/SDL3/src/video/x11/SDL_x11window.h | 4 +- libs/SDL3/src/video/x11/SDL_x11xsync.c | 12 +- libs/SDL3/src/video/x11/SDL_x11xsync.h | 16 +- libs/SDL3/src/video/x11/SDL_x11xtest.c | 84 + libs/SDL3/src/video/x11/SDL_x11xtest.h | 31 + libs/SDL3/test/CMakeLists.txt | 40 +- .../cmake/SDLEntryTestActivity.java.cmake | 1 - libs/SDL3/test/build-shaders.sh | 24 + libs/SDL3/test/childprocess.c | 10 + libs/SDL3/test/gamepad_face_axby.bmp | Bin 0 -> 33030 bytes libs/SDL3/test/gamepad_face_axby.h | 2756 +++++++++++ libs/SDL3/test/gamepadutils.c | 636 ++- libs/SDL3/test/gamepadutils.h | 30 + libs/SDL3/test/msdf_font.bmp | Bin 0 -> 196662 bytes libs/SDL3/test/msdf_font.csv | 95 + libs/SDL3/test/testaudio.c | 11 +- libs/SDL3/test/testautomation_hints.c | 124 +- libs/SDL3/test/testautomation_joystick.c | 17 + libs/SDL3/test/testautomation_rect.c | 272 ++ libs/SDL3/test/testautomation_render.c | 361 ++ libs/SDL3/test/testautomation_surface.c | 41 +- libs/SDL3/test/testautomation_video.c | 31 + libs/SDL3/test/testcontroller.c | 365 +- libs/SDL3/test/testdialog.c | 8 +- libs/SDL3/test/testffmpeg_vulkan.c | 17 + libs/SDL3/test/testgeometry.c | 3 +- libs/SDL3/test/testgpurender_effects.c | 339 ++ .../testgpurender_effects_CRT.frag.dxil.h | 392 ++ .../test/testgpurender_effects_CRT.frag.hlsl | 56 + .../test/testgpurender_effects_CRT.frag.msl.h | 106 + .../test/testgpurender_effects_CRT.frag.spv.h | 189 + ...stgpurender_effects_grayscale.frag.dxil.h} | 377 +- .../testgpurender_effects_grayscale.frag.hlsl | 19 + ...testgpurender_effects_grayscale.frag.msl.h | 63 + ...testgpurender_effects_grayscale.frag.spv.h | 96 + libs/SDL3/test/testgpurender_msdf.c | 318 ++ libs/SDL3/test/testgpurender_msdf.frag.dxil.h | 399 ++ libs/SDL3/test/testgpurender_msdf.frag.hlsl | 37 + libs/SDL3/test/testgpurender_msdf.frag.msl.h | 97 + libs/SDL3/test/testgpurender_msdf.frag.spv.h | 162 + libs/SDL3/test/testhaptic.c | 2 +- libs/SDL3/test/testime.c | 13 +- libs/SDL3/test/testmessage.c | 82 +- libs/SDL3/test/testmodal.c | 2 +- libs/SDL3/test/testmouse.c | 6 + libs/SDL3/test/testnative.c | 2 +- libs/SDL3/test/testpopup.c | 48 +- libs/SDL3/test/testprocess.c | 171 +- libs/SDL3/test/testrendercopyex.c | 4 +- libs/SDL3/test/testrendertarget.c | 6 +- libs/SDL3/test/testscale.c | 5 +- libs/SDL3/test/testsprite.c | 10 +- libs/SDL3/test/testutils.c | 10 +- libs/SDL3/test/testutils.h | 3 +- libs/SDL3/test/testviewport.c | 4 +- .../wayland-protocols/pointer-warp-v1.xml | 72 + libs/SDL3/wayland-protocols/xdg-shell.xml | 77 +- 537 files changed, 50325 insertions(+), 23727 deletions(-) create mode 100644 libs/SDL3/.github/actions/setup-ngage-sdk/action.yml create mode 100644 libs/SDL3/VisualC/examples/asyncio/01-load-bitmaps/01-load-bitmaps.vcxproj create mode 100644 libs/SDL3/VisualC/examples/audio/04-multiple-streams/04-multiple-streams.vcxproj create mode 100644 libs/SDL3/VisualC/examples/demo/04-bytepusher/04-bytepusher.vcxproj create mode 100644 libs/SDL3/VisualC/examples/input/01-joystick-polling/01-joystick-polling.vcxproj create mode 100644 libs/SDL3/VisualC/examples/input/02-joystick-events/02-joystick-events.vcxproj create mode 100644 libs/SDL3/VisualC/examples/renderer/19-affine-textures/19-affine-textures.vcxproj create mode 100644 libs/SDL3/cmake/PreseedNokiaNGageCache.cmake rename libs/SDL3/cmake/{sdltargets.cmake => sdlcommands.cmake} (100%) create mode 100644 libs/SDL3/examples/audio/05-planar-data/README.txt create mode 100644 libs/SDL3/examples/audio/05-planar-data/onmouseover.webp create mode 100644 libs/SDL3/examples/audio/05-planar-data/planar-data.c create mode 100644 libs/SDL3/examples/audio/05-planar-data/thumbnail.png create mode 100644 libs/SDL3/examples/renderer/19-affine-textures/README.txt create mode 100644 libs/SDL3/examples/renderer/19-affine-textures/affine-textures.c create mode 100644 libs/SDL3/examples/renderer/19-affine-textures/onmouseover.webp create mode 100644 libs/SDL3/examples/renderer/19-affine-textures/thumbnail.png create mode 100644 libs/SDL3/src/audio/ngage/SDL_ngageaudio.c create mode 100644 libs/SDL3/src/audio/ngage/SDL_ngageaudio.cpp rename libs/SDL3/src/{video/vita/SDL_vitagl_pvr_c.h => audio/ngage/SDL_ngageaudio.h} (71%) create mode 100644 libs/SDL3/src/audio/ngage/SDL_ngageaudio.hpp create mode 100644 libs/SDL3/src/core/linux/SDL_progressbar.c rename libs/SDL3/src/{video/vita/SDL_vitagles_pvr_c.h => core/linux/SDL_progressbar.h} (66%) create mode 100644 libs/SDL3/src/core/ngage/SDL_ngage.cpp create mode 100644 libs/SDL3/src/core/ngage/SDL_ngage.h rename libs/SDL3/src/core/windows/{SDL_gameinput.c => SDL_gameinput.cpp} (85%) create mode 100644 libs/SDL3/src/filesystem/ngage/SDL_sysfilesystem.c create mode 100644 libs/SDL3/src/filesystem/ngage/SDL_sysfilesystem.cpp create mode 100644 libs/SDL3/src/haptic/hidapi/SDL_hidapihaptic.c create mode 100644 libs/SDL3/src/haptic/hidapi/SDL_hidapihaptic.h create mode 100644 libs/SDL3/src/haptic/hidapi/SDL_hidapihaptic_c.h create mode 100644 libs/SDL3/src/haptic/hidapi/SDL_hidapihaptic_lg4ff.c rename libs/SDL3/src/joystick/gdk/{SDL_gameinputjoystick.c => SDL_gameinputjoystick.cpp} (88%) create mode 100644 libs/SDL3/src/joystick/hidapi/SDL_hidapi_8bitdo.c create mode 100644 libs/SDL3/src/joystick/hidapi/SDL_hidapi_flydigi.c create mode 100644 libs/SDL3/src/joystick/hidapi/SDL_hidapi_gip.c create mode 100644 libs/SDL3/src/joystick/hidapi/SDL_hidapi_lg4ff.c create mode 100644 libs/SDL3/src/locale/ngage/SDL_syslocale.cpp create mode 100644 libs/SDL3/src/main/ngage/SDL_sysmain_callbacks.c create mode 100644 libs/SDL3/src/main/ngage/SDL_sysmain_main.cpp create mode 100644 libs/SDL3/src/main/ngage/SDL_sysmain_main.hpp delete mode 100644 libs/SDL3/src/render/gpu/shaders/color.frag create mode 100644 libs/SDL3/src/render/gpu/shaders/color.frag.dxil.h delete mode 100644 libs/SDL3/src/render/gpu/shaders/color.frag.metal.h create mode 100644 libs/SDL3/src/render/gpu/shaders/color.frag.msl.h delete mode 100644 libs/SDL3/src/render/gpu/shaders/color.frag.sm50.dxbc.h delete mode 100644 libs/SDL3/src/render/gpu/shaders/color.frag.sm60.dxil.h delete mode 100644 libs/SDL3/src/render/gpu/shaders/dxbc50.h create mode 100644 libs/SDL3/src/render/gpu/shaders/dxil.h delete mode 100644 libs/SDL3/src/render/gpu/shaders/dxil60.h delete mode 100644 libs/SDL3/src/render/gpu/shaders/linepoint.vert create mode 100644 libs/SDL3/src/render/gpu/shaders/linepoint.vert.dxil.h delete mode 100644 libs/SDL3/src/render/gpu/shaders/linepoint.vert.metal.h create mode 100644 libs/SDL3/src/render/gpu/shaders/linepoint.vert.msl.h delete mode 100644 libs/SDL3/src/render/gpu/shaders/linepoint.vert.sm50.dxbc.h delete mode 100644 libs/SDL3/src/render/gpu/shaders/linepoint.vert.sm60.dxil.h delete mode 100644 libs/SDL3/src/render/gpu/shaders/metal.h create mode 100644 libs/SDL3/src/render/gpu/shaders/msl.h create mode 100644 libs/SDL3/src/render/gpu/shaders/texture_pixelart.frag.hlsli delete mode 100644 libs/SDL3/src/render/gpu/shaders/texture_rgb.frag create mode 100644 libs/SDL3/src/render/gpu/shaders/texture_rgb.frag.dxil.h delete mode 100644 libs/SDL3/src/render/gpu/shaders/texture_rgb.frag.metal.h create mode 100644 libs/SDL3/src/render/gpu/shaders/texture_rgb.frag.msl.h delete mode 100644 libs/SDL3/src/render/gpu/shaders/texture_rgb.frag.sm50.dxbc.h delete mode 100644 libs/SDL3/src/render/gpu/shaders/texture_rgb.frag.sm60.dxil.h create mode 100644 libs/SDL3/src/render/gpu/shaders/texture_rgb_pixelart.frag.dxil.h create mode 100644 libs/SDL3/src/render/gpu/shaders/texture_rgb_pixelart.frag.msl.h create mode 100644 libs/SDL3/src/render/gpu/shaders/texture_rgb_pixelart.frag.spv.h delete mode 100644 libs/SDL3/src/render/gpu/shaders/texture_rgba.frag create mode 100644 libs/SDL3/src/render/gpu/shaders/texture_rgba.frag.dxil.h delete mode 100644 libs/SDL3/src/render/gpu/shaders/texture_rgba.frag.metal.h create mode 100644 libs/SDL3/src/render/gpu/shaders/texture_rgba.frag.msl.h delete mode 100644 libs/SDL3/src/render/gpu/shaders/texture_rgba.frag.sm50.dxbc.h create mode 100644 libs/SDL3/src/render/gpu/shaders/texture_rgba_pixelart.frag.dxil.h create mode 100644 libs/SDL3/src/render/gpu/shaders/texture_rgba_pixelart.frag.msl.h create mode 100644 libs/SDL3/src/render/gpu/shaders/texture_rgba_pixelart.frag.spv.h delete mode 100644 libs/SDL3/src/render/gpu/shaders/tri_color.vert create mode 100644 libs/SDL3/src/render/gpu/shaders/tri_color.vert.dxil.h delete mode 100644 libs/SDL3/src/render/gpu/shaders/tri_color.vert.metal.h create mode 100644 libs/SDL3/src/render/gpu/shaders/tri_color.vert.msl.h delete mode 100644 libs/SDL3/src/render/gpu/shaders/tri_color.vert.sm50.dxbc.h delete mode 100644 libs/SDL3/src/render/gpu/shaders/tri_color.vert.sm60.dxil.h delete mode 100644 libs/SDL3/src/render/gpu/shaders/tri_texture.vert create mode 100644 libs/SDL3/src/render/gpu/shaders/tri_texture.vert.dxil.h delete mode 100644 libs/SDL3/src/render/gpu/shaders/tri_texture.vert.metal.h create mode 100644 libs/SDL3/src/render/gpu/shaders/tri_texture.vert.msl.h delete mode 100644 libs/SDL3/src/render/gpu/shaders/tri_texture.vert.sm50.dxbc.h delete mode 100644 libs/SDL3/src/render/gpu/shaders/tri_texture.vert.sm60.dxil.h create mode 100644 libs/SDL3/src/render/ngage/SDL_render_ngage.c create mode 100644 libs/SDL3/src/render/ngage/SDL_render_ngage.cpp create mode 100644 libs/SDL3/src/render/ngage/SDL_render_ngage_c.h create mode 100644 libs/SDL3/src/render/ngage/SDL_render_ngage_c.hpp create mode 100644 libs/SDL3/src/render/ngage/SDL_render_ops.cpp create mode 100644 libs/SDL3/src/render/ngage/SDL_render_ops.hpp create mode 100644 libs/SDL3/src/time/ngage/SDL_systime.cpp create mode 100644 libs/SDL3/src/timer/ngage/SDL_systimer.cpp create mode 100644 libs/SDL3/src/video/ngage/SDL_ngagevideo.c create mode 100644 libs/SDL3/src/video/ngage/SDL_ngagevideo.h delete mode 100644 libs/SDL3/src/video/vita/SDL_vitagles_c.h rename libs/SDL3/src/video/windows/{SDL_windowsgameinput.c => SDL_windowsgameinput.cpp} (80%) create mode 100644 libs/SDL3/src/video/x11/SDL_x11xtest.c create mode 100644 libs/SDL3/src/video/x11/SDL_x11xtest.h create mode 100644 libs/SDL3/test/build-shaders.sh create mode 100644 libs/SDL3/test/gamepad_face_axby.bmp create mode 100644 libs/SDL3/test/gamepad_face_axby.h create mode 100644 libs/SDL3/test/msdf_font.bmp create mode 100644 libs/SDL3/test/msdf_font.csv create mode 100644 libs/SDL3/test/testgpurender_effects.c create mode 100644 libs/SDL3/test/testgpurender_effects_CRT.frag.dxil.h create mode 100644 libs/SDL3/test/testgpurender_effects_CRT.frag.hlsl create mode 100644 libs/SDL3/test/testgpurender_effects_CRT.frag.msl.h create mode 100644 libs/SDL3/test/testgpurender_effects_CRT.frag.spv.h rename libs/SDL3/{src/render/gpu/shaders/texture_rgba.frag.sm60.dxil.h => test/testgpurender_effects_grayscale.frag.dxil.h} (51%) create mode 100644 libs/SDL3/test/testgpurender_effects_grayscale.frag.hlsl create mode 100644 libs/SDL3/test/testgpurender_effects_grayscale.frag.msl.h create mode 100644 libs/SDL3/test/testgpurender_effects_grayscale.frag.spv.h create mode 100644 libs/SDL3/test/testgpurender_msdf.c create mode 100644 libs/SDL3/test/testgpurender_msdf.frag.dxil.h create mode 100644 libs/SDL3/test/testgpurender_msdf.frag.hlsl create mode 100644 libs/SDL3/test/testgpurender_msdf.frag.msl.h create mode 100644 libs/SDL3/test/testgpurender_msdf.frag.spv.h create mode 100644 libs/SDL3/wayland-protocols/pointer-warp-v1.xml diff --git a/libs/SDL3/.github/actions/setup-ngage-sdk/action.yml b/libs/SDL3/.github/actions/setup-ngage-sdk/action.yml new file mode 100644 index 00000000..fa83418b --- /dev/null +++ b/libs/SDL3/.github/actions/setup-ngage-sdk/action.yml @@ -0,0 +1,102 @@ +name: 'Setup Nonka N-Gage SDK' +description: 'Download and setup Nokia N-Gage SDK' +inputs: + path: + description: 'Installation path' + default: 'default' +runs: + using: 'composite' + steps: + - uses: actions/setup-python@v5 + with: + python-version: '3.x' + - name: 'Verify platform' + id: calc + shell: sh + run: | + case "${{ runner.os }}-${{ runner.arch }}" in + "Windows-X86" | "Windows-X64") + echo "ok!" + echo "cache-key=ngage-sdk-windows" >> ${GITHUB_OUTPUT} + default_install_path="C:/ngagesdk" + ;; + *) + echo "Unsupported ${{ runner.os }}-${{ runner.arch }}" + exit 1; + ;; + esac + install_path="${{ inputs.path }}" + if [ "x$install_path" = "xdefault" ]; then + install_path="$default_install_path" + fi + echo "install-path=$install_path" >> ${GITHUB_OUTPUT} + + toolchain_repo="https://github.com/ngagesdk/ngage-toolchain" + toolchain_branch="main" + echo "toolchain-repo=${toolchain_repo}" >> ${GITHUB_OUTPUT} + echo "toolchain-branch=${toolchain_branch}" >> ${GITHUB_OUTPUT} + + sdk_repo="https://github.com/ngagesdk/sdk" + sdk_branch="main" + echo "sdk-repo=${sdk_repo}" >> ${GITHUB_OUTPUT} + echo "sdk-branch=${sdk_branch}" >> ${GITHUB_OUTPUT} + + tools_repo="https://github.com/ngagesdk/tools" + tools_branch="main" + echo "tools-repo=${tools_repo}" >> ${GITHUB_OUTPUT} + echo "tools-branch=${tools_branch}" >> ${GITHUB_OUTPUT} + + extras_repo="https://github.com/ngagesdk/extras" + extras_branch="main" + echo "extras-repo=${extras_repo}" >> ${GITHUB_OUTPUT} + echo "extras-branch=${extras_branch}" >> ${GITHUB_OUTPUT} +# - name: 'Restore cached ${{ steps.calc.outputs.archive }}' +# id: cache-restore +# uses: actions/cache/restore@v4 +# with: +# path: '${{ runner.temp }}' +# key: ${{ steps.calc.outputs.cache-key }} + - name: 'Download N-Gage SDK' +# if: ${{ !steps.cache-restore.outputs.cache-hit || steps.cache-restore.outputs.cache-hit == 'false' }} + shell: pwsh + run: | + + Invoke-WebRequest "${{ steps.calc.outputs.toolchain-repo }}/archive/refs/heads/${{ steps.calc.outputs.toolchain-branch }}.zip" -OutFile "${{ runner.temp }}/ngage-toolchain.zip" + Invoke-WebRequest "${{ steps.calc.outputs.sdk-repo }}/archive/refs/heads/${{ steps.calc.outputs.sdk-branch }}.zip" -OutFile "${{ runner.temp }}/sdk.zip" + Invoke-WebRequest "${{ steps.calc.outputs.tools-repo }}/archive/refs/heads/${{ steps.calc.outputs.tools-branch }}.zip" -OutFile "${{ runner.temp }}/tools.zip" + Invoke-WebRequest "${{ steps.calc.outputs.extras-repo }}/archive/refs/heads/${{ steps.calc.outputs.extras-branch }}.zip" -OutFile "${{ runner.temp }}/extras.zip" + +# - name: 'Cache ${{ steps.calc.outputs.archive }}' +# if: ${{ !steps.cache-restore.outputs.cache-hit || steps.cache-restore.outputs.cache-hit == 'false' }} +# uses: actions/cache/save@v4 +# with: +# path: | +# ${{ runner.temp }}/apps.zip +# ${{ runner.temp }}/sdk.zip +# ${{ runner.temp }}/tools.zip +# key: ${{ steps.calc.outputs.cache-key }} + - name: 'Extract N-Gage SDK' + shell: pwsh + run: | + New-Item -ItemType Directory -Path "${{ steps.calc.outputs.install-path }}" -Force + + New-Item -ItemType Directory -Path "${{ runner.temp }}/ngage-toolchain-temp" -Force + 7z "-o${{ runner.temp }}/ngage-toolchain-temp" x "${{ runner.temp }}/ngage-toolchain.zip" + Move-Item -Path "${{ runner.temp }}/ngage-toolchain-temp/ngage-toolchain-${{ steps.calc.outputs.toolchain-branch }}/*" -Destination "${{ steps.calc.outputs.install-path }}" + + 7z "-o${{ steps.calc.outputs.install-path }}/sdk" x "${{ runner.temp }}/sdk.zip" + Move-Item -Path "${{ steps.calc.outputs.install-path }}/sdk/sdk-${{ steps.calc.outputs.sdk-branch }}" -Destination "${{ steps.calc.outputs.install-path }}/sdk/sdk" + + 7z "-o${{ steps.calc.outputs.install-path }}/sdk" x "${{ runner.temp }}/tools.zip" + Move-Item -Path "${{ steps.calc.outputs.install-path }}/sdk/tools-${{ steps.calc.outputs.tools-branch }}" -Destination "${{ steps.calc.outputs.install-path }}/sdk/tools" + + 7z "-o${{ steps.calc.outputs.install-path }}/sdk" x "${{ runner.temp }}/extras.zip" + Move-Item -Path "${{ steps.calc.outputs.install-path }}/sdk/extras-${{ steps.calc.outputs.extras-branch }}" -Destination "${{ steps.calc.outputs.install-path }}/sdk/extras" + - name: 'Set output variables' + id: final + shell: sh + run: | + echo "${{ steps.calc.outputs.install-path }}/sdk/sdk/6.1/Shared/EPOC32/gcc/bin" >> $GITHUB_PATH + echo "${{ steps.calc.outputs.install-path }}/sdk/sdk/6.1/Shared/EPOC32/ngagesdk/bin" >> $GITHUB_PATH + echo "NGAGESDK=${{ steps.calc.outputs.install-path }}" >> $GITHUB_ENV + echo "CMAKE_TOOLCHAIN_FILE=${{ steps.calc.outputs.install-path }}/cmake/ngage-toolchain.cmake" >> $GITHUB_ENV diff --git a/libs/SDL3/.github/workflows/create-test-plan.py b/libs/SDL3/.github/workflows/create-test-plan.py index a137889c..219a4de1 100644 --- a/libs/SDL3/.github/workflows/create-test-plan.py +++ b/libs/SDL3/.github/workflows/create-test-plan.py @@ -54,6 +54,7 @@ class SdlPlatform(Enum): Riscos = "riscos" FreeBSD = "freebsd" NetBSD = "netbsd" + NGage = "ngage" class Msys2Platform(Enum): @@ -113,7 +114,8 @@ JOB_SPECS = { "msvc-gdk-x64": JobSpec(name="GDK (MSVC, x64)", os=JobOs.WindowsLatest, platform=SdlPlatform.Msvc, artifact="SDL-VC-GDK", msvc_arch=MsvcArch.X64, msvc_project="VisualC-GDK/SDL.sln", gdk=True, no_cmake=True, ), "ubuntu-22.04": JobSpec(name="Ubuntu 22.04", os=JobOs.Ubuntu22_04, platform=SdlPlatform.Linux, artifact="SDL-ubuntu22.04", ), "ubuntu-24.04-arm64": JobSpec(name="Ubuntu 24.04 (ARM64)", os=JobOs.Ubuntu24_04_arm, platform=SdlPlatform.Linux, artifact="SDL-ubuntu24.04-arm64", ), - "steamrt-sniper": JobSpec(name="Steam Linux Runtime (Sniper)", os=JobOs.UbuntuLatest, platform=SdlPlatform.Linux, artifact="SDL-slrsniper", container="registry.gitlab.steamos.cloud/steamrt/sniper/sdk:beta", ), + "steamrt3": JobSpec(name="Steam Linux Runtime 3.0 (x86_64)", os=JobOs.UbuntuLatest, platform=SdlPlatform.Linux, artifact="SDL-steamrt3", container="registry.gitlab.steamos.cloud/steamrt/sniper/sdk:latest", ), + "steamrt3-arm64": JobSpec(name="Steam Linux Runtime 3.0 (arm64)", os=JobOs.Ubuntu24_04_arm, platform=SdlPlatform.Linux, artifact="SDL-steamrt3-arm64", container="registry.gitlab.steamos.cloud/steamrt/sniper/sdk/arm64:latest", ), "ubuntu-intel-icx": JobSpec(name="Ubuntu 22.04 (Intel oneAPI)", os=JobOs.Ubuntu22_04, platform=SdlPlatform.Linux, artifact="SDL-ubuntu22.04-oneapi", intel=IntelCompiler.Icx, ), "ubuntu-intel-icc": JobSpec(name="Ubuntu 22.04 (Intel Compiler)", os=JobOs.Ubuntu22_04, platform=SdlPlatform.Linux, artifact="SDL-ubuntu22.04-icc", intel=IntelCompiler.Icc, ), "macos-framework-x64": JobSpec(name="MacOS (Framework) (x64)", os=JobOs.Macos13, platform=SdlPlatform.MacOS, artifact="SDL-macos-framework", apple_framework=True, apple_archs={AppleArch.Aarch64, AppleArch.X86_64, }, xcode=True, ), @@ -138,11 +140,12 @@ JOB_SPECS = { "riscos": JobSpec(name="RISC OS", os=JobOs.UbuntuLatest, platform=SdlPlatform.Riscos, artifact="SDL-riscos", container="riscosdotinfo/riscos-gccsdk-4.7:latest", ), "netbsd": JobSpec(name="NetBSD", os=JobOs.UbuntuLatest, platform=SdlPlatform.NetBSD, artifact="SDL-netbsd-x64", ), "freebsd": JobSpec(name="FreeBSD", os=JobOs.UbuntuLatest, platform=SdlPlatform.FreeBSD, artifact="SDL-freebsd-x64", ), + "ngage": JobSpec(name="N-Gage", os=JobOs.WindowsLatest, platform=SdlPlatform.NGage, artifact="SDL-ngage", ), } class StaticLibType(Enum): - MSVC = "SDL3-static.lib" + STATIC_LIB = "SDL3-static.lib" A = "libSDL3.a" @@ -222,6 +225,7 @@ class JobDetails: check_sources: bool = False setup_python: bool = False pypi_packages: list[str] = dataclasses.field(default_factory=list) + setup_gage_sdk_path: str = "" def to_workflow(self, enable_artifacts: bool) -> dict[str, str|bool]: data = { @@ -289,6 +293,7 @@ class JobDetails: "check-sources": self.check_sources, "setup-python": self.setup_python, "pypi-packages": my_shlex_join(self.pypi_packages), + "setup-ngage-sdk-path": self.setup_gage_sdk_path, } return {k: v for k, v in data.items() if v != ""} @@ -364,7 +369,7 @@ def spec_to_job(spec: JobSpec, key: str, trackmem_symbol_names: bool) -> JobDeta job.msvc_project_flags.append("-p:TreatWarningsAsError=true") job.test_pkg_config = False job.shared_lib = SharedLibType.WIN32 - job.static_lib = StaticLibType.MSVC + job.static_lib = StaticLibType.STATIC_LIB job.cmake_arguments.extend(( "-DCMAKE_MSVC_DEBUG_INFORMATION_FORMAT=ProgramDatabase", "-DCMAKE_EXE_LINKER_FLAGS=-DEBUG", @@ -381,9 +386,11 @@ def spec_to_job(spec: JobSpec, key: str, trackmem_symbol_names: bool) -> JobDeta match spec.msvc_arch: case MsvcArch.X86: job.cflags.append("/clang:-m32") + job.cxxflags.append("/clang:-m32") job.ldflags.append("/MACHINE:X86") case MsvcArch.X64: job.cflags.append("/clang:-m64") + job.cxxflags.append("/clang:-m64") job.ldflags.append("/MACHINE:X64") case _: raise ValueError(f"Unsupported clang-cl architecture (arch={spec.msvc_arch})") @@ -737,6 +744,19 @@ def spec_to_job(spec: JobSpec, key: str, trackmem_symbol_names: bool) -> JobDeta job.cpactions_arch = "x86-64" job.cpactions_setup_cmd = "export PATH=\"/usr/pkg/sbin:/usr/pkg/bin:/sbin:$PATH\"; export PKG_CONFIG_PATH=\"/usr/pkg/lib/pkgconfig\";export PKG_PATH=\"https://cdn.netBSD.org/pub/pkgsrc/packages/NetBSD/$(uname -p)/$(uname -r|cut -f \"1 2\" -d.)/All/\";echo \"PKG_PATH=$PKG_PATH\";echo \"uname -a -> \"$(uname -a)\"\";sudo -E sysctl -w security.pax.aslr.enabled=0;sudo -E sysctl -w security.pax.aslr.global=0;sudo -E pkgin clean;sudo -E pkgin update" job.cpactions_install_cmd = "sudo -E pkgin -y install cmake dbus pkgconf ninja-build pulseaudio libxkbcommon wayland wayland-protocols libinotify libusb1" + case SdlPlatform.NGage: + build_parallel = False + job.cmake_build_type = "Release" + job.setup_ninja = True + job.static_lib = StaticLibType.STATIC_LIB + job.shared_lib = None + job.clang_tidy = False + job.werror = False # FIXME: enable SDL_WERROR + job.shared = False + job.run_tests = False + job.setup_gage_sdk_path = "C:/ngagesdk" + job.cmake_toolchain_file = "C:/ngagesdk/cmake/ngage-toolchain.cmake" + job.test_pkg_config = False case _: raise ValueError(f"Unsupported platform={spec.platform}") diff --git a/libs/SDL3/.github/workflows/generic.yml b/libs/SDL3/.github/workflows/generic.yml index 9776431e..083859b3 100644 --- a/libs/SDL3/.github/workflows/generic.yml +++ b/libs/SDL3/.github/workflows/generic.yml @@ -93,6 +93,11 @@ jobs: with: arch: ${{ matrix.platform.msvc-vcvars-arch }} sdk: ${{ matrix.platform.msvc-vcvars-sdk }} + - name: 'Set up Nokia N-Gage SDK' + uses: ./.github/actions/setup-ngage-sdk + if: ${{ matrix.platform.setup-ngage-sdk-path != '' }} + with: + path: '${{ matrix.platform.setup-ngage-sdk-path }}' - name: 'Set up Windows GDK Desktop' uses: ./.github/actions/setup-gdk-desktop if: ${{ matrix.platform.setup-gdk-folder != '' }} diff --git a/libs/SDL3/.github/workflows/release.yml b/libs/SDL3/.github/workflows/release.yml index 4fbd5837..b0215a52 100644 --- a/libs/SDL3/.github/workflows/release.yml +++ b/libs/SDL3/.github/workflows/release.yml @@ -532,6 +532,7 @@ jobs: with: sparse-checkout: 'build-scripts/build-release.py' - name: 'Setup Android NDK' + id: setup-ndk uses: nttld/setup-ndk@v1 with: local-cache: true @@ -561,6 +562,8 @@ jobs: run: | python build-scripts/build-release.py \ --actions android \ + --android-api 23 \ + --android-ndk-home "${{ steps.setup-ndk.outputs.ndk-path }}" \ --commit ${{ inputs.commit }} \ --root "${{ steps.tar.outputs.path }}" \ --github \ diff --git a/libs/SDL3/.wikiheaders-options b/libs/SDL3/.wikiheaders-options index 9c2b5bb9..38a2f273 100644 --- a/libs/SDL3/.wikiheaders-options +++ b/libs/SDL3/.wikiheaders-options @@ -9,6 +9,7 @@ versionfname = include/SDL3/SDL_version.h versionmajorregex = \A\#define\s+SDL_MAJOR_VERSION\s+(\d+)\Z versionminorregex = \A\#define\s+SDL_MINOR_VERSION\s+(\d+)\Z versionmicroregex = \A\#define\s+SDL_MICRO_VERSION\s+(\d+)\Z +apipropertyregex = \A\s*\#\s*define\s+SDL_PROP_ selectheaderregex = \ASDL.*?\.h\Z projecturl = https://libsdl.org/ wikiurl = https://wiki.libsdl.org @@ -25,7 +26,7 @@ manpagesymbolfilterregex = \A[US]int\d+\Z headercategoryeval = s/\ASDL_test_?.*?\.h\Z//; s/\ASDL_?(.*?)\.h\Z/$1/; ucfirst(); quickrefenabled = 1 -quickrefcategoryorder = Init,Hints,Error,Version,Properties,Log,Video,Events,Keyboard,Mouse,Touch,Gamepad,Joystick,Haptic,Audio,Time,Timer,Render,SharedObject,Thread,Mutex,Atomic,Filesystem,IOStream,AsyncIO,Storage,Pixels,Surface,Blendmode,Rect,Camera,Clipboard,Dialog,GPU,Messagebox,Vulkan,Metal,Platform,Power,Sensor,Process,Bits,Endian,Assert,CPUInfo,Intrinsics,Locale,System,Misc,GUID,Main,Stdinc +quickrefcategoryorder = Init,Hints,Error,Version,Properties,Log,Video,Events,Keyboard,Mouse,Touch,Gamepad,Joystick,Haptic,Audio,Time,Timer,Render,SharedObject,Thread,Mutex,Atomic,Filesystem,IOStream,AsyncIO,Storage,Pixels,Surface,Blendmode,Rect,Camera,Clipboard,Dialog,Tray,Messagebox,GPU,Vulkan,Metal,Platform,Power,Sensor,Process,Bits,Endian,Assert,CPUInfo,Intrinsics,Locale,System,Misc,GUID,Main,Stdinc quickreftitle = SDL3 API Quick Reference quickrefurl = https://libsdl.org/ quickrefdesc = The latest version of this document can be found at https://wiki.libsdl.org/SDL3/QuickReference diff --git a/libs/SDL3/Android.mk b/libs/SDL3/Android.mk index b5fe8955..3ecc631d 100644 --- a/libs/SDL3/Android.mk +++ b/libs/SDL3/Android.mk @@ -42,6 +42,7 @@ LOCAL_SRC_FILES := \ $(wildcard $(LOCAL_PATH)/src/haptic/*.c) \ $(wildcard $(LOCAL_PATH)/src/haptic/android/*.c) \ $(wildcard $(LOCAL_PATH)/src/haptic/dummy/*.c) \ + $(wildcard $(LOCAL_PATH)/src/haptic/hidapi/*.c) \ $(wildcard $(LOCAL_PATH)/src/hidapi/*.c) \ $(wildcard $(LOCAL_PATH)/src/hidapi/android/*.cpp) \ $(wildcard $(LOCAL_PATH)/src/joystick/*.c) \ diff --git a/libs/SDL3/CMakeLists.txt b/libs/SDL3/CMakeLists.txt index c10eb8cf..fd62e985 100644 --- a/libs/SDL3/CMakeLists.txt +++ b/libs/SDL3/CMakeLists.txt @@ -5,7 +5,7 @@ if(NOT DEFINED CMAKE_BUILD_TYPE) endif() # See docs/release_checklist.md -project(SDL3 LANGUAGES C VERSION "3.2.10") +project(SDL3 LANGUAGES C VERSION "3.3.0") if(CMAKE_SOURCE_DIR STREQUAL PROJECT_SOURCE_DIR) set(SDL3_MAINPROJECT ON) @@ -67,15 +67,16 @@ find_package(PkgConfig) list(APPEND CMAKE_MODULE_PATH "${SDL3_SOURCE_DIR}/cmake") include("${SDL3_SOURCE_DIR}/cmake/macros.cmake") include("${SDL3_SOURCE_DIR}/cmake/sdlchecks.cmake") +include("${SDL3_SOURCE_DIR}/cmake/sdlcommands.cmake") include("${SDL3_SOURCE_DIR}/cmake/sdlcompilers.cmake") include("${SDL3_SOURCE_DIR}/cmake/sdlcpu.cmake") include("${SDL3_SOURCE_DIR}/cmake/sdlmanpages.cmake") include("${SDL3_SOURCE_DIR}/cmake/sdlplatform.cmake") -include("${SDL3_SOURCE_DIR}/cmake/sdltargets.cmake") include("${SDL3_SOURCE_DIR}/cmake/GetGitRevisionDescription.cmake") include("${SDL3_SOURCE_DIR}/cmake/3rdparty.cmake") include("${SDL3_SOURCE_DIR}/cmake/PreseedMSVCCache.cmake") include("${SDL3_SOURCE_DIR}/cmake/PreseedEmscriptenCache.cmake") +include("${SDL3_SOURCE_DIR}/cmake/PreseedNokiaNGageCache.cmake") SDL_DetectCompiler() SDL_DetectTargetCPUArchitectures(SDL_CPUS) @@ -155,7 +156,7 @@ endif() # The hidraw support doesn't catch Xbox, PS4 and Nintendo controllers, # so we'll just use libusb when it's available. libusb does not support iOS, # so we default to yes on iOS. -if(IOS OR TVOS OR VISIONOS OR WATCHOS OR ANDROID) +if(IOS OR TVOS OR VISIONOS OR WATCHOS OR ANDROID OR NGAGE) set(SDL_HIDAPI_LIBUSB_AVAILABLE FALSE) else() set(SDL_HIDAPI_LIBUSB_AVAILABLE TRUE) @@ -219,7 +220,7 @@ if(EMSCRIPTEN) set(SDL_SHARED_AVAILABLE OFF) endif() -if(VITA OR PSP OR PS2 OR N3DS OR RISCOS) +if(VITA OR PSP OR PS2 OR N3DS OR RISCOS OR NGAGE) set(SDL_SHARED_AVAILABLE OFF) endif() @@ -357,6 +358,7 @@ dep_option(SDL_X11_XRANDR "Enable Xrandr support" "${SDL_X11_XRANDR_DEF dep_option(SDL_X11_XSCRNSAVER "Enable Xscrnsaver support" ON SDL_X11 OFF) dep_option(SDL_X11_XSHAPE "Enable XShape support" ON SDL_X11 OFF) dep_option(SDL_X11_XSYNC "Enable Xsync support" ON SDL_X11 OFF) +dep_option(SDL_X11_XTEST "Enable XTest support" ON SDL_X11 OFF) dep_option(SDL_WAYLAND "Use Wayland video driver" ${UNIX_SYS} "SDL_VIDEO" OFF) dep_option(SDL_WAYLAND_SHARED "Dynamically load Wayland support" ON "SDL_WAYLAND;SDL_DEPS_SHARED" OFF) dep_option(SDL_WAYLAND_LIBDECOR "Use client-side window decorations on Wayland" ON "SDL_WAYLAND" OFF) @@ -413,6 +415,24 @@ if(VITA) set_option(VIDEO_VITA_PVR "Build with PSVita PVR gles/gles2 support" OFF) endif() +if (NGAGE) + set(SDL_GPU OFF) + set(SDL_CAMERA OFF) + set(SDL_JOYSTICK OFF) + set(SDL_HAPTIC OFF) + set(SDL_HIDAPI OFF) + set(SDL_POWER OFF) + set(SDL_SENSOR OFF) + set(SDL_DIALOG OFF) + set(SDL_DISKAUDIO OFF) + set(SDL_DUMMYAUDIO OFF) + set(SDL_DUMMYCAMERA OFF) + set(SDL_DUMMYVIDEO OFF) + set(SDL_OFFSCREEN OFF) + set(SDL_RENDER_GPU OFF) + set(SDL_VIRTUAL_JOYSTICK OFF) +endif() + if(NOT (SDL_SHARED OR SDL_STATIC)) message(FATAL_ERROR "SDL_SHARED and SDL_STATIC cannot both be disabled") endif() @@ -1103,6 +1123,8 @@ if(SDL_LIBC) check_symbol_exists(poll "poll.h" HAVE_POLL) check_symbol_exists(memfd_create "sys/mman.h" HAVE_MEMFD_CREATE) check_symbol_exists(posix_fallocate "fcntl.h" HAVE_POSIX_FALLOCATE) + check_symbol_exists(posix_spawn_file_actions_addchdir "spawn.h" HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDCHDIR) + check_symbol_exists(posix_spawn_file_actions_addchdir_np "spawn.h" HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDCHDIR_NP) if(SDL_SYSTEM_ICONV) check_c_source_compiles(" @@ -1291,8 +1313,8 @@ if(ANDROID) list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake/android") sdl_glob_sources("${SDL3_SOURCE_DIR}/src/core/android/*.c") - sdl_sources("${ANDROID_NDK}/sources/android/cpufeatures/cpu-features.c") - set_property(SOURCE "${ANDROID_NDK}/sources/android/cpufeatures/cpu-features.c" APPEND_STRING PROPERTY COMPILE_FLAGS " -Wno-declaration-after-statement") + sdl_sources("${CMAKE_ANDROID_NDK}/sources/android/cpufeatures/cpu-features.c") + set_property(SOURCE "${CMAKE_ANDROID_NDK}/sources/android/cpufeatures/cpu-features.c" APPEND_STRING PROPERTY COMPILE_FLAGS " -Wno-declaration-after-statement") sdl_glob_sources("${SDL3_SOURCE_DIR}/src/misc/android/*.c") set(HAVE_SDL_MISC TRUE) @@ -1546,7 +1568,6 @@ elseif(EMSCRIPTEN) #enable gles if(SDL_OPENGLES) - set(SDL_VIDEO_OPENGL_EGL 1) set(HAVE_OPENGLES TRUE) set(SDL_VIDEO_OPENGL_ES2 1) set(SDL_VIDEO_RENDER_OGL_ES2 1) @@ -1734,6 +1755,7 @@ elseif(UNIX AND NOT APPLE AND NOT RISCOS AND NOT HAIKU) sdl_sources( "${SDL3_SOURCE_DIR}/src/core/linux/SDL_dbus.c" "${SDL3_SOURCE_DIR}/src/core/linux/SDL_system_theme.c" + "${SDL3_SOURCE_DIR}/src/core/linux/SDL_progressbar.c" ) endif() @@ -1891,11 +1913,13 @@ elseif(UNIX AND NOT APPLE AND NOT RISCOS AND NOT HAIKU) set (USE_POSIX_SPAWN 1) endif() elseif(WINDOWS) + enable_language(CXX) check_c_source_compiles(" #include int main(int argc, char **argv) { return 0; }" HAVE_WIN32_CC) sdl_glob_sources("${SDL3_SOURCE_DIR}/src/core/windows/*.c") + sdl_glob_sources("${SDL3_SOURCE_DIR}/src/core/windows/*.cpp") sdl_glob_sources("${SDL3_SOURCE_DIR}/src/main/windows/*.c") sdl_glob_sources("${SDL3_SOURCE_DIR}/src/io/windows/*.c") @@ -1978,6 +2002,7 @@ elseif(WINDOWS) check_include_file(audioclient.h HAVE_AUDIOCLIENT_H) check_include_file(sensorsapi.h HAVE_SENSORSAPI_H) check_include_file(shellscalingapi.h HAVE_SHELLSCALINGAPI_H) + check_include_file(shobjidl_core.h HAVE_SHOBJIDL_CORE_H) check_c_source_compiles(" #include #include @@ -2005,6 +2030,7 @@ elseif(WINDOWS) if(SDL_VIDEO) set(SDL_VIDEO_DRIVER_WINDOWS 1) sdl_glob_sources("${SDL3_SOURCE_DIR}/src/video/windows/*.c") + sdl_glob_sources("${SDL3_SOURCE_DIR}/src/video/windows/*.cpp") CheckOpenVR() @@ -2132,7 +2158,7 @@ elseif(WINDOWS) set(SDL_JOYSTICK_WGI 1) endif() if(HAVE_GAMEINPUT_H) - sdl_glob_sources("${SDL3_SOURCE_DIR}/src/joystick/gdk/*.c") + sdl_glob_sources("${SDL3_SOURCE_DIR}/src/joystick/gdk/*.cpp") set(SDL_JOYSTICK_GAMEINPUT 1) endif() set(HAVE_SDL_JOYSTICK TRUE) @@ -2924,6 +2950,81 @@ elseif(N3DS) set(HAVE_SDL_LOCALE TRUE) sdl_glob_sources("${SDL3_SOURCE_DIR}/src/io/n3ds/*.c") + +elseif(NGAGE) + + enable_language(CXX) + + set(SDL_MAIN_USE_CALLBACKS 1) + sdl_glob_sources("${SDL3_SOURCE_DIR}/src/main/ngage/*.c") + sdl_glob_sources("${SDL3_SOURCE_DIR}/src/main/ngage/*.cpp") + sdl_glob_sources("${SDL3_SOURCE_DIR}/src/core/ngage/*.cpp") + set(HAVE_SDL_MAIN_CALLBACKS TRUE) + + if(SDL_AUDIO) + set(SDL_AUDIO_DRIVER_NGAGE 1) + sdl_glob_sources("${SDL3_SOURCE_DIR}/src/audio/ngage/*.c") + sdl_glob_sources("${SDL3_SOURCE_DIR}/src/audio/ngage/*.cpp") + set(HAVE_SDL_AUDIO TRUE) + endif() + + set(SDL_FILESYSTEM_NGAGE 1) + sdl_glob_sources("${SDL3_SOURCE_DIR}/src/filesystem/ngage/*.c") + sdl_glob_sources("${SDL3_SOURCE_DIR}/src/filesystem/ngage/*.cpp") + sdl_glob_sources("${SDL3_SOURCE_DIR}/src/filesystem/posix/*.c") + set(HAVE_SDL_FILESYSTEM TRUE) + + sdl_glob_sources("${SDL3_SOURCE_DIR}/src/locale/ngage/*.cpp") + + if(SDL_RENDER) + set(SDL_VIDEO_RENDER_NGAGE 1) + sdl_glob_sources("${SDL3_SOURCE_DIR}/src/render/ngage/*.c") + endif() + + sdl_glob_sources("${SDL3_SOURCE_DIR}/src/time/ngage/*.cpp") + set(SDL_TIME_NGAGE 1) + + sdl_glob_sources("${SDL3_SOURCE_DIR}/src/render/ngage/*.cpp") + sdl_glob_sources("${SDL3_SOURCE_DIR}/src/time/unix/*.c") + + set(SDL_TIMER_NGAGE 1) + sdl_glob_sources("${SDL3_SOURCE_DIR}/src/timer/ngage/*.cpp") + + set(SDL_FSOPS_POSIX 1) + + set(SDL_VIDEO_DRIVER_NGAGE 1) + sdl_glob_sources("${SDL3_SOURCE_DIR}/src/video/ngage/*.c") + set(HAVE_SDL_TIMERS TRUE) + + set_option(SDL_LEAN_AND_MEAN "Enable lean and mean" ON) + if(SDL_LEAN_AND_MEAN) + sdl_compile_definitions( + PRIVATE + SDL_LEAN_AND_MEAN + ) + endif() + + sdl_link_dependency(ngage + LINK_OPTIONS "SHELL:-s MAIN_COMPAT=0" + PKG_CONFIG_LINK_OPTIONS "-s;MAIN_COMPAT=0" + LIBS + NRenderer + 3dtypes + cone + libgcc + libgcc_ngage + mediaclientaudiostream + charconv + bitgdi + euser + estlib + ws32 + hal + fbscli + efsrv + scdv + gdi + ) endif() sdl_sources(${SDL3_SOURCE_DIR}/src/dialog/SDL_dialog.c) @@ -3017,7 +3118,7 @@ if(SDL_GPU) set(SDL_GPU_D3D11 1) set(HAVE_SDL_GPU TRUE) endif() - if(SDL_RENDER_D3D12) + if(WINDOWS) sdl_glob_sources("${SDL3_SOURCE_DIR}/src/gpu/d3d12/*.c") set(SDL_GPU_D3D12 1) set(HAVE_SDL_GPU TRUE) @@ -3104,8 +3205,8 @@ endif() # We always need to have threads and timers around if(NOT HAVE_SDL_THREADS) - # The emscripten platform has been carefully vetted to work without threads - if(EMSCRIPTEN) + # The Emscripten and N-Gage platform has been carefully vetted to work without threads + if(EMSCRIPTEN OR NGAGE) set(SDL_THREADS_DISABLED 1) sdl_glob_sources("${SDL3_SOURCE_DIR}/src/thread/generic/*.c") else() @@ -3288,7 +3389,7 @@ else() endif() if(ANDROID) - sdl_include_directories(PRIVATE SYSTEM "${ANDROID_NDK}/sources/android/cpufeatures") + sdl_include_directories(PRIVATE SYSTEM "${CMAKE_ANDROID_NDK}/sources/android/cpufeatures") endif() if(APPLE) diff --git a/libs/SDL3/VisualC-GDK/SDL/SDL.vcxproj b/libs/SDL3/VisualC-GDK/SDL/SDL.vcxproj index 5bf00043..58194e72 100644 --- a/libs/SDL3/VisualC-GDK/SDL/SDL.vcxproj +++ b/libs/SDL3/VisualC-GDK/SDL/SDL.vcxproj @@ -168,8 +168,8 @@ - call $(ProjectDir)..\..\src\render\direct3d12\compile_shaders_xbox.bat $(ProjectDir)..\ - call $(ProjectDir)..\..\src\gpu\d3d12\compile_shaders_xbox.bat $(ProjectDir)..\ + call "$(ProjectDir)..\..\src\render\direct3d12\compile_shaders_xbox.bat" "$(ProjectDir)..\" + call "$(ProjectDir)..\..\src\gpu\d3d12\compile_shaders_xbox.bat" "$(ProjectDir)..\" @@ -454,8 +454,10 @@ + + @@ -643,7 +645,7 @@ - + @@ -703,13 +705,18 @@ stdcpp17 stdcpp17 + + - + + + + @@ -725,6 +732,7 @@ + @@ -885,7 +893,7 @@ - + diff --git a/libs/SDL3/VisualC-GDK/SDL/SDL.vcxproj.filters b/libs/SDL3/VisualC-GDK/SDL/SDL.vcxproj.filters index f760877e..8a988ace 100644 --- a/libs/SDL3/VisualC-GDK/SDL/SDL.vcxproj.filters +++ b/libs/SDL3/VisualC-GDK/SDL/SDL.vcxproj.filters @@ -27,7 +27,7 @@ - + @@ -56,13 +56,18 @@ + + - + + + + @@ -78,6 +83,7 @@ + @@ -189,7 +195,7 @@ - + @@ -343,8 +349,10 @@ + + diff --git a/libs/SDL3/VisualC-GDK/tests/testgdk/src/testgdk.cpp b/libs/SDL3/VisualC-GDK/tests/testgdk/src/testgdk.cpp index 51fc75bc..20bc03ff 100644 --- a/libs/SDL3/VisualC-GDK/tests/testgdk/src/testgdk.cpp +++ b/libs/SDL3/VisualC-GDK/tests/testgdk/src/testgdk.cpp @@ -56,8 +56,7 @@ static struct static SDL_AudioStream *stream; /* Call this instead of exit(), so we can clean up SDL: atexit() is evil. */ -static void -quit(int rc) +static void quit(int rc) { SDL_free(sprites); SDL_DestroyAudioStream(stream); @@ -80,8 +79,7 @@ static int fillerup(void) return 0; } -void -UserLoggedIn(XUserHandle user) +static void UserLoggedIn(XUserHandle user) { HRESULT hr; char gamertag[128]; @@ -96,8 +94,7 @@ UserLoggedIn(XUserHandle user) XUserCloseHandle(user); } -void -AddUserUICallback(XAsyncBlock *asyncBlock) +static void AddUserUICallback(XAsyncBlock *asyncBlock) { HRESULT hr; XUserHandle user = NULL; @@ -123,8 +120,7 @@ AddUserUICallback(XAsyncBlock *asyncBlock) delete asyncBlock; } -void -AddUserUI() +static void AddUserUI() { HRESULT hr; XAsyncBlock *asyncBlock = new XAsyncBlock; @@ -141,8 +137,7 @@ AddUserUI() } } -void -AddUserSilentCallback(XAsyncBlock *asyncBlock) +static void AddUserSilentCallback(XAsyncBlock *asyncBlock) { HRESULT hr; XUserHandle user = NULL; @@ -168,8 +163,7 @@ AddUserSilentCallback(XAsyncBlock *asyncBlock) delete asyncBlock; } -void -AddUserSilent() +static void AddUserSilent() { HRESULT hr; XAsyncBlock *asyncBlock = new XAsyncBlock; @@ -186,30 +180,27 @@ AddUserSilent() } } -int -LoadSprite(const char *file) +static bool LoadSprite(const char *file) { int i; for (i = 0; i < state->num_windows; ++i) { /* This does the SDL_LoadBMP step repeatedly, but that's OK for test code. */ - sprites[i] = LoadTexture(state->renderers[i], file, true, &sprite_w, &sprite_h); + sprites[i] = LoadTexture(state->renderers[i], file, true); if (!sprites[i]) { - return -1; - } - if (!SDL_SetTextureBlendMode(sprites[i], blendMode)) { - SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't set blend mode: %s", SDL_GetError()); - SDL_DestroyTexture(sprites[i]); - return -1; + return false; } + sprite_w = sprites[i]->w; + sprite_h = sprites[i]->h; + + SDL_SetTextureBlendMode(sprites[i], blendMode); } /* We're ready to roll. :) */ - return 0; + return true; } -void -DrawSprites(SDL_Renderer * renderer, SDL_Texture * sprite) +static void DrawSprites(SDL_Renderer * renderer, SDL_Texture * sprite) { SDL_Rect viewport; SDL_FRect temp; @@ -300,8 +291,7 @@ DrawSprites(SDL_Renderer * renderer, SDL_Texture * sprite) SDL_RenderPresent(renderer); } -void -loop() +static void loop() { int i; SDL_Event event; @@ -329,8 +319,7 @@ loop() fillerup(); } -int -main(int argc, char *argv[]) +int main(int argc, char *argv[]) { int i; const char *icon = "icon.bmp"; @@ -413,7 +402,7 @@ main(int argc, char *argv[]) SDL_SetRenderDrawColor(renderer, 0xA0, 0xA0, 0xA0, 0xFF); SDL_RenderClear(renderer); } - if (LoadSprite(icon) < 0) { + if (!LoadSprite(icon)) { quit(2); } diff --git a/libs/SDL3/VisualC/SDL.sln b/libs/SDL3/VisualC/SDL.sln index bd804d44..b5cf786a 100644 --- a/libs/SDL3/VisualC/SDL.sln +++ b/libs/SDL3/VisualC/SDL.sln @@ -69,7 +69,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "camera", "camera", "{AAEC83 EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "01-read-and-draw", "examples\camera\01-read-and-draw\01-read-and-draw.vcxproj", "{510ACF0C-4012-4216-98EF-E4F155DE33CE}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "game", "game", "{D1BF59F6-22DC-493B-BDEB-451A50DA793D}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "demo", "demo", "{D1BF59F6-22DC-493B-BDEB-451A50DA793D}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "01-snake", "examples\demo\01-snake\01-snake.vcxproj", "{7820969A-5B7B-4046-BB0A-82905D457FC5}" EndProject @@ -115,6 +115,22 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "02-woodeneye-008", "example EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "03-infinite-monkeys", "examples\demo\03-infinite-monkeys\03-infinite-monkeys.vcxproj", "{75AEE75A-C016-4497-960B-D767B822237D}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "19-affine-textures", "examples\renderer\19-affine-textures\19-affine-textures.vcxproj", "{E21C50BF-54B4-434C-AA24-9A6469553987}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "04-multiple-streams", "examples\audio\04-multiple-streams\04-multiple-streams.vcxproj", "{7117A55C-BE4E-41DB-A4FC-4070E35A8B28}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "asyncio", "asyncio", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "input", "input", "{8DEAE483-FDE7-463F-9FD5-F597BBAED1F9}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "01-load-bitmaps", "examples\asyncio\01-load-bitmaps\01-load-bitmaps.vcxproj", "{6A2BFA8B-C027-400D-A18B-3E9E1CC4DDD0}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "04-bytepusher", "examples\demo\04-bytepusher\04-bytepusher.vcxproj", "{3DB9B219-769E-43AC-8B8B-319DB6045DCF}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "01-joystick-polling", "examples\input\01-joystick-polling\01-joystick-polling.vcxproj", "{B3852DB7-E925-4026-8B9D-D2272EFEFF3C}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "02-joystick-events", "examples\input\02-joystick-events\02-joystick-events.vcxproj", "{FCBDF2B2-1129-49AE-9406-3F219E65CA89}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Win32 = Debug|Win32 @@ -515,6 +531,54 @@ Global {75AEE75A-C016-4497-960B-D767B822237D}.Release|Win32.Build.0 = Release|Win32 {75AEE75A-C016-4497-960B-D767B822237D}.Release|x64.ActiveCfg = Release|x64 {75AEE75A-C016-4497-960B-D767B822237D}.Release|x64.Build.0 = Release|x64 + {E21C50BF-54B4-434C-AA24-9A6469553987}.Debug|Win32.ActiveCfg = Debug|Win32 + {E21C50BF-54B4-434C-AA24-9A6469553987}.Debug|Win32.Build.0 = Debug|Win32 + {E21C50BF-54B4-434C-AA24-9A6469553987}.Debug|x64.ActiveCfg = Debug|x64 + {E21C50BF-54B4-434C-AA24-9A6469553987}.Debug|x64.Build.0 = Debug|x64 + {E21C50BF-54B4-434C-AA24-9A6469553987}.Release|Win32.ActiveCfg = Release|Win32 + {E21C50BF-54B4-434C-AA24-9A6469553987}.Release|Win32.Build.0 = Release|Win32 + {E21C50BF-54B4-434C-AA24-9A6469553987}.Release|x64.ActiveCfg = Release|x64 + {E21C50BF-54B4-434C-AA24-9A6469553987}.Release|x64.Build.0 = Release|x64 + {7117A55C-BE4E-41DB-A4FC-4070E35A8B28}.Debug|Win32.ActiveCfg = Debug|Win32 + {7117A55C-BE4E-41DB-A4FC-4070E35A8B28}.Debug|Win32.Build.0 = Debug|Win32 + {7117A55C-BE4E-41DB-A4FC-4070E35A8B28}.Debug|x64.ActiveCfg = Debug|x64 + {7117A55C-BE4E-41DB-A4FC-4070E35A8B28}.Debug|x64.Build.0 = Debug|x64 + {7117A55C-BE4E-41DB-A4FC-4070E35A8B28}.Release|Win32.ActiveCfg = Release|Win32 + {7117A55C-BE4E-41DB-A4FC-4070E35A8B28}.Release|Win32.Build.0 = Release|Win32 + {7117A55C-BE4E-41DB-A4FC-4070E35A8B28}.Release|x64.ActiveCfg = Release|x64 + {7117A55C-BE4E-41DB-A4FC-4070E35A8B28}.Release|x64.Build.0 = Release|x64 + {6A2BFA8B-C027-400D-A18B-3E9E1CC4DDD0}.Debug|Win32.ActiveCfg = Debug|Win32 + {6A2BFA8B-C027-400D-A18B-3E9E1CC4DDD0}.Debug|Win32.Build.0 = Debug|Win32 + {6A2BFA8B-C027-400D-A18B-3E9E1CC4DDD0}.Debug|x64.ActiveCfg = Debug|x64 + {6A2BFA8B-C027-400D-A18B-3E9E1CC4DDD0}.Debug|x64.Build.0 = Debug|x64 + {6A2BFA8B-C027-400D-A18B-3E9E1CC4DDD0}.Release|Win32.ActiveCfg = Release|Win32 + {6A2BFA8B-C027-400D-A18B-3E9E1CC4DDD0}.Release|Win32.Build.0 = Release|Win32 + {6A2BFA8B-C027-400D-A18B-3E9E1CC4DDD0}.Release|x64.ActiveCfg = Release|x64 + {6A2BFA8B-C027-400D-A18B-3E9E1CC4DDD0}.Release|x64.Build.0 = Release|x64 + {3DB9B219-769E-43AC-8B8B-319DB6045DCF}.Debug|Win32.ActiveCfg = Debug|Win32 + {3DB9B219-769E-43AC-8B8B-319DB6045DCF}.Debug|Win32.Build.0 = Debug|Win32 + {3DB9B219-769E-43AC-8B8B-319DB6045DCF}.Debug|x64.ActiveCfg = Debug|x64 + {3DB9B219-769E-43AC-8B8B-319DB6045DCF}.Debug|x64.Build.0 = Debug|x64 + {3DB9B219-769E-43AC-8B8B-319DB6045DCF}.Release|Win32.ActiveCfg = Release|Win32 + {3DB9B219-769E-43AC-8B8B-319DB6045DCF}.Release|Win32.Build.0 = Release|Win32 + {3DB9B219-769E-43AC-8B8B-319DB6045DCF}.Release|x64.ActiveCfg = Release|x64 + {3DB9B219-769E-43AC-8B8B-319DB6045DCF}.Release|x64.Build.0 = Release|x64 + {B3852DB7-E925-4026-8B9D-D2272EFEFF3C}.Debug|Win32.ActiveCfg = Debug|Win32 + {B3852DB7-E925-4026-8B9D-D2272EFEFF3C}.Debug|Win32.Build.0 = Debug|Win32 + {B3852DB7-E925-4026-8B9D-D2272EFEFF3C}.Debug|x64.ActiveCfg = Debug|x64 + {B3852DB7-E925-4026-8B9D-D2272EFEFF3C}.Debug|x64.Build.0 = Debug|x64 + {B3852DB7-E925-4026-8B9D-D2272EFEFF3C}.Release|Win32.ActiveCfg = Release|Win32 + {B3852DB7-E925-4026-8B9D-D2272EFEFF3C}.Release|Win32.Build.0 = Release|Win32 + {B3852DB7-E925-4026-8B9D-D2272EFEFF3C}.Release|x64.ActiveCfg = Release|x64 + {B3852DB7-E925-4026-8B9D-D2272EFEFF3C}.Release|x64.Build.0 = Release|x64 + {FCBDF2B2-1129-49AE-9406-3F219E65CA89}.Debug|Win32.ActiveCfg = Debug|Win32 + {FCBDF2B2-1129-49AE-9406-3F219E65CA89}.Debug|Win32.Build.0 = Debug|Win32 + {FCBDF2B2-1129-49AE-9406-3F219E65CA89}.Debug|x64.ActiveCfg = Debug|x64 + {FCBDF2B2-1129-49AE-9406-3F219E65CA89}.Debug|x64.Build.0 = Debug|x64 + {FCBDF2B2-1129-49AE-9406-3F219E65CA89}.Release|Win32.ActiveCfg = Release|Win32 + {FCBDF2B2-1129-49AE-9406-3F219E65CA89}.Release|Win32.Build.0 = Release|Win32 + {FCBDF2B2-1129-49AE-9406-3F219E65CA89}.Release|x64.ActiveCfg = Release|x64 + {FCBDF2B2-1129-49AE-9406-3F219E65CA89}.Release|x64.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -572,6 +636,14 @@ Global {608C6C67-7766-471F-BBFF-8B00086039AF} = {1B61A1B7-92DE-4C37-9151-D2928D6449AB} {A3F601E0-B54C-4DD8-8A97-FDEF7624EE60} = {D1BF59F6-22DC-493B-BDEB-451A50DA793D} {75AEE75A-C016-4497-960B-D767B822237D} = {D1BF59F6-22DC-493B-BDEB-451A50DA793D} + {E21C50BF-54B4-434C-AA24-9A6469553987} = {F91DDAF0-B74F-4516-A1A9-42ED8DFCBF6A} + {7117A55C-BE4E-41DB-A4FC-4070E35A8B28} = {1B61A1B7-92DE-4C37-9151-D2928D6449AB} + {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} = {1498F0CD-F4DA-4847-9CB2-FB18D48061D5} + {8DEAE483-FDE7-463F-9FD5-F597BBAED1F9} = {1498F0CD-F4DA-4847-9CB2-FB18D48061D5} + {6A2BFA8B-C027-400D-A18B-3E9E1CC4DDD0} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} + {3DB9B219-769E-43AC-8B8B-319DB6045DCF} = {D1BF59F6-22DC-493B-BDEB-451A50DA793D} + {B3852DB7-E925-4026-8B9D-D2272EFEFF3C} = {8DEAE483-FDE7-463F-9FD5-F597BBAED1F9} + {FCBDF2B2-1129-49AE-9406-3F219E65CA89} = {8DEAE483-FDE7-463F-9FD5-F597BBAED1F9} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {C320C9F2-1A8F-41D7-B02B-6338F872BCAD} diff --git a/libs/SDL3/VisualC/SDL/SDL.vcxproj b/libs/SDL3/VisualC/SDL/SDL.vcxproj index a15978a2..843f8e61 100644 --- a/libs/SDL3/VisualC/SDL/SDL.vcxproj +++ b/libs/SDL3/VisualC/SDL/SDL.vcxproj @@ -367,8 +367,10 @@ + + @@ -422,6 +424,16 @@ + + Create + Create + Create + Create + $(IntDir)$(TargetName)_cpp.pch + $(IntDir)$(TargetName)_cpp.pch + $(IntDir)$(TargetName)_cpp.pch + $(IntDir)$(TargetName)_cpp.pch + @@ -541,7 +553,12 @@ - + + $(IntDir)$(TargetName)_cpp.pch + $(IntDir)$(TargetName)_cpp.pch + $(IntDir)$(TargetName)_cpp.pch + $(IntDir)$(TargetName)_cpp.pch + @@ -573,13 +590,23 @@ + + - + + $(IntDir)$(TargetName)_cpp.pch + $(IntDir)$(TargetName)_cpp.pch + $(IntDir)$(TargetName)_cpp.pch + $(IntDir)$(TargetName)_cpp.pch + + + + @@ -595,6 +622,7 @@ + @@ -720,7 +748,12 @@ - + + $(IntDir)$(TargetName)_cpp.pch + $(IntDir)$(TargetName)_cpp.pch + $(IntDir)$(TargetName)_cpp.pch + $(IntDir)$(TargetName)_cpp.pch + diff --git a/libs/SDL3/VisualC/SDL/SDL.vcxproj.filters b/libs/SDL3/VisualC/SDL/SDL.vcxproj.filters index d653ee05..375c175c 100644 --- a/libs/SDL3/VisualC/SDL/SDL.vcxproj.filters +++ b/libs/SDL3/VisualC/SDL/SDL.vcxproj.filters @@ -82,6 +82,9 @@ {ebc2fca3-3c26-45e3-815e-3e0581d5e226} + + {06DB01C0-65B5-4DE7-8ADC-C0B0CA3A1E69} + {47c445a2-7014-4e15-9660-7c89a27dddcf} @@ -534,9 +537,6 @@ events - - events - events @@ -564,6 +564,9 @@ haptic + + haptic + haptic @@ -621,6 +624,9 @@ haptic\windows + + haptic\hidapi + joystick\hidapi @@ -953,6 +959,7 @@ + @@ -1040,7 +1047,7 @@ core - + core\windows @@ -1079,9 +1086,6 @@ events - - events - events @@ -1163,21 +1167,36 @@ haptic\windows + + haptic\hidapi + + + haptic\hidapi + haptic\dummy joystick\dummy - + joystick\gdk + + joystick\hidapi + + + joystick\hidapi + joystick\hidapi joystick\hidapi + + joystick\hidapi + joystick\hidapi @@ -1223,6 +1242,9 @@ joystick\hidapi + + joystick\hidapi + joystick\hidapi @@ -1346,7 +1368,7 @@ video\windows - + video\windows @@ -1585,11 +1607,12 @@ + + + core\windows + - - - diff --git a/libs/SDL3/VisualC/examples/asyncio/01-load-bitmaps/01-load-bitmaps.vcxproj b/libs/SDL3/VisualC/examples/asyncio/01-load-bitmaps/01-load-bitmaps.vcxproj new file mode 100644 index 00000000..7cdf9869 --- /dev/null +++ b/libs/SDL3/VisualC/examples/asyncio/01-load-bitmaps/01-load-bitmaps.vcxproj @@ -0,0 +1,13 @@ + + + + {6A2BFA8B-C027-400D-A18B-3E9E1CC4DDD0} + + + + + + + + + \ No newline at end of file diff --git a/libs/SDL3/VisualC/examples/audio/04-multiple-streams/04-multiple-streams.vcxproj b/libs/SDL3/VisualC/examples/audio/04-multiple-streams/04-multiple-streams.vcxproj new file mode 100644 index 00000000..b2e0a32c --- /dev/null +++ b/libs/SDL3/VisualC/examples/audio/04-multiple-streams/04-multiple-streams.vcxproj @@ -0,0 +1,13 @@ + + + + {7117A55C-BE4E-41DB-A4FC-4070E35A8B28} + + + + + + + + + \ No newline at end of file diff --git a/libs/SDL3/VisualC/examples/demo/04-bytepusher/04-bytepusher.vcxproj b/libs/SDL3/VisualC/examples/demo/04-bytepusher/04-bytepusher.vcxproj new file mode 100644 index 00000000..b7894b5e --- /dev/null +++ b/libs/SDL3/VisualC/examples/demo/04-bytepusher/04-bytepusher.vcxproj @@ -0,0 +1,13 @@ + + + + {3DB9B219-769E-43AC-8B8B-319DB6045DCF} + + + + + + + + + \ No newline at end of file diff --git a/libs/SDL3/VisualC/examples/generate.py b/libs/SDL3/VisualC/examples/generate.py index e06110e4..62614d26 100644 --- a/libs/SDL3/VisualC/examples/generate.py +++ b/libs/SDL3/VisualC/examples/generate.py @@ -47,7 +47,8 @@ def main(): for category in path.iterdir(): if category.is_dir(): for example in category.iterdir(): - generate(category.name, example.name, get_c_source_filename(example)) + if example.is_dir(): + generate(category.name, example.name, get_c_source_filename(example)) if __name__ == "__main__": diff --git a/libs/SDL3/VisualC/examples/input/01-joystick-polling/01-joystick-polling.vcxproj b/libs/SDL3/VisualC/examples/input/01-joystick-polling/01-joystick-polling.vcxproj new file mode 100644 index 00000000..e3fc6715 --- /dev/null +++ b/libs/SDL3/VisualC/examples/input/01-joystick-polling/01-joystick-polling.vcxproj @@ -0,0 +1,13 @@ + + + + {B3852DB7-E925-4026-8B9D-D2272EFEFF3C} + + + + + + + + + \ No newline at end of file diff --git a/libs/SDL3/VisualC/examples/input/02-joystick-events/02-joystick-events.vcxproj b/libs/SDL3/VisualC/examples/input/02-joystick-events/02-joystick-events.vcxproj new file mode 100644 index 00000000..c119584d --- /dev/null +++ b/libs/SDL3/VisualC/examples/input/02-joystick-events/02-joystick-events.vcxproj @@ -0,0 +1,13 @@ + + + + {FCBDF2B2-1129-49AE-9406-3F219E65CA89} + + + + + + + + + \ No newline at end of file diff --git a/libs/SDL3/VisualC/examples/renderer/19-affine-textures/19-affine-textures.vcxproj b/libs/SDL3/VisualC/examples/renderer/19-affine-textures/19-affine-textures.vcxproj new file mode 100644 index 00000000..b5575ecd --- /dev/null +++ b/libs/SDL3/VisualC/examples/renderer/19-affine-textures/19-affine-textures.vcxproj @@ -0,0 +1,13 @@ + + + + {E21C50BF-54B4-434C-AA24-9A6469553987} + + + + + + + + + \ No newline at end of file diff --git a/libs/SDL3/Xcode/SDL/Info-Framework.plist b/libs/SDL3/Xcode/SDL/Info-Framework.plist index eec80013..adf5f832 100644 --- a/libs/SDL3/Xcode/SDL/Info-Framework.plist +++ b/libs/SDL3/Xcode/SDL/Info-Framework.plist @@ -19,10 +19,10 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 3.2.10 + 3.3.0 CFBundleSignature SDLX CFBundleVersion - 3.2.10 + 3.3.0 diff --git a/libs/SDL3/Xcode/SDL/SDL.xcodeproj/project.pbxproj b/libs/SDL3/Xcode/SDL/SDL.xcodeproj/project.pbxproj index 5926329c..ffe8fa75 100644 --- a/libs/SDL3/Xcode/SDL/SDL.xcodeproj/project.pbxproj +++ b/libs/SDL3/Xcode/SDL/SDL.xcodeproj/project.pbxproj @@ -71,6 +71,10 @@ 63134A262A7902FD0021E9A6 /* SDL_pen.c in Sources */ = {isa = PBXBuildFile; fileRef = 63134A242A7902FD0021E9A6 /* SDL_pen.c */; }; 75E0915A241EA924004729E1 /* SDL_virtualjoystick.c in Sources */ = {isa = PBXBuildFile; fileRef = 75E09158241EA924004729E1 /* SDL_virtualjoystick.c */; }; 75E09163241EA924004729E1 /* SDL_virtualjoystick_c.h in Headers */ = {isa = PBXBuildFile; fileRef = 75E09159241EA924004729E1 /* SDL_virtualjoystick_c.h */; }; + 89E5801E2D03602200DAF6D3 /* SDL_hidapi_lg4ff.c in Sources */ = {isa = PBXBuildFile; fileRef = 89E5801D2D03602200DAF6D3 /* SDL_hidapi_lg4ff.c */; }; + 89E580232D03606400DAF6D3 /* SDL_hidapihaptic.c in Sources */ = {isa = PBXBuildFile; fileRef = 89E5801F2D03606400DAF6D3 /* SDL_hidapihaptic.c */; }; + 89E580242D03606400DAF6D3 /* SDL_hidapihaptic_lg4ff.c in Sources */ = {isa = PBXBuildFile; fileRef = 89E580212D03606400DAF6D3 /* SDL_hidapihaptic_lg4ff.c */; }; + 89E580252D03606400DAF6D3 /* SDL_hidapihaptic_c.h in Headers */ = {isa = PBXBuildFile; fileRef = 89E580202D03606400DAF6D3 /* SDL_hidapihaptic_c.h */; }; 9846B07C287A9020000C35C8 /* SDL_hidapi_shield.c in Sources */ = {isa = PBXBuildFile; fileRef = 9846B07B287A9020000C35C8 /* SDL_hidapi_shield.c */; }; A1626A3E2617006A003F1973 /* SDL_triangle.c in Sources */ = {isa = PBXBuildFile; fileRef = A1626A3D2617006A003F1973 /* SDL_triangle.c */; }; A1626A522617008D003F1973 /* SDL_triangle.h in Headers */ = {isa = PBXBuildFile; fileRef = A1626A512617008C003F1973 /* SDL_triangle.h */; }; @@ -384,6 +388,7 @@ F32DDAD42AB795A30041EAA5 /* SDL_audioresample.c in Sources */ = {isa = PBXBuildFile; fileRef = F32DDACE2AB795A30041EAA5 /* SDL_audioresample.c */; }; F338A1182D1B37D8007CDFDF /* SDL_tray.m in Sources */ = {isa = PBXBuildFile; fileRef = F338A1172D1B37D8007CDFDF /* SDL_tray.m */; }; F338A11A2D1B37E4007CDFDF /* SDL_tray.c in Sources */ = {isa = PBXBuildFile; fileRef = F338A1192D1B37E4007CDFDF /* SDL_tray.c */; }; + F3395BA82D9A5971007246C8 /* SDL_hidapi_8bitdo.c in Sources */ = {isa = PBXBuildFile; fileRef = F3395BA72D9A5971007246C8 /* SDL_hidapi_8bitdo.c */; }; F34400342D40217A003F26D7 /* LICENSE.txt in Resources */ = {isa = PBXBuildFile; fileRef = F373DA182D388A1E002158FA /* LICENSE.txt */; }; F34400362D40217A003F26D7 /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = F373DA192D388A1E002158FA /* README.md */; }; F344003D2D4022E1003F26D7 /* INSTALL.md in Resources */ = {isa = PBXBuildFile; fileRef = F344003C2D4022E1003F26D7 /* INSTALL.md */; }; @@ -433,6 +438,7 @@ F3B439532C935C2C00792030 /* SDL_posixprocess.c in Sources */ = {isa = PBXBuildFile; fileRef = F3B439522C935C2C00792030 /* SDL_posixprocess.c */; }; F3B439562C937DAB00792030 /* SDL_process.c in Sources */ = {isa = PBXBuildFile; fileRef = F3B439542C937DAB00792030 /* SDL_process.c */; }; F3B439572C937DAB00792030 /* SDL_sysprocess.h in Headers */ = {isa = PBXBuildFile; fileRef = F3B439552C937DAB00792030 /* SDL_sysprocess.h */; }; + F3B6B80A2DC3EA54004954FD /* SDL_hidapi_gip.c in Sources */ = {isa = PBXBuildFile; fileRef = F3B6B8092DC3EA54004954FD /* SDL_hidapi_gip.c */; }; F3C1BD752D1F1A3000846529 /* SDL_tray_utils.c in Sources */ = {isa = PBXBuildFile; fileRef = F3C1BD742D1F1A3000846529 /* SDL_tray_utils.c */; }; F3C1BD762D1F1A3000846529 /* SDL_tray_utils.h in Headers */ = {isa = PBXBuildFile; fileRef = F3C1BD732D1F1A3000846529 /* SDL_tray_utils.h */; }; F3C2CB222C5DDDB2004D7998 /* SDL_categories_c.h in Headers */ = {isa = PBXBuildFile; fileRef = F3C2CB202C5DDDB2004D7998 /* SDL_categories_c.h */; }; @@ -535,6 +541,7 @@ F3FA5A232B59ACE000FEAD97 /* yuv_rgb_lsx.c in Sources */ = {isa = PBXBuildFile; fileRef = F3FA5A1A2B59ACE000FEAD97 /* yuv_rgb_lsx.c */; }; F3FA5A242B59ACE000FEAD97 /* yuv_rgb_lsx.h in Headers */ = {isa = PBXBuildFile; fileRef = F3FA5A1B2B59ACE000FEAD97 /* yuv_rgb_lsx.h */; }; F3FA5A252B59ACE000FEAD97 /* yuv_rgb_common.h in Headers */ = {isa = PBXBuildFile; fileRef = F3FA5A1C2B59ACE000FEAD97 /* yuv_rgb_common.h */; }; + F3FBB1082DDF93AB0000F99F /* SDL_hidapi_flydigi.c in Sources */ = {isa = PBXBuildFile; fileRef = F3395BA72D9A5971007246C9 /* SDL_hidapi_flydigi.c */; }; F3FD042E2C9B755700824C4C /* SDL_hidapi_nintendo.h in Headers */ = {isa = PBXBuildFile; fileRef = F3FD042C2C9B755700824C4C /* SDL_hidapi_nintendo.h */; }; F3FD042F2C9B755700824C4C /* SDL_hidapi_steam_hori.c in Sources */ = {isa = PBXBuildFile; fileRef = F3FD042D2C9B755700824C4C /* SDL_hidapi_steam_hori.c */; }; FA73671D19A540EF004122E4 /* CoreVideo.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FA73671C19A540EF004122E4 /* CoreVideo.framework */; platformFilters = (ios, maccatalyst, macos, tvos, ); settings = {ATTRIBUTES = (Required, ); }; }; @@ -608,6 +615,10 @@ 63134A242A7902FD0021E9A6 /* SDL_pen.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_pen.c; sourceTree = ""; }; 75E09158241EA924004729E1 /* SDL_virtualjoystick.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_virtualjoystick.c; sourceTree = ""; }; 75E09159241EA924004729E1 /* SDL_virtualjoystick_c.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_virtualjoystick_c.h; sourceTree = ""; }; + 89E5801D2D03602200DAF6D3 /* SDL_hidapi_lg4ff.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = SDL_hidapi_lg4ff.c; sourceTree = ""; }; + 89E5801F2D03606400DAF6D3 /* SDL_hidapihaptic.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = SDL_hidapihaptic.c; sourceTree = ""; }; + 89E580202D03606400DAF6D3 /* SDL_hidapihaptic_c.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDL_hidapihaptic_c.h; sourceTree = ""; }; + 89E580212D03606400DAF6D3 /* SDL_hidapihaptic_lg4ff.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = SDL_hidapihaptic_lg4ff.c; sourceTree = ""; }; 9846B07B287A9020000C35C8 /* SDL_hidapi_shield.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_hidapi_shield.c; sourceTree = ""; }; A1626A3D2617006A003F1973 /* SDL_triangle.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_triangle.c; sourceTree = ""; }; A1626A512617008C003F1973 /* SDL_triangle.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_triangle.h; sourceTree = ""; }; @@ -938,6 +949,8 @@ F32DDACE2AB795A30041EAA5 /* SDL_audioresample.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_audioresample.c; sourceTree = ""; }; F338A1172D1B37D8007CDFDF /* SDL_tray.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDL_tray.m; sourceTree = ""; }; F338A1192D1B37E4007CDFDF /* SDL_tray.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = SDL_tray.c; sourceTree = ""; }; + F3395BA72D9A5971007246C8 /* SDL_hidapi_8bitdo.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = SDL_hidapi_8bitdo.c; sourceTree = ""; }; + F3395BA72D9A5971007246C9 /* SDL_hidapi_flydigi.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = SDL_hidapi_flydigi.c; sourceTree = ""; }; F344003C2D4022E1003F26D7 /* INSTALL.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = INSTALL.md; sourceTree = ""; }; F362B9152B3349E200D30B94 /* controller_list.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = controller_list.h; sourceTree = ""; }; F362B9162B3349E200D30B94 /* SDL_gamepad_c.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_gamepad_c.h; sourceTree = ""; }; @@ -1002,6 +1015,7 @@ F3B439522C935C2C00792030 /* SDL_posixprocess.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_posixprocess.c; sourceTree = ""; }; F3B439542C937DAB00792030 /* SDL_process.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_process.c; sourceTree = ""; }; F3B439552C937DAB00792030 /* SDL_sysprocess.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_sysprocess.h; sourceTree = ""; }; + F3B6B8092DC3EA54004954FD /* SDL_hidapi_gip.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = SDL_hidapi_gip.c; sourceTree = ""; }; F3C1BD732D1F1A3000846529 /* SDL_tray_utils.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDL_tray_utils.h; sourceTree = ""; }; F3C1BD742D1F1A3000846529 /* SDL_tray_utils.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = SDL_tray_utils.c; sourceTree = ""; }; F3C2CB202C5DDDB2004D7998 /* SDL_categories_c.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_categories_c.h; sourceTree = ""; }; @@ -1477,6 +1491,16 @@ path = virtual; sourceTree = ""; }; + 89E580222D03606400DAF6D3 /* hidapi */ = { + isa = PBXGroup; + children = ( + 89E5801F2D03606400DAF6D3 /* SDL_hidapihaptic.c */, + 89E580202D03606400DAF6D3 /* SDL_hidapihaptic_c.h */, + 89E580212D03606400DAF6D3 /* SDL_hidapihaptic_lg4ff.c */, + ); + path = hidapi; + sourceTree = ""; + }; A75FDAA423E2790500529352 /* ios */ = { isa = PBXGroup; children = ( @@ -1535,6 +1559,7 @@ A7D8A5C223E2513D00DCD162 /* haptic */ = { isa = PBXGroup; children = ( + 89E580222D03606400DAF6D3 /* hidapi */, A7D8A5CD23E2513D00DCD162 /* darwin */, A7D8A5C323E2513D00DCD162 /* dummy */, A7D8A5C623E2513D00DCD162 /* SDL_haptic_c.h */, @@ -1904,8 +1929,12 @@ A7D8A7BE23E2513E00DCD162 /* hidapi */ = { isa = PBXGroup; children = ( + F3395BA72D9A5971007246C8 /* SDL_hidapi_8bitdo.c */, F32305FE28939F6400E66D30 /* SDL_hidapi_combined.c */, + F3395BA72D9A5971007246C9 /* SDL_hidapi_flydigi.c */, A7D8A7C923E2513E00DCD162 /* SDL_hidapi_gamecube.c */, + F3B6B8092DC3EA54004954FD /* SDL_hidapi_gip.c */, + 89E5801D2D03602200DAF6D3 /* SDL_hidapi_lg4ff.c */, F3F07D59269640160074468B /* SDL_hidapi_luna.c */, F3FD042C2C9B755700824C4C /* SDL_hidapi_nintendo.h */, F388C95428B5F6F600661ECF /* SDL_hidapi_ps3.c */, @@ -2617,6 +2646,7 @@ F37E18642BAA40670098C111 /* SDL_time_c.h in Headers */, F31013C82C24E98200FBE946 /* SDL_keymap_c.h in Headers */, 63134A252A7902FD0021E9A6 /* SDL_pen_c.h in Headers */, + 89E580252D03606400DAF6D3 /* SDL_hidapihaptic_c.h in Headers */, F36C34312C0F876500991150 /* SDL_offscreenvulkan.h in Headers */, A7D8B2C023E2514200DCD162 /* SDL_pixels_c.h in Headers */, F37E18622BAA40090098C111 /* SDL_sysfilesystem.h in Headers */, @@ -2905,6 +2935,8 @@ A7D8BBDD23E2574800DCD162 /* SDL_uikitmodes.m in Sources */, A7D8BA3723E2514400DCD162 /* SDL_d3dmath.c in Sources */, F3A9AE9C2C8A13C100AAC390 /* SDL_pipeline_gpu.c in Sources */, + 89E580232D03606400DAF6D3 /* SDL_hidapihaptic.c in Sources */, + 89E580242D03606400DAF6D3 /* SDL_hidapihaptic_lg4ff.c in Sources */, 75E0915A241EA924004729E1 /* SDL_virtualjoystick.c in Sources */, F338A11A2D1B37E4007CDFDF /* SDL_tray.c in Sources */, A7D8ABEB23E2514100DCD162 /* SDL_nullvideo.c in Sources */, @@ -2966,6 +2998,7 @@ A7D8B76423E2514300DCD162 /* SDL_mixer.c in Sources */, A7D8BB5723E2514500DCD162 /* SDL_events.c in Sources */, A7D8ADE623E2514100DCD162 /* SDL_blit_0.c in Sources */, + 89E5801E2D03602200DAF6D3 /* SDL_hidapi_lg4ff.c in Sources */, A7D8B8A823E2514400DCD162 /* SDL_diskaudio.c in Sources */, 56A2373329F9C113003CCA5F /* SDL_sysrwlock.c in Sources */, F3A9AE9A2C8A13C100AAC390 /* SDL_shaders_gpu.c in Sources */, @@ -3029,6 +3062,7 @@ A7D8BA5B23E2514400DCD162 /* SDL_shaders_gles2.c in Sources */, A7D8B14023E2514200DCD162 /* SDL_blit_1.c in Sources */, A7D8BBDB23E2574800DCD162 /* SDL_uikitmetalview.m in Sources */, + F3B6B80A2DC3EA54004954FD /* SDL_hidapi_gip.c in Sources */, A7D8BB1523E2514500DCD162 /* SDL_mouse.c in Sources */, F395C19C2569C68F00942BFF /* SDL_iokitjoystick.c in Sources */, A7D8B4B223E2514300DCD162 /* SDL_sysjoystick.c in Sources */, @@ -3051,9 +3085,11 @@ 000028F8113A53F4333E0000 /* SDL_main_callbacks.c in Sources */, 000098E9DAA43EF6FF7F0000 /* SDL_camera.c in Sources */, F310138E2C1F2CB700FBE946 /* SDL_random.c in Sources */, + F3395BA82D9A5971007246C8 /* SDL_hidapi_8bitdo.c in Sources */, 00001B2471F503DD3C1B0000 /* SDL_camera_dummy.c in Sources */, 00002B20A48E055EB0350000 /* SDL_camera_coremedia.m in Sources */, 000080903BC03006F24E0000 /* SDL_filesystem.c in Sources */, + F3FBB1082DDF93AB0000F99F /* SDL_hidapi_flydigi.c in Sources */, 0000481D255AF155B42C0000 /* SDL_sysfsops.c in Sources */, 0000494CC93F3E624D3C0000 /* SDL_systime.c in Sources */, 000095FA1BDE436CF3AF0000 /* SDL_time.c in Sources */, @@ -3085,8 +3121,8 @@ CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; DEPLOYMENT_POSTPROCESSING = YES; - DYLIB_COMPATIBILITY_VERSION = 201.0.0; - DYLIB_CURRENT_VERSION = 201.10.0; + DYLIB_COMPATIBILITY_VERSION = 301.0.0; + DYLIB_CURRENT_VERSION = 301.0.0; DYLIB_INSTALL_NAME_BASE = "@rpath"; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_ALTIVEC_EXTENSIONS = YES; @@ -3121,7 +3157,7 @@ "@loader_path/Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 10.13; - MARKETING_VERSION = 3.2.10; + MARKETING_VERSION = 3.3.0; OTHER_LDFLAGS = "$(CONFIG_FRAMEWORK_LDFLAGS)"; PRODUCT_BUNDLE_IDENTIFIER = org.libsdl.SDL3; PRODUCT_NAME = SDL3; @@ -3149,8 +3185,8 @@ ALLOW_TARGET_PLATFORM_SPECIALIZATION = YES; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; - DYLIB_COMPATIBILITY_VERSION = 201.0.0; - DYLIB_CURRENT_VERSION = 201.10.0; + DYLIB_COMPATIBILITY_VERSION = 301.0.0; + DYLIB_CURRENT_VERSION = 301.0.0; DYLIB_INSTALL_NAME_BASE = "@rpath"; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; @@ -3182,7 +3218,7 @@ "@loader_path/Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 10.13; - MARKETING_VERSION = 3.2.10; + MARKETING_VERSION = 3.3.0; ONLY_ACTIVE_ARCH = YES; OTHER_LDFLAGS = "$(CONFIG_FRAMEWORK_LDFLAGS)"; PRODUCT_BUNDLE_IDENTIFIER = org.libsdl.SDL3; diff --git a/libs/SDL3/Xcode/SDL/pkg-support/SDL.info b/libs/SDL3/Xcode/SDL/pkg-support/SDL.info index 2ef06d56..196d9560 100644 --- a/libs/SDL3/Xcode/SDL/pkg-support/SDL.info +++ b/libs/SDL3/Xcode/SDL/pkg-support/SDL.info @@ -1,4 +1,4 @@ -Title SDL 3.2.10 +Title SDL 3.3.0 Version 1 Description SDL Library for macOS (http://www.libsdl.org) DefaultLocation /Library/Frameworks diff --git a/libs/SDL3/Xcode/SDL/pkg-support/share/cmake/SDL3/SDL3Config.cmake b/libs/SDL3/Xcode/SDL/pkg-support/share/cmake/SDL3/SDL3Config.cmake index 9d29aae9..b60ef0a7 100644 --- a/libs/SDL3/Xcode/SDL/pkg-support/share/cmake/SDL3/SDL3Config.cmake +++ b/libs/SDL3/Xcode/SDL/pkg-support/share/cmake/SDL3/SDL3Config.cmake @@ -92,7 +92,7 @@ if(NOT TARGET SDL3::Headers) add_library(SDL3::Headers INTERFACE IMPORTED) set_target_properties(SDL3::Headers PROPERTIES - INTERFACE_COMPILE_OPTIONS "SHELL:-F \"${_sdl3_framework_parent_path}\"" + INTERFACE_COMPILE_OPTIONS "-F${_sdl3_framework_parent_path}" ) endif() set(SDL3_Headers_FOUND TRUE) diff --git a/libs/SDL3/android-project/app/proguard-rules.pro b/libs/SDL3/android-project/app/proguard-rules.pro index 1eeb90e2..147cbd37 100644 --- a/libs/SDL3/android-project/app/proguard-rules.pro +++ b/libs/SDL3/android-project/app/proguard-rules.pro @@ -36,7 +36,6 @@ int messageboxShowMessageBox(int, java.lang.String, java.lang.String, int[], int[], java.lang.String[], int[]); void minimizeWindow(); boolean openURL(java.lang.String); - void onNativePen(int, int, int , float , float , float); void requestPermission(java.lang.String, int); boolean showToast(java.lang.String, int, int, int, int); boolean sendMessage(int, int); @@ -51,6 +50,8 @@ boolean supportsRelativeMouse(); int openFileDescriptor(java.lang.String, java.lang.String); boolean showFileDialog(java.lang.String[], boolean, boolean, int); + java.lang.String getPreferredLocales(); + java.lang.String formatLocale(java.util.Locale); } -keep,includedescriptorclasses,allowoptimization class org.libsdl.app.HIDDeviceManager { diff --git a/libs/SDL3/android-project/app/src/main/java/org/libsdl/app/HIDDeviceBLESteamController.java b/libs/SDL3/android-project/app/src/main/java/org/libsdl/app/HIDDeviceBLESteamController.java index d2dc0d23..df4763ac 100644 --- a/libs/SDL3/android-project/app/src/main/java/org/libsdl/app/HIDDeviceBLESteamController.java +++ b/libs/SDL3/android-project/app/src/main/java/org/libsdl/app/HIDDeviceBLESteamController.java @@ -44,9 +44,9 @@ class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDe private static final int CHROMEBOOK_CONNECTION_CHECK_INTERVAL = 10000; - static public final UUID steamControllerService = UUID.fromString("100F6C32-1735-4313-B402-38567131E5F3"); - static public final UUID inputCharacteristic = UUID.fromString("100F6C33-1735-4313-B402-38567131E5F3"); - static public final UUID reportCharacteristic = UUID.fromString("100F6C34-1735-4313-B402-38567131E5F3"); + static final UUID steamControllerService = UUID.fromString("100F6C32-1735-4313-B402-38567131E5F3"); + static final UUID inputCharacteristic = UUID.fromString("100F6C33-1735-4313-B402-38567131E5F3"); + static final UUID reportCharacteristic = UUID.fromString("100F6C34-1735-4313-B402-38567131E5F3"); static private final byte[] enterValveMode = new byte[] { (byte)0xC0, (byte)0x87, 0x03, 0x08, 0x07, 0x00 }; static class GattOperation { @@ -156,7 +156,7 @@ class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDe } } - public HIDDeviceBLESteamController(HIDDeviceManager manager, BluetoothDevice device) { + HIDDeviceBLESteamController(HIDDeviceManager manager, BluetoothDevice device) { mManager = manager; mDevice = device; mDeviceId = mManager.getDeviceIDForIdentifier(getIdentifier()); @@ -169,17 +169,17 @@ class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDe // final HIDDeviceBLESteamController finalThis = this; // mHandler.postDelayed(new Runnable() { // @Override - // public void run() { + // void run() { // finalThis.checkConnectionForChromebookIssue(); // } // }, CHROMEBOOK_CONNECTION_CHECK_INTERVAL); } - public String getIdentifier() { + String getIdentifier() { return String.format("SteamController.%s", mDevice.getAddress()); } - public BluetoothGatt getGatt() { + BluetoothGatt getGatt() { return mGatt; } @@ -219,7 +219,7 @@ class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDe return btManager.getConnectionState(mDevice, BluetoothProfile.GATT); } - public void reconnect() { + void reconnect() { if (getConnectionState() != BluetoothProfile.STATE_CONNECTED) { mGatt.disconnect(); @@ -401,12 +401,12 @@ class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDe queueGattOperation(op); } - public void writeCharacteristic(UUID uuid, byte[] value) { + void writeCharacteristic(UUID uuid, byte[] value) { GattOperation op = HIDDeviceBLESteamController.GattOperation.writeCharacteristic(mGatt, uuid, value); queueGattOperation(op); } - public void readCharacteristic(UUID uuid) { + void readCharacteristic(UUID uuid) { GattOperation op = HIDDeviceBLESteamController.GattOperation.readCharacteristic(mGatt, uuid); queueGattOperation(op); } @@ -415,6 +415,7 @@ class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDe ////////////// BluetoothGattCallback overridden methods ////////////////////////////////////////////////////////////////////////////////////////////////////// + @Override public void onConnectionStateChange(BluetoothGatt g, int status, int newState) { //Log.v(TAG, "onConnectionStateChange status=" + status + " newState=" + newState); mIsReconnecting = false; @@ -437,6 +438,7 @@ class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDe // Disconnection is handled in SteamLink using the ACTION_ACL_DISCONNECTED Intent. } + @Override public void onServicesDiscovered(BluetoothGatt gatt, int status) { //Log.v(TAG, "onServicesDiscovered status=" + status); if (status == 0) { @@ -453,6 +455,7 @@ class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDe } } + @Override public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { //Log.v(TAG, "onCharacteristicRead status=" + status + " uuid=" + characteristic.getUuid()); @@ -463,6 +466,7 @@ class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDe finishCurrentGattOperation(); } + @Override public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { //Log.v(TAG, "onCharacteristicWrite status=" + status + " uuid=" + characteristic.getUuid()); @@ -478,6 +482,7 @@ class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDe finishCurrentGattOperation(); } + @Override public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { // Enable this for verbose logging of controller input reports //Log.v(TAG, "onCharacteristicChanged uuid=" + characteristic.getUuid() + " data=" + HexDump.dumpHexString(characteristic.getValue())); @@ -487,10 +492,12 @@ class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDe } } + @Override public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) { //Log.v(TAG, "onDescriptorRead status=" + status); } + @Override public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) { BluetoothGattCharacteristic chr = descriptor.getCharacteristic(); //Log.v(TAG, "onDescriptorWrite status=" + status + " uuid=" + chr.getUuid() + " descriptor=" + descriptor.getUuid()); @@ -508,14 +515,17 @@ class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDe finishCurrentGattOperation(); } + @Override public void onReliableWriteCompleted(BluetoothGatt gatt, int status) { //Log.v(TAG, "onReliableWriteCompleted status=" + status); } + @Override public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) { //Log.v(TAG, "onReadRemoteRssi status=" + status); } + @Override public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) { //Log.v(TAG, "onMtuChanged status=" + status); } diff --git a/libs/SDL3/android-project/app/src/main/java/org/libsdl/app/HIDDeviceManager.java b/libs/SDL3/android-project/app/src/main/java/org/libsdl/app/HIDDeviceManager.java index 37d80ca0..f7c56c44 100644 --- a/libs/SDL3/android-project/app/src/main/java/org/libsdl/app/HIDDeviceManager.java +++ b/libs/SDL3/android-project/app/src/main/java/org/libsdl/app/HIDDeviceManager.java @@ -32,7 +32,7 @@ public class HIDDeviceManager { private static HIDDeviceManager sManager; private static int sManagerRefCount = 0; - public static HIDDeviceManager acquire(Context context) { + static public HIDDeviceManager acquire(Context context) { if (sManagerRefCount == 0) { sManager = new HIDDeviceManager(context); } @@ -40,7 +40,7 @@ public class HIDDeviceManager { return sManager; } - public static void release(HIDDeviceManager manager) { + static public void release(HIDDeviceManager manager) { if (manager == sManager) { --sManagerRefCount; if (sManagerRefCount == 0) { @@ -121,11 +121,11 @@ public class HIDDeviceManager { } } - public Context getContext() { + Context getContext() { return mContext; } - public int getDeviceIDForIdentifier(String identifier) { + int getDeviceIDForIdentifier(String identifier) { SharedPreferences.Editor spedit = mSharedPreferences.edit(); int result = mSharedPreferences.getInt(identifier, 0); @@ -288,9 +288,13 @@ public class HIDDeviceManager { 0x1532, // Razer Wildcat 0x20d6, // PowerA 0x24c6, // PowerA + 0x294b, // Snakebyte 0x2dc8, // 8BitDo 0x2e24, // Hyperkin + 0x2e95, // SCUF + 0x3285, // Nacon 0x3537, // GameSir + 0x366c, // ByoWave }; if (usbInterface.getId() == 0 && @@ -439,7 +443,7 @@ public class HIDDeviceManager { // Chromebooks do not pass along ACTION_ACL_CONNECTED / ACTION_ACL_DISCONNECTED properly. // This function provides a sort of dummy version of that, watching for changes in the // connected devices and attempting to add controllers as things change. - public void chromebookConnectionHandler() { + void chromebookConnectionHandler() { if (!mIsChromebook) { return; } @@ -478,7 +482,7 @@ public class HIDDeviceManager { }, 10000); } - public boolean connectBluetoothDevice(BluetoothDevice bluetoothDevice) { + boolean connectBluetoothDevice(BluetoothDevice bluetoothDevice) { Log.v(TAG, "connectBluetoothDevice device=" + bluetoothDevice); synchronized (this) { if (mBluetoothDevices.containsKey(bluetoothDevice)) { @@ -499,7 +503,7 @@ public class HIDDeviceManager { return true; } - public void disconnectBluetoothDevice(BluetoothDevice bluetoothDevice) { + void disconnectBluetoothDevice(BluetoothDevice bluetoothDevice) { synchronized (this) { HIDDeviceBLESteamController device = mBluetoothDevices.get(bluetoothDevice); if (device == null) @@ -513,7 +517,7 @@ public class HIDDeviceManager { } } - public boolean isSteamController(BluetoothDevice bluetoothDevice) { + boolean isSteamController(BluetoothDevice bluetoothDevice) { // Sanity check. If you pass in a null device, by definition it is never a Steam Controller. if (bluetoothDevice == null) { return false; @@ -567,7 +571,7 @@ public class HIDDeviceManager { ////////// JNI interface functions ////////////////////////////////////////////////////////////////////////////////////////////////////// - public boolean initialize(boolean usb, boolean bluetooth) { + boolean initialize(boolean usb, boolean bluetooth) { Log.v(TAG, "initialize(" + usb + ", " + bluetooth + ")"); if (usb) { @@ -579,7 +583,7 @@ public class HIDDeviceManager { return true; } - public boolean openDevice(int deviceID) { + boolean openDevice(int deviceID) { Log.v(TAG, "openDevice deviceID=" + deviceID); HIDDevice device = getDevice(deviceID); if (device == null) { @@ -621,7 +625,7 @@ public class HIDDeviceManager { return false; } - public int writeReport(int deviceID, byte[] report, boolean feature) { + int writeReport(int deviceID, byte[] report, boolean feature) { try { //Log.v(TAG, "writeReport deviceID=" + deviceID + " length=" + report.length); HIDDevice device; @@ -638,7 +642,7 @@ public class HIDDeviceManager { return -1; } - public boolean readReport(int deviceID, byte[] report, boolean feature) { + boolean readReport(int deviceID, byte[] report, boolean feature) { try { //Log.v(TAG, "readReport deviceID=" + deviceID); HIDDevice device; @@ -655,7 +659,7 @@ public class HIDDeviceManager { return false; } - public void closeDevice(int deviceID) { + void closeDevice(int deviceID) { try { Log.v(TAG, "closeDevice deviceID=" + deviceID); HIDDevice device; diff --git a/libs/SDL3/android-project/app/src/main/java/org/libsdl/app/HIDDeviceUSB.java b/libs/SDL3/android-project/app/src/main/java/org/libsdl/app/HIDDeviceUSB.java index 27414386..50dbb220 100644 --- a/libs/SDL3/android-project/app/src/main/java/org/libsdl/app/HIDDeviceUSB.java +++ b/libs/SDL3/android-project/app/src/main/java/org/libsdl/app/HIDDeviceUSB.java @@ -30,7 +30,7 @@ class HIDDeviceUSB implements HIDDevice { mRunning = false; } - public String getIdentifier() { + String getIdentifier() { return String.format("%s/%x/%x/%d", mDevice.getDeviceName(), mDevice.getVendorId(), mDevice.getProductId(), mInterfaceIndex); } @@ -100,7 +100,7 @@ class HIDDeviceUSB implements HIDDevice { return mDevice; } - public String getDeviceName() { + String getDeviceName() { return getManufacturerName() + " " + getProductName() + "(0x" + String.format("%x", getVendorId()) + "/0x" + String.format("%x", getProductId()) + ")"; } diff --git a/libs/SDL3/android-project/app/src/main/java/org/libsdl/app/SDL.java b/libs/SDL3/android-project/app/src/main/java/org/libsdl/app/SDL.java index b132fea0..d5d3d69a 100644 --- a/libs/SDL3/android-project/app/src/main/java/org/libsdl/app/SDL.java +++ b/libs/SDL3/android-project/app/src/main/java/org/libsdl/app/SDL.java @@ -12,14 +12,14 @@ public class SDL { // This function should be called first and sets up the native code // so it can call into the Java classes - public static void setupJNI() { + static public void setupJNI() { SDLActivity.nativeSetupJNI(); SDLAudioManager.nativeSetupJNI(); SDLControllerManager.nativeSetupJNI(); } // This function should be called each time the activity is started - public static void initialize() { + static public void initialize() { setContext(null); SDLActivity.initialize(); @@ -28,20 +28,20 @@ public class SDL { } // This function stores the current activity (SDL or not) - public static void setContext(Context context) { + static public void setContext(Context context) { SDLAudioManager.setContext(context); mContext = context; } - public static Context getContext() { + static public Context getContext() { return mContext; } - public static void loadLibrary(String libraryName) throws UnsatisfiedLinkError, SecurityException, NullPointerException { + static void loadLibrary(String libraryName) throws UnsatisfiedLinkError, SecurityException, NullPointerException { loadLibrary(libraryName, mContext); } - public static void loadLibrary(String libraryName, Context context) throws UnsatisfiedLinkError, SecurityException, NullPointerException { + static void loadLibrary(String libraryName, Context context) throws UnsatisfiedLinkError, SecurityException, NullPointerException { if (libraryName == null) { throw new NullPointerException("No library name provided."); diff --git a/libs/SDL3/android-project/app/src/main/java/org/libsdl/app/SDLActivity.java b/libs/SDL3/android-project/app/src/main/java/org/libsdl/app/SDLActivity.java index 6bfcc0bd..278be468 100644 --- a/libs/SDL3/android-project/app/src/main/java/org/libsdl/app/SDLActivity.java +++ b/libs/SDL3/android-project/app/src/main/java/org/libsdl/app/SDLActivity.java @@ -23,6 +23,7 @@ import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Handler; +import android.os.LocaleList; import android.os.Message; import android.os.ParcelFileDescriptor; import android.util.DisplayMetrics; @@ -59,8 +60,8 @@ import java.util.Locale; public class SDLActivity extends Activity implements View.OnSystemUiVisibilityChangeListener { private static final String TAG = "SDL"; private static final int SDL_MAJOR_VERSION = 3; - private static final int SDL_MINOR_VERSION = 2; - private static final int SDL_MICRO_VERSION = 10; + private static final int SDL_MINOR_VERSION = 3; + private static final int SDL_MICRO_VERSION = 0; /* // Display InputType.SOURCE/CLASS of events and devices // @@ -2116,6 +2117,44 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh int requestCode; boolean multipleChoice; } + + /** + * This method is called by SDL using JNI. + */ + public static String getPreferredLocales() { + String result = ""; + if (Build.VERSION.SDK_INT >= 24 /* Android 7 (N) */) { + LocaleList locales = LocaleList.getAdjustedDefault(); + for (int i = 0; i < locales.size(); i++) { + if (i != 0) result += ","; + result += formatLocale(locales.get(i)); + } + } else if (mCurrentLocale != null) { + result = formatLocale(mCurrentLocale); + } + return result; + } + + public static String formatLocale(Locale locale) { + String result = ""; + String lang = ""; + if (locale.getLanguage() == "in") { + // Indonesian is "id" according to ISO 639.2, but on Android is "in" because of Java backwards compatibility + lang = "id"; + } else if (locale.getLanguage() == "") { + // Make sure language is never empty + lang = "und"; + } else { + lang = locale.getLanguage(); + } + + if (locale.getCountry() == "") { + result = lang; + } else { + result = lang + "_" + locale.getCountry(); + } + return result; + } } /** diff --git a/libs/SDL3/android-project/app/src/main/java/org/libsdl/app/SDLAudioManager.java b/libs/SDL3/android-project/app/src/main/java/org/libsdl/app/SDLAudioManager.java index 6ad2f543..36d8a3cd 100644 --- a/libs/SDL3/android-project/app/src/main/java/org/libsdl/app/SDLAudioManager.java +++ b/libs/SDL3/android-project/app/src/main/java/org/libsdl/app/SDLAudioManager.java @@ -10,14 +10,14 @@ import android.util.Log; import java.util.Arrays; import java.util.ArrayList; -public class SDLAudioManager { +class SDLAudioManager { protected static final String TAG = "SDLAudio"; protected static Context mContext; private static AudioDeviceCallback mAudioDeviceCallback; - public static void initialize() { + static void initialize() { mAudioDeviceCallback = null; if(Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) @@ -26,25 +26,25 @@ public class SDLAudioManager { @Override public void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) { for (AudioDeviceInfo deviceInfo : addedDevices) { - addAudioDevice(deviceInfo.isSink(), deviceInfo.getProductName().toString(), deviceInfo.getId()); + nativeAddAudioDevice(deviceInfo.isSink(), deviceInfo.getProductName().toString(), deviceInfo.getId()); } } @Override public void onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices) { for (AudioDeviceInfo deviceInfo : removedDevices) { - removeAudioDevice(deviceInfo.isSink(), deviceInfo.getId()); + nativeRemoveAudioDevice(deviceInfo.isSink(), deviceInfo.getId()); } } }; } } - public static void setContext(Context context) { + static void setContext(Context context) { mContext = context; } - public static void release(Context context) { + static void release(Context context) { // no-op atm } @@ -74,7 +74,7 @@ public class SDLAudioManager { return null; } - public static void registerAudioDeviceCallback() { + static void registerAudioDeviceCallback() { if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) { AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); // get an initial list now, before hotplug callbacks fire. @@ -82,16 +82,16 @@ public class SDLAudioManager { if (dev.getType() == AudioDeviceInfo.TYPE_TELEPHONY) { continue; // Device cannot be opened } - addAudioDevice(dev.isSink(), dev.getProductName().toString(), dev.getId()); + nativeAddAudioDevice(dev.isSink(), dev.getProductName().toString(), dev.getId()); } for (AudioDeviceInfo dev : audioManager.getDevices(AudioManager.GET_DEVICES_INPUTS)) { - addAudioDevice(dev.isSink(), dev.getProductName().toString(), dev.getId()); + nativeAddAudioDevice(dev.isSink(), dev.getProductName().toString(), dev.getId()); } audioManager.registerAudioDeviceCallback(mAudioDeviceCallback, null); } } - public static void unregisterAudioDeviceCallback() { + static void unregisterAudioDeviceCallback() { if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) { AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); audioManager.unregisterAudioDeviceCallback(mAudioDeviceCallback); @@ -99,7 +99,7 @@ public class SDLAudioManager { } /** This method is called by SDL using JNI. */ - public static void audioSetThreadPriority(boolean recording, int device_id) { + static void audioSetThreadPriority(boolean recording, int device_id) { try { /* Set thread name */ @@ -117,10 +117,10 @@ public class SDLAudioManager { } } - public static native int nativeSetupJNI(); + static native int nativeSetupJNI(); - public static native void removeAudioDevice(boolean recording, int deviceId); + static native void nativeRemoveAudioDevice(boolean recording, int deviceId); - public static native void addAudioDevice(boolean recording, String name, int deviceId); + static native void nativeAddAudioDevice(boolean recording, String name, int deviceId); } diff --git a/libs/SDL3/android-project/app/src/main/java/org/libsdl/app/SDLControllerManager.java b/libs/SDL3/android-project/app/src/main/java/org/libsdl/app/SDLControllerManager.java index e1c892e7..7395bd2e 100644 --- a/libs/SDL3/android-project/app/src/main/java/org/libsdl/app/SDLControllerManager.java +++ b/libs/SDL3/android-project/app/src/main/java/org/libsdl/app/SDLControllerManager.java @@ -20,20 +20,20 @@ import android.view.View; public class SDLControllerManager { - public static native int nativeSetupJNI(); + static native int nativeSetupJNI(); - public static native void nativeAddJoystick(int device_id, String name, String desc, + static native void nativeAddJoystick(int device_id, String name, String desc, int vendor_id, int product_id, int button_mask, int naxes, int axis_mask, int nhats, boolean can_rumble); - public static native void nativeRemoveJoystick(int device_id); - public static native void nativeAddHaptic(int device_id, String name); - public static native void nativeRemoveHaptic(int device_id); - public static native boolean onNativePadDown(int device_id, int keycode); - public static native boolean onNativePadUp(int device_id, int keycode); - public static native void onNativeJoy(int device_id, int axis, + static native void nativeRemoveJoystick(int device_id); + static native void nativeAddHaptic(int device_id, String name); + static native void nativeRemoveHaptic(int device_id); + static public native boolean onNativePadDown(int device_id, int keycode); + static public native boolean onNativePadUp(int device_id, int keycode); + static native void onNativeJoy(int device_id, int axis, float value); - public static native void onNativeHat(int device_id, int hat_id, + static native void onNativeHat(int device_id, int hat_id, int x, int y); protected static SDLJoystickHandler mJoystickHandler; @@ -41,7 +41,7 @@ public class SDLControllerManager private static final String TAG = "SDLControllerManager"; - public static void initialize() { + static void initialize() { if (mJoystickHandler == null) { if (Build.VERSION.SDK_INT >= 19 /* Android 4.4 (KITKAT) */) { mJoystickHandler = new SDLJoystickHandler_API19(); @@ -62,48 +62,48 @@ public class SDLControllerManager } // Joystick glue code, just a series of stubs that redirect to the SDLJoystickHandler instance - public static boolean handleJoystickMotionEvent(MotionEvent event) { + static public boolean handleJoystickMotionEvent(MotionEvent event) { return mJoystickHandler.handleMotionEvent(event); } /** * This method is called by SDL using JNI. */ - public static void pollInputDevices() { + static void pollInputDevices() { mJoystickHandler.pollInputDevices(); } /** * This method is called by SDL using JNI. */ - public static void pollHapticDevices() { + static void pollHapticDevices() { mHapticHandler.pollHapticDevices(); } /** * This method is called by SDL using JNI. */ - public static void hapticRun(int device_id, float intensity, int length) { + static void hapticRun(int device_id, float intensity, int length) { mHapticHandler.run(device_id, intensity, length); } /** * This method is called by SDL using JNI. */ - public static void hapticRumble(int device_id, float low_frequency_intensity, float high_frequency_intensity, int length) { + static void hapticRumble(int device_id, float low_frequency_intensity, float high_frequency_intensity, int length) { mHapticHandler.rumble(device_id, low_frequency_intensity, high_frequency_intensity, length); } /** * This method is called by SDL using JNI. */ - public static void hapticStop(int device_id) + static void hapticStop(int device_id) { mHapticHandler.stop(device_id); } // Check if a given device is considered a possible SDL joystick - public static boolean isDeviceSDLJoystick(int deviceId) { + static public boolean isDeviceSDLJoystick(int deviceId) { InputDevice device = InputDevice.getDevice(deviceId); // We cannot use InputDevice.isVirtual before API 16, so let's accept // only nonnegative device ids (VIRTUAL_KEYBOARD equals -1) @@ -140,14 +140,14 @@ class SDLJoystickHandler { * @param event the event to be handled. * @return if given event was processed. */ - public boolean handleMotionEvent(MotionEvent event) { + boolean handleMotionEvent(MotionEvent event) { return false; } /** * Handles adding and removing of input devices. */ - public void pollInputDevices() { + void pollInputDevices() { } } @@ -155,11 +155,11 @@ class SDLJoystickHandler { class SDLJoystickHandler_API16 extends SDLJoystickHandler { static class SDLJoystick { - public int device_id; - public String name; - public String desc; - public ArrayList axes; - public ArrayList hats; + int device_id; + String name; + String desc; + ArrayList axes; + ArrayList hats; } static class RangeComparator implements Comparator { @Override @@ -210,13 +210,13 @@ class SDLJoystickHandler_API16 extends SDLJoystickHandler { private final ArrayList mJoysticks; - public SDLJoystickHandler_API16() { + SDLJoystickHandler_API16() { mJoysticks = new ArrayList(); } @Override - public void pollInputDevices() { + void pollInputDevices() { int[] deviceIds = InputDevice.getDeviceIds(); for (int device_id : deviceIds) { @@ -299,7 +299,7 @@ class SDLJoystickHandler_API16 extends SDLJoystickHandler { } @Override - public boolean handleMotionEvent(MotionEvent event) { + boolean handleMotionEvent(MotionEvent event) { int actionPointerIndex = event.getActionIndex(); int action = event.getActionMasked(); if (action == MotionEvent.ACTION_MOVE) { @@ -321,7 +321,7 @@ class SDLJoystickHandler_API16 extends SDLJoystickHandler { return true; } - public String getJoystickDescriptor(InputDevice joystickDevice) { + String getJoystickDescriptor(InputDevice joystickDevice) { String desc = joystickDevice.getDescriptor(); if (desc != null && !desc.isEmpty()) { @@ -330,16 +330,16 @@ class SDLJoystickHandler_API16 extends SDLJoystickHandler { return joystickDevice.getName(); } - public int getProductId(InputDevice joystickDevice) { + int getProductId(InputDevice joystickDevice) { return 0; } - public int getVendorId(InputDevice joystickDevice) { + int getVendorId(InputDevice joystickDevice) { return 0; } - public int getAxisMask(List ranges) { + int getAxisMask(List ranges) { return -1; } - public int getButtonMask(InputDevice joystickDevice) { + int getButtonMask(InputDevice joystickDevice) { return -1; } } @@ -347,17 +347,17 @@ class SDLJoystickHandler_API16 extends SDLJoystickHandler { class SDLJoystickHandler_API19 extends SDLJoystickHandler_API16 { @Override - public int getProductId(InputDevice joystickDevice) { + int getProductId(InputDevice joystickDevice) { return joystickDevice.getProductId(); } @Override - public int getVendorId(InputDevice joystickDevice) { + int getVendorId(InputDevice joystickDevice) { return joystickDevice.getVendorId(); } @Override - public int getAxisMask(List ranges) { + int getAxisMask(List ranges) { // For compatibility, keep computing the axis mask like before, // only really distinguishing 2, 4 and 6 axes. int axis_mask = 0; @@ -394,7 +394,7 @@ class SDLJoystickHandler_API19 extends SDLJoystickHandler_API16 { } @Override - public int getButtonMask(InputDevice joystickDevice) { + int getButtonMask(InputDevice joystickDevice) { int button_mask = 0; int[] keys = new int[] { KeyEvent.KEYCODE_BUTTON_A, @@ -491,7 +491,7 @@ class SDLJoystickHandler_API19 extends SDLJoystickHandler_API16 { class SDLHapticHandler_API31 extends SDLHapticHandler { @Override - public void run(int device_id, float intensity, int length) { + void run(int device_id, float intensity, int length) { SDLHaptic haptic = getHaptic(device_id); if (haptic != null) { vibrate(haptic.vib, intensity, length); @@ -499,7 +499,7 @@ class SDLHapticHandler_API31 extends SDLHapticHandler { } @Override - public void rumble(int device_id, float low_frequency_intensity, float high_frequency_intensity, int length) { + void rumble(int device_id, float low_frequency_intensity, float high_frequency_intensity, int length) { InputDevice device = InputDevice.getDevice(device_id); if (device == null) { return; @@ -543,7 +543,7 @@ class SDLHapticHandler_API31 extends SDLHapticHandler { class SDLHapticHandler_API26 extends SDLHapticHandler { @Override - public void run(int device_id, float intensity, int length) { + void run(int device_id, float intensity, int length) { SDLHaptic haptic = getHaptic(device_id); if (haptic != null) { if (intensity == 0.0f) { @@ -575,36 +575,36 @@ class SDLHapticHandler_API26 extends SDLHapticHandler { class SDLHapticHandler { static class SDLHaptic { - public int device_id; - public String name; - public Vibrator vib; + int device_id; + String name; + Vibrator vib; } private final ArrayList mHaptics; - public SDLHapticHandler() { + SDLHapticHandler() { mHaptics = new ArrayList(); } - public void run(int device_id, float intensity, int length) { + void run(int device_id, float intensity, int length) { SDLHaptic haptic = getHaptic(device_id); if (haptic != null) { haptic.vib.vibrate(length); } } - public void rumble(int device_id, float low_frequency_intensity, float high_frequency_intensity, int length) { + void rumble(int device_id, float low_frequency_intensity, float high_frequency_intensity, int length) { // Not supported in older APIs } - public void stop(int device_id) { + void stop(int device_id) { SDLHaptic haptic = getHaptic(device_id); if (haptic != null) { haptic.vib.cancel(); } } - public void pollHapticDevices() { + void pollHapticDevices() { final int deviceId_VIBRATOR_SERVICE = 999999; boolean hasVibratorService = false; @@ -724,31 +724,31 @@ class SDLGenericMotionListener_API14 implements View.OnGenericMotionListener { return consumed; } - public boolean supportsRelativeMouse() { + boolean supportsRelativeMouse() { return false; } - public boolean inRelativeMode() { + boolean inRelativeMode() { return false; } - public boolean setRelativeMouseEnabled(boolean enabled) { + boolean setRelativeMouseEnabled(boolean enabled) { return false; } - public void reclaimRelativeMouseModeIfNeeded() { + void reclaimRelativeMouseModeIfNeeded() { } - public boolean checkRelativeEvent(MotionEvent event) { + boolean checkRelativeEvent(MotionEvent event) { return inRelativeMode(); } - public float getEventX(MotionEvent event, int pointerIndex) { + float getEventX(MotionEvent event, int pointerIndex) { return event.getX(pointerIndex); } - public float getEventY(MotionEvent event, int pointerIndex) { + float getEventY(MotionEvent event, int pointerIndex) { return event.getY(pointerIndex); } @@ -760,23 +760,23 @@ class SDLGenericMotionListener_API24 extends SDLGenericMotionListener_API14 { private boolean mRelativeModeEnabled; @Override - public boolean supportsRelativeMouse() { + boolean supportsRelativeMouse() { return true; } @Override - public boolean inRelativeMode() { + boolean inRelativeMode() { return mRelativeModeEnabled; } @Override - public boolean setRelativeMouseEnabled(boolean enabled) { + boolean setRelativeMouseEnabled(boolean enabled) { mRelativeModeEnabled = enabled; return true; } @Override - public float getEventX(MotionEvent event, int pointerIndex) { + float getEventX(MotionEvent event, int pointerIndex) { if (mRelativeModeEnabled && event.getToolType(pointerIndex) == MotionEvent.TOOL_TYPE_MOUSE) { return event.getAxisValue(MotionEvent.AXIS_RELATIVE_X, pointerIndex); } else { @@ -785,7 +785,7 @@ class SDLGenericMotionListener_API24 extends SDLGenericMotionListener_API14 { } @Override - public float getEventY(MotionEvent event, int pointerIndex) { + float getEventY(MotionEvent event, int pointerIndex) { if (mRelativeModeEnabled && event.getToolType(pointerIndex) == MotionEvent.TOOL_TYPE_MOUSE) { return event.getAxisValue(MotionEvent.AXIS_RELATIVE_Y, pointerIndex); } else { @@ -799,17 +799,17 @@ class SDLGenericMotionListener_API26 extends SDLGenericMotionListener_API24 { private boolean mRelativeModeEnabled; @Override - public boolean supportsRelativeMouse() { + boolean supportsRelativeMouse() { return (!SDLActivity.isDeXMode() || Build.VERSION.SDK_INT >= 27 /* Android 8.1 (O_MR1) */); } @Override - public boolean inRelativeMode() { + boolean inRelativeMode() { return mRelativeModeEnabled; } @Override - public boolean setRelativeMouseEnabled(boolean enabled) { + boolean setRelativeMouseEnabled(boolean enabled) { if (!SDLActivity.isDeXMode() || Build.VERSION.SDK_INT >= 27 /* Android 8.1 (O_MR1) */) { if (enabled) { SDLActivity.getContentView().requestPointerCapture(); @@ -824,25 +824,25 @@ class SDLGenericMotionListener_API26 extends SDLGenericMotionListener_API24 { } @Override - public void reclaimRelativeMouseModeIfNeeded() { + void reclaimRelativeMouseModeIfNeeded() { if (mRelativeModeEnabled && !SDLActivity.isDeXMode()) { SDLActivity.getContentView().requestPointerCapture(); } } @Override - public boolean checkRelativeEvent(MotionEvent event) { + boolean checkRelativeEvent(MotionEvent event) { return event.getSource() == InputDevice.SOURCE_MOUSE_RELATIVE; } @Override - public float getEventX(MotionEvent event, int pointerIndex) { + float getEventX(MotionEvent event, int pointerIndex) { // Relative mouse in capture mode will only have relative for X/Y return event.getX(pointerIndex); } @Override - public float getEventY(MotionEvent event, int pointerIndex) { + float getEventY(MotionEvent event, int pointerIndex) { // Relative mouse in capture mode will only have relative for X/Y return event.getY(pointerIndex); } diff --git a/libs/SDL3/android-project/app/src/main/java/org/libsdl/app/SDLDummyEdit.java b/libs/SDL3/android-project/app/src/main/java/org/libsdl/app/SDLDummyEdit.java index 40e556ff..61d8fe85 100644 --- a/libs/SDL3/android-project/app/src/main/java/org/libsdl/app/SDLDummyEdit.java +++ b/libs/SDL3/android-project/app/src/main/java/org/libsdl/app/SDLDummyEdit.java @@ -14,14 +14,14 @@ public class SDLDummyEdit extends View implements View.OnKeyListener InputConnection ic; int input_type; - public SDLDummyEdit(Context context) { + SDLDummyEdit(Context context) { super(context); setFocusableInTouchMode(true); setFocusable(true); setOnKeyListener(this); } - public void setInputType(int input_type) { + void setInputType(int input_type) { this.input_type = input_type; } diff --git a/libs/SDL3/android-project/app/src/main/java/org/libsdl/app/SDLInputConnection.java b/libs/SDL3/android-project/app/src/main/java/org/libsdl/app/SDLInputConnection.java index accce4ba..027b8fd3 100644 --- a/libs/SDL3/android-project/app/src/main/java/org/libsdl/app/SDLInputConnection.java +++ b/libs/SDL3/android-project/app/src/main/java/org/libsdl/app/SDLInputConnection.java @@ -7,12 +7,12 @@ import android.view.*; import android.view.inputmethod.BaseInputConnection; import android.widget.EditText; -public class SDLInputConnection extends BaseInputConnection +class SDLInputConnection extends BaseInputConnection { protected EditText mEditText; protected String mCommittedText = ""; - public SDLInputConnection(View targetView, boolean fullEditor) { + SDLInputConnection(View targetView, boolean fullEditor) { super(targetView, fullEditor); mEditText = new EditText(SDL.getContext()); } diff --git a/libs/SDL3/android-project/app/src/main/java/org/libsdl/app/SDLSurface.java b/libs/SDL3/android-project/app/src/main/java/org/libsdl/app/SDLSurface.java index 080501c9..42b4f362 100644 --- a/libs/SDL3/android-project/app/src/main/java/org/libsdl/app/SDLSurface.java +++ b/libs/SDL3/android-project/app/src/main/java/org/libsdl/app/SDLSurface.java @@ -40,10 +40,10 @@ public class SDLSurface extends SurfaceView implements SurfaceHolder.Callback, protected float mWidth, mHeight; // Is SurfaceView ready for rendering - public boolean mIsSurfaceReady; + protected boolean mIsSurfaceReady; // Startup - public SDLSurface(Context context) { + protected SDLSurface(Context context) { super(context); getHolder().addCallback(this); @@ -66,11 +66,11 @@ public class SDLSurface extends SurfaceView implements SurfaceHolder.Callback, mIsSurfaceReady = false; } - public void handlePause() { + protected void handlePause() { enableSensor(Sensor.TYPE_ACCELEROMETER, false); } - public void handleResume() { + protected void handleResume() { setFocusable(true); setFocusableInTouchMode(true); requestFocus(); @@ -80,7 +80,7 @@ public class SDLSurface extends SurfaceView implements SurfaceHolder.Callback, enableSensor(Sensor.TYPE_ACCELEROMETER, true); } - public Surface getNativeSurface() { + protected Surface getNativeSurface() { return getHolder().getSurface(); } @@ -299,7 +299,7 @@ public class SDLSurface extends SurfaceView implements SurfaceHolder.Callback, } // Sensor events - public void enableSensor(int sensortype, boolean enabled) { + protected void enableSensor(int sensortype, boolean enabled) { // TODO: This uses getDefaultSensor - what if we have >1 accels? if (enabled) { mSensorManager.registerListener(this, @@ -363,6 +363,7 @@ public class SDLSurface extends SurfaceView implements SurfaceHolder.Callback, } // Captured pointer events for API 26. + @Override public boolean onCapturedPointerEvent(MotionEvent event) { int action = event.getActionMasked(); @@ -401,7 +402,7 @@ public class SDLSurface extends SurfaceView implements SurfaceHolder.Callback, SDLActivity.onNativeMouse(button, action, x, y, true); return true; } - } + } return false; } diff --git a/libs/SDL3/build-scripts/build-release.py b/libs/SDL3/build-scripts/build-release.py index f3faa06c..c00743e9 100644 --- a/libs/SDL3/build-scripts/build-release.py +++ b/libs/SDL3/build-scripts/build-release.py @@ -1023,8 +1023,9 @@ class Releaser: "cmake", "-S", str(self.root), "-B", str(build_dir), - f'''-DCMAKE_C_FLAGS="-ffile-prefix-map={self.root}=/src/{self.project}"''', - f'''-DCMAKE_CXX_FLAGS="-ffile-prefix-map={self.root}=/src/{self.project}"''', + # NDK 21e does not support -ffile-prefix-map + # f'''-DCMAKE_C_FLAGS="-ffile-prefix-map={self.root}=/src/{self.project}"''', + # f'''-DCMAKE_CXX_FLAGS="-ffile-prefix-map={self.root}=/src/{self.project}"''', f"-DCMAKE_TOOLCHAIN_FILE={cmake_toolchain_file}", f"-DCMAKE_PREFIX_PATH={str(android_deps_path)}", f"-DCMAKE_FIND_ROOT_PATH_MODE_PACKAGE=BOTH", @@ -1530,7 +1531,7 @@ def main(argv=None) -> int: parser.error("Invalid --android-api, and/or could not be detected") android_api_path = Path(args.android_home) / f"platforms/{args.android_api.name}" if not android_api_path.is_dir(): - parser.error(f"Android API directory does not exist ({android_api_path})") + logger.warning(f"Android API directory does not exist ({android_api_path})") with section_printer.group("Android arguments"): print(f"android_home = {args.android_home}") print(f"android_ndk_home = {args.android_ndk_home}") diff --git a/libs/SDL3/build-scripts/wikiheaders.pl b/libs/SDL3/build-scripts/wikiheaders.pl index 98591493..c2732af5 100644 --- a/libs/SDL3/build-scripts/wikiheaders.pl +++ b/libs/SDL3/build-scripts/wikiheaders.pl @@ -32,6 +32,7 @@ my $wikisubdir = ''; my $incsubdir = 'include'; my $readmesubdir = undef; my $apiprefixregex = undef; +my $apipropertyregex = undef; my $versionfname = 'include/SDL_version.h'; my $versionmajorregex = '\A\#define\s+SDL_MAJOR_VERSION\s+(\d+)\Z'; my $versionminorregex = '\A\#define\s+SDL_MINOR_VERSION\s+(\d+)\Z'; @@ -43,7 +44,6 @@ my $wikiurl = 'https://wiki.libsdl.org'; my $bugreporturl = 'https://github.com/libsdl-org/sdlwiki/issues/new'; my $srcpath = undef; my $wikipath = undef; -my $wikireadmesubdir = 'README'; my $warn_about_missing = 0; my $copy_direction = 0; my $optionsfname = undef; @@ -111,6 +111,7 @@ if (defined $optionsfname) { $srcpath = $val, next if $key eq 'srcpath'; $wikipath = $val, next if $key eq 'wikipath'; $apiprefixregex = $val, next if $key eq 'apiprefixregex'; + $apipropertyregex = $val, next if $key eq 'apipropertyregex'; $projectfullname = $val, next if $key eq 'projectfullname'; $projectshortname = $val, next if $key eq 'projectshortname'; $wikisubdir = $val, next if $key eq 'wikisubdir'; @@ -424,7 +425,11 @@ sub dewikify_chunk { $str .= "\n```$codelang\n$code\n```\n"; } } elsif ($dewikify_mode eq 'manpage') { - $str =~ s/\./\\[char46]/gms; # make sure these can't become control codes. + # make sure these can't become part of roff syntax. + $str =~ s/\./\\[char46]/gms; + $str =~ s/"/\\(dq/gms; + $str =~ s/'/\\(aq/gms; + if ($wikitype eq 'mediawiki') { # Dump obvious wikilinks. if (defined $apiprefixregex) { @@ -449,33 +454,52 @@ sub dewikify_chunk { # bullets $str =~ s/^\* /\n\\\(bu /gm; } elsif ($wikitype eq 'md') { + # bullets + $str =~ s/^\- /\n\\(bu /gm; + # merge paragraphs + $str =~ s/^[ \t]+//gm; + $str =~ s/([^\-\n])\n([^\-\n])/$1 $2/g; + $str =~ s/\n\n/\n.PP\n/g; + # Dump obvious wikilinks. if (defined $apiprefixregex) { - $str =~ s/\[(\`?$apiprefixregex[a-zA-Z0-9_]+\`?)\]\($apiprefixregex[a-zA-Z0-9_]+\)/\n.BR $1\n/gms; + my $apr = $apiprefixregex; + if(!($apr =~ /\A\(.*\)\Z/s)) { + # we're relying on the apiprefixregex having a capturing group. + $apr = "(" . $apr . ")"; + } + $str =~ s/(\S*?)\[\`?($apr[a-zA-Z0-9_]+)\`?\]\($apr[a-zA-Z0-9_]+\)(\S*)\s*/\n.BR "" "$1" "$2" "$5"\n/gm; + # handle cases like "[x](x), [y](y), [z](z)" being separated. + while($str =~ s/(\.BR[^\n]*)\n\n\.BR/$1\n.BR/gm) {} } # links $str =~ s/\[(.*?)]\((https?\:\/\/.*?)\)/\n.URL "$2" "$1"\n/g; # is also popular. :/ - $str =~ s/\s*\`(.*?)\`\s*/\n.BR $1\n/gms; + $str =~ s/\s*(\S*?)\`([^\n]*?)\`(\S*)\s*/\n.BR "" "$1" "$2" "$3"\n/gms; # bold+italic (this looks bad, just make it bold). - $str =~ s/\s*\*\*\*(.*?)\*\*\*\s*/\n.B $1\n/gms; + $str =~ s/\s*(\S*?)\*\*\*([^\n]*?)\*\*\*(\S*)\s*/\n.BR "" "$1" "$2" "$3"\n/gms; # bold - $str =~ s/\s*\*\*(.*?)\*\*\s*/\n.B $1\n/gms; + $str =~ s/\s*(\S*?)\*\*([^\n]*?)\*\*(\S*)\s*/\n.BR "" "$1" "$2" "$3"\n/gms; # italic - $str =~ s/\s*\*(.*?)\*\s*/\n.I $1\n/gms; - - # bullets - $str =~ s/^\- /\n\\\(bu /gm; + $str =~ s/\s*(\S*?)\*([^\n]*?)\*(\S*)\s*/\n.IR "" "$1" "$2" "$3"\n/gms; } + # cleanup unnecessary quotes + $str =~ s/(\.[IB]R?)(.*?) ""\n/$1$2\n/gm; + $str =~ s/(\.[IB]R?) "" ""(.*?)\n/$1$2\n/gm; + $str =~ s/"(\S+)"/$1/gm; + # cleanup unnecessary whitespace + $str =~ s/ +\n/\n/gm; + if (defined $code) { $code =~ s/\A\n+//gms; $code =~ s/\n+\Z//gms; + $code =~ s/\\/\\(rs/gms; if ($dewikify_manpage_code_indent) { $str .= "\n.IP\n" } else { @@ -580,7 +604,7 @@ sub dewikify { $retval .= dewikify_chunk($wikitype, $1, $2, $3); } } elsif ($wikitype eq 'md') { - while ($str =~ s/\A(.*?)\n```(.*?)\n(.*?)\n```\n//ms) { + while ($str =~ s/\A(.*?)\n?```(.*?)\n(.*?)\n```\n//ms) { $retval .= dewikify_chunk($wikitype, $1, $2, $3); } } @@ -803,21 +827,23 @@ sub print_big_ascii_string { die("Don't have a big ascii entry for '$ch'!\n") if not defined $rowsref; my $row = @$rowsref[$rownum]; + my $outstr = ''; if ($lowascii) { my @x = split //, $row; foreach (@x) { - my $v = ($_ eq "\x{2588}") ? 'X' : ' '; - print $fh $v; + $outstr .= ($_ eq "\x{2588}") ? 'X' : ' '; } } else { - print $fh $row; + $outstr = $row; } $charidx++; - - if ($charidx < $charcount) { - print $fh " "; + if ($charidx == $charcount) { + $outstr =~ s/\s*\Z//; # dump extra spaces at the end of the line. + } else { + $outstr .= ' '; # space between glyphs. } + print $fh $outstr; } print $fh "\n"; } @@ -1011,7 +1037,6 @@ sub generate_quickref { my $incpath = "$srcpath"; $incpath .= "/$incsubdir" if $incsubdir ne ''; -my $wikireadmepath = "$wikipath/$wikireadmesubdir"; my $readmepath = undef; if (defined $readmesubdir) { $readmepath = "$srcpath/$readmesubdir"; @@ -1343,7 +1368,7 @@ while (my $d = readdir(DH)) { # update strings now that we know everything pending is to be applied to this declaration. Add pending blank lines and the new text. # At Sam's request, don't list property defines with functions. (See #9440) - my $is_property = /\A\s*\#\s*define\s+SDL_PROP_/; + my $is_property = (defined $apipropertyregex) ? /$apipropertyregex/ : 0; if (!$is_property) { if ($blank_lines > 0) { while ($blank_lines > 0) { @@ -2060,18 +2085,15 @@ if ($copy_direction == 1) { # --copy-to-headers } if (defined $readmepath) { - if ( -d $wikireadmepath ) { - mkdir($readmepath); # just in case - opendir(DH, $wikireadmepath) or die("Can't opendir '$wikireadmepath': $!\n"); - while (readdir(DH)) { - my $dent = $_; - if ($dent =~ /\A(.*?)\.md\Z/) { # we only bridge Markdown files here. - next if $1 eq 'FrontPage'; - filecopy("$wikireadmepath/$dent", "$readmepath/README-$dent", "\n"); - } + mkdir($readmepath); # just in case + opendir(DH, $wikipath) or die("Can't opendir '$wikipath': $!\n"); + while (readdir(DH)) { + my $dent = $_; + if ($dent =~ /\AREADME\-.*?\.md\Z/) { # we only bridge Markdown files here that start with "README-". + filecopy("$wikipath/$dent", "$readmepath/$dent", "\n"); } - closedir(DH); } + closedir(DH); } } elsif ($copy_direction == -1) { # --copy-to-wiki @@ -2676,31 +2698,27 @@ __EOF__ # Write out READMEs... if (defined $readmepath) { if ( -d $readmepath ) { - mkdir($wikireadmepath); # just in case + mkdir($wikipath); # just in case opendir(DH, $readmepath) or die("Can't opendir '$readmepath': $!\n"); while (my $d = readdir(DH)) { my $dent = $d; - if ($dent =~ /\AREADME\-(.*?\.md)\Z/) { # we only bridge Markdown files here. - my $wikifname = $1; - next if $wikifname eq 'FrontPage.md'; - filecopy("$readmepath/$dent", "$wikireadmepath/$wikifname", "\n"); + if ($dent =~ /\AREADME\-.*?\.md\Z/) { # we only bridge Markdown files here that start with "README-". + filecopy("$readmepath/$dent", "$wikipath/$dent", "\n"); } } closedir(DH); my @pages = (); - opendir(DH, $wikireadmepath) or die("Can't opendir '$wikireadmepath': $!\n"); + opendir(DH, $wikipath) or die("Can't opendir '$wikipath': $!\n"); while (my $d = readdir(DH)) { my $dent = $d; - if ($dent =~ /\A(.*?)\.(mediawiki|md)\Z/) { - my $wikiname = $1; - next if $wikiname eq 'FrontPage'; - push @pages, $wikiname; + if ($dent =~ /\A(README\-.*?)\.md\Z/) { + push @pages, $1; } } closedir(DH); - open(FH, '>', "$wikireadmepath/FrontPage.md") or die("Can't open '$wikireadmepath/FrontPage.md': $!\n"); + open(FH, '>', "$wikipath/READMEs.md") or die("Can't open '$wikipath/READMEs.md': $!\n"); print FH "# All READMEs available here\n\n"; foreach (sort @pages) { my $wikiname = $_; @@ -2765,7 +2783,6 @@ __EOF__ my $wikitype = $wikitypes{$sym}; my $sectionsref = $wikisyms{$sym}; my $remarks = $sectionsref->{'Remarks'}; - my $params = $sectionsref->{'Function Parameters'}; my $returns = $sectionsref->{'Return Value'}; my $version = $sectionsref->{'Version'}; my $threadsafety = $sectionsref->{'Thread Safety'}; @@ -2773,6 +2790,23 @@ __EOF__ my $examples = $sectionsref->{'Code Examples'}; my $deprecated = $sectionsref->{'Deprecated'}; my $headerfile = $manpageheaderfiletext; + + my $params = undef; + + if ($symtype == -1) { # category documentation block. + # nothing to be done here. + } elsif (($symtype == 1) || (($symtype == 5))) { # we'll assume a typedef (5) with a \param is a function pointer typedef. + $params = $sectionsref->{'Function Parameters'}; + } elsif ($symtype == 2) { + $params = $sectionsref->{'Macro Parameters'}; + } elsif ($symtype == 3) { + $params = $sectionsref->{'Fields'}; + } elsif ($symtype == 4) { + $params = $sectionsref->{'Values'}; + } else { + die("Unexpected symtype $symtype"); + } + $headerfile =~ s/\%fname\%/$headersymslocation{$sym}/g; $headerfile .= "\n"; @@ -2839,18 +2873,22 @@ __EOF__ $str .= dewikify($wikitype, $deprecated) . "\n"; } + my $incfile = $mainincludefname; if (defined $headerfile) { - $str .= ".SH HEADER FILE\n"; - $str .= dewikify($wikitype, $headerfile) . "\n"; + if($headerfile =~ /Defined in (.*)/) { + $incfile = $1; + } } $str .= ".SH SYNOPSIS\n"; $str .= ".nf\n"; - $str .= ".B #include \\(dq$mainincludefname\\(dq\n"; + $str .= ".B #include <$incfile>\n"; $str .= ".PP\n"; my @decllines = split /\n/, $decl; foreach (@decllines) { + $_ =~ s/\\/\\(rs/g; # fix multiline macro defs + $_ =~ s/"/\\(dq/g; $str .= ".BI \"$_\n"; } $str .= ".fi\n"; @@ -2938,8 +2976,13 @@ __EOF__ } if (defined $returns) { + # Check for md link in return type: ([SDL_Renderer](SDL_Renderer) *) + # This would've prevented the next regex from working properly (it'd leave " *)") + $returns =~ s/\A\(\[.*?\]\((.*?)\)/\($1/ms; + # Chop datatype in parentheses off the front. + $returns =~ s/\A\(.*?\) //; + $returns = dewikify($wikitype, $returns); - $returns =~ s/\A\(.*?\)\s*//; # Chop datatype in parentheses off the front. $str .= ".SH RETURN VALUE\n"; $str .= "$returns\n"; } @@ -2975,6 +3018,8 @@ __EOF__ s/\A\/*//; s/\A\.BR\s+//; # dewikify added this, but we want to handle it. s/\A\.I\s+//; # dewikify added this, but we want to handle it. + s/\A\.PP\s*//; # dewikify added this, but we want to handle it. + s/\\\(bu//; # dewikify added this, but we want to handle it. s/\A\s*[\:\*\-]\s*//; s/\A\s+//; s/\s+\Z//; diff --git a/libs/SDL3/cmake/PreseedMSVCCache.cmake b/libs/SDL3/cmake/PreseedMSVCCache.cmake index 17495aa4..23025eb0 100644 --- a/libs/SDL3/cmake/PreseedMSVCCache.cmake +++ b/libs/SDL3/cmake/PreseedMSVCCache.cmake @@ -15,6 +15,7 @@ if(MSVC) set(HAVE_MMDEVICEAPI_H "1" CACHE INTERNAL "Have include mmdeviceapi.h") set(HAVE_SENSORSAPI_H "1" CACHE INTERNAL "Have include sensorsapi.h") set(HAVE_SHELLSCALINGAPI_H "1" CACHE INTERNAL "Have include shellscalingapi.h") + set(HAVE_SHOBJIDL_CORE_H "1" CACHE INTERNAL "Have include shobjidl_core.h") set(HAVE_TPCSHRD_H "1" CACHE INTERNAL "Have include tpcshrd.h") set(HAVE_WIN32_CC "1" CACHE INTERNAL "Test HAVE_WIN32_CC") set(HAVE_XINPUT_H "1" CACHE INTERNAL "Test HAVE_XINPUT_H") diff --git a/libs/SDL3/cmake/PreseedNokiaNGageCache.cmake b/libs/SDL3/cmake/PreseedNokiaNGageCache.cmake new file mode 100644 index 00000000..298177ba --- /dev/null +++ b/libs/SDL3/cmake/PreseedNokiaNGageCache.cmake @@ -0,0 +1,189 @@ +if(NGAGESDK) + function(SDL_Preseed_CMakeCache) + set(COMPILER_SUPPORTS_ARMNEON "" CACHE INTERNAL "Test COMPILER_SUPPORTS_ARMNEON") + set(COMPILER_SUPPORTS_FDIAGNOSTICS_COLOR_ALWAYS "" CACHE INTERNAL "Test COMPILER_SUPPORTS_FDIAGNOSTICS_COLOR_ALWAYS") + set(COMPILER_SUPPORTS_SYNC_LOCK_TEST_AND_SET "" CACHE INTERNAL "Test COMPILER_SUPPORTS_SYNC_LOCK_TEST_AND_SET") + set(HAVE_CLANG_COMMENT_BLOCK_COMMANDS "" CACHE INTERNAL "Test HAVE_CLANG_COMMENT_BLOCK_COMMANDS") + set(HAVE_ALLOCA_H "" CACHE INTERNAL "Have include alloca.h") + set(HAVE_LIBM "1" CACHE INTERNAL "Have library m") + set(HAVE_POSIX_SPAWN "" CACHE INTERNAL "Have symbol posix_spawn") + set(HAVE_MALLOC "1" CACHE INTERNAL "Have include malloc.h") + set(LIBC_HAS_ABS "1" CACHE INTERNAL "Have symbol abs") + set(LIBC_HAS_ACOS "1" CACHE INTERNAL "Have symbol acos") + set(LIBC_HAS_ACOSF "" CACHE INTERNAL "Have symbol acosf") + set(LIBC_HAS_ASIN "1" CACHE INTERNAL "Have symbol asin") + set(LIBC_HAS_ASINF "" CACHE INTERNAL "Have symbol asinf") + set(LIBC_HAS_ATAN "1" CACHE INTERNAL "Have symbol atan") + set(LIBC_HAS_ATAN2 "1" CACHE INTERNAL "Have symbol atan2") + set(LIBC_HAS_ATAN2F "" CACHE INTERNAL "Have symbol atan2f") + set(LIBC_HAS_ATANF "" CACHE INTERNAL "Have symbol atanf") + set(LIBC_HAS_ATOF "" CACHE INTERNAL "Have symbol atof") + set(LIBC_HAS_ATOI "" CACHE INTERNAL "Have symbol atoi") + set(LIBC_HAS_BCOPY "1" CACHE INTERNAL "Have symbol bcopy") + set(LIBC_HAS_CALLOC "" CACHE INTERNAL "Have symbol calloc") + set(LIBC_HAS_CEIL "1" CACHE INTERNAL "Have symbol ceil") + set(LIBC_HAS_CEILF "" CACHE INTERNAL "Have symbol ceilf") + set(LIBC_HAS_COPYSIGN "1" CACHE INTERNAL "Have symbol copysign") + set(LIBC_HAS_COPYSIGNF "1" CACHE INTERNAL "Have symbol copysignf") + set(LIBC_HAS_COS "1" CACHE INTERNAL "Have symbol cos") + set(LIBC_HAS_COSF "" CACHE INTERNAL "Have symbol cosf") + set(LIBC_HAS_EXP "1" CACHE INTERNAL "Have symbol exp") + set(LIBC_HAS_EXPF "" CACHE INTERNAL "Have symbol expf") + set(LIBC_HAS_FABS "1" CACHE INTERNAL "Have symbol fabs") + set(LIBC_HAS_FABSF "1" CACHE INTERNAL "Have symbol fabsf") + set(LIBC_HAS_FLOAT_H "1" CACHE INTERNAL "Have include float.h") + set(LIBC_HAS_FLOOR "1" CACHE INTERNAL "Have symbol floor") + set(LIBC_HAS_FLOORF "" CACHE INTERNAL "Have symbol floorf") + set(LIBC_HAS_FMOD "" CACHE INTERNAL "Have symbol fmod") + set(LIBC_HAS_FMODF "" CACHE INTERNAL "Have symbol fmodf") + set(LIBC_HAS_FOPEN64 "" CACHE INTERNAL "Have symbol fopen64") + set(LIBC_HAS_FREE "1" CACHE INTERNAL "Have symbol free") + set(LIBC_HAS_FSEEKO "" CACHE INTERNAL "Have symbol fseeko") + set(LIBC_HAS_FSEEKO64 "" CACHE INTERNAL "Have symbol fseeko64") + set(LIBC_HAS_GETENV "" CACHE INTERNAL "Have symbol getenv") + set(LIBC_HAS_ICONV_H "" CACHE INTERNAL "Have include iconv.h") + set(LIBC_HAS_INDEX "1" CACHE INTERNAL "Have symbol index") + set(LIBC_HAS_INTTYPES_H "1" CACHE INTERNAL "Have include inttypes.h") + set(LIBC_HAS_ISINF "1" CACHE INTERNAL "Have include isinf(double)") + set(LIBC_ISINF_HANDLES_FLOAT "1" CACHE INTERNAL "Have include isinf(float)") + set(LIBC_HAS_ISINFF "1" CACHE INTERNAL "Have include isinff(float)") + set(LIBC_HAS_ISNAN "1" CACHE INTERNAL "Have include isnan(double)") + set(LIBC_ISNAN_HANDLES_FLOAT "1" CACHE INTERNAL "Have include isnan(float)") + set(LIBC_HAS_ISNANF "1" CACHE INTERNAL "Have include isnanf(float)") + set(LIBC_HAS_ITOA "" CACHE INTERNAL "Have symbol itoa") + set(LIBC_HAS_LIMITS_H "1" CACHE INTERNAL "Have include limits.h") + set(LIBC_HAS_LOG "1" CACHE INTERNAL "Have symbol log") + set(LIBC_HAS_LOG10 "" CACHE INTERNAL "Have symbol log10") + set(LIBC_HAS_LOG10F "" CACHE INTERNAL "Have symbol log10f") + set(LIBC_HAS_LOGF "" CACHE INTERNAL "Have symbol logf") + set(LIBC_HAS_LROUND "" CACHE INTERNAL "Have symbol lround") + set(LIBC_HAS_LROUNDF "" CACHE INTERNAL "Have symbol lroundf") + set(LIBC_HAS_MALLOC "1" CACHE INTERNAL "Have symbol malloc") + set(LIBC_HAS_MALLOC_H "" CACHE INTERNAL "Have include malloc.h") + set(LIBC_HAS_MATH_H "1" CACHE INTERNAL "Have include math.h") + set(LIBC_HAS_MEMCMP "1" CACHE INTERNAL "Have symbol memcmp") + set(LIBC_HAS_MEMCPY "" CACHE INTERNAL "Have symbol memcpy") + set(LIBC_HAS_MEMMOVE "" CACHE INTERNAL "Have symbol memmove") + set(LIBC_HAS_MEMORY_H "" CACHE INTERNAL "Have include memory.h") + set(LIBC_HAS_MEMSET "" CACHE INTERNAL "Have symbol memset") + set(LIBC_HAS_MODF "1" CACHE INTERNAL "Have symbol modf") + set(LIBC_HAS_MODFF "" CACHE INTERNAL "Have symbol modff") + set(LIBC_HAS_POW "1" CACHE INTERNAL "Have symbol pow") + set(LIBC_HAS_POWF "" CACHE INTERNAL "Have symbol powf") + set(LIBC_HAS_PUTENV "" CACHE INTERNAL "Have symbol putenv") + set(LIBC_HAS_REALLOC "" CACHE INTERNAL "Have symbol realloc") + set(LIBC_HAS_RINDEX "1" CACHE INTERNAL "Have symbol rindex") + set(LIBC_HAS_ROUND "" CACHE INTERNAL "Have symbol round") + set(LIBC_HAS_ROUNDF "" CACHE INTERNAL "Have symbol roundf") + set(LIBC_HAS_SCALBN "1" CACHE INTERNAL "Have symbol scalbn") + set(LIBC_HAS_SCALBNF "" CACHE INTERNAL "Have symbol scalbnf") + set(LIBC_HAS_SETENV "" CACHE INTERNAL "Have symbol setenv") + set(LIBC_HAS_SIGNAL_H "" CACHE INTERNAL "Have include signal.h") + set(LIBC_HAS_SIN "1" CACHE INTERNAL "Have symbol sin") + set(LIBC_HAS_SINF "" CACHE INTERNAL "Have symbol sinf") + set(LIBC_HAS_SQR "" CACHE INTERNAL "Have symbol sqr") + set(LIBC_HAS_SQRT "1" CACHE INTERNAL "Have symbol sqrt") + set(LIBC_HAS_SQRTF "" CACHE INTERNAL "Have symbol sqrtf") + set(LIBC_HAS_SSCANF "1" CACHE INTERNAL "Have symbol sscanf") + set(LIBC_HAS_STDARG_H "1" CACHE INTERNAL "Have include stdarg.h") + set(LIBC_HAS_STDBOOL_H "1" CACHE INTERNAL "Have include stdbool.h") + set(LIBC_HAS_STDDEF_H "1" CACHE INTERNAL "Have include stddef.h") + set(LIBC_HAS_STDINT_H "1" CACHE INTERNAL "Have include stdint.h") + set(LIBC_HAS_STDIO_H "1" CACHE INTERNAL "Have include stdio.h") + set(LIBC_HAS_STDLIB_H "1" CACHE INTERNAL "Have include stdlib.h") + set(LIBC_HAS_STRCASESTR "" CACHE INTERNAL "Have symbol strcasestr") + set(LIBC_HAS_STRCHR "1" CACHE INTERNAL "Have symbol strchr") + set(LIBC_HAS_STRCMP "1" CACHE INTERNAL "Have symbol strcmp") + set(LIBC_HAS_STRINGS_H "" CACHE INTERNAL "Have include strings.h") + set(LIBC_HAS_STRING_H "1" CACHE INTERNAL "Have include string.h") + set(LIBC_HAS_STRLCAT "" CACHE INTERNAL "Have symbol strlcat") + set(LIBC_HAS_STRLCPY "" CACHE INTERNAL "Have symbol strlcpy") + set(LIBC_HAS_STRLEN "1" CACHE INTERNAL "Have symbol strlen") + set(LIBC_HAS_STRNCMP "1" CACHE INTERNAL "Have symbol strncmp") + set(LIBC_HAS_STRNLEN "" CACHE INTERNAL "Have symbol strnlen") + set(LIBC_HAS_STRNSTR "" CACHE INTERNAL "Have symbol strnstr") + set(LIBC_HAS_STRPBRK "1" CACHE INTERNAL "Have symbol strpbrk") + set(LIBC_HAS_STRRCHR "1" CACHE INTERNAL "Have symbol strrchr") + set(LIBC_HAS_STRSTR "1" CACHE INTERNAL "Have symbol strstr") + set(LIBC_HAS_STRTOD "" CACHE INTERNAL "Have symbol strtod") + set(LIBC_HAS_STRTOK_R "" CACHE INTERNAL "Have symbol strtok_r") + set(LIBC_HAS_STRTOL "" CACHE INTERNAL "Have symbol strtol") + set(LIBC_HAS_STRTOLL "" CACHE INTERNAL "Have symbol strtoll") + set(LIBC_HAS_STRTOUL "" CACHE INTERNAL "Have symbol strtoul") + set(LIBC_HAS_STRTOULL "" CACHE INTERNAL "Have symbol strtoull") + set(LIBC_HAS_SYS_TYPES_H "1" CACHE INTERNAL "Have include sys/types.h") + set(LIBC_HAS_TAN "1" CACHE INTERNAL "Have symbol tan") + set(LIBC_HAS_TANF "" CACHE INTERNAL "Have symbol tanf") + set(LIBC_HAS_TIME_H "1" CACHE INTERNAL "Have include time.h") + set(LIBC_HAS_TRUNC "" CACHE INTERNAL "Have symbol trunc") + set(LIBC_HAS_TRUNCF "" CACHE INTERNAL "Have symbol truncf") + set(LIBC_HAS_UNSETENV "" CACHE INTERNAL "Have symbol unsetenv") + set(LIBC_HAS_VSNPRINTF "" CACHE INTERNAL "Have symbol vsnprintf") + set(LIBC_HAS_VSSCANF "" CACHE INTERNAL "Have symbol vsscanf") + set(LIBC_HAS_WCHAR_H "1" CACHE INTERNAL "Have include wchar.h") + set(LIBC_HAS_WCSCMP "" CACHE INTERNAL "Have symbol wcscmp") + set(LIBC_HAS_WCSDUP "" CACHE INTERNAL "Have symbol wcsdup") + set(LIBC_HAS_WCSLCAT "" CACHE INTERNAL "Have symbol wcslcat") + set(LIBC_HAS_WCSLCPY "" CACHE INTERNAL "Have symbol wcslcpy") + set(LIBC_HAS_WCSLEN "" CACHE INTERNAL "Have symbol wcslen") + set(LIBC_HAS_WCSNCMP "" CACHE INTERNAL "Have symbol wcsncmp") + set(LIBC_HAS_WCSNLEN "" CACHE INTERNAL "Have symbol wcsnlen") + set(LIBC_HAS_WCSSTR "" CACHE INTERNAL "Have symbol wcsstr") + set(LIBC_HAS_WCSTOL "" CACHE INTERNAL "Have symbol wcstol") + set(LIBC_HAS__EXIT "" CACHE INTERNAL "Have symbol _Exit") + set(LIBC_HAS__I64TOA "" CACHE INTERNAL "Have symbol _i64toa") + set(LIBC_HAS__LTOA "" CACHE INTERNAL "Have symbol _ltoa") + set(LIBC_HAS__STRREV "" CACHE INTERNAL "Have symbol _strrev") + set(LIBC_HAS__UI64TOA "" CACHE INTERNAL "Have symbol _ui64toa") + set(LIBC_HAS__UITOA "" CACHE INTERNAL "Have symbol _uitoa") + set(LIBC_HAS__ULTOA "" CACHE INTERNAL "Have symbol _ultoa") + set(LIBC_HAS__WCSDUP "" CACHE INTERNAL "Have symbol _wcsdup") + set(LIBC_IS_GLIBC "" CACHE INTERNAL "Have symbol __GLIBC__") + set(_ALLOCA_IN_MALLOC_H "" CACHE INTERNAL "Have symbol _alloca") + set(HAVE_GCC_WALL "1" CACHE INTERNAL "Test HAVE_GCC_WALL") + set(HAVE_GCC_WUNDEF "1" CACHE INTERNAL "Test HAVE_GCC_WUNDEF") + set(HAVE_GCC_WFLOAT_CONVERSION "" CACHE INTERNAL "Test HAVE_GCC_WFLOAT_CONVERSION") + set(HAVE_GCC_NO_STRICT_ALIASING "1" CACHE INTERNAL "Test HAVE_GCC_NO_STRICT_ALIASING") + set(HAVE_GCC_WDOCUMENTATION "" CACHE INTERNAL "Test HAVE_GCC_WDOCUMENTATION") + set(HAVE_GCC_WDOCUMENTATION_UNKNOWN_COMMAND "" CACHE INTERNAL "Test HAVE_GCC_WDOCUMENTATION_UNKNOWN_COMMAND") + set(HAVE_GCC_COMMENT_BLOCK_COMMANDS "" CACHE INTERNAL "Test HAVE_GCC_COMMENT_BLOCK_COMMANDS") + set(HAVE_GCC_WSHADOW "1" CACHE INTERNAL "Test HAVE_GCC_WSHADOW") + set(HAVE_GCC_WUNUSED_LOCAL_TYPEDEFS "" CACHE INTERNAL "Test HAVE_GCC_WUNUSED_LOCAL_TYPEDEFS") + set(HAVE_GCC_WIMPLICIT_FALLTHROUGH "" CACHE INTERNAL "Test HAVE_GCC_WIMPLICIT_FALLTHROUGH") + set(HAVE_GCC_FVISIBILITY "" CACHE INTERNAL "Test HAVE_GCC_FVISIBILITY") + set(HAVE_ST_MTIM "" CACHE INTERNAL "Test HAVE_ST_MTIM") + #set(HAVE_O_CLOEXEC "" CACHE INTERNAL "Test HAVE_O_CLOEXEC") + #set(COMPILER_SUPPORTS_FDIAGNOSTICS_COLOR "" CACHE INTERNAL "Test COMPILER_SUPPORTS_FDIAGNOSTICS_COLOR") + set(COMPILER_SUPPORTS_GCC_ATOMICS "" CACHE INTERNAL "Test COMPILER_SUPPORTS_GCC_ATOMICS") + set(LINKER_SUPPORTS_VERSION_SCRIPT "" CACHE INTERNAL "Test LINKER_SUPPORTS_VERSION_SCRIPT") + set(LINKER_SUPPORTS_WL_NO_UNDEFINED "" CACHE INTERNAL "Test LINKER_SUPPORTS_WL_NO_UNDEFINED") + set(ICONV_IN_LIBC "" CACHE INTERNAL "Test ICONV_IN_LIBC") + set(ICONV_IN_LIBICONV "" CACHE INTERNAL "Test ICONV_IN_LIBICONV") + #set(LIBC_HAS_WORKING_LIBUNWIND "" CACHE INTERNAL "Test LIBC_HAS_WORKING_LIBUNWIND") + #set(LIBUNWIND_HAS_WORKINGLIBUNWIND "" CACHE INTERNAL "Test LIBUNWIND_HAS_WORKINGLIBUNWIND") + set(HAVE_GETPAGESIZE "" CACHE INTERNAL "Have symbol getpagesize") + set(HAVE_SIGACTION "" CACHE INTERNAL "Have symbol sigaction") + set(HAVE_SA_SIGACTION "" CACHE INTERNAL "Have symbol sa_sigaction") + set(HAVE_SETJMP "" CACHE INTERNAL "Have symbol setjmp") + set(HAVE_NANOSLEEP "" CACHE INTERNAL "Have symbol nanosleep") + set(HAVE_GMTIME_R "" CACHE INTERNAL "Have symbol gmtime_r") + set(HAVE_LOCALTIME_R "" CACHE INTERNAL "Have symbol localtime_r") + set(HAVE_NL_LANGINFO "" CACHE INTERNAL "Have symbol nl_langinfo") + set(HAVE_SYSCONF "" CACHE INTERNAL "Have symbol sysconf") + set(HAVE_SYSCTLBYNAME "" CACHE INTERNAL "Have symbol sysctlbyname") + set(HAVE_GETAUXVAL "" CACHE INTERNAL "Have symbol getauxval") + set(HAVE_ELF_AUX_INFO "" CACHE INTERNAL "Have symbol elf_aux_info") + set(HAVE_POLL "" CACHE INTERNAL "Have symbol poll") + set(HAVE_MEMFD_CREATE "" CACHE INTERNAL "Have symbol memfd_create") + set(HAVE_POSIX_FALLOCATE "" CACHE INTERNAL "Have symbol posix_fallocate") + set(HAVE_DLOPEN_IN_LIBC "" CACHE INTERNAL "Have symbol dlopen") + + set(HAVE_GETHOSTNAME "" CACHE INTERNAL "Have symbol gethostname") + set(HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDCHDIR "" CACHE INTERNAL "Have symbol addchdir") + set(HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDCHDIR_NP "" CACHE INTERNAL "Have symbol addchdir_np") + set(HAVE_FDATASYNC "" CACHE INTERNAL "Have symbol fdatasync") + + set(HAVE_SDL_FSOPS "1" CACHE INTERNAL "Enable SDL_FSOPS") + set(HAVE_SDL_LOCALE "1" CACHE INTERNAL "Enable SDL_LOCALE") + endfunction() +endif() diff --git a/libs/SDL3/cmake/macros.cmake b/libs/SDL3/cmake/macros.cmake index 3885abae..3ee413c5 100644 --- a/libs/SDL3/cmake/macros.cmake +++ b/libs/SDL3/cmake/macros.cmake @@ -362,9 +362,6 @@ function(SDL_PrintSummary) message(STATUS "") message(STATUS " Build Shared Library: ${SDL_SHARED}") message(STATUS " Build Static Library: ${SDL_STATIC}") - if(SDL_STATIC) - message(STATUS " Build Static Library with Position Independent Code: ${SDL_STATIC_PIC}") - endif() if(APPLE) message(STATUS " Build libraries as Apple Framework: ${SDL_FRAMEWORK}") endif() diff --git a/libs/SDL3/cmake/sdlchecks.cmake b/libs/SDL3/cmake/sdlchecks.cmake index a444688a..06edce5b 100644 --- a/libs/SDL3/cmake/sdlchecks.cmake +++ b/libs/SDL3/cmake/sdlchecks.cmake @@ -1,3 +1,10 @@ +macro(check_c_source_compiles_static SOURCE VAR) + set(saved_CMAKE_TRY_COMPILE_TARGET_TYPE "${CMAKE_TRY_COMPILE_TARGET_TYPE}") + set(CMAKE_TRY_COMPILE_TARGET_TYPE "STATIC_LIBRARY") + check_c_source_compiles("${SOURCE}" ${VAR} ${ARGN}) + set(CMAKE_TRY_COMPILE_TARGET_TYPE "${saved_CMAKE_TRY_COMPILE_TARGET_TYPE}") +endmacro() + macro(FindLibraryAndSONAME _LIB) cmake_parse_arguments(_FLAS "" "" "LIBDIRS" ${ARGN}) @@ -274,10 +281,11 @@ macro(CheckX11) set(Xrandr_PKG_CONFIG_SPEC xrandr) set(Xrender_PKG_CONFIG_SPEC xrender) set(Xss_PKG_CONFIG_SPEC xscrnsaver) + set(Xtst_PKG_CONFIG_SPEC xtst) find_package(X11) - foreach(_LIB X11 Xext Xcursor Xi Xfixes Xrandr Xrender Xss) + foreach(_LIB X11 Xext Xcursor Xi Xfixes Xrandr Xrender Xss Xtst) get_filename_component(_libdir "${X11_${_LIB}_LIB}" DIRECTORY) FindLibraryAndSONAME("${_LIB}" LIBDIRS ${_libdir}) endforeach() @@ -310,6 +318,7 @@ macro(CheckX11) find_file(HAVE_XSYNC_H NAMES "X11/extensions/sync.h" HINTS "${X11_INCLUDEDIR}") find_file(HAVE_XSS_H NAMES "X11/extensions/scrnsaver.h" HINTS "${X11_INCLUDEDIR}") find_file(HAVE_XSHAPE_H NAMES "X11/extensions/shape.h" HINTS "${X11_INCLUDEDIR}") + find_file(HAVE_XTEST_H NAMES "X11/extensions/XTest.h" HINTS "${X11_INCLUDEDIR}") find_file(HAVE_XDBE_H NAMES "X11/extensions/Xdbe.h" HINTS "${X11_INCLUDEDIR}") find_file(HAVE_XEXT_H NAMES "X11/extensions/Xext.h" HINTS "${X11_INCLUDEDIR}") @@ -367,7 +376,7 @@ macro(CheckX11) list(APPEND CMAKE_REQUIRED_LIBRARIES ${X11_LIB}) - check_c_source_compiles(" + check_c_source_compiles_static(" #include int main(int argc, char **argv) { Display *display; @@ -408,7 +417,7 @@ macro(CheckX11) set(SDL_VIDEO_DRIVER_X11_XINPUT2 1) # Check for multitouch - check_c_source_compiles(" + check_c_source_compiles_static(" #include #include #include @@ -425,7 +434,7 @@ macro(CheckX11) # check along with XInput2.h because we use Xfixes with XIBarrierReleasePointer if(SDL_X11_XFIXES AND HAVE_XFIXES_H_ AND HAVE_XINPUT2_H) - check_c_source_compiles(" + check_c_source_compiles_static(" #include #include #include @@ -472,6 +481,16 @@ macro(CheckX11) set(SDL_VIDEO_DRIVER_X11_XSHAPE 1) set(HAVE_X11_XSHAPE TRUE) endif() + + if(SDL_X11_XTEST AND HAVE_XTEST_H AND XTST_LIB) + if(HAVE_X11_SHARED) + set(SDL_VIDEO_DRIVER_X11_DYNAMIC_XTEST "\"${XTST_LIB_SONAME}\"") + else() + sdl_link_dependency(xtst LIBS X11::Xtst CMAKE_MODULE X11 PKG_CONFIG_SPECS ${Xtst_PKG_CONFIG_SPEC}) + endif() + set(SDL_VIDEO_DRIVER_X11_XTEST 1) + set(HAVE_X11_XTEST TRUE) + endif() endif() endif() if(NOT HAVE_X11) @@ -575,6 +594,18 @@ macro(CheckWayland) sdl_link_dependency(wayland LIBS PkgConfig::PC_WAYLAND PKG_CONFIG_PREFIX PC_WAYLAND PKG_CONFIG_SPECS ${WAYLAND_PKG_CONFIG_SPEC}) endif() + # xkbcommon doesn't provide internal version defines, so generate them here. + if (PC_WAYLAND_xkbcommon_VERSION MATCHES "^([0-9]+)\\.([0-9]+)\\.([0-9]+)") + set(SDL_XKBCOMMON_VERSION_MAJOR ${CMAKE_MATCH_1}) + set(SDL_XKBCOMMON_VERSION_MINOR ${CMAKE_MATCH_2}) + set(SDL_XKBCOMMON_VERSION_PATCH ${CMAKE_MATCH_3}) + else() + message(WARNING "Failed to parse xkbcommon version; defaulting to lowest supported (0.5.0)") + set(SDL_XKBCOMMON_VERSION_MAJOR 0) + set(SDL_XKBCOMMON_VERSION_MINOR 5) + set(SDL_XKBCOMMON_VERSION_PATCH 0) + endif() + if(SDL_WAYLAND_LIBDECOR) set(LibDecor_PKG_CONFIG_SPEC libdecor-0) pkg_check_modules(PC_LIBDECOR IMPORTED_TARGET ${LibDecor_PKG_CONFIG_SPEC}) @@ -795,7 +826,7 @@ endmacro() macro(CheckPTHREAD) cmake_push_check_state() if(SDL_PTHREADS) - if(ANDROID) + if(ANDROID OR SDL_PTHREADS_PRIVATE) # the android libc provides built-in support for pthreads, so no # additional linking or compile flags are necessary elseif(LINUX) @@ -1085,7 +1116,7 @@ macro(CheckHIDAPI) if(LibUSB_FOUND) cmake_push_check_state() list(APPEND CMAKE_REQUIRED_LIBRARIES LibUSB::LibUSB) - check_c_source_compiles(" + check_c_source_compiles_static(" #include #include int main(int argc, char **argv) { @@ -1124,6 +1155,7 @@ macro(CheckHIDAPI) set(HAVE_SDL_JOYSTICK TRUE) set(HAVE_HIDAPI_JOYSTICK TRUE) sdl_glob_sources("${SDL3_SOURCE_DIR}/src/joystick/hidapi/*.c") + sdl_glob_sources("${SDL3_SOURCE_DIR}/src/haptic/hidapi/*.c") endif() else() set(SDL_HIDAPI_DISABLED 1) diff --git a/libs/SDL3/cmake/sdltargets.cmake b/libs/SDL3/cmake/sdlcommands.cmake similarity index 100% rename from libs/SDL3/cmake/sdltargets.cmake rename to libs/SDL3/cmake/sdlcommands.cmake diff --git a/libs/SDL3/cmake/sdlcpu.cmake b/libs/SDL3/cmake/sdlcpu.cmake index 0c2ca1f5..5c4b575e 100644 --- a/libs/SDL3/cmake/sdlcpu.cmake +++ b/libs/SDL3/cmake/sdlcpu.cmake @@ -4,15 +4,15 @@ function(SDL_DetectTargetCPUArchitectures DETECTED_ARCHS) if(APPLE AND CMAKE_OSX_ARCHITECTURES) foreach(known_arch IN LISTS known_archs) - set(SDL_CPU_${known_arch} "0") + set(SDL_CPU_${known_arch} "0" PARENT_SCOPE) endforeach() set(detected_archs) foreach(osx_arch IN LISTS CMAKE_OSX_ARCHITECTURES) if(osx_arch STREQUAL "x86_64") - set(SDL_CPU_X64 "1") + set(SDL_CPU_X64 "1" PARENT_SCOPE) list(APPEND detected_archs "X64") elseif(osx_arch STREQUAL "arm64") - set(SDL_CPU_ARM64 "1") + set(SDL_CPU_ARM64 "1" PARENT_SCOPE) list(APPEND detected_archs "ARM64") endif() endforeach() diff --git a/libs/SDL3/cmake/sdlplatform.cmake b/libs/SDL3/cmake/sdlplatform.cmake index 677b1870..60e0e77f 100644 --- a/libs/SDL3/cmake/sdlplatform.cmake +++ b/libs/SDL3/cmake/sdlplatform.cmake @@ -22,6 +22,8 @@ function(SDL_DetectCMakePlatform) set(sdl_cmake_platform Haiku) elseif(NINTENDO_3DS) set(sdl_cmake_platform n3ds) + elseif(NGAGESDK) + set(sdl_cmake_platform ngage) elseif(PS2) set(sdl_cmake_platform ps2) elseif(VITA) diff --git a/libs/SDL3/cmake/test/CMakeLists.txt b/libs/SDL3/cmake/test/CMakeLists.txt index e3766f0e..ffaa1977 100644 --- a/libs/SDL3/cmake/test/CMakeLists.txt +++ b/libs/SDL3/cmake/test/CMakeLists.txt @@ -96,12 +96,14 @@ if(TEST_STATIC) add_executable(gui-static WIN32 main_gui.c) target_link_libraries(gui-static PRIVATE SDL3::SDL3-static) - # Assume SDL library has been built with `set(CMAKE_POSITION_INDEPENDENT_CODE ON)` - add_library(sharedlib-static SHARED main_lib.c) - target_link_libraries(sharedlib-static PRIVATE SDL3::SDL3-static) - generate_export_header(sharedlib-static EXPORT_MACRO_NAME MYLIBRARY_EXPORT) - target_compile_definitions(sharedlib-static PRIVATE "EXPORT_HEADER=\"${CMAKE_CURRENT_BINARY_DIR}/sharedlib-static_export.h\"") - set_target_properties(sharedlib-static PROPERTIES C_VISIBILITY_PRESET "hidden") + if(TEST_SHARED) + # Assume SDL library has been built with `set(CMAKE_POSITION_INDEPENDENT_CODE ON)` + add_library(sharedlib-static SHARED main_lib.c) + target_link_libraries(sharedlib-static PRIVATE SDL3::SDL3-static) + generate_export_header(sharedlib-static EXPORT_MACRO_NAME MYLIBRARY_EXPORT) + target_compile_definitions(sharedlib-static PRIVATE "EXPORT_HEADER=\"${CMAKE_CURRENT_BINARY_DIR}/sharedlib-static_export.h\"") + set_target_properties(sharedlib-static PROPERTIES C_VISIBILITY_PRESET "hidden") + endif() if(TEST_TEST) add_executable(sdltest-static sdltest.c) diff --git a/libs/SDL3/cmake/test/main_gui.c b/libs/SDL3/cmake/test/main_gui.c index 18ed1010..c0c4f901 100644 --- a/libs/SDL3/cmake/test/main_gui.c +++ b/libs/SDL3/cmake/test/main_gui.c @@ -1,24 +1,37 @@ -#include +#define SDL_MAIN_USE_CALLBACKS #include +#include -int main(int argc, char *argv[]) +static SDL_Window *window; + +SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event) +{ + return SDL_APP_CONTINUE; +} + +SDL_AppResult SDL_AppIterate(void *appstate) { - SDL_Window *window = NULL; SDL_Surface *screenSurface = NULL; + screenSurface = SDL_GetWindowSurface(window); + SDL_FillSurfaceRect(screenSurface, NULL, SDL_MapSurfaceRGB(screenSurface, 0xff, 0xff, 0xff)); + SDL_UpdateWindowSurface(window); + return SDL_APP_CONTINUE; +} + +SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) +{ if (!SDL_Init(SDL_INIT_VIDEO)) { SDL_Log("Could not initialize SDL: %s", SDL_GetError()); - return 1; + return SDL_APP_FAILURE; } window = SDL_CreateWindow("Hello SDL", 640, 480, 0); if (!window) { SDL_Log("could not create window: %s", SDL_GetError()); - return 1; + return SDL_APP_FAILURE; } - screenSurface = SDL_GetWindowSurface(window); - SDL_FillSurfaceRect(screenSurface, NULL, SDL_MapSurfaceRGB(screenSurface, 0xff, 0xff, 0xff)); - SDL_UpdateWindowSurface(window); - SDL_Delay(100); - SDL_DestroyWindow(window); - SDL_Quit(); - return 0; + return SDL_APP_CONTINUE; +} + +void SDL_AppQuit(void *appstate, SDL_AppResult result) { + SDL_DestroyWindow(window); } diff --git a/libs/SDL3/cmake/test/sdltest.c b/libs/SDL3/cmake/test/sdltest.c index f598a98c..baf8e9b5 100644 --- a/libs/SDL3/cmake/test/sdltest.c +++ b/libs/SDL3/cmake/test/sdltest.c @@ -1,9 +1,24 @@ +#define SDL_MAIN_USE_CALLBACKS #include +#include #include +SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event) +{ + return SDL_APP_SUCCESS; +} -int main(int argc, char *argv[]) { +SDL_AppResult SDL_AppIterate(void *appstate) +{ + return SDL_APP_SUCCESS; +} + +SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) +{ SDLTest_CommonState state; SDLTest_CommonDefaultArgs(&state, argc, argv); - return 0; + return SDL_APP_SUCCESS; +} + +void SDL_AppQuit(void *appstate, SDL_AppResult result) { } diff --git a/libs/SDL3/docs/README-android.md b/libs/SDL3/docs/README-android.md index e57ca81d..b2568e94 100644 --- a/libs/SDL3/docs/README-android.md +++ b/libs/SDL3/docs/README-android.md @@ -149,7 +149,7 @@ target_link_libraries(yourgame PRIVATE SDL3::SDL3) If you use ndk-build, add the following before `include $(BUILD_SHARED_LIBRARY)` to your `Android.mk`: ``` -LOCAL_SHARED_LIBARARIES := SDL3 SDL3-Headers +LOCAL_SHARED_LIBRARIES := SDL3 SDL3-Headers ``` And add the following at the bottom: ``` diff --git a/libs/SDL3/docs/README-cmake.md b/libs/SDL3/docs/README-cmake.md index f2e4759c..7aea86ef 100644 --- a/libs/SDL3/docs/README-cmake.md +++ b/libs/SDL3/docs/README-cmake.md @@ -157,7 +157,7 @@ flags to the compiler. - Use [`CMAKE_EXE_LINKER_FLAGS`](https://cmake.org/cmake/help/latest/variable/CMAKE_EXE_LINKER_FLAGS.html) to pass extra option to the linker for executables. - Use [`CMAKE_SHARED_LINKER_FLAGS`](https://cmake.org/cmake/help/latest/variable/CMAKE_SHARED_LINKER_FLAGS.html) to pass extra options to the linker for shared libraries. -#### Examples +#### Compile Options Examples - build a SDL library optimized for (more) modern x64 microprocessor architectures. @@ -240,7 +240,7 @@ Append with a version number to target a specific SDK revision: e.g. `iphoneos12 CMake documentation: [link](https://cmake.org/cmake/help/latest/variable/CMAKE_OSX_SYSROOT.html) -#### Examples +#### Apple Examples - for macOS, building a dylib and/or static library for x86_64 and arm64: @@ -328,8 +328,8 @@ Configure your project with `-DSDL_LIBC=ON` to make use of sanitizers. ### CMake fails to build without X11 or Wayland support Install the required system packages prior to running CMake. -See [README-linux](linux#build-dependencies) for the list of dependencies on Linux. -Other unix operationg systems should provide similar packages. +See [README-linux.md](README-linux.md#build-dependencies) for the list of dependencies on Linux. +Other unix operating systems should provide similar packages. If you **really** don't need to show windows, add `-DSDL_UNIX_CONSOLE_BUILD=ON` to the CMake configure command. diff --git a/libs/SDL3/docs/README-documentation-rules.md b/libs/SDL3/docs/README-documentation-rules.md index 3151de76..e6dfa25e 100644 --- a/libs/SDL3/docs/README-documentation-rules.md +++ b/libs/SDL3/docs/README-documentation-rules.md @@ -34,6 +34,12 @@ things, you might confuse it. This is to the benefit of documentation, though, where we would rather you not do surprising things. +## UTF-8 only! + +All text must be UTF-8 encoded. The wiki will refuse to update files that are +malformed. + + ## We _sort of_ write in Doxygen format. To document a symbol, we use something that looks like Doxygen (and Javadoc) @@ -156,6 +162,23 @@ wikiheaders will wordwrap header comments so they fit in 80 columns, so if you don't leave a blank line between paragraphs, they will smush into a single block of text when wordwrapping. +## Lists must be the start of a new paragraph. + +If you write this: + +``` +Here is some text without a blank line +before an unordered list! +- item a +- item b +- item c +``` + +...then wikiheaders will word wrap this as a single paragraph, mangling the list. + +Put a blank line before the list, and everything will format and wrap correctly. + +This is a limitation of wikiheaders. Don't get bit by it! ## Don't worry about word wrapping. @@ -304,6 +327,21 @@ to the headers: - "Version" - "See Also" +## Unrecognized sections are removed from the headers! + +If you add Doxygen with a `##` (`###`, etc) section header, it'll +migrate to the wiki and be _removed_ from the headers. Generally +the correct thing to do is _never use section headers in the Doxygen_. + +## wikiheaders will reorder standard sections. + +The standard sections are always kept in a consistent order by +wikiheaders, both in the headers and the wiki. If they're placed in +a non-standard order, wikiheaders will reorder them. + +For sections that aren't standard, wikiheaders will place them at +the end of the wiki page, in the order they were seen when it loaded +the page for processing. ## It's okay to repeat yourself. @@ -318,7 +356,7 @@ through, header users can search for the function name. You might be reading this document on the wiki! Any `README-*.md` files in the docs directory are bridged to the wiki, so `docs/README-linux.md` lands -at https://wiki.libsdl.org/SDL3/README/linux ...these are just copied directly +at https://wiki.libsdl.org/SDL3/README-linux ...these are just copied directly without any further processing by wikiheaders, and changes go in both directions. diff --git a/libs/SDL3/docs/README-emscripten.md b/libs/SDL3/docs/README-emscripten.md index 2b814689..54e7d2de 100644 --- a/libs/SDL3/docs/README-emscripten.md +++ b/libs/SDL3/docs/README-emscripten.md @@ -103,7 +103,7 @@ getting started. Another option is to use SDL' main callbacks, which handle this for you without platform-specific code in your app. Please refer to -[the wiki](https://wiki.libsdl.org/SDL3/README/main-functions#main-callbacks-in-sdl3) +[the wiki](https://wiki.libsdl.org/SDL3/README-main-functions#main-callbacks-in-sdl3) or `docs/README-main-functions.md` in the SDL source code. @@ -230,7 +230,7 @@ tools. mkdir build cd build emcmake cmake .. -# you can also do `emcmake cmake -G Ninja ..` and then use `ninja` instead of this command. +# you can also try `emcmake cmake -G Ninja ..` and then use `ninja` instead of this command. emmake make -j4 ``` diff --git a/libs/SDL3/docs/README-highdpi.md b/libs/SDL3/docs/README-highdpi.md index bec0125f..dd3a2fa9 100644 --- a/libs/SDL3/docs/README-highdpi.md +++ b/libs/SDL3/docs/README-highdpi.md @@ -1,32 +1,6 @@ SDL 3.0 has new support for high DPI displays. Interfaces provided by SDL uses the platform's native coordinates unless otherwise specified. -To reconcile platform differences in their approach to high-density scaling, SDL provides the following interfaces: -- `SDL_GetWindowSize()` retrieves the window dimensions in native coordinates. -- `SDL_GetWindowSizeInPixels()` retrieves the window dimensions in pixels-addressable. -- `SDL_GetDisplayContentScale()` retrieves the suggested amplification factor when drawing in native coordinates. -- `SDL_GetWindowDisplayScale()` retrieves the suggested amplification factor when drawing in pixels-addressable. -- `SDL_GetWindowPixelDensity()` retrieves how many addressable pixels correspond to one unit of native coordinates. -- `SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED` is emitted when the value retrievable from `SDL_GetWindowSizeInPixels()` changes. -- `SDL_EVENT_WINDOW_DISPLAY_SCALE_CHANGED` is emitted when the value retrievable from `SDL_GetWindowDisplayScale()` changes. -- Windows created with `SDL_WINDOW_HIGH_PIXEL_DENSITY` will ask the platform to display addressable pixels at their natural scale. - -## Numeric example - -Given a fullscreen window spanning a 3840x2160 monitor set to 2x display or 200% scaling, the following tabulates the effect of creating a window with or without `SDL_WINDOW_HIGH_PIXEL_DENSITY` on MacOS and Win32: - -| Value | MacOS (Default) | MacOS (HD) | Win32 (Default & HD) | -|--------------------------------|-----------------|------------|----------------------| -| `SDL_GetWindowSize()` | 1920x1080 | 1920x1080 | 3840x2160 | -| `SDL_GetWindowSizeInPixels()` | 1920x1080 | 3840x2160 | 3840x2160 | -| `SDL_GetDisplayContentScale()` | 1.0 | 1.0 | 2.0 | -| `SDL_GetWindowDisplayScale()` | 1.0 | 2.0 | 2.0 | -| `SDL_GetWindowPixelDensity()` | 1.0 | 2.0 | 1.0 | - -Observe the philosophical difference between the approaches taken by MacOS and Win32: -- Win32 coordinate system always deals in physical device pixels, high DPI support is achieved by providing an advisory hint for the developer to enlarge drawn objects. Ignoring the advisory scale factor results in graphics appearing tiny. -- MacOS coordinate system always deals in physical content sizes, high DPI support is achieved by providing an optional flag for the developer to request finer granularity. Omitting the granularity request results in graphics appearing coarse. - ## Explanation Displays now have a content display scale, which is the expected scale for content based on the DPI settings of the display. For example, a 4K display might have a 2.0 (200%) display scale, which means that the user expects UI elements to be twice as big on this display, to aid in readability. You can query the display content scale using `SDL_GetDisplayContentScale()`, and when this changes you get an `SDL_EVENT_WINDOW_DISPLAY_SCALE_CHANGED` event. @@ -34,3 +8,33 @@ Displays now have a content display scale, which is the expected scale for conte The window size is now distinct from the window pixel size, and the ratio between the two is the window pixel density. If the window is created with the `SDL_WINDOW_HIGH_PIXEL_DENSITY` flag, SDL will try to match the native pixel density for the display, otherwise it will try to have the pixel size match the window size. You can query the window pixel density using `SDL_GetWindowPixelDensity()`. You can query the window pixel size using `SDL_GetWindowSizeInPixels()`, and when this changes you get an `SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED` event. You are guaranteed to get a `SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED` event when a window is created and resized, and you can use this event to create and resize your graphics context for the window. The window has a display scale, which is the scale from the pixel resolution to the desired content size, e.g. the combination of the pixel density and the content scale. For example, a 3840x2160 window displayed at 200% on Windows, and a 1920x1080 window with the high density flag on a 2x display on macOS will both have a pixel size of 3840x2160 and a display scale of 2.0. You can query the window display scale using `SDL_GetWindowDisplayScale()`, and when this changes you get an `SDL_EVENT_WINDOW_DISPLAY_SCALE_CHANGED` event. + +## Numeric example + +Given a window spanning a 3840x2160 monitor set to 2x display or 200% scaling, the following tabulates the effect of creating a window with or without `SDL_WINDOW_HIGH_PIXEL_DENSITY` on macOS and Windows: + +| Value | macOS (Default) | macOS (HD) | Windows (Default & HD) | +|--------------------------------|-----------------|------------|------------------------| +| `SDL_GetWindowSize()` | 1920x1080 | 1920x1080 | 3840x2160 | +| `SDL_GetWindowSizeInPixels()` | 1920x1080 | 3840x2160 | 3840x2160 | +| `SDL_GetDisplayContentScale()` | 1.0 | 1.0 | 2.0 | +| `SDL_GetWindowDisplayScale()` | 1.0 | 2.0 | 2.0 | +| `SDL_GetWindowPixelDensity()` | 1.0 | 2.0 | 1.0 | + +Observe the difference between the approaches taken by macOS and Windows: +- The Windows and Android coordinate system always deals in physical device pixels, high DPI support is achieved by providing a content scale that tells the developer to draw objects larger. Ignoring this scale factor results in graphics appearing tiny. +- The macOS and iOS coordinate system always deals in window coordinates, high DPI support is achieved by providing an optional flag for the developer to request more pixels. Omitting this flag results in graphics having low detail. +- On Linux, X11 uses a similar approach to Windows and Wayland uses a similar approach to macOS. + +## Solution + +Proper high DPI support takes into account both the content scale and the pixel density. + +First, you'd create your window with the `SDL_WINDOW_HIGH_PIXEL_DENSITY` flag, assuming you want the highest detail possible. Then you'd get the window display scale to see how much your UI elements should be enlarged to be readable. + +If you're using the SDL 2D renderer, SDL provides the function `SDL_ConvertEventToRenderCoordinates()` to convert mouse coordinates between window coordinates and rendering coordinates, and the more general functions `SDL_RenderCoordinatesFromWindow()` and `SDL_RenderCoordinatesToWindow()` to do other conversion between them. + +If you're not using the 2D renderer, you can implement this yourself using `SDL_GetWindowPixelDensity()` as scale factor to convert from window coordinates to pixels. + +Finally you'll want to test on both Windows and macOS if possible to make sure your high DPI support works in all environments. + diff --git a/libs/SDL3/docs/README-linux.md b/libs/SDL3/docs/README-linux.md index 3f2d2c05..42891def 100644 --- a/libs/SDL3/docs/README-linux.md +++ b/libs/SDL3/docs/README-linux.md @@ -17,7 +17,7 @@ Ubuntu 18.04, all available features enabled: sudo apt-get install build-essential git make \ pkg-config cmake ninja-build gnome-desktop-testing libasound2-dev libpulse-dev \ libaudio-dev libjack-dev libsndio-dev libx11-dev libxext-dev \ - libxrandr-dev libxcursor-dev libxfixes-dev libxi-dev libxss-dev \ + libxrandr-dev libxcursor-dev libxfixes-dev libxi-dev libxss-dev libxtst-dev \ libxkbcommon-dev libdrm-dev libgbm-dev libgl1-mesa-dev libgles2-mesa-dev \ libegl1-mesa-dev libdbus-1-dev libibus-1.0-dev libudev-dev @@ -46,7 +46,8 @@ openSUSE Tumbleweed: libgbm-devel pipewire-devel libpulse-devel sndio-devel Mesa-libEGL-devel Arch: - sudo pacman -S alsa-lib cmake hidapi ibus jack libdecor libgl libpulse libusb libx11 libxcursor libxext libxinerama libxkbcommon libxrandr libxrender libxss mesa ninja pipewire sndio vulkan-driver vulkan-headers wayland wayland-protocols + + sudo pacman -S alsa-lib cmake hidapi ibus jack libdecor libgl libpulse libusb libx11 libxcursor libxext libxinerama libxkbcommon libxrandr libxrender libxss libxtst mesa ninja pipewire sndio vulkan-driver vulkan-headers wayland wayland-protocols Joystick does not work diff --git a/libs/SDL3/docs/README-migration.md b/libs/SDL3/docs/README-migration.md index deca6d0c..8b71b9d0 100644 --- a/libs/SDL3/docs/README-migration.md +++ b/libs/SDL3/docs/README-migration.md @@ -1459,7 +1459,7 @@ The following symbols have been removed: * SDL_RENDERER_PRESENTVSYNC - replaced with SDL_PROP_RENDERER_CREATE_PRESENT_VSYNC_NUMBER during renderer creation and SDL_PROP_RENDERER_VSYNC_NUMBER after renderer creation * SDL_RENDERER_SOFTWARE - you can check whether the name of the renderer is `SDL_SOFTWARE_RENDERER` * SDL_RENDERER_TARGETTEXTURE - all renderers support target texture functionality -* SDL_ScaleModeBest = use SDL_SCALEMODE_LINEAR instead +* SDL_ScaleModeBest - use SDL_SCALEMODE_LINEAR instead ## SDL_rwops.h diff --git a/libs/SDL3/docs/README-ngage.md b/libs/SDL3/docs/README-ngage.md index 84192b01..ab5b402c 100644 --- a/libs/SDL3/docs/README-ngage.md +++ b/libs/SDL3/docs/README-ngage.md @@ -1,5 +1,64 @@ -Support for the Nokia N-Gage has been removed from SDL3 (but will make a -comeback when newer compilers are available for the platform). +# Nokia N-Gage -SDL2 still supports this platform. +SDL port for the Nokia N-Gage +[Homebrew toolchain](https://github.com/ngagesdk/ngage-toolchain) +contributed by: +- [Michael Fitzmayer](https://github.com/mupfdev) + +- [Anonymous Maarten](https://github.com/madebr) + +Many thanks to: + +- icculus and slouken for always making room for us, even when we show up in 2025 + still waving the N-Gage flag. + +- The Nokia N-Gage [Discord community](https://discord.gg/dbUzqJ26vs) + who keeps the platform alive. + +- The staff and supporters of the + [Suomen pelimuseo](https://www.vapriikki.fi/nayttelyt/fantastinen-floppi/), and + to Heikki Jungmann, for their ongoing love and dedication for the Nokia N-Gage -- + you guys are awesome! + +## History + +When SDL support was discontinued due to the lack of C99 support at the time, +this version was rebuilt from the ground up after resolving the compiler issues. + +In contrast to the earlier SDL2 port, this version features a dedicated rendering +backend and a functional, albeit limited, audio interface. Support for the +software renderer has been removed. + +The outcome is a significantly leaner and more efficient SDL port, which we hope +will breathe new life into this beloved yet obscure platform. + +## To the Stubborn Legends of the DC Scene + +This port is lovingly dedicated to the ever-nostalgic Dreamcast homebrew scene -- +because if we managed to pull this off for the N-Gage (yes, the N-Gage), surely +you guys can stop clinging to SDL2 like it's a rare Shenmue prototype and finally +make the leap to SDL3. It's 2025, not 1999 -- and let's be honest, you're rocking +a state-of-the-art C23 compiler. The irony writes itself. + +## Existing Issues and Limitations + +- For now, the new + [SDL3 main callbacks](https://wiki.libsdl.org/SDL3/README/main-functions#how-to-use-main-callbacks-in-sdl3) + are not optional and must be used. This is important as the callbacks + are optional on other platforms. + +- If the application is put in the background while sound is playing, + some of the audio is looped until the app is back in focus. + +- It is recommended initialising SDLs audio sub-system even when it + is not required. The backend is started at a higher level. Initialising + SDLs audio sub-system ensures that the backend is properly deinitialised. + +- Because the audio sample rate can change during phone calls, the sample + rate is currently fixed at 8kHz to ensure stable behavior. Although + dynamically adjusting the sample rate is theoretically possible, the + current implementation doesn't support it yet. This limitation is + expected to be resolved in a future update. + +- Dependency tracking is currently non-functional. diff --git a/libs/SDL3/docs/README-platforms.md b/libs/SDL3/docs/README-platforms.md index 77ae411d..765cace3 100644 --- a/libs/SDL3/docs/README-platforms.md +++ b/libs/SDL3/docs/README-platforms.md @@ -11,7 +11,7 @@ - [macOS](README-macos.md) - [NetBSD](README-bsd.md) - [Nintendo Switch](README-switch.md) -- [Nintendo 3DS](README-3ds.md) +- [Nintendo 3DS](README-n3ds.md) - [OpenBSD](README-bsd.md) - [PlayStation 2](README-ps2.md) - [PlayStation 4](README-ps4.md) @@ -24,6 +24,7 @@ - [Windows](README-windows.md) - [Windows GDK](README-gdk.md) - [Xbox](README-gdk.md) +- [Nokia N-Gage](README-ngage.md) ## Unsupported Platforms @@ -33,7 +34,6 @@ All of these still work with [SDL2](/SDL2), which is an incompatible API, but an - Google Stadia - NaCL -- Nokia N-Gage - OS/2 - QNX - WinPhone diff --git a/libs/SDL3/docs/README-psp.md b/libs/SDL3/docs/README-psp.md index 65d2af29..1dd75ced 100644 --- a/libs/SDL3/docs/README-psp.md +++ b/libs/SDL3/docs/README-psp.md @@ -1,13 +1,13 @@ PSP ====== SDL port for the Sony PSP contributed by: -- Captian Lex +- Captain Lex - Francisco Javier Trujillo Mata - Wouter Wijsman Credit to - Marcus R.Brown,Jim Paris,Matthew H for the original SDL 1.2 for PSP + Marcus R.Brown, Jim Paris, Matthew H for the original SDL 1.2 for PSP Geecko for his PSP GU lib "Glib2d" ## Building diff --git a/libs/SDL3/docs/README-versions.md b/libs/SDL3/docs/README-versions.md index 097dba1c..6c2d920c 100644 --- a/libs/SDL3/docs/README-versions.md +++ b/libs/SDL3/docs/README-versions.md @@ -1,60 +1,48 @@ # Versioning -## Since 2.23.0 +## Since 3.2.0 SDL follows an "odd/even" versioning policy, similar to GLib, GTK, Flatpak and older versions of the Linux kernel: -* The major version (first part) increases when backwards compatibility - is broken, which will happen infrequently. - -* If the minor version (second part) is divisible by 2 - (for example 2.24.x, 2.26.x), this indicates a version of SDL that - is believed to be stable and suitable for production use. +* If the minor version (second part) and the patch version (third part) is + divisible by 2 (for example 3.2.6, 3.4.0), this indicates a version of + SDL that is believed to be stable and suitable for production use. * In stable releases, the patchlevel or micro version (third part) - indicates bugfix releases. Bugfix releases should not add or - remove ABI, so the ".0" release (for example 2.24.0) should be - forwards-compatible with all the bugfix releases from the - same cycle (for example 2.24.1). + indicates bugfix releases. Bugfix releases may add small changes + to the ABI, so newer patch versions are backwards-compatible but + not fully forwards-compatible. For example, programs built against + SDL 3.2.0 should work fine with SDL 3.2.8, but programs built against + SDL 3.2.8 may not work with 3.2.0. - * The minor version increases when new API or ABI is added, or when - other significant changes are made. Newer minor versions are - backwards-compatible, but not fully forwards-compatible. - For example, programs built against SDL 2.24.x should work fine - with SDL 2.26.x, but programs built against SDL 2.26.x will not - necessarily work with 2.24.x. + * The minor version increases when significant changes are made that + require longer development or testing time, e.g. major new functionality, + or revamping support for a platform. Newer minor versions are + backwards-compatible, but not fully forwards-compatible. For example, + programs built against SDL 3.2.x should work fine with SDL 3.4.x, + but programs built against SDL 3.4.x may not work with 3.2.x. -* If the minor version (second part) is not divisible by 2 - (for example 2.23.x, 2.25.x), this indicates a development prerelease - of SDL that is not suitable for stable software distributions. +* If the minor version (second part) or patch version (third part) is not + divisible by 2 (for example 3.2.9, 3.3.x), this indicates a development + prerelease of SDL that is not suitable for stable software distributions. Use with caution. - * The patchlevel or micro version (third part) increases with - each prerelease. - - * Each prerelease might add new API and/or ABI. + * The patchlevel or micro version (third part) increases with each prerelease. * Prereleases are backwards-compatible with older stable branches. - For example, 2.25.x will be backwards-compatible with 2.24.x. + For example, programs built against SDL 3.2.x should work fine with + SDL 3.3.x, but programs built against SDL 3.3.x may not work with 3.2.x. - * Prereleases are not guaranteed to be backwards-compatible with - each other. For example, new API or ABI added in 2.25.1 - might be removed or changed in 2.25.2. - If this would be a problem for you, please do not use prereleases. + * Prereleases are not guaranteed to be backwards-compatible with each other. + For example, new API or ABI added in 3.3.0 might be removed or changed in + 3.3.1. If this would be a problem for you, please do not use prereleases. - * Only upgrade to a prerelease if you can guarantee that you will - promptly upgrade to the stable release that follows it. - For example, do not upgrade to 2.23.x unless you will be able to - upgrade to 2.24.0 when it becomes available. + * Only use a prerelease if you can guarantee that you will promptly upgrade + to the stable release that follows it. For example, do not use 3.3.x + unless you will be able to upgrade to 3.4.0 when it becomes available. * Software distributions that have a freeze policy (in particular Linux distributions with a release cycle, such as Debian and Fedora) - should usually only package stable releases, and not prereleases. + should only package stable releases, and not prereleases. -## Before 2.23.0 - -Older versions of SDL followed a similar policy, but instead of the -odd/even rule applying to the minor version, it applied to the patchlevel -(micro version, third part). For example, 2.0.22 was a stable release -and 2.0.21 was a prerelease. diff --git a/libs/SDL3/docs/README-wayland.md b/libs/SDL3/docs/README-wayland.md index 75a9b906..a3cd06f7 100644 --- a/libs/SDL3/docs/README-wayland.md +++ b/libs/SDL3/docs/README-wayland.md @@ -59,6 +59,15 @@ encounter limitations or behavior that is different from other windowing systems `SDL_APP_ID` hint string, the desktop entry file name should match the application ID. For example, if your application ID is set to `org.my_org.sdl_app`, the desktop entry file should be named `org.my_org.sdl_app.desktop`. +### The application progress bar can't be set via ```SDL_SetWindowProgressState()``` or ```SDL_SetWindowProgressValue()``` + +- Only some Desktop Environemnts support the underlying API. Known compatible DEs: Unity, KDE +- The underlying API requires a desktop entry file, aka a `.desktop` file. + Please see the [Desktop Entry Specification](https://specifications.freedesktop.org/desktop-entry-spec/latest/) for + more information on the format of this file. Note that if your application manually sets the application ID via the + `SDL_APP_ID` hint string, the desktop entry file name should match the application ID. For example, if your + application ID is set to `org.my_org.sdl_app`, the desktop entry file should be named `org.my_org.sdl_app.desktop`. + ### Keyboard grabs don't work when running under XWayland - On GNOME based desktops, the dconf setting `org/gnome/mutter/wayland/xwayland-allow-grabs` must be enabled. diff --git a/libs/SDL3/docs/release_checklist.md b/libs/SDL3/docs/release_checklist.md index 56fb23d0..5c19fc47 100644 --- a/libs/SDL3/docs/release_checklist.md +++ b/libs/SDL3/docs/release_checklist.md @@ -15,6 +15,8 @@ * Create a GitHub release and attach the archives you just generated. +* If this is a feature release, also tag the sdlwiki with the same tag. + ## New feature release * Update `WhatsNew.txt` diff --git a/libs/SDL3/examples/CMakeLists.txt b/libs/SDL3/examples/CMakeLists.txt index 2a13a038..1c5a40e6 100644 --- a/libs/SDL3/examples/CMakeLists.txt +++ b/libs/SDL3/examples/CMakeLists.txt @@ -111,6 +111,10 @@ macro(add_sdl_example_executable TARGET) elseif(EMSCRIPTEN) set_property(TARGET ${TARGET} PROPERTY SUFFIX ".html") target_link_options(${TARGET} PRIVATE -sALLOW_MEMORY_GROWTH=1) + elseif(NGAGE) + string(MD5 TARGET_MD5 "${TARGET}") + string(SUBSTRING "${TARGET_MD5}" 0 8 TARGET_MD5_8) + target_link_options(${TARGET} PRIVATE "SHELL:-s UID3=0x${TARGET_MD5_8}") endif() if(OPENGL_FOUND) @@ -136,10 +140,12 @@ add_sdl_example_executable(renderer-viewport SOURCES renderer/14-viewport/viewpo add_sdl_example_executable(renderer-cliprect SOURCES renderer/15-cliprect/cliprect.c DATAFILES ${CMAKE_CURRENT_SOURCE_DIR}/../test/sample.bmp) add_sdl_example_executable(renderer-read-pixels SOURCES renderer/17-read-pixels/read-pixels.c DATAFILES ${CMAKE_CURRENT_SOURCE_DIR}/../test/sample.bmp) add_sdl_example_executable(renderer-debug-text SOURCES renderer/18-debug-text/debug-text.c) +add_sdl_example_executable(renderer-affine-textures SOURCES renderer/19-affine-textures/affine-textures.c DATAFILES ${CMAKE_CURRENT_SOURCE_DIR}/../test/sample.bmp) add_sdl_example_executable(audio-simple-playback SOURCES audio/01-simple-playback/simple-playback.c) add_sdl_example_executable(audio-simple-playback-callback SOURCES audio/02-simple-playback-callback/simple-playback-callback.c) add_sdl_example_executable(audio-load-wav SOURCES audio/03-load-wav/load-wav.c DATAFILES ${CMAKE_CURRENT_SOURCE_DIR}/../test/sample.wav) add_sdl_example_executable(audio-multiple-streams SOURCES audio/04-multiple-streams/multiple-streams.c DATAFILES ${CMAKE_CURRENT_SOURCE_DIR}/../test/sample.wav ${CMAKE_CURRENT_SOURCE_DIR}/../test/sword.wav) +add_sdl_example_executable(audio-planar-data SOURCES audio/05-planar-data/planar-data.c) add_sdl_example_executable(input-joystick-polling SOURCES input/01-joystick-polling/joystick-polling.c) add_sdl_example_executable(input-joystick-events SOURCES input/02-joystick-events/joystick-events.c) add_sdl_example_executable(camera-read-and-draw SOURCES camera/01-read-and-draw/read-and-draw.c) diff --git a/libs/SDL3/examples/audio/04-multiple-streams/README.txt b/libs/SDL3/examples/audio/04-multiple-streams/README.txt index 9ef2275b..ad7f5f8a 100644 --- a/libs/SDL3/examples/audio/04-multiple-streams/README.txt +++ b/libs/SDL3/examples/audio/04-multiple-streams/README.txt @@ -1,5 +1,5 @@ If you're running this in a web browser, you need to click the window before you'll hear anything! -This example code loads two .wav files, puts them an audio streams and binds +This example code loads two .wav files, puts them in audio streams and binds them for playback, repeating both sounds on loop. This shows several streams mixing into a single playback device. diff --git a/libs/SDL3/examples/audio/04-multiple-streams/multiple-streams.c b/libs/SDL3/examples/audio/04-multiple-streams/multiple-streams.c index 8d3bfaa0..188023c8 100644 --- a/libs/SDL3/examples/audio/04-multiple-streams/multiple-streams.c +++ b/libs/SDL3/examples/audio/04-multiple-streams/multiple-streams.c @@ -1,5 +1,5 @@ /* - * This example code loads two .wav files, puts them an audio streams and + * This example code loads two .wav files, puts them in audio streams and * binds them for playback, repeating both sounds on loop. This shows several * streams mixing into a single playback device. * diff --git a/libs/SDL3/examples/audio/05-planar-data/README.txt b/libs/SDL3/examples/audio/05-planar-data/README.txt new file mode 100644 index 00000000..c7bd0ab5 --- /dev/null +++ b/libs/SDL3/examples/audio/05-planar-data/README.txt @@ -0,0 +1,7 @@ +This example code draws two clickable buttons. Each causes a sound to play, +fed to either the left or right audio channel through separate (planar) +arrays. + +Planar audio can feed both channels at the same time from different arrays, +as well, but this example only uses one channel at a time for clarity. A +NULL array will supply silence for that channel. diff --git a/libs/SDL3/examples/audio/05-planar-data/onmouseover.webp b/libs/SDL3/examples/audio/05-planar-data/onmouseover.webp new file mode 100644 index 0000000000000000000000000000000000000000..90880c9e4544826bd5afa597497eed33ea79a96e GIT binary patch literal 17488 zcmeHO2{@Er+n*U*$dY|u%I41}5o|LG-*L^Z^P--{#QLj4(WY$?SVnR`DF8)p=qREb9R*b8 zoUq<7EIMkat=!#NT=16)H_%>|h-e!#_`MUMeNdcu$8M)OcV}+@PG903go?~8U`1OU zQr|t3+kdCxK4A@*RF>q)i1^PYDR|SBvo+iJg!GSF^S?B_H4<{EztH)0n#;b42X3`T zT**2Y;)SOY5ou_$hL4`5#Vr_M8Gz*-Y+6V88KUBap0t@7C`^%$F^(6IH}4qFh;l-I>R;pQ zkK*gmzLxhxaA)02%;y-qjf!k}>Q?p3QuU%nDd3H+@1GKOFwpf~-gXRh!c&Ah!a~L? zZuG*A?~1dLVJn+3Y*k8G^o^*YpH3KS^@okYu~qszP9wPBp#rzR$|`K^RCe9(`(2Cm zS6Mp-taWGW34wv1%&H6U&abWC-6teOr*HorK&oeDS?;t_5#ne!bg&OLl03zF&ZGTy zS`H@x5i%%M;$3~svt%!tBG(DQqexy>l&di3OpFw7S|B{DhJanzd|XEK0T8eafFW1l zpsh`0(#|(z?*hTD_L)F!Q*I7%Y_}i1_DJ!A&2%>Sl!k(6V9U3s+ZoZ-%WHy>Wj|hT zf3f=hVsk!}VLh;b9g|0|Em9X#U(suwqft7X=WIZ&7s9RwejLYlxLvDiY;@`!u8HYM z=aF2nu_8iqOGeFbHGkqw8OB!YcCE9WSv{8+h+?7A4t^uAx*oWHPn;Wt(qWm#Q^(<)?Fi|Wd_tD^wwAs zt%6l_NZT~We8$_0sfuiI7!dEQHYoG|-m9%Zq7=g^1x(HOrw zD}z#!JXMEkWFi_Xo0`rrYzKR@$1B_fJE#WnlpN|bew_~v6z~0#5{OdCO-ch{h0?f2 zYGMQ)pVAVLH}Kt%O$Jfx>3={9H&OJX;|NYm2I~OwwLz@$JI&P3h+Gl zWb~m`kzho$T{KH1@}m|;anoUCRAHmWY)R-~4?QE~+SWmMg#Fz-bd8sO2brzf2Ad?zq*5HN;(}P2ZL6_)w}fiF%&voZsNOdJBxQhz3HPmi z4?dhRV*I9DzVwl<8o-AB@w=JnM7)}n{%*WQhJD(^iQ4b_LR>Yy9YB1-u|$(V%N61q@UKxfc`6ZoD}8s_`G@^>`DKkRtl^k z4WfEcc6&(xH@C38`7KYLBNP_8!F4n@Gzj;0%QgVBb-M2mv-18|A6t6Qv-aP}cM=k? zC{%3wO4e$b!akC!W^_rM16x}7Mn&sjHFnh{j5&>he}$(nQBHF5PM<*=MX0(P`FrUz ztmDC(8O*x%=4TTqp^TRpK)cLm!|&(|JEZyj`tMnt>)Sdx%T@I}KrAPSywJo;p7xIgDxP>~!cD*H?OKMh zm8C6)HqHU#=S{r=%HCfXTc|JB-Ee11xNUo2g!9<>;c-Vvs_m*HG0iF3V8dax+m?yX z6Vt|vygi5B$p3(Yf{&vW!>t@N`2@AouWMXD+pM_AftZpL)4>Nol42){u;zuwC)=94XS(bqLq$QHT zul-NV#(vW97486!foEhh!4*%o9*ZsukAbadKx*j7H*9~Hzi_CJ`G0fSuOoSU@#WO z^qh}_|ADg4R7Zv6Y4)WNb)Z-fx_}T|A)^n{XfHBnwr&JQ2ypvUBW=8zOy=2}cJuP> zo{rPYQ(ak_OYq(@X&gVak}xv0dirzL4`(_f$oDZaQ>)jp8zvM~HAfO{S&`#u8xTxxw9%}Z=qJknKR z{CsQiwaLO@mwMJU1kFJ7!(47q7lL<+KZ3FT z!~3cfT48n3(yB^jhT8pt}Cw>YC`k=D$Rxb~K4{NpVB< zQF+)Z7w5OeT@`Rg3=9GUwZGN)JTmbVW>4@ViZTF=*<&wzOzL+6wWBBk!qMMqd5FO| zmOVoAQ+Awu^_vDsP>M>0OH2Thc4Z^HcsJOlbe+Jeem5!QNYqe_W1h15rLWM~fy{;l zbp=2SwCj+)2Iy#`JVoHiHNCsbt;HP%PvaI#yG6NoB+9ECvX?(`BpKanM$xSdHe)m$ zA#j64qI&gOWK1pH@g6}<6F`GpkHa8=4hLC(ihS@3C#5nVgQRP#B)cGRrTpPKG^k)y%Dx~EDAR_*na7&o)Kumwr;dc92bOGhy}Rs`g|VWYPQ6Ygnn_^neUFP`?I+);V=KA9@VtDW6|PXhW7JFSOL z(3Ou9d{{!G0yu^!0+*W5AEYbCS6%6MX*EH{BLvP6{nDmuOHBTWb!l=5Bx0LpHb zKorCs5DYbc?5WL#0H^jg*67`%xVCJN)HtuCTPGY-FhScyDjR@XJ&M>(ev6fSHZeij>xrx?lWhn`Sj}?Ax(w2(_v%b4BIz+JXmVZ z6^xs#xWF5)h+I53noNHlx=B_M;qY(_fm!)Av z@+>;!k#bmcWuXT0wLc?;1*o$|-|Txz%xzum#^~!;ggXOMkM!pB%=6NFBBkg1BJQ&B z2_nu;yS5@k&72#lnrC=8A01&S6J!Lr`Uc<*C$=WAPHOz|Nh0mi!w+wjG~5Q^B{D%pmOYwCPh^X!B*WxeeJ{Nr31bvuHA^}efK;Axoj0YDL|=RP>MVJ=)2Y{K@-|>I{qm7aV6$tzDIWI7<~IYCY~|N4Pab@x9mmG?;nd?b>pZrz;Z`|m z1&v3+Tx$z5F^(0Lv6BpJtH*NFl0IAl-TyzvVgY2R6uSvwmkRNKAL=|On4}_M#d}UT zuT3?4*iK)%nF80Nt z7^(#Lba}g|)y>K^0*%4N>__@B!4wao=3=|OcGCm9$~GIs=eoX>1_t4Pev zZXH;BQ2jb;O(uun5ZyrirK1;HUWIyn&FdkU=GKl|jNxPRoWu6|4Nj!qjVkgkJnA*g z8^go{gU6dD{ji9`KiOFkKHgf5-@Q|AFy7+U)3m^o9@U)azK~CV4+^1HfK|Y%t)MNr z5d@!Bg`Qb94sZpWZ)mETw;^HQZ^mXh7(skOWW<{|Mewp~7uffu2&6lr9(%lCQwHy+ zSuQ(haYhX=YyM?3*8EL;h&Vgc;|B-kxPaR6+~-kS{Bpw~WFHG9LkN-8VWm$TE2bFb zM2wjMxf*K(-eCnj7*o~q4Sck)-LrtPWdP)I+bp6WB5WYyv5aY8rN|2eD{p%BbO;slp3n~88QU_jsOR>3I|iZ8Te9Gs zD!=qh_(Y&*uUeN9qCfSjLPdeHdQE4mZi8XvNpaEDmQXH-p2 z+J8_IK$#^M_IV#lh&ynj=)lu|q6hkWN_aOJ2mW`lEd8*=LeWPM`> zt*>CNzAv*LWR{bvuj8-rS%$OM=L!gTXl6se1BiVz?{unI zN_{ocjg(K)GlD44 +#include + +/* We will use this renderer to draw into this window every frame. */ +static SDL_Window *window = NULL; +static SDL_Renderer *renderer = NULL; +static SDL_AudioStream *stream = NULL; + +/* location of buttons on the screen. */ +static const SDL_FRect rect_left_button = { 100, 170, 100, 100 }; +static const SDL_FRect rect_right_button = { 440, 170, 100, 100 }; + +/* -1 if we're currently playing left, 1 if playing right, 0 if not playing. */ +static int playing_sound = 0; + +/* Raw audio data. These arrays are at the end of the source file. */ +static const Uint8 left[1870]; +static const Uint8 right[1777]; + + +/* This function runs once at startup. */ +SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) +{ + const SDL_AudioSpec spec = { SDL_AUDIO_U8, 2, 4000 }; /* Uint8 data, stereo, 4000Hz. */ + + SDL_SetAppMetadata("Example Audio Planar Data", "1.0", "com.example.audio-planar-data"); + + if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO)) { + SDL_Log("Couldn't initialize SDL: %s", SDL_GetError()); + return SDL_APP_FAILURE; + } + + if (!SDL_CreateWindowAndRenderer("examples/audio/planar-data", 640, 480, 0, &window, &renderer)) { + SDL_Log("Couldn't create window/renderer: %s", SDL_GetError()); + return SDL_APP_FAILURE; + } + + stream = SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &spec, NULL, NULL); + if (!stream) { + SDL_Log("Couldn't open audio device stream: %s", SDL_GetError()); + return SDL_APP_FAILURE; + } + + SDL_ResumeAudioStreamDevice(stream); /* SDL_OpenAudioDeviceStream starts the device paused. Resume it! */ + + return SDL_APP_CONTINUE; /* carry on with the program! */ +} + +/* This function runs when a new event (mouse input, keypresses, etc) occurs. */ +SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event) +{ + if (event->type == SDL_EVENT_QUIT) { + return SDL_APP_SUCCESS; /* end the program, reporting success to the OS. */ + } else if (event->type == SDL_EVENT_MOUSE_BUTTON_DOWN) { + if (playing_sound == 0) { /* nothing currently playing? */ + const SDL_FPoint point = { event->button.x, event->button.y }; + if (SDL_PointInRectFloat(&point, &rect_left_button)) { /* clicked left button? */ + const Uint8 *planes[] = { left, NULL }; /* specify NULL to say "this specific channel is silent" */ + SDL_PutAudioStreamPlanarData(stream, (const void * const *) planes, -1, SDL_arraysize(left)); + SDL_FlushAudioStream(stream); /* that's all we're playing until it completes. */ + playing_sound = -1; /* left is playing */ + } else if (SDL_PointInRectFloat(&point, &rect_right_button)) { /* clicked right button? */ + const Uint8 *planes[] = { NULL, right }; /* specify NULL to say "this specific channel is silent" */ + SDL_PutAudioStreamPlanarData(stream, (const void * const *) planes, -1, SDL_arraysize(right)); + SDL_FlushAudioStream(stream); /* that's all we're playing until it completes. */ + playing_sound = 1; /* right is playing */ + } + } + } + + return SDL_APP_CONTINUE; /* carry on with the program! */ +} + +static void render_button(const SDL_FRect *rect, const char *str, int button_value) +{ + float x, y; + + if (playing_sound == button_value) { + SDL_SetRenderDrawColor(renderer, 0, 255, 0, 255); /* green while playing */ + } else { + SDL_SetRenderDrawColor(renderer, 0, 0, 255, 255); /* blue while not playing */ + } + + SDL_RenderFillRect(renderer, rect); + SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255); + + x = rect->x + ((rect->w - (SDL_DEBUG_TEXT_FONT_CHARACTER_SIZE * SDL_strlen(str))) / 2.0f); + y = rect->y + ((rect->h - SDL_DEBUG_TEXT_FONT_CHARACTER_SIZE) / 2.0f); + SDL_RenderDebugText(renderer, x, y, str); +} + +/* This function runs once per frame, and is the heart of the program. */ +SDL_AppResult SDL_AppIterate(void *appstate) +{ + if (playing_sound) { + if (SDL_GetAudioStreamQueued(stream) == 0) { /* sound is done? We can play a new sound now. */ + playing_sound = 0; + } + } + + SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); + SDL_RenderClear(renderer); + + render_button(&rect_left_button, "LEFT", -1); + render_button(&rect_right_button, "RIGHT", 1); + + SDL_RenderPresent(renderer); + + return SDL_APP_CONTINUE; /* carry on with the program! */ +} + +/* This function runs once at shutdown. */ +void SDL_AppQuit(void *appstate, SDL_AppResult result) +{ + SDL_DestroyAudioStream(stream); + /* SDL will clean up the window/renderer for us. */ +} + + + +/* This is the audio data, as raw PCM samples (Uint8, 1 channel, 4000Hz) packed into C byte arrays for convenience. */ + +static const Uint8 left[1870] = { + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x80, 0x80, 0x81, 0x80, 0x81, 0x82, 0x82, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x82, 0x82, 0x81, 0x80, 0x80, 0x80, 0x7f, 0x7e, 0x7e, 0x7e, 0x7d, + 0x7b, 0x7b, 0x7b, 0x7b, 0x7c, 0x7d, 0x7d, 0x7e, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x85, 0x84, + 0x84, 0x83, 0x81, 0x7f, 0x7d, 0x7c, 0x7a, 0x7a, 0x7a, 0x77, 0x77, 0x77, 0x76, 0x76, 0x76, 0x77, + 0x78, 0x7d, 0x82, 0x89, 0x8e, 0x92, 0x95, 0x95, 0x91, 0x8b, 0x84, 0x7d, 0x77, 0x73, 0x72, 0x72, + 0x74, 0x75, 0x75, 0x75, 0x76, 0x74, 0x73, 0x73, 0x74, 0x79, 0x81, 0x89, 0x8f, 0x96, 0x9b, 0x9c, + 0x98, 0x91, 0x88, 0x7e, 0x77, 0x74, 0x73, 0x74, 0x77, 0x7b, 0x7c, 0x7a, 0x77, 0x73, 0x6d, 0x69, + 0x68, 0x6a, 0x73, 0x7f, 0x87, 0x8e, 0x99, 0xa1, 0x9e, 0x97, 0x90, 0x86, 0x7c, 0x76, 0x77, 0x7b, + 0x80, 0x89, 0x91, 0x93, 0x91, 0x8e, 0x87, 0x7c, 0x71, 0x6b, 0x65, 0x60, 0x5d, 0x5f, 0x60, 0x61, + 0x6b, 0x7b, 0x84, 0x8d, 0xa0, 0xae, 0xae, 0xa8, 0xa1, 0x94, 0x81, 0x73, 0x6f, 0x70, 0x74, 0x7e, + 0x8d, 0x95, 0x97, 0x98, 0x92, 0x83, 0x72, 0x69, 0x61, 0x5a, 0x56, 0x59, 0x5d, 0x5f, 0x65, 0x75, + 0x82, 0x87, 0x95, 0xaa, 0xb4, 0xb0, 0xaa, 0xa0, 0x8d, 0x77, 0x6c, 0x6c, 0x6d, 0x72, 0x81, 0x91, + 0x98, 0x9a, 0x9a, 0x8f, 0x7a, 0x6a, 0x61, 0x58, 0x4f, 0x50, 0x57, 0x5b, 0x61, 0x74, 0x85, 0x8a, + 0x96, 0xab, 0xb4, 0xae, 0xa5, 0x9c, 0x88, 0x71, 0x67, 0x69, 0x6c, 0x73, 0x85, 0x96, 0x9d, 0xa1, + 0xa3, 0x96, 0x7f, 0x6e, 0x63, 0x56, 0x4c, 0x4d, 0x52, 0x53, 0x58, 0x6b, 0x80, 0x86, 0x92, 0xaa, + 0xb8, 0xb4, 0xac, 0xa5, 0x90, 0x75, 0x69, 0x6a, 0x6c, 0x73, 0x86, 0x98, 0x9c, 0xa2, 0xa7, 0x99, + 0x7f, 0x6e, 0x61, 0x54, 0x4c, 0x4b, 0x4d, 0x4f, 0x54, 0x66, 0x7c, 0x85, 0x90, 0xa9, 0xbc, 0xba, + 0xb4, 0xac, 0x95, 0x78, 0x69, 0x67, 0x67, 0x71, 0x86, 0x99, 0x9d, 0xa4, 0xab, 0x9b, 0x7f, 0x6e, + 0x5f, 0x50, 0x4b, 0x4e, 0x4e, 0x4e, 0x54, 0x60, 0x77, 0x86, 0x8e, 0xa4, 0xbb, 0xbf, 0xb9, 0xb3, + 0x9e, 0x7d, 0x68, 0x65, 0x63, 0x6b, 0x84, 0x9a, 0x9d, 0xa3, 0xb0, 0x9f, 0x83, 0x71, 0x5f, 0x4d, + 0x4c, 0x51, 0x51, 0x51, 0x56, 0x5a, 0x64, 0x7d, 0x90, 0x99, 0xad, 0xc3, 0xc2, 0xb5, 0xaa, 0x92, + 0x71, 0x62, 0x65, 0x6a, 0x78, 0x92, 0xa2, 0xa1, 0xa7, 0xa8, 0x91, 0x78, 0x66, 0x55, 0x4a, 0x50, + 0x54, 0x50, 0x50, 0x58, 0x5a, 0x65, 0x8b, 0x9b, 0x9b, 0xb7, 0xc9, 0xb3, 0xa6, 0xa2, 0x7d, 0x5a, + 0x66, 0x6f, 0x70, 0x94, 0xa2, 0x90, 0x9b, 0xa5, 0x8f, 0x82, 0x77, 0x5c, 0x58, 0x60, 0x50, 0x46, + 0x56, 0x49, 0x3a, 0x54, 0x97, 0xbe, 0xa9, 0xb0, 0xad, 0x91, 0xa7, 0xb3, 0x83, 0x6f, 0x6c, 0x5b, + 0x71, 0x91, 0x9c, 0xac, 0x98, 0x78, 0x8a, 0xa6, 0xad, 0x9e, 0x72, 0x4d, 0x4e, 0x4f, 0x4e, 0x4a, + 0x48, 0x46, 0x42, 0x4e, 0x99, 0xd5, 0xae, 0xb0, 0xb1, 0x8a, 0xb3, 0xbd, 0x82, 0x6b, 0x53, 0x56, + 0x8b, 0x97, 0xa7, 0xaf, 0x74, 0x6b, 0x92, 0xaf, 0xc1, 0x8f, 0x55, 0x47, 0x4e, 0x60, 0x5e, 0x45, + 0x4a, 0x4f, 0x3a, 0x44, 0x9f, 0xdf, 0xac, 0xa8, 0x93, 0x79, 0xbf, 0xc3, 0x92, 0x67, 0x36, 0x5a, + 0x90, 0x9b, 0xb6, 0xa1, 0x6b, 0x68, 0x8d, 0xc3, 0xca, 0x83, 0x4f, 0x3d, 0x53, 0x72, 0x63, 0x46, + 0x44, 0x55, 0x4f, 0x4c, 0x78, 0xcb, 0xbb, 0x93, 0x99, 0x79, 0xad, 0xd0, 0x9f, 0x70, 0x37, 0x4f, + 0x90, 0x9e, 0xaf, 0x94, 0x73, 0x71, 0x89, 0xc0, 0xc0, 0x8f, 0x5b, 0x45, 0x62, 0x79, 0x6f, 0x5b, + 0x46, 0x56, 0x54, 0x53, 0x59, 0x90, 0xd8, 0x95, 0x8c, 0x8c, 0x88, 0xd6, 0xb8, 0x83, 0x4c, 0x2f, + 0x80, 0xa2, 0xaa, 0x9c, 0x69, 0x74, 0x80, 0xb0, 0xc6, 0x99, 0x78, 0x54, 0x69, 0x80, 0x7c, 0x69, + 0x4b, 0x4e, 0x57, 0x4e, 0x4c, 0x5f, 0xae, 0xc3, 0x82, 0x86, 0x83, 0xac, 0xd9, 0xa3, 0x6a, 0x31, + 0x50, 0xa0, 0xad, 0xa6, 0x6d, 0x59, 0x7f, 0x9e, 0xc8, 0xaf, 0x81, 0x74, 0x70, 0x8b, 0x83, 0x76, + 0x58, 0x50, 0x56, 0x59, 0x58, 0x49, 0x62, 0x7c, 0xce, 0x99, 0x71, 0x9c, 0x8d, 0xd4, 0xb1, 0x6c, + 0x4f, 0x37, 0x95, 0xab, 0x9b, 0x7f, 0x4b, 0x82, 0xa2, 0xba, 0xb5, 0x7b, 0x7d, 0x7d, 0x8d, 0x8b, + 0x71, 0x62, 0x54, 0x5b, 0x4e, 0x5d, 0x4c, 0x5e, 0x57, 0x9c, 0xd4, 0x67, 0x94, 0x83, 0xa2, 0xd8, + 0x83, 0x70, 0x2e, 0x59, 0xb5, 0x9d, 0xa1, 0x51, 0x55, 0x97, 0xad, 0xcb, 0x86, 0x77, 0x78, 0x95, + 0xa1, 0x76, 0x6d, 0x58, 0x67, 0x5b, 0x4f, 0x66, 0x55, 0x67, 0x4e, 0x67, 0xd9, 0x88, 0x89, 0x86, + 0x6f, 0xcd, 0x9b, 0x89, 0x4e, 0x39, 0x9f, 0xa0, 0xa9, 0x7a, 0x47, 0x88, 0x99, 0xbe, 0xac, 0x6b, + 0x88, 0x87, 0xaf, 0x9a, 0x67, 0x71, 0x63, 0x74, 0x62, 0x55, 0x5c, 0x5e, 0x65, 0x5c, 0x54, 0xb1, + 0xb0, 0x79, 0x8d, 0x6f, 0xac, 0xb7, 0x8e, 0x73, 0x44, 0x7b, 0xa1, 0x99, 0x90, 0x5a, 0x70, 0x97, + 0xa0, 0xb4, 0x89, 0x83, 0x8e, 0x96, 0xa3, 0x7e, 0x6f, 0x6c, 0x6a, 0x6b, 0x5b, 0x5a, 0x61, 0x5e, + 0x5d, 0x63, 0x66, 0xa0, 0xa6, 0x7c, 0x8d, 0x83, 0xa4, 0xad, 0x88, 0x7b, 0x58, 0x75, 0x95, 0x91, + 0x92, 0x70, 0x75, 0x93, 0x9c, 0xab, 0x92, 0x84, 0x8d, 0x91, 0x96, 0x81, 0x70, 0x6b, 0x6c, 0x68, + 0x62, 0x59, 0x5e, 0x69, 0x5a, 0x5a, 0x68, 0x5f, 0xa2, 0xb0, 0x6d, 0x87, 0x7e, 0xa0, 0xba, 0x89, + 0x78, 0x53, 0x73, 0xa6, 0x9b, 0x95, 0x6c, 0x65, 0x8e, 0x9a, 0xab, 0x97, 0x7b, 0x85, 0x8e, 0x9a, + 0x91, 0x71, 0x6b, 0x68, 0x65, 0x6e, 0x58, 0x5d, 0x70, 0x5d, 0x6d, 0x67, 0x5e, 0x80, 0x78, 0x94, + 0x98, 0x7c, 0x96, 0x90, 0xa1, 0xa5, 0x82, 0x7f, 0x70, 0x7e, 0x94, 0x87, 0x87, 0x80, 0x88, 0x92, + 0x8e, 0x96, 0x8c, 0x89, 0x84, 0x73, 0x72, 0x6f, 0x71, 0x6d, 0x5e, 0x61, 0x6a, 0x70, 0x77, 0x6f, + 0x6d, 0x79, 0x76, 0x7f, 0x77, 0x75, 0x7e, 0x90, 0xa8, 0x8c, 0x85, 0x98, 0x9b, 0xa7, 0x93, 0x79, + 0x78, 0x79, 0x91, 0x94, 0x87, 0x86, 0x85, 0x86, 0x8b, 0x89, 0x82, 0x7c, 0x74, 0x6d, 0x6c, 0x75, + 0x75, 0x6f, 0x64, 0x69, 0x74, 0x7e, 0x83, 0x76, 0x75, 0x85, 0x8a, 0x89, 0x88, 0x78, 0x81, 0x88, + 0x83, 0x85, 0x7e, 0x80, 0x88, 0x89, 0x8c, 0x8d, 0x8a, 0x8b, 0x88, 0x88, 0x89, 0x85, 0x81, 0x81, + 0x7e, 0x7c, 0x7c, 0x77, 0x7d, 0x76, 0x6f, 0x7d, 0x7f, 0x78, 0x73, 0x76, 0x83, 0x84, 0x80, 0x7f, + 0x82, 0x86, 0x80, 0x81, 0x83, 0x81, 0x81, 0x7e, 0x7d, 0x7b, 0x83, 0x8b, 0x85, 0x7a, 0x76, 0x83, + 0x87, 0x82, 0x7d, 0x76, 0x7b, 0x80, 0x83, 0x81, 0x7a, 0x79, 0x7d, 0x82, 0x81, 0x82, 0x82, 0x83, + 0x86, 0x80, 0x80, 0x81, 0x7e, 0x80, 0x7d, 0x7a, 0x7e, 0x81, 0x7e, 0x7e, 0x80, 0x7f, 0x81, 0x82, + 0x80, 0x81, 0x82, 0x7f, 0x7f, 0x7d, 0x7c, 0x7f, 0x7b, 0x7b, 0x7d, 0x7a, 0x7a, 0x7e, 0x7e, 0x7c, + 0x7c, 0x7f, 0x80, 0x7f, 0x80, 0x82, 0x81, 0x81, 0x80, 0x7e, 0x80, 0x7f, 0x81, 0x7b, 0x7c, 0x7f, + 0x7f, 0x81, 0x7f, 0x7f, 0x80, 0x80, 0x7f, 0x80, 0x7f, 0x7f, 0x83, 0x7e, 0x7f, 0x85, 0x81, 0x83, + 0x84, 0x80, 0x84, 0x81, 0x81, 0x83, 0x81, 0x83, 0x80, 0x84, 0x80, 0x80, 0x85, 0x80, 0x81, 0x7f, + 0x82, 0x82, 0x81, 0x81, 0x80, 0x81, 0x80, 0x87, 0x81, 0x7c, 0x80, 0x7f, 0x80, 0x7d, 0x7c, 0x7d, + 0x80, 0x80, 0x80, 0x82, 0x7d, 0x81, 0x82, 0x7e, 0x82, 0x81, 0x81, 0x81, 0x80, 0x80, 0x80, 0x82, + 0x7f, 0x80, 0x7f, 0x7f, 0x81, 0x7f, 0x80, 0x7e, 0x81, 0x80, 0x7e, 0x80, 0x7e, 0x7f, 0x80, 0x80, + 0x82, 0x7f, 0x83, 0x83, 0x80, 0x80, 0x7f, 0x7f, 0x7f, 0x7e, 0x7e, 0x7f, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x81, 0x80, 0x7f, 0x7f, 0x7f, 0x7e, 0x7f, 0x7e, 0x7d, 0x7e, 0x7d, 0x7c, 0x7d, 0x7c, 0x7c, + 0x7d, 0x7c, 0x7d, 0x7e, 0x7f, 0x7e, 0x7e, 0x7f, 0x7d, 0x7f, 0x7f, 0x80, 0x7f, 0x7e, 0x7f, 0x80, + 0x7e, 0x80, 0x7e, 0x7e, 0x80, 0x7e, 0x80, 0x7e, 0x7f, 0x7e, 0x7d, 0x7f, 0x7d, 0x7d, 0x7d, 0x7d, + 0x7d, 0x7d, 0x7e, 0x7f, 0x7f, 0x7d, 0x7e, 0x7f, 0x7e, 0x80, 0x7f, 0x7f, 0x80, 0x7f, 0x80, 0x80, + 0x80, 0x7f, 0x80, 0x7f, 0x7f, 0x7f, 0x7f, 0x81, 0x80, 0x80, 0x80, 0x7f, 0x7f, 0x7f, 0x80, 0x7f, + 0x7f, 0x80, 0x80, 0x7f, 0x80, 0x80, 0x7f, 0x7f, 0x80, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x80, + 0x81, 0x80, 0x82, 0x83, 0x81, 0x82, 0x81, 0x82, 0x82, 0x82, 0x81, 0x81, 0x83, 0x82, 0x82, 0x82, + 0x81, 0x83, 0x82, 0x81, 0x81, 0x80, 0x80, 0x7e, 0x7e, 0x7e, 0x7f, 0x7f, 0x7e, 0x80, 0x7d, 0x80, + 0x81, 0x7e, 0x7f, 0x7f, 0x80, 0x7f, 0x7f, 0x80, 0x80, 0x80, 0x81, 0x81, 0x80, 0x81, 0x81, 0x80, + 0x7f, 0x7f, 0x7f, 0x80, 0x7f, 0x7f, 0x7f, 0x7e, 0x7f, 0x81, 0x80, 0x7f, 0x81, 0x81, 0x82, 0x81, + 0x80, 0x82, 0x82, 0x80, 0x81, 0x81, 0x80, 0x80, 0x7e, 0x7d, 0x7f, 0x7e, 0x81, 0x81, 0x7e, 0x7f, + 0x82, 0x7f, 0x7d, 0x7f, 0x7d, 0x81, 0x7f, 0x7f, 0x80, 0x7f, 0x80, 0x7f, 0x80, 0x7f, 0x7f, 0x80, + 0x7f, 0x7e, 0x7f, 0x7f, 0x7e, 0x7c, 0x7d, 0x7e, 0x7d, 0x7d, 0x7e, 0x7d, 0x7e, 0x7c, 0x7e, 0x7e, + 0x7c, 0x7e, 0x7d, 0x7e, 0x7e, 0x7e, 0x7d, 0x7d, 0x7c, 0x7b, 0x7c, 0x7b, 0x7b, 0x7b, 0x7b, 0x7b, + 0x7b, 0x7c, 0x7c, 0x7d, 0x7c, 0x7d, 0x7d, 0x7d, 0x7d, 0x7d, 0x7c, 0x7d, 0x7d, 0x7d, 0x7d, 0x7d, + 0x7d, 0x7d, 0x7d, 0x7d, 0x7e, 0x7d, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x80, 0x80, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x82, 0x82, 0x82, 0x82, 0x81, 0x81, 0x81, 0x80, 0x80, 0x80, 0x80, 0x81, + 0x81, 0x81, 0x80, 0x80, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x81, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x81, 0x81, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x7f, 0x7f, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, + 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7e, 0x7e, 0x7e, 0x7e, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7f, 0x7f, 0x80, 0x7f, 0x80, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x80, 0x7f, 0x81, 0x80, 0x83, 0x80, 0x80, 0x80, 0x84, 0x84, + 0x7b, 0x7e, 0x80, 0x80, 0x7e, 0x80, 0x7e, 0x7f, 0x81, 0x81, 0x80, 0x7f, 0x80, 0x7f, 0x7e, 0x7e, + 0x7f, 0x80, 0x80, 0x7f, 0x81, 0x82, 0x80, 0x80, 0x81, 0x81, 0x80, 0x81, 0x7f, 0x80, 0x80, 0x81, + 0x81, 0x81, 0x81, 0x84, 0x83, 0x7f, 0x7f, 0x80, 0x80, 0x7f, 0x81, 0x7e, 0x7e, 0x7f, 0x81, 0x7f, + 0x7f, 0x80, 0x7f, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x7f, 0x81, 0x82, 0x82, 0x80, 0x7f, 0x80, + 0x7f, 0x7f, 0x7e, 0x7e, 0x7e, 0x7e, 0x7f, 0x7e, 0x7f, 0x7f, 0x7f, 0x7d, 0x7e, 0x7e, 0x7f, 0x80, + 0x80, 0x80, 0x80, 0x81, 0x80, 0x80, 0x80, 0x80, 0x80, 0x81, 0x81, 0x80, 0x80, 0x81, 0x7f, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x7f, 0x80, 0x7f, 0x7e, 0x7e, 0x7f, 0x7f, 0x7e, 0x7e, 0x7e, 0x7f, + 0x7e, 0x7d, 0x7d, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7f, 0x7e, 0x80, 0x7e, 0x7f, 0x7f, + 0x7e, 0x7f, 0x7e, 0x80, 0x7f, 0x80, 0x7f, 0x7f, 0x7f, 0x7f, 0x80, 0x7f, 0x7f, 0x7e, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x80, 0x7f, 0x7f, 0x7f, 0x81, 0x80, 0x7f, 0x80, 0x80, 0x82, + 0x81, 0x80, 0x80, 0x80, 0x80, 0x7f, 0x7f, 0x7f, 0x7f, 0x81, 0x81, 0x80, 0x81, 0x80, 0x82, 0x7f, + 0x7f, 0x7e, 0x7e, 0x80, 0x7e, 0x80, 0x7f, 0x7f, 0x7f, 0x7f, 0x80, 0x7f, 0x80, 0x7f, 0x81, 0x80, + 0x81, 0x80, 0x80, 0x81, 0x80, 0x83, 0x80, 0x80, 0x7f, 0x7f, 0x80, 0x7f, 0x80, 0x7e, 0x80, 0x7f, + 0x7f, 0x80, 0x7f, 0x82, 0x80, 0x81, 0x7f, 0x7e, 0x80, 0x7f, 0x80, 0x7f, 0x7f, 0x7f, 0x7f, 0x80, + 0x7f, 0x7f, 0x7f, 0x80, 0x7f, 0x7f, 0x80, 0x7f, 0x81, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x7f, 0x7f, 0x80, 0x81, 0x80, 0x80, 0x80, 0x81, 0x81, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7e, 0x7e, 0x7f, 0x7f, 0x80, 0x80, 0x80, 0x7f, 0x80, 0x81, 0x7f, 0x80, 0x7e, 0x7f, 0x7f, + 0x7e, 0x80, 0x7e, 0x7f, 0x7f, 0x7f, 0x7f, 0x7e, 0x7e, 0x7d, 0x7f, 0x7e, 0x7f, 0x7f, 0x7f, 0x80, + 0x7f, 0x80, 0x80, 0x7f, 0x80, 0x7f, 0x80, 0x81, 0x81, 0x81, 0x80, 0x80, 0x7f, 0x7f, 0x80, 0x7f, + 0x7f, 0x7f, 0x81, 0x81, 0x7f, 0x80, 0x7f, 0x80, 0x7f, 0x7f, 0x7f, 0x7f, 0x80, 0x80, 0x7f, 0x7f, + 0x7f, 0x80, 0x80, 0x7f, 0x80, 0x80, 0x81, 0x80, 0x80, 0x7f, 0x7e, 0x7f, 0x7e, 0x7e, 0x7e, 0x7e, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x80, 0x7f, 0x80, 0x80, 0x7f, 0x80, 0x7e, 0x7f, 0x7e +}; + +static const Uint8 right[1777] = { + 0x7f, 0x7e, 0x7e, 0x7f, 0x80, 0x80, 0x80, 0x7f, 0x80, 0x81, 0x82, 0x83, 0x82, 0x83, 0x83, 0x83, + 0x82, 0x81, 0x81, 0x80, 0x7f, 0x7e, 0x7c, 0x7b, 0x7a, 0x7a, 0x79, 0x79, 0x79, 0x7a, 0x7a, 0x7b, + 0x7c, 0x7e, 0x80, 0x82, 0x84, 0x86, 0x88, 0x89, 0x89, 0x89, 0x88, 0x87, 0x84, 0x82, 0x80, 0x7e, + 0x7c, 0x7b, 0x7a, 0x7a, 0x79, 0x78, 0x77, 0x75, 0x76, 0x77, 0x78, 0x78, 0x78, 0x7b, 0x81, 0x87, + 0x8c, 0x8e, 0x90, 0x92, 0x91, 0x8d, 0x87, 0x81, 0x7d, 0x7b, 0x7a, 0x79, 0x79, 0x7a, 0x79, 0x78, + 0x75, 0x74, 0x75, 0x75, 0x75, 0x76, 0x76, 0x76, 0x76, 0x7b, 0x83, 0x88, 0x8b, 0x8f, 0x95, 0x98, + 0x95, 0x8d, 0x86, 0x83, 0x80, 0x7e, 0x7c, 0x7c, 0x7e, 0x7e, 0x7c, 0x79, 0x78, 0x76, 0x75, 0x72, + 0x73, 0x74, 0x72, 0x6f, 0x6d, 0x72, 0x7e, 0x87, 0x8b, 0x90, 0x98, 0x9f, 0x9b, 0x91, 0x85, 0x7f, + 0x7b, 0x78, 0x79, 0x7f, 0x87, 0x8b, 0x8a, 0x89, 0x89, 0x86, 0x81, 0x79, 0x75, 0x74, 0x73, 0x73, + 0x6f, 0x6d, 0x6e, 0x6e, 0x6e, 0x6f, 0x72, 0x77, 0x82, 0x8f, 0x95, 0x99, 0x9c, 0x9e, 0x99, 0x8c, + 0x7f, 0x74, 0x71, 0x70, 0x74, 0x7e, 0x8a, 0x92, 0x91, 0x8f, 0x8f, 0x8d, 0x85, 0x7c, 0x76, 0x75, + 0x76, 0x75, 0x71, 0x6d, 0x6b, 0x68, 0x64, 0x64, 0x66, 0x6e, 0x83, 0x8f, 0x93, 0x9b, 0xa3, 0xa4, + 0x97, 0x86, 0x76, 0x6f, 0x6d, 0x6e, 0x78, 0x87, 0x94, 0x98, 0x96, 0x94, 0x91, 0x89, 0x7e, 0x74, + 0x6f, 0x70, 0x74, 0x72, 0x6e, 0x6b, 0x67, 0x62, 0x60, 0x60, 0x69, 0x84, 0x91, 0x95, 0xa1, 0xae, + 0xb0, 0x9b, 0x84, 0x74, 0x6a, 0x65, 0x67, 0x78, 0x8b, 0x98, 0x9f, 0x9e, 0x9a, 0x90, 0x86, 0x7c, + 0x71, 0x6a, 0x6c, 0x73, 0x74, 0x6d, 0x69, 0x65, 0x5e, 0x5c, 0x60, 0x6f, 0x8b, 0x95, 0x9b, 0xac, + 0xb3, 0xa5, 0x89, 0x7a, 0x6b, 0x5c, 0x5f, 0x70, 0x88, 0x97, 0xa5, 0xac, 0xa1, 0x95, 0x8e, 0x86, + 0x76, 0x6a, 0x6b, 0x72, 0x72, 0x6c, 0x67, 0x5e, 0x55, 0x52, 0x56, 0x78, 0x9c, 0x91, 0x9c, 0xbc, + 0xb8, 0x98, 0x83, 0x7f, 0x5e, 0x4c, 0x6c, 0x83, 0x8a, 0x9a, 0xb7, 0xae, 0x8a, 0x8f, 0x93, 0x79, + 0x69, 0x76, 0x76, 0x69, 0x70, 0x70, 0x5b, 0x50, 0x51, 0x57, 0x52, 0x77, 0xb2, 0x90, 0x95, 0xc8, + 0xb1, 0x8d, 0x89, 0x8a, 0x55, 0x4e, 0x87, 0x7f, 0x82, 0xb3, 0xb9, 0x8f, 0x8c, 0x9d, 0x79, 0x71, + 0x80, 0x6a, 0x61, 0x7b, 0x70, 0x51, 0x63, 0x62, 0x3e, 0x50, 0x61, 0x9a, 0xad, 0x7e, 0xba, 0xb5, + 0x94, 0x9f, 0x93, 0x75, 0x4b, 0x7b, 0x79, 0x6c, 0xab, 0xaf, 0x9f, 0x93, 0x8e, 0x7a, 0x7f, 0x89, + 0x6a, 0x6e, 0x71, 0x66, 0x5e, 0x63, 0x5c, 0x53, 0x53, 0x50, 0x5a, 0xb8, 0xbd, 0x6d, 0xc3, 0xb2, + 0x8a, 0xa7, 0xa1, 0x70, 0x4c, 0x88, 0x63, 0x7d, 0xb1, 0xa1, 0xa6, 0x8e, 0x6a, 0x7c, 0x95, 0x8b, + 0x84, 0x72, 0x5c, 0x5c, 0x67, 0x64, 0x61, 0x56, 0x65, 0x52, 0x44, 0x80, 0xda, 0x8a, 0x88, 0xc9, + 0x89, 0x96, 0xb1, 0x92, 0x4a, 0x6f, 0x6d, 0x78, 0xa5, 0xa7, 0xa0, 0x98, 0x66, 0x6e, 0xa6, 0x9d, + 0x95, 0x70, 0x52, 0x57, 0x73, 0x69, 0x72, 0x5a, 0x55, 0x52, 0x50, 0x3d, 0xb8, 0xdb, 0x5d, 0xa9, + 0xab, 0x82, 0xad, 0xc3, 0x65, 0x4c, 0x6c, 0x6d, 0x98, 0xac, 0x9f, 0x97, 0x74, 0x5a, 0xa0, 0xb1, + 0x9e, 0x7e, 0x52, 0x54, 0x74, 0x71, 0x6a, 0x6a, 0x5a, 0x53, 0x4b, 0x46, 0x5e, 0xe5, 0xaa, 0x62, + 0xab, 0x8f, 0x97, 0xcb, 0xa5, 0x4b, 0x4f, 0x67, 0x88, 0xa6, 0xa4, 0x98, 0x84, 0x61, 0x80, 0xb7, + 0xb4, 0x98, 0x64, 0x4e, 0x64, 0x77, 0x72, 0x72, 0x55, 0x54, 0x4e, 0x52, 0x3c, 0x96, 0xf0, 0x69, + 0x7f, 0xa2, 0x80, 0xc1, 0xc8, 0x75, 0x46, 0x4d, 0x74, 0xa4, 0x9e, 0x95, 0x8a, 0x6a, 0x73, 0xa6, + 0xb7, 0xb4, 0x81, 0x60, 0x5e, 0x71, 0x7c, 0x74, 0x6b, 0x54, 0x54, 0x48, 0x4f, 0x44, 0xc3, 0xcb, + 0x5b, 0x9b, 0x86, 0x99, 0xd4, 0xa3, 0x71, 0x3e, 0x4b, 0x91, 0x99, 0x9d, 0x95, 0x70, 0x72, 0x85, + 0xb0, 0xbd, 0xa5, 0x7e, 0x67, 0x67, 0x78, 0x77, 0x6e, 0x63, 0x53, 0x5b, 0x39, 0x50, 0x48, 0xb5, + 0xc2, 0x6a, 0xa5, 0x77, 0xa8, 0xbd, 0x98, 0x89, 0x3a, 0x60, 0x83, 0x85, 0xa9, 0x87, 0x87, 0x74, + 0x77, 0xac, 0xa9, 0xb9, 0x8a, 0x71, 0x6b, 0x6d, 0x81, 0x6d, 0x66, 0x51, 0x60, 0x3c, 0x50, 0x4a, + 0x91, 0xbf, 0x83, 0xae, 0x7a, 0xa4, 0xa1, 0x97, 0x92, 0x4b, 0x73, 0x68, 0x86, 0x8e, 0x8c, 0x95, + 0x79, 0x83, 0x86, 0xa2, 0xab, 0xa6, 0x8d, 0x79, 0x6a, 0x75, 0x68, 0x74, 0x56, 0x5c, 0x4e, 0x4c, + 0x49, 0x5d, 0xb1, 0x88, 0xb9, 0x8d, 0xa4, 0x90, 0x94, 0x8b, 0x66, 0x72, 0x69, 0x83, 0x7c, 0x91, + 0x82, 0x89, 0x79, 0x87, 0x8a, 0xa1, 0x9f, 0xa5, 0x95, 0x8d, 0x7a, 0x6f, 0x6f, 0x61, 0x62, 0x58, + 0x5f, 0x52, 0x52, 0x4f, 0x80, 0x90, 0xa1, 0xa6, 0xa3, 0x9c, 0x90, 0x86, 0x74, 0x6d, 0x6c, 0x7a, + 0x83, 0x8a, 0x8c, 0x88, 0x7f, 0x80, 0x82, 0x8f, 0x99, 0x9e, 0xa3, 0x9a, 0x93, 0x84, 0x73, 0x68, + 0x5d, 0x5e, 0x5d, 0x5f, 0x5e, 0x5d, 0x52, 0x6a, 0x7d, 0x8d, 0x9f, 0xa6, 0xac, 0xa0, 0x95, 0x7d, + 0x6e, 0x64, 0x6a, 0x76, 0x81, 0x8e, 0x98, 0x94, 0x8e, 0x84, 0x84, 0x84, 0x86, 0x91, 0x98, 0x9d, + 0x9a, 0x8c, 0x7b, 0x67, 0x5c, 0x58, 0x58, 0x5e, 0x5e, 0x64, 0x60, 0x67, 0x75, 0x7f, 0x8e, 0x99, + 0xa2, 0xa5, 0xa2, 0x99, 0x87, 0x74, 0x6a, 0x67, 0x6e, 0x7b, 0x87, 0x96, 0x97, 0x97, 0x94, 0x8e, + 0x8c, 0x8a, 0x8b, 0x8a, 0x8a, 0x88, 0x84, 0x7b, 0x72, 0x66, 0x5e, 0x58, 0x57, 0x5a, 0x60, 0x64, + 0x6c, 0x78, 0x81, 0x8c, 0x96, 0x9d, 0x9f, 0xa1, 0x99, 0x8f, 0x80, 0x76, 0x70, 0x6c, 0x70, 0x76, + 0x81, 0x8a, 0x93, 0x97, 0x98, 0x94, 0x91, 0x8c, 0x8a, 0x87, 0x81, 0x7c, 0x74, 0x71, 0x6b, 0x68, + 0x65, 0x62, 0x60, 0x61, 0x63, 0x67, 0x6c, 0x77, 0x82, 0x8a, 0x96, 0x9c, 0xa4, 0xa5, 0xa0, 0x95, + 0x86, 0x7b, 0x71, 0x6e, 0x6e, 0x73, 0x79, 0x82, 0x8b, 0x94, 0x99, 0x98, 0x95, 0x8e, 0x88, 0x81, + 0x7b, 0x77, 0x73, 0x6f, 0x6c, 0x6a, 0x68, 0x67, 0x66, 0x69, 0x69, 0x6e, 0x70, 0x77, 0x81, 0x88, + 0x91, 0x97, 0x9f, 0xa1, 0xa2, 0x9b, 0x92, 0x82, 0x77, 0x6c, 0x6a, 0x6b, 0x71, 0x79, 0x83, 0x8d, + 0x93, 0x97, 0x96, 0x95, 0x8f, 0x8b, 0x84, 0x7d, 0x76, 0x71, 0x6a, 0x68, 0x67, 0x68, 0x6b, 0x6d, + 0x71, 0x73, 0x76, 0x79, 0x7e, 0x83, 0x8a, 0x8f, 0x94, 0x97, 0x97, 0x95, 0x8f, 0x87, 0x7f, 0x79, + 0x76, 0x76, 0x78, 0x7a, 0x7e, 0x81, 0x86, 0x8a, 0x8c, 0x8e, 0x8d, 0x8a, 0x86, 0x80, 0x7c, 0x78, + 0x74, 0x71, 0x70, 0x70, 0x72, 0x74, 0x76, 0x78, 0x7a, 0x7c, 0x7e, 0x80, 0x82, 0x82, 0x81, 0x80, + 0x7e, 0x7f, 0x81, 0x84, 0x88, 0x8b, 0x8d, 0x8d, 0x8b, 0x87, 0x83, 0x7d, 0x7a, 0x77, 0x78, 0x7a, + 0x7e, 0x81, 0x83, 0x84, 0x84, 0x84, 0x82, 0x80, 0x7f, 0x7d, 0x7b, 0x7a, 0x78, 0x78, 0x77, 0x78, + 0x78, 0x79, 0x7c, 0x7e, 0x81, 0x83, 0x83, 0x84, 0x83, 0x82, 0x81, 0x80, 0x80, 0x7f, 0x7f, 0x80, + 0x80, 0x81, 0x82, 0x83, 0x84, 0x84, 0x84, 0x83, 0x83, 0x81, 0x80, 0x7f, 0x7f, 0x80, 0x80, 0x80, + 0x7f, 0x7e, 0x7d, 0x7c, 0x7c, 0x7c, 0x7d, 0x7d, 0x7f, 0x80, 0x80, 0x81, 0x80, 0x7f, 0x7e, 0x7d, + 0x7d, 0x7d, 0x7e, 0x80, 0x80, 0x81, 0x82, 0x82, 0x81, 0x81, 0x81, 0x81, 0x80, 0x80, 0x81, 0x80, + 0x81, 0x80, 0x7f, 0x7f, 0x80, 0x80, 0x80, 0x81, 0x80, 0x80, 0x7e, 0x7d, 0x7d, 0x7e, 0x7e, 0x7f, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x81, 0x80, 0x80, 0x80, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x80, 0x80, 0x80, 0x80, 0x80, 0x7f, 0x7f, 0x7e, 0x7e, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7e, 0x7e, 0x7e, 0x7e, 0x7f, 0x80, 0x81, 0x82, 0x82, 0x82, 0x82, 0x81, 0x80, + 0x7f, 0x7f, 0x7e, 0x7e, 0x7e, 0x7e, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7e, 0x7f, 0x7f, 0x7f, 0x7f, + 0x80, 0x80, 0x81, 0x81, 0x80, 0x80, 0x80, 0x80, 0x7f, 0x7f, 0x7e, 0x7f, 0x7e, 0x7f, 0x7f, 0x7f, + 0x80, 0x80, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x82, 0x82, 0x82, 0x82, + 0x81, 0x81, 0x80, 0x7f, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7f, 0x7f, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x81, 0x80, 0x81, 0x80, 0x80, 0x80, 0x80, 0x80, 0x7f, 0x7f, 0x7f, 0x7f, 0x7e, 0x7e, 0x7e, + 0x7e, 0x7d, 0x7e, 0x7e, 0x7f, 0x80, 0x81, 0x82, 0x82, 0x82, 0x81, 0x81, 0x80, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x80, 0x80, 0x80, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x7f, 0x80, 0x80, 0x80, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x80, 0x7f, 0x80, 0x80, 0x80, 0x7f, 0x80, 0x7f, 0x80, 0x7f, 0x7f, 0x7f, 0x80, + 0x80, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x7f, 0x87, 0x83, 0x7d, 0x81, 0x80, 0x7e, 0x81, 0x7b, + 0x7d, 0x84, 0x7f, 0x81, 0x83, 0x82, 0x7f, 0x80, 0x7c, 0x7b, 0x7d, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x7e, 0x7f, 0x7e, 0x7f, 0x80, 0x80, 0x81, 0x82, 0x82, 0x82, 0x82, 0x80, 0x80, 0x7f, 0x7f, + 0x7e, 0x7e, 0x7e, 0x7f, 0x7f, 0x7f, 0x7f, 0x80, 0x82, 0x80, 0x7f, 0x80, 0x81, 0x80, 0x81, 0x7f, + 0x83, 0x85, 0x7f, 0x80, 0x84, 0x83, 0x7d, 0x7c, 0x7d, 0x80, 0x7d, 0x7d, 0x7e, 0x7e, 0x7d, 0x83, + 0x81, 0x7d, 0x7d, 0x81, 0x7f, 0x7c, 0x7c, 0x7c, 0x7d, 0x7c, 0x83, 0x80, 0x84, 0x84, 0x82, 0x7d, + 0x7f, 0x7d, 0x7c, 0x7e, 0x7e, 0x7f, 0x81, 0x84, 0x82, 0x81, 0x7e, 0x7f, 0x7f, 0x7f, 0x7e, 0x80, + 0x81, 0x80, 0x7f, 0x80, 0x7f, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x80, 0x80, 0x80, 0x80, 0x81, 0x81, + 0x7f, 0x7f, 0x7f, 0x7f, 0x81, 0x80, 0x82, 0x81, 0x83, 0x81, 0x82, 0x80, 0x80, 0x7f, 0x7f, 0x80, + 0x7d, 0x80, 0x7e, 0x81, 0x7f, 0x81, 0x7f, 0x80, 0x7f, 0x7f, 0x7e, 0x7d, 0x81, 0x80, 0x82, 0x7f, + 0x81, 0x7f, 0x7f, 0x7e, 0x7e, 0x7f, 0x7e, 0x80, 0x7f, 0x81, 0x7f, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x82, 0x81, 0x81, 0x80, 0x81, 0x7f, 0x80, 0x7f, 0x7f, 0x7e, 0x7e, 0x7f, 0x7e, 0x7f, 0x7f, 0x7e, + 0x7f, 0x7e, 0x7f, 0x7e, 0x7e, 0x7e, 0x7e, 0x7f, 0x7e, 0x80, 0x7f, 0x82, 0x80, 0x81, 0x81, 0x80, + 0x81, 0x80, 0x81, 0x80, 0x81, 0x80, 0x81, 0x80, 0x80, 0x7e, 0x7f, 0x7e, 0x7d, 0x7e, 0x7e, 0x7f, + 0x7f, 0x7f, 0x7e, 0x7d, 0x80, 0x7e, 0x7f, 0x7f, 0x80, 0x7f, 0x7f, 0x81, 0x7e, 0x81, 0x81, 0x83, + 0x80, 0x81, 0x80, 0x80, 0x81, 0x7f, 0x80, 0x80, 0x80, 0x81, 0x7f, 0x80, 0x7f, 0x80, 0x7d, 0x80, + 0x7e, 0x7d, 0x80, 0x80, 0x83, 0x7f, 0x83, 0x7e, 0x83, 0x7f, 0x80, 0x7f, 0x7e, 0x81, 0x7f, 0x7f, + 0x80, 0x80, 0x81, 0x7e, 0x7f, 0x7f, 0x80, 0x81, 0x80, 0x83, 0x7f, 0x82, 0x7f, 0x82, 0x7f, 0x80, + 0x80, 0x7e, 0x7f, 0x7d, 0x7e, 0x7d, 0x7e, 0x7e, 0x7f, 0x7e, 0x7f, 0x7f, 0x7f, 0x80, 0x7f, 0x81, + 0x82, 0x80, 0x82, 0x80, 0x82, 0x80, 0x7f, 0x7e, 0x7f, 0x7e, 0x7f, 0x80, 0x7e, 0x80, 0x80, 0x81, + 0x7f, 0x7f, 0x7e, 0x80, 0x7d, 0x7e, 0x7e, 0x7f, 0x80, 0x7f, 0x80, 0x7e, 0x81, 0x7e, 0x81, 0x7f, + 0x80, 0x7f, 0x80, 0x81, 0x7f, 0x80, 0x7e, 0x81, 0x7e, 0x80, 0x7d, 0x80, 0x80, 0x80, 0x81, 0x80, + 0x82, 0x7e, 0x83, 0x7d, 0x80, 0x7c, 0x7d, 0x7e, 0x7c, 0x7e, 0x7d, 0x7e, 0x7f, 0x7e, 0x7e, 0x80, + 0x7e, 0x81, 0x7e, 0x81, 0x7f, 0x81, 0x80, 0x80, 0x80, 0x80, 0x80, 0x81, 0x83, 0x80, 0x81, 0x80, + 0x80, 0x80, 0x80, 0x7f, 0x80, 0x7e, 0x7f, 0x7f, 0x81, 0x7f, 0x80, 0x80, 0x7f, 0x7e, 0x80, 0x80, + 0x81, 0x82, 0x81, 0x82, 0x81, 0x81, 0x81, 0x82, 0x80, 0x80, 0x7e, 0x82, 0x80, 0x84, 0x81, 0x80, + 0x7f, 0x81, 0x80, 0x7f, 0x80, 0x7d, 0x80, 0x7d, 0x81, 0x7f, 0x81, 0x80, 0x81, 0x81, 0x80, 0x80, + 0x7e, 0x80, 0x7f, 0x81, 0x7f, 0x81, 0x81, 0x81, 0x7f, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x81, 0x80, 0x80, 0x7e, 0x81, 0x7f, 0x7f, 0x7e, 0x7e, 0x7f, 0x7f, 0x80, 0x7f, 0x7f, 0x7e, 0x81, + 0x7e, 0x7f, 0x80, 0x80, 0x7f, 0x7f, 0x7f, 0x7f, 0x81, 0x7f, 0x80, 0x7f, 0x80, 0x80, 0x7f, 0x80, + 0x80, 0x80, 0x7f, 0x7f, 0x7f, 0x80, 0x7f, 0x7e, 0x7d, 0x7e, 0x7e, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7e, 0x7f, 0x7f, 0x80, 0x7f, 0x80, 0x7f, 0x80, 0x7f, 0x7f, 0x80, 0x80, 0x80, 0x80, 0x81, + 0x80 +}; + diff --git a/libs/SDL3/examples/audio/05-planar-data/thumbnail.png b/libs/SDL3/examples/audio/05-planar-data/thumbnail.png new file mode 100644 index 0000000000000000000000000000000000000000..3a040dfaef38a15faef6715e6bf25b4d0da53510 GIT binary patch literal 2637 zcmeAS@N?(olHy`uVBq!ia0y~yU}|7sV0^&A1{5*9c;^X_(~{}z9N_8ftWZ#tpO%@E z%D|v8v38=Z$Ke2xR{zUcu3CIzf`umn=Dt~^)ECvN6vDC9X*Hjk{@-RbUeB)X#Sfwi zKBRAa_2AK()y=CFIQ}*4;P&g%3wl)RxzM$TU+(?mJ>`7g@3C|Aa$R0?L1)3sZ8QDU zj^yNp^6Xb?adcrUIeo_HnTjUs{ja6{4EFCI*_{4=cm8Vsv(Y>p9Y;2wG01(y`{>00RZZ@tm!KQOR zwXQj?^<4K{)Y4;7rJwkeD{E6{zTV^qsZ%QsxXtmKE5e<*%m2rZ_SpR{ zdaW_321PmxxOg|cX8+%OfAdzZ$-;3#DvTw2EzTX+5V*%WtMvF!yRtdYI0b6h)_vKw z{}w}}!AO7?iq-+MdneU;#!8TavfC3&Vd9T(Ecf zWS|IVfk$L91B0G22s2hJwJ!q-vX^-Jy0X7y5ffn%k@ym_2Ph<2;u=xnoS&PUnpeW$ zT$GwvlA5AWo>`Ki;O^-g5Z=fq&cMK->gnPbQgQ3;t&3UB4kE4>tq=bH|McW!6-l@C zQi{T{+@CIJ@Oo6=xHYX~1tY_OT$>msU>f0IXkcJraA06!P+(wW5NH6J~h*M-BdhK)XY2QyZA8%%j5 z)L?sbUR<9#FeQ?n)JTthkQYeTg%?$3;2s>=HNXV;XkkKgWe@83S3`NR9)H*asx zNxpt==Kb^gwyj_Dk!3=OM%Chra`WS(`}2SDG-$s`*FUrAZ*~7Joi}|s790+KdmqP2 z&H7d$HkIMq7WqllkIwJgbYy;mW$v0jIrbfg?dHiQZkw84!F1w!^W@%H*8UIHox8C= zDt`U9>f7Ng3ih4*%bxuCY#y?eFh$ zjqcwr?wfQk!=**OcjL0dsuJ(QSvD-caXI@VivTGF0W=IqEI!EBg{7$KS?l6bD#^g` z?yxYh(88hsEuPTbN_qkVbP0l+XkK3mfWd literal 0 HcmV?d00001 diff --git a/libs/SDL3/examples/demo/01-snake/snake.c b/libs/SDL3/examples/demo/01-snake/snake.c index 0aca862d..d740f2c6 100644 --- a/libs/SDL3/examples/demo/01-snake/snake.c +++ b/libs/SDL3/examples/demo/01-snake/snake.c @@ -21,6 +21,8 @@ #define THREE_BITS 0x7U /* ~CHAR_MAX >> (CHAR_BIT - SNAKE_CELL_MAX_BITS) */ #define SHIFT(x, y) (((x) + ((y) * SNAKE_GAME_WIDTH)) * SNAKE_CELL_MAX_BITS) +static SDL_Joystick *joystick = NULL; + typedef enum { SNAKE_CELL_NOTHING = 0U, @@ -186,6 +188,8 @@ void snake_step(SnakeContext *ctx) case SNAKE_DIR_DOWN: ++ctx->head_ypos; break; + default: + break; } wrap_around_(&ctx->head_xpos, SNAKE_GAME_WIDTH); wrap_around_(&ctx->head_ypos, SNAKE_GAME_HEIGHT); @@ -238,6 +242,26 @@ static SDL_AppResult handle_key_event_(SnakeContext *ctx, SDL_Scancode key_code) return SDL_APP_CONTINUE; } +static SDL_AppResult handle_hat_event_(SnakeContext *ctx, Uint8 hat) { + switch (hat) { + case SDL_HAT_RIGHT: + snake_redir(ctx, SNAKE_DIR_RIGHT); + break; + case SDL_HAT_UP: + snake_redir(ctx, SNAKE_DIR_UP); + break; + case SDL_HAT_LEFT: + snake_redir(ctx, SNAKE_DIR_LEFT); + break; + case SDL_HAT_DOWN: + snake_redir(ctx, SNAKE_DIR_DOWN); + break; + default: + break; + } + return SDL_APP_CONTINUE; +} + SDL_AppResult SDL_AppIterate(void *appstate) { AppState *as = (AppState *)appstate; @@ -305,7 +329,8 @@ SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) } } - if (!SDL_Init(SDL_INIT_VIDEO)) { + if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK)) { + SDL_Log("Couldn't initialize SDL: %s", SDL_GetError()); return SDL_APP_FAILURE; } @@ -333,14 +358,35 @@ SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event) switch (event->type) { case SDL_EVENT_QUIT: return SDL_APP_SUCCESS; + case SDL_EVENT_JOYSTICK_ADDED: + if (joystick == NULL) { + joystick = SDL_OpenJoystick(event->jdevice.which); + if (!joystick) { + SDL_Log("Failed to open joystick ID %u: %s", (unsigned int) event->jdevice.which, SDL_GetError()); + } + } + break; + case SDL_EVENT_JOYSTICK_REMOVED: + if (joystick && (SDL_GetJoystickID(joystick) == event->jdevice.which)) { + SDL_CloseJoystick(joystick); + joystick = NULL; + } + break; + case SDL_EVENT_JOYSTICK_HAT_MOTION: + return handle_hat_event_(ctx, event->jhat.value); case SDL_EVENT_KEY_DOWN: return handle_key_event_(ctx, event->key.scancode); + default: + break; } return SDL_APP_CONTINUE; } void SDL_AppQuit(void *appstate, SDL_AppResult result) { + if (joystick) { + SDL_CloseJoystick(joystick); + } if (appstate != NULL) { AppState *as = (AppState *)appstate; SDL_DestroyRenderer(as->renderer); diff --git a/libs/SDL3/examples/demo/04-bytepusher/bytepusher.c b/libs/SDL3/examples/demo/04-bytepusher/bytepusher.c index acb2ea48..c7e3e059 100644 --- a/libs/SDL3/examples/demo/04-bytepusher/bytepusher.c +++ b/libs/SDL3/examples/demo/04-bytepusher/bytepusher.c @@ -27,7 +27,6 @@ typedef struct { Uint8 ram[RAM_SIZE + 8]; - Uint8 screenbuf[SCREEN_W * SCREEN_H]; Uint64 last_tick; Uint64 tick_acc; SDL_Window* window; @@ -187,7 +186,7 @@ SDL_AppResult SDL_AppInit(void** appstate, int argc, char* argv[]) { } if (!(vm->screen = SDL_CreateSurfaceFrom( - SCREEN_W, SCREEN_H, SDL_PIXELFORMAT_INDEX8, vm->screenbuf, SCREEN_W + SCREEN_W, SCREEN_H, SDL_PIXELFORMAT_INDEX8, vm->ram, SCREEN_W ))) { return SDL_APP_FAILURE; } @@ -296,18 +295,18 @@ SDL_AppResult SDL_AppIterate(void* appstate) { SDL_UnlockTexture(vm->screentex); SDL_RenderTexture(vm->renderer, vm->screentex, NULL, NULL); - } - if (vm->display_help) { - print(vm, 4, 4, "Drop a BytePusher file in this"); - print(vm, 8, 12, "window to load and run it!"); - print(vm, 4, 28, "Press ENTER to switch between"); - print(vm, 8, 36, "positional and symbolic input."); - } + if (vm->display_help) { + print(vm, 4, 4, "Drop a BytePusher file in this"); + print(vm, 8, 12, "window to load and run it!"); + print(vm, 4, 28, "Press ENTER to switch between"); + print(vm, 8, 36, "positional and symbolic input."); + } - if (vm->status_ticks > 0) { - vm->status_ticks -= 1; - print(vm, 4, SCREEN_H - 12, vm->status); + if (vm->status_ticks > 0) { + vm->status_ticks -= 1; + print(vm, 4, SCREEN_H - 12, vm->status); + } } SDL_SetRenderTarget(vm->renderer, NULL); diff --git a/libs/SDL3/examples/input/01-joystick-polling/joystick-polling.c b/libs/SDL3/examples/input/01-joystick-polling/joystick-polling.c index 6eb23b83..647cdef9 100644 --- a/libs/SDL3/examples/input/01-joystick-polling/joystick-polling.c +++ b/libs/SDL3/examples/input/01-joystick-polling/joystick-polling.c @@ -41,7 +41,7 @@ SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) return SDL_APP_FAILURE; } - if (!SDL_CreateWindowAndRenderer("examples/input/joystick-polling", 640, 480, 0, &window, &renderer)) { + if (!SDL_CreateWindowAndRenderer("examples/input/joystick-polling", 640, 480, SDL_WINDOW_RESIZABLE, &window, &renderer)) { SDL_Log("Couldn't create window/renderer: %s", SDL_GetError()); return SDL_APP_FAILURE; } diff --git a/libs/SDL3/examples/renderer/19-affine-textures/README.txt b/libs/SDL3/examples/renderer/19-affine-textures/README.txt new file mode 100644 index 00000000..34e341b6 --- /dev/null +++ b/libs/SDL3/examples/renderer/19-affine-textures/README.txt @@ -0,0 +1,3 @@ +This example creates an SDL window and renderer, and uses +SDL_RenderTextureAffine to draw a 3D cube using only 2D rendering operations. + diff --git a/libs/SDL3/examples/renderer/19-affine-textures/affine-textures.c b/libs/SDL3/examples/renderer/19-affine-textures/affine-textures.c new file mode 100644 index 00000000..6c5ccbf8 --- /dev/null +++ b/libs/SDL3/examples/renderer/19-affine-textures/affine-textures.c @@ -0,0 +1,145 @@ +/* affine-textures.c ... */ + +/* +* This example creates an SDL window and renderer, and then draws a cube +* using affine-transformed textures every frame. +* +* This code is public domain. Feel free to use it for any purpose! +*/ + +#define SDL_MAIN_USE_CALLBACKS 1 /* use the callbacks instead of main() */ +#include +#include + +/* We will use this renderer to draw into this window every frame. */ +static SDL_Window *window = NULL; +static SDL_Renderer *renderer = NULL; +static SDL_Texture *texture = NULL; +static int texture_width = 0; +static int texture_height = 0; + +#define WINDOW_WIDTH 640 +#define WINDOW_HEIGHT 480 + +/* This function runs once at startup. */ +SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) +{ + SDL_Surface *surface = NULL; + char *bmp_path = NULL; + + SDL_SetAppMetadata("Example Renderer Affine Textures", "1.0", "com.example.renderer-affine-textures"); + + if (!SDL_Init(SDL_INIT_VIDEO)) { + SDL_Log("Couldn't initialize SDL: %s", SDL_GetError()); + return SDL_APP_FAILURE; + } + + if (!SDL_CreateWindowAndRenderer("examples/renderer/affine-textures", WINDOW_WIDTH, WINDOW_HEIGHT, 0, &window, &renderer)) { + SDL_Log("Couldn't create window/renderer: %s", SDL_GetError()); + return SDL_APP_FAILURE; + } + + /* Textures are pixel data that we upload to the video hardware for fast drawing. Lots of 2D + engines refer to these as "sprites." We'll do a static texture (upload once, draw many + times) with data from a bitmap file. */ + + /* SDL_Surface is pixel data the CPU can access. SDL_Texture is pixel data the GPU can access. + Load a .bmp into a surface, move it to a texture from there. */ + SDL_asprintf(&bmp_path, "%ssample.bmp", SDL_GetBasePath()); /* allocate a string of the full file path */ + surface = SDL_LoadBMP(bmp_path); + if (!surface) { + SDL_Log("Couldn't load bitmap: %s", SDL_GetError()); + return SDL_APP_FAILURE; + } + + SDL_free(bmp_path); /* done with this, the file is loaded. */ + + texture_width = surface->w; + texture_height = surface->h; + + texture = SDL_CreateTextureFromSurface(renderer, surface); + if (!texture) { + SDL_Log("Couldn't create static texture: %s", SDL_GetError()); + return SDL_APP_FAILURE; + } + + SDL_DestroySurface(surface); /* done with this, the texture has a copy of the pixels now. */ + + return SDL_APP_CONTINUE; /* carry on with the program! */ +} + +/* This function runs when a new event (mouse input, keypresses, etc) occurs. */ +SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event) +{ + if (event->type == SDL_EVENT_QUIT) { + return SDL_APP_SUCCESS; /* end the program, reporting success to the OS. */ + } + return SDL_APP_CONTINUE; /* carry on with the program! */ +} + +/* This function runs once per frame, and is the heart of the program. */ +SDL_AppResult SDL_AppIterate(void *appstate) +{ + const float x0 = 0.5f * WINDOW_WIDTH; + const float y0 = 0.5f * WINDOW_HEIGHT; + const float px = SDL_min(WINDOW_WIDTH, WINDOW_HEIGHT) / SDL_sqrtf(3.0f); + + const Uint64 now = SDL_GetTicks(); + const float rad = (((float) ((int) (now % 2000))) / 2000.0f) * SDL_PI_F * 2; + const float cos = SDL_cosf(rad); + const float sin = SDL_sinf(rad); + const float k[3] = { 3.0f / SDL_sqrtf(50.0f), 4.0f / SDL_sqrtf(50.0f), 5.0f / SDL_sqrtf(50.0f)}; + float mat[9] = { + cos + (1.0f-cos)*k[0]*k[0], -sin*k[2] + (1.0f-cos)*k[0]*k[1], sin*k[1] + (1.0f-cos)*k[0]*k[2], + sin*k[2] + (1.0f-cos)*k[0]*k[1], cos + (1.0f-cos)*k[1]*k[1], -sin*k[0] + (1.0f-cos)*k[1]*k[2], + -sin*k[1] + (1.0f-cos)*k[0]*k[2], sin*k[0] + (1.0f-cos)*k[1]*k[2], cos + (1.0f-cos)*k[2]*k[2], + }; + + float corners[16]; + int i; + + for (i = 0; i < 8; i++) { + const float x = (i & 1) ? -0.5f : 0.5f; + const float y = (i & 2) ? -0.5f : 0.5f; + const float z = (i & 4) ? -0.5f : 0.5f; + corners[0 + 2*i] = mat[0]*x + mat[1]*y + mat[2]*z; + corners[1 + 2*i] = mat[3]*x + mat[4]*y + mat[5]*z; + } + + SDL_SetRenderDrawColor(renderer, 0x42, 0x87, 0xf5, SDL_ALPHA_OPAQUE); // light blue background. + SDL_RenderClear(renderer); + + for (i = 1; i < 7; i++) { + const int dir = 3 & ((i & 4) ? ~i : i); + const int odd = (i & 1) ^ ((i & 2) >> 1) ^ ((i & 4) >> 2); + if (0 < (odd ? 1.0f : -1.0f) * mat[5 + dir]) continue; + int origin_index = (1 << ((dir - 1) % 3)); + int right_index = (1 << ((dir + odd) % 3)) | origin_index; + int down_index = (1 << ((dir + (odd^1)) % 3)) | origin_index; + if (!odd) { + origin_index ^= 7; + right_index ^= 7; + down_index ^= 7; + } + SDL_FPoint origin, right, down; + origin.x = x0 + px*corners[0 + 2*origin_index]; + origin.y = y0 + px*corners[1 + 2*origin_index]; + right.x = x0 + px*corners[0 + 2*right_index]; + right.y = y0 + px*corners[1 + 2*right_index]; + down.x = x0 + px*corners[0 + 2*down_index]; + down.y = y0 + px*corners[1 + 2*down_index]; + SDL_RenderTextureAffine(renderer, texture, NULL, &origin, &right, &down); + } + + SDL_RenderPresent(renderer); + + return SDL_APP_CONTINUE; +} + +/* This function runs once at shutdown. */ +void SDL_AppQuit(void *appstate, SDL_AppResult result) +{ + SDL_DestroyTexture(texture); + /* SDL will clean up the window/renderer for us. */ +} + diff --git a/libs/SDL3/examples/renderer/19-affine-textures/onmouseover.webp b/libs/SDL3/examples/renderer/19-affine-textures/onmouseover.webp new file mode 100644 index 0000000000000000000000000000000000000000..31e450d2e2a4e74aaa6ba1229b6f225ff1f9dee0 GIT binary patch literal 211490 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

jS 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ྫྷO$((Rg~M(8REd1HPEZN5UrhN+OLEEM#bLt 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 #define SDL_TriggerBreakpoint() assert(0) #elif SDL_HAS_BUILTIN(__builtin_debugtrap) diff --git a/libs/SDL3/include/SDL3/SDL_audio.h b/libs/SDL3/include/SDL3/SDL_audio.h index c6acf885..b91586fd 100644 --- a/libs/SDL3/include/SDL3/SDL_audio.h +++ b/libs/SDL3/include/SDL3/SDL_audio.h @@ -1021,7 +1021,8 @@ extern SDL_DECLSPEC void SDLCALL SDL_UnbindAudioStream(SDL_AudioStream *stream); /** * Query an audio stream for its currently-bound device. * - * This reports the audio device that an audio stream is currently bound to. + * This reports the logical audio device that an audio stream is currently + * bound to. * * If not bound, or invalid, this returns zero, which is not a valid device * ID. @@ -1149,14 +1150,14 @@ extern SDL_DECLSPEC float SDLCALL SDL_GetAudioStreamFrequencyRatio(SDL_AudioStre * * The frequency ratio is used to adjust the rate at which input data is * consumed. Changing this effectively modifies the speed and pitch of the - * audio. A value greater than 1.0 will play the audio faster, and at a higher - * pitch. A value less than 1.0 will play the audio slower, and at a lower - * pitch. + * audio. A value greater than 1.0f will play the audio faster, and at a + * higher pitch. A value less than 1.0f will play the audio slower, and at a + * lower pitch. 1.0f means play at normal speed. * * This is applied during SDL_GetAudioStreamData, and can be continuously * changed to create various effects. * - * \param stream the stream the frequency ratio is being changed. + * \param stream the stream on which the frequency ratio is being changed. * \param ratio the frequency ratio. 1.0 is normal speed. Must be between 0.01 * and 100. * \returns true on success or false on failure; call SDL_GetError() for more @@ -1332,7 +1333,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SetAudioStreamInputChannelMap(SDL_AudioStre * Channel maps are optional; most things do not need them, instead passing * data in the [order that SDL expects](CategoryAudio#channel-layouts). * - * The output channel map reorders data that leaving a stream via + * The output channel map reorders data that is leaving a stream via * SDL_GetAudioStreamData. * * Each item in the array represents an input channel, and its value is the @@ -1414,6 +1415,136 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SetAudioStreamOutputChannelMap(SDL_AudioStr */ extern SDL_DECLSPEC bool SDLCALL SDL_PutAudioStreamData(SDL_AudioStream *stream, const void *buf, int len); +/** + * A callback that fires for completed SDL_PutAudioStreamDataNoCopy() data. + * + * When using SDL_PutAudioStreamDataNoCopy() to provide data to an + * SDL_AudioStream, it's not safe to dispose of the data until the stream has + * completely consumed it. Often times it's difficult to know exactly when + * this has happened. + * + * This callback fires once when the stream no longer needs the buffer, + * allowing the app to easily free or reuse it. + * + * \param userdata an opaque pointer provided by the app for their personal + * use. + * \param buf the pointer provided to SDL_PutAudioStreamDataNoCopy(). + * \param buflen the size of buffer, in bytes, provided to + * SDL_PutAudioStreamDataNoCopy(). + * + * \threadsafety This callbacks may run from any thread, so if you need to + * protect shared data, you should use SDL_LockAudioStream to + * serialize access; this lock will be held before your callback + * is called, so your callback does not need to manage the lock + * explicitly. + * + * \since This datatype is available since SDL 3.4.0. + * + * \sa SDL_SetAudioStreamGetCallback + * \sa SDL_SetAudioStreamPutCallback + */ +typedef void (SDLCALL *SDL_AudioStreamDataCompleteCallback)(void *userdata, const void *buf, int buflen); + +/** + * Add external data to an audio stream without copying it. + * + * Unlike SDL_PutAudioStreamData(), this function does not make a copy of the + * provided data, instead storing the provided pointer. This means that the + * put operation does not need to allocate and copy the data, but the original + * data must remain available until the stream is done with it, either by + * being read from the stream in its entirety, or a call to + * SDL_ClearAudioStream() or SDL_DestroyAudioStream(). + * + * The data must match the format/channels/samplerate specified in the latest + * call to SDL_SetAudioStreamFormat, or the format specified when creating the + * stream if it hasn't been changed. + * + * An optional callback may be provided, which is called when the stream no + * longer needs the data. Once this callback fires, the stream will not access + * the data again. This callback will fire for any reason the data is no + * longer needed, including clearing or destroying the stream. + * + * Note that there is still an allocation to store tracking information, so + * this function is more efficient for larger blocks of data. If you're + * planning to put a few samples at a time, it will be more efficient to use + * SDL_PutAudioStreamData(), which allocates and buffers in blocks. + * + * \param stream the stream the audio data is being added to. + * \param buf a pointer to the audio data to add. + * \param len the number of bytes to add to the stream. + * \param callback the callback function to call when the data is no longer + * needed by the stream. May be NULL. + * \param userdata an opaque pointer provided to the callback for its own + * personal use. + * \returns true on success or false on failure; call SDL_GetError() for more + * information. + * + * \threadsafety It is safe to call this function from any thread, but if the + * stream has a callback set, the caller might need to manage + * extra locking. + * + * \since This function is available since SDL 3.4.0. + * + * \sa SDL_ClearAudioStream + * \sa SDL_FlushAudioStream + * \sa SDL_GetAudioStreamData + * \sa SDL_GetAudioStreamQueued + */ +extern SDL_DECLSPEC bool SDLCALL SDL_PutAudioStreamDataNoCopy(SDL_AudioStream *stream, const void *buf, int len, SDL_AudioStreamDataCompleteCallback callback, void *userdata); + +/** + * Add data to the stream with each channel in a separate array. + * + * This data must match the format/channels/samplerate specified in the latest + * call to SDL_SetAudioStreamFormat, or the format specified when creating the + * stream if it hasn't been changed. + * + * The data will be interleaved and queued. Note that SDL_AudioStream only + * operates on interleaved data, so this is simply a convenience function for + * easily queueing data from sources that provide separate arrays. There is no + * equivalent function to retrieve planar data. + * + * The arrays in `channel_buffers` are ordered as they are to be interleaved; + * the first array will be the first sample in the interleaved data. Any + * individual array may be NULL; in this case, silence will be interleaved for + * that channel. + * + * `num_channels` specifies how many arrays are in `channel_buffers`. This can + * be used as a safety to prevent overflow, in case the stream format has + * changed elsewhere. If more channels are specified than the current input + * spec, they are ignored. If less channels are specified, the missing arrays + * are treated as if they are NULL (silence is written to those channels). If + * the count is -1, SDL will assume the array count matches the current input + * spec. + * + * Note that `num_samples` is the number of _samples per array_. This can also + * be thought of as the number of _sample frames_ to be queued. A value of 1 + * with stereo arrays will queue two samples to the stream. This is different + * than SDL_PutAudioStreamData, which wants the size of a single array in + * bytes. + * + * \param stream the stream the audio data is being added to. + * \param channel_buffers a pointer to an array of arrays, one array per + * channel. + * \param num_channels the number of arrays in `channel_buffers` or -1. + * \param num_samples the number of _samples_ per array to write to the + * stream. + * \returns true on success or false on failure; call SDL_GetError() for more + * information. + * + * \threadsafety It is safe to call this function from any thread, but if the + * stream has a callback set, the caller might need to manage + * extra locking. + * + * \since This function is available since SDL 3.4.0. + * + * \sa SDL_ClearAudioStream + * \sa SDL_FlushAudioStream + * \sa SDL_GetAudioStreamData + * \sa SDL_GetAudioStreamQueued + */ +extern SDL_DECLSPEC bool SDLCALL SDL_PutAudioStreamPlanarData(SDL_AudioStream *stream, const void * const *channel_buffers, int num_channels, int num_samples); + /** * Get converted/resampled data from the stream. * @@ -1583,8 +1714,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_PauseAudioStreamDevice(SDL_AudioStream *str * previously been paused. Once unpaused, any bound audio streams will begin * to progress again, and audio can be generated. * - * Remember, SDL_OpenAudioDeviceStream opens device in a paused state, so this - * function call is required for audio playback to begin on such device. + * SDL_OpenAudioDeviceStream opens audio devices in a paused state, so this + * function call is required for audio playback to begin on such devices. * * \param stream the audio stream associated with the audio device to resume. * \returns true on success or false on failure; call SDL_GetError() for more @@ -1841,7 +1972,7 @@ extern SDL_DECLSPEC void SDLCALL SDL_DestroyAudioStream(SDL_AudioStream *stream) * Also unlike other functions, the audio device begins paused. This is to map * more closely to SDL2-style behavior, since there is no extra step here to * bind a stream to begin audio flowing. The audio device should be resumed - * with `SDL_ResumeAudioStreamDevice(stream);` + * with SDL_ResumeAudioStreamDevice(). * * This function works with both playback and recording devices. * @@ -1887,6 +2018,85 @@ extern SDL_DECLSPEC void SDLCALL SDL_DestroyAudioStream(SDL_AudioStream *stream) */ extern SDL_DECLSPEC SDL_AudioStream * SDLCALL SDL_OpenAudioDeviceStream(SDL_AudioDeviceID devid, const SDL_AudioSpec *spec, SDL_AudioStreamCallback callback, void *userdata); +/** + * A callback that fires around an audio device's processing work. + * + * This callback fires when a logical audio device is about to start accessing + * its bound audio streams, and fires again when it has finished accessing + * them. It covers the range of one "iteration" of the audio device. + * + * It can be useful to use this callback to update state that must apply to + * all bound audio streams atomically: to make sure state changes don't happen + * while half of the streams are already processed for the latest audio + * buffer. + * + * This callback should run as quickly as possible and not block for any + * significant time, as this callback delays submission of data to the audio + * device, which can cause audio playback problems. This callback delays all + * audio processing across a single physical audio device: all its logical + * devices and all bound audio streams. Use it carefully. + * + * \param userdata a pointer provided by the app through + * SDL_SetAudioPostmixCallback, for its own use. + * \param devid the audio device this callback is running for. + * \param start true if this is the start of the iteration, false if the end. + * + * \threadsafety This will run from a background thread owned by SDL. The + * application is responsible for locking resources the callback + * touches that need to be protected. + * + * \since This datatype is available since SDL 3.4.0. + * + * \sa SDL_SetAudioIterationCallbacks + */ +typedef void (SDLCALL *SDL_AudioIterationCallback)(void *userdata, SDL_AudioDeviceID devid, bool start); + +/** + * Set callbacks that fire around a new iteration of audio device processing. + * + * Two callbacks are provided here: one that runs when a device is about to + * process its bound audio streams, and another that runs when the device has + * finished processing them. + * + * These callbacks can run at any time, and from any thread; if you need to + * serialize access to your app's data, you should provide and use a mutex or + * other synchronization device. + * + * Generally these callbacks are used to apply state that applies to multiple + * bound audio streams, with a guarantee that the audio device's thread isn't + * halfway through processing them. Generally a finer-grained lock through + * SDL_LockAudioStream() is more appropriate. + * + * The callbacks are extremely time-sensitive; the callback should do the + * least amount of work possible and return as quickly as it can. The longer + * the callback runs, the higher the risk of audio dropouts or other problems. + * + * This function will block until the audio device is in between iterations, + * so any existing callback that might be running will finish before this + * function sets the new callback and returns. + * + * Physical devices do not accept these callbacks, only logical devices + * created through SDL_OpenAudioDevice() can be. + * + * Setting a NULL callback function disables any previously-set callback. + * Either callback may be NULL, and the same callback is permitted to be used + * for both. + * + * \param devid the ID of an opened audio device. + * \param start a callback function to be called at the start of an iteration. + * Can be NULL. + * \param end a callback function to be called at the end of an iteration. Can + * be NULL. + * \param userdata app-controlled pointer passed to callback. Can be NULL. + * \returns true on success or false on failure; call SDL_GetError() for more + * information. + * + * \threadsafety It is safe to call this function from any thread. + * + * \since This function is available since SDL 3.4.0. + */ +extern SDL_DECLSPEC bool SDLCALL SDL_SetAudioIterationCallbacks(SDL_AudioDeviceID devid, SDL_AudioIterationCallback start, SDL_AudioIterationCallback end, void *userdata); + /** * A callback that fires when data is about to be fed to an audio device. * diff --git a/libs/SDL3/include/SDL3/SDL_begin_code.h b/libs/SDL3/include/SDL3/SDL_begin_code.h index a6b47cf4..e306a906 100644 --- a/libs/SDL3/include/SDL3/SDL_begin_code.h +++ b/libs/SDL3/include/SDL3/SDL_begin_code.h @@ -261,9 +261,9 @@ * * On compilers without restrict support, this is defined to nothing. * - * \since This macro is available since SDL 3.2.0. + * \since This macro is available since SDL 3.4.0. */ -#define SDL_RESTRICT __restrict__ +#define SDL_RESTRICT __restrict /** * Check if the compiler supports a given builtin functionality. @@ -281,9 +281,61 @@ */ #define SDL_HAS_BUILTIN(x) __has_builtin(x) +/** + * A macro to specify data alignment. + * + * This informs the compiler that a given datatype or variable must be aligned + * to a specific byte count. + * + * For example: + * + * ```c + * // make sure this is struct is aligned to 16 bytes for SIMD access. + * typedef struct { + * float x, y, z, w; + * } SDL_ALIGNED(16) MySIMDAlignedData; + * + * // make sure this one field in a struct is aligned to 16 bytes for SIMD access. + * typedef struct { + * SomeStuff stuff; + * float position[4] SDL_ALIGNED(16); + * SomeOtherStuff other_stuff; + * } MyStruct; + * + * // make sure this variable is aligned to 32 bytes. + * int SDL_ALIGNED(32) myval = 0; + * ``` + * + * Alignment is only guaranteed for things the compiler places: local + * variables on the stack and global/static variables. To dynamically allocate + * something that respects this alignment, use SDL_aligned_alloc() or some + * other mechanism. + * + * On compilers without alignment support, this macro is defined to an invalid + * symbol, to make it clear that the current compiler is likely to generate + * incorrect code when it sees this macro. + * + * \param x the byte count to align to, so the data's address will be a + * multiple of this value. + * + * \since This macro is available since SDL 3.4.0. + */ +#define SDL_ALIGNED(x) __attribute__((aligned(x))) + /* end of wiki documentation section. */ #endif +/* `restrict` is from C99, but __restrict works with both Visual Studio and GCC. */ +#ifndef SDL_RESTRICT +# if defined(restrict) || ((defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L))) +# define SDL_RESTRICT restrict +# elif defined(_MSC_VER) || defined(__GNUC__) || defined(__clang__) +# define SDL_RESTRICT __restrict +# else +# define SDL_RESTRICT +# endif +#endif + #ifndef SDL_HAS_BUILTIN #ifdef __has_builtin #define SDL_HAS_BUILTIN(x) __has_builtin(x) @@ -389,7 +441,7 @@ #endif /* SDL_FORCE_INLINE not defined */ #ifndef SDL_NORETURN -#ifdef __GNUC__ +#if defined(__GNUC__) #define SDL_NORETURN __attribute__((noreturn)) #elif defined(_MSC_VER) #define SDL_NORETURN __declspec(noreturn) @@ -484,3 +536,18 @@ #define SDL_ALLOC_SIZE2(p1, p2) #endif #endif /* SDL_ALLOC_SIZE2 not defined */ + +#ifndef SDL_ALIGNED +#if defined(__clang__) || defined(__GNUC__) +#define SDL_ALIGNED(x) __attribute__((aligned(x))) +#elif defined(_MSC_VER) +#define SDL_ALIGNED(x) __declspec(align(x)) +#elif defined(__cplusplus) && (__cplusplus >= 201103L) +#define SDL_ALIGNED(x) alignas(x) +#elif defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) +#define SDL_ALIGNED(x) _Alignas(x) +#else +#define SDL_ALIGNED(x) PLEASE_DEFINE_SDL_ALIGNED +#endif +#endif /* SDL_ALIGNED not defined */ + diff --git a/libs/SDL3/include/SDL3/SDL_events.h b/libs/SDL3/include/SDL3/SDL_events.h index 56a2194b..353ecd3e 100644 --- a/libs/SDL3/include/SDL3/SDL_events.h +++ b/libs/SDL3/include/SDL3/SDL_events.h @@ -135,7 +135,8 @@ typedef enum SDL_EventType /* 0x201 was SDL_SYSWMEVENT, reserve the number for sdl2-compat */ SDL_EVENT_WINDOW_SHOWN = 0x202, /**< Window has been shown */ SDL_EVENT_WINDOW_HIDDEN, /**< Window has been hidden */ - SDL_EVENT_WINDOW_EXPOSED, /**< Window has been exposed and should be redrawn, and can be redrawn directly from event watchers for this event */ + SDL_EVENT_WINDOW_EXPOSED, /**< Window has been exposed and should be redrawn, and can be redrawn directly from event watchers for this event. + data1 is 1 for live-resize expose events, 0 otherwise. */ SDL_EVENT_WINDOW_MOVED, /**< Window has been moved to data1, data2 */ SDL_EVENT_WINDOW_RESIZED, /**< Window has been resized to data1xdata2 */ SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED,/**< The pixel size of the window has changed to data1xdata2 */ @@ -492,6 +493,8 @@ typedef struct SDL_MouseWheelEvent SDL_MouseWheelDirection direction; /**< Set to one of the SDL_MOUSEWHEEL_* defines. When FLIPPED the values in X and Y will be opposite. Multiply by -1 to change them back */ float mouse_x; /**< X coordinate, relative to window */ float mouse_y; /**< Y coordinate, relative to window */ + Sint32 integer_x; /**< The amount scrolled horizontally, accumulated to whole scroll "ticks" (added in 3.2.12) */ + Sint32 integer_y; /**< The amount scrolled vertically, accumulated to whole scroll "ticks" (added in 3.2.12) */ } SDL_MouseWheelEvent; /** @@ -779,7 +782,7 @@ typedef struct SDL_TouchFingerEvent } SDL_TouchFingerEvent; /** - * Pressure-sensitive pen proximity event structure (event.pmotion.*) + * Pressure-sensitive pen proximity event structure (event.pproximity.*) * * When a pen becomes visible to the system (it is close enough to a tablet, * etc), SDL will send an SDL_EVENT_PEN_PROXIMITY_IN event with the new pen's @@ -1565,6 +1568,38 @@ extern SDL_DECLSPEC Uint32 SDLCALL SDL_RegisterEvents(int numevents); */ extern SDL_DECLSPEC SDL_Window * SDLCALL SDL_GetWindowFromEvent(const SDL_Event *event); +/** + * Generate a human-readable description of an event. + * + * This will fill `buf` with a null-terminated string that might look + * something like this: + * + * ``` + * SDL_EVENT_MOUSE_MOTION (timestamp=1140256324 windowid=2 which=0 state=0 x=492.99 y=139.09 xrel=52 yrel=6) + * ``` + * + * The exact format of the string is not guaranteed; it is intended for + * logging purposes, to be read by a human, and not parsed by a computer. + * + * The returned value follows the same rules as SDL_snprintf(): `buf` will + * always be NULL-terminated (unless `buflen` is zero), and will be truncated + * if `buflen` is too small. The return code is the number of bytes needed for + * the complete string, not counting the NULL-terminator, whether the string + * was truncated or not. Unlike SDL_snprintf(), though, this function never + * returns -1. + * + * \param event an event to describe. May be NULL. + * \param buf the buffer to fill with the description string. May be NULL. + * \param buflen the maximum bytes that can be written to `buf`. + * \returns number of bytes needed for the full string, not counting the + * null-terminator byte. + * + * \threadsafety It is safe to call this function from any thread. + * + * \since This function is available since SDL 3.4.0. + */ +extern SDL_DECLSPEC int SDLCALL SDL_GetEventDescription(const SDL_Event *event, char *buf, int buflen); + /* Ends C function definitions when using C++ */ #ifdef __cplusplus } diff --git a/libs/SDL3/include/SDL3/SDL_filesystem.h b/libs/SDL3/include/SDL3/SDL_filesystem.h index af3ca27e..031feaf9 100644 --- a/libs/SDL3/include/SDL3/SDL_filesystem.h +++ b/libs/SDL3/include/SDL3/SDL_filesystem.h @@ -444,10 +444,10 @@ extern SDL_DECLSPEC bool SDLCALL SDL_GetPathInfo(const char *path, SDL_PathInfo * Enumerate a directory tree, filtered by pattern, and return a list. * * Files are filtered out if they don't match the string in `pattern`, which - * may contain wildcard characters '\*' (match everything) and '?' (match one + * may contain wildcard characters `*` (match everything) and `?` (match one * character). If pattern is NULL, no filtering is done and all results are * returned. Subdirectories are permitted, and are specified with a path - * separator of '/'. Wildcard characters '\*' and '?' never match a path + * separator of `/`. Wildcard characters `*` and `?` never match a path * separator. * * `flags` may be set to SDL_GLOB_CASEINSENSITIVE to make the pattern matching diff --git a/libs/SDL3/include/SDL3/SDL_gamepad.h b/libs/SDL3/include/SDL3/SDL_gamepad.h index 99f8b659..460ae987 100644 --- a/libs/SDL3/include/SDL3/SDL_gamepad.h +++ b/libs/SDL3/include/SDL3/SDL_gamepad.h @@ -118,6 +118,7 @@ typedef enum SDL_GamepadType SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_LEFT, SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT, SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_PAIR, + SDL_GAMEPAD_TYPE_GAMECUBE, SDL_GAMEPAD_TYPE_COUNT } SDL_GamepadType; @@ -127,8 +128,9 @@ typedef enum SDL_GamepadType * For controllers that use a diamond pattern for the face buttons, the * south/east/west/north buttons below correspond to the locations in the * diamond pattern. For Xbox controllers, this would be A/B/X/Y, for Nintendo - * Switch controllers, this would be B/A/Y/X, for PlayStation controllers this - * would be Cross/Circle/Square/Triangle. + * Switch controllers, this would be B/A/Y/X, for GameCube controllers this + * would be A/X/B/Y, for PlayStation controllers this would be + * Cross/Circle/Square/Triangle. * * For controllers that don't use a diamond pattern for the face buttons, the * south/east/west/north buttons indicate the buttons labeled A, B, C, D, or @@ -1156,10 +1158,12 @@ extern SDL_DECLSPEC bool SDLCALL SDL_GamepadHasAxis(SDL_Gamepad *gamepad, SDL_Ga * return a negative value. Note that this differs from the value reported by * the lower-level SDL_GetJoystickAxis(), which normally uses the full range. * + * Note that for invalid gamepads or axes, this will return 0. Zero is also a + * valid value in normal operation; usually it means a centered axis. + * * \param gamepad a gamepad. * \param axis an axis index (one of the SDL_GamepadAxis values). - * \returns axis state (including 0) on success or 0 (also) on failure; call - * SDL_GetError() for more information. + * \returns axis state. * * \since This function is available since SDL 3.2.0. * diff --git a/libs/SDL3/include/SDL3/SDL_gpu.h b/libs/SDL3/include/SDL3/SDL_gpu.h index 9f516d73..b3ff2022 100644 --- a/libs/SDL3/include/SDL3/SDL_gpu.h +++ b/libs/SDL3/include/SDL3/SDL_gpu.h @@ -206,14 +206,20 @@ * underlying graphics API. While it's possible that we have done something * inefficiently, it's very unlikely especially if you are relatively * inexperienced with GPU rendering. Please see the performance tips above and - * make sure you are following them. Additionally, tools like RenderDoc can be - * very helpful for diagnosing incorrect behavior and performance issues. + * make sure you are following them. Additionally, tools like + * [RenderDoc](https://renderdoc.org/) + * can be very helpful for diagnosing incorrect behavior and performance + * issues. * * ## System Requirements * - * **Vulkan:** Supported on Windows, Linux, Nintendo Switch, and certain - * Android devices. Requires Vulkan 1.0 with the following extensions and - * device features: + * ### Vulkan + * + * SDL driver name: "vulkan" (for use in SDL_CreateGPUDevice() and + * SDL_PROP_GPU_DEVICE_CREATE_NAME_STRING) + * + * Supported on Windows, Linux, Nintendo Switch, and certain Android devices. + * Requires Vulkan 1.0 with the following extensions and device features: * * - `VK_KHR_swapchain` * - `VK_KHR_maintenance1` @@ -222,13 +228,21 @@ * - `depthClamp` * - `shaderClipDistance` * - `drawIndirectFirstInstance` + * - `sampleRateShading` * - * **D3D12:** Supported on Windows 10 or newer, Xbox One (GDK), and Xbox - * Series X|S (GDK). Requires a GPU that supports DirectX 12 Feature Level - * 11_1. + * ### D3D12 * - * **Metal:** Supported on macOS 10.14+ and iOS/tvOS 13.0+. Hardware - * requirements vary by operating system: + * SDL driver name: "direct3d12" + * + * Supported on Windows 10 or newer, Xbox One (GDK), and Xbox Series X|S + * (GDK). Requires a GPU that supports DirectX 12 Feature Level 11_1. + * + * ### Metal + * + * SDL driver name: "metal" + * + * Supported on macOS 10.14+ and iOS/tvOS 13.0+. Hardware requirements vary by + * operating system: * * - macOS requires an Apple Silicon or * [Intel Mac2 family](https://developer.apple.com/documentation/metal/mtlfeatureset/mtlfeatureset_macos_gpufamily2_v1?language=objc) @@ -236,6 +250,26 @@ * - iOS/tvOS requires an A9 GPU or newer * - iOS Simulator and tvOS Simulator are unsupported * + * ## Coordinate System + * + * The GPU API uses a left-handed coordinate system, following the convention + * of D3D12 and Metal. Specifically: + * + * - **Normalized Device Coordinates:** The lower-left corner has an x,y + * coordinate of `(-1.0, -1.0)`. The upper-right corner is `(1.0, 1.0)`. Z + * values range from `[0.0, 1.0]` where 0 is the near plane. + * - **Viewport Coordinates:** The top-left corner has an x,y coordinate of + * `(0, 0)` and extends to the bottom-right corner at `(viewportWidth, + * viewportHeight)`. +Y is down. + * - **Texture Coordinates:** The top-left corner has an x,y coordinate of + * `(0, 0)` and extends to the bottom-right corner at `(1.0, 1.0)`. +Y is + * down. + * + * If the backend driver differs from this convention (e.g. Vulkan, which has + * an NDC that assumes +Y is down), SDL will automatically convert the + * coordinate system behind the scenes, so you don't need to perform any + * coordinate flipping logic in your shaders. + * * ## Uniform Data * * Uniforms are for passing data to shaders. The uniform data will be constant @@ -301,6 +335,39 @@ * unreferenced data in a bound resource without cycling, but overwriting a * section of data that has already been referenced will produce unexpected * results. + * + * ## Debugging + * + * At some point of your GPU journey, you will probably encounter issues that + * are not traceable with regular debugger - for example, your code compiles + * but you get an empty screen, or your shader fails in runtime. + * + * For debugging such cases, there are tools that allow visually inspecting + * the whole GPU frame, every drawcall, every bound resource, memory buffers, + * etc. They are the following, per platform: + * + * * For Windows/Linux, use + * [RenderDoc](https://renderdoc.org/) + * * For MacOS (Metal), use Xcode built-in debugger (Open XCode, go to Debug > + * Debug Executable..., select your application, set "GPU Frame Capture" to + * "Metal" in scheme "Options" window, run your app, and click the small + * Metal icon on the bottom to capture a frame) + * + * Aside from that, you may want to enable additional debug layers to receive + * more detailed error messages, based on your GPU backend: + * + * * For D3D12, the debug layer is an optional feature that can be installed + * via "Windows Settings -> System -> Optional features" and adding the + * "Graphics Tools" optional feature. + * * For Vulkan, you will need to install Vulkan SDK on Windows, and on Linux, + * you usually have some sort of `vulkan-validation-layers` system package + * that should be installed. + * * For Metal, it should be enough just to run the application from XCode to + * receive detailed errors or warnings in the output. + * + * Don't hesitate to use tools as RenderDoc when encountering runtime issues + * or unexpected output on screen, quick GPU frame inspection can usually help + * you fix the majority of such problems. */ #ifndef SDL_gpu_h_ @@ -1310,6 +1377,17 @@ typedef struct SDL_GPUViewport * A structure specifying parameters related to transferring data to or from a * texture. * + * If either of `pixels_per_row` or `rows_per_layer` is zero, then width and + * height of passed SDL_GPUTextureRegion to SDL_UploadToGPUTexture or + * SDL_DownloadFromGPUTexture are used as default values respectively and data + * is considered to be tightly packed. + * + * **WARNING**: Direct3D 12 requires texture data row pitch to be 256 byte + * aligned, and offsets to be aligned to 512 bytes. If they are not, SDL will + * make a temporary copy of the data that is properly aligned, but this adds + * overhead to the transfer process. Apps can avoid this by aligning their + * data appropriately, or using a different GPU backend than Direct3D 12. + * * \since This struct is available since SDL 3.2.0. * * \sa SDL_UploadToGPUTexture @@ -1391,7 +1469,7 @@ typedef struct SDL_GPUTextureRegion */ typedef struct SDL_GPUBlitRegion { - SDL_GPUTexture *texture; /**< The texture. */ + SDL_GPUTexture *texture; /**< The texture. */ Uint32 mip_level; /**< The mip level index of the region. */ Uint32 layer_or_depth_plane; /**< The layer index or depth plane of the region. This value is treated as a layer index on 2D array and cube textures, and as a depth plane on 3D textures. */ Uint32 x; /**< The left offset of the region. */ @@ -1520,8 +1598,8 @@ typedef struct SDL_GPUSamplerCreateInfo SDL_GPUCompareOp compare_op; /**< The comparison operator to apply to fetched data before filtering. */ float min_lod; /**< Clamps the minimum of the computed LOD value. */ float max_lod; /**< Clamps the maximum of the computed LOD value. */ - bool enable_anisotropy; /**< true to enable anisotropic filtering. */ - bool enable_compare; /**< true to enable comparison against a reference value during lookups. */ + bool enable_anisotropy; /**< true to enable anisotropic filtering. */ + bool enable_compare; /**< true to enable comparison against a reference value during lookups. */ Uint8 padding1; Uint8 padding2; @@ -1613,6 +1691,9 @@ typedef struct SDL_GPUStencilOpState * \since This struct is available since SDL 3.2.0. * * \sa SDL_GPUColorTargetDescription + * \sa SDL_GPUBlendFactor + * \sa SDL_GPUBlendOp + * \sa SDL_GPUColorComponentFlags */ typedef struct SDL_GPUColorTargetBlendState { @@ -1623,8 +1704,8 @@ typedef struct SDL_GPUColorTargetBlendState SDL_GPUBlendFactor dst_alpha_blendfactor; /**< The value to be multiplied by the destination alpha. */ SDL_GPUBlendOp alpha_blend_op; /**< The blend operation for the alpha component. */ SDL_GPUColorComponentFlags color_write_mask; /**< A bitmask specifying which of the RGBA components are enabled for writing. Writes to all channels if enable_color_write_mask is false. */ - bool enable_blend; /**< Whether blending is enabled for the color target. */ - bool enable_color_write_mask; /**< Whether the color write mask is enabled. */ + bool enable_blend; /**< Whether blending is enabled for the color target. */ + bool enable_color_write_mask; /**< Whether the color write mask is enabled. */ Uint8 padding1; Uint8 padding2; } SDL_GPUColorTargetBlendState; @@ -1636,6 +1717,8 @@ typedef struct SDL_GPUColorTargetBlendState * \since This struct is available since SDL 3.2.0. * * \sa SDL_CreateGPUShader + * \sa SDL_GPUShaderFormat + * \sa SDL_GPUShaderStage */ typedef struct SDL_GPUShaderCreateInfo { @@ -1741,8 +1824,8 @@ typedef struct SDL_GPURasterizerState float depth_bias_constant_factor; /**< A scalar factor controlling the depth value added to each fragment. */ float depth_bias_clamp; /**< The maximum depth bias of a fragment. */ float depth_bias_slope_factor; /**< A scalar factor applied to a fragment's slope in depth calculations. */ - bool enable_depth_bias; /**< true to bias fragment depth values. */ - bool enable_depth_clip; /**< true to enable depth clip, false to enable depth clamp. */ + bool enable_depth_bias; /**< true to bias fragment depth values. */ + bool enable_depth_clip; /**< true to enable depth clip, false to enable depth clamp. */ Uint8 padding1; Uint8 padding2; } SDL_GPURasterizerState; @@ -1759,8 +1842,8 @@ typedef struct SDL_GPUMultisampleState { SDL_GPUSampleCount sample_count; /**< The number of samples to be used in rasterization. */ Uint32 sample_mask; /**< Reserved for future use. Must be set to 0. */ - bool enable_mask; /**< Reserved for future use. Must be set to false. */ - Uint8 padding1; + bool enable_mask; /**< Reserved for future use. Must be set to false. */ + bool enable_alpha_to_coverage; /**< true enables the alpha-to-coverage feature. */ Uint8 padding2; Uint8 padding3; } SDL_GPUMultisampleState; @@ -1780,9 +1863,9 @@ typedef struct SDL_GPUDepthStencilState SDL_GPUStencilOpState front_stencil_state; /**< The stencil op state for front-facing triangles. */ Uint8 compare_mask; /**< Selects the bits of the stencil values participating in the stencil test. */ Uint8 write_mask; /**< Selects the bits of the stencil values updated by the stencil test. */ - bool enable_depth_test; /**< true enables the depth test. */ - bool enable_depth_write; /**< true enables depth writes. Depth writes are always disabled when enable_depth_test is false. */ - bool enable_stencil_test; /**< true enables the stencil test. */ + bool enable_depth_test; /**< true enables the depth test. */ + bool enable_depth_write; /**< true enables depth writes. Depth writes are always disabled when enable_depth_test is false. */ + bool enable_stencil_test; /**< true enables the stencil test. */ Uint8 padding1; Uint8 padding2; Uint8 padding3; @@ -1817,7 +1900,7 @@ typedef struct SDL_GPUGraphicsPipelineTargetInfo const SDL_GPUColorTargetDescription *color_target_descriptions; /**< A pointer to an array of color target descriptions. */ Uint32 num_color_targets; /**< The number of color target descriptions in the above array. */ SDL_GPUTextureFormat depth_stencil_format; /**< The pixel format of the depth-stencil target. Ignored if has_depth_stencil_target is false. */ - bool has_depth_stencil_target; /**< true specifies that the pipeline uses a depth-stencil target. */ + bool has_depth_stencil_target; /**< true specifies that the pipeline uses a depth-stencil target. */ Uint8 padding1; Uint8 padding2; Uint8 padding3; @@ -1924,8 +2007,8 @@ typedef struct SDL_GPUColorTargetInfo SDL_GPUTexture *resolve_texture; /**< The texture that will receive the results of a multisample resolve operation. Ignored if a RESOLVE* store_op is not used. */ Uint32 resolve_mip_level; /**< The mip level of the resolve texture to use for the resolve operation. Ignored if a RESOLVE* store_op is not used. */ Uint32 resolve_layer; /**< The layer index of the resolve texture to use for the resolve operation. Ignored if a RESOLVE* store_op is not used. */ - bool cycle; /**< true cycles the texture if the texture is bound and load_op is not LOAD */ - bool cycle_resolve_texture; /**< true cycles the resolve texture if the resolve texture is bound. Ignored if a RESOLVE* store_op is not used. */ + bool cycle; /**< true cycles the texture if the texture is bound and load_op is not LOAD */ + bool cycle_resolve_texture; /**< true cycles the resolve texture if the resolve texture is bound. Ignored if a RESOLVE* store_op is not used. */ Uint8 padding1; Uint8 padding2; } SDL_GPUColorTargetInfo; @@ -1982,7 +2065,7 @@ typedef struct SDL_GPUDepthStencilTargetInfo SDL_GPUStoreOp store_op; /**< What is done with the depth results of the render pass. */ SDL_GPULoadOp stencil_load_op; /**< What is done with the stencil contents at the beginning of the render pass. */ SDL_GPUStoreOp stencil_store_op; /**< What is done with the stencil results of the render pass. */ - bool cycle; /**< true cycles the texture if the texture is bound and any load ops are not LOAD */ + bool cycle; /**< true cycles the texture if the texture is bound and any load ops are not LOAD */ Uint8 clear_stencil; /**< The value to clear the stencil component to at the beginning of the render pass. Ignored if SDL_GPU_LOADOP_CLEAR is not used. */ Uint8 padding1; Uint8 padding2; @@ -2002,7 +2085,7 @@ typedef struct SDL_GPUBlitInfo { SDL_FColor clear_color; /**< The color to clear the destination region to before the blit. Ignored if load_op is not SDL_GPU_LOADOP_CLEAR. */ SDL_FlipMode flip_mode; /**< The flip mode for the source region. */ SDL_GPUFilter filter; /**< The filter mode used when blitting. */ - bool cycle; /**< true cycles the destination texture if it is already bound. */ + bool cycle; /**< true cycles the destination texture if it is already bound. */ Uint8 padding1; Uint8 padding2; Uint8 padding3; @@ -2111,6 +2194,13 @@ extern SDL_DECLSPEC bool SDLCALL SDL_GPUSupportsProperties( /** * Creates a GPU context. * + * The GPU driver name can be one of the following: + * + * - "vulkan": [Vulkan](CategoryGPU#vulkan) + * - "direct3d12": [D3D12](CategoryGPU#d3d12) + * - "metal": [Metal](CategoryGPU#metal) + * - NULL: let SDL pick the optimal driver + * * \param format_flags a bitflag indicating which shader formats the app is * able to provide. * \param debug_mode enable debug mode properties and validations. @@ -2121,6 +2211,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_GPUSupportsProperties( * * \since This function is available since SDL 3.2.0. * + * \sa SDL_CreateGPUDeviceWithProperties * \sa SDL_GetGPUShaderFormats * \sa SDL_GetGPUDeviceDriver * \sa SDL_DestroyGPUDevice @@ -2140,6 +2231,8 @@ extern SDL_DECLSPEC SDL_GPUDevice * SDLCALL SDL_CreateGPUDevice( * properties and validations, defaults to true. * - `SDL_PROP_GPU_DEVICE_CREATE_PREFERLOWPOWER_BOOLEAN`: enable to prefer * energy efficiency over maximum GPU performance, defaults to false. + * - `SDL_PROP_GPU_DEVICE_CREATE_VERBOSE_BOOLEAN`: enable to automatically log + * useful debug information on device creation, defaults to true. * - `SDL_PROP_GPU_DEVICE_CREATE_NAME_STRING`: the name of the GPU driver to * use, if a specific one is desired. * @@ -2163,6 +2256,25 @@ extern SDL_DECLSPEC SDL_GPUDevice * SDLCALL SDL_CreateGPUDevice( * - `SDL_PROP_GPU_DEVICE_CREATE_D3D12_SEMANTIC_NAME_STRING`: the prefix to * use for all vertex semantics, default is "TEXCOORD". * + * With the Vulkan renderer: + * + * - `SDL_PROP_GPU_DEVICE_CREATE_VULKAN_SHADERCLIPDISTANCE_BOOLEAN`: Enable + * device feature shaderClipDistance. If disabled, clip distances are not + * supported in shader code: gl_ClipDistance[] built-ins of GLSL, + * SV_ClipDistance0/1 semantics of HLSL and [[clip_distance]] attribute of + * Metal. Defaults to true. + * - `SDL_PROP_GPU_DEVICE_CREATE_VULKAN_DEPTHCLAMP_BOOLEAN`: Enable device + * feature depthClamp. If disabled, there is no depth clamp support and + * enable_depth_clip in SDL_GPURasterizerState must always be set to true. + * Defaults to true. + * - `SDL_PROP_GPU_DEVICE_CREATE_VULKAN_DRAWINDIRECTFIRST_BOOLEAN`: Enable + * device feature drawIndirectFirstInstance. If disabled, the argument + * first_instance of SDL_GPUIndirectDrawCommand must be set to zero. + * Defaults to true. + * - `SDL_PROP_GPU_DEVICE_CREATE_VULKAN_SAMPLERANISOTROPY_BOOLEAN`: Enable + * device feature samplerAnisotropy. If disabled, enable_anisotropy of + * SDL_GPUSamplerCreateInfo must be set to false. Defaults to true. + * * \param props the properties to use. * \returns a GPU context on success or NULL on failure; call SDL_GetError() * for more information. @@ -2177,16 +2289,21 @@ extern SDL_DECLSPEC SDL_GPUDevice * SDLCALL SDL_CreateGPUDevice( extern SDL_DECLSPEC SDL_GPUDevice * SDLCALL SDL_CreateGPUDeviceWithProperties( SDL_PropertiesID props); -#define SDL_PROP_GPU_DEVICE_CREATE_DEBUGMODE_BOOLEAN "SDL.gpu.device.create.debugmode" -#define SDL_PROP_GPU_DEVICE_CREATE_PREFERLOWPOWER_BOOLEAN "SDL.gpu.device.create.preferlowpower" -#define SDL_PROP_GPU_DEVICE_CREATE_NAME_STRING "SDL.gpu.device.create.name" -#define SDL_PROP_GPU_DEVICE_CREATE_SHADERS_PRIVATE_BOOLEAN "SDL.gpu.device.create.shaders.private" -#define SDL_PROP_GPU_DEVICE_CREATE_SHADERS_SPIRV_BOOLEAN "SDL.gpu.device.create.shaders.spirv" -#define SDL_PROP_GPU_DEVICE_CREATE_SHADERS_DXBC_BOOLEAN "SDL.gpu.device.create.shaders.dxbc" -#define SDL_PROP_GPU_DEVICE_CREATE_SHADERS_DXIL_BOOLEAN "SDL.gpu.device.create.shaders.dxil" -#define SDL_PROP_GPU_DEVICE_CREATE_SHADERS_MSL_BOOLEAN "SDL.gpu.device.create.shaders.msl" -#define SDL_PROP_GPU_DEVICE_CREATE_SHADERS_METALLIB_BOOLEAN "SDL.gpu.device.create.shaders.metallib" -#define SDL_PROP_GPU_DEVICE_CREATE_D3D12_SEMANTIC_NAME_STRING "SDL.gpu.device.create.d3d12.semantic" +#define SDL_PROP_GPU_DEVICE_CREATE_DEBUGMODE_BOOLEAN "SDL.gpu.device.create.debugmode" +#define SDL_PROP_GPU_DEVICE_CREATE_PREFERLOWPOWER_BOOLEAN "SDL.gpu.device.create.preferlowpower" +#define SDL_PROP_GPU_DEVICE_CREATE_VERBOSE_BOOLEAN "SDL.gpu.device.create.verbose" +#define SDL_PROP_GPU_DEVICE_CREATE_NAME_STRING "SDL.gpu.device.create.name" +#define SDL_PROP_GPU_DEVICE_CREATE_SHADERS_PRIVATE_BOOLEAN "SDL.gpu.device.create.shaders.private" +#define SDL_PROP_GPU_DEVICE_CREATE_SHADERS_SPIRV_BOOLEAN "SDL.gpu.device.create.shaders.spirv" +#define SDL_PROP_GPU_DEVICE_CREATE_SHADERS_DXBC_BOOLEAN "SDL.gpu.device.create.shaders.dxbc" +#define SDL_PROP_GPU_DEVICE_CREATE_SHADERS_DXIL_BOOLEAN "SDL.gpu.device.create.shaders.dxil" +#define SDL_PROP_GPU_DEVICE_CREATE_SHADERS_MSL_BOOLEAN "SDL.gpu.device.create.shaders.msl" +#define SDL_PROP_GPU_DEVICE_CREATE_SHADERS_METALLIB_BOOLEAN "SDL.gpu.device.create.shaders.metallib" +#define SDL_PROP_GPU_DEVICE_CREATE_D3D12_SEMANTIC_NAME_STRING "SDL.gpu.device.create.d3d12.semantic" +#define SDL_PROP_GPU_DEVICE_CREATE_VULKAN_SHADERCLIPDISTANCE_BOOLEAN "SDL.gpu.device.create.vulkan.shaderclipdistance" +#define SDL_PROP_GPU_DEVICE_CREATE_VULKAN_DEPTHCLAMP_BOOLEAN "SDL.gpu.device.create.vulkan.depthclamp" +#define SDL_PROP_GPU_DEVICE_CREATE_VULKAN_DRAWINDIRECTFIRST_BOOLEAN "SDL.gpu.device.create.vulkan.drawindirectfirstinstance" +#define SDL_PROP_GPU_DEVICE_CREATE_VULKAN_SAMPLERANISOTROPY_BOOLEAN "SDL.gpu.device.create.vulkan.sampleranisotropy" /** * Destroys a GPU context previously returned by SDL_CreateGPUDevice. @@ -2250,6 +2367,116 @@ extern SDL_DECLSPEC const char * SDLCALL SDL_GetGPUDeviceDriver(SDL_GPUDevice *d */ extern SDL_DECLSPEC SDL_GPUShaderFormat SDLCALL SDL_GetGPUShaderFormats(SDL_GPUDevice *device); +/** + * Get the properties associated with a GPU device. + * + * All properties are optional and may differ between GPU backends and SDL + * versions. + * + * The following properties are provided by SDL: + * + * `SDL_PROP_GPU_DEVICE_NAME_STRING`: Contains the name of the underlying + * device as reported by the system driver. This string has no standardized + * format, is highly inconsistent between hardware devices and drivers, and is + * able to change at any time. Do not attempt to parse this string as it is + * bound to fail at some point in the future when system drivers are updated, + * new hardware devices are introduced, or when SDL adds new GPU backends or + * modifies existing ones. + * + * Strings that have been found in the wild include: + * + * - GTX 970 + * - GeForce GTX 970 + * - NVIDIA GeForce GTX 970 + * - Microsoft Direct3D12 (NVIDIA GeForce GTX 970) + * - NVIDIA Graphics Device + * - GeForce GPU + * - P106-100 + * - AMD 15D8:C9 + * - AMD Custom GPU 0405 + * - AMD Radeon (TM) Graphics + * - ASUS Radeon RX 470 Series + * - Intel(R) Arc(tm) A380 Graphics (DG2) + * - Virtio-GPU Venus (NVIDIA TITAN V) + * - SwiftShader Device (LLVM 16.0.0) + * - llvmpipe (LLVM 15.0.4, 256 bits) + * - Microsoft Basic Render Driver + * - unknown device + * + * The above list shows that the same device can have different formats, the + * vendor name may or may not appear in the string, the included vendor name + * may not be the vendor of the chipset on the device, some manufacturers + * include pseudo-legal marks while others don't, some devices may not use a + * marketing name in the string, the device string may be wrapped by the name + * of a translation interface, the device may be emulated in software, or the + * string may contain generic text that does not identify the device at all. + * + * `SDL_PROP_GPU_DEVICE_DRIVER_NAME_STRING`: Contains the self-reported name + * of the underlying system driver. + * + * Strings that have been found in the wild include: + * + * - Intel Corporation + * - Intel open-source Mesa driver + * - Qualcomm Technologies Inc. Adreno Vulkan Driver + * - MoltenVK + * - Mali-G715 + * - venus + * + * `SDL_PROP_GPU_DEVICE_DRIVER_VERSION_STRING`: Contains the self-reported + * version of the underlying system driver. This is a relatively short version + * string in an unspecified format. If SDL_PROP_GPU_DEVICE_DRIVER_INFO_STRING + * is available then that property should be preferred over this one as it may + * contain additional information that is useful for identifying the exact + * driver version used. + * + * Strings that have been found in the wild include: + * + * - 53.0.0 + * - 0.405.2463 + * - 32.0.15.6614 + * + * `SDL_PROP_GPU_DEVICE_DRIVER_INFO_STRING`: Contains the detailed version + * information of the underlying system driver as reported by the driver. This + * is an arbitrary string with no standardized format and it may contain + * newlines. This property should be preferred over + * SDL_PROP_GPU_DEVICE_DRIVER_VERSION_STRING if it is available as it usually + * contains the same information but in a format that is easier to read. + * + * Strings that have been found in the wild include: + * + * - 101.6559 + * - 1.2.11 + * - Mesa 21.2.2 (LLVM 12.0.1) + * - Mesa 22.2.0-devel (git-f226222 2022-04-14 impish-oibaf-ppa) + * - v1.r53p0-00eac0.824c4f31403fb1fbf8ee1042422c2129 + * + * This string has also been observed to be a multiline string (which has a + * trailing newline): + * + * ``` + * Driver Build: 85da404, I46ff5fc46f, 1606794520 + * Date: 11/30/20 + * Compiler Version: EV031.31.04.01 + * Driver Branch: promo490_3_Google + * ``` + * + * \param device a GPU context to query. + * \returns a valid property ID on success or 0 on failure; call + * SDL_GetError() for more information. + * + * \threadsafety It is safe to call this function from any thread. + * + * \since This function is available since SDL 3.4.0. + */ +extern SDL_DECLSPEC SDL_PropertiesID SDLCALL SDL_GetGPUDeviceProperties(SDL_GPUDevice *device); + +#define SDL_PROP_GPU_DEVICE_NAME_STRING "SDL.gpu.device.name" +#define SDL_PROP_GPU_DEVICE_DRIVER_NAME_STRING "SDL.gpu.device.driver_name" +#define SDL_PROP_GPU_DEVICE_DRIVER_VERSION_STRING "SDL.gpu.device.driver_version" +#define SDL_PROP_GPU_DEVICE_DRIVER_INFO_STRING "SDL.gpu.device.driver_info" + + /* State Creation */ /** @@ -2467,9 +2694,9 @@ extern SDL_DECLSPEC SDL_GPUShader * SDLCALL SDL_CreateGPUShader( * - `SDL_PROP_GPU_TEXTURE_CREATE_D3D12_CLEAR_DEPTH_FLOAT`: (Direct3D 12 only) * if the texture usage is SDL_GPU_TEXTUREUSAGE_DEPTH_STENCIL_TARGET, clear * the texture to a depth of this value. Defaults to zero. - * - `SDL_PROP_GPU_TEXTURE_CREATE_D3D12_CLEAR_STENCIL_UINT8`: (Direct3D 12 + * - `SDL_PROP_GPU_TEXTURE_CREATE_D3D12_CLEAR_STENCIL_NUMBER`: (Direct3D 12 * only) if the texture usage is SDL_GPU_TEXTUREUSAGE_DEPTH_STENCIL_TARGET, - * clear the texture to a stencil of this value. Defaults to zero. + * clear the texture to a stencil of this Uint8 value. Defaults to zero. * - `SDL_PROP_GPU_TEXTURE_CREATE_NAME_STRING`: a name that can be displayed * in debugging tools. * @@ -2495,13 +2722,13 @@ extern SDL_DECLSPEC SDL_GPUTexture * SDLCALL SDL_CreateGPUTexture( SDL_GPUDevice *device, const SDL_GPUTextureCreateInfo *createinfo); -#define SDL_PROP_GPU_TEXTURE_CREATE_D3D12_CLEAR_R_FLOAT "SDL.gpu.texture.create.d3d12.clear.r" -#define SDL_PROP_GPU_TEXTURE_CREATE_D3D12_CLEAR_G_FLOAT "SDL.gpu.texture.create.d3d12.clear.g" -#define SDL_PROP_GPU_TEXTURE_CREATE_D3D12_CLEAR_B_FLOAT "SDL.gpu.texture.create.d3d12.clear.b" -#define SDL_PROP_GPU_TEXTURE_CREATE_D3D12_CLEAR_A_FLOAT "SDL.gpu.texture.create.d3d12.clear.a" -#define SDL_PROP_GPU_TEXTURE_CREATE_D3D12_CLEAR_DEPTH_FLOAT "SDL.gpu.texture.create.d3d12.clear.depth" -#define SDL_PROP_GPU_TEXTURE_CREATE_D3D12_CLEAR_STENCIL_UINT8 "SDL.gpu.texture.create.d3d12.clear.stencil" -#define SDL_PROP_GPU_TEXTURE_CREATE_NAME_STRING "SDL.gpu.texture.create.name" +#define SDL_PROP_GPU_TEXTURE_CREATE_D3D12_CLEAR_R_FLOAT "SDL.gpu.texture.create.d3d12.clear.r" +#define SDL_PROP_GPU_TEXTURE_CREATE_D3D12_CLEAR_G_FLOAT "SDL.gpu.texture.create.d3d12.clear.g" +#define SDL_PROP_GPU_TEXTURE_CREATE_D3D12_CLEAR_B_FLOAT "SDL.gpu.texture.create.d3d12.clear.b" +#define SDL_PROP_GPU_TEXTURE_CREATE_D3D12_CLEAR_A_FLOAT "SDL.gpu.texture.create.d3d12.clear.a" +#define SDL_PROP_GPU_TEXTURE_CREATE_D3D12_CLEAR_DEPTH_FLOAT "SDL.gpu.texture.create.d3d12.clear.depth" +#define SDL_PROP_GPU_TEXTURE_CREATE_D3D12_CLEAR_STENCIL_NUMBER "SDL.gpu.texture.create.d3d12.clear.stencil" +#define SDL_PROP_GPU_TEXTURE_CREATE_NAME_STRING "SDL.gpu.texture.create.name" /** * Creates a buffer object to be used in graphics or compute workflows. @@ -2822,6 +3049,9 @@ extern SDL_DECLSPEC SDL_GPUCommandBuffer * SDLCALL SDL_AcquireGPUCommandBuffer( * terms this means you must ensure that vec3 and vec4 fields are 16-byte * aligned. * + * For detailed information about accessing uniform data from a shader, please + * refer to SDL_CreateGPUShader. + * * \param command_buffer a command buffer. * \param slot_index the vertex uniform slot to push data to. * \param data client data to write. @@ -3775,7 +4005,7 @@ extern SDL_DECLSPEC void SDLCALL SDL_ReleaseWindowFromGPUDevice( * supported via SDL_WindowSupportsGPUPresentMode / * SDL_WindowSupportsGPUSwapchainComposition prior to calling this function. * - * SDL_GPU_PRESENTMODE_VSYNC and SDL_GPU_SWAPCHAINCOMPOSITION_SDR are always + * SDL_GPU_PRESENTMODE_VSYNC with SDL_GPU_SWAPCHAINCOMPOSITION_SDR is always * supported. * * \param device a GPU context. @@ -3849,7 +4079,9 @@ extern SDL_DECLSPEC SDL_GPUTextureFormat SDLCALL SDL_GetGPUSwapchainTextureForma * buffer used to acquire it. * * This function will fill the swapchain texture handle with NULL if too many - * frames are in flight. This is not an error. + * frames are in flight. This is not an error. This NULL pointer should not be + * passed back into SDL. Instead, it should be considered as an indication to + * wait until the swapchain is available. * * If you use this function, it is possible to create a situation where many * command buffers are allocated while the rendering context waits for the GPU @@ -4211,3 +4443,8 @@ extern SDL_DECLSPEC void SDLCALL SDL_GDKResumeGPU(SDL_GPUDevice *device); #include #endif /* SDL_gpu_h_ */ + + + + + diff --git a/libs/SDL3/include/SDL3/SDL_haptic.h b/libs/SDL3/include/SDL3/SDL_haptic.h index a45335b2..9a20396d 100644 --- a/libs/SDL3/include/SDL3/SDL_haptic.h +++ b/libs/SDL3/include/SDL3/SDL_haptic.h @@ -70,7 +70,7 @@ * { * SDL_Haptic *haptic; * SDL_HapticEffect effect; - * int effect_id; + * SDL_HapticEffectID effect_id; * * // Open the device * haptic = SDL_OpenHapticFromJoystick(joystick); @@ -149,6 +149,19 @@ extern "C" { */ typedef struct SDL_Haptic SDL_Haptic; +/* + * Misc defines. + */ + +/** + * Used to play a device an infinite number of times. + * + * \since This macro is available since SDL 3.2.0. + * + * \sa SDL_RunHapticEffect + */ +#define SDL_HAPTIC_INFINITY 4294967295U + /** * \name Haptic features @@ -162,6 +175,11 @@ typedef struct SDL_Haptic SDL_Haptic; */ /* @{ */ +/** + * Type of haptic effect. + */ +typedef Uint16 SDL_HapticEffectType; + /** * Constant effect supported. * @@ -383,6 +401,11 @@ typedef struct SDL_Haptic SDL_Haptic; */ /* @{ */ +/** + * Type of coordinates used for haptic direction. + */ +typedef Uint8 SDL_HapticDirectionType; + /** * Uses polar coordinates for the direction. * @@ -426,18 +449,15 @@ typedef struct SDL_Haptic SDL_Haptic; /* @} *//* Haptic features */ -/* - * Misc defines. - */ /** - * Used to play a device an infinite number of times. + * ID for haptic effects. * - * \since This macro is available since SDL 3.2.0. + * This is -1 if the ID is invalid. * - * \sa SDL_RunHapticEffect + * \sa SDL_CreateHapticEffect */ -#define SDL_HAPTIC_INFINITY 4294967295U +typedef int SDL_HapticEffectID; /** @@ -545,8 +565,8 @@ typedef struct SDL_Haptic SDL_Haptic; */ typedef struct SDL_HapticDirection { - Uint8 type; /**< The type of encoding. */ - Sint32 dir[3]; /**< The encoded direction. */ + SDL_HapticDirectionType type; /**< The type of encoding. */ + Sint32 dir[3]; /**< The encoded direction. */ } SDL_HapticDirection; @@ -566,7 +586,7 @@ typedef struct SDL_HapticDirection typedef struct SDL_HapticConstant { /* Header */ - Uint16 type; /**< SDL_HAPTIC_CONSTANT */ + SDL_HapticEffectType type; /**< SDL_HAPTIC_CONSTANT */ SDL_HapticDirection direction; /**< Direction of the effect. */ /* Replay */ @@ -652,9 +672,9 @@ typedef struct SDL_HapticConstant typedef struct SDL_HapticPeriodic { /* Header */ - Uint16 type; /**< SDL_HAPTIC_SINE, SDL_HAPTIC_SQUARE - SDL_HAPTIC_TRIANGLE, SDL_HAPTIC_SAWTOOTHUP or - SDL_HAPTIC_SAWTOOTHDOWN */ + SDL_HapticEffectType type; /**< SDL_HAPTIC_SINE, SDL_HAPTIC_SQUARE + SDL_HAPTIC_TRIANGLE, SDL_HAPTIC_SAWTOOTHUP or + SDL_HAPTIC_SAWTOOTHDOWN */ SDL_HapticDirection direction; /**< Direction of the effect. */ /* Replay */ @@ -708,8 +728,8 @@ typedef struct SDL_HapticPeriodic typedef struct SDL_HapticCondition { /* Header */ - Uint16 type; /**< SDL_HAPTIC_SPRING, SDL_HAPTIC_DAMPER, - SDL_HAPTIC_INERTIA or SDL_HAPTIC_FRICTION */ + SDL_HapticEffectType type; /**< SDL_HAPTIC_SPRING, SDL_HAPTIC_DAMPER, + SDL_HAPTIC_INERTIA or SDL_HAPTIC_FRICTION */ SDL_HapticDirection direction; /**< Direction of the effect. */ /* Replay */ @@ -747,7 +767,7 @@ typedef struct SDL_HapticCondition typedef struct SDL_HapticRamp { /* Header */ - Uint16 type; /**< SDL_HAPTIC_RAMP */ + SDL_HapticEffectType type; /**< SDL_HAPTIC_RAMP */ SDL_HapticDirection direction; /**< Direction of the effect. */ /* Replay */ @@ -786,7 +806,7 @@ typedef struct SDL_HapticRamp typedef struct SDL_HapticLeftRight { /* Header */ - Uint16 type; /**< SDL_HAPTIC_LEFTRIGHT */ + SDL_HapticEffectType type; /**< SDL_HAPTIC_LEFTRIGHT */ /* Replay */ Uint32 length; /**< Duration of the effect in milliseconds. */ @@ -816,7 +836,7 @@ typedef struct SDL_HapticLeftRight typedef struct SDL_HapticCustom { /* Header */ - Uint16 type; /**< SDL_HAPTIC_CUSTOM */ + SDL_HapticEffectType type; /**< SDL_HAPTIC_CUSTOM */ SDL_HapticDirection direction; /**< Direction of the effect. */ /* Replay */ @@ -915,7 +935,7 @@ typedef struct SDL_HapticCustom typedef union SDL_HapticEffect { /* Common for all force feedback effects */ - Uint16 type; /**< Effect type. */ + SDL_HapticEffectType type; /**< Effect type. */ SDL_HapticConstant constant; /**< Constant effect. */ SDL_HapticPeriodic periodic; /**< Periodic effect. */ SDL_HapticCondition condition; /**< Condition effect. */ @@ -1193,7 +1213,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_HapticEffectSupported(SDL_Haptic *haptic, c * \sa SDL_RunHapticEffect * \sa SDL_UpdateHapticEffect */ -extern SDL_DECLSPEC int SDLCALL SDL_CreateHapticEffect(SDL_Haptic *haptic, const SDL_HapticEffect *effect); +extern SDL_DECLSPEC SDL_HapticEffectID SDLCALL SDL_CreateHapticEffect(SDL_Haptic *haptic, const SDL_HapticEffect *effect); /** * Update the properties of an effect. @@ -1215,7 +1235,7 @@ extern SDL_DECLSPEC int SDLCALL SDL_CreateHapticEffect(SDL_Haptic *haptic, const * \sa SDL_CreateHapticEffect * \sa SDL_RunHapticEffect */ -extern SDL_DECLSPEC bool SDLCALL SDL_UpdateHapticEffect(SDL_Haptic *haptic, int effect, const SDL_HapticEffect *data); +extern SDL_DECLSPEC bool SDLCALL SDL_UpdateHapticEffect(SDL_Haptic *haptic, SDL_HapticEffectID effect, const SDL_HapticEffect *data); /** * Run the haptic effect on its associated haptic device. @@ -1239,7 +1259,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_UpdateHapticEffect(SDL_Haptic *haptic, int * \sa SDL_StopHapticEffect * \sa SDL_StopHapticEffects */ -extern SDL_DECLSPEC bool SDLCALL SDL_RunHapticEffect(SDL_Haptic *haptic, int effect, Uint32 iterations); +extern SDL_DECLSPEC bool SDLCALL SDL_RunHapticEffect(SDL_Haptic *haptic, SDL_HapticEffectID effect, Uint32 iterations); /** * Stop the haptic effect on its associated haptic device. @@ -1254,7 +1274,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_RunHapticEffect(SDL_Haptic *haptic, int eff * \sa SDL_RunHapticEffect * \sa SDL_StopHapticEffects */ -extern SDL_DECLSPEC bool SDLCALL SDL_StopHapticEffect(SDL_Haptic *haptic, int effect); +extern SDL_DECLSPEC bool SDLCALL SDL_StopHapticEffect(SDL_Haptic *haptic, SDL_HapticEffectID effect); /** * Destroy a haptic effect on the device. @@ -1269,7 +1289,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_StopHapticEffect(SDL_Haptic *haptic, int ef * * \sa SDL_CreateHapticEffect */ -extern SDL_DECLSPEC void SDLCALL SDL_DestroyHapticEffect(SDL_Haptic *haptic, int effect); +extern SDL_DECLSPEC void SDLCALL SDL_DestroyHapticEffect(SDL_Haptic *haptic, SDL_HapticEffectID effect); /** * Get the status of the current effect on the specified haptic device. @@ -1285,7 +1305,7 @@ extern SDL_DECLSPEC void SDLCALL SDL_DestroyHapticEffect(SDL_Haptic *haptic, int * * \sa SDL_GetHapticFeatures */ -extern SDL_DECLSPEC bool SDLCALL SDL_GetHapticEffectStatus(SDL_Haptic *haptic, int effect); +extern SDL_DECLSPEC bool SDLCALL SDL_GetHapticEffectStatus(SDL_Haptic *haptic, SDL_HapticEffectID effect); /** * Set the global gain of the specified haptic device. diff --git a/libs/SDL3/include/SDL3/SDL_hints.h b/libs/SDL3/include/SDL3/SDL_hints.h index 9c8ad3f8..7961157c 100644 --- a/libs/SDL3/include/SDL3/SDL_hints.h +++ b/libs/SDL3/include/SDL3/SDL_hints.h @@ -711,8 +711,6 @@ extern "C" { * * This hint only applies to the emscripten platform. * - * The default value is "#canvas" - * * This hint should be set before creating a window. * * \since This hint is available since SDL 3.2.0. @@ -726,7 +724,7 @@ extern "C" { * * The variable can be one of: * - * - "#window": the javascript window object (default) + * - "#window": the javascript window object * - "#document": the javascript document object * - "#screen": the javascript window.screen object * - "#canvas": the WebGL canvas element @@ -1074,8 +1072,8 @@ extern "C" { * * By default, SDL will try all available GPU backends in a reasonable order * until it finds one that can work, but this hint allows the app or user to - * force a specific target, such as "direct3d11" if, say, your hardware - * supports D3D12 but want to try using D3D11 instead. + * force a specific target, such as "direct3d12" if, say, your hardware + * supports Vulkan but you want to try using D3D12 instead. * * This hint should be set before any GPU functions are called. * @@ -1723,6 +1721,43 @@ extern "C" { */ #define SDL_HINT_JOYSTICK_HIDAPI_STEAM_HORI "SDL_JOYSTICK_HIDAPI_STEAM_HORI" +/** + * A variable controlling whether the HIDAPI driver for some Logitech wheels + * should be used. + * + * This variable can be set to the following values: + * + * - "0": HIDAPI driver is not used + * - "1": HIDAPI driver is used + * + * The default is the value of SDL_HINT_JOYSTICK_HIDAPI + */ +#define SDL_HINT_JOYSTICK_HIDAPI_LG4FF "SDL_JOYSTICK_HIDAPI_LG4FF" + +/** + * A variable controlling whether the HIDAPI driver for 8BitDo controllers + * should be used. + * + * This variable can be set to the following values: + * + * "0" - HIDAPI driver is not used. "1" - HIDAPI driver is used. + * + * The default is the value of SDL_HINT_JOYSTICK_HIDAPI + */ +#define SDL_HINT_JOYSTICK_HIDAPI_8BITDO "SDL_JOYSTICK_HIDAPI_8BITDO" + +/** + * A variable controlling whether the HIDAPI driver for Flydigi controllers + * should be used. + * + * This variable can be set to the following values: + * + * "0" - HIDAPI driver is not used. "1" - HIDAPI driver is used. + * + * The default is the value of SDL_HINT_JOYSTICK_HIDAPI + */ +#define SDL_HINT_JOYSTICK_HIDAPI_FLYDIGI "SDL_JOYSTICK_HIDAPI_FLYDIGI" + /** * A variable controlling whether the HIDAPI driver for Nintendo Switch * controllers should be used. @@ -1926,6 +1961,41 @@ extern "C" { */ #define SDL_HINT_JOYSTICK_HIDAPI_XBOX_ONE_HOME_LED "SDL_JOYSTICK_HIDAPI_XBOX_ONE_HOME_LED" +/** + * A variable controlling whether the new HIDAPI driver for wired Xbox One + * (GIP) controllers should be used. + * + * The variable can be set to the following values: + * + * - "0": HIDAPI driver is not used. + * - "1": HIDAPI driver is used. + * + * The default is the value of SDL_HINT_JOYSTICK_HIDAPI_XBOX_ONE. + * + * This hint should be set before initializing joysticks and gamepads. + * + * \since This hint is available since SDL 3.4.0. + */ +#define SDL_HINT_JOYSTICK_HIDAPI_GIP "SDL_JOYSTICK_HIDAPI_GIP" + +/** + * A variable controlling whether the new HIDAPI driver for wired Xbox One + * (GIP) controllers should reset the controller if it can't get the metadata + * from the controller. + * + * The variable can be set to the following values: + * + * - "0": Assume this is a generic controller. + * - "1": Reset the controller to get metadata. + * + * By default the controller is not reset. + * + * This hint should be set before initializing joysticks and gamepads. + * + * \since This hint is available since SDL 3.4.0. + */ +#define SDL_HINT_JOYSTICK_HIDAPI_GIP_RESET_FOR_METADATA "SDL_JOYSTICK_HIDAPI_GIP_RESET_FOR_METADATA" + /** * A variable controlling whether IOKit should be used for controller * handling. @@ -2026,8 +2096,8 @@ extern "C" { * * The variable can be set to the following values: * - * - "0": RAWINPUT drivers are not used. - * - "1": RAWINPUT drivers are used. (default) + * - "0": RAWINPUT drivers are not used. (default) + * - "1": RAWINPUT drivers are used. * * This hint should be set before SDL is initialized. * @@ -3407,6 +3477,26 @@ extern "C" { */ #define SDL_HINT_VIDEO_MAC_FULLSCREEN_MENU_VISIBILITY "SDL_VIDEO_MAC_FULLSCREEN_MENU_VISIBILITY" +/** + * A variable controlling whether SDL will attempt to automatically set the + * destination display to a mode most closely matching that of the previous + * display if an exclusive fullscreen window is moved onto it. + * + * The variable can be set to the following values: + * + * - "0": SDL will not attempt to automatically set a matching mode on the + * destination display. If an exclusive fullscreen window is moved to a new + * display, the window will become fullscreen desktop. + * - "1": SDL will attempt to automatically set a mode on the destination + * display that most closely matches the mode of the display that the + * exclusive fullscreen window was previously on. (default) + * + * This hint can be set anytime. + * + * \since This hint is available since SDL 3.4.0. + */ +#define SDL_HINT_VIDEO_MATCH_EXCLUSIVE_MODE_ON_MOVE "SDL_VIDEO_MATCH_EXCLUSIVE_MODE_ON_MOVE" + /** * A variable controlling whether fullscreen windows are minimized when they * lose focus. diff --git a/libs/SDL3/include/SDL3/SDL_init.h b/libs/SDL3/include/SDL3/SDL_init.h index adf0de8a..557f2b99 100644 --- a/libs/SDL3/include/SDL3/SDL_init.h +++ b/libs/SDL3/include/SDL3/SDL_init.h @@ -79,7 +79,7 @@ typedef Uint32 SDL_InitFlags; #define SDL_INIT_AUDIO 0x00000010u /**< `SDL_INIT_AUDIO` implies `SDL_INIT_EVENTS` */ #define SDL_INIT_VIDEO 0x00000020u /**< `SDL_INIT_VIDEO` implies `SDL_INIT_EVENTS`, should be initialized on the main thread */ -#define SDL_INIT_JOYSTICK 0x00000200u /**< `SDL_INIT_JOYSTICK` implies `SDL_INIT_EVENTS`, should be initialized on the same thread as SDL_INIT_VIDEO on Windows if you don't set SDL_HINT_JOYSTICK_THREAD */ +#define SDL_INIT_JOYSTICK 0x00000200u /**< `SDL_INIT_JOYSTICK` implies `SDL_INIT_EVENTS` */ #define SDL_INIT_HAPTIC 0x00001000u #define SDL_INIT_GAMEPAD 0x00002000u /**< `SDL_INIT_GAMEPAD` implies `SDL_INIT_JOYSTICK` */ #define SDL_INIT_EVENTS 0x00004000u @@ -101,7 +101,7 @@ typedef Uint32 SDL_InitFlags; * to run. * * See - * [Main callbacks in SDL3](https://wiki.libsdl.org/SDL3/README/main-functions#main-callbacks-in-sdl3) + * [Main callbacks in SDL3](https://wiki.libsdl.org/SDL3/README-main-functions#main-callbacks-in-sdl3) * for complete details. * * \since This enum is available since SDL 3.2.0. diff --git a/libs/SDL3/include/SDL3/SDL_iostream.h b/libs/SDL3/include/SDL3/SDL_iostream.h index 4ca16093..d1596f91 100644 --- a/libs/SDL3/include/SDL3/SDL_iostream.h +++ b/libs/SDL3/include/SDL3/SDL_iostream.h @@ -823,7 +823,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_ReadS8(SDL_IOStream *src, Sint8 *value); * * \param src the stream from which to read data. * \param value a pointer filled in with the data read. - * \returns true on successful write or false on failure; call SDL_GetError() + * \returns true on successful read or false on failure; call SDL_GetError() * for more information. * * \threadsafety This function is not thread safe. @@ -846,7 +846,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_ReadU16LE(SDL_IOStream *src, Uint16 *value) * * \param src the stream from which to read data. * \param value a pointer filled in with the data read. - * \returns true on successful write or false on failure; call SDL_GetError() + * \returns true on successful read or false on failure; call SDL_GetError() * for more information. * * \threadsafety This function is not thread safe. @@ -869,7 +869,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_ReadS16LE(SDL_IOStream *src, Sint16 *value) * * \param src the stream from which to read data. * \param value a pointer filled in with the data read. - * \returns true on successful write or false on failure; call SDL_GetError() + * \returns true on successful read or false on failure; call SDL_GetError() * for more information. * * \threadsafety This function is not thread safe. @@ -892,7 +892,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_ReadU16BE(SDL_IOStream *src, Uint16 *value) * * \param src the stream from which to read data. * \param value a pointer filled in with the data read. - * \returns true on successful write or false on failure; call SDL_GetError() + * \returns true on successful read or false on failure; call SDL_GetError() * for more information. * * \threadsafety This function is not thread safe. @@ -915,7 +915,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_ReadS16BE(SDL_IOStream *src, Sint16 *value) * * \param src the stream from which to read data. * \param value a pointer filled in with the data read. - * \returns true on successful write or false on failure; call SDL_GetError() + * \returns true on successful read or false on failure; call SDL_GetError() * for more information. * * \threadsafety This function is not thread safe. @@ -938,7 +938,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_ReadU32LE(SDL_IOStream *src, Uint32 *value) * * \param src the stream from which to read data. * \param value a pointer filled in with the data read. - * \returns true on successful write or false on failure; call SDL_GetError() + * \returns true on successful read or false on failure; call SDL_GetError() * for more information. * * \threadsafety This function is not thread safe. @@ -961,7 +961,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_ReadS32LE(SDL_IOStream *src, Sint32 *value) * * \param src the stream from which to read data. * \param value a pointer filled in with the data read. - * \returns true on successful write or false on failure; call SDL_GetError() + * \returns true on successful read or false on failure; call SDL_GetError() * for more information. * * \threadsafety This function is not thread safe. @@ -984,7 +984,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_ReadU32BE(SDL_IOStream *src, Uint32 *value) * * \param src the stream from which to read data. * \param value a pointer filled in with the data read. - * \returns true on successful write or false on failure; call SDL_GetError() + * \returns true on successful read or false on failure; call SDL_GetError() * for more information. * * \threadsafety This function is not thread safe. @@ -1007,7 +1007,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_ReadS32BE(SDL_IOStream *src, Sint32 *value) * * \param src the stream from which to read data. * \param value a pointer filled in with the data read. - * \returns true on successful write or false on failure; call SDL_GetError() + * \returns true on successful read or false on failure; call SDL_GetError() * for more information. * * \threadsafety This function is not thread safe. @@ -1030,7 +1030,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_ReadU64LE(SDL_IOStream *src, Uint64 *value) * * \param src the stream from which to read data. * \param value a pointer filled in with the data read. - * \returns true on successful write or false on failure; call SDL_GetError() + * \returns true on successful read or false on failure; call SDL_GetError() * for more information. * * \threadsafety This function is not thread safe. @@ -1053,7 +1053,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_ReadS64LE(SDL_IOStream *src, Sint64 *value) * * \param src the stream from which to read data. * \param value a pointer filled in with the data read. - * \returns true on successful write or false on failure; call SDL_GetError() + * \returns true on successful read or false on failure; call SDL_GetError() * for more information. * * \threadsafety This function is not thread safe. @@ -1076,7 +1076,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_ReadU64BE(SDL_IOStream *src, Uint64 *value) * * \param src the stream from which to read data. * \param value a pointer filled in with the data read. - * \returns true on successful write or false on failure; call SDL_GetError() + * \returns true on successful read or false on failure; call SDL_GetError() * for more information. * * \threadsafety This function is not thread safe. diff --git a/libs/SDL3/include/SDL3/SDL_joystick.h b/libs/SDL3/include/SDL3/SDL_joystick.h index d15668b6..cadccf68 100644 --- a/libs/SDL3/include/SDL3/SDL_joystick.h +++ b/libs/SDL3/include/SDL3/SDL_joystick.h @@ -107,6 +107,10 @@ typedef Uint32 SDL_JoystickID; * This is by no means a complete list of everything that can be plugged into * a computer. * + * You may refer to + * [XInput Controller Types](https://learn.microsoft.com/en-us/windows/win32/xinput/xinput-and-controller-subtypes) + * table for a general understanding of each joystick type. + * * \since This enum is available since SDL 3.2.0. */ typedef enum SDL_JoystickType diff --git a/libs/SDL3/include/SDL3/SDL_log.h b/libs/SDL3/include/SDL3/SDL_log.h index 3fd7ec2e..2019c3b4 100644 --- a/libs/SDL3/include/SDL3/SDL_log.h +++ b/libs/SDL3/include/SDL3/SDL_log.h @@ -206,6 +206,9 @@ extern SDL_DECLSPEC void SDLCALL SDL_ResetLogPriorities(void); * SDL_LOG_PRIORITY_WARN and higher have a prefix showing their priority, e.g. * "WARNING: ". * + * This function makes a copy of its string argument, **prefix**, so it is not + * necessary to keep the value of **prefix** alive after the call returns. + * * \param priority the SDL_LogPriority to modify. * \param prefix the prefix to use for that log priority, or NULL to use no * prefix. diff --git a/libs/SDL3/include/SDL3/SDL_main.h b/libs/SDL3/include/SDL3/SDL_main.h index 905d78e9..305a1a38 100644 --- a/libs/SDL3/include/SDL3/SDL_main.h +++ b/libs/SDL3/include/SDL3/SDL_main.h @@ -47,7 +47,7 @@ * * For more information, see: * - * https://wiki.libsdl.org/SDL3/README/main-functions + * https://wiki.libsdl.org/SDL3/README-main-functions */ #ifndef SDL_main_h_ @@ -68,7 +68,7 @@ * proper entry point for the platform, and all the other magic details * needed, like manually calling SDL_SetMainReady. * - * Please see [README/main-functions](README/main-functions), (or + * Please see [README-main-functions](README-main-functions), (or * docs/README-main-functions.md in the source tree) for a more detailed * explanation. * @@ -85,7 +85,7 @@ * SDL_AppQuit. The app should not provide a `main` function in this case, and * doing so will likely cause the build to fail. * - * Please see [README/main-functions](README/main-functions), (or + * Please see [README-main-functions](README-main-functions), (or * docs/README-main-functions.md in the source tree) for a more detailed * explanation. * @@ -347,10 +347,10 @@ extern SDLMAIN_DECLSPEC SDL_AppResult SDLCALL SDL_AppInit(void **appstate, int a * Apps implement this function when using SDL_MAIN_USE_CALLBACKS. If using a * standard "main" function, you should not supply this. * - * This function is called repeatedly by SDL after SDL_AppInit returns 0. The - * function should operate as a single iteration the program's primary loop; - * it should update whatever state it needs and draw a new frame of video, - * usually. + * This function is called repeatedly by SDL after SDL_AppInit returns + * SDL_APP_CONTINUE. The function should operate as a single iteration the + * program's primary loop; it should update whatever state it needs and draw a + * new frame of video, usually. * * On some platforms, this function will be called at the refresh rate of the * display (which might change during the life of your app!). There are no @@ -512,7 +512,7 @@ typedef int (SDLCALL *SDL_main_func)(int argc, char *argv[]); * SDL_MAIN_USE_CALLBACKS. * * Program startup is a surprisingly complex topic. Please see - * [README/main-functions](README/main-functions), (or + * [README-main-functions](README-main-functions), (or * docs/README-main-functions.md in the source tree) for a more detailed * explanation. * @@ -618,8 +618,8 @@ extern SDL_DECLSPEC int SDLCALL SDL_EnterAppMainCallbacks(int argc, char *argv[] * \param name the window class name, in UTF-8 encoding. If NULL, SDL * currently uses "SDL_app" but this isn't guaranteed. * \param style the value to use in WNDCLASSEX::style. If `name` is NULL, SDL - * currently uses `(CS_BYTEALIGNCLIENT | CS_OWNDC)` regardless of - * what is specified here. + * currently uses `(CS_BYTEALIGNCLIENT \| CS_OWNDC)` regardless + * of what is specified here. * \param hInst the HINSTANCE to use in WNDCLASSEX::hInstance. If zero, SDL * will use `GetModuleHandle(NULL)` instead. * \returns true on success or false on failure; call SDL_GetError() for more diff --git a/libs/SDL3/include/SDL3/SDL_mouse.h b/libs/SDL3/include/SDL3/SDL_mouse.h index 864135d2..4ecd1d00 100644 --- a/libs/SDL3/include/SDL3/SDL_mouse.h +++ b/libs/SDL3/include/SDL3/SDL_mouse.h @@ -160,6 +160,44 @@ typedef Uint32 SDL_MouseButtonFlags; #define SDL_BUTTON_X1MASK SDL_BUTTON_MASK(SDL_BUTTON_X1) #define SDL_BUTTON_X2MASK SDL_BUTTON_MASK(SDL_BUTTON_X2) +/** + * A callback used to transform mouse motion delta from raw values. + * + * This is called during SDL's handling of platform mouse events to scale the + * values of the resulting motion delta. + * + * \param userdata what was passed as `userdata` to + * SDL_SetRelativeMouseTransform(). + * \param timestamp the associated time at which this mouse motion event was + * received. + * \param window the associated window to which this mouse motion event was + * addressed. + * \param mouseID the associated mouse from which this mouse motion event was + * emitted. + * \param x pointer to a variable that will be treated as the resulting x-axis + * motion. + * \param y pointer to a variable that will be treated as the resulting y-axis + * motion. + * + * \threadsafety This callback is called by SDL's internal mouse input + * processing procedure, which may be a thread separate from the + * main event loop that is run at realtime priority. Stalling + * this thread with too much work in the callback can therefore + * potentially freeze the entire system. Care should be taken + * with proper synchronization practices when adding other side + * effects beyond mutation of the x and y values. + * + * \since This datatype is available since SDL 3.4.0. + * + * \sa SDL_SetRelativeMouseTransform + */ +typedef void (SDLCALL *SDL_MouseMotionTransformCallback)( + void *userdata, + Uint64 timestamp, + SDL_Window *window, + SDL_MouseID mouseID, + float *x, float *y +); /* Function prototypes */ @@ -380,6 +418,24 @@ extern SDL_DECLSPEC void SDLCALL SDL_WarpMouseInWindow(SDL_Window *window, */ extern SDL_DECLSPEC bool SDLCALL SDL_WarpMouseGlobal(float x, float y); +/** + * Set a user-defined function by which to transform relative mouse inputs. + * + * This overrides the relative system scale and relative speed scale hints. + * Should be called prior to enabling relative mouse mode, fails otherwise. + * + * \param callback a callback used to transform relative mouse motion, or NULL + * for default behavior. + * \param userdata a pointer that will be passed to `callback`. + * \returns true on success or false on failure; call SDL_GetError() for more + * information. + * + * \threadsafety This function should only be called on the main thread. + * + * \since This function is available since SDL 3.4.0. + */ +extern SDL_DECLSPEC bool SDLCALL SDL_SetRelativeMouseTransform(SDL_MouseMotionTransformCallback callback, void *userdata); + /** * Set relative mouse mode for a window. * @@ -522,15 +578,16 @@ extern SDL_DECLSPEC SDL_Cursor * SDLCALL SDL_CreateCursor(const Uint8 *data, /** * Create a color cursor. * - * If this function is passed a surface with alternate representations, the - * surface will be interpreted as the content to be used for 100% display - * scale, and the alternate representations will be used for high DPI - * situations. For example, if the original surface is 32x32, then on a 2x - * macOS display or 200% display scale on Windows, a 64x64 version of the - * image will be used, if available. If a matching version of the image isn't - * available, the closest larger size image will be downscaled to the - * appropriate size and be used instead, if available. Otherwise, the closest - * smaller image will be upscaled and be used instead. + * If this function is passed a surface with alternate representations added + * with SDL_AddSurfaceAlternateImage(), the surface will be interpreted as the + * content to be used for 100% display scale, and the alternate + * representations will be used for high DPI situations. For example, if the + * original surface is 32x32, then on a 2x macOS display or 200% display scale + * on Windows, a 64x64 version of the image will be used, if available. If a + * matching version of the image isn't available, the closest larger size + * image will be downscaled to the appropriate size and be used instead, if + * available. Otherwise, the closest smaller image will be upscaled and be + * used instead. * * \param surface an SDL_Surface structure representing the cursor image. * \param hot_x the x position of the cursor hot spot. @@ -542,6 +599,7 @@ extern SDL_DECLSPEC SDL_Cursor * SDLCALL SDL_CreateCursor(const Uint8 *data, * * \since This function is available since SDL 3.2.0. * + * \sa SDL_AddSurfaceAlternateImage * \sa SDL_CreateCursor * \sa SDL_CreateSystemCursor * \sa SDL_DestroyCursor diff --git a/libs/SDL3/include/SDL3/SDL_mutex.h b/libs/SDL3/include/SDL3/SDL_mutex.h index c88ec153..f1b66404 100644 --- a/libs/SDL3/include/SDL3/SDL_mutex.h +++ b/libs/SDL3/include/SDL3/SDL_mutex.h @@ -942,7 +942,7 @@ typedef enum SDL_InitStatus * Here is an example of using this: * * ```c - * static SDL_AtomicInitState init; + * static SDL_InitState init; * * bool InitSystem(void) * { diff --git a/libs/SDL3/include/SDL3/SDL_pixels.h b/libs/SDL3/include/SDL3/SDL_pixels.h index 4127ac06..f9d6e9e3 100644 --- a/libs/SDL3/include/SDL3/SDL_pixels.h +++ b/libs/SDL3/include/SDL3/SDL_pixels.h @@ -517,7 +517,7 @@ typedef enum SDL_PackedLayout * ABGR32, define a platform-independent encoding into bytes in the order * specified. For example, in RGB24 data, each pixel is encoded in 3 bytes * (red, green, blue) in that order, and in ABGR32 data, each pixel is - * encoded in 4 bytes alpha, blue, green, red) in that order. Use these + * encoded in 4 bytes (alpha, blue, green, red) in that order. Use these * names if the property of a format that is important to you is the order * of the bytes in memory or on disk. * - Names with a bit count per component, such as ARGB8888 and XRGB1555, are @@ -1379,7 +1379,7 @@ extern SDL_DECLSPEC Uint32 SDLCALL SDL_MapRGBA(const SDL_PixelFormatDetails *for * (e.g., a completely white pixel in 16-bit RGB565 format would return [0xff, * 0xff, 0xff] not [0xf8, 0xfc, 0xf8]). * - * \param pixel a pixel value. + * \param pixelvalue a pixel value. * \param format a pointer to SDL_PixelFormatDetails describing the pixel * format. * \param palette an optional palette for indexed formats, may be NULL. @@ -1397,7 +1397,7 @@ extern SDL_DECLSPEC Uint32 SDLCALL SDL_MapRGBA(const SDL_PixelFormatDetails *for * \sa SDL_MapRGB * \sa SDL_MapRGBA */ -extern SDL_DECLSPEC void SDLCALL SDL_GetRGB(Uint32 pixel, const SDL_PixelFormatDetails *format, const SDL_Palette *palette, Uint8 *r, Uint8 *g, Uint8 *b); +extern SDL_DECLSPEC void SDLCALL SDL_GetRGB(Uint32 pixelvalue, const SDL_PixelFormatDetails *format, const SDL_Palette *palette, Uint8 *r, Uint8 *g, Uint8 *b); /** * Get RGBA values from a pixel in the specified format. @@ -1410,7 +1410,7 @@ extern SDL_DECLSPEC void SDLCALL SDL_GetRGB(Uint32 pixel, const SDL_PixelFormatD * If the surface has no alpha component, the alpha will be returned as 0xff * (100% opaque). * - * \param pixel a pixel value. + * \param pixelvalue a pixel value. * \param format a pointer to SDL_PixelFormatDetails describing the pixel * format. * \param palette an optional palette for indexed formats, may be NULL. @@ -1429,7 +1429,7 @@ extern SDL_DECLSPEC void SDLCALL SDL_GetRGB(Uint32 pixel, const SDL_PixelFormatD * \sa SDL_MapRGB * \sa SDL_MapRGBA */ -extern SDL_DECLSPEC void SDLCALL SDL_GetRGBA(Uint32 pixel, const SDL_PixelFormatDetails *format, const SDL_Palette *palette, Uint8 *r, Uint8 *g, Uint8 *b, Uint8 *a); +extern SDL_DECLSPEC void SDLCALL SDL_GetRGBA(Uint32 pixelvalue, const SDL_PixelFormatDetails *format, const SDL_Palette *palette, Uint8 *r, Uint8 *g, Uint8 *b, Uint8 *a); /* Ends C function definitions when using C++ */ diff --git a/libs/SDL3/include/SDL3/SDL_platform_defines.h b/libs/SDL3/include/SDL3/SDL_platform_defines.h index 6b240a8b..f7f14be0 100644 --- a/libs/SDL3/include/SDL3/SDL_platform_defines.h +++ b/libs/SDL3/include/SDL3/SDL_platform_defines.h @@ -317,7 +317,7 @@ #define SDL_PLATFORM_CYGWIN 1 #endif -#if defined(_WIN32) || defined(SDL_PLATFORM_CYGWIN) +#if (defined(_WIN32) || defined(SDL_PLATFORM_CYGWIN)) && !defined(__NGAGE__) /** * A preprocessor macro that is only defined if compiling for Windows. @@ -473,4 +473,15 @@ #define SDL_PLATFORM_3DS 1 #endif +#ifdef __NGAGE__ + +/** + * A preprocessor macro that is only defined if compiling for the Nokia + * N-Gage. + * + * \since This macro is available since SDL 3.4.0. + */ +#define SDL_PLATFORM_NGAGE 1 +#endif + #endif /* SDL_platform_defines_h_ */ diff --git a/libs/SDL3/include/SDL3/SDL_process.h b/libs/SDL3/include/SDL3/SDL_process.h index 511b2f9c..57e3afd9 100644 --- a/libs/SDL3/include/SDL3/SDL_process.h +++ b/libs/SDL3/include/SDL3/SDL_process.h @@ -166,6 +166,9 @@ typedef enum SDL_ProcessIO * - `SDL_PROP_PROCESS_CREATE_ENVIRONMENT_POINTER`: an SDL_Environment * pointer. If this property is set, it will be the entire environment for * the process, otherwise the current environment is used. + * - `SDL_PROP_PROCESS_CREATE_WORKING_DIRECTORY_STRING`: a UTF-8 encoded + * string representing the working directory for the process, defaults to + * the current working directory. * - `SDL_PROP_PROCESS_CREATE_STDIN_NUMBER`: an SDL_ProcessIO value describing * where standard input for the process comes from, defaults to * `SDL_PROCESS_STDIO_NULL`. @@ -192,6 +195,12 @@ typedef enum SDL_ProcessIO * run in the background. In this case the default input and output is * `SDL_PROCESS_STDIO_NULL` and the exitcode of the process is not * available, and will always be 0. + * - `SDL_PROP_PROCESS_CREATE_CMDLINE_STRING`: a string containing the program + * to run and any parameters. This string is passed directly to + * `CreateProcess` on Windows, and does nothing on other platforms. This + * property is only important if you want to start programs that does + * non-standard command-line processing, and in most cases using + * `SDL_PROP_PROCESS_CREATE_ARGS_POINTER` is sufficient. * * On POSIX platforms, wait() and waitpid(-1, ...) should not be called, and * SIGCHLD should not be ignored or handled because those would prevent SDL @@ -219,6 +228,7 @@ extern SDL_DECLSPEC SDL_Process * SDLCALL SDL_CreateProcessWithProperties(SDL_Pr #define SDL_PROP_PROCESS_CREATE_ARGS_POINTER "SDL.process.create.args" #define SDL_PROP_PROCESS_CREATE_ENVIRONMENT_POINTER "SDL.process.create.environment" +#define SDL_PROP_PROCESS_CREATE_WORKING_DIRECTORY_STRING "SDL.process.create.working_directory" #define SDL_PROP_PROCESS_CREATE_STDIN_NUMBER "SDL.process.create.stdin_option" #define SDL_PROP_PROCESS_CREATE_STDIN_POINTER "SDL.process.create.stdin_source" #define SDL_PROP_PROCESS_CREATE_STDOUT_NUMBER "SDL.process.create.stdout_option" @@ -227,6 +237,7 @@ extern SDL_DECLSPEC SDL_Process * SDLCALL SDL_CreateProcessWithProperties(SDL_Pr #define SDL_PROP_PROCESS_CREATE_STDERR_POINTER "SDL.process.create.stderr_source" #define SDL_PROP_PROCESS_CREATE_STDERR_TO_STDOUT_BOOLEAN "SDL.process.create.stderr_to_stdout" #define SDL_PROP_PROCESS_CREATE_BACKGROUND_BOOLEAN "SDL.process.create.background" +#define SDL_PROP_PROCESS_CREATE_CMDLINE_STRING "SDL.process.create.cmdline" /** * Get the properties associated with a process. diff --git a/libs/SDL3/include/SDL3/SDL_rect.h b/libs/SDL3/include/SDL3/SDL_rect.h index eb2d34a6..e09f92a4 100644 --- a/libs/SDL3/include/SDL3/SDL_rect.h +++ b/libs/SDL3/include/SDL3/SDL_rect.h @@ -324,7 +324,7 @@ SDL_FORCE_INLINE bool SDL_PointInRectFloat(const SDL_FPoint *p, const SDL_FRect } /** - * Determine whether a floating point rectangle can contain any point. + * Determine whether a floating point rectangle takes no space. * * A rectangle is considered "empty" for this function if `r` is NULL, or if * `r`'s width and/or height are < 0.0f. diff --git a/libs/SDL3/include/SDL3/SDL_render.h b/libs/SDL3/include/SDL3/SDL_render.h index 3352545d..e2e42750 100644 --- a/libs/SDL3/include/SDL3/SDL_render.h +++ b/libs/SDL3/include/SDL3/SDL_render.h @@ -59,6 +59,7 @@ #include #include #include +#include #include /* Set up for C function definitions, even when using C++ */ @@ -97,6 +98,21 @@ typedef enum SDL_TextureAccess SDL_TEXTUREACCESS_TARGET /**< Texture can be used as a render target */ } SDL_TextureAccess; +/** + * The addressing mode for a texture when used in SDL_RenderGeometry(). + * + * This affects how texture coordinates are interpreted outside of [0, 1] + * + * \since This enum is available since SDL 3.4.0. + */ +typedef enum SDL_TextureAddressMode +{ + SDL_TEXTURE_ADDRESS_INVALID = -1, + SDL_TEXTURE_ADDRESS_AUTO, /**< Wrapping is enabled if texture coordinates are outside [0, 1], this is the default */ + SDL_TEXTURE_ADDRESS_CLAMP, /**< Texture coordinates are clamped to the [0, 1] range */ + SDL_TEXTURE_ADDRESS_WRAP, /**< The texture is repeated (tiled) */ +} SDL_TextureAddressMode; + /** * How the logical size is mapped to the output. * @@ -267,6 +283,15 @@ extern SDL_DECLSPEC SDL_Renderer * SDLCALL SDL_CreateRenderer(SDL_Window *window * present synchronized with the refresh rate. This property can take any * value that is supported by SDL_SetRenderVSync() for the renderer. * + * With the SDL GPU renderer: + * + * - `SDL_PROP_RENDERER_CREATE_GPU_SHADERS_SPIRV_BOOLEAN`: the app is able to + * provide SPIR-V shaders to SDL_GPURenderState, optional. + * - `SDL_PROP_RENDERER_CREATE_GPU_SHADERS_DXIL_BOOLEAN`: the app is able to + * provide DXIL shaders to SDL_GPURenderState, optional. + * - `SDL_PROP_RENDERER_CREATE_GPU_SHADERS_MSL_BOOLEAN`: the app is able to + * provide MSL shaders to SDL_GPURenderState, optional. + * * With the vulkan renderer: * * - `SDL_PROP_RENDERER_CREATE_VULKAN_INSTANCE_POINTER`: the VkInstance to use @@ -303,6 +328,9 @@ extern SDL_DECLSPEC SDL_Renderer * SDLCALL SDL_CreateRendererWithProperties(SDL_ #define SDL_PROP_RENDERER_CREATE_SURFACE_POINTER "SDL.renderer.create.surface" #define SDL_PROP_RENDERER_CREATE_OUTPUT_COLORSPACE_NUMBER "SDL.renderer.create.output_colorspace" #define SDL_PROP_RENDERER_CREATE_PRESENT_VSYNC_NUMBER "SDL.renderer.create.present_vsync" +#define SDL_PROP_RENDERER_CREATE_GPU_SHADERS_SPIRV_BOOLEAN "SDL.renderer.create.gpu.shaders_spirv" +#define SDL_PROP_RENDERER_CREATE_GPU_SHADERS_DXIL_BOOLEAN "SDL.renderer.create.gpu.shaders_dxil" +#define SDL_PROP_RENDERER_CREATE_GPU_SHADERS_MSL_BOOLEAN "SDL.renderer.create.gpu.shaders_msl" #define SDL_PROP_RENDERER_CREATE_VULKAN_INSTANCE_POINTER "SDL.renderer.create.vulkan.instance" #define SDL_PROP_RENDERER_CREATE_VULKAN_SURFACE_NUMBER "SDL.renderer.create.vulkan.surface" #define SDL_PROP_RENDERER_CREATE_VULKAN_PHYSICAL_DEVICE_POINTER "SDL.renderer.create.vulkan.physical_device" @@ -310,6 +338,37 @@ extern SDL_DECLSPEC SDL_Renderer * SDLCALL SDL_CreateRendererWithProperties(SDL_ #define SDL_PROP_RENDERER_CREATE_VULKAN_GRAPHICS_QUEUE_FAMILY_INDEX_NUMBER "SDL.renderer.create.vulkan.graphics_queue_family_index" #define SDL_PROP_RENDERER_CREATE_VULKAN_PRESENT_QUEUE_FAMILY_INDEX_NUMBER "SDL.renderer.create.vulkan.present_queue_family_index" +/** + * Create a 2D GPU rendering context for a window, with support for the + * specified shader format. + * + * This is a convenience function to create a SDL GPU backed renderer, + * intended to be used with SDL_GPURenderState. The resulting renderer will + * support shaders in one of the specified shader formats. + * + * If no available GPU driver supports any of the specified shader formats, + * this function will fail. + * + * \param window the window where rendering is displayed. + * \param format_flags a bitflag indicating which shader formats the app is + * able to provide. + * \param device a pointer filled with the associated GPU device, or NULL on + * error. + * \returns a valid rendering context or NULL if there was an error; call + * SDL_GetError() for more information. + * + * \threadsafety This function should only be called on the main thread. + * + * \since This function is available since SDL 3.4.0. + * + * \sa SDL_CreateRendererWithProperties + * \sa SDL_GetGPUShaderFormats + * \sa SDL_CreateGPUShader + * \sa SDL_CreateGPURenderState + * \sa SDL_SetRenderGPUState + */ +extern SDL_DECLSPEC SDL_Renderer * SDLCALL SDL_CreateGPURenderer(SDL_Window *window, SDL_GPUShaderFormat format_flags, SDL_GPUDevice **device); + /** * Create a 2D software rendering context for a surface. * @@ -1607,8 +1666,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_GetRenderViewport(SDL_Renderer *renderer, S * Return whether an explicit rectangle was set as the viewport. * * This is useful if you're saving and restoring the viewport and want to know - * whether you should restore a specific rectangle or NULL. Note that the - * viewport is always reset when changing rendering targets. + * whether you should restore a specific rectangle or NULL. * * Each render target has its own viewport. This function checks the viewport * for the current render target. @@ -2235,6 +2293,43 @@ extern SDL_DECLSPEC bool SDLCALL SDL_RenderTextureTiled(SDL_Renderer *renderer, */ extern SDL_DECLSPEC bool SDLCALL SDL_RenderTexture9Grid(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_FRect *srcrect, float left_width, float right_width, float top_height, float bottom_height, float scale, const SDL_FRect *dstrect); +/** + * Perform a scaled copy using the 9-grid algorithm to the current rendering + * target at subpixel precision. + * + * The pixels in the texture are split into a 3x3 grid, using the different + * corner sizes for each corner, and the sides and center making up the + * remaining pixels. The corners are then scaled using `scale` and fit into + * the corners of the destination rectangle. The sides and center are then + * tiled into place to cover the remaining destination rectangle. + * + * \param renderer the renderer which should copy parts of a texture. + * \param texture the source texture. + * \param srcrect the SDL_Rect structure representing the rectangle to be used + * for the 9-grid, or NULL to use the entire texture. + * \param left_width the width, in pixels, of the left corners in `srcrect`. + * \param right_width the width, in pixels, of the right corners in `srcrect`. + * \param top_height the height, in pixels, of the top corners in `srcrect`. + * \param bottom_height the height, in pixels, of the bottom corners in + * `srcrect`. + * \param scale the scale used to transform the corner of `srcrect` into the + * corner of `dstrect`, or 0.0f for an unscaled copy. + * \param dstrect a pointer to the destination rectangle, or NULL for the + * entire rendering target. + * \param tileScale the scale used to transform the borders and center of + * `srcrect` into the borders and middle of `dstrect`, or + * 1.0f for an unscaled copy. + * \returns true on success or false on failure; call SDL_GetError() for more + * information. + * + * \threadsafety This function should only be called on the main thread. + * + * \since This function is available since SDL 3.4.0. + * + * \sa SDL_RenderTexture + */ +extern SDL_DECLSPEC bool SDLCALL SDL_RenderTexture9GridTiled(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_FRect *srcrect, float left_width, float right_width, float top_height, float bottom_height, float scale, const SDL_FRect *dstrect, float tileScale); + /** * Render a list of triangles, optionally using a texture and indices into the * vertex array Color and alpha modulation is done per vertex @@ -2256,6 +2351,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_RenderTexture9Grid(SDL_Renderer *renderer, * \since This function is available since SDL 3.2.0. * * \sa SDL_RenderGeometryRaw + * \sa SDL_SetRenderTextureAddressMode */ extern SDL_DECLSPEC bool SDLCALL SDL_RenderGeometry(SDL_Renderer *renderer, SDL_Texture *texture, @@ -2288,6 +2384,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_RenderGeometry(SDL_Renderer *renderer, * \since This function is available since SDL 3.2.0. * * \sa SDL_RenderGeometry + * \sa SDL_SetRenderTextureAddressMode */ extern SDL_DECLSPEC bool SDLCALL SDL_RenderGeometryRaw(SDL_Renderer *renderer, SDL_Texture *texture, @@ -2297,6 +2394,44 @@ extern SDL_DECLSPEC bool SDLCALL SDL_RenderGeometryRaw(SDL_Renderer *renderer, int num_vertices, const void *indices, int num_indices, int size_indices); +/** + * Set the texture addressing mode used in SDL_RenderGeometry(). + * + * \param renderer the rendering context. + * \param u_mode the SDL_TextureAddressMode to use for horizontal texture + * coordinates in SDL_RenderGeometry(). + * \param v_mode the SDL_TextureAddressMode to use for vertical texture + * coordinates in SDL_RenderGeometry(). + * \returns true on success or false on failure; call SDL_GetError() for more + * information. + * + * \since This function is available since SDL 3.4.0. + * + * \sa SDL_RenderGeometry + * \sa SDL_RenderGeometryRaw + * \sa SDL_GetRenderTextureAddressMode + */ +extern SDL_DECLSPEC bool SDLCALL SDL_SetRenderTextureAddressMode(SDL_Renderer *renderer, SDL_TextureAddressMode u_mode, SDL_TextureAddressMode v_mode); + +/** + * Get the texture addressing mode used in SDL_RenderGeometry(). + * + * \param renderer the rendering context. + * \param u_mode a pointer filled in with the SDL_TextureAddressMode to use + * for horizontal texture coordinates in SDL_RenderGeometry(), + * may be NULL. + * \param v_mode a pointer filled in with the SDL_TextureAddressMode to use + * for vertical texture coordinates in SDL_RenderGeometry(), may + * be NULL. + * \returns true on success or false on failure; call SDL_GetError() for more + * information. + * + * \since This function is available since SDL 3.4.0. + * + * \sa SDL_SetRenderTextureAddressMode + */ +extern SDL_DECLSPEC bool SDLCALL SDL_GetRenderTextureAddressMode(SDL_Renderer *renderer, SDL_TextureAddressMode *u_mode, SDL_TextureAddressMode *v_mode); + /** * Read pixels from the current rendering target. * @@ -2348,8 +2483,7 @@ extern SDL_DECLSPEC SDL_Surface * SDLCALL SDL_RenderReadPixels(SDL_Renderer *ren * should not be done; you are only required to change back the rendering * target to default via `SDL_SetRenderTarget(renderer, NULL)` afterwards, as * textures by themselves do not have a concept of backbuffers. Calling - * SDL_RenderPresent while rendering to a texture will still update the screen - * with any current drawing that has been done _to the window itself_. + * SDL_RenderPresent while rendering to a texture will fail. * * \param renderer the rendering context. * \returns true on success or false on failure; call SDL_GetError() for more @@ -2637,6 +2771,161 @@ extern SDL_DECLSPEC bool SDLCALL SDL_RenderDebugText(SDL_Renderer *renderer, flo */ extern SDL_DECLSPEC bool SDLCALL SDL_RenderDebugTextFormat(SDL_Renderer *renderer, float x, float y, SDL_PRINTF_FORMAT_STRING const char *fmt, ...) SDL_PRINTF_VARARG_FUNC(4); +/** + * Set default scale mode for new textures for given renderer. + * + * When a renderer is created, scale_mode defaults to SDL_SCALEMODE_LINEAR. + * + * \param renderer the renderer to update. + * \param scale_mode the scale mode to change to for new textures. + * \returns true on success or false on failure; call SDL_GetError() for more + * information. + * + * \threadsafety This function should only be called on the main thread. + * + * \since This function is available since SDL 3.4.0. + * + * \sa SDL_GetDefaultTextureScaleMode + */ +extern SDL_DECLSPEC bool SDLCALL SDL_SetDefaultTextureScaleMode(SDL_Renderer *renderer, SDL_ScaleMode scale_mode); + +/** + * Get default texture scale mode of the given renderer. + * + * \param renderer the renderer to get data from. + * \param scale_mode a SDL_ScaleMode filled with current default scale mode. + * See SDL_SetDefaultTextureScaleMode() for the meaning of + * the value. + * \returns true on success or false on failure; call SDL_GetError() for more + * information. + * + * \threadsafety This function should only be called on the main thread. + * + * \since This function is available since SDL 3.4.0. + * + * \sa SDL_SetDefaultTextureScaleMode + */ +extern SDL_DECLSPEC bool SDLCALL SDL_GetDefaultTextureScaleMode(SDL_Renderer *renderer, SDL_ScaleMode *scale_mode); + +/** + * GPU render state description. + * + * This structure should be initialized using SDL_INIT_INTERFACE(). + * + * \since This struct is available since SDL 3.4.0. + * + * \sa SDL_CreateGPURenderState + */ +typedef struct SDL_GPURenderStateDesc +{ + Uint32 version; /**< the version of this interface */ + + SDL_GPUShader *fragment_shader; /**< The fragment shader to use when this render state is active */ + + Sint32 num_sampler_bindings; /**< The number of additional fragment samplers to bind when this render state is active */ + const SDL_GPUTextureSamplerBinding *sampler_bindings; /**< Additional fragment samplers to bind when this render state is active */ + + Sint32 num_storage_textures; /**< The number of storage textures to bind when this render state is active */ + SDL_GPUTexture *const *storage_textures; /**< Storage textures to bind when this render state is active */ + + Sint32 num_storage_buffers; /**< The number of storage buffers to bind when this render state is active */ + SDL_GPUBuffer *const *storage_buffers; /**< Storage buffers to bind when this render state is active */ +} SDL_GPURenderStateDesc; + +/* Check the size of SDL_GPURenderStateDesc + * + * If this assert fails, either the compiler is padding to an unexpected size, + * or the interface has been updated and this should be updated to match and + * the code using this interface should be updated to handle the old version. + */ +SDL_COMPILE_TIME_ASSERT(SDL_GPURenderStateDesc_SIZE, + (sizeof(void *) == 4 && sizeof(SDL_GPURenderStateDesc) == 32) || + (sizeof(void *) == 8 && sizeof(SDL_GPURenderStateDesc) == 64)); + +/** + * A custom GPU render state. + * + * \since This struct is available since SDL 3.4.0. + * + * \sa SDL_CreateGPURenderState + * \sa SDL_SetGPURenderStateFragmentUniforms + * \sa SDL_SetRenderGPUState + * \sa SDL_DestroyGPURenderState + */ +typedef struct SDL_GPURenderState SDL_GPURenderState; + +/** + * Create custom GPU render state. + * + * \param renderer the renderer to use. + * \param desc GPU render state description, initialized using + * SDL_INIT_INTERFACE(). + * \returns a custom GPU render state or NULL on failure; call SDL_GetError() + * for more information. + * + * \threadsafety This function should be called on the thread that created the + * renderer. + * + * \since This function is available since SDL 3.4.0. + * + * \sa SDL_SetGPURenderStateFragmentUniforms + * \sa SDL_SetRenderGPUState + * \sa SDL_DestroyGPURenderState + */ +extern SDL_DECLSPEC SDL_GPURenderState * SDLCALL SDL_CreateGPURenderState(SDL_Renderer *renderer, SDL_GPURenderStateDesc *desc); + +/** + * Set fragment shader uniform variables in a custom GPU render state. + * + * The data is copied and will be pushed using + * SDL_PushGPUFragmentUniformData() during draw call execution. + * + * \param state the state to modify. + * \param slot_index the fragment uniform slot to push data to. + * \param data client data to write. + * \param length the length of the data to write. + * \returns true on success or false on failure; call SDL_GetError() for more + * information. + * + * \threadsafety This function should be called on the thread that created the + * renderer. + * + * \since This function is available since SDL 3.4.0. + */ +extern SDL_DECLSPEC bool SDLCALL SDL_SetGPURenderStateFragmentUniforms(SDL_GPURenderState *state, Uint32 slot_index, const void *data, Uint32 length); + +/** + * Set custom GPU render state. + * + * This function sets custom GPU render state for subsequent draw calls. This + * allows using custom shaders with the GPU renderer. + * + * \param renderer the renderer to use. + * \param state the state to to use, or NULL to clear custom GPU render state. + * \returns true on success or false on failure; call SDL_GetError() for more + * information. + * + * \threadsafety This function should be called on the thread that created the + * renderer. + * + * \since This function is available since SDL 3.4.0. + */ +extern SDL_DECLSPEC bool SDLCALL SDL_SetRenderGPUState(SDL_Renderer *renderer, SDL_GPURenderState *state); + +/** + * Destroy custom GPU render state. + * + * \param state the state to destroy. + * + * \threadsafety This function should be called on the thread that created the + * renderer. + * + * \since This function is available since SDL 3.4.0. + * + * \sa SDL_CreateGPURenderState + */ +extern SDL_DECLSPEC void SDLCALL SDL_DestroyGPURenderState(SDL_GPURenderState *state); + /* Ends C function definitions when using C++ */ #ifdef __cplusplus } diff --git a/libs/SDL3/include/SDL3/SDL_stdinc.h b/libs/SDL3/include/SDL3/SDL_stdinc.h index b2728da2..f198de75 100644 --- a/libs/SDL3/include/SDL3/SDL_stdinc.h +++ b/libs/SDL3/include/SDL3/SDL_stdinc.h @@ -2119,7 +2119,7 @@ extern SDL_DECLSPEC int SDLCALL SDL_abs(int x); * * \param x the first value to compare. * \param y the second value to compare. - * \returns the lesser of `x` and `y`. + * \returns the greater of `x` and `y`. * * \threadsafety It is safe to call this macro from any thread. * @@ -4656,7 +4656,7 @@ extern SDL_DECLSPEC float SDLCALL SDL_atanf(float x); * * Domain: `-INF <= x <= INF`, `-INF <= y <= INF` * - * Range: `-Pi/2 <= y <= Pi/2` + * Range: `-Pi <= y <= Pi` * * This function operates on double-precision floating point values, use * SDL_atan2f for single-precision floats. @@ -4692,7 +4692,7 @@ extern SDL_DECLSPEC double SDLCALL SDL_atan2(double y, double x); * * Domain: `-INF <= x <= INF`, `-INF <= y <= INF` * - * Range: `-Pi/2 <= y <= Pi/2` + * Range: `-Pi <= y <= Pi` * * This function operates on single-precision floating point values, use * SDL_atan2 for double-precision floats. @@ -5974,8 +5974,12 @@ size_t wcslcpy(wchar_t *dst, const wchar_t *src, size_t size); size_t wcslcat(wchar_t *dst, const wchar_t *src, size_t size); #endif +#ifndef _WIN32 /* strdup is not ANSI but POSIX, and its prototype might be hidden... */ +/* not for windows: might conflict with string.h where strdup may have + * dllimport attribute: https://github.com/libsdl-org/SDL/issues/12948 */ char *strdup(const char *str); +#endif /* Starting LLVM 16, the analyser errors out if these functions do not have their prototype defined (clang-diagnostic-implicit-function-declaration) */ diff --git a/libs/SDL3/include/SDL3/SDL_storage.h b/libs/SDL3/include/SDL3/SDL_storage.h index 6837ebaa..1b20b34a 100644 --- a/libs/SDL3/include/SDL3/SDL_storage.h +++ b/libs/SDL3/include/SDL3/SDL_storage.h @@ -334,6 +334,10 @@ typedef struct SDL_Storage SDL_Storage; /** * Opens up a read-only container for the application's filesystem. * + * By default, SDL_OpenTitleStorage uses the generic storage implementation. + * When the path override is not provided, the generic implementation will use + * the output of SDL_GetBasePath as the base path. + * * \param override a path to override the backend's default title root. * \param props a property list that may contain backend-specific information. * \returns a title storage container on success or NULL on failure; call diff --git a/libs/SDL3/include/SDL3/SDL_surface.h b/libs/SDL3/include/SDL3/SDL_surface.h index 7bff7cfb..69f4d69c 100644 --- a/libs/SDL3/include/SDL3/SDL_surface.h +++ b/libs/SDL3/include/SDL3/SDL_surface.h @@ -32,7 +32,8 @@ * There is also a simple .bmp loader, SDL_LoadBMP(). SDL itself does not * provide loaders for various other file formats, but there are several * excellent external libraries that do, including its own satellite library, - * SDL_image: + * [SDL_image](https://wiki.libsdl.org/SDL3_image) + * : * * https://github.com/libsdl-org/SDL_image */ @@ -83,8 +84,9 @@ typedef Uint32 SDL_SurfaceFlags; typedef enum SDL_ScaleMode { SDL_SCALEMODE_INVALID = -1, - SDL_SCALEMODE_NEAREST, /**< nearest pixel sampling */ - SDL_SCALEMODE_LINEAR /**< linear filtering */ + SDL_SCALEMODE_NEAREST, /**< nearest pixel sampling */ + SDL_SCALEMODE_LINEAR, /**< linear filtering */ + SDL_SCALEMODE_PIXELART /**< nearest pixel sampling with improved scaling for pixel art */ } SDL_ScaleMode; /** @@ -1135,9 +1137,6 @@ extern SDL_DECLSPEC bool SDLCALL SDL_FillSurfaceRects(SDL_Surface *dst, const SD * If either `srcrect` or `dstrect` are NULL, the entire surface (`src` or * `dst`) is copied while ensuring clipping to `dst->clip_rect`. * - * The final blit rectangles are saved in `srcrect` and `dstrect` after all - * clipping is performed. - * * The blit function should not be called on a locked surface. * * The blit semantics for surfaces with and without blending and colorkey are @@ -1282,10 +1281,11 @@ extern SDL_DECLSPEC bool SDLCALL SDL_BlitSurfaceUncheckedScaled(SDL_Surface *src * * \param src the SDL_Surface structure to be copied from. * \param srcrect the SDL_Rect structure representing the rectangle to be - * copied, may not be NULL. + * copied, or NULL to copy the entire surface. * \param dst the SDL_Surface structure that is the blit target. * \param dstrect the SDL_Rect structure representing the target rectangle in - * the destination surface, may not be NULL. + * the destination surface, or NULL to fill the entire + * destination surface. * \param scaleMode the SDL_ScaleMode to be used. * \returns true on success or false on failure; call SDL_GetError() for more * information. diff --git a/libs/SDL3/include/SDL3/SDL_system.h b/libs/SDL3/include/SDL3/SDL_system.h index 294089ff..625db182 100644 --- a/libs/SDL3/include/SDL3/SDL_system.h +++ b/libs/SDL3/include/SDL3/SDL_system.h @@ -247,14 +247,14 @@ typedef void (SDLCALL *SDL_iOSAnimationCallback)(void *userdata); * * For more information see: * - * https://wiki.libsdl.org/SDL3/README/ios + * https://wiki.libsdl.org/SDL3/README-ios * * Note that if you use the "main callbacks" instead of a standard C `main` * function, you don't have to use this API, as SDL will manage this for you. * * Details on main callbacks are here: * - * https://wiki.libsdl.org/SDL3/README/main-functions + * https://wiki.libsdl.org/SDL3/README-main-functions * * \param window the window for which the animation callback should be set. * \param interval the number of frames after which **callback** will be diff --git a/libs/SDL3/include/SDL3/SDL_timer.h b/libs/SDL3/include/SDL3/SDL_timer.h index cf94881d..9088a792 100644 --- a/libs/SDL3/include/SDL3/SDL_timer.h +++ b/libs/SDL3/include/SDL3/SDL_timer.h @@ -185,10 +185,12 @@ extern "C" { #define SDL_NS_TO_US(NS) ((NS) / SDL_NS_PER_US) /** - * Get the number of milliseconds since SDL library initialization. + * Get the number of milliseconds that have elapsed since the SDL library + * initialization. * - * \returns an unsigned 64-bit value representing the number of milliseconds - * since the SDL library initialized. + * \returns an unsigned 64‑bit integer that represents the number of + * milliseconds that have elapsed since the SDL library was + * initialized (typically via a call to SDL_Init). * * \threadsafety It is safe to call this function from any thread. * diff --git a/libs/SDL3/include/SDL3/SDL_version.h b/libs/SDL3/include/SDL3/SDL_version.h index a3b6ae82..e3fb742d 100644 --- a/libs/SDL3/include/SDL3/SDL_version.h +++ b/libs/SDL3/include/SDL3/SDL_version.h @@ -53,7 +53,7 @@ extern "C" { * * \since This macro is available since SDL 3.2.0. */ -#define SDL_MINOR_VERSION 2 +#define SDL_MINOR_VERSION 3 /** * The current micro (or patchlevel) version of the SDL headers. @@ -62,7 +62,7 @@ extern "C" { * * \since This macro is available since SDL 3.2.0. */ -#define SDL_MICRO_VERSION 10 +#define SDL_MICRO_VERSION 0 /** * This macro turns the version numbers into a numeric value. @@ -148,13 +148,14 @@ extern "C" { extern SDL_DECLSPEC int SDLCALL SDL_GetVersion(void); /** - * Get the code revision of SDL that is linked against your program. + * Get the code revision of the SDL library that is linked against your + * program. * - * This value is the revision of the code you are linked with and may be + * This value is the revision of the code you are linking against and may be * different from the code you are compiling with, which is found in the * constant SDL_REVISION. * - * The revision is arbitrary string (a hash value) uniquely identifying the + * The revision is an arbitrary string (a hash value) uniquely identifying the * exact revision of the SDL library in use, and is only useful in comparing * against other revisions. It is NOT an incrementing number. * diff --git a/libs/SDL3/include/SDL3/SDL_video.h b/libs/SDL3/include/SDL3/SDL_video.h index a7afc326..d5107b43 100644 --- a/libs/SDL3/include/SDL3/SDL_video.h +++ b/libs/SDL3/include/SDL3/SDL_video.h @@ -307,6 +307,21 @@ typedef enum SDL_FlashOperation SDL_FLASH_UNTIL_FOCUSED /**< Flash the window until it gets focus */ } SDL_FlashOperation; +/** + * Window progress state + * + * \since This enum is available since SDL 3.2.8. + */ +typedef enum SDL_ProgressState +{ + SDL_PROGRESS_STATE_INVALID = -1, /**< An invalid progress state indicating an error; check SDL_GetError() */ + SDL_PROGRESS_STATE_NONE, /**< No progress bar is shown */ + SDL_PROGRESS_STATE_INDETERMINATE, /**< The progress bar is shown in a indeterminate state */ + SDL_PROGRESS_STATE_NORMAL, /**< The progress bar is shown in a normal state */ + SDL_PROGRESS_STATE_PAUSED, /**< The progress bar is shown in a paused state */ + SDL_PROGRESS_STATE_ERROR /**< The progress bar is shown in a state indicating the application had an error */ +} SDL_ProgressState; + /** * An opaque handle to an OpenGL context. * @@ -426,10 +441,10 @@ typedef SDL_EGLint *(SDLCALL *SDL_EGLIntArrayCallback)(void *userdata, SDL_EGLDi */ typedef enum SDL_GLAttr { - SDL_GL_RED_SIZE, /**< the minimum number of bits for the red channel of the color buffer; defaults to 3. */ - SDL_GL_GREEN_SIZE, /**< the minimum number of bits for the green channel of the color buffer; defaults to 3. */ - SDL_GL_BLUE_SIZE, /**< the minimum number of bits for the blue channel of the color buffer; defaults to 2. */ - SDL_GL_ALPHA_SIZE, /**< the minimum number of bits for the alpha channel of the color buffer; defaults to 0. */ + SDL_GL_RED_SIZE, /**< the minimum number of bits for the red channel of the color buffer; defaults to 8. */ + SDL_GL_GREEN_SIZE, /**< the minimum number of bits for the green channel of the color buffer; defaults to 8. */ + SDL_GL_BLUE_SIZE, /**< the minimum number of bits for the blue channel of the color buffer; defaults to 8. */ + SDL_GL_ALPHA_SIZE, /**< the minimum number of bits for the alpha channel of the color buffer; defaults to 8. */ SDL_GL_BUFFER_SIZE, /**< the minimum number of bits for frame buffer size; defaults to 0. */ SDL_GL_DOUBLEBUFFER, /**< whether the output is single or double buffered; defaults to double buffering on. */ SDL_GL_DEPTH_SIZE, /**< the minimum number of bits in the depth buffer; defaults to 16. */ @@ -617,6 +632,11 @@ extern SDL_DECLSPEC SDL_DisplayID SDLCALL SDL_GetPrimaryDisplay(void); * responsible for any coordinate transformations needed to conform to the * requested display orientation. * + * On Wayland: + * + * - `SDL_PROP_DISPLAY_WAYLAND_WL_OUTPUT_POINTER`: the wl_output associated + * with the display + * * \param displayID the instance ID of the display to query. * \returns a valid property ID on success or 0 on failure; call * SDL_GetError() for more information. @@ -629,6 +649,7 @@ extern SDL_DECLSPEC SDL_PropertiesID SDLCALL SDL_GetDisplayProperties(SDL_Displa #define SDL_PROP_DISPLAY_HDR_ENABLED_BOOLEAN "SDL.display.HDR_enabled" #define SDL_PROP_DISPLAY_KMSDRM_PANEL_ORIENTATION_NUMBER "SDL.display.KMSDRM.panel_orientation" +#define SDL_PROP_DISPLAY_WAYLAND_WL_OUTPUT_POINTER "SDL.display.wayland.wl_output" /** * Get the name of a display in UTF-8 encoding. @@ -1041,6 +1062,10 @@ extern SDL_DECLSPEC SDL_Window ** SDLCALL SDL_GetWindows(int *count); /** * Create a window with the specified dimensions and flags. * + * The window size is a request and may be different than expected based on + * the desktop layout and window manager policies. Your application should be + * prepared to handle a window of any size. + * * `flags` may be any of the following OR'd together: * * - `SDL_WINDOW_FULLSCREEN`: fullscreen window at desktop resolution @@ -1127,6 +1152,10 @@ extern SDL_DECLSPEC SDL_Window * SDLCALL SDL_CreateWindow(const char *title, int /** * Create a child popup window of the specified parent window. * + * The window size is a request and may be different than expected based on + * the desktop layout and window manager policies. Your application should be + * prepared to handle a window of any size. + * * The flags parameter **must** contain at least one of the following: * * - `SDL_WINDOW_TOOLTIP`: The popup window is a tooltip and will not pass any @@ -1159,6 +1188,16 @@ extern SDL_DECLSPEC SDL_Window * SDLCALL SDL_CreateWindow(const char *title, int * Popup windows implicitly do not have a border/decorations and do not appear * on the taskbar/dock or in lists of windows such as alt-tab menus. * + * By default, popup window positions will automatically be constrained to + * keep the entire window within display bounds. This can be overridden with + * the `SDL_PROP_WINDOW_CREATE_CONSTRAIN_POPUP_BOOLEAN` property. + * + * By default, popup menus will automatically grab keyboard focus from the + * parent when shown. This behavior can be overridden by setting the + * `SDL_WINDOW_NOT_FOCUSABLE` flag, setting the + * `SDL_PROP_WINDOW_CREATE_FOCUSABLE_BOOLEAN` property to false, or toggling + * it after creation via the `SDL_SetWindowFocusable()` function. + * * If a parent window is hidden or destroyed, any child popup windows will be * recursively hidden or destroyed as well. Child popup windows not explicitly * hidden will be restored when the parent is shown. @@ -1189,12 +1228,20 @@ extern SDL_DECLSPEC SDL_Window * SDLCALL SDL_CreatePopupWindow(SDL_Window *paren /** * Create a window with the specified properties. * + * The window size is a request and may be different than expected based on + * the desktop layout and window manager policies. Your application should be + * prepared to handle a window of any size. + * * These are the supported properties: * * - `SDL_PROP_WINDOW_CREATE_ALWAYS_ON_TOP_BOOLEAN`: true if the window should * be always on top * - `SDL_PROP_WINDOW_CREATE_BORDERLESS_BOOLEAN`: true if the window has no * window decoration + * - `SDL_PROP_WINDOW_CREATE_CONSTRAIN_POPUP_BOOLEAN`: true if the "tooltip" + * and "menu" window types should be automatically constrained to be + * entirely within display bounds (default), false if no constraints on the + * position are desired. * - `SDL_PROP_WINDOW_CREATE_EXTERNAL_GRAPHICS_CONTEXT_BOOLEAN`: true if the * window will be used with an externally managed graphics context. * - `SDL_PROP_WINDOW_CREATE_FOCUSABLE_BOOLEAN`: true if the window should @@ -1256,7 +1303,7 @@ extern SDL_DECLSPEC SDL_Window * SDLCALL SDL_CreatePopupWindow(SDL_Window *paren * - `SDL_PROP_WINDOW_CREATE_WAYLAND_SURFACE_ROLE_CUSTOM_BOOLEAN` - true if * the application wants to use the Wayland surface for a custom role and * does not want it attached to an XDG toplevel window. See - * [README/wayland](README/wayland) for more information on using custom + * [README-wayland](README-wayland) for more information on using custom * surfaces. * - `SDL_PROP_WINDOW_CREATE_WAYLAND_CREATE_EGL_WINDOW_BOOLEAN` - true if the * application wants an associated `wl_egl_window` object to be created and @@ -1264,7 +1311,7 @@ extern SDL_DECLSPEC SDL_Window * SDLCALL SDL_CreatePopupWindow(SDL_Window *paren * property or `SDL_WINDOW_OPENGL` flag set. * - `SDL_PROP_WINDOW_CREATE_WAYLAND_WL_SURFACE_POINTER` - the wl_surface * associated with the window, if you want to wrap an existing window. See - * [README/wayland](README/wayland) for more information. + * [README-wayland](README-wayland) for more information. * * These are additional supported properties on Windows: * @@ -1280,8 +1327,22 @@ extern SDL_DECLSPEC SDL_Window * SDLCALL SDL_CreatePopupWindow(SDL_Window *paren * * The window is implicitly shown if the "hidden" property is not set. * - * Windows with the "tooltip" and "menu" properties are popup windows and have - * the behaviors and guidelines outlined in SDL_CreatePopupWindow(). + * These are additional supported properties with Emscripten: + * + * - `SDL_PROP_WINDOW_CREATE_EMSCRIPTEN_CANVAS_ID_STRING`: the id given to the + * canvas element. This should start with a '#' sign + * - `SDL_PROP_WINDOW_CREATE_EMSCRIPTEN_KEYBOARD_ELEMENT_STRING`: override the + * binding element for keyboard inputs for this canvas. The variable can be + * one of: + * - "#window": the javascript window object (default) + * - "#document": the javascript document object + * - "#screen": the javascript window.screen object + * - "#canvas": the WebGL canvas element + * - "#none": Don't bind anything at all + * - any other string without a leading # sign applies to the element on the + * page with that ID. Windows with the "tooltip" and "menu" properties are + * popup windows and have the behaviors and guidelines outlined in + * SDL_CreatePopupWindow(). * * If this window is being created to be used with an SDL_Renderer, you should * not add a graphics API specific property @@ -1309,6 +1370,7 @@ extern SDL_DECLSPEC SDL_Window * SDLCALL SDL_CreateWindowWithProperties(SDL_Prop #define SDL_PROP_WINDOW_CREATE_ALWAYS_ON_TOP_BOOLEAN "SDL.window.create.always_on_top" #define SDL_PROP_WINDOW_CREATE_BORDERLESS_BOOLEAN "SDL.window.create.borderless" +#define SDL_PROP_WINDOW_CREATE_CONSTRAIN_POPUP_BOOLEAN "SDL.window.create.constrain_popup" #define SDL_PROP_WINDOW_CREATE_FOCUSABLE_BOOLEAN "SDL.window.create.focusable" #define SDL_PROP_WINDOW_CREATE_EXTERNAL_GRAPHICS_CONTEXT_BOOLEAN "SDL.window.create.external_graphics_context" #define SDL_PROP_WINDOW_CREATE_FLAGS_NUMBER "SDL.window.create.flags" @@ -1341,6 +1403,8 @@ extern SDL_DECLSPEC SDL_Window * SDLCALL SDL_CreateWindowWithProperties(SDL_Prop #define SDL_PROP_WINDOW_CREATE_WIN32_HWND_POINTER "SDL.window.create.win32.hwnd" #define SDL_PROP_WINDOW_CREATE_WIN32_PIXEL_FORMAT_HWND_POINTER "SDL.window.create.win32.pixel_format_hwnd" #define SDL_PROP_WINDOW_CREATE_X11_WINDOW_NUMBER "SDL.window.create.x11.window" +#define SDL_PROP_WINDOW_CREATE_EMSCRIPTEN_CANVAS_ID_STRING "SDL.window.create.emscripten.canvas_id" +#define SDL_PROP_WINDOW_CREATE_EMSCRIPTEN_KEYBOARD_ELEMENT_STRING "SDL.window.create.emscripten.keyboard_element" /** * Get the numeric ID of a window. @@ -1452,8 +1516,8 @@ extern SDL_DECLSPEC SDL_Window * SDLCALL SDL_GetWindowParent(SDL_Window *window) * * On OpenVR: * - * - `SDL_PROP_WINDOW_OPENVR_OVERLAY_ID`: the OpenVR Overlay Handle ID for the - * associated overlay window. + * - `SDL_PROP_WINDOW_OPENVR_OVERLAY_ID_NUMBER`: the OpenVR Overlay Handle ID + * for the associated overlay window. * * On Vivante: * @@ -1505,6 +1569,13 @@ extern SDL_DECLSPEC SDL_Window * SDLCALL SDL_GetWindowParent(SDL_Window *window) * - `SDL_PROP_WINDOW_X11_WINDOW_NUMBER`: the X11 Window associated with the * window * + * On Emscripten: + * + * - `SDL_PROP_WINDOW_EMSCRIPTEN_CANVAS_ID_STRING`: the id the canvas element + * will have + * - `SDL_PROP_WINDOW_EMSCRIPTEN_KEYBOARD_ELEMENT_STRING`: the keyboard + * element that associates keyboard events to this window + * * \param window the window to query. * \returns a valid property ID on success or 0 on failure; call * SDL_GetError() for more information. @@ -1531,7 +1602,7 @@ extern SDL_DECLSPEC SDL_PropertiesID SDLCALL SDL_GetWindowProperties(SDL_Window #define SDL_PROP_WINDOW_KMSDRM_GBM_DEVICE_POINTER "SDL.window.kmsdrm.gbm_dev" #define SDL_PROP_WINDOW_COCOA_WINDOW_POINTER "SDL.window.cocoa.window" #define SDL_PROP_WINDOW_COCOA_METAL_VIEW_TAG_NUMBER "SDL.window.cocoa.metal_view_tag" -#define SDL_PROP_WINDOW_OPENVR_OVERLAY_ID "SDL.window.openvr.overlay_id" +#define SDL_PROP_WINDOW_OPENVR_OVERLAY_ID_NUMBER "SDL.window.openvr.overlay_id" #define SDL_PROP_WINDOW_VIVANTE_DISPLAY_POINTER "SDL.window.vivante.display" #define SDL_PROP_WINDOW_VIVANTE_WINDOW_POINTER "SDL.window.vivante.window" #define SDL_PROP_WINDOW_VIVANTE_SURFACE_POINTER "SDL.window.vivante.surface" @@ -1550,6 +1621,8 @@ extern SDL_DECLSPEC SDL_PropertiesID SDLCALL SDL_GetWindowProperties(SDL_Window #define SDL_PROP_WINDOW_X11_DISPLAY_POINTER "SDL.window.x11.display" #define SDL_PROP_WINDOW_X11_SCREEN_NUMBER "SDL.window.x11.screen" #define SDL_PROP_WINDOW_X11_WINDOW_NUMBER "SDL.window.x11.window" +#define SDL_PROP_WINDOW_EMSCRIPTEN_CANVAS_ID_STRING "SDL.window.emscripten.canvas_id" +#define SDL_PROP_WINDOW_EMSCRIPTEN_KEYBOARD_ELEMENT_STRING "SDL.window.emscripten.keyboard_element" /** * Get the window flags. @@ -1607,15 +1680,16 @@ extern SDL_DECLSPEC const char * SDLCALL SDL_GetWindowTitle(SDL_Window *window); /** * Set the icon for a window. * - * If this function is passed a surface with alternate representations, the - * surface will be interpreted as the content to be used for 100% display - * scale, and the alternate representations will be used for high DPI - * situations. For example, if the original surface is 32x32, then on a 2x - * macOS display or 200% display scale on Windows, a 64x64 version of the - * image will be used, if available. If a matching version of the image isn't - * available, the closest larger size image will be downscaled to the - * appropriate size and be used instead, if available. Otherwise, the closest - * smaller image will be upscaled and be used instead. + * If this function is passed a surface with alternate representations added + * using SDL_AddSurfaceAlternateImage(), the surface will be interpreted as + * the content to be used for 100% display scale, and the alternate + * representations will be used for high DPI situations. For example, if the + * original surface is 32x32, then on a 2x macOS display or 200% display scale + * on Windows, a 64x64 version of the image will be used, if available. If a + * matching version of the image isn't available, the closest larger size + * image will be downscaled to the appropriate size and be used instead, if + * available. Otherwise, the closest smaller image will be upscaled and be + * used instead. * * \param window the window to change. * \param icon an SDL_Surface structure containing the icon for the window. @@ -1625,6 +1699,8 @@ extern SDL_DECLSPEC const char * SDLCALL SDL_GetWindowTitle(SDL_Window *window); * \threadsafety This function should only be called on the main thread. * * \since This function is available since SDL 3.2.0. + * + * \sa SDL_AddSurfaceAlternateImage */ extern SDL_DECLSPEC bool SDLCALL SDL_SetWindowIcon(SDL_Window *window, SDL_Surface *icon); @@ -1818,7 +1894,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_GetWindowSafeArea(SDL_Window *window, SDL_R extern SDL_DECLSPEC bool SDLCALL SDL_SetWindowAspectRatio(SDL_Window *window, float min_aspect, float max_aspect); /** - * Get the size of a window's client area. + * Get the aspect ratio of a window's client area. * * \param window the window to query the width and height from. * \param min_aspect a pointer filled in with the minimum aspect ratio of the @@ -2445,7 +2521,6 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SetWindowKeyboardGrab(SDL_Window *window, b * * \sa SDL_GetWindowMouseRect * \sa SDL_SetWindowMouseRect - * \sa SDL_SetWindowMouseGrab * \sa SDL_SetWindowKeyboardGrab */ extern SDL_DECLSPEC bool SDLCALL SDL_SetWindowMouseGrab(SDL_Window *window, bool grabbed); @@ -2790,6 +2865,62 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SetWindowShape(SDL_Window *window, SDL_Surf */ extern SDL_DECLSPEC bool SDLCALL SDL_FlashWindow(SDL_Window *window, SDL_FlashOperation operation); +/** + * Sets the state of the progress bar for the given window’s taskbar icon. + * + * \param window the window whose progress state is to be modified. + * \param state the progress state. `SDL_PROGRESS_STATE_NONE` stops displaying + * the progress bar. + * \returns true on success or false on failure; call SDL_GetError() for more + * information. + * + * \threadsafety This function should only be called on the main thread. + * + * \since This function is available since SDL 3.4.0. + */ +extern SDL_DECLSPEC bool SDLCALL SDL_SetWindowProgressState(SDL_Window *window, SDL_ProgressState state); + +/** + * Get the state of the progress bar for the given window’s taskbar icon. + * + * \param window the window to get the current progress state from. + * \returns the progress state, or `SDL_PROGRESS_STATE_INVALID` on failure; + * call SDL_GetError() for more information. + * + * \threadsafety This function should only be called on the main thread. + * + * \since This function is available since SDL 3.4.0. + */ +extern SDL_DECLSPEC SDL_ProgressState SDLCALL SDL_GetWindowProgressState(SDL_Window *window); + +/** + * Sets the value of the progress bar for the given window’s taskbar icon. + * + * \param window the window whose progress value is to be modified. + * \param value the progress value in the range of [0.0f - 1.0f]. If the value + * is outside the valid range, it gets clamped. + * \returns true on success or false on failure; call SDL_GetError() for more + * information. + * + * \threadsafety This function should only be called on the main thread. + * + * \since This function is available since SDL 3.4.0. + */ +extern SDL_DECLSPEC bool SDLCALL SDL_SetWindowProgressValue(SDL_Window *window, float value); + +/** + * Get the value of the progress bar for the given window’s taskbar icon. + * + * \param window the window to get the current progress value from. + * \returns the progress value in the range of [0.0f - 1.0f], or -1.0f on + * failure; call SDL_GetError() for more information. + * + * \threadsafety This function should only be called on the main thread. + * + * \since This function is available since SDL 3.4.0. + */ +extern SDL_DECLSPEC float SDLCALL SDL_GetWindowProgressValue(SDL_Window *window); + /** * Destroy a window. * diff --git a/libs/SDL3/include/SDL3/SDL_vulkan.h b/libs/SDL3/include/SDL3/SDL_vulkan.h index 710afbe6..e91e1484 100644 --- a/libs/SDL3/include/SDL3/SDL_vulkan.h +++ b/libs/SDL3/include/SDL3/SDL_vulkan.h @@ -51,14 +51,14 @@ extern "C" { #endif -/* Avoid including vulkan.h, don't define VkInstance if it's already included */ -#ifdef VULKAN_H_ +/* Avoid including vulkan_core.h, don't define VkInstance if it's already included */ +#ifdef VULKAN_CORE_H_ #define NO_SDL_VULKAN_TYPEDEFS #endif #ifndef NO_SDL_VULKAN_TYPEDEFS #define VK_DEFINE_HANDLE(object) typedef struct object##_T* object; -#if defined(__LP64__) || defined(_WIN64) || defined(__x86_64__) || defined(_M_X64) || defined(__ia64) || defined (_M_IA64) || defined(__aarch64__) || defined(__powerpc64__) +#if defined(__LP64__) || defined(_WIN64) || (defined(__x86_64__) && !defined(__ILP32__)) || defined(_M_X64) || defined(__ia64) || defined (_M_IA64) || defined(__aarch64__) || defined(__powerpc64__) || (defined(__riscv) && __riscv_xlen == 64) #define VK_DEFINE_NON_DISPATCHABLE_HANDLE(object) typedef struct object##_T *object; #else #define VK_DEFINE_NON_DISPATCHABLE_HANDLE(object) typedef uint64_t object; diff --git a/libs/SDL3/include/build_config/SDL_build_config.h.cmake b/libs/SDL3/include/build_config/SDL_build_config.h.cmake index 6ce491e7..76916aff 100644 --- a/libs/SDL3/include/build_config/SDL_build_config.h.cmake +++ b/libs/SDL3/include/build_config/SDL_build_config.h.cmake @@ -31,6 +31,12 @@ /* General platform specific identifiers */ #include +#cmakedefine SDL_PLATFORM_PRIVATE 1 + +#ifdef SDL_PLATFORM_PRIVATE +#include "SDL_begin_config_private.h" +#endif + #cmakedefine HAVE_GCC_ATOMICS 1 #cmakedefine HAVE_GCC_SYNC_LOCK_TEST_AND_SET 1 @@ -223,8 +229,11 @@ #cmakedefine HAVE_TPCSHRD_H 1 #cmakedefine HAVE_ROAPI_H 1 #cmakedefine HAVE_SHELLSCALINGAPI_H 1 +#cmakedefine HAVE_SHOBJIDL_CORE_H 1 #cmakedefine USE_POSIX_SPAWN 1 +#cmakedefine HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDCHDIR 1 +#cmakedefine HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDCHDIR_NP 1 /* SDL internal assertion support */ #cmakedefine SDL_DEFAULT_ASSERT_LEVEL_CONFIGURED 1 @@ -272,8 +281,11 @@ #cmakedefine SDL_AUDIO_DRIVER_PSP 1 #cmakedefine SDL_AUDIO_DRIVER_PS2 1 #cmakedefine SDL_AUDIO_DRIVER_N3DS 1 +#cmakedefine SDL_AUDIO_DRIVER_NGAGE 1 #cmakedefine SDL_AUDIO_DRIVER_QNX 1 +#cmakedefine SDL_AUDIO_DRIVER_PRIVATE 1 + /* Enable various input drivers */ #cmakedefine SDL_INPUT_LINUXEV 1 #cmakedefine SDL_INPUT_LINUXKD 1 @@ -299,11 +311,17 @@ #cmakedefine SDL_JOYSTICK_VITA 1 #cmakedefine SDL_JOYSTICK_WGI 1 #cmakedefine SDL_JOYSTICK_XINPUT 1 + +#cmakedefine SDL_JOYSTICK_PRIVATE 1 + #cmakedefine SDL_HAPTIC_DUMMY 1 #cmakedefine SDL_HAPTIC_LINUX 1 #cmakedefine SDL_HAPTIC_IOKIT 1 #cmakedefine SDL_HAPTIC_DINPUT 1 #cmakedefine SDL_HAPTIC_ANDROID 1 + +#cmakedefine SDL_HAPTIC_PRIVATE 1 + #cmakedefine SDL_LIBUSB_DYNAMIC @SDL_LIBUSB_DYNAMIC@ #cmakedefine SDL_UDEV_DYNAMIC @SDL_UDEV_DYNAMIC@ @@ -312,6 +330,8 @@ #cmakedefine SDL_PROCESS_POSIX 1 #cmakedefine SDL_PROCESS_WINDOWS 1 +#cmakedefine SDL_PROCESS_PRIVATE 1 + /* Enable various sensor drivers */ #cmakedefine SDL_SENSOR_ANDROID 1 #cmakedefine SDL_SENSOR_COREMOTION 1 @@ -320,11 +340,15 @@ #cmakedefine SDL_SENSOR_VITA 1 #cmakedefine SDL_SENSOR_N3DS 1 +#cmakedefine SDL_SENSOR_PRIVATE 1 + /* Enable various shared object loading systems */ #cmakedefine SDL_LOADSO_DLOPEN 1 #cmakedefine SDL_LOADSO_DUMMY 1 #cmakedefine SDL_LOADSO_WINDOWS 1 +#cmakedefine SDL_LOADSO_PRIVATE 1 + /* Enable various threading systems */ #cmakedefine SDL_THREAD_GENERIC_COND_SUFFIX 1 #cmakedefine SDL_THREAD_GENERIC_RWLOCK_SUFFIX 1 @@ -337,6 +361,8 @@ #cmakedefine SDL_THREAD_PS2 1 #cmakedefine SDL_THREAD_N3DS 1 +#cmakedefine SDL_THREAD_PRIVATE 1 + /* Enable various RTC systems */ #cmakedefine SDL_TIME_UNIX 1 #cmakedefine SDL_TIME_WINDOWS 1 @@ -344,6 +370,9 @@ #cmakedefine SDL_TIME_PSP 1 #cmakedefine SDL_TIME_PS2 1 #cmakedefine SDL_TIME_N3DS 1 +#cmakedefine SDL_TIME_NGAGE 1 + +#cmakedefine SDL_TIME_PRIVATE 1 /* Enable various timer systems */ #cmakedefine SDL_TIMER_HAIKU 1 @@ -354,6 +383,8 @@ #cmakedefine SDL_TIMER_PS2 1 #cmakedefine SDL_TIMER_N3DS 1 +#cmakedefine SDL_TIMER_PRIVATE 1 + /* Enable various video drivers */ #cmakedefine SDL_VIDEO_DRIVER_ANDROID 1 #cmakedefine SDL_VIDEO_DRIVER_COCOA 1 @@ -364,6 +395,7 @@ #cmakedefine SDL_VIDEO_DRIVER_KMSDRM_DYNAMIC @SDL_VIDEO_DRIVER_KMSDRM_DYNAMIC@ #cmakedefine SDL_VIDEO_DRIVER_KMSDRM_DYNAMIC_GBM @SDL_VIDEO_DRIVER_KMSDRM_DYNAMIC_GBM@ #cmakedefine SDL_VIDEO_DRIVER_N3DS 1 +#cmakedefine SDL_VIDEO_DRIVER_NGAGE 1 #cmakedefine SDL_VIDEO_DRIVER_OFFSCREEN 1 #cmakedefine SDL_VIDEO_DRIVER_PS2 1 #cmakedefine SDL_VIDEO_DRIVER_PSP 1 @@ -390,6 +422,7 @@ #cmakedefine SDL_VIDEO_DRIVER_X11_DYNAMIC_XINPUT2 @SDL_VIDEO_DRIVER_X11_DYNAMIC_XINPUT2@ #cmakedefine SDL_VIDEO_DRIVER_X11_DYNAMIC_XRANDR @SDL_VIDEO_DRIVER_X11_DYNAMIC_XRANDR@ #cmakedefine SDL_VIDEO_DRIVER_X11_DYNAMIC_XSS @SDL_VIDEO_DRIVER_X11_DYNAMIC_XSS@ +#cmakedefine SDL_VIDEO_DRIVER_X11_DYNAMIC_XTEST @SDL_VIDEO_DRIVER_X11_DYNAMIC_XTEST@ #cmakedefine SDL_VIDEO_DRIVER_X11_HAS_XKBLOOKUPKEYSYM 1 #cmakedefine SDL_VIDEO_DRIVER_X11_SUPPORTS_GENERIC_EVENTS 1 #cmakedefine SDL_VIDEO_DRIVER_X11_XCURSOR 1 @@ -401,8 +434,11 @@ #cmakedefine SDL_VIDEO_DRIVER_X11_XSCRNSAVER 1 #cmakedefine SDL_VIDEO_DRIVER_X11_XSHAPE 1 #cmakedefine SDL_VIDEO_DRIVER_X11_XSYNC 1 +#cmakedefine SDL_VIDEO_DRIVER_X11_XTEST 1 #cmakedefine SDL_VIDEO_DRIVER_QNX 1 +#cmakedefine SDL_VIDEO_DRIVER_PRIVATE 1 + #cmakedefine SDL_VIDEO_RENDER_D3D 1 #cmakedefine SDL_VIDEO_RENDER_D3D11 1 #cmakedefine SDL_VIDEO_RENDER_D3D12 1 @@ -411,10 +447,13 @@ #cmakedefine SDL_VIDEO_RENDER_VULKAN 1 #cmakedefine SDL_VIDEO_RENDER_OGL 1 #cmakedefine SDL_VIDEO_RENDER_OGL_ES2 1 +#cmakedefine SDL_VIDEO_RENDER_NGAGE 1 #cmakedefine SDL_VIDEO_RENDER_PS2 1 #cmakedefine SDL_VIDEO_RENDER_PSP 1 #cmakedefine SDL_VIDEO_RENDER_VITA_GXM 1 +#cmakedefine SDL_VIDEO_RENDER_PRIVATE 1 + /* Enable OpenGL support */ #cmakedefine SDL_VIDEO_OPENGL 1 #cmakedefine SDL_VIDEO_OPENGL_ES 1 @@ -424,6 +463,8 @@ #cmakedefine SDL_VIDEO_OPENGL_WGL 1 #cmakedefine SDL_VIDEO_OPENGL_EGL 1 +#cmakedefine SDL_VIDEO_STATIC_ANGLE 1 + /* Enable Vulkan support */ #cmakedefine SDL_VIDEO_VULKAN 1 @@ -436,6 +477,8 @@ #cmakedefine SDL_GPU_VULKAN 1 #cmakedefine SDL_GPU_METAL 1 +#cmakedefine SDL_GPU_PRIVATE 1 + /* Enable system power support */ #cmakedefine SDL_POWER_ANDROID 1 #cmakedefine SDL_POWER_LINUX 1 @@ -449,6 +492,8 @@ #cmakedefine SDL_POWER_PSP 1 #cmakedefine SDL_POWER_N3DS 1 +#cmakedefine SDL_POWER_PRIVATE 1 + /* Enable system filesystem support */ #cmakedefine SDL_FILESYSTEM_ANDROID 1 #cmakedefine SDL_FILESYSTEM_HAIKU 1 @@ -463,14 +508,20 @@ #cmakedefine SDL_FILESYSTEM_PS2 1 #cmakedefine SDL_FILESYSTEM_N3DS 1 +#cmakedefine SDL_FILESYSTEM_PRIVATE 1 + /* Enable system storage support */ #cmakedefine SDL_STORAGE_STEAM @SDL_STORAGE_STEAM@ +#cmakedefine SDL_STORAGE_PRIVATE 1 + /* Enable system FSops support */ #cmakedefine SDL_FSOPS_POSIX 1 #cmakedefine SDL_FSOPS_WINDOWS 1 #cmakedefine SDL_FSOPS_DUMMY 1 +#cmakedefine SDL_FSOPS_PRIVATE 1 + /* Enable camera subsystem */ #cmakedefine SDL_CAMERA_DRIVER_DUMMY 1 /* !!! FIXME: for later cmakedefine SDL_CAMERA_DRIVER_DISK 1 */ @@ -483,6 +534,8 @@ #cmakedefine SDL_CAMERA_DRIVER_PIPEWIRE_DYNAMIC @SDL_CAMERA_DRIVER_PIPEWIRE_DYNAMIC@ #cmakedefine SDL_CAMERA_DRIVER_VITA 1 +#cmakedefine SDL_CAMERA_DRIVER_PRIVATE 1 + /* Enable dialog subsystem */ #cmakedefine SDL_DIALOG_DUMMY 1 @@ -505,6 +558,11 @@ #cmakedefine SDL_VIDEO_VITA_PVR 1 #cmakedefine SDL_VIDEO_VITA_PVR_OGL 1 +/* xkbcommon version info */ +#define SDL_XKBCOMMON_VERSION_MAJOR @SDL_XKBCOMMON_VERSION_MAJOR@ +#define SDL_XKBCOMMON_VERSION_MINOR @SDL_XKBCOMMON_VERSION_MINOR@ +#define SDL_XKBCOMMON_VERSION_PATCH @SDL_XKBCOMMON_VERSION_PATCH@ + /* Libdecor version info */ #define SDL_LIBDECOR_VERSION_MAJOR @SDL_LIBDECOR_VERSION_MAJOR@ #define SDL_LIBDECOR_VERSION_MINOR @SDL_LIBDECOR_VERSION_MINOR@ @@ -545,4 +603,8 @@ typedef unsigned int uintptr_t; #cmakedefine SDL_DISABLE_LASX 1 #cmakedefine SDL_DISABLE_NEON 1 +#ifdef SDL_PLATFORM_PRIVATE +#include "SDL_end_config_private.h" +#endif + #endif /* SDL_build_config_h_ */ diff --git a/libs/SDL3/include/build_config/SDL_build_config_windows.h b/libs/SDL3/include/build_config/SDL_build_config_windows.h index 44275176..24c578b6 100644 --- a/libs/SDL3/include/build_config/SDL_build_config_windows.h +++ b/libs/SDL3/include/build_config/SDL_build_config_windows.h @@ -95,6 +95,9 @@ typedef unsigned int uintptr_t; #if defined(_WIN32_MAXVER) && _WIN32_MAXVER >= 0x0603 /* Windows 8.1 SDK */ #define HAVE_SHELLSCALINGAPI_H 1 #endif +#if defined(_WIN32_MAXVER) && _WIN32_MAXVER >= 0x0601 /* Windows 7 SDK */ +#define HAVE_SHOBJIDL_CORE_H 1 +#endif #define HAVE_MMDEVICEAPI_H 1 #define HAVE_AUDIOCLIENT_H 1 #define HAVE_TPCSHRD_H 1 diff --git a/libs/SDL3/include/build_config/SDL_build_config_wingdk.h b/libs/SDL3/include/build_config/SDL_build_config_wingdk.h index 502eb929..f51a2fff 100644 --- a/libs/SDL3/include/build_config/SDL_build_config_wingdk.h +++ b/libs/SDL3/include/build_config/SDL_build_config_wingdk.h @@ -43,6 +43,7 @@ #define HAVE_D3D11_H 1 #define HAVE_ROAPI_H 1 #define HAVE_SHELLSCALINGAPI_H 1 +#define HAVE_SHOBJIDL_CORE_H 1 #define HAVE_MMDEVICEAPI_H 1 #define HAVE_AUDIOCLIENT_H 1 #define HAVE_TPCSHRD_H 1 diff --git a/libs/SDL3/include/build_config/SDL_build_config_xbox.h b/libs/SDL3/include/build_config/SDL_build_config_xbox.h index 5834e71f..500468dd 100644 --- a/libs/SDL3/include/build_config/SDL_build_config_xbox.h +++ b/libs/SDL3/include/build_config/SDL_build_config_xbox.h @@ -41,6 +41,7 @@ /*#define HAVE_WINDOWS_GAMING_INPUT_H 1*/ /*#define HAVE_ROAPI_H 1*/ /*#define HAVE_SHELLSCALINGAPI_H 1*/ +/*#define HAVE_SHOBJIDL_CORE_H 1*/ #define HAVE_MMDEVICEAPI_H 1 #define HAVE_AUDIOCLIENT_H 1 /*#define HAVE_TPCSHRD_H 1*/ diff --git a/libs/SDL3/src/SDL.c b/libs/SDL3/src/SDL.c index 502f6617..34e15251 100644 --- a/libs/SDL3/src/SDL.c +++ b/libs/SDL3/src/SDL.c @@ -728,6 +728,8 @@ const char *SDL_GetPlatform(void) return "macOS"; #elif defined(SDL_PLATFORM_NETBSD) return "NetBSD"; +#elif defined(SDL_PLATFORM_NGAGE) + return "Nokia N-Gage"; #elif defined(SDL_PLATFORM_OPENBSD) return "OpenBSD"; #elif defined(SDL_PLATFORM_OS2) diff --git a/libs/SDL3/src/SDL_error.c b/libs/SDL3/src/SDL_error.c index 3c62c8af..3f4273b4 100644 --- a/libs/SDL3/src/SDL_error.c +++ b/libs/SDL3/src/SDL_error.c @@ -20,6 +20,8 @@ */ #include "SDL_internal.h" +#include "stdlib/SDL_vacopy.h" + // Simple error handling in SDL #include "SDL_error_c.h" diff --git a/libs/SDL3/src/SDL_hints.c b/libs/SDL3/src/SDL_hints.c index 9c4ccd4b..786d654a 100644 --- a/libs/SDL3/src/SDL_hints.c +++ b/libs/SDL3/src/SDL_hints.c @@ -22,6 +22,10 @@ #include "SDL_hints_c.h" +#ifdef SDL_PLATFORM_ANDROID +#include "core/android/SDL_android.h" +#endif + typedef struct SDL_HintWatch { SDL_HintCallback callback; @@ -83,7 +87,7 @@ static void SDLCALL CleanupHintProperty(void *userdata, void *value) SDL_free(hint); } -static const char* GetHintEnvironmentVariable(const char *name) +static const char *GetHintEnvironmentVariable(const char *name) { const char *result = SDL_getenv(name); if (!result && name && *name) { @@ -147,6 +151,13 @@ bool SDL_SetHintWithPriority(const char *name, const char *value, SDL_HintPriori } } +#ifdef SDL_PLATFORM_ANDROID + if (SDL_strcmp(name, SDL_HINT_ANDROID_ALLOW_RECREATE_ACTIVITY) == 0) { + // Special handling for this hint, which needs to persist outside the normal application flow + Android_SetAllowRecreateActivity(SDL_GetStringBoolean(value, false)); + } +#endif // SDL_PLATFORM_ANDROID + SDL_UnlockProperties(hints); return result; @@ -185,6 +196,17 @@ bool SDL_ResetHint(const char *name) result = true; } +#ifdef SDL_PLATFORM_ANDROID + if (SDL_strcmp(name, SDL_HINT_ANDROID_ALLOW_RECREATE_ACTIVITY) == 0) { + // Special handling for this hint, which needs to persist outside the normal application flow + if (env) { + Android_SetAllowRecreateActivity(SDL_GetStringBoolean(env, false)); + } else { + Android_SetAllowRecreateActivity(false); + } + } +#endif // SDL_PLATFORM_ANDROID + SDL_UnlockProperties(hints); return result; @@ -210,6 +232,17 @@ static void SDLCALL ResetHintsCallback(void *userdata, SDL_PropertiesID hints, c SDL_free(hint->value); hint->value = NULL; hint->priority = SDL_HINT_DEFAULT; + +#ifdef SDL_PLATFORM_ANDROID + if (SDL_strcmp(name, SDL_HINT_ANDROID_ALLOW_RECREATE_ACTIVITY) == 0) { + // Special handling for this hint, which needs to persist outside the normal application flow + if (env) { + Android_SetAllowRecreateActivity(SDL_GetStringBoolean(env, false)); + } else { + Android_SetAllowRecreateActivity(false); + } + } +#endif // SDL_PLATFORM_ANDROID } void SDL_ResetHints(void) diff --git a/libs/SDL3/src/SDL_internal.h b/libs/SDL3/src/SDL_internal.h index a345252f..8fcd96a7 100644 --- a/libs/SDL3/src/SDL_internal.h +++ b/libs/SDL3/src/SDL_internal.h @@ -265,6 +265,12 @@ extern "C" { #include "SDL_utils_c.h" #include "SDL_hashtable.h" +#define PUSH_SDL_ERROR() \ + { char *_error = SDL_strdup(SDL_GetError()); + +#define POP_SDL_ERROR() \ + SDL_SetError("%s", _error); SDL_free(_error); } + // Do any initialization that needs to happen before threads are started extern void SDL_InitMainThread(void); diff --git a/libs/SDL3/src/SDL_log.c b/libs/SDL3/src/SDL_log.c index 10a814ff..da55dcf1 100644 --- a/libs/SDL3/src/SDL_log.c +++ b/libs/SDL3/src/SDL_log.c @@ -587,6 +587,25 @@ void SDL_LogMessageV(int category, SDL_LogPriority priority, SDL_PRINTF_FORMAT_S return; } +#if defined(SDL_PLATFORM_NGAGE) + extern void NGAGE_vnprintf(char *buf, size_t size, const char *fmt, va_list ap); + char buf[1024]; + NGAGE_vnprintf(buf, sizeof(buf), fmt, ap); + +#ifdef ENABLE_FILE_LOG + FILE* file; + file = fopen("E:/SDL_Log.txt", "a"); + if (file) + { + vfprintf(file, fmt, ap); + fprintf(file, "\n"); + (void)fclose(file); + } +#endif + + return; +#endif + // Render into stack buffer va_copy(aq, ap); len = SDL_vsnprintf(stack_buf, sizeof(stack_buf), fmt, aq); @@ -767,9 +786,14 @@ static void SDLCALL SDL_LogOutput(void *userdata, int category, SDL_LogPriority (void)fclose(pFile); } } +#elif defined(SDL_PLATFORM_NGAGE) + { + /* Nothing to do here. */ + } #endif #if defined(HAVE_STDIO_H) && \ !(defined(SDL_PLATFORM_APPLE) && (defined(SDL_VIDEO_DRIVER_COCOA) || defined(SDL_VIDEO_DRIVER_UIKIT))) && \ + !(defined(SDL_PLATFORM_NGAGE)) && \ !(defined(SDL_PLATFORM_WIN32)) (void)fprintf(stderr, "%s%s\n", GetLogPriorityPrefix(priority), message); #endif diff --git a/libs/SDL3/src/SDL_utils.c b/libs/SDL3/src/SDL_utils.c index 8d321430..f2090747 100644 --- a/libs/SDL3/src/SDL_utils.c +++ b/libs/SDL3/src/SDL_utils.c @@ -403,6 +403,10 @@ const char *SDL_GetPersistentString(const char *string) static int PrefixMatch(const char *a, const char *b) { int matchlen = 0; + // Fixes the "HORI HORl Taiko No Tatsujin Drum Controller" + if (SDL_strncmp(a, "HORI ", 5) == 0 && SDL_strncmp(b, "HORl ", 5) == 0) { + return 5; + } while (*a && *b) { if (SDL_tolower((unsigned char)*a++) == SDL_tolower((unsigned char)*b++)) { ++matchlen; @@ -424,8 +428,8 @@ char *SDL_CreateDeviceName(Uint16 vendor, Uint16 product, const char *vendor_nam { "ASTRO Gaming", "ASTRO" }, { "Bensussen Deutsch & Associates,Inc.(BDA)", "BDA" }, { "Guangzhou Chicken Run Network Technology Co., Ltd.", "GameSir" }, - { "HORI CO.,LTD", "HORI" }, { "HORI CO.,LTD.", "HORI" }, + { "HORI CO.,LTD", "HORI" }, { "Mad Catz Inc.", "Mad Catz" }, { "Nintendo Co., Ltd.", "Nintendo" }, { "NVIDIA Corporation ", "" }, diff --git a/libs/SDL3/src/atomic/SDL_atomic.c b/libs/SDL3/src/atomic/SDL_atomic.c index 5e7774da..7a575ac2 100644 --- a/libs/SDL3/src/atomic/SDL_atomic.c +++ b/libs/SDL3/src/atomic/SDL_atomic.c @@ -163,7 +163,7 @@ bool SDL_CompareAndSwapAtomicU32(SDL_AtomicU32 *a, Uint32 oldval, Uint32 newval) #elif defined(HAVE_GCC_ATOMICS) return __sync_bool_compare_and_swap(&a->value, oldval, newval); #elif defined(SDL_PLATFORM_MACOS) // this is deprecated in 10.12 sdk; favor gcc atomics. - return OSAtomicCompareAndSwap32Barrier((int32_t)oldval, (int32_t)newval, (int32_t*)&a->value); + return OSAtomicCompareAndSwap32Barrier((int32_t)oldval, (int32_t)newval, (int32_t *)&a->value); #elif defined(SDL_PLATFORM_SOLARIS) SDL_COMPILE_TIME_ASSERT(atomic_cas, sizeof(uint_t) == sizeof(a->value)); return ((Uint32)atomic_cas_uint((volatile uint_t *)&a->value, (uint_t)oldval, (uint_t)newval) == oldval); diff --git a/libs/SDL3/src/audio/SDL_audio.c b/libs/SDL3/src/audio/SDL_audio.c index cf817e4f..90d9f9bb 100644 --- a/libs/SDL3/src/audio/SDL_audio.c +++ b/libs/SDL3/src/audio/SDL_audio.c @@ -77,6 +77,9 @@ static const AudioBootStrap *const bootstrap[] = { #ifdef SDL_AUDIO_DRIVER_N3DS &N3DSAUDIO_bootstrap, #endif +#ifdef SDL_AUDIO_DRIVER_NGAGE + &NGAGEAUDIO_bootstrap, +#endif #ifdef SDL_AUDIO_DRIVER_EMSCRIPTEN &EMSCRIPTENAUDIO_bootstrap, #endif @@ -410,6 +413,7 @@ static SDL_LogicalAudioDevice *ObtainLogicalAudioDevice(SDL_AudioDeviceID devid, SDL_LockRWLockForReading(current_audio.device_hash_lock); SDL_FindInHashTable(current_audio.device_hash, (const void *) (uintptr_t) devid, (const void **) &logdev); if (logdev) { + SDL_assert(logdev->instance_id == devid); device = logdev->physical_device; SDL_assert(device != NULL); RefPhysicalAudioDevice(device); // reference it, in case the logical device migrates to a new default. @@ -459,6 +463,7 @@ static SDL_AudioDevice *ObtainPhysicalAudioDevice(SDL_AudioDeviceID devid) // ! } else { SDL_LockRWLockForReading(current_audio.device_hash_lock); SDL_FindInHashTable(current_audio.device_hash, (const void *) (uintptr_t) devid, (const void **) &device); + SDL_assert(device->instance_id == devid); SDL_UnlockRWLock(current_audio.device_hash_lock); if (!device) { @@ -883,6 +888,7 @@ static bool SDLCALL FindLowestDeviceID(void *userdata, const SDL_HashTable *tabl if (isphysical && (devid_recording == data->recording) && (devid < data->highest)) { data->highest = devid; data->result = (SDL_AudioDevice *) value; + SDL_assert(data->result->instance_id == devid); } return true; // keep iterating. } @@ -1051,7 +1057,10 @@ static bool SDLCALL DestroyOnePhysicalAudioDevice(void *userdata, const SDL_Hash const SDL_AudioDeviceID devid = (SDL_AudioDeviceID) (uintptr_t) key; const bool isphysical = !!(devid & (1<<1)); if (isphysical) { - DestroyPhysicalAudioDevice((SDL_AudioDevice *) value); + SDL_AudioDevice *dev = (SDL_AudioDevice *) value; + + SDL_assert(dev->instance_id == devid); + DestroyPhysicalAudioDevice(dev); } return true; // keep iterating. } @@ -1147,7 +1156,20 @@ bool SDL_PlaybackAudioThreadIterate(SDL_AudioDevice *device) // We should have updated this elsewhere if the format changed! SDL_assert(SDL_AudioSpecsEqual(&stream->dst_spec, &device->spec, NULL, NULL)); - const int br = SDL_GetAtomicInt(&logdev->paused) ? 0 : SDL_GetAudioStreamDataAdjustGain(stream, device_buffer, buffer_size, logdev->gain); + int br = 0; + + if (!SDL_GetAtomicInt(&logdev->paused)) { + if (logdev->iteration_start) { + logdev->iteration_start(logdev->iteration_userdata, logdev->instance_id, true); + } + + br = SDL_GetAudioStreamDataAdjustGain(stream, device_buffer, buffer_size, logdev->gain); + + if (logdev->iteration_end) { + logdev->iteration_end(logdev->iteration_userdata, logdev->instance_id, false); + } + } + if (br < 0) { // Probably OOM. Kill the audio device; the whole thing is likely dying soon anyhow. failed = true; SDL_memset(device_buffer, device->silence_value, buffer_size); // just supply silence to the device before we die. @@ -1185,6 +1207,10 @@ bool SDL_PlaybackAudioThreadIterate(SDL_AudioDevice *device) SDL_memset(mix_buffer, '\0', work_buffer_size); // start with silence. } + if (logdev->iteration_start) { + logdev->iteration_start(logdev->iteration_userdata, logdev->instance_id, true); + } + for (SDL_AudioStream *stream = logdev->bound_streams; stream; stream = stream->next_binding) { // We should have updated this elsewhere if the format changed! SDL_assert(SDL_AudioSpecsEqual(&stream->dst_spec, &outspec, NULL, NULL)); @@ -1207,6 +1233,10 @@ bool SDL_PlaybackAudioThreadIterate(SDL_AudioDevice *device) } } + if (logdev->iteration_end) { + logdev->iteration_end(logdev->iteration_userdata, logdev->instance_id, false); + } + if (postmix) { SDL_assert(mix_buffer == device->postmix_buffer); postmix(logdev->postmix_userdata, &outspec, mix_buffer, work_buffer_size); @@ -1385,6 +1415,7 @@ static int SDLCALL RecordingAudioThread(void *devicep) // thread entry point typedef struct CountAudioDevicesData { int devs_seen; + int devs_skipped; const int num_devices; SDL_AudioDeviceID *result; const bool recording; @@ -1400,7 +1431,13 @@ static bool SDLCALL CountAudioDevices(void *userdata, const SDL_HashTable *table const bool isphysical = !!(devid & (1<<1)); if (isphysical && (devid_recording == data->recording)) { SDL_assert(data->devs_seen < data->num_devices); - data->result[data->devs_seen++] = devid; + SDL_AudioDevice *device = (SDL_AudioDevice *) value; // this is normally risky, but we hold the device_hash_lock here. + const bool zombie = SDL_GetAtomicInt(&device->zombie) != 0; + if (zombie) { + data->devs_skipped++; + } else { + data->result[data->devs_seen++] = devid; + } } return true; // keep iterating. } @@ -1416,10 +1453,11 @@ static SDL_AudioDeviceID *GetAudioDevices(int *count, bool recording) num_devices = SDL_GetAtomicInt(recording ? ¤t_audio.recording_device_count : ¤t_audio.playback_device_count); result = (SDL_AudioDeviceID *) SDL_malloc((num_devices + 1) * sizeof (SDL_AudioDeviceID)); if (result) { - CountAudioDevicesData data = { 0, num_devices, result, recording }; + CountAudioDevicesData data = { 0, 0, num_devices, result, recording }; SDL_IterateHashTable(current_audio.device_hash, CountAudioDevices, &data); - SDL_assert(data.devs_seen == num_devices); - result[data.devs_seen] = 0; // null-terminated. + SDL_assert((data.devs_seen + data.devs_skipped) == num_devices); + num_devices = data.devs_seen; // might be less if we skipped any. + result[num_devices] = 0; // null-terminated. } } SDL_UnlockRWLock(current_audio.device_hash_lock); @@ -1464,6 +1502,7 @@ static bool SDLCALL FindAudioDeviceByCallback(void *userdata, const SDL_HashTabl SDL_AudioDevice *device = (SDL_AudioDevice *) value; if (data->callback(device, data->userdata)) { // found it? data->retval = device; + SDL_assert(data->retval->instance_id == devid); return false; // stop iterating, we found it. } } @@ -1502,12 +1541,33 @@ SDL_AudioDevice *SDL_FindPhysicalAudioDeviceByHandle(void *handle) const char *SDL_GetAudioDeviceName(SDL_AudioDeviceID devid) { + // bit #1 of devid is set for physical devices and unset for logical. + const bool islogical = !(devid & (1<<1)); const char *result = NULL; - SDL_AudioDevice *device = ObtainPhysicalAudioDevice(devid); - if (device) { - result = SDL_GetPersistentString(device->name); + const void *vdev = NULL; + + if (!SDL_GetCurrentAudioDriver()) { + SDL_SetError("Audio subsystem is not initialized"); + } else { + // This does not call ObtainPhysicalAudioDevice() because the device's name never changes, so + // it doesn't have to lock the whole device. However, just to make sure the device pointer itself + // remains valid (in case the device is unplugged at the wrong moment), we hold the + // device_hash_lock while we copy the string. + SDL_LockRWLockForReading(current_audio.device_hash_lock); + SDL_FindInHashTable(current_audio.device_hash, (const void *) (uintptr_t) devid, &vdev); + if (!vdev) { + SDL_SetError("Invalid audio device instance ID"); + } else if (islogical) { + const SDL_LogicalAudioDevice *logdev = (const SDL_LogicalAudioDevice *) vdev; + SDL_assert(logdev->instance_id == devid); + result = SDL_GetPersistentString(logdev->physical_device->name); + } else { + const SDL_AudioDevice *device = (const SDL_AudioDevice *) vdev; + SDL_assert(device->instance_id == devid); + result = SDL_GetPersistentString(device->name); + } + SDL_UnlockRWLock(current_audio.device_hash_lock); } - ReleaseAudioDevice(device); return result; } @@ -1715,13 +1775,18 @@ static bool OpenPhysicalAudioDevice(SDL_AudioDevice *device, const SDL_AudioSpec SDL_copyp(&spec, inspec ? inspec : &device->default_spec); PrepareAudioFormat(device->recording, &spec); - /* We allow the device format to change if it's better than the current settings (by various definitions of "better"). This prevents - something low quality, like an old game using S8/8000Hz audio, from ruining a music thing playing at CD quality that tries to open later. - (or some VoIP library that opens for mono output ruining your surround-sound game because it got there first). + /* We impose a simple minimum on device formats. This prevents something low quality, like an old game using S8/8000Hz audio, + from ruining a music thing playing at CD quality that tries to open later, or some VoIP library that opens for mono output + ruining your surround-sound game because it got there first. These are just requests! The backend may change any of these values during OpenDevice method! */ - device->spec.format = (SDL_AUDIO_BITSIZE(device->default_spec.format) >= SDL_AUDIO_BITSIZE(spec.format)) ? device->default_spec.format : spec.format; - device->spec.freq = SDL_max(device->default_spec.freq, spec.freq); - device->spec.channels = SDL_max(device->default_spec.channels, spec.channels); + + const SDL_AudioFormat minimum_format = device->recording ? DEFAULT_AUDIO_RECORDING_FORMAT : DEFAULT_AUDIO_PLAYBACK_FORMAT; + const int minimum_channels = device->recording ? DEFAULT_AUDIO_RECORDING_CHANNELS : DEFAULT_AUDIO_PLAYBACK_CHANNELS; + const int minimum_freq = device->recording ? DEFAULT_AUDIO_RECORDING_FREQUENCY : DEFAULT_AUDIO_PLAYBACK_FREQUENCY; + + device->spec.format = (SDL_AUDIO_BITSIZE(minimum_format) >= SDL_AUDIO_BITSIZE(spec.format)) ? minimum_format : spec.format; + device->spec.channels = SDL_max(minimum_channels, spec.channels); + device->spec.freq = SDL_max(minimum_freq, spec.freq); device->sample_frames = SDL_GetDefaultSampleFramesFromFreq(device->spec.freq); SDL_UpdatedAudioDeviceFormat(device); // start this off sane. @@ -1889,8 +1954,9 @@ bool SDL_SetAudioPostmixCallback(SDL_AudioDeviceID devid, SDL_AudioPostmixCallba { SDL_AudioDevice *device = NULL; SDL_LogicalAudioDevice *logdev = ObtainLogicalAudioDevice(devid, &device); - bool result = true; + bool result = false; if (logdev) { + result = true; if (callback && !device->postmix_buffer) { device->postmix_buffer = (float *)SDL_aligned_alloc(SDL_GetSIMDAlignment(), device->work_buffer_size); if (!device->postmix_buffer) { @@ -1909,6 +1975,21 @@ bool SDL_SetAudioPostmixCallback(SDL_AudioDeviceID devid, SDL_AudioPostmixCallba return result; } +bool SDL_SetAudioIterationCallbacks(SDL_AudioDeviceID devid, SDL_AudioIterationCallback iter_start, SDL_AudioIterationCallback iter_end, void *userdata) +{ + SDL_AudioDevice *device = NULL; + SDL_LogicalAudioDevice *logdev = ObtainLogicalAudioDevice(devid, &device); + bool result = false; + if (logdev) { + logdev->iteration_start = iter_start; + logdev->iteration_end = iter_end; + logdev->iteration_userdata = userdata; + result = true; + } + ReleaseAudioDevice(device); + return result; +} + bool SDL_BindAudioStreams(SDL_AudioDeviceID devid, SDL_AudioStream * const *streams, int num_streams) { const bool islogical = !(devid & (1<<1)); diff --git a/libs/SDL3/src/audio/SDL_audiocvt.c b/libs/SDL3/src/audio/SDL_audiocvt.c index f751b0e5..1d688400 100644 --- a/libs/SDL3/src/audio/SDL_audiocvt.c +++ b/libs/SDL3/src/audio/SDL_audiocvt.c @@ -280,7 +280,7 @@ void ConvertAudio(int num_frames, // swizzle input to "standard" format if necessary. if (src_map) { - void* buf = scratch ? scratch : dst; // use scratch if available, since it has to be big enough to hold src, unless it's NULL, then dst has to be. + void *buf = scratch ? scratch : dst; // use scratch if available, since it has to be big enough to hold src, unless it's NULL, then dst has to be. SwizzleAudio(num_frames, buf, src, src_channels, src_map, src_format); src = buf; } @@ -318,7 +318,7 @@ void ConvertAudio(int num_frames, // get us to float format. if (srcconvert) { - void* buf = (channelconvert || dstconvert) ? scratch : dst; + void *buf = (channelconvert || dstconvert) ? scratch : dst; ConvertAudioToFloat((float *) buf, src, num_frames * src_channels, src_format); src = buf; } @@ -332,7 +332,7 @@ void ConvertAudio(int num_frames, buf[i] *= gain; } } else { - float *fsrc = (float *)src; + const float *fsrc = (const float *)src; for (int i = 0; i < total_samples; i++) { buf[i] = fsrc[i] * gain; } @@ -368,7 +368,7 @@ void ConvertAudio(int num_frames, channel_converter = override; } - void* buf = dstconvert ? scratch : dst; + void *buf = dstconvert ? scratch : dst; channel_converter((float *) buf, (const float *) src, num_frames); src = buf; } @@ -399,7 +399,7 @@ static int CalculateMaxFrameSize(SDL_AudioFormat src_format, int src_channels, S return max_format_size * max_channels; } -static Sint64 GetAudioStreamResampleRate(SDL_AudioStream* stream, int src_freq, Sint64 resample_offset) +static Sint64 GetAudioStreamResampleRate(SDL_AudioStream *stream, int src_freq, Sint64 resample_offset) { src_freq = (int)((float)src_freq * stream->freq_ratio); @@ -768,16 +768,48 @@ bool SDL_SetAudioStreamGain(SDL_AudioStream *stream, float gain) static bool CheckAudioStreamIsFullySetup(SDL_AudioStream *stream) { - if (stream->src_spec.format == 0) { + if (stream->src_spec.format == SDL_AUDIO_UNKNOWN) { return SDL_SetError("Stream has no source format"); - } else if (stream->dst_spec.format == 0) { + } else if (stream->dst_spec.format == SDL_AUDIO_UNKNOWN) { return SDL_SetError("Stream has no destination format"); } return true; } -static bool PutAudioStreamBuffer(SDL_AudioStream *stream, const void *buf, int len, SDL_ReleaseAudioBufferCallback callback, void* userdata) +// you MUST hold `stream->lock` when calling this, and validate your parameters! +static bool PutAudioStreamBufferInternal(SDL_AudioStream *stream, const SDL_AudioSpec *spec, const int *chmap, const void *buf, int len, SDL_ReleaseAudioBufferCallback callback, void *userdata) +{ + SDL_AudioTrack *track = NULL; + + if (callback) { + track = SDL_CreateAudioTrack(stream->queue, spec, chmap, (Uint8 *)buf, len, len, callback, userdata); + if (!track) { + return false; + } + } + + const int prev_available = stream->put_callback ? SDL_GetAudioStreamAvailable(stream) : 0; + + bool retval = true; + + if (track) { + SDL_AddTrackToAudioQueue(stream->queue, track); + } else { + retval = SDL_WriteToAudioQueue(stream->queue, spec, chmap, (const Uint8 *)buf, len); + } + + if (retval) { + if (stream->put_callback) { + const int newavail = SDL_GetAudioStreamAvailable(stream) - prev_available; + stream->put_callback(stream->put_callback_userdata, stream, newavail, newavail); + } + } + + return retval; +} + +static bool PutAudioStreamBuffer(SDL_AudioStream *stream, const void *buf, int len, SDL_ReleaseAudioBufferCallback callback, void *userdata) { #if DEBUG_AUDIOSTREAM SDL_Log("AUDIOSTREAM: wants to put %d bytes", len); @@ -795,42 +827,16 @@ static bool PutAudioStreamBuffer(SDL_AudioStream *stream, const void *buf, int l return SDL_SetError("Can't add partial sample frames"); } - SDL_AudioTrack* track = NULL; - - if (callback) { - track = SDL_CreateAudioTrack(stream->queue, &stream->src_spec, stream->src_chmap, (Uint8 *)buf, len, len, callback, userdata); - - if (!track) { - SDL_UnlockMutex(stream->lock); - return false; - } - } - - const int prev_available = stream->put_callback ? SDL_GetAudioStreamAvailable(stream) : 0; - - bool result = true; - - if (track) { - SDL_AddTrackToAudioQueue(stream->queue, track); - } else { - result = SDL_WriteToAudioQueue(stream->queue, &stream->src_spec, stream->src_chmap, (const Uint8 *)buf, len); - } - - if (result) { - if (stream->put_callback) { - const int newavail = SDL_GetAudioStreamAvailable(stream) - prev_available; - stream->put_callback(stream->put_callback_userdata, stream, newavail, newavail); - } - } + const bool retval = PutAudioStreamBufferInternal(stream, &stream->src_spec, stream->src_chmap, buf, len, callback, userdata); SDL_UnlockMutex(stream->lock); - return result; + return retval; } static void SDLCALL FreeAllocatedAudioBuffer(void *userdata, const void *buf, int len) { - SDL_free((void*) buf); + SDL_free((void *)buf); } bool SDL_PutAudioStreamData(SDL_AudioStream *stream, const void *buf, int len) @@ -857,9 +863,8 @@ bool SDL_PutAudioStreamData(SDL_AudioStream *stream, const void *buf, int len) } SDL_memcpy(data, buf, len); - buf = data; - bool ret = PutAudioStreamBuffer(stream, buf, len, FreeAllocatedAudioBuffer, NULL); + bool ret = PutAudioStreamBuffer(stream, data, len, FreeAllocatedAudioBuffer, NULL); if (!ret) { SDL_free(data); } @@ -869,6 +874,181 @@ bool SDL_PutAudioStreamData(SDL_AudioStream *stream, const void *buf, int len) return PutAudioStreamBuffer(stream, buf, len, NULL, NULL); } + +#define GENERIC_INTERLEAVE_FUNCTION(bits) \ + static void InterleaveAudioChannelsGeneric##bits(void *output, const void * const *channel_buffers, const int channels, int num_samples) { \ + Uint##bits *dst = (Uint##bits *) output; \ + const Uint##bits * const *srcs = (const Uint##bits * const *) channel_buffers; \ + for (int frame = 0; frame < num_samples; frame++) { \ + for (int channel = 0; channel < channels; channel++) { \ + *(dst++) = srcs[channel][frame]; \ + } \ + } \ + } + +GENERIC_INTERLEAVE_FUNCTION(8) +GENERIC_INTERLEAVE_FUNCTION(16) +GENERIC_INTERLEAVE_FUNCTION(32) +//GENERIC_INTERLEAVE_FUNCTION(64) (we don't have any 64-bit audio data types at the moment.) +#undef GENERIC_INTERLEAVE_FUNCTION + +#define GENERIC_INTERLEAVE_WITH_NULLS_FUNCTION(bits) \ + static void InterleaveAudioChannelsWithNullsGeneric##bits(void *output, const void * const *channel_buffers, const int channels, int num_samples, const int isilence) { \ + const Uint##bits silence = (Uint##bits) isilence; \ + Uint##bits *dst = (Uint##bits *) output; \ + const Uint##bits * const *srcs = (const Uint##bits * const *) channel_buffers; \ + for (int frame = 0; frame < num_samples; frame++) { \ + for (int channel = 0; channel < channels; channel++) { \ + *(dst++) = srcs[channel] ? srcs[channel][frame] : silence; \ + } \ + } \ + } + +GENERIC_INTERLEAVE_WITH_NULLS_FUNCTION(8) +GENERIC_INTERLEAVE_WITH_NULLS_FUNCTION(16) +GENERIC_INTERLEAVE_WITH_NULLS_FUNCTION(32) +//GENERIC_INTERLEAVE_WITH_NULLS_FUNCTION(64) (we don't have any 64-bit audio data types at the moment.) +#undef GENERIC_INTERLEAVE_WITH_NULLS_FUNCTION + +static void InterleaveAudioChannels(void *output, const void * const *channel_buffers, int channels, int num_samples, const SDL_AudioSpec *spec) +{ + bool have_null_channel = false; + void *channels_full[16]; + + // if didn't specify enough channels, pad out a channel array with NULLs. + if ((channels >= 0) && (channels < spec->channels)) { + have_null_channel = true; + SDL_assert(SDL_IsSupportedChannelCount(spec->channels)); + SDL_assert(spec->channels <= SDL_arraysize(channels_full)); + SDL_memcpy(channels_full, channel_buffers, channels * sizeof (*channel_buffers)); + SDL_memset(channels_full + channels, 0, (spec->channels - channels) * sizeof (*channel_buffers)); + channel_buffers = (const void * const *) channels_full; + } + + channels = spec->channels; // it's either < 0, needs to be clamped to spec->channels, or we just padded it out to spec->channels with channels_full. + + if (!have_null_channel) { + for (int i = 0; i < channels; i++) { + if (channel_buffers[i] == NULL) { + have_null_channel = true; + break; + } + } + } + + if (have_null_channel) { + const int silence = SDL_GetSilenceValueForFormat(spec->format); + switch (SDL_AUDIO_BITSIZE(spec->format)) { + case 8: InterleaveAudioChannelsWithNullsGeneric8(output, channel_buffers, channels, num_samples, silence); break; + case 16: InterleaveAudioChannelsWithNullsGeneric16(output, channel_buffers, channels, num_samples, silence); break; + case 32: InterleaveAudioChannelsWithNullsGeneric32(output, channel_buffers, channels, num_samples, silence); break; + //case 64: InterleaveAudioChannelsGeneric64(output, channel_buffers, channels, num_samples); break; (we don't have any 64-bit audio data types at the moment.) + default: SDL_assert(!"Missing needed generic audio interleave function!"); SDL_memset(output, 0, SDL_AUDIO_FRAMESIZE(*spec) * num_samples); break; + } + } else { + // !!! FIXME: it would be possible to do this really well in SIMD for stereo data, using unpack (intel) or zip (arm) instructions, etc. + switch (SDL_AUDIO_BITSIZE(spec->format)) { + case 8: InterleaveAudioChannelsGeneric8(output, channel_buffers, channels, num_samples); break; + case 16: InterleaveAudioChannelsGeneric16(output, channel_buffers, channels, num_samples); break; + case 32: InterleaveAudioChannelsGeneric32(output, channel_buffers, channels, num_samples); break; + //case 64: InterleaveAudioChannelsGeneric64(output, channel_buffers, channels, num_samples); break; (we don't have any 64-bit audio data types at the moment.) + default: SDL_assert(!"Missing needed generic audio interleave function!"); SDL_memset(output, 0, SDL_AUDIO_FRAMESIZE(*spec) * num_samples); break; + } + } +} + +bool SDL_PutAudioStreamPlanarData(SDL_AudioStream *stream, const void * const *channel_buffers, int num_channels, int num_samples) +{ + if (!stream) { + return SDL_InvalidParamError("stream"); + } else if (!channel_buffers) { + return SDL_InvalidParamError("channel_buffers"); + } else if (num_samples < 0) { + return SDL_InvalidParamError("num_samples"); + } else if (num_samples == 0) { + return true; // nothing to do. + } + + // we do the interleaving up front without the lock held, so the audio device doesn't starve while we work. + // but we _do_ need to know the current input spec. + SDL_AudioSpec spec; + int chmap_copy[SDL_MAX_CHANNELMAP_CHANNELS]; + int *chmap = NULL; + SDL_LockMutex(stream->lock); + if (!CheckAudioStreamIsFullySetup(stream)) { + SDL_UnlockMutex(stream->lock); + return false; + } + SDL_copyp(&spec, &stream->src_spec); + if (stream->src_chmap) { + chmap = chmap_copy; + SDL_memcpy(chmap, stream->src_chmap, sizeof (*chmap) * spec.channels); + } + SDL_UnlockMutex(stream->lock); + + if (spec.channels == 1) { // nothing to interleave, just use the usual function. + return SDL_PutAudioStreamData(stream, channel_buffers[0], SDL_AUDIO_FRAMESIZE(spec) * num_samples); + } + + bool retval = false; + + const int len = SDL_AUDIO_FRAMESIZE(spec) * num_samples; + #if DEBUG_AUDIOSTREAM + SDL_Log("AUDIOSTREAM: wants to put %d bytes of planar data", len); + #endif + + // Is the data small enough to just interleave it on the stack and put it through the normal interface? + #define INTERLEAVE_STACK_SIZE 1024 + Uint8 stackbuf[INTERLEAVE_STACK_SIZE]; + void *data = stackbuf; + SDL_ReleaseAudioBufferCallback callback = NULL; + + if (len > INTERLEAVE_STACK_SIZE) { + // too big for the stack? Just SDL_malloc a block and interleave into that. To avoid the extra copy, we'll just set it as a + // new track in the queue (the distinction is specifying a callback to PutAudioStreamBufferInternal, to release the buffer). + data = SDL_malloc(len); + if (!data) { + return false; + } + callback = FreeAllocatedAudioBuffer; + } + + InterleaveAudioChannels(data, channel_buffers, num_channels, num_samples, &spec); + + // it's okay if the stream format changed on another thread while we didn't hold the lock; PutAudioStreamBufferInternal will notice + // and set up a new track with the right format, and the next SDL_PutAudioStreamData will notice that stream->src_spec doesn't + // match the new track and set up a new one again. It's a bad idea to change the format on another thread while putting here, + // but everything _will_ work out with the format that was (presumably) expected. + SDL_LockMutex(stream->lock); + retval = PutAudioStreamBufferInternal(stream, &spec, chmap, data, len, callback, NULL); + SDL_UnlockMutex(stream->lock); + + return retval; +} + +static void SDLCALL DontFreeThisAudioBuffer(void *userdata, const void *buf, int len) +{ + // We don't own the buffer, but know it will outlive the stream +} + +bool SDL_PutAudioStreamDataNoCopy(SDL_AudioStream *stream, const void *buf, int len, SDL_AudioStreamDataCompleteCallback callback, void *userdata) +{ + if (!stream) { + return SDL_InvalidParamError("stream"); + } else if (!buf) { + return SDL_InvalidParamError("buf"); + } else if (len < 0) { + return SDL_InvalidParamError("len"); + } else if (len == 0) { + if (callback) { + callback(userdata, buf, len); + } + return true; // nothing to do. + } + + return PutAudioStreamBuffer(stream, buf, len, callback ? callback : DontFreeThisAudioBuffer, userdata); +} + bool SDL_FlushAudioStream(SDL_AudioStream *stream) { if (!stream) { @@ -901,8 +1081,8 @@ static Uint8 *EnsureAudioStreamWorkBufferSize(SDL_AudioStream *stream, size_t ne return ptr; } -static Sint64 NextAudioStreamIter(SDL_AudioStream* stream, void** inout_iter, - Sint64* inout_resample_offset, SDL_AudioSpec* out_spec, int **out_chmap, bool* out_flushed) +static Sint64 NextAudioStreamIter(SDL_AudioStream *stream, void **inout_iter, + Sint64 *inout_resample_offset, SDL_AudioSpec *out_spec, int **out_chmap, bool *out_flushed) { SDL_AudioSpec spec; bool flushed; @@ -956,9 +1136,9 @@ static Sint64 NextAudioStreamIter(SDL_AudioStream* stream, void** inout_iter, return output_frames; } -static Sint64 GetAudioStreamAvailableFrames(SDL_AudioStream* stream, Sint64* out_resample_offset) +static Sint64 GetAudioStreamAvailableFrames(SDL_AudioStream *stream, Sint64 *out_resample_offset) { - void* iter = SDL_BeginAudioQueueIter(stream->queue); + void *iter = SDL_BeginAudioQueueIter(stream->queue); Sint64 resample_offset = stream->resample_offset; Sint64 output_frames = 0; @@ -980,9 +1160,9 @@ static Sint64 GetAudioStreamAvailableFrames(SDL_AudioStream* stream, Sint64* out return output_frames; } -static Sint64 GetAudioStreamHead(SDL_AudioStream* stream, SDL_AudioSpec* out_spec, int **out_chmap, bool* out_flushed) +static Sint64 GetAudioStreamHead(SDL_AudioStream *stream, SDL_AudioSpec *out_spec, int **out_chmap, bool *out_flushed) { - void* iter = SDL_BeginAudioQueueIter(stream->queue); + void *iter = SDL_BeginAudioQueueIter(stream->queue); if (!iter) { SDL_zerop(out_spec); @@ -998,8 +1178,8 @@ static Sint64 GetAudioStreamHead(SDL_AudioStream* stream, SDL_AudioSpec* out_spe // Enough input data MUST be available! static bool GetAudioStreamDataInternal(SDL_AudioStream *stream, void *buf, int output_frames, float gain) { - const SDL_AudioSpec* src_spec = &stream->input_spec; - const SDL_AudioSpec* dst_spec = &stream->dst_spec; + const SDL_AudioSpec *src_spec = &stream->input_spec; + const SDL_AudioSpec *dst_spec = &stream->dst_spec; const SDL_AudioFormat src_format = src_spec->format; const int src_channels = src_spec->channels; @@ -1019,7 +1199,7 @@ static bool GetAudioStreamDataInternal(SDL_AudioStream *stream, void *buf, int o // Not resampling? It's an easy conversion (and maybe not even that!) if (resample_rate == 0) { - Uint8* work_buffer = NULL; + Uint8 *work_buffer = NULL; // Ensure we have enough scratch space for any conversions if ((src_format != dst_format) || (src_channels != dst_channels) || (gain != 1.0f)) { @@ -1089,7 +1269,7 @@ static bool GetAudioStreamDataInternal(SDL_AudioStream *stream, void *buf, int o work_buffer_capacity += resample_bytes; } - Uint8* work_buffer = EnsureAudioStreamWorkBufferSize(stream, work_buffer_capacity); + Uint8 *work_buffer = EnsureAudioStreamWorkBufferSize(stream, work_buffer_capacity); if (!work_buffer) { return false; @@ -1101,7 +1281,7 @@ static bool GetAudioStreamDataInternal(SDL_AudioStream *stream, void *buf, int o const float postresample_gain = (input_frames > output_frames) ? gain : 1.0f; // (dst channel map is NULL because we'll do the final swizzle on ConvertAudio after resample.) - const Uint8* input_buffer = SDL_ReadFromAudioQueue(stream->queue, + const Uint8 *input_buffer = SDL_ReadFromAudioQueue(stream->queue, NULL, resample_format, resample_channels, NULL, padding_frames, input_frames, padding_frames, work_buffer, preresample_gain); @@ -1112,11 +1292,11 @@ static bool GetAudioStreamDataInternal(SDL_AudioStream *stream, void *buf, int o input_buffer += padding_frames * resample_frame_size; // Decide where the resampled output goes - void* resample_buffer = (resample_buffer_offset != -1) ? (work_buffer + resample_buffer_offset) : buf; + void *resample_buffer = (resample_buffer_offset != -1) ? (work_buffer + resample_buffer_offset) : buf; SDL_ResampleAudio(resample_channels, - (const float *) input_buffer, input_frames, - (float*) resample_buffer, output_frames, + (const float *)input_buffer, input_frames, + (float *)resample_buffer, output_frames, resample_rate, &stream->resample_offset); // Convert to the final format, if necessary (src channel map is NULL because SDL_ReadFromAudioQueue already handled this). @@ -1326,11 +1506,6 @@ void SDL_DestroyAudioStream(SDL_AudioStream *stream) SDL_free(stream); } -static void SDLCALL DontFreeThisAudioBuffer(void *userdata, const void *buf, int len) -{ - // We don't own the buffer, but know it will outlive the stream -} - bool SDL_ConvertAudioSamples(const SDL_AudioSpec *src_spec, const Uint8 *src_data, int src_len, const SDL_AudioSpec *dst_spec, Uint8 **dst_data, int *dst_len) { if (dst_data) { @@ -1357,8 +1532,7 @@ bool SDL_ConvertAudioSamples(const SDL_AudioSpec *src_spec, const Uint8 *src_dat SDL_AudioStream *stream = SDL_CreateAudioStream(src_spec, dst_spec); if (stream) { - if (PutAudioStreamBuffer(stream, src_data, src_len, DontFreeThisAudioBuffer, NULL) && - SDL_FlushAudioStream(stream)) { + if (SDL_PutAudioStreamDataNoCopy(stream, src_data, src_len, NULL, NULL) && SDL_FlushAudioStream(stream)) { dstlen = SDL_GetAudioStreamAvailable(stream); if (dstlen >= 0) { dst = (Uint8 *)SDL_malloc(dstlen); diff --git a/libs/SDL3/src/audio/SDL_audioqueue.h b/libs/SDL3/src/audio/SDL_audioqueue.h index 46629462..dc3c71c2 100644 --- a/libs/SDL3/src/audio/SDL_audioqueue.h +++ b/libs/SDL3/src/audio/SDL_audioqueue.h @@ -25,7 +25,7 @@ // Internal functions used by SDL_AudioStream for queueing audio. -typedef void (SDLCALL *SDL_ReleaseAudioBufferCallback)(void *userdata, const void *buffer, int buflen); +typedef SDL_AudioStreamDataCompleteCallback SDL_ReleaseAudioBufferCallback; typedef struct SDL_AudioQueue SDL_AudioQueue; typedef struct SDL_AudioTrack SDL_AudioTrack; diff --git a/libs/SDL3/src/audio/SDL_audiotypecvt.c b/libs/SDL3/src/audio/SDL_audiotypecvt.c index a27575f2..78c325e5 100644 --- a/libs/SDL3/src/audio/SDL_audiotypecvt.c +++ b/libs/SDL3/src/audio/SDL_audiotypecvt.c @@ -185,7 +185,7 @@ static void SDL_Convert_F32_to_S32_Scalar(Sint32 *dst, const float *src, int num #undef SIGNMASK -static void SDL_Convert_Swap16_Scalar(Uint16* dst, const Uint16* src, int num_samples) +static void SDL_Convert_Swap16_Scalar(Uint16 *dst, const Uint16 *src, int num_samples) { int i; @@ -194,7 +194,7 @@ static void SDL_Convert_Swap16_Scalar(Uint16* dst, const Uint16* src, int num_sa } } -static void SDL_Convert_Swap32_Scalar(Uint32* dst, const Uint32* src, int num_samples) +static void SDL_Convert_Swap32_Scalar(Uint32 *dst, const Uint32 *src, int num_samples) { int i; @@ -375,7 +375,7 @@ static void SDL_TARGETING("sse2") SDL_Convert_F32_to_S8_SSE2(Sint8 *dst, const f const __m128i bytes = _mm_packus_epi16(shorts0, shorts1); - _mm_store_si128((__m128i*)&dst[i], bytes); + _mm_store_si128((__m128i *)&dst[i], bytes); }) } @@ -409,7 +409,7 @@ static void SDL_TARGETING("sse2") SDL_Convert_F32_to_U8_SSE2(Uint8 *dst, const f const __m128i bytes = _mm_packus_epi16(shorts0, shorts1); - _mm_store_si128((__m128i*)&dst[i], bytes); + _mm_store_si128((__m128i *)&dst[i], bytes); }) } @@ -441,8 +441,8 @@ static void SDL_TARGETING("sse2") SDL_Convert_F32_to_S16_SSE2(Sint16 *dst, const const __m128i shorts0 = _mm_packs_epi32(ints0, ints1); const __m128i shorts1 = _mm_packs_epi32(ints2, ints3); - _mm_store_si128((__m128i*)&dst[i], shorts0); - _mm_store_si128((__m128i*)&dst[i + 8], shorts1); + _mm_store_si128((__m128i *)&dst[i], shorts0); + _mm_store_si128((__m128i *)&dst[i + 8], shorts1); }) } @@ -477,55 +477,55 @@ static void SDL_TARGETING("sse2") SDL_Convert_F32_to_S32_SSE2(Sint32 *dst, const const __m128i ints2 = _mm_xor_si128(_mm_cvttps_epi32(values3), _mm_castps_si128(_mm_cmpge_ps(values3, limit))); const __m128i ints3 = _mm_xor_si128(_mm_cvttps_epi32(values4), _mm_castps_si128(_mm_cmpge_ps(values4, limit))); - _mm_store_si128((__m128i*)&dst[i], ints0); - _mm_store_si128((__m128i*)&dst[i + 4], ints1); - _mm_store_si128((__m128i*)&dst[i + 8], ints2); - _mm_store_si128((__m128i*)&dst[i + 12], ints3); + _mm_store_si128((__m128i *)&dst[i], ints0); + _mm_store_si128((__m128i *)&dst[i + 4], ints1); + _mm_store_si128((__m128i *)&dst[i + 8], ints2); + _mm_store_si128((__m128i *)&dst[i + 12], ints3); }) } #endif // FIXME: SDL doesn't have SSSE3 detection, so use the next one up #ifdef SDL_SSE4_1_INTRINSICS -static void SDL_TARGETING("ssse3") SDL_Convert_Swap16_SSSE3(Uint16* dst, const Uint16* src, int num_samples) +static void SDL_TARGETING("ssse3") SDL_Convert_Swap16_SSSE3(Uint16 *dst, const Uint16 *src, int num_samples) { const __m128i shuffle = _mm_set_epi8(14, 15, 12, 13, 10, 11, 8, 9, 6, 7, 4, 5, 2, 3, 0, 1); CONVERT_16_FWD({ dst[i] = SDL_Swap16(src[i]); }, { - __m128i ints0 = _mm_loadu_si128((const __m128i*)&src[i]); - __m128i ints1 = _mm_loadu_si128((const __m128i*)&src[i + 8]); + __m128i ints0 = _mm_loadu_si128((const __m128i *)&src[i]); + __m128i ints1 = _mm_loadu_si128((const __m128i *)&src[i + 8]); ints0 = _mm_shuffle_epi8(ints0, shuffle); ints1 = _mm_shuffle_epi8(ints1, shuffle); - _mm_store_si128((__m128i*)&dst[i], ints0); - _mm_store_si128((__m128i*)&dst[i + 8], ints1); + _mm_store_si128((__m128i *)&dst[i], ints0); + _mm_store_si128((__m128i *)&dst[i + 8], ints1); }) } -static void SDL_TARGETING("ssse3") SDL_Convert_Swap32_SSSE3(Uint32* dst, const Uint32* src, int num_samples) +static void SDL_TARGETING("ssse3") SDL_Convert_Swap32_SSSE3(Uint32 *dst, const Uint32 *src, int num_samples) { const __m128i shuffle = _mm_set_epi8(12, 13, 14, 15, 8, 9, 10, 11, 4, 5, 6, 7, 0, 1, 2, 3); CONVERT_16_FWD({ dst[i] = SDL_Swap32(src[i]); }, { - __m128i ints0 = _mm_loadu_si128((const __m128i*)&src[i]); - __m128i ints1 = _mm_loadu_si128((const __m128i*)&src[i + 4]); - __m128i ints2 = _mm_loadu_si128((const __m128i*)&src[i + 8]); - __m128i ints3 = _mm_loadu_si128((const __m128i*)&src[i + 12]); + __m128i ints0 = _mm_loadu_si128((const __m128i *)&src[i]); + __m128i ints1 = _mm_loadu_si128((const __m128i *)&src[i + 4]); + __m128i ints2 = _mm_loadu_si128((const __m128i *)&src[i + 8]); + __m128i ints3 = _mm_loadu_si128((const __m128i *)&src[i + 12]); ints0 = _mm_shuffle_epi8(ints0, shuffle); ints1 = _mm_shuffle_epi8(ints1, shuffle); ints2 = _mm_shuffle_epi8(ints2, shuffle); ints3 = _mm_shuffle_epi8(ints3, shuffle); - _mm_store_si128((__m128i*)&dst[i], ints0); - _mm_store_si128((__m128i*)&dst[i + 4], ints1); - _mm_store_si128((__m128i*)&dst[i + 8], ints2); - _mm_store_si128((__m128i*)&dst[i + 12], ints3); + _mm_store_si128((__m128i *)&dst[i], ints0); + _mm_store_si128((__m128i *)&dst[i + 4], ints1); + _mm_store_si128((__m128i *)&dst[i + 8], ints2); + _mm_store_si128((__m128i *)&dst[i + 12], ints3); }) } #endif @@ -774,41 +774,41 @@ static void SDL_Convert_F32_to_S32_NEON(Sint32 *dst, const float *src, int num_s fesetenv(&fenv); } -static void SDL_Convert_Swap16_NEON(Uint16* dst, const Uint16* src, int num_samples) +static void SDL_Convert_Swap16_NEON(Uint16 *dst, const Uint16 *src, int num_samples) { CONVERT_16_FWD({ dst[i] = SDL_Swap16(src[i]); }, { - uint8x16_t ints0 = vld1q_u8((const Uint8*)&src[i]); - uint8x16_t ints1 = vld1q_u8((const Uint8*)&src[i + 8]); + uint8x16_t ints0 = vld1q_u8((const Uint8 *)&src[i]); + uint8x16_t ints1 = vld1q_u8((const Uint8 *)&src[i + 8]); ints0 = vrev16q_u8(ints0); ints1 = vrev16q_u8(ints1); - vst1q_u8((Uint8*)&dst[i], ints0); - vst1q_u8((Uint8*)&dst[i + 8], ints1); + vst1q_u8((Uint8 *)&dst[i], ints0); + vst1q_u8((Uint8 *)&dst[i + 8], ints1); }) } -static void SDL_Convert_Swap32_NEON(Uint32* dst, const Uint32* src, int num_samples) +static void SDL_Convert_Swap32_NEON(Uint32 *dst, const Uint32 *src, int num_samples) { CONVERT_16_FWD({ dst[i] = SDL_Swap32(src[i]); }, { - uint8x16_t ints0 = vld1q_u8((const Uint8*)&src[i]); - uint8x16_t ints1 = vld1q_u8((const Uint8*)&src[i + 4]); - uint8x16_t ints2 = vld1q_u8((const Uint8*)&src[i + 8]); - uint8x16_t ints3 = vld1q_u8((const Uint8*)&src[i + 12]); + uint8x16_t ints0 = vld1q_u8((const Uint8 *)&src[i]); + uint8x16_t ints1 = vld1q_u8((const Uint8 *)&src[i + 4]); + uint8x16_t ints2 = vld1q_u8((const Uint8 *)&src[i + 8]); + uint8x16_t ints3 = vld1q_u8((const Uint8 *)&src[i + 12]); ints0 = vrev32q_u8(ints0); ints1 = vrev32q_u8(ints1); ints2 = vrev32q_u8(ints2); ints3 = vrev32q_u8(ints3); - vst1q_u8((Uint8*)&dst[i], ints0); - vst1q_u8((Uint8*)&dst[i + 4], ints1); - vst1q_u8((Uint8*)&dst[i + 8], ints2); - vst1q_u8((Uint8*)&dst[i + 12], ints3); + vst1q_u8((Uint8 *)&dst[i], ints0); + vst1q_u8((Uint8 *)&dst[i + 4], ints1); + vst1q_u8((Uint8 *)&dst[i + 8], ints2); + vst1q_u8((Uint8 *)&dst[i + 12], ints3); }) } @@ -839,8 +839,8 @@ static void (*SDL_Convert_F32_to_U8)(Uint8 *dst, const float *src, int num_sampl static void (*SDL_Convert_F32_to_S16)(Sint16 *dst, const float *src, int num_samples) = NULL; static void (*SDL_Convert_F32_to_S32)(Sint32 *dst, const float *src, int num_samples) = NULL; -static void (*SDL_Convert_Swap16)(Uint16* dst, const Uint16* src, int num_samples) = NULL; -static void (*SDL_Convert_Swap32)(Uint32* dst, const Uint32* src, int num_samples) = NULL; +static void (*SDL_Convert_Swap16)(Uint16 *dst, const Uint16 *src, int num_samples) = NULL; +static void (*SDL_Convert_Swap32)(Uint32 *dst, const Uint32 *src, int num_samples) = NULL; void ConvertAudioToFloat(float *dst, const void *src, int num_samples, SDL_AudioFormat src_fmt) { @@ -858,7 +858,7 @@ void ConvertAudioToFloat(float *dst, const void *src, int num_samples, SDL_Audio break; case SDL_AUDIO_S16 ^ SDL_AUDIO_MASK_BIG_ENDIAN: - SDL_Convert_Swap16((Uint16*) dst, (const Uint16*) src, num_samples); + SDL_Convert_Swap16((Uint16 *)dst, (const Uint16 *)src, num_samples); SDL_Convert_S16_to_F32(dst, (const Sint16 *) dst, num_samples); break; @@ -867,12 +867,12 @@ void ConvertAudioToFloat(float *dst, const void *src, int num_samples, SDL_Audio break; case SDL_AUDIO_S32 ^ SDL_AUDIO_MASK_BIG_ENDIAN: - SDL_Convert_Swap32((Uint32*) dst, (const Uint32*) src, num_samples); + SDL_Convert_Swap32((Uint32 *)dst, (const Uint32 *)src, num_samples); SDL_Convert_S32_to_F32(dst, (const Sint32 *) dst, num_samples); break; case SDL_AUDIO_F32 ^ SDL_AUDIO_MASK_BIG_ENDIAN: - SDL_Convert_Swap32((Uint32*) dst, (const Uint32*) src, num_samples); + SDL_Convert_Swap32((Uint32 *)dst, (const Uint32 *)src, num_samples); break; default: SDL_assert(!"Unexpected audio format!"); break; @@ -896,7 +896,7 @@ void ConvertAudioFromFloat(void *dst, const float *src, int num_samples, SDL_Aud case SDL_AUDIO_S16 ^ SDL_AUDIO_MASK_BIG_ENDIAN: SDL_Convert_F32_to_S16((Sint16 *) dst, src, num_samples); - SDL_Convert_Swap16((Uint16*) dst, (const Uint16*) dst, num_samples); + SDL_Convert_Swap16((Uint16 *)dst, (const Uint16 *)dst, num_samples); break; case SDL_AUDIO_S32: @@ -905,22 +905,22 @@ void ConvertAudioFromFloat(void *dst, const float *src, int num_samples, SDL_Aud case SDL_AUDIO_S32 ^ SDL_AUDIO_MASK_BIG_ENDIAN: SDL_Convert_F32_to_S32((Sint32 *) dst, src, num_samples); - SDL_Convert_Swap32((Uint32*) dst, (const Uint32*) dst, num_samples); + SDL_Convert_Swap32((Uint32 *)dst, (const Uint32 *)dst, num_samples); break; case SDL_AUDIO_F32 ^ SDL_AUDIO_MASK_BIG_ENDIAN: - SDL_Convert_Swap32((Uint32*) dst, (const Uint32*) src, num_samples); + SDL_Convert_Swap32((Uint32 *)dst, (const Uint32 *)src, num_samples); break; default: SDL_assert(!"Unexpected audio format!"); break; } } -void ConvertAudioSwapEndian(void* dst, const void* src, int num_samples, int bitsize) +void ConvertAudioSwapEndian(void *dst, const void *src, int num_samples, int bitsize) { switch (bitsize) { - case 16: SDL_Convert_Swap16((Uint16*) dst, (const Uint16*) src, num_samples); break; - case 32: SDL_Convert_Swap32((Uint32*) dst, (const Uint32*) src, num_samples); break; + case 16: SDL_Convert_Swap16((Uint16 *)dst, (const Uint16 *)src, num_samples); break; + case 32: SDL_Convert_Swap32((Uint32 *)dst, (const Uint32 *)src, num_samples); break; default: SDL_assert(!"Unexpected audio format!"); break; } } diff --git a/libs/SDL3/src/audio/SDL_sysaudio.h b/libs/SDL3/src/audio/SDL_sysaudio.h index 4a88bd23..603eaab1 100644 --- a/libs/SDL3/src/audio/SDL_sysaudio.h +++ b/libs/SDL3/src/audio/SDL_sysaudio.h @@ -112,7 +112,7 @@ extern void SDL_AudioThreadFinalize(SDL_AudioDevice *device); extern void ConvertAudioToFloat(float *dst, const void *src, int num_samples, SDL_AudioFormat src_fmt); extern void ConvertAudioFromFloat(void *dst, const float *src, int num_samples, SDL_AudioFormat dst_fmt); -extern void ConvertAudioSwapEndian(void* dst, const void* src, int num_samples, int bitsize); +extern void ConvertAudioSwapEndian(void *dst, const void *src, int num_samples, int bitsize); extern bool SDL_ChannelMapIsDefault(const int *map, int channels); extern bool SDL_ChannelMapIsBogus(const int *map, int channels); @@ -121,7 +121,7 @@ extern bool SDL_ChannelMapIsBogus(const int *map, int channels); extern void ConvertAudio(int num_frames, const void *src, SDL_AudioFormat src_format, int src_channels, const int *src_map, void *dst, SDL_AudioFormat dst_format, int dst_channels, const int *dst_map, - void* scratch, float gain); + void *scratch, float gain); // Compare two SDL_AudioSpecs, return true if they match exactly. // Using SDL_memcmp directly isn't safe, since potential padding might not be initialized. @@ -201,7 +201,7 @@ struct SDL_AudioQueue; // forward decl. struct SDL_AudioStream { - SDL_Mutex* lock; + SDL_Mutex *lock; SDL_PropertiesID props; @@ -217,7 +217,7 @@ struct SDL_AudioStream float freq_ratio; float gain; - struct SDL_AudioQueue* queue; + struct SDL_AudioQueue *queue; SDL_AudioSpec input_spec; // The spec of input data currently being processed int *input_chmap; @@ -264,6 +264,13 @@ struct SDL_LogicalAudioDevice // true if device was opened with SDL_OpenAudioDeviceStream (so it forbids binding changes, etc). bool simplified; + // If non-NULL, callback into the app that alerts it to start/end of device iteration. + SDL_AudioIterationCallback iteration_start; + SDL_AudioIterationCallback iteration_end; + + // App-supplied pointer for iteration callbacks. + void *iteration_userdata; + // If non-NULL, callback into the app that lets them access the final postmix buffer. SDL_AudioPostmixCallback postmix; @@ -386,6 +393,7 @@ extern AudioBootStrap PS2AUDIO_bootstrap; extern AudioBootStrap PSPAUDIO_bootstrap; extern AudioBootStrap VITAAUD_bootstrap; extern AudioBootStrap N3DSAUDIO_bootstrap; +extern AudioBootStrap NGAGEAUDIO_bootstrap; extern AudioBootStrap EMSCRIPTENAUDIO_bootstrap; extern AudioBootStrap QSAAUDIO_bootstrap; diff --git a/libs/SDL3/src/audio/aaudio/SDL_aaudio.c b/libs/SDL3/src/audio/aaudio/SDL_aaudio.c index 3360bec9..5436be07 100644 --- a/libs/SDL3/src/audio/aaudio/SDL_aaudio.c +++ b/libs/SDL3/src/audio/aaudio/SDL_aaudio.c @@ -308,6 +308,12 @@ static bool BuildAAudioStream(SDL_AudioDevice *device) ctx.AAudioStreamBuilder_setFormat(builder, format); ctx.AAudioStreamBuilder_setSampleRate(builder, device->spec.freq); ctx.AAudioStreamBuilder_setChannelCount(builder, device->spec.channels); + + // If no specific buffer size has been requested, the device will pick the optimal + if(SDL_GetHint(SDL_HINT_AUDIO_DEVICE_SAMPLE_FRAMES)) { + ctx.AAudioStreamBuilder_setBufferCapacityInFrames(builder, 2 * device->sample_frames); // AAudio requires that the buffer capacity is at least + ctx.AAudioStreamBuilder_setFramesPerDataCallback(builder, device->sample_frames); // twice the size of the data callback buffer size + } const aaudio_direction_t direction = (recording ? AAUDIO_DIRECTION_INPUT : AAUDIO_DIRECTION_OUTPUT); ctx.AAudioStreamBuilder_setDirection(builder, direction); @@ -366,7 +372,7 @@ static bool BuildAAudioStream(SDL_AudioDevice *device) hidden->processed_bytes = 0; hidden->callback_bytes = 0; - hidden->semaphore = SDL_CreateSemaphore(recording ? 0 : hidden->num_buffers); + hidden->semaphore = SDL_CreateSemaphore(recording ? 0 : hidden->num_buffers - 1); if (!hidden->semaphore) { LOGI("SDL Failed SDL_CreateSemaphore %s recording:%d", SDL_GetError(), recording); return false; diff --git a/libs/SDL3/src/audio/aaudio/SDL_aaudiofuncs.h b/libs/SDL3/src/audio/aaudio/SDL_aaudiofuncs.h index 02988212..1d9f7104 100644 --- a/libs/SDL3/src/audio/aaudio/SDL_aaudiofuncs.h +++ b/libs/SDL3/src/audio/aaudio/SDL_aaudiofuncs.h @@ -31,7 +31,7 @@ SDL_PROC_UNUSED(void, AAudioStreamBuilder_setSamplesPerFrame, (AAudioStreamBuild SDL_PROC(void, AAudioStreamBuilder_setFormat, (AAudioStreamBuilder * builder, aaudio_format_t format)) SDL_PROC_UNUSED(void, AAudioStreamBuilder_setSharingMode, (AAudioStreamBuilder * builder, aaudio_sharing_mode_t sharingMode)) SDL_PROC(void, AAudioStreamBuilder_setDirection, (AAudioStreamBuilder * builder, aaudio_direction_t direction)) -SDL_PROC_UNUSED(void, AAudioStreamBuilder_setBufferCapacityInFrames, (AAudioStreamBuilder * builder, int32_t numFrames)) +SDL_PROC(void, AAudioStreamBuilder_setBufferCapacityInFrames, (AAudioStreamBuilder * builder, int32_t numFrames)) SDL_PROC(void, AAudioStreamBuilder_setPerformanceMode, (AAudioStreamBuilder * builder, aaudio_performance_mode_t mode)) SDL_PROC_UNUSED(void, AAudioStreamBuilder_setUsage, (AAudioStreamBuilder * builder, aaudio_usage_t usage)) // API 28 SDL_PROC_UNUSED(void, AAudioStreamBuilder_setContentType, (AAudioStreamBuilder * builder, aaudio_content_type_t contentType)) // API 28 diff --git a/libs/SDL3/src/audio/alsa/SDL_alsa_audio.c b/libs/SDL3/src/audio/alsa/SDL_alsa_audio.c index badbc43e..4ba156e3 100644 --- a/libs/SDL3/src/audio/alsa/SDL_alsa_audio.c +++ b/libs/SDL3/src/audio/alsa/SDL_alsa_audio.c @@ -38,6 +38,7 @@ #include "../SDL_sysaudio.h" #include "SDL_alsa_audio.h" +#include "../../core/linux/SDL_udev.h" #if SDL_ALSA_DEBUG #define LOGDEBUG(...) SDL_LogDebug(SDL_LOG_CATEGORY_AUDIO, "ALSA: " __VA_ARGS__) @@ -87,7 +88,7 @@ static int (*ALSA_snd_device_name_free_hint)(void **); static snd_pcm_sframes_t (*ALSA_snd_pcm_avail)(snd_pcm_t *); static size_t (*ALSA_snd_ctl_card_info_sizeof)(void); static size_t (*ALSA_snd_pcm_info_sizeof)(void); -static int (*ALSA_snd_card_next)(int*); +static int (*ALSA_snd_card_next)(int *); static int (*ALSA_snd_ctl_open)(snd_ctl_t **,const char *,int); static int (*ALSA_snd_ctl_close)(snd_ctl_t *); static int (*ALSA_snd_ctl_card_info)(snd_ctl_t *, snd_ctl_card_info_t *); @@ -348,28 +349,25 @@ static char *get_pcm_str(void *handle) // This function waits until it is possible to write a full sound buffer static bool ALSA_WaitDevice(SDL_AudioDevice *device) { - const int fulldelay = (int) ((((Uint64) device->sample_frames) * 1000) / device->spec.freq); - const int delay = SDL_max(fulldelay, 10); + const int sample_frames = device->sample_frames; + const int fulldelay = (int) ((((Uint64) sample_frames) * 1000) / device->spec.freq); + const int delay = SDL_clamp(fulldelay, 1, 5); while (!SDL_GetAtomicInt(&device->shutdown)) { - const int rc = ALSA_snd_pcm_wait(device->hidden->pcm, delay); - if (rc < 0 && (rc != -EAGAIN)) { + const int rc = ALSA_snd_pcm_avail(device->hidden->pcm); + if (rc < 0) { const int status = ALSA_snd_pcm_recover(device->hidden->pcm, rc, 0); if (status < 0) { // Hmm, not much we can do - abort - SDL_LogError(SDL_LOG_CATEGORY_AUDIO, "ALSA: snd_pcm_wait failed (unrecoverable): %s", ALSA_snd_strerror(rc)); + SDL_LogError(SDL_LOG_CATEGORY_AUDIO, "ALSA wait failed (unrecoverable): %s", ALSA_snd_strerror(rc)); return false; } - continue; } - - if (rc > 0) { - break; // ready to go! + if (rc >= sample_frames) { + break; } - - // Timed out! Make sure we aren't shutting down and then wait again. + SDL_Delay(delay); } - return true; } @@ -431,8 +429,11 @@ static int ALSA_RecordDevice(SDL_AudioDevice *device, void *buffer, int buflen) SDL_assert((buflen % frame_size) == 0); const snd_pcm_sframes_t total_available = ALSA_snd_pcm_avail(device->hidden->pcm); - const int total_frames = SDL_min(buflen / frame_size, total_available); + if (total_available == 0) { + return 0; // go back to WaitDevice and try again. + } + const int total_frames = SDL_min(buflen / frame_size, total_available); const int rc = ALSA_snd_pcm_readi(device->hidden->pcm, buffer, total_frames); SDL_assert(rc != -EAGAIN); // assuming this can't happen if we used snd_pcm_wait and queried for available space. snd_pcm_recover won't handle it! @@ -671,7 +672,7 @@ static void swizzle_map_compute(const struct ALSA_pcm_cfg_ctx *ctx, int *swizzle static int alsa_chmap_install(struct ALSA_pcm_cfg_ctx *ctx, const unsigned int *chmap) { bool isstack; - snd_pcm_chmap_t *chmap_to_install = (snd_pcm_chmap_t*)SDL_small_alloc(unsigned int, 1 + ctx->chans_n, &isstack); + snd_pcm_chmap_t *chmap_to_install = (snd_pcm_chmap_t *)SDL_small_alloc(unsigned int, 1 + ctx->chans_n, &isstack); if (!chmap_to_install) { return -1; } @@ -1215,7 +1216,7 @@ static int hotplug_device_process(snd_ctl_t *ctl, snd_ctl_card_info_t *ctl_card_ unsigned int subdev_idx = 0; const bool recording = direction == SND_PCM_STREAM_CAPTURE ? true : false; // used for the unicity of the device bool isstack; - snd_pcm_info_t *pcm_info = (snd_pcm_info_t*)SDL_small_alloc(Uint8, ALSA_snd_pcm_info_sizeof(), &isstack); + snd_pcm_info_t *pcm_info = (snd_pcm_info_t *)SDL_small_alloc(Uint8, ALSA_snd_pcm_info_sizeof(), &isstack); SDL_memset(pcm_info, 0, ALSA_snd_pcm_info_sizeof()); while (true) { @@ -1445,6 +1446,65 @@ static int SDLCALL ALSA_HotplugThread(void *arg) } #endif +#ifdef SDL_USE_LIBUDEV + +static bool udev_initialized; + +static void ALSA_udev_callback(SDL_UDEV_deviceevent udev_type, int udev_class, const char *devpath) +{ + if (!devpath) { + return; + } + + switch (udev_type) { + case SDL_UDEV_DEVICEADDED: + ALSA_HotplugIteration(NULL, NULL); + break; + + case SDL_UDEV_DEVICEREMOVED: + ALSA_HotplugIteration(NULL, NULL); + break; + + default: + break; + } +} + +static bool ALSA_start_udev() +{ + udev_initialized = SDL_UDEV_Init(); + if (udev_initialized) { + // Set up the udev callback + if (!SDL_UDEV_AddCallback(ALSA_udev_callback)) { + SDL_UDEV_Quit(); + udev_initialized = false; + } + } + return udev_initialized; +} + +static void ALSA_stop_udev() +{ + if (udev_initialized) { + SDL_UDEV_DelCallback(ALSA_udev_callback); + SDL_UDEV_Quit(); + udev_initialized = false; + } +} + +#else + +static bool ALSA_start_udev() +{ + return false; +} + +static void ALSA_stop_udev() +{ +} + +#endif // SDL_USE_LIBUDEV + static void ALSA_DetectDevices(SDL_AudioDevice **default_playback, SDL_AudioDevice **default_recording) { ALSA_guess_device_prefix(); @@ -1454,17 +1514,19 @@ static void ALSA_DetectDevices(SDL_AudioDevice **default_playback, SDL_AudioDevi bool has_default_playback = false, has_default_recording = false; ALSA_HotplugIteration(&has_default_playback, &has_default_recording); // run once now before a thread continues to check. if (has_default_playback) { - *default_playback = SDL_AddAudioDevice(/*recording=*/false, "ALSA default playback device", NULL, (void*)&default_playback_handle); + *default_playback = SDL_AddAudioDevice(/*recording=*/false, "ALSA default playback device", NULL, (void *)&default_playback_handle); } if (has_default_recording) { - *default_recording = SDL_AddAudioDevice(/*recording=*/true, "ALSA default recording device", NULL, (void*)&default_recording_handle); + *default_recording = SDL_AddAudioDevice(/*recording=*/true, "ALSA default recording device", NULL, (void *)&default_recording_handle); } + if (!ALSA_start_udev()) { #if SDL_ALSA_HOTPLUG_THREAD - SDL_SetAtomicInt(&ALSA_hotplug_shutdown, 0); - ALSA_hotplug_thread = SDL_CreateThread(ALSA_HotplugThread, "SDLHotplugALSA", NULL); - // if the thread doesn't spin, oh well, you just don't get further hotplug events. + SDL_SetAtomicInt(&ALSA_hotplug_shutdown, 0); + ALSA_hotplug_thread = SDL_CreateThread(ALSA_HotplugThread, "SDLHotplugALSA", NULL); + // if the thread doesn't spin, oh well, you just don't get further hotplug events. #endif + } } static void ALSA_DeinitializeStart(void) @@ -1479,6 +1541,7 @@ static void ALSA_DeinitializeStart(void) ALSA_hotplug_thread = NULL; } #endif + ALSA_stop_udev(); // Shutting down! Clean up any data we've gathered. for (dev = hotplug_devices; dev; dev = next) { diff --git a/libs/SDL3/src/audio/coreaudio/SDL_coreaudio.m b/libs/SDL3/src/audio/coreaudio/SDL_coreaudio.m index 57b19c72..e7d9e58a 100644 --- a/libs/SDL3/src/audio/coreaudio/SDL_coreaudio.m +++ b/libs/SDL3/src/audio/coreaudio/SDL_coreaudio.m @@ -341,7 +341,7 @@ static void ResumeAudioDevices(void) static void InterruptionBegin(SDL_AudioDevice *device) { - if (device != NULL && device->hidden->audioQueue != NULL) { + if (device != NULL && device->hidden != NULL && device->hidden->audioQueue != NULL) { device->hidden->interrupted = true; AudioQueuePause(device->hidden->audioQueue); } @@ -366,7 +366,7 @@ static void InterruptionEnd(SDL_AudioDevice *device) { @synchronized(self) { NSNumber *type = note.userInfo[AVAudioSessionInterruptionTypeKey]; - if (type.unsignedIntegerValue == AVAudioSessionInterruptionTypeBegan) { + if (type && (type.unsignedIntegerValue == AVAudioSessionInterruptionTypeBegan)) { InterruptionBegin(self.device); } else { InterruptionEnd(self.device); diff --git a/libs/SDL3/src/audio/disk/SDL_diskaudio.c b/libs/SDL3/src/audio/disk/SDL_diskaudio.c index 9e05478c..34909666 100644 --- a/libs/SDL3/src/audio/disk/SDL_diskaudio.c +++ b/libs/SDL3/src/audio/disk/SDL_diskaudio.c @@ -100,6 +100,16 @@ static const char *get_filename(const bool recording) return devname; } +static const char *AudioFormatString(SDL_AudioFormat fmt) +{ + const char *str = SDL_GetAudioFormatName(fmt); + SDL_assert(str); + if (SDL_strncmp(str, "SDL_AUDIO_", 10) == 0) { + str += 10; // so we return "S8" instead of "SDL_AUDIO_S8", etc. + } + return str; +} + static bool DISKAUDIO_OpenDevice(SDL_AudioDevice *device) { bool recording = device->recording; @@ -136,7 +146,9 @@ static bool DISKAUDIO_OpenDevice(SDL_AudioDevice *device) } SDL_LogCritical(SDL_LOG_CATEGORY_AUDIO, "You are using the SDL disk i/o audio driver!"); - SDL_LogCritical(SDL_LOG_CATEGORY_AUDIO, " %s file [%s].", recording ? "Reading from" : "Writing to", fname); + SDL_LogCritical(SDL_LOG_CATEGORY_AUDIO, " %s file [%s], format=%s channels=%d freq=%d.", + recording ? "Reading from" : "Writing to", fname, + AudioFormatString(device->spec.format), device->spec.channels, device->spec.freq); return true; // We're ready to rock and roll. :-) } diff --git a/libs/SDL3/src/audio/emscripten/SDL_emscriptenaudio.c b/libs/SDL3/src/audio/emscripten/SDL_emscriptenaudio.c index 55fb5b49..4be91910 100644 --- a/libs/SDL3/src/audio/emscripten/SDL_emscriptenaudio.c +++ b/libs/SDL3/src/audio/emscripten/SDL_emscriptenaudio.c @@ -56,7 +56,7 @@ static bool EMSCRIPTENAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buf } for (var j = 0; j < $1; ++j) { - channelData[j] = HEAPF32[buf + (j*numChannels + c)]; + channelData[j] = HEAPF32[buf + (j * numChannels + c)]; } } }, buffer, buffer_size / framelen); @@ -189,7 +189,7 @@ static bool EMSCRIPTENAUDIO_OpenDevice(SDL_AudioDevice *device) } // limit to native freq - device->spec.freq = EM_ASM_INT({ return Module['SDL3'].audioContext.sampleRate; }); + device->spec.freq = MAIN_THREAD_EM_ASM_INT({ return Module['SDL3'].audioContext.sampleRate; }); device->sample_frames = SDL_GetDefaultSampleFramesFromFreq(device->spec.freq) * 2; // double the buffer size, some browsers need more, and we'll just have to live with the latency. SDL_UpdatedAudioDeviceFormat(device); diff --git a/libs/SDL3/src/audio/ngage/SDL_ngageaudio.c b/libs/SDL3/src/audio/ngage/SDL_ngageaudio.c new file mode 100644 index 00000000..3595bd70 --- /dev/null +++ b/libs/SDL3/src/audio/ngage/SDL_ngageaudio.c @@ -0,0 +1,103 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +#include "SDL_internal.h" + +#ifdef SDL_AUDIO_DRIVER_NGAGE + +#include "../SDL_sysaudio.h" +#include "SDL_ngageaudio.h" + +static SDL_AudioDevice *devptr = NULL; + +SDL_AudioDevice *NGAGE_GetAudioDeviceAddr() +{ + return devptr; +} + +static bool NGAGEAUDIO_OpenDevice(SDL_AudioDevice *device) +{ + SDL_PrivateAudioData *phdata = SDL_calloc(1, sizeof(SDL_PrivateAudioData)); + if (!phdata) { + SDL_OutOfMemory(); + return false; + } + device->hidden = phdata; + + phdata->buffer = SDL_calloc(1, device->buffer_size); + if (!phdata->buffer) { + SDL_OutOfMemory(); + SDL_free(phdata); + return false; + } + devptr = device; + + // Since the phone can change the sample rate during a phone call, + // we set the sample rate to 8KHz to be safe. Even though it + // might be possible to adjust the sample rate dynamically, it's + // not supported by the current implementation. + + device->spec.format = SDL_AUDIO_S16LE; + device->spec.channels = 1; + device->spec.freq = 8000; + + SDL_UpdatedAudioDeviceFormat(device); + + return true; +} + +static Uint8 *NGAGEAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size) +{ + SDL_PrivateAudioData *phdata = (SDL_PrivateAudioData *)device->hidden; + if (!phdata) { + *buffer_size = 0; + return 0; + } + + *buffer_size = device->buffer_size; + return phdata->buffer; +} + +static void NGAGEAUDIO_CloseDevice(SDL_AudioDevice *device) +{ + if (device->hidden) { + SDL_free(device->hidden->buffer); + SDL_free(device->hidden); + } + + return; +} + +static bool NGAGEAUDIO_Init(SDL_AudioDriverImpl *impl) +{ + impl->OpenDevice = NGAGEAUDIO_OpenDevice; + impl->GetDeviceBuf = NGAGEAUDIO_GetDeviceBuf; + impl->CloseDevice = NGAGEAUDIO_CloseDevice; + + impl->ProvidesOwnCallbackThread = true; + impl->OnlyHasDefaultPlaybackDevice = true; + + return true; +} + +AudioBootStrap NGAGEAUDIO_bootstrap = { "N-Gage", "N-Gage audio driver", NGAGEAUDIO_Init, false }; + +#endif // SDL_AUDIO_DRIVER_NGAGE diff --git a/libs/SDL3/src/audio/ngage/SDL_ngageaudio.cpp b/libs/SDL3/src/audio/ngage/SDL_ngageaudio.cpp new file mode 100644 index 00000000..9acf030c --- /dev/null +++ b/libs/SDL3/src/audio/ngage/SDL_ngageaudio.cpp @@ -0,0 +1,368 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "SDL_ngageaudio.h" +#include "../SDL_sysaudio.h" +#include "SDL_internal.h" + +#ifdef __cplusplus +} +#endif + +#ifdef SDL_AUDIO_DRIVER_NGAGE + +#include "SDL_ngageaudio.hpp" + +CAudio::CAudio() : CActive(EPriorityStandard), iBufDes(NULL, 0) {} + +CAudio *CAudio::NewL(TInt aLatency) +{ + CAudio *self = new (ELeave) CAudio(); + CleanupStack::PushL(self); + self->ConstructL(aLatency); + CleanupStack::Pop(self); + return self; +} + +void CAudio::ConstructL(TInt aLatency) +{ + CActiveScheduler::Add(this); + User::LeaveIfError(iTimer.CreateLocal()); + iTimerCreated = ETrue; + + iStream = CMdaAudioOutputStream::NewL(*this); + if (!iStream) { + SDL_Log("Error: Failed to create audio stream"); + User::Leave(KErrNoMemory); + } + + iLatency = aLatency; + iLatencySamples = aLatency * 8; // 8kHz. + + // Determine minimum and maximum number of samples to write with one + // WriteL request. + iMinWrite = iLatencySamples / 8; + iMaxWrite = iLatencySamples / 2; + + // Set defaults. + iState = EStateNone; + iTimerCreated = EFalse; + iTimerActive = EFalse; +} + +CAudio::~CAudio() +{ + if (iStream) { + iStream->Stop(); + + while (iState != EStateDone) { + User::After(100000); // 100ms. + } + + delete iStream; + } +} + +void CAudio::Start() +{ + if (iStream) { + // Set to 8kHz mono audio. + iStreamSettings.iChannels = TMdaAudioDataSettings::EChannelsMono; + iStreamSettings.iSampleRate = TMdaAudioDataSettings::ESampleRate8000Hz; + iStream->Open(&iStreamSettings); + iState = EStateOpening; + } else { + SDL_Log("Error: Failed to open audio stream"); + } +} + +// Feeds more processed data to the audio stream. +void CAudio::Feed() +{ + // If a WriteL is already in progress, or we aren't even playing; + // do nothing! + if ((iState != EStateWriting) && (iState != EStatePlaying)) { + return; + } + + // Figure out the number of samples that really have been played + // through the output. + TTimeIntervalMicroSeconds pos = iStream->Position(); + + TInt played = 8 * (pos.Int64() / TInt64(1000)).GetTInt(); // 8kHz. + + played += iBaseSamplesPlayed; + + // Determine the difference between the number of samples written to + // CMdaAudioOutputStream and the number of samples it has played. + // The difference is the amount of data in the buffers. + if (played < 0) { + played = 0; + } + + TInt buffered = iSamplesWritten - played; + if (buffered < 0) { + buffered = 0; + } + + if (iState == EStateWriting) { + return; + } + + // The trick for low latency: Do not let the buffers fill up beyond the + // latency desired! We write as many samples as the difference between + // the latency target (in samples) and the amount of data buffered. + TInt samplesToWrite = iLatencySamples - buffered; + + // Do not write very small blocks. This should improve efficiency, since + // writes to the streaming API are likely to be expensive. + if (samplesToWrite < iMinWrite) { + // Not enough data to write, set up a timer to fire after a while. + // Try againwhen it expired. + if (iTimerActive) { + return; + } + iTimerActive = ETrue; + SetActive(); + iTimer.After(iStatus, (1000 * iLatency) / 8); + return; + } + + // Do not write more than the set number of samples at once. + int numSamples = samplesToWrite; + if (numSamples > iMaxWrite) { + numSamples = iMaxWrite; + } + + SDL_AudioDevice *device = NGAGE_GetAudioDeviceAddr(); + if (device) { + SDL_PrivateAudioData *phdata = (SDL_PrivateAudioData *)device->hidden; + + iBufDes.Set(phdata->buffer, 2 * numSamples, 2 * numSamples); + iStream->WriteL(iBufDes); + iState = EStateWriting; + + // Keep track of the number of samples written (for latency calculations). + iSamplesWritten += numSamples; + } else { + // Output device not ready yet. Let's go for another round. + if (iTimerActive) { + return; + } + iTimerActive = ETrue; + SetActive(); + iTimer.After(iStatus, (1000 * iLatency) / 8); + } +} + +void CAudio::RunL() +{ + iTimerActive = EFalse; + Feed(); +} + +void CAudio::DoCancel() +{ + iTimerActive = EFalse; + iTimer.Cancel(); +} + +void CAudio::StartThread() +{ + TInt heapMinSize = 8192; // 8 KB initial heap size. + TInt heapMaxSize = 1024 * 1024; // 1 MB maximum heap size. + + TInt err = iProcess.Create(_L("ProcessThread"), ProcessThreadCB, KDefaultStackSize * 2, heapMinSize, heapMaxSize, this); + if (err == KErrNone) { + iProcess.SetPriority(EPriorityLess); + iProcess.Resume(); + } else { + SDL_Log("Error: Failed to create audio processing thread: %d", err); + } +} + +void CAudio::StopThread() +{ + if (iStreamStarted) { + iProcess.Kill(KErrNone); + iProcess.Close(); + iStreamStarted = EFalse; + } +} + +TInt CAudio::ProcessThreadCB(TAny *aPtr) +{ + CAudio *self = static_cast(aPtr); + SDL_AudioDevice *device = NGAGE_GetAudioDeviceAddr(); + + while (self->iStreamStarted) { + if (device) { + SDL_PlaybackAudioThreadIterate(device); + } else { + device = NGAGE_GetAudioDeviceAddr(); + } + User::After(100000); // 100ms. + } + return KErrNone; +} + +void CAudio::MaoscOpenComplete(TInt aError) +{ + if (aError == KErrNone) { + iStream->SetVolume(1); + iStreamStarted = ETrue; + StartThread(); + + } else { + SDL_Log("Error: Failed to open audio stream: %d", aError); + } +} + +void CAudio::MaoscBufferCopied(TInt aError, const TDesC8 & /*aBuffer*/) +{ + if (aError == KErrNone) { + iState = EStatePlaying; + Feed(); + } else if (aError == KErrAbort) { + // The stream has been stopped. + iState = EStateDone; + } else { + SDL_Log("Error: Failed to copy audio buffer: %d", aError); + } +} + +void CAudio::MaoscPlayComplete(TInt aError) +{ + // If we finish due to an underflow, we'll need to restart playback. + // Normally KErrUnderlow is raised at stream end, but in our case the API + // should never see the stream end -- we are continuously feeding it more + // data! Many underflow errors mean that the latency target is too low. + if (aError == KErrUnderflow) { + // The number of samples played gets resetted to zero when we restart + // playback after underflow. + iBaseSamplesPlayed = iSamplesWritten; + + iStream->Stop(); + Cancel(); + + iStream->SetAudioPropertiesL(TMdaAudioDataSettings::ESampleRate8000Hz, TMdaAudioDataSettings::EChannelsMono); + + iState = EStatePlaying; + Feed(); + return; + + } else if (aError != KErrNone) { + // Handle error. + } + + // We shouldn't get here. + SDL_Log("%s: %d", __FUNCTION__, aError); +} + +static TBool gAudioRunning; + +TBool AudioIsReady() +{ + return gAudioRunning; +} + +TInt AudioThreadCB(TAny *aParams) +{ + CTrapCleanup *cleanup = CTrapCleanup::New(); + if (!cleanup) { + return KErrNoMemory; + } + + CActiveScheduler *scheduler = new CActiveScheduler(); + if (!scheduler) { + delete cleanup; + return KErrNoMemory; + } + + CActiveScheduler::Install(scheduler); + + TRAPD(err, + { + TInt latency = *(TInt *)aParams; + CAudio *audio = CAudio::NewL(latency); + CleanupStack::PushL(audio); + + gAudioRunning = ETrue; + audio->Start(); + TBool once = EFalse; + + while (gAudioRunning) { + // Allow active scheduler to process any events. + TInt error; + CActiveScheduler::RunIfReady(error, CActive::EPriorityIdle); + + if (!once) { + SDL_AudioDevice *device = NGAGE_GetAudioDeviceAddr(); + if (device) { + // Stream ready; start feeding audio data. + // After feeding it once, the callbacks will take over. + audio->iState = CAudio::EStatePlaying; + audio->Feed(); + once = ETrue; + } + } + + User::After(100000); // 100ms. + } + + CleanupStack::PopAndDestroy(audio); + }); + + delete scheduler; + delete cleanup; + return err; +} + +RThread audioThread; + +void InitAudio(TInt *aLatency) +{ + _LIT(KAudioThreadName, "AudioThread"); + + TInt err = audioThread.Create(KAudioThreadName, AudioThreadCB, KDefaultStackSize, 0, aLatency); + if (err != KErrNone) { + User::Leave(err); + } + + audioThread.Resume(); +} + +void DeinitAudio() +{ + gAudioRunning = EFalse; + + TRequestStatus status; + audioThread.Logon(status); + User::WaitForRequest(status); + + audioThread.Close(); +} + +#endif // SDL_AUDIO_DRIVER_NGAGE diff --git a/libs/SDL3/src/video/vita/SDL_vitagl_pvr_c.h b/libs/SDL3/src/audio/ngage/SDL_ngageaudio.h similarity index 71% rename from libs/SDL3/src/video/vita/SDL_vitagl_pvr_c.h rename to libs/SDL3/src/audio/ngage/SDL_ngageaudio.h index 118602d6..dda2c91e 100644 --- a/libs/SDL3/src/video/vita/SDL_vitagl_pvr_c.h +++ b/libs/SDL3/src/audio/ngage/SDL_ngageaudio.h @@ -18,14 +18,27 @@ misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. */ +#include "SDL_internal.h" -#ifndef SDL_vitagl_pvr_c_h_ -#define SDL_vitagl_pvr_c_h_ +#ifndef SDL_ngageaudio_h +#define SDL_ngageaudio_h -#include "SDL_vitavideo.h" +typedef struct SDL_PrivateAudioData +{ + Uint8 *buffer; -extern SDL_GLContext VITA_GL_CreateContext(SDL_VideoDevice *_this, SDL_Window *window); -extern int VITA_GL_LoadLibrary(SDL_VideoDevice *_this, const char *path); -extern SDL_FunctionPointer VITA_GL_GetProcAddress(SDL_VideoDevice *_this, const char *proc); +} SDL_PrivateAudioData; -#endif // SDL_vitagl_pvr_c_h_ +#ifdef __cplusplus +extern "C" { +#endif + +#include "../SDL_sysaudio.h" + +SDL_AudioDevice *NGAGE_GetAudioDeviceAddr(); + +#ifdef __cplusplus +} +#endif + +#endif // SDL_ngageaudio_h diff --git a/libs/SDL3/src/audio/ngage/SDL_ngageaudio.hpp b/libs/SDL3/src/audio/ngage/SDL_ngageaudio.hpp new file mode 100644 index 00000000..e0635f5a --- /dev/null +++ b/libs/SDL3/src/audio/ngage/SDL_ngageaudio.hpp @@ -0,0 +1,98 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +#ifndef SDL_ngageaudio_hpp +#define SDL_ngageaudio_hpp + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#include "../SDL_sysaudio.h" +#include "SDL_ngageaudio.h" + +#ifdef __cplusplus +} +#endif + +TBool AudioIsReady(); +void InitAudio(TInt *aLatency); +void DeinitAudio(); + +class CAudio : public CActive, public MMdaAudioOutputStreamCallback +{ + public: + static CAudio *NewL(TInt aLatency); + ~CAudio(); + + void ConstructL(TInt aLatency); + void Start(); + void Feed(); + + void RunL(); + void DoCancel(); + + static TInt ProcessThreadCB(TAny * /*aPtr*/); + + // From MMdaAudioOutputStreamCallback + void MaoscOpenComplete(TInt aError); + void MaoscBufferCopied(TInt aError, const TDesC8 &aBuffer); + void MaoscPlayComplete(TInt aError); + + enum + { + EStateNone = 0, + EStateOpening, + EStatePlaying, + EStateWriting, + EStateDone + } iState; + + private: + CAudio(); + void StartThread(); + void StopThread(); + + CMdaAudioOutputStream *iStream; + TMdaAudioDataSettings iStreamSettings; + TBool iStreamStarted; + + TPtr8 iBufDes; // Descriptor for the buffer. + TInt iLatency; // Latency target in ms + TInt iLatencySamples; // Latency target in samples. + TInt iMinWrite; // Min number of samples to write per turn. + TInt iMaxWrite; // Max number of samples to write per turn. + TInt iBaseSamplesPlayed; // amples played before last restart. + TInt iSamplesWritten; // Number of samples written so far. + + RTimer iTimer; + TBool iTimerCreated; + TBool iTimerActive; + + RThread iProcess; +}; + +#endif // SDL_ngageaudio_hpp \ No newline at end of file diff --git a/libs/SDL3/src/audio/pipewire/SDL_pipewire.c b/libs/SDL3/src/audio/pipewire/SDL_pipewire.c index 32c93f61..a9e95a6f 100644 --- a/libs/SDL3/src/audio/pipewire/SDL_pipewire.c +++ b/libs/SDL3/src/audio/pipewire/SDL_pipewire.c @@ -266,13 +266,11 @@ static bool pipewire_core_version_at_least(int major, int minor, int patch) static bool io_list_check_add(struct io_node *node) { struct io_node *n; - bool ret = true; // See if the node is already in the list spa_list_for_each (n, &hotplug_io_list, link) { if (n->id == node->id) { - ret = false; - goto dup_found; + return false; } } @@ -283,9 +281,7 @@ static bool io_list_check_add(struct io_node *node) SDL_AddAudioDevice(node->recording, node->name, &node->spec, PW_ID_TO_HANDLE(node->id)); } -dup_found: - - return ret; + return true; } static void io_list_remove(Uint32 id) @@ -550,7 +546,7 @@ static void node_event_info(void *object, const struct pw_node_info *info) // Need to parse the parameters to get the sample rate for (i = 0; i < info->n_params; ++i) { - pw_node_enum_params((struct pw_node*)node->proxy, 0, info->params[i].id, 0, 0, NULL); + pw_node_enum_params((struct pw_node *)node->proxy, 0, info->params[i].id, 0, 0, NULL); } hotplug_core_sync(node); @@ -1118,7 +1114,13 @@ static bool PIPEWIRE_OpenDevice(SDL_AudioDevice *device) stream_name = SDL_GetHint(SDL_HINT_AUDIO_DEVICE_STREAM_NAME); if (!stream_name || *stream_name == '\0') { - stream_name = "Audio Stream"; + if (app_name) { + stream_name = app_name; + } else if (app_id) { + stream_name = app_id; + } else { + stream_name = "SDL Audio Stream"; + } } /* diff --git a/libs/SDL3/src/audio/pulseaudio/SDL_pulseaudio.c b/libs/SDL3/src/audio/pulseaudio/SDL_pulseaudio.c index 69e8c1a8..4d618a6e 100644 --- a/libs/SDL3/src/audio/pulseaudio/SDL_pulseaudio.c +++ b/libs/SDL3/src/audio/pulseaudio/SDL_pulseaudio.c @@ -672,7 +672,8 @@ static bool PULSEAUDIO_OpenDevice(SDL_AudioDevice *device) paspec.rate = device->spec.freq; // Reduced prebuffering compared to the defaults. - paattr.fragsize = device->buffer_size; // despite the name, this is only used for recording devices, according to PulseAudio docs! + + paattr.fragsize = device->buffer_size * 2; // despite the name, this is only used for recording devices, according to PulseAudio docs! (times 2 because we want _more_ than our buffer size sent from the server at a time, which helps some drivers). paattr.tlength = device->buffer_size; paattr.prebuf = -1; paattr.maxlength = -1; diff --git a/libs/SDL3/src/audio/vita/SDL_vitaaudio.c b/libs/SDL3/src/audio/vita/SDL_vitaaudio.c index e194f212..86e8a691 100644 --- a/libs/SDL3/src/audio/vita/SDL_vitaaudio.c +++ b/libs/SDL3/src/audio/vita/SDL_vitaaudio.c @@ -130,7 +130,8 @@ static bool VITAAUD_OpenDevice(SDL_AudioDevice *device) static bool VITAAUD_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buffer_size) { - return (sceAudioOutOutput(device->hidden->port, buffer) == 0); + // sceAudioOutOutput returns amount of samples queued or < 0 on error + return (sceAudioOutOutput(device->hidden->port, buffer) >= 0); } // This function waits until it is possible to write a full sound buffer diff --git a/libs/SDL3/src/audio/wasapi/SDL_wasapi.c b/libs/SDL3/src/audio/wasapi/SDL_wasapi.c index db0974b0..4b782d34 100644 --- a/libs/SDL3/src/audio/wasapi/SDL_wasapi.c +++ b/libs/SDL3/src/audio/wasapi/SDL_wasapi.c @@ -750,7 +750,7 @@ static bool mgmtthrtask_PrepDevice(void *userdata) // Try querying IAudioClient3 if sharemode is AUDCLNT_SHAREMODE_SHARED if (sharemode == AUDCLNT_SHAREMODE_SHARED) { IAudioClient3 *client3 = NULL; - ret = IAudioClient_QueryInterface(client, &SDL_IID_IAudioClient3, (void**)&client3); + ret = IAudioClient_QueryInterface(client, &SDL_IID_IAudioClient3, (void **)&client3); if (SUCCEEDED(ret)) { UINT32 default_period_in_frames = 0; UINT32 fundamental_period_in_frames = 0; diff --git a/libs/SDL3/src/camera/coremedia/SDL_camera_coremedia.m b/libs/SDL3/src/camera/coremedia/SDL_camera_coremedia.m index 2ecfd13c..f58fb927 100644 --- a/libs/SDL3/src/camera/coremedia/SDL_camera_coremedia.m +++ b/libs/SDL3/src/camera/coremedia/SDL_camera_coremedia.m @@ -239,7 +239,7 @@ static void COREMEDIA_CloseDevice(SDL_Camera *device) hidden.session = nil; [session stopRunning]; [session removeInput:[session.inputs objectAtIndex:0]]; - [session removeOutput:(AVCaptureVideoDataOutput*)[session.outputs objectAtIndex:0]]; + [session removeOutput:(AVCaptureVideoDataOutput *)[session.outputs objectAtIndex:0]]; session = nil; } diff --git a/libs/SDL3/src/camera/emscripten/SDL_camera_emscripten.c b/libs/SDL3/src/camera/emscripten/SDL_camera_emscripten.c index fa2a5114..36418c57 100644 --- a/libs/SDL3/src/camera/emscripten/SDL_camera_emscripten.c +++ b/libs/SDL3/src/camera/emscripten/SDL_camera_emscripten.c @@ -61,7 +61,7 @@ static SDL_CameraFrameResult EMSCRIPTENCAMERA_AcquireFrame(SDL_Camera *device, S SDL3.camera.ctx2d.drawImage(SDL3.camera.video, 0, 0, w, h); const imgrgba = SDL3.camera.ctx2d.getImageData(0, 0, w, h).data; - Module.HEAPU8.set(imgrgba, rgba); + HEAPU8.set(imgrgba, rgba); return 1; }, device->actual_spec.width, device->actual_spec.height, rgba); diff --git a/libs/SDL3/src/camera/mediafoundation/SDL_camera_mediafoundation.c b/libs/SDL3/src/camera/mediafoundation/SDL_camera_mediafoundation.c index d9d627d0..433be4d8 100644 --- a/libs/SDL3/src/camera/mediafoundation/SDL_camera_mediafoundation.c +++ b/libs/SDL3/src/camera/mediafoundation/SDL_camera_mediafoundation.c @@ -675,7 +675,7 @@ static HRESULT GetDefaultStride(IMFMediaType *pType, LONG *plStride) LONG lStride = 0; // Try to get the default stride from the media type. - HRESULT ret = IMFMediaType_GetUINT32(pType, &SDL_MF_MT_DEFAULT_STRIDE, (UINT32*)&lStride); + HRESULT ret = IMFMediaType_GetUINT32(pType, &SDL_MF_MT_DEFAULT_STRIDE, (UINT32 *)&lStride); if (FAILED(ret)) { // Attribute not set. Try to calculate the default stride. @@ -741,7 +741,7 @@ static bool MEDIAFOUNDATION_OpenDevice(SDL_Camera *device, const SDL_CameraSpec SDL_Log("CAMERA: opening device with symlink of '%s'", utf8symlink); #endif - wstrsymlink = WIN_UTF8ToString(utf8symlink); + wstrsymlink = WIN_UTF8ToStringW(utf8symlink); if (!wstrsymlink) { goto failed; } @@ -901,7 +901,7 @@ static char *QueryActivationObjectString(IMFActivate *activation, const GUID *pg return NULL; } - char *utf8str = WIN_StringToUTF8(wstr); + char *utf8str = WIN_StringToUTF8W(wstr); CoTaskMemFree(wstr); return utf8str; } @@ -1001,7 +1001,7 @@ static void MaybeAddDevice(IMFActivate *activation) if (name && symlink) { IMFMediaSource *source = NULL; // "activating" here only creates an object, it doesn't open the actual camera hardware or start recording. - HRESULT ret = IMFActivate_ActivateObject(activation, &SDL_IID_IMFMediaSource, (void**)&source); + HRESULT ret = IMFActivate_ActivateObject(activation, &SDL_IID_IMFMediaSource, (void **)&source); if (SUCCEEDED(ret) && source) { CameraFormatAddData add_data; GatherCameraSpecs(source, &add_data); diff --git a/libs/SDL3/src/camera/pipewire/SDL_camera_pipewire.c b/libs/SDL3/src/camera/pipewire/SDL_camera_pipewire.c index 5d868bfc..056ed4f7 100644 --- a/libs/SDL3/src/camera/pipewire/SDL_camera_pipewire.c +++ b/libs/SDL3/src/camera/pipewire/SDL_camera_pipewire.c @@ -25,6 +25,10 @@ #include "../SDL_syscamera.h" +#ifdef HAVE_DBUS_DBUS_H +#include "../../core/linux/SDL_dbus.h" +#endif + #include #include #include @@ -80,6 +84,9 @@ static int (*PIPEWIRE_pw_thread_loop_start)(struct pw_thread_loop *); static struct pw_context *(*PIPEWIRE_pw_context_new)(struct pw_loop *, struct pw_properties *, size_t); static void (*PIPEWIRE_pw_context_destroy)(struct pw_context *); static struct pw_core *(*PIPEWIRE_pw_context_connect)(struct pw_context *, struct pw_properties *, size_t); +#ifdef SDL_USE_LIBDBUS +static struct pw_core *(*PIPEWIRE_pw_context_connect_fd)(struct pw_context *, int, struct pw_properties *, size_t); +#endif static void (*PIPEWIRE_pw_proxy_add_object_listener)(struct pw_proxy *, struct spa_hook *, const void *, void *); static void (*PIPEWIRE_pw_proxy_add_listener)(struct pw_proxy *, struct spa_hook *, const struct pw_proxy_events *, void *); static void *(*PIPEWIRE_pw_proxy_get_user_data)(struct pw_proxy *); @@ -175,6 +182,9 @@ static bool load_pipewire_syms(void) SDL_PIPEWIRE_SYM(pw_context_new); SDL_PIPEWIRE_SYM(pw_context_destroy); SDL_PIPEWIRE_SYM(pw_context_connect); +#ifdef SDL_USE_LIBDBUS + SDL_PIPEWIRE_SYM(pw_context_connect_fd); +#endif SDL_PIPEWIRE_SYM(pw_proxy_add_listener); SDL_PIPEWIRE_SYM(pw_proxy_add_object_listener); SDL_PIPEWIRE_SYM(pw_proxy_get_user_data); @@ -828,7 +838,7 @@ static void node_event_info(void *object, const struct pw_node_info *info) if (!(info->params[i].flags & SPA_PARAM_INFO_READ)) continue; - res = pw_node_enum_params((struct pw_node*)g->proxy, + res = pw_node_enum_params((struct pw_node *)g->proxy, ++SPA_PARAMS_INFO_SEQ(info->params[i]), id, 0, -1, NULL); if (SPA_RESULT_IS_ASYNC(res)) SPA_PARAMS_INFO_SEQ(info->params[i]) = res; @@ -1025,6 +1035,13 @@ static bool pipewire_server_version_at_least(int major, int minor, int patch) static bool hotplug_loop_init(void) { int res; +#ifdef SDL_USE_LIBDBUS + int fd; + + fd = SDL_DBus_CameraPortalRequestAccess(); + if (fd == -1) + return false; +#endif spa_list_init(&hotplug.global_list); @@ -1043,8 +1060,15 @@ static bool hotplug_loop_init(void) if (!hotplug.context) { return SDL_SetError("Pipewire: Failed to create hotplug detection context (%i)", errno); } - +#ifdef SDL_USE_LIBDBUS + if (fd >= 0) { + hotplug.core = PIPEWIRE_pw_context_connect_fd(hotplug.context, fd, NULL, 0); + } else { + hotplug.core = PIPEWIRE_pw_context_connect(hotplug.context, NULL, 0); + } +#else hotplug.core = PIPEWIRE_pw_context_connect(hotplug.context, NULL, 0); +#endif if (!hotplug.core) { return SDL_SetError("Pipewire: Failed to connect hotplug detection context (%i)", errno); } diff --git a/libs/SDL3/src/camera/v4l2/SDL_camera_v4l2.c b/libs/SDL3/src/camera/v4l2/SDL_camera_v4l2.c index 9cdb54b0..664d4418 100644 --- a/libs/SDL3/src/camera/v4l2/SDL_camera_v4l2.c +++ b/libs/SDL3/src/camera/v4l2/SDL_camera_v4l2.c @@ -193,7 +193,7 @@ static SDL_CameraFrameResult V4L2_AcquireFrame(SDL_Camera *device, SDL_Surface * *timestampNS = (((Uint64) buf.timestamp.tv_sec) * SDL_NS_PER_SECOND) + SDL_US_TO_NS(buf.timestamp.tv_usec); #if DEBUG_CAMERA - SDL_Log("CAMERA: debug mmap: image %d/%d data[0]=%p", buf.index, device->hidden->nb_buffers, (void*)frame->pixels); + SDL_Log("CAMERA: debug mmap: image %d/%d data[0]=%p", buf.index, device->hidden->nb_buffers, (void *)frame->pixels); #endif break; @@ -230,7 +230,7 @@ static SDL_CameraFrameResult V4L2_AcquireFrame(SDL_Camera *device, SDL_Surface * return SDL_CAMERA_FRAME_ERROR; } - frame->pixels = (void*)buf.m.userptr; + frame->pixels = (void *)buf.m.userptr; if (device->hidden->driver_pitch) { frame->pitch = device->hidden->driver_pitch; } else { @@ -241,7 +241,7 @@ static SDL_CameraFrameResult V4L2_AcquireFrame(SDL_Camera *device, SDL_Surface * *timestampNS = (((Uint64) buf.timestamp.tv_sec) * SDL_NS_PER_SECOND) + SDL_US_TO_NS(buf.timestamp.tv_usec); #if DEBUG_CAMERA - SDL_Log("CAMERA: debug userptr: image %d/%d data[0]=%p", buf.index, device->hidden->nb_buffers, (void*)frame->pixels); + SDL_Log("CAMERA: debug userptr: image %d/%d data[0]=%p", buf.index, device->hidden->nb_buffers, (void *)frame->pixels); #endif break; diff --git a/libs/SDL3/src/camera/vita/SDL_camera_vita.c b/libs/SDL3/src/camera/vita/SDL_camera_vita.c index 42a5a89d..874c6d8d 100644 --- a/libs/SDL3/src/camera/vita/SDL_camera_vita.c +++ b/libs/SDL3/src/camera/vita/SDL_camera_vita.c @@ -93,7 +93,7 @@ static void MaybeAddDevice(Sint32 devid) GatherCameraSpecs(devid, &add_data, &fullname, &position); if (add_data.num_specs > 0) { - SDL_AddCamera(fullname, position, add_data.num_specs, add_data.specs, (void*)devid); + SDL_AddCamera(fullname, position, add_data.num_specs, add_data.specs, (void *)devid); } SDL_free(fullname); @@ -102,7 +102,7 @@ static void MaybeAddDevice(Sint32 devid) static SceUID imbUid = -1; -static void freeBuffers(SceCameraInfo* info) +static void freeBuffers(SceCameraInfo *info) { if (imbUid != -1) { sceKernelFreeMemBlock(imbUid); @@ -118,7 +118,7 @@ static bool VITACAMERA_OpenDevice(SDL_Camera *device, const SDL_CameraSpec *spec return SDL_SetError("Only one camera can be active"); } - SceCameraInfo* info = (SceCameraInfo*)SDL_calloc(1, sizeof(SceCameraInfo)); + SceCameraInfo *info = (SceCameraInfo *)SDL_calloc(1, sizeof(SceCameraInfo)); info->size = sizeof(SceCameraInfo); info->priority = SCE_CAMERA_PRIORITY_SHARE; @@ -139,12 +139,12 @@ static bool VITACAMERA_OpenDevice(SDL_Camera *device, const SDL_CameraSpec *spec info->format = SCE_CAMERA_FORMAT_YUV420_PLANE; info->pitch = 0; // same size surface - info->sizeIBase = spec->width*spec->height;; + info->sizeIBase = spec->width * spec->height;; info->sizeUBase = ((spec->width+1)/2) * ((spec->height+1) / 2); info->sizeVBase = ((spec->width+1)/2) * ((spec->height+1) / 2); // PHYCONT memory size *must* be a multiple of 1MB, we can just always spend 2MB, since we don't use PHYCONT anywhere else - imbUid = sceKernelAllocMemBlock("CameraI", SCE_KERNEL_MEMBLOCK_TYPE_USER_MAIN_PHYCONT_NC_RW, 2*1024*1024 , NULL); + imbUid = sceKernelAllocMemBlock("CameraI", SCE_KERNEL_MEMBLOCK_TYPE_USER_MAIN_PHYCONT_NC_RW, 2 * 1024 * 1024 , NULL); if (imbUid < 0) { return SDL_SetError("sceKernelAllocMemBlock error: 0x%08X", imbUid); @@ -179,7 +179,7 @@ static void VITACAMERA_CloseDevice(SDL_Camera *device) if (device->hidden) { sceCameraStop((int)device->handle); sceCameraClose((int)device->handle); - freeBuffers((SceCameraInfo*)device->hidden); + freeBuffers((SceCameraInfo *)device->hidden); SDL_free(device->hidden); } } @@ -205,7 +205,7 @@ static SDL_CameraFrameResult VITACAMERA_AcquireFrame(SDL_Camera *device, SDL_Sur *timestampNS = read.timestamp; - SceCameraInfo* info = (SceCameraInfo*)(device->hidden); + SceCameraInfo *info = (SceCameraInfo *)(device->hidden); frame->pitch = info->width; frame->pixels = SDL_aligned_alloc(SDL_GetSIMDAlignment(), info->sizeIBase + info->sizeUBase + info->sizeVBase); diff --git a/libs/SDL3/src/core/SDL_core_unsupported.c b/libs/SDL3/src/core/SDL_core_unsupported.c index af963ed8..9ace0e0b 100644 --- a/libs/SDL3/src/core/SDL_core_unsupported.c +++ b/libs/SDL3/src/core/SDL_core_unsupported.c @@ -119,7 +119,7 @@ void *SDL_GetAndroidActivity(void) } SDL_DECLSPEC const char * SDLCALL SDL_GetAndroidCachePath(void); -const char* SDL_GetAndroidCachePath(void) +const char *SDL_GetAndroidCachePath(void) { SDL_Unsupported(); return NULL; @@ -127,7 +127,7 @@ const char* SDL_GetAndroidCachePath(void) SDL_DECLSPEC const char * SDLCALL SDL_GetAndroidExternalStoragePath(void); -const char* SDL_GetAndroidExternalStoragePath(void) +const char *SDL_GetAndroidExternalStoragePath(void) { SDL_Unsupported(); return NULL; @@ -172,7 +172,7 @@ bool SDL_SendAndroidMessage(Uint32 command, int param) } SDL_DECLSPEC bool SDLCALL SDL_ShowAndroidToast(const char *message, int duration, int gravity, int xoffset, int yoffset); -bool SDL_ShowAndroidToast(const char* message, int duration, int gravity, int xoffset, int yoffset) +bool SDL_ShowAndroidToast(const char *message, int duration, int gravity, int xoffset, int yoffset) { (void)message; (void)duration; diff --git a/libs/SDL3/src/core/android/SDL_android.c b/libs/SDL3/src/core/android/SDL_android.c index daf0f29f..6cdffef2 100644 --- a/libs/SDL3/src/core/android/SDL_android.c +++ b/libs/SDL3/src/core/android/SDL_android.c @@ -257,17 +257,17 @@ JNIEXPORT void JNICALL SDL_JAVA_AUDIO_INTERFACE(nativeSetupJNI)( JNIEnv *env, jclass jcls); JNIEXPORT void JNICALL - SDL_JAVA_AUDIO_INTERFACE(addAudioDevice)(JNIEnv *env, jclass jcls, jboolean recording, jstring name, + SDL_JAVA_AUDIO_INTERFACE(nativeAddAudioDevice)(JNIEnv *env, jclass jcls, jboolean recording, jstring name, jint device_id); JNIEXPORT void JNICALL - SDL_JAVA_AUDIO_INTERFACE(removeAudioDevice)(JNIEnv *env, jclass jcls, jboolean recording, + SDL_JAVA_AUDIO_INTERFACE(nativeRemoveAudioDevice)(JNIEnv *env, jclass jcls, jboolean recording, jint device_id); static JNINativeMethod SDLAudioManager_tab[] = { { "nativeSetupJNI", "()I", SDL_JAVA_AUDIO_INTERFACE(nativeSetupJNI) }, - { "addAudioDevice", "(ZLjava/lang/String;I)V", SDL_JAVA_AUDIO_INTERFACE(addAudioDevice) }, - { "removeAudioDevice", "(ZI)V", SDL_JAVA_AUDIO_INTERFACE(removeAudioDevice) } + { "nativeAddAudioDevice", "(ZLjava/lang/String;I)V", SDL_JAVA_AUDIO_INTERFACE(nativeAddAudioDevice) }, + { "nativeRemoveAudioDevice", "(ZI)V", SDL_JAVA_AUDIO_INTERFACE(nativeRemoveAudioDevice) } }; // Java class SDLControllerManager @@ -371,6 +371,7 @@ static jmethodID midShowTextInput; static jmethodID midSupportsRelativeMouse; static jmethodID midOpenFileDescriptor; static jmethodID midShowFileDialog; +static jmethodID midGetPreferredLocales; // audio manager static jclass mAudioManagerClass; @@ -660,6 +661,7 @@ JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeSetupJNI)(JNIEnv *env, jclass cl midSupportsRelativeMouse = (*env)->GetStaticMethodID(env, mActivityClass, "supportsRelativeMouse", "()Z"); midOpenFileDescriptor = (*env)->GetStaticMethodID(env, mActivityClass, "openFileDescriptor", "(Ljava/lang/String;Ljava/lang/String;)I"); midShowFileDialog = (*env)->GetStaticMethodID(env, mActivityClass, "showFileDialog", "([Ljava/lang/String;ZZI)Z"); + midGetPreferredLocales = (*env)->GetStaticMethodID(env, mActivityClass, "getPreferredLocales", "()Ljava/lang/String;"); if (!midClipboardGetText || !midClipboardHasText || @@ -691,7 +693,8 @@ JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeSetupJNI)(JNIEnv *env, jclass cl !midShowTextInput || !midSupportsRelativeMouse || !midOpenFileDescriptor || - !midShowFileDialog) { + !midShowFileDialog || + !midGetPreferredLocales) { __android_log_print(ANDROID_LOG_WARN, "SDL", "Missing some Java callbacks, do you have the latest version of SDLActivity.java?"); } @@ -751,6 +754,8 @@ JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeSetupJNI)(JNIEnv *env typedef int (*SDL_main_func)(int argc, char *argv[]); static int run_count = 0; +static bool allow_recreate_activity; +static bool allow_recreate_activity_set; JNIEXPORT int JNICALL SDL_JAVA_INTERFACE(nativeCheckSDLThreadCounter)( JNIEnv *env, jclass jcls) @@ -760,10 +765,16 @@ JNIEXPORT int JNICALL SDL_JAVA_INTERFACE(nativeCheckSDLThreadCounter)( return tmp; } +void Android_SetAllowRecreateActivity(bool enabled) +{ + allow_recreate_activity = enabled; + allow_recreate_activity_set = true; +} + JNIEXPORT jboolean JNICALL SDL_JAVA_INTERFACE(nativeAllowRecreateActivity)( JNIEnv *env, jclass jcls) { - return SDL_GetHintBoolean(SDL_HINT_ANDROID_ALLOW_RECREATE_ACTIVITY, false); + return allow_recreate_activity; } JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeInitMainThread)( @@ -1075,7 +1086,7 @@ JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeAddTouch)( } JNIEXPORT void JNICALL -SDL_JAVA_AUDIO_INTERFACE(addAudioDevice)(JNIEnv *env, jclass jcls, jboolean recording, +SDL_JAVA_AUDIO_INTERFACE(nativeAddAudioDevice)(JNIEnv *env, jclass jcls, jboolean recording, jstring name, jint device_id) { #if ALLOW_MULTIPLE_ANDROID_AUDIO_DEVICES @@ -1091,7 +1102,7 @@ SDL_JAVA_AUDIO_INTERFACE(addAudioDevice)(JNIEnv *env, jclass jcls, jboolean reco } JNIEXPORT void JNICALL -SDL_JAVA_AUDIO_INTERFACE(removeAudioDevice)(JNIEnv *env, jclass jcls, jboolean recording, +SDL_JAVA_AUDIO_INTERFACE(nativeRemoveAudioDevice)(JNIEnv *env, jclass jcls, jboolean recording, jint device_id) { #if ALLOW_MULTIPLE_ANDROID_AUDIO_DEVICES @@ -1526,6 +1537,14 @@ JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeSetenv)( // Note that we call setenv() directly to avoid affecting SDL environments setenv(utfname, utfvalue, 1); // This should NOT be SDL_setenv() + if (SDL_strcmp(utfname, SDL_HINT_ANDROID_ALLOW_RECREATE_ACTIVITY) == 0) { + // Special handling for this hint, which needs to persist outside the normal application flow + // Only set this the first time we run, in case it's been set by the application via SDL_SetHint() + if (!allow_recreate_activity_set) { + Android_SetAllowRecreateActivity(SDL_GetStringBoolean(utfvalue, false)); + } + } + (*env)->ReleaseStringUTFChars(env, name, utfname); (*env)->ReleaseStringUTFChars(env, value, utfvalue); } @@ -2569,65 +2588,22 @@ bool Android_JNI_ShowToast(const char *message, int duration, int gravity, int x bool Android_JNI_GetLocale(char *buf, size_t buflen) { - AConfiguration *cfg; - - SDL_assert(buflen > 6); - - // Need to re-create the asset manager if locale has changed (SDL_EVENT_LOCALE_CHANGED) - Internal_Android_Destroy_AssetManager(); - - if (!asset_manager) { - Internal_Android_Create_AssetManager(); - } - - if (!asset_manager) { - return false; - } - - cfg = AConfiguration_new(); - if (!cfg) { - return false; - } - - { - char language[2] = {}; - char country[2] = {}; - size_t id = 0; - - AConfiguration_fromAssetManager(cfg, asset_manager); - AConfiguration_getLanguage(cfg, language); - AConfiguration_getCountry(cfg, country); - - // Indonesian is "id" according to ISO 639.2, but on Android is "in" because of Java backwards compatibility - if (language[0] == 'i' && language[1] == 'n') { - language[1] = 'd'; - } - - // copy language (not null terminated) - if (language[0]) { - buf[id++] = language[0]; - if (language[1]) { - buf[id++] = language[1]; + bool result = false; + if (buf && buflen > 0) { + *buf = '\0'; + JNIEnv *env = Android_JNI_GetEnv(); + jstring string = (jstring)(*env)->CallStaticObjectMethod(env, mActivityClass, midGetPreferredLocales); + if (string) { + const char *utf8string = (*env)->GetStringUTFChars(env, string, NULL); + if (utf8string) { + result = true; + SDL_strlcpy(buf, utf8string, buflen); + (*env)->ReleaseStringUTFChars(env, string, utf8string); } + (*env)->DeleteLocalRef(env, string); } - - buf[id++] = '_'; - - // copy country (not null terminated) - if (country[0]) { - buf[id++] = country[0]; - if (country[1]) { - buf[id++] = country[1]; - } - } - - buf[id++] = '\0'; - SDL_assert(id <= buflen); } - - AConfiguration_delete(cfg); - - return true; + return result; } bool Android_JNI_OpenURL(const char *url) @@ -2715,7 +2691,7 @@ JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeFileDialog)( // Convert fileList to string size_t count = (*env)->GetArrayLength(env, fileList); - char **charFileList = SDL_calloc(count + 1, sizeof(char*)); + char **charFileList = SDL_calloc(count + 1, sizeof(char *)); if (charFileList == NULL) { mAndroidFileDialogData.callback(mAndroidFileDialogData.userdata, NULL, -1); @@ -2771,7 +2747,7 @@ JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeFileDialog)( } bool Android_JNI_OpenFileDialog( - SDL_DialogFileCallback callback, void* userdata, + SDL_DialogFileCallback callback, void *userdata, const SDL_DialogFileFilter *filters, int nfilters, bool forwrite, bool multiple) { diff --git a/libs/SDL3/src/core/android/SDL_android.h b/libs/SDL3/src/core/android/SDL_android.h index 3541c2a9..925cc199 100644 --- a/libs/SDL3/src/core/android/SDL_android.h +++ b/libs/SDL3/src/core/android/SDL_android.h @@ -55,6 +55,8 @@ bool Android_WaitLifecycleEvent(SDL_AndroidLifecycleEvent *event, Sint64 timeout void Android_LockActivityMutex(void); void Android_UnlockActivityMutex(void); +void Android_SetAllowRecreateActivity(bool enabled); + // Interface from the SDL library into the Android Java activity extern void Android_JNI_SetActivityTitle(const char *title); extern void Android_JNI_SetWindowStyle(bool fullscreen); @@ -149,7 +151,7 @@ bool SDL_IsAndroidTablet(void); bool SDL_IsAndroidTV(void); // File Dialogs -bool Android_JNI_OpenFileDialog(SDL_DialogFileCallback callback, void* userdata, +bool Android_JNI_OpenFileDialog(SDL_DialogFileCallback callback, void *userdata, const SDL_DialogFileFilter *filters, int nfilters, bool forwrite, bool multiple); diff --git a/libs/SDL3/src/core/freebsd/SDL_evdev_kbd_freebsd.c b/libs/SDL3/src/core/freebsd/SDL_evdev_kbd_freebsd.c index 16a21713..afd3ad05 100644 --- a/libs/SDL3/src/core/freebsd/SDL_evdev_kbd_freebsd.c +++ b/libs/SDL3/src/core/freebsd/SDL_evdev_kbd_freebsd.c @@ -324,7 +324,7 @@ void SDL_EVDEV_kbd_set_muted(SDL_EVDEV_keyboard_state *state, bool muted) { } -void SDL_EVDEV_kbd_set_vt_switch_callbacks(SDL_EVDEV_keyboard_state *state, void (*release_callback)(void*), void *release_callback_data, void (*acquire_callback)(void*), void *acquire_callback_data) +void SDL_EVDEV_kbd_set_vt_switch_callbacks(SDL_EVDEV_keyboard_state *state, void (*release_callback)(void *), void *release_callback_data, void (*acquire_callback)(void *), void *acquire_callback_data) { } diff --git a/libs/SDL3/src/core/gdk/SDL_gdk.cpp b/libs/SDL3/src/core/gdk/SDL_gdk.cpp index 738daa5b..4e792ef2 100644 --- a/libs/SDL3/src/core/gdk/SDL_gdk.cpp +++ b/libs/SDL3/src/core/gdk/SDL_gdk.cpp @@ -105,7 +105,7 @@ bool GDK_RegisterChangeNotifications(void) SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, "[GDK] in RegisterAppConstrainedChangeNotification handler"); SDL_VideoDevice *_this = SDL_GetVideoDevice(); if (_this) { - if (constrained) { + if (constrained && !((_this->windows) && _this->windows->text_input_active)) { SDL_SetKeyboardFocus(NULL); } else { SDL_SetKeyboardFocus(_this->windows); diff --git a/libs/SDL3/src/core/haiku/SDL_BApp.h b/libs/SDL3/src/core/haiku/SDL_BApp.h index cbd3e3a0..d540c36d 100644 --- a/libs/SDL3/src/core/haiku/SDL_BApp.h +++ b/libs/SDL3/src/core/haiku/SDL_BApp.h @@ -82,7 +82,7 @@ extern "C" SDL_BLooper *SDL_Looper; class SDL_BLooper : public BLooper { public: - SDL_BLooper(const char* name) : BLooper(name) + SDL_BLooper(const char *name) : BLooper(name) { #ifdef SDL_VIDEO_OPENGL _current_context = NULL; diff --git a/libs/SDL3/src/core/haiku/SDL_BeApp.cc b/libs/SDL3/src/core/haiku/SDL_BeApp.cc index 350f7f3e..4737f48e 100644 --- a/libs/SDL3/src/core/haiku/SDL_BeApp.cc +++ b/libs/SDL3/src/core/haiku/SDL_BeApp.cc @@ -56,7 +56,7 @@ const char *SDL_signature = "application/x-SDL-executable"; // Create a descendant of BApplication class SDL_BApp : public BApplication { public: - SDL_BApp(const char* signature) : + SDL_BApp(const char *signature) : BApplication(signature) { } @@ -65,7 +65,7 @@ public: } - virtual void RefsReceived(BMessage* message) { + virtual void RefsReceived(BMessage *message) { entry_ref entryRef; for (int32 i = 0; message->FindRef("refs", i, &entryRef) == B_OK; i++) { BPath referencePath = BPath(&entryRef); diff --git a/libs/SDL3/src/core/linux/SDL_dbus.c b/libs/SDL3/src/core/linux/SDL_dbus.c index 9a2fc1ea..b61a1cd9 100644 --- a/libs/SDL3/src/core/linux/SDL_dbus.c +++ b/libs/SDL3/src/core/linux/SDL_dbus.c @@ -48,6 +48,8 @@ static bool LoadDBUSSyms(void) SDL_DBUS_SYM(DBusConnection *(*)(DBusBusType, DBusError *), bus_get_private); SDL_DBUS_SYM(dbus_bool_t (*)(DBusConnection *, DBusError *), bus_register); SDL_DBUS_SYM(void (*)(DBusConnection *, const char *, DBusError *), bus_add_match); + SDL_DBUS_SYM(void (*)(DBusConnection *, const char *, DBusError *), bus_remove_match); + SDL_DBUS_SYM(const char *(*)(DBusConnection *), bus_get_unique_name); SDL_DBUS_SYM(DBusConnection *(*)(const char *, DBusError *), connection_open_private); SDL_DBUS_SYM(void (*)(DBusConnection *, dbus_bool_t), connection_set_exit_on_disconnect); SDL_DBUS_SYM(dbus_bool_t (*)(DBusConnection *), connection_get_is_connected); @@ -61,10 +63,12 @@ static bool LoadDBUSSyms(void) SDL_DBUS_SYM(void (*)(DBusConnection *), connection_unref); SDL_DBUS_SYM(void (*)(DBusConnection *), connection_flush); SDL_DBUS_SYM(dbus_bool_t (*)(DBusConnection *, int), connection_read_write); + SDL_DBUS_SYM(dbus_bool_t (*)(DBusConnection *, int), connection_read_write_dispatch); SDL_DBUS_SYM(DBusDispatchStatus (*)(DBusConnection *), connection_dispatch); SDL_DBUS_SYM(dbus_bool_t (*)(DBusMessage *, const char *, const char *), message_is_signal); SDL_DBUS_SYM(dbus_bool_t (*)(DBusMessage *, const char *), message_has_path); SDL_DBUS_SYM(DBusMessage *(*)(const char *, const char *, const char *, const char *), message_new_method_call); + SDL_DBUS_SYM(DBusMessage *(*)(const char *, const char *, const char *), message_new_signal); SDL_DBUS_SYM(dbus_bool_t (*)(DBusMessage *, int, ...), message_append_args); SDL_DBUS_SYM(dbus_bool_t (*)(DBusMessage *, int, va_list), message_append_args_valist); SDL_DBUS_SYM(void (*)(DBusMessage *, DBusMessageIter *), message_iter_init_append); @@ -82,6 +86,7 @@ static bool LoadDBUSSyms(void) SDL_DBUS_SYM(dbus_bool_t (*)(void), threads_init_default); SDL_DBUS_SYM(void (*)(DBusError *), error_init); SDL_DBUS_SYM(dbus_bool_t (*)(const DBusError *), error_is_set); + SDL_DBUS_SYM(dbus_bool_t (*)(const DBusError *, const char *), error_has_name); SDL_DBUS_SYM(void (*)(DBusError *), error_free); SDL_DBUS_SYM(char *(*)(void), get_local_machine_id); SDL_DBUS_SYM_OPTIONAL(char *(*)(DBusError *), try_get_local_machine_id); @@ -638,4 +643,184 @@ failed: return NULL; } +typedef struct SDL_DBus_CameraPortalMessageHandlerData +{ + uint32_t response; + char *path; + DBusError *err; + bool done; +} SDL_DBus_CameraPortalMessageHandlerData; + +static DBusHandlerResult SDL_DBus_CameraPortalMessageHandler(DBusConnection *conn, DBusMessage *msg, void *v) +{ + SDL_DBus_CameraPortalMessageHandlerData *data = v; + const char *name, *old, *new; + + if (dbus.message_is_signal(msg, "org.freedesktop.DBus", "NameOwnerChanged")) { + if (!dbus.message_get_args(msg, data->err, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_STRING, &old, + DBUS_TYPE_STRING, &new, + DBUS_TYPE_INVALID)) { + data->done = true; + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + if (SDL_strcmp(name, "org.freedesktop.portal.Desktop") != 0 || + SDL_strcmp(new, "") != 0) { + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + data->done = true; + data->response = -1; + return DBUS_HANDLER_RESULT_HANDLED; + } + if (!dbus.message_has_path(msg, data->path) || !dbus.message_is_signal(msg, "org.freedesktop.portal.Request", "Response")) { + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + dbus.message_get_args(msg, data->err, DBUS_TYPE_UINT32, &data->response, DBUS_TYPE_INVALID); + data->done = true; + return DBUS_HANDLER_RESULT_HANDLED; +} + +#define SIGNAL_NAMEOWNERCHANGED "type='signal',\ + sender='org.freedesktop.DBus',\ + interface='org.freedesktop.DBus',\ + member='NameOwnerChanged',\ + arg0='org.freedesktop.portal.Desktop',\ + arg2=''" + +/* + * Requests access for the camera. Returns -1 on error, -2 on denied access or + * missing portal, otherwise returns a file descriptor to be used by the Pipewire driver. + * https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.Camera.html + */ +int SDL_DBus_CameraPortalRequestAccess(void) +{ + SDL_DBus_CameraPortalMessageHandlerData data; + DBusError err; + DBusMessageIter iter, iterDict; + DBusMessage *reply, *msg; + int fd; + + if (SDL_GetSandbox() == SDL_SANDBOX_NONE) { + return -2; + } + + if (!SDL_DBus_GetContext()) { + return -2; + } + + dbus.error_init(&err); + + msg = dbus.message_new_method_call("org.freedesktop.portal.Desktop", + "/org/freedesktop/portal/desktop", + "org.freedesktop.portal.Camera", + "AccessCamera"); + + dbus.message_iter_init_append(msg, &iter); + if (!dbus.message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &iterDict) || + !dbus.message_iter_close_container(&iter, &iterDict)) { + SDL_OutOfMemory(); + dbus.message_unref(msg); + goto failed; + } + + reply = dbus.connection_send_with_reply_and_block(dbus.session_conn, msg, DBUS_TIMEOUT_USE_DEFAULT, &err); + dbus.message_unref(msg); + + if (reply) { + dbus.message_get_args(reply, &err, DBUS_TYPE_OBJECT_PATH, &data.path, DBUS_TYPE_INVALID); + if (dbus.error_is_set(&err)) { + dbus.message_unref(reply); + goto failed; + } + if ((data.path = SDL_strdup(data.path)) == NULL) { + dbus.message_unref(reply); + SDL_OutOfMemory(); + goto failed; + } + dbus.message_unref(reply); + } else { + if (dbus.error_has_name(&err, DBUS_ERROR_NAME_HAS_NO_OWNER)) { + return -2; + } + goto failed; + } + + dbus.bus_add_match(dbus.session_conn, SIGNAL_NAMEOWNERCHANGED, &err); + if (dbus.error_is_set(&err)) { + SDL_free(data.path); + goto failed; + } + data.err = &err; + data.done = false; + if (!dbus.connection_add_filter(dbus.session_conn, SDL_DBus_CameraPortalMessageHandler, &data, NULL)) { + SDL_free(data.path); + SDL_OutOfMemory(); + goto failed; + } + while (!data.done && dbus.connection_read_write_dispatch(dbus.session_conn, -1)) { + ; + } + + dbus.bus_remove_match(dbus.session_conn, SIGNAL_NAMEOWNERCHANGED, &err); + if (dbus.error_is_set(&err)) { + SDL_free(data.path); + goto failed; + } + dbus.connection_remove_filter(dbus.session_conn, SDL_DBus_CameraPortalMessageHandler, &data); + SDL_free(data.path); + if (!data.done) { + goto failed; + } + if (dbus.error_is_set(&err)) { // from the message handler + goto failed; + } + if (data.response == 1 || data.response == 2) { + return -2; + } else if (data.response != 0) { + goto failed; + } + + msg = dbus.message_new_method_call("org.freedesktop.portal.Desktop", + "/org/freedesktop/portal/desktop", + "org.freedesktop.portal.Camera", + "OpenPipeWireRemote"); + + dbus.message_iter_init_append(msg, &iter); + if (!dbus.message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &iterDict) || + !dbus.message_iter_close_container(&iter, &iterDict)) { + SDL_OutOfMemory(); + dbus.message_unref(msg); + goto failed; + } + + reply = dbus.connection_send_with_reply_and_block(dbus.session_conn, msg, DBUS_TIMEOUT_USE_DEFAULT, &err); + dbus.message_unref(msg); + + if (reply) { + dbus.message_get_args(reply, &err, DBUS_TYPE_UNIX_FD, &fd, DBUS_TYPE_INVALID); + dbus.message_unref(reply); + if (dbus.error_is_set(&err)) { + goto failed; + } + } else { + goto failed; + } + + return fd; + +failed: + if (dbus.error_is_set(&err)) { + if (dbus.error_has_name(&err, DBUS_ERROR_NO_MEMORY)) { + SDL_OutOfMemory(); + } + SDL_SetError("%s: %s", err.name, err.message); + dbus.error_free(&err); + } else { + SDL_SetError("Error requesting access for the camera"); + } + + return -1; +} + #endif diff --git a/libs/SDL3/src/core/linux/SDL_dbus.h b/libs/SDL3/src/core/linux/SDL_dbus.h index 2073d6ce..230b20fd 100644 --- a/libs/SDL3/src/core/linux/SDL_dbus.h +++ b/libs/SDL3/src/core/linux/SDL_dbus.h @@ -34,6 +34,9 @@ #ifndef DBUS_TIMEOUT_INFINITE #define DBUS_TIMEOUT_INFINITE ((int) 0x7fffffff) #endif +#ifndef DBUS_TYPE_UNIX_FD +#define DBUS_TYPE_UNIX_FD ((int) 'h') +#endif typedef struct SDL_DBusContext { @@ -43,6 +46,8 @@ typedef struct SDL_DBusContext DBusConnection *(*bus_get_private)(DBusBusType, DBusError *); dbus_bool_t (*bus_register)(DBusConnection *, DBusError *); void (*bus_add_match)(DBusConnection *, const char *, DBusError *); + void (*bus_remove_match)(DBusConnection *, const char *, DBusError *); + const char *(*bus_get_unique_name)(DBusConnection *); DBusConnection *(*connection_open_private)(const char *, DBusError *); void (*connection_set_exit_on_disconnect)(DBusConnection *, dbus_bool_t); dbus_bool_t (*connection_get_is_connected)(DBusConnection *); @@ -57,10 +62,12 @@ typedef struct SDL_DBusContext void (*connection_unref)(DBusConnection *); void (*connection_flush)(DBusConnection *); dbus_bool_t (*connection_read_write)(DBusConnection *, int); + dbus_bool_t (*connection_read_write_dispatch)(DBusConnection *, int); DBusDispatchStatus (*connection_dispatch)(DBusConnection *); dbus_bool_t (*message_is_signal)(DBusMessage *, const char *, const char *); dbus_bool_t (*message_has_path)(DBusMessage *, const char *); DBusMessage *(*message_new_method_call)(const char *, const char *, const char *, const char *); + DBusMessage *(*message_new_signal)(const char *, const char *, const char *); dbus_bool_t (*message_append_args)(DBusMessage *, int, ...); dbus_bool_t (*message_append_args_valist)(DBusMessage *, int, va_list); void (*message_iter_init_append)(DBusMessage *, DBusMessageIter *); @@ -78,6 +85,7 @@ typedef struct SDL_DBusContext dbus_bool_t (*threads_init_default)(void); void (*error_init)(DBusError *); dbus_bool_t (*error_is_set)(const DBusError *); + dbus_bool_t (*error_has_name)(const DBusError *, const char *); void (*error_free)(DBusError *); char *(*get_local_machine_id)(void); char *(*try_get_local_machine_id)(DBusError *); @@ -109,6 +117,8 @@ extern char *SDL_DBus_GetLocalMachineId(void); extern char **SDL_DBus_DocumentsPortalRetrieveFiles(const char *key, int *files_count); +extern int SDL_DBus_CameraPortalRequestAccess(void); + #endif // HAVE_DBUS_DBUS_H #endif // SDL_dbus_h_ diff --git a/libs/SDL3/src/core/linux/SDL_evdev.c b/libs/SDL3/src/core/linux/SDL_evdev.c index 5746e2ef..902f7e69 100644 --- a/libs/SDL3/src/core/linux/SDL_evdev.c +++ b/libs/SDL3/src/core/linux/SDL_evdev.c @@ -287,8 +287,8 @@ static void SDL_EVDEV_udev_callback(SDL_UDEV_deviceevent udev_event, int udev_cl } #endif // SDL_USE_LIBUDEV -void SDL_EVDEV_SetVTSwitchCallbacks(void (*release_callback)(void*), void *release_callback_data, - void (*acquire_callback)(void*), void *acquire_callback_data) +void SDL_EVDEV_SetVTSwitchCallbacks(void (*release_callback)(void *), void *release_callback_data, + void (*acquire_callback)(void *), void *acquire_callback_data) { SDL_EVDEV_kbd_set_vt_switch_callbacks(_this->kbd, release_callback, release_callback_data, diff --git a/libs/SDL3/src/core/linux/SDL_evdev.h b/libs/SDL3/src/core/linux/SDL_evdev.h index d3e2fe16..c3de16fe 100644 --- a/libs/SDL3/src/core/linux/SDL_evdev.h +++ b/libs/SDL3/src/core/linux/SDL_evdev.h @@ -30,8 +30,8 @@ struct input_event; extern bool SDL_EVDEV_Init(void); extern void SDL_EVDEV_Quit(void); -extern void SDL_EVDEV_SetVTSwitchCallbacks(void (*release_callback)(void*), void *release_callback_data, - void (*acquire_callback)(void*), void *acquire_callback_data); +extern void SDL_EVDEV_SetVTSwitchCallbacks(void (*release_callback)(void *), void *release_callback_data, + void (*acquire_callback)(void *), void *acquire_callback_data); extern int SDL_EVDEV_GetDeviceCount(int device_class); extern void SDL_EVDEV_Poll(void); extern Uint64 SDL_EVDEV_GetEventTimestamp(struct input_event *event); diff --git a/libs/SDL3/src/core/linux/SDL_evdev_kbd.c b/libs/SDL3/src/core/linux/SDL_evdev_kbd.c index b1a56644..32340bff 100644 --- a/libs/SDL3/src/core/linux/SDL_evdev_kbd.c +++ b/libs/SDL3/src/core/linux/SDL_evdev_kbd.c @@ -495,7 +495,7 @@ void SDL_EVDEV_kbd_set_muted(SDL_EVDEV_keyboard_state *state, bool muted) state->muted = muted; } -void SDL_EVDEV_kbd_set_vt_switch_callbacks(SDL_EVDEV_keyboard_state *state, void (*release_callback)(void*), void *release_callback_data, void (*acquire_callback)(void*), void *acquire_callback_data) +void SDL_EVDEV_kbd_set_vt_switch_callbacks(SDL_EVDEV_keyboard_state *state, void (*release_callback)(void *), void *release_callback_data, void (*acquire_callback)(void *), void *acquire_callback_data) { if (state == NULL) { return; @@ -978,7 +978,7 @@ void SDL_EVDEV_kbd_set_muted(SDL_EVDEV_keyboard_state *state, bool muted) { } -void SDL_EVDEV_kbd_set_vt_switch_callbacks(SDL_EVDEV_keyboard_state *state, void (*release_callback)(void*), void *release_callback_data, void (*acquire_callback)(void*), void *acquire_callback_data) +void SDL_EVDEV_kbd_set_vt_switch_callbacks(SDL_EVDEV_keyboard_state *state, void (*release_callback)(void *), void *release_callback_data, void (*acquire_callback)(void *), void *acquire_callback_data) { } diff --git a/libs/SDL3/src/core/linux/SDL_evdev_kbd.h b/libs/SDL3/src/core/linux/SDL_evdev_kbd.h index 6ea19fb0..b7a2834c 100644 --- a/libs/SDL3/src/core/linux/SDL_evdev_kbd.h +++ b/libs/SDL3/src/core/linux/SDL_evdev_kbd.h @@ -27,7 +27,7 @@ typedef struct SDL_EVDEV_keyboard_state SDL_EVDEV_keyboard_state; extern SDL_EVDEV_keyboard_state *SDL_EVDEV_kbd_init(void); extern void SDL_EVDEV_kbd_set_muted(SDL_EVDEV_keyboard_state *state, bool muted); -extern void SDL_EVDEV_kbd_set_vt_switch_callbacks(SDL_EVDEV_keyboard_state *state, void (*release_callback)(void*), void *release_callback_data, void (*acquire_callback)(void*), void *acquire_callback_data); +extern void SDL_EVDEV_kbd_set_vt_switch_callbacks(SDL_EVDEV_keyboard_state *state, void (*release_callback)(void *), void *release_callback_data, void (*acquire_callback)(void *), void *acquire_callback_data); extern void SDL_EVDEV_kbd_update(SDL_EVDEV_keyboard_state *state); extern void SDL_EVDEV_kbd_keycode(SDL_EVDEV_keyboard_state *state, unsigned int keycode, int down); extern void SDL_EVDEV_kbd_quit(SDL_EVDEV_keyboard_state *state); diff --git a/libs/SDL3/src/core/linux/SDL_progressbar.c b/libs/SDL3/src/core/linux/SDL_progressbar.c new file mode 100644 index 00000000..e50f8361 --- /dev/null +++ b/libs/SDL3/src/core/linux/SDL_progressbar.c @@ -0,0 +1,159 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_progressbar.h" +#include "SDL_internal.h" + +#include "SDL_dbus.h" + +#ifdef SDL_USE_LIBDBUS + +#include + +#include "../unix/SDL_appid.h" + +#define UnityLauncherAPI_DBUS_INTERFACE "com.canonical.Unity.LauncherEntry" +#define UnityLauncherAPI_DBUS_SIGNAL "Update" + +static char *GetDBUSObjectPath() +{ + char *app_id = SDL_strdup(SDL_GetAppID()); + + if (!app_id) { + return NULL; + } + + // Sanitize exe_name to make it a legal D-Bus path element + for (char *p = app_id; *p; ++p) { + if (!SDL_isalnum(*p)) { + *p = '_'; + } + } + + // Ensure it starts with a letter or underscore + if (!SDL_isalpha(app_id[0]) && app_id[0] != '_') { + SDL_memmove(app_id + 1, app_id, SDL_strlen(app_id) + 1); + app_id[0] = '_'; + } + + // Create full path + char path[1024]; + SDL_snprintf(path, sizeof(path), "/org/libsdl/%s_%d", app_id, getpid()); + + SDL_free(app_id); + + return SDL_strdup(path); +} + +static char *GetAppDesktopPath() +{ + const char *desktop_suffix = ".desktop"; + const char *app_id = SDL_GetAppID(); + const size_t desktop_path_total_length = SDL_strlen(app_id) + SDL_strlen(desktop_suffix) + 1; + char *desktop_path = (char *)SDL_malloc(desktop_path_total_length); + if (!desktop_path) { + return NULL; + } + *desktop_path = '\0'; + SDL_strlcat(desktop_path, app_id, desktop_path_total_length); + SDL_strlcat(desktop_path, desktop_suffix, desktop_path_total_length); + + return desktop_path; +} + +static int ShouldShowProgress(SDL_ProgressState progressState) +{ + if (progressState == SDL_PROGRESS_STATE_INVALID || + progressState == SDL_PROGRESS_STATE_NONE) { + return 0; + } + + // Unity LauncherAPI only supports "normal" display of progress + return 1; +} + +bool DBUS_ApplyWindowProgress(SDL_VideoDevice *_this, SDL_Window *window) +{ + // Signal signature: + // signal com.canonical.Unity.LauncherEntry.Update (in s app_uri, in a{sv} properties) + + SDL_DBusContext *dbus = SDL_DBus_GetContext(); + + if (!dbus || !dbus->session_conn) { + return false; + } + + char *objectPath = GetDBUSObjectPath(); + if (!objectPath) { + return false; + } + + DBusMessage *msg = dbus->message_new_signal(objectPath, UnityLauncherAPI_DBUS_INTERFACE, UnityLauncherAPI_DBUS_SIGNAL); + if (!msg) { + SDL_free(objectPath); + return false; + } + + char *desktop_path = GetAppDesktopPath(); + if (!desktop_path) { + dbus->message_unref(msg); + SDL_free(objectPath); + return false; + } + + const char *progress_visible_str = "progress-visible"; + const char *progress_str = "progress"; + int dbus_type_boolean_str = DBUS_TYPE_BOOLEAN; + int dbus_type_double_str = DBUS_TYPE_DOUBLE; + + const int progress_visible = ShouldShowProgress(window->progress_state); + double progress = (double)window->progress_value; + + DBusMessageIter args, props; + dbus->message_iter_init_append(msg, &args); + dbus->message_iter_append_basic(&args, DBUS_TYPE_STRING, &desktop_path); // Setup app_uri paramter + dbus->message_iter_open_container(&args, DBUS_TYPE_ARRAY, "{sv}", &props); // Setup properties parameter + DBusMessageIter key_it, value_it; + // Set progress visible property + dbus->message_iter_open_container(&props, DBUS_TYPE_DICT_ENTRY, NULL, &key_it); + dbus->message_iter_append_basic(&key_it, DBUS_TYPE_STRING, &progress_visible_str); // Append progress-visible key data + dbus->message_iter_open_container(&key_it, DBUS_TYPE_VARIANT, (const char *)&dbus_type_boolean_str, &value_it); + dbus->message_iter_append_basic(&value_it, DBUS_TYPE_BOOLEAN, &progress_visible); // Append progress-visible value data + dbus->message_iter_close_container(&key_it, &value_it); + dbus->message_iter_close_container(&props, &key_it); + // Set progress value property + dbus->message_iter_open_container(&props, DBUS_TYPE_DICT_ENTRY, NULL, &key_it); + dbus->message_iter_append_basic(&key_it, DBUS_TYPE_STRING, &progress_str); // Append progress key data + dbus->message_iter_open_container(&key_it, DBUS_TYPE_VARIANT, (const char *)&dbus_type_double_str, &value_it); + dbus->message_iter_append_basic(&value_it, DBUS_TYPE_DOUBLE, &progress); // Append progress value data + dbus->message_iter_close_container(&key_it, &value_it); + dbus->message_iter_close_container(&props, &key_it); + dbus->message_iter_close_container(&args, &props); + + dbus->connection_send(dbus->session_conn, msg, NULL); + + SDL_free(desktop_path); + dbus->message_unref(msg); + SDL_free(objectPath); + + return true; +} + +#endif // SDL_USE_LIBDBUS diff --git a/libs/SDL3/src/video/vita/SDL_vitagles_pvr_c.h b/libs/SDL3/src/core/linux/SDL_progressbar.h similarity index 66% rename from libs/SDL3/src/video/vita/SDL_vitagles_pvr_c.h rename to libs/SDL3/src/core/linux/SDL_progressbar.h index ad87212b..da9b815f 100644 --- a/libs/SDL3/src/video/vita/SDL_vitagles_pvr_c.h +++ b/libs/SDL3/src/core/linux/SDL_progressbar.h @@ -19,14 +19,12 @@ 3. This notice may not be removed or altered from any source distribution. */ -#ifndef SDL_vitagles_pvr_c_h_ -#define SDL_vitagles_pvr_c_h_ +#ifndef SDL_prograssbar_h_ +#define SDL_prograssbar_h_ -#include "SDL_vitavideo.h" +#include "../../video/SDL_sysvideo.h" +#include "SDL_internal.h" -extern bool VITA_GLES_MakeCurrent(SDL_VideoDevice *_this, SDL_Window *window, SDL_GLContext context); -extern bool VITA_GLES_SwapWindow(SDL_VideoDevice *_this, SDL_Window *window); -extern SDL_GLContext VITA_GLES_CreateContext(SDL_VideoDevice *_this, SDL_Window *window); -extern bool VITA_GLES_LoadLibrary(SDL_VideoDevice *_this, const char *path); +extern bool DBUS_ApplyWindowProgress(SDL_VideoDevice *_this, SDL_Window *window); -#endif // SDL_vitagles_pvr_c_h_ +#endif // SDL_prograssbar_h_ diff --git a/libs/SDL3/src/core/linux/SDL_udev.c b/libs/SDL3/src/core/linux/SDL_udev.c index 907c34c7..def0e6a8 100644 --- a/libs/SDL3/src/core/linux/SDL_udev.c +++ b/libs/SDL3/src/core/linux/SDL_udev.c @@ -224,7 +224,7 @@ bool SDL_UDEV_GetProductInfo(const char *device_path, Uint16 *vendor, Uint16 *pr struct stat statbuf; char type; struct udev_device *dev; - const char* val; + const char *val; int class_temp; if (!_this) { diff --git a/libs/SDL3/src/core/ngage/SDL_ngage.cpp b/libs/SDL3/src/core/ngage/SDL_ngage.cpp new file mode 100644 index 00000000..5f14af1d --- /dev/null +++ b/libs/SDL3/src/core/ngage/SDL_ngage.cpp @@ -0,0 +1,77 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +bool NGAGE_IsClassicModel() +{ + int phone_id; + HAL::Get(HALData::EMachineUid, phone_id); + + return (0x101f8c19 == phone_id); +} + +void NGAGE_printf(const char *fmt, ...) +{ + char buffer[512] = { 0 }; + + va_list ap; + va_start(ap, fmt); + vsprintf(buffer, fmt, ap); + va_end(ap); + + TBuf<512> buf; + buf.Copy(TPtrC8((TText8 *)buffer)); + + RDebug::Print(_L("%S"), &buf); +} + +void NGAGE_vnprintf(char *buf, size_t size, const char *fmt, va_list ap) +{ + char buffer[512] = { 0 }; + + vsprintf(buffer, fmt, ap); + + TBuf<512> tbuf; + tbuf.Copy(TPtrC8((TText8 *)buffer)); + + RDebug::Print(_L("%S"), &tbuf); + + strncpy(buf, buffer, size - 1); + buf[size - 1] = '\0'; +} + +TInt NGAGE_GetFreeHeapMemory() +{ + TInt free = 0; + return User::Available(free); +} + +#ifdef __cplusplus +} +#endif diff --git a/libs/SDL3/src/core/ngage/SDL_ngage.h b/libs/SDL3/src/core/ngage/SDL_ngage.h new file mode 100644 index 00000000..66c4c60e --- /dev/null +++ b/libs/SDL3/src/core/ngage/SDL_ngage.h @@ -0,0 +1,36 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +#ifndef SDL_ngage_h +#define SDL_ngage_h + +#ifdef __cplusplus +extern "C" { +#endif + +bool NGAGE_IsClassicModel(); + +#ifdef __cplusplus +} +#endif + +#endif /* SDL_ngage_h */ diff --git a/libs/SDL3/src/core/windows/SDL_gameinput.c b/libs/SDL3/src/core/windows/SDL_gameinput.cpp similarity index 85% rename from libs/SDL3/src/core/windows/SDL_gameinput.c rename to libs/SDL3/src/core/windows/SDL_gameinput.cpp index 9ac5912d..e2ea3fb4 100644 --- a/libs/SDL3/src/core/windows/SDL_gameinput.c +++ b/libs/SDL3/src/core/windows/SDL_gameinput.cpp @@ -25,16 +25,11 @@ #include "SDL_windows.h" #include "SDL_gameinput.h" -#ifdef SDL_PLATFORM_WIN32 -#include -// {11BE2A7E-4254-445A-9C09-FFC40F006918} -DEFINE_GUID(SDL_IID_GameInput, 0x11BE2A7E, 0x4254, 0x445A, 0x9C, 0x09, 0xFF, 0xC4, 0x0F, 0x00, 0x69, 0x18); -#endif - static SDL_SharedObject *g_hGameInputDLL; static IGameInput *g_pGameInput; static int g_nGameInputRefCount; + bool SDL_InitGameInput(IGameInput **ppGameInput) { if (g_nGameInputRefCount == 0) { @@ -43,7 +38,7 @@ bool SDL_InitGameInput(IGameInput **ppGameInput) return false; } - typedef HRESULT (WINAPI *GameInputCreate_t)(IGameInput * *gameInput); + typedef HRESULT (WINAPI *GameInputCreate_t)(IGameInput **gameInput); GameInputCreate_t GameInputCreateFunc = (GameInputCreate_t)SDL_LoadFunction(g_hGameInputDLL, "GameInputCreate"); if (!GameInputCreateFunc) { SDL_UnloadObject(g_hGameInputDLL); @@ -58,15 +53,19 @@ bool SDL_InitGameInput(IGameInput **ppGameInput) } #ifdef SDL_PLATFORM_WIN32 - hr = IGameInput_QueryInterface(pGameInput, &SDL_IID_GameInput, (void **)&g_pGameInput); - IGameInput_Release(pGameInput); +#if GAMEINPUT_API_VERSION >= 1 + hr = pGameInput->QueryInterface(IID_IGameInput, (void **)&g_pGameInput); +#else + // We require GameInput v1.1 or newer + hr = E_NOINTERFACE; +#endif + pGameInput->Release(); if (FAILED(hr)) { SDL_UnloadObject(g_hGameInputDLL); return WIN_SetErrorFromHRESULT("GameInput QueryInterface failed", hr); } #else // Assume that the version we get is compatible with the current SDK - // If that isn't the case, define the correct GUID for SDL_IID_GameInput above g_pGameInput = pGameInput; #endif } @@ -85,7 +84,7 @@ void SDL_QuitGameInput(void) --g_nGameInputRefCount; if (g_nGameInputRefCount == 0) { if (g_pGameInput) { - IGameInput_Release(g_pGameInput); + g_pGameInput->Release(); g_pGameInput = NULL; } if (g_hGameInputDLL) { diff --git a/libs/SDL3/src/core/windows/SDL_gameinput.h b/libs/SDL3/src/core/windows/SDL_gameinput.h index 0022c0bd..4d2beb56 100644 --- a/libs/SDL3/src/core/windows/SDL_gameinput.h +++ b/libs/SDL3/src/core/windows/SDL_gameinput.h @@ -25,9 +25,16 @@ #ifdef HAVE_GAMEINPUT_H -#define COBJMACROS #include +#ifndef GAMEINPUT_API_VERSION +#define GAMEINPUT_API_VERSION 0 +#endif + +#if GAMEINPUT_API_VERSION == 1 +using namespace GameInput::v1; +#endif + extern bool SDL_InitGameInput(IGameInput **ppGameInput); extern void SDL_QuitGameInput(void); diff --git a/libs/SDL3/src/core/windows/SDL_windows.c b/libs/SDL3/src/core/windows/SDL_windows.c index 3259e787..d96d8e0b 100644 --- a/libs/SDL3/src/core/windows/SDL_windows.c +++ b/libs/SDL3/src/core/windows/SDL_windows.c @@ -261,7 +261,7 @@ char *WIN_LookupAudioDeviceName(const WCHAR *name, const GUID *guid) char *result = NULL; if (WIN_IsEqualGUID(guid, &nullguid)) { - return WIN_StringToUTF8(name); // No GUID, go with what we've got. + return WIN_StringToUTF8W(name); // No GUID, go with what we've got. } ptr = (const unsigned char *)guid; @@ -270,37 +270,37 @@ char *WIN_LookupAudioDeviceName(const WCHAR *name, const GUID *guid) ptr[3], ptr[2], ptr[1], ptr[0], ptr[5], ptr[4], ptr[7], ptr[6], ptr[8], ptr[9], ptr[10], ptr[11], ptr[12], ptr[13], ptr[14], ptr[15]); - strw = WIN_UTF8ToString(keystr); + strw = WIN_UTF8ToStringW(keystr); rc = (RegOpenKeyExW(HKEY_LOCAL_MACHINE, strw, 0, KEY_QUERY_VALUE, &hkey) == ERROR_SUCCESS); SDL_free(strw); if (!rc) { - return WIN_StringToUTF8(name); // oh well. + return WIN_StringToUTF8W(name); // oh well. } rc = (RegQueryValueExW(hkey, L"Name", NULL, NULL, NULL, &len) == ERROR_SUCCESS); if (!rc) { RegCloseKey(hkey); - return WIN_StringToUTF8(name); // oh well. + return WIN_StringToUTF8W(name); // oh well. } strw = (WCHAR *)SDL_malloc(len + sizeof(WCHAR)); if (!strw) { RegCloseKey(hkey); - return WIN_StringToUTF8(name); // oh well. + return WIN_StringToUTF8W(name); // oh well. } rc = (RegQueryValueExW(hkey, L"Name", NULL, NULL, (LPBYTE)strw, &len) == ERROR_SUCCESS); RegCloseKey(hkey); if (!rc) { SDL_free(strw); - return WIN_StringToUTF8(name); // oh well. + return WIN_StringToUTF8W(name); // oh well. } strw[len / 2] = 0; // make sure it's null-terminated. - result = WIN_StringToUTF8(strw); + result = WIN_StringToUTF8W(strw); SDL_free(strw); - return result ? result : WIN_StringToUTF8(name); + return result ? result : WIN_StringToUTF8W(name); #endif } diff --git a/libs/SDL3/src/core/windows/SDL_xinput.c b/libs/SDL3/src/core/windows/SDL_xinput.c index ba5e4c10..53dab5e5 100644 --- a/libs/SDL3/src/core/windows/SDL_xinput.c +++ b/libs/SDL3/src/core/windows/SDL_xinput.c @@ -32,7 +32,6 @@ XInputSetState_t SDL_XInputSetState = NULL; XInputGetCapabilities_t SDL_XInputGetCapabilities = NULL; XInputGetCapabilitiesEx_t SDL_XInputGetCapabilitiesEx = NULL; XInputGetBatteryInformation_t SDL_XInputGetBatteryInformation = NULL; -DWORD SDL_XInputVersion = 0; static HMODULE s_pXInputDLL = NULL; static int s_XInputDLLRefCount = 0; @@ -54,9 +53,6 @@ bool WIN_LoadXInputDLL(void) SDL_XInputGetCapabilities = (XInputGetCapabilities_t)XInputGetCapabilities; SDL_XInputGetBatteryInformation = (XInputGetBatteryInformation_t)XInputGetBatteryInformation; - // XInput 1.4 ships with Windows 8 and 8.1: - SDL_XInputVersion = (1 << 16) | 4; - return true; } @@ -68,8 +64,6 @@ void WIN_UnloadXInputDLL(void) bool WIN_LoadXInputDLL(void) { - DWORD version = 0; - if (s_pXInputDLL) { SDL_assert(s_XInputDLLRefCount > 0); s_XInputDLLRefCount++; @@ -80,15 +74,10 @@ bool WIN_LoadXInputDLL(void) * This is XInput emulation over Windows.Gaming.Input, and has all the * limitations of that API (no devices at startup, no background input, etc.) */ - version = (1 << 16) | 4; s_pXInputDLL = LoadLibrary(TEXT("XInput1_4.dll")); // 1.4 Ships with Windows 8. if (!s_pXInputDLL) { - version = (1 << 16) | 3; s_pXInputDLL = LoadLibrary(TEXT("XInput1_3.dll")); // 1.3 can be installed as a redistributable component. } - if (!s_pXInputDLL) { - s_pXInputDLL = LoadLibrary(TEXT("bin\\XInput1_3.dll")); - } if (!s_pXInputDLL) { // "9.1.0" Ships with Vista and Win7, and is more limited than 1.3+ (e.g. XInputGetStateEx is not available.) s_pXInputDLL = LoadLibrary(TEXT("XInput9_1_0.dll")); @@ -98,7 +87,6 @@ bool WIN_LoadXInputDLL(void) } SDL_assert(s_XInputDLLRefCount == 0); - SDL_XInputVersion = version; s_XInputDLLRefCount = 1; // 100 is the ordinal for _XInputGetStateEx, which returns the same struct as XinputGetState, but with extra data in wButtons for the guide button, we think... diff --git a/libs/SDL3/src/core/windows/SDL_xinput.h b/libs/SDL3/src/core/windows/SDL_xinput.h index d499cd58..2306ba43 100644 --- a/libs/SDL3/src/core/windows/SDL_xinput.h +++ b/libs/SDL3/src/core/windows/SDL_xinput.h @@ -260,7 +260,6 @@ extern XInputSetState_t SDL_XInputSetState; extern XInputGetCapabilities_t SDL_XInputGetCapabilities; extern XInputGetCapabilitiesEx_t SDL_XInputGetCapabilitiesEx; extern XInputGetBatteryInformation_t SDL_XInputGetBatteryInformation; -extern DWORD SDL_XInputVersion; // ((major << 16) & 0xFF00) | (minor & 0xFF) // Ends C function definitions when using C++ #ifdef __cplusplus diff --git a/libs/SDL3/src/core/windows/version.rc b/libs/SDL3/src/core/windows/version.rc index 4f94933a..0ae85b71 100644 --- a/libs/SDL3/src/core/windows/version.rc +++ b/libs/SDL3/src/core/windows/version.rc @@ -9,8 +9,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US // VS_VERSION_INFO VERSIONINFO - FILEVERSION 3,2,10,0 - PRODUCTVERSION 3,2,10,0 + FILEVERSION 3,3,0,0 + PRODUCTVERSION 3,3,0,0 FILEFLAGSMASK 0x3fL FILEFLAGS 0x0L FILEOS 0x40004L @@ -23,12 +23,12 @@ BEGIN BEGIN VALUE "CompanyName", "\0" VALUE "FileDescription", "SDL\0" - VALUE "FileVersion", "3, 2, 10, 0\0" + VALUE "FileVersion", "3, 3, 0, 0\0" VALUE "InternalName", "SDL\0" VALUE "LegalCopyright", "Copyright (C) 2025 Sam Lantinga\0" VALUE "OriginalFilename", "SDL3.dll\0" VALUE "ProductName", "Simple DirectMedia Layer\0" - VALUE "ProductVersion", "3, 2, 10, 0\0" + VALUE "ProductVersion", "3, 3, 0, 0\0" END END BLOCK "VarFileInfo" diff --git a/libs/SDL3/src/cpuinfo/SDL_cpuinfo.c b/libs/SDL3/src/cpuinfo/SDL_cpuinfo.c index 81d1e914..fc747c19 100644 --- a/libs/SDL3/src/cpuinfo/SDL_cpuinfo.c +++ b/libs/SDL3/src/cpuinfo/SDL_cpuinfo.c @@ -115,7 +115,11 @@ #define CPU_CFG2_LSX (1 << 6) #define CPU_CFG2_LASX (1 << 7) -#if defined(SDL_ALTIVEC_BLITTERS) && defined(HAVE_SETJMP) && !defined(SDL_PLATFORM_MACOS) && !defined(SDL_PLATFORM_OPENBSD) && !defined(SDL_PLATFORM_FREEBSD) +#if !defined(SDL_CPUINFO_DISABLED) && \ + !((defined(SDL_PLATFORM_MACOS) && (defined(__ppc__) || defined(__ppc64__))) || (defined(SDL_PLATFORM_OPENBSD) && defined(__powerpc__))) && \ + !(defined(SDL_PLATFORM_FREEBSD) && defined(__powerpc__)) && \ + !(defined(SDL_PLATFORM_LINUX) && defined(__powerpc__) && defined(HAVE_GETAUXVAL)) && \ + defined(SDL_ALTIVEC_BLITTERS) && defined(HAVE_SETJMP) /* This is the brute force way of detecting instruction sets... the idea is borrowed from the libmpeg2 library - thanks! */ @@ -344,6 +348,8 @@ static int CPU_haveAltiVec(void) elf_aux_info(AT_HWCAP, &cpufeatures, sizeof(cpufeatures)); altivec = cpufeatures & PPC_FEATURE_HAS_ALTIVEC; return altivec; +#elif defined(SDL_PLATFORM_LINUX) && defined(__powerpc__) && defined(HAVE_GETAUXVAL) + altivec = getauxval(AT_HWCAP) & PPC_FEATURE_HAS_ALTIVEC; #elif defined(SDL_ALTIVEC_BLITTERS) && defined(HAVE_SETJMP) void (*handler)(int sig); handler = signal(SIGILL, illegal_instruction); @@ -415,6 +421,12 @@ static int CPU_haveARMSIMD(void) return regs.r[0]; } +#elif defined(SDL_PLATFORM_NGAGE) +static int CPU_haveARMSIMD(void) +{ + // The RM920T is based on the ARMv4T architecture and doesn't have SIMD. + return 0; +} #else static int CPU_haveARMSIMD(void) { @@ -462,6 +474,8 @@ static int CPU_haveNEON(void) return 1; #elif defined(SDL_PLATFORM_3DS) return 0; +#elif defined(SDL_PLATFORM_NGAGE) + return 0; // The ARM920T is based on the ARMv4T architecture and doesn't have NEON. #elif defined(SDL_PLATFORM_APPLE) && defined(__ARM_ARCH) && (__ARM_ARCH >= 7) // (note that sysctlbyname("hw.optional.neon") doesn't work!) return 1; // all Apple ARMv7 chips and later have NEON. diff --git a/libs/SDL3/src/dialog/cocoa/SDL_cocoadialog.m b/libs/SDL3/src/dialog/cocoa/SDL_cocoadialog.m index fb9c5ad8..d12dab87 100644 --- a/libs/SDL3/src/dialog/cocoa/SDL_cocoadialog.m +++ b/libs/SDL3/src/dialog/cocoa/SDL_cocoadialog.m @@ -27,15 +27,44 @@ #import #import +static void AddFileExtensionType(NSMutableArray *types, const char *pattern_ptr) +{ + if (!*pattern_ptr) { + return; // in case the string had an extra ';' at the end. + } + + // -[UTType typeWithFilenameExtension] will return nil if there's a period in the string. It's better to + // allow too many files than not allow the one the user actually needs, so just take the part after the '.' + const char *dot = SDL_strrchr(pattern_ptr, '.'); + NSString *extstr = [NSString stringWithFormat: @"%s", dot ? (dot + 1) : pattern_ptr]; + if (@available(macOS 11.0, *)) { + UTType *uttype = [UTType typeWithFilenameExtension:extstr]; + if (uttype) { // still failed? Don't add the pattern. This is what the pre-macOS11 path does internally anyhow. + [types addObject:uttype]; + } + } else { + [types addObject:extstr]; + } +} + +static void ReactivateAfterDialog(void) +{ + for (NSRunningApplication *i in [NSRunningApplication runningApplicationsWithBundleIdentifier:@"com.apple.dock"]) { + [i activateWithOptions:0]; + break; + } + [NSApp activateIgnoringOtherApps:YES]; +} + void SDL_SYS_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_DialogFileCallback callback, void *userdata, SDL_PropertiesID props) { - SDL_Window* window = SDL_GetPointerProperty(props, SDL_PROP_FILE_DIALOG_WINDOW_POINTER, NULL); + SDL_Window *window = SDL_GetPointerProperty(props, SDL_PROP_FILE_DIALOG_WINDOW_POINTER, NULL); SDL_DialogFileFilter *filters = SDL_GetPointerProperty(props, SDL_PROP_FILE_DIALOG_FILTERS_POINTER, NULL); int nfilters = (int) SDL_GetNumberProperty(props, SDL_PROP_FILE_DIALOG_NFILTERS_NUMBER, 0); bool allow_many = SDL_GetBooleanProperty(props, SDL_PROP_FILE_DIALOG_MANY_BOOLEAN, false); - const char* default_location = SDL_GetStringProperty(props, SDL_PROP_FILE_DIALOG_LOCATION_STRING, NULL); - const char* title = SDL_GetStringProperty(props, SDL_PROP_FILE_DIALOG_TITLE_STRING, NULL); - const char* accept = SDL_GetStringProperty(props, SDL_PROP_FILE_DIALOG_ACCEPT_STRING, NULL); + const char *default_location = SDL_GetStringProperty(props, SDL_PROP_FILE_DIALOG_LOCATION_STRING, NULL); + const char *title = SDL_GetStringProperty(props, SDL_PROP_FILE_DIALOG_TITLE_STRING, NULL); + const char *accept = SDL_GetStringProperty(props, SDL_PROP_FILE_DIALOG_ACCEPT_STRING, NULL); if (filters) { const char *msg = validate_filters(filters, nfilters); @@ -87,7 +116,7 @@ void SDL_SYS_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_DialogFil if (filters) { // On macOS 11.0 and up, this is an array of UTType. Prior to that, it's an array of NSString - NSMutableArray *types = [[NSMutableArray alloc] initWithCapacity:nfilters ]; + NSMutableArray *types = [[NSMutableArray alloc] initWithCapacity:nfilters]; int has_all_files = 0; for (int i = 0; i < nfilters; i++) { @@ -102,21 +131,14 @@ void SDL_SYS_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_DialogFil for (char *c = pattern; *c; c++) { if (*c == ';') { *c = '\0'; - if(@available(macOS 11.0, *)) { - [types addObject: [UTType typeWithFilenameExtension:[NSString stringWithFormat: @"%s", pattern_ptr]]]; - } else { - [types addObject: [NSString stringWithFormat: @"%s", pattern_ptr]]; - } + AddFileExtensionType(types, pattern_ptr); pattern_ptr = c + 1; } else if (*c == '*') { has_all_files = 1; } } - if(@available(macOS 11.0, *)) { - [types addObject: [UTType typeWithFilenameExtension:[NSString stringWithFormat: @"%s", pattern_ptr]]]; - } else { - [types addObject: [NSString stringWithFormat: @"%s", pattern_ptr]]; - } + + AddFileExtensionType(types, pattern_ptr); // get the last piece of the string. SDL_free(pattern); } @@ -148,7 +170,7 @@ void SDL_SYS_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_DialogFil [dialog beginSheetModalForWindow:w completionHandler:^(NSInteger result) { if (result == NSModalResponseOK) { if (dialog_as_open) { - NSArray* urls = [dialog_as_open URLs]; + NSArray *urls = [dialog_as_open URLs]; const char *files[[urls count] + 1]; for (int i = 0; i < [urls count]; i++) { files[i] = [[[urls objectAtIndex:i] path] UTF8String]; @@ -163,11 +185,13 @@ void SDL_SYS_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_DialogFil const char *files[1] = { NULL }; callback(userdata, files, -1); } + + ReactivateAfterDialog(); }]; } else { if ([dialog runModal] == NSModalResponseOK) { if (dialog_as_open) { - NSArray* urls = [dialog_as_open URLs]; + NSArray *urls = [dialog_as_open URLs]; const char *files[[urls count] + 1]; for (int i = 0; i < [urls count]; i++) { files[i] = [[[urls objectAtIndex:i] path] UTF8String]; @@ -182,6 +206,7 @@ void SDL_SYS_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_DialogFil const char *files[1] = { NULL }; callback(userdata, files, -1); } + ReactivateAfterDialog(); } } diff --git a/libs/SDL3/src/dialog/haiku/SDL_haikudialog.cc b/libs/SDL3/src/dialog/haiku/SDL_haikudialog.cc index d60e3434..539d6a6a 100644 --- a/libs/SDL3/src/dialog/haiku/SDL_haikudialog.cc +++ b/libs/SDL3/src/dialog/haiku/SDL_haikudialog.cc @@ -163,7 +163,7 @@ public: case B_CANCEL: // Whenever the dialog is closed (Cancel but also after Open and Save) { nFiles = m_files.size(); - const char* files[nFiles + 1]; + const char *files[nFiles + 1]; for (int i = 0; i < nFiles; i++) { files[i] = m_files[i].c_str(); } @@ -194,14 +194,14 @@ private: void SDL_SYS_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_DialogFileCallback callback, void *userdata, SDL_PropertiesID props) { - SDL_Window* window = (SDL_Window*) SDL_GetPointerProperty(props, SDL_PROP_FILE_DIALOG_WINDOW_POINTER, NULL); - SDL_DialogFileFilter* filters = (SDL_DialogFileFilter*) SDL_GetPointerProperty(props, SDL_PROP_FILE_DIALOG_FILTERS_POINTER, NULL); + SDL_Window *window = (SDL_Window *)SDL_GetPointerProperty(props, SDL_PROP_FILE_DIALOG_WINDOW_POINTER, NULL); + SDL_DialogFileFilter *filters = (SDL_DialogFileFilter *)SDL_GetPointerProperty(props, SDL_PROP_FILE_DIALOG_FILTERS_POINTER, NULL); int nfilters = (int) SDL_GetNumberProperty(props, SDL_PROP_FILE_DIALOG_NFILTERS_NUMBER, 0); bool many = SDL_GetBooleanProperty(props, SDL_PROP_FILE_DIALOG_MANY_BOOLEAN, false); - const char* location = SDL_GetStringProperty(props, SDL_PROP_FILE_DIALOG_LOCATION_STRING, NULL); - const char* title = SDL_GetStringProperty(props, SDL_PROP_FILE_DIALOG_TITLE_STRING, NULL); - const char* accept = SDL_GetStringProperty(props, SDL_PROP_FILE_DIALOG_ACCEPT_STRING, NULL); - const char* cancel = SDL_GetStringProperty(props, SDL_PROP_FILE_DIALOG_CANCEL_STRING, NULL); + const char *location = SDL_GetStringProperty(props, SDL_PROP_FILE_DIALOG_LOCATION_STRING, NULL); + const char *title = SDL_GetStringProperty(props, SDL_PROP_FILE_DIALOG_TITLE_STRING, NULL); + const char *accept = SDL_GetStringProperty(props, SDL_PROP_FILE_DIALOG_ACCEPT_STRING, NULL); + const char *cancel = SDL_GetStringProperty(props, SDL_PROP_FILE_DIALOG_CANCEL_STRING, NULL); bool modal = !!window; @@ -222,7 +222,7 @@ void SDL_SYS_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_DialogFil }; if (!SDL_InitBeApp()) { - char* err = SDL_strdup(SDL_GetError()); + char *err = SDL_strdup(SDL_GetError()); SDL_SetError("Couldn't init Be app: %s", err); SDL_free(err); callback(userdata, NULL, -1); @@ -251,9 +251,9 @@ void SDL_SYS_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_DialogFil SDLBRefFilter *filter = new(std::nothrow) SDLBRefFilter(filters, nfilters); if (looper == NULL || messenger == NULL || filter == NULL) { - SDL_free(looper); - SDL_free(messenger); - SDL_free(filter); + delete looper; + delete messenger; + delete filter; SDL_OutOfMemory(); callback(userdata, NULL, -1); return; diff --git a/libs/SDL3/src/dialog/unix/SDL_portaldialog.c b/libs/SDL3/src/dialog/unix/SDL_portaldialog.c index efecd124..dadfc9f5 100644 --- a/libs/SDL3/src/dialog/unix/SDL_portaldialog.c +++ b/libs/SDL3/src/dialog/unix/SDL_portaldialog.c @@ -288,12 +288,12 @@ void SDL_Portal_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_Dialog const char *method; const char *method_title; - SDL_Window* window = SDL_GetPointerProperty(props, SDL_PROP_FILE_DIALOG_WINDOW_POINTER, NULL); + SDL_Window *window = SDL_GetPointerProperty(props, SDL_PROP_FILE_DIALOG_WINDOW_POINTER, NULL); SDL_DialogFileFilter *filters = SDL_GetPointerProperty(props, SDL_PROP_FILE_DIALOG_FILTERS_POINTER, NULL); int nfilters = (int) SDL_GetNumberProperty(props, SDL_PROP_FILE_DIALOG_NFILTERS_NUMBER, 0); bool allow_many = SDL_GetBooleanProperty(props, SDL_PROP_FILE_DIALOG_MANY_BOOLEAN, false); - const char* default_location = SDL_GetStringProperty(props, SDL_PROP_FILE_DIALOG_LOCATION_STRING, NULL); - const char* accept = SDL_GetStringProperty(props, SDL_PROP_FILE_DIALOG_ACCEPT_STRING, NULL); + const char *default_location = SDL_GetStringProperty(props, SDL_PROP_FILE_DIALOG_LOCATION_STRING, NULL); + const char *accept = SDL_GetStringProperty(props, SDL_PROP_FILE_DIALOG_ACCEPT_STRING, NULL); bool open_folders = false; switch (type) { diff --git a/libs/SDL3/src/dialog/windows/SDL_windowsdialog.c b/libs/SDL3/src/dialog/windows/SDL_windowsdialog.c index 2de224fe..2f4a12e8 100644 --- a/libs/SDL3/src/dialog/windows/SDL_windowsdialog.c +++ b/libs/SDL3/src/dialog/windows/SDL_windowsdialog.c @@ -261,7 +261,7 @@ void windows_ShowFileDialog(void *ptr) chosen_files_list[nfiles] = NULL; - if (WideCharToMultiByte(CP_UTF8, 0, file_ptr, -1, chosen_folder, MAX_PATH, NULL, NULL) >= MAX_PATH) { + if (WideCharToMultiByte(CP_UTF8, 0, file_ptr, -1, chosen_folder, MAX_PATH, NULL, NULL) == 0) { SDL_SetError("Path too long or invalid character in path"); SDL_free(chosen_files_list); callback(userdata, NULL, -1); @@ -273,11 +273,11 @@ void windows_ShowFileDialog(void *ptr) SDL_strlcpy(chosen_file, chosen_folder, MAX_PATH); chosen_file[chosen_folder_size] = '\\'; - file_ptr += SDL_strlen(chosen_folder) + 1; + file_ptr += SDL_wcslen(file_ptr) + 1; while (*file_ptr) { nfiles++; - char **new_cfl = (char **) SDL_realloc(chosen_files_list, sizeof(char*) * (nfiles + 1)); + char **new_cfl = (char **) SDL_realloc(chosen_files_list, sizeof(char *) * (nfiles + 1)); if (!new_cfl) { for (size_t i = 0; i < nfiles - 1; i++) { @@ -295,7 +295,7 @@ void windows_ShowFileDialog(void *ptr) int diff = ((int) chosen_folder_size) + 1; - if (WideCharToMultiByte(CP_UTF8, 0, file_ptr, -1, chosen_file + diff, MAX_PATH - diff, NULL, NULL) >= MAX_PATH - diff) { + if (WideCharToMultiByte(CP_UTF8, 0, file_ptr, -1, chosen_file + diff, MAX_PATH - diff, NULL, NULL) == 0) { SDL_SetError("Path too long or invalid character in path"); for (size_t i = 0; i < nfiles - 1; i++) { @@ -308,7 +308,7 @@ void windows_ShowFileDialog(void *ptr) return; } - file_ptr += SDL_strlen(chosen_file) + 1 - diff; + file_ptr += SDL_wcslen(file_ptr) + 1; chosen_files_list[nfiles - 1] = SDL_strdup(chosen_file); @@ -327,7 +327,7 @@ void windows_ShowFileDialog(void *ptr) // If the user chose only one file, it's all just one string if (nfiles == 0) { nfiles++; - char **new_cfl = (char **) SDL_realloc(chosen_files_list, sizeof(char*) * (nfiles + 1)); + char **new_cfl = (char **) SDL_realloc(chosen_files_list, sizeof(char *) * (nfiles + 1)); if (!new_cfl) { SDL_free(chosen_files_list); @@ -348,7 +348,7 @@ void windows_ShowFileDialog(void *ptr) } } - callback(userdata, (const char * const*) chosen_files_list, getFilterIndex(dialog.nFilterIndex)); + callback(userdata, (const char * const *) chosen_files_list, getFilterIndex(dialog.nFilterIndex)); for (size_t i = 0; i < nfiles; i++) { SDL_free(chosen_files_list[i]); @@ -443,11 +443,11 @@ void windows_ShowFolderDialog(void *ptr) SHGetPathFromIDListW(lpItem, buffer); char *chosen_file = WIN_StringToUTF8W(buffer); const char *files[2] = { chosen_file, NULL }; - callback(userdata, (const char * const*) files, -1); + callback(userdata, (const char * const *) files, -1); SDL_free(chosen_file); } else { const char *files[1] = { NULL }; - callback(userdata, (const char * const*) files, -1); + callback(userdata, (const char * const *) files, -1); } } @@ -516,6 +516,14 @@ static void ShowFileDialog(SDL_DialogFileCallback callback, void *userdata, SDL_ filters_str = win_get_filters(filters, nfilters); + DWORD flags = 0; + if (allow_many) { + flags |= OFN_ALLOWMULTISELECT; + } + if (is_save) { + flags |= OFN_OVERWRITEPROMPT; + } + if (!filters_str && filters) { callback(userdata, NULL, -1); SDL_free(args); @@ -526,7 +534,7 @@ static void ShowFileDialog(SDL_DialogFileCallback callback, void *userdata, SDL_ args->filters_str = filters_str; args->default_file = default_location ? SDL_strdup(default_location) : NULL; args->parent = window; - args->flags = allow_many ? OFN_ALLOWMULTISELECT : 0; + args->flags = flags; args->callback = callback; args->userdata = userdata; args->title = title ? SDL_strdup(title) : NULL; diff --git a/libs/SDL3/src/dynapi/SDL_dynapi.h b/libs/SDL3/src/dynapi/SDL_dynapi.h index 99ef9a9e..e975be08 100644 --- a/libs/SDL3/src/dynapi/SDL_dynapi.h +++ b/libs/SDL3/src/dynapi/SDL_dynapi.h @@ -63,6 +63,8 @@ #define SDL_DYNAMIC_API 0 // vitasdk doesn't support dynamic linking #elif defined(SDL_PLATFORM_3DS) #define SDL_DYNAMIC_API 0 // devkitARM doesn't support dynamic linking +#elif defined(SDL_PLATFORM_NGAGE) +#define SDL_DYNAMIC_API 0 #elif defined(DYNAPI_NEEDS_DLOPEN) && !defined(HAVE_DLOPEN) #define SDL_DYNAMIC_API 0 // we need dlopen(), but don't have it.... #endif diff --git a/libs/SDL3/src/dynapi/SDL_dynapi.sym b/libs/SDL3/src/dynapi/SDL_dynapi.sym index 73f34843..91bd4300 100644 --- a/libs/SDL3/src/dynapi/SDL_dynapi.sym +++ b/libs/SDL3/src/dynapi/SDL_dynapi.sym @@ -1234,6 +1234,26 @@ SDL3_0.0.0 { SDL_ClickTrayEntry; SDL_UpdateTrays; SDL_StretchSurface; + SDL_SetRelativeMouseTransform; + SDL_RenderTexture9GridTiled; + SDL_SetDefaultTextureScaleMode; + SDL_GetDefaultTextureScaleMode; + SDL_CreateGPURenderState; + SDL_SetGPURenderStateFragmentUniforms; + SDL_SetRenderGPUState; + SDL_DestroyGPURenderState; + SDL_SetWindowProgressState; + SDL_SetWindowProgressValue; + SDL_GetWindowProgressState; + SDL_GetWindowProgressValue; + SDL_SetRenderTextureAddressMode; + SDL_GetRenderTextureAddressMode; + SDL_GetGPUDeviceProperties; + SDL_CreateGPURenderer; + SDL_PutAudioStreamPlanarData; + SDL_SetAudioIterationCallbacks; + SDL_GetEventDescription; + SDL_PutAudioStreamDataNoCopy; # extra symbols go here (don't modify this line) local: *; }; diff --git a/libs/SDL3/src/dynapi/SDL_dynapi_overrides.h b/libs/SDL3/src/dynapi/SDL_dynapi_overrides.h index 77fd553c..fcc0bad7 100644 --- a/libs/SDL3/src/dynapi/SDL_dynapi_overrides.h +++ b/libs/SDL3/src/dynapi/SDL_dynapi_overrides.h @@ -1259,3 +1259,23 @@ #define SDL_ClickTrayEntry SDL_ClickTrayEntry_REAL #define SDL_UpdateTrays SDL_UpdateTrays_REAL #define SDL_StretchSurface SDL_StretchSurface_REAL +#define SDL_SetRelativeMouseTransform SDL_SetRelativeMouseTransform_REAL +#define SDL_RenderTexture9GridTiled SDL_RenderTexture9GridTiled_REAL +#define SDL_SetDefaultTextureScaleMode SDL_SetDefaultTextureScaleMode_REAL +#define SDL_GetDefaultTextureScaleMode SDL_GetDefaultTextureScaleMode_REAL +#define SDL_CreateGPURenderState SDL_CreateGPURenderState_REAL +#define SDL_SetGPURenderStateFragmentUniforms SDL_SetGPURenderStateFragmentUniforms_REAL +#define SDL_SetRenderGPUState SDL_SetRenderGPUState_REAL +#define SDL_DestroyGPURenderState SDL_DestroyGPURenderState_REAL +#define SDL_SetWindowProgressState SDL_SetWindowProgressState_REAL +#define SDL_SetWindowProgressValue SDL_SetWindowProgressValue_REAL +#define SDL_GetWindowProgressState SDL_GetWindowProgressState_REAL +#define SDL_GetWindowProgressValue SDL_GetWindowProgressValue_REAL +#define SDL_SetRenderTextureAddressMode SDL_SetRenderTextureAddressMode_REAL +#define SDL_GetRenderTextureAddressMode SDL_GetRenderTextureAddressMode_REAL +#define SDL_GetGPUDeviceProperties SDL_GetGPUDeviceProperties_REAL +#define SDL_CreateGPURenderer SDL_CreateGPURenderer_REAL +#define SDL_PutAudioStreamPlanarData SDL_PutAudioStreamPlanarData_REAL +#define SDL_SetAudioIterationCallbacks SDL_SetAudioIterationCallbacks_REAL +#define SDL_GetEventDescription SDL_GetEventDescription_REAL +#define SDL_PutAudioStreamDataNoCopy SDL_PutAudioStreamDataNoCopy_REAL diff --git a/libs/SDL3/src/dynapi/SDL_dynapi_procs.h b/libs/SDL3/src/dynapi/SDL_dynapi_procs.h index e86ac2a3..60e0dfea 100644 --- a/libs/SDL3/src/dynapi/SDL_dynapi_procs.h +++ b/libs/SDL3/src/dynapi/SDL_dynapi_procs.h @@ -137,7 +137,7 @@ SDL_DYNAPI_PROC(SDL_GPUSampler*,SDL_CreateGPUSampler,(SDL_GPUDevice *a, const SD SDL_DYNAPI_PROC(SDL_GPUShader*,SDL_CreateGPUShader,(SDL_GPUDevice *a, const SDL_GPUShaderCreateInfo *b),(a,b),return) SDL_DYNAPI_PROC(SDL_GPUTexture*,SDL_CreateGPUTexture,(SDL_GPUDevice *a, const SDL_GPUTextureCreateInfo *b),(a,b),return) SDL_DYNAPI_PROC(SDL_GPUTransferBuffer*,SDL_CreateGPUTransferBuffer,(SDL_GPUDevice *a, const SDL_GPUTransferBufferCreateInfo *b),(a,b),return) -SDL_DYNAPI_PROC(int,SDL_CreateHapticEffect,(SDL_Haptic *a, const SDL_HapticEffect *b),(a,b),return) +SDL_DYNAPI_PROC(SDL_HapticEffectID,SDL_CreateHapticEffect,(SDL_Haptic *a, const SDL_HapticEffect *b),(a,b),return) SDL_DYNAPI_PROC(SDL_Mutex*,SDL_CreateMutex,(void),(),return) SDL_DYNAPI_PROC(SDL_Palette*,SDL_CreatePalette,(int a),(a),return) SDL_DYNAPI_PROC(SDL_Window*,SDL_CreatePopupWindow,(SDL_Window *a, int b, int c, int d, int e, SDL_WindowFlags f),(a,b,c,d,e,f),return) @@ -171,7 +171,7 @@ SDL_DYNAPI_PROC(void,SDL_DestroyCondition,(SDL_Condition *a),(a),) SDL_DYNAPI_PROC(void,SDL_DestroyCursor,(SDL_Cursor *a),(a),) SDL_DYNAPI_PROC(void,SDL_DestroyEnvironment,(SDL_Environment *a),(a),) SDL_DYNAPI_PROC(void,SDL_DestroyGPUDevice,(SDL_GPUDevice *a),(a),) -SDL_DYNAPI_PROC(void,SDL_DestroyHapticEffect,(SDL_Haptic *a, int b),(a,b),) +SDL_DYNAPI_PROC(void,SDL_DestroyHapticEffect,(SDL_Haptic *a, SDL_HapticEffectID b),(a,b),) SDL_DYNAPI_PROC(void,SDL_DestroyMutex,(SDL_Mutex *a),(a),) SDL_DYNAPI_PROC(void,SDL_DestroyPalette,(SDL_Palette *a),(a),) SDL_DYNAPI_PROC(void,SDL_DestroyProcess,(SDL_Process *a),(a),) @@ -386,7 +386,7 @@ SDL_DYNAPI_PROC(SDL_JoystickID*,SDL_GetGamepads,(int *a),(a),return) SDL_DYNAPI_PROC(SDL_MouseButtonFlags,SDL_GetGlobalMouseState,(float *a, float *b),(a,b),return) SDL_DYNAPI_PROC(SDL_PropertiesID,SDL_GetGlobalProperties,(void),(),return) SDL_DYNAPI_PROC(SDL_Window*,SDL_GetGrabbedWindow,(void),(),return) -SDL_DYNAPI_PROC(bool,SDL_GetHapticEffectStatus,(SDL_Haptic *a, int b),(a,b),return) +SDL_DYNAPI_PROC(bool,SDL_GetHapticEffectStatus,(SDL_Haptic *a, SDL_HapticEffectID b),(a,b),return) SDL_DYNAPI_PROC(Uint32,SDL_GetHapticFeatures,(SDL_Haptic *a),(a),return) SDL_DYNAPI_PROC(SDL_Haptic*,SDL_GetHapticFromID,(SDL_HapticID a),(a),return) SDL_DYNAPI_PROC(SDL_HapticID,SDL_GetHapticID,(SDL_Haptic *a),(a),return) @@ -802,7 +802,7 @@ SDL_DYNAPI_PROC(bool,SDL_RumbleGamepadTriggers,(SDL_Gamepad *a, Uint16 b, Uint16 SDL_DYNAPI_PROC(bool,SDL_RumbleJoystick,(SDL_Joystick *a, Uint16 b, Uint16 c, Uint32 d),(a,b,c,d),return) SDL_DYNAPI_PROC(bool,SDL_RumbleJoystickTriggers,(SDL_Joystick *a, Uint16 b, Uint16 c, Uint32 d),(a,b,c,d),return) SDL_DYNAPI_PROC(int,SDL_RunApp,(int a, char *b[], SDL_main_func c, void *d),(a,b,c,d),return) -SDL_DYNAPI_PROC(bool,SDL_RunHapticEffect,(SDL_Haptic *a, int b, Uint32 c),(a,b,c),return) +SDL_DYNAPI_PROC(bool,SDL_RunHapticEffect,(SDL_Haptic *a, SDL_HapticEffectID b, Uint32 c),(a,b,c),return) SDL_DYNAPI_PROC(bool,SDL_SaveBMP,(SDL_Surface *a, const char *b),(a,b),return) SDL_DYNAPI_PROC(bool,SDL_SaveBMP_IO,(SDL_Surface *a, SDL_IOStream *b, bool c),(a,b,c),return) SDL_DYNAPI_PROC(SDL_Surface*,SDL_ScaleSurface,(SDL_Surface *a, int b, int c, SDL_ScaleMode d),(a,b,c,d),return) @@ -948,7 +948,7 @@ SDL_DYNAPI_PROC(void,SDL_SignalSemaphore,(SDL_Semaphore *a),(a),) SDL_DYNAPI_PROC(bool,SDL_StartTextInput,(SDL_Window *a),(a),return) SDL_DYNAPI_PROC(bool,SDL_StartTextInputWithProperties,(SDL_Window *a, SDL_PropertiesID b),(a,b),return) SDL_DYNAPI_PROC(Uint32,SDL_StepUTF8,(const char **a, size_t *b),(a,b),return) -SDL_DYNAPI_PROC(bool,SDL_StopHapticEffect,(SDL_Haptic *a, int b),(a,b),return) +SDL_DYNAPI_PROC(bool,SDL_StopHapticEffect,(SDL_Haptic *a, SDL_HapticEffectID b),(a,b),return) SDL_DYNAPI_PROC(bool,SDL_StopHapticEffects,(SDL_Haptic *a),(a),return) SDL_DYNAPI_PROC(bool,SDL_StopHapticRumble,(SDL_Haptic *a),(a),return) SDL_DYNAPI_PROC(bool,SDL_StopTextInput,(SDL_Window *a),(a),return) @@ -986,7 +986,7 @@ SDL_DYNAPI_PROC(void,SDL_UnmapGPUTransferBuffer,(SDL_GPUDevice *a, SDL_GPUTransf SDL_DYNAPI_PROC(void,SDL_UnregisterApp,(void),(),) SDL_DYNAPI_PROC(bool,SDL_UnsetEnvironmentVariable,(SDL_Environment *a, const char *b),(a,b),return) SDL_DYNAPI_PROC(void,SDL_UpdateGamepads,(void),(),) -SDL_DYNAPI_PROC(bool,SDL_UpdateHapticEffect,(SDL_Haptic *a, int b, const SDL_HapticEffect *c),(a,b,c),return) +SDL_DYNAPI_PROC(bool,SDL_UpdateHapticEffect,(SDL_Haptic *a, SDL_HapticEffectID b, const SDL_HapticEffect *c),(a,b,c),return) SDL_DYNAPI_PROC(void,SDL_UpdateJoysticks,(void),(),) SDL_DYNAPI_PROC(bool,SDL_UpdateNVTexture,(SDL_Texture *a, const SDL_Rect *b, const Uint8 *c, int d, const Uint8 *e, int f),(a,b,c,d,e,f),return) SDL_DYNAPI_PROC(void,SDL_UpdateSensors,(void),(),) @@ -1267,3 +1267,23 @@ SDL_DYNAPI_PROC(bool,SDL_AudioStreamDevicePaused,(SDL_AudioStream *a),(a),return SDL_DYNAPI_PROC(void,SDL_ClickTrayEntry,(SDL_TrayEntry *a),(a),) SDL_DYNAPI_PROC(void,SDL_UpdateTrays,(void),(),) SDL_DYNAPI_PROC(bool,SDL_StretchSurface,(SDL_Surface *a,const SDL_Rect *b,SDL_Surface *c,const SDL_Rect *d,SDL_ScaleMode e),(a,b,c,d,e),return) +SDL_DYNAPI_PROC(bool,SDL_SetRelativeMouseTransform,(SDL_MouseMotionTransformCallback a,void *b),(a,b),return) +SDL_DYNAPI_PROC(bool,SDL_RenderTexture9GridTiled,(SDL_Renderer *a,SDL_Texture *b,const SDL_FRect *c,float d,float e,float f,float g,float h,const SDL_FRect *i,float j),(a,b,c,d,e,f,g,h,i,j),return) +SDL_DYNAPI_PROC(bool,SDL_SetDefaultTextureScaleMode,(SDL_Renderer *a,SDL_ScaleMode b),(a,b),return) +SDL_DYNAPI_PROC(bool,SDL_GetDefaultTextureScaleMode,(SDL_Renderer *a,SDL_ScaleMode *b),(a,b),return) +SDL_DYNAPI_PROC(SDL_GPURenderState*,SDL_CreateGPURenderState,(SDL_Renderer *a,SDL_GPURenderStateDesc *b),(a,b),return) +SDL_DYNAPI_PROC(bool,SDL_SetGPURenderStateFragmentUniforms,(SDL_GPURenderState *a,Uint32 b,const void *c,Uint32 d),(a,b,c,d),return) +SDL_DYNAPI_PROC(bool,SDL_SetRenderGPUState,(SDL_Renderer *a,SDL_GPURenderState *b),(a,b),return) +SDL_DYNAPI_PROC(void,SDL_DestroyGPURenderState,(SDL_GPURenderState *a),(a),) +SDL_DYNAPI_PROC(bool,SDL_SetWindowProgressState,(SDL_Window *a,SDL_ProgressState b),(a,b),return) +SDL_DYNAPI_PROC(bool,SDL_SetWindowProgressValue,(SDL_Window *a,float b),(a,b),return) +SDL_DYNAPI_PROC(SDL_ProgressState,SDL_GetWindowProgressState,(SDL_Window *a),(a),return) +SDL_DYNAPI_PROC(float,SDL_GetWindowProgressValue,(SDL_Window *a),(a),return) +SDL_DYNAPI_PROC(bool,SDL_SetRenderTextureAddressMode,(SDL_Renderer *a,SDL_TextureAddressMode b,SDL_TextureAddressMode c),(a,b,c),return) +SDL_DYNAPI_PROC(bool,SDL_GetRenderTextureAddressMode,(SDL_Renderer *a,SDL_TextureAddressMode *b,SDL_TextureAddressMode *c),(a,b,c),return) +SDL_DYNAPI_PROC(SDL_PropertiesID,SDL_GetGPUDeviceProperties,(SDL_GPUDevice *a),(a),return) +SDL_DYNAPI_PROC(SDL_Renderer*,SDL_CreateGPURenderer,(SDL_Window *a,SDL_GPUShaderFormat b,SDL_GPUDevice **c),(a,b,c),return) +SDL_DYNAPI_PROC(bool,SDL_PutAudioStreamPlanarData,(SDL_AudioStream *a,const void * const*b,int c,int d),(a,b,c,d),return) +SDL_DYNAPI_PROC(bool,SDL_SetAudioIterationCallbacks,(SDL_AudioDeviceID a,SDL_AudioIterationCallback b,SDL_AudioIterationCallback c,void *d),(a,b,c,d),return) +SDL_DYNAPI_PROC(int,SDL_GetEventDescription,(const SDL_Event *a,char *b,int c),(a,b,c),return) +SDL_DYNAPI_PROC(bool,SDL_PutAudioStreamDataNoCopy,(SDL_AudioStream *a,const void *b,int c,SDL_AudioStreamDataCompleteCallback d,void *e),(a,b,c,d,e),return) diff --git a/libs/SDL3/src/events/SDL_clipboardevents.c b/libs/SDL3/src/events/SDL_clipboardevents.c index d5cf8ad7..529af920 100644 --- a/libs/SDL3/src/events/SDL_clipboardevents.c +++ b/libs/SDL3/src/events/SDL_clipboardevents.c @@ -29,17 +29,7 @@ void SDL_SendClipboardUpdate(bool owner, char **mime_types, size_t num_mime_types) { if (!owner) { - /* Clear our internal clipboard contents when external clipboard is set. - * - * Wayland recursively sends a data offer to the client from which the clipboard data originated, - * and as the client can't determine the origin of the offer, the clipboard must not be cleared, - * or the original data may be destroyed. Cleanup will be done in the backend when an offer - * cancellation event arrives. - */ - if (SDL_strcmp(SDL_GetCurrentVideoDriver(), "wayland") != 0) { - SDL_CancelClipboardData(0); - } - + SDL_CancelClipboardData(0); SDL_SaveClipboardMimeTypes((const char **)mime_types, num_mime_types); } diff --git a/libs/SDL3/src/events/SDL_events.c b/libs/SDL3/src/events/SDL_events.c index 349d5750..c04d52b2 100644 --- a/libs/SDL3/src/events/SDL_events.c +++ b/libs/SDL3/src/events/SDL_events.c @@ -430,26 +430,18 @@ static void SDLCALL SDL_EventLoggingChanged(void *userdata, const char *name, co SDL_EventLoggingVerbosity = (hint && *hint) ? SDL_clamp(SDL_atoi(hint), 0, 3) : 0; } -static void SDL_LogEvent(const SDL_Event *event) +int SDL_GetEventDescription(const SDL_Event *event, char *buf, int buflen) { + if (!event) { + return SDL_snprintf(buf, buflen, "(null)"); + } + static const char *pen_axisnames[] = { "PRESSURE", "XTILT", "YTILT", "DISTANCE", "ROTATION", "SLIDER", "TANGENTIAL_PRESSURE" }; SDL_COMPILE_TIME_ASSERT(pen_axisnames_array_matches, SDL_arraysize(pen_axisnames) == SDL_PEN_AXIS_COUNT); char name[64]; char details[128]; - // sensor/mouse/pen/finger motion are spammy, ignore these if they aren't demanded. - if ((SDL_EventLoggingVerbosity < 2) && - ((event->type == SDL_EVENT_MOUSE_MOTION) || - (event->type == SDL_EVENT_FINGER_MOTION) || - (event->type == SDL_EVENT_PEN_AXIS) || - (event->type == SDL_EVENT_PEN_MOTION) || - (event->type == SDL_EVENT_GAMEPAD_TOUCHPAD_MOTION) || - (event->type == SDL_EVENT_GAMEPAD_SENSOR_UPDATE) || - (event->type == SDL_EVENT_SENSOR_UPDATE))) { - return; - } - // this is to make (void)SDL_snprintf() calls cleaner. #define uint unsigned int @@ -633,9 +625,10 @@ static void SDL_LogEvent(const SDL_Event *event) #undef PRINT_MBUTTON_EVENT SDL_EVENT_CASE(SDL_EVENT_MOUSE_WHEEL) - (void)SDL_snprintf(details, sizeof(details), " (timestamp=%u windowid=%u which=%u x=%g y=%g direction=%s)", + (void)SDL_snprintf(details, sizeof(details), " (timestamp=%u windowid=%u which=%u x=%g y=%g integer_x=%d integer_y=%d direction=%s)", (uint)event->wheel.timestamp, (uint)event->wheel.windowID, (uint)event->wheel.which, event->wheel.x, event->wheel.y, + (int)event->wheel.integer_x, (int)event->wheel.integer_y, event->wheel.direction == SDL_MOUSEWHEEL_NORMAL ? "normal" : "flipped"); break; @@ -879,12 +872,45 @@ static void SDL_LogEvent(const SDL_Event *event) } break; } +#undef uint + int retval = 0; if (name[0]) { - SDL_Log("SDL EVENT: %s%s", name, details); + retval = SDL_snprintf(buf, buflen, "%s%s", name, details); + } else if (buf && (buflen > 0)) { + *buf = '\0'; + } + return retval; +} + +static void SDL_LogEvent(const SDL_Event *event) +{ + if (!event) { + return; } -#undef uint + // sensor/mouse/pen/finger motion are spammy, ignore these if they aren't demanded. + if ((SDL_EventLoggingVerbosity < 2) && + ((event->type == SDL_EVENT_MOUSE_MOTION) || + (event->type == SDL_EVENT_FINGER_MOTION) || + (event->type == SDL_EVENT_PEN_AXIS) || + (event->type == SDL_EVENT_PEN_MOTION) || + (event->type == SDL_EVENT_GAMEPAD_AXIS_MOTION) || + (event->type == SDL_EVENT_GAMEPAD_SENSOR_UPDATE) || + (event->type == SDL_EVENT_GAMEPAD_TOUCHPAD_MOTION) || + (event->type == SDL_EVENT_GAMEPAD_UPDATE_COMPLETE) || + (event->type == SDL_EVENT_JOYSTICK_AXIS_MOTION) || + (event->type == SDL_EVENT_JOYSTICK_UPDATE_COMPLETE) || + (event->type == SDL_EVENT_SENSOR_UPDATE))) { + return; + } + + char buf[256]; + const int rc = SDL_GetEventDescription(event, buf, sizeof (buf)); + SDL_assert(rc < sizeof (buf)); // if this overflows, we should make `buf` larger, but this is currently larger than the max SDL_GetEventDescription returns. + if (buf[0]) { + SDL_Log("SDL EVENT: %s", buf); + } } void SDL_StopEventLoop(void) @@ -1076,16 +1102,11 @@ static void SDL_SendWakeupEvent(void) return; } - SDL_LockMutex(_this->wakeup_lock); - { - if (_this->wakeup_window) { - _this->SendWakeupEvent(_this, _this->wakeup_window); - - // No more wakeup events needed until we enter a new wait - _this->wakeup_window = NULL; - } + // We only want to do this once while waiting for an event, so set it to NULL atomically here + SDL_Window *wakeup_window = (SDL_Window *)SDL_SetAtomicPointer(&_this->wakeup_window, NULL); + if (wakeup_window) { + _this->SendWakeupEvent(_this, wakeup_window); } - SDL_UnlockMutex(_this->wakeup_lock); #endif } @@ -1379,9 +1400,7 @@ bool SDL_RunOnMainThread(SDL_MainThreadCallback callback, void *userdata, bool w return true; } - // Maximum wait of 30 seconds to prevent deadlocking forever - const Sint32 MAX_CALLBACK_WAIT = 30 * 1000; - SDL_WaitSemaphoreTimeout(entry->semaphore, MAX_CALLBACK_WAIT); + SDL_WaitSemaphore(entry->semaphore); switch (SDL_GetAtomicInt(&entry->state)) { case SDL_MAIN_CALLBACK_COMPLETE: @@ -1525,18 +1544,7 @@ static int SDL_WaitEventTimeout_Device(SDL_VideoDevice *_this, SDL_Window *wakeu */ SDL_PumpEventsInternal(true); - SDL_LockMutex(_this->wakeup_lock); - { - status = SDL_PeepEvents(event, 1, SDL_GETEVENT, SDL_EVENT_FIRST, SDL_EVENT_LAST); - // If status == 0 we are going to block so wakeup will be needed. - if (status == 0) { - _this->wakeup_window = wakeup_window; - } else { - _this->wakeup_window = NULL; - } - } - SDL_UnlockMutex(_this->wakeup_lock); - + status = SDL_PeepEvents(event, 1, SDL_GETEVENT, SDL_EVENT_FIRST, SDL_EVENT_LAST); if (status < 0) { // Got an error: return break; @@ -1549,8 +1557,6 @@ static int SDL_WaitEventTimeout_Device(SDL_VideoDevice *_this, SDL_Window *wakeu if (timeoutNS > 0) { Sint64 elapsed = SDL_GetTicksNS() - start; if (elapsed >= timeoutNS) { - // Set wakeup_window to NULL without holding the lock. - _this->wakeup_window = NULL; return 0; } loop_timeoutNS = (timeoutNS - elapsed); @@ -1563,9 +1569,9 @@ static int SDL_WaitEventTimeout_Device(SDL_VideoDevice *_this, SDL_Window *wakeu loop_timeoutNS = poll_intervalNS; } } + SDL_SetAtomicPointer(&_this->wakeup_window, wakeup_window); status = _this->WaitEventTimeout(_this, loop_timeoutNS); - // Set wakeup_window to NULL without holding the lock. - _this->wakeup_window = NULL; + SDL_SetAtomicPointer(&_this->wakeup_window, NULL); if (status == 0 && poll_intervalNS != SDL_MAX_SINT64 && loop_timeoutNS == poll_intervalNS) { // We may have woken up to poll. Try again continue; @@ -1823,7 +1829,7 @@ void SDL_SetEventEnabled(Uint32 type, bool enabled) Uint8 lo = (type & 0xff); if (SDL_disabled_events[hi] && - (SDL_disabled_events[hi]->bits[lo / 32] & (1 << (lo & 31)))) { + (SDL_disabled_events[hi]->bits[lo / 32] & (1U << (lo & 31)))) { current_state = false; } else { current_state = true; @@ -1832,7 +1838,7 @@ void SDL_SetEventEnabled(Uint32 type, bool enabled) if ((enabled != false) != current_state) { if (enabled) { SDL_assert(SDL_disabled_events[hi] != NULL); - SDL_disabled_events[hi]->bits[lo / 32] &= ~(1 << (lo & 31)); + SDL_disabled_events[hi]->bits[lo / 32] &= ~(1U << (lo & 31)); // Gamepad events depend on joystick events switch (type) { @@ -1863,7 +1869,7 @@ void SDL_SetEventEnabled(Uint32 type, bool enabled) } // Out of memory, nothing we can do... if (SDL_disabled_events[hi]) { - SDL_disabled_events[hi]->bits[lo / 32] |= (1 << (lo & 31)); + SDL_disabled_events[hi]->bits[lo / 32] |= (1U << (lo & 31)); SDL_FlushEvent(type); } } @@ -1882,7 +1888,7 @@ bool SDL_EventEnabled(Uint32 type) Uint8 lo = (type & 0xff); if (SDL_disabled_events[hi] && - (SDL_disabled_events[hi]->bits[lo / 32] & (1 << (lo & 31)))) { + (SDL_disabled_events[hi]->bits[lo / 32] & (1U << (lo & 31)))) { return false; } else { return true; diff --git a/libs/SDL3/src/events/SDL_keyboard.c b/libs/SDL3/src/events/SDL_keyboard.c index 7f1ac7aa..fc06f072 100644 --- a/libs/SDL3/src/events/SDL_keyboard.c +++ b/libs/SDL3/src/events/SDL_keyboard.c @@ -58,9 +58,6 @@ typedef struct SDL_Keyboard Uint8 keysource[SDL_SCANCODE_COUNT]; bool keystate[SDL_SCANCODE_COUNT]; SDL_Keymap *keymap; - bool french_numbers; - bool latin_letters; - bool thai_keyboard; Uint32 keycode_options; bool autorelease_pending; Uint64 hardware_timestamp; @@ -175,6 +172,19 @@ void SDL_RemoveKeyboard(SDL_KeyboardID keyboardID, bool send_event) } } +void SDL_SetKeyboardName(SDL_KeyboardID keyboardID, const char *name) +{ + SDL_assert(keyboardID != 0); + + const int keyboard_index = SDL_GetKeyboardIndex(keyboardID); + + if (keyboard_index >= 0) { + SDL_KeyboardInstance *instance = &SDL_keyboards[keyboard_index]; + SDL_free(instance->name); + instance->name = SDL_strdup(name ? name : ""); + } +} + bool SDL_HasKeyboard(void) { return (SDL_keyboard_count > 0); @@ -229,19 +239,22 @@ void SDL_ResetKeyboard(void) } } -SDL_Keymap *SDL_GetCurrentKeymap(void) +SDL_Keymap *SDL_GetCurrentKeymap(bool ignore_options) { SDL_Keyboard *keyboard = &SDL_keyboard; + SDL_Keymap *keymap = SDL_keyboard.keymap; - if (keyboard->thai_keyboard) { - // Thai keyboards are QWERTY plus Thai characters, use the default QWERTY keymap - return NULL; - } + if (!ignore_options) { + if (keymap && keymap->thai_keyboard) { + // Thai keyboards are QWERTY plus Thai characters, use the default QWERTY keymap + return NULL; + } - if ((keyboard->keycode_options & KEYCODE_OPTION_LATIN_LETTERS) && - !keyboard->latin_letters) { - // We'll use the default QWERTY keymap - return NULL; + if ((keyboard->keycode_options & KEYCODE_OPTION_LATIN_LETTERS) && + keymap && !keymap->latin_letters) { + // We'll use the default QWERTY keymap + return NULL; + } } return keyboard->keymap; @@ -251,35 +264,39 @@ void SDL_SetKeymap(SDL_Keymap *keymap, bool send_event) { SDL_Keyboard *keyboard = &SDL_keyboard; - if (keyboard->keymap) { + if (keyboard->keymap && keyboard->keymap->auto_release) { SDL_DestroyKeymap(keyboard->keymap); } keyboard->keymap = keymap; - // Detect French number row (all symbols) - keyboard->french_numbers = true; - for (int i = SDL_SCANCODE_1; i <= SDL_SCANCODE_0; ++i) { - if (SDL_isdigit(SDL_GetKeymapKeycode(keymap, (SDL_Scancode)i, SDL_KMOD_NONE)) || - !SDL_isdigit(SDL_GetKeymapKeycode(keymap, (SDL_Scancode)i, SDL_KMOD_SHIFT))) { - keyboard->french_numbers = false; - break; - } - } + if (keymap && !keymap->layout_determined) { + keymap->layout_determined = true; - // Detect non-Latin keymap - keyboard->thai_keyboard = false; - keyboard->latin_letters = false; - for (int i = SDL_SCANCODE_A; i <= SDL_SCANCODE_D; ++i) { - SDL_Keycode key = SDL_GetKeymapKeycode(keymap, (SDL_Scancode)i, SDL_KMOD_NONE); - if (key <= 0xFF) { - keyboard->latin_letters = true; - break; + // Detect French number row (all symbols) + keymap->french_numbers = true; + for (int i = SDL_SCANCODE_1; i <= SDL_SCANCODE_0; ++i) { + if (SDL_isdigit(SDL_GetKeymapKeycode(keymap, (SDL_Scancode)i, SDL_KMOD_NONE)) || + !SDL_isdigit(SDL_GetKeymapKeycode(keymap, (SDL_Scancode)i, SDL_KMOD_SHIFT))) { + keymap->french_numbers = false; + break; + } } - if (key >= 0x0E00 && key <= 0x0E7F) { - keyboard->thai_keyboard = true; - break; + // Detect non-Latin keymap + keymap->thai_keyboard = false; + keymap->latin_letters = false; + for (int i = SDL_SCANCODE_A; i <= SDL_SCANCODE_D; ++i) { + SDL_Keycode key = SDL_GetKeymapKeycode(keymap, (SDL_Scancode)i, SDL_KMOD_NONE); + if (key <= 0xFF) { + keymap->latin_letters = true; + break; + } + + if (key >= 0x0E00 && key <= 0x0E7F) { + keymap->thai_keyboard = true; + break; + } } } @@ -308,7 +325,7 @@ static void SetKeymapEntry(SDL_Scancode scancode, SDL_Keymod modstate, SDL_Keyco SDL_Keyboard *keyboard = &SDL_keyboard; if (!keyboard->keymap) { - keyboard->keymap = SDL_CreateKeymap(); + keyboard->keymap = SDL_CreateKeymap(true); } SDL_SetKeymapEntry(keyboard->keymap, scancode, modstate, keycode); @@ -475,7 +492,7 @@ SDL_Keycode SDL_GetKeyFromScancode(SDL_Scancode scancode, SDL_Keymod modstate, b SDL_Keyboard *keyboard = &SDL_keyboard; if (key_event) { - SDL_Keymap *keymap = SDL_GetCurrentKeymap(); + SDL_Keymap *keymap = SDL_GetCurrentKeymap(false); bool numlock = (modstate & SDL_KMOD_NUM) != 0; SDL_Keycode keycode; @@ -483,7 +500,7 @@ SDL_Keycode SDL_GetKeyFromScancode(SDL_Scancode scancode, SDL_Keymod modstate, b modstate = SDL_KMOD_NONE; if ((keyboard->keycode_options & KEYCODE_OPTION_FRENCH_NUMBERS) && - keyboard->french_numbers && + keymap && keymap->french_numbers && (scancode >= SDL_SCANCODE_1 && scancode <= SDL_SCANCODE_0)) { // Add the shift state to generate a numeric keycode modstate |= SDL_KMOD_SHIFT; @@ -876,7 +893,7 @@ void SDL_QuitKeyboard(void) SDL_free(SDL_keyboards); SDL_keyboards = NULL; - if (SDL_keyboard.keymap) { + if (SDL_keyboard.keymap && SDL_keyboard.keymap->auto_release) { SDL_DestroyKeymap(SDL_keyboard.keymap); SDL_keyboard.keymap = NULL; } diff --git a/libs/SDL3/src/events/SDL_keyboard_c.h b/libs/SDL3/src/events/SDL_keyboard_c.h index ddfb5c57..9a6589dd 100644 --- a/libs/SDL3/src/events/SDL_keyboard_c.h +++ b/libs/SDL3/src/events/SDL_keyboard_c.h @@ -43,6 +43,9 @@ extern void SDL_AddKeyboard(SDL_KeyboardID keyboardID, const char *name, bool se // A keyboard has been removed from the system extern void SDL_RemoveKeyboard(SDL_KeyboardID keyboardID, bool send_event); +// Set or update the name of a keyboard instance. +extern void SDL_SetKeyboardName(SDL_KeyboardID keyboardID, const char *name); + // Set the mapping of scancode to key codes extern void SDL_SetKeymap(SDL_Keymap *keymap, bool send_event); diff --git a/libs/SDL3/src/events/SDL_keymap.c b/libs/SDL3/src/events/SDL_keymap.c index bd087860..318fc686 100644 --- a/libs/SDL3/src/events/SDL_keymap.c +++ b/libs/SDL3/src/events/SDL_keymap.c @@ -23,22 +23,17 @@ #include "SDL_keymap_c.h" #include "SDL_keyboard_c.h" -struct SDL_Keymap -{ - SDL_HashTable *scancode_to_keycode; - SDL_HashTable *keycode_to_scancode; -}; - static SDL_Keycode SDL_GetDefaultKeyFromScancode(SDL_Scancode scancode, SDL_Keymod modstate); static SDL_Scancode SDL_GetDefaultScancodeFromKey(SDL_Keycode key, SDL_Keymod *modstate); -SDL_Keymap *SDL_CreateKeymap(void) +SDL_Keymap *SDL_CreateKeymap(bool auto_release) { - SDL_Keymap *keymap = (SDL_Keymap *)SDL_malloc(sizeof(*keymap)); + SDL_Keymap *keymap = (SDL_Keymap *)SDL_calloc(1, sizeof(*keymap)); if (!keymap) { return NULL; } + keymap->auto_release = auto_release; keymap->scancode_to_keycode = SDL_CreateHashTable(256, false, SDL_HashID, SDL_KeyMatchID, NULL, NULL); keymap->keycode_to_scancode = SDL_CreateHashTable(256, false, SDL_HashID, SDL_KeyMatchID, NULL, NULL); if (!keymap->scancode_to_keycode || !keymap->keycode_to_scancode) { @@ -102,16 +97,74 @@ void SDL_SetKeymapEntry(SDL_Keymap *keymap, SDL_Scancode scancode, SDL_Keymod mo SDL_Keycode SDL_GetKeymapKeycode(SDL_Keymap *keymap, SDL_Scancode scancode, SDL_Keymod modstate) { - SDL_Keycode keycode; + if (keymap) { + const void *value; + const SDL_Keymod normalized_modstate = NormalizeModifierStateForKeymap(modstate); + Uint32 key = ((Uint32)normalized_modstate << 16) | scancode; - const Uint32 key = ((Uint32)NormalizeModifierStateForKeymap(modstate) << 16) | scancode; - const void *value; - if (keymap && SDL_FindInHashTable(keymap->scancode_to_keycode, (void *)(uintptr_t)key, &value)) { - keycode = (SDL_Keycode)(uintptr_t)value; - } else { - keycode = SDL_GetDefaultKeyFromScancode(scancode, modstate); + // First, try the requested set of modifiers. + if (SDL_FindInHashTable(keymap->scancode_to_keycode, (void *)(uintptr_t)key, &value)) { + return (SDL_Keycode)(uintptr_t)value; + } + + // If the requested set of modifiers was not found, search for the key from the highest to lowest modifier levels. + if (normalized_modstate) { + SDL_Keymod caps_mask = normalized_modstate & SDL_KMOD_CAPS; + + for (int i = caps_mask ? 2 : 1; i; --i) { + // Shift level 5 + if (normalized_modstate & SDL_KMOD_LEVEL5) { + const SDL_Keymod shifted_modstate = SDL_KMOD_LEVEL5 | caps_mask; + key = ((Uint32)shifted_modstate << 16) | scancode; + + if (shifted_modstate != normalized_modstate && SDL_FindInHashTable(keymap->scancode_to_keycode, (void *)(uintptr_t)key, &value)) { + return (SDL_Keycode)(uintptr_t)value; + } + } + + // Shift level 4 (Level 3 + Shift) + if ((normalized_modstate & (SDL_KMOD_MODE | SDL_KMOD_SHIFT)) == (SDL_KMOD_MODE | SDL_KMOD_SHIFT)) { + const SDL_Keymod shifted_modstate = SDL_KMOD_MODE | SDL_KMOD_SHIFT | caps_mask; + key = ((Uint32)shifted_modstate << 16) | scancode; + + if (shifted_modstate != normalized_modstate && SDL_FindInHashTable(keymap->scancode_to_keycode, (void *)(uintptr_t)key, &value)) { + return (SDL_Keycode)(uintptr_t)value; + } + } + + // Shift level 3 + if (normalized_modstate & SDL_KMOD_MODE) { + const SDL_Keymod shifted_modstate = SDL_KMOD_MODE | caps_mask; + key = ((Uint32)shifted_modstate << 16) | scancode; + + if (shifted_modstate != normalized_modstate && SDL_FindInHashTable(keymap->scancode_to_keycode, (void *)(uintptr_t)key, &value)) { + return (SDL_Keycode)(uintptr_t)value; + } + } + + // Shift level 2 + if (normalized_modstate & SDL_KMOD_SHIFT) { + const SDL_Keymod shifted_modstate = SDL_KMOD_SHIFT | caps_mask; + key = ((Uint32)shifted_modstate << 16) | scancode; + + if (shifted_modstate != normalized_modstate && SDL_FindInHashTable(keymap->scancode_to_keycode, (void *)(uintptr_t)key, &value)) { + return (SDL_Keycode)(uintptr_t)value; + } + } + + // Shift Level 1 (unmodified) + key = ((Uint32)caps_mask << 16) | scancode; + if (SDL_FindInHashTable(keymap->scancode_to_keycode, (void *)(uintptr_t)key, &value)) { + return (SDL_Keycode)(uintptr_t)value; + } + + // Clear the capslock mask, if set. + caps_mask = SDL_KMOD_NONE; + } + } } - return keycode; + + return SDL_GetDefaultKeyFromScancode(scancode, modstate); } SDL_Scancode SDL_GetKeymapScancode(SDL_Keymap *keymap, SDL_Keycode keycode, SDL_Keymod *modstate) @@ -1064,7 +1117,7 @@ const char *SDL_GetKeyName(SDL_Keycode key) // but the key name is defined as the letter printed on that key, // which is usually the shifted capital letter. if (key > 0x7F || (key >= 'a' && key <= 'z')) { - SDL_Keymap *keymap = SDL_GetCurrentKeymap(); + SDL_Keymap *keymap = SDL_GetCurrentKeymap(false); SDL_Keymod modstate; SDL_Scancode scancode = SDL_GetKeymapScancode(keymap, key, &modstate); if (scancode != SDL_SCANCODE_UNKNOWN && !(modstate & SDL_KMOD_SHIFT)) { @@ -1132,7 +1185,7 @@ SDL_Keycode SDL_GetKeyFromName(const char *name) // SDL_Keycode is defined as the unshifted key on the keyboard, // but the key name is defined as the letter printed on that key, // which is usually the shifted capital letter. - SDL_Keymap *keymap = SDL_GetCurrentKeymap(); + SDL_Keymap *keymap = SDL_GetCurrentKeymap(false); SDL_Keymod modstate; SDL_Scancode scancode = SDL_GetKeymapScancode(keymap, key, &modstate); if (scancode != SDL_SCANCODE_UNKNOWN && (modstate & (SDL_KMOD_SHIFT | SDL_KMOD_CAPS))) { diff --git a/libs/SDL3/src/events/SDL_keymap_c.h b/libs/SDL3/src/events/SDL_keymap_c.h index 9c803978..311e397e 100644 --- a/libs/SDL3/src/events/SDL_keymap_c.h +++ b/libs/SDL3/src/events/SDL_keymap_c.h @@ -23,10 +23,22 @@ #ifndef SDL_keymap_c_h_ #define SDL_keymap_c_h_ -typedef struct SDL_Keymap SDL_Keymap; +typedef struct SDL_Keymap +{ + SDL_HashTable *scancode_to_keycode; + SDL_HashTable *keycode_to_scancode; + bool auto_release; + bool layout_determined; + bool french_numbers; + bool latin_letters; + bool thai_keyboard; +} SDL_Keymap; -SDL_Keymap *SDL_GetCurrentKeymap(void); -SDL_Keymap *SDL_CreateKeymap(void); +/* This may return null even when a keymap is bound, depending on the current keyboard mapping options. + * Set 'ignore_options' to true to always return the keymap that is actually bound. + */ +SDL_Keymap *SDL_GetCurrentKeymap(bool ignore_options); +SDL_Keymap *SDL_CreateKeymap(bool auto_release); void SDL_SetKeymapEntry(SDL_Keymap *keymap, SDL_Scancode scancode, SDL_Keymod modstate, SDL_Keycode keycode); SDL_Keycode SDL_GetKeymapKeycode(SDL_Keymap *keymap, SDL_Scancode scancode, SDL_Keymod modstate); SDL_Scancode SDL_GetKeymapScancode(SDL_Keymap *keymap, SDL_Keycode keycode, SDL_Keymod *modstate); diff --git a/libs/SDL3/src/events/SDL_mouse.c b/libs/SDL3/src/events/SDL_mouse.c index 777733fe..5838d47c 100644 --- a/libs/SDL3/src/events/SDL_mouse.c +++ b/libs/SDL3/src/events/SDL_mouse.c @@ -138,21 +138,25 @@ static void SDLCALL SDL_TouchMouseEventsChanged(void *userdata, const char *name #ifdef SDL_PLATFORM_VITA static void SDLCALL SDL_VitaTouchMouseDeviceChanged(void *userdata, const char *name, const char *oldValue, const char *hint) { + Uint8 vita_touch_mouse_device = 1; + SDL_Mouse *mouse = (SDL_Mouse *)userdata; if (hint) { switch (*hint) { - default: case '0': - mouse->vita_touch_mouse_device = 1; + vita_touch_mouse_device = 1; break; case '1': - mouse->vita_touch_mouse_device = 2; + vita_touch_mouse_device = 2; break; case '2': - mouse->vita_touch_mouse_device = 3; + vita_touch_mouse_device = 3; + break; + default: break; } } + mouse->vita_touch_mouse_device = vita_touch_mouse_device; } #endif @@ -229,9 +233,9 @@ static void SDLCALL SDL_MouseRelativeCursorVisibleChanged(void *userdata, const { SDL_Mouse *mouse = (SDL_Mouse *)userdata; - mouse->relative_mode_cursor_visible = SDL_GetStringBoolean(hint, false); + mouse->relative_mode_hide_cursor = !(SDL_GetStringBoolean(hint, false)); - SDL_SetCursor(NULL); // Update cursor visibility + SDL_RedrawCursor(); // Update cursor visibility } static void SDLCALL SDL_MouseIntegerModeChanged(void *userdata, const char *name, const char *oldValue, const char *hint) @@ -304,7 +308,7 @@ bool SDL_PreInitMouse(void) mouse->was_touch_mouse_events = false; // no touch to mouse movement event pending - mouse->cursor_shown = true; + mouse->cursor_visible = true; return true; } @@ -409,6 +413,19 @@ void SDL_RemoveMouse(SDL_MouseID mouseID, bool send_event) } } +void SDL_SetMouseName(SDL_MouseID mouseID, const char *name) +{ + SDL_assert(mouseID != 0); + + const int mouse_index = SDL_GetMouseIndex(mouseID); + + if (mouse_index >= 0) { + SDL_MouseInstance *instance = &SDL_mice[mouse_index]; + SDL_free(instance->name); + instance->name = SDL_strdup(name ? name : ""); + } +} + bool SDL_HasMouse(void) { return (SDL_mouse_count > 0); @@ -593,7 +610,7 @@ void SDL_SetMouseFocus(SDL_Window *window) } // Update cursor visibility - SDL_SetCursor(NULL); + SDL_RedrawCursor(); } bool SDL_MousePositionInWindow(SDL_Window *window, float x, float y) @@ -719,14 +736,19 @@ static void SDL_PrivateSendMouseMotion(Uint64 timestamp, SDL_Window *window, SDL if (relative) { if (mouse->relative_mode) { - if (mouse->enable_relative_system_scale) { - if (mouse->ApplySystemScale) { - mouse->ApplySystemScale(mouse->system_scale_data, timestamp, window, mouseID, &x, &y); + if (mouse->InputTransform) { + void *data = mouse->input_transform_data; + mouse->InputTransform(data, timestamp, window, mouseID, &x, &y); + } else { + if (mouse->enable_relative_system_scale) { + if (mouse->ApplySystemScale) { + mouse->ApplySystemScale(mouse->system_scale_data, timestamp, window, mouseID, &x, &y); + } + } + if (mouse->enable_relative_speed_scale) { + x *= mouse->relative_speed_scale; + y *= mouse->relative_speed_scale; } - } - if (mouse->enable_relative_speed_scale) { - x *= mouse->relative_speed_scale; - y *= mouse->relative_speed_scale; } } else { if (mouse->enable_normal_speed_scale) { @@ -794,7 +816,7 @@ static void SDL_PrivateSendMouseMotion(Uint64 timestamp, SDL_Window *window, SDL } // Move the mouse cursor, if needed - if (mouse->cursor_shown && !mouse->relative_mode && + if (mouse->cursor_visible && !mouse->relative_mode && mouse->MoveCursor && mouse->cur_cursor) { mouse->MoveCursor(mouse->cur_cursor); } @@ -1022,18 +1044,14 @@ void SDL_SendMouseWheel(Uint64 timestamp, SDL_Window *window, SDL_MouseID mouseI SDL_SetMouseFocus(window); } - // Accumulate fractional wheel motion if integer mode is enabled - if (mouse->integer_mode_flags & 2) { - mouse->integer_mode_residual_scroll_x = SDL_modff(mouse->integer_mode_residual_scroll_x + x, &x); - mouse->integer_mode_residual_scroll_y = SDL_modff(mouse->integer_mode_residual_scroll_y + y, &y); - } - if (x == 0.0f && y == 0.0f) { return; } // Post the event, if desired if (SDL_EventEnabled(SDL_EVENT_MOUSE_WHEEL)) { + float integer_x, integer_y; + if (!mouse->relative_mode || mouse->warp_emulation_active) { // We're not in relative mode, so all mouse events are global mouse events mouseID = SDL_GLOBAL_MOUSE_ID; @@ -1044,11 +1062,26 @@ void SDL_SendMouseWheel(Uint64 timestamp, SDL_Window *window, SDL_MouseID mouseI event.common.timestamp = timestamp; event.wheel.windowID = mouse->focus ? mouse->focus->id : 0; event.wheel.which = mouseID; - event.wheel.x = x; - event.wheel.y = y; event.wheel.direction = direction; event.wheel.mouse_x = mouse->x; event.wheel.mouse_y = mouse->y; + + mouse->residual_scroll_x = SDL_modff(mouse->residual_scroll_x + x, &integer_x); + event.wheel.integer_x = (Sint32)integer_x; + + mouse->residual_scroll_y = SDL_modff(mouse->residual_scroll_y + y, &integer_y); + event.wheel.integer_y = (Sint32)integer_y; + + // Return the accumulated values in x/y when integer wheel mode is enabled. + // This is necessary for compatibility with sdl2-compat 2.32.54. + if (mouse->integer_mode_flags & 2) { + event.wheel.x = integer_x; + event.wheel.y = integer_y; + } else { + event.wheel.x = x; + event.wheel.y = y; + } + SDL_PushEvent(&event); } } @@ -1060,10 +1093,12 @@ void SDL_QuitMouse(void) if (mouse->added_mouse_touch_device) { SDL_DelTouch(SDL_MOUSE_TOUCHID); + mouse->added_mouse_touch_device = false; } if (mouse->added_pen_touch_device) { SDL_DelTouch(SDL_PEN_TOUCHID); + mouse->added_pen_touch_device = false; } if (mouse->CaptureMouse) { @@ -1148,6 +1183,17 @@ void SDL_QuitMouse(void) SDL_mice = NULL; } +bool SDL_SetRelativeMouseTransform(SDL_MouseMotionTransformCallback transform, void *userdata) +{ + SDL_Mouse *mouse = SDL_GetMouse(); + if (mouse->relative_mode) { + return SDL_SetError("Can't set mouse transform while relative mode is active"); + } + mouse->InputTransform = transform; + mouse->input_transform_data = userdata; + return true; +} + SDL_MouseButtonFlags SDL_GetMouseState(float *x, float *y) { SDL_Mouse *mouse = SDL_GetMouse(); @@ -1258,7 +1304,7 @@ static void SDL_MaybeEnableWarpEmulation(SDL_Window *window, float x, float y) { SDL_Mouse *mouse = SDL_GetMouse(); - if (!mouse->warp_emulation_prohibited && mouse->warp_emulation_hint && !mouse->cursor_shown && !mouse->warp_emulation_active) { + if (!mouse->warp_emulation_prohibited && mouse->warp_emulation_hint && !mouse->cursor_visible && !mouse->warp_emulation_active) { if (!window) { window = mouse->focus; } @@ -1272,8 +1318,9 @@ static void SDL_MaybeEnableWarpEmulation(SDL_Window *window, float x, float y) // Require two consecutive warps to the center within a certain timespan to enter warp emulation mode. const Uint64 now = SDL_GetTicksNS(); if (now - mouse->last_center_warp_time_ns < WARP_EMULATION_THRESHOLD_NS) { - if (SDL_SetRelativeMouseMode(true)) { - mouse->warp_emulation_active = true; + mouse->warp_emulation_active = true; + if (!SDL_SetRelativeMouseMode(true)) { + mouse->warp_emulation_active = false; } } @@ -1329,7 +1376,7 @@ bool SDL_SetRelativeMouseMode(bool enabled) if (enabled) { // Update cursor visibility before we potentially warp the mouse - SDL_SetCursor(NULL); + SDL_RedrawCursor(); } if (enabled && focusWindow) { @@ -1349,7 +1396,7 @@ bool SDL_SetRelativeMouseMode(bool enabled) if (!enabled) { // Update cursor visibility after we restore the mouse position - SDL_SetCursor(NULL); + SDL_RedrawCursor(); } // Flush pending mouse motion - ideally we would pump events, but that's not always safe @@ -1459,7 +1506,7 @@ SDL_Cursor *SDL_CreateCursor(const Uint8 *data, const Uint8 *mask, int w, int h, SDL_Surface *surface; SDL_Cursor *cursor; int x, y; - Uint32 *pixel; + Uint32 *pixels; Uint8 datab = 0, maskb = 0; const Uint32 black = 0xFF000000; const Uint32 white = 0xFFFFFFFF; @@ -1480,16 +1527,16 @@ SDL_Cursor *SDL_CreateCursor(const Uint8 *data, const Uint8 *mask, int w, int h, return NULL; } for (y = 0; y < h; ++y) { - pixel = (Uint32 *)((Uint8 *)surface->pixels + y * surface->pitch); + pixels = (Uint32 *)((Uint8 *)surface->pixels + y * surface->pitch); for (x = 0; x < w; ++x) { if ((x % 8) == 0) { datab = *data++; maskb = *mask++; } if (maskb & 0x80) { - *pixel++ = (datab & 0x80) ? black : white; + *pixels++ = (datab & 0x80) ? black : white; } else { - *pixel++ = (datab & 0x80) ? inverted : transparent; + *pixels++ = (datab & 0x80) ? inverted : transparent; } datab <<= 1; maskb <<= 1; @@ -1568,6 +1615,26 @@ SDL_Cursor *SDL_CreateSystemCursor(SDL_SystemCursor id) return cursor; } +void SDL_RedrawCursor(void) +{ + SDL_Mouse *mouse = SDL_GetMouse(); + SDL_Cursor *cursor; + + if (mouse->focus) { + cursor = mouse->cur_cursor; + } else { + cursor = mouse->def_cursor; + } + + if (mouse->focus && (!mouse->cursor_visible || (mouse->relative_mode && mouse->relative_mode_hide_cursor))) { + cursor = NULL; + } + + if (mouse->ShowCursor) { + mouse->ShowCursor(cursor); + } +} + /* SDL_SetCursor(NULL) can be used to force the cursor redraw, if this is desired for any reason. This is used when setting the video mode and when the SDL window gains the mouse focus. @@ -1576,7 +1643,7 @@ bool SDL_SetCursor(SDL_Cursor *cursor) { SDL_Mouse *mouse = SDL_GetMouse(); - // Return immediately if setting the cursor to the currently set one (fixes #7151) + // already on this cursor, no further action required if (cursor == mouse->cur_cursor) { return true; } @@ -1596,23 +1663,10 @@ bool SDL_SetCursor(SDL_Cursor *cursor) } } mouse->cur_cursor = cursor; - } else { - if (mouse->focus) { - cursor = mouse->cur_cursor; - } else { - cursor = mouse->def_cursor; - } } - if (cursor && (!mouse->focus || (mouse->cursor_shown && (!mouse->relative_mode || mouse->relative_mode_cursor_visible)))) { - if (mouse->ShowCursor) { - mouse->ShowCursor(cursor); - } - } else { - if (mouse->ShowCursor) { - mouse->ShowCursor(NULL); - } - } + SDL_RedrawCursor(); + return true; } @@ -1680,9 +1734,9 @@ bool SDL_ShowCursor(void) mouse->warp_emulation_active = false; } - if (!mouse->cursor_shown) { - mouse->cursor_shown = true; - SDL_SetCursor(NULL); + if (!mouse->cursor_visible) { + mouse->cursor_visible = true; + SDL_RedrawCursor(); } return true; } @@ -1691,9 +1745,9 @@ bool SDL_HideCursor(void) { SDL_Mouse *mouse = SDL_GetMouse(); - if (mouse->cursor_shown) { - mouse->cursor_shown = false; - SDL_SetCursor(NULL); + if (mouse->cursor_visible) { + mouse->cursor_visible = false; + SDL_RedrawCursor(); } return true; } @@ -1702,5 +1756,5 @@ bool SDL_CursorVisible(void) { SDL_Mouse *mouse = SDL_GetMouse(); - return mouse->cursor_shown; + return mouse->cursor_visible; } diff --git a/libs/SDL3/src/events/SDL_mouse_c.h b/libs/SDL3/src/events/SDL_mouse_c.h index dd9a0c15..0bc913b9 100644 --- a/libs/SDL3/src/events/SDL_mouse_c.h +++ b/libs/SDL3/src/events/SDL_mouse_c.h @@ -87,16 +87,18 @@ typedef struct // Get absolute mouse coordinates. (x) and (y) are never NULL and set to zero before call. SDL_MouseButtonFlags (*GetGlobalMouseState)(float *x, float *y); - // Platform-specific system mouse transform - void (*ApplySystemScale)(void *internal, Uint64 timestamp, SDL_Window *window, SDL_MouseID mouseID, float *x, float *y); + // Platform-specific system mouse transform applied in relative mode + SDL_MouseMotionTransformCallback ApplySystemScale; void *system_scale_data; + // User-defined mouse input transform applied in relative mode + SDL_MouseMotionTransformCallback InputTransform; + void *input_transform_data; + // integer mode data Uint8 integer_mode_flags; // 1 to enable mouse quantization, 2 to enable wheel quantization float integer_mode_residual_motion_x; float integer_mode_residual_motion_y; - float integer_mode_residual_scroll_x; - float integer_mode_residual_scroll_y; // Data common to all mice SDL_Window *focus; @@ -105,12 +107,14 @@ typedef struct float x_accu; float y_accu; float last_x, last_y; // the last reported x and y coordinates + float residual_scroll_x; + float residual_scroll_y; double click_motion_x; double click_motion_y; bool has_position; bool relative_mode; bool relative_mode_warp_motion; - bool relative_mode_cursor_visible; + bool relative_mode_hide_cursor; bool relative_mode_center; bool warp_emulation_hint; bool warp_emulation_active; @@ -144,7 +148,7 @@ typedef struct SDL_Cursor *cursors; SDL_Cursor *def_cursor; SDL_Cursor *cur_cursor; - bool cursor_shown; + bool cursor_visible; // Driver-dependent data. void *internal; @@ -165,9 +169,15 @@ extern void SDL_AddMouse(SDL_MouseID mouseID, const char *name, bool send_event) // A mouse has been removed from the system extern void SDL_RemoveMouse(SDL_MouseID mouseID, bool send_event); +// Set or update the name of a mouse instance. +extern void SDL_SetMouseName(SDL_MouseID mouseID, const char *name); + // Get the mouse state structure extern SDL_Mouse *SDL_GetMouse(void); +// Set the default mouse cursor +extern void SDL_RedrawCursor(void); + // Set the default mouse cursor extern void SDL_SetDefaultCursor(SDL_Cursor *cursor); diff --git a/libs/SDL3/src/events/SDL_pen.c b/libs/SDL3/src/events/SDL_pen.c index 1ef7062a..cd3730ca 100644 --- a/libs/SDL3/src/events/SDL_pen.c +++ b/libs/SDL3/src/events/SDL_pen.c @@ -565,10 +565,19 @@ void SDL_SendPenButton(Uint64 timestamp, SDL_PenID instance_id, SDL_Window *wind event.pbutton.down = down; SDL_PushEvent(&event); - if (window && (pen_touching == instance_id)) { + if (window && (!pen_touching || (pen_touching == instance_id))) { SDL_Mouse *mouse = SDL_GetMouse(); if (mouse && mouse->pen_mouse_events) { - SDL_SendMouseButton(timestamp, window, SDL_PEN_MOUSEID, button + 1, down); + static const Uint8 mouse_buttons[] = { + SDL_BUTTON_LEFT, + SDL_BUTTON_RIGHT, + SDL_BUTTON_MIDDLE, + SDL_BUTTON_X1, + SDL_BUTTON_X2 + }; + if (button < SDL_arraysize(mouse_buttons)) { + SDL_SendMouseButton(timestamp, window, SDL_PEN_MOUSEID, mouse_buttons[button], down); + } } } } diff --git a/libs/SDL3/src/events/SDL_touch.c b/libs/SDL3/src/events/SDL_touch.c index ec4acb14..bf3cc023 100644 --- a/libs/SDL3/src/events/SDL_touch.c +++ b/libs/SDL3/src/events/SDL_touch.c @@ -83,9 +83,11 @@ SDL_Touch *SDL_GetTouch(SDL_TouchID id) { int index = SDL_GetTouchIndex(id); if (index < 0 || index >= SDL_num_touch) { - if (SDL_GetVideoDevice()->ResetTouch != NULL) { + if ((id == SDL_MOUSE_TOUCHID) || (id == SDL_PEN_TOUCHID)) { + // this is a virtual touch device, but for some reason they aren't added to the system. Just ignore it. + } else if ( SDL_GetVideoDevice()->ResetTouch) { SDL_SetError("Unknown touch id %d, resetting", (int)id); - (SDL_GetVideoDevice()->ResetTouch)(SDL_GetVideoDevice()); + SDL_GetVideoDevice()->ResetTouch(SDL_GetVideoDevice()); } else { SDL_SetError("Unknown touch device id %d, cannot reset", (int)id); } @@ -203,6 +205,16 @@ int SDL_AddTouch(SDL_TouchID touchID, SDL_TouchDeviceType type, const char *name return index; } +// Set or update the name of a touch. +void SDL_SetTouchName(SDL_TouchID id, const char *name) +{ + SDL_Touch *touch = SDL_GetTouch(id); + if (touch) { + SDL_free(touch->name); + touch->name = SDL_strdup(name ? name : ""); + } +} + static bool SDL_AddFinger(SDL_Touch *touch, SDL_FingerID fingerid, float x, float y, float pressure) { SDL_Finger *finger; diff --git a/libs/SDL3/src/events/SDL_touch_c.h b/libs/SDL3/src/events/SDL_touch_c.h index db2d64b8..e46ba681 100644 --- a/libs/SDL3/src/events/SDL_touch_c.h +++ b/libs/SDL3/src/events/SDL_touch_c.h @@ -42,6 +42,9 @@ extern bool SDL_TouchDevicesAvailable(void); // Add a touch, returning the index of the touch, or -1 if there was an error. extern int SDL_AddTouch(SDL_TouchID id, SDL_TouchDeviceType type, const char *name); +// Set or update the name of a touch. +extern void SDL_SetTouchName(SDL_TouchID id, const char *name); + // Get the touch with a given id extern SDL_Touch *SDL_GetTouch(SDL_TouchID id); diff --git a/libs/SDL3/src/events/SDL_windowevents.c b/libs/SDL3/src/events/SDL_windowevents.c index e20cd3ab..fa5488a6 100644 --- a/libs/SDL3/src/events/SDL_windowevents.c +++ b/libs/SDL3/src/events/SDL_windowevents.c @@ -188,6 +188,7 @@ bool SDL_SendWindowEvent(SDL_Window *window, SDL_EventType windowevent, int data if (data1 == 0 || (SDL_DisplayID)data1 == window->last_displayID) { return false; } + window->update_fullscreen_on_display_changed = true; window->last_displayID = (SDL_DisplayID)data1; break; case SDL_EVENT_WINDOW_OCCLUDED: diff --git a/libs/SDL3/src/events/scancodes_xfree86.h b/libs/SDL3/src/events/scancodes_xfree86.h index 5e51bb17..81d625e8 100644 --- a/libs/SDL3/src/events/scancodes_xfree86.h +++ b/libs/SDL3/src/events/scancodes_xfree86.h @@ -371,10 +371,10 @@ static const SDL_Scancode xfree86_scancode_table2[] = { /* 188, 0x0bc */ SDL_SCANCODE_F18, // XF86Launch9 /* 189, 0x0bd */ SDL_SCANCODE_F19, // NoSymbol /* 190, 0x0be */ SDL_SCANCODE_F20, // XF86AudioMicMute - /* 191, 0x0bf */ SDL_SCANCODE_UNKNOWN, // XF86TouchpadToggle - /* 192, 0x0c0 */ SDL_SCANCODE_UNKNOWN, // XF86TouchpadOn - /* 193, 0x0c1 */ SDL_SCANCODE_UNKNOWN, // XF86TouchpadOff - /* 194, 0x0c2 */ SDL_SCANCODE_UNKNOWN, // NoSymbol + /* 191, 0x0bf */ SDL_SCANCODE_F21, // XF86TouchpadToggle + /* 192, 0x0c0 */ SDL_SCANCODE_F22, // XF86TouchpadOn + /* 193, 0x0c1 */ SDL_SCANCODE_F23, // XF86TouchpadOff + /* 194, 0x0c2 */ SDL_SCANCODE_F24, // NoSymbol /* 195, 0x0c3 */ SDL_SCANCODE_MODE, // Mode_switch /* 196, 0x0c4 */ SDL_SCANCODE_UNKNOWN, // NoSymbol /* 197, 0x0c5 */ SDL_SCANCODE_UNKNOWN, // NoSymbol diff --git a/libs/SDL3/src/filesystem/cocoa/SDL_sysfilesystem.m b/libs/SDL3/src/filesystem/cocoa/SDL_sysfilesystem.m index 5818764e..6ecef5df 100644 --- a/libs/SDL3/src/filesystem/cocoa/SDL_sysfilesystem.m +++ b/libs/SDL3/src/filesystem/cocoa/SDL_sysfilesystem.m @@ -136,7 +136,7 @@ char *SDL_SYS_GetUserFolder(SDL_Folder folder) return NULL; #else char *result = NULL; - const char* base; + const char *base; NSArray *array; NSSearchPathDirectory dir; NSString *str; diff --git a/libs/SDL3/src/filesystem/gdk/SDL_sysfilesystem.cpp b/libs/SDL3/src/filesystem/gdk/SDL_sysfilesystem.cpp index ffafe43a..9a97f187 100644 --- a/libs/SDL3/src/filesystem/gdk/SDL_sysfilesystem.cpp +++ b/libs/SDL3/src/filesystem/gdk/SDL_sysfilesystem.cpp @@ -111,7 +111,7 @@ char *SDL_SYS_GetPrefPath(const char *org, const char *app) return NULL; } - folderPath = (char*) SDL_malloc(MAX_PATH); + folderPath = (char *)SDL_malloc(MAX_PATH); do { result = XGameSaveFilesGetFolderWithUiResult(&block, MAX_PATH, folderPath); } while (result == E_PENDING); diff --git a/libs/SDL3/src/filesystem/ngage/SDL_sysfilesystem.c b/libs/SDL3/src/filesystem/ngage/SDL_sysfilesystem.c new file mode 100644 index 00000000..bc33a2af --- /dev/null +++ b/libs/SDL3/src/filesystem/ngage/SDL_sysfilesystem.c @@ -0,0 +1,67 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2024 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +extern void NGAGE_GetAppPath(char *path); + +char *SDL_SYS_GetBasePath(void) +{ + char app_path[512]; + NGAGE_GetAppPath(app_path); + char *base_path = SDL_strdup(app_path); + return base_path; +} + +char *SDL_SYS_GetPrefPath(const char *org, const char *app) +{ + char *pref_path; + if (SDL_asprintf(&pref_path, "C:/System/Apps/%s/%s/", org, app) < 0) + return NULL; + else + return pref_path; +} + +char *SDL_SYS_GetUserFolder(SDL_Folder folder) +{ + const char *folder_path = NULL; + switch (folder) + { + case SDL_FOLDER_HOME: + folder_path = "C:/"; + break; + case SDL_FOLDER_PICTURES: + folder_path = "C:/Nokia/Pictures/"; + break; + case SDL_FOLDER_SAVEDGAMES: + folder_path = "C:/"; + break; + case SDL_FOLDER_SCREENSHOTS: + folder_path = "C:/Nokia/Pictures/"; + break; + case SDL_FOLDER_VIDEOS: + folder_path = "C:/Nokia/Videos/"; + break; + default: + folder_path = "C:/Nokia/Others/"; + break; + } + return SDL_strdup(folder_path); +} diff --git a/libs/SDL3/src/filesystem/ngage/SDL_sysfilesystem.cpp b/libs/SDL3/src/filesystem/ngage/SDL_sysfilesystem.cpp new file mode 100644 index 00000000..1c2b6aed --- /dev/null +++ b/libs/SDL3/src/filesystem/ngage/SDL_sysfilesystem.cpp @@ -0,0 +1,68 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2024 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#ifdef __cplusplus +extern "C" { +#endif + +#include "SDL_internal.h" + +#ifdef __cplusplus +} +#endif + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +void NGAGE_GetAppPath(char *path) +{ + TBuf<512> aPath; + + TFileName fullExePath = RProcess().FileName(); + + TParsePtrC parser(fullExePath); + aPath.Copy(parser.DriveAndPath()); + + TBuf8<512> utf8Path; // Temporary buffer for UTF-8 data. + CnvUtfConverter::ConvertFromUnicodeToUtf8(utf8Path, aPath); + + // Copy UTF-8 data to the provided char* buffer. + strncpy(path, (const char *)utf8Path.Ptr(), utf8Path.Length()); + path[utf8Path.Length()] = '\0'; + + // Replace backslashes with forward slashes. + for (int i = 0; i < utf8Path.Length(); i++) + { + if (path[i] == '\\') + { + path[i] = '/'; + } + } +} + +#ifdef __cplusplus +} +#endif diff --git a/libs/SDL3/src/filesystem/unix/SDL_sysfilesystem.c b/libs/SDL3/src/filesystem/unix/SDL_sysfilesystem.c index b0f2dd5c..858aaa2c 100644 --- a/libs/SDL3/src/filesystem/unix/SDL_sysfilesystem.c +++ b/libs/SDL3/src/filesystem/unix/SDL_sysfilesystem.c @@ -377,7 +377,7 @@ static char *xdg_user_dir_lookup_with_fallback (const char *type, const char *fa if (!config_home || config_home[0] == 0) { l = SDL_strlen (home_dir) + SDL_strlen ("/.config/user-dirs.dirs") + 1; - config_file = (char*) SDL_malloc (l); + config_file = (char *)SDL_malloc (l); if (!config_file) goto error; @@ -387,7 +387,7 @@ static char *xdg_user_dir_lookup_with_fallback (const char *type, const char *fa else { l = SDL_strlen (config_home) + SDL_strlen ("/user-dirs.dirs") + 1; - config_file = (char*) SDL_malloc (l); + config_file = (char *)SDL_malloc (l); if (!config_file) goto error; @@ -449,7 +449,7 @@ static char *xdg_user_dir_lookup_with_fallback (const char *type, const char *fa if (relative) { l = SDL_strlen (home_dir) + 1 + SDL_strlen (p) + 1; - user_dir = (char*) SDL_malloc (l); + user_dir = (char *)SDL_malloc (l); if (!user_dir) goto error2; @@ -458,7 +458,7 @@ static char *xdg_user_dir_lookup_with_fallback (const char *type, const char *fa } else { - user_dir = (char*) SDL_malloc (SDL_strlen (p) + 1); + user_dir = (char *)SDL_malloc (SDL_strlen (p) + 1); if (!user_dir) goto error2; @@ -503,7 +503,7 @@ static char *xdg_user_dir_lookup (const char *type) // Special case desktop for historical compatibility if (SDL_strcmp(type, "DESKTOP") == 0) { size_t length = SDL_strlen(home_dir) + SDL_strlen("/Desktop") + 1; - user_dir = (char*) SDL_malloc(length); + user_dir = (char *)SDL_malloc(length); if (!user_dir) return NULL; diff --git a/libs/SDL3/src/filesystem/windows/SDL_sysfilesystem.c b/libs/SDL3/src/filesystem/windows/SDL_sysfilesystem.c index 39ba4148..a4c033f0 100644 --- a/libs/SDL3/src/filesystem/windows/SDL_sysfilesystem.c +++ b/libs/SDL3/src/filesystem/windows/SDL_sysfilesystem.c @@ -181,7 +181,7 @@ char *SDL_SYS_GetPrefPath(const char *org, const char *app) char *SDL_SYS_GetUserFolder(SDL_Folder folder) { typedef HRESULT (WINAPI *pfnSHGetKnownFolderPath)(REFGUID /* REFKNOWNFOLDERID */, DWORD, HANDLE, PWSTR*); - HMODULE lib = LoadLibrary(L"Shell32.dll"); + HMODULE lib = LoadLibraryW(L"Shell32.dll"); pfnSHGetKnownFolderPath pSHGetKnownFolderPath = NULL; char *result = NULL; diff --git a/libs/SDL3/src/gpu/SDL_gpu.c b/libs/SDL3/src/gpu/SDL_gpu.c index b7ca6962..39cf2eb8 100644 --- a/libs/SDL3/src/gpu/SDL_gpu.c +++ b/libs/SDL3/src/gpu/SDL_gpu.c @@ -56,15 +56,47 @@ } #define CHECK_RENDERPASS \ - if (!((Pass *)render_pass)->in_progress) { \ + if (!((RenderPass *)render_pass)->in_progress) { \ SDL_assert_release(!"Render pass not in progress!"); \ return; \ } -#define CHECK_GRAPHICS_PIPELINE_BOUND \ - if (!((CommandBufferCommonHeader *)RENDERPASS_COMMAND_BUFFER)->graphics_pipeline_bound) { \ - SDL_assert_release(!"Graphics pipeline not bound!"); \ - return; \ +#define CHECK_SAMPLER_TEXTURES \ + RenderPass *rp = (RenderPass *)render_pass; \ + for (Uint32 color_target_index = 0; color_target_index < rp->num_color_targets; color_target_index += 1) { \ + for (Uint32 texture_sampler_index = 0; texture_sampler_index < num_bindings; texture_sampler_index += 1) { \ + if (rp->color_targets[color_target_index] == texture_sampler_bindings[texture_sampler_index].texture) { \ + SDL_assert_release(!"Texture cannot be simultaneously bound as a color target and a sampler!"); \ + } \ + } \ + } \ + \ + for (Uint32 texture_sampler_index = 0; texture_sampler_index < num_bindings; texture_sampler_index += 1) { \ + if (rp->depth_stencil_target != NULL && rp->depth_stencil_target == texture_sampler_bindings[texture_sampler_index].texture) { \ + SDL_assert_release(!"Texture cannot be simultaneously bound as a depth stencil target and a sampler!"); \ + } \ + } + +#define CHECK_STORAGE_TEXTURES \ + RenderPass *rp = (RenderPass *)render_pass; \ + for (Uint32 color_target_index = 0; color_target_index < rp->num_color_targets; color_target_index += 1) { \ + for (Uint32 texture_sampler_index = 0; texture_sampler_index < num_bindings; texture_sampler_index += 1) { \ + if (rp->color_targets[color_target_index] == storage_textures[texture_sampler_index]) { \ + SDL_assert_release(!"Texture cannot be simultaneously bound as a color target and a storage texture!"); \ + } \ + } \ + } \ + \ + for (Uint32 texture_sampler_index = 0; texture_sampler_index < num_bindings; texture_sampler_index += 1) { \ + if (rp->depth_stencil_target != NULL && rp->depth_stencil_target == storage_textures[texture_sampler_index]) { \ + SDL_assert_release(!"Texture cannot be simultaneously bound as a depth stencil target and a storage texture!"); \ + } \ + } + +#define CHECK_GRAPHICS_PIPELINE_BOUND \ + if (!((RenderPass *)render_pass)->graphics_pipeline) { \ + SDL_assert_release(!"Graphics pipeline not bound!"); \ + return; \ } #define CHECK_COMPUTEPASS \ @@ -74,7 +106,7 @@ } #define CHECK_COMPUTE_PIPELINE_BOUND \ - if (!((CommandBufferCommonHeader *)COMPUTEPASS_COMMAND_BUFFER)->compute_pipeline_bound) { \ + if (!((ComputePass *)compute_pass)->compute_pipeline) { \ SDL_assert_release(!"Compute pipeline not bound!"); \ return; \ } @@ -137,23 +169,137 @@ ((CommandBufferCommonHeader *)command_buffer)->device #define RENDERPASS_COMMAND_BUFFER \ - ((Pass *)render_pass)->command_buffer + ((RenderPass *)render_pass)->command_buffer #define RENDERPASS_DEVICE \ ((CommandBufferCommonHeader *)RENDERPASS_COMMAND_BUFFER)->device +#define RENDERPASS_BOUND_PIPELINE \ + ((RenderPass *)render_pass)->graphics_pipeline + #define COMPUTEPASS_COMMAND_BUFFER \ ((Pass *)compute_pass)->command_buffer #define COMPUTEPASS_DEVICE \ ((CommandBufferCommonHeader *)COMPUTEPASS_COMMAND_BUFFER)->device +#define COMPUTEPASS_BOUND_PIPELINE \ + ((ComputePass *)compute_pass)->compute_pipeline + #define COPYPASS_COMMAND_BUFFER \ ((Pass *)copy_pass)->command_buffer #define COPYPASS_DEVICE \ ((CommandBufferCommonHeader *)COPYPASS_COMMAND_BUFFER)->device +static bool TextureFormatIsComputeWritable[] = { + false, // INVALID + false, // A8_UNORM + true, // R8_UNORM + true, // R8G8_UNORM + true, // R8G8B8A8_UNORM + true, // R16_UNORM + true, // R16G16_UNORM + true, // R16G16B16A16_UNORM + true, // R10G10B10A2_UNORM + false, // B5G6R5_UNORM + false, // B5G5R5A1_UNORM + false, // B4G4R4A4_UNORM + false, // B8G8R8A8_UNORM + false, // BC1_UNORM + false, // BC2_UNORM + false, // BC3_UNORM + false, // BC4_UNORM + false, // BC5_UNORM + false, // BC7_UNORM + false, // BC6H_FLOAT + false, // BC6H_UFLOAT + true, // R8_SNORM + true, // R8G8_SNORM + true, // R8G8B8A8_SNORM + true, // R16_SNORM + true, // R16G16_SNORM + true, // R16G16B16A16_SNORM + true, // R16_FLOAT + true, // R16G16_FLOAT + true, // R16G16B16A16_FLOAT + true, // R32_FLOAT + true, // R32G32_FLOAT + true, // R32G32B32A32_FLOAT + true, // R11G11B10_UFLOAT + true, // R8_UINT + true, // R8G8_UINT + true, // R8G8B8A8_UINT + true, // R16_UINT + true, // R16G16_UINT + true, // R16G16B16A16_UINT + true, // R32_UINT + true, // R32G32_UINT + true, // R32G32B32A32_UINT + true, // R8_INT + true, // R8G8_INT + true, // R8G8B8A8_INT + true, // R16_INT + true, // R16G16_INT + true, // R16G16B16A16_INT + true, // R32_INT + true, // R32G32_INT + true, // R32G32B32A32_INT + false, // R8G8B8A8_UNORM_SRGB + false, // B8G8R8A8_UNORM_SRGB + false, // BC1_UNORM_SRGB + false, // BC3_UNORM_SRGB + false, // BC3_UNORM_SRGB + false, // BC7_UNORM_SRGB + false, // D16_UNORM + false, // D24_UNORM + false, // D32_FLOAT + false, // D24_UNORM_S8_UINT + false, // D32_FLOAT_S8_UINT + false, // ASTC_4x4_UNORM + false, // ASTC_5x4_UNORM + false, // ASTC_5x5_UNORM + false, // ASTC_6x5_UNORM + false, // ASTC_6x6_UNORM + false, // ASTC_8x5_UNORM + false, // ASTC_8x6_UNORM + false, // ASTC_8x8_UNORM + false, // ASTC_10x5_UNORM + false, // ASTC_10x6_UNORM + false, // ASTC_10x8_UNORM + false, // ASTC_10x10_UNORM + false, // ASTC_12x10_UNORM + false, // ASTC_12x12_UNORM + false, // ASTC_4x4_UNORM_SRGB + false, // ASTC_5x4_UNORM_SRGB + false, // ASTC_5x5_UNORM_SRGB + false, // ASTC_6x5_UNORM_SRGB + false, // ASTC_6x6_UNORM_SRGB + false, // ASTC_8x5_UNORM_SRGB + false, // ASTC_8x6_UNORM_SRGB + false, // ASTC_8x8_UNORM_SRGB + false, // ASTC_10x5_UNORM_SRGB + false, // ASTC_10x6_UNORM_SRGB + false, // ASTC_10x8_UNORM_SRGB + false, // ASTC_10x10_UNORM_SRGB + false, // ASTC_12x10_UNORM_SRGB + false, // ASTC_12x12_UNORM_SRGB + false, // ASTC_4x4_FLOAT + false, // ASTC_5x4_FLOAT + false, // ASTC_5x5_FLOAT + false, // ASTC_6x5_FLOAT + false, // ASTC_6x6_FLOAT + false, // ASTC_8x5_FLOAT + false, // ASTC_8x6_FLOAT + false, // ASTC_8x8_FLOAT + false, // ASTC_10x5_FLOAT + false, // ASTC_10x6_FLOAT + false, // ASTC_10x8_FLOAT + false, // ASTC_10x10_FLOAT + false, // ASTC_12x10_FLOAT + false // ASTC_12x12_FLOAT +}; + // Drivers #ifndef SDL_GPU_DISABLED @@ -371,13 +517,79 @@ void SDL_GPU_BlitCommon( SDL_EndGPURenderPass(render_pass); } +static void SDL_GPU_CheckGraphicsBindings(SDL_GPURenderPass *render_pass) +{ + RenderPass *rp = (RenderPass *)render_pass; + GraphicsPipelineCommonHeader *pipeline = (GraphicsPipelineCommonHeader *)RENDERPASS_BOUND_PIPELINE; + for (Uint32 i = 0; i < pipeline->num_vertex_samplers; i += 1) { + if (!rp->vertex_sampler_bound[i]) { + SDL_assert_release(!"Missing vertex sampler binding!"); + } + } + for (Uint32 i = 0; i < pipeline->num_vertex_storage_textures; i += 1) { + if (!rp->vertex_storage_texture_bound[i]) { + SDL_assert_release(!"Missing vertex storage texture binding!"); + } + } + for (Uint32 i = 0; i < pipeline->num_vertex_storage_buffers; i += 1) { + if (!rp->vertex_storage_buffer_bound[i]) { + SDL_assert_release(!"Missing vertex storage buffer binding!"); + } + } + for (Uint32 i = 0; i < pipeline->num_fragment_samplers; i += 1) { + if (!rp->fragment_sampler_bound[i]) { + SDL_assert_release(!"Missing fragment sampler binding!"); + } + } + for (Uint32 i = 0; i < pipeline->num_fragment_storage_textures; i += 1) { + if (!rp->fragment_storage_texture_bound[i]) { + SDL_assert_release(!"Missing fragment storage texture binding!"); + } + } + for (Uint32 i = 0; i < pipeline->num_fragment_storage_buffers; i += 1) { + if (!rp->fragment_storage_buffer_bound[i]) { + SDL_assert_release(!"Missing fragment storage buffer binding!"); + } + } +} + +static void SDL_GPU_CheckComputeBindings(SDL_GPUComputePass *compute_pass) +{ + ComputePass *cp = (ComputePass *)compute_pass; + ComputePipelineCommonHeader *pipeline = (ComputePipelineCommonHeader *)COMPUTEPASS_BOUND_PIPELINE; + for (Uint32 i = 0; i < pipeline->numSamplers; i += 1) { + if (!cp->sampler_bound[i]) { + SDL_assert_release(!"Missing compute sampler binding!"); + } + } + for (Uint32 i = 0; i < pipeline->numReadonlyStorageTextures; i += 1) { + if (!cp->read_only_storage_texture_bound[i]) { + SDL_assert_release(!"Missing compute readonly storage texture binding!"); + } + } + for (Uint32 i = 0; i < pipeline->numReadonlyStorageBuffers; i += 1) { + if (!cp->read_only_storage_buffer_bound[i]) { + SDL_assert_release(!"Missing compute readonly storage buffer binding!"); + } + } + for (Uint32 i = 0; i < pipeline->numReadWriteStorageTextures; i += 1) { + if (!cp->read_write_storage_texture_bound[i]) { + SDL_assert_release(!"Missing compute read-write storage texture binding!"); + } + } + for (Uint32 i = 0; i < pipeline->numReadWriteStorageBuffers; i += 1) { + if (!cp->read_write_storage_buffer_bound[i]) { + SDL_assert_release(!"Missing compute read-write storage buffer bbinding!"); + } + } +} + // Driver Functions #ifndef SDL_GPU_DISABLED static const SDL_GPUBootstrap * SDL_GPUSelectBackend(SDL_PropertiesID props) { Uint32 i; - SDL_GPUShaderFormat format_flags = 0; const char *gpudriver; SDL_VideoDevice *_this = SDL_GetVideoDevice(); @@ -386,25 +598,6 @@ static const SDL_GPUBootstrap * SDL_GPUSelectBackend(SDL_PropertiesID props) return NULL; } - if (SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_SHADERS_PRIVATE_BOOLEAN, false)) { - format_flags |= SDL_GPU_SHADERFORMAT_PRIVATE; - } - if (SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_SHADERS_SPIRV_BOOLEAN, false)) { - format_flags |= SDL_GPU_SHADERFORMAT_SPIRV; - } - if (SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_SHADERS_DXBC_BOOLEAN, false)) { - format_flags |= SDL_GPU_SHADERFORMAT_DXBC; - } - if (SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_SHADERS_DXIL_BOOLEAN, false)) { - format_flags |= SDL_GPU_SHADERFORMAT_DXIL; - } - if (SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_SHADERS_MSL_BOOLEAN, false)) { - format_flags |= SDL_GPU_SHADERFORMAT_MSL; - } - if (SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_SHADERS_METALLIB_BOOLEAN, false)) { - format_flags |= SDL_GPU_SHADERFORMAT_METALLIB; - } - gpudriver = SDL_GetHint(SDL_HINT_GPU_DRIVER); if (gpudriver == NULL) { gpudriver = SDL_GetStringProperty(props, SDL_PROP_GPU_DEVICE_CREATE_NAME_STRING, NULL); @@ -414,11 +607,7 @@ static const SDL_GPUBootstrap * SDL_GPUSelectBackend(SDL_PropertiesID props) if (gpudriver != NULL) { for (i = 0; backends[i]; i += 1) { if (SDL_strcasecmp(gpudriver, backends[i]->name) == 0) { - if (!(backends[i]->shader_formats & format_flags)) { - SDL_SetError("Required shader format for backend %s not provided!", gpudriver); - return NULL; - } - if (backends[i]->PrepareDriver(_this)) { + if (backends[i]->PrepareDriver(_this, props)) { return backends[i]; } } @@ -429,11 +618,7 @@ static const SDL_GPUBootstrap * SDL_GPUSelectBackend(SDL_PropertiesID props) } for (i = 0; backends[i]; i += 1) { - if ((backends[i]->shader_formats & format_flags) == 0) { - // Don't select a backend which doesn't support the app's shaders. - continue; - } - if (backends[i]->PrepareDriver(_this)) { + if (backends[i]->PrepareDriver(_this, props)) { return backends[i]; } } @@ -532,7 +717,6 @@ SDL_GPUDevice *SDL_CreateGPUDeviceWithProperties(SDL_PropertiesID props) result = selectedBackend->CreateDevice(debug_mode, preferLowPower, props); if (result != NULL) { result->backend = selectedBackend->name; - result->shader_formats = selectedBackend->shader_formats; result->debug_mode = debug_mode; } } @@ -586,6 +770,13 @@ SDL_GPUShaderFormat SDL_GetGPUShaderFormats(SDL_GPUDevice *device) return device->shader_formats; } +SDL_PropertiesID SDL_GetGPUDeviceProperties(SDL_GPUDevice *device) +{ + CHECK_DEVICE_MAGIC(device, 0); + + return device->GetDeviceProperties(device); +} + Uint32 SDL_GPUTextureFormatTexelBlockSize( SDL_GPUTextureFormat format) { @@ -721,6 +912,13 @@ bool SDL_GPUTextureSupportsFormat( CHECK_TEXTUREFORMAT_ENUM_INVALID(format, false) } + if ((usage & SDL_GPU_TEXTUREUSAGE_COMPUTE_STORAGE_WRITE) || + (usage & SDL_GPU_TEXTUREUSAGE_COMPUTE_STORAGE_SIMULTANEOUS_READ_WRITE)) { + if (!TextureFormatIsComputeWritable[format]) { + return false; + } + } + return device->SupportsTextureFormat( device->driverData, format, @@ -828,6 +1026,8 @@ SDL_GPUGraphicsPipeline *SDL_CreateGPUGraphicsPipeline( CHECK_BLENDFACTOR_ENUM_INVALID(blend_state->src_alpha_blendfactor, NULL) CHECK_BLENDFACTOR_ENUM_INVALID(blend_state->dst_alpha_blendfactor, NULL) CHECK_BLENDOP_ENUM_INVALID(blend_state->alpha_blend_op, NULL) + + // TODO: validate that format support blending? } } if (graphicsPipelineCreateInfo->target_info.has_depth_stencil_target) { @@ -841,6 +1041,18 @@ SDL_GPUGraphicsPipeline *SDL_CreateGPUGraphicsPipeline( return NULL; } } + if (graphicsPipelineCreateInfo->multisample_state.enable_alpha_to_coverage) { + if (graphicsPipelineCreateInfo->target_info.num_color_targets < 1) { + SDL_assert_release(!"Alpha-to-coverage enabled but no color targets present!"); + return NULL; + } + if (!FormatHasAlpha(graphicsPipelineCreateInfo->target_info.color_target_descriptions[0].format)) { + SDL_assert_release(!"Format is not compatible with alpha-to-coverage!"); + return NULL; + } + + // TODO: validate that format supports belnding? This is only required on Metal. + } if (graphicsPipelineCreateInfo->vertex_input_state.num_vertex_buffers > 0 && graphicsPipelineCreateInfo->vertex_input_state.vertex_buffer_descriptions == NULL) { SDL_assert_release(!"Vertex buffer descriptions array pointer cannot be NULL!"); return NULL; @@ -1343,15 +1555,29 @@ SDL_GPUCommandBuffer *SDL_AcquireGPUCommandBuffer( commandBufferHeader = (CommandBufferCommonHeader *)command_buffer; commandBufferHeader->device = device; commandBufferHeader->render_pass.command_buffer = command_buffer; - commandBufferHeader->render_pass.in_progress = false; - commandBufferHeader->graphics_pipeline_bound = false; commandBufferHeader->compute_pass.command_buffer = command_buffer; - commandBufferHeader->compute_pass.in_progress = false; - commandBufferHeader->compute_pipeline_bound = false; commandBufferHeader->copy_pass.command_buffer = command_buffer; - commandBufferHeader->copy_pass.in_progress = false; - commandBufferHeader->swapchain_texture_acquired = false; - commandBufferHeader->submitted = false; + + if (device->debug_mode) { + commandBufferHeader->render_pass.in_progress = false; + commandBufferHeader->render_pass.graphics_pipeline = NULL; + commandBufferHeader->compute_pass.in_progress = false; + commandBufferHeader->compute_pass.compute_pipeline = NULL; + commandBufferHeader->copy_pass.in_progress = false; + commandBufferHeader->swapchain_texture_acquired = false; + commandBufferHeader->submitted = false; + SDL_zeroa(commandBufferHeader->render_pass.vertex_sampler_bound); + SDL_zeroa(commandBufferHeader->render_pass.vertex_storage_texture_bound); + SDL_zeroa(commandBufferHeader->render_pass.vertex_storage_buffer_bound); + SDL_zeroa(commandBufferHeader->render_pass.fragment_sampler_bound); + SDL_zeroa(commandBufferHeader->render_pass.fragment_storage_texture_bound); + SDL_zeroa(commandBufferHeader->render_pass.fragment_storage_buffer_bound); + SDL_zeroa(commandBufferHeader->compute_pass.sampler_bound); + SDL_zeroa(commandBufferHeader->compute_pass.read_only_storage_texture_bound); + SDL_zeroa(commandBufferHeader->compute_pass.read_only_storage_buffer_bound); + SDL_zeroa(commandBufferHeader->compute_pass.read_write_storage_texture_bound); + SDL_zeroa(commandBufferHeader->compute_pass.read_write_storage_buffer_bound); + } return command_buffer; } @@ -1469,30 +1695,47 @@ SDL_GPURenderPass *SDL_BeginGPURenderPass( if (color_target_infos[i].cycle && color_target_infos[i].load_op == SDL_GPU_LOADOP_LOAD) { SDL_assert_release(!"Cannot cycle color target when load op is LOAD!"); + return NULL; } if (color_target_infos[i].store_op == SDL_GPU_STOREOP_RESOLVE || color_target_infos[i].store_op == SDL_GPU_STOREOP_RESOLVE_AND_STORE) { if (color_target_infos[i].resolve_texture == NULL) { SDL_assert_release(!"Store op is RESOLVE or RESOLVE_AND_STORE but resolve_texture is NULL!"); + return NULL; } else { TextureCommonHeader *resolveTextureHeader = (TextureCommonHeader *)color_target_infos[i].resolve_texture; if (textureHeader->info.sample_count == SDL_GPU_SAMPLECOUNT_1) { SDL_assert_release(!"Store op is RESOLVE or RESOLVE_AND_STORE but texture is not multisample!"); + return NULL; } if (resolveTextureHeader->info.sample_count != SDL_GPU_SAMPLECOUNT_1) { SDL_assert_release(!"Resolve texture must have a sample count of 1!"); + return NULL; } if (resolveTextureHeader->info.format != textureHeader->info.format) { SDL_assert_release(!"Resolve texture must have the same format as its corresponding color target!"); + return NULL; } if (resolveTextureHeader->info.type == SDL_GPU_TEXTURETYPE_3D) { SDL_assert_release(!"Resolve texture must not be of TEXTURETYPE_3D!"); + return NULL; } if (!(resolveTextureHeader->info.usage & SDL_GPU_TEXTUREUSAGE_COLOR_TARGET)) { SDL_assert_release(!"Resolve texture usage must include COLOR_TARGET!"); + return NULL; } } } + + if (color_target_infos[i].layer_or_depth_plane >= textureHeader->info.layer_count_or_depth) { + SDL_assert_release(!"Color target layer index must be less than the texture's layer count!"); + return NULL; + } + + if (color_target_infos[i].mip_level >= textureHeader->info.num_levels) { + SDL_assert_release(!"Color target mip level must be less than the texture's level count!"); + return NULL; + } } if (depth_stencil_target_info != NULL) { @@ -1500,10 +1743,12 @@ SDL_GPURenderPass *SDL_BeginGPURenderPass( TextureCommonHeader *textureHeader = (TextureCommonHeader *)depth_stencil_target_info->texture; if (!(textureHeader->info.usage & SDL_GPU_TEXTUREUSAGE_DEPTH_STENCIL_TARGET)) { SDL_assert_release(!"Depth target must have been created with the DEPTH_STENCIL_TARGET usage flag!"); + return NULL; } if (depth_stencil_target_info->cycle && (depth_stencil_target_info->load_op == SDL_GPU_LOADOP_LOAD || depth_stencil_target_info->stencil_load_op == SDL_GPU_LOADOP_LOAD)) { SDL_assert_release(!"Cannot cycle depth target when load op or stencil load op is LOAD!"); + return NULL; } if (depth_stencil_target_info->store_op == SDL_GPU_STOREOP_RESOLVE || @@ -1511,6 +1756,7 @@ SDL_GPURenderPass *SDL_BeginGPURenderPass( depth_stencil_target_info->store_op == SDL_GPU_STOREOP_RESOLVE_AND_STORE || depth_stencil_target_info->stencil_store_op == SDL_GPU_STOREOP_RESOLVE_AND_STORE) { SDL_assert_release(!"RESOLVE store ops are not supported for depth-stencil targets!"); + return NULL; } } } @@ -1522,7 +1768,18 @@ SDL_GPURenderPass *SDL_BeginGPURenderPass( depth_stencil_target_info); commandBufferHeader = (CommandBufferCommonHeader *)command_buffer; - commandBufferHeader->render_pass.in_progress = true; + + if (COMMAND_BUFFER_DEVICE->debug_mode) { + commandBufferHeader->render_pass.in_progress = true; + for (Uint32 i = 0; i < num_color_targets; i += 1) { + commandBufferHeader->render_pass.color_targets[i] = color_target_infos[i].texture; + } + commandBufferHeader->render_pass.num_color_targets = num_color_targets; + if (depth_stencil_target_info != NULL) { + commandBufferHeader->render_pass.depth_stencil_target = depth_stencil_target_info->texture; + } + } + return (SDL_GPURenderPass *)&(commandBufferHeader->render_pass); } @@ -1530,8 +1787,6 @@ void SDL_BindGPUGraphicsPipeline( SDL_GPURenderPass *render_pass, SDL_GPUGraphicsPipeline *graphics_pipeline) { - CommandBufferCommonHeader *commandBufferHeader; - if (render_pass == NULL) { SDL_InvalidParamError("render_pass"); return; @@ -1545,8 +1800,10 @@ void SDL_BindGPUGraphicsPipeline( RENDERPASS_COMMAND_BUFFER, graphics_pipeline); - commandBufferHeader = (CommandBufferCommonHeader *)RENDERPASS_COMMAND_BUFFER; - commandBufferHeader->graphics_pipeline_bound = true; + + if (RENDERPASS_DEVICE->debug_mode) { + RENDERPASS_BOUND_PIPELINE = graphics_pipeline; + } } void SDL_SetGPUViewport( @@ -1696,6 +1953,15 @@ void SDL_BindGPUVertexSamplers( if (RENDERPASS_DEVICE->debug_mode) { CHECK_RENDERPASS + + if (!((CommandBufferCommonHeader *)RENDERPASS_COMMAND_BUFFER)->ignore_render_pass_texture_validation) + { + CHECK_SAMPLER_TEXTURES + } + + for (Uint32 i = 0; i < num_bindings; i += 1) { + ((RenderPass *)render_pass)->vertex_sampler_bound[first_slot + i] = true; + } } RENDERPASS_DEVICE->BindVertexSamplers( @@ -1722,6 +1988,11 @@ void SDL_BindGPUVertexStorageTextures( if (RENDERPASS_DEVICE->debug_mode) { CHECK_RENDERPASS + CHECK_STORAGE_TEXTURES + + for (Uint32 i = 0; i < num_bindings; i += 1) { + ((RenderPass *)render_pass)->vertex_storage_texture_bound[first_slot + i] = true; + } } RENDERPASS_DEVICE->BindVertexStorageTextures( @@ -1748,6 +2019,10 @@ void SDL_BindGPUVertexStorageBuffers( if (RENDERPASS_DEVICE->debug_mode) { CHECK_RENDERPASS + + for (Uint32 i = 0; i < num_bindings; i += 1) { + ((RenderPass *)render_pass)->vertex_storage_buffer_bound[first_slot + i] = true; + } } RENDERPASS_DEVICE->BindVertexStorageBuffers( @@ -1774,6 +2049,14 @@ void SDL_BindGPUFragmentSamplers( if (RENDERPASS_DEVICE->debug_mode) { CHECK_RENDERPASS + + if (!((CommandBufferCommonHeader *)RENDERPASS_COMMAND_BUFFER)->ignore_render_pass_texture_validation) { + CHECK_SAMPLER_TEXTURES + } + + for (Uint32 i = 0; i < num_bindings; i += 1) { + ((RenderPass *)render_pass)->fragment_sampler_bound[first_slot + i] = true; + } } RENDERPASS_DEVICE->BindFragmentSamplers( @@ -1800,6 +2083,11 @@ void SDL_BindGPUFragmentStorageTextures( if (RENDERPASS_DEVICE->debug_mode) { CHECK_RENDERPASS + CHECK_STORAGE_TEXTURES + + for (Uint32 i = 0; i < num_bindings; i += 1) { + ((RenderPass *)render_pass)->fragment_storage_texture_bound[first_slot + i] = true; + } } RENDERPASS_DEVICE->BindFragmentStorageTextures( @@ -1826,6 +2114,10 @@ void SDL_BindGPUFragmentStorageBuffers( if (RENDERPASS_DEVICE->debug_mode) { CHECK_RENDERPASS + + for (Uint32 i = 0; i < num_bindings; i += 1) { + ((RenderPass *)render_pass)->fragment_storage_buffer_bound[first_slot + i] = true; + } } RENDERPASS_DEVICE->BindFragmentStorageBuffers( @@ -1851,6 +2143,7 @@ void SDL_DrawGPUIndexedPrimitives( if (RENDERPASS_DEVICE->debug_mode) { CHECK_RENDERPASS CHECK_GRAPHICS_PIPELINE_BOUND + SDL_GPU_CheckGraphicsBindings(render_pass); } RENDERPASS_DEVICE->DrawIndexedPrimitives( @@ -1877,6 +2170,7 @@ void SDL_DrawGPUPrimitives( if (RENDERPASS_DEVICE->debug_mode) { CHECK_RENDERPASS CHECK_GRAPHICS_PIPELINE_BOUND + SDL_GPU_CheckGraphicsBindings(render_pass); } RENDERPASS_DEVICE->DrawPrimitives( @@ -1905,6 +2199,7 @@ void SDL_DrawGPUPrimitivesIndirect( if (RENDERPASS_DEVICE->debug_mode) { CHECK_RENDERPASS CHECK_GRAPHICS_PIPELINE_BOUND + SDL_GPU_CheckGraphicsBindings(render_pass); } RENDERPASS_DEVICE->DrawPrimitivesIndirect( @@ -1932,6 +2227,7 @@ void SDL_DrawGPUIndexedPrimitivesIndirect( if (RENDERPASS_DEVICE->debug_mode) { CHECK_RENDERPASS CHECK_GRAPHICS_PIPELINE_BOUND + SDL_GPU_CheckGraphicsBindings(render_pass); } RENDERPASS_DEVICE->DrawIndexedPrimitivesIndirect( @@ -1945,6 +2241,7 @@ void SDL_EndGPURenderPass( SDL_GPURenderPass *render_pass) { CommandBufferCommonHeader *commandBufferCommonHeader; + commandBufferCommonHeader = (CommandBufferCommonHeader *)RENDERPASS_COMMAND_BUFFER; if (render_pass == NULL) { SDL_InvalidParamError("render_pass"); @@ -1958,9 +2255,22 @@ void SDL_EndGPURenderPass( RENDERPASS_DEVICE->EndRenderPass( RENDERPASS_COMMAND_BUFFER); - commandBufferCommonHeader = (CommandBufferCommonHeader *)RENDERPASS_COMMAND_BUFFER; - commandBufferCommonHeader->render_pass.in_progress = false; - commandBufferCommonHeader->graphics_pipeline_bound = false; + if (RENDERPASS_DEVICE->debug_mode) { + commandBufferCommonHeader->render_pass.in_progress = false; + for (Uint32 i = 0; i < MAX_COLOR_TARGET_BINDINGS; i += 1) + { + commandBufferCommonHeader->render_pass.color_targets[i] = NULL; + } + commandBufferCommonHeader->render_pass.num_color_targets = 0; + commandBufferCommonHeader->render_pass.depth_stencil_target = NULL; + commandBufferCommonHeader->render_pass.graphics_pipeline = NULL; + SDL_zeroa(commandBufferCommonHeader->render_pass.vertex_sampler_bound); + SDL_zeroa(commandBufferCommonHeader->render_pass.vertex_storage_texture_bound); + SDL_zeroa(commandBufferCommonHeader->render_pass.vertex_storage_buffer_bound); + SDL_zeroa(commandBufferCommonHeader->render_pass.fragment_sampler_bound); + SDL_zeroa(commandBufferCommonHeader->render_pass.fragment_storage_texture_bound); + SDL_zeroa(commandBufferCommonHeader->render_pass.fragment_storage_buffer_bound); + } } // Compute Pass @@ -2004,6 +2314,16 @@ SDL_GPUComputePass *SDL_BeginGPUComputePass( SDL_assert_release(!"Texture must be created with COMPUTE_STORAGE_WRITE or COMPUTE_STORAGE_SIMULTANEOUS_READ_WRITE flag"); return NULL; } + + if (storage_texture_bindings[i].layer >= header->info.layer_count_or_depth) { + SDL_assert_release(!"Storage texture layer index must be less than the texture's layer count!"); + return NULL; + } + + if (storage_texture_bindings[i].mip_level >= header->info.num_levels) { + SDL_assert_release(!"Storage texture mip level must be less than the texture's level count!"); + return NULL; + } } // TODO: validate buffer usage? @@ -2017,7 +2337,19 @@ SDL_GPUComputePass *SDL_BeginGPUComputePass( num_storage_buffer_bindings); commandBufferHeader = (CommandBufferCommonHeader *)command_buffer; - commandBufferHeader->compute_pass.in_progress = true; + + if (COMMAND_BUFFER_DEVICE->debug_mode) { + commandBufferHeader->compute_pass.in_progress = true; + + for (Uint32 i = 0; i < num_storage_texture_bindings; i += 1) { + commandBufferHeader->compute_pass.read_write_storage_texture_bound[i] = true; + } + + for (Uint32 i = 0; i < num_storage_buffer_bindings; i += 1) { + commandBufferHeader->compute_pass.read_write_storage_buffer_bound[i] = true; + } + } + return (SDL_GPUComputePass *)&(commandBufferHeader->compute_pass); } @@ -2025,8 +2357,6 @@ void SDL_BindGPUComputePipeline( SDL_GPUComputePass *compute_pass, SDL_GPUComputePipeline *compute_pipeline) { - CommandBufferCommonHeader *commandBufferHeader; - if (compute_pass == NULL) { SDL_InvalidParamError("compute_pass"); return; @@ -2044,8 +2374,10 @@ void SDL_BindGPUComputePipeline( COMPUTEPASS_COMMAND_BUFFER, compute_pipeline); - commandBufferHeader = (CommandBufferCommonHeader *)COMPUTEPASS_COMMAND_BUFFER; - commandBufferHeader->compute_pipeline_bound = true; + + if (COMPUTEPASS_DEVICE->debug_mode) { + COMPUTEPASS_BOUND_PIPELINE = compute_pipeline; + } } void SDL_BindGPUComputeSamplers( @@ -2065,6 +2397,10 @@ void SDL_BindGPUComputeSamplers( if (COMPUTEPASS_DEVICE->debug_mode) { CHECK_COMPUTEPASS + + for (Uint32 i = 0; i < num_bindings; i += 1) { + ((ComputePass *)compute_pass)->sampler_bound[first_slot + i] = true; + } } COMPUTEPASS_DEVICE->BindComputeSamplers( @@ -2091,6 +2427,10 @@ void SDL_BindGPUComputeStorageTextures( if (COMPUTEPASS_DEVICE->debug_mode) { CHECK_COMPUTEPASS + + for (Uint32 i = 0; i < num_bindings; i += 1) { + ((ComputePass *)compute_pass)->read_only_storage_texture_bound[first_slot + i] = true; + } } COMPUTEPASS_DEVICE->BindComputeStorageTextures( @@ -2117,6 +2457,10 @@ void SDL_BindGPUComputeStorageBuffers( if (COMPUTEPASS_DEVICE->debug_mode) { CHECK_COMPUTEPASS + + for (Uint32 i = 0; i < num_bindings; i += 1) { + ((ComputePass *)compute_pass)->read_only_storage_buffer_bound[first_slot + i] = true; + } } COMPUTEPASS_DEVICE->BindComputeStorageBuffers( @@ -2140,6 +2484,7 @@ void SDL_DispatchGPUCompute( if (COMPUTEPASS_DEVICE->debug_mode) { CHECK_COMPUTEPASS CHECK_COMPUTE_PIPELINE_BOUND + SDL_GPU_CheckComputeBindings(compute_pass); } COMPUTEPASS_DEVICE->DispatchCompute( @@ -2162,6 +2507,7 @@ void SDL_DispatchGPUComputeIndirect( if (COMPUTEPASS_DEVICE->debug_mode) { CHECK_COMPUTEPASS CHECK_COMPUTE_PIPELINE_BOUND + SDL_GPU_CheckComputeBindings(compute_pass); } COMPUTEPASS_DEVICE->DispatchComputeIndirect( @@ -2187,9 +2533,16 @@ void SDL_EndGPUComputePass( COMPUTEPASS_DEVICE->EndComputePass( COMPUTEPASS_COMMAND_BUFFER); - commandBufferCommonHeader = (CommandBufferCommonHeader *)COMPUTEPASS_COMMAND_BUFFER; - commandBufferCommonHeader->compute_pass.in_progress = false; - commandBufferCommonHeader->compute_pipeline_bound = false; + if (COMPUTEPASS_DEVICE->debug_mode) { + commandBufferCommonHeader = (CommandBufferCommonHeader *)COMPUTEPASS_COMMAND_BUFFER; + commandBufferCommonHeader->compute_pass.in_progress = false; + commandBufferCommonHeader->compute_pass.compute_pipeline = false; + SDL_zeroa(commandBufferCommonHeader->compute_pass.sampler_bound); + SDL_zeroa(commandBufferCommonHeader->compute_pass.read_only_storage_texture_bound); + SDL_zeroa(commandBufferCommonHeader->compute_pass.read_only_storage_buffer_bound); + SDL_zeroa(commandBufferCommonHeader->compute_pass.read_write_storage_texture_bound); + SDL_zeroa(commandBufferCommonHeader->compute_pass.read_write_storage_buffer_bound); + } } // TransferBuffer Data @@ -2247,7 +2600,11 @@ SDL_GPUCopyPass *SDL_BeginGPUCopyPass( command_buffer); commandBufferHeader = (CommandBufferCommonHeader *)command_buffer; - commandBufferHeader->copy_pass.in_progress = true; + + if (COMMAND_BUFFER_DEVICE->debug_mode) { + commandBufferHeader->copy_pass.in_progress = true; + } + return (SDL_GPUCopyPass *)&(commandBufferHeader->copy_pass); } @@ -2359,6 +2716,13 @@ void SDL_CopyGPUTextureToTexture( SDL_assert_release(!"Destination texture cannot be NULL!"); return; } + + TextureCommonHeader *srcHeader = (TextureCommonHeader *)source->texture; + TextureCommonHeader *dstHeader = (TextureCommonHeader *)destination->texture; + if (srcHeader->info.format != dstHeader->info.format) { + SDL_assert_release(!"Source and destination textures must have the same format!"); + return; + } } COPYPASS_DEVICE->CopyTextureToTexture( @@ -2498,7 +2862,9 @@ void SDL_EndGPUCopyPass( COPYPASS_DEVICE->EndCopyPass( COPYPASS_COMMAND_BUFFER); - ((CommandBufferCommonHeader *)COPYPASS_COMMAND_BUFFER)->copy_pass.in_progress = false; + if (COPYPASS_DEVICE->debug_mode) { + ((CommandBufferCommonHeader *)COPYPASS_COMMAND_BUFFER)->copy_pass.in_progress = false; + } } void SDL_GenerateMipmapsForGPUTexture( @@ -2528,11 +2894,19 @@ void SDL_GenerateMipmapsForGPUTexture( SDL_assert_release(!"GenerateMipmaps texture must be created with SAMPLER and COLOR_TARGET usage flags!"); return; } + + CommandBufferCommonHeader *commandBufferHeader = (CommandBufferCommonHeader *)command_buffer; + commandBufferHeader->ignore_render_pass_texture_validation = true; } COMMAND_BUFFER_DEVICE->GenerateMipmaps( command_buffer, texture); + + if (COMMAND_BUFFER_DEVICE->debug_mode) { + CommandBufferCommonHeader *commandBufferHeader = (CommandBufferCommonHeader *)command_buffer; + commandBufferHeader->ignore_render_pass_texture_validation = false; + } } void SDL_BlitGPUTexture( diff --git a/libs/SDL3/src/gpu/SDL_sysgpu.h b/libs/SDL3/src/gpu/SDL_sysgpu.h index 6de1765f..b4b54b26 100644 --- a/libs/SDL3/src/gpu/SDL_sysgpu.h +++ b/libs/SDL3/src/gpu/SDL_sysgpu.h @@ -24,6 +24,21 @@ #ifndef SDL_GPU_DRIVER_H #define SDL_GPU_DRIVER_H +// GraphicsDevice Limits + +#define MAX_TEXTURE_SAMPLERS_PER_STAGE 16 +#define MAX_STORAGE_TEXTURES_PER_STAGE 8 +#define MAX_STORAGE_BUFFERS_PER_STAGE 8 +#define MAX_UNIFORM_BUFFERS_PER_STAGE 4 +#define MAX_COMPUTE_WRITE_TEXTURES 8 +#define MAX_COMPUTE_WRITE_BUFFERS 8 +#define UNIFORM_BUFFER_SIZE 32768 +#define MAX_VERTEX_BUFFERS 16 +#define MAX_VERTEX_ATTRIBUTES 16 +#define MAX_COLOR_TARGET_BINDINGS 4 +#define MAX_PRESENT_COUNT 16 +#define MAX_FRAMES_IN_FLIGHT 3 + // Common Structs typedef struct Pass @@ -32,16 +47,51 @@ typedef struct Pass bool in_progress; } Pass; +typedef struct ComputePass +{ + SDL_GPUCommandBuffer *command_buffer; + bool in_progress; + + SDL_GPUComputePipeline *compute_pipeline; + + bool sampler_bound[MAX_TEXTURE_SAMPLERS_PER_STAGE]; + bool read_only_storage_texture_bound[MAX_STORAGE_TEXTURES_PER_STAGE]; + bool read_only_storage_buffer_bound[MAX_STORAGE_BUFFERS_PER_STAGE]; + bool read_write_storage_texture_bound[MAX_COMPUTE_WRITE_TEXTURES]; + bool read_write_storage_buffer_bound[MAX_COMPUTE_WRITE_BUFFERS]; +} ComputePass; + +typedef struct RenderPass +{ + SDL_GPUCommandBuffer *command_buffer; + bool in_progress; + SDL_GPUTexture *color_targets[MAX_COLOR_TARGET_BINDINGS]; + Uint32 num_color_targets; + SDL_GPUTexture *depth_stencil_target; + + SDL_GPUGraphicsPipeline *graphics_pipeline; + + bool vertex_sampler_bound[MAX_TEXTURE_SAMPLERS_PER_STAGE]; + bool vertex_storage_texture_bound[MAX_STORAGE_TEXTURES_PER_STAGE]; + bool vertex_storage_buffer_bound[MAX_STORAGE_BUFFERS_PER_STAGE]; + + bool fragment_sampler_bound[MAX_TEXTURE_SAMPLERS_PER_STAGE]; + bool fragment_storage_texture_bound[MAX_STORAGE_TEXTURES_PER_STAGE]; + bool fragment_storage_buffer_bound[MAX_STORAGE_BUFFERS_PER_STAGE]; +} RenderPass; + typedef struct CommandBufferCommonHeader { SDL_GPUDevice *device; - Pass render_pass; - bool graphics_pipeline_bound; - Pass compute_pass; - bool compute_pipeline_bound; + + RenderPass render_pass; + ComputePass compute_pass; + Pass copy_pass; bool swapchain_texture_acquired; bool submitted; + // used to avoid tripping assert on GenerateMipmaps + bool ignore_render_pass_texture_validation; } CommandBufferCommonHeader; typedef struct TextureCommonHeader @@ -49,6 +99,29 @@ typedef struct TextureCommonHeader SDL_GPUTextureCreateInfo info; } TextureCommonHeader; +typedef struct GraphicsPipelineCommonHeader +{ + Uint32 num_vertex_samplers; + Uint32 num_vertex_storage_textures; + Uint32 num_vertex_storage_buffers; + Uint32 num_vertex_uniform_buffers; + + Uint32 num_fragment_samplers; + Uint32 num_fragment_storage_textures; + Uint32 num_fragment_storage_buffers; + Uint32 num_fragment_uniform_buffers; +} GraphicsPipelineCommonHeader; + +typedef struct ComputePipelineCommonHeader +{ + Uint32 numSamplers; + Uint32 numReadonlyStorageTextures; + Uint32 numReadonlyStorageBuffers; + Uint32 numReadWriteStorageTextures; + Uint32 numReadWriteStorageBuffers; + Uint32 numUniformBuffers; +} ComputePipelineCommonHeader; + typedef struct BlitFragmentUniforms { // texcoord space @@ -136,6 +209,7 @@ static inline Sint32 Texture_GetBlockWidth( case SDL_GPU_TEXTUREFORMAT_BC6H_RGB_FLOAT: case SDL_GPU_TEXTUREFORMAT_BC6H_RGB_UFLOAT: case SDL_GPU_TEXTUREFORMAT_BC1_RGBA_UNORM_SRGB: + case SDL_GPU_TEXTUREFORMAT_BC2_RGBA_UNORM_SRGB: case SDL_GPU_TEXTUREFORMAT_BC3_RGBA_UNORM_SRGB: case SDL_GPU_TEXTUREFORMAT_BC7_RGBA_UNORM_SRGB: case SDL_GPU_TEXTUREFORMAT_ASTC_4x4_UNORM: @@ -253,6 +327,7 @@ static inline Sint32 Texture_GetBlockHeight( case SDL_GPU_TEXTUREFORMAT_BC6H_RGB_FLOAT: case SDL_GPU_TEXTUREFORMAT_BC6H_RGB_UFLOAT: case SDL_GPU_TEXTUREFORMAT_BC1_RGBA_UNORM_SRGB: + case SDL_GPU_TEXTUREFORMAT_BC2_RGBA_UNORM_SRGB: case SDL_GPU_TEXTUREFORMAT_BC3_RGBA_UNORM_SRGB: case SDL_GPU_TEXTUREFORMAT_BC7_RGBA_UNORM_SRGB: case SDL_GPU_TEXTUREFORMAT_ASTC_5x4_UNORM: @@ -371,6 +446,154 @@ static inline bool IsIntegerFormat( } } +static inline bool IsCompressedFormat( + SDL_GPUTextureFormat format) +{ + switch (format) { + case SDL_GPU_TEXTUREFORMAT_BC1_RGBA_UNORM: + case SDL_GPU_TEXTUREFORMAT_BC1_RGBA_UNORM_SRGB: + case SDL_GPU_TEXTUREFORMAT_BC2_RGBA_UNORM: + case SDL_GPU_TEXTUREFORMAT_BC2_RGBA_UNORM_SRGB: + case SDL_GPU_TEXTUREFORMAT_BC3_RGBA_UNORM: + case SDL_GPU_TEXTUREFORMAT_BC3_RGBA_UNORM_SRGB: + case SDL_GPU_TEXTUREFORMAT_BC4_R_UNORM: + case SDL_GPU_TEXTUREFORMAT_BC5_RG_UNORM: + case SDL_GPU_TEXTUREFORMAT_BC6H_RGB_FLOAT: + case SDL_GPU_TEXTUREFORMAT_BC6H_RGB_UFLOAT: + case SDL_GPU_TEXTUREFORMAT_BC7_RGBA_UNORM: + case SDL_GPU_TEXTUREFORMAT_BC7_RGBA_UNORM_SRGB: + case SDL_GPU_TEXTUREFORMAT_ASTC_4x4_UNORM: + case SDL_GPU_TEXTUREFORMAT_ASTC_5x4_UNORM: + case SDL_GPU_TEXTUREFORMAT_ASTC_5x5_UNORM: + case SDL_GPU_TEXTUREFORMAT_ASTC_6x5_UNORM: + case SDL_GPU_TEXTUREFORMAT_ASTC_6x6_UNORM: + case SDL_GPU_TEXTUREFORMAT_ASTC_8x5_UNORM: + case SDL_GPU_TEXTUREFORMAT_ASTC_8x6_UNORM: + case SDL_GPU_TEXTUREFORMAT_ASTC_8x8_UNORM: + case SDL_GPU_TEXTUREFORMAT_ASTC_10x5_UNORM: + case SDL_GPU_TEXTUREFORMAT_ASTC_10x6_UNORM: + case SDL_GPU_TEXTUREFORMAT_ASTC_10x8_UNORM: + case SDL_GPU_TEXTUREFORMAT_ASTC_10x10_UNORM: + case SDL_GPU_TEXTUREFORMAT_ASTC_12x10_UNORM: + case SDL_GPU_TEXTUREFORMAT_ASTC_12x12_UNORM: + case SDL_GPU_TEXTUREFORMAT_ASTC_4x4_UNORM_SRGB: + case SDL_GPU_TEXTUREFORMAT_ASTC_5x4_UNORM_SRGB: + case SDL_GPU_TEXTUREFORMAT_ASTC_5x5_UNORM_SRGB: + case SDL_GPU_TEXTUREFORMAT_ASTC_6x5_UNORM_SRGB: + case SDL_GPU_TEXTUREFORMAT_ASTC_6x6_UNORM_SRGB: + case SDL_GPU_TEXTUREFORMAT_ASTC_8x5_UNORM_SRGB: + case SDL_GPU_TEXTUREFORMAT_ASTC_8x6_UNORM_SRGB: + case SDL_GPU_TEXTUREFORMAT_ASTC_8x8_UNORM_SRGB: + case SDL_GPU_TEXTUREFORMAT_ASTC_10x5_UNORM_SRGB: + case SDL_GPU_TEXTUREFORMAT_ASTC_10x6_UNORM_SRGB: + case SDL_GPU_TEXTUREFORMAT_ASTC_10x8_UNORM_SRGB: + case SDL_GPU_TEXTUREFORMAT_ASTC_10x10_UNORM_SRGB: + case SDL_GPU_TEXTUREFORMAT_ASTC_12x10_UNORM_SRGB: + case SDL_GPU_TEXTUREFORMAT_ASTC_12x12_UNORM_SRGB: + case SDL_GPU_TEXTUREFORMAT_ASTC_4x4_FLOAT: + case SDL_GPU_TEXTUREFORMAT_ASTC_5x4_FLOAT: + case SDL_GPU_TEXTUREFORMAT_ASTC_5x5_FLOAT: + case SDL_GPU_TEXTUREFORMAT_ASTC_6x5_FLOAT: + case SDL_GPU_TEXTUREFORMAT_ASTC_6x6_FLOAT: + case SDL_GPU_TEXTUREFORMAT_ASTC_8x5_FLOAT: + case SDL_GPU_TEXTUREFORMAT_ASTC_8x6_FLOAT: + case SDL_GPU_TEXTUREFORMAT_ASTC_8x8_FLOAT: + case SDL_GPU_TEXTUREFORMAT_ASTC_10x5_FLOAT: + case SDL_GPU_TEXTUREFORMAT_ASTC_10x6_FLOAT: + case SDL_GPU_TEXTUREFORMAT_ASTC_10x8_FLOAT: + case SDL_GPU_TEXTUREFORMAT_ASTC_10x10_FLOAT: + case SDL_GPU_TEXTUREFORMAT_ASTC_12x10_FLOAT: + case SDL_GPU_TEXTUREFORMAT_ASTC_12x12_FLOAT: + return true; + + default: + return false; + } +} + +static inline bool FormatHasAlpha( + SDL_GPUTextureFormat format) +{ + switch (format) { + case SDL_GPU_TEXTUREFORMAT_ASTC_12x10_UNORM: + case SDL_GPU_TEXTUREFORMAT_ASTC_12x12_UNORM: + case SDL_GPU_TEXTUREFORMAT_ASTC_12x10_UNORM_SRGB: + case SDL_GPU_TEXTUREFORMAT_ASTC_12x12_UNORM_SRGB: + case SDL_GPU_TEXTUREFORMAT_ASTC_12x10_FLOAT: + case SDL_GPU_TEXTUREFORMAT_ASTC_12x12_FLOAT: + case SDL_GPU_TEXTUREFORMAT_ASTC_10x5_UNORM: + case SDL_GPU_TEXTUREFORMAT_ASTC_10x6_UNORM: + case SDL_GPU_TEXTUREFORMAT_ASTC_10x8_UNORM: + case SDL_GPU_TEXTUREFORMAT_ASTC_10x10_UNORM: + case SDL_GPU_TEXTUREFORMAT_ASTC_10x5_UNORM_SRGB: + case SDL_GPU_TEXTUREFORMAT_ASTC_10x6_UNORM_SRGB: + case SDL_GPU_TEXTUREFORMAT_ASTC_10x8_UNORM_SRGB: + case SDL_GPU_TEXTUREFORMAT_ASTC_10x10_UNORM_SRGB: + case SDL_GPU_TEXTUREFORMAT_ASTC_10x5_FLOAT: + case SDL_GPU_TEXTUREFORMAT_ASTC_10x6_FLOAT: + case SDL_GPU_TEXTUREFORMAT_ASTC_10x8_FLOAT: + case SDL_GPU_TEXTUREFORMAT_ASTC_10x10_FLOAT: + case SDL_GPU_TEXTUREFORMAT_ASTC_8x5_UNORM: + case SDL_GPU_TEXTUREFORMAT_ASTC_8x6_UNORM: + case SDL_GPU_TEXTUREFORMAT_ASTC_8x8_UNORM: + case SDL_GPU_TEXTUREFORMAT_ASTC_8x5_UNORM_SRGB: + case SDL_GPU_TEXTUREFORMAT_ASTC_8x6_UNORM_SRGB: + case SDL_GPU_TEXTUREFORMAT_ASTC_8x8_UNORM_SRGB: + case SDL_GPU_TEXTUREFORMAT_ASTC_8x5_FLOAT: + case SDL_GPU_TEXTUREFORMAT_ASTC_8x6_FLOAT: + case SDL_GPU_TEXTUREFORMAT_ASTC_8x8_FLOAT: + case SDL_GPU_TEXTUREFORMAT_ASTC_6x5_UNORM: + case SDL_GPU_TEXTUREFORMAT_ASTC_6x6_UNORM: + case SDL_GPU_TEXTUREFORMAT_ASTC_6x5_UNORM_SRGB: + case SDL_GPU_TEXTUREFORMAT_ASTC_6x6_UNORM_SRGB: + case SDL_GPU_TEXTUREFORMAT_ASTC_6x5_FLOAT: + case SDL_GPU_TEXTUREFORMAT_ASTC_6x6_FLOAT: + case SDL_GPU_TEXTUREFORMAT_ASTC_5x4_UNORM: + case SDL_GPU_TEXTUREFORMAT_ASTC_5x5_UNORM: + case SDL_GPU_TEXTUREFORMAT_ASTC_5x4_UNORM_SRGB: + case SDL_GPU_TEXTUREFORMAT_ASTC_5x5_UNORM_SRGB: + case SDL_GPU_TEXTUREFORMAT_ASTC_5x4_FLOAT: + case SDL_GPU_TEXTUREFORMAT_ASTC_5x5_FLOAT: + case SDL_GPU_TEXTUREFORMAT_ASTC_4x4_UNORM: + case SDL_GPU_TEXTUREFORMAT_ASTC_4x4_UNORM_SRGB: + case SDL_GPU_TEXTUREFORMAT_ASTC_4x4_FLOAT: + // ASTC textures may or may not have alpha; return true as this is mainly intended for validation + return true; + + case SDL_GPU_TEXTUREFORMAT_BC1_RGBA_UNORM: + case SDL_GPU_TEXTUREFORMAT_BC2_RGBA_UNORM: + case SDL_GPU_TEXTUREFORMAT_BC3_RGBA_UNORM: + case SDL_GPU_TEXTUREFORMAT_BC7_RGBA_UNORM: + case SDL_GPU_TEXTUREFORMAT_BC1_RGBA_UNORM_SRGB: + case SDL_GPU_TEXTUREFORMAT_BC2_RGBA_UNORM_SRGB: + case SDL_GPU_TEXTUREFORMAT_BC3_RGBA_UNORM_SRGB: + case SDL_GPU_TEXTUREFORMAT_BC7_RGBA_UNORM_SRGB: + case SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM: + case SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM: + case SDL_GPU_TEXTUREFORMAT_B5G5R5A1_UNORM: + case SDL_GPU_TEXTUREFORMAT_B4G4R4A4_UNORM: + case SDL_GPU_TEXTUREFORMAT_R10G10B10A2_UNORM: + case SDL_GPU_TEXTUREFORMAT_R16G16B16A16_UNORM: + case SDL_GPU_TEXTUREFORMAT_A8_UNORM: + case SDL_GPU_TEXTUREFORMAT_R8G8B8A8_SNORM: + case SDL_GPU_TEXTUREFORMAT_R16G16B16A16_SNORM: + case SDL_GPU_TEXTUREFORMAT_R16G16B16A16_FLOAT: + case SDL_GPU_TEXTUREFORMAT_R32G32B32A32_FLOAT: + case SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UINT: + case SDL_GPU_TEXTUREFORMAT_R16G16B16A16_UINT: + case SDL_GPU_TEXTUREFORMAT_R32G32B32A32_UINT: + case SDL_GPU_TEXTUREFORMAT_R8G8B8A8_INT: + case SDL_GPU_TEXTUREFORMAT_R16G16B16A16_INT: + case SDL_GPU_TEXTUREFORMAT_R32G32B32A32_INT: + case SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM_SRGB: + case SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM_SRGB: + return true; + + default: + return false; + } +} + static inline Uint32 IndexSize(SDL_GPUIndexElementSize size) { return (size == SDL_GPU_INDEXELEMENTSIZE_16BIT) ? 2 : 4; @@ -385,21 +608,6 @@ static inline Uint32 BytesPerRow( return blocksPerRow * SDL_GPUTextureFormatTexelBlockSize(format); } -// GraphicsDevice Limits - -#define MAX_TEXTURE_SAMPLERS_PER_STAGE 16 -#define MAX_STORAGE_TEXTURES_PER_STAGE 8 -#define MAX_STORAGE_BUFFERS_PER_STAGE 8 -#define MAX_UNIFORM_BUFFERS_PER_STAGE 4 -#define MAX_COMPUTE_WRITE_TEXTURES 8 -#define MAX_COMPUTE_WRITE_BUFFERS 8 -#define UNIFORM_BUFFER_SIZE 32768 -#define MAX_VERTEX_BUFFERS 16 -#define MAX_VERTEX_ATTRIBUTES 16 -#define MAX_COLOR_TARGET_BINDINGS 4 -#define MAX_PRESENT_COUNT 16 -#define MAX_FRAMES_IN_FLIGHT 3 - // Internal Macros #define EXPAND_ARRAY_IF_NEEDED(arr, elementType, newCount, capacity, newCapacity) \ @@ -457,10 +665,12 @@ typedef struct SDL_GPURenderer SDL_GPURenderer; struct SDL_GPUDevice { - // Quit + // Device void (*DestroyDevice)(SDL_GPUDevice *device); + SDL_PropertiesID (*GetDeviceProperties)(SDL_GPUDevice *device); + // State Creation SDL_GPUComputePipeline *(*CreateComputePipeline)( @@ -892,6 +1102,7 @@ struct SDL_GPUDevice result->func = name##_##func; #define ASSIGN_DRIVER(name) \ ASSIGN_DRIVER_FUNC(DestroyDevice, name) \ + ASSIGN_DRIVER_FUNC(GetDeviceProperties, name) \ ASSIGN_DRIVER_FUNC(CreateComputePipeline, name) \ ASSIGN_DRIVER_FUNC(CreateGraphicsPipeline, name) \ ASSIGN_DRIVER_FUNC(CreateSampler, name) \ @@ -977,8 +1188,7 @@ struct SDL_GPUDevice typedef struct SDL_GPUBootstrap { const char *name; - const SDL_GPUShaderFormat shader_formats; - bool (*PrepareDriver)(SDL_VideoDevice *_this); + bool (*PrepareDriver)(SDL_VideoDevice *_this, SDL_PropertiesID props); SDL_GPUDevice *(*CreateDevice)(bool debug_mode, bool prefer_low_power, SDL_PropertiesID props); } SDL_GPUBootstrap; diff --git a/libs/SDL3/src/gpu/d3d12/SDL_gpu_d3d12.c b/libs/SDL3/src/gpu/d3d12/SDL_gpu_d3d12.c index be13d8da..09f6f348 100644 --- a/libs/SDL3/src/gpu/d3d12/SDL_gpu_d3d12.c +++ b/libs/SDL3/src/gpu/d3d12/SDL_gpu_d3d12.c @@ -121,18 +121,6 @@ #define SDL_GPU_SHADERSTAGE_COMPUTE (SDL_GPUShaderStage)2 -#define EXPAND_ELEMENTS_IF_NEEDED(arr, initialValue, type) \ - if (arr->count == arr->capacity) { \ - if (arr->capacity == 0) { \ - arr->capacity = initialValue; \ - } else { \ - arr->capacity *= 2; \ - } \ - arr->elements = (type *)SDL_realloc( \ - arr->elements, \ - arr->capacity * sizeof(type)); \ - } - #ifdef _WIN32 #define HRESULT_FMT "(0x%08lX)" #else @@ -475,6 +463,115 @@ static DXGI_FORMAT SDLToD3D12_DepthFormat[] = { }; SDL_COMPILE_TIME_ASSERT(SDLToD3D12_DepthFormat, SDL_arraysize(SDLToD3D12_DepthFormat) == SDL_GPU_TEXTUREFORMAT_MAX_ENUM_VALUE); +static DXGI_FORMAT SDLToD3D12_TypelessFormat[] = { + DXGI_FORMAT_UNKNOWN, // INVALID + DXGI_FORMAT_UNKNOWN, // A8_UNORM + DXGI_FORMAT_UNKNOWN, // R8_UNORM + DXGI_FORMAT_UNKNOWN, // R8G8_UNORM + DXGI_FORMAT_UNKNOWN, // R8G8B8A8_UNORM + DXGI_FORMAT_UNKNOWN, // R16_UNORM + DXGI_FORMAT_UNKNOWN, // R16G16_UNORM + DXGI_FORMAT_UNKNOWN, // R16G16B16A16_UNORM + DXGI_FORMAT_UNKNOWN, // R10G10B10A2_UNORM + DXGI_FORMAT_UNKNOWN, // B5G6R5_UNORM + DXGI_FORMAT_UNKNOWN, // B5G5R5A1_UNORM + DXGI_FORMAT_UNKNOWN, // B4G4R4A4_UNORM + DXGI_FORMAT_UNKNOWN, // B8G8R8A8_UNORM + DXGI_FORMAT_UNKNOWN, // BC1_UNORM + DXGI_FORMAT_UNKNOWN, // BC2_UNORM + DXGI_FORMAT_UNKNOWN, // BC3_UNORM + DXGI_FORMAT_UNKNOWN, // BC4_UNORM + DXGI_FORMAT_UNKNOWN, // BC5_UNORM + DXGI_FORMAT_UNKNOWN, // BC7_UNORM + DXGI_FORMAT_UNKNOWN, // BC6H_FLOAT + DXGI_FORMAT_UNKNOWN, // BC6H_UFLOAT + DXGI_FORMAT_UNKNOWN, // R8_SNORM + DXGI_FORMAT_UNKNOWN, // R8G8_SNORM + DXGI_FORMAT_UNKNOWN, // R8G8B8A8_SNORM + DXGI_FORMAT_UNKNOWN, // R16_SNORM + DXGI_FORMAT_UNKNOWN, // R16G16_SNORM + DXGI_FORMAT_UNKNOWN, // R16G16B16A16_SNORM + DXGI_FORMAT_UNKNOWN, // R16_FLOAT + DXGI_FORMAT_UNKNOWN, // R16G16_FLOAT + DXGI_FORMAT_UNKNOWN, // R16G16B16A16_FLOAT + DXGI_FORMAT_UNKNOWN, // R32_FLOAT + DXGI_FORMAT_UNKNOWN, // R32G32_FLOAT + DXGI_FORMAT_UNKNOWN, // R32G32B32A32_FLOAT + DXGI_FORMAT_UNKNOWN, // R11G11B10_UFLOAT + DXGI_FORMAT_UNKNOWN, // R8_UINT + DXGI_FORMAT_UNKNOWN, // R8G8_UINT + DXGI_FORMAT_UNKNOWN, // R8G8B8A8_UINT + DXGI_FORMAT_UNKNOWN, // R16_UINT + DXGI_FORMAT_UNKNOWN, // R16G16_UINT + DXGI_FORMAT_UNKNOWN, // R16G16B16A16_UINT + DXGI_FORMAT_UNKNOWN, // R32_UINT + DXGI_FORMAT_UNKNOWN, // R32G32_UINT + DXGI_FORMAT_UNKNOWN, // R32G32B32A32_UINT + DXGI_FORMAT_UNKNOWN, // R8_INT + DXGI_FORMAT_UNKNOWN, // R8G8_INT + DXGI_FORMAT_UNKNOWN, // R8G8B8A8_INT + DXGI_FORMAT_UNKNOWN, // R16_INT + DXGI_FORMAT_UNKNOWN, // R16G16_INT + DXGI_FORMAT_UNKNOWN, // R16G16B16A16_INT + DXGI_FORMAT_UNKNOWN, // R32_INT + DXGI_FORMAT_UNKNOWN, // R32G32_INT + DXGI_FORMAT_UNKNOWN, // R32G32B32A32_INT + DXGI_FORMAT_UNKNOWN, // R8G8B8A8_UNORM_SRGB + DXGI_FORMAT_UNKNOWN, // B8G8R8A8_UNORM_SRGB + DXGI_FORMAT_UNKNOWN, // BC1_UNORM_SRGB + DXGI_FORMAT_UNKNOWN, // BC2_UNORM_SRGB + DXGI_FORMAT_UNKNOWN, // BC3_UNORM_SRGB + DXGI_FORMAT_UNKNOWN, // BC7_UNORM_SRGB + DXGI_FORMAT_R16_TYPELESS, // D16_UNORM + DXGI_FORMAT_R24G8_TYPELESS, // D24_UNORM + DXGI_FORMAT_R32_TYPELESS, // D32_FLOAT + DXGI_FORMAT_R24G8_TYPELESS, // D24_UNORM_S8_UINT + DXGI_FORMAT_R32G8X24_TYPELESS, // D32_FLOAT_S8_UINT + DXGI_FORMAT_UNKNOWN, // ASTC_4x4_UNORM + DXGI_FORMAT_UNKNOWN, // ASTC_5x4_UNORM + DXGI_FORMAT_UNKNOWN, // ASTC_5x5_UNORM + DXGI_FORMAT_UNKNOWN, // ASTC_6x5_UNORM + DXGI_FORMAT_UNKNOWN, // ASTC_6x6_UNORM + DXGI_FORMAT_UNKNOWN, // ASTC_8x5_UNORM + DXGI_FORMAT_UNKNOWN, // ASTC_8x6_UNORM + DXGI_FORMAT_UNKNOWN, // ASTC_8x8_UNORM + DXGI_FORMAT_UNKNOWN, // ASTC_10x5_UNORM + DXGI_FORMAT_UNKNOWN, // ASTC_10x6_UNORM + DXGI_FORMAT_UNKNOWN, // ASTC_10x8_UNORM + DXGI_FORMAT_UNKNOWN, // ASTC_10x10_UNORM + DXGI_FORMAT_UNKNOWN, // ASTC_12x10_UNORM + DXGI_FORMAT_UNKNOWN, // ASTC_12x12_UNORM + DXGI_FORMAT_UNKNOWN, // ASTC_4x4_UNORM_SRGB + DXGI_FORMAT_UNKNOWN, // ASTC_5x4_UNORM_SRGB + DXGI_FORMAT_UNKNOWN, // ASTC_5x5_UNORM_SRGB + DXGI_FORMAT_UNKNOWN, // ASTC_6x5_UNORM_SRGB + DXGI_FORMAT_UNKNOWN, // ASTC_6x6_UNORM_SRGB + DXGI_FORMAT_UNKNOWN, // ASTC_8x5_UNORM_SRGB + DXGI_FORMAT_UNKNOWN, // ASTC_8x6_UNORM_SRGB + DXGI_FORMAT_UNKNOWN, // ASTC_8x8_UNORM_SRGB + DXGI_FORMAT_UNKNOWN, // ASTC_10x5_UNORM_SRGB + DXGI_FORMAT_UNKNOWN, // ASTC_10x6_UNORM_SRGB + DXGI_FORMAT_UNKNOWN, // ASTC_10x8_UNORM_SRGB + DXGI_FORMAT_UNKNOWN, // ASTC_10x10_UNORM_SRGB + DXGI_FORMAT_UNKNOWN, // ASTC_12x10_UNORM_SRGB + DXGI_FORMAT_UNKNOWN, // ASTC_12x12_UNORM_SRGB + DXGI_FORMAT_UNKNOWN, // ASTC_4x4_FLOAT + DXGI_FORMAT_UNKNOWN, // ASTC_5x4_FLOAT + DXGI_FORMAT_UNKNOWN, // ASTC_5x5_FLOAT + DXGI_FORMAT_UNKNOWN, // ASTC_6x5_FLOAT + DXGI_FORMAT_UNKNOWN, // ASTC_6x6_FLOAT + DXGI_FORMAT_UNKNOWN, // ASTC_8x5_FLOAT + DXGI_FORMAT_UNKNOWN, // ASTC_8x6_FLOAT + DXGI_FORMAT_UNKNOWN, // ASTC_8x8_FLOAT + DXGI_FORMAT_UNKNOWN, // ASTC_10x5_FLOAT + DXGI_FORMAT_UNKNOWN, // ASTC_10x6_FLOAT + DXGI_FORMAT_UNKNOWN, // ASTC_10x8_FLOAT + DXGI_FORMAT_UNKNOWN, // ASTC_10x10_FLOAT + DXGI_FORMAT_UNKNOWN, // ASTC_12x10_FLOAT + DXGI_FORMAT_UNKNOWN, // ASTC_12x12_FLOAT +}; +SDL_COMPILE_TIME_ASSERT(SDLToD3D12_TypelessFormat, SDL_arraysize(SDLToD3D12_TypelessFormat) == SDL_GPU_TEXTUREFORMAT_MAX_ENUM_VALUE); + static D3D12_COMPARISON_FUNC SDLToD3D12_CompareOp[] = { D3D12_COMPARISON_FUNC_NEVER, // INVALID D3D12_COMPARISON_FUNC_NEVER, // NEVER @@ -777,6 +874,7 @@ struct D3D12Renderer // FIXME: these might not be necessary since we're not using custom heaps bool UMA; bool UMACacheCoherent; + SDL_PropertiesID props; Uint32 allowedFramesInFlight; // Indirect command signatures @@ -906,26 +1004,38 @@ struct D3D12CommandBuffer Uint32 vertexBufferOffsets[MAX_VERTEX_BUFFERS]; Uint32 vertexBufferCount; - D3D12Texture *vertexSamplerTextures[MAX_TEXTURE_SAMPLERS_PER_STAGE]; - D3D12Sampler *vertexSamplers[MAX_TEXTURE_SAMPLERS_PER_STAGE]; - D3D12Texture *vertexStorageTextures[MAX_STORAGE_TEXTURES_PER_STAGE]; - D3D12Buffer *vertexStorageBuffers[MAX_STORAGE_BUFFERS_PER_STAGE]; + D3D12_CPU_DESCRIPTOR_HANDLE vertexSamplerTextureDescriptorHandles[MAX_TEXTURE_SAMPLERS_PER_STAGE]; + D3D12_CPU_DESCRIPTOR_HANDLE vertexSamplerDescriptorHandles[MAX_TEXTURE_SAMPLERS_PER_STAGE]; + D3D12_CPU_DESCRIPTOR_HANDLE vertexStorageTextureDescriptorHandles[MAX_STORAGE_TEXTURES_PER_STAGE]; + D3D12_CPU_DESCRIPTOR_HANDLE vertexStorageBufferDescriptorHandles[MAX_STORAGE_BUFFERS_PER_STAGE]; + D3D12UniformBuffer *vertexUniformBuffers[MAX_UNIFORM_BUFFERS_PER_STAGE]; - D3D12Texture *fragmentSamplerTextures[MAX_TEXTURE_SAMPLERS_PER_STAGE]; - D3D12Sampler *fragmentSamplers[MAX_TEXTURE_SAMPLERS_PER_STAGE]; - D3D12Texture *fragmentStorageTextures[MAX_STORAGE_TEXTURES_PER_STAGE]; - D3D12Buffer *fragmentStorageBuffers[MAX_STORAGE_BUFFERS_PER_STAGE]; + D3D12_CPU_DESCRIPTOR_HANDLE fragmentSamplerTextureDescriptorHandles[MAX_TEXTURE_SAMPLERS_PER_STAGE]; + D3D12_CPU_DESCRIPTOR_HANDLE fragmentSamplerDescriptorHandles[MAX_TEXTURE_SAMPLERS_PER_STAGE]; + D3D12_CPU_DESCRIPTOR_HANDLE fragmentStorageTextureDescriptorHandles[MAX_STORAGE_TEXTURES_PER_STAGE]; + D3D12_CPU_DESCRIPTOR_HANDLE fragmentStorageBufferDescriptorHandles[MAX_STORAGE_BUFFERS_PER_STAGE]; + D3D12UniformBuffer *fragmentUniformBuffers[MAX_UNIFORM_BUFFERS_PER_STAGE]; - D3D12Texture *computeSamplerTextures[MAX_TEXTURE_SAMPLERS_PER_STAGE]; - D3D12Sampler *computeSamplers[MAX_TEXTURE_SAMPLERS_PER_STAGE]; + D3D12_CPU_DESCRIPTOR_HANDLE computeSamplerTextureDescriptorHandles[MAX_TEXTURE_SAMPLERS_PER_STAGE]; + D3D12_CPU_DESCRIPTOR_HANDLE computeSamplerDescriptorHandles[MAX_TEXTURE_SAMPLERS_PER_STAGE]; + D3D12_CPU_DESCRIPTOR_HANDLE computeReadOnlyStorageTextureDescriptorHandles[MAX_STORAGE_TEXTURES_PER_STAGE]; + D3D12_CPU_DESCRIPTOR_HANDLE computeReadOnlyStorageBufferDescriptorHandles[MAX_STORAGE_BUFFERS_PER_STAGE]; + + // Track these separately because barriers can happen mid compute pass D3D12Texture *computeReadOnlyStorageTextures[MAX_STORAGE_TEXTURES_PER_STAGE]; D3D12Buffer *computeReadOnlyStorageBuffers[MAX_STORAGE_BUFFERS_PER_STAGE]; + + D3D12_CPU_DESCRIPTOR_HANDLE computeReadWriteStorageTextureDescriptorHandles[MAX_COMPUTE_WRITE_TEXTURES]; + D3D12_CPU_DESCRIPTOR_HANDLE computeReadWriteStorageBufferDescriptorHandles[MAX_COMPUTE_WRITE_BUFFERS]; + + // Track these separately because they are bound when the compute pass begins D3D12TextureSubresource *computeReadWriteStorageTextureSubresources[MAX_COMPUTE_WRITE_TEXTURES]; Uint32 computeReadWriteStorageTextureSubresourceCount; D3D12Buffer *computeReadWriteStorageBuffers[MAX_COMPUTE_WRITE_BUFFERS]; Uint32 computeReadWriteStorageBufferCount; + D3D12UniformBuffer *computeUniformBuffers[MAX_UNIFORM_BUFFERS_PER_STAGE]; // Resource tracking @@ -989,22 +1099,14 @@ typedef struct D3D12GraphicsRootSignature struct D3D12GraphicsPipeline { + GraphicsPipelineCommonHeader header; + ID3D12PipelineState *pipelineState; D3D12GraphicsRootSignature *rootSignature; SDL_GPUPrimitiveType primitiveType; Uint32 vertexStrides[MAX_VERTEX_BUFFERS]; - Uint32 vertexSamplerCount; - Uint32 vertexUniformBufferCount; - Uint32 vertexStorageBufferCount; - Uint32 vertexStorageTextureCount; - - Uint32 fragmentSamplerCount; - Uint32 fragmentUniformBufferCount; - Uint32 fragmentStorageBufferCount; - Uint32 fragmentStorageTextureCount; - SDL_AtomicInt referenceCount; }; @@ -1023,16 +1125,11 @@ typedef struct D3D12ComputeRootSignature struct D3D12ComputePipeline { + ComputePipelineCommonHeader header; + ID3D12PipelineState *pipelineState; D3D12ComputeRootSignature *rootSignature; - Uint32 numSamplers; - Uint32 numReadOnlyStorageTextures; - Uint32 numReadOnlyStorageBuffers; - Uint32 numReadWriteStorageTextures; - Uint32 numReadWriteStorageBuffers; - Uint32 numUniformBuffers; - SDL_AtomicInt referenceCount; }; @@ -1113,7 +1210,7 @@ static ID3D12CommandQueue *s_CommandQueue; #if defined(SDL_PLATFORM_XBOXONE) // These are not defined in d3d12_x.h. -typedef HRESULT (D3DAPI* PFN_D3D12_XBOX_CREATE_DEVICE)(_In_opt_ IGraphicsUnknown*, _In_ const D3D12XBOX_CREATE_DEVICE_PARAMETERS*, _In_ REFIID, _Outptr_opt_ void**); +typedef HRESULT (D3DAPI* PFN_D3D12_XBOX_CREATE_DEVICE)(_In_opt_ IGraphicsUnknown *, _In_ const D3D12XBOX_CREATE_DEVICE_PARAMETERS*, _In_ REFIID, _Outptr_opt_ void **); #define D3D12_STANDARD_MULTISAMPLE_PATTERN DXGI_STANDARD_MULTISAMPLE_QUALITY_PATTERN #endif @@ -1179,7 +1276,6 @@ static void D3D12_INTERNAL_SetError( // Release / Cleanup static void D3D12_INTERNAL_ReleaseStagingDescriptorHandle( - D3D12Renderer *renderer, D3D12StagingDescriptor *cpuDescriptor) { D3D12StagingDescriptorPool *pool = cpuDescriptor->pool; @@ -1193,7 +1289,6 @@ static void D3D12_INTERNAL_ReleaseStagingDescriptorHandle( } static void D3D12_INTERNAL_DestroyBuffer( - D3D12Renderer *renderer, D3D12Buffer *buffer) { if (!buffer) { @@ -1207,13 +1302,10 @@ static void D3D12_INTERNAL_DestroyBuffer( NULL); } D3D12_INTERNAL_ReleaseStagingDescriptorHandle( - renderer, &buffer->srvDescriptor); D3D12_INTERNAL_ReleaseStagingDescriptorHandle( - renderer, &buffer->uavDescriptor); D3D12_INTERNAL_ReleaseStagingDescriptorHandle( - renderer, &buffer->cbvDescriptor); if (buffer->handle) { @@ -1264,7 +1356,6 @@ static void D3D12_INTERNAL_ReleaseBufferContainer( } static void D3D12_INTERNAL_DestroyTexture( - D3D12Renderer *renderer, D3D12Texture *texture) { if (!texture) { @@ -1275,24 +1366,20 @@ static void D3D12_INTERNAL_DestroyTexture( if (subresource->rtvHandles) { for (Uint32 depthIndex = 0; depthIndex < subresource->depth; depthIndex += 1) { D3D12_INTERNAL_ReleaseStagingDescriptorHandle( - renderer, &subresource->rtvHandles[depthIndex]); } SDL_free(subresource->rtvHandles); } D3D12_INTERNAL_ReleaseStagingDescriptorHandle( - renderer, &subresource->uavHandle); D3D12_INTERNAL_ReleaseStagingDescriptorHandle( - renderer, &subresource->dsvHandle); } SDL_free(texture->subresources); D3D12_INTERNAL_ReleaseStagingDescriptorHandle( - renderer, &texture->srvHandle); if (texture->resource) { @@ -1344,11 +1431,9 @@ static void D3D12_INTERNAL_ReleaseTextureContainer( } static void D3D12_INTERNAL_DestroySampler( - D3D12Renderer *renderer, D3D12Sampler *sampler) { D3D12_INTERNAL_ReleaseStagingDescriptorHandle( - renderer, &sampler->handle); SDL_free(sampler); @@ -1503,7 +1588,6 @@ static void D3D12_INTERNAL_DestroyRenderer(D3D12Renderer *renderer) // Release uniform buffers for (Uint32 i = 0; i < renderer->uniformBufferPoolCount; i += 1) { D3D12_INTERNAL_DestroyBuffer( - renderer, renderer->uniformBufferPool[i]->buffer); SDL_free(renderer->uniformBufferPool[i]); } @@ -1560,6 +1644,8 @@ static void D3D12_INTERNAL_DestroyRenderer(D3D12Renderer *renderer) SDL_free(renderer->graphicsPipelinesToDestroy); SDL_free(renderer->computePipelinesToDestroy); + SDL_DestroyProperties(renderer->props); + // Tear down D3D12 objects if (renderer->indirectDrawCommandSignature) { ID3D12CommandSignature_Release(renderer->indirectDrawCommandSignature); @@ -1647,6 +1733,12 @@ static void D3D12_DestroyDevice(SDL_GPUDevice *device) SDL_free(device); } +static SDL_PropertiesID D3D12_GetDeviceProperties(SDL_GPUDevice *device) +{ + D3D12Renderer *renderer = (D3D12Renderer *)device->driverData; + return renderer->props; +} + // Barriers static inline Uint32 D3D12_INTERNAL_CalcSubresource( @@ -2120,15 +2212,15 @@ static D3D12StagingDescriptorPool *D3D12_INTERNAL_CreateStagingDescriptorPool( return NULL; } - D3D12StagingDescriptorPool *pool = (D3D12StagingDescriptorPool*) SDL_calloc(1, sizeof(D3D12StagingDescriptorPool)); + D3D12StagingDescriptorPool *pool = (D3D12StagingDescriptorPool *)SDL_calloc(1, sizeof(D3D12StagingDescriptorPool)); pool->heapCount = 1; - pool->heaps = (D3D12DescriptorHeap**) SDL_malloc(sizeof(D3D12DescriptorHeap*)); + pool->heaps = (D3D12DescriptorHeap **)SDL_malloc(sizeof(D3D12DescriptorHeap *)); pool->heaps[0] = heap; pool->freeDescriptorCapacity = STAGING_HEAP_DESCRIPTOR_COUNT; pool->freeDescriptorCount = STAGING_HEAP_DESCRIPTOR_COUNT; - pool->freeDescriptors = (D3D12StagingDescriptor*) SDL_malloc(STAGING_HEAP_DESCRIPTOR_COUNT * sizeof(D3D12StagingDescriptor)); + pool->freeDescriptors = (D3D12StagingDescriptor *)SDL_malloc(STAGING_HEAP_DESCRIPTOR_COUNT * sizeof(D3D12StagingDescriptor)); for (Uint32 i = 0; i < STAGING_HEAP_DESCRIPTOR_COUNT; i += 1) { pool->freeDescriptors[i].pool = pool; @@ -2158,12 +2250,12 @@ static bool D3D12_INTERNAL_ExpandStagingDescriptorPool( } pool->heapCount += 1; - pool->heaps = (D3D12DescriptorHeap**) SDL_realloc(pool->heaps, pool->heapCount * sizeof(D3D12DescriptorHeap*)); + pool->heaps = (D3D12DescriptorHeap **)SDL_realloc(pool->heaps, pool->heapCount * sizeof(D3D12DescriptorHeap *)); pool->heaps[pool->heapCount - 1] = heap; pool->freeDescriptorCapacity += STAGING_HEAP_DESCRIPTOR_COUNT; pool->freeDescriptorCount += STAGING_HEAP_DESCRIPTOR_COUNT; - pool->freeDescriptors = (D3D12StagingDescriptor*) SDL_realloc(pool->freeDescriptors, pool->freeDescriptorCapacity * sizeof(D3D12StagingDescriptor)); + pool->freeDescriptors = (D3D12StagingDescriptor *)SDL_realloc(pool->freeDescriptors, pool->freeDescriptorCapacity * sizeof(D3D12StagingDescriptor)); for (Uint32 i = 0; i < STAGING_HEAP_DESCRIPTOR_COUNT; i += 1) { pool->freeDescriptors[i].pool = pool; @@ -2498,16 +2590,32 @@ static D3D12GraphicsRootSignature *D3D12_INTERNAL_CreateGraphicsRootSignature( return d3d12GraphicsRootSignature; } +static bool D3D12_INTERNAL_IsValidShaderBytecode( + const Uint8 *code, + size_t codeSize) +{ + // Both DXIL and DXBC bytecode have a 4 byte header containing `DXBC`. + if (codeSize < 4 || code == NULL) { + return false; + } + return SDL_memcmp(code, "DXBC", 4) == 0; +} + static bool D3D12_INTERNAL_CreateShaderBytecode( D3D12Renderer *renderer, - Uint32 stage, - SDL_GPUShaderFormat format, const Uint8 *code, size_t codeSize, - const char *entrypointName, + SDL_GPUShaderFormat format, void **pBytecode, size_t *pBytecodeSize) { + if (!D3D12_INTERNAL_IsValidShaderBytecode(code, codeSize)) { + if (format == SDL_GPU_SHADERFORMAT_DXBC) { + SET_STRING_ERROR_AND_RETURN("The provided shader code is not valid DXBC!", false); + } + SET_STRING_ERROR_AND_RETURN("The provided shader code is not valid DXIL!", false); + } + if (pBytecode != NULL) { *pBytecode = SDL_malloc(codeSize); if (!*pBytecode) { @@ -2726,11 +2834,9 @@ static SDL_GPUComputePipeline *D3D12_CreateComputePipeline( if (!D3D12_INTERNAL_CreateShaderBytecode( renderer, - SDL_GPU_SHADERSTAGE_COMPUTE, - createinfo->format, createinfo->code, createinfo->code_size, - createinfo->entrypoint, + createinfo->format, &bytecode, &bytecodeSize)) { return NULL; @@ -2777,12 +2883,12 @@ static SDL_GPUComputePipeline *D3D12_CreateComputePipeline( computePipeline->pipelineState = pipelineState; computePipeline->rootSignature = rootSignature; - computePipeline->numSamplers = createinfo->num_samplers; - computePipeline->numReadOnlyStorageTextures = createinfo->num_readonly_storage_textures; - computePipeline->numReadOnlyStorageBuffers = createinfo->num_readonly_storage_buffers; - computePipeline->numReadWriteStorageTextures = createinfo->num_readwrite_storage_textures; - computePipeline->numReadWriteStorageBuffers = createinfo->num_readwrite_storage_buffers; - computePipeline->numUniformBuffers = createinfo->num_uniform_buffers; + computePipeline->header.numSamplers = createinfo->num_samplers; + computePipeline->header.numReadonlyStorageTextures = createinfo->num_readonly_storage_textures; + computePipeline->header.numReadonlyStorageBuffers = createinfo->num_readonly_storage_buffers; + computePipeline->header.numReadWriteStorageTextures = createinfo->num_readwrite_storage_textures; + computePipeline->header.numReadWriteStorageBuffers = createinfo->num_readwrite_storage_buffers; + computePipeline->header.numUniformBuffers = createinfo->num_uniform_buffers; SDL_SetAtomicInt(&computePipeline->referenceCount, 0); if (renderer->debug_mode && SDL_HasProperty(createinfo->props, SDL_PROP_GPU_COMPUTEPIPELINE_CREATE_NAME_STRING)) { @@ -2843,7 +2949,7 @@ static bool D3D12_INTERNAL_ConvertBlendState( } SDL_zerop(blendDesc); - blendDesc->AlphaToCoverageEnable = FALSE; + blendDesc->AlphaToCoverageEnable = pipelineInfo->multisample_state.enable_alpha_to_coverage; blendDesc->IndependentBlendEnable = FALSE; for (UINT i = 0; i < MAX_COLOR_TARGET_BINDINGS; i += 1) { @@ -3063,15 +3169,15 @@ static SDL_GPUGraphicsPipeline *D3D12_CreateGraphicsPipeline( pipeline->primitiveType = createinfo->primitive_type; - pipeline->vertexSamplerCount = vertShader->num_samplers; - pipeline->vertexStorageTextureCount = vertShader->numStorageTextures; - pipeline->vertexStorageBufferCount = vertShader->numStorageBuffers; - pipeline->vertexUniformBufferCount = vertShader->numUniformBuffers; + pipeline->header.num_vertex_samplers = vertShader->num_samplers; + pipeline->header.num_vertex_storage_textures = vertShader->numStorageTextures; + pipeline->header.num_vertex_storage_buffers = vertShader->numStorageBuffers; + pipeline->header.num_vertex_uniform_buffers = vertShader->numUniformBuffers; - pipeline->fragmentSamplerCount = fragShader->num_samplers; - pipeline->fragmentStorageTextureCount = fragShader->numStorageTextures; - pipeline->fragmentStorageBufferCount = fragShader->numStorageBuffers; - pipeline->fragmentUniformBufferCount = fragShader->numUniformBuffers; + pipeline->header.num_fragment_samplers = fragShader->num_samplers; + pipeline->header.num_fragment_storage_textures = fragShader->numStorageTextures; + pipeline->header.num_fragment_storage_buffers = fragShader->numStorageBuffers; + pipeline->header.num_fragment_uniform_buffers = fragShader->numUniformBuffers; SDL_SetAtomicInt(&pipeline->referenceCount, 0); @@ -3144,11 +3250,9 @@ static SDL_GPUShader *D3D12_CreateShader( if (!D3D12_INTERNAL_CreateShaderBytecode( renderer, - createinfo->stage, - createinfo->format, createinfo->code, createinfo->code_size, - createinfo->entrypoint, + createinfo->format, &bytecode, &bytecodeSize)) { return NULL; @@ -3188,6 +3292,10 @@ static D3D12Texture *D3D12_INTERNAL_CreateTexture( D3D12_CLEAR_VALUE clearValue; DXGI_FORMAT format; bool useClearValue = false; + bool needsSRV = + (createinfo->usage & SDL_GPU_TEXTUREUSAGE_SAMPLER) || + (createinfo->usage & SDL_GPU_TEXTUREUSAGE_GRAPHICS_STORAGE_READ) || + (createinfo->usage & SDL_GPU_TEXTUREUSAGE_COMPUTE_STORAGE_READ); bool needsUAV = (createinfo->usage & SDL_GPU_TEXTUREUSAGE_COMPUTE_STORAGE_WRITE) || (createinfo->usage & SDL_GPU_TEXTUREUSAGE_COMPUTE_STORAGE_SIMULTANEOUS_READ_WRITE); @@ -3207,6 +3315,7 @@ static D3D12Texture *D3D12_INTERNAL_CreateTexture( if (createinfo->usage & SDL_GPU_TEXTUREUSAGE_COLOR_TARGET) { resourceFlags |= D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET; useClearValue = true; + clearValue.Format = format; clearValue.Color[0] = SDL_GetFloatProperty(createinfo->props, SDL_PROP_GPU_TEXTURE_CREATE_D3D12_CLEAR_R_FLOAT, 0); clearValue.Color[1] = SDL_GetFloatProperty(createinfo->props, SDL_PROP_GPU_TEXTURE_CREATE_D3D12_CLEAR_G_FLOAT, 0); clearValue.Color[2] = SDL_GetFloatProperty(createinfo->props, SDL_PROP_GPU_TEXTURE_CREATE_D3D12_CLEAR_B_FLOAT, 0); @@ -3216,9 +3325,10 @@ static D3D12Texture *D3D12_INTERNAL_CreateTexture( if (createinfo->usage & SDL_GPU_TEXTUREUSAGE_DEPTH_STENCIL_TARGET) { resourceFlags |= D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL; useClearValue = true; + clearValue.Format = SDLToD3D12_DepthFormat[createinfo->format]; clearValue.DepthStencil.Depth = SDL_GetFloatProperty(createinfo->props, SDL_PROP_GPU_TEXTURE_CREATE_D3D12_CLEAR_DEPTH_FLOAT, 0); - clearValue.DepthStencil.Stencil = (UINT8)SDL_GetNumberProperty(createinfo->props, SDL_PROP_GPU_TEXTURE_CREATE_D3D12_CLEAR_STENCIL_UINT8, 0); - format = SDLToD3D12_DepthFormat[createinfo->format]; + clearValue.DepthStencil.Stencil = (UINT8)SDL_GetNumberProperty(createinfo->props, SDL_PROP_GPU_TEXTURE_CREATE_D3D12_CLEAR_STENCIL_NUMBER, 0); + format = needsSRV ? SDLToD3D12_TypelessFormat[createinfo->format] : SDLToD3D12_DepthFormat[createinfo->format]; } if (needsUAV) { @@ -3235,7 +3345,7 @@ static D3D12Texture *D3D12_INTERNAL_CreateTexture( if (createinfo->type != SDL_GPU_TEXTURETYPE_3D) { desc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D; - desc.Alignment = isSwapchainTexture ? 0 : D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT; + desc.Alignment = isSwapchainTexture ? 0 : isMultisample ? D3D12_DEFAULT_MSAA_RESOURCE_PLACEMENT_ALIGNMENT : D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT; desc.Width = createinfo->width; desc.Height = createinfo->height; desc.DepthOrArraySize = (UINT16)createinfo->layer_count_or_depth; @@ -3260,7 +3370,6 @@ static D3D12Texture *D3D12_INTERNAL_CreateTexture( } initialState = isSwapchainTexture ? D3D12_RESOURCE_STATE_PRESENT : D3D12_INTERNAL_DefaultTextureResourceState(createinfo->usage); - clearValue.Format = desc.Format; res = ID3D12Device_CreateCommittedResource( renderer->device, @@ -3273,16 +3382,14 @@ static D3D12Texture *D3D12_INTERNAL_CreateTexture( (void **)&handle); if (FAILED(res)) { D3D12_INTERNAL_SetError(renderer, "Failed to create texture!", res); - D3D12_INTERNAL_DestroyTexture(renderer, texture); + D3D12_INTERNAL_DestroyTexture(texture); return NULL; } texture->resource = handle; // Create the SRV if applicable - if ((createinfo->usage & SDL_GPU_TEXTUREUSAGE_SAMPLER) || - (createinfo->usage & SDL_GPU_TEXTUREUSAGE_GRAPHICS_STORAGE_READ) || - (createinfo->usage & SDL_GPU_TEXTUREUSAGE_COMPUTE_STORAGE_READ)) { + if (needsSRV) { D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc; D3D12_INTERNAL_AssignStagingDescriptorHandle( @@ -3339,7 +3446,7 @@ static D3D12Texture *D3D12_INTERNAL_CreateTexture( texture->subresources = (D3D12TextureSubresource *)SDL_calloc( texture->subresourceCount, sizeof(D3D12TextureSubresource)); if (!texture->subresources) { - D3D12_INTERNAL_DestroyTexture(renderer, texture); + D3D12_INTERNAL_DestroyTexture(texture); return NULL; } for (Uint32 layerIndex = 0; layerIndex < layerCount; layerIndex += 1) { @@ -3483,7 +3590,9 @@ static SDL_GPUTexture *D3D12_CreateTexture( // Copy properties so we don't lose information when the client destroys them container->header.info = *createinfo; container->header.info.props = SDL_CreateProperties(); - SDL_CopyProperties(createinfo->props, container->header.info.props); + if (createinfo->props) { + SDL_CopyProperties(createinfo->props, container->header.info.props); + } container->textureCapacity = 1; container->textureCount = 1; @@ -3617,7 +3726,7 @@ static D3D12Buffer *D3D12_INTERNAL_CreateBuffer( (void **)&handle); if (FAILED(res)) { D3D12_INTERNAL_SetError(renderer, "Could not create buffer!", res); - D3D12_INTERNAL_DestroyBuffer(renderer, buffer); + D3D12_INTERNAL_DestroyBuffer(buffer); return NULL; } @@ -3707,7 +3816,7 @@ static D3D12Buffer *D3D12_INTERNAL_CreateBuffer( (void **)&buffer->mapPointer); if (FAILED(res)) { D3D12_INTERNAL_SetError(renderer, "Failed to map upload buffer!", res); - D3D12_INTERNAL_DestroyBuffer(renderer, buffer); + D3D12_INTERNAL_DestroyBuffer(buffer); return NULL; } } @@ -4523,14 +4632,14 @@ static void D3D12_BindGraphicsPipeline( d3d12CommandBuffer->needFragmentUniformBufferBind[i] = true; } - for (i = 0; i < pipeline->vertexUniformBufferCount; i += 1) { + for (i = 0; i < pipeline->header.num_vertex_uniform_buffers; i += 1) { if (d3d12CommandBuffer->vertexUniformBuffers[i] == NULL) { d3d12CommandBuffer->vertexUniformBuffers[i] = D3D12_INTERNAL_AcquireUniformBufferFromPool( d3d12CommandBuffer); } } - for (i = 0; i < pipeline->fragmentUniformBufferCount; i += 1) { + for (i = 0; i < pipeline->header.num_fragment_uniform_buffers; i += 1) { if (d3d12CommandBuffer->fragmentUniformBuffers[i] == NULL) { d3d12CommandBuffer->fragmentUniformBuffers[i] = D3D12_INTERNAL_AcquireUniformBufferFromPool( d3d12CommandBuffer); @@ -4597,21 +4706,21 @@ static void D3D12_BindVertexSamplers( D3D12TextureContainer *container = (D3D12TextureContainer *)textureSamplerBindings[i].texture; D3D12Sampler *sampler = (D3D12Sampler *)textureSamplerBindings[i].sampler; - if (d3d12CommandBuffer->vertexSamplers[firstSlot + i] != sampler) { + if (d3d12CommandBuffer->vertexSamplerDescriptorHandles[firstSlot + i].ptr != sampler->handle.cpuHandle.ptr) { D3D12_INTERNAL_TrackSampler( d3d12CommandBuffer, sampler); - d3d12CommandBuffer->vertexSamplers[firstSlot + i] = sampler; + d3d12CommandBuffer->vertexSamplerDescriptorHandles[firstSlot + i] = sampler->handle.cpuHandle; d3d12CommandBuffer->needVertexSamplerBind = true; } - if (d3d12CommandBuffer->vertexSamplerTextures[firstSlot + i] != container->activeTexture) { + if (d3d12CommandBuffer->vertexSamplerTextureDescriptorHandles[firstSlot + i].ptr != container->activeTexture->srvHandle.cpuHandle.ptr) { D3D12_INTERNAL_TrackTexture( d3d12CommandBuffer, container->activeTexture); - d3d12CommandBuffer->vertexSamplerTextures[firstSlot + i] = container->activeTexture; + d3d12CommandBuffer->vertexSamplerTextureDescriptorHandles[firstSlot + i] = container->activeTexture->srvHandle.cpuHandle; d3d12CommandBuffer->needVertexSamplerBind = true; } } @@ -4629,10 +4738,10 @@ static void D3D12_BindVertexStorageTextures( D3D12TextureContainer *container = (D3D12TextureContainer *)storageTextures[i]; D3D12Texture *texture = container->activeTexture; - if (d3d12CommandBuffer->vertexStorageTextures[firstSlot + i] != texture) { + if (d3d12CommandBuffer->vertexStorageTextureDescriptorHandles[firstSlot + i].ptr != texture->srvHandle.cpuHandle.ptr) { D3D12_INTERNAL_TrackTexture(d3d12CommandBuffer, texture); - d3d12CommandBuffer->vertexStorageTextures[firstSlot + i] = texture; + d3d12CommandBuffer->vertexStorageTextureDescriptorHandles[firstSlot + i] = texture->srvHandle.cpuHandle; d3d12CommandBuffer->needVertexStorageTextureBind = true; } } @@ -4648,12 +4757,12 @@ static void D3D12_BindVertexStorageBuffers( for (Uint32 i = 0; i < numBindings; i += 1) { D3D12BufferContainer *container = (D3D12BufferContainer *)storageBuffers[i]; - if (d3d12CommandBuffer->vertexStorageBuffers[firstSlot + i] != container->activeBuffer) { + if (d3d12CommandBuffer->vertexStorageBufferDescriptorHandles[firstSlot + i].ptr != container->activeBuffer->srvDescriptor.cpuHandle.ptr) { D3D12_INTERNAL_TrackBuffer( d3d12CommandBuffer, container->activeBuffer); - d3d12CommandBuffer->vertexStorageBuffers[firstSlot + i] = container->activeBuffer; + d3d12CommandBuffer->vertexStorageBufferDescriptorHandles[firstSlot + i] = container->activeBuffer->srvDescriptor.cpuHandle; d3d12CommandBuffer->needVertexStorageBufferBind = true; } } @@ -4671,21 +4780,21 @@ static void D3D12_BindFragmentSamplers( D3D12TextureContainer *container = (D3D12TextureContainer *)textureSamplerBindings[i].texture; D3D12Sampler *sampler = (D3D12Sampler *)textureSamplerBindings[i].sampler; - if (d3d12CommandBuffer->fragmentSamplers[firstSlot + i] != sampler) { + if (d3d12CommandBuffer->fragmentSamplerDescriptorHandles[firstSlot + i].ptr != sampler->handle.cpuHandle.ptr) { D3D12_INTERNAL_TrackSampler( d3d12CommandBuffer, sampler); - d3d12CommandBuffer->fragmentSamplers[firstSlot + i] = sampler; + d3d12CommandBuffer->fragmentSamplerDescriptorHandles[firstSlot + i] = sampler->handle.cpuHandle; d3d12CommandBuffer->needFragmentSamplerBind = true; } - if (d3d12CommandBuffer->fragmentSamplerTextures[firstSlot + i] != container->activeTexture) { + if (d3d12CommandBuffer->fragmentSamplerTextureDescriptorHandles[firstSlot + i].ptr != container->activeTexture->srvHandle.cpuHandle.ptr) { D3D12_INTERNAL_TrackTexture( d3d12CommandBuffer, container->activeTexture); - d3d12CommandBuffer->fragmentSamplerTextures[firstSlot + i] = container->activeTexture; + d3d12CommandBuffer->fragmentSamplerTextureDescriptorHandles[firstSlot + i] = container->activeTexture->srvHandle.cpuHandle; d3d12CommandBuffer->needFragmentSamplerBind = true; } } @@ -4703,10 +4812,10 @@ static void D3D12_BindFragmentStorageTextures( D3D12TextureContainer *container = (D3D12TextureContainer *)storageTextures[i]; D3D12Texture *texture = container->activeTexture; - if (d3d12CommandBuffer->fragmentStorageTextures[firstSlot + i] != texture) { + if (d3d12CommandBuffer->fragmentStorageTextureDescriptorHandles[firstSlot + i].ptr != texture->srvHandle.cpuHandle.ptr) { D3D12_INTERNAL_TrackTexture(d3d12CommandBuffer, texture); - d3d12CommandBuffer->fragmentStorageTextures[firstSlot + i] = texture; + d3d12CommandBuffer->fragmentStorageTextureDescriptorHandles[firstSlot + i] = texture->srvHandle.cpuHandle; d3d12CommandBuffer->needFragmentStorageTextureBind = true; } } @@ -4723,12 +4832,12 @@ static void D3D12_BindFragmentStorageBuffers( for (Uint32 i = 0; i < numBindings; i += 1) { D3D12BufferContainer *container = (D3D12BufferContainer *)storageBuffers[i]; - if (d3d12CommandBuffer->fragmentStorageBuffers[firstSlot + i] != container->activeBuffer) { + if (d3d12CommandBuffer->fragmentStorageBufferDescriptorHandles[firstSlot + i].ptr != container->activeBuffer->srvDescriptor.cpuHandle.ptr) { D3D12_INTERNAL_TrackBuffer( d3d12CommandBuffer, container->activeBuffer); - d3d12CommandBuffer->fragmentStorageBuffers[firstSlot + i] = container->activeBuffer; + d3d12CommandBuffer->fragmentStorageBufferDescriptorHandles[firstSlot + i] = container->activeBuffer->srvDescriptor.cpuHandle; d3d12CommandBuffer->needFragmentStorageBufferBind = true; } } @@ -4809,15 +4918,19 @@ static void D3D12_INTERNAL_WriteGPUDescriptors( gpuBaseDescriptor->ptr = heap->descriptorHeapGPUStart.ptr + (heap->currentDescriptorIndex * heap->descriptorSize); for (Uint32 i = 0; i < resourceHandleCount; i += 1) { - ID3D12Device_CopyDescriptorsSimple( - commandBuffer->renderer->device, - 1, - gpuHeapCpuHandle, - resourceDescriptorHandles[i], - heapType); + // This will crash the driver if it gets a null handle! Cool! + if (resourceDescriptorHandles[i].ptr != 0) + { + ID3D12Device_CopyDescriptorsSimple( + commandBuffer->renderer->device, + 1, + gpuHeapCpuHandle, + resourceDescriptorHandles[i], + heapType); - heap->currentDescriptorIndex += 1; - gpuHeapCpuHandle.ptr += heap->descriptorSize; + heap->currentDescriptorIndex += 1; + gpuHeapCpuHandle.ptr += heap->descriptorSize; + } } } @@ -4847,19 +4960,21 @@ static void D3D12_INTERNAL_BindGraphicsResources( 0, commandBuffer->vertexBufferCount, vertexBufferViews); + + commandBuffer->needVertexBufferBind = false; } if (commandBuffer->needVertexSamplerBind) { - if (graphicsPipeline->vertexSamplerCount > 0) { - for (Uint32 i = 0; i < graphicsPipeline->vertexSamplerCount; i += 1) { - cpuHandles[i] = commandBuffer->vertexSamplers[i]->handle.cpuHandle; + if (graphicsPipeline->header.num_vertex_samplers > 0) { + for (Uint32 i = 0; i < graphicsPipeline->header.num_vertex_samplers; i += 1) { + cpuHandles[i] = commandBuffer->vertexSamplerDescriptorHandles[i]; } D3D12_INTERNAL_WriteGPUDescriptors( commandBuffer, D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER, cpuHandles, - graphicsPipeline->vertexSamplerCount, + graphicsPipeline->header.num_vertex_samplers, &gpuDescriptorHandle); ID3D12GraphicsCommandList_SetGraphicsRootDescriptorTable( @@ -4867,15 +4982,15 @@ static void D3D12_INTERNAL_BindGraphicsResources( graphicsPipeline->rootSignature->vertexSamplerRootIndex, gpuDescriptorHandle); - for (Uint32 i = 0; i < graphicsPipeline->vertexSamplerCount; i += 1) { - cpuHandles[i] = commandBuffer->vertexSamplerTextures[i]->srvHandle.cpuHandle; + for (Uint32 i = 0; i < graphicsPipeline->header.num_vertex_samplers; i += 1) { + cpuHandles[i] = commandBuffer->vertexSamplerTextureDescriptorHandles[i]; } D3D12_INTERNAL_WriteGPUDescriptors( commandBuffer, D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, cpuHandles, - graphicsPipeline->vertexSamplerCount, + graphicsPipeline->header.num_vertex_samplers, &gpuDescriptorHandle); ID3D12GraphicsCommandList_SetGraphicsRootDescriptorTable( @@ -4887,16 +5002,16 @@ static void D3D12_INTERNAL_BindGraphicsResources( } if (commandBuffer->needVertexStorageTextureBind) { - if (graphicsPipeline->vertexStorageTextureCount > 0) { - for (Uint32 i = 0; i < graphicsPipeline->vertexStorageTextureCount; i += 1) { - cpuHandles[i] = commandBuffer->vertexStorageTextures[i]->srvHandle.cpuHandle; + if (graphicsPipeline->header.num_vertex_storage_textures > 0) { + for (Uint32 i = 0; i < graphicsPipeline->header.num_vertex_storage_textures; i += 1) { + cpuHandles[i] = commandBuffer->vertexStorageTextureDescriptorHandles[i]; } D3D12_INTERNAL_WriteGPUDescriptors( commandBuffer, D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, cpuHandles, - graphicsPipeline->vertexStorageTextureCount, + graphicsPipeline->header.num_vertex_storage_textures, &gpuDescriptorHandle); ID3D12GraphicsCommandList_SetGraphicsRootDescriptorTable( @@ -4908,16 +5023,16 @@ static void D3D12_INTERNAL_BindGraphicsResources( } if (commandBuffer->needVertexStorageBufferBind) { - if (graphicsPipeline->vertexStorageBufferCount > 0) { - for (Uint32 i = 0; i < graphicsPipeline->vertexStorageBufferCount; i += 1) { - cpuHandles[i] = commandBuffer->vertexStorageBuffers[i]->srvDescriptor.cpuHandle; + if (graphicsPipeline->header.num_vertex_storage_buffers > 0) { + for (Uint32 i = 0; i < graphicsPipeline->header.num_vertex_storage_buffers; i += 1) { + cpuHandles[i] = commandBuffer->vertexStorageBufferDescriptorHandles[i]; } D3D12_INTERNAL_WriteGPUDescriptors( commandBuffer, D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, cpuHandles, - graphicsPipeline->vertexStorageBufferCount, + graphicsPipeline->header.num_vertex_storage_buffers, &gpuDescriptorHandle); ID3D12GraphicsCommandList_SetGraphicsRootDescriptorTable( @@ -4930,7 +5045,7 @@ static void D3D12_INTERNAL_BindGraphicsResources( for (Uint32 i = 0; i < MAX_UNIFORM_BUFFERS_PER_STAGE; i += 1) { if (commandBuffer->needVertexUniformBufferBind[i]) { - if (graphicsPipeline->vertexUniformBufferCount > i) { + if (graphicsPipeline->header.num_vertex_uniform_buffers > i) { ID3D12GraphicsCommandList_SetGraphicsRootConstantBufferView( commandBuffer->graphicsCommandList, graphicsPipeline->rootSignature->vertexUniformBufferRootIndex[i], @@ -4941,16 +5056,16 @@ static void D3D12_INTERNAL_BindGraphicsResources( } if (commandBuffer->needFragmentSamplerBind) { - if (graphicsPipeline->fragmentSamplerCount > 0) { - for (Uint32 i = 0; i < graphicsPipeline->fragmentSamplerCount; i += 1) { - cpuHandles[i] = commandBuffer->fragmentSamplers[i]->handle.cpuHandle; + if (graphicsPipeline->header.num_fragment_samplers > 0) { + for (Uint32 i = 0; i < graphicsPipeline->header.num_fragment_samplers; i += 1) { + cpuHandles[i] = commandBuffer->fragmentSamplerDescriptorHandles[i]; } D3D12_INTERNAL_WriteGPUDescriptors( commandBuffer, D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER, cpuHandles, - graphicsPipeline->fragmentSamplerCount, + graphicsPipeline->header.num_fragment_samplers, &gpuDescriptorHandle); ID3D12GraphicsCommandList_SetGraphicsRootDescriptorTable( @@ -4958,15 +5073,15 @@ static void D3D12_INTERNAL_BindGraphicsResources( graphicsPipeline->rootSignature->fragmentSamplerRootIndex, gpuDescriptorHandle); - for (Uint32 i = 0; i < graphicsPipeline->fragmentSamplerCount; i += 1) { - cpuHandles[i] = commandBuffer->fragmentSamplerTextures[i]->srvHandle.cpuHandle; + for (Uint32 i = 0; i < graphicsPipeline->header.num_fragment_samplers; i += 1) { + cpuHandles[i] = commandBuffer->fragmentSamplerTextureDescriptorHandles[i]; } D3D12_INTERNAL_WriteGPUDescriptors( commandBuffer, D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, cpuHandles, - graphicsPipeline->fragmentSamplerCount, + graphicsPipeline->header.num_fragment_samplers, &gpuDescriptorHandle); ID3D12GraphicsCommandList_SetGraphicsRootDescriptorTable( @@ -4978,16 +5093,16 @@ static void D3D12_INTERNAL_BindGraphicsResources( } if (commandBuffer->needFragmentStorageTextureBind) { - if (graphicsPipeline->fragmentStorageTextureCount > 0) { - for (Uint32 i = 0; i < graphicsPipeline->fragmentStorageTextureCount; i += 1) { - cpuHandles[i] = commandBuffer->fragmentStorageTextures[i]->srvHandle.cpuHandle; + if (graphicsPipeline->header.num_fragment_storage_textures > 0) { + for (Uint32 i = 0; i < graphicsPipeline->header.num_fragment_storage_textures; i += 1) { + cpuHandles[i] = commandBuffer->fragmentStorageTextureDescriptorHandles[i]; } D3D12_INTERNAL_WriteGPUDescriptors( commandBuffer, D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, cpuHandles, - graphicsPipeline->fragmentStorageTextureCount, + graphicsPipeline->header.num_fragment_storage_textures, &gpuDescriptorHandle); ID3D12GraphicsCommandList_SetGraphicsRootDescriptorTable( @@ -4999,16 +5114,16 @@ static void D3D12_INTERNAL_BindGraphicsResources( } if (commandBuffer->needFragmentStorageBufferBind) { - if (graphicsPipeline->fragmentStorageBufferCount > 0) { - for (Uint32 i = 0; i < graphicsPipeline->fragmentStorageBufferCount; i += 1) { - cpuHandles[i] = commandBuffer->fragmentStorageBuffers[i]->srvDescriptor.cpuHandle; + if (graphicsPipeline->header.num_fragment_storage_buffers > 0) { + for (Uint32 i = 0; i < graphicsPipeline->header.num_fragment_storage_buffers; i += 1) { + cpuHandles[i] = commandBuffer->fragmentStorageBufferDescriptorHandles[i]; } D3D12_INTERNAL_WriteGPUDescriptors( commandBuffer, D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, cpuHandles, - graphicsPipeline->fragmentStorageBufferCount, + graphicsPipeline->header.num_fragment_storage_buffers, &gpuDescriptorHandle); ID3D12GraphicsCommandList_SetGraphicsRootDescriptorTable( @@ -5021,7 +5136,7 @@ static void D3D12_INTERNAL_BindGraphicsResources( for (Uint32 i = 0; i < MAX_UNIFORM_BUFFERS_PER_STAGE; i += 1) { if (commandBuffer->needFragmentUniformBufferBind[i]) { - if (graphicsPipeline->fragmentUniformBufferCount > i) { + if (graphicsPipeline->header.num_fragment_uniform_buffers > i) { ID3D12GraphicsCommandList_SetGraphicsRootConstantBufferView( commandBuffer->graphicsCommandList, graphicsPipeline->rootSignature->fragmentUniformBufferRootIndex[i], @@ -5186,15 +5301,15 @@ static void D3D12_EndRenderPass( SDL_zeroa(d3d12CommandBuffer->vertexBufferOffsets); d3d12CommandBuffer->vertexBufferCount = 0; - SDL_zeroa(d3d12CommandBuffer->vertexSamplerTextures); - SDL_zeroa(d3d12CommandBuffer->vertexSamplers); - SDL_zeroa(d3d12CommandBuffer->vertexStorageTextures); - SDL_zeroa(d3d12CommandBuffer->vertexStorageBuffers); + SDL_zeroa(d3d12CommandBuffer->vertexSamplerTextureDescriptorHandles); + SDL_zeroa(d3d12CommandBuffer->vertexSamplerDescriptorHandles); + SDL_zeroa(d3d12CommandBuffer->vertexStorageTextureDescriptorHandles); + SDL_zeroa(d3d12CommandBuffer->vertexStorageBufferDescriptorHandles); - SDL_zeroa(d3d12CommandBuffer->fragmentSamplerTextures); - SDL_zeroa(d3d12CommandBuffer->fragmentSamplers); - SDL_zeroa(d3d12CommandBuffer->fragmentStorageTextures); - SDL_zeroa(d3d12CommandBuffer->fragmentStorageBuffers); + SDL_zeroa(d3d12CommandBuffer->fragmentSamplerTextureDescriptorHandles); + SDL_zeroa(d3d12CommandBuffer->fragmentSamplerDescriptorHandles); + SDL_zeroa(d3d12CommandBuffer->fragmentStorageTextureDescriptorHandles); + SDL_zeroa(d3d12CommandBuffer->fragmentStorageBufferDescriptorHandles); } // Compute Pass @@ -5228,6 +5343,7 @@ static void D3D12_BeginComputePass( D3D12_RESOURCE_STATE_UNORDERED_ACCESS); d3d12CommandBuffer->computeReadWriteStorageTextureSubresources[i] = subresource; + d3d12CommandBuffer->computeReadWriteStorageTextureDescriptorHandles[i] = subresource->uavHandle.cpuHandle; D3D12_INTERNAL_TrackTexture( d3d12CommandBuffer, @@ -5246,6 +5362,7 @@ static void D3D12_BeginComputePass( D3D12_RESOURCE_STATE_UNORDERED_ACCESS); d3d12CommandBuffer->computeReadWriteStorageBuffers[i] = buffer; + d3d12CommandBuffer->computeReadWriteStorageBufferDescriptorHandles[i] = buffer->uavDescriptor.cpuHandle; D3D12_INTERNAL_TrackBuffer( d3d12CommandBuffer, @@ -5287,7 +5404,7 @@ static void D3D12_BindComputePipeline( d3d12CommandBuffer->needComputeUniformBufferBind[i] = true; } - for (Uint32 i = 0; i < pipeline->numUniformBuffers; i += 1) { + for (Uint32 i = 0; i < pipeline->header.numUniformBuffers; i += 1) { if (d3d12CommandBuffer->computeUniformBuffers[i] == NULL) { d3d12CommandBuffer->computeUniformBuffers[i] = D3D12_INTERNAL_AcquireUniformBufferFromPool( d3d12CommandBuffer); @@ -5297,9 +5414,9 @@ static void D3D12_BindComputePipeline( D3D12_INTERNAL_TrackComputePipeline(d3d12CommandBuffer, pipeline); // Bind write-only resources after setting root signature - if (pipeline->numReadWriteStorageTextures > 0) { - for (Uint32 i = 0; i < pipeline->numReadWriteStorageTextures; i += 1) { - cpuHandles[i] = d3d12CommandBuffer->computeReadWriteStorageTextureSubresources[i]->uavHandle.cpuHandle; + if (pipeline->header.numReadWriteStorageTextures > 0) { + for (Uint32 i = 0; i < pipeline->header.numReadWriteStorageTextures; i += 1) { + cpuHandles[i] = d3d12CommandBuffer->computeReadWriteStorageTextureDescriptorHandles[i]; } D3D12_INTERNAL_WriteGPUDescriptors( @@ -5315,9 +5432,9 @@ static void D3D12_BindComputePipeline( gpuDescriptorHandle); } - if (pipeline->numReadWriteStorageBuffers > 0) { - for (Uint32 i = 0; i < pipeline->numReadWriteStorageBuffers; i += 1) { - cpuHandles[i] = d3d12CommandBuffer->computeReadWriteStorageBuffers[i]->uavDescriptor.cpuHandle; + if (pipeline->header.numReadWriteStorageBuffers > 0) { + for (Uint32 i = 0; i < pipeline->header.numReadWriteStorageBuffers; i += 1) { + cpuHandles[i] = d3d12CommandBuffer->computeReadWriteStorageBufferDescriptorHandles[i]; } D3D12_INTERNAL_WriteGPUDescriptors( @@ -5346,21 +5463,21 @@ static void D3D12_BindComputeSamplers( D3D12TextureContainer *container = (D3D12TextureContainer *)textureSamplerBindings[i].texture; D3D12Sampler *sampler = (D3D12Sampler *)textureSamplerBindings[i].sampler; - if (d3d12CommandBuffer->computeSamplers[firstSlot + i] != sampler) { + if (d3d12CommandBuffer->computeSamplerDescriptorHandles[firstSlot + i].ptr != sampler->handle.cpuHandle.ptr) { D3D12_INTERNAL_TrackSampler( d3d12CommandBuffer, (D3D12Sampler *)textureSamplerBindings[i].sampler); - d3d12CommandBuffer->computeSamplers[firstSlot + i] = (D3D12Sampler *)textureSamplerBindings[i].sampler; + d3d12CommandBuffer->computeSamplerDescriptorHandles[firstSlot + i] = sampler->handle.cpuHandle; d3d12CommandBuffer->needComputeSamplerBind = true; } - if (d3d12CommandBuffer->computeSamplerTextures[firstSlot + i] != container->activeTexture) { + if (d3d12CommandBuffer->computeSamplerTextureDescriptorHandles[firstSlot + i].ptr != container->activeTexture->srvHandle.cpuHandle.ptr) { D3D12_INTERNAL_TrackTexture( d3d12CommandBuffer, container->activeTexture); - d3d12CommandBuffer->computeSamplerTextures[firstSlot + i] = container->activeTexture; + d3d12CommandBuffer->computeSamplerTextureDescriptorHandles[firstSlot + i] = container->activeTexture->srvHandle.cpuHandle; d3d12CommandBuffer->needComputeSamplerBind = true; } } @@ -5397,6 +5514,7 @@ static void D3D12_BindComputeStorageTextures( container->activeTexture); d3d12CommandBuffer->computeReadOnlyStorageTextures[firstSlot + i] = container->activeTexture; + d3d12CommandBuffer->computeReadOnlyStorageTextureDescriptorHandles[firstSlot + i] = container->activeTexture->srvHandle.cpuHandle; d3d12CommandBuffer->needComputeReadOnlyStorageTextureBind = true; } } @@ -5434,6 +5552,7 @@ static void D3D12_BindComputeStorageBuffers( buffer); d3d12CommandBuffer->computeReadOnlyStorageBuffers[firstSlot + i] = buffer; + d3d12CommandBuffer->computeReadOnlyStorageBufferDescriptorHandles[firstSlot + i] = buffer->srvDescriptor.cpuHandle; d3d12CommandBuffer->needComputeReadOnlyStorageBufferBind = true; } } @@ -5469,16 +5588,16 @@ static void D3D12_INTERNAL_BindComputeResources( D3D12_GPU_DESCRIPTOR_HANDLE gpuDescriptorHandle; if (commandBuffer->needComputeSamplerBind) { - if (computePipeline->numSamplers > 0) { - for (Uint32 i = 0; i < computePipeline->numSamplers; i += 1) { - cpuHandles[i] = commandBuffer->computeSamplers[i]->handle.cpuHandle; + if (computePipeline->header.numSamplers > 0) { + for (Uint32 i = 0; i < computePipeline->header.numSamplers; i += 1) { + cpuHandles[i] = commandBuffer->computeSamplerDescriptorHandles[i]; } D3D12_INTERNAL_WriteGPUDescriptors( commandBuffer, D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER, cpuHandles, - computePipeline->numSamplers, + computePipeline->header.numSamplers, &gpuDescriptorHandle); ID3D12GraphicsCommandList_SetComputeRootDescriptorTable( @@ -5486,15 +5605,15 @@ static void D3D12_INTERNAL_BindComputeResources( computePipeline->rootSignature->samplerRootIndex, gpuDescriptorHandle); - for (Uint32 i = 0; i < computePipeline->numSamplers; i += 1) { - cpuHandles[i] = commandBuffer->computeSamplerTextures[i]->srvHandle.cpuHandle; + for (Uint32 i = 0; i < computePipeline->header.numSamplers; i += 1) { + cpuHandles[i] = commandBuffer->computeSamplerTextureDescriptorHandles[i]; } D3D12_INTERNAL_WriteGPUDescriptors( commandBuffer, D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, cpuHandles, - computePipeline->numSamplers, + computePipeline->header.numSamplers, &gpuDescriptorHandle); ID3D12GraphicsCommandList_SetComputeRootDescriptorTable( @@ -5506,16 +5625,16 @@ static void D3D12_INTERNAL_BindComputeResources( } if (commandBuffer->needComputeReadOnlyStorageTextureBind) { - if (computePipeline->numReadOnlyStorageTextures > 0) { - for (Uint32 i = 0; i < computePipeline->numReadOnlyStorageTextures; i += 1) { - cpuHandles[i] = commandBuffer->computeReadOnlyStorageTextures[i]->srvHandle.cpuHandle; + if (computePipeline->header.numReadonlyStorageTextures > 0) { + for (Uint32 i = 0; i < computePipeline->header.numReadonlyStorageTextures; i += 1) { + cpuHandles[i] = commandBuffer->computeReadOnlyStorageTextureDescriptorHandles[i]; } D3D12_INTERNAL_WriteGPUDescriptors( commandBuffer, D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, cpuHandles, - computePipeline->numReadOnlyStorageTextures, + computePipeline->header.numReadonlyStorageTextures, &gpuDescriptorHandle); ID3D12GraphicsCommandList_SetComputeRootDescriptorTable( @@ -5527,16 +5646,16 @@ static void D3D12_INTERNAL_BindComputeResources( } if (commandBuffer->needComputeReadOnlyStorageBufferBind) { - if (computePipeline->numReadOnlyStorageBuffers > 0) { - for (Uint32 i = 0; i < computePipeline->numReadOnlyStorageBuffers; i += 1) { - cpuHandles[i] = commandBuffer->computeReadOnlyStorageBuffers[i]->srvDescriptor.cpuHandle; + if (computePipeline->header.numReadonlyStorageBuffers > 0) { + for (Uint32 i = 0; i < computePipeline->header.numReadonlyStorageBuffers; i += 1) { + cpuHandles[i] = commandBuffer->computeReadOnlyStorageBufferDescriptorHandles[i]; } D3D12_INTERNAL_WriteGPUDescriptors( commandBuffer, D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, cpuHandles, - computePipeline->numReadOnlyStorageBuffers, + computePipeline->header.numReadonlyStorageBuffers, &gpuDescriptorHandle); ID3D12GraphicsCommandList_SetComputeRootDescriptorTable( @@ -5549,7 +5668,7 @@ static void D3D12_INTERNAL_BindComputeResources( for (Uint32 i = 0; i < MAX_UNIFORM_BUFFERS_PER_STAGE; i += 1) { if (commandBuffer->needComputeUniformBufferBind[i]) { - if (computePipeline->numUniformBuffers > i) { + if (computePipeline->header.numUniformBuffers > i) { ID3D12GraphicsCommandList_SetComputeRootConstantBufferView( commandBuffer->graphicsCommandList, computePipeline->rootSignature->uniformBufferRootIndex[i], @@ -5648,8 +5767,11 @@ static void D3D12_EndComputePass( } } - SDL_zeroa(d3d12CommandBuffer->computeSamplerTextures); - SDL_zeroa(d3d12CommandBuffer->computeSamplers); + SDL_zeroa(d3d12CommandBuffer->computeSamplerTextureDescriptorHandles); + SDL_zeroa(d3d12CommandBuffer->computeSamplerDescriptorHandles); + + SDL_zeroa(d3d12CommandBuffer->computeReadWriteStorageTextureDescriptorHandles); + SDL_zeroa(d3d12CommandBuffer->computeReadWriteStorageBufferDescriptorHandles); d3d12CommandBuffer->currentComputePipeline = NULL; } @@ -5815,7 +5937,7 @@ static void D3D12_UploadToTexture( &destinationLocation, destination->x, destination->y, - sliceIndex, + destination->z + sliceIndex, &sourceLocation, NULL); } @@ -5824,6 +5946,10 @@ static void D3D12_UploadToTexture( D3D12_INTERNAL_ReleaseBuffer( d3d12CommandBuffer->renderer, temporaryBuffer); + + if (d3d12CommandBuffer->renderer->debug_mode) { + SDL_LogWarn(SDL_LOG_CATEGORY_GPU, "Texture upload row pitch not aligned to 256 bytes! This is suboptimal on D3D12!"); + } } else if (needsPlacementCopy) { temporaryBuffer = D3D12_INTERNAL_CreateBuffer( d3d12CommandBuffer->renderer, @@ -5861,7 +5987,9 @@ static void D3D12_UploadToTexture( d3d12CommandBuffer->renderer, temporaryBuffer); - SDL_LogWarn(SDL_LOG_CATEGORY_GPU, "Texture upload offset not aligned to 512 bytes! This is suboptimal on D3D12!"); + if (d3d12CommandBuffer->renderer->debug_mode) { + SDL_LogWarn(SDL_LOG_CATEGORY_GPU, "Texture upload offset not aligned to 512 bytes! This is suboptimal on D3D12!"); + } } else { sourceLocation.pResource = transferBufferContainer->activeBuffer->handle; sourceLocation.PlacedFootprint.Offset = source->offset; @@ -6132,6 +6260,9 @@ static void D3D12_DownloadFromTexture( destinationLocation.pResource = textureDownload->temporaryBuffer->handle; destinationLocation.PlacedFootprint.Offset = 0; + if (d3d12CommandBuffer->renderer->debug_mode) { + SDL_LogWarn(SDL_LOG_CATEGORY_GPU, "Texture pitch or offset not aligned properly! This is suboptimal on D3D12!"); + } } else { destinationLocation.pResource = destinationBuffer->handle; destinationLocation.PlacedFootprint.Offset = destination->offset; @@ -6474,9 +6605,7 @@ static void D3D12_INTERNAL_DestroySwapchain( { renderer->commandQueue->PresentX(0, NULL, NULL); for (Uint32 i = 0; i < windowData->swapchainTextureCount; i += 1) { - D3D12_INTERNAL_DestroyTexture( - renderer, - windowData->textureContainers[i].activeTexture); + D3D12_INTERNAL_DestroyTexture(windowData->textureContainers[i].activeTexture); } } @@ -6492,9 +6621,7 @@ static bool D3D12_INTERNAL_ResizeSwapchain( // Clean up the previous swapchain textures for (Uint32 i = 0; i < windowData->swapchainTextureCount; i += 1) { - D3D12_INTERNAL_DestroyTexture( - renderer, - windowData->textureContainers[i].activeTexture); + D3D12_INTERNAL_DestroyTexture(windowData->textureContainers[i].activeTexture); } // Create a new swapchain @@ -6633,10 +6760,8 @@ static bool D3D12_INTERNAL_ResizeSwapchain( // Release views and clean up for (Uint32 i = 0; i < windowData->swapchainTextureCount; i += 1) { D3D12_INTERNAL_ReleaseStagingDescriptorHandle( - renderer, &windowData->textureContainers[i].activeTexture->srvHandle); D3D12_INTERNAL_ReleaseStagingDescriptorHandle( - renderer, &windowData->textureContainers[i].activeTexture->subresources[0].rtvHandles[0]); SDL_free(windowData->textureContainers[i].activeTexture->subresources[0].rtvHandles); @@ -6684,10 +6809,8 @@ static void D3D12_INTERNAL_DestroySwapchain( // Release views and clean up for (Uint32 i = 0; i < windowData->swapchainTextureCount; i += 1) { D3D12_INTERNAL_ReleaseStagingDescriptorHandle( - renderer, &windowData->textureContainers[i].activeTexture->srvHandle); D3D12_INTERNAL_ReleaseStagingDescriptorHandle( - renderer, &windowData->textureContainers[i].activeTexture->subresources[0].rtvHandles[0]); SDL_free(windowData->textureContainers[i].activeTexture->subresources[0].rtvHandles); @@ -7238,20 +7361,22 @@ static SDL_GPUCommandBuffer *D3D12_AcquireCommandBuffer( SDL_zeroa(commandBuffer->vertexBufferOffsets); commandBuffer->vertexBufferCount = 0; - SDL_zeroa(commandBuffer->vertexSamplerTextures); - SDL_zeroa(commandBuffer->vertexSamplers); - SDL_zeroa(commandBuffer->vertexStorageTextures); - SDL_zeroa(commandBuffer->vertexStorageBuffers); + SDL_zeroa(commandBuffer->vertexSamplerTextureDescriptorHandles); + SDL_zeroa(commandBuffer->vertexSamplerDescriptorHandles); + SDL_zeroa(commandBuffer->vertexStorageTextureDescriptorHandles); + SDL_zeroa(commandBuffer->vertexStorageBufferDescriptorHandles); SDL_zeroa(commandBuffer->vertexUniformBuffers); - SDL_zeroa(commandBuffer->fragmentSamplerTextures); - SDL_zeroa(commandBuffer->fragmentSamplers); - SDL_zeroa(commandBuffer->fragmentStorageTextures); - SDL_zeroa(commandBuffer->fragmentStorageBuffers); + SDL_zeroa(commandBuffer->fragmentSamplerTextureDescriptorHandles); + SDL_zeroa(commandBuffer->fragmentSamplerDescriptorHandles); + SDL_zeroa(commandBuffer->fragmentStorageTextureDescriptorHandles); + SDL_zeroa(commandBuffer->fragmentStorageBufferDescriptorHandles); SDL_zeroa(commandBuffer->fragmentUniformBuffers); - SDL_zeroa(commandBuffer->computeSamplerTextures); - SDL_zeroa(commandBuffer->computeSamplers); + SDL_zeroa(commandBuffer->computeSamplerTextureDescriptorHandles); + SDL_zeroa(commandBuffer->computeSamplerDescriptorHandles); + SDL_zeroa(commandBuffer->computeReadOnlyStorageTextureDescriptorHandles); + SDL_zeroa(commandBuffer->computeReadOnlyStorageBufferDescriptorHandles); SDL_zeroa(commandBuffer->computeReadOnlyStorageTextures); SDL_zeroa(commandBuffer->computeReadOnlyStorageBuffers); SDL_zeroa(commandBuffer->computeReadWriteStorageTextureSubresources); @@ -7396,7 +7521,7 @@ static bool D3D12_INTERNAL_AcquireSwapchainTexture( 1, &barrierDesc); - *swapchainTexture = (SDL_GPUTexture*)&windowData->textureContainers[swapchainIndex]; + *swapchainTexture = (SDL_GPUTexture *)&windowData->textureContainers[swapchainIndex]; return true; } @@ -7439,7 +7564,6 @@ static void D3D12_INTERNAL_PerformPendingDestroys(D3D12Renderer *renderer) for (Sint32 i = renderer->buffersToDestroyCount - 1; i >= 0; i -= 1) { if (SDL_GetAtomicInt(&renderer->buffersToDestroy[i]->referenceCount) == 0) { D3D12_INTERNAL_DestroyBuffer( - renderer, renderer->buffersToDestroy[i]); renderer->buffersToDestroy[i] = renderer->buffersToDestroy[renderer->buffersToDestroyCount - 1]; @@ -7450,7 +7574,6 @@ static void D3D12_INTERNAL_PerformPendingDestroys(D3D12Renderer *renderer) for (Sint32 i = renderer->texturesToDestroyCount - 1; i >= 0; i -= 1) { if (SDL_GetAtomicInt(&renderer->texturesToDestroy[i]->referenceCount) == 0) { D3D12_INTERNAL_DestroyTexture( - renderer, renderer->texturesToDestroy[i]); renderer->texturesToDestroy[i] = renderer->texturesToDestroy[renderer->texturesToDestroyCount - 1]; @@ -7461,7 +7584,6 @@ static void D3D12_INTERNAL_PerformPendingDestroys(D3D12Renderer *renderer) for (Sint32 i = renderer->samplersToDestroyCount - 1; i >= 0; i -= 1) { if (SDL_GetAtomicInt(&renderer->samplersToDestroy[i]->referenceCount) == 0) { D3D12_INTERNAL_DestroySampler( - renderer, renderer->samplersToDestroy[i]); renderer->samplersToDestroy[i] = renderer->samplersToDestroy[renderer->samplersToDestroyCount - 1]; @@ -7811,7 +7933,7 @@ static bool D3D12_Submit( ID3D12Resource_Release(windowData->textureContainers[presentData->swapchainImageIndex].activeTexture->resource); #endif - windowData->inFlightFences[windowData->frameCounter] = (SDL_GPUFence*)d3d12CommandBuffer->inFlightFence; + windowData->inFlightFences[windowData->frameCounter] = (SDL_GPUFence *)d3d12CommandBuffer->inFlightFence; (void)SDL_AtomicIncRef(&d3d12CommandBuffer->inFlightFence->referenceCount); windowData->frameCounter = (windowData->frameCounter + 1) % renderer->allowedFramesInFlight; } @@ -8100,8 +8222,7 @@ static void D3D12_INTERNAL_InitBlitResources( shaderCreateInfo.code = (Uint8 *)D3D12_FullscreenVert; shaderCreateInfo.code_size = sizeof(D3D12_FullscreenVert); shaderCreateInfo.stage = SDL_GPU_SHADERSTAGE_VERTEX; - shaderCreateInfo.format = SDL_GPU_SHADERFORMAT_DXBC; - shaderCreateInfo.entrypoint = "main"; + shaderCreateInfo.format = SDL_GPU_SHADERFORMAT_DXIL; renderer->blitVertexShader = D3D12_CreateShader( (SDL_GPURenderer *)renderer, @@ -8210,7 +8331,7 @@ static void D3D12_INTERNAL_InitBlitResources( } } -static bool D3D12_PrepareDriver(SDL_VideoDevice *_this) +static bool D3D12_PrepareDriver(SDL_VideoDevice *_this, SDL_PropertiesID props) { #if defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES) return true; @@ -8226,6 +8347,17 @@ static bool D3D12_PrepareDriver(SDL_VideoDevice *_this) IDXGIFactory6 *factory6; IDXGIAdapter1 *adapter; + // Early check to see if the app has _any_ D3D12 formats, if not we don't + // have to fuss with loading D3D in the first place. + bool has_dxbc = SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_SHADERS_DXBC_BOOLEAN, false); + bool has_dxil = SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_SHADERS_DXIL_BOOLEAN, false); + bool supports_dxil = false; + // TODO SM7: bool has_spirv = SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_SHADERS_SPIRV_BOOLEAN, false); + // TODO SM7: bool supports_spirv = false; + if (!has_dxbc && !has_dxil) { + return false; + } + // Can we load D3D12? d3d12Dll = SDL_LoadObject(D3D12_DLL); @@ -8320,6 +8452,18 @@ static bool D3D12_PrepareDriver(SDL_VideoDevice *_this) (void **)&device); if (SUCCEEDED(res)) { + D3D12_FEATURE_DATA_SHADER_MODEL shaderModel; + shaderModel.HighestShaderModel = D3D_SHADER_MODEL_6_0; + + res = ID3D12Device_CheckFeatureSupport( + device, + D3D12_FEATURE_SHADER_MODEL, + &shaderModel, + sizeof(shaderModel)); + if (SUCCEEDED(res) && shaderModel.HighestShaderModel >= D3D_SHADER_MODEL_6_0) { + supports_dxil = true; + } + ID3D12Device_Release(device); } IDXGIAdapter1_Release(adapter); @@ -8328,6 +8472,11 @@ static bool D3D12_PrepareDriver(SDL_VideoDevice *_this) SDL_UnloadObject(d3d12Dll); SDL_UnloadObject(dxgiDll); + if (!supports_dxil && !has_dxbc) { + SDL_LogWarn(SDL_LOG_CATEGORY_GPU, "D3D12: DXIL is not supported and DXBC is not being provided"); + return false; + } + if (FAILED(res)) { SDL_LogWarn(SDL_LOG_CATEGORY_GPU, "D3D12: Could not create D3D12Device with feature level " D3D_FEATURE_LEVEL_CHOICE_STR); return false; @@ -8338,38 +8487,38 @@ static bool D3D12_PrepareDriver(SDL_VideoDevice *_this) } #if !(defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES)) && defined(HAVE_IDXGIINFOQUEUE) -static void D3D12_INTERNAL_TryInitializeDXGIDebug(D3D12Renderer *renderer) +static bool D3D12_INTERNAL_TryInitializeDXGIDebug(D3D12Renderer *renderer) { PFN_DXGI_GET_DEBUG_INTERFACE DXGIGetDebugInterfaceFunc; HRESULT res; renderer->dxgidebug_dll = SDL_LoadObject(DXGIDEBUG_DLL); if (renderer->dxgidebug_dll == NULL) { - SDL_LogWarn(SDL_LOG_CATEGORY_GPU, "Could not find " DXGIDEBUG_DLL); - return; + return false; } DXGIGetDebugInterfaceFunc = (PFN_DXGI_GET_DEBUG_INTERFACE)SDL_LoadFunction( renderer->dxgidebug_dll, DXGI_GET_DEBUG_INTERFACE_FUNC); if (DXGIGetDebugInterfaceFunc == NULL) { - SDL_LogWarn(SDL_LOG_CATEGORY_GPU, "Could not load function: " DXGI_GET_DEBUG_INTERFACE_FUNC); - return; + return false; } res = DXGIGetDebugInterfaceFunc(&D3D_IID_IDXGIDebug, (void **)&renderer->dxgiDebug); if (FAILED(res)) { - SDL_LogWarn(SDL_LOG_CATEGORY_GPU, "Could not get IDXGIDebug interface"); + return false; } res = DXGIGetDebugInterfaceFunc(&D3D_IID_IDXGIInfoQueue, (void **)&renderer->dxgiInfoQueue); if (FAILED(res)) { - SDL_LogWarn(SDL_LOG_CATEGORY_GPU, "Could not get IDXGIInfoQueue interface"); + return false; } + + return true; } #endif -static void D3D12_INTERNAL_TryInitializeD3D12Debug(D3D12Renderer *renderer) +static bool D3D12_INTERNAL_TryInitializeD3D12Debug(D3D12Renderer *renderer) { PFN_D3D12_GET_DEBUG_INTERFACE D3D12GetDebugInterfaceFunc; HRESULT res; @@ -8378,21 +8527,20 @@ static void D3D12_INTERNAL_TryInitializeD3D12Debug(D3D12Renderer *renderer) renderer->d3d12_dll, D3D12_GET_DEBUG_INTERFACE_FUNC); if (D3D12GetDebugInterfaceFunc == NULL) { - SDL_LogWarn(SDL_LOG_CATEGORY_GPU, "Could not load function: " D3D12_GET_DEBUG_INTERFACE_FUNC); - return; + return false; } res = D3D12GetDebugInterfaceFunc(D3D_GUID(D3D_IID_ID3D12Debug), (void **)&renderer->d3d12Debug); if (FAILED(res)) { - SDL_LogWarn(SDL_LOG_CATEGORY_GPU, "Could not get ID3D12Debug interface"); - return; + return false; } ID3D12Debug_EnableDebugLayer(renderer->d3d12Debug); + return true; } #if !(defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES)) -static bool D3D12_INTERNAL_TryInitializeD3D12DebugInfoQueue(D3D12Renderer *renderer) +static void D3D12_INTERNAL_TryInitializeD3D12DebugInfoQueue(D3D12Renderer *renderer) { ID3D12InfoQueue *infoQueue = NULL; D3D12_MESSAGE_SEVERITY severities[] = { D3D12_MESSAGE_SEVERITY_INFO }; @@ -8404,7 +8552,7 @@ static bool D3D12_INTERNAL_TryInitializeD3D12DebugInfoQueue(D3D12Renderer *rende D3D_GUID(D3D_IID_ID3D12InfoQueue), (void **)&infoQueue); if (FAILED(res)) { - CHECK_D3D12_ERROR_AND_RETURN("Failed to convert ID3D12Device to ID3D12InfoQueue", false); + return; } SDL_zero(filter); @@ -8420,8 +8568,6 @@ static bool D3D12_INTERNAL_TryInitializeD3D12DebugInfoQueue(D3D12Renderer *rende true); ID3D12InfoQueue_Release(infoQueue); - - return true; } static void WINAPI D3D12_INTERNAL_OnD3D12DebugInfoMsg( @@ -8558,8 +8704,14 @@ static SDL_GPUDevice *D3D12_CreateDevice(bool debugMode, bool preferLowPower, SD D3D12_FEATURE_DATA_ARCHITECTURE architecture; D3D12_COMMAND_QUEUE_DESC queueDesc; + bool verboseLogs = SDL_GetBooleanProperty( + props, + SDL_PROP_GPU_DEVICE_CREATE_VERBOSE_BOOLEAN, + true); + renderer = (D3D12Renderer *)SDL_calloc(1, sizeof(D3D12Renderer)); + bool hasDxgiDebug = false; #if !(defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES)) // Load the DXGI library renderer->dxgi_dll = SDL_LoadObject(DXGI_DLL); @@ -8571,8 +8723,10 @@ static SDL_GPUDevice *D3D12_CreateDevice(bool debugMode, bool preferLowPower, SD #ifdef HAVE_IDXGIINFOQUEUE // Initialize the DXGI debug layer, if applicable if (debugMode) { - D3D12_INTERNAL_TryInitializeDXGIDebug(renderer); + hasDxgiDebug = D3D12_INTERNAL_TryInitializeDXGIDebug(renderer); } +#else + hasDxgiDebug = true; #endif // Load the CreateDXGIFactory1 function @@ -8658,15 +8812,39 @@ static SDL_GPUDevice *D3D12_CreateDevice(bool debugMode, bool preferLowPower, SD CHECK_D3D12_ERROR_AND_RETURN("Could not get adapter driver version", NULL); } - SDL_LogInfo(SDL_LOG_CATEGORY_GPU, "SDL_GPU Driver: D3D12"); - SDL_LogInfo(SDL_LOG_CATEGORY_GPU, "D3D12 Adapter: %S", adapterDesc.Description); - SDL_LogInfo( - SDL_LOG_CATEGORY_GPU, - "D3D12 Driver: %d.%d.%d.%d", + renderer->props = SDL_CreateProperties(); + if (verboseLogs) { + SDL_LogInfo(SDL_LOG_CATEGORY_GPU, "SDL_GPU Driver: D3D12"); + } + + // Record device name + char *deviceName = SDL_iconv_wchar_utf8(&adapterDesc.Description[0]); + SDL_SetStringProperty( + renderer->props, + SDL_PROP_GPU_DEVICE_NAME_STRING, + deviceName); + if (verboseLogs) { + SDL_LogInfo(SDL_LOG_CATEGORY_GPU, "D3D12 Adapter: %s", deviceName); + } + SDL_free(deviceName); + + // Record driver version + char driverVer[64]; + (void)SDL_snprintf( + driverVer, + SDL_arraysize(driverVer), + "%d.%d.%d.%d", HIWORD(umdVersion.HighPart), LOWORD(umdVersion.HighPart), HIWORD(umdVersion.LowPart), LOWORD(umdVersion.LowPart)); + SDL_SetStringProperty( + renderer->props, + SDL_PROP_GPU_DEVICE_DRIVER_VERSION_STRING, + driverVer); + if (verboseLogs) { + SDL_LogInfo(SDL_LOG_CATEGORY_GPU, "D3D12 Driver: %s", driverVer); + } #endif // Load the D3D library @@ -8705,7 +8883,27 @@ static SDL_GPUDevice *D3D12_CreateDevice(bool debugMode, bool preferLowPower, SD // Initialize the D3D12 debug layer, if applicable if (debugMode) { - D3D12_INTERNAL_TryInitializeD3D12Debug(renderer); + bool hasD3d12Debug = D3D12_INTERNAL_TryInitializeD3D12Debug(renderer); +#if (defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES)) + if (hasD3d12Debug) { + SDL_LogInfo( + SDL_LOG_CATEGORY_GPU, + "Validation layers enabled, expect debug level performance!"); +#else + if (hasDxgiDebug && hasD3d12Debug) { + SDL_LogInfo( + SDL_LOG_CATEGORY_GPU, + "Validation layers enabled, expect debug level performance!"); + } else if (hasDxgiDebug || hasD3d12Debug) { + SDL_LogWarn( + SDL_LOG_CATEGORY_GPU, + "Validation layers partially enabled, some warnings may not be available"); +#endif + } else { + SDL_LogWarn( + SDL_LOG_CATEGORY_GPU, + "Validation layers not found, continuing without validation"); + } } // Create the D3D12Device @@ -8752,9 +8950,7 @@ static SDL_GPUDevice *D3D12_CreateDevice(bool debugMode, bool preferLowPower, SD // Initialize the D3D12 debug info queue, if applicable if (debugMode) { - if (!D3D12_INTERNAL_TryInitializeD3D12DebugInfoQueue(renderer)) { - return NULL; - } + D3D12_INTERNAL_TryInitializeD3D12DebugInfoQueue(renderer); D3D12_INTERNAL_TryInitializeD3D12DebugInfoLogger(renderer); } #endif @@ -9035,8 +9231,23 @@ static SDL_GPUDevice *D3D12_CreateDevice(bool debugMode, bool preferLowPower, SD return NULL; } + SDL_GPUShaderFormat shaderFormats = SDL_GPU_SHADERFORMAT_DXBC; + + D3D12_FEATURE_DATA_SHADER_MODEL shaderModel; + shaderModel.HighestShaderModel = D3D_SHADER_MODEL_6_0; + + res = ID3D12Device_CheckFeatureSupport( + renderer->device, + D3D12_FEATURE_SHADER_MODEL, + &shaderModel, + sizeof(shaderModel)); + if (SUCCEEDED(res) && shaderModel.HighestShaderModel >= D3D_SHADER_MODEL_6_0) { + shaderFormats |= SDL_GPU_SHADERFORMAT_DXIL; + } + ASSIGN_DRIVER(D3D12) result->driverData = (SDL_GPURenderer *)renderer; + result->shader_formats = shaderFormats; result->debug_mode = debugMode; renderer->sdlGPUDevice = result; @@ -9045,7 +9256,6 @@ static SDL_GPUDevice *D3D12_CreateDevice(bool debugMode, bool preferLowPower, SD SDL_GPUBootstrap D3D12Driver = { "direct3d12", - SDL_GPU_SHADERFORMAT_DXIL | SDL_GPU_SHADERFORMAT_DXBC, D3D12_PrepareDriver, D3D12_CreateDevice }; diff --git a/libs/SDL3/src/gpu/metal/SDL_gpu_metal.m b/libs/SDL3/src/gpu/metal/SDL_gpu_metal.m index 4ab10206..26dea454 100644 --- a/libs/SDL3/src/gpu/metal/SDL_gpu_metal.m +++ b/libs/SDL3/src/gpu/metal/SDL_gpu_metal.m @@ -476,33 +476,21 @@ typedef struct MetalShader typedef struct MetalGraphicsPipeline { + GraphicsPipelineCommonHeader header; + id handle; SDL_GPURasterizerState rasterizerState; SDL_GPUPrimitiveType primitiveType; id depth_stencil_state; - - Uint32 vertexSamplerCount; - Uint32 vertexUniformBufferCount; - Uint32 vertexStorageBufferCount; - Uint32 vertexStorageTextureCount; - - Uint32 fragmentSamplerCount; - Uint32 fragmentUniformBufferCount; - Uint32 fragmentStorageBufferCount; - Uint32 fragmentStorageTextureCount; } MetalGraphicsPipeline; typedef struct MetalComputePipeline { + ComputePipelineCommonHeader header; + id handle; - Uint32 numSamplers; - Uint32 numReadonlyStorageTextures; - Uint32 numReadWriteStorageTextures; - Uint32 numReadonlyStorageBuffers; - Uint32 numReadWriteStorageBuffers; - Uint32 numUniformBuffers; Uint32 threadcountX; Uint32 threadcountY; Uint32 threadcountZ; @@ -643,6 +631,7 @@ struct MetalRenderer id queue; bool debugMode; + SDL_PropertiesID props; Uint32 allowedFramesInFlight; MetalWindowData **claimedWindows; @@ -765,11 +754,20 @@ static void METAL_DestroyDevice(SDL_GPUDevice *device) // Release the command queue renderer->queue = nil; + // Release properties + SDL_DestroyProperties(renderer->props); + // Free the primary structures SDL_free(renderer); SDL_free(device); } +static SDL_PropertiesID METAL_GetDeviceProperties(SDL_GPUDevice *device) +{ + MetalRenderer *renderer = (MetalRenderer *)device->driverData; + return renderer->props; +} + // Resource tracking static void METAL_INTERNAL_TrackBuffer( @@ -826,6 +824,17 @@ typedef struct MetalLibraryFunction id function; } MetalLibraryFunction; +static bool METAL_INTERNAL_IsValidMetalLibrary( + const Uint8 *code, + size_t codeSize) +{ + // Metal libraries have a 4 byte header containing `MTLB`. + if (codeSize < 4 || code == NULL) { + return false; + } + return SDL_memcmp(code, "MTLB", 4) == 0; +} + // This function assumes that it's called from within an autorelease pool static MetalLibraryFunction METAL_INTERNAL_CompileShader( MetalRenderer *renderer, @@ -840,6 +849,10 @@ static MetalLibraryFunction METAL_INTERNAL_CompileShader( dispatch_data_t data; id function; + if (!entrypoint) { + entrypoint = "main0"; + } + if (format == SDL_GPU_SHADERFORMAT_MSL) { NSString *codeString = [[NSString alloc] initWithBytes:code @@ -850,6 +863,11 @@ static MetalLibraryFunction METAL_INTERNAL_CompileShader( options:nil error:&error]; } else if (format == SDL_GPU_SHADERFORMAT_METALLIB) { + if (!METAL_INTERNAL_IsValidMetalLibrary(code, codeSize)) { + SET_STRING_ERROR_AND_RETURN( + "The provided shader code is not a valid Metal library!", + libraryFunction); + } data = dispatch_data_create( code, codeSize, @@ -1055,12 +1073,12 @@ static SDL_GPUComputePipeline *METAL_CreateComputePipeline( pipeline = SDL_calloc(1, sizeof(MetalComputePipeline)); pipeline->handle = handle; - pipeline->numSamplers = createinfo->num_samplers; - pipeline->numReadonlyStorageTextures = createinfo->num_readonly_storage_textures; - pipeline->numReadWriteStorageTextures = createinfo->num_readwrite_storage_textures; - pipeline->numReadonlyStorageBuffers = createinfo->num_readonly_storage_buffers; - pipeline->numReadWriteStorageBuffers = createinfo->num_readwrite_storage_buffers; - pipeline->numUniformBuffers = createinfo->num_uniform_buffers; + pipeline->header.numSamplers = createinfo->num_samplers; + pipeline->header.numReadonlyStorageTextures = createinfo->num_readonly_storage_textures; + pipeline->header.numReadWriteStorageTextures = createinfo->num_readwrite_storage_textures; + pipeline->header.numReadonlyStorageBuffers = createinfo->num_readonly_storage_buffers; + pipeline->header.numReadWriteStorageBuffers = createinfo->num_readwrite_storage_buffers; + pipeline->header.numUniformBuffers = createinfo->num_uniform_buffers; pipeline->threadcountX = createinfo->threadcount_x; pipeline->threadcountY = createinfo->threadcount_y; pipeline->threadcountZ = createinfo->threadcount_z; @@ -1121,6 +1139,7 @@ static SDL_GPUGraphicsPipeline *METAL_CreateGraphicsPipeline( // Multisample pipelineDescriptor.rasterSampleCount = SDLToMetal_SampleCount[createinfo->multisample_state.sample_count]; + pipelineDescriptor.alphaToCoverageEnabled = createinfo->multisample_state.enable_alpha_to_coverage; // Depth Stencil @@ -1203,14 +1222,14 @@ static SDL_GPUGraphicsPipeline *METAL_CreateGraphicsPipeline( result->depth_stencil_state = depthStencilState; result->rasterizerState = createinfo->rasterizer_state; result->primitiveType = createinfo->primitive_type; - result->vertexSamplerCount = vertexShader->numSamplers; - result->vertexUniformBufferCount = vertexShader->numUniformBuffers; - result->vertexStorageBufferCount = vertexShader->numStorageBuffers; - result->vertexStorageTextureCount = vertexShader->numStorageTextures; - result->fragmentSamplerCount = fragmentShader->numSamplers; - result->fragmentUniformBufferCount = fragmentShader->numUniformBuffers; - result->fragmentStorageBufferCount = fragmentShader->numStorageBuffers; - result->fragmentStorageTextureCount = fragmentShader->numStorageTextures; + result->header.num_vertex_samplers = vertexShader->numSamplers; + result->header.num_vertex_uniform_buffers = vertexShader->numUniformBuffers; + result->header.num_vertex_storage_buffers = vertexShader->numStorageBuffers; + result->header.num_vertex_storage_textures = vertexShader->numStorageTextures; + result->header.num_fragment_samplers = fragmentShader->numSamplers; + result->header.num_fragment_uniform_buffers = fragmentShader->numUniformBuffers; + result->header.num_fragment_storage_buffers = fragmentShader->numStorageBuffers; + result->header.num_fragment_storage_textures = fragmentShader->numStorageTextures; return (SDL_GPUGraphicsPipeline *)result; } } @@ -1494,7 +1513,9 @@ static SDL_GPUTexture *METAL_CreateTexture( // Copy properties so we don't lose information when the client destroys them container->header.info = *createinfo; container->header.info.props = SDL_CreateProperties(); - SDL_CopyProperties(createinfo->props, container->header.info.props); + if (createinfo->props) { + SDL_CopyProperties(createinfo->props, container->header.info.props); + } container->activeTexture = texture; container->textureCapacity = 1; @@ -2406,14 +2427,14 @@ static void METAL_BindGraphicsPipeline( metalCommandBuffer->needFragmentUniformBufferBind[i] = true; } - for (i = 0; i < pipeline->vertexUniformBufferCount; i += 1) { + for (i = 0; i < pipeline->header.num_vertex_uniform_buffers; i += 1) { if (metalCommandBuffer->vertexUniformBuffers[i] == NULL) { metalCommandBuffer->vertexUniformBuffers[i] = METAL_INTERNAL_AcquireUniformBufferFromPool( metalCommandBuffer); } } - for (i = 0; i < pipeline->fragmentUniformBufferCount; i += 1) { + for (i = 0; i < pipeline->header.num_fragment_uniform_buffers; i += 1) { if (metalCommandBuffer->fragmentUniformBuffers[i] == NULL) { metalCommandBuffer->fragmentUniformBuffers[i] = METAL_INTERNAL_AcquireUniformBufferFromPool( metalCommandBuffer); @@ -2644,11 +2665,11 @@ static void METAL_INTERNAL_BindGraphicsResources( // Vertex Samplers+Textures if (commandBuffer->needVertexSamplerBind) { - if (graphicsPipeline->vertexSamplerCount > 0) { + if (graphicsPipeline->header.num_vertex_samplers > 0) { [commandBuffer->renderEncoder setVertexSamplerStates:commandBuffer->vertexSamplers - withRange:NSMakeRange(0, graphicsPipeline->vertexSamplerCount)]; + withRange:NSMakeRange(0, graphicsPipeline->header.num_vertex_samplers)]; [commandBuffer->renderEncoder setVertexTextures:commandBuffer->vertexTextures - withRange:NSMakeRange(0, graphicsPipeline->vertexSamplerCount)]; + withRange:NSMakeRange(0, graphicsPipeline->header.num_vertex_samplers)]; } commandBuffer->needVertexSamplerBind = false; } @@ -2656,10 +2677,10 @@ static void METAL_INTERNAL_BindGraphicsResources( // Vertex Storage Textures if (commandBuffer->needVertexStorageTextureBind) { - if (graphicsPipeline->vertexStorageTextureCount > 0) { + if (graphicsPipeline->header.num_vertex_storage_textures > 0) { [commandBuffer->renderEncoder setVertexTextures:commandBuffer->vertexStorageTextures - withRange:NSMakeRange(graphicsPipeline->vertexSamplerCount, - graphicsPipeline->vertexStorageTextureCount)]; + withRange:NSMakeRange(graphicsPipeline->header.num_vertex_samplers, + graphicsPipeline->header.num_vertex_storage_textures)]; } commandBuffer->needVertexStorageTextureBind = false; } @@ -2667,20 +2688,20 @@ static void METAL_INTERNAL_BindGraphicsResources( // Vertex Storage Buffers if (commandBuffer->needVertexStorageBufferBind) { - if (graphicsPipeline->vertexStorageBufferCount > 0) { + if (graphicsPipeline->header.num_vertex_storage_buffers > 0) { [commandBuffer->renderEncoder setVertexBuffers:commandBuffer->vertexStorageBuffers offsets:offsets - withRange:NSMakeRange(graphicsPipeline->vertexUniformBufferCount, - graphicsPipeline->vertexStorageBufferCount)]; + withRange:NSMakeRange(graphicsPipeline->header.num_vertex_uniform_buffers, + graphicsPipeline->header.num_vertex_storage_buffers)]; } commandBuffer->needVertexStorageBufferBind = false; } // Vertex Uniform Buffers - for (Uint32 i = 0; i < graphicsPipeline->vertexUniformBufferCount; i += 1) { + for (Uint32 i = 0; i < graphicsPipeline->header.num_vertex_uniform_buffers; i += 1) { if (commandBuffer->needVertexUniformBufferBind[i]) { - if (graphicsPipeline->vertexUniformBufferCount > i) { + if (graphicsPipeline->header.num_vertex_uniform_buffers > i) { [commandBuffer->renderEncoder setVertexBuffer:commandBuffer->vertexUniformBuffers[i]->handle offset:commandBuffer->vertexUniformBuffers[i]->drawOffset @@ -2693,11 +2714,11 @@ static void METAL_INTERNAL_BindGraphicsResources( // Fragment Samplers+Textures if (commandBuffer->needFragmentSamplerBind) { - if (graphicsPipeline->fragmentSamplerCount > 0) { + if (graphicsPipeline->header.num_fragment_samplers > 0) { [commandBuffer->renderEncoder setFragmentSamplerStates:commandBuffer->fragmentSamplers - withRange:NSMakeRange(0, graphicsPipeline->fragmentSamplerCount)]; + withRange:NSMakeRange(0, graphicsPipeline->header.num_fragment_samplers)]; [commandBuffer->renderEncoder setFragmentTextures:commandBuffer->fragmentTextures - withRange:NSMakeRange(0, graphicsPipeline->fragmentSamplerCount)]; + withRange:NSMakeRange(0, graphicsPipeline->header.num_fragment_samplers)]; } commandBuffer->needFragmentSamplerBind = false; } @@ -2705,10 +2726,10 @@ static void METAL_INTERNAL_BindGraphicsResources( // Fragment Storage Textures if (commandBuffer->needFragmentStorageTextureBind) { - if (graphicsPipeline->fragmentStorageTextureCount > 0) { + if (graphicsPipeline->header.num_fragment_storage_textures > 0) { [commandBuffer->renderEncoder setFragmentTextures:commandBuffer->fragmentStorageTextures - withRange:NSMakeRange(graphicsPipeline->fragmentSamplerCount, - graphicsPipeline->fragmentStorageTextureCount)]; + withRange:NSMakeRange(graphicsPipeline->header.num_fragment_samplers, + graphicsPipeline->header.num_fragment_storage_textures)]; } commandBuffer->needFragmentStorageTextureBind = false; } @@ -2716,20 +2737,20 @@ static void METAL_INTERNAL_BindGraphicsResources( // Fragment Storage Buffers if (commandBuffer->needFragmentStorageBufferBind) { - if (graphicsPipeline->fragmentStorageBufferCount > 0) { + if (graphicsPipeline->header.num_fragment_storage_buffers > 0) { [commandBuffer->renderEncoder setFragmentBuffers:commandBuffer->fragmentStorageBuffers offsets:offsets - withRange:NSMakeRange(graphicsPipeline->fragmentUniformBufferCount, - graphicsPipeline->fragmentStorageBufferCount)]; + withRange:NSMakeRange(graphicsPipeline->header.num_fragment_uniform_buffers, + graphicsPipeline->header.num_fragment_storage_buffers)]; } commandBuffer->needFragmentStorageBufferBind = false; } // Fragment Uniform Buffers - for (Uint32 i = 0; i < graphicsPipeline->fragmentUniformBufferCount; i += 1) { + for (Uint32 i = 0; i < graphicsPipeline->header.num_fragment_uniform_buffers; i += 1) { if (commandBuffer->needFragmentUniformBufferBind[i]) { - if (graphicsPipeline->fragmentUniformBufferCount > i) { + if (graphicsPipeline->header.num_fragment_uniform_buffers > i) { [commandBuffer->renderEncoder setFragmentBuffer:commandBuffer->fragmentUniformBuffers[i]->handle offset:commandBuffer->fragmentUniformBuffers[i]->drawOffset @@ -2748,38 +2769,38 @@ static void METAL_INTERNAL_BindComputeResources( NSUInteger offsets[MAX_STORAGE_BUFFERS_PER_STAGE] = { 0 }; if (commandBuffer->needComputeSamplerBind) { - if (computePipeline->numSamplers > 0) { + if (computePipeline->header.numSamplers > 0) { [commandBuffer->computeEncoder setTextures:commandBuffer->computeSamplerTextures - withRange:NSMakeRange(0, computePipeline->numSamplers)]; + withRange:NSMakeRange(0, computePipeline->header.numSamplers)]; [commandBuffer->computeEncoder setSamplerStates:commandBuffer->computeSamplers - withRange:NSMakeRange(0, computePipeline->numSamplers)]; + withRange:NSMakeRange(0, computePipeline->header.numSamplers)]; } commandBuffer->needComputeSamplerBind = false; } if (commandBuffer->needComputeReadOnlyStorageTextureBind) { - if (computePipeline->numReadonlyStorageTextures > 0) { + if (computePipeline->header.numReadonlyStorageTextures > 0) { [commandBuffer->computeEncoder setTextures:commandBuffer->computeReadOnlyTextures withRange:NSMakeRange( - computePipeline->numSamplers, - computePipeline->numReadonlyStorageTextures)]; + computePipeline->header.numSamplers, + computePipeline->header.numReadonlyStorageTextures)]; } commandBuffer->needComputeReadOnlyStorageTextureBind = false; } if (commandBuffer->needComputeReadOnlyStorageBufferBind) { - if (computePipeline->numReadonlyStorageBuffers > 0) { + if (computePipeline->header.numReadonlyStorageBuffers > 0) { [commandBuffer->computeEncoder setBuffers:commandBuffer->computeReadOnlyBuffers offsets:offsets - withRange:NSMakeRange(computePipeline->numUniformBuffers, - computePipeline->numReadonlyStorageBuffers)]; + withRange:NSMakeRange(computePipeline->header.numUniformBuffers, + computePipeline->header.numReadonlyStorageBuffers)]; } commandBuffer->needComputeReadOnlyStorageBufferBind = false; } for (Uint32 i = 0; i < MAX_UNIFORM_BUFFERS_PER_STAGE; i += 1) { if (commandBuffer->needComputeUniformBufferBind[i]) { - if (computePipeline->numUniformBuffers > i) { + if (computePipeline->header.numUniformBuffers > i) { [commandBuffer->computeEncoder setBuffer:commandBuffer->computeUniformBuffers[i]->handle offset:commandBuffer->computeUniformBuffers[i]->drawOffset @@ -3127,7 +3148,7 @@ static void METAL_BindComputePipeline( metalCommandBuffer->needComputeUniformBufferBind[i] = true; } - for (Uint32 i = 0; i < pipeline->numUniformBuffers; i += 1) { + for (Uint32 i = 0; i < pipeline->header.numUniformBuffers; i += 1) { if (metalCommandBuffer->computeUniformBuffers[i] == NULL) { metalCommandBuffer->computeUniformBuffers[i] = METAL_INTERNAL_AcquireUniformBufferFromPool( metalCommandBuffer); @@ -3135,22 +3156,22 @@ static void METAL_BindComputePipeline( } // Bind write-only resources - if (pipeline->numReadWriteStorageTextures > 0) { + if (pipeline->header.numReadWriteStorageTextures > 0) { [metalCommandBuffer->computeEncoder setTextures:metalCommandBuffer->computeReadWriteTextures withRange:NSMakeRange( - pipeline->numSamplers + - pipeline->numReadonlyStorageTextures, - pipeline->numReadWriteStorageTextures)]; + pipeline->header.numSamplers + + pipeline->header.numReadonlyStorageTextures, + pipeline->header.numReadWriteStorageTextures)]; } NSUInteger offsets[MAX_COMPUTE_WRITE_BUFFERS] = { 0 }; - if (pipeline->numReadWriteStorageBuffers > 0) { + if (pipeline->header.numReadWriteStorageBuffers > 0) { [metalCommandBuffer->computeEncoder setBuffers:metalCommandBuffer->computeReadWriteBuffers offsets:offsets withRange:NSMakeRange( - pipeline->numUniformBuffers + - pipeline->numReadonlyStorageBuffers, - pipeline->numReadWriteStorageBuffers)]; + pipeline->header.numUniformBuffers + + pipeline->header.numReadonlyStorageBuffers, + pipeline->header.numReadWriteStorageBuffers)]; } } } @@ -4274,8 +4295,13 @@ static bool METAL_SupportsTextureFormat( // Device Creation -static bool METAL_PrepareDriver(SDL_VideoDevice *this) +static bool METAL_PrepareDriver(SDL_VideoDevice *this, SDL_PropertiesID props) { + if (!SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_SHADERS_MSL_BOOLEAN, false) && + !SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_SHADERS_METALLIB_BOOLEAN, false)) { + return false; + } + if (@available(macOS 10.14, iOS 13.0, tvOS 13.0, *)) { return (this->Metal_CreateView != NULL); } @@ -4440,6 +4466,11 @@ static SDL_GPUDevice *METAL_CreateDevice(bool debugMode, bool preferLowPower, SD id device = NULL; bool hasHardwareSupport = false; + bool verboseLogs = SDL_GetBooleanProperty( + props, + SDL_PROP_GPU_DEVICE_CREATE_VERBOSE_BOOLEAN, + true); + if (debugMode) { /* Due to a Metal driver quirk, once a MTLDevice has been created * with this environment variable set, the Metal validation layers @@ -4493,12 +4524,20 @@ static SDL_GPUDevice *METAL_CreateDevice(bool debugMode, bool preferLowPower, SD renderer->device = device; renderer->queue = [device newCommandQueue]; - // Print driver info - SDL_LogInfo(SDL_LOG_CATEGORY_GPU, "SDL_GPU Driver: Metal"); - SDL_LogInfo( - SDL_LOG_CATEGORY_GPU, - "Metal Device: %s", - [device.name UTF8String]); + renderer->props = SDL_CreateProperties(); + if (verboseLogs) { + SDL_LogInfo(SDL_LOG_CATEGORY_GPU, "SDL_GPU Driver: Metal"); + } + + // Record device name + const char *deviceName = [device.name UTF8String]; + SDL_SetStringProperty( + renderer->props, + SDL_PROP_GPU_DEVICE_NAME_STRING, + deviceName); + if (verboseLogs) { + SDL_LogInfo(SDL_LOG_CATEGORY_GPU, "Metal Device: %s", deviceName); + } // Remember debug mode renderer->debugMode = debugMode; @@ -4564,6 +4603,7 @@ static SDL_GPUDevice *METAL_CreateDevice(bool debugMode, bool preferLowPower, SD SDL_GPUDevice *result = SDL_calloc(1, sizeof(SDL_GPUDevice)); ASSIGN_DRIVER(METAL) result->driverData = (SDL_GPURenderer *)renderer; + result->shader_formats = SDL_GPU_SHADERFORMAT_MSL | SDL_GPU_SHADERFORMAT_METALLIB; renderer->sdlGPUDevice = result; return result; @@ -4572,7 +4612,6 @@ static SDL_GPUDevice *METAL_CreateDevice(bool debugMode, bool preferLowPower, SD SDL_GPUBootstrap MetalDriver = { "metal", - SDL_GPU_SHADERFORMAT_MSL | SDL_GPU_SHADERFORMAT_METALLIB, METAL_PrepareDriver, METAL_CreateDevice }; diff --git a/libs/SDL3/src/gpu/vulkan/SDL_gpu_vulkan.c b/libs/SDL3/src/gpu/vulkan/SDL_gpu_vulkan.c index 7ea10367..068d6820 100644 --- a/libs/SDL3/src/gpu/vulkan/SDL_gpu_vulkan.c +++ b/libs/SDL3/src/gpu/vulkan/SDL_gpu_vulkan.c @@ -33,8 +33,6 @@ #include "../SDL_sysgpu.h" -#define VULKAN_INTERNAL_clamp(val, min, max) SDL_max(min, SDL_min(val, max)) - // Global Vulkan Loader Entry Points static PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr = NULL; @@ -79,33 +77,6 @@ typedef struct VulkanExtensions VK_COMPONENT_SWIZZLE_IDENTITY \ } -#define NULL_DESC_LAYOUT (VkDescriptorSetLayout)0 -#define NULL_PIPELINE_LAYOUT (VkPipelineLayout)0 -#define NULL_RENDER_PASS (SDL_GPURenderPass *)0 - -#define EXPAND_ELEMENTS_IF_NEEDED(arr, initialValue, type) \ - do { \ - if (arr->count == arr->capacity) { \ - if (arr->capacity == 0) { \ - arr->capacity = initialValue; \ - } else { \ - arr->capacity *= 2; \ - } \ - arr->elements = (type *)SDL_realloc( \ - arr->elements, \ - arr->capacity * sizeof(type)); \ - } \ - } while (0) - -#define MOVE_ARRAY_CONTENTS_AND_RESET(i, dstArr, dstCount, srcArr, srcCount) \ - do { \ - for ((i) = 0; (i) < (srcCount); (i) += 1) { \ - (dstArr)[i] = (srcArr)[i]; \ - } \ - (dstCount) = (srcCount); \ - (srcCount) = 0; \ - while (0) - // Conversions static const Uint8 DEVICE_PRIORITY_HIGHPERFORMANCE[] = { @@ -606,7 +577,7 @@ typedef struct VulkanSampler typedef struct VulkanShader { VkShaderModule shaderModule; - const char *entrypointName; + char *entrypointName; SDL_GPUShaderStage stage; Uint32 numSamplers; Uint32 numStorageTextures; @@ -732,7 +703,7 @@ typedef struct WindowData // Synchronization primitives VkSemaphore imageAvailableSemaphore[MAX_FRAMES_IN_FLIGHT]; - VkSemaphore renderFinishedSemaphore[MAX_FRAMES_IN_FLIGHT]; + VkSemaphore *renderFinishedSemaphore; SDL_GPUFence *inFlightFences[MAX_FRAMES_IN_FLIGHT]; Uint32 frameCounter; @@ -822,13 +793,13 @@ typedef struct DescriptorSetLayout typedef struct GraphicsPipelineResourceLayoutHashTableKey { Uint32 vertexSamplerCount; - Uint32 vertexStorageBufferCount; Uint32 vertexStorageTextureCount; + Uint32 vertexStorageBufferCount; Uint32 vertexUniformBufferCount; Uint32 fragmentSamplerCount; - Uint32 fragmentStorageBufferCount; Uint32 fragmentStorageTextureCount; + Uint32 fragmentStorageBufferCount; Uint32 fragmentUniformBufferCount; } GraphicsPipelineResourceLayoutHashTableKey; @@ -846,18 +817,20 @@ typedef struct VulkanGraphicsPipelineResourceLayout DescriptorSetLayout *descriptorSetLayouts[4]; Uint32 vertexSamplerCount; - Uint32 vertexStorageBufferCount; Uint32 vertexStorageTextureCount; + Uint32 vertexStorageBufferCount; Uint32 vertexUniformBufferCount; Uint32 fragmentSamplerCount; - Uint32 fragmentStorageBufferCount; Uint32 fragmentStorageTextureCount; + Uint32 fragmentStorageBufferCount; Uint32 fragmentUniformBufferCount; } VulkanGraphicsPipelineResourceLayout; typedef struct VulkanGraphicsPipeline { + GraphicsPipelineCommonHeader header; + VkPipeline pipeline; SDL_GPUPrimitiveType primitiveType; @@ -901,6 +874,8 @@ typedef struct VulkanComputePipelineResourceLayout typedef struct VulkanComputePipeline { + ComputePipelineCommonHeader header; + VkShaderModule shaderModule; VkPipeline pipeline; VulkanComputePipelineResourceLayout *resourceLayout; @@ -1038,25 +1013,33 @@ typedef struct VulkanCommandBuffer Uint32 vertexBufferCount; bool needVertexBufferBind; - VulkanTexture *vertexSamplerTextures[MAX_TEXTURE_SAMPLERS_PER_STAGE]; - VulkanSampler *vertexSamplers[MAX_TEXTURE_SAMPLERS_PER_STAGE]; - VulkanTexture *vertexStorageTextures[MAX_STORAGE_TEXTURES_PER_STAGE]; - VulkanBuffer *vertexStorageBuffers[MAX_STORAGE_BUFFERS_PER_STAGE]; + VkImageView vertexSamplerTextureViewBindings[MAX_TEXTURE_SAMPLERS_PER_STAGE]; + VkSampler vertexSamplerBindings[MAX_TEXTURE_SAMPLERS_PER_STAGE]; + VkImageView vertexStorageTextureViewBindings[MAX_STORAGE_TEXTURES_PER_STAGE]; + VkBuffer vertexStorageBufferBindings[MAX_STORAGE_BUFFERS_PER_STAGE]; - VulkanTexture *fragmentSamplerTextures[MAX_TEXTURE_SAMPLERS_PER_STAGE]; - VulkanSampler *fragmentSamplers[MAX_TEXTURE_SAMPLERS_PER_STAGE]; - VulkanTexture *fragmentStorageTextures[MAX_STORAGE_TEXTURES_PER_STAGE]; - VulkanBuffer *fragmentStorageBuffers[MAX_STORAGE_BUFFERS_PER_STAGE]; + VkImageView fragmentSamplerTextureViewBindings[MAX_TEXTURE_SAMPLERS_PER_STAGE]; + VkSampler fragmentSamplerBindings[MAX_TEXTURE_SAMPLERS_PER_STAGE]; + VkImageView fragmentStorageTextureViewBindings[MAX_STORAGE_TEXTURES_PER_STAGE]; + VkBuffer fragmentStorageBufferBindings[MAX_STORAGE_BUFFERS_PER_STAGE]; + VkImageView computeSamplerTextureViewBindings[MAX_TEXTURE_SAMPLERS_PER_STAGE]; + VkSampler computeSamplerBindings[MAX_TEXTURE_SAMPLERS_PER_STAGE]; + VkImageView readOnlyComputeStorageTextureViewBindings[MAX_STORAGE_TEXTURES_PER_STAGE]; + VkBuffer readOnlyComputeStorageBufferBindings[MAX_STORAGE_BUFFERS_PER_STAGE]; + + // Track these separately because barriers can happen mid compute pass + VulkanTexture *readOnlyComputeStorageTextures[MAX_STORAGE_TEXTURES_PER_STAGE]; + VulkanBuffer *readOnlyComputeStorageBuffers[MAX_STORAGE_BUFFERS_PER_STAGE]; + + VkImageView readWriteComputeStorageTextureViewBindings[MAX_COMPUTE_WRITE_TEXTURES]; + VkBuffer readWriteComputeStorageBufferBindings[MAX_COMPUTE_WRITE_BUFFERS]; + + // Track these separately because they are barriered when the compute pass begins VulkanTextureSubresource *readWriteComputeStorageTextureSubresources[MAX_COMPUTE_WRITE_TEXTURES]; Uint32 readWriteComputeStorageTextureSubresourceCount; VulkanBuffer *readWriteComputeStorageBuffers[MAX_COMPUTE_WRITE_BUFFERS]; - VulkanTexture *computeSamplerTextures[MAX_TEXTURE_SAMPLERS_PER_STAGE]; - VulkanSampler *computeSamplers[MAX_TEXTURE_SAMPLERS_PER_STAGE]; - VulkanTexture *readOnlyComputeStorageTextures[MAX_STORAGE_TEXTURES_PER_STAGE]; - VulkanBuffer *readOnlyComputeStorageBuffers[MAX_STORAGE_BUFFERS_PER_STAGE]; - // Uniform buffers VulkanUniformBuffer *vertexUniformBuffers[MAX_UNIFORM_BUFFERS_PER_STAGE]; @@ -1117,6 +1100,7 @@ struct VulkanRenderer VkPhysicalDevice physicalDevice; VkPhysicalDeviceProperties2KHR physicalDeviceProperties; VkPhysicalDeviceDriverPropertiesKHR physicalDeviceDriverProperties; + VkPhysicalDeviceFeatures desiredDeviceFeatures; VkDevice logicalDevice; Uint8 integratedMemoryNotification; Uint8 outOfDeviceLocalMemoryWarning; @@ -1125,6 +1109,7 @@ struct VulkanRenderer bool debugMode; bool preferLowPower; + SDL_PropertiesID props; Uint32 allowedFramesInFlight; VulkanExtensions supports; @@ -1206,6 +1191,9 @@ struct VulkanRenderer SDL_Mutex *acquireUniformBufferLock; SDL_Mutex *renderPassFetchLock; SDL_Mutex *framebufferFetchLock; + SDL_Mutex *graphicsPipelineLayoutFetchLock; + SDL_Mutex *computePipelineLayoutFetchLock; + SDL_Mutex *descriptorSetLayoutFetchLock; SDL_Mutex *windowLock; Uint8 defragInProgress; @@ -1223,7 +1211,7 @@ struct VulkanRenderer // Forward declarations -static bool VULKAN_INTERNAL_DefragmentMemory(VulkanRenderer *renderer); +static bool VULKAN_INTERNAL_DefragmentMemory(VulkanRenderer *renderer, VulkanCommandBuffer *commandBuffer); static bool VULKAN_INTERNAL_BeginCommandBuffer(VulkanRenderer *renderer, VulkanCommandBuffer *commandBuffer); static void VULKAN_ReleaseWindow(SDL_GPURenderer *driverData, SDL_Window *window); static bool VULKAN_Wait(SDL_GPURenderer *driverData); @@ -1255,6 +1243,7 @@ static inline const char *VkErrorMessages(VkResult code) ERR_TO_STR(VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT) ERR_TO_STR(VK_SUBOPTIMAL_KHR) ERR_TO_STR(VK_ERROR_NATIVE_WINDOW_IN_USE_KHR) + ERR_TO_STR(VK_ERROR_INVALID_SHADER_NV) default: return "Unhandled VkResult!"; } @@ -1323,7 +1312,6 @@ static inline Uint32 VULKAN_INTERNAL_NextHighestAlignment32( } static void VULKAN_INTERNAL_MakeMemoryUnavailable( - VulkanRenderer *renderer, VulkanMemoryAllocation *allocation) { Uint32 i, j; @@ -1373,7 +1361,6 @@ static void VULKAN_INTERNAL_MarkAllocationsForDefrag( renderer->allocationsToDefragCount += 1; VULKAN_INTERNAL_MakeMemoryUnavailable( - renderer, currentAllocator->allocations[allocationIndex]); } } @@ -1793,8 +1780,6 @@ static void VULKAN_INTERNAL_DeallocateMemory( static Uint8 VULKAN_INTERNAL_AllocateMemory( VulkanRenderer *renderer, - VkBuffer buffer, - VkImage image, Uint32 memoryTypeIndex, VkDeviceSize allocationSize, Uint8 isHostVisible, @@ -2075,8 +2060,6 @@ static Uint8 VULKAN_INTERNAL_BindResourceMemory( allocationResult = VULKAN_INTERNAL_AllocateMemory( renderer, - buffer, - image, memoryTypeIndex, allocationSize, isHostVisible, @@ -2373,24 +2356,6 @@ static Uint8 VULKAN_INTERNAL_BindMemoryForBuffer( // Resource tracking -#define ADD_TO_ARRAY_UNIQUE(resource, type, array, count, capacity) \ - Uint32 i; \ - \ - for (i = 0; i < commandBuffer->count; i += 1) { \ - if (commandBuffer->array[i] == resource) { \ - return; \ - } \ - } \ - \ - if (commandBuffer->count == commandBuffer->capacity) { \ - commandBuffer->capacity += 1; \ - commandBuffer->array = SDL_realloc( \ - commandBuffer->array, \ - commandBuffer->capacity * sizeof(type)); \ - } \ - commandBuffer->array[commandBuffer->count] = resource; \ - commandBuffer->count += 1; - #define TRACK_RESOURCE(resource, type, array, count, capacity) \ for (Sint32 i = commandBuffer->count - 1; i >= 0; i -= 1) { \ if (commandBuffer->array[i] == resource) { \ @@ -2469,7 +2434,6 @@ static void VULKAN_INTERNAL_TrackComputePipeline( } static void VULKAN_INTERNAL_TrackFramebuffer( - VulkanRenderer *renderer, VulkanCommandBuffer *commandBuffer, VulkanFramebuffer *framebuffer) { @@ -3175,7 +3139,7 @@ static void VULKAN_INTERNAL_DestroyShader( vulkanShader->shaderModule, NULL); - SDL_free((void *)vulkanShader->entrypointName); + SDL_free(vulkanShader->entrypointName); SDL_free(vulkanShader); } @@ -3213,7 +3177,6 @@ static void VULKAN_INTERNAL_DestroySwapchain( SDL_free(windowData->textureContainers[i].activeTexture->subresources); SDL_free(windowData->textureContainers[i].activeTexture); } - windowData->imageCount = 0; SDL_free(windowData->textureContainers); windowData->textureContainers = NULL; @@ -3242,7 +3205,8 @@ static void VULKAN_INTERNAL_DestroySwapchain( NULL); windowData->imageAvailableSemaphore[i] = VK_NULL_HANDLE; } - + } + for (i = 0; i < windowData->imageCount; i += 1) { if (windowData->renderFinishedSemaphore[i]) { renderer->vkDestroySemaphore( renderer->logicalDevice, @@ -3251,6 +3215,10 @@ static void VULKAN_INTERNAL_DestroySwapchain( windowData->renderFinishedSemaphore[i] = VK_NULL_HANDLE; } } + SDL_free(windowData->renderFinishedSemaphore); + windowData->renderFinishedSemaphore = NULL; + + windowData->imageCount = 0; } static void VULKAN_INTERNAL_DestroyGraphicsPipelineResourceLayout( @@ -3329,7 +3297,7 @@ static void SDLCALL VULKAN_INTERNAL_GraphicsPipelineResourceLayoutHashDestroy(vo VulkanRenderer *renderer = (VulkanRenderer *)userdata; VulkanGraphicsPipelineResourceLayout *resourceLayout = (VulkanGraphicsPipelineResourceLayout *)value; VULKAN_INTERNAL_DestroyGraphicsPipelineResourceLayout(renderer, resourceLayout); - SDL_free((void*)key); + SDL_free((void *)key); } static Uint32 SDLCALL VULKAN_INTERNAL_ComputePipelineResourceLayoutHashFunction(void *userdata, const void *key) @@ -3360,7 +3328,7 @@ static void SDLCALL VULKAN_INTERNAL_ComputePipelineResourceLayoutHashDestroy(voi VulkanRenderer *renderer = (VulkanRenderer *)userdata; VulkanComputePipelineResourceLayout *resourceLayout = (VulkanComputePipelineResourceLayout *)value; VULKAN_INTERNAL_DestroyComputePipelineResourceLayout(renderer, resourceLayout); - SDL_free((void*)key); + SDL_free((void *)key); } static Uint32 SDLCALL VULKAN_INTERNAL_DescriptorSetLayoutHashFunction(void *userdata, const void *key) @@ -3393,7 +3361,7 @@ static void SDLCALL VULKAN_INTERNAL_DescriptorSetLayoutHashDestroy(void *userdat VulkanRenderer *renderer = (VulkanRenderer *)userdata; DescriptorSetLayout *layout = (DescriptorSetLayout *)value; VULKAN_INTERNAL_DestroyDescriptorSetLayout(renderer, layout); - SDL_free((void*)key); + SDL_free((void *)key); } static Uint32 SDLCALL VULKAN_INTERNAL_CommandPoolHashFunction(void *userdata, const void *key) @@ -3750,10 +3718,13 @@ static DescriptorSetLayout *VULKAN_INTERNAL_FetchDescriptorSetLayout( key.writeStorageBufferCount = writeStorageBufferCount; key.uniformBufferCount = uniformBufferCount; + SDL_LockMutex(renderer->descriptorSetLayoutFetchLock); + if (SDL_FindInHashTable( renderer->descriptorSetLayoutHashTable, (const void *)&key, (const void **)&layout)) { + SDL_UnlockMutex(renderer->descriptorSetLayoutFetchLock); return layout; } @@ -3836,7 +3807,10 @@ static DescriptorSetLayout *VULKAN_INTERNAL_FetchDescriptorSetLayout( NULL, &descriptorSetLayout); - CHECK_VULKAN_ERROR_AND_RETURN(vulkanResult, vkCreateDescriptorSetLayout, NULL); + if (vulkanResult != VK_SUCCESS) { + SDL_UnlockMutex(renderer->descriptorSetLayoutFetchLock); + CHECK_VULKAN_ERROR_AND_RETURN(vulkanResult, vkCreateDescriptorSetLayout, NULL); + } layout = SDL_malloc(sizeof(DescriptorSetLayout)); layout->descriptorSetLayout = descriptorSetLayout; @@ -3858,6 +3832,7 @@ static DescriptorSetLayout *VULKAN_INTERNAL_FetchDescriptorSetLayout( (const void *)allocedKey, (const void *)layout, true); + SDL_UnlockMutex(renderer->descriptorSetLayoutFetchLock); return layout; } @@ -3878,10 +3853,14 @@ static VulkanGraphicsPipelineResourceLayout *VULKAN_INTERNAL_FetchGraphicsPipeli key.fragmentStorageTextureCount = fragmentShader->numStorageTextures; key.fragmentStorageBufferCount = fragmentShader->numStorageBuffers; key.fragmentUniformBufferCount = fragmentShader->numUniformBuffers; + + SDL_LockMutex(renderer->graphicsPipelineLayoutFetchLock); + if (SDL_FindInHashTable( renderer->graphicsPipelineResourceLayoutHashTable, (const void *)&key, (const void **)&pipelineResourceLayout)) { + SDL_UnlockMutex(renderer->graphicsPipelineLayoutFetchLock); return pipelineResourceLayout; } @@ -3964,6 +3943,7 @@ static VulkanGraphicsPipelineResourceLayout *VULKAN_INTERNAL_FetchGraphicsPipeli if (vulkanResult != VK_SUCCESS) { VULKAN_INTERNAL_DestroyGraphicsPipelineResourceLayout(renderer, pipelineResourceLayout); + SDL_UnlockMutex(renderer->graphicsPipelineLayoutFetchLock); CHECK_VULKAN_ERROR_AND_RETURN(vulkanResult, vkCreatePipelineLayout, NULL); } @@ -3975,6 +3955,7 @@ static VulkanGraphicsPipelineResourceLayout *VULKAN_INTERNAL_FetchGraphicsPipeli (const void *)allocedKey, (const void *)pipelineResourceLayout, true); + SDL_UnlockMutex(renderer->graphicsPipelineLayoutFetchLock); return pipelineResourceLayout; } @@ -3993,10 +3974,13 @@ static VulkanComputePipelineResourceLayout *VULKAN_INTERNAL_FetchComputePipeline key.readWriteStorageBufferCount = createinfo->num_readwrite_storage_buffers; key.uniformBufferCount = createinfo->num_uniform_buffers; + SDL_LockMutex(renderer->computePipelineLayoutFetchLock); + if (SDL_FindInHashTable( renderer->computePipelineResourceLayoutHashTable, (const void *)&key, (const void **)&pipelineResourceLayout)) { + SDL_UnlockMutex(renderer->computePipelineLayoutFetchLock); return pipelineResourceLayout; } @@ -4065,6 +4049,7 @@ static VulkanComputePipelineResourceLayout *VULKAN_INTERNAL_FetchComputePipeline if (vulkanResult != VK_SUCCESS) { VULKAN_INTERNAL_DestroyComputePipelineResourceLayout(renderer, pipelineResourceLayout); + SDL_UnlockMutex(renderer->computePipelineLayoutFetchLock); CHECK_VULKAN_ERROR_AND_RETURN(vulkanResult, vkCreatePipelineLayout, NULL); } @@ -4076,6 +4061,7 @@ static VulkanComputePipelineResourceLayout *VULKAN_INTERNAL_FetchComputePipeline (const void *)allocedKey, (const void *)pipelineResourceLayout, true); + SDL_UnlockMutex(renderer->computePipelineLayoutFetchLock); return pipelineResourceLayout; } @@ -4484,6 +4470,7 @@ static Uint32 VULKAN_INTERNAL_CreateSwapchain( VkSemaphoreCreateInfo semaphoreCreateInfo; SwapchainSupportDetails swapchainSupportDetails; bool hasValidSwapchainComposition, hasValidPresentMode; + VkCompositeAlphaFlagsKHR compositeAlphaFlag = 0; Uint32 i; windowData->frameCounter = 0; @@ -4624,6 +4611,25 @@ static Uint32 VULKAN_INTERNAL_CreateSwapchain( requestedImageCount = SDL_max(requestedImageCount, 3); } + // Default to opaque, if available, followed by inherit, and overwrite with a value that supports transparency, if necessary. + if (swapchainSupportDetails.capabilities.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR) { + compositeAlphaFlag = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; + } else if (swapchainSupportDetails.capabilities.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR) { + compositeAlphaFlag = VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR; + } + + if ((windowData->window->flags & SDL_WINDOW_TRANSPARENT) || !compositeAlphaFlag) { + if (swapchainSupportDetails.capabilities.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR) { + compositeAlphaFlag = VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR; + } else if (swapchainSupportDetails.capabilities.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR) { + compositeAlphaFlag = VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR; + } else if (swapchainSupportDetails.capabilities.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR) { + compositeAlphaFlag = VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR; + } else { + SDL_LogWarn(SDL_LOG_CATEGORY_GPU, "SDL_WINDOW_TRANSPARENT flag set, but no suitable swapchain composite alpha value supported!"); + } + } + swapchainCreateInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; swapchainCreateInfo.pNext = NULL; swapchainCreateInfo.flags = 0; @@ -4645,7 +4651,7 @@ static Uint32 VULKAN_INTERNAL_CreateSwapchain( #else swapchainCreateInfo.preTransform = swapchainSupportDetails.capabilities.currentTransform; #endif - swapchainCreateInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; + swapchainCreateInfo.compositeAlpha = compositeAlphaFlag; swapchainCreateInfo.presentMode = SDLToVK_PresentMode[windowData->presentMode]; swapchainCreateInfo.clipped = VK_TRUE; swapchainCreateInfo.oldSwapchain = VK_NULL_HANDLE; @@ -4790,6 +4796,12 @@ static Uint32 VULKAN_INTERNAL_CreateSwapchain( CHECK_VULKAN_ERROR_AND_RETURN(vulkanResult, vkCreateSemaphore, false); } + windowData->inFlightFences[i] = NULL; + } + + windowData->renderFinishedSemaphore = SDL_malloc( + sizeof(VkSemaphore) * windowData->imageCount); + for (i = 0; i < windowData->imageCount; i += 1) { vulkanResult = renderer->vkCreateSemaphore( renderer->logicalDevice, &semaphoreCreateInfo, @@ -4809,8 +4821,6 @@ static Uint32 VULKAN_INTERNAL_CreateSwapchain( windowData->swapchain = VK_NULL_HANDLE; CHECK_VULKAN_ERROR_AND_RETURN(vulkanResult, vkCreateSemaphore, false); } - - windowData->inFlightFences[i] = NULL; } windowData->needsSwapchainRecreate = false; @@ -4946,16 +4956,28 @@ static void VULKAN_DestroyDevice( SDL_DestroyMutex(renderer->acquireUniformBufferLock); SDL_DestroyMutex(renderer->renderPassFetchLock); SDL_DestroyMutex(renderer->framebufferFetchLock); + SDL_DestroyMutex(renderer->graphicsPipelineLayoutFetchLock); + SDL_DestroyMutex(renderer->computePipelineLayoutFetchLock); + SDL_DestroyMutex(renderer->descriptorSetLayoutFetchLock); SDL_DestroyMutex(renderer->windowLock); renderer->vkDestroyDevice(renderer->logicalDevice, NULL); renderer->vkDestroyInstance(renderer->instance, NULL); + SDL_DestroyProperties(renderer->props); + SDL_free(renderer); SDL_free(device); SDL_Vulkan_UnloadLibrary(); } +static SDL_PropertiesID VULKAN_GetDeviceProperties( + SDL_GPUDevice *device) +{ + VulkanRenderer *renderer = (VulkanRenderer *)device->driverData; + return renderer->props; +} + static DescriptorSetCache *VULKAN_INTERNAL_AcquireDescriptorSetCache( VulkanRenderer *renderer) { @@ -5093,8 +5115,8 @@ static void VULKAN_INTERNAL_BindGraphicsDescriptorSets( currentWriteDescriptorSet->pTexelBufferView = NULL; currentWriteDescriptorSet->pBufferInfo = NULL; - imageInfos[imageInfoCount].sampler = commandBuffer->vertexSamplers[i]->sampler; - imageInfos[imageInfoCount].imageView = commandBuffer->vertexSamplerTextures[i]->fullView; + imageInfos[imageInfoCount].sampler = commandBuffer->vertexSamplerBindings[i]; + imageInfos[imageInfoCount].imageView = commandBuffer->vertexSamplerTextureViewBindings[i]; imageInfos[imageInfoCount].imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; currentWriteDescriptorSet->pImageInfo = &imageInfos[imageInfoCount]; @@ -5117,7 +5139,7 @@ static void VULKAN_INTERNAL_BindGraphicsDescriptorSets( currentWriteDescriptorSet->pBufferInfo = NULL; imageInfos[imageInfoCount].sampler = VK_NULL_HANDLE; - imageInfos[imageInfoCount].imageView = commandBuffer->vertexStorageTextures[i]->fullView; + imageInfos[imageInfoCount].imageView = commandBuffer->vertexStorageTextureViewBindings[i]; imageInfos[imageInfoCount].imageLayout = VK_IMAGE_LAYOUT_GENERAL; currentWriteDescriptorSet->pImageInfo = &imageInfos[imageInfoCount]; @@ -5139,7 +5161,7 @@ static void VULKAN_INTERNAL_BindGraphicsDescriptorSets( currentWriteDescriptorSet->pTexelBufferView = NULL; currentWriteDescriptorSet->pImageInfo = NULL; - bufferInfos[bufferInfoCount].buffer = commandBuffer->vertexStorageBuffers[i]->buffer; + bufferInfos[bufferInfoCount].buffer = commandBuffer->vertexStorageBufferBindings[i]; bufferInfos[bufferInfoCount].offset = 0; bufferInfos[bufferInfoCount].range = VK_WHOLE_SIZE; @@ -5212,8 +5234,8 @@ static void VULKAN_INTERNAL_BindGraphicsDescriptorSets( currentWriteDescriptorSet->pTexelBufferView = NULL; currentWriteDescriptorSet->pBufferInfo = NULL; - imageInfos[imageInfoCount].sampler = commandBuffer->fragmentSamplers[i]->sampler; - imageInfos[imageInfoCount].imageView = commandBuffer->fragmentSamplerTextures[i]->fullView; + imageInfos[imageInfoCount].sampler = commandBuffer->fragmentSamplerBindings[i]; + imageInfos[imageInfoCount].imageView = commandBuffer->fragmentSamplerTextureViewBindings[i]; imageInfos[imageInfoCount].imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; currentWriteDescriptorSet->pImageInfo = &imageInfos[imageInfoCount]; @@ -5236,7 +5258,7 @@ static void VULKAN_INTERNAL_BindGraphicsDescriptorSets( currentWriteDescriptorSet->pBufferInfo = NULL; imageInfos[imageInfoCount].sampler = VK_NULL_HANDLE; - imageInfos[imageInfoCount].imageView = commandBuffer->fragmentStorageTextures[i]->fullView; + imageInfos[imageInfoCount].imageView = commandBuffer->fragmentStorageTextureViewBindings[i]; imageInfos[imageInfoCount].imageLayout = VK_IMAGE_LAYOUT_GENERAL; currentWriteDescriptorSet->pImageInfo = &imageInfos[imageInfoCount]; @@ -5258,7 +5280,7 @@ static void VULKAN_INTERNAL_BindGraphicsDescriptorSets( currentWriteDescriptorSet->pTexelBufferView = NULL; currentWriteDescriptorSet->pImageInfo = NULL; - bufferInfos[bufferInfoCount].buffer = commandBuffer->fragmentStorageBuffers[i]->buffer; + bufferInfos[bufferInfoCount].buffer = commandBuffer->fragmentStorageBufferBindings[i]; bufferInfos[bufferInfoCount].offset = 0; bufferInfos[bufferInfoCount].range = VK_WHOLE_SIZE; @@ -5602,6 +5624,7 @@ static void VULKAN_PopDebugGroup( static VulkanTexture *VULKAN_INTERNAL_CreateTexture( VulkanRenderer *renderer, + bool transitionToDefaultLayout, const SDL_GPUTextureCreateInfo *createinfo) { VkResult vulkanResult; @@ -5829,15 +5852,17 @@ static VulkanTexture *VULKAN_INTERNAL_CreateTexture( &nameInfo); } - // Let's transition to the default barrier state, because for some reason Vulkan doesn't let us do that with initialLayout. - VulkanCommandBuffer *barrierCommandBuffer = (VulkanCommandBuffer *)VULKAN_AcquireCommandBuffer((SDL_GPURenderer *)renderer); - VULKAN_INTERNAL_TextureTransitionToDefaultUsage( - renderer, - barrierCommandBuffer, - VULKAN_TEXTURE_USAGE_MODE_UNINITIALIZED, - texture); - VULKAN_INTERNAL_TrackTexture(barrierCommandBuffer, texture); - VULKAN_Submit((SDL_GPUCommandBuffer *)barrierCommandBuffer); + if (transitionToDefaultLayout) { + // Let's transition to the default barrier state, because for some reason Vulkan doesn't let us do that with initialLayout. + VulkanCommandBuffer *barrierCommandBuffer = (VulkanCommandBuffer *)VULKAN_AcquireCommandBuffer((SDL_GPURenderer *)renderer); + VULKAN_INTERNAL_TextureTransitionToDefaultUsage( + renderer, + barrierCommandBuffer, + VULKAN_TEXTURE_USAGE_MODE_UNINITIALIZED, + texture); + VULKAN_INTERNAL_TrackTexture(barrierCommandBuffer, texture); + VULKAN_Submit((SDL_GPUCommandBuffer *)barrierCommandBuffer); + } return texture; } @@ -5887,6 +5912,7 @@ static void VULKAN_INTERNAL_CycleActiveBuffer( static void VULKAN_INTERNAL_CycleActiveTexture( VulkanRenderer *renderer, + VulkanCommandBuffer *commandBuffer, VulkanTextureContainer *container) { VulkanTexture *texture; @@ -5904,8 +5930,15 @@ static void VULKAN_INTERNAL_CycleActiveTexture( // No texture is available, generate a new one. texture = VULKAN_INTERNAL_CreateTexture( renderer, + false, &container->header.info); + VULKAN_INTERNAL_TextureTransitionToDefaultUsage( + renderer, + commandBuffer, + VULKAN_TEXTURE_USAGE_MODE_UNINITIALIZED, + texture); + if (!texture) { return; } @@ -5969,6 +6002,7 @@ static VulkanTextureSubresource *VULKAN_INTERNAL_PrepareTextureSubresourceForWri SDL_GetAtomicInt(&textureContainer->activeTexture->referenceCount) > 0) { VULKAN_INTERNAL_CycleActiveTexture( renderer, + commandBuffer, textureContainer); textureSubresource = VULKAN_INTERNAL_FetchTextureSubresource( @@ -5989,7 +6023,6 @@ static VulkanTextureSubresource *VULKAN_INTERNAL_PrepareTextureSubresourceForWri static VkRenderPass VULKAN_INTERNAL_CreateRenderPass( VulkanRenderer *renderer, - VulkanCommandBuffer *commandBuffer, const SDL_GPUColorTargetInfo *colorTargetInfos, Uint32 numColorTargets, const SDL_GPUDepthStencilTargetInfo *depthStencilTargetInfo) @@ -6367,7 +6400,7 @@ static SDL_GPUGraphicsPipeline *VULKAN_CreateGraphicsPipeline( multisampleStateCreateInfo.sampleShadingEnable = VK_FALSE; multisampleStateCreateInfo.minSampleShading = 1.0f; multisampleStateCreateInfo.pSampleMask = &sampleMask; - multisampleStateCreateInfo.alphaToCoverageEnable = VK_FALSE; + multisampleStateCreateInfo.alphaToCoverageEnable = createinfo->multisample_state.enable_alpha_to_coverage; multisampleStateCreateInfo.alphaToOneEnable = VK_FALSE; // Depth Stencil State @@ -6520,9 +6553,38 @@ static SDL_GPUGraphicsPipeline *VULKAN_CreateGraphicsPipeline( &nameInfo); } + // Put this data in the pipeline we can do validation in gpu.c + graphicsPipeline->header.num_vertex_samplers = graphicsPipeline->resourceLayout->vertexSamplerCount; + graphicsPipeline->header.num_vertex_storage_buffers = graphicsPipeline->resourceLayout->vertexStorageBufferCount; + graphicsPipeline->header.num_vertex_storage_textures = graphicsPipeline->resourceLayout->vertexStorageTextureCount; + graphicsPipeline->header.num_vertex_uniform_buffers = graphicsPipeline->resourceLayout->vertexUniformBufferCount; + graphicsPipeline->header.num_fragment_samplers = graphicsPipeline->resourceLayout->fragmentSamplerCount; + graphicsPipeline->header.num_fragment_storage_buffers = graphicsPipeline->resourceLayout->fragmentStorageBufferCount; + graphicsPipeline->header.num_fragment_storage_textures = graphicsPipeline->resourceLayout->fragmentStorageTextureCount; + graphicsPipeline->header.num_fragment_uniform_buffers = graphicsPipeline->resourceLayout->fragmentUniformBufferCount; + return (SDL_GPUGraphicsPipeline *)graphicsPipeline; } +static bool VULKAN_INTERNAL_IsValidShaderBytecode( + const Uint8 *code, + size_t codeSize) +{ + // SPIR-V bytecode has a 4 byte header containing 0x07230203. SPIR-V is + // defined as a stream of words and not a stream of bytes so both byte + // orders need to be considered. + // + // FIXME: It is uncertain if drivers are able to load both byte orders. If + // needed we may need to do an optional swizzle internally so apps can + // continue to treat shader code as an opaque blob. + if (codeSize < 4 || code == NULL) { + return false; + } + const Uint32 magic = 0x07230203; + const Uint32 magicInv = 0x03022307; + return SDL_memcmp(code, &magic, 4) == 0 || SDL_memcmp(code, &magicInv, 4) == 0; +} + static SDL_GPUComputePipeline *VULKAN_CreateComputePipeline( SDL_GPURenderer *driverData, const SDL_GPUComputePipelineCreateInfo *createinfo) @@ -6538,6 +6600,10 @@ static SDL_GPUComputePipeline *VULKAN_CreateComputePipeline( SET_STRING_ERROR_AND_RETURN("Incompatible shader format for Vulkan!", NULL); } + if (!VULKAN_INTERNAL_IsValidShaderBytecode(createinfo->code, createinfo->code_size)) { + SET_STRING_ERROR_AND_RETURN("The provided shader code is not valid SPIR-V!", NULL); + } + vulkanComputePipeline = SDL_malloc(sizeof(VulkanComputePipeline)); shaderModuleCreateInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; shaderModuleCreateInfo.pNext = NULL; @@ -6614,6 +6680,14 @@ static SDL_GPUComputePipeline *VULKAN_CreateComputePipeline( &nameInfo); } + // Track these here for debug layer + vulkanComputePipeline->header.numSamplers = vulkanComputePipeline->resourceLayout->numSamplers; + vulkanComputePipeline->header.numReadonlyStorageTextures = vulkanComputePipeline->resourceLayout->numReadonlyStorageTextures; + vulkanComputePipeline->header.numReadonlyStorageBuffers = vulkanComputePipeline->resourceLayout->numReadonlyStorageBuffers; + vulkanComputePipeline->header.numReadWriteStorageTextures = vulkanComputePipeline->resourceLayout->numReadWriteStorageTextures; + vulkanComputePipeline->header.numReadWriteStorageBuffers = vulkanComputePipeline->resourceLayout->numReadWriteStorageBuffers; + vulkanComputePipeline->header.numUniformBuffers = vulkanComputePipeline->resourceLayout->numUniformBuffers; + return (SDL_GPUComputePipeline *)vulkanComputePipeline; } @@ -6682,7 +6756,10 @@ static SDL_GPUShader *VULKAN_CreateShader( VkResult vulkanResult; VkShaderModuleCreateInfo vkShaderModuleCreateInfo; VulkanRenderer *renderer = (VulkanRenderer *)driverData; - size_t entryPointNameLength; + + if (!VULKAN_INTERNAL_IsValidShaderBytecode(createinfo->code, createinfo->code_size)) { + SET_STRING_ERROR_AND_RETURN("The provided shader code is not valid SPIR-V!", NULL); + } vulkanShader = SDL_malloc(sizeof(VulkanShader)); vkShaderModuleCreateInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; @@ -6702,10 +6779,11 @@ static SDL_GPUShader *VULKAN_CreateShader( CHECK_VULKAN_ERROR_AND_RETURN(vulkanResult, vkCreateShaderModule, NULL); } - entryPointNameLength = SDL_strlen(createinfo->entrypoint) + 1; - vulkanShader->entrypointName = SDL_malloc(entryPointNameLength); - SDL_utf8strlcpy((char *)vulkanShader->entrypointName, createinfo->entrypoint, entryPointNameLength); - + const char *entrypoint = createinfo->entrypoint; + if (!entrypoint) { + entrypoint = "main"; + } + vulkanShader->entrypointName = SDL_strdup(entrypoint); vulkanShader->stage = createinfo->stage; vulkanShader->numSamplers = createinfo->num_samplers; vulkanShader->numStorageTextures = createinfo->num_storage_textures; @@ -6751,6 +6829,7 @@ static SDL_GPUTexture *VULKAN_CreateTexture( texture = VULKAN_INTERNAL_CreateTexture( renderer, + true, createinfo); if (texture == NULL) { @@ -6762,7 +6841,9 @@ static SDL_GPUTexture *VULKAN_CreateTexture( // Copy properties so we don't lose information when the client destroys them container->header.info = *createinfo; container->header.info.props = SDL_CreateProperties(); - SDL_CopyProperties(createinfo->props, container->header.info.props); + if (createinfo->props) { + SDL_CopyProperties(createinfo->props, container->header.info.props); + } container->canBeCycled = true; container->activeTexture = texture; @@ -6925,7 +7006,7 @@ static void VULKAN_INTERNAL_ReleaseBuffer( renderer->buffersToDestroy[renderer->buffersToDestroyCount] = vulkanBuffer; renderer->buffersToDestroyCount += 1; - vulkanBuffer->markedForDestroy = 1; + vulkanBuffer->markedForDestroy = true; vulkanBuffer->container = NULL; SDL_UnlockMutex(renderer->disposeLock); @@ -7048,7 +7129,6 @@ static void VULKAN_ReleaseGraphicsPipeline( static VkRenderPass VULKAN_INTERNAL_FetchRenderPass( VulkanRenderer *renderer, - VulkanCommandBuffer *commandBuffer, const SDL_GPUColorTargetInfo *colorTargetInfos, Uint32 numColorTargets, const SDL_GPUDepthStencilTargetInfo *depthStencilTargetInfo) @@ -7106,7 +7186,6 @@ static VkRenderPass VULKAN_INTERNAL_FetchRenderPass( renderPassHandle = VULKAN_INTERNAL_CreateRenderPass( renderer, - commandBuffer, colorTargetInfos, numColorTargets, depthStencilTargetInfo); @@ -7409,21 +7488,21 @@ static void VULKAN_BindVertexSamplers( VulkanTextureContainer *textureContainer = (VulkanTextureContainer *)textureSamplerBindings[i].texture; VulkanSampler *sampler = (VulkanSampler *)textureSamplerBindings[i].sampler; - if (vulkanCommandBuffer->vertexSamplers[firstSlot + i] != sampler) { + if (vulkanCommandBuffer->vertexSamplerBindings[firstSlot + i] != sampler->sampler) { VULKAN_INTERNAL_TrackSampler( vulkanCommandBuffer, (VulkanSampler *)textureSamplerBindings[i].sampler); - vulkanCommandBuffer->vertexSamplers[firstSlot + i] = (VulkanSampler *)textureSamplerBindings[i].sampler; + vulkanCommandBuffer->vertexSamplerBindings[firstSlot + i] = sampler->sampler; vulkanCommandBuffer->needNewVertexResourceDescriptorSet = true; } - if (vulkanCommandBuffer->vertexSamplerTextures[firstSlot + i] != textureContainer->activeTexture) { + if (vulkanCommandBuffer->vertexSamplerTextureViewBindings[firstSlot + i] != textureContainer->activeTexture->fullView) { VULKAN_INTERNAL_TrackTexture( vulkanCommandBuffer, textureContainer->activeTexture); - vulkanCommandBuffer->vertexSamplerTextures[firstSlot + i] = textureContainer->activeTexture; + vulkanCommandBuffer->vertexSamplerTextureViewBindings[firstSlot + i] = textureContainer->activeTexture->fullView; vulkanCommandBuffer->needNewVertexResourceDescriptorSet = true; } } @@ -7440,12 +7519,12 @@ static void VULKAN_BindVertexStorageTextures( for (Uint32 i = 0; i < numBindings; i += 1) { VulkanTextureContainer *textureContainer = (VulkanTextureContainer *)storageTextures[i]; - if (vulkanCommandBuffer->vertexStorageTextures[firstSlot + i] != textureContainer->activeTexture) { + if (vulkanCommandBuffer->vertexStorageTextureViewBindings[firstSlot + i] != textureContainer->activeTexture->fullView) { VULKAN_INTERNAL_TrackTexture( vulkanCommandBuffer, textureContainer->activeTexture); - vulkanCommandBuffer->vertexStorageTextures[firstSlot + i] = textureContainer->activeTexture; + vulkanCommandBuffer->vertexStorageTextureViewBindings[firstSlot + i] = textureContainer->activeTexture->fullView; vulkanCommandBuffer->needNewVertexResourceDescriptorSet = true; } } @@ -7462,12 +7541,12 @@ static void VULKAN_BindVertexStorageBuffers( for (Uint32 i = 0; i < numBindings; i += 1) { VulkanBufferContainer *bufferContainer = (VulkanBufferContainer *)storageBuffers[i]; - if (vulkanCommandBuffer->vertexStorageBuffers[firstSlot + i] != bufferContainer->activeBuffer) { + if (vulkanCommandBuffer->vertexStorageBufferBindings[firstSlot + i] != bufferContainer->activeBuffer->buffer) { VULKAN_INTERNAL_TrackBuffer( vulkanCommandBuffer, bufferContainer->activeBuffer); - vulkanCommandBuffer->vertexStorageBuffers[firstSlot + i] = bufferContainer->activeBuffer; + vulkanCommandBuffer->vertexStorageBufferBindings[firstSlot + i] = bufferContainer->activeBuffer->buffer; vulkanCommandBuffer->needNewVertexResourceDescriptorSet = true; } } @@ -7485,21 +7564,21 @@ static void VULKAN_BindFragmentSamplers( VulkanTextureContainer *textureContainer = (VulkanTextureContainer *)textureSamplerBindings[i].texture; VulkanSampler *sampler = (VulkanSampler *)textureSamplerBindings[i].sampler; - if (vulkanCommandBuffer->fragmentSamplers[firstSlot + i] != sampler) { + if (vulkanCommandBuffer->fragmentSamplerBindings[firstSlot + i] != sampler->sampler) { VULKAN_INTERNAL_TrackSampler( vulkanCommandBuffer, (VulkanSampler *)textureSamplerBindings[i].sampler); - vulkanCommandBuffer->fragmentSamplers[firstSlot + i] = (VulkanSampler *)textureSamplerBindings[i].sampler; + vulkanCommandBuffer->fragmentSamplerBindings[firstSlot + i] = sampler->sampler; vulkanCommandBuffer->needNewFragmentResourceDescriptorSet = true; } - if (vulkanCommandBuffer->fragmentSamplerTextures[firstSlot + i] != textureContainer->activeTexture) { + if (vulkanCommandBuffer->fragmentSamplerTextureViewBindings[firstSlot + i] != textureContainer->activeTexture->fullView) { VULKAN_INTERNAL_TrackTexture( vulkanCommandBuffer, textureContainer->activeTexture); - vulkanCommandBuffer->fragmentSamplerTextures[firstSlot + i] = textureContainer->activeTexture; + vulkanCommandBuffer->fragmentSamplerTextureViewBindings[firstSlot + i] = textureContainer->activeTexture->fullView; vulkanCommandBuffer->needNewFragmentResourceDescriptorSet = true; } } @@ -7516,12 +7595,12 @@ static void VULKAN_BindFragmentStorageTextures( for (Uint32 i = 0; i < numBindings; i += 1) { VulkanTextureContainer *textureContainer = (VulkanTextureContainer *)storageTextures[i]; - if (vulkanCommandBuffer->fragmentStorageTextures[firstSlot + i] != textureContainer->activeTexture) { + if (vulkanCommandBuffer->fragmentStorageTextureViewBindings[firstSlot + i] != textureContainer->activeTexture->fullView) { VULKAN_INTERNAL_TrackTexture( vulkanCommandBuffer, textureContainer->activeTexture); - vulkanCommandBuffer->fragmentStorageTextures[firstSlot + i] = textureContainer->activeTexture; + vulkanCommandBuffer->fragmentStorageTextureViewBindings[firstSlot + i] = textureContainer->activeTexture->fullView; vulkanCommandBuffer->needNewFragmentResourceDescriptorSet = true; } } @@ -7540,12 +7619,12 @@ static void VULKAN_BindFragmentStorageBuffers( for (i = 0; i < numBindings; i += 1) { bufferContainer = (VulkanBufferContainer *)storageBuffers[i]; - if (vulkanCommandBuffer->fragmentStorageBuffers[firstSlot + i] != bufferContainer->activeBuffer) { + if (vulkanCommandBuffer->fragmentStorageBufferBindings[firstSlot + i] != bufferContainer->activeBuffer->buffer) { VULKAN_INTERNAL_TrackBuffer( vulkanCommandBuffer, bufferContainer->activeBuffer); - vulkanCommandBuffer->fragmentStorageBuffers[firstSlot + i] = bufferContainer->activeBuffer; + vulkanCommandBuffer->fragmentStorageBufferBindings[firstSlot + i] = bufferContainer->activeBuffer->buffer; vulkanCommandBuffer->needNewFragmentResourceDescriptorSet = true; } } @@ -7790,7 +7869,6 @@ static void VULKAN_BeginRenderPass( renderPass = VULKAN_INTERNAL_FetchRenderPass( renderer, - vulkanCommandBuffer, colorTargetInfos, numColorTargets, depthStencilTargetInfo); @@ -7812,7 +7890,7 @@ static void VULKAN_BeginRenderPass( return; } - VULKAN_INTERNAL_TrackFramebuffer(renderer, vulkanCommandBuffer, framebuffer); + VULKAN_INTERNAL_TrackFramebuffer(vulkanCommandBuffer, framebuffer); // Set clear values @@ -7943,11 +8021,11 @@ static void VULKAN_BindVertexBuffers( for (Uint32 i = 0; i < numBindings; i += 1) { VulkanBuffer *buffer = ((VulkanBufferContainer *)bindings[i].buffer)->activeBuffer; - if (vulkanCommandBuffer->vertexBuffers[i] != buffer->buffer || vulkanCommandBuffer->vertexBufferOffsets[i] != bindings[i].offset) { + if (vulkanCommandBuffer->vertexBuffers[firstSlot + i] != buffer->buffer || vulkanCommandBuffer->vertexBufferOffsets[firstSlot + i] != bindings[i].offset) { VULKAN_INTERNAL_TrackBuffer(vulkanCommandBuffer, buffer); - vulkanCommandBuffer->vertexBuffers[i] = buffer->buffer; - vulkanCommandBuffer->vertexBufferOffsets[i] = bindings[i].offset; + vulkanCommandBuffer->vertexBuffers[firstSlot + i] = buffer->buffer; + vulkanCommandBuffer->vertexBufferOffsets[firstSlot + i] = bindings[i].offset; vulkanCommandBuffer->needVertexBufferBind = true; } } @@ -8059,15 +8137,15 @@ static void VULKAN_EndRenderPass( SDL_zeroa(vulkanCommandBuffer->vertexBufferOffsets); vulkanCommandBuffer->vertexBufferCount = 0; - SDL_zeroa(vulkanCommandBuffer->vertexSamplers); - SDL_zeroa(vulkanCommandBuffer->vertexSamplerTextures); - SDL_zeroa(vulkanCommandBuffer->vertexStorageTextures); - SDL_zeroa(vulkanCommandBuffer->vertexStorageBuffers); + SDL_zeroa(vulkanCommandBuffer->vertexSamplerBindings); + SDL_zeroa(vulkanCommandBuffer->vertexSamplerTextureViewBindings); + SDL_zeroa(vulkanCommandBuffer->vertexStorageTextureViewBindings); + SDL_zeroa(vulkanCommandBuffer->vertexStorageBufferBindings); - SDL_zeroa(vulkanCommandBuffer->fragmentSamplers); - SDL_zeroa(vulkanCommandBuffer->fragmentSamplerTextures); - SDL_zeroa(vulkanCommandBuffer->fragmentStorageTextures); - SDL_zeroa(vulkanCommandBuffer->fragmentStorageBuffers); + SDL_zeroa(vulkanCommandBuffer->fragmentSamplerBindings); + SDL_zeroa(vulkanCommandBuffer->fragmentSamplerTextureViewBindings); + SDL_zeroa(vulkanCommandBuffer->fragmentStorageTextureViewBindings); + SDL_zeroa(vulkanCommandBuffer->fragmentStorageBufferBindings); } static void VULKAN_BeginComputePass( @@ -8097,6 +8175,7 @@ static void VULKAN_BeginComputePass( VULKAN_TEXTURE_USAGE_MODE_COMPUTE_STORAGE_READ_WRITE); vulkanCommandBuffer->readWriteComputeStorageTextureSubresources[i] = subresource; + vulkanCommandBuffer->readWriteComputeStorageTextureViewBindings[i] = subresource->computeWriteView; VULKAN_INTERNAL_TrackTexture( vulkanCommandBuffer, @@ -8110,9 +8189,10 @@ static void VULKAN_BeginComputePass( vulkanCommandBuffer, bufferContainer, storageBufferBindings[i].cycle, - VULKAN_BUFFER_USAGE_MODE_COMPUTE_STORAGE_READ); + VULKAN_BUFFER_USAGE_MODE_COMPUTE_STORAGE_READ_WRITE); vulkanCommandBuffer->readWriteComputeStorageBuffers[i] = buffer; + vulkanCommandBuffer->readWriteComputeStorageBufferBindings[i] = buffer->buffer; VULKAN_INTERNAL_TrackBuffer( vulkanCommandBuffer, @@ -8164,21 +8244,21 @@ static void VULKAN_BindComputeSamplers( VulkanTextureContainer *textureContainer = (VulkanTextureContainer *)textureSamplerBindings[i].texture; VulkanSampler *sampler = (VulkanSampler *)textureSamplerBindings[i].sampler; - if (vulkanCommandBuffer->computeSamplers[firstSlot + i] != sampler) { + if (vulkanCommandBuffer->computeSamplerBindings[firstSlot + i] != sampler->sampler) { VULKAN_INTERNAL_TrackSampler( vulkanCommandBuffer, sampler); - vulkanCommandBuffer->computeSamplers[firstSlot + i] = sampler; + vulkanCommandBuffer->computeSamplerBindings[firstSlot + i] = sampler->sampler; vulkanCommandBuffer->needNewComputeReadOnlyDescriptorSet = true; } - if (vulkanCommandBuffer->computeSamplerTextures[firstSlot + i] != textureContainer->activeTexture) { + if (vulkanCommandBuffer->computeSamplerTextureViewBindings[firstSlot + i] != textureContainer->activeTexture->fullView) { VULKAN_INTERNAL_TrackTexture( vulkanCommandBuffer, textureContainer->activeTexture); - vulkanCommandBuffer->computeSamplerTextures[firstSlot + i] = textureContainer->activeTexture; + vulkanCommandBuffer->computeSamplerTextureViewBindings[firstSlot + i] = textureContainer->activeTexture->fullView; vulkanCommandBuffer->needNewComputeReadOnlyDescriptorSet = true; } } @@ -8219,6 +8299,7 @@ static void VULKAN_BindComputeStorageTextures( textureContainer->activeTexture); vulkanCommandBuffer->readOnlyComputeStorageTextures[firstSlot + i] = textureContainer->activeTexture; + vulkanCommandBuffer->readOnlyComputeStorageTextureViewBindings[firstSlot + i] = textureContainer->activeTexture->fullView; vulkanCommandBuffer->needNewComputeReadOnlyDescriptorSet = true; } } @@ -8258,6 +8339,7 @@ static void VULKAN_BindComputeStorageBuffers( bufferContainer->activeBuffer); vulkanCommandBuffer->readOnlyComputeStorageBuffers[firstSlot + i] = bufferContainer->activeBuffer; + vulkanCommandBuffer->readOnlyComputeStorageBufferBindings[firstSlot + i] = bufferContainer->activeBuffer->buffer; vulkanCommandBuffer->needNewComputeReadOnlyDescriptorSet = true; } } @@ -8332,8 +8414,8 @@ static void VULKAN_INTERNAL_BindComputeDescriptorSets( currentWriteDescriptorSet->pTexelBufferView = NULL; currentWriteDescriptorSet->pBufferInfo = NULL; - imageInfos[imageInfoCount].sampler = commandBuffer->computeSamplers[i]->sampler; - imageInfos[imageInfoCount].imageView = commandBuffer->computeSamplerTextures[i]->fullView; + imageInfos[imageInfoCount].sampler = commandBuffer->computeSamplerBindings[i]; + imageInfos[imageInfoCount].imageView = commandBuffer->computeSamplerTextureViewBindings[i]; imageInfos[imageInfoCount].imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; currentWriteDescriptorSet->pImageInfo = &imageInfos[imageInfoCount]; @@ -8356,7 +8438,7 @@ static void VULKAN_INTERNAL_BindComputeDescriptorSets( currentWriteDescriptorSet->pBufferInfo = NULL; imageInfos[imageInfoCount].sampler = VK_NULL_HANDLE; - imageInfos[imageInfoCount].imageView = commandBuffer->readOnlyComputeStorageTextures[i]->fullView; + imageInfos[imageInfoCount].imageView = commandBuffer->readOnlyComputeStorageTextureViewBindings[i]; imageInfos[imageInfoCount].imageLayout = VK_IMAGE_LAYOUT_GENERAL; currentWriteDescriptorSet->pImageInfo = &imageInfos[imageInfoCount]; @@ -8378,7 +8460,7 @@ static void VULKAN_INTERNAL_BindComputeDescriptorSets( currentWriteDescriptorSet->pTexelBufferView = NULL; currentWriteDescriptorSet->pImageInfo = NULL; - bufferInfos[bufferInfoCount].buffer = commandBuffer->readOnlyComputeStorageBuffers[i]->buffer; + bufferInfos[bufferInfoCount].buffer = commandBuffer->readOnlyComputeStorageBufferBindings[i]; bufferInfos[bufferInfoCount].offset = 0; bufferInfos[bufferInfoCount].range = VK_WHOLE_SIZE; @@ -8413,7 +8495,7 @@ static void VULKAN_INTERNAL_BindComputeDescriptorSets( currentWriteDescriptorSet->pBufferInfo = NULL; imageInfos[imageInfoCount].sampler = VK_NULL_HANDLE; - imageInfos[imageInfoCount].imageView = commandBuffer->readWriteComputeStorageTextureSubresources[i]->computeWriteView; + imageInfos[imageInfoCount].imageView = commandBuffer->readWriteComputeStorageTextureViewBindings[i]; imageInfos[imageInfoCount].imageLayout = VK_IMAGE_LAYOUT_GENERAL; currentWriteDescriptorSet->pImageInfo = &imageInfos[imageInfoCount]; @@ -8435,7 +8517,7 @@ static void VULKAN_INTERNAL_BindComputeDescriptorSets( currentWriteDescriptorSet->pTexelBufferView = NULL; currentWriteDescriptorSet->pImageInfo = NULL; - bufferInfos[bufferInfoCount].buffer = commandBuffer->readWriteComputeStorageBuffers[i]->buffer; + bufferInfos[bufferInfoCount].buffer = commandBuffer->readWriteComputeStorageBufferBindings[i]; bufferInfos[bufferInfoCount].offset = 0; bufferInfos[bufferInfoCount].range = VK_WHOLE_SIZE; @@ -8602,9 +8684,12 @@ static void VULKAN_EndComputePass( } } - // we don't need a barrier because sampler state is always the default if sampler bit is set - SDL_zeroa(vulkanCommandBuffer->computeSamplerTextures); - SDL_zeroa(vulkanCommandBuffer->computeSamplers); + // we don't need a barrier for sampler resources because sampler state is always the default if sampler bit is set + SDL_zeroa(vulkanCommandBuffer->computeSamplerTextureViewBindings); + SDL_zeroa(vulkanCommandBuffer->computeSamplerBindings); + + SDL_zeroa(vulkanCommandBuffer->readWriteComputeStorageTextureViewBindings); + SDL_zeroa(vulkanCommandBuffer->readWriteComputeStorageBufferBindings); vulkanCommandBuffer->currentComputePipeline = NULL; @@ -9470,21 +9555,23 @@ static SDL_GPUCommandBuffer *VULKAN_AcquireCommandBuffer( SDL_zeroa(commandBuffer->vertexBufferOffsets); commandBuffer->vertexBufferCount = 0; - SDL_zeroa(commandBuffer->vertexSamplerTextures); - SDL_zeroa(commandBuffer->vertexSamplers); - SDL_zeroa(commandBuffer->vertexStorageTextures); - SDL_zeroa(commandBuffer->vertexStorageBuffers); + SDL_zeroa(commandBuffer->vertexSamplerTextureViewBindings); + SDL_zeroa(commandBuffer->vertexSamplerBindings); + SDL_zeroa(commandBuffer->vertexStorageTextureViewBindings); + SDL_zeroa(commandBuffer->vertexStorageBufferBindings); - SDL_zeroa(commandBuffer->fragmentSamplerTextures); - SDL_zeroa(commandBuffer->fragmentSamplers); - SDL_zeroa(commandBuffer->fragmentStorageTextures); - SDL_zeroa(commandBuffer->fragmentStorageBuffers); + SDL_zeroa(commandBuffer->fragmentSamplerTextureViewBindings); + SDL_zeroa(commandBuffer->fragmentSamplerBindings); + SDL_zeroa(commandBuffer->fragmentStorageTextureViewBindings); + SDL_zeroa(commandBuffer->fragmentStorageBufferBindings); SDL_zeroa(commandBuffer->readWriteComputeStorageTextureSubresources); commandBuffer->readWriteComputeStorageTextureSubresourceCount = 0; SDL_zeroa(commandBuffer->readWriteComputeStorageBuffers); - SDL_zeroa(commandBuffer->computeSamplerTextures); - SDL_zeroa(commandBuffer->computeSamplers); + SDL_zeroa(commandBuffer->computeSamplerTextureViewBindings); + SDL_zeroa(commandBuffer->computeSamplerBindings); + SDL_zeroa(commandBuffer->readOnlyComputeStorageTextureViewBindings); + SDL_zeroa(commandBuffer->readOnlyComputeStorageBufferBindings); SDL_zeroa(commandBuffer->readOnlyComputeStorageTextures); SDL_zeroa(commandBuffer->readOnlyComputeStorageBuffers); @@ -9981,7 +10068,7 @@ static bool VULKAN_INTERNAL_AcquireSwapchainTexture( } vulkanCommandBuffer->signalSemaphores[vulkanCommandBuffer->signalSemaphoreCount] = - windowData->renderFinishedSemaphore[windowData->frameCounter]; + windowData->renderFinishedSemaphore[swapchainImageIndex]; vulkanCommandBuffer->signalSemaphoreCount += 1; *swapchainTexture = (SDL_GPUTexture *)swapchainTextureContainer; @@ -10024,7 +10111,7 @@ static SDL_GPUTextureFormat VULKAN_GetSwapchainTextureFormat( SDL_GPURenderer *driverData, SDL_Window *window) { - VulkanRenderer *renderer = (VulkanRenderer*)driverData; + VulkanRenderer *renderer = (VulkanRenderer *)driverData; WindowData *windowData = VULKAN_INTERNAL_FetchWindowData(window); if (windowData == NULL) { @@ -10445,7 +10532,9 @@ static bool VULKAN_Submit( Uint32 swapchainImageIndex; VulkanTextureSubresource *swapchainTextureSubresource; VulkanMemorySubAllocator *allocator; - bool presenting = false; + bool performCleanups = + (renderer->claimedWindowCount > 0 && vulkanCommandBuffer->presentDataCount > 0) || + renderer->claimedWindowCount == 0; SDL_LockMutex(renderer->submitLock); @@ -10468,6 +10557,15 @@ static bool VULKAN_Submit( swapchainTextureSubresource); } + if (performCleanups && + renderer->allocationsToDefragCount > 0 && + !renderer->defragInProgress) { + if (!VULKAN_INTERNAL_DefragmentMemory(renderer, vulkanCommandBuffer)) + { + SDL_LogError(SDL_LOG_CATEGORY_GPU, "%s", "Failed to defragment memory, likely OOM!"); + } + } + if (!VULKAN_INTERNAL_EndCommandBuffer(renderer, vulkanCommandBuffer)) { SDL_UnlockMutex(renderer->submitLock); return false; @@ -10505,17 +10603,13 @@ static bool VULKAN_Submit( } // Present, if applicable - bool result = true; - for (Uint32 j = 0; j < vulkanCommandBuffer->presentDataCount; j += 1) { - presenting = true; - presentData = &vulkanCommandBuffer->presentDatas[j]; presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; presentInfo.pNext = NULL; presentInfo.pWaitSemaphores = - &presentData->windowData->renderFinishedSemaphore[presentData->windowData->frameCounter]; + &presentData->windowData->renderFinishedSemaphore[presentData->swapchainImageIndex]; presentInfo.waitSemaphoreCount = 1; presentInfo.pSwapchains = &presentData->windowData->swapchain; presentInfo.swapchainCount = 1; @@ -10528,7 +10622,7 @@ static bool VULKAN_Submit( if (presentResult == VK_SUCCESS || presentResult == VK_SUBOPTIMAL_KHR || presentResult == VK_ERROR_OUT_OF_DATE_KHR) { // If presenting, the swapchain is using the in-flight fence - presentData->windowData->inFlightFences[presentData->windowData->frameCounter] = (SDL_GPUFence*)vulkanCommandBuffer->inFlightFence; + presentData->windowData->inFlightFences[presentData->windowData->frameCounter] = (SDL_GPUFence *)vulkanCommandBuffer->inFlightFence; (void)SDL_AtomicIncRef(&vulkanCommandBuffer->inFlightFence->referenceCount); if (presentResult == VK_SUBOPTIMAL_KHR || presentResult == VK_ERROR_OUT_OF_DATE_KHR) { @@ -10547,60 +10641,50 @@ static bool VULKAN_Submit( (presentData->windowData->frameCounter + 1) % renderer->allowedFramesInFlight; } - // Check if we can perform any cleanups + if (performCleanups) { + for (Sint32 i = renderer->submittedCommandBufferCount - 1; i >= 0; i -= 1) { + vulkanResult = renderer->vkGetFenceStatus( + renderer->logicalDevice, + renderer->submittedCommandBuffers[i]->inFlightFence->fence); - for (Sint32 i = renderer->submittedCommandBufferCount - 1; i >= 0; i -= 1) { - vulkanResult = renderer->vkGetFenceStatus( - renderer->logicalDevice, - renderer->submittedCommandBuffers[i]->inFlightFence->fence); - - if (vulkanResult == VK_SUCCESS) { - VULKAN_INTERNAL_CleanCommandBuffer( - renderer, - renderer->submittedCommandBuffers[i], - false); - } - } - - if (renderer->checkEmptyAllocations) { - SDL_LockMutex(renderer->allocatorLock); - - for (Uint32 i = 0; i < VK_MAX_MEMORY_TYPES; i += 1) { - allocator = &renderer->memoryAllocator->subAllocators[i]; - - for (Sint32 j = allocator->allocationCount - 1; j >= 0; j -= 1) { - if (allocator->allocations[j]->usedRegionCount == 0) { - VULKAN_INTERNAL_DeallocateMemory( - renderer, - allocator, - j); - } + if (vulkanResult == VK_SUCCESS) { + VULKAN_INTERNAL_CleanCommandBuffer( + renderer, + renderer->submittedCommandBuffers[i], + false); } } - renderer->checkEmptyAllocations = false; + if (renderer->checkEmptyAllocations) { + SDL_LockMutex(renderer->allocatorLock); - SDL_UnlockMutex(renderer->allocatorLock); - } + for (Uint32 i = 0; i < VK_MAX_MEMORY_TYPES; i += 1) { + allocator = &renderer->memoryAllocator->subAllocators[i]; - // Check pending destroys - VULKAN_INTERNAL_PerformPendingDestroys(renderer); + for (Sint32 j = allocator->allocationCount - 1; j >= 0; j -= 1) { + if (allocator->allocations[j]->usedRegionCount == 0) { + VULKAN_INTERNAL_DeallocateMemory( + renderer, + allocator, + j); + } + } + } - // Defrag! - if ( - presenting && - renderer->allocationsToDefragCount > 0 && - !renderer->defragInProgress) { - result = VULKAN_INTERNAL_DefragmentMemory(renderer); + renderer->checkEmptyAllocations = false; + + SDL_UnlockMutex(renderer->allocatorLock); + } + + VULKAN_INTERNAL_PerformPendingDestroys(renderer); } // Mark command buffer as submitted - // This must happen after defrag, because it will try to acquire new command buffers. VULKAN_INTERNAL_ReleaseCommandBuffer(vulkanCommandBuffer); SDL_UnlockMutex(renderer->submitLock); - return result; + return true; } static bool VULKAN_Cancel( @@ -10627,43 +10711,28 @@ static bool VULKAN_Cancel( } static bool VULKAN_INTERNAL_DefragmentMemory( - VulkanRenderer *renderer) + VulkanRenderer *renderer, + VulkanCommandBuffer *commandBuffer) { - VulkanMemoryAllocation *allocation; - VulkanMemoryUsedRegion *currentRegion; - VulkanBuffer *newBuffer; - VulkanTexture *newTexture; - VkBufferCopy bufferCopy; - VkImageCopy imageCopy; - VulkanCommandBuffer *commandBuffer; - VulkanTextureSubresource *srcSubresource; - VulkanTextureSubresource *dstSubresource; - Uint32 i, subresourceIndex; - renderer->defragInProgress = 1; - - commandBuffer = (VulkanCommandBuffer *)VULKAN_AcquireCommandBuffer((SDL_GPURenderer *)renderer); - if (commandBuffer == NULL) { - return false; - } commandBuffer->isDefrag = 1; SDL_LockMutex(renderer->allocatorLock); - allocation = renderer->allocationsToDefrag[renderer->allocationsToDefragCount - 1]; + VulkanMemoryAllocation *allocation = renderer->allocationsToDefrag[renderer->allocationsToDefragCount - 1]; renderer->allocationsToDefragCount -= 1; /* For each used region in the allocation * create a new resource, copy the data * and re-point the resource containers */ - for (i = 0; i < allocation->usedRegionCount; i += 1) { - currentRegion = allocation->usedRegions[i]; + for (Uint32 i = 0; i < allocation->usedRegionCount; i += 1) { + VulkanMemoryUsedRegion *currentRegion = allocation->usedRegions[i]; if (currentRegion->isBuffer && !currentRegion->vulkanBuffer->markedForDestroy) { currentRegion->vulkanBuffer->usage |= VK_BUFFER_USAGE_TRANSFER_DST_BIT; - newBuffer = VULKAN_INTERNAL_CreateBuffer( + VulkanBuffer *newBuffer = VULKAN_INTERNAL_CreateBuffer( renderer, currentRegion->vulkanBuffer->size, currentRegion->vulkanBuffer->usage, @@ -10673,6 +10742,7 @@ static bool VULKAN_INTERNAL_DefragmentMemory( if (newBuffer == NULL) { SDL_UnlockMutex(renderer->allocatorLock); + SDL_LogError(SDL_LOG_CATEGORY_GPU, "%s", "Failed to allocate defrag buffer!"); return false; } @@ -10691,6 +10761,7 @@ static bool VULKAN_INTERNAL_DefragmentMemory( VULKAN_BUFFER_USAGE_MODE_COPY_DESTINATION, newBuffer); + VkBufferCopy bufferCopy; bufferCopy.srcOffset = 0; bufferCopy.dstOffset = 0; bufferCopy.size = currentRegion->resourceSize; @@ -10730,20 +10801,22 @@ static bool VULKAN_INTERNAL_DefragmentMemory( VULKAN_INTERNAL_ReleaseBuffer(renderer, currentRegion->vulkanBuffer); } else if (!currentRegion->isBuffer && !currentRegion->vulkanTexture->markedForDestroy) { - newTexture = VULKAN_INTERNAL_CreateTexture( + VulkanTexture *newTexture = VULKAN_INTERNAL_CreateTexture( renderer, + false, ¤tRegion->vulkanTexture->container->header.info); if (newTexture == NULL) { SDL_UnlockMutex(renderer->allocatorLock); + SDL_LogError(SDL_LOG_CATEGORY_GPU, "%s", "Failed to allocate defrag buffer!"); return false; } SDL_GPUTextureCreateInfo info = currentRegion->vulkanTexture->container->header.info; - for (subresourceIndex = 0; subresourceIndex < currentRegion->vulkanTexture->subresourceCount; subresourceIndex += 1) { + for (Uint32 subresourceIndex = 0; subresourceIndex < currentRegion->vulkanTexture->subresourceCount; subresourceIndex += 1) { // copy subresource if necessary - srcSubresource = ¤tRegion->vulkanTexture->subresources[subresourceIndex]; - dstSubresource = &newTexture->subresources[subresourceIndex]; + VulkanTextureSubresource *srcSubresource = ¤tRegion->vulkanTexture->subresources[subresourceIndex]; + VulkanTextureSubresource *dstSubresource = &newTexture->subresources[subresourceIndex]; VULKAN_INTERNAL_TextureSubresourceTransitionFromDefaultUsage( renderer, @@ -10751,12 +10824,14 @@ static bool VULKAN_INTERNAL_DefragmentMemory( VULKAN_TEXTURE_USAGE_MODE_COPY_SOURCE, srcSubresource); - VULKAN_INTERNAL_TextureSubresourceTransitionFromDefaultUsage( + VULKAN_INTERNAL_TextureSubresourceMemoryBarrier( renderer, commandBuffer, + VULKAN_TEXTURE_USAGE_MODE_UNINITIALIZED, VULKAN_TEXTURE_USAGE_MODE_COPY_DESTINATION, dstSubresource); + VkImageCopy imageCopy; imageCopy.srcOffset.x = 0; imageCopy.srcOffset.y = 0; imageCopy.srcOffset.z = 0; @@ -10808,8 +10883,7 @@ static bool VULKAN_INTERNAL_DefragmentMemory( SDL_UnlockMutex(renderer->allocatorLock); - return VULKAN_Submit( - (SDL_GPUCommandBuffer *)commandBuffer); + return true; } // Format Info @@ -11186,11 +11260,14 @@ static Uint8 VULKAN_INTERNAL_IsDeviceSuitable( renderer->vkGetPhysicalDeviceFeatures( physicalDevice, &deviceFeatures); - if (!deviceFeatures.independentBlend || - !deviceFeatures.imageCubeArray || - !deviceFeatures.depthClamp || - !deviceFeatures.shaderClipDistance || - !deviceFeatures.drawIndirectFirstInstance) { + + if ((!deviceFeatures.independentBlend && renderer->desiredDeviceFeatures.independentBlend) || + (!deviceFeatures.imageCubeArray && renderer->desiredDeviceFeatures.imageCubeArray) || + (!deviceFeatures.depthClamp && renderer->desiredDeviceFeatures.depthClamp) || + (!deviceFeatures.shaderClipDistance && renderer->desiredDeviceFeatures.shaderClipDistance) || + (!deviceFeatures.drawIndirectFirstInstance && renderer->desiredDeviceFeatures.drawIndirectFirstInstance) || + (!deviceFeatures.sampleRateShading && renderer->desiredDeviceFeatures.sampleRateShading) || + (!deviceFeatures.samplerAnisotropy && renderer->desiredDeviceFeatures.samplerAnisotropy)) { return 0; } @@ -11406,7 +11483,6 @@ static Uint8 VULKAN_INTERNAL_CreateLogicalDevice( { VkResult vulkanResult; VkDeviceCreateInfo deviceCreateInfo; - VkPhysicalDeviceFeatures desiredDeviceFeatures; VkPhysicalDeviceFeatures haveDeviceFeatures; VkPhysicalDevicePortabilitySubsetFeaturesKHR portabilityFeatures; const char **deviceExtensions; @@ -11430,21 +11506,13 @@ static Uint8 VULKAN_INTERNAL_CreateLogicalDevice( // specifying used device features - SDL_zero(desiredDeviceFeatures); - desiredDeviceFeatures.independentBlend = VK_TRUE; - desiredDeviceFeatures.samplerAnisotropy = VK_TRUE; - desiredDeviceFeatures.imageCubeArray = VK_TRUE; - desiredDeviceFeatures.depthClamp = VK_TRUE; - desiredDeviceFeatures.shaderClipDistance = VK_TRUE; - desiredDeviceFeatures.drawIndirectFirstInstance = VK_TRUE; - if (haveDeviceFeatures.fillModeNonSolid) { - desiredDeviceFeatures.fillModeNonSolid = VK_TRUE; + renderer->desiredDeviceFeatures.fillModeNonSolid = VK_TRUE; renderer->supportsFillModeNonSolid = true; } if (haveDeviceFeatures.multiDrawIndirect) { - desiredDeviceFeatures.multiDrawIndirect = VK_TRUE; + renderer->desiredDeviceFeatures.multiDrawIndirect = VK_TRUE; renderer->supportsMultiDrawIndirect = true; } @@ -11485,7 +11553,7 @@ static Uint8 VULKAN_INTERNAL_CreateLogicalDevice( deviceCreateInfo.enabledExtensionCount); CreateDeviceExtensionArray(&renderer->supports, deviceExtensions); deviceCreateInfo.ppEnabledExtensionNames = deviceExtensions; - deviceCreateInfo.pEnabledFeatures = &desiredDeviceFeatures; + deviceCreateInfo.pEnabledFeatures = &renderer->desiredDeviceFeatures; vulkanResult = renderer->vkCreateDevice( renderer->physicalDevice, @@ -11570,11 +11638,15 @@ static bool VULKAN_INTERNAL_PrepareVulkan( return true; } -static bool VULKAN_PrepareDriver(SDL_VideoDevice *_this) +static bool VULKAN_PrepareDriver(SDL_VideoDevice *_this, SDL_PropertiesID props) { // Set up dummy VulkanRenderer VulkanRenderer *renderer; - Uint8 result; + bool result = false; + + if (!SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_SHADERS_SPIRV_BOOLEAN, false)) { + return false; + } if (_this->Vulkan_CreateSurface == NULL) { return false; @@ -11584,16 +11656,27 @@ static bool VULKAN_PrepareDriver(SDL_VideoDevice *_this) return false; } - renderer = (VulkanRenderer *)SDL_malloc(sizeof(VulkanRenderer)); - SDL_memset(renderer, '\0', sizeof(VulkanRenderer)); + renderer = (VulkanRenderer *)SDL_calloc(1, sizeof(*renderer)); + if (renderer) { + // Opt out device features (higher compatibility in exchange for reduced functionality) + renderer->desiredDeviceFeatures.samplerAnisotropy = SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_VULKAN_SAMPLERANISOTROPY_BOOLEAN, true) ? VK_TRUE : VK_FALSE; + renderer->desiredDeviceFeatures.depthClamp = SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_VULKAN_DEPTHCLAMP_BOOLEAN, true) ? VK_TRUE : VK_FALSE; + renderer->desiredDeviceFeatures.shaderClipDistance = SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_VULKAN_SHADERCLIPDISTANCE_BOOLEAN, true) ? VK_TRUE : VK_FALSE; + renderer->desiredDeviceFeatures.drawIndirectFirstInstance = SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_VULKAN_DRAWINDIRECTFIRST_BOOLEAN, true) ? VK_TRUE : VK_FALSE; - result = VULKAN_INTERNAL_PrepareVulkan(renderer); + // These features have near universal support so they are always enabled + renderer->desiredDeviceFeatures.independentBlend = VK_TRUE; + renderer->desiredDeviceFeatures.sampleRateShading = VK_TRUE; + renderer->desiredDeviceFeatures.imageCubeArray = VK_TRUE; - if (result) { - renderer->vkDestroyInstance(renderer->instance, NULL); + result = VULKAN_INTERNAL_PrepareVulkan(renderer); + if (result) { + renderer->vkDestroyInstance(renderer->instance, NULL); + } + SDL_free(renderer); } - SDL_free(renderer); SDL_Vulkan_UnloadLibrary(); + return result; } @@ -11604,42 +11687,141 @@ static SDL_GPUDevice *VULKAN_CreateDevice(bool debugMode, bool preferLowPower, S SDL_GPUDevice *result; Uint32 i; + bool verboseLogs = SDL_GetBooleanProperty( + props, + SDL_PROP_GPU_DEVICE_CREATE_VERBOSE_BOOLEAN, + true); + if (!SDL_Vulkan_LoadLibrary(NULL)) { SDL_assert(!"This should have failed in PrepareDevice first!"); return NULL; } - renderer = (VulkanRenderer *)SDL_malloc(sizeof(VulkanRenderer)); - SDL_memset(renderer, '\0', sizeof(VulkanRenderer)); + renderer = (VulkanRenderer *)SDL_calloc(1, sizeof(*renderer)); + if (!renderer) { + SDL_Vulkan_UnloadLibrary(); + return false; + } + renderer->debugMode = debugMode; renderer->preferLowPower = preferLowPower; renderer->allowedFramesInFlight = 2; + // Opt out device features (higher compatibility in exchange for reduced functionality) + renderer->desiredDeviceFeatures.samplerAnisotropy = SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_VULKAN_SAMPLERANISOTROPY_BOOLEAN, true) ? VK_TRUE : VK_FALSE; + renderer->desiredDeviceFeatures.depthClamp = SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_VULKAN_DEPTHCLAMP_BOOLEAN, true) ? VK_TRUE : VK_FALSE; + renderer->desiredDeviceFeatures.shaderClipDistance = SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_VULKAN_SHADERCLIPDISTANCE_BOOLEAN, true) ? VK_TRUE : VK_FALSE; + renderer->desiredDeviceFeatures.drawIndirectFirstInstance = SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_VULKAN_DRAWINDIRECTFIRST_BOOLEAN, true) ? VK_TRUE : VK_FALSE; + + // These features have near universal support so they are always enabled + renderer->desiredDeviceFeatures.independentBlend = VK_TRUE; + renderer->desiredDeviceFeatures.sampleRateShading = VK_TRUE; + renderer->desiredDeviceFeatures.imageCubeArray = VK_TRUE; + if (!VULKAN_INTERNAL_PrepareVulkan(renderer)) { SDL_free(renderer); SDL_Vulkan_UnloadLibrary(); SET_STRING_ERROR_AND_RETURN("Failed to initialize Vulkan!", NULL); } - SDL_LogInfo(SDL_LOG_CATEGORY_GPU, "SDL_GPU Driver: Vulkan"); - SDL_LogInfo( - SDL_LOG_CATEGORY_GPU, - "Vulkan Device: %s", - renderer->physicalDeviceProperties.properties.deviceName); + renderer->props = SDL_CreateProperties(); + if (verboseLogs) { + SDL_LogInfo(SDL_LOG_CATEGORY_GPU, "SDL_GPU Driver: Vulkan"); + } + + // Record device name + const char *deviceName = renderer->physicalDeviceProperties.properties.deviceName; + SDL_SetStringProperty( + renderer->props, + SDL_PROP_GPU_DEVICE_NAME_STRING, + deviceName); + if (verboseLogs) { + SDL_LogInfo(SDL_LOG_CATEGORY_GPU, "Vulkan Device: %s", deviceName); + } + + // Record driver version. This is provided as a backup if + // VK_KHR_driver_properties is not available but as most drivers support it + // this property should be rarely used. + // + // This uses a vendor-specific encoding and it isn't well documented. The + // vendor ID is the registered PCI ID of the vendor and can be found in + // online databases. + char driverVer[64]; + Uint32 rawDriverVer = renderer->physicalDeviceProperties.properties.driverVersion; + Uint32 vendorId = renderer->physicalDeviceProperties.properties.vendorID; + if (vendorId == 0x10de) { + // Nvidia uses 10|8|8|6 encoding. + (void)SDL_snprintf( + driverVer, + SDL_arraysize(driverVer), + "%d.%d.%d.%d", + (rawDriverVer >> 22) & 0x3ff, + (rawDriverVer >> 14) & 0xff, + (rawDriverVer >> 6) & 0xff, + rawDriverVer & 0x3f); + } +#ifdef SDL_PLATFORM_WINDOWS + else if (vendorId == 0x8086) { + // Intel uses 18|14 encoding on Windows only. + (void)SDL_snprintf( + driverVer, + SDL_arraysize(driverVer), + "%d.%d", + (rawDriverVer >> 14) & 0x3ffff, + rawDriverVer & 0x3fff); + } +#endif + else { + // Assume standard Vulkan 10|10|12 encoding for everything else. AMD and + // Mesa are known to use this encoding. + (void)SDL_snprintf( + driverVer, + SDL_arraysize(driverVer), + "%d.%d.%d", + (rawDriverVer >> 22) & 0x3ff, + (rawDriverVer >> 12) & 0x3ff, + rawDriverVer & 0xfff); + } + SDL_SetStringProperty( + renderer->props, + SDL_PROP_GPU_DEVICE_DRIVER_VERSION_STRING, + driverVer); + // Log this only if VK_KHR_driver_properties is not available. + if (renderer->supports.KHR_driver_properties) { - SDL_LogInfo( - SDL_LOG_CATEGORY_GPU, - "Vulkan Driver: %s %s", - renderer->physicalDeviceDriverProperties.driverName, - renderer->physicalDeviceDriverProperties.driverInfo); - SDL_LogInfo( - SDL_LOG_CATEGORY_GPU, - "Vulkan Conformance: %u.%u.%u", - renderer->physicalDeviceDriverProperties.conformanceVersion.major, - renderer->physicalDeviceDriverProperties.conformanceVersion.minor, - renderer->physicalDeviceDriverProperties.conformanceVersion.patch); + // Record driver name and version + const char *driverName = renderer->physicalDeviceDriverProperties.driverName; + const char *driverInfo = renderer->physicalDeviceDriverProperties.driverInfo; + SDL_SetStringProperty( + renderer->props, + SDL_PROP_GPU_DEVICE_DRIVER_NAME_STRING, + driverName); + SDL_SetStringProperty( + renderer->props, + SDL_PROP_GPU_DEVICE_DRIVER_INFO_STRING, + driverInfo); + if (verboseLogs) { + // FIXME: driverInfo can be a multiline string. + SDL_LogInfo(SDL_LOG_CATEGORY_GPU, "Vulkan Driver: %s %s", driverName, driverInfo); + } + + // Record conformance level + if (verboseLogs) { + char conformance[64]; + (void)SDL_snprintf( + conformance, + SDL_arraysize(conformance), + "%u.%u.%u.%u", + renderer->physicalDeviceDriverProperties.conformanceVersion.major, + renderer->physicalDeviceDriverProperties.conformanceVersion.minor, + renderer->physicalDeviceDriverProperties.conformanceVersion.subminor, + renderer->physicalDeviceDriverProperties.conformanceVersion.patch); + SDL_LogInfo(SDL_LOG_CATEGORY_GPU, "Vulkan Conformance: %s", conformance); + } } else { - SDL_LogWarn(SDL_LOG_CATEGORY_GPU, "KHR_driver_properties unsupported! Bother your vendor about this!"); + if (verboseLogs) { + SDL_LogInfo(SDL_LOG_CATEGORY_GPU, "Vulkan Driver: %s", driverVer); + } } if (!VULKAN_INTERNAL_CreateLogicalDevice( @@ -11654,6 +11836,7 @@ static SDL_GPUDevice *VULKAN_CreateDevice(bool debugMode, bool preferLowPower, S ASSIGN_DRIVER(VULKAN) result->driverData = (SDL_GPURenderer *)renderer; + result->shader_formats = SDL_GPU_SHADERFORMAT_SPIRV; /* * Create initial swapchain array @@ -11673,6 +11856,9 @@ static SDL_GPUDevice *VULKAN_CreateDevice(bool debugMode, bool preferLowPower, S renderer->acquireUniformBufferLock = SDL_CreateMutex(); renderer->renderPassFetchLock = SDL_CreateMutex(); renderer->framebufferFetchLock = SDL_CreateMutex(); + renderer->graphicsPipelineLayoutFetchLock = SDL_CreateMutex(); + renderer->computePipelineLayoutFetchLock = SDL_CreateMutex(); + renderer->descriptorSetLayoutFetchLock = SDL_CreateMutex(); renderer->windowLock = SDL_CreateMutex(); /* @@ -11733,7 +11919,7 @@ static SDL_GPUDevice *VULKAN_CreateDevice(bool debugMode, bool preferLowPower, S renderer->renderPassHashTable = SDL_CreateHashTable( 0, // !!! FIXME: a real guess here, for a _minimum_ if not a maximum, could be useful. - false, // manually synchronized due to timing + false, // manually synchronized due to lookup timing VULKAN_INTERNAL_RenderPassHashFunction, VULKAN_INTERNAL_RenderPassHashKeyMatch, VULKAN_INTERNAL_RenderPassHashDestroy, @@ -11749,7 +11935,7 @@ static SDL_GPUDevice *VULKAN_CreateDevice(bool debugMode, bool preferLowPower, S renderer->graphicsPipelineResourceLayoutHashTable = SDL_CreateHashTable( 0, // !!! FIXME: a real guess here, for a _minimum_ if not a maximum, could be useful. - true, // thread-safe + false, // manually synchronized due to lookup timing VULKAN_INTERNAL_GraphicsPipelineResourceLayoutHashFunction, VULKAN_INTERNAL_GraphicsPipelineResourceLayoutHashKeyMatch, VULKAN_INTERNAL_GraphicsPipelineResourceLayoutHashDestroy, @@ -11757,7 +11943,7 @@ static SDL_GPUDevice *VULKAN_CreateDevice(bool debugMode, bool preferLowPower, S renderer->computePipelineResourceLayoutHashTable = SDL_CreateHashTable( 0, // !!! FIXME: a real guess here, for a _minimum_ if not a maximum, could be useful. - true, // thread-safe + false, // manually synchronized due to lookup timing VULKAN_INTERNAL_ComputePipelineResourceLayoutHashFunction, VULKAN_INTERNAL_ComputePipelineResourceLayoutHashKeyMatch, VULKAN_INTERNAL_ComputePipelineResourceLayoutHashDestroy, @@ -11765,7 +11951,7 @@ static SDL_GPUDevice *VULKAN_CreateDevice(bool debugMode, bool preferLowPower, S renderer->descriptorSetLayoutHashTable = SDL_CreateHashTable( 0, // !!! FIXME: a real guess here, for a _minimum_ if not a maximum, could be useful. - true, // thread-safe + false, // manually synchronized due to lookup timing VULKAN_INTERNAL_DescriptorSetLayoutHashFunction, VULKAN_INTERNAL_DescriptorSetLayoutHashKeyMatch, VULKAN_INTERNAL_DescriptorSetLayoutHashDestroy, @@ -11844,7 +12030,6 @@ static SDL_GPUDevice *VULKAN_CreateDevice(bool debugMode, bool preferLowPower, S SDL_GPUBootstrap VulkanDriver = { "vulkan", - SDL_GPU_SHADERFORMAT_SPIRV, VULKAN_PrepareDriver, VULKAN_CreateDevice }; diff --git a/libs/SDL3/src/haptic/SDL_haptic.c b/libs/SDL3/src/haptic/SDL_haptic.c index 1c11db68..2e8024fa 100644 --- a/libs/SDL3/src/haptic/SDL_haptic.c +++ b/libs/SDL3/src/haptic/SDL_haptic.c @@ -21,6 +21,9 @@ #include "SDL_internal.h" #include "SDL_syshaptic.h" +#ifdef SDL_JOYSTICK_HIDAPI +#include "hidapi/SDL_hidapihaptic.h" +#endif #include "SDL_haptic_c.h" #include "../joystick/SDL_joystick_c.h" // For SDL_IsJoystickValid #include "../SDL_hints_c.h" @@ -112,7 +115,17 @@ static SDL_Haptic *SDL_haptics = NULL; bool SDL_InitHaptics(void) { - return SDL_SYS_HapticInit(); + if (!SDL_SYS_HapticInit()) { + return false; + } + #ifdef SDL_JOYSTICK_HIDAPI + if (!SDL_HIDAPI_HapticInit()) { + SDL_SYS_HapticQuit(); + return false; + } + #endif + + return true; } static bool SDL_GetHapticIndex(SDL_HapticID instance_id, int *driver_index) @@ -205,7 +218,6 @@ SDL_Haptic *SDL_OpenHaptic(SDL_HapticID instance_id) } // Initialize the haptic device - SDL_SetObjectValid(haptic, SDL_OBJECT_TYPE_HAPTIC, true); haptic->instance_id = instance_id; haptic->rumble_id = -1; if (!SDL_SYS_HapticOpen(haptic)) { @@ -227,6 +239,8 @@ SDL_Haptic *SDL_OpenHaptic(SDL_HapticID instance_id) haptic->next = SDL_haptics; SDL_haptics = haptic; + SDL_SetObjectValid(haptic, SDL_OBJECT_TYPE_HAPTIC, true); + // Disable autocenter and set gain to max. if (haptic->supported & SDL_HAPTIC_GAIN) { SDL_SetHapticGain(haptic, 100); @@ -295,7 +309,11 @@ bool SDL_IsJoystickHaptic(SDL_Joystick *joystick) // Must be a valid joystick if (SDL_IsJoystickValid(joystick) && !SDL_IsGamepad(SDL_GetJoystickID(joystick))) { + #ifdef SDL_JOYSTICK_HIDAPI + result = SDL_SYS_JoystickIsHaptic(joystick) || SDL_HIDAPI_JoystickIsHaptic(joystick); + #else result = SDL_SYS_JoystickIsHaptic(joystick); + #endif } } SDL_UnlockJoysticks(); @@ -310,16 +328,8 @@ SDL_Haptic *SDL_OpenHapticFromJoystick(SDL_Joystick *joystick) SDL_LockJoysticks(); { - // Must be a valid joystick - if (!SDL_IsJoystickValid(joystick)) { - SDL_SetError("Haptic: Joystick isn't valid."); - SDL_UnlockJoysticks(); - return NULL; - } - - // Joystick must be haptic - if (SDL_IsGamepad(SDL_GetJoystickID(joystick)) || - !SDL_SYS_JoystickIsHaptic(joystick)) { + // Joystick must be valid and haptic + if (!SDL_IsJoystickHaptic(joystick)) { SDL_SetError("Haptic: Joystick isn't a haptic device."); SDL_UnlockJoysticks(); return NULL; @@ -328,7 +338,11 @@ SDL_Haptic *SDL_OpenHapticFromJoystick(SDL_Joystick *joystick) hapticlist = SDL_haptics; // Check to see if joystick's haptic is already open while (hapticlist) { + #ifdef SDL_JOYSTICK_HIDAPI + if (SDL_SYS_JoystickSameHaptic(hapticlist, joystick) || SDL_HIDAPI_JoystickSameHaptic(hapticlist, joystick)) { + #else if (SDL_SYS_JoystickSameHaptic(hapticlist, joystick)) { + #endif haptic = hapticlist; ++haptic->ref_count; SDL_UnlockJoysticks(); @@ -349,6 +363,16 @@ SDL_Haptic *SDL_OpenHapticFromJoystick(SDL_Joystick *joystick) */ SDL_SetObjectValid(haptic, SDL_OBJECT_TYPE_HAPTIC, true); haptic->rumble_id = -1; + #ifdef SDL_JOYSTICK_HIDAPI + if (SDL_HIDAPI_JoystickIsHaptic(joystick)) { + if (!SDL_HIDAPI_HapticOpenFromJoystick(haptic, joystick)) { + SDL_SetError("Haptic: SDL_HIDAPI_HapticOpenFromJoystick failed."); + SDL_free(haptic); + SDL_UnlockJoysticks(); + return NULL; + } + } else + #endif if (!SDL_SYS_HapticOpenFromJoystick(haptic, joystick)) { SDL_SetError("Haptic: SDL_SYS_HapticOpenFromJoystick failed."); SDL_SetObjectValid(haptic, SDL_OBJECT_TYPE_HAPTIC, false); @@ -379,12 +403,22 @@ SDL_Haptic *SDL_OpenHapticFromJoystick(SDL_Joystick *joystick) haptic->next = SDL_haptics; SDL_haptics = haptic; + SDL_SetObjectValid(haptic, SDL_OBJECT_TYPE_HAPTIC, true); + + // Disable autocenter and set gain to max. + if (haptic->supported & SDL_HAPTIC_GAIN) { + SDL_SetHapticGain(haptic, 100); + } + if (haptic->supported & SDL_HAPTIC_AUTOCENTER) { + SDL_SetHapticAutocenter(haptic, 0); + } + return haptic; } void SDL_CloseHaptic(SDL_Haptic *haptic) { - int i; + SDL_HapticEffectID i; SDL_Haptic *hapticlist; SDL_Haptic *hapticlistprev; @@ -395,13 +429,20 @@ void SDL_CloseHaptic(SDL_Haptic *haptic) return; } - // Close it, properly removing effects if needed - for (i = 0; i < haptic->neffects; i++) { - if (haptic->effects[i].hweffect != NULL) { - SDL_DestroyHapticEffect(haptic, i); + #ifdef SDL_JOYSTICK_HIDAPI + if (SDL_HIDAPI_HapticIsHidapi(haptic)) { + SDL_HIDAPI_HapticClose(haptic); + } else + #endif + { + // Close it, properly removing effects if needed + for (i = 0; i < haptic->neffects; i++) { + if (haptic->effects[i].hweffect != NULL) { + SDL_DestroyHapticEffect(haptic, i); + } } + SDL_SYS_HapticClose(haptic); } - SDL_SYS_HapticClose(haptic); SDL_SetObjectValid(haptic, SDL_OBJECT_TYPE_HAPTIC, false); // Remove from the list @@ -433,6 +474,9 @@ void SDL_QuitHaptics(void) SDL_CloseHaptic(SDL_haptics); } + #ifdef SDL_JOYSTICK_HIDAPI + SDL_HIDAPI_HapticQuit(); + #endif SDL_SYS_HapticQuit(); } @@ -478,9 +522,9 @@ bool SDL_HapticEffectSupported(SDL_Haptic *haptic, const SDL_HapticEffect *effec return false; } -int SDL_CreateHapticEffect(SDL_Haptic *haptic, const SDL_HapticEffect *effect) +SDL_HapticEffectID SDL_CreateHapticEffect(SDL_Haptic *haptic, const SDL_HapticEffect *effect) { - int i; + SDL_HapticEffectID i; CHECK_HAPTIC_MAGIC(haptic, -1); @@ -495,6 +539,12 @@ int SDL_CreateHapticEffect(SDL_Haptic *haptic, const SDL_HapticEffect *effect) return -1; } + #ifdef SDL_JOYSTICK_HIDAPI + if (SDL_HIDAPI_HapticIsHidapi(haptic)) { + return SDL_HIDAPI_HapticNewEffect(haptic, effect); + } + #endif + // See if there's a free slot for (i = 0; i < haptic->neffects; i++) { if (haptic->effects[i].hweffect == NULL) { @@ -514,7 +564,7 @@ int SDL_CreateHapticEffect(SDL_Haptic *haptic, const SDL_HapticEffect *effect) return -1; } -static bool ValidEffect(SDL_Haptic *haptic, int effect) +static bool ValidEffect(SDL_Haptic *haptic, SDL_HapticEffectID effect) { if ((effect < 0) || (effect >= haptic->neffects)) { SDL_SetError("Haptic: Invalid effect identifier."); @@ -523,10 +573,16 @@ static bool ValidEffect(SDL_Haptic *haptic, int effect) return true; } -bool SDL_UpdateHapticEffect(SDL_Haptic *haptic, int effect, const SDL_HapticEffect *data) +bool SDL_UpdateHapticEffect(SDL_Haptic *haptic, SDL_HapticEffectID effect, const SDL_HapticEffect *data) { CHECK_HAPTIC_MAGIC(haptic, false); + #ifdef SDL_JOYSTICK_HIDAPI + if (SDL_HIDAPI_HapticIsHidapi(haptic)) { + return SDL_HIDAPI_HapticUpdateEffect(haptic, effect, data); + } + #endif + if (!ValidEffect(haptic, effect)) { return false; } @@ -550,10 +606,16 @@ bool SDL_UpdateHapticEffect(SDL_Haptic *haptic, int effect, const SDL_HapticEffe return true; } -bool SDL_RunHapticEffect(SDL_Haptic *haptic, int effect, Uint32 iterations) +bool SDL_RunHapticEffect(SDL_Haptic *haptic, SDL_HapticEffectID effect, Uint32 iterations) { CHECK_HAPTIC_MAGIC(haptic, false); + #ifdef SDL_JOYSTICK_HIDAPI + if (SDL_HIDAPI_HapticIsHidapi(haptic)) { + return SDL_HIDAPI_HapticRunEffect(haptic, effect, iterations); + } + #endif + if (!ValidEffect(haptic, effect)) { return false; } @@ -566,10 +628,16 @@ bool SDL_RunHapticEffect(SDL_Haptic *haptic, int effect, Uint32 iterations) return true; } -bool SDL_StopHapticEffect(SDL_Haptic *haptic, int effect) +bool SDL_StopHapticEffect(SDL_Haptic *haptic, SDL_HapticEffectID effect) { CHECK_HAPTIC_MAGIC(haptic, false); + #ifdef SDL_JOYSTICK_HIDAPI + if (SDL_HIDAPI_HapticIsHidapi(haptic)) { + return SDL_HIDAPI_HapticStopEffect(haptic, effect); + } + #endif + if (!ValidEffect(haptic, effect)) { return false; } @@ -582,10 +650,17 @@ bool SDL_StopHapticEffect(SDL_Haptic *haptic, int effect) return true; } -void SDL_DestroyHapticEffect(SDL_Haptic *haptic, int effect) +void SDL_DestroyHapticEffect(SDL_Haptic *haptic, SDL_HapticEffectID effect) { CHECK_HAPTIC_MAGIC(haptic,); + #ifdef SDL_JOYSTICK_HIDAPI + if (SDL_HIDAPI_HapticIsHidapi(haptic)) { + SDL_HIDAPI_HapticDestroyEffect(haptic, effect); + return; + } + #endif + if (!ValidEffect(haptic, effect)) { return; } @@ -598,10 +673,16 @@ void SDL_DestroyHapticEffect(SDL_Haptic *haptic, int effect) SDL_SYS_HapticDestroyEffect(haptic, &haptic->effects[effect]); } -bool SDL_GetHapticEffectStatus(SDL_Haptic *haptic, int effect) +bool SDL_GetHapticEffectStatus(SDL_Haptic *haptic, SDL_HapticEffectID effect) { CHECK_HAPTIC_MAGIC(haptic, false); + #ifdef SDL_JOYSTICK_HIDAPI + if (SDL_HIDAPI_HapticIsHidapi(haptic)) { + return SDL_HIDAPI_HapticGetEffectStatus(haptic, effect); + } + #endif + if (!ValidEffect(haptic, effect)) { return false; } @@ -648,6 +729,12 @@ bool SDL_SetHapticGain(SDL_Haptic *haptic, int gain) real_gain = gain; } + #ifdef SDL_JOYSTICK_HIDAPI + if (SDL_HIDAPI_HapticIsHidapi(haptic)) { + return SDL_HIDAPI_HapticSetGain(haptic, real_gain); + } + #endif + return SDL_SYS_HapticSetGain(haptic, real_gain); } @@ -663,6 +750,12 @@ bool SDL_SetHapticAutocenter(SDL_Haptic *haptic, int autocenter) return SDL_SetError("Haptic: Autocenter must be between 0 and 100."); } + #ifdef SDL_JOYSTICK_HIDAPI + if (SDL_HIDAPI_HapticIsHidapi(haptic)) { + return SDL_HIDAPI_HapticSetAutocenter(haptic, autocenter); + } + #endif + return SDL_SYS_HapticSetAutocenter(haptic, autocenter); } @@ -674,6 +767,12 @@ bool SDL_PauseHaptic(SDL_Haptic *haptic) return SDL_SetError("Haptic: Device does not support setting pausing."); } + #ifdef SDL_JOYSTICK_HIDAPI + if (SDL_HIDAPI_HapticIsHidapi(haptic)) { + return SDL_HIDAPI_HapticPause(haptic); + } + #endif + return SDL_SYS_HapticPause(haptic); } @@ -685,6 +784,12 @@ bool SDL_ResumeHaptic(SDL_Haptic *haptic) return true; // Not going to be paused, so we pretend it's unpaused. } + #ifdef SDL_JOYSTICK_HIDAPI + if (SDL_HIDAPI_HapticIsHidapi(haptic)) { + return SDL_HIDAPI_HapticResume(haptic); + } + #endif + return SDL_SYS_HapticResume(haptic); } @@ -692,6 +797,12 @@ bool SDL_StopHapticEffects(SDL_Haptic *haptic) { CHECK_HAPTIC_MAGIC(haptic, false); + #ifdef SDL_JOYSTICK_HIDAPI + if (SDL_HIDAPI_HapticIsHidapi(haptic)) { + return SDL_HIDAPI_HapticStopAll(haptic); + } + #endif + return SDL_SYS_HapticStopAll(haptic); } diff --git a/libs/SDL3/src/haptic/SDL_syshaptic.h b/libs/SDL3/src/haptic/SDL_syshaptic.h index ec60a71e..21b3afd1 100644 --- a/libs/SDL3/src/haptic/SDL_syshaptic.h +++ b/libs/SDL3/src/haptic/SDL_syshaptic.h @@ -52,7 +52,7 @@ struct SDL_Haptic struct haptic_hwdata *hwdata; // Driver dependent int ref_count; // Count for multiple opens - int rumble_id; // ID of rumble effect for simple rumble API. + SDL_HapticEffectID rumble_id; // ID of rumble effect for simple rumble API. SDL_HapticEffect rumble_effect; // Rumble effect. struct SDL_Haptic *next; // pointer to next haptic we have allocated }; diff --git a/libs/SDL3/src/haptic/hidapi/SDL_hidapihaptic.c b/libs/SDL3/src/haptic/hidapi/SDL_hidapihaptic.c new file mode 100644 index 00000000..7f59f6c9 --- /dev/null +++ b/libs/SDL3/src/haptic/hidapi/SDL_hidapihaptic.c @@ -0,0 +1,306 @@ +/* + Simple DirectMedia Layer + Copyright (C) 2025 Katharine Chui + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +#ifdef SDL_JOYSTICK_HIDAPI + +#include "SDL_hidapihaptic.h" +#include "SDL_hidapihaptic_c.h" +#include "SDL3/SDL_mutex.h" +#include "SDL3/SDL_error.h" + +extern struct SDL_JoystickDriver SDL_HIDAPI_JoystickDriver; + +typedef struct haptic_list_node +{ + SDL_Haptic *haptic; + struct haptic_list_node *next; +} haptic_list_node; + +static haptic_list_node *haptic_list_head = NULL; +static SDL_Mutex *haptic_list_mutex = NULL; + +static SDL_HIDAPI_HapticDriver *drivers[] = { + #ifdef SDL_HAPTIC_HIDAPI_LG4FF + &SDL_HIDAPI_HapticDriverLg4ff, + #endif + NULL +}; + +bool SDL_HIDAPI_HapticInit(void) +{ + haptic_list_head = NULL; + haptic_list_mutex = SDL_CreateMutex(); + if (haptic_list_mutex == NULL) { + return SDL_OutOfMemory(); + } + return true; +} + +bool SDL_HIDAPI_HapticIsHidapi(SDL_Haptic *haptic) +{ + haptic_list_node *cur; + bool ret = false; + + SDL_LockMutex(haptic_list_mutex); + cur = haptic_list_head; + while (cur != NULL) { + if (cur->haptic == haptic) { + ret = true; + break; + } + cur = cur->next; + } + + SDL_UnlockMutex(haptic_list_mutex); + + return ret; +} + + +bool SDL_HIDAPI_JoystickIsHaptic(SDL_Joystick *joystick) +{ + const int numdrivers = SDL_arraysize(drivers) - 1; + int i; + + SDL_AssertJoysticksLocked(); + + if (joystick->driver != &SDL_HIDAPI_JoystickDriver) { + return false; + } + + for (i = 0; i < numdrivers; ++i) { + if (drivers[i]->JoystickSupported(joystick)) { + return true; + } + } + return false; +} + +bool SDL_HIDAPI_HapticOpenFromJoystick(SDL_Haptic *haptic, SDL_Joystick *joystick) +{ + const int numdrivers = SDL_arraysize(drivers) - 1; + int i; + + SDL_AssertJoysticksLocked(); + + if (joystick->driver != &SDL_HIDAPI_JoystickDriver) { + return SDL_SetError("Cannot open hidapi haptic from non hidapi joystick"); + } + + for (i = 0; i < numdrivers; ++i) { + if (drivers[i]->JoystickSupported(joystick)) { + SDL_HIDAPI_HapticDevice *device; + haptic_list_node *list_node; + // the driver is responsible for calling SDL_SetError + void *ctx = drivers[i]->Open(joystick); + if (ctx == NULL) { + return false; + } + + device = SDL_malloc(sizeof(SDL_HIDAPI_HapticDevice)); + if (device == NULL) { + SDL_HIDAPI_HapticDevice temp; + temp.ctx = ctx; + temp.driver = drivers[i]; + temp.joystick = joystick; + temp.driver->Close(&temp); + return SDL_OutOfMemory(); + } + + device->driver = drivers[i]; + device->haptic = haptic; + device->joystick = joystick; + device->ctx = ctx; + + list_node = SDL_malloc(sizeof(haptic_list_node)); + if (list_node == NULL) { + device->driver->Close(device); + SDL_free(device); + return SDL_OutOfMemory(); + } + + haptic->hwdata = (struct haptic_hwdata *)device; + + // this is outside of the syshaptic driver + + haptic->neffects = device->driver->NumEffects(device); + haptic->nplaying = device->driver->NumEffectsPlaying(device); + haptic->supported = device->driver->GetFeatures(device); + haptic->naxes = device->driver->NumAxes(device); + + // outside of SYS_HAPTIC + haptic->instance_id = 255; + + list_node->haptic = haptic; + list_node->next = NULL; + + // grab a joystick ref so that it doesn't get fully destroyed before the haptic is closed + SDL_OpenJoystick(SDL_GetJoystickID(joystick)); + + SDL_LockMutex(haptic_list_mutex); + if (haptic_list_head == NULL) { + haptic_list_head = list_node; + } else { + haptic_list_node *cur = haptic_list_head; + while(cur->next != NULL) { + cur = cur->next; + } + cur->next = list_node; + } + SDL_UnlockMutex(haptic_list_mutex); + + return true; + } + } + + return SDL_SetError("No supported HIDAPI haptic driver found for joystick"); +} + +bool SDL_HIDAPI_JoystickSameHaptic(SDL_Haptic *haptic, SDL_Joystick *joystick) +{ + SDL_HIDAPI_HapticDevice *device; + + SDL_AssertJoysticksLocked(); + if (joystick->driver != &SDL_HIDAPI_JoystickDriver) { + return false; + } + + device = (SDL_HIDAPI_HapticDevice *)haptic->hwdata; + + if (joystick == device->joystick) { + return true; + } + return false; +} + +void SDL_HIDAPI_HapticClose(SDL_Haptic *haptic) +{ + haptic_list_node *cur, *last; + + SDL_LockMutex(haptic_list_mutex); + + cur = haptic_list_head; + last = NULL; + while (cur != NULL) { + if (cur->haptic == haptic) { + SDL_HIDAPI_HapticDevice *device = (SDL_HIDAPI_HapticDevice *)haptic->hwdata; + + device->driver->Close(device); + + // a reference was grabbed during open, now release it + SDL_CloseJoystick(device->joystick); + + if (cur == haptic_list_head) { + haptic_list_head = cur->next; + } else { + last->next = cur->next; + } + + SDL_free(device->ctx); + SDL_free(device); + SDL_free(cur); + SDL_UnlockMutex(haptic_list_mutex); + return; + } + last = cur; + cur = cur->next; + } + + SDL_UnlockMutex(haptic_list_mutex); +} + +void SDL_HIDAPI_HapticQuit(void) +{ + // the list is cleared in SDL_haptic.c + if (haptic_list_mutex != NULL) { + SDL_DestroyMutex(haptic_list_mutex); + haptic_list_mutex = NULL; + } +} + +SDL_HapticEffectID SDL_HIDAPI_HapticNewEffect(SDL_Haptic *haptic, const SDL_HapticEffect *base) +{ + SDL_HIDAPI_HapticDevice *device = (SDL_HIDAPI_HapticDevice *)haptic->hwdata; + return device->driver->CreateEffect(device, base); +} + +bool SDL_HIDAPI_HapticUpdateEffect(SDL_Haptic *haptic, SDL_HapticEffectID id, const SDL_HapticEffect *data) +{ + SDL_HIDAPI_HapticDevice *device = (SDL_HIDAPI_HapticDevice *)haptic->hwdata; + return device->driver->UpdateEffect(device, id, data); +} + +bool SDL_HIDAPI_HapticRunEffect(SDL_Haptic *haptic, SDL_HapticEffectID id, Uint32 iterations) +{ + SDL_HIDAPI_HapticDevice *device = (SDL_HIDAPI_HapticDevice *)haptic->hwdata; + return device->driver->RunEffect(device, id, iterations); +} + +bool SDL_HIDAPI_HapticStopEffect(SDL_Haptic *haptic, SDL_HapticEffectID id) +{ + SDL_HIDAPI_HapticDevice *device = (SDL_HIDAPI_HapticDevice *)haptic->hwdata; + return device->driver->StopEffect(device, id); +} + +void SDL_HIDAPI_HapticDestroyEffect(SDL_Haptic *haptic, SDL_HapticEffectID id) +{ + SDL_HIDAPI_HapticDevice *device = (SDL_HIDAPI_HapticDevice *)haptic->hwdata; + device->driver->DestroyEffect(device, id); +} + +bool SDL_HIDAPI_HapticGetEffectStatus(SDL_Haptic *haptic, SDL_HapticEffectID id) +{ + SDL_HIDAPI_HapticDevice *device = (SDL_HIDAPI_HapticDevice *)haptic->hwdata; + return device->driver->GetEffectStatus(device, id); +} + +bool SDL_HIDAPI_HapticSetGain(SDL_Haptic *haptic, int gain) +{ + SDL_HIDAPI_HapticDevice *device = (SDL_HIDAPI_HapticDevice *)haptic->hwdata; + return device->driver->SetGain(device, gain); +} + +bool SDL_HIDAPI_HapticSetAutocenter(SDL_Haptic *haptic, int autocenter) +{ + SDL_HIDAPI_HapticDevice *device = (SDL_HIDAPI_HapticDevice *)haptic->hwdata; + return device->driver->SetAutocenter(device, autocenter); +} + +bool SDL_HIDAPI_HapticPause(SDL_Haptic *haptic) +{ + SDL_HIDAPI_HapticDevice *device = (SDL_HIDAPI_HapticDevice *)haptic->hwdata; + return device->driver->Pause(device); +} + +bool SDL_HIDAPI_HapticResume(SDL_Haptic *haptic) +{ + SDL_HIDAPI_HapticDevice *device = (SDL_HIDAPI_HapticDevice *)haptic->hwdata; + return device->driver->Resume(device); +} + +bool SDL_HIDAPI_HapticStopAll(SDL_Haptic *haptic) +{ + SDL_HIDAPI_HapticDevice *device = (SDL_HIDAPI_HapticDevice *)haptic->hwdata; + return device->driver->StopEffects(device); +} + +#endif //SDL_JOYSTICK_HIDAPI diff --git a/libs/SDL3/src/haptic/hidapi/SDL_hidapihaptic.h b/libs/SDL3/src/haptic/hidapi/SDL_hidapihaptic.h new file mode 100644 index 00000000..4734bbc5 --- /dev/null +++ b/libs/SDL3/src/haptic/hidapi/SDL_hidapihaptic.h @@ -0,0 +1,48 @@ +/* + Simple DirectMedia Layer + Copyright (C) 2025 Katharine Chui + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/* + All hid command sent and effect rendering are ported from https://github.com/berarma/new-lg4ff +*/ + +#ifndef SDL_hidapihaptic_h_ +#define SDL_hidapihaptic_h_ + +bool SDL_HIDAPI_HapticInit(void); +bool SDL_HIDAPI_HapticIsHidapi(SDL_Haptic *haptic); +bool SDL_HIDAPI_JoystickIsHaptic(SDL_Joystick *joystick); +bool SDL_HIDAPI_HapticOpenFromJoystick(SDL_Haptic *haptic, SDL_Joystick *joystick); +bool SDL_HIDAPI_JoystickSameHaptic(SDL_Haptic *haptic, SDL_Joystick *joystick); +void SDL_HIDAPI_HapticClose(SDL_Haptic *haptic); +void SDL_HIDAPI_HapticQuit(void); +SDL_HapticEffectID SDL_HIDAPI_HapticNewEffect(SDL_Haptic *haptic, const SDL_HapticEffect *base); +bool SDL_HIDAPI_HapticUpdateEffect(SDL_Haptic *haptic, SDL_HapticEffectID id, const SDL_HapticEffect *data); +bool SDL_HIDAPI_HapticRunEffect(SDL_Haptic *haptic, SDL_HapticEffectID id, Uint32 iterations); +bool SDL_HIDAPI_HapticStopEffect(SDL_Haptic *haptic, SDL_HapticEffectID id); +void SDL_HIDAPI_HapticDestroyEffect(SDL_Haptic *haptic, SDL_HapticEffectID id); +bool SDL_HIDAPI_HapticGetEffectStatus(SDL_Haptic *haptic, SDL_HapticEffectID id); +bool SDL_HIDAPI_HapticSetGain(SDL_Haptic *haptic, int gain); +bool SDL_HIDAPI_HapticSetAutocenter(SDL_Haptic *haptic, int autocenter); +bool SDL_HIDAPI_HapticPause(SDL_Haptic *haptic); +bool SDL_HIDAPI_HapticResume(SDL_Haptic *haptic); +bool SDL_HIDAPI_HapticStopAll(SDL_Haptic *haptic); + +#endif //SDL_hidapihaptic_h_ diff --git a/libs/SDL3/src/haptic/hidapi/SDL_hidapihaptic_c.h b/libs/SDL3/src/haptic/hidapi/SDL_hidapihaptic_c.h new file mode 100644 index 00000000..7d678811 --- /dev/null +++ b/libs/SDL3/src/haptic/hidapi/SDL_hidapihaptic_c.h @@ -0,0 +1,70 @@ +/* + Simple DirectMedia Layer + Copyright (C) 2025 Katharine Chui + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +#ifndef SDL_hidapihaptic_c_h_ +#define SDL_hidapihaptic_c_h_ + +#include "SDL3/SDL_haptic.h" +#include "SDL3/SDL_joystick.h" +#include "../SDL_syshaptic.h" +#include "../../joystick/SDL_joystick_c.h" // accessing _SDL_JoystickDriver +#include "../../joystick/SDL_sysjoystick.h" // accessing _SDL_Joystick + +#define SDL_HAPTIC_HIDAPI_LG4FF + +typedef struct SDL_HIDAPI_HapticDriver SDL_HIDAPI_HapticDriver; +typedef struct SDL_HIDAPI_HapticDevice +{ + SDL_Haptic *haptic; /* related haptic ref */ + SDL_Joystick *joystick; /* related hidapi joystick */ + SDL_HIDAPI_HapticDriver *driver; /* driver to use */ + void *ctx; /* driver specific context */ +} SDL_HIDAPI_HapticDevice; + +struct SDL_HIDAPI_HapticDriver +{ + bool (*JoystickSupported)(SDL_Joystick *joystick); /* return true if haptic can be opened from the joystick */ + void *(*Open)(SDL_Joystick *joystick); /* returns a driver context allocated with SDL_malloc, or null if it cannot be allocated */ + + /* functions below need to handle the possibility of a null joystick instance, indicating the absence of the joystick */ + void (*Close)(SDL_HIDAPI_HapticDevice *device); /* cleanup resources allocated during Open, do NOT free driver context created in Open */ + + /* below mirror SDL_haptic.h effect interfaces */ + int (*NumEffects)(SDL_HIDAPI_HapticDevice *device); /* returns supported number of effects the device can store */ + int (*NumEffectsPlaying)(SDL_HIDAPI_HapticDevice *device); /* returns supported number of effects the device can play concurrently */ + Uint32 (*GetFeatures)(SDL_HIDAPI_HapticDevice *device); /* returns supported effects in a bitmask */ + int (*NumAxes)(SDL_HIDAPI_HapticDevice *device); /* returns the number of haptic axes */ + SDL_HapticEffectID (*CreateEffect)(SDL_HIDAPI_HapticDevice *device, const SDL_HapticEffect *data); /* returns effect id if created correctly, negative number on error */ + bool (*UpdateEffect)(SDL_HIDAPI_HapticDevice *device, SDL_HapticEffectID id, const SDL_HapticEffect *data); /* returns true on success, false on error */ + bool (*RunEffect)(SDL_HIDAPI_HapticDevice *device, SDL_HapticEffectID id, Uint32 iterations); /* returns true on success, false on error */ + bool (*StopEffect)(SDL_HIDAPI_HapticDevice *device, SDL_HapticEffectID id); /* returns true on success, false on error */ + void (*DestroyEffect)(SDL_HIDAPI_HapticDevice *device, SDL_HapticEffectID id); + bool (*GetEffectStatus)(SDL_HIDAPI_HapticDevice *device, SDL_HapticEffectID id); /* returns true if playing, false if not playing or on error */ + bool (*SetGain)(SDL_HIDAPI_HapticDevice *device, int gain); /* gain 0 - 100, returns true on success, false on error */ + bool (*SetAutocenter)(SDL_HIDAPI_HapticDevice *device, int autocenter); /* autocenter 0 - 100, returns true on success, false on error */ + bool (*Pause)(SDL_HIDAPI_HapticDevice *device); /* returns true on success, false on error */ + bool (*Resume)(SDL_HIDAPI_HapticDevice *device); /* returns true on success, false on error */ + bool (*StopEffects)(SDL_HIDAPI_HapticDevice *device); /* returns true on success, false on error */ +}; + +extern SDL_HIDAPI_HapticDriver SDL_HIDAPI_HapticDriverLg4ff; + +#endif //SDL_joystick_c_h_ diff --git a/libs/SDL3/src/haptic/hidapi/SDL_hidapihaptic_lg4ff.c b/libs/SDL3/src/haptic/hidapi/SDL_hidapihaptic_lg4ff.c new file mode 100644 index 00000000..ad9bf0d9 --- /dev/null +++ b/libs/SDL3/src/haptic/hidapi/SDL_hidapihaptic_lg4ff.c @@ -0,0 +1,1264 @@ +/* + Simple DirectMedia Layer + Copyright (C) 2025 Simon Wood + Copyright (C) 2025 Michal Malý + Copyright (C) 2025 Bernat Arlandis + Copyright (C) 2025 Katharine Chui + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +#include "SDL_internal.h" + +#ifdef SDL_JOYSTICK_HIDAPI + +#include "SDL_hidapihaptic_c.h" + +#ifdef SDL_HAPTIC_HIDAPI_LG4FF + +#include "SDL3/SDL_thread.h" +#include "SDL3/SDL_mutex.h" +#include "SDL3/SDL_timer.h" + +#include + +#define USB_VENDOR_ID_LOGITECH 0x046d +#define USB_DEVICE_ID_LOGITECH_G29_WHEEL 0xc24f +#define USB_DEVICE_ID_LOGITECH_G27_WHEEL 0xc29b +#define USB_DEVICE_ID_LOGITECH_G25_WHEEL 0xc299 +#define USB_DEVICE_ID_LOGITECH_DFGT_WHEEL 0xc29a +#define USB_DEVICE_ID_LOGITECH_DFP_WHEEL 0xc298 +#define USB_DEVICE_ID_LOGITECH_WHEEL 0xc294 + +static Uint32 supported_device_ids[] = { + USB_DEVICE_ID_LOGITECH_G29_WHEEL, + USB_DEVICE_ID_LOGITECH_G27_WHEEL, + USB_DEVICE_ID_LOGITECH_G25_WHEEL, + USB_DEVICE_ID_LOGITECH_DFGT_WHEEL, + USB_DEVICE_ID_LOGITECH_DFP_WHEEL, + USB_DEVICE_ID_LOGITECH_WHEEL +}; + + + +#define LG4FF_MAX_EFFECTS 16 + +#define FF_EFFECT_STARTED 0 +#define FF_EFFECT_ALLSET 1 +#define FF_EFFECT_PLAYING 2 +#define FF_EFFECT_UPDATING 3 + +struct lg4ff_effect_state { + SDL_HapticEffect effect; + Uint64 start_at; + Uint64 play_at; + Uint64 stop_at; + Uint32 flags; + Uint64 time_playing; + Uint64 updated_at; + Uint32 phase; + Uint32 phase_adj; + Uint32 count; + + double direction_gain; + Sint32 slope; + + bool allocated; +}; + +struct lg4ff_effect_parameters { + Sint32 level; + Sint32 d1; + Sint32 d2; + Sint32 k1; + Sint32 k2; + Uint32 clip; +}; + +struct lg4ff_slot { + Sint32 id; + struct lg4ff_effect_parameters parameters; + Uint8 current_cmd[7]; + Uint32 cmd_op; + bool is_updated; + Uint32 effect_type; +}; + +typedef struct lg4ff_device { + Uint16 product_id; + Uint16 release_number; + struct lg4ff_effect_state states[LG4FF_MAX_EFFECTS]; + struct lg4ff_slot slots[4]; + Sint32 effects_used; + + Sint32 gain; + Sint32 app_gain; + + Sint32 spring_level; + Sint32 damper_level; + Sint32 friction_level; + + Sint32 peak_ffb_level; + + SDL_Joystick *hid_handle; + + bool stop_thread; + SDL_Thread *thread; + char thread_name_buf[256]; + + SDL_Mutex *mutex; + + bool is_ffex; +} lg4ff_device; + +static SDL_INLINE Uint64 get_time_ms(void) { + return SDL_GetTicks(); +} + +#define test_bit(bit, field) (*(field) & (1 << bit)) +#define __set_bit(bit, field) {*(field) = *(field) | (1 << bit);} +#define __clear_bit(bit, field) {*(field) = *(field) & ~(1 << bit);} +#define sin_deg(in) (double)(SDL_sin((double)(in) * SDL_PI_D / 180.0)) + +#define time_after_eq(a, b) (a >= b) +#define time_before(a, b) (a < b) +#define time_diff(a, b) (a - b) + +#define STOP_EFFECT(state) ((state)->flags = 0) + +#define CLAMP_VALUE_U16(x) ((Uint16)((x) > 0xffff ? 0xffff : (x))) +#define SCALE_VALUE_U16(x, bits) (CLAMP_VALUE_U16(x) >> (16 - bits)) +#define CLAMP_VALUE_S16(x) ((Uint16)((x) <= -0x8000 ? -0x8000 : ((x) > 0x7fff ? 0x7fff : (x)))) +#define TRANSLATE_FORCE(x) ((CLAMP_VALUE_S16(x) + 0x8000) >> 8) +#define SCALE_COEFF(x, bits) SCALE_VALUE_U16(abs32(x) * 2, bits) + +static SDL_INLINE Sint32 abs32(Sint32 x) { + return x < 0 ? -x : x; +} +static SDL_INLINE Sint64 abs64(Sint64 x) { + return x < 0 ? -x : x; +} + +static SDL_INLINE bool effect_is_periodic(const SDL_HapticEffect *effect) +{ + + return effect->type == SDL_HAPTIC_SINE || + effect->type == SDL_HAPTIC_TRIANGLE || + effect->type == SDL_HAPTIC_SAWTOOTHUP || + effect->type == SDL_HAPTIC_SAWTOOTHDOWN || + effect->type == SDL_HAPTIC_SQUARE; +} + +static SDL_INLINE bool effect_is_condition(const SDL_HapticEffect *effect) +{ + return effect->type == SDL_HAPTIC_SPRING || + effect->type == SDL_HAPTIC_DAMPER || + effect->type == SDL_HAPTIC_FRICTION; +} + +// linux SDL_syshaptic.c SDL_SYS_ToDirection +static Uint16 to_linux_direction(SDL_HapticDirection *src) +{ + Uint32 tmp; + + switch (src->type) { + case SDL_HAPTIC_POLAR: + tmp = ((src->dir[0] % 36000) * 0x8000) / 18000; /* convert to range [0,0xFFFF] */ + return (Uint16)tmp; + + case SDL_HAPTIC_SPHERICAL: + /* + We convert to polar, because that's the only supported direction on Linux. + The first value of a spherical direction is practically the same as a + Polar direction, except that we have to add 90 degrees. It is the angle + from EAST {1,0} towards SOUTH {0,1}. + --> add 9000 + --> finally convert to [0,0xFFFF] as in case SDL_HAPTIC_POLAR. + */ + tmp = ((src->dir[0]) + 9000) % 36000; /* Convert to polars */ + tmp = (tmp * 0x8000) / 18000; /* convert to range [0,0xFFFF] */ + return (Uint16)tmp; + + case SDL_HAPTIC_CARTESIAN: + if (!src->dir[1]) { + return (Uint16) (src->dir[0] >= 0 ? 0x4000 : 0xC000); + } else if (!src->dir[0]) { + return (Uint16) (src->dir[1] >= 0 ? 0x8000 : 0); + } else { + float f = (float)SDL_atan2(src->dir[1], src->dir[0]); /* Ideally we'd use fixed point math instead of floats... */ + /* + SDL_atan2 takes the parameters: Y-axis-value and X-axis-value (in that order) + - Y-axis-value is the second coordinate (from center to SOUTH) + - X-axis-value is the first coordinate (from center to EAST) + We add 36000, because SDL_atan2 also returns negative values. Then we practically + have the first spherical value. Therefore we proceed as in case + SDL_HAPTIC_SPHERICAL and add another 9000 to get the polar value. + --> add 45000 in total + --> finally convert to [0,0xFFFF] as in case SDL_HAPTIC_POLAR. + */ + tmp = (((Sint32) (f * 18000. / SDL_PI_D)) + 45000) % 36000; + tmp = (tmp * 0x8000) / 18000; /* convert to range [0,0xFFFF] */ + return (Uint16)tmp; + } + case SDL_HAPTIC_STEERING_AXIS: + return 0x4000; + default: + SDL_assert(0); + } + + return 0; +} + +static Uint16 get_effect_direction(SDL_HapticEffect *effect) +{ + Uint16 direction = 0; + if (effect_is_periodic(effect)) { + direction = to_linux_direction(&effect->periodic.direction); + } else if (effect_is_condition(effect)) { + direction = to_linux_direction(&effect->condition.direction); + } else { + switch(effect->type) { + case SDL_HAPTIC_CONSTANT: + direction = to_linux_direction(&effect->constant.direction); + break; + case SDL_HAPTIC_RAMP: + direction = to_linux_direction(&effect->ramp.direction); + break; + default: + SDL_assert(0); + } + } + + return direction; +} + +static Uint32 get_effect_replay_length(SDL_HapticEffect *effect) +{ + Uint32 length = 0; + if (effect_is_periodic(effect)) { + length = effect->periodic.length; + } else if (effect_is_condition(effect)) { + length = effect->condition.length; + } else { + switch(effect->type) { + case SDL_HAPTIC_CONSTANT: + length = effect->constant.length; + break; + case SDL_HAPTIC_RAMP: + length = effect->ramp.length; + break; + default: + SDL_assert(0); + } + } + + if (length == SDL_HAPTIC_INFINITY) { + length = 0; + } + + return length; +} + +static Uint16 get_effect_replay_delay(SDL_HapticEffect *effect) +{ + Uint16 delay = 0; + if (effect_is_periodic(effect)) { + delay = effect->periodic.delay; + } else if (effect_is_condition(effect)) { + delay = effect->condition.delay; + } else { + switch(effect->type) { + case SDL_HAPTIC_CONSTANT: + delay = effect->constant.delay; + break; + case SDL_HAPTIC_RAMP: + delay = effect->ramp.delay; + break; + default: + SDL_assert(0); + } + } + + return delay; +} + +/* + *Ported* + Original function by: + Bernat Arlandis + `git blame 1a2d5727876dd7befce23d9695924e9446b31c4b hid-lg4ff.c`, https://github.com/berarma/new-lg4ff.git +*/ +static int lg4ff_play_effect(struct lg4ff_device *device, SDL_HapticEffectID effect_id, int value) +{ + struct lg4ff_effect_state *state; + Uint64 now = get_time_ms(); + + state = &device->states[effect_id]; + + if (value > 0) { + if (test_bit(FF_EFFECT_STARTED, &state->flags)) { + STOP_EFFECT(state); + } else { + device->effects_used++; + } + __set_bit(FF_EFFECT_STARTED, &state->flags); + state->start_at = now; + state->count = value; + } else { + if (test_bit(FF_EFFECT_STARTED, &state->flags)) { + STOP_EFFECT(state); + device->effects_used--; + } + } + + return 0; +} + +/* + *Ported* + Original function by: + Bernat Arlandis + `git blame 1a2d5727876dd7befce23d9695924e9446b31c4b hid-lg4ff.c`, https://github.com/berarma/new-lg4ff.git +*/ +static int lg4ff_upload_effect(struct lg4ff_device *device, const SDL_HapticEffect *effect, SDL_HapticEffectID id) +{ + struct lg4ff_effect_state *state; + Uint64 now = get_time_ms(); + + if (effect_is_periodic(effect) && effect->periodic.period == 0) { + return -1; + } + + state = &device->states[id]; + + if (test_bit(FF_EFFECT_STARTED, &state->flags) && effect->type != state->effect.type) { + return -1; + } + + state->effect = *effect; + + if (test_bit(FF_EFFECT_STARTED, &state->flags)) { + __set_bit(FF_EFFECT_UPDATING, &state->flags); + state->updated_at = now; + } + + return 0; +} + +/* + *Ported* + Original function by: + Bernat Arlandis + `git blame 1a2d5727876dd7befce23d9695924e9446b31c4b hid-lg4ff.c`, https://github.com/berarma/new-lg4ff.git +*/ +static void lg4ff_update_state(struct lg4ff_effect_state *state, const Uint64 now) +{ + SDL_HapticEffect *effect = &state->effect; + Uint64 phase_time; + Uint16 effect_direction = get_effect_direction(effect); + + if (!test_bit(FF_EFFECT_ALLSET, &state->flags)) { + state->play_at = state->start_at + get_effect_replay_delay(effect); + if (!test_bit(FF_EFFECT_UPDATING, &state->flags)) { + state->updated_at = state->play_at; + } + state->direction_gain = sin_deg(effect_direction * 360 / 0x10000); + if (effect_is_periodic(effect)) { + state->phase_adj = effect->periodic.phase * 360 / effect->periodic.period; + } + if (get_effect_replay_length(effect)) { + state->stop_at = state->play_at + get_effect_replay_length(effect); + } + } + __set_bit(FF_EFFECT_ALLSET, &state->flags); + + if (test_bit(FF_EFFECT_UPDATING, &state->flags)) { + __clear_bit(FF_EFFECT_PLAYING, &state->flags); + state->play_at = state->updated_at + get_effect_replay_delay(effect); + state->direction_gain = sin_deg(effect_direction * 360 / 0x10000); + if (get_effect_replay_length(effect)) { + state->stop_at = state->updated_at + get_effect_replay_length(effect); + } + if (effect_is_periodic(effect)) { + state->phase_adj = state->phase; + } + } + __clear_bit(FF_EFFECT_UPDATING, &state->flags); + + state->slope = 0; + if (effect->type == SDL_HAPTIC_RAMP && effect->ramp.length && (effect->ramp.length - effect->ramp.attack_length - effect->ramp.fade_length) != 0) { + state->slope = ((effect->ramp.end - effect->ramp.start) << 16) / (effect->ramp.length - effect->ramp.attack_length - effect->ramp.fade_length); + } + + if (!test_bit(FF_EFFECT_PLAYING, &state->flags) && time_after_eq(now, + state->play_at) && (get_effect_replay_length(effect) == 0 || + time_before(now, state->stop_at))) { + __set_bit(FF_EFFECT_PLAYING, &state->flags); + } + + if (test_bit(FF_EFFECT_PLAYING, &state->flags)) { + state->time_playing = time_diff(now, state->play_at); + if (effect_is_periodic(effect)) { + phase_time = time_diff(now, state->updated_at); + state->phase = (phase_time % effect->periodic.period) * 360 / effect->periodic.period; + state->phase += state->phase_adj % 360; + } + } +} + +/* + *Ported* + Original function by: + Bernat Arlandis + `git blame 1a2d5727876dd7befce23d9695924e9446b31c4b hid-lg4ff.c`, https://github.com/berarma/new-lg4ff.git +*/ +static Sint32 lg4ff_calculate_constant(struct lg4ff_effect_state *state) +{ + SDL_HapticConstant *constant = (SDL_HapticConstant *)&state->effect; + Sint32 level_sign; + Sint32 level = constant->level; + Sint32 d, t; + + if (state->time_playing < constant->attack_length) { + level_sign = level < 0 ? -1 : 1; + d = level - level_sign * constant->attack_level; + level = (Sint32) (level_sign * constant->attack_level + d * state->time_playing / constant->attack_length); + } else if (constant->length && constant->fade_length) { + t = (Sint32) (state->time_playing - constant->length + constant->fade_length); + if (t > 0) { + level_sign = level < 0 ? -1 : 1; + d = level - level_sign * constant->fade_level; + level = level - d * t / constant->fade_length; + } + } + + return (Sint32)(state->direction_gain * level); +} + +/* + *Ported* + Original function by: + Bernat Arlandis + `git blame 1a2d5727876dd7befce23d9695924e9446b31c4b hid-lg4ff.c`, https://github.com/berarma/new-lg4ff.git +*/ +static Sint32 lg4ff_calculate_ramp(struct lg4ff_effect_state *state) +{ + SDL_HapticRamp *ramp = (SDL_HapticRamp *)&state->effect; + Sint32 level_sign; + Sint32 level; + Sint32 d, t; + + if (state->time_playing < ramp->attack_length) { + level = ramp->start; + level_sign = level < 0 ? -1 : 1; + t = (Sint32) (ramp->attack_length - state->time_playing); + d = level - level_sign * ramp->attack_level; + level = level_sign * ramp->attack_level + d * t / ramp->attack_length; + } else if (ramp->length && state->time_playing >= ramp->length - ramp->fade_length && ramp->fade_length) { + level = ramp->end; + level_sign = level < 0 ? -1 : 1; + t = (Sint32) (state->time_playing - ramp->length + ramp->fade_length); + d = level_sign * ramp->fade_level - level; + level = level - d * t / ramp->fade_length; + } else { + t = (Sint32) (state->time_playing - ramp->attack_length); + level = ramp->start + ((t * state->slope) >> 16); + } + + return (Sint32)(state->direction_gain * level); +} + +/* + *Ported* + Original function by: + Bernat Arlandis + `git blame 1a2d5727876dd7befce23d9695924e9446b31c4b hid-lg4ff.c`, https://github.com/berarma/new-lg4ff.git +*/ +static Sint32 lg4ff_calculate_periodic(struct lg4ff_effect_state *state) +{ + SDL_HapticPeriodic *periodic = (SDL_HapticPeriodic *)&state->effect; + Sint32 magnitude = periodic->magnitude; + Sint32 magnitude_sign = magnitude < 0 ? -1 : 1; + Sint32 level = periodic->offset; + Sint32 d, t; + + if (state->time_playing < periodic->attack_length) { + d = magnitude - magnitude_sign * periodic->attack_level; + magnitude = (Sint32) (magnitude_sign * periodic->attack_level + d * state->time_playing / periodic->attack_length); + } else if (periodic->length && periodic->fade_length) { + t = (Sint32) (state->time_playing - get_effect_replay_length(&state->effect) + periodic->fade_length); + if (t > 0) { + d = magnitude - magnitude_sign * periodic->fade_level; + magnitude = magnitude - d * t / periodic->fade_length; + } + } + + switch (periodic->type) { + case SDL_HAPTIC_SINE: + level += (Sint32)(sin_deg(state->phase) * magnitude); + break; + case SDL_HAPTIC_SQUARE: + level += (state->phase < 180 ? 1 : -1) * magnitude; + break; + case SDL_HAPTIC_TRIANGLE: + level += (Sint32) (abs64((Sint64)state->phase * magnitude * 2 / 360 - magnitude) * 2 - magnitude); + break; + case SDL_HAPTIC_SAWTOOTHUP: + level += state->phase * magnitude * 2 / 360 - magnitude; + break; + case SDL_HAPTIC_SAWTOOTHDOWN: + level += magnitude - state->phase * magnitude * 2 / 360; + break; + default: + SDL_assert(0); + } + + return (Sint32)(state->direction_gain * level); +} + +/* + *Ported* + Original function by: + Bernat Arlandis + `git blame 1a2d5727876dd7befce23d9695924e9446b31c4b hid-lg4ff.c`, https://github.com/berarma/new-lg4ff.git +*/ +static void lg4ff_calculate_spring(struct lg4ff_effect_state *state, struct lg4ff_effect_parameters *parameters) +{ + SDL_HapticCondition *condition = (SDL_HapticCondition *)&state->effect; + + parameters->d1 = ((Sint32)condition->center[0]) - condition->deadband[0] / 2; + parameters->d2 = ((Sint32)condition->center[0]) + condition->deadband[0] / 2; + parameters->k1 = condition->left_coeff[0]; + parameters->k2 = condition->right_coeff[0]; + parameters->clip = (Uint16)condition->right_sat[0]; +} + +/* + *Ported* + Original function by: + Bernat Arlandis + `git blame 1a2d5727876dd7befce23d9695924e9446b31c4b hid-lg4ff.c`, https://github.com/berarma/new-lg4ff.git +*/ +static void lg4ff_calculate_resistance(struct lg4ff_effect_state *state, struct lg4ff_effect_parameters *parameters) +{ + SDL_HapticCondition *condition = (SDL_HapticCondition *)&state->effect; + + parameters->k1 = condition->left_coeff[0]; + parameters->k2 = condition->right_coeff[0]; + parameters->clip = (Uint16)condition->right_sat[0]; +} + +/* + *Ported* + Original function by: + Bernat Arlandis + `git blame 1a2d5727876dd7befce23d9695924e9446b31c4b hid-lg4ff.c`, https://github.com/berarma/new-lg4ff.git +*/ +static void lg4ff_update_slot(struct lg4ff_slot *slot, struct lg4ff_effect_parameters *parameters) +{ + Uint8 original_cmd[7]; + Sint32 d1; + Sint32 d2; + Sint32 k1; + Sint32 k2; + Sint32 s1; + Sint32 s2; + + SDL_memcpy(original_cmd, slot->current_cmd, sizeof(original_cmd)); + + if ((original_cmd[0] & 0xf) == 1) { + original_cmd[0] = (original_cmd[0] & 0xf0) + 0xc; + } + + if (slot->effect_type == SDL_HAPTIC_CONSTANT) { + if (slot->cmd_op == 0) { + slot->cmd_op = 1; + } else { + slot->cmd_op = 0xc; + } + } else { + if (parameters->clip == 0) { + slot->cmd_op = 3; + } else if (slot->cmd_op == 3) { + slot->cmd_op = 1; + } else { + slot->cmd_op = 0xc; + } + } + + slot->current_cmd[0] = (Uint8)((0x10 << slot->id) + slot->cmd_op); + + if (slot->cmd_op == 3) { + slot->current_cmd[1] = 0; + slot->current_cmd[2] = 0; + slot->current_cmd[3] = 0; + slot->current_cmd[4] = 0; + slot->current_cmd[5] = 0; + slot->current_cmd[6] = 0; + } else { + switch (slot->effect_type) { + case SDL_HAPTIC_CONSTANT: + slot->current_cmd[1] = 0x00; + slot->current_cmd[2] = 0; + slot->current_cmd[3] = 0; + slot->current_cmd[4] = 0; + slot->current_cmd[5] = 0; + slot->current_cmd[6] = 0; + slot->current_cmd[2 + slot->id] = TRANSLATE_FORCE(parameters->level); + break; + case SDL_HAPTIC_SPRING: + d1 = SCALE_VALUE_U16(((parameters->d1) + 0x8000) & 0xffff, 11); + d2 = SCALE_VALUE_U16(((parameters->d2) + 0x8000) & 0xffff, 11); + s1 = parameters->k1 < 0; + s2 = parameters->k2 < 0; + k1 = abs32(parameters->k1); + k2 = abs32(parameters->k2); + if (k1 < 2048) { + d1 = 0; + } else { + k1 -= 2048; + } + if (k2 < 2048) { + d2 = 2047; + } else { + k2 -= 2048; + } + slot->current_cmd[1] = 0x0b; + slot->current_cmd[2] = (Uint8)(d1 >> 3); + slot->current_cmd[3] = (Uint8)(d2 >> 3); + slot->current_cmd[4] = (SCALE_COEFF(k2, 4) << 4) + SCALE_COEFF(k1, 4); + slot->current_cmd[5] = (Uint8)(((d2 & 7) << 5) + ((d1 & 7) << 1) + (s2 << 4) + s1); + slot->current_cmd[6] = SCALE_VALUE_U16(parameters->clip, 8); + break; + case SDL_HAPTIC_DAMPER: + s1 = parameters->k1 < 0; + s2 = parameters->k2 < 0; + slot->current_cmd[1] = 0x0c; + slot->current_cmd[2] = SCALE_COEFF(parameters->k1, 4); + slot->current_cmd[3] = (Uint8)s1; + slot->current_cmd[4] = SCALE_COEFF(parameters->k2, 4); + slot->current_cmd[5] = (Uint8)s2; + slot->current_cmd[6] = SCALE_VALUE_U16(parameters->clip, 8); + break; + case SDL_HAPTIC_FRICTION: + s1 = parameters->k1 < 0; + s2 = parameters->k2 < 0; + slot->current_cmd[1] = 0x0e; + slot->current_cmd[2] = SCALE_COEFF(parameters->k1, 8); + slot->current_cmd[3] = SCALE_COEFF(parameters->k2, 8); + slot->current_cmd[4] = SCALE_VALUE_U16(parameters->clip, 8); + slot->current_cmd[5] = (Uint8)((s2 << 4) + s1); + slot->current_cmd[6] = 0; + break; + } + } + + if (SDL_memcmp(original_cmd, slot->current_cmd, sizeof(original_cmd))) { + slot->is_updated = 1; + } +} + +/* + *Ported* + Original function by: + Bernat Arlandis + `git blame 1a2d5727876dd7befce23d9695924e9446b31c4b hid-lg4ff.c`, https://github.com/berarma/new-lg4ff.git +*/ +static int lg4ff_init_slots(struct lg4ff_device *device) +{ + struct lg4ff_effect_parameters parameters; + Uint8 cmd[7] = {0}; + int i; + bool ret; + + // Set/unset fixed loop mode + cmd[0] = 0x0d; + //cmd[1] = fixed_loop ? 1 : 0; + cmd[1] = 0; + ret = SDL_SendJoystickEffect(device->hid_handle, cmd, 7); + if (!ret) { + return -1; + } + + SDL_memset(&device->states, 0, sizeof(device->states)); + SDL_memset(&device->slots, 0, sizeof(device->slots)); + SDL_memset(¶meters, 0, sizeof(parameters)); + + device->slots[0].effect_type = SDL_HAPTIC_CONSTANT; + device->slots[1].effect_type = SDL_HAPTIC_SPRING; + device->slots[2].effect_type = SDL_HAPTIC_DAMPER; + device->slots[3].effect_type = SDL_HAPTIC_FRICTION; + + for (i = 0; i < 4; i++) { + device->slots[i].id = i; + lg4ff_update_slot(&device->slots[i], ¶meters); + ret = SDL_SendJoystickEffect(device->hid_handle, cmd, 7); + if (!ret) { + return -1; + } + device->slots[i].is_updated = 0; + } + + return 0; +} + +/* + *Ported* + Original function by: + Bernat Arlandis + `git blame 1a2d5727876dd7befce23d9695924e9446b31c4b hid-lg4ff.c`, https://github.com/berarma/new-lg4ff.git +*/ +static int lg4ff_timer(struct lg4ff_device *device) +{ + struct lg4ff_slot *slot; + struct lg4ff_effect_state *state; + struct lg4ff_effect_parameters parameters[4]; + Uint64 now = get_time_ms(); + Uint16 gain; + Sint32 count; + Sint32 effect_id; + int i; + Sint32 ffb_level; + int status = 0; + + // XXX how to detect stacked up effects here? + + SDL_memset(parameters, 0, sizeof(parameters)); + + gain = (Uint16)((Uint32)device->gain * device->app_gain / 0xffff); + + count = device->effects_used; + + for (effect_id = 0; effect_id < LG4FF_MAX_EFFECTS; effect_id++) { + + if (!count) { + break; + } + + state = &device->states[effect_id]; + + if (!test_bit(FF_EFFECT_STARTED, &state->flags)) { + continue; + } + + count--; + + if (test_bit(FF_EFFECT_ALLSET, &state->flags)) { + if (get_effect_replay_length(&state->effect) && time_after_eq(now, state->stop_at)) { + STOP_EFFECT(state); + if (!--state->count) { + device->effects_used--; + continue; + } + __set_bit(FF_EFFECT_STARTED, &state->flags); + state->start_at = state->stop_at; + } + } + + lg4ff_update_state(state, now); + + if (!test_bit(FF_EFFECT_PLAYING, &state->flags)) { + continue; + } + + if (effect_is_periodic(&state->effect)) { + parameters[0].level += lg4ff_calculate_periodic(state); + } else { + switch (state->effect.type) { + case SDL_HAPTIC_CONSTANT: + parameters[0].level += lg4ff_calculate_constant(state); + break; + case SDL_HAPTIC_RAMP: + parameters[0].level += lg4ff_calculate_ramp(state); + break; + case SDL_HAPTIC_SPRING: + lg4ff_calculate_spring(state, ¶meters[1]); + break; + case SDL_HAPTIC_DAMPER: + lg4ff_calculate_resistance(state, ¶meters[2]); + break; + case SDL_HAPTIC_FRICTION: + lg4ff_calculate_resistance(state, ¶meters[3]); + break; + } + } + } + + parameters[0].level = (Sint32)((Sint64)parameters[0].level * gain / 0xffff); + parameters[1].clip = parameters[1].clip * device->spring_level / 100; + parameters[2].clip = parameters[2].clip * device->damper_level / 100; + parameters[3].clip = parameters[3].clip * device->friction_level / 100; + + ffb_level = abs32(parameters[0].level); + for (i = 1; i < 4; i++) { + parameters[i].k1 = (Sint32)((Sint64)parameters[i].k1 * gain / 0xffff); + parameters[i].k2 = (Sint32)((Sint64)parameters[i].k2 * gain / 0xffff); + parameters[i].clip = parameters[i].clip * gain / 0xffff; + ffb_level = (Sint32)(ffb_level + parameters[i].clip * 0x7fff / 0xffff); + } + if (ffb_level > device->peak_ffb_level) { + device->peak_ffb_level = ffb_level; + } + + for (i = 0; i < 4; i++) { + slot = &device->slots[i]; + lg4ff_update_slot(slot, ¶meters[i]); + if (slot->is_updated) { + bool ret = SDL_SendJoystickEffect(device->hid_handle, slot->current_cmd, 7); + if (!ret) { + status = -1; + } + slot->is_updated = 0; + } + } + + return status; +} + +static bool SDL_HIDAPI_HapticDriverLg4ff_JoystickSupported(SDL_Joystick *joystick) +{ + Uint16 vendor_id = SDL_GetJoystickVendor(joystick); + Uint16 product_id = SDL_GetJoystickProduct(joystick); + if (vendor_id != USB_VENDOR_ID_LOGITECH) { + return false; + } + for (int i = 0;i < sizeof(supported_device_ids) / sizeof(Uint32);i++) { + if (supported_device_ids[i] == product_id) { + return true; + } + } + return false; +} + +static int SDLCALL SDL_HIDAPI_HapticDriverLg4ff_ThreadFunction(void *ctx_in) +{ + lg4ff_device *ctx = (lg4ff_device *)ctx_in; + while (true) { + if (ctx->stop_thread) { + return 0; + } + SDL_LockMutex(ctx->mutex); + lg4ff_timer(ctx); + SDL_UnlockMutex(ctx->mutex); + SDL_Delay(2); + } +} + +static int SDL_HIDAPI_HapticDriverLg4ff_GetEnvInt(const char *env_name, int min, int max, int def) +{ + const char *env = SDL_getenv(env_name); + int value = 0; + if (env == NULL) { + return def; + } + value = SDL_atoi(env); + if (value < min) { + value = min; + } + if (value > max) { + value = max; + } + return value; +} + +/* + ffex identification method by: + Simon Wood + Michal Malý + lg4ff_init + `git blame v6.12 drivers/hid/hid-lg4ff.c`, https://github.com/torvalds/linux.git +*/ +static void *SDL_HIDAPI_HapticDriverLg4ff_Open(SDL_Joystick *joystick) +{ + lg4ff_device *ctx; + if (!SDL_HIDAPI_HapticDriverLg4ff_JoystickSupported(joystick)) { + SDL_SetError("Device not supported by the lg4ff hidapi haptic driver"); + return NULL; + } + + ctx = SDL_malloc(sizeof(lg4ff_device)); + if (ctx == NULL) { + SDL_OutOfMemory(); + return NULL; + } + SDL_memset(ctx, 0, sizeof(lg4ff_device)); + + ctx->hid_handle = joystick; + if (lg4ff_init_slots(ctx) != 0) { + SDL_SetError("lg4ff hidapi driver failed initializing effect slots"); + SDL_free(ctx); + return NULL; + } + + ctx->mutex = SDL_CreateMutex(); + if (ctx->mutex == NULL) { + SDL_free(ctx); + return NULL; + } + + ctx->spring_level = SDL_HIDAPI_HapticDriverLg4ff_GetEnvInt("SDL_HAPTIC_LG4FF_SPRING", 0, 100, 30); + ctx->damper_level = SDL_HIDAPI_HapticDriverLg4ff_GetEnvInt("SDL_HAPTIC_LG4FF_DAMPER", 0, 100, 30); + ctx->friction_level = SDL_HIDAPI_HapticDriverLg4ff_GetEnvInt("SDL_HAPTIC_LG4FF_FRICTION", 0, 100, 30); + ctx->gain = SDL_HIDAPI_HapticDriverLg4ff_GetEnvInt("SDL_HAPTIC_LG4FF_GAIN", 0, 65535, 65535); + ctx->app_gain = 65535; + + ctx->product_id = SDL_GetJoystickProduct(joystick); + ctx->release_number = SDL_GetJoystickProductVersion(joystick); + + SDL_snprintf(ctx->thread_name_buf, sizeof(ctx->thread_name_buf), "SDL_hidapihaptic_lg4ff %d %04x:%04x", SDL_GetJoystickID(joystick), USB_VENDOR_ID_LOGITECH, ctx->product_id); + ctx->stop_thread = false; + ctx->thread = SDL_CreateThread(SDL_HIDAPI_HapticDriverLg4ff_ThreadFunction, ctx->thread_name_buf, ctx); + + if (ctx->product_id == USB_DEVICE_ID_LOGITECH_WHEEL && + (ctx->release_number >> 8) == 0x21 && + (ctx->release_number & 0xff) == 0x00) { + ctx->is_ffex = true; + } else { + ctx->is_ffex = false; + } + + return ctx; +} + +static bool SDL_HIDAPI_HapticDriverLg4ff_StopEffects(SDL_HIDAPI_HapticDevice *device) +{ + lg4ff_device *ctx = (lg4ff_device *)device->ctx; + int i; + + SDL_LockMutex(ctx->mutex); + for (i = 0;i < LG4FF_MAX_EFFECTS;i++) { + struct lg4ff_effect_state *state = &ctx->states[i]; + STOP_EFFECT(state); + } + SDL_UnlockMutex(ctx->mutex); + + return true; +} + +static void SDL_HIDAPI_HapticDriverLg4ff_Close(SDL_HIDAPI_HapticDevice *device) +{ + lg4ff_device *ctx = (lg4ff_device *)device->ctx; + + SDL_HIDAPI_HapticDriverLg4ff_StopEffects(device); + + // let effects finish in lg4ff_timer + SDL_Delay(50); + + ctx->stop_thread = true; + SDL_WaitThread(ctx->thread, NULL); + SDL_DestroyMutex(ctx->mutex); +} + +static int SDL_HIDAPI_HapticDriverLg4ff_NumEffects(SDL_HIDAPI_HapticDevice *device) +{ + return LG4FF_MAX_EFFECTS; +} + +static Uint32 SDL_HIDAPI_HapticDriverLg4ff_GetFeatures(SDL_HIDAPI_HapticDevice *device) +{ + return SDL_HAPTIC_CONSTANT | + SDL_HAPTIC_SPRING | + SDL_HAPTIC_DAMPER | + SDL_HAPTIC_AUTOCENTER | + SDL_HAPTIC_SINE | + SDL_HAPTIC_SQUARE | + SDL_HAPTIC_TRIANGLE | + SDL_HAPTIC_SAWTOOTHUP | + SDL_HAPTIC_SAWTOOTHDOWN | + SDL_HAPTIC_RAMP | + SDL_HAPTIC_FRICTION | + SDL_HAPTIC_STATUS | + SDL_HAPTIC_GAIN; +} + +static bool SDL_HIDAPI_HapticDriverLg4ff_EffectSupported(SDL_HIDAPI_HapticDevice *device, const SDL_HapticEffect *effect) { + Uint32 features = SDL_HIDAPI_HapticDriverLg4ff_GetFeatures(device); + return (features & effect->type)? true : false; +} + +static int SDL_HIDAPI_HapticDriverLg4ff_NumAxes(SDL_HIDAPI_HapticDevice *device) +{ + return 1; +} + +static SDL_HapticEffectID SDL_HIDAPI_HapticDriverLg4ff_CreateEffect(SDL_HIDAPI_HapticDevice *device, const SDL_HapticEffect *data) +{ + lg4ff_device *ctx = (lg4ff_device *)device->ctx; + SDL_HapticEffectID i; + SDL_HapticEffectID state_slot = -1; + int ret; + if (!SDL_HIDAPI_HapticDriverLg4ff_EffectSupported(device, data)) { + SDL_SetError("Unsupported effect"); + return -1; + } + + SDL_LockMutex(ctx->mutex); + for (i = 0;i < LG4FF_MAX_EFFECTS;i++) { + if (!ctx->states[i].allocated) { + state_slot = i; + break; + } + } + if (state_slot == -1) { + SDL_UnlockMutex(ctx->mutex); + SDL_SetError("All effect slots in-use"); + return -1; + } + + ret = lg4ff_upload_effect(ctx, data, state_slot); + SDL_UnlockMutex(ctx->mutex); + if (ret == 0) { + ctx->states[state_slot].allocated = true; + return state_slot; + } else { + SDL_SetError("Bad effect parameters"); + return -1; + } +} + +// assumes ctx->mutex locked +static bool lg4ff_effect_slot_valid_active(lg4ff_device *ctx, SDL_HapticEffectID id) +{ + if (id >= LG4FF_MAX_EFFECTS || id < 0) { + return false; + } + if (!ctx->states[id].allocated) { + return false; + } + return true; +} + +static bool SDL_HIDAPI_HapticDriverLg4ff_UpdateEffect(SDL_HIDAPI_HapticDevice *device, SDL_HapticEffectID id, const SDL_HapticEffect *data) +{ + lg4ff_device *ctx = (lg4ff_device *)device->ctx; + int ret; + + SDL_LockMutex(ctx->mutex); + if (!lg4ff_effect_slot_valid_active(ctx, id)) { + SDL_UnlockMutex(ctx->mutex); + SDL_SetError("Bad effect id"); + return false; + } + + ret = lg4ff_upload_effect(ctx, data, id); + SDL_UnlockMutex(ctx->mutex); + + return ret == 0; +} + +static bool SDL_HIDAPI_HapticDriverLg4ff_RunEffect(SDL_HIDAPI_HapticDevice *device, SDL_HapticEffectID id, Uint32 iterations) +{ + lg4ff_device *ctx = (lg4ff_device *)device->ctx; + int ret; + + SDL_LockMutex(ctx->mutex); + if (!lg4ff_effect_slot_valid_active(ctx, id)) { + SDL_UnlockMutex(ctx->mutex); + SDL_SetError("Bad effect id"); + return false; + } + + ret = lg4ff_play_effect(ctx, id, iterations); + SDL_UnlockMutex(ctx->mutex); + + return ret == 0; +} + +static bool SDL_HIDAPI_HapticDriverLg4ff_StopEffect(SDL_HIDAPI_HapticDevice *device, SDL_HapticEffectID id) +{ + return SDL_HIDAPI_HapticDriverLg4ff_RunEffect(device, id, 0); +} + +static void SDL_HIDAPI_HapticDriverLg4ff_DestroyEffect(SDL_HIDAPI_HapticDevice *device, SDL_HapticEffectID id) +{ + lg4ff_device *ctx = (lg4ff_device *)device->ctx; + struct lg4ff_effect_state *state; + + SDL_LockMutex(ctx->mutex); + if (!lg4ff_effect_slot_valid_active(ctx, id)) { + SDL_UnlockMutex(ctx->mutex); + return; + } + + state = &ctx->states[id]; + STOP_EFFECT(state); + state->allocated = false; + + SDL_UnlockMutex(ctx->mutex); +} + +static bool SDL_HIDAPI_HapticDriverLg4ff_GetEffectStatus(SDL_HIDAPI_HapticDevice *device, SDL_HapticEffectID id) +{ + lg4ff_device *ctx = (lg4ff_device *)device->ctx; + bool ret = false; + + SDL_LockMutex(ctx->mutex); + if (!lg4ff_effect_slot_valid_active(ctx, id)) { + SDL_UnlockMutex(ctx->mutex); + return false; + } + + if (test_bit(FF_EFFECT_STARTED, &ctx->states[id].flags)) { + ret = true; + } + SDL_UnlockMutex(ctx->mutex); + + return ret; +} + +static bool SDL_HIDAPI_HapticDriverLg4ff_SetGain(SDL_HIDAPI_HapticDevice *device, int gain) +{ + lg4ff_device *ctx = (lg4ff_device *)device->ctx; + if (gain > 100) { + gain = 100; + } + if (gain < 0) { + gain = 0; + } + ctx->app_gain = (65535 * gain) / 100; + return true; +} + +/* + *Ported* + Original functions by: + Simon Wood + Michal Malý + lg4ff_set_autocenter_default lg4ff_set_autocenter_ffex + `git blame v6.12 drivers/hid/hid-lg4ff.c`, https://github.com/torvalds/linux.git +*/ +static bool SDL_HIDAPI_HapticDriverLg4ff_SetAutocenter(SDL_HIDAPI_HapticDevice *device, int autocenter) +{ + lg4ff_device *ctx = (lg4ff_device *)device->ctx; + Uint8 cmd[7] = {0}; + bool ret; + + if (autocenter < 0) { + autocenter = 0; + } + if (autocenter > 100) { + autocenter = 100; + } + + SDL_LockMutex(ctx->mutex); + if (ctx->is_ffex) { + int magnitude = (90 * autocenter) / 100; + + cmd[0] = 0xfe; + cmd[1] = 0x03; + cmd[2] = (Uint8)((Uint16)magnitude >> 14); + cmd[3] = (Uint8)((Uint16)magnitude >> 14); + cmd[4] = (Uint8)magnitude; + + ret = SDL_SendJoystickEffect(ctx->hid_handle, cmd, sizeof(cmd)); + if (!ret) { + SDL_UnlockMutex(ctx->mutex); + SDL_SetError("Failed sending autocenter command"); + return false; + } + } else { + Uint32 expand_a; + Uint32 expand_b; + int magnitude = (65535 * autocenter) / 100; + + // first disable + cmd[0] = 0xf5; + + ret = SDL_SendJoystickEffect(ctx->hid_handle, cmd, sizeof(cmd)); + if (!ret) { + SDL_UnlockMutex(ctx->mutex); + SDL_SetError("Failed sending autocenter disable command"); + return false; + } + + if (magnitude == 0) { + SDL_UnlockMutex(ctx->mutex); + return true; + } + + // set strength + if (magnitude <= 0xaaaa) { + expand_a = 0x0c * magnitude; + expand_b = 0x80 * magnitude; + } else { + expand_a = (0x0c * 0xaaaa) + 0x06 * (magnitude - 0xaaaa); + expand_b = (0x80 * 0xaaaa) + 0xff * (magnitude - 0xaaaa); + } + expand_a = expand_a >> 1; + + SDL_memset(cmd, 0x00, 7); + cmd[0] = 0xfe; + cmd[1] = 0x0d; + cmd[2] = (Uint8)(expand_a / 0xaaaa); + cmd[3] = (Uint8)(expand_a / 0xaaaa); + cmd[4] = (Uint8)(expand_b / 0xaaaa); + + ret = SDL_SendJoystickEffect(ctx->hid_handle, cmd, sizeof(cmd)); + if (!ret) { + SDL_UnlockMutex(ctx->mutex); + SDL_SetError("Failed sending autocenter magnitude command"); + return false; + } + + // enable + SDL_memset(cmd, 0x00, 7); + cmd[0] = 0x14; + + ret = SDL_SendJoystickEffect(ctx->hid_handle, cmd, sizeof(cmd)); + if (!ret) { + SDL_UnlockMutex(ctx->mutex); + SDL_SetError("Failed sending autocenter enable command"); + return false; + } + } + SDL_UnlockMutex(ctx->mutex); + return true; +} + +static bool SDL_HIDAPI_HapticDriverLg4ff_Pause(SDL_HIDAPI_HapticDevice *device) +{ + return SDL_Unsupported(); +} + +static bool SDL_HIDAPI_HapticDriverLg4ff_Resume(SDL_HIDAPI_HapticDevice *device) +{ + return SDL_Unsupported(); +} + +SDL_HIDAPI_HapticDriver SDL_HIDAPI_HapticDriverLg4ff = { + SDL_HIDAPI_HapticDriverLg4ff_JoystickSupported, + SDL_HIDAPI_HapticDriverLg4ff_Open, + SDL_HIDAPI_HapticDriverLg4ff_Close, + SDL_HIDAPI_HapticDriverLg4ff_NumEffects, + SDL_HIDAPI_HapticDriverLg4ff_NumEffects, + SDL_HIDAPI_HapticDriverLg4ff_GetFeatures, + SDL_HIDAPI_HapticDriverLg4ff_NumAxes, + SDL_HIDAPI_HapticDriverLg4ff_CreateEffect, + SDL_HIDAPI_HapticDriverLg4ff_UpdateEffect, + SDL_HIDAPI_HapticDriverLg4ff_RunEffect, + SDL_HIDAPI_HapticDriverLg4ff_StopEffect, + SDL_HIDAPI_HapticDriverLg4ff_DestroyEffect, + SDL_HIDAPI_HapticDriverLg4ff_GetEffectStatus, + SDL_HIDAPI_HapticDriverLg4ff_SetGain, + SDL_HIDAPI_HapticDriverLg4ff_SetAutocenter, + SDL_HIDAPI_HapticDriverLg4ff_Pause, + SDL_HIDAPI_HapticDriverLg4ff_Resume, + SDL_HIDAPI_HapticDriverLg4ff_StopEffects, +}; + +#endif //SDL_HAPTIC_HIDAPI_LG4FF +#endif //SDL_JOYSTICK_HIDAPI diff --git a/libs/SDL3/src/hidapi/SDL_hidapi.c b/libs/SDL3/src/hidapi/SDL_hidapi.c index b14b75eb..41e24ec8 100644 --- a/libs/SDL3/src/hidapi/SDL_hidapi.c +++ b/libs/SDL3/src/hidapi/SDL_hidapi.c @@ -807,6 +807,8 @@ typedef struct LIBUSB_hid_device_ LIBUSB_hid_device; #define hid_send_feature_report LIBUSB_hid_send_feature_report #define hid_set_nonblocking LIBUSB_hid_set_nonblocking #define hid_write LIBUSB_hid_write +#define hid_version LIBUSB_hid_version +#define hid_version_str LIBUSB_hid_version_str #define input_report LIBUSB_input_report #define make_path LIBUSB_make_path #define new_hid_device LIBUSB_new_hid_device @@ -1107,6 +1109,11 @@ bool SDL_HIDAPI_ShouldIgnoreDevice(int bus, Uint16 vendor_id, Uint16 product_id, (usage == USB_USAGE_GENERIC_KEYBOARD || usage == USB_USAGE_GENERIC_MOUSE)) { return true; } + } else if (vendor_id == USB_VENDOR_FLYDIGI && product_id == USB_PRODUCT_FLYDIGI_GAMEPAD) { + if (usage_page == USB_USAGEPAGE_VENDOR_FLYDIGI) { + return false; + } + return true; } else if (usage_page == USB_USAGEPAGE_GENERIC_DESKTOP && (usage == USB_USAGE_GENERIC_JOYSTICK || usage == USB_USAGE_GENERIC_GAMEPAD || usage == USB_USAGE_GENERIC_MULTIAXISCONTROLLER)) { // This is a controller @@ -1171,9 +1178,9 @@ int SDL_hid_init(void) if (libusb_ctx.libhandle != NULL) { bool loaded = true; #ifdef SDL_LIBUSB_DYNAMIC -#define LOAD_LIBUSB_SYMBOL(type, func) \ - if (!(libusb_ctx.func = (type)SDL_LoadFunction(libusb_ctx.libhandle, "libusb_" #func))) { \ - loaded = false; \ +#define LOAD_LIBUSB_SYMBOL(type, func) \ + if ((libusb_ctx.func = (type)SDL_LoadFunction(libusb_ctx.libhandle, "libusb_" #func)) == NULL) { \ + loaded = false; \ } #else #define LOAD_LIBUSB_SYMBOL(type, func) \ diff --git a/libs/SDL3/src/hidapi/SDL_hidapi_windows.h b/libs/SDL3/src/hidapi/SDL_hidapi_windows.h index c29122ba..91f71c20 100644 --- a/libs/SDL3/src/hidapi/SDL_hidapi_windows.h +++ b/libs/SDL3/src/hidapi/SDL_hidapi_windows.h @@ -62,6 +62,10 @@ #define wcsstr SDL_wcsstr #define wcstol SDL_wcstol +// These functions conflict when linking both SDL and hidapi statically +#define hid_winapi_descriptor_reconstruct_pp_data SDL_hid_winapi_descriptor_reconstruct_pp_data +#define hid_winapi_get_container_id SDL_hid_winapi_get_container_id + #undef HIDAPI_H__ #include "windows/hid.c" #define HAVE_PLATFORM_BACKEND 1 diff --git a/libs/SDL3/src/hidapi/android/hid.cpp b/libs/SDL3/src/hidapi/android/hid.cpp index 887390ed..2dbfb8ed 100644 --- a/libs/SDL3/src/hidapi/android/hid.cpp +++ b/libs/SDL3/src/hidapi/android/hid.cpp @@ -319,7 +319,7 @@ private: hid_buffer_entry *m_pFree; }; -static jbyteArray NewByteArray( JNIEnv* env, const uint8_t *pData, size_t nDataLen ) +static jbyteArray NewByteArray( JNIEnv *env, const uint8_t *pData, size_t nDataLen ) { jbyteArray array = env->NewByteArray( (jsize)nDataLen ); jbyte *pBuf = env->GetByteArrayElements( array, NULL ); @@ -333,7 +333,7 @@ static char *CreateStringFromJString( JNIEnv *env, const jstring &sString ) { size_t nLength = env->GetStringUTFLength( sString ); const char *pjChars = env->GetStringUTFChars( sString, NULL ); - char *psString = (char*)malloc( nLength + 1 ); + char *psString = (char *)malloc( nLength + 1 ); SDL_memcpy( psString, pjChars, nLength ); psString[ nLength ] = '\0'; env->ReleaseStringUTFChars( sString, pjChars ); @@ -344,7 +344,7 @@ static wchar_t *CreateWStringFromJString( JNIEnv *env, const jstring &sString ) { size_t nLength = env->GetStringLength( sString ); const jchar *pjChars = env->GetStringChars( sString, NULL ); - wchar_t *pwString = (wchar_t*)malloc( ( nLength + 1 ) * sizeof( wchar_t ) ); + wchar_t *pwString = (wchar_t *)malloc( ( nLength + 1 ) * sizeof( wchar_t ) ); wchar_t *pwChars = pwString; for ( size_t iIndex = 0; iIndex < nLength; ++iIndex ) { @@ -358,7 +358,7 @@ static wchar_t *CreateWStringFromJString( JNIEnv *env, const jstring &sString ) static wchar_t *CreateWStringFromWString( const wchar_t *pwSrc ) { size_t nLength = SDL_wcslen( pwSrc ); - wchar_t *pwString = (wchar_t*)malloc( ( nLength + 1 ) * sizeof( wchar_t ) ); + wchar_t *pwString = (wchar_t *)malloc( ( nLength + 1 ) * sizeof( wchar_t ) ); SDL_memcpy( pwString, pwSrc, nLength * sizeof( wchar_t ) ); pwString[ nLength ] = '\0'; return pwString; @@ -997,7 +997,7 @@ JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceInputReport)(J hid_device_ref pDevice = FindDevice( nDeviceID ); if ( pDevice ) { - pDevice->ProcessInput( reinterpret_cast< const uint8_t* >( pBuf ), nBufSize ); + pDevice->ProcessInput( reinterpret_cast< const uint8_t * >( pBuf ), nBufSize ); } env->ReleaseByteArrayElements(value, pBuf, 0); @@ -1013,7 +1013,7 @@ JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceReportResponse hid_device_ref pDevice = FindDevice( nDeviceID ); if ( pDevice ) { - pDevice->ProcessReportResponse( reinterpret_cast< const uint8_t* >( pBuf ), nBufSize ); + pDevice->ProcessReportResponse( reinterpret_cast< const uint8_t * >( pBuf ), nBufSize ); } env->ReleaseByteArrayElements(value, pBuf, 0); @@ -1375,7 +1375,7 @@ int hid_get_report_descriptor(hid_device *device, unsigned char *buf, size_t buf return -1; } -HID_API_EXPORT const wchar_t* HID_API_CALL hid_error(hid_device *device) +HID_API_EXPORT const wchar_t * HID_API_CALL hid_error(hid_device *device) { return NULL; } diff --git a/libs/SDL3/src/hidapi/libusb/hid.c b/libs/SDL3/src/hidapi/libusb/hid.c index f4b1ccb3..94cc50dc 100644 --- a/libs/SDL3/src/hidapi/libusb/hid.c +++ b/libs/SDL3/src/hidapi/libusb/hid.c @@ -71,6 +71,11 @@ extern "C" { #define DETACH_KERNEL_DRIVER #endif +#if defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable:5287) /* operands are different enum types */ +#endif + /* Uncomment to enable the retrieval of Usage and Usage Page in hid_enumerate(). Warning, on platforms different from FreeBSD this is very invasive as it requires the detach @@ -879,9 +884,13 @@ static int is_xboxone(unsigned short vendor_id, const struct libusb_interface_de 0x1532, /* Razer Wildcat */ 0x20d6, /* PowerA */ 0x24c6, /* PowerA */ + 0x294b, /* Snakebyte */ 0x2dc8, /* 8BitDo */ 0x2e24, /* Hyperkin */ + 0x2e95, /* SCUF */ + 0x3285, /* Nacon */ 0x3537, /* GameSir */ + 0x366c, /* ByoWave */ }; if (intf_desc->bInterfaceNumber == 0 && @@ -1238,6 +1247,7 @@ static void init_xbox360(libusb_device_handle *device_handle, unsigned short idV (void)conf_desc; if ((idVendor == 0x05ac && idProduct == 0x055b) /* Gamesir-G3w */ || + (idVendor == 0x20d6 && idProduct == 0x4010) /* PowerA Battle Dragon Advanced Wireless Controller */ || idVendor == 0x0f0d /* Hori Xbox controllers */) { unsigned char data[20]; @@ -2139,6 +2149,10 @@ uint16_t get_usb_code_for_current_locale(void) return 0x0; } +#if defined(_MSC_VER) +#pragma warning (pop) +#endif + #ifdef __cplusplus } #endif diff --git a/libs/SDL3/src/io/windows/SDL_asyncio_windows_ioring.c b/libs/SDL3/src/io/windows/SDL_asyncio_windows_ioring.c index 2d38efc3..497456cf 100644 --- a/libs/SDL3/src/io/windows/SDL_asyncio_windows_ioring.c +++ b/libs/SDL3/src/io/windows/SDL_asyncio_windows_ioring.c @@ -50,7 +50,7 @@ static void *ioring_handle = NULL; SDL_IORING_FUNC(BOOL, IsIoRingOpSupported, (HIORING ioRing, IORING_OP_CODE op)) \ SDL_IORING_FUNC(HRESULT, CreateIoRing, (IORING_VERSION ioringVersion, IORING_CREATE_FLAGS flags, UINT32 submissionQueueSize, UINT32 completionQueueSize, HIORING* h)) \ SDL_IORING_FUNC(HRESULT, GetIoRingInfo, (HIORING ioRing, IORING_INFO* info)) \ - SDL_IORING_FUNC(HRESULT, SubmitIoRing, (HIORING ioRing, UINT32 waitOperations, UINT32 milliseconds, UINT32* submittedEntries)) \ + SDL_IORING_FUNC(HRESULT, SubmitIoRing, (HIORING ioRing, UINT32 waitOperations, UINT32 milliseconds, UINT32 * submittedEntries)) \ SDL_IORING_FUNC(HRESULT, CloseIoRing, (HIORING ioRing)) \ SDL_IORING_FUNC(HRESULT, PopIoRingCompletion, (HIORING ioRing, IORING_CQE* cqe)) \ SDL_IORING_FUNC(HRESULT, SetIoRingCompletionEvent, (HIORING ioRing, HANDLE hEvent)) \ diff --git a/libs/SDL3/src/joystick/SDL_gamepad.c b/libs/SDL3/src/joystick/SDL_gamepad.c index 9e8659d6..8a5ecc0a 100644 --- a/libs/SDL3/src/joystick/SDL_gamepad.c +++ b/libs/SDL3/src/joystick/SDL_gamepad.c @@ -62,6 +62,7 @@ typedef enum { SDL_GAMEPAD_FACE_STYLE_UNKNOWN, SDL_GAMEPAD_FACE_STYLE_ABXY, + SDL_GAMEPAD_FACE_STYLE_AXBY, SDL_GAMEPAD_FACE_STYLE_BAYX, SDL_GAMEPAD_FACE_STYLE_SONY, } SDL_GamepadFaceStyle; @@ -701,12 +702,19 @@ static GamepadMapping_t *SDL_CreateMappingForHIDAPIGamepad(SDL_GUID guid) SDL_GetJoystickGUIDInfo(guid, &vendor, &product, NULL, NULL); + if (SDL_IsJoystickWheel(vendor, product)) { + // We don't want to pick up Logitech FFB wheels here + // Some versions of WINE will also not treat devices that show up as gamepads as wheels + return NULL; + } + if ((vendor == USB_VENDOR_NINTENDO && product == USB_PRODUCT_NINTENDO_GAMECUBE_ADAPTER) || (vendor == USB_VENDOR_DRAGONRISE && (product == USB_PRODUCT_EVORETRO_GAMECUBE_ADAPTER1 || - product == USB_PRODUCT_EVORETRO_GAMECUBE_ADAPTER2))) { + product == USB_PRODUCT_EVORETRO_GAMECUBE_ADAPTER2 || + product == USB_PRODUCT_EVORETRO_GAMECUBE_ADAPTER3))) { // GameCube driver has 12 buttons and 6 axes - SDL_strlcat(mapping_string, "a:b0,b:b1,dpdown:b6,dpleft:b4,dpright:b5,dpup:b7,lefttrigger:a4,leftx:a0,lefty:a1~,rightshoulder:b9,righttrigger:a5,rightx:a2,righty:a3~,start:b8,x:b2,y:b3,", sizeof(mapping_string)); + SDL_strlcat(mapping_string, "a:b0,b:b2,dpdown:b6,dpleft:b4,dpright:b5,dpup:b7,lefttrigger:a4,leftx:a0,lefty:a1~,rightshoulder:b9,righttrigger:a5,rightx:a2,righty:a3~,start:b8,x:b1,y:b3,hint:!SDL_GAMECONTROLLER_USE_GAMECUBE_LABELS:=1,", sizeof(mapping_string)); } else if (vendor == USB_VENDOR_NINTENDO && (guid.data[15] == k_eSwitchDeviceInfoControllerType_HVCLeft || guid.data[15] == k_eSwitchDeviceInfoControllerType_HVCRight || @@ -771,17 +779,31 @@ static GamepadMapping_t *SDL_CreateMappingForHIDAPIGamepad(SDL_GUID guid) } break; } + } else if (vendor == USB_VENDOR_8BITDO && + (product == USB_PRODUCT_8BITDO_SF30_PRO || + product == USB_PRODUCT_8BITDO_SF30_PRO_BT || + product == USB_PRODUCT_8BITDO_SN30_PRO || + product == USB_PRODUCT_8BITDO_SN30_PRO_BT || + product == USB_PRODUCT_8BITDO_PRO_2 || + product == USB_PRODUCT_8BITDO_PRO_2_BT)) { + SDL_strlcat(mapping_string, "a:b1,b:b0,back:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", sizeof(mapping_string)); + if (product == USB_PRODUCT_8BITDO_PRO_2 || product == USB_PRODUCT_8BITDO_PRO_2_BT) { + SDL_strlcat(mapping_string, "paddle1:b14,paddle2:b13,", sizeof(mapping_string)); + } + } else if (vendor == USB_VENDOR_8BITDO && + (product == USB_PRODUCT_8BITDO_SF30_PRO || + product == USB_PRODUCT_8BITDO_SF30_PRO_BT)) { + // This controller has no guide button + SDL_strlcat(mapping_string, "a:b1,b:b0,back:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", sizeof(mapping_string)); } else { // All other gamepads have the standard set of 19 buttons and 6 axes - SDL_strlcat(mapping_string, "a:b0,b:b1,back:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,", sizeof(mapping_string)); + if (SDL_IsJoystickGameCube(vendor, product)) { + SDL_strlcat(mapping_string, "a:b0,b:b2,back:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b1,y:b3,hint:!SDL_GAMECONTROLLER_USE_GAMECUBE_LABELS:=1,", sizeof(mapping_string)); + } else { + SDL_strlcat(mapping_string, "a:b0,b:b1,back:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,", sizeof(mapping_string)); + } - if (SDL_IsJoystickXboxSeriesX(vendor, product)) { - // XBox Series X Controllers have a share button under the guide button - SDL_strlcat(mapping_string, "misc1:b11,", sizeof(mapping_string)); - } else if (SDL_IsJoystickXboxOneElite(vendor, product)) { - // XBox One Elite Controllers have 4 back paddle buttons - SDL_strlcat(mapping_string, "paddle1:b11,paddle2:b13,paddle3:b12,paddle4:b14,", sizeof(mapping_string)); - } else if (SDL_IsJoystickSteamController(vendor, product)) { + if (SDL_IsJoystickSteamController(vendor, product)) { // Steam controllers have 2 back paddle buttons SDL_strlcat(mapping_string, "paddle1:b12,paddle2:b11,", sizeof(mapping_string)); } else if (SDL_IsJoystickNintendoSwitchPro(vendor, product) || @@ -796,18 +818,36 @@ static GamepadMapping_t *SDL_CreateMappingForHIDAPIGamepad(SDL_GUID guid) SDL_strlcat(mapping_string, "misc1:b11,", sizeof(mapping_string)); } else if (SDL_IsJoystickGoogleStadiaController(vendor, product)) { // The Google Stadia controller has a share button and a Google Assistant button - SDL_strlcat(mapping_string, "misc1:b11,misc2:b12", sizeof(mapping_string)); + SDL_strlcat(mapping_string, "misc1:b11,misc2:b12,", sizeof(mapping_string)); } else if (SDL_IsJoystickNVIDIASHIELDController(vendor, product)) { // The NVIDIA SHIELD controller has a share button between back and start buttons SDL_strlcat(mapping_string, "misc1:b11,", sizeof(mapping_string)); if (product == USB_PRODUCT_NVIDIA_SHIELD_CONTROLLER_V103) { // The original SHIELD controller has a touchpad and plus/minus buttons as well - SDL_strlcat(mapping_string, "touchpad:b12,misc2:b13,misc3:b14", sizeof(mapping_string)); + SDL_strlcat(mapping_string, "touchpad:b12,misc2:b13,misc3:b14,", sizeof(mapping_string)); } } else if (SDL_IsJoystickHoriSteamController(vendor, product)) { /* The Wireless HORIPad for Steam has QAM, Steam, Capsense L/R Sticks, 2 rear buttons, and 2 misc buttons */ SDL_strlcat(mapping_string, "paddle1:b13,paddle2:b12,paddle3:b15,paddle4:b14,misc2:b11,misc3:b16,misc4:b17", sizeof(mapping_string)); + } else if (SDL_IsJoystickFlydigiController(vendor, product)) { + SDL_strlcat(mapping_string, "paddle1:b11,paddle2:b12,paddle3:b13,paddle4:b14,", sizeof(mapping_string)); + switch (guid.data[15]) { + case 20: + case 21: + case 22: + case 23: + case 28: + case 80: + case 81: + case 85: + case 105: + // Vader series of controllers have C/Z buttons + SDL_strlcat(mapping_string, "misc2:b15,misc3:b16,", sizeof(mapping_string)); + break; + } + } else if (vendor == USB_VENDOR_8BITDO && product == USB_PRODUCT_8BITDO_ULTIMATE2_WIRELESS) { + SDL_strlcat(mapping_string, "paddle1:b12,paddle2:b11,paddle3:b14,paddle4:b13,", sizeof(mapping_string)); } else { switch (SDL_GetGamepadTypeFromGUID(guid, NULL)) { case SDL_GAMEPAD_TYPE_PS4: @@ -822,6 +862,15 @@ static GamepadMapping_t *SDL_CreateMappingForHIDAPIGamepad(SDL_GUID guid) SDL_strlcat(mapping_string, "paddle1:b16,paddle2:b15,paddle3:b14,paddle4:b13,", sizeof(mapping_string)); } break; + case SDL_GAMEPAD_TYPE_XBOXONE: + if (SDL_IsJoystickXboxOneElite(vendor, product)) { + // XBox One Elite Controllers have 4 back paddle buttons + SDL_strlcat(mapping_string, "paddle1:b11,paddle2:b13,paddle3:b12,paddle4:b14,", sizeof(mapping_string)); + } else if (SDL_IsJoystickXboxSeriesX(vendor, product)) { + // XBox Series X Controllers have a share button under the guide button + SDL_strlcat(mapping_string, "misc1:b11,", sizeof(mapping_string)); + } + break; default: if (vendor == 0 && product == 0) { // This is a Bluetooth Nintendo Switch Pro controller @@ -910,7 +959,7 @@ static GamepadMapping_t *SDL_PrivateMatchGamepadMappingForGUID(SDL_GUID guid, bo // An exact match, including CRC return mapping; } else if (crc && exact_match_crc) { - return NULL; + continue; } if (!best_match) { @@ -981,7 +1030,8 @@ static const char *map_StringForGamepadType[] = { "switchpro", "joyconleft", "joyconright", - "joyconpair" + "joyconpair", + "gamecube" }; SDL_COMPILE_TIME_ASSERT(map_StringForGamepadType, SDL_arraysize(map_StringForGamepadType) == SDL_GAMEPAD_TYPE_COUNT); @@ -1096,7 +1146,7 @@ SDL_COMPILE_TIME_ASSERT(map_StringForGamepadButton, SDL_arraysize(map_StringForG /* * convert a string to its enum equivalent */ -static SDL_GamepadButton SDL_PrivateGetGamepadButtonFromString(const char *str, bool baxy) +static SDL_GamepadButton SDL_PrivateGetGamepadButtonFromString(const char *str, bool axby, bool baxy) { int i; @@ -1106,7 +1156,17 @@ static SDL_GamepadButton SDL_PrivateGetGamepadButtonFromString(const char *str, for (i = 0; i < SDL_arraysize(map_StringForGamepadButton); ++i) { if (SDL_strcasecmp(str, map_StringForGamepadButton[i]) == 0) { - if (baxy) { + if (axby) { + // Need to swap face buttons + switch (i) { + case SDL_GAMEPAD_BUTTON_EAST: + return SDL_GAMEPAD_BUTTON_WEST; + case SDL_GAMEPAD_BUTTON_WEST: + return SDL_GAMEPAD_BUTTON_EAST; + default: + break; + } + } else if (baxy) { // Need to swap face buttons switch (i) { case SDL_GAMEPAD_BUTTON_SOUTH: @@ -1128,7 +1188,7 @@ static SDL_GamepadButton SDL_PrivateGetGamepadButtonFromString(const char *str, } SDL_GamepadButton SDL_GetGamepadButtonFromString(const char *str) { - return SDL_PrivateGetGamepadButtonFromString(str, false); + return SDL_PrivateGetGamepadButtonFromString(str, false, false); } /* @@ -1155,6 +1215,7 @@ static bool SDL_PrivateParseGamepadElement(SDL_Gamepad *gamepad, const char *szG char half_axis_output = 0; int i; SDL_GamepadBinding *new_bindings; + bool axby_mapping = false; bool baxy_mapping = false; SDL_AssertJoysticksLocked(); @@ -1165,12 +1226,17 @@ static bool SDL_PrivateParseGamepadElement(SDL_Gamepad *gamepad, const char *szG half_axis_output = *szGameButton++; } + if (SDL_strstr(gamepad->mapping->mapping, ",hint:SDL_GAMECONTROLLER_USE_GAMECUBE_LABELS:=1") != NULL) { + axby_mapping = true; + } if (SDL_strstr(gamepad->mapping->mapping, ",hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1") != NULL) { baxy_mapping = true; } + // FIXME: We fix these up when loading the mapping, does this ever get hit? + //SDL_assert(!axby_mapping && !baxy_mapping); axis = SDL_GetGamepadAxisFromString(szGameButton); - button = SDL_PrivateGetGamepadButtonFromString(szGameButton, baxy_mapping); + button = SDL_PrivateGetGamepadButtonFromString(szGameButton, axby_mapping, baxy_mapping); if (axis != SDL_GAMEPAD_AXIS_INVALID) { bind.output_type = SDL_GAMEPAD_BINDTYPE_AXIS; bind.output.axis.axis = axis; @@ -1261,7 +1327,7 @@ static bool SDL_PrivateParseGamepadElement(SDL_Gamepad *gamepad, const char *szG static bool SDL_PrivateParseGamepadConfigString(SDL_Gamepad *gamepad, const char *pchString) { char szGameButton[20]; - char szJoystickButton[20]; + char szJoystickButton[128]; bool bGameButton = true; int i = 0; const char *pchPos = pchString; @@ -1336,6 +1402,8 @@ static SDL_GamepadFaceStyle SDL_GetGamepadFaceStyleFromString(const char *string { if (SDL_strcmp(string, "abxy") == 0) { return SDL_GAMEPAD_FACE_STYLE_ABXY; + } else if (SDL_strcmp(string, "axby") == 0) { + return SDL_GAMEPAD_FACE_STYLE_AXBY; } else if (SDL_strcmp(string, "bayx") == 0) { return SDL_GAMEPAD_FACE_STYLE_BAYX; } else if (SDL_strcmp(string, "sony") == 0) { @@ -1357,6 +1425,8 @@ static SDL_GamepadFaceStyle SDL_GetGamepadFaceStyleForGamepadType(SDL_GamepadTyp case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT: case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_PAIR: return SDL_GAMEPAD_FACE_STYLE_BAYX; + case SDL_GAMEPAD_TYPE_GAMECUBE: + return SDL_GAMEPAD_FACE_STYLE_AXBY; default: return SDL_GAMEPAD_FACE_STYLE_ABXY; } @@ -1383,6 +1453,11 @@ static void SDL_UpdateGamepadFaceStyle(SDL_Gamepad *gamepad) } } + if (gamepad->face_style == SDL_GAMEPAD_FACE_STYLE_UNKNOWN && + SDL_strstr(gamepad->mapping->mapping, "SDL_GAMECONTROLLER_USE_GAMECUBE_LABELS") != NULL) { + // This controller uses GameCube button style + gamepad->face_style = SDL_GAMEPAD_FACE_STYLE_AXBY; + } if (gamepad->face_style == SDL_GAMEPAD_FACE_STYLE_UNKNOWN && SDL_strstr(gamepad->mapping->mapping, "SDL_GAMECONTROLLER_USE_BUTTON_LABELS") != NULL) { // This controller uses Nintendo button style @@ -1783,6 +1858,11 @@ static GamepadMapping_t *SDL_PrivateGenerateAutomaticGamepadMapping(const char * char name_string[128]; char mapping[1024]; + // Remove the CRC from the GUID + // We already know that this GUID doesn't have a mapping without the CRC, and we want newly + // added mappings without a CRC to override this mapping. + SDL_SetJoystickGUIDCRC(&guid, 0); + // Remove any commas in the name SDL_strlcpy(name_string, name, sizeof(name_string)); { @@ -1943,7 +2023,37 @@ bool SDL_ReloadGamepadMappings(void) return true; } -static char *SDL_ConvertMappingToPositional(const char *mapping) +static char *SDL_ConvertMappingToPositionalAXBY(const char *mapping) +{ + // Add space for '!' and null terminator + size_t length = SDL_strlen(mapping) + 1 + 1; + char *remapped = (char *)SDL_malloc(length); + if (remapped) { + char *button_B; + char *button_X; + char *hint; + + SDL_strlcpy(remapped, mapping, length); + button_B = SDL_strstr(remapped, ",b:"); + button_X = SDL_strstr(remapped, ",x:"); + hint = SDL_strstr(remapped, "hint:SDL_GAMECONTROLLER_USE_GAMECUBE_LABELS"); + + if (button_B) { + button_B[1] = 'x'; + } + if (button_X) { + button_X[1] = 'b'; + } + if (hint) { + hint += 5; + SDL_memmove(hint + 1, hint, SDL_strlen(hint) + 1); + *hint = '!'; + } + } + return remapped; +} + +static char *SDL_ConvertMappingToPositionalBAXY(const char *mapping) { // Add space for '!' and null terminator size_t length = SDL_strlen(mapping) + 1 + 1; @@ -1956,23 +2066,23 @@ static char *SDL_ConvertMappingToPositional(const char *mapping) char *hint; SDL_strlcpy(remapped, mapping, length); - button_A = SDL_strstr(remapped, "a:"); - button_B = SDL_strstr(remapped, "b:"); - button_X = SDL_strstr(remapped, "x:"); - button_Y = SDL_strstr(remapped, "y:"); + button_A = SDL_strstr(remapped, ",a:"); + button_B = SDL_strstr(remapped, ",b:"); + button_X = SDL_strstr(remapped, ",x:"); + button_Y = SDL_strstr(remapped, ",y:"); hint = SDL_strstr(remapped, "hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS"); if (button_A) { - *button_A = 'b'; + button_A[1] = 'b'; } if (button_B) { - *button_B = 'a'; + button_B[1] = 'a'; } if (button_X) { - *button_X = 'y'; + button_X[1] = 'y'; } if (button_Y) { - *button_Y = 'x'; + button_Y[1] = 'x'; } if (hint) { hint += 5; @@ -1988,9 +2098,11 @@ static char *SDL_ConvertMappingToPositional(const char *mapping) */ static int SDL_PrivateAddGamepadMapping(const char *mappingString, SDL_GamepadMappingPriority priority) { + char *appended = NULL; char *remapped = NULL; char *pchGUID; - SDL_GUID jGUID; + SDL_GUID guid; + Uint16 vendor, product; bool is_default_mapping = false; bool is_xinput_mapping = false; bool existing = false; @@ -2004,6 +2116,28 @@ static int SDL_PrivateAddGamepadMapping(const char *mappingString, SDL_GamepadMa return -1; } + pchGUID = SDL_PrivateGetGamepadGUIDFromMappingString(mappingString); + if (!pchGUID) { + SDL_SetError("Couldn't parse GUID from %s", mappingString); + return -1; + } + if (!SDL_strcasecmp(pchGUID, "default")) { + is_default_mapping = true; + } else if (!SDL_strcasecmp(pchGUID, "xinput")) { + is_xinput_mapping = true; + } + guid = SDL_StringToGUID(pchGUID); + SDL_free(pchGUID); + + SDL_GetJoystickGUIDInfo(guid, &vendor, &product, NULL, NULL); + if (SDL_IsJoystickGameCube(vendor, product) && + SDL_strstr(mappingString, "SDL_GAMECONTROLLER_USE_GAMECUBE_LABELS") == NULL) { + SDL_asprintf(&appended, "%shint:SDL_GAMECONTROLLER_USE_GAMECUBE_LABELS:=1,", mappingString); + if (appended) { + mappingString = appended; + } + } + { // Extract and verify the hint field const char *tmp; @@ -2035,18 +2169,31 @@ static int SDL_PrivateAddGamepadMapping(const char *mappingString, SDL_GamepadMa default_value = false; } - if (SDL_strcmp(hint, "SDL_GAMECONTROLLER_USE_BUTTON_LABELS") == 0) { + if (SDL_strcmp(hint, "SDL_GAMECONTROLLER_USE_GAMECUBE_LABELS") == 0) { // This hint is used to signal whether the mapping uses positional buttons or not if (negate) { // This mapping uses positional buttons, we can use it as-is } else { // This mapping uses labeled buttons, we need to swap them to positional - remapped = SDL_ConvertMappingToPositional(mappingString); + remapped = SDL_ConvertMappingToPositionalAXBY(mappingString); if (!remapped) { goto done; } mappingString = remapped; } + } else if (SDL_strcmp(hint, "SDL_GAMECONTROLLER_USE_BUTTON_LABELS") == 0) { + // This hint is used to signal whether the mapping uses positional buttons or not + if (negate) { + // This mapping uses positional buttons, we can use it as-is + } else { + // This mapping uses labeled buttons, we need to swap them to positional + remapped = SDL_ConvertMappingToPositionalBAXY(mappingString); + if (!remapped) { + goto done; + } + mappingString = remapped; + } + } else { value = SDL_GetHintBoolean(hint, default_value); if (negate) { @@ -2083,20 +2230,7 @@ static int SDL_PrivateAddGamepadMapping(const char *mappingString, SDL_GamepadMa } #endif - pchGUID = SDL_PrivateGetGamepadGUIDFromMappingString(mappingString); - if (!pchGUID) { - SDL_SetError("Couldn't parse GUID from %s", mappingString); - goto done; - } - if (!SDL_strcasecmp(pchGUID, "default")) { - is_default_mapping = true; - } else if (!SDL_strcasecmp(pchGUID, "xinput")) { - is_xinput_mapping = true; - } - jGUID = SDL_StringToGUID(pchGUID); - SDL_free(pchGUID); - - pGamepadMapping = SDL_PrivateAddMappingForGUID(jGUID, mappingString, &existing, priority); + pGamepadMapping = SDL_PrivateAddMappingForGUID(guid, mappingString, &existing, priority); if (!pGamepadMapping) { goto done; } @@ -2112,9 +2246,8 @@ static int SDL_PrivateAddGamepadMapping(const char *mappingString, SDL_GamepadMa result = 1; } done: - if (remapped) { - SDL_free(remapped); - } + SDL_free(appended); + SDL_free(remapped); return result; } @@ -2751,6 +2884,7 @@ SDL_Gamepad *SDL_OpenGamepad(SDL_JoystickID instance_id) gamepad->joystick = SDL_OpenJoystick(instance_id); if (!gamepad->joystick) { + SDL_SetObjectValid(gamepad, SDL_OBJECT_TYPE_GAMEPAD, false); SDL_free(gamepad); SDL_UnlockJoysticks(); return NULL; @@ -2759,6 +2893,7 @@ SDL_Gamepad *SDL_OpenGamepad(SDL_JoystickID instance_id) if (gamepad->joystick->naxes) { gamepad->last_match_axis = (SDL_GamepadBinding **)SDL_calloc(gamepad->joystick->naxes, sizeof(*gamepad->last_match_axis)); if (!gamepad->last_match_axis) { + SDL_SetObjectValid(gamepad, SDL_OBJECT_TYPE_GAMEPAD, false); SDL_CloseJoystick(gamepad->joystick); SDL_free(gamepad); SDL_UnlockJoysticks(); @@ -2768,6 +2903,7 @@ SDL_Gamepad *SDL_OpenGamepad(SDL_JoystickID instance_id) if (gamepad->joystick->nhats) { gamepad->last_hat_mask = (Uint8 *)SDL_calloc(gamepad->joystick->nhats, sizeof(*gamepad->last_hat_mask)); if (!gamepad->last_hat_mask) { + SDL_SetObjectValid(gamepad, SDL_OBJECT_TYPE_GAMEPAD, false); SDL_CloseJoystick(gamepad->joystick); SDL_free(gamepad->last_match_axis); SDL_free(gamepad); @@ -2986,6 +3122,24 @@ static SDL_GamepadButtonLabel SDL_GetGamepadButtonLabelForFaceStyle(SDL_GamepadF break; } break; + case SDL_GAMEPAD_FACE_STYLE_AXBY: + switch (button) { + case SDL_GAMEPAD_BUTTON_SOUTH: + label = SDL_GAMEPAD_BUTTON_LABEL_A; + break; + case SDL_GAMEPAD_BUTTON_EAST: + label = SDL_GAMEPAD_BUTTON_LABEL_X; + break; + case SDL_GAMEPAD_BUTTON_WEST: + label = SDL_GAMEPAD_BUTTON_LABEL_B; + break; + case SDL_GAMEPAD_BUTTON_NORTH: + label = SDL_GAMEPAD_BUTTON_LABEL_Y; + break; + default: + break; + } + break; case SDL_GAMEPAD_FACE_STYLE_BAYX: switch (button) { case SDL_GAMEPAD_BUTTON_SOUTH: diff --git a/libs/SDL3/src/joystick/SDL_gamepad_db.h b/libs/SDL3/src/joystick/SDL_gamepad_db.h index 64e2dd78..94b164f0 100644 --- a/libs/SDL3/src/joystick/SDL_gamepad_db.h +++ b/libs/SDL3/src/joystick/SDL_gamepad_db.h @@ -197,7 +197,6 @@ 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,", "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,", - "03000000790000004418000000000000,Mayflash GameCube Controller,a:b1,b:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b0,y:b3,", "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,", "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,", @@ -207,7 +206,8 @@ static const char *s_GamepadMappings[] = { "03000000152000000182000000000000,NGDS,a:b2,b:b1,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:a3,righty:a4,start:b9,x:b3,y:b0,", "030000005509000000b4000000000000,NVIDIA Virtual Gamepad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:+a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:-a2,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", "030000004b120000014d000000000000,NYKO AIRFLO EX,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b2,y:b3,", - "03000000790000004318000000000000,Nintendo GameCube Controller,a:b1,b:b0,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b2,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "03000000790000004318000000000000,Nintendo GameCube Controller,a:b1,b:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b0,y:b3,", + "03000000790000004418000000000000,Nintendo GameCube Controller,a:b1,b:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b0,y:b3,", "03000000bd12000015d0000000000000,Nintendo Retrolink USB Super SNES Classic Controller,a:b2,b:b1,back:b8,leftshoulder:b4,leftx:a0,lefty:a1,rightshoulder:b5,start:b9,x:b3,y:b0,", "030000007e0500000920000000000000,Nintendo Switch Pro Controller,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:a2,righty:a3,start:b9,x:b2,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "030000000d0500000308000000000000,Nostromo N45,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b12,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b10,x:b2,y:b3,", @@ -215,7 +215,7 @@ static const char *s_GamepadMappings[] = { "03000000362800000100000000000000,OUYA Game Controller,a:b0,b:b3,dpdown:b9,dpleft:b10,dpright:b11,dpup:b8,guide:b14,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:b13,rightx:a3,righty:a4,x:b1,y:b2,", "03000000782300000a10000000000000,Onlive Wireless Controller,a:b15,b:b14,back:b7,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b5,leftshoulder:b11,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a3,righty:a4,start:b6,x:b13,y:b12,", "030000006b14000001a1000000000000,Orange Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b2,y:b3,", - "0300000009120000072f000000000000,OrangeFox86 DreamPicoPort,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:-a2,leftx:a0,lefty:a1,righttrigger:-a5,start:b11,x:b3,y:b4,", + "0300000009120000072f000000000000,OrangeFox86 DreamPicoPort,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:-a2,leftx:a0,lefty:a1,righttrigger:-a5,rightx:a3,righty:a4,start:b11,x:b3,y:b4,", "03000000120c0000f60e000000000000,P4 Wired Gamepad,a:b1,b:b2,back:b12,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b5,lefttrigger:b7,rightshoulder:b4,righttrigger:b6,start:b9,x:b0,y:b3,", "030000006f0e00000901000000000000,PDP Versus Fighting Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,", "03000000632500002306000000000000,PS Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b3,y:b4,", @@ -242,6 +242,7 @@ static const char *s_GamepadMappings[] = { "03000000d62000002640000000000000,PowerA OPS v1 Wireless Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,", "03000000d62000003340000000000000,PowerA OPS v3 Pro Wireless Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,", "03000000d62000006dca000000000000,PowerA Pro Ex,a:b1,b:b2,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:a2,righty:a3,start:b9,x:b0,y:b3,", + "03000000d620000011a7000000000000,PowerA Wired GameCube Controller,a:b1,b:b2,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:a2,righty:a3,start:b9,x:b0,y:b3,", "03000000d62000009557000000000000,Pro Elite PS3 Controller,a:b1,b:b2,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:a2,righty:a3,start:b9,x:b0,y:b3,", "03000000d62000009f31000000000000,Pro Ex mini PS3 Controller,a:b1,b:b2,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:a2,righty:a3,start:b9,x:b0,y:b3,", "03000000d6200000c757000000000000,Pro Ex mini PS3 Controller,a:b1,b:b2,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:a2,righty:a3,start:b9,x:b0,y:b3,", @@ -310,7 +311,6 @@ static const char *s_GamepadMappings[] = { "03000000110100003114000000000000,SteelSeries Stratus Duo,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,", "03000000381000001814000000000000,SteelSeries Stratus XL,a:b0,b:b1,back:b18,dpdown:b13,dpleft:b14,dpright:b15,dpup:b12,guide:b19,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b2,y:b3,", "03000000110100001914000000000000,SteelSeries,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:,leftstick:b13,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:,rightstick:b14,righttrigger:b7,rightx:a3,righty:a4,start:b11,x:b3,y:b4,", - "03000000d620000011a7000000000000,Switch,a:b1,b:b2,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:a2,righty:a3,start:b9,x:b0,y:b3,", "030000004f04000007d0000000000000,T Mini Wireless,a:b1,b:b2,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:a2,righty:a3,start:b9,x:b0,y:b3,", "03000000fa1900000706000000000000,Team 5,a:b2,b:b1,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:b3,y:b0,", "03000000b50700001203000000000000,Techmobility X6-38V,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,", @@ -413,15 +413,16 @@ static const char *s_GamepadMappings[] = { "03000000380700005082000000010000,Mad Catz FightPad PRO (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", "03000000380700008433000000010000,Mad Catz 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,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", "03000000380700008483000000010000,Mad Catz FightStick TE S+ (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", - "03000000790000004418000000010000,Mayflash GameCube Controller,a:b1,b:b2,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b0,y:b3,", "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:b4,b:b8,back:b32,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b16,leftstick:b40,lefttrigger:b24,leftx:a0,lefty:a4,rightshoulder:b20,rightstick:b44,righttrigger:b28,rightx:a8,righty:a12,start:b36,x:b0,y:b12,", + "03000000853200008906000000010000,NACON Revolution X Unlimited,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,", "030000001008000001e5000006010000,NEXT SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b6,start:b9,x:b3,y:b0,", "03000000550900001472000025050000,NVIDIA Controller v01.04,a:b0,b:b1,back:b17,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b15,leftshoulder:b4,leftstick:b7,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a5,start:b6,x:b2,y:b3,", "030000004b120000014d000000010000,NYKO AIRFLO EX,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b2,y:b3,", + "03000000790000004418000000010000,Nintendo GameCube Controller,a:b1,b:b2,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b0,y:b3,", "030000007e0500000920000000000000,Nintendo Switch Pro Controller,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:a2,righty:a3,start:b9,x:b2,y:b3,", "050000007e05000009200000ff070000,Nintendo Switch Pro Controller,a:b1,b:b0,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b10,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", - "0300000009120000072f000000010000,OrangeFox86 DreamPicoPort,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:a2,leftx:a0,lefty:a1,righttrigger:a5,start:b11,x:b3,y:b4,", + "0300000009120000072f000000010000,OrangeFox86 DreamPicoPort,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:a2,leftx:a0,lefty:a1,righttrigger:a5,rightx:a3,righty:a4,start:b11,x:b3,y:b4,", "030000006f0e00000901000002010000,PDP Versus Fighting Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,", "030000004c0500006802000000000000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,", "030000004c0500006802000000010000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,", @@ -550,9 +551,10 @@ static const char *s_GamepadMappings[] = { "03000000790000001100000010010000,Elecom Gamepad,crc:e86c,a:b2,b:b3,back:b6,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b7,x:b0,y:b1,", "0300000079000000d418000000010000,GPD Win 2 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", "0500000047532067616d657061640000,GS Gamepad,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,", - "03000000341a000005f7000010010000,GameCube {HuiJia USB box},a:b1,b:b2,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b0,y:b3,", + "03000000341a000005f7000010010000,GameCube {HuiJia USB box},a:b1,b:b0,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b2,y:b3,", "03000000bc2000000055000011010000,GameSir G3w,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,", "0500000049190000020400001b010000,GameSir T4 Pro,crc:8283,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b23,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,", + "03000000373500009710000001020000,GameSir-K1 FLUX,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b15,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,", "03000000ac0500001a06000011010000,GameSir-T3 2.02,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b15,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,", "0500000047532047616d657061640000,GameStop Gamepad,a:b0,b:b1,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:b2,y:b3,", "03000000c01100000140000011010000,GameStop PS4 Fun Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", @@ -622,7 +624,6 @@ static const char *s_GamepadMappings[] = { "03000000380700008084000011010000,Mad Catz fightstick (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", "03000000380700001888000010010000,MadCatz PC USB Wired Stick 8818,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,", "03000000380700003888000010010000,MadCatz PC USB Wired Stick 8838,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:a0,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", - "03000000790000004418000010010000,Mayflash GameCube Controller,a:b1,b:b2,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b0,y:b3,", "03000000780000000600000010010000,Microntek USB Joystick,a:b2,b:b1,back:b8,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,start:b9,x:b3,y:b0,", "030000005e0400000e00000000010000,Microsoft SideWinder,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,rightshoulder:b7,start:b8,x:b3,y:b4,", "030000005e0400008e02000004010000,Microsoft X-Box 360 pad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", @@ -640,7 +641,8 @@ static const char *s_GamepadMappings[] = { "05000000550900001472000001000000,NVIDIA Controller v01.04,a:b0,b:b1,back:b14,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b16,leftshoulder:b4,leftstick:b7,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a5,start:b6,x:b2,y:b3,", "030000004b120000014d000000010000,NYKO AIRFLO EX,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b2,y:b3,", "03000000451300000830000010010000,NYKO CORE,a:b1,b:b2,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:a2,righty:a5,start:b9,x:b0,y:b3,", - "03000000790000004318000010010000,Nintendo GameCube Controller,a:b1,b:b0,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b2,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "03000000790000004318000010010000,Nintendo GameCube Controller,a:b1,b:b2,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b0,y:b3,", + "03000000790000004418000010010000,Nintendo GameCube Controller,a:b1,b:b2,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b0,y:b3,", "030000007e0500001920000011810000,Nintendo N64 Controller,crc:d670,a:b0,b:b1,back:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b9,lefttrigger:b8,leftx:a0,lefty:a1,misc1:b5,rightshoulder:b7,righttrigger:b3,start:b11,x:b4,y:b10,", "050000007e0500001920000001800000,Nintendo N64 Controller,crc:5e1c,a:b0,b:b1,back:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b9,lefttrigger:b8,leftx:a0,lefty:a1,misc1:b5,rightshoulder:b7,righttrigger:b3,start:b11,x:b4,y:b10,", "030000007e0500001e20000011810000,Nintendo SEGA Genesis Controller,crc:bb22,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b6,misc1:b3,rightshoulder:b2,righttrigger:b4,start:b5,", @@ -652,7 +654,6 @@ static const char *s_GamepadMappings[] = { "060000007e0500000820000000000000,Nintendo Switch Joy-Con (L/R),a:b0,b:b1,back:b9,dpdown:b15,dpleft:b16,dpright:b17,dpup:b14,guide:b11,leftshoulder:b5,leftstick:b12,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b13,righttrigger:b8,rightx:a2,righty:a3,start:b10,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "050000007e0500000720000001800000,Nintendo Switch Joy-Con (R),a:b1,b:b2,guide:b9,leftshoulder:b4,leftstick:b10,leftx:a1~,lefty:a0,rightshoulder:b6,start:b8,x:b0,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "03000000d620000013a7000011010000,Nintendo Switch PowerA Controller,a:b1,b:b2,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:a2,righty:a3,start:b9,x:b0,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", - "03000000d620000011a7000011010000,Nintendo Switch PowerA Core Plus Controller,a:b1,b:b2,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:a2,righty:a3,start:b9,x:b0,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "030000007e0500000920000011810000,Nintendo Switch Pro Controller,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b11,leftshoulder:b5,leftstick:b12,lefttrigger:b7,leftx:a0,lefty:a1,misc1:b4,rightshoulder:b6,rightstick:b13,righttrigger:b8,rightx:a2,righty:a3,start:b10,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "050000004c69632050726f20436f6e00,Nintendo Switch Pro Controller,crc:15b7,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,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "050000007e0500000920000001000000,Nintendo Switch Pro Controller,a:b1,b:b0,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:a2,righty:a3,start:b9,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", @@ -699,6 +700,7 @@ static const char *s_GamepadMappings[] = { "050000004c050000e60c000000010000,PS5 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", "050000004c050000e60c000000810000,PS5 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,", "030000004c050000da0c000011010000,Playstation Controller,a:b2,b:b1,back:b8,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,start:b9,x:b3,y:b0,", + "03000000d620000011a7000011010000,PowerA Wired GameCube Controller,a:b1,b:b2,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:a2,righty:a3,start:b9,x:b0,y:b3,", "03000000c62400003a54000001010000,PowerA XBox One Controller,a:b0,b:b1,back:b6,dpdown:h0.7,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", "03000000c62400000053000000010000,PowerA,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", "03000000300f00001211000011010000,QanBa Arcade JoyStick,a:b2,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b5,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b6,start:b9,x:b1,y:b3,", @@ -795,6 +797,7 @@ static const char *s_GamepadMappings[] = { "03000000c0160000e105000010010000,Xin-Mo Dual Arcade,crc:82d5,a:b1,b:b2,back:b9,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,rightshoulder:b4,righttrigger:b5,start:b8,x:b0,y:b3,", /* Ultimate Atari Fight Stick */ "03000000120c0000100e000011010000,ZEROPLUS P4 Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", "03000000120c0000101e000011010000,ZEROPLUS P4 Wired Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", + "03000000120c0000182e000011010000,ZEROPLUS P4 Wired Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,", "03000000666600006706000000010000,boom PSX to PC Converter,a:b2,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,leftshoulder:b6,leftstick:b9,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b10,righttrigger:b5,rightx:a2,righty:a3,start:b11,x:b3,y:b0,", "03000000830500006020000010010000,iBuffalo SNES Controller,a:b1,b:b0,back:b6,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b7,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "050000006964726f69643a636f6e0000,idroid:con,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,", @@ -867,7 +870,9 @@ static const char *s_GamepadMappings[] = { "05000000ac05000001000000ff076d01,*,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b10,x:b2,y:b3,", "05000000ac050000020000004f066d02,*,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b6,leftshoulder:b4,rightshoulder:b5,x:b2,y:b3,", "05000000ac05000004000000a8986d04,8BitDo Micro gamepad,a:b1,b:b0,back:b4,dpdown:b7,dpleft:b8,dpright:b9,dpup:b10,guide:b2,leftshoulder:b11,lefttrigger:b12,rightshoulder:b13,righttrigger:b14,start:b3,x:b6,y:b5,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "05000000ac05000004000000fd216d04,8BitDo Pro 2,crc:ac95,a:b3,b:b2,back:b6,dpdown:b9,dpleft:b10,dpright:b11,dpup:b12,guide:b4,leftshoulder:b13,leftstick:b14,lefttrigger:+a2,leftx:a0,lefty:a1~,paddle1:b1,paddle2:b0,rightshoulder:b16,rightstick:b17,righttrigger:+a5,rightx:a3,righty:a4~,start:b5,x:b8,y:b7,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "05000000ac050000040000003b8a6d04,8BitDo SN30 Pro+,crc:3e00,a:b1,b:b0,back:b4,dpdown:b7,dpleft:b8,dpright:b9,dpup:b10,guide:b2,leftshoulder:b11,leftstick:b12,lefttrigger:b13,leftx:a0,lefty:a1~,rightshoulder:b14,rightstick:b15,righttrigger:b16,rightx:a2,righty:a3~,start:b3,x:b6,y:b5,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", + "05000000ac05000004000000209f6d04,8Bitdo SN30 Pro,crc:40d6,a:b1,b:b0,back:b4,dpdown:b7,dpleft:b8,dpright:b9,dpup:b10,guide:b2,leftshoulder:b11,leftstick:b12,lefttrigger:b13,leftx:a0,lefty:a1~,rightshoulder:b14,rightstick:b15,righttrigger:b16,rightx:a2,righty:a3~,start:b3,x:b6,y:b5,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", "050000008a35000003010000ff070000,Backbone One,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b10,x:b2,y:b3,", "050000008a35000004010000ff070000,Backbone One,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b10,x:b2,y:b3,", "050000007e050000062000000f060000,Nintendo Switch Joy-Con (L),+leftx:h0.2,+lefty:h0.4,-leftx:h0.8,-lefty:h0.1,a:b0,b:b2,leftshoulder:b4,rightshoulder:b5,x:b1,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", @@ -904,7 +909,7 @@ static const char *s_GamepadMappings[] = { "0000000050535669746120436f6e7400,PSVita Controller,crc:d598,a:b2,b:b1,back:b10,dpdown:b6,dpleft:b7,dpright:b9,dpup:b8,leftshoulder:b4,leftstick:b14,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b15,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b3,y:b0,", #endif #ifdef SDL_JOYSTICK_N3DS - "000000004e696e74656e646f20334400,Nintendo 3DS,crc:3210,a:b0,b:b1,back:b2,dpdown:b7,dpleft:b5,dpright:b4,dpup:b6,leftshoulder:b9,lefttrigger:b14,leftx:a0,lefty:a1,rightshoulder:b8,righttrigger:b15,rightx:a2,righty:a3,start:b3,x:b10,y:b11,", + "000000004e696e74656e646f20334400,Nintendo 3DS,crc:3210,a:b1,b:b0,back:b2,dpdown:b7,dpleft:b5,dpright:b4,dpup:b6,leftshoulder:b9,lefttrigger:b14,leftx:a0,lefty:a1,rightshoulder:b8,righttrigger:b15,rightx:a2,righty:a3,start:b3,x:b11,y:b10,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", #endif NULL }; diff --git a/libs/SDL3/src/joystick/SDL_joystick.c b/libs/SDL3/src/joystick/SDL_joystick.c index 78c64b7f..0e9fcab3 100644 --- a/libs/SDL3/src/joystick/SDL_joystick.c +++ b/libs/SDL3/src/joystick/SDL_joystick.c @@ -125,6 +125,172 @@ static int SDL_joystick_player_count SDL_GUARDED_BY(SDL_joystick_lock) = 0; static SDL_JoystickID *SDL_joystick_players SDL_GUARDED_BY(SDL_joystick_lock) = NULL; static bool SDL_joystick_allows_background_events = false; +static Uint32 initial_old_xboxone_controllers[] = { + MAKE_VIDPID(0x0000, 0x6686), + MAKE_VIDPID(0x0079, 0x18a1), + MAKE_VIDPID(0x0079, 0x18c2), + MAKE_VIDPID(0x0079, 0x18c8), + MAKE_VIDPID(0x0079, 0x18cf), + MAKE_VIDPID(0x03f0, 0x0495), + MAKE_VIDPID(0x045e, 0x02d1), + MAKE_VIDPID(0x045e, 0x02dd), + MAKE_VIDPID(0x045e, 0x02e0), + MAKE_VIDPID(0x045e, 0x02e3), + MAKE_VIDPID(0x045e, 0x02ea), + MAKE_VIDPID(0x045e, 0x02fd), + MAKE_VIDPID(0x045e, 0x02ff), + MAKE_VIDPID(0x045e, 0x0867), + MAKE_VIDPID(0x045e, 0x0b00), + MAKE_VIDPID(0x045e, 0x0b05), + MAKE_VIDPID(0x045e, 0x0b0a), + MAKE_VIDPID(0x045e, 0x0b0c), + MAKE_VIDPID(0x045e, 0x0b20), + MAKE_VIDPID(0x045e, 0x0b21), + MAKE_VIDPID(0x045e, 0x0b22), + MAKE_VIDPID(0x046d, 0x0000), + MAKE_VIDPID(0x046d, 0x1004), + MAKE_VIDPID(0x046d, 0x1007), + MAKE_VIDPID(0x046d, 0x1008), + MAKE_VIDPID(0x046d, 0xf301), + MAKE_VIDPID(0x0738, 0x02a0), + MAKE_VIDPID(0x0738, 0x4a01), + MAKE_VIDPID(0x0738, 0x7263), + MAKE_VIDPID(0x0738, 0xb738), + MAKE_VIDPID(0x0738, 0xcb29), + MAKE_VIDPID(0x0738, 0xf401), + MAKE_VIDPID(0x0c12, 0x0e17), + MAKE_VIDPID(0x0c12, 0x0e1c), + MAKE_VIDPID(0x0c12, 0x0e22), + MAKE_VIDPID(0x0c12, 0x0e30), + MAKE_VIDPID(0x0d62, 0x9a1a), + MAKE_VIDPID(0x0d62, 0x9a1b), + MAKE_VIDPID(0x0e00, 0x0e00), + MAKE_VIDPID(0x0e6f, 0x012a), + MAKE_VIDPID(0x0e6f, 0x0139), + MAKE_VIDPID(0x0e6f, 0x013B), + MAKE_VIDPID(0x0e6f, 0x013a), + MAKE_VIDPID(0x0e6f, 0x0145), + MAKE_VIDPID(0x0e6f, 0x0146), + MAKE_VIDPID(0x0e6f, 0x0152), + MAKE_VIDPID(0x0e6f, 0x015b), + MAKE_VIDPID(0x0e6f, 0x015c), + MAKE_VIDPID(0x0e6f, 0x015d), + MAKE_VIDPID(0x0e6f, 0x015f), + MAKE_VIDPID(0x0e6f, 0x0160), + MAKE_VIDPID(0x0e6f, 0x0161), + MAKE_VIDPID(0x0e6f, 0x0162), + MAKE_VIDPID(0x0e6f, 0x0163), + MAKE_VIDPID(0x0e6f, 0x0164), + MAKE_VIDPID(0x0e6f, 0x0165), + MAKE_VIDPID(0x0e6f, 0x0166), + MAKE_VIDPID(0x0e6f, 0x0167), + MAKE_VIDPID(0x0e6f, 0x0205), + MAKE_VIDPID(0x0e6f, 0x0206), + MAKE_VIDPID(0x0e6f, 0x0246), + MAKE_VIDPID(0x0e6f, 0x0261), + MAKE_VIDPID(0x0e6f, 0x0262), + MAKE_VIDPID(0x0e6f, 0x02a0), + MAKE_VIDPID(0x0e6f, 0x02a1), + MAKE_VIDPID(0x0e6f, 0x02a2), + MAKE_VIDPID(0x0e6f, 0x02a3), + MAKE_VIDPID(0x0e6f, 0x02a4), + MAKE_VIDPID(0x0e6f, 0x02a5), + MAKE_VIDPID(0x0e6f, 0x02a6), + MAKE_VIDPID(0x0e6f, 0x02a7), + MAKE_VIDPID(0x0e6f, 0x02a8), + MAKE_VIDPID(0x0e6f, 0x02a9), + MAKE_VIDPID(0x0e6f, 0x02aa), + MAKE_VIDPID(0x0e6f, 0x02ab), + MAKE_VIDPID(0x0e6f, 0x02ac), + MAKE_VIDPID(0x0e6f, 0x02ad), + MAKE_VIDPID(0x0e6f, 0x02ae), + MAKE_VIDPID(0x0e6f, 0x02af), + MAKE_VIDPID(0x0e6f, 0x02b0), + MAKE_VIDPID(0x0e6f, 0x02b1), + MAKE_VIDPID(0x0e6f, 0x02b2), + MAKE_VIDPID(0x0e6f, 0x02b3), + MAKE_VIDPID(0x0e6f, 0x02b5), + MAKE_VIDPID(0x0e6f, 0x02b6), + MAKE_VIDPID(0x0e6f, 0x02b8), + MAKE_VIDPID(0x0e6f, 0x02bd), + MAKE_VIDPID(0x0e6f, 0x02be), + MAKE_VIDPID(0x0e6f, 0x02bf), + MAKE_VIDPID(0x0e6f, 0x02c0), + MAKE_VIDPID(0x0e6f, 0x02c1), + MAKE_VIDPID(0x0e6f, 0x02c2), + MAKE_VIDPID(0x0e6f, 0x02c3), + MAKE_VIDPID(0x0e6f, 0x02c4), + MAKE_VIDPID(0x0e6f, 0x02c5), + MAKE_VIDPID(0x0e6f, 0x02c6), + MAKE_VIDPID(0x0e6f, 0x02c7), + MAKE_VIDPID(0x0e6f, 0x02c8), + MAKE_VIDPID(0x0e6f, 0x02c9), + MAKE_VIDPID(0x0e6f, 0x02ca), + MAKE_VIDPID(0x0e6f, 0x02cb), + MAKE_VIDPID(0x0e6f, 0x02cd), + MAKE_VIDPID(0x0e6f, 0x02ce), + MAKE_VIDPID(0x0e6f, 0x02cf), + MAKE_VIDPID(0x0e6f, 0x02d5), + MAKE_VIDPID(0x0e6f, 0x0346), + MAKE_VIDPID(0x0e6f, 0x0446), + MAKE_VIDPID(0x0e6f, 0xf501), + MAKE_VIDPID(0x0f0d, 0x0063), + MAKE_VIDPID(0x0f0d, 0x0067), + MAKE_VIDPID(0x0f0d, 0x0078), + MAKE_VIDPID(0x0f0d, 0x0097), + MAKE_VIDPID(0x0f0d, 0x00ba), + MAKE_VIDPID(0x0f0d, 0x00c0), + MAKE_VIDPID(0x0f0d, 0x00c5), + MAKE_VIDPID(0x0f0d, 0x00d8), + MAKE_VIDPID(0x0f0d, 0x00ed), + MAKE_VIDPID(0x0fff, 0x02a1), + MAKE_VIDPID(0x12ab, 0x0304), + MAKE_VIDPID(0x1430, 0x0291), + MAKE_VIDPID(0x1430, 0x02a9), + MAKE_VIDPID(0x1430, 0x070b), + MAKE_VIDPID(0x1430, 0x0719), + MAKE_VIDPID(0x146b, 0x0611), + MAKE_VIDPID(0x1532, 0x0a00), + MAKE_VIDPID(0x1532, 0x0a03), + MAKE_VIDPID(0x1532, 0x0a14), + MAKE_VIDPID(0x1532, 0x0a15), + MAKE_VIDPID(0x16d0, 0x0f3f), + MAKE_VIDPID(0x1bad, 0x028e), + MAKE_VIDPID(0x1bad, 0x02a0), + MAKE_VIDPID(0x1bad, 0x5500), + MAKE_VIDPID(0x20ab, 0x55ef), + MAKE_VIDPID(0x24c6, 0x541a), + MAKE_VIDPID(0x24c6, 0x542a), + MAKE_VIDPID(0x24c6, 0x543a), + MAKE_VIDPID(0x24c6, 0x5509), + MAKE_VIDPID(0x24c6, 0x551a), + MAKE_VIDPID(0x24c6, 0x561a), + MAKE_VIDPID(0x24c6, 0x581a), + MAKE_VIDPID(0x24c6, 0x591a), + MAKE_VIDPID(0x24c6, 0x592a), + MAKE_VIDPID(0x24c6, 0x791a), + MAKE_VIDPID(0x2516, 0x0069), + MAKE_VIDPID(0x25b1, 0x0360), + MAKE_VIDPID(0x2c22, 0x2203), + MAKE_VIDPID(0x2e24, 0x0652), + MAKE_VIDPID(0x2e24, 0x1618), + MAKE_VIDPID(0x2e24, 0x1688), + MAKE_VIDPID(0x2f24, 0x0011), + MAKE_VIDPID(0x2f24, 0x002e), + MAKE_VIDPID(0x2f24, 0x0050), + MAKE_VIDPID(0x2f24, 0x0053), + MAKE_VIDPID(0x2f24, 0x008f), + MAKE_VIDPID(0x2f24, 0x0091), + MAKE_VIDPID(0x2f24, 0x00b7), + MAKE_VIDPID(0xd2d2, 0xd2d2), +}; +static SDL_vidpid_list old_xboxone_controllers = { + "SDL_JOYSTICK_OLD_XBOXONE_CONTROLLERS", 0, 0, NULL, + "SDL_JOYSTICK_OLD_XBOXONE_CONTROLLERS_EXCLUDED", 0, 0, NULL, + SDL_arraysize(initial_old_xboxone_controllers), initial_old_xboxone_controllers, + false +}; + static Uint32 initial_arcadestick_devices[] = { MAKE_VIDPID(0x0079, 0x181a), // Venom Arcade Stick MAKE_VIDPID(0x0079, 0x181b), // Venom Arcade Stick @@ -277,6 +443,7 @@ static Uint32 initial_blacklist_devices[] = { MAKE_VIDPID(0x1532, 0x0282), // Razer Huntsman Mini Analog, non-functional DInput device MAKE_VIDPID(0x26ce, 0x01a2), // ASRock LED Controller MAKE_VIDPID(0x20d6, 0x0002), // PowerA Enhanced Wireless Controller for Nintendo Switch (charging port only) + MAKE_VIDPID(0x3434, 0x0211), // Keychron K1 Pro System Control }; static SDL_vidpid_list blacklist_devices = { SDL_HINT_JOYSTICK_BLACKLIST_DEVICES, 0, 0, NULL, @@ -289,7 +456,17 @@ static Uint32 initial_flightstick_devices[] = { MAKE_VIDPID(0x044f, 0x0402), // HOTAS Warthog Joystick MAKE_VIDPID(0x044f, 0xb10a), // ThrustMaster, Inc. T.16000M Joystick MAKE_VIDPID(0x046d, 0xc215), // Logitech Extreme 3D + MAKE_VIDPID(0x0583, 0x6258), // Padix USB joystick with viewfinder + MAKE_VIDPID(0x0583, 0x688f), // Padix QF-688uv Windstorm Pro + MAKE_VIDPID(0x0583, 0x7070), // Padix QF-707u Bazooka + MAKE_VIDPID(0x0583, 0xa019), // Padix USB vibration joystick with viewfinder + MAKE_VIDPID(0x0583, 0xa131), // Padix USB Wireless 2.4GHz + MAKE_VIDPID(0x0583, 0xa209), // Padix MetalStrike ForceFeedback + MAKE_VIDPID(0x0583, 0xb010), // Padix MetalStrike Pro + MAKE_VIDPID(0x0583, 0xb012), // Padix Wireless MetalStrike + MAKE_VIDPID(0x0583, 0xb013), // Padix USB Wireless 2.4GHZ MAKE_VIDPID(0x0738, 0x2221), // Saitek Pro Flight X-56 Rhino Stick + MAKE_VIDPID(0x10f5, 0x7084), // Turtle Beach VelocityOne MAKE_VIDPID(0x231d, 0x0126), // Gunfighter Mk.III 'Space Combat Edition' (right) MAKE_VIDPID(0x231d, 0x0127), // Gunfighter Mk.III 'Space Combat Edition' (left) MAKE_VIDPID(0x362c, 0x0001), // Yawman Arrow @@ -302,7 +479,13 @@ static SDL_vidpid_list flightstick_devices = { }; static Uint32 initial_gamecube_devices[] = { + MAKE_VIDPID(0x0079, 0x1843), // DragonRise GameCube Controller Adapter + MAKE_VIDPID(0x0079, 0x1844), // DragonRise GameCube Controller Adapter + MAKE_VIDPID(0x0079, 0x1846), // DragonRise GameCube Controller Adapter + MAKE_VIDPID(0x057e, 0x0337), // Nintendo Wii U GameCube Controller Adapter + MAKE_VIDPID(0x0926, 0x8888), // Cyber Gadget GameCube Controller MAKE_VIDPID(0x0e6f, 0x0185), // PDP Wired Fight Pad Pro for Nintendo Switch + MAKE_VIDPID(0x1a34, 0xf705), // GameCube {HuiJia USB box} MAKE_VIDPID(0x20d6, 0xa711), // PowerA Wired Controller Nintendo GameCube Style }; static SDL_vidpid_list gamecube_devices = { @@ -331,6 +514,7 @@ static SDL_vidpid_list rog_gamepad_mice = { static Uint32 initial_throttle_devices[] = { MAKE_VIDPID(0x044f, 0x0404), // HOTAS Warthog Throttle MAKE_VIDPID(0x0738, 0xa221), // Saitek Pro Flight X-56 Rhino Throttle + MAKE_VIDPID(0x10f5, 0x7085), // Turtle Beach VelocityOne Throttle }; static SDL_vidpid_list throttle_devices = { SDL_HINT_JOYSTICK_THROTTLE_DEVICES, 0, 0, NULL, @@ -374,6 +558,14 @@ static Uint32 initial_wheel_devices[] = { MAKE_VIDPID(0x046d, 0xca03), // Logitech Momo Racing MAKE_VIDPID(0x0483, 0x0522), // Simagic Wheelbase (including M10, Alpha Mini, Alpha, Alpha U) MAKE_VIDPID(0x0483, 0xa355), // VRS DirectForce Pro Wheel Base + MAKE_VIDPID(0x0583, 0xa132), // Padix USB Wireless 2.4GHz Wheelpad + MAKE_VIDPID(0x0583, 0xa133), // Padix USB Wireless 2.4GHz Wheel + MAKE_VIDPID(0x0583, 0xa202), // Padix Force Feedback Wheel + MAKE_VIDPID(0x0583, 0xb002), // Padix Vibration USB Wheel + MAKE_VIDPID(0x0583, 0xb005), // Padix USB Wheel + MAKE_VIDPID(0x0583, 0xb008), // Padix USB Wireless 2.4GHz Wheel + MAKE_VIDPID(0x0583, 0xb009), // Padix USB Wheel + MAKE_VIDPID(0x0583, 0xb018), // Padix TW6 Wheel MAKE_VIDPID(0x0eb7, 0x0001), // Fanatec ClubSport Wheel Base V2 MAKE_VIDPID(0x0eb7, 0x0004), // Fanatec ClubSport Wheel Base V2.5 MAKE_VIDPID(0x0eb7, 0x0005), // Fanatec CSL Elite Wheel Base+ (PS4) @@ -636,8 +828,7 @@ bool SDL_InitJoysticks(void) SDL_joysticks_initialized = true; - SDL_InitGamepadMappings(); - + SDL_LoadVIDPIDList(&old_xboxone_controllers); SDL_LoadVIDPIDList(&arcadestick_devices); SDL_LoadVIDPIDList(&blacklist_devices); SDL_LoadVIDPIDList(&flightstick_devices); @@ -647,6 +838,8 @@ bool SDL_InitJoysticks(void) SDL_LoadVIDPIDList(&wheel_devices); SDL_LoadVIDPIDList(&zero_centered_devices); + SDL_InitGamepadMappings(); + // See if we should allow joystick events while in the background SDL_AddHintCallback(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, SDL_JoystickAllowBackgroundEventsChanged, NULL); @@ -1122,7 +1315,9 @@ SDL_Joystick *SDL_OpenJoystick(SDL_JoystickID instance_id) joystick->attached = true; joystick->led_expiration = SDL_GetTicks(); joystick->battery_percent = -1; +#ifdef SDL_JOYSTICK_VIRTUAL joystick->is_virtual = (driver == &SDL_VIRTUAL_JoystickDriver); +#endif if (!driver->Open(joystick, device_index)) { SDL_SetObjectValid(joystick, SDL_OBJECT_TYPE_JOYSTICK, false); @@ -1166,9 +1361,35 @@ SDL_Joystick *SDL_OpenJoystick(SDL_JoystickID instance_id) // If this joystick is known to have all zero centered axes, skip the auto-centering code if (SDL_JoystickAxesCenteredAtZero(joystick)) { - int i; + for (int i = 0; i < joystick->naxes; ++i) { + joystick->axes[i].has_initial_value = true; + } + } - for (i = 0; i < joystick->naxes; ++i) { + // We know the initial values for HIDAPI and XInput joysticks + if ((SDL_IsJoystickHIDAPI(joystick->guid) || + SDL_IsJoystickXInput(joystick->guid) || + SDL_IsJoystickRAWINPUT(joystick->guid) || + SDL_IsJoystickWGI(joystick->guid)) && + joystick->naxes >= SDL_GAMEPAD_AXIS_COUNT) { + int left_trigger, right_trigger; + if (SDL_IsJoystickXInput(joystick->guid)) { + left_trigger = 2; + right_trigger = 5; + } else { + left_trigger = SDL_GAMEPAD_AXIS_LEFT_TRIGGER; + right_trigger = SDL_GAMEPAD_AXIS_RIGHT_TRIGGER; + } + for (int i = 0; i < SDL_GAMEPAD_AXIS_COUNT; ++i) { + int initial_value; + if (i == left_trigger || i == right_trigger) { + initial_value = SDL_MIN_SINT16; + } else { + initial_value = 0; + } + joystick->axes[i].value = initial_value; + joystick->axes[i].zero = initial_value; + joystick->axes[i].initial_value = initial_value; joystick->axes[i].has_initial_value = true; } } @@ -1814,6 +2035,14 @@ bool SDL_RumbleJoystickTriggers(SDL_Joystick *joystick, Uint16 left_rumble, Uint result = true; } else { result = joystick->driver->RumbleTriggers(joystick, left_rumble, right_rumble); + if (result) { + joystick->trigger_rumble_resend = SDL_GetTicks() + SDL_RUMBLE_RESEND_MS; + if (joystick->trigger_rumble_resend == 0) { + joystick->trigger_rumble_resend = 1; + } + } else { + joystick->trigger_rumble_resend = 0; + } } if (result) { @@ -1824,6 +2053,7 @@ bool SDL_RumbleJoystickTriggers(SDL_Joystick *joystick, Uint16 left_rumble, Uint joystick->trigger_rumble_expiration = SDL_GetTicks() + SDL_min(duration_ms, SDL_MAX_RUMBLE_DURATION_MS); } else { joystick->trigger_rumble_expiration = 0; + joystick->trigger_rumble_resend = 0; } } } @@ -1987,6 +2217,7 @@ void SDL_QuitJoysticks(void) SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, SDL_JoystickAllowBackgroundEventsChanged, NULL); + SDL_FreeVIDPIDList(&old_xboxone_controllers); SDL_FreeVIDPIDList(&arcadestick_devices); SDL_FreeVIDPIDList(&blacklist_devices); SDL_FreeVIDPIDList(&flightstick_devices); @@ -2478,6 +2709,15 @@ void SDL_UpdateJoysticks(void) if (joystick->trigger_rumble_expiration && now >= joystick->trigger_rumble_expiration) { SDL_RumbleJoystickTriggers(joystick, 0, 0, 0); + joystick->trigger_rumble_resend = 0; + } + + if (joystick->trigger_rumble_resend && now >= joystick->trigger_rumble_resend) { + joystick->driver->RumbleTriggers(joystick, joystick->left_trigger_rumble, joystick->right_trigger_rumble); + joystick->trigger_rumble_resend = now + SDL_RUMBLE_RESEND_MS; + if (joystick->trigger_rumble_resend == 0) { + joystick->trigger_rumble_resend = 1; + } } } @@ -2729,8 +2969,7 @@ SDL_GamepadType SDL_GetGamepadTypeFromVIDPID(Uint16 vendor, Uint16 product, cons type = SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_PAIR; } else if (forUI && SDL_IsJoystickGameCube(vendor, product)) { - // We don't have a type for the Nintendo GameCube controller - type = SDL_GAMEPAD_TYPE_STANDARD; + type = SDL_GAMEPAD_TYPE_GAMECUBE; } else { switch (GuessControllerType(vendor, product)) { @@ -2832,77 +3071,12 @@ bool SDL_IsJoystickXboxOneElite(Uint16 vendor_id, Uint16 product_id) bool SDL_IsJoystickXboxSeriesX(Uint16 vendor_id, Uint16 product_id) { - if (vendor_id == USB_VENDOR_MICROSOFT) { - if (product_id == USB_PRODUCT_XBOX_SERIES_X || - product_id == USB_PRODUCT_XBOX_SERIES_X_BLE) { - return true; - } + // Most new controllers have the share button, so we'll default to true and + // have a list of older XBox One controllers that are known not to have it. + if (SDL_VIDPIDInList(vendor_id, product_id, &old_xboxone_controllers)) { + return false; } - if (vendor_id == USB_VENDOR_PDP) { - if (product_id == USB_PRODUCT_XBOX_SERIES_X_VICTRIX_GAMBIT || - product_id == USB_PRODUCT_XBOX_SERIES_X_PDP_BLUE || - product_id == USB_PRODUCT_XBOX_SERIES_X_PDP_AFTERGLOW) { - return true; - } - } - if (vendor_id == USB_VENDOR_POWERA_ALT) { - if ((product_id >= 0x2001 && product_id <= 0x201a) || - product_id == USB_PRODUCT_XBOX_SERIES_X_POWERA_FUSION_PRO2 || - product_id == USB_PRODUCT_XBOX_SERIES_X_POWERA_FUSION_PRO4 || - product_id == USB_PRODUCT_XBOX_SERIES_X_POWERA_FUSION_PRO_WIRELESS_USB || - product_id == USB_PRODUCT_XBOX_SERIES_X_POWERA_FUSION_PRO_WIRELESS_DONGLE || - product_id == USB_PRODUCT_XBOX_SERIES_X_POWERA_MOGA_XP_ULTRA || - product_id == USB_PRODUCT_XBOX_SERIES_X_POWERA_SPECTRA) { - return true; - } - } - if (vendor_id == USB_VENDOR_HORI) { - if (product_id == USB_PRODUCT_HORI_FIGHTING_COMMANDER_OCTA_SERIES_X || - product_id == USB_PRODUCT_HORI_HORIPAD_PRO_SERIES_X) { - return true; - } - } - if (vendor_id == USB_VENDOR_HP) { - if (product_id == USB_PRODUCT_XBOX_SERIES_X_HP_HYPERX || - product_id == USB_PRODUCT_XBOX_SERIES_X_HP_HYPERX_RGB) { - return true; - } - } - if (vendor_id == USB_VENDOR_RAZER) { - if (product_id == USB_PRODUCT_RAZER_WOLVERINE_V2 || - product_id == USB_PRODUCT_RAZER_WOLVERINE_V2_CHROMA || - product_id == USB_PRODUCT_RAZER_WOLVERINE_V3_PRO) { - return true; - } - } - if (vendor_id == USB_VENDOR_THRUSTMASTER) { - if (product_id == USB_PRODUCT_THRUSTMASTER_ESWAPX_PRO_SERIES_X) { - return true; - } - } - if (vendor_id == USB_VENDOR_TURTLE_BEACH) { - if (product_id == USB_PRODUCT_TURTLE_BEACH_SERIES_X_REACT_R || - product_id == USB_PRODUCT_TURTLE_BEACH_SERIES_X_RECON) { - return true; - } - } - if (vendor_id == USB_VENDOR_8BITDO) { - if (product_id == USB_PRODUCT_8BITDO_XBOX_CONTROLLER1 || - product_id == USB_PRODUCT_8BITDO_XBOX_CONTROLLER2) { - return true; - } - } - if (vendor_id == USB_VENDOR_GAMESIR) { - if (product_id == USB_PRODUCT_GAMESIR_G7) { - return true; - } - } - if (vendor_id == USB_VENDOR_ASUS) { - if (product_id == USB_PRODUCT_ROG_RAIKIRI) { - return true; - } - } - return false; + return true; } bool SDL_IsJoystickBluetoothXboxOne(Uint16 vendor_id, Uint16 product_id) @@ -3027,6 +3201,11 @@ bool SDL_IsJoystickHoriSteamController(Uint16 vendor_id, Uint16 product_id) return vendor_id == USB_VENDOR_HORI && (product_id == USB_PRODUCT_HORI_STEAM_CONTROLLER || product_id == USB_PRODUCT_HORI_STEAM_CONTROLLER_BT); } +bool SDL_IsJoystickFlydigiController(Uint16 vendor_id, Uint16 product_id) +{ + return vendor_id == USB_VENDOR_FLYDIGI && product_id == USB_PRODUCT_FLYDIGI_GAMEPAD; +} + bool SDL_IsJoystickSteamDeck(Uint16 vendor_id, Uint16 product_id) { EControllerType eType = GuessControllerType(vendor_id, product_id); @@ -3063,7 +3242,7 @@ bool SDL_IsJoystickVIRTUAL(SDL_GUID guid) return (guid.data[14] == 'v') ? true : false; } -static bool SDL_IsJoystickWheel(Uint16 vendor_id, Uint16 product_id) +bool SDL_IsJoystickWheel(Uint16 vendor_id, Uint16 product_id) { return SDL_VIDPIDInList(vendor_id, product_id, &wheel_devices); } diff --git a/libs/SDL3/src/joystick/SDL_joystick_c.h b/libs/SDL3/src/joystick/SDL_joystick_c.h index d931cf7e..cbc33608 100644 --- a/libs/SDL3/src/joystick/SDL_joystick_c.h +++ b/libs/SDL3/src/joystick/SDL_joystick_c.h @@ -135,6 +135,9 @@ extern bool SDL_IsJoystickSteamController(Uint16 vendor_id, Uint16 product_id); // Function to return whether a joystick is a HORI Steam controller extern bool SDL_IsJoystickHoriSteamController(Uint16 vendor_id, Uint16 product_id); +// Function to return whether a joystick is a Flydigi controller +extern bool SDL_IsJoystickFlydigiController(Uint16 vendor_id, Uint16 product_id); + // Function to return whether a joystick is a Steam Deck extern bool SDL_IsJoystickSteamDeck(Uint16 vendor_id, Uint16 product_id); @@ -156,6 +159,9 @@ extern bool SDL_IsJoystickRAWINPUT(SDL_GUID guid); // Function to return whether a joystick guid comes from the Virtual driver extern bool SDL_IsJoystickVIRTUAL(SDL_GUID guid); +// Function to return whether a joystick is a wheel +bool SDL_IsJoystickWheel(Uint16 vendor_id, Uint16 product_id); + // Function to return whether a joystick should be ignored extern bool SDL_ShouldIgnoreJoystick(Uint16 vendor_id, Uint16 product_id, Uint16 version, const char *name); diff --git a/libs/SDL3/src/joystick/SDL_sysjoystick.h b/libs/SDL3/src/joystick/SDL_sysjoystick.h index f8f2d1af..041ebc3b 100644 --- a/libs/SDL3/src/joystick/SDL_sysjoystick.h +++ b/libs/SDL3/src/joystick/SDL_sysjoystick.h @@ -113,6 +113,7 @@ struct SDL_Joystick Uint16 left_trigger_rumble _guarded; Uint16 right_trigger_rumble _guarded; Uint64 trigger_rumble_expiration _guarded; + Uint64 trigger_rumble_resend _guarded; Uint8 led_red _guarded; Uint8 led_green _guarded; diff --git a/libs/SDL3/src/joystick/apple/SDL_mfijoystick.m b/libs/SDL3/src/joystick/apple/SDL_mfijoystick.m index 811a9f1a..001ef314 100644 --- a/libs/SDL3/src/joystick/apple/SDL_mfijoystick.m +++ b/libs/SDL3/src/joystick/apple/SDL_mfijoystick.m @@ -346,7 +346,9 @@ static bool IOS_AddMFIJoystickDevice(SDL_JoystickDeviceItem *device, GCControlle (device->is_switch_joycon_pair && HIDAPI_IsDevicePresent(USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_SWITCH_JOYCON_PAIR, 0, "")) || (device->is_stadia && HIDAPI_IsDevicePresent(USB_VENDOR_GOOGLE, USB_PRODUCT_GOOGLE_STADIA_CONTROLLER, 0, "")) || (device->is_switch_joyconL && HIDAPI_IsDevicePresent(USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_SWITCH_JOYCON_LEFT, 0, "")) || - (device->is_switch_joyconR && HIDAPI_IsDevicePresent(USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_SWITCH_JOYCON_RIGHT, 0, ""))) { + (device->is_switch_joyconR && HIDAPI_IsDevicePresent(USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_SWITCH_JOYCON_RIGHT, 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, "")))) { // The HIDAPI driver is taking care of this device return false; } diff --git a/libs/SDL3/src/joystick/controller_list.h b/libs/SDL3/src/joystick/controller_list.h index 5a62ece1..63107389 100644 --- a/libs/SDL3/src/joystick/controller_list.h +++ b/libs/SDL3/src/joystick/controller_list.h @@ -96,6 +96,7 @@ static const ControllerDescription_t arrControllers[] = { { MAKE_CONTROLLER_ID( 0x0c12, 0x0ef6 ), k_eControllerType_PS4Controller, NULL }, // Hitbox Arcade Stick { MAKE_CONTROLLER_ID( 0x0c12, 0x1cf6 ), k_eControllerType_PS4Controller, NULL }, // EMIO PS4 Elite Controller { MAKE_CONTROLLER_ID( 0x0c12, 0x1e10 ), k_eControllerType_PS4Controller, NULL }, // P4 Wired Gamepad generic knock off - lightbar but not trackpad or gyro + { MAKE_CONTROLLER_ID( 0x0c12, 0x2e18 ), k_eControllerType_PS4Controller, NULL }, // ZEROPLUS P4 Wired Gamepad { MAKE_CONTROLLER_ID( 0x0e6f, 0x0203 ), k_eControllerType_PS4Controller, NULL }, // Victrix Pro FS (PS4 peripheral but no trackpad/lightbar) { MAKE_CONTROLLER_ID( 0x0e6f, 0x0207 ), k_eControllerType_PS4Controller, NULL }, // Victrix Pro FS V2 w/ Touchpad for PS4 { MAKE_CONTROLLER_ID( 0x0e6f, 0x020a ), k_eControllerType_PS4Controller, NULL }, // Victrix Pro FS PS4/PS5 (PS4 mode) @@ -481,10 +482,8 @@ static const ControllerDescription_t arrControllers[] = { { MAKE_CONTROLLER_ID( 0xf0d, 0xed ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller { MAKE_CONTROLLER_ID( 0xf0d, 0xc0 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller { MAKE_CONTROLLER_ID( 0xe6f, 0x152 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller - { MAKE_CONTROLLER_ID( 0xe6f, 0x2a7 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller { MAKE_CONTROLLER_ID( 0x46d, 0x1007 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller { MAKE_CONTROLLER_ID( 0xe6f, 0x2b8 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller - { MAKE_CONTROLLER_ID( 0xe6f, 0x2a8 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller { MAKE_CONTROLLER_ID( 0x79, 0x18a1 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller // Added from Minidumps 10-9-19 @@ -525,14 +524,7 @@ static const ControllerDescription_t arrControllers[] = { { MAKE_CONTROLLER_ID( 0xd62, 0x9a1b ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller { MAKE_CONTROLLER_ID( 0xe00, 0xe00 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller { MAKE_CONTROLLER_ID( 0xe6f, 0x12a ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller - { MAKE_CONTROLLER_ID( 0xe6f, 0x2a1 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller - { MAKE_CONTROLLER_ID( 0xe6f, 0x2a2 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller - { MAKE_CONTROLLER_ID( 0xe6f, 0x2a5 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller { MAKE_CONTROLLER_ID( 0xe6f, 0x2b2 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller - { MAKE_CONTROLLER_ID( 0xe6f, 0x2bd ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller - { MAKE_CONTROLLER_ID( 0xe6f, 0x2bf ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller - { MAKE_CONTROLLER_ID( 0xe6f, 0x2c0 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller - { MAKE_CONTROLLER_ID( 0xe6f, 0x2c6 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller { MAKE_CONTROLLER_ID( 0xf0d, 0x97 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller { MAKE_CONTROLLER_ID( 0xf0d, 0xba ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller { MAKE_CONTROLLER_ID( 0xf0d, 0xd8 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller diff --git a/libs/SDL3/src/joystick/darwin/SDL_iokitjoystick.c b/libs/SDL3/src/joystick/darwin/SDL_iokitjoystick.c index 9327276a..e87ab824 100644 --- a/libs/SDL3/src/joystick/darwin/SDL_iokitjoystick.c +++ b/libs/SDL3/src/joystick/darwin/SDL_iokitjoystick.c @@ -27,6 +27,7 @@ #include "SDL_iokitjoystick_c.h" #include "../hidapi/SDL_hidapijoystick_c.h" #include "../../haptic/darwin/SDL_syshaptic_c.h" // For haptic hot plugging +#include "../usb_ids.h" #define SDL_JOYSTICK_RUNLOOP_MODE CFSTR("SDLJoystick") @@ -213,6 +214,29 @@ static bool GetHIDScaledCalibratedState(recDevice *pDevice, recElement *pElement return result; } +static bool GetHIDScaledCalibratedState_NACON_Revolution_X_Unlimited(recDevice *pDevice, recElement *pElement, SInt32 min, SInt32 max, SInt32 *pValue) +{ + if (pElement->minReport == 0 && pElement->maxReport == 255) { + return GetHIDScaledCalibratedState(pDevice, pElement, min, max, pValue); + } + + // This device thumbstick axes have an unusual axis range that + // doesn't work with GetHIDScaledCalibratedState() above. + // + // See https://github.com/libsdl-org/SDL/issues/13143 for details + if (GetHIDElementState(pDevice, pElement, pValue)) { + if (*pValue >= 0) { + // Negative axis values range from 32767 (at rest) to 0 (minimum) + *pValue = -32767 + *pValue; + } else if (*pValue < 0) { + // Positive axis values range from -32768 (at rest) to 0 (maximum) + *pValue = 32768 + *pValue; + } + return true; + } + return false; +} + static void JoystickDeviceWasRemovedCallback(void *ctx, IOReturn result, void *sender) { recDevice *device = (recDevice *)ctx; @@ -506,6 +530,11 @@ static bool GetDeviceInfo(IOHIDDeviceRef hidDevice, recDevice *pDevice) pDevice->guid = SDL_CreateJoystickGUID(SDL_HARDWARE_BUS_USB, (Uint16)vendor, (Uint16)product, (Uint16)version, manufacturer_string, product_string, 0, 0); pDevice->steam_virtual_gamepad_slot = GetSteamVirtualGamepadSlot((Uint16)vendor, (Uint16)product, product_string); + if (vendor == USB_VENDOR_NACON_ALT && + product == USB_PRODUCT_NACON_REVOLUTION_X_UNLIMITED_BT) { + pDevice->nacon_revolution_x_unlimited = true; + } + array = IOHIDDeviceCopyMatchingElements(hidDevice, NULL, kIOHIDOptionsTypeNone); if (array) { AddHIDElements(array, pDevice); @@ -957,7 +986,11 @@ static void DARWIN_JoystickUpdate(SDL_Joystick *joystick) i = 0; while (element) { - goodRead = GetHIDScaledCalibratedState(device, element, -32768, 32767, &value); + if (device->nacon_revolution_x_unlimited) { + goodRead = GetHIDScaledCalibratedState_NACON_Revolution_X_Unlimited(device, element, -32768, 32767, &value); + } else { + goodRead = GetHIDScaledCalibratedState(device, element, -32768, 32767, &value); + } if (goodRead) { SDL_SendJoystickAxis(timestamp, joystick, i, value); } diff --git a/libs/SDL3/src/joystick/darwin/SDL_iokitjoystick_c.h b/libs/SDL3/src/joystick/darwin/SDL_iokitjoystick_c.h index 91deb240..3a70d2bd 100644 --- a/libs/SDL3/src/joystick/darwin/SDL_iokitjoystick_c.h +++ b/libs/SDL3/src/joystick/darwin/SDL_iokitjoystick_c.h @@ -72,6 +72,7 @@ struct joystick_hwdata int instance_id; SDL_GUID guid; int steam_virtual_gamepad_slot; + bool nacon_revolution_x_unlimited; struct joystick_hwdata *pNext; // next device }; diff --git a/libs/SDL3/src/joystick/gdk/SDL_gameinputjoystick.c b/libs/SDL3/src/joystick/gdk/SDL_gameinputjoystick.cpp similarity index 88% rename from libs/SDL3/src/joystick/gdk/SDL_gameinputjoystick.c rename to libs/SDL3/src/joystick/gdk/SDL_gameinputjoystick.cpp index 6cf0a902..67cf51fa 100644 --- a/libs/SDL3/src/joystick/gdk/SDL_gameinputjoystick.c +++ b/libs/SDL3/src/joystick/gdk/SDL_gameinputjoystick.cpp @@ -24,6 +24,7 @@ #include "../SDL_sysjoystick.h" #include "../usb_ids.h" +#include "../../core/windows/SDL_windows.h" #include "../../core/windows/SDL_gameinput.h" // Default value for SDL_HINT_JOYSTICK_GAMEINPUT @@ -66,7 +67,7 @@ typedef struct joystick_hwdata static GAMEINPUT_InternalList g_GameInputList = { NULL }; static IGameInput *g_pGameInput = NULL; -static GameInputCallbackToken g_GameInputCallbackToken = GAMEINPUT_INVALID_CALLBACK_TOKEN_VALUE; +static GameInputCallbackToken g_GameInputCallbackToken = 0; static Uint64 g_GameInputTimestampOffset; static bool GAMEINPUT_InternalIsGamepad(const GameInputDeviceInfo *info) @@ -93,15 +94,22 @@ static bool GAMEINPUT_InternalAddOrFind(IGameInputDevice *pDevice) SDL_AssertJoysticksLocked(); - info = IGameInputDevice_GetDeviceInfo(pDevice); - if (info->capabilities & GameInputDeviceCapabilityWireless) { +#if GAMEINPUT_API_VERSION >= 1 + HRESULT hr = pDevice->GetDeviceInfo(&info); + if (FAILED(hr)) { + return WIN_SetErrorFromHRESULT("IGameInputDevice::GetDeviceInfo", hr); + } +#else + info = pDevice->GetDeviceInfo(); +#endif + if (false /*info->capabilities & GameInputDeviceCapabilityWireless*/) { bus = SDL_HARDWARE_BUS_BLUETOOTH; } else { bus = SDL_HARDWARE_BUS_USB; } vendor = info->vendorId; product = info->productId; - version = (info->firmwareVersion.major << 8) | info->firmwareVersion.minor; + //version = (info->firmwareVersion.major << 8) | info->firmwareVersion.minor; if (SDL_JoystickHandledByAnotherDriver(&SDL_GAMEINPUT_JoystickDriver, vendor, product, version, "")) { return true; @@ -130,18 +138,20 @@ static bool GAMEINPUT_InternalAddOrFind(IGameInputDevice *pDevice) // Generate a device path for (idx = 0; idx < APP_LOCAL_DEVICE_ID_SIZE; ++idx) { SDL_snprintf(tmp, SDL_arraysize(tmp), "%02hhX", info->deviceId.value[idx]); - SDL_strlcat(elem->path, tmp, SDL_arraysize(tmp)); - } - - if (info->deviceStrings) { - // In theory we could get the manufacturer and product strings here, but they're NULL for all the controllers I've tested + SDL_strlcat(elem->path, tmp, SDL_arraysize(elem->path)); } +#if GAMEINPUT_API_VERSION >= 1 if (info->displayName) { - // This could give us a product string, but it's NULL for all the controllers I've tested + product_string = info->displayName; } +#else + if (info->displayName) { + product_string = info->displayName->data; + } +#endif - IGameInputDevice_AddRef(pDevice); + pDevice->AddRef(); elem->device = pDevice; elem->name = SDL_CreateJoystickName(vendor, product, manufacturer_string, product_string); elem->guid = SDL_CreateJoystickGUID(bus, vendor, product, version, manufacturer_string, product_string, 'g', 0); @@ -168,7 +178,7 @@ static bool GAMEINPUT_InternalRemoveByIndex(int idx) elem = g_GameInputList.devices[idx]; if (elem) { - IGameInputDevice_Release(elem->device); + elem->device->Release(); SDL_free(elem->name); SDL_free(elem); } @@ -199,8 +209,8 @@ static GAMEINPUT_InternalDevice *GAMEINPUT_InternalFindByIndex(int idx) static void CALLBACK GAMEINPUT_InternalJoystickDeviceCallback( _In_ GameInputCallbackToken callbackToken, - _In_ void* context, - _In_ IGameInputDevice* device, + _In_ void *context, + _In_ IGameInputDevice *device, _In_ uint64_t timestamp, _In_ GameInputDeviceStatus currentStatus, _In_ GameInputDeviceStatus previousStatus) @@ -232,10 +242,11 @@ static void CALLBACK GAMEINPUT_InternalJoystickDeviceCallback( } static void GAMEINPUT_JoystickDetect(void); +static void GAMEINPUT_JoystickQuit(void); static bool GAMEINPUT_JoystickInit(void) { - HRESULT hR; + HRESULT hr; if (!SDL_GetHintBoolean(SDL_HINT_JOYSTICK_GAMEINPUT, SDL_GAMEINPUT_DEFAULT)) { return true; @@ -245,21 +256,21 @@ static bool GAMEINPUT_JoystickInit(void) return false; } - hR = IGameInput_RegisterDeviceCallback(g_pGameInput, - NULL, + hr = g_pGameInput->RegisterDeviceCallback(NULL, GameInputKindController, GameInputDeviceConnected, GameInputBlockingEnumeration, NULL, GAMEINPUT_InternalJoystickDeviceCallback, &g_GameInputCallbackToken); - if (FAILED(hR)) { - return SDL_SetError("IGameInput::RegisterDeviceCallback failure with HRESULT of %08lX", hR); + if (FAILED(hr)) { + GAMEINPUT_JoystickQuit(); + return WIN_SetErrorFromHRESULT("IGameInput::RegisterDeviceCallback", hr); } // Calculate the relative offset between SDL timestamps and GameInput timestamps Uint64 now = SDL_GetTicksNS(); - uint64_t timestampUS = IGameInput_GetCurrentTimestamp(g_pGameInput); + uint64_t timestampUS = g_pGameInput->GetCurrentTimestamp(); g_GameInputTimestampOffset = (SDL_NS_TO_US(now) - timestampUS); GAMEINPUT_JoystickDetect(); @@ -292,7 +303,7 @@ static void GAMEINPUT_JoystickDetect(void) elem->isAdded = true; } - if (elem->isDeleteRequested || !(IGameInputDevice_GetDeviceStatus(elem->device) & GameInputDeviceConnected)) { + if (elem->isDeleteRequested || !(elem->device->GetDeviceStatus() & GameInputDeviceConnected)) { SDL_PrivateJoystickRemoved(elem->device_instance); GAMEINPUT_InternalRemoveByIndex(idx--); } @@ -357,6 +368,7 @@ static SDL_JoystickID GAMEINPUT_JoystickGetDeviceInstanceID(int device_index) static void GAMEINPUT_UpdatePowerInfo(SDL_Joystick *joystick, IGameInputDevice *device) { +#if 0 GameInputBatteryState battery_state; SDL_PowerState state; int percent = 0; @@ -385,10 +397,10 @@ static void GAMEINPUT_UpdatePowerInfo(SDL_Joystick *joystick, IGameInputDevice * percent = (int)SDL_roundf((battery_state.remainingCapacity / battery_state.fullChargeCapacity) * 100.0f); } SDL_SendJoystickPowerInfo(joystick, state, percent); +#endif } -#ifdef IGameInput_RegisterSystemButtonCallback - +#if GAMEINPUT_API_VERSION >= 1 static void CALLBACK GAMEINPUT_InternalSystemButtonCallback( _In_ GameInputCallbackToken callbackToken, _In_ void * context, @@ -415,8 +427,7 @@ static void CALLBACK GAMEINPUT_InternalSystemButtonCallback( SDL_UnlockJoysticks(); } } - -#endif // IGameInput_RegisterSystemButtonCallback +#endif // GAMEINPUT_API_VERSION >= 1 static bool GAMEINPUT_JoystickOpen(SDL_Joystick *joystick, int device_index) { @@ -441,19 +452,15 @@ static bool GAMEINPUT_JoystickOpen(SDL_Joystick *joystick, int device_index) joystick->nbuttons = 11; joystick->nhats = 1; -#ifdef IGameInput_RegisterSystemButtonCallback +#if GAMEINPUT_API_VERSION >= 1 if (info->supportedSystemButtons != GameInputSystemButtonNone) { if (info->supportedSystemButtons & GameInputSystemButtonShare) { ++joystick->nbuttons; } -#if 1 // The C macro in GameInput.h version 10.0.26100 refers to a focus policy which I guess has been removed from the final API? -#undef IGameInput_RegisterSystemButtonCallback -#define IGameInput_RegisterSystemButtonCallback(This, device, buttonFilter, context, callbackFunc, callbackToken) ((This)->lpVtbl->RegisterSystemButtonCallback(This, device, buttonFilter, context, callbackFunc, callbackToken)) -#endif - IGameInput_RegisterSystemButtonCallback(g_pGameInput, elem->device, (GameInputSystemButtonGuide | GameInputSystemButtonShare), joystick, GAMEINPUT_InternalSystemButtonCallback, &hwdata->system_button_callback_token); + g_pGameInput->RegisterSystemButtonCallback(elem->device, (GameInputSystemButtonGuide | GameInputSystemButtonShare), joystick, GAMEINPUT_InternalSystemButtonCallback, &hwdata->system_button_callback_token); } -#endif // IGameInput_RegisterSystemButtonCallback +#endif // GAMEINPUT_API_VERSION >= 1 } else { joystick->naxes = info->controllerAxisCount; joystick->nbuttons = info->controllerButtonCount; @@ -467,6 +474,7 @@ static bool GAMEINPUT_JoystickOpen(SDL_Joystick *joystick, int device_index) SDL_SetBooleanProperty(SDL_GetJoystickProperties(joystick), SDL_PROP_JOYSTICK_CAP_TRIGGER_RUMBLE_BOOLEAN, true); } +#if 0 if (info->supportedInput & GameInputKindTouch) { SDL_PrivateJoystickAddTouchpad(joystick, info->touchPointCount); } @@ -482,6 +490,7 @@ static bool GAMEINPUT_JoystickOpen(SDL_Joystick *joystick, int device_index) } else { joystick->connection_state = SDL_JOYSTICK_CONNECTION_WIRED; } +#endif return true; } @@ -492,7 +501,7 @@ static bool GAMEINPUT_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequenc GameInputRumbleParams *params = &hwdata->rumbleParams; params->lowFrequency = (float)low_frequency_rumble / (float)SDL_MAX_UINT16; params->highFrequency = (float)high_frequency_rumble / (float)SDL_MAX_UINT16; - IGameInputDevice_SetRumbleState(hwdata->devref->device, params); + hwdata->devref->device->SetRumbleState(params); return true; } @@ -503,7 +512,7 @@ static bool GAMEINPUT_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left GameInputRumbleParams *params = &hwdata->rumbleParams; params->leftTrigger = (float)left_rumble / (float)SDL_MAX_UINT16; params->rightTrigger = (float)right_rumble / (float)SDL_MAX_UINT16; - IGameInputDevice_SetRumbleState(hwdata->devref->device, params); + hwdata->devref->device->SetRumbleState(params); return true; } @@ -531,15 +540,15 @@ static void GAMEINPUT_JoystickUpdate(SDL_Joystick *joystick) IGameInputReading *reading = NULL; Uint64 timestamp; GameInputGamepadState state; - HRESULT hR; + HRESULT hr; - hR = IGameInput_GetCurrentReading(g_pGameInput, info->supportedInput, device, &reading); - if (FAILED(hR)) { + hr = g_pGameInput->GetCurrentReading(info->supportedInput, device, &reading); + if (FAILED(hr)) { // don't SetError here since there can be a legitimate case when there's no reading avail return; } - timestamp = SDL_US_TO_NS(IGameInputReading_GetTimestamp(reading) + g_GameInputTimestampOffset); + timestamp = SDL_US_TO_NS(reading->GetTimestamp() + g_GameInputTimestampOffset); if (GAMEINPUT_InternalIsGamepad(info)) { static WORD s_XInputButtons[] = { @@ -557,7 +566,7 @@ static void GAMEINPUT_JoystickUpdate(SDL_Joystick *joystick) }; Uint8 btnidx = 0, hat = 0; - if (IGameInputReading_GetGamepadState(reading, &state)) { + if (reading->GetGamepadState(&state)) { for (btnidx = 0; btnidx < SDL_arraysize(s_XInputButtons); ++btnidx) { WORD button_mask = s_XInputButtons[btnidx]; if (!button_mask) { @@ -599,7 +608,7 @@ static void GAMEINPUT_JoystickUpdate(SDL_Joystick *joystick) if (button_state) { uint32_t i; - uint32_t button_count = IGameInputReading_GetControllerButtonState(reading, info->controllerButtonCount, button_state); + uint32_t button_count = reading->GetControllerButtonState(info->controllerButtonCount, button_state); for (i = 0; i < button_count; ++i) { SDL_SendJoystickButton(timestamp, joystick, (Uint8)i, button_state[i]); } @@ -609,7 +618,7 @@ static void GAMEINPUT_JoystickUpdate(SDL_Joystick *joystick) #define CONVERT_AXIS(v) (Sint16)((v)*65535.0f - 32768.0f) if (axis_state) { uint32_t i; - uint32_t axis_count = IGameInputReading_GetControllerAxisState(reading, info->controllerAxisCount, axis_state); + uint32_t axis_count = reading->GetControllerAxisState(info->controllerAxisCount, axis_state); for (i = 0; i < axis_count; ++i) { SDL_SendJoystickAxis(timestamp, joystick, (Uint8)i, CONVERT_AXIS(axis_state[i])); } @@ -619,7 +628,7 @@ static void GAMEINPUT_JoystickUpdate(SDL_Joystick *joystick) if (switch_state) { uint32_t i; - uint32_t switch_count = IGameInputReading_GetControllerSwitchState(reading, info->controllerSwitchCount, switch_state); + uint32_t switch_count = reading->GetControllerSwitchState(info->controllerSwitchCount, switch_state); for (i = 0; i < switch_count; ++i) { Uint8 hat; switch (switch_state[i]) { @@ -658,6 +667,7 @@ static void GAMEINPUT_JoystickUpdate(SDL_Joystick *joystick) } } +#if 0 if (info->supportedInput & GameInputKindTouch) { GameInputTouchState *touch_state = SDL_stack_alloc(GameInputTouchState, info->touchPointCount); if (touch_state) { @@ -679,19 +689,24 @@ static void GAMEINPUT_JoystickUpdate(SDL_Joystick *joystick) // FIXME: How do we interpret the motion data? } } +#endif - IGameInputReading_Release(reading); + reading->Release(); // FIXME: We can poll this at a much lower rate GAMEINPUT_UpdatePowerInfo(joystick, device); } -static void GAMEINPUT_JoystickClose(SDL_Joystick* joystick) +static void GAMEINPUT_JoystickClose(SDL_Joystick *joystick) { GAMEINPUT_InternalJoystickHwdata *hwdata = joystick->hwdata; if (hwdata->system_button_callback_token) { - IGameInput_UnregisterCallback(g_pGameInput, hwdata->system_button_callback_token, 5000); +#if GAMEINPUT_API_VERSION >= 1 + g_pGameInput->UnregisterCallback(hwdata->system_button_callback_token); +#else + g_pGameInput->UnregisterCallback(hwdata->system_button_callback_token, 10000); +#endif } SDL_free(hwdata); @@ -702,8 +717,14 @@ static void GAMEINPUT_JoystickQuit(void) { if (g_pGameInput) { // free the callback - IGameInput_UnregisterCallback(g_pGameInput, g_GameInputCallbackToken, /*timeoutInUs:*/ 10000); - g_GameInputCallbackToken = GAMEINPUT_INVALID_CALLBACK_TOKEN_VALUE; + if (g_GameInputCallbackToken) { +#if GAMEINPUT_API_VERSION >= 1 + g_pGameInput->UnregisterCallback(g_GameInputCallbackToken); +#else + g_pGameInput->UnregisterCallback(g_GameInputCallbackToken, 10000); +#endif + g_GameInputCallbackToken = 0; + } // free the list while (g_GameInputList.count > 0) { @@ -738,7 +759,7 @@ static bool GAMEINPUT_JoystickGetGamepadMapping(int device_index, SDL_GamepadMap out->back.kind = EMappingKind_Button; out->back.target = SDL_GAMEPAD_BUTTON_BACK; -#ifdef IGameInput_RegisterSystemButtonCallback +#if GAMEINPUT_API_VERSION >= 1 if (elem->info->supportedSystemButtons & GameInputSystemButtonGuide) { out->guide.kind = EMappingKind_Button; out->guide.target = SDL_GAMEPAD_BUTTON_GUIDE; @@ -748,7 +769,7 @@ static bool GAMEINPUT_JoystickGetGamepadMapping(int device_index, SDL_GamepadMap out->misc1.kind = EMappingKind_Button; out->misc1.target = SDL_GAMEPAD_BUTTON_GAMEINPUT_SHARE; } -#endif +#endif // GAMEINPUT_API_VERSION >= 1 out->start.kind = EMappingKind_Button; out->start.target = SDL_GAMEPAD_BUTTON_START; diff --git a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_8bitdo.c b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_8bitdo.c new file mode 100644 index 00000000..10f19fc2 --- /dev/null +++ b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_8bitdo.c @@ -0,0 +1,647 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2024 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +#ifdef SDL_JOYSTICK_HIDAPI + +#include "../SDL_sysjoystick.h" +#include "SDL_hidapijoystick_c.h" +#include "SDL_hidapi_rumble.h" + +#ifdef SDL_JOYSTICK_HIDAPI_8BITDO + +// Define this if you want to log all packets from the controller +#if 0 +#define DEBUG_8BITDO_PROTOCOL +#endif + +enum +{ + SDL_GAMEPAD_BUTTON_8BITDO_L4 = 11, + SDL_GAMEPAD_BUTTON_8BITDO_R4, + SDL_GAMEPAD_BUTTON_8BITDO_PL, + SDL_GAMEPAD_BUTTON_8BITDO_PR, + SDL_GAMEPAD_NUM_8BITDO_BUTTONS, +}; + +#define SDL_8BITDO_FEATURE_REPORTID_ENABLE_SDL_REPORTID 0x06 +#define SDL_8BITDO_REPORTID_SDL_REPORTID 0x04 +#define SDL_8BITDO_REPORTID_NOT_SUPPORTED_SDL_REPORTID 0x03 +#define SDL_8BITDO_BT_REPORTID_SDL_REPORTID 0x01 + +#define ABITDO_ACCEL_SCALE 4096.f +#define ABITDO_GYRO_MAX_DEGREES_PER_SECOND 2000.f + +typedef struct +{ + bool sensors_supported; + bool sensors_enabled; + bool touchpad_01_supported; + bool touchpad_02_supported; + bool rumble_supported; + bool rumble_type; + bool rgb_supported; + bool player_led_supported; + bool powerstate_supported; + Uint8 serial[6]; + Uint16 version; + Uint16 version_beta; + float accelScale; + float gyroScale; + 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_interval; +} SDL_Driver8BitDo_Context; + +#pragma pack(push,1) +typedef struct +{ + bool sensors_supported; + bool touchpad_01_supported; + bool touchpad_02_supported; + bool rumble_supported; + bool rumble_type; + bool rgb_supported; + Uint8 device_type; + Uint8 serial[6]; + Uint16 version; + Uint16 version_beta; + Uint16 pid; +} ABITDO_DEVICE_INFO; + +typedef struct +{ + // Accelerometer values + short sAccelX; + short sAccelY; + short sAccelZ; + + // Gyroscope values + short sGyroX; + short sGyroY; + short sGyroZ; +} ABITDO_SENSORS; + +#pragma pack(pop) + + +static void HIDAPI_Driver8BitDo_RegisterHints(SDL_HintCallback callback, void *userdata) +{ + SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_8BITDO, callback, userdata); +} + +static void HIDAPI_Driver8BitDo_UnregisterHints(SDL_HintCallback callback, void *userdata) +{ + SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_8BITDO, callback, userdata); +} + +static bool HIDAPI_Driver8BitDo_IsEnabled(void) +{ + return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_8BITDO, SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT)); +} + +static int ReadFeatureReport(SDL_hid_device *dev, Uint8 report_id, Uint8 *report, size_t length) +{ + SDL_memset(report, 0, length); + report[0] = report_id; + return SDL_hid_get_feature_report(dev, report, length); +} + +static bool HIDAPI_Driver8BitDo_IsSupportedDevice(SDL_HIDAPI_Device *device, const char *name, SDL_GamepadType type, Uint16 vendor_id, Uint16 product_id, Uint16 version, int interface_number, int interface_class, int interface_subclass, int interface_protocol) +{ + if (vendor_id == USB_VENDOR_8BITDO) { + switch (product_id) { + case USB_PRODUCT_8BITDO_SF30_PRO: + case USB_PRODUCT_8BITDO_SF30_PRO_BT: + case USB_PRODUCT_8BITDO_SN30_PRO: + case USB_PRODUCT_8BITDO_SN30_PRO_BT: + case USB_PRODUCT_8BITDO_PRO_2: + case USB_PRODUCT_8BITDO_PRO_2_BT: + case USB_PRODUCT_8BITDO_ULTIMATE2_WIRELESS: + return true; + default: + break; + } + } + return false; +} + +static bool HIDAPI_Driver8BitDo_InitDevice(SDL_HIDAPI_Device *device) +{ + SDL_Driver8BitDo_Context *ctx = (SDL_Driver8BitDo_Context *)SDL_calloc(1, sizeof(*ctx)); + if (!ctx) { + return false; + } + device->context = ctx; + + if (device->product_id == USB_PRODUCT_8BITDO_ULTIMATE2_WIRELESS) { + // The Ultimate 2 Wireless v1.02 firmware has 12 byte reports, v1.03 firmware has 34 byte reports + const int ULTIMATE2_WIRELESS_V103_REPORT_SIZE = 34; + const int MAX_ATTEMPTS = 3; + + for (int attempt = 0; attempt < MAX_ATTEMPTS; ++attempt) { + Uint8 data[USB_PACKET_LENGTH]; + int size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 80); + if (size == 0) { + // Try again + continue; + } + if (size >= ULTIMATE2_WIRELESS_V103_REPORT_SIZE) { + ctx->sensors_supported = true; + ctx->rumble_supported = true; + ctx->powerstate_supported = true; + } + break; + } + } else { + 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_ENABLE_SDL_REPORTID, data, sizeof(data)); + if (size > 0) { + ctx->sensors_supported = true; + ctx->rumble_supported = true; + ctx->powerstate_supported = true; + + // Set the serial number to the Bluetooth MAC address + if (size >= 12 && data[10] != 0) { + char serial[18]; + (void)SDL_snprintf(serial, sizeof(serial), "%.2x-%.2x-%.2x-%.2x-%.2x-%.2x", + data[10], data[9], data[8], data[7], data[6], data[5]); + HIDAPI_SetDeviceSerial(device, serial); + } + break; + } + + // Try again + SDL_Delay(10); + } + } + + if (device->product_id == USB_PRODUCT_8BITDO_SF30_PRO || device->product_id == USB_PRODUCT_8BITDO_SF30_PRO_BT) { + HIDAPI_SetDeviceName(device, "8BitDo SF30 Pro"); + } else if (device->product_id == USB_PRODUCT_8BITDO_SN30_PRO || device->product_id == USB_PRODUCT_8BITDO_SN30_PRO_BT) { + HIDAPI_SetDeviceName(device, "8BitDo SN30 Pro"); + } else if (device->product_id == USB_PRODUCT_8BITDO_PRO_2 || device->product_id == USB_PRODUCT_8BITDO_PRO_2_BT) { + HIDAPI_SetDeviceName(device, "8BitDo Pro 2"); + } + + return HIDAPI_JoystickConnected(device, NULL); +} + +static int HIDAPI_Driver8BitDo_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id) +{ + return -1; +} + +static void HIDAPI_Driver8BitDo_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index) +{ +} + +static Uint64 HIDAPI_Driver8BitDo_GetIMURateForProductID(SDL_HIDAPI_Device *device) +{ + // TODO: If sensor time stamp is sent, these fixed settings from observation can be replaced + switch (device->product_id) { + // Note, This is estimated by observation of Bluetooth packets received in the testcontroller tool + case USB_PRODUCT_8BITDO_SN30_PRO_BT: + case USB_PRODUCT_8BITDO_SF30_PRO_BT: + return 90; // Observed to be anywhere between 60-90 hz. Possibly lossy in current state + case USB_PRODUCT_8BITDO_SF30_PRO: + case USB_PRODUCT_8BITDO_SN30_PRO: + return 100; + case USB_PRODUCT_8BITDO_PRO_2: + case USB_PRODUCT_8BITDO_PRO_2_BT:// Note, labelled as "BT" but appears this way when wired. Observed bluetooth packet rate seems to be 80-90hz + return (device->is_bluetooth ? 85 : 100); + case USB_PRODUCT_8BITDO_ULTIMATE2_WIRELESS: + default: + return 120; + break; + } +} + +#ifndef DEG2RAD +#define DEG2RAD(x) ((float)(x) * (float)(SDL_PI_F / 180.f)) +#endif + +static bool HIDAPI_Driver8BitDo_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ + SDL_Driver8BitDo_Context *ctx = (SDL_Driver8BitDo_Context *)device->context; + + SDL_AssertJoysticksLocked(); + + SDL_zeroa(ctx->last_state); + + // Initialize the joystick capabilities + if (device->product_id == USB_PRODUCT_8BITDO_PRO_2 || + device->product_id == USB_PRODUCT_8BITDO_PRO_2_BT || + device->product_id == USB_PRODUCT_8BITDO_ULTIMATE2_WIRELESS) { + // This controller has additional buttons + joystick->nbuttons = SDL_GAMEPAD_NUM_8BITDO_BUTTONS; + } else { + joystick->nbuttons = 11; + } + joystick->naxes = SDL_GAMEPAD_AXIS_COUNT; + joystick->nhats = 1; + + if (ctx->sensors_supported) { + + // Different 8Bitdo controllers in different connection modes have different polling rates. + const Uint64 imu_polling_rate = HIDAPI_Driver8BitDo_GetIMURateForProductID(device); + ctx->sensor_timestamp_interval = SDL_NS_PER_SECOND / imu_polling_rate; + + SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, (float)imu_polling_rate); + SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, (float)imu_polling_rate); + + ctx->accelScale = SDL_STANDARD_GRAVITY / ABITDO_ACCEL_SCALE; + // Hardware senses +/- N Degrees per second mapped to +/- INT16_MAX + ctx->gyroScale = DEG2RAD(ABITDO_GYRO_MAX_DEGREES_PER_SECOND) / INT16_MAX; + } + + return true; +} + +static bool HIDAPI_Driver8BitDo_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) +{ + SDL_Driver8BitDo_Context *ctx = (SDL_Driver8BitDo_Context *)device->context; + if (ctx->rumble_supported) { + Uint8 rumble_packet[5] = { 0x05, 0x00, 0x00, 0x00, 0x00 }; + rumble_packet[1] = low_frequency_rumble >> 8; + rumble_packet[2] = high_frequency_rumble >> 8; + + 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 bool HIDAPI_Driver8BitDo_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble) +{ + return SDL_Unsupported(); +} + +static Uint32 HIDAPI_Driver8BitDo_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ + SDL_Driver8BitDo_Context *ctx = (SDL_Driver8BitDo_Context *)device->context; + Uint32 caps = 0; + if (ctx->rumble_supported) { + caps |= SDL_JOYSTICK_CAP_RUMBLE; + } + if (ctx->rgb_supported) { + caps |= SDL_JOYSTICK_CAP_RGB_LED; + } + return caps; +} + +static bool HIDAPI_Driver8BitDo_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue) +{ + return SDL_Unsupported(); +} + +static bool HIDAPI_Driver8BitDo_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size) +{ + return SDL_Unsupported(); +} + +static bool HIDAPI_Driver8BitDo_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled) +{ + SDL_Driver8BitDo_Context *ctx = (SDL_Driver8BitDo_Context *)device->context; + if (ctx->sensors_supported) { + ctx->sensors_enabled = enabled; + return true; + } + return SDL_Unsupported(); +} + +static void HIDAPI_Driver8BitDo_HandleOldStatePacket(SDL_Joystick *joystick, SDL_Driver8BitDo_Context *ctx, Uint8 *data, int size) +{ + Sint16 axis; + Uint64 timestamp = SDL_GetTicksNS(); + + if (ctx->last_state[2] != data[2]) { + Uint8 hat; + + switch (data[2]) { + case 0: + hat = SDL_HAT_UP; + break; + case 1: + hat = SDL_HAT_RIGHTUP; + break; + case 2: + hat = SDL_HAT_RIGHT; + break; + case 3: + hat = SDL_HAT_RIGHTDOWN; + break; + case 4: + hat = SDL_HAT_DOWN; + break; + case 5: + hat = SDL_HAT_LEFTDOWN; + break; + case 6: + hat = SDL_HAT_LEFT; + break; + case 7: + hat = SDL_HAT_LEFTUP; + break; + default: + hat = SDL_HAT_CENTERED; + break; + } + SDL_SendJoystickHat(timestamp, joystick, 0, hat); + } + + if (ctx->last_state[0] != data[0]) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data[0] & 0x01) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data[0] & 0x02) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data[0] & 0x08) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data[0] & 0x10) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data[0] & 0x40) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data[0] & 0x80) != 0)); + } + + if (ctx->last_state[1] != data[1]) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[1] & 0x10) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data[1] & 0x04) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data[1] & 0x08) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data[1] & 0x20) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data[1] & 0x40) != 0)); + + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, (data[1] & 0x01) ? SDL_MAX_SINT16 : SDL_MIN_SINT16); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, (data[1] & 0x02) ? SDL_MAX_SINT16 : SDL_MIN_SINT16); + } + +#define READ_STICK_AXIS(offset) \ + (data[offset] == 0x7f ? 0 : (Sint16)HIDAPI_RemapVal((float)((int)data[offset] - 0x7f), -0x7f, 0xff - 0x7f, SDL_MIN_SINT16, SDL_MAX_SINT16)) + { + axis = READ_STICK_AXIS(3); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis); + axis = READ_STICK_AXIS(4); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis); + axis = READ_STICK_AXIS(5); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis); + axis = READ_STICK_AXIS(6); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis); + } +#undef READ_STICK_AXIS + + SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state))); +} + +static void HIDAPI_Driver8BitDo_HandleStatePacket(SDL_Joystick *joystick, SDL_Driver8BitDo_Context *ctx, Uint8 *data, int size) +{ + Sint16 axis; + Uint64 timestamp = SDL_GetTicksNS(); + + switch (data[0]) { + case SDL_8BITDO_REPORTID_NOT_SUPPORTED_SDL_REPORTID: // Firmware without enhanced mode + case SDL_8BITDO_REPORTID_SDL_REPORTID: // Enhanced mode USB report + case SDL_8BITDO_BT_REPORTID_SDL_REPORTID: // Enhanced mode Bluetooth report + break; + default: + // We don't know how to handle this report + return; + } + + if (ctx->last_state[1] != data[1]) { + Uint8 hat; + + switch (data[1]) { + case 0: + hat = SDL_HAT_UP; + break; + case 1: + hat = SDL_HAT_RIGHTUP; + break; + case 2: + hat = SDL_HAT_RIGHT; + break; + case 3: + hat = SDL_HAT_RIGHTDOWN; + break; + case 4: + hat = SDL_HAT_DOWN; + break; + case 5: + hat = SDL_HAT_LEFTDOWN; + break; + case 6: + hat = SDL_HAT_LEFT; + break; + case 7: + hat = SDL_HAT_LEFTUP; + break; + default: + hat = SDL_HAT_CENTERED; + break; + } + SDL_SendJoystickHat(timestamp, joystick, 0, hat); + } + + + if (ctx->last_state[8] != data[8]) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data[8] & 0x01) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data[8] & 0x02) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data[8] & 0x08) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data[8] & 0x10) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data[8] & 0x40) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data[8] & 0x80) != 0)); + + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_8BITDO_PL, ((data[8] & 0x20) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_8BITDO_PR, ((data[8] & 0x04) != 0)); + } + + if (ctx->last_state[9] != data[9]) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[9] & 0x10) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data[9] & 0x04) != 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_RIGHT_STICK, ((data[9] & 0x40) != 0)); + } + + if (size > 10 && ctx->last_state[10] != data[10]) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_8BITDO_L4, ((data[10] & 0x01) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_8BITDO_R4, ((data[10] & 0x02) != 0)); + } + +#define READ_STICK_AXIS(offset) \ + (data[offset] == 0x7f ? 0 : (Sint16)HIDAPI_RemapVal((float)((int)data[offset] - 0x7f), -0x7f, 0xff - 0x7f, SDL_MIN_SINT16, SDL_MAX_SINT16)) + { + axis = READ_STICK_AXIS(2); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis); + axis = READ_STICK_AXIS(3); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis); + axis = READ_STICK_AXIS(4); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis); + axis = READ_STICK_AXIS(5); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis); + } +#undef READ_STICK_AXIS + +#define READ_TRIGGER_AXIS(offset) \ + (Sint16)(((int)data[offset] * 257) - 32768) + { + axis = READ_TRIGGER_AXIS(7); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis); + axis = READ_TRIGGER_AXIS(6); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis); + } +#undef READ_TRIGGER_AXIS + + if (ctx->powerstate_supported) { + SDL_PowerState state; + int percent; + Uint8 status = data[14] >> 7; + Uint8 level = (data[14] & 0x7f); + if (level == 100) { + status = 2; + } + switch (status) { + case 0: + state = SDL_POWERSTATE_ON_BATTERY; + percent = level; + break; + case 1: + state = SDL_POWERSTATE_CHARGING; + percent = level; + break; + case 2: + state = SDL_POWERSTATE_CHARGED; + percent = 100; + break; + default: + state = SDL_POWERSTATE_UNKNOWN; + percent = 0; + break; + } + SDL_SendJoystickPowerInfo(joystick, state, percent); + } + + if (ctx->sensors_enabled) { + Uint64 sensor_timestamp; + float values[3]; + ABITDO_SENSORS *sensors = (ABITDO_SENSORS *)&data[15]; + + // Note: we cannot use the time stamp of the receiving computer due to packet delay creating "spiky" timings. + // The imu time stamp is intended to be the sample time of the on-board hardware. + // In the absence of time stamp data from the data[], we can simulate that by + // advancing a time stamp by the observed/known imu clock rate. This is 8ms = 125 Hz + sensor_timestamp = ctx->sensor_timestamp; + ctx->sensor_timestamp += ctx->sensor_timestamp_interval; + + // This device's IMU values are reported differently from SDL + // Thus we perform a rotation of the coordinate system to match the SDL standard. + + // By observation of this device: + // Hardware x is reporting roll (rotation about the power jack's axis) + // Hardware y is reporting pitch (rotation about the horizontal axis) + // Hardware z is reporting yaw (rotation about the joysticks' center axis) + values[0] = -sensors->sGyroY * ctx->gyroScale; // Rotation around pitch axis + values[1] = sensors->sGyroZ * ctx->gyroScale; // Rotation around yaw axis + values[2] = -sensors->sGyroX * ctx->gyroScale; // Rotation around roll axis + SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_GYRO, sensor_timestamp, values, 3); + + // By observation of this device: + // Accelerometer X is positive when front of the controller points toward the sky. + // Accelerometer y is positive when left side of the controller points toward the sky. + // Accelerometer Z is positive when sticks point toward the sky. + values[0] = -sensors->sAccelY * ctx->accelScale; // Acceleration along pitch axis + values[1] = sensors->sAccelZ * ctx->accelScale; // Acceleration along yaw axis + values[2] = -sensors->sAccelX * ctx->accelScale; // Acceleration along roll axis + SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, sensor_timestamp, values, 3); + } + + SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state))); +} + +static bool HIDAPI_Driver8BitDo_UpdateDevice(SDL_HIDAPI_Device *device) +{ + SDL_Driver8BitDo_Context *ctx = (SDL_Driver8BitDo_Context *)device->context; + SDL_Joystick *joystick = NULL; + Uint8 data[USB_PACKET_LENGTH]; + int size = 0; + + if (device->num_joysticks > 0) { + joystick = SDL_GetJoystickFromID(device->joysticks[0]); + } else { + return false; + } + + while ((size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 0)) > 0) { +#ifdef DEBUG_8BITDO_PROTOCOL + HIDAPI_DumpPacket("8BitDo packet: size = %d", data, size); +#endif + if (!joystick) { + continue; + } + + if (size == 9) { + // Old firmware USB report for the SF30 Pro and SN30 Pro controllers + HIDAPI_Driver8BitDo_HandleOldStatePacket(joystick, ctx, data, size); + } else { + HIDAPI_Driver8BitDo_HandleStatePacket(joystick, ctx, data, size); + } + } + + if (size < 0) { + // Read error, device is disconnected + HIDAPI_JoystickDisconnected(device, device->joysticks[0]); + } + return (size >= 0); +} + +static void HIDAPI_Driver8BitDo_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ +} + +static void HIDAPI_Driver8BitDo_FreeDevice(SDL_HIDAPI_Device *device) +{ +} + +SDL_HIDAPI_DeviceDriver SDL_HIDAPI_Driver8BitDo = { + SDL_HINT_JOYSTICK_HIDAPI_8BITDO, + true, + HIDAPI_Driver8BitDo_RegisterHints, + HIDAPI_Driver8BitDo_UnregisterHints, + HIDAPI_Driver8BitDo_IsEnabled, + HIDAPI_Driver8BitDo_IsSupportedDevice, + HIDAPI_Driver8BitDo_InitDevice, + HIDAPI_Driver8BitDo_GetDevicePlayerIndex, + HIDAPI_Driver8BitDo_SetDevicePlayerIndex, + HIDAPI_Driver8BitDo_UpdateDevice, + HIDAPI_Driver8BitDo_OpenJoystick, + HIDAPI_Driver8BitDo_RumbleJoystick, + HIDAPI_Driver8BitDo_RumbleJoystickTriggers, + HIDAPI_Driver8BitDo_GetJoystickCapabilities, + HIDAPI_Driver8BitDo_SetJoystickLED, + HIDAPI_Driver8BitDo_SendJoystickEffect, + HIDAPI_Driver8BitDo_SetJoystickSensorsEnabled, + HIDAPI_Driver8BitDo_CloseJoystick, + HIDAPI_Driver8BitDo_FreeDevice, +}; + +#endif // SDL_JOYSTICK_HIDAPI_8BITDO + +#endif // SDL_JOYSTICK_HIDAPI diff --git a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_flydigi.c b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_flydigi.c new file mode 100644 index 00000000..1ee50d1e --- /dev/null +++ b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_flydigi.c @@ -0,0 +1,518 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2024 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +#ifdef SDL_JOYSTICK_HIDAPI + +#include "../SDL_sysjoystick.h" +#include "SDL_hidapijoystick_c.h" +#include "SDL_hidapi_rumble.h" + +#ifdef SDL_JOYSTICK_HIDAPI_FLYDIGI + +// Define this if you want to log all packets from the controller +#if 0 +#define DEBUG_FLYDIGI_PROTOCOL +#endif + +enum +{ + SDL_GAMEPAD_BUTTON_FLYDIGI_M1 = 11, + SDL_GAMEPAD_BUTTON_FLYDIGI_M2, + SDL_GAMEPAD_BUTTON_FLYDIGI_M3, + SDL_GAMEPAD_BUTTON_FLYDIGI_M4, + SDL_GAMEPAD_NUM_BASE_FLYDIGI_BUTTONS +}; + +/* Rate of IMU Sensor Packets over wireless Dongle observed in testcontroller tool at 1000hz */ +#define SENSOR_INTERVAL_VADER4_PRO_DONGLE_RATE_HZ 1000 +#define SENSOR_INTERVAL_VADER4_PRO_DONGLE_NS (SDL_NS_PER_SECOND / SENSOR_INTERVAL_VADER4_PRO_DONGLE_RATE_HZ) +/* Rate of IMU Sensor Packets over wired observed in testcontroller tool connection at 500hz */ +#define SENSOR_INTERVAL_VADER_PRO4_WIRED_RATE_HZ 500 +#define SENSOR_INTERVAL_VADER_PRO4_WIRED_NS (SDL_NS_PER_SECOND / SENSOR_INTERVAL_VADER_PRO4_WIRED_RATE_HZ) + +#define FLYDIGI_CMD_REPORT_ID 0x05 +#define FLYDIGI_HAPTIC_COMMAND 0x0F +#define FLYDIGI_GET_CONFIG_COMMAND 0xEB +#define FLYDIGI_GET_INFO_COMMAND 0xEC + +#define LOAD16(A, B) (Sint16)((Uint16)(A) | (((Uint16)(B)) << 8)) + +typedef struct +{ + Uint8 deviceID; + bool has_cz; + bool wireless; + bool sensors_supported; + bool sensors_enabled; + Uint16 firmware_version; + Uint64 sensor_timestamp_ns; // Simulate onboard clock. Advance by known time step. Nanoseconds. + Uint64 sensor_timestamp_step_ns; // Based on observed rate of receipt of IMU sensor packets. + float accelScale; + Uint8 last_state[USB_PACKET_LENGTH]; +} SDL_DriverFlydigi_Context; + + +static void HIDAPI_DriverFlydigi_RegisterHints(SDL_HintCallback callback, void *userdata) +{ + SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_FLYDIGI, callback, userdata); +} + +static void HIDAPI_DriverFlydigi_UnregisterHints(SDL_HintCallback callback, void *userdata) +{ + SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_FLYDIGI, callback, userdata); +} + +static bool HIDAPI_DriverFlydigi_IsEnabled(void) +{ + return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_FLYDIGI, SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT)); +} + +static bool HIDAPI_DriverFlydigi_IsSupportedDevice(SDL_HIDAPI_Device *device, const char *name, SDL_GamepadType type, Uint16 vendor_id, Uint16 product_id, Uint16 version, int interface_number, int interface_class, int interface_subclass, int interface_protocol) +{ + return SDL_IsJoystickFlydigiController(vendor_id, product_id) && interface_number == 2; +} + +static void UpdateDeviceIdentity(SDL_HIDAPI_Device *device) +{ + SDL_DriverFlydigi_Context *ctx = (SDL_DriverFlydigi_Context *)device->context; + + // Detecting the Vader 2 can take over 1000 read retries, so be generous here + for (int attempt = 0; ctx->deviceID == 0 && attempt < 30; ++attempt) { + const Uint8 request[] = { FLYDIGI_CMD_REPORT_ID, FLYDIGI_GET_INFO_COMMAND, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + // This write will occasionally return -1, so ignore failure here and try again + (void)SDL_hid_write(device->dev, request, sizeof(request)); + + // Read the reply + for (int i = 0; i < 100; ++i) { + SDL_Delay(1); + + Uint8 data[USB_PACKET_LENGTH]; + int size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 0); + if (size < 0) { + break; + } + if (size == 0) { + continue; + } + +#ifdef DEBUG_FLYDIGI_PROTOCOL + HIDAPI_DumpPacket("Flydigi packet: size = %d", data, size); +#endif + if (size == 32 && data[15] == 236) { + ctx->deviceID = data[3]; + ctx->firmware_version = data[9] | (data[10] << 8); + + char serial[9]; + (void)SDL_snprintf(serial, sizeof(serial), "%.2x%.2x%.2x%.2x", data[5], data[6], data[7], data[8]); + HIDAPI_SetDeviceSerial(device, serial); + + // The Vader 2 with firmware 6.0.4.9 doesn't report the connection state + if (ctx->firmware_version >= 0x6400) { + switch (data[13]) { + case 0: + // Wireless connection + ctx->wireless = true; + break; + case 1: + // Wired connection + ctx->wireless = false; + break; + default: + break; + } + } + + // Done! + break; + } + } + } + + if (ctx->deviceID == 0) { + // Try to guess from the name of the controller + if (SDL_strstr(device->name, "VADER") != NULL) { + if (SDL_strstr(device->name, "VADER2") != NULL) { + ctx->deviceID = 20; + } else if (SDL_strstr(device->name, "VADER3") != NULL) { + ctx->deviceID = 28; + } else if (SDL_strstr(device->name, "VADER4") != NULL) { + ctx->deviceID = 85; + } + } else if (SDL_strstr(device->name, "APEX") != NULL) { + if (SDL_strstr(device->name, "APEX2") != NULL) { + ctx->deviceID = 19; + } else if (SDL_strstr(device->name, "APEX3") != NULL) { + ctx->deviceID = 24; + } else if (SDL_strstr(device->name, "APEX4") != NULL) { + ctx->deviceID = 84; + } + } + } + device->guid.data[15] = ctx->deviceID; + + // This is the previous sensor default of 125hz. + // Override this in the switch statement below based on observed sensor packet rate. + ctx->sensor_timestamp_step_ns = SDL_NS_PER_SECOND / 125; + + switch (ctx->deviceID) { + case 19: + HIDAPI_SetDeviceName(device, "Flydigi Apex 2"); + break; + case 24: + case 26: + case 29: + HIDAPI_SetDeviceName(device, "Flydigi Apex 3"); + break; + case 84: + // The Apex 4 controller has sensors, but they're only reported when gyro mouse is enabled + HIDAPI_SetDeviceName(device, "Flydigi Apex 4"); + break; + case 20: + case 21: + case 23: + // The Vader 2 controller has sensors, but they're only reported when gyro mouse is enabled + HIDAPI_SetDeviceName(device, "Flydigi Vader 2"); + ctx->has_cz = true; + break; + case 22: + HIDAPI_SetDeviceName(device, "Flydigi Vader 2 Pro"); + ctx->has_cz = true; + break; + case 28: + HIDAPI_SetDeviceName(device, "Flydigi Vader 3"); + ctx->has_cz = true; + break; + case 80: + case 81: + HIDAPI_SetDeviceName(device, "Flydigi Vader 3 Pro"); + ctx->has_cz = true; + ctx->sensors_supported = true; + ctx->accelScale = SDL_STANDARD_GRAVITY / 256.0f; + ctx->sensor_timestamp_step_ns = ctx->wireless ? SENSOR_INTERVAL_VADER4_PRO_DONGLE_NS : SENSOR_INTERVAL_VADER_PRO4_WIRED_NS; + break; + case 85: + case 105: + HIDAPI_SetDeviceName(device, "Flydigi Vader 4 Pro"); + ctx->has_cz = true; + ctx->sensors_supported = true; + ctx->accelScale = SDL_STANDARD_GRAVITY / 256.0f; + ctx->sensor_timestamp_step_ns = ctx->wireless ? SENSOR_INTERVAL_VADER4_PRO_DONGLE_NS : SENSOR_INTERVAL_VADER_PRO4_WIRED_NS; + break; + default: + SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "Unknown FlyDigi controller with ID %d, name '%s'", ctx->deviceID, device->name); + break; + } +} + +static bool HIDAPI_DriverFlydigi_InitDevice(SDL_HIDAPI_Device *device) +{ + SDL_DriverFlydigi_Context *ctx = (SDL_DriverFlydigi_Context *)SDL_calloc(1, sizeof(*ctx)); + if (!ctx) { + return false; + } + device->context = ctx; + + UpdateDeviceIdentity(device); + + return HIDAPI_JoystickConnected(device, NULL); +} + +static int HIDAPI_DriverFlydigi_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id) +{ + return -1; +} + +static void HIDAPI_DriverFlydigi_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index) +{ +} + +#ifndef DEG2RAD +#define DEG2RAD(x) ((float)(x) * (float)(SDL_PI_F / 180.f)) +#endif + +static bool HIDAPI_DriverFlydigi_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ + SDL_DriverFlydigi_Context *ctx = (SDL_DriverFlydigi_Context *)device->context; + + SDL_AssertJoysticksLocked(); + + SDL_zeroa(ctx->last_state); + + // Initialize the joystick capabilities + joystick->nbuttons = SDL_GAMEPAD_NUM_BASE_FLYDIGI_BUTTONS; + if (ctx->has_cz) { + joystick->nbuttons += 2; + } + joystick->naxes = SDL_GAMEPAD_AXIS_COUNT; + joystick->nhats = 1; + + if (ctx->wireless) { + joystick->connection_state = SDL_JOYSTICK_CONNECTION_WIRELESS; + } + + if (ctx->sensors_supported) { + + const float flSensorRate = ctx->wireless ? (float)SENSOR_INTERVAL_VADER4_PRO_DONGLE_RATE_HZ : (float)SENSOR_INTERVAL_VADER_PRO4_WIRED_RATE_HZ; + SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, flSensorRate); + SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, flSensorRate); + } + + return true; +} + +static bool HIDAPI_DriverFlydigi_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) +{ + Uint8 rumble_packet[4] = { FLYDIGI_CMD_REPORT_ID, FLYDIGI_HAPTIC_COMMAND, 0x00, 0x00 }; + rumble_packet[2] = low_frequency_rumble >> 8; + rumble_packet[3] = high_frequency_rumble >> 8; + + if (SDL_HIDAPI_SendRumble(device, rumble_packet, sizeof(rumble_packet)) != sizeof(rumble_packet)) { + return SDL_SetError("Couldn't send rumble packet"); + } + return true; +} + +static bool HIDAPI_DriverFlydigi_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble) +{ + return SDL_Unsupported(); +} + +static Uint32 HIDAPI_DriverFlydigi_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ + return SDL_JOYSTICK_CAP_RUMBLE; +} + +static bool HIDAPI_DriverFlydigi_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue) +{ + return SDL_Unsupported(); +} + +static bool HIDAPI_DriverFlydigi_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size) +{ + return SDL_Unsupported(); +} + +static bool HIDAPI_DriverFlydigi_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled) +{ + SDL_DriverFlydigi_Context *ctx = (SDL_DriverFlydigi_Context *)device->context; + if (ctx->sensors_supported) { + ctx->sensors_enabled = enabled; + return true; + } + return SDL_Unsupported(); +} +static void HIDAPI_DriverFlydigi_HandleStatePacket(SDL_Joystick *joystick, SDL_DriverFlydigi_Context *ctx, Uint8 *data, int size) +{ + Sint16 axis; + Uint64 timestamp = SDL_GetTicksNS(); + if (data[0] != 0x04 && data[0] != 0xFE) { + // We don't know how to handle this report + return; + } + + Uint8 extra_button_index = SDL_GAMEPAD_NUM_BASE_FLYDIGI_BUTTONS; + + if (ctx->last_state[9] != data[9]) { + Uint8 hat; + + switch (data[9] & 0x0F) { + case 0b0001: + hat = SDL_HAT_UP; + break; + case 0b0011: + hat = SDL_HAT_RIGHTUP; + break; + case 0b0010: + hat = SDL_HAT_RIGHT; + break; + case 0b0110: + hat = SDL_HAT_RIGHTDOWN; + break; + case 0b0100: + hat = SDL_HAT_DOWN; + break; + case 0b1100: + hat = SDL_HAT_LEFTDOWN; + break; + case 0b1000: + hat = SDL_HAT_LEFT; + break; + case 0b1001: + hat = SDL_HAT_LEFTUP; + break; + default: + hat = SDL_HAT_CENTERED; + break; + } + SDL_SendJoystickHat(timestamp, joystick, 0, hat); + + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data[9] & 0x10) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data[9] & 0x20) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data[9] & 0x40) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data[9] & 0x80) != 0)); + } + + if (ctx->last_state[10] != data[10]) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data[10] & 0x01) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data[10] & 0x02) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data[10] & 0x04) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data[10] & 0x08) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data[10] & 0x40) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data[10] & 0x80) != 0)); + } + + if (ctx->last_state[7] != data[7]) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_FLYDIGI_M1, ((data[7] & 0x04) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_FLYDIGI_M2, ((data[7] & 0x08) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_FLYDIGI_M3, ((data[7] & 0x10) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_FLYDIGI_M4, ((data[7] & 0x20) != 0)); + if (ctx->has_cz) { + SDL_SendJoystickButton(timestamp, joystick, extra_button_index++, ((data[7] & 0x01) != 0)); + SDL_SendJoystickButton(timestamp, joystick, extra_button_index++, ((data[7] & 0x02) != 0)); + } + } + + if (ctx->last_state[8] != data[8]) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[8] & 0x08) != 0)); + // The '+' button is used to toggle gyro mouse mode, so don't pass that to the application + //SDL_SendJoystickButton(timestamp, joystick, extra_button_index++, ((data[8] & 0x01) != 0)); + // The '-' button is only available on the Vader 2, for simplicity let's ignore that + //SDL_SendJoystickButton(timestamp, joystick, extra_button_index++, ((data[8] & 0x10) != 0)); + } + +#define READ_STICK_AXIS(offset) \ + (data[offset] == 0x7f ? 0 : (Sint16)HIDAPI_RemapVal((float)((int)data[offset] - 0x7f), -0x7f, 0xff - 0x7f, SDL_MIN_SINT16, SDL_MAX_SINT16)) + { + axis = READ_STICK_AXIS(17); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis); + axis = READ_STICK_AXIS(19); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis); + axis = READ_STICK_AXIS(21); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis); + axis = READ_STICK_AXIS(22); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis); + } +#undef READ_STICK_AXIS + +#define READ_TRIGGER_AXIS(offset) \ + (Sint16)(((int)data[offset] * 257) - 32768) + { + axis = READ_TRIGGER_AXIS(23); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis); + axis = READ_TRIGGER_AXIS(24); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis); + } +#undef READ_TRIGGER_AXIS + + if (ctx->sensors_enabled) { + Uint64 sensor_timestamp; + float values[3]; + + // Advance the imu sensor time stamp based on the observed rate of receipt of packets in the testcontroller app. + // This varies between Product ID and connection type. + sensor_timestamp = ctx->sensor_timestamp_ns; + ctx->sensor_timestamp_ns += ctx->sensor_timestamp_step_ns; + + // Pitch and yaw scales may be receiving extra filtering for the sake of bespoke direct mouse output. + // As result, roll has a different scaling factor than pitch and yaw. + // These values were estimated using the testcontroller tool in lieux of hard data sheet references. + const float flPitchAndYawScale = DEG2RAD(72000.0f); + const float flRollScale = DEG2RAD(1200.0f); + values[0] = HIDAPI_RemapVal(-1.0f * LOAD16(data[26], data[27]), INT16_MIN, INT16_MAX, -flPitchAndYawScale, flPitchAndYawScale); + values[1] = HIDAPI_RemapVal(-1.0f * LOAD16(data[18], data[20]), INT16_MIN, INT16_MAX, -flPitchAndYawScale, flPitchAndYawScale); + values[2] = HIDAPI_RemapVal(-1.0f * LOAD16(data[29], data[30]), INT16_MIN, INT16_MAX, -flRollScale, flRollScale); + + SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_GYRO, sensor_timestamp, values, 3); + + values[0] = -LOAD16(data[11], data[12]) * ctx->accelScale; // Acceleration along pitch axis + values[1] = LOAD16(data[15], data[16]) * ctx->accelScale; // Acceleration along yaw axis + values[2] = LOAD16(data[13], data[14]) * ctx->accelScale; // Acceleration along roll axis + SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, sensor_timestamp, values, 3); + } + + SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state))); +} + +static bool HIDAPI_DriverFlydigi_UpdateDevice(SDL_HIDAPI_Device *device) +{ + SDL_DriverFlydigi_Context *ctx = (SDL_DriverFlydigi_Context *)device->context; + SDL_Joystick *joystick = NULL; + Uint8 data[USB_PACKET_LENGTH]; + int size = 0; + + if (device->num_joysticks > 0) { + joystick = SDL_GetJoystickFromID(device->joysticks[0]); + } else { + return false; + } + + while ((size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 0)) > 0) { +#ifdef DEBUG_FLYDIGI_PROTOCOL + HIDAPI_DumpPacket("Flydigi packet: size = %d", data, size); +#endif + if (!joystick) { + continue; + } + + HIDAPI_DriverFlydigi_HandleStatePacket(joystick, ctx, data, size); + } + + if (size < 0) { + // Read error, device is disconnected + HIDAPI_JoystickDisconnected(device, device->joysticks[0]); + } + return (size >= 0); +} + +static void HIDAPI_DriverFlydigi_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ +} + +static void HIDAPI_DriverFlydigi_FreeDevice(SDL_HIDAPI_Device *device) +{ +} + +SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverFlydigi = { + SDL_HINT_JOYSTICK_HIDAPI_FLYDIGI, + true, + HIDAPI_DriverFlydigi_RegisterHints, + HIDAPI_DriverFlydigi_UnregisterHints, + HIDAPI_DriverFlydigi_IsEnabled, + HIDAPI_DriverFlydigi_IsSupportedDevice, + HIDAPI_DriverFlydigi_InitDevice, + HIDAPI_DriverFlydigi_GetDevicePlayerIndex, + HIDAPI_DriverFlydigi_SetDevicePlayerIndex, + HIDAPI_DriverFlydigi_UpdateDevice, + HIDAPI_DriverFlydigi_OpenJoystick, + HIDAPI_DriverFlydigi_RumbleJoystick, + HIDAPI_DriverFlydigi_RumbleJoystickTriggers, + HIDAPI_DriverFlydigi_GetJoystickCapabilities, + HIDAPI_DriverFlydigi_SetJoystickLED, + HIDAPI_DriverFlydigi_SendJoystickEffect, + HIDAPI_DriverFlydigi_SetJoystickSensorsEnabled, + HIDAPI_DriverFlydigi_CloseJoystick, + HIDAPI_DriverFlydigi_FreeDevice, +}; + +#endif // SDL_JOYSTICK_HIDAPI_FLYDIGI + +#endif // SDL_JOYSTICK_HIDAPI diff --git a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_gamecube.c b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_gamecube.c index 4d45c7a2..b95fb89c 100644 --- a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_gamecube.c +++ b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_gamecube.c @@ -31,7 +31,9 @@ #ifdef SDL_JOYSTICK_HIDAPI_GAMECUBE // Define this if you want to log all packets from the controller -// #define DEBUG_GAMECUBE_PROTOCOL +#if 0 +#define DEBUG_GAMECUBE_PROTOCOL +#endif #define MAX_CONTROLLERS 4 @@ -74,7 +76,8 @@ static bool HIDAPI_DriverGameCube_IsSupportedDevice(SDL_HIDAPI_Device *device, c } if (vendor_id == USB_VENDOR_DRAGONRISE && (product_id == USB_PRODUCT_EVORETRO_GAMECUBE_ADAPTER1 || - product_id == USB_PRODUCT_EVORETRO_GAMECUBE_ADAPTER2)) { + product_id == USB_PRODUCT_EVORETRO_GAMECUBE_ADAPTER2 || + product_id == USB_PRODUCT_EVORETRO_GAMECUBE_ADAPTER3)) { // EVORETRO GameCube Controller Adapter return true; } @@ -119,22 +122,15 @@ static bool HIDAPI_DriverGameCube_InitDevice(SDL_HIDAPI_Device *device) } device->context = ctx; - ctx->joysticks[0] = 0; - ctx->joysticks[1] = 0; - ctx->joysticks[2] = 0; - ctx->joysticks[3] = 0; ctx->rumble[0] = rumbleMagic; - ctx->useRumbleBrake = false; if (device->vendor_id != USB_VENDOR_NINTENDO) { ctx->pc_mode = true; } if (ctx->pc_mode) { - for (i = 0; i < MAX_CONTROLLERS; ++i) { - ResetAxisRange(ctx, i); - HIDAPI_JoystickConnected(device, &ctx->joysticks[i]); - } + ResetAxisRange(ctx, 0); + HIDAPI_JoystickConnected(device, &ctx->joysticks[0]); } else { // This is all that's needed to initialize the device. Really! if (SDL_hid_write(device->dev, &initMagic, sizeof(initMagic)) != sizeof(initMagic)) { @@ -204,69 +200,61 @@ static void HIDAPI_DriverGameCube_SetDevicePlayerIndex(SDL_HIDAPI_Device *device { } -static void HIDAPI_DriverGameCube_HandleJoystickPacket(SDL_HIDAPI_Device *device, SDL_DriverGameCube_Context *ctx, const Uint8 *packet, int size) +static void HIDAPI_DriverGameCube_HandleJoystickPacket(SDL_HIDAPI_Device *device, SDL_DriverGameCube_Context *ctx, const Uint8 *packet, bool invert_c_stick) { SDL_Joystick *joystick; - Uint8 i, v; + const Uint8 i = 0; // We have a separate context for each connected controller in PC mode, just use the first index + Uint8 v; Sint16 axis_value; Uint64 timestamp = SDL_GetTicksNS(); - if (size != 10) { - return; // How do we handle this packet? - } - - i = packet[0] - 1; - if (i >= MAX_CONTROLLERS) { - return; // How do we handle this packet? - } - joystick = SDL_GetJoystickFromID(ctx->joysticks[i]); if (!joystick) { // Hasn't been opened yet, skip return; } -#define READ_BUTTON(off, flag, button) \ - SDL_SendJoystickButton( \ - timestamp, \ - joystick, \ - button, \ +#define READ_BUTTON(off, flag, button) \ + SDL_SendJoystickButton( \ + timestamp, \ + joystick, \ + button, \ ((packet[off] & flag) != 0)); - READ_BUTTON(1, 0x02, 0) // A - READ_BUTTON(1, 0x04, 1) // B - READ_BUTTON(1, 0x08, 3) // Y - READ_BUTTON(1, 0x01, 2) // X - READ_BUTTON(2, 0x80, 4) // DPAD_LEFT - READ_BUTTON(2, 0x20, 5) // DPAD_RIGHT - READ_BUTTON(2, 0x40, 6) // DPAD_DOWN - READ_BUTTON(2, 0x10, 7) // DPAD_UP - READ_BUTTON(2, 0x02, 8) // START - READ_BUTTON(1, 0x80, 9) // RIGHTSHOULDER + READ_BUTTON(0, 0x02, 0) // A + READ_BUTTON(0, 0x04, 1) // B + READ_BUTTON(0, 0x08, 3) // Y + READ_BUTTON(0, 0x01, 2) // X + READ_BUTTON(1, 0x80, 4) // DPAD_LEFT + READ_BUTTON(1, 0x20, 5) // DPAD_RIGHT + READ_BUTTON(1, 0x40, 6) // DPAD_DOWN + READ_BUTTON(1, 0x10, 7) // DPAD_UP + READ_BUTTON(1, 0x02, 8) // START + READ_BUTTON(0, 0x80, 9) // RIGHTSHOULDER /* These two buttons are for the bottoms of the analog triggers. * More than likely, you're going to want to read the axes instead! * -flibit */ - READ_BUTTON(1, 0x20, 10) // TRIGGERRIGHT - READ_BUTTON(1, 0x10, 11) // TRIGGERLEFT + READ_BUTTON(0, 0x20, 10) // TRIGGERRIGHT + READ_BUTTON(0, 0x10, 11) // TRIGGERLEFT #undef READ_BUTTON -#define READ_AXIS(off, axis, invert) \ - v = invert ? (0xff - packet[off]) : packet[off]; \ - if (v < ctx->min_axis[i * SDL_GAMEPAD_AXIS_COUNT + axis]) \ - ctx->min_axis[i * SDL_GAMEPAD_AXIS_COUNT + axis] = v; \ - if (v > ctx->max_axis[i * SDL_GAMEPAD_AXIS_COUNT + axis]) \ - ctx->max_axis[i * SDL_GAMEPAD_AXIS_COUNT + axis] = v; \ +#define READ_AXIS(off, axis, invert) \ + v = (invert) ? (0xff - packet[off]) : packet[off]; \ + if (v < ctx->min_axis[i * SDL_GAMEPAD_AXIS_COUNT + axis]) \ + ctx->min_axis[i * SDL_GAMEPAD_AXIS_COUNT + axis] = v; \ + if (v > ctx->max_axis[i * SDL_GAMEPAD_AXIS_COUNT + axis]) \ + ctx->max_axis[i * SDL_GAMEPAD_AXIS_COUNT + axis] = v; \ axis_value = (Sint16)HIDAPI_RemapVal(v, ctx->min_axis[i * SDL_GAMEPAD_AXIS_COUNT + axis], ctx->max_axis[i * SDL_GAMEPAD_AXIS_COUNT + axis], SDL_MIN_SINT16, SDL_MAX_SINT16); \ - SDL_SendJoystickAxis( \ - timestamp, \ - joystick, \ + SDL_SendJoystickAxis( \ + timestamp, \ + joystick, \ axis, axis_value); - READ_AXIS(3, SDL_GAMEPAD_AXIS_LEFTX, 0) - READ_AXIS(4, SDL_GAMEPAD_AXIS_LEFTY, 1) - READ_AXIS(6, SDL_GAMEPAD_AXIS_RIGHTX, 0) - READ_AXIS(5, SDL_GAMEPAD_AXIS_RIGHTY, 1) - READ_AXIS(7, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, 0) - READ_AXIS(8, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, 0) + READ_AXIS(2, SDL_GAMEPAD_AXIS_LEFTX, 0) + READ_AXIS(3, SDL_GAMEPAD_AXIS_LEFTY, 1) + READ_AXIS(5, SDL_GAMEPAD_AXIS_RIGHTX, invert_c_stick ? 1 : 0) + READ_AXIS(4, SDL_GAMEPAD_AXIS_RIGHTY, invert_c_stick ? 0 : 1) + READ_AXIS(6, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, 0) + READ_AXIS(7, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, 0) #undef READ_AXIS } @@ -365,7 +353,18 @@ static bool HIDAPI_DriverGameCube_UpdateDevice(SDL_HIDAPI_Device *device) HIDAPI_DumpPacket("Nintendo GameCube packet: size = %d", packet, size); #endif if (ctx->pc_mode) { - HIDAPI_DriverGameCube_HandleJoystickPacket(device, ctx, packet, size); + if (size == 10) { + // This is the older firmware + // The first byte is the index of the connected controller + // The C stick has an inverted value range compared to the left stick + HIDAPI_DriverGameCube_HandleJoystickPacket(device, ctx, &packet[1], true); + } else if (size == 9) { + // This is the newer firmware (version 0x7) + // The C stick has the same value range compared to the left stick + HIDAPI_DriverGameCube_HandleJoystickPacket(device, ctx, packet, false); + } else { + // How do we handle this packet? + } } else { HIDAPI_DriverGameCube_HandleNintendoPacket(device, ctx, packet, size); } diff --git a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_gip.c b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_gip.c new file mode 100644 index 00000000..4851adc2 --- /dev/null +++ b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_gip.c @@ -0,0 +1,2958 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +#ifdef SDL_JOYSTICK_HIDAPI + +#include "../../events/SDL_keyboard_c.h" +#include "../SDL_sysjoystick.h" +#include "SDL_hidapijoystick_c.h" +#include "SDL_hidapi_rumble.h" + +#ifdef SDL_JOYSTICK_HIDAPI_GIP + +// This driver is based on the Microsoft GIP spec at: +// https://aka.ms/gipdocs +// https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-gipusb/e7c90904-5e21-426e-b9ad-d82adeee0dbc + +// Define this if you want to log all packets from the controller +#if 0 +#define DEBUG_XBOX_PROTOCOL +#endif + +#define MAX_MESSAGE_LENGTH 0x4000 +#define MAX_ATTACHMENTS 8 + +#define GIP_DATA_CLASS_COMMAND (0u << 5) +#define GIP_DATA_CLASS_LOW_LATENCY (1u << 5) +#define GIP_DATA_CLASS_STANDARD_LATENCY (2u << 5) +#define GIP_DATA_CLASS_AUDIO (3u << 5) + +#define GIP_DATA_CLASS_SHIFT 5 +#define GIP_DATA_CLASS_MASK (7u << 5) + +/* System messages */ +#define GIP_CMD_PROTO_CONTROL 0x01 +#define GIP_CMD_HELLO_DEVICE 0x02 +#define GIP_CMD_STATUS_DEVICE 0x03 +#define GIP_CMD_METADATA 0x04 +#define GIP_CMD_SET_DEVICE_STATE 0x05 +#define GIP_CMD_SECURITY 0x06 +#define GIP_CMD_GUIDE_BUTTON 0x07 +#define GIP_CMD_AUDIO_CONTROL 0x08 +#define GIP_CMD_LED 0x0a +#define GIP_CMD_HID_REPORT 0x0b +#define GIP_CMD_FIRMWARE 0x0c +#define GIP_CMD_EXTENDED 0x1e +#define GIP_CMD_DEBUG 0x1f +#define GIP_AUDIO_DATA 0x60 + +/* Navigation vendor messages */ +#define GIP_CMD_DIRECT_MOTOR 0x09 +#define GIP_LL_INPUT_REPORT 0x20 +#define GIP_LL_OVERFLOW_INPUT_REPORT 0x26 + +/* Wheel and ArcadeStick vendor messages */ +#define GIP_CMD_INITIAL_REPORTS_REQUEST 0x0a +#define GIP_LL_STATIC_CONFIGURATION 0x21 +#define GIP_LL_BUTTON_INFO_REPORT 0x22 + +/* Wheel vendor messages */ +#define GIP_CMD_SET_APPLICATION_MEMORY 0x0b +#define GIP_CMD_SET_EQUATIONS_STATES 0x0c +#define GIP_CMD_SET_EQUATION 0x0d + +/* FlightStick vendor messages */ +#define GIP_CMD_DEVICE_CAPABILITIES 0x00 +#define GIP_CMD_LED_CAPABILITIES 0x01 +#define GIP_CMD_SET_LED_STATE 0x02 + +/* Undocumented Elite 2 vendor messages */ +#define GIP_CMD_RAW_REPORT 0x0c +#define GIP_CMD_GUIDE_COLOR 0x0e +#define GIP_SL_ELITE_CONFIG 0x4d + +#define GIP_BTN_OFFSET_XBE1 28 +#define GIP_BTN_OFFSET_XBE2 14 + +#define GIP_FLAG_FRAGMENT (1u << 7) +#define GIP_FLAG_INIT_FRAG (1u << 6) +#define GIP_FLAG_SYSTEM (1u << 5) +#define GIP_FLAG_ACME (1u << 4) +#define GIP_FLAG_ATTACHMENT_MASK 0x7 + +#define GIP_AUDIO_FORMAT_NULL 0 +#define GIP_AUDIO_FORMAT_8000HZ_1CH 1 +#define GIP_AUDIO_FORMAT_8000HZ_2CH 2 +#define GIP_AUDIO_FORMAT_12000HZ_1CH 3 +#define GIP_AUDIO_FORMAT_12000HZ_2CH 4 +#define GIP_AUDIO_FORMAT_16000HZ_1CH 5 +#define GIP_AUDIO_FORMAT_16000HZ_2CH 6 +#define GIP_AUDIO_FORMAT_20000HZ_1CH 7 +#define GIP_AUDIO_FORMAT_20000HZ_2CH 8 +#define GIP_AUDIO_FORMAT_24000HZ_1CH 9 +#define GIP_AUDIO_FORMAT_24000HZ_2CH 10 +#define GIP_AUDIO_FORMAT_32000HZ_1CH 11 +#define GIP_AUDIO_FORMAT_32000HZ_2CH 12 +#define GIP_AUDIO_FORMAT_40000HZ_1CH 13 +#define GIP_AUDIO_FORMAT_40000HZ_2CH 14 +#define GIP_AUDIO_FORMAT_48000HZ_1CH 15 +#define GIP_AUDIO_FORMAT_48000HZ_2CH 16 +#define GIP_AUDIO_FORMAT_48000HZ_6CH 32 +#define GIP_AUDIO_FORMAT_48000HZ_8CH 33 + +/* Protocol Control constants */ +#define GIP_CONTROL_CODE_ACK 0 +#define GIP_CONTROL_CODE_NACK 1 /* obsolete */ +#define GIP_CONTROL_CODE_UNK 2 /* obsolete */ +#define GIP_CONTROL_CODE_AB 3 /* obsolete */ +#define GIP_CONTROL_CODE_MPER 4 /* obsolete */ +#define GIP_CONTROL_CODE_STOP 5 /* obsolete */ +#define GIP_CONTROL_CODE_START 6 /* obsolete */ +#define GIP_CONTROL_CODE_ERR 7 /* obsolete */ + +/* Status Device constants */ +#define GIP_POWER_LEVEL_OFF 0 +#define GIP_POWER_LEVEL_STANDBY 1 /* obsolete */ +#define GIP_POWER_LEVEL_FULL 2 + +#define GIP_NOT_CHARGING 0 +#define GIP_CHARGING 1 +#define GIP_CHARGE_ERROR 2 + +#define GIP_BATTERY_ABSENT 0 +#define GIP_BATTERY_STANDARD 1 +#define GIP_BATTERY_RECHARGEABLE 2 + +#define GIP_BATTERY_CRITICAL 0 +#define GIP_BATTERY_LOW 1 +#define GIP_BATTERY_MEDIUM 2 +#define GIP_BATTERY_FULL 3 + +#define GIP_EVENT_FAULT 0x0002 + +#define GIP_FAULT_UNKNOWN 0 +#define GIP_FAULT_HARD 1 +#define GIP_FAULT_NMI 2 +#define GIP_FAULT_SVC 3 +#define GIP_FAULT_PEND_SV 4 +#define GIP_FAULT_SMART_PTR 5 +#define GIP_FAULT_MCU 6 +#define GIP_FAULT_BUS 7 +#define GIP_FAULT_USAGE 8 +#define GIP_FAULT_RADIO_HANG 9 +#define GIP_FAULT_WATCHDOG 10 +#define GIP_FAULT_LINK_STALL 11 +#define GIP_FAULT_ASSERTION 12 + +/* Metadata constants */ +#define GIP_MESSAGE_FLAG_BIG_ENDIAN (1u << 0) +#define GIP_MESSAGE_FLAG_RELIABLE (1u << 1) +#define GIP_MESSAGE_FLAG_SEQUENCED (1u << 2) +#define GIP_MESSAGE_FLAG_DOWNSTREAM (1u << 3) +#define GIP_MESSAGE_FLAG_UPSTREAM (1u << 4) +#define GIP_MESSAGE_FLAG_DS_REQUEST_RESPONSE (1u << 5) + +#define GIP_DATA_TYPE_CUSTOM 1 +#define GIP_DATA_TYPE_AUDIO 2 +#define GIP_DATA_TYPE_SECURITY 3 +#define GIP_DATA_TYPE_GIP 4 + +/* Set Device State constants */ +#define GIP_STATE_START 0 +#define GIP_STATE_STOP 1 +#define GIP_STATE_STANDBY 2 /* obsolete */ +#define GIP_STATE_FULL_POWER 3 +#define GIP_STATE_OFF 4 +#define GIP_STATE_QUIESCE 5 +#define GIP_STATE_UNK6 6 +#define GIP_STATE_RESET 7 + +/* Guide Button Status constants */ +#define GIP_LED_GUIDE 0 +#define GIP_LID_IR 1 /* deprecated */ + +#define GIP_LED_GUIDE_OFF 0 +#define GIP_LED_GUIDE_ON 1 +#define GIP_LED_GUIDE_FAST_BLINK 2 +#define GIP_LED_GUIDE_SLOW_BLINK 3 +#define GIP_LED_GUIDE_CHARGING_BLINK 4 +#define GIP_LED_GUIDE_RAMP_TO_LEVEL 0xd + +#define GIP_LED_IR_OFF 0 +#define GIP_LED_IR_ON_100MS 1 +#define GIP_LED_IR_PATTERN 4 + +/* Direct Motor Command constants */ +#define GIP_MOTOR_RIGHT_VIBRATION (1u << 0) +#define GIP_MOTOR_LEFT_VIBRATION (1u << 1) +#define GIP_MOTOR_RIGHT_IMPULSE (1u << 2) +#define GIP_MOTOR_LEFT_IMPULSE (1u << 3) +#define GIP_MOTOR_ALL 0xF + +/* Extended Comand constants */ +#define GIP_EXTCMD_GET_CAPABILITIES 0x00 +#define GIP_EXTCMD_GET_TELEMETRY_DATA 0x01 +#define GIP_EXTCMD_GET_SERIAL_NUMBER 0x04 + +#define GIP_EXTENDED_STATUS_OK 0 +#define GIP_EXTENDED_STATUS_NOT_SUPPORTED 1 +#define GIP_EXTENDED_STATUS_NOT_READY 2 +#define GIP_EXTENDED_STATUS_ACCESS_DENIED 3 +#define GIP_EXTENDED_STATUS_FAILED 4 + +/* Internal constants, not part of protocol */ +#define GIP_HELLO_TIMEOUT 2000 +#define GIP_ACME_TIMEOUT 10 + +#define GIP_DEFAULT_IN_SYSTEM_MESSAGES 0x5e +#define GIP_DEFAULT_OUT_SYSTEM_MESSAGES 0x472 + +#define GIP_FEATURE_CONSOLE_FUNCTION_MAP (1u << 0) +#define GIP_FEATURE_CONSOLE_FUNCTION_MAP_OVERFLOW (1u << 1) +#define GIP_FEATURE_ELITE_BUTTONS (1u << 2) +#define GIP_FEATURE_DYNAMIC_LATENCY_INPUT (1u << 3) +#define GIP_FEATURE_SECURITY_OPT_OUT (1u << 4) +#define GIP_FEATURE_MOTOR_CONTROL (1u << 5) +#define GIP_FEATURE_GUIDE_COLOR (1u << 6) +#define GIP_FEATURE_EXTENDED_SET_DEVICE_STATE (1u << 7) + +#define GIP_QUIRK_NO_HELLO (1u << 0) +#define GIP_QUIRK_BROKEN_METADATA (1u << 1) +#define GIP_QUIRK_NO_IMPULSE_VIBRATION (1u << 2) + +typedef enum +{ + GIP_METADATA_NONE = 0, + GIP_METADATA_GOT = 1, + GIP_METADATA_FAKED = 2, + GIP_METADATA_PENDING = 3, +} GIP_MetadataStatus; + +#ifndef VK_LWIN +#define VK_LWIN 0x5b +#endif + +typedef enum +{ + GIP_TYPE_UNKNOWN = -1, + GIP_TYPE_GAMEPAD = 0, + GIP_TYPE_ARCADE_STICK = 1, + GIP_TYPE_WHEEL = 2, + GIP_TYPE_FLIGHT_STICK = 3, + GIP_TYPE_NAVIGATION_CONTROLLER = 4, + GIP_TYPE_CHATPAD = 5, + GIP_TYPE_HEADSET = 6, +} GIP_AttachmentType; + +typedef enum +{ + GIP_RUMBLE_STATE_IDLE, + GIP_RUMBLE_STATE_QUEUED, + GIP_RUMBLE_STATE_BUSY, +} GIP_RumbleState; + +typedef enum +{ + GIP_BTN_FMT_UNKNOWN, + GIP_BTN_FMT_XBE1, + GIP_BTN_FMT_XBE2_RAW, + GIP_BTN_FMT_XBE2_4, + GIP_BTN_FMT_XBE2_5, +} GIP_EliteButtonFormat; + +/* These come across the wire as little-endian, so let's store them in-memory as such so we can memcmp */ +#define MAKE_GUID(NAME, A, B, C, D0, D1, D2, D3, D4, D5, D6, D7) \ + static const GUID NAME = { SDL_Swap32LE(A), SDL_Swap16LE(B), SDL_Swap16LE(C), { D0, D1, D2, D3, D4, D5, D6, D7 } } + +typedef struct GUID +{ + Uint32 a; + Uint16 b; + Uint16 c; + Uint8 d[8]; +} GUID; +SDL_COMPILE_TIME_ASSERT(GUID, sizeof(GUID) == 16); + +MAKE_GUID(GUID_ArcadeStick, 0x332054cc, 0xa34b, 0x41d5, 0xa3, 0x4a, 0xa6, 0xa6, 0x71, 0x1e, 0xc4, 0xb3); +MAKE_GUID(GUID_DynamicLatencyInput, 0x87f2e56b, 0xc3bb, 0x49b1, 0x82, 0x65, 0xff, 0xff, 0xf3, 0x77, 0x99, 0xee); +MAKE_GUID(GUID_FlightStick, 0x03f1a011, 0xefe9, 0x4cc1, 0x96, 0x9c, 0x38, 0xdc, 0x55, 0xf4, 0x04, 0xd0); +MAKE_GUID(GUID_IHeadset, 0xbc25d1a3, 0xc24e, 0x4992, 0x9d, 0xda, 0xef, 0x4f, 0x12, 0x3e, 0xf5, 0xdc); +MAKE_GUID(GUID_IConsoleFunctionMap_InputReport, 0xecddd2fe, 0xd387, 0x4294, 0xbd, 0x96, 0x1a, 0x71, 0x2e, 0x3d, 0xc7, 0x7d); +MAKE_GUID(GUID_IConsoleFunctionMap_OverflowInputReport, 0x137d4bd0, 0x9347, 0x4472, 0xaa, 0x26, 0x8c, 0x34, 0xa0, 0x8f, 0xf9, 0xbd); +MAKE_GUID(GUID_IController, 0x9776ff56, 0x9bfd, 0x4581, 0xad, 0x45, 0xb6, 0x45, 0xbb, 0xa5, 0x26, 0xd6); +MAKE_GUID(GUID_IDevAuthPCOptOut, 0x7a34ce77, 0x7de2, 0x45c6, 0x8c, 0xa4, 0x00, 0x42, 0xc0, 0x8b, 0xd9, 0x4a); +MAKE_GUID(GUID_IEliteButtons, 0x37d19ff7, 0xb5c6, 0x49d1, 0xa7, 0x5e, 0x03, 0xb2, 0x4b, 0xef, 0x8c, 0x89); +MAKE_GUID(GUID_IGamepad, 0x082e402c, 0x07df, 0x45e1, 0xa5, 0xab, 0xa3, 0x12, 0x7a, 0xf1, 0x97, 0xb5); +MAKE_GUID(GUID_NavigationController, 0xb8f31fe7, 0x7386, 0x40e9, 0xa9, 0xf8, 0x2f, 0x21, 0x26, 0x3a, 0xcf, 0xb7); +MAKE_GUID(GUID_Wheel, 0x646979cf, 0x6b71, 0x4e96, 0x8d, 0xf9, 0x59, 0xe3, 0x98, 0xd7, 0x42, 0x0c); + +/* + * The following GUIDs are observed, but the exact meanings aren't known, so + * for now we document them but don't use them anywhere. + * + * MAKE_GUID(GUID_GamepadEmu, 0xe2e5f1bc, 0xa6e6, 0x41a2, 0x8f, 0x43, 0x33, 0xcf, 0xa2, 0x51, 0x09, 0x81); + * MAKE_GUID(GUID_IAudioOnly, 0x92844cd1, 0xf7c8, 0x49ef, 0x97, 0x77, 0x46, 0x7d, 0xa7, 0x08, 0xad, 0x10); + * MAKE_GUID(GUID_IControllerProfileModeState, 0xf758dc66, 0x022c, 0x48b8, 0xa4, 0xf6, 0x45, 0x7b, 0xa8, 0x0e, 0x2a, 0x5b); + * MAKE_GUID(GUID_ICustomAudio, 0x63fd9cc9, 0x94ee, 0x4b5d, 0x9c, 0x4d, 0x8b, 0x86, 0x4c, 0x14, 0x9c, 0xac); + * MAKE_GUID(GUID_IExtendedDeviceFlags, 0x34ad9b1e, 0x36ad, 0x4fb5, 0x8a, 0xc7, 0x17, 0x23, 0x4c, 0x9f, 0x54, 0x6f); + * MAKE_GUID(GUID_IProgrammableGamepad, 0x31c1034d, 0xb5b7, 0x4551, 0x98, 0x13, 0x87, 0x69, 0xd4, 0xa0, 0xe4, 0xf9); + * MAKE_GUID(GUID_IVirtualDevice, 0xdfd26825, 0x110a, 0x4e94, 0xb9, 0x37, 0xb2, 0x7c, 0xe4, 0x7b, 0x25, 0x40); + * MAKE_GUID(GUID_OnlineDevAuth, 0x632b1fd1, 0xa3e9, 0x44f9, 0x84, 0x20, 0x5c, 0xe3, 0x44, 0xa0, 0x64, 0x04); + */ + +static const int GIP_DataClassMtu[8] = { 64, 64, 64, 2048, 0, 0, 0, 0 }; + +typedef struct GIP_Quirks +{ + Uint16 vendor_id; + Uint16 product_id; + Uint8 attachment_index; + Uint32 added_features; + Uint32 filtered_features; + Uint32 quirks; + Uint32 extra_in_system[8]; + Uint32 extra_out_system[8]; + GIP_AttachmentType device_type; + Uint8 extra_buttons; + Uint8 extra_axes; +} GIP_Quirks; + +static const GIP_Quirks quirks[] = { + { USB_VENDOR_MICROSOFT, USB_PRODUCT_XBOX_ONE_ELITE_SERIES_1, 0, + .added_features = GIP_FEATURE_ELITE_BUTTONS, + .filtered_features = GIP_FEATURE_CONSOLE_FUNCTION_MAP }, + + { USB_VENDOR_MICROSOFT, USB_PRODUCT_XBOX_ONE_ELITE_SERIES_2, 0, + .added_features = GIP_FEATURE_ELITE_BUTTONS | GIP_FEATURE_DYNAMIC_LATENCY_INPUT | GIP_FEATURE_CONSOLE_FUNCTION_MAP | GIP_FEATURE_GUIDE_COLOR | GIP_FEATURE_EXTENDED_SET_DEVICE_STATE, + .extra_in_system = { 1 << GIP_CMD_FIRMWARE }, + .extra_out_system = { 1 << GIP_CMD_FIRMWARE } }, + + { USB_VENDOR_MICROSOFT, USB_PRODUCT_XBOX_SERIES_X, 0, + .added_features = GIP_FEATURE_DYNAMIC_LATENCY_INPUT }, + + { USB_VENDOR_PDP, USB_PRODUCT_PDP_ROCK_CANDY, 0, + .quirks = GIP_QUIRK_NO_HELLO }, + + { USB_VENDOR_POWERA, USB_PRODUCT_BDA_XB1_FIGHTPAD, 0, + .filtered_features = GIP_FEATURE_MOTOR_CONTROL }, + + { USB_VENDOR_POWERA, USB_PRODUCT_BDA_XB1_CLASSIC, 0, + .quirks = GIP_QUIRK_NO_IMPULSE_VIBRATION }, + + { USB_VENDOR_POWERA, USB_PRODUCT_BDA_XB1_SPECTRA_PRO, 0, + .quirks = GIP_QUIRK_NO_IMPULSE_VIBRATION }, + + { USB_VENDOR_RAZER, USB_PRODUCT_RAZER_ATROX, 0, + .filtered_features = GIP_FEATURE_MOTOR_CONTROL, + .device_type = GIP_TYPE_ARCADE_STICK }, + + { USB_VENDOR_THRUSTMASTER, USB_PRODUCT_THRUSTMASTER_T_FLIGHT_HOTAS_ONE, 0, + .filtered_features = GIP_FEATURE_MOTOR_CONTROL, + .device_type = GIP_TYPE_FLIGHT_STICK, + .extra_buttons = 5, + .extra_axes = 3 }, + + {0}, +}; + +typedef struct GIP_Header +{ + Uint8 message_type; + Uint8 flags; + Uint8 sequence_id; + Uint64 length; +} GIP_Header; + +typedef struct GIP_AudioFormat +{ + Uint8 inbound; + Uint8 outbound; +} GIP_AudioFormat; + +typedef struct GIP_DeviceMetadata +{ + Uint8 num_audio_formats; + Uint8 num_preferred_types; + Uint8 num_supported_interfaces; + Uint8 hid_descriptor_size; + + Uint32 in_system_messages[8]; + Uint32 out_system_messages[8]; + + GIP_AudioFormat *audio_formats; + char **preferred_types; + GUID *supported_interfaces; + Uint8 *hid_descriptor; + + GIP_AttachmentType device_type; +} GIP_DeviceMetadata; + +typedef struct GIP_MessageMetadata +{ + Uint8 type; + Uint16 length; + Uint16 data_type; + Uint32 flags; + Uint16 period; + Uint16 persistence_timeout; +} GIP_MessageMetadata; + +typedef struct GIP_Metadata +{ + Uint16 version_major; + Uint16 version_minor; + + GIP_DeviceMetadata device; + + Uint8 num_messages; + GIP_MessageMetadata *message_metadata; +} GIP_Metadata; + +struct GIP_Device; +typedef struct GIP_Attachment +{ + struct GIP_Device *device; + Uint8 attachment_index; + SDL_JoystickID joystick; + SDL_KeyboardID keyboard; + + Uint8 fragment_message; + Uint16 total_length; + Uint8 *fragment_data; + Uint32 fragment_offset; + Uint64 fragment_timer; + int fragment_retries; + + Uint16 firmware_major_version; + Uint16 firmware_minor_version; + + GIP_MetadataStatus got_metadata; + Uint64 metadata_next; + int metadata_retries; + GIP_Metadata metadata; + + Uint8 seq_system; + Uint8 seq_security; + Uint8 seq_extended; + Uint8 seq_audio; + Uint8 seq_vendor; + + int device_state; + + GIP_RumbleState rumble_state; + Uint64 rumble_time; + bool rumble_pending; + Uint8 left_impulse_level; + Uint8 right_impulse_level; + Uint8 left_vibration_level; + Uint8 right_vibration_level; + + Uint8 last_input[64]; + + Uint8 last_modifiers; + bool capslock; + SDL_Keycode last_key; + Uint32 altcode; + int altcode_digit; + + GIP_AttachmentType attachment_type; + GIP_EliteButtonFormat xbe_format; + Uint32 features; + Uint32 quirks; + Uint8 share_button_idx; + Uint8 paddle_idx; + + Uint8 extra_button_idx; + int extra_buttons; + int extra_axes; +} GIP_Attachment; + +typedef struct GIP_Device +{ + SDL_HIDAPI_Device *device; + + Uint64 hello_deadline; + bool got_hello; + bool reset_for_metadata; + int timeout; + + GIP_Attachment *attachments[MAX_ATTACHMENTS]; +} GIP_Device; + +typedef struct GIP_HelloDevice +{ + Uint64 device_id; + Uint16 vendor_id; + Uint16 product_id; + Uint16 firmware_major_version; + Uint16 firmware_minor_version; + Uint16 firmware_build_version; + Uint16 firmware_revision; + Uint8 hardware_major_version; + Uint8 hardware_minor_version; + Uint8 rf_proto_major_version; + Uint8 rf_proto_minor_version; + Uint8 security_major_version; + Uint8 security_minor_version; + Uint8 gip_major_version; + Uint8 gip_minor_version; +} GIP_HelloDevice; + +typedef struct GIP_Status +{ + int power_level; + int charge; + int battery_type; + int battery_level; +} GIP_Status; + +typedef struct GIP_StatusEvent +{ + Uint16 event_type; + Uint32 fault_tag; + Uint32 fault_address; +} GIP_StatusEvent; + +typedef struct GIP_ExtendedStatus +{ + GIP_Status base; + bool device_active; + + int num_events; + GIP_StatusEvent events[5]; +} GIP_ExtendedStatus; + +typedef struct GIP_DirectMotor +{ + Uint8 motor_bitmap; + Uint8 left_impulse_level; + Uint8 right_impulse_level; + Uint8 left_vibration_level; + Uint8 right_vibration_level; + Uint8 duration; + Uint8 delay; + Uint8 repeat; +} GIP_DirectMotor; + +typedef struct GIP_InitialReportsRequest +{ + Uint8 type; + Uint8 data[2]; +} GIP_InitialReportsRequest; + +static bool GIP_SetMetadataDefaults(GIP_Attachment *attachment); + +static int GIP_DecodeLength(Uint64 *length, const Uint8 *bytes, int num_bytes) +{ + *length = 0; + int offset; + + for (offset = 0; offset < num_bytes; offset++) { + Uint8 byte = bytes[offset]; + *length |= (byte & 0x7full) << (offset * 7); + if (!(byte & 0x80)) { + offset++; + break; + } + } + return offset; +} + +static int GIP_EncodeLength(Uint64 length, Uint8 *bytes, int num_bytes) +{ + int offset; + + for (offset = 0; offset < num_bytes; offset++) { + Uint8 byte = length & 0x7f; + length >>= 7; + if (length) { + byte |= 0x80; + } + bytes[offset] = byte; + if (!length) { + offset++; + break; + } + } + return offset; +} + +static bool GIP_SupportsSystemMessage(GIP_Attachment *attachment, Uint8 command, bool upstream) +{ + if (upstream) { + return attachment->metadata.device.in_system_messages[command >> 5] & (1u << command); + } else { + return attachment->metadata.device.out_system_messages[command >> 5] & (1u << command); + } +} + +static bool GIP_SupportsVendorMessage(GIP_Attachment *attachment, Uint8 command, bool upstream) +{ + size_t i; + for (i = 0; i < attachment->metadata.num_messages; i++) { + GIP_MessageMetadata *metadata = &attachment->metadata.message_metadata[i]; + if (metadata->type != command) { + continue; + } + if (metadata->flags & GIP_MESSAGE_FLAG_DS_REQUEST_RESPONSE) { + return true; + } + if (upstream) { + return metadata->flags & GIP_MESSAGE_FLAG_UPSTREAM; + } else { + return metadata->flags & GIP_MESSAGE_FLAG_DOWNSTREAM; + } + } + return false; +} + +static Uint8 GIP_SequenceNext(GIP_Attachment *attachment, Uint8 command, bool system) +{ + Uint8 seq; + + if (system) { + switch (command) { + case GIP_CMD_SECURITY: + seq = attachment->seq_security++; + if (!seq) { + seq = attachment->seq_security++; + } + break; + case GIP_CMD_EXTENDED: + seq = attachment->seq_extended++; + if (!seq) { + seq = attachment->seq_extended++; + } + break; + case GIP_AUDIO_DATA: + seq = attachment->seq_audio++; + if (!seq) { + seq = attachment->seq_audio++; + } + break; + default: + seq = attachment->seq_system++; + if (!seq) { + seq = attachment->seq_system++; + } + break; + } + } else { + if (command == GIP_CMD_DIRECT_MOTOR) { + // The motor sequence number is optional and always works with 0 + return 0; + } + + seq = attachment->seq_vendor++; + if (!seq) { + seq = attachment->seq_vendor++; + } + } + return seq; +} + +static void GIP_HandleQuirks(GIP_Attachment *attachment) +{ + size_t i, j; + for (i = 0; quirks[i].vendor_id; i++) { + if (quirks[i].vendor_id != attachment->device->device->vendor_id) { + continue; + } + if (quirks[i].product_id != attachment->device->device->product_id) { + continue; + } + if (quirks[i].attachment_index != attachment->attachment_index) { + continue; + } + attachment->features |= quirks[i].added_features; + attachment->features &= ~quirks[i].filtered_features; + attachment->quirks = quirks[i].quirks; + attachment->attachment_type = quirks[i].device_type; + + for (j = 0; j < 8; ++j) { + attachment->metadata.device.in_system_messages[j] |= quirks[i].extra_in_system[j]; + attachment->metadata.device.out_system_messages[j] |= quirks[i].extra_out_system[j]; + } + + attachment->extra_buttons = quirks[i].extra_buttons; + attachment->extra_axes = quirks[i].extra_axes; + break; + } +} + +static bool GIP_SendRawMessage( + GIP_Device *device, + Uint8 message_type, + Uint8 flags, + Uint8 seq, + const Uint8 *bytes, + int num_bytes, + bool async, + SDL_HIDAPI_RumbleSentCallback callback, + void *userdata) +{ + Uint8 buffer[2054] = { message_type, flags, seq }; + int offset = 3; + + if (num_bytes < 0) { + SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, "GIP: Invalid message length %d", num_bytes); + return false; + } + + if (num_bytes > GIP_DataClassMtu[message_type >> GIP_DATA_CLASS_SHIFT]) { + SDL_LogError(SDL_LOG_CATEGORY_INPUT, + "Attempted to send a message that requires fragmenting, which is not yet supported."); + return false; + } + + offset += GIP_EncodeLength(num_bytes, &buffer[offset], sizeof(buffer) - offset); + + if (num_bytes > 0) { + SDL_memcpy(&buffer[offset], bytes, num_bytes); + } + num_bytes += offset; +#ifdef DEBUG_XBOX_PROTOCOL + HIDAPI_DumpPacket("GIP sending message: size = %d", buffer, num_bytes); +#endif + + if (async) { + if (!SDL_HIDAPI_LockRumble()) { + return false; + } + + return SDL_HIDAPI_SendRumbleWithCallbackAndUnlock(device->device, buffer, num_bytes, callback, userdata) == num_bytes; + } else { + return SDL_hid_write(device->device->dev, buffer, num_bytes) == num_bytes; + } +} + +static bool GIP_SendSystemMessage( + GIP_Attachment *attachment, + Uint8 message_type, + Uint8 flags, + const Uint8 *bytes, + int num_bytes) +{ + return GIP_SendRawMessage(attachment->device, + message_type, + GIP_FLAG_SYSTEM | attachment->attachment_index | flags, + GIP_SequenceNext(attachment, message_type, true), + bytes, + num_bytes, + false, + NULL, + NULL); +} + +static bool GIP_SendVendorMessage( + GIP_Attachment *attachment, + Uint8 message_type, + Uint8 flags, + const Uint8 *bytes, + int num_bytes) +{ + return GIP_SendRawMessage(attachment->device, + message_type, + flags, + GIP_SequenceNext(attachment, message_type, false), + bytes, + num_bytes, + true, + NULL, + NULL); +} + +static bool GIP_AttachmentIsController(GIP_Attachment *attachment) +{ + return attachment->attachment_type != GIP_TYPE_CHATPAD && + attachment->attachment_type != GIP_TYPE_HEADSET; +} + +static void GIP_MetadataFree(GIP_Metadata *metadata) +{ + if (metadata->device.audio_formats) { + SDL_free(metadata->device.audio_formats); + } + if (metadata->device.preferred_types) { + int i; + for (i = 0; i < metadata->device.num_preferred_types; i++) { + if (metadata->device.preferred_types[i]) { + SDL_free(metadata->device.preferred_types[i]); + } + } + SDL_free(metadata->device.preferred_types); + } + if (metadata->device.supported_interfaces) { + SDL_free(metadata->device.supported_interfaces); + } + if (metadata->device.hid_descriptor) { + SDL_free(metadata->device.hid_descriptor); + } + + if (metadata->message_metadata) { + SDL_free(metadata->message_metadata); + } + SDL_memset(metadata, 0, sizeof(*metadata)); +} + +static bool GIP_ParseDeviceMetadata(GIP_Metadata *metadata, const Uint8 *bytes, int num_bytes, int *offset) +{ + GIP_DeviceMetadata *device = &metadata->device; + int buffer_offset; + int count; + int length; + int i; + + bytes = &bytes[*offset]; + num_bytes -= *offset; + if (num_bytes < 16) { + return false; + } + + length = bytes[0]; + length |= bytes[1] << 8; + if (num_bytes < length) { + return false; + } + + /* Skip supported firmware versions for now */ + + buffer_offset = bytes[4]; + buffer_offset |= bytes[5] << 8; + if (buffer_offset >= length) { + return false; + } + if (buffer_offset > 0) { + device->num_audio_formats = bytes[buffer_offset]; + if (buffer_offset + device->num_audio_formats + 1 > length) { + return false; + } + device->audio_formats = SDL_malloc(device->num_audio_formats); + SDL_memcpy(device->audio_formats, &bytes[buffer_offset + 1], device->num_audio_formats); + } + + buffer_offset = bytes[6]; + buffer_offset |= bytes[7] << 8; + if (buffer_offset >= length) { + return false; + } + if (buffer_offset > 0) { + count = bytes[buffer_offset]; + if (buffer_offset + count + 1 > length) { + return false; + } + + for (i = 0; i < count; i++) { + Uint8 message = bytes[buffer_offset + 1 + i]; +#ifdef DEBUG_XBOX_PROTOCOL + SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, + "GIP: Supported upstream system message %02x", + message); +#endif + device->in_system_messages[message >> 5] |= 1u << (message & 0x1F); + } + } + + buffer_offset = bytes[8]; + buffer_offset |= bytes[9] << 8; + if (buffer_offset >= length) { + return false; + } + if (buffer_offset > 0) { + count = bytes[buffer_offset]; + if (buffer_offset + count + 1 > length) { + return false; + } + + for (i = 0; i < count; i++) { + Uint8 message = bytes[buffer_offset + 1 + i]; +#ifdef DEBUG_XBOX_PROTOCOL + SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, + "GIP: Supported downstream system message %02x", + message); +#endif + device->out_system_messages[message >> 5] |= 1u << (message & 0x1F); + } + } + + buffer_offset = bytes[10]; + buffer_offset |= bytes[11] << 8; + if (buffer_offset >= length) { + return false; + } + if (buffer_offset > 0) { + device->num_preferred_types = bytes[buffer_offset]; + device->preferred_types = SDL_calloc(device->num_preferred_types, sizeof(char *)); + buffer_offset++; + for (i = 0; i < device->num_preferred_types; i++) { + if (buffer_offset + 2 >= length) { + return false; + } + + count = bytes[buffer_offset]; + count |= bytes[buffer_offset]; + buffer_offset += 2; + if (buffer_offset + count > length) { + return false; + } + + device->preferred_types[i] = SDL_calloc(count + 1, sizeof(char)); + SDL_memcpy(device->preferred_types[i], &bytes[buffer_offset], count); + buffer_offset += count; + } + } + + buffer_offset = bytes[12]; + buffer_offset |= bytes[13] << 8; + if (buffer_offset >= length) { + return false; + } + if (buffer_offset > 0) { + device->num_supported_interfaces = bytes[buffer_offset]; + if (buffer_offset + 1 + (Sint32) (device->num_supported_interfaces * sizeof(GUID)) > length) { + return false; + } + device->supported_interfaces = SDL_calloc(device->num_supported_interfaces, sizeof(GUID)); + SDL_memcpy(device->supported_interfaces, + &bytes[buffer_offset + 1], + sizeof(GUID) * device->num_supported_interfaces); + } + + if (metadata->version_major > 1 || metadata->version_minor >= 1) { + /* HID descriptor support added in metadata version 1.1 */ + buffer_offset = bytes[14]; + buffer_offset |= bytes[15] << 8; + if (buffer_offset >= length) { + return false; + } + if (buffer_offset > 0) { + device->hid_descriptor_size = bytes[buffer_offset]; + if (buffer_offset + 1 + device->hid_descriptor_size > length) { + return false; + } + device->hid_descriptor = SDL_malloc(device->hid_descriptor_size); + SDL_memcpy(device->hid_descriptor, &bytes[buffer_offset + 1], device->hid_descriptor_size); +#ifdef DEBUG_XBOX_PROTOCOL + HIDAPI_DumpPacket("GIP received HID descriptor: size = %d", device->hid_descriptor, device->hid_descriptor_size); +#endif + } + } + + *offset += length; + return true; +} + +static bool GIP_ParseMessageMetadata(GIP_MessageMetadata *metadata, const Uint8 *bytes, int num_bytes, int *offset) +{ + Uint16 length; + + bytes = &bytes[*offset]; + num_bytes -= *offset; + + if (num_bytes < 2) { + return false; + } + length = bytes[0]; + length |= bytes[1] << 8; + if (num_bytes < length) { + return false; + } + + if (length < 15) { + return false; + } + + metadata->type = bytes[2]; + metadata->length = bytes[3]; + metadata->length |= bytes[4] << 8; + metadata->data_type = bytes[5]; + metadata->data_type |= bytes[6] << 8; + metadata->flags = bytes[7]; + metadata->flags |= bytes[8] << 8; + metadata->flags |= bytes[9] << 16; + metadata->flags |= bytes[10] << 24; + metadata->period = bytes[11]; + metadata->period |= bytes[12] << 8; + metadata->persistence_timeout = bytes[13]; + metadata->persistence_timeout |= bytes[14] << 8; + +#ifdef DEBUG_XBOX_PROTOCOL + SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, + "GIP: Supported vendor message type %02x of length %d, %s, %s, %s", + metadata->type, + metadata->length, + metadata->flags & GIP_MESSAGE_FLAG_UPSTREAM ? + (metadata->flags & GIP_MESSAGE_FLAG_DOWNSTREAM ? "bidirectional" : "upstream") : + metadata->flags & GIP_MESSAGE_FLAG_DOWNSTREAM ? "downstream" : + metadata->flags & GIP_MESSAGE_FLAG_DS_REQUEST_RESPONSE ? "downstream request response" : + "unknown direction", + metadata->flags & GIP_MESSAGE_FLAG_SEQUENCED ? "sequenced" : "not sequenced", + metadata->flags & GIP_MESSAGE_FLAG_RELIABLE ? "reliable" : "unreliable"); +#endif + + *offset += length; + return true; +} + +static bool GIP_ParseMetadata(GIP_Metadata *metadata, const Uint8 *bytes, int num_bytes) +{ + int header_size; + int metadata_size; + int offset = 0; + int i; + + if (num_bytes < 16) { + return false; + } + +#ifdef DEBUG_XBOX_PROTOCOL + HIDAPI_DumpPacket("GIP received metadata: size = %d", bytes, num_bytes); +#endif + + header_size = bytes[0]; + header_size |= bytes[1] << 8; + if (num_bytes < header_size || header_size < 16) { + return false; + } + metadata->version_major = bytes[2]; + metadata->version_major |= bytes[3] << 8; + metadata->version_minor = bytes[4]; + metadata->version_minor |= bytes[5] << 8; + /* Middle bytes are reserved */ + metadata_size = bytes[14]; + metadata_size |= bytes[15] << 8; + + if (num_bytes < metadata_size || metadata_size < header_size) { + return false; + } + offset = header_size; + + if (!GIP_ParseDeviceMetadata(metadata, bytes, num_bytes, &offset)) { + goto err; + } + + if (offset >= num_bytes) { + goto err; + } + metadata->num_messages = bytes[offset]; + offset++; + if (metadata->num_messages > 0) { + metadata->message_metadata = SDL_calloc(metadata->num_messages, sizeof(*metadata->message_metadata)); + for (i = 0; i < metadata->num_messages; i++) { + if (!GIP_ParseMessageMetadata(&metadata->message_metadata[i], bytes, num_bytes, &offset)) { + goto err; + } + } + } + + return true; + +err: + GIP_MetadataFree(metadata); + return false; +} + +static bool GIP_Acknowledge( + GIP_Device *device, + const GIP_Header *header, + Uint32 fragment_offset, + Uint16 bytes_remaining) +{ + Uint8 buffer[] = { + GIP_CONTROL_CODE_ACK, + header->message_type, + header->flags & GIP_FLAG_SYSTEM, + (Uint8) fragment_offset, + (Uint8) (fragment_offset >> 8), + (Uint8) (fragment_offset >> 16), + fragment_offset >> 24, + (Uint8) bytes_remaining, + bytes_remaining >> 8, + }; + + return GIP_SendRawMessage(device, + GIP_CMD_PROTO_CONTROL, + GIP_FLAG_SYSTEM | (header->flags & GIP_FLAG_ATTACHMENT_MASK), + header->sequence_id, + buffer, + sizeof(buffer), + false, + NULL, + NULL); +} + +static bool GIP_FragmentFailed(GIP_Attachment *attachment, const GIP_Header *header) +{ + attachment->fragment_retries++; + if (attachment->fragment_retries > 8) { + if (attachment->fragment_data) { + SDL_free(attachment->fragment_data); + attachment->fragment_data = NULL; + } + attachment->fragment_message = 0; + } + return GIP_Acknowledge(attachment->device, + header, + attachment->fragment_offset, + (Uint16) (attachment->total_length - attachment->fragment_offset)); +} + +static bool GIP_EnableEliteButtons(GIP_Attachment *attachment) { + if (attachment->device->device->vendor_id == USB_VENDOR_MICROSOFT) { + if (attachment->device->device->product_id == USB_PRODUCT_XBOX_ONE_ELITE_SERIES_1) { + attachment->xbe_format = GIP_BTN_FMT_XBE1; + } else if (attachment->device->device->product_id == USB_PRODUCT_XBOX_ONE_ELITE_SERIES_2) { + if (attachment->firmware_major_version == 4) { + attachment->xbe_format = GIP_BTN_FMT_XBE2_4; + } else if (attachment->firmware_major_version == 5) { + /* + * The exact range for this being necessary is unknown, but it + * starts at 5.11 and at either 5.16 or 5.17. This approach + * still works on 5.21, even if it's not necessary, so having + * a loose upper limit is fine. + */ + if (attachment->firmware_minor_version >= 11 && + attachment->firmware_minor_version < 17) + { + attachment->xbe_format = GIP_BTN_FMT_XBE2_RAW; + } else { + attachment->xbe_format = GIP_BTN_FMT_XBE2_5; + } + } + } + } + if (attachment->xbe_format == GIP_BTN_FMT_XBE2_RAW) { + /* + * The meaning of this packet is unknown and not documented, but it's + * needed for the Elite 2 controller to send raw reports + */ + static const Uint8 enable_raw_report[] = { 7, 0 }; + + return GIP_SendVendorMessage(attachment, + GIP_SL_ELITE_CONFIG, + 0, + enable_raw_report, + sizeof(enable_raw_report)); + } + + return true; +} + +static bool GIP_SendGuideButtonLED(GIP_Attachment *attachment, Uint8 pattern, Uint8 intensity) +{ + Uint8 buffer[] = { + GIP_LED_GUIDE, + pattern, + intensity, + }; + + if (!GIP_SupportsSystemMessage(attachment, GIP_CMD_LED, false)) { + return true; + } + return GIP_SendSystemMessage(attachment, GIP_CMD_LED, 0, buffer, sizeof(buffer)); +} + +static bool GIP_SendQueryFirmware(GIP_Attachment *attachment, Uint8 slot) +{ + /* The "slot" variable might not be correct; the packet format is still unclear */ + Uint8 buffer[] = { 0x1, slot, 0, 0, 0 }; + + return GIP_SendSystemMessage(attachment, GIP_CMD_FIRMWARE, 0, buffer, sizeof(buffer)); +} + +static bool GIP_SendSetDeviceState(GIP_Attachment *attachment, Uint8 state) +{ + Uint8 buffer[] = { state }; + return GIP_SendSystemMessage(attachment, + GIP_CMD_SET_DEVICE_STATE, + attachment->attachment_index, + buffer, + sizeof(buffer)); +} + +static bool GIP_SendInitSequence(GIP_Attachment *attachment) +{ + if (attachment->features & GIP_FEATURE_EXTENDED_SET_DEVICE_STATE) { + /* + * The meaning of this packet is unknown and not documented, but it's + * needed for the Elite 2 controller to start up on older firmwares + */ + static const Uint8 set_device_state[] = { GIP_STATE_UNK6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x55, 0x53, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 }; + + if (!GIP_SendSystemMessage(attachment, + GIP_CMD_SET_DEVICE_STATE, + 0, + set_device_state, + sizeof(set_device_state))) + { + return false; + } + } + if (!GIP_EnableEliteButtons(attachment)) { + return false; + } + if (!GIP_SendSetDeviceState(attachment, GIP_STATE_START)) { + return false; + } + attachment->device_state = GIP_STATE_START; + + if (!GIP_SendGuideButtonLED(attachment, GIP_LED_GUIDE_ON, 20)) { + return false; + } + + if (GIP_SupportsSystemMessage(attachment, GIP_CMD_SECURITY, false) && + !(attachment->features & GIP_FEATURE_SECURITY_OPT_OUT)) + { + /* TODO: Implement Security command property */ + Uint8 buffer[] = { 0x1, 0x0 }; + GIP_SendSystemMessage(attachment, GIP_CMD_SECURITY, 0, buffer, sizeof(buffer)); + } + + if (GIP_SupportsVendorMessage(attachment, GIP_CMD_INITIAL_REPORTS_REQUEST, false)) { + GIP_InitialReportsRequest request = { 0 }; + GIP_SendVendorMessage(attachment, GIP_CMD_INITIAL_REPORTS_REQUEST, 0, (const Uint8 *)&request, sizeof(request)); + } + + if (GIP_SupportsVendorMessage(attachment, GIP_CMD_DEVICE_CAPABILITIES, false)) { + GIP_SendVendorMessage(attachment, GIP_CMD_DEVICE_CAPABILITIES, 0, NULL, 0); + } + + if ((!attachment->attachment_index || GIP_AttachmentIsController(attachment)) && !attachment->joystick) { + return HIDAPI_JoystickConnected(attachment->device->device, &attachment->joystick); + } + if (attachment->attachment_type == GIP_TYPE_CHATPAD && !attachment->keyboard) { + attachment->keyboard = (SDL_KeyboardID)(uintptr_t) attachment; + SDL_AddKeyboard(attachment->keyboard, "Xbox One Chatpad", true); + } + return true; +} + +static bool GIP_EnsureMetadata(GIP_Attachment *attachment) +{ + switch (attachment->got_metadata) { + case GIP_METADATA_GOT: + case GIP_METADATA_FAKED: + return true; + case GIP_METADATA_NONE: + if (attachment->device->got_hello) { + attachment->device->timeout = GIP_ACME_TIMEOUT; + attachment->got_metadata = GIP_METADATA_PENDING; + attachment->metadata_next = SDL_GetTicks() + 500; + attachment->metadata_retries = 0; + return GIP_SendSystemMessage(attachment, GIP_CMD_METADATA, 0, NULL, 0); + } else { + return GIP_SetMetadataDefaults(attachment); + } + default: + return true; + } +} + +static bool GIP_SetMetadataDefaults(GIP_Attachment *attachment) +{ + if (attachment->attachment_index == 0) { + /* Some decent default settings */ + attachment->features |= GIP_FEATURE_MOTOR_CONTROL; + attachment->attachment_type = GIP_TYPE_GAMEPAD; + attachment->metadata.device.in_system_messages[0] |= (1u << GIP_CMD_GUIDE_BUTTON); + + if (SDL_IsJoystickXboxSeriesX(attachment->device->device->vendor_id, attachment->device->device->product_id)) { + attachment->features |= GIP_FEATURE_CONSOLE_FUNCTION_MAP; + } + } + + GIP_HandleQuirks(attachment); + + if (GIP_SupportsSystemMessage(attachment, GIP_CMD_FIRMWARE, false)) { + GIP_SendQueryFirmware(attachment, 2); + } + + attachment->got_metadata = GIP_METADATA_FAKED; + attachment->device->hello_deadline = 0; + if (!attachment->joystick) { + return HIDAPI_JoystickConnected(attachment->device->device, &attachment->joystick); + } + return true; +} + +static bool GIP_HandleCommandProtocolControl( + GIP_Attachment *attachment, + const GIP_Header *header, + const Uint8 *bytes, + int num_bytes) +{ + // TODO + SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "GIP: Unimplemented Protocol Control message"); + return false; +} + +static bool GIP_HandleCommandHelloDevice( + GIP_Attachment *attachment, + const GIP_Header *header, + const Uint8 *bytes, + int num_bytes) +{ + GIP_HelloDevice message = {0}; + + if (num_bytes != 28) { + return false; + } + + message.device_id = (Uint64) bytes[0]; + message.device_id |= (Uint64) bytes[1] << 8; + message.device_id |= (Uint64) bytes[2] << 16; + message.device_id |= (Uint64) bytes[3] << 24; + message.device_id |= (Uint64) bytes[4] << 32; + message.device_id |= (Uint64) bytes[5] << 40; + message.device_id |= (Uint64) bytes[6] << 48; + message.device_id |= (Uint64) bytes[7] << 56; + + message.vendor_id = bytes[8]; + message.vendor_id |= bytes[9] << 8; + + message.product_id = bytes[10]; + message.product_id |= bytes[11] << 8; + + message.firmware_major_version = bytes[12]; + message.firmware_major_version |= bytes[13] << 8; + + message.firmware_minor_version = bytes[14]; + message.firmware_minor_version |= bytes[15] << 8; + + message.firmware_build_version = bytes[16]; + message.firmware_build_version |= bytes[17] << 8; + + message.firmware_revision = bytes[18]; + message.firmware_revision |= bytes[19] << 8; + + message.hardware_major_version = bytes[20]; + message.hardware_minor_version = bytes[21]; + + message.rf_proto_major_version = bytes[22]; + message.rf_proto_minor_version = bytes[23]; + + message.security_major_version = bytes[24]; + message.security_minor_version = bytes[25]; + + message.gip_major_version = bytes[26]; + message.gip_minor_version = bytes[27]; + + SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, + "GIP: Device hello from %" SDL_PRIx64 " (%04x:%04x)", + message.device_id, message.vendor_id, message.product_id); + SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, + "GIP: Firmware version %d.%d.%d rev %d", + message.firmware_major_version, + message.firmware_minor_version, + message.firmware_build_version, + message.firmware_revision); + + /* + * The GIP spec specifies that the host should reject the device if any of these are wrong. + * I don't know if Windows or an Xbox do, however, so let's just log warnings instead. + */ + if (message.rf_proto_major_version != 1 && message.rf_proto_minor_version != 0) { + SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, + "GIP: Invalid RF protocol version %d.%d, expected 1.0", + message.rf_proto_major_version, message.rf_proto_minor_version); + } + + if (message.security_major_version != 1 && message.security_minor_version != 0) { + SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, + "GIP: Invalid security protocol version %d.%d, expected 1.0", + message.security_major_version, message.security_minor_version); + } + + if (message.gip_major_version != 1 && message.gip_minor_version != 0) { + SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, + "GIP: Invalid GIP version %d.%d, expected 1.0", + message.gip_major_version, message.gip_minor_version); + } + + if (header->flags & GIP_FLAG_ATTACHMENT_MASK) { + return GIP_SendSystemMessage(attachment, GIP_CMD_METADATA, 0, NULL, 0); + } else { + attachment->firmware_major_version = message.firmware_major_version; + attachment->firmware_minor_version = message.firmware_minor_version; + + if (attachment->attachment_index == 0) { + attachment->device->hello_deadline = 0; + attachment->device->got_hello = true; + } + if (attachment->got_metadata == GIP_METADATA_FAKED) { + attachment->got_metadata = GIP_METADATA_NONE; + } + GIP_EnsureMetadata(attachment); + } + return true; +} + +static bool GIP_HandleCommandStatusDevice( + GIP_Attachment *attachment, + const GIP_Header *header, + const Uint8 *bytes, + int num_bytes) +{ + GIP_ExtendedStatus status; + SDL_Joystick *joystick = NULL; + SDL_PowerState power_state; + int power_percent = 0; + int i; + + if (num_bytes < 1) { + return false; + } + SDL_zero(status); + status.base.battery_level = bytes[0] & 3; + status.base.battery_type = (bytes[0] >> 2) & 3; + status.base.charge = (bytes[0] >> 4) & 3; + status.base.power_level = (bytes[0] >> 6) & 3; + + if (attachment->joystick) { + joystick = SDL_GetJoystickFromID(attachment->joystick); + } + if (joystick) { + switch (status.base.battery_level) { + case GIP_BATTERY_CRITICAL: + power_percent = 1; + break; + case GIP_BATTERY_LOW: + power_percent = 25; + break; + case GIP_BATTERY_MEDIUM: + power_percent = 50; + break; + case GIP_BATTERY_FULL: + power_percent = 100; + break; + } + switch (status.base.charge) { + case GIP_CHARGING: + if (status.base.battery_level == GIP_BATTERY_FULL) { + power_state = SDL_POWERSTATE_CHARGED; + } else { + power_state = SDL_POWERSTATE_CHARGING; + } + break; + case GIP_NOT_CHARGING: + power_state = SDL_POWERSTATE_ON_BATTERY; + break; + case GIP_CHARGE_ERROR: + default: + power_state = SDL_POWERSTATE_UNKNOWN; + break; + } + + switch (status.base.battery_type) { + case GIP_BATTERY_ABSENT: + power_state = SDL_POWERSTATE_NO_BATTERY; + break; + case GIP_BATTERY_STANDARD: + case GIP_BATTERY_RECHARGEABLE: + break; + default: + power_state = SDL_POWERSTATE_UNKNOWN; + break; + } + + SDL_SendJoystickPowerInfo(joystick, power_state, power_percent); + } + + if (num_bytes >= 4) { + status.device_active = bytes[1] & 1; + if (bytes[1] & 2) { + /* Events present */ + if (num_bytes < 5) { + return false; + } + status.num_events = bytes[4]; + if (status.num_events > 5) { + SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, + "GIP: Device reported too many events, %d > 5", + status.num_events); + return false; + } + if (5 + status.num_events * 10 > num_bytes) { + return false; + } + for (i = 0; i < status.num_events; i++) { + status.events[i].event_type = bytes[i * 10 + 5]; + status.events[i].event_type |= bytes[i * 10 + 6] << 8; + status.events[i].fault_tag = bytes[i * 10 + 7]; + status.events[i].fault_tag |= bytes[i * 10 + 8] << 8; + status.events[i].fault_tag |= bytes[i * 10 + 9] << 16; + status.events[i].fault_tag |= bytes[i * 10 + 10] << 24; + status.events[i].fault_tag = bytes[i * 10 + 11]; + status.events[i].fault_tag |= bytes[i * 10 + 12] << 8; + status.events[i].fault_tag |= bytes[i * 10 + 13] << 16; + status.events[i].fault_tag |= bytes[i * 10 + 14] << 24; + } + } + } + + GIP_EnsureMetadata(attachment); + return true; +} + +static bool GIP_HandleCommandMetadataRespose( + GIP_Attachment *attachment, + const GIP_Header *header, + const Uint8 *bytes, + int num_bytes) +{ + GIP_Metadata metadata = {0}; + const GUID *expected_guid = NULL; + bool found_expected_guid; + bool found_controller_guid = false; + int i; + + if (!GIP_ParseMetadata(&metadata, bytes, num_bytes)) { + return false; + } + + if (attachment->got_metadata == GIP_METADATA_GOT) { + GIP_MetadataFree(&attachment->metadata); + } + attachment->metadata = metadata; + attachment->got_metadata = GIP_METADATA_GOT; + attachment->features = 0; + + attachment->attachment_type = GIP_TYPE_UNKNOWN; +#ifdef DEBUG_XBOX_PROTOCOL + for (i = 0; i < metadata.device.num_preferred_types; i++) { + const char *type = metadata.device.preferred_types[i]; + SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "GIP: Device preferred type: %s", type); + } +#endif + for (i = 0; i < metadata.device.num_preferred_types; i++) { + const char *type = metadata.device.preferred_types[i]; + if (SDL_strcmp(type, "Windows.Xbox.Input.Gamepad") == 0) { + attachment->attachment_type = GIP_TYPE_GAMEPAD; + expected_guid = &GUID_IGamepad; + break; + } + if (SDL_strcmp(type, "Microsoft.Xbox.Input.ArcadeStick") == 0) { + attachment->attachment_type = GIP_TYPE_ARCADE_STICK; + expected_guid = &GUID_ArcadeStick; + break; + } + if (SDL_strcmp(type, "Windows.Xbox.Input.ArcadeStick") == 0) { + attachment->attachment_type = GIP_TYPE_ARCADE_STICK; + expected_guid = &GUID_ArcadeStick; + break; + } + if (SDL_strcmp(type, "Microsoft.Xbox.Input.FlightStick") == 0) { + attachment->attachment_type = GIP_TYPE_FLIGHT_STICK; + expected_guid = &GUID_FlightStick; + break; + } + if (SDL_strcmp(type, "Windows.Xbox.Input.FlightStick") == 0) { + attachment->attachment_type = GIP_TYPE_FLIGHT_STICK; + expected_guid = &GUID_FlightStick; + break; + } + if (SDL_strcmp(type, "Microsoft.Xbox.Input.Wheel") == 0) { + attachment->attachment_type = GIP_TYPE_WHEEL; + expected_guid = &GUID_Wheel; + break; + } + if (SDL_strcmp(type, "Windows.Xbox.Input.Wheel") == 0) { + attachment->attachment_type = GIP_TYPE_WHEEL; + expected_guid = &GUID_Wheel; + break; + } + if (SDL_strcmp(type, "Windows.Xbox.Input.NavigationController") == 0) { + attachment->attachment_type = GIP_TYPE_NAVIGATION_CONTROLLER; + expected_guid = &GUID_NavigationController; + break; + } + if (SDL_strcmp(type, "Windows.Xbox.Input.Chatpad") == 0) { + attachment->attachment_type = GIP_TYPE_CHATPAD; + break; + } + if (SDL_strcmp(type, "Windows.Xbox.Input.Headset") == 0) { + attachment->attachment_type = GIP_TYPE_HEADSET; + expected_guid = &GUID_IHeadset; + break; + } + } + + found_expected_guid = !expected_guid; + for (i = 0; i < metadata.device.num_supported_interfaces; i++) { + const GUID* guid = &metadata.device.supported_interfaces[i]; +#ifdef DEBUG_XBOX_PROTOCOL + SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, + "GIP: Supported interface: %08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x", + guid->a, guid->b, guid->c, guid->d[0], guid->d[1], + guid->d[2], guid->d[3], guid->d[4], guid->d[5], guid->d[6], guid->d[7]); +#endif + if (expected_guid && SDL_memcmp(expected_guid, guid, sizeof(GUID)) == 0) { + found_expected_guid = true; + } + if (SDL_memcmp(&GUID_IController, guid, sizeof(GUID)) == 0) { + found_controller_guid = true; + continue; + } + if (SDL_memcmp(&GUID_IDevAuthPCOptOut, guid, sizeof(GUID)) == 0) { + attachment->features |= GIP_FEATURE_SECURITY_OPT_OUT; + continue; + } + if (SDL_memcmp(&GUID_IConsoleFunctionMap_InputReport, guid, sizeof(GUID)) == 0) { + attachment->features |= GIP_FEATURE_CONSOLE_FUNCTION_MAP; + continue; + } + if (SDL_memcmp(&GUID_IConsoleFunctionMap_OverflowInputReport, guid, sizeof(GUID)) == 0) { + attachment->features |= GIP_FEATURE_CONSOLE_FUNCTION_MAP_OVERFLOW; + continue; + } + if (SDL_memcmp(&GUID_IEliteButtons, guid, sizeof(GUID)) == 0) { + attachment->features |= GIP_FEATURE_ELITE_BUTTONS; + continue; + } + if (SDL_memcmp(&GUID_DynamicLatencyInput, guid, sizeof(GUID)) == 0) { + attachment->features |= GIP_FEATURE_DYNAMIC_LATENCY_INPUT; + continue; + } + } + + for (i = 0; i < metadata.num_messages; i++) { + GIP_MessageMetadata *message = &metadata.message_metadata[i]; + if (message->type == GIP_CMD_DIRECT_MOTOR && message->length >= 9 && + (message->flags & GIP_MESSAGE_FLAG_DOWNSTREAM)) { + attachment->features |= GIP_FEATURE_MOTOR_CONTROL; + } + } + + if (!found_expected_guid || (GIP_AttachmentIsController(attachment) && !found_controller_guid)) { + SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, + "GIP: Controller was missing expected GUID. This controller probably won't work on an actual Xbox."); + } + + if ((attachment->features & GIP_FEATURE_GUIDE_COLOR) && + !GIP_SupportsVendorMessage(attachment, GIP_CMD_GUIDE_COLOR, false)) + { + attachment->features &= ~GIP_FEATURE_GUIDE_COLOR; + } + + GIP_HandleQuirks(attachment); + + return GIP_SendInitSequence(attachment); +} + +static bool GIP_HandleCommandSecurity( + GIP_Attachment *attachment, + const GIP_Header *header, + const Uint8 *bytes, + int num_bytes) +{ + // TODO + SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "GIP: Unimplemented Security message"); + return false; +} + +static bool GIP_HandleCommandGuideButtonStatus( + GIP_Attachment *attachment, + const GIP_Header *header, + const Uint8 *bytes, + int num_bytes) +{ + Uint64 timestamp = SDL_GetTicksNS(); + SDL_Joystick *joystick = NULL; + + if (attachment->device->device->num_joysticks < 1) { + return true; + } + + joystick = SDL_GetJoystickFromID(attachment->joystick); + if (!joystick) { + return false; + } + if (bytes[1] == VK_LWIN) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, (bytes[0] & 0x01) != 0); + } + + return true; +} + +static bool GIP_HandleCommandAudioControl( + GIP_Attachment *attachment, + const GIP_Header *header, + const Uint8 *bytes, + int num_bytes) +{ + // TODO + SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "GIP: Unimplemented Audio Control message"); + return false; +} + +static bool GIP_HandleCommandFirmware( + GIP_Attachment *attachment, + const GIP_Header *header, + const Uint8 *bytes, + int num_bytes) +{ + if (num_bytes < 1) { + return false; + } + if (bytes[0] == 1) { + Uint16 major, minor, build, rev; + + if (num_bytes < 14) { + SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "GIP: Discarding too-short firmware message"); + + return false; + } + major = bytes[6]; + major |= bytes[7] << 8; + minor = bytes[8]; + minor |= bytes[9] << 8; + build = bytes[10]; + build |= bytes[11] << 8; + rev = bytes[12]; + rev |= bytes[13] << 8; + + SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "GIP: Firmware version: %d.%d.%d rev %d", major, minor, build, rev); + + attachment->firmware_major_version = major; + attachment->firmware_minor_version = minor; + + if (attachment->device->device->vendor_id == USB_VENDOR_MICROSOFT && + attachment->device->device->product_id == USB_PRODUCT_XBOX_ONE_ELITE_SERIES_2) + { + return GIP_EnableEliteButtons(attachment); + } + return true; + } else { + SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "GIP: Unimplemented Firmware message"); + + return false; + } +} + +static bool GIP_HandleCommandRawReport( + GIP_Attachment *attachment, + const GIP_Header *header, + const Uint8 *bytes, + int num_bytes) +{ + Uint64 timestamp = SDL_GetTicksNS(); + SDL_Joystick *joystick = NULL; + + if (attachment->device->device->num_joysticks < 1) { + return true; + } + + joystick = SDL_GetJoystickFromID(attachment->joystick); + if (!joystick) { + return true; + } + + if (num_bytes < 17) { + SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "GIP: Discarding too-short raw report"); + return false; + } + + if ((attachment->features & GIP_FEATURE_ELITE_BUTTONS) && attachment->xbe_format == GIP_BTN_FMT_XBE2_RAW) { + if (bytes[15] & 3) { + SDL_SendJoystickButton(timestamp, + joystick, + attachment->paddle_idx, + 0); + SDL_SendJoystickButton(timestamp, + joystick, + attachment->paddle_idx + 1, + 0); + SDL_SendJoystickButton(timestamp, + joystick, + attachment->paddle_idx + 2, + 0); + SDL_SendJoystickButton(timestamp, + joystick, + attachment->paddle_idx + 3, + 0); + } else { + SDL_SendJoystickButton(timestamp, + joystick, + attachment->paddle_idx, + (bytes[GIP_BTN_OFFSET_XBE2] & 0x01) != 0); + SDL_SendJoystickButton(timestamp, + joystick, + attachment->paddle_idx + 1, + (bytes[GIP_BTN_OFFSET_XBE2] & 0x02) != 0); + SDL_SendJoystickButton(timestamp, + joystick, + attachment->paddle_idx + 2, + (bytes[GIP_BTN_OFFSET_XBE2] & 0x04) != 0); + SDL_SendJoystickButton(timestamp, + joystick, + attachment->paddle_idx + 3, + (bytes[GIP_BTN_OFFSET_XBE2] & 0x08) != 0); + } + } + return true; +} + +static bool GIP_HandleCommandHidReport( + GIP_Attachment *attachment, + const GIP_Header *header, + const Uint8 *bytes, + int num_bytes) +{ + Uint64 timestamp = SDL_GetTicksNS(); + // SDL doesn't have HID descriptor parsing, so we have to hardcode for the Chatpad descriptor instead. + // I don't know of any other devices that emit HID reports, so this should be safe. + if (attachment->attachment_type != GIP_TYPE_CHATPAD || !attachment->keyboard || num_bytes != 8) { + SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "GIP: Unimplemented HID Report message"); + return false; + } + + Uint8 modifiers = bytes[0]; + Uint8 changed_modifiers = modifiers ^ attachment->last_modifiers; + if (changed_modifiers & 0x02) { + if (modifiers & 0x02) { + SDL_SendKeyboardKey(timestamp, attachment->keyboard, 0, SDL_SCANCODE_LSHIFT, true); + } else { + SDL_SendKeyboardKey(timestamp, attachment->keyboard, 0, SDL_SCANCODE_LSHIFT, false); + } + } + // The chatpad has several non-ASCII characters that it sends as Alt codes + if (changed_modifiers & 0x04) { + if (modifiers & 0x04) { + attachment->altcode_digit = 0; + attachment->altcode = 0; + } else { + if (attachment->altcode_digit == 4) { + char utf8[4] = {0}; + // Some Alt codes don't match their Unicode codepoint for some reason + switch (attachment->altcode) { + case 128: + SDL_UCS4ToUTF8(0x20AC, utf8); + break; + case 138: + SDL_UCS4ToUTF8(0x0160, utf8); + break; + case 140: + SDL_UCS4ToUTF8(0x0152, utf8); + break; + case 154: + SDL_UCS4ToUTF8(0x0161, utf8); + break; + case 156: + SDL_UCS4ToUTF8(0x0153, utf8); + break; + default: + SDL_UCS4ToUTF8(attachment->altcode, utf8); + break; + } + SDL_SendKeyboardText(utf8); + } + attachment->altcode_digit = -1; + SDL_SendKeyboardKey(timestamp, attachment->keyboard, 0, SDL_SCANCODE_NUMLOCKCLEAR, true); + SDL_SendKeyboardKey(timestamp, attachment->keyboard, 0, SDL_SCANCODE_NUMLOCKCLEAR, false); + } + } + + if (!bytes[2] && attachment->last_key) { + if (attachment->last_key == SDL_SCANCODE_CAPSLOCK) { + attachment->capslock = !attachment->capslock; + } + SDL_SendKeyboardKey(timestamp, attachment->keyboard, 0, attachment->last_key, false); + if (!(attachment->last_modifiers & 0xfd)) { + SDL_Keycode keycode = SDL_GetKeymapKeycode(NULL, + attachment->last_key, + ((attachment->last_modifiers & 0x02) || attachment->capslock) ? SDL_KMOD_SHIFT : 0); + if (keycode && keycode < 0x80) { + char text[2] = { (char)keycode }; + SDL_SendKeyboardText(text); + } + } + attachment->last_key = 0; + } else { + SDL_SendKeyboardKey(timestamp, attachment->keyboard, 0, bytes[2], true); + attachment->last_key = bytes[2]; + + if ((modifiers & 0x04) && attachment->altcode_digit >= 0) { + int digit = bytes[2] - SDL_SCANCODE_KP_1 + 1; + if (digit < 1 || digit > 10) { + attachment->altcode_digit = -1; + } else { + attachment->altcode_digit++; + attachment->altcode *= 10; + if (digit < 10) { + attachment->altcode += digit; + } + } + } + } + + attachment->last_modifiers = modifiers; + return true; +} + +static bool GIP_HandleCommandExtended( + GIP_Attachment *attachment, + const GIP_Header *header, + const Uint8 *bytes, + int num_bytes) +{ + char serial[33] = {0}; + + if (num_bytes < 2) { + return false; + } + + switch (bytes[0]) { + case GIP_EXTCMD_GET_SERIAL_NUMBER: + if (bytes[1] != GIP_EXTENDED_STATUS_OK) { + return true; + } + if (header->flags & GIP_FLAG_ATTACHMENT_MASK) { + return true; + } + SDL_memcpy(serial, &bytes[2], SDL_min(sizeof(serial) - 1, num_bytes - 2)); + HIDAPI_SetDeviceSerial(attachment->device->device, serial); + break; + default: + // TODO + SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "GIP: Extended message type %02x", bytes[0]); + return false; + } + + return true; +} + +static void GIP_HandleNavigationReport( + GIP_Attachment *attachment, + SDL_Joystick *joystick, + Uint64 timestamp, + const Uint8 *bytes, + int num_bytes) +{ + if (attachment->last_input[0] != bytes[0]) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((bytes[0] & 0x04) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((bytes[0] & 0x08) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((bytes[0] & 0x10) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((bytes[0] & 0x20) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((bytes[0] & 0x40) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((bytes[0] & 0x80) != 0)); + } + + if (attachment->last_input[1] != bytes[1]) { + Uint8 hat = 0; + + if (bytes[1] & 0x01) { + hat |= SDL_HAT_UP; + } + if (bytes[1] & 0x02) { + hat |= SDL_HAT_DOWN; + } + if (bytes[1] & 0x04) { + hat |= SDL_HAT_LEFT; + } + if (bytes[1] & 0x08) { + hat |= SDL_HAT_RIGHT; + } + SDL_SendJoystickHat(timestamp, joystick, 0, hat); + + if (attachment->attachment_type == GIP_TYPE_ARCADE_STICK) { + /* Previous */ + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((bytes[1] & 0x10) != 0)); + /* Next */ + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((bytes[1] & 0x20) != 0)); + } else { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((bytes[1] & 0x10) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((bytes[1] & 0x20) != 0)); + } + } +} + +static void GIP_HandleGamepadReport( + GIP_Attachment *attachment, + SDL_Joystick *joystick, + Uint64 timestamp, + const Uint8 *bytes, + int num_bytes) +{ + Sint16 axis; + + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((bytes[1] & 0x40) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((bytes[1] & 0x80) != 0)); + + axis = bytes[2]; + axis |= bytes[3] << 8; + axis = SDL_clamp(axis, 0, 1023); + axis = (axis - 512) * 64; + if (axis == 32704) { + axis = 32767; + } + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis); + + axis = bytes[4]; + axis |= bytes[5] << 8; + axis = SDL_clamp(axis, 0, 1023); + axis = (axis - 512) * 64; + if (axis == 32704) { + axis = 32767; + } + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis); + + axis = bytes[6]; + axis |= bytes[7] << 8; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis); + axis = bytes[8]; + axis |= bytes[9] << 8; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, ~axis); + axis = bytes[10]; + axis |= bytes[11] << 8; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis); + axis = bytes[12]; + axis |= bytes[13] << 8; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, ~axis); +} + +static void GIP_HandleArcadeStickReport( + GIP_Attachment *attachment, + SDL_Joystick *joystick, + Uint64 timestamp, + const Uint8 *bytes, + int num_bytes) +{ + Sint16 axis; + axis = bytes[2]; + axis |= bytes[3] << 8; + axis = SDL_clamp(axis, 0, 1023); + axis = (axis - 512) * 64; + if (axis == 32704) { + axis = 32767; + } + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis); + + axis = bytes[4]; + axis |= bytes[5] << 8; + axis = SDL_clamp(axis, 0, 1023); + axis = (axis - 512) * 64; + if (axis == 32704) { + axis = 32767; + } + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis); + + if (num_bytes >= 19) { + /* Extra button 6 */ + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, (bytes[18] & 0x40) ? 32767 : -32768); + /* Extra button 7 */ + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, (bytes[18] & 0x80) ? 32767 : -32768); + } +} + +static void GIP_HandleFlightStickReport( + GIP_Attachment *attachment, + SDL_Joystick *joystick, + Uint64 timestamp, + const Uint8 *bytes, + int num_bytes) +{ + Sint16 axis; + int i; + + if (num_bytes < 19) { + return; + } + + if (attachment->last_input[2] != bytes[2]) { + /* Fire 1 and 2 */ + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((bytes[2] & 0x01) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((bytes[2] & 0x02) != 0)); + } + for (i = 0; i < attachment->extra_buttons;) { + if (attachment->last_input[i / 8 + 3] != bytes[i / 8 + 3]) { + for (; i < attachment->extra_buttons; i++) { + SDL_SendJoystickButton(timestamp, + joystick, + (Uint8) (attachment->extra_button_idx + i), + ((bytes[i / 8 + 3] & (1u << i)) != 0)); + } + } else { + i += 8; + } + } + + /* Roll, pitch and yaw are signed. Throttle and any extra axes are unsigned. All values are full-range. */ + axis = bytes[11]; + axis |= bytes[12] << 8; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis); + + axis = bytes[13]; + axis |= bytes[14] << 8; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis); + + axis = bytes[15]; + axis |= bytes[16] << 8; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis); + + /* There are no more signed values, so skip RIGHTY */ + + axis = (bytes[18] << 8) - 0x8000; + axis |= bytes[17]; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis); + + for (i = 0; i < attachment->extra_axes; i++) { + if (20 + i * 2 >= num_bytes) { + return; + } + axis = (bytes[20 + i * 2] << 8) - 0x8000; + axis |= bytes[19 + i * 2]; + SDL_SendJoystickAxis(timestamp, joystick, (Uint8) (SDL_GAMEPAD_AXIS_RIGHT_TRIGGER + i), axis); + } +} + +static bool GIP_HandleLLInputReport( + GIP_Attachment *attachment, + const GIP_Header *header, + const Uint8 *bytes, + int num_bytes) +{ + Uint64 timestamp = SDL_GetTicksNS(); + SDL_Joystick *joystick = NULL; + + if (attachment->device->device->num_joysticks < 1) { + GIP_EnsureMetadata(attachment); + if (attachment->got_metadata != GIP_METADATA_GOT && attachment->got_metadata != GIP_METADATA_FAKED) { + return true; + } + } + + joystick = SDL_GetJoystickFromID(attachment->joystick); + if (!joystick) { + return false; + } + + if (attachment->device_state != GIP_STATE_START) { + SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "GIP: Discarding early input report"); + attachment->device_state = GIP_STATE_START; + return true; + } + + if (num_bytes < 14) { + SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "GIP: Discarding too-short input report"); + return false; + } + + GIP_HandleNavigationReport(attachment, joystick, timestamp, bytes, num_bytes); + + switch (attachment->attachment_type) { + case GIP_TYPE_GAMEPAD: + default: + GIP_HandleGamepadReport(attachment, joystick, timestamp, bytes, num_bytes); + break; + case GIP_TYPE_ARCADE_STICK: + GIP_HandleArcadeStickReport(attachment, joystick, timestamp, bytes, num_bytes); + break; + case GIP_TYPE_FLIGHT_STICK: + GIP_HandleFlightStickReport(attachment, joystick, timestamp, bytes, num_bytes); + break; + } + + if (attachment->features & GIP_FEATURE_ELITE_BUTTONS) { + bool clear = false; + if (attachment->xbe_format == GIP_BTN_FMT_XBE1 && + num_bytes > GIP_BTN_OFFSET_XBE1 && + attachment->last_input[GIP_BTN_OFFSET_XBE1] != bytes[GIP_BTN_OFFSET_XBE1] && + (bytes[GIP_BTN_OFFSET_XBE1] & 0x10)) + { + SDL_SendJoystickButton(timestamp, + joystick, + attachment->paddle_idx, + (bytes[GIP_BTN_OFFSET_XBE1] & 0x02) != 0); + SDL_SendJoystickButton(timestamp, + joystick, + attachment->paddle_idx + 1, + (bytes[GIP_BTN_OFFSET_XBE1] & 0x08) != 0); + SDL_SendJoystickButton(timestamp, + joystick, + attachment->paddle_idx + 2, + (bytes[GIP_BTN_OFFSET_XBE1] & 0x01) != 0); + SDL_SendJoystickButton(timestamp, + joystick, + attachment->paddle_idx + 3, + (bytes[GIP_BTN_OFFSET_XBE1] & 0x04) != 0); + } else if ((attachment->xbe_format == GIP_BTN_FMT_XBE2_4 || + attachment->xbe_format == GIP_BTN_FMT_XBE2_5) && + num_bytes > GIP_BTN_OFFSET_XBE2) + { + int profile_offset = attachment->xbe_format == GIP_BTN_FMT_XBE2_4 ? 15 : 20; + if (attachment->last_input[GIP_BTN_OFFSET_XBE2] != bytes[GIP_BTN_OFFSET_XBE2] || + attachment->last_input[profile_offset] != bytes[profile_offset]) + { + if (bytes[profile_offset] & 3) { + clear = true; + } else { + SDL_SendJoystickButton(timestamp, + joystick, + attachment->paddle_idx, + (bytes[GIP_BTN_OFFSET_XBE2] & 0x01) != 0); + SDL_SendJoystickButton(timestamp, + joystick, + attachment->paddle_idx + 1, + (bytes[GIP_BTN_OFFSET_XBE2] & 0x02) != 0); + SDL_SendJoystickButton(timestamp, + joystick, + attachment->paddle_idx + 2, + (bytes[GIP_BTN_OFFSET_XBE2] & 0x04) != 0); + SDL_SendJoystickButton(timestamp, + joystick, + attachment->paddle_idx + 3, + (bytes[GIP_BTN_OFFSET_XBE2] & 0x08) != 0); + } + } + } else { + clear = true; + } + if (clear) { + SDL_SendJoystickButton(timestamp, + joystick, + attachment->paddle_idx, + 0); + SDL_SendJoystickButton(timestamp, + joystick, + attachment->paddle_idx + 1, + 0); + SDL_SendJoystickButton(timestamp, + joystick, + attachment->paddle_idx + 2, + 0); + SDL_SendJoystickButton(timestamp, + joystick, + attachment->paddle_idx + 3, + 0); + } + } + + if ((attachment->features & GIP_FEATURE_CONSOLE_FUNCTION_MAP) && num_bytes >= 32) { + int function_map_offset = -1; + if (attachment->features & GIP_FEATURE_DYNAMIC_LATENCY_INPUT) { + /* The dynamic latency input bytes are after the console function map */ + if (num_bytes >= 40) { + function_map_offset = num_bytes - 26; + } + } else { + function_map_offset = num_bytes - 18; + } + if (function_map_offset >= 14) { + if (attachment->last_input[function_map_offset] != bytes[function_map_offset]) { + SDL_SendJoystickButton(timestamp, + joystick, + attachment->share_button_idx, + (bytes[function_map_offset] & 0x01) != 0); + } + } + } + + SDL_memcpy(attachment->last_input, bytes, SDL_min(num_bytes, sizeof(attachment->last_input))); + + return true; +} + +static bool GIP_HandleLLStaticConfiguration( + GIP_Attachment *attachment, + const GIP_Header *header, + const Uint8 *bytes, + int num_bytes) +{ + // TODO + SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "GIP: Unimplemented Static Configuration message"); + return false; +} + +static bool GIP_HandleLLButtonInfoReport( + GIP_Attachment *attachment, + const GIP_Header *header, + const Uint8 *bytes, + int num_bytes) +{ + // TODO + SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "GIP: Unimplemented Button Info Report message"); + return false; +} + +static bool GIP_HandleLLOverflowInputReport( + GIP_Attachment *attachment, + const GIP_Header *header, + const Uint8 *bytes, + int num_bytes) +{ + // TODO + SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "GIP: Unimplemented Overflow Input Report message"); + return false; +} + +static bool GIP_HandleAudioData( + GIP_Attachment *attachment, + const GIP_Header *header, + const Uint8 *bytes, + int num_bytes) +{ + // TODO + SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "GIP: Unimplemented Audio Data message"); + return false; +} + +static bool GIP_HandleSystemMessage( + GIP_Attachment *attachment, + const GIP_Header *header, + const Uint8 *bytes, + int num_bytes) +{ + if (attachment->attachment_index > 0 && attachment->attachment_type == GIP_TYPE_UNKNOWN) { + // XXX If we reattach to a controller after it's been initialized, it might have + // attachments we don't know about. Try to figure out what this one is. + if (header->message_type == GIP_CMD_HID_REPORT && num_bytes == 8) { + if (!attachment->keyboard) { + attachment->keyboard = (SDL_KeyboardID)(uintptr_t) attachment; + SDL_AddKeyboard(attachment->keyboard, "Xbox One Chatpad", true); + } + attachment->attachment_type = GIP_TYPE_CHATPAD; + attachment->metadata.device.in_system_messages[0] |= (1u << GIP_CMD_HID_REPORT); + } + } + if (!GIP_SupportsSystemMessage(attachment, header->message_type, true)) { + SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, + "GIP: Received claimed-unsupported system message type %02x", + header->message_type); + return false; + } + switch (header->message_type) { + case GIP_CMD_PROTO_CONTROL: + return GIP_HandleCommandProtocolControl(attachment, header, bytes, num_bytes); + case GIP_CMD_HELLO_DEVICE: + return GIP_HandleCommandHelloDevice(attachment, header, bytes, num_bytes); + case GIP_CMD_STATUS_DEVICE: + return GIP_HandleCommandStatusDevice(attachment, header, bytes, num_bytes); + case GIP_CMD_METADATA: + return GIP_HandleCommandMetadataRespose(attachment, header, bytes, num_bytes); + case GIP_CMD_SECURITY: + return GIP_HandleCommandSecurity(attachment, header, bytes, num_bytes); + case GIP_CMD_GUIDE_BUTTON: + return GIP_HandleCommandGuideButtonStatus(attachment, header, bytes, num_bytes); + case GIP_CMD_AUDIO_CONTROL: + return GIP_HandleCommandAudioControl(attachment, header, bytes, num_bytes); + case GIP_CMD_FIRMWARE: + return GIP_HandleCommandFirmware(attachment, header, bytes, num_bytes); + case GIP_CMD_HID_REPORT: + return GIP_HandleCommandHidReport(attachment, header, bytes, num_bytes); + case GIP_CMD_EXTENDED: + return GIP_HandleCommandExtended(attachment, header, bytes, num_bytes); + case GIP_AUDIO_DATA: + return GIP_HandleAudioData(attachment, header, bytes, num_bytes); + default: + SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, + "GIP: Received unknown system message type %02x", + header->message_type); + return false; + } +} + +static GIP_Attachment *GIP_EnsureAttachment(GIP_Device *device, Uint8 attachment_index) +{ + GIP_Attachment *attachment = device->attachments[attachment_index]; + if (!attachment) { + attachment = SDL_calloc(1, sizeof(*attachment)); + attachment->attachment_index = attachment_index; + if (attachment_index > 0) { + attachment->attachment_type = GIP_TYPE_UNKNOWN; + } + attachment->device = device; + attachment->metadata.device.in_system_messages[0] = GIP_DEFAULT_IN_SYSTEM_MESSAGES; + attachment->metadata.device.out_system_messages[0] = GIP_DEFAULT_OUT_SYSTEM_MESSAGES; + device->attachments[attachment_index] = attachment; + } + return attachment; +} + +static bool GIP_HandleMessage( + GIP_Attachment *attachment, + const GIP_Header *header, + const Uint8 *bytes, + int num_bytes) +{ + if (header->flags & GIP_FLAG_SYSTEM) { + return GIP_HandleSystemMessage(attachment, header, bytes, num_bytes); + } else { + switch (header->message_type) { + case GIP_CMD_RAW_REPORT: + if (attachment->features & GIP_FEATURE_ELITE_BUTTONS) { + return GIP_HandleCommandRawReport(attachment, header, bytes, num_bytes); + } + break; + case GIP_LL_INPUT_REPORT: + return GIP_HandleLLInputReport(attachment, header, bytes, num_bytes); + case GIP_LL_STATIC_CONFIGURATION: + return GIP_HandleLLStaticConfiguration(attachment, header, bytes, num_bytes); + case GIP_LL_BUTTON_INFO_REPORT: + return GIP_HandleLLButtonInfoReport(attachment, header, bytes, num_bytes); + case GIP_LL_OVERFLOW_INPUT_REPORT: + return GIP_HandleLLOverflowInputReport(attachment, header, bytes, num_bytes); + } + } + SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, + "GIP: Received unknown vendor message type %02x", + header->message_type); + return false; +} + +static void GIP_ReceivePacket(GIP_Device *device, const Uint8 *bytes, int num_bytes) +{ + GIP_Header header; + int offset = 3; + bool ok = true; + Uint64 fragment_offset = 0; + Uint16 bytes_remaining = 0; + bool is_fragment; + Uint8 attachment_index; + GIP_Attachment *attachment; + + if (num_bytes < 5) { + return; + } + + header.message_type = bytes[0]; + header.flags = bytes[1]; + header.sequence_id = bytes[2]; + offset += GIP_DecodeLength(&header.length, &bytes[offset], num_bytes - offset); + + is_fragment = header.flags & GIP_FLAG_FRAGMENT; + attachment_index = header.flags & GIP_FLAG_ATTACHMENT_MASK; + attachment = GIP_EnsureAttachment(device, attachment_index); + +#ifdef DEBUG_XBOX_PROTOCOL + HIDAPI_DumpPacket("GIP received message: size = %d", bytes, num_bytes); +#endif + + /* Handle coalescing fragmented messages */ + if (is_fragment) { + if (header.flags & GIP_FLAG_INIT_FRAG) { + Uint64 total_length; + if (attachment->fragment_message) { + /* + * Reset fragment buffer if we get a new initial + * fragment before finishing the last message. + * TODO: Is this the correct behavior? + */ + if (attachment->fragment_data) { + SDL_free(attachment->fragment_data); + attachment->fragment_data = NULL; + } + } + offset += GIP_DecodeLength(&total_length, &bytes[offset], num_bytes - offset); + if (total_length > MAX_MESSAGE_LENGTH) { + return; + } + attachment->total_length = (Uint16) total_length; + attachment->fragment_message = header.message_type; + if (header.length > num_bytes - offset) { + SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, + "GIP: Received fragment that claims to be %" SDL_PRIu64 " bytes, expected %i", + header.length, num_bytes - offset); + return; + } + if (header.length > total_length) { + SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, + "GIP: Received too long fragment, %" SDL_PRIu64 " bytes, exceeds %d", + header.length, attachment->total_length); + return; + } + attachment->fragment_data = SDL_malloc(attachment->total_length); + SDL_memcpy(attachment->fragment_data, &bytes[offset], (size_t) header.length); + fragment_offset = header.length; + attachment->fragment_offset = (Uint32) fragment_offset; + bytes_remaining = (Uint16) (attachment->total_length - fragment_offset); + } else { + if (header.message_type != attachment->fragment_message) { + SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, + "GIP: Received out of sequence message type %02x, expected %02x", + header.message_type, attachment->fragment_message); + GIP_FragmentFailed(attachment, &header); + return; + } + + offset += GIP_DecodeLength(&fragment_offset, &bytes[offset], num_bytes - offset); + if (fragment_offset != attachment->fragment_offset) { + SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, + "GIP: Received out of sequence fragment, (claimed %" SDL_PRIu64 ", expected %d)", + fragment_offset, attachment->fragment_offset); + GIP_Acknowledge(device, + &header, + attachment->fragment_offset, + (Uint16) (attachment->total_length - attachment->fragment_offset)); + return; + } else if (fragment_offset + header.length > attachment->total_length) { + SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, + "GIP: Received too long fragment, %" SDL_PRIu64 " exceeds %d", + fragment_offset + header.length, attachment->total_length); + GIP_FragmentFailed(attachment, &header); + return; + } + + bytes_remaining = attachment->total_length - (Uint16) (fragment_offset + header.length); + if (header.length != 0) { + SDL_memcpy(&attachment->fragment_data[fragment_offset], &bytes[offset], (size_t) header.length); + } else { + ok = GIP_HandleMessage(attachment, &header, attachment->fragment_data, attachment->total_length); + if (attachment->fragment_data) { + SDL_free(attachment->fragment_data); + attachment->fragment_data = NULL; + } + attachment->fragment_message = 0; + } + fragment_offset += header.length; + attachment->fragment_offset = (Uint16) fragment_offset; + } + attachment->fragment_timer = SDL_GetTicks(); + } else if (header.length + offset > num_bytes) { + SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, + "GIP: Received message with erroneous length (claimed %" SDL_PRIu64 ", actual %d), discarding", + header.length + offset, num_bytes); + return; + } else { + num_bytes -= offset; + bytes += offset; + fragment_offset = header.length; + ok = GIP_HandleMessage(attachment, &header, bytes, num_bytes); + } + + if (ok && (header.flags & GIP_FLAG_ACME)) { + GIP_Acknowledge(device, &header, (Uint32) fragment_offset, bytes_remaining); + } +} + +static void HIDAPI_DriverGIP_RumbleSent(void *userdata) +{ + GIP_Attachment *ctx = (GIP_Attachment *)userdata; + ctx->rumble_time = SDL_GetTicks(); +} + +static bool HIDAPI_DriverGIP_UpdateRumble(GIP_Attachment *attachment) +{ + GIP_DirectMotor motor; + + if (!(attachment->features & GIP_FEATURE_MOTOR_CONTROL)) { + return true; + } + + if (attachment->rumble_state == GIP_RUMBLE_STATE_QUEUED && attachment->rumble_time) { + attachment->rumble_state = GIP_RUMBLE_STATE_BUSY; + } + + if (attachment->rumble_state == GIP_RUMBLE_STATE_BUSY) { + const int RUMBLE_BUSY_TIME_MS = 10; + if (SDL_GetTicks() >= (attachment->rumble_time + RUMBLE_BUSY_TIME_MS)) { + attachment->rumble_time = 0; + attachment->rumble_state = GIP_RUMBLE_STATE_IDLE; + } + } + + if (!attachment->rumble_pending) { + return true; + } + + if (attachment->rumble_state != GIP_RUMBLE_STATE_IDLE) { + return true; + } + + // We're no longer pending, even if we fail to send the rumble below + attachment->rumble_pending = false; + + motor.motor_bitmap = GIP_MOTOR_ALL; + motor.left_impulse_level = attachment->left_impulse_level; + motor.right_impulse_level = attachment->right_impulse_level; + motor.left_vibration_level = attachment->left_vibration_level; + motor.right_vibration_level = attachment->right_vibration_level; + motor.duration = SDL_RUMBLE_RESEND_MS / 10 + 5; // Add a 50ms leniency, just in case + motor.delay = 0; + motor.repeat = 0; + + Uint8 message[9] = {0}; + SDL_memcpy(&message[1], &motor, sizeof(motor)); + if (!GIP_SendRawMessage(attachment->device, + GIP_CMD_DIRECT_MOTOR, + attachment->attachment_index, + GIP_SequenceNext(attachment, GIP_CMD_DIRECT_MOTOR, false), + message, + sizeof(message), + true, + HIDAPI_DriverGIP_RumbleSent, + attachment)) + { + return SDL_SetError("Couldn't send rumble packet"); + } + + attachment->rumble_state = GIP_RUMBLE_STATE_QUEUED; + + return true; +} + +static void HIDAPI_DriverGIP_RegisterHints(SDL_HintCallback callback, void *userdata) +{ + SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_GIP, callback, userdata); + SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_GIP_RESET_FOR_METADATA, callback, userdata); +} + +static void HIDAPI_DriverGIP_UnregisterHints(SDL_HintCallback callback, void *userdata) +{ + SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_GIP, callback, userdata); + SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_GIP_RESET_FOR_METADATA, callback, userdata); +} + +static bool HIDAPI_DriverGIP_IsEnabled(void) +{ + return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_GIP, + SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_XBOX_ONE, + SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_XBOX, + SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT)))); +} + +static bool HIDAPI_DriverGIP_IsSupportedDevice(SDL_HIDAPI_Device *device, const char *name, SDL_GamepadType type, Uint16 vendor_id, Uint16 product_id, Uint16 version, int interface_number, int interface_class, int interface_subclass, int interface_protocol) +{ + // Xbox One controllers speak HID over bluetooth instead of GIP + if (device && device->is_bluetooth) { + return false; + } +#if defined(SDL_PLATFORM_MACOS) && defined(SDL_JOYSTICK_MFI) + if (!SDL_IsJoystickBluetoothXboxOne(vendor_id, product_id)) { + // On macOS we get a shortened version of the real report and + // you can't write output reports for wired controllers, so + // we'll just use the GCController support instead. + return false; + } +#endif + return (type == SDL_GAMEPAD_TYPE_XBOXONE); +} + +static bool HIDAPI_DriverGIP_InitDevice(SDL_HIDAPI_Device *device) +{ + GIP_Device *ctx; + GIP_Attachment *attachment; + + ctx = (GIP_Device *)SDL_calloc(1, sizeof(*ctx)); + if (!ctx) { + return false; + } + ctx->device = device; + ctx->reset_for_metadata = SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_GIP_RESET_FOR_METADATA, false); + + attachment = GIP_EnsureAttachment(ctx, 0); + GIP_HandleQuirks(attachment); + + if (attachment->quirks & GIP_QUIRK_NO_HELLO) { + ctx->got_hello = true; + GIP_EnsureMetadata(attachment); + } else { + ctx->hello_deadline = SDL_GetTicks() + GIP_HELLO_TIMEOUT; + } + + device->context = ctx; + device->type = SDL_GAMEPAD_TYPE_XBOXONE; + + return true; +} + +static int HIDAPI_DriverGIP_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id) +{ + return -1; +} + +static void HIDAPI_DriverGIP_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index) +{ +} + +static GIP_Attachment * HIDAPI_DriverGIP_FindAttachment(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ + GIP_Device *ctx = (GIP_Device *)device->context; + int i; + + for (i = 0; i < MAX_ATTACHMENTS; i++) { + if (ctx->attachments[i] && ctx->attachments[i]->joystick == joystick->instance_id) { + return ctx->attachments[i]; + } + } + return NULL; +} + +static bool HIDAPI_DriverGIP_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ + GIP_Attachment *attachment = HIDAPI_DriverGIP_FindAttachment(device, joystick); + if (!attachment) { + return SDL_SetError("Invalid joystick"); + } + + SDL_AssertJoysticksLocked(); + + attachment->left_impulse_level = 0; + attachment->right_impulse_level = 0; + attachment->left_vibration_level = 0; + attachment->right_vibration_level = 0; + attachment->rumble_state = GIP_RUMBLE_STATE_IDLE; + attachment->rumble_time = 0; + attachment->rumble_pending = false; + SDL_zeroa(attachment->last_input); + + // Initialize the joystick capabilities + joystick->nbuttons = 11; + GIP_EnableEliteButtons(attachment); + if (attachment->xbe_format != GIP_BTN_FMT_UNKNOWN || + (device->vendor_id == USB_VENDOR_MICROSOFT && + device->product_id == USB_PRODUCT_XBOX_ONE_ELITE_SERIES_2)) + { + attachment->paddle_idx = (Uint8) joystick->nbuttons; + joystick->nbuttons += 4; + } + if (attachment->features & GIP_FEATURE_CONSOLE_FUNCTION_MAP) { + attachment->share_button_idx = (Uint8) joystick->nbuttons; + joystick->nbuttons++; + } + if (attachment->extra_buttons > 0) { + attachment->extra_button_idx = (Uint8) joystick->nbuttons; + joystick->nbuttons += attachment->extra_buttons; + } + + joystick->naxes = SDL_GAMEPAD_AXIS_COUNT; + if (attachment->attachment_type == GIP_TYPE_FLIGHT_STICK) { + /* Flight sticks have at least 4 axes, but only 3 are signed values, so we leave RIGHTY unused */ + joystick->naxes += attachment->extra_axes - 1; + } + + joystick->nhats = 1; + + return true; +} + +static bool HIDAPI_DriverGIP_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) +{ + GIP_Attachment *attachment = HIDAPI_DriverGIP_FindAttachment(device, joystick); + if (!attachment) { + return SDL_SetError("Invalid joystick"); + } + + if (!(attachment->features & GIP_FEATURE_MOTOR_CONTROL)) { + return SDL_Unsupported(); + } + + // Magnitude is 1..100 so scale the 16-bit input here + attachment->left_vibration_level = (Uint8)(low_frequency_rumble / 655); + attachment->right_vibration_level = (Uint8)(high_frequency_rumble / 655); + attachment->rumble_pending = true; + + return HIDAPI_DriverGIP_UpdateRumble(attachment); +} + +static bool HIDAPI_DriverGIP_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble) +{ + GIP_Attachment *attachment = HIDAPI_DriverGIP_FindAttachment(device, joystick); + if (!attachment) { + return SDL_SetError("Invalid joystick"); + } + + if (!(attachment->features & GIP_FEATURE_MOTOR_CONTROL) || (attachment->quirks & GIP_QUIRK_NO_IMPULSE_VIBRATION)) { + return SDL_Unsupported(); + } + + // Magnitude is 1..100 so scale the 16-bit input here + attachment->left_impulse_level = (Uint8)(left_rumble / 655); + attachment->right_impulse_level = (Uint8)(right_rumble / 655); + attachment->rumble_pending = true; + + return HIDAPI_DriverGIP_UpdateRumble(attachment); +} + +static Uint32 HIDAPI_DriverGIP_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ + GIP_Attachment *attachment = HIDAPI_DriverGIP_FindAttachment(device, joystick); + Uint32 result = 0; + if (!attachment) { + return 0; + } + + if (attachment->features & GIP_FEATURE_MOTOR_CONTROL) { + result |= SDL_JOYSTICK_CAP_RUMBLE; + if (!(attachment->quirks & GIP_QUIRK_NO_IMPULSE_VIBRATION)) { + result |= SDL_JOYSTICK_CAP_TRIGGER_RUMBLE; + } + } + + if (attachment->features & GIP_FEATURE_GUIDE_COLOR) { + result |= SDL_JOYSTICK_CAP_RGB_LED; + } + + return result; +} + +static bool HIDAPI_DriverGIP_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue) +{ + GIP_Attachment *attachment = HIDAPI_DriverGIP_FindAttachment(device, joystick); + Uint8 buffer[] = { 0x00, 0x00, 0x00, 0x00, 0x00 }; + + if (!attachment) { + return SDL_SetError("Invalid joystick"); + } + + if (!(attachment->features & GIP_FEATURE_GUIDE_COLOR)) { + return SDL_Unsupported(); + } + + buffer[1] = 0x00; // Whiteness? Sets white intensity when RGB is 0, seems additive + buffer[2] = red; + buffer[3] = green; + buffer[4] = blue; + + if (!GIP_SendVendorMessage(attachment, GIP_CMD_GUIDE_COLOR, 0, buffer, sizeof(buffer))) { + return SDL_SetError("Couldn't send LED packet"); + } + return true; +} + +static bool HIDAPI_DriverGIP_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size) +{ + return SDL_Unsupported(); +} + + +static bool HIDAPI_DriverGIP_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled) +{ + return SDL_Unsupported(); +} + +static bool HIDAPI_DriverGIP_UpdateDevice(SDL_HIDAPI_Device *device) +{ + GIP_Device *ctx = (GIP_Device *)device->context; + Uint8 bytes[USB_PACKET_LENGTH]; + int i; + int num_bytes; + bool perform_reset = false; + Uint64 timestamp; + + while ((num_bytes = SDL_hid_read_timeout(device->dev, bytes, sizeof(bytes), ctx->timeout)) > 0) { + ctx->timeout = 0; + GIP_ReceivePacket(ctx, bytes, num_bytes); + } + + timestamp = SDL_GetTicks(); + if (ctx->hello_deadline && timestamp >= ctx->hello_deadline) { + ctx->hello_deadline = 0; + perform_reset = true; + } + for (i = 0; i < MAX_ATTACHMENTS; i++) { + GIP_Attachment *attachment = ctx->attachments[i]; + if (!attachment) { + continue; + } + if (attachment->fragment_message && timestamp >= attachment->fragment_timer + 1000) { + SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, "GIP: Reliable message transfer failed"); + attachment->fragment_message = 0; + } + if (!perform_reset && + attachment->got_metadata == GIP_METADATA_PENDING && + timestamp >= attachment->metadata_next && + attachment->fragment_message != GIP_CMD_METADATA) + { + if (attachment->metadata_retries < 3) { + SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, "GIP: Retrying metadata request"); + attachment->metadata_retries++; + attachment->metadata_next = timestamp + 500; + GIP_SendSystemMessage(attachment, GIP_CMD_METADATA, 0, NULL, 0); + } else { + perform_reset = true; + } + } + if (perform_reset) { + if (ctx->reset_for_metadata) { + GIP_SendSetDeviceState(attachment, GIP_STATE_RESET); + } else { + GIP_SetMetadataDefaults(attachment); + GIP_SendInitSequence(attachment); + } + perform_reset = false; + } + HIDAPI_DriverGIP_UpdateRumble(attachment); + } + + if (num_bytes < 0 && device->num_joysticks > 0) { + // Read error, device is disconnected + for (i = 0; i < MAX_ATTACHMENTS; i++) { + GIP_Attachment *attachment = ctx->attachments[i]; + if (attachment) { + HIDAPI_JoystickDisconnected(device, attachment->joystick); + } + } + } + return (num_bytes >= 0); +} +static void HIDAPI_DriverGIP_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ +} + +static void HIDAPI_DriverGIP_FreeDevice(SDL_HIDAPI_Device *device) +{ + GIP_Device *context = (GIP_Device *)device->context; + int i; + + for (i = 0; i < MAX_ATTACHMENTS; i++) { + GIP_Attachment *attachment = context->attachments[i]; + if (!attachment) { + continue; + } + if (attachment->fragment_data) { + SDL_free(attachment->fragment_data); + attachment->fragment_data = NULL; + } + if (attachment->keyboard) { + SDL_RemoveKeyboard(attachment->keyboard, true); + } + GIP_MetadataFree(&attachment->metadata); + SDL_free(attachment); + context->attachments[i] = NULL; + } +} + +SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverGIP = { + SDL_HINT_JOYSTICK_HIDAPI_GIP, + true, + HIDAPI_DriverGIP_RegisterHints, + HIDAPI_DriverGIP_UnregisterHints, + HIDAPI_DriverGIP_IsEnabled, + HIDAPI_DriverGIP_IsSupportedDevice, + HIDAPI_DriverGIP_InitDevice, + HIDAPI_DriverGIP_GetDevicePlayerIndex, + HIDAPI_DriverGIP_SetDevicePlayerIndex, + HIDAPI_DriverGIP_UpdateDevice, + HIDAPI_DriverGIP_OpenJoystick, + HIDAPI_DriverGIP_RumbleJoystick, + HIDAPI_DriverGIP_RumbleJoystickTriggers, + HIDAPI_DriverGIP_GetJoystickCapabilities, + HIDAPI_DriverGIP_SetJoystickLED, + HIDAPI_DriverGIP_SendJoystickEffect, + HIDAPI_DriverGIP_SetJoystickSensorsEnabled, + HIDAPI_DriverGIP_CloseJoystick, + HIDAPI_DriverGIP_FreeDevice, +}; + +#endif // SDL_JOYSTICK_HIDAPI_GIP + +#endif // SDL_JOYSTICK_HIDAPI diff --git a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_lg4ff.c b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_lg4ff.c new file mode 100644 index 00000000..90191198 --- /dev/null +++ b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_lg4ff.c @@ -0,0 +1,1002 @@ +/* + Simple DirectMedia Layer + Copyright (C) 2025 Simon Wood + Copyright (C) 2025 Michal Malý + Copyright (C) 2025 Katharine Chui + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +#include "SDL_internal.h" + +#ifdef SDL_JOYSTICK_HIDAPI + +#include "../SDL_sysjoystick.h" +#include "SDL3/SDL_events.h" +#include "SDL_hidapijoystick_c.h" + +#ifdef SDL_JOYSTICK_HIDAPI_LG4FF + +#define USB_VENDOR_ID_LOGITECH 0x046d +#define USB_DEVICE_ID_LOGITECH_G29_WHEEL 0xc24f +#define USB_DEVICE_ID_LOGITECH_G27_WHEEL 0xc29b +#define USB_DEVICE_ID_LOGITECH_G25_WHEEL 0xc299 +#define USB_DEVICE_ID_LOGITECH_DFGT_WHEEL 0xc29a +#define USB_DEVICE_ID_LOGITECH_DFP_WHEEL 0xc298 +#define USB_DEVICE_ID_LOGITECH_WHEEL 0xc294 + +static Uint32 supported_device_ids[] = { + USB_DEVICE_ID_LOGITECH_G29_WHEEL, + USB_DEVICE_ID_LOGITECH_G27_WHEEL, + USB_DEVICE_ID_LOGITECH_G25_WHEEL, + USB_DEVICE_ID_LOGITECH_DFGT_WHEEL, + USB_DEVICE_ID_LOGITECH_DFP_WHEEL, + USB_DEVICE_ID_LOGITECH_WHEEL +}; + +// keep the same order as the supported_ids array +static const char *supported_device_names[] = { + "Logitech G29", + "Logitech G27", + "Logitech G25", + "Logitech Driving Force GT", + "Logitech Driving Force Pro", + "Driving Force EX" +}; + +static const char *HIDAPI_DriverLg4ff_GetDeviceName(Uint32 device_id) +{ + for (int i = 0;i < (sizeof supported_device_ids) / sizeof(Uint32);i++) { + if (supported_device_ids[i] == device_id) { + return supported_device_names[i]; + } + } + SDL_assert(0); + return ""; +} + +static int HIDAPI_DriverLg4ff_GetNumberOfButtons(Uint32 device_id) +{ + switch (device_id) { + case USB_DEVICE_ID_LOGITECH_G29_WHEEL: + return 25; + case USB_DEVICE_ID_LOGITECH_G27_WHEEL: + return 23; + case USB_DEVICE_ID_LOGITECH_G25_WHEEL: + return 19; + case USB_DEVICE_ID_LOGITECH_DFGT_WHEEL: + return 21; + case USB_DEVICE_ID_LOGITECH_DFP_WHEEL: + return 14; + case USB_DEVICE_ID_LOGITECH_WHEEL: + return 13; + default: + SDL_assert(0); + return 0; + } +} + +typedef struct +{ + Uint8 last_report_buf[32]; + bool initialized; + bool is_ffex; + Uint16 range; +} SDL_DriverLg4ff_Context; + +static void HIDAPI_DriverLg4ff_RegisterHints(SDL_HintCallback callback, void *userdata) +{ + SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_LG4FF, callback, userdata); +} + +static void HIDAPI_DriverLg4ff_UnregisterHints(SDL_HintCallback callback, void *userdata) +{ + SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_LG4FF, callback, userdata); +} + +static bool HIDAPI_DriverLg4ff_IsEnabled(void) +{ + bool enabled = SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_LG4FF, + SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT)); + + return enabled; +} + +/* + Wheel id information by: + Michal Malý + Simon Wood + `git blame v6.12 drivers/hid/hid-lg4ff.c`, https://github.com/torvalds/linux.git +*/ +static Uint16 HIDAPI_DriverLg4ff_IdentifyWheel(Uint16 device_id, Uint16 release_number) +{ + #define is_device(ret, m, r) { \ + if ((release_number & m) == r) { \ + return ret; \ + } \ + } + #define is_dfp { \ + is_device(USB_DEVICE_ID_LOGITECH_DFP_WHEEL, 0xf000, 0x1000); \ + } + #define is_dfgt { \ + is_device(USB_DEVICE_ID_LOGITECH_DFGT_WHEEL, 0xff00, 0x1300); \ + } + #define is_g25 { \ + is_device(USB_DEVICE_ID_LOGITECH_G25_WHEEL, 0xff00, 0x1200); \ + } + #define is_g27 { \ + is_device(USB_DEVICE_ID_LOGITECH_G27_WHEEL, 0xfff0, 0x1230); \ + } + #define is_g29 { \ + is_device(USB_DEVICE_ID_LOGITECH_G29_WHEEL, 0xfff8, 0x1350); \ + is_device(USB_DEVICE_ID_LOGITECH_G29_WHEEL, 0xff00, 0x8900); \ + } + switch(device_id){ + case USB_DEVICE_ID_LOGITECH_DFP_WHEEL: + case USB_DEVICE_ID_LOGITECH_WHEEL: + is_g29; + is_g27; + is_g25; + is_dfgt; + is_dfp; + break; + case USB_DEVICE_ID_LOGITECH_DFGT_WHEEL: + is_g29; + is_dfgt; + break; + case USB_DEVICE_ID_LOGITECH_G25_WHEEL: + is_g29; + is_g27; + is_g25; + break; + case USB_DEVICE_ID_LOGITECH_G27_WHEEL: + is_g29; + is_g27; + break; + case USB_DEVICE_ID_LOGITECH_G29_WHEEL: + is_g29; + break; + } + return 0; + #undef is_device + #undef is_dfp + #undef is_dfgt + #undef is_g25 + #undef is_g27 + #undef is_g29 +} + +static int SDL_HIDAPI_DriverLg4ff_GetEnvInt(const char *env_name, int min, int max, int def) +{ + const char *env = SDL_getenv(env_name); + int value = 0; + if(env == NULL) { + return def; + } + value = SDL_atoi(env); + if (value < min) { + value = min; + } + if (value > max) { + value = max; + } + return value; +} + +/* + Commands by: + Michal Malý + Simon Wood + `git blame v6.12 drivers/hid/hid-lg4ff.c`, https://github.com/torvalds/linux.git +*/ +static bool HIDAPI_DriverLg4ff_SwitchMode(SDL_HIDAPI_Device *device, Uint16 target_product_id){ + int ret = 0; + + switch(target_product_id){ + case USB_DEVICE_ID_LOGITECH_G29_WHEEL:{ + Uint8 cmd[] = {0xf8, 0x09, 0x05, 0x01, 0x01, 0x00, 0x00}; + ret = SDL_hid_write(device->dev, cmd, sizeof(cmd)); + break; + } + case USB_DEVICE_ID_LOGITECH_G27_WHEEL:{ + Uint8 cmd[] = {0xf8, 0x09, 0x04, 0x01, 0x00, 0x00, 0x00}; + ret = SDL_hid_write(device->dev, cmd, sizeof(cmd)); + break; + } + case USB_DEVICE_ID_LOGITECH_G25_WHEEL:{ + Uint8 cmd[] = {0xf8, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00}; + ret = SDL_hid_write(device->dev, cmd, sizeof(cmd)); + break; + } + case USB_DEVICE_ID_LOGITECH_DFGT_WHEEL:{ + Uint8 cmd[] = {0xf8, 0x09, 0x03, 0x01, 0x00, 0x00, 0x00}; + ret = SDL_hid_write(device->dev, cmd, sizeof(cmd)); + break; + } + case USB_DEVICE_ID_LOGITECH_DFP_WHEEL:{ + Uint8 cmd[] = {0xf8, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00}; + ret = SDL_hid_write(device->dev, cmd, sizeof(cmd)); + break; + } + case USB_DEVICE_ID_LOGITECH_WHEEL:{ + Uint8 cmd[] = {0xf8, 0x09, 0x00, 0x01, 0x00, 0x00, 0x00}; + ret = SDL_hid_write(device->dev, cmd, sizeof(cmd)); + break; + } + default:{ + SDL_assert(0); + } + } + if(ret == -1){ + return false; + } + return true; +} + +static bool HIDAPI_DriverLg4ff_IsSupportedDevice( + SDL_HIDAPI_Device *device, + const char *name, + SDL_GamepadType type, + Uint16 vendor_id, + Uint16 product_id, + Uint16 version, + int interface_number, + int interface_class, + int interface_subclass, + int interface_protocol) +{ + int i; + if (vendor_id != USB_VENDOR_ID_LOGITECH) { + return false; + } + for (i = 0;i < sizeof(supported_device_ids) / sizeof(Uint32);i++) { + if (supported_device_ids[i] == product_id) { + break; + } + } + if (i == sizeof(supported_device_ids) / sizeof(Uint32)) { + return false; + } + Uint16 real_id = HIDAPI_DriverLg4ff_IdentifyWheel(product_id, version); + if (real_id == product_id || real_id == 0) { + // either it is already in native mode, or we don't know what the native mode is + return true; + } + // a supported native mode is found, send mode change command, then still state that we support the device + if (device != NULL && SDL_HIDAPI_DriverLg4ff_GetEnvInt("SDL_HIDAPI_LG4FF_NO_MODE_SWITCH", 0, 1, 0) == 0) { + HIDAPI_DriverLg4ff_SwitchMode(device, real_id); + } + return true; +} + +/* + *Ported* + Original functions by: + Michal Malý + lg4ff_set_range_g25 lg4ff_set_range_dfp + `git blame v6.12 drivers/hid/hid-lg4ff.c`, https://github.com/torvalds/linux.git +*/ +static bool HIDAPI_DriverLg4ff_SetRange(SDL_HIDAPI_Device *device, int range) +{ + Uint8 cmd[7] = {0}; + int ret = 0; + SDL_DriverLg4ff_Context *ctx = (SDL_DriverLg4ff_Context *)device->context; + + if (range < 40) { + range = 40; + } + if (range > 900) { + range = 900; + } + + ctx->range = (Uint16)range; + switch (device->product_id) { + case USB_DEVICE_ID_LOGITECH_G29_WHEEL: + case USB_DEVICE_ID_LOGITECH_G27_WHEEL: + case USB_DEVICE_ID_LOGITECH_G25_WHEEL: + case USB_DEVICE_ID_LOGITECH_DFGT_WHEEL:{ + cmd[0] = 0xf8; + cmd[1] = 0x81; + cmd[2] = range & 0x00ff; + cmd[3] = (range & 0xff00) >> 8; + ret = SDL_hid_write(device->dev, cmd, sizeof(cmd)); + if (ret == -1) { + return false; + } + break; + } + case USB_DEVICE_ID_LOGITECH_DFP_WHEEL:{ + int start_left, start_right, full_range; + + /* Prepare "coarse" limit command */ + cmd[0] = 0xf8; + cmd[1] = 0x00; /* Set later */ + cmd[2] = 0x00; + cmd[3] = 0x00; + cmd[4] = 0x00; + cmd[5] = 0x00; + cmd[6] = 0x00; + + if (range > 200) { + cmd[1] = 0x03; + full_range = 900; + } else { + cmd[1] = 0x02; + full_range = 200; + } + ret = SDL_hid_write(device->dev, cmd, 7); + if(ret == -1){ + return false; + } + + /* Prepare "fine" limit command */ + cmd[0] = 0x81; + cmd[1] = 0x0b; + cmd[2] = 0x00; + cmd[3] = 0x00; + cmd[4] = 0x00; + cmd[5] = 0x00; + cmd[6] = 0x00; + + if (range != 200 && range != 900) { + /* Construct fine limit command */ + start_left = (((full_range - range + 1) * 2047) / full_range); + start_right = 0xfff - start_left; + + cmd[2] = (Uint8)(start_left >> 4); + cmd[3] = (Uint8)(start_right >> 4); + cmd[4] = 0xff; + cmd[5] = (start_right & 0xe) << 4 | (start_left & 0xe); + cmd[6] = 0xff; + } + + ret = SDL_hid_write(device->dev, cmd, 7); + if (ret == -1) { + return false; + } + break; + } + case USB_DEVICE_ID_LOGITECH_WHEEL: + // no range setting for ffex/dfex + break; + default: + SDL_assert(0); + } + + return true; +} + +/* + *Ported* + Original functions by: + Simon Wood + Michal Malý + lg4ff_set_autocenter_default lg4ff_set_autocenter_ffex + `git blame v6.12 drivers/hid/hid-lg4ff.c`, https://github.com/torvalds/linux.git +*/ +static bool HIDAPI_DriverLg4ff_SetAutoCenter(SDL_HIDAPI_Device *device, int magnitude) +{ + SDL_DriverLg4ff_Context *ctx = (SDL_DriverLg4ff_Context *)device->context; + Uint8 cmd[7] = {0}; + int ret; + + if (magnitude < 0) { + magnitude = 0; + } + if (magnitude > 65535) { + magnitude = 65535; + } + + if (ctx->is_ffex) { + magnitude = magnitude * 90 / 65535; + + cmd[0] = 0xfe; + cmd[1] = 0x03; + cmd[2] = (Uint8)((Uint16)magnitude >> 14); + cmd[3] = (Uint8)((Uint16)magnitude >> 14); + cmd[4] = (Uint8)magnitude; + + ret = SDL_hid_write(device->dev, cmd, sizeof(cmd)); + if(ret == -1){ + return false; + } + } else { + Uint32 expand_a; + Uint32 expand_b; + // first disable + cmd[0] = 0xf5; + + ret = SDL_hid_write(device->dev, cmd, sizeof(cmd)); + if (ret == -1) { + return false; + } + + if (magnitude == 0) { + return true; + } + + // set strength + + if (magnitude <= 0xaaaa) { + expand_a = 0x0c * magnitude; + expand_b = 0x80 * magnitude; + } else { + expand_a = (0x0c * 0xaaaa) + 0x06 * (magnitude - 0xaaaa); + expand_b = (0x80 * 0xaaaa) + 0xff * (magnitude - 0xaaaa); + } + // TODO do not adjust for MOMO wheels, when support is added + expand_a = expand_a >> 1; + + SDL_memset(cmd, 0x00, sizeof(cmd)); + cmd[0] = 0xfe; + cmd[1] = 0x0d; + cmd[2] = (Uint8)(expand_a / 0xaaaa); + cmd[3] = (Uint8)(expand_a / 0xaaaa); + cmd[4] = (Uint8)(expand_b / 0xaaaa); + + ret = SDL_hid_write(device->dev, cmd, sizeof(cmd)); + if (ret == -1) { + return false; + } + + // enable + SDL_memset(cmd, 0x00, sizeof(cmd)); + cmd[0] = 0x14; + + ret = SDL_hid_write(device->dev, cmd, sizeof(cmd)); + if (ret == -1) { + return false; + } + } + return true; +} + +/* + ffex identification method by: + Simon Wood + Michal Malý + lg4ff_init + `git blame v6.12 drivers/hid/hid-lg4ff.c`, https://github.com/torvalds/linux.git +*/ +static bool HIDAPI_DriverLg4ff_InitDevice(SDL_HIDAPI_Device *device) +{ + SDL_DriverLg4ff_Context *ctx; + + ctx = (SDL_DriverLg4ff_Context *)SDL_malloc(sizeof(SDL_DriverLg4ff_Context)); + if (ctx == NULL) { + SDL_OutOfMemory(); + return false; + } + SDL_memset(ctx, 0, sizeof(SDL_DriverLg4ff_Context)); + + device->context = ctx; + device->joystick_type = SDL_JOYSTICK_TYPE_WHEEL; + + HIDAPI_SetDeviceName(device, HIDAPI_DriverLg4ff_GetDeviceName(device->product_id)); + + if (SDL_hid_set_nonblocking(device->dev, 1) != 0) { + return false; + } + + if (!HIDAPI_DriverLg4ff_SetAutoCenter(device, 0)) { + return false; + } + + if (device->product_id == USB_DEVICE_ID_LOGITECH_WHEEL && + (device->version >> 8) == 0x21 && + (device->version & 0xff) == 0x00) { + ctx->is_ffex = true; + } else { + ctx->is_ffex = false; + } + + ctx->range = 900; + + return HIDAPI_JoystickConnected(device, NULL); +} + +static int HIDAPI_DriverLg4ff_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id) +{ + return -1; +} + +static void HIDAPI_DriverLg4ff_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index) +{ +} + + +static bool HIDAPI_DriverLg4ff_GetBit(const Uint8 *buf, int bit_num, size_t buf_len) +{ + int byte_offset = bit_num / 8; + int local_bit = bit_num % 8; + Uint8 mask = 1 << local_bit; + if ((size_t)byte_offset >= buf_len) { + SDL_assert(0); + } + return (buf[byte_offset] & mask) ? true : false; +} + +/* + *Ported* + Original functions by: + Michal Malý + lg4ff_adjust_dfp_x_axis + `git blame v6.12 drivers/hid/hid-lg4ff.c`, https://github.com/torvalds/linux.git +*/ +static Uint16 lg4ff_adjust_dfp_x_axis(Uint16 value, Uint16 range) +{ + Uint16 max_range; + Sint32 new_value; + + if (range == 900) + return value; + else if (range == 200) + return value; + else if (range < 200) + max_range = 200; + else + max_range = 900; + + new_value = 8192 + ((value - 8192) * max_range / range); + if (new_value < 0) + return 0; + else if (new_value > 16383) + return 16383; + else + return (Uint16)new_value; +} + +static bool HIDAPI_DriverLg4ff_HandleState(SDL_HIDAPI_Device *device, + SDL_Joystick *joystick, + Uint8 *report_buf, + size_t report_size) +{ + SDL_DriverLg4ff_Context *ctx = (SDL_DriverLg4ff_Context *)device->context; + Uint8 hat = 0; + Uint8 last_hat = 0; + int num_buttons = HIDAPI_DriverLg4ff_GetNumberOfButtons(device->product_id); + int bit_offset = 0; + Uint64 timestamp = SDL_GetTicksNS(); + + bool state_changed = false; + + switch (device->product_id) { + case USB_DEVICE_ID_LOGITECH_G29_WHEEL: + case USB_DEVICE_ID_LOGITECH_G27_WHEEL: + case USB_DEVICE_ID_LOGITECH_G25_WHEEL: + case USB_DEVICE_ID_LOGITECH_DFGT_WHEEL: + hat = report_buf[0] & 0x0f; + last_hat = ctx->last_report_buf[0] & 0x0f; + break; + case USB_DEVICE_ID_LOGITECH_DFP_WHEEL: + hat = report_buf[3] >> 4; + last_hat = ctx->last_report_buf[3] >> 4; + break; + case USB_DEVICE_ID_LOGITECH_WHEEL: + hat = report_buf[2] & 0x0F; + last_hat = ctx->last_report_buf[2] & 0x0F; + break; + default: + SDL_assert(0); + } + + if (hat != last_hat) { + Uint8 sdl_hat = 0; + state_changed = true; + switch (hat) { + case 0: + sdl_hat = SDL_HAT_UP; + break; + case 1: + sdl_hat = SDL_HAT_RIGHTUP; + break; + case 2: + sdl_hat = SDL_HAT_RIGHT; + break; + case 3: + sdl_hat = SDL_HAT_RIGHTDOWN; + break; + case 4: + sdl_hat = SDL_HAT_DOWN; + break; + case 5: + sdl_hat = SDL_HAT_LEFTDOWN; + break; + case 6: + sdl_hat = SDL_HAT_LEFT; + break; + case 7: + sdl_hat = SDL_HAT_LEFTUP; + break; + case 8: + sdl_hat = SDL_HAT_CENTERED; + break; + // do not assert out, in case hardware can report weird hat values + } + SDL_SendJoystickHat(timestamp, joystick, 0, sdl_hat); + } + + switch (device->product_id) { + case USB_DEVICE_ID_LOGITECH_G29_WHEEL: + case USB_DEVICE_ID_LOGITECH_G27_WHEEL: + case USB_DEVICE_ID_LOGITECH_G25_WHEEL: + case USB_DEVICE_ID_LOGITECH_DFGT_WHEEL: + bit_offset = 4; + break; + case USB_DEVICE_ID_LOGITECH_DFP_WHEEL: + bit_offset = 14; + break; + case USB_DEVICE_ID_LOGITECH_WHEEL: + bit_offset = 0; + break; + default: + SDL_assert(0); + } + + if (device->product_id == USB_DEVICE_ID_LOGITECH_G27_WHEEL) { + // ref https://github.com/sonik-br/lgff_wheel_adapter/blob/d97f7823154818e1b3edff6d51498a122c302728/pico_lgff_wheel_adapter/reports.h#L265-L310 + // shifter_r is outside of the main button bit field for this particular wheel + num_buttons--; + + bool button_on = HIDAPI_DriverLg4ff_GetBit(report_buf, 80, report_size); + bool button_was_on = HIDAPI_DriverLg4ff_GetBit(ctx->last_report_buf, 80, report_size); + if (button_on != button_was_on) { + state_changed = true; + SDL_SendJoystickButton(timestamp, joystick, (Uint8)(SDL_GAMEPAD_BUTTON_SOUTH + num_buttons), button_on); + } + } + + for (int i = 0;i < num_buttons;i++) { + int bit_num = bit_offset + i; + bool button_on = HIDAPI_DriverLg4ff_GetBit(report_buf, bit_num, report_size); + bool button_was_on = HIDAPI_DriverLg4ff_GetBit(ctx->last_report_buf, bit_num, report_size); + if(button_on != button_was_on){ + state_changed = true; + SDL_SendJoystickButton(timestamp, joystick, (Uint8)(SDL_GAMEPAD_BUTTON_SOUTH + i), button_on); + } + } + + switch (device->product_id) { + case USB_DEVICE_ID_LOGITECH_G29_WHEEL:{ + Uint16 x = *(Uint16 *)&report_buf[4]; + Uint16 last_x = *(Uint16 *)&ctx->last_report_buf[4]; + if (x != last_x) { + state_changed = true; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, x - 32768); + } + if (report_buf[6] != ctx->last_report_buf[6]) { + state_changed = true; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, report_buf[6] * 257 - 32768); + } + if (report_buf[7] != ctx->last_report_buf[7]) { + state_changed = true; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, report_buf[7] * 257 - 32768); + } + if (report_buf[8] != ctx->last_report_buf[8]) { + state_changed = true; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, report_buf[8] * 257 - 32768); + } + break; + } + case USB_DEVICE_ID_LOGITECH_G27_WHEEL: + case USB_DEVICE_ID_LOGITECH_G25_WHEEL:{ + Uint16 x = report_buf[4] << 6; + Uint16 last_x = ctx->last_report_buf[4] << 6; + x = x | report_buf[3] >> 2; + last_x = last_x | ctx->last_report_buf[3] >> 2; + if (x != last_x) { + state_changed = true; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, x * 4 - 32768); + } + if (report_buf[5] != ctx->last_report_buf[5]) { + state_changed = true; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, report_buf[5] * 257 - 32768); + } + if (report_buf[6] != ctx->last_report_buf[6]) { + state_changed = true; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, report_buf[6] * 257 - 32768); + } + if (report_buf[7] != ctx->last_report_buf[7]) { + state_changed = true; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, report_buf[7] * 257 - 32768); + } + break; + } + case USB_DEVICE_ID_LOGITECH_DFGT_WHEEL:{ + Uint16 x = report_buf[4]; + Uint16 last_x = ctx->last_report_buf[4]; + x = x | (report_buf[5] & 0x3F) << 8; + last_x = last_x | (ctx->last_report_buf[5] & 0x3F) << 8; + if (x != last_x) { + state_changed = true; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, x * 4 - 32768); + } + if (report_buf[6] != ctx->last_report_buf[6]) { + state_changed = true; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, report_buf[6] * 257 - 32768); + } + if (report_buf[7] != ctx->last_report_buf[7]) { + state_changed = true; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, report_buf[7] * 257 - 32768); + } + break; + } + case USB_DEVICE_ID_LOGITECH_DFP_WHEEL:{ + Uint16 x = report_buf[0]; + Uint16 last_x = ctx->last_report_buf[0]; + x = x | (report_buf[1] & 0x3F) << 8; + last_x = last_x | (ctx->last_report_buf[1] & 0x3F) << 8; + if (x != last_x) { + state_changed = true; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, lg4ff_adjust_dfp_x_axis(x, ctx->range) * 4 - 32768); + } + if (report_buf[5] != ctx->last_report_buf[5]) { + state_changed = true; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, report_buf[5] * 257 - 32768); + } + if (report_buf[6] != ctx->last_report_buf[6]) { + state_changed = true; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, report_buf[6] * 257 - 32768); + } + break; + } + case USB_DEVICE_ID_LOGITECH_WHEEL:{ + if (report_buf[3] != ctx->last_report_buf[3]) { + state_changed = true; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, report_buf[3] * 257 - 32768); + } + if (report_buf[4] != ctx->last_report_buf[4]) { + state_changed = true; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, report_buf[4] * 257 - 32768); + } + if (report_buf[5] != ctx->last_report_buf[5]) { + state_changed = true; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, report_buf[5] * 257 - 32768); + } + if (report_buf[6] != ctx->last_report_buf[6]) { + state_changed = true; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, report_buf[7] * 257 - 32768); + } + break; + } + default: + SDL_assert(0); + } + + SDL_memcpy(ctx->last_report_buf, report_buf, report_size); + return state_changed; +} + +static bool HIDAPI_DriverLg4ff_UpdateDevice(SDL_HIDAPI_Device *device) +{ + SDL_Joystick *joystick = NULL; + int r; + Uint8 report_buf[32] = {0}; + size_t report_size = 0; + SDL_DriverLg4ff_Context *ctx = (SDL_DriverLg4ff_Context *)device->context; + + if (device->num_joysticks > 0) { + joystick = SDL_GetJoystickFromID(device->joysticks[0]); + if (joystick == NULL) { + return false; + } + } else { + return false; + } + + switch (device->product_id) { + case USB_DEVICE_ID_LOGITECH_G29_WHEEL: + report_size = 12; + break; + case USB_DEVICE_ID_LOGITECH_G27_WHEEL: + case USB_DEVICE_ID_LOGITECH_G25_WHEEL: + report_size = 11; + break; + case USB_DEVICE_ID_LOGITECH_DFGT_WHEEL: + case USB_DEVICE_ID_LOGITECH_DFP_WHEEL: + report_size = 8; + break; + case USB_DEVICE_ID_LOGITECH_WHEEL: + report_size = 27; + break; + default: + SDL_assert(0); + } + + do { + r = SDL_hid_read(device->dev, report_buf, report_size); + if (r < 0) { + /* Failed to read from controller */ + HIDAPI_JoystickDisconnected(device, device->joysticks[0]); + return false; + } else if ((size_t)r == report_size) { + bool state_changed = HIDAPI_DriverLg4ff_HandleState(device, joystick, report_buf, report_size); + if(state_changed && !ctx->initialized) { + ctx->initialized = true; + HIDAPI_DriverLg4ff_SetRange(device, SDL_HIDAPI_DriverLg4ff_GetEnvInt("SDL_HIDAPI_LG4FF_RANGE", 40, 900, 900)); + HIDAPI_DriverLg4ff_SetAutoCenter(device, 0); + } + } + } while (r > 0); + + return true; +} + +static bool HIDAPI_DriverLg4ff_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ + SDL_AssertJoysticksLocked(); + + // Initialize the joystick capabilities + joystick->nhats = 1; + joystick->nbuttons = HIDAPI_DriverLg4ff_GetNumberOfButtons(device->product_id); + switch(device->product_id){ + case USB_DEVICE_ID_LOGITECH_G29_WHEEL: + case USB_DEVICE_ID_LOGITECH_G27_WHEEL: + case USB_DEVICE_ID_LOGITECH_G25_WHEEL: + case USB_DEVICE_ID_LOGITECH_WHEEL: + joystick->naxes = 4; + break; + case USB_DEVICE_ID_LOGITECH_DFGT_WHEEL: + joystick->naxes = 3; + break; + case USB_DEVICE_ID_LOGITECH_DFP_WHEEL: + joystick->naxes = 3; + break; + default: + SDL_assert(0); + } + + return true; +} + +static bool HIDAPI_DriverLg4ff_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) +{ + return SDL_Unsupported(); +} + +static bool HIDAPI_DriverLg4ff_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble) +{ + return SDL_Unsupported(); +} + +static Uint32 HIDAPI_DriverLg4ff_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ + switch(device->product_id) { + case USB_DEVICE_ID_LOGITECH_G29_WHEEL: + case USB_DEVICE_ID_LOGITECH_G27_WHEEL: + return SDL_JOYSTICK_CAP_MONO_LED; + default: + return 0; + } +} + +/* + Commands by: + Michal Malý + Simon Wood + lg4ff_led_set_brightness lg4ff_set_leds + `git blame v6.12 drivers/hid/hid-lg4ff.c`, https://github.com/torvalds/linux.git +*/ +static bool HIDAPI_DriverLg4ff_SendLedCommand(SDL_HIDAPI_Device *device, Uint8 state) +{ + Uint8 cmd[7]; + Uint8 led_state = 0; + + switch (state) { + case 0: + led_state = 0; + break; + case 1: + led_state = 1; + break; + case 2: + led_state = 3; + break; + case 3: + led_state = 7; + break; + case 4: + led_state = 15; + break; + case 5: + led_state = 31; + break; + default: + SDL_assert(0); + } + + cmd[0] = 0xf8; + cmd[1] = 0x12; + cmd[2] = led_state; + cmd[3] = 0x00; + cmd[4] = 0x00; + cmd[5] = 0x00; + cmd[6] = 0x00; + + return SDL_hid_write(device->dev, cmd, sizeof(cmd)) == sizeof(cmd); +} + +static bool HIDAPI_DriverLg4ff_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue) +{ + int max_led = red; + + // only g27/g29, and g923 when supported is added + if (device->product_id != USB_DEVICE_ID_LOGITECH_G29_WHEEL && + device->product_id != USB_DEVICE_ID_LOGITECH_G27_WHEEL) { + return SDL_Unsupported(); + } + + if (green > max_led) { + max_led = green; + } + if (blue > max_led) { + max_led = blue; + } + + return HIDAPI_DriverLg4ff_SendLedCommand(device, (Uint8)((5 * max_led) / 255)); +} + +static bool HIDAPI_DriverLg4ff_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size) +{ + // allow programs to send raw commands + return SDL_hid_write(device->dev, data, size) == size; +} + +static bool HIDAPI_DriverLg4ff_SetSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled) +{ + // On steam deck, sensors are enabled by default. Nothing to do here. + return SDL_Unsupported(); +} + +static void HIDAPI_DriverLg4ff_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ + // remember to stop effects on haptics close, when implemented + HIDAPI_DriverLg4ff_SetJoystickLED(device, joystick, 0, 0, 0); +} + +static void HIDAPI_DriverLg4ff_FreeDevice(SDL_HIDAPI_Device *device) +{ + // device context is freed in SDL_hidapijoystick.c +} + + +SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverLg4ff = { + SDL_HINT_JOYSTICK_HIDAPI_LG4FF, + true, + HIDAPI_DriverLg4ff_RegisterHints, + HIDAPI_DriverLg4ff_UnregisterHints, + HIDAPI_DriverLg4ff_IsEnabled, + HIDAPI_DriverLg4ff_IsSupportedDevice, + HIDAPI_DriverLg4ff_InitDevice, + HIDAPI_DriverLg4ff_GetDevicePlayerIndex, + HIDAPI_DriverLg4ff_SetDevicePlayerIndex, + HIDAPI_DriverLg4ff_UpdateDevice, + HIDAPI_DriverLg4ff_OpenJoystick, + HIDAPI_DriverLg4ff_RumbleJoystick, + HIDAPI_DriverLg4ff_RumbleJoystickTriggers, + HIDAPI_DriverLg4ff_GetJoystickCapabilities, + HIDAPI_DriverLg4ff_SetJoystickLED, + HIDAPI_DriverLg4ff_SendJoystickEffect, + HIDAPI_DriverLg4ff_SetSensorsEnabled, + HIDAPI_DriverLg4ff_CloseJoystick, + HIDAPI_DriverLg4ff_FreeDevice, +}; + + +#endif /* SDL_JOYSTICK_HIDAPI_LG4FF */ + +#endif /* SDL_JOYSTICK_HIDAPI */ diff --git a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_steamdeck.c b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_steamdeck.c index 75a13cc1..213b2449 100644 --- a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_steamdeck.c +++ b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_steamdeck.c @@ -233,6 +233,18 @@ static void HIDAPI_DriverSteamDeck_HandleState(SDL_HIDAPI_Device *device, values[1] = (pInReport->payload.deckState.sAccelZ / 32768.0f) * 2.0f * SDL_STANDARD_GRAVITY; values[2] = (-pInReport->payload.deckState.sAccelY / 32768.0f) * 2.0f * SDL_STANDARD_GRAVITY; SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, ctx->sensor_timestamp_us, values, 3); + + SDL_SendJoystickTouchpad(timestamp, joystick, 0, 0, + pInReport->payload.deckState.sPressurePadLeft > 0, + pInReport->payload.deckState.sLeftPadX / 65536.0f + 0.5f, + pInReport->payload.deckState.sLeftPadY / 65536.0f + 0.5f, + pInReport->payload.deckState.sPressurePadLeft / 32768.0f); + + SDL_SendJoystickTouchpad(timestamp, joystick, 1, 0, + pInReport->payload.deckState.sPressurePadRight > 0, + pInReport->payload.deckState.sRightPadX / 65536.0f + 0.5f, + pInReport->payload.deckState.sRightPadY / 65536.0f + 0.5f, + pInReport->payload.deckState.sPressurePadRight / 32768.0f); } /*****************************************************************************************************/ @@ -366,6 +378,9 @@ static bool HIDAPI_DriverSteamDeck_OpenJoystick(SDL_HIDAPI_Device *device, SDL_J SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, update_rate_in_hz); SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, update_rate_in_hz); + SDL_PrivateJoystickAddTouchpad(joystick, 1); + SDL_PrivateJoystickAddTouchpad(joystick, 1); + return true; } diff --git a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_switch.c b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_switch.c index 0e7b823c..82420808 100644 --- a/libs/SDL3/src/joystick/hidapi/SDL_hidapi_switch.c +++ b/libs/SDL3/src/joystick/hidapi/SDL_hidapi_switch.c @@ -58,9 +58,7 @@ #define SWITCH_GYRO_SCALE 14.2842f #define SWITCH_ACCEL_SCALE 4096.f -#define SWITCH_GYRO_SCALE_OFFSET 13371.0f #define SWITCH_GYRO_SCALE_MULT 936.0f -#define SWITCH_ACCEL_SCALE_OFFSET 16384.0f #define SWITCH_ACCEL_SCALE_MULT 4.0f enum @@ -981,7 +979,7 @@ static bool LoadStickCalibration(SDL_DriverSwitch_Context *ctx) /* Stick calibration values are 12-bits each and are packed by bit * For whatever reason the fields are in a different order for each stick * Left: X-Max, Y-Max, X-Center, Y-Center, X-Min, Y-Min - * Right: X-Center, Y-Center, X-Min, Y-Min, X-Max, Y-Max + * Right: X-Center, Y-Center, X-Max, Y-Max, X-Min, Y-Min */ // Left stick @@ -995,10 +993,10 @@ static bool LoadStickCalibration(SDL_DriverSwitch_Context *ctx) // Right stick ctx->m_StickCalData[1].axis[0].sCenter = ((pRightStickCal[1] << 8) & 0xF00) | pRightStickCal[0]; // X Axis center ctx->m_StickCalData[1].axis[1].sCenter = (pRightStickCal[2] << 4) | (pRightStickCal[1] >> 4); // Y Axis center - ctx->m_StickCalData[1].axis[0].sMin = ((pRightStickCal[4] << 8) & 0xF00) | pRightStickCal[3]; // X Axis min below center - ctx->m_StickCalData[1].axis[1].sMin = (pRightStickCal[5] << 4) | (pRightStickCal[4] >> 4); // Y Axis min below center - ctx->m_StickCalData[1].axis[0].sMax = ((pRightStickCal[7] << 8) & 0xF00) | pRightStickCal[6]; // X Axis max above center - ctx->m_StickCalData[1].axis[1].sMax = (pRightStickCal[8] << 4) | (pRightStickCal[7] >> 4); // Y Axis max above center + ctx->m_StickCalData[1].axis[0].sMax = ((pRightStickCal[4] << 8) & 0xF00) | pRightStickCal[3]; // X Axis max above center + ctx->m_StickCalData[1].axis[1].sMax = (pRightStickCal[5] << 4) | (pRightStickCal[4] >> 4); // Y Axis max above center + ctx->m_StickCalData[1].axis[0].sMin = ((pRightStickCal[7] << 8) & 0xF00) | pRightStickCal[6]; // X Axis min below center + ctx->m_StickCalData[1].axis[1].sMin = (pRightStickCal[8] << 4) | (pRightStickCal[7] >> 4); // Y Axis min below center // Filter out any values that were uninitialized (0xFFF) in the SPI read for (stick = 0; stick < 2; ++stick) { @@ -1044,6 +1042,8 @@ static bool LoadIMUCalibration(SDL_DriverSwitch_Context *ctx) if (WriteSubcommand(ctx, k_eSwitchSubcommandIDs_SPIFlashRead, (uint8_t *)&readParams, sizeof(readParams), &reply)) { Uint8 *pIMUScale; Sint16 sAccelRawX, sAccelRawY, sAccelRawZ, sGyroRawX, sGyroRawY, sGyroRawZ; + Sint16 sAccelSensCoeffX, sAccelSensCoeffY, sAccelSensCoeffZ; + Sint16 sGyroSensCoeffX, sGyroSensCoeffY, sGyroSensCoeffZ; // IMU scale gives us multipliers for converting raw values to real world values pIMUScale = reply->spiReadData.rgucReadData; @@ -1052,10 +1052,18 @@ static bool LoadIMUCalibration(SDL_DriverSwitch_Context *ctx) sAccelRawY = (pIMUScale[3] << 8) | pIMUScale[2]; sAccelRawZ = (pIMUScale[5] << 8) | pIMUScale[4]; + sAccelSensCoeffX = (pIMUScale[7] << 8) | pIMUScale[6]; + sAccelSensCoeffY = (pIMUScale[9] << 8) | pIMUScale[8]; + sAccelSensCoeffZ = (pIMUScale[11] << 8) | pIMUScale[10]; + sGyroRawX = (pIMUScale[13] << 8) | pIMUScale[12]; sGyroRawY = (pIMUScale[15] << 8) | pIMUScale[14]; sGyroRawZ = (pIMUScale[17] << 8) | pIMUScale[16]; + sGyroSensCoeffX = (pIMUScale[19] << 8) | pIMUScale[18]; + sGyroSensCoeffY = (pIMUScale[21] << 8) | pIMUScale[20]; + sGyroSensCoeffZ = (pIMUScale[23] << 8) | pIMUScale[22]; + // Check for user calibration data. If it's present and set, it'll override the factory settings readParams.unAddress = k_unSPIIMUUserScaleStartOffset; readParams.ucLength = k_unSPIIMUUserScaleLength; @@ -1072,14 +1080,14 @@ static bool LoadIMUCalibration(SDL_DriverSwitch_Context *ctx) } // Accelerometer scale - ctx->m_IMUScaleData.fAccelScaleX = SWITCH_ACCEL_SCALE_MULT / (SWITCH_ACCEL_SCALE_OFFSET - (float)sAccelRawX) * SDL_STANDARD_GRAVITY; - ctx->m_IMUScaleData.fAccelScaleY = SWITCH_ACCEL_SCALE_MULT / (SWITCH_ACCEL_SCALE_OFFSET - (float)sAccelRawY) * SDL_STANDARD_GRAVITY; - ctx->m_IMUScaleData.fAccelScaleZ = SWITCH_ACCEL_SCALE_MULT / (SWITCH_ACCEL_SCALE_OFFSET - (float)sAccelRawZ) * SDL_STANDARD_GRAVITY; + ctx->m_IMUScaleData.fAccelScaleX = SWITCH_ACCEL_SCALE_MULT / ((float)sAccelSensCoeffX - (float)sAccelRawX) * SDL_STANDARD_GRAVITY; + ctx->m_IMUScaleData.fAccelScaleY = SWITCH_ACCEL_SCALE_MULT / ((float)sAccelSensCoeffY - (float)sAccelRawY) * SDL_STANDARD_GRAVITY; + ctx->m_IMUScaleData.fAccelScaleZ = SWITCH_ACCEL_SCALE_MULT / ((float)sAccelSensCoeffZ - (float)sAccelRawZ) * SDL_STANDARD_GRAVITY; // Gyro scale - ctx->m_IMUScaleData.fGyroScaleX = SWITCH_GYRO_SCALE_MULT / (SWITCH_GYRO_SCALE_OFFSET - (float)sGyroRawX) * SDL_PI_F / 180.0f; - ctx->m_IMUScaleData.fGyroScaleY = SWITCH_GYRO_SCALE_MULT / (SWITCH_GYRO_SCALE_OFFSET - (float)sGyroRawY) * SDL_PI_F / 180.0f; - ctx->m_IMUScaleData.fGyroScaleZ = SWITCH_GYRO_SCALE_MULT / (SWITCH_GYRO_SCALE_OFFSET - (float)sGyroRawZ) * SDL_PI_F / 180.0f; + ctx->m_IMUScaleData.fGyroScaleX = SWITCH_GYRO_SCALE_MULT / ((float)sGyroSensCoeffX - (float)sGyroRawX) * SDL_PI_F / 180.0f; + ctx->m_IMUScaleData.fGyroScaleY = SWITCH_GYRO_SCALE_MULT / ((float)sGyroSensCoeffY - (float)sGyroRawY) * SDL_PI_F / 180.0f; + ctx->m_IMUScaleData.fGyroScaleZ = SWITCH_GYRO_SCALE_MULT / ((float)sGyroSensCoeffZ - (float)sGyroRawZ) * SDL_PI_F / 180.0f; } else { // Use default values @@ -1101,14 +1109,17 @@ static Sint16 ApplyStickCalibration(SDL_DriverSwitch_Context *ctx, int nStick, i { sRawValue -= ctx->m_StickCalData[nStick].axis[nAxis].sCenter; - if (sRawValue > ctx->m_StickExtents[nStick].axis[nAxis].sMax) { - ctx->m_StickExtents[nStick].axis[nAxis].sMax = sRawValue; + if (sRawValue >= 0) { + if (sRawValue > ctx->m_StickExtents[nStick].axis[nAxis].sMax) { + ctx->m_StickExtents[nStick].axis[nAxis].sMax = sRawValue; + } + return (Sint16)HIDAPI_RemapVal(sRawValue, 0, ctx->m_StickExtents[nStick].axis[nAxis].sMax, 0, SDL_MAX_SINT16); + } else { + if (sRawValue < ctx->m_StickExtents[nStick].axis[nAxis].sMin) { + ctx->m_StickExtents[nStick].axis[nAxis].sMin = sRawValue; + } + return (Sint16)HIDAPI_RemapVal(sRawValue, ctx->m_StickExtents[nStick].axis[nAxis].sMin, 0, SDL_MIN_SINT16, 0); } - if (sRawValue < ctx->m_StickExtents[nStick].axis[nAxis].sMin) { - ctx->m_StickExtents[nStick].axis[nAxis].sMin = sRawValue; - } - - return (Sint16)HIDAPI_RemapVal(sRawValue, ctx->m_StickExtents[nStick].axis[nAxis].sMin, ctx->m_StickExtents[nStick].axis[nAxis].sMax, SDL_MIN_SINT16, SDL_MAX_SINT16); } static Sint16 ApplySimpleStickCalibration(SDL_DriverSwitch_Context *ctx, int nStick, int nAxis, Sint16 sRawValue) @@ -1118,14 +1129,17 @@ static Sint16 ApplySimpleStickCalibration(SDL_DriverSwitch_Context *ctx, int nSt sRawValue -= usJoystickCenter; - if (sRawValue > ctx->m_SimpleStickExtents[nStick].axis[nAxis].sMax) { - ctx->m_SimpleStickExtents[nStick].axis[nAxis].sMax = sRawValue; + if (sRawValue >= 0) { + if (sRawValue > ctx->m_SimpleStickExtents[nStick].axis[nAxis].sMax) { + ctx->m_SimpleStickExtents[nStick].axis[nAxis].sMax = sRawValue; + } + return (Sint16)HIDAPI_RemapVal(sRawValue, 0, ctx->m_SimpleStickExtents[nStick].axis[nAxis].sMax, 0, SDL_MAX_SINT16); + } else { + if (sRawValue < ctx->m_SimpleStickExtents[nStick].axis[nAxis].sMin) { + ctx->m_SimpleStickExtents[nStick].axis[nAxis].sMin = sRawValue; + } + return (Sint16)HIDAPI_RemapVal(sRawValue, ctx->m_SimpleStickExtents[nStick].axis[nAxis].sMin, 0, SDL_MIN_SINT16, 0); } - if (sRawValue < ctx->m_SimpleStickExtents[nStick].axis[nAxis].sMin) { - ctx->m_SimpleStickExtents[nStick].axis[nAxis].sMin = sRawValue; - } - - return (Sint16)HIDAPI_RemapVal(sRawValue, ctx->m_SimpleStickExtents[nStick].axis[nAxis].sMin, ctx->m_SimpleStickExtents[nStick].axis[nAxis].sMax, SDL_MIN_SINT16, SDL_MAX_SINT16); } static Uint8 RemapButton(SDL_DriverSwitch_Context *ctx, Uint8 button) @@ -1367,7 +1381,7 @@ static void UpdateDeviceIdentity(SDL_HIDAPI_Device *device) if (ctx->m_bInputOnly) { if (SDL_IsJoystickGameCube(device->vendor_id, device->product_id)) { - device->type = SDL_GAMEPAD_TYPE_STANDARD; + device->type = SDL_GAMEPAD_TYPE_GAMECUBE; } } else { char serial[18]; diff --git a/libs/SDL3/src/joystick/hidapi/SDL_hidapijoystick.c b/libs/SDL3/src/joystick/hidapi/SDL_hidapijoystick.c index 6293519c..5d26deaf 100644 --- a/libs/SDL3/src/joystick/hidapi/SDL_hidapijoystick.c +++ b/libs/SDL3/src/joystick/hidapi/SDL_hidapijoystick.c @@ -82,9 +82,21 @@ static SDL_HIDAPI_DeviceDriver *SDL_HIDAPI_drivers[] = { &SDL_HIDAPI_DriverXbox360, &SDL_HIDAPI_DriverXbox360W, #endif +#ifdef SDL_JOYSTICK_HIDAPI_GIP + &SDL_HIDAPI_DriverGIP, +#endif #ifdef SDL_JOYSTICK_HIDAPI_XBOXONE &SDL_HIDAPI_DriverXboxOne, #endif +#ifdef SDL_JOYSTICK_HIDAPI_LG4FF + &SDL_HIDAPI_DriverLg4ff, +#endif +#ifdef SDL_JOYSTICK_HIDAPI_8BITDO + &SDL_HIDAPI_Driver8BitDo, +#endif +#ifdef SDL_JOYSTICK_HIDAPI_FLYDIGI + &SDL_HIDAPI_DriverFlydigi, +#endif }; static int SDL_HIDAPI_numdrivers = 0; static SDL_AtomicInt SDL_HIDAPI_updating_devices; @@ -288,9 +300,13 @@ static SDL_GamepadType SDL_GetJoystickGameControllerProtocol(const char *name, U 0x1532, // Razer 0x20d6, // PowerA 0x24c6, // PowerA + 0x294b, // Snakebyte 0x2dc8, // 8BitDo 0x2e24, // Hyperkin + 0x2e95, // SCUF + 0x3285, // Nacon 0x3537, // GameSir + 0x366c, // ByoWave }; int i; @@ -338,7 +354,7 @@ static SDL_HIDAPI_DeviceDriver *HIDAPI_GetDeviceDriver(SDL_HIDAPI_Device *device return NULL; } - if (device->vendor_id != USB_VENDOR_VALVE) { + if (device->vendor_id != USB_VENDOR_VALVE && device->vendor_id != USB_VENDOR_FLYDIGI) { if (device->usage_page && device->usage_page != USAGE_PAGE_GENERIC_DESKTOP) { return NULL; } @@ -751,11 +767,12 @@ bool HIDAPI_JoystickConnected(SDL_HIDAPI_Device *device, SDL_JoystickID *pJoysti ++SDL_HIDAPI_numjoysticks; - SDL_PrivateJoystickAdded(joystickID); - if (pJoystickID) { *pJoystickID = joystickID; } + + SDL_PrivateJoystickAdded(joystickID); + return true; } @@ -1051,6 +1068,11 @@ static bool HIDAPI_CreateCombinedJoyCons(void) info.usage = USB_USAGE_GENERIC_GAMEPAD; info.manufacturer_string = L"Nintendo"; info.product_string = L"Switch Joy-Con (L/R)"; + if (children[0]->is_bluetooth || children[1]->is_bluetooth) { + info.bus_type = SDL_HID_API_BUS_BLUETOOTH; + } else { + info.bus_type = SDL_HID_API_BUS_USB; + } combined = HIDAPI_AddDevice(&info, 2, children); if (combined && combined->driver) { diff --git a/libs/SDL3/src/joystick/hidapi/SDL_hidapijoystick_c.h b/libs/SDL3/src/joystick/hidapi/SDL_hidapijoystick_c.h index 9cd9f400..f6b8ebfa 100644 --- a/libs/SDL3/src/joystick/hidapi/SDL_hidapijoystick_c.h +++ b/libs/SDL3/src/joystick/hidapi/SDL_hidapijoystick_c.h @@ -40,6 +40,10 @@ #define SDL_JOYSTICK_HIDAPI_XBOXONE #define SDL_JOYSTICK_HIDAPI_SHIELD #define SDL_JOYSTICK_HIDAPI_STEAM_HORI +#define SDL_JOYSTICK_HIDAPI_LG4FF +#define SDL_JOYSTICK_HIDAPI_8BITDO +#define SDL_JOYSTICK_HIDAPI_FLYDIGI +#define SDL_JOYSTICK_HIDAPI_GIP // Joystick capability definitions #define SDL_JOYSTICK_CAP_MONO_LED 0x00000001 @@ -139,6 +143,7 @@ typedef struct SDL_HIDAPI_DeviceDriver // HIDAPI device support extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverCombined; extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverGameCube; +extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverGIP; extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverJoyCons; extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverLuna; extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverNintendoClassic; @@ -157,6 +162,9 @@ extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverXbox360; extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverXbox360W; extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverXboxOne; extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverSteamHori; +extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverLg4ff; +extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_Driver8BitDo; +extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverFlydigi; // Return true if a HID device is present and supported as a joystick of the given type extern bool HIDAPI_IsDeviceTypePresent(SDL_GamepadType type); diff --git a/libs/SDL3/src/joystick/usb_ids.h b/libs/SDL3/src/joystick/usb_ids.h index 794beb86..33a8a6cb 100644 --- a/libs/SDL3/src/joystick/usb_ids.h +++ b/libs/SDL3/src/joystick/usb_ids.h @@ -32,6 +32,7 @@ #define USB_VENDOR_BACKBONE 0x358a #define USB_VENDOR_GAMESIR 0x3537 #define USB_VENDOR_DRAGONRISE 0x0079 +#define USB_VENDOR_FLYDIGI 0x04b4 #define USB_VENDOR_GOOGLE 0x18d1 #define USB_VENDOR_HORI 0x0f0d #define USB_VENDOR_HP 0x03f0 @@ -59,18 +60,25 @@ #define USB_VENDOR_VALVE 0x28de #define USB_VENDOR_ZEROPLUS 0x0c12 -#define USB_PRODUCT_8BITDO_XBOX_CONTROLLER1 0x2002 // Ultimate Wired Controller for Xbox -#define USB_PRODUCT_8BITDO_XBOX_CONTROLLER2 0x3106 // Ultimate Wireless / Pro 2 Wired Controller +#define USB_PRODUCT_8BITDO_ULTIMATE2_WIRELESS 0x6012 +#define USB_PRODUCT_8BITDO_SF30_PRO 0x6000 // B + START +#define USB_PRODUCT_8BITDO_SF30_PRO_BT 0x6100 // B + START +#define USB_PRODUCT_8BITDO_SN30_PRO 0x6001 // B + START +#define USB_PRODUCT_8BITDO_SN30_PRO_BT 0x6101 // B + START +#define USB_PRODUCT_8BITDO_PRO_2 0x6003 // mode switch to D +#define USB_PRODUCT_8BITDO_PRO_2_BT 0x6006 // mode switch to D #define USB_PRODUCT_AMAZON_LUNA_CONTROLLER 0x0419 #define USB_PRODUCT_ASTRO_C40_XBOX360 0x0024 #define USB_PRODUCT_BACKBONE_ONE_IOS 0x0103 #define USB_PRODUCT_BACKBONE_ONE_IOS_PS5 0x0104 -#define USB_PRODUCT_GAMESIR_G7 0x1001 +#define USB_PRODUCT_BDA_XB1_CLASSIC 0x581a +#define USB_PRODUCT_BDA_XB1_FIGHTPAD 0x791a +#define USB_PRODUCT_BDA_XB1_SPECTRA_PRO 0x592a #define USB_PRODUCT_GOOGLE_STADIA_CONTROLLER 0x9400 #define USB_PRODUCT_EVORETRO_GAMECUBE_ADAPTER1 0x1843 -#define USB_PRODUCT_EVORETRO_GAMECUBE_ADAPTER2 0x1846 -#define USB_PRODUCT_HORI_FIGHTING_COMMANDER_OCTA_SERIES_X 0x0150 -#define USB_PRODUCT_HORI_HORIPAD_PRO_SERIES_X 0x014f +#define USB_PRODUCT_EVORETRO_GAMECUBE_ADAPTER2 0x1844 +#define USB_PRODUCT_EVORETRO_GAMECUBE_ADAPTER3 0x1846 +#define USB_PRODUCT_FLYDIGI_GAMEPAD 0x2412 #define USB_PRODUCT_HORI_FIGHTING_STICK_ALPHA_PS4 0x011c #define USB_PRODUCT_HORI_FIGHTING_STICK_ALPHA_PS5 0x0184 #define USB_PRODUCT_HORI_FIGHTING_STICK_ALPHA_PS5 0x0184 @@ -83,6 +91,7 @@ #define USB_PRODUCT_NACON_REVOLUTION_5_PRO_PS4_WIRED 0x0d17 #define USB_PRODUCT_NACON_REVOLUTION_5_PRO_PS5_WIRELESS 0x0d18 #define USB_PRODUCT_NACON_REVOLUTION_5_PRO_PS5_WIRED 0x0d19 +#define USB_PRODUCT_NACON_REVOLUTION_X_UNLIMITED_BT 0x0689 #define USB_PRODUCT_NINTENDO_GAMECUBE_ADAPTER 0x0337 #define USB_PRODUCT_NINTENDO_N64_CONTROLLER 0x2019 #define USB_PRODUCT_NINTENDO_SEGA_GENESIS_CONTROLLER 0x201e @@ -96,6 +105,8 @@ #define USB_PRODUCT_NINTENDO_WII_REMOTE2 0x0330 #define USB_PRODUCT_NVIDIA_SHIELD_CONTROLLER_V103 0x7210 #define USB_PRODUCT_NVIDIA_SHIELD_CONTROLLER_V104 0x7214 +#define USB_PRODUCT_PDP_ROCK_CANDY 0x0246 +#define USB_PRODUCT_POWERA_MINI 0x541a #define USB_PRODUCT_RAZER_ATROX 0x0a00 #define USB_PRODUCT_RAZER_KITSUNE 0x1012 #define USB_PRODUCT_RAZER_PANTHERA 0x0401 @@ -112,7 +123,6 @@ #define USB_PRODUCT_RAZER_WOLVERINE_V2_PRO_XBOX_WIRED 0x1010 #define USB_PRODUCT_RAZER_WOLVERINE_V2_PRO_XBOX_WIRELESS 0x1011 #define USB_PRODUCT_RAZER_WOLVERINE_V3_PRO 0x0a3f -#define USB_PRODUCT_ROG_RAIKIRI 0x1a38 #define USB_PRODUCT_SAITEK_CYBORG_V3 0xf622 #define USB_PRODUCT_SHANWAN_DS3 0x0523 #define USB_PRODUCT_SONY_DS3 0x0268 @@ -122,11 +132,10 @@ #define USB_PRODUCT_SONY_DS4_STRIKEPAD 0x05c5 #define USB_PRODUCT_SONY_DS5 0x0ce6 #define USB_PRODUCT_SONY_DS5_EDGE 0x0df2 +#define USB_PRODUCT_STEALTH_ULTRA_WIRED 0x7073 #define USB_PRODUCT_SWITCH_RETROBIT_CONTROLLER 0x0575 #define USB_PRODUCT_THRUSTMASTER_ESWAPX_PRO_PS4 0xd00e -#define USB_PRODUCT_THRUSTMASTER_ESWAPX_PRO_SERIES_X 0xd012 -#define USB_PRODUCT_TURTLE_BEACH_SERIES_X_REACT_R 0x7013 -#define USB_PRODUCT_TURTLE_BEACH_SERIES_X_RECON 0x7009 +#define USB_PRODUCT_THRUSTMASTER_T_FLIGHT_HOTAS_ONE 0xb68c #define USB_PRODUCT_VALVE_STEAM_CONTROLLER_DONGLE 0x1142 #define USB_PRODUCT_VICTRIX_FS_PRO 0x0203 #define USB_PRODUCT_VICTRIX_FS_PRO_V2 0x0207 @@ -148,23 +157,13 @@ #define USB_PRODUCT_XBOX_ONE_S_REV2_BLE 0x0b20 #define USB_PRODUCT_XBOX_SERIES_X 0x0b12 #define USB_PRODUCT_XBOX_SERIES_X_BLE 0x0b13 -#define USB_PRODUCT_XBOX_SERIES_X_HP_HYPERX 0x08b6 -#define USB_PRODUCT_XBOX_SERIES_X_HP_HYPERX_RGB 0x07a0 -#define USB_PRODUCT_XBOX_SERIES_X_PDP_AFTERGLOW 0x02da -#define USB_PRODUCT_XBOX_SERIES_X_PDP_BLUE 0x02d9 -#define USB_PRODUCT_XBOX_SERIES_X_POWERA_FUSION_PRO2 0x4001 -#define USB_PRODUCT_XBOX_SERIES_X_POWERA_FUSION_PRO4 0x400b -#define USB_PRODUCT_XBOX_SERIES_X_POWERA_FUSION_PRO_WIRELESS_USB 0x4014 -#define USB_PRODUCT_XBOX_SERIES_X_POWERA_FUSION_PRO_WIRELESS_DONGLE 0x4016 -#define USB_PRODUCT_XBOX_SERIES_X_POWERA_MOGA_XP_ULTRA 0x890b -#define USB_PRODUCT_XBOX_SERIES_X_POWERA_SPECTRA 0x4002 -#define USB_PRODUCT_XBOX_SERIES_X_VICTRIX_GAMBIT 0x02d6 #define USB_PRODUCT_XBOX_ONE_XBOXGIP_CONTROLLER 0x02ff // XBOXGIP driver software PID #define USB_PRODUCT_STEAM_VIRTUAL_GAMEPAD 0x11ff // USB usage pages #define USB_USAGEPAGE_GENERIC_DESKTOP 0x0001 #define USB_USAGEPAGE_BUTTON 0x0009 +#define USB_USAGEPAGE_VENDOR_FLYDIGI 0xFFA0 // USB usages for USAGE_PAGE_GENERIC_DESKTOP #define USB_USAGE_GENERIC_POINTER 0x0001 diff --git a/libs/SDL3/src/joystick/windows/SDL_dinputjoystick.c b/libs/SDL3/src/joystick/windows/SDL_dinputjoystick.c index b00218d9..a9638823 100644 --- a/libs/SDL3/src/joystick/windows/SDL_dinputjoystick.c +++ b/libs/SDL3/src/joystick/windows/SDL_dinputjoystick.c @@ -293,7 +293,7 @@ static bool QueryDeviceName(LPDIRECTINPUTDEVICE8 device, Uint16 vendor_id, Uint1 } *manufacturer_string = NULL; - *product_string = WIN_StringToUTF8(dipstr.wsz); + *product_string = WIN_StringToUTF8W(dipstr.wsz); return true; } diff --git a/libs/SDL3/src/joystick/windows/SDL_rawinputjoystick.c b/libs/SDL3/src/joystick/windows/SDL_rawinputjoystick.c index 3e2b270f..8590d9a8 100644 --- a/libs/SDL3/src/joystick/windows/SDL_rawinputjoystick.c +++ b/libs/SDL3/src/joystick/windows/SDL_rawinputjoystick.c @@ -158,6 +158,7 @@ struct joystick_hwdata Uint8 wgi_correlation_count; Uint8 wgi_uncorrelate_count; WindowsGamingInputGamepadState *wgi_slot; + struct __x_ABI_CWindows_CGaming_CInput_CGamepadVibration vibration; #endif bool triggers_rumbling; @@ -449,7 +450,6 @@ typedef struct WindowsGamingInputGamepadState bool used; // Is currently mapped to an SDL device bool connected; // Just used during update to track disconnected Uint8 correlation_id; - struct __x_ABI_CWindows_CGaming_CInput_CGamepadVibration vibration; } WindowsGamingInputGamepadState; static struct @@ -1030,7 +1030,7 @@ static bool RAWINPUT_JoystickInit(void) { SDL_assert(!SDL_RAWINPUT_inited); - if (!SDL_GetHintBoolean(SDL_HINT_JOYSTICK_RAWINPUT, true)) { + if (!SDL_GetHintBoolean(SDL_HINT_JOYSTICK_RAWINPUT, false)) { return true; } @@ -1482,12 +1482,11 @@ static bool RAWINPUT_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency #ifdef SDL_JOYSTICK_RAWINPUT_WGI // Save off the motor state in case trigger rumble is started - WindowsGamingInputGamepadState *gamepad_state = ctx->wgi_slot; - HRESULT hr; - gamepad_state->vibration.LeftMotor = (DOUBLE)low_frequency_rumble / SDL_MAX_UINT16; - gamepad_state->vibration.RightMotor = (DOUBLE)high_frequency_rumble / SDL_MAX_UINT16; + ctx->vibration.LeftMotor = (DOUBLE)low_frequency_rumble / SDL_MAX_UINT16; + ctx->vibration.RightMotor = (DOUBLE)high_frequency_rumble / SDL_MAX_UINT16; if (!rumbled && ctx->wgi_correlated) { - hr = __x_ABI_CWindows_CGaming_CInput_CIGamepad_put_Vibration(gamepad_state->gamepad, gamepad_state->vibration); + WindowsGamingInputGamepadState *gamepad_state = ctx->wgi_slot; + HRESULT hr = __x_ABI_CWindows_CGaming_CInput_CIGamepad_put_Vibration(gamepad_state->gamepad, ctx->vibration); if (SUCCEEDED(hr)) { rumbled = true; } @@ -1509,12 +1508,11 @@ static bool RAWINPUT_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left_ #ifdef SDL_JOYSTICK_RAWINPUT_WGI RAWINPUT_DeviceContext *ctx = joystick->hwdata; + ctx->vibration.LeftTrigger = (DOUBLE)left_rumble / SDL_MAX_UINT16; + ctx->vibration.RightTrigger = (DOUBLE)right_rumble / SDL_MAX_UINT16; if (ctx->wgi_correlated) { WindowsGamingInputGamepadState *gamepad_state = ctx->wgi_slot; - HRESULT hr; - gamepad_state->vibration.LeftTrigger = (DOUBLE)left_rumble / SDL_MAX_UINT16; - gamepad_state->vibration.RightTrigger = (DOUBLE)right_rumble / SDL_MAX_UINT16; - hr = __x_ABI_CWindows_CGaming_CInput_CIGamepad_put_Vibration(gamepad_state->gamepad, gamepad_state->vibration); + HRESULT hr = __x_ABI_CWindows_CGaming_CInput_CIGamepad_put_Vibration(gamepad_state->gamepad, ctx->vibration); if (!SUCCEEDED(hr)) { return SDL_SetError("Setting vibration failed: 0x%lx", hr); } diff --git a/libs/SDL3/src/locale/ngage/SDL_syslocale.cpp b/libs/SDL3/src/locale/ngage/SDL_syslocale.cpp new file mode 100644 index 00000000..4ab0eb73 --- /dev/null +++ b/libs/SDL3/src/locale/ngage/SDL_syslocale.cpp @@ -0,0 +1,307 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "../SDL_syslocale.h" +#include "SDL_internal.h" + +#include +#include +#include +#include + +bool SDL_SYS_GetPreferredLocales(char *buf, size_t buflen) +{ + TLanguage language = User::Language(); + const char *locale; + + switch (language) { + case ELangFrench: + case ELangSwissFrench: + locale = "fr_CH"; + break; + case ELangBelgianFrench: + locale = "fr_BE"; + break; + case ELangInternationalFrench: + locale = "fr_FR"; + break; + case ELangGerman: + case ELangSwissGerman: + case ELangAustrian: + locale = "de_DE"; + break; + case ELangSpanish: + case ELangInternationalSpanish: + case ELangLatinAmericanSpanish: + locale = "es_ES"; + break; + case ELangItalian: + case ELangSwissItalian: + locale = "it_IT"; + break; + case ELangSwedish: + case ELangFinlandSwedish: + locale = "sv_SE"; + break; + case ELangDanish: + locale = "da_DK"; + break; + case ELangNorwegian: + case ELangNorwegianNynorsk: + locale = "no_NO"; + break; + case ELangFinnish: + locale = "fi_FI"; + break; + case ELangPortuguese: + locale = "pt_PT"; + break; + case ELangBrazilianPortuguese: + locale = "pt_BR"; + break; + case ELangTurkish: + case ELangCyprusTurkish: + locale = "tr_TR"; + break; + case ELangIcelandic: + locale = "is_IS"; + break; + case ELangRussian: + locale = "ru_RU"; + break; + case ELangHungarian: + locale = "hu_HU"; + break; + case ELangDutch: + locale = "nl_NL"; + break; + case ELangBelgianFlemish: + locale = "nl_BE"; + break; + case ELangAustralian: + case ELangNewZealand: + locale = "en_AU"; + break; + case ELangCzech: + locale = "cs_CZ"; + break; + case ELangSlovak: + locale = "sk_SK"; + break; + case ELangPolish: + locale = "pl_PL"; + break; + case ELangSlovenian: + locale = "sl_SI"; + break; + case ELangTaiwanChinese: + locale = "zh_TW"; + break; + case ELangHongKongChinese: + locale = "zh_HK"; + break; + case ELangPrcChinese: + locale = "zh_CN"; + break; + case ELangJapanese: + locale = "ja_JP"; + break; + case ELangThai: + locale = "th_TH"; + break; + case ELangAfrikaans: + locale = "af_ZA"; + break; + case ELangAlbanian: + locale = "sq_AL"; + break; + case ELangAmharic: + locale = "am_ET"; + break; + case ELangArabic: + locale = "ar_SA"; + break; + case ELangArmenian: + locale = "hy_AM"; + break; + case ELangAzerbaijani: + locale = "az_AZ"; + break; + case ELangBelarussian: + locale = "be_BY"; + break; + case ELangBengali: + locale = "bn_IN"; + break; + case ELangBulgarian: + locale = "bg_BG"; + break; + case ELangBurmese: + locale = "my_MM"; + break; + case ELangCatalan: + locale = "ca_ES"; + break; + case ELangCroatian: + locale = "hr_HR"; + break; + case ELangEstonian: + locale = "et_EE"; + break; + case ELangFarsi: + locale = "fa_IR"; + break; + case ELangCanadianFrench: + locale = "fr_CA"; + break; + case ELangScotsGaelic: + locale = "gd_GB"; + break; + case ELangGeorgian: + locale = "ka_GE"; + break; + case ELangGreek: + case ELangCyprusGreek: + locale = "el_GR"; + break; + case ELangGujarati: + locale = "gu_IN"; + break; + case ELangHebrew: + locale = "he_IL"; + break; + case ELangHindi: + locale = "hi_IN"; + break; + case ELangIndonesian: + locale = "id_ID"; + break; + case ELangIrish: + locale = "ga_IE"; + break; + case ELangKannada: + locale = "kn_IN"; + break; + case ELangKazakh: + locale = "kk_KZ"; + break; + case ELangKhmer: + locale = "km_KH"; + break; + case ELangKorean: + locale = "ko_KR"; + break; + case ELangLao: + locale = "lo_LA"; + break; + case ELangLatvian: + locale = "lv_LV"; + break; + case ELangLithuanian: + locale = "lt_LT"; + break; + case ELangMacedonian: + locale = "mk_MK"; + break; + case ELangMalay: + locale = "ms_MY"; + break; + case ELangMalayalam: + locale = "ml_IN"; + break; + case ELangMarathi: + locale = "mr_IN"; + break; + case ELangMoldavian: + locale = "ro_MD"; + break; + case ELangMongolian: + locale = "mn_MN"; + break; + case ELangPunjabi: + locale = "pa_IN"; + break; + case ELangRomanian: + locale = "ro_RO"; + break; + case ELangSerbian: + locale = "sr_RS"; + break; + case ELangSinhalese: + locale = "si_LK"; + break; + case ELangSomali: + locale = "so_SO"; + break; + case ELangSwahili: + locale = "sw_KE"; + break; + case ELangTajik: + locale = "tg_TJ"; + break; + case ELangTamil: + locale = "ta_IN"; + break; + case ELangTelugu: + locale = "te_IN"; + break; + case ELangTibetan: + locale = "bo_CN"; + break; + case ELangTigrinya: + locale = "ti_ET"; + break; + case ELangTurkmen: + locale = "tk_TM"; + break; + case ELangUkrainian: + locale = "uk_UA"; + break; + case ELangUrdu: + locale = "ur_PK"; + break; + case ELangUzbek: + locale = "uz_UZ"; + break; + case ELangVietnamese: + locale = "vi_VN"; + break; + case ELangWelsh: + locale = "cy_GB"; + break; + case ELangZulu: + locale = "zu_ZA"; + break; + case ELangEnglish: + locale = "en_GB"; + break; + case ELangAmerican: + case ELangCanadianEnglish: + case ELangInternationalEnglish: + case ELangSouthAfricanEnglish: + default: + locale = "en_US"; + break; + } + + SDL_strlcpy(buf, locale, buflen); + + return true; +} diff --git a/libs/SDL3/src/main/SDL_main_callbacks.c b/libs/SDL3/src/main/SDL_main_callbacks.c index 9f3d9c13..913c9a71 100644 --- a/libs/SDL3/src/main/SDL_main_callbacks.c +++ b/libs/SDL3/src/main/SDL_main_callbacks.c @@ -94,7 +94,7 @@ bool SDL_HasMainCallbacks(void) return false; } -SDL_AppResult SDL_InitMainCallbacks(int argc, char* argv[], SDL_AppInit_func appinit, SDL_AppIterate_func appiter, SDL_AppEvent_func appevent, SDL_AppQuit_func appquit) +SDL_AppResult SDL_InitMainCallbacks(int argc, char *argv[], SDL_AppInit_func appinit, SDL_AppIterate_func appiter, SDL_AppEvent_func appevent, SDL_AppQuit_func appquit) { SDL_main_iteration_callback = appiter; SDL_main_event_callback = appevent; diff --git a/libs/SDL3/src/main/SDL_runapp.c b/libs/SDL3/src/main/SDL_runapp.c index ddda4a63..eccdd3fc 100644 --- a/libs/SDL3/src/main/SDL_runapp.c +++ b/libs/SDL3/src/main/SDL_runapp.c @@ -24,7 +24,7 @@ * If not, you can special case it here by appending || defined(__YOUR_PLATFORM__) */ #if ( !defined(SDL_MAIN_NEEDED) && !defined(SDL_MAIN_AVAILABLE) ) || defined(SDL_PLATFORM_ANDROID) -int SDL_RunApp(int argc, char* argv[], SDL_main_func mainFunction, void * reserved) +int SDL_RunApp(int argc, char *argv[], SDL_main_func mainFunction, void * reserved) { (void)reserved; @@ -32,7 +32,7 @@ int SDL_RunApp(int argc, char* argv[], SDL_main_func mainFunction, void * reserv { // make sure argv isn't NULL, in case some user code doesn't like that static char dummyargv0[] = { 'S', 'D', 'L', '_', 'a', 'p', 'p', '\0' }; - static char* argvdummy[2] = { dummyargv0, NULL }; + static char *argvdummy[2] = { dummyargv0, NULL }; argc = 1; argv = argvdummy; } diff --git a/libs/SDL3/src/main/emscripten/SDL_sysmain_callbacks.c b/libs/SDL3/src/main/emscripten/SDL_sysmain_callbacks.c index d3b2f95f..babffb3b 100644 --- a/libs/SDL3/src/main/emscripten/SDL_sysmain_callbacks.c +++ b/libs/SDL3/src/main/emscripten/SDL_sysmain_callbacks.c @@ -34,7 +34,7 @@ static void EmscriptenInternalMainloop(void) } } -int SDL_EnterAppMainCallbacks(int argc, char* argv[], SDL_AppInit_func appinit, SDL_AppIterate_func appiter, SDL_AppEvent_func appevent, SDL_AppQuit_func appquit) +int SDL_EnterAppMainCallbacks(int argc, char *argv[], SDL_AppInit_func appinit, SDL_AppIterate_func appiter, SDL_AppEvent_func appevent, SDL_AppQuit_func appquit) { const SDL_AppResult rc = SDL_InitMainCallbacks(argc, argv, appinit, appiter, appevent, appquit); if (rc == SDL_APP_CONTINUE) { diff --git a/libs/SDL3/src/main/emscripten/SDL_sysmain_runapp.c b/libs/SDL3/src/main/emscripten/SDL_sysmain_runapp.c index 20dd6ebe..8e2252ee 100644 --- a/libs/SDL3/src/main/emscripten/SDL_sysmain_runapp.c +++ b/libs/SDL3/src/main/emscripten/SDL_sysmain_runapp.c @@ -26,7 +26,7 @@ EM_JS_DEPS(sdlrunapp, "$dynCall,$stringToNewUTF8"); -int SDL_RunApp(int argc, char* argv[], SDL_main_func mainFunction, void * reserved) +int SDL_RunApp(int argc, char *argv[], SDL_main_func mainFunction, void * reserved) { (void)reserved; diff --git a/libs/SDL3/src/main/gdk/SDL_sysmain_runapp.cpp b/libs/SDL3/src/main/gdk/SDL_sysmain_runapp.cpp index d7bdea0a..5a202259 100644 --- a/libs/SDL3/src/main/gdk/SDL_sysmain_runapp.cpp +++ b/libs/SDL3/src/main/gdk/SDL_sysmain_runapp.cpp @@ -40,7 +40,7 @@ static BOOL OutOfMemory(void) /* Gets the arguments with GetCommandLine, converts them to argc and argv and calls SDL_main */ extern "C" -int SDL_RunApp(int, char**, SDL_main_func mainFunction, void *reserved) +int SDL_RunApp(int, char **, SDL_main_func mainFunction, void *reserved) { LPWSTR *argvw; char **argv; diff --git a/libs/SDL3/src/main/generic/SDL_sysmain_callbacks.c b/libs/SDL3/src/main/generic/SDL_sysmain_callbacks.c index 716489f1..afe5dbde 100644 --- a/libs/SDL3/src/main/generic/SDL_sysmain_callbacks.c +++ b/libs/SDL3/src/main/generic/SDL_sysmain_callbacks.c @@ -51,7 +51,7 @@ static SDL_AppResult GenericIterateMainCallbacks(void) return SDL_IterateMainCallbacks(!iterate_after_waitevent); } -int SDL_EnterAppMainCallbacks(int argc, char* argv[], SDL_AppInit_func appinit, SDL_AppIterate_func appiter, SDL_AppEvent_func appevent, SDL_AppQuit_func appquit) +int SDL_EnterAppMainCallbacks(int argc, char *argv[], SDL_AppInit_func appinit, SDL_AppIterate_func appiter, SDL_AppEvent_func appevent, SDL_AppQuit_func appquit) { SDL_AppResult rc = SDL_InitMainCallbacks(argc, argv, appinit, appiter, appevent, appquit); if (rc == 0) { diff --git a/libs/SDL3/src/main/ios/SDL_sysmain_callbacks.m b/libs/SDL3/src/main/ios/SDL_sysmain_callbacks.m index becbda27..8ebaec67 100644 --- a/libs/SDL3/src/main/ios/SDL_sysmain_callbacks.m +++ b/libs/SDL3/src/main/ios/SDL_sysmain_callbacks.m @@ -64,7 +64,7 @@ static SDLIosMainCallbacksDisplayLink *globalDisplayLink; // SDL_RunApp will land in UIApplicationMain, which calls SDL_main from postFinishLaunch, which calls this. // When we return from here, we're living in the RunLoop, and a CADisplayLink is firing regularly for us. -int SDL_EnterAppMainCallbacks(int argc, char* argv[], SDL_AppInit_func appinit, SDL_AppIterate_func appiter, SDL_AppEvent_func appevent, SDL_AppQuit_func appquit) +int SDL_EnterAppMainCallbacks(int argc, char *argv[], SDL_AppInit_func appinit, SDL_AppIterate_func appiter, SDL_AppEvent_func appevent, SDL_AppQuit_func appquit) { SDL_AppResult rc = SDL_InitMainCallbacks(argc, argv, appinit, appiter, appevent, appquit); if (rc == SDL_APP_CONTINUE) { diff --git a/libs/SDL3/src/main/n3ds/SDL_sysmain_runapp.c b/libs/SDL3/src/main/n3ds/SDL_sysmain_runapp.c index 74ead997..06e4bf41 100644 --- a/libs/SDL3/src/main/n3ds/SDL_sysmain_runapp.c +++ b/libs/SDL3/src/main/n3ds/SDL_sysmain_runapp.c @@ -25,7 +25,7 @@ #include <3ds.h> -int SDL_RunApp(int argc, char* argv[], SDL_main_func mainFunction, void * reserved) +int SDL_RunApp(int argc, char *argv[], SDL_main_func mainFunction, void * reserved) { int result; // init diff --git a/libs/SDL3/src/main/ngage/SDL_sysmain_callbacks.c b/libs/SDL3/src/main/ngage/SDL_sysmain_callbacks.c new file mode 100644 index 00000000..7a6e7ecc --- /dev/null +++ b/libs/SDL3/src/main/ngage/SDL_sysmain_callbacks.c @@ -0,0 +1,31 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +#ifdef SDL_PLATFORM_NGAGE + +int SDL_EnterAppMainCallbacks(int argc, char *argv[], SDL_AppInit_func appinit, SDL_AppIterate_func appiter, SDL_AppEvent_func appevent, SDL_AppQuit_func appquit) +{ + // Intentionally does nothing; Callbacks are called using the RunL() method. + return 0; +} + +#endif // SDL_PLATFORM_NGAGE diff --git a/libs/SDL3/src/main/ngage/SDL_sysmain_main.cpp b/libs/SDL3/src/main/ngage/SDL_sysmain_main.cpp new file mode 100644 index 00000000..c03634fb --- /dev/null +++ b/libs/SDL3/src/main/ngage/SDL_sysmain_main.cpp @@ -0,0 +1,199 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#ifdef __cplusplus +extern "C" { +#endif + +#include "SDL_internal.h" + +extern SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]); +extern SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event); +extern SDL_AppResult SDL_AppIterate(void *appstate); +extern void SDL_AppQuit(void *appstate, SDL_AppResult result); + +#ifdef __cplusplus +} +#endif + +#include +#include +#include +#include + +#include "SDL_sysmain_main.hpp" +#include "../../audio/ngage/SDL_ngageaudio.hpp" +#include "../../render/ngage/SDL_render_ngage_c.hpp" + +CRenderer *gRenderer = 0; + +GLDEF_C TInt E32Main() +{ + // Get args and environment. + int argc = 1; + char *argv[] = { "game", NULL }; + char **envp = NULL; + + // Create lvalue variables for __crt0 arguments. + char **argv_lvalue = argv; + char **envp_lvalue = envp; + + CTrapCleanup *cleanup = CTrapCleanup::New(); + if (!cleanup) + { + return KErrNoMemory; + } + + TRAPD(err, + { + CActiveScheduler *scheduler = new (ELeave) CActiveScheduler(); + CleanupStack::PushL(scheduler); + CActiveScheduler::Install(scheduler); + + TInt posixErr = SpawnPosixServerThread(); + if (posixErr != KErrNone) + { + SDL_Log("Error: Failed to spawn POSIX server thread: %d", posixErr); + User::Leave(posixErr); + } + + __crt0(argc, argv_lvalue, envp_lvalue); + + // Increase heap size. + RHeap *newHeap = User::ChunkHeap(NULL, 7500000, 7500000, KMinHeapGrowBy); + if (!newHeap) + { + SDL_Log("Error: Failed to create new heap"); + User::Leave(KErrNoMemory); + } + CleanupStack::PushL(newHeap); + + RHeap *oldHeap = User::SwitchHeap(newHeap); + + TInt targetLatency = 225; + InitAudio(&targetLatency); + + // Wait until audio is ready. + while (!AudioIsReady()) + { + User::After(100000); // 100ms. + } + + // Create and start the rendering backend. + gRenderer = CRenderer::NewL(); + CleanupStack::PushL(gRenderer); + + // Create and start the SDL main runner. + CSDLmain *mainApp = CSDLmain::NewL(); + CleanupStack::PushL(mainApp); + mainApp->Start(); + + // Start the active scheduler to handle events. + CActiveScheduler::Start(); + + CleanupStack::PopAndDestroy(gRenderer); + CleanupStack::PopAndDestroy(mainApp); + + User::SwitchHeap(oldHeap); + + CleanupStack::PopAndDestroy(newHeap); + CleanupStack::PopAndDestroy(scheduler); + }); + + if (err != KErrNone) + { + SDL_Log("Error: %d", err); + } + + return err; +} + +CSDLmain *CSDLmain::NewL() +{ + CSDLmain *self = new (ELeave) CSDLmain(); + CleanupStack::PushL(self); + self->ConstructL(); + CleanupStack::Pop(self); + return self; +} + +CSDLmain::CSDLmain() : CActive(EPriorityLow) {} + +void CSDLmain::ConstructL() +{ + CActiveScheduler::Add(this); +} + +CSDLmain::~CSDLmain() +{ + Cancel(); +} + +void CSDLmain::Start() +{ + SetActive(); + TRequestStatus *status = &iStatus; + User::RequestComplete(status, KErrNone); +} + +void CSDLmain::DoCancel() {} + +static bool callbacks_initialized = false; + +void CSDLmain::RunL() +{ + if (callbacks_initialized) + { + SDL_Event event; + + iResult = SDL_AppIterate(NULL); + if (iResult != SDL_APP_CONTINUE) + { + DeinitAudio(); + SDL_AppQuit(NULL, iResult); + SDL_Quit(); + CActiveScheduler::Stop(); + return; + } + + SDL_PumpEvents(); + if (SDL_PollEvent(&event)) + { + iResult = SDL_AppEvent(NULL, &event); + if (iResult != SDL_APP_CONTINUE) + { + DeinitAudio(); + SDL_AppQuit(NULL, iResult); + SDL_Quit(); + CActiveScheduler::Stop(); + return; + } + } + + Start(); + } + else + { + SDL_SetMainReady(); + SDL_AppInit(NULL, 0, NULL); + callbacks_initialized = true; + Start(); + } +} diff --git a/libs/SDL3/src/main/ngage/SDL_sysmain_main.hpp b/libs/SDL3/src/main/ngage/SDL_sysmain_main.hpp new file mode 100644 index 00000000..da99b6f4 --- /dev/null +++ b/libs/SDL3/src/main/ngage/SDL_sysmain_main.hpp @@ -0,0 +1,46 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +#ifndef SDL_sysmain_main_hpp_ +#define SDL_sysmain_main_hpp_ + +#include + +class CSDLmain : public CActive +{ +public: + static CSDLmain *NewL(); + ~CSDLmain(); + + void Start(); + +protected: + void DoCancel() ; + void RunL(); + +private: + CSDLmain(); + void ConstructL(); + SDL_AppResult iResult; +}; + +#endif // SDL_sysmain_main_hpp_ diff --git a/libs/SDL3/src/main/ps2/SDL_sysmain_runapp.c b/libs/SDL3/src/main/ps2/SDL_sysmain_runapp.c index 23443c5a..658bce30 100644 --- a/libs/SDL3/src/main/ps2/SDL_sysmain_runapp.c +++ b/libs/SDL3/src/main/ps2/SDL_sysmain_runapp.c @@ -64,7 +64,7 @@ static void deinit_drivers(void) deinit_ps2_filesystem_driver(); } -int SDL_RunApp(int argc, char* argv[], SDL_main_func mainFunction, void * reserved) +int SDL_RunApp(int argc, char *argv[], SDL_main_func mainFunction, void * reserved) { int res; (void)reserved; diff --git a/libs/SDL3/src/main/psp/SDL_sysmain_runapp.c b/libs/SDL3/src/main/psp/SDL_sysmain_runapp.c index e89f5ffe..7f09914b 100644 --- a/libs/SDL3/src/main/psp/SDL_sysmain_runapp.c +++ b/libs/SDL3/src/main/psp/SDL_sysmain_runapp.c @@ -69,7 +69,7 @@ int sdl_psp_setup_callbacks(void) return thid; } -int SDL_RunApp(int argc, char* argv[], SDL_main_func mainFunction, void * reserved) +int SDL_RunApp(int argc, char *argv[], SDL_main_func mainFunction, void * reserved) { (void)reserved; sdl_psp_setup_callbacks(); diff --git a/libs/SDL3/src/main/windows/SDL_sysmain_runapp.c b/libs/SDL3/src/main/windows/SDL_sysmain_runapp.c index d8c3fac1..d2055397 100644 --- a/libs/SDL3/src/main/windows/SDL_sysmain_runapp.c +++ b/libs/SDL3/src/main/windows/SDL_sysmain_runapp.c @@ -36,7 +36,7 @@ static int OutOfMemory(void) return -1; } -int MINGW32_FORCEALIGN SDL_RunApp(int _argc, char* _argv[], SDL_main_func mainFunction, void * reserved) +int MINGW32_FORCEALIGN SDL_RunApp(int _argc, char *_argv[], SDL_main_func mainFunction, void * reserved) { /* Gets the arguments with GetCommandLine, converts them to argc and argv and calls SDL_main */ diff --git a/libs/SDL3/src/process/SDL_process.c b/libs/SDL3/src/process/SDL_process.c index 3ccf5d58..5db6db49 100644 --- a/libs/SDL3/src/process/SDL_process.c +++ b/libs/SDL3/src/process/SDL_process.c @@ -45,10 +45,18 @@ SDL_Process *SDL_CreateProcess(const char * const *args, bool pipe_stdio) SDL_Process *SDL_CreateProcessWithProperties(SDL_PropertiesID props) { const char * const *args = SDL_GetPointerProperty(props, SDL_PROP_PROCESS_CREATE_ARGS_POINTER, NULL); +#if defined(SDL_PLATFORM_WINDOWS) + const char *cmdline = SDL_GetStringProperty(props, SDL_PROP_PROCESS_CREATE_CMDLINE_STRING, NULL); + if ((!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"); + return NULL; + } +#else if (!args || !args[0] || !args[0][0]) { SDL_InvalidParamError("SDL_PROP_PROCESS_CREATE_ARGS_POINTER"); return NULL; } +#endif SDL_Process *process = (SDL_Process *)SDL_calloc(1, sizeof(*process)); if (!process) { diff --git a/libs/SDL3/src/process/posix/SDL_posixprocess.c b/libs/SDL3/src/process/posix/SDL_posixprocess.c index 98b9f75b..2e05b013 100644 --- a/libs/SDL3/src/process/posix/SDL_posixprocess.c +++ b/libs/SDL3/src/process/posix/SDL_posixprocess.c @@ -37,6 +37,12 @@ #include "../../io/SDL_iostream_c.h" +#if defined(HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDCHDIR_NP) && \ + !defined(HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDCHDIR) +#define HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDCHDIR +#define posix_spawn_file_actions_addchdir posix_spawn_file_actions_addchdir_np +#endif + #define READ_END 0 #define WRITE_END 1 @@ -156,6 +162,7 @@ bool SDL_SYS_CreateProcessWithProperties(SDL_Process *process, SDL_PropertiesID char * const *args = SDL_GetPointerProperty(props, SDL_PROP_PROCESS_CREATE_ARGS_POINTER, NULL); SDL_Environment *env = SDL_GetPointerProperty(props, SDL_PROP_PROCESS_CREATE_ENVIRONMENT_POINTER, SDL_GetEnvironment()); char **envp = NULL; + const char *working_directory = SDL_GetStringProperty(props, SDL_PROP_PROCESS_CREATE_WORKING_DIRECTORY_STRING, NULL); SDL_ProcessIO stdin_option = (SDL_ProcessIO)SDL_GetNumberProperty(props, SDL_PROP_PROCESS_CREATE_STDIN_NUMBER, SDL_PROCESS_STDIO_NULL); SDL_ProcessIO stdout_option = (SDL_ProcessIO)SDL_GetNumberProperty(props, SDL_PROP_PROCESS_CREATE_STDOUT_NUMBER, SDL_PROCESS_STDIO_INHERITED); SDL_ProcessIO stderr_option = (SDL_ProcessIO)SDL_GetNumberProperty(props, SDL_PROP_PROCESS_CREATE_STDERR_NUMBER, SDL_PROCESS_STDIO_INHERITED); @@ -192,6 +199,30 @@ bool SDL_SYS_CreateProcessWithProperties(SDL_Process *process, SDL_PropertiesID goto posix_spawn_fail_attr; } + if (working_directory) { +#ifdef HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDCHDIR +#ifdef SDL_PLATFORM_APPLE + if (__builtin_available(macOS 10.15, *)) { + if (posix_spawn_file_actions_addchdir(&fa, working_directory) != 0) { + SDL_SetError("posix_spawn_file_actions_addchdir failed: %s", strerror(errno)); + goto posix_spawn_fail_all; + } + } else { + SDL_SetError("Setting the working directory is only supported on macOS 10.15 and newer"); + goto posix_spawn_fail_all; + } +#else + if (posix_spawn_file_actions_addchdir(&fa, working_directory) != 0) { + SDL_SetError("posix_spawn_file_actions_addchdir failed: %s", strerror(errno)); + goto posix_spawn_fail_all; + } +#endif // SDL_PLATFORM_APPLE +#else + SDL_SetError("Setting the working directory is not supported"); + goto posix_spawn_fail_all; +#endif + } + // Background processes don't have access to the terminal if (process->background) { if (stdin_option == SDL_PROCESS_STDIO_INHERITED) { diff --git a/libs/SDL3/src/process/windows/SDL_windowsprocess.c b/libs/SDL3/src/process/windows/SDL_windowsprocess.c index c1aee5c4..65d8a394 100644 --- a/libs/SDL3/src/process/windows/SDL_windowsprocess.c +++ b/libs/SDL3/src/process/windows/SDL_windowsprocess.c @@ -106,9 +106,12 @@ static bool join_arguments(const char * const *args, LPWSTR *args_out) len = 0; for (i = 0; args[i]; i++) { const char *a = args[i]; + bool quotes = *a == '\0' || SDL_strpbrk(a, " \r\n\t\v") != NULL; - /* two double quotes to surround an argument with */ - len += 2; + if (quotes) { + /* surround the argument with double quote if it is empty or contains whitespaces */ + len += 2; + } for (; *a; a++) { switch (*a) { @@ -116,8 +119,8 @@ static bool join_arguments(const char * const *args, LPWSTR *args_out) len += 2; break; case '\\': - /* only escape backslashes that precede a double quote */ - len += (a[1] == '"' || a[1] == '\0') ? 2 : 1; + /* only escape backslashes that precede a double quote (including the enclosing double quote) */ + len += (a[1] == '"' || (quotes && a[1] == '\0')) ? 2 : 1; break; case ' ': case '^': @@ -149,8 +152,11 @@ static bool join_arguments(const char * const *args, LPWSTR *args_out) i_out = 0; for (i = 0; args[i]; i++) { const char *a = args[i]; + bool quotes = *a == '\0' || SDL_strpbrk(a, " \r\n\t\v") != NULL; - result[i_out++] = '"'; + if (quotes) { + result[i_out++] = '"'; + } for (; *a; a++) { switch (*a) { case '"': @@ -163,7 +169,7 @@ static bool join_arguments(const char * const *args, LPWSTR *args_out) break; case '\\': result[i_out++] = *a; - if (a[1] == '"' || a[1] == '\0') { + if (a[1] == '"' || (quotes && a[1] == '\0')) { result[i_out++] = *a; } break; @@ -188,7 +194,9 @@ static bool join_arguments(const char * const *args, LPWSTR *args_out) break; } } - result[i_out++] = '"'; + if (quotes) { + result[i_out++] = '"'; + } result[i_out++] = ' '; } SDL_assert(i_out == len); @@ -237,8 +245,10 @@ static bool join_env(char **env, LPWSTR *env_out) bool SDL_SYS_CreateProcessWithProperties(SDL_Process *process, SDL_PropertiesID props) { const char * const *args = SDL_GetPointerProperty(props, SDL_PROP_PROCESS_CREATE_ARGS_POINTER, NULL); + const char *cmdline = SDL_GetStringProperty(props, SDL_PROP_PROCESS_CREATE_CMDLINE_STRING, NULL); SDL_Environment *env = SDL_GetPointerProperty(props, SDL_PROP_PROCESS_CREATE_ENVIRONMENT_POINTER, SDL_GetEnvironment()); char **envp = NULL; + const char *working_directory = SDL_GetStringProperty(props, SDL_PROP_PROCESS_CREATE_WORKING_DIRECTORY_STRING, NULL); SDL_ProcessIO stdin_option = (SDL_ProcessIO)SDL_GetNumberProperty(props, SDL_PROP_PROCESS_CREATE_STDIN_NUMBER, SDL_PROCESS_STDIO_NULL); SDL_ProcessIO stdout_option = (SDL_ProcessIO)SDL_GetNumberProperty(props, SDL_PROP_PROCESS_CREATE_STDOUT_NUMBER, SDL_PROCESS_STDIO_INHERITED); SDL_ProcessIO stderr_option = (SDL_ProcessIO)SDL_GetNumberProperty(props, SDL_PROP_PROCESS_CREATE_STDERR_NUMBER, SDL_PROCESS_STDIO_INHERITED); @@ -246,6 +256,7 @@ bool SDL_SYS_CreateProcessWithProperties(SDL_Process *process, SDL_PropertiesID !SDL_HasProperty(props, SDL_PROP_PROCESS_CREATE_STDERR_NUMBER); LPWSTR createprocess_cmdline = NULL; LPWSTR createprocess_env = NULL; + LPWSTR createprocess_cwd = NULL; STARTUPINFOW startup_info; DWORD creation_flags; SECURITY_ATTRIBUTES security_attributes; @@ -284,7 +295,12 @@ bool SDL_SYS_CreateProcessWithProperties(SDL_Process *process, SDL_PropertiesID security_attributes.bInheritHandle = TRUE; security_attributes.lpSecurityDescriptor = NULL; - if (!join_arguments(args, &createprocess_cmdline)) { + if (cmdline) { + createprocess_cmdline = WIN_UTF8ToString(cmdline); + if (!createprocess_cmdline) { + goto done; + } + } else if (!join_arguments(args, &createprocess_cmdline)) { goto done; } @@ -292,6 +308,13 @@ bool SDL_SYS_CreateProcessWithProperties(SDL_Process *process, SDL_PropertiesID goto done; } + if (working_directory) { + createprocess_cwd = WIN_UTF8ToStringW(working_directory); + if (!createprocess_cwd) { + goto done; + } + } + // Background processes don't have access to the terminal // This isn't necessary on Windows, but we keep the same behavior as the POSIX implementation. if (process->background) { @@ -304,6 +327,7 @@ bool SDL_SYS_CreateProcessWithProperties(SDL_Process *process, SDL_PropertiesID if (stderr_option == SDL_PROCESS_STDIO_INHERITED) { stderr_option = SDL_PROCESS_STDIO_NULL; } + creation_flags |= CREATE_NO_WINDOW; } switch (stdin_option) { @@ -329,7 +353,7 @@ bool SDL_SYS_CreateProcessWithProperties(SDL_Process *process, SDL_PropertiesID startup_info.hStdInput = stdin_pipe[READ_END]; break; case SDL_PROCESS_STDIO_NULL: - startup_info.hStdInput = CreateFile(TEXT("\\\\.\\NUL"), GENERIC_ALL, 0, &security_attributes, OPEN_EXISTING, 0, NULL); + startup_info.hStdInput = CreateFile(TEXT("\\\\.\\NUL"), (GENERIC_READ | GENERIC_WRITE), 0, &security_attributes, OPEN_EXISTING, 0, NULL); break; case SDL_PROCESS_STDIO_INHERITED: default: @@ -366,7 +390,7 @@ bool SDL_SYS_CreateProcessWithProperties(SDL_Process *process, SDL_PropertiesID startup_info.hStdOutput = stdout_pipe[WRITE_END]; break; case SDL_PROCESS_STDIO_NULL: - startup_info.hStdOutput = CreateFile(TEXT("\\\\.\\NUL"), GENERIC_ALL, 0, &security_attributes, OPEN_EXISTING, 0, NULL); + startup_info.hStdOutput = CreateFile(TEXT("\\\\.\\NUL"), (GENERIC_READ | GENERIC_WRITE), 0, &security_attributes, OPEN_EXISTING, 0, NULL); break; case SDL_PROCESS_STDIO_INHERITED: default: @@ -412,7 +436,7 @@ bool SDL_SYS_CreateProcessWithProperties(SDL_Process *process, SDL_PropertiesID startup_info.hStdError = stderr_pipe[WRITE_END]; break; case SDL_PROCESS_STDIO_NULL: - startup_info.hStdError = CreateFile(TEXT("\\\\.\\NUL"), GENERIC_ALL, 0, &security_attributes, OPEN_EXISTING, 0, NULL); + startup_info.hStdError = CreateFile(TEXT("\\\\.\\NUL"), (GENERIC_READ | GENERIC_WRITE), 0, &security_attributes, OPEN_EXISTING, 0, NULL); break; case SDL_PROCESS_STDIO_INHERITED: default: @@ -427,7 +451,7 @@ bool SDL_SYS_CreateProcessWithProperties(SDL_Process *process, SDL_PropertiesID } } - if (!CreateProcessW(NULL, createprocess_cmdline, NULL, NULL, TRUE, creation_flags, createprocess_env, NULL, &startup_info, &data->process_information)) { + if (!CreateProcessW(NULL, createprocess_cmdline, NULL, NULL, TRUE, creation_flags, createprocess_env, createprocess_cwd, &startup_info, &data->process_information)) { WIN_SetError("CreateProcess"); goto done; } @@ -479,6 +503,7 @@ done: } SDL_free(createprocess_cmdline); SDL_free(createprocess_env); + SDL_free(createprocess_cwd); SDL_free(envp); if (!result) { diff --git a/libs/SDL3/src/render/SDL_render.c b/libs/SDL3/src/render/SDL_render.c index 60024b85..0a088a8b 100644 --- a/libs/SDL3/src/render/SDL_render.c +++ b/libs/SDL3/src/render/SDL_render.c @@ -120,6 +120,9 @@ static const SDL_RenderDriver *render_drivers[] = { #ifdef SDL_VIDEO_RENDER_METAL &METAL_RenderDriver, #endif +#ifdef SDL_VIDEO_RENDER_NGAGE + &NGAGE_RenderDriver, +#endif #ifdef SDL_VIDEO_RENDER_OGL &GL_RenderDriver, #endif @@ -344,6 +347,16 @@ static bool FlushRenderCommandsIfTextureNeeded(SDL_Texture *texture) return true; } +static bool FlushRenderCommandsIfGPURenderStateNeeded(SDL_GPURenderState *state) +{ + SDL_Renderer *renderer = state->renderer; + if (state->last_command_generation == renderer->render_command_generation) { + // the current command queue depends on this state, flush the queue now before it changes + return FlushRenderCommands(renderer); + } + return true; +} + bool SDL_FlushRenderer(SDL_Renderer *renderer) { if (!FlushRenderCommands(renderer)) { @@ -574,7 +587,12 @@ static SDL_RenderCommand *PrepQueueCmdDraw(SDL_Renderer *renderer, const SDL_Ren if (texture) { cmd->data.draw.texture_scale_mode = texture->scaleMode; } - cmd->data.draw.texture_address_mode = SDL_TEXTURE_ADDRESS_CLAMP; + cmd->data.draw.texture_address_mode_u = SDL_TEXTURE_ADDRESS_CLAMP; + cmd->data.draw.texture_address_mode_v = SDL_TEXTURE_ADDRESS_CLAMP; + cmd->data.draw.gpu_render_state = renderer->gpu_render_state; + if (renderer->gpu_render_state) { + renderer->gpu_render_state->last_command_generation = renderer->render_command_generation; + } } } return cmd; @@ -713,13 +731,15 @@ static bool QueueCmdGeometry(SDL_Renderer *renderer, SDL_Texture *texture, const float *uv, int uv_stride, int num_vertices, const void *indices, int num_indices, int size_indices, - float scale_x, float scale_y, SDL_TextureAddressMode texture_address_mode) + float scale_x, float scale_y, + SDL_TextureAddressMode texture_address_mode_u, SDL_TextureAddressMode texture_address_mode_v) { SDL_RenderCommand *cmd; bool result = false; cmd = PrepQueueCmdDraw(renderer, SDL_RENDERCMD_GEOMETRY, texture); if (cmd) { - cmd->data.draw.texture_address_mode = texture_address_mode; + cmd->data.draw.texture_address_mode_u = texture_address_mode_u; + cmd->data.draw.texture_address_mode_v = texture_address_mode_v; result = renderer->QueueGeometry(renderer, cmd, texture, xy, xy_stride, color, color_stride, uv, uv_stride, @@ -1086,6 +1106,8 @@ SDL_Renderer *SDL_CreateRendererWithProperties(SDL_PropertiesID props) renderer->line_method = SDL_GetRenderLineMethod(); } + renderer->scale_mode = SDL_SCALEMODE_LINEAR; + renderer->SDR_white_point = 1.0f; renderer->HDR_headroom = 1.0f; renderer->desired_color_scale = 1.0f; @@ -1168,6 +1190,37 @@ SDL_Renderer *SDL_CreateRenderer(SDL_Window *window, const char *name) return renderer; } +SDL_Renderer *SDL_CreateGPURenderer(SDL_Window *window, SDL_GPUShaderFormat format_flags, SDL_GPUDevice **device) +{ + if (!device) { + SDL_InvalidParamError("device"); + return NULL; + } + + *device = NULL; + SDL_Renderer *renderer; + + SDL_PropertiesID props = SDL_CreateProperties(); + SDL_SetPointerProperty(props, SDL_PROP_RENDERER_CREATE_WINDOW_POINTER, window); + if (format_flags & SDL_GPU_SHADERFORMAT_SPIRV) { + SDL_SetBooleanProperty(props, SDL_PROP_RENDERER_CREATE_GPU_SHADERS_SPIRV_BOOLEAN, true); + } + if (format_flags & SDL_GPU_SHADERFORMAT_DXIL) { + SDL_SetBooleanProperty(props, SDL_PROP_RENDERER_CREATE_GPU_SHADERS_DXIL_BOOLEAN, true); + } + if (format_flags & SDL_GPU_SHADERFORMAT_MSL) { + SDL_SetBooleanProperty(props, SDL_PROP_RENDERER_CREATE_GPU_SHADERS_MSL_BOOLEAN, true); + } + SDL_SetStringProperty(props, SDL_PROP_RENDERER_CREATE_NAME_STRING, "gpu"); + + renderer = SDL_CreateRendererWithProperties(props); + if (renderer) { + *device = (SDL_GPUDevice *)SDL_GetPointerProperty(SDL_GetRendererProperties(renderer), SDL_PROP_RENDERER_GPU_DEVICE_POINTER, NULL); + } + SDL_DestroyProperties(props); + return renderer; +} + SDL_Renderer *SDL_CreateSoftwareRenderer(SDL_Surface *surface) { #ifdef SDL_VIDEO_RENDER_SW @@ -1392,7 +1445,7 @@ SDL_Texture *SDL_CreateTextureWithProperties(SDL_Renderer *renderer, SDL_Propert texture->color.b = 1.0f; texture->color.a = 1.0f; texture->blendMode = SDL_ISPIXELFORMAT_ALPHA(format) ? SDL_BLENDMODE_BLEND : SDL_BLENDMODE_NONE; - texture->scaleMode = SDL_SCALEMODE_LINEAR; + texture->scaleMode = renderer->scale_mode; texture->view.pixel_w = w; texture->view.pixel_h = h; texture->view.viewport.w = -1; @@ -1658,8 +1711,6 @@ SDL_Texture *SDL_CreateTextureFromSurface(SDL_Renderer *renderer, SDL_Surface *s } } - surface_colorspace = SDL_GetSurfaceColorspace(surface); - // Try to have the best pixel format for the texture // No alpha, but a colorkey => promote to alpha if (!SDL_ISPIXELFORMAT_ALPHA(surface->format) && SDL_SurfaceHasColorKey(surface)) { @@ -1721,6 +1772,9 @@ SDL_Texture *SDL_CreateTextureFromSurface(SDL_Renderer *renderer, SDL_Surface *s } } + surface_colorspace = SDL_GetSurfaceColorspace(surface); + texture_colorspace = surface_colorspace; + if (surface_colorspace == SDL_COLORSPACE_SRGB_LINEAR || SDL_COLORSPACETRANSFER(surface_colorspace) == SDL_TRANSFER_CHARACTERISTICS_PQ) { if (SDL_ISPIXELFORMAT_FLOAT(format)) { @@ -1964,8 +2018,12 @@ bool SDL_SetTextureScaleMode(SDL_Texture *texture, SDL_ScaleMode scaleMode) { CHECK_TEXTURE_MAGIC(texture, false); - if (scaleMode != SDL_SCALEMODE_NEAREST && - scaleMode != SDL_SCALEMODE_LINEAR) { + switch (scaleMode) { + case SDL_SCALEMODE_NEAREST: + case SDL_SCALEMODE_LINEAR: + case SDL_SCALEMODE_PIXELART: + break; + default: return SDL_InvalidParamError("scaleMode"); } @@ -2572,11 +2630,12 @@ static void UpdateLogicalPresentation(SDL_Renderer *renderer) const float logical_h = view->logical_h; int iwidth, iheight; - if (renderer->target) { + if (is_main_view) { + SDL_GetRenderOutputSize(renderer, &iwidth, &iheight); + } else { + SDL_assert(renderer->target != NULL); iwidth = (int)renderer->target->w; iheight = (int)renderer->target->h; - } else { - SDL_GetRenderOutputSize(renderer, &iwidth, &iheight); } view->logical_src_rect.x = 0.0f; @@ -3719,7 +3778,7 @@ bool SDL_RenderLines(SDL_Renderer *renderer, const SDL_FPoint *points, int count result = QueueCmdGeometry(renderer, NULL, xy, xy_stride, &renderer->color, 0 /* color_stride */, NULL, 0, num_vertices, indices, num_indices, size_indices, - 1.0f, 1.0f, SDL_TEXTURE_ADDRESS_CLAMP); + 1.0f, 1.0f, SDL_TEXTURE_ADDRESS_CLAMP, SDL_TEXTURE_ADDRESS_CLAMP); } SDL_small_free(xy, isstack1); @@ -3900,7 +3959,7 @@ static bool SDL_RenderTextureInternal(SDL_Renderer *renderer, SDL_Texture *textu result = QueueCmdGeometry(renderer, texture, xy, xy_stride, &texture->color, 0 /* color_stride */, uv, uv_stride, num_vertices, indices, num_indices, size_indices, - scale_x, scale_y, SDL_TEXTURE_ADDRESS_CLAMP); + scale_x, scale_y, SDL_TEXTURE_ADDRESS_CLAMP, SDL_TEXTURE_ADDRESS_CLAMP); } else { const SDL_FRect rect = { dstrect->x * scale_x, dstrect->y * scale_y, dstrect->w * scale_x, dstrect->h * scale_y }; result = QueueCmdCopy(renderer, texture, srcrect, &rect); @@ -3930,8 +3989,7 @@ bool SDL_RenderTexture(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_F real_srcrect.w = (float)texture->w; real_srcrect.h = (float)texture->h; if (srcrect) { - if (!SDL_GetRectIntersectionFloat(srcrect, &real_srcrect, &real_srcrect) || - real_srcrect.w == 0.0f || real_srcrect.h == 0.0f) { + if (!SDL_GetRectIntersectionFloat(srcrect, &real_srcrect, &real_srcrect)) { return true; } } @@ -4063,7 +4121,7 @@ bool SDL_RenderTextureAffine(SDL_Renderer *renderer, SDL_Texture *texture, &texture->color, 0 /* color_stride */, uv, uv_stride, num_vertices, indices, num_indices, size_indices, - scale_x, scale_y, SDL_TEXTURE_ADDRESS_CLAMP + scale_x, scale_y, SDL_TEXTURE_ADDRESS_CLAMP, SDL_TEXTURE_ADDRESS_CLAMP ); } return result; @@ -4213,7 +4271,7 @@ bool SDL_RenderTextureRotated(SDL_Renderer *renderer, SDL_Texture *texture, result = QueueCmdGeometry(renderer, texture, xy, xy_stride, &texture->color, 0 /* color_stride */, uv, uv_stride, num_vertices, indices, num_indices, size_indices, - scale_x, scale_y, SDL_TEXTURE_ADDRESS_CLAMP); + scale_x, scale_y, SDL_TEXTURE_ADDRESS_CLAMP, SDL_TEXTURE_ADDRESS_CLAMP); } else { result = QueueCmdCopyEx(renderer, texture, &real_srcrect, dstrect, angle, &real_center, flip, scale_x, scale_y); } @@ -4265,7 +4323,8 @@ static bool SDL_RenderTextureTiled_Wrap(SDL_Renderer *renderer, SDL_Texture *tex return QueueCmdGeometry(renderer, texture, xy, xy_stride, &texture->color, 0 /* color_stride */, uv, uv_stride, num_vertices, indices, num_indices, size_indices, - view->current_scale.x, view->current_scale.y, SDL_TEXTURE_ADDRESS_WRAP); + view->current_scale.x, view->current_scale.y, + SDL_TEXTURE_ADDRESS_WRAP, SDL_TEXTURE_ADDRESS_WRAP); } static bool SDL_RenderTextureTiled_Iterate(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_FRect *srcrect, float scale, const SDL_FRect *dstrect) @@ -4519,6 +4578,143 @@ bool SDL_RenderTexture9Grid(SDL_Renderer *renderer, SDL_Texture *texture, const return true; } +bool SDL_RenderTexture9GridTiled(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_FRect *srcrect, float left_width, float right_width, float top_height, float bottom_height, float scale, const SDL_FRect *dstrect, float tileScale) +{ + SDL_FRect full_src, full_dst; + SDL_FRect curr_src, curr_dst; + float dst_left_width; + float dst_right_width; + float dst_top_height; + float dst_bottom_height; + + CHECK_RENDERER_MAGIC(renderer, false); + CHECK_TEXTURE_MAGIC(texture, false); + + if (renderer != texture->renderer) { + return SDL_SetError("Texture was not created with this renderer"); + } + + if (!srcrect) { + full_src.x = 0; + full_src.y = 0; + full_src.w = (float)texture->w; + full_src.h = (float)texture->h; + srcrect = &full_src; + } + + if (!dstrect) { + GetRenderViewportSize(renderer, &full_dst); + dstrect = &full_dst; + } + + if (scale <= 0.0f || scale == 1.0f) { + dst_left_width = SDL_ceilf(left_width); + dst_right_width = SDL_ceilf(right_width); + dst_top_height = SDL_ceilf(top_height); + dst_bottom_height = SDL_ceilf(bottom_height); + } else { + dst_left_width = SDL_ceilf(left_width * scale); + dst_right_width = SDL_ceilf(right_width * scale); + dst_top_height = SDL_ceilf(top_height * scale); + dst_bottom_height = SDL_ceilf(bottom_height * scale); + } + + // Center + curr_src.x = srcrect->x + left_width; + curr_src.y = srcrect->y + top_height; + curr_src.w = srcrect->w - left_width - right_width; + curr_src.h = srcrect->h - top_height - bottom_height; + curr_dst.x = dstrect->x + dst_left_width; + curr_dst.y = dstrect->y + dst_top_height; + curr_dst.w = dstrect->w - dst_left_width - dst_right_width; + curr_dst.h = dstrect->h - dst_top_height - dst_bottom_height; + if (!SDL_RenderTextureTiled(renderer, texture, &curr_src, tileScale, &curr_dst)) { + return false; + } + + // Upper-left corner + curr_src.x = srcrect->x; + curr_src.y = srcrect->y; + curr_src.w = left_width; + curr_src.h = top_height; + curr_dst.x = dstrect->x; + curr_dst.y = dstrect->y; + curr_dst.w = dst_left_width; + curr_dst.h = dst_top_height; + if (!SDL_RenderTexture(renderer, texture, &curr_src, &curr_dst)) { + return false; + } + + // Upper-right corner + curr_src.x = srcrect->x + srcrect->w - right_width; + curr_src.w = right_width; + curr_dst.x = dstrect->x + dstrect->w - dst_right_width; + curr_dst.w = dst_right_width; + if (!SDL_RenderTexture(renderer, texture, &curr_src, &curr_dst)) { + return false; + } + + // Lower-right corner + curr_src.y = srcrect->y + srcrect->h - bottom_height; + curr_src.h = bottom_height; + curr_dst.y = dstrect->y + dstrect->h - dst_bottom_height; + curr_dst.h = dst_bottom_height; + if (!SDL_RenderTexture(renderer, texture, &curr_src, &curr_dst)) { + return false; + } + + // Lower-left corner + curr_src.x = srcrect->x; + curr_src.w = left_width; + curr_dst.x = dstrect->x; + curr_dst.w = dst_left_width; + if (!SDL_RenderTexture(renderer, texture, &curr_src, &curr_dst)) { + return false; + } + + // Left + curr_src.y = srcrect->y + top_height; + curr_src.h = srcrect->h - top_height - bottom_height; + curr_dst.y = dstrect->y + dst_top_height; + curr_dst.h = dstrect->h - dst_top_height - dst_bottom_height; + if (!SDL_RenderTextureTiled(renderer, texture, &curr_src, tileScale, &curr_dst)) { + return false; + } + + // Right + curr_src.x = srcrect->x + srcrect->w - right_width; + curr_src.w = right_width; + curr_dst.x = dstrect->x + dstrect->w - dst_right_width; + curr_dst.w = dst_right_width; + if (!SDL_RenderTextureTiled(renderer, texture, &curr_src, tileScale, &curr_dst)) { + return false; + } + + // Top + curr_src.x = srcrect->x + left_width; + curr_src.y = srcrect->y; + curr_src.w = srcrect->w - left_width - right_width; + curr_src.h = top_height; + curr_dst.x = dstrect->x + dst_left_width; + curr_dst.y = dstrect->y; + curr_dst.w = dstrect->w - dst_left_width - dst_right_width; + curr_dst.h = dst_top_height; + if (!SDL_RenderTextureTiled(renderer, texture, &curr_src, tileScale, &curr_dst)) { + return false; + } + + // Bottom + curr_src.y = srcrect->y + srcrect->h - bottom_height; + curr_src.h = bottom_height; + curr_dst.y = dstrect->y + dstrect->h - dst_bottom_height; + curr_dst.h = dst_bottom_height; + if (!SDL_RenderTextureTiled(renderer, texture, &curr_src, tileScale, &curr_dst)) { + return false; + } + + return true; +} + bool SDL_RenderGeometry(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_Vertex *vertices, int num_vertices, @@ -4875,7 +5071,7 @@ static bool SDLCALL SDL_SW_RenderGeometryRaw(SDL_Renderer *renderer, result = QueueCmdGeometry(renderer, texture, xy, xy_stride, color, color_stride, uv, uv_stride, num_vertices, prev, 3, 4, - scale_x, scale_y, SDL_TEXTURE_ADDRESS_CLAMP); + scale_x, scale_y, SDL_TEXTURE_ADDRESS_CLAMP, SDL_TEXTURE_ADDRESS_CLAMP); if (!result) { goto end; } @@ -4895,7 +5091,7 @@ static bool SDLCALL SDL_SW_RenderGeometryRaw(SDL_Renderer *renderer, result = QueueCmdGeometry(renderer, texture, xy, xy_stride, color, color_stride, uv, uv_stride, num_vertices, prev, 3, 4, - scale_x, scale_y, SDL_TEXTURE_ADDRESS_CLAMP); + scale_x, scale_y, SDL_TEXTURE_ADDRESS_CLAMP, SDL_TEXTURE_ADDRESS_CLAMP); if (!result) { goto end; } @@ -4920,7 +5116,8 @@ bool SDL_RenderGeometryRaw(SDL_Renderer *renderer, { int i; int count = indices ? num_indices : num_vertices; - SDL_TextureAddressMode texture_address_mode; + SDL_TextureAddressMode texture_address_mode_u; + SDL_TextureAddressMode texture_address_mode_v; CHECK_RENDERER_MAGIC(renderer, false); @@ -4975,17 +5172,37 @@ bool SDL_RenderGeometryRaw(SDL_Renderer *renderer, texture = texture->native; } - texture_address_mode = renderer->texture_address_mode; - if (texture_address_mode == SDL_TEXTURE_ADDRESS_AUTO && texture) { - texture_address_mode = SDL_TEXTURE_ADDRESS_CLAMP; + texture_address_mode_u = renderer->texture_address_mode_u; + texture_address_mode_v = renderer->texture_address_mode_v; + if (texture && + (texture_address_mode_u == SDL_TEXTURE_ADDRESS_AUTO || + texture_address_mode_u == SDL_TEXTURE_ADDRESS_AUTO)) { for (i = 0; i < num_vertices; ++i) { const float *uv_ = (const float *)((const char *)uv + i * uv_stride); float u = uv_[0]; float v = uv_[1]; - if (u < 0.0f || v < 0.0f || u > 1.0f || v > 1.0f) { - texture_address_mode = SDL_TEXTURE_ADDRESS_WRAP; - break; + if (u < 0.0f || u > 1.0f) { + if (texture_address_mode_u == SDL_TEXTURE_ADDRESS_AUTO) { + texture_address_mode_u = SDL_TEXTURE_ADDRESS_WRAP; + if (texture_address_mode_v != SDL_TEXTURE_ADDRESS_AUTO) { + break; + } + } } + if (v < 0.0f || v > 1.0f) { + if (texture_address_mode_v == SDL_TEXTURE_ADDRESS_AUTO) { + texture_address_mode_v = SDL_TEXTURE_ADDRESS_WRAP; + if (texture_address_mode_u != SDL_TEXTURE_ADDRESS_AUTO) { + break; + } + } + } + } + if (texture_address_mode_u == SDL_TEXTURE_ADDRESS_AUTO) { + texture_address_mode_u = SDL_TEXTURE_ADDRESS_CLAMP; + } + if (texture_address_mode_v == SDL_TEXTURE_ADDRESS_AUTO) { + texture_address_mode_v = SDL_TEXTURE_ADDRESS_CLAMP; } } @@ -5011,7 +5228,9 @@ bool SDL_RenderGeometryRaw(SDL_Renderer *renderer, // For the software renderer, try to reinterpret triangles as SDL_Rect #ifdef SDL_VIDEO_RENDER_SW - if (renderer->software && texture_address_mode == SDL_TEXTURE_ADDRESS_CLAMP) { + if (renderer->software && + texture_address_mode_u == SDL_TEXTURE_ADDRESS_CLAMP && + texture_address_mode_v == SDL_TEXTURE_ADDRESS_CLAMP) { return SDL_SW_RenderGeometryRaw(renderer, texture, xy, xy_stride, color, color_stride, uv, uv_stride, num_vertices, indices, num_indices, size_indices); @@ -5023,7 +5242,36 @@ bool SDL_RenderGeometryRaw(SDL_Renderer *renderer, xy, xy_stride, color, color_stride, uv, uv_stride, num_vertices, indices, num_indices, size_indices, view->current_scale.x, view->current_scale.y, - texture_address_mode); + texture_address_mode_u, texture_address_mode_v); +} + +bool SDL_SetRenderTextureAddressMode(SDL_Renderer *renderer, SDL_TextureAddressMode u_mode, SDL_TextureAddressMode v_mode) +{ + CHECK_RENDERER_MAGIC(renderer, false); + + renderer->texture_address_mode_u = u_mode; + renderer->texture_address_mode_v = v_mode; + return true; +} + +bool SDL_GetRenderTextureAddressMode(SDL_Renderer *renderer, SDL_TextureAddressMode *u_mode, SDL_TextureAddressMode *v_mode) +{ + if (u_mode) { + *u_mode = SDL_TEXTURE_ADDRESS_INVALID; + } + if (v_mode) { + *v_mode = SDL_TEXTURE_ADDRESS_INVALID; + } + + CHECK_RENDERER_MAGIC(renderer, false); + + if (u_mode) { + *u_mode = renderer->texture_address_mode_u; + } + if (v_mode) { + *v_mode = renderer->texture_address_mode_v; + } + return true; } SDL_Surface *SDL_RenderReadPixels(SDL_Renderer *renderer, const SDL_Rect *rect) @@ -5133,9 +5381,8 @@ bool SDL_RenderPresent(SDL_Renderer *renderer) CHECK_RENDERER_MAGIC(renderer, false); - SDL_Texture *target = renderer->target; - if (target) { - SDL_SetRenderTarget(renderer, NULL); + if (renderer->target) { + return SDL_SetError("You can't present on a render target"); } SDL_RenderLogicalPresentation(renderer); @@ -5156,10 +5403,6 @@ bool SDL_RenderPresent(SDL_Renderer *renderer) presented = false; } - if (target) { - SDL_SetRenderTarget(renderer, target); - } - if (renderer->simulate_vsync || (!presented && renderer->wanted_vsync)) { SDL_SimulateRenderVSync(renderer); @@ -5575,7 +5818,7 @@ static bool CreateDebugTextAtlas(SDL_Renderer *renderer) // Convert temp surface into texture SDL_Texture *texture = SDL_CreateTextureFromSurface(renderer, atlas); if (texture) { - SDL_SetTextureScaleMode(texture, SDL_SCALEMODE_NEAREST); + SDL_SetTextureScaleMode(texture, SDL_SCALEMODE_PIXELART); renderer->debug_char_texture_atlas = texture; } SDL_DestroySurface(atlas); @@ -5665,3 +5908,165 @@ bool SDL_RenderDebugTextFormat(SDL_Renderer *renderer, float x, float y, SDL_PRI SDL_free(str); return retval; } + +bool SDL_SetDefaultTextureScaleMode(SDL_Renderer *renderer, SDL_ScaleMode scale_mode) +{ + CHECK_RENDERER_MAGIC(renderer, false); + + renderer->scale_mode = scale_mode; + + return true; +} + +bool SDL_GetDefaultTextureScaleMode(SDL_Renderer *renderer, SDL_ScaleMode *scale_mode) +{ + if (scale_mode) { + *scale_mode = SDL_SCALEMODE_LINEAR; + } + + CHECK_RENDERER_MAGIC(renderer, false); + + if (scale_mode) { + *scale_mode = renderer->scale_mode; + } + return true; +} + +SDL_GPURenderState *SDL_CreateGPURenderState(SDL_Renderer *renderer, SDL_GPURenderStateDesc *desc) +{ + CHECK_RENDERER_MAGIC(renderer, NULL); + + if (!desc) { + SDL_InvalidParamError("desc"); + return NULL; + } + + if (desc->version < sizeof(*desc)) { + // Update this to handle older versions of this interface + SDL_SetError("Invalid desc, should be initialized with SDL_INIT_INTERFACE()"); + return NULL; + } + + if (!desc->fragment_shader) { + SDL_SetError("desc->fragment_shader is required"); + return NULL; + } + + SDL_GPUDevice *device = (SDL_GPUDevice *)SDL_GetPointerProperty(renderer->props, SDL_PROP_RENDERER_GPU_DEVICE_POINTER, NULL); + if (!device) { + SDL_SetError("Renderer isn't associated with a GPU device"); + return NULL; + } + + SDL_GPURenderState *state = (SDL_GPURenderState *)SDL_calloc(1, sizeof(*state)); + if (!state) { + return NULL; + } + + state->renderer = renderer; + state->fragment_shader = desc->fragment_shader; + + if (desc->num_sampler_bindings > 0) { + state->sampler_bindings = (SDL_GPUTextureSamplerBinding *)SDL_calloc(desc->num_sampler_bindings, sizeof(*state->sampler_bindings)); + if (!state->sampler_bindings) { + SDL_DestroyGPURenderState(state); + return NULL; + } + SDL_memcpy(state->sampler_bindings, desc->sampler_bindings, desc->num_sampler_bindings * sizeof(*state->sampler_bindings)); + state->num_sampler_bindings = desc->num_sampler_bindings; + } + + if (desc->num_storage_textures > 0) { + state->storage_textures = (SDL_GPUTexture **)SDL_calloc(desc->num_storage_textures, sizeof(*state->storage_textures)); + if (!state->storage_textures) { + SDL_DestroyGPURenderState(state); + return NULL; + } + SDL_memcpy(state->storage_textures, desc->storage_textures, desc->num_storage_textures * sizeof(*state->storage_textures)); + state->num_storage_textures = desc->num_storage_textures; + } + + if (desc->num_storage_buffers > 0) { + state->storage_buffers = (SDL_GPUBuffer **)SDL_calloc(desc->num_storage_buffers, sizeof(*state->storage_buffers)); + if (!state->storage_buffers) { + SDL_DestroyGPURenderState(state); + return NULL; + } + SDL_memcpy(state->storage_buffers, desc->storage_buffers, desc->num_storage_buffers * sizeof(*state->storage_buffers)); + state->num_storage_buffers = desc->num_storage_buffers; + } + + return state; +} + +bool SDL_SetGPURenderStateFragmentUniforms(SDL_GPURenderState *state, Uint32 slot_index, const void *data, Uint32 length) +{ + if (!state) { + return SDL_InvalidParamError("state"); + } + + if (!FlushRenderCommandsIfGPURenderStateNeeded(state)) { + return false; + } + + for (int i = 0; i < state->num_uniform_buffers; i++) { + SDL_GPURenderStateUniformBuffer *buffer = &state->uniform_buffers[i]; + if (buffer->slot_index == slot_index) { + void *new_data = SDL_realloc(buffer->data, length); + if (!new_data) { + return false; + } + SDL_memcpy(new_data, data, length); + buffer->data = new_data; + buffer->length = length; + return true; + } + } + + SDL_GPURenderStateUniformBuffer *buffers = (SDL_GPURenderStateUniformBuffer *)SDL_realloc(state->uniform_buffers, (state->num_uniform_buffers + 1) * sizeof(*state->uniform_buffers)); + if (!buffers) { + return false; + } + + SDL_GPURenderStateUniformBuffer *buffer = &buffers[state->num_uniform_buffers]; + buffer->slot_index = slot_index; + buffer->length = length; + buffer->data = SDL_malloc(length); + if (!buffer->data) { + SDL_free(buffers); + return false; + } + SDL_memcpy(buffer->data, data, length); + + state->uniform_buffers = buffers; + ++state->num_uniform_buffers; + return true; +} + +bool SDL_SetRenderGPUState(SDL_Renderer *renderer, SDL_GPURenderState *state) +{ + CHECK_RENDERER_MAGIC(renderer, false); + + renderer->gpu_render_state = state; + return true; +} + +void SDL_DestroyGPURenderState(SDL_GPURenderState *state) +{ + if (!state) { + return; + } + + FlushRenderCommandsIfGPURenderStateNeeded(state); + + if (state->num_uniform_buffers > 0) { + for (int i = 0; i < state->num_uniform_buffers; i++) { + SDL_free(state->uniform_buffers[i].data); + } + SDL_free(state->uniform_buffers); + } + SDL_free(state->sampler_bindings); + SDL_free(state->storage_textures); + SDL_free(state->storage_buffers); + SDL_free(state); +} diff --git a/libs/SDL3/src/render/SDL_sysrender.h b/libs/SDL3/src/render/SDL_sysrender.h index bbafefdc..d42f5362 100644 --- a/libs/SDL3/src/render/SDL_sysrender.h +++ b/libs/SDL3/src/render/SDL_sysrender.h @@ -32,14 +32,6 @@ extern "C" { #endif -typedef enum SDL_TextureAddressMode -{ - SDL_TEXTURE_ADDRESS_INVALID = -1, - SDL_TEXTURE_ADDRESS_AUTO, - SDL_TEXTURE_ADDRESS_CLAMP, - SDL_TEXTURE_ADDRESS_WRAP, -} SDL_TextureAddressMode; - /** * A rectangle, with the origin at the upper left (double precision). */ @@ -118,6 +110,36 @@ struct SDL_Texture SDL_Texture *next; }; +// Define the GPU render state structure +typedef struct SDL_GPURenderStateUniformBuffer +{ + Uint32 slot_index; + void *data; + Uint32 length; +} SDL_GPURenderStateUniformBuffer; + +// Define the GPU render state structure +struct SDL_GPURenderState +{ + SDL_Renderer *renderer; + + Uint32 last_command_generation; // last command queue generation this state was in. + + SDL_GPUShader *fragment_shader; + + int num_sampler_bindings; + SDL_GPUTextureSamplerBinding *sampler_bindings; + + int num_storage_textures; + SDL_GPUTexture **storage_textures; + + int num_storage_buffers; + SDL_GPUBuffer **storage_buffers; + + int num_uniform_buffers; + SDL_GPURenderStateUniformBuffer *uniform_buffers; +}; + typedef enum { SDL_RENDERCMD_NO_OP, @@ -157,7 +179,9 @@ typedef struct SDL_RenderCommand SDL_BlendMode blend; SDL_Texture *texture; SDL_ScaleMode texture_scale_mode; - SDL_TextureAddressMode texture_address_mode; + SDL_TextureAddressMode texture_address_mode_u; + SDL_TextureAddressMode texture_address_mode_v; + SDL_GPURenderState *gpu_render_state; } draw; struct { @@ -265,6 +289,9 @@ struct SDL_Renderer // The method of drawing lines SDL_RenderLineMethod line_method; + // Default scale mode for textures created with this renderer + SDL_ScaleMode scale_mode; + // The list of textures SDL_Texture *textures; SDL_Texture *target; @@ -278,7 +305,9 @@ struct SDL_Renderer float color_scale; SDL_FColor color; /**< Color for drawing operations values */ SDL_BlendMode blendMode; /**< The drawing blend mode */ - SDL_TextureAddressMode texture_address_mode; + SDL_TextureAddressMode texture_address_mode_u; + SDL_TextureAddressMode texture_address_mode_v; + SDL_GPURenderState *gpu_render_state; SDL_RenderCommand *render_commands; SDL_RenderCommand *render_commands_tail; @@ -328,6 +357,7 @@ extern SDL_RenderDriver D3D12_RenderDriver; extern SDL_RenderDriver GL_RenderDriver; extern SDL_RenderDriver GLES2_RenderDriver; extern SDL_RenderDriver METAL_RenderDriver; +extern SDL_RenderDriver NGAGE_RenderDriver; extern SDL_RenderDriver VULKAN_RenderDriver; extern SDL_RenderDriver PS2_RenderDriver; extern SDL_RenderDriver PSP_RenderDriver; @@ -338,6 +368,12 @@ extern SDL_RenderDriver GPU_RenderDriver; // Clean up any renderers at shutdown extern void SDL_QuitRender(void); +#define RENDER_SAMPLER_HASHKEY(scale_mode, address_u, address_v) \ + (((scale_mode == SDL_SCALEMODE_NEAREST) << 0) | \ + ((address_u == SDL_TEXTURE_ADDRESS_WRAP) << 1) | \ + ((address_v == SDL_TEXTURE_ADDRESS_WRAP) << 2)) +#define RENDER_SAMPLER_COUNT (((1 << 0) | (1 << 1) | (1 << 2)) + 1) + // Add a supported texture format to a renderer extern bool SDL_AddSupportedTextureFormat(SDL_Renderer *renderer, SDL_PixelFormat format); diff --git a/libs/SDL3/src/render/direct3d/SDL_render_d3d.c b/libs/SDL3/src/render/direct3d/SDL_render_d3d.c index 1d395f13..5132a232 100644 --- a/libs/SDL3/src/render/direct3d/SDL_render_d3d.c +++ b/libs/SDL3/src/render/direct3d/SDL_render_d3d.c @@ -61,7 +61,8 @@ typedef struct bool beginScene; bool enableSeparateAlphaBlend; SDL_ScaleMode scaleMode[3]; - SDL_TextureAddressMode addressMode[3]; + SDL_TextureAddressMode addressModeU[3]; + SDL_TextureAddressMode addressModeV[3]; IDirect3DSurface9 *defaultRenderTarget; IDirect3DSurface9 *currentRenderTarget; void *d3dxDLL; @@ -278,8 +279,11 @@ static void D3D_InitRenderState(D3D_RenderData *data) } // Reset our current address mode - for (int i = 0; i < SDL_arraysize(data->addressMode); ++i) { - data->addressMode[i] = SDL_TEXTURE_ADDRESS_INVALID; + for (int i = 0; i < SDL_arraysize(data->addressModeU); ++i) { + data->addressModeU[i] = SDL_TEXTURE_ADDRESS_INVALID; + } + for (int i = 0; i < SDL_arraysize(data->addressModeV); ++i) { + data->addressModeV[i] = SDL_TEXTURE_ADDRESS_INVALID; } // Start the render with beginScene @@ -921,6 +925,7 @@ static void UpdateTextureScaleMode(D3D_RenderData *data, SDL_ScaleMode scaleMode { if (scaleMode != data->scaleMode[index]) { switch (scaleMode) { + case SDL_SCALEMODE_PIXELART: case SDL_SCALEMODE_NEAREST: IDirect3DDevice9_SetSamplerState(data->device, index, D3DSAMP_MINFILTER, D3DTEXF_POINT); IDirect3DDevice9_SetSamplerState(data->device, index, D3DSAMP_MAGFILTER, D3DTEXF_POINT); @@ -936,22 +941,28 @@ static void UpdateTextureScaleMode(D3D_RenderData *data, SDL_ScaleMode scaleMode } } -static void UpdateTextureAddressMode(D3D_RenderData *data, SDL_TextureAddressMode addressMode, unsigned index) +static DWORD TranslateAddressMode(SDL_TextureAddressMode addressMode) { - if (addressMode != data->addressMode[index]) { - switch (addressMode) { - case SDL_TEXTURE_ADDRESS_CLAMP: - IDirect3DDevice9_SetSamplerState(data->device, index, D3DSAMP_ADDRESSU, D3DTADDRESS_CLAMP); - IDirect3DDevice9_SetSamplerState(data->device, index, D3DSAMP_ADDRESSV, D3DTADDRESS_CLAMP); - break; - case SDL_TEXTURE_ADDRESS_WRAP: - IDirect3DDevice9_SetSamplerState(data->device, index, D3DSAMP_ADDRESSU, D3DTADDRESS_WRAP); - IDirect3DDevice9_SetSamplerState(data->device, index, D3DSAMP_ADDRESSV, D3DTADDRESS_WRAP); - break; - default: - break; - } - data->addressMode[index] = addressMode; + switch (addressMode) { + case SDL_TEXTURE_ADDRESS_CLAMP: + return D3DTADDRESS_CLAMP; + case SDL_TEXTURE_ADDRESS_WRAP: + return D3DTADDRESS_WRAP; + default: + SDL_assert(!"Unknown texture address mode"); + return D3DTADDRESS_CLAMP; + } +} + +static void UpdateTextureAddressMode(D3D_RenderData *data, SDL_TextureAddressMode addressModeU, SDL_TextureAddressMode addressModeV, unsigned index) +{ + if (addressModeU != data->addressModeU[index]) { + IDirect3DDevice9_SetSamplerState(data->device, index, D3DSAMP_ADDRESSU, TranslateAddressMode(addressModeU)); + data->addressModeU[index] = addressModeU; + } + if (addressModeV != data->addressModeV[index]) { + IDirect3DDevice9_SetSamplerState(data->device, index, D3DSAMP_ADDRESSV, TranslateAddressMode(addressModeV)); + data->addressModeV[index] = addressModeV; } } @@ -1046,15 +1057,15 @@ static bool SetDrawState(D3D_RenderData *data, const SDL_RenderCommand *cmd) if (texture) { UpdateTextureScaleMode(data, cmd->data.draw.texture_scale_mode, 0); - UpdateTextureAddressMode(data, cmd->data.draw.texture_address_mode, 0); + UpdateTextureAddressMode(data, cmd->data.draw.texture_address_mode_u, cmd->data.draw.texture_address_mode_v, 0); #ifdef SDL_HAVE_YUV D3D_TextureData *texturedata = (D3D_TextureData *)texture->internal; if (texturedata && texturedata->yuv) { UpdateTextureScaleMode(data, cmd->data.draw.texture_scale_mode, 1); UpdateTextureScaleMode(data, cmd->data.draw.texture_scale_mode, 2); - UpdateTextureAddressMode(data, cmd->data.draw.texture_address_mode, 1); - UpdateTextureAddressMode(data, cmd->data.draw.texture_address_mode, 2); + UpdateTextureAddressMode(data, cmd->data.draw.texture_address_mode_u, cmd->data.draw.texture_address_mode_v, 1); + UpdateTextureAddressMode(data, cmd->data.draw.texture_address_mode_u, cmd->data.draw.texture_address_mode_v, 2); } #endif // SDL_HAVE_YUV } diff --git a/libs/SDL3/src/render/direct3d11/D3D11_PixelShader_Advanced.h b/libs/SDL3/src/render/direct3d11/D3D11_PixelShader_Advanced.h index a1297c98..59c90ae6 100644 --- a/libs/SDL3/src/render/direct3d11/D3D11_PixelShader_Advanced.h +++ b/libs/SDL3/src/render/direct3d11/D3D11_PixelShader_Advanced.h @@ -3,7 +3,7 @@ // Generated by Microsoft (R) HLSL Shader Compiler 10.1 // // -// Buffer Definitions: +// Buffer Definitions: // // cbuffer Constants // { @@ -12,14 +12,15 @@ // float texture_type; // Offset: 4 Size: 4 // float input_type; // Offset: 8 Size: 4 // float color_scale; // Offset: 12 Size: 4 -// float tonemap_method; // Offset: 16 Size: 4 -// float tonemap_factor1; // Offset: 20 Size: 4 -// float tonemap_factor2; // Offset: 24 Size: 4 -// float sdr_white_point; // Offset: 28 Size: 4 -// float4 Yoffset; // Offset: 32 Size: 16 -// float4 Rcoeff; // Offset: 48 Size: 16 -// float4 Gcoeff; // Offset: 64 Size: 16 -// float4 Bcoeff; // Offset: 80 Size: 16 +// float4 texel_size; // Offset: 16 Size: 16 +// float tonemap_method; // Offset: 32 Size: 4 +// float tonemap_factor1; // Offset: 36 Size: 4 +// float tonemap_factor2; // Offset: 40 Size: 4 +// float sdr_white_point; // Offset: 44 Size: 4 +// float4 Yoffset; // Offset: 48 Size: 16 +// float4 Rcoeff; // Offset: 64 Size: 16 +// float4 Gcoeff; // Offset: 80 Size: 16 +// float4 Bcoeff; // Offset: 96 Size: 16 // // } // @@ -28,11 +29,11 @@ // // Name Type Format Dim HLSL Bind Count // ------------------------------ ---------- ------- ----------- -------------- ------ -// sampler0 sampler NA NA s0 1 -// texture0 texture float4 2d t0 1 -// texture1 texture float4 2d t1 1 -// texture2 texture float4 2d t2 1 -// Constants cbuffer NA NA cb0 1 +// sampler0 sampler NA NA s0 1 +// texture0 texture float4 2d t0 1 +// texture1 texture float4 2d t1 1 +// texture2 texture float4 2d t2 1 +// Constants cbuffer NA NA cb0 1 // // // @@ -40,8 +41,8 @@ // // Name Index Mask Register SysValue Format Used // -------------------- ----- ------ -------- -------- ------- ------ -// SV_POSITION 0 xyzw 0 POS float -// TEXCOORD 0 xy 1 NONE float xy +// SV_POSITION 0 xyzw 0 POS float +// TEXCOORD 0 xy 1 NONE float xy // COLOR 0 xyzw 2 NONE float xyzw // // @@ -53,7 +54,7 @@ // ps_5_0 dcl_globalFlags refactoringAllowed -dcl_constantbuffer CB0[6], immediateIndexed +dcl_constantbuffer CB0[7], immediateIndexed dcl_sampler s0, mode_default dcl_resource_texture2d (float,float,float,float) t0 dcl_resource_texture2d (float,float,float,float) t1 @@ -65,46 +66,71 @@ dcl_temps 7 eq r0.xyzw, cb0[0].yzzz, l(0.000000, 3.000000, 2.000000, 1.000000) if_nz r0.x mov r1.xyzw, l(1.000000,1.000000,1.000000,1.000000) -else +else eq r0.x, cb0[0].y, l(1.000000) if_nz r0.x sample_indexable(texture2d)(float,float,float,float) r1.xyzw, v1.xyxx, t0.xyzw, s0 - else + else eq r0.x, cb0[0].y, l(2.000000) if_nz r0.x - sample_indexable(texture2d)(float,float,float,float) r2.x, v1.xyxx, t0.xyzw, s0 - sample_indexable(texture2d)(float,float,float,float) r2.yz, v1.xyxx, t1.zxyw, s0 - add r2.xyz, r2.xyzx, cb0[2].xyzx - dp3 r1.x, r2.xyzx, cb0[3].xyzx - dp3 r1.y, r2.xyzx, cb0[4].xyzx - dp3 r1.z, r2.xyzx, cb0[5].xyzx - else + deriv_rtx_coarse r2.xy, v1.xyxx + deriv_rty_coarse r2.zw, v1.xxxy + add r3.xy, |r2.zwzz|, |r2.xyxx| + mul r3.xy, r3.xyxx, cb0[1].zwzz + max r3.xy, r3.xyxx, l(0.000010, 0.000010, 0.000000, 0.000000) + min r3.xy, r3.xyxx, l(1.000000, 1.000000, 0.000000, 0.000000) + mul r3.zw, r3.xxxy, l(0.000000, 0.000000, 0.500000, 0.500000) + mad r3.zw, v1.xxxy, cb0[1].zzzw, -r3.zzzw + add r3.xy, -r3.xyxx, l(1.000000, 1.000000, 0.000000, 0.000000) + frc r4.xy, r3.zwzz + add r4.zw, -r3.xxxy, l(0.000000, 0.000000, 1.000000, 1.000000) + add r3.xy, -r3.xyxx, r4.xyxx + div r4.xy, l(1.000000, 1.000000, 1.000000, 1.000000), r4.zwzz + mul_sat r3.xy, r3.xyxx, r4.xyxx + mad r4.xy, r3.xyxx, l(-2.000000, -2.000000, 0.000000, 0.000000), l(3.000000, 3.000000, 0.000000, 0.000000) + mul r3.xy, r3.xyxx, r3.xyxx + round_ni r3.zw, r3.zzzw + mad r3.xy, r4.xyxx, r3.xyxx, r3.zwzz + add r3.xy, r3.xyxx, l(0.500000, 0.500000, 0.000000, 0.000000) + mul r3.xy, r3.xyxx, cb0[1].xyxx + sample_d_indexable(texture2d)(float,float,float,float) r1.xyzw, r3.xyxx, t0.xyzw, s0, r2.xyxx, r2.zwzz + else eq r0.x, cb0[0].y, l(3.000000) if_nz r0.x sample_indexable(texture2d)(float,float,float,float) r2.x, v1.xyxx, t0.xyzw, s0 - sample_indexable(texture2d)(float,float,float,float) r2.yz, v1.xyxx, t1.zyxw, s0 - add r2.xyz, r2.xyzx, cb0[2].xyzx - dp3 r1.x, r2.xyzx, cb0[3].xyzx - dp3 r1.y, r2.xyzx, cb0[4].xyzx - dp3 r1.z, r2.xyzx, cb0[5].xyzx - else + sample_indexable(texture2d)(float,float,float,float) r2.yz, v1.xyxx, t1.zxyw, s0 + add r2.xyz, r2.xyzx, cb0[3].xyzx + dp3 r1.x, r2.xyzx, cb0[4].xyzx + dp3 r1.y, r2.xyzx, cb0[5].xyzx + dp3 r1.z, r2.xyzx, cb0[6].xyzx + else eq r0.x, cb0[0].y, l(4.000000) if_nz r0.x sample_indexable(texture2d)(float,float,float,float) r2.x, v1.xyxx, t0.xyzw, s0 - sample_indexable(texture2d)(float,float,float,float) r2.y, v1.xyxx, t1.yxzw, s0 - sample_indexable(texture2d)(float,float,float,float) r2.z, v1.xyxx, t2.yzxw, s0 - add r2.xyz, r2.xyzx, cb0[2].xyzx - dp3 r1.x, r2.xyzx, cb0[3].xyzx - dp3 r1.y, r2.xyzx, cb0[4].xyzx - dp3 r1.z, r2.xyzx, cb0[5].xyzx - else - mov r1.xyz, l(1.000000,0,0,0) - endif - endif - endif - mov r1.w, l(1.000000) - endif -endif + sample_indexable(texture2d)(float,float,float,float) r2.yz, v1.xyxx, t1.zyxw, s0 + add r2.xyz, r2.xyzx, cb0[3].xyzx + dp3 r1.x, r2.xyzx, cb0[4].xyzx + dp3 r1.y, r2.xyzx, cb0[5].xyzx + dp3 r1.z, r2.xyzx, cb0[6].xyzx + else + eq r0.x, cb0[0].y, l(5.000000) + if_nz r0.x + sample_indexable(texture2d)(float,float,float,float) r2.x, v1.xyxx, t0.xyzw, s0 + sample_indexable(texture2d)(float,float,float,float) r2.y, v1.xyxx, t1.yxzw, s0 + sample_indexable(texture2d)(float,float,float,float) r2.z, v1.xyxx, t2.yzxw, s0 + add r2.xyz, r2.xyzx, cb0[3].xyzx + dp3 r1.x, r2.xyzx, cb0[4].xyzx + dp3 r1.y, r2.xyzx, cb0[5].xyzx + dp3 r1.z, r2.xyzx, cb0[6].xyzx + else + mov r1.xyz, l(1.000000,0,0,0) + endif + endif + endif + mov r1.w, l(1.000000) + endif + endif +endif log r2.xyz, |r1.xyzx| mul r2.xyz, r2.xyzx, l(0.012683, 0.012683, 0.012683, 0.000000) exp r2.xyz, r2.xyzx @@ -116,11 +142,11 @@ log r2.xyz, |r2.xyzx| mul r2.xyz, r2.xyzx, l(6.277395, 6.277395, 6.277395, 0.000000) exp r2.xyz, r2.xyzx mul r2.xyz, r2.xyzx, l(10000.000000, 10000.000000, 10000.000000, 0.000000) -div r2.xyz, r2.xyzx, cb0[1].wwww +div r2.xyz, r2.xyzx, cb0[2].wwww movc r2.xyz, r0.yyyy, r2.xyzx, r1.xyzx -ne r0.x, cb0[1].x, l(0.000000) -mul r3.xyz, r2.xyzx, cb0[1].yyyy -eq r4.xy, cb0[1].xxxx, l(1.000000, 2.000000, 0.000000, 0.000000) +ne r0.x, cb0[2].x, l(0.000000) +mul r3.xyz, r2.xyzx, cb0[2].yyyy +eq r4.xy, cb0[2].xxxx, l(1.000000, 2.000000, 0.000000, 0.000000) dp3 r5.x, l(0.627404, 0.329283, 0.043313, 0.000000), r2.xyzx dp3 r5.y, l(0.069097, 0.919541, 0.011362, 0.000000), r2.xyzx dp3 r5.z, l(0.016391, 0.088013, 0.895595, 0.000000), r2.xyzx @@ -128,7 +154,7 @@ movc r5.xyz, r0.zzzz, r5.xyzx, r2.xyzx max r2.w, r5.z, r5.y max r2.w, r2.w, r5.x lt r3.w, l(0.000000), r2.w -mad r4.zw, cb0[1].yyyz, r2.wwww, l(0.000000, 0.000000, 1.000000, 1.000000) +mad r4.zw, cb0[2].yyyz, r2.wwww, l(0.000000, 0.000000, 1.000000, 1.000000) div r2.w, r4.z, r4.w mul r6.xyz, r2.wwww, r5.xyzx movc r5.xyz, r3.wwww, r6.xyzx, r5.xyzx @@ -150,9 +176,9 @@ if_nz r0.w mul r5.xyz, r5.xyzx, l(2.400000, 2.400000, 2.400000, 0.000000) exp r5.xyz, r5.xyzx movc r2.xyz, r3.xyzx, r4.xyzx, r5.xyzx - endif + endif mul r1.xyz, r2.xyzx, cb0[0].wwww -else +else if_nz r0.z mul r1.xyz, r2.xyzx, cb0[0].wwww ne r0.x, l(0.000000, 0.000000, 0.000000, 0.000000), cb0[0].x @@ -164,8 +190,8 @@ else exp r4.xyz, r4.xyzx mad r4.xyz, r4.xyzx, l(1.055000, 1.055000, 1.055000, 0.000000), l(-0.055000, -0.055000, -0.055000, 0.000000) movc_sat r1.xyz, r0.xzwx, r3.xyzx, r4.xyzx - endif - else + endif + else if_nz r0.y dp3 r0.x, l(1.660496, -0.587656, -0.072840, 0.000000), r2.xyzx dp3 r0.y, l(-0.124547, 1.132895, -0.008348, 0.000000), r2.xyzx @@ -180,881 +206,1024 @@ else exp r4.xyz, r4.xyzx mad r4.xyz, r4.xyzx, l(1.055000, 1.055000, 1.055000, 0.000000), l(-0.055000, -0.055000, -0.055000, 0.000000) movc_sat r1.xyz, r0.xyzx, r3.xyzx, r4.xyzx - endif - else + endif + else mul r1.xyz, r2.xyzx, cb0[0].wwww - endif - endif -endif + endif + endif +endif mul o0.xyzw, r1.xyzw, v2.xyzw -ret -// Approximately 126 instruction slots used +ret +// Approximately 151 instruction slots used #endif const BYTE g_main[] = { - 68, 88, 66, 67, 154, 200, - 28, 25, 30, 140, 0, 183, - 251, 32, 6, 185, 210, 29, - 251, 110, 1, 0, 0, 0, - 64, 20, 0, 0, 5, 0, - 0, 0, 52, 0, 0, 0, - 80, 4, 0, 0, 196, 4, - 0, 0, 248, 4, 0, 0, - 164, 19, 0, 0, 82, 68, - 69, 70, 20, 4, 0, 0, - 1, 0, 0, 0, 12, 1, - 0, 0, 5, 0, 0, 0, - 60, 0, 0, 0, 0, 5, - 255, 255, 0, 1, 0, 0, - 233, 3, 0, 0, 82, 68, - 49, 49, 60, 0, 0, 0, - 24, 0, 0, 0, 32, 0, - 0, 0, 40, 0, 0, 0, - 36, 0, 0, 0, 12, 0, - 0, 0, 0, 0, 0, 0, - 220, 0, 0, 0, 3, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 1, 0, 0, 0, 1, 0, - 0, 0, 229, 0, 0, 0, - 2, 0, 0, 0, 5, 0, - 0, 0, 4, 0, 0, 0, - 255, 255, 255, 255, 0, 0, - 0, 0, 1, 0, 0, 0, - 13, 0, 0, 0, 238, 0, - 0, 0, 2, 0, 0, 0, - 5, 0, 0, 0, 4, 0, - 0, 0, 255, 255, 255, 255, - 1, 0, 0, 0, 1, 0, - 0, 0, 13, 0, 0, 0, - 247, 0, 0, 0, 2, 0, - 0, 0, 5, 0, 0, 0, - 4, 0, 0, 0, 255, 255, - 255, 255, 2, 0, 0, 0, - 1, 0, 0, 0, 13, 0, - 0, 0, 0, 1, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 1, 0, 0, 0, - 1, 0, 0, 0, 115, 97, - 109, 112, 108, 101, 114, 48, - 0, 116, 101, 120, 116, 117, - 114, 101, 48, 0, 116, 101, - 120, 116, 117, 114, 101, 49, - 0, 116, 101, 120, 116, 117, - 114, 101, 50, 0, 67, 111, - 110, 115, 116, 97, 110, 116, - 115, 0, 171, 171, 0, 1, - 0, 0, 12, 0, 0, 0, - 36, 1, 0, 0, 96, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 4, 3, - 0, 0, 0, 0, 0, 0, - 4, 0, 0, 0, 2, 0, - 0, 0, 24, 3, 0, 0, - 0, 0, 0, 0, 255, 255, - 255, 255, 0, 0, 0, 0, - 255, 255, 255, 255, 0, 0, - 0, 0, 60, 3, 0, 0, - 4, 0, 0, 0, 4, 0, - 0, 0, 2, 0, 0, 0, - 24, 3, 0, 0, 0, 0, - 0, 0, 255, 255, 255, 255, - 0, 0, 0, 0, 255, 255, - 255, 255, 0, 0, 0, 0, - 73, 3, 0, 0, 8, 0, - 0, 0, 4, 0, 0, 0, - 2, 0, 0, 0, 24, 3, - 0, 0, 0, 0, 0, 0, - 255, 255, 255, 255, 0, 0, - 0, 0, 255, 255, 255, 255, - 0, 0, 0, 0, 84, 3, - 0, 0, 12, 0, 0, 0, - 4, 0, 0, 0, 2, 0, - 0, 0, 24, 3, 0, 0, - 0, 0, 0, 0, 255, 255, - 255, 255, 0, 0, 0, 0, - 255, 255, 255, 255, 0, 0, - 0, 0, 96, 3, 0, 0, - 16, 0, 0, 0, 4, 0, - 0, 0, 2, 0, 0, 0, - 24, 3, 0, 0, 0, 0, - 0, 0, 255, 255, 255, 255, - 0, 0, 0, 0, 255, 255, - 255, 255, 0, 0, 0, 0, - 111, 3, 0, 0, 20, 0, - 0, 0, 4, 0, 0, 0, - 2, 0, 0, 0, 24, 3, - 0, 0, 0, 0, 0, 0, - 255, 255, 255, 255, 0, 0, - 0, 0, 255, 255, 255, 255, - 0, 0, 0, 0, 127, 3, - 0, 0, 24, 0, 0, 0, - 4, 0, 0, 0, 2, 0, - 0, 0, 24, 3, 0, 0, - 0, 0, 0, 0, 255, 255, - 255, 255, 0, 0, 0, 0, - 255, 255, 255, 255, 0, 0, - 0, 0, 143, 3, 0, 0, - 28, 0, 0, 0, 4, 0, - 0, 0, 2, 0, 0, 0, - 24, 3, 0, 0, 0, 0, - 0, 0, 255, 255, 255, 255, - 0, 0, 0, 0, 255, 255, - 255, 255, 0, 0, 0, 0, - 159, 3, 0, 0, 32, 0, - 0, 0, 16, 0, 0, 0, - 2, 0, 0, 0, 176, 3, - 0, 0, 0, 0, 0, 0, - 255, 255, 255, 255, 0, 0, - 0, 0, 255, 255, 255, 255, - 0, 0, 0, 0, 212, 3, - 0, 0, 48, 0, 0, 0, - 16, 0, 0, 0, 2, 0, - 0, 0, 176, 3, 0, 0, - 0, 0, 0, 0, 255, 255, - 255, 255, 0, 0, 0, 0, - 255, 255, 255, 255, 0, 0, - 0, 0, 219, 3, 0, 0, - 64, 0, 0, 0, 16, 0, - 0, 0, 2, 0, 0, 0, - 176, 3, 0, 0, 0, 0, - 0, 0, 255, 255, 255, 255, - 0, 0, 0, 0, 255, 255, - 255, 255, 0, 0, 0, 0, - 226, 3, 0, 0, 80, 0, - 0, 0, 16, 0, 0, 0, - 2, 0, 0, 0, 176, 3, - 0, 0, 0, 0, 0, 0, - 255, 255, 255, 255, 0, 0, - 0, 0, 255, 255, 255, 255, - 0, 0, 0, 0, 115, 99, - 82, 71, 66, 95, 111, 117, - 116, 112, 117, 116, 0, 102, - 108, 111, 97, 116, 0, 171, - 0, 0, 3, 0, 1, 0, - 1, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 17, 3, 0, 0, - 116, 101, 120, 116, 117, 114, - 101, 95, 116, 121, 112, 101, - 0, 105, 110, 112, 117, 116, - 95, 116, 121, 112, 101, 0, - 99, 111, 108, 111, 114, 95, - 115, 99, 97, 108, 101, 0, - 116, 111, 110, 101, 109, 97, - 112, 95, 109, 101, 116, 104, - 111, 100, 0, 116, 111, 110, - 101, 109, 97, 112, 95, 102, - 97, 99, 116, 111, 114, 49, - 0, 116, 111, 110, 101, 109, - 97, 112, 95, 102, 97, 99, - 116, 111, 114, 50, 0, 115, - 100, 114, 95, 119, 104, 105, - 116, 101, 95, 112, 111, 105, - 110, 116, 0, 89, 111, 102, - 102, 115, 101, 116, 0, 102, - 108, 111, 97, 116, 52, 0, - 171, 171, 1, 0, 3, 0, - 1, 0, 4, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 167, 3, - 0, 0, 82, 99, 111, 101, - 102, 102, 0, 71, 99, 111, - 101, 102, 102, 0, 66, 99, - 111, 101, 102, 102, 0, 77, - 105, 99, 114, 111, 115, 111, - 102, 116, 32, 40, 82, 41, - 32, 72, 76, 83, 76, 32, - 83, 104, 97, 100, 101, 114, - 32, 67, 111, 109, 112, 105, - 108, 101, 114, 32, 49, 48, - 46, 49, 0, 171, 171, 171, - 73, 83, 71, 78, 108, 0, - 0, 0, 3, 0, 0, 0, - 8, 0, 0, 0, 80, 0, - 0, 0, 0, 0, 0, 0, - 1, 0, 0, 0, 3, 0, - 0, 0, 0, 0, 0, 0, - 15, 0, 0, 0, 92, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 3, 0, - 0, 0, 1, 0, 0, 0, - 3, 3, 0, 0, 101, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 3, 0, - 0, 0, 2, 0, 0, 0, - 15, 15, 0, 0, 83, 86, - 95, 80, 79, 83, 73, 84, - 73, 79, 78, 0, 84, 69, - 88, 67, 79, 79, 82, 68, - 0, 67, 79, 76, 79, 82, - 0, 171, 79, 83, 71, 78, - 44, 0, 0, 0, 1, 0, - 0, 0, 8, 0, 0, 0, - 32, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 3, 0, 0, 0, 0, 0, - 0, 0, 15, 0, 0, 0, - 83, 86, 95, 84, 65, 82, - 71, 69, 84, 0, 171, 171, - 83, 72, 69, 88, 164, 14, - 0, 0, 80, 0, 0, 0, - 169, 3, 0, 0, 106, 8, - 0, 1, 89, 0, 0, 4, - 70, 142, 32, 0, 0, 0, - 0, 0, 6, 0, 0, 0, - 90, 0, 0, 3, 0, 96, - 16, 0, 0, 0, 0, 0, - 88, 24, 0, 4, 0, 112, - 16, 0, 0, 0, 0, 0, - 85, 85, 0, 0, 88, 24, - 0, 4, 0, 112, 16, 0, - 1, 0, 0, 0, 85, 85, - 0, 0, 88, 24, 0, 4, - 0, 112, 16, 0, 2, 0, - 0, 0, 85, 85, 0, 0, - 98, 16, 0, 3, 50, 16, - 16, 0, 1, 0, 0, 0, - 98, 16, 0, 3, 242, 16, - 16, 0, 2, 0, 0, 0, - 101, 0, 0, 3, 242, 32, - 16, 0, 0, 0, 0, 0, - 104, 0, 0, 2, 7, 0, - 0, 0, 24, 0, 0, 11, - 242, 0, 16, 0, 0, 0, - 0, 0, 150, 138, 32, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 2, 64, 0, 0, - 0, 0, 0, 0, 0, 0, - 64, 64, 0, 0, 0, 64, - 0, 0, 128, 63, 31, 0, - 4, 3, 10, 0, 16, 0, - 0, 0, 0, 0, 54, 0, - 0, 8, 242, 0, 16, 0, - 1, 0, 0, 0, 2, 64, - 0, 0, 0, 0, 128, 63, - 0, 0, 128, 63, 0, 0, - 128, 63, 0, 0, 128, 63, - 18, 0, 0, 1, 24, 0, - 0, 8, 18, 0, 16, 0, - 0, 0, 0, 0, 26, 128, - 32, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 1, 64, - 0, 0, 0, 0, 128, 63, - 31, 0, 4, 3, 10, 0, - 16, 0, 0, 0, 0, 0, - 69, 0, 0, 139, 194, 0, - 0, 128, 67, 85, 21, 0, - 242, 0, 16, 0, 1, 0, - 0, 0, 70, 16, 16, 0, - 1, 0, 0, 0, 70, 126, - 16, 0, 0, 0, 0, 0, - 0, 96, 16, 0, 0, 0, - 0, 0, 18, 0, 0, 1, - 24, 0, 0, 8, 18, 0, - 16, 0, 0, 0, 0, 0, - 26, 128, 32, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 1, 64, 0, 0, 0, 0, - 0, 64, 31, 0, 4, 3, - 10, 0, 16, 0, 0, 0, - 0, 0, 69, 0, 0, 139, - 194, 0, 0, 128, 67, 85, - 21, 0, 18, 0, 16, 0, - 2, 0, 0, 0, 70, 16, - 16, 0, 1, 0, 0, 0, - 70, 126, 16, 0, 0, 0, - 0, 0, 0, 96, 16, 0, - 0, 0, 0, 0, 69, 0, - 0, 139, 194, 0, 0, 128, - 67, 85, 21, 0, 98, 0, - 16, 0, 2, 0, 0, 0, - 70, 16, 16, 0, 1, 0, - 0, 0, 38, 125, 16, 0, - 1, 0, 0, 0, 0, 96, - 16, 0, 0, 0, 0, 0, - 0, 0, 0, 8, 114, 0, - 16, 0, 2, 0, 0, 0, - 70, 2, 16, 0, 2, 0, - 0, 0, 70, 130, 32, 0, - 0, 0, 0, 0, 2, 0, - 0, 0, 16, 0, 0, 8, - 18, 0, 16, 0, 1, 0, - 0, 0, 70, 2, 16, 0, - 2, 0, 0, 0, 70, 130, - 32, 0, 0, 0, 0, 0, - 3, 0, 0, 0, 16, 0, - 0, 8, 34, 0, 16, 0, - 1, 0, 0, 0, 70, 2, - 16, 0, 2, 0, 0, 0, - 70, 130, 32, 0, 0, 0, - 0, 0, 4, 0, 0, 0, - 16, 0, 0, 8, 66, 0, - 16, 0, 1, 0, 0, 0, - 70, 2, 16, 0, 2, 0, - 0, 0, 70, 130, 32, 0, - 0, 0, 0, 0, 5, 0, - 0, 0, 18, 0, 0, 1, - 24, 0, 0, 8, 18, 0, - 16, 0, 0, 0, 0, 0, - 26, 128, 32, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 1, 64, 0, 0, 0, 0, - 64, 64, 31, 0, 4, 3, - 10, 0, 16, 0, 0, 0, - 0, 0, 69, 0, 0, 139, - 194, 0, 0, 128, 67, 85, - 21, 0, 18, 0, 16, 0, - 2, 0, 0, 0, 70, 16, - 16, 0, 1, 0, 0, 0, - 70, 126, 16, 0, 0, 0, - 0, 0, 0, 96, 16, 0, - 0, 0, 0, 0, 69, 0, - 0, 139, 194, 0, 0, 128, - 67, 85, 21, 0, 98, 0, - 16, 0, 2, 0, 0, 0, - 70, 16, 16, 0, 1, 0, - 0, 0, 102, 124, 16, 0, - 1, 0, 0, 0, 0, 96, - 16, 0, 0, 0, 0, 0, - 0, 0, 0, 8, 114, 0, - 16, 0, 2, 0, 0, 0, - 70, 2, 16, 0, 2, 0, - 0, 0, 70, 130, 32, 0, - 0, 0, 0, 0, 2, 0, - 0, 0, 16, 0, 0, 8, - 18, 0, 16, 0, 1, 0, - 0, 0, 70, 2, 16, 0, - 2, 0, 0, 0, 70, 130, - 32, 0, 0, 0, 0, 0, - 3, 0, 0, 0, 16, 0, - 0, 8, 34, 0, 16, 0, - 1, 0, 0, 0, 70, 2, - 16, 0, 2, 0, 0, 0, - 70, 130, 32, 0, 0, 0, - 0, 0, 4, 0, 0, 0, - 16, 0, 0, 8, 66, 0, - 16, 0, 1, 0, 0, 0, - 70, 2, 16, 0, 2, 0, - 0, 0, 70, 130, 32, 0, - 0, 0, 0, 0, 5, 0, - 0, 0, 18, 0, 0, 1, - 24, 0, 0, 8, 18, 0, - 16, 0, 0, 0, 0, 0, - 26, 128, 32, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 1, 64, 0, 0, 0, 0, - 128, 64, 31, 0, 4, 3, - 10, 0, 16, 0, 0, 0, - 0, 0, 69, 0, 0, 139, - 194, 0, 0, 128, 67, 85, - 21, 0, 18, 0, 16, 0, - 2, 0, 0, 0, 70, 16, - 16, 0, 1, 0, 0, 0, - 70, 126, 16, 0, 0, 0, - 0, 0, 0, 96, 16, 0, - 0, 0, 0, 0, 69, 0, - 0, 139, 194, 0, 0, 128, - 67, 85, 21, 0, 34, 0, - 16, 0, 2, 0, 0, 0, - 70, 16, 16, 0, 1, 0, - 0, 0, 22, 126, 16, 0, - 1, 0, 0, 0, 0, 96, - 16, 0, 0, 0, 0, 0, - 69, 0, 0, 139, 194, 0, - 0, 128, 67, 85, 21, 0, - 66, 0, 16, 0, 2, 0, - 0, 0, 70, 16, 16, 0, - 1, 0, 0, 0, 150, 124, - 16, 0, 2, 0, 0, 0, - 0, 96, 16, 0, 0, 0, - 0, 0, 0, 0, 0, 8, - 114, 0, 16, 0, 2, 0, - 0, 0, 70, 2, 16, 0, - 2, 0, 0, 0, 70, 130, - 32, 0, 0, 0, 0, 0, - 2, 0, 0, 0, 16, 0, - 0, 8, 18, 0, 16, 0, - 1, 0, 0, 0, 70, 2, - 16, 0, 2, 0, 0, 0, - 70, 130, 32, 0, 0, 0, - 0, 0, 3, 0, 0, 0, - 16, 0, 0, 8, 34, 0, - 16, 0, 1, 0, 0, 0, - 70, 2, 16, 0, 2, 0, - 0, 0, 70, 130, 32, 0, - 0, 0, 0, 0, 4, 0, - 0, 0, 16, 0, 0, 8, - 66, 0, 16, 0, 1, 0, - 0, 0, 70, 2, 16, 0, - 2, 0, 0, 0, 70, 130, - 32, 0, 0, 0, 0, 0, - 5, 0, 0, 0, 18, 0, - 0, 1, 54, 0, 0, 8, - 114, 0, 16, 0, 1, 0, - 0, 0, 2, 64, 0, 0, - 0, 0, 128, 63, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 21, 0, - 0, 1, 21, 0, 0, 1, - 21, 0, 0, 1, 54, 0, - 0, 5, 130, 0, 16, 0, - 1, 0, 0, 0, 1, 64, - 0, 0, 0, 0, 128, 63, - 21, 0, 0, 1, 21, 0, - 0, 1, 47, 0, 0, 6, - 114, 0, 16, 0, 2, 0, - 0, 0, 70, 2, 16, 128, - 129, 0, 0, 0, 1, 0, - 0, 0, 56, 0, 0, 10, - 114, 0, 16, 0, 2, 0, - 0, 0, 70, 2, 16, 0, - 2, 0, 0, 0, 2, 64, - 0, 0, 172, 205, 79, 60, - 172, 205, 79, 60, 172, 205, - 79, 60, 0, 0, 0, 0, - 25, 0, 0, 5, 114, 0, - 16, 0, 2, 0, 0, 0, - 70, 2, 16, 0, 2, 0, - 0, 0, 0, 0, 0, 10, - 114, 0, 16, 0, 3, 0, - 0, 0, 70, 2, 16, 0, - 2, 0, 0, 0, 2, 64, - 0, 0, 0, 0, 86, 191, - 0, 0, 86, 191, 0, 0, - 86, 191, 0, 0, 0, 0, - 52, 0, 0, 10, 114, 0, - 16, 0, 3, 0, 0, 0, - 70, 2, 16, 0, 3, 0, - 0, 0, 2, 64, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 50, 0, - 0, 16, 114, 0, 16, 0, - 2, 0, 0, 0, 70, 2, - 16, 128, 65, 0, 0, 0, - 2, 0, 0, 0, 2, 64, - 0, 0, 0, 128, 149, 65, - 0, 128, 149, 65, 0, 128, - 149, 65, 0, 0, 0, 0, - 2, 64, 0, 0, 0, 208, - 150, 65, 0, 208, 150, 65, - 0, 208, 150, 65, 0, 0, - 0, 0, 14, 0, 0, 7, - 114, 0, 16, 0, 2, 0, - 0, 0, 70, 2, 16, 0, - 3, 0, 0, 0, 70, 2, - 16, 0, 2, 0, 0, 0, - 47, 0, 0, 6, 114, 0, - 16, 0, 2, 0, 0, 0, - 70, 2, 16, 128, 129, 0, - 0, 0, 2, 0, 0, 0, - 56, 0, 0, 10, 114, 0, - 16, 0, 2, 0, 0, 0, - 70, 2, 16, 0, 2, 0, - 0, 0, 2, 64, 0, 0, - 107, 224, 200, 64, 107, 224, - 200, 64, 107, 224, 200, 64, - 0, 0, 0, 0, 25, 0, - 0, 5, 114, 0, 16, 0, - 2, 0, 0, 0, 70, 2, - 16, 0, 2, 0, 0, 0, - 56, 0, 0, 10, 114, 0, - 16, 0, 2, 0, 0, 0, - 70, 2, 16, 0, 2, 0, - 0, 0, 2, 64, 0, 0, - 0, 64, 28, 70, 0, 64, - 28, 70, 0, 64, 28, 70, - 0, 0, 0, 0, 14, 0, - 0, 8, 114, 0, 16, 0, - 2, 0, 0, 0, 70, 2, - 16, 0, 2, 0, 0, 0, - 246, 143, 32, 0, 0, 0, - 0, 0, 1, 0, 0, 0, - 55, 0, 0, 9, 114, 0, - 16, 0, 2, 0, 0, 0, - 86, 5, 16, 0, 0, 0, - 0, 0, 70, 2, 16, 0, - 2, 0, 0, 0, 70, 2, - 16, 0, 1, 0, 0, 0, - 57, 0, 0, 8, 18, 0, - 16, 0, 0, 0, 0, 0, - 10, 128, 32, 0, 0, 0, - 0, 0, 1, 0, 0, 0, - 1, 64, 0, 0, 0, 0, - 0, 0, 56, 0, 0, 8, - 114, 0, 16, 0, 3, 0, - 0, 0, 70, 2, 16, 0, - 2, 0, 0, 0, 86, 133, - 32, 0, 0, 0, 0, 0, - 1, 0, 0, 0, 24, 0, - 0, 11, 50, 0, 16, 0, - 4, 0, 0, 0, 6, 128, - 32, 0, 0, 0, 0, 0, - 1, 0, 0, 0, 2, 64, - 0, 0, 0, 0, 128, 63, - 0, 0, 0, 64, 0, 0, - 0, 0, 0, 0, 0, 0, - 16, 0, 0, 10, 18, 0, - 16, 0, 5, 0, 0, 0, - 2, 64, 0, 0, 140, 157, - 32, 63, 200, 151, 168, 62, - 249, 104, 49, 61, 0, 0, - 0, 0, 70, 2, 16, 0, - 2, 0, 0, 0, 16, 0, - 0, 10, 34, 0, 16, 0, - 5, 0, 0, 0, 2, 64, - 0, 0, 186, 130, 141, 61, - 10, 103, 107, 63, 175, 39, - 58, 60, 0, 0, 0, 0, - 70, 2, 16, 0, 2, 0, - 0, 0, 16, 0, 0, 10, - 66, 0, 16, 0, 5, 0, - 0, 0, 2, 64, 0, 0, - 107, 70, 134, 60, 41, 64, - 180, 61, 183, 69, 101, 63, - 0, 0, 0, 0, 70, 2, - 16, 0, 2, 0, 0, 0, - 55, 0, 0, 9, 114, 0, - 16, 0, 5, 0, 0, 0, - 166, 10, 16, 0, 0, 0, - 0, 0, 70, 2, 16, 0, - 5, 0, 0, 0, 70, 2, - 16, 0, 2, 0, 0, 0, - 52, 0, 0, 7, 130, 0, - 16, 0, 2, 0, 0, 0, - 42, 0, 16, 0, 5, 0, - 0, 0, 26, 0, 16, 0, - 5, 0, 0, 0, 52, 0, - 0, 7, 130, 0, 16, 0, - 2, 0, 0, 0, 58, 0, - 16, 0, 2, 0, 0, 0, - 10, 0, 16, 0, 5, 0, - 0, 0, 49, 0, 0, 7, - 130, 0, 16, 0, 3, 0, - 0, 0, 1, 64, 0, 0, - 0, 0, 0, 0, 58, 0, - 16, 0, 2, 0, 0, 0, - 50, 0, 0, 13, 194, 0, - 16, 0, 4, 0, 0, 0, - 86, 137, 32, 0, 0, 0, - 0, 0, 1, 0, 0, 0, - 246, 15, 16, 0, 2, 0, - 0, 0, 2, 64, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 128, 63, - 0, 0, 128, 63, 14, 0, - 0, 7, 130, 0, 16, 0, - 2, 0, 0, 0, 42, 0, - 16, 0, 4, 0, 0, 0, - 58, 0, 16, 0, 4, 0, - 0, 0, 56, 0, 0, 7, - 114, 0, 16, 0, 6, 0, - 0, 0, 246, 15, 16, 0, - 2, 0, 0, 0, 70, 2, - 16, 0, 5, 0, 0, 0, - 55, 0, 0, 9, 114, 0, - 16, 0, 5, 0, 0, 0, - 246, 15, 16, 0, 3, 0, - 0, 0, 70, 2, 16, 0, - 6, 0, 0, 0, 70, 2, - 16, 0, 5, 0, 0, 0, - 16, 0, 0, 10, 18, 0, - 16, 0, 6, 0, 0, 0, - 2, 64, 0, 0, 34, 139, - 212, 63, 160, 112, 22, 191, - 35, 45, 149, 189, 0, 0, - 0, 0, 70, 2, 16, 0, - 5, 0, 0, 0, 16, 0, - 0, 10, 34, 0, 16, 0, - 6, 0, 0, 0, 2, 64, - 0, 0, 127, 18, 255, 189, - 180, 2, 145, 63, 13, 198, - 8, 188, 0, 0, 0, 0, - 70, 2, 16, 0, 5, 0, - 0, 0, 16, 0, 0, 10, - 66, 0, 16, 0, 6, 0, - 0, 0, 2, 64, 0, 0, - 179, 183, 148, 188, 205, 5, - 206, 189, 60, 51, 143, 63, - 0, 0, 0, 0, 70, 2, - 16, 0, 5, 0, 0, 0, - 55, 0, 0, 9, 114, 0, - 16, 0, 5, 0, 0, 0, - 166, 10, 16, 0, 0, 0, - 0, 0, 70, 2, 16, 0, - 6, 0, 0, 0, 70, 2, - 16, 0, 5, 0, 0, 0, - 55, 0, 0, 9, 226, 0, - 16, 0, 4, 0, 0, 0, - 86, 5, 16, 0, 4, 0, - 0, 0, 6, 9, 16, 0, - 5, 0, 0, 0, 6, 9, - 16, 0, 2, 0, 0, 0, - 55, 0, 0, 9, 114, 0, - 16, 0, 3, 0, 0, 0, - 6, 0, 16, 0, 4, 0, - 0, 0, 70, 2, 16, 0, - 3, 0, 0, 0, 150, 7, - 16, 0, 4, 0, 0, 0, - 55, 0, 0, 9, 114, 0, - 16, 0, 2, 0, 0, 0, - 6, 0, 16, 0, 0, 0, - 0, 0, 70, 2, 16, 0, - 3, 0, 0, 0, 70, 2, - 16, 0, 2, 0, 0, 0, - 31, 0, 4, 3, 58, 0, - 16, 0, 0, 0, 0, 0, - 57, 0, 0, 11, 18, 0, - 16, 0, 0, 0, 0, 0, - 2, 64, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 10, 128, 32, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 31, 0, 4, 3, - 10, 0, 16, 0, 0, 0, - 0, 0, 29, 0, 0, 10, - 114, 0, 16, 0, 3, 0, - 0, 0, 2, 64, 0, 0, - 230, 174, 37, 61, 230, 174, - 37, 61, 230, 174, 37, 61, - 0, 0, 0, 0, 70, 2, - 16, 0, 2, 0, 0, 0, - 56, 0, 0, 10, 114, 0, - 16, 0, 4, 0, 0, 0, - 70, 2, 16, 0, 2, 0, - 0, 0, 2, 64, 0, 0, - 145, 131, 158, 61, 145, 131, - 158, 61, 145, 131, 158, 61, - 0, 0, 0, 0, 0, 0, - 0, 10, 114, 0, 16, 0, - 5, 0, 0, 0, 70, 2, - 16, 0, 2, 0, 0, 0, - 2, 64, 0, 0, 174, 71, - 97, 61, 174, 71, 97, 61, - 174, 71, 97, 61, 0, 0, - 0, 0, 56, 0, 0, 11, - 114, 0, 16, 0, 5, 0, - 0, 0, 70, 2, 16, 128, - 129, 0, 0, 0, 5, 0, - 0, 0, 2, 64, 0, 0, - 111, 167, 114, 63, 111, 167, - 114, 63, 111, 167, 114, 63, - 0, 0, 0, 0, 47, 0, - 0, 5, 114, 0, 16, 0, - 5, 0, 0, 0, 70, 2, - 16, 0, 5, 0, 0, 0, - 56, 0, 0, 10, 114, 0, - 16, 0, 5, 0, 0, 0, - 70, 2, 16, 0, 5, 0, - 0, 0, 2, 64, 0, 0, - 154, 153, 25, 64, 154, 153, - 25, 64, 154, 153, 25, 64, - 0, 0, 0, 0, 25, 0, - 0, 5, 114, 0, 16, 0, - 5, 0, 0, 0, 70, 2, - 16, 0, 5, 0, 0, 0, - 55, 0, 0, 9, 114, 0, - 16, 0, 2, 0, 0, 0, - 70, 2, 16, 0, 3, 0, - 0, 0, 70, 2, 16, 0, - 4, 0, 0, 0, 70, 2, - 16, 0, 5, 0, 0, 0, - 21, 0, 0, 1, 56, 0, - 0, 8, 114, 0, 16, 0, - 1, 0, 0, 0, 70, 2, - 16, 0, 2, 0, 0, 0, - 246, 143, 32, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 18, 0, 0, 1, 31, 0, - 4, 3, 42, 0, 16, 0, - 0, 0, 0, 0, 56, 0, - 0, 8, 114, 0, 16, 0, - 1, 0, 0, 0, 70, 2, - 16, 0, 2, 0, 0, 0, - 246, 143, 32, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 57, 0, 0, 11, 18, 0, - 16, 0, 0, 0, 0, 0, - 2, 64, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 10, 128, 32, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 31, 0, 0, 3, - 10, 0, 16, 0, 0, 0, - 0, 0, 29, 0, 0, 10, - 210, 0, 16, 0, 0, 0, - 0, 0, 2, 64, 0, 0, - 28, 46, 77, 59, 0, 0, - 0, 0, 28, 46, 77, 59, - 28, 46, 77, 59, 6, 9, - 16, 0, 1, 0, 0, 0, - 56, 0, 0, 10, 114, 0, - 16, 0, 3, 0, 0, 0, - 70, 2, 16, 0, 1, 0, - 0, 0, 2, 64, 0, 0, - 82, 184, 78, 65, 82, 184, - 78, 65, 82, 184, 78, 65, - 0, 0, 0, 0, 47, 0, - 0, 6, 114, 0, 16, 0, - 4, 0, 0, 0, 70, 2, - 16, 128, 129, 0, 0, 0, - 1, 0, 0, 0, 56, 0, - 0, 10, 114, 0, 16, 0, - 4, 0, 0, 0, 70, 2, - 16, 0, 4, 0, 0, 0, - 2, 64, 0, 0, 85, 85, - 213, 62, 85, 85, 213, 62, - 85, 85, 213, 62, 0, 0, - 0, 0, 25, 0, 0, 5, - 114, 0, 16, 0, 4, 0, - 0, 0, 70, 2, 16, 0, - 4, 0, 0, 0, 50, 0, - 0, 15, 114, 0, 16, 0, - 4, 0, 0, 0, 70, 2, - 16, 0, 4, 0, 0, 0, - 2, 64, 0, 0, 61, 10, - 135, 63, 61, 10, 135, 63, - 61, 10, 135, 63, 0, 0, - 0, 0, 2, 64, 0, 0, - 174, 71, 97, 189, 174, 71, - 97, 189, 174, 71, 97, 189, - 0, 0, 0, 0, 55, 32, - 0, 9, 114, 0, 16, 0, - 1, 0, 0, 0, 134, 3, - 16, 0, 0, 0, 0, 0, - 70, 2, 16, 0, 3, 0, - 0, 0, 70, 2, 16, 0, - 4, 0, 0, 0, 21, 0, - 0, 1, 18, 0, 0, 1, - 31, 0, 4, 3, 26, 0, - 16, 0, 0, 0, 0, 0, - 16, 0, 0, 10, 18, 0, - 16, 0, 0, 0, 0, 0, - 2, 64, 0, 0, 34, 139, - 212, 63, 160, 112, 22, 191, - 35, 45, 149, 189, 0, 0, - 0, 0, 70, 2, 16, 0, - 2, 0, 0, 0, 16, 0, - 0, 10, 34, 0, 16, 0, - 0, 0, 0, 0, 2, 64, - 0, 0, 127, 18, 255, 189, - 180, 2, 145, 63, 13, 198, - 8, 188, 0, 0, 0, 0, - 70, 2, 16, 0, 2, 0, - 0, 0, 16, 0, 0, 10, - 66, 0, 16, 0, 0, 0, - 0, 0, 2, 64, 0, 0, - 179, 183, 148, 188, 205, 5, - 206, 189, 60, 51, 143, 63, - 0, 0, 0, 0, 70, 2, - 16, 0, 2, 0, 0, 0, - 56, 0, 0, 8, 114, 0, - 16, 0, 1, 0, 0, 0, - 70, 2, 16, 0, 0, 0, - 0, 0, 246, 143, 32, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 57, 0, 0, 11, - 18, 0, 16, 0, 0, 0, - 0, 0, 2, 64, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 10, 128, - 32, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 31, 0, - 0, 3, 10, 0, 16, 0, - 0, 0, 0, 0, 29, 0, - 0, 10, 114, 0, 16, 0, - 0, 0, 0, 0, 2, 64, - 0, 0, 28, 46, 77, 59, - 28, 46, 77, 59, 28, 46, - 77, 59, 0, 0, 0, 0, - 70, 2, 16, 0, 1, 0, - 0, 0, 56, 0, 0, 10, - 114, 0, 16, 0, 3, 0, - 0, 0, 70, 2, 16, 0, - 1, 0, 0, 0, 2, 64, - 0, 0, 82, 184, 78, 65, - 82, 184, 78, 65, 82, 184, - 78, 65, 0, 0, 0, 0, - 47, 0, 0, 6, 114, 0, - 16, 0, 4, 0, 0, 0, - 70, 2, 16, 128, 129, 0, - 0, 0, 1, 0, 0, 0, - 56, 0, 0, 10, 114, 0, - 16, 0, 4, 0, 0, 0, - 70, 2, 16, 0, 4, 0, - 0, 0, 2, 64, 0, 0, - 85, 85, 213, 62, 85, 85, - 213, 62, 85, 85, 213, 62, - 0, 0, 0, 0, 25, 0, - 0, 5, 114, 0, 16, 0, - 4, 0, 0, 0, 70, 2, - 16, 0, 4, 0, 0, 0, - 50, 0, 0, 15, 114, 0, - 16, 0, 4, 0, 0, 0, - 70, 2, 16, 0, 4, 0, - 0, 0, 2, 64, 0, 0, - 61, 10, 135, 63, 61, 10, - 135, 63, 61, 10, 135, 63, - 0, 0, 0, 0, 2, 64, - 0, 0, 174, 71, 97, 189, - 174, 71, 97, 189, 174, 71, - 97, 189, 0, 0, 0, 0, - 55, 32, 0, 9, 114, 0, - 16, 0, 1, 0, 0, 0, - 70, 2, 16, 0, 0, 0, - 0, 0, 70, 2, 16, 0, - 3, 0, 0, 0, 70, 2, - 16, 0, 4, 0, 0, 0, - 21, 0, 0, 1, 18, 0, - 0, 1, 56, 0, 0, 8, - 114, 0, 16, 0, 1, 0, - 0, 0, 70, 2, 16, 0, - 2, 0, 0, 0, 246, 143, - 32, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 21, 0, - 0, 1, 21, 0, 0, 1, - 21, 0, 0, 1, 56, 0, - 0, 7, 242, 32, 16, 0, - 0, 0, 0, 0, 70, 14, - 16, 0, 1, 0, 0, 0, - 70, 30, 16, 0, 2, 0, - 0, 0, 62, 0, 0, 1, - 83, 84, 65, 84, 148, 0, - 0, 0, 126, 0, 0, 0, - 7, 0, 0, 0, 0, 0, - 0, 0, 3, 0, 0, 0, - 71, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 9, 0, 0, 0, 11, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 8, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 3, 0, 0, 0, 10, 0, - 0, 0, 3, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0 + 68, 88, 66, 67, 211, 185, + 112, 94, 11, 90, 193, 206, + 105, 12, 109, 71, 22, 113, + 149, 88, 1, 0, 0, 0, + 152, 23, 0, 0, 5, 0, + 0, 0, 52, 0, 0, 0, + 128, 4, 0, 0, 244, 4, + 0, 0, 40, 5, 0, 0, + 252, 22, 0, 0, 82, 68, + 69, 70, 68, 4, 0, 0, + 1, 0, 0, 0, 12, 1, + 0, 0, 5, 0, 0, 0, + 60, 0, 0, 0, 0, 5, + 255, 255, 0, 1, 0, 0, + 28, 4, 0, 0, 82, 68, + 49, 49, 60, 0, 0, 0, + 24, 0, 0, 0, 32, 0, + 0, 0, 40, 0, 0, 0, + 36, 0, 0, 0, 12, 0, + 0, 0, 0, 0, 0, 0, + 220, 0, 0, 0, 3, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 1, 0, + 0, 0, 229, 0, 0, 0, + 2, 0, 0, 0, 5, 0, + 0, 0, 4, 0, 0, 0, + 255, 255, 255, 255, 0, 0, + 0, 0, 1, 0, 0, 0, + 13, 0, 0, 0, 238, 0, + 0, 0, 2, 0, 0, 0, + 5, 0, 0, 0, 4, 0, + 0, 0, 255, 255, 255, 255, + 1, 0, 0, 0, 1, 0, + 0, 0, 13, 0, 0, 0, + 247, 0, 0, 0, 2, 0, + 0, 0, 5, 0, 0, 0, + 4, 0, 0, 0, 255, 255, + 255, 255, 2, 0, 0, 0, + 1, 0, 0, 0, 13, 0, + 0, 0, 0, 1, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 115, 97, + 109, 112, 108, 101, 114, 48, + 0, 116, 101, 120, 116, 117, + 114, 101, 48, 0, 116, 101, + 120, 116, 117, 114, 101, 49, + 0, 116, 101, 120, 116, 117, + 114, 101, 50, 0, 67, 111, + 110, 115, 116, 97, 110, 116, + 115, 0, 171, 171, 0, 1, + 0, 0, 13, 0, 0, 0, + 36, 1, 0, 0, 112, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 44, 3, + 0, 0, 0, 0, 0, 0, + 4, 0, 0, 0, 2, 0, + 0, 0, 64, 3, 0, 0, + 0, 0, 0, 0, 255, 255, + 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, + 0, 0, 100, 3, 0, 0, + 4, 0, 0, 0, 4, 0, + 0, 0, 2, 0, 0, 0, + 64, 3, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, + 0, 0, 0, 0, 255, 255, + 255, 255, 0, 0, 0, 0, + 113, 3, 0, 0, 8, 0, + 0, 0, 4, 0, 0, 0, + 2, 0, 0, 0, 64, 3, + 0, 0, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, + 0, 0, 255, 255, 255, 255, + 0, 0, 0, 0, 124, 3, + 0, 0, 12, 0, 0, 0, + 4, 0, 0, 0, 2, 0, + 0, 0, 64, 3, 0, 0, + 0, 0, 0, 0, 255, 255, + 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, + 0, 0, 136, 3, 0, 0, + 16, 0, 0, 0, 16, 0, + 0, 0, 2, 0, 0, 0, + 156, 3, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, + 0, 0, 0, 0, 255, 255, + 255, 255, 0, 0, 0, 0, + 192, 3, 0, 0, 32, 0, + 0, 0, 4, 0, 0, 0, + 2, 0, 0, 0, 64, 3, + 0, 0, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, + 0, 0, 255, 255, 255, 255, + 0, 0, 0, 0, 207, 3, + 0, 0, 36, 0, 0, 0, + 4, 0, 0, 0, 2, 0, + 0, 0, 64, 3, 0, 0, + 0, 0, 0, 0, 255, 255, + 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, + 0, 0, 223, 3, 0, 0, + 40, 0, 0, 0, 4, 0, + 0, 0, 2, 0, 0, 0, + 64, 3, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, + 0, 0, 0, 0, 255, 255, + 255, 255, 0, 0, 0, 0, + 239, 3, 0, 0, 44, 0, + 0, 0, 4, 0, 0, 0, + 2, 0, 0, 0, 64, 3, + 0, 0, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, + 0, 0, 255, 255, 255, 255, + 0, 0, 0, 0, 255, 3, + 0, 0, 48, 0, 0, 0, + 16, 0, 0, 0, 2, 0, + 0, 0, 156, 3, 0, 0, + 0, 0, 0, 0, 255, 255, + 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, + 0, 0, 7, 4, 0, 0, + 64, 0, 0, 0, 16, 0, + 0, 0, 2, 0, 0, 0, + 156, 3, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, + 0, 0, 0, 0, 255, 255, + 255, 255, 0, 0, 0, 0, + 14, 4, 0, 0, 80, 0, + 0, 0, 16, 0, 0, 0, + 2, 0, 0, 0, 156, 3, + 0, 0, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, + 0, 0, 255, 255, 255, 255, + 0, 0, 0, 0, 21, 4, + 0, 0, 96, 0, 0, 0, + 16, 0, 0, 0, 2, 0, + 0, 0, 156, 3, 0, 0, + 0, 0, 0, 0, 255, 255, + 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, + 0, 0, 115, 99, 82, 71, + 66, 95, 111, 117, 116, 112, + 117, 116, 0, 102, 108, 111, + 97, 116, 0, 171, 0, 0, + 3, 0, 1, 0, 1, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 57, 3, 0, 0, 116, 101, + 120, 116, 117, 114, 101, 95, + 116, 121, 112, 101, 0, 105, + 110, 112, 117, 116, 95, 116, + 121, 112, 101, 0, 99, 111, + 108, 111, 114, 95, 115, 99, + 97, 108, 101, 0, 116, 101, + 120, 101, 108, 95, 115, 105, + 122, 101, 0, 102, 108, 111, + 97, 116, 52, 0, 171, 171, + 1, 0, 3, 0, 1, 0, + 4, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 147, 3, 0, 0, + 116, 111, 110, 101, 109, 97, + 112, 95, 109, 101, 116, 104, + 111, 100, 0, 116, 111, 110, + 101, 109, 97, 112, 95, 102, + 97, 99, 116, 111, 114, 49, + 0, 116, 111, 110, 101, 109, + 97, 112, 95, 102, 97, 99, + 116, 111, 114, 50, 0, 115, + 100, 114, 95, 119, 104, 105, + 116, 101, 95, 112, 111, 105, + 110, 116, 0, 89, 111, 102, + 102, 115, 101, 116, 0, 82, + 99, 111, 101, 102, 102, 0, + 71, 99, 111, 101, 102, 102, + 0, 66, 99, 111, 101, 102, + 102, 0, 77, 105, 99, 114, + 111, 115, 111, 102, 116, 32, + 40, 82, 41, 32, 72, 76, + 83, 76, 32, 83, 104, 97, + 100, 101, 114, 32, 67, 111, + 109, 112, 105, 108, 101, 114, + 32, 49, 48, 46, 49, 0, + 73, 83, 71, 78, 108, 0, + 0, 0, 3, 0, 0, 0, + 8, 0, 0, 0, 80, 0, + 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 3, 0, + 0, 0, 0, 0, 0, 0, + 15, 0, 0, 0, 92, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 3, 0, + 0, 0, 1, 0, 0, 0, + 3, 3, 0, 0, 101, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 3, 0, + 0, 0, 2, 0, 0, 0, + 15, 15, 0, 0, 83, 86, + 95, 80, 79, 83, 73, 84, + 73, 79, 78, 0, 84, 69, + 88, 67, 79, 79, 82, 68, + 0, 67, 79, 76, 79, 82, + 0, 171, 79, 83, 71, 78, + 44, 0, 0, 0, 1, 0, + 0, 0, 8, 0, 0, 0, + 32, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 3, 0, 0, 0, 0, 0, + 0, 0, 15, 0, 0, 0, + 83, 86, 95, 84, 65, 82, + 71, 69, 84, 0, 171, 171, + 83, 72, 69, 88, 204, 17, + 0, 0, 80, 0, 0, 0, + 115, 4, 0, 0, 106, 8, + 0, 1, 89, 0, 0, 4, + 70, 142, 32, 0, 0, 0, + 0, 0, 7, 0, 0, 0, + 90, 0, 0, 3, 0, 96, + 16, 0, 0, 0, 0, 0, + 88, 24, 0, 4, 0, 112, + 16, 0, 0, 0, 0, 0, + 85, 85, 0, 0, 88, 24, + 0, 4, 0, 112, 16, 0, + 1, 0, 0, 0, 85, 85, + 0, 0, 88, 24, 0, 4, + 0, 112, 16, 0, 2, 0, + 0, 0, 85, 85, 0, 0, + 98, 16, 0, 3, 50, 16, + 16, 0, 1, 0, 0, 0, + 98, 16, 0, 3, 242, 16, + 16, 0, 2, 0, 0, 0, + 101, 0, 0, 3, 242, 32, + 16, 0, 0, 0, 0, 0, + 104, 0, 0, 2, 7, 0, + 0, 0, 24, 0, 0, 11, + 242, 0, 16, 0, 0, 0, + 0, 0, 150, 138, 32, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 2, 64, 0, 0, + 0, 0, 0, 0, 0, 0, + 64, 64, 0, 0, 0, 64, + 0, 0, 128, 63, 31, 0, + 4, 3, 10, 0, 16, 0, + 0, 0, 0, 0, 54, 0, + 0, 8, 242, 0, 16, 0, + 1, 0, 0, 0, 2, 64, + 0, 0, 0, 0, 128, 63, + 0, 0, 128, 63, 0, 0, + 128, 63, 0, 0, 128, 63, + 18, 0, 0, 1, 24, 0, + 0, 8, 18, 0, 16, 0, + 0, 0, 0, 0, 26, 128, + 32, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 1, 64, + 0, 0, 0, 0, 128, 63, + 31, 0, 4, 3, 10, 0, + 16, 0, 0, 0, 0, 0, + 69, 0, 0, 139, 194, 0, + 0, 128, 67, 85, 21, 0, + 242, 0, 16, 0, 1, 0, + 0, 0, 70, 16, 16, 0, + 1, 0, 0, 0, 70, 126, + 16, 0, 0, 0, 0, 0, + 0, 96, 16, 0, 0, 0, + 0, 0, 18, 0, 0, 1, + 24, 0, 0, 8, 18, 0, + 16, 0, 0, 0, 0, 0, + 26, 128, 32, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 1, 64, 0, 0, 0, 0, + 0, 64, 31, 0, 4, 3, + 10, 0, 16, 0, 0, 0, + 0, 0, 122, 0, 0, 5, + 50, 0, 16, 0, 2, 0, + 0, 0, 70, 16, 16, 0, + 1, 0, 0, 0, 124, 0, + 0, 5, 194, 0, 16, 0, + 2, 0, 0, 0, 6, 20, + 16, 0, 1, 0, 0, 0, + 0, 0, 0, 9, 50, 0, + 16, 0, 3, 0, 0, 0, + 230, 10, 16, 128, 129, 0, + 0, 0, 2, 0, 0, 0, + 70, 0, 16, 128, 129, 0, + 0, 0, 2, 0, 0, 0, + 56, 0, 0, 8, 50, 0, + 16, 0, 3, 0, 0, 0, + 70, 0, 16, 0, 3, 0, + 0, 0, 230, 138, 32, 0, + 0, 0, 0, 0, 1, 0, + 0, 0, 52, 0, 0, 10, + 50, 0, 16, 0, 3, 0, + 0, 0, 70, 0, 16, 0, + 3, 0, 0, 0, 2, 64, + 0, 0, 172, 197, 39, 55, + 172, 197, 39, 55, 0, 0, + 0, 0, 0, 0, 0, 0, + 51, 0, 0, 10, 50, 0, + 16, 0, 3, 0, 0, 0, + 70, 0, 16, 0, 3, 0, + 0, 0, 2, 64, 0, 0, + 0, 0, 128, 63, 0, 0, + 128, 63, 0, 0, 0, 0, + 0, 0, 0, 0, 56, 0, + 0, 10, 194, 0, 16, 0, + 3, 0, 0, 0, 6, 4, + 16, 0, 3, 0, 0, 0, + 2, 64, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 63, 0, 0, + 0, 63, 50, 0, 0, 11, + 194, 0, 16, 0, 3, 0, + 0, 0, 6, 20, 16, 0, + 1, 0, 0, 0, 166, 142, + 32, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 166, 14, + 16, 128, 65, 0, 0, 0, + 3, 0, 0, 0, 0, 0, + 0, 11, 50, 0, 16, 0, + 3, 0, 0, 0, 70, 0, + 16, 128, 65, 0, 0, 0, + 3, 0, 0, 0, 2, 64, + 0, 0, 0, 0, 128, 63, + 0, 0, 128, 63, 0, 0, + 0, 0, 0, 0, 0, 0, + 26, 0, 0, 5, 50, 0, + 16, 0, 4, 0, 0, 0, + 230, 10, 16, 0, 3, 0, + 0, 0, 0, 0, 0, 11, + 194, 0, 16, 0, 4, 0, + 0, 0, 6, 4, 16, 128, + 65, 0, 0, 0, 3, 0, + 0, 0, 2, 64, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 128, 63, + 0, 0, 128, 63, 0, 0, + 0, 8, 50, 0, 16, 0, + 3, 0, 0, 0, 70, 0, + 16, 128, 65, 0, 0, 0, + 3, 0, 0, 0, 70, 0, + 16, 0, 4, 0, 0, 0, + 14, 0, 0, 10, 50, 0, + 16, 0, 4, 0, 0, 0, + 2, 64, 0, 0, 0, 0, + 128, 63, 0, 0, 128, 63, + 0, 0, 128, 63, 0, 0, + 128, 63, 230, 10, 16, 0, + 4, 0, 0, 0, 56, 32, + 0, 7, 50, 0, 16, 0, + 3, 0, 0, 0, 70, 0, + 16, 0, 3, 0, 0, 0, + 70, 0, 16, 0, 4, 0, + 0, 0, 50, 0, 0, 15, + 50, 0, 16, 0, 4, 0, + 0, 0, 70, 0, 16, 0, + 3, 0, 0, 0, 2, 64, + 0, 0, 0, 0, 0, 192, + 0, 0, 0, 192, 0, 0, + 0, 0, 0, 0, 0, 0, + 2, 64, 0, 0, 0, 0, + 64, 64, 0, 0, 64, 64, + 0, 0, 0, 0, 0, 0, + 0, 0, 56, 0, 0, 7, + 50, 0, 16, 0, 3, 0, + 0, 0, 70, 0, 16, 0, + 3, 0, 0, 0, 70, 0, + 16, 0, 3, 0, 0, 0, + 65, 0, 0, 5, 194, 0, + 16, 0, 3, 0, 0, 0, + 166, 14, 16, 0, 3, 0, + 0, 0, 50, 0, 0, 9, + 50, 0, 16, 0, 3, 0, + 0, 0, 70, 0, 16, 0, + 4, 0, 0, 0, 70, 0, + 16, 0, 3, 0, 0, 0, + 230, 10, 16, 0, 3, 0, + 0, 0, 0, 0, 0, 10, + 50, 0, 16, 0, 3, 0, + 0, 0, 70, 0, 16, 0, + 3, 0, 0, 0, 2, 64, + 0, 0, 0, 0, 0, 63, + 0, 0, 0, 63, 0, 0, + 0, 0, 0, 0, 0, 0, + 56, 0, 0, 8, 50, 0, + 16, 0, 3, 0, 0, 0, + 70, 0, 16, 0, 3, 0, + 0, 0, 70, 128, 32, 0, + 0, 0, 0, 0, 1, 0, + 0, 0, 73, 0, 0, 143, + 194, 0, 0, 128, 67, 85, + 21, 0, 242, 0, 16, 0, + 1, 0, 0, 0, 70, 0, + 16, 0, 3, 0, 0, 0, + 70, 126, 16, 0, 0, 0, + 0, 0, 0, 96, 16, 0, + 0, 0, 0, 0, 70, 0, + 16, 0, 2, 0, 0, 0, + 230, 10, 16, 0, 2, 0, + 0, 0, 18, 0, 0, 1, + 24, 0, 0, 8, 18, 0, + 16, 0, 0, 0, 0, 0, + 26, 128, 32, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 1, 64, 0, 0, 0, 0, + 64, 64, 31, 0, 4, 3, + 10, 0, 16, 0, 0, 0, + 0, 0, 69, 0, 0, 139, + 194, 0, 0, 128, 67, 85, + 21, 0, 18, 0, 16, 0, + 2, 0, 0, 0, 70, 16, + 16, 0, 1, 0, 0, 0, + 70, 126, 16, 0, 0, 0, + 0, 0, 0, 96, 16, 0, + 0, 0, 0, 0, 69, 0, + 0, 139, 194, 0, 0, 128, + 67, 85, 21, 0, 98, 0, + 16, 0, 2, 0, 0, 0, + 70, 16, 16, 0, 1, 0, + 0, 0, 38, 125, 16, 0, + 1, 0, 0, 0, 0, 96, + 16, 0, 0, 0, 0, 0, + 0, 0, 0, 8, 114, 0, + 16, 0, 2, 0, 0, 0, + 70, 2, 16, 0, 2, 0, + 0, 0, 70, 130, 32, 0, + 0, 0, 0, 0, 3, 0, + 0, 0, 16, 0, 0, 8, + 18, 0, 16, 0, 1, 0, + 0, 0, 70, 2, 16, 0, + 2, 0, 0, 0, 70, 130, + 32, 0, 0, 0, 0, 0, + 4, 0, 0, 0, 16, 0, + 0, 8, 34, 0, 16, 0, + 1, 0, 0, 0, 70, 2, + 16, 0, 2, 0, 0, 0, + 70, 130, 32, 0, 0, 0, + 0, 0, 5, 0, 0, 0, + 16, 0, 0, 8, 66, 0, + 16, 0, 1, 0, 0, 0, + 70, 2, 16, 0, 2, 0, + 0, 0, 70, 130, 32, 0, + 0, 0, 0, 0, 6, 0, + 0, 0, 18, 0, 0, 1, + 24, 0, 0, 8, 18, 0, + 16, 0, 0, 0, 0, 0, + 26, 128, 32, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 1, 64, 0, 0, 0, 0, + 128, 64, 31, 0, 4, 3, + 10, 0, 16, 0, 0, 0, + 0, 0, 69, 0, 0, 139, + 194, 0, 0, 128, 67, 85, + 21, 0, 18, 0, 16, 0, + 2, 0, 0, 0, 70, 16, + 16, 0, 1, 0, 0, 0, + 70, 126, 16, 0, 0, 0, + 0, 0, 0, 96, 16, 0, + 0, 0, 0, 0, 69, 0, + 0, 139, 194, 0, 0, 128, + 67, 85, 21, 0, 98, 0, + 16, 0, 2, 0, 0, 0, + 70, 16, 16, 0, 1, 0, + 0, 0, 102, 124, 16, 0, + 1, 0, 0, 0, 0, 96, + 16, 0, 0, 0, 0, 0, + 0, 0, 0, 8, 114, 0, + 16, 0, 2, 0, 0, 0, + 70, 2, 16, 0, 2, 0, + 0, 0, 70, 130, 32, 0, + 0, 0, 0, 0, 3, 0, + 0, 0, 16, 0, 0, 8, + 18, 0, 16, 0, 1, 0, + 0, 0, 70, 2, 16, 0, + 2, 0, 0, 0, 70, 130, + 32, 0, 0, 0, 0, 0, + 4, 0, 0, 0, 16, 0, + 0, 8, 34, 0, 16, 0, + 1, 0, 0, 0, 70, 2, + 16, 0, 2, 0, 0, 0, + 70, 130, 32, 0, 0, 0, + 0, 0, 5, 0, 0, 0, + 16, 0, 0, 8, 66, 0, + 16, 0, 1, 0, 0, 0, + 70, 2, 16, 0, 2, 0, + 0, 0, 70, 130, 32, 0, + 0, 0, 0, 0, 6, 0, + 0, 0, 18, 0, 0, 1, + 24, 0, 0, 8, 18, 0, + 16, 0, 0, 0, 0, 0, + 26, 128, 32, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 1, 64, 0, 0, 0, 0, + 160, 64, 31, 0, 4, 3, + 10, 0, 16, 0, 0, 0, + 0, 0, 69, 0, 0, 139, + 194, 0, 0, 128, 67, 85, + 21, 0, 18, 0, 16, 0, + 2, 0, 0, 0, 70, 16, + 16, 0, 1, 0, 0, 0, + 70, 126, 16, 0, 0, 0, + 0, 0, 0, 96, 16, 0, + 0, 0, 0, 0, 69, 0, + 0, 139, 194, 0, 0, 128, + 67, 85, 21, 0, 34, 0, + 16, 0, 2, 0, 0, 0, + 70, 16, 16, 0, 1, 0, + 0, 0, 22, 126, 16, 0, + 1, 0, 0, 0, 0, 96, + 16, 0, 0, 0, 0, 0, + 69, 0, 0, 139, 194, 0, + 0, 128, 67, 85, 21, 0, + 66, 0, 16, 0, 2, 0, + 0, 0, 70, 16, 16, 0, + 1, 0, 0, 0, 150, 124, + 16, 0, 2, 0, 0, 0, + 0, 96, 16, 0, 0, 0, + 0, 0, 0, 0, 0, 8, + 114, 0, 16, 0, 2, 0, + 0, 0, 70, 2, 16, 0, + 2, 0, 0, 0, 70, 130, + 32, 0, 0, 0, 0, 0, + 3, 0, 0, 0, 16, 0, + 0, 8, 18, 0, 16, 0, + 1, 0, 0, 0, 70, 2, + 16, 0, 2, 0, 0, 0, + 70, 130, 32, 0, 0, 0, + 0, 0, 4, 0, 0, 0, + 16, 0, 0, 8, 34, 0, + 16, 0, 1, 0, 0, 0, + 70, 2, 16, 0, 2, 0, + 0, 0, 70, 130, 32, 0, + 0, 0, 0, 0, 5, 0, + 0, 0, 16, 0, 0, 8, + 66, 0, 16, 0, 1, 0, + 0, 0, 70, 2, 16, 0, + 2, 0, 0, 0, 70, 130, + 32, 0, 0, 0, 0, 0, + 6, 0, 0, 0, 18, 0, + 0, 1, 54, 0, 0, 8, + 114, 0, 16, 0, 1, 0, + 0, 0, 2, 64, 0, 0, + 0, 0, 128, 63, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 21, 0, + 0, 1, 21, 0, 0, 1, + 21, 0, 0, 1, 54, 0, + 0, 5, 130, 0, 16, 0, + 1, 0, 0, 0, 1, 64, + 0, 0, 0, 0, 128, 63, + 21, 0, 0, 1, 21, 0, + 0, 1, 21, 0, 0, 1, + 47, 0, 0, 6, 114, 0, + 16, 0, 2, 0, 0, 0, + 70, 2, 16, 128, 129, 0, + 0, 0, 1, 0, 0, 0, + 56, 0, 0, 10, 114, 0, + 16, 0, 2, 0, 0, 0, + 70, 2, 16, 0, 2, 0, + 0, 0, 2, 64, 0, 0, + 172, 205, 79, 60, 172, 205, + 79, 60, 172, 205, 79, 60, + 0, 0, 0, 0, 25, 0, + 0, 5, 114, 0, 16, 0, + 2, 0, 0, 0, 70, 2, + 16, 0, 2, 0, 0, 0, + 0, 0, 0, 10, 114, 0, + 16, 0, 3, 0, 0, 0, + 70, 2, 16, 0, 2, 0, + 0, 0, 2, 64, 0, 0, + 0, 0, 86, 191, 0, 0, + 86, 191, 0, 0, 86, 191, + 0, 0, 0, 0, 52, 0, + 0, 10, 114, 0, 16, 0, + 3, 0, 0, 0, 70, 2, + 16, 0, 3, 0, 0, 0, + 2, 64, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 50, 0, 0, 16, + 114, 0, 16, 0, 2, 0, + 0, 0, 70, 2, 16, 128, + 65, 0, 0, 0, 2, 0, + 0, 0, 2, 64, 0, 0, + 0, 128, 149, 65, 0, 128, + 149, 65, 0, 128, 149, 65, + 0, 0, 0, 0, 2, 64, + 0, 0, 0, 208, 150, 65, + 0, 208, 150, 65, 0, 208, + 150, 65, 0, 0, 0, 0, + 14, 0, 0, 7, 114, 0, + 16, 0, 2, 0, 0, 0, + 70, 2, 16, 0, 3, 0, + 0, 0, 70, 2, 16, 0, + 2, 0, 0, 0, 47, 0, + 0, 6, 114, 0, 16, 0, + 2, 0, 0, 0, 70, 2, + 16, 128, 129, 0, 0, 0, + 2, 0, 0, 0, 56, 0, + 0, 10, 114, 0, 16, 0, + 2, 0, 0, 0, 70, 2, + 16, 0, 2, 0, 0, 0, + 2, 64, 0, 0, 107, 224, + 200, 64, 107, 224, 200, 64, + 107, 224, 200, 64, 0, 0, + 0, 0, 25, 0, 0, 5, + 114, 0, 16, 0, 2, 0, + 0, 0, 70, 2, 16, 0, + 2, 0, 0, 0, 56, 0, + 0, 10, 114, 0, 16, 0, + 2, 0, 0, 0, 70, 2, + 16, 0, 2, 0, 0, 0, + 2, 64, 0, 0, 0, 64, + 28, 70, 0, 64, 28, 70, + 0, 64, 28, 70, 0, 0, + 0, 0, 14, 0, 0, 8, + 114, 0, 16, 0, 2, 0, + 0, 0, 70, 2, 16, 0, + 2, 0, 0, 0, 246, 143, + 32, 0, 0, 0, 0, 0, + 2, 0, 0, 0, 55, 0, + 0, 9, 114, 0, 16, 0, + 2, 0, 0, 0, 86, 5, + 16, 0, 0, 0, 0, 0, + 70, 2, 16, 0, 2, 0, + 0, 0, 70, 2, 16, 0, + 1, 0, 0, 0, 57, 0, + 0, 8, 18, 0, 16, 0, + 0, 0, 0, 0, 10, 128, + 32, 0, 0, 0, 0, 0, + 2, 0, 0, 0, 1, 64, + 0, 0, 0, 0, 0, 0, + 56, 0, 0, 8, 114, 0, + 16, 0, 3, 0, 0, 0, + 70, 2, 16, 0, 2, 0, + 0, 0, 86, 133, 32, 0, + 0, 0, 0, 0, 2, 0, + 0, 0, 24, 0, 0, 11, + 50, 0, 16, 0, 4, 0, + 0, 0, 6, 128, 32, 0, + 0, 0, 0, 0, 2, 0, + 0, 0, 2, 64, 0, 0, + 0, 0, 128, 63, 0, 0, + 0, 64, 0, 0, 0, 0, + 0, 0, 0, 0, 16, 0, + 0, 10, 18, 0, 16, 0, + 5, 0, 0, 0, 2, 64, + 0, 0, 140, 157, 32, 63, + 200, 151, 168, 62, 249, 104, + 49, 61, 0, 0, 0, 0, + 70, 2, 16, 0, 2, 0, + 0, 0, 16, 0, 0, 10, + 34, 0, 16, 0, 5, 0, + 0, 0, 2, 64, 0, 0, + 186, 130, 141, 61, 10, 103, + 107, 63, 175, 39, 58, 60, + 0, 0, 0, 0, 70, 2, + 16, 0, 2, 0, 0, 0, + 16, 0, 0, 10, 66, 0, + 16, 0, 5, 0, 0, 0, + 2, 64, 0, 0, 107, 70, + 134, 60, 41, 64, 180, 61, + 183, 69, 101, 63, 0, 0, + 0, 0, 70, 2, 16, 0, + 2, 0, 0, 0, 55, 0, + 0, 9, 114, 0, 16, 0, + 5, 0, 0, 0, 166, 10, + 16, 0, 0, 0, 0, 0, + 70, 2, 16, 0, 5, 0, + 0, 0, 70, 2, 16, 0, + 2, 0, 0, 0, 52, 0, + 0, 7, 130, 0, 16, 0, + 2, 0, 0, 0, 42, 0, + 16, 0, 5, 0, 0, 0, + 26, 0, 16, 0, 5, 0, + 0, 0, 52, 0, 0, 7, + 130, 0, 16, 0, 2, 0, + 0, 0, 58, 0, 16, 0, + 2, 0, 0, 0, 10, 0, + 16, 0, 5, 0, 0, 0, + 49, 0, 0, 7, 130, 0, + 16, 0, 3, 0, 0, 0, + 1, 64, 0, 0, 0, 0, + 0, 0, 58, 0, 16, 0, + 2, 0, 0, 0, 50, 0, + 0, 13, 194, 0, 16, 0, + 4, 0, 0, 0, 86, 137, + 32, 0, 0, 0, 0, 0, + 2, 0, 0, 0, 246, 15, + 16, 0, 2, 0, 0, 0, + 2, 64, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 128, 63, 0, 0, + 128, 63, 14, 0, 0, 7, + 130, 0, 16, 0, 2, 0, + 0, 0, 42, 0, 16, 0, + 4, 0, 0, 0, 58, 0, + 16, 0, 4, 0, 0, 0, + 56, 0, 0, 7, 114, 0, + 16, 0, 6, 0, 0, 0, + 246, 15, 16, 0, 2, 0, + 0, 0, 70, 2, 16, 0, + 5, 0, 0, 0, 55, 0, + 0, 9, 114, 0, 16, 0, + 5, 0, 0, 0, 246, 15, + 16, 0, 3, 0, 0, 0, + 70, 2, 16, 0, 6, 0, + 0, 0, 70, 2, 16, 0, + 5, 0, 0, 0, 16, 0, + 0, 10, 18, 0, 16, 0, + 6, 0, 0, 0, 2, 64, + 0, 0, 34, 139, 212, 63, + 160, 112, 22, 191, 35, 45, + 149, 189, 0, 0, 0, 0, + 70, 2, 16, 0, 5, 0, + 0, 0, 16, 0, 0, 10, + 34, 0, 16, 0, 6, 0, + 0, 0, 2, 64, 0, 0, + 127, 18, 255, 189, 180, 2, + 145, 63, 13, 198, 8, 188, + 0, 0, 0, 0, 70, 2, + 16, 0, 5, 0, 0, 0, + 16, 0, 0, 10, 66, 0, + 16, 0, 6, 0, 0, 0, + 2, 64, 0, 0, 179, 183, + 148, 188, 205, 5, 206, 189, + 60, 51, 143, 63, 0, 0, + 0, 0, 70, 2, 16, 0, + 5, 0, 0, 0, 55, 0, + 0, 9, 114, 0, 16, 0, + 5, 0, 0, 0, 166, 10, + 16, 0, 0, 0, 0, 0, + 70, 2, 16, 0, 6, 0, + 0, 0, 70, 2, 16, 0, + 5, 0, 0, 0, 55, 0, + 0, 9, 226, 0, 16, 0, + 4, 0, 0, 0, 86, 5, + 16, 0, 4, 0, 0, 0, + 6, 9, 16, 0, 5, 0, + 0, 0, 6, 9, 16, 0, + 2, 0, 0, 0, 55, 0, + 0, 9, 114, 0, 16, 0, + 3, 0, 0, 0, 6, 0, + 16, 0, 4, 0, 0, 0, + 70, 2, 16, 0, 3, 0, + 0, 0, 150, 7, 16, 0, + 4, 0, 0, 0, 55, 0, + 0, 9, 114, 0, 16, 0, + 2, 0, 0, 0, 6, 0, + 16, 0, 0, 0, 0, 0, + 70, 2, 16, 0, 3, 0, + 0, 0, 70, 2, 16, 0, + 2, 0, 0, 0, 31, 0, + 4, 3, 58, 0, 16, 0, + 0, 0, 0, 0, 57, 0, + 0, 11, 18, 0, 16, 0, + 0, 0, 0, 0, 2, 64, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 10, 128, 32, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 31, 0, 4, 3, 10, 0, + 16, 0, 0, 0, 0, 0, + 29, 0, 0, 10, 114, 0, + 16, 0, 3, 0, 0, 0, + 2, 64, 0, 0, 230, 174, + 37, 61, 230, 174, 37, 61, + 230, 174, 37, 61, 0, 0, + 0, 0, 70, 2, 16, 0, + 2, 0, 0, 0, 56, 0, + 0, 10, 114, 0, 16, 0, + 4, 0, 0, 0, 70, 2, + 16, 0, 2, 0, 0, 0, + 2, 64, 0, 0, 145, 131, + 158, 61, 145, 131, 158, 61, + 145, 131, 158, 61, 0, 0, + 0, 0, 0, 0, 0, 10, + 114, 0, 16, 0, 5, 0, + 0, 0, 70, 2, 16, 0, + 2, 0, 0, 0, 2, 64, + 0, 0, 174, 71, 97, 61, + 174, 71, 97, 61, 174, 71, + 97, 61, 0, 0, 0, 0, + 56, 0, 0, 11, 114, 0, + 16, 0, 5, 0, 0, 0, + 70, 2, 16, 128, 129, 0, + 0, 0, 5, 0, 0, 0, + 2, 64, 0, 0, 111, 167, + 114, 63, 111, 167, 114, 63, + 111, 167, 114, 63, 0, 0, + 0, 0, 47, 0, 0, 5, + 114, 0, 16, 0, 5, 0, + 0, 0, 70, 2, 16, 0, + 5, 0, 0, 0, 56, 0, + 0, 10, 114, 0, 16, 0, + 5, 0, 0, 0, 70, 2, + 16, 0, 5, 0, 0, 0, + 2, 64, 0, 0, 154, 153, + 25, 64, 154, 153, 25, 64, + 154, 153, 25, 64, 0, 0, + 0, 0, 25, 0, 0, 5, + 114, 0, 16, 0, 5, 0, + 0, 0, 70, 2, 16, 0, + 5, 0, 0, 0, 55, 0, + 0, 9, 114, 0, 16, 0, + 2, 0, 0, 0, 70, 2, + 16, 0, 3, 0, 0, 0, + 70, 2, 16, 0, 4, 0, + 0, 0, 70, 2, 16, 0, + 5, 0, 0, 0, 21, 0, + 0, 1, 56, 0, 0, 8, + 114, 0, 16, 0, 1, 0, + 0, 0, 70, 2, 16, 0, + 2, 0, 0, 0, 246, 143, + 32, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 18, 0, + 0, 1, 31, 0, 4, 3, + 42, 0, 16, 0, 0, 0, + 0, 0, 56, 0, 0, 8, + 114, 0, 16, 0, 1, 0, + 0, 0, 70, 2, 16, 0, + 2, 0, 0, 0, 246, 143, + 32, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 57, 0, + 0, 11, 18, 0, 16, 0, + 0, 0, 0, 0, 2, 64, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 10, 128, 32, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 31, 0, 0, 3, 10, 0, + 16, 0, 0, 0, 0, 0, + 29, 0, 0, 10, 210, 0, + 16, 0, 0, 0, 0, 0, + 2, 64, 0, 0, 28, 46, + 77, 59, 0, 0, 0, 0, + 28, 46, 77, 59, 28, 46, + 77, 59, 6, 9, 16, 0, + 1, 0, 0, 0, 56, 0, + 0, 10, 114, 0, 16, 0, + 3, 0, 0, 0, 70, 2, + 16, 0, 1, 0, 0, 0, + 2, 64, 0, 0, 82, 184, + 78, 65, 82, 184, 78, 65, + 82, 184, 78, 65, 0, 0, + 0, 0, 47, 0, 0, 6, + 114, 0, 16, 0, 4, 0, + 0, 0, 70, 2, 16, 128, + 129, 0, 0, 0, 1, 0, + 0, 0, 56, 0, 0, 10, + 114, 0, 16, 0, 4, 0, + 0, 0, 70, 2, 16, 0, + 4, 0, 0, 0, 2, 64, + 0, 0, 85, 85, 213, 62, + 85, 85, 213, 62, 85, 85, + 213, 62, 0, 0, 0, 0, + 25, 0, 0, 5, 114, 0, + 16, 0, 4, 0, 0, 0, + 70, 2, 16, 0, 4, 0, + 0, 0, 50, 0, 0, 15, + 114, 0, 16, 0, 4, 0, + 0, 0, 70, 2, 16, 0, + 4, 0, 0, 0, 2, 64, + 0, 0, 61, 10, 135, 63, + 61, 10, 135, 63, 61, 10, + 135, 63, 0, 0, 0, 0, + 2, 64, 0, 0, 174, 71, + 97, 189, 174, 71, 97, 189, + 174, 71, 97, 189, 0, 0, + 0, 0, 55, 32, 0, 9, + 114, 0, 16, 0, 1, 0, + 0, 0, 134, 3, 16, 0, + 0, 0, 0, 0, 70, 2, + 16, 0, 3, 0, 0, 0, + 70, 2, 16, 0, 4, 0, + 0, 0, 21, 0, 0, 1, + 18, 0, 0, 1, 31, 0, + 4, 3, 26, 0, 16, 0, + 0, 0, 0, 0, 16, 0, + 0, 10, 18, 0, 16, 0, + 0, 0, 0, 0, 2, 64, + 0, 0, 34, 139, 212, 63, + 160, 112, 22, 191, 35, 45, + 149, 189, 0, 0, 0, 0, + 70, 2, 16, 0, 2, 0, + 0, 0, 16, 0, 0, 10, + 34, 0, 16, 0, 0, 0, + 0, 0, 2, 64, 0, 0, + 127, 18, 255, 189, 180, 2, + 145, 63, 13, 198, 8, 188, + 0, 0, 0, 0, 70, 2, + 16, 0, 2, 0, 0, 0, + 16, 0, 0, 10, 66, 0, + 16, 0, 0, 0, 0, 0, + 2, 64, 0, 0, 179, 183, + 148, 188, 205, 5, 206, 189, + 60, 51, 143, 63, 0, 0, + 0, 0, 70, 2, 16, 0, + 2, 0, 0, 0, 56, 0, + 0, 8, 114, 0, 16, 0, + 1, 0, 0, 0, 70, 2, + 16, 0, 0, 0, 0, 0, + 246, 143, 32, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 57, 0, 0, 11, 18, 0, + 16, 0, 0, 0, 0, 0, + 2, 64, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 10, 128, 32, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 31, 0, 0, 3, + 10, 0, 16, 0, 0, 0, + 0, 0, 29, 0, 0, 10, + 114, 0, 16, 0, 0, 0, + 0, 0, 2, 64, 0, 0, + 28, 46, 77, 59, 28, 46, + 77, 59, 28, 46, 77, 59, + 0, 0, 0, 0, 70, 2, + 16, 0, 1, 0, 0, 0, + 56, 0, 0, 10, 114, 0, + 16, 0, 3, 0, 0, 0, + 70, 2, 16, 0, 1, 0, + 0, 0, 2, 64, 0, 0, + 82, 184, 78, 65, 82, 184, + 78, 65, 82, 184, 78, 65, + 0, 0, 0, 0, 47, 0, + 0, 6, 114, 0, 16, 0, + 4, 0, 0, 0, 70, 2, + 16, 128, 129, 0, 0, 0, + 1, 0, 0, 0, 56, 0, + 0, 10, 114, 0, 16, 0, + 4, 0, 0, 0, 70, 2, + 16, 0, 4, 0, 0, 0, + 2, 64, 0, 0, 85, 85, + 213, 62, 85, 85, 213, 62, + 85, 85, 213, 62, 0, 0, + 0, 0, 25, 0, 0, 5, + 114, 0, 16, 0, 4, 0, + 0, 0, 70, 2, 16, 0, + 4, 0, 0, 0, 50, 0, + 0, 15, 114, 0, 16, 0, + 4, 0, 0, 0, 70, 2, + 16, 0, 4, 0, 0, 0, + 2, 64, 0, 0, 61, 10, + 135, 63, 61, 10, 135, 63, + 61, 10, 135, 63, 0, 0, + 0, 0, 2, 64, 0, 0, + 174, 71, 97, 189, 174, 71, + 97, 189, 174, 71, 97, 189, + 0, 0, 0, 0, 55, 32, + 0, 9, 114, 0, 16, 0, + 1, 0, 0, 0, 70, 2, + 16, 0, 0, 0, 0, 0, + 70, 2, 16, 0, 3, 0, + 0, 0, 70, 2, 16, 0, + 4, 0, 0, 0, 21, 0, + 0, 1, 18, 0, 0, 1, + 56, 0, 0, 8, 114, 0, + 16, 0, 1, 0, 0, 0, + 70, 2, 16, 0, 2, 0, + 0, 0, 246, 143, 32, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 21, 0, 0, 1, + 21, 0, 0, 1, 21, 0, + 0, 1, 56, 0, 0, 7, + 242, 32, 16, 0, 0, 0, + 0, 0, 70, 14, 16, 0, + 1, 0, 0, 0, 70, 30, + 16, 0, 2, 0, 0, 0, + 62, 0, 0, 1, 83, 84, + 65, 84, 148, 0, 0, 0, + 151, 0, 0, 0, 7, 0, + 0, 0, 0, 0, 0, 0, + 3, 0, 0, 0, 92, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 10, 0, + 0, 0, 12, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 8, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 3, 0, + 0, 0, 10, 0, 0, 0, + 5, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0 }; diff --git a/libs/SDL3/src/render/direct3d11/D3D11_PixelShader_Colors.h b/libs/SDL3/src/render/direct3d11/D3D11_PixelShader_Colors.h index 6658e4d1..b2e30cea 100644 --- a/libs/SDL3/src/render/direct3d11/D3D11_PixelShader_Colors.h +++ b/libs/SDL3/src/render/direct3d11/D3D11_PixelShader_Colors.h @@ -3,7 +3,7 @@ // Generated by Microsoft (R) HLSL Shader Compiler 10.1 // // -// Buffer Definitions: +// Buffer Definitions: // // cbuffer Constants // { @@ -12,14 +12,15 @@ // float texture_type; // Offset: 4 Size: 4 [unused] // float input_type; // Offset: 8 Size: 4 [unused] // float color_scale; // Offset: 12 Size: 4 -// float tonemap_method; // Offset: 16 Size: 4 [unused] -// float tonemap_factor1; // Offset: 20 Size: 4 [unused] -// float tonemap_factor2; // Offset: 24 Size: 4 [unused] -// float sdr_white_point; // Offset: 28 Size: 4 [unused] -// float4 Yoffset; // Offset: 32 Size: 16 [unused] -// float4 Rcoeff; // Offset: 48 Size: 16 [unused] -// float4 Gcoeff; // Offset: 64 Size: 16 [unused] -// float4 Bcoeff; // Offset: 80 Size: 16 [unused] +// float4 texel_size; // Offset: 16 Size: 16 [unused] +// float tonemap_method; // Offset: 32 Size: 4 [unused] +// float tonemap_factor1; // Offset: 36 Size: 4 [unused] +// float tonemap_factor2; // Offset: 40 Size: 4 [unused] +// float sdr_white_point; // Offset: 44 Size: 4 [unused] +// float4 Yoffset; // Offset: 48 Size: 16 [unused] +// float4 Rcoeff; // Offset: 64 Size: 16 [unused] +// float4 Gcoeff; // Offset: 80 Size: 16 [unused] +// float4 Bcoeff; // Offset: 96 Size: 16 [unused] // // } // @@ -28,7 +29,7 @@ // // Name Type Format Dim HLSL Bind Count // ------------------------------ ---------- ------- ----------- -------------- ------ -// Constants cbuffer NA NA cb0 1 +// Constants cbuffer NA NA cb0 1 // // // @@ -36,8 +37,8 @@ // // Name Index Mask Register SysValue Format Used // -------------------- ----- ------ -------- -------- ------- ------ -// SV_POSITION 0 xyzw 0 POS float -// TEXCOORD 0 xy 1 NONE float +// SV_POSITION 0 xyzw 0 POS float +// TEXCOORD 0 xy 1 NONE float // COLOR 0 xyzw 2 NONE float xyzw // // @@ -72,213 +73,218 @@ dcl_temps 1 mov r0.x, cb0[0].w mov r0.w, l(1.000000) mul o0.xyzw, r0.xxxw, v2.xyzw -ret +ret // Approximately 4 instruction slots used #endif const BYTE g_main[] = { - 68, 88, 66, 67, 78, 223, - 23, 23, 93, 184, 255, 26, - 153, 0, 220, 179, 25, 194, - 30, 249, 1, 0, 0, 0, - 192, 4, 0, 0, 6, 0, - 0, 0, 56, 0, 0, 0, - 172, 0, 0, 0, 56, 1, - 0, 0, 180, 1, 0, 0, - 24, 4, 0, 0, 140, 4, - 0, 0, 65, 111, 110, 57, - 108, 0, 0, 0, 108, 0, - 0, 0, 0, 2, 255, 255, - 60, 0, 0, 0, 48, 0, - 0, 0, 1, 0, 36, 0, - 0, 0, 48, 0, 0, 0, - 48, 0, 0, 0, 36, 0, - 0, 0, 48, 0, 0, 0, - 0, 0, 1, 0, 0, 0, - 0, 0, 0, 0, 0, 2, - 255, 255, 31, 0, 0, 2, - 0, 0, 0, 128, 1, 0, - 15, 176, 5, 0, 0, 3, - 0, 0, 7, 128, 1, 0, - 228, 176, 0, 0, 255, 160, - 1, 0, 0, 2, 0, 0, - 8, 128, 1, 0, 255, 176, - 1, 0, 0, 2, 0, 8, - 15, 128, 0, 0, 228, 128, - 255, 255, 0, 0, 83, 72, - 68, 82, 132, 0, 0, 0, - 64, 0, 0, 0, 33, 0, - 0, 0, 89, 0, 0, 4, - 70, 142, 32, 0, 0, 0, - 0, 0, 1, 0, 0, 0, - 98, 16, 0, 3, 242, 16, - 16, 0, 2, 0, 0, 0, - 101, 0, 0, 3, 242, 32, - 16, 0, 0, 0, 0, 0, - 104, 0, 0, 2, 1, 0, - 0, 0, 54, 0, 0, 6, - 18, 0, 16, 0, 0, 0, - 0, 0, 58, 128, 32, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 54, 0, 0, 5, - 130, 0, 16, 0, 0, 0, - 0, 0, 1, 64, 0, 0, - 0, 0, 128, 63, 56, 0, - 0, 7, 242, 32, 16, 0, - 0, 0, 0, 0, 6, 12, - 16, 0, 0, 0, 0, 0, - 70, 30, 16, 0, 2, 0, - 0, 0, 62, 0, 0, 1, - 83, 84, 65, 84, 116, 0, - 0, 0, 4, 0, 0, 0, - 1, 0, 0, 0, 0, 0, - 0, 0, 2, 0, 0, 0, - 1, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 1, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 2, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 82, 68, - 69, 70, 92, 2, 0, 0, - 1, 0, 0, 0, 72, 0, - 0, 0, 1, 0, 0, 0, - 28, 0, 0, 0, 0, 4, - 255, 255, 0, 1, 0, 0, - 49, 2, 0, 0, 60, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 1, 0, - 0, 0, 1, 0, 0, 0, - 67, 111, 110, 115, 116, 97, - 110, 116, 115, 0, 171, 171, - 60, 0, 0, 0, 12, 0, - 0, 0, 96, 0, 0, 0, - 96, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 128, 1, 0, 0, 0, 0, - 0, 0, 4, 0, 0, 0, - 0, 0, 0, 0, 144, 1, - 0, 0, 0, 0, 0, 0, - 160, 1, 0, 0, 4, 0, - 0, 0, 4, 0, 0, 0, - 0, 0, 0, 0, 144, 1, - 0, 0, 0, 0, 0, 0, - 173, 1, 0, 0, 8, 0, - 0, 0, 4, 0, 0, 0, - 0, 0, 0, 0, 144, 1, - 0, 0, 0, 0, 0, 0, - 184, 1, 0, 0, 12, 0, - 0, 0, 4, 0, 0, 0, - 2, 0, 0, 0, 144, 1, - 0, 0, 0, 0, 0, 0, - 196, 1, 0, 0, 16, 0, - 0, 0, 4, 0, 0, 0, - 0, 0, 0, 0, 144, 1, - 0, 0, 0, 0, 0, 0, - 211, 1, 0, 0, 20, 0, - 0, 0, 4, 0, 0, 0, - 0, 0, 0, 0, 144, 1, - 0, 0, 0, 0, 0, 0, - 227, 1, 0, 0, 24, 0, - 0, 0, 4, 0, 0, 0, - 0, 0, 0, 0, 144, 1, - 0, 0, 0, 0, 0, 0, - 243, 1, 0, 0, 28, 0, - 0, 0, 4, 0, 0, 0, - 0, 0, 0, 0, 144, 1, - 0, 0, 0, 0, 0, 0, - 3, 2, 0, 0, 32, 0, - 0, 0, 16, 0, 0, 0, - 0, 0, 0, 0, 12, 2, - 0, 0, 0, 0, 0, 0, - 28, 2, 0, 0, 48, 0, - 0, 0, 16, 0, 0, 0, - 0, 0, 0, 0, 12, 2, - 0, 0, 0, 0, 0, 0, - 35, 2, 0, 0, 64, 0, - 0, 0, 16, 0, 0, 0, - 0, 0, 0, 0, 12, 2, - 0, 0, 0, 0, 0, 0, - 42, 2, 0, 0, 80, 0, - 0, 0, 16, 0, 0, 0, - 0, 0, 0, 0, 12, 2, - 0, 0, 0, 0, 0, 0, - 115, 99, 82, 71, 66, 95, - 111, 117, 116, 112, 117, 116, - 0, 171, 171, 171, 0, 0, - 3, 0, 1, 0, 1, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 116, 101, 120, 116, - 117, 114, 101, 95, 116, 121, - 112, 101, 0, 105, 110, 112, - 117, 116, 95, 116, 121, 112, - 101, 0, 99, 111, 108, 111, - 114, 95, 115, 99, 97, 108, - 101, 0, 116, 111, 110, 101, - 109, 97, 112, 95, 109, 101, - 116, 104, 111, 100, 0, 116, - 111, 110, 101, 109, 97, 112, - 95, 102, 97, 99, 116, 111, - 114, 49, 0, 116, 111, 110, - 101, 109, 97, 112, 95, 102, - 97, 99, 116, 111, 114, 50, - 0, 115, 100, 114, 95, 119, - 104, 105, 116, 101, 95, 112, - 111, 105, 110, 116, 0, 89, - 111, 102, 102, 115, 101, 116, - 0, 171, 1, 0, 3, 0, - 1, 0, 4, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 82, 99, 111, 101, 102, 102, - 0, 71, 99, 111, 101, 102, - 102, 0, 66, 99, 111, 101, - 102, 102, 0, 77, 105, 99, - 114, 111, 115, 111, 102, 116, - 32, 40, 82, 41, 32, 72, - 76, 83, 76, 32, 83, 104, - 97, 100, 101, 114, 32, 67, - 111, 109, 112, 105, 108, 101, - 114, 32, 49, 48, 46, 49, - 0, 171, 171, 171, 73, 83, - 71, 78, 108, 0, 0, 0, - 3, 0, 0, 0, 8, 0, - 0, 0, 80, 0, 0, 0, - 0, 0, 0, 0, 1, 0, - 0, 0, 3, 0, 0, 0, - 0, 0, 0, 0, 15, 0, - 0, 0, 92, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 3, 0, 0, 0, - 1, 0, 0, 0, 3, 0, - 0, 0, 101, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 3, 0, 0, 0, - 2, 0, 0, 0, 15, 15, - 0, 0, 83, 86, 95, 80, - 79, 83, 73, 84, 73, 79, - 78, 0, 84, 69, 88, 67, - 79, 79, 82, 68, 0, 67, - 79, 76, 79, 82, 0, 171, - 79, 83, 71, 78, 44, 0, - 0, 0, 1, 0, 0, 0, - 8, 0, 0, 0, 32, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 3, 0, - 0, 0, 0, 0, 0, 0, - 15, 0, 0, 0, 83, 86, - 95, 84, 65, 82, 71, 69, - 84, 0, 171, 171 + 68, 88, 66, 67, 131, 2, + 46, 215, 253, 13, 129, 98, + 132, 106, 250, 166, 217, 206, + 9, 154, 1, 0, 0, 0, + 224, 4, 0, 0, 6, 0, + 0, 0, 56, 0, 0, 0, + 172, 0, 0, 0, 56, 1, + 0, 0, 180, 1, 0, 0, + 56, 4, 0, 0, 172, 4, + 0, 0, 65, 111, 110, 57, + 108, 0, 0, 0, 108, 0, + 0, 0, 0, 2, 255, 255, + 60, 0, 0, 0, 48, 0, + 0, 0, 1, 0, 36, 0, + 0, 0, 48, 0, 0, 0, + 48, 0, 0, 0, 36, 0, + 0, 0, 48, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 0, 2, + 255, 255, 31, 0, 0, 2, + 0, 0, 0, 128, 1, 0, + 15, 176, 5, 0, 0, 3, + 0, 0, 7, 128, 1, 0, + 228, 176, 0, 0, 255, 160, + 1, 0, 0, 2, 0, 0, + 8, 128, 1, 0, 255, 176, + 1, 0, 0, 2, 0, 8, + 15, 128, 0, 0, 228, 128, + 255, 255, 0, 0, 83, 72, + 68, 82, 132, 0, 0, 0, + 64, 0, 0, 0, 33, 0, + 0, 0, 89, 0, 0, 4, + 70, 142, 32, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 98, 16, 0, 3, 242, 16, + 16, 0, 2, 0, 0, 0, + 101, 0, 0, 3, 242, 32, + 16, 0, 0, 0, 0, 0, + 104, 0, 0, 2, 1, 0, + 0, 0, 54, 0, 0, 6, + 18, 0, 16, 0, 0, 0, + 0, 0, 58, 128, 32, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 54, 0, 0, 5, + 130, 0, 16, 0, 0, 0, + 0, 0, 1, 64, 0, 0, + 0, 0, 128, 63, 56, 0, + 0, 7, 242, 32, 16, 0, + 0, 0, 0, 0, 6, 12, + 16, 0, 0, 0, 0, 0, + 70, 30, 16, 0, 2, 0, + 0, 0, 62, 0, 0, 1, + 83, 84, 65, 84, 116, 0, + 0, 0, 4, 0, 0, 0, + 1, 0, 0, 0, 0, 0, + 0, 0, 2, 0, 0, 0, + 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 2, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 82, 68, + 69, 70, 124, 2, 0, 0, + 1, 0, 0, 0, 72, 0, + 0, 0, 1, 0, 0, 0, + 28, 0, 0, 0, 0, 4, + 255, 255, 0, 1, 0, 0, + 84, 2, 0, 0, 60, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 1, 0, + 0, 0, 1, 0, 0, 0, + 67, 111, 110, 115, 116, 97, + 110, 116, 115, 0, 171, 171, + 60, 0, 0, 0, 13, 0, + 0, 0, 96, 0, 0, 0, + 112, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 152, 1, 0, 0, 0, 0, + 0, 0, 4, 0, 0, 0, + 0, 0, 0, 0, 168, 1, + 0, 0, 0, 0, 0, 0, + 184, 1, 0, 0, 4, 0, + 0, 0, 4, 0, 0, 0, + 0, 0, 0, 0, 168, 1, + 0, 0, 0, 0, 0, 0, + 197, 1, 0, 0, 8, 0, + 0, 0, 4, 0, 0, 0, + 0, 0, 0, 0, 168, 1, + 0, 0, 0, 0, 0, 0, + 208, 1, 0, 0, 12, 0, + 0, 0, 4, 0, 0, 0, + 2, 0, 0, 0, 168, 1, + 0, 0, 0, 0, 0, 0, + 220, 1, 0, 0, 16, 0, + 0, 0, 16, 0, 0, 0, + 0, 0, 0, 0, 232, 1, + 0, 0, 0, 0, 0, 0, + 248, 1, 0, 0, 32, 0, + 0, 0, 4, 0, 0, 0, + 0, 0, 0, 0, 168, 1, + 0, 0, 0, 0, 0, 0, + 7, 2, 0, 0, 36, 0, + 0, 0, 4, 0, 0, 0, + 0, 0, 0, 0, 168, 1, + 0, 0, 0, 0, 0, 0, + 23, 2, 0, 0, 40, 0, + 0, 0, 4, 0, 0, 0, + 0, 0, 0, 0, 168, 1, + 0, 0, 0, 0, 0, 0, + 39, 2, 0, 0, 44, 0, + 0, 0, 4, 0, 0, 0, + 0, 0, 0, 0, 168, 1, + 0, 0, 0, 0, 0, 0, + 55, 2, 0, 0, 48, 0, + 0, 0, 16, 0, 0, 0, + 0, 0, 0, 0, 232, 1, + 0, 0, 0, 0, 0, 0, + 63, 2, 0, 0, 64, 0, + 0, 0, 16, 0, 0, 0, + 0, 0, 0, 0, 232, 1, + 0, 0, 0, 0, 0, 0, + 70, 2, 0, 0, 80, 0, + 0, 0, 16, 0, 0, 0, + 0, 0, 0, 0, 232, 1, + 0, 0, 0, 0, 0, 0, + 77, 2, 0, 0, 96, 0, + 0, 0, 16, 0, 0, 0, + 0, 0, 0, 0, 232, 1, + 0, 0, 0, 0, 0, 0, + 115, 99, 82, 71, 66, 95, + 111, 117, 116, 112, 117, 116, + 0, 171, 171, 171, 0, 0, + 3, 0, 1, 0, 1, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 116, 101, 120, 116, + 117, 114, 101, 95, 116, 121, + 112, 101, 0, 105, 110, 112, + 117, 116, 95, 116, 121, 112, + 101, 0, 99, 111, 108, 111, + 114, 95, 115, 99, 97, 108, + 101, 0, 116, 101, 120, 101, + 108, 95, 115, 105, 122, 101, + 0, 171, 1, 0, 3, 0, + 1, 0, 4, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 116, 111, 110, 101, 109, 97, + 112, 95, 109, 101, 116, 104, + 111, 100, 0, 116, 111, 110, + 101, 109, 97, 112, 95, 102, + 97, 99, 116, 111, 114, 49, + 0, 116, 111, 110, 101, 109, + 97, 112, 95, 102, 97, 99, + 116, 111, 114, 50, 0, 115, + 100, 114, 95, 119, 104, 105, + 116, 101, 95, 112, 111, 105, + 110, 116, 0, 89, 111, 102, + 102, 115, 101, 116, 0, 82, + 99, 111, 101, 102, 102, 0, + 71, 99, 111, 101, 102, 102, + 0, 66, 99, 111, 101, 102, + 102, 0, 77, 105, 99, 114, + 111, 115, 111, 102, 116, 32, + 40, 82, 41, 32, 72, 76, + 83, 76, 32, 83, 104, 97, + 100, 101, 114, 32, 67, 111, + 109, 112, 105, 108, 101, 114, + 32, 49, 48, 46, 49, 0, + 73, 83, 71, 78, 108, 0, + 0, 0, 3, 0, 0, 0, + 8, 0, 0, 0, 80, 0, + 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 3, 0, + 0, 0, 0, 0, 0, 0, + 15, 0, 0, 0, 92, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 3, 0, + 0, 0, 1, 0, 0, 0, + 3, 0, 0, 0, 101, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 3, 0, + 0, 0, 2, 0, 0, 0, + 15, 15, 0, 0, 83, 86, + 95, 80, 79, 83, 73, 84, + 73, 79, 78, 0, 84, 69, + 88, 67, 79, 79, 82, 68, + 0, 67, 79, 76, 79, 82, + 0, 171, 79, 83, 71, 78, + 44, 0, 0, 0, 1, 0, + 0, 0, 8, 0, 0, 0, + 32, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 3, 0, 0, 0, 0, 0, + 0, 0, 15, 0, 0, 0, + 83, 86, 95, 84, 65, 82, + 71, 69, 84, 0, 171, 171 }; diff --git a/libs/SDL3/src/render/direct3d11/D3D11_PixelShader_Common.hlsli b/libs/SDL3/src/render/direct3d11/D3D11_PixelShader_Common.hlsli index b940c0c6..b47a5e12 100644 --- a/libs/SDL3/src/render/direct3d11/D3D11_PixelShader_Common.hlsli +++ b/libs/SDL3/src/render/direct3d11/D3D11_PixelShader_Common.hlsli @@ -18,9 +18,10 @@ static const float TONEMAP_CHROME = 2; static const float TEXTURETYPE_NONE = 0; static const float TEXTURETYPE_RGB = 1; -static const float TEXTURETYPE_NV12 = 2; -static const float TEXTURETYPE_NV21 = 3; -static const float TEXTURETYPE_YUV = 4; +static const float TEXTURETYPE_RGB_PIXELART = 2; +static const float TEXTURETYPE_NV12 = 3; +static const float TEXTURETYPE_NV21 = 4; +static const float TEXTURETYPE_YUV = 5; static const float INPUTTYPE_UNSPECIFIED = 0; static const float INPUTTYPE_SRGB = 1; @@ -33,6 +34,7 @@ cbuffer Constants : register(b0) float texture_type; float input_type; float color_scale; + float4 texel_size; float tonemap_method; float tonemap_factor1; @@ -122,6 +124,21 @@ float4 GetInputColor(PixelShaderInput input) rgba = 1.0; } else if (texture_type == TEXTURETYPE_RGB) { rgba = texture0.Sample(sampler0, input.tex); + } else if (texture_type == TEXTURETYPE_RGB_PIXELART) { + // box filter size in texel units + float2 boxSize = clamp(fwidth(input.tex) * texel_size.zw, 1e-5, 1); + + // scale uv by texture size to get texel coordinate + float2 tx = input.tex * texel_size.zw - 0.5 * boxSize; + + // compute offset for pixel-sized box filter + float2 txOffset = smoothstep(1 - boxSize, 1, frac(tx)); + + // compute bilinear sample uv coordinates + float2 uv = (floor(tx) + 0.5 + txOffset) * texel_size.xy; + + // sample the texture + rgba = texture0.SampleGrad(sampler0, uv, ddx(input.tex), ddy(input.tex)); } else if (texture_type == TEXTURETYPE_NV12) { float3 yuv; yuv.x = texture0.Sample(sampler0, input.tex).r; diff --git a/libs/SDL3/src/render/direct3d11/D3D11_PixelShader_Textures.h b/libs/SDL3/src/render/direct3d11/D3D11_PixelShader_Textures.h index 996cac62..4b06c09c 100644 --- a/libs/SDL3/src/render/direct3d11/D3D11_PixelShader_Textures.h +++ b/libs/SDL3/src/render/direct3d11/D3D11_PixelShader_Textures.h @@ -3,7 +3,7 @@ // Generated by Microsoft (R) HLSL Shader Compiler 10.1 // // -// Buffer Definitions: +// Buffer Definitions: // // cbuffer Constants // { @@ -12,14 +12,15 @@ // float texture_type; // Offset: 4 Size: 4 [unused] // float input_type; // Offset: 8 Size: 4 [unused] // float color_scale; // Offset: 12 Size: 4 -// float tonemap_method; // Offset: 16 Size: 4 [unused] -// float tonemap_factor1; // Offset: 20 Size: 4 [unused] -// float tonemap_factor2; // Offset: 24 Size: 4 [unused] -// float sdr_white_point; // Offset: 28 Size: 4 [unused] -// float4 Yoffset; // Offset: 32 Size: 16 [unused] -// float4 Rcoeff; // Offset: 48 Size: 16 [unused] -// float4 Gcoeff; // Offset: 64 Size: 16 [unused] -// float4 Bcoeff; // Offset: 80 Size: 16 [unused] +// float4 texel_size; // Offset: 16 Size: 16 [unused] +// float tonemap_method; // Offset: 32 Size: 4 [unused] +// float tonemap_factor1; // Offset: 36 Size: 4 [unused] +// float tonemap_factor2; // Offset: 40 Size: 4 [unused] +// float sdr_white_point; // Offset: 44 Size: 4 [unused] +// float4 Yoffset; // Offset: 48 Size: 16 [unused] +// float4 Rcoeff; // Offset: 64 Size: 16 [unused] +// float4 Gcoeff; // Offset: 80 Size: 16 [unused] +// float4 Bcoeff; // Offset: 96 Size: 16 [unused] // // } // @@ -28,9 +29,9 @@ // // Name Type Format Dim HLSL Bind Count // ------------------------------ ---------- ------- ----------- -------------- ------ -// theSampler sampler NA NA s0 1 -// theTexture texture float4 2d t0 1 -// Constants cbuffer NA NA cb0 1 +// theSampler sampler NA NA s0 1 +// theTexture texture float4 2d t0 1 +// Constants cbuffer NA NA cb0 1 // // // @@ -38,8 +39,8 @@ // // Name Index Mask Register SysValue Format Used // -------------------- ----- ------ -------- -------- ------- ------ -// SV_POSITION 0 xyzw 0 POS float -// TEXCOORD 0 xy 1 NONE float xy +// SV_POSITION 0 xyzw 0 POS float +// TEXCOORD 0 xy 1 NONE float xy // COLOR 0 xyzw 2 NONE float xyzw // // @@ -61,7 +62,7 @@ // // Target Sampler Source Sampler Source Resource // -------------- --------------- ---------------- -// s0 s0 t0 +// s0 s0 t0 // // // Level9 shader bytecode: @@ -87,246 +88,251 @@ dcl_temps 1 sample r0.xyzw, v1.xyxx, t0.xyzw, s0 mul r0.xyz, r0.xyzx, cb0[0].wwww mul o0.xyzw, r0.xyzw, v2.xyzw -ret +ret // Approximately 4 instruction slots used #endif const BYTE g_main[] = { - 68, 88, 66, 67, 8, 152, - 224, 210, 182, 254, 37, 89, - 68, 213, 13, 174, 95, 42, - 2, 11, 1, 0, 0, 0, - 132, 5, 0, 0, 6, 0, - 0, 0, 56, 0, 0, 0, - 220, 0, 0, 0, 168, 1, - 0, 0, 36, 2, 0, 0, - 220, 4, 0, 0, 80, 5, - 0, 0, 65, 111, 110, 57, - 156, 0, 0, 0, 156, 0, - 0, 0, 0, 2, 255, 255, - 104, 0, 0, 0, 52, 0, - 0, 0, 1, 0, 40, 0, - 0, 0, 52, 0, 0, 0, - 52, 0, 1, 0, 36, 0, - 0, 0, 52, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 1, 0, 0, 0, 0, 0, - 0, 0, 0, 2, 255, 255, - 31, 0, 0, 2, 0, 0, - 0, 128, 0, 0, 3, 176, - 31, 0, 0, 2, 0, 0, - 0, 128, 1, 0, 15, 176, - 31, 0, 0, 2, 0, 0, - 0, 144, 0, 8, 15, 160, - 66, 0, 0, 3, 0, 0, - 15, 128, 0, 0, 228, 176, - 0, 8, 228, 160, 5, 0, - 0, 3, 0, 0, 7, 128, - 0, 0, 228, 128, 0, 0, - 255, 160, 5, 0, 0, 3, - 0, 0, 15, 128, 0, 0, - 228, 128, 1, 0, 228, 176, - 1, 0, 0, 2, 0, 8, - 15, 128, 0, 0, 228, 128, - 255, 255, 0, 0, 83, 72, - 68, 82, 196, 0, 0, 0, - 64, 0, 0, 0, 49, 0, - 0, 0, 89, 0, 0, 4, - 70, 142, 32, 0, 0, 0, - 0, 0, 1, 0, 0, 0, - 90, 0, 0, 3, 0, 96, - 16, 0, 0, 0, 0, 0, - 88, 24, 0, 4, 0, 112, - 16, 0, 0, 0, 0, 0, - 85, 85, 0, 0, 98, 16, - 0, 3, 50, 16, 16, 0, - 1, 0, 0, 0, 98, 16, - 0, 3, 242, 16, 16, 0, - 2, 0, 0, 0, 101, 0, - 0, 3, 242, 32, 16, 0, - 0, 0, 0, 0, 104, 0, - 0, 2, 1, 0, 0, 0, - 69, 0, 0, 9, 242, 0, - 16, 0, 0, 0, 0, 0, - 70, 16, 16, 0, 1, 0, - 0, 0, 70, 126, 16, 0, - 0, 0, 0, 0, 0, 96, - 16, 0, 0, 0, 0, 0, - 56, 0, 0, 8, 114, 0, - 16, 0, 0, 0, 0, 0, - 70, 2, 16, 0, 0, 0, - 0, 0, 246, 143, 32, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 56, 0, 0, 7, - 242, 32, 16, 0, 0, 0, - 0, 0, 70, 14, 16, 0, - 0, 0, 0, 0, 70, 30, - 16, 0, 2, 0, 0, 0, - 62, 0, 0, 1, 83, 84, - 65, 84, 116, 0, 0, 0, - 4, 0, 0, 0, 1, 0, - 0, 0, 0, 0, 0, 0, - 3, 0, 0, 0, 2, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 1, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 1, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 82, 68, 69, 70, - 176, 2, 0, 0, 1, 0, - 0, 0, 156, 0, 0, 0, - 3, 0, 0, 0, 28, 0, - 0, 0, 0, 4, 255, 255, - 0, 1, 0, 0, 133, 2, - 0, 0, 124, 0, 0, 0, - 3, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 1, 0, 0, 0, - 1, 0, 0, 0, 135, 0, - 0, 0, 2, 0, 0, 0, - 5, 0, 0, 0, 4, 0, - 0, 0, 255, 255, 255, 255, - 0, 0, 0, 0, 1, 0, - 0, 0, 13, 0, 0, 0, - 146, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 1, 0, 0, 0, 1, 0, - 0, 0, 116, 104, 101, 83, - 97, 109, 112, 108, 101, 114, - 0, 116, 104, 101, 84, 101, - 120, 116, 117, 114, 101, 0, - 67, 111, 110, 115, 116, 97, - 110, 116, 115, 0, 146, 0, - 0, 0, 12, 0, 0, 0, - 180, 0, 0, 0, 96, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 212, 1, - 0, 0, 0, 0, 0, 0, - 4, 0, 0, 0, 0, 0, - 0, 0, 228, 1, 0, 0, - 0, 0, 0, 0, 244, 1, - 0, 0, 4, 0, 0, 0, - 4, 0, 0, 0, 0, 0, - 0, 0, 228, 1, 0, 0, - 0, 0, 0, 0, 1, 2, - 0, 0, 8, 0, 0, 0, - 4, 0, 0, 0, 0, 0, - 0, 0, 228, 1, 0, 0, - 0, 0, 0, 0, 12, 2, - 0, 0, 12, 0, 0, 0, - 4, 0, 0, 0, 2, 0, - 0, 0, 228, 1, 0, 0, - 0, 0, 0, 0, 24, 2, - 0, 0, 16, 0, 0, 0, - 4, 0, 0, 0, 0, 0, - 0, 0, 228, 1, 0, 0, - 0, 0, 0, 0, 39, 2, - 0, 0, 20, 0, 0, 0, - 4, 0, 0, 0, 0, 0, - 0, 0, 228, 1, 0, 0, - 0, 0, 0, 0, 55, 2, - 0, 0, 24, 0, 0, 0, - 4, 0, 0, 0, 0, 0, - 0, 0, 228, 1, 0, 0, - 0, 0, 0, 0, 71, 2, - 0, 0, 28, 0, 0, 0, - 4, 0, 0, 0, 0, 0, - 0, 0, 228, 1, 0, 0, - 0, 0, 0, 0, 87, 2, - 0, 0, 32, 0, 0, 0, - 16, 0, 0, 0, 0, 0, - 0, 0, 96, 2, 0, 0, - 0, 0, 0, 0, 112, 2, - 0, 0, 48, 0, 0, 0, - 16, 0, 0, 0, 0, 0, - 0, 0, 96, 2, 0, 0, - 0, 0, 0, 0, 119, 2, - 0, 0, 64, 0, 0, 0, - 16, 0, 0, 0, 0, 0, - 0, 0, 96, 2, 0, 0, - 0, 0, 0, 0, 126, 2, - 0, 0, 80, 0, 0, 0, - 16, 0, 0, 0, 0, 0, - 0, 0, 96, 2, 0, 0, - 0, 0, 0, 0, 115, 99, - 82, 71, 66, 95, 111, 117, - 116, 112, 117, 116, 0, 171, - 171, 171, 0, 0, 3, 0, - 1, 0, 1, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 116, 101, 120, 116, 117, 114, - 101, 95, 116, 121, 112, 101, - 0, 105, 110, 112, 117, 116, - 95, 116, 121, 112, 101, 0, - 99, 111, 108, 111, 114, 95, - 115, 99, 97, 108, 101, 0, - 116, 111, 110, 101, 109, 97, - 112, 95, 109, 101, 116, 104, - 111, 100, 0, 116, 111, 110, - 101, 109, 97, 112, 95, 102, - 97, 99, 116, 111, 114, 49, - 0, 116, 111, 110, 101, 109, - 97, 112, 95, 102, 97, 99, - 116, 111, 114, 50, 0, 115, - 100, 114, 95, 119, 104, 105, - 116, 101, 95, 112, 111, 105, - 110, 116, 0, 89, 111, 102, - 102, 115, 101, 116, 0, 171, - 1, 0, 3, 0, 1, 0, - 4, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 82, 99, - 111, 101, 102, 102, 0, 71, - 99, 111, 101, 102, 102, 0, - 66, 99, 111, 101, 102, 102, - 0, 77, 105, 99, 114, 111, - 115, 111, 102, 116, 32, 40, - 82, 41, 32, 72, 76, 83, - 76, 32, 83, 104, 97, 100, - 101, 114, 32, 67, 111, 109, - 112, 105, 108, 101, 114, 32, - 49, 48, 46, 49, 0, 171, - 171, 171, 73, 83, 71, 78, - 108, 0, 0, 0, 3, 0, - 0, 0, 8, 0, 0, 0, - 80, 0, 0, 0, 0, 0, - 0, 0, 1, 0, 0, 0, - 3, 0, 0, 0, 0, 0, - 0, 0, 15, 0, 0, 0, - 92, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 3, 0, 0, 0, 1, 0, - 0, 0, 3, 3, 0, 0, - 101, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 3, 0, 0, 0, 2, 0, - 0, 0, 15, 15, 0, 0, - 83, 86, 95, 80, 79, 83, - 73, 84, 73, 79, 78, 0, - 84, 69, 88, 67, 79, 79, - 82, 68, 0, 67, 79, 76, - 79, 82, 0, 171, 79, 83, - 71, 78, 44, 0, 0, 0, - 1, 0, 0, 0, 8, 0, - 0, 0, 32, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 3, 0, 0, 0, - 0, 0, 0, 0, 15, 0, - 0, 0, 83, 86, 95, 84, - 65, 82, 71, 69, 84, 0, - 171, 171 + 68, 88, 66, 67, 59, 235, + 166, 93, 21, 32, 225, 129, + 78, 220, 127, 97, 218, 34, + 222, 113, 1, 0, 0, 0, + 164, 5, 0, 0, 6, 0, + 0, 0, 56, 0, 0, 0, + 220, 0, 0, 0, 168, 1, + 0, 0, 36, 2, 0, 0, + 252, 4, 0, 0, 112, 5, + 0, 0, 65, 111, 110, 57, + 156, 0, 0, 0, 156, 0, + 0, 0, 0, 2, 255, 255, + 104, 0, 0, 0, 52, 0, + 0, 0, 1, 0, 40, 0, + 0, 0, 52, 0, 0, 0, + 52, 0, 1, 0, 36, 0, + 0, 0, 52, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, + 0, 0, 0, 2, 255, 255, + 31, 0, 0, 2, 0, 0, + 0, 128, 0, 0, 3, 176, + 31, 0, 0, 2, 0, 0, + 0, 128, 1, 0, 15, 176, + 31, 0, 0, 2, 0, 0, + 0, 144, 0, 8, 15, 160, + 66, 0, 0, 3, 0, 0, + 15, 128, 0, 0, 228, 176, + 0, 8, 228, 160, 5, 0, + 0, 3, 0, 0, 7, 128, + 0, 0, 228, 128, 0, 0, + 255, 160, 5, 0, 0, 3, + 0, 0, 15, 128, 0, 0, + 228, 128, 1, 0, 228, 176, + 1, 0, 0, 2, 0, 8, + 15, 128, 0, 0, 228, 128, + 255, 255, 0, 0, 83, 72, + 68, 82, 196, 0, 0, 0, + 64, 0, 0, 0, 49, 0, + 0, 0, 89, 0, 0, 4, + 70, 142, 32, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 90, 0, 0, 3, 0, 96, + 16, 0, 0, 0, 0, 0, + 88, 24, 0, 4, 0, 112, + 16, 0, 0, 0, 0, 0, + 85, 85, 0, 0, 98, 16, + 0, 3, 50, 16, 16, 0, + 1, 0, 0, 0, 98, 16, + 0, 3, 242, 16, 16, 0, + 2, 0, 0, 0, 101, 0, + 0, 3, 242, 32, 16, 0, + 0, 0, 0, 0, 104, 0, + 0, 2, 1, 0, 0, 0, + 69, 0, 0, 9, 242, 0, + 16, 0, 0, 0, 0, 0, + 70, 16, 16, 0, 1, 0, + 0, 0, 70, 126, 16, 0, + 0, 0, 0, 0, 0, 96, + 16, 0, 0, 0, 0, 0, + 56, 0, 0, 8, 114, 0, + 16, 0, 0, 0, 0, 0, + 70, 2, 16, 0, 0, 0, + 0, 0, 246, 143, 32, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 56, 0, 0, 7, + 242, 32, 16, 0, 0, 0, + 0, 0, 70, 14, 16, 0, + 0, 0, 0, 0, 70, 30, + 16, 0, 2, 0, 0, 0, + 62, 0, 0, 1, 83, 84, + 65, 84, 116, 0, 0, 0, + 4, 0, 0, 0, 1, 0, + 0, 0, 0, 0, 0, 0, + 3, 0, 0, 0, 2, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 1, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 82, 68, 69, 70, + 208, 2, 0, 0, 1, 0, + 0, 0, 156, 0, 0, 0, + 3, 0, 0, 0, 28, 0, + 0, 0, 0, 4, 255, 255, + 0, 1, 0, 0, 168, 2, + 0, 0, 124, 0, 0, 0, + 3, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 135, 0, + 0, 0, 2, 0, 0, 0, + 5, 0, 0, 0, 4, 0, + 0, 0, 255, 255, 255, 255, + 0, 0, 0, 0, 1, 0, + 0, 0, 13, 0, 0, 0, + 146, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 1, 0, + 0, 0, 116, 104, 101, 83, + 97, 109, 112, 108, 101, 114, + 0, 116, 104, 101, 84, 101, + 120, 116, 117, 114, 101, 0, + 67, 111, 110, 115, 116, 97, + 110, 116, 115, 0, 146, 0, + 0, 0, 13, 0, 0, 0, + 180, 0, 0, 0, 112, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 236, 1, + 0, 0, 0, 0, 0, 0, + 4, 0, 0, 0, 0, 0, + 0, 0, 252, 1, 0, 0, + 0, 0, 0, 0, 12, 2, + 0, 0, 4, 0, 0, 0, + 4, 0, 0, 0, 0, 0, + 0, 0, 252, 1, 0, 0, + 0, 0, 0, 0, 25, 2, + 0, 0, 8, 0, 0, 0, + 4, 0, 0, 0, 0, 0, + 0, 0, 252, 1, 0, 0, + 0, 0, 0, 0, 36, 2, + 0, 0, 12, 0, 0, 0, + 4, 0, 0, 0, 2, 0, + 0, 0, 252, 1, 0, 0, + 0, 0, 0, 0, 48, 2, + 0, 0, 16, 0, 0, 0, + 16, 0, 0, 0, 0, 0, + 0, 0, 60, 2, 0, 0, + 0, 0, 0, 0, 76, 2, + 0, 0, 32, 0, 0, 0, + 4, 0, 0, 0, 0, 0, + 0, 0, 252, 1, 0, 0, + 0, 0, 0, 0, 91, 2, + 0, 0, 36, 0, 0, 0, + 4, 0, 0, 0, 0, 0, + 0, 0, 252, 1, 0, 0, + 0, 0, 0, 0, 107, 2, + 0, 0, 40, 0, 0, 0, + 4, 0, 0, 0, 0, 0, + 0, 0, 252, 1, 0, 0, + 0, 0, 0, 0, 123, 2, + 0, 0, 44, 0, 0, 0, + 4, 0, 0, 0, 0, 0, + 0, 0, 252, 1, 0, 0, + 0, 0, 0, 0, 139, 2, + 0, 0, 48, 0, 0, 0, + 16, 0, 0, 0, 0, 0, + 0, 0, 60, 2, 0, 0, + 0, 0, 0, 0, 147, 2, + 0, 0, 64, 0, 0, 0, + 16, 0, 0, 0, 0, 0, + 0, 0, 60, 2, 0, 0, + 0, 0, 0, 0, 154, 2, + 0, 0, 80, 0, 0, 0, + 16, 0, 0, 0, 0, 0, + 0, 0, 60, 2, 0, 0, + 0, 0, 0, 0, 161, 2, + 0, 0, 96, 0, 0, 0, + 16, 0, 0, 0, 0, 0, + 0, 0, 60, 2, 0, 0, + 0, 0, 0, 0, 115, 99, + 82, 71, 66, 95, 111, 117, + 116, 112, 117, 116, 0, 171, + 171, 171, 0, 0, 3, 0, + 1, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 116, 101, 120, 116, 117, 114, + 101, 95, 116, 121, 112, 101, + 0, 105, 110, 112, 117, 116, + 95, 116, 121, 112, 101, 0, + 99, 111, 108, 111, 114, 95, + 115, 99, 97, 108, 101, 0, + 116, 101, 120, 101, 108, 95, + 115, 105, 122, 101, 0, 171, + 1, 0, 3, 0, 1, 0, + 4, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 116, 111, + 110, 101, 109, 97, 112, 95, + 109, 101, 116, 104, 111, 100, + 0, 116, 111, 110, 101, 109, + 97, 112, 95, 102, 97, 99, + 116, 111, 114, 49, 0, 116, + 111, 110, 101, 109, 97, 112, + 95, 102, 97, 99, 116, 111, + 114, 50, 0, 115, 100, 114, + 95, 119, 104, 105, 116, 101, + 95, 112, 111, 105, 110, 116, + 0, 89, 111, 102, 102, 115, + 101, 116, 0, 82, 99, 111, + 101, 102, 102, 0, 71, 99, + 111, 101, 102, 102, 0, 66, + 99, 111, 101, 102, 102, 0, + 77, 105, 99, 114, 111, 115, + 111, 102, 116, 32, 40, 82, + 41, 32, 72, 76, 83, 76, + 32, 83, 104, 97, 100, 101, + 114, 32, 67, 111, 109, 112, + 105, 108, 101, 114, 32, 49, + 48, 46, 49, 0, 73, 83, + 71, 78, 108, 0, 0, 0, + 3, 0, 0, 0, 8, 0, + 0, 0, 80, 0, 0, 0, + 0, 0, 0, 0, 1, 0, + 0, 0, 3, 0, 0, 0, + 0, 0, 0, 0, 15, 0, + 0, 0, 92, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 3, 0, 0, 0, + 1, 0, 0, 0, 3, 3, + 0, 0, 101, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 3, 0, 0, 0, + 2, 0, 0, 0, 15, 15, + 0, 0, 83, 86, 95, 80, + 79, 83, 73, 84, 73, 79, + 78, 0, 84, 69, 88, 67, + 79, 79, 82, 68, 0, 67, + 79, 76, 79, 82, 0, 171, + 79, 83, 71, 78, 44, 0, + 0, 0, 1, 0, 0, 0, + 8, 0, 0, 0, 32, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 3, 0, + 0, 0, 0, 0, 0, 0, + 15, 0, 0, 0, 83, 86, + 95, 84, 65, 82, 71, 69, + 84, 0, 171, 171 }; diff --git a/libs/SDL3/src/render/direct3d11/D3D11_VertexShader.h b/libs/SDL3/src/render/direct3d11/D3D11_VertexShader.h index 97beaa39..e6aac573 100644 --- a/libs/SDL3/src/render/direct3d11/D3D11_VertexShader.h +++ b/libs/SDL3/src/render/direct3d11/D3D11_VertexShader.h @@ -3,7 +3,7 @@ // Generated by Microsoft (R) HLSL Shader Compiler 10.1 // // -// Buffer Definitions: +// Buffer Definitions: // // cbuffer VertexShaderConstants // { @@ -18,7 +18,7 @@ // // Name Type Format Dim HLSL Bind Count // ------------------------------ ---------- ------- ----------- -------------- ------ -// VertexShaderConstants cbuffer NA NA cb0 1 +// VertexShaderConstants cbuffer NA NA cb0 1 // // // @@ -26,8 +26,8 @@ // // Name Index Mask Register SysValue Format Used // -------------------- ----- ------ -------- -------- ------- ------ -// POSITION 0 xyz 0 NONE float xyz -// TEXCOORD 0 xy 1 NONE float xy +// POSITION 0 xyz 0 NONE float xyz +// TEXCOORD 0 xy 1 NONE float xy // COLOR 0 xyzw 2 NONE float xyzw // // @@ -36,7 +36,7 @@ // Name Index Mask Register SysValue Format Used // -------------------- ----- ------ -------- -------- ------- ------ // SV_POSITION 0 xyzw 0 POS float xyzw -// TEXCOORD 0 xy 1 NONE float xy +// TEXCOORD 0 xy 1 NONE float xy // COLOR 0 xyzw 2 NONE float xyzw // // @@ -93,247 +93,247 @@ mad r1.xyzw, r0.zzzz, cb0[6].xyzw, r1.xyzw mad o0.xyzw, r0.wwww, cb0[7].xyzw, r1.xyzw mov o1.xy, v1.xyxx mov o2.xyzw, v2.xyzw -ret +ret // Approximately 11 instruction slots used #endif const BYTE g_main[] = { - 68, 88, 66, 67, 152, 172, - 81, 45, 198, 200, 12, 38, - 143, 4, 178, 228, 158, 175, - 169, 64, 1, 0, 0, 0, - 140, 5, 0, 0, 6, 0, - 0, 0, 56, 0, 0, 0, - 108, 1, 0, 0, 52, 3, - 0, 0, 176, 3, 0, 0, - 168, 4, 0, 0, 24, 5, - 0, 0, 65, 111, 110, 57, - 44, 1, 0, 0, 44, 1, - 0, 0, 0, 2, 254, 255, - 248, 0, 0, 0, 52, 0, - 0, 0, 1, 0, 36, 0, - 0, 0, 48, 0, 0, 0, - 48, 0, 0, 0, 36, 0, - 1, 0, 48, 0, 0, 0, - 0, 0, 8, 0, 1, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 2, 254, 255, - 31, 0, 0, 2, 5, 0, - 0, 128, 0, 0, 15, 144, - 31, 0, 0, 2, 5, 0, - 1, 128, 1, 0, 15, 144, - 31, 0, 0, 2, 5, 0, - 2, 128, 2, 0, 15, 144, - 5, 0, 0, 3, 0, 0, - 15, 128, 0, 0, 85, 144, - 2, 0, 228, 160, 4, 0, - 0, 4, 0, 0, 15, 128, - 0, 0, 0, 144, 1, 0, - 228, 160, 0, 0, 228, 128, - 4, 0, 0, 4, 0, 0, - 15, 128, 0, 0, 170, 144, - 3, 0, 228, 160, 0, 0, - 228, 128, 2, 0, 0, 3, - 0, 0, 15, 128, 0, 0, - 228, 128, 4, 0, 228, 160, - 5, 0, 0, 3, 1, 0, - 15, 128, 0, 0, 85, 128, - 6, 0, 228, 160, 4, 0, - 0, 4, 1, 0, 15, 128, - 0, 0, 0, 128, 5, 0, - 228, 160, 1, 0, 228, 128, - 4, 0, 0, 4, 1, 0, - 15, 128, 0, 0, 170, 128, - 7, 0, 228, 160, 1, 0, - 228, 128, 4, 0, 0, 4, - 0, 0, 15, 128, 0, 0, - 255, 128, 8, 0, 228, 160, - 1, 0, 228, 128, 4, 0, - 0, 4, 0, 0, 3, 192, - 0, 0, 255, 128, 0, 0, - 228, 160, 0, 0, 228, 128, - 1, 0, 0, 2, 0, 0, - 12, 192, 0, 0, 228, 128, - 1, 0, 0, 2, 0, 0, - 3, 224, 1, 0, 228, 144, - 1, 0, 0, 2, 1, 0, - 15, 224, 2, 0, 228, 144, - 255, 255, 0, 0, 83, 72, - 68, 82, 192, 1, 0, 0, - 64, 0, 1, 0, 112, 0, - 0, 0, 89, 0, 0, 4, - 70, 142, 32, 0, 0, 0, - 0, 0, 8, 0, 0, 0, - 95, 0, 0, 3, 114, 16, - 16, 0, 0, 0, 0, 0, - 95, 0, 0, 3, 50, 16, - 16, 0, 1, 0, 0, 0, - 95, 0, 0, 3, 242, 16, - 16, 0, 2, 0, 0, 0, - 103, 0, 0, 4, 242, 32, - 16, 0, 0, 0, 0, 0, - 1, 0, 0, 0, 101, 0, - 0, 3, 50, 32, 16, 0, - 1, 0, 0, 0, 101, 0, - 0, 3, 242, 32, 16, 0, - 2, 0, 0, 0, 104, 0, - 0, 2, 2, 0, 0, 0, - 56, 0, 0, 8, 242, 0, - 16, 0, 0, 0, 0, 0, - 86, 21, 16, 0, 0, 0, - 0, 0, 70, 142, 32, 0, - 0, 0, 0, 0, 1, 0, - 0, 0, 50, 0, 0, 10, - 242, 0, 16, 0, 0, 0, - 0, 0, 6, 16, 16, 0, - 0, 0, 0, 0, 70, 142, - 32, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 70, 14, - 16, 0, 0, 0, 0, 0, - 50, 0, 0, 10, 242, 0, - 16, 0, 0, 0, 0, 0, - 166, 26, 16, 0, 0, 0, - 0, 0, 70, 142, 32, 0, - 0, 0, 0, 0, 2, 0, - 0, 0, 70, 14, 16, 0, - 0, 0, 0, 0, 0, 0, - 0, 8, 242, 0, 16, 0, - 0, 0, 0, 0, 70, 14, - 16, 0, 0, 0, 0, 0, - 70, 142, 32, 0, 0, 0, - 0, 0, 3, 0, 0, 0, - 56, 0, 0, 8, 242, 0, - 16, 0, 1, 0, 0, 0, - 86, 5, 16, 0, 0, 0, - 0, 0, 70, 142, 32, 0, - 0, 0, 0, 0, 5, 0, - 0, 0, 50, 0, 0, 10, - 242, 0, 16, 0, 1, 0, - 0, 0, 6, 0, 16, 0, - 0, 0, 0, 0, 70, 142, - 32, 0, 0, 0, 0, 0, - 4, 0, 0, 0, 70, 14, - 16, 0, 1, 0, 0, 0, - 50, 0, 0, 10, 242, 0, - 16, 0, 1, 0, 0, 0, - 166, 10, 16, 0, 0, 0, - 0, 0, 70, 142, 32, 0, - 0, 0, 0, 0, 6, 0, - 0, 0, 70, 14, 16, 0, - 1, 0, 0, 0, 50, 0, - 0, 10, 242, 32, 16, 0, - 0, 0, 0, 0, 246, 15, - 16, 0, 0, 0, 0, 0, - 70, 142, 32, 0, 0, 0, - 0, 0, 7, 0, 0, 0, - 70, 14, 16, 0, 1, 0, - 0, 0, 54, 0, 0, 5, - 50, 32, 16, 0, 1, 0, - 0, 0, 70, 16, 16, 0, - 1, 0, 0, 0, 54, 0, - 0, 5, 242, 32, 16, 0, - 2, 0, 0, 0, 70, 30, - 16, 0, 2, 0, 0, 0, - 62, 0, 0, 1, 83, 84, - 65, 84, 116, 0, 0, 0, - 11, 0, 0, 0, 2, 0, - 0, 0, 0, 0, 0, 0, - 6, 0, 0, 0, 8, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 1, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 2, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 82, 68, 69, 70, - 240, 0, 0, 0, 1, 0, - 0, 0, 84, 0, 0, 0, - 1, 0, 0, 0, 28, 0, - 0, 0, 0, 4, 254, 255, - 0, 1, 0, 0, 198, 0, - 0, 0, 60, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 1, 0, 0, 0, - 1, 0, 0, 0, 86, 101, - 114, 116, 101, 120, 83, 104, - 97, 100, 101, 114, 67, 111, - 110, 115, 116, 97, 110, 116, - 115, 0, 171, 171, 60, 0, - 0, 0, 2, 0, 0, 0, - 108, 0, 0, 0, 128, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 156, 0, - 0, 0, 0, 0, 0, 0, - 64, 0, 0, 0, 2, 0, - 0, 0, 164, 0, 0, 0, - 0, 0, 0, 0, 180, 0, - 0, 0, 64, 0, 0, 0, - 64, 0, 0, 0, 2, 0, - 0, 0, 164, 0, 0, 0, - 0, 0, 0, 0, 109, 111, - 100, 101, 108, 0, 171, 171, - 2, 0, 3, 0, 4, 0, - 4, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 112, 114, - 111, 106, 101, 99, 116, 105, - 111, 110, 65, 110, 100, 86, - 105, 101, 119, 0, 77, 105, - 99, 114, 111, 115, 111, 102, - 116, 32, 40, 82, 41, 32, - 72, 76, 83, 76, 32, 83, - 104, 97, 100, 101, 114, 32, - 67, 111, 109, 112, 105, 108, - 101, 114, 32, 49, 48, 46, - 49, 0, 171, 171, 73, 83, - 71, 78, 104, 0, 0, 0, - 3, 0, 0, 0, 8, 0, - 0, 0, 80, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 3, 0, 0, 0, - 0, 0, 0, 0, 7, 7, - 0, 0, 89, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 3, 0, 0, 0, - 1, 0, 0, 0, 3, 3, - 0, 0, 98, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 3, 0, 0, 0, - 2, 0, 0, 0, 15, 15, - 0, 0, 80, 79, 83, 73, - 84, 73, 79, 78, 0, 84, - 69, 88, 67, 79, 79, 82, - 68, 0, 67, 79, 76, 79, - 82, 0, 79, 83, 71, 78, - 108, 0, 0, 0, 3, 0, - 0, 0, 8, 0, 0, 0, - 80, 0, 0, 0, 0, 0, - 0, 0, 1, 0, 0, 0, - 3, 0, 0, 0, 0, 0, - 0, 0, 15, 0, 0, 0, - 92, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 3, 0, 0, 0, 1, 0, - 0, 0, 3, 12, 0, 0, - 101, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 3, 0, 0, 0, 2, 0, - 0, 0, 15, 0, 0, 0, - 83, 86, 95, 80, 79, 83, - 73, 84, 73, 79, 78, 0, - 84, 69, 88, 67, 79, 79, - 82, 68, 0, 67, 79, 76, + 68, 88, 66, 67, 152, 172, + 81, 45, 198, 200, 12, 38, + 143, 4, 178, 228, 158, 175, + 169, 64, 1, 0, 0, 0, + 140, 5, 0, 0, 6, 0, + 0, 0, 56, 0, 0, 0, + 108, 1, 0, 0, 52, 3, + 0, 0, 176, 3, 0, 0, + 168, 4, 0, 0, 24, 5, + 0, 0, 65, 111, 110, 57, + 44, 1, 0, 0, 44, 1, + 0, 0, 0, 2, 254, 255, + 248, 0, 0, 0, 52, 0, + 0, 0, 1, 0, 36, 0, + 0, 0, 48, 0, 0, 0, + 48, 0, 0, 0, 36, 0, + 1, 0, 48, 0, 0, 0, + 0, 0, 8, 0, 1, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 2, 254, 255, + 31, 0, 0, 2, 5, 0, + 0, 128, 0, 0, 15, 144, + 31, 0, 0, 2, 5, 0, + 1, 128, 1, 0, 15, 144, + 31, 0, 0, 2, 5, 0, + 2, 128, 2, 0, 15, 144, + 5, 0, 0, 3, 0, 0, + 15, 128, 0, 0, 85, 144, + 2, 0, 228, 160, 4, 0, + 0, 4, 0, 0, 15, 128, + 0, 0, 0, 144, 1, 0, + 228, 160, 0, 0, 228, 128, + 4, 0, 0, 4, 0, 0, + 15, 128, 0, 0, 170, 144, + 3, 0, 228, 160, 0, 0, + 228, 128, 2, 0, 0, 3, + 0, 0, 15, 128, 0, 0, + 228, 128, 4, 0, 228, 160, + 5, 0, 0, 3, 1, 0, + 15, 128, 0, 0, 85, 128, + 6, 0, 228, 160, 4, 0, + 0, 4, 1, 0, 15, 128, + 0, 0, 0, 128, 5, 0, + 228, 160, 1, 0, 228, 128, + 4, 0, 0, 4, 1, 0, + 15, 128, 0, 0, 170, 128, + 7, 0, 228, 160, 1, 0, + 228, 128, 4, 0, 0, 4, + 0, 0, 15, 128, 0, 0, + 255, 128, 8, 0, 228, 160, + 1, 0, 228, 128, 4, 0, + 0, 4, 0, 0, 3, 192, + 0, 0, 255, 128, 0, 0, + 228, 160, 0, 0, 228, 128, + 1, 0, 0, 2, 0, 0, + 12, 192, 0, 0, 228, 128, + 1, 0, 0, 2, 0, 0, + 3, 224, 1, 0, 228, 144, + 1, 0, 0, 2, 1, 0, + 15, 224, 2, 0, 228, 144, + 255, 255, 0, 0, 83, 72, + 68, 82, 192, 1, 0, 0, + 64, 0, 1, 0, 112, 0, + 0, 0, 89, 0, 0, 4, + 70, 142, 32, 0, 0, 0, + 0, 0, 8, 0, 0, 0, + 95, 0, 0, 3, 114, 16, + 16, 0, 0, 0, 0, 0, + 95, 0, 0, 3, 50, 16, + 16, 0, 1, 0, 0, 0, + 95, 0, 0, 3, 242, 16, + 16, 0, 2, 0, 0, 0, + 103, 0, 0, 4, 242, 32, + 16, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 101, 0, + 0, 3, 50, 32, 16, 0, + 1, 0, 0, 0, 101, 0, + 0, 3, 242, 32, 16, 0, + 2, 0, 0, 0, 104, 0, + 0, 2, 2, 0, 0, 0, + 56, 0, 0, 8, 242, 0, + 16, 0, 0, 0, 0, 0, + 86, 21, 16, 0, 0, 0, + 0, 0, 70, 142, 32, 0, + 0, 0, 0, 0, 1, 0, + 0, 0, 50, 0, 0, 10, + 242, 0, 16, 0, 0, 0, + 0, 0, 6, 16, 16, 0, + 0, 0, 0, 0, 70, 142, + 32, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 70, 14, + 16, 0, 0, 0, 0, 0, + 50, 0, 0, 10, 242, 0, + 16, 0, 0, 0, 0, 0, + 166, 26, 16, 0, 0, 0, + 0, 0, 70, 142, 32, 0, + 0, 0, 0, 0, 2, 0, + 0, 0, 70, 14, 16, 0, + 0, 0, 0, 0, 0, 0, + 0, 8, 242, 0, 16, 0, + 0, 0, 0, 0, 70, 14, + 16, 0, 0, 0, 0, 0, + 70, 142, 32, 0, 0, 0, + 0, 0, 3, 0, 0, 0, + 56, 0, 0, 8, 242, 0, + 16, 0, 1, 0, 0, 0, + 86, 5, 16, 0, 0, 0, + 0, 0, 70, 142, 32, 0, + 0, 0, 0, 0, 5, 0, + 0, 0, 50, 0, 0, 10, + 242, 0, 16, 0, 1, 0, + 0, 0, 6, 0, 16, 0, + 0, 0, 0, 0, 70, 142, + 32, 0, 0, 0, 0, 0, + 4, 0, 0, 0, 70, 14, + 16, 0, 1, 0, 0, 0, + 50, 0, 0, 10, 242, 0, + 16, 0, 1, 0, 0, 0, + 166, 10, 16, 0, 0, 0, + 0, 0, 70, 142, 32, 0, + 0, 0, 0, 0, 6, 0, + 0, 0, 70, 14, 16, 0, + 1, 0, 0, 0, 50, 0, + 0, 10, 242, 32, 16, 0, + 0, 0, 0, 0, 246, 15, + 16, 0, 0, 0, 0, 0, + 70, 142, 32, 0, 0, 0, + 0, 0, 7, 0, 0, 0, + 70, 14, 16, 0, 1, 0, + 0, 0, 54, 0, 0, 5, + 50, 32, 16, 0, 1, 0, + 0, 0, 70, 16, 16, 0, + 1, 0, 0, 0, 54, 0, + 0, 5, 242, 32, 16, 0, + 2, 0, 0, 0, 70, 30, + 16, 0, 2, 0, 0, 0, + 62, 0, 0, 1, 83, 84, + 65, 84, 116, 0, 0, 0, + 11, 0, 0, 0, 2, 0, + 0, 0, 0, 0, 0, 0, + 6, 0, 0, 0, 8, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 1, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 2, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 82, 68, 69, 70, + 240, 0, 0, 0, 1, 0, + 0, 0, 84, 0, 0, 0, + 1, 0, 0, 0, 28, 0, + 0, 0, 0, 4, 254, 255, + 0, 1, 0, 0, 198, 0, + 0, 0, 60, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 86, 101, + 114, 116, 101, 120, 83, 104, + 97, 100, 101, 114, 67, 111, + 110, 115, 116, 97, 110, 116, + 115, 0, 171, 171, 60, 0, + 0, 0, 2, 0, 0, 0, + 108, 0, 0, 0, 128, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 156, 0, + 0, 0, 0, 0, 0, 0, + 64, 0, 0, 0, 2, 0, + 0, 0, 164, 0, 0, 0, + 0, 0, 0, 0, 180, 0, + 0, 0, 64, 0, 0, 0, + 64, 0, 0, 0, 2, 0, + 0, 0, 164, 0, 0, 0, + 0, 0, 0, 0, 109, 111, + 100, 101, 108, 0, 171, 171, + 2, 0, 3, 0, 4, 0, + 4, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 112, 114, + 111, 106, 101, 99, 116, 105, + 111, 110, 65, 110, 100, 86, + 105, 101, 119, 0, 77, 105, + 99, 114, 111, 115, 111, 102, + 116, 32, 40, 82, 41, 32, + 72, 76, 83, 76, 32, 83, + 104, 97, 100, 101, 114, 32, + 67, 111, 109, 112, 105, 108, + 101, 114, 32, 49, 48, 46, + 49, 0, 171, 171, 73, 83, + 71, 78, 104, 0, 0, 0, + 3, 0, 0, 0, 8, 0, + 0, 0, 80, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 3, 0, 0, 0, + 0, 0, 0, 0, 7, 7, + 0, 0, 89, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 3, 0, 0, 0, + 1, 0, 0, 0, 3, 3, + 0, 0, 98, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 3, 0, 0, 0, + 2, 0, 0, 0, 15, 15, + 0, 0, 80, 79, 83, 73, + 84, 73, 79, 78, 0, 84, + 69, 88, 67, 79, 79, 82, + 68, 0, 67, 79, 76, 79, + 82, 0, 79, 83, 71, 78, + 108, 0, 0, 0, 3, 0, + 0, 0, 8, 0, 0, 0, + 80, 0, 0, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + 3, 0, 0, 0, 0, 0, + 0, 0, 15, 0, 0, 0, + 92, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 3, 0, 0, 0, 1, 0, + 0, 0, 3, 12, 0, 0, + 101, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 3, 0, 0, 0, 2, 0, + 0, 0, 15, 0, 0, 0, + 83, 86, 95, 80, 79, 83, + 73, 84, 73, 79, 78, 0, + 84, 69, 88, 67, 79, 79, + 82, 68, 0, 67, 79, 76, 79, 82, 0, 171 }; diff --git a/libs/SDL3/src/render/direct3d11/SDL_render_d3d11.c b/libs/SDL3/src/render/direct3d11/SDL_render_d3d11.c index 60f646fd..12fd5abc 100644 --- a/libs/SDL3/src/render/direct3d11/SDL_render_d3d11.c +++ b/libs/SDL3/src/render/direct3d11/SDL_render_d3d11.c @@ -50,16 +50,6 @@ /* !!! FIXME: vertex buffer bandwidth could be lower; only use UV coords when !!! FIXME: textures are needed. */ -// Sampler types -typedef enum -{ - D3D11_SAMPLER_NEAREST_CLAMP, - D3D11_SAMPLER_NEAREST_WRAP, - D3D11_SAMPLER_LINEAR_CLAMP, - D3D11_SAMPLER_LINEAR_WRAP, - D3D11_SAMPLER_COUNT -} D3D11_Sampler; - // Vertex shader, common values typedef struct { @@ -74,9 +64,10 @@ static const float TONEMAP_CHROME = 2; //static const float TEXTURETYPE_NONE = 0; static const float TEXTURETYPE_RGB = 1; -static const float TEXTURETYPE_NV12 = 2; -static const float TEXTURETYPE_NV21 = 3; -static const float TEXTURETYPE_YUV = 4; +static const float TEXTURETYPE_RGB_PIXELART = 2; +static const float TEXTURETYPE_NV12 = 3; +static const float TEXTURETYPE_NV21 = 4; +static const float TEXTURETYPE_YUV = 5; static const float INPUTTYPE_UNSPECIFIED = 0; static const float INPUTTYPE_SRGB = 1; @@ -90,6 +81,11 @@ typedef struct float input_type; float color_scale; + float texel_width; + float texel_height; + float texture_width; + float texture_height; + float tonemap_method; float tonemap_factor1; float tonemap_factor2; @@ -172,7 +168,7 @@ typedef struct ID3D11PixelShader *pixelShaders[NUM_SHADERS]; int blendModesCount; D3D11_BlendMode *blendModes; - ID3D11SamplerState *samplers[D3D11_SAMPLER_COUNT]; + ID3D11SamplerState *samplers[RENDER_SAMPLER_COUNT]; D3D_FEATURE_LEVEL featureLevel; bool pixelSizeChanged; @@ -532,7 +528,6 @@ static HRESULT D3D11_CreateDeviceResources(SDL_Renderer *renderer) }; D3D11_BUFFER_DESC constantBufferDesc; - D3D11_SAMPLER_DESC samplerDesc; D3D11_RASTERIZER_DESC rasterDesc; // See if we need debug interfaces @@ -721,38 +716,6 @@ static HRESULT D3D11_CreateDeviceResources(SDL_Renderer *renderer) goto done; } - // Create samplers to use when drawing textures: - static struct - { - D3D11_FILTER filter; - D3D11_TEXTURE_ADDRESS_MODE address; - } samplerParams[] = { - { D3D11_FILTER_MIN_MAG_MIP_POINT, D3D11_TEXTURE_ADDRESS_CLAMP }, - { D3D11_FILTER_MIN_MAG_MIP_POINT, D3D11_TEXTURE_ADDRESS_WRAP }, - { D3D11_FILTER_MIN_MAG_MIP_LINEAR, D3D11_TEXTURE_ADDRESS_CLAMP }, - { D3D11_FILTER_MIN_MAG_MIP_LINEAR, D3D11_TEXTURE_ADDRESS_WRAP }, - }; - SDL_COMPILE_TIME_ASSERT(samplerParams_SIZE, SDL_arraysize(samplerParams) == D3D11_SAMPLER_COUNT); - SDL_zero(samplerDesc); - samplerDesc.AddressW = D3D11_TEXTURE_ADDRESS_CLAMP; - samplerDesc.MipLODBias = 0.0f; - samplerDesc.MaxAnisotropy = 1; - samplerDesc.ComparisonFunc = D3D11_COMPARISON_ALWAYS; - samplerDesc.MinLOD = 0.0f; - samplerDesc.MaxLOD = D3D11_FLOAT32_MAX; - for (int i = 0; i < SDL_arraysize(samplerParams); ++i) { - samplerDesc.Filter = samplerParams[i].filter; - samplerDesc.AddressU = samplerParams[i].address; - samplerDesc.AddressV = samplerParams[i].address; - result = ID3D11Device_CreateSamplerState(data->d3dDevice, - &samplerDesc, - &data->samplers[i]); - if (FAILED(result)) { - WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("ID3D11Device1::CreateSamplerState [nearest-pixel filter]"), result); - goto done; - } - } - // Setup Direct3D rasterizer states SDL_zero(rasterDesc); rasterDesc.AntialiasedLineEnable = FALSE; @@ -2106,7 +2069,15 @@ static void D3D11_SetupShaderConstants(SDL_Renderer *renderer, const SDL_RenderC constants->input_type = INPUTTYPE_HDR10; break; default: - constants->texture_type = TEXTURETYPE_RGB; + if (cmd->data.draw.texture_scale_mode == SDL_SCALEMODE_PIXELART) { + constants->texture_type = TEXTURETYPE_RGB_PIXELART; + constants->texture_width = texture->w; + constants->texture_height = texture->h; + constants->texel_width = 1.0f / constants->texture_width; + constants->texel_height = 1.0f / constants->texture_height; + } else { + constants->texture_type = TEXTURETYPE_RGB; + } if (texture->colorspace == SDL_COLORSPACE_SRGB_LINEAR) { constants->input_type = INPUTTYPE_SCRGB; } else if (texture->colorspace == SDL_COLORSPACE_HDR10) { @@ -2291,6 +2262,64 @@ static bool D3D11_SetDrawState(SDL_Renderer *renderer, const SDL_RenderCommand * return true; } +static ID3D11SamplerState *D3D11_GetSamplerState(D3D11_RenderData *data, SDL_ScaleMode scale_mode, SDL_TextureAddressMode address_u, SDL_TextureAddressMode address_v) +{ + Uint32 key = RENDER_SAMPLER_HASHKEY(scale_mode, address_u, address_v); + SDL_assert(key < SDL_arraysize(data->samplers)); + if (!data->samplers[key]) { + D3D11_SAMPLER_DESC samplerDesc; + SDL_zero(samplerDesc); + samplerDesc.AddressW = D3D11_TEXTURE_ADDRESS_CLAMP; + samplerDesc.MipLODBias = 0.0f; + samplerDesc.MaxAnisotropy = 1; + samplerDesc.ComparisonFunc = D3D11_COMPARISON_ALWAYS; + samplerDesc.MinLOD = 0.0f; + samplerDesc.MaxLOD = D3D11_FLOAT32_MAX; + switch (scale_mode) { + case SDL_SCALEMODE_NEAREST: + samplerDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_POINT; + break; + case SDL_SCALEMODE_PIXELART: // Uses linear sampling + case SDL_SCALEMODE_LINEAR: + samplerDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR; + break; + default: + SDL_SetError("Unknown scale mode: %d", scale_mode); + return NULL; + } + switch (address_u) { + case SDL_TEXTURE_ADDRESS_CLAMP: + samplerDesc.AddressU = D3D11_TEXTURE_ADDRESS_CLAMP; + break; + case SDL_TEXTURE_ADDRESS_WRAP: + samplerDesc.AddressU = D3D11_TEXTURE_ADDRESS_WRAP; + break; + default: + SDL_SetError("Unknown texture address mode: %d", address_u); + return NULL; + } + switch (address_v) { + case SDL_TEXTURE_ADDRESS_CLAMP: + samplerDesc.AddressV = D3D11_TEXTURE_ADDRESS_CLAMP; + break; + case SDL_TEXTURE_ADDRESS_WRAP: + samplerDesc.AddressV = D3D11_TEXTURE_ADDRESS_WRAP; + break; + default: + SDL_SetError("Unknown texture address mode: %d", address_v); + return NULL; + } + HRESULT result = ID3D11Device_CreateSamplerState(data->d3dDevice, + &samplerDesc, + &data->samplers[key]); + if (FAILED(result)) { + WIN_SetErrorFromHRESULT("ID3D11Device::CreateSamplerState", result); + return NULL; + } + } + return data->samplers[key]; +} + static bool D3D11_SetCopyState(SDL_Renderer *renderer, const SDL_RenderCommand *cmd, const Float4X4 *matrix) { SDL_Texture *texture = cmd->data.draw.texture; @@ -2305,34 +2334,11 @@ static bool D3D11_SetCopyState(SDL_Renderer *renderer, const SDL_RenderCommand * D3D11_SetupShaderConstants(renderer, cmd, texture, &constants); - switch (cmd->data.draw.texture_scale_mode) { - case SDL_SCALEMODE_NEAREST: - switch (cmd->data.draw.texture_address_mode) { - case SDL_TEXTURE_ADDRESS_CLAMP: - textureSampler = rendererData->samplers[D3D11_SAMPLER_NEAREST_CLAMP]; - break; - case SDL_TEXTURE_ADDRESS_WRAP: - textureSampler = rendererData->samplers[D3D11_SAMPLER_NEAREST_WRAP]; - break; - default: - return SDL_SetError("Unknown texture address mode: %d", cmd->data.draw.texture_address_mode); - } - break; - case SDL_SCALEMODE_LINEAR: - switch (cmd->data.draw.texture_address_mode) { - case SDL_TEXTURE_ADDRESS_CLAMP: - textureSampler = rendererData->samplers[D3D11_SAMPLER_LINEAR_CLAMP]; - break; - case SDL_TEXTURE_ADDRESS_WRAP: - textureSampler = rendererData->samplers[D3D11_SAMPLER_LINEAR_WRAP]; - break; - default: - return SDL_SetError("Unknown texture address mode: %d", cmd->data.draw.texture_address_mode); - } - break; - default: - return SDL_SetError("Unknown scale mode: %d", cmd->data.draw.texture_scale_mode); + textureSampler = D3D11_GetSamplerState(rendererData, cmd->data.draw.texture_scale_mode, cmd->data.draw.texture_address_mode_u, cmd->data.draw.texture_address_mode_v); + if (!textureSampler) { + return false; } + #ifdef SDL_HAVE_YUV if (textureData->yuv) { ID3D11ShaderResourceView *shaderResources[3]; diff --git a/libs/SDL3/src/render/direct3d12/D3D12_PixelShader_Advanced.h b/libs/SDL3/src/render/direct3d12/D3D12_PixelShader_Advanced.h index b8f40fea..8c03218a 100644 --- a/libs/SDL3/src/render/direct3d12/D3D12_PixelShader_Advanced.h +++ b/libs/SDL3/src/render/direct3d12/D3D12_PixelShader_Advanced.h @@ -15,7 +15,7 @@ ; -------------------- ----- ------ -------- -------- ------- ------ ; SV_Target 0 xyzw 0 TARGET float xyzw ; -; shader hash: f6874446eaee41c102142c02d5bb3ab7 +; shader hash: d37677952ab198af41892c76b2b3f57f ; ; Pipeline Runtime Information: ; @@ -50,16 +50,17 @@ ; float texture_type; ; Offset: 4 ; float input_type; ; Offset: 8 ; float color_scale; ; Offset: 12 -; float tonemap_method; ; Offset: 16 -; float tonemap_factor1; ; Offset: 20 -; float tonemap_factor2; ; Offset: 24 -; float sdr_white_point; ; Offset: 28 -; float4 Yoffset; ; Offset: 32 -; float4 Rcoeff; ; Offset: 48 -; float4 Gcoeff; ; Offset: 64 -; float4 Bcoeff; ; Offset: 80 +; float4 texel_size; ; Offset: 16 +; float tonemap_method; ; Offset: 32 +; float tonemap_factor1; ; Offset: 36 +; float tonemap_factor2; ; Offset: 40 +; float sdr_white_point; ; Offset: 44 +; float4 Yoffset; ; Offset: 48 +; float4 Rcoeff; ; Offset: 64 +; float4 Gcoeff; ; Offset: 80 +; float4 Bcoeff; ; Offset: 96 ; -; } Constants; ; Offset: 0 Size: 96 +; } Constants; ; Offset: 0 Size: 112 ; ; } ; @@ -93,7 +94,7 @@ target triple = "dxil-ms-dx" %dx.types.ResRet.f32 = type { float, float, float, float, i32 } %"class.Texture2D >" = type { <4 x float>, %"class.Texture2D >::mips_type" } %"class.Texture2D >::mips_type" = type { i32 } -%Constants = type { float, float, float, float, float, float, float, float, <4 x float>, <4 x float>, <4 x float>, <4 x float> } +%Constants = type { float, float, float, float, <4 x float>, float, float, float, float, <4 x float>, <4 x float>, <4 x float>, <4 x float> } %struct.SamplerState = type { i32 } define void @main() { @@ -111,7 +112,7 @@ define void @main() { %12 = call %dx.types.CBufRet.f32 @dx.op.cbufferLoadLegacy.f32(i32 59, %dx.types.Handle %5, i32 0) ; CBufferLoadLegacy(handle,regIndex) %13 = extractvalue %dx.types.CBufRet.f32 %12, 1 %14 = fcmp fast oeq float %13, 0.000000e+00 - br i1 %14, label %114, label %15 + br i1 %14, label %175, label %15 ;

)J@ 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 P{ZzX|P!1~s_v!F&%W;!P literal 0 HcmV?d00001 diff --git a/libs/SDL3/test/msdf_font.csv b/libs/SDL3/test/msdf_font.csv new file mode 100644 index 00000000..8fffcbe7 --- /dev/null +++ b/libs/SDL3/test/msdf_font.csv @@ -0,0 +1,95 @@ +32,0.259765625,0,-0,0,-0,0,256,0,256 +33,0.26416015625,0.04259963723256735,-0.74801901743264665,0.22009567526743265,0.063391442155309036,167.5,44.5,174.5,76.5 +34,0.3984375,0.034401000396196514,-0.74801901743264665,0.36403649960380352,-0.41838351822503961,242.5,175.5,255.5,188.5 +35,0.64599609375,-0.0068815929576070169,-0.74801901743264665,0.65238940545760693,0.038034865293185421,0.5,111.5,26.5,142.5 +36,0.57177734375,0.033055325128763823,-0.79873217115689388,0.54018686237123603,0.08874801901743265,142.5,0.5,162.5,35.5 +37,0.82666015625,0.020303136762083947,-0.77337559429477021,0.80635701948791605,0.038034865293185421,175.5,44.5,206.5,76.5 +38,0.728515625,0.021001510499207562,-0.77337559429477021,0.75634223950079238,0.038034865293185421,214.5,44.5,243.5,76.5 +39,0.21923828125,0.033793550663629157,-0.74801901743264665,0.18593301183637084,-0.41838351822503961,89.5,232.5,95.5,245.5 +40,0.294921875,0.012882577258320127,-0.74801901743264665,0.29180492274167991,0.19017432646592711,62.5,0.5,73.5,37.5 +41,0.294921875,0.0031169522583201267,-0.74801901743264665,0.28203929774167991,0.19017432646592711,74.5,0.5,85.5,37.5 +42,0.55078125,0.0079258648227020154,-0.79873217115689388,0.54041397892729792,-0.26624405705229792,234.5,78.5,255.5,99.5 +43,0.57177734375,0.018668052322702015,-0.62123613312202852,0.55115616642729792,-0.063391442155309036,22.5,232.5,43.5,254.5 +44,0.2587890625,0.011122520676505546,-0.16481774960380352,0.21397513557349446,0.16481774960380349,118.5,232.5,126.5,245.5 +45,0.32177734375,0.0089933513272583213,-0.34231378763866882,0.31327227367274169,-0.19017432646592711,242.5,189.5,254.5,195.5 +46,0.2626953125,0.04259963723256735,-0.13946117274167988,0.22009567526743265,0.063391442155309036,138.5,232.5,145.5,240.5 +47,0.36669921875,-0.019747146146988907,-0.74801901743264665,0.38595808364698891,0.038034865293185421,95.5,143.5,111.5,174.5 +48,0.57177734375,0.019400474197702015,-0.77337559429477021,0.55188858830229792,0.038034865293185421,0.5,78.5,21.5,110.5 +49,0.57177734375,0.055885375396196514,-0.74801901743264665,0.38552087460380352,0.038034865293185421,137.5,143.5,150.5,174.5 +50,0.57177734375,0.016959067947702015,-0.77337559429477021,0.54944718205229792,0.038034865293185421,22.5,78.5,43.5,110.5 +51,0.57177734375,0.012564536697702015,-0.77337559429477021,0.54505265080229792,0.038034865293185421,44.5,78.5,65.5,110.5 +52,0.57177734375,-0.0044912589144215989,-0.74801901743264665,0.57871000891442159,0.038034865293185421,232.5,143.5,255.5,174.5 +53,0.57177734375,0.035740872003763823,-0.74801901743264665,0.54287240924623603,0.038034865293185421,235.5,111.5,255.5,142.5 +54,0.57177734375,0.023795005447702015,-0.77337559429477021,0.55628311955229792,0.038034865293185421,66.5,78.5,87.5,110.5 +55,0.57177734375,0.017203208572702015,-0.74801901743264665,0.54969132267729792,0.038034865293185421,82.5,175.5,103.5,206.5 +56,0.57177734375,0.018912192947702015,-0.77337559429477021,0.55140030705229792,0.038034865293185421,88.5,78.5,109.5,110.5 +57,0.57177734375,0.017935630447702015,-0.77337559429477021,0.55042374455229792,0.038034865293185421,110.5,78.5,131.5,110.5 +58,0.2626953125,0.04259963723256735,-0.59587955625990485,0.22009567526743265,0.063391442155309036,130.5,175.5,137.5,201.5 +59,0.2626953125,-0.0044854552545562608,-0.59587955625990485,0.22372373650455626,0.16481774960380349,104.5,175.5,113.5,205.5 +60,0.57177734375,0.018912192947702015,-0.64659270998415219,0.55140030705229792,-0.08874801901743265,0.5,232.5,21.5,254.5 +61,0.57177734375,0.018912192947702015,-0.51980982567353418,0.55140030705229792,-0.19017432646592711,96.5,232.5,117.5,245.5 +62,0.57177734375,0.018912192947702015,-0.64659270998415219,0.55140030705229792,-0.08874801901743265,44.5,232.5,65.5,254.5 +63,0.431640625,-0.018492394884112522,-0.77337559429477021,0.43792598863411253,0.063391442155309036,22.5,44.5,40.5,77.5 +64,0.896484375,0.030102809899960288,-0.74801901743264665,0.86686984635003961,0.13946117274167988,163.5,0.5,196.5,35.5 +65,0.63232421875,-0.026639959513668827,-0.74801901743264665,0.65798761576366882,0.038034865293185421,23.5,175.5,50.5,206.5 +66,0.64599609375,0.065804217016640215,-0.74801901743264665,0.6236489079833597,0.038034865293185421,0.5,175.5,22.5,206.5 +67,0.6298828125,0.025799202654516594,-0.77337559429477021,0.63435704734548326,0.038034865293185421,132.5,78.5,156.5,110.5 +68,0.7255859375,0.064146304848454794,-0.74801901743264665,0.69806072640154515,0.038034865293185421,189.5,143.5,214.5,174.5 +69,0.5556640625,0.06817752699088743,-0.74801901743264665,0.52459591050911247,0.038034865293185421,170.5,143.5,188.5,174.5 +70,0.51611328125,0.06817752699088743,-0.74801901743264665,0.52459591050911247,0.038034865293185421,151.5,143.5,169.5,174.5 +71,0.72705078125,0.027053953917392983,-0.77337559429477021,0.68632495233260693,0.038034865293185421,157.5,78.5,183.5,110.5 +72,0.7373046875,0.063885140154516601,-0.74801901743264665,0.67244298484548326,0.038034865293185421,112.5,143.5,136.5,174.5 +73,0.279296875,0.063090425663629157,-0.74801901743264665,0.21522988683637084,0.038034865293185421,244.5,44.5,250.5,75.5 +74,0.2685546875,-0.11647790585380349,-0.74801901743264665,0.21315759335380352,0.21553090332805072,48.5,0.5,61.5,38.5 +75,0.6123046875,0.063379834835578408,-0.74801901743264665,0.64658110266442159,0.038034865293185421,45.5,143.5,68.5,174.5 +76,0.52197265625,0.069398230115887444,-0.74801901743264665,0.52581661363411247,0.038034865293185421,26.5,143.5,44.5,174.5 +77,0.8994140625,0.069358378318145758,-0.74801901743264665,0.83005568418185416,0.038034865293185421,51.5,175.5,81.5,206.5 +78,0.7529296875,0.059751773598454787,-0.74801901743264665,0.69366619515154515,0.038034865293185421,0.5,143.5,25.5,174.5 +79,0.77783203125,0.033923939555269372,-0.77337559429477021,0.74390809169473049,0.038034865293185421,184.5,78.5,212.5,110.5 +80,0.6015625,0.070164700128763829,-0.74801901743264665,0.57729623737123603,0.038034865293185421,191.5,111.5,211.5,142.5 +81,0.77783203125,0.033923939555269372,-0.77337559429477021,0.74390809169473049,0.21553090332805072,19.5,0.5,47.5,39.5 +82,0.6171875,0.070198748266640215,-0.74801901743264665,0.6280434392333597,0.038034865293185421,129.5,111.5,151.5,142.5 +83,0.54833984375,0.022313137628763823,-0.77337559429477021,0.52944467487123603,0.038034865293185421,213.5,78.5,233.5,110.5 +84,0.55078125,-0.016942430789421599,-0.74801901743264665,0.56625883703942159,0.038034865293185421,79.5,111.5,102.5,142.5 +85,0.72900390625,0.059734749529516594,-0.74801901743264665,0.66829259422048326,0.038034865293185421,54.5,111.5,78.5,142.5 +86,0.59619140625,-0.031539796082607013,-0.74801901743264665,0.62773120233260693,0.038034865293185421,27.5,111.5,53.5,142.5 +87,0.92333984375,-0.020105038505348741,-0.74801901743264665,0.94344488225534862,0.038034865293185421,152.5,111.5,190.5,142.5 +88,0.57763671875,-0.028382992026545213,-0.74801901743264665,0.60553142952654515,0.038034865293185421,69.5,143.5,94.5,174.5 +89,0.55908203125,-0.037416195151545213,-0.74801901743264665,0.59649822640154515,0.038034865293185421,103.5,111.5,128.5,142.5 +90,0.572265625,0.0069663263916402082,-0.74801901743264665,0.5648110173583597,0.038034865293185421,212.5,111.5,234.5,142.5 +91,0.3271484375,0.052189217883320121,-0.74801901743264665,0.33111156336667991,0.19017432646592711,102.5,0.5,113.5,37.5 +92,0.36669921875,-0.019258864896988907,-0.74801901743264665,0.38644636489698891,0.038034865293185421,215.5,143.5,231.5,174.5 +93,0.3271484375,-0.0037189852416798751,-0.74801901743264665,0.27520336024167991,0.19017432646592711,114.5,0.5,125.5,37.5 +94,0.57177734375,0.0072104670166402082,-0.74801901743264665,0.5650551579833597,-0.24088748019017434,66.5,232.5,88.5,252.5 +95,0.43798828125,-0.034571627996236129,0.063391442155309036,0.47255990924623614,0.19017432646592711,89.5,246.5,109.5,251.5 +96,0.27734375,0.012133131314381934,-0.79873217115689388,0.26569889993561807,-0.57052297939778129,127.5,232.5,137.5,241.5 +97,0.5556640625,0.019122285434825671,-0.57052297939778129,0.50089724581517436,0.038034865293185421,21.5,207.5,40.5,231.5 +98,0.61181640625,0.054556724197702015,-0.79873217115689388,0.58704483830229792,0.038034865293185421,41.5,44.5,62.5,77.5 +99,0.47900390625,0.022767370740887478,-0.57052297939778129,0.47918575425911253,0.038034865293185421,168.5,207.5,186.5,231.5 +100,0.61181640625,0.024527427322702015,-0.79873217115689388,0.55701554142729792,0.038034865293185421,63.5,44.5,84.5,77.5 +101,0.5615234375,0.028904934503763823,-0.57052297939778129,0.53603647174623603,0.038034865293185421,200.5,175.5,220.5,199.5 +102,0.33642578125,-0.017288715828050714,-0.79873217115689388,0.41377309082805075,0.038034865293185421,128.5,44.5,145.5,77.5 +103,0.54296875,-0.022557665164421599,-0.59587955625990485,0.56064360266442159,0.26624405705229792,197.5,0.5,220.5,34.5 +104,0.61328125,0.055027981378763823,-0.79873217115689388,0.56215951862123603,0.038034865293185421,146.5,44.5,166.5,77.5 +105,0.25244140625,0.050883394413629157,-0.77337559429477021,0.20302285558637084,0.038034865293185421,207.5,44.5,213.5,76.5 +106,0.25244140625,-0.091592586172741686,-0.77337559429477021,0.21268633617274166,0.26624405705229792,6.5,0.5,18.5,41.5 +107,0.525390625,0.047947903253763823,-0.79873217115689388,0.55507944049623603,0.038034865293185421,107.5,44.5,127.5,77.5 +108,0.25244140625,0.050150972538629157,-0.79873217115689388,0.20229043371137084,0.038034865293185421,244.5,0.5,250.5,33.5 +109,0.92578125,0.059382660831022144,-0.57052297939778129,0.87079312041897783,0.038034865293185421,60.5,207.5,92.5,231.5 +110,0.61328125,0.055027981378763823,-0.57052297939778129,0.56215951862123603,0.038034865293185421,0.5,207.5,20.5,231.5 +111,0.6015625,0.022347185766640208,-0.57052297939778129,0.5801918767333597,0.038034865293185421,145.5,207.5,167.5,231.5 +112,0.61181640625,0.054556724197702015,-0.57052297939778129,0.58704483830229792,0.26624405705229792,85.5,44.5,106.5,77.5 +113,0.61181640625,0.024039146072702015,-0.57052297939778129,0.55652726017729792,0.26624405705229792,0.5,44.5,21.5,77.5 +114,0.40869140625,0.0493276266590729,-0.57052297939778129,0.42967627959092713,0.038034865293185421,138.5,175.5,153.5,199.5 +115,0.4765625,0.012757605115887478,-0.57052297939778129,0.46917598863411253,0.038034865293185421,41.5,207.5,59.5,231.5 +116,0.3564453125,-0.0156137795909271,-0.69730586370839942,0.36473487334092713,0.038034865293185421,114.5,175.5,129.5,204.5 +117,0.61328125,0.049901028253763823,-0.57052297939778129,0.55703256549623603,0.038034865293185421,221.5,175.5,241.5,199.5 +118,0.49951171875,-0.029166486108359792,-0.57052297939778129,0.5286782048583597,0.038034865293185421,154.5,175.5,176.5,199.5 +119,0.77490234375,-0.01825405791897786,-0.57052297939778129,0.79315640166897772,0.038034865293185421,93.5,207.5,125.5,231.5 +120,0.5234375,-0.017203595483359792,-0.57052297939778129,0.5406410954833597,0.038034865293185421,177.5,175.5,199.5,199.5 +121,0.5009765625,-0.027945782983359792,-0.57052297939778129,0.5298989079833597,0.26624405705229792,221.5,0.5,243.5,33.5 +122,0.46875,0.0059216676158874784,-0.57052297939778129,0.46234005113411253,0.038034865293185421,126.5,207.5,144.5,231.5 +123,0.375,-0.0048715920909271002,-0.74801901743264665,0.37547706084092713,0.19017432646592711,86.5,0.5,101.5,37.5 +124,0.54931640625,0.21077847971969096,-0.79873217115689377,0.33756136403030901,0.29160063391442159,0.5,0.5,5.5,43.5 +125,0.375,1.122040907289984e-05,-0.74801901743264665,0.38035987334092713,0.19017432646592711,126.5,0.5,141.5,37.5 +126,0.57177734375,0.018912192947702015,-0.44374009508716328,0.55140030705229792,-0.24088748019017434,234.5,100.5,255.5,108.5 diff --git a/libs/SDL3/test/testaudio.c b/libs/SDL3/test/testaudio.c index 8abb3b2d..459f3e3c 100644 --- a/libs/SDL3/test/testaudio.c +++ b/libs/SDL3/test/testaudio.c @@ -718,16 +718,15 @@ static Texture *CreateTexture(const char *fname) if (!tex) { SDL_Log("Out of memory!"); } else { - int texw, texh; - tex->texture = LoadTexture(state->renderers[0], fname, true, &texw, &texh); + tex->texture = LoadTexture(state->renderers[0], fname, true); if (!tex->texture) { SDL_Log("Failed to load '%s': %s", fname, SDL_GetError()); SDL_free(tex); return NULL; } SDL_SetTextureBlendMode(tex->texture, SDL_BLENDMODE_BLEND); - tex->w = (float) texw; - tex->h = (float) texh; + tex->w = (float)tex->texture->w; + tex->h = (float)tex->texture->h; } return tex; } @@ -1061,6 +1060,10 @@ SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) { int i; + char version[32]; /* use SDL's version number, since this test program is part of SDL's sources. */ + SDL_snprintf(version, sizeof (version), "%d.%d.%d", SDL_MAJOR_VERSION, SDL_MINOR_VERSION, SDL_MICRO_VERSION); + SDL_SetAppMetadata("SDL testaudio", version, "org.libsdl.testaudio"); + state = SDLTest_CommonCreateState(argv, SDL_INIT_VIDEO | SDL_INIT_AUDIO); if (!state) { return SDL_APP_FAILURE; diff --git a/libs/SDL3/test/testautomation_hints.c b/libs/SDL3/test/testautomation_hints.c index e2c158b4..825beb20 100644 --- a/libs/SDL3/test/testautomation_hints.c +++ b/libs/SDL3/test/testautomation_hints.c @@ -69,9 +69,19 @@ static int SDLCALL hints_getHint(void *arg) return TEST_COMPLETED; } +typedef struct { + char *name; + char *value; + char *oldValue; +} HintCallbackContext; + static void SDLCALL hints_testHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint) { - *(char **)userdata = hint ? SDL_strdup(hint) : NULL; + HintCallbackContext *context = userdata; + + context->name = name ? SDL_strdup(name) : NULL; + context->value = hint ? SDL_strdup(hint) : NULL; + context->oldValue = oldValue ? SDL_strdup(oldValue) : NULL; } /** @@ -83,7 +93,7 @@ static int SDLCALL hints_setHint(void *arg) const char *originalValue; char *value; const char *testValue; - char *callbackValue; + HintCallbackContext callback_data; bool result; int i, j; @@ -133,8 +143,10 @@ static int SDLCALL hints_setHint(void *arg) SDLTest_AssertPass("Call to SDL_GetHint() after saving and restoring hint"); originalValue = SDL_GetHint(testHint); value = (originalValue == NULL) ? NULL : SDL_strdup(originalValue); - SDL_SetHint(testHint, "temp"); - SDL_SetHint(testHint, value); + result = SDL_SetHint(testHint, "temp"); + SDLTest_AssertCheck(!result, "SDL_SetHint(\"%s\", \"temp\") should return false", testHint); + result = SDL_SetHint(testHint, value); + SDLTest_AssertCheck(!result, "SDL_SetHint(\"%s\", \"%s\" should return false", testHint, value); SDL_free(value); testValue = SDL_GetHint(testHint); SDLTest_AssertCheck( @@ -143,7 +155,8 @@ static int SDLCALL hints_setHint(void *arg) testValue); SDLTest_AssertPass("Call to SDL_SetHintWithPriority(NULL, SDL_HINT_DEFAULT)"); - SDL_SetHintWithPriority(testHint, NULL, SDL_HINT_DEFAULT); + result = SDL_SetHintWithPriority(testHint, NULL, SDL_HINT_DEFAULT); + SDLTest_AssertCheck(!result, "SDL_SetHintWithPriority(\"%s\", NULL, SDL_HINT_DEFAULT) should return false", testHint); testValue = SDL_GetHint(testHint); SDLTest_AssertCheck( testValue && SDL_strcmp(testValue, "original") == 0, @@ -151,7 +164,8 @@ static int SDLCALL hints_setHint(void *arg) testValue); SDLTest_AssertPass("Call to SDL_SetHintWithPriority(\"temp\", SDL_HINT_OVERRIDE)"); - SDL_SetHintWithPriority(testHint, "temp", SDL_HINT_OVERRIDE); + result = SDL_SetHintWithPriority(testHint, "temp", SDL_HINT_OVERRIDE); + SDLTest_AssertCheck(result, "SDL_SetHintWithPriority(\"%s\", \"temp\", SDL_HINT_OVERRIDE) should return true", testHint); testValue = SDL_GetHint(testHint); SDLTest_AssertCheck( testValue && SDL_strcmp(testValue, "temp") == 0, @@ -159,7 +173,8 @@ static int SDLCALL hints_setHint(void *arg) testValue); SDLTest_AssertPass("Call to SDL_SetHintWithPriority(NULL, SDL_HINT_OVERRIDE)"); - SDL_SetHintWithPriority(testHint, NULL, SDL_HINT_OVERRIDE); + result = SDL_SetHintWithPriority(testHint, NULL, SDL_HINT_OVERRIDE); + SDLTest_AssertCheck(result, "SDL_SetHintWithPriority(\"%s\", NULL, SDL_HINT_OVERRIDE) should return true", testHint); testValue = SDL_GetHint(testHint); SDLTest_AssertCheck( testValue == NULL, @@ -175,50 +190,83 @@ static int SDLCALL hints_setHint(void *arg) testValue); /* Make sure callback functionality works past a reset */ + SDL_zero(callback_data); SDLTest_AssertPass("Call to SDL_AddHintCallback()"); - callbackValue = NULL; - SDL_AddHintCallback(testHint, hints_testHintChanged, &callbackValue); + SDL_AddHintCallback(testHint, hints_testHintChanged, &callback_data); SDLTest_AssertCheck( - callbackValue && SDL_strcmp(callbackValue, "original") == 0, - "callbackValue = %s, expected \"original\"", - callbackValue); - SDL_free(callbackValue); - - SDLTest_AssertPass("Call to SDL_SetHintWithPriority(\"temp\", SDL_HINT_OVERRIDE), using callback"); - callbackValue = NULL; - SDL_SetHintWithPriority(testHint, "temp", SDL_HINT_OVERRIDE); + callback_data.name && SDL_strcmp(callback_data.name, testHint) == 0, + "callback_data.name = \"%s\", expected \"%s\"", + callback_data.name, testHint); SDLTest_AssertCheck( - callbackValue && SDL_strcmp(callbackValue, "temp") == 0, - "callbackValue = %s, expected \"temp\"", - callbackValue); - SDL_free(callbackValue); + callback_data.value && SDL_strcmp(callback_data.value, "original") == 0, + "callback_data.value = \"%s\", expected \"%s\"", + callback_data.value, "original"); + SDL_free(callback_data.name); + SDL_free(callback_data.value); + SDL_free(callback_data.oldValue); + SDL_zero(callback_data); SDLTest_AssertPass("Call to SDL_ResetHint(), using callback"); - callbackValue = NULL; SDL_ResetHint(testHint); SDLTest_AssertCheck( - callbackValue && SDL_strcmp(callbackValue, "original") == 0, + callback_data.value && SDL_strcmp(callback_data.value, "original") == 0, "callbackValue = %s, expected \"original\"", - callbackValue); - SDL_free(callbackValue); + callback_data.value); + SDL_free(callback_data.name); + SDL_free(callback_data.value); + SDL_free(callback_data.oldValue); + SDL_zero(callback_data); SDLTest_AssertPass("Call to SDL_SetHintWithPriority(\"temp\", SDL_HINT_OVERRIDE), using callback after reset"); - callbackValue = NULL; - SDL_SetHintWithPriority(testHint, "temp", SDL_HINT_OVERRIDE); + result = SDL_SetHintWithPriority(testHint, "temp", SDL_HINT_OVERRIDE); + SDLTest_AssertCheck(result, "SDL_SetHintWithPriority(\"%s\", \"temp\", SDL_HINT_OVERRIDE) should return true", testHint); SDLTest_AssertCheck( - callbackValue && SDL_strcmp(callbackValue, "temp") == 0, + callback_data.value && SDL_strcmp(callback_data.value, "temp") == 0, "callbackValue = %s, expected \"temp\"", - callbackValue); - SDL_free(callbackValue); + callback_data.value); + SDL_free(callback_data.name); + SDL_free(callback_data.value); + SDL_free(callback_data.oldValue); + SDL_zero(callback_data); SDLTest_AssertPass("Call to SDL_ResetHint(), after clearing callback"); - callbackValue = NULL; - SDL_RemoveHintCallback(testHint, hints_testHintChanged, &callbackValue); + SDL_RemoveHintCallback(testHint, hints_testHintChanged, &callback_data); SDL_ResetHint(testHint); SDLTest_AssertCheck( - callbackValue == NULL, + !callback_data.value, "callbackValue = %s, expected \"(null)\"", - callbackValue); + callback_data.value); + SDL_free(callback_data.name); + SDL_free(callback_data.value); + SDL_free(callback_data.oldValue); + SDL_zero(callback_data); + + /* Make sure callback functionality work with hint renamed in sdl3 */ + SDLTest_AssertPass("Call to SDL_AddHintCallback()"); + SDL_AddHintCallback(SDL_HINT_WINDOW_ALLOW_TOPMOST, hints_testHintChanged, &callback_data); + SDLTest_AssertPass("Call to SDL_SetHintWithPriority(\"temp\", SDL_HINT_OVERRIDE), using callback"); + SDLTest_AssertCheck(callback_data.name && SDL_strcmp(callback_data.name, SDL_HINT_WINDOW_ALLOW_TOPMOST) == 0, "callback was called with name \"%s\" (expected \"%s\")", callback_data.name, SDL_HINT_WINDOW_ALLOW_TOPMOST); + SDLTest_AssertCheck(!callback_data.value, "callback was called with null value, was %s", callback_data.value); + SDLTest_AssertCheck(!callback_data.oldValue, "callback was called with null oldvalue, was %s", callback_data.oldValue); + SDL_free(callback_data.name); + SDL_free(callback_data.value); + SDL_free(callback_data.oldValue); + SDL_zero(callback_data); + result = SDL_SetHintWithPriority(SDL_HINT_WINDOW_ALLOW_TOPMOST, "temp", SDL_HINT_OVERRIDE); + SDLTest_AssertCheck(result, "SDL_SetHintWithPriority(\"%s\", \"temp\", SDL_HINT_OVERRIDE) should return true", testHint); + SDLTest_AssertCheck( + callback_data.name && SDL_strcmp(callback_data.name, SDL_HINT_WINDOW_ALLOW_TOPMOST) == 0, + "callback_data.name = \"%s\", expected \"%s\"", + callback_data.name, SDL_HINT_WINDOW_ALLOW_TOPMOST); + SDLTest_AssertCheck( + callback_data.value && SDL_strcmp(callback_data.value, "temp") == 0, + "callback_data.value = \"%s\", expected \"%s\"", + callback_data.value, "temp"); + SDL_free(callback_data.name); + SDL_free(callback_data.value); + SDL_free(callback_data.oldValue); + SDL_zero(callback_data); + SDL_ResetHint(testHint); return TEST_COMPLETED; } @@ -226,17 +274,19 @@ static int SDLCALL hints_setHint(void *arg) /* ================= Test References ================== */ /* Hints test cases */ -static const SDLTest_TestCaseReference hintsTest1 = { +static const SDLTest_TestCaseReference hintsGetHint = { hints_getHint, "hints_getHint", "Call to SDL_GetHint", TEST_ENABLED }; -static const SDLTest_TestCaseReference hintsTest2 = { +static const SDLTest_TestCaseReference hintsSetHint = { hints_setHint, "hints_setHint", "Call to SDL_SetHint", TEST_ENABLED }; /* Sequence of Hints test cases */ static const SDLTest_TestCaseReference *hintsTests[] = { - &hintsTest1, &hintsTest2, NULL + &hintsGetHint, + &hintsSetHint, + NULL }; /* Hints test suite (global) */ diff --git a/libs/SDL3/test/testautomation_joystick.c b/libs/SDL3/test/testautomation_joystick.c index 274ba79f..bce6c7f2 100644 --- a/libs/SDL3/test/testautomation_joystick.c +++ b/libs/SDL3/test/testautomation_joystick.c @@ -132,6 +132,23 @@ static int SDLCALL TestVirtualJoystick(void *arg) SDL_UpdateJoysticks(); SDLTest_AssertCheck(SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_SOUTH) == false, "SDL_GetGamepadButton(SDL_GAMEPAD_BUTTON_SOUTH) == false"); + /* Set an explicit mapping with legacy GameCube style buttons */ + SDL_SetGamepadMapping(SDL_GetJoystickID(joystick), "ff0013db5669727475616c2043007601,Virtual Nintendo GameCube,a:b0,b:b1,x:b2,y:b3,back:b4,guide:b5,start:b6,leftstick:b7,rightstick:b8,leftshoulder:b9,rightshoulder:b10,dpup:b11,dpdown:b12,dpleft:b13,dpright:b14,misc1:b15,paddle1:b16,paddle2:b17,paddle3:b18,paddle4:b19,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:a4,righttrigger:a5,hint:SDL_GAMECONTROLLER_USE_GAMECUBE_LABELS:=1,"); + { + const char *name = SDL_GetGamepadName(gamepad); + SDLTest_AssertCheck(name && SDL_strcmp(name, "Virtual Nintendo GameCube") == 0, "SDL_GetGamepadName() -> \"%s\" (expected \"%s\")", name, "Virtual Nintendo GameCube"); + } + SDLTest_AssertCheck(SDL_GetGamepadButtonLabel(gamepad, SDL_GAMEPAD_BUTTON_EAST) == SDL_GAMEPAD_BUTTON_LABEL_X, "SDL_GetGamepadButtonLabel(SDL_GAMEPAD_BUTTON_EAST) == SDL_GAMEPAD_BUTTON_LABEL_X"); + + /* Set the east button and verify that the gamepad responds, mapping "B" to the west button */ + SDLTest_AssertCheck(SDL_SetJoystickVirtualButton(joystick, SDL_GAMEPAD_BUTTON_EAST, true), "SDL_SetJoystickVirtualButton(SDL_GAMEPAD_BUTTON_EAST, true)"); + SDL_UpdateJoysticks(); + SDLTest_AssertCheck(SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_WEST) == true, "SDL_GetGamepadButton(SDL_GAMEPAD_BUTTON_WEST) == true"); + + SDLTest_AssertCheck(SDL_SetJoystickVirtualButton(joystick, SDL_GAMEPAD_BUTTON_EAST, false), "SDL_SetJoystickVirtualButton(SDL_GAMEPAD_BUTTON_EAST, false)"); + SDL_UpdateJoysticks(); + SDLTest_AssertCheck(SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_WEST) == false, "SDL_GetGamepadButton(SDL_GAMEPAD_BUTTON_WEST) == false"); + /* Set an explicit mapping with legacy Nintendo style buttons */ SDL_SetGamepadMapping(SDL_GetJoystickID(joystick), "ff0013db5669727475616c2043007601,Virtual Nintendo Gamepad,a:b1,b:b0,x:b3,y:b2,back:b4,guide:b5,start:b6,leftstick:b7,rightstick:b8,leftshoulder:b9,rightshoulder:b10,dpup:b11,dpdown:b12,dpleft:b13,dpright:b14,misc1:b15,paddle1:b16,paddle2:b17,paddle3:b18,paddle4:b19,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:a4,righttrigger:a5,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,"); { diff --git a/libs/SDL3/test/testautomation_rect.c b/libs/SDL3/test/testautomation_rect.c index 34673705..fa5c52cc 100644 --- a/libs/SDL3/test/testautomation_rect.c +++ b/libs/SDL3/test/testautomation_rect.c @@ -14,6 +14,33 @@ /** * Private helper to check SDL_GetRectAndLineIntersectionFloat results */ +static bool IsFRectEqual(const SDL_FRect *r1, const SDL_FRect *r2) { + static const float MAX_DELTA = 1e-5f; + SDL_FRect delta; + delta.x = r1->x - r2->x; + delta.y = r1->y - r2->y; + delta.w = r1->w - r2->w; + delta.h = r1->h - r2->h; + + return -MAX_DELTA <= delta.x && delta.x <= MAX_DELTA + && -MAX_DELTA <= delta.y && delta.y <= MAX_DELTA + && -MAX_DELTA <= delta.w && delta.w <= MAX_DELTA + && -MAX_DELTA <= delta.w && delta.h <= MAX_DELTA; +} + +/* ! + * \brief Private helper to check SDL_FPoint equality + */ +static bool IsFPointEqual(const SDL_FPoint *p1, const SDL_FPoint *p2) { + static const float MAX_DELTA = 1e-5f; + SDL_FPoint delta; + delta.x = p1->x - p2->x; + delta.y = p1->y - p2->y; + + return -MAX_DELTA <= delta.x && delta.x <= MAX_DELTA + && -MAX_DELTA <= delta.y && delta.y <= MAX_DELTA; +} + static void validateIntersectRectAndLineFloatResults( bool intersection, bool expectedIntersection, SDL_FRect *rect, @@ -1941,6 +1968,226 @@ static int SDLCALL rect_testFRectEqualsParam(void *arg) return TEST_COMPLETED; } +/* ! + * \brief Test SDL_HasRectIntersectionFloat + * + * \sa + * http://wiki.libsdl.org/SDL3/SDL_HasRectIntersectionFloat + */ +static int SDLCALL rect_testHasIntersectionFloat(void *arg) +{ + const struct { + SDL_FRect r1; + SDL_FRect r2; + bool expected; + } cases[] = { + { { 0, 0, 0, 0 }, {0, 0, 0, 0}, true }, + { { 0, 0, -200, 200 }, {0, 0, -200, 200}, false }, + { { 0, 0, 10, 10 }, {-5, 5, 10, 2}, true }, + { { 0, 0, 10, 10 }, {-5, -5, 10, 2}, false }, + { { 0, 0, 10, 10 }, {-5, -5, 2, 10}, false }, + { { 0, 0, 10, 10 }, {-5, -5, 5, 5}, true }, + { { 0, 0, 10, 10 }, {-5, -5, 5.1f, 5.1f}, true }, + { { 0, 0, 10, 10 }, {-4.99f, -4.99f, 5, 5}, true }, + }; + size_t i; + + for (i = 0; i < SDL_arraysize(cases); i++) { + bool result; + SDLTest_AssertPass("About to call SDL_HasRectIntersectionFloat(&{ %g, %g, %g, %g }, &{ %g, %g, %g, %g })", + cases[i].r1.x, cases[i].r1.y, cases[i].r1.w, cases[i].r1.h, + cases[i].r2.x, cases[i].r2.y, cases[i].r2.w, cases[i].r2.h + ); + result = SDL_HasRectIntersectionFloat(&cases[i].r1, &cases[i].r2); + SDLTest_AssertCheck(result == cases[i].expected, "Got %d, expected %d", result, cases[i].expected); + } + return TEST_COMPLETED; +} + +/* ! + * \brief Test SDL_GetRectIntersectionFloat + * + * \sa + * http://wiki.libsdl.org/SDL3/SDL_GetRectIntersectionFloat + */ +static int SDLCALL rect_testGetRectIntersectionFloat(void *arg) +{ + const struct { + SDL_FRect r1; + SDL_FRect r2; + bool result; + SDL_FRect intersect; + } cases[] = { + { { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, true }, + { { 0, 0, -200, 200 }, { 0, 0, -200, 200 }, false }, + { { 0, 0, 10, 10 }, { -5, 5, 9.9f, 2 }, true, { 0, 5, 4.9f, 2 } }, + { { 0, 0, 10, 10 }, { -5, -5, 10, 2 }, false}, + { { 0, 0, 10, 10 }, { -5, -5, 2, 10 }, false}, + { { 0, 0, 10, 10 }, { -5, -5, 5, 5 }, true}, + { { 0, 0, 10, 10 }, { -5, -5, 5.5f, 6 }, true, { 0, 0, 0.5f, 1 } } + }; + size_t i; + + for (i = 0; i < SDL_arraysize(cases); i++) { + bool result; + SDL_FRect intersect; + SDLTest_AssertPass("About to call SDL_GetRectIntersectionFloat(&{ %g, %g, %g, %g }, &{ %g, %g, %g, %g })", + cases[i].r1.x, cases[i].r1.y, cases[i].r1.w, cases[i].r1.h, + cases[i].r2.x, cases[i].r2.y, cases[i].r2.w, cases[i].r2.h + ); + result = SDL_GetRectIntersectionFloat(&cases[i].r1, &cases[i].r2, &intersect); + SDLTest_AssertCheck(result == cases[i].result, "Got %d, expected %d", result, cases[i].result); + if (cases[i].result) { + SDLTest_AssertCheck(IsFRectEqual(&intersect, &cases[i].intersect), + "Got { %g, %g, %g, %g }, expected { %g, %g, %g, %g }", + intersect.x, intersect.y, intersect.w, intersect.h, + cases[i].intersect.x, cases[i].intersect.y, cases[i].intersect.w, cases[i].intersect.h); + } + } + return TEST_COMPLETED; +} + +/* ! + * \brief Test SDL_GetRectUnionFloat + * + * \sa + * http://wiki.libsdl.org/SDL3/SDL_GetRectUnionFloat + */ +static int SDLCALL rect_testGetRectUntionFloat(void *arg) +{ + const struct { + SDL_FRect r1; + SDL_FRect r2; + SDL_FRect expected; + } cases[] = { + { { 0, 0, 10, 10 }, { 19.9f, 20, 10, 10 }, { 0, 0, 29.9f, 30 } }, + { { 0, 0, 0, 0 }, { 20, 20.1f, 10.1f, 10 }, { 0, 0.f, 30.1f, 30.1f } }, + { { -200, -4.5f, 450, 33 }, { 20, 20, 10, 10 }, { -200, -4.5f, 450, 34.5f } }, + { { 0, 0, 15, 16.5f }, { 20, 20, 0, 0 }, { 0, 0, 20.f, 20.f } } + }; + size_t i; + + for (i = 0; i < SDL_arraysize(cases); i++) { + SDL_FRect result; + SDLTest_AssertPass("About to call SDL_GetRectUnionFloat(&{ %g, %g, %g, %g }, &{ %g, %g, %g, %g })", + cases[i].r1.x, cases[i].r1.y, cases[i].r1.w, cases[i].r1.h, + cases[i].r2.x, cases[i].r2.y, cases[i].r2.w, cases[i].r2.h + ); + SDL_GetRectUnionFloat(&cases[i].r1, &cases[i].r2, &result); + SDLTest_AssertCheck(IsFRectEqual(&result, &cases[i].expected), + "Got { %g, %g, %g, %g }, expected { %g, %g, %g, %g }", + result.x, result.y, result.w, result.h, + cases[i].expected.x, cases[i].expected.y, cases[i].expected.w, cases[i].expected.h); + } + return TEST_COMPLETED; +} + +/* ! + * \brief Test SDL_EncloseFPointsUnionFRect + * + * \sa + * http://wiki.libsdl.org/SDL3/SDL_GetRectEnclosingPointsFloat + */ +static int SDLCALL rect_testGetRectEnclosingPointsFloat(void *arg) +{ + const struct { + bool with_clip; + SDL_FRect clip; + bool result; + SDL_FRect enclosing; + } cases[] = { + { true, { 0, 0, 10, 10 }, true, { 0.5f, 0.1f, 5, 7 }}, + { true, { 1.2f, 1, 10, 10 }, true, { 1.5f, 1.1f, 4, 6 }}, + { true, { -10, -10, 3, 3 }, false }, + { false, { 0 }, true, { 0.5f, 0.1f, 5, 7 }} + }; + const SDL_FPoint points[] = { + { 0.5f, 0.1f }, + { 5.5f, 7.1f }, + { 1.5f, 1.1f } + }; + char points_str[256]; + size_t i; + + SDL_strlcpy(points_str, "{", sizeof(points_str)); + for (i = 0; i < SDL_arraysize(points); i++) { + char point_str[32]; + SDL_snprintf(point_str, sizeof(point_str), "{ %g, %g }, ", points[i].x, points[i].y); + SDL_strlcat(points_str, point_str, sizeof(points_str)); + } + SDL_strlcat(points_str, "}", sizeof(points_str)); + for (i = 0; i < SDL_arraysize(cases); i++) { + char clip_str[64]; + bool result; + SDL_FRect enclosing; + const SDL_FRect* clip_ptr = NULL; + if (cases[i].with_clip) { + SDL_snprintf(clip_str, sizeof(clip_str), "&{ %g, %g, %g, %g }", + cases[i].clip.x, cases[i].clip.y, cases[i].clip.w, cases[i].clip.h); + clip_ptr = &cases[i].clip; + } else { + SDL_strlcpy(clip_str, "NULL", sizeof(clip_str)); + } + SDLTest_AssertPass("About to call SDL_GetRectEnclosingPointsFloat(&%s, %d, %s)", points_str, (int)SDL_arraysize(points), clip_str); + result = SDL_GetRectEnclosingPointsFloat(points, SDL_arraysize(points), clip_ptr, &enclosing); + SDLTest_AssertCheck(result == cases[i].result, "Got %d, expected %d", result, cases[i].result); + if (cases[i].result) { + SDLTest_AssertCheck(IsFRectEqual(&enclosing, &cases[i].enclosing), + "Got { %g, %g, %g, %g }, expected { %g, %g, %g, %g }", + enclosing.x, enclosing.y, enclosing.w, enclosing.h, + cases[i].enclosing.x, cases[i].enclosing.y, cases[i].enclosing.w, cases[i].enclosing.h); + } + } + return TEST_COMPLETED; +} + +/* ! + * \brief Test SDL_GetRectAndLineIntersectionFloat + * + * \sa + * http://wiki.libsdl.org/SDL3/SDL_GetRectAndLineIntersectionFloat + */ +static int SDLCALL rect_testGetRectAndLineIntersectionFloat(void *arg) +{ + const struct { + SDL_FRect rect; + SDL_FPoint p1; + SDL_FPoint p2; + bool result; + SDL_FPoint expected1; + SDL_FPoint expected2; + } cases[] = { + { { 0, 0, 0, 0 }, { -4.8f, -4.8f }, { 5.2f, 5.2f}, true, { 0, 0 }, { 0, 0 } }, + { { 0, 0, 2, 2 }, { -1, -1 }, { 3.5f, 3.5f}, true, { 0, 0 }, { 2, 2 } }, + { { -4, -4, 14, 14 }, { 8, 22 }, { 8, 33}, false } + + }; + size_t i; + + for (i = 0; i < SDL_arraysize(cases); i++) { + bool result; + SDL_FPoint p1 = cases[i].p1; + SDL_FPoint p2 = cases[i].p2; + + SDLTest_AssertPass("About to call SDL_GetRectAndLineIntersectionFloat(&{%g, %g, %g, %g}, &%g, &%g, &%g, &%g)", + cases[i].rect.x, cases[i].rect.y, cases[i].rect.w, cases[i].rect.h, + p1.x, p1.y, p2.x, p2.y); + result = SDL_GetRectAndLineIntersectionFloat(&cases[i].rect, &p1.x, &p1.y, &p2.x, &p2.y); + SDLTest_AssertCheck(result == cases[i].result, "Got %d, expected %d", result, cases[i].result); + if (cases[i].result) { + SDLTest_AssertCheck(IsFPointEqual(&p1, &cases[i].expected1), + "Got p1={ %g, %g }, expected p1={ %g, %g }", + p1.x, p1.y, + cases[i].expected1.x, cases[i].expected1.y); + SDLTest_AssertCheck(IsFPointEqual(&p2, &cases[i].expected2), + "Got p2={ %g, %g }, expected p2={ %g, %g }", + p2.x, p2.y, + cases[i].expected2.x, cases[i].expected2.y); + } + } + return TEST_COMPLETED; +} + /* ================= Test References ================== */ /* Rect test cases */ @@ -2097,6 +2344,26 @@ static const SDLTest_TestCaseReference rectTestFRectEqualsParam = { rect_testFRectEqualsParam, "rect_testFRectEqualsParam", "Negative tests against SDL_RectsEqualFloat with invalid parameters", TEST_ENABLED }; +static const SDLTest_TestCaseReference rectTestHasIntersectionFloat = { + rect_testHasIntersectionFloat, "rect_testHasIntersectionFloat", "Tests SDL_HasRectIntersectionFloat", TEST_ENABLED +}; + +static const SDLTest_TestCaseReference rectTestGetRectIntersectionFloat = { + rect_testGetRectIntersectionFloat, "rect_testGetRectIntersectionFloat", "Tests SDL_GetRectIntersectionFloat", TEST_ENABLED +}; + +static const SDLTest_TestCaseReference rectTestGetRectUntionFloat = { + rect_testGetRectUntionFloat, "rect_testGetRectUntionFloat", "Tests SDL_GetRectUnionFloat", TEST_ENABLED +}; + +static const SDLTest_TestCaseReference rectTestGetRectEnclosingPointsFloat = { + rect_testGetRectEnclosingPointsFloat, "rect_testGetRectEnclosingPointsFloat", "Tests SDL_GetRectEnclosingPointsFloat", TEST_ENABLED +}; + +static const SDLTest_TestCaseReference rectTestGetRectAndLineIntersectionFloat = { + rect_testGetRectAndLineIntersectionFloat, "rect_testGetRectAndLineIntersectionFloat", "Tests SDL_GetRectAndLineIntersectionFloat", TEST_ENABLED +}; + /** * Sequence of Rect test cases; functions that handle simple rectangles including overlaps and merges. */ @@ -2136,6 +2403,11 @@ static const SDLTest_TestCaseReference *rectTests[] = { &rectTestRectEqualsParam, &rectTestFRectEquals, &rectTestFRectEqualsParam, + &rectTestHasIntersectionFloat, + &rectTestGetRectIntersectionFloat, + &rectTestGetRectUntionFloat, + &rectTestGetRectEnclosingPointsFloat, + &rectTestGetRectAndLineIntersectionFloat, NULL }; diff --git a/libs/SDL3/test/testautomation_render.c b/libs/SDL3/test/testautomation_render.c index 9a0503da..714a34ac 100644 --- a/libs/SDL3/test/testautomation_render.c +++ b/libs/SDL3/test/testautomation_render.c @@ -616,6 +616,189 @@ static int SDLCALL render_testBlit9Grid(void *arg) return TEST_COMPLETED; } +/** + * Tests tiled 9-grid blitting. + */ +static int SDLCALL render_testBlit9GridTiled(void *arg) +{ + SDL_Surface *referenceSurface = NULL; + SDL_Surface *source = NULL; + SDL_Texture *texture; + int x, y; + SDL_FRect rect; + int ret = 0; + + /* Create source surface */ + source = SDL_CreateSurface(3, 3, SDL_PIXELFORMAT_RGBA32); + SDLTest_AssertCheck(source != NULL, "Verify source surface is not NULL"); + for (y = 0; y < 3; ++y) { + for (x = 0; x < 3; ++x) { + SDL_WriteSurfacePixel(source, x, y, (Uint8)((1 + x) * COLOR_SEPARATION), (Uint8)((1 + y) * COLOR_SEPARATION), 0, 255); + } + } + texture = SDL_CreateTextureFromSurface(renderer, source); + SDLTest_AssertCheck(texture != NULL, "Verify source texture is not NULL"); + ret = SDL_SetTextureScaleMode(texture, SDL_SCALEMODE_NEAREST); + SDLTest_AssertCheck(ret == true, "Validate results from call to SDL_SetTextureScaleMode, expected: true, got: %i", ret); + + /* Tiled 9-grid blit - 1.0 scale */ + { + SDLTest_Log("tiled 9-grid blit - 1.0 scale"); + /* Create reference surface */ + SDL_DestroySurface(referenceSurface); + referenceSurface = SDL_CreateSurface(TESTRENDER_SCREEN_W, TESTRENDER_SCREEN_H, SDL_PIXELFORMAT_RGBA32); + SDLTest_AssertCheck(referenceSurface != NULL, "Verify reference surface is not NULL"); + Fill9GridReferenceSurface(referenceSurface, 1, 1, 1, 1); + + /* Clear surface. */ + clearScreen(); + + /* Tiled blit. */ + rect.x = 0.0f; + rect.y = 0.0f; + rect.w = (float)TESTRENDER_SCREEN_W; + rect.h = (float)TESTRENDER_SCREEN_H; + ret = SDL_RenderTexture9GridTiled(renderer, texture, NULL, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, &rect, 1.0f); + SDLTest_AssertCheck(ret == true, "Validate results from call to SDL_RenderTexture9GridTiled, expected: true, got: %i", ret); + + /* See if it's the same */ + compare(referenceSurface, ALLOWABLE_ERROR_OPAQUE); + + /* Make current */ + SDL_RenderPresent(renderer); + } + + /* Tiled 9-grid blit - 2.0 scale */ + { + SDLTest_Log("tiled 9-grid blit - 2.0 scale"); + /* Create reference surface */ + SDL_DestroySurface(referenceSurface); + referenceSurface = SDL_CreateSurface(TESTRENDER_SCREEN_W, TESTRENDER_SCREEN_H, SDL_PIXELFORMAT_RGBA32); + SDLTest_AssertCheck(referenceSurface != NULL, "Verify reference surface is not NULL"); + Fill9GridReferenceSurface(referenceSurface, 2, 2, 2, 2); + + /* Clear surface. */ + clearScreen(); + + /* Tiled blit. */ + rect.x = 0.0f; + rect.y = 0.0f; + rect.w = (float)TESTRENDER_SCREEN_W; + rect.h = (float)TESTRENDER_SCREEN_H; + ret = SDL_RenderTexture9GridTiled(renderer, texture, NULL, 1.0f, 1.0f, 1.0f, 1.0f, 2.0f, &rect, 2.0f); + SDLTest_AssertCheck(ret == true, "Validate results from call to SDL_RenderTexture9GridTiled, expected: true, got: %i", ret); + + /* See if it's the same */ + compare(referenceSurface, ALLOWABLE_ERROR_OPAQUE); + + /* Make current */ + SDL_RenderPresent(renderer); + } + + /* Clean up. */ + SDL_DestroySurface(source); + SDL_DestroyTexture(texture); + + /* Create complex source surface */ + source = SDL_CreateSurface(5, 5, SDL_PIXELFORMAT_RGBA32); + SDLTest_AssertCheck(source != NULL, "Verify source surface is not NULL"); + SDL_WriteSurfacePixel(source, 0, 0, (Uint8)((1) * COLOR_SEPARATION), (Uint8)((1) * COLOR_SEPARATION), 0, 255); + SDL_WriteSurfacePixel(source, 1, 0, (Uint8)((2) * COLOR_SEPARATION), (Uint8)((1) * COLOR_SEPARATION), 0, 255); + SDL_WriteSurfacePixel(source, 2, 0, (Uint8)((2) * COLOR_SEPARATION), (Uint8)((1) * COLOR_SEPARATION), 0, 255); + SDL_WriteSurfacePixel(source, 3, 0, (Uint8)((3) * COLOR_SEPARATION), (Uint8)((1) * COLOR_SEPARATION), 0, 255); + SDL_WriteSurfacePixel(source, 4, 0, (Uint8)((3) * COLOR_SEPARATION), (Uint8)((1) * COLOR_SEPARATION), 0, 255); + + SDL_WriteSurfacePixel(source, 0, 1, (Uint8)((1) * COLOR_SEPARATION), (Uint8)((2) * COLOR_SEPARATION), 0, 255); + SDL_WriteSurfacePixel(source, 1, 1, (Uint8)((2) * COLOR_SEPARATION), (Uint8)((2) * COLOR_SEPARATION), 0, 255); + SDL_WriteSurfacePixel(source, 2, 1, (Uint8)((2) * COLOR_SEPARATION), (Uint8)((2) * COLOR_SEPARATION), 0, 255); + SDL_WriteSurfacePixel(source, 3, 1, (Uint8)((3) * COLOR_SEPARATION), (Uint8)((2) * COLOR_SEPARATION), 0, 255); + SDL_WriteSurfacePixel(source, 4, 1, (Uint8)((3) * COLOR_SEPARATION), (Uint8)((2) * COLOR_SEPARATION), 0, 255); + + SDL_WriteSurfacePixel(source, 0, 2, (Uint8)((1) * COLOR_SEPARATION), (Uint8)((2) * COLOR_SEPARATION), 0, 255); + SDL_WriteSurfacePixel(source, 1, 2, (Uint8)((2) * COLOR_SEPARATION), (Uint8)((2) * COLOR_SEPARATION), 0, 255); + SDL_WriteSurfacePixel(source, 2, 2, (Uint8)((2) * COLOR_SEPARATION), (Uint8)((2) * COLOR_SEPARATION), 0, 255); + SDL_WriteSurfacePixel(source, 3, 2, (Uint8)((3) * COLOR_SEPARATION), (Uint8)((2) * COLOR_SEPARATION), 0, 255); + SDL_WriteSurfacePixel(source, 4, 2, (Uint8)((3) * COLOR_SEPARATION), (Uint8)((2) * COLOR_SEPARATION), 0, 255); + + SDL_WriteSurfacePixel(source, 0, 3, (Uint8)((1) * COLOR_SEPARATION), (Uint8)((3) * COLOR_SEPARATION), 0, 255); + SDL_WriteSurfacePixel(source, 1, 3, (Uint8)((2) * COLOR_SEPARATION), (Uint8)((3) * COLOR_SEPARATION), 0, 255); + SDL_WriteSurfacePixel(source, 2, 3, (Uint8)((2) * COLOR_SEPARATION), (Uint8)((3) * COLOR_SEPARATION), 0, 255); + SDL_WriteSurfacePixel(source, 3, 3, (Uint8)((3) * COLOR_SEPARATION), (Uint8)((3) * COLOR_SEPARATION), 0, 255); + SDL_WriteSurfacePixel(source, 4, 3, (Uint8)((3) * COLOR_SEPARATION), (Uint8)((3) * COLOR_SEPARATION), 0, 255); + + SDL_WriteSurfacePixel(source, 0, 4, (Uint8)((1) * COLOR_SEPARATION), (Uint8)((3) * COLOR_SEPARATION), 0, 255); + SDL_WriteSurfacePixel(source, 1, 4, (Uint8)((2) * COLOR_SEPARATION), (Uint8)((3) * COLOR_SEPARATION), 0, 255); + SDL_WriteSurfacePixel(source, 2, 4, (Uint8)((2) * COLOR_SEPARATION), (Uint8)((3) * COLOR_SEPARATION), 0, 255); + SDL_WriteSurfacePixel(source, 3, 4, (Uint8)((3) * COLOR_SEPARATION), (Uint8)((3) * COLOR_SEPARATION), 0, 255); + SDL_WriteSurfacePixel(source, 4, 4, (Uint8)((3) * COLOR_SEPARATION), (Uint8)((3) * COLOR_SEPARATION), 0, 255); + + texture = SDL_CreateTextureFromSurface(renderer, source); + SDLTest_AssertCheck(texture != NULL, "Verify source texture is not NULL"); + ret = SDL_SetTextureScaleMode(texture, SDL_SCALEMODE_NEAREST); + SDLTest_AssertCheck(ret == true, "Validate results from call to SDL_SetTextureScaleMode, expected: true, got: %i", ret); + + /* complex tiled 9-grid blit - 1.0 scale */ + { + SDLTest_Log("complex tiled 9-grid blit - 1.0 scale"); + /* Create reference surface */ + SDL_DestroySurface(referenceSurface); + referenceSurface = SDL_CreateSurface(TESTRENDER_SCREEN_W, TESTRENDER_SCREEN_H, SDL_PIXELFORMAT_RGBA32); + SDLTest_AssertCheck(referenceSurface != NULL, "Verify reference surface is not NULL"); + Fill9GridReferenceSurface(referenceSurface, 1, 2, 1, 2); + + /* Clear surface. */ + clearScreen(); + + /* Tiled blit. */ + rect.x = 0.0f; + rect.y = 0.0f; + rect.w = (float)TESTRENDER_SCREEN_W; + rect.h = (float)TESTRENDER_SCREEN_H; + ret = SDL_RenderTexture9GridTiled(renderer, texture, NULL, 1.0f, 2.0f, 1.0f, 2.0f, 1.0f, &rect, 1.0f); + SDLTest_AssertCheck(ret == true, "Validate results from call to SDL_RenderTexture9GridTiled, expected: true, got: %i", ret); + + /* See if it's the same */ + compare(referenceSurface, ALLOWABLE_ERROR_OPAQUE); + + /* Make current */ + SDL_RenderPresent(renderer); + } + + /* complex tiled 9-grid blit - 2.0 scale */ + { + SDLTest_Log("complex tiled 9-grid blit - 2.0 scale"); + /* Create reference surface */ + SDL_DestroySurface(referenceSurface); + referenceSurface = SDL_CreateSurface(TESTRENDER_SCREEN_W, TESTRENDER_SCREEN_H, SDL_PIXELFORMAT_RGBA32); + SDLTest_AssertCheck(referenceSurface != NULL, "Verify reference surface is not NULL"); + Fill9GridReferenceSurface(referenceSurface, 2, 4, 2, 4); + + /* Clear surface. */ + clearScreen(); + + /* Tiled blit. */ + rect.x = 0.0f; + rect.y = 0.0f; + rect.w = (float)TESTRENDER_SCREEN_W; + rect.h = (float)TESTRENDER_SCREEN_H; + ret = SDL_RenderTexture9GridTiled(renderer, texture, NULL, 1.0f, 2.0f, 1.0f, 2.0f, 2.0f, &rect, 2.0f); + SDLTest_AssertCheck(ret == true, "Validate results from call to SDL_RenderTexture9GridTiled, expected: true, got: %i", ret); + + /* See if it's the same */ + compare(referenceSurface, ALLOWABLE_ERROR_OPAQUE); + + /* Make current */ + SDL_RenderPresent(renderer); + } + + /* Clean up. */ + SDL_DestroySurface(referenceSurface); + SDL_DestroySurface(source); + SDL_DestroyTexture(texture); + + return TEST_COMPLETED; +} + /** * Blits doing color tests. * @@ -1017,6 +1200,129 @@ static int SDLCALL render_testViewport(void *arg) return TEST_COMPLETED; } +static int SDLCALL render_testRGBSurfaceNoAlpha(void* arg) +{ + SDL_Surface *surface; + SDL_Renderer *software_renderer; + SDL_Surface *surface2; + SDL_Texture *texture2; + bool result; + SDL_FRect dest_rect; + SDL_FPoint point; + const SDL_PixelFormatDetails *format_details; + Uint8 r, g, b, a; + + SDLTest_AssertPass("About to call SDL_CreateSurface(128, 128, SDL_PIXELFORMAT_RGBX32)"); + surface = SDL_CreateSurface(128, 128, SDL_PIXELFORMAT_RGBX32); + SDLTest_AssertCheck(surface != NULL, "Returned surface must be not NULL"); + if (surface == NULL) { + return TEST_ABORTED; + } + + SDLTest_AssertPass("About to call SDL_GetPixelFormatDetails(surface->format)"); + format_details = SDL_GetPixelFormatDetails(surface->format); + SDLTest_AssertCheck(format_details != NULL, "Result must be non-NULL, is %p", format_details); + if (format_details == NULL) { + SDL_DestroySurface(surface); + return TEST_ABORTED; + } + + SDLTest_AssertCheck(format_details->bits_per_pixel == 32, "format_details->bits_per_pixel is %d, should be %d", format_details->bits_per_pixel, 32); + SDLTest_AssertCheck(format_details->bytes_per_pixel == 4, "format_details->bytes_per_pixel is %d, should be %d", format_details->bytes_per_pixel, 4); + + SDLTest_AssertPass("About to call SDL_CreateSoftwareRenderer(surface)"); + software_renderer = SDL_CreateSoftwareRenderer(surface); + SDLTest_AssertCheck(software_renderer != NULL, "Returned renderer must be not NULL"); + if (software_renderer == NULL) { + SDL_DestroySurface(surface); + return TEST_ABORTED; + } + + SDLTest_AssertPass("About to call SDL_CreateSurface(16, 16, SDL_PIXELFORMAT_RGBX32)"); + surface2 = SDL_CreateSurface(16, 16, SDL_PIXELFORMAT_RGBX32); + SDLTest_AssertCheck(surface2 != NULL, "Returned surface must be not NULL"); + if (surface2 == NULL) { + SDL_DestroySurface(surface); + SDL_DestroyRenderer(software_renderer); + return TEST_ABORTED; + } + + SDLTest_AssertPass("About to call SDL_FillRect(surface2, NULL, 0)"); + result = SDL_FillSurfaceRect(surface2, NULL, SDL_MapRGB(SDL_GetPixelFormatDetails(surface2->format), NULL, 0, 0, 0)); + SDLTest_AssertCheck(result == true, "Result is %d, should be %d", result, true); + + SDLTest_AssertPass("About to call SDL_CreateTextureFromSurface(software_renderer, surface2)"); + texture2 = SDL_CreateTextureFromSurface(software_renderer, surface2); + SDLTest_AssertCheck(texture2 != NULL, "Returned texture is not NULL"); + + SDLTest_AssertPass("About to call SDL_SetRenderDrawColor(renderer, 0xaa, 0xbb, 0xcc, 0x0)"); + result = SDL_SetRenderDrawColor(software_renderer, 0xaa, 0xbb, 0xcc, 0x0); + SDLTest_AssertCheck(result == true, "Result is %d, should be %d", result, true); + + SDLTest_AssertPass("About to call SDL_RenderClear(renderer)"); + result = SDL_RenderClear(software_renderer); + SDLTest_AssertCheck(result == true, "Result is %d, should be %d", result, true); + + SDLTest_AssertPass("About to call SDL_SetRenderDrawColor(renderer, 0x0, 0x0, 0x0, 0x0)"); + result = SDL_SetRenderDrawColor(software_renderer, 0x0, 0x0, 0x0, 0x0); + SDLTest_AssertCheck(result == true, "Result is %d, should be %d", result, true); + + dest_rect.x = 32; + dest_rect.y = 32; + dest_rect.w = (float)surface2->w; + dest_rect.h = (float)surface2->h; + point.x = 0; + point.y = 0; + SDLTest_AssertPass("About to call SDL_RenderCopy(software_renderer, texture, NULL, &{%g, %g, %g, %g})", + dest_rect.x, dest_rect.h, dest_rect.w, dest_rect.h); + result = SDL_RenderTextureRotated(software_renderer, texture2, NULL, &dest_rect, 180, &point, SDL_FLIP_NONE); + SDLTest_AssertCheck(result == true, "Result is %d, should be %d", result, true); + + SDLTest_AssertPass("About to call SDL_RenderPresent(software_renderer)"); + SDL_RenderPresent(software_renderer); + + SDLTest_AssertPass("About to call SDL_ReadSurfacePixel(0, 0)"); + result = SDL_ReadSurfacePixel(surface, 0, 0, &r, &g, &b, &a); + SDLTest_AssertCheck(result == true, "Result is %d, should be %d", result, true); + SDLTest_AssertCheck(r == 0xaa && g == 0xbb && b == 0xcc && a == SDL_ALPHA_OPAQUE, + "Pixel at (0, 0) is {0x%02x,0x%02x,0x%02x,0x%02x}, should be {0x%02x,0x%02x,0x%02x,0x%02x}", + r, g, b, a, 0xaa, 0xbb, 0xcc, SDL_ALPHA_OPAQUE); + + SDLTest_AssertPass("About to call SDL_ReadSurfacePixel(15, 15)"); + result = SDL_ReadSurfacePixel(surface, 15, 15, &r, &g, &b, &a); + SDLTest_AssertCheck(result == true, "Result is %d, should be %d", result, true); + SDLTest_AssertCheck(r == 0xaa && g == 0xbb && b == 0xcc && a == SDL_ALPHA_OPAQUE, + "Pixel at (0, 0) is {0x%02x,0x%02x,0x%02x,0x%02x}, should be {0x%02x,0x%02x,0x%02x,0x%02x}", + r, g, b, a, 0xaa, 0xbb, 0xcc, SDL_ALPHA_OPAQUE); + + SDLTest_AssertPass("About to call SDL_ReadSurfacePixel(16, 16)"); + result = SDL_ReadSurfacePixel(surface, 16, 16, &r, &g, &b, &a); + SDLTest_AssertCheck(result == true, "Result is %d, should be %d", result, true); + SDLTest_AssertCheck(r == 0x00 && g == 0x00 && b == 0x00 && a == SDL_ALPHA_OPAQUE, + "Pixel at (0, 0) is {0x%02x,0x%02x,0x%02x,0x%02x}, should be {0x%02x,0x%02x,0x%02x,0x%02x}", + r, g, b, a, 0x00, 0x00, 0x00, SDL_ALPHA_OPAQUE); + + SDLTest_AssertPass("About to call SDL_ReadSurfacePixel(31, 31)"); + result = SDL_ReadSurfacePixel(surface, 31, 31, &r, &g, &b, &a); + SDLTest_AssertCheck(result == true, "Result is %d, should be %d", result, true); + SDLTest_AssertCheck(r == 0x00 && g == 0x00 && b == 0x00 && a == SDL_ALPHA_OPAQUE, + "Pixel at (0, 0) is {0x%02x,0x%02x,0x%02x,0x%02x}, should be {0x%02x,0x%02x,0x%02x,0x%02x}", + r, g, b, a, 0x00, 0x00, 0x00, SDL_ALPHA_OPAQUE); + + SDLTest_AssertPass("About to call SDL_ReadSurfacePixel(32, 32)"); + result = SDL_ReadSurfacePixel(surface, 32, 32, &r, &g, &b, &a); + SDLTest_AssertCheck(result == true, "Result is %d, should be %d", result, true); + SDLTest_AssertCheck(r == 0xaa && g == 0xbb && b == 0xcc && a == SDL_ALPHA_OPAQUE, + "Pixel at (0, 0) is {0x%02x,0x%02x,0x%02x,0x%02x}, should be {0x%02x,0x%02x,0x%02x,0x%02x}", + r, g, b, a, 0xaa, 0xbb, 0xcc, SDL_ALPHA_OPAQUE); + + SDL_DestroyTexture(texture2); + SDL_DestroySurface(surface2); + SDL_DestroyRenderer(software_renderer); + SDL_DestroySurface(surface); + return TEST_COMPLETED; +} + /** * Test clip rect */ @@ -1226,6 +1532,46 @@ static int SDLCALL render_testLogicalSize(void *arg) return TEST_COMPLETED; } +/** + * @brief Tests setting and getting texture scale mode. + * + * \sa + * http://wiki.libsdl.org/SDL2/SDL_SetTextureScaleMode + * http://wiki.libsdl.org/SDL2/SDL_GetTextureScaleMode + */ +static int SDLCALL render_testGetSetTextureScaleMode(void *arg) +{ + const struct { + const char *name; + SDL_ScaleMode mode; + } modes[] = { + { "SDL_SCALEMODE_NEAREST", SDL_SCALEMODE_NEAREST }, + { "SDL_SCALEMODE_LINEAR", SDL_SCALEMODE_LINEAR }, + { "SDL_SCALEMODE_PIXELART", SDL_SCALEMODE_PIXELART }, + }; + size_t i; + + for (i = 0; i < SDL_arraysize(modes); i++) { + SDL_Texture *texture; + bool result; + SDL_ScaleMode actual_mode = SDL_SCALEMODE_NEAREST; + + SDL_ClearError(); + SDLTest_AssertPass("About to call SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, 16, 16)"); + texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, 16, 16); + SDLTest_AssertCheck(texture != NULL, "SDL_CreateTexture must return a non-NULL texture"); + SDLTest_AssertPass("About to call SDL_SetTextureScaleMode(texture, %s)", modes[i].name); + result = SDL_SetTextureScaleMode(texture, modes[i].mode); + SDLTest_AssertCheck(result == true, "SDL_SetTextureScaleMode returns %d, expected %d", result, true); + SDLTest_AssertPass("About to call SDL_GetTextureScaleMode(texture)"); + result = SDL_GetTextureScaleMode(texture, &actual_mode); + SDLTest_AssertCheck(result == true, "SDL_SetTextureScaleMode returns %d, expected %d", result, true); + SDLTest_AssertCheck(actual_mode == modes[i].mode, "SDL_GetTextureScaleMode must return %s (%d), actual=%d", + modes[i].name, modes[i].mode, actual_mode); + } + return TEST_COMPLETED; +} + /* Helper functions */ /** @@ -1665,6 +2011,10 @@ static const SDLTest_TestCaseReference renderTestBlit9Grid = { render_testBlit9Grid, "render_testBlit9Grid", "Tests 9-grid blitting", TEST_ENABLED }; +static const SDLTest_TestCaseReference renderTestBlit9GridTiled = { + render_testBlit9GridTiled, "render_testBlit9GridTiled", "Tests tiled 9-grid blitting", TEST_ENABLED +}; + static const SDLTest_TestCaseReference renderTestBlitColor = { render_testBlitColor, "render_testBlitColor", "Tests blitting with color", TEST_ENABLED }; @@ -1693,6 +2043,14 @@ static const SDLTest_TestCaseReference renderTestTextureState = { render_testTextureState, "render_testTextureState", "Tests texture state changes", TEST_ENABLED }; +static const SDLTest_TestCaseReference renderTestGetSetTextureScaleMode = { + render_testGetSetTextureScaleMode, "render_testGetSetTextureScaleMode", "Tests setting/getting texture scale mode", TEST_ENABLED +}; + +static const SDLTest_TestCaseReference renderTestRGBSurfaceNoAlpha = { + render_testRGBSurfaceNoAlpha, "render_testRGBSurfaceNoAlpha", "Tests RGB surface with no alpha using software renderer", TEST_ENABLED +}; + /* Sequence of Render test cases */ static const SDLTest_TestCaseReference *renderTests[] = { &renderTestGetNumRenderDrivers, @@ -1701,6 +2059,7 @@ static const SDLTest_TestCaseReference *renderTests[] = { &renderTestBlit, &renderTestBlitTiled, &renderTestBlit9Grid, + &renderTestBlit9GridTiled, &renderTestBlitColor, &renderTestBlendModes, &renderTestViewport, @@ -1708,6 +2067,8 @@ static const SDLTest_TestCaseReference *renderTests[] = { &renderTestLogicalSize, &renderTestUVWrapping, &renderTestTextureState, + &renderTestGetSetTextureScaleMode, + &renderTestRGBSurfaceNoAlpha, NULL }; diff --git a/libs/SDL3/test/testautomation_surface.c b/libs/SDL3/test/testautomation_surface.c index 014fca79..d3382309 100644 --- a/libs/SDL3/test/testautomation_surface.c +++ b/libs/SDL3/test/testautomation_surface.c @@ -959,6 +959,36 @@ static int SDLCALL surface_testBlitBlendMul(void *arg) return TEST_COMPLETED; } +/** + * Tests blitting invalid surfaces. + */ +static int SDLCALL surface_testBlitInvalid(void *arg) +{ + SDL_Surface *valid, *invalid; + bool result; + + valid = SDL_CreateSurface(1, 1, SDL_PIXELFORMAT_RGBA8888); + SDLTest_AssertCheck(valid != NULL, "Check surface creation"); + invalid = SDL_CreateSurface(0, 0, SDL_PIXELFORMAT_RGBA8888); + SDLTest_AssertCheck(invalid != NULL, "Check surface creation"); + SDLTest_AssertCheck(invalid->pixels == NULL, "Check surface pixels are NULL"); + + result = SDL_BlitSurface(invalid, NULL, valid, NULL); + SDLTest_AssertCheck(result == true, "SDL_BlitSurface(invalid, NULL, valid, NULL), result = %s\n", result ? "true" : "false"); + result = SDL_BlitSurface(valid, NULL, invalid, NULL); + SDLTest_AssertCheck(result == true, "SDL_BlitSurface(valid, NULL, invalid, NULL), result = %s\n", result ? "true" : "false"); + + result = SDL_BlitSurfaceScaled(invalid, NULL, valid, NULL, SDL_SCALEMODE_NEAREST); + SDLTest_AssertCheck(result == false, "SDL_BlitSurfaceScaled(invalid, NULL, valid, NULL, SDL_SCALEMODE_NEAREST), result = %s\n", result ? "true" : "false"); + result = SDL_BlitSurfaceScaled(valid, NULL, invalid, NULL, SDL_SCALEMODE_NEAREST); + SDLTest_AssertCheck(result == false, "SDL_BlitSurfaceScaled(valid, NULL, invalid, NULL, SDL_SCALEMODE_NEAREST), result = %s\n", result ? "true" : "false"); + + SDL_DestroySurface(valid); + SDL_DestroySurface(invalid); + + return TEST_COMPLETED; +} + static int SDLCALL surface_testOverflow(void *arg) { char buf[1024]; @@ -1506,7 +1536,7 @@ static int SDLCALL surface_testScale(void *arg) SDL_PIXELFORMAT_ARGB128_FLOAT, SDL_PIXELFORMAT_RGBA128_FLOAT, }; SDL_ScaleMode modes[] = { - SDL_SCALEMODE_NEAREST, SDL_SCALEMODE_LINEAR + SDL_SCALEMODE_NEAREST, SDL_SCALEMODE_LINEAR, SDL_SCALEMODE_PIXELART }; SDL_Surface *surface, *result; SDL_PixelFormat format; @@ -1543,7 +1573,9 @@ static int SDLCALL surface_testScale(void *arg) deltaA <= MAXIMUM_ERROR, "Checking %s %s scaling results, expected %.4f,%.4f,%.4f,%.4f got %.4f,%.4f,%.4f,%.4f", SDL_GetPixelFormatName(format), - mode == SDL_SCALEMODE_NEAREST ? "nearest" : "linear", + mode == SDL_SCALEMODE_NEAREST ? "nearest" : + mode == SDL_SCALEMODE_LINEAR ? "linear" : + mode == SDL_SCALEMODE_PIXELART ? "pixelart" : "unknown", srcR, srcG, srcB, srcA, actualR, actualG, actualB, actualA); SDL_DestroySurface(surface); @@ -1630,6 +1662,10 @@ static const SDLTest_TestCaseReference surfaceTestBlitBlendMul = { surface_testBlitBlendMul, "surface_testBlitBlendMul", "Tests blitting routines with mul blending mode.", TEST_ENABLED }; +static const SDLTest_TestCaseReference surfaceTestBlitInvalid = { + surface_testBlitInvalid, "surface_testBlitInvalid", "Tests blitting routines with invalid surfaces.", TEST_ENABLED +}; + static const SDLTest_TestCaseReference surfaceTestOverflow = { surface_testOverflow, "surface_testOverflow", "Test overflow detection.", TEST_ENABLED }; @@ -1678,6 +1714,7 @@ static const SDLTest_TestCaseReference *surfaceTests[] = { &surfaceTestBlitBlendAddPremultiplied, &surfaceTestBlitBlendMod, &surfaceTestBlitBlendMul, + &surfaceTestBlitInvalid, &surfaceTestOverflow, &surfaceTestFlip, &surfaceTestPalette, diff --git a/libs/SDL3/test/testautomation_video.c b/libs/SDL3/test/testautomation_video.c index 09c55e78..b2bddaa7 100644 --- a/libs/SDL3/test/testautomation_video.c +++ b/libs/SDL3/test/testautomation_video.c @@ -2444,6 +2444,33 @@ static int SDLCALL video_getWindowSurface(void *arg) return TEST_COMPLETED; } +/** + * Tests SDL_RaiseWindow + */ +static int SDLCALL video_raiseWindow(void *arg) +{ + bool result; + SDL_Window *window; + + SDLTest_AssertPass("SDL_SetWindowInputFocus is not supported on dummy and SDL2 wayland driver"); + if (SDL_strcmp(SDL_GetCurrentVideoDriver(), "dummy") == 0 || SDL_strcmp(SDL_GetCurrentVideoDriver(), "wayland") == 0) { + return TEST_SKIPPED; + } + + window = createVideoSuiteTestWindow("video_raiseWindow"); + if (!window) { + return TEST_ABORTED; + } + + SDLTest_AssertPass("About to call SDL_RaiseWindow(window)"); + result = SDL_RaiseWindow(window); + SDLTest_AssertCheck(result, "Result is %d, expected 1 (%s)", result, SDL_GetError()); + + destroyVideoSuiteTestWindow(window); + + return TEST_COMPLETED; +} + /* ================= Test References ================== */ /* Video test cases */ @@ -2534,6 +2561,9 @@ static const SDLTest_TestCaseReference videoTestCreateMaximized = { static const SDLTest_TestCaseReference videoTestGetWindowSurface = { video_getWindowSurface, "video_getWindowSurface", "Checks window surface functionality", TEST_ENABLED }; +static const SDLTest_TestCaseReference videoTestRaiseWindow = { + video_raiseWindow, "video_raiseWindow", "Checks window focus", TEST_ENABLED +}; /* Sequence of Video test cases */ static const SDLTest_TestCaseReference *videoTests[] = { @@ -2559,6 +2589,7 @@ static const SDLTest_TestCaseReference *videoTests[] = { &videoTestCreateMinimized, &videoTestCreateMaximized, &videoTestGetWindowSurface, + &videoTestRaiseWindow, NULL }; diff --git a/libs/SDL3/test/testcontroller.c b/libs/SDL3/test/testcontroller.c index b717b30e..7602253f 100644 --- a/libs/SDL3/test/testcontroller.c +++ b/libs/SDL3/test/testcontroller.c @@ -32,12 +32,9 @@ #define TITLE_HEIGHT 48.0f #define PANEL_SPACING 25.0f #define PANEL_WIDTH 250.0f -#define MINIMUM_BUTTON_WIDTH 96.0f -#define BUTTON_MARGIN 16.0f -#define BUTTON_PADDING 12.0f #define GAMEPAD_WIDTH 512.0f #define GAMEPAD_HEIGHT 560.0f - +#define BUTTON_MARGIN 16.0f #define SCREEN_WIDTH (PANEL_WIDTH + PANEL_SPACING + GAMEPAD_WIDTH + PANEL_SPACING + PANEL_WIDTH) #define SCREEN_HEIGHT (TITLE_HEIGHT + GAMEPAD_HEIGHT) @@ -49,6 +46,228 @@ typedef struct int m_nFarthestValue; } AxisState; +struct Quaternion +{ + float x, y, z, w; +}; + +static Quaternion quat_identity = { 0.0f, 0.0f, 0.0f, 1.0f }; + +Quaternion QuaternionFromEuler(float roll, float pitch, float yaw) +{ + Quaternion q; + float cy = SDL_cosf(yaw * 0.5f); + float sy = SDL_sinf(yaw * 0.5f); + float cp = SDL_cosf(pitch * 0.5f); + float sp = SDL_sinf(pitch * 0.5f); + float cr = SDL_cosf(roll * 0.5f); + float sr = SDL_sinf(roll * 0.5f); + + q.w = cr * cp * cy + sr * sp * sy; + q.x = sr * cp * cy - cr * sp * sy; + q.y = cr * sp * cy + sr * cp * sy; + q.z = cr * cp * sy - sr * sp * cy; + + return q; +} + +static void EulerFromQuaternion(Quaternion q, float *roll, float *pitch, float *yaw) +{ + float sinr_cosp = 2.0f * (q.w * q.x + q.y * q.z); + float cosr_cosp = 1.0f - 2.0f * (q.x * q.x + q.y * q.y); + float roll_rad = SDL_atan2f(sinr_cosp, cosr_cosp); + + float sinp = 2.0f * (q.w * q.y - q.z * q.x); + float pitch_rad; + if (SDL_fabsf(sinp) >= 1.0f) { + pitch_rad = SDL_copysignf(SDL_PI_F / 2.0f, sinp); + } else { + pitch_rad = SDL_asinf(sinp); + } + + float siny_cosp = 2.0f * (q.w * q.z + q.x * q.y); + float cosy_cosp = 1.0f - 2.0f * (q.y * q.y + q.z * q.z); + float yaw_rad = SDL_atan2f(siny_cosp, cosy_cosp); + + if (roll) + *roll = roll_rad; + if (pitch) + *pitch = pitch_rad; + if (yaw) + *yaw = yaw_rad; +} + +static void EulerDegreesFromQuaternion(Quaternion q, float *pitch, float *yaw, float *roll) +{ + float pitch_rad, yaw_rad, roll_rad; + EulerFromQuaternion(q, &pitch_rad, &yaw_rad, &roll_rad); + if (pitch) { + *pitch = pitch_rad * (180.0f / SDL_PI_F); + } + if (yaw) { + *yaw = yaw_rad * (180.0f / SDL_PI_F); + } + if (roll) { + *roll = roll_rad * (180.0f / SDL_PI_F); + } +} + +Quaternion MultiplyQuaternion(Quaternion a, Quaternion b) +{ + Quaternion q; + q.x = a.x * b.w + a.y * b.z - a.z * b.y + a.w * b.x; + q.y = -a.x * b.z + a.y * b.w + a.z * b.x + a.w * b.y; + q.z = a.x * b.y - a.y * b.x + a.z * b.w + a.w * b.z; + q.w = -a.x * b.x - a.y * b.y - a.z * b.z + a.w * b.w; + return q; +} + +void NormalizeQuaternion(Quaternion *q) +{ + float mag = SDL_sqrtf(q->x * q->x + q->y * q->y + q->z * q->z + q->w * q->w); + if (mag > 0.0f) { + q->x /= mag; + q->y /= mag; + q->z /= mag; + q->w /= mag; + } +} + +float Normalize180(float angle) +{ + angle = SDL_fmodf(angle + 180.0f, 360.0f); + if (angle < 0.0f) { + angle += 360.0f; + } + return angle - 180.0f; +} + +typedef struct +{ + Uint64 gyro_packet_number; + Uint64 accelerometer_packet_number; + /* When both gyro and accelerometer events have been processed, we can increment this and use it to calculate polling rate over time.*/ + Uint64 imu_packet_counter; + + Uint64 starting_time_stamp_ns; /* Use this to help estimate how many packets are received over a duration */ + Uint16 imu_estimated_sensor_rate; /* in Hz, used to estimate how many packets are received over a duration */ + + Uint64 last_sensor_time_stamp_ns;/* Comes from the event data/HID implementation. Official PS5/Edge gives true hardware time stamps. Others are simulated. Nanoseconds i.e. 1e9 */ + + /* Fresh data copied from sensor events. */ + float accel_data[3]; /* Meters per second squared, i.e. 9.81f means 9.81 meters per second squared */ + float gyro_data[3]; /* Degrees per second, i.e. 100.0f means 100 degrees per second */ + + float last_accel_data[3];/* Needed to detect motion (and inhibit drift calibration) */ + float accelerometer_length_squared; + float gyro_drift_accumulator[3]; + bool is_calibrating_drift; /* Starts on, but can be turned back on by the user to restart the drift calibration. */ + int gyro_drift_sample_count; + float gyro_drift_solution[3]; /* Non zero if calibration is complete. */ + + Quaternion integrated_rotation; /* Used to help test whether the time stamps and gyro degrees per second are set up correctly by the HID implementation */ +} IMUState; + +/* Reset the Drift calculation state */ +void StartGyroDriftCalibration(IMUState *imustate) +{ + imustate->is_calibrating_drift = true; + imustate->gyro_drift_sample_count = 0; + SDL_zeroa(imustate->gyro_drift_solution); + SDL_zeroa(imustate->gyro_drift_accumulator); +} +void ResetIMUState(IMUState *imustate) +{ + imustate->gyro_packet_number = 0; + imustate->accelerometer_packet_number = 0; + imustate->starting_time_stamp_ns = SDL_GetTicksNS(); + imustate->integrated_rotation = quat_identity; + imustate->accelerometer_length_squared = 0.0f; + imustate->integrated_rotation = quat_identity; + SDL_zeroa(imustate->last_accel_data); + SDL_zeroa(imustate->gyro_drift_solution); + StartGyroDriftCalibration(imustate); +} + +void ResetGyroOrientation(IMUState *imustate) +{ + imustate->integrated_rotation = quat_identity; +} + +/* More samples = more accurate drift correction, but also more time to calibrate.*/ +#define SDL_GAMEPAD_IMU_MIN_GYRO_DRIFT_SAMPLE_COUNT 1024 + +/* + * Average drift _per packet_ as opposed to _per second_ + * This reduces a small amount of overhead when applying the drift correction. + */ +void FinalizeDriftSolution(IMUState *imustate) +{ + if (imustate->gyro_drift_sample_count >= SDL_GAMEPAD_IMU_MIN_GYRO_DRIFT_SAMPLE_COUNT) { + imustate->gyro_drift_solution[0] = imustate->gyro_drift_accumulator[0] / (float)imustate->gyro_drift_sample_count; + imustate->gyro_drift_solution[1] = imustate->gyro_drift_accumulator[1] / (float)imustate->gyro_drift_sample_count; + imustate->gyro_drift_solution[2] = imustate->gyro_drift_accumulator[2] / (float)imustate->gyro_drift_sample_count; + } + + imustate->is_calibrating_drift = false; + ResetGyroOrientation(imustate); +} + +/* Sample gyro packet in order to calculate drift*/ +void SampleGyroPacketForDrift( IMUState *imustate ) +{ + if ( !imustate->is_calibrating_drift ) + return; + + /* Get the length squared difference of the last accelerometer data vs. the new one */ + float accelerometer_difference[3]; + accelerometer_difference[0] = imustate->accel_data[0] - imustate->last_accel_data[0]; + accelerometer_difference[1] = imustate->accel_data[1] - imustate->last_accel_data[1]; + accelerometer_difference[2] = imustate->accel_data[2] - imustate->last_accel_data[2]; + SDL_memcpy(imustate->last_accel_data, imustate->accel_data, sizeof(imustate->last_accel_data)); + + imustate->accelerometer_length_squared = accelerometer_difference[0] * accelerometer_difference[0] + accelerometer_difference[1] * accelerometer_difference[1] + accelerometer_difference[2] * accelerometer_difference[2]; + + /* Ideal threshold will vary considerably depending on IMU. PS5 needs a low value (0.05f). Nintendo Switch needs a higher value (0.15f). */ + const float flAccelerometerMovementThreshold = ACCELEROMETER_NOISE_THRESHOLD; + if (imustate->accelerometer_length_squared > flAccelerometerMovementThreshold * flAccelerometerMovementThreshold) { + /* Reset the drift calibration if the accelerometer has moved significantly */ + StartGyroDriftCalibration(imustate); + } else { + /* Sensor is stationary enough to evaluate for drift.*/ + ++imustate->gyro_drift_sample_count; + + imustate->gyro_drift_accumulator[0] += imustate->gyro_data[0]; + imustate->gyro_drift_accumulator[1] += imustate->gyro_data[1]; + imustate->gyro_drift_accumulator[2] += imustate->gyro_data[2]; + + if (imustate->gyro_drift_sample_count >= SDL_GAMEPAD_IMU_MIN_GYRO_DRIFT_SAMPLE_COUNT) { + FinalizeDriftSolution(imustate); + } + } +} + +void ApplyDriftSolution(float *gyro_data, const float *drift_solution) +{ + gyro_data[0] -= drift_solution[0]; + gyro_data[1] -= drift_solution[1]; + gyro_data[2] -= drift_solution[2]; +} + +void UpdateGyroRotation(IMUState *imustate, Uint64 sensorTimeStampDelta_ns) +{ + float sensorTimeDeltaTimeSeconds = SDL_NS_TO_SECONDS((float)sensorTimeStampDelta_ns); + /* Integrate speeds to get Rotational Displacement*/ + float pitch = imustate->gyro_data[0] * sensorTimeDeltaTimeSeconds; + float yaw = imustate->gyro_data[1] * sensorTimeDeltaTimeSeconds; + float roll = imustate->gyro_data[2] * sensorTimeDeltaTimeSeconds; + + /* Use quaternions to avoid gimbal lock*/ + Quaternion delta_rotation = QuaternionFromEuler(pitch, yaw, roll); + imustate->integrated_rotation = MultiplyQuaternion(imustate->integrated_rotation, delta_rotation); + NormalizeQuaternion(&imustate->integrated_rotation); +} + typedef struct { SDL_JoystickID id; @@ -56,6 +275,7 @@ typedef struct SDL_Joystick *joystick; int num_axes; AxisState *axis_state; + IMUState *imu_state; SDL_Gamepad *gamepad; char *mapping; @@ -71,6 +291,7 @@ static SDL_Renderer *screen = NULL; static ControllerDisplayMode display_mode = CONTROLLER_MODE_TESTING; static GamepadImage *image = NULL; static GamepadDisplay *gamepad_elements = NULL; +static GyroDisplay *gyro_elements = NULL; static GamepadTypeDisplay *gamepad_type = NULL; static JoystickDisplay *joystick_elements = NULL; static GamepadButton *setup_mapping_button = NULL; @@ -265,6 +486,8 @@ static void ClearButtonHighlights(void) ClearGamepadImage(image); SetGamepadDisplayHighlight(gamepad_elements, SDL_GAMEPAD_ELEMENT_INVALID, false); SetGamepadTypeDisplayHighlight(gamepad_type, SDL_GAMEPAD_TYPE_UNSELECTED, false); + SetGamepadButtonHighlight(GetGyroResetButton( gyro_elements ), false, false); + SetGamepadButtonHighlight(GetGyroCalibrateButton(gyro_elements), false, false); SetGamepadButtonHighlight(setup_mapping_button, false, false); SetGamepadButtonHighlight(done_mapping_button, false, false); SetGamepadButtonHighlight(cancel_button, false, false); @@ -276,6 +499,8 @@ static void ClearButtonHighlights(void) static void UpdateButtonHighlights(float x, float y, bool button_down) { ClearButtonHighlights(); + SetGamepadButtonHighlight(GetGyroResetButton(gyro_elements), GamepadButtonContains(GetGyroResetButton(gyro_elements), x, y), button_down); + SetGamepadButtonHighlight(GetGyroCalibrateButton(gyro_elements), GamepadButtonContains(GetGyroCalibrateButton(gyro_elements), x, y), button_down); if (display_mode == CONTROLLER_MODE_TESTING) { SetGamepadButtonHighlight(setup_mapping_button, GamepadButtonContains(setup_mapping_button, x, y), button_down); @@ -915,6 +1140,8 @@ static void AddController(SDL_JoystickID id, bool verbose) if (new_controller->joystick) { new_controller->num_axes = SDL_GetNumJoystickAxes(new_controller->joystick); new_controller->axis_state = (AxisState *)SDL_calloc(new_controller->num_axes, sizeof(*new_controller->axis_state)); + new_controller->imu_state = (IMUState *)SDL_calloc(1, sizeof(*new_controller->imu_state)); + ResetIMUState(new_controller->imu_state); } joystick = new_controller->joystick; @@ -959,6 +1186,9 @@ static void DelController(SDL_JoystickID id) if (controllers[i].axis_state) { SDL_free(controllers[i].axis_state); } + if (controllers[i].imu_state) { + SDL_free(controllers[i].imu_state); + } if (controllers[i].joystick) { SDL_CloseJoystick(controllers[i].joystick); } @@ -1133,6 +1363,99 @@ static void HandleGamepadRemoved(SDL_JoystickID id) controllers[i].gamepad = NULL; } } +static void HandleGamepadAccelerometerEvent(SDL_Event *event) +{ + controller->imu_state->accelerometer_packet_number++; + SDL_memcpy(controller->imu_state->accel_data, event->gsensor.data, sizeof(controller->imu_state->accel_data)); +} + +static void HandleGamepadGyroEvent(SDL_Event *event) +{ + controller->imu_state->gyro_packet_number++; + SDL_memcpy(controller->imu_state->gyro_data, event->gsensor.data, sizeof(controller->imu_state->gyro_data)); +} + +#define SDL_GAMEPAD_IMU_MIN_POLLING_RATE_ESTIMATION_COUNT 2048 +static void EstimatePacketRate() +{ + Uint64 now_ns = SDL_GetTicksNS(); + if (controller->imu_state->imu_packet_counter == 0) { + controller->imu_state->starting_time_stamp_ns = now_ns; + } + + /* Require a significant sample size before averaging rate. */ + if (controller->imu_state->imu_packet_counter >= SDL_GAMEPAD_IMU_MIN_POLLING_RATE_ESTIMATION_COUNT) { + Uint64 deltatime_ns = now_ns - controller->imu_state->starting_time_stamp_ns; + controller->imu_state->imu_estimated_sensor_rate = (Uint16)((controller->imu_state->imu_packet_counter * 1000000000ULL) / deltatime_ns); + } + + /* Flush sampled data after a brief period so that the imu_estimated_sensor_rate value can be read.*/ + if (controller->imu_state->imu_packet_counter >= SDL_GAMEPAD_IMU_MIN_POLLING_RATE_ESTIMATION_COUNT * 2) { + controller->imu_state->starting_time_stamp_ns = now_ns; + controller->imu_state->imu_packet_counter = 0; + } + ++controller->imu_state->imu_packet_counter; +} + +static void UpdateGamepadOrientation( Uint64 delta_time_ns ) +{ + if (!controller || !controller->imu_state) + return; + + SampleGyroPacketForDrift(controller->imu_state); + ApplyDriftSolution(controller->imu_state->gyro_data, controller->imu_state->gyro_drift_solution); + UpdateGyroRotation(controller->imu_state, delta_time_ns); +} + +static void HandleGamepadSensorEvent( SDL_Event* event ) +{ + if (!controller) { + return; + } + + if (controller->id != event->gsensor.which) { + return; + } + + if (event->gsensor.sensor == SDL_SENSOR_GYRO) { + HandleGamepadGyroEvent(event); + } else if (event->gsensor.sensor == SDL_SENSOR_ACCEL) { + HandleGamepadAccelerometerEvent(event); + } + + /* + This is where we can update the quaternion because we need to have a drift solution, which requires both + accelerometer and gyro events are received before progressing. + */ + if ( controller->imu_state->accelerometer_packet_number == controller->imu_state->gyro_packet_number ) { + + EstimatePacketRate(); + Uint64 sensorTimeStampDelta_ns = event->gsensor.sensor_timestamp - controller->imu_state->last_sensor_time_stamp_ns ; + UpdateGamepadOrientation(sensorTimeStampDelta_ns); + + float display_euler_angles[3]; + EulerDegreesFromQuaternion(controller->imu_state->integrated_rotation, &display_euler_angles[0], &display_euler_angles[1], &display_euler_angles[2]); + + float drift_calibration_progress_frac = controller->imu_state->gyro_drift_sample_count / (float)SDL_GAMEPAD_IMU_MIN_GYRO_DRIFT_SAMPLE_COUNT; + int reported_polling_rate_hz = sensorTimeStampDelta_ns > 0 ? (int)(SDL_NS_PER_SECOND / sensorTimeStampDelta_ns) : 0; + + /* Send the results to the frontend */ + SetGamepadDisplayIMUValues(gyro_elements, + controller->imu_state->gyro_drift_solution, + display_euler_angles, + &controller->imu_state->integrated_rotation, + reported_polling_rate_hz, + controller->imu_state->imu_estimated_sensor_rate, + drift_calibration_progress_frac, + controller->imu_state->accelerometer_length_squared + ); + + /* Also show the gyro correction next to the gyro speed - this is useful in turntable tests as you can use a turntable to calibrate for drift, and that drift correction is functionally the same as the turn table speed (ignoring drift) */ + SetGamepadDisplayGyroDriftCorrection(gamepad_elements, controller->imu_state->gyro_drift_solution); + + controller->imu_state->last_sensor_time_stamp_ns = event->gsensor.sensor_timestamp; + } +} static Uint16 ConvertAxisToRumble(Sint16 axisval) { @@ -1296,7 +1619,9 @@ static void VirtualGamepadMouseDown(float x, float y) int element = GetGamepadImageElementAt(image, x, y); if (element == SDL_GAMEPAD_ELEMENT_INVALID) { - SDL_FPoint point = { x, y }; + SDL_FPoint point; + point.x = x; + point.y = y; SDL_FRect touchpad; GetGamepadTouchpadArea(image, &touchpad); if (SDL_PointInRectFloat(&point, &touchpad)) { @@ -1738,8 +2063,9 @@ SDL_AppResult SDLCALL SDL_AppEvent(void *appstate, SDL_Event *event) break; #endif /* VERBOSE_TOUCHPAD */ -#ifdef VERBOSE_SENSORS + case SDL_EVENT_GAMEPAD_SENSOR_UPDATE: +#ifdef VERBOSE_SENSORS SDL_Log("Gamepad %" SDL_PRIu32 " sensor %s: %.2f, %.2f, %.2f (%" SDL_PRIu64 ")", event->gsensor.which, GetSensorName((SDL_SensorType) event->gsensor.sensor), @@ -1747,8 +2073,10 @@ SDL_AppResult SDLCALL SDL_AppEvent(void *appstate, SDL_Event *event) event->gsensor.data[1], event->gsensor.data[2], event->gsensor.sensor_timestamp); - break; + #endif /* VERBOSE_SENSORS */ + HandleGamepadSensorEvent(event); + break; #ifdef VERBOSE_AXES case SDL_EVENT_GAMEPAD_AXIS_MOTION: @@ -1807,7 +2135,11 @@ SDL_AppResult SDLCALL SDL_AppEvent(void *appstate, SDL_Event *event) } if (display_mode == CONTROLLER_MODE_TESTING) { - if (GamepadButtonContains(setup_mapping_button, event->button.x, event->button.y)) { + if (GamepadButtonContains(GetGyroResetButton(gyro_elements), event->button.x, event->button.y)) { + ResetGyroOrientation(controller->imu_state); + } else if (GamepadButtonContains(GetGyroCalibrateButton(gyro_elements), event->button.x, event->button.y)) { + StartGyroDriftCalibration(controller->imu_state); + } else if (GamepadButtonContains(setup_mapping_button, event->button.x, event->button.y)) { SetDisplayMode(CONTROLLER_MODE_BINDING); } } else if (display_mode == CONTROLLER_MODE_BINDING) { @@ -1886,6 +2218,10 @@ SDL_AppResult SDLCALL SDL_AppEvent(void *appstate, SDL_Event *event) SDL_ReloadGamepadMappings(); } else if (event->key.key == SDLK_ESCAPE) { done = true; + } else if (event->key.key == SDLK_SPACE) { + if (controller && controller->imu_state) { + ResetGyroOrientation(controller->imu_state); + } } } else if (display_mode == CONTROLLER_MODE_BINDING) { if (event->key.key == SDLK_C && (event->key.mod & SDL_KMOD_CTRL)) { @@ -1994,6 +2330,7 @@ SDL_AppResult SDLCALL SDL_AppIterate(void *appstate) if (display_mode == CONTROLLER_MODE_TESTING) { RenderGamepadButton(setup_mapping_button); + RenderGyroDisplay(gyro_elements, gamepad_elements, controller->gamepad); } else if (display_mode == CONTROLLER_MODE_BINDING) { DrawBindingTips(screen); RenderGamepadButton(done_mapping_button); @@ -2148,6 +2485,17 @@ SDL_AppResult SDLCALL SDL_AppInit(void **appstate, int argc, char *argv[]) area.h = GAMEPAD_HEIGHT; SetGamepadDisplayArea(gamepad_elements, &area); + gyro_elements = CreateGyroDisplay(screen); + const float vidReservedHeight = 24.0f; + /* Bottom right of the screen */ + area.w = SCREEN_WIDTH * 0.375f; + area.h = SCREEN_HEIGHT * 0.475f; + area.x = SCREEN_WIDTH - area.w; + area.y = SCREEN_HEIGHT - area.h - vidReservedHeight; + + SetGyroDisplayArea(gyro_elements, &area); + InitCirclePoints3D(); + gamepad_type = CreateGamepadTypeDisplay(screen); area.x = 0; area.y = TITLE_HEIGHT; @@ -2227,6 +2575,7 @@ void SDLCALL SDL_AppQuit(void *appstate, SDL_AppResult result) SDL_free(controller_name); DestroyGamepadImage(image); DestroyGamepadDisplay(gamepad_elements); + DestroyGyroDisplay(gyro_elements); DestroyGamepadTypeDisplay(gamepad_type); DestroyJoystickDisplay(joystick_elements); DestroyGamepadButton(setup_mapping_button); diff --git a/libs/SDL3/test/testdialog.c b/libs/SDL3/test/testdialog.c index 3d19fb94..dcff5bd9 100644 --- a/libs/SDL3/test/testdialog.c +++ b/libs/SDL3/test/testdialog.c @@ -15,8 +15,9 @@ #include #include -const SDL_DialogFileFilter filters[3] = { +const SDL_DialogFileFilter filters[] = { { "All files", "*" }, + { "SVI Session Indexes", "index;svi-index;index.pb" }, { "JPG images", "jpg;jpeg" }, { "PNG images", "png" } }; @@ -54,7 +55,6 @@ int main(int argc, char *argv[]) const SDL_FRect open_folder_rect = { 370, 50, 220, 140 }; int i; const char *initial_path = NULL; - const int nfilters = sizeof(filters) / sizeof(*filters); /* Initialize test framework */ state = SDLTest_CommonCreateState(argv, 0); @@ -112,11 +112,11 @@ int main(int argc, char *argv[]) * - Nonzero if the user is allowed to choose multiple entries (not for SDL_ShowSaveFileDialog) */ if (SDL_PointInRectFloat(&p, &open_file_rect)) { - SDL_ShowOpenFileDialog(callback, NULL, w, filters, nfilters, initial_path, 1); + SDL_ShowOpenFileDialog(callback, NULL, w, filters, SDL_arraysize(filters), initial_path, 1); } else if (SDL_PointInRectFloat(&p, &open_folder_rect)) { SDL_ShowOpenFolderDialog(callback, NULL, w, initial_path, 1); } else if (SDL_PointInRectFloat(&p, &save_file_rect)) { - SDL_ShowSaveFileDialog(callback, NULL, w, filters, nfilters, initial_path); + SDL_ShowSaveFileDialog(callback, NULL, w, filters, SDL_arraysize(filters), initial_path); } } } diff --git a/libs/SDL3/test/testffmpeg_vulkan.c b/libs/SDL3/test/testffmpeg_vulkan.c index 4f2ba811..a6da6cbd 100644 --- a/libs/SDL3/test/testffmpeg_vulkan.c +++ b/libs/SDL3/test/testffmpeg_vulkan.c @@ -679,6 +679,16 @@ void SetupVulkanRenderProperties(VulkanVideoContext *context, SDL_PropertiesID p SDL_SetNumberProperty(props, SDL_PROP_RENDERER_CREATE_VULKAN_GRAPHICS_QUEUE_FAMILY_INDEX_NUMBER, context->graphicsQueueFamilyIndex); } +#if LIBAVUTIL_VERSION_MAJOR >= 59 +static void AddQueueFamily(AVVulkanDeviceContext *ctx, int idx, int num, VkQueueFlagBits flags) +{ + AVVulkanDeviceQueueFamily *entry = &ctx->qf[ctx->nb_qf++]; + entry->idx = idx; + entry->num = num; + entry->flags = flags; +} +#endif /* LIBAVUTIL_VERSION_MAJOR */ + void SetupVulkanDeviceContextData(VulkanVideoContext *context, AVVulkanDeviceContext *ctx) { ctx->get_proc_addr = context->vkGetInstanceProcAddr; @@ -690,6 +700,12 @@ void SetupVulkanDeviceContextData(VulkanVideoContext *context, AVVulkanDeviceCon ctx->nb_enabled_inst_extensions = context->instanceExtensionsCount; ctx->enabled_dev_extensions = context->deviceExtensions; ctx->nb_enabled_dev_extensions = context->deviceExtensionsCount; +#if LIBAVUTIL_VERSION_MAJOR >= 59 + AddQueueFamily(ctx, context->graphicsQueueFamilyIndex, context->graphicsQueueCount, VK_QUEUE_GRAPHICS_BIT); + AddQueueFamily(ctx, context->transferQueueFamilyIndex, context->transferQueueCount, VK_QUEUE_TRANSFER_BIT); + AddQueueFamily(ctx, context->computeQueueFamilyIndex, context->computeQueueCount, VK_QUEUE_COMPUTE_BIT); + AddQueueFamily(ctx, context->decodeQueueFamilyIndex, context->decodeQueueCount, VK_QUEUE_VIDEO_DECODE_BIT_KHR); +#else ctx->queue_family_index = context->graphicsQueueFamilyIndex; ctx->nb_graphics_queues = context->graphicsQueueCount; ctx->queue_family_tx_index = context->transferQueueFamilyIndex; @@ -700,6 +716,7 @@ void SetupVulkanDeviceContextData(VulkanVideoContext *context, AVVulkanDeviceCon ctx->nb_encode_queues = 0; ctx->queue_family_decode_index = context->decodeQueueFamilyIndex; ctx->nb_decode_queues = context->decodeQueueCount; +#endif /* LIBAVUTIL_VERSION_MAJOR */ } static int CreateCommandBuffers(VulkanVideoContext *context, SDL_Renderer *renderer) diff --git a/libs/SDL3/test/testgeometry.c b/libs/SDL3/test/testgeometry.c index a510cfd4..971de0ff 100644 --- a/libs/SDL3/test/testgeometry.c +++ b/libs/SDL3/test/testgeometry.c @@ -28,7 +28,6 @@ static bool use_texture = false; static SDL_Texture **sprites; static SDL_BlendMode blendMode = SDL_BLENDMODE_NONE; static float angle = 0.0f; -static int sprite_w, sprite_h; static int translate_cx = 0; static int translate_cy = 0; @@ -52,7 +51,7 @@ static int LoadSprite(const char *file) for (i = 0; i < state->num_windows; ++i) { /* This does the SDL_LoadBMP step repeatedly, but that's OK for test code. */ - sprites[i] = LoadTexture(state->renderers[i], file, true, &sprite_w, &sprite_h); + sprites[i] = LoadTexture(state->renderers[i], file, true); if (!sprites[i]) { return -1; } diff --git a/libs/SDL3/test/testgpurender_effects.c b/libs/SDL3/test/testgpurender_effects.c new file mode 100644 index 00000000..b10700b7 --- /dev/null +++ b/libs/SDL3/test/testgpurender_effects.c @@ -0,0 +1,339 @@ +/* + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely. +*/ + +#define SDL_MAIN_USE_CALLBACKS 1 /* use the callbacks instead of main() */ +#include +#include + +#include "testutils.h" + +#include "testgpurender_effects_grayscale.frag.dxil.h" +#include "testgpurender_effects_grayscale.frag.msl.h" +#include "testgpurender_effects_grayscale.frag.spv.h" +#include "testgpurender_effects_CRT.frag.dxil.h" +#include "testgpurender_effects_CRT.frag.msl.h" +#include "testgpurender_effects_CRT.frag.spv.h" + +/* The window is twice the size of the background */ +#define WINDOW_WIDTH (408 * 2) +#define WINDOW_HEIGHT (167 * 2) +#define NUM_SPRITES 15 +#define MAX_SPEED 1 + +static SDL_Window *window = NULL; +static SDL_Renderer *renderer = NULL; +static SDL_Texture *target = NULL; +static SDL_GPUDevice *device = NULL; +static SDL_Texture *background; +static SDL_Texture *sprite; +static SDL_FRect positions[NUM_SPRITES]; +static SDL_FRect velocities[NUM_SPRITES]; + +typedef enum +{ + EFFECT_NONE, + EFFECT_GRAYSCALE, + EFFECT_CRT, + NUM_EFFECTS +} FullscreenEffect; + +typedef struct +{ + const char *name; + const unsigned char *dxil_shader_source; + unsigned int dxil_shader_source_len; + const unsigned char *msl_shader_source; + unsigned int msl_shader_source_len; + const unsigned char *spirv_shader_source; + unsigned int spirv_shader_source_len; + int num_samplers; + int num_uniform_buffers; + SDL_GPUShader *shader; + SDL_GPURenderState *state; +} FullscreenEffectData; + +typedef struct +{ + float texture_width; + float texture_height; +} CRTEffectUniforms; + +static FullscreenEffectData effects[] = { + { + "NONE", + NULL, + 0, + NULL, + 0, + NULL, + 0, + 0, + 0, + NULL, + NULL + }, + { + "Grayscale", + testgpu_effects_grayscale_frag_dxil, + sizeof(testgpu_effects_grayscale_frag_dxil), + testgpu_effects_grayscale_frag_msl, + sizeof(testgpu_effects_grayscale_frag_msl), + testgpu_effects_grayscale_frag_spv, + sizeof(testgpu_effects_grayscale_frag_spv), + 1, + 0, + NULL, + NULL + }, + { + "CRT monitor", + testgpu_effects_CRT_frag_dxil, + sizeof(testgpu_effects_CRT_frag_dxil), + testgpu_effects_CRT_frag_msl, + sizeof(testgpu_effects_CRT_frag_msl), + testgpu_effects_CRT_frag_spv, + sizeof(testgpu_effects_CRT_frag_spv), + 1, + 1, + NULL, + NULL + } +}; +SDL_COMPILE_TIME_ASSERT(effects, SDL_arraysize(effects) == NUM_EFFECTS); + +static int current_effect = 0; + +static void DrawScene(void) +{ + int i; + int window_w = WINDOW_WIDTH; + int window_h = WINDOW_HEIGHT; + SDL_FRect *position, *velocity; + + SDL_RenderTexture(renderer, background, NULL, NULL); + + /* Move the sprite, bounce at the wall, and draw */ + for (i = 0; i < NUM_SPRITES; ++i) { + position = &positions[i]; + velocity = &velocities[i]; + position->x += velocity->x; + if ((position->x < 0) || (position->x >= (window_w - sprite->w))) { + velocity->x = -velocity->x; + position->x += velocity->x; + } + position->y += velocity->y; + if ((position->y < 0) || (position->y >= (window_h - sprite->h))) { + velocity->y = -velocity->y; + position->y += velocity->y; + } + + /* Blit the sprite onto the screen */ + SDL_RenderTexture(renderer, sprite, NULL, position); + } +} + +static bool InitGPURenderState(void) +{ + SDL_GPUShaderFormat formats; + SDL_GPUShaderCreateInfo info; + SDL_GPURenderStateDesc desc; + int i; + + formats = SDL_GetGPUShaderFormats(device); + if (formats == SDL_GPU_SHADERFORMAT_INVALID) { + SDL_Log("Couldn't get supported shader formats: %s", SDL_GetError()); + return false; + } + + for (i = 0; i < SDL_arraysize(effects); ++i) { + FullscreenEffectData *data = &effects[i]; + + if (i == EFFECT_NONE) { + continue; + } + + SDL_zero(info); + if (formats & SDL_GPU_SHADERFORMAT_SPIRV) { + info.format = SDL_GPU_SHADERFORMAT_SPIRV; + info.code = data->spirv_shader_source; + info.code_size = data->spirv_shader_source_len; + } else if (formats & SDL_GPU_SHADERFORMAT_DXIL) { + info.format = SDL_GPU_SHADERFORMAT_DXIL; + info.code = data->dxil_shader_source; + info.code_size = data->dxil_shader_source_len; + } else if (formats & SDL_GPU_SHADERFORMAT_MSL) { + info.format = SDL_GPU_SHADERFORMAT_MSL; + info.code = data->msl_shader_source; + info.code_size = data->msl_shader_source_len; + } else { + SDL_Log("No supported shader format found"); + return false; + } + info.num_samplers = data->num_samplers; + info.num_uniform_buffers = data->num_uniform_buffers; + info.stage = SDL_GPU_SHADERSTAGE_FRAGMENT; + data->shader = SDL_CreateGPUShader(device, &info); + if (!data->shader) { + SDL_Log("Couldn't create shader: %s", SDL_GetError()); + return false; + } + + SDL_INIT_INTERFACE(&desc); + desc.fragment_shader = data->shader; + data->state = SDL_CreateGPURenderState(renderer, &desc); + if (!data->state) { + SDL_Log("Couldn't create render state: %s", SDL_GetError()); + return false; + } + + if (i == EFFECT_CRT) { + CRTEffectUniforms uniforms; + SDL_zero(uniforms); + uniforms.texture_width = (float)target->w; + uniforms.texture_height = (float)target->h; + if (!SDL_SetGPURenderStateFragmentUniforms(data->state, 0, &uniforms, sizeof(uniforms))) { + SDL_Log("Couldn't set uniform data: %s", SDL_GetError()); + return false; + } + } + } + return true; +} + +static void QuitGPURenderState(void) +{ + int i; + + for (i = 0; i < SDL_arraysize(effects); ++i) { + FullscreenEffectData *data = &effects[i]; + + if (i == EFFECT_NONE) { + continue; + } + + SDL_DestroyGPURenderState(data->state); + SDL_ReleaseGPUShader(device, data->shader); + } +} + +/* This function runs once at startup. */ +SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) +{ + const char *description = "GPU render effects example"; + int i; + + SDL_SetAppMetadata(description, "1.0", "com.example.testgpurender_effects"); + + if (!SDL_Init(SDL_INIT_VIDEO)) { + SDL_Log("Couldn't initialize SDL: %s", SDL_GetError()); + return SDL_APP_FAILURE; + } + + window = SDL_CreateWindow(description, WINDOW_WIDTH, WINDOW_HEIGHT, 0); + if (!window) { + SDL_Log("Couldn't create window: %s", SDL_GetError()); + return SDL_APP_FAILURE; + } + + renderer = SDL_CreateGPURenderer(window, SDL_GPU_SHADERFORMAT_SPIRV | SDL_GPU_SHADERFORMAT_DXIL | SDL_GPU_SHADERFORMAT_MSL, &device); + if (!renderer || !device) { + SDL_Log("Couldn't create renderer: %s", SDL_GetError()); + return SDL_APP_FAILURE; + } + SDL_SetRenderVSync(renderer, 1); + + target = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_TARGET, WINDOW_WIDTH, WINDOW_HEIGHT); + if (!target) { + SDL_Log("Couldn't create target texture: %s", SDL_GetError()); + return SDL_APP_FAILURE; + } + + background = LoadTexture(renderer, "sample.bmp", false); + if (!background) { + SDL_Log("Couldn't create background: %s", SDL_GetError()); + return SDL_APP_FAILURE; + } + + sprite = LoadTexture(renderer, "icon.bmp", true); + if (!sprite) { + SDL_Log("Couldn't create sprite: %s", SDL_GetError()); + return SDL_APP_FAILURE; + } + + /* Initialize the sprite positions */ + for (i = 0; i < NUM_SPRITES; ++i) { + positions[i].x = (float)SDL_rand(WINDOW_WIDTH - sprite->w); + positions[i].y = (float)SDL_rand(WINDOW_HEIGHT - sprite->h); + positions[i].w = (float)sprite->w; + positions[i].h = (float)sprite->h; + velocities[i].x = 0.0f; + velocities[i].y = 0.0f; + while (velocities[i].x == 0.f && velocities[i].y == 0.f) { + velocities[i].x = (float)(SDL_rand(MAX_SPEED * 2 + 1) - MAX_SPEED); + velocities[i].y = (float)(SDL_rand(MAX_SPEED * 2 + 1) - MAX_SPEED); + } + } + + if (!InitGPURenderState()) { + return SDL_APP_FAILURE; + } + + return SDL_APP_CONTINUE; /* carry on with the program! */ +} + +/* This function runs when a new event (mouse input, keypresses, etc) occurs. */ +SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event) +{ + if (event->type == SDL_EVENT_QUIT || + (event->type == SDL_EVENT_KEY_DOWN && event->key.key == SDLK_ESCAPE)) { + return SDL_APP_SUCCESS; /* end the program, reporting success to the OS. */ + } + if (event->type == SDL_EVENT_KEY_DOWN) { + if (event->key.key == SDLK_SPACE) { + current_effect = (current_effect + 1) % NUM_EFFECTS; + } + } + return SDL_APP_CONTINUE; /* carry on with the program! */ +} + +/* This function runs once per frame, and is the heart of the program. */ +SDL_AppResult SDL_AppIterate(void *appstate) +{ + FullscreenEffectData *effect = &effects[current_effect]; + + /* Draw the scene to the render target */ + SDL_SetRenderTarget(renderer, target); + DrawScene(); + SDL_SetRenderTarget(renderer, NULL); + + /* Display the render target with the fullscreen effect */ + SDL_SetRenderGPUState(renderer, effect->state); + SDL_RenderTexture(renderer, target, NULL, NULL); + SDL_SetRenderGPUState(renderer, NULL); + + /* Draw some help text */ + SDL_SetRenderDrawColor(renderer, 255, 255, 255, SDL_ALPHA_OPAQUE); + SDL_RenderDebugTextFormat(renderer, 4.0f, WINDOW_HEIGHT - SDL_DEBUG_TEXT_FONT_CHARACTER_SIZE - 4.0f, + "Current effect: %s, press SPACE to cycle", effect->name); + + SDL_RenderPresent(renderer); + + return SDL_APP_CONTINUE; /* carry on with the program! */ +} + +/* This function runs once at shutdown. */ +void SDL_AppQuit(void *appstate, SDL_AppResult result) +{ + /* SDL will clean up the window/renderer for us. */ + QuitGPURenderState(); +} + diff --git a/libs/SDL3/test/testgpurender_effects_CRT.frag.dxil.h b/libs/SDL3/test/testgpurender_effects_CRT.frag.dxil.h new file mode 100644 index 00000000..8e07c824 --- /dev/null +++ b/libs/SDL3/test/testgpurender_effects_CRT.frag.dxil.h @@ -0,0 +1,392 @@ +static const unsigned char testgpu_effects_CRT_frag_dxil[] = { + 0x44, 0x58, 0x42, 0x43, 0xfc, 0x37, 0x25, 0xf8, 0x2a, 0xa6, 0x16, 0x0c, + 0xe6, 0x1f, 0x75, 0x7e, 0x5a, 0x39, 0x2b, 0x5a, 0x01, 0x00, 0x00, 0x00, + 0x38, 0x12, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, + 0x4c, 0x00, 0x00, 0x00, 0xa8, 0x00, 0x00, 0x00, 0xe4, 0x00, 0x00, 0x00, + 0xf0, 0x01, 0x00, 0x00, 0x50, 0x09, 0x00, 0x00, 0x6c, 0x09, 0x00, 0x00, + 0x53, 0x46, 0x49, 0x30, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x49, 0x53, 0x47, 0x31, 0x54, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x48, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x0f, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x03, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x54, 0x45, 0x58, 0x43, 0x4f, 0x4f, 0x52, 0x44, 0x00, 0x00, 0x00, 0x00, + 0x4f, 0x53, 0x47, 0x31, 0x34, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x53, 0x56, 0x5f, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x00, 0x00, 0x00, + 0x50, 0x53, 0x56, 0x30, 0x04, 0x01, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x01, 0x00, 0x02, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x13, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x18, 0x00, 0x00, 0x00, 0x00, 0x54, 0x45, 0x58, 0x43, 0x4f, 0x4f, 0x52, + 0x44, 0x00, 0x54, 0x45, 0x58, 0x43, 0x4f, 0x4f, 0x52, 0x44, 0x00, 0x6d, + 0x61, 0x69, 0x6e, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x44, 0x00, 0x03, 0x02, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x01, 0x42, 0x00, + 0x03, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x44, 0x10, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x0f, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x53, 0x54, 0x41, 0x54, 0x58, 0x07, 0x00, 0x00, + 0x60, 0x00, 0x00, 0x00, 0xd6, 0x01, 0x00, 0x00, 0x44, 0x58, 0x49, 0x4c, + 0x00, 0x01, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x40, 0x07, 0x00, 0x00, + 0x42, 0x43, 0xc0, 0xde, 0x21, 0x0c, 0x00, 0x00, 0xcd, 0x01, 0x00, 0x00, + 0x0b, 0x82, 0x20, 0x00, 0x02, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, + 0x07, 0x81, 0x23, 0x91, 0x41, 0xc8, 0x04, 0x49, 0x06, 0x10, 0x32, 0x39, + 0x92, 0x01, 0x84, 0x0c, 0x25, 0x05, 0x08, 0x19, 0x1e, 0x04, 0x8b, 0x62, + 0x80, 0x18, 0x45, 0x02, 0x42, 0x92, 0x0b, 0x42, 0xc4, 0x10, 0x32, 0x14, + 0x38, 0x08, 0x18, 0x4b, 0x0a, 0x32, 0x62, 0x88, 0x48, 0x90, 0x14, 0x20, + 0x43, 0x46, 0x88, 0xa5, 0x00, 0x19, 0x32, 0x42, 0xe4, 0x48, 0x0e, 0x90, + 0x11, 0x23, 0xc4, 0x50, 0x41, 0x51, 0x81, 0x8c, 0xe1, 0x83, 0xe5, 0x8a, + 0x04, 0x31, 0x46, 0x06, 0x51, 0x18, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x1b, 0x8c, 0xe0, 0xff, 0xff, 0xff, 0xff, 0x07, 0x40, 0x02, 0xa8, 0x0d, + 0x84, 0xf0, 0xff, 0xff, 0xff, 0xff, 0x03, 0x20, 0x6d, 0x30, 0x86, 0xff, + 0xff, 0xff, 0xff, 0x1f, 0x00, 0x09, 0xa8, 0x00, 0x49, 0x18, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x13, 0x82, 0x60, 0x42, 0x20, 0x4c, 0x08, 0x06, + 0x00, 0x00, 0x00, 0x00, 0x89, 0x20, 0x00, 0x00, 0x53, 0x00, 0x00, 0x00, + 0x32, 0x22, 0x88, 0x09, 0x20, 0x64, 0x85, 0x04, 0x13, 0x23, 0xa4, 0x84, + 0x04, 0x13, 0x23, 0xe3, 0x84, 0xa1, 0x90, 0x14, 0x12, 0x4c, 0x8c, 0x8c, + 0x0b, 0x84, 0xc4, 0x4c, 0x10, 0x90, 0xc1, 0x08, 0x40, 0x09, 0x00, 0x0a, + 0x66, 0x00, 0xe6, 0x08, 0xc0, 0x60, 0x8e, 0x00, 0x29, 0xc6, 0x40, 0x10, + 0x44, 0x41, 0x90, 0x51, 0x0c, 0x80, 0x20, 0x88, 0x62, 0x20, 0xa4, 0x18, + 0x03, 0x31, 0x0c, 0xc3, 0x40, 0xca, 0x4d, 0xc3, 0xe5, 0x4f, 0xd8, 0x43, + 0x48, 0xfe, 0x4a, 0x48, 0x2b, 0x31, 0xf9, 0xc5, 0x6d, 0xa3, 0x62, 0x18, + 0x86, 0x81, 0xa0, 0xe2, 0x9e, 0xe1, 0xf2, 0x27, 0xec, 0x21, 0x24, 0x3f, + 0x04, 0x9a, 0x61, 0x21, 0x50, 0xd0, 0x14, 0x86, 0x21, 0x1c, 0x67, 0x18, + 0x86, 0x81, 0x20, 0x88, 0x81, 0x9e, 0x32, 0x0c, 0xc4, 0x40, 0xd1, 0x51, + 0xc3, 0xe5, 0x4f, 0xd8, 0x43, 0x48, 0x3e, 0xb7, 0x51, 0xc5, 0x4a, 0x4c, + 0x7e, 0x71, 0xdb, 0x88, 0x18, 0x86, 0x61, 0x28, 0xc4, 0x44, 0x38, 0x04, + 0x51, 0x73, 0x04, 0x41, 0x31, 0x1c, 0xa2, 0x20, 0x08, 0x8b, 0xae, 0x81, + 0x80, 0x61, 0x04, 0x62, 0x98, 0xa9, 0x0d, 0xc6, 0x81, 0x1d, 0xc2, 0x61, + 0x1e, 0xe6, 0xc1, 0x0d, 0x68, 0xa1, 0x1c, 0xf0, 0x81, 0x1e, 0xea, 0x41, + 0x1e, 0xca, 0x41, 0x0e, 0x48, 0x81, 0x0f, 0xec, 0xa1, 0x1c, 0xc6, 0x81, + 0x1e, 0xde, 0x41, 0x1e, 0xf8, 0xc0, 0x1c, 0xd8, 0xe1, 0x1d, 0xc2, 0x81, + 0x1e, 0xd8, 0x00, 0x0c, 0xe8, 0xc0, 0x0f, 0xc0, 0xc0, 0x0f, 0xf4, 0x40, + 0x0f, 0xda, 0x21, 0x1d, 0xe0, 0x61, 0x1e, 0x7e, 0x81, 0x1e, 0xf2, 0x01, + 0x1e, 0xca, 0x01, 0x05, 0xc4, 0x4c, 0x62, 0x30, 0x0e, 0xec, 0x10, 0x0e, + 0xf3, 0x30, 0x0f, 0x6e, 0x40, 0x0b, 0xe5, 0x80, 0x0f, 0xf4, 0x50, 0x0f, + 0xf2, 0x50, 0x0e, 0x72, 0x40, 0x0a, 0x7c, 0x60, 0x0f, 0xe5, 0x30, 0x0e, + 0xf4, 0xf0, 0x0e, 0xf2, 0xc0, 0x07, 0xe6, 0xc0, 0x0e, 0xef, 0x10, 0x0e, + 0xf4, 0xc0, 0x06, 0x60, 0x40, 0x07, 0x7e, 0x00, 0x06, 0x7e, 0x80, 0x04, + 0x6d, 0x23, 0x6e, 0x18, 0x41, 0x18, 0x8e, 0x99, 0xb0, 0x87, 0xf8, 0x39, + 0xa7, 0x99, 0x88, 0x6b, 0x42, 0x81, 0x47, 0xdf, 0x4d, 0xd2, 0x14, 0x51, + 0xc2, 0xe4, 0xb3, 0x00, 0xf3, 0x2c, 0x44, 0xc4, 0x4e, 0xc0, 0x44, 0xa0, + 0x80, 0xa0, 0x30, 0x11, 0x08, 0x00, 0x00, 0x00, 0x13, 0x14, 0x72, 0xc0, + 0x87, 0x74, 0x60, 0x87, 0x36, 0x68, 0x87, 0x79, 0x68, 0x03, 0x72, 0xc0, + 0x87, 0x0d, 0xaf, 0x50, 0x0e, 0x6d, 0xd0, 0x0e, 0x7a, 0x50, 0x0e, 0x6d, + 0x00, 0x0f, 0x7a, 0x30, 0x07, 0x72, 0xa0, 0x07, 0x73, 0x20, 0x07, 0x6d, + 0x90, 0x0e, 0x71, 0xa0, 0x07, 0x73, 0x20, 0x07, 0x6d, 0x90, 0x0e, 0x78, + 0xa0, 0x07, 0x73, 0x20, 0x07, 0x6d, 0x90, 0x0e, 0x71, 0x60, 0x07, 0x7a, + 0x30, 0x07, 0x72, 0xd0, 0x06, 0xe9, 0x30, 0x07, 0x72, 0xa0, 0x07, 0x73, + 0x20, 0x07, 0x6d, 0x90, 0x0e, 0x76, 0x40, 0x07, 0x7a, 0x60, 0x07, 0x74, + 0xd0, 0x06, 0xe6, 0x10, 0x07, 0x76, 0xa0, 0x07, 0x73, 0x20, 0x07, 0x6d, + 0x60, 0x0e, 0x73, 0x20, 0x07, 0x7a, 0x30, 0x07, 0x72, 0xd0, 0x06, 0xe6, + 0x60, 0x07, 0x74, 0xa0, 0x07, 0x76, 0x40, 0x07, 0x6d, 0xe0, 0x0e, 0x78, + 0xa0, 0x07, 0x71, 0x60, 0x07, 0x7a, 0x30, 0x07, 0x72, 0xa0, 0x07, 0x76, + 0x40, 0x07, 0x43, 0x9e, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x86, 0x3c, 0x06, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x0c, 0x79, 0x10, 0x20, 0x00, 0x04, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0xf2, 0x28, 0x40, 0x00, 0x04, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0xe4, 0x79, 0x80, 0x00, 0x18, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0xc8, 0x13, 0x01, 0x01, + 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x90, 0x87, 0x02, + 0x02, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x21, 0xcf, + 0x05, 0x04, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, + 0x20, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x32, 0x1e, 0x98, 0x14, + 0x19, 0x11, 0x4c, 0x90, 0x8c, 0x09, 0x26, 0x47, 0xc6, 0x04, 0x43, 0x22, + 0x4a, 0x60, 0x04, 0xa0, 0x18, 0x8a, 0xa0, 0x24, 0xca, 0xa0, 0x20, 0xca, + 0xa1, 0x34, 0xca, 0xa3, 0x98, 0x8a, 0xaf, 0x10, 0xa8, 0x28, 0x89, 0x11, + 0x80, 0x22, 0x28, 0x84, 0x02, 0xa1, 0x6e, 0x06, 0x80, 0xbe, 0x19, 0x00, + 0x02, 0x67, 0x00, 0x48, 0x9c, 0x01, 0xa0, 0x71, 0x2c, 0xc4, 0x20, 0x02, + 0x81, 0x40, 0x9e, 0x07, 0x00, 0x00, 0x00, 0x00, 0x79, 0x18, 0x00, 0x00, + 0x91, 0x00, 0x00, 0x00, 0x1a, 0x03, 0x4c, 0x90, 0x46, 0x02, 0x13, 0xc4, + 0x8e, 0x0c, 0x6f, 0xec, 0xed, 0x4d, 0x0c, 0x24, 0xc6, 0x05, 0xc7, 0x45, + 0xa6, 0x06, 0x46, 0xc6, 0x05, 0x07, 0x04, 0x45, 0x8c, 0xe6, 0x26, 0x26, + 0x06, 0x67, 0x26, 0xa7, 0x2c, 0x65, 0x43, 0x10, 0x4c, 0x10, 0x08, 0x64, + 0x82, 0x40, 0x24, 0x1b, 0x84, 0x81, 0xd8, 0x20, 0x10, 0x04, 0x05, 0xb8, + 0xb9, 0x09, 0x02, 0xa1, 0x6c, 0x18, 0x0e, 0x84, 0x98, 0x20, 0x74, 0x1a, + 0x93, 0xba, 0x2f, 0xba, 0x32, 0x3c, 0xba, 0x3a, 0xb9, 0xb2, 0x09, 0x02, + 0xb1, 0x4c, 0x10, 0x08, 0x66, 0x83, 0x40, 0x34, 0x1b, 0x12, 0x42, 0x59, + 0x18, 0x62, 0x60, 0x08, 0x67, 0x43, 0xf0, 0x4c, 0x10, 0xc0, 0x80, 0x23, + 0x43, 0x97, 0x07, 0x57, 0xf6, 0x35, 0xf4, 0xe6, 0x46, 0x57, 0x86, 0x47, + 0x37, 0x41, 0x20, 0x9a, 0x09, 0x02, 0xe1, 0x6c, 0x40, 0x88, 0x48, 0x9a, + 0x88, 0x81, 0x02, 0x36, 0x04, 0xd5, 0x04, 0x41, 0x0c, 0x3a, 0x26, 0x75, + 0x5f, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x1b, 0x10, 0xe2, 0xc2, + 0x18, 0x62, 0x20, 0x80, 0x0d, 0x41, 0xb6, 0x81, 0x80, 0x00, 0x4b, 0x9b, + 0x20, 0x7c, 0x1b, 0xa5, 0xa1, 0x37, 0x37, 0xba, 0x32, 0x3c, 0xba, 0x2f, + 0xb9, 0xb2, 0xb9, 0x37, 0xb6, 0x3a, 0xba, 0xb4, 0x37, 0xb7, 0x09, 0x02, + 0xf1, 0x4c, 0x10, 0x08, 0x68, 0x03, 0x82, 0x74, 0x13, 0xe1, 0x35, 0x1f, + 0xb3, 0x41, 0xa0, 0xc0, 0x60, 0xc3, 0x40, 0x70, 0x61, 0x30, 0x41, 0x10, + 0x80, 0x0d, 0xc0, 0x86, 0x81, 0x20, 0x03, 0x32, 0xd8, 0x10, 0x94, 0xc1, + 0x86, 0x61, 0x18, 0x03, 0x33, 0x98, 0x20, 0x8c, 0x81, 0xb7, 0x21, 0x40, + 0x03, 0x12, 0x6d, 0x61, 0x69, 0x6e, 0x44, 0xa8, 0x8a, 0xb0, 0x86, 0x9e, + 0x9e, 0xa4, 0x88, 0x26, 0x08, 0x45, 0x35, 0x41, 0x28, 0xac, 0x0d, 0x01, + 0x31, 0x41, 0x28, 0xae, 0x09, 0x42, 0x81, 0x4d, 0x10, 0x88, 0x68, 0x83, + 0x30, 0xc5, 0xc1, 0x86, 0x85, 0x58, 0x03, 0x36, 0x68, 0x03, 0x37, 0x78, + 0x83, 0x01, 0x0e, 0x88, 0x36, 0x90, 0x83, 0x0d, 0xc1, 0xb0, 0x41, 0x98, + 0xa6, 0x0d, 0xcb, 0xb0, 0x06, 0x6c, 0xd0, 0x06, 0x74, 0xf0, 0x06, 0xc3, + 0x1b, 0x0c, 0x6d, 0x50, 0x07, 0x1b, 0x84, 0x39, 0xb0, 0x03, 0x26, 0x53, + 0x56, 0x5f, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x13, 0x84, 0x22, 0xdb, + 0xb0, 0x10, 0x78, 0xc0, 0x06, 0x79, 0xe0, 0x06, 0x6d, 0x30, 0xc0, 0x01, + 0xd1, 0x06, 0x72, 0xb0, 0x21, 0xd0, 0x83, 0x0d, 0xc3, 0x1d, 0xec, 0x01, + 0xb0, 0xa1, 0x18, 0x03, 0x35, 0xe0, 0x83, 0x0d, 0xa0, 0x61, 0xc6, 0xf6, + 0x16, 0x46, 0x37, 0x37, 0x41, 0x20, 0x24, 0x16, 0x69, 0x6e, 0x73, 0x74, + 0x73, 0x13, 0x04, 0x62, 0xa2, 0x31, 0x97, 0x76, 0xf6, 0xc5, 0x46, 0x46, + 0x63, 0x2e, 0xed, 0xec, 0x6b, 0x8e, 0x6e, 0x82, 0x40, 0x50, 0x44, 0xe8, + 0xca, 0xf0, 0xbe, 0xdc, 0xde, 0xe4, 0xda, 0x36, 0x28, 0x7e, 0xf0, 0x07, + 0xa0, 0x10, 0x0a, 0xa2, 0x80, 0x8c, 0x02, 0x29, 0x94, 0xc2, 0x50, 0x85, + 0x8d, 0xcd, 0xae, 0xcd, 0x25, 0x8d, 0xac, 0xcc, 0x8d, 0x6e, 0x4a, 0x10, + 0x54, 0x21, 0xc3, 0x73, 0xb1, 0x2b, 0x93, 0x9b, 0x4b, 0x7b, 0x73, 0x9b, + 0x12, 0x10, 0x4d, 0xc8, 0xf0, 0x5c, 0xec, 0xc2, 0xd8, 0xec, 0xca, 0xe4, + 0xa6, 0x04, 0x45, 0x1d, 0x32, 0x3c, 0x97, 0x39, 0xb4, 0x30, 0xb2, 0x32, + 0xb9, 0xa6, 0x37, 0xb2, 0x32, 0xb6, 0x29, 0x01, 0x52, 0x86, 0x0c, 0xcf, + 0x45, 0xae, 0x6c, 0xee, 0xad, 0x4e, 0x6e, 0xac, 0x6c, 0x6e, 0x4a, 0xa0, + 0x55, 0x22, 0xc3, 0x73, 0xa1, 0xcb, 0x83, 0x2b, 0x0b, 0x72, 0x73, 0x7b, + 0xa3, 0x0b, 0xa3, 0x4b, 0x7b, 0x73, 0x9b, 0x9b, 0x22, 0x84, 0x81, 0x19, + 0xd4, 0x21, 0xc3, 0x73, 0xb1, 0x4b, 0x2b, 0xbb, 0x4b, 0x22, 0x9b, 0xa2, + 0x0b, 0xa3, 0x2b, 0x9b, 0x12, 0xa0, 0x41, 0x1d, 0x32, 0x3c, 0x97, 0x32, + 0x37, 0x3a, 0xb9, 0x3c, 0xa8, 0xb7, 0x34, 0x37, 0xba, 0xb9, 0x29, 0x01, + 0x1f, 0x74, 0x21, 0xc3, 0x73, 0x19, 0x7b, 0xab, 0x73, 0xa3, 0x2b, 0x93, + 0x9b, 0x9b, 0x12, 0x94, 0x02, 0x00, 0x00, 0x00, 0x79, 0x18, 0x00, 0x00, + 0x4c, 0x00, 0x00, 0x00, 0x33, 0x08, 0x80, 0x1c, 0xc4, 0xe1, 0x1c, 0x66, + 0x14, 0x01, 0x3d, 0x88, 0x43, 0x38, 0x84, 0xc3, 0x8c, 0x42, 0x80, 0x07, + 0x79, 0x78, 0x07, 0x73, 0x98, 0x71, 0x0c, 0xe6, 0x00, 0x0f, 0xed, 0x10, + 0x0e, 0xf4, 0x80, 0x0e, 0x33, 0x0c, 0x42, 0x1e, 0xc2, 0xc1, 0x1d, 0xce, + 0xa1, 0x1c, 0x66, 0x30, 0x05, 0x3d, 0x88, 0x43, 0x38, 0x84, 0x83, 0x1b, + 0xcc, 0x03, 0x3d, 0xc8, 0x43, 0x3d, 0x8c, 0x03, 0x3d, 0xcc, 0x78, 0x8c, + 0x74, 0x70, 0x07, 0x7b, 0x08, 0x07, 0x79, 0x48, 0x87, 0x70, 0x70, 0x07, + 0x7a, 0x70, 0x03, 0x76, 0x78, 0x87, 0x70, 0x20, 0x87, 0x19, 0xcc, 0x11, + 0x0e, 0xec, 0x90, 0x0e, 0xe1, 0x30, 0x0f, 0x6e, 0x30, 0x0f, 0xe3, 0xf0, + 0x0e, 0xf0, 0x50, 0x0e, 0x33, 0x10, 0xc4, 0x1d, 0xde, 0x21, 0x1c, 0xd8, + 0x21, 0x1d, 0xc2, 0x61, 0x1e, 0x66, 0x30, 0x89, 0x3b, 0xbc, 0x83, 0x3b, + 0xd0, 0x43, 0x39, 0xb4, 0x03, 0x3c, 0xbc, 0x83, 0x3c, 0x84, 0x03, 0x3b, + 0xcc, 0xf0, 0x14, 0x76, 0x60, 0x07, 0x7b, 0x68, 0x07, 0x37, 0x68, 0x87, + 0x72, 0x68, 0x07, 0x37, 0x80, 0x87, 0x70, 0x90, 0x87, 0x70, 0x60, 0x07, + 0x76, 0x28, 0x07, 0x76, 0xf8, 0x05, 0x76, 0x78, 0x87, 0x77, 0x80, 0x87, + 0x5f, 0x08, 0x87, 0x71, 0x18, 0x87, 0x72, 0x98, 0x87, 0x79, 0x98, 0x81, + 0x2c, 0xee, 0xf0, 0x0e, 0xee, 0xe0, 0x0e, 0xf5, 0xc0, 0x0e, 0xec, 0x30, + 0x03, 0x62, 0xc8, 0xa1, 0x1c, 0xe4, 0xa1, 0x1c, 0xcc, 0xa1, 0x1c, 0xe4, + 0xa1, 0x1c, 0xdc, 0x61, 0x1c, 0xca, 0x21, 0x1c, 0xc4, 0x81, 0x1d, 0xca, + 0x61, 0x06, 0xd6, 0x90, 0x43, 0x39, 0xc8, 0x43, 0x39, 0x98, 0x43, 0x39, + 0xc8, 0x43, 0x39, 0xb8, 0xc3, 0x38, 0x94, 0x43, 0x38, 0x88, 0x03, 0x3b, + 0x94, 0xc3, 0x2f, 0xbc, 0x83, 0x3c, 0xfc, 0x82, 0x3b, 0xd4, 0x03, 0x3b, + 0xb0, 0xc3, 0x8c, 0xc8, 0x21, 0x07, 0x7c, 0x70, 0x03, 0x72, 0x10, 0x87, + 0x73, 0x70, 0x03, 0x7b, 0x08, 0x07, 0x79, 0x60, 0x87, 0x70, 0xc8, 0x87, + 0x77, 0xa8, 0x07, 0x7a, 0x98, 0x81, 0x3c, 0xe4, 0x80, 0x0f, 0x6e, 0x40, + 0x0f, 0xe5, 0xd0, 0x0e, 0xf0, 0x00, 0x00, 0x00, 0x71, 0x20, 0x00, 0x00, + 0x1e, 0x00, 0x00, 0x00, 0x66, 0xb0, 0x0d, 0x97, 0xef, 0x3c, 0xbe, 0x10, + 0x50, 0x45, 0x41, 0x44, 0xa5, 0x03, 0x0c, 0x25, 0x61, 0x00, 0x02, 0xe6, + 0x17, 0xb7, 0x6d, 0x07, 0xd2, 0x70, 0xf9, 0xce, 0xe3, 0x0b, 0x11, 0x01, + 0x4c, 0x44, 0x08, 0x34, 0xc3, 0x42, 0xd8, 0x80, 0x33, 0x5c, 0xbe, 0xf3, + 0xf8, 0x83, 0x33, 0xd9, 0x7e, 0x71, 0xdb, 0x16, 0x30, 0x0d, 0x97, 0xef, + 0x3c, 0xfe, 0xe2, 0x00, 0x83, 0xd8, 0x3c, 0xd4, 0xe4, 0x17, 0xb7, 0x6d, + 0x04, 0xd0, 0x70, 0xf9, 0xce, 0xe3, 0x4b, 0x00, 0xf3, 0x2c, 0x84, 0x5f, + 0xdc, 0xb6, 0x09, 0x54, 0xc3, 0xe5, 0x3b, 0x8f, 0x2f, 0x4d, 0x4e, 0x44, + 0xa0, 0xd4, 0xf4, 0x50, 0x93, 0x5f, 0xdc, 0xb6, 0x15, 0x3c, 0xc3, 0xe5, + 0x3b, 0x8f, 0x4f, 0x35, 0x40, 0x84, 0xf9, 0xc5, 0x6d, 0x1b, 0x00, 0xc1, + 0x00, 0x48, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x41, 0x53, 0x48, + 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x8a, 0x1a, 0xfd, + 0x5f, 0xde, 0x04, 0xb4, 0xb6, 0x99, 0xe5, 0xcf, 0x84, 0xe3, 0x07, 0x28, + 0x44, 0x58, 0x49, 0x4c, 0xc4, 0x08, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, + 0x31, 0x02, 0x00, 0x00, 0x44, 0x58, 0x49, 0x4c, 0x00, 0x01, 0x00, 0x00, + 0x10, 0x00, 0x00, 0x00, 0xac, 0x08, 0x00, 0x00, 0x42, 0x43, 0xc0, 0xde, + 0x21, 0x0c, 0x00, 0x00, 0x28, 0x02, 0x00, 0x00, 0x0b, 0x82, 0x20, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x07, 0x81, 0x23, 0x91, + 0x41, 0xc8, 0x04, 0x49, 0x06, 0x10, 0x32, 0x39, 0x92, 0x01, 0x84, 0x0c, + 0x25, 0x05, 0x08, 0x19, 0x1e, 0x04, 0x8b, 0x62, 0x80, 0x18, 0x45, 0x02, + 0x42, 0x92, 0x0b, 0x42, 0xc4, 0x10, 0x32, 0x14, 0x38, 0x08, 0x18, 0x4b, + 0x0a, 0x32, 0x62, 0x88, 0x48, 0x90, 0x14, 0x20, 0x43, 0x46, 0x88, 0xa5, + 0x00, 0x19, 0x32, 0x42, 0xe4, 0x48, 0x0e, 0x90, 0x11, 0x23, 0xc4, 0x50, + 0x41, 0x51, 0x81, 0x8c, 0xe1, 0x83, 0xe5, 0x8a, 0x04, 0x31, 0x46, 0x06, + 0x51, 0x18, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x1b, 0x8c, 0xe0, 0xff, + 0xff, 0xff, 0xff, 0x07, 0x40, 0x02, 0xa8, 0x0d, 0x84, 0xf0, 0xff, 0xff, + 0xff, 0xff, 0x03, 0x20, 0x6d, 0x30, 0x86, 0xff, 0xff, 0xff, 0xff, 0x1f, + 0x00, 0x09, 0xa8, 0x00, 0x49, 0x18, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x13, 0x82, 0x60, 0x42, 0x20, 0x4c, 0x08, 0x06, 0x00, 0x00, 0x00, 0x00, + 0x89, 0x20, 0x00, 0x00, 0x53, 0x00, 0x00, 0x00, 0x32, 0x22, 0x88, 0x09, + 0x20, 0x64, 0x85, 0x04, 0x13, 0x23, 0xa4, 0x84, 0x04, 0x13, 0x23, 0xe3, + 0x84, 0xa1, 0x90, 0x14, 0x12, 0x4c, 0x8c, 0x8c, 0x0b, 0x84, 0xc4, 0x4c, + 0x10, 0x90, 0xc1, 0x08, 0x40, 0x09, 0x00, 0x0a, 0x66, 0x00, 0xe6, 0x08, + 0xc0, 0x60, 0x8e, 0x00, 0x29, 0xc6, 0x40, 0x10, 0x44, 0x41, 0x90, 0x51, + 0x0c, 0x80, 0x20, 0x88, 0x62, 0x20, 0xa4, 0x18, 0x03, 0x31, 0x0c, 0xc3, + 0x40, 0xca, 0x4d, 0xc3, 0xe5, 0x4f, 0xd8, 0x43, 0x48, 0xfe, 0x4a, 0x48, + 0x2b, 0x31, 0xf9, 0xc5, 0x6d, 0xa3, 0x62, 0x18, 0x86, 0x81, 0xa0, 0xe2, + 0x9e, 0xe1, 0xf2, 0x27, 0xec, 0x21, 0x24, 0x3f, 0x04, 0x9a, 0x61, 0x21, + 0x50, 0xd0, 0x14, 0x86, 0x21, 0x1c, 0x67, 0x18, 0x86, 0x81, 0x20, 0x88, + 0x81, 0x9e, 0x32, 0x0c, 0xc4, 0x40, 0xd1, 0x51, 0xc3, 0xe5, 0x4f, 0xd8, + 0x43, 0x48, 0x3e, 0xb7, 0x51, 0xc5, 0x4a, 0x4c, 0x7e, 0x71, 0xdb, 0x88, + 0x18, 0x86, 0x61, 0x28, 0xc4, 0x44, 0x38, 0x04, 0x51, 0x73, 0x04, 0x41, + 0x31, 0x1c, 0xa2, 0x20, 0x08, 0x8b, 0xae, 0x81, 0x80, 0x61, 0x04, 0x62, + 0x98, 0xa9, 0x0d, 0xc6, 0x81, 0x1d, 0xc2, 0x61, 0x1e, 0xe6, 0xc1, 0x0d, + 0x68, 0xa1, 0x1c, 0xf0, 0x81, 0x1e, 0xea, 0x41, 0x1e, 0xca, 0x41, 0x0e, + 0x48, 0x81, 0x0f, 0xec, 0xa1, 0x1c, 0xc6, 0x81, 0x1e, 0xde, 0x41, 0x1e, + 0xf8, 0xc0, 0x1c, 0xd8, 0xe1, 0x1d, 0xc2, 0x81, 0x1e, 0xd8, 0x00, 0x0c, + 0xe8, 0xc0, 0x0f, 0xc0, 0xc0, 0x0f, 0xf4, 0x40, 0x0f, 0xda, 0x21, 0x1d, + 0xe0, 0x61, 0x1e, 0x7e, 0x81, 0x1e, 0xf2, 0x01, 0x1e, 0xca, 0x01, 0x05, + 0xc4, 0x4c, 0x62, 0x30, 0x0e, 0xec, 0x10, 0x0e, 0xf3, 0x30, 0x0f, 0x6e, + 0x40, 0x0b, 0xe5, 0x80, 0x0f, 0xf4, 0x50, 0x0f, 0xf2, 0x50, 0x0e, 0x72, + 0x40, 0x0a, 0x7c, 0x60, 0x0f, 0xe5, 0x30, 0x0e, 0xf4, 0xf0, 0x0e, 0xf2, + 0xc0, 0x07, 0xe6, 0xc0, 0x0e, 0xef, 0x10, 0x0e, 0xf4, 0xc0, 0x06, 0x60, + 0x40, 0x07, 0x7e, 0x00, 0x06, 0x7e, 0x80, 0x04, 0x6d, 0x23, 0x6e, 0x18, + 0x41, 0x18, 0x8e, 0x99, 0xb0, 0x87, 0xf8, 0x39, 0xa7, 0x99, 0x88, 0x6b, + 0x42, 0x81, 0x47, 0xdf, 0x4d, 0xd2, 0x14, 0x51, 0xc2, 0xe4, 0xb3, 0x00, + 0xf3, 0x2c, 0x44, 0xc4, 0x4e, 0xc0, 0x44, 0xa0, 0x80, 0xa0, 0x30, 0x11, + 0x08, 0x00, 0x00, 0x00, 0x13, 0x14, 0x72, 0xc0, 0x87, 0x74, 0x60, 0x87, + 0x36, 0x68, 0x87, 0x79, 0x68, 0x03, 0x72, 0xc0, 0x87, 0x0d, 0xaf, 0x50, + 0x0e, 0x6d, 0xd0, 0x0e, 0x7a, 0x50, 0x0e, 0x6d, 0x00, 0x0f, 0x7a, 0x30, + 0x07, 0x72, 0xa0, 0x07, 0x73, 0x20, 0x07, 0x6d, 0x90, 0x0e, 0x71, 0xa0, + 0x07, 0x73, 0x20, 0x07, 0x6d, 0x90, 0x0e, 0x78, 0xa0, 0x07, 0x73, 0x20, + 0x07, 0x6d, 0x90, 0x0e, 0x71, 0x60, 0x07, 0x7a, 0x30, 0x07, 0x72, 0xd0, + 0x06, 0xe9, 0x30, 0x07, 0x72, 0xa0, 0x07, 0x73, 0x20, 0x07, 0x6d, 0x90, + 0x0e, 0x76, 0x40, 0x07, 0x7a, 0x60, 0x07, 0x74, 0xd0, 0x06, 0xe6, 0x10, + 0x07, 0x76, 0xa0, 0x07, 0x73, 0x20, 0x07, 0x6d, 0x60, 0x0e, 0x73, 0x20, + 0x07, 0x7a, 0x30, 0x07, 0x72, 0xd0, 0x06, 0xe6, 0x60, 0x07, 0x74, 0xa0, + 0x07, 0x76, 0x40, 0x07, 0x6d, 0xe0, 0x0e, 0x78, 0xa0, 0x07, 0x71, 0x60, + 0x07, 0x7a, 0x30, 0x07, 0x72, 0xa0, 0x07, 0x76, 0x40, 0x07, 0x43, 0x9e, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x86, + 0x3c, 0x06, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x0c, 0x79, 0x10, 0x20, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x18, 0xf2, 0x28, 0x40, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x30, 0xe4, 0x79, 0x80, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x60, 0xc8, 0x13, 0x01, 0x01, 0x10, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xc0, 0x90, 0x87, 0x02, 0x02, 0x60, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x21, 0xcf, 0x05, 0x04, 0xc0, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x20, 0x00, 0x00, 0x00, + 0x0f, 0x00, 0x00, 0x00, 0x32, 0x1e, 0x98, 0x14, 0x19, 0x11, 0x4c, 0x90, + 0x8c, 0x09, 0x26, 0x47, 0xc6, 0x04, 0x43, 0x22, 0x4a, 0x60, 0x04, 0xa0, + 0x20, 0x8a, 0xa1, 0x08, 0x4a, 0xa2, 0x0c, 0xca, 0x83, 0x8a, 0x92, 0x18, + 0x01, 0x28, 0x82, 0x42, 0x28, 0x10, 0xea, 0x66, 0x00, 0x08, 0x9c, 0x01, + 0x20, 0x71, 0x06, 0x80, 0xc6, 0xb1, 0x10, 0x83, 0x08, 0x04, 0x02, 0x79, + 0x1e, 0x00, 0x00, 0x00, 0x79, 0x18, 0x00, 0x00, 0x5e, 0x00, 0x00, 0x00, + 0x1a, 0x03, 0x4c, 0x90, 0x46, 0x02, 0x13, 0xc4, 0x8e, 0x0c, 0x6f, 0xec, + 0xed, 0x4d, 0x0c, 0x24, 0xc6, 0x05, 0xc7, 0x45, 0xa6, 0x06, 0x46, 0xc6, + 0x05, 0x07, 0x04, 0x45, 0x8c, 0xe6, 0x26, 0x26, 0x06, 0x67, 0x26, 0xa7, + 0x2c, 0x65, 0x43, 0x10, 0x4c, 0x10, 0x08, 0x64, 0x82, 0x40, 0x24, 0x1b, + 0x84, 0x81, 0x98, 0x20, 0x10, 0xca, 0x06, 0x61, 0x30, 0x28, 0xc0, 0xcd, + 0x4d, 0x10, 0x88, 0x65, 0xc3, 0x80, 0x24, 0xc4, 0x04, 0xa1, 0xab, 0x08, + 0x4c, 0x10, 0x08, 0x66, 0x82, 0x40, 0x34, 0x1b, 0x04, 0xc2, 0xd9, 0x90, + 0x10, 0x0b, 0xd3, 0x10, 0x43, 0x43, 0x3c, 0x1b, 0x02, 0x68, 0x82, 0x00, + 0x06, 0xd6, 0x04, 0x81, 0x70, 0x36, 0x20, 0x84, 0xc4, 0x4c, 0xc4, 0x60, + 0x00, 0x1b, 0x02, 0x6a, 0x82, 0x20, 0x06, 0xd7, 0x06, 0x84, 0xb0, 0x98, + 0x86, 0x18, 0x08, 0x60, 0x43, 0x70, 0x6d, 0x20, 0x22, 0xa0, 0xc2, 0x26, + 0x08, 0x63, 0x80, 0x6d, 0x08, 0xb4, 0x09, 0x82, 0x00, 0x90, 0x68, 0x0b, + 0x4b, 0x73, 0x23, 0x42, 0x55, 0x84, 0x35, 0xf4, 0xf4, 0x24, 0x45, 0x34, + 0x41, 0x28, 0xa0, 0x09, 0x42, 0x11, 0x6d, 0x08, 0x88, 0x09, 0x42, 0x21, + 0x4d, 0x10, 0x8a, 0x69, 0x82, 0x40, 0x3c, 0x1b, 0x84, 0x89, 0x0c, 0x36, + 0x2c, 0x84, 0xf7, 0x81, 0x41, 0x18, 0x88, 0xc1, 0x30, 0x06, 0x04, 0x18, + 0x94, 0xc1, 0x86, 0x60, 0xd8, 0x20, 0x4c, 0xd3, 0x86, 0x65, 0xf0, 0x3e, + 0x30, 0x38, 0x03, 0x31, 0x18, 0xc4, 0x60, 0x00, 0x03, 0x34, 0xd8, 0x20, + 0x98, 0x41, 0x1a, 0x30, 0x99, 0xb2, 0xfa, 0xa2, 0x0a, 0x93, 0x3b, 0x2b, + 0xa3, 0x9b, 0x20, 0x14, 0xd4, 0x86, 0x85, 0x58, 0x83, 0x8f, 0x0d, 0xc2, + 0x00, 0x0c, 0x86, 0x31, 0x20, 0xc0, 0xa0, 0x0c, 0x36, 0x04, 0x6d, 0xb0, + 0x61, 0x50, 0x03, 0x37, 0x00, 0x36, 0x14, 0x5c, 0xf7, 0x06, 0x19, 0x50, + 0x85, 0x8d, 0xcd, 0xae, 0xcd, 0x25, 0x8d, 0xac, 0xcc, 0x8d, 0x6e, 0x4a, + 0x10, 0x54, 0x21, 0xc3, 0x73, 0xb1, 0x2b, 0x93, 0x9b, 0x4b, 0x7b, 0x73, + 0x9b, 0x12, 0x10, 0x4d, 0xc8, 0xf0, 0x5c, 0xec, 0xc2, 0xd8, 0xec, 0xca, + 0xe4, 0xa6, 0x04, 0x46, 0x1d, 0x32, 0x3c, 0x97, 0x39, 0xb4, 0x30, 0xb2, + 0x32, 0xb9, 0xa6, 0x37, 0xb2, 0x32, 0xb6, 0x29, 0x41, 0x52, 0x86, 0x0c, + 0xcf, 0x45, 0xae, 0x6c, 0xee, 0xad, 0x4e, 0x6e, 0xac, 0x6c, 0x6e, 0x4a, + 0x80, 0xd5, 0x21, 0xc3, 0x73, 0xb1, 0x4b, 0x2b, 0xbb, 0x4b, 0x22, 0x9b, + 0xa2, 0x0b, 0xa3, 0x2b, 0x9b, 0x12, 0x68, 0x75, 0xc8, 0xf0, 0x5c, 0xca, + 0xdc, 0xe8, 0xe4, 0xf2, 0xa0, 0xde, 0xd2, 0xdc, 0xe8, 0xe6, 0xa6, 0x04, + 0x6f, 0x00, 0x00, 0x00, 0x79, 0x18, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00, + 0x33, 0x08, 0x80, 0x1c, 0xc4, 0xe1, 0x1c, 0x66, 0x14, 0x01, 0x3d, 0x88, + 0x43, 0x38, 0x84, 0xc3, 0x8c, 0x42, 0x80, 0x07, 0x79, 0x78, 0x07, 0x73, + 0x98, 0x71, 0x0c, 0xe6, 0x00, 0x0f, 0xed, 0x10, 0x0e, 0xf4, 0x80, 0x0e, + 0x33, 0x0c, 0x42, 0x1e, 0xc2, 0xc1, 0x1d, 0xce, 0xa1, 0x1c, 0x66, 0x30, + 0x05, 0x3d, 0x88, 0x43, 0x38, 0x84, 0x83, 0x1b, 0xcc, 0x03, 0x3d, 0xc8, + 0x43, 0x3d, 0x8c, 0x03, 0x3d, 0xcc, 0x78, 0x8c, 0x74, 0x70, 0x07, 0x7b, + 0x08, 0x07, 0x79, 0x48, 0x87, 0x70, 0x70, 0x07, 0x7a, 0x70, 0x03, 0x76, + 0x78, 0x87, 0x70, 0x20, 0x87, 0x19, 0xcc, 0x11, 0x0e, 0xec, 0x90, 0x0e, + 0xe1, 0x30, 0x0f, 0x6e, 0x30, 0x0f, 0xe3, 0xf0, 0x0e, 0xf0, 0x50, 0x0e, + 0x33, 0x10, 0xc4, 0x1d, 0xde, 0x21, 0x1c, 0xd8, 0x21, 0x1d, 0xc2, 0x61, + 0x1e, 0x66, 0x30, 0x89, 0x3b, 0xbc, 0x83, 0x3b, 0xd0, 0x43, 0x39, 0xb4, + 0x03, 0x3c, 0xbc, 0x83, 0x3c, 0x84, 0x03, 0x3b, 0xcc, 0xf0, 0x14, 0x76, + 0x60, 0x07, 0x7b, 0x68, 0x07, 0x37, 0x68, 0x87, 0x72, 0x68, 0x07, 0x37, + 0x80, 0x87, 0x70, 0x90, 0x87, 0x70, 0x60, 0x07, 0x76, 0x28, 0x07, 0x76, + 0xf8, 0x05, 0x76, 0x78, 0x87, 0x77, 0x80, 0x87, 0x5f, 0x08, 0x87, 0x71, + 0x18, 0x87, 0x72, 0x98, 0x87, 0x79, 0x98, 0x81, 0x2c, 0xee, 0xf0, 0x0e, + 0xee, 0xe0, 0x0e, 0xf5, 0xc0, 0x0e, 0xec, 0x30, 0x03, 0x62, 0xc8, 0xa1, + 0x1c, 0xe4, 0xa1, 0x1c, 0xcc, 0xa1, 0x1c, 0xe4, 0xa1, 0x1c, 0xdc, 0x61, + 0x1c, 0xca, 0x21, 0x1c, 0xc4, 0x81, 0x1d, 0xca, 0x61, 0x06, 0xd6, 0x90, + 0x43, 0x39, 0xc8, 0x43, 0x39, 0x98, 0x43, 0x39, 0xc8, 0x43, 0x39, 0xb8, + 0xc3, 0x38, 0x94, 0x43, 0x38, 0x88, 0x03, 0x3b, 0x94, 0xc3, 0x2f, 0xbc, + 0x83, 0x3c, 0xfc, 0x82, 0x3b, 0xd4, 0x03, 0x3b, 0xb0, 0xc3, 0x8c, 0xc8, + 0x21, 0x07, 0x7c, 0x70, 0x03, 0x72, 0x10, 0x87, 0x73, 0x70, 0x03, 0x7b, + 0x08, 0x07, 0x79, 0x60, 0x87, 0x70, 0xc8, 0x87, 0x77, 0xa8, 0x07, 0x7a, + 0x98, 0x81, 0x3c, 0xe4, 0x80, 0x0f, 0x6e, 0x40, 0x0f, 0xe5, 0xd0, 0x0e, + 0xf0, 0x00, 0x00, 0x00, 0x71, 0x20, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, + 0x66, 0xb0, 0x0d, 0x97, 0xef, 0x3c, 0xbe, 0x10, 0x50, 0x45, 0x41, 0x44, + 0xa5, 0x03, 0x0c, 0x25, 0x61, 0x00, 0x02, 0xe6, 0x17, 0xb7, 0x6d, 0x07, + 0xd2, 0x70, 0xf9, 0xce, 0xe3, 0x0b, 0x11, 0x01, 0x4c, 0x44, 0x08, 0x34, + 0xc3, 0x42, 0xd8, 0x80, 0x33, 0x5c, 0xbe, 0xf3, 0xf8, 0x83, 0x33, 0xd9, + 0x7e, 0x71, 0xdb, 0x16, 0x30, 0x0d, 0x97, 0xef, 0x3c, 0xfe, 0xe2, 0x00, + 0x83, 0xd8, 0x3c, 0xd4, 0xe4, 0x17, 0xb7, 0x6d, 0x04, 0xd0, 0x70, 0xf9, + 0xce, 0xe3, 0x4b, 0x00, 0xf3, 0x2c, 0x84, 0x5f, 0xdc, 0xb6, 0x09, 0x54, + 0xc3, 0xe5, 0x3b, 0x8f, 0x2f, 0x4d, 0x4e, 0x44, 0xa0, 0xd4, 0xf4, 0x50, + 0x93, 0x5f, 0xdc, 0xb6, 0x15, 0x3c, 0xc3, 0xe5, 0x3b, 0x8f, 0x4f, 0x35, + 0x40, 0x84, 0xf9, 0xc5, 0x6d, 0x1b, 0x00, 0xc1, 0x00, 0x48, 0x03, 0x00, + 0x61, 0x20, 0x00, 0x00, 0x8f, 0x00, 0x00, 0x00, 0x13, 0x04, 0x41, 0x2c, + 0x10, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x64, 0x8d, 0x00, 0x50, + 0x51, 0x02, 0x65, 0x40, 0x44, 0xb1, 0x94, 0x46, 0xd9, 0x15, 0x5e, 0xb1, + 0x95, 0x5c, 0x29, 0x14, 0xc2, 0x0c, 0x00, 0x0d, 0x63, 0x04, 0xf6, 0xa9, + 0x82, 0xf8, 0x37, 0x46, 0xa0, 0xb3, 0xe6, 0xcc, 0x82, 0xc1, 0x18, 0x41, + 0x0d, 0xaf, 0x2f, 0xfd, 0x8d, 0x11, 0xd0, 0xf0, 0xfa, 0xdf, 0xdf, 0x18, + 0x01, 0x08, 0x82, 0x20, 0xff, 0x8d, 0x11, 0x80, 0x20, 0x08, 0x92, 0x60, + 0x30, 0x46, 0xb0, 0xd6, 0x6a, 0xad, 0x7e, 0x63, 0x04, 0x20, 0x08, 0x82, + 0xf6, 0x37, 0x46, 0x00, 0x82, 0x20, 0x08, 0x7e, 0x63, 0x04, 0xbb, 0x3f, + 0xca, 0x24, 0x18, 0x8c, 0x11, 0xb4, 0xe6, 0x9c, 0xd3, 0xde, 0x18, 0x01, + 0x08, 0x82, 0x20, 0x08, 0x0a, 0x63, 0x04, 0x20, 0x08, 0x82, 0xf8, 0x37, + 0x46, 0x00, 0x82, 0x20, 0x08, 0xff, 0xc2, 0x0c, 0x00, 0x00, 0x00, 0x00, + 0x23, 0x06, 0x09, 0x00, 0x82, 0x60, 0x70, 0xb5, 0xc1, 0x34, 0x06, 0x6b, + 0xb0, 0x06, 0xdb, 0x88, 0x41, 0x02, 0x80, 0x20, 0x18, 0x5c, 0x6e, 0x40, + 0x69, 0x6c, 0xc0, 0x06, 0xdc, 0x88, 0x41, 0x02, 0x80, 0x20, 0x18, 0x5c, + 0x6f, 0x50, 0x91, 0x41, 0x1b, 0xb4, 0x41, 0x37, 0x62, 0x90, 0x00, 0x20, + 0x08, 0x06, 0x86, 0x1d, 0x50, 0x6f, 0xe0, 0x06, 0x66, 0x30, 0x8d, 0x18, + 0x24, 0x00, 0x08, 0x82, 0x81, 0x71, 0x07, 0x15, 0x1c, 0xbc, 0x81, 0x47, + 0x8d, 0x18, 0x24, 0x00, 0x08, 0x82, 0x81, 0x81, 0x07, 0x16, 0x1c, 0xc0, + 0x01, 0x1a, 0x54, 0x23, 0x06, 0x09, 0x00, 0x82, 0x60, 0x60, 0xe4, 0xc1, + 0x15, 0x07, 0x71, 0x00, 0x06, 0xd6, 0x88, 0x41, 0x02, 0x80, 0x20, 0x18, + 0x18, 0x7a, 0x80, 0xc9, 0x81, 0x1c, 0xa4, 0xc1, 0x35, 0x62, 0x90, 0x00, + 0x20, 0x08, 0x06, 0xc6, 0x1e, 0x64, 0x73, 0x30, 0x07, 0x61, 0x80, 0x99, + 0xb1, 0xc0, 0xc7, 0x0c, 0x06, 0x3e, 0x23, 0x06, 0x09, 0x00, 0x82, 0x60, + 0xa0, 0xf0, 0xc1, 0x27, 0x04, 0x42, 0x60, 0x41, 0x24, 0x1f, 0x0b, 0x08, + 0xf9, 0x98, 0x40, 0xc8, 0xc7, 0x04, 0x06, 0x3e, 0x26, 0x30, 0xf0, 0x19, + 0x31, 0x78, 0x00, 0x10, 0x04, 0x83, 0x27, 0x14, 0xcc, 0x20, 0x82, 0x9c, + 0x46, 0x92, 0xf8, 0x80, 0x0f, 0xc2, 0x40, 0x1a, 0x4d, 0x08, 0x80, 0xd1, + 0x04, 0x21, 0x18, 0x4d, 0x18, 0x84, 0xd1, 0x04, 0x62, 0xb0, 0xc0, 0x91, + 0x8f, 0x69, 0x48, 0x7c, 0x6c, 0x43, 0xe2, 0x33, 0x62, 0x70, 0x00, 0x20, + 0x08, 0x06, 0xd4, 0x29, 0xbc, 0xc1, 0x45, 0x0a, 0xa3, 0x09, 0x41, 0x60, + 0x4b, 0x18, 0xc8, 0xc7, 0x02, 0x41, 0x3e, 0x23, 0x06, 0x06, 0x00, 0x82, + 0x60, 0x10, 0xb1, 0x02, 0x1d, 0x04, 0x16, 0x94, 0x81, 0x7c, 0x2c, 0x38, + 0x03, 0xf8, 0x8c, 0x26, 0x1c, 0x80, 0x49, 0x6a, 0x20, 0x1f, 0x0b, 0x04, + 0xf9, 0xd8, 0x19, 0x04, 0xf1, 0x19, 0x8e, 0x10, 0x82, 0xe1, 0x1b, 0x31, + 0x30, 0x00, 0x10, 0x04, 0x83, 0x88, 0x16, 0x5c, 0x61, 0x18, 0x31, 0x30, + 0x00, 0x10, 0x04, 0x83, 0xa8, 0x16, 0xfc, 0x20, 0xb0, 0x35, 0x08, 0xe2, + 0x33, 0xdd, 0x20, 0x04, 0x84, 0x05, 0x73, 0x20, 0x9f, 0xe1, 0x88, 0xa0, + 0x0e, 0x88, 0x6f, 0xba, 0xe1, 0x0e, 0xf4, 0x20, 0xb0, 0x4e, 0x0f, 0xe4, + 0x63, 0x41, 0x27, 0x1f, 0x0b, 0x2e, 0xf9, 0x58, 0x70, 0xc9, 0xc7, 0x82, + 0x3e, 0x80, 0x8f, 0x49, 0x81, 0x7c, 0x2c, 0x38, 0xe4, 0x63, 0x61, 0xe0, + 0x06, 0xf2, 0xb1, 0x40, 0x90, 0x8f, 0x89, 0xc1, 0x1b, 0xc8, 0xc7, 0x02, + 0x42, 0x3e, 0x36, 0x06, 0x70, 0x20, 0x1f, 0x0b, 0x0c, 0xf9, 0x8c, 0x18, + 0x24, 0x00, 0x08, 0x82, 0x01, 0xb2, 0x0e, 0xab, 0x40, 0x0e, 0xe4, 0xc0, + 0x0b, 0xc5, 0x88, 0x41, 0x02, 0x80, 0x20, 0x18, 0x20, 0xeb, 0xb0, 0x0a, + 0xe4, 0x40, 0x0e, 0xb3, 0x30, 0x8c, 0x18, 0x24, 0x00, 0x08, 0x82, 0x01, + 0xb2, 0x0e, 0xab, 0x40, 0x0e, 0xe4, 0xb0, 0x0b, 0xc1, 0x88, 0x41, 0x02, + 0x80, 0x20, 0x18, 0x20, 0xeb, 0xb0, 0x0a, 0xe4, 0x40, 0x0e, 0xb2, 0x30, + 0x06, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; +static const unsigned int testgpu_effects_CRT_frag_dxil_len = 4664; diff --git a/libs/SDL3/test/testgpurender_effects_CRT.frag.hlsl b/libs/SDL3/test/testgpurender_effects_CRT.frag.hlsl new file mode 100644 index 00000000..af6b9d7a --- /dev/null +++ b/libs/SDL3/test/testgpurender_effects_CRT.frag.hlsl @@ -0,0 +1,56 @@ +// This shader is adapted from the Lightweight CRT Effect at: +// https://godotshaders.com/shader/lightweight-crt-effect/ + +cbuffer Context : register(b0, space3) { + float2 resolution; +}; + +Texture2D u_texture : register(t0, space2); +SamplerState u_sampler : register(s0, space2); + +struct PSInput { + float4 v_color : COLOR0; + float2 v_uv : TEXCOORD0; +}; + +struct PSOutput { + float4 o_color : SV_Target; +}; + +static const float PI = 3.14159265f; + +// These could be turned into uniforms if you wanted to tweak them +static const float scan_line_amount = 0.5; // Range 0-1 +static const float warp_amount = 0.05; // Range 0-1 +static const float vignette_amount = 0.5; // Range 0-1 +static const float vignette_intensity = 0.3; // Range 0-1 +static const float grille_amount = 0.05; // Range 0-1 +static const float brightness_boost = 1.2; // Range 1-2 + +PSOutput main(PSInput input) { + PSOutput output; + + float2 uv = input.v_uv; + + float2 delta = uv - 0.5; + float warp_factor = dot(delta, delta) * warp_amount; + uv += delta * warp_factor; + + float scanline = sin(uv.y * resolution.y * PI) * 0.5 + 0.5; + scanline = lerp(1.0, scanline, scan_line_amount * 0.5); + + float grille = fmod(uv.x * resolution.x, 3.0) < 1.5 ? 0.95 : 1.05; + grille = lerp(1.0, grille, grille_amount * 0.5); + + float4 color = u_texture.Sample(u_sampler, input.v_uv) * input.v_color; + color.rgb *= scanline * grille; + + float2 v_uv = uv * (1.0 - uv.xy); + float vignette = v_uv.x * v_uv.y * 15.0; + vignette = lerp(1.0, vignette, vignette_amount * 0.7); + + color.rgb *= vignette * brightness_boost; + + output.o_color = color; + return output; +} diff --git a/libs/SDL3/test/testgpurender_effects_CRT.frag.msl.h b/libs/SDL3/test/testgpurender_effects_CRT.frag.msl.h new file mode 100644 index 00000000..b12e388c --- /dev/null +++ b/libs/SDL3/test/testgpurender_effects_CRT.frag.msl.h @@ -0,0 +1,106 @@ +static const unsigned char testgpu_effects_CRT_frag_msl[] = { + 0x23, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x20, 0x3c, 0x6d, 0x65, + 0x74, 0x61, 0x6c, 0x5f, 0x73, 0x74, 0x64, 0x6c, 0x69, 0x62, 0x3e, 0x0a, + 0x23, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x20, 0x3c, 0x73, 0x69, + 0x6d, 0x64, 0x2f, 0x73, 0x69, 0x6d, 0x64, 0x2e, 0x68, 0x3e, 0x0a, 0x0a, + 0x75, 0x73, 0x69, 0x6e, 0x67, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, + 0x61, 0x63, 0x65, 0x20, 0x6d, 0x65, 0x74, 0x61, 0x6c, 0x3b, 0x0a, 0x0a, + 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x20, 0x74, 0x79, 0x70, 0x65, 0x5f, + 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x0a, 0x7b, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x32, 0x20, 0x72, 0x65, 0x73, + 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x3b, 0x0a, 0x7d, 0x3b, 0x0a, + 0x0a, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x20, 0x6d, 0x61, 0x69, 0x6e, + 0x30, 0x5f, 0x6f, 0x75, 0x74, 0x0a, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x34, 0x20, 0x6f, 0x75, 0x74, 0x5f, 0x76, + 0x61, 0x72, 0x5f, 0x53, 0x56, 0x5f, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, + 0x20, 0x5b, 0x5b, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x28, 0x30, 0x29, 0x5d, + 0x5d, 0x3b, 0x0a, 0x7d, 0x3b, 0x0a, 0x0a, 0x73, 0x74, 0x72, 0x75, 0x63, + 0x74, 0x20, 0x6d, 0x61, 0x69, 0x6e, 0x30, 0x5f, 0x69, 0x6e, 0x0a, 0x7b, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x34, 0x20, + 0x69, 0x6e, 0x5f, 0x76, 0x61, 0x72, 0x5f, 0x43, 0x4f, 0x4c, 0x4f, 0x52, + 0x30, 0x20, 0x5b, 0x5b, 0x75, 0x73, 0x65, 0x72, 0x28, 0x6c, 0x6f, 0x63, + 0x6e, 0x30, 0x29, 0x5d, 0x5d, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, + 0x6c, 0x6f, 0x61, 0x74, 0x32, 0x20, 0x69, 0x6e, 0x5f, 0x76, 0x61, 0x72, + 0x5f, 0x54, 0x45, 0x58, 0x43, 0x4f, 0x4f, 0x52, 0x44, 0x30, 0x20, 0x5b, + 0x5b, 0x75, 0x73, 0x65, 0x72, 0x28, 0x6c, 0x6f, 0x63, 0x6e, 0x31, 0x29, + 0x5d, 0x5d, 0x3b, 0x0a, 0x7d, 0x3b, 0x0a, 0x0a, 0x66, 0x72, 0x61, 0x67, + 0x6d, 0x65, 0x6e, 0x74, 0x20, 0x6d, 0x61, 0x69, 0x6e, 0x30, 0x5f, 0x6f, + 0x75, 0x74, 0x20, 0x6d, 0x61, 0x69, 0x6e, 0x30, 0x28, 0x6d, 0x61, 0x69, + 0x6e, 0x30, 0x5f, 0x69, 0x6e, 0x20, 0x69, 0x6e, 0x20, 0x5b, 0x5b, 0x73, + 0x74, 0x61, 0x67, 0x65, 0x5f, 0x69, 0x6e, 0x5d, 0x5d, 0x2c, 0x20, 0x63, + 0x6f, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x74, 0x20, 0x74, 0x79, 0x70, 0x65, + 0x5f, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x26, 0x20, 0x43, 0x6f, + 0x6e, 0x74, 0x65, 0x78, 0x74, 0x20, 0x5b, 0x5b, 0x62, 0x75, 0x66, 0x66, + 0x65, 0x72, 0x28, 0x30, 0x29, 0x5d, 0x5d, 0x2c, 0x20, 0x74, 0x65, 0x78, + 0x74, 0x75, 0x72, 0x65, 0x32, 0x64, 0x3c, 0x66, 0x6c, 0x6f, 0x61, 0x74, + 0x3e, 0x20, 0x75, 0x5f, 0x74, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x20, + 0x5b, 0x5b, 0x74, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x28, 0x30, 0x29, + 0x5d, 0x5d, 0x2c, 0x20, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x20, + 0x75, 0x5f, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x20, 0x5b, 0x5b, + 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x28, 0x30, 0x29, 0x5d, 0x5d, + 0x29, 0x0a, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, 0x69, 0x6e, + 0x30, 0x5f, 0x6f, 0x75, 0x74, 0x20, 0x6f, 0x75, 0x74, 0x20, 0x3d, 0x20, + 0x7b, 0x7d, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, + 0x74, 0x32, 0x20, 0x5f, 0x34, 0x39, 0x20, 0x3d, 0x20, 0x69, 0x6e, 0x2e, + 0x69, 0x6e, 0x5f, 0x76, 0x61, 0x72, 0x5f, 0x54, 0x45, 0x58, 0x43, 0x4f, + 0x4f, 0x52, 0x44, 0x30, 0x20, 0x2d, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, + 0x32, 0x28, 0x30, 0x2e, 0x35, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x32, 0x20, 0x5f, 0x35, 0x33, 0x20, 0x3d, + 0x20, 0x69, 0x6e, 0x2e, 0x69, 0x6e, 0x5f, 0x76, 0x61, 0x72, 0x5f, 0x54, + 0x45, 0x58, 0x43, 0x4f, 0x4f, 0x52, 0x44, 0x30, 0x20, 0x2b, 0x20, 0x28, + 0x5f, 0x34, 0x39, 0x20, 0x2a, 0x20, 0x28, 0x64, 0x6f, 0x74, 0x28, 0x5f, + 0x34, 0x39, 0x2c, 0x20, 0x5f, 0x34, 0x39, 0x29, 0x20, 0x2a, 0x20, 0x30, + 0x2e, 0x30, 0x35, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x37, 0x34, + 0x35, 0x30, 0x35, 0x38, 0x30, 0x35, 0x39, 0x36, 0x39, 0x32, 0x33, 0x38, + 0x32, 0x38, 0x31, 0x32, 0x35, 0x29, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x34, 0x20, 0x5f, 0x37, 0x35, 0x20, + 0x3d, 0x20, 0x75, 0x5f, 0x74, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x2e, + 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x28, 0x75, 0x5f, 0x73, 0x61, 0x6d, + 0x70, 0x6c, 0x65, 0x72, 0x2c, 0x20, 0x69, 0x6e, 0x2e, 0x69, 0x6e, 0x5f, + 0x76, 0x61, 0x72, 0x5f, 0x54, 0x45, 0x58, 0x43, 0x4f, 0x4f, 0x52, 0x44, + 0x30, 0x29, 0x20, 0x2a, 0x20, 0x69, 0x6e, 0x2e, 0x69, 0x6e, 0x5f, 0x76, + 0x61, 0x72, 0x5f, 0x43, 0x4f, 0x4c, 0x4f, 0x52, 0x30, 0x3b, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x32, 0x20, 0x5f, 0x38, + 0x30, 0x20, 0x3d, 0x20, 0x5f, 0x35, 0x33, 0x20, 0x2a, 0x20, 0x28, 0x66, + 0x6c, 0x6f, 0x61, 0x74, 0x32, 0x28, 0x31, 0x2e, 0x30, 0x29, 0x20, 0x2d, + 0x20, 0x5f, 0x35, 0x33, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, + 0x6c, 0x6f, 0x61, 0x74, 0x33, 0x20, 0x5f, 0x38, 0x38, 0x20, 0x3d, 0x20, + 0x28, 0x5f, 0x37, 0x35, 0x2e, 0x78, 0x79, 0x7a, 0x20, 0x2a, 0x20, 0x28, + 0x6d, 0x69, 0x78, 0x28, 0x31, 0x2e, 0x30, 0x2c, 0x20, 0x28, 0x73, 0x69, + 0x6e, 0x28, 0x28, 0x5f, 0x35, 0x33, 0x2e, 0x79, 0x20, 0x2a, 0x20, 0x43, + 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x6c, + 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x79, 0x29, 0x20, 0x2a, 0x20, 0x33, + 0x2e, 0x31, 0x34, 0x31, 0x35, 0x39, 0x32, 0x37, 0x34, 0x31, 0x30, 0x31, + 0x32, 0x35, 0x37, 0x33, 0x32, 0x34, 0x32, 0x31, 0x38, 0x37, 0x35, 0x29, + 0x20, 0x2a, 0x20, 0x30, 0x2e, 0x35, 0x29, 0x20, 0x2b, 0x20, 0x30, 0x2e, + 0x35, 0x2c, 0x20, 0x30, 0x2e, 0x32, 0x35, 0x29, 0x20, 0x2a, 0x20, 0x6d, + 0x69, 0x78, 0x28, 0x31, 0x2e, 0x30, 0x2c, 0x20, 0x28, 0x66, 0x6d, 0x6f, + 0x64, 0x28, 0x5f, 0x35, 0x33, 0x2e, 0x78, 0x20, 0x2a, 0x20, 0x43, 0x6f, + 0x6e, 0x74, 0x65, 0x78, 0x74, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x6c, 0x75, + 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x78, 0x2c, 0x20, 0x33, 0x2e, 0x30, 0x29, + 0x20, 0x3c, 0x20, 0x31, 0x2e, 0x35, 0x29, 0x20, 0x3f, 0x20, 0x30, 0x2e, + 0x39, 0x34, 0x39, 0x39, 0x39, 0x39, 0x39, 0x38, 0x38, 0x30, 0x37, 0x39, + 0x30, 0x37, 0x31, 0x30, 0x34, 0x34, 0x39, 0x32, 0x31, 0x38, 0x37, 0x35, + 0x20, 0x3a, 0x20, 0x31, 0x2e, 0x30, 0x34, 0x39, 0x39, 0x39, 0x39, 0x39, + 0x35, 0x32, 0x33, 0x31, 0x36, 0x32, 0x38, 0x34, 0x31, 0x37, 0x39, 0x36, + 0x38, 0x37, 0x35, 0x2c, 0x20, 0x30, 0x2e, 0x30, 0x32, 0x35, 0x30, 0x30, + 0x30, 0x30, 0x30, 0x30, 0x33, 0x37, 0x32, 0x35, 0x32, 0x39, 0x30, 0x32, + 0x39, 0x38, 0x34, 0x36, 0x31, 0x39, 0x31, 0x34, 0x30, 0x36, 0x32, 0x35, + 0x29, 0x29, 0x29, 0x2e, 0x78, 0x79, 0x7a, 0x20, 0x2a, 0x20, 0x28, 0x6d, + 0x69, 0x78, 0x28, 0x31, 0x2e, 0x30, 0x2c, 0x20, 0x28, 0x5f, 0x38, 0x30, + 0x2e, 0x78, 0x20, 0x2a, 0x20, 0x5f, 0x38, 0x30, 0x2e, 0x79, 0x29, 0x20, + 0x2a, 0x20, 0x31, 0x35, 0x2e, 0x30, 0x2c, 0x20, 0x30, 0x2e, 0x33, 0x34, + 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x34, 0x30, 0x33, 0x39, 0x35, 0x33, + 0x35, 0x35, 0x32, 0x32, 0x34, 0x36, 0x30, 0x39, 0x33, 0x37, 0x35, 0x29, + 0x20, 0x2a, 0x20, 0x31, 0x2e, 0x32, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, + 0x34, 0x37, 0x36, 0x38, 0x33, 0x37, 0x31, 0x35, 0x38, 0x32, 0x30, 0x33, + 0x31, 0x32, 0x35, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6f, 0x75, + 0x74, 0x2e, 0x6f, 0x75, 0x74, 0x5f, 0x76, 0x61, 0x72, 0x5f, 0x53, 0x56, + 0x5f, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x20, 0x3d, 0x20, 0x66, 0x6c, + 0x6f, 0x61, 0x74, 0x34, 0x28, 0x5f, 0x38, 0x38, 0x2e, 0x78, 0x2c, 0x20, + 0x5f, 0x38, 0x38, 0x2e, 0x79, 0x2c, 0x20, 0x5f, 0x38, 0x38, 0x2e, 0x7a, + 0x2c, 0x20, 0x5f, 0x37, 0x35, 0x2e, 0x77, 0x29, 0x3b, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x6f, 0x75, 0x74, + 0x3b, 0x0a, 0x7d, 0x0a, 0x0a +}; +static const unsigned int testgpu_effects_CRT_frag_msl_len = 1229; diff --git a/libs/SDL3/test/testgpurender_effects_CRT.frag.spv.h b/libs/SDL3/test/testgpurender_effects_CRT.frag.spv.h new file mode 100644 index 00000000..068e5ba0 --- /dev/null +++ b/libs/SDL3/test/testgpurender_effects_CRT.frag.spv.h @@ -0,0 +1,189 @@ +static const unsigned char testgpu_effects_CRT_frag_spv[] = { + 0x03, 0x02, 0x23, 0x07, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0e, 0x00, + 0x5a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x02, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x06, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x47, 0x4c, 0x53, 0x4c, 0x2e, 0x73, 0x74, 0x64, 0x2e, 0x34, 0x35, 0x30, + 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x08, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x6d, 0x61, 0x69, 0x6e, 0x00, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, + 0x10, 0x00, 0x03, 0x00, 0x02, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x03, 0x00, 0x05, 0x00, 0x00, 0x00, 0x58, 0x02, 0x00, 0x00, + 0x05, 0x00, 0x06, 0x00, 0x06, 0x00, 0x00, 0x00, 0x74, 0x79, 0x70, 0x65, + 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x00, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x72, 0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x00, + 0x05, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00, 0x43, 0x6f, 0x6e, 0x74, + 0x65, 0x78, 0x74, 0x00, 0x05, 0x00, 0x06, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x74, 0x79, 0x70, 0x65, 0x2e, 0x32, 0x64, 0x2e, 0x69, 0x6d, 0x61, 0x67, + 0x65, 0x00, 0x00, 0x00, 0x05, 0x00, 0x05, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x75, 0x5f, 0x74, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x06, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x74, 0x79, 0x70, 0x65, + 0x2e, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x00, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x05, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x75, 0x5f, 0x73, 0x61, + 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x00, 0x00, 0x00, 0x05, 0x00, 0x06, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x69, 0x6e, 0x2e, 0x76, 0x61, 0x72, 0x2e, 0x43, + 0x4f, 0x4c, 0x4f, 0x52, 0x30, 0x00, 0x00, 0x00, 0x05, 0x00, 0x07, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x69, 0x6e, 0x2e, 0x76, 0x61, 0x72, 0x2e, 0x54, + 0x45, 0x58, 0x43, 0x4f, 0x4f, 0x52, 0x44, 0x30, 0x00, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x07, 0x00, 0x05, 0x00, 0x00, 0x00, 0x6f, 0x75, 0x74, 0x2e, + 0x76, 0x61, 0x72, 0x2e, 0x53, 0x56, 0x5f, 0x54, 0x61, 0x72, 0x67, 0x65, + 0x74, 0x00, 0x00, 0x00, 0x05, 0x00, 0x04, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x6d, 0x61, 0x69, 0x6e, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x07, 0x00, + 0x0c, 0x00, 0x00, 0x00, 0x74, 0x79, 0x70, 0x65, 0x2e, 0x73, 0x61, 0x6d, + 0x70, 0x6c, 0x65, 0x64, 0x2e, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x00, 0x00, + 0x47, 0x00, 0x04, 0x00, 0x03, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x1e, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x47, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00, + 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x47, 0x00, 0x04, 0x00, 0x09, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x0b, 0x00, 0x00, 0x00, + 0x22, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, + 0x0b, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x48, 0x00, 0x05, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x03, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x16, 0x00, 0x03, 0x00, + 0x0d, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, 0x00, + 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0xdb, 0x0f, 0x49, 0x40, + 0x2b, 0x00, 0x04, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x3f, 0x2b, 0x00, 0x04, 0x00, 0x0d, 0x00, 0x00, 0x00, + 0x10, 0x00, 0x00, 0x00, 0xcd, 0xcc, 0x4c, 0x3d, 0x2b, 0x00, 0x04, 0x00, + 0x0d, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x9a, 0x99, 0x99, 0x3f, + 0x15, 0x00, 0x04, 0x00, 0x12, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, 0x00, 0x12, 0x00, 0x00, 0x00, + 0x13, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x17, 0x00, 0x04, 0x00, + 0x14, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x2c, 0x00, 0x05, 0x00, 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, + 0x0f, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, 0x00, + 0x12, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x2b, 0x00, 0x04, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x80, 0x3f, 0x2b, 0x00, 0x04, 0x00, 0x0d, 0x00, 0x00, 0x00, + 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x40, 0x2b, 0x00, 0x04, 0x00, + 0x0d, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x3f, + 0x2b, 0x00, 0x04, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, + 0x33, 0x33, 0x73, 0x3f, 0x2b, 0x00, 0x04, 0x00, 0x0d, 0x00, 0x00, 0x00, + 0x1b, 0x00, 0x00, 0x00, 0x66, 0x66, 0x86, 0x3f, 0x2c, 0x00, 0x05, 0x00, + 0x14, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, + 0x17, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, 0x00, 0x0d, 0x00, 0x00, 0x00, + 0x1d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x41, 0x1e, 0x00, 0x03, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, + 0x1e, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, + 0x19, 0x00, 0x09, 0x00, 0x08, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x20, 0x00, 0x04, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x02, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x20, 0x00, 0x04, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x17, 0x00, 0x04, 0x00, 0x21, 0x00, 0x00, 0x00, + 0x0d, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, + 0x22, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, + 0x20, 0x00, 0x04, 0x00, 0x23, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x14, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x24, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x13, 0x00, 0x02, 0x00, + 0x25, 0x00, 0x00, 0x00, 0x21, 0x00, 0x03, 0x00, 0x26, 0x00, 0x00, 0x00, + 0x25, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x27, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x14, 0x00, 0x02, 0x00, + 0x28, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x03, 0x00, 0x0c, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x17, 0x00, 0x04, 0x00, 0x29, 0x00, 0x00, 0x00, + 0x0d, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00, + 0x1e, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x3b, 0x00, 0x04, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00, 0x20, 0x00, 0x00, 0x00, + 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00, + 0x22, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x3b, 0x00, 0x04, 0x00, 0x23, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00, 0x24, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, 0x00, + 0x0d, 0x00, 0x00, 0x00, 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3e, + 0x2b, 0x00, 0x04, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x00, 0x00, + 0xcd, 0xcc, 0xcc, 0x3c, 0x2b, 0x00, 0x04, 0x00, 0x0d, 0x00, 0x00, 0x00, + 0x2c, 0x00, 0x00, 0x00, 0x33, 0x33, 0xb3, 0x3e, 0x2e, 0x00, 0x03, 0x00, + 0x21, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x00, 0x00, 0x36, 0x00, 0x05, 0x00, + 0x25, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x26, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00, 0x2e, 0x00, 0x00, 0x00, + 0x3d, 0x00, 0x04, 0x00, 0x21, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, 0x14, 0x00, 0x00, 0x00, + 0x30, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x83, 0x00, 0x05, 0x00, + 0x14, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, + 0x15, 0x00, 0x00, 0x00, 0x94, 0x00, 0x05, 0x00, 0x0d, 0x00, 0x00, 0x00, + 0x32, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, + 0x85, 0x00, 0x05, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, + 0x32, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x8e, 0x00, 0x05, 0x00, + 0x14, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, + 0x33, 0x00, 0x00, 0x00, 0x81, 0x00, 0x05, 0x00, 0x14, 0x00, 0x00, 0x00, + 0x35, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, + 0x51, 0x00, 0x05, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, + 0x35, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x41, 0x00, 0x06, 0x00, + 0x27, 0x00, 0x00, 0x00, 0x37, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, + 0x16, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, + 0x0d, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x37, 0x00, 0x00, 0x00, + 0x85, 0x00, 0x05, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, + 0x36, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x85, 0x00, 0x05, 0x00, + 0x0d, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, + 0x0e, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x06, 0x00, 0x0d, 0x00, 0x00, 0x00, + 0x3b, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, + 0x3a, 0x00, 0x00, 0x00, 0x85, 0x00, 0x05, 0x00, 0x0d, 0x00, 0x00, 0x00, + 0x3c, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, + 0x81, 0x00, 0x05, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x00, 0x00, + 0x3c, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x08, 0x00, + 0x0d, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x2e, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x00, 0x00, + 0x2a, 0x00, 0x00, 0x00, 0x51, 0x00, 0x05, 0x00, 0x0d, 0x00, 0x00, 0x00, + 0x3f, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x41, 0x00, 0x06, 0x00, 0x27, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, + 0x3d, 0x00, 0x04, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x41, 0x00, 0x00, 0x00, + 0x40, 0x00, 0x00, 0x00, 0x85, 0x00, 0x05, 0x00, 0x0d, 0x00, 0x00, 0x00, + 0x42, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x41, 0x00, 0x00, 0x00, + 0x8c, 0x00, 0x05, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x43, 0x00, 0x00, 0x00, + 0x42, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0xb8, 0x00, 0x05, 0x00, + 0x28, 0x00, 0x00, 0x00, 0x44, 0x00, 0x00, 0x00, 0x43, 0x00, 0x00, 0x00, + 0x19, 0x00, 0x00, 0x00, 0xa9, 0x00, 0x06, 0x00, 0x0d, 0x00, 0x00, 0x00, + 0x45, 0x00, 0x00, 0x00, 0x44, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, + 0x1b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x08, 0x00, 0x0d, 0x00, 0x00, 0x00, + 0x46, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x2e, 0x00, 0x00, 0x00, + 0x17, 0x00, 0x00, 0x00, 0x45, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x00, 0x00, + 0x3d, 0x00, 0x04, 0x00, 0x08, 0x00, 0x00, 0x00, 0x47, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x48, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x56, 0x00, 0x05, 0x00, + 0x0c, 0x00, 0x00, 0x00, 0x49, 0x00, 0x00, 0x00, 0x47, 0x00, 0x00, 0x00, + 0x48, 0x00, 0x00, 0x00, 0x57, 0x00, 0x06, 0x00, 0x21, 0x00, 0x00, 0x00, + 0x4a, 0x00, 0x00, 0x00, 0x49, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x85, 0x00, 0x05, 0x00, 0x21, 0x00, 0x00, 0x00, + 0x4b, 0x00, 0x00, 0x00, 0x4a, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00, + 0x85, 0x00, 0x05, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00, + 0x3e, 0x00, 0x00, 0x00, 0x46, 0x00, 0x00, 0x00, 0x4f, 0x00, 0x08, 0x00, + 0x29, 0x00, 0x00, 0x00, 0x4d, 0x00, 0x00, 0x00, 0x4b, 0x00, 0x00, 0x00, + 0x4b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x8e, 0x00, 0x05, 0x00, 0x29, 0x00, 0x00, 0x00, + 0x4e, 0x00, 0x00, 0x00, 0x4d, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00, + 0x83, 0x00, 0x05, 0x00, 0x14, 0x00, 0x00, 0x00, 0x4f, 0x00, 0x00, 0x00, + 0x1c, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00, 0x85, 0x00, 0x05, 0x00, + 0x14, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00, + 0x4f, 0x00, 0x00, 0x00, 0x51, 0x00, 0x05, 0x00, 0x0d, 0x00, 0x00, 0x00, + 0x51, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x51, 0x00, 0x05, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x52, 0x00, 0x00, 0x00, + 0x50, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x85, 0x00, 0x05, 0x00, + 0x0d, 0x00, 0x00, 0x00, 0x53, 0x00, 0x00, 0x00, 0x51, 0x00, 0x00, 0x00, + 0x52, 0x00, 0x00, 0x00, 0x85, 0x00, 0x05, 0x00, 0x0d, 0x00, 0x00, 0x00, + 0x54, 0x00, 0x00, 0x00, 0x53, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, + 0x0c, 0x00, 0x08, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x2e, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, + 0x54, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x85, 0x00, 0x05, 0x00, + 0x0d, 0x00, 0x00, 0x00, 0x56, 0x00, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, + 0x11, 0x00, 0x00, 0x00, 0x4f, 0x00, 0x08, 0x00, 0x29, 0x00, 0x00, 0x00, + 0x57, 0x00, 0x00, 0x00, 0x4e, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x8e, 0x00, 0x05, 0x00, 0x29, 0x00, 0x00, 0x00, 0x58, 0x00, 0x00, 0x00, + 0x57, 0x00, 0x00, 0x00, 0x56, 0x00, 0x00, 0x00, 0x4f, 0x00, 0x09, 0x00, + 0x21, 0x00, 0x00, 0x00, 0x59, 0x00, 0x00, 0x00, 0x4b, 0x00, 0x00, 0x00, + 0x58, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x03, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x59, 0x00, 0x00, 0x00, 0xfd, 0x00, 0x01, 0x00, + 0x38, 0x00, 0x01, 0x00 +}; +static const unsigned int testgpu_effects_CRT_frag_spv_len = 2224; diff --git a/libs/SDL3/src/render/gpu/shaders/texture_rgba.frag.sm60.dxil.h b/libs/SDL3/test/testgpurender_effects_grayscale.frag.dxil.h similarity index 51% rename from libs/SDL3/src/render/gpu/shaders/texture_rgba.frag.sm60.dxil.h rename to libs/SDL3/test/testgpurender_effects_grayscale.frag.dxil.h index eba70806..eec4d217 100644 --- a/libs/SDL3/src/render/gpu/shaders/texture_rgba.frag.sm60.dxil.h +++ b/libs/SDL3/test/testgpurender_effects_grayscale.frag.dxil.h @@ -1,156 +1,14 @@ -#if 0 -; -; Input signature: -; -; Name Index Mask Register SysValue Format Used -; -------------------- ----- ------ -------- -------- ------- ------ -; TEXCOORD 0 xyzw 0 NONE float xyzw -; TEXCOORD 1 xy 1 NONE float xy -; -; -; Output signature: -; -; Name Index Mask Register SysValue Format Used -; -------------------- ----- ------ -------- -------- ------- ------ -; SV_Target 0 xyzw 0 TARGET float xyzw -; -; shader hash: ab99026f3068b3314341ba15eaf626a8 -; -; Pipeline Runtime Information: -; -; Pixel Shader -; DepthOutput=0 -; SampleFrequency=0 -; -; -; Input signature: -; -; Name Index InterpMode DynIdx -; -------------------- ----- ---------------------- ------ -; TEXCOORD 0 linear -; TEXCOORD 1 linear -; -; Output signature: -; -; Name Index InterpMode DynIdx -; -------------------- ----- ---------------------- ------ -; SV_Target 0 -; -; Buffer Definitions: -; -; -; Resource Bindings: -; -; Name Type Format Dim ID HLSL Bind Count -; ------------------------------ ---------- ------- ----------- ------- -------------- ------ -; __13_sampler sampler NA NA S0 s0,space2 1 -; _13 texture f32 2d T0 t0,space2 1 -; -; -; ViewId state: -; -; Number of inputs: 6, outputs: 4 -; Outputs dependent on ViewId: { } -; Inputs contributing to computation of Outputs: -; output 0 depends on inputs: { 0, 4, 5 } -; output 1 depends on inputs: { 1, 4, 5 } -; output 2 depends on inputs: { 2, 4, 5 } -; output 3 depends on inputs: { 3, 4, 5 } -; -target datalayout = "e-m:e-p:32:32-i1:32-i8:32-i16:32-i32:32-i64:64-f16:32-f32:32-f64:64-n8:16:32:64" -target triple = "dxil-ms-dx" - -%dx.types.Handle = type { i8* } -%dx.types.ResRet.f32 = type { float, float, float, float, i32 } -%"class.Texture2D >" = type { <4 x float>, %"class.Texture2D >::mips_type" } -%"class.Texture2D >::mips_type" = type { i32 } -%struct.SamplerState = type { i32 } - -define void @main() { - %1 = call %dx.types.Handle @dx.op.createHandle(i32 57, i8 0, i32 0, i32 0, i1 false) ; CreateHandle(resourceClass,rangeId,index,nonUniformIndex) - %2 = call %dx.types.Handle @dx.op.createHandle(i32 57, i8 3, i32 0, i32 0, i1 false) ; CreateHandle(resourceClass,rangeId,index,nonUniformIndex) - %3 = call float @dx.op.loadInput.f32(i32 4, i32 1, i32 0, i8 0, i32 undef) ; LoadInput(inputSigId,rowIndex,colIndex,gsVertexAxis) - %4 = call float @dx.op.loadInput.f32(i32 4, i32 1, i32 0, i8 1, i32 undef) ; LoadInput(inputSigId,rowIndex,colIndex,gsVertexAxis) - %5 = call float @dx.op.loadInput.f32(i32 4, i32 0, i32 0, i8 0, i32 undef) ; LoadInput(inputSigId,rowIndex,colIndex,gsVertexAxis) - %6 = call float @dx.op.loadInput.f32(i32 4, i32 0, i32 0, i8 1, i32 undef) ; LoadInput(inputSigId,rowIndex,colIndex,gsVertexAxis) - %7 = call float @dx.op.loadInput.f32(i32 4, i32 0, i32 0, i8 2, i32 undef) ; LoadInput(inputSigId,rowIndex,colIndex,gsVertexAxis) - %8 = call float @dx.op.loadInput.f32(i32 4, i32 0, i32 0, i8 3, i32 undef) ; LoadInput(inputSigId,rowIndex,colIndex,gsVertexAxis) - %9 = call %dx.types.ResRet.f32 @dx.op.sample.f32(i32 60, %dx.types.Handle %1, %dx.types.Handle %2, float %3, float %4, float undef, float undef, i32 0, i32 0, i32 undef, float undef) ; Sample(srv,sampler,coord0,coord1,coord2,coord3,offset0,offset1,offset2,clamp) - %10 = extractvalue %dx.types.ResRet.f32 %9, 0 - %11 = extractvalue %dx.types.ResRet.f32 %9, 1 - %12 = extractvalue %dx.types.ResRet.f32 %9, 2 - %13 = extractvalue %dx.types.ResRet.f32 %9, 3 - %14 = fmul fast float %10, %5 - %15 = fmul fast float %11, %6 - %16 = fmul fast float %12, %7 - %17 = fmul fast float %13, %8 - call void @dx.op.storeOutput.f32(i32 5, i32 0, i32 0, i8 0, float %14) ; StoreOutput(outputSigId,rowIndex,colIndex,value) - call void @dx.op.storeOutput.f32(i32 5, i32 0, i32 0, i8 1, float %15) ; StoreOutput(outputSigId,rowIndex,colIndex,value) - call void @dx.op.storeOutput.f32(i32 5, i32 0, i32 0, i8 2, float %16) ; StoreOutput(outputSigId,rowIndex,colIndex,value) - call void @dx.op.storeOutput.f32(i32 5, i32 0, i32 0, i8 3, float %17) ; StoreOutput(outputSigId,rowIndex,colIndex,value) - ret void -} - -; Function Attrs: nounwind readnone -declare float @dx.op.loadInput.f32(i32, i32, i32, i8, i32) #0 - -; Function Attrs: nounwind -declare void @dx.op.storeOutput.f32(i32, i32, i32, i8, float) #1 - -; Function Attrs: nounwind readonly -declare %dx.types.ResRet.f32 @dx.op.sample.f32(i32, %dx.types.Handle, %dx.types.Handle, float, float, float, float, i32, i32, i32, float) #2 - -; Function Attrs: nounwind readonly -declare %dx.types.Handle @dx.op.createHandle(i32, i8, i32, i32, i1) #2 - -attributes #0 = { nounwind readnone } -attributes #1 = { nounwind } -attributes #2 = { nounwind readonly } - -!llvm.ident = !{!0} -!dx.version = !{!1} -!dx.valver = !{!2} -!dx.shaderModel = !{!3} -!dx.resources = !{!4} -!dx.viewIdState = !{!10} -!dx.entryPoints = !{!11} - -!0 = !{!"dxc(private) 1.8.0.4662 (416fab6b5)"} -!1 = !{i32 1, i32 0} -!2 = !{i32 1, i32 8} -!3 = !{!"ps", i32 6, i32 0} -!4 = !{!5, null, null, !8} -!5 = !{!6} -!6 = !{i32 0, %"class.Texture2D >"* undef, !"", i32 2, i32 0, i32 1, i32 2, i32 0, !7} -!7 = !{i32 0, i32 9} -!8 = !{!9} -!9 = !{i32 0, %struct.SamplerState* undef, !"", i32 2, i32 0, i32 1, i32 0, null} -!10 = !{[8 x i32] [i32 6, i32 4, i32 1, i32 2, i32 4, i32 8, i32 15, i32 15]} -!11 = !{void ()* @main, !"main", !12, !4, null} -!12 = !{!13, !20, null} -!13 = !{!14, !17} -!14 = !{i32 0, !"TEXCOORD", i8 9, i8 0, !15, i8 2, i32 1, i8 4, i32 0, i8 0, !16} -!15 = !{i32 0} -!16 = !{i32 3, i32 15} -!17 = !{i32 1, !"TEXCOORD", i8 9, i8 0, !18, i8 2, i32 1, i8 2, i32 1, i8 0, !19} -!18 = !{i32 1} -!19 = !{i32 3, i32 3} -!20 = !{!21} -!21 = !{i32 0, !"SV_Target", i8 9, i8 16, !15, i8 0, i32 1, i8 4, i32 0, i8 0, !16} - -#endif - -static const unsigned char texture_rgba_frag_sm60_dxil[] = { - 0x44, 0x58, 0x42, 0x43, 0x5c, 0x82, 0x6d, 0x45, 0x43, 0x35, 0xdf, 0xf5, - 0xad, 0xf7, 0x1b, 0x5e, 0xc0, 0x65, 0x38, 0x3f, 0x01, 0x00, 0x00, 0x00, - 0x24, 0x0f, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, +static const unsigned char testgpu_effects_grayscale_frag_dxil[] = { + 0x44, 0x58, 0x42, 0x43, 0x29, 0xf5, 0x99, 0x97, 0x2a, 0xac, 0xde, 0x59, + 0xf3, 0x00, 0x55, 0x1d, 0x65, 0x99, 0x31, 0x2a, 0x01, 0x00, 0x00, 0x00, + 0x3c, 0x0f, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00, 0xa8, 0x00, 0x00, 0x00, 0xe4, 0x00, 0x00, 0x00, - 0xd8, 0x01, 0x00, 0x00, 0x38, 0x08, 0x00, 0x00, 0x54, 0x08, 0x00, 0x00, + 0xd8, 0x01, 0x00, 0x00, 0x44, 0x08, 0x00, 0x00, 0x60, 0x08, 0x00, 0x00, 0x53, 0x46, 0x49, 0x30, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x49, 0x53, 0x47, 0x31, 0x54, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x0f, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, @@ -177,13 +35,13 @@ static const unsigned char texture_rgba_frag_sm60_dxil[] = { 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x44, 0x00, 0x03, 0x02, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x01, 0x42, 0x00, 0x03, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x44, 0x10, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x44, 0x10, 0x03, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x53, 0x54, 0x41, 0x54, 0x58, 0x06, 0x00, 0x00, - 0x60, 0x00, 0x00, 0x00, 0x96, 0x01, 0x00, 0x00, 0x44, 0x58, 0x49, 0x4c, - 0x00, 0x01, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x40, 0x06, 0x00, 0x00, - 0x42, 0x43, 0xc0, 0xde, 0x21, 0x0c, 0x00, 0x00, 0x8d, 0x01, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x53, 0x54, 0x41, 0x54, 0x64, 0x06, 0x00, 0x00, + 0x60, 0x00, 0x00, 0x00, 0x99, 0x01, 0x00, 0x00, 0x44, 0x58, 0x49, 0x4c, + 0x00, 0x01, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x4c, 0x06, 0x00, 0x00, + 0x42, 0x43, 0xc0, 0xde, 0x21, 0x0c, 0x00, 0x00, 0x90, 0x01, 0x00, 0x00, 0x0b, 0x82, 0x20, 0x00, 0x02, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x07, 0x81, 0x23, 0x91, 0x41, 0xc8, 0x04, 0x49, 0x06, 0x10, 0x32, 0x39, 0x92, 0x01, 0x84, 0x0c, 0x25, 0x05, 0x08, 0x19, 0x1e, 0x04, 0x8b, 0x62, @@ -236,53 +94,54 @@ static const unsigned char texture_rgba_frag_sm60_dxil[] = { 0x0c, 0x79, 0x10, 0x20, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0xf2, 0x34, 0x40, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0xe4, 0x81, 0x80, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x20, 0x0b, 0x04, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x20, 0x0b, 0x04, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x32, 0x1e, 0x98, 0x14, 0x19, 0x11, 0x4c, 0x90, 0x8c, 0x09, 0x26, 0x47, 0xc6, 0x04, 0x43, 0x22, 0x25, 0x30, 0x02, 0x50, 0x0c, 0x45, 0x50, 0x12, - 0x65, 0x50, 0x1e, 0x85, 0x50, 0x2c, 0x54, 0x4a, 0x62, 0x04, 0xa0, 0x08, - 0x0a, 0xa1, 0x40, 0xc8, 0xce, 0x00, 0x10, 0x9e, 0x01, 0xa0, 0x3c, 0x16, - 0x62, 0x10, 0x81, 0x40, 0x20, 0xcf, 0x03, 0x00, 0x79, 0x18, 0x00, 0x00, - 0x7b, 0x00, 0x00, 0x00, 0x1a, 0x03, 0x4c, 0x90, 0x46, 0x02, 0x13, 0xc4, - 0x31, 0x20, 0xc3, 0x1b, 0x43, 0x81, 0x93, 0x4b, 0xb3, 0x0b, 0xa3, 0x2b, - 0x4b, 0x01, 0x89, 0x71, 0xc1, 0x71, 0x81, 0x71, 0xa1, 0xb1, 0xb1, 0x91, - 0x01, 0x41, 0xa1, 0x89, 0xb1, 0x31, 0x0b, 0x13, 0xb3, 0x11, 0xab, 0x49, - 0xd9, 0x10, 0x04, 0x13, 0x04, 0xa2, 0x98, 0x20, 0x10, 0xc6, 0x06, 0x61, - 0x20, 0x36, 0x08, 0x04, 0x41, 0x01, 0x6e, 0x6e, 0x82, 0x40, 0x1c, 0x1b, - 0x86, 0x03, 0x21, 0x26, 0x08, 0xd6, 0xc4, 0xe1, 0x2b, 0x66, 0x66, 0x82, - 0x40, 0x20, 0x13, 0x04, 0x22, 0xd9, 0x20, 0x10, 0xcd, 0x86, 0x84, 0x50, - 0x16, 0x86, 0x18, 0x18, 0xc2, 0xd9, 0x10, 0x3c, 0x13, 0x04, 0x8c, 0x22, - 0xf3, 0xf5, 0x15, 0x33, 0xf3, 0x35, 0x17, 0xd6, 0x06, 0xc7, 0x56, 0x26, - 0xb7, 0x01, 0x21, 0x22, 0x89, 0x21, 0x06, 0x02, 0xd8, 0x10, 0x4c, 0x1b, - 0x08, 0x08, 0x00, 0xa8, 0x09, 0x82, 0x00, 0x6c, 0x00, 0x36, 0x0c, 0xc4, - 0x75, 0x6d, 0x08, 0xb0, 0x0d, 0xc3, 0x60, 0x65, 0x13, 0x84, 0xac, 0xda, - 0x10, 0x6c, 0x24, 0xda, 0xc2, 0xd2, 0xdc, 0x88, 0x50, 0x15, 0x61, 0x0d, - 0x3d, 0x3d, 0x49, 0x11, 0x4d, 0x10, 0x0a, 0x67, 0x82, 0x50, 0x3c, 0x1b, - 0x02, 0x62, 0x82, 0x50, 0x40, 0x13, 0x84, 0x22, 0x9a, 0x20, 0x10, 0xca, - 0x04, 0x81, 0x58, 0x36, 0x08, 0x64, 0x50, 0x06, 0x1b, 0x16, 0xc2, 0xfb, - 0xc0, 0x20, 0x0c, 0xc4, 0x60, 0x18, 0x03, 0x02, 0x0c, 0xcc, 0x60, 0x43, - 0x30, 0x6c, 0x10, 0xc8, 0x80, 0x0c, 0x36, 0x2c, 0x83, 0xf7, 0x81, 0x01, - 0x1a, 0x88, 0xc1, 0x20, 0x06, 0x03, 0x18, 0xa4, 0xc1, 0x06, 0xe1, 0x0c, - 0xd4, 0x80, 0xc9, 0x94, 0xd5, 0x17, 0x55, 0x98, 0xdc, 0x59, 0x19, 0xdd, - 0x04, 0xa1, 0x90, 0x36, 0x2c, 0x04, 0x1b, 0x7c, 0x6d, 0x10, 0x06, 0x60, - 0x30, 0x8c, 0x01, 0x01, 0x06, 0x66, 0xb0, 0x21, 0x70, 0x83, 0x0d, 0xc3, - 0x1a, 0xbc, 0x01, 0xb0, 0xa1, 0xb0, 0x3a, 0x38, 0xa8, 0x00, 0x1a, 0x66, - 0x6c, 0x6f, 0x61, 0x74, 0x73, 0x13, 0x04, 0x82, 0x61, 0x91, 0xe6, 0x36, - 0x47, 0x37, 0x37, 0x41, 0x20, 0x1a, 0x1a, 0x73, 0x69, 0x67, 0x5f, 0x6c, - 0x64, 0x34, 0xe6, 0xd2, 0xce, 0xbe, 0xe6, 0xe8, 0x88, 0xd0, 0x95, 0xe1, - 0x7d, 0xb9, 0xbd, 0xc9, 0xb5, 0x6d, 0x50, 0xe4, 0x60, 0x0e, 0xe8, 0xa0, - 0x0e, 0xec, 0x00, 0xb9, 0x83, 0x39, 0xc0, 0x83, 0xa1, 0x0a, 0x1b, 0x9b, - 0x5d, 0x9b, 0x4b, 0x1a, 0x59, 0x99, 0x1b, 0xdd, 0x94, 0x20, 0xa8, 0x42, - 0x86, 0xe7, 0x62, 0x57, 0x26, 0x37, 0x97, 0xf6, 0xe6, 0x36, 0x25, 0x20, - 0x9a, 0x90, 0xe1, 0xb9, 0xd8, 0x85, 0xb1, 0xd9, 0x95, 0xc9, 0x4d, 0x09, - 0x8a, 0x3a, 0x64, 0x78, 0x2e, 0x73, 0x68, 0x61, 0x64, 0x65, 0x72, 0x4d, - 0x6f, 0x64, 0x65, 0x6c, 0x53, 0x02, 0xa4, 0x0c, 0x19, 0x9e, 0x8b, 0x5c, - 0xd9, 0xdc, 0x5b, 0x9d, 0xdc, 0x58, 0xd9, 0xdc, 0x94, 0x80, 0xaa, 0x44, - 0x86, 0xe7, 0x42, 0x97, 0x07, 0x57, 0x16, 0xe4, 0xe6, 0xf6, 0x46, 0x17, - 0x46, 0x97, 0xf6, 0xe6, 0x36, 0x37, 0x25, 0xc8, 0xea, 0x90, 0xe1, 0xb9, - 0xd8, 0xa5, 0x95, 0xdd, 0x25, 0x91, 0x4d, 0xd1, 0x85, 0xd1, 0x95, 0x4d, - 0x09, 0xb6, 0x3a, 0x64, 0x78, 0x2e, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x50, - 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x53, 0x02, 0x38, 0xe8, 0x42, 0x86, 0xe7, - 0x32, 0xf6, 0x56, 0xe7, 0x46, 0x57, 0x26, 0x37, 0x37, 0x25, 0xc0, 0x03, + 0x65, 0x50, 0x16, 0xe5, 0x51, 0x2c, 0xa5, 0x50, 0x08, 0x54, 0x4a, 0x62, + 0x04, 0xa0, 0x08, 0x0a, 0xa1, 0x40, 0xc8, 0xce, 0x00, 0x10, 0x9e, 0x01, + 0xa0, 0x3c, 0x16, 0x62, 0x10, 0xc7, 0x01, 0x20, 0xcf, 0x03, 0x00, 0x00, + 0x79, 0x18, 0x00, 0x00, 0x7d, 0x00, 0x00, 0x00, 0x1a, 0x03, 0x4c, 0x90, + 0x46, 0x02, 0x13, 0xc4, 0x8e, 0x0c, 0x6f, 0xec, 0xed, 0x4d, 0x0c, 0x24, + 0xc6, 0x05, 0xc7, 0x45, 0xa6, 0x06, 0x46, 0xc6, 0x05, 0x07, 0x04, 0x45, + 0x8c, 0xe6, 0x26, 0x26, 0x06, 0x67, 0x26, 0xa7, 0x2c, 0x65, 0x43, 0x10, + 0x4c, 0x10, 0x88, 0x62, 0x82, 0x40, 0x18, 0x1b, 0x84, 0x81, 0xd8, 0x20, + 0x10, 0x04, 0x05, 0xb8, 0xb9, 0x09, 0x02, 0x71, 0x6c, 0x18, 0x0e, 0x84, + 0x98, 0x20, 0x58, 0x15, 0x93, 0xba, 0x2f, 0xba, 0x32, 0x3c, 0xba, 0x3a, + 0xb9, 0xb2, 0x09, 0x02, 0x81, 0x4c, 0x10, 0x88, 0x64, 0x83, 0x40, 0x34, + 0x1b, 0x12, 0x42, 0x59, 0x18, 0x62, 0x60, 0x08, 0x67, 0x43, 0xf0, 0x4c, + 0x10, 0x30, 0x8b, 0x49, 0xdd, 0xd7, 0x5c, 0x58, 0x1b, 0x1c, 0x5b, 0x99, + 0xdc, 0x06, 0x84, 0x88, 0x24, 0x86, 0x18, 0x08, 0x60, 0x43, 0x30, 0x6d, + 0x20, 0x20, 0x00, 0xa0, 0x26, 0x08, 0x02, 0xb0, 0x01, 0xd8, 0x30, 0x10, + 0xd7, 0xb5, 0x21, 0xc0, 0x36, 0x0c, 0x83, 0x95, 0x4d, 0x10, 0xb2, 0x6b, + 0x43, 0xb0, 0x91, 0x68, 0x0b, 0x4b, 0x73, 0x23, 0x42, 0x55, 0x84, 0x35, + 0xf4, 0xf4, 0x24, 0x45, 0x34, 0x41, 0x28, 0xa0, 0x09, 0x42, 0x11, 0x6d, + 0x08, 0x88, 0x09, 0x42, 0x21, 0x4d, 0x10, 0x8a, 0x69, 0x82, 0x40, 0x28, + 0x13, 0x04, 0x62, 0xd9, 0x20, 0x90, 0x41, 0x19, 0x6c, 0x58, 0x08, 0xef, + 0x03, 0x83, 0x30, 0x10, 0x83, 0x61, 0x0c, 0x08, 0x30, 0x30, 0x83, 0x0d, + 0xc1, 0xb0, 0x41, 0x20, 0x03, 0x32, 0xd8, 0xb0, 0x0c, 0xde, 0x07, 0x06, + 0x68, 0x20, 0x06, 0x83, 0x18, 0x0c, 0x60, 0x90, 0x06, 0x1b, 0x84, 0x33, + 0x50, 0x03, 0x26, 0x53, 0x56, 0x5f, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, + 0x13, 0x84, 0x82, 0x9a, 0x20, 0x10, 0xcc, 0x06, 0x81, 0x0c, 0xdc, 0x60, + 0xc3, 0x42, 0xb0, 0xc1, 0xd7, 0x06, 0x61, 0x00, 0x06, 0xc3, 0x18, 0x10, + 0x60, 0xf0, 0x06, 0x1b, 0x02, 0x38, 0xd8, 0x30, 0xac, 0x41, 0x1c, 0x00, + 0x1b, 0x0a, 0xab, 0x93, 0x83, 0x0a, 0xa0, 0x61, 0xc6, 0xf6, 0x16, 0x46, + 0x37, 0xc7, 0x22, 0xcd, 0x6d, 0x8e, 0x6e, 0x6e, 0x82, 0x40, 0x34, 0x34, + 0xe6, 0xd2, 0xce, 0xbe, 0xd8, 0xc8, 0x26, 0x08, 0x84, 0x43, 0x63, 0x2e, + 0xed, 0xec, 0x6b, 0x8e, 0x6e, 0x82, 0x40, 0x3c, 0x44, 0xe8, 0xca, 0xf0, + 0xbe, 0xdc, 0xde, 0xe4, 0xda, 0x36, 0x28, 0x74, 0x80, 0xd4, 0x81, 0x1d, + 0xdc, 0x01, 0x1e, 0xe4, 0x81, 0x1e, 0xec, 0xc1, 0x50, 0x85, 0x8d, 0xcd, + 0xae, 0xcd, 0x25, 0x8d, 0xac, 0xcc, 0x8d, 0x6e, 0x4a, 0x10, 0x54, 0x21, + 0xc3, 0x73, 0xb1, 0x2b, 0x93, 0x9b, 0x4b, 0x7b, 0x73, 0x9b, 0x12, 0x10, + 0x4d, 0xc8, 0xf0, 0x5c, 0xec, 0xc2, 0xd8, 0xec, 0xca, 0xe4, 0xa6, 0x04, + 0x45, 0x1d, 0x32, 0x3c, 0x97, 0x39, 0xb4, 0x30, 0xb2, 0x32, 0xb9, 0xa6, + 0x37, 0xb2, 0x32, 0xb6, 0x29, 0x01, 0x52, 0x86, 0x0c, 0xcf, 0x45, 0xae, + 0x6c, 0xee, 0xad, 0x4e, 0x6e, 0xac, 0x6c, 0x6e, 0x4a, 0x40, 0x55, 0x22, + 0xc3, 0x73, 0xa1, 0xcb, 0x83, 0x2b, 0x0b, 0x72, 0x73, 0x7b, 0xa3, 0x0b, + 0xa3, 0x4b, 0x7b, 0x73, 0x9b, 0x9b, 0x12, 0x64, 0x75, 0xc8, 0xf0, 0x5c, + 0xec, 0xd2, 0xca, 0xee, 0x92, 0xc8, 0xa6, 0xe8, 0xc2, 0xe8, 0xca, 0xa6, + 0x04, 0x5b, 0x1d, 0x32, 0x3c, 0x97, 0x32, 0x37, 0x3a, 0xb9, 0x3c, 0xa8, + 0xb7, 0x34, 0x37, 0xba, 0xb9, 0x29, 0x81, 0x1c, 0x74, 0x21, 0xc3, 0x73, + 0x19, 0x7b, 0xab, 0x73, 0xa3, 0x2b, 0x93, 0x9b, 0x9b, 0x12, 0xec, 0x01, 0x00, 0x00, 0x00, 0x00, 0x79, 0x18, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00, 0x33, 0x08, 0x80, 0x1c, 0xc4, 0xe1, 0x1c, 0x66, 0x14, 0x01, 0x3d, 0x88, 0x43, 0x38, 0x84, 0xc3, 0x8c, 0x42, 0x80, 0x07, 0x79, 0x78, 0x07, 0x73, @@ -317,12 +176,12 @@ static const unsigned char texture_rgba_frag_sm60_dxil[] = { 0xd5, 0x70, 0xf9, 0xce, 0xe3, 0x4b, 0x93, 0x13, 0x11, 0x28, 0x35, 0x3d, 0xd4, 0xe4, 0x17, 0xb7, 0x6d, 0x00, 0x04, 0x03, 0x20, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x41, 0x53, 0x48, 0x14, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0xab, 0x99, 0x02, 0x6f, 0x30, 0x68, 0xb3, 0x31, - 0x43, 0x41, 0xba, 0x15, 0xea, 0xf6, 0x26, 0xa8, 0x44, 0x58, 0x49, 0x4c, - 0xc8, 0x06, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0xb2, 0x01, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x56, 0x34, 0x28, 0xb6, 0xc7, 0x9e, 0xae, 0x05, + 0xf9, 0x5e, 0x19, 0x9a, 0x37, 0xcf, 0x7a, 0x50, 0x44, 0x58, 0x49, 0x4c, + 0xd4, 0x06, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0xb5, 0x01, 0x00, 0x00, 0x44, 0x58, 0x49, 0x4c, 0x00, 0x01, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, - 0xb0, 0x06, 0x00, 0x00, 0x42, 0x43, 0xc0, 0xde, 0x21, 0x0c, 0x00, 0x00, - 0xa9, 0x01, 0x00, 0x00, 0x0b, 0x82, 0x20, 0x00, 0x02, 0x00, 0x00, 0x00, + 0xbc, 0x06, 0x00, 0x00, 0x42, 0x43, 0xc0, 0xde, 0x21, 0x0c, 0x00, 0x00, + 0xac, 0x01, 0x00, 0x00, 0x0b, 0x82, 0x20, 0x00, 0x02, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x07, 0x81, 0x23, 0x91, 0x41, 0xc8, 0x04, 0x49, 0x06, 0x10, 0x32, 0x39, 0x92, 0x01, 0x84, 0x0c, 0x25, 0x05, 0x08, 0x19, 0x1e, 0x04, 0x8b, 0x62, 0x80, 0x14, 0x45, 0x02, 0x42, 0x92, 0x0b, 0x42, @@ -377,40 +236,40 @@ static const unsigned char texture_rgba_frag_sm60_dxil[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x0b, 0x04, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x32, 0x1e, 0x98, 0x14, 0x19, 0x11, 0x4c, 0x90, 0x8c, 0x09, 0x26, 0x47, 0xc6, 0x04, 0x43, 0x22, 0x25, 0x30, 0x02, 0x50, - 0x10, 0xc5, 0x50, 0x04, 0x25, 0x51, 0x06, 0xe5, 0x41, 0xa5, 0x24, 0x46, - 0x00, 0x8a, 0xa0, 0x10, 0x0a, 0x84, 0xec, 0x0c, 0x00, 0xe1, 0x19, 0x00, - 0xca, 0x63, 0x21, 0x06, 0x11, 0x08, 0x04, 0xf2, 0x3c, 0x00, 0x00, 0x00, + 0x10, 0xc5, 0x50, 0x04, 0x25, 0x51, 0x06, 0x65, 0x51, 0x1e, 0x54, 0x4a, + 0x62, 0x04, 0xa0, 0x08, 0x0a, 0xa1, 0x40, 0xc8, 0xce, 0x00, 0x10, 0x9e, + 0x01, 0xa0, 0x3c, 0x16, 0x62, 0x10, 0xc7, 0x01, 0x20, 0xcf, 0x03, 0x00, 0x79, 0x18, 0x00, 0x00, 0x5a, 0x00, 0x00, 0x00, 0x1a, 0x03, 0x4c, 0x90, - 0x46, 0x02, 0x13, 0xc4, 0x31, 0x20, 0xc3, 0x1b, 0x43, 0x81, 0x93, 0x4b, - 0xb3, 0x0b, 0xa3, 0x2b, 0x4b, 0x01, 0x89, 0x71, 0xc1, 0x71, 0x81, 0x71, - 0xa1, 0xb1, 0xb1, 0x91, 0x01, 0x41, 0xa1, 0x89, 0xb1, 0x31, 0x0b, 0x13, - 0xb3, 0x11, 0xab, 0x49, 0xd9, 0x10, 0x04, 0x13, 0x04, 0xa2, 0x98, 0x20, - 0x10, 0xc6, 0x06, 0x61, 0x20, 0x26, 0x08, 0xc4, 0xb1, 0x41, 0x18, 0x0c, - 0x0a, 0x70, 0x73, 0x13, 0x04, 0x02, 0xd9, 0x30, 0x20, 0x09, 0x31, 0x41, - 0xb0, 0x24, 0x02, 0x13, 0x04, 0x22, 0x99, 0x20, 0x10, 0xca, 0x06, 0x81, - 0x70, 0x36, 0x24, 0xc4, 0xc2, 0x34, 0xc4, 0xd0, 0x10, 0xcf, 0x86, 0x00, - 0x9a, 0x20, 0x60, 0xd3, 0x06, 0x84, 0x90, 0x98, 0x86, 0x18, 0x08, 0x60, - 0x43, 0x30, 0x6d, 0x20, 0x22, 0x00, 0xa0, 0x26, 0x08, 0x19, 0xb5, 0x21, - 0xb0, 0x26, 0x08, 0x02, 0x40, 0xa2, 0x2d, 0x2c, 0xcd, 0x8d, 0x08, 0x55, - 0x11, 0xd6, 0xd0, 0xd3, 0x93, 0x14, 0xd1, 0x04, 0xa1, 0x68, 0x26, 0x08, - 0x85, 0xb3, 0x21, 0x20, 0x26, 0x08, 0xc5, 0x33, 0x41, 0x28, 0xa0, 0x09, - 0x02, 0xb1, 0x4c, 0x10, 0x08, 0x66, 0x83, 0x00, 0x06, 0x61, 0xb0, 0x61, - 0x21, 0xb4, 0x8d, 0xeb, 0xbc, 0xe1, 0x23, 0x38, 0x31, 0xd8, 0x10, 0x0c, - 0x1b, 0x04, 0x30, 0x00, 0x83, 0x0d, 0xcb, 0xa0, 0x6d, 0x1c, 0x19, 0x78, - 0x83, 0x37, 0x70, 0x65, 0xb0, 0x41, 0x18, 0x03, 0x33, 0x60, 0x32, 0x65, - 0xf5, 0x45, 0x15, 0x26, 0x77, 0x56, 0x46, 0x37, 0x41, 0x28, 0xa2, 0x0d, - 0x0b, 0x81, 0x06, 0x5b, 0x1a, 0x74, 0xdc, 0xf0, 0x11, 0x9c, 0x18, 0x6c, - 0x08, 0xd4, 0x60, 0xc3, 0x70, 0x06, 0x6b, 0x00, 0x6c, 0x28, 0xb0, 0x8c, - 0x0d, 0x2a, 0xa0, 0x0a, 0x1b, 0x9b, 0x5d, 0x9b, 0x4b, 0x1a, 0x59, 0x99, - 0x1b, 0xdd, 0x94, 0x20, 0xa8, 0x42, 0x86, 0xe7, 0x62, 0x57, 0x26, 0x37, - 0x97, 0xf6, 0xe6, 0x36, 0x25, 0x20, 0x9a, 0x90, 0xe1, 0xb9, 0xd8, 0x85, - 0xb1, 0xd9, 0x95, 0xc9, 0x4d, 0x09, 0x8c, 0x3a, 0x64, 0x78, 0x2e, 0x73, - 0x68, 0x61, 0x64, 0x65, 0x72, 0x4d, 0x6f, 0x64, 0x65, 0x6c, 0x53, 0x82, - 0xa4, 0x0c, 0x19, 0x9e, 0x8b, 0x5c, 0xd9, 0xdc, 0x5b, 0x9d, 0xdc, 0x58, - 0xd9, 0xdc, 0x94, 0x80, 0xaa, 0x43, 0x86, 0xe7, 0x62, 0x97, 0x56, 0x76, - 0x97, 0x44, 0x36, 0x45, 0x17, 0x46, 0x57, 0x36, 0x25, 0xb0, 0xea, 0x90, - 0xe1, 0xb9, 0x94, 0xb9, 0xd1, 0xc9, 0xe5, 0x41, 0xbd, 0xa5, 0xb9, 0xd1, - 0xcd, 0x4d, 0x09, 0xd8, 0x00, 0x00, 0x00, 0x00, 0x79, 0x18, 0x00, 0x00, + 0x46, 0x02, 0x13, 0xc4, 0x8e, 0x0c, 0x6f, 0xec, 0xed, 0x4d, 0x0c, 0x24, + 0xc6, 0x05, 0xc7, 0x45, 0xa6, 0x06, 0x46, 0xc6, 0x05, 0x07, 0x04, 0x45, + 0x8c, 0xe6, 0x26, 0x26, 0x06, 0x67, 0x26, 0xa7, 0x2c, 0x65, 0x43, 0x10, + 0x4c, 0x10, 0x88, 0x62, 0x82, 0x40, 0x18, 0x1b, 0x84, 0x81, 0x98, 0x20, + 0x10, 0xc7, 0x06, 0x61, 0x30, 0x28, 0xc0, 0xcd, 0x4d, 0x10, 0x08, 0x64, + 0xc3, 0x80, 0x24, 0xc4, 0x04, 0xc1, 0x9a, 0x08, 0x4c, 0x10, 0x88, 0x64, + 0x82, 0x40, 0x28, 0x1b, 0x04, 0xc2, 0xd9, 0x90, 0x10, 0x0b, 0xd3, 0x10, + 0x43, 0x43, 0x3c, 0x1b, 0x02, 0x68, 0x82, 0x80, 0x51, 0x1b, 0x10, 0x42, + 0x62, 0x1a, 0x62, 0x20, 0x80, 0x0d, 0xc1, 0xb4, 0x81, 0x88, 0x00, 0x80, + 0x9a, 0x20, 0x64, 0xd5, 0x86, 0xc0, 0x9a, 0x20, 0x08, 0x00, 0x89, 0xb6, + 0xb0, 0x34, 0x37, 0x22, 0x54, 0x45, 0x58, 0x43, 0x4f, 0x4f, 0x52, 0x44, + 0x13, 0x84, 0xc2, 0x99, 0x20, 0x14, 0xcf, 0x86, 0x80, 0x98, 0x20, 0x14, + 0xd0, 0x04, 0xa1, 0x88, 0x26, 0x08, 0xc4, 0x32, 0x41, 0x20, 0x98, 0x0d, + 0x02, 0x18, 0x84, 0xc1, 0x86, 0x85, 0xd0, 0x36, 0xae, 0xf3, 0x86, 0x8f, + 0xe0, 0xc4, 0x60, 0x43, 0x30, 0x6c, 0x10, 0xc0, 0x00, 0x0c, 0x36, 0x2c, + 0x83, 0xb6, 0x71, 0x64, 0xe0, 0x0d, 0xde, 0xc0, 0x95, 0xc1, 0x06, 0x61, + 0x0c, 0xcc, 0x80, 0xc9, 0x94, 0xd5, 0x17, 0x55, 0x98, 0xdc, 0x59, 0x19, + 0xdd, 0x04, 0xa1, 0x90, 0x26, 0x08, 0x44, 0xb3, 0x41, 0x00, 0x03, 0x35, + 0xd8, 0xb0, 0x10, 0x68, 0xb0, 0xa5, 0x41, 0xc7, 0x0d, 0x1f, 0xc1, 0xad, + 0xc1, 0x86, 0x80, 0x0d, 0x36, 0x0c, 0x67, 0xd0, 0x06, 0xc0, 0x86, 0x02, + 0xcb, 0xdc, 0xa0, 0x02, 0xaa, 0xb0, 0xb1, 0xd9, 0xb5, 0xb9, 0xa4, 0x91, + 0x95, 0xb9, 0xd1, 0x4d, 0x09, 0x82, 0x2a, 0x64, 0x78, 0x2e, 0x76, 0x65, + 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x53, 0x02, 0xa2, 0x09, 0x19, 0x9e, 0x8b, + 0x5d, 0x18, 0x9b, 0x5d, 0x99, 0xdc, 0x94, 0xc0, 0xa8, 0x43, 0x86, 0xe7, + 0x32, 0x87, 0x16, 0x46, 0x56, 0x26, 0xd7, 0xf4, 0x46, 0x56, 0xc6, 0x36, + 0x25, 0x48, 0xca, 0x90, 0xe1, 0xb9, 0xc8, 0x95, 0xcd, 0xbd, 0xd5, 0xc9, + 0x8d, 0x95, 0xcd, 0x4d, 0x09, 0xa8, 0x3a, 0x64, 0x78, 0x2e, 0x76, 0x69, + 0x65, 0x77, 0x49, 0x64, 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, 0x02, 0xab, + 0x0e, 0x19, 0x9e, 0x4b, 0x99, 0x1b, 0x9d, 0x5c, 0x1e, 0xd4, 0x5b, 0x9a, + 0x1b, 0xdd, 0xdc, 0x94, 0xc0, 0x0d, 0x00, 0x00, 0x79, 0x18, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00, 0x33, 0x08, 0x80, 0x1c, 0xc4, 0xe1, 0x1c, 0x66, 0x14, 0x01, 0x3d, 0x88, 0x43, 0x38, 0x84, 0xc3, 0x8c, 0x42, 0x80, 0x07, 0x79, 0x78, 0x07, 0x73, 0x98, 0x71, 0x0c, 0xe6, 0x00, 0x0f, 0xed, 0x10, @@ -443,25 +302,27 @@ static const unsigned char texture_rgba_frag_sm60_dxil[] = { 0xdb, 0x00, 0x34, 0x5c, 0xbe, 0xf3, 0xf8, 0x12, 0xc0, 0x3c, 0x0b, 0xe1, 0x17, 0xb7, 0x6d, 0x02, 0xd5, 0x70, 0xf9, 0xce, 0xe3, 0x4b, 0x93, 0x13, 0x11, 0x28, 0x35, 0x3d, 0xd4, 0xe4, 0x17, 0xb7, 0x6d, 0x00, 0x04, 0x03, - 0x20, 0x0d, 0x00, 0x00, 0x61, 0x20, 0x00, 0x00, 0x3b, 0x00, 0x00, 0x00, - 0x13, 0x04, 0x41, 0x2c, 0x10, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, - 0xf4, 0x46, 0x00, 0x88, 0xcc, 0x00, 0x14, 0x42, 0x29, 0x94, 0x5c, 0xe1, - 0x51, 0x29, 0x83, 0x12, 0xa0, 0x31, 0x03, 0x00, 0x23, 0x06, 0x09, 0x00, - 0x82, 0x60, 0x00, 0x69, 0x05, 0x84, 0x61, 0xc9, 0x88, 0x41, 0x02, 0x80, - 0x20, 0x18, 0x40, 0x9b, 0x41, 0x64, 0x99, 0x32, 0x62, 0x90, 0x00, 0x20, - 0x08, 0x06, 0xc6, 0x97, 0x6c, 0x9a, 0xa4, 0x8c, 0x18, 0x24, 0x00, 0x08, - 0x82, 0x81, 0x01, 0x06, 0x0a, 0xb7, 0x15, 0xcb, 0x88, 0x41, 0x02, 0x80, - 0x20, 0x18, 0x18, 0x61, 0xb0, 0x70, 0x1c, 0xc5, 0x8c, 0x18, 0x24, 0x00, - 0x08, 0x82, 0x81, 0x21, 0x06, 0x4c, 0xd7, 0x1d, 0xcd, 0x88, 0x41, 0x02, - 0x80, 0x20, 0x18, 0x18, 0x63, 0xd0, 0x78, 0x5e, 0xe5, 0x8c, 0x18, 0x24, - 0x00, 0x08, 0x82, 0x81, 0x41, 0x06, 0xce, 0xf7, 0x29, 0xcf, 0x88, 0xc1, - 0x03, 0x80, 0x20, 0x18, 0x34, 0x63, 0xc0, 0x20, 0x87, 0x51, 0x24, 0x09, - 0x18, 0x80, 0x01, 0x94, 0x8c, 0x26, 0x04, 0xc0, 0x68, 0x82, 0x10, 0x8c, - 0x26, 0x0c, 0xc2, 0x68, 0x02, 0x31, 0x18, 0x91, 0xc8, 0xc7, 0x88, 0x44, - 0x3e, 0x46, 0x24, 0xf2, 0x31, 0x22, 0x91, 0xcf, 0x88, 0x41, 0x02, 0x80, - 0x20, 0x18, 0x20, 0x6d, 0x70, 0xa5, 0x41, 0x1a, 0x84, 0x01, 0x31, 0x62, - 0x90, 0x00, 0x20, 0x08, 0x06, 0x48, 0x1b, 0x5c, 0x69, 0x90, 0x06, 0xd3, - 0x30, 0x62, 0x90, 0x00, 0x20, 0x08, 0x06, 0x48, 0x1b, 0x5c, 0x69, 0x90, - 0x06, 0x60, 0x20, 0x8c, 0x18, 0x24, 0x00, 0x08, 0x82, 0x01, 0xd2, 0x06, - 0x57, 0x1a, 0xa4, 0x01, 0x15, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + 0x20, 0x0d, 0x00, 0x00, 0x61, 0x20, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00, + 0x13, 0x04, 0x41, 0x2c, 0x10, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0xf4, 0x46, 0x00, 0xa8, 0x94, 0x40, 0x19, 0x10, 0x99, 0x01, 0x28, 0x84, + 0x52, 0x28, 0xb9, 0xc2, 0xa3, 0x31, 0x46, 0xc0, 0x97, 0xbd, 0xd9, 0x7e, + 0x63, 0x04, 0x62, 0x1b, 0xb3, 0xf1, 0x37, 0x03, 0x00, 0x00, 0x00, 0x00, + 0x23, 0x06, 0x09, 0x00, 0x82, 0x60, 0x00, 0x75, 0x85, 0xb4, 0x6d, 0xcb, + 0x88, 0x41, 0x02, 0x80, 0x20, 0x18, 0x40, 0x9e, 0xa1, 0x70, 0x1c, 0x33, + 0x62, 0x90, 0x00, 0x20, 0x08, 0x06, 0x86, 0x18, 0x24, 0x5e, 0x47, 0x29, + 0x23, 0x06, 0x09, 0x00, 0x82, 0x60, 0x60, 0x8c, 0x81, 0xf2, 0x79, 0xcd, + 0x32, 0x62, 0x90, 0x00, 0x20, 0x08, 0x06, 0x06, 0x19, 0x2c, 0xdf, 0x67, + 0x31, 0x23, 0x06, 0x09, 0x00, 0x82, 0x60, 0x60, 0x94, 0x01, 0x03, 0x06, + 0x60, 0xf0, 0x34, 0x23, 0x06, 0x09, 0x00, 0x82, 0x60, 0x60, 0x98, 0x41, + 0x13, 0x06, 0x61, 0xf0, 0x38, 0x23, 0x06, 0x0f, 0x00, 0x82, 0x60, 0xd0, + 0x94, 0xc1, 0x72, 0x18, 0x05, 0x81, 0x20, 0x62, 0x20, 0x06, 0x0f, 0x32, + 0x9a, 0x10, 0x00, 0xa3, 0x09, 0x42, 0x30, 0x9a, 0x30, 0x0c, 0x16, 0x14, + 0xf2, 0xb1, 0xc3, 0x91, 0x8f, 0x05, 0x84, 0x7c, 0x4c, 0x89, 0xe4, 0x63, + 0xc1, 0x21, 0x1f, 0x0b, 0x06, 0xf8, 0x8c, 0x18, 0x24, 0x00, 0x08, 0x82, + 0x01, 0x02, 0x07, 0x17, 0x1b, 0xb0, 0xc1, 0x18, 0x04, 0x23, 0x06, 0x09, + 0x00, 0x82, 0x60, 0x80, 0xc0, 0xc1, 0xc5, 0x06, 0x6c, 0xb0, 0x05, 0x23, + 0x06, 0x09, 0x00, 0x82, 0x60, 0x80, 0xc0, 0xc1, 0xc5, 0x06, 0x6c, 0x20, + 0x06, 0xc1, 0x88, 0x41, 0x02, 0x80, 0x20, 0x18, 0x20, 0x70, 0x70, 0xb1, + 0x01, 0x1b, 0x68, 0x06, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; +static const unsigned int testgpu_effects_grayscale_frag_dxil_len = 3900; diff --git a/libs/SDL3/test/testgpurender_effects_grayscale.frag.hlsl b/libs/SDL3/test/testgpurender_effects_grayscale.frag.hlsl new file mode 100644 index 00000000..a83bfd0c --- /dev/null +++ b/libs/SDL3/test/testgpurender_effects_grayscale.frag.hlsl @@ -0,0 +1,19 @@ +Texture2D u_texture : register(t0, space2); +SamplerState u_sampler : register(s0, space2); + +struct PSInput { + float4 v_color : COLOR0; + float2 v_uv : TEXCOORD0; +}; + +struct PSOutput { + float4 o_color : SV_Target; +}; + +PSOutput main(PSInput input) { + PSOutput output; + float4 color = u_texture.Sample(u_sampler, input.v_uv) * input.v_color; + float gray = color.r * 0.299 + color.g * 0.587 + color.r * 0.114; + output.o_color = float4(gray, gray, gray, color.a); + return output; +} diff --git a/libs/SDL3/test/testgpurender_effects_grayscale.frag.msl.h b/libs/SDL3/test/testgpurender_effects_grayscale.frag.msl.h new file mode 100644 index 00000000..e0ab1e72 --- /dev/null +++ b/libs/SDL3/test/testgpurender_effects_grayscale.frag.msl.h @@ -0,0 +1,63 @@ +static const unsigned char testgpu_effects_grayscale_frag_msl[] = { + 0x23, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x20, 0x3c, 0x6d, 0x65, + 0x74, 0x61, 0x6c, 0x5f, 0x73, 0x74, 0x64, 0x6c, 0x69, 0x62, 0x3e, 0x0a, + 0x23, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x20, 0x3c, 0x73, 0x69, + 0x6d, 0x64, 0x2f, 0x73, 0x69, 0x6d, 0x64, 0x2e, 0x68, 0x3e, 0x0a, 0x0a, + 0x75, 0x73, 0x69, 0x6e, 0x67, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, + 0x61, 0x63, 0x65, 0x20, 0x6d, 0x65, 0x74, 0x61, 0x6c, 0x3b, 0x0a, 0x0a, + 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x20, 0x6d, 0x61, 0x69, 0x6e, 0x30, + 0x5f, 0x6f, 0x75, 0x74, 0x0a, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, + 0x6c, 0x6f, 0x61, 0x74, 0x34, 0x20, 0x6f, 0x75, 0x74, 0x5f, 0x76, 0x61, + 0x72, 0x5f, 0x53, 0x56, 0x5f, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x20, + 0x5b, 0x5b, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x28, 0x30, 0x29, 0x5d, 0x5d, + 0x3b, 0x0a, 0x7d, 0x3b, 0x0a, 0x0a, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, + 0x20, 0x6d, 0x61, 0x69, 0x6e, 0x30, 0x5f, 0x69, 0x6e, 0x0a, 0x7b, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x34, 0x20, 0x69, + 0x6e, 0x5f, 0x76, 0x61, 0x72, 0x5f, 0x43, 0x4f, 0x4c, 0x4f, 0x52, 0x30, + 0x20, 0x5b, 0x5b, 0x75, 0x73, 0x65, 0x72, 0x28, 0x6c, 0x6f, 0x63, 0x6e, + 0x30, 0x29, 0x5d, 0x5d, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, + 0x6f, 0x61, 0x74, 0x32, 0x20, 0x69, 0x6e, 0x5f, 0x76, 0x61, 0x72, 0x5f, + 0x54, 0x45, 0x58, 0x43, 0x4f, 0x4f, 0x52, 0x44, 0x30, 0x20, 0x5b, 0x5b, + 0x75, 0x73, 0x65, 0x72, 0x28, 0x6c, 0x6f, 0x63, 0x6e, 0x31, 0x29, 0x5d, + 0x5d, 0x3b, 0x0a, 0x7d, 0x3b, 0x0a, 0x0a, 0x66, 0x72, 0x61, 0x67, 0x6d, + 0x65, 0x6e, 0x74, 0x20, 0x6d, 0x61, 0x69, 0x6e, 0x30, 0x5f, 0x6f, 0x75, + 0x74, 0x20, 0x6d, 0x61, 0x69, 0x6e, 0x30, 0x28, 0x6d, 0x61, 0x69, 0x6e, + 0x30, 0x5f, 0x69, 0x6e, 0x20, 0x69, 0x6e, 0x20, 0x5b, 0x5b, 0x73, 0x74, + 0x61, 0x67, 0x65, 0x5f, 0x69, 0x6e, 0x5d, 0x5d, 0x2c, 0x20, 0x74, 0x65, + 0x78, 0x74, 0x75, 0x72, 0x65, 0x32, 0x64, 0x3c, 0x66, 0x6c, 0x6f, 0x61, + 0x74, 0x3e, 0x20, 0x75, 0x5f, 0x74, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, + 0x20, 0x5b, 0x5b, 0x74, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x28, 0x30, + 0x29, 0x5d, 0x5d, 0x2c, 0x20, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, + 0x20, 0x75, 0x5f, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x20, 0x5b, + 0x5b, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x28, 0x30, 0x29, 0x5d, + 0x5d, 0x29, 0x0a, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, 0x69, + 0x6e, 0x30, 0x5f, 0x6f, 0x75, 0x74, 0x20, 0x6f, 0x75, 0x74, 0x20, 0x3d, + 0x20, 0x7b, 0x7d, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, + 0x61, 0x74, 0x34, 0x20, 0x5f, 0x33, 0x30, 0x20, 0x3d, 0x20, 0x75, 0x5f, + 0x74, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x2e, 0x73, 0x61, 0x6d, 0x70, + 0x6c, 0x65, 0x28, 0x75, 0x5f, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, + 0x2c, 0x20, 0x69, 0x6e, 0x2e, 0x69, 0x6e, 0x5f, 0x76, 0x61, 0x72, 0x5f, + 0x54, 0x45, 0x58, 0x43, 0x4f, 0x4f, 0x52, 0x44, 0x30, 0x29, 0x20, 0x2a, + 0x20, 0x69, 0x6e, 0x2e, 0x69, 0x6e, 0x5f, 0x76, 0x61, 0x72, 0x5f, 0x43, + 0x4f, 0x4c, 0x4f, 0x52, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, + 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x5f, 0x33, 0x31, 0x20, 0x3d, 0x20, 0x5f, + 0x33, 0x30, 0x2e, 0x78, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, + 0x6f, 0x61, 0x74, 0x20, 0x5f, 0x33, 0x37, 0x20, 0x3d, 0x20, 0x28, 0x28, + 0x5f, 0x33, 0x31, 0x20, 0x2a, 0x20, 0x30, 0x2e, 0x32, 0x39, 0x38, 0x39, + 0x39, 0x39, 0x39, 0x39, 0x34, 0x39, 0x39, 0x33, 0x32, 0x30, 0x39, 0x38, + 0x33, 0x38, 0x38, 0x36, 0x37, 0x31, 0x38, 0x37, 0x35, 0x29, 0x20, 0x2b, + 0x20, 0x28, 0x5f, 0x33, 0x30, 0x2e, 0x79, 0x20, 0x2a, 0x20, 0x30, 0x2e, + 0x35, 0x38, 0x37, 0x30, 0x30, 0x30, 0x30, 0x31, 0x32, 0x33, 0x39, 0x37, + 0x37, 0x36, 0x36, 0x31, 0x31, 0x33, 0x32, 0x38, 0x31, 0x32, 0x35, 0x29, + 0x29, 0x20, 0x2b, 0x20, 0x28, 0x5f, 0x33, 0x31, 0x20, 0x2a, 0x20, 0x30, + 0x2e, 0x31, 0x31, 0x34, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x35, + 0x39, 0x36, 0x30, 0x34, 0x36, 0x34, 0x34, 0x37, 0x37, 0x35, 0x33, 0x39, + 0x30, 0x36, 0x32, 0x35, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6f, + 0x75, 0x74, 0x2e, 0x6f, 0x75, 0x74, 0x5f, 0x76, 0x61, 0x72, 0x5f, 0x53, + 0x56, 0x5f, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x20, 0x3d, 0x20, 0x66, + 0x6c, 0x6f, 0x61, 0x74, 0x34, 0x28, 0x5f, 0x33, 0x37, 0x2c, 0x20, 0x5f, + 0x33, 0x37, 0x2c, 0x20, 0x5f, 0x33, 0x37, 0x2c, 0x20, 0x5f, 0x33, 0x30, + 0x2e, 0x77, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x74, + 0x75, 0x72, 0x6e, 0x20, 0x6f, 0x75, 0x74, 0x3b, 0x0a, 0x7d, 0x0a, 0x0a +}; +static const unsigned int testgpu_effects_grayscale_frag_msl_len = 720; diff --git a/libs/SDL3/test/testgpurender_effects_grayscale.frag.spv.h b/libs/SDL3/test/testgpurender_effects_grayscale.frag.spv.h new file mode 100644 index 00000000..9d1f7759 --- /dev/null +++ b/libs/SDL3/test/testgpurender_effects_grayscale.frag.spv.h @@ -0,0 +1,96 @@ +static const unsigned char testgpu_effects_grayscale_frag_spv[] = { + 0x03, 0x02, 0x23, 0x07, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0e, 0x00, + 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x02, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x08, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x6d, 0x61, 0x69, 0x6e, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x10, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x03, 0x00, 0x05, 0x00, 0x00, 0x00, 0x58, 0x02, 0x00, 0x00, + 0x05, 0x00, 0x06, 0x00, 0x05, 0x00, 0x00, 0x00, 0x74, 0x79, 0x70, 0x65, + 0x2e, 0x32, 0x64, 0x2e, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x05, 0x00, 0x06, 0x00, 0x00, 0x00, 0x75, 0x5f, 0x74, 0x65, + 0x78, 0x74, 0x75, 0x72, 0x65, 0x00, 0x00, 0x00, 0x05, 0x00, 0x06, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x74, 0x79, 0x70, 0x65, 0x2e, 0x73, 0x61, 0x6d, + 0x70, 0x6c, 0x65, 0x72, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x05, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x75, 0x5f, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, + 0x72, 0x00, 0x00, 0x00, 0x05, 0x00, 0x06, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x69, 0x6e, 0x2e, 0x76, 0x61, 0x72, 0x2e, 0x43, 0x4f, 0x4c, 0x4f, 0x52, + 0x30, 0x00, 0x00, 0x00, 0x05, 0x00, 0x07, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x69, 0x6e, 0x2e, 0x76, 0x61, 0x72, 0x2e, 0x54, 0x45, 0x58, 0x43, 0x4f, + 0x4f, 0x52, 0x44, 0x30, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x07, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x6f, 0x75, 0x74, 0x2e, 0x76, 0x61, 0x72, 0x2e, + 0x53, 0x56, 0x5f, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x6d, 0x61, 0x69, 0x6e, + 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x07, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x74, 0x79, 0x70, 0x65, 0x2e, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x64, + 0x2e, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x47, 0x00, 0x04, 0x00, 0x03, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x47, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x22, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x16, 0x00, 0x03, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, + 0x2b, 0x00, 0x04, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, + 0x87, 0x16, 0x99, 0x3e, 0x2b, 0x00, 0x04, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x0c, 0x00, 0x00, 0x00, 0xa2, 0x45, 0x16, 0x3f, 0x2b, 0x00, 0x04, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0xd5, 0x78, 0xe9, 0x3d, + 0x19, 0x00, 0x09, 0x00, 0x05, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x20, 0x00, 0x04, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x02, 0x00, 0x07, 0x00, 0x00, 0x00, + 0x20, 0x00, 0x04, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x17, 0x00, 0x04, 0x00, 0x10, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, + 0x11, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, + 0x17, 0x00, 0x04, 0x00, 0x12, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x13, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, + 0x14, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, + 0x13, 0x00, 0x02, 0x00, 0x15, 0x00, 0x00, 0x00, 0x21, 0x00, 0x03, 0x00, + 0x16, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x03, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00, + 0x0e, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x3b, 0x00, 0x04, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00, 0x11, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00, + 0x13, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x3b, 0x00, 0x04, 0x00, 0x14, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x36, 0x00, 0x05, 0x00, 0x15, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, + 0xf8, 0x00, 0x02, 0x00, 0x17, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, + 0x10, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x3d, 0x00, 0x04, 0x00, 0x12, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, 0x05, 0x00, 0x00, 0x00, + 0x1a, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x56, 0x00, 0x05, 0x00, 0x09, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, + 0x1a, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x57, 0x00, 0x06, 0x00, + 0x10, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, + 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x85, 0x00, 0x05, 0x00, + 0x10, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, + 0x18, 0x00, 0x00, 0x00, 0x51, 0x00, 0x05, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x1f, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x85, 0x00, 0x05, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, + 0x1f, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x51, 0x00, 0x05, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x85, 0x00, 0x05, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x22, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, + 0x81, 0x00, 0x05, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, + 0x20, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x85, 0x00, 0x05, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, + 0x0d, 0x00, 0x00, 0x00, 0x81, 0x00, 0x05, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x25, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, + 0x51, 0x00, 0x05, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, + 0x1e, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x50, 0x00, 0x07, 0x00, + 0x10, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, + 0x25, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, + 0x3e, 0x00, 0x03, 0x00, 0x04, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, + 0xfd, 0x00, 0x01, 0x00, 0x38, 0x00, 0x01, 0x00 +}; +static const unsigned int testgpu_effects_grayscale_frag_spv_len = 1112; diff --git a/libs/SDL3/test/testgpurender_msdf.c b/libs/SDL3/test/testgpurender_msdf.c new file mode 100644 index 00000000..299d8beb --- /dev/null +++ b/libs/SDL3/test/testgpurender_msdf.c @@ -0,0 +1,318 @@ +/* + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely. +*/ + +#define SDL_MAIN_USE_CALLBACKS 1 /* use the callbacks instead of main() */ +#include +#include + +#include "testutils.h" + +/* This font was created with: + * ./msdf-atlas-gen.exe -font OpenSans-VariableFont_wdth,wght.ttf -chars '[32,126]' -type msdf -potr -yorigin top -imageout msdf_font.bmp -csv msdf_font.csv + */ + +/* This is the distance field range in pixels used when generating the font atlas, defaults to 2 */ +#define DISTANCE_FIELD_RANGE 2.0f + +/* MSDF shaders */ +#include "testgpurender_msdf.frag.dxil.h" +#include "testgpurender_msdf.frag.msl.h" +#include "testgpurender_msdf.frag.spv.h" + +typedef struct +{ + float distance_field_range; + float texture_width; + float texture_height; + float padding; +} MSDFShaderUniforms; + +static SDL_Window *window = NULL; +static SDL_Renderer *renderer = NULL; +static SDL_Texture *font_texture = NULL; +static SDL_GPUDevice *device = NULL; +static SDL_GPUShader *shader = NULL; +static SDL_GPURenderState *render_state = NULL; + +typedef struct +{ + bool loaded; + SDL_FRect src; + SDL_FRect dst; + float advance; +} GlyphInfo; + +static GlyphInfo glyphs[128]; + +static bool LoadFontTexture(void) +{ + font_texture = LoadTexture(renderer, "msdf_font.bmp", false); + if (!font_texture) { + SDL_Log("Failed to create font texture: %s", SDL_GetError()); + return false; + } + SDL_SetTextureBlendMode(font_texture, SDL_BLENDMODE_BLEND); + + /* Set the font color, doesn't need to be done every frame */ + SDL_SetTextureColorMod(font_texture, 0, 0, 0); + + return true; +} + +static bool LoadFontLayout(void) +{ + const char *file = "msdf_font.csv"; + char *path; + int offset = 0, len, codepoint; + float src_left, src_top, src_right, src_bottom; + float dst_left, dst_top, dst_right, dst_bottom; + float advance; + char *font_layout; + + path = GetNearbyFilename(file); + if (path) { + font_layout = (char *)SDL_LoadFile(path, NULL); + SDL_free(path); + } else { + font_layout = (char *)SDL_LoadFile(file, NULL); + } + if (!font_layout) { + SDL_Log("Failed to load font layout: %s", SDL_GetError()); + return false; + } + + while (SDL_sscanf(&font_layout[offset], "%d,%f,%f,%f,%f,%f,%f,%f,%f,%f%n", + &codepoint, &advance, + &dst_left, &dst_top, &dst_right, &dst_bottom, + &src_left, &src_top, &src_right, &src_bottom, &len) == 10) { + if (codepoint >= 0 && codepoint < SDL_arraysize(glyphs)) { + GlyphInfo *glyph = &glyphs[codepoint]; + glyph->loaded = true; + glyph->src.x = src_left; + glyph->src.y = src_top; + glyph->src.w = src_right - src_left; + glyph->src.h = src_bottom - src_top; + glyph->dst.x = dst_left; + glyph->dst.y = dst_top; + glyph->dst.w = dst_right - dst_left; + glyph->dst.h = dst_bottom - dst_top; + glyph->advance = advance; + } + offset += len; + } + SDL_free(font_layout); + return true; +} + +static float MeasureText(const char *text, float font_size) +{ + float width = 0.0f; + + while (*text) { + GlyphInfo *glyph; + Uint32 codepoint = SDL_StepUTF8(&text, NULL); + if (codepoint >= SDL_arraysize(glyphs)) { + continue; + } + + glyph = &glyphs[codepoint]; + if (!glyph->loaded) { + continue; + } + width += (glyph->advance * font_size); + } + return width; +} + +static void RenderText(const char *text, float font_size, float x, float y) +{ + SDL_FRect dst; + + /* The y coordinate is actually the baseline for the text */ + + while (*text) { + GlyphInfo *glyph; + Uint32 codepoint = SDL_StepUTF8(&text, NULL); + if (codepoint >= SDL_arraysize(glyphs)) { + continue; + } + + glyph = &glyphs[codepoint]; + if (!glyph->loaded) { + continue; + } + + dst.x = x + glyph->dst.x * font_size; + dst.y = y + glyph->dst.y * font_size; + dst.w = glyph->dst.w * font_size; + dst.h = glyph->dst.h * font_size; + SDL_RenderTexture(renderer, font_texture, &glyph->src, &dst); + x += (glyph->advance * font_size); + } +} + +static bool InitGPURenderState(void) +{ + SDL_GPUShaderFormat formats; + SDL_GPUShaderCreateInfo info; + SDL_GPURenderStateDesc desc; + MSDFShaderUniforms uniforms; + + device = (SDL_GPUDevice *)SDL_GetPointerProperty(SDL_GetRendererProperties(renderer), SDL_PROP_RENDERER_GPU_DEVICE_POINTER, NULL); + if (!device) { + SDL_Log("Couldn't get GPU device"); + return false; + } + + formats = SDL_GetGPUShaderFormats(device); + if (formats == SDL_GPU_SHADERFORMAT_INVALID) { + SDL_Log("Couldn't get supported shader formats: %s", SDL_GetError()); + return false; + } + + SDL_zero(info); + if (formats & SDL_GPU_SHADERFORMAT_SPIRV) { + info.format = SDL_GPU_SHADERFORMAT_SPIRV; + info.code = testgpurender_msdf_frag_spv; + info.code_size = testgpurender_msdf_frag_spv_len; + } else if (formats & SDL_GPU_SHADERFORMAT_DXIL) { + info.format = SDL_GPU_SHADERFORMAT_DXIL; + info.code = testgpurender_msdf_frag_dxil; + info.code_size = testgpurender_msdf_frag_dxil_len; + } else if (formats & SDL_GPU_SHADERFORMAT_MSL) { + info.format = SDL_GPU_SHADERFORMAT_MSL; + info.code = testgpurender_msdf_frag_msl; + info.code_size = testgpurender_msdf_frag_msl_len; + } else { + SDL_Log("No supported shader format found"); + return false; + } + info.num_samplers = 1; + info.num_uniform_buffers = 1; + info.stage = SDL_GPU_SHADERSTAGE_FRAGMENT; + shader = SDL_CreateGPUShader(device, &info); + if (!shader) { + SDL_Log("Couldn't create shader: %s", SDL_GetError()); + return false; + } + + SDL_INIT_INTERFACE(&desc); + desc.fragment_shader = shader; + render_state = SDL_CreateGPURenderState(renderer, &desc); + if (!render_state) { + SDL_Log("Couldn't create render state: %s", SDL_GetError()); + return false; + } + + SDL_zero(uniforms); + uniforms.distance_field_range = DISTANCE_FIELD_RANGE; + uniforms.texture_width = (float)font_texture->w; + uniforms.texture_height = (float)font_texture->h; + if (!SDL_SetGPURenderStateFragmentUniforms(render_state, 0, &uniforms, sizeof(uniforms))) { + SDL_Log("Couldn't set uniform data: %s", SDL_GetError()); + return false; + } + + return true; +} + +static void QuitGPURenderState(void) +{ + if (render_state) { + SDL_DestroyGPURenderState(render_state); + render_state = NULL; + } + if (shader) { + SDL_ReleaseGPUShader(device, shader); + shader = NULL; + } +} + +/* This function runs once at startup. */ +SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) +{ + const char *description = "GPU render MSDF example"; + + SDL_SetAppMetadata(description, "1.0", "com.example.testgpurender_msdf"); + + if (!SDL_Init(SDL_INIT_VIDEO)) { + SDL_Log("Couldn't initialize SDL: %s", SDL_GetError()); + return SDL_APP_FAILURE; + } + + window = SDL_CreateWindow(description, 640, 480, 0); + if (!window) { + SDL_Log("Couldn't create window: %s", SDL_GetError()); + return SDL_APP_FAILURE; + } + + renderer = SDL_CreateRenderer(window, "gpu"); + if (!renderer) { + SDL_Log("Couldn't create renderer: %s", SDL_GetError()); + return SDL_APP_FAILURE; + } + SDL_SetRenderVSync(renderer, 1); + + if (!LoadFontTexture() || !LoadFontLayout()) { + return SDL_APP_FAILURE; + } + + if (!InitGPURenderState()) { + return SDL_APP_FAILURE; + } + + return SDL_APP_CONTINUE; /* carry on with the program! */ +} + +/* This function runs when a new event (mouse input, keypresses, etc) occurs. */ +SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event) +{ + if (event->type == SDL_EVENT_QUIT || + (event->type == SDL_EVENT_KEY_DOWN && event->key.key == SDLK_ESCAPE)) { + return SDL_APP_SUCCESS; /* end the program, reporting success to the OS. */ + } + return SDL_APP_CONTINUE; /* carry on with the program! */ +} + +/* This function runs once per frame, and is the heart of the program. */ +SDL_AppResult SDL_AppIterate(void *appstate) +{ + const char *text = "Hello world!"; + float text_width; + float text_height; + float x, y; + int output_width, output_height; + + SDL_SetRenderDrawColor(renderer, 255, 255, 255, SDL_ALPHA_OPAQUE); + SDL_RenderClear(renderer); + + text_height = 72.0f; + text_width = MeasureText(text, text_height); + SDL_GetCurrentRenderOutputSize(renderer, &output_width, &output_height); + x = (output_width - text_width) / 2; + y = (output_height - text_height) / 2; + SDL_SetRenderGPUState(renderer, render_state); + RenderText("Hello World!", text_height, x, y); + SDL_SetRenderGPUState(renderer, NULL); + + SDL_RenderPresent(renderer); + + return SDL_APP_CONTINUE; /* carry on with the program! */ +} + +/* This function runs once at shutdown. */ +void SDL_AppQuit(void *appstate, SDL_AppResult result) +{ + /* SDL will clean up the window/renderer for us. */ + QuitGPURenderState(); +} + diff --git a/libs/SDL3/test/testgpurender_msdf.frag.dxil.h b/libs/SDL3/test/testgpurender_msdf.frag.dxil.h new file mode 100644 index 00000000..6a3037a7 --- /dev/null +++ b/libs/SDL3/test/testgpurender_msdf.frag.dxil.h @@ -0,0 +1,399 @@ +static const unsigned char testgpurender_msdf_frag_dxil[] = { + 0x44, 0x58, 0x42, 0x43, 0x2e, 0x49, 0xe7, 0xd0, 0x3a, 0xbb, 0xb2, 0xf2, + 0x7d, 0x23, 0x3a, 0x6b, 0x9a, 0xa6, 0x80, 0x0a, 0x01, 0x00, 0x00, 0x00, + 0x88, 0x12, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, + 0x4c, 0x00, 0x00, 0x00, 0xa8, 0x00, 0x00, 0x00, 0xe4, 0x00, 0x00, 0x00, + 0xf0, 0x01, 0x00, 0x00, 0xa0, 0x09, 0x00, 0x00, 0xbc, 0x09, 0x00, 0x00, + 0x53, 0x46, 0x49, 0x30, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x49, 0x53, 0x47, 0x31, 0x54, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x48, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x0f, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x03, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x54, 0x45, 0x58, 0x43, 0x4f, 0x4f, 0x52, 0x44, 0x00, 0x00, 0x00, 0x00, + 0x4f, 0x53, 0x47, 0x31, 0x34, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x53, 0x56, 0x5f, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x00, 0x00, 0x00, + 0x50, 0x53, 0x56, 0x30, 0x04, 0x01, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x01, 0x00, 0x02, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x13, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x18, 0x00, 0x00, 0x00, 0x00, 0x54, 0x45, 0x58, 0x43, 0x4f, 0x4f, 0x52, + 0x44, 0x00, 0x54, 0x45, 0x58, 0x43, 0x4f, 0x4f, 0x52, 0x44, 0x00, 0x6d, + 0x61, 0x69, 0x6e, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x44, 0x00, 0x03, 0x02, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x01, 0x42, 0x00, + 0x03, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x44, 0x10, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x53, 0x54, 0x41, 0x54, 0xa8, 0x07, 0x00, 0x00, + 0x60, 0x00, 0x00, 0x00, 0xea, 0x01, 0x00, 0x00, 0x44, 0x58, 0x49, 0x4c, + 0x00, 0x01, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x90, 0x07, 0x00, 0x00, + 0x42, 0x43, 0xc0, 0xde, 0x21, 0x0c, 0x00, 0x00, 0xe1, 0x01, 0x00, 0x00, + 0x0b, 0x82, 0x20, 0x00, 0x02, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, + 0x07, 0x81, 0x23, 0x91, 0x41, 0xc8, 0x04, 0x49, 0x06, 0x10, 0x32, 0x39, + 0x92, 0x01, 0x84, 0x0c, 0x25, 0x05, 0x08, 0x19, 0x1e, 0x04, 0x8b, 0x62, + 0x80, 0x18, 0x45, 0x02, 0x42, 0x92, 0x0b, 0x42, 0xc4, 0x10, 0x32, 0x14, + 0x38, 0x08, 0x18, 0x4b, 0x0a, 0x32, 0x62, 0x88, 0x48, 0x90, 0x14, 0x20, + 0x43, 0x46, 0x88, 0xa5, 0x00, 0x19, 0x32, 0x42, 0xe4, 0x48, 0x0e, 0x90, + 0x11, 0x23, 0xc4, 0x50, 0x41, 0x51, 0x81, 0x8c, 0xe1, 0x83, 0xe5, 0x8a, + 0x04, 0x31, 0x46, 0x06, 0x51, 0x18, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x1b, 0x8c, 0xe0, 0xff, 0xff, 0xff, 0xff, 0x07, 0x40, 0x02, 0xa8, 0x0d, + 0x84, 0xf0, 0xff, 0xff, 0xff, 0xff, 0x03, 0x20, 0x6d, 0x30, 0x86, 0xff, + 0xff, 0xff, 0xff, 0x1f, 0x00, 0x09, 0xa8, 0x00, 0x49, 0x18, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x13, 0x82, 0x60, 0x42, 0x20, 0x4c, 0x08, 0x06, + 0x00, 0x00, 0x00, 0x00, 0x89, 0x20, 0x00, 0x00, 0x54, 0x00, 0x00, 0x00, + 0x32, 0x22, 0x88, 0x09, 0x20, 0x64, 0x85, 0x04, 0x13, 0x23, 0xa4, 0x84, + 0x04, 0x13, 0x23, 0xe3, 0x84, 0xa1, 0x90, 0x14, 0x12, 0x4c, 0x8c, 0x8c, + 0x0b, 0x84, 0xc4, 0x4c, 0x10, 0x98, 0xc1, 0x08, 0x40, 0x09, 0x00, 0x0a, + 0x66, 0x00, 0xe6, 0x08, 0xc0, 0x60, 0x8e, 0x00, 0x29, 0xc6, 0x40, 0x10, + 0x44, 0x41, 0x90, 0x51, 0x0c, 0x80, 0x20, 0x88, 0x62, 0x20, 0xe4, 0xa6, + 0xe1, 0xf2, 0x27, 0xec, 0x21, 0x24, 0x7f, 0x25, 0xa4, 0x95, 0x98, 0xfc, + 0xe2, 0xb6, 0x51, 0x31, 0x0c, 0xc3, 0x40, 0x50, 0x71, 0xcf, 0x70, 0xf9, + 0x13, 0xf6, 0x10, 0x92, 0x1f, 0x02, 0xcd, 0xb0, 0x10, 0x28, 0x58, 0x0a, + 0xa3, 0x10, 0x0c, 0x33, 0x0c, 0xc3, 0x40, 0x10, 0xc4, 0x40, 0x4d, 0x21, + 0x06, 0x62, 0x18, 0xe8, 0x29, 0xc6, 0x40, 0x0c, 0xc3, 0x30, 0x50, 0x54, + 0x86, 0x81, 0x18, 0x68, 0x3a, 0x6a, 0xb8, 0xfc, 0x09, 0x7b, 0x08, 0xc9, + 0xe7, 0x36, 0xaa, 0x58, 0x89, 0xc9, 0x2f, 0x6e, 0x1b, 0x11, 0xc3, 0x30, + 0x0c, 0x85, 0xa8, 0x08, 0x86, 0x20, 0x6b, 0x8e, 0x20, 0x28, 0x06, 0x43, + 0x14, 0x04, 0x81, 0x51, 0x36, 0x10, 0x30, 0x8c, 0x40, 0x0c, 0x33, 0xb5, + 0xc1, 0x38, 0xb0, 0x43, 0x38, 0xcc, 0xc3, 0x3c, 0xb8, 0x01, 0x2d, 0x94, + 0x03, 0x3e, 0xd0, 0x43, 0x3d, 0xc8, 0x43, 0x39, 0xc8, 0x01, 0x29, 0xf0, + 0x81, 0x3d, 0x94, 0xc3, 0x38, 0xd0, 0xc3, 0x3b, 0xc8, 0x03, 0x1f, 0x98, + 0x03, 0x3b, 0xbc, 0x43, 0x38, 0xd0, 0x03, 0x1b, 0x80, 0x01, 0x1d, 0xf8, + 0x01, 0x18, 0xf8, 0x81, 0x1e, 0xe8, 0x41, 0x3b, 0xa4, 0x03, 0x3c, 0xcc, + 0xc3, 0x2f, 0xd0, 0x43, 0x3e, 0xc0, 0x43, 0x39, 0xa0, 0x80, 0x98, 0x49, + 0x0c, 0xc6, 0x81, 0x1d, 0xc2, 0x61, 0x1e, 0xe6, 0xc1, 0x0d, 0x68, 0xa1, + 0x1c, 0xf0, 0x81, 0x1e, 0xea, 0x41, 0x1e, 0xca, 0x41, 0x0e, 0x48, 0x81, + 0x0f, 0xec, 0xa1, 0x1c, 0xc6, 0x81, 0x1e, 0xde, 0x41, 0x1e, 0xf8, 0xc0, + 0x1c, 0xd8, 0xe1, 0x1d, 0xc2, 0x81, 0x1e, 0xd8, 0x00, 0x0c, 0xe8, 0xc0, + 0x0f, 0xc0, 0xc0, 0x0f, 0x90, 0xc0, 0x75, 0xe4, 0x0d, 0x23, 0x08, 0xc3, + 0x31, 0x13, 0xf6, 0x10, 0x3f, 0xe7, 0x34, 0x13, 0x71, 0x4d, 0x48, 0x18, + 0x40, 0x0a, 0x6f, 0x92, 0xa6, 0x88, 0x12, 0x26, 0x9f, 0x05, 0x98, 0x67, + 0x21, 0x22, 0x76, 0x02, 0x26, 0x02, 0x05, 0x04, 0x8d, 0x89, 0x40, 0x00, + 0x13, 0x14, 0x72, 0xc0, 0x87, 0x74, 0x60, 0x87, 0x36, 0x68, 0x87, 0x79, + 0x68, 0x03, 0x72, 0xc0, 0x87, 0x0d, 0xaf, 0x50, 0x0e, 0x6d, 0xd0, 0x0e, + 0x7a, 0x50, 0x0e, 0x6d, 0x00, 0x0f, 0x7a, 0x30, 0x07, 0x72, 0xa0, 0x07, + 0x73, 0x20, 0x07, 0x6d, 0x90, 0x0e, 0x71, 0xa0, 0x07, 0x73, 0x20, 0x07, + 0x6d, 0x90, 0x0e, 0x78, 0xa0, 0x07, 0x73, 0x20, 0x07, 0x6d, 0x90, 0x0e, + 0x71, 0x60, 0x07, 0x7a, 0x30, 0x07, 0x72, 0xd0, 0x06, 0xe9, 0x30, 0x07, + 0x72, 0xa0, 0x07, 0x73, 0x20, 0x07, 0x6d, 0x90, 0x0e, 0x76, 0x40, 0x07, + 0x7a, 0x60, 0x07, 0x74, 0xd0, 0x06, 0xe6, 0x10, 0x07, 0x76, 0xa0, 0x07, + 0x73, 0x20, 0x07, 0x6d, 0x60, 0x0e, 0x73, 0x20, 0x07, 0x7a, 0x30, 0x07, + 0x72, 0xd0, 0x06, 0xe6, 0x60, 0x07, 0x74, 0xa0, 0x07, 0x76, 0x40, 0x07, + 0x6d, 0xe0, 0x0e, 0x78, 0xa0, 0x07, 0x71, 0x60, 0x07, 0x7a, 0x30, 0x07, + 0x72, 0xa0, 0x07, 0x76, 0x40, 0x07, 0x43, 0x9e, 0x00, 0x08, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x86, 0x3c, 0x06, 0x10, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x79, 0x10, 0x20, + 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0xf2, 0x34, + 0x40, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0xe4, + 0x79, 0x80, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, + 0xc8, 0x13, 0x01, 0x01, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xc0, 0x90, 0x67, 0x02, 0x02, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x80, 0x21, 0x8f, 0x05, 0x04, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x43, 0x9e, 0x0c, 0x08, 0x80, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xb2, 0x40, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, + 0x32, 0x1e, 0x98, 0x18, 0x19, 0x11, 0x4c, 0x90, 0x8c, 0x09, 0x26, 0x47, + 0xc6, 0x04, 0x43, 0x22, 0x4a, 0x60, 0x04, 0xa0, 0x18, 0x8a, 0xa0, 0x24, + 0xca, 0xa0, 0x30, 0xca, 0xa1, 0x10, 0x4a, 0xa3, 0x3c, 0xca, 0xa5, 0xc4, + 0xa8, 0x28, 0x89, 0x11, 0x80, 0x22, 0x28, 0x84, 0x02, 0x21, 0xac, 0x06, + 0xe8, 0x9b, 0x01, 0xa0, 0x70, 0x06, 0x80, 0xc4, 0x19, 0x00, 0x22, 0x67, + 0x00, 0xa8, 0x1c, 0x0b, 0x31, 0x88, 0x40, 0x20, 0x10, 0x04, 0x01, 0x00, + 0x79, 0x18, 0x00, 0x00, 0x9d, 0x00, 0x00, 0x00, 0x1a, 0x03, 0x4c, 0x90, + 0x46, 0x02, 0x13, 0xc4, 0x8e, 0x0c, 0x6f, 0xec, 0xed, 0x4d, 0x0c, 0x24, + 0xc6, 0x05, 0xc7, 0x45, 0xa6, 0x06, 0x46, 0xc6, 0x05, 0x07, 0x04, 0x45, + 0x8c, 0xe6, 0x26, 0x26, 0x06, 0x67, 0x26, 0xa7, 0x2c, 0x65, 0x43, 0x10, + 0x4c, 0x10, 0x88, 0x64, 0x82, 0x40, 0x28, 0x1b, 0x84, 0x81, 0xd8, 0x20, + 0x10, 0x04, 0x05, 0xb8, 0xb9, 0x09, 0x02, 0xb1, 0x6c, 0x18, 0x0e, 0x84, + 0x98, 0x20, 0x7c, 0x1c, 0x93, 0xba, 0x2f, 0xba, 0x32, 0x3c, 0xba, 0x3a, + 0xb9, 0xb2, 0x09, 0x02, 0xc1, 0x4c, 0x10, 0x88, 0x66, 0x83, 0x40, 0x34, + 0x1b, 0x12, 0x42, 0x59, 0x18, 0x62, 0x60, 0x08, 0x67, 0x43, 0xf0, 0x4c, + 0x10, 0xc4, 0xc0, 0x23, 0x43, 0x97, 0x07, 0x57, 0xf6, 0x35, 0xf4, 0xe6, + 0x46, 0x57, 0x86, 0x47, 0x37, 0x41, 0x20, 0x9c, 0x09, 0x02, 0xf1, 0x6c, + 0x40, 0x88, 0x48, 0x9a, 0x88, 0x81, 0x02, 0x36, 0x04, 0xd5, 0x04, 0x81, + 0x0c, 0x3e, 0x26, 0x75, 0x5f, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, + 0x1b, 0x10, 0xe2, 0xc2, 0x18, 0x62, 0x20, 0x80, 0x0d, 0x41, 0xb6, 0x81, + 0x80, 0x00, 0x4b, 0x9b, 0x20, 0x84, 0x41, 0x47, 0x6e, 0xe8, 0xcd, 0x8d, + 0xae, 0x0c, 0x8f, 0xee, 0x8b, 0x2c, 0x6d, 0x8e, 0x2e, 0xcc, 0x6d, 0xac, + 0xec, 0xcb, 0x2c, 0xad, 0x8c, 0x8d, 0xec, 0x4b, 0x2e, 0xcc, 0xed, 0xac, + 0x6c, 0x82, 0x40, 0x40, 0x13, 0x04, 0x6c, 0xdb, 0x80, 0x20, 0xdd, 0x44, + 0x78, 0x4d, 0xf3, 0x91, 0x1a, 0x7a, 0x73, 0xa3, 0x2b, 0xc3, 0xa3, 0xfb, + 0xa2, 0x2b, 0xc3, 0xa3, 0xab, 0x93, 0x2b, 0xfb, 0x9a, 0x4b, 0xd3, 0x2b, + 0x9b, 0x20, 0x10, 0xd1, 0x04, 0x81, 0x90, 0x36, 0x28, 0x48, 0x18, 0x4c, + 0x62, 0xe0, 0x35, 0xcd, 0x37, 0x06, 0xcc, 0x86, 0x81, 0x02, 0x03, 0x32, + 0xd8, 0x30, 0x10, 0x5c, 0x19, 0x4c, 0x10, 0x04, 0x60, 0x03, 0xb0, 0x61, + 0x20, 0xd0, 0x00, 0x0d, 0x36, 0x04, 0x69, 0xb0, 0x61, 0x18, 0xce, 0x40, + 0x0d, 0x26, 0x08, 0x65, 0x00, 0x06, 0x1b, 0x02, 0x36, 0x20, 0xd1, 0x16, + 0x96, 0xe6, 0x46, 0x84, 0xaa, 0x08, 0x6b, 0xe8, 0xe9, 0x49, 0x8a, 0x68, + 0x82, 0x50, 0x58, 0x13, 0x84, 0xe2, 0xda, 0x10, 0x10, 0x13, 0x84, 0x02, + 0x9b, 0x20, 0x14, 0xd9, 0x04, 0x81, 0x98, 0x36, 0x08, 0x53, 0x1d, 0x6c, + 0x58, 0x88, 0x37, 0x80, 0x83, 0x38, 0x90, 0x83, 0x39, 0x18, 0xe8, 0x80, + 0x88, 0x03, 0x3b, 0xd8, 0x10, 0x0c, 0x1b, 0x84, 0x69, 0xda, 0xb0, 0x0c, + 0x6f, 0x00, 0x07, 0x71, 0x80, 0x07, 0x73, 0x30, 0xcc, 0xc1, 0x10, 0x07, + 0x79, 0xb0, 0x41, 0xb8, 0x03, 0x3d, 0x60, 0x32, 0x65, 0xf5, 0x45, 0x15, + 0x26, 0x77, 0x56, 0x46, 0x37, 0x41, 0x28, 0xb4, 0x0d, 0x0b, 0xc1, 0x07, + 0x70, 0xd0, 0x07, 0x72, 0x10, 0x07, 0x03, 0x1d, 0x10, 0x71, 0x60, 0x07, + 0x1b, 0x02, 0x3f, 0xd8, 0x30, 0xec, 0xc1, 0x1f, 0x00, 0x1b, 0x8a, 0x33, + 0x70, 0x03, 0x50, 0xd8, 0x00, 0x1a, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x73, + 0x13, 0x04, 0x82, 0x62, 0x91, 0xe6, 0x36, 0x47, 0x37, 0x37, 0x41, 0x20, + 0x2a, 0x1a, 0x73, 0x69, 0x67, 0x5f, 0x6c, 0x64, 0x34, 0xe6, 0xd2, 0xce, + 0xbe, 0xe6, 0xe8, 0x88, 0xd0, 0x95, 0xe1, 0x7d, 0xb9, 0xbd, 0xc9, 0xb5, + 0x6d, 0x50, 0x44, 0x61, 0x14, 0x48, 0xa1, 0x14, 0x4c, 0x01, 0x39, 0x05, + 0x31, 0x40, 0x85, 0xa1, 0x0a, 0x1b, 0x9b, 0x5d, 0x9b, 0x4b, 0x1a, 0x59, + 0x99, 0x1b, 0xdd, 0x94, 0x20, 0xa8, 0x42, 0x86, 0xe7, 0x62, 0x57, 0x26, + 0x37, 0x97, 0xf6, 0xe6, 0x36, 0x25, 0x20, 0x9a, 0x90, 0xe1, 0xb9, 0xd8, + 0x85, 0xb1, 0xd9, 0x95, 0xc9, 0x4d, 0x09, 0x8a, 0x3a, 0x64, 0x78, 0x2e, + 0x73, 0x68, 0x61, 0x64, 0x65, 0x72, 0x4d, 0x6f, 0x64, 0x65, 0x6c, 0x53, + 0x02, 0xa4, 0x0c, 0x19, 0x9e, 0x8b, 0x5c, 0xd9, 0xdc, 0x5b, 0x9d, 0xdc, + 0x58, 0xd9, 0xdc, 0x94, 0x40, 0xab, 0x44, 0x86, 0xe7, 0x42, 0x97, 0x07, + 0x57, 0x16, 0xe4, 0xe6, 0xf6, 0x46, 0x17, 0x46, 0x97, 0xf6, 0xe6, 0x36, + 0x37, 0x45, 0x28, 0x03, 0x35, 0xa8, 0x43, 0x86, 0xe7, 0x62, 0x97, 0x56, + 0x76, 0x97, 0x44, 0x36, 0x45, 0x17, 0x46, 0x57, 0x36, 0x25, 0x60, 0x83, + 0x3a, 0x64, 0x78, 0x2e, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x50, 0x6f, 0x69, + 0x6e, 0x74, 0x73, 0x53, 0x02, 0x50, 0xe8, 0x42, 0x86, 0xe7, 0x32, 0xf6, + 0x56, 0xe7, 0x46, 0x57, 0x26, 0x37, 0x37, 0x25, 0x40, 0x05, 0x00, 0x00, + 0x79, 0x18, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00, 0x33, 0x08, 0x80, 0x1c, + 0xc4, 0xe1, 0x1c, 0x66, 0x14, 0x01, 0x3d, 0x88, 0x43, 0x38, 0x84, 0xc3, + 0x8c, 0x42, 0x80, 0x07, 0x79, 0x78, 0x07, 0x73, 0x98, 0x71, 0x0c, 0xe6, + 0x00, 0x0f, 0xed, 0x10, 0x0e, 0xf4, 0x80, 0x0e, 0x33, 0x0c, 0x42, 0x1e, + 0xc2, 0xc1, 0x1d, 0xce, 0xa1, 0x1c, 0x66, 0x30, 0x05, 0x3d, 0x88, 0x43, + 0x38, 0x84, 0x83, 0x1b, 0xcc, 0x03, 0x3d, 0xc8, 0x43, 0x3d, 0x8c, 0x03, + 0x3d, 0xcc, 0x78, 0x8c, 0x74, 0x70, 0x07, 0x7b, 0x08, 0x07, 0x79, 0x48, + 0x87, 0x70, 0x70, 0x07, 0x7a, 0x70, 0x03, 0x76, 0x78, 0x87, 0x70, 0x20, + 0x87, 0x19, 0xcc, 0x11, 0x0e, 0xec, 0x90, 0x0e, 0xe1, 0x30, 0x0f, 0x6e, + 0x30, 0x0f, 0xe3, 0xf0, 0x0e, 0xf0, 0x50, 0x0e, 0x33, 0x10, 0xc4, 0x1d, + 0xde, 0x21, 0x1c, 0xd8, 0x21, 0x1d, 0xc2, 0x61, 0x1e, 0x66, 0x30, 0x89, + 0x3b, 0xbc, 0x83, 0x3b, 0xd0, 0x43, 0x39, 0xb4, 0x03, 0x3c, 0xbc, 0x83, + 0x3c, 0x84, 0x03, 0x3b, 0xcc, 0xf0, 0x14, 0x76, 0x60, 0x07, 0x7b, 0x68, + 0x07, 0x37, 0x68, 0x87, 0x72, 0x68, 0x07, 0x37, 0x80, 0x87, 0x70, 0x90, + 0x87, 0x70, 0x60, 0x07, 0x76, 0x28, 0x07, 0x76, 0xf8, 0x05, 0x76, 0x78, + 0x87, 0x77, 0x80, 0x87, 0x5f, 0x08, 0x87, 0x71, 0x18, 0x87, 0x72, 0x98, + 0x87, 0x79, 0x98, 0x81, 0x2c, 0xee, 0xf0, 0x0e, 0xee, 0xe0, 0x0e, 0xf5, + 0xc0, 0x0e, 0xec, 0x30, 0x03, 0x62, 0xc8, 0xa1, 0x1c, 0xe4, 0xa1, 0x1c, + 0xcc, 0xa1, 0x1c, 0xe4, 0xa1, 0x1c, 0xdc, 0x61, 0x1c, 0xca, 0x21, 0x1c, + 0xc4, 0x81, 0x1d, 0xca, 0x61, 0x06, 0xd6, 0x90, 0x43, 0x39, 0xc8, 0x43, + 0x39, 0x98, 0x43, 0x39, 0xc8, 0x43, 0x39, 0xb8, 0xc3, 0x38, 0x94, 0x43, + 0x38, 0x88, 0x03, 0x3b, 0x94, 0xc3, 0x2f, 0xbc, 0x83, 0x3c, 0xfc, 0x82, + 0x3b, 0xd4, 0x03, 0x3b, 0xb0, 0xc3, 0x8c, 0xc8, 0x21, 0x07, 0x7c, 0x70, + 0x03, 0x72, 0x10, 0x87, 0x73, 0x70, 0x03, 0x7b, 0x08, 0x07, 0x79, 0x60, + 0x87, 0x70, 0xc8, 0x87, 0x77, 0xa8, 0x07, 0x7a, 0x98, 0x81, 0x3c, 0xe4, + 0x80, 0x0f, 0x6e, 0x40, 0x0f, 0xe5, 0xd0, 0x0e, 0xf0, 0x00, 0x00, 0x00, + 0x71, 0x20, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x46, 0x00, 0x0d, 0x97, + 0xef, 0x3c, 0x7e, 0x80, 0x34, 0x40, 0x84, 0xf9, 0xc5, 0x6d, 0xdb, 0xc1, + 0x36, 0x5c, 0xbe, 0xf3, 0xf8, 0x42, 0x40, 0x15, 0x05, 0x11, 0x95, 0x0e, + 0x30, 0x94, 0x84, 0x01, 0x08, 0x98, 0x5f, 0xdc, 0xb6, 0x21, 0x48, 0xc3, + 0xe5, 0x3b, 0x8f, 0x2f, 0x44, 0x04, 0x30, 0x11, 0x21, 0xd0, 0x0c, 0x0b, + 0x61, 0x05, 0xce, 0x70, 0xf9, 0xce, 0xe3, 0x0f, 0xce, 0x64, 0xfb, 0xc5, + 0x6d, 0x5b, 0xc0, 0x34, 0x5c, 0xbe, 0xf3, 0xf8, 0x8b, 0x03, 0x0c, 0x62, + 0xf3, 0x50, 0x93, 0x5f, 0xdc, 0xb6, 0x0d, 0x40, 0xc3, 0xe5, 0x3b, 0x8f, + 0x2f, 0x01, 0xcc, 0xb3, 0x10, 0x7e, 0x71, 0xdb, 0x26, 0x50, 0x0d, 0x97, + 0xef, 0x3c, 0xbe, 0x34, 0x39, 0x11, 0x81, 0x52, 0xd3, 0x43, 0x4d, 0x7e, + 0x71, 0xdb, 0x66, 0xf0, 0x0c, 0x97, 0xef, 0x3c, 0x3e, 0xd5, 0x00, 0x11, + 0xe6, 0x17, 0xb7, 0x6d, 0x00, 0x04, 0x03, 0x20, 0x0d, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x48, 0x41, 0x53, 0x48, 0x14, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xa0, 0x40, 0x54, 0xe9, 0xac, 0xdc, 0xa5, 0x12, + 0x84, 0x83, 0x18, 0xdf, 0x37, 0xc0, 0x99, 0x88, 0x44, 0x58, 0x49, 0x4c, + 0xc4, 0x08, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x31, 0x02, 0x00, 0x00, + 0x44, 0x58, 0x49, 0x4c, 0x00, 0x01, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, + 0xac, 0x08, 0x00, 0x00, 0x42, 0x43, 0xc0, 0xde, 0x21, 0x0c, 0x00, 0x00, + 0x28, 0x02, 0x00, 0x00, 0x0b, 0x82, 0x20, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x13, 0x00, 0x00, 0x00, 0x07, 0x81, 0x23, 0x91, 0x41, 0xc8, 0x04, 0x49, + 0x06, 0x10, 0x32, 0x39, 0x92, 0x01, 0x84, 0x0c, 0x25, 0x05, 0x08, 0x19, + 0x1e, 0x04, 0x8b, 0x62, 0x80, 0x18, 0x45, 0x02, 0x42, 0x92, 0x0b, 0x42, + 0xc4, 0x10, 0x32, 0x14, 0x38, 0x08, 0x18, 0x4b, 0x0a, 0x32, 0x62, 0x88, + 0x48, 0x90, 0x14, 0x20, 0x43, 0x46, 0x88, 0xa5, 0x00, 0x19, 0x32, 0x42, + 0xe4, 0x48, 0x0e, 0x90, 0x11, 0x23, 0xc4, 0x50, 0x41, 0x51, 0x81, 0x8c, + 0xe1, 0x83, 0xe5, 0x8a, 0x04, 0x31, 0x46, 0x06, 0x51, 0x18, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x1b, 0x8c, 0xe0, 0xff, 0xff, 0xff, 0xff, 0x07, + 0x40, 0x02, 0xa8, 0x0d, 0x84, 0xf0, 0xff, 0xff, 0xff, 0xff, 0x03, 0x20, + 0x6d, 0x30, 0x86, 0xff, 0xff, 0xff, 0xff, 0x1f, 0x00, 0x09, 0xa8, 0x00, + 0x49, 0x18, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x13, 0x82, 0x60, 0x42, + 0x20, 0x4c, 0x08, 0x06, 0x00, 0x00, 0x00, 0x00, 0x89, 0x20, 0x00, 0x00, + 0x54, 0x00, 0x00, 0x00, 0x32, 0x22, 0x88, 0x09, 0x20, 0x64, 0x85, 0x04, + 0x13, 0x23, 0xa4, 0x84, 0x04, 0x13, 0x23, 0xe3, 0x84, 0xa1, 0x90, 0x14, + 0x12, 0x4c, 0x8c, 0x8c, 0x0b, 0x84, 0xc4, 0x4c, 0x10, 0x98, 0xc1, 0x08, + 0x40, 0x09, 0x00, 0x0a, 0x66, 0x00, 0xe6, 0x08, 0xc0, 0x60, 0x8e, 0x00, + 0x29, 0xc6, 0x40, 0x10, 0x44, 0x41, 0x90, 0x51, 0x0c, 0x80, 0x20, 0x88, + 0x62, 0x20, 0xe4, 0xa6, 0xe1, 0xf2, 0x27, 0xec, 0x21, 0x24, 0x7f, 0x25, + 0xa4, 0x95, 0x98, 0xfc, 0xe2, 0xb6, 0x51, 0x31, 0x0c, 0xc3, 0x40, 0x50, + 0x71, 0xcf, 0x70, 0xf9, 0x13, 0xf6, 0x10, 0x92, 0x1f, 0x02, 0xcd, 0xb0, + 0x10, 0x28, 0x58, 0x0a, 0xa3, 0x10, 0x0c, 0x33, 0x0c, 0xc3, 0x40, 0x10, + 0xc4, 0x40, 0x4d, 0x21, 0x06, 0x62, 0x18, 0xe8, 0x29, 0xc6, 0x40, 0x0c, + 0xc3, 0x30, 0x50, 0x54, 0x86, 0x81, 0x18, 0x68, 0x3a, 0x6a, 0xb8, 0xfc, + 0x09, 0x7b, 0x08, 0xc9, 0xe7, 0x36, 0xaa, 0x58, 0x89, 0xc9, 0x2f, 0x6e, + 0x1b, 0x11, 0xc3, 0x30, 0x0c, 0x85, 0xa8, 0x08, 0x86, 0x20, 0x6b, 0x8e, + 0x20, 0x28, 0x06, 0x43, 0x14, 0x04, 0x81, 0x51, 0x36, 0x10, 0x30, 0x8c, + 0x40, 0x0c, 0x33, 0xb5, 0xc1, 0x38, 0xb0, 0x43, 0x38, 0xcc, 0xc3, 0x3c, + 0xb8, 0x01, 0x2d, 0x94, 0x03, 0x3e, 0xd0, 0x43, 0x3d, 0xc8, 0x43, 0x39, + 0xc8, 0x01, 0x29, 0xf0, 0x81, 0x3d, 0x94, 0xc3, 0x38, 0xd0, 0xc3, 0x3b, + 0xc8, 0x03, 0x1f, 0x98, 0x03, 0x3b, 0xbc, 0x43, 0x38, 0xd0, 0x03, 0x1b, + 0x80, 0x01, 0x1d, 0xf8, 0x01, 0x18, 0xf8, 0x81, 0x1e, 0xe8, 0x41, 0x3b, + 0xa4, 0x03, 0x3c, 0xcc, 0xc3, 0x2f, 0xd0, 0x43, 0x3e, 0xc0, 0x43, 0x39, + 0xa0, 0x80, 0x98, 0x49, 0x0c, 0xc6, 0x81, 0x1d, 0xc2, 0x61, 0x1e, 0xe6, + 0xc1, 0x0d, 0x68, 0xa1, 0x1c, 0xf0, 0x81, 0x1e, 0xea, 0x41, 0x1e, 0xca, + 0x41, 0x0e, 0x48, 0x81, 0x0f, 0xec, 0xa1, 0x1c, 0xc6, 0x81, 0x1e, 0xde, + 0x41, 0x1e, 0xf8, 0xc0, 0x1c, 0xd8, 0xe1, 0x1d, 0xc2, 0x81, 0x1e, 0xd8, + 0x00, 0x0c, 0xe8, 0xc0, 0x0f, 0xc0, 0xc0, 0x0f, 0x90, 0xc0, 0x75, 0xe4, + 0x0d, 0x23, 0x08, 0xc3, 0x31, 0x13, 0xf6, 0x10, 0x3f, 0xe7, 0x34, 0x13, + 0x71, 0x4d, 0x48, 0x18, 0x40, 0x0a, 0x6f, 0x92, 0xa6, 0x88, 0x12, 0x26, + 0x9f, 0x05, 0x98, 0x67, 0x21, 0x22, 0x76, 0x02, 0x26, 0x02, 0x05, 0x04, + 0x8d, 0x89, 0x40, 0x00, 0x13, 0x14, 0x72, 0xc0, 0x87, 0x74, 0x60, 0x87, + 0x36, 0x68, 0x87, 0x79, 0x68, 0x03, 0x72, 0xc0, 0x87, 0x0d, 0xaf, 0x50, + 0x0e, 0x6d, 0xd0, 0x0e, 0x7a, 0x50, 0x0e, 0x6d, 0x00, 0x0f, 0x7a, 0x30, + 0x07, 0x72, 0xa0, 0x07, 0x73, 0x20, 0x07, 0x6d, 0x90, 0x0e, 0x71, 0xa0, + 0x07, 0x73, 0x20, 0x07, 0x6d, 0x90, 0x0e, 0x78, 0xa0, 0x07, 0x73, 0x20, + 0x07, 0x6d, 0x90, 0x0e, 0x71, 0x60, 0x07, 0x7a, 0x30, 0x07, 0x72, 0xd0, + 0x06, 0xe9, 0x30, 0x07, 0x72, 0xa0, 0x07, 0x73, 0x20, 0x07, 0x6d, 0x90, + 0x0e, 0x76, 0x40, 0x07, 0x7a, 0x60, 0x07, 0x74, 0xd0, 0x06, 0xe6, 0x10, + 0x07, 0x76, 0xa0, 0x07, 0x73, 0x20, 0x07, 0x6d, 0x60, 0x0e, 0x73, 0x20, + 0x07, 0x7a, 0x30, 0x07, 0x72, 0xd0, 0x06, 0xe6, 0x60, 0x07, 0x74, 0xa0, + 0x07, 0x76, 0x40, 0x07, 0x6d, 0xe0, 0x0e, 0x78, 0xa0, 0x07, 0x71, 0x60, + 0x07, 0x7a, 0x30, 0x07, 0x72, 0xa0, 0x07, 0x76, 0x40, 0x07, 0x43, 0x9e, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x86, + 0x3c, 0x06, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x0c, 0x79, 0x10, 0x20, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x18, 0xf2, 0x34, 0x40, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x30, 0xe4, 0x79, 0x80, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x60, 0xc8, 0x13, 0x01, 0x01, 0x10, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xc0, 0x90, 0x67, 0x02, 0x02, 0x20, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x21, 0x8f, 0x05, 0x04, 0xc0, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x43, 0x9e, 0x0c, 0x08, 0x80, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb2, 0x40, 0x00, 0x00, + 0x0f, 0x00, 0x00, 0x00, 0x32, 0x1e, 0x98, 0x14, 0x19, 0x11, 0x4c, 0x90, + 0x8c, 0x09, 0x26, 0x47, 0xc6, 0x04, 0x43, 0x22, 0x4a, 0x60, 0x04, 0xa0, + 0x20, 0x8a, 0xa1, 0x08, 0x4a, 0xa2, 0x0c, 0x0a, 0xa3, 0x3c, 0xa8, 0x28, + 0x89, 0x11, 0x80, 0x22, 0x28, 0x84, 0x02, 0xa1, 0x6f, 0x06, 0x80, 0xc4, + 0x19, 0x00, 0x22, 0x67, 0x00, 0xa8, 0x1c, 0x0b, 0x31, 0x88, 0x40, 0x20, + 0x10, 0x04, 0x01, 0x00, 0x79, 0x18, 0x00, 0x00, 0x5f, 0x00, 0x00, 0x00, + 0x1a, 0x03, 0x4c, 0x90, 0x46, 0x02, 0x13, 0xc4, 0x8e, 0x0c, 0x6f, 0xec, + 0xed, 0x4d, 0x0c, 0x24, 0xc6, 0x05, 0xc7, 0x45, 0xa6, 0x06, 0x46, 0xc6, + 0x05, 0x07, 0x04, 0x45, 0x8c, 0xe6, 0x26, 0x26, 0x06, 0x67, 0x26, 0xa7, + 0x2c, 0x65, 0x43, 0x10, 0x4c, 0x10, 0x88, 0x64, 0x82, 0x40, 0x28, 0x1b, + 0x84, 0x81, 0x98, 0x20, 0x10, 0xcb, 0x06, 0x61, 0x30, 0x28, 0xc0, 0xcd, + 0x4d, 0x10, 0x08, 0x66, 0xc3, 0x80, 0x24, 0xc4, 0x04, 0xe1, 0xbb, 0x08, + 0x4c, 0x10, 0x88, 0x66, 0x82, 0x40, 0x38, 0x1b, 0x04, 0xc2, 0xd9, 0x90, + 0x10, 0x0b, 0xd3, 0x10, 0x43, 0x43, 0x3c, 0x1b, 0x02, 0x68, 0x82, 0x20, + 0x06, 0xd8, 0x04, 0x81, 0x78, 0x26, 0x08, 0x04, 0xb4, 0x01, 0x21, 0x24, + 0x66, 0x22, 0x06, 0x0a, 0xd8, 0x10, 0x54, 0x13, 0x04, 0x32, 0xc8, 0x36, + 0x20, 0xc4, 0xc5, 0x34, 0xc4, 0x40, 0x00, 0x1b, 0x02, 0x6c, 0x03, 0x11, + 0x01, 0x56, 0x36, 0x41, 0x28, 0x03, 0x6d, 0x43, 0xb0, 0x4d, 0x10, 0x04, + 0x80, 0x44, 0x5b, 0x58, 0x9a, 0x1b, 0x11, 0xaa, 0x22, 0xac, 0xa1, 0xa7, + 0x27, 0x29, 0xa2, 0x09, 0x42, 0x21, 0x4d, 0x10, 0x8a, 0x69, 0x43, 0x40, + 0x4c, 0x10, 0x0a, 0x6a, 0x82, 0x50, 0x54, 0x13, 0x04, 0x22, 0xda, 0x20, + 0x4c, 0x65, 0xb0, 0x61, 0x21, 0x3e, 0x30, 0x08, 0x03, 0x31, 0x18, 0x83, + 0x81, 0x0c, 0x88, 0x30, 0x30, 0x83, 0x0d, 0xc1, 0xb0, 0x41, 0x98, 0xa6, + 0x0d, 0xcb, 0xf0, 0x81, 0x41, 0x18, 0xa0, 0xc1, 0x18, 0x0c, 0x63, 0x30, + 0x84, 0x41, 0x1a, 0x6c, 0x10, 0xce, 0x40, 0x0d, 0x98, 0x4c, 0x59, 0x7d, + 0x51, 0x85, 0xc9, 0x9d, 0x95, 0xd1, 0x4d, 0x10, 0x0a, 0x6b, 0xc3, 0x42, + 0xb0, 0x01, 0x18, 0xb4, 0x81, 0x18, 0x84, 0xc1, 0x40, 0x06, 0x44, 0x18, + 0x98, 0xc1, 0x86, 0xc0, 0x0d, 0x36, 0x0c, 0x6b, 0xf0, 0x06, 0xc0, 0x86, + 0xa2, 0xf3, 0xe0, 0x40, 0x03, 0xaa, 0xb0, 0xb1, 0xd9, 0xb5, 0xb9, 0xa4, + 0x91, 0x95, 0xb9, 0xd1, 0x4d, 0x09, 0x82, 0x2a, 0x64, 0x78, 0x2e, 0x76, + 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x53, 0x02, 0xa2, 0x09, 0x19, 0x9e, + 0x8b, 0x5d, 0x18, 0x9b, 0x5d, 0x99, 0xdc, 0x94, 0xc0, 0xa8, 0x43, 0x86, + 0xe7, 0x32, 0x87, 0x16, 0x46, 0x56, 0x26, 0xd7, 0xf4, 0x46, 0x56, 0xc6, + 0x36, 0x25, 0x48, 0xca, 0x90, 0xe1, 0xb9, 0xc8, 0x95, 0xcd, 0xbd, 0xd5, + 0xc9, 0x8d, 0x95, 0xcd, 0x4d, 0x09, 0xb2, 0x3a, 0x64, 0x78, 0x2e, 0x76, + 0x69, 0x65, 0x77, 0x49, 0x64, 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, 0x82, + 0xad, 0x0e, 0x19, 0x9e, 0x4b, 0x99, 0x1b, 0x9d, 0x5c, 0x1e, 0xd4, 0x5b, + 0x9a, 0x1b, 0xdd, 0xdc, 0x94, 0x00, 0x0e, 0x00, 0x79, 0x18, 0x00, 0x00, + 0x4c, 0x00, 0x00, 0x00, 0x33, 0x08, 0x80, 0x1c, 0xc4, 0xe1, 0x1c, 0x66, + 0x14, 0x01, 0x3d, 0x88, 0x43, 0x38, 0x84, 0xc3, 0x8c, 0x42, 0x80, 0x07, + 0x79, 0x78, 0x07, 0x73, 0x98, 0x71, 0x0c, 0xe6, 0x00, 0x0f, 0xed, 0x10, + 0x0e, 0xf4, 0x80, 0x0e, 0x33, 0x0c, 0x42, 0x1e, 0xc2, 0xc1, 0x1d, 0xce, + 0xa1, 0x1c, 0x66, 0x30, 0x05, 0x3d, 0x88, 0x43, 0x38, 0x84, 0x83, 0x1b, + 0xcc, 0x03, 0x3d, 0xc8, 0x43, 0x3d, 0x8c, 0x03, 0x3d, 0xcc, 0x78, 0x8c, + 0x74, 0x70, 0x07, 0x7b, 0x08, 0x07, 0x79, 0x48, 0x87, 0x70, 0x70, 0x07, + 0x7a, 0x70, 0x03, 0x76, 0x78, 0x87, 0x70, 0x20, 0x87, 0x19, 0xcc, 0x11, + 0x0e, 0xec, 0x90, 0x0e, 0xe1, 0x30, 0x0f, 0x6e, 0x30, 0x0f, 0xe3, 0xf0, + 0x0e, 0xf0, 0x50, 0x0e, 0x33, 0x10, 0xc4, 0x1d, 0xde, 0x21, 0x1c, 0xd8, + 0x21, 0x1d, 0xc2, 0x61, 0x1e, 0x66, 0x30, 0x89, 0x3b, 0xbc, 0x83, 0x3b, + 0xd0, 0x43, 0x39, 0xb4, 0x03, 0x3c, 0xbc, 0x83, 0x3c, 0x84, 0x03, 0x3b, + 0xcc, 0xf0, 0x14, 0x76, 0x60, 0x07, 0x7b, 0x68, 0x07, 0x37, 0x68, 0x87, + 0x72, 0x68, 0x07, 0x37, 0x80, 0x87, 0x70, 0x90, 0x87, 0x70, 0x60, 0x07, + 0x76, 0x28, 0x07, 0x76, 0xf8, 0x05, 0x76, 0x78, 0x87, 0x77, 0x80, 0x87, + 0x5f, 0x08, 0x87, 0x71, 0x18, 0x87, 0x72, 0x98, 0x87, 0x79, 0x98, 0x81, + 0x2c, 0xee, 0xf0, 0x0e, 0xee, 0xe0, 0x0e, 0xf5, 0xc0, 0x0e, 0xec, 0x30, + 0x03, 0x62, 0xc8, 0xa1, 0x1c, 0xe4, 0xa1, 0x1c, 0xcc, 0xa1, 0x1c, 0xe4, + 0xa1, 0x1c, 0xdc, 0x61, 0x1c, 0xca, 0x21, 0x1c, 0xc4, 0x81, 0x1d, 0xca, + 0x61, 0x06, 0xd6, 0x90, 0x43, 0x39, 0xc8, 0x43, 0x39, 0x98, 0x43, 0x39, + 0xc8, 0x43, 0x39, 0xb8, 0xc3, 0x38, 0x94, 0x43, 0x38, 0x88, 0x03, 0x3b, + 0x94, 0xc3, 0x2f, 0xbc, 0x83, 0x3c, 0xfc, 0x82, 0x3b, 0xd4, 0x03, 0x3b, + 0xb0, 0xc3, 0x8c, 0xc8, 0x21, 0x07, 0x7c, 0x70, 0x03, 0x72, 0x10, 0x87, + 0x73, 0x70, 0x03, 0x7b, 0x08, 0x07, 0x79, 0x60, 0x87, 0x70, 0xc8, 0x87, + 0x77, 0xa8, 0x07, 0x7a, 0x98, 0x81, 0x3c, 0xe4, 0x80, 0x0f, 0x6e, 0x40, + 0x0f, 0xe5, 0xd0, 0x0e, 0xf0, 0x00, 0x00, 0x00, 0x71, 0x20, 0x00, 0x00, + 0x22, 0x00, 0x00, 0x00, 0x46, 0x00, 0x0d, 0x97, 0xef, 0x3c, 0x7e, 0x80, + 0x34, 0x40, 0x84, 0xf9, 0xc5, 0x6d, 0xdb, 0xc1, 0x36, 0x5c, 0xbe, 0xf3, + 0xf8, 0x42, 0x40, 0x15, 0x05, 0x11, 0x95, 0x0e, 0x30, 0x94, 0x84, 0x01, + 0x08, 0x98, 0x5f, 0xdc, 0xb6, 0x21, 0x48, 0xc3, 0xe5, 0x3b, 0x8f, 0x2f, + 0x44, 0x04, 0x30, 0x11, 0x21, 0xd0, 0x0c, 0x0b, 0x61, 0x05, 0xce, 0x70, + 0xf9, 0xce, 0xe3, 0x0f, 0xce, 0x64, 0xfb, 0xc5, 0x6d, 0x5b, 0xc0, 0x34, + 0x5c, 0xbe, 0xf3, 0xf8, 0x8b, 0x03, 0x0c, 0x62, 0xf3, 0x50, 0x93, 0x5f, + 0xdc, 0xb6, 0x0d, 0x40, 0xc3, 0xe5, 0x3b, 0x8f, 0x2f, 0x01, 0xcc, 0xb3, + 0x10, 0x7e, 0x71, 0xdb, 0x26, 0x50, 0x0d, 0x97, 0xef, 0x3c, 0xbe, 0x34, + 0x39, 0x11, 0x81, 0x52, 0xd3, 0x43, 0x4d, 0x7e, 0x71, 0xdb, 0x66, 0xf0, + 0x0c, 0x97, 0xef, 0x3c, 0x3e, 0xd5, 0x00, 0x11, 0xe6, 0x17, 0xb7, 0x6d, + 0x00, 0x04, 0x03, 0x20, 0x0d, 0x00, 0x00, 0x00, 0x61, 0x20, 0x00, 0x00, + 0x86, 0x00, 0x00, 0x00, 0x13, 0x04, 0x41, 0x2c, 0x10, 0x00, 0x00, 0x00, + 0x0e, 0x00, 0x00, 0x00, 0x84, 0x8d, 0x00, 0x50, 0x51, 0x02, 0x65, 0x40, + 0x44, 0xb1, 0x95, 0xdd, 0x0c, 0x40, 0x21, 0x94, 0x51, 0x29, 0x94, 0x5c, + 0x21, 0x95, 0x69, 0x40, 0xa1, 0x06, 0x14, 0x1e, 0x0d, 0x23, 0x00, 0x63, + 0x04, 0x20, 0x08, 0x82, 0xf0, 0x2f, 0x8c, 0x11, 0x80, 0x20, 0x08, 0xc2, + 0xdf, 0x0c, 0xc0, 0x18, 0x01, 0x08, 0x82, 0x20, 0xfe, 0x01, 0x00, 0x00, + 0x23, 0x06, 0x09, 0x00, 0x82, 0x60, 0x90, 0x99, 0x81, 0xb2, 0x91, 0x01, + 0x19, 0x4c, 0x23, 0x06, 0x09, 0x00, 0x82, 0x60, 0x90, 0x9d, 0xc1, 0x22, + 0x95, 0x41, 0x19, 0x50, 0x23, 0x06, 0x09, 0x00, 0x82, 0x60, 0x90, 0xa1, + 0x01, 0xc3, 0x99, 0x81, 0x19, 0x54, 0x23, 0x06, 0x09, 0x00, 0x82, 0x60, + 0x60, 0xc0, 0x01, 0x84, 0x06, 0x67, 0xe0, 0x45, 0x23, 0x06, 0x09, 0x00, + 0x82, 0x60, 0x60, 0xc4, 0x41, 0x94, 0x06, 0x68, 0x60, 0x49, 0x23, 0x06, + 0x09, 0x00, 0x82, 0x60, 0x60, 0xc8, 0x81, 0x94, 0x06, 0x69, 0x00, 0x06, + 0xd3, 0x88, 0x41, 0x02, 0x80, 0x20, 0x18, 0x18, 0x73, 0x30, 0xa9, 0x81, + 0x1a, 0x60, 0xd4, 0x88, 0x41, 0x02, 0x80, 0x20, 0x18, 0x18, 0x74, 0x40, + 0xad, 0xc1, 0x1a, 0x84, 0x41, 0x35, 0x62, 0x90, 0x00, 0x20, 0x08, 0x06, + 0x46, 0x1d, 0x54, 0x6c, 0xc0, 0x06, 0x99, 0x35, 0x62, 0xf0, 0x00, 0x20, + 0x08, 0x06, 0x0d, 0x1d, 0x3c, 0x09, 0x62, 0x14, 0xcb, 0xd2, 0x06, 0x6d, + 0x70, 0x2d, 0xa3, 0x09, 0x01, 0x30, 0x9a, 0x20, 0x04, 0xa3, 0x09, 0x83, + 0x30, 0x62, 0x60, 0x00, 0x20, 0x08, 0x06, 0x53, 0x1d, 0x54, 0xca, 0x88, + 0x81, 0x01, 0x80, 0x20, 0x18, 0x4c, 0x76, 0x60, 0x29, 0x23, 0x06, 0x06, + 0x00, 0x82, 0x60, 0x30, 0xdd, 0x41, 0x1c, 0x08, 0x23, 0x06, 0x06, 0x00, + 0x82, 0x60, 0x30, 0xe1, 0x81, 0x1c, 0x08, 0x23, 0x06, 0x06, 0x00, 0x82, + 0x60, 0x30, 0xe5, 0x01, 0xe6, 0x8c, 0x18, 0x18, 0x00, 0x08, 0x82, 0xc1, + 0xa4, 0x07, 0x99, 0x33, 0x62, 0x60, 0x00, 0x20, 0x08, 0x06, 0xd3, 0x1e, + 0xd4, 0x81, 0x30, 0x62, 0x60, 0x00, 0x20, 0x08, 0x06, 0x13, 0x1f, 0xd8, + 0x81, 0x60, 0x82, 0x01, 0x1f, 0x13, 0x0c, 0xf8, 0x18, 0x26, 0xd0, 0xc7, + 0x32, 0x81, 0x3e, 0x23, 0x06, 0x07, 0x00, 0x82, 0x60, 0x60, 0x81, 0x02, + 0x1a, 0x5c, 0x7d, 0x30, 0x9a, 0x10, 0x00, 0xa3, 0x09, 0x42, 0x30, 0x9a, + 0x30, 0x08, 0x36, 0x08, 0xf4, 0x31, 0x42, 0xa0, 0xcf, 0x88, 0x41, 0x02, + 0x80, 0x20, 0x18, 0x44, 0xa8, 0xf0, 0x06, 0x42, 0x80, 0x1c, 0x16, 0x8c, + 0x81, 0x7c, 0x46, 0x0c, 0x0e, 0x00, 0x04, 0xc1, 0xe0, 0x59, 0x85, 0x36, + 0x08, 0xc4, 0x60, 0xc4, 0xe0, 0x00, 0x40, 0x10, 0x0c, 0x1e, 0x56, 0x70, + 0x03, 0xec, 0x1a, 0x31, 0x38, 0x00, 0x10, 0x04, 0x83, 0xa7, 0x15, 0xd8, + 0x20, 0xb8, 0x46, 0x0c, 0x0e, 0x00, 0x04, 0xc1, 0xe0, 0x71, 0x85, 0x36, + 0xd0, 0xb2, 0x11, 0x83, 0x03, 0x00, 0x41, 0x30, 0x78, 0x5e, 0x21, 0x0e, + 0x02, 0xc1, 0x02, 0x35, 0x80, 0x8f, 0x05, 0x86, 0x7c, 0x2c, 0x58, 0x03, + 0xf8, 0x8c, 0x18, 0x1c, 0x00, 0x08, 0x82, 0xc1, 0x33, 0x0b, 0x75, 0x10, + 0xb8, 0xc1, 0x88, 0xc1, 0x01, 0x80, 0x20, 0x18, 0x3c, 0xb4, 0x30, 0x07, + 0xc1, 0x1a, 0x58, 0x30, 0x06, 0xf2, 0x19, 0x31, 0x48, 0x00, 0x10, 0x04, + 0x03, 0x04, 0x17, 0xee, 0x00, 0x16, 0x60, 0xe1, 0x14, 0xce, 0x60, 0xc4, + 0x20, 0x01, 0x40, 0x10, 0x0c, 0x10, 0x5c, 0xb8, 0x03, 0x58, 0x80, 0x05, + 0x3f, 0x30, 0x83, 0x11, 0x83, 0x04, 0x00, 0x41, 0x30, 0x40, 0x70, 0xe1, + 0x0e, 0x60, 0x01, 0x16, 0x4c, 0xa1, 0x0c, 0x46, 0x0c, 0x12, 0x00, 0x04, + 0xc1, 0x00, 0xc1, 0x85, 0x3b, 0x80, 0x05, 0x58, 0xe8, 0x83, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00 +}; +static const unsigned int testgpurender_msdf_frag_dxil_len = 4744; diff --git a/libs/SDL3/test/testgpurender_msdf.frag.hlsl b/libs/SDL3/test/testgpurender_msdf.frag.hlsl new file mode 100644 index 00000000..2d852fbf --- /dev/null +++ b/libs/SDL3/test/testgpurender_msdf.frag.hlsl @@ -0,0 +1,37 @@ +cbuffer Context : register(b0, space3) { + float distance_field_range; + float2 texture_size; +}; + +Texture2D u_texture : register(t0, space2); +SamplerState u_sampler : register(s0, space2); + +struct PSInput { + float4 v_color : COLOR0; + float2 v_uv : TEXCOORD0; +}; + +struct PSOutput { + float4 o_color : SV_Target; +}; + +float median(float r, float g, float b) { + return max(min(r, g), min(max(r, g), b)); +} + +float screenPxRange(float2 texCoord) { + float2 unitRange = float2(distance_field_range, distance_field_range)/texture_size; + float2 screenTexSize = float2(1.0, 1.0)/fwidth(texCoord); + return max(0.5*dot(unitRange, screenTexSize), 1.0); +} + +PSOutput main(PSInput input) { + PSOutput output; + float3 msd = u_texture.Sample(u_sampler, input.v_uv).rgb; + float sd = median(msd.r, msd.g, msd.b); + float screenPxDistance = screenPxRange(input.v_uv)*(sd - 0.5); + float opacity = clamp(screenPxDistance + 0.5, 0.0, 1.0); + output.o_color.rgb = input.v_color.rgb; + output.o_color.a = (input.v_color.a * opacity); + return output; +} diff --git a/libs/SDL3/test/testgpurender_msdf.frag.msl.h b/libs/SDL3/test/testgpurender_msdf.frag.msl.h new file mode 100644 index 00000000..6fc854f6 --- /dev/null +++ b/libs/SDL3/test/testgpurender_msdf.frag.msl.h @@ -0,0 +1,97 @@ +static const unsigned char testgpurender_msdf_frag_msl[] = { + 0x23, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x20, 0x3c, 0x6d, 0x65, + 0x74, 0x61, 0x6c, 0x5f, 0x73, 0x74, 0x64, 0x6c, 0x69, 0x62, 0x3e, 0x0a, + 0x23, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x20, 0x3c, 0x73, 0x69, + 0x6d, 0x64, 0x2f, 0x73, 0x69, 0x6d, 0x64, 0x2e, 0x68, 0x3e, 0x0a, 0x0a, + 0x75, 0x73, 0x69, 0x6e, 0x67, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, + 0x61, 0x63, 0x65, 0x20, 0x6d, 0x65, 0x74, 0x61, 0x6c, 0x3b, 0x0a, 0x0a, + 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x20, 0x74, 0x79, 0x70, 0x65, 0x5f, + 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x0a, 0x7b, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x64, 0x69, 0x73, 0x74, + 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x72, + 0x61, 0x6e, 0x67, 0x65, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x70, 0x61, + 0x63, 0x6b, 0x65, 0x64, 0x5f, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x32, 0x20, + 0x74, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, + 0x3b, 0x0a, 0x7d, 0x3b, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x73, 0x74, 0x61, + 0x6e, 0x74, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x34, 0x20, 0x5f, 0x33, + 0x35, 0x20, 0x3d, 0x20, 0x7b, 0x7d, 0x3b, 0x0a, 0x0a, 0x73, 0x74, 0x72, + 0x75, 0x63, 0x74, 0x20, 0x6d, 0x61, 0x69, 0x6e, 0x30, 0x5f, 0x6f, 0x75, + 0x74, 0x0a, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, + 0x74, 0x34, 0x20, 0x6f, 0x75, 0x74, 0x5f, 0x76, 0x61, 0x72, 0x5f, 0x53, + 0x56, 0x5f, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x20, 0x5b, 0x5b, 0x63, + 0x6f, 0x6c, 0x6f, 0x72, 0x28, 0x30, 0x29, 0x5d, 0x5d, 0x3b, 0x0a, 0x7d, + 0x3b, 0x0a, 0x0a, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x20, 0x6d, 0x61, + 0x69, 0x6e, 0x30, 0x5f, 0x69, 0x6e, 0x0a, 0x7b, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x34, 0x20, 0x69, 0x6e, 0x5f, 0x76, + 0x61, 0x72, 0x5f, 0x43, 0x4f, 0x4c, 0x4f, 0x52, 0x30, 0x20, 0x5b, 0x5b, + 0x75, 0x73, 0x65, 0x72, 0x28, 0x6c, 0x6f, 0x63, 0x6e, 0x30, 0x29, 0x5d, + 0x5d, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, + 0x32, 0x20, 0x69, 0x6e, 0x5f, 0x76, 0x61, 0x72, 0x5f, 0x54, 0x45, 0x58, + 0x43, 0x4f, 0x4f, 0x52, 0x44, 0x30, 0x20, 0x5b, 0x5b, 0x75, 0x73, 0x65, + 0x72, 0x28, 0x6c, 0x6f, 0x63, 0x6e, 0x31, 0x29, 0x5d, 0x5d, 0x3b, 0x0a, + 0x7d, 0x3b, 0x0a, 0x0a, 0x66, 0x72, 0x61, 0x67, 0x6d, 0x65, 0x6e, 0x74, + 0x20, 0x6d, 0x61, 0x69, 0x6e, 0x30, 0x5f, 0x6f, 0x75, 0x74, 0x20, 0x6d, + 0x61, 0x69, 0x6e, 0x30, 0x28, 0x6d, 0x61, 0x69, 0x6e, 0x30, 0x5f, 0x69, + 0x6e, 0x20, 0x69, 0x6e, 0x20, 0x5b, 0x5b, 0x73, 0x74, 0x61, 0x67, 0x65, + 0x5f, 0x69, 0x6e, 0x5d, 0x5d, 0x2c, 0x20, 0x63, 0x6f, 0x6e, 0x73, 0x74, + 0x61, 0x6e, 0x74, 0x20, 0x74, 0x79, 0x70, 0x65, 0x5f, 0x43, 0x6f, 0x6e, + 0x74, 0x65, 0x78, 0x74, 0x26, 0x20, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, + 0x74, 0x20, 0x5b, 0x5b, 0x62, 0x75, 0x66, 0x66, 0x65, 0x72, 0x28, 0x30, + 0x29, 0x5d, 0x5d, 0x2c, 0x20, 0x74, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, + 0x32, 0x64, 0x3c, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x3e, 0x20, 0x75, 0x5f, + 0x74, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x20, 0x5b, 0x5b, 0x74, 0x65, + 0x78, 0x74, 0x75, 0x72, 0x65, 0x28, 0x30, 0x29, 0x5d, 0x5d, 0x2c, 0x20, + 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x20, 0x75, 0x5f, 0x73, 0x61, + 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x20, 0x5b, 0x5b, 0x73, 0x61, 0x6d, 0x70, + 0x6c, 0x65, 0x72, 0x28, 0x30, 0x29, 0x5d, 0x5d, 0x29, 0x0a, 0x7b, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, 0x69, 0x6e, 0x30, 0x5f, 0x6f, 0x75, + 0x74, 0x20, 0x6f, 0x75, 0x74, 0x20, 0x3d, 0x20, 0x7b, 0x7d, 0x3b, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x34, 0x20, 0x5f, + 0x34, 0x32, 0x20, 0x3d, 0x20, 0x75, 0x5f, 0x74, 0x65, 0x78, 0x74, 0x75, + 0x72, 0x65, 0x2e, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x28, 0x75, 0x5f, + 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x2c, 0x20, 0x69, 0x6e, 0x2e, + 0x69, 0x6e, 0x5f, 0x76, 0x61, 0x72, 0x5f, 0x54, 0x45, 0x58, 0x43, 0x4f, + 0x4f, 0x52, 0x44, 0x30, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, + 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x5f, 0x34, 0x33, 0x20, 0x3d, 0x20, 0x5f, + 0x34, 0x32, 0x2e, 0x78, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, + 0x6f, 0x61, 0x74, 0x20, 0x5f, 0x34, 0x34, 0x20, 0x3d, 0x20, 0x5f, 0x34, + 0x32, 0x2e, 0x79, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, + 0x61, 0x74, 0x34, 0x20, 0x5f, 0x36, 0x35, 0x20, 0x3d, 0x20, 0x66, 0x6c, + 0x6f, 0x61, 0x74, 0x34, 0x28, 0x69, 0x6e, 0x2e, 0x69, 0x6e, 0x5f, 0x76, + 0x61, 0x72, 0x5f, 0x43, 0x4f, 0x4c, 0x4f, 0x52, 0x30, 0x2e, 0x78, 0x2c, + 0x20, 0x69, 0x6e, 0x2e, 0x69, 0x6e, 0x5f, 0x76, 0x61, 0x72, 0x5f, 0x43, + 0x4f, 0x4c, 0x4f, 0x52, 0x30, 0x2e, 0x79, 0x2c, 0x20, 0x69, 0x6e, 0x2e, + 0x69, 0x6e, 0x5f, 0x76, 0x61, 0x72, 0x5f, 0x43, 0x4f, 0x4c, 0x4f, 0x52, + 0x30, 0x2e, 0x7a, 0x2c, 0x20, 0x5f, 0x33, 0x35, 0x2e, 0x77, 0x29, 0x3b, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x5f, 0x36, 0x35, 0x2e, 0x77, 0x20, 0x3d, + 0x20, 0x69, 0x6e, 0x2e, 0x69, 0x6e, 0x5f, 0x76, 0x61, 0x72, 0x5f, 0x43, + 0x4f, 0x4c, 0x4f, 0x52, 0x30, 0x2e, 0x77, 0x20, 0x2a, 0x20, 0x66, 0x61, + 0x73, 0x74, 0x3a, 0x3a, 0x63, 0x6c, 0x61, 0x6d, 0x70, 0x28, 0x28, 0x70, + 0x72, 0x65, 0x63, 0x69, 0x73, 0x65, 0x3a, 0x3a, 0x6d, 0x61, 0x78, 0x28, + 0x30, 0x2e, 0x35, 0x20, 0x2a, 0x20, 0x64, 0x6f, 0x74, 0x28, 0x66, 0x6c, + 0x6f, 0x61, 0x74, 0x32, 0x28, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, + 0x2e, 0x64, 0x69, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x66, 0x69, + 0x65, 0x6c, 0x64, 0x5f, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x29, 0x20, 0x2f, + 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x32, 0x28, 0x43, 0x6f, 0x6e, 0x74, + 0x65, 0x78, 0x74, 0x2e, 0x74, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x5f, + 0x73, 0x69, 0x7a, 0x65, 0x29, 0x2c, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, + 0x32, 0x28, 0x31, 0x2e, 0x30, 0x29, 0x20, 0x2f, 0x20, 0x66, 0x77, 0x69, + 0x64, 0x74, 0x68, 0x28, 0x69, 0x6e, 0x2e, 0x69, 0x6e, 0x5f, 0x76, 0x61, + 0x72, 0x5f, 0x54, 0x45, 0x58, 0x43, 0x4f, 0x4f, 0x52, 0x44, 0x30, 0x29, + 0x29, 0x2c, 0x20, 0x31, 0x2e, 0x30, 0x29, 0x20, 0x2a, 0x20, 0x28, 0x70, + 0x72, 0x65, 0x63, 0x69, 0x73, 0x65, 0x3a, 0x3a, 0x6d, 0x61, 0x78, 0x28, + 0x70, 0x72, 0x65, 0x63, 0x69, 0x73, 0x65, 0x3a, 0x3a, 0x6d, 0x69, 0x6e, + 0x28, 0x5f, 0x34, 0x33, 0x2c, 0x20, 0x5f, 0x34, 0x34, 0x29, 0x2c, 0x20, + 0x70, 0x72, 0x65, 0x63, 0x69, 0x73, 0x65, 0x3a, 0x3a, 0x6d, 0x69, 0x6e, + 0x28, 0x70, 0x72, 0x65, 0x63, 0x69, 0x73, 0x65, 0x3a, 0x3a, 0x6d, 0x61, + 0x78, 0x28, 0x5f, 0x34, 0x33, 0x2c, 0x20, 0x5f, 0x34, 0x34, 0x29, 0x2c, + 0x20, 0x5f, 0x34, 0x32, 0x2e, 0x7a, 0x29, 0x29, 0x20, 0x2d, 0x20, 0x30, + 0x2e, 0x35, 0x29, 0x29, 0x20, 0x2b, 0x20, 0x30, 0x2e, 0x35, 0x2c, 0x20, + 0x30, 0x2e, 0x30, 0x2c, 0x20, 0x31, 0x2e, 0x30, 0x29, 0x3b, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x6f, 0x75, 0x74, 0x2e, 0x6f, 0x75, 0x74, 0x5f, 0x76, + 0x61, 0x72, 0x5f, 0x53, 0x56, 0x5f, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, + 0x20, 0x3d, 0x20, 0x5f, 0x36, 0x35, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x6f, 0x75, 0x74, 0x3b, 0x0a, + 0x7d, 0x0a, 0x0a +}; +static const unsigned int testgpurender_msdf_frag_msl_len = 1119; diff --git a/libs/SDL3/test/testgpurender_msdf.frag.spv.h b/libs/SDL3/test/testgpurender_msdf.frag.spv.h new file mode 100644 index 00000000..1df67336 --- /dev/null +++ b/libs/SDL3/test/testgpurender_msdf.frag.spv.h @@ -0,0 +1,162 @@ +static const unsigned char testgpurender_msdf_frag_spv[] = { + 0x03, 0x02, 0x23, 0x07, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0e, 0x00, + 0x46, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x02, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x06, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x47, 0x4c, 0x53, 0x4c, 0x2e, 0x73, 0x74, 0x64, 0x2e, 0x34, 0x35, 0x30, + 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x08, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x6d, 0x61, 0x69, 0x6e, 0x00, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, + 0x10, 0x00, 0x03, 0x00, 0x02, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x03, 0x00, 0x05, 0x00, 0x00, 0x00, 0x58, 0x02, 0x00, 0x00, + 0x05, 0x00, 0x06, 0x00, 0x06, 0x00, 0x00, 0x00, 0x74, 0x79, 0x70, 0x65, + 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x00, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x09, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x64, 0x69, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x66, 0x69, 0x65, + 0x6c, 0x64, 0x5f, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x00, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x07, 0x00, 0x06, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x74, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, + 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00, + 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x00, 0x05, 0x00, 0x06, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x74, 0x79, 0x70, 0x65, 0x2e, 0x32, 0x64, 0x2e, + 0x69, 0x6d, 0x61, 0x67, 0x65, 0x00, 0x00, 0x00, 0x05, 0x00, 0x05, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x75, 0x5f, 0x74, 0x65, 0x78, 0x74, 0x75, 0x72, + 0x65, 0x00, 0x00, 0x00, 0x05, 0x00, 0x06, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x74, 0x79, 0x70, 0x65, 0x2e, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, + 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x05, 0x00, 0x0b, 0x00, 0x00, 0x00, + 0x75, 0x5f, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x06, 0x00, 0x03, 0x00, 0x00, 0x00, 0x69, 0x6e, 0x2e, 0x76, + 0x61, 0x72, 0x2e, 0x43, 0x4f, 0x4c, 0x4f, 0x52, 0x30, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x07, 0x00, 0x04, 0x00, 0x00, 0x00, 0x69, 0x6e, 0x2e, 0x76, + 0x61, 0x72, 0x2e, 0x54, 0x45, 0x58, 0x43, 0x4f, 0x4f, 0x52, 0x44, 0x30, + 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x07, 0x00, 0x05, 0x00, 0x00, 0x00, + 0x6f, 0x75, 0x74, 0x2e, 0x76, 0x61, 0x72, 0x2e, 0x53, 0x56, 0x5f, 0x54, + 0x61, 0x72, 0x67, 0x65, 0x74, 0x00, 0x00, 0x00, 0x05, 0x00, 0x04, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x6d, 0x61, 0x69, 0x6e, 0x00, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x07, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x74, 0x79, 0x70, 0x65, + 0x2e, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x64, 0x2e, 0x69, 0x6d, 0x61, + 0x67, 0x65, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x47, 0x00, 0x04, 0x00, 0x05, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00, + 0x22, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x47, 0x00, 0x04, 0x00, 0x09, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, + 0x0b, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x47, 0x00, 0x04, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x48, 0x00, 0x05, 0x00, 0x06, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x48, 0x00, 0x05, 0x00, 0x06, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x23, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x47, 0x00, 0x03, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x15, 0x00, 0x04, 0x00, + 0x0d, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x2b, 0x00, 0x04, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, 0x00, 0x0d, 0x00, 0x00, 0x00, + 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, 0x00, 0x03, 0x00, + 0x10, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, 0x00, + 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, + 0x2b, 0x00, 0x04, 0x00, 0x10, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, 0x00, 0x10, 0x00, 0x00, 0x00, + 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3f, 0x2b, 0x00, 0x04, 0x00, + 0x0d, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x17, 0x00, 0x04, 0x00, 0x15, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x05, 0x00, 0x15, 0x00, 0x00, 0x00, + 0x16, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, + 0x1e, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, + 0x15, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x17, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x19, 0x00, 0x09, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, + 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x1a, 0x00, 0x02, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, + 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x17, 0x00, 0x04, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x1b, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, + 0x1c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, + 0x20, 0x00, 0x04, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x1a, 0x00, 0x00, 0x00, 0x13, 0x00, 0x02, 0x00, 0x1e, 0x00, 0x00, 0x00, + 0x21, 0x00, 0x03, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, + 0x1b, 0x00, 0x03, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x20, 0x00, 0x04, 0x00, 0x20, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x10, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x21, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00, + 0x17, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x3b, 0x00, 0x04, 0x00, 0x18, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00, 0x19, 0x00, 0x00, 0x00, + 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00, + 0x1b, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x3b, 0x00, 0x04, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00, 0x1d, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, + 0x22, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x03, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, + 0x36, 0x00, 0x05, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00, + 0x24, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, 0x1a, 0x00, 0x00, 0x00, + 0x25, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, + 0x15, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x3d, 0x00, 0x04, 0x00, 0x08, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x28, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x56, 0x00, 0x05, 0x00, + 0x0c, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, + 0x28, 0x00, 0x00, 0x00, 0x57, 0x00, 0x06, 0x00, 0x1a, 0x00, 0x00, 0x00, + 0x2a, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x51, 0x00, 0x05, 0x00, 0x10, 0x00, 0x00, 0x00, + 0x2b, 0x00, 0x00, 0x00, 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x51, 0x00, 0x05, 0x00, 0x10, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, + 0x2a, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x51, 0x00, 0x05, 0x00, + 0x10, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x00, 0x00, 0x2a, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x07, 0x00, 0x10, 0x00, 0x00, 0x00, + 0x2e, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x4f, 0x00, 0x00, 0x00, + 0x2b, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x07, 0x00, + 0x10, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x50, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, + 0x0c, 0x00, 0x07, 0x00, 0x10, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x4f, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00, + 0x2d, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x07, 0x00, 0x10, 0x00, 0x00, 0x00, + 0x31, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, + 0x2e, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00, + 0x20, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, + 0x0f, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, 0x10, 0x00, 0x00, 0x00, + 0x33, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x50, 0x00, 0x05, 0x00, + 0x15, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, + 0x33, 0x00, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00, 0x21, 0x00, 0x00, 0x00, + 0x35, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, + 0x3d, 0x00, 0x04, 0x00, 0x15, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, + 0x35, 0x00, 0x00, 0x00, 0x88, 0x00, 0x05, 0x00, 0x15, 0x00, 0x00, 0x00, + 0x37, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, + 0xd1, 0x00, 0x04, 0x00, 0x15, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, + 0x26, 0x00, 0x00, 0x00, 0x88, 0x00, 0x05, 0x00, 0x15, 0x00, 0x00, 0x00, + 0x39, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, + 0x94, 0x00, 0x05, 0x00, 0x10, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x00, 0x00, + 0x37, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, 0x85, 0x00, 0x05, 0x00, + 0x10, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, + 0x3a, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x07, 0x00, 0x10, 0x00, 0x00, 0x00, + 0x3c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, + 0x3b, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x83, 0x00, 0x05, 0x00, + 0x10, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, + 0x11, 0x00, 0x00, 0x00, 0x85, 0x00, 0x05, 0x00, 0x10, 0x00, 0x00, 0x00, + 0x3e, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x00, 0x00, + 0x81, 0x00, 0x05, 0x00, 0x10, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00, + 0x3e, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x08, 0x00, + 0x10, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x2b, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, + 0x13, 0x00, 0x00, 0x00, 0x4f, 0x00, 0x09, 0x00, 0x1a, 0x00, 0x00, 0x00, + 0x41, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00, 0x22, 0x00, 0x00, 0x00, + 0x42, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, + 0x3d, 0x00, 0x04, 0x00, 0x10, 0x00, 0x00, 0x00, 0x43, 0x00, 0x00, 0x00, + 0x42, 0x00, 0x00, 0x00, 0x85, 0x00, 0x05, 0x00, 0x10, 0x00, 0x00, 0x00, + 0x44, 0x00, 0x00, 0x00, 0x43, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, + 0x52, 0x00, 0x06, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x45, 0x00, 0x00, 0x00, + 0x44, 0x00, 0x00, 0x00, 0x41, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x3e, 0x00, 0x03, 0x00, 0x05, 0x00, 0x00, 0x00, 0x45, 0x00, 0x00, 0x00, + 0xfd, 0x00, 0x01, 0x00, 0x38, 0x00, 0x01, 0x00 +}; +static const unsigned int testgpurender_msdf_frag_spv_len = 1904; diff --git a/libs/SDL3/test/testhaptic.c b/libs/SDL3/test/testhaptic.c index a31d2786..d617db59 100644 --- a/libs/SDL3/test/testhaptic.c +++ b/libs/SDL3/test/testhaptic.c @@ -37,7 +37,7 @@ int main(int argc, char **argv) char *name = NULL; int index = -1; SDL_HapticEffect efx[9]; - int id[9]; + SDL_HapticEffectID id[9]; int nefx; unsigned int supported; SDL_HapticID *haptics; diff --git a/libs/SDL3/test/testime.c b/libs/SDL3/test/testime.c index ea56d3c8..a1a7e442 100644 --- a/libs/SDL3/test/testime.c +++ b/libs/SDL3/test/testime.c @@ -987,18 +987,19 @@ int main(int argc, char *argv[]) WindowState *ctx = &windowstate[i]; SDL_Window *window = state->windows[i]; SDL_Renderer *renderer = state->renderers[i]; - int icon_w = 0, icon_h = 0; SDL_SetRenderLogicalPresentation(renderer, WINDOW_WIDTH, WINDOW_HEIGHT, SDL_LOGICAL_PRESENTATION_LETTERBOX); ctx->window = window; ctx->renderer = renderer; ctx->rendererID = i; - ctx->settings_icon = LoadTexture(renderer, "icon.bmp", true, &icon_w, &icon_h); - ctx->settings_rect.x = (float)WINDOW_WIDTH - icon_w - MARGIN; - ctx->settings_rect.y = MARGIN; - ctx->settings_rect.w = (float)icon_w; - ctx->settings_rect.h = (float)icon_h; + ctx->settings_icon = LoadTexture(renderer, "icon.bmp", true); + if (ctx->settings_icon) { + ctx->settings_rect.w = (float)ctx->settings_icon->w; + ctx->settings_rect.h = (float)ctx->settings_icon->h; + ctx->settings_rect.x = (float)WINDOW_WIDTH - ctx->settings_rect.w - MARGIN; + ctx->settings_rect.y = MARGIN; + } InitInput(ctx); diff --git a/libs/SDL3/test/testmessage.c b/libs/SDL3/test/testmessage.c index 1e5dbc9a..f4e602ac 100644 --- a/libs/SDL3/test/testmessage.c +++ b/libs/SDL3/test/testmessage.c @@ -23,8 +23,7 @@ #endif /* Call this instead of exit(), so we can clean up SDL: atexit() is evil. */ -static void -quit(int rc) +static void quit(int rc) { SDL_Quit(); /* Let 'main()' return normally */ @@ -33,9 +32,9 @@ quit(int rc) } } -static int SDLCALL -button_messagebox(void *eventNumber) +static int SDLCALL button_messagebox(void *eventNumber) { + int i; const SDL_MessageBoxButtonData buttons[] = { { SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, 0, @@ -43,52 +42,79 @@ button_messagebox(void *eventNumber) { SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT, 1, "Cancel" }, + { 0, + 2, + "Retry" } }; - SDL_MessageBoxData data = { SDL_MESSAGEBOX_INFORMATION, NULL, /* no parent window */ "Custom MessageBox", "This is a custom messagebox", - 2, + SDL_arraysize(buttons), NULL, /* buttons */ NULL /* Default color scheme */ }; - int button = -1; - int success = 0; - data.buttons = buttons; - if (eventNumber) { - data.message = "This is a custom messagebox from a background thread."; - } + for (i = 0; ; ++i) { + SDL_MessageBoxColorScheme colorScheme; + if (i != 0) { + int j; + for (j = 0; j < SDL_MESSAGEBOX_COLOR_COUNT; ++j) { + colorScheme.colors[j].r = SDL_rand(256); + colorScheme.colors[j].g = SDL_rand(256); + colorScheme.colors[j].b = SDL_rand(256); + } + data.colorScheme = &colorScheme; + } else { + data.colorScheme = NULL; + } + + int button = -1; + data.buttons = buttons; + if (eventNumber) { + data.message = "This is a custom messagebox from a background thread."; + } + + if (!SDL_ShowMessageBox(&data, &button)) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error Presenting MessageBox: %s", SDL_GetError()); + if (eventNumber) { + SDL_Event event; + event.type = (Uint32)(intptr_t)eventNumber; + SDL_PushEvent(&event); + return 1; + } else { + quit(2); + } + } + + const char* text; + if (button == 1) { + text = "Cancel"; + } else if (button == 2) { + text = "Retry"; + } else { + text = "OK"; + } + SDL_Log("Pressed button: %d, %s", button, button == -1 ? "[closed]" : text); + + if (button == 2) { + continue; + } - success = SDL_ShowMessageBox(&data, &button); - if (success == -1) { - SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error Presenting MessageBox: %s", SDL_GetError()); if (eventNumber) { SDL_Event event; event.type = (Uint32)(intptr_t)eventNumber; SDL_PushEvent(&event); - return 1; - } else { - quit(2); } - } - SDL_Log("Pressed button: %d, %s", button, button == -1 ? "[closed]" : button == 1 ? "Cancel" - : "OK"); - if (eventNumber) { - SDL_Event event; - event.type = (Uint32)(intptr_t)eventNumber; - SDL_PushEvent(&event); + return 0; } - - return 0; } int main(int argc, char *argv[]) { - int success; + bool success; SDLTest_CommonState *state; /* Initialize test framework */ diff --git a/libs/SDL3/test/testmodal.c b/libs/SDL3/test/testmodal.c index 9e5ea63e..d898b896 100644 --- a/libs/SDL3/test/testmodal.c +++ b/libs/SDL3/test/testmodal.c @@ -97,7 +97,7 @@ int main(int argc, char *argv[]) } if (e.key.key == SDLK_M) { - if (SDL_SetWindowParent(w2, w2)) { + if (SDL_SetWindowParent(w2, w1)) { if (SDL_SetWindowModal(w2, true)) { SDL_SetWindowTitle(w2, "Modal Window"); } diff --git a/libs/SDL3/test/testmouse.c b/libs/SDL3/test/testmouse.c index bc839c09..05e15eac 100644 --- a/libs/SDL3/test/testmouse.c +++ b/libs/SDL3/test/testmouse.c @@ -117,6 +117,8 @@ static void loop(void *arg) struct mouse_loop_data *loop_data = (struct mouse_loop_data *)arg; SDL_Event event; SDL_Renderer *renderer = loop_data->renderer; + float fx, fy; + SDL_MouseButtonFlags flags; /* Check for events */ while (SDL_PollEvent(&event)) { @@ -265,6 +267,10 @@ static void loop(void *arg) DrawObject(renderer, active); } + flags = SDL_GetGlobalMouseState(&fx, &fy); + SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255); + SDL_RenderDebugTextFormat(renderer, 0, 0, "Global Mouse State: x=%f y=%f flags=%" SDL_PRIu32, fx, fy, flags); + SDL_RenderPresent(renderer); #ifdef SDL_PLATFORM_EMSCRIPTEN diff --git a/libs/SDL3/test/testnative.c b/libs/SDL3/test/testnative.c index a4b58fa4..5ee3bff7 100644 --- a/libs/SDL3/test/testnative.c +++ b/libs/SDL3/test/testnative.c @@ -168,7 +168,7 @@ int main(int argc, char *argv[]) SDL_SetRenderDrawColor(renderer, 0xA0, 0xA0, 0xA0, 0xFF); SDL_RenderClear(renderer); - sprite = LoadTexture(renderer, "icon.bmp", true, NULL, NULL); + sprite = LoadTexture(renderer, "icon.bmp", true); if (!sprite) { quit(6); } diff --git a/libs/SDL3/test/testpopup.c b/libs/SDL3/test/testpopup.c index 786f03c2..987eab8d 100644 --- a/libs/SDL3/test/testpopup.c +++ b/libs/SDL3/test/testpopup.c @@ -49,6 +49,9 @@ struct PopupWindow static struct PopupWindow *menus; static struct PopupWindow tooltip; +static bool no_constraints; +static bool no_grab; + /* Call this instead of exit(), so we can clean up SDL: atexit() is evil. */ static void quit(int rc) { @@ -95,14 +98,27 @@ static bool create_popup(struct PopupWindow *new_popup, bool is_menu) const int w = is_menu ? MENU_WIDTH : TOOLTIP_WIDTH; const int h = is_menu ? MENU_HEIGHT : TOOLTIP_HEIGHT; const int v_off = is_menu ? 0 : 32; - const SDL_WindowFlags flags = is_menu ? SDL_WINDOW_POPUP_MENU : SDL_WINDOW_TOOLTIP; float x, y; focus = SDL_GetMouseFocus(); SDL_GetMouseState(&x, &y); - new_win = SDL_CreatePopupWindow(focus, - (int)x, (int)y + v_off, w, h, flags); + + SDL_PropertiesID props = SDL_CreateProperties(); + SDL_SetPointerProperty(props, SDL_PROP_WINDOW_CREATE_PARENT_POINTER, focus); + SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_CONSTRAIN_POPUP_BOOLEAN, !no_constraints); + SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_FOCUSABLE_BOOLEAN, !no_grab); + if (is_menu) { + SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_MENU_BOOLEAN, true); + } else { + SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_TOOLTIP_BOOLEAN, true); + } + SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_WIDTH_NUMBER, w); + SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_HEIGHT_NUMBER, h); + SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_X_NUMBER, (int)x); + SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_Y_NUMBER, (int)y + v_off); + new_win = SDL_CreateWindowWithProperties(props); + SDL_DestroyProperties(props); if (new_win) { new_renderer = SDL_CreateRenderer(new_win, state->renderdriver); @@ -249,8 +265,30 @@ int main(int argc, char *argv[]) } /* Parse commandline */ - if (!SDLTest_CommonDefaultArgs(state, argc, argv)) { - return 1; + for (i = 1; i < argc;) { + int consumed; + + consumed = SDLTest_CommonArg(state, i); + if (consumed == 0) { + consumed = -1; + if (SDL_strcasecmp(argv[i], "--no-constraints") == 0) { + no_constraints = true; + consumed = 1; + } else if (SDL_strcasecmp(argv[i], "--no-grab") == 0) { + no_grab = true; + consumed = 1; + } + } + if (consumed < 0) { + static const char *options[] = { + "[--no-constraints]", + "[--no-grab]", + NULL + }; + SDLTest_CommonLogUsage(state, argv[0], options); + return 1; + } + i += consumed; } if (!SDLTest_CommonInit(state)) { diff --git a/libs/SDL3/test/testprocess.c b/libs/SDL3/test/testprocess.c index 425b4451..4d6437d3 100644 --- a/libs/SDL3/test/testprocess.c +++ b/libs/SDL3/test/testprocess.c @@ -82,7 +82,7 @@ static int SDLCALL process_testArguments(void *arg) "", " ", "a b c", - "a\tb\tc\t", + "a\tb\tc\t\v\r\n", "\"a b\" c", "'a' 'b' 'c'", "%d%%%s", @@ -965,6 +965,165 @@ cleanup: return TEST_COMPLETED; } +static int process_testWindowsCmdline(void *arg) +{ + TestProcessData *data = (TestProcessData *)arg; + const char *process_args[] = { + data->childprocess_path, + "--print-arguments", + "--", + "", + " ", + "a b c", + "a\tb\tc\t", + "\"a b\" c", + "'a' 'b' 'c'", + "%d%%%s", + "\\t\\c", + "evil\\", + "a\\b\"c\\", + "\"\\^&|<>%", /* characters with a special meaning */ + NULL + }; + /* this will have the same result as process_args, but escaped in a different way */ + const char *process_cmdline_template = + "%s " + "--print-arguments " + "-- " + "\"\" " + "\" \" " + "a\" \"b\" \"c\t" /* using tab as delimiter */ + "\"a\tb\tc\t\" " + "\"\"\"\"a b\"\"\" c\" " + "\"'a' 'b' 'c'\" " + "%%d%%%%%%s " /* will be passed to sprintf */ + "\\t\\c " + "evil\\ " + "a\\b\"\\\"\"c\\ " + "\\\"\\^&|<>%%"; + char process_cmdline[65535]; + SDL_PropertiesID props; + SDL_Process *process = NULL; + char *buffer; + int exit_code; + int i; + size_t total_read = 0; + +#ifndef SDL_PLATFORM_WINDOWS + SDLTest_AssertPass("SDL_PROP_PROCESS_CREATE_CMDLINE_STRING only works on Windows"); + return TEST_SKIPPED; +#endif + + props = SDL_CreateProperties(); + SDLTest_AssertCheck(props != 0, "SDL_CreateProperties()"); + if (!props) { + goto failed; + } + SDL_SetNumberProperty(props, SDL_PROP_PROCESS_CREATE_STDIN_NUMBER, SDL_PROCESS_STDIO_APP); + SDL_SetNumberProperty(props, SDL_PROP_PROCESS_CREATE_STDOUT_NUMBER, SDL_PROCESS_STDIO_APP); + SDL_SetBooleanProperty(props, SDL_PROP_PROCESS_CREATE_STDERR_TO_STDOUT_BOOLEAN, true); + + process = SDL_CreateProcessWithProperties(props); + SDLTest_AssertCheck(process == NULL, "SDL_CreateProcessWithProperties() should fail"); + + SDL_snprintf(process_cmdline, SDL_arraysize(process_cmdline), process_cmdline_template, data->childprocess_path); + SDL_SetStringProperty(props, SDL_PROP_PROCESS_CREATE_CMDLINE_STRING, process_cmdline); + + process = SDL_CreateProcessWithProperties(props); + SDLTest_AssertCheck(process != NULL, "SDL_CreateProcessWithProperties()"); + if (!process) { + goto failed; + } + + exit_code = 0xdeadbeef; + buffer = (char *)SDL_ReadProcess(process, &total_read, &exit_code); + SDLTest_AssertCheck(buffer != NULL, "SDL_ReadProcess()"); + SDLTest_AssertCheck(exit_code == 0, "Exit code should be 0, is %d", exit_code); + if (!buffer) { + goto failed; + } + SDLTest_LogEscapedString("stdout of process: ", buffer, total_read); + + for (i = 3; process_args[i]; i++) { + char line[64]; + SDL_snprintf(line, sizeof(line), "|%d=%s|", i - 3, process_args[i]); + SDLTest_AssertCheck(!!SDL_strstr(buffer, line), "Check %s is in output", line); + } + SDL_free(buffer); + + SDLTest_AssertPass("About to destroy process"); + SDL_DestroyProcess(process); + + return TEST_COMPLETED; + +failed: + SDL_DestroyProcess(process); + return TEST_ABORTED; +} + +static int process_testWindowsCmdlinePrecedence(void *arg) +{ + TestProcessData *data = (TestProcessData *)arg; + const char *process_args[] = { + data->childprocess_path, + "--print-arguments", + "--", + "argument 1", + NULL + }; + const char *process_cmdline_template = "%s --print-arguments -- \"argument 2\""; + char process_cmdline[65535]; + SDL_PropertiesID props; + SDL_Process *process = NULL; + char *buffer; + int exit_code; + size_t total_read = 0; + +#ifndef SDL_PLATFORM_WINDOWS + SDLTest_AssertPass("SDL_PROP_PROCESS_CREATE_CMDLINE_STRING only works on Windows"); + return TEST_SKIPPED; +#endif + + props = SDL_CreateProperties(); + SDLTest_AssertCheck(props != 0, "SDL_CreateProperties()"); + if (!props) { + goto failed; + } + + SDL_snprintf(process_cmdline, SDL_arraysize(process_cmdline), process_cmdline_template, data->childprocess_path); + SDL_SetPointerProperty(props, SDL_PROP_PROCESS_CREATE_ARGS_POINTER, (void *)process_args); + SDL_SetStringProperty(props, SDL_PROP_PROCESS_CREATE_CMDLINE_STRING, (const char *)process_cmdline); + SDL_SetNumberProperty(props, SDL_PROP_PROCESS_CREATE_STDIN_NUMBER, SDL_PROCESS_STDIO_APP); + SDL_SetNumberProperty(props, SDL_PROP_PROCESS_CREATE_STDOUT_NUMBER, SDL_PROCESS_STDIO_APP); + SDL_SetBooleanProperty(props, SDL_PROP_PROCESS_CREATE_STDERR_TO_STDOUT_BOOLEAN, true); + + process = SDL_CreateProcessWithProperties(props); + SDLTest_AssertCheck(process != NULL, "SDL_CreateProcessWithProperties()"); + if (!process) { + goto failed; + } + + exit_code = 0xdeadbeef; + buffer = (char *)SDL_ReadProcess(process, &total_read, &exit_code); + SDLTest_AssertCheck(buffer != NULL, "SDL_ReadProcess()"); + SDLTest_AssertCheck(exit_code == 0, "Exit code should be 0, is %d", exit_code); + if (!buffer) { + goto failed; + } + SDLTest_LogEscapedString("stdout of process: ", buffer, total_read); + SDLTest_AssertCheck(!!SDL_strstr(buffer, "|0=argument 2|"), "Check |0=argument 2| is printed"); + SDL_free(buffer); + + SDLTest_AssertPass("About to destroy process"); + SDL_DestroyProcess(process); + + return TEST_COMPLETED; + +failed: + SDL_DestroyProcess(process); + return TEST_ABORTED; +} + static const SDLTest_TestCaseReference processTestArguments = { process_testArguments, "process_testArguments", "Test passing arguments to child process", TEST_ENABLED }; @@ -1017,6 +1176,14 @@ static const SDLTest_TestCaseReference processTestFileRedirection = { process_testFileRedirection, "process_testFileRedirection", "Test redirection from/to files", TEST_ENABLED }; +static const SDLTest_TestCaseReference processTestWindowsCmdline = { + process_testWindowsCmdline, "process_testWindowsCmdline", "Test passing cmdline directly to CreateProcess", TEST_ENABLED +}; + +static const SDLTest_TestCaseReference processTestWindowsCmdlinePrecedence = { + process_testWindowsCmdlinePrecedence, "process_testWindowsCmdlinePrecedence", "Test SDL_PROP_PROCESS_CREATE_CMDLINE_STRING precedence over SDL_PROP_PROCESS_CREATE_ARGS_POINTER", TEST_ENABLED +}; + static const SDLTest_TestCaseReference *processTests[] = { &processTestArguments, &processTestExitCode, @@ -1031,6 +1198,8 @@ static const SDLTest_TestCaseReference *processTests[] = { &processTestNonExistingExecutable, &processTestBatBadButVulnerability, &processTestFileRedirection, + &processTestWindowsCmdline, + &processTestWindowsCmdlinePrecedence, NULL }; diff --git a/libs/SDL3/test/testrendercopyex.c b/libs/SDL3/test/testrendercopyex.c index e46d522a..eba31e61 100644 --- a/libs/SDL3/test/testrendercopyex.c +++ b/libs/SDL3/test/testrendercopyex.c @@ -135,8 +135,8 @@ int main(int argc, char *argv[]) drawstate->window = state->windows[i]; drawstate->renderer = state->renderers[i]; - drawstate->sprite = LoadTexture(drawstate->renderer, "icon.bmp", true, NULL, NULL); - drawstate->background = LoadTexture(drawstate->renderer, "sample.bmp", false, NULL, NULL); + drawstate->sprite = LoadTexture(drawstate->renderer, "icon.bmp", true); + drawstate->background = LoadTexture(drawstate->renderer, "sample.bmp", false); if (!drawstate->sprite || !drawstate->background) { quit(2); } diff --git a/libs/SDL3/test/testrendertarget.c b/libs/SDL3/test/testrendertarget.c index bad57fdc..ab830efa 100644 --- a/libs/SDL3/test/testrendertarget.c +++ b/libs/SDL3/test/testrendertarget.c @@ -253,11 +253,11 @@ int main(int argc, char *argv[]) drawstate->window = state->windows[i]; drawstate->renderer = state->renderers[i]; if (test_composite) { - drawstate->sprite = LoadTexture(drawstate->renderer, "icon-alpha.bmp", true, NULL, NULL); + drawstate->sprite = LoadTexture(drawstate->renderer, "icon-alpha.bmp", true); } else { - drawstate->sprite = LoadTexture(drawstate->renderer, "icon.bmp", true, NULL, NULL); + drawstate->sprite = LoadTexture(drawstate->renderer, "icon.bmp", true); } - drawstate->background = LoadTexture(drawstate->renderer, "sample.bmp", false, NULL, NULL); + drawstate->background = LoadTexture(drawstate->renderer, "sample.bmp", false); if (!drawstate->sprite || !drawstate->background) { quit(2); } diff --git a/libs/SDL3/test/testscale.c b/libs/SDL3/test/testscale.c index a282b07f..766a2af2 100644 --- a/libs/SDL3/test/testscale.c +++ b/libs/SDL3/test/testscale.c @@ -126,12 +126,13 @@ int main(int argc, char *argv[]) drawstate->window = state->windows[i]; drawstate->renderer = state->renderers[i]; - drawstate->sprite = LoadTexture(drawstate->renderer, "icon.bmp", true, NULL, NULL); - drawstate->background = LoadTexture(drawstate->renderer, "sample.bmp", false, NULL, NULL); + drawstate->sprite = LoadTexture(drawstate->renderer, "icon.bmp", true); + drawstate->background = LoadTexture(drawstate->renderer, "sample.bmp", false); if (!drawstate->sprite || !drawstate->background) { quit(2); } SDL_GetTextureSize(drawstate->sprite, &drawstate->sprite_rect.w, &drawstate->sprite_rect.h); + SDL_SetTextureScaleMode(drawstate->sprite, SDL_SCALEMODE_PIXELART); drawstate->scale_direction = 1; } diff --git a/libs/SDL3/test/testsprite.c b/libs/SDL3/test/testsprite.c index 2f202d6d..dd146a9b 100644 --- a/libs/SDL3/test/testsprite.c +++ b/libs/SDL3/test/testsprite.c @@ -53,16 +53,18 @@ void SDL_AppQuit(void *appstate, SDL_AppResult result) static int LoadSprite(const char *file) { - int i, w, h; + int i; for (i = 0; i < state->num_windows; ++i) { /* This does the SDL_LoadBMP step repeatedly, but that's OK for test code. */ if (sprites[i]) { SDL_DestroyTexture(sprites[i]); } - sprites[i] = LoadTexture(state->renderers[i], file, true, &w, &h); - sprite_w = (float)w; - sprite_h = (float)h; + sprites[i] = LoadTexture(state->renderers[i], file, true); + if (sprites[i]) { + sprite_w = (float)sprites[i]->w; + sprite_h = (float)sprites[i]->h; + } if (!sprites[i]) { return -1; } diff --git a/libs/SDL3/test/testutils.c b/libs/SDL3/test/testutils.c index ab044bf2..36445d5b 100644 --- a/libs/SDL3/test/testutils.c +++ b/libs/SDL3/test/testutils.c @@ -72,7 +72,7 @@ char *GetResourceFilename(const char *user_specified, const char *def) * * If height_out is non-NULL, set it to the texture height. */ -SDL_Texture *LoadTexture(SDL_Renderer *renderer, const char *file, bool transparent, int *width_out, int *height_out) +SDL_Texture *LoadTexture(SDL_Renderer *renderer, const char *file, bool transparent) { SDL_Surface *temp = NULL; SDL_Texture *texture = NULL; @@ -117,14 +117,6 @@ SDL_Texture *LoadTexture(SDL_Renderer *renderer, const char *file, bool transpar } } - if (width_out) { - *width_out = temp->w; - } - - if (height_out) { - *height_out = temp->h; - } - texture = SDL_CreateTextureFromSurface(renderer, temp); if (!texture) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create texture: %s", SDL_GetError()); diff --git a/libs/SDL3/test/testutils.h b/libs/SDL3/test/testutils.h index 1429d756..65030cd2 100644 --- a/libs/SDL3/test/testutils.h +++ b/libs/SDL3/test/testutils.h @@ -16,8 +16,7 @@ #include -SDL_Texture *LoadTexture(SDL_Renderer *renderer, const char *file, bool transparent, - int *width_out, int *height_out); +SDL_Texture *LoadTexture(SDL_Renderer *renderer, const char *file, bool transparent); char *GetNearbyFilename(const char *file); char *GetResourceFilename(const char *user_specified, const char *def); diff --git a/libs/SDL3/test/testviewport.c b/libs/SDL3/test/testviewport.c index 01e27e7b..e0ba1a1a 100644 --- a/libs/SDL3/test/testviewport.c +++ b/libs/SDL3/test/testviewport.c @@ -31,7 +31,6 @@ static bool use_target = false; static Uint32 wait_start; #endif static SDL_Texture *sprite; -static int sprite_w, sprite_h; /* Call this instead of exit(), so we can clean up SDL: atexit() is evil. */ static void @@ -184,8 +183,7 @@ int main(int argc, char *argv[]) quit(2); } - sprite = LoadTexture(state->renderers[0], "icon.bmp", true, &sprite_w, &sprite_h); - + sprite = LoadTexture(state->renderers[0], "icon.bmp", true); if (!sprite) { quit(2); } diff --git a/libs/SDL3/wayland-protocols/pointer-warp-v1.xml b/libs/SDL3/wayland-protocols/pointer-warp-v1.xml new file mode 100644 index 00000000..158dad83 --- /dev/null +++ b/libs/SDL3/wayland-protocols/pointer-warp-v1.xml @@ -0,0 +1,72 @@ + + + + Copyright © 2024 Neal Gompa + Copyright © 2024 Xaver Hugl + Copyright © 2024 Matthias Klumpp + Copyright © 2024 Vlad Zahorodnii + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + + + + This global interface allows applications to request the pointer to be + moved to a position relative to a wl_surface. + + Note that if the desired behavior is to constrain the pointer to an area + or lock it to a position, this protocol does not provide a reliable way + to do that. The pointer constraint and pointer lock protocols should be + used for those use cases instead. + + Warning! The protocol described in this file is currently in the testing + phase. Backward compatible changes may be added together with the + corresponding interface version bump. Backward incompatible changes can + only be done by creating a new major version of the extension. + + + + + Destroy the pointer warp manager. + + + + + + Request the compositor to move the pointer to a surface-local position. + Whether or not the compositor honors the request is implementation defined, + but it should + - honor it if the surface has pointer focus, including + when it has an implicit pointer grab + - reject it if the enter serial is incorrect + - reject it if the requested position is outside of the surface + + Note that the enter serial is valid for any surface of the client, + and does not have to be from the surface the pointer is warped to. + + + + + + + + + + diff --git a/libs/SDL3/wayland-protocols/xdg-shell.xml b/libs/SDL3/wayland-protocols/xdg-shell.xml index 777eaa74..c4d4685a 100644 --- a/libs/SDL3/wayland-protocols/xdg-shell.xml +++ b/libs/SDL3/wayland-protocols/xdg-shell.xml @@ -29,7 +29,7 @@ DEALINGS IN THE SOFTWARE. - + The xdg_wm_base interface is exposed as a global object enabling clients to turn their wl_surfaces into windows in a desktop environment. It @@ -122,7 +122,7 @@ - + The xdg_positioner provides a collection of rules for the placement of a child surface relative to a parent surface. Rules can be defined to ensure @@ -344,7 +344,7 @@ The default adjustment is none. - @@ -407,7 +407,7 @@ - + An interface that may be implemented by a wl_surface, for implementations that provide a desktop-style user interface. @@ -434,7 +434,8 @@ manipulate a buffer prior to the first xdg_surface.configure call must also be treated as errors. - After creating a role-specific object and setting it up, the client must + After creating a role-specific object and setting it up (e.g. by sending + the title, app ID, size constraints, parent, etc), the client must perform an initial commit without any buffer attached. The compositor will reply with initial wl_surface state such as wl_surface.preferred_buffer_scale followed by an xdg_surface.configure @@ -515,8 +516,7 @@ portions like drop-shadows which should be ignored for the purposes of aligning, placing and constraining windows. - The window geometry is double buffered, and will be applied at the - time wl_surface.commit of the corresponding wl_surface is called. + The window geometry is double-buffered state, see wl_surface.commit. When maintaining a position, the compositor should treat the (x, y) coordinate of the window geometry as the top left corner of the window. @@ -617,7 +617,7 @@ - + This interface defines an xdg_surface role which allows a surface to, among other things, set window-like properties such as maximize, @@ -625,13 +625,17 @@ id, and well as trigger user interactive operations such as interactive resize and move. + A xdg_toplevel by default is responsible for providing the full intended + visual representation of the toplevel, which depending on the window + state, may mean things like a title bar, window controls and drop shadow. + Unmapping an xdg_toplevel means that the surface cannot be shown by the compositor until it is explicitly mapped again. All active operations (e.g., move, resize) are canceled and all attributes (e.g. title, state, stacking, ...) are discarded for an xdg_toplevel surface when it is unmapped. The xdg_toplevel returns to the state it had right after xdg_surface.get_toplevel. The client - can re-map the toplevel by perfoming a commit without any buffer + can re-map the toplevel by performing a commit without any buffer attached, waiting for a configure event and handling it as usual (see xdg_surface description). @@ -828,8 +832,7 @@ configure event to ensure that both the client and the compositor setting the state can be synchronized. - States set in this way are double-buffered. They will get applied on - the next commit. + States set in this way are double-buffered, see wl_surface.commit. @@ -869,24 +872,36 @@ The window is currently in a tiled layout and the left edge is considered to be adjacent to another part of the tiling grid. + + The client should draw without shadow or other decoration outside of + the window geometry on the left edge. The window is currently in a tiled layout and the right edge is considered to be adjacent to another part of the tiling grid. + + The client should draw without shadow or other decoration outside of + the window geometry on the right edge. The window is currently in a tiled layout and the top edge is considered to be adjacent to another part of the tiling grid. + + The client should draw without shadow or other decoration outside of + the window geometry on the top edge. The window is currently in a tiled layout and the bottom edge is considered to be adjacent to another part of the tiling grid. + + The client should draw without shadow or other decoration outside of + the window geometry on the bottom edge. @@ -896,6 +911,38 @@ outputs are switched off due to screen locking. + + + The left edge of the window is currently constrained, meaning it + shouldn't attempt to resize from that edge. It can for example mean + it's tiled next to a monitor edge on the constrained side of the + window. + + + + + The right edge of the window is currently constrained, meaning it + shouldn't attempt to resize from that edge. It can for example mean + it's tiled next to a monitor edge on the constrained side of the + window. + + + + + The top edge of the window is currently constrained, meaning it + shouldn't attempt to resize from that edge. It can for example mean + it's tiled next to a monitor edge on the constrained side of the + window. + + + + + The bottom edge of the window is currently constrained, meaning it + shouldn't attempt to resize from that edge. It can for example mean + it's tiled next to a monitor edge on the constrained side of the + window. + + @@ -908,8 +955,7 @@ The width and height arguments are in window geometry coordinates. See xdg_surface.set_window_geometry. - Values set in this way are double-buffered. They will get applied - on the next commit. + Values set in this way are double-buffered, see wl_surface.commit. The compositor can use this information to allow or disallow different states like maximize or fullscreen and draw accurate @@ -949,8 +995,7 @@ The width and height arguments are in window geometry coordinates. See xdg_surface.set_window_geometry. - Values set in this way are double-buffered. They will get applied - on the next commit. + Values set in this way are double-buffered, see wl_surface.commit. The compositor can use this information to allow or disallow different states like maximize or fullscreen and draw accurate @@ -1194,7 +1239,7 @@ - + A popup surface is a short-lived, temporary surface. It can be used to implement for example menus, popovers, tooltips and other similar user