summaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/ci-macos.yml2
-rw-r--r--makefile2
-rw-r--r--scripts/src/osd/modules.lua5
-rw-r--r--scripts/src/osd/sdl3.lua446
-rw-r--r--scripts/src/osd/sdl3_cfg.lua167
-rw-r--r--scripts/src/osd/windows.lua25
-rw-r--r--scripts/src/osd/windows_cfg.lua11
-rw-r--r--scripts/src/tools.lua5
-rw-r--r--src/osd/modules/font/font_sdl.cpp4
-rw-r--r--src/osd/modules/font/font_sdl3.cpp389
-rw-r--r--src/osd/modules/input/input_common.cpp15
-rw-r--r--src/osd/modules/input/input_dinput.cpp4
-rw-r--r--src/osd/modules/input/input_sdl.cpp5
-rw-r--r--src/osd/modules/input/input_sdl3.cpp2944
-rw-r--r--src/osd/modules/input/input_x11.cpp2
-rw-r--r--src/osd/modules/lib/osdlib_unix.cpp10
-rw-r--r--src/osd/modules/lib/osdobj_common.cpp19
-rw-r--r--src/osd/modules/monitor/monitor_sdl.cpp30
-rw-r--r--src/osd/modules/opengl/osd_opengl.h11
-rw-r--r--src/osd/modules/render/draw13.cpp8
-rw-r--r--src/osd/modules/render/drawbgfx.cpp84
-rw-r--r--src/osd/modules/render/drawogl.cpp15
-rw-r--r--src/osd/modules/render/drawsdl.cpp10
-rw-r--r--src/osd/modules/render/drawsdl3accel.cpp1165
-rw-r--r--src/osd/modules/render/drawsdl3soft.cpp700
-rw-r--r--src/osd/modules/render/sdlglcontext.h12
-rw-r--r--src/osd/modules/sound/sdl3_sound.cpp373
-rw-r--r--src/osd/modules/sound/sdl_sound.cpp5
-rwxr-xr-xsrc/osd/sdl/taputil.sh2
-rw-r--r--src/osd/sdl3/android_main.cpp11
-rw-r--r--src/osd/sdl3/osdsdl.cpp889
-rw-r--r--src/osd/sdl3/osdsdl.h216
-rw-r--r--src/osd/sdl3/sdlmain.cpp113
-rw-r--r--src/osd/sdl3/sdlopts.cpp127
-rw-r--r--src/osd/sdl3/sdlopts.h100
-rw-r--r--src/osd/sdl3/sdlprefix.h86
-rwxr-xr-xsrc/osd/sdl3/taputil.sh43
-rw-r--r--src/osd/sdl3/video.cpp182
-rw-r--r--src/osd/sdl3/window.cpp1336
-rw-r--r--src/osd/sdl3/window.h134
-rw-r--r--src/tools/imgtool/imgtool.cpp7
-rw-r--r--src/tools/testkeys.cpp85
42 files changed, 9742 insertions, 57 deletions
diff --git a/.github/workflows/ci-macos.yml b/.github/workflows/ci-macos.yml
index ba56880902e..ae8c0abcdc7 100644
--- a/.github/workflows/ci-macos.yml
+++ b/.github/workflows/ci-macos.yml
@@ -29,7 +29,7 @@ jobs:
with:
fetch-depth: 0
- name: Install dependencies
- run: brew install python3 sdl2
+ run: brew install python3 sdl3
- name: Build
env:
USE_LIBSDL: 1
diff --git a/makefile b/makefile
index b68998441dd..1f3990f6f50 100644
--- a/makefile
+++ b/makefile
@@ -463,7 +463,7 @@ OSD := sdl
else ifeq ($(TARGETOS),solaris)
OSD := sdl
else ifeq ($(TARGETOS),macosx)
-OSD := sdl
+OSD := sdl3
else ifeq ($(TARGETOS),asmjs)
OSD := sdl
endif # TARGETOS
diff --git a/scripts/src/osd/modules.lua b/scripts/src/osd/modules.lua
index 4b796a1f5b4..fddf73b4046 100644
--- a/scripts/src/osd/modules.lua
+++ b/scripts/src/osd/modules.lua
@@ -79,6 +79,7 @@ function osdmodulesbuild()
MAME_DIR .. "src/osd/modules/font/font_none.cpp",
MAME_DIR .. "src/osd/modules/font/font_osx.cpp",
MAME_DIR .. "src/osd/modules/font/font_sdl.cpp",
+ MAME_DIR .. "src/osd/modules/font/font_sdl3.cpp",
MAME_DIR .. "src/osd/modules/font/font_windows.cpp",
MAME_DIR .. "src/osd/modules/input/assignmenthelper.cpp",
MAME_DIR .. "src/osd/modules/input/assignmenthelper.h",
@@ -91,6 +92,7 @@ function osdmodulesbuild()
MAME_DIR .. "src/osd/modules/input/input_none.cpp",
MAME_DIR .. "src/osd/modules/input/input_rawinput.cpp",
MAME_DIR .. "src/osd/modules/input/input_sdl.cpp",
+ MAME_DIR .. "src/osd/modules/input/input_sdl3.cpp",
MAME_DIR .. "src/osd/modules/input/input_win32.cpp",
MAME_DIR .. "src/osd/modules/input/input_wincommon.h",
MAME_DIR .. "src/osd/modules/input/input_windows.cpp",
@@ -129,6 +131,8 @@ function osdmodulesbuild()
MAME_DIR .. "src/osd/modules/render/drawnone.cpp",
MAME_DIR .. "src/osd/modules/render/drawogl.cpp",
MAME_DIR .. "src/osd/modules/render/drawsdl.cpp",
+ MAME_DIR .. "src/osd/modules/render/drawsdl3accel.cpp",
+ MAME_DIR .. "src/osd/modules/render/drawsdl3soft.cpp",
MAME_DIR .. "src/osd/modules/render/render_module.h",
MAME_DIR .. "src/osd/modules/sound/coreaudio_sound.cpp",
MAME_DIR .. "src/osd/modules/sound/js_sound.cpp",
@@ -139,6 +143,7 @@ function osdmodulesbuild()
MAME_DIR .. "src/osd/modules/sound/pulse_sound.cpp",
MAME_DIR .. "src/osd/modules/sound/pipewire_sound.cpp",
MAME_DIR .. "src/osd/modules/sound/sdl_sound.cpp",
+ MAME_DIR .. "src/osd/modules/sound/sdl3_sound.cpp",
MAME_DIR .. "src/osd/modules/sound/sound_module.cpp",
MAME_DIR .. "src/osd/modules/sound/sound_module.h",
MAME_DIR .. "src/osd/modules/sound/wasapi_sound.cpp",
diff --git a/scripts/src/osd/sdl3.lua b/scripts/src/osd/sdl3.lua
new file mode 100644
index 00000000000..18c20675281
--- /dev/null
+++ b/scripts/src/osd/sdl3.lua
@@ -0,0 +1,446 @@
+-- license:BSD-3-Clause
+-- copyright-holders:MAMEdev Team
+
+---------------------------------------------------------------------------
+--
+-- sdl3.lua
+--
+-- Rules for the building with SDL
+--
+---------------------------------------------------------------------------
+
+dofile("modules.lua")
+
+
+function maintargetosdoptions(_target,_subtarget)
+ osdmodulestargetconf()
+
+ if _OPTIONS["USE_DISPATCH_GL"]~="1" and _OPTIONS["MESA_INSTALL_ROOT"] then
+ libdirs {
+ path.join(_OPTIONS["MESA_INSTALL_ROOT"],"lib"),
+ }
+ linkoptions {
+ "-Wl,-rpath=" .. path.join(_OPTIONS["MESA_INSTALL_ROOT"],"lib"),
+ }
+ end
+
+ if _OPTIONS["NO_X11"]~="1" then
+ links {
+ "X11",
+ "Xinerama",
+ }
+ else
+ if _OPTIONS["targetos"]=="linux" or _OPTIONS["targetos"]=="netbsd" or _OPTIONS["targetos"]=="openbsd" then
+ links {
+ "EGL",
+ }
+ end
+ end
+
+ if _OPTIONS["NO_USE_XINPUT"]~="1" then
+ links {
+ "Xext",
+ "Xi",
+ }
+ end
+
+ if BASE_TARGETOS=="unix" and _OPTIONS["targetos"]~="macosx" and _OPTIONS["targetos"]~="android" and _OPTIONS["targetos"]~="asmjs" then
+ links {
+ "SDL3_ttf",
+ }
+ local str = backtick(pkgconfigcmd() .. " --libs fontconfig")
+ addlibfromstring(str)
+ addoptionsfromstring(str)
+ end
+
+ if _OPTIONS["targetos"]=="windows" then
+ if _OPTIONS["USE_LIBSDL"]~="1" then
+ configuration { "mingw*"}
+ links {
+ "SDL3",
+ }
+ configuration { "vs*" }
+ links {
+ "SDL3",
+ "imm32",
+ "version",
+ }
+ configuration { }
+ else
+ local str = backtick(sdlconfigcmd() .. " --libs | sed 's/ -lSDLmain//'")
+ addlibfromstring(str)
+ addoptionsfromstring(str)
+ end
+ configuration { "x32", "vs*" }
+ libdirs {
+ path.join(_OPTIONS["SDL_INSTALL_ROOT"],"lib","x86")
+ }
+ configuration { "x64", "vs*" }
+ libdirs {
+ path.join(_OPTIONS["SDL_INSTALL_ROOT"],"lib","x64")
+ }
+ configuration { }
+
+ links {
+ "dinput8",
+ "psapi",
+ }
+ elseif _OPTIONS["targetos"]=="haiku" then
+ links {
+ "network",
+ "bsd",
+ }
+ end
+
+ configuration { "mingw*" or "vs*" }
+ targetprefix "sdl"
+ links {
+ "psapi",
+ "ole32",
+ }
+ configuration { }
+end
+
+
+function sdlconfigcmd()
+ if _OPTIONS["targetos"]=="asmjs" then
+ return "sdl3-config"
+ elseif _OPTIONS["SDL_PKGCONFIG_PATH"] then
+ return path.join(_OPTIONS["SDL_PKGCONFIG_PATH"],"pkg-config") .. " sdl3"
+ elseif not _OPTIONS["SDL_INSTALL_ROOT"] then
+ return pkgconfigcmd() .. " sdl3"
+ else
+ return path.join(_OPTIONS["SDL_INSTALL_ROOT"],"bin","sdl3") .. "-config"
+ end
+end
+
+
+newoption {
+ trigger = "MESA_INSTALL_ROOT",
+ description = "link against specific GL-Library - also adds rpath to executable (overridden by USE_DISPATCH_GL)",
+}
+
+newoption {
+ trigger = "SDL_INI_PATH",
+ description = "Default search path for .ini files",
+}
+
+newoption {
+ trigger = "NO_X11",
+ description = "Disable use of X11",
+ allowed = {
+ { "0", "Enable X11" },
+ { "1", "Disable X11" },
+ },
+}
+
+if not _OPTIONS["NO_X11"] then
+ if _OPTIONS["targetos"]=="windows" or _OPTIONS["targetos"]=="macosx" or _OPTIONS["targetos"]=="haiku" or _OPTIONS["targetos"]=="asmjs" or _OPTIONS["targetos"]=="android" then
+ _OPTIONS["NO_X11"] = "1"
+ else
+ _OPTIONS["NO_X11"] = "0"
+ end
+end
+
+newoption {
+ trigger = "NO_USE_XINPUT",
+ description = "Disable use of Xinput",
+ allowed = {
+ { "0", "Enable Xinput" },
+ { "1", "Disable Xinput" },
+ },
+}
+
+if not _OPTIONS["NO_USE_XINPUT"] then
+ if _OPTIONS["targetos"]=="windows" or _OPTIONS["targetos"]=="macosx" or _OPTIONS["targetos"]=="haiku" or _OPTIONS["targetos"]=="asmjs" or _OPTIONS["targetos"]=="android" then
+ _OPTIONS["NO_USE_XINPUT"] = "1"
+ else
+ _OPTIONS["NO_USE_XINPUT"] = "0"
+ end
+end
+
+newoption {
+ trigger = "NO_USE_XINPUT_WII_LIGHTGUN_HACK",
+ description = "Disable use of Xinput Wii Lightgun Hack",
+ allowed = {
+ { "0", "Enable Xinput Wii Lightgun Hack" },
+ { "1", "Disable Xinput Wii Lightgun Hack" },
+ },
+}
+
+if not _OPTIONS["NO_USE_XINPUT_WII_LIGHTGUN_HACK"] then
+ _OPTIONS["NO_USE_XINPUT_WII_LIGHTGUN_HACK"] = "1"
+end
+
+newoption {
+ trigger = "SDL_INSTALL_ROOT",
+ description = "Equivalent to the ./configure --prefix=<path>",
+}
+
+newoption {
+ trigger = "SDL_PKGCONFIG_PATH",
+ description = "Location of pkg-config command that knows about SDL. Useful for non-root Homebrew installs on Linux.",
+}
+
+newoption {
+ trigger = "SDL_FRAMEWORK_PATH",
+ description = "Location of SDL framework for custom OS X installations",
+}
+
+-- SDL 3's framework now contains all Apple platforms in a single framework, so we need to
+-- specifically ask for the macOS version.
+if not _OPTIONS["SDL_FRAMEWORK_PATH"] then
+ _OPTIONS["SDL_FRAMEWORK_PATH"] = "/Library/Frameworks/SDL3.xcframework/macos-arm64_x86_64/"
+end
+
+newoption {
+ trigger = "USE_LIBSDL",
+ description = "Use SDL library on OS (rather than framework/dll)",
+ allowed = {
+ { "0", "Use framework/dll" },
+ { "1", "Use library" },
+ },
+}
+
+if not _OPTIONS["USE_LIBSDL"] then
+ _OPTIONS["USE_LIBSDL"] = "0"
+end
+
+
+BASE_TARGETOS = "unix"
+SDLOS_TARGETOS = "unix"
+if _OPTIONS["targetos"]=="windows" then
+ BASE_TARGETOS = "win32"
+ SDLOS_TARGETOS = "win32"
+elseif _OPTIONS["targetos"]=="macosx" then
+ SDLOS_TARGETOS = "macosx"
+end
+
+if BASE_TARGETOS=="unix" then
+ if _OPTIONS["targetos"]=="macosx" then
+ local os_version = str_to_version(backtick("sw_vers -productVersion"))
+
+ links {
+ "Cocoa.framework",
+ }
+ linkoptions {
+ "-framework QuartzCore",
+ "-framework OpenGL",
+ "-framework IOKit",
+ "-rpath " .. _OPTIONS["SDL_FRAMEWORK_PATH"],
+ }
+
+
+ if os_version>=101100 then
+ linkoptions {
+ "-weak_framework Metal",
+ }
+ end
+ if _OPTIONS["USE_LIBSDL"]~="1" then
+ linkoptions {
+ "-F" .. _OPTIONS["SDL_FRAMEWORK_PATH"],
+ }
+ links {
+ "SDL3.framework",
+ }
+ else
+ local str = backtick(sdlconfigcmd() .. " --libs --static | sed 's/-lSDLmain//'")
+ addlibfromstring(str)
+ addoptionsfromstring(str)
+ end
+ else
+ if _OPTIONS["NO_X11"]=="1" then
+ _OPTIONS["USE_QTDEBUG"] = "0"
+ else
+ libdirs {
+ "/usr/X11/lib",
+ "/usr/X11R6/lib",
+ "/usr/openwin/lib",
+ }
+ end
+ local str = backtick(sdlconfigcmd() .. " --libs")
+ addlibfromstring(str)
+ addoptionsfromstring(str)
+
+ if _OPTIONS["targetos"]~="haiku" and _OPTIONS["targetos"]~="android" then
+ links {
+ "m",
+ "pthread",
+ }
+ if _OPTIONS["targetos"]=="solaris" then
+ links {
+ "socket",
+ "nsl",
+ }
+ elseif _OPTIONS["targetos"]~="asmjs" then
+ links {
+ "util",
+ }
+ end
+ end
+ end
+end
+
+project ("qtdbg_" .. _OPTIONS["osd"])
+ uuid (os.uuid("qtdbg_" .. _OPTIONS["osd"]))
+ kind (LIBTYPE)
+
+ dofile("sdl3_cfg.lua")
+ includedirs {
+ MAME_DIR .. "src/emu",
+ MAME_DIR .. "src/devices", -- accessing imagedev from debugger
+ MAME_DIR .. "src/osd",
+ MAME_DIR .. "src/lib",
+ MAME_DIR .. "src/lib/util",
+ MAME_DIR .. "src/osd/modules/render",
+ MAME_DIR .. "3rdparty",
+ }
+ configuration { "linux-* or freebsd" }
+ buildoptions {
+ "-fPIC",
+ }
+ configuration { }
+
+ qtdebuggerbuild()
+
+project ("osd_" .. _OPTIONS["osd"])
+ targetsubdir(_OPTIONS["target"] .."_" .._OPTIONS["subtarget"])
+ uuid (os.uuid("osd_" .. _OPTIONS["osd"]))
+ kind (LIBTYPE)
+
+ dofile("sdl3_cfg.lua")
+ osdmodulesbuild()
+
+ includedirs {
+ MAME_DIR .. "src/emu",
+ MAME_DIR .. "src/devices", -- accessing imagedev from debugger
+ MAME_DIR .. "src/osd",
+ MAME_DIR .. "src/lib",
+ MAME_DIR .. "src/lib/util",
+ MAME_DIR .. "src/osd/modules/file",
+ MAME_DIR .. "src/osd/modules/render",
+ MAME_DIR .. "3rdparty",
+ MAME_DIR .. "src/osd/sdl3",
+ }
+
+ if _OPTIONS["targetos"]=="macosx" then
+ files {
+ MAME_DIR .. "src/osd/modules/debugger/debugosx.mm",
+ MAME_DIR .. "src/osd/modules/debugger/osx/breakpointsview.mm",
+ MAME_DIR .. "src/osd/modules/debugger/osx/breakpointsview.h",
+ MAME_DIR .. "src/osd/modules/debugger/osx/consoleview.mm",
+ MAME_DIR .. "src/osd/modules/debugger/osx/consoleview.h",
+ MAME_DIR .. "src/osd/modules/debugger/osx/debugcommandhistory.mm",
+ MAME_DIR .. "src/osd/modules/debugger/osx/debugcommandhistory.h",
+ MAME_DIR .. "src/osd/modules/debugger/osx/debugconsole.mm",
+ MAME_DIR .. "src/osd/modules/debugger/osx/debugconsole.h",
+ MAME_DIR .. "src/osd/modules/debugger/osx/debugview.mm",
+ MAME_DIR .. "src/osd/modules/debugger/osx/debugview.h",
+ MAME_DIR .. "src/osd/modules/debugger/osx/debugwindowhandler.mm",
+ MAME_DIR .. "src/osd/modules/debugger/osx/debugwindowhandler.h",
+ MAME_DIR .. "src/osd/modules/debugger/osx/deviceinfoviewer.mm",
+ MAME_DIR .. "src/osd/modules/debugger/osx/deviceinfoviewer.h",
+ MAME_DIR .. "src/osd/modules/debugger/osx/devicesviewer.mm",
+ MAME_DIR .. "src/osd/modules/debugger/osx/devicesviewer.h",
+ MAME_DIR .. "src/osd/modules/debugger/osx/disassemblyview.mm",
+ MAME_DIR .. "src/osd/modules/debugger/osx/disassemblyviewer.mm",
+ MAME_DIR .. "src/osd/modules/debugger/osx/disassemblyviewer.h",
+ MAME_DIR .. "src/osd/modules/debugger/osx/errorlogview.mm",
+ MAME_DIR .. "src/osd/modules/debugger/osx/errorlogview.h",
+ MAME_DIR .. "src/osd/modules/debugger/osx/exceptionpointsview.mm",
+ MAME_DIR .. "src/osd/modules/debugger/osx/exceptionpointsview.h",
+ MAME_DIR .. "src/osd/modules/debugger/osx/disassemblyview.h",
+ MAME_DIR .. "src/osd/modules/debugger/osx/errorlogviewer.mm",
+ MAME_DIR .. "src/osd/modules/debugger/osx/errorlogviewer.h",
+ MAME_DIR .. "src/osd/modules/debugger/osx/memoryview.mm",
+ MAME_DIR .. "src/osd/modules/debugger/osx/memoryview.h",
+ MAME_DIR .. "src/osd/modules/debugger/osx/memoryviewer.mm",
+ MAME_DIR .. "src/osd/modules/debugger/osx/memoryviewer.h",
+ MAME_DIR .. "src/osd/modules/debugger/osx/pointsviewer.mm",
+ MAME_DIR .. "src/osd/modules/debugger/osx/pointsviewer.h",
+ MAME_DIR .. "src/osd/modules/debugger/osx/registerpointsview.mm",
+ MAME_DIR .. "src/osd/modules/debugger/osx/registerpointsview.h",
+ MAME_DIR .. "src/osd/modules/debugger/osx/registersview.mm",
+ MAME_DIR .. "src/osd/modules/debugger/osx/registersview.h",
+ MAME_DIR .. "src/osd/modules/debugger/osx/watchpointsview.mm",
+ MAME_DIR .. "src/osd/modules/debugger/osx/watchpointsview.h",
+ MAME_DIR .. "src/osd/modules/debugger/osx/debugosx.h",
+ }
+ end
+
+ files {
+ MAME_DIR .. "src/osd/osdepend.h",
+ MAME_DIR .. "src/osd/modules/osdwindow.cpp",
+ MAME_DIR .. "src/osd/modules/osdwindow.h",
+ MAME_DIR .. "src/osd/sdl3/osdsdl.cpp",
+ MAME_DIR .. "src/osd/sdl3/osdsdl.h",
+ MAME_DIR .. "src/osd/sdl3/sdlmain.cpp",
+ MAME_DIR .. "src/osd/sdl3/sdlopts.cpp",
+ MAME_DIR .. "src/osd/sdl3/sdlopts.h",
+ MAME_DIR .. "src/osd/sdl3/sdlprefix.h",
+ MAME_DIR .. "src/osd/sdl3/video.cpp",
+ MAME_DIR .. "src/osd/sdl3/window.cpp",
+ MAME_DIR .. "src/osd/sdl3/window.h",
+ }
+
+project ("ocore_" .. _OPTIONS["osd"])
+ targetsubdir(_OPTIONS["target"] .."_" .. _OPTIONS["subtarget"])
+ uuid (os.uuid("ocore_" .. _OPTIONS["osd"]))
+ kind (LIBTYPE)
+
+ removeflags {
+ "SingleOutputDir",
+ }
+
+ dofile("sdl3_cfg.lua")
+
+ includedirs {
+ MAME_DIR .. "src/emu",
+ MAME_DIR .. "src/osd",
+ MAME_DIR .. "src/lib",
+ MAME_DIR .. "src/lib/util",
+ MAME_DIR .. "src/osd/sdl3",
+ }
+
+ files {
+ MAME_DIR .. "src/osd/osdcore.cpp",
+ MAME_DIR .. "src/osd/osdcore.h",
+ MAME_DIR .. "src/osd/osdfile.h",
+ MAME_DIR .. "src/osd/strconv.cpp",
+ MAME_DIR .. "src/osd/strconv.h",
+ MAME_DIR .. "src/osd/osdsync.cpp",
+ MAME_DIR .. "src/osd/osdsync.h",
+ MAME_DIR .. "src/osd/modules/osdmodule.cpp",
+ MAME_DIR .. "src/osd/modules/osdmodule.h",
+ MAME_DIR .. "src/osd/modules/lib/osdlib_" .. SDLOS_TARGETOS .. ".cpp",
+ MAME_DIR .. "src/osd/modules/lib/osdlib.h",
+ }
+
+ if BASE_TARGETOS=="unix" then
+ files {
+ MAME_DIR .. "src/osd/modules/file/posixdir.cpp",
+ MAME_DIR .. "src/osd/modules/file/posixfile.cpp",
+ MAME_DIR .. "src/osd/modules/file/posixfile.h",
+ MAME_DIR .. "src/osd/modules/file/posixptty.cpp",
+ MAME_DIR .. "src/osd/modules/file/posixsocket.cpp",
+ }
+ elseif BASE_TARGETOS=="win32" then
+ includedirs {
+ MAME_DIR .. "src/osd/windows",
+ }
+ files {
+ MAME_DIR .. "src/osd/modules/file/windir.cpp",
+ MAME_DIR .. "src/osd/modules/file/winfile.cpp",
+ MAME_DIR .. "src/osd/modules/file/winfile.h",
+ MAME_DIR .. "src/osd/modules/file/winptty.cpp",
+ MAME_DIR .. "src/osd/modules/file/winsocket.cpp",
+ MAME_DIR .. "src/osd/windows/winutil.cpp", -- FIXME put the necessary functions somewhere more appropriate?
+ MAME_DIR .. "src/osd/windows/winutil.h",
+ }
+ else
+ files {
+ MAME_DIR .. "src/osd/modules/file/stdfile.cpp",
+ }
+ end
+
+
diff --git a/scripts/src/osd/sdl3_cfg.lua b/scripts/src/osd/sdl3_cfg.lua
new file mode 100644
index 00000000000..535cf749005
--- /dev/null
+++ b/scripts/src/osd/sdl3_cfg.lua
@@ -0,0 +1,167 @@
+-- license:BSD-3-Clause
+-- copyright-holders:MAMEdev Team
+
+dofile('modules.lua')
+
+forcedincludes {
+ MAME_DIR .. "src/osd/sdl/sdlprefix.h"
+}
+
+if _OPTIONS["USE_TAPTUN"]=="1" or _OPTIONS["USE_PCAP"]=="1" then
+ defines {
+ "USE_NETWORK",
+ }
+ if _OPTIONS["USE_TAPTUN"]=="1" then
+ defines {
+ "OSD_NET_USE_TAPTUN",
+ }
+ end
+ if _OPTIONS["USE_PCAP"]=="1" then
+ defines {
+ "OSD_NET_USE_PCAP",
+ }
+ end
+end
+
+if _OPTIONS["NO_OPENGL"]~="1" and _OPTIONS["USE_DISPATCH_GL"]~="1" and _OPTIONS["MESA_INSTALL_ROOT"] then
+ includedirs {
+ path.join(_OPTIONS["MESA_INSTALL_ROOT"],"include"),
+ }
+end
+
+if _OPTIONS["SDL_INI_PATH"]~=nil then
+ defines {
+ "'INI_PATH=\"" .. _OPTIONS["SDL_INI_PATH"] .. "\"'",
+ }
+end
+
+if _OPTIONS["NO_X11"]=="1" then
+ defines {
+ "SDLMAME_NO_X11",
+ }
+else
+ defines {
+ "SDLMAME_X11",
+ }
+ includedirs {
+ "/usr/X11/include",
+ "/usr/X11R6/include",
+ "/usr/openwin/include",
+ }
+end
+
+if _OPTIONS["NO_USE_XINPUT"]=="1" then
+ defines {
+ "USE_XINPUT=0",
+ }
+else
+ defines {
+ "USE_XINPUT=1",
+ "USE_XINPUT_DEBUG=0",
+ }
+end
+
+if _OPTIONS["NO_USE_XINPUT_WII_LIGHTGUN_HACK"]=="1" then
+ defines {
+ "USE_XINPUT_WII_LIGHTGUN_HACK=0",
+ }
+else
+ defines {
+ "USE_XINPUT_WII_LIGHTGUN_HACK=1",
+ }
+end
+
+if _OPTIONS["NO_USE_MIDI"]~="1" and _OPTIONS["targetos"]=="linux" then
+ buildoptions {
+ backtick(pkgconfigcmd() .. " --cflags alsa"),
+ }
+end
+
+defines {
+ "SDLMAME_SDL3=1",
+}
+
+defines {
+ "OSD_SDL",
+}
+
+if BASE_TARGETOS=="unix" then
+ defines {
+ "SDLMAME_UNIX",
+ }
+ if _OPTIONS["targetos"]=="macosx" then
+ if _OPTIONS["USE_LIBSDL"]~="1" then
+ buildoptions {
+ "-F" .. _OPTIONS["SDL_FRAMEWORK_PATH"],
+ }
+ else
+ defines {
+ "MACOSX_USE_LIBSDL",
+ }
+ buildoptions {
+ backtick(sdlconfigcmd() .. " --cflags | sed 's:/SDL3::'"),
+ }
+ end
+ else
+ buildoptions {
+ backtick(sdlconfigcmd() .. " --cflags | sed 's:/SDL3::'"),
+ }
+ if _OPTIONS["targetos"]~="asmjs" then
+ buildoptions {
+ backtick(pkgconfigcmd() .. " --cflags fontconfig"),
+ }
+ end
+ end
+end
+
+if _OPTIONS["targetos"]=="windows" then
+ configuration { "mingw* or vs*" }
+ defines {
+ "UNICODE",
+ "_UNICODE",
+ "_WIN32_WINNT=0x0600",
+ "WIN32_LEAN_AND_MEAN",
+ "NOMINMAX",
+ }
+
+ configuration { }
+
+elseif _OPTIONS["targetos"]=="linux" then
+ if _OPTIONS["QT_HOME"]~=nil then
+ buildoptions {
+ "-I" .. backtick(_OPTIONS["QT_HOME"] .. "/bin/qmake -query QT_INSTALL_HEADERS"),
+ }
+ else
+ buildoptions {
+ backtick(pkgconfigcmd() .. " --cflags Qt5Widgets"),
+ }
+ end
+elseif _OPTIONS["targetos"]=="macosx" then
+ defines {
+ "SDLMAME_MACOSX",
+ "SDLMAME_DARWIN",
+ }
+elseif _OPTIONS["targetos"]=="freebsd" then
+ buildoptions {
+ -- /usr/local/include is not considered a system include director on FreeBSD. GL.h resides there and throws warnings
+ "-isystem /usr/local/include",
+ }
+end
+
+configuration { "osx*" }
+ includedirs {
+ MAME_DIR .. "3rdparty/bx/include/compat/osx",
+ }
+
+configuration { "freebsd" }
+ includedirs {
+ MAME_DIR .. "3rdparty/bx/include/compat/freebsd",
+ }
+
+configuration { "netbsd" }
+ includedirs {
+ MAME_DIR .. "3rdparty/bx/include/compat/freebsd",
+ }
+
+configuration { }
+
diff --git a/scripts/src/osd/windows.lua b/scripts/src/osd/windows.lua
index ac300261ce8..f65fae4f513 100644
--- a/scripts/src/osd/windows.lua
+++ b/scripts/src/osd/windows.lua
@@ -28,6 +28,12 @@ function maintargetosdoptions(_target,_subtarget)
}
end
+ if _OPTIONS["USE_SDL3"] == "1" then
+ links {
+ "SDL3.dll",
+ }
+ end
+
links {
"comctl32",
"comdlg32",
@@ -42,10 +48,23 @@ end
newoption {
trigger = "USE_SDL",
- description = "Enable SDL sound output",
+ description = "Enable SDL2 sound output",
+ allowed = {
+ { "0", "Disable SDL2 sound output" },
+ { "1", "Enable SDL2 sound output" },
+ },
+}
+
+if not _OPTIONS["USE_SDL"] then
+ _OPTIONS["USE_SDL"] = "0"
+end
+
+newoption {
+ trigger = "USE_SDL3",
+ description = "Enable SDL3 sound output",
allowed = {
- { "0", "Disable SDL sound output" },
- { "1", "Enable SDL sound output" },
+ { "0", "Disable SDL3 sound output" },
+ { "1", "Enable SDL3 sound output" },
},
}
diff --git a/scripts/src/osd/windows_cfg.lua b/scripts/src/osd/windows_cfg.lua
index 98893440f9f..fd144ae3f3e 100644
--- a/scripts/src/osd/windows_cfg.lua
+++ b/scripts/src/osd/windows_cfg.lua
@@ -58,7 +58,16 @@ if _OPTIONS["USE_SDL"]=="1" then
"USE_SDL_SOUND",
}
else
+ if _OPTIONS["USE_SDL3"]=="1" then
defines {
- "USE_SDL=0",
+ "SDLMAME_SDL3=1",
+ "USE_XINPUT=0",
+ "USE_SDL3=1",
+ "USE_SDL_SOUND",
}
+ else
+ defines {
+ "USE_SDL=0",
+ }
+ end
end
diff --git a/scripts/src/tools.lua b/scripts/src/tools.lua
index e2340724e0c..7d0e85039cd 100644
--- a/scripts/src/tools.lua
+++ b/scripts/src/tools.lua
@@ -794,12 +794,11 @@ if (_OPTIONS["osd"] == "sdl") then
if _OPTIONS["USE_LIBSDL"]~="1" then
configuration { "mingw*"}
links {
- "SDL2main",
- "SDL2",
+ "SDL3",
}
configuration { "vs*" }
links {
- "SDL2",
+ "SDL3",
"imm32",
"version",
}
diff --git a/src/osd/modules/font/font_sdl.cpp b/src/osd/modules/font/font_sdl.cpp
index 514e0cc0784..9767f5d3cf8 100644
--- a/src/osd/modules/font/font_sdl.cpp
+++ b/src/osd/modules/font/font_sdl.cpp
@@ -1,13 +1,13 @@
// license:BSD-3-Clause
// copyright-holders:Olivier Galibert, R. Belmont, Vas Crabb
/*
- * font_sdl.c
+ * font_sdl.cpp
*
*/
#include "font_module.h"
-#if defined(SDLMAME_UNIX) && !defined(SDLMAME_MACOSX) && !defined(SDLMAME_HAIKU) && !defined(SDLMAME_ANDROID)
+#if defined(SDLMAME_UNIX) && !defined(SDLMAME_MACOSX) && !defined(SDLMAME_HAIKU) && !defined(SDLMAME_ANDROID) && !defined(SDLMAME_SDL3)
#include "corestr.h"
#include "emucore.h"
diff --git a/src/osd/modules/font/font_sdl3.cpp b/src/osd/modules/font/font_sdl3.cpp
new file mode 100644
index 00000000000..ff677e85bc2
--- /dev/null
+++ b/src/osd/modules/font/font_sdl3.cpp
@@ -0,0 +1,389 @@
+// license:BSD-3-Clause
+// copyright-holders:Olivier Galibert, R. Belmont, Vas Crabb
+/*
+ * font_sdl3.cpp
+ *
+ */
+
+#include "font_module.h"
+
+#if defined(SDLMAME_UNIX) && !defined(SDLMAME_MACOSX) && !defined(SDLMAME_HAIKU) && !defined(SDLMAME_ANDROID) && defined(SDLMAME_SDL3)
+
+#include "corestr.h"
+#include "emucore.h"
+#include "fileio.h"
+#include "unicode.h"
+#include "osdcore.h"
+
+#include <SDL3_ttf/SDL_ttf.h>
+
+#if !defined(SDLMAME_HAIKU) && !defined(SDLMAME_EMSCRIPTEN)
+#include <fontconfig/fontconfig.h>
+#endif
+
+
+//-------------------------------------------------
+// font_open - attempt to "open" a handle to the
+// font with the given name
+//-------------------------------------------------
+
+class osd_font_sdl : public osd_font
+{
+public:
+ osd_font_sdl() : m_font(nullptr, &TTF_CloseFont) { }
+ osd_font_sdl(osd_font_sdl &&obj) : m_font(std::move(obj.m_font)) { }
+ virtual ~osd_font_sdl() { close(); }
+
+ virtual bool open(std::string const &font_path, std::string const &name, int &height);
+ virtual void close();
+ virtual bool get_bitmap(char32_t chnum, bitmap_argb32 &bitmap, std::int32_t &width, std::int32_t &xoffs, std::int32_t &yoffs);
+
+ osd_font_sdl & operator=(osd_font_sdl &&obj)
+ {
+ using std::swap;
+ swap(m_font, obj.m_font);
+ return *this;
+ }
+
+private:
+ typedef std::unique_ptr<TTF_Font, void (*)(TTF_Font *)> TTF_Font_ptr;
+
+ osd_font_sdl(osd_font_sdl const &) = delete;
+ osd_font_sdl & operator=(osd_font_sdl const &) = delete;
+
+ static constexpr double POINT_SIZE = 144.0;
+
+#if !defined(SDLMAME_HAIKU) && !defined(SDLMAME_EMSCRIPTEN)
+ TTF_Font_ptr search_font_config(std::string const &family, std::string const &style, bool &bakedstyles);
+#endif
+ bool BDF_Check_Magic(std::string const &name);
+ TTF_Font_ptr TTF_OpenFont_Magic(std::string const &name, int fsize, long index);
+
+ TTF_Font_ptr m_font;
+};
+
+bool osd_font_sdl::open(std::string const &font_path, std::string const &_name, int &height)
+{
+ bool bakedstyles = false;
+
+ std::string name(_name);
+ if (name.compare("default") == 0)
+ {
+ name = "Liberation Sans|Regular";
+ }
+
+ // accept qualifiers from the name
+ bool const underline = (strreplace(name, "[U]", "") + strreplace(name, "[u]", "") > 0);
+ bool const strike = (strreplace(name, "[S]", "") + strreplace(name, "[s]", "") > 0);
+ std::string::size_type const separator = name.rfind('|');
+ std::string const family(name.substr(0, separator));
+ std::string const style((std::string::npos != separator) ? name.substr(separator + 1) : std::string());
+
+ // first up, try it as a filename
+ TTF_Font_ptr font = TTF_OpenFont_Magic(family, POINT_SIZE, 0);
+
+ // if no success, try the font path
+ if (!font)
+ {
+ osd_printf_verbose("Searching font %s in -%s path/s\n", family, font_path);
+ //emu_file file(options().font_path(), OPEN_FLAG_READ);
+ emu_file file(font_path, OPEN_FLAG_READ);
+ if (!file.open(family))
+ {
+ std::string full_name = file.fullpath();
+ font = TTF_OpenFont_Magic(full_name, POINT_SIZE, 0);
+ if (font)
+ osd_printf_verbose("Found font %s\n", full_name);
+ }
+ }
+
+ // if that didn't work, crank up the FontConfig database
+#if !defined(SDLMAME_HAIKU) && !defined(SDLMAME_EMSCRIPTEN)
+ if (!font)
+ {
+ font = search_font_config(family, style, bakedstyles);
+ }
+#endif
+
+ if (!font)
+ {
+ if (!BDF_Check_Magic(name))
+ {
+ osd_printf_verbose("font %s is not TrueType or BDF, using MAME default\n", name);
+ }
+ return false;
+ }
+
+ // apply styles
+ int styleflags = 0;
+ if (!bakedstyles)
+ {
+ if ((style.find("Bold") != std::string::npos) || (style.find("Black") != std::string::npos)) styleflags |= TTF_STYLE_BOLD;
+ if ((style.find("Italic") != std::string::npos) || (style.find("Oblique") != std::string::npos)) styleflags |= TTF_STYLE_ITALIC;
+ }
+ styleflags |= underline ? TTF_STYLE_UNDERLINE : 0;
+ // SDL_ttf 2.0.9 and earlier does not define TTF_STYLE_STRIKETHROUGH
+#if SDL_VERSIONNUM(SDL_TTF_MAJOR_VERSION, SDL_TTF_MINOR_VERSION, SDL_TTF_MICRO_VERSION) > SDL_VERSIONNUM(2,0,9)
+ styleflags |= strike ? TTF_STYLE_STRIKETHROUGH : 0;
+#else
+ if (strike)
+ osd_printf_warning("Ignoring strikethrough for SDL_TTF older than 2.0.10\n");
+#endif // PATCHLEVEL
+ TTF_SetFontStyle(font.get(), styleflags);
+
+ height = TTF_GetFontLineSkip(font.get());
+
+ m_font = std::move(font);
+ return true;
+}
+
+//-------------------------------------------------
+// font_close - release resources associated with
+// a given OSD font
+//-------------------------------------------------
+
+void osd_font_sdl::close()
+{
+ m_font.reset();
+}
+
+//-------------------------------------------------
+// font_get_bitmap - allocate and populate a
+// BITMAP_FORMAT_ARGB32 bitmap containing the
+// pixel values rgb_t(0xff,0xff,0xff,0xff)
+// or rgb_t(0x00,0xff,0xff,0xff) for each
+// pixel of a black & white font
+//-------------------------------------------------
+
+bool osd_font_sdl::get_bitmap(char32_t chnum, bitmap_argb32 &bitmap, std::int32_t &width, std::int32_t &xoffs, std::int32_t &yoffs)
+{
+ SDL_Color const fcol = { 0xff, 0xff, 0xff };
+ char ustr[16];
+ ustr[utf8_from_uchar(ustr, std::size(ustr), chnum)] = '\0';
+ std::unique_ptr<SDL_Surface, void (*)(SDL_Surface *)> const drawsurf(TTF_RenderText_Solid(m_font.get(), ustr, 0, fcol), &SDL_DestroySurface);
+
+ // was nothing returned?
+ if (drawsurf)
+ {
+ // allocate a MAME destination bitmap
+ bitmap.allocate(drawsurf->w, drawsurf->h);
+
+ // copy the rendered character image into it
+ for (int y = 0; y < bitmap.height(); y++)
+ {
+ std::uint32_t *const dstrow = &bitmap.pix(y);
+ std::uint8_t const *const srcrow = reinterpret_cast<std::uint8_t const *>(drawsurf->pixels) + (y * drawsurf->pitch);
+
+ for (int x = 0; x < drawsurf->w; x++)
+ {
+ dstrow[x] = srcrow[x] ? rgb_t(0xff, 0xff, 0xff, 0xff) : rgb_t(0x00, 0xff, 0xff, 0xff);
+ }
+ }
+
+ // what are these?
+ xoffs = yoffs = 0;
+ width = drawsurf->w;
+ }
+
+ return bitmap.valid();
+}
+
+osd_font_sdl::TTF_Font_ptr osd_font_sdl::TTF_OpenFont_Magic(std::string const &name, int fsize, long index)
+{
+ emu_file file(OPEN_FLAG_READ);
+ if (!file.open(name))
+ {
+ unsigned char const ttf_magic[] = { 0x00, 0x01, 0x00, 0x00, 0x00 };
+ unsigned char const ttc1_magic[] = { 0x74, 0x74, 0x63, 0x66, 0x00, 0x01, 0x00, 0x00 };
+ unsigned char const ttc2_magic[] = { 0x74, 0x74, 0x63, 0x66, 0x00, 0x02, 0x00, 0x00 };
+ unsigned char buffer[std::max({ sizeof(ttf_magic), sizeof(ttc1_magic), sizeof(ttc2_magic) })];
+ auto const bytes_read = file.read(buffer, std::size(buffer));
+ file.close();
+
+ if (((bytes_read >= sizeof(ttf_magic)) && !std::memcmp(buffer, ttf_magic, sizeof(ttf_magic))) ||
+ ((bytes_read >= sizeof(ttc1_magic)) && !std::memcmp(buffer, ttc1_magic, sizeof(ttc1_magic))) ||
+ ((bytes_read >= sizeof(ttc2_magic)) && !std::memcmp(buffer, ttc2_magic, sizeof(ttc2_magic))))
+ {
+ SDL_PropertiesID props = SDL_CreateProperties();
+
+ SDL_SetStringProperty(props, TTF_PROP_FONT_CREATE_FILENAME_STRING, name.c_str());
+ SDL_SetNumberProperty(props, TTF_PROP_FONT_CREATE_FACE_NUMBER, index);
+ SDL_SetFloatProperty(props, TTF_PROP_FONT_CREATE_SIZE_FLOAT, POINT_SIZE);
+
+ return TTF_Font_ptr(TTF_OpenFontWithProperties(props), &TTF_CloseFont);
+ }
+ }
+ return TTF_Font_ptr(nullptr, &TTF_CloseFont);
+}
+
+bool osd_font_sdl::BDF_Check_Magic(std::string const &name)
+{
+ emu_file file(OPEN_FLAG_READ);
+ if (!file.open(name))
+ {
+ unsigned char const magic[] = { 'S', 'T', 'A', 'R', 'T', 'F', 'O', 'N', 'T' };
+ unsigned char buffer[sizeof(magic)];
+ if ((sizeof(magic) != file.read(buffer, sizeof(magic))) || memcmp(buffer, magic, sizeof(magic)))
+ return true;
+ }
+ return false;
+}
+
+#if !defined(SDLMAME_HAIKU) && !defined(SDLMAME_EMSCRIPTEN)
+osd_font_sdl::TTF_Font_ptr osd_font_sdl::search_font_config(std::string const &family, std::string const &style, bool &bakedstyles)
+{
+ TTF_Font_ptr font(nullptr, &TTF_CloseFont);
+
+ FcConfig *const config = FcConfigGetCurrent();
+ std::unique_ptr<FcPattern, void (*)(FcPattern *)> pat(FcPatternCreate(), &FcPatternDestroy);
+ std::unique_ptr<FcObjectSet, void (*)(FcObjectSet *)> os(FcObjectSetCreate(), &FcObjectSetDestroy);
+ FcPatternAddString(pat.get(), FC_FAMILY, (const FcChar8 *)family.c_str());
+
+ // try and get a font with the requested styles baked-in
+ if (!style.empty())
+ FcPatternAddString(pat.get(), FC_STYLE, (const FcChar8 *)style.c_str());
+
+ FcPatternAddString(pat.get(), FC_FONTFORMAT, (const FcChar8 *)"TrueType");
+
+ FcObjectSetAdd(os.get(), FC_FILE);
+ FcObjectSetAdd(os.get(), FC_INDEX);
+ std::unique_ptr<FcFontSet, void (*)(FcFontSet *)> fontset(FcFontList(config, pat.get(), os.get()), &FcFontSetDestroy);
+
+ for (int i = 0; (i < fontset->nfont) && !font; i++)
+ {
+ FcValue val;
+ if ((FcPatternGet(fontset->fonts[i], FC_FILE, 0, &val) == FcResultMatch) && (val.type == FcTypeString))
+ {
+ osd_printf_verbose("Matching font: %s\n", val.u.s);
+
+ std::string const match_name((const char*)val.u.s);
+ long const index = ((FcPatternGet(fontset->fonts[i], FC_INDEX, 0, &val) == FcResultMatch) && (val.type == FcTypeInteger)) ? val.u.i : 0;
+ font = TTF_OpenFont_Magic(match_name, POINT_SIZE, index);
+
+ if (font)
+ bakedstyles = true;
+ }
+ }
+
+ // didn't get a font above? try again with no baked-in styles
+ // note that this simply returns the first match for the family name, which could be regular if you're lucky, but it could be bold oblique or something
+ if (!font && !style.empty())
+ {
+ pat.reset(FcPatternCreate());
+ FcPatternAddString(pat.get(), FC_FAMILY, (const FcChar8 *)family.c_str());
+ FcPatternAddString(pat.get(), FC_FONTFORMAT, (const FcChar8 *)"TrueType");
+ fontset.reset(FcFontList(config, pat.get(), os.get()));
+
+ for (int i = 0; (i < fontset->nfont) && !font; i++)
+ {
+ FcValue val;
+ if ((FcPatternGet(fontset->fonts[i], FC_FILE, 0, &val) == FcResultMatch) && (val.type == FcTypeString))
+ {
+ osd_printf_verbose("Matching unstyled font: %s\n", val.u.s);
+
+ std::string const match_name((const char*)val.u.s);
+ long const index = ((FcPatternGet(fontset->fonts[i], FC_INDEX, 0, &val) == FcResultMatch) && (val.type == FcTypeInteger)) ? val.u.i : 0;
+ font = TTF_OpenFont_Magic(match_name, POINT_SIZE, index);
+
+ if (font)
+ bakedstyles = false;
+ }
+ }
+ }
+
+ return font;
+}
+#endif
+
+
+class font_sdl : public osd_module, public font_module
+{
+public:
+ font_sdl() : osd_module(OSD_FONT_PROVIDER, "sdl"), font_module()
+ {
+ }
+
+ osd_font::ptr font_alloc() override
+ {
+ return std::make_unique<osd_font_sdl>();
+ }
+
+ virtual int init(osd_interface &osd, const osd_options &options) override
+ {
+ if (!TTF_Init())
+ {
+ osd_printf_error("SDL_ttf failed: %s\n", SDL_GetError());
+ return -1;
+ }
+ return 0;
+ }
+
+ virtual void exit() override
+ {
+ TTF_Quit();
+ }
+
+ virtual bool get_font_families(std::string const &font_path, std::vector<std::pair<std::string, std::string> > &result) override;
+};
+
+
+bool font_sdl::get_font_families(std::string const &font_path, std::vector<std::pair<std::string, std::string> > &result)
+{
+ result.clear();
+
+ // TODO: enumerate TTF files in font path, since we can load them, too
+
+#if !defined(SDLMAME_HAIKU) && !defined(SDLMAME_EMSCRIPTEN)
+ FcConfig *const config = FcConfigGetCurrent();
+ std::unique_ptr<FcPattern, void (*)(FcPattern *)> pat(FcPatternCreate(), &FcPatternDestroy);
+ FcPatternAddString(pat.get(), FC_FONTFORMAT, (const FcChar8 *)"TrueType");
+
+ std::unique_ptr<FcObjectSet, void (*)(FcObjectSet *)> os(FcObjectSetCreate(), &FcObjectSetDestroy);
+ FcObjectSetAdd(os.get(), FC_FAMILY);
+ FcObjectSetAdd(os.get(), FC_FILE);
+ FcObjectSetAdd(os.get(), FC_STYLE);
+
+ std::unique_ptr<FcFontSet, void (*)(FcFontSet *)> fontset(FcFontList(config, pat.get(), os.get()), &FcFontSetDestroy);
+ for (int i = 0; (i < fontset->nfont); i++)
+ {
+ FcValue val;
+ if ((FcPatternGet(fontset->fonts[i], FC_FILE, 0, &val) == FcResultMatch) &&
+ (val.type == FcTypeString) &&
+ (FcPatternGet(fontset->fonts[i], FC_FAMILY, 0, &val) == FcResultMatch) &&
+ (val.type == FcTypeString))
+ {
+ auto const compare_fonts = [](std::pair<std::string, std::string> const &a, std::pair<std::string, std::string> const &b) -> bool
+ {
+ int const second = core_stricmp(a.second, b.second);
+ if (second < 0) return true;
+ else if (second > 0) return false;
+ else return core_stricmp(b.first, b.first) < 0;
+ };
+ std::string config((const char *)val.u.s);
+ std::string display(config);
+ if ((FcPatternGet(fontset->fonts[i], FC_STYLE, 0, &val) == FcResultMatch) && (val.type == FcTypeString))
+ {
+ config.push_back('|');
+ config.append((const char *)val.u.s);
+ display.push_back(' ');
+ display.append((const char *)val.u.s);
+ }
+ std::pair<std::string, std::string> font(std::move(config), std::move(display));
+ auto const pos = std::lower_bound(result.begin(), result.end(), font, compare_fonts);
+ if ((result.end() == pos) || (pos->first != font.first)) result.emplace(pos, std::move(font));
+ }
+ }
+
+ return true;
+#else
+ return false;
+#endif
+}
+
+#else /* SDLMAME_UNIX */
+
+MODULE_NOT_SUPPORTED(font_sdl, OSD_FONT_PROVIDER, "sdl")
+
+#endif
+
+MODULE_DEFINITION(FONT_SDL, font_sdl)
diff --git a/src/osd/modules/input/input_common.cpp b/src/osd/modules/input/input_common.cpp
index ddcd3805640..66e9fb85eb0 100644
--- a/src/osd/modules/input/input_common.cpp
+++ b/src/osd/modules/input/input_common.cpp
@@ -28,7 +28,11 @@
#endif
#if defined(OSD_SDL) || defined(SDLMAME_WIN32)
+#ifdef SDLMAME_SDL3
+#include <SDL3/SDL.h>
+#else
#include <SDL2/SDL.h>
+#endif
#define KEY_TRANS_SDL(sdlsc) SDL_SCANCODE_##sdlsc,
#else
#define KEY_TRANS_SDL(sdlsc)
@@ -173,10 +177,17 @@ key_trans_entry keyboard_trans_table::s_default_table[] =
KEY_TRANS_ENTRY0(OTHER_SWITCH, F22, UNKNOWN, VK_F22, 0, "F22"),
KEY_TRANS_ENTRY0(OTHER_SWITCH, F23, UNKNOWN, VK_F23, 0, "F23"),
KEY_TRANS_ENTRY0(OTHER_SWITCH, F24, UNKNOWN, VK_F24, 0, "F24"),
+#ifdef SDLMAME_SDL3
+ KEY_TRANS_ENTRY0(OTHER_SWITCH, MEDIA_NEXT_TRACK, NEXTTRACK, VK_MEDIA_NEXT_TRACK, 0, "AUDIONEXT"),
+ KEY_TRANS_ENTRY0(OTHER_SWITCH, MUTE, MUTE, VK_VOLUME_MUTE, 0, "VOLUMEMUTE"),
+ KEY_TRANS_ENTRY0(OTHER_SWITCH, MEDIA_PLAY, PLAYPAUSE, VK_MEDIA_PLAY_PAUSE, 0, "AUDIOPLAY"),
+ KEY_TRANS_ENTRY0(OTHER_SWITCH, MEDIA_STOP, MEDIASTOP, VK_MEDIA_STOP, 0, "AUDIOSTOP"),
+#else
KEY_TRANS_ENTRY0(OTHER_SWITCH, AUDIONEXT, NEXTTRACK, VK_MEDIA_NEXT_TRACK, 0, "AUDIONEXT"),
KEY_TRANS_ENTRY0(OTHER_SWITCH, AUDIOMUTE, MUTE, VK_VOLUME_MUTE, 0, "VOLUMEMUTE"),
KEY_TRANS_ENTRY0(OTHER_SWITCH, AUDIOPLAY, PLAYPAUSE, VK_MEDIA_PLAY_PAUSE, 0, "AUDIOPLAY"),
KEY_TRANS_ENTRY0(OTHER_SWITCH, AUDIOSTOP, MEDIASTOP, VK_MEDIA_STOP, 0, "AUDIOSTOP"),
+#endif
KEY_TRANS_ENTRY0(OTHER_SWITCH, VOLUMEDOWN, VOLUMEDOWN, VK_VOLUME_DOWN, 0, "VOLUMEDOWN"),
KEY_TRANS_ENTRY0(OTHER_SWITCH, VOLUMEUP, VOLUMEUP, VK_VOLUME_UP, 0, "VOLUMEUP"),
KEY_TRANS_ENTRY0(OTHER_SWITCH, AC_HOME, WEBHOME, VK_BROWSER_HOME, 0, "NAVHOME"),
@@ -186,8 +197,12 @@ key_trans_entry keyboard_trans_table::s_default_table[] =
KEY_TRANS_ENTRY0(OTHER_SWITCH, AC_STOP, WEBSTOP, VK_BROWSER_STOP, 0, "NAVSTOP"),
KEY_TRANS_ENTRY0(OTHER_SWITCH, AC_FORWARD, WEBFORWARD, VK_BROWSER_FORWARD, 0, "NAVFORWARD"),
KEY_TRANS_ENTRY0(OTHER_SWITCH, AC_BACK, WEBBACK, VK_BROWSER_BACK, 0, "NAVBACK"),
+#ifdef SDLMAME_SDL3
+ KEY_TRANS_ENTRY0(OTHER_SWITCH, MEDIA_SELECT, MEDIASELECT, VK_LAUNCH_MEDIA_SELECT, 0, "MEDIASEL"),
+#else
KEY_TRANS_ENTRY0(OTHER_SWITCH, MAIL, MAIL, VK_LAUNCH_MAIL, 0, "MAIL"),
KEY_TRANS_ENTRY0(OTHER_SWITCH, MEDIASELECT, MEDIASELECT, VK_LAUNCH_MEDIA_SELECT, 0, "MEDIASEL"),
+#endif
// sentinel
KEY_TRANS_ENTRY0(INVALID, UNKNOWN, UNKNOWN, 0, 0, "INVALID")
diff --git a/src/osd/modules/input/input_dinput.cpp b/src/osd/modules/input/input_dinput.cpp
index 6f539c2f7fc..900aa4b8eec 100644
--- a/src/osd/modules/input/input_dinput.cpp
+++ b/src/osd/modules/input/input_dinput.cpp
@@ -107,9 +107,13 @@ Rz Rudder
#include "util/corestr.h"
#ifdef SDLMAME_WIN32
+#ifdef SDLMAME_SDL3
+#include <SDL3/SDL.h>
+#else
#include <SDL2/SDL.h>
#include <SDL2/SDL_syswm.h>
#endif
+#endif
#include <algorithm>
#include <cmath>
diff --git a/src/osd/modules/input/input_sdl.cpp b/src/osd/modules/input/input_sdl.cpp
index f1fb0241b40..2c1772ae42b 100644
--- a/src/osd/modules/input/input_sdl.cpp
+++ b/src/osd/modules/input/input_sdl.cpp
@@ -15,7 +15,7 @@
#include "modules/osdmodule.h"
-#if defined(OSD_SDL)
+#if defined(OSD_SDL) && !defined(SDLMAME_SDL3)
#include "assignmenthelper.h"
#include "input_common.h"
@@ -2872,9 +2872,10 @@ MODULE_NOT_SUPPORTED(sdl_game_controller_module, OSD_JOYSTICKINPUT_PROVIDER, "sd
#endif // defined(SDLMAME_SDL2)
-
+#ifdef SDLMAME_SDL2
MODULE_DEFINITION(KEYBOARDINPUT_SDL, osd::sdl_keyboard_module)
MODULE_DEFINITION(MOUSEINPUT_SDL, osd::sdl_mouse_module)
MODULE_DEFINITION(LIGHTGUNINPUT_SDL, osd::sdl_lightgun_module)
MODULE_DEFINITION(JOYSTICKINPUT_SDLJOY, osd::sdl_joystick_module)
MODULE_DEFINITION(JOYSTICKINPUT_SDLGAME, osd::sdl_game_controller_module)
+#endif
diff --git a/src/osd/modules/input/input_sdl3.cpp b/src/osd/modules/input/input_sdl3.cpp
new file mode 100644
index 00000000000..70658068fa7
--- /dev/null
+++ b/src/osd/modules/input/input_sdl3.cpp
@@ -0,0 +1,2944 @@
+// license:BSD-3-Clause
+// copyright-holders:Olivier Galibert, R. Belmont, Brad Hughes, Vas Crabb
+//============================================================
+//
+// input_sdl.cpp - SDL 3 implementation of MAME input routines
+//
+// SDLMAME by Olivier Galibert and R. Belmont
+//
+// SixAxis info: left analog is axes 0 & 1, right analog is axes 2 & 3,
+// analog L2 is axis 12 and analog L3 is axis 13
+//
+//============================================================
+
+#include "input_module.h"
+
+#include "modules/osdmodule.h"
+
+#if defined(OSD_SDL) && defined(SDLMAME_SDL3)
+
+#include "assignmenthelper.h"
+#include "input_common.h"
+
+#include "interface/inputseq.h"
+#include "modules/lib/osdobj_common.h"
+#include "osdsdl.h"
+// emu
+#include "inpttype.h"
+
+// standard SDL header
+#include <SDL3/SDL.h>
+
+#include <algorithm>
+#include <cctype>
+#include <chrono>
+#include <cmath>
+#include <cstddef>
+#include <cstring>
+#include <initializer_list>
+#include <iterator>
+#include <list>
+#include <memory>
+#include <optional>
+#include <string>
+#include <string_view>
+#include <tuple>
+#include <utility>
+
+// winnt.h defines this
+#ifdef DELETE
+#undef DELETE
+#endif
+
+
+namespace osd {
+
+namespace {
+
+char const *const CONTROLLER_AXIS_XBOX[]{
+ "LSX",
+ "LSY",
+ "RSX",
+ "RSY",
+ "LT",
+ "RT" };
+
+char const *const CONTROLLER_AXIS_PS[]{
+ "LSX",
+ "LSY",
+ "RSX",
+ "RSY",
+ "L2",
+ "R2" };
+
+char const *const CONTROLLER_AXIS_SWITCH[]{
+ "LSX",
+ "LSY",
+ "RSX",
+ "RSY",
+ "ZL",
+ "ZR" };
+
+char const *const CONTROLLER_BUTTON_XBOX360[]{
+ "A",
+ "B",
+ "X",
+ "Y",
+ "Back",
+ "Guide",
+ "Start",
+ "LSB",
+ "RSB",
+ "LB",
+ "RB",
+ "D-pad Up",
+ "D-pad Down",
+ "D-pad Left",
+ "D-pad Right",
+ "Share",
+ "P1",
+ "P2",
+ "P3",
+ "P4",
+ "Touchpad" };
+
+char const *const CONTROLLER_BUTTON_XBOXONE[]{
+ "A",
+ "B",
+ "X",
+ "Y",
+ "View",
+ "Logo",
+ "Menu",
+ "LSB",
+ "RSB",
+ "LB",
+ "RB",
+ "D-pad Up",
+ "D-pad Down",
+ "D-pad Left",
+ "D-pad Right",
+ "Share",
+ "P1",
+ "P2",
+ "P3",
+ "P4",
+ "Touchpad" };
+
+char const *const CONTROLLER_BUTTON_PS3[]{
+ "Cross",
+ "Circle",
+ "Square",
+ "Triangle",
+ "Select",
+ "PS",
+ "Start",
+ "L3",
+ "R3",
+ "L1",
+ "R1",
+ "D-pad Up",
+ "D-pad Down",
+ "D-pad Left",
+ "D-pad Right",
+ "Mute",
+ "P1",
+ "P2",
+ "P3",
+ "P4",
+ "Touchpad" };
+
+char const *const CONTROLLER_BUTTON_PS4[]{
+ "Cross",
+ "Circle",
+ "Square",
+ "Triangle",
+ "Share",
+ "PS",
+ "Options",
+ "L3",
+ "R3",
+ "L1",
+ "R1",
+ "D-pad Up",
+ "D-pad Down",
+ "D-pad Left",
+ "D-pad Right",
+ "Mute",
+ "P1",
+ "P2",
+ "P3",
+ "P4",
+ "Touchpad" };
+
+char const *const CONTROLLER_BUTTON_PS5[]{
+ "Cross",
+ "Circle",
+ "Square",
+ "Triangle",
+ "Create",
+ "PS",
+ "Options",
+ "L3",
+ "R3",
+ "L1",
+ "R1",
+ "D-pad Up",
+ "D-pad Down",
+ "D-pad Left",
+ "D-pad Right",
+ "Mute",
+ "P1",
+ "P2",
+ "P3",
+ "P4",
+ "Touchpad" };
+
+char const *const CONTROLLER_BUTTON_SWITCH[]{
+ "A",
+ "B",
+ "X",
+ "Y",
+ "-",
+ "Home",
+ "+",
+ "LSB",
+ "RSB",
+ "L",
+ "R",
+ "D-pad Up",
+ "D-pad Down",
+ "D-pad Left",
+ "D-pad Right",
+ "Capture",
+ "RSR",
+ "LSL",
+ "RSL",
+ "LSR",
+ "Touchpad" };
+
+[[maybe_unused]] char const *const CONTROLLER_BUTTON_STADIA[]{
+ "A",
+ "B",
+ "X",
+ "Y",
+ "Options",
+ "Logo",
+ "Menu",
+ "L3",
+ "R3",
+ "L1",
+ "R1",
+ "D-pad Up",
+ "D-pad Down",
+ "D-pad Left",
+ "D-pad Right",
+ "Capture",
+ "P1",
+ "P2",
+ "P3",
+ "P4",
+ "Touchpad" };
+
+[[maybe_unused]] char const *const CONTROLLER_BUTTON_SHIELD[]{
+ "A",
+ "B",
+ "X",
+ "Y",
+ "Back",
+ "Logo",
+ "Start",
+ "LSB",
+ "RSB",
+ "LB",
+ "RB",
+ "D-pad Up",
+ "D-pad Down",
+ "D-pad Left",
+ "D-pad Right",
+ "Share",
+ "P1",
+ "P2",
+ "P3",
+ "P4",
+ "Touchpad" };
+
+struct key_lookup_table
+{
+ int code;
+ const char *name;
+};
+
+#define KE(x) { SDL_SCANCODE_ ## x, "SDL_SCANCODE_" #x },
+
+key_lookup_table const sdl_lookup_table[] =
+{
+ KE(UNKNOWN)
+
+ KE(A)
+ KE(B)
+ KE(C)
+ KE(D)
+ KE(E)
+ KE(F)
+ KE(G)
+ KE(H)
+ KE(I)
+ KE(J)
+ KE(K)
+ KE(L)
+ KE(M)
+ KE(N)
+ KE(O)
+ KE(P)
+ KE(Q)
+ KE(R)
+ KE(S)
+ KE(T)
+ KE(U)
+ KE(V)
+ KE(W)
+ KE(X)
+ KE(Y)
+ KE(Z)
+
+ KE(1)
+ KE(2)
+ KE(3)
+ KE(4)
+ KE(5)
+ KE(6)
+ KE(7)
+ KE(8)
+ KE(9)
+ KE(0)
+
+ KE(RETURN)
+ KE(ESCAPE)
+ KE(BACKSPACE)
+ KE(TAB)
+ KE(SPACE)
+
+ KE(MINUS)
+ KE(EQUALS)
+ KE(LEFTBRACKET)
+ KE(RIGHTBRACKET)
+ KE(BACKSLASH)
+ KE(NONUSHASH)
+ KE(SEMICOLON)
+ KE(APOSTROPHE)
+ KE(GRAVE)
+ KE(COMMA)
+ KE(PERIOD)
+ KE(SLASH)
+
+ KE(CAPSLOCK)
+
+ KE(F1)
+ KE(F2)
+ KE(F3)
+ KE(F4)
+ KE(F5)
+ KE(F6)
+ KE(F7)
+ KE(F8)
+ KE(F9)
+ KE(F10)
+ KE(F11)
+ KE(F12)
+
+ KE(PRINTSCREEN)
+ KE(SCROLLLOCK)
+ KE(PAUSE)
+ KE(INSERT)
+ KE(HOME)
+ KE(PAGEUP)
+ KE(DELETE)
+ KE(END)
+ KE(PAGEDOWN)
+ KE(RIGHT)
+ KE(LEFT)
+ KE(DOWN)
+ KE(UP)
+
+ KE(NUMLOCKCLEAR)
+ KE(KP_DIVIDE)
+ KE(KP_MULTIPLY)
+ KE(KP_MINUS)
+ KE(KP_PLUS)
+ KE(KP_ENTER)
+ KE(KP_1)
+ KE(KP_2)
+ KE(KP_3)
+ KE(KP_4)
+ KE(KP_5)
+ KE(KP_6)
+ KE(KP_7)
+ KE(KP_8)
+ KE(KP_9)
+ KE(KP_0)
+ KE(KP_PERIOD)
+
+ KE(NONUSBACKSLASH)
+ KE(APPLICATION)
+ KE(POWER)
+ KE(KP_EQUALS)
+ KE(F13)
+ KE(F14)
+ KE(F15)
+ KE(F16)
+ KE(F17)
+ KE(F18)
+ KE(F19)
+ KE(F20)
+ KE(F21)
+ KE(F22)
+ KE(F23)
+ KE(F24)
+ KE(EXECUTE)
+ KE(HELP)
+ KE(MENU)
+ KE(SELECT)
+ KE(STOP)
+ KE(AGAIN)
+ KE(UNDO)
+ KE(CUT)
+ KE(COPY)
+ KE(PASTE)
+ KE(FIND)
+ KE(MUTE)
+ KE(VOLUMEUP)
+ KE(VOLUMEDOWN)
+ KE(KP_COMMA)
+ KE(KP_EQUALSAS400)
+
+ KE(INTERNATIONAL1)
+ KE(INTERNATIONAL2)
+ KE(INTERNATIONAL3)
+ KE(INTERNATIONAL4)
+ KE(INTERNATIONAL5)
+ KE(INTERNATIONAL6)
+ KE(INTERNATIONAL7)
+ KE(INTERNATIONAL8)
+ KE(INTERNATIONAL9)
+ KE(LANG1)
+ KE(LANG2)
+ KE(LANG3)
+ KE(LANG4)
+ KE(LANG5)
+ KE(LANG6)
+ KE(LANG7)
+ KE(LANG8)
+ KE(LANG9)
+
+ KE(ALTERASE)
+ KE(SYSREQ)
+ KE(CANCEL)
+ KE(CLEAR)
+ KE(PRIOR)
+ KE(RETURN2)
+ KE(SEPARATOR)
+ KE(OUT)
+ KE(OPER)
+ KE(CLEARAGAIN)
+ KE(CRSEL)
+ KE(EXSEL)
+
+ KE(KP_00)
+ KE(KP_000)
+ KE(THOUSANDSSEPARATOR)
+ KE(DECIMALSEPARATOR)
+ KE(CURRENCYUNIT)
+ KE(CURRENCYSUBUNIT)
+ KE(KP_LEFTPAREN)
+ KE(KP_RIGHTPAREN)
+ KE(KP_LEFTBRACE)
+ KE(KP_RIGHTBRACE)
+ KE(KP_TAB)
+ KE(KP_BACKSPACE)
+ KE(KP_A)
+ KE(KP_B)
+ KE(KP_C)
+ KE(KP_D)
+ KE(KP_E)
+ KE(KP_F)
+ KE(KP_XOR)
+ KE(KP_POWER)
+ KE(KP_PERCENT)
+ KE(KP_LESS)
+ KE(KP_GREATER)
+ KE(KP_AMPERSAND)
+ KE(KP_DBLAMPERSAND)
+ KE(KP_VERTICALBAR)
+ KE(KP_DBLVERTICALBAR)
+ KE(KP_COLON)
+ KE(KP_HASH)
+ KE(KP_SPACE)
+ KE(KP_AT)
+ KE(KP_EXCLAM)
+ KE(KP_MEMSTORE)
+ KE(KP_MEMRECALL)
+ KE(KP_MEMCLEAR)
+ KE(KP_MEMADD)
+ KE(KP_MEMSUBTRACT)
+ KE(KP_MEMMULTIPLY)
+ KE(KP_MEMDIVIDE)
+ KE(KP_PLUSMINUS)
+ KE(KP_CLEAR)
+ KE(KP_CLEARENTRY)
+ KE(KP_BINARY)
+ KE(KP_OCTAL)
+ KE(KP_DECIMAL)
+ KE(KP_HEXADECIMAL)
+
+ KE(LCTRL)
+ KE(LSHIFT)
+ KE(LALT)
+ KE(LGUI)
+ KE(RCTRL)
+ KE(RSHIFT)
+ KE(RALT)
+ KE(RGUI)
+
+ KE(MODE)
+ KE(MEDIA_NEXT_TRACK)
+ KE(MEDIA_PREVIOUS_TRACK)
+ KE(MEDIA_STOP)
+ KE(MEDIA_PLAY)
+ KE(MUTE)
+ KE(MEDIA_SELECT)
+ KE(AC_SEARCH)
+ KE(AC_HOME)
+ KE(AC_BACK)
+ KE(AC_FORWARD)
+ KE(AC_STOP)
+ KE(AC_REFRESH)
+ KE(AC_BOOKMARKS)
+
+ KE(MEDIA_EJECT)
+ KE(SLEEP)
+};
+
+
+//============================================================
+// lookup_sdl_code
+//============================================================
+
+int lookup_sdl_code(std::string_view scode)
+{
+ auto const found = std::find_if(
+ std::begin(sdl_lookup_table),
+ std::end(sdl_lookup_table),
+ [&scode] (auto const &key) { return scode == key.name; });
+ return (std::end(sdl_lookup_table) != found) ? found->code : -1;
+}
+
+
+//============================================================
+// sdl_device
+//============================================================
+
+using sdl_device = event_based_device<SDL_Event>;
+
+
+//============================================================
+// sdl_keyboard_device
+//============================================================
+
+class sdl_keyboard_device : public sdl_device
+{
+public:
+ sdl_keyboard_device(
+ std::string &&name,
+ std::string &&id,
+ input_module &module,
+ SDL_KeyboardID &kbdid,
+ keyboard_trans_table const &trans_table) :
+ sdl_device(std::move(name), std::move(id), module),
+ m_keyboard_id(kbdid),
+ m_trans_table(trans_table),
+ m_keyboard({{0}}),
+ m_capslock_pressed(std::chrono::steady_clock::time_point::min())
+ {
+ }
+
+ virtual void poll(bool relative_reset) override
+ {
+ sdl_device::poll(relative_reset);
+
+#ifdef SDL_PLATFORM_APPLE
+ if (m_keyboard.state[SDL_SCANCODE_CAPSLOCK] && (std::chrono::steady_clock::now() > (m_capslock_pressed + std::chrono::milliseconds(30))))
+ m_keyboard.state[SDL_SCANCODE_CAPSLOCK] = 0x00;
+#endif
+ }
+
+ virtual void process_event(SDL_Event const &event) override
+ {
+ switch (event.type)
+ {
+ case SDL_EVENT_KEY_DOWN:
+ // TODO: when we add proper multi-keyboard support
+ if (1) // event.key.which == m_keyboard_id)
+ {
+ if (event.key.scancode == SDL_SCANCODE_CAPSLOCK)
+ m_capslock_pressed = std::chrono::steady_clock::now();
+
+ m_keyboard.state[event.key.scancode] = 0x80;
+ }
+ break;
+
+ case SDL_EVENT_KEY_UP:
+ if (event.key.which == m_keyboard_id)
+ {
+#ifdef SDL_PLATFORM_APPLE
+ if (event.key.scancode == SDL_SCANCODE_CAPSLOCK)
+ break;
+#endif
+
+ m_keyboard.state[event.key.scancode] = 0x00;
+ }
+ break;
+ }
+ }
+
+ virtual void reset() override
+ {
+ sdl_device::reset();
+ memset(&m_keyboard.state, 0, sizeof(m_keyboard.state));
+ m_capslock_pressed = std::chrono::steady_clock::time_point::min();
+ }
+
+ virtual void configure(input_device &device) override
+ {
+ // populate it
+ for (int keynum = 0; m_trans_table[keynum].mame_key != ITEM_ID_INVALID; keynum++)
+ {
+ input_item_id itemid = m_trans_table[keynum].mame_key;
+ device.add_item(
+ m_trans_table[keynum].ui_name,
+ std::string_view(),
+ itemid,
+ generic_button_get_state<s32>,
+ &m_keyboard.state[m_trans_table[keynum].sdl_scancode]);
+ }
+ }
+
+private:
+ // state information for a keyboard
+ struct keyboard_state
+ {
+ s32 state[0x3ff]; // must be s32!
+ s8 oldkey[MAX_KEYS];
+ s8 currkey[MAX_KEYS];
+ };
+
+ SDL_KeyboardID m_keyboard_id;
+ keyboard_trans_table const &m_trans_table;
+ keyboard_state m_keyboard;
+ std::chrono::steady_clock::time_point m_capslock_pressed;
+};
+
+
+//============================================================
+// sdl_mouse_device_base
+//============================================================
+
+class sdl_mouse_device_base : public sdl_device
+{
+public:
+ virtual void poll(bool relative_reset) override
+ {
+ sdl_device::poll(relative_reset);
+
+ if (relative_reset)
+ {
+ m_mouse.lV = std::exchange(m_v, 0);
+ m_mouse.lH = std::exchange(m_h, 0);
+ }
+ }
+
+ virtual void reset() override
+ {
+ sdl_device::reset();
+ memset(&m_mouse, 0, sizeof(m_mouse));
+ m_v = m_h = 0;
+ }
+
+protected:
+ // state information for a mouse
+ struct mouse_state
+ {
+ s32 lX, lY, lV, lH;
+ s32 buttons[MAX_BUTTONS];
+ };
+
+ sdl_mouse_device_base(std::string &&name, std::string &&id, input_module &module) :
+ sdl_device(std::move(name), std::move(id), module),
+ m_mouse({0}),
+ m_v(0),
+ m_h(0)
+ {
+ }
+
+ void add_common_items(input_device &device, unsigned buttons)
+ {
+ // add horizontal and vertical axes - relative for a mouse or absolute for a gun
+ device.add_item(
+ "X",
+ std::string_view(),
+ ITEM_ID_XAXIS,
+ generic_axis_get_state<s32>,
+ &m_mouse.lX);
+ device.add_item(
+ "Y",
+ std::string_view(),
+ ITEM_ID_YAXIS,
+ generic_axis_get_state<s32>,
+ &m_mouse.lY);
+
+ // add buttons
+ for (int button = 0; button < buttons; button++)
+ {
+ input_item_id itemid = (input_item_id)(ITEM_ID_BUTTON1 + button);
+ int const offset = button ^ (((1 == button) || (2 == button)) ? 3 : 0);
+ device.add_item(
+ default_button_name(button),
+ std::string_view(),
+ itemid,
+ generic_button_get_state<s32>,
+ &m_mouse.buttons[offset]);
+ }
+ }
+
+ mouse_state m_mouse;
+ s32 m_v, m_h;
+};
+
+
+//============================================================
+// sdl_mouse_device
+//============================================================
+
+class sdl_mouse_device : public sdl_mouse_device_base
+{
+public:
+ sdl_mouse_device(std::string &&name, std::string &&id, input_module &module) :
+ sdl_mouse_device_base(std::move(name), std::move(id), module),
+ m_x(0),
+ m_y(0)
+ {
+ }
+
+ virtual void poll(bool relative_reset) override
+ {
+ sdl_mouse_device_base::poll(relative_reset);
+
+ if (relative_reset)
+ {
+ m_mouse.lX = std::exchange(m_x, 0);
+ m_mouse.lY = std::exchange(m_y, 0);
+ }
+ }
+
+ virtual void reset() override
+ {
+ sdl_mouse_device_base::reset();
+ m_x = m_y = 0;
+ }
+
+ virtual void configure(input_device &device) override
+ {
+ add_common_items(device, 5);
+
+ // add scroll axes
+ device.add_item(
+ "Scroll V",
+ std::string_view(),
+ ITEM_ID_ZAXIS,
+ generic_axis_get_state<s32>,
+ &m_mouse.lV);
+ device.add_item(
+ "Scroll H",
+ std::string_view(),
+ ITEM_ID_RZAXIS,
+ generic_axis_get_state<s32>,
+ &m_mouse.lH);
+ }
+
+ virtual void process_event(SDL_Event const &event) override
+ {
+ switch (event.type)
+ {
+ case SDL_EVENT_MOUSE_MOTION:
+ m_x += event.motion.xrel * input_device::RELATIVE_PER_PIXEL;
+ m_y += event.motion.yrel * input_device::RELATIVE_PER_PIXEL;
+ break;
+
+ case SDL_EVENT_MOUSE_BUTTON_DOWN:
+ m_mouse.buttons[event.button.button - 1] = 0x80;
+ break;
+
+ case SDL_EVENT_MOUSE_BUTTON_UP:
+ m_mouse.buttons[event.button.button - 1] = 0;
+ break;
+
+ case SDL_EVENT_MOUSE_WHEEL:
+ // adjust SDL 1-per-click to match Win32 120-per-click
+ m_v += std::lround(event.wheel.integer_y * 120 * input_device::RELATIVE_PER_PIXEL);
+ m_h += std::lround(event.wheel.integer_x * 120 * input_device::RELATIVE_PER_PIXEL);
+ break;
+ }
+ }
+
+private:
+ s32 m_x, m_y;
+};
+
+
+//============================================================
+// sdl_lightgun_device
+//============================================================
+
+class sdl_lightgun_device : public sdl_mouse_device_base
+{
+public:
+ sdl_lightgun_device(std::string &&name, std::string &&id, input_module &module) :
+ sdl_mouse_device_base(std::move(name), std::move(id), module),
+ m_x(0),
+ m_y(0),
+ m_window(0)
+ {
+ }
+
+ virtual void poll(bool relative_reset) override
+ {
+ sdl_mouse_device_base::poll(relative_reset);
+
+ SDL_Window *const win(m_window ? SDL_GetWindowFromID(m_window) : nullptr);
+ if (win)
+ {
+ int w, h;
+ SDL_GetWindowSize(win, &w, &h);
+ m_mouse.lX = normalize_absolute_axis(m_x, 0, w - 1);
+ m_mouse.lY = normalize_absolute_axis(m_y, 0, h - 1);
+ }
+ else
+ {
+ m_mouse.lX = 0;
+ m_mouse.lY = 0;
+ }
+ }
+
+ virtual void reset() override
+ {
+ sdl_mouse_device_base::reset();
+ m_x = m_y = 0;
+ m_window = 0;
+ }
+
+ virtual void configure(input_device &device) override
+ {
+ add_common_items(device, 5);
+
+ // add scroll axes
+ device.add_item(
+ "Scroll V",
+ std::string_view(),
+ ITEM_ID_ADD_RELATIVE1,
+ generic_axis_get_state<s32>,
+ &m_mouse.lV);
+ device.add_item(
+ "Scroll H",
+ std::string_view(),
+ ITEM_ID_ADD_RELATIVE2,
+ generic_axis_get_state<s32>,
+ &m_mouse.lH);
+ }
+
+ virtual void process_event(SDL_Event const &event) override
+ {
+ switch (event.type)
+ {
+ case SDL_EVENT_MOUSE_MOTION:
+ m_x = event.motion.x;
+ m_y = event.motion.y;
+ m_window = event.motion.windowID;
+ break;
+
+ case SDL_EVENT_MOUSE_BUTTON_DOWN:
+ m_mouse.buttons[event.button.button - 1] = 0x80;
+ m_x = event.button.x;
+ m_y = event.button.y;
+ m_window = event.button.windowID;
+ break;
+
+ case SDL_EVENT_MOUSE_BUTTON_UP:
+ m_mouse.buttons[event.button.button - 1] = 0;
+ m_x = event.button.x;
+ m_y = event.button.y;
+ m_window = event.button.windowID;
+ break;
+
+ case SDL_EVENT_MOUSE_WHEEL:
+ // adjust SDL 1-per-click to match Win32 120-per-click
+ m_v += std::lround(event.wheel.integer_y * 120 * input_device::RELATIVE_PER_PIXEL);
+ m_h += std::lround(event.wheel.integer_x * 120 * input_device::RELATIVE_PER_PIXEL);
+ break;
+
+ case SDL_EVENT_WINDOW_MOUSE_LEAVE:
+ if (event.window.windowID == m_window)
+ m_window = 0;
+ break;
+ }
+ }
+
+private:
+ s32 m_x, m_y;
+ u32 m_window;
+};
+
+
+//============================================================
+// sdl_dual_lightgun_device
+//============================================================
+
+class sdl_dual_lightgun_device : public sdl_mouse_device_base
+{
+public:
+ sdl_dual_lightgun_device(std::string &&name, std::string &&id, input_module &module, u8 index) :
+ sdl_mouse_device_base(std::move(name), std::move(id), module),
+ m_index(index)
+ {
+ }
+
+ virtual void configure(input_device &device) override
+ {
+ add_common_items(device, 2);
+ }
+
+ virtual void process_event(SDL_Event const &event) override
+ {
+ switch (event.type)
+ {
+ case SDL_EVENT_MOUSE_BUTTON_DOWN:
+ {
+ SDL_Window *const win(SDL_GetWindowFromID(event.button.windowID));
+ u8 const button = translate_button(event);
+ if (win && ((button / 2) == m_index))
+ {
+ int w, h;
+ SDL_GetWindowSize(win, &w, &h);
+ m_mouse.buttons[(button & 1) << 1] = 0x80;
+ m_mouse.lX = normalize_absolute_axis(event.button.x, 0, w - 1);
+ m_mouse.lY = normalize_absolute_axis(event.button.y, 0, h - 1);
+ }
+ }
+ break;
+
+ case SDL_EVENT_MOUSE_BUTTON_UP:
+ {
+ u8 const button = translate_button(event);
+ if ((button / 2) == m_index)
+ m_mouse.buttons[(button & 1) << 1] = 0;
+ }
+ break;
+ }
+ }
+
+private:
+ static u8 translate_button(SDL_Event const &event)
+ {
+ u8 const index(event.button.button - 1);
+ return index ^ (((1 == index) || (2 == index)) ? 3 : 0);
+ }
+
+ u8 const m_index;
+};
+
+
+//============================================================
+// sdl_joystick_device_base
+//============================================================
+
+class sdl_joystick_device_base : public sdl_device, protected joystick_assignment_helper
+{
+public:
+ std::optional<std::string> const &serial() const { return m_serial; }
+ SDL_JoystickID instance() const { return m_instance; }
+
+ bool is_instance(SDL_JoystickID instance) const { return m_instance == instance; }
+
+ bool reconnect_match(std::string_view g, char const *s) const
+ {
+ return
+ (0 > m_instance) &&
+ (id() == g) &&
+ ((s && serial() && (*serial() == s)) || (!s && !serial()));
+ }
+
+protected:
+ sdl_joystick_device_base(
+ std::string &&name,
+ std::string &&id,
+ input_module &module,
+ char const *serial) :
+ sdl_device(std::move(name), std::move(id), module),
+ m_instance(-1)
+ {
+ if (serial)
+ m_serial = serial;
+ }
+
+ void set_instance(SDL_JoystickID instance)
+ {
+ assert(0 > m_instance);
+ assert(0 <= instance);
+
+ m_instance = instance;
+ }
+
+ void clear_instance()
+ {
+ m_instance = -1;
+ }
+
+private:
+ std::optional<std::string> m_serial;
+ SDL_JoystickID m_instance;
+};
+
+
+//============================================================
+// sdl_joystick_device
+//============================================================
+
+class sdl_joystick_device : public sdl_joystick_device_base
+{
+public:
+ sdl_joystick_device(
+ std::string &&name,
+ std::string &&id,
+ input_module &module,
+ SDL_Joystick *joy,
+ char const *serial) :
+ sdl_joystick_device_base(
+ std::move(name),
+ std::move(id),
+ module,
+ serial),
+ m_joystick({{0}}),
+ m_joydevice(joy),
+ m_hapdevice(SDL_OpenHapticFromJoystick(joy))
+ {
+ set_instance(SDL_GetJoystickID(joy));
+ }
+
+ virtual void configure(input_device &device) override
+ {
+ input_device::assignment_vector assignments;
+ char tempname[32];
+
+ int const axiscount = SDL_GetNumJoystickAxes(m_joydevice);
+ int const buttoncount = SDL_GetNumJoystickButtons(m_joydevice);
+ int const hatcount = SDL_GetNumJoystickHats(m_joydevice);
+ int const ballcount = SDL_GetNumJoystickBalls(m_joydevice);
+
+ // loop over all axes
+ input_item_id axisactual[MAX_AXES];
+ for (int axis = 0; (axis < MAX_AXES) && (axis < axiscount); axis++)
+ {
+ input_item_id itemid;
+
+ if (axis < INPUT_MAX_AXIS)
+ itemid = input_item_id(ITEM_ID_XAXIS + axis);
+ else if (axis < (INPUT_MAX_AXIS + INPUT_MAX_ADD_ABSOLUTE))
+ itemid = input_item_id(ITEM_ID_ADD_ABSOLUTE1 + axis - INPUT_MAX_AXIS);
+ else
+ itemid = ITEM_ID_OTHER_AXIS_ABSOLUTE;
+
+ snprintf(tempname, sizeof(tempname), "A%d", axis + 1);
+ axisactual[axis] = device.add_item(
+ tempname,
+ std::string_view(),
+ itemid,
+ generic_axis_get_state<s32>,
+ &m_joystick.axes[axis]);
+ }
+
+ // loop over all buttons
+ for (int button = 0; (button < MAX_BUTTONS) && (button < buttoncount); button++)
+ {
+ input_item_id itemid;
+
+ m_joystick.buttons[button] = 0;
+
+ if (button < INPUT_MAX_BUTTONS)
+ itemid = input_item_id(ITEM_ID_BUTTON1 + button);
+ else if (button < INPUT_MAX_BUTTONS + INPUT_MAX_ADD_SWITCH)
+ itemid = input_item_id(ITEM_ID_ADD_SWITCH1 + button - INPUT_MAX_BUTTONS);
+ else
+ itemid = ITEM_ID_OTHER_SWITCH;
+
+ input_item_id const actual = device.add_item(
+ default_button_name(button),
+ std::string_view(),
+ itemid,
+ generic_button_get_state<s32>,
+ &m_joystick.buttons[button]);
+
+ // there are sixteen action button types
+ if (button < 16)
+ {
+ input_seq const seq(make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_NONE, actual));
+ assignments.emplace_back(ioport_type(IPT_BUTTON1 + button), SEQ_TYPE_STANDARD, seq);
+
+ // assign the first few buttons to UI actions and pedals
+ switch (button)
+ {
+ case 0:
+ assignments.emplace_back(IPT_PEDAL, SEQ_TYPE_INCREMENT, seq);
+ assignments.emplace_back(IPT_UI_SELECT, SEQ_TYPE_STANDARD, seq);
+ break;
+ case 1:
+ assignments.emplace_back(IPT_PEDAL2, SEQ_TYPE_INCREMENT, seq);
+ assignments.emplace_back((3 > buttoncount) ? IPT_UI_CLEAR : IPT_UI_BACK, SEQ_TYPE_STANDARD, seq);
+ break;
+ case 2:
+ assignments.emplace_back(IPT_PEDAL3, SEQ_TYPE_INCREMENT, seq);
+ assignments.emplace_back(IPT_UI_CLEAR, SEQ_TYPE_STANDARD, seq);
+ break;
+ case 3:
+ assignments.emplace_back(IPT_UI_HELP, SEQ_TYPE_STANDARD, seq);
+ break;
+ }
+ }
+ }
+
+ // loop over all hats
+ input_item_id hatactual[MAX_HATS][4];
+ for (int hat = 0; (hat < MAX_HATS) && (hat < hatcount); hat++)
+ {
+ input_item_id itemid;
+
+ snprintf(tempname, sizeof(tempname), "Hat %d Up", hat + 1);
+ itemid = input_item_id((hat < INPUT_MAX_HATS) ? ITEM_ID_HAT1UP + (4 * hat) : ITEM_ID_OTHER_SWITCH);
+ hatactual[hat][0] = device.add_item(
+ tempname,
+ std::string_view(),
+ itemid,
+ generic_button_get_state<s32>,
+ &m_joystick.hatsU[hat]);
+
+ snprintf(tempname, sizeof(tempname), "Hat %d Down", hat + 1);
+ itemid = input_item_id((hat < INPUT_MAX_HATS) ? ITEM_ID_HAT1DOWN + (4 * hat) : ITEM_ID_OTHER_SWITCH);
+ hatactual[hat][1] = device.add_item(
+ tempname,
+ std::string_view(),
+ itemid,
+ generic_button_get_state<s32>,
+ &m_joystick.hatsD[hat]);
+
+ snprintf(tempname, sizeof(tempname), "Hat %d Left", hat + 1);
+ itemid = input_item_id((hat < INPUT_MAX_HATS) ? ITEM_ID_HAT1LEFT + (4 * hat) : ITEM_ID_OTHER_SWITCH);
+ hatactual[hat][2] = device.add_item(
+ tempname,
+ std::string_view(),
+ itemid,
+ generic_button_get_state<s32>,
+ &m_joystick.hatsL[hat]);
+
+ snprintf(tempname, sizeof(tempname), "Hat %d Right", hat + 1);
+ itemid = input_item_id((hat < INPUT_MAX_HATS) ? ITEM_ID_HAT1RIGHT + (4 * hat) : ITEM_ID_OTHER_SWITCH);
+ hatactual[hat][3] = device.add_item(
+ tempname,
+ std::string_view(),
+ itemid,
+ generic_button_get_state<s32>,
+ &m_joystick.hatsR[hat]);
+ }
+
+ // loop over all (track)balls
+ for (int ball = 0; (ball < (MAX_AXES / 2)) && (ball < ballcount); ball++)
+ {
+ int itemid;
+
+ if (ball * 2 < INPUT_MAX_ADD_RELATIVE)
+ itemid = ITEM_ID_ADD_RELATIVE1 + ball * 2;
+ else
+ itemid = ITEM_ID_OTHER_AXIS_RELATIVE;
+
+ snprintf(tempname, sizeof(tempname), "R%d X", ball + 1);
+ input_item_id const xactual = device.add_item(
+ tempname,
+ std::string_view(),
+ input_item_id(itemid),
+ generic_axis_get_state<s32>,
+ &m_joystick.balls[ball * 2]);
+
+ snprintf(tempname, sizeof(tempname), "R%d Y", ball + 1);
+ input_item_id const yactual = device.add_item(
+ tempname,
+ std::string_view(),
+ input_item_id(itemid + 1),
+ generic_axis_get_state<s32>,
+ &m_joystick.balls[ball * 2 + 1]);
+
+ if (0 == ball)
+ {
+ // assign the first trackball to dial, trackball, mouse and lightgun inputs
+ input_seq const xseq(make_code(ITEM_CLASS_RELATIVE, ITEM_MODIFIER_NONE, xactual));
+ input_seq const yseq(make_code(ITEM_CLASS_RELATIVE, ITEM_MODIFIER_NONE, yactual));
+ assignments.emplace_back(IPT_DIAL, SEQ_TYPE_STANDARD, xseq);
+ assignments.emplace_back(IPT_DIAL_V, SEQ_TYPE_STANDARD, yseq);
+ assignments.emplace_back(IPT_TRACKBALL_X, SEQ_TYPE_STANDARD, xseq);
+ assignments.emplace_back(IPT_TRACKBALL_Y, SEQ_TYPE_STANDARD, yseq);
+ assignments.emplace_back(IPT_LIGHTGUN_X, SEQ_TYPE_STANDARD, xseq);
+ assignments.emplace_back(IPT_LIGHTGUN_Y, SEQ_TYPE_STANDARD, yseq);
+ assignments.emplace_back(IPT_MOUSE_X, SEQ_TYPE_STANDARD, xseq);
+ assignments.emplace_back(IPT_MOUSE_Y, SEQ_TYPE_STANDARD, yseq);
+ if (2 > axiscount)
+ {
+ // use it for joystick inputs if axes are limited
+ assignments.emplace_back(IPT_AD_STICK_X, SEQ_TYPE_STANDARD, xseq);
+ assignments.emplace_back(IPT_AD_STICK_Y, SEQ_TYPE_STANDARD, yseq);
+ }
+ else
+ {
+ // use for non-centring throttle control
+ assignments.emplace_back(IPT_AD_STICK_Z, SEQ_TYPE_STANDARD, yseq);
+ }
+ }
+ else if ((1 == ball) && (2 > axiscount))
+ {
+ // provide a non-centring throttle control
+ input_seq const yseq(make_code(ITEM_CLASS_RELATIVE, ITEM_MODIFIER_NONE, yactual));
+ assignments.emplace_back(IPT_AD_STICK_Z, SEQ_TYPE_STANDARD, yseq);
+ }
+ }
+
+ // set up default assignments for axes and hats
+ add_directional_assignments(
+ assignments,
+ (1 <= axiscount) ? axisactual[0] : ITEM_ID_INVALID, // assume first axis is X
+ (2 <= axiscount) ? axisactual[1] : ITEM_ID_INVALID, // assume second axis is Y
+ (1 <= hatcount) ? hatactual[0][2] : ITEM_ID_INVALID,
+ (1 <= hatcount) ? hatactual[0][3] : ITEM_ID_INVALID,
+ (1 <= hatcount) ? hatactual[0][0] : ITEM_ID_INVALID,
+ (1 <= hatcount) ? hatactual[0][1] : ITEM_ID_INVALID);
+ if (2 <= axiscount)
+ {
+ // put pedals on the last of the second, third or fourth axis
+ input_item_id const pedalitem = axisactual[(std::min)(axiscount, 4) - 1];
+ assignments.emplace_back(
+ IPT_PEDAL,
+ SEQ_TYPE_STANDARD,
+ input_seq(make_code(ITEM_CLASS_ABSOLUTE, ITEM_MODIFIER_NEG, pedalitem)));
+ assignments.emplace_back(
+ IPT_PEDAL2,
+ SEQ_TYPE_STANDARD,
+ input_seq(make_code(ITEM_CLASS_ABSOLUTE, ITEM_MODIFIER_POS, pedalitem)));
+ }
+ if (3 <= axiscount)
+ {
+ // assign X/Y to one of the twin sticks
+ assignments.emplace_back(
+ (4 <= axiscount) ? IPT_JOYSTICKLEFT_LEFT : IPT_JOYSTICKRIGHT_LEFT,
+ SEQ_TYPE_STANDARD,
+ input_seq(make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_LEFT, axisactual[0])));
+ assignments.emplace_back(
+ (4 <= axiscount) ? IPT_JOYSTICKLEFT_RIGHT : IPT_JOYSTICKRIGHT_RIGHT,
+ SEQ_TYPE_STANDARD,
+ input_seq(make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_RIGHT, axisactual[0])));
+ assignments.emplace_back(
+ (4 <= axiscount) ? IPT_JOYSTICKLEFT_UP : IPT_JOYSTICKRIGHT_UP,
+ SEQ_TYPE_STANDARD,
+ input_seq(make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_UP, axisactual[1])));
+ assignments.emplace_back(
+ (4 <= axiscount) ? IPT_JOYSTICKLEFT_DOWN : IPT_JOYSTICKRIGHT_DOWN,
+ SEQ_TYPE_STANDARD,
+ input_seq(make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_DOWN, axisactual[1])));
+
+ // use third or fourth axis for Z
+ input_seq const seq(make_code(ITEM_CLASS_ABSOLUTE, ITEM_MODIFIER_NONE, axisactual[(std::min)(axiscount, 4) - 1]));
+ assignments.emplace_back(IPT_AD_STICK_Z, SEQ_TYPE_STANDARD, seq);
+
+ // use this for focus next/previous to make system selection menu practical to navigate
+ input_seq const upseq(make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_NEG, axisactual[2]));
+ input_seq const downseq(make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_POS, axisactual[2]));
+ assignments.emplace_back(IPT_UI_FOCUS_PREV, SEQ_TYPE_STANDARD, upseq);
+ assignments.emplace_back(IPT_UI_FOCUS_NEXT, SEQ_TYPE_STANDARD, downseq);
+ if (4 <= axiscount)
+ {
+ // use for zoom as well if there's another axis to use for previous/next group
+ assignments.emplace_back(IPT_UI_ZOOM_IN, SEQ_TYPE_STANDARD, downseq);
+ assignments.emplace_back(IPT_UI_ZOOM_OUT, SEQ_TYPE_STANDARD, upseq);
+ }
+
+ // use this for twin sticks, too
+ assignments.emplace_back((4 <= axiscount) ? IPT_JOYSTICKRIGHT_LEFT : IPT_JOYSTICKLEFT_UP, SEQ_TYPE_STANDARD, upseq);
+ assignments.emplace_back((4 <= axiscount) ? IPT_JOYSTICKRIGHT_RIGHT : IPT_JOYSTICKLEFT_DOWN, SEQ_TYPE_STANDARD, downseq);
+
+ // put previous/next group on the last of the third or fourth axis
+ input_item_id const groupitem = axisactual[(std::min)(axiscount, 4) - 1];
+ assignments.emplace_back(
+ IPT_UI_PREV_GROUP,
+ SEQ_TYPE_STANDARD,
+ input_seq(make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_NEG, groupitem)));
+ assignments.emplace_back(
+ IPT_UI_NEXT_GROUP,
+ SEQ_TYPE_STANDARD,
+ input_seq(make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_POS, groupitem)));
+ }
+ if (4 <= axiscount)
+ {
+ // use this for twin sticks
+ input_seq const upseq(make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_NEG, axisactual[3]));
+ input_seq const downseq(make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_POS, axisactual[3]));
+ assignments.emplace_back(IPT_JOYSTICKRIGHT_UP, SEQ_TYPE_STANDARD, upseq);
+ assignments.emplace_back(IPT_JOYSTICKRIGHT_DOWN, SEQ_TYPE_STANDARD, downseq);
+ }
+
+ // set default assignments
+ device.set_default_assignments(std::move(assignments));
+ }
+
+ ~sdl_joystick_device()
+ {
+ close_device();
+ }
+
+ virtual void reset() override
+ {
+ sdl_joystick_device_base::reset();
+ clear_buffer();
+ }
+
+ virtual void process_event(SDL_Event const &event) override
+ {
+ if (!m_joydevice)
+ return;
+
+ switch (event.type)
+ {
+ case SDL_EVENT_JOYSTICK_AXIS_MOTION:
+ if (event.jaxis.axis < MAX_AXES)
+ m_joystick.axes[event.jaxis.axis] = (event.jaxis.value * 2);
+ break;
+
+ case SDL_EVENT_JOYSTICK_BALL_MOTION:
+ //printf("Ball %d %d\n", event.jball.xrel, event.jball.yrel);
+ if (event.jball.ball < (MAX_AXES / 2))
+ {
+ m_joystick.balls[event.jball.ball * 2] = event.jball.xrel * input_device::RELATIVE_PER_PIXEL;
+ m_joystick.balls[event.jball.ball * 2 + 1] = event.jball.yrel * input_device::RELATIVE_PER_PIXEL;
+ }
+ break;
+
+ case SDL_EVENT_JOYSTICK_HAT_MOTION:
+ if (event.jhat.hat < MAX_HATS)
+ {
+ m_joystick.hatsU[event.jhat.hat] = (event.jhat.value & SDL_HAT_UP) ? 0x80 : 0;
+ m_joystick.hatsD[event.jhat.hat] = (event.jhat.value & SDL_HAT_DOWN) ? 0x80 : 0;
+ m_joystick.hatsL[event.jhat.hat] = (event.jhat.value & SDL_HAT_LEFT) ? 0x80 : 0;
+ m_joystick.hatsR[event.jhat.hat] = (event.jhat.value & SDL_HAT_RIGHT) ? 0x80 : 0;
+ }
+ break;
+
+ case SDL_EVENT_JOYSTICK_BUTTON_DOWN:
+ case SDL_EVENT_JOYSTICK_BUTTON_UP:
+ if (event.jbutton.button < MAX_BUTTONS)
+ m_joystick.buttons[event.jbutton.button] = (event.jbutton.down) ? 0x80 : 0;
+ break;
+
+ case SDL_EVENT_JOYSTICK_REMOVED:
+ osd_printf_verbose("Joystick: %s [ID %s] disconnected\n", name(), id());
+ clear_instance();
+ clear_buffer();
+ close_device();
+ break;
+ }
+ }
+
+ bool has_haptic() const
+ {
+ return m_hapdevice != nullptr;
+ }
+
+ void attach_device(SDL_Joystick *joy)
+ {
+ assert(joy);
+ assert(!m_joydevice);
+
+ set_instance(SDL_GetJoystickID(joy));
+ m_joydevice = joy;
+ m_hapdevice = SDL_OpenHapticFromJoystick(joy);
+
+ osd_printf_verbose("Joystick: %s [ID %s] reconnected\n", name(), id());
+ }
+
+protected:
+ // state information for a joystick
+ struct sdl_joystick_state
+ {
+ s32 axes[MAX_AXES];
+ s32 buttons[MAX_BUTTONS];
+ s32 hatsU[MAX_HATS], hatsD[MAX_HATS], hatsL[MAX_HATS], hatsR[MAX_HATS];
+ s32 balls[MAX_AXES];
+ };
+
+ sdl_joystick_state m_joystick;
+
+private:
+ SDL_Joystick *m_joydevice;
+ SDL_Haptic *m_hapdevice;
+
+ void clear_buffer()
+ {
+ memset(&m_joystick, 0, sizeof(m_joystick));
+ }
+
+ void close_device()
+ {
+ if (m_joydevice)
+ {
+ if (m_hapdevice)
+ {
+ SDL_CloseHaptic(m_hapdevice);
+ m_hapdevice = nullptr;
+ }
+ SDL_CloseJoystick(m_joydevice);
+ m_joydevice = nullptr;
+ }
+ }
+};
+
+
+//============================================================
+// sdl_sixaxis_joystick_device
+//============================================================
+
+class sdl_sixaxis_joystick_device : public sdl_joystick_device
+{
+public:
+ using sdl_joystick_device::sdl_joystick_device;
+
+ virtual void process_event(SDL_Event const &event) override
+ {
+ switch (event.type)
+ {
+ case SDL_EVENT_JOYSTICK_AXIS_MOTION:
+ {
+ int const axis = event.jaxis.axis;
+ if (axis <= 3)
+ {
+ m_joystick.axes[event.jaxis.axis] = (event.jaxis.value * 2);
+ }
+ else
+ {
+ int const magic = (event.jaxis.value / 2) + 16384;
+ m_joystick.axes[event.jaxis.axis] = magic;
+ }
+ }
+ break;
+
+ default:
+ // Call the base for other events
+ sdl_joystick_device::process_event(event);
+ break;
+ }
+ }
+};
+
+
+//============================================================
+// sdl_game_controller_device
+//============================================================
+
+class sdl_game_controller_device : public sdl_joystick_device_base
+{
+public:
+ sdl_game_controller_device(
+ std::string &&name,
+ std::string &&id,
+ input_module &module,
+ SDL_Gamepad *ctrl,
+ char const *serial) :
+ sdl_joystick_device_base(
+ std::move(name),
+ std::move(id),
+ module,
+ serial),
+ m_controller({{0}}),
+ m_ctrldevice(ctrl)
+ {
+ set_instance(SDL_GetJoystickID(SDL_GetGamepadJoystick(ctrl)));
+ }
+
+ ~sdl_game_controller_device()
+ {
+ close_device();
+ }
+
+ virtual void configure(input_device &device) override
+ {
+ input_device::assignment_vector assignments;
+ char const *const *axisnames = CONTROLLER_AXIS_XBOX;
+ char const *const *buttonnames = CONTROLLER_BUTTON_XBOX360;
+ bool digitaltriggers = false;
+ bool avoidpaddles = false;
+ auto const ctrltype = SDL_GetGamepadType(m_ctrldevice);
+ switch (ctrltype)
+ {
+ case SDL_GAMEPAD_TYPE_STANDARD:
+ osd_printf_verbose("Game Controller: ... unknown type\n", int(ctrltype));
+ break;
+ case SDL_GAMEPAD_TYPE_XBOX360:
+ osd_printf_verbose("Game Controller: ... Xbox 360 type\n");
+ axisnames = CONTROLLER_AXIS_XBOX;
+ buttonnames = CONTROLLER_BUTTON_XBOX360;
+ break;
+ case SDL_GAMEPAD_TYPE_XBOXONE:
+ osd_printf_verbose("Game Controller: ... Xbox One type\n");
+ axisnames = CONTROLLER_AXIS_XBOX;
+ buttonnames = CONTROLLER_BUTTON_XBOXONE;
+ break;
+ case SDL_GAMEPAD_TYPE_PS3:
+ osd_printf_verbose("Game Controller: ... PlayStation 3 type\n");
+ axisnames = CONTROLLER_AXIS_PS;
+ buttonnames = CONTROLLER_BUTTON_PS3;
+ break;
+ case SDL_GAMEPAD_TYPE_PS4:
+ osd_printf_verbose("Game Controller: ... PlayStation 4 type\n");
+ axisnames = CONTROLLER_AXIS_PS;
+ buttonnames = CONTROLLER_BUTTON_PS4;
+ break;
+ case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO:
+ osd_printf_verbose("Game Controller: ... Switch Pro Controller type\n");
+ axisnames = CONTROLLER_AXIS_SWITCH;
+ buttonnames = CONTROLLER_BUTTON_SWITCH;
+ digitaltriggers = true;
+ break;
+ //case SDL_GAMEPAD_TYPE_VIRTUAL:
+ case SDL_GAMEPAD_TYPE_PS5:
+ osd_printf_verbose("Game Controller: ... PlayStation 5 type\n");
+ axisnames = CONTROLLER_AXIS_PS;
+ buttonnames = CONTROLLER_BUTTON_PS5;
+ break;
+
+ case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_PAIR:
+ osd_printf_verbose("Game Controller: ... Joy-Con pair type\n");
+ axisnames = CONTROLLER_AXIS_SWITCH;
+ buttonnames = CONTROLLER_BUTTON_SWITCH;
+ digitaltriggers = true;
+ avoidpaddles = true;
+ break;
+
+ default: // do some other checks and fall back to Xbox layout if still unrecognized
+ {
+ const auto joystick = SDL_GetJoystickFromID(SDL_GetGamepadID(m_ctrldevice));
+ const auto vendor_id = SDL_GetJoystickVendor(joystick);
+ const auto product_id = SDL_GetJoystickProduct(joystick);
+
+ if (vendor_id == 0x18d1 && product_id == 0x9400)
+ {
+ osd_printf_verbose("Game Controller: ... Google Stadia type\n");
+ axisnames = CONTROLLER_AXIS_PS;
+ buttonnames = CONTROLLER_BUTTON_STADIA;
+ }
+ else if (vendor_id == 0x0955 && (product_id == 0x7210 || product_id == 0x7214))
+ {
+ osd_printf_verbose("Game Controller: ... NVIDIA Shield type\n");
+ axisnames = CONTROLLER_AXIS_XBOX;
+ buttonnames = CONTROLLER_BUTTON_SHIELD;
+ }
+ else
+ {
+ osd_printf_verbose("Game Controller: ... unrecognized type (%d)\n", int(ctrltype));
+ }
+ }
+ break;
+ }
+
+ // keep track of item numbers as we add controls
+ std::pair<input_item_id, input_item_id> axisitems[SDL_GAMEPAD_AXIS_COUNT];
+ input_item_id buttonitems[SDL_GAMEPAD_BUTTON_COUNT];
+ std::tuple<input_item_id, SDL_GamepadButton, SDL_GamepadAxis> numberedbuttons[16];
+ std::fill(
+ std::begin(axisitems),
+ std::end(axisitems),
+ std::make_pair(ITEM_ID_INVALID, ITEM_ID_INVALID));
+ std::fill(
+ std::begin(buttonitems),
+ std::end(buttonitems),
+ ITEM_ID_INVALID);
+ std::fill(
+ std::begin(numberedbuttons),
+ std::end(numberedbuttons),
+ std::make_tuple(ITEM_ID_INVALID, SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_INVALID));
+
+ // add axes
+ std::tuple<SDL_GamepadAxis, input_item_id, bool> const axes[]{
+ { SDL_GAMEPAD_AXIS_LEFTX, ITEM_ID_XAXIS, false },
+ { SDL_GAMEPAD_AXIS_LEFTY, ITEM_ID_YAXIS, false },
+ { SDL_GAMEPAD_AXIS_RIGHTX, ITEM_ID_ZAXIS, false },
+ { SDL_GAMEPAD_AXIS_RIGHTY, ITEM_ID_RZAXIS, false },
+ { SDL_GAMEPAD_AXIS_LEFT_TRIGGER, ITEM_ID_SLIDER1, true },
+ { SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, ITEM_ID_SLIDER2, true } };
+ for (auto [axis, item, buttontest] : axes)
+ {
+ bool avail = !buttontest || !digitaltriggers;
+ avail = avail && SDL_GamepadHasAxis(m_ctrldevice, axis);
+ if (avail)
+ {
+ int bind_count = 0;
+ SDL_GamepadBinding **binding = SDL_GetGamepadBindings(m_ctrldevice, &bind_count);
+ bool hasAxisBinding = false;
+ for (int idx = 0; idx < bind_count; idx++)
+ {
+ if (binding[idx]->input.axis.axis == axis)
+ {
+ // SDL 3 returns both analog (button) and digital (axis) bindings for
+ // analog triggers. So just allow it if there's an analog binding.
+ if (binding[idx]->input_type == SDL_GAMEPAD_BINDTYPE_AXIS)
+ {
+ hasAxisBinding = true;
+ break;
+ }
+ }
+ }
+
+ if (!hasAxisBinding)
+ {
+ avail = false;
+ }
+ }
+ if (avail)
+ {
+ axisitems[axis].first = device.add_item(
+ axisnames[axis],
+ std::string_view(),
+ item,
+ generic_axis_get_state<s32>,
+ &m_controller.axes[axis]);
+ }
+ }
+
+ // add automatically numbered buttons
+ std::tuple<SDL_GamepadButton, SDL_GamepadAxis, bool> const generalbuttons[]{
+ { SDL_GAMEPAD_BUTTON_SOUTH, SDL_GAMEPAD_AXIS_INVALID, true },
+ { SDL_GAMEPAD_BUTTON_EAST, SDL_GAMEPAD_AXIS_INVALID, true },
+ { SDL_GAMEPAD_BUTTON_WEST, SDL_GAMEPAD_AXIS_INVALID, true },
+ { SDL_GAMEPAD_BUTTON_NORTH, SDL_GAMEPAD_AXIS_INVALID, true },
+ { SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, SDL_GAMEPAD_AXIS_INVALID, true },
+ { SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, SDL_GAMEPAD_AXIS_INVALID, true },
+ { SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, true },
+ { SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, true },
+ { SDL_GAMEPAD_BUTTON_LEFT_STICK, SDL_GAMEPAD_AXIS_INVALID, true },
+ { SDL_GAMEPAD_BUTTON_RIGHT_STICK, SDL_GAMEPAD_AXIS_INVALID, true },
+ { SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1, SDL_GAMEPAD_AXIS_INVALID, true },
+ { SDL_GAMEPAD_BUTTON_LEFT_PADDLE1, SDL_GAMEPAD_AXIS_INVALID, true },
+ { SDL_GAMEPAD_BUTTON_RIGHT_PADDLE2, SDL_GAMEPAD_AXIS_INVALID, true },
+ { SDL_GAMEPAD_BUTTON_LEFT_PADDLE2, SDL_GAMEPAD_AXIS_INVALID, true },
+ { SDL_GAMEPAD_BUTTON_GUIDE, SDL_GAMEPAD_AXIS_INVALID, false },
+ { SDL_GAMEPAD_BUTTON_MISC1, SDL_GAMEPAD_AXIS_INVALID, false },
+ { SDL_GAMEPAD_BUTTON_TOUCHPAD, SDL_GAMEPAD_AXIS_INVALID, false },
+ };
+ input_item_id button_item = ITEM_ID_BUTTON1;
+ unsigned buttoncount = 0;
+ for (auto [button, axis, field] : generalbuttons)
+ {
+ bool avail = true;
+ input_item_id actual = ITEM_ID_INVALID;
+ if (SDL_GAMEPAD_BUTTON_INVALID != button)
+ {
+ avail = SDL_GamepadHasButton(m_ctrldevice, button);
+ if (avail)
+ {
+ int bind_count = 0;
+ SDL_GamepadBinding **binding = SDL_GetGamepadBindings(m_ctrldevice, &bind_count);
+ for (int idx = 0; idx < bind_count; idx++)
+ {
+ if (binding[idx]->input.button == button)
+ {
+ if (binding[idx]->input_type == SDL_GAMEPAD_BINDTYPE_NONE)
+ {
+ avail = false;
+ }
+ }
+ }
+ }
+ if (avail)
+ {
+ actual = buttonitems[button] = device.add_item(
+ buttonnames[button],
+ std::string_view(),
+ button_item++,
+ generic_button_get_state<s32>,
+ &m_controller.buttons[button]);
+ if (field && (std::size(numberedbuttons) > buttoncount))
+ std::get<1>(numberedbuttons[buttoncount]) = button;
+ }
+ }
+ else
+ {
+ avail = SDL_GamepadHasAxis(m_ctrldevice, axis);
+ if (avail)
+ {
+ int bind_count = 0;
+ SDL_GamepadBinding **binding = SDL_GetGamepadBindings(m_ctrldevice, &bind_count);
+ for (int idx = 0; idx < bind_count; idx++)
+ {
+ if (binding[idx]->input.axis.axis ==axis)
+ {
+ switch (binding[idx]->input_type)
+ {
+ case SDL_GAMEPAD_BINDTYPE_NONE:
+ avail = false;
+ break;
+ case SDL_GAMEPAD_BINDTYPE_BUTTON:
+ break;
+ default:
+ avail = digitaltriggers;
+ break;
+ }
+ }
+ }
+ }
+ if (avail)
+ {
+ actual = axisitems[axis].second = device.add_item(
+ axisnames[axis],
+ std::string_view(),
+ button_item++,
+ [] (void *device_internal, void *item_internal) -> int
+ {
+ return (*reinterpret_cast<s32 const *>(item_internal) <= -16'384) ? 1 : 0;
+ },
+ &m_controller.axes[axis]);
+ if (field && (std::size(numberedbuttons) > buttoncount))
+ std::get<2>(numberedbuttons[buttoncount]) = axis;
+ }
+ }
+
+ // add default button assignments
+ if (field && avail && (std::size(numberedbuttons) > buttoncount))
+ {
+ std::get<0>(numberedbuttons[buttoncount]) = actual;
+ add_button_assignment(assignments, ioport_type(IPT_BUTTON1 + buttoncount++), { actual });
+ }
+ }
+
+ // add buttons with fixed item IDs
+ std::pair<SDL_GamepadButton, input_item_id> const fixedbuttons[]{
+ { SDL_GAMEPAD_BUTTON_BACK, ITEM_ID_SELECT },
+ { SDL_GAMEPAD_BUTTON_START, ITEM_ID_START },
+ { SDL_GAMEPAD_BUTTON_DPAD_UP, ITEM_ID_HAT1UP },
+ { SDL_GAMEPAD_BUTTON_DPAD_DOWN, ITEM_ID_HAT1DOWN },
+ { SDL_GAMEPAD_BUTTON_DPAD_LEFT, ITEM_ID_HAT1LEFT },
+ { SDL_GAMEPAD_BUTTON_DPAD_RIGHT, ITEM_ID_HAT1RIGHT } };
+ for (auto [button, item] : fixedbuttons)
+ {
+ bool avail = true;
+ avail = SDL_GamepadHasButton(m_ctrldevice, button);
+ if (avail)
+ {
+ int bind_count = 0;
+ SDL_GamepadBinding **binding = SDL_GetGamepadBindings(m_ctrldevice, &bind_count);
+ for (int idx = 0; idx < bind_count; idx++)
+ {
+ if (binding[idx]->input.button == button)
+ {
+ switch (binding[idx]->input_type)
+ {
+ case SDL_GAMEPAD_BINDTYPE_NONE:
+ avail = false;
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ }
+ if (avail)
+ {
+ buttonitems[button] = device.add_item(
+ buttonnames[button],
+ std::string_view(),
+ item,
+ generic_button_get_state<s32>,
+ &m_controller.buttons[button]);
+ }
+ }
+
+ // try to get a "complete" joystick for primary movement controls
+ input_item_id diraxis[2][2];
+ choose_primary_stick(
+ diraxis,
+ axisitems[SDL_GAMEPAD_AXIS_LEFTX].first,
+ axisitems[SDL_GAMEPAD_AXIS_LEFTY].first,
+ axisitems[SDL_GAMEPAD_AXIS_RIGHTX].first,
+ axisitems[SDL_GAMEPAD_AXIS_RIGHTY].first);
+
+ // now set up controls using the primary joystick
+ add_directional_assignments(
+ assignments,
+ diraxis[0][0],
+ diraxis[0][1],
+ buttonitems[SDL_GAMEPAD_BUTTON_DPAD_LEFT],
+ buttonitems[SDL_GAMEPAD_BUTTON_DPAD_RIGHT],
+ buttonitems[SDL_GAMEPAD_BUTTON_DPAD_UP],
+ buttonitems[SDL_GAMEPAD_BUTTON_DPAD_DOWN]);
+
+ // assign a secondary stick axis to joystick Z if available
+ bool const zaxis = add_assignment(
+ assignments,
+ IPT_AD_STICK_Z,
+ SEQ_TYPE_STANDARD,
+ ITEM_CLASS_ABSOLUTE,
+ ITEM_MODIFIER_NONE,
+ { diraxis[1][1], diraxis[1][0] });
+ if (!zaxis)
+ {
+ // if both triggers are present, combine them, or failing that, fall back to a pair of buttons
+ if ((ITEM_ID_INVALID != axisitems[SDL_GAMEPAD_AXIS_LEFT_TRIGGER].first) && (ITEM_ID_INVALID != axisitems[SDL_GAMEPAD_AXIS_RIGHT_TRIGGER].first))
+ {
+ assignments.emplace_back(
+ IPT_AD_STICK_Z,
+ SEQ_TYPE_STANDARD,
+ input_seq(
+ make_code(ITEM_CLASS_ABSOLUTE, ITEM_MODIFIER_NONE, axisitems[SDL_GAMEPAD_AXIS_LEFT_TRIGGER].first),
+ make_code(ITEM_CLASS_ABSOLUTE, ITEM_MODIFIER_REVERSE, axisitems[SDL_GAMEPAD_AXIS_RIGHT_TRIGGER].first)));
+ }
+ else if (add_axis_inc_dec_assignment(assignments, IPT_AD_STICK_Z, buttonitems[SDL_GAMEPAD_BUTTON_LEFT_SHOULDER], buttonitems[SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER]))
+ {
+ // took shoulder buttons
+ }
+ else if (add_axis_inc_dec_assignment(assignments, IPT_AD_STICK_Z, axisitems[SDL_GAMEPAD_AXIS_LEFT_TRIGGER].second, axisitems[SDL_GAMEPAD_AXIS_RIGHT_TRIGGER].second))
+ {
+ // took trigger buttons
+ }
+ else if (add_axis_inc_dec_assignment(assignments, IPT_AD_STICK_Z, buttonitems[SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1], buttonitems[SDL_GAMEPAD_BUTTON_LEFT_PADDLE1]))
+ {
+ // took P1/P2
+ }
+ else if (add_axis_inc_dec_assignment(assignments, IPT_AD_STICK_Z, buttonitems[SDL_GAMEPAD_BUTTON_RIGHT_PADDLE2], buttonitems[SDL_GAMEPAD_BUTTON_LEFT_PADDLE2]))
+ {
+ // took P3/P4
+ }
+ }
+
+ // prefer trigger axes for pedals, otherwise take half axes and buttons
+ unsigned pedalbutton = 0;
+ if (!add_assignment(assignments, IPT_PEDAL, SEQ_TYPE_STANDARD, ITEM_CLASS_ABSOLUTE, ITEM_MODIFIER_NEG, { axisitems[SDL_GAMEPAD_AXIS_RIGHT_TRIGGER].first }))
+ {
+ add_assignment(
+ assignments,
+ IPT_PEDAL,
+ SEQ_TYPE_STANDARD,
+ ITEM_CLASS_ABSOLUTE,
+ ITEM_MODIFIER_NEG,
+ { diraxis[1][1], diraxis[0][1] });
+ bool const incbutton = add_assignment(
+ assignments,
+ IPT_PEDAL,
+ SEQ_TYPE_INCREMENT,
+ ITEM_CLASS_SWITCH,
+ ITEM_MODIFIER_NONE,
+ { axisitems[SDL_GAMEPAD_AXIS_RIGHT_TRIGGER].second, buttonitems[SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER] });
+ if (!incbutton)
+ {
+ if (add_assignment(assignments, IPT_PEDAL, SEQ_TYPE_INCREMENT, ITEM_CLASS_SWITCH, ITEM_MODIFIER_NONE, { std::get<0>(numberedbuttons[pedalbutton]) }))
+ ++pedalbutton;
+ }
+ }
+ if (!add_assignment(assignments, IPT_PEDAL2, SEQ_TYPE_STANDARD, ITEM_CLASS_ABSOLUTE, ITEM_MODIFIER_NEG, { axisitems[SDL_GAMEPAD_AXIS_LEFT_TRIGGER].first }))
+ {
+ add_assignment(
+ assignments,
+ IPT_PEDAL2,
+ SEQ_TYPE_STANDARD,
+ ITEM_CLASS_ABSOLUTE,
+ ITEM_MODIFIER_POS,
+ { diraxis[1][1], diraxis[0][1] });
+ bool const incbutton = add_assignment(
+ assignments,
+ IPT_PEDAL2,
+ SEQ_TYPE_INCREMENT,
+ ITEM_CLASS_SWITCH,
+ ITEM_MODIFIER_NONE,
+ { axisitems[SDL_GAMEPAD_AXIS_LEFT_TRIGGER].second, buttonitems[SDL_GAMEPAD_BUTTON_LEFT_SHOULDER] });
+ if (!incbutton)
+ {
+ if (add_assignment(assignments, IPT_PEDAL2, SEQ_TYPE_INCREMENT, ITEM_CLASS_SWITCH, ITEM_MODIFIER_NONE, { std::get<0>(numberedbuttons[pedalbutton]) }))
+ ++pedalbutton;
+ }
+ }
+ add_assignment(assignments, IPT_PEDAL3, SEQ_TYPE_INCREMENT, ITEM_CLASS_SWITCH, ITEM_MODIFIER_NONE, { std::get<0>(numberedbuttons[pedalbutton]) });
+
+ // potentially use thumb sticks and/or D-pad and A/B/X/Y diamond for twin sticks
+ add_twin_stick_assignments(
+ assignments,
+ axisitems[SDL_GAMEPAD_AXIS_LEFTX].first,
+ axisitems[SDL_GAMEPAD_AXIS_LEFTY].first,
+ axisitems[SDL_GAMEPAD_AXIS_RIGHTX].first,
+ axisitems[SDL_GAMEPAD_AXIS_RIGHTY].first,
+ buttonitems[SDL_GAMEPAD_BUTTON_DPAD_LEFT],
+ buttonitems[SDL_GAMEPAD_BUTTON_DPAD_RIGHT],
+ buttonitems[SDL_GAMEPAD_BUTTON_DPAD_UP],
+ buttonitems[SDL_GAMEPAD_BUTTON_DPAD_DOWN],
+ buttonitems[SDL_GAMEPAD_BUTTON_WEST],
+ buttonitems[SDL_GAMEPAD_BUTTON_EAST],
+ buttonitems[SDL_GAMEPAD_BUTTON_NORTH],
+ buttonitems[SDL_GAMEPAD_BUTTON_SOUTH]);
+
+ // add assignments for buttons with fixed functions
+ add_button_assignment(assignments, IPT_SELECT, { buttonitems[SDL_GAMEPAD_BUTTON_BACK] });
+ add_button_assignment(assignments, IPT_START, { buttonitems[SDL_GAMEPAD_BUTTON_START] });
+ add_button_assignment(assignments, IPT_UI_MENU, { buttonitems[SDL_GAMEPAD_BUTTON_GUIDE] });
+
+ // the first button is always UI select
+ if (add_button_assignment(assignments, IPT_UI_SELECT, { std::get<0>(numberedbuttons[0]) }))
+ {
+ if (SDL_GAMEPAD_BUTTON_INVALID != std::get<1>(numberedbuttons[0]))
+ buttonitems[std::get<1>(numberedbuttons[0])] = ITEM_ID_INVALID;
+ if (SDL_GAMEPAD_AXIS_INVALID != std::get<2>(numberedbuttons[0]))
+ axisitems[std::get<2>(numberedbuttons[0])].second = ITEM_ID_INVALID;
+ }
+
+ // try to get a matching pair of buttons for previous/next group
+ if (consume_button_pair(assignments, IPT_UI_PREV_GROUP, IPT_UI_NEXT_GROUP, axisitems[SDL_GAMEPAD_AXIS_LEFT_TRIGGER].second, axisitems[SDL_GAMEPAD_AXIS_RIGHT_TRIGGER].second))
+ {
+ // took digital triggers
+ }
+ else if (!avoidpaddles && consume_button_pair(assignments, IPT_UI_PREV_GROUP, IPT_UI_NEXT_GROUP, buttonitems[SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1], buttonitems[SDL_GAMEPAD_BUTTON_LEFT_PADDLE1]))
+ {
+ // took upper paddles
+ }
+ else if (consume_trigger_pair(assignments, IPT_UI_PREV_GROUP, IPT_UI_NEXT_GROUP, axisitems[SDL_GAMEPAD_AXIS_LEFT_TRIGGER].first, axisitems[SDL_GAMEPAD_AXIS_RIGHT_TRIGGER].first))
+ {
+ // took analog triggers
+ }
+ else if (!avoidpaddles && consume_button_pair(assignments, IPT_UI_PREV_GROUP, IPT_UI_NEXT_GROUP, buttonitems[SDL_GAMEPAD_BUTTON_RIGHT_PADDLE2], buttonitems[SDL_GAMEPAD_BUTTON_LEFT_PADDLE2]))
+ {
+ // took lower paddles
+ }
+ else if (consume_axis_pair(assignments, IPT_UI_PREV_GROUP, IPT_UI_NEXT_GROUP, diraxis[1][1]))
+ {
+ // took secondary Y
+ }
+ else if (consume_axis_pair(assignments, IPT_UI_PREV_GROUP, IPT_UI_NEXT_GROUP, diraxis[1][0]))
+ {
+ // took secondary X
+ }
+
+ // try to get a matching pair of buttons for page up/down
+ if (!avoidpaddles && consume_button_pair(assignments, IPT_UI_PAGE_UP, IPT_UI_PAGE_DOWN, buttonitems[SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1], buttonitems[SDL_GAMEPAD_BUTTON_LEFT_PADDLE1]))
+ {
+ // took upper paddles
+ }
+ else if (!avoidpaddles && consume_button_pair(assignments, IPT_UI_PAGE_UP, IPT_UI_PAGE_DOWN, buttonitems[SDL_GAMEPAD_BUTTON_RIGHT_PADDLE2], buttonitems[SDL_GAMEPAD_BUTTON_LEFT_PADDLE2]))
+ {
+ // took lower paddles
+ }
+ else
+ if (consume_trigger_pair(assignments, IPT_UI_PAGE_UP, IPT_UI_PAGE_DOWN, axisitems[SDL_GAMEPAD_AXIS_LEFT_TRIGGER].first, axisitems[SDL_GAMEPAD_AXIS_RIGHT_TRIGGER].first))
+ {
+ // took analog triggers
+ }
+ else if (consume_axis_pair(assignments, IPT_UI_PAGE_UP, IPT_UI_PAGE_DOWN, diraxis[1][1]))
+ {
+ // took secondary Y
+ }
+
+ // try to assign X button to UI clear
+ if (add_button_assignment(assignments, IPT_UI_CLEAR, { buttonitems[SDL_GAMEPAD_BUTTON_WEST] }))
+ {
+ buttonitems[SDL_GAMEPAD_BUTTON_WEST] = ITEM_ID_INVALID;
+ }
+ else
+ {
+ // otherwise try to find an unassigned button
+ for (auto [item, button, axis] : numberedbuttons)
+ {
+ if ((SDL_GAMEPAD_BUTTON_INVALID != button) && (ITEM_ID_INVALID != buttonitems[button]))
+ {
+ add_button_assignment(assignments, IPT_UI_CLEAR, { item });
+ buttonitems[button] = ITEM_ID_INVALID;
+ break;
+ }
+ else if ((SDL_GAMEPAD_AXIS_INVALID != axis) && (ITEM_ID_INVALID != axisitems[axis].second))
+ {
+ add_button_assignment(assignments, IPT_UI_CLEAR, { item });
+ axisitems[axis].second = ITEM_ID_INVALID;
+ break;
+ }
+ }
+ }
+
+ // try to assign B button to UI back
+ if (add_button_assignment(assignments, IPT_UI_BACK, { buttonitems[SDL_GAMEPAD_BUTTON_EAST] }))
+ {
+ buttonitems[SDL_GAMEPAD_BUTTON_WEST] = ITEM_ID_INVALID;
+ }
+ else
+ {
+ // otherwise try to find an unassigned button
+ for (auto [item, button, axis] : numberedbuttons)
+ {
+ if ((SDL_GAMEPAD_BUTTON_INVALID != button) && (ITEM_ID_INVALID != buttonitems[button]))
+ {
+ add_button_assignment(assignments, IPT_UI_CLEAR, { item });
+ buttonitems[button] = ITEM_ID_INVALID;
+ break;
+ }
+ else if ((SDL_GAMEPAD_AXIS_INVALID != axis) && (ITEM_ID_INVALID != axisitems[axis].second))
+ {
+ add_button_assignment(assignments, IPT_UI_CLEAR, { item });
+ axisitems[axis].second = ITEM_ID_INVALID;
+ break;
+ }
+ }
+ }
+
+ // try to assign Y button to UI help
+ if (add_button_assignment(assignments, IPT_UI_HELP, { buttonitems[SDL_GAMEPAD_BUTTON_NORTH] }))
+ {
+ buttonitems[SDL_GAMEPAD_BUTTON_NORTH] = ITEM_ID_INVALID;
+ }
+ else
+ {
+ // otherwise try to find an unassigned button
+ for (auto [item, button, axis] : numberedbuttons)
+ {
+ if ((SDL_GAMEPAD_BUTTON_INVALID != button) && (ITEM_ID_INVALID != buttonitems[button]))
+ {
+ add_button_assignment(assignments, IPT_UI_HELP, { item });
+ buttonitems[button] = ITEM_ID_INVALID;
+ break;
+ }
+ else if ((SDL_GAMEPAD_AXIS_INVALID != axis) && (ITEM_ID_INVALID != axisitems[axis].second))
+ {
+ add_button_assignment(assignments, IPT_UI_HELP, { item });
+ axisitems[axis].second = ITEM_ID_INVALID;
+ break;
+ }
+ }
+ }
+
+ // put focus previous/next on the shoulder buttons if available - this can be overloaded with zoom
+ if (add_button_pair_assignment(assignments, IPT_UI_FOCUS_PREV, IPT_UI_FOCUS_NEXT, buttonitems[SDL_GAMEPAD_BUTTON_LEFT_SHOULDER], buttonitems[SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER]))
+ {
+ // took shoulder buttons
+ }
+ else if (add_axis_pair_assignment(assignments, IPT_UI_FOCUS_PREV, IPT_UI_FOCUS_NEXT, diraxis[1][0]))
+ {
+ // took secondary X
+ }
+ else if (add_axis_pair_assignment(assignments, IPT_UI_FOCUS_PREV, IPT_UI_FOCUS_NEXT, diraxis[1][1]))
+ {
+ // took secondary Y
+ }
+
+ // put zoom on the secondary stick if available, or fall back to shoulder buttons
+ if (add_axis_pair_assignment(assignments, IPT_UI_ZOOM_OUT, IPT_UI_ZOOM_IN, diraxis[1][0]))
+ {
+ // took secondary X
+ if (axisitems[SDL_GAMEPAD_AXIS_LEFTX].first == diraxis[1][0])
+ add_button_assignment(assignments, IPT_UI_ZOOM_DEFAULT, { buttonitems[SDL_GAMEPAD_BUTTON_LEFT_STICK] });
+ else if (axisitems[SDL_GAMEPAD_AXIS_RIGHTX].first == diraxis[1][0])
+ add_button_assignment(assignments, IPT_UI_ZOOM_DEFAULT, { buttonitems[SDL_GAMEPAD_BUTTON_RIGHT_STICK] });
+ diraxis[1][0] = ITEM_ID_INVALID;
+ }
+ else if (add_axis_pair_assignment(assignments, IPT_UI_ZOOM_IN, IPT_UI_ZOOM_OUT, diraxis[1][1]))
+ {
+ // took secondary Y
+ if (axisitems[SDL_GAMEPAD_AXIS_LEFTY].first == diraxis[1][1])
+ add_button_assignment(assignments, IPT_UI_ZOOM_DEFAULT, { buttonitems[SDL_GAMEPAD_BUTTON_LEFT_STICK] });
+ else if (axisitems[SDL_GAMEPAD_AXIS_RIGHTY].first == diraxis[1][1])
+ add_button_assignment(assignments, IPT_UI_ZOOM_DEFAULT, { buttonitems[SDL_GAMEPAD_BUTTON_RIGHT_STICK] });
+ diraxis[1][1] = ITEM_ID_INVALID;
+ }
+ else if (consume_button_pair(assignments, IPT_UI_ZOOM_OUT, IPT_UI_ZOOM_IN, buttonitems[SDL_GAMEPAD_BUTTON_LEFT_SHOULDER], buttonitems[SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER]))
+ {
+ // took shoulder buttons
+ }
+
+ // set default assignments
+ device.set_default_assignments(std::move(assignments));
+ }
+
+ virtual void reset() override
+ {
+ sdl_joystick_device_base::reset();
+ clear_buffer();
+ }
+
+ virtual void process_event(SDL_Event const &event) override
+ {
+ if (!m_ctrldevice)
+ return;
+
+ switch (event.type)
+ {
+ case SDL_EVENT_GAMEPAD_AXIS_MOTION:
+ if (event.gaxis.axis < SDL_GAMEPAD_AXIS_COUNT)
+ {
+ switch (event.gaxis.axis)
+ {
+ case SDL_GAMEPAD_AXIS_LEFT_TRIGGER: // MAME wants negative values for triggers
+ case SDL_GAMEPAD_AXIS_RIGHT_TRIGGER:
+ m_controller.axes[event.gaxis.axis] = -normalize_absolute_axis(event.gaxis.value, -32'767, 32'767);
+ break;
+ default:
+ m_controller.axes[event.gaxis.axis] = normalize_absolute_axis(event.gaxis.value, -32'767, 32'767);
+ }
+ }
+ break;
+
+ case SDL_EVENT_GAMEPAD_BUTTON_DOWN:
+ case SDL_EVENT_GAMEPAD_BUTTON_UP:
+ if (event.gbutton.button < SDL_GAMEPAD_BUTTON_COUNT)
+ m_controller.buttons[event.gbutton.button] = (event.gbutton.down) ? 0x80 : 0x00;
+ break;
+
+ case SDL_EVENT_GAMEPAD_REMOVED:
+ osd_printf_verbose("Game Controller: %s [ID %s] disconnected\n", name(), id());
+ clear_instance();
+ clear_buffer();
+ close_device();
+ break;
+ }
+ }
+
+ void attach_device(SDL_Gamepad *ctrl)
+ {
+ assert(ctrl);
+ assert(!m_ctrldevice);
+
+ set_instance(SDL_GetJoystickID(SDL_GetGamepadJoystick(ctrl)));
+ m_ctrldevice = ctrl;
+
+ osd_printf_verbose("Game Controller: %s [ID %s] reconnected\n", name(), id());
+ }
+
+private:
+ // state information for a game controller
+ struct sdl_controller_state
+ {
+ s32 axes[SDL_GAMEPAD_AXIS_COUNT];
+ s32 buttons[SDL_GAMEPAD_BUTTON_COUNT];
+ };
+
+ sdl_controller_state m_controller;
+ SDL_Gamepad *m_ctrldevice;
+
+ void clear_buffer()
+ {
+ memset(&m_controller, 0, sizeof(m_controller));
+ }
+
+ void close_device()
+ {
+ if (m_ctrldevice)
+ {
+ SDL_CloseGamepad(m_ctrldevice);
+ m_ctrldevice = nullptr;
+ }
+ }
+};
+
+
+//============================================================
+// sdl_input_module
+//============================================================
+
+template <typename Info>
+class sdl_input_module :
+ public input_module_impl<Info, sdl_osd_interface>,
+ protected sdl_event_manager::subscriber
+{
+public:
+ sdl_input_module(char const *type, char const *name) :
+ input_module_impl<Info, sdl_osd_interface>(type, name)
+ {
+ }
+
+ virtual void exit() override
+ {
+ // unsubscribe for events
+ unsubscribe();
+
+ input_module_impl<Info, sdl_osd_interface>::exit();
+ }
+
+protected:
+ virtual void handle_event(SDL_Event const &event) override
+ {
+ // dispatch event to every device by default
+ this->devicelist().for_each_device(
+ [&event] (auto &device) { device.queue_events(&event, 1); });
+ }
+};
+
+
+//============================================================
+// sdl_keyboard_module
+//============================================================
+
+class sdl_keyboard_module : public sdl_input_module<sdl_keyboard_device>
+{
+public:
+ sdl_keyboard_module() :
+ sdl_input_module<sdl_keyboard_device>(OSD_KEYBOARDINPUT_PROVIDER, "sdl")
+ {
+ }
+
+ virtual void input_init(running_machine &machine) override
+ {
+ sdl_input_module<sdl_keyboard_device>::input_init(machine);
+
+ constexpr int event_types[] = {
+ int(SDL_EVENT_KEY_DOWN),
+ int(SDL_EVENT_KEY_UP) };
+
+ subscribe(osd(), event_types);
+
+ // Read our keymap and store a pointer to our table
+ sdlinput_read_keymap();
+
+ osd_printf_verbose("Keyboard: Start initialization\n");
+
+ int count = 0;
+ const auto keyboards = SDL_GetKeyboards(&count);
+
+ osd_printf_verbose("Keyboard: Using SDL 3.2+, found %d keyboard%c\n", count, count > 1 ? 's' : ' ');
+
+ // TODO: Multiple keyboard/mouse support is Windows-only in SDL 3.2; this will expand to Linux Wayland
+ // in SDL 3.4. X11 will return all of the available keyboards but only return keypresses for the system
+ // composite device #0 unless SDL is specially compiled (which its not in distro packages).
+ // On macOS only the system keyboard is supported.
+ auto &devinfo = create_device<sdl_keyboard_device>(
+ DEVICE_CLASS_KEYBOARD,
+ "System keyboard",
+ "System keyboard",
+ keyboards[0],
+ *m_key_trans_table);
+ osd_printf_verbose("Keyboard: Registered %s\n", devinfo.name());
+ SDL_free(keyboards);
+
+ osd_printf_verbose("Keyboard: End initialization\n");
+ }
+
+private:
+ void sdlinput_read_keymap()
+ {
+ keyboard_trans_table &default_table = keyboard_trans_table::instance();
+
+ // Allocate a block of translation entries big enough to hold what's in the default table
+ auto key_trans_entries = std::make_unique<key_trans_entry []>(default_table.size());
+
+ // copy the elements from the default table and ask SDL for key names
+ for (int i = 0; i < default_table.size(); i++)
+ {
+ key_trans_entries[i] = default_table[i];
+ char const *const name = SDL_GetScancodeName(SDL_Scancode(default_table[i].sdl_scancode));
+ if (name && *name)
+ key_trans_entries[i].ui_name = name;
+ }
+
+ // Allocate the trans table to be associated with the machine so we don't have to free it
+ m_key_trans_table = std::make_unique<keyboard_trans_table>(std::move(key_trans_entries), default_table.size());
+
+ if (!options()->bool_value(SDLOPTION_KEYMAP))
+ return;
+
+ const char *const keymap_filename = dynamic_cast<sdl_options const &>(*options()).keymap_file();
+ osd_printf_verbose("Keymap: Start reading keymap_file %s\n", keymap_filename);
+
+ FILE *const keymap_file = fopen(keymap_filename, "r");
+ if (!keymap_file)
+ {
+ osd_printf_warning("Keymap: Unable to open keymap %s, using default\n", keymap_filename);
+ return;
+ }
+
+ int line = 1;
+ int sdl3section = 0;
+ while (!feof(keymap_file))
+ {
+ char buf[256];
+
+ char *ret = fgets(buf, 255, keymap_file);
+ if (ret && buf[0] != '\n' && buf[0] != '#')
+ {
+ buf[255] = 0;
+ int len = strlen(buf);
+ if (len && buf[len - 1] == '\n')
+ buf[len - 1] = 0;
+ if (strncmp(buf, "[SDL3]", 6) == 0)
+ {
+ sdl3section = 1;
+ }
+ else if (sdl3section == 1)
+ {
+ char mks[41] = {0};
+ char sks[41] = {0};
+ char kns[41] = {0};
+
+ int n = sscanf(buf, "%40s %40s %40c\n", mks, sks, kns);
+ if (n != 3)
+ osd_printf_error("Keymap: Error on line %d : Expected 3 parameters, got %d\n", line, n);
+
+ int index = default_table.lookup_mame_index(mks);
+ int sk = lookup_sdl_code(sks);
+
+ if (sk >= 0 && index >= 0)
+ {
+ key_trans_entry &entry = (*m_key_trans_table)[index];
+ entry.sdl_scancode = sk;
+ entry.ui_name = const_cast<char *>(m_ui_names.emplace_back(kns).c_str());
+ osd_printf_verbose("Keymap: Mapped <%s> to <%s> with ui-text <%s>\n", sks, mks, kns);
+ }
+ else
+ {
+ osd_printf_error("Keymap: Error on line %d - %s key not found: %s\n", line, (sk<0) ? "sdl" : "mame", buf);
+ }
+ }
+ }
+ line++;
+ }
+ fclose(keymap_file);
+ osd_printf_verbose("Keymap: Processed %d lines\n", line);
+ }
+
+ std::unique_ptr<keyboard_trans_table> m_key_trans_table;
+ std::list<std::string> m_ui_names;
+};
+
+
+//============================================================
+// sdl_mouse_module
+//============================================================
+
+class sdl_mouse_module : public sdl_input_module<sdl_mouse_device>
+{
+public:
+ sdl_mouse_module() : sdl_input_module<sdl_mouse_device>(OSD_MOUSEINPUT_PROVIDER, "sdl")
+ {
+ }
+
+ virtual void input_init(running_machine &machine) override
+ {
+ sdl_input_module::input_init(machine);
+
+ constexpr int event_types[] = {
+ int(SDL_EVENT_MOUSE_MOTION),
+ int(SDL_EVENT_MOUSE_BUTTON_DOWN),
+ int(SDL_EVENT_MOUSE_BUTTON_UP),
+ int(SDL_EVENT_MOUSE_WHEEL) };
+
+ subscribe(osd(), event_types);
+
+ osd_printf_verbose("Mouse: Start initialization\n");
+
+ int count = 0;
+ const auto mice = SDL_GetMice(&count);
+
+ osd_printf_verbose("Mouse: Using SDL 3.2+, found %d %s\n", count, (count == 1) ? "mouse" : "mice");
+
+ // TODO: add mice other than the first one
+ auto &devinfo = create_device<sdl_mouse_device>(
+ DEVICE_CLASS_MOUSE,
+ "System mouse",
+ "System mouse");
+
+ osd_printf_verbose("Mouse: Registered %s\n", devinfo.name());
+ SDL_free(mice);
+
+ osd_printf_verbose("Mouse: End initialization\n");
+ }
+};
+
+
+//============================================================
+// sdl_lightgun_module
+//============================================================
+
+class sdl_lightgun_module : public sdl_input_module<sdl_mouse_device_base>
+{
+public:
+ sdl_lightgun_module() : sdl_input_module<sdl_mouse_device_base>(OSD_LIGHTGUNINPUT_PROVIDER, "sdl")
+ {
+ }
+
+ virtual void input_init(running_machine &machine) override
+ {
+ auto &sdlopts = dynamic_cast<sdl_options const &>(*options());
+ sdl_input_module::input_init(machine);
+ bool const dual(sdlopts.dual_lightgun());
+
+ if (!dual)
+ {
+ constexpr int event_types[] = {
+ int(SDL_EVENT_MOUSE_MOTION),
+ int(SDL_EVENT_MOUSE_BUTTON_DOWN),
+ int(SDL_EVENT_MOUSE_BUTTON_UP),
+ int(SDL_EVENT_MOUSE_WHEEL),
+ int(SDL_EVENT_WINDOW_SHOWN),
+ int(SDL_EVENT_WINDOW_HIDDEN),
+ int(SDL_EVENT_WINDOW_EXPOSED),
+ int(SDL_EVENT_WINDOW_MOVED),
+ int(SDL_EVENT_WINDOW_RESIZED),
+ int(SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED),
+ int(SDL_EVENT_WINDOW_METAL_VIEW_RESIZED),
+ int(SDL_EVENT_WINDOW_MINIMIZED),
+ int(SDL_EVENT_WINDOW_MAXIMIZED),
+ int(SDL_EVENT_WINDOW_RESTORED),
+ int(SDL_EVENT_WINDOW_MOUSE_ENTER),
+ int(SDL_EVENT_WINDOW_MOUSE_LEAVE),
+ int(SDL_EVENT_WINDOW_FOCUS_GAINED),
+ int(SDL_EVENT_WINDOW_FOCUS_LOST),
+ int(SDL_EVENT_WINDOW_CLOSE_REQUESTED),
+ int(SDL_EVENT_WINDOW_HIT_TEST),
+ int(SDL_EVENT_WINDOW_ICCPROF_CHANGED),
+ int(SDL_EVENT_WINDOW_DISPLAY_CHANGED),
+ int(SDL_EVENT_WINDOW_DISPLAY_SCALE_CHANGED),
+ int(SDL_EVENT_WINDOW_SAFE_AREA_CHANGED),
+ int(SDL_EVENT_WINDOW_OCCLUDED),
+ int(SDL_EVENT_WINDOW_ENTER_FULLSCREEN),
+ int(SDL_EVENT_WINDOW_LEAVE_FULLSCREEN),
+ int(SDL_EVENT_WINDOW_DESTROYED),
+ int(SDL_EVENT_WINDOW_HDR_STATE_CHANGED) };
+ subscribe(osd(), event_types);
+ }
+ else
+ {
+ constexpr int event_types[] = {
+ int(SDL_EVENT_MOUSE_BUTTON_DOWN),
+ int(SDL_EVENT_MOUSE_BUTTON_UP) };
+ subscribe(osd(), event_types);
+ }
+
+ osd_printf_verbose("Lightgun: Start initialization\n");
+
+ if (!dual)
+ {
+ auto &devinfo = create_device<sdl_lightgun_device>(
+ DEVICE_CLASS_LIGHTGUN,
+ "System pointer gun 1",
+ "System pointer gun 1");
+ osd_printf_verbose("Lightgun: Registered %s\n", devinfo.name());
+ }
+ else
+ {
+ auto &dev1info = create_device<sdl_dual_lightgun_device>(
+ DEVICE_CLASS_LIGHTGUN,
+ "System pointer gun 1",
+ "System pointer gun 1",
+ 0);
+ osd_printf_verbose("Lightgun: Registered %s\n", dev1info.name());
+
+ auto &dev2info = create_device<sdl_dual_lightgun_device>(
+ DEVICE_CLASS_LIGHTGUN,
+ "System pointer gun 2",
+ "System pointer gun 2",
+ 1);
+ osd_printf_verbose("Lightgun: Registered %s\n", dev2info.name());
+ }
+
+ osd_printf_verbose("Lightgun: End initialization\n");
+ }
+};
+
+
+//============================================================
+// sdl_joystick_module_base
+//============================================================
+
+class sdl_joystick_module_base : public sdl_input_module<sdl_joystick_device_base>
+{
+protected:
+ sdl_joystick_module_base(char const *name) :
+ sdl_input_module<sdl_joystick_device_base>(OSD_JOYSTICKINPUT_PROVIDER, name),
+ m_initialized_joystick(false),
+ m_initialized_haptic(false)
+ {
+ }
+
+ virtual ~sdl_joystick_module_base()
+ {
+ assert(!m_initialized_joystick);
+ assert(!m_initialized_haptic);
+ }
+
+ bool have_joystick() const { return m_initialized_joystick; }
+ bool have_haptic() const { return m_initialized_haptic; }
+
+ void init_joystick()
+ {
+ assert(!m_initialized_joystick);
+ assert(!m_initialized_haptic);
+
+ m_initialized_joystick = SDL_InitSubSystem(SDL_INIT_JOYSTICK);
+ if (!m_initialized_joystick)
+ {
+ osd_printf_error("Could not initialize SDL Joystick subsystem: %s.\n", SDL_GetError());
+ return;
+ }
+
+ m_initialized_haptic = SDL_InitSubSystem(SDL_INIT_HAPTIC);
+ if (!m_initialized_haptic)
+ osd_printf_verbose("Could not initialize SDL Haptic subsystem: %s.\n", SDL_GetError());
+ }
+
+ void quit_joystick()
+ {
+ if (m_initialized_joystick)
+ {
+ SDL_QuitSubSystem(SDL_INIT_JOYSTICK);
+ m_initialized_joystick = false;
+ }
+
+ if (m_initialized_haptic)
+ {
+ SDL_QuitSubSystem(SDL_INIT_HAPTIC);
+ m_initialized_haptic = false;
+ }
+ }
+
+ sdl_joystick_device *create_joystick_device(SDL_JoystickID sdl_id, bool sixaxis)
+ {
+ // open the joystick device
+ SDL_Joystick *const joy = SDL_OpenJoystick(sdl_id);
+ if (!joy)
+ {
+ osd_printf_error("Joystick: Could not open SDL joystick %d: %s.\n", (int)sdl_id, SDL_GetError());
+ return nullptr;
+ }
+
+ // get basic info
+ char const *const name = SDL_GetJoystickName(joy);
+ SDL_GUID guid = SDL_GetJoystickGUID(joy);
+ char guid_str[256];
+ guid_str[0] = '\0';
+ SDL_GUIDToString(guid, guid_str, sizeof(guid_str) - 1);
+ char const *const serial = SDL_GetJoystickSerial(joy);
+ std::string id(guid_str);
+ if (serial)
+ id.append(1, '-').append(serial);
+
+ // print some diagnostic info
+ osd_printf_verbose("Joystick: %s [GUID %s] Vendor ID %04X, Product ID %04X, Revision %04X, Serial %s\n",
+ name ? name : "<nullptr>",
+ guid_str,
+ SDL_GetJoystickVendor(joy),
+ SDL_GetJoystickProduct(joy),
+ SDL_GetJoystickProductVersion(joy),
+ serial ? serial : "<nullptr>");
+ osd_printf_verbose("Joystick: ... %d axes, %d buttons %d hats %d balls\n",
+ SDL_GetNumJoystickAxes(joy),
+ SDL_GetNumJoystickButtons(joy),
+ SDL_GetNumJoystickHats(joy),
+ SDL_GetNumJoystickBalls(joy));
+ if (SDL_GetNumJoystickButtons(joy) > MAX_BUTTONS)
+ osd_printf_verbose("Joystick: ... Has %d buttons which exceeds supported %d buttons\n", SDL_GetNumJoystickButtons(joy), MAX_BUTTONS);
+
+ // instantiate device
+ sdl_joystick_device &devinfo = sixaxis
+ ? create_device<sdl_sixaxis_joystick_device>(DEVICE_CLASS_JOYSTICK, name ? name : guid_str, guid_str, joy, serial)
+ : create_device<sdl_joystick_device>(DEVICE_CLASS_JOYSTICK, name ? name : guid_str, guid_str, joy, serial);
+
+ if (devinfo.has_haptic())
+ osd_printf_verbose("Joystick: ... Has haptic capability\n");
+ else
+ osd_printf_verbose("Joystick: ... Does not have haptic capability\n");
+
+ return &devinfo;
+ }
+
+ void dispatch_joystick_event(SDL_Event const &event)
+ {
+ // figure out which joystick this event is destined for
+ sdl_joystick_device_base *const target_device = find_joystick(event.jdevice.which); // FIXME: this depends on SDL_JoystickID being the same size as Sint32
+
+ // if we find a matching joystick, dispatch the event to the joystick
+ if (target_device)
+ target_device->queue_events(&event, 1);
+ }
+
+ device_info *find_reconnect_match(SDL_GUID const &guid, char const *serial)
+ {
+ char guid_str[256];
+ guid_str[0] = '\0';
+ SDL_GUIDToString(guid, guid_str, sizeof(guid_str) - 1);
+ auto target_device = std::find_if(
+ devicelist().begin(),
+ devicelist().end(),
+ [&guid_str, &serial] (auto const &device)
+ {
+ return device->reconnect_match(guid_str, serial);
+ });
+ return (devicelist().end() != target_device) ? target_device->get() : nullptr;
+ }
+
+ sdl_joystick_device_base *find_joystick(SDL_JoystickID instance)
+ {
+ for (auto &device : devicelist())
+ {
+ if (device->is_instance(instance))
+ return device.get();
+ }
+ return nullptr;
+ }
+
+private:
+ bool m_initialized_joystick;
+ bool m_initialized_haptic;
+};
+
+
+//============================================================
+// sdl_joystick_module
+//============================================================
+
+class sdl_joystick_module : public sdl_joystick_module_base
+{
+public:
+ sdl_joystick_module() : sdl_joystick_module_base("sdljoy")
+ {
+ }
+
+ virtual void exit() override
+ {
+ sdl_joystick_module_base::exit();
+
+ quit_joystick();
+ }
+
+ virtual void input_init(running_machine &machine) override
+ {
+ auto &sdlopts = dynamic_cast<sdl_options const &>(*options());
+ bool const sixaxis_mode = sdlopts.sixaxis();
+
+ if (!sdlopts.debug() && sdlopts.background_input())
+ SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1");
+
+ init_joystick();
+ if (!have_joystick())
+ return;
+
+ sdl_joystick_module_base::input_init(machine);
+
+ osd_printf_verbose("Joystick: Start initialization\n");
+ int stick_count = 0;
+ const auto joysticks = SDL_GetJoysticks(&stick_count);
+ for (int physical_stick = 0; physical_stick < stick_count; physical_stick++)
+ {
+ create_joystick_device(joysticks[physical_stick], sixaxis_mode);
+ }
+ SDL_free(joysticks);
+
+ constexpr int event_types[] = {
+ int(SDL_EVENT_JOYSTICK_AXIS_MOTION),
+ int(SDL_EVENT_JOYSTICK_BALL_MOTION),
+ int(SDL_EVENT_JOYSTICK_HAT_MOTION),
+ int(SDL_EVENT_JOYSTICK_BUTTON_DOWN),
+ int(SDL_EVENT_JOYSTICK_BUTTON_UP),
+ int(SDL_EVENT_JOYSTICK_ADDED),
+ int(SDL_EVENT_JOYSTICK_REMOVED) };
+ subscribe(osd(), event_types);
+
+ osd_printf_verbose("Joystick: End initialization\n");
+ }
+
+ virtual void handle_event(SDL_Event const &event) override
+ {
+ if (SDL_EVENT_JOYSTICK_ADDED == event.type)
+ {
+ SDL_Joystick *const joy = SDL_OpenJoystick(event.jdevice.which);
+ if (!joy)
+ {
+ osd_printf_error("Joystick: Could not open SDL joystick %d: %s.\n", event.jdevice.which, SDL_GetError());
+ }
+ else
+ {
+ SDL_GUID guid = SDL_GetJoystickGUID(joy);
+ char const *const serial = SDL_GetJoystickSerial(joy);
+ auto *const target_device = find_reconnect_match(guid, serial);
+ if (target_device)
+ {
+ auto &devinfo = dynamic_cast<sdl_joystick_device &>(*target_device);
+ devinfo.attach_device(joy);
+ }
+ else
+ {
+ SDL_CloseJoystick(joy);
+ }
+ }
+ }
+ else
+ {
+ dispatch_joystick_event(event);
+ }
+ }
+};
+
+
+//============================================================
+// sdl_game_controller_module
+//============================================================
+
+class sdl_game_controller_module : public sdl_joystick_module_base
+{
+public:
+ sdl_game_controller_module() :
+ sdl_joystick_module_base("sdlgame"),
+ m_initialized_game_controller(false)
+ {
+ }
+
+ virtual void exit() override
+ {
+ sdl_joystick_module_base::exit();
+
+ if (m_initialized_game_controller)
+ SDL_QuitSubSystem(SDL_INIT_GAMEPAD);
+
+ quit_joystick();
+ }
+
+ virtual void input_init(running_machine &machine) override
+ {
+ auto &sdlopts = dynamic_cast<sdl_options const &>(*options());
+ bool const sixaxis_mode = sdlopts.sixaxis();
+
+ if (!sdlopts.debug() && sdlopts.background_input())
+ SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1");
+
+ init_joystick();
+ if (!have_joystick())
+ return;
+
+ m_initialized_game_controller = SDL_InitSubSystem(SDL_INIT_GAMEPAD);
+ if (m_initialized_game_controller)
+ {
+ char const *const mapfile = sdlopts.controller_mapping_file();
+ if (mapfile && *mapfile && std::strcmp(mapfile, OSDOPTVAL_NONE))
+ {
+ auto const count = SDL_AddGamepadMappingsFromFile(mapfile);
+ if (0 <= count)
+ osd_printf_verbose("Game Controller: %d controller mapping(s) added from file [%s].\n", count, mapfile);
+ else
+ osd_printf_error("Game Controller: Error adding mappings from file [%s]: %s.\n", mapfile, SDL_GetError());
+ }
+ }
+ else
+ {
+ osd_printf_warning("Could not initialize SDL Game Controller: %s.\n", SDL_GetError());
+ }
+
+ sdl_joystick_module_base::input_init(machine);
+
+ osd_printf_verbose("Game Controller: Start initialization\n");
+ int stick_count = 0;
+ const auto joysticks = SDL_GetJoysticks(&stick_count);
+ for (int physical_stick = 0; physical_stick < stick_count; physical_stick++)
+ {
+ // try to open as a game controller
+ SDL_Gamepad *ctrl = nullptr;
+ if (m_initialized_game_controller && SDL_IsGamepad(joysticks[physical_stick]))
+ {
+ ctrl = SDL_OpenGamepad(joysticks[physical_stick]);
+ if (!ctrl)
+ osd_printf_warning("Game Controller: Could not open SDL game controller %d: %s.\n", joysticks[physical_stick], SDL_GetError());
+ }
+
+ // fall back to joystick API if necessary
+ if (!ctrl)
+ create_joystick_device(joysticks[physical_stick], sixaxis_mode);
+ else
+ create_game_controller_device(joysticks[physical_stick], ctrl);
+ }
+ SDL_free(joysticks);
+
+ constexpr int joy_event_types[] = {
+ int(SDL_EVENT_JOYSTICK_AXIS_MOTION),
+ int(SDL_EVENT_JOYSTICK_BALL_MOTION),
+ int(SDL_EVENT_JOYSTICK_HAT_MOTION),
+ int(SDL_EVENT_JOYSTICK_BUTTON_DOWN),
+ int(SDL_EVENT_JOYSTICK_BUTTON_UP),
+ int(SDL_EVENT_JOYSTICK_ADDED),
+ int(SDL_EVENT_JOYSTICK_REMOVED) };
+ constexpr int event_types[] = {
+ int(SDL_EVENT_JOYSTICK_AXIS_MOTION),
+ int(SDL_EVENT_JOYSTICK_BALL_MOTION),
+ int(SDL_EVENT_JOYSTICK_HAT_MOTION),
+ int(SDL_EVENT_JOYSTICK_BUTTON_DOWN),
+ int(SDL_EVENT_JOYSTICK_BUTTON_UP),
+ int(SDL_EVENT_JOYSTICK_ADDED),
+ int(SDL_EVENT_JOYSTICK_REMOVED),
+ int(SDL_EVENT_GAMEPAD_AXIS_MOTION),
+ int(SDL_EVENT_GAMEPAD_BUTTON_DOWN),
+ int(SDL_EVENT_GAMEPAD_BUTTON_UP),
+ int(SDL_EVENT_GAMEPAD_ADDED),
+ int(SDL_EVENT_GAMEPAD_REMOVED) };
+ if (m_initialized_game_controller)
+ subscribe(osd(), event_types);
+ else
+ subscribe(osd(), joy_event_types);
+
+ osd_printf_verbose("Game Controller: End initialization\n");
+ }
+
+ virtual void handle_event(SDL_Event const &event) override
+ {
+ switch (event.type)
+ {
+ case SDL_EVENT_JOYSTICK_ADDED:
+ {
+ // make sure this isn't an event for a reconnected game controller
+ auto const controller = find_joystick(event.jdevice.which);
+ if (find_joystick(event.jdevice.which))
+ {
+ osd_printf_verbose(
+ "Game Controller: Got SDL joystick added event for reconnected game controller %s [ID %s]\n",
+ controller->name(),
+ controller->id());
+ break;
+ }
+
+ SDL_Joystick *const joy = SDL_OpenJoystick(event.jdevice.which);
+ if (!joy)
+ {
+ osd_printf_error("Joystick: Could not open SDL joystick %d: %s.\n", event.jdevice.which, SDL_GetError());
+ break;
+ }
+
+ SDL_GUID guid = SDL_GetJoystickGUID(joy);
+ char const *const serial = SDL_GetJoystickSerial(joy);
+ auto *const target_device = find_reconnect_match(guid, serial);
+ if (target_device)
+ {
+ // if this downcast fails, opening as a game controller worked initially but failed on reconnection
+ auto *const devinfo = dynamic_cast<sdl_joystick_device *>(target_device);
+ if (devinfo)
+ devinfo->attach_device(joy);
+ else
+ SDL_CloseJoystick(joy);
+ }
+ else
+ {
+ SDL_CloseJoystick(joy);
+ }
+ }
+ break;
+
+ // for devices supported by the game controller API, this is received before the corresponding SDL_EVENT_JOYSTICK_ADDED
+ case SDL_EVENT_GAMEPAD_ADDED:
+ if (m_initialized_game_controller)
+ {
+ SDL_Gamepad *const ctrl = SDL_OpenGamepad(event.cdevice.which);
+ if (!ctrl)
+ {
+ osd_printf_error("Game Controller: Could not open SDL game controller %d: %s.\n", event.cdevice.which, SDL_GetError());
+ break;
+ }
+
+ SDL_GUID guid = SDL_GetJoystickGUIDForID(event.cdevice.which);
+ char const *const serial = SDL_GetGamepadSerial(ctrl);
+ auto *const target_device = find_reconnect_match(guid, serial);
+ if (target_device)
+ {
+ // downcast can fail if there was an error opening the device as a game controller the first time
+ auto *const devinfo = dynamic_cast<sdl_game_controller_device *>(target_device);
+ if (devinfo)
+ devinfo->attach_device(ctrl);
+ else
+ SDL_CloseGamepad(ctrl);
+ }
+ else
+ {
+ SDL_CloseGamepad(ctrl);
+ }
+ }
+ break;
+
+ default:
+ dispatch_joystick_event(event);
+ }
+ }
+
+private:
+ sdl_game_controller_device *create_game_controller_device(SDL_JoystickID sdl_id, SDL_Gamepad *ctrl)
+ {
+ // get basic info
+ char const *const name = SDL_GetGamepadName(ctrl);
+ SDL_GUID guid = SDL_GetJoystickGUIDForID(sdl_id);
+ char guid_str[256];
+ guid_str[0] = '\0';
+ SDL_GUIDToString(guid, guid_str, sizeof(guid_str) - 1);
+ char const *const serial = SDL_GetGamepadSerial(ctrl);
+ std::string id(guid_str);
+ if (serial)
+ id.append(1, '-').append(serial);
+
+ // print some diagnostic info
+ osd_printf_verbose("Game Controller: %s [GUID %s] Vendor ID %04X, Product ID %04X, Revision %04X, Serial %s\n",
+ name ? name : "<nullptr>",
+ guid_str,
+ SDL_GetGamepadVendor(ctrl),
+ SDL_GetGamepadProduct(ctrl),
+ SDL_GetGamepadProductVersion(ctrl),
+ serial ? serial : "<nullptr>");
+ char *const mapping = SDL_GetGamepadMapping(ctrl);
+ if (mapping)
+ {
+ osd_printf_verbose("Game Controller: ... mapping [%s]\n", mapping);
+ SDL_free(mapping);
+ }
+ else
+ {
+ osd_printf_verbose("Game Controller: ... no mapping\n");
+ }
+
+ // instantiate device
+ sdl_game_controller_device &devinfo = create_device<sdl_game_controller_device>(
+ DEVICE_CLASS_JOYSTICK,
+ name ? name : guid_str,
+ guid_str,
+ ctrl,
+ serial);
+ return &devinfo;
+ }
+
+ bool m_initialized_game_controller;
+};
+
+} // anonymous namespace
+
+} // namespace osd
+
+
+#else // defined(SDLMAME_SDL3)
+
+namespace osd {
+
+namespace {
+MODULE_NOT_SUPPORTED(sdl_keyboard_module, OSD_KEYBOARDINPUT_PROVIDER, "sdl")
+MODULE_NOT_SUPPORTED(sdl_mouse_module, OSD_MOUSEINPUT_PROVIDER, "sdl")
+MODULE_NOT_SUPPORTED(sdl_lightgun_module, OSD_LIGHTGUNINPUT_PROVIDER, "sdl")
+MODULE_NOT_SUPPORTED(sdl_joystick_module, OSD_JOYSTICKINPUT_PROVIDER, "sdljoy")
+MODULE_NOT_SUPPORTED(sdl_game_controller_module, OSD_JOYSTICKINPUT_PROVIDER, "sdlgame")
+} // anonymous namespace
+
+} // namespace osd
+
+#endif // defined(SDLMAME_SDL3)
+
+#ifdef SDLMAME_SDL3
+MODULE_DEFINITION(KEYBOARDINPUT_SDL, osd::sdl_keyboard_module)
+MODULE_DEFINITION(MOUSEINPUT_SDL, osd::sdl_mouse_module)
+MODULE_DEFINITION(LIGHTGUNINPUT_SDL, osd::sdl_lightgun_module)
+MODULE_DEFINITION(JOYSTICKINPUT_SDLJOY, osd::sdl_joystick_module)
+MODULE_DEFINITION(JOYSTICKINPUT_SDLGAME, osd::sdl_game_controller_module)
+#endif
diff --git a/src/osd/modules/input/input_x11.cpp b/src/osd/modules/input/input_x11.cpp
index 5230dfd7164..8fcaf0fa461 100644
--- a/src/osd/modules/input/input_x11.cpp
+++ b/src/osd/modules/input/input_x11.cpp
@@ -16,7 +16,7 @@
#include "input_common.h"
-#include "sdl/osdsdl.h"
+#include "osdsdl.h"
// MAME headers
#include "inpttype.h"
diff --git a/src/osd/modules/lib/osdlib_unix.cpp b/src/osd/modules/lib/osdlib_unix.cpp
index 1cfa9b1f183..1b924cae7f3 100644
--- a/src/osd/modules/lib/osdlib_unix.cpp
+++ b/src/osd/modules/lib/osdlib_unix.cpp
@@ -2,7 +2,7 @@
// copyright-holders:Olivier Galibert, R. Belmont
//============================================================
//
-// sdlos_*.c - OS specific low level code
+// osdlib_unix.cpp - OS specific low level code for POSIX-like systems
//
// SDLMAME by Olivier Galibert and R. Belmont
//
@@ -12,7 +12,11 @@
#include "osdcore.h"
#include "osdlib.h"
+#ifdef SDLMAME_SDL3
+#include <SDL3/SDL.h>
+#else
#include <SDL2/SDL.h>
+#endif
#include <csignal>
#include <cstdio>
@@ -183,7 +187,11 @@ std::error_condition osd_set_clipboard_text(std::string_view text) noexcept
try
{
std::string const clip(text); // need to do this to ensure there's a terminating NUL for SDL
+ #ifdef SDLMAME_SDL3
+ if (!SDL_SetClipboardText(clip.c_str()))
+ #else
if (0 > SDL_SetClipboardText(clip.c_str()))
+ #endif
{
// SDL_GetError returns a message, can't really convert it to an error condition
return std::errc::io_error; // TODO: better error code?
diff --git a/src/osd/modules/lib/osdobj_common.cpp b/src/osd/modules/lib/osdobj_common.cpp
index 82fabfdc0ba..46f1dac6a6b 100644
--- a/src/osd/modules/lib/osdobj_common.cpp
+++ b/src/osd/modules/lib/osdobj_common.cpp
@@ -237,17 +237,28 @@ void osd_common_t::register_options()
#if !defined(OSD_WINDOWS) && !defined(SDLMAME_WIN32)
REGISTER_MODULE(m_mod_man, RENDERER_BGFX); // try BGFX after OpenGL on other operating systems for now
#endif
+#ifdef SDLMAME_SDL3
+ REGISTER_MODULE(m_mod_man, RENDERER_SDL3ACCEL);
+#if !defined(SDLMAME_EMSCRIPTEN)
+ REGISTER_MODULE(m_mod_man, RENDERER_SDL3SOFT);
+#endif
+#else
REGISTER_MODULE(m_mod_man, RENDERER_SDL2);
#if !defined(SDLMAME_EMSCRIPTEN)
REGISTER_MODULE(m_mod_man, RENDERER_SDL1);
#endif
+#endif
REGISTER_MODULE(m_mod_man, RENDERER_NONE);
REGISTER_MODULE(m_mod_man, SOUND_WASAPI);
REGISTER_MODULE(m_mod_man, SOUND_XAUDIO2);
REGISTER_MODULE(m_mod_man, SOUND_COREAUDIO);
REGISTER_MODULE(m_mod_man, SOUND_JS);
+#ifdef SDLMAME_SDL3
+ REGISTER_MODULE(m_mod_man, SOUND_SDL3);
+#else
REGISTER_MODULE(m_mod_man, SOUND_SDL);
+#endif
#ifndef NO_USE_PORTAUDIO
REGISTER_MODULE(m_mod_man, SOUND_PORTAUDIO);
#endif
@@ -284,26 +295,34 @@ void osd_common_t::register_options()
#endif
REGISTER_MODULE(m_mod_man, MIDI_NONE);
+#if defined(SDLMAME_SDL2) || defined(SDLMAME_SDL3)
REGISTER_MODULE(m_mod_man, KEYBOARDINPUT_SDL);
+#endif
REGISTER_MODULE(m_mod_man, KEYBOARDINPUT_RAWINPUT);
REGISTER_MODULE(m_mod_man, KEYBOARDINPUT_DINPUT);
REGISTER_MODULE(m_mod_man, KEYBOARDINPUT_WIN32);
REGISTER_MODULE(m_mod_man, KEYBOARD_NONE);
+#if defined(SDLMAME_SDL2) || defined(SDLMAME_SDL3)
REGISTER_MODULE(m_mod_man, MOUSEINPUT_SDL);
+#endif
REGISTER_MODULE(m_mod_man, MOUSEINPUT_RAWINPUT);
REGISTER_MODULE(m_mod_man, MOUSEINPUT_DINPUT);
REGISTER_MODULE(m_mod_man, MOUSEINPUT_WIN32);
REGISTER_MODULE(m_mod_man, MOUSE_NONE);
+#if defined(SDLMAME_SDL2) || defined(SDLMAME_SDL3)
REGISTER_MODULE(m_mod_man, LIGHTGUNINPUT_SDL);
+#endif
REGISTER_MODULE(m_mod_man, LIGHTGUN_X11);
REGISTER_MODULE(m_mod_man, LIGHTGUNINPUT_RAWINPUT);
REGISTER_MODULE(m_mod_man, LIGHTGUNINPUT_WIN32);
REGISTER_MODULE(m_mod_man, LIGHTGUN_NONE);
+#if defined(SDLMAME_SDL2) || defined(SDLMAME_SDL3)
REGISTER_MODULE(m_mod_man, JOYSTICKINPUT_SDLGAME);
REGISTER_MODULE(m_mod_man, JOYSTICKINPUT_SDLJOY);
+#endif
REGISTER_MODULE(m_mod_man, JOYSTICKINPUT_WINHYBRID);
REGISTER_MODULE(m_mod_man, JOYSTICKINPUT_DINPUT);
REGISTER_MODULE(m_mod_man, JOYSTICKINPUT_XINPUT);
diff --git a/src/osd/modules/monitor/monitor_sdl.cpp b/src/osd/modules/monitor/monitor_sdl.cpp
index 679c7a4f97d..d96654a41dd 100644
--- a/src/osd/modules/monitor/monitor_sdl.cpp
+++ b/src/osd/modules/monitor/monitor_sdl.cpp
@@ -18,7 +18,11 @@
#include "osdcore.h"
#include "window.h"
+#ifdef SDLMAME_SDL3
+#include <SDL3/SDL.h>
+#else
#include <SDL2/SDL.h>
+#endif
#include <algorithm>
@@ -44,6 +48,7 @@ public:
private:
void refresh() override
{
+#ifndef SDLMAME_SDL3
SDL_DisplayMode dmode;
#if defined(SDLMAME_WIN32)
@@ -51,6 +56,7 @@ private:
#else
SDL_GetCurrentDisplayMode(oshandle(), &dmode);
#endif
+#endif
SDL_Rect dimensions;
SDL_GetDisplayBounds(oshandle(), &dimensions);
@@ -101,8 +107,11 @@ public:
{
if (!m_initialized)
return nullptr;
-
+#ifdef SDLMAME_SDL3
+ std::uint64_t display = SDL_GetDisplayForWindow(static_cast<const sdl_window_info &>(window).platform_window());
+#else
std::uint64_t display = SDL_GetWindowDisplayIndex(static_cast<const sdl_window_info &>(window).platform_window());
+#endif
return monitor_from_handle(display);
}
@@ -115,14 +124,23 @@ protected:
osd_printf_verbose("Enter init_monitors\n");
- for (i = 0; i < SDL_GetNumVideoDisplays(); i++)
+#ifdef SDLMAME_SDL3
+ int num_displays = 0;
+ const auto displays = SDL_GetDisplays(&num_displays);
+#else
+ int num_displays = SDL_GetNumVideoDisplays();
+#endif
+ for (i = 0; i < num_displays; i++)
{
char temp[64];
snprintf(temp, sizeof(temp) - 1, "%s%d", OSDOPTION_SCREEN, i);
// allocate a new monitor info
+#ifdef SDLMAME_SDL3
+ std::shared_ptr<osd_monitor_info> monitor = std::make_shared<sdl_monitor_info>(*this, displays[i], temp, 1.0f);
+#else
std::shared_ptr<osd_monitor_info> monitor = std::make_shared<sdl_monitor_info>(*this, i, temp, 1.0f);
-
+#endif
osd_printf_verbose("Adding monitor %s (%d x %d)\n",
monitor->devicename(),
monitor->position_size().width(), monitor->position_size().height());
@@ -155,8 +173,14 @@ private:
osdrect_to_sdlrect(rect2, sdl2);
SDL_Rect intersection;
+#ifdef SDLMAME_SDL3
+ if (SDL_GetRectIntersection(&sdl1, &sdl2, &intersection))
+#else
if (SDL_IntersectRect(&sdl1, &sdl2, &intersection))
+#endif
+ {
return intersection.w + intersection.h;
+ }
return 0;
}
diff --git a/src/osd/modules/opengl/osd_opengl.h b/src/osd/modules/opengl/osd_opengl.h
index 712b3b880b4..9d3b6f2c60c 100644
--- a/src/osd/modules/opengl/osd_opengl.h
+++ b/src/osd/modules/opengl/osd_opengl.h
@@ -9,15 +9,15 @@
*
***************************************************************/
- #ifndef _OSD_OPENGL_H
- #define _OSD_OPENGL_H
+ #ifndef MAME_OSD_OPENGL_OSD_OPENGL_H
+ #define MAME_OSD_OPENGL_OSD_OPENGL_H
#if USE_OPENGL
/* equivalent to #include <GL/gl.h>
* #include <GL/glext.h>
*/
- #ifdef OSD_WINDOWS
+ #if defined(OSD_WINDOWS)
#ifdef _MSC_VER
#include <windows.h>
#include "GL/GL.h"
@@ -35,6 +35,9 @@
#elif defined(OSD_MAC)
#include <OpenGL/gl.h>
#include <OpenGL/glext.h>
+ #elif defined(SDLMAME_SDL3)
+ #include <SDL3/SDL_version.h>
+ #include <SDL3/SDL_opengl.h>
#else
#include <SDL2/SDL_version.h>
#include <SDL2/SDL_opengl.h>
@@ -114,7 +117,7 @@
#endif /* USE_OPENGL */
- #endif /* _OSD_OPENGL_H */
+ #endif /* MAME_OSD_OPENGL_OSD_OPENGL_H */
#else /* MANGLE */
/***************************************************************
diff --git a/src/osd/modules/render/draw13.cpp b/src/osd/modules/render/draw13.cpp
index a36ee8a8f52..5ed8008b4aa 100644
--- a/src/osd/modules/render/draw13.cpp
+++ b/src/osd/modules/render/draw13.cpp
@@ -14,7 +14,7 @@
#include "modules/osdmodule.h"
-#if defined(OSD_SDL)
+#if defined(OSD_SDL) && !defined(SDLMAME_SDL3)
// OSD headers
#include "sdlopts.h"
@@ -1181,12 +1181,10 @@ copy_info_t const video_sdl2::s_blit_info_default[] =
} // namespace osd
-
-#else // defined(OSD_SDL)
+#else // defined(OSD_SDL) && !defined(SDLMAME_SDL3)
namespace osd { namespace { MODULE_NOT_SUPPORTED(video_sdl2, OSD_RENDERER_PROVIDER, "accel") } }
-#endif // defined(OSD_SDL)
-
+#endif // defined(OSD_SDL) && !defined(SDLMAME_SDL3)
MODULE_DEFINITION(RENDERER_SDL2, osd::video_sdl2)
diff --git a/src/osd/modules/render/drawbgfx.cpp b/src/osd/modules/render/drawbgfx.cpp
index ef90478e59e..d3afeca046b 100644
--- a/src/osd/modules/render/drawbgfx.cpp
+++ b/src/osd/modules/render/drawbgfx.cpp
@@ -45,16 +45,18 @@
#if defined(SDLMAME_WIN32) || defined(OSD_WINDOWS)
// standard windows headers
#include <windows.h>
-#if defined(SDLMAME_WIN32)
+#if defined(SDLMAME_WIN32) && !defined(SDLMAME_SDL3)
#include <SDL2/SDL_syswm.h>
#endif
#else
#if defined(OSD_MAC)
extern void *GetOSWindow(void *wincontroller);
#else
+#ifndef SDLMAME_SDL3
#include <SDL2/SDL_syswm.h>
#endif
#endif
+#endif
#include <bgfx/bgfx.h>
#include <bgfx/platform.h>
@@ -385,7 +387,60 @@ bool video_bgfx::init_bgfx_library(osd_window &window)
//============================================================
// Utility for setting up window handle
//============================================================
+#ifdef SDLMAME_SDL3
+bool video_bgfx::set_platform_data(bgfx::PlatformData &platform_data, osd_window const &window)
+{
+#if defined(OSD_WINDOWS)
+ platform_data.ndt = nullptr;
+ platform_data.nwh = dynamic_cast<win_window_info const &>(window).platform_window();
+#elif defined(OSD_MAC)
+ platform_data.ndt = nullptr;
+ platform_data.nwh = GetOSWindow(dynamic_cast<mac_window_info const &>(window).platform_window());
+#elif defined(SDLMAME_EMSCRIPTEN)
+ platform_data.ndt = nullptr;
+ platform_data.nwh = (void *)"#canvas"; // HTML5 target selector
+#else // defined(OSD_*)
+ const auto winProps = SDL_GetWindowProperties(dynamic_cast<sdl_window_info const &>(window).platform_window());
+#if defined(SDL_PLATFORM_WINDOWS)
+ platform_data.ndt = nullptr;
+ platform_data.nwh = (HWND)SDL_GetPointerProperty(winProps, SDL_PROP_WINDOW_WIN32_HWND_POINTER, NULL);
+#endif
+#if defined(SDL_PLATFORM_MACOS)
+ platform_data.ndt = nullptr;
+ platform_data.nwh = SDL_GetPointerProperty(winProps, SDL_PROP_WINDOW_COCOA_WINDOW_POINTER, NULL);
+#endif
+#if defined(SDL_PLATFORM_LINUX)
+ if (SDL_strcmp(SDL_GetCurrentVideoDriver(), "x11") == 0)
+ {
+ platform_data.ndt = (void *)SDL_GetPointerProperty(winProps, SDL_PROP_WINDOW_X11_DISPLAY_POINTER, NULL);
+ platform_data.nwh = (void *)SDL_GetNumberProperty(winProps, SDL_PROP_WINDOW_X11_WINDOW_NUMBER, 0);
+ }
+ else if (SDL_strcmp(SDL_GetCurrentVideoDriver(), "wayland") == 0)
+ {
+ platform_data.ndt = (struct wl_display *)SDL_GetPointerProperty(winProps, SDL_PROP_WINDOW_WAYLAND_DISPLAY_POINTER, NULL);
+ platform_data.nwh = (struct wl_surface *)SDL_GetPointerProperty(winProps, SDL_PROP_WINDOW_WAYLAND_SURFACE_POINTER, NULL);
+ if (!platform_data.nwh)
+ {
+ osd_printf_error("BGFX: Error creating a Wayland window\n");
+ return false;
+ }
+ platform_data.type = bgfx::NativeWindowHandleType::Wayland;
+ }
+#endif
+#if defined(SDL_PLATFORM_ANDROID)
+ platform_data.ndt = nullptr;
+ platform_data.nwh = SDL_GetPointerProperty(winProps, SDL_PROP_WINDOW_ANDROID_WINDOW_POINTER, NULL);
+#endif
+#endif // defined(OSD_*)
+ platform_data.context = nullptr;
+ platform_data.backBuffer = nullptr;
+ platform_data.backBufferDS = nullptr;
+ bgfx::setPlatformData(platform_data);
+
+ return true;
+}
+#else
bool video_bgfx::set_platform_data(bgfx::PlatformData &platform_data, osd_window const &window)
{
#if defined(OSD_WINDOWS)
@@ -457,6 +512,7 @@ bool video_bgfx::set_platform_data(bgfx::PlatformData &platform_data, osd_window
return true;
}
+#endif
} // anonymous namespace
@@ -501,6 +557,31 @@ uint32_t renderer_bgfx::s_height[16] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
//============================================================
#ifdef OSD_SDL
+#ifdef SDLMAME_SDL3
+static std::pair<void *, bool> sdlNativeWindowHandle(SDL_Window *window)
+{
+#if defined(SDL_PLATFORM_WIN32)
+ return std::make_pair((HWND)SDL_GetPointerProperty(SDL_GetWindowProperties(window), SDL_PROP_WINDOW_WIN32_HWND_POINTER, NULL), true);
+#endif
+#if defined(SDLMAME_MACOSX)
+ return std::make_pair(SDL_GetPointerProperty(SDL_GetWindowProperties(window), SDL_PROP_WINDOW_COCOA_WINDOW_POINTER, NULL), true);
+#endif
+#if defined(SDL_PLATFORM_LINUX)
+ if (SDL_strcmp(SDL_GetCurrentVideoDriver(), "x11") == 0)
+ {
+ return std::make_pair((void *)uintptr_t(SDL_GetNumberProperty(SDL_GetWindowProperties(window), SDL_PROP_WINDOW_X11_WINDOW_NUMBER, 0)), true);
+ }
+ else if (SDL_strcmp(SDL_GetCurrentVideoDriver(), "wayland") == 0)
+ {
+ return std::make_pair((struct wl_surface *)SDL_GetPointerProperty(SDL_GetWindowProperties(window), SDL_PROP_WINDOW_WAYLAND_SURFACE_POINTER, NULL), true);
+ }
+#endif
+#if defined(SDL_PLATFORM_ANDROID)
+ return std::make_pair(SDL_GetPointerProperty(SDL_GetWindowProperties(window), SDL_PROP_WINDOW_ANDROID_WINDOW_POINTER, NULL), true);
+#endif
+ return std::make_pair(nullptr, false);
+}
+#else
static std::pair<void *, bool> sdlNativeWindowHandle(SDL_Window *window)
{
SDL_SysWMinfo wmi;
@@ -534,6 +615,7 @@ static std::pair<void *, bool> sdlNativeWindowHandle(SDL_Window *window)
return std::make_pair(nullptr, false);
}
}
+#endif
#endif // OSD_SDL
diff --git a/src/osd/modules/render/drawogl.cpp b/src/osd/modules/render/drawogl.cpp
index f9f76946bf4..b9229ed46f3 100644
--- a/src/osd/modules/render/drawogl.cpp
+++ b/src/osd/modules/render/drawogl.cpp
@@ -49,7 +49,11 @@ typedef uint64_t HashT;
// standard SDL headers
#define TOBEMIGRATED 1
+#ifdef SDLMAME_SDL3
+#include <SDL3/SDL.h>
+#else
#include <SDL2/SDL.h>
+#endif
#endif // !defined(OSD_WINDOWS && !defined(OSD_MAC)
@@ -1244,16 +1248,9 @@ int renderer_ogl::draw(const int update)
/*
Mac hack: macOS version 10.15 and later flipped from assuming you don't support Retina to
- assuming you do support Retina. SDL 2.0.11 is scheduled to fix this, but it's not out yet.
- So we double-scale everything if you're on 10.15 or later and SDL is not at least version 2.0.11.
+ assuming you do support Retina.
*/
- #if defined(SDLMAME_MACOSX) && !defined(OSD_MAC)
- SDL_version sdlVers;
- SDL_GetVersion(&sdlVers);
- // Only do this if SDL is not at least 2.0.11.
- if ((sdlVers.major == 2) && (sdlVers.minor == 0) && (sdlVers.patch < 11))
- #endif
- #if defined(SDLMAME_MACOSX) || defined(OSD_MAC)
+ #if !defined(SDLMAME_MACOSX) && defined(OSD_MAC)
{
// now get the Darwin kernel version
int dMaj, dMin, dPatch;
diff --git a/src/osd/modules/render/drawsdl.cpp b/src/osd/modules/render/drawsdl.cpp
index b1a9e93f42e..77ae714b9b3 100644
--- a/src/osd/modules/render/drawsdl.cpp
+++ b/src/osd/modules/render/drawsdl.cpp
@@ -14,7 +14,7 @@
#include "modules/osdmodule.h"
-#if defined(OSD_SDL)
+#if defined(OSD_SDL) && !defined(SDLMAME_SDL3)
// from specific OSD implementation
#include "sdlopts.h"
@@ -274,7 +274,7 @@ int renderer_sdl1::create()
m_blittimer = 0;
yuv_init();
- osd_printf_verbose("Leave renderer_sdl2::create\n");
+ osd_printf_verbose("Leave renderer_sdl1::create\n");
return 0;
}
@@ -777,12 +777,10 @@ sdl_scale_mode const video_sdl1::s_scale_modes[] = {
} // namespace osd
-
-#else // defined(OSD_SDL)
+#else // defined(OSD_SDL) && !defined(SDLMAME_SDL3)
namespace osd { namespace { MODULE_NOT_SUPPORTED(video_sdl1, OSD_RENDERER_PROVIDER, "soft") } }
-#endif // defined(OSD_SDL)
-
+#endif // defined(OSD_SDL) && !defined(SDLMAME_SDL3)
MODULE_DEFINITION(RENDERER_SDL1, osd::video_sdl1)
diff --git a/src/osd/modules/render/drawsdl3accel.cpp b/src/osd/modules/render/drawsdl3accel.cpp
new file mode 100644
index 00000000000..62f31f545fe
--- /dev/null
+++ b/src/osd/modules/render/drawsdl3accel.cpp
@@ -0,0 +1,1165 @@
+// license:BSD-3-Clause
+// copyright-holders: Couriersud, Olivier Galibert, R. Belmont
+//============================================================
+//
+// drawsdl3accel.cpp - SDL accelerated drawing using SDL's renderer API
+//
+// SDLMAME by Olivier Galibert and R. Belmont
+//
+// renderer_sdl2 by Couriersud
+//
+//============================================================
+
+#include "render_module.h"
+
+#include "modules/osdmodule.h"
+
+#if defined(OSD_SDL) && defined (SDLMAME_SDL3)
+
+// OSD headers
+#include "sdlopts.h"
+#include "window.h"
+
+// lib/util
+#include "options.h"
+
+// emu
+#include "emucore.h"
+#include "render.h"
+
+// standard SDL headers
+#include <SDL3/SDL.h>
+
+// standard C headers
+#include <algorithm>
+#include <cmath>
+#include <cstdio>
+#include <iterator>
+#include <list>
+namespace osd {
+
+namespace {
+
+struct quad_setup_data
+{
+ quad_setup_data() = default;
+
+ void compute(const render_primitive &prim, const int prescale);
+
+ int32_t dudx = 0, dvdx = 0, dudy = 0, dvdy = 0;
+ int32_t startu = 0, startv = 0;
+ int32_t rotwidth = 0, rotheight = 0;
+};
+
+//============================================================
+// Textures
+//============================================================
+
+class renderer_sdl2;
+struct copy_info_t;
+
+/* texture_info holds information about a texture */
+class texture_info
+{
+public:
+ texture_info(renderer_sdl2 *renderer, const render_texinfo &texsource, const quad_setup_data &setup, const uint32_t flags);
+ ~texture_info();
+
+ void set_data(const render_texinfo &texsource, const uint32_t flags);
+ void render_quad(const render_primitive &prim, const int x, const int y);
+ bool matches(const render_primitive &prim, const quad_setup_data &setup);
+
+ copy_info_t const *compute_size_type();
+
+ void *m_pixels; // pixels for the texture
+ int m_pitch;
+
+ copy_info_t const *m_copyinfo;
+ quad_setup_data m_setup;
+
+ osd_ticks_t m_last_access;
+
+ int raw_width() const { return m_texinfo.width; }
+ int raw_height() const { return m_texinfo.height; }
+
+ const render_texinfo &texinfo() const { return m_texinfo; }
+ render_texinfo &texinfo() { return m_texinfo; }
+
+ HashT hash() const { return m_hash; }
+ uint32_t flags() const { return m_flags; }
+
+private:
+ bool is_pixels_owned() const;
+
+ void set_coloralphamode(SDL_Texture *texture_id, const render_color *color);
+
+ Uint32 m_sdl_access;
+ renderer_sdl2 * m_renderer;
+ render_texinfo m_texinfo; // copy of the texture info
+ HashT m_hash; // hash value for the texture (must be >= pointer size)
+ uint32_t m_flags; // rendering flags
+
+ SDL_Texture * m_texture_id;
+ bool m_is_rotated;
+
+ int m_format; // texture format
+ SDL_BlendMode m_sdl_blendmode;
+};
+
+// inline functions and macros
+#include "blit13.ipp"
+
+//============================================================
+// TEXCOPY FUNCS
+//============================================================
+
+enum SDL_TEXFORMAT_E
+{
+ SDL_TEXFORMAT_ARGB32 = 0,
+ SDL_TEXFORMAT_RGB32,
+ SDL_TEXFORMAT_RGB32_PALETTED,
+ SDL_TEXFORMAT_YUY16,
+ SDL_TEXFORMAT_YUY16_PALETTED,
+ SDL_TEXFORMAT_PALETTE16,
+ SDL_TEXFORMAT_RGB15,
+ SDL_TEXFORMAT_RGB15_PALETTED,
+ SDL_TEXFORMAT_PALETTE16A,
+ SDL_TEXFORMAT_PALETTE16_ARGB1555,
+ SDL_TEXFORMAT_RGB15_ARGB1555,
+ SDL_TEXFORMAT_RGB15_PALETTED_ARGB1555,
+ SDL_TEXFORMAT_LAST = SDL_TEXFORMAT_RGB15_PALETTED_ARGB1555
+};
+
+struct copy_info_t
+{
+ int src_fmt;
+ Uint32 dst_fmt;
+ const blit_base *blitter;
+ Uint32 bm_mask;
+ const char *srcname;
+ const char *dstname;
+ /* Statistics */
+ mutable uint64_t pixel_count;
+ mutable int64_t time;
+ mutable int samples;
+ mutable int perf;
+ /* list */
+ copy_info_t const *next;
+};
+
+/* renderer_sdl2 is the information about SDL for the current screen */
+class renderer_sdl2 : public osd_renderer
+{
+public:
+ renderer_sdl2(
+ osd_window &window,
+ copy_info_t const *const (&blit_info)[SDL_TEXFORMAT_LAST + 1]);
+
+ virtual ~renderer_sdl2()
+ {
+ destroy_all_textures();
+ SDL_DestroyRenderer(m_sdl_renderer);
+ m_sdl_renderer = nullptr;
+ }
+
+ virtual int create() override;
+ virtual int draw(const int update) override;
+ virtual int xy_to_render_target(const int x, const int y, int *xt, int *yt) override;
+ virtual render_primitive_list *get_primitives() override;
+
+ int RendererSupportsFormat(Uint32 format, Uint32 access, const char *sformat);
+
+ SDL_Renderer *m_sdl_renderer;
+ copy_info_t const *const (&m_blit_info)[SDL_TEXFORMAT_LAST + 1];
+
+private:
+ void render_quad(texture_info *texture, const render_primitive &prim, const int x, const int y);
+
+ texture_info *texture_find(const render_primitive &prim, const quad_setup_data &setup);
+ texture_info *texture_update(const render_primitive &prim);
+
+ void destroy_all_textures();
+
+ int32_t m_blittimer;
+
+ std::list<texture_info> m_texlist; // list of active textures
+
+ float m_last_hofs;
+ float m_last_vofs;
+
+ int m_width;
+ int m_height;
+
+ osd_dim m_blit_dim;
+
+ struct
+ {
+ Uint32 format;
+ int status;
+ } fmt_support[30];
+
+ // Stats
+ int64_t m_last_blit_time;
+ int64_t m_last_blit_pixels;
+};
+
+
+//============================================================
+// CONSTANTS
+//============================================================
+
+#define STAT_PIXEL_THRESHOLD (150*150)
+
+enum
+{
+ TEXTURE_TYPE_NONE,
+ TEXTURE_TYPE_PLAIN,
+ TEXTURE_TYPE_SURFACE
+};
+
+
+//============================================================
+// Inline functions
+//============================================================
+
+static inline bool is_opaque(const float &a)
+{
+ return (a >= 1.0f);
+}
+
+static inline bool is_transparent(const float &a)
+{
+ return (a < 0.0001f);
+}
+
+//============================================================
+// CONSTRUCTOR & DESTRUCTOR
+//============================================================
+
+renderer_sdl2::renderer_sdl2(
+ osd_window &window,
+ copy_info_t const *const (&blit_info)[SDL_TEXFORMAT_LAST + 1])
+ : osd_renderer(window)
+ , m_sdl_renderer(nullptr)
+ , m_blit_info(blit_info)
+ , m_blittimer(0)
+ , m_last_hofs(0)
+ , m_last_vofs(0)
+ , m_width(0)
+ , m_height(0)
+ , m_blit_dim(0, 0)
+ , m_last_blit_time(0)
+ , m_last_blit_pixels(0)
+{
+ for (int i = 0; i < 30; i++)
+ {
+ fmt_support[i].format = 0;
+ fmt_support[i].status = 0;
+ }
+}
+
+//============================================================
+// INLINES
+//============================================================
+
+
+static inline float round_nearest(float f)
+{
+ return floor(f + 0.5f);
+}
+
+static inline HashT texture_compute_hash(const render_texinfo &texture, const uint32_t flags)
+{
+ return (HashT)texture.base ^ (flags & (PRIMFLAG_BLENDMODE_MASK | PRIMFLAG_TEXFORMAT_MASK));
+}
+
+static inline SDL_BlendMode map_blendmode(const int blendmode)
+{
+ switch (blendmode)
+ {
+ case BLENDMODE_NONE:
+ return SDL_BLENDMODE_NONE;
+ case BLENDMODE_ALPHA:
+ return SDL_BLENDMODE_BLEND;
+ case BLENDMODE_RGB_MULTIPLY:
+ return SDL_BLENDMODE_MOD;
+ case BLENDMODE_ADD:
+ return SDL_BLENDMODE_ADD;
+ default:
+ osd_printf_warning("Unknown Blendmode %d", blendmode);
+ }
+ return SDL_BLENDMODE_NONE;
+}
+
+void texture_info::set_coloralphamode(SDL_Texture *texture_id, const render_color *color)
+{
+ uint32_t sr = (uint32_t)(255.0f * color->r);
+ uint32_t sg = (uint32_t)(255.0f * color->g);
+ uint32_t sb = (uint32_t)(255.0f * color->b);
+ uint32_t sa = (uint32_t)(255.0f * color->a);
+
+
+ if (color->r >= 1.0f && color->g >= 1.0f && color->b >= 1.0f && is_opaque(color->a))
+ {
+ SDL_SetTextureColorMod(texture_id, 0xFF, 0xFF, 0xFF);
+ SDL_SetTextureAlphaMod(texture_id, 0xFF);
+ }
+ /* coloring-only case */
+ else if (is_opaque(color->a))
+ {
+ SDL_SetTextureColorMod(texture_id, sr, sg, sb);
+ SDL_SetTextureAlphaMod(texture_id, 0xFF);
+ }
+ /* alpha and/or coloring case */
+ else if (!is_transparent(color->a))
+ {
+ SDL_SetTextureColorMod(texture_id, sr, sg, sb);
+ SDL_SetTextureAlphaMod(texture_id, sa);
+ }
+ else
+ {
+ SDL_SetTextureColorMod(texture_id, 0xFF, 0xFF, 0xFF);
+ SDL_SetTextureAlphaMod(texture_id, 0x00);
+ }
+}
+
+void texture_info::render_quad(const render_primitive &prim, const int x, const int y)
+{
+ SDL_FRect target_rect;
+
+ target_rect.x = x;
+ target_rect.y = y;
+ target_rect.w = round_nearest(prim.bounds.x1) - round_nearest(prim.bounds.x0);
+ target_rect.h = round_nearest(prim.bounds.y1) - round_nearest(prim.bounds.y0);
+
+ SDL_SetTextureBlendMode(m_texture_id, m_sdl_blendmode);
+ set_coloralphamode(m_texture_id, &prim.color);
+ //printf("%d %d %d %d\n", target_rect.x, target_rect.y, target_rect.w, target_rect.h);
+ // Arghhh .. Just another bug. SDL_RenderTexture has severe issues with scaling ...
+ SDL_RenderTexture(m_renderer->m_sdl_renderer, m_texture_id, nullptr, &target_rect);
+ //SDL_RenderTextureRotated(m_renderer->m_sdl_renderer, m_texture_id, nullptr, &target_rect, 0, nullptr, SDL_FLIP_NONE);
+ //SDL_RenderTextureRotated(m_renderer->m_sdl_renderer, m_texture_id, nullptr, nullptr, 0, nullptr, SDL_FLIP_NONE);
+}
+
+void renderer_sdl2::render_quad(texture_info *texture, const render_primitive &prim, const int x, const int y)
+{
+ SDL_FRect target_rect;
+
+ target_rect.x = x;
+ target_rect.y = y;
+ target_rect.w = round_nearest(prim.bounds.x1 - prim.bounds.x0);
+ target_rect.h = round_nearest(prim.bounds.y1 - prim.bounds.y0);
+
+ if (texture)
+ {
+ copy_info_t const *copyinfo = texture->m_copyinfo;
+ copyinfo->time -= osd_ticks();
+ texture->render_quad(prim, x, y);
+ copyinfo->time += osd_ticks();
+
+ copyinfo->pixel_count += std::max(STAT_PIXEL_THRESHOLD , (texture->raw_width() * texture->raw_height()));
+ if (m_last_blit_pixels)
+ {
+ copyinfo->time += (m_last_blit_time * (int64_t) (texture->raw_width() * texture->raw_height())) / (int64_t) m_last_blit_pixels;
+ }
+ copyinfo->samples++;
+ copyinfo->perf = (texture->m_copyinfo->pixel_count * (osd_ticks_per_second()/1000)) / std::max<int64_t>(texture->m_copyinfo->time, 1);
+ }
+ else
+ {
+ uint32_t sr = (uint32_t)(255.0f * prim.color.r);
+ uint32_t sg = (uint32_t)(255.0f * prim.color.g);
+ uint32_t sb = (uint32_t)(255.0f * prim.color.b);
+ uint32_t sa = (uint32_t)(255.0f * prim.color.a);
+
+ SDL_SetRenderDrawBlendMode(m_sdl_renderer, map_blendmode(PRIMFLAG_GET_BLENDMODE(prim.flags)));
+ SDL_SetRenderDrawColor(m_sdl_renderer, sr, sg, sb, sa);
+ SDL_RenderFillRect(m_sdl_renderer, &target_rect);
+ }
+}
+
+int renderer_sdl2::RendererSupportsFormat(Uint32 format, Uint32 access, const char *sformat)
+{
+ int i;
+ for (i = 0; fmt_support[i].format != 0; i++)
+ {
+ if (format == fmt_support[i].format)
+ {
+ return fmt_support[i].status;
+ }
+ }
+ /* not tested yet */
+ fmt_support[i].format = format;
+ fmt_support[i + 1].format = 0;
+ SDL_Texture *texid = SDL_CreateTexture(m_sdl_renderer, SDL_PixelFormat(format), SDL_TextureAccess(access), 16, 16);
+ if (texid)
+ {
+ fmt_support[i].status = 1;
+ SDL_DestroyTexture(texid);
+ return 1;
+ }
+ osd_printf_verbose("Pixelformat <%s> error %s \n", sformat, SDL_GetError());
+ osd_printf_verbose("Pixelformat <%s> not supported\n", sformat);
+ fmt_support[i].status = 0;
+ return 0;
+}
+
+
+//============================================================
+// sdl_info::create
+//============================================================
+int renderer_sdl2::create()
+{
+ osd_printf_verbose("Enter renderer_sdl2::create\n");
+
+ // create renderer
+ m_sdl_renderer = SDL_CreateRenderer(dynamic_cast<sdl_window_info &>(window()).platform_window(), nullptr);
+
+ if (!m_sdl_renderer)
+ {
+ fatalerror("Error on creating renderer: %s\n", SDL_GetError());
+ }
+
+ if (video_config.waitvsync)
+ {
+ SDL_SetRenderVSync(m_sdl_renderer, SDL_RENDERER_VSYNC_ADAPTIVE);
+ }
+
+ /* Enable bilinear filtering in case it is supported.
+ * This applies to all texture operations. However, artwort is pre-scaled
+ * and thus shouldn't be affected.
+ */
+#if SDL_VERSION_ATLEAST(3, 3, 2)
+ if (video_config.filter)
+ {
+ SDL_SetDefaultTextureScaleMode(m_sdl_renderer, SDL_SCALEMODE_LINEAR);
+ }
+ else
+ {
+ SDL_SetDefaultTextureScaleMode(m_sdl_renderer, SDL_SCALEMODE_NEAREST);
+ }
+#endif
+ m_blittimer = 3;
+
+ const auto props = SDL_GetRendererProperties(m_sdl_renderer);
+ osd_printf_verbose("SDL renderer using driver %s\n", SDL_GetStringProperty(props, SDL_PROP_RENDERER_NAME_STRING, "Unknown"));
+
+ osd_printf_verbose("Leave renderer_sdl2::create\n");
+ return 0;
+}
+
+
+//============================================================
+// drawsdl_xy_to_render_target
+//============================================================
+
+int renderer_sdl2::xy_to_render_target(int x, int y, int *xt, int *yt)
+{
+ *xt = x - m_last_hofs;
+ *yt = y - m_last_vofs;
+ if (*xt<0 || *xt >= m_blit_dim.width())
+ return 0;
+ if (*yt<0 || *yt >= m_blit_dim.height())
+ return 0;
+ return 1;
+}
+
+//============================================================
+// drawsdl_destroy_all_textures
+//============================================================
+
+void renderer_sdl2::destroy_all_textures()
+{
+ if (window().m_primlist)
+ {
+ window().m_primlist->acquire_lock();
+ m_texlist.clear();
+ window().m_primlist->release_lock();
+ }
+ else
+ m_texlist.clear();
+}
+
+//============================================================
+// sdl_info::draw
+//============================================================
+
+int renderer_sdl2::draw(int update)
+{
+ texture_info *texture=nullptr;
+ float vofs, hofs;
+ int blit_pixels = 0;
+
+ osd_dim wdim = window().get_size();
+
+ if (has_flags(FI_CHANGED) || (wdim.width() != m_width) || (wdim.height() != m_height))
+ {
+ destroy_all_textures();
+ m_width = wdim.width();
+ m_height = wdim.height();
+ SDL_SetRenderViewport(m_sdl_renderer, nullptr);
+ m_blittimer = 3;
+ clear_flags(FI_CHANGED);
+ }
+
+ //SDL_SelectRenderer(window().sdl_window);
+
+ if (m_blittimer > 0)
+ {
+ /* SDL Underlays need alpha = 0 ! */
+ SDL_SetRenderDrawBlendMode(m_sdl_renderer, SDL_BLENDMODE_NONE);
+ //SDL_SetRenderDrawColor(0,0,0,255);
+ SDL_SetRenderDrawColor(m_sdl_renderer, 0,0,0,0);
+ SDL_RenderFillRect(m_sdl_renderer, nullptr);
+ m_blittimer--;
+ }
+
+ // compute centering parameters
+ vofs = hofs = 0.0f;
+
+ if (video_config.centerv || video_config.centerh)
+ {
+ int ch, cw;
+
+ ch = wdim.height();
+ cw = wdim.width();
+
+ if (video_config.centerv)
+ {
+ vofs = (ch - m_blit_dim.height()) / 2.0f;
+ }
+ if (video_config.centerh)
+ {
+ hofs = (cw - m_blit_dim.width()) / 2.0f;
+ }
+ }
+
+ m_last_hofs = hofs;
+ m_last_vofs = vofs;
+
+ window().m_primlist->acquire_lock();
+
+ // now draw
+ for (render_primitive &prim : *window().m_primlist)
+ {
+ Uint8 sr, sg, sb, sa;
+
+ switch (prim.type)
+ {
+ case render_primitive::LINE:
+ sr = (int)(255.0f * prim.color.r);
+ sg = (int)(255.0f * prim.color.g);
+ sb = (int)(255.0f * prim.color.b);
+ sa = (int)(255.0f * prim.color.a);
+
+ SDL_SetRenderDrawBlendMode(m_sdl_renderer, map_blendmode(PRIMFLAG_GET_BLENDMODE(prim.flags)));
+ SDL_SetRenderDrawColor(m_sdl_renderer, sr, sg, sb, sa);
+ SDL_RenderLine(m_sdl_renderer, prim.bounds.x0 + hofs, prim.bounds.y0 + vofs,
+ prim.bounds.x1 + hofs, prim.bounds.y1 + vofs);
+ break;
+ case render_primitive::QUAD:
+ texture = texture_update(prim);
+ if (texture)
+ blit_pixels += (texture->raw_height() * texture->raw_width());
+ render_quad(texture, prim,
+ round_nearest(hofs + prim.bounds.x0),
+ round_nearest(vofs + prim.bounds.y0));
+ break;
+ default:
+ throw emu_fatalerror("Unexpected render_primitive type\n");
+ }
+ }
+
+ window().m_primlist->release_lock();
+
+ m_last_blit_pixels = blit_pixels;
+ m_last_blit_time = -osd_ticks();
+ SDL_RenderPresent(m_sdl_renderer);
+ m_last_blit_time += osd_ticks();
+
+ return 0;
+}
+
+
+//============================================================
+// texture handling
+//============================================================
+
+//============================================================
+// texture_compute_size and type
+//============================================================
+
+copy_info_t const *texture_info::compute_size_type()
+{
+ copy_info_t const *result = nullptr;
+ int maxperf = 0;
+
+ for (copy_info_t const *bi = m_renderer->m_blit_info[m_format]; bi != nullptr; bi = bi->next)
+ {
+ if ((m_is_rotated == bi->blitter->m_is_rot) && (m_sdl_blendmode == bi->bm_mask))
+ {
+ if (m_renderer->RendererSupportsFormat(bi->dst_fmt, m_sdl_access, bi->dstname))
+ {
+ int const perf = bi->perf;
+ if (perf == 0)
+ {
+ return bi;
+ }
+ else if (perf > ((maxperf * 102) / 100))
+ {
+ result = bi;
+ maxperf = perf;
+ }
+ }
+ }
+ }
+
+ if (result)
+ return result;
+
+ // try last resort handlers
+ for (copy_info_t const *bi = m_renderer->m_blit_info[m_format]; bi != nullptr; bi = bi->next)
+ {
+ if ((m_is_rotated == bi->blitter->m_is_rot) && (m_sdl_blendmode == bi->bm_mask))
+ if (m_renderer->RendererSupportsFormat(bi->dst_fmt, m_sdl_access, bi->dstname))
+ return bi;
+ }
+ //FIXME: crash implement a -do nothing handler
+ return nullptr;
+}
+
+bool texture_info::is_pixels_owned() const
+{
+ // do we own / allocated it ?
+ return (m_sdl_access == SDL_TEXTUREACCESS_STATIC) && !m_copyinfo->blitter->m_is_passthrough;
+}
+
+//============================================================
+// texture_info::matches
+//============================================================
+
+bool texture_info::matches(const render_primitive &prim, const quad_setup_data &setup)
+{
+ return texinfo().base == prim.texture.base &&
+ texinfo().width == prim.texture.width &&
+ texinfo().height == prim.texture.height &&
+ texinfo().rowpixels == prim.texture.rowpixels &&
+ m_setup.dudx == setup.dudx &&
+ m_setup.dvdx == setup.dvdx &&
+ m_setup.dudy == setup.dudy &&
+ m_setup.dvdy == setup.dvdy &&
+ m_setup.startu == setup.startu &&
+ m_setup.startv == setup.startv &&
+ ((flags() ^ prim.flags) & (PRIMFLAG_BLENDMODE_MASK | PRIMFLAG_TEXFORMAT_MASK)) == 0;
+}
+
+//============================================================
+// texture_create
+//============================================================
+
+texture_info::texture_info(renderer_sdl2 *renderer, const render_texinfo &texsource, const quad_setup_data &setup, uint32_t flags)
+{
+ // fill in the core data
+ m_renderer = renderer;
+ m_hash = texture_compute_hash(texsource, flags);
+ m_flags = flags;
+ m_texinfo = texsource;
+ m_texinfo.seqid = -1; // force set data
+ m_is_rotated = false;
+ m_setup = setup;
+ m_sdl_blendmode = map_blendmode(PRIMFLAG_GET_BLENDMODE(flags));
+ m_pitch = 0;
+
+ switch (PRIMFLAG_GET_TEXFORMAT(flags))
+ {
+ case TEXFORMAT_ARGB32:
+ m_format = SDL_TEXFORMAT_ARGB32;
+ break;
+ case TEXFORMAT_RGB32:
+ m_format = texsource.palette ? SDL_TEXFORMAT_RGB32_PALETTED : SDL_TEXFORMAT_RGB32;
+ break;
+ case TEXFORMAT_PALETTE16:
+ m_format = SDL_TEXFORMAT_PALETTE16;
+ break;
+ case TEXFORMAT_YUY16:
+ m_format = texsource.palette ? SDL_TEXFORMAT_YUY16_PALETTED : SDL_TEXFORMAT_YUY16;
+ break;
+
+ default:
+ osd_printf_error("Unknown textureformat %d\n", PRIMFLAG_GET_TEXFORMAT(flags));
+ }
+
+ if (setup.rotwidth != m_texinfo.width || setup.rotheight != m_texinfo.height
+ || setup.dudx < 0 || setup.dvdy < 0 || (PRIMFLAG_GET_TEXORIENT(flags) != 0))
+ m_is_rotated = true;
+ else
+ m_is_rotated = false;
+
+ m_sdl_access = SDL_TEXTUREACCESS_STREAMING;
+
+ // Watch out for 0x0 textures ...
+ if (!m_setup.rotwidth || !m_setup.rotheight)
+ osd_printf_warning("Trying to create texture with zero dim\n");
+
+ // set copy_info
+ m_copyinfo = compute_size_type();
+
+ m_texture_id = SDL_CreateTexture(m_renderer->m_sdl_renderer, SDL_PixelFormat(m_copyinfo->dst_fmt), SDL_TextureAccess(m_sdl_access),
+ m_setup.rotwidth, m_setup.rotheight);
+
+ if (!m_texture_id)
+ osd_printf_error("Error creating texture: %d x %d, pixelformat %s error: %s\n", m_setup.rotwidth, m_setup.rotheight,
+ m_copyinfo->dstname, SDL_GetError());
+
+#if !SDL_VERSION_ATLEAST(3, 3, 2)
+ if (video_config.filter)
+ {
+ SDL_SetTextureScaleMode(m_texture_id, SDL_SCALEMODE_LINEAR);
+ }
+ else
+ {
+ SDL_SetTextureScaleMode(m_texture_id, SDL_SCALEMODE_NEAREST);
+ }
+#endif
+
+ if (m_sdl_access == SDL_TEXTUREACCESS_STATIC)
+ {
+ if (m_copyinfo->blitter->m_is_passthrough)
+ m_pixels = nullptr;
+ else
+ m_pixels = malloc(m_setup.rotwidth * m_setup.rotheight * m_copyinfo->blitter->m_dest_bpp);
+ }
+ m_last_access = osd_ticks();
+}
+
+texture_info::~texture_info()
+{
+ if (is_pixels_owned() && m_pixels)
+ free(m_pixels);
+ SDL_DestroyTexture(m_texture_id);
+}
+
+//============================================================
+// texture_set_data
+//============================================================
+
+void texture_info::set_data(const render_texinfo &texsource, const uint32_t flags)
+{
+ m_copyinfo->time -= osd_ticks();
+ if (m_sdl_access == SDL_TEXTUREACCESS_STATIC)
+ {
+ if (m_copyinfo->blitter->m_is_passthrough)
+ {
+ m_pixels = texsource.base;
+ m_pitch = m_texinfo.rowpixels * m_copyinfo->blitter->m_dest_bpp;
+ }
+ else
+ {
+ m_pitch = m_setup.rotwidth * m_copyinfo->blitter->m_dest_bpp;
+ m_copyinfo->blitter->texop(this, &texsource);
+ }
+ SDL_UpdateTexture(m_texture_id, nullptr, m_pixels, m_pitch);
+ }
+ else
+ {
+ SDL_LockTexture(m_texture_id, nullptr, (void **)&m_pixels, &m_pitch);
+ if ( m_copyinfo->blitter->m_is_passthrough )
+ {
+ const uint8_t *src = (uint8_t *)texsource.base;
+ uint8_t *dst = (uint8_t *)m_pixels;
+ int spitch = texsource.rowpixels * m_copyinfo->blitter->m_dest_bpp;
+ int num = texsource.width * m_copyinfo->blitter->m_dest_bpp;
+ int h = texsource.height;
+ while (h--) {
+ memcpy(dst, src, num);
+ src += spitch;
+ dst += m_pitch;
+ }
+ }
+ else
+ m_copyinfo->blitter->texop(this, &texsource);
+ SDL_UnlockTexture(m_texture_id);
+ }
+ m_copyinfo->time += osd_ticks();
+}
+
+//============================================================
+// compute rotation setup
+//============================================================
+
+inline float signf(const float a)
+{
+ return (0.0f < a) - (a < 0.0f);
+}
+
+void quad_setup_data::compute(const render_primitive &prim, const int prescale)
+{
+ const render_quad_texuv *texcoords = &prim.texcoords;
+ int texwidth = prim.texture.width;
+ int texheight = prim.texture.height;
+ float fdudx, fdvdx, fdudy, fdvdy;
+ float width, height;
+ float fscale;
+ /* determine U/V deltas */
+ if ((PRIMFLAG_GET_SCREENTEX(prim.flags)))
+ fscale = (float) prescale;
+ else
+ fscale = 1.0f;
+
+ fdudx = (texcoords->tr.u - texcoords->tl.u); // a a11
+ fdvdx = (texcoords->tr.v - texcoords->tl.v); // c a21
+ fdudy = (texcoords->bl.u - texcoords->tl.u); // b a12
+ fdvdy = (texcoords->bl.v - texcoords->tl.v); // d a22
+
+ width = fabsf(( fdudx * (float) (texwidth) + fdvdx * (float) (texheight)) ) * fscale;
+ height = fabsf((fdudy * (float) (texwidth) + fdvdy * (float) (texheight)) ) * fscale;
+
+ fdudx = signf(fdudx) / fscale;
+ fdvdy = signf(fdvdy) / fscale;
+ fdvdx = signf(fdvdx) / fscale;
+ fdudy = signf(fdudy) / fscale;
+
+#if 0
+ printf("tl.u %f tl.v %f\n", texcoords->tl.u, texcoords->tl.v);
+ printf("tr.u %f tr.v %f\n", texcoords->tr.u, texcoords->tr.v);
+ printf("bl.u %f bl.v %f\n", texcoords->bl.u, texcoords->bl.v);
+ printf("br.u %f br.v %f\n", texcoords->br.u, texcoords->br.v);
+ /* compute start and delta U,V coordinates now */
+#endif
+
+ dudx = round_nearest(65536.0f * fdudx);
+ dvdx = round_nearest(65536.0f * fdvdx);
+ dudy = round_nearest(65536.0f * fdudy);
+ dvdy = round_nearest(65536.0f * fdvdy);
+ startu = round_nearest(65536.0f * (float) texwidth * texcoords->tl.u);
+ startv = round_nearest(65536.0f * (float) texheight * texcoords->tl.v);
+
+ /* clamp to integers */
+
+ rotwidth = round_nearest(width);
+ rotheight = round_nearest(height);
+
+ //printf("%d %d rot %d %d\n", texwidth, texheight, rotwidth, rotheight);
+
+ startu += (dudx + dudy) / 2;
+ startv += (dvdx + dvdy) / 2;
+
+}
+
+//============================================================
+// texture_find
+//============================================================
+
+texture_info *renderer_sdl2::texture_find(const render_primitive &prim, const quad_setup_data &setup)
+{
+ const HashT texhash = texture_compute_hash(prim.texture, prim.flags);
+ const osd_ticks_t now = osd_ticks();
+
+ // find a match
+ for (auto texture = m_texlist.begin(); texture != m_texlist.end(); )
+ {
+ if ((texture->hash() == texhash) && texture->matches(prim, setup))
+ {
+ // would we choose another blitter based on performance?
+ if ((texture->m_copyinfo->samples & 0x7f) == 0x7f)
+ {
+ if (texture->m_copyinfo != texture->compute_size_type())
+ return nullptr;
+ }
+ texture->m_last_access = now;
+ return &*texture;
+ }
+ else
+ {
+ // free resources not needed any longer?
+ if ((now - texture->m_last_access) > osd_ticks_per_second())
+ texture = m_texlist.erase(texture);
+ else
+ ++texture;
+ }
+ }
+
+ // nothing found
+ return nullptr;
+}
+
+//============================================================
+// texture_update
+//============================================================
+
+texture_info * renderer_sdl2::texture_update(const render_primitive &prim)
+{
+ quad_setup_data setup;
+ texture_info *texture;
+
+ setup.compute(prim, window().prescale());
+
+ texture = texture_find(prim, setup);
+
+ // if we didn't find one, create a new texture
+ if (!texture && prim.texture.base)
+ {
+ // add us to the texture list
+ texture = &m_texlist.emplace_front(this, prim.texture, setup, prim.flags);
+ }
+
+ if (texture)
+ {
+ if (prim.texture.base && (texture->texinfo().seqid != prim.texture.seqid))
+ {
+ texture->texinfo().seqid = prim.texture.seqid;
+ // if we found it, but with a different seqid, copy the data
+ texture->set_data(prim.texture, prim.flags);
+ }
+
+ }
+ return texture;
+}
+
+render_primitive_list *renderer_sdl2::get_primitives()
+{
+ osd_dim nd = window().get_size();
+ if (nd != m_blit_dim)
+ {
+ m_blit_dim = nd;
+ notify_changed();
+ }
+ window().target()->set_bounds(m_blit_dim.width(), m_blit_dim.height(), window().pixel_aspect());
+ return &window().target()->get_primitives();
+}
+
+
+class video_sdl3_accel : public osd_module, public render_module
+{
+public:
+ video_sdl3_accel()
+ : osd_module(OSD_RENDERER_PROVIDER, "accel")
+ , m_blit_info_initialized(false)
+ , m_gllib_loaded(false)
+ {
+ std::fill(std::begin(m_blit_info), std::end(m_blit_info), nullptr);
+ }
+ ~video_sdl3_accel()
+ {
+ free_copy_info();
+ }
+
+ virtual int init(osd_interface &osd, osd_options const &options) override;
+ virtual void exit() override { free_copy_info(); }
+
+ virtual std::unique_ptr<osd_renderer> create(osd_window &window) override;
+
+protected:
+ virtual unsigned flags() const override { return FLAG_INTERACTIVE | FLAG_SDL_NEEDS_OPENGL; }
+
+private:
+ static inline constexpr Uint32 BM_ALL = UINT32_MAX; // SDL_BLENDMODE_MASK | SDL_BLENDMODE_BLEND | SDL_BLENDMODE_ADD | SDL_BLENDMODE_MOD
+
+ void expand_copy_info();
+ void free_copy_info();
+
+ static void add_list(copy_info_t const *&head, copy_info_t const &element, Uint32 bm);
+
+ copy_info_t const *m_blit_info[SDL_TEXFORMAT_LAST + 1];
+ bool m_blit_info_initialized;
+ bool m_gllib_loaded;
+
+ static copy_info_t const s_blit_info_default[];
+};
+
+int video_sdl3_accel::init(osd_interface &osd, osd_options const &options)
+{
+ osd_printf_verbose("Using SDL native texturing driver (SDL 3.2+)\n");
+
+ // Load the GL library now - else MT will fail
+ char const *libname = nullptr;
+#if USE_OPENGL
+ libname = dynamic_cast<sdl_options const &>(options).gl_lib();
+ if (libname && (!*libname || !std::strcmp(libname, OSDOPTVAL_AUTO)))
+ libname = nullptr;
+#endif
+
+ if (!m_gllib_loaded)
+ {
+ // No fatalerror here since not all video drivers support GL!
+ if (SDL_GL_LoadLibrary(libname) != 0)
+ {
+ osd_printf_error("Unable to load OpenGL shared library: %s\n", libname ? libname : "<default>");
+ m_gllib_loaded = true;
+ }
+ else
+ {
+ osd_printf_verbose("Loaded OpenGL shared library: %s\n", libname ? libname : "<default>");
+ }
+ }
+
+ return 0;
+}
+
+std::unique_ptr<osd_renderer> video_sdl3_accel::create(osd_window &window)
+{
+ if (!m_blit_info_initialized)
+ {
+ // On macOS, calling this from drawsdl2_init will prohibit fullscreen toggling.
+ // It is than not possible to toggle from fullscreen to window mode.
+ expand_copy_info();
+ m_blit_info_initialized = true;
+ }
+
+ return std::make_unique<renderer_sdl2>(window, m_blit_info);
+}
+
+void video_sdl3_accel::expand_copy_info()
+{
+ for (const copy_info_t *bi = s_blit_info_default; bi->src_fmt != -1; bi++)
+ {
+ if (bi->bm_mask == BM_ALL)
+ {
+ add_list(m_blit_info[bi->src_fmt], *bi, SDL_BLENDMODE_NONE);
+ add_list(m_blit_info[bi->src_fmt], *bi, SDL_BLENDMODE_ADD);
+ add_list(m_blit_info[bi->src_fmt], *bi, SDL_BLENDMODE_MOD);
+ add_list(m_blit_info[bi->src_fmt], *bi, SDL_BLENDMODE_BLEND);
+ }
+ else
+ {
+ add_list(m_blit_info[bi->src_fmt], *bi, bi->bm_mask);
+ }
+ }
+}
+
+void video_sdl3_accel::free_copy_info()
+{
+ if (m_blit_info_initialized)
+ {
+ for (int i = 0; i <= SDL_TEXFORMAT_LAST; i++)
+ {
+ for (copy_info_t const *bi = m_blit_info[i]; bi != nullptr; )
+ {
+ if (bi->pixel_count)
+ {
+ osd_printf_verbose(
+ "%s -> %s %s blendmode 0x%02x, %d samples: %d KPixel/sec\n",
+ bi->srcname,
+ bi->dstname,
+ bi->blitter->m_is_rot ? "rot" : "norot",
+ bi->bm_mask,
+ bi->samples,
+ bi->perf);
+ }
+ delete std::exchange(bi, bi->next);
+ }
+ m_blit_info[i] = nullptr;
+ }
+ m_blit_info_initialized = false;
+ }
+}
+
+void video_sdl3_accel::add_list(copy_info_t const *&head, copy_info_t const &element, Uint32 bm)
+{
+ copy_info_t *const newci = new copy_info_t(element);
+
+ newci->bm_mask = bm;
+ newci->next = head;
+ head = newci;
+}
+
+
+//============================================================
+// STATIC VARIABLES
+//============================================================
+
+#define ENTRY(a,b,f) { SDL_TEXFORMAT_ ## a, SDL_PIXELFORMAT_ ## b, &texcopy_ ## f, BM_ALL, #a, #b, 0, 0, 0, 0}
+#define ENTRY_BM(a,b,f,bm) { SDL_TEXFORMAT_ ## a, SDL_PIXELFORMAT_ ## b, &texcopy_ ## f, bm, #a, #b, 0, 0, 0, 0}
+#define ENTRY_LR(a,b,f) { SDL_TEXFORMAT_ ## a, SDL_PIXELFORMAT_ ## b, &texcopy_ ## f, BM_ALL, #a, #b, 0, 0, 0, -1}
+
+copy_info_t const video_sdl3_accel::s_blit_info_default[] =
+{
+ /* no rotation */
+ ENTRY(ARGB32, ARGB8888, argb32_argb32),
+ ENTRY_LR(ARGB32, XRGB8888, argb32_rgb32),
+ /* Entry primarily for directfb */
+ ENTRY_BM(ARGB32, XRGB8888, argb32_rgb32, SDL_BLENDMODE_ADD),
+ ENTRY_BM(ARGB32, XRGB8888, argb32_rgb32, SDL_BLENDMODE_MOD),
+ ENTRY_BM(ARGB32, XRGB8888, argb32_rgb32, SDL_BLENDMODE_NONE),
+
+ ENTRY(RGB32, ARGB8888, rgb32_argb32),
+ ENTRY(RGB32, XRGB8888, rgb32_rgb32),
+
+ ENTRY(RGB32_PALETTED, ARGB8888, rgb32pal_argb32),
+ ENTRY(RGB32_PALETTED, XRGB8888, rgb32pal_argb32),
+
+ ENTRY(YUY16, UYVY, yuv16_uyvy),
+ ENTRY(YUY16, YUY2, yuv16_yuy2),
+ ENTRY(YUY16, YVYU, yuv16_yvyu),
+ ENTRY(YUY16, ARGB8888, yuv16_argb32),
+ ENTRY(YUY16, XRGB8888, yuv16_argb32),
+
+ ENTRY(YUY16_PALETTED, UYVY, yuv16pal_uyvy),
+ ENTRY(YUY16_PALETTED, YUY2, yuv16pal_yuy2),
+ ENTRY(YUY16_PALETTED, YVYU, yuv16pal_yvyu),
+ ENTRY(YUY16_PALETTED, ARGB8888, yuv16pal_argb32),
+ ENTRY(YUY16_PALETTED, XRGB8888, yuv16pal_argb32),
+
+ ENTRY(PALETTE16, ARGB8888, pal16_argb32),
+ ENTRY(PALETTE16, XRGB8888, pal16_argb32),
+
+ ENTRY(RGB15, XRGB1555, rgb15_rgb555),
+ ENTRY(RGB15, ARGB1555, rgb15_argb1555),
+ ENTRY(RGB15, ARGB8888, rgb15_argb32),
+ ENTRY(RGB15, XRGB8888, rgb15_argb32),
+
+ ENTRY(RGB15_PALETTED, ARGB8888, rgb15pal_argb32),
+ ENTRY(RGB15_PALETTED, XRGB8888, rgb15pal_argb32),
+
+ ENTRY(PALETTE16A, ARGB8888, pal16a_argb32),
+ ENTRY(PALETTE16A, XRGB8888, pal16a_rgb32),
+
+ /* rotation */
+ ENTRY(ARGB32, ARGB8888, rot_argb32_argb32),
+ ENTRY_LR(ARGB32, XRGB8888, rot_argb32_rgb32),
+ /* Entry primarily for directfb */
+ ENTRY_BM(ARGB32, XRGB8888, rot_argb32_rgb32, SDL_BLENDMODE_ADD),
+ ENTRY_BM(ARGB32, XRGB8888, rot_argb32_rgb32, SDL_BLENDMODE_MOD),
+ ENTRY_BM(ARGB32, XRGB8888, rot_argb32_rgb32, SDL_BLENDMODE_NONE),
+
+ ENTRY(RGB32, ARGB8888, rot_rgb32_argb32),
+ ENTRY(RGB32, XRGB8888, rot_argb32_argb32),
+
+ ENTRY(RGB32_PALETTED, ARGB8888, rot_rgb32pal_argb32),
+ ENTRY(RGB32_PALETTED, XRGB8888, rot_rgb32pal_argb32),
+
+ ENTRY(YUY16, ARGB8888, rot_yuv16_argb32rot),
+ ENTRY(YUY16, XRGB8888, rot_yuv16_argb32rot),
+
+ ENTRY(YUY16_PALETTED, ARGB8888, rot_yuv16pal_argb32rot),
+ ENTRY(YUY16_PALETTED, XRGB8888, rot_yuv16pal_argb32rot),
+
+ ENTRY(PALETTE16, ARGB8888, rot_pal16_argb32),
+ ENTRY(PALETTE16, XRGB8888, rot_pal16_argb32),
+
+ ENTRY(RGB15, XRGB1555, rot_rgb15_argb1555),
+ ENTRY(RGB15, ARGB1555, rot_rgb15_argb1555),
+ ENTRY(RGB15, ARGB8888, rot_rgb15_argb32),
+ ENTRY(RGB15, XRGB8888, rot_rgb15_argb32),
+
+ ENTRY(RGB15_PALETTED, ARGB8888, rot_rgb15pal_argb32),
+ ENTRY(RGB15_PALETTED, XRGB8888, rot_rgb15pal_argb32),
+
+ ENTRY(PALETTE16A, ARGB8888, rot_pal16a_argb32),
+ ENTRY(PALETTE16A, XRGB8888, rot_pal16a_rgb32),
+
+ { -1 },
+};
+
+} // anonymous namespace
+
+} // namespace osd
+
+
+#else // defined(OSD_SDL) && defined (SDLMAME_SDL3)
+
+namespace osd { namespace { MODULE_NOT_SUPPORTED(video_sdl3_accel, OSD_RENDERER_PROVIDER, "accel") } }
+
+#endif // defined(OSD_SDL) && defined (SDLMAME_SDL3)
+
+MODULE_DEFINITION(RENDERER_SDL3ACCEL, osd::video_sdl3_accel)
diff --git a/src/osd/modules/render/drawsdl3soft.cpp b/src/osd/modules/render/drawsdl3soft.cpp
new file mode 100644
index 00000000000..8601f8315ef
--- /dev/null
+++ b/src/osd/modules/render/drawsdl3soft.cpp
@@ -0,0 +1,700 @@
+// license:BSD-3-Clause
+// copyright-holders:Couriersud, Olivier Galibert, R. Belmont
+//============================================================
+//
+// drawsdl3soft.cpp - SDL3 software renderer
+//
+// SDLMAME by Olivier Galibert and R. Belmont
+//
+// yuvmodes by Couriersud
+//
+//============================================================
+
+#include "render_module.h"
+
+#include "modules/osdmodule.h"
+
+#if defined(OSD_SDL) && defined (SDLMAME_SDL3)
+
+// from specific OSD implementation
+#include "sdlopts.h"
+#include "window.h"
+
+// general OSD headers
+#include "modules/monitor/monitor_module.h"
+
+// MAME headers
+#include "emucore.h"
+#include "render.h"
+#include "rendersw.hxx"
+
+#include <SDL3/SDL.h>
+
+// standard C headers
+#include <cmath>
+#include <cstdio>
+#include <memory>
+
+
+namespace osd {
+
+namespace {
+
+//============================================================
+// CONSTANTS
+//============================================================
+
+#define DRAW2_SCALEMODE_NEAREST "0"
+#define DRAW2_SCALEMODE_LINEAR "1"
+#define DRAW2_SCALEMODE_BEST "2"
+
+
+struct sdl_scale_mode
+{
+ const char *name;
+ int is_scale; /* Scale mode? */
+ int is_yuv; /* Yuv mode? */
+ int mult_w; /* Width multiplier */
+ int mult_h; /* Height multiplier */
+ const char *sdl_scale_mode_hint; /* what to use as a hint ? */
+ int pixel_format; /* Pixel/Overlay format */
+ void (*yuv_blit)(const uint16_t *bitmap, uint8_t *ptr, const int pitch, const uint32_t *lookup, const int width, const int height);
+};
+
+
+// renderer_sdl1 is the information about SDL for the current screen
+class renderer_sdl1 : public osd_renderer
+{
+public:
+
+ renderer_sdl1(osd_window &w, sdl_scale_mode const &scale_mode)
+ : osd_renderer(w)
+ , m_scale_mode(scale_mode)
+ , m_sdl_renderer(nullptr)
+ , m_texture_id(nullptr)
+ , m_yuv_lookup()
+ , m_yuv_bitmap()
+ //, m_hw_scale_width(0)
+ //, m_hw_scale_height(0)
+ , m_last_hofs(0)
+ , m_last_vofs(0)
+ , m_blit_dim(0, 0)
+ , m_last_dim(0, 0)
+ {
+ }
+ virtual ~renderer_sdl1();
+
+ virtual int create() override;
+ virtual int draw(const int update) override;
+ virtual int xy_to_render_target(const int x, const int y, int *xt, int *yt) override;
+ virtual render_primitive_list *get_primitives() override;
+
+private:
+ void destroy_all_textures();
+ void yuv_init();
+ void setup_texture(const osd_dim &size);
+ void yuv_lookup_set(unsigned int pen, unsigned char red,
+ unsigned char green, unsigned char blue);
+
+ int32_t m_blittimer;
+
+ sdl_scale_mode const &m_scale_mode;
+ SDL_Renderer *m_sdl_renderer;
+ SDL_Texture *m_texture_id;
+
+ // YUV overlay
+ std::unique_ptr<uint32_t []> m_yuv_lookup;
+ std::unique_ptr<uint16_t []> m_yuv_bitmap;
+
+ // if we leave scaling to SDL and the underlying driver, this
+ // is the render_target_width/height to use
+
+ int m_last_hofs;
+ int m_last_vofs;
+ osd_dim m_blit_dim;
+ osd_dim m_last_dim;
+};
+
+
+//============================================================
+// PROTOTYPES
+//============================================================
+
+// YUV overlays
+
+static void yuv_RGB_to_YV12(const uint16_t *bitmap, uint8_t *ptr, const int pitch,
+ const uint32_t *lookup, const int width, const int height);
+static void yuv_RGB_to_YV12X2(const uint16_t *bitmap, uint8_t *ptr, const int pitch,
+ const uint32_t *lookup, const int width, const int height);
+static void yuv_RGB_to_YUY2(const uint16_t *bitmap, uint8_t *ptr, const int pitch,
+ const uint32_t *lookup, const int width, const int height);
+static void yuv_RGB_to_YUY2X2(const uint16_t *bitmap, uint8_t *ptr, const int pitch,
+ const uint32_t *lookup, const int width, const int height);
+
+//============================================================
+// setup_texture for window
+//============================================================
+
+void renderer_sdl1::setup_texture(const osd_dim &size)
+{
+ const SDL_DisplayMode *mode = SDL_GetCurrentDisplayMode(window().monitor()->oshandle());
+ uint32_t fmt;
+
+ m_yuv_bitmap.reset();
+
+ fmt = (m_scale_mode.pixel_format ? m_scale_mode.pixel_format : mode->format);
+
+ if (m_scale_mode.is_scale)
+ {
+ int m_hw_scale_width = 0;
+ int m_hw_scale_height = 0;
+
+ window().target()->compute_minimum_size(m_hw_scale_width, m_hw_scale_height);
+ if (window().prescale())
+ {
+ m_hw_scale_width *= window().prescale();
+ m_hw_scale_height *= window().prescale();
+
+ /* This must be a multiple of 2 */
+ m_hw_scale_width = (m_hw_scale_width + 1) & ~1;
+ }
+ if (m_scale_mode.is_yuv)
+ m_yuv_bitmap = std::make_unique<uint16_t []>(m_hw_scale_width * m_hw_scale_height);
+
+ int w = m_hw_scale_width * m_scale_mode.mult_w;
+ int h = m_hw_scale_height * m_scale_mode.mult_h;
+
+ m_texture_id = SDL_CreateTexture(m_sdl_renderer, SDL_PixelFormat(fmt), SDL_TEXTUREACCESS_STREAMING, w, h);
+ }
+ else
+ {
+ m_texture_id = SDL_CreateTexture(m_sdl_renderer, SDL_PixelFormat(fmt), SDL_TEXTUREACCESS_STREAMING,
+ size.width(), size.height());
+ }
+}
+
+//============================================================
+// renderer_sdl1::create
+//============================================================
+
+int renderer_sdl1::create()
+{
+ // create renderer
+ osd_printf_verbose("Enter renderer_sdl1::create()\n");
+ SDL_Window *sdl_window = dynamic_cast<sdl_window_info &>(window()).platform_window();
+ m_sdl_renderer = SDL_CreateRenderer(sdl_window, "software");
+ if (!m_sdl_renderer)
+ {
+ fatalerror("Error creating renderer: %s\n", SDL_GetError());
+ }
+
+ if (video_config.waitvsync)
+ {
+ SDL_SetRenderVSync(m_sdl_renderer, SDL_RENDERER_VSYNC_ADAPTIVE);
+ }
+
+ m_yuv_lookup = nullptr;
+ m_blittimer = 0;
+
+ yuv_init();
+ osd_printf_verbose("Leave renderer_sdl1::create\n");
+ return 0;
+}
+
+//============================================================
+// DESTRUCTOR
+//============================================================
+
+renderer_sdl1::~renderer_sdl1()
+{
+ destroy_all_textures();
+
+ SDL_DestroyRenderer(m_sdl_renderer);
+}
+
+//============================================================
+// drawsdl_xy_to_render_target
+//============================================================
+
+int renderer_sdl1::xy_to_render_target(int x, int y, int *xt, int *yt)
+{
+ *xt = x - m_last_hofs;
+ *yt = y - m_last_vofs;
+ if (*xt<0 || *xt >= m_blit_dim.width())
+ return 0;
+ if (*yt<0 || *yt >= m_blit_dim.height())
+ return 0;
+ return 1;
+}
+
+//============================================================
+// drawsdl_destroy_all_textures
+//============================================================
+
+void renderer_sdl1::destroy_all_textures()
+{
+ SDL_DestroyTexture(m_texture_id);
+ m_texture_id = nullptr;
+}
+
+
+//============================================================
+// renderer_sdl2::draw
+//============================================================
+
+int renderer_sdl1::draw(int update)
+{
+ uint8_t *surfptr;
+ int32_t pitch;
+ Uint32 rmask, gmask, bmask;
+ Uint32 amask;
+ int32_t vofs, hofs, blitwidth, blitheight, ch, cw;
+ int bpp;
+
+ osd_dim wdim = window().get_size();
+ if (has_flags(FI_CHANGED) || (wdim != m_last_dim))
+ {
+ destroy_all_textures();
+ clear_flags(FI_CHANGED);
+ m_blittimer = 3;
+ m_last_dim = wdim;
+ SDL_SetRenderViewport(m_sdl_renderer, nullptr);
+ if (m_texture_id != nullptr)
+ SDL_DestroyTexture(m_texture_id);
+ setup_texture(m_blit_dim);
+ m_blittimer = 3;
+ }
+
+ // lock it if we need it
+ const auto props = SDL_GetTextureProperties(m_texture_id);
+ const SDL_PixelFormat format = (SDL_PixelFormat)SDL_GetNumberProperty(props, SDL_PROP_TEXTURE_FORMAT_NUMBER, 0);
+
+ SDL_GetMasksForPixelFormat(format, &bpp, &rmask, &gmask, &bmask, &amask);
+ bpp = bpp / 8; /* convert to bytes per pixels */
+
+ // Clear if necessary
+ if (m_blittimer > 0)
+ {
+ /* SDL Underlays need alpha = 0 ! */
+ SDL_SetRenderDrawColor(m_sdl_renderer, 0, 0, 0, 0);
+ SDL_RenderFillRect(m_sdl_renderer, nullptr);
+ m_blittimer--;
+ }
+
+ SDL_LockTexture(m_texture_id, nullptr, (void **) &surfptr, &pitch);
+
+ // get ready to center the image
+ vofs = hofs = 0;
+ blitwidth = m_blit_dim.width();
+ blitheight = m_blit_dim.height();
+
+ ch = wdim.height();
+ cw = wdim.width();
+
+ // do not crash if the window's smaller than the blit area
+ if (blitheight > ch)
+ {
+ blitheight = ch;
+ }
+ else if (video_config.centerv)
+ {
+ vofs = (ch - m_blit_dim.height()) / 2;
+ }
+
+ if (blitwidth > cw)
+ {
+ blitwidth = cw;
+ }
+ else if (video_config.centerh)
+ {
+ hofs = (cw - m_blit_dim.width()) / 2;
+ }
+
+ m_last_hofs = hofs;
+ m_last_vofs = vofs;
+
+ window().m_primlist->acquire_lock();
+
+ auto mamewidth = SDL_GetNumberProperty(props, SDL_PROP_TEXTURE_WIDTH_NUMBER, 0);
+ auto mameheight = SDL_GetNumberProperty(props, SDL_PROP_TEXTURE_HEIGHT_NUMBER, 0);
+
+ mamewidth /= m_scale_mode.mult_w;
+ mameheight /= m_scale_mode.mult_h;
+
+ // rescale bounds
+ float fw = (float) mamewidth / (float) blitwidth;
+ float fh = (float) mameheight / (float) blitheight;
+
+ // FIXME: this could be a lot easier if we get the primlist here!
+ // Bounds would be set fit for purpose and done!
+
+ for (render_primitive &prim : *window().m_primlist)
+ {
+ prim.bounds.x0 = floor(fw * prim.bounds.x0 + 0.5f);
+ prim.bounds.x1 = floor(fw * prim.bounds.x1 + 0.5f);
+ prim.bounds.y0 = floor(fh * prim.bounds.y0 + 0.5f);
+ prim.bounds.y1 = floor(fh * prim.bounds.y1 + 0.5f);
+ }
+
+ // render to it
+ if (!m_scale_mode.is_yuv)
+ {
+ switch (rmask)
+ {
+ case 0xff000000:
+ software_renderer<uint32_t, 0,0,0, 24,16,8>::draw_primitives(*window().m_primlist, surfptr, mamewidth, mameheight, pitch / 4);
+ break;
+
+ case 0x0000ff00:
+ software_renderer<uint32_t, 0,0,0, 8,16,24>::draw_primitives(*window().m_primlist, surfptr, mamewidth, mameheight, pitch / 4);
+ break;
+
+ case 0x00ff0000:
+ software_renderer<uint32_t, 0,0,0, 16,8,0>::draw_primitives(*window().m_primlist, surfptr, mamewidth, mameheight, pitch / 4);
+ break;
+
+ case 0x000000ff:
+ software_renderer<uint32_t, 0,0,0, 0,8,16>::draw_primitives(*window().m_primlist, surfptr, mamewidth, mameheight, pitch / 4);
+ break;
+
+ case 0xf800:
+ software_renderer<uint16_t, 3,2,3, 11,5,0>::draw_primitives(*window().m_primlist, surfptr, mamewidth, mameheight, pitch / 2);
+ break;
+
+ case 0x7c00:
+ software_renderer<uint16_t, 3,3,3, 10,5,0>::draw_primitives(*window().m_primlist, surfptr, mamewidth, mameheight, pitch / 2);
+ break;
+
+ default:
+ osd_printf_error("SDL: ERROR! Unknown video mode: R=%08X G=%08X B=%08X\n", rmask, gmask, bmask);
+ break;
+ }
+ }
+ else
+ {
+ assert (m_yuv_bitmap != nullptr);
+ assert (surfptr != nullptr);
+ software_renderer<uint16_t, 3,3,3, 10,5,0>::draw_primitives(*window().m_primlist, m_yuv_bitmap.get(), mamewidth, mameheight, mamewidth);
+ m_scale_mode.yuv_blit(m_yuv_bitmap.get(), surfptr, pitch, m_yuv_lookup.get(), mamewidth, mameheight);
+ }
+
+ window().m_primlist->release_lock();
+
+ // unlock and flip
+ SDL_UnlockTexture(m_texture_id);
+ {
+ SDL_FRect r;
+
+ r.x=hofs;
+ r.y=vofs;
+ r.w=blitwidth;
+ r.h=blitheight;
+ SDL_RenderTexture(m_sdl_renderer, m_texture_id, nullptr, (const SDL_FRect *)&r);
+ SDL_RenderPresent(m_sdl_renderer);
+ }
+ return 0;
+}
+//============================================================
+// YUV Blitting
+//============================================================
+
+#define CU_CLAMP(v, a, b) ((v < a)? a: ((v > b)? b: v))
+#define RGB2YUV_F(r,g,b,y,u,v) \
+ (y) = (0.299*(r) + 0.587*(g) + 0.114*(b) ); \
+ (u) = (-0.169*(r) - 0.331*(g) + 0.5*(b) + 128); \
+ (v) = (0.5*(r) - 0.419*(g) - 0.081*(b) + 128); \
+ (y) = CU_CLAMP(y,0,255); \
+ (u) = CU_CLAMP(u,0,255); \
+ (v) = CU_CLAMP(v,0,255)
+
+#define RGB2YUV(r,g,b,y,u,v) \
+ (y) = (( 8453*(r) + 16594*(g) + 3223*(b) + 524288) >> 15); \
+ (u) = (( -4878*(r) - 9578*(g) + 14456*(b) + 4210688) >> 15); \
+ (v) = (( 14456*(r) - 12105*(g) - 2351*(b) + 4210688) >> 15)
+
+#ifdef LSB_FIRST
+#define Y1MASK 0x000000FF
+#define UMASK 0x0000FF00
+#define Y2MASK 0x00FF0000
+#define VMASK 0xFF000000
+#define Y1SHIFT 0
+#define USHIFT 8
+#define Y2SHIFT 16
+#define VSHIFT 24
+#else
+#define Y1MASK 0xFF000000
+#define UMASK 0x00FF0000
+#define Y2MASK 0x0000FF00
+#define VMASK 0x000000FF
+#define Y1SHIFT 24
+#define USHIFT 16
+#define Y2SHIFT 8
+#define VSHIFT 0
+#endif
+
+#define YMASK (Y1MASK|Y2MASK)
+#define UVMASK (UMASK|VMASK)
+
+void renderer_sdl1::yuv_lookup_set(unsigned int pen, unsigned char red,
+ unsigned char green, unsigned char blue)
+{
+ uint32_t y,u,v;
+
+ RGB2YUV(red,green,blue,y,u,v);
+
+ /* Storing this data in YUYV order simplifies using the data for
+ YUY2, both with and without smoothing... */
+ m_yuv_lookup[pen]=(y<<Y1SHIFT)|(u<<USHIFT)|(y<<Y2SHIFT)|(v<<VSHIFT);
+}
+
+void renderer_sdl1::yuv_init()
+{
+ if (!m_yuv_lookup)
+ m_yuv_lookup = std::make_unique<uint32_t []>(65536);
+ for (unsigned char r = 0; r < 32; r++)
+ for (unsigned char g = 0; g < 32; g++)
+ for (unsigned char b = 0; b < 32; b++)
+ {
+ int idx = (r << 10) | (g << 5) | b;
+ yuv_lookup_set(idx,
+ (r << 3) | (r >> 2),
+ (g << 3) | (g >> 2),
+ (b << 3) | (b >> 2));
+ }
+}
+
+//uint32_t *lookup = sdl->m_yuv_lookup;
+
+static void yuv_RGB_to_YV12(const uint16_t *bitmap, uint8_t *ptr, const int pitch,
+ const uint32_t *lookup, const int width, const int height)
+{
+ int x, y;
+ uint8_t *pixels[3];
+ int u1,v1,y1,u2,v2,y2,u3,v3,y3,u4,v4,y4; /* 12 */
+
+ pixels[0] = ptr;
+ pixels[1] = ptr + pitch * height;
+ pixels[2] = pixels[1] + pitch * height / 4;
+
+ for(y=0;y<height;y+=2)
+ {
+ const uint16_t *src=bitmap + (y * width) ;
+ const uint16_t *src2=src + width;
+
+ uint8_t *dest_y = pixels[0] + y * pitch;
+ uint8_t *dest_v = pixels[1] + (y>>1) * pitch / 2;
+ uint8_t *dest_u = pixels[2] + (y>>1) * pitch / 2;
+
+ for(x=0;x<width;x+=2)
+ {
+ v1 = lookup[src[x]];
+ y1 = (v1>>Y1SHIFT) & 0xff;
+ u1 = (v1>>USHIFT) & 0xff;
+ v1 = (v1>>VSHIFT) & 0xff;
+
+ v2 = lookup[src[x+1]];
+ y2 = (v2>>Y1SHIFT) & 0xff;
+ u2 = (v2>>USHIFT) & 0xff;
+ v2 = (v2>>VSHIFT) & 0xff;
+
+ v3 = lookup[src2[x]];
+ y3 = (v3>>Y1SHIFT) & 0xff;
+ u3 = (v3>>USHIFT) & 0xff;
+ v3 = (v3>>VSHIFT) & 0xff;
+
+ v4 = lookup[src2[x+1]];
+ y4 = (v4>>Y1SHIFT) & 0xff;
+ u4 = (v4>>USHIFT) & 0xff;
+ v4 = (v4>>VSHIFT) & 0xff;
+
+ dest_y[x] = y1;
+ dest_y[x+pitch] = y3;
+ dest_y[x+1] = y2;
+ dest_y[x+pitch+1] = y4;
+
+ dest_u[x>>1] = (u1+u2+u3+u4)/4;
+ dest_v[x>>1] = (v1+v2+v3+v4)/4;
+
+ }
+ }
+}
+
+static void yuv_RGB_to_YV12X2(const uint16_t *bitmap, uint8_t *ptr, const int pitch,
+ const uint32_t *lookup, const int width, const int height)
+{
+ /* this one is used when scale==2 */
+ unsigned int x,y;
+ int u1,v1,y1;
+ uint8_t *pixels[3];
+
+ pixels[0] = ptr;
+ pixels[1] = ptr + pitch * height * 2;
+ int p2 = (pitch >> 1);
+ pixels[2] = pixels[1] + p2 * height;
+
+ for(y=0;y<height;y++)
+ {
+ const uint16_t *src = bitmap + (y * width) ;
+
+ uint16_t *dest_y = (uint16_t *)(pixels[0] + 2 * y * pitch);
+ uint8_t *dest_v = pixels[1] + y * p2;
+ uint8_t *dest_u = pixels[2] + y * p2;
+ for(x=0;x<width;x++)
+ {
+ v1 = lookup[src[x]];
+ y1 = (v1 >> Y1SHIFT) & 0xff;
+ u1 = (v1 >> USHIFT) & 0xff;
+ v1 = (v1 >> VSHIFT) & 0xff;
+
+ dest_y[x + pitch/2] = y1 << 8 | y1;
+ dest_y[x] = y1 << 8 | y1;
+ dest_u[x] = u1;
+ dest_v[x] = v1;
+ }
+ }
+}
+
+static void yuv_RGB_to_YUY2(const uint16_t *bitmap, uint8_t *ptr, const int pitch,
+ const uint32_t *lookup, const int width, const int height)
+{
+ /* this one is used when scale==2 */
+ unsigned int y;
+ uint32_t p1,p2,uv;
+ const int yuv_pitch = pitch/4;
+
+ for(y=0;y<height;y++)
+ {
+ const uint16_t *src=bitmap + (y * width) ;
+ const uint16_t *end=src+width;
+
+ uint32_t *dest = (uint32_t *) ptr;
+ dest += y * yuv_pitch;
+ for(; src<end; src+=2)
+ {
+ p1 = lookup[src[0]];
+ p2 = lookup[src[1]];
+ uv = (p1&UVMASK)>>1;
+ uv += (p2&UVMASK)>>1;
+ *dest++ = (p1&Y1MASK)|(p2&Y2MASK)|(uv&UVMASK);
+ }
+ }
+}
+
+static void yuv_RGB_to_YUY2X2(const uint16_t *bitmap, uint8_t *ptr, const int pitch,
+ const uint32_t *lookup, const int width, const int height)
+{
+ /* this one is used when scale==2 */
+ unsigned int y;
+ int yuv_pitch = pitch / 4;
+
+ for(y=0;y<height;y++)
+ {
+ const uint16_t *src=bitmap + (y * width) ;
+ const uint16_t *end=src+width;
+
+ uint32_t *dest = (uint32_t *) ptr;
+ dest += (y * yuv_pitch);
+ for(; src<end; src++)
+ {
+ dest[0] = lookup[src[0]];
+ dest++;
+ }
+ }
+}
+
+render_primitive_list *renderer_sdl1::get_primitives()
+{
+ osd_dim nd = window().get_size();
+ if (nd != m_blit_dim)
+ {
+ m_blit_dim = nd;
+ notify_changed();
+ }
+ window().target()->set_bounds(m_blit_dim.width(), m_blit_dim.height(), window().pixel_aspect());
+ return &window().target()->get_primitives();
+}
+
+
+class video_sdl3soft : public osd_module, public render_module
+{
+public:
+ video_sdl3soft()
+ : osd_module(OSD_RENDERER_PROVIDER, "soft")
+ , m_scale_mode(-1)
+ {
+ }
+
+ virtual int init(osd_interface &osd, osd_options const &options) override;
+ virtual void exit() override { }
+
+ virtual std::unique_ptr<osd_renderer> create(osd_window &window) override;
+
+protected:
+ virtual unsigned flags() const override { return FLAG_INTERACTIVE; }
+
+private:
+ static int get_scale_mode(char const *modestr);
+
+ int m_scale_mode;
+
+ static sdl_scale_mode const s_scale_modes[];
+};
+
+int video_sdl3soft::init(osd_interface &osd, osd_options const &options)
+{
+ osd_printf_verbose("Using SDL multi-window soft driver (SDL 3.2+)\n");
+
+ // yuv settings ...
+ char const *const modestr = dynamic_cast<sdl_options const &>(options).scale_mode();
+ m_scale_mode = get_scale_mode(modestr);
+ if (m_scale_mode < 0)
+ {
+ osd_printf_warning("Invalid yuvmode value %s; reverting to none\n", modestr);
+ m_scale_mode = 0;
+ }
+
+ return 0;
+}
+
+std::unique_ptr<osd_renderer> video_sdl3soft::create(osd_window &window)
+{
+ return std::make_unique<renderer_sdl1>(window, s_scale_modes[m_scale_mode]);
+}
+
+int video_sdl3soft::get_scale_mode(char const *modestr)
+{
+ const sdl_scale_mode *sm = s_scale_modes;
+ int index = 0;
+ while (sm->name)
+ {
+ if (!strcmp(sm->name, modestr))
+ return index;
+ index++;
+ sm++;
+ }
+ return -1;
+}
+
+sdl_scale_mode const video_sdl3soft::s_scale_modes[] = {
+ { "none", 0, 0, 1, 1, DRAW2_SCALEMODE_NEAREST, 0, nullptr },
+ { "hwblit", 1, 0, 1, 1, DRAW2_SCALEMODE_LINEAR, 0, nullptr },
+ { "hwbest", 1, 0, 1, 1, DRAW2_SCALEMODE_BEST, 0, nullptr },
+ /* SDL1.2 uses interpolation as well */
+ { "yv12", 1, 1, 1, 1, DRAW2_SCALEMODE_BEST, SDL_PIXELFORMAT_YV12, yuv_RGB_to_YV12 },
+ { "yv12x2", 1, 1, 2, 2, DRAW2_SCALEMODE_BEST, SDL_PIXELFORMAT_YV12, yuv_RGB_to_YV12X2 },
+ { "yuy2", 1, 1, 1, 1, DRAW2_SCALEMODE_BEST, SDL_PIXELFORMAT_YUY2, yuv_RGB_to_YUY2 },
+ { "yuy2x2", 1, 1, 2, 1, DRAW2_SCALEMODE_BEST, SDL_PIXELFORMAT_YUY2, yuv_RGB_to_YUY2X2 },
+ { nullptr } };
+
+} // anonymous namespace
+
+} // namespace osd
+
+#else // defined(OSD_SDL) && defined (SDLMAME_SDL3)
+
+namespace osd { namespace { MODULE_NOT_SUPPORTED(video_sdl3soft, OSD_RENDERER_PROVIDER, "soft") } }
+
+#endif // defined(OSD_SDL) && defined (SDLMAME_SDL3)
+
+MODULE_DEFINITION(RENDERER_SDL3SOFT, osd::video_sdl3soft)
+
diff --git a/src/osd/modules/render/sdlglcontext.h b/src/osd/modules/render/sdlglcontext.h
index d2055060bd6..b62f757c652 100644
--- a/src/osd/modules/render/sdlglcontext.h
+++ b/src/osd/modules/render/sdlglcontext.h
@@ -17,7 +17,11 @@
#include "strformat.h"
+#ifdef SDLMAME_SDL3
+#include <SDL3/SDL.h>
+#else
#include <SDL2/SDL.h>
+#endif
#include <string>
@@ -38,7 +42,13 @@ public:
virtual ~sdl_gl_context()
{
if (m_context)
+ {
+#ifdef SDLMAME_SDL3
+ SDL_GL_DestroyContext(m_context);
+#else
SDL_GL_DeleteContext(m_context);
+#endif
+ }
}
virtual explicit operator bool() const override
@@ -66,7 +76,7 @@ public:
virtual void *get_proc_address(const char *proc) override
{
- return SDL_GL_GetProcAddress(proc);
+ return (void *)SDL_GL_GetProcAddress(proc);
}
virtual void swap_buffer() override
diff --git a/src/osd/modules/sound/sdl3_sound.cpp b/src/osd/modules/sound/sdl3_sound.cpp
new file mode 100644
index 00000000000..c04c4934154
--- /dev/null
+++ b/src/osd/modules/sound/sdl3_sound.cpp
@@ -0,0 +1,373 @@
+// license:BSD-3-Clause
+// copyright-holders:Olivier Galibert, R. Belmont
+//============================================================
+//
+// sdl3_sound.cpp - SDL3+ implementation of MAME sound routines
+//
+// SDLMAME by Olivier Galibert and R. Belmont
+//
+//============================================================
+
+#include "sound_module.h"
+
+#include "modules/osdmodule.h"
+
+#if (defined(OSD_SDL) || defined(USE_SDL_SOUND)) && defined(SDLMAME_SDL3)
+
+#include "modules/lib/osdobj_common.h"
+#include "osdcore.h"
+
+// standard sdl header
+#include <SDL3/SDL.h>
+
+#include <algorithm>
+#include <cmath>
+#include <fstream>
+#include <memory>
+#include <map>
+
+
+namespace osd {
+
+namespace {
+
+class sound_sdl3 : public osd_module, public sound_module
+{
+public:
+ sound_sdl3() :
+ osd_module(OSD_SOUND_PROVIDER, "sdl"), sound_module()
+ {
+ }
+
+ virtual ~sound_sdl3() { }
+
+ virtual int init(osd_interface &osd, const osd_options &options) override;
+ virtual void exit() override;
+
+ virtual bool external_per_channel_volume() override { return false; }
+ virtual bool split_streams_per_source() override { return true; }
+
+ virtual uint32_t get_generation() override;
+ virtual osd::audio_info get_information() override;
+ virtual uint32_t stream_sink_open(uint32_t node, std::string name, uint32_t rate) override;
+ virtual uint32_t stream_source_open(uint32_t node, std::string name, uint32_t rate) override;
+ virtual void stream_close(uint32_t id) override;
+ virtual void stream_sink_update(uint32_t id, const int16_t *buffer, int samples_this_frame) override;
+ virtual void stream_source_update(uint32_t id, int16_t *buffer, int samples_this_frame) override;
+
+private:
+ struct device_info {
+ SDL_AudioDeviceID m_device_id;
+ std::string m_name;
+ int m_freq;
+ uint8_t m_channels;
+ bool m_issource;
+ bool m_def;
+ device_info(const SDL_AudioDeviceID device_id, const char *name, int freq, uint8_t channels, bool source, bool def = false) :
+ m_device_id(device_id),
+ m_name(name),
+ m_freq(freq),
+ m_channels(channels),
+ m_issource(source),
+ m_def(def)
+ {
+ }
+ };
+
+ struct stream_info {
+ uint32_t m_id;
+ SDL_AudioDeviceID m_sdl_id;
+ SDL_AudioStream *m_sdl_stream;
+ abuffer m_buffer;
+ stream_info(uint32_t id, uint8_t channels) : m_id(id), m_sdl_id(0), m_buffer(channels) {}
+ };
+
+ std::vector<device_info> m_devices;
+ uint32_t m_default_sink, m_default_source;
+ uint32_t m_stream_next_id;
+ osd::audio_info m_deviceinfo;
+
+ std::map<uint32_t, std::unique_ptr<stream_info>> m_streams;
+
+ static void sink_callback(void *userdata, Uint8 *stream, int len);
+};
+
+//============================================================
+// sound_sdl3::init
+//============================================================
+
+int sound_sdl3::init(osd_interface &osd, const osd_options &options)
+{
+ m_stream_next_id = 1;
+
+ if(!SDL_InitSubSystem(SDL_INIT_AUDIO)) {
+ osd_printf_error("Could not initialize SDL %s\n", SDL_GetError());
+ return -1;
+ }
+
+ osd_printf_verbose("SDL Audio: Start initialization\n");
+ char const *const audio_driver = SDL_GetCurrentAudioDriver();
+ osd_printf_verbose("SDL Audio: Driver is %s\n", audio_driver ? audio_driver : "not initialized");
+
+ if(options.audio_latency() > 0.0f)
+ osd_printf_verbose("SDL Audio: %s module does not support audio_latency option\n", name());
+
+ int dev_count = 0;
+ SDL_AudioSpec spec;
+ SDL_AudioDeviceID *devices = SDL_GetAudioPlaybackDevices(&dev_count);
+
+ if (dev_count) {
+ // add default sink device
+ SDL_GetAudioDeviceFormat(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &spec, nullptr);
+ // if the device reports 0 channels, force it to stereo as a fallback
+ if (spec.channels == 0) {
+ spec.channels = 2;
+ }
+ m_devices.emplace_back(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, "Default", spec.freq, spec.channels, false, true);
+ m_default_sink = m_devices.size();
+ osd_printf_verbose("SDL Audio: Sink device %d: device 'SDL default sink device'\n", m_default_sink);
+ }
+
+ for(int i=0; i != dev_count; i++) {
+ const char *const name = SDL_GetAudioDeviceName(devices[i]);
+ const bool success = SDL_GetAudioDeviceFormat(devices[i], &spec, nullptr);
+
+ // if the device reports 0 channels, force it to stereo as a fallback
+ if (spec.channels == 0) {
+ spec.channels = 2;
+ }
+
+ if(success) {
+ m_devices.emplace_back(devices[i], name, spec.freq, spec.channels, false);
+
+ osd_printf_verbose("SDL Audio: Sink device %d: device '%s' freq %d channels %d (SDL ID %d)\n", m_devices.size(), name, spec.freq, spec.channels, devices[i]);
+ }
+ }
+
+ SDL_free(devices);
+
+ devices = SDL_GetAudioRecordingDevices(&dev_count);
+
+ if (dev_count) {
+ // add default device
+ SDL_GetAudioDeviceFormat(SDL_AUDIO_DEVICE_DEFAULT_RECORDING, &spec, nullptr);
+ // if the device reports 0 channels, assume mono as a good microphone fallback
+ if (spec.channels == 0) {
+ spec.channels = 1;
+ }
+ m_devices.emplace_back(SDL_AUDIO_DEVICE_DEFAULT_RECORDING, "Default", spec.freq, spec.channels, true, true);
+ m_default_source = m_devices.size();
+ osd_printf_verbose("SDL Audio: Source device %d: device 'SDL default source device'\n", m_default_source);
+ }
+
+ for (int i = 0; i != dev_count; i++)
+ {
+ const char *const name = SDL_GetAudioDeviceName(devices[i]);
+ const bool success = SDL_GetAudioDeviceFormat(devices[i], &spec, nullptr);
+
+ // if the device reports 0 channels, assume mono as a good microphone fallback
+ if (spec.channels == 0)
+ {
+ spec.channels = 1;
+ }
+
+ if (success) {
+ m_devices.emplace_back(devices[i], name, spec.freq, spec.channels, true);
+ osd_printf_verbose("SDL Audio: Source device %d: device '%s' freq %d channels %d (SDL ID %d)\n", m_devices.size(), name, spec.freq, spec.channels, devices[i]);
+ }
+ }
+ SDL_free(devices);
+ return 0;
+}
+
+void sound_sdl3::exit()
+{
+ SDL_QuitSubSystem(SDL_INIT_AUDIO);
+ m_devices.clear();
+}
+
+uint32_t sound_sdl3::get_generation()
+{
+ return 1;
+}
+
+osd::audio_info sound_sdl3::get_information()
+{
+ enum { FL, FR, FC, LFE, BL, BR, BC, SL, SR, AUX };
+ static const char *const posname[10] = { "FL", "FR", "FC", "LFE", "BL", "BR", "BC", "SL", "SR", "AUX" };
+
+ static const osd::channel_position pos3d[10] = {
+ osd::channel_position::FL(),
+ osd::channel_position::FR(),
+ osd::channel_position::FC(),
+ osd::channel_position::LFE(),
+ osd::channel_position::RL(),
+ osd::channel_position::RR(),
+ osd::channel_position::RC(),
+ osd::channel_position(-0.2, 0.0, 0.0),
+ osd::channel_position( 0.2, 0.0, 0.0),
+ osd::channel_position::ONREQ()
+ };
+
+ static const uint32_t positions[8][9] = {
+ { FC },
+ { FL, FR },
+ { FL, FR, LFE },
+ { FL, FR, BL, BR },
+ { FL, FR, LFE, BL, BR },
+ { FL, FR, FC, LFE, BL, BR },
+ { FL, FR, FC, LFE, BC, SL, SR },
+ { FL, FR, FC, LFE, BL, BR, SL, SR, AUX }
+ };
+
+ m_deviceinfo.m_nodes.clear();
+ m_deviceinfo.m_nodes.resize(m_devices.size());
+ m_deviceinfo.m_default_sink = m_default_sink;
+ m_deviceinfo.m_default_source = 0;
+ m_deviceinfo.m_generation = 1;
+ for (uint32_t node = 0; node < m_devices.size(); node++) {
+ m_deviceinfo.m_nodes[node].m_name = m_devices[node].m_name;
+ m_deviceinfo.m_nodes[node].m_display_name = m_devices[node].m_name;
+ m_deviceinfo.m_nodes[node].m_id = node + 1;
+ uint32_t freq = m_devices[node].m_freq;
+ m_deviceinfo.m_nodes[node].m_rate = audio_rate_range{ freq, freq, freq };
+ if (m_devices[node].m_issource) {
+ m_deviceinfo.m_nodes[node].m_sources = m_devices[node].m_channels;
+ } else {
+ m_deviceinfo.m_nodes[node].m_sinks = m_devices[node].m_channels;
+ }
+ int channels = m_devices[node].m_channels;
+ int index = std::min(channels, 8) - 1;
+ for(uint32_t port = 0; port != channels; port++) {
+ uint32_t pos = positions[index][std::min(8U, port)];
+ m_deviceinfo.m_nodes[node].m_port_names.push_back(posname[pos]);
+ m_deviceinfo.m_nodes[node].m_port_positions.push_back(pos3d[pos]);
+ }
+ }
+ return m_deviceinfo;
+}
+
+uint32_t sound_sdl3::stream_sink_open(uint32_t node, std::string name, uint32_t rate)
+{
+ const int devnode = node - 1;
+ const SDL_AudioDeviceID device_id = m_devices[devnode].m_device_id;
+ SDL_AudioSpec dspec;
+
+ dspec.freq = m_devices[devnode].m_freq;
+ dspec.format = SDL_AUDIO_S16;
+ dspec.channels = m_devices[devnode].m_channels;
+
+ std::unique_ptr<stream_info> stream = std::make_unique<stream_info>(m_stream_next_id ++, dspec.channels);
+
+ stream->m_sdl_id = SDL_OpenAudioDevice(device_id, &dspec);
+ if(!stream->m_sdl_id) {
+ osd_printf_error("SDL Audio: Could not open audio device %s\n", SDL_GetError());
+ return 0;
+ }
+
+ stream->m_sdl_stream = SDL_CreateAudioStream(&dspec, nullptr);
+ if (!stream->m_sdl_stream) {
+ osd_printf_error("SDL Audio: Could not create audio stream %s\n", SDL_GetError());
+ SDL_CloseAudioDevice(stream->m_sdl_id);
+ return 0;
+ }
+
+ if (!SDL_BindAudioStream(stream->m_sdl_id, stream->m_sdl_stream)) {
+ osd_printf_error("SDL Audio: Could not bind audio stream %s\n", SDL_GetError());
+ SDL_DestroyAudioStream(stream->m_sdl_stream);
+ SDL_CloseAudioDevice(stream->m_sdl_id);
+ return 0;
+ }
+
+ uint32_t id = stream->m_id;
+ m_streams[stream->m_id] = std::move(stream);
+ osd_printf_verbose("SDL Audio: Opened sink stream id %d on device %d at rate %d\n", id, device_id, rate);
+ return id;
+}
+
+uint32_t sound_sdl3::stream_source_open(uint32_t node, std::string name, uint32_t rate)
+{
+ const int devnode = node - 1;
+ const SDL_AudioDeviceID device_id = m_devices[devnode].m_device_id;
+ SDL_AudioSpec dspec;
+
+ dspec.freq = m_devices[devnode].m_freq;
+ dspec.format = SDL_AUDIO_S16;
+ dspec.channels = m_devices[devnode].m_channels;
+
+ std::unique_ptr<stream_info> stream = std::make_unique<stream_info>(m_stream_next_id++, dspec.channels);
+
+ printf("opening source device %d at rate %d channels %d\n", device_id, dspec.freq, dspec.channels);
+
+ stream->m_sdl_id = SDL_OpenAudioDevice(device_id, &dspec);
+ if (!stream->m_sdl_id)
+ {
+ osd_printf_error("SDL Audio: Could not open audio device %s\n", SDL_GetError());
+ return 0;
+ }
+
+ stream->m_sdl_stream = SDL_CreateAudioStream(nullptr, &dspec);
+ if (!stream->m_sdl_stream)
+ {
+ osd_printf_error("SDL Audio: Could not create audio stream %s\n", SDL_GetError());
+ SDL_CloseAudioDevice(stream->m_sdl_id);
+ return 0;
+ }
+
+ if (!SDL_BindAudioStream(stream->m_sdl_id, stream->m_sdl_stream))
+ {
+ osd_printf_error("SDL Audio: Could not bind audio stream %s\n", SDL_GetError());
+ SDL_DestroyAudioStream(stream->m_sdl_stream);
+ SDL_CloseAudioDevice(stream->m_sdl_id);
+ return 0;
+ }
+
+ uint32_t id = stream->m_id;
+ m_streams[stream->m_id] = std::move(stream);
+ osd_printf_verbose("SDL Audio: Opened source stream id %d on device %d at rate %d\n", id, device_id, rate);
+ return id;
+}
+
+void sound_sdl3::stream_close(uint32_t id)
+{
+ osd_printf_verbose("SDL Audio: Closing stream id %d\n", id);
+
+ auto si = m_streams.find(id);
+ if(si == m_streams.end())
+ return;
+ SDL_UnbindAudioStream(si->second->m_sdl_stream);
+ SDL_DestroyAudioStream(si->second->m_sdl_stream);
+ SDL_CloseAudioDevice(si->second->m_sdl_id);
+ m_streams.erase(si);
+}
+
+void sound_sdl3::stream_sink_update(uint32_t id, const int16_t *buffer, int samples_this_frame)
+{
+ auto si = m_streams.find(id);
+ if(si == m_streams.end())
+ return;
+ stream_info *stream = si->second.get();
+ SDL_PutAudioStreamData(stream->m_sdl_stream, (void *)buffer, samples_this_frame * sizeof(int16_t) * stream->m_buffer.channels());
+}
+
+void sound_sdl3::stream_source_update(uint32_t id, int16_t *buffer, int samples_this_frame)
+{
+ auto si = m_streams.find(id);
+ if (si == m_streams.end())
+ return;
+ stream_info *stream = si->second.get();
+ SDL_GetAudioStreamData(stream->m_sdl_stream, (void *)buffer, samples_this_frame * sizeof(int16_t) * stream->m_buffer.channels());
+}
+
+} // anonymous namespace
+
+} // namespace osd
+
+#else // (defined(OSD_SDL) || defined(USE_SDL_SOUND)) && defined(SDLMAME_SDL3)
+
+namespace osd { namespace { MODULE_NOT_SUPPORTED(sound_sdl3, OSD_SOUND_PROVIDER, "sdl") } }
+
+#endif
+
+MODULE_DEFINITION(SOUND_SDL3, osd::sound_sdl3)
+
diff --git a/src/osd/modules/sound/sdl_sound.cpp b/src/osd/modules/sound/sdl_sound.cpp
index a43b9a4e6e4..38a21c0eaa9 100644
--- a/src/osd/modules/sound/sdl_sound.cpp
+++ b/src/osd/modules/sound/sdl_sound.cpp
@@ -12,7 +12,7 @@
#include "modules/osdmodule.h"
-#if (defined(OSD_SDL) || defined(USE_SDL_SOUND))
+#if (defined(OSD_SDL) || defined(USE_SDL_SOUND)) && !defined(SDLMAME_SDL3)
#include "modules/lib/osdobj_common.h"
#include "osdcore.h"
@@ -251,8 +251,7 @@ void sound_sdl::sink_callback(void *userdata, uint8_t *data, int len)
} // namespace osd
-
-#else // (defined(OSD_SDL) || defined(USE_SDL_SOUND))
+#else // (defined(OSD_SDL) || defined(USE_SDL_SOUND)) && !defined(SDLMAME_SDL3)
namespace osd { namespace { MODULE_NOT_SUPPORTED(sound_sdl, OSD_SOUND_PROVIDER, "sdl") } }
diff --git a/src/osd/sdl/taputil.sh b/src/osd/sdl/taputil.sh
index c230d7c19f8..2d167715fdf 100755
--- a/src/osd/sdl/taputil.sh
+++ b/src/osd/sdl/taputil.sh
@@ -40,4 +40,4 @@ chmod 666 /dev/net/tun
ip tuntap add dev $TAP mode tap user $NAME pi
ip link set $TAP up arp on
ip addr replace dev $TAP $HOSTIP/32
-ip route replace $EMUIP via $HOSTIP dev $TAP
+ip route replace $EMUIP via $HOSTIP dev $TAP
diff --git a/src/osd/sdl3/android_main.cpp b/src/osd/sdl3/android_main.cpp
new file mode 100644
index 00000000000..00879ecbe27
--- /dev/null
+++ b/src/osd/sdl3/android_main.cpp
@@ -0,0 +1,11 @@
+#ifdef __ANDROID__
+
+extern "C" int SDL_main(int argc, char *argv[]);
+
+// Using this in main library to prevent linker removing SDL_main
+int dummy_main(int argc, char** argv)
+{
+ return SDL_main(argc, argv);
+}
+
+#endif /* __ANDROID__ */
diff --git a/src/osd/sdl3/osdsdl.cpp b/src/osd/sdl3/osdsdl.cpp
new file mode 100644
index 00000000000..276085462c6
--- /dev/null
+++ b/src/osd/sdl3/osdsdl.cpp
@@ -0,0 +1,889 @@
+// license:BSD-3-Clause
+// copyright-holders:Olivier Galibert, R. Belmont
+
+#include "osdsdl.h"
+
+#include "modules/input/input_common.h"
+#include "modules/lib/osdlib.h"
+#include "window.h"
+
+#include "util/language.h"
+#include "util/unicode.h"
+
+// TODO: reduce dependence on concrete emu classes
+#include "emu.h"
+#include "main.h"
+#include "uiinput.h"
+
+#include "ui/uimain.h"
+
+#include <algorithm>
+#include <cmath>
+#include <cstdio>
+#include <cstring>
+
+
+namespace {
+
+//============================================================
+// defines_verbose
+//============================================================
+
+#define MAC_EXPAND_STR(_m) #_m
+#define MACRO_VERBOSE(_mac) \
+ do { \
+ if (strcmp(MAC_EXPAND_STR(_mac), #_mac) != 0) \
+ osd_printf_verbose("%s=%s ", #_mac, MAC_EXPAND_STR(_mac)); \
+ } while (0)
+
+void defines_verbose()
+{
+ osd_printf_verbose("Build version: %s\n", emulator_info::get_build_version());
+ osd_printf_verbose("Build architecure: ");
+ MACRO_VERBOSE(SDLMAME_ARCH);
+ osd_printf_verbose("\n");
+ osd_printf_verbose("Build defines 1: ");
+ MACRO_VERBOSE(SDLMAME_UNIX);
+ MACRO_VERBOSE(SDLMAME_X11);
+ MACRO_VERBOSE(SDLMAME_WIN32);
+ MACRO_VERBOSE(SDLMAME_MACOSX);
+ MACRO_VERBOSE(SDLMAME_DARWIN);
+ MACRO_VERBOSE(SDLMAME_LINUX);
+ MACRO_VERBOSE(SDLMAME_SOLARIS);
+ MACRO_VERBOSE(SDLMAME_IRIX);
+ MACRO_VERBOSE(SDLMAME_BSD);
+ osd_printf_verbose("\n");
+ osd_printf_verbose("Build defines 1: ");
+ MACRO_VERBOSE(LSB_FIRST);
+ MACRO_VERBOSE(MAME_NOASM);
+ MACRO_VERBOSE(MAME_DEBUG);
+ MACRO_VERBOSE(BIGENDIAN);
+ MACRO_VERBOSE(CPP_COMPILE);
+ MACRO_VERBOSE(SYNC_IMPLEMENTATION);
+ osd_printf_verbose("\n");
+ osd_printf_verbose("SDL/OpenGL defines: ");
+ osd_printf_verbose("SDL_VERSION=%d ", SDL_VERSION);
+ MACRO_VERBOSE(USE_OPENGL);
+ MACRO_VERBOSE(USE_DISPATCH_GL);
+ osd_printf_verbose("\n");
+ osd_printf_verbose("Compiler defines A: ");
+ MACRO_VERBOSE(__GNUC__);
+ MACRO_VERBOSE(__GNUC_MINOR__);
+ MACRO_VERBOSE(__GNUC_PATCHLEVEL__);
+ MACRO_VERBOSE(__VERSION__);
+ osd_printf_verbose("\n");
+ osd_printf_verbose("Compiler defines B: ");
+ MACRO_VERBOSE(__amd64__);
+ MACRO_VERBOSE(__x86_64__);
+ MACRO_VERBOSE(__unix__);
+ MACRO_VERBOSE(__i386__);
+ MACRO_VERBOSE(__ppc__);
+ MACRO_VERBOSE(__ppc64__);
+ osd_printf_verbose("\n");
+ osd_printf_verbose("Compiler defines C: ");
+ MACRO_VERBOSE(_FORTIFY_SOURCE);
+ MACRO_VERBOSE(__USE_FORTIFY_LEVEL);
+ osd_printf_verbose("\n");
+}
+
+
+//============================================================
+// osd_sdl_info
+//============================================================
+
+void osd_sdl_info()
+{
+ int num = SDL_GetNumVideoDrivers();
+
+ osd_printf_verbose("Available videodrivers: ");
+ for (int i = 0; i < num; i++)
+ {
+ const char *name = SDL_GetVideoDriver(i);
+ osd_printf_verbose("%s ", name);
+ }
+ osd_printf_verbose("\n");
+
+ osd_printf_verbose("Current Videodriver: %s\n", SDL_GetCurrentVideoDriver());
+ const auto displays = SDL_GetDisplays(&num);
+ osd_printf_verbose("%d displays found\n", num);
+ for (int i = 0; i < num; i++)
+ {
+ SDL_DisplayMode *mode;
+
+ osd_printf_verbose("\tDisplay #%d\n", i);
+ mode = (SDL_DisplayMode *)SDL_GetDesktopDisplayMode(displays[i]);
+ if (mode)
+ {
+ osd_printf_verbose("\t\tDesktop Mode: %dx%d-%d@%d\n", mode->w, mode->h, SDL_BITSPERPIXEL(mode->format), mode->refresh_rate);
+ }
+ else
+ {
+ osd_printf_verbose("Couldn't get desktop mode %s\n", SDL_GetError());
+ }
+
+ mode = (SDL_DisplayMode *)SDL_GetCurrentDisplayMode(displays[i]);
+ if (mode)
+ {
+ osd_printf_verbose("\t\tCurrent Display Mode: %dx%d-%d@%d\n", mode->w, mode->h, SDL_BITSPERPIXEL(mode->format), mode->refresh_rate);
+ }
+ else
+ {
+ osd_printf_verbose("Couldn't get display mode %s\n", SDL_GetError());
+ }
+
+ osd_printf_verbose("\t\tRenderdrivers:\n");
+ for (int j = 0; j < SDL_GetNumRenderDrivers(); j++)
+ {
+ osd_printf_verbose("\t\t\t%10s\n", SDL_GetRenderDriver(j));
+ }
+ }
+ SDL_free(displays);
+
+ osd_printf_verbose("Available audio drivers: \n");
+ num = SDL_GetNumAudioDrivers();
+ for (int i = 0; i < num; i++)
+ {
+ osd_printf_verbose("\t%-20s\n", SDL_GetAudioDriver(i));
+ }
+}
+
+
+sdl_window_info *window_from_id(Uint32 id)
+{
+ SDL_Window const *const sdl_window = SDL_GetWindowFromID(id);
+
+ auto const window = std::find_if(
+ osd_common_t::window_list().begin(),
+ osd_common_t::window_list().end(),
+ [sdl_window] (std::unique_ptr<osd_window> const &w)
+ {
+ return dynamic_cast<sdl_window_info &>(*w).platform_window() == sdl_window;
+ });
+
+ if (window == osd_common_t::window_list().end())
+ return nullptr;
+
+ return &static_cast<sdl_window_info &>(**window);
+}
+
+} // anonymous namespace
+
+
+
+//============================================================
+// SDL OSD interface
+//============================================================
+
+sdl_osd_interface::sdl_osd_interface(sdl_options &options) :
+ osd_common_t(options),
+ m_options(options),
+ m_focus_window(nullptr),
+ m_mouse_over_window(0),
+ m_modifier_keys(0),
+ m_last_click_time(std::chrono::steady_clock::time_point::min()),
+ m_last_click_x(0),
+ m_last_click_y(0),
+ m_enable_touch(false),
+ m_next_ptrdev(0)
+{
+}
+
+
+sdl_osd_interface::~sdl_osd_interface()
+{
+}
+
+
+void sdl_osd_interface::init(running_machine &machine)
+{
+ // call our parent
+ osd_common_t::init(machine);
+
+ const char *stemp;
+
+ // determine if we are benchmarking, and adjust options appropriately
+ int bench = options().bench();
+ if (bench > 0)
+ {
+ options().set_value(OPTION_SLEEP, false, OPTION_PRIORITY_MAXIMUM);
+ options().set_value(OPTION_THROTTLE, false, OPTION_PRIORITY_MAXIMUM);
+ options().set_value(OSDOPTION_SOUND, "none", OPTION_PRIORITY_MAXIMUM);
+ options().set_value(OSDOPTION_VIDEO, "none", OPTION_PRIORITY_MAXIMUM);
+ options().set_value(OPTION_SECONDS_TO_RUN, bench, OPTION_PRIORITY_MAXIMUM);
+ }
+
+ // Some driver options - must be before audio init!
+ stemp = options().audio_driver();
+ if (stemp != nullptr && strcmp(stemp, OSDOPTVAL_AUTO) != 0)
+ {
+ osd_printf_verbose("Setting SDL audiodriver '%s' ...\n", stemp);
+ osd_setenv(SDLENV_AUDIODRIVER, stemp, 1);
+ }
+
+ stemp = options().video_driver();
+ if (stemp != nullptr && strcmp(stemp, OSDOPTVAL_AUTO) != 0)
+ {
+ osd_printf_verbose("Setting SDL videodriver '%s' ...\n", stemp);
+ osd_setenv(SDLENV_VIDEODRIVER, stemp, 1);
+ }
+
+ stemp = options().render_driver();
+ if (stemp != nullptr)
+ {
+ if (strcmp(stemp, OSDOPTVAL_AUTO) != 0)
+ {
+ osd_printf_verbose("Setting SDL renderdriver '%s' ...\n", stemp);
+ //osd_setenv(SDLENV_RENDERDRIVER, stemp, 1);
+ SDL_SetHint(SDL_HINT_RENDER_DRIVER, stemp);
+ }
+ else
+ {
+#if defined(SDLMAME_WIN32)
+ // OpenGL renderer has less issues with mode switching on windows
+ osd_printf_verbose("Setting SDL renderdriver '%s' ...\n", "opengl");
+ //osd_setenv(SDLENV_RENDERDRIVER, stemp, 1);
+ SDL_SetHint(SDL_HINT_RENDER_DRIVER, "opengl");
+#endif
+ }
+ }
+
+ /* Set the SDL environment variable for drivers wanting to load the
+ * lib at startup.
+ */
+#if USE_OPENGL
+ /* FIXME: move lib loading code from drawogl.c here */
+
+ stemp = options().gl_lib();
+ if (stemp != nullptr && strcmp(stemp, OSDOPTVAL_AUTO) != 0)
+ {
+ osd_setenv("SDL_VIDEO_GL_DRIVER", stemp, 1);
+ osd_printf_verbose("Setting SDL_VIDEO_GL_DRIVER = '%s' ...\n", stemp);
+ }
+#endif
+
+ /* get number of processors */
+ stemp = options().numprocessors();
+
+ osd_num_processors = 0;
+
+ if (strcmp(stemp, "auto") != 0)
+ {
+ osd_num_processors = atoi(stemp);
+ if (osd_num_processors < 1)
+ {
+ osd_printf_warning("numprocessors < 1 doesn't make much sense. Assuming auto ...\n");
+ osd_num_processors = 0;
+ }
+ }
+
+ /* do we want touch support or will we use mouse emulation? */
+ m_enable_touch = options().enable_touch();
+ try
+ {
+ if (m_enable_touch)
+ {
+ int count = 0;
+ const SDL_TouchID *devices = SDL_GetTouchDevices(&count);
+ m_ptrdev_map.reserve(std::max<int>(count + 1, 8));
+ map_pointer_device(SDL_MOUSE_TOUCHID);
+ for (int i = 0; count > i; ++i)
+ {
+ map_pointer_device(devices[i]);
+ }
+ }
+ else
+ {
+ m_ptrdev_map.reserve(1);
+ map_pointer_device(SDL_MOUSE_TOUCHID);
+ }
+ }
+ catch (std::bad_alloc const &)
+ {
+ osd_printf_error("sdl_osd_interface: error allocating pointer data\n");
+ // survivable - it will still attempt to allocate mappings when it first sees devices
+ }
+
+#if defined(SDLMAME_ANDROID)
+ SDL_SetHint(SDL_HINT_VIDEO_EXTERNAL_CONTEXT, "1");
+#endif
+ /* Initialize SDL */
+
+ if (!SDL_InitSubSystem(SDL_INIT_VIDEO))
+ {
+ osd_printf_error("Could not initialize SDL %s\n", SDL_GetError());
+ exit(-1);
+ }
+
+ osd_sdl_info();
+
+ defines_verbose();
+
+ osd_common_t::init_subsystems();
+
+ if (options().oslog())
+ {
+ using namespace std::placeholders;
+ machine.add_logerror_callback(std::bind(&sdl_osd_interface::output_oslog, this, _1));
+ }
+
+
+
+#ifdef SDLMAME_EMSCRIPTEN
+ SDL_SetEventEnabled(SDL_EVENT_TEXT_INPUT, false);
+#else
+ SDL_SetEventEnabled(SDL_EVENT_TEXT_INPUT, true);
+#endif
+}
+
+
+void sdl_osd_interface::input_update(bool relative_reset)
+{
+ process_events_buf();
+ poll_input_modules(relative_reset);
+}
+
+
+void sdl_osd_interface::customize_input_type_list(std::vector<input_type_entry> &typelist)
+{
+ // loop over the defaults
+ for (input_type_entry &entry : typelist)
+ {
+ switch (entry.type())
+ {
+ // configurable UI mode switch
+ case IPT_UI_TOGGLE_UI:
+ {
+ char const *const uimode = options().ui_mode_key();
+ input_item_id mameid_code = ITEM_ID_INVALID;
+ if (!uimode || !*uimode || !strcmp(uimode, "auto"))
+ {
+#if defined(SDL_PLATFORM_APPLE) && defined(__MACH__)
+ mameid_code = keyboard_trans_table::instance().lookup_mame_code("ITEM_ID_INSERT");
+#endif
+ }
+ else
+ {
+ std::string fullmode("ITEM_ID_");
+ fullmode.append(uimode);
+ mameid_code = keyboard_trans_table::instance().lookup_mame_code(fullmode.c_str());
+ }
+ if (ITEM_ID_INVALID != mameid_code)
+ {
+ input_code const ui_code = input_code(DEVICE_CLASS_KEYBOARD, 0, ITEM_CLASS_SWITCH, ITEM_MODIFIER_NONE, input_item_id(mameid_code));
+ entry.defseq(SEQ_TYPE_STANDARD).set(ui_code);
+ }
+ }
+ break;
+
+ // alt-enter for fullscreen
+ case IPT_OSD_1:
+ entry.configure_osd("TOGGLE_FULLSCREEN", N_p("input-name", "Toggle Fullscreen"));
+ entry.defseq(SEQ_TYPE_STANDARD).set(KEYCODE_ENTER, KEYCODE_LALT);
+ break;
+
+ // page down for fastforward (must be OSD_3 as per src/emu/ui.c)
+ case IPT_UI_FAST_FORWARD:
+ entry.defseq(SEQ_TYPE_STANDARD).set(KEYCODE_PGDN);
+ break;
+
+ // OSD hotkeys use LALT/LCTRL and start at F3, they start
+ // at F3 because F1-F2 are hardcoded into many drivers to
+ // various dipswitches, and pressing them together with
+ // LALT/LCTRL will still press/toggle these dipswitches.
+
+ // LALT-F10 to toggle OpenGL filtering
+ case IPT_OSD_5:
+ entry.configure_osd("TOGGLE_FILTER", N_p("input-name", "Toggle Filter"));
+ entry.defseq(SEQ_TYPE_STANDARD).set(KEYCODE_F10, KEYCODE_LALT);
+ break;
+
+ // add a Not LALT condition to the throttle key
+ case IPT_UI_THROTTLE:
+ entry.defseq(SEQ_TYPE_STANDARD).set(KEYCODE_F10, input_seq::not_code, KEYCODE_LALT);
+ break;
+
+ // LALT-F8 to decrease OpenGL prescaling
+ case IPT_OSD_6:
+ entry.configure_osd("DECREASE_PRESCALE", N_p("input-name", "Decrease Prescaling"));
+ entry.defseq(SEQ_TYPE_STANDARD).set(KEYCODE_F8, KEYCODE_LALT);
+ break;
+
+ // add a Not LALT condition to the frameskip dec key
+ case IPT_UI_FRAMESKIP_DEC:
+ entry.defseq(SEQ_TYPE_STANDARD).set(KEYCODE_F8, input_seq::not_code, KEYCODE_LALT, input_seq::not_code, KEYCODE_LSHIFT, input_seq::not_code, KEYCODE_RSHIFT);
+ break;
+
+ // LALT-F9 to increase OpenGL prescaling
+ case IPT_OSD_7:
+ entry.configure_osd("INCREASE_PRESCALE", N_p("input-name", "Increase Prescaling"));
+ entry.defseq(SEQ_TYPE_STANDARD).set(KEYCODE_F9, KEYCODE_LALT);
+ break;
+
+ // add a Not LALT condition to the load state key
+ case IPT_UI_FRAMESKIP_INC:
+ entry.defseq(SEQ_TYPE_STANDARD).set(KEYCODE_F9, input_seq::not_code, KEYCODE_LALT);
+ break;
+
+ // LSHIFT-LALT-F12 for fullscreen video (BGFX)
+ case IPT_OSD_8:
+ entry.configure_osd("RENDER_AVI", N_p("input-name", "Record Rendered Video"));
+ entry.defseq(SEQ_TYPE_STANDARD).set(KEYCODE_F12, KEYCODE_LSHIFT, KEYCODE_LALT);
+ break;
+
+ // disable the config menu if the ALT key is down
+ // (allows ALT-TAB to switch between apps)
+ case IPT_UI_MENU:
+ entry.defseq(SEQ_TYPE_STANDARD).set(KEYCODE_TAB, input_seq::not_code, KEYCODE_LALT, input_seq::not_code, KEYCODE_RALT);
+ break;
+
+#if defined(SDL_PLATFORM_APPLE) && defined(__MACH__)
+ // 78-key Apple MacBook & Bluetooth keyboards have no right control key
+ case IPT_MAHJONG_SCORE:
+ if (entry.player() == 0)
+ entry.defseq(SEQ_TYPE_STANDARD).set(KEYCODE_SLASH);
+ break;
+#endif
+
+ // leave everything else alone
+ default:
+ break;
+ }
+ }
+}
+
+
+void sdl_osd_interface::release_keys()
+{
+ auto const keybd = dynamic_cast<input_module_base *>(m_keyboard_input);
+ if (keybd)
+ keybd->reset_devices();
+}
+
+
+bool sdl_osd_interface::should_hide_mouse()
+{
+ // if we are paused, no
+ if (machine().paused())
+ return false;
+
+ // if neither mice nor lightguns are enabled in the core, then no
+ if (!options().mouse() && !options().lightgun())
+ return false;
+
+ if (!mouse_over_window())
+ return false;
+
+ // otherwise, yes
+ return true;
+}
+
+
+void sdl_osd_interface::process_events_buf()
+{
+ SDL_PumpEvents();
+}
+
+
+void sdl_osd_interface::process_events()
+{
+ std::lock_guard<std::mutex> lock(subscription_mutex());
+ SDL_Event event;
+ while (SDL_PollEvent(&event))
+ {
+ // handle UI events
+ switch (event.type)
+ {
+ case SDL_EVENT_WINDOW_SHOWN:
+ case SDL_EVENT_WINDOW_HIDDEN:
+ case SDL_EVENT_WINDOW_EXPOSED:
+ case SDL_EVENT_WINDOW_MOVED:
+ case SDL_EVENT_WINDOW_RESIZED:
+ case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED:
+ case SDL_EVENT_WINDOW_METAL_VIEW_RESIZED:
+ case SDL_EVENT_WINDOW_MINIMIZED:
+ case SDL_EVENT_WINDOW_MAXIMIZED:
+ case SDL_EVENT_WINDOW_RESTORED:
+ case SDL_EVENT_WINDOW_MOUSE_ENTER:
+ case SDL_EVENT_WINDOW_MOUSE_LEAVE:
+ case SDL_EVENT_WINDOW_FOCUS_GAINED:
+ case SDL_EVENT_WINDOW_FOCUS_LOST:
+ case SDL_EVENT_WINDOW_CLOSE_REQUESTED:
+ case SDL_EVENT_WINDOW_HIT_TEST:
+ case SDL_EVENT_WINDOW_ICCPROF_CHANGED:
+ case SDL_EVENT_WINDOW_DISPLAY_CHANGED:
+ case SDL_EVENT_WINDOW_DISPLAY_SCALE_CHANGED:
+ case SDL_EVENT_WINDOW_SAFE_AREA_CHANGED:
+ case SDL_EVENT_WINDOW_OCCLUDED:
+ case SDL_EVENT_WINDOW_ENTER_FULLSCREEN:
+ case SDL_EVENT_WINDOW_LEAVE_FULLSCREEN:
+ case SDL_EVENT_WINDOW_DESTROYED:
+ case SDL_EVENT_WINDOW_HDR_STATE_CHANGED:
+ process_window_event(event);
+ break;
+
+ case SDL_EVENT_KEY_DOWN:
+ if (event.key.scancode == SDL_SCANCODE_LCTRL)
+ m_modifier_keys |= MODIFIER_KEY_LCTRL;
+ else if (event.key.scancode == SDL_SCANCODE_RCTRL)
+ m_modifier_keys |= MODIFIER_KEY_RCTRL;
+ else if (event.key.scancode == SDL_SCANCODE_LSHIFT)
+ m_modifier_keys |= MODIFIER_KEY_LSHIFT;
+ else if (event.key.scancode == SDL_SCANCODE_RSHIFT)
+ m_modifier_keys |= MODIFIER_KEY_RSHIFT;
+
+ if (event.key.key < 0x20)
+ {
+ // push control characters - they don't arrive as text input events
+ machine().ui_input().push_char_event(osd_common_t::window_list().front()->target(), event.key.key);
+ }
+ else if (m_modifier_keys & MODIFIER_KEY_CTRL)
+ {
+ // SDL filters out control characters for text input, so they are decoded here
+ if (event.key.key >= 0x40 && event.key.key < 0x7f)
+ {
+ machine().ui_input().push_char_event(osd_common_t::window_list().front()->target(), event.key.key & 0x1f);
+ }
+ else if (m_modifier_keys & MODIFIER_KEY_SHIFT)
+ {
+ if (event.key.key == SDLK_2) // Ctrl-@ (NUL)
+ machine().ui_input().push_char_event(osd_common_t::window_list().front()->target(), 0x00);
+ else if (event.key.key == SDLK_6) // Ctrl-^ (RS)
+ machine().ui_input().push_char_event(osd_common_t::window_list().front()->target(), 0x1e);
+ else if (event.key.key == SDLK_MINUS) // Ctrl-_ (US)
+ machine().ui_input().push_char_event(osd_common_t::window_list().front()->target(), 0x1f);
+ }
+ }
+ break;
+
+ case SDL_EVENT_KEY_UP:
+ if (event.key.scancode == SDL_SCANCODE_LCTRL)
+ m_modifier_keys &= ~MODIFIER_KEY_LCTRL;
+ else if (event.key.scancode == SDL_SCANCODE_RCTRL)
+ m_modifier_keys &= ~MODIFIER_KEY_RCTRL;
+ else if (event.key.scancode == SDL_SCANCODE_LSHIFT)
+ m_modifier_keys &= ~MODIFIER_KEY_LSHIFT;
+ else if (event.key.scancode == SDL_SCANCODE_RSHIFT)
+ m_modifier_keys &= ~MODIFIER_KEY_RSHIFT;
+ break;
+
+ case SDL_EVENT_TEXT_INPUT:
+ process_textinput_event(event);
+ break;
+
+ case SDL_EVENT_MOUSE_MOTION:
+ if (!m_enable_touch || (SDL_TOUCH_MOUSEID != event.motion.which))
+ {
+ auto const window = window_from_id(event.motion.windowID);
+ if (!window)
+ break;
+
+ unsigned device;
+ try
+ {
+ device = map_pointer_device(SDL_MOUSE_TOUCHID);
+ }
+ catch (std::bad_alloc const &)
+ {
+ osd_printf_error("sdl_osd_interface: error allocating pointer data\n");
+ break;
+ }
+
+ int x, y;
+ window->xy_to_render_target(event.motion.x, event.motion.y, &x, &y);
+ window->mouse_moved(device, x, y);
+ }
+ break;
+
+ case SDL_EVENT_MOUSE_BUTTON_DOWN:
+ case SDL_EVENT_MOUSE_BUTTON_UP:
+ if (!m_enable_touch || (SDL_TOUCH_MOUSEID != event.button.which))
+ {
+ auto const window = window_from_id(event.button.windowID);
+ if (!window)
+ break;
+
+ unsigned device;
+ try
+ {
+ device = map_pointer_device(SDL_MOUSE_TOUCHID);
+ }
+ catch (std::bad_alloc const &)
+ {
+ osd_printf_error("sdl_osd_interface: error allocating pointer data\n");
+ break;
+ }
+
+ int x, y;
+ window->xy_to_render_target(event.button.x, event.button.y, &x, &y);
+ unsigned button(event.button.button - 1);
+ if ((1 == button) || (2 == button))
+ button ^= 3;
+ if (event.button.down)
+ window->mouse_down(device, x, y, button);
+ else
+ window->mouse_up(device, x, y, button);
+ }
+ break;
+
+ case SDL_EVENT_MOUSE_WHEEL:
+ {
+ auto const window = window_from_id(event.wheel.windowID);
+ if (window)
+ {
+ unsigned device;
+ try
+ {
+ device = map_pointer_device(SDL_MOUSE_TOUCHID);
+ }
+ catch (std::bad_alloc const &)
+ {
+ osd_printf_error("sdl_osd_interface: error allocating pointer data\n");
+ break;
+ }
+ window->mouse_wheel(device, std::lround(event.wheel.integer_y * 120));
+ }
+ }
+ break;
+
+ case SDL_EVENT_FINGER_MOTION:
+ case SDL_EVENT_FINGER_DOWN:
+ case SDL_EVENT_FINGER_UP:
+ if (m_enable_touch && (SDL_MOUSE_TOUCHID != event.tfinger.touchID))
+ {
+ // ignore if it doesn't map to a window we own
+ auto const window = window_from_id(event.tfinger.windowID);
+ if (!window)
+ break;
+
+ // map SDL touch device ID to a zero-based device number
+ unsigned device;
+ try
+ {
+ device = map_pointer_device(event.tfinger.touchID);
+ }
+ catch (std::bad_alloc const &)
+ {
+ osd_printf_error("sdl_osd_interface: error allocating pointer data\n");
+ break;
+ }
+
+ // convert normalised coordinates to what MAME wants
+ auto const size = window->get_size();
+ int const winx = std::lround(event.tfinger.x * size.width());
+ int const winy = std::lround(event.tfinger.y * size.height());
+ int x, y;
+ window->xy_to_render_target(winx, winy, &x, &y);
+
+ // call appropriate window method
+ switch (event.type)
+ {
+ case SDL_EVENT_FINGER_MOTION:
+ window->finger_moved(event.tfinger.fingerID, device, x, y);
+ break;
+ case SDL_EVENT_FINGER_DOWN:
+ window->finger_down(event.tfinger.fingerID, device, x, y);
+ break;
+ case SDL_EVENT_FINGER_UP:
+ window->finger_up(event.tfinger.fingerID, device, x, y);
+ break;
+ }
+ }
+ break;
+ }
+
+ // let input modules do their thing
+ dispatch_event(event.type, event);
+ }
+}
+
+
+void sdl_osd_interface::osd_exit()
+{
+ osd_common_t::osd_exit();
+
+ SDL_QuitSubSystem(SDL_INIT_VIDEO);
+}
+
+
+void sdl_osd_interface::output_oslog(const char *buffer)
+{
+ fputs(buffer, stderr);
+}
+
+
+void sdl_osd_interface::process_window_event(SDL_Event const &event)
+{
+ auto const window = window_from_id(event.window.windowID);
+
+ if (!window)
+ {
+ // This condition may occur when the fullscreen toggle is used
+ osd_printf_verbose("Skipped window event due to missing window param from SDL\n");
+ return;
+ }
+
+ switch (event.window.type)
+ {
+ case SDL_EVENT_WINDOW_MOVED:
+ window->notify_changed();
+ m_focus_window = window;
+ break;
+
+ case SDL_EVENT_WINDOW_RESIZED:
+#ifdef SDLMAME_LINUX
+ /* FIXME: SDL2 sends some spurious resize events on Ubuntu
+ * while in fullscreen mode. Ignore them for now.
+ */
+ if (!window->fullscreen())
+#endif
+ {
+ //printf("event data1,data2 %d x %d %ld\n", event.window.data1, event.window.data2, sizeof(SDL_Event));
+ window->resize(event.window.data1, event.window.data2);
+ }
+ break;
+
+ case SDL_EVENT_WINDOW_MOUSE_ENTER:
+ {
+ m_mouse_over_window = 1;
+ unsigned device;
+ try
+ {
+ device = map_pointer_device(SDL_MOUSE_TOUCHID);
+ }
+ catch (std::bad_alloc const &)
+ {
+ osd_printf_error("sdl_osd_interface: error allocating pointer data\n");
+ break;
+ }
+ window->mouse_entered(device);
+ }
+ break;
+
+ case SDL_EVENT_WINDOW_MOUSE_LEAVE:
+ {
+ m_mouse_over_window = 0;
+ unsigned device;
+ try
+ {
+ device = map_pointer_device(SDL_MOUSE_TOUCHID);
+ }
+ catch (std::bad_alloc const &)
+ {
+ osd_printf_error("sdl_osd_interface: error allocating pointer data\n");
+ break;
+ }
+ window->mouse_left(device);
+ }
+ break;
+
+ case SDL_EVENT_WINDOW_FOCUS_GAINED:
+ m_focus_window = window;
+ machine().ui_input().push_window_focus_event(window->target());
+ break;
+
+ case SDL_EVENT_WINDOW_FOCUS_LOST:
+ if (window == m_focus_window)
+ m_focus_window = nullptr;
+ machine().ui_input().push_window_defocus_event(window->target());
+ break;
+
+ case SDL_EVENT_WINDOW_CLOSE_REQUESTED:
+ machine().schedule_exit();
+ break;
+
+ default:
+ break;
+ }
+}
+
+void sdl_osd_interface::process_textinput_event(SDL_Event const &event)
+{
+ if (*event.text.text)
+ {
+ auto const window = focus_window(event.text);
+ //printf("Focus window is %p - wl %p\n", window, osd_common_t::window_list().front().get());
+ if (window != nullptr)
+ {
+ auto ptr = event.text.text;
+ auto len = std::strlen(event.text.text);
+ while (len)
+ {
+ char32_t ch;
+ auto chlen = uchar_from_utf8(&ch, ptr, len);
+ if (0 > chlen)
+ {
+ ch = 0x0fffd;
+ chlen = 1;
+ }
+ ptr += chlen;
+ len -= chlen;
+ machine().ui_input().push_char_event(window->target(), ch);
+ }
+ }
+ }
+}
+
+
+void sdl_osd_interface::check_osd_inputs()
+{
+ // check for toggling fullscreen mode (don't do this in debug mode)
+ if (machine().ui_input().pressed(IPT_OSD_1) && !(machine().debug_flags & DEBUG_FLAG_OSD_ENABLED))
+ {
+ // destroy the renderers first so that the render module can bounce if it depends on having a window handle
+ for (auto it = osd_common_t::window_list().rbegin(); osd_common_t::window_list().rend() != it; ++it)
+ (*it)->renderer_reset();
+ for (auto const &curwin : osd_common_t::window_list())
+ dynamic_cast<sdl_window_info &>(*curwin).toggle_full_screen();
+ }
+
+ auto const &window = osd_common_t::window_list().front();
+
+ if (USE_OPENGL)
+ {
+ // FIXME: on a per window basis
+ if (machine().ui_input().pressed(IPT_OSD_5))
+ {
+ video_config.filter = !video_config.filter;
+ machine().ui().popup_time(1, "Filter %s", video_config.filter? "enabled" : "disabled");
+ }
+ }
+
+ if (machine().ui_input().pressed(IPT_OSD_6))
+ dynamic_cast<sdl_window_info &>(*window).modify_prescale(-1);
+
+ if (machine().ui_input().pressed(IPT_OSD_7))
+ dynamic_cast<sdl_window_info &>(*window).modify_prescale(1);
+
+ if (machine().ui_input().pressed(IPT_OSD_8))
+ window->renderer().record();
+}
+
+
+template <typename T>
+sdl_window_info *sdl_osd_interface::focus_window(T const &event) const
+{
+ // FIXME: SDL does not properly report the window for certain versions of Ubuntu - is this still relevant?
+ if (m_enable_touch)
+ return window_from_id(event.windowID);
+ else
+ return m_focus_window;
+}
+
+
+unsigned sdl_osd_interface::map_pointer_device(SDL_TouchID device)
+{
+ auto devpos(std::lower_bound(
+ m_ptrdev_map.begin(),
+ m_ptrdev_map.end(),
+ device,
+ [] (std::pair<SDL_TouchID, unsigned> const &mapping, SDL_TouchID id)
+ {
+ return mapping.first < id;
+ }));
+ if ((m_ptrdev_map.end() == devpos) || (device != devpos->first))
+ {
+ devpos = m_ptrdev_map.emplace(devpos, device, m_next_ptrdev);
+ ++m_next_ptrdev;
+ }
+ return devpos->second;
+}
diff --git a/src/osd/sdl3/osdsdl.h b/src/osd/sdl3/osdsdl.h
new file mode 100644
index 00000000000..81bca3166a0
--- /dev/null
+++ b/src/osd/sdl3/osdsdl.h
@@ -0,0 +1,216 @@
+// license:BSD-3-Clause
+// copyright-holders:Olivier Galibert, R. Belmont
+#ifndef MAME_OSD_SDL_OSDSDL_H
+#define MAME_OSD_SDL_OSDSDL_H
+
+#pragma once
+
+#include "sdlopts.h"
+
+#include "modules/lib/osdobj_common.h"
+#include "modules/osdmodule.h"
+
+#include <SDL3/SDL.h>
+
+#include <cassert>
+#include <chrono>
+#include <memory>
+#include <mutex>
+#include <unordered_map>
+#include <utility>
+#include <string>
+#include <vector>
+
+
+//============================================================
+// Defines
+//============================================================
+
+#define SDLMAME_LED(x) "led" #x
+
+// read by sdlmame
+
+#define SDLENV_DESKTOPDIM "SDLMAME_DESKTOPDIM"
+#define SDLENV_VMWARE "SDLMAME_VMWARE"
+
+// set by sdlmame
+
+#define SDLENV_VISUALID "SDL_VIDEO_X11_VISUALID"
+#define SDLENV_VIDEODRIVER "SDL_VIDEODRIVER"
+#define SDLENV_AUDIODRIVER "SDL_AUDIODRIVER"
+#define SDLENV_RENDERDRIVER "SDL_VIDEO_RENDERER"
+
+
+//============================================================
+// TYPE DEFINITIONS
+//============================================================
+
+template <typename EventRecord, typename EventType>
+class event_subscription_manager
+{
+public: // need extra public section for forward declaration
+ class subscriber;
+
+private:
+ class impl
+ {
+ public:
+ std::mutex m_mutex;
+ std::unordered_multimap<EventType, subscriber *> m_subs;
+ };
+
+ std::shared_ptr<impl> m_impl;
+
+protected:
+ event_subscription_manager() : m_impl(new impl)
+ {
+ }
+
+ ~event_subscription_manager() = default;
+
+ std::mutex &subscription_mutex()
+ {
+ return m_impl->m_mutex;
+ }
+
+ void dispatch_event(EventType const &type, EventRecord const &event)
+ {
+ auto const matches = m_impl->m_subs.equal_range(type);
+ for (auto it = matches.first; matches.second != it; ++it)
+ it->second->handle_event(event);
+ }
+
+public:
+ class subscriber
+ {
+ public:
+ virtual void handle_event(EventRecord const &event) = 0;
+
+ protected:
+ subscriber() = default;
+
+ virtual ~subscriber()
+ {
+ unsubscribe();
+ }
+
+ template <typename T>
+ void subscribe(event_subscription_manager &host, T &&types)
+ {
+ assert(!m_host.lock());
+ assert(host.m_impl);
+
+ m_host = host.m_impl;
+
+ std::lock_guard<std::mutex> lock(host.m_impl->m_mutex);
+ for (auto const &t : types)
+ host.m_impl->m_subs.emplace(t, this);
+ }
+
+ void unsubscribe()
+ {
+ auto const host(m_host.lock());
+ m_host.reset();
+ if (host)
+ {
+ std::lock_guard<std::mutex> lock(host->m_mutex);
+ auto it = host->m_subs.begin();
+ while (host->m_subs.end() != it)
+ {
+ if (it->second == this)
+ it = host->m_subs.erase(it);
+ else
+ ++it;
+ }
+ }
+ }
+
+ private:
+ std::weak_ptr<impl> m_host;
+ };
+};
+
+
+using sdl_event_manager = event_subscription_manager<SDL_Event, uint32_t>;
+
+
+class sdl_window_info;
+
+class sdl_osd_interface : public osd_common_t, public sdl_event_manager
+{
+public:
+ // construction/destruction
+ sdl_osd_interface(sdl_options &options);
+ virtual ~sdl_osd_interface();
+
+ // general overridables
+ virtual void init(running_machine &machine) override;
+ virtual void update(bool skip_redraw) override;
+ virtual void input_update(bool relative_reset) override;
+ virtual void check_osd_inputs() override;
+
+ // input overridables
+ virtual void customize_input_type_list(std::vector<input_type_entry> &typelist) override;
+
+ virtual bool video_init() override;
+ virtual bool window_init() override;
+
+ virtual void video_exit() override;
+ virtual void window_exit() override;
+
+ // SDL-specific
+ virtual bool has_focus() const override { return bool(m_focus_window); }
+ void release_keys();
+ bool should_hide_mouse();
+ void process_events_buf();
+
+ virtual sdl_options &options() override { return m_options; }
+
+ virtual void process_events() override;
+
+private:
+ enum
+ {
+ MODIFIER_KEY_LCTRL = 0x01,
+ MODIFIER_KEY_RCTRL = 0x02,
+ MODIFIER_KEY_LSHIFT = 0x04,
+ MODIFIER_KEY_RSHIFT = 0x08,
+
+ MODIFIER_KEY_CTRL = MODIFIER_KEY_LCTRL | MODIFIER_KEY_RCTRL,
+ MODIFIER_KEY_SHIFT = MODIFIER_KEY_LSHIFT | MODIFIER_KEY_RSHIFT
+ };
+
+ virtual void osd_exit() override;
+
+ void extract_video_config();
+ void output_oslog(const char *buffer);
+
+ void process_window_event(SDL_Event const &event);
+ void process_textinput_event(SDL_Event const &event);
+
+ bool mouse_over_window() const { return m_mouse_over_window > 0; }
+ template <typename T> sdl_window_info *focus_window(T const &event) const;
+
+ unsigned map_pointer_device(SDL_TouchID device);
+
+ sdl_options &m_options;
+ sdl_window_info *m_focus_window;
+ int m_mouse_over_window;
+ uint8_t m_modifier_keys;
+
+ std::chrono::steady_clock::time_point m_last_click_time;
+ int m_last_click_x;
+ int m_last_click_y;
+
+ bool m_enable_touch;
+ unsigned m_next_ptrdev;
+ std::vector<std::pair<SDL_TouchID, unsigned> > m_ptrdev_map;
+};
+
+//============================================================
+// sdlwork.c
+//============================================================
+
+extern int osd_num_processors;
+
+#endif // MAME_OSD_SDL_OSDSDL_H
diff --git a/src/osd/sdl3/sdlmain.cpp b/src/osd/sdl3/sdlmain.cpp
new file mode 100644
index 00000000000..99f319a946a
--- /dev/null
+++ b/src/osd/sdl3/sdlmain.cpp
@@ -0,0 +1,113 @@
+// license:BSD-3-Clause
+// copyright-holders:Olivier Galibert, R. Belmont
+//============================================================
+//
+// sdlmain.cpp - main file for SDLMAME.
+//
+// SDLMAME by Olivier Galibert and R. Belmont
+//
+//============================================================
+
+// OSD headers
+#include "osdsdl.h"
+#include "modules/lib/osdlib.h"
+#include "modules/diagnostics/diagnostics_module.h"
+
+// MAME headers
+#include "emu.h"
+#include "emuopts.h"
+#include "main.h"
+#include "video.h"
+
+#include "corestr.h"
+
+#include "osdepend.h"
+#include "strconv.h"
+
+#include <SDL3/SDL.h>
+
+// only for oslog callback
+#include <functional>
+
+#ifdef SDLMAME_UNIX
+#if (!defined(SDLMAME_MACOSX)) && (!defined(SDLMAME_EMSCRIPTEN)) && (!defined(SDLMAME_ANDROID))
+#ifndef SDLMAME_HAIKU
+#include <fontconfig/fontconfig.h>
+#endif
+#endif
+#ifdef SDLMAME_MACOSX
+#define __ASSERT_MACROS_DEFINE_VERSIONS_WITHOUT_UNDERSCORES 0
+#include <Carbon/Carbon.h>
+#endif
+#endif
+
+// standard includes
+#if !defined(SDLMAME_WIN32)
+#include <unistd.h>
+#endif
+
+
+//============================================================
+// Global variables
+//============================================================
+
+#if defined(SDLMAME_UNIX) || defined(SDLMAME_WIN32)
+int sdl_entered_debugger;
+#endif
+
+
+//============================================================
+// main
+//============================================================
+
+// we do some special sauce on Win32...
+
+#if defined(SDLMAME_WIN32)
+/* gee */
+//extern "C" DECLSPEC void SDLCALL SDL_SetModuleHandle(void *hInst);
+#endif
+
+int main(int argc, char** argv)
+{
+ std::vector<std::string> args = osd_get_command_line(argc, argv);
+ int res = 0;
+
+ // disable I/O buffering
+ setvbuf(stdout, (char *) nullptr, _IONBF, 0);
+ setvbuf(stderr, (char *) nullptr, _IONBF, 0);
+
+ // Initialize crash diagnostics
+ diagnostics_module::get_instance()->init_crash_diagnostics();
+
+#if defined(SDLMAME_ANDROID)
+ /* Enable standard application logging */
+ SDL_SetLogPriority(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_VERBOSE);
+#endif
+
+ // FIXME: this should be done differently
+
+#ifdef SDLMAME_UNIX
+ sdl_entered_debugger = 0;
+#if (!defined(SDLMAME_MACOSX)) && (!defined(SDLMAME_HAIKU)) && (!defined(SDLMAME_EMSCRIPTEN)) && (!defined(SDLMAME_ANDROID))
+ FcInit();
+#endif
+#endif
+
+ {
+ sdl_options options;
+ sdl_osd_interface osd(options);
+ osd.register_options();
+ res = emulator_info::start_frontend(options, osd, args);
+ }
+
+#ifdef SDLMAME_UNIX
+#if (!defined(SDLMAME_MACOSX)) && (!defined(SDLMAME_HAIKU)) && (!defined(SDLMAME_EMSCRIPTEN)) && (!defined(SDLMAME_ANDROID))
+ if (!sdl_entered_debugger)
+ {
+ FcFini();
+ }
+#endif
+#endif
+
+ exit(res);
+}
diff --git a/src/osd/sdl3/sdlopts.cpp b/src/osd/sdl3/sdlopts.cpp
new file mode 100644
index 00000000000..d1862657d37
--- /dev/null
+++ b/src/osd/sdl3/sdlopts.cpp
@@ -0,0 +1,127 @@
+// license:BSD-3-Clause
+// copyright-holders:Olivier Galibert, R. Belmont
+
+#include "sdlopts.h"
+
+// emu
+#include "main.h"
+
+// lib/util
+#include "util/corestr.h"
+
+#include <SDL3/SDL.h>
+
+#include <string>
+
+#if defined(SDLMAME_ANDROID)
+#include "unistd.h"
+#endif
+
+
+namespace {
+
+//============================================================
+// OPTIONS
+//============================================================
+
+#ifndef INI_PATH
+#if defined(SDLMAME_WIN32)
+ #define INI_PATH ".;ini;ini/presets"
+#elif defined(SDLMAME_MACOSX)
+ #define INI_PATH "$HOME/Library/Application Support/APP_NAME;$HOME/.APP_NAME;.;ini"
+#else
+ #define INI_PATH "$HOME/.APP_NAME;.;ini"
+#endif // MACOSX
+#endif // INI_PATH
+
+
+//============================================================
+// Local variables
+//============================================================
+
+const options_entry f_sdl_option_entries[] =
+{
+ { SDLOPTION_INIPATH, INI_PATH, core_options::option_type::MULTIPATH, "path to ini files" },
+
+ // performance options
+ { nullptr, nullptr, core_options::option_type::HEADER, "SDL PERFORMANCE OPTIONS" },
+ { SDLOPTION_SDLVIDEOFPS, "0", core_options::option_type::BOOLEAN, "show sdl video performance" },
+ // video options
+ { nullptr, nullptr, core_options::option_type::HEADER, "SDL VIDEO OPTIONS" },
+// OS X can be trusted to have working hardware OpenGL, so default to it on for the best user experience
+ { SDLOPTION_CENTERH, "1", core_options::option_type::BOOLEAN, "center horizontally within the view area" },
+ { SDLOPTION_CENTERV, "1", core_options::option_type::BOOLEAN, "center vertically within the view area" },
+ { SDLOPTION_SCALEMODE ";sm", OSDOPTVAL_NONE, core_options::option_type::STRING, "Scale mode: none, hwblit, hwbest, yv12, yuy2, yv12x2, yuy2x2 (-video soft only)" },
+
+ // full screen options
+#ifdef SDLMAME_X11
+ { nullptr, nullptr, core_options::option_type::HEADER, "SDL FULL SCREEN OPTIONS" },
+ { SDLOPTION_USEALLHEADS, "0", core_options::option_type::BOOLEAN, "split full screen image across monitors" },
+ { SDLOPTION_ATTACH_WINDOW, "", core_options::option_type::STRING, "attach to arbitrary window" },
+#endif // SDLMAME_X11
+
+ // keyboard mapping
+ { nullptr, nullptr, core_options::option_type::HEADER, "SDL KEYBOARD MAPPING" },
+ { SDLOPTION_KEYMAP, "0", core_options::option_type::BOOLEAN, "enable keymap" },
+ { SDLOPTION_KEYMAP_FILE, "keymap.dat", core_options::option_type::PATH, "keymap filename" },
+
+ // joystick mapping
+ { nullptr, nullptr, core_options::option_type::HEADER, "SDL INPUT OPTIONS" },
+ { SDLOPTION_ENABLE_TOUCH, "0", core_options::option_type::BOOLEAN, "enable touch input support" },
+ { SDLOPTION_SIXAXIS, "0", core_options::option_type::BOOLEAN, "use special handling for PS3 Sixaxis controllers" },
+ { SDLOPTION_DUAL_LIGHTGUN ";dual", "0", core_options::option_type::BOOLEAN, "enable dual lightgun input" },
+
+#if (USE_XINPUT)
+ // lightgun mapping
+ { nullptr, nullptr, core_options::option_type::HEADER, "SDL LIGHTGUN MAPPING" },
+ { SDLOPTION_LIGHTGUNINDEX "1", OSDOPTVAL_AUTO, core_options::option_type::STRING, "name of lightgun mapped to lightgun #1" },
+ { SDLOPTION_LIGHTGUNINDEX "2", OSDOPTVAL_AUTO, core_options::option_type::STRING, "name of lightgun mapped to lightgun #2" },
+ { SDLOPTION_LIGHTGUNINDEX "3", OSDOPTVAL_AUTO, core_options::option_type::STRING, "name of lightgun mapped to lightgun #3" },
+ { SDLOPTION_LIGHTGUNINDEX "4", OSDOPTVAL_AUTO, core_options::option_type::STRING, "name of lightgun mapped to lightgun #4" },
+ { SDLOPTION_LIGHTGUNINDEX "5", OSDOPTVAL_AUTO, core_options::option_type::STRING, "name of lightgun mapped to lightgun #5" },
+ { SDLOPTION_LIGHTGUNINDEX "6", OSDOPTVAL_AUTO, core_options::option_type::STRING, "name of lightgun mapped to lightgun #6" },
+ { SDLOPTION_LIGHTGUNINDEX "7", OSDOPTVAL_AUTO, core_options::option_type::STRING, "name of lightgun mapped to lightgun #7" },
+ { SDLOPTION_LIGHTGUNINDEX "8", OSDOPTVAL_AUTO, core_options::option_type::STRING, "name of lightgun mapped to lightgun #8" },
+#endif
+
+ // SDL low level driver options
+ { nullptr, nullptr, core_options::option_type::HEADER, "SDL LOW-LEVEL DRIVER OPTIONS" },
+ { SDLOPTION_VIDEODRIVER ";vd", OSDOPTVAL_AUTO, core_options::option_type::STRING, "SDL video driver to use ('x11', 'directfb', ... or 'auto' for SDL default" },
+ { SDLOPTION_RENDERDRIVER ";rd", OSDOPTVAL_AUTO, core_options::option_type::STRING, "SDL render driver to use ('software', 'opengl', 'directfb' ... or 'auto' for SDL default" },
+ { SDLOPTION_AUDIODRIVER ";ad", OSDOPTVAL_AUTO, core_options::option_type::STRING, "SDL audio driver to use ('alsa', 'arts', ... or 'auto' for SDL default" },
+#if USE_OPENGL
+ { SDLOPTION_GL_LIB, SDLOPTVAL_GLLIB, core_options::option_type::STRING, "alternative libGL.so to use; 'auto' for system default" },
+#endif
+
+ // End of list
+ { nullptr }
+};
+
+} // anonymous namespace
+
+
+//============================================================
+// sdl_options
+//============================================================
+
+sdl_options::sdl_options() : osd_options()
+{
+#if defined(SDLMAME_ANDROID)
+ chdir(SDL_GetAndroidExternalStoragePath()); // FIXME: why is this here of all places?
+#endif
+ std::string ini_path(INI_PATH);
+ add_entries(f_sdl_option_entries);
+ strreplace(ini_path, "APP_NAME", emulator_info::get_appname_lower());
+ set_default_value(SDLOPTION_INIPATH, std::move(ini_path));
+}
+
+
+//============================================================
+// osd_setup_osd_specific_emu_options
+//============================================================
+
+void osd_setup_osd_specific_emu_options(emu_options &opts)
+{
+ opts.add_entries(osd_options::s_option_entries);
+ opts.add_entries(f_sdl_option_entries);
+}
diff --git a/src/osd/sdl3/sdlopts.h b/src/osd/sdl3/sdlopts.h
new file mode 100644
index 00000000000..f6338fe1db6
--- /dev/null
+++ b/src/osd/sdl3/sdlopts.h
@@ -0,0 +1,100 @@
+// license:BSD-3-Clause
+// copyright-holders:Olivier Galibert, R. Belmont
+#ifndef MAME_OSD_SDL_SDLOPTS_H
+#define MAME_OSD_SDL_SDLOPTS_H
+
+#pragma once
+
+#include "modules/lib/osdobj_common.h"
+
+
+//============================================================
+// Option identifiers
+//============================================================
+
+#define SDLOPTION_INIPATH "inipath"
+#define SDLOPTION_SDLVIDEOFPS "sdlvideofps"
+#define SDLOPTION_USEALLHEADS "useallheads"
+#define SDLOPTION_ATTACH_WINDOW "attach_window"
+#define SDLOPTION_CENTERH "centerh"
+#define SDLOPTION_CENTERV "centerv"
+
+#define SDLOPTION_SCALEMODE "scalemode"
+
+#define SDLOPTION_WAITVSYNC "waitvsync"
+#define SDLOPTION_SYNCREFRESH "syncrefresh"
+#define SDLOPTION_KEYMAP "keymap"
+#define SDLOPTION_KEYMAP_FILE "keymap_file"
+
+#define SDLOPTION_ENABLE_TOUCH "enable_touch"
+#define SDLOPTION_SIXAXIS "sixaxis"
+#define SDLOPTION_DUAL_LIGHTGUN "dual_lightgun"
+#if defined(USE_XINPUT) && USE_XINPUT
+#define SDLOPTION_LIGHTGUNINDEX "lightgun_index"
+#endif
+
+#define SDLOPTION_AUDIODRIVER "audiodriver"
+#define SDLOPTION_VIDEODRIVER "videodriver"
+#define SDLOPTION_RENDERDRIVER "renderdriver"
+#define SDLOPTION_GL_LIB "gl_lib"
+
+
+//============================================================
+// Option values
+//============================================================
+
+#define SDLOPTVAL_OPENGL "opengl"
+#define SDLOPTVAL_SOFT "soft"
+#define SDLOPTVAL_SDL2ACCEL "accel"
+#define SDLOPTVAL_BGFX "bgfx"
+
+#ifdef SDLMAME_MACOSX
+/* Vas Crabb: Default GL-lib for MACOSX */
+#define SDLOPTVAL_GLLIB "/System/Library/Frameworks/OpenGL.framework/Libraries/libGL.dylib"
+#else
+#define SDLOPTVAL_GLLIB OSDOPTVAL_AUTO
+#endif
+
+
+//============================================================
+// TYPE DEFINITIONS
+//============================================================
+
+class sdl_options : public osd_options
+{
+public:
+ // construction/destruction
+ sdl_options();
+
+ // performance options
+ bool video_fps() const { return bool_value(SDLOPTION_SDLVIDEOFPS); }
+
+ // video options
+ bool centerh() const { return bool_value(SDLOPTION_CENTERH); }
+ bool centerv() const { return bool_value(SDLOPTION_CENTERV); }
+ const char *scale_mode() const { return value(SDLOPTION_SCALEMODE); }
+
+ // full screen options
+#if defined(SDLMAME_X11)
+ bool use_all_heads() const { return bool_value(SDLOPTION_USEALLHEADS); }
+ const char *attach_window() const { return value(SDLOPTION_ATTACH_WINDOW); }
+#endif // SDLMAME_X11
+
+ // keyboard mapping
+ bool keymap() const { return bool_value(SDLOPTION_KEYMAP); }
+ const char *keymap_file() const { return value(SDLOPTION_KEYMAP_FILE); }
+
+ // input options
+ bool enable_touch() const { return bool_value(SDLOPTION_ENABLE_TOUCH); }
+ bool sixaxis() const { return bool_value(SDLOPTION_SIXAXIS); }
+ bool dual_lightgun() const { return bool_value(SDLOPTION_DUAL_LIGHTGUN); }
+
+ const char *video_driver() const { return value(SDLOPTION_VIDEODRIVER); }
+ const char *render_driver() const { return value(SDLOPTION_RENDERDRIVER); }
+ const char *audio_driver() const { return value(SDLOPTION_AUDIODRIVER); }
+#if USE_OPENGL
+ const char *gl_lib() const { return value(SDLOPTION_GL_LIB); }
+#endif
+};
+
+#endif // MAME_OSD_SDL_SDLOPTS_H
diff --git a/src/osd/sdl3/sdlprefix.h b/src/osd/sdl3/sdlprefix.h
new file mode 100644
index 00000000000..2d8596edaa4
--- /dev/null
+++ b/src/osd/sdl3/sdlprefix.h
@@ -0,0 +1,86 @@
+// license:BSD-3-Clause
+// copyright-holders:Olivier Galibert, R. Belmont
+//============================================================
+//
+// sdlprefix.h - prefix file, included by ALL files
+//
+// SDLMAME by Olivier Galibert and R. Belmont
+//
+//============================================================
+
+//============================================================
+// System specific defines
+//============================================================
+
+/* Only problems ... */
+#if defined(_WIN32)
+#define SDLMAME_WIN32 1
+#endif
+
+
+#ifdef SDL_PLATFORM_APPLE
+#define SDLMAME_DARWIN 1
+#endif /* SDL_PLATFORM_APPLE */
+
+#ifdef SDLMAME_UNIX
+
+#if defined(__sun__) && defined(__svr4__)
+#define SDLMAME_SOLARIS 1
+#define NO_AFFINITY_NP 1
+//#undef _XOPEN_SOURCE
+//#undef _XOPEN_VERSION
+//#undef _XOPEN_SOURCE_EXTENDED
+//#undef _XPG6
+//#undef _XPG5
+//#undef _XPG4_2
+//#define _XOPEN_SOURCE
+//#define _XOPEN_VERSION 4
+#elif defined(__irix__) || defined(__sgi)
+#define SDLMAME_IRIX 1
+/* Large file support on IRIX needs _SGI_SOURCE */
+#undef _POSIX_SOURCE
+
+#elif defined(__linux__) || defined(__FreeBSD_kernel__)
+#define SDLMAME_LINUX 1
+
+#elif defined(__FreeBSD__)
+#define SDLMAME_FREEBSD 1
+#define NO_AFFINITY_NP 1
+#elif defined(__DragonFly__)
+#define SDLMAME_DRAGONFLY 1
+#elif defined(__OpenBSD__)
+#define SDLMAME_OPENBSD 1
+#elif defined(__NetBSD__)
+#define SDLMAME_NETBSD 1
+#endif
+
+#if defined(__DragonFly__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)
+#define SDLMAME_BSD 1
+#endif
+
+#if defined(SDL_PLATFORM_HAIKU)
+#define SDLMAME_HAIKU 1
+#define SDLMAME_NO64BITIO 1
+#endif
+
+#if defined(__EMSCRIPTEN__)
+#define SDLMAME_EMSCRIPTEN 1
+#define SDLMAME_NO64BITIO 1
+struct _IO_FILE {}; //_IO_FILE is an opaque type in the emscripten libc which makes clang cranky
+#endif
+
+#if defined(__ANDROID__)
+#define SDLMAME_ANDROID 1
+#endif
+
+// fix for Ubuntu 8.10
+#ifdef _FORTIFY_SOURCE
+#undef _FORTIFY_SOURCE
+#endif
+
+// nasty hack to stop altivec #define vector/bool/pixel screwing us over
+#if defined(__ALTIVEC__) && !defined(__APPLE_ALTIVEC__)
+#define __APPLE_ALTIVEC__ 1
+#endif
+
+#endif /* SDLMAME_UNIX */
diff --git a/src/osd/sdl3/taputil.sh b/src/osd/sdl3/taputil.sh
new file mode 100755
index 00000000000..2d167715fdf
--- /dev/null
+++ b/src/osd/sdl3/taputil.sh
@@ -0,0 +1,43 @@
+#!/bin/sh
+# license:BSD-3-Clause
+# copyright-holders:Carl
+NAME=$2
+OURUID=`id -u $NAME`
+HOSTIP=$4
+EMUIP=$3
+TAP="tap-mess-$OURUID-0"
+
+if [ `id -u` != "0" ]
+then
+echo "must be run as root"
+exit
+fi
+
+if [ "$1" = "-d" ]
+then
+echo 0 > /proc/sys/net/ipv4/ip_forward
+echo 0 > /proc/sys/net/ipv4/conf/all/proxy_arp
+chmod 660 /dev/net/tun
+ip tuntap del dev $TAP mode tap
+exit
+fi
+
+if [ "$#" != "4" ]
+then
+echo "usage: mess-tap [-c] [-d] USER EMUADDR HOSTADDR"
+echo "-c create interface"
+echo "-d delete interface"
+echo "USER user to own interface, required to delete"
+echo "EMUADDR emulated machine ip address"
+echo "HOSTADDR host ip address"
+exit
+fi
+
+echo 1 > /proc/sys/net/ipv4/ip_forward
+echo 1 > /proc/sys/net/ipv4/conf/all/proxy_arp
+chmod 666 /dev/net/tun
+
+ip tuntap add dev $TAP mode tap user $NAME pi
+ip link set $TAP up arp on
+ip addr replace dev $TAP $HOSTIP/32
+ip route replace $EMUIP via $HOSTIP dev $TAP
diff --git a/src/osd/sdl3/video.cpp b/src/osd/sdl3/video.cpp
new file mode 100644
index 00000000000..395fc931a9b
--- /dev/null
+++ b/src/osd/sdl3/video.cpp
@@ -0,0 +1,182 @@
+// license:BSD-3-Clause
+// copyright-holders:Olivier Galibert, R. Belmont
+//============================================================
+//
+// video.cpp - SDL video handling
+//
+// SDLMAME by Olivier Galibert and R. Belmont
+//
+//============================================================
+
+#include "window.h"
+
+// MAMEOS headers
+#include "osdsdl.h"
+#include "modules/lib/osdlib.h"
+#include "modules/monitor/monitor_module.h"
+#include "modules/render/render_module.h"
+
+// MAME headers
+#include "emu.h"
+#include "emuopts.h"
+#include "main.h"
+#include "rendutil.h"
+#include "uiinput.h"
+
+#include <SDL3/SDL.h>
+
+
+//============================================================
+// GLOBAL VARIABLES
+//============================================================
+
+osd_video_config video_config;
+
+
+//============================================================
+// PROTOTYPES
+//============================================================
+
+static void get_resolution(const char *defdata, const char *data, osd_window_config *config, int report_error);
+
+
+//============================================================
+// video_init
+//============================================================
+
+bool sdl_osd_interface::video_init()
+{
+ int index;
+
+ // extract data from the options
+ extract_video_config();
+
+ // we need the beam width in a float, contrary to what the core does.
+ video_config.beamwidth = options().beam_width_min();
+
+ // initialize the window system so we can make windows
+ if (!window_init())
+ return false;
+
+ // create the windows
+ for (index = 0; index < video_config.numscreens; index++)
+ {
+ osd_window_config conf;
+ get_resolution(options().resolution(), options().resolution(index), &conf, true);
+
+ // create window ...
+ auto win = std::make_unique<sdl_window_info>(machine(), *m_render, index, m_monitor_module->pick_monitor(reinterpret_cast<osd_options &>(options()), index), &conf);
+ if (win->window_init())
+ return false;
+
+ s_window_list.emplace_back(std::move(win));
+ }
+
+ if (m_render->is_interactive())
+ SDL_RaiseWindow(dynamic_cast<sdl_window_info &>(*osd_common_t::s_window_list.front()).platform_window());
+
+ return true;
+}
+
+//============================================================
+// video_exit
+//============================================================
+
+void sdl_osd_interface::video_exit()
+{
+ window_exit();
+}
+
+//============================================================
+// update
+//============================================================
+
+void sdl_osd_interface::update(bool skip_redraw)
+{
+ osd_common_t::update(skip_redraw);
+
+ // if we're not skipping this redraw, update all windows
+ if (!skip_redraw)
+ {
+// profiler_mark(PROFILER_BLIT);
+ for (auto const &window : osd_common_t::window_list())
+ window->update();
+// profiler_mark(PROFILER_END);
+ }
+
+ // if we're running, disable some parts of the debugger
+ if ((machine().debug_flags & DEBUG_FLAG_OSD_ENABLED) != 0)
+ debugger_update();
+}
+
+//============================================================
+// extract_video_config
+//============================================================
+
+void sdl_osd_interface::extract_video_config()
+{
+ video_config.perftest = options().video_fps();
+
+ // global options: extract the data
+ video_config.windowed = options().window();
+ video_config.prescale = options().prescale();
+ video_config.filter = options().filter();
+ video_config.numscreens = options().numscreens();
+ #ifdef SDLMAME_X11
+ video_config.restrictonemonitor = !options().use_all_heads();
+ #endif
+
+ // if we are in debug mode, never go full screen
+ if (machine().debug_flags & DEBUG_FLAG_OSD_ENABLED)
+ video_config.windowed = true;
+
+ video_config.switchres = options().switch_res();
+ video_config.centerh = options().centerh();
+ video_config.centerv = options().centerv();
+ video_config.waitvsync = options().wait_vsync();
+ video_config.syncrefresh = options().sync_refresh();
+ if (!video_config.waitvsync && video_config.syncrefresh)
+ {
+ osd_printf_warning("-syncrefresh specified without -waitvsync. Reverting to -nosyncrefresh\n");
+ video_config.syncrefresh = 0;
+ }
+
+ if (video_config.prescale < 1 || video_config.prescale > 20)
+ {
+ osd_printf_warning("Invalid prescale option, reverting to '1'\n");
+ video_config.prescale = 1;
+ }
+
+ // misc options: sanity check values
+
+ // global options: sanity check values
+ if (video_config.numscreens < 1 || video_config.numscreens > MAX_VIDEO_WINDOWS)
+ {
+ osd_printf_warning("Invalid numscreens value %d; reverting to 1\n", video_config.numscreens);
+ video_config.numscreens = 1;
+ }
+}
+
+
+//============================================================
+// get_resolution
+//============================================================
+
+static void get_resolution(const char *defdata, const char *data, osd_window_config *config, int report_error)
+{
+ config->width = config->height = config->depth = config->refresh = 0;
+ if (strcmp(data, OSDOPTVAL_AUTO) == 0)
+ {
+ if (strcmp(defdata, OSDOPTVAL_AUTO) == 0)
+ return;
+ data = defdata;
+ }
+
+ if (sscanf(data, "%dx%dx%d", &config->width, &config->height, &config->depth) < 2 && report_error)
+ osd_printf_error("Illegal resolution value = %s\n", data);
+
+ const char * at_pos = strchr(data, '@');
+ if (at_pos)
+ if (sscanf(at_pos + 1, "%d", &config->refresh) < 1 && report_error)
+ osd_printf_error("Illegal refresh rate in resolution value = %s\n", data);
+}
diff --git a/src/osd/sdl3/window.cpp b/src/osd/sdl3/window.cpp
new file mode 100644
index 00000000000..3262845fc58
--- /dev/null
+++ b/src/osd/sdl3/window.cpp
@@ -0,0 +1,1336 @@
+// license:BSD-3-Clause
+// copyright-holders:Olivier Galibert, R. Belmont
+//============================================================
+//
+// window.c - SDL window handling
+//
+// SDLMAME by Olivier Galibert and R. Belmont
+//
+//============================================================
+
+// MAME headers
+#include "emu.h"
+#include "emuopts.h"
+#include "render.h"
+#include "screen.h"
+#include "uiinput.h"
+#include "ui/uimain.h"
+
+// OSD headers
+#include "modules/monitor/monitor_common.h"
+#include "osdsdl.h"
+#include "window.h"
+
+// standard C headers
+#include <algorithm>
+#include <cassert>
+#include <cmath>
+#include <list>
+#include <memory>
+
+#ifndef _MSC_VER
+#include <unistd.h>
+#endif
+
+#ifdef SDLMAME_WIN32
+#include <windows.h>
+#endif
+
+
+//============================================================
+// PARAMETERS
+//============================================================
+
+// these are arbitrary values since AFAIK there's no way to make X/SDL tell you
+#define WINDOW_DECORATION_WIDTH (8) // should be more than plenty
+#define WINDOW_DECORATION_HEIGHT (48) // title bar + bottom drag region
+
+// minimum window dimension
+#define MIN_WINDOW_DIM 200
+
+#ifndef SDLMAME_WIN32
+#define WMSZ_TOP (0)
+#define WMSZ_BOTTOM (1)
+#define WMSZ_BOTTOMLEFT (2)
+#define WMSZ_BOTTOMRIGHT (3)
+#define WMSZ_LEFT (4)
+#define WMSZ_TOPLEFT (5)
+#define WMSZ_TOPRIGHT (6)
+#define WMSZ_RIGHT (7)
+#endif
+
+#define SDL_VERSION_EQUALS(v1, vnum2) (SDL_VERSIONNUM(v1.major, v1.minor, v1.patch) == vnum2)
+
+
+
+//============================================================
+// window_init
+// (main thread)
+//============================================================
+
+bool sdl_osd_interface::window_init()
+{
+ osd_printf_verbose("Enter sdlwindow_init\n");
+
+ // We may want to set a number of the hints SDL2 provides.
+ // The code below will document which hints were set.
+ char const *const hints[] = {
+ SDL_HINT_FRAMEBUFFER_ACCELERATION,
+ SDL_HINT_RENDER_DRIVER,
+ SDL_HINT_RENDER_VSYNC,
+ SDL_HINT_VIDEO_X11_XRANDR,
+ SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS,
+ SDL_HINT_ORIENTATIONS,
+ SDL_HINT_XINPUT_ENABLED, SDL_HINT_GAMECONTROLLERCONFIG,
+ SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, SDL_HINT_WINDOW_ALLOW_TOPMOST,
+ SDL_HINT_TIMER_RESOLUTION,
+ SDL_HINT_RENDER_DIRECT3D_THREADSAFE, SDL_HINT_VIDEO_ALLOW_SCREENSAVER,
+ SDL_HINT_MAC_CTRL_CLICK_EMULATE_RIGHT_CLICK,
+ SDL_HINT_VIDEO_WIN_D3DCOMPILER,
+ SDL_HINT_VIDEO_MAC_FULLSCREEN_SPACES,
+ SDL_HINT_RENDER_DIRECT3D11_DEBUG
+ };
+
+ osd_printf_verbose("\nHints:\n");
+ for (auto const hintname : hints)
+ {
+ char const *const hintvalue(SDL_GetHint(hintname));
+ osd_printf_verbose("\t%-40s %s\n", hintname, hintvalue ? hintvalue : "(NULL)");
+ }
+
+ // set up the window list
+ osd_printf_verbose("Leave sdlwindow_init\n");
+ return true;
+}
+
+
+//============================================================
+// sdlwindow_exit
+// (main thread)
+//============================================================
+
+void sdl_osd_interface::window_exit()
+{
+ osd_printf_verbose("Enter sdlwindow_exit\n");
+
+ // free all the windows
+ m_focus_window = nullptr;
+ while (!osd_common_t::s_window_list.empty())
+ {
+ auto window = std::move(osd_common_t::s_window_list.back());
+ s_window_list.pop_back();
+ window->destroy();
+ }
+
+ osd_printf_verbose("Leave sdlwindow_exit\n");
+}
+
+void sdl_window_info::capture_pointer()
+{
+ if (!m_mouse_captured)
+ {
+ SDL_SetWindowMouseGrab(platform_window(), true);
+ SDL_SetWindowKeyboardGrab(platform_window(), true);
+ SDL_SetWindowRelativeMouseMode(platform_window(), true);
+ m_mouse_captured = true;
+ }
+}
+
+void sdl_window_info::release_pointer()
+{
+ if (m_mouse_captured)
+ {
+ SDL_SetWindowMouseGrab(platform_window(), false);
+ SDL_SetWindowKeyboardGrab(platform_window(), false);
+ SDL_SetWindowRelativeMouseMode(platform_window(), false);
+ m_mouse_captured = false;
+ }
+}
+
+void sdl_window_info::hide_pointer()
+{
+ if (!m_mouse_hidden)
+ {
+ SDL_HideCursor();
+ m_mouse_hidden = true;
+ }
+}
+
+void sdl_window_info::show_pointer()
+{
+ if (m_mouse_hidden)
+ {
+ SDL_ShowCursor();
+ m_mouse_hidden = false;
+ }
+}
+
+
+//============================================================
+// sdlwindow_resize
+//============================================================
+
+void sdl_window_info::resize(int32_t width, int32_t height)
+{
+ osd_dim cd = get_size();
+
+ if (width != cd.width() || height != cd.height())
+ {
+ SDL_SetWindowSize(platform_window(), width, height);
+ renderer().notify_changed();
+ }
+}
+
+
+//============================================================
+// sdlwindow_clear_surface
+//============================================================
+
+void sdl_window_info::notify_changed()
+{
+ renderer().notify_changed();
+}
+
+
+//============================================================
+// sdlwindow_toggle_full_screen
+//============================================================
+
+void sdl_window_info::toggle_full_screen()
+{
+ // if we are in debug mode, never go full screen
+ if (machine().debug_flags & DEBUG_FLAG_OSD_ENABLED)
+ return;
+
+ // If we are going fullscreen (leaving windowed) remember our windowed size
+ if (!fullscreen())
+ {
+ m_windowed_dim = get_size();
+ }
+
+ // kill off the drawers
+ renderer_reset();
+ bool is_osx = false;
+#ifdef SDLMAME_MACOSX
+ // FIXME: This is weird behaviour and certainly a bug in SDL
+ is_osx = true;
+#endif
+ if (fullscreen() && (video_config.switchres || is_osx))
+ {
+ SDL_SetWindowFullscreen(platform_window(), 0);
+ SDL_SetWindowFullscreenMode(platform_window(), &m_original_mode);
+ SDL_SetWindowFullscreen(platform_window(), SDL_WINDOW_FULLSCREEN);
+ }
+ SDL_DestroyWindow(platform_window());
+ set_platform_window(nullptr);
+ downcast<sdl_osd_interface &>(machine().osd()).release_keys();
+
+ // toggle the window mode
+ set_fullscreen(!fullscreen());
+
+ complete_create();
+}
+
+void sdl_window_info::modify_prescale(int dir)
+{
+ int new_prescale = prescale();
+
+ if (dir > 0 && prescale() < 20)
+ new_prescale = prescale() + 1;
+ if (dir < 0 && prescale() > 1)
+ new_prescale = prescale() - 1;
+
+ if (new_prescale != prescale())
+ {
+ if (m_fullscreen && video_config.switchres)
+ {
+ complete_destroy();
+
+ m_prescale = new_prescale;
+
+ complete_create();
+ }
+ else
+ {
+ m_prescale = new_prescale;
+ notify_changed();
+ }
+ }
+ machine().ui().popup_time(1, "Prescale %d", prescale());
+}
+
+//============================================================
+// sdlwindow_update_cursor_state
+// (main or window thread)
+//============================================================
+
+void sdl_window_info::update_cursor_state()
+{
+#if (USE_XINPUT && USE_XINPUT_WII_LIGHTGUN_HACK)
+ // Hack for wii-lightguns:
+ // they stop working with a grabbed mouse;
+ // even a ShowCursor(SDL_DISABLE) already does this.
+ // To make the cursor disappear, we'll just set an empty cursor image.
+ unsigned char data[]={0,0,0,0,0,0,0,0};
+ SDL_Cursor *c;
+ c=SDL_CreateCursor(data, data, 8, 8, 0, 0);
+ SDL_SetCursor(c);
+#else
+ // do not do mouse capture if the debugger's enabled to avoid
+ // the possibility of losing control
+ if (!(machine().debug_flags & DEBUG_FLAG_OSD_ENABLED))
+ {
+ bool should_hide_mouse = downcast<sdl_osd_interface&>(machine().osd()).should_hide_mouse();
+
+ if (!fullscreen() && !should_hide_mouse)
+ {
+ show_pointer();
+ release_pointer();
+ }
+ else
+ {
+ hide_pointer();
+ capture_pointer();
+ }
+
+ SDL_SetCursor(nullptr); // Force an update in case the underlying driver has changed visibility
+ }
+#endif
+}
+
+int sdl_window_info::xy_to_render_target(int x, int y, int *xt, int *yt)
+{
+ return renderer().xy_to_render_target(x, y, xt, yt);
+}
+
+void sdl_window_info::mouse_entered(unsigned device)
+{
+ m_mouse_inside = true;
+}
+
+void sdl_window_info::mouse_left(unsigned device)
+{
+ m_mouse_inside = false;
+
+ auto info(std::lower_bound(m_active_pointers.begin(), m_active_pointers.end(), SDL_FingerID(-1), &sdl_pointer_info::compare));
+ if ((m_active_pointers.end() == info) || (info->finger != SDL_FingerID(-1)))
+ return;
+
+ // leaving implicitly releases buttons, so check hold/drag if necessary
+ if (BIT(info->buttons, 0) && (0 < info->clickcnt))
+ {
+ auto const now(std::chrono::steady_clock::now());
+ auto const exp(std::chrono::milliseconds(250) + info->pressed);
+ int const dx(info->x - info->pressedx);
+ int const dy(info->y - info->pressedy);
+ int const distance((dx * dx) + (dy * dy));
+ if ((exp < now) || (CLICK_DISTANCE < distance))
+ info->clickcnt = -info->clickcnt;
+ }
+
+ // push to UI manager
+ machine().ui_input().push_pointer_leave(
+ target(),
+ osd::ui_event_handler::pointer::MOUSE,
+ info->index,
+ device,
+ info->x, info->y,
+ info->buttons, info->clickcnt);
+
+ // dump pointer data
+ m_pointer_mask &= ~(decltype(m_pointer_mask)(1) << info->index);
+ if (info->index < m_next_pointer)
+ m_next_pointer = info->index;
+ m_active_pointers.erase(info);
+}
+
+void sdl_window_info::mouse_down(unsigned device, int x, int y, unsigned button)
+{
+ if (!m_mouse_inside)
+ return;
+
+ auto const info(map_pointer(SDL_FingerID(-1), device));
+ if (m_active_pointers.end() == info)
+ return;
+
+ if ((x == info->x) && (y == info->y) && BIT(info->buttons, button))
+ return;
+
+ // detect multi-click actions
+ if (0 == button)
+ {
+ info->primary_down(
+ x,
+ y,
+ std::chrono::milliseconds(250),
+ CLICK_DISTANCE,
+ false,
+ m_ptrdev_info);
+ }
+
+ // update info and push to UI manager
+ auto const pressed(decltype(info->buttons)(1) << button);
+ info->x = x;
+ info->y = y;
+ info->buttons |= pressed;
+ machine().ui_input().push_pointer_update(
+ target(),
+ osd::ui_event_handler::pointer::MOUSE,
+ info->index,
+ device,
+ x, y,
+ info->buttons, pressed, 0, info->clickcnt);
+}
+
+void sdl_window_info::mouse_up(unsigned device, int x, int y, unsigned button)
+{
+ if (!m_mouse_inside)
+ return;
+
+ auto const info(map_pointer(SDL_FingerID(-1), device));
+ if (m_active_pointers.end() == info)
+ return;
+
+ if ((x == info->x) && (y == info->y) && !BIT(info->buttons, button))
+ return;
+
+ // detect multi-click actions
+ if (0 == button)
+ {
+ info->check_primary_hold_drag(
+ x,
+ y,
+ std::chrono::milliseconds(250),
+ CLICK_DISTANCE);
+ }
+
+ // update info and push to UI manager
+ auto const released(decltype(info->buttons)(1) << button);
+ info->x = x;
+ info->y = y;
+ info->buttons &= ~released;
+ machine().ui_input().push_pointer_update(
+ target(),
+ osd::ui_event_handler::pointer::MOUSE,
+ info->index,
+ device,
+ x, y,
+ info->buttons, 0, released, info->clickcnt);
+}
+
+void sdl_window_info::mouse_moved(unsigned device, int x, int y)
+{
+ if (!m_mouse_inside)
+ return;
+
+ auto const info(map_pointer(SDL_FingerID(-1), device));
+ if (m_active_pointers.end() == info)
+ return;
+
+ // detect multi-click actions
+ if (BIT(info->buttons, 0))
+ {
+ info->check_primary_hold_drag(
+ x,
+ y,
+ std::chrono::milliseconds(250),
+ CLICK_DISTANCE);
+ }
+
+ // update info and push to UI manager
+ info->x = x;
+ info->y = y;
+ machine().ui_input().push_pointer_update(
+ target(),
+ osd::ui_event_handler::pointer::MOUSE,
+ info->index,
+ device,
+ x, y,
+ info->buttons, 0, 0, info->clickcnt);
+}
+
+void sdl_window_info::mouse_wheel(unsigned device, int y)
+{
+ if (!m_mouse_inside)
+ return;
+
+ auto const info(map_pointer(SDL_FingerID(-1), device));
+ if (m_active_pointers.end() == info)
+ return;
+
+ // push to UI manager
+ machine().ui_input().push_mouse_wheel_event(target(), info->x, info->y, y, 3);
+}
+
+void sdl_window_info::finger_down(SDL_FingerID finger, unsigned device, int x, int y)
+{
+ auto const info(map_pointer(finger, device));
+ if (m_active_pointers.end() == info)
+ return;
+
+ assert(!info->buttons);
+
+ // detect multi-click actions
+ info->primary_down(
+ x,
+ y,
+ std::chrono::milliseconds(250),
+ TAP_DISTANCE,
+ true,
+ m_ptrdev_info);
+
+ // update info and push to UI manager
+ info->x = x;
+ info->y = y;
+ info->buttons = 1;
+ machine().ui_input().push_pointer_update(
+ target(),
+ osd::ui_event_handler::pointer::TOUCH,
+ info->index,
+ device,
+ x, y,
+ 1, 1, 0, info->clickcnt);
+}
+
+void sdl_window_info::finger_up(SDL_FingerID finger, unsigned device, int x, int y)
+{
+ auto info(std::lower_bound(m_active_pointers.begin(), m_active_pointers.end(), finger, &sdl_pointer_info::compare));
+ if ((m_active_pointers.end() == info) || (info->finger != finger))
+ return;
+
+ assert(1 == info->buttons);
+
+ // check for conversion to a (multi-)click-and-hold/drag
+ info->check_primary_hold_drag(
+ x,
+ y,
+ std::chrono::milliseconds(250),
+ TAP_DISTANCE);
+
+ // need to remember touches to recognise multi-tap gestures
+ if (0 < info->clickcnt)
+ {
+ auto const now(std::chrono::steady_clock::now());
+ auto const time = std::chrono::milliseconds(250);
+ if ((time + info->pressed) >= now)
+ {
+ try
+ {
+ unsigned i(0);
+ if (m_ptrdev_info.size() > device)
+ i = m_ptrdev_info[device].clear_expired_touches(now, time);
+ else
+ m_ptrdev_info.resize(device + 1);
+
+ if (std::size(m_ptrdev_info[device].touches) > i)
+ {
+ m_ptrdev_info[device].touches[i].when = info->pressed;
+ m_ptrdev_info[device].touches[i].x = info->pressedx;
+ m_ptrdev_info[device].touches[i].y = info->pressedy;
+ m_ptrdev_info[device].touches[i].cnt = info->clickcnt;
+ }
+ }
+ catch (std::bad_alloc const &)
+ {
+ osd_printf_error("win_window_info: error allocating pointer data\n");
+ }
+ }
+ }
+
+ // push to UI manager
+ machine().ui_input().push_pointer_update(
+ target(),
+ osd::ui_event_handler::pointer::TOUCH,
+ info->index,
+ device,
+ x, y,
+ 0, 0, 1, info->clickcnt);
+ machine().ui_input().push_pointer_leave(
+ target(),
+ osd::ui_event_handler::pointer::TOUCH,
+ info->index,
+ device,
+ x, y,
+ 0, info->clickcnt);
+
+ // dump pointer data
+ m_pointer_mask &= ~(decltype(m_pointer_mask)(1) << info->index);
+ if (info->index < m_next_pointer)
+ m_next_pointer = info->index;
+ m_active_pointers.erase(info);
+}
+
+void sdl_window_info::finger_moved(SDL_FingerID finger, unsigned device, int x, int y)
+{
+ auto info(std::lower_bound(m_active_pointers.begin(), m_active_pointers.end(), finger, &sdl_pointer_info::compare));
+ if ((m_active_pointers.end() == info) || (info->finger != finger))
+
+ assert(1 == info->buttons);
+
+ if ((x != info->x) || (y != info->y))
+ {
+ info->check_primary_hold_drag(
+ x,
+ y,
+ std::chrono::milliseconds(250),
+ TAP_DISTANCE);
+
+ // update info and push to UI manager
+ info->x = x;
+ info->y = y;
+ machine().ui_input().push_pointer_update(
+ target(),
+ osd::ui_event_handler::pointer::TOUCH,
+ info->index,
+ device,
+ x, y,
+ 1, 0, 0, info->clickcnt);
+ }
+}
+
+//============================================================
+// sdlwindow_video_window_create
+// (main thread)
+//============================================================
+
+int sdl_window_info::window_init()
+{
+ // set the initial maximized state
+ // FIXME: Does not belong here
+ m_startmaximized = downcast<sdl_options &>(machine().options()).maximize();
+
+ create_target();
+
+ int result = complete_create();
+
+ // handle error conditions
+ if (result == 1)
+ goto error;
+
+ return 0;
+
+error:
+ destroy();
+ return 1;
+}
+
+
+//============================================================
+// sdlwindow_video_window_destroy
+//============================================================
+
+void sdl_window_info::complete_destroy()
+{
+ // Release pointer grab and hide if needed
+ show_pointer();
+ release_pointer();
+
+ if (fullscreen() && video_config.switchres)
+ {
+ SDL_SetWindowFullscreen(platform_window(), 0);
+ SDL_SetWindowFullscreenMode(platform_window(), &m_original_mode);
+ SDL_SetWindowFullscreen(platform_window(), SDL_WINDOW_FULLSCREEN);
+ }
+
+ renderer_reset();
+ SDL_DestroyWindow(platform_window());
+ set_platform_window(nullptr);
+ downcast<sdl_osd_interface &>(machine().osd()).release_keys();
+}
+
+
+//============================================================
+// pick_best_mode
+//============================================================
+
+osd_dim sdl_window_info::pick_best_mode()
+{
+ int minimum_width, minimum_height, target_width, target_height;
+ int i;
+ int num;
+ float size_score, best_score = 0.0f;
+ osd_dim ret(0,0);
+
+ // determine the minimum width/height for the selected target
+ target()->compute_minimum_size(minimum_width, minimum_height);
+
+ // use those as the target for now
+ target_width = minimum_width * std::max(1, prescale());
+ target_height = minimum_height * std::max(1, prescale());
+
+ // if we're not stretching, allow some slop on the minimum since we can handle it
+ {
+ minimum_width -= 4;
+ minimum_height -= 4;
+ }
+
+ SDL_DisplayMode **modes = SDL_GetFullscreenDisplayModes(monitor()->oshandle(), &num);
+ if (num == 0)
+ {
+ osd_printf_error("SDL: No modes available?!\n");
+ exit(-1);
+ }
+ else
+ {
+ for (i = 0; i < num; ++i)
+ {
+ // compute initial score based on difference between target and current
+ size_score = 1.0f / (1.0f + abs((int32_t)modes[i]->w - target_width) + abs((int32_t)modes[i]->h - target_height));
+
+ // if the mode is too small, give a big penalty
+ if (modes[i]->w < minimum_width || modes[i]->h < minimum_height)
+ size_score *= 0.01f;
+
+ // if mode is smaller than we'd like, it only scores up to 0.1
+ if (modes[i]->w < target_width || modes[i]->h < target_height)
+ size_score *= 0.1f;
+
+ // if we're looking for a particular mode, that's a winner
+ if (modes[i]->w == m_win_config.width && modes[i]->h == m_win_config.height)
+ size_score = 2.0f;
+
+ // refresh adds some points
+ if (m_win_config.refresh)
+ size_score *= 1.0f / (1.0f + abs(m_win_config.refresh - modes[i]->refresh_rate) / 10.0f);
+
+ osd_printf_verbose("%4dx%4d@%2d -> %f\n", (int)modes[i]->w, (int)modes[i]->h, (int) modes[i]->refresh_rate, (double) size_score);
+
+ // best so far?
+ if (size_score > best_score)
+ {
+ best_score = size_score;
+ ret = osd_dim(modes[i]->w, modes[i]->h);
+ }
+
+ }
+ }
+ return ret;
+}
+
+//============================================================
+// sdlwindow_video_window_update
+// (main thread)
+//============================================================
+
+void sdl_window_info::update()
+{
+ // adjust the cursor state
+ update_cursor_state();
+
+ // if we're visible and running and not in the middle of a resize, draw
+ if (target() != nullptr)
+ {
+ int tempwidth, tempheight;
+
+ // see if the games video mode has changed
+ target()->compute_minimum_size(tempwidth, tempheight);
+ if (osd_dim(tempwidth, tempheight) != m_minimum_dim)
+ {
+ m_minimum_dim = osd_dim(tempwidth, tempheight);
+
+ if (!this->m_fullscreen)
+ {
+ //Don't resize window without user interaction;
+ //window_resize(blitwidth, blitheight);
+ }
+ else if (video_config.switchres)
+ {
+ osd_dim tmp = this->pick_best_mode();
+ resize(tmp.width(), tmp.height());
+ }
+ }
+
+ osd_ticks_t event_wait_ticks;
+ if (video_config.waitvsync && video_config.syncrefresh)
+ event_wait_ticks = osd_ticks_per_second(); // block at most a second
+ else
+ event_wait_ticks = 0;
+
+ if (m_rendered_event.wait(event_wait_ticks))
+ {
+ const int update = 1;
+
+ // ensure the target bounds are up-to-date, and then get the primitives
+
+ render_primitive_list &primlist = *renderer().get_primitives();
+
+ // and redraw now
+
+ // Check whether window has vector screens
+
+ {
+ const screen_device *screen = screen_device_enumerator(machine().root_device()).byindex(index());
+ if ((screen != nullptr) && (screen->screen_type() == SCREEN_TYPE_VECTOR))
+ renderer().set_flags(osd_renderer::FLAG_HAS_VECTOR_SCREEN);
+ else
+ renderer().clear_flags(osd_renderer::FLAG_HAS_VECTOR_SCREEN);
+ }
+
+ m_primlist = &primlist;
+
+ if (m_primlist == nullptr)
+ {
+ // if no bitmap, just fill
+ }
+ else
+ {
+ // otherwise, render with our drawing system
+ if (video_config.perftest)
+ measure_fps(update);
+ else
+ renderer().draw(update);
+ }
+
+ // all done, ready for next
+ m_rendered_event.set();
+ }
+ }
+}
+
+
+//============================================================
+// complete_create
+//============================================================
+
+int sdl_window_info::complete_create()
+{
+ osd_dim temp(0,0);
+
+ // clear out original mode. Needed on OSX
+ if (fullscreen())
+ {
+ // default to the current mode exactly
+ temp = monitor()->position_size().dim();
+
+ // if we're allowed to switch resolutions, override with something better
+ if (video_config.switchres)
+ temp = pick_best_mode();
+ }
+ else if (m_windowed_dim.width() > 0)
+ {
+ // if we have a remembered size force the new window size to it
+ temp = m_windowed_dim;
+ }
+ else if (m_startmaximized)
+ {
+ temp = get_max_bounds(keepaspect());
+ }
+ else
+ {
+ temp = get_min_bounds(keepaspect());
+ }
+
+ // create the window .....
+
+ osd_printf_verbose("Enter sdl_window_info::create\n");
+ if (renderer_sdl_needs_opengl())
+ {
+ SDL_GL_SetAttribute( SDL_GL_DOUBLEBUFFER, 1 );
+ }
+
+#if defined(SDLMAME_WIN32)
+ SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, "0");
+#endif
+
+ // get monitor work area for centering
+ osd_rect work = monitor()->usuable_position_size();
+
+ // create or attach to an existing window
+ SDL_Window *sdlwindow;
+#ifdef SDLMAME_X11
+ const char *attach_window = downcast<sdl_options &>(machine().options()).attach_window();
+#else
+ const char *attach_window = nullptr;
+#endif
+
+ // create the SDL window
+ SDL_PropertiesID props = SDL_CreateProperties();
+ SDL_SetStringProperty(props, SDL_PROP_WINDOW_CREATE_TITLE_STRING, title().c_str());
+ SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_X_NUMBER, work.left() + (work.width() - temp.width()) / 2);
+ SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_Y_NUMBER, work.top() + (work.height() - temp.height()) / 2);
+ SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_WIDTH_NUMBER, temp.width());
+ SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_HEIGHT_NUMBER, temp.height());
+ SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_RESIZABLE_BOOLEAN, true);
+
+ if (fullscreen())
+ {
+ SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_FULLSCREEN_BOOLEAN, true);
+ }
+
+ if (renderer_sdl_needs_opengl())
+ {
+ SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_OPENGL_BOOLEAN, true);
+ }
+
+ if (attach_window && *attach_window)
+ {
+ // we're attaching to an existing window; parse the argument
+ unsigned long long attach_window_value;
+ try
+ {
+ attach_window_value = std::stoull(attach_window, nullptr, 0);
+ }
+ catch (std::invalid_argument &)
+ {
+ osd_printf_error("Invalid -attach_window value: %s\n", attach_window);
+ return 1;
+ }
+
+ // and attach to it
+ SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_X11_WINDOW_NUMBER, attach_window_value);
+ }
+
+ sdlwindow = SDL_CreateWindowWithProperties(props);
+
+ if (sdlwindow == nullptr)
+ {
+ if (renderer_sdl_needs_opengl())
+ {
+ osd_printf_error("OpenGL not supported on this driver: %s\n", SDL_GetError());
+ }
+ else
+ {
+ osd_printf_error("Window creation failed: %s\n", SDL_GetError());
+ }
+
+ osd_printf_verbose("Exit sdl_window_info::create\n");
+ return 1;
+ }
+
+ set_platform_window(sdlwindow);
+ renderer_create();
+
+ if (fullscreen() && video_config.switchres)
+ {
+ const SDL_DisplayMode *mode = SDL_GetWindowFullscreenMode(platform_window());
+ m_original_mode = *mode;
+ SDL_DisplayMode newmode;
+ newmode.w = temp.width();
+ newmode.h = temp.height();
+ if (m_win_config.refresh)
+ {
+ newmode.refresh_rate = m_win_config.refresh;
+ }
+ SDL_SetWindowFullscreenMode(platform_window(), &newmode); // Try to set mode
+#ifndef SDLMAME_WIN32
+ /* FIXME: Warp the mouse to 0,0 in case a virtual desktop resolution
+ * is in place after the mode switch - which will most likely be the case
+ * This is a hack to work around a deficiency in SDL2
+ */
+ SDL_WarpMouseInWindow(platform_window(), 1, 1);
+#endif
+ }
+ else
+ {
+ //SDL_SetWindowFullscreenMode(window().sdl_window(), nullptr); // Use desktop
+ }
+
+ // show window
+
+ SDL_ShowWindow(platform_window());
+ //SDL_SetWindowFullscreen(window->sdl_window(), 0);
+ //SDL_SetWindowFullscreen(window->sdl_window(), window->fullscreen());
+ SDL_RaiseWindow(platform_window());
+
+#ifdef SDLMAME_WIN32
+// if (fullscreen())
+// SDL_SetWindowGrab(platform_window(), true);
+#endif
+
+ // update monitor resolution after mode change to ensure proper pixel aspect
+ monitor()->refresh();
+ if (fullscreen() && video_config.switchres)
+ monitor()->update_resolution(temp.width(), temp.height());
+
+ // initialize the drawing backend
+ if (renderer().create())
+ return 1;
+
+ // Make sure we have a consistent state
+ SDL_HideCursor();
+ SDL_ShowCursor();
+
+ return 0;
+}
+
+
+//============================================================
+// draw_video_contents
+// (window thread)
+//============================================================
+
+void sdl_window_info::measure_fps(int update)
+{
+ const unsigned long frames_skip4fps = 100;
+ static int64_t lastTime=0, sumdt=0, startTime=0;
+ static unsigned long frames = 0;
+ int64_t currentTime, t0;
+ double dt;
+ double tps;
+ osd_ticks_t tps_t;
+
+ tps_t = osd_ticks_per_second();
+ tps = (double) tps_t;
+
+ t0 = osd_ticks();
+
+ renderer().draw(update);
+
+ frames++;
+ currentTime = osd_ticks();
+ if(startTime==0||frames==frames_skip4fps)
+ startTime=currentTime;
+ if( frames>=frames_skip4fps )
+ sumdt+=currentTime-t0;
+ if( (currentTime-lastTime)>1L*osd_ticks_per_second() && frames>frames_skip4fps )
+ {
+ dt = (double) (currentTime-startTime) / tps; // in decimale sec.
+ osd_printf_info("%6.2lfs, %4lu F, "
+ "avrg game: %5.2lf FPS %.2lf ms/f, "
+ "avrg video: %5.2lf FPS %.2lf ms/f, "
+ "last video: %5.2lf FPS %.2lf ms/f\n",
+ dt, frames-frames_skip4fps,
+ (double)(frames-frames_skip4fps)/dt, // avrg game fps
+ ( (currentTime-startTime) / ((frames-frames_skip4fps)) ) * 1000.0 / osd_ticks_per_second(),
+ (double)(frames-frames_skip4fps)/((double)(sumdt) / tps), // avrg vid fps
+ ( sumdt / ((frames-frames_skip4fps)) ) * 1000.0 / tps,
+ 1.0/((currentTime-t0) / osd_ticks_per_second()), // this vid fps
+ (currentTime-t0) * 1000.0 / tps
+ );
+ lastTime = currentTime;
+ }
+}
+
+int sdl_window_info::wnd_extra_width()
+{
+ return m_fullscreen ? 0 : WINDOW_DECORATION_WIDTH;
+}
+
+int sdl_window_info::wnd_extra_height()
+{
+ return m_fullscreen ? 0 : WINDOW_DECORATION_HEIGHT;
+}
+
+
+//============================================================
+// constrain_to_aspect_ratio
+// (window thread)
+//============================================================
+
+osd_rect sdl_window_info::constrain_to_aspect_ratio(const osd_rect &rect, int adjustment)
+{
+ int32_t extrawidth = wnd_extra_width();
+ int32_t extraheight = wnd_extra_height();
+ int32_t propwidth, propheight;
+ int32_t minwidth, minheight;
+ int32_t maxwidth, maxheight;
+ int32_t viswidth, visheight;
+ int32_t adjwidth, adjheight;
+ float pixel_aspect;
+
+ // get the pixel aspect ratio for the target monitor
+ pixel_aspect = monitor()->pixel_aspect();
+
+ // determine the proposed width/height
+ propwidth = rect.width() - extrawidth;
+ propheight = rect.height() - extraheight;
+
+ // based on which edge we are adjusting, take either the width, height, or both as gospel
+ // and scale to fit using that as our parameter
+ switch (adjustment)
+ {
+ case WMSZ_BOTTOM:
+ case WMSZ_TOP:
+ target()->compute_visible_area(10000, propheight, pixel_aspect, target()->orientation(), propwidth, propheight);
+ break;
+
+ case WMSZ_LEFT:
+ case WMSZ_RIGHT:
+ target()->compute_visible_area(propwidth, 10000, pixel_aspect, target()->orientation(), propwidth, propheight);
+ break;
+
+ default:
+ target()->compute_visible_area(propwidth, propheight, pixel_aspect, target()->orientation(), propwidth, propheight);
+ break;
+ }
+
+ // get the minimum width/height for the current layout
+ target()->compute_minimum_size(minwidth, minheight);
+
+ // clamp against the absolute minimum
+ propwidth = std::max(propwidth, MIN_WINDOW_DIM);
+ propheight = std::max(propheight, MIN_WINDOW_DIM);
+
+ // clamp against the minimum width and height
+ propwidth = std::max(propwidth, minwidth);
+ propheight = std::max(propheight, minheight);
+
+ // clamp against the maximum (fit on one screen for full screen mode)
+ if (m_fullscreen)
+ {
+ maxwidth = monitor()->position_size().width() - extrawidth;
+ maxheight = monitor()->position_size().height() - extraheight;
+ }
+ else
+ {
+ maxwidth = monitor()->usuable_position_size().width() - extrawidth;
+ maxheight = monitor()->usuable_position_size().height() - extraheight;
+
+ // further clamp to the maximum width/height in the window
+ if (m_win_config.width != 0)
+ maxwidth = std::min(maxwidth, m_win_config.width + extrawidth);
+ if (m_win_config.height != 0)
+ maxheight = std::min(maxheight, m_win_config.height + extraheight);
+ }
+
+ // clamp to the maximum
+ propwidth = std::min(propwidth, maxwidth);
+ propheight = std::min(propheight, maxheight);
+
+ // compute the visible area based on the proposed rectangle
+ target()->compute_visible_area(propwidth, propheight, pixel_aspect, target()->orientation(), viswidth, visheight);
+
+ // clamp visable area to the proposed rectangle
+ viswidth = std::min(viswidth, propwidth);
+ visheight = std::min(visheight, propheight);
+
+ // compute the adjustments we need to make
+ adjwidth = (viswidth + extrawidth) - rect.width();
+ adjheight = (visheight + extraheight) - rect.height();
+
+ // based on which corner we're adjusting, constrain in different ways
+ osd_rect ret(rect);
+
+ switch (adjustment)
+ {
+ case WMSZ_BOTTOM:
+ case WMSZ_BOTTOMRIGHT:
+ case WMSZ_RIGHT:
+ ret = rect.resize(rect.width() + adjwidth, rect.height() + adjheight);
+ break;
+
+ case WMSZ_BOTTOMLEFT:
+ ret = rect.move_by(-adjwidth, 0).resize(rect.width() + adjwidth, rect.height() + adjheight);
+ break;
+
+ case WMSZ_LEFT:
+ case WMSZ_TOPLEFT:
+ case WMSZ_TOP:
+ ret = rect.move_by(-adjwidth, -adjheight).resize(rect.width() + adjwidth, rect.height() + adjheight);
+ break;
+
+ case WMSZ_TOPRIGHT:
+ ret = rect.move_by(0, -adjheight).resize(rect.width() + adjwidth, rect.height() + adjheight);
+ break;
+}
+ return ret;
+}
+
+
+
+//============================================================
+// get_min_bounds
+// (window thread)
+//============================================================
+
+osd_dim sdl_window_info::get_min_bounds(int constrain)
+{
+ int32_t minwidth, minheight;
+
+ //assert(GetCurrentThreadId() == window_threadid);
+
+ // get the minimum target size
+ target()->compute_minimum_size(minwidth, minheight);
+
+ // check if visible area is bigger
+ int32_t viswidth, visheight;
+ target()->compute_visible_area(minwidth, minheight, monitor()->aspect(), target()->orientation(), viswidth, visheight);
+ minwidth = std::max(viswidth, minwidth);
+ minheight = std::max(visheight, minheight);
+
+ // expand to our minimum dimensions
+ if (minwidth < MIN_WINDOW_DIM)
+ minwidth = MIN_WINDOW_DIM;
+ if (minheight < MIN_WINDOW_DIM)
+ minheight = MIN_WINDOW_DIM;
+
+ // account for extra window stuff
+ minwidth += wnd_extra_width();
+ minheight += wnd_extra_height();
+
+ // if we want it constrained, figure out which one is larger
+ if (constrain)
+ {
+ // first constrain with no height limit
+ osd_rect test1(0,0,minwidth,10000);
+ test1 = constrain_to_aspect_ratio(test1, WMSZ_BOTTOMRIGHT);
+
+ // then constrain with no width limit
+ osd_rect test2(0,0,10000,minheight);
+ test2 = constrain_to_aspect_ratio(test2, WMSZ_BOTTOMRIGHT);
+
+ // pick the larger
+ if (test1.width() > test2.width())
+ {
+ minwidth = test1.width();
+ minheight = test1.height();
+ }
+ else
+ {
+ minwidth = test2.width();
+ minheight = test2.height();
+ }
+ }
+
+ // remove extra window stuff
+ minwidth -= wnd_extra_width();
+ minheight -= wnd_extra_height();
+
+ return osd_dim(minwidth, minheight);
+}
+
+//============================================================
+// get_size
+//============================================================
+
+osd_dim sdl_window_info::get_size()
+{
+ int w=0; int h=0;
+ SDL_GetWindowSize(platform_window(), &w, &h);
+ return osd_dim(w,h);
+}
+
+
+//============================================================
+// get_max_bounds
+// (window thread)
+//============================================================
+
+osd_dim sdl_window_info::get_max_bounds(int constrain)
+{
+ //assert(GetCurrentThreadId() == window_threadid);
+
+ // compute the maximum client area
+ // monitor()->refresh();
+ osd_rect maximum = monitor()->usuable_position_size();
+
+ // clamp to the window's max
+ int tempw = maximum.width();
+ int temph = maximum.height();
+ if (m_win_config.width != 0)
+ {
+ int temp = m_win_config.width + wnd_extra_width();
+ if (temp < maximum.width())
+ tempw = temp;
+ }
+ if (m_win_config.height != 0)
+ {
+ int temp = m_win_config.height + wnd_extra_height();
+ if (temp < maximum.height())
+ temph = temp;
+ }
+
+ maximum = maximum.resize(tempw, temph);
+
+ // constrain to fit
+ if (constrain)
+ maximum = constrain_to_aspect_ratio(maximum, WMSZ_BOTTOMRIGHT);
+
+ // remove extra window stuff
+ maximum = maximum.resize(maximum.width() - wnd_extra_width(), maximum.height() - wnd_extra_height());
+
+ return maximum.dim();
+}
+
+
+std::vector<sdl_window_info::sdl_pointer_info>::iterator sdl_window_info::map_pointer(SDL_FingerID finger, unsigned device)
+{
+ auto found(std::lower_bound(m_active_pointers.begin(), m_active_pointers.end(), finger, &sdl_pointer_info::compare));
+ if ((m_active_pointers.end() != found) && (found->finger == finger))
+ return found;
+
+ if ((sizeof(m_next_pointer) * 8) <= m_next_pointer)
+ {
+ assert(~decltype(m_pointer_mask)(0) == m_pointer_mask);
+ osd_printf_warning("sdl_window_info: exceeded maximum number of active pointers\n");
+ return m_active_pointers.end();
+ }
+ assert(!BIT(m_pointer_mask, m_next_pointer));
+
+ try
+ {
+ found = m_active_pointers.emplace(
+ found,
+ sdl_pointer_info(finger, m_next_pointer, device));
+ m_pointer_mask |= decltype(m_pointer_mask)(1) << m_next_pointer;
+ do
+ {
+ ++m_next_pointer;
+ }
+ while (((sizeof(m_next_pointer) * 8) > m_next_pointer) && BIT(m_pointer_mask, m_next_pointer));
+
+ return found;
+ }
+ catch (std::bad_alloc const &)
+ {
+ osd_printf_error("sdl_window_info: error allocating pointer data\n");
+ return m_active_pointers.end();
+ }
+}
+
+
+inline sdl_window_info::sdl_pointer_info::sdl_pointer_info(SDL_FingerID f, unsigned i, unsigned d)
+ : pointer_info(i, d)
+ , finger(f)
+{
+}
+
+
+
+
+//============================================================
+// construction and destruction
+//============================================================
+
+sdl_window_info::sdl_window_info(
+ running_machine &a_machine,
+ render_module &renderprovider,
+ int index,
+ const std::shared_ptr<osd_monitor_info> &a_monitor,
+ const osd_window_config *config)
+ : osd_window_t(a_machine, renderprovider, index, std::move(a_monitor), *config)
+ , m_startmaximized(0)
+ // Following three are used by input code to defer resizes
+ , m_minimum_dim(0, 0)
+ , m_windowed_dim(0, 0)
+ , m_rendered_event(0, 1)
+ , m_extra_flags(0)
+ , m_mouse_captured(false)
+ , m_mouse_hidden(false)
+ , m_pointer_mask(0)
+ , m_next_pointer(0)
+ , m_mouse_inside(false)
+{
+ //FIXME: these should be per_window in config-> or even better a bit set
+ m_fullscreen = !video_config.windowed;
+ m_prescale = video_config.prescale;
+
+ m_windowed_dim = osd_dim(config->width, config->height);
+
+ m_ptrdev_info.reserve(1);
+ m_active_pointers.reserve(16);
+}
+
+sdl_window_info::~sdl_window_info()
+{
+}
+
+
+//============================================================
+// osd_set_aggressive_input_focus
+//============================================================
+
+void osd_set_aggressive_input_focus(bool aggressive_focus)
+{
+ // dummy implementation for now
+}
diff --git a/src/osd/sdl3/window.h b/src/osd/sdl3/window.h
new file mode 100644
index 00000000000..1d812c48f8e
--- /dev/null
+++ b/src/osd/sdl3/window.h
@@ -0,0 +1,134 @@
+// license:BSD-3-Clause
+// copyright-holders:Olivier Galibert, R. Belmont
+//============================================================
+//
+// window.h - SDL window handling
+//
+// SDLMAME by Olivier Galibert and R. Belmont
+//
+//============================================================
+
+#ifndef MAME_OSD_SDL_WINDOW_H
+#define MAME_OSD_SDL_WINDOW_H
+
+#include "modules/osdwindow.h"
+#include "osdsync.h"
+
+#include <SDL3/SDL.h>
+
+#include <chrono>
+#include <cstdint>
+#include <memory>
+#include <vector>
+
+
+//============================================================
+// TYPE DEFINITIONS
+//============================================================
+
+class render_target;
+
+typedef uintptr_t HashT;
+
+#define OSDWORK_CALLBACK(name) void *name(void *param, int threadid)
+
+class sdl_window_info : public osd_window_t<SDL_Window*>
+{
+public:
+ sdl_window_info(
+ running_machine &a_machine,
+ render_module &renderprovider,
+ int index,
+ const std::shared_ptr<osd_monitor_info> &a_monitor,
+ const osd_window_config *config);
+
+ ~sdl_window_info();
+
+ int window_init();
+
+ void update() override;
+ void toggle_full_screen();
+ void modify_prescale(int dir);
+ void resize(int32_t width, int32_t height);
+ void complete_destroy() override;
+
+ void capture_pointer() override;
+ void release_pointer() override;
+ void show_pointer() override;
+ void hide_pointer() override;
+
+ void notify_changed();
+
+ osd_dim get_size() override;
+
+ int xy_to_render_target(int x, int y, int *xt, int *yt);
+
+ void mouse_entered(unsigned device);
+ void mouse_left(unsigned device);
+ void mouse_down(unsigned device, int x, int y, unsigned button);
+ void mouse_up(unsigned device, int x, int y, unsigned button);
+ void mouse_moved(unsigned device, int x, int y);
+ void mouse_wheel(unsigned device, int y);
+ void finger_down(SDL_FingerID finger, unsigned device, int x, int y);
+ void finger_up(SDL_FingerID finger, unsigned device, int x, int y);
+ void finger_moved(SDL_FingerID finger, unsigned device, int x, int y);
+
+private:
+ struct sdl_pointer_info : public pointer_info
+ {
+ static constexpr bool compare(sdl_pointer_info const &info, SDL_FingerID finger) { return info.finger < finger; }
+
+ sdl_pointer_info(sdl_pointer_info const &) = default;
+ sdl_pointer_info(sdl_pointer_info &&) = default;
+ sdl_pointer_info &operator=(sdl_pointer_info const &) = default;
+ sdl_pointer_info &operator=(sdl_pointer_info &&) = default;
+
+ sdl_pointer_info(SDL_FingerID, unsigned i, unsigned d);
+
+ SDL_FingerID finger;
+ };
+
+ // returns 0 on success, else 1
+ int complete_create();
+
+ int wnd_extra_width();
+ int wnd_extra_height();
+ osd_rect constrain_to_aspect_ratio(const osd_rect &rect, int adjustment);
+ osd_dim get_min_bounds(int constrain);
+ osd_dim get_max_bounds(int constrain);
+ void update_cursor_state();
+ osd_dim pick_best_mode();
+ void set_fullscreen(int afullscreen) { m_fullscreen = afullscreen; }
+
+ void measure_fps(int update);
+
+ std::vector<sdl_pointer_info>::iterator map_pointer(SDL_FingerID finger, unsigned device);
+
+ // window handle and info
+ int m_startmaximized;
+
+ // dimensions
+ osd_dim m_minimum_dim;
+ osd_dim m_windowed_dim;
+
+ // rendering info
+ osd_event m_rendered_event;
+
+ // Original display_mode
+ SDL_DisplayMode m_original_mode;
+
+ int m_extra_flags;
+
+ // monitor info
+ bool m_mouse_captured;
+ bool m_mouse_hidden;
+
+ // info on currently active pointers - 64 pointers ought to be enough for anyone
+ uint64_t m_pointer_mask;
+ unsigned m_next_pointer;
+ bool m_mouse_inside;
+ std::vector<pointer_dev_info> m_ptrdev_info;
+ std::vector<sdl_pointer_info> m_active_pointers;
+};
+
+#endif // MAME_OSD_SDL_WINDOW_H
diff --git a/src/tools/imgtool/imgtool.cpp b/src/tools/imgtool/imgtool.cpp
index 43c4d8109c6..3c72500b1d0 100644
--- a/src/tools/imgtool/imgtool.cpp
+++ b/src/tools/imgtool/imgtool.cpp
@@ -244,7 +244,7 @@ void imgtool_warn(const char *format, ...)
if (global_warn)
{
va_start(va, format);
- vsprintf(buffer, format, va);
+ vsnprintf(buffer, 2000, format, va);
va_end(va);
global_warn(buffer);
}
@@ -1703,11 +1703,12 @@ imgtoolerr_t imgtool::partition::get_file(const char *filename, const char *fork
if (filter_extension != nullptr)
{
- alloc_dest = (char*)malloc(strlen(filename) + 1 + strlen(filter_extension) + 1);
+ const size_t length = strlen(filename) + 1 + strlen(filter_extension) + 1;
+ alloc_dest = (char *)malloc(length);
if (!alloc_dest)
return IMGTOOLERR_OUTOFMEMORY;
- sprintf(alloc_dest, "%s.%s", filename, filter_extension);
+ snprintf(alloc_dest, length, "%s.%s", filename, filter_extension);
dest = alloc_dest;
}
else
diff --git a/src/tools/testkeys.cpp b/src/tools/testkeys.cpp
index 112f04bf630..c0f39aed09d 100644
--- a/src/tools/testkeys.cpp
+++ b/src/tools/testkeys.cpp
@@ -9,16 +9,45 @@
//
//============================================================
+#ifdef SDLMAME_SDL3
+#include <SDL3/SDL.h>
+#include <SDL3/SDL_main.h>
+#endif
+
#include "osdcore.h"
+#ifndef SDLMAME_SDL3
#include "SDL2/SDL.h"
+#endif
#include <iostream>
#include <string>
//#include "unicode.h"
+#ifdef SDLMAME_SDL3
+#if defined(SDL_PLATFORM_WINDOWS)
+#ifndef WINAPI
+ #define WINAPI __stdcall
+#endif
+// TODO: Why is this is necessary here but not for MAME itself?
+typedef struct HINSTANCE__ * HINSTANCE;
+typedef char *LPSTR;
+typedef wchar_t *PWSTR;
+
+extern "C" {
+ int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPSTR szCmdLine, int sw)
+ {
+ (void)hInst;
+ (void)hPrev;
+ (void)szCmdLine;
+ (void)sw;
+ return SDL_RunApp(0, NULL, SDL_main, NULL);
+ }
+} /* extern "C" */
+#endif
+#endif
struct key_lookup_table { int code; const char *name; };
#define KE(x) { SDL_SCANCODE_##x, "SDL_SCANCODE_" #x },
@@ -253,6 +282,14 @@ static constexpr key_lookup_table sdl_lookup[] =
KE(RGUI)
KE(MODE)
+#ifdef SDLMAME_SDL3
+ KE(MEDIA_NEXT_TRACK)
+ KE(MEDIA_PREVIOUS_TRACK)
+ KE(MEDIA_STOP)
+ KE(MEDIA_PLAY)
+ KE(MUTE)
+ KE(MEDIA_SELECT)
+#else
KE(AUDIONEXT)
KE(AUDIOPREV)
KE(AUDIOSTOP)
@@ -263,6 +300,7 @@ static constexpr key_lookup_table sdl_lookup[] =
KE(MAIL)
KE(CALCULATOR)
KE(COMPUTER)
+#endif
KE(AC_SEARCH)
KE(AC_HOME)
KE(AC_BACK)
@@ -271,7 +309,11 @@ static constexpr key_lookup_table sdl_lookup[] =
KE(AC_REFRESH)
KE(AC_BOOKMARKS)
- KE(BRIGHTNESSDOWN)
+#ifdef SDLMAME_SDL3
+ KE(MEDIA_EJECT)
+ KE(SLEEP)
+#else
+
KE(BRIGHTNESSUP)
KE(DISPLAYSWITCH)
KE(KBDILLUMTOGGLE)
@@ -282,6 +324,7 @@ static constexpr key_lookup_table sdl_lookup[] =
KE(APP1)
KE(APP2)
+#endif
};
static char const *lookup_key_name(int kc)
@@ -296,49 +339,79 @@ static char const *lookup_key_name(int kc)
int main(int argc, char *argv[])
{
- if (SDL_Init(SDL_INIT_VIDEO) < 0) {
+#ifdef SDLMAME_SDL3
+ if (!SDL_Init(SDL_INIT_VIDEO))
+#else
+ if (SDL_Init(SDL_INIT_VIDEO) < 0)
+#endif
+ {
fprintf(stderr, "Couldn't initialize SDL: %s\n", SDL_GetError());
exit(1);
}
+#ifdef SDLMAME_SDL3
+ SDL_CreateWindow("Input Test", 100, 100, 0);
+#else
SDL_CreateWindow("Input Test", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 100, 100, 0);
+#endif
SDL_Event event;
bool quit = false;
std::string lasttext;
while (SDL_PollEvent(&event) || !quit) {
switch(event.type) {
+#ifdef SDLMAME_SDL3
+ case SDL_EVENT_QUIT:
+#else
case SDL_QUIT:
+#endif
quit = true;
break;
+#ifdef SDLMAME_SDL3
+ case SDL_EVENT_KEY_DOWN:
+ if (event.key.scancode == SDLK_ESCAPE) {
+#else
case SDL_KEYDOWN:
if (event.key.keysym.sym == SDLK_ESCAPE) {
+#endif
quit = true;
} else {
std::cout
<< "ITEM_ID_XY "
+#ifdef SDLMAME_SDL3
+ << lookup_key_name(event.key.scancode)
+#else
<< lookup_key_name(event.key.keysym.scancode)
+#endif
<< ' '
<< std::endl;
lasttext.clear();
}
break;
+#ifdef SDLMAME_SDL3
+ case SDL_EVENT_KEY_UP:
+#else
case SDL_KEYUP:
+#endif
std::cout
<< "ITEM_ID_XY "
+#ifdef SDLMAME_SDL3
+ << lookup_key_name(event.key.scancode)
+#else
<< lookup_key_name(event.key.keysym.scancode)
+#endif
<< ' '
<< lasttext
<< std::endl;
break;
+#ifdef SDLMAME_SDL3
+ case SDL_EVENT_TEXT_INPUT:
+#else
case SDL_TEXTINPUT:
+#endif
lasttext = event.text.text;
break;
}
event.type = 0;
-
-#ifdef SDLMAME_OS2
- SDL_Delay(10);
-#endif
}
SDL_Quit();
return(0);