diff options
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 @@ -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); |
