mirror of
https://github.com/RPCS3/rpcs3.git
synced 2026-05-07 13:37:46 +00:00
Merge branch 'master' into windows-clang
This commit is contained in:
commit
f8453e7758
123 changed files with 4196 additions and 1925 deletions
|
|
@ -6,7 +6,7 @@ export HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=1
|
|||
export HOMEBREW_NO_ENV_HINTS=1
|
||||
export HOMEBREW_NO_INSTALL_CLEANUP=1
|
||||
|
||||
brew install -f --overwrite --quiet googletest opencv@4 ffmpeg@5 "llvm@$LLVM_COMPILER_VER" glew sdl3 vulkan-headers vulkan-loader
|
||||
brew install -f --overwrite --quiet googletest opencv@4 ffmpeg@5 "llvm@$LLVM_COMPILER_VER" sdl3 vulkan-headers vulkan-loader
|
||||
brew unlink --quiet ffmpeg qtbase qtsvg qtdeclarative
|
||||
|
||||
brew link -f --quiet "llvm@$LLVM_COMPILER_VER" ffmpeg@5
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ brew install -f --overwrite --quiet ccache "llvm@$LLVM_COMPILER_VER"
|
|||
brew link -f --overwrite --quiet "llvm@$LLVM_COMPILER_VER"
|
||||
# shellcheck disable=SC3009
|
||||
arch -x86_64 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
|
||||
arch -x86_64 /usr/local/bin/brew install -f --overwrite --quiet python@3.14 opencv@4 ffmpeg@5 "llvm@$LLVM_COMPILER_VER" glew sdl3 vulkan-headers vulkan-loader
|
||||
arch -x86_64 /usr/local/bin/brew install -f --overwrite --quiet python@3.14 opencv@4 ffmpeg@5 "llvm@$LLVM_COMPILER_VER" sdl3 vulkan-headers vulkan-loader
|
||||
arch -x86_64 /usr/local/bin/brew unlink --quiet ffmpeg qtbase qtsvg qtdeclarative
|
||||
arch -x86_64 /usr/local/bin/brew link -f --overwrite --quiet "llvm@$LLVM_COMPILER_VER" ffmpeg@5
|
||||
|
||||
|
|
|
|||
|
|
@ -57,6 +57,10 @@ else
|
|||
rm -f translations.zip
|
||||
fi
|
||||
|
||||
# Copy Qt translations manually
|
||||
QT_TRANS="$WORKDIR/qt-downloader/$QT_VER/clang_64/translations"
|
||||
cp $QT_TRANS/qt*.qm rpcs3.app/Contents/translations
|
||||
|
||||
# Hack
|
||||
install_name_tool -delete_rpath /opt/homebrew/lib RPCS3.app/Contents/MacOS/rpcs3 || echo "Hack for deleting rpath /opt/homebrew/lib not needed"
|
||||
install_name_tool -delete_rpath /opt/homebrew/opt/llvm@$LLVM_COMPILER_VER/lib RPCS3.app/Contents/MacOS/rpcs3 || echo "Hack for deleting rpath /opt/homebrew/opt/llvm@$LLVM_COMPILER_VER/lib not needed"
|
||||
|
|
|
|||
|
|
@ -58,6 +58,10 @@ else
|
|||
rm -f translations.zip
|
||||
fi
|
||||
|
||||
# Copy Qt translations manually
|
||||
QT_TRANS="$WORKDIR/qt-downloader/$QT_VER/clang_64/translations"
|
||||
cp $QT_TRANS/qt*.qm rpcs3.app/Contents/translations
|
||||
|
||||
# Need to do this rename hack due to case insensitive filesystem
|
||||
mv rpcs3.app RPCS3_.app
|
||||
mv RPCS3_.app RPCS3.app
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ else
|
|||
echo "Failed to download translations.zip. Continuing without translations."
|
||||
exit 0
|
||||
}
|
||||
unzip -o translations.zip -d "./bin/share/qt6/translations" >/dev/null 2>&1 || \
|
||||
7z x translations.zip -o"./bin/share/qt6/translations" >/dev/null 2>&1 || \
|
||||
echo "Failed to extract translations.zip. Continuing without translations."
|
||||
rm -f translations.zip
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ QT_DECL_URL="${QT_HOST}${QT_PREFIX}${QT_PREFIX_2}qtdeclarative${QT_SUFFIX}"
|
|||
QT_TOOL_URL="${QT_HOST}${QT_PREFIX}${QT_PREFIX_2}qttools${QT_SUFFIX}"
|
||||
QT_MM_URL="${QT_HOST}${QT_PREFIX}addons.qtmultimedia.${QT_PREFIX_2}qtmultimedia${QT_SUFFIX}"
|
||||
QT_SVG_URL="${QT_HOST}${QT_PREFIX}${QT_PREFIX_2}qtsvg${QT_SUFFIX}"
|
||||
QT_TRANSLATIONS_URL="${QT_HOST}${QT_PREFIX}${QT_PREFIX_2}qttranslations${QT_SUFFIX}"
|
||||
LLVMLIBS_URL="https://github.com/RPCS3/llvm-mirror/releases/download/custom-build-win-${LLVM_VER}/llvmlibs_mt.7z"
|
||||
VULKAN_SDK_URL="https://www.dropbox.com/scl/fi/sjjh0fc4ld281pjbl2xzu/VulkanSDK-${VULKAN_VER}-Installer.exe?rlkey=f6wzc0lvms5vwkt2z3qabfv9d&dl=1"
|
||||
CCACHE_URL="https://github.com/ccache/ccache/releases/download/v4.11.2/ccache-4.11.2-windows-x86_64.zip"
|
||||
|
|
@ -24,6 +25,7 @@ DEP_URLS=" \
|
|||
$QT_TOOL_URL \
|
||||
$QT_MM_URL \
|
||||
$QT_SVG_URL \
|
||||
$QT_TRANSLATIONS_URL \
|
||||
$LLVMLIBS_URL \
|
||||
$VULKAN_SDK_URL\
|
||||
$CCACHE_URL"
|
||||
|
|
|
|||
10
.github/workflows/rpcs3.yml
vendored
10
.github/workflows/rpcs3.yml
vendored
|
|
@ -528,7 +528,11 @@ jobs:
|
|||
env:
|
||||
CCACHE_DIR: ${{ github.workspace }}/ccache
|
||||
QT_VER_MAIN: '6'
|
||||
LLVM_COMPILER_VER: '19'
|
||||
LLVM_COMPILER_VER: '-devel'
|
||||
CC: 'clang-devel'
|
||||
CXX: 'clang++-devel'
|
||||
LLVM_CONFIG: 'llvm-config-devel'
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@main
|
||||
|
|
@ -547,8 +551,10 @@ jobs:
|
|||
id: root
|
||||
uses: vmactions/freebsd-vm@v1
|
||||
with:
|
||||
envs: 'QT_VER_MAIN LLVM_COMPILER_VER CCACHE_DIR'
|
||||
envs: 'QT_VER_MAIN LLVM_COMPILER_VER CCACHE_DIR CC CXX LLVM_CONFIG'
|
||||
usesh: true
|
||||
copyback: false
|
||||
release: "14.3"
|
||||
run: .ci/install-freebsd.sh && .ci/build-freebsd.sh
|
||||
|
||||
- name: Save Build Ccache
|
||||
|
|
|
|||
6
3rdparty/CMakeLists.txt
vendored
6
3rdparty/CMakeLists.txt
vendored
|
|
@ -107,7 +107,7 @@ add_subdirectory(yaml-cpp)
|
|||
|
||||
# OpenGL
|
||||
|
||||
if (NOT ANDROID)
|
||||
if (NOT ANDROID AND NOT APPLE)
|
||||
find_package(OpenGL REQUIRED OPTIONAL_COMPONENTS EGL)
|
||||
|
||||
add_library(3rdparty_opengl INTERFACE)
|
||||
|
|
@ -119,8 +119,6 @@ if (NOT ANDROID)
|
|||
else()
|
||||
target_link_libraries(3rdparty_opengl INTERFACE dxgi.lib d2d1.lib dwrite.lib)
|
||||
endif()
|
||||
elseif(APPLE)
|
||||
target_link_libraries(3rdparty_opengl INTERFACE OpenGL::GL OpenGL::GLU)
|
||||
else()
|
||||
target_link_libraries(3rdparty_opengl INTERFACE OpenGL::GL OpenGL::GLU OpenGL::GLX)
|
||||
endif()
|
||||
|
|
@ -335,7 +333,7 @@ endif()
|
|||
|
||||
# GLEW
|
||||
add_library(3rdparty_glew INTERFACE)
|
||||
if(NOT MSVC AND NOT ANDROID)
|
||||
if(NOT MSVC AND NOT ANDROID AND NOT APPLE)
|
||||
find_package(GLEW REQUIRED)
|
||||
target_link_libraries(3rdparty_glew INTERFACE GLEW::GLEW)
|
||||
endif()
|
||||
|
|
|
|||
2
3rdparty/OpenAL/openal-soft
vendored
2
3rdparty/OpenAL/openal-soft
vendored
|
|
@ -1 +1 @@
|
|||
Subproject commit 0e5e98e4ac8adae92e4f7653dd6eee17aa9c8791
|
||||
Subproject commit 75c00596307bf05ba7bbc8c7022836bf52f17477
|
||||
2
3rdparty/libpng/libpng
vendored
2
3rdparty/libpng/libpng
vendored
|
|
@ -1 +1 @@
|
|||
Subproject commit 49363adcfaf098748d7a4c8c624ad8c45a8c3a86
|
||||
Subproject commit 4e3f57d50f552841550a36eabbb3fbcecacb7750
|
||||
2
3rdparty/libsdl-org/SDL
vendored
2
3rdparty/libsdl-org/SDL
vendored
|
|
@ -1 +1 @@
|
|||
Subproject commit 7f3ae3d57459e59943a4ecfefc8f6277ec6bf540
|
||||
Subproject commit a962f40bbba175e9716557a25d5d7965f134a3d3
|
||||
36
3rdparty/libsdl-org/SDL.vcxproj
vendored
36
3rdparty/libsdl-org/SDL.vcxproj
vendored
|
|
@ -23,6 +23,7 @@
|
|||
<ClInclude Include="SDL\include\SDL3\SDL_clipboard.h" />
|
||||
<ClInclude Include="SDL\include\SDL3\SDL_copying.h" />
|
||||
<ClInclude Include="SDL\include\SDL3\SDL_cpuinfo.h" />
|
||||
<ClInclude Include="SDL\include\SDL3\SDL_dlopennote.h" />
|
||||
<ClInclude Include="SDL\include\SDL3\SDL_egl.h" />
|
||||
<ClInclude Include="SDL\include\SDL3\SDL_endian.h" />
|
||||
<ClInclude Include="SDL\include\SDL3\SDL_error.h" />
|
||||
|
|
@ -102,6 +103,7 @@
|
|||
<ClInclude Include="SDL\src\audio\wasapi\SDL_wasapi.h" />
|
||||
<ClInclude Include="SDL\src\camera\SDL_camera_c.h" />
|
||||
<ClInclude Include="SDL\src\camera\SDL_syscamera.h" />
|
||||
<ClInclude Include="SDL\src\core\SDL_core_unsupported.h" />
|
||||
<ClInclude Include="SDL\src\core\windows\SDL_directx.h" />
|
||||
<ClInclude Include="SDL\src\core\windows\SDL_gameinput.h" />
|
||||
<ClInclude Include="SDL\src\core\windows\SDL_hid.h" />
|
||||
|
|
@ -130,6 +132,8 @@
|
|||
<ClInclude Include="SDL\src\filesystem\SDL_sysfilesystem.h" />
|
||||
<ClInclude Include="SDL\src\gpu\SDL_sysgpu.h" />
|
||||
<ClInclude Include="SDL\src\gpu\vulkan\SDL_gpu_vulkan_vkfuncs.h" />
|
||||
<ClInclude Include="SDL\src\haptic\hidapi\SDL_hidapihaptic.h" />
|
||||
<ClInclude Include="SDL\src\haptic\hidapi\SDL_hidapihaptic_c.h" />
|
||||
<ClInclude Include="SDL\src\io\SDL_asyncio_c.h" />
|
||||
<ClInclude Include="SDL\src\io\SDL_sysasyncio.h" />
|
||||
<ClInclude Include="SDL\src\haptic\SDL_haptic_c.h" />
|
||||
|
|
@ -140,7 +144,11 @@
|
|||
<ClInclude Include="SDL\src\hidapi\SDL_hidapi_c.h" />
|
||||
<ClInclude Include="SDL\src\joystick\controller_type.h" />
|
||||
<ClInclude Include="SDL\src\joystick\hidapi\SDL_hidapijoystick_c.h" />
|
||||
<ClInclude Include="SDL\src\joystick\hidapi\SDL_hidapi_flydigi.h" />
|
||||
<ClInclude Include="SDL\src\joystick\hidapi\SDL_hidapi_nintendo.h" />
|
||||
<ClInclude Include="SDL\src\joystick\hidapi\SDL_hidapi_rumble.h" />
|
||||
<ClInclude Include="SDL\src\joystick\hidapi\SDL_hidapi_sinput.h" />
|
||||
<ClInclude Include="SDL\src\joystick\hidapi\SDL_report_descriptor.h" />
|
||||
<ClInclude Include="SDL\src\joystick\SDL_gamepad_c.h" />
|
||||
<ClInclude Include="SDL\src\joystick\SDL_gamepad_db.h" />
|
||||
<ClInclude Include="SDL\src\joystick\SDL_joystick_c.h" />
|
||||
|
|
@ -156,6 +164,7 @@
|
|||
<ClInclude Include="SDL\src\libm\math_private.h" />
|
||||
<ClInclude Include="SDL\src\locale\SDL_syslocale.h" />
|
||||
<ClInclude Include="SDL\src\main\SDL_main_callbacks.h" />
|
||||
<ClInclude Include="SDL\src\misc\SDL_libusb.h" />
|
||||
<ClInclude Include="SDL\src\misc\SDL_sysurl.h" />
|
||||
<ClInclude Include="SDL\src\power\SDL_syspower.h" />
|
||||
<ClInclude Include="SDL\src\render\direct3d11\SDL_shaders_d3d11.h" />
|
||||
|
|
@ -175,7 +184,6 @@
|
|||
<ClInclude Include="SDL\src\render\software\SDL_drawline.h" />
|
||||
<ClInclude Include="SDL\src\render\software\SDL_drawpoint.h" />
|
||||
<ClInclude Include="SDL\src\render\software\SDL_render_sw_c.h" />
|
||||
<ClInclude Include="SDL\src\render\software\SDL_rotate.h" />
|
||||
<ClInclude Include="SDL\src\render\software\SDL_triangle.h" />
|
||||
<ClInclude Include="SDL\src\render\vulkan\SDL_shaders_vulkan.h" />
|
||||
<ClInclude Include="SDL\src\SDL_assert_c.h" />
|
||||
|
|
@ -184,20 +192,35 @@
|
|||
<ClCompile Include="SDL\src\camera\dummy\SDL_camera_dummy.c" />
|
||||
<ClCompile Include="SDL\src\camera\mediafoundation\SDL_camera_mediafoundation.c" />
|
||||
<ClCompile Include="SDL\src\camera\SDL_camera.c" />
|
||||
<ClCompile Include="SDL\src\core\windows\pch_cpp.cpp" />
|
||||
<ClCompile Include="SDL\src\core\windows\SDL_gameinput.cpp" />
|
||||
<ClCompile Include="SDL\src\dialog\SDL_dialog.c" />
|
||||
<ClCompile Include="SDL\src\dialog\SDL_dialog_utils.c" />
|
||||
<ClCompile Include="SDL\src\filesystem\SDL_filesystem.c" />
|
||||
<ClCompile Include="SDL\src\filesystem\windows\SDL_sysfsops.c" />
|
||||
<ClCompile Include="SDL\src\haptic\hidapi\SDL_hidapihaptic.c" />
|
||||
<ClCompile Include="SDL\src\haptic\hidapi\SDL_hidapihaptic_lg4ff.c" />
|
||||
<ClCompile Include="SDL\src\io\windows\SDL_asyncio_windows_ioring.c" />
|
||||
<ClCompile Include="SDL\src\gpu\SDL_gpu.c" />
|
||||
<ClCompile Include="SDL\src\gpu\d3d12\SDL_gpu_d3d12.c" />
|
||||
<ClCompile Include="SDL\src\gpu\vulkan\SDL_gpu_vulkan.c" />
|
||||
<ClCompile Include="SDL\src\io\generic\SDL_asyncio_generic.c" />
|
||||
<ClCompile Include="SDL\src\io\SDL_asyncio.c" />
|
||||
<ClCompile Include="SDL\src\joystick\gdk\SDL_gameinputjoystick.cpp" />
|
||||
<ClCompile Include="SDL\src\joystick\hidapi\SDL_hidapi_8bitdo.c" />
|
||||
<ClCompile Include="SDL\src\joystick\hidapi\SDL_hidapi_flydigi.c" />
|
||||
<ClCompile Include="SDL\src\joystick\hidapi\SDL_hidapi_gip.c" />
|
||||
<ClCompile Include="SDL\src\joystick\hidapi\SDL_hidapi_lg4ff.c" />
|
||||
<ClCompile Include="SDL\src\joystick\hidapi\SDL_hidapi_sinput.c" />
|
||||
<ClCompile Include="SDL\src\joystick\hidapi\SDL_hidapi_steam_triton.c" />
|
||||
<ClCompile Include="SDL\src\joystick\hidapi\SDL_hidapi_switch2.c" />
|
||||
<ClCompile Include="SDL\src\joystick\hidapi\SDL_hidapi_zuiki.c" />
|
||||
<ClCompile Include="SDL\src\joystick\hidapi\SDL_report_descriptor.c" />
|
||||
<ClCompile Include="SDL\src\main\generic\SDL_sysmain_callbacks.c" />
|
||||
<ClCompile Include="SDL\src\main\SDL_main_callbacks.c" />
|
||||
<ClCompile Include="SDL\src\main\SDL_runapp.c" />
|
||||
<ClCompile Include="SDL\src\main\windows\SDL_sysmain_runapp.c" />
|
||||
<ClCompile Include="SDL\src\misc\SDL_libusb.c" />
|
||||
<ClCompile Include="SDL\src\render\vulkan\SDL_render_vulkan.c" />
|
||||
<ClCompile Include="SDL\src\render\vulkan\SDL_shaders_vulkan.c" />
|
||||
<ClCompile Include="SDL\src\SDL_guid.c" />
|
||||
|
|
@ -241,6 +264,7 @@
|
|||
<ClInclude Include="SDL\src\video\khronos\vulkan\vulkan_xcb.h" />
|
||||
<ClInclude Include="SDL\src\video\khronos\vulkan\vulkan_xlib.h" />
|
||||
<ClInclude Include="SDL\src\video\khronos\vulkan\vulkan_xlib_xrandr.h" />
|
||||
<ClInclude Include="SDL\src\video\miniz.h" />
|
||||
<ClInclude Include="SDL\src\video\offscreen\SDL_offscreenevents_c.h" />
|
||||
<ClInclude Include="SDL\src\video\offscreen\SDL_offscreenframebuffer_c.h" />
|
||||
<ClInclude Include="SDL\src\video\offscreen\SDL_offscreenopengles.h" />
|
||||
|
|
@ -256,13 +280,14 @@
|
|||
<ClInclude Include="SDL\src\video\SDL_pixels_c.h" />
|
||||
<ClInclude Include="SDL\src\video\SDL_rect_c.h" />
|
||||
<ClInclude Include="SDL\src\video\SDL_RLEaccel_c.h" />
|
||||
<ClInclude Include="SDL\src\video\SDL_rotate.h" />
|
||||
<ClInclude Include="SDL\src\video\SDL_stb_c.h" />
|
||||
<ClInclude Include="SDL\src\video\SDL_surface_c.h" />
|
||||
<ClInclude Include="SDL\src\video\SDL_sysvideo.h" />
|
||||
<ClInclude Include="SDL\src\video\SDL_video_unsupported.h" />
|
||||
<ClInclude Include="SDL\src\video\SDL_vulkan_internal.h" />
|
||||
<ClInclude Include="SDL\src\video\SDL_yuv_c.h" />
|
||||
<ClInclude Include="SDL\src\video\windows\SDL_msctf.h" />
|
||||
<ClInclude Include="SDL\src\video\windows\SDL_surface_utils.h" />
|
||||
<ClInclude Include="SDL\src\video\windows\SDL_windowsclipboard.h" />
|
||||
<ClInclude Include="SDL\src\video\windows\SDL_windowsevents.h" />
|
||||
<ClInclude Include="SDL\src\video\windows\SDL_windowsframebuffer.h" />
|
||||
|
|
@ -303,7 +328,6 @@
|
|||
<ClCompile Include="SDL\src\audio\SDL_wave.c" />
|
||||
<ClCompile Include="SDL\src\audio\wasapi\SDL_wasapi.c" />
|
||||
<ClCompile Include="SDL\src\core\SDL_core_unsupported.c" />
|
||||
<ClCompile Include="SDL\src\core\windows\SDL_gameinput.c" />
|
||||
<ClCompile Include="SDL\src\core\windows\SDL_hid.c" />
|
||||
<ClCompile Include="SDL\src\core\windows\SDL_immdevice.c" />
|
||||
<ClCompile Include="SDL\src\core\windows\SDL_windows.c" />
|
||||
|
|
@ -333,7 +357,6 @@
|
|||
<ClCompile Include="SDL\src\hidapi\SDL_hidapi.c" />
|
||||
<ClCompile Include="SDL\src\joystick\controller_type.c" />
|
||||
<ClCompile Include="SDL\src\joystick\dummy\SDL_sysjoystick.c" />
|
||||
<ClCompile Include="SDL\src\joystick\gdk\SDL_gameinputjoystick.c" />
|
||||
<ClCompile Include="SDL\src\joystick\hidapi\SDL_hidapijoystick.c" />
|
||||
<ClCompile Include="SDL\src\joystick\hidapi\SDL_hidapi_combined.c" />
|
||||
<ClCompile Include="SDL\src\joystick\hidapi\SDL_hidapi_gamecube.c" />
|
||||
|
|
@ -393,7 +416,6 @@
|
|||
<ClCompile Include="SDL\src\render\software\SDL_drawline.c" />
|
||||
<ClCompile Include="SDL\src\render\software\SDL_drawpoint.c" />
|
||||
<ClCompile Include="SDL\src\render\software\SDL_render_sw.c" />
|
||||
<ClCompile Include="SDL\src\render\software\SDL_rotate.c" />
|
||||
<ClCompile Include="SDL\src\render\software\SDL_triangle.c" />
|
||||
<ClCompile Include="SDL\src\SDL.c" />
|
||||
<ClCompile Include="SDL\src\SDL_assert.c" />
|
||||
|
|
@ -464,6 +486,7 @@
|
|||
<ClCompile Include="SDL\src\video\SDL_pixels.c" />
|
||||
<ClCompile Include="SDL\src\video\SDL_rect.c" />
|
||||
<ClCompile Include="SDL\src\video\SDL_RLEaccel.c" />
|
||||
<ClCompile Include="SDL\src\video\SDL_rotate.c" />
|
||||
<ClCompile Include="SDL\src\video\SDL_stb.c" />
|
||||
<ClCompile Include="SDL\src\video\SDL_stretch.c" />
|
||||
<ClCompile Include="SDL\src\video\SDL_surface.c" />
|
||||
|
|
@ -471,12 +494,11 @@
|
|||
<ClCompile Include="SDL\src\video\SDL_video_unsupported.c" />
|
||||
<ClCompile Include="SDL\src\video\SDL_vulkan_utils.c" />
|
||||
<ClCompile Include="SDL\src\video\SDL_yuv.c" />
|
||||
<ClCompile Include="SDL\src\video\windows\SDL_surface_utils.c" />
|
||||
<ClCompile Include="SDL\src\video\windows\SDL_windowsclipboard.c" />
|
||||
<ClCompile Include="SDL\src\video\windows\SDL_windowsevents.c" />
|
||||
<ClCompile Include="SDL\src\video\windows\SDL_windowsframebuffer.c" />
|
||||
<ClCompile Include="SDL\src\video\windows\SDL_windowsgameinput.cpp" />
|
||||
<ClCompile Include="SDL\src\video\windows\SDL_windowskeyboard.c" />
|
||||
<ClCompile Include="SDL\src\video\windows\SDL_windowsgameinput.c" />
|
||||
<ClCompile Include="SDL\src\video\windows\SDL_windowsmessagebox.c" />
|
||||
<ClCompile Include="SDL\src\video\windows\SDL_windowsmodes.c" />
|
||||
<ClCompile Include="SDL\src\video\windows\SDL_windowsmouse.c" />
|
||||
|
|
|
|||
147
3rdparty/libsdl-org/SDL.vcxproj.filters
vendored
147
3rdparty/libsdl-org/SDL.vcxproj.filters
vendored
|
|
@ -214,6 +214,9 @@
|
|||
<Filter Include="io\windows">
|
||||
<UniqueIdentifier>{000028b2ea36d7190d13777a4dc70000}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="haptic\hidapi">
|
||||
<UniqueIdentifier>{695ffc61-5497-4227-b415-15e9bdd5b6bf}</UniqueIdentifier>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="SDL\include\SDL3\SDL_begin_code.h">
|
||||
|
|
@ -699,9 +702,6 @@
|
|||
<ClInclude Include="SDL\src\video\yuv2rgb\yuv_rgb_std_func.h">
|
||||
<Filter>video\yuv2rgb</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="SDL\src\video\windows\SDL_surface_utils.h">
|
||||
<Filter>video\windows</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="SDL\src\video\windows\SDL_windowsclipboard.h">
|
||||
<Filter>video\windows</Filter>
|
||||
</ClInclude>
|
||||
|
|
@ -831,9 +831,6 @@
|
|||
<ClInclude Include="SDL\src\render\software\SDL_render_sw_c.h">
|
||||
<Filter>render\software</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="SDL\src\render\software\SDL_rotate.h">
|
||||
<Filter>render\software</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="SDL\src\render\software\SDL_triangle.h">
|
||||
<Filter>render\software</Filter>
|
||||
</ClInclude>
|
||||
|
|
@ -911,12 +908,6 @@
|
|||
<ClInclude Include="SDL\src\hidapi\SDL_hidapi_c.h" />
|
||||
<ClInclude Include="SDL\src\thread\generic\SDL_sysrwlock_c.h" />
|
||||
<ClInclude Include="SDL\src\thread\generic\SDL_sysrwlock_c.h" />
|
||||
<ClInclude Include="SDL\src\video\yuv2rgb\yuv_rgb_common.h" />
|
||||
<ClInclude Include="SDL\src\video\yuv2rgb\yuv_rgb_internal.h" />
|
||||
<ClInclude Include="SDL\src\video\yuv2rgb\yuv_rgb_lsx.h" />
|
||||
<ClInclude Include="SDL\src\video\yuv2rgb\yuv_rgb_lsx_func.h" />
|
||||
<ClInclude Include="SDL\src\video\yuv2rgb\yuv_rgb_sse.h" />
|
||||
<ClInclude Include="SDL\src\video\yuv2rgb\yuv_rgb_std.h" />
|
||||
<ClInclude Include="SDL\src\render\vulkan\SDL_shaders_vulkan.h">
|
||||
<Filter>render\vulkan</Filter>
|
||||
</ClInclude>
|
||||
|
|
@ -950,6 +941,60 @@
|
|||
<ClInclude Include="SDL\include\SDL3\SDL_storage.h" />
|
||||
<ClInclude Include="SDL\include\SDL3\SDL_time.h" />
|
||||
<ClInclude Include="SDL\src\events\SDL_categories_c.h" />
|
||||
<ClInclude Include="SDL\src\video\yuv2rgb\yuv_rgb_std.h">
|
||||
<Filter>video\yuv2rgb</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="SDL\src\video\yuv2rgb\yuv_rgb_common.h">
|
||||
<Filter>video\yuv2rgb</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="SDL\src\video\yuv2rgb\yuv_rgb_internal.h">
|
||||
<Filter>video\yuv2rgb</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="SDL\src\video\yuv2rgb\yuv_rgb_lsx.h">
|
||||
<Filter>video\yuv2rgb</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="SDL\src\video\yuv2rgb\yuv_rgb_lsx_func.h">
|
||||
<Filter>video\yuv2rgb</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="SDL\src\video\yuv2rgb\yuv_rgb_sse.h">
|
||||
<Filter>video\yuv2rgb</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="SDL\src\video\miniz.h">
|
||||
<Filter>video</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="SDL\src\video\SDL_rotate.h">
|
||||
<Filter>video</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="SDL\src\video\SDL_video_unsupported.h">
|
||||
<Filter>video</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="SDL\src\misc\SDL_libusb.h">
|
||||
<Filter>misc</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="SDL\src\haptic\hidapi\SDL_hidapihaptic.h">
|
||||
<Filter>haptic\hidapi</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="SDL\src\haptic\hidapi\SDL_hidapihaptic_c.h">
|
||||
<Filter>haptic\hidapi</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="SDL\src\core\SDL_core_unsupported.h">
|
||||
<Filter>core</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="SDL\src\joystick\hidapi\SDL_hidapi_flydigi.h">
|
||||
<Filter>joystick\hidapi</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="SDL\src\joystick\hidapi\SDL_hidapi_nintendo.h">
|
||||
<Filter>joystick\hidapi</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="SDL\src\joystick\hidapi\SDL_hidapi_sinput.h">
|
||||
<Filter>joystick\hidapi</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="SDL\src\joystick\hidapi\SDL_report_descriptor.h">
|
||||
<Filter>joystick\hidapi</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="SDL\include\SDL3\SDL_dlopennote.h">
|
||||
<Filter>API Headers</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="SDL\src\audio\wasapi\SDL_wasapi.c" />
|
||||
|
|
@ -1037,9 +1082,6 @@
|
|||
<ClCompile Include="SDL\src\core\SDL_core_unsupported.c">
|
||||
<Filter>core</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="SDL\src\core\windows\SDL_gameinput.c">
|
||||
<Filter>core\windows</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="SDL\src\core\windows\SDL_hid.c">
|
||||
<Filter>core\windows</Filter>
|
||||
</ClCompile>
|
||||
|
|
@ -1166,9 +1208,6 @@
|
|||
<ClCompile Include="SDL\src\joystick\dummy\SDL_sysjoystick.c">
|
||||
<Filter>joystick\dummy</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="SDL\src\joystick\gdk\SDL_gameinputjoystick.c">
|
||||
<Filter>joystick\gdk</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="SDL\src\joystick\hidapi\SDL_hidapi_combined.c">
|
||||
<Filter>joystick\hidapi</Filter>
|
||||
</ClCompile>
|
||||
|
|
@ -1328,9 +1367,6 @@
|
|||
<ClCompile Include="SDL\src\video\dummy\SDL_nullvideo.c">
|
||||
<Filter>video\dummy</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="SDL\src\video\windows\SDL_surface_utils.c">
|
||||
<Filter>video\windows</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="SDL\src\video\windows\SDL_windowsclipboard.c">
|
||||
<Filter>video\windows</Filter>
|
||||
</ClCompile>
|
||||
|
|
@ -1343,9 +1379,6 @@
|
|||
<ClCompile Include="SDL\src\video\windows\SDL_windowskeyboard.c">
|
||||
<Filter>video\windows</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="SDL\src\video\windows\SDL_windowsgameinput.c">
|
||||
<Filter>video\windows</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="SDL\src\video\windows\SDL_windowsmessagebox.c">
|
||||
<Filter>video\windows</Filter>
|
||||
</ClCompile>
|
||||
|
|
@ -1508,9 +1541,6 @@
|
|||
<ClCompile Include="SDL\src\render\software\SDL_render_sw.c">
|
||||
<Filter>render\software</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="SDL\src\render\software\SDL_rotate.c">
|
||||
<Filter>render\software</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="SDL\src\render\software\SDL_triangle.c">
|
||||
<Filter>render\software</Filter>
|
||||
</ClCompile>
|
||||
|
|
@ -1535,9 +1565,6 @@
|
|||
</ClCompile>
|
||||
<ClCompile Include="SDL\src\thread\generic\SDL_sysrwlock.c" />
|
||||
<ClCompile Include="SDL\src\thread\generic\SDL_sysrwlock.c" />
|
||||
<ClCompile Include="SDL\src\video\yuv2rgb\yuv_rgb_lsx.c" />
|
||||
<ClCompile Include="SDL\src\video\yuv2rgb\yuv_rgb_sse.c" />
|
||||
<ClCompile Include="SDL\src\video\yuv2rgb\yuv_rgb_std.c" />
|
||||
<ClCompile Include="SDL\src\render\vulkan\SDL_render_vulkan.c">
|
||||
<Filter>render\vulkan</Filter>
|
||||
</ClCompile>
|
||||
|
|
@ -1579,6 +1606,66 @@
|
|||
<ClCompile Include="SDL\src\storage\generic\SDL_genericstorage.c" />
|
||||
<ClCompile Include="SDL\src\storage\steam\SDL_steamstorage.c" />
|
||||
<ClCompile Include="SDL\src\storage\SDL_storage.c" />
|
||||
<ClCompile Include="SDL\src\video\windows\SDL_windowsgameinput.cpp">
|
||||
<Filter>video\windows</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="SDL\src\video\yuv2rgb\yuv_rgb_lsx.c">
|
||||
<Filter>video\yuv2rgb</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="SDL\src\video\yuv2rgb\yuv_rgb_sse.c">
|
||||
<Filter>video\yuv2rgb</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="SDL\src\video\yuv2rgb\yuv_rgb_std.c">
|
||||
<Filter>video\yuv2rgb</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="SDL\src\video\SDL_rotate.c">
|
||||
<Filter>video</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="SDL\src\misc\SDL_libusb.c">
|
||||
<Filter>misc</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="SDL\src\haptic\hidapi\SDL_hidapihaptic.c">
|
||||
<Filter>haptic\hidapi</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="SDL\src\haptic\hidapi\SDL_hidapihaptic_lg4ff.c">
|
||||
<Filter>haptic\hidapi</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="SDL\src\core\windows\pch_cpp.cpp">
|
||||
<Filter>core\windows</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="SDL\src\core\windows\SDL_gameinput.cpp">
|
||||
<Filter>core\windows</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="SDL\src\joystick\gdk\SDL_gameinputjoystick.cpp">
|
||||
<Filter>joystick\gdk</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="SDL\src\joystick\hidapi\SDL_hidapi_8bitdo.c">
|
||||
<Filter>joystick\hidapi</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="SDL\src\joystick\hidapi\SDL_hidapi_flydigi.c">
|
||||
<Filter>joystick\hidapi</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="SDL\src\joystick\hidapi\SDL_hidapi_gip.c">
|
||||
<Filter>joystick\hidapi</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="SDL\src\joystick\hidapi\SDL_hidapi_lg4ff.c">
|
||||
<Filter>joystick\hidapi</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="SDL\src\joystick\hidapi\SDL_hidapi_sinput.c">
|
||||
<Filter>joystick\hidapi</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="SDL\src\joystick\hidapi\SDL_hidapi_steam_triton.c">
|
||||
<Filter>joystick\hidapi</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="SDL\src\joystick\hidapi\SDL_hidapi_switch2.c">
|
||||
<Filter>joystick\hidapi</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="SDL\src\joystick\hidapi\SDL_hidapi_zuiki.c">
|
||||
<Filter>joystick\hidapi</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="SDL\src\joystick\hidapi\SDL_report_descriptor.c">
|
||||
<Filter>joystick\hidapi</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ResourceCompile Include="SDL\src\core\windows\version.rc" />
|
||||
|
|
|
|||
|
|
@ -108,9 +108,10 @@ Clone and initialize the repository
|
|||
```bash
|
||||
git clone --recurse-submodules https://github.com/RPCS3/rpcs3.git
|
||||
cd rpcs3
|
||||
git submodule sync
|
||||
# This is automatically done by `git clone --recurse-submodules`,
|
||||
# but in case you forgot it, you can manually fetch submodules this way:
|
||||
git submodule update --init
|
||||
git submodule update --init --recursive
|
||||
```
|
||||
|
||||
### Windows
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ bool cheat_info::from_str(std::string_view cheat_line)
|
|||
s64 val64 = 0;
|
||||
if (cheat_vec.size() != 5 || !try_to_int64(&val64, cheat_vec[2], 0, cheat_type_max - 1))
|
||||
{
|
||||
log_cheat.fatal("Failed to parse cheat line");
|
||||
log_cheat.error("Failed to parse cheat line: '%s'", cheat_line);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -151,8 +151,9 @@ if (NOT ANDROID)
|
|||
get_target_property(WINDEPLOYQT_EXECUTABLE Qt6::windeployqt IMPORTED_LOCATION)
|
||||
add_custom_command(TARGET rpcs3 POST_BUILD
|
||||
COMMAND ${WINDEPLOYQT_EXECUTABLE} --no-compiler-runtime --no-opengl-sw --no-patchqt
|
||||
--no-translations --no-system-d3d-compiler --no-system-dxc-compiler --no-quick-import
|
||||
--no-system-d3d-compiler --no-system-dxc-compiler --no-quick-import
|
||||
--plugindir "$<IF:$<CXX_COMPILER_FRONTEND_VARIANT:MSVC>,$<TARGET_FILE_DIR:rpcs3>/qt6/plugins,$<TARGET_FILE_DIR:rpcs3>/share/qt6/plugins>"
|
||||
--translationdir "$<IF:$<CXX_COMPILER_FRONTEND_VARIANT:MSVC>,$<TARGET_FILE_DIR:rpcs3>/qt6/translations,$<TARGET_FILE_DIR:rpcs3>/share/qt6/translations>"
|
||||
--verbose 0
|
||||
$<TARGET_FILE:rpcs3>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -546,7 +546,7 @@ target_sources(rpcs3_emu PRIVATE
|
|||
RSX/rsx_vertex_data.cpp
|
||||
)
|
||||
|
||||
if(NOT ANDROID)
|
||||
if(NOT ANDROID AND NOT APPLE)
|
||||
target_sources(rpcs3_emu PRIVATE
|
||||
RSX/GL/GLCommonDecompiler.cpp
|
||||
RSX/GL/GLCompute.cpp
|
||||
|
|
@ -667,6 +667,7 @@ target_link_libraries(rpcs3_emu
|
|||
3rdparty::yaml-cpp
|
||||
3rdparty::zlib
|
||||
3rdparty::zstd
|
||||
3rdparty::libcurl
|
||||
)
|
||||
|
||||
if(APPLE)
|
||||
|
|
|
|||
|
|
@ -1501,8 +1501,15 @@ void gem_config_data::operator()()
|
|||
vc = vc_attribute;
|
||||
}
|
||||
|
||||
if (g_cfg.io.camera != camera_handler::qt)
|
||||
switch (g_cfg.io.camera)
|
||||
{
|
||||
#ifdef HAVE_SDL3
|
||||
case camera_handler::sdl:
|
||||
#endif
|
||||
case camera_handler::qt:
|
||||
break;
|
||||
case camera_handler::fake:
|
||||
case camera_handler::null:
|
||||
video_conversion_in_progress = false;
|
||||
done();
|
||||
continue;
|
||||
|
|
|
|||
|
|
@ -215,7 +215,7 @@ error_code cell_music_select_contents()
|
|||
const std::string vfs_dir_path = vfs::get("/dev_hdd0/music");
|
||||
const std::string title = get_localized_string(localized_string_id::RSX_OVERLAYS_MEDIA_DIALOG_TITLE);
|
||||
|
||||
error_code error = rsx::overlays::show_media_list_dialog(rsx::overlays::media_list_dialog::media_type::audio, vfs_dir_path, title,
|
||||
error_code error = rsx::overlays::show_media_list_dialog(rsx::overlays::media_list_dialog::media_type::audio, music_selection_context::max_depth, vfs_dir_path, title,
|
||||
[&music](s32 status, utils::media_info info)
|
||||
{
|
||||
sysutil_register_cb([&music, info = std::move(info), status](ppu_thread& ppu) -> s32
|
||||
|
|
|
|||
|
|
@ -134,7 +134,7 @@ error_code cell_music_decode_select_contents()
|
|||
const std::string vfs_dir_path = vfs::get("/dev_hdd0/music");
|
||||
const std::string title = get_localized_string(localized_string_id::RSX_OVERLAYS_MEDIA_DIALOG_TITLE);
|
||||
|
||||
error_code error = rsx::overlays::show_media_list_dialog(rsx::overlays::media_list_dialog::media_type::audio, vfs_dir_path, title,
|
||||
error_code error = rsx::overlays::show_media_list_dialog(rsx::overlays::media_list_dialog::media_type::audio, music_selection_context::max_depth, vfs_dir_path, title,
|
||||
[&dec](s32 status, utils::media_info info)
|
||||
{
|
||||
sysutil_register_cb([&dec, info = std::move(info), status](ppu_thread& ppu) -> s32
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
#include "stdafx.h"
|
||||
#include "Emu/Cell/PPUModule.h"
|
||||
#include "Emu/System.h"
|
||||
#include "Emu/IdManager.h"
|
||||
#include "Emu/VFS.h"
|
||||
#include "cellSysutil.h"
|
||||
|
|
@ -107,30 +108,16 @@ bool check_photo_path(const std::string& file_path)
|
|||
return true;
|
||||
}
|
||||
|
||||
std::string get_available_photo_path(const std::string& filename)
|
||||
std::string get_available_photo_path(std::string_view filename)
|
||||
{
|
||||
const std::string photo_dir = "/dev_hdd0/photo/";
|
||||
std::string dst_path = vfs::get(photo_dir + filename);
|
||||
|
||||
// Do not overwrite existing files. Add a suffix instead.
|
||||
for (u32 i = 0; fs::exists(dst_path); i++)
|
||||
std::string_view extension = ".png";
|
||||
if (const auto extension_start = filename.find_last_of('.');
|
||||
extension_start != umax)
|
||||
{
|
||||
const std::string suffix = fmt::format("_%d", i);
|
||||
std::string new_filename = filename;
|
||||
|
||||
if (const usz pos = new_filename.find_last_of('.'); pos != std::string::npos)
|
||||
{
|
||||
new_filename.insert(pos, suffix);
|
||||
}
|
||||
else
|
||||
{
|
||||
new_filename.append(suffix);
|
||||
}
|
||||
|
||||
dst_path = vfs::get(photo_dir + new_filename);
|
||||
extension = filename.substr(extension_start);
|
||||
}
|
||||
|
||||
return dst_path;
|
||||
return Emu.GetCallbacks().get_photo_path(fmt::format("%s%s", Emu.GetTitle(), extension));
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -142,7 +142,7 @@ error_code select_photo(std::string dst_dir)
|
|||
const std::string vfs_dir_path = vfs::get("/dev_hdd0/photo");
|
||||
const std::string title = get_localized_string(localized_string_id::RSX_OVERLAYS_MEDIA_DIALOG_TITLE_PHOTO_IMPORT);
|
||||
|
||||
error_code error = rsx::overlays::show_media_list_dialog(rsx::overlays::media_list_dialog::media_type::photo, vfs_dir_path, title,
|
||||
error_code error = rsx::overlays::show_media_list_dialog(rsx::overlays::media_list_dialog::media_type::photo, umax, vfs_dir_path, title,
|
||||
[&pi_manager, dst_dir](s32 status, utils::media_info info)
|
||||
{
|
||||
sysutil_register_cb([&pi_manager, dst_dir, info, status](ppu_thread& ppu) -> s32
|
||||
|
|
@ -176,10 +176,29 @@ error_code select_photo(std::string dst_dir)
|
|||
|
||||
const std::string filename = info.path.substr(info.path.find_last_of(fs::delim) + 1);
|
||||
const std::string title = info.get_metadata("title", filename);
|
||||
const std::string dst_path = dst_dir + "/" + filename;
|
||||
std::string dst_path = dst_dir + "/";
|
||||
std::string sub_type = info.sub_type;
|
||||
|
||||
strcpy_trunc(g_filedata->dstFileName, filename);
|
||||
// Try to find a unique filename (TODO: how does the PS3 copy the files exactly?)
|
||||
std::string extension;
|
||||
std::string dst_filename = filename;
|
||||
if (const auto extension_start = filename.find_last_of('.');
|
||||
extension_start != umax)
|
||||
{
|
||||
extension = filename.substr(extension_start);
|
||||
dst_filename = filename.substr(0, extension_start);
|
||||
}
|
||||
|
||||
std::string suffix = extension;
|
||||
u32 counter = 0;
|
||||
while (!Emu.IsStopped() && fs::is_file(dst_path + dst_filename + suffix))
|
||||
{
|
||||
suffix = fmt::format(" %d%s", ++counter, extension);
|
||||
}
|
||||
dst_filename += std::move(suffix);
|
||||
dst_path += dst_filename;
|
||||
|
||||
strcpy_trunc(g_filedata->dstFileName, dst_filename);
|
||||
strcpy_trunc(g_filedata->photo_title, title);
|
||||
strcpy_trunc(g_filedata->game_title, Emu.GetTitle());
|
||||
strcpy_trunc(g_filedata->game_comment, ""); // TODO
|
||||
|
|
|
|||
|
|
@ -33,18 +33,12 @@ std::string screenshot_info::get_overlay_path() const
|
|||
|
||||
std::string screenshot_info::get_photo_title() const
|
||||
{
|
||||
std::string photo = photo_title;
|
||||
if (photo.empty())
|
||||
photo = Emu.GetTitle();
|
||||
return photo;
|
||||
return photo_title.empty() ? Emu.GetTitle() : photo_title;
|
||||
}
|
||||
|
||||
std::string screenshot_info::get_game_title() const
|
||||
{
|
||||
std::string game = game_title;
|
||||
if (game.empty())
|
||||
game = Emu.GetTitle();
|
||||
return game;
|
||||
return game_title.empty() ? Emu.GetTitle() : game_title;
|
||||
}
|
||||
|
||||
std::string screenshot_info::get_game_comment() const
|
||||
|
|
@ -52,20 +46,6 @@ std::string screenshot_info::get_game_comment() const
|
|||
return game_comment;
|
||||
}
|
||||
|
||||
std::string screenshot_info::get_screenshot_path(const std::string& date_path) const
|
||||
{
|
||||
u32 counter = 0;
|
||||
std::string path = vfs::get("/dev_hdd0/photo/" + date_path + "/" + get_photo_title());
|
||||
std::string suffix = ".png";
|
||||
|
||||
while (!Emu.IsStopped() && fs::is_file(path + suffix))
|
||||
{
|
||||
suffix = fmt::format("_%d.png", ++counter);
|
||||
}
|
||||
|
||||
return path + suffix;
|
||||
}
|
||||
|
||||
|
||||
error_code cellScreenShotSetParameter(vm::cptr<CellScreenShotSetParam> param)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -44,7 +44,6 @@ struct screenshot_info
|
|||
std::string get_photo_title() const;
|
||||
std::string get_game_title() const;
|
||||
std::string get_game_comment() const;
|
||||
std::string get_screenshot_path(const std::string& date_path) const;
|
||||
};
|
||||
|
||||
struct screenshot_manager : public screenshot_info
|
||||
|
|
|
|||
|
|
@ -1265,7 +1265,7 @@ struct SceNpCommunicationId
|
|||
// OnlineId structure
|
||||
struct SceNpOnlineId
|
||||
{
|
||||
char data[16 + 1]; // char term;
|
||||
char data[SCE_NET_NP_ONLINEID_MAX_LENGTH + 1]; // char term;
|
||||
char dummy[3];
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -144,9 +144,9 @@ error_code sceNpClansCreateRequest(vm::ptr<SceNpClansRequestHandle> handle, u64
|
|||
}
|
||||
|
||||
auto& clans_manager = g_fxo->get<sce_np_clans_manager>();
|
||||
|
||||
|
||||
s32 reqId = 0;
|
||||
SceNpClansError res = clans_manager.client->create_request(&reqId);
|
||||
const SceNpClansError res = clans_manager.client->create_request(reqId);
|
||||
if (res != SCE_NP_CLANS_SUCCESS)
|
||||
{
|
||||
return res;
|
||||
|
|
@ -167,8 +167,8 @@ error_code sceNpClansDestroyRequest(SceNpClansRequestHandle handle)
|
|||
}
|
||||
|
||||
auto& clans_manager = g_fxo->get<sce_np_clans_manager>();
|
||||
|
||||
SceNpClansError res = clans_manager.client->destroy_request(handle);
|
||||
|
||||
const SceNpClansError res = clans_manager.client->destroy_request(handle);
|
||||
if (res != SCE_NP_CLANS_SUCCESS)
|
||||
{
|
||||
return res;
|
||||
|
|
@ -220,7 +220,7 @@ error_code sceNpClansCreateClan(SceNpClansRequestHandle handle, vm::cptr<char> n
|
|||
std::string tag_str;
|
||||
vm::read_string(tag.addr(), SCE_NP_CLANS_CLAN_TAG_MAX_LENGTH, tag_str);
|
||||
|
||||
SceNpClansError res = clans_manager.client->create_clan(nph, handle, name_str, tag_str, clanId);
|
||||
const SceNpClansError res = clans_manager.client->create_clan(nph, handle, name_str, tag_str, clanId);
|
||||
if (res != SCE_NP_CLANS_SUCCESS)
|
||||
{
|
||||
return res;
|
||||
|
|
@ -246,7 +246,7 @@ error_code sceNpClansDisbandClan(SceNpClansRequestHandle handle, SceNpClanId cla
|
|||
auto& nph = g_fxo->get<named_thread<np::np_handler>>();
|
||||
auto& clans_manager = g_fxo->get<sce_np_clans_manager>();
|
||||
|
||||
SceNpClansError res = clans_manager.client->disband_dlan(nph, handle, clanId);
|
||||
const SceNpClansError res = clans_manager.client->disband_dlan(nph, handle, clanId);
|
||||
if (res != SCE_NP_CLANS_SUCCESS)
|
||||
{
|
||||
return res;
|
||||
|
|
@ -283,13 +283,13 @@ error_code sceNpClansGetClanList(SceNpClansRequestHandle handle, vm::cptr<SceNpC
|
|||
SceNpClansPagingRequest host_paging = {};
|
||||
if (paging)
|
||||
{
|
||||
std::memcpy(&host_paging, paging.get_ptr(), sizeof(SceNpClansPagingRequest));
|
||||
host_paging = *paging;
|
||||
}
|
||||
|
||||
SceNpClansEntry host_clanList[SCE_NP_CLANS_PAGING_REQUEST_PAGE_MAX] = {};
|
||||
std::vector<SceNpClansEntry> host_clanList(SCE_NP_CLANS_PAGING_REQUEST_PAGE_MAX);
|
||||
SceNpClansPagingResult host_pageResult = {};
|
||||
|
||||
SceNpClansError ret = clans_manager.client->get_clan_list(nph, handle, &host_paging, host_clanList, &host_pageResult);
|
||||
const SceNpClansError ret = clans_manager.client->get_clan_list(nph, handle, host_paging, host_clanList, host_pageResult);
|
||||
if (ret != SCE_NP_CLANS_SUCCESS)
|
||||
{
|
||||
return ret;
|
||||
|
|
@ -297,9 +297,9 @@ error_code sceNpClansGetClanList(SceNpClansRequestHandle handle, vm::cptr<SceNpC
|
|||
|
||||
if (clanList && host_pageResult.count > 0)
|
||||
{
|
||||
std::memcpy(clanList.get_ptr(), host_clanList, sizeof(SceNpClansEntry) * host_pageResult.count);
|
||||
std::memcpy(clanList.get_ptr(), host_clanList.data(), sizeof(SceNpClansEntry) * host_pageResult.count);
|
||||
}
|
||||
std::memcpy(pageResult.get_ptr(), &host_pageResult, sizeof(SceNpClansPagingResult));
|
||||
*pageResult = host_pageResult;
|
||||
|
||||
return CELL_OK;
|
||||
}
|
||||
|
|
@ -383,16 +383,15 @@ error_code sceNpClansSearchByName(SceNpClansRequestHandle handle, vm::cptr<SceNp
|
|||
SceNpClansPagingRequest host_paging = {};
|
||||
if (paging)
|
||||
{
|
||||
std::memcpy(&host_paging, paging.get_ptr(), sizeof(SceNpClansPagingRequest));
|
||||
host_paging = *paging;
|
||||
}
|
||||
|
||||
SceNpClansSearchableName host_search = {};
|
||||
std::memcpy(&host_search, search.get_ptr(), sizeof(SceNpClansSearchableName));
|
||||
const SceNpClansSearchableName host_search = *search;
|
||||
|
||||
SceNpClansClanBasicInfo host_results[SCE_NP_CLANS_PAGING_REQUEST_PAGE_MAX] = {};
|
||||
std::vector<SceNpClansClanBasicInfo> host_results(SCE_NP_CLANS_PAGING_REQUEST_PAGE_MAX);
|
||||
SceNpClansPagingResult host_pageResult = {};
|
||||
|
||||
SceNpClansError ret = clans_manager.client->clan_search(handle, &host_paging, &host_search, host_results, &host_pageResult);
|
||||
const SceNpClansError ret = clans_manager.client->clan_search(handle, host_paging, host_search, host_results, host_pageResult);
|
||||
if (ret != SCE_NP_CLANS_SUCCESS)
|
||||
{
|
||||
return ret;
|
||||
|
|
@ -400,9 +399,9 @@ error_code sceNpClansSearchByName(SceNpClansRequestHandle handle, vm::cptr<SceNp
|
|||
|
||||
if (results && host_pageResult.count > 0)
|
||||
{
|
||||
std::memcpy(results.get_ptr(), host_results, sizeof(SceNpClansClanBasicInfo) * host_pageResult.count);
|
||||
std::memcpy(results.get_ptr(), host_results.data(), sizeof(SceNpClansClanBasicInfo) * host_pageResult.count);
|
||||
}
|
||||
std::memcpy(pageResult.get_ptr(), &host_pageResult, sizeof(SceNpClansPagingResult));
|
||||
*pageResult = host_pageResult;
|
||||
|
||||
return CELL_OK;
|
||||
}
|
||||
|
|
@ -425,14 +424,14 @@ error_code sceNpClansGetClanInfo(SceNpClansRequestHandle handle, SceNpClanId cla
|
|||
auto& clans_manager = g_fxo->get<sce_np_clans_manager>();
|
||||
|
||||
SceNpClansClanInfo host_info = {};
|
||||
|
||||
SceNpClansError ret = clans_manager.client->get_clan_info(handle, clanId, &host_info);
|
||||
|
||||
const SceNpClansError ret = clans_manager.client->get_clan_info(handle, clanId, host_info);
|
||||
if (ret != SCE_NP_CLANS_SUCCESS)
|
||||
{
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::memcpy(info.get_ptr(), &host_info, sizeof(SceNpClansClanInfo));
|
||||
|
||||
*info = host_info;
|
||||
|
||||
return CELL_OK;
|
||||
}
|
||||
|
|
@ -455,10 +454,9 @@ error_code sceNpClansUpdateClanInfo(SceNpClansRequestHandle handle, SceNpClanId
|
|||
auto& nph = g_fxo->get<named_thread<np::np_handler>>();
|
||||
auto& clans_manager = g_fxo->get<sce_np_clans_manager>();
|
||||
|
||||
SceNpClansUpdatableClanInfo host_info = {};
|
||||
std::memcpy(&host_info, info.get_ptr(), sizeof(SceNpClansUpdatableClanInfo));
|
||||
const SceNpClansUpdatableClanInfo host_info = *info;
|
||||
|
||||
SceNpClansError ret = clans_manager.client->update_clan_info(nph, handle, clanId, &host_info);
|
||||
const SceNpClansError ret = clans_manager.client->update_clan_info(nph, handle, clanId, host_info);
|
||||
if (ret != SCE_NP_CLANS_SUCCESS)
|
||||
{
|
||||
return ret;
|
||||
|
|
@ -495,13 +493,13 @@ error_code sceNpClansGetMemberList(SceNpClansRequestHandle handle, SceNpClanId c
|
|||
SceNpClansPagingRequest host_paging = {};
|
||||
if (paging)
|
||||
{
|
||||
std::memcpy(&host_paging, paging.get_ptr(), sizeof(SceNpClansPagingRequest));
|
||||
host_paging = *paging;
|
||||
}
|
||||
|
||||
SceNpClansMemberEntry host_memList_addr[SCE_NP_CLANS_PAGING_REQUEST_PAGE_MAX] = {};
|
||||
std::vector<SceNpClansMemberEntry> host_memList_addr(SCE_NP_CLANS_PAGING_REQUEST_PAGE_MAX);
|
||||
SceNpClansPagingResult host_pageResult = {};
|
||||
|
||||
SceNpClansError ret = clans_manager.client->get_member_list(nph, handle, clanId, &host_paging, status, host_memList_addr, &host_pageResult);
|
||||
const SceNpClansError ret = clans_manager.client->get_member_list(nph, handle, clanId, host_paging, status, host_memList_addr, host_pageResult);
|
||||
if (ret != SCE_NP_CLANS_SUCCESS)
|
||||
{
|
||||
return ret;
|
||||
|
|
@ -509,9 +507,9 @@ error_code sceNpClansGetMemberList(SceNpClansRequestHandle handle, SceNpClanId c
|
|||
|
||||
if (memList && host_pageResult.count > 0)
|
||||
{
|
||||
std::memcpy(memList.get_ptr(), host_memList_addr, sizeof(SceNpClansMemberEntry) * host_pageResult.count);
|
||||
std::memcpy(memList.get_ptr(), host_memList_addr.data(), sizeof(SceNpClansMemberEntry) * host_pageResult.count);
|
||||
}
|
||||
std::memcpy(pageResult.get_ptr(), &host_pageResult, sizeof(SceNpClansPagingResult));
|
||||
*pageResult = host_pageResult;
|
||||
|
||||
return CELL_OK;
|
||||
}
|
||||
|
|
@ -533,18 +531,17 @@ error_code sceNpClansGetMemberInfo(SceNpClansRequestHandle handle, SceNpClanId c
|
|||
auto& nph = g_fxo->get<named_thread<np::np_handler>>();
|
||||
auto& clans_manager = g_fxo->get<sce_np_clans_manager>();
|
||||
|
||||
SceNpId host_npid = {};
|
||||
std::memcpy(&host_npid, npid.get_ptr(), sizeof(SceNpId));
|
||||
const SceNpId host_npid = *npid;
|
||||
|
||||
SceNpClansMemberEntry host_memInfo = {};
|
||||
|
||||
SceNpClansError ret = clans_manager.client->get_member_info(nph, handle, clanId, host_npid, &host_memInfo);
|
||||
const SceNpClansError ret = clans_manager.client->get_member_info(nph, handle, clanId, host_npid, host_memInfo);
|
||||
if (ret != SCE_NP_CLANS_SUCCESS)
|
||||
{
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::memcpy(memInfo.get_ptr(), &host_memInfo, sizeof(SceNpClansMemberEntry));
|
||||
*memInfo = host_memInfo;
|
||||
|
||||
return CELL_OK;
|
||||
}
|
||||
|
|
@ -566,10 +563,9 @@ error_code sceNpClansUpdateMemberInfo(SceNpClansRequestHandle handle, SceNpClanI
|
|||
auto& nph = g_fxo->get<named_thread<np::np_handler>>();
|
||||
auto& clans_manager = g_fxo->get<sce_np_clans_manager>();
|
||||
|
||||
SceNpClansUpdatableMemberInfo host_info = {};
|
||||
std::memcpy(&host_info, info.get_ptr(), sizeof(SceNpClansUpdatableMemberInfo));
|
||||
const SceNpClansUpdatableMemberInfo host_info = *info;
|
||||
|
||||
SceNpClansError ret = clans_manager.client->update_member_info(nph, handle, clanId, &host_info);
|
||||
const SceNpClansError ret = clans_manager.client->update_member_info(nph, handle, clanId, host_info);
|
||||
if (ret != SCE_NP_CLANS_SUCCESS)
|
||||
{
|
||||
return ret;
|
||||
|
|
@ -595,10 +591,9 @@ error_code sceNpClansChangeMemberRole(SceNpClansRequestHandle handle, SceNpClanI
|
|||
auto& nph = g_fxo->get<named_thread<np::np_handler>>();
|
||||
auto& clans_manager = g_fxo->get<sce_np_clans_manager>();
|
||||
|
||||
SceNpId host_npid = {};
|
||||
std::memcpy(&host_npid, npid.get_ptr(), sizeof(SceNpId));
|
||||
const SceNpId host_npid = *npid;
|
||||
|
||||
SceNpClansError ret = clans_manager.client->change_member_role(nph, handle, clanId, host_npid, static_cast<SceNpClansMemberRole>(role));
|
||||
const SceNpClansError ret = clans_manager.client->change_member_role(nph, handle, clanId, host_npid, static_cast<SceNpClansMemberRole>(role));
|
||||
if (ret != SCE_NP_CLANS_SUCCESS)
|
||||
{
|
||||
return ret;
|
||||
|
|
@ -650,7 +645,7 @@ error_code sceNpClansJoinClan(SceNpClansRequestHandle handle, SceNpClanId clanId
|
|||
auto& nph = g_fxo->get<named_thread<np::np_handler>>();
|
||||
auto& clans_manager = g_fxo->get<sce_np_clans_manager>();
|
||||
|
||||
SceNpClansError ret = clans_manager.client->join_clan(nph, handle, clanId);
|
||||
const SceNpClansError ret = clans_manager.client->join_clan(nph, handle, clanId);
|
||||
if (ret != SCE_NP_CLANS_SUCCESS)
|
||||
{
|
||||
return ret;
|
||||
|
|
@ -671,7 +666,7 @@ error_code sceNpClansLeaveClan(SceNpClansRequestHandle handle, SceNpClanId clanI
|
|||
auto& nph = g_fxo->get<named_thread<np::np_handler>>();
|
||||
auto& clans_manager = g_fxo->get<sce_np_clans_manager>();
|
||||
|
||||
SceNpClansError ret = clans_manager.client->leave_clan(nph, handle, clanId);
|
||||
const SceNpClansError ret = clans_manager.client->leave_clan(nph, handle, clanId);
|
||||
if (ret != SCE_NP_CLANS_SUCCESS)
|
||||
{
|
||||
return ret;
|
||||
|
|
@ -705,16 +700,15 @@ error_code sceNpClansKickMember(SceNpClansRequestHandle handle, SceNpClanId clan
|
|||
auto& nph = g_fxo->get<named_thread<np::np_handler>>();
|
||||
auto& clans_manager = g_fxo->get<sce_np_clans_manager>();
|
||||
|
||||
SceNpId host_npid = {};
|
||||
std::memcpy(&host_npid, npid.get_ptr(), sizeof(SceNpId));
|
||||
const SceNpId host_npid = *npid;
|
||||
|
||||
SceNpClansMessage host_message = {};
|
||||
if (message)
|
||||
{
|
||||
std::memcpy(&host_message, message.get_ptr(), sizeof(SceNpClansMessage));
|
||||
host_message = *message;
|
||||
}
|
||||
|
||||
SceNpClansError ret = clans_manager.client->kick_member(nph, handle, clanId, host_npid, &host_message);
|
||||
const SceNpClansError ret = clans_manager.client->kick_member(nph, handle, clanId, host_npid, host_message);
|
||||
if (ret != SCE_NP_CLANS_SUCCESS)
|
||||
{
|
||||
return ret;
|
||||
|
|
@ -748,16 +742,15 @@ error_code sceNpClansSendInvitation(SceNpClansRequestHandle handle, SceNpClanId
|
|||
auto& nph = g_fxo->get<named_thread<np::np_handler>>();
|
||||
auto& clans_manager = g_fxo->get<sce_np_clans_manager>();
|
||||
|
||||
SceNpId host_npid = {};
|
||||
std::memcpy(&host_npid, npid.get_ptr(), sizeof(SceNpId));
|
||||
const SceNpId host_npid = *npid;
|
||||
|
||||
SceNpClansMessage host_message = {};
|
||||
if (message)
|
||||
{
|
||||
std::memcpy(&host_message, message.get_ptr(), sizeof(SceNpClansMessage));
|
||||
host_message = *message;
|
||||
}
|
||||
|
||||
SceNpClansError ret = clans_manager.client->send_invitation(nph, handle, clanId, host_npid, &host_message);
|
||||
const SceNpClansError ret = clans_manager.client->send_invitation(nph, handle, clanId, host_npid, host_message);
|
||||
if (ret != SCE_NP_CLANS_SUCCESS)
|
||||
{
|
||||
return ret;
|
||||
|
|
@ -783,10 +776,9 @@ error_code sceNpClansCancelInvitation(SceNpClansRequestHandle handle, SceNpClanI
|
|||
auto& nph = g_fxo->get<named_thread<np::np_handler>>();
|
||||
auto& clans_manager = g_fxo->get<sce_np_clans_manager>();
|
||||
|
||||
SceNpId host_npid = {};
|
||||
std::memcpy(&host_npid, npid.get_ptr(), sizeof(SceNpId));
|
||||
const SceNpId host_npid = *npid;
|
||||
|
||||
SceNpClansError ret = clans_manager.client->cancel_invitation(nph, handle, clanId, host_npid);
|
||||
const SceNpClansError ret = clans_manager.client->cancel_invitation(nph, handle, clanId, host_npid);
|
||||
if (ret != SCE_NP_CLANS_SUCCESS)
|
||||
{
|
||||
return ret;
|
||||
|
|
@ -818,15 +810,10 @@ error_code sceNpClansSendInvitationResponse(SceNpClansRequestHandle handle, SceN
|
|||
SceNpClansMessage host_message = {};
|
||||
if (message)
|
||||
{
|
||||
std::memcpy(&host_message, message.get_ptr(), sizeof(SceNpClansMessage));
|
||||
host_message = *message;
|
||||
}
|
||||
|
||||
if (message)
|
||||
{
|
||||
std::memcpy(&host_message, message.get_ptr(), sizeof(SceNpClansMessage));
|
||||
}
|
||||
|
||||
SceNpClansError ret = clans_manager.client->send_invitation_response(nph, handle, clanId, &host_message, accept);
|
||||
const SceNpClansError ret = clans_manager.client->send_invitation_response(nph, handle, clanId, host_message, accept);
|
||||
if (ret != SCE_NP_CLANS_SUCCESS)
|
||||
{
|
||||
return ret;
|
||||
|
|
@ -858,10 +845,10 @@ error_code sceNpClansSendMembershipRequest(SceNpClansRequestHandle handle, u32 c
|
|||
SceNpClansMessage host_message = {};
|
||||
if (message)
|
||||
{
|
||||
std::memcpy(&host_message, message.get_ptr(), sizeof(SceNpClansMessage));
|
||||
host_message = *message;
|
||||
}
|
||||
|
||||
SceNpClansError ret = clans_manager.client->request_membership(nph, handle, clanId, &host_message);
|
||||
const SceNpClansError ret = clans_manager.client->request_membership(nph, handle, clanId, host_message);
|
||||
if (ret != SCE_NP_CLANS_SUCCESS)
|
||||
{
|
||||
return ret;
|
||||
|
|
@ -882,8 +869,7 @@ error_code sceNpClansCancelMembershipRequest(SceNpClansRequestHandle handle, Sce
|
|||
auto& nph = g_fxo->get<named_thread<np::np_handler>>();
|
||||
auto& clans_manager = g_fxo->get<sce_np_clans_manager>();
|
||||
|
||||
SceNpClansError ret = clans_manager.client->cancel_request_membership(nph, handle, clanId);
|
||||
|
||||
const SceNpClansError ret = clans_manager.client->cancel_request_membership(nph, handle, clanId);
|
||||
if (ret != SCE_NP_CLANS_SUCCESS)
|
||||
{
|
||||
return ret;
|
||||
|
|
@ -917,16 +903,15 @@ error_code sceNpClansSendMembershipResponse(SceNpClansRequestHandle handle, SceN
|
|||
auto& nph = g_fxo->get<named_thread<np::np_handler>>();
|
||||
auto& clans_manager = g_fxo->get<sce_np_clans_manager>();
|
||||
|
||||
SceNpId host_npid = {};
|
||||
std::memcpy(&host_npid, npid.get_ptr(), sizeof(SceNpId));
|
||||
const SceNpId host_npid = *npid;
|
||||
|
||||
SceNpClansMessage host_message = {};
|
||||
if (message)
|
||||
{
|
||||
std::memcpy(&host_message, message.get_ptr(), sizeof(SceNpClansMessage));
|
||||
host_message = *message;
|
||||
}
|
||||
|
||||
SceNpClansError ret = clans_manager.client->send_membership_response(nph, handle, clanId, host_npid, &host_message, allow);
|
||||
const SceNpClansError ret = clans_manager.client->send_membership_response(nph, handle, clanId, host_npid, host_message, allow);
|
||||
if (ret != SCE_NP_CLANS_SUCCESS)
|
||||
{
|
||||
return ret;
|
||||
|
|
@ -963,13 +948,13 @@ error_code sceNpClansGetBlacklist(SceNpClansRequestHandle handle, SceNpClanId cl
|
|||
SceNpClansPagingRequest host_paging = {};
|
||||
if (paging)
|
||||
{
|
||||
std::memcpy(&host_paging, paging.get_ptr(), sizeof(SceNpClansPagingRequest));
|
||||
host_paging = *paging;
|
||||
}
|
||||
|
||||
SceNpClansBlacklistEntry host_blacklist[SCE_NP_CLANS_PAGING_REQUEST_PAGE_MAX] = {};
|
||||
std::vector<SceNpClansBlacklistEntry> host_blacklist(SCE_NP_CLANS_PAGING_REQUEST_PAGE_MAX);
|
||||
SceNpClansPagingResult host_pageResult = {};
|
||||
|
||||
SceNpClansError ret = clans_manager.client->get_blacklist(nph, handle, clanId, &host_paging, host_blacklist, &host_pageResult);
|
||||
const SceNpClansError ret = clans_manager.client->get_blacklist(nph, handle, clanId, host_paging, host_blacklist, host_pageResult);
|
||||
if (ret != SCE_NP_CLANS_SUCCESS)
|
||||
{
|
||||
return ret;
|
||||
|
|
@ -977,9 +962,9 @@ error_code sceNpClansGetBlacklist(SceNpClansRequestHandle handle, SceNpClanId cl
|
|||
|
||||
if (bl && host_pageResult.count > 0)
|
||||
{
|
||||
std::memcpy(bl.get_ptr(), host_blacklist, sizeof(SceNpClansBlacklistEntry) * host_pageResult.count);
|
||||
std::memcpy(bl.get_ptr(), host_blacklist.data(), sizeof(SceNpClansBlacklistEntry) * host_pageResult.count);
|
||||
}
|
||||
std::memcpy(pageResult.get_ptr(), &host_pageResult, sizeof(SceNpClansPagingResult));
|
||||
*pageResult = host_pageResult;
|
||||
|
||||
return CELL_OK;
|
||||
}
|
||||
|
|
@ -1001,10 +986,9 @@ error_code sceNpClansAddBlacklistEntry(SceNpClansRequestHandle handle, SceNpClan
|
|||
auto& nph = g_fxo->get<named_thread<np::np_handler>>();
|
||||
auto& clans_manager = g_fxo->get<sce_np_clans_manager>();
|
||||
|
||||
SceNpId host_member = {};
|
||||
std::memcpy(&host_member, member.get_ptr(), sizeof(SceNpId));
|
||||
const SceNpId host_member = *member;
|
||||
|
||||
SceNpClansError ret = clans_manager.client->add_blacklist_entry(nph, handle, clanId, host_member);
|
||||
const SceNpClansError ret = clans_manager.client->add_blacklist_entry(nph, handle, clanId, host_member);
|
||||
if (ret != SCE_NP_CLANS_SUCCESS)
|
||||
{
|
||||
return ret;
|
||||
|
|
@ -1030,10 +1014,9 @@ error_code sceNpClansRemoveBlacklistEntry(SceNpClansRequestHandle handle, SceNpC
|
|||
auto& nph = g_fxo->get<named_thread<np::np_handler>>();
|
||||
auto& clans_manager = g_fxo->get<sce_np_clans_manager>();
|
||||
|
||||
SceNpId host_member = {};
|
||||
std::memcpy(&host_member, member.get_ptr(), sizeof(SceNpId));
|
||||
const SceNpId host_member = *member;
|
||||
|
||||
SceNpClansError ret = clans_manager.client->remove_blacklist_entry(nph, handle, clanId, host_member);
|
||||
const SceNpClansError ret = clans_manager.client->remove_blacklist_entry(nph, handle, clanId, host_member);
|
||||
if (ret != SCE_NP_CLANS_SUCCESS)
|
||||
{
|
||||
return ret;
|
||||
|
|
@ -1070,13 +1053,13 @@ error_code sceNpClansRetrieveAnnouncements(SceNpClansRequestHandle handle, SceNp
|
|||
SceNpClansPagingRequest host_paging = {};
|
||||
if (paging)
|
||||
{
|
||||
std::memcpy(&host_paging, paging.get_ptr(), sizeof(SceNpClansPagingRequest));
|
||||
host_paging = *paging;
|
||||
}
|
||||
|
||||
SceNpClansMessageEntry host_announcements[SCE_NP_CLANS_PAGING_REQUEST_PAGE_MAX] = {};
|
||||
std::vector<SceNpClansMessageEntry> host_announcements(SCE_NP_CLANS_PAGING_REQUEST_PAGE_MAX);
|
||||
SceNpClansPagingResult host_pageResult = {};
|
||||
|
||||
SceNpClansError ret = clans_manager.client->retrieve_announcements(nph, handle, clanId, &host_paging, host_announcements, &host_pageResult);
|
||||
const SceNpClansError ret = clans_manager.client->retrieve_announcements(nph, handle, clanId, host_paging, host_announcements, host_pageResult);
|
||||
if (ret != SCE_NP_CLANS_SUCCESS)
|
||||
{
|
||||
return ret;
|
||||
|
|
@ -1084,9 +1067,9 @@ error_code sceNpClansRetrieveAnnouncements(SceNpClansRequestHandle handle, SceNp
|
|||
|
||||
if (mlist && host_pageResult.count > 0)
|
||||
{
|
||||
std::memcpy(mlist.get_ptr(), host_announcements, sizeof(SceNpClansMessageEntry) * host_pageResult.count);
|
||||
std::memcpy(mlist.get_ptr(), host_announcements.data(), sizeof(SceNpClansMessageEntry) * host_pageResult.count);
|
||||
}
|
||||
std::memcpy(pageResult.get_ptr(), &host_pageResult, sizeof(SceNpClansPagingResult));
|
||||
*pageResult = host_pageResult;
|
||||
|
||||
return CELL_OK;
|
||||
}
|
||||
|
|
@ -1113,17 +1096,16 @@ error_code sceNpClansPostAnnouncement(SceNpClansRequestHandle handle, SceNpClanI
|
|||
auto& clans_manager = g_fxo->get<sce_np_clans_manager>();
|
||||
auto& nph = g_fxo->get<named_thread<np::np_handler>>();
|
||||
|
||||
SceNpClansMessage host_announcement = {};
|
||||
std::memcpy(&host_announcement, message.get_ptr(), sizeof(SceNpClansMessage));
|
||||
const SceNpClansMessage host_announcement = *message;
|
||||
|
||||
SceNpClansMessageData host_data = {};
|
||||
if (data)
|
||||
{
|
||||
std::memcpy(&host_data, data.get_ptr(), sizeof(SceNpClansMessageData));
|
||||
host_data = *data;
|
||||
}
|
||||
|
||||
SceNpClansMessageId host_announcementId = 0;
|
||||
SceNpClansError ret = clans_manager.client->post_announcement(nph, handle, clanId, &host_announcement, &host_data, duration, &host_announcementId);
|
||||
const SceNpClansError ret = clans_manager.client->post_announcement(nph, handle, clanId, host_announcement, host_data, duration, host_announcementId);
|
||||
if (ret != SCE_NP_CLANS_SUCCESS)
|
||||
{
|
||||
return ret;
|
||||
|
|
@ -1146,7 +1128,7 @@ error_code sceNpClansRemoveAnnouncement(SceNpClansRequestHandle handle, SceNpCla
|
|||
auto& clans_manager = g_fxo->get<sce_np_clans_manager>();
|
||||
auto& nph = g_fxo->get<named_thread<np::np_handler>>();
|
||||
|
||||
SceNpClansError ret = clans_manager.client->delete_announcement(nph, handle, clanId, mId);
|
||||
const SceNpClansError ret = clans_manager.client->delete_announcement(nph, handle, clanId, mId);
|
||||
if (ret != SCE_NP_CLANS_SUCCESS)
|
||||
{
|
||||
return ret;
|
||||
|
|
|
|||
|
|
@ -246,7 +246,7 @@ private:
|
|||
{0x054C, 0x01C8, 0x01C8, "PSP Type A", nullptr, nullptr},
|
||||
{0x054C, 0x01C9, 0x01C9, "PSP Type B", nullptr, nullptr},
|
||||
{0x054C, 0x01CA, 0x01CA, "PSP Type C", nullptr, nullptr},
|
||||
{0x054C, 0x01CB, 0x01CB, "PSP Type D", nullptr, nullptr},
|
||||
{0x054C, 0x01CB, 0x01CB, "PSP Type D", nullptr, nullptr}, // UsbPspCm
|
||||
{0x054C, 0x02D2, 0x02D2, "PSP Slim", nullptr, nullptr},
|
||||
|
||||
// 0x0900: "H050 USJ(C) PCB rev00", 0x0910: "USIO PCB rev00"
|
||||
|
|
@ -261,9 +261,6 @@ private:
|
|||
// Tony Hawk RIDE Skateboard
|
||||
{0x12BA, 0x0400, 0x0400, "Tony Hawk RIDE Skateboard Controller", nullptr, nullptr},
|
||||
|
||||
// PSP in UsbPspCm mode
|
||||
{0x054C, 0x01CB, 0x01CB, "UsbPspcm", nullptr, nullptr},
|
||||
|
||||
// Sony Stereo Headsets
|
||||
{0x12BA, 0x0032, 0x0032, "Wireless Stereo Headset", nullptr, nullptr},
|
||||
{0x12BA, 0x0042, 0x0042, "Wireless Stereo Headset", nullptr, nullptr},
|
||||
|
|
@ -636,6 +633,8 @@ void usb_handler_thread::operator()()
|
|||
// Process asynchronous requests that are pending
|
||||
libusb_handle_events_timeout_completed(ctx, &lusb_tv, nullptr);
|
||||
|
||||
u64 delay = 1'000;
|
||||
|
||||
// Process fake transfers
|
||||
if (!fake_transfers.empty())
|
||||
{
|
||||
|
|
@ -650,6 +649,13 @@ void usb_handler_thread::operator()()
|
|||
|
||||
if (transfer->expected_time > timestamp)
|
||||
{
|
||||
const u64 diff_time = transfer->expected_time - timestamp;
|
||||
|
||||
if (diff_time < delay)
|
||||
{
|
||||
delay = diff_time;
|
||||
}
|
||||
|
||||
++it;
|
||||
continue;
|
||||
}
|
||||
|
|
@ -668,7 +674,7 @@ void usb_handler_thread::operator()()
|
|||
if (handled_devices.empty())
|
||||
thread_ctrl::wait_for(500'000);
|
||||
else
|
||||
thread_ctrl::wait_for(1'000);
|
||||
thread_ctrl::wait_for(delay);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -878,7 +884,9 @@ std::pair<u32, UsbTransfer&> usb_handler_thread::get_free_transfer()
|
|||
|
||||
u32 transfer_id = get_free_transfer_id();
|
||||
auto& transfer = get_transfer(transfer_id);
|
||||
transfer.busy = true;
|
||||
|
||||
libusb_transfer* const transfer_buf = transfer.transfer;
|
||||
transfer = {.transfer_id = transfer_id, .transfer = transfer_buf, .busy = true};
|
||||
|
||||
return {transfer_id, transfer};
|
||||
}
|
||||
|
|
@ -1046,6 +1054,27 @@ void connect_usb_controller(u8 index, input::product_type type)
|
|||
}
|
||||
}
|
||||
|
||||
void reconnect_usb(u32 assigned_number)
|
||||
{
|
||||
auto usbh = g_fxo->try_get<named_thread<usb_handler_thread>>();
|
||||
if (!usbh)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard lock(usbh->mutex);
|
||||
for (auto& [nr, pair] : usbh->handled_devices)
|
||||
{
|
||||
auto& [internal_dev, dev] = pair;
|
||||
if (nr == assigned_number)
|
||||
{
|
||||
usbh->disconnect_usb_device(dev, false);
|
||||
usbh->connect_usb_device(dev, false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void handle_hotplug_event(bool connected)
|
||||
{
|
||||
if (auto usbh = g_fxo->try_get<named_thread<usb_handler_thread>>())
|
||||
|
|
|
|||
|
|
@ -89,4 +89,5 @@ error_code sys_usbd_register_extra_ldd(ppu_thread& ppu, u32 handle, vm::cptr<cha
|
|||
error_code sys_usbd_unregister_extra_ldd(ppu_thread& ppu, u32 handle, vm::cptr<char> s_product, u16 slen_product);
|
||||
|
||||
void connect_usb_controller(u8 index, input::product_type);
|
||||
void reconnect_usb(u32 assigned_number);
|
||||
void handle_hotplug_event(bool connected);
|
||||
|
|
|
|||
|
|
@ -700,8 +700,3 @@ void usb_device_dimensions::interrupt_transfer(u32 buf_size, u8* buf, u32 endpoi
|
|||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void usb_device_dimensions::isochronous_transfer(UsbTransfer* transfer)
|
||||
{
|
||||
usb_device_emulated::isochronous_transfer(transfer);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -76,7 +76,6 @@ public:
|
|||
|
||||
void control_transfer(u8 bmRequestType, u8 bRequest, u16 wValue, u16 wIndex, u16 wLength, u32 buf_size, u8* buf, UsbTransfer* transfer) override;
|
||||
void interrupt_transfer(u32 buf_size, u8* buf, u32 endpoint, UsbTransfer* transfer) override;
|
||||
void isochronous_transfer(UsbTransfer* transfer) override;
|
||||
|
||||
protected:
|
||||
std::queue<std::array<u8, 32>> m_queries;
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ kamen_rider_figure& rider_gate::get_figure_by_uid(const std::array<u8, 7> uid)
|
|||
return figures[7];
|
||||
}
|
||||
|
||||
void rider_gate::get_blank_response(u8 command, u8 sequence, std::array<u8, 64>& reply_buf)
|
||||
void rider_gate::get_blank_response(std::array<u8, 64>& reply_buf, u8 command, u8 sequence)
|
||||
{
|
||||
reply_buf = {0x55, 0x02, command, sequence};
|
||||
reply_buf[4] = generate_checksum(reply_buf, 4);
|
||||
|
|
@ -93,7 +93,7 @@ void rider_gate::query_block(std::array<u8, 64>& reply_buf, u8 command, u8 seque
|
|||
reply_buf[21] = generate_checksum(reply_buf, 21);
|
||||
}
|
||||
|
||||
void rider_gate::write_block(std::array<u8, 64>& replyBuf, u8 command, u8 sequence, const u8* uid, u8 sector, u8 block, const u8* to_write_buf)
|
||||
void rider_gate::write_block(std::array<u8, 64>& reply_buf, u8 command, u8 sequence, const u8* uid, u8 sector, u8 block, const u8* to_write_buf)
|
||||
{
|
||||
std::lock_guard lock(kamen_mutex);
|
||||
|
||||
|
|
@ -108,7 +108,7 @@ void rider_gate::write_block(std::array<u8, 64>& replyBuf, u8 command, u8 sequen
|
|||
}
|
||||
}
|
||||
|
||||
get_blank_response(command, sequence, replyBuf);
|
||||
get_blank_response(reply_buf, command, sequence);
|
||||
}
|
||||
|
||||
std::optional<std::array<u8, 64>> rider_gate::pop_added_removed_response()
|
||||
|
|
@ -190,11 +190,50 @@ u8 rider_gate::load_figure(const std::array<u8, 0x14 * 0x10>& buf, fs::file in_f
|
|||
usb_device_kamen_rider::usb_device_kamen_rider(const std::array<u8, 7>& location)
|
||||
: usb_device_emulated(location)
|
||||
{
|
||||
device = UsbDescriptorNode(USB_DESCRIPTOR_DEVICE, UsbDeviceDescriptor{0x200, 0x0, 0x0, 0x0, 0x40, 0x0E6F, 0x200A, 0x100, 0x1, 0x2, 0x3, 0x1});
|
||||
auto& config0 = device.add_node(UsbDescriptorNode(USB_DESCRIPTOR_CONFIG, UsbDeviceConfiguration{0x29, 0x1, 0x1, 0x0, 0x80, 0xFA}));
|
||||
config0.add_node(UsbDescriptorNode(USB_DESCRIPTOR_INTERFACE, UsbDeviceInterface{0x0, 0x0, 0x2, 0x3, 0x0, 0x0, 0x0}));
|
||||
config0.add_node(UsbDescriptorNode(USB_DESCRIPTOR_ENDPOINT, UsbDeviceEndpoint{0x81, 0x3, 0x40, 0x1}));
|
||||
config0.add_node(UsbDescriptorNode(USB_DESCRIPTOR_ENDPOINT, UsbDeviceEndpoint{0x1, 0x3, 0x40, 0x1}));
|
||||
device = UsbDescriptorNode(USB_DESCRIPTOR_DEVICE, UsbDeviceDescriptor{
|
||||
.bcdUSB = 0x0200,
|
||||
.bDeviceClass = 0x00,
|
||||
.bDeviceSubClass = 0x00,
|
||||
.bDeviceProtocol = 0x00,
|
||||
.bMaxPacketSize0 = 0x40,
|
||||
.idVendor = 0x0E6F,
|
||||
.idProduct = 0x200A,
|
||||
.bcdDevice = 0x0100,
|
||||
.iManufacturer = 0x01,
|
||||
.iProduct = 0x02,
|
||||
.iSerialNumber = 0x03,
|
||||
.bNumConfigurations = 0x01});
|
||||
auto& config0 = device.add_node(UsbDescriptorNode(USB_DESCRIPTOR_CONFIG, UsbDeviceConfiguration{
|
||||
.wTotalLength = 0x0029,
|
||||
.bNumInterfaces = 0x01,
|
||||
.bConfigurationValue = 0x01,
|
||||
.iConfiguration = 0x00,
|
||||
.bmAttributes = 0x80,
|
||||
.bMaxPower = 0xFA}));
|
||||
config0.add_node(UsbDescriptorNode(USB_DESCRIPTOR_INTERFACE, UsbDeviceInterface{
|
||||
.bInterfaceNumber = 0x00,
|
||||
.bAlternateSetting = 0x00,
|
||||
.bNumEndpoints = 0x02,
|
||||
.bInterfaceClass = 0x03,
|
||||
.bInterfaceSubClass = 0x00,
|
||||
.bInterfaceProtocol = 0x00,
|
||||
.iInterface = 0x00}));
|
||||
config0.add_node(UsbDescriptorNode(USB_DESCRIPTOR_HID, UsbDeviceHID{
|
||||
.bcdHID = 0x0100,
|
||||
.bCountryCode = 0x00,
|
||||
.bNumDescriptors = 0x01,
|
||||
.bDescriptorType = 0x22,
|
||||
.wDescriptorLength = 0x001d}));
|
||||
config0.add_node(UsbDescriptorNode(USB_DESCRIPTOR_ENDPOINT, UsbDeviceEndpoint{
|
||||
.bEndpointAddress = 0x81,
|
||||
.bmAttributes = 0x03,
|
||||
.wMaxPacketSize = 0x0040,
|
||||
.bInterval = 0x1}));
|
||||
config0.add_node(UsbDescriptorNode(USB_DESCRIPTOR_ENDPOINT, UsbDeviceEndpoint{
|
||||
.bEndpointAddress = 0x01,
|
||||
.bmAttributes = 0x03,
|
||||
.wMaxPacketSize = 0x0040,
|
||||
.bInterval = 0x1}));
|
||||
}
|
||||
|
||||
usb_device_kamen_rider::~usb_device_kamen_rider()
|
||||
|
|
@ -227,7 +266,7 @@ void usb_device_kamen_rider::interrupt_transfer(u32 buf_size, u8* buf, u32 endpo
|
|||
if (endpoint == 0x81)
|
||||
{
|
||||
// Respond after FF command
|
||||
transfer->expected_time = get_timestamp() + 1000;
|
||||
transfer->expected_time = get_timestamp() + 22000;
|
||||
std::optional<std::array<u8, 64>> response = g_ridergate.pop_added_removed_response();
|
||||
if (response)
|
||||
{
|
||||
|
|
@ -246,6 +285,7 @@ void usb_device_kamen_rider::interrupt_transfer(u32 buf_size, u8* buf, u32 endpo
|
|||
}
|
||||
else if (endpoint == 0x01)
|
||||
{
|
||||
transfer->expected_time = get_timestamp() + 10;
|
||||
const u8 command = buf[2];
|
||||
const u8 sequence = buf[3];
|
||||
|
||||
|
|
@ -261,7 +301,7 @@ void usb_device_kamen_rider::interrupt_transfer(u32 buf_size, u8* buf, u32 endpo
|
|||
case 0xC0:
|
||||
case 0xC3: // Color Commands
|
||||
{
|
||||
g_ridergate.get_blank_response(command, sequence, q_result);
|
||||
g_ridergate.get_blank_response(q_result, command, sequence);
|
||||
break;
|
||||
}
|
||||
case 0xD0: // Tag List
|
||||
|
|
|
|||
|
|
@ -18,11 +18,11 @@ struct kamen_rider_figure
|
|||
class rider_gate
|
||||
{
|
||||
public:
|
||||
void get_blank_response(u8 command, u8 sequence, std::array<u8, 64>& reply_buf);
|
||||
void wake_rider_gate(std::array<u8, 64>& replyBuf, u8 command, u8 sequence);
|
||||
void get_list_tags(std::array<u8, 64>& replyBuf, u8 command, u8 sequence);
|
||||
void query_block(std::array<u8, 64>& replyBuf, u8 command, u8 sequence, const u8* uid, u8 sector, u8 block);
|
||||
void write_block(std::array<u8, 64>& replyBuf, u8 command, u8 sequence, const u8* uid, u8 sector, u8 block, const u8* to_write_buf);
|
||||
void get_blank_response(std::array<u8, 64>& reply_buf, u8 command, u8 sequence);
|
||||
void wake_rider_gate(std::array<u8, 64>& reply_buf, u8 command, u8 sequence);
|
||||
void get_list_tags(std::array<u8, 64>& reply_buf, u8 command, u8 sequence);
|
||||
void query_block(std::array<u8, 64>& reply_buf, u8 command, u8 sequence, const u8* uid, u8 sector, u8 block);
|
||||
void write_block(std::array<u8, 64>& reply_buf, u8 command, u8 sequence, const u8* uid, u8 sector, u8 block, const u8* to_write_buf);
|
||||
std::optional<std::array<u8, 64>> pop_added_removed_response();
|
||||
|
||||
bool remove_figure(u8 position);
|
||||
|
|
|
|||
|
|
@ -17,7 +17,254 @@
|
|||
#include "Input/pad_thread.h"
|
||||
#include "Input/sdl_instance.h"
|
||||
|
||||
LOG_CHANNEL(logitech_g27_log, "LOGIG27");
|
||||
LOG_CHANNEL(logitech_g27_log, "logitech_g27");
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct DFEX_data
|
||||
{
|
||||
u8 square : 1;
|
||||
u8 cross : 1;
|
||||
u8 circle : 1;
|
||||
u8 triangle : 1;
|
||||
u8 l1 : 1; // Left_paddle
|
||||
u8 r1 : 1; // Right_paddle
|
||||
u8 l2 : 1;
|
||||
u8 r2 : 1;
|
||||
|
||||
u8 select : 1; // Share
|
||||
u8 start : 1; // Options
|
||||
u8 l3 : 1;
|
||||
u8 r3 : 1;
|
||||
u8 ps : 1;
|
||||
u8 : 3;
|
||||
|
||||
u8 dpad; // 00=N, 01=NE, 02=E, 03=SW, 04=S, 05=SW, 06=W, 07=NW, 08=IDLE
|
||||
u8 steering; // 00=Left, ff=Right
|
||||
u8 brake_throttle; // 00=ThrottlePressed, 7f=BothReleased/BothPressed, ff=BrakePressed
|
||||
u8 const1;
|
||||
u8 const2;
|
||||
u32 : 32;
|
||||
u32 : 32;
|
||||
u16 : 16;
|
||||
u8 brake; // 00=Released, ff=Pressed
|
||||
u8 throttle; // 00=Released, ff=Pressed
|
||||
u32 : 32;
|
||||
u32 : 32;
|
||||
};
|
||||
|
||||
struct DFP_data
|
||||
{
|
||||
u16 steering : 14; // 0000=Left, 1fff=Mid, 3fff=Right
|
||||
u16 cross: 1;
|
||||
u16 square : 1;
|
||||
|
||||
u8 circle : 1;
|
||||
u8 triangle : 1;
|
||||
u8 r1 : 1; // Right_paddle
|
||||
u8 l1 : 1; // Left_paddle
|
||||
u8 r2 : 1;
|
||||
u8 l2 : 1;
|
||||
u8 select : 1; // Share
|
||||
u8 start : 1; // Options
|
||||
|
||||
u8 r3 : 1;
|
||||
u8 l3 : 1;
|
||||
u8 r3_2 : 1;
|
||||
u8 l3_2 : 1;
|
||||
u8 dpad : 4; // 0=N, 1=NE, 2=E, 3=SW, 4=S, 5=SW, 6=W, 7=NW, 8=IDLE
|
||||
|
||||
u8 brake_throttle; // 00=ThrottlePressed, 7f=BothReleased/BothPressed, ff=BrakePressed
|
||||
u8 throttle; // 00=Pressed, ff=Released
|
||||
u8 brake; // 00=Pressed, ff=Released
|
||||
|
||||
u8 pedals_attached : 1;
|
||||
u8 powered : 1;
|
||||
u8 : 1;
|
||||
u8 self_check_done : 1;
|
||||
u8 set1 : 1; // always set
|
||||
u8 : 3;
|
||||
};
|
||||
|
||||
struct DFGT_data
|
||||
{
|
||||
u8 dpad : 4; // 0=N, 1=NE, 2=E, 3=SW, 4=S, 5=SW, 6=W, 7=NW, 8=IDLE
|
||||
u8 cross: 1;
|
||||
u8 square : 1;
|
||||
u8 circle : 1;
|
||||
u8 triangle : 1;
|
||||
|
||||
u8 r1 : 1; // Right_paddle
|
||||
u8 l1 : 1; // Left_paddle
|
||||
u8 r2 : 1;
|
||||
u8 l2 : 1;
|
||||
u8 select : 1; // Share
|
||||
u8 start : 1; // Options
|
||||
u8 r3 : 1;
|
||||
u8 l3 : 1;
|
||||
|
||||
u8 : 2;
|
||||
u8 dial_center : 1;
|
||||
u8 plus : 1;
|
||||
u8 dial_cw : 1;
|
||||
u8 dial_ccw : 1;
|
||||
u8 minus : 1;
|
||||
u8 : 1;
|
||||
|
||||
u8 ps : 1;
|
||||
u8 pedals_attached : 1;
|
||||
u8 powered : 1;
|
||||
u8 self_check_done : 1;
|
||||
u8 set1 : 1;
|
||||
u8 : 1;
|
||||
u8 set2 : 1;
|
||||
u8 : 1;
|
||||
|
||||
u16 steering : 14; // 0000=Left, 1fff=Mid, 3fff=Right
|
||||
u16 : 2;
|
||||
u8 throttle; // 00=Pressed, ff=Released
|
||||
u8 brake; // 00=Pressed, ff=Released
|
||||
};
|
||||
|
||||
struct G25_data
|
||||
{
|
||||
u8 dpad : 4; // 0=N, 1=NE, 2=E, 3=SW, 4=S, 5=SW, 6=W, 7=NW, 8=IDLE
|
||||
u8 cross: 1;
|
||||
u8 square : 1;
|
||||
u8 circle : 1;
|
||||
u8 triangle : 1;
|
||||
|
||||
u8 r1 : 1; // Right_paddle
|
||||
u8 l1 : 1; // Left_paddle
|
||||
u8 r2 : 1; // + dial_center
|
||||
u8 l2 : 1;
|
||||
u8 select : 1; // Share
|
||||
u8 start : 1; // Options
|
||||
u8 r3 : 1; // + dial_cw + plus
|
||||
u8 l3 : 1; // + dial_ccw + minus
|
||||
|
||||
u8 gear1 : 1;
|
||||
u8 gear2 : 1;
|
||||
u8 gear3 : 1;
|
||||
u8 gear4 : 1;
|
||||
u8 gear5 : 1;
|
||||
u8 gear6 : 1;
|
||||
u8 gearR : 1;
|
||||
u8 : 1;
|
||||
|
||||
u16 pedals_detached : 1;
|
||||
u16 powered : 1;
|
||||
u16 steering : 14; // 0000=Left, 1fff=Mid, 3fff=Right
|
||||
|
||||
u8 throttle; // 00=Pressed, ff=Released
|
||||
u8 brake; // 00=Pressed, ff=Released
|
||||
u8 clutch; // 00=Pressed, ff=Released
|
||||
|
||||
u8 shifter_x; // 30=left(1,2), 7a=middle(3,4), b2=right(5,6)
|
||||
u8 shifter_y; // 32=bottom(2,4,6), b7=top(1,3,5)
|
||||
|
||||
u8 shifter_attached : 1;
|
||||
u8 set1 : 1;
|
||||
u8 : 1;
|
||||
u8 shifter_pressed : 1;
|
||||
u8 : 4;
|
||||
};
|
||||
|
||||
struct G27_data
|
||||
{
|
||||
u8 dpad : 4; // 0=N, 1=NE, 2=E, 3=SW, 4=S, 5=SW, 6=W, 7=NW, 8=IDLE
|
||||
u8 cross: 1;
|
||||
u8 square : 1;
|
||||
u8 circle : 1;
|
||||
u8 triangle : 1;
|
||||
|
||||
u8 r1 : 1; // Right_paddle
|
||||
u8 l1 : 1; // Left_paddle
|
||||
u8 r2 : 1;
|
||||
u8 l2 : 1;
|
||||
u8 select : 1; // Share
|
||||
u8 start : 1; // Options
|
||||
u8 r3 : 1; // + dial_center
|
||||
u8 l3 : 1;
|
||||
|
||||
u8 gear1 : 1;
|
||||
u8 gear2 : 1;
|
||||
u8 gear3 : 1;
|
||||
u8 gear4 : 1;
|
||||
u8 gear5 : 1;
|
||||
u8 gear6 : 1;
|
||||
u8 dial_cw : 1;
|
||||
u8 dial_ccw : 1;
|
||||
|
||||
u16 plus : 1;
|
||||
u16 minus : 1;
|
||||
u16 steering : 14; // 0000=Left, 1fff=Mid, 3fff=Right
|
||||
|
||||
u8 throttle; // 00=Pressed, ff=Released
|
||||
u8 brake; // 00=Pressed, ff=Released
|
||||
u8 clutch; // 00=Pressed, ff=Released
|
||||
|
||||
u8 shifter_x; // 30=left(1,2), 7a=middle(3,4), b2=right(5,6)
|
||||
u8 shifter_y; // 32=bottom(2,4,6), b7=top(1,3,5)
|
||||
|
||||
u8 gearR : 1;
|
||||
u8 pedals_detached : 1;
|
||||
u8 powered : 1;
|
||||
u8 shifter_attached : 1;
|
||||
u8 set1 : 1;
|
||||
u8 : 1;
|
||||
u8 shifter_pressed : 1;
|
||||
u8 range : 1;
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
static const std::map<logitech_personality,
|
||||
std::pair<UsbDeviceDescriptor, std::array<u8, 0x29>>> s_logitech_personality = {
|
||||
{
|
||||
logitech_personality::driving_force_ex,
|
||||
{
|
||||
UsbDeviceDescriptor{0x0200, 0x00, 0x00, 0x00, 0x10, 0x046D, 0xC294, 0x1350, 0x01, 0x02, 0x00, 0x01},
|
||||
{0x09, 0x02, 0x29, 0x00, 0x01, 0x01, 0x04, 0x80, 0x31, 0x09, 0x04, 0x00, 0x00, 0x02, 0x03, 0x00,
|
||||
0x00, 0x00, 0x09, 0x21, 0x00, 0x01, 0x21, 0x01, 0x22, 0x9D, 0x00, 0x07, 0x05, 0x81, 0x03, 0x40,
|
||||
0x00, 0x0A, 0x07, 0x05, 0x01, 0x03, 0x10, 0x00, 0x0A}
|
||||
}
|
||||
},
|
||||
{
|
||||
logitech_personality::driving_force_pro,
|
||||
{
|
||||
UsbDeviceDescriptor{0x0200, 0x00, 0x00, 0x00, 0x10, 0x046D, 0xC298, 0x1350, 0x01, 0x02, 0x00, 0x01},
|
||||
{0x09, 0x02, 0x29, 0x00, 0x01, 0x01, 0x04, 0x80, 0x31, 0x09, 0x04, 0x00, 0x00, 0x02, 0x03, 0x00,
|
||||
0x00, 0x00, 0x09, 0x21, 0x00, 0x01, 0x21, 0x01, 0x22, 0x61, 0x00, 0x07, 0x05, 0x81, 0x03, 0x08,
|
||||
0x00, 0x0A, 0x07, 0x05, 0x01, 0x03, 0x08, 0x00, 0x0A}
|
||||
}
|
||||
},
|
||||
{
|
||||
logitech_personality::g25,
|
||||
{
|
||||
UsbDeviceDescriptor{0x0200, 0x00, 0x00, 0x00, 0x10, 0x046D, 0xC299, 0x1350, 0x01, 0x02, 0x00, 0x01},
|
||||
{0x09, 0x02, 0x29, 0x00, 0x01, 0x01, 0x04, 0x80, 0x31, 0x09, 0x04, 0x00, 0x00, 0x02, 0x03, 0x00,
|
||||
0x00, 0x00, 0x09, 0x21, 0x11, 0x01, 0x21, 0x01, 0x22, 0x6F, 0x00, 0x07, 0x05, 0x81, 0x03, 0x10,
|
||||
0x00, 0x02, 0x07, 0x05, 0x01, 0x03, 0x10, 0x00, 0x02}
|
||||
}
|
||||
},
|
||||
{
|
||||
logitech_personality::driving_force_gt,
|
||||
{
|
||||
UsbDeviceDescriptor{0x0200, 0x00, 0x00, 0x00, 0x10, 0x046D, 0xC29A, 0x1350, 0x00, 0x02, 0x00, 0x01},
|
||||
{0x09, 0x02, 0x29, 0x00, 0x01, 0x01, 0x00, 0x80, 0x31, 0x09, 0x04, 0x00, 0x00, 0x02, 0x03, 0x00,
|
||||
0x00, 0xFE, 0x09, 0x21, 0x11, 0x01, 0x21, 0x01, 0x22, 0x73, 0x00, 0x07, 0x05, 0x81, 0x03, 0x10,
|
||||
0x00, 0x02, 0x07, 0x05, 0x01, 0x03, 0x10, 0x00, 0x02}
|
||||
}
|
||||
},
|
||||
{
|
||||
logitech_personality::g27,
|
||||
{
|
||||
UsbDeviceDescriptor{0x0200, 0x00, 0x00, 0x00, 0x10, 0x046D, 0xC29B, 0x1350, 0x01, 0x02, 0x00, 0x01},
|
||||
{0x09, 0x02, 0x29, 0x00, 0x01, 0x01, 0x04, 0x80, 0x31, 0x09, 0x04, 0x00, 0x00, 0x02, 0x03, 0x00,
|
||||
0x00, 0x00, 0x09, 0x21, 0x11, 0x01, 0x21, 0x01, 0x22, 0x85, 0x00, 0x07, 0x05, 0x81, 0x03, 0x10,
|
||||
0x00, 0x02, 0x07, 0x05, 0x01, 0x03, 0x10, 0x00, 0x02}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// ref: https://github.com/libsdl-org/SDL/issues/7941, need to use SDL_HAPTIC_STEERING_AXIS for some windows drivers
|
||||
static const SDL_HapticDirection STEERING_DIRECTION =
|
||||
|
|
@ -26,20 +273,31 @@ static const SDL_HapticDirection STEERING_DIRECTION =
|
|||
.dir = {0, 0, 0}
|
||||
};
|
||||
|
||||
usb_device_logitech_g27::usb_device_logitech_g27(u32 controller_index, const std::array<u8, 7>& location)
|
||||
: usb_device_emulated(location), m_controller_index(controller_index)
|
||||
void usb_device_logitech_g27::set_personality(logitech_personality personality, bool reconnect)
|
||||
{
|
||||
device = UsbDescriptorNode(USB_DESCRIPTOR_DEVICE, UsbDeviceDescriptor{0x0200, 0, 0, 0, 16, 0x046d, 0xc29b, 0x1350, 1, 2, 0, 1});
|
||||
m_personality = personality;
|
||||
device = UsbDescriptorNode(USB_DESCRIPTOR_DEVICE, ::at32(s_logitech_personality, personality).first);
|
||||
|
||||
// parse the raw response like with passthrough device
|
||||
static constexpr u8 raw_config[] = {0x9, 0x2, 0x29, 0x0, 0x1, 0x1, 0x4, 0x80, 0x31, 0x9, 0x4, 0x0, 0x0, 0x2, 0x3, 0x0, 0x0, 0x0, 0x9, 0x21, 0x11, 0x1, 0x21, 0x1, 0x22, 0x85, 0x0, 0x7, 0x5, 0x81, 0x3, 0x10, 0x0, 0x2, 0x7, 0x5, 0x1, 0x3, 0x10, 0x0, 0x2};
|
||||
const u8* raw_config = ::at32(s_logitech_personality, personality).second.data();
|
||||
auto& conf = device.add_node(UsbDescriptorNode(raw_config[0], raw_config[1], &raw_config[2]));
|
||||
for (unsigned int index = raw_config[0]; index < sizeof(raw_config);)
|
||||
for (unsigned int index = raw_config[0]; index < raw_config[2];)
|
||||
{
|
||||
conf.add_node(UsbDescriptorNode(raw_config[index], raw_config[index + 1], &raw_config[index + 2]));
|
||||
index += raw_config[index];
|
||||
}
|
||||
|
||||
if (reconnect)
|
||||
{
|
||||
reconnect_usb(assigned_number);
|
||||
}
|
||||
}
|
||||
|
||||
usb_device_logitech_g27::usb_device_logitech_g27(u32 controller_index, const std::array<u8, 7>& location)
|
||||
: usb_device_emulated(location), m_controller_index(controller_index)
|
||||
{
|
||||
set_personality(logitech_personality::driving_force_ex);
|
||||
|
||||
m_default_spring_effect.type = SDL_HAPTIC_SPRING;
|
||||
m_default_spring_effect.condition.direction = STEERING_DIRECTION;
|
||||
m_default_spring_effect.condition.length = SDL_HAPTIC_INFINITY;
|
||||
|
|
@ -63,7 +321,14 @@ usb_device_logitech_g27::usb_device_logitech_g27(u32 controller_index, const std
|
|||
while (thread_ctrl::state() != thread_state::aborting)
|
||||
{
|
||||
sdl_refresh();
|
||||
thread_ctrl::wait_for(5'000'000);
|
||||
thread_ctrl::wait_for(1'000'000);
|
||||
|
||||
std::unique_lock lock(g_cfg_logitech_g27.m_mutex);
|
||||
if (logitech_personality::invalid != m_next_personality && m_personality != m_next_personality)
|
||||
{
|
||||
set_personality(m_next_personality, true);
|
||||
m_next_personality = logitech_personality::invalid;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -115,7 +380,7 @@ u16 usb_device_logitech_g27::get_num_emu_devices()
|
|||
|
||||
void usb_device_logitech_g27::control_transfer(u8 bmRequestType, u8 bRequest, u16 wValue, u16 wIndex, u16 wLength, u32 buf_size, u8* buf, UsbTransfer* transfer)
|
||||
{
|
||||
logitech_g27_log.todo("control transfer bmRequestType %02x, bRequest %02x, wValue %04x, wIndex %04x, wLength %04x, %s", bmRequestType, bRequest, wValue, wIndex, wLength, fmt::buf_to_hexstring(buf, buf_size));
|
||||
logitech_g27_log.notice("control transfer bmRequestType %02x, bRequest %02x, wValue %04x, wIndex %04x, wLength %04x, %s", bmRequestType, bRequest, wValue, wIndex, wLength, fmt::buf_to_hexstring(buf, buf_size));
|
||||
|
||||
usb_device_emulated::control_transfer(bmRequestType, bRequest, wValue, wIndex, wLength, buf_size, buf, transfer);
|
||||
}
|
||||
|
|
@ -194,9 +459,11 @@ static inline logitech_g27_sdl_mapping get_runtime_mapping()
|
|||
|
||||
convert_mapping(cfg.dial_clockwise, mapping.dial_clockwise);
|
||||
convert_mapping(cfg.dial_anticlockwise, mapping.dial_anticlockwise);
|
||||
convert_mapping(cfg.dial_center, mapping.dial_center);
|
||||
|
||||
convert_mapping(cfg.select, mapping.select);
|
||||
convert_mapping(cfg.pause, mapping.pause);
|
||||
convert_mapping(cfg.start, mapping.start);
|
||||
convert_mapping(cfg.ps, mapping.ps);
|
||||
|
||||
convert_mapping(cfg.shifter_1, mapping.shifter_1);
|
||||
convert_mapping(cfg.shifter_2, mapping.shifter_2);
|
||||
|
|
@ -205,7 +472,6 @@ static inline logitech_g27_sdl_mapping get_runtime_mapping()
|
|||
convert_mapping(cfg.shifter_5, mapping.shifter_5);
|
||||
convert_mapping(cfg.shifter_6, mapping.shifter_6);
|
||||
convert_mapping(cfg.shifter_r, mapping.shifter_r);
|
||||
convert_mapping(cfg.shifter_press, mapping.shifter_press);
|
||||
|
||||
return mapping;
|
||||
}
|
||||
|
|
@ -467,6 +733,50 @@ static u8 hat_components_to_logitech_g27_hat(bool up, bool down, bool left, bool
|
|||
return sdl_hat_to_logitech_g27_hat(sdl_hat);
|
||||
}
|
||||
|
||||
static std::pair<u8, u8> shifter_to_coord_xy(bool shifter_1, bool shifter_2, bool shifter_3, bool shifter_4,
|
||||
bool shifter_5, bool shifter_6, bool shifter_r)
|
||||
{
|
||||
// rough analog values recorded in https://github.com/RPCS3/rpcs3/pull/17199#issuecomment-2883934412
|
||||
constexpr u8 coord_center = 0x80;
|
||||
constexpr u8 coord_top = 0xb7;
|
||||
constexpr u8 coord_bottom = 0x32;
|
||||
constexpr u8 coord_left = 0x30;
|
||||
constexpr u8 coord_right = 0xb3;
|
||||
constexpr u8 coord_right_reverse = 0xaa;
|
||||
if (shifter_1)
|
||||
{
|
||||
return {coord_left, coord_top};
|
||||
}
|
||||
else if (shifter_2)
|
||||
{
|
||||
return {coord_left, coord_bottom};
|
||||
}
|
||||
else if (shifter_3)
|
||||
{
|
||||
return {coord_center, coord_top};
|
||||
}
|
||||
else if (shifter_4)
|
||||
{
|
||||
return {coord_center, coord_bottom};
|
||||
}
|
||||
else if (shifter_5)
|
||||
{
|
||||
return {coord_right, coord_top};
|
||||
}
|
||||
else if (shifter_6)
|
||||
{
|
||||
return {coord_right, coord_bottom};
|
||||
}
|
||||
else if (shifter_r)
|
||||
{
|
||||
return {coord_right_reverse, coord_bottom};
|
||||
}
|
||||
else
|
||||
{
|
||||
return {coord_center, coord_center};
|
||||
}
|
||||
}
|
||||
|
||||
static bool fetch_sdl_as_button(SDL_Joystick* joystick, const sdl_mapping& mapping)
|
||||
{
|
||||
switch (mapping.type)
|
||||
|
|
@ -646,6 +956,220 @@ static inline void set_bit(u8* buf, int bit_num, bool set)
|
|||
buf[byte_num] = buf[byte_num] & (~mask);
|
||||
}
|
||||
|
||||
void usb_device_logitech_g27::transfer_dfex(u32 buf_size, u8* buf, UsbTransfer* transfer)
|
||||
{
|
||||
DFEX_data data{};
|
||||
ensure(buf_size >= sizeof(data));
|
||||
transfer->expected_count = sizeof(data);
|
||||
|
||||
const std::lock_guard lock(m_sdl_handles_mutex);
|
||||
data.square = sdl_to_logitech_g27_button(m_joysticks, m_mapping.square);
|
||||
data.cross = sdl_to_logitech_g27_button(m_joysticks, m_mapping.cross);
|
||||
data.circle = sdl_to_logitech_g27_button(m_joysticks, m_mapping.circle);
|
||||
data.triangle = sdl_to_logitech_g27_button(m_joysticks, m_mapping.triangle);
|
||||
data.l1 = sdl_to_logitech_g27_button(m_joysticks, m_mapping.shift_down);
|
||||
data.r1 = sdl_to_logitech_g27_button(m_joysticks, m_mapping.shift_up);
|
||||
data.l2 = sdl_to_logitech_g27_button(m_joysticks, m_mapping.l2);
|
||||
data.r2 = sdl_to_logitech_g27_button(m_joysticks, m_mapping.r2);
|
||||
data.select = sdl_to_logitech_g27_button(m_joysticks, m_mapping.select);
|
||||
data.start = sdl_to_logitech_g27_button(m_joysticks, m_mapping.start);
|
||||
data.l3 = sdl_to_logitech_g27_button(m_joysticks, m_mapping.l3);
|
||||
data.r3 = sdl_to_logitech_g27_button(m_joysticks, m_mapping.r3);
|
||||
data.dpad = hat_components_to_logitech_g27_hat(
|
||||
sdl_to_logitech_g27_button(m_joysticks, m_mapping.up),
|
||||
sdl_to_logitech_g27_button(m_joysticks, m_mapping.down),
|
||||
sdl_to_logitech_g27_button(m_joysticks, m_mapping.left),
|
||||
sdl_to_logitech_g27_button(m_joysticks, m_mapping.right)
|
||||
);
|
||||
data.steering = sdl_to_logitech_g27_steering(m_joysticks, m_mapping.steering) >> 6;
|
||||
data.brake_throttle = 0x7f;
|
||||
data.const1 = 0x7f;
|
||||
data.const2 = 0x7f;
|
||||
data.brake = 0xff - sdl_to_logitech_g27_pedal(m_joysticks, m_mapping.brake);
|
||||
data.throttle = 0xff - sdl_to_logitech_g27_pedal(m_joysticks, m_mapping.throttle);
|
||||
std::memcpy(buf, &data, sizeof(data));
|
||||
}
|
||||
|
||||
void usb_device_logitech_g27::transfer_dfp(u32 buf_size, u8* buf, UsbTransfer* transfer)
|
||||
{
|
||||
DFP_data data{};
|
||||
ensure(buf_size >= sizeof(data));
|
||||
transfer->expected_count = sizeof(data);
|
||||
|
||||
const std::lock_guard lock(m_sdl_handles_mutex);
|
||||
data.steering = sdl_to_logitech_g27_steering(m_joysticks, m_mapping.steering);
|
||||
data.cross = sdl_to_logitech_g27_button(m_joysticks, m_mapping.cross);
|
||||
data.square = sdl_to_logitech_g27_button(m_joysticks, m_mapping.square);
|
||||
data.circle = sdl_to_logitech_g27_button(m_joysticks, m_mapping.circle);
|
||||
data.triangle = sdl_to_logitech_g27_button(m_joysticks, m_mapping.triangle);
|
||||
data.r1 = sdl_to_logitech_g27_button(m_joysticks, m_mapping.shift_up);
|
||||
data.l1 = sdl_to_logitech_g27_button(m_joysticks, m_mapping.shift_down);
|
||||
data.r2 = sdl_to_logitech_g27_button(m_joysticks, m_mapping.r2);
|
||||
data.l2 = sdl_to_logitech_g27_button(m_joysticks, m_mapping.l2);
|
||||
data.select = sdl_to_logitech_g27_button(m_joysticks, m_mapping.select);
|
||||
data.start = sdl_to_logitech_g27_button(m_joysticks, m_mapping.start);
|
||||
data.r3 = data.r3_2 = sdl_to_logitech_g27_button(m_joysticks, m_mapping.r3);
|
||||
data.l3 = data.l3_2 = sdl_to_logitech_g27_button(m_joysticks, m_mapping.l3);
|
||||
data.dpad = hat_components_to_logitech_g27_hat(
|
||||
sdl_to_logitech_g27_button(m_joysticks, m_mapping.up),
|
||||
sdl_to_logitech_g27_button(m_joysticks, m_mapping.down),
|
||||
sdl_to_logitech_g27_button(m_joysticks, m_mapping.left),
|
||||
sdl_to_logitech_g27_button(m_joysticks, m_mapping.right)
|
||||
);
|
||||
data.brake_throttle = 0x7f;
|
||||
data.throttle = sdl_to_logitech_g27_pedal(m_joysticks, m_mapping.throttle);
|
||||
data.brake = sdl_to_logitech_g27_pedal(m_joysticks, m_mapping.brake);
|
||||
data.pedals_attached = 1;
|
||||
data.powered = 1;
|
||||
data.self_check_done = 1;
|
||||
data.set1 = 1;
|
||||
std::memcpy(buf, &data, sizeof(data));
|
||||
}
|
||||
|
||||
void usb_device_logitech_g27::transfer_dfgt(u32 buf_size, u8* buf, UsbTransfer* transfer)
|
||||
{
|
||||
DFGT_data data{};
|
||||
ensure(buf_size >= sizeof(data));
|
||||
transfer->expected_count = sizeof(data);
|
||||
|
||||
const std::lock_guard lock(m_sdl_handles_mutex);
|
||||
data.dpad = hat_components_to_logitech_g27_hat(
|
||||
sdl_to_logitech_g27_button(m_joysticks, m_mapping.up),
|
||||
sdl_to_logitech_g27_button(m_joysticks, m_mapping.down),
|
||||
sdl_to_logitech_g27_button(m_joysticks, m_mapping.left),
|
||||
sdl_to_logitech_g27_button(m_joysticks, m_mapping.right)
|
||||
);
|
||||
data.cross = sdl_to_logitech_g27_button(m_joysticks, m_mapping.cross);
|
||||
data.square = sdl_to_logitech_g27_button(m_joysticks, m_mapping.square);
|
||||
data.circle = sdl_to_logitech_g27_button(m_joysticks, m_mapping.circle);
|
||||
data.triangle = sdl_to_logitech_g27_button(m_joysticks, m_mapping.triangle);
|
||||
data.r1 = sdl_to_logitech_g27_button(m_joysticks, m_mapping.shift_up);
|
||||
data.l1 = sdl_to_logitech_g27_button(m_joysticks, m_mapping.shift_down);
|
||||
data.r2 = sdl_to_logitech_g27_button(m_joysticks, m_mapping.r2);
|
||||
data.l2 = sdl_to_logitech_g27_button(m_joysticks, m_mapping.l2);
|
||||
data.select = sdl_to_logitech_g27_button(m_joysticks, m_mapping.select);
|
||||
data.start = sdl_to_logitech_g27_button(m_joysticks, m_mapping.start);
|
||||
data.r3 = sdl_to_logitech_g27_button(m_joysticks, m_mapping.r3);
|
||||
data.l3 = sdl_to_logitech_g27_button(m_joysticks, m_mapping.l3);
|
||||
data.dial_center = sdl_to_logitech_g27_button(m_joysticks, m_mapping.dial_center);
|
||||
data.plus = sdl_to_logitech_g27_button(m_joysticks, m_mapping.plus);
|
||||
data.dial_cw = sdl_to_logitech_g27_button(m_joysticks, m_mapping.dial_clockwise);
|
||||
data.dial_ccw = sdl_to_logitech_g27_button(m_joysticks, m_mapping.dial_anticlockwise);
|
||||
data.minus = sdl_to_logitech_g27_button(m_joysticks, m_mapping.minus);
|
||||
data.ps = sdl_to_logitech_g27_button(m_joysticks, m_mapping.ps);
|
||||
data.pedals_attached = 1;
|
||||
data.powered = 1;
|
||||
data.self_check_done = 1;
|
||||
data.set1 = 1;
|
||||
data.set2 = 1;
|
||||
data.steering = sdl_to_logitech_g27_steering(m_joysticks, m_mapping.steering);
|
||||
data.throttle = sdl_to_logitech_g27_pedal(m_joysticks, m_mapping.throttle);
|
||||
data.brake = sdl_to_logitech_g27_pedal(m_joysticks, m_mapping.brake);
|
||||
std::memcpy(buf, &data, sizeof(data));
|
||||
}
|
||||
|
||||
void usb_device_logitech_g27::transfer_g25(u32 buf_size, u8* buf, UsbTransfer* transfer)
|
||||
{
|
||||
G25_data data{};
|
||||
ensure(buf_size >= sizeof(data));
|
||||
transfer->expected_count = sizeof(data);
|
||||
|
||||
const std::lock_guard lock(m_sdl_handles_mutex);
|
||||
data.dpad = hat_components_to_logitech_g27_hat(
|
||||
sdl_to_logitech_g27_button(m_joysticks, m_mapping.up),
|
||||
sdl_to_logitech_g27_button(m_joysticks, m_mapping.down),
|
||||
sdl_to_logitech_g27_button(m_joysticks, m_mapping.left),
|
||||
sdl_to_logitech_g27_button(m_joysticks, m_mapping.right)
|
||||
);
|
||||
data.cross = sdl_to_logitech_g27_button(m_joysticks, m_mapping.cross);
|
||||
data.square = sdl_to_logitech_g27_button(m_joysticks, m_mapping.square);
|
||||
data.circle = sdl_to_logitech_g27_button(m_joysticks, m_mapping.circle);
|
||||
data.triangle = sdl_to_logitech_g27_button(m_joysticks, m_mapping.triangle);
|
||||
data.r1 = sdl_to_logitech_g27_button(m_joysticks, m_mapping.shift_up);
|
||||
data.l1 = sdl_to_logitech_g27_button(m_joysticks, m_mapping.shift_down);
|
||||
data.r2 = sdl_to_logitech_g27_button(m_joysticks, m_mapping.r2);
|
||||
data.l2 = sdl_to_logitech_g27_button(m_joysticks, m_mapping.l2);
|
||||
data.select = sdl_to_logitech_g27_button(m_joysticks, m_mapping.select);
|
||||
data.start = sdl_to_logitech_g27_button(m_joysticks, m_mapping.start);
|
||||
data.r3 = sdl_to_logitech_g27_button(m_joysticks, m_mapping.r3);
|
||||
data.l3 = sdl_to_logitech_g27_button(m_joysticks, m_mapping.l3);
|
||||
data.gear1 = sdl_to_logitech_g27_button(m_joysticks, m_mapping.shifter_1);
|
||||
data.gear2 = sdl_to_logitech_g27_button(m_joysticks, m_mapping.shifter_2);
|
||||
data.gear3 = sdl_to_logitech_g27_button(m_joysticks, m_mapping.shifter_3);
|
||||
data.gear4 = sdl_to_logitech_g27_button(m_joysticks, m_mapping.shifter_4);
|
||||
data.gear5 = sdl_to_logitech_g27_button(m_joysticks, m_mapping.shifter_5);
|
||||
data.gear6 = sdl_to_logitech_g27_button(m_joysticks, m_mapping.shifter_6);
|
||||
data.gearR = sdl_to_logitech_g27_button(m_joysticks, m_mapping.shifter_r);
|
||||
data.pedals_detached = 0;
|
||||
data.powered = 1;
|
||||
data.steering = sdl_to_logitech_g27_steering(m_joysticks, m_mapping.steering);
|
||||
data.throttle = sdl_to_logitech_g27_pedal(m_joysticks, m_mapping.throttle);
|
||||
data.brake = sdl_to_logitech_g27_pedal(m_joysticks, m_mapping.brake);
|
||||
data.clutch = sdl_to_logitech_g27_pedal(m_joysticks, m_mapping.clutch);
|
||||
auto [shifter_x, shifter_y] = shifter_to_coord_xy(data.gear1, data.gear2,
|
||||
data.gear3, data.gear4, data.gear5, data.gear6, data.gearR);
|
||||
data.shifter_x = shifter_x;
|
||||
data.shifter_y = shifter_y;
|
||||
data.shifter_attached = 1;
|
||||
data.set1 = 1;
|
||||
data.shifter_pressed = data.gearR;
|
||||
std::memcpy(buf, &data, sizeof(data));
|
||||
}
|
||||
|
||||
void usb_device_logitech_g27::transfer_g27(u32 buf_size, u8* buf, UsbTransfer* transfer)
|
||||
{
|
||||
G27_data data{};
|
||||
ensure(buf_size >= sizeof(data));
|
||||
transfer->expected_count = sizeof(data);
|
||||
|
||||
const std::lock_guard lock(m_sdl_handles_mutex);
|
||||
data.dpad = hat_components_to_logitech_g27_hat(
|
||||
sdl_to_logitech_g27_button(m_joysticks, m_mapping.up),
|
||||
sdl_to_logitech_g27_button(m_joysticks, m_mapping.down),
|
||||
sdl_to_logitech_g27_button(m_joysticks, m_mapping.left),
|
||||
sdl_to_logitech_g27_button(m_joysticks, m_mapping.right)
|
||||
);
|
||||
data.cross = sdl_to_logitech_g27_button(m_joysticks, m_mapping.cross);
|
||||
data.square = sdl_to_logitech_g27_button(m_joysticks, m_mapping.square);
|
||||
data.circle = sdl_to_logitech_g27_button(m_joysticks, m_mapping.circle);
|
||||
data.triangle = sdl_to_logitech_g27_button(m_joysticks, m_mapping.triangle);
|
||||
data.r1 = sdl_to_logitech_g27_button(m_joysticks, m_mapping.shift_up);
|
||||
data.l1 = sdl_to_logitech_g27_button(m_joysticks, m_mapping.shift_down);
|
||||
data.r2 = sdl_to_logitech_g27_button(m_joysticks, m_mapping.r2);
|
||||
data.l2 = sdl_to_logitech_g27_button(m_joysticks, m_mapping.l2);
|
||||
data.select = sdl_to_logitech_g27_button(m_joysticks, m_mapping.select);
|
||||
data.start = sdl_to_logitech_g27_button(m_joysticks, m_mapping.start);
|
||||
data.r3 = sdl_to_logitech_g27_button(m_joysticks, m_mapping.r3);
|
||||
data.l3 = sdl_to_logitech_g27_button(m_joysticks, m_mapping.l3);
|
||||
data.gear1 = sdl_to_logitech_g27_button(m_joysticks, m_mapping.shifter_1);
|
||||
data.gear2 = sdl_to_logitech_g27_button(m_joysticks, m_mapping.shifter_2);
|
||||
data.gear3 = sdl_to_logitech_g27_button(m_joysticks, m_mapping.shifter_3);
|
||||
data.gear4 = sdl_to_logitech_g27_button(m_joysticks, m_mapping.shifter_4);
|
||||
data.gear5 = sdl_to_logitech_g27_button(m_joysticks, m_mapping.shifter_5);
|
||||
data.gear6 = sdl_to_logitech_g27_button(m_joysticks, m_mapping.shifter_6);
|
||||
const bool shifter_r = sdl_to_logitech_g27_button(m_joysticks, m_mapping.shifter_r);
|
||||
data.dial_cw = sdl_to_logitech_g27_button(m_joysticks, m_mapping.dial_clockwise);
|
||||
data.dial_ccw = sdl_to_logitech_g27_button(m_joysticks, m_mapping.dial_anticlockwise);
|
||||
data.plus = sdl_to_logitech_g27_button(m_joysticks, m_mapping.plus);
|
||||
data.minus = sdl_to_logitech_g27_button(m_joysticks, m_mapping.minus);
|
||||
data.steering = sdl_to_logitech_g27_steering(m_joysticks, m_mapping.steering);
|
||||
data.throttle = sdl_to_logitech_g27_pedal(m_joysticks, m_mapping.throttle);
|
||||
data.brake = sdl_to_logitech_g27_pedal(m_joysticks, m_mapping.brake);
|
||||
data.clutch = sdl_to_logitech_g27_pedal(m_joysticks, m_mapping.clutch);
|
||||
auto [shifter_x, shifter_y] = shifter_to_coord_xy(data.gear1, data.gear2,
|
||||
data.gear3, data.gear4, data.gear5, data.gear6, shifter_r);
|
||||
data.shifter_x = shifter_x;
|
||||
data.shifter_y = shifter_y;
|
||||
data.gearR = shifter_r;
|
||||
data.pedals_detached = 0;
|
||||
data.powered = 1;
|
||||
data.shifter_attached = 1;
|
||||
data.set1 = 1;
|
||||
data.shifter_pressed = shifter_r;
|
||||
data.range = (m_wheel_range > 360);
|
||||
std::memcpy(buf, &data, sizeof(data));
|
||||
}
|
||||
|
||||
void usb_device_logitech_g27::interrupt_transfer(u32 buf_size, u8* buf, u32 endpoint, UsbTransfer* transfer)
|
||||
{
|
||||
transfer->fake = true;
|
||||
|
|
@ -655,168 +1179,31 @@ void usb_device_logitech_g27::interrupt_transfer(u32 buf_size, u8* buf, u32 endp
|
|||
|
||||
if (endpoint & (1 << 7))
|
||||
{
|
||||
if (buf_size < 11)
|
||||
{
|
||||
logitech_g27_log.error("Not populating input buffer with a buffer of the size of %u", buf_size);
|
||||
return;
|
||||
}
|
||||
|
||||
ensure(buf_size >= 11);
|
||||
memset(buf, 0, buf_size);
|
||||
|
||||
transfer->expected_count = 11;
|
||||
|
||||
sdl_instance::get_instance().pump_events();
|
||||
|
||||
// Fetch input states from SDL
|
||||
m_sdl_handles_mutex.lock();
|
||||
const u16 steering = sdl_to_logitech_g27_steering(m_joysticks, m_mapping.steering);
|
||||
const u8 throttle = sdl_to_logitech_g27_pedal(m_joysticks, m_mapping.throttle);
|
||||
const u8 brake = sdl_to_logitech_g27_pedal(m_joysticks, m_mapping.brake);
|
||||
const u8 clutch = sdl_to_logitech_g27_pedal(m_joysticks, m_mapping.clutch);
|
||||
const bool shift_up = sdl_to_logitech_g27_button(m_joysticks, m_mapping.shift_up);
|
||||
const bool shift_down = sdl_to_logitech_g27_button(m_joysticks, m_mapping.shift_down);
|
||||
|
||||
const bool up = sdl_to_logitech_g27_button(m_joysticks, m_mapping.up);
|
||||
const bool down = sdl_to_logitech_g27_button(m_joysticks, m_mapping.down);
|
||||
const bool left = sdl_to_logitech_g27_button(m_joysticks, m_mapping.left);
|
||||
const bool right = sdl_to_logitech_g27_button(m_joysticks, m_mapping.right);
|
||||
|
||||
const bool triangle = sdl_to_logitech_g27_button(m_joysticks, m_mapping.triangle);
|
||||
const bool cross = sdl_to_logitech_g27_button(m_joysticks, m_mapping.cross);
|
||||
const bool square = sdl_to_logitech_g27_button(m_joysticks, m_mapping.square);
|
||||
const bool circle = sdl_to_logitech_g27_button(m_joysticks, m_mapping.circle);
|
||||
|
||||
const bool l2 = sdl_to_logitech_g27_button(m_joysticks, m_mapping.l2);
|
||||
const bool l3 = sdl_to_logitech_g27_button(m_joysticks, m_mapping.l3);
|
||||
const bool r2 = sdl_to_logitech_g27_button(m_joysticks, m_mapping.r2);
|
||||
const bool r3 = sdl_to_logitech_g27_button(m_joysticks, m_mapping.r3);
|
||||
|
||||
const bool plus = sdl_to_logitech_g27_button(m_joysticks, m_mapping.plus);
|
||||
const bool minus = sdl_to_logitech_g27_button(m_joysticks, m_mapping.minus);
|
||||
|
||||
const bool dial_clockwise = sdl_to_logitech_g27_button(m_joysticks, m_mapping.dial_clockwise);
|
||||
const bool dial_anticlockwise = sdl_to_logitech_g27_button(m_joysticks, m_mapping.dial_anticlockwise);
|
||||
|
||||
const bool select = sdl_to_logitech_g27_button(m_joysticks, m_mapping.select);
|
||||
const bool pause = sdl_to_logitech_g27_button(m_joysticks, m_mapping.pause);
|
||||
|
||||
const bool shifter_1 = sdl_to_logitech_g27_button(m_joysticks, m_mapping.shifter_1);
|
||||
const bool shifter_2 = sdl_to_logitech_g27_button(m_joysticks, m_mapping.shifter_2);
|
||||
const bool shifter_3 = sdl_to_logitech_g27_button(m_joysticks, m_mapping.shifter_3);
|
||||
const bool shifter_4 = sdl_to_logitech_g27_button(m_joysticks, m_mapping.shifter_4);
|
||||
const bool shifter_5 = sdl_to_logitech_g27_button(m_joysticks, m_mapping.shifter_5);
|
||||
const bool shifter_6 = sdl_to_logitech_g27_button(m_joysticks, m_mapping.shifter_6);
|
||||
const bool shifter_r = sdl_to_logitech_g27_button(m_joysticks, m_mapping.shifter_r);
|
||||
const bool shifter_press = sdl_to_logitech_g27_button(m_joysticks, m_mapping.shifter_press);
|
||||
m_sdl_handles_mutex.unlock();
|
||||
|
||||
// populate buffer
|
||||
buf[0] = hat_components_to_logitech_g27_hat(up, down, left, right);
|
||||
set_bit(buf, 8, shift_up);
|
||||
set_bit(buf, 9, shift_down);
|
||||
|
||||
set_bit(buf, 7, triangle);
|
||||
set_bit(buf, 4, cross);
|
||||
set_bit(buf, 5, square);
|
||||
set_bit(buf, 6, circle);
|
||||
|
||||
set_bit(buf, 11, l2);
|
||||
set_bit(buf, 15, l3);
|
||||
set_bit(buf, 10, r2);
|
||||
set_bit(buf, 14, r3);
|
||||
|
||||
set_bit(buf, 22, dial_clockwise);
|
||||
set_bit(buf, 23, dial_anticlockwise);
|
||||
|
||||
set_bit(buf, 24, plus);
|
||||
set_bit(buf, 25, minus);
|
||||
|
||||
set_bit(buf, 12, select);
|
||||
set_bit(buf, 13, pause);
|
||||
|
||||
set_bit(buf, 16, shifter_1);
|
||||
set_bit(buf, 17, shifter_2);
|
||||
set_bit(buf, 18, shifter_3);
|
||||
set_bit(buf, 19, shifter_4);
|
||||
set_bit(buf, 20, shifter_5);
|
||||
set_bit(buf, 21, shifter_6);
|
||||
set_bit(buf, 80, shifter_r);
|
||||
|
||||
// calibrated, unsure
|
||||
set_bit(buf, 82, true);
|
||||
// shifter connected
|
||||
set_bit(buf, 83, true);
|
||||
/*
|
||||
* shifter pressed/down bit
|
||||
* mechanical references:
|
||||
* - G29 shifter mechanical explanation https://youtu.be/d7qCn3o8K98?t=1124
|
||||
* - same mechanism on the G27 https://youtu.be/rdjejtIfkVA?t=760
|
||||
* - same mechanism on the G25 https://youtu.be/eCyt_4luwF0?t=130
|
||||
* on healthy G29/G27/G25 shifters, shifter is mechnically kept pressed in reverse, the bit should be set
|
||||
* the shifter_press mapping alone captures instead a shifter press without going into reverse, ie. neutral press, just in case there are games using it for input
|
||||
*/
|
||||
set_bit(buf, 86, shifter_press | shifter_r);
|
||||
|
||||
buf[3] = (steering << 2) | buf[3];
|
||||
buf[4] = steering >> 6;
|
||||
buf[5] = throttle;
|
||||
buf[6] = brake;
|
||||
buf[7] = clutch;
|
||||
|
||||
// rough analog values recorded in https://github.com/RPCS3/rpcs3/pull/17199#issuecomment-2883934412
|
||||
// buf[8] shifter x
|
||||
// buf[9] shifter y
|
||||
constexpr u8 shifter_coord_center = 0x80;
|
||||
constexpr u8 shifter_coord_top = 0xb7;
|
||||
constexpr u8 shifter_coord_bottom = 0x32;
|
||||
constexpr u8 shifter_coord_left = 0x30;
|
||||
constexpr u8 shifter_coord_right = 0xb3;
|
||||
constexpr u8 shifter_coord_right_reverse = 0xaa;
|
||||
if (shifter_1)
|
||||
switch (m_personality)
|
||||
{
|
||||
buf[8] = shifter_coord_left;
|
||||
buf[9] = shifter_coord_top;
|
||||
}
|
||||
else if (shifter_2)
|
||||
{
|
||||
buf[8] = shifter_coord_left;
|
||||
buf[9] = shifter_coord_bottom;
|
||||
}
|
||||
else if (shifter_3)
|
||||
{
|
||||
buf[8] = shifter_coord_center;
|
||||
buf[9] = shifter_coord_top;
|
||||
}
|
||||
else if (shifter_4)
|
||||
{
|
||||
buf[8] = shifter_coord_center;
|
||||
buf[9] = shifter_coord_bottom;
|
||||
}
|
||||
else if (shifter_5)
|
||||
{
|
||||
buf[8] = shifter_coord_right;
|
||||
buf[9] = shifter_coord_top;
|
||||
}
|
||||
else if (shifter_6)
|
||||
{
|
||||
buf[8] = shifter_coord_right;
|
||||
buf[9] = shifter_coord_bottom;
|
||||
}
|
||||
else if (shifter_r)
|
||||
{
|
||||
buf[8] = shifter_coord_right_reverse;
|
||||
buf[9] = shifter_coord_bottom;
|
||||
}
|
||||
else
|
||||
{
|
||||
buf[8] = shifter_coord_center;
|
||||
buf[9] = shifter_coord_center;
|
||||
case logitech_personality::driving_force_ex:
|
||||
transfer_dfex(buf_size, buf, transfer);
|
||||
break;
|
||||
case logitech_personality::driving_force_pro:
|
||||
transfer_dfp(buf_size, buf, transfer);
|
||||
break;
|
||||
case logitech_personality::g25:
|
||||
transfer_g25(buf_size, buf, transfer);
|
||||
break;
|
||||
case logitech_personality::driving_force_gt:
|
||||
transfer_dfgt(buf_size, buf, transfer);
|
||||
break;
|
||||
case logitech_personality::g27:
|
||||
transfer_g27(buf_size, buf, transfer);
|
||||
break;
|
||||
case logitech_personality::invalid:
|
||||
fmt::throw_exception("unreachable");
|
||||
}
|
||||
|
||||
buf[10] = buf[10] | (m_wheel_range > 360 ? 0x90 : 0x10);
|
||||
|
||||
// logitech_g27_log.error("%02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x", buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7], buf[8], buf[9], buf[10]);
|
||||
// logitech_g27_log.error("dev=%d, ep in : %s", static_cast<u8>(m_personality), fmt::buf_to_hexstring(buf, buf_size));
|
||||
|
||||
return;
|
||||
}
|
||||
|
|
@ -831,8 +1218,7 @@ void usb_device_logitech_g27::interrupt_transfer(u32 buf_size, u8* buf, u32 endp
|
|||
|
||||
transfer->expected_count = buf_size;
|
||||
|
||||
// logitech_g27_log.error("%02x %02x %02x %02x %02x %02x %02x", buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6]);
|
||||
// printf("%02x %02x %02x %02x %02x %02x %02x\n", buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6]);
|
||||
// logitech_g27_log.error("ep out : %s", fmt::buf_to_hexstring(buf, buf_size));
|
||||
|
||||
// TODO maybe force clipping from cfg
|
||||
|
||||
|
|
@ -843,9 +1229,44 @@ void usb_device_logitech_g27::interrupt_transfer(u32 buf_size, u8* buf, u32 endp
|
|||
switch (buf[1])
|
||||
{
|
||||
case 0x01:
|
||||
case 0x09:
|
||||
case 0x10:
|
||||
{
|
||||
// Change to DFP
|
||||
logitech_g27_log.error("Drive Force Pro mode switch command ignored");
|
||||
// Change device mode
|
||||
u8 cmd = buf[1];
|
||||
u8 arg = buf[2];
|
||||
if (buf[8] == 0xf8) // we have 2 commands back to back
|
||||
{
|
||||
cmd = buf[9];
|
||||
arg = buf[10];
|
||||
}
|
||||
|
||||
m_next_personality = logitech_personality::invalid;
|
||||
if (cmd == 0x09 && arg == 0x04)
|
||||
m_next_personality = logitech_personality::g27;
|
||||
else if (cmd == 0x09 && arg == 0x03)
|
||||
m_next_personality = logitech_personality::driving_force_gt;
|
||||
else if ((cmd == 0x09 && arg == 0x02) || cmd == 0x10)
|
||||
m_next_personality = logitech_personality::g25;
|
||||
else if ((cmd == 0x09 && arg == 0x01) || cmd == 0x01)
|
||||
m_next_personality = logitech_personality::driving_force_pro;
|
||||
else if (cmd == 0x09 && arg == 0x00)
|
||||
m_next_personality = logitech_personality::driving_force_ex;
|
||||
|
||||
if (logitech_personality limit = static_cast<logitech_personality>(g_cfg_logitech_g27.compatibility_limit.get());
|
||||
limit < m_next_personality)
|
||||
{
|
||||
m_next_personality = limit;
|
||||
}
|
||||
|
||||
logitech_g27_log.success("Change device mode : buf=[%s], cmd=0x%x, arg=0x%x, lim=%d -> pers=%d(%s)",
|
||||
fmt::buf_to_hexstring(buf, buf_size), cmd, arg, g_cfg_logitech_g27.compatibility_limit.get(),
|
||||
static_cast<u8>(m_next_personality),
|
||||
m_next_personality == logitech_personality::g27 ? "G27"
|
||||
: m_next_personality == logitech_personality::driving_force_gt ? "Driving Force GT"
|
||||
: m_next_personality == logitech_personality::g25 ? "G25"
|
||||
: m_next_personality == logitech_personality::driving_force_pro ? "Driving Force Pro"
|
||||
: m_next_personality == logitech_personality::driving_force_ex ? "Driving Force EX" : "Invalid");
|
||||
break;
|
||||
}
|
||||
case 0x02:
|
||||
|
|
@ -862,24 +1283,12 @@ void usb_device_logitech_g27::interrupt_transfer(u32 buf_size, u8* buf, u32 endp
|
|||
m_wheel_range = 900;
|
||||
break;
|
||||
}
|
||||
case 0x09:
|
||||
{
|
||||
// Change device mode
|
||||
logitech_g27_log.error("Change device mode to %d %s detaching command ignored", buf[2], buf[3] ? "with" : "without");
|
||||
break;
|
||||
}
|
||||
case 0x0a:
|
||||
{
|
||||
// Revert indentity
|
||||
logitech_g27_log.error("Revert device identity after reset %s command ignored", buf[2] ? "enable" : "disable");
|
||||
break;
|
||||
}
|
||||
case 0x10:
|
||||
{
|
||||
// Switch to G25 with detach
|
||||
logitech_g27_log.error("Switch to G25 with detach command ignored");
|
||||
break;
|
||||
}
|
||||
case 0x11:
|
||||
{
|
||||
// Switch to G25 without detach
|
||||
|
|
|
|||
|
|
@ -16,6 +16,16 @@
|
|||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
enum class logitech_personality
|
||||
{
|
||||
driving_force_ex,
|
||||
driving_force_pro,
|
||||
g25,
|
||||
driving_force_gt,
|
||||
g27,
|
||||
invalid,
|
||||
};
|
||||
|
||||
enum class logitech_g27_ffb_state
|
||||
{
|
||||
inactive,
|
||||
|
|
@ -83,9 +93,11 @@ struct logitech_g27_sdl_mapping
|
|||
|
||||
sdl_mapping dial_clockwise {};
|
||||
sdl_mapping dial_anticlockwise {};
|
||||
sdl_mapping dial_center {};
|
||||
|
||||
sdl_mapping select {};
|
||||
sdl_mapping pause {};
|
||||
sdl_mapping start {};
|
||||
sdl_mapping ps {};
|
||||
|
||||
sdl_mapping shifter_1 {};
|
||||
sdl_mapping shifter_2 {};
|
||||
|
|
@ -94,7 +106,6 @@ struct logitech_g27_sdl_mapping
|
|||
sdl_mapping shifter_5 {};
|
||||
sdl_mapping shifter_6 {};
|
||||
sdl_mapping shifter_r {};
|
||||
sdl_mapping shifter_press {};
|
||||
};
|
||||
|
||||
class usb_device_logitech_g27 : public usb_device_emulated
|
||||
|
|
@ -112,9 +123,17 @@ public:
|
|||
|
||||
private:
|
||||
void sdl_refresh();
|
||||
void set_personality(logitech_personality personality, bool reconnect = false);
|
||||
void transfer_dfex(u32 buf_size, u8* buf, UsbTransfer* transfer);
|
||||
void transfer_dfp(u32 buf_size, u8* buf, UsbTransfer* transfer);
|
||||
void transfer_dfgt(u32 buf_size, u8* buf, UsbTransfer* transfer);
|
||||
void transfer_g25(u32 buf_size, u8* buf, UsbTransfer* transfer);
|
||||
void transfer_g27(u32 buf_size, u8* buf, UsbTransfer* transfer);
|
||||
|
||||
u32 m_controller_index = 0;
|
||||
|
||||
logitech_personality m_personality = logitech_personality::invalid;
|
||||
logitech_personality m_next_personality = logitech_personality::invalid;
|
||||
logitech_g27_sdl_mapping m_mapping {};
|
||||
bool m_reverse_effects = false;
|
||||
|
||||
|
|
|
|||
|
|
@ -96,9 +96,11 @@ public:
|
|||
|
||||
emulated_logitech_g27_mapping dial_clockwise{this, "dial_clockwise", 0, sdl_mapping_type::button, 21, hat_component::none, false};
|
||||
emulated_logitech_g27_mapping dial_anticlockwise{this, "dial_anticlockwise", 0, sdl_mapping_type::button, 22, hat_component::none, false};
|
||||
emulated_logitech_g27_mapping dial_center{this, "dial_center", 0, sdl_mapping_type::button, 23, hat_component::none, false};
|
||||
|
||||
emulated_logitech_g27_mapping select{this, "select", 0, sdl_mapping_type::button, 8, hat_component::none, false};
|
||||
emulated_logitech_g27_mapping pause{this, "pause", 0, sdl_mapping_type::button, 9, hat_component::none, false};
|
||||
emulated_logitech_g27_mapping start{this, "pause", 0, sdl_mapping_type::button, 9, hat_component::none, false};
|
||||
emulated_logitech_g27_mapping ps{this, "ps", 0, sdl_mapping_type::button, 24, hat_component::none, false};
|
||||
|
||||
emulated_logitech_g27_mapping shifter_1{this, "shifter_1", 0, sdl_mapping_type::button, 3, hat_component::none, false};
|
||||
emulated_logitech_g27_mapping shifter_2{this, "shifter_2", 0, sdl_mapping_type::button, 0, hat_component::none, false};
|
||||
|
|
@ -107,9 +109,9 @@ public:
|
|||
emulated_logitech_g27_mapping shifter_5{this, "shifter_5", 0, sdl_mapping_type::hat, 0, hat_component::up, false};
|
||||
emulated_logitech_g27_mapping shifter_6{this, "shifter_6", 0, sdl_mapping_type::hat, 0, hat_component::down, false};
|
||||
emulated_logitech_g27_mapping shifter_r{this, "shifter_r", 0, sdl_mapping_type::hat, 0, hat_component::left, false};
|
||||
emulated_logitech_g27_mapping shifter_press{this, "shifter_press", 0, sdl_mapping_type::hat, 0, hat_component::right, false};
|
||||
|
||||
cfg::_bool reverse_effects{this, "reverse_effects", false};
|
||||
cfg::uint<0, 4> compatibility_limit{this, "compatibility_limit", 4};
|
||||
cfg::uint<0, 0xFFFFFFFFFFFFFFFF> ffb_device_type_id{this, "ffb_device_type_id", 0};
|
||||
cfg::uint<0, 0xFFFFFFFFFFFFFFFF> led_device_type_id{this, "led_device_type_id", 0};
|
||||
|
||||
|
|
|
|||
|
|
@ -219,7 +219,7 @@ void usb_device_topshotelite::control_transfer(u8 bmRequestType, u8 bRequest, u1
|
|||
|
||||
extern bool is_input_allowed();
|
||||
|
||||
static void set_sensor_pos(struct TopShotElite_data* ts, s32 led_lx, s32 led_ly, s32 led_rx, s32 led_ry, s32 detect_l, s32 detect_r)
|
||||
static void set_sensor_pos(TopShotElite_data* ts, s32 led_lx, s32 led_ly, s32 led_rx, s32 led_ry, s32 detect_l, s32 detect_r)
|
||||
{
|
||||
ts->led_lx_hi = led_lx >> 2;
|
||||
ts->led_lx_lo = led_lx & 0x3;
|
||||
|
|
@ -250,7 +250,7 @@ void usb_device_topshotelite::interrupt_transfer(u32 buf_size, u8* buf, u32 /*en
|
|||
transfer->expected_result = HC_CC_NOERR;
|
||||
transfer->expected_time = get_timestamp() + 4000;
|
||||
|
||||
struct TopShotElite_data ts{};
|
||||
TopShotElite_data ts{};
|
||||
ts.dpad = Dpad_None;
|
||||
ts.stick_lx = ts.stick_ly = ts.stick_rx = ts.stick_ry = 0x7f;
|
||||
if (m_mode)
|
||||
|
|
@ -274,7 +274,7 @@ void usb_device_topshotelite::interrupt_transfer(u32 buf_size, u8* buf, u32 /*en
|
|||
|
||||
if (m_controller_index >= g_cfg_topshotelite.players.size())
|
||||
{
|
||||
topshotelite_log.warning("Top Shot Fearmaster controllers are only supported for Player1 to Player%d", g_cfg_topshotelite.players.size());
|
||||
topshotelite_log.warning("Top Shot Elite controllers are only supported for Player1 to Player%d", g_cfg_topshotelite.players.size());
|
||||
prepare_data(&ts, buf);
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -247,7 +247,7 @@ static int get_heartrate_sensor_value(u8 heartrate)
|
|||
return sensor_data[heartrate - 30];
|
||||
}
|
||||
|
||||
static void set_sensor_pos(struct TopShotFearmaster_data* ts, s32 led_lx, s32 led_ly, s32 led_rx, s32 led_ry, s32 detect_l, s32 detect_r)
|
||||
static void set_sensor_pos(TopShotFearmaster_data* ts, s32 led_lx, s32 led_ly, s32 led_rx, s32 led_ry, s32 detect_l, s32 detect_r)
|
||||
{
|
||||
ts->led_lx_hi = led_lx >> 2;
|
||||
ts->led_lx_lo = led_lx & 0x3;
|
||||
|
|
@ -278,7 +278,7 @@ void usb_device_topshotfearmaster::interrupt_transfer(u32 buf_size, u8* buf, u32
|
|||
transfer->expected_result = HC_CC_NOERR;
|
||||
transfer->expected_time = get_timestamp() + 4000;
|
||||
|
||||
struct TopShotFearmaster_data ts{};
|
||||
TopShotFearmaster_data ts{};
|
||||
ts.dpad = Dpad_None;
|
||||
ts.stick_lx = ts.stick_ly = ts.stick_rx = ts.stick_ry = 0x7f;
|
||||
if (m_mode)
|
||||
|
|
|
|||
|
|
@ -36,32 +36,38 @@ void cfg_camera::save() const
|
|||
}
|
||||
}
|
||||
|
||||
cfg_camera::camera_setting cfg_camera::get_camera_setting(std::string_view camera, bool& success)
|
||||
cfg_camera::camera_setting cfg_camera::get_camera_setting(std::string_view handler, std::string_view camera, bool& success)
|
||||
{
|
||||
camera_setting setting;
|
||||
const std::string value = cameras.get_value(camera);
|
||||
camera_setting setting {};
|
||||
const std::string value = cameras.get_value(fmt::format("%s-%s", handler, camera));
|
||||
success = !value.empty();
|
||||
if (success)
|
||||
{
|
||||
setting.from_string(cameras.get_value(camera));
|
||||
setting.from_string(value);
|
||||
}
|
||||
return setting;
|
||||
}
|
||||
|
||||
void cfg_camera::set_camera_setting(const std::string& camera, const camera_setting& setting)
|
||||
void cfg_camera::set_camera_setting(std::string_view handler, std::string_view camera, const camera_setting& setting)
|
||||
{
|
||||
if (handler.empty())
|
||||
{
|
||||
camera_log.error("String '%s' cannot be used as handler key.", handler);
|
||||
return;
|
||||
}
|
||||
|
||||
if (camera.empty())
|
||||
{
|
||||
camera_log.error("String '%s' cannot be used as camera key.", camera);
|
||||
return;
|
||||
}
|
||||
|
||||
cameras.set_value(camera, setting.to_string());
|
||||
cameras.set_value(fmt::format("%s-%s", handler, camera), setting.to_string());
|
||||
}
|
||||
|
||||
std::string cfg_camera::camera_setting::to_string() const
|
||||
{
|
||||
return fmt::format("%d,%d,%f,%f,%d", width, height, min_fps, max_fps, format);
|
||||
return fmt::format("%d,%d,%f,%f,%d,%d", width, height, min_fps, max_fps, format, colorspace);
|
||||
}
|
||||
|
||||
void cfg_camera::camera_setting::from_string(std::string_view text)
|
||||
|
|
@ -102,16 +108,19 @@ void cfg_camera::camera_setting::from_string(std::string_view text)
|
|||
return true;
|
||||
};
|
||||
|
||||
if (!to_integer(::at32(list, 0), width) ||
|
||||
!to_integer(::at32(list, 1), height) ||
|
||||
!to_double(::at32(list, 2), min_fps) ||
|
||||
!to_double(::at32(list, 3), max_fps) ||
|
||||
!to_integer(::at32(list, 4), format))
|
||||
usz pos = 0;
|
||||
if (!to_integer(::at32(list, pos++), width) ||
|
||||
!to_integer(::at32(list, pos++), height) ||
|
||||
!to_double(::at32(list, pos++), min_fps) ||
|
||||
!to_double(::at32(list, pos++), max_fps) ||
|
||||
!to_integer(::at32(list, pos++), format) ||
|
||||
!to_integer(::at32(list, pos++), colorspace))
|
||||
{
|
||||
width = 0;
|
||||
height = 0;
|
||||
min_fps = 0;
|
||||
max_fps = 0;
|
||||
format = 0;
|
||||
colorspace = 0;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,18 +15,20 @@ struct cfg_camera final : cfg::node
|
|||
double min_fps = 0;
|
||||
double max_fps = 0;
|
||||
int format = 0;
|
||||
int colorspace = 0;
|
||||
|
||||
static constexpr u32 member_count = 5;
|
||||
static constexpr u32 member_count = 6;
|
||||
|
||||
std::string to_string() const;
|
||||
void from_string(std::string_view text);
|
||||
};
|
||||
camera_setting get_camera_setting(std::string_view camera, bool& success);
|
||||
void set_camera_setting(const std::string& camera, const camera_setting& setting);
|
||||
|
||||
camera_setting get_camera_setting(std::string_view handler, std::string_view camera, bool& success);
|
||||
void set_camera_setting(std::string_view handler, std::string_view camera, const camera_setting& setting);
|
||||
|
||||
const std::string path;
|
||||
|
||||
cfg::map_entry cameras{ this, "Cameras" }; // <camera>: <width>,<height>,<min_fps>,<max_fps>,<format>
|
||||
cfg::map_entry cameras{ this, "Cameras" }; // <handler-camera>: <width>,<height>,<min_fps>,<max_fps>,<format>,<colorspace>
|
||||
};
|
||||
|
||||
extern cfg_camera g_cfg_camera;
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -25,15 +25,15 @@ namespace clan
|
|||
};
|
||||
|
||||
enum class ClanSearchFilterOperator : u8
|
||||
{
|
||||
Equal,
|
||||
NotEqual,
|
||||
GreaterThan,
|
||||
GreaterThanOrEqual,
|
||||
LessThan,
|
||||
LessThanOrEqual,
|
||||
Like,
|
||||
};
|
||||
{
|
||||
Equal,
|
||||
NotEqual,
|
||||
GreaterThan,
|
||||
GreaterThanOrEqual,
|
||||
LessThan,
|
||||
LessThanOrEqual,
|
||||
Like,
|
||||
};
|
||||
|
||||
enum class ClanRequestAction
|
||||
{
|
||||
|
|
@ -70,9 +70,8 @@ namespace clan
|
|||
{
|
||||
private:
|
||||
|
||||
|
||||
static size_t curl_write_callback(void* data, size_t size, size_t nmemb, void* clientp);
|
||||
SceNpClansError send_request(u32 reqId, ClanRequestAction action, ClanManagerOperationType type, pugi::xml_document* xml_body, pugi::xml_document* out_response);
|
||||
SceNpClansError send_request(u32 reqId, ClanRequestAction action, ClanManagerOperationType type, const pugi::xml_document& xml_body, pugi::xml_document& out_response);
|
||||
|
||||
/// @brief Forge and get a V2.1 Ticket for clan operations
|
||||
std::string get_clan_ticket(np::np_handler& nph);
|
||||
|
|
@ -81,43 +80,43 @@ namespace clan
|
|||
clans_client();
|
||||
~clans_client();
|
||||
|
||||
SceNpClansError create_request(s32* req_id);
|
||||
SceNpClansError create_request(s32& req_id);
|
||||
SceNpClansError destroy_request(u32 req_id);
|
||||
|
||||
SceNpClansError clan_search(u32 req_id, SceNpClansPagingRequest* paging, SceNpClansSearchableName* search, SceNpClansClanBasicInfo* clan_list, SceNpClansPagingResult* page_result);
|
||||
SceNpClansError clan_search(u32 req_id, const SceNpClansPagingRequest& paging, const SceNpClansSearchableName& search, std::vector<SceNpClansClanBasicInfo>& clan_list, SceNpClansPagingResult& page_result);
|
||||
|
||||
SceNpClansError create_clan(np::np_handler& nph, u32 req_id, std::string_view name, std::string_view tag, vm::ptr<SceNpClanId> clan_id);
|
||||
SceNpClansError disband_dlan(np::np_handler& nph, u32 req_id, SceNpClanId clan_id);
|
||||
|
||||
SceNpClansError get_clan_list(np::np_handler& nph, u32 req_id, SceNpClansPagingRequest* paging, SceNpClansEntry* clan_list, SceNpClansPagingResult* page_result);
|
||||
SceNpClansError get_clan_info(u32 req_id, SceNpClanId clan_id, SceNpClansClanInfo* clan_info);
|
||||
SceNpClansError get_clan_list(np::np_handler& nph, u32 req_id, const SceNpClansPagingRequest&, std::vector<SceNpClansEntry>& clan_list, SceNpClansPagingResult& page_result);
|
||||
SceNpClansError get_clan_info(u32 req_id, SceNpClanId clan_id, SceNpClansClanInfo& clan_info);
|
||||
|
||||
SceNpClansError get_member_info(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpId np_id, SceNpClansMemberEntry* mem_info);
|
||||
SceNpClansError get_member_list(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpClansPagingRequest* paging, SceNpClansMemberStatus status, SceNpClansMemberEntry* mem_list, SceNpClansPagingResult* page_result);
|
||||
SceNpClansError get_member_info(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, const SceNpId& np_id, SceNpClansMemberEntry& mem_info);
|
||||
SceNpClansError get_member_list(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, const SceNpClansPagingRequest& paging, SceNpClansMemberStatus status, std::vector<SceNpClansMemberEntry>& mem_list, SceNpClansPagingResult& page_result);
|
||||
|
||||
SceNpClansError get_blacklist(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpClansPagingRequest* paging, SceNpClansBlacklistEntry* bl, SceNpClansPagingResult* page_result);
|
||||
SceNpClansError add_blacklist_entry(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpId np_id);
|
||||
SceNpClansError remove_blacklist_entry(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpId np_id);
|
||||
SceNpClansError get_blacklist(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, const SceNpClansPagingRequest& paging, std::vector<SceNpClansBlacklistEntry>& bl, SceNpClansPagingResult& page_result);
|
||||
SceNpClansError add_blacklist_entry(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, const SceNpId& np_id);
|
||||
SceNpClansError remove_blacklist_entry(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, const SceNpId& np_id);
|
||||
|
||||
SceNpClansError request_membership(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpClansMessage* message);
|
||||
SceNpClansError request_membership(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpClansMessage& message);
|
||||
SceNpClansError cancel_request_membership(np::np_handler& nph, u32 req_id, SceNpClanId clan_id);
|
||||
SceNpClansError send_membership_response(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpId np_id, SceNpClansMessage* message, b8 allow);
|
||||
SceNpClansError send_membership_response(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, const SceNpId& np_id, SceNpClansMessage& message, b8 allow);
|
||||
|
||||
SceNpClansError send_invitation(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpId np_id, SceNpClansMessage* message);
|
||||
SceNpClansError cancel_invitation(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpId np_id);
|
||||
SceNpClansError send_invitation_response(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpClansMessage* message, b8 accept);
|
||||
SceNpClansError send_invitation(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, const SceNpId& np_id, SceNpClansMessage& message);
|
||||
SceNpClansError cancel_invitation(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, const SceNpId& np_id);
|
||||
SceNpClansError send_invitation_response(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpClansMessage& message, b8 accept);
|
||||
|
||||
SceNpClansError join_clan(np::np_handler& nph, u32 req_id, SceNpClanId clan_id);
|
||||
SceNpClansError leave_clan(np::np_handler& nph, u32 req_id, SceNpClanId clan_id);
|
||||
|
||||
SceNpClansError update_member_info(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpClansUpdatableMemberInfo* info);
|
||||
SceNpClansError update_clan_info(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpClansUpdatableClanInfo* info);
|
||||
SceNpClansError update_member_info(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, const SceNpClansUpdatableMemberInfo& info);
|
||||
SceNpClansError update_clan_info(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, const SceNpClansUpdatableClanInfo& info);
|
||||
|
||||
SceNpClansError kick_member(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpId np_id, SceNpClansMessage* message);
|
||||
SceNpClansError change_member_role(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpId np_id, SceNpClansMemberRole role);
|
||||
SceNpClansError kick_member(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, const SceNpId& np_id, SceNpClansMessage& message);
|
||||
SceNpClansError change_member_role(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, const SceNpId& np_id, SceNpClansMemberRole role);
|
||||
|
||||
SceNpClansError retrieve_announcements(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpClansPagingRequest* paging, SceNpClansMessageEntry* announcements, SceNpClansPagingResult* page_result);
|
||||
SceNpClansError post_announcement(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpClansMessage* announcement, SceNpClansMessageData* data, u32 duration, SceNpClansMessageId* announcement_id);
|
||||
SceNpClansError retrieve_announcements(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, const SceNpClansPagingRequest& paging, std::vector<SceNpClansMessageEntry>& announcements, SceNpClansPagingResult& page_result);
|
||||
SceNpClansError post_announcement(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, const SceNpClansMessage& announcement, const SceNpClansMessageData& data, u32 duration, SceNpClansMessageId& announcement_id);
|
||||
SceNpClansError delete_announcement(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpClansMessageId announcement_id);
|
||||
};
|
||||
} // namespace clan
|
||||
|
|
|
|||
|
|
@ -680,79 +680,9 @@ namespace rsx
|
|||
ROP_control_t rop_control{};
|
||||
alignas(16) fragment_context_t payload{};
|
||||
|
||||
if (REGS(m_ctx)->alpha_test_enabled())
|
||||
{
|
||||
const u32 alpha_func = static_cast<u32>(REGS(m_ctx)->alpha_func());
|
||||
rop_control.set_alpha_test_func(alpha_func);
|
||||
rop_control.enable_alpha_test();
|
||||
}
|
||||
|
||||
if (REGS(m_ctx)->polygon_stipple_enabled())
|
||||
{
|
||||
rop_control.enable_polygon_stipple();
|
||||
}
|
||||
|
||||
auto can_use_hw_a2c = [&]() -> bool
|
||||
{
|
||||
const auto& config = RSX(m_ctx)->get_backend_config();
|
||||
if (!config.supports_hw_a2c)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (config.supports_hw_a2c_1spp)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return REGS(m_ctx)->surface_antialias() != rsx::surface_antialiasing::center_1_sample;
|
||||
};
|
||||
|
||||
if (REGS(m_ctx)->msaa_alpha_to_coverage_enabled() && !can_use_hw_a2c())
|
||||
{
|
||||
// TODO: Properly support alpha-to-coverage and alpha-to-one behavior in shaders
|
||||
// Alpha values generate a coverage mask for order independent blending
|
||||
// Requires hardware AA to work properly (or just fragment sample stage in fragment shaders)
|
||||
// Simulated using combined alpha blend and alpha test
|
||||
rop_control.enable_alpha_to_coverage();
|
||||
if (REGS(m_ctx)->msaa_sample_mask())
|
||||
{
|
||||
rop_control.enable_MSAA_writes();
|
||||
}
|
||||
|
||||
// Sample configuration bits
|
||||
switch (REGS(m_ctx)->surface_antialias())
|
||||
{
|
||||
case rsx::surface_antialiasing::center_1_sample:
|
||||
break;
|
||||
case rsx::surface_antialiasing::diagonal_centered_2_samples:
|
||||
rop_control.set_msaa_control(1u);
|
||||
break;
|
||||
default:
|
||||
rop_control.set_msaa_control(3u);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if framebuffer is actually an XRGB format and not a WZYX format
|
||||
switch (REGS(m_ctx)->surface_color())
|
||||
{
|
||||
case rsx::surface_color_format::w16z16y16x16:
|
||||
case rsx::surface_color_format::w32z32y32x32:
|
||||
case rsx::surface_color_format::x32:
|
||||
// These behave very differently from "normal" formats.
|
||||
break;
|
||||
default:
|
||||
// Integer framebuffer formats.
|
||||
rop_control.enable_framebuffer_INT();
|
||||
|
||||
// Check if we want sRGB conversion.
|
||||
if (REGS(m_ctx)->framebuffer_srgb_enabled())
|
||||
{
|
||||
rop_control.enable_framebuffer_sRGB();
|
||||
}
|
||||
break;
|
||||
}
|
||||
// Always encode the alpha function. Toggling alpha-test is not guaranteed to trigger context param reload anymore.
|
||||
const u32 alpha_func = static_cast<u32>(REGS(m_ctx)->alpha_func());
|
||||
rop_control.set_alpha_test_func(alpha_func);
|
||||
|
||||
// Generate wpos coefficients
|
||||
// wpos equation is now as follows (ignoring pixel center offset):
|
||||
|
|
@ -766,7 +696,6 @@ namespace rsx
|
|||
payload.rop_control = rop_control.value;
|
||||
payload.alpha_ref = REGS(m_ctx)->alpha_ref();
|
||||
|
||||
|
||||
const auto window_origin = REGS(m_ctx)->shader_window_origin();
|
||||
const u32 window_height = REGS(m_ctx)->shader_window_height();
|
||||
const auto pixel_center = REGS(m_ctx)->pixel_center();
|
||||
|
|
|
|||
|
|
@ -40,6 +40,9 @@ namespace rsx
|
|||
|
||||
xform_instancing_state_dirty = (1 << 25), // Transform instancing state has changed
|
||||
|
||||
zeta_address_is_cyclic = (1 << 26), // The currently bound Z buffer is active for R/W in a cyclic manner
|
||||
zeta_address_cyclic_barrier = (1 << 27), // A memory barrier is required to "end" the Z buffer cyclic state
|
||||
|
||||
// TODO - Should signal that we simply need to do a FP compare before the next draw call and invalidate the ucode if the content has changed.
|
||||
// Marking as dirty to invalidate hot cache also works, it's not like there's tons of barriers per frame anyway.
|
||||
fragment_program_needs_rehash = fragment_program_ucode_dirty,
|
||||
|
|
|
|||
|
|
@ -715,7 +715,10 @@ void GLGSRender::end()
|
|||
m_frame_stats.textures_upload_time += m_profiler.duration();
|
||||
|
||||
gl::command_context cmd{ gl_state };
|
||||
if (auto ds = std::get<1>(m_rtts.m_bound_depth_stencil)) ds->write_barrier(cmd);
|
||||
if (auto ds = std::get<1>(m_rtts.m_bound_depth_stencil))
|
||||
{
|
||||
ds->write_barrier(cmd);
|
||||
}
|
||||
|
||||
for (auto &rtt : m_rtts.m_bound_render_targets)
|
||||
{
|
||||
|
|
@ -725,6 +728,8 @@ void GLGSRender::end()
|
|||
}
|
||||
}
|
||||
|
||||
m_graphics_state.clear(rsx::zeta_address_cyclic_barrier);
|
||||
|
||||
update_draw_state();
|
||||
|
||||
if (g_cfg.video.debug_output)
|
||||
|
|
|
|||
|
|
@ -93,18 +93,29 @@ void GLFragmentDecompilerThread::insertOutputs(std::stringstream & OS)
|
|||
{
|
||||
const std::pair<std::string, std::string> table[] =
|
||||
{
|
||||
{ "ocol0", m_ctrl & CELL_GCM_SHADER_CONTROL_32_BITS_EXPORTS ? "r0" : "h0" },
|
||||
{ "ocol1", m_ctrl & CELL_GCM_SHADER_CONTROL_32_BITS_EXPORTS ? "r2" : "h4" },
|
||||
{ "ocol2", m_ctrl & CELL_GCM_SHADER_CONTROL_32_BITS_EXPORTS ? "r3" : "h6" },
|
||||
{ "ocol3", m_ctrl & CELL_GCM_SHADER_CONTROL_32_BITS_EXPORTS ? "r4" : "h8" },
|
||||
{ "ocol0", m_prog.ctrl & CELL_GCM_SHADER_CONTROL_32_BITS_EXPORTS ? "r0" : "h0" },
|
||||
{ "ocol1", m_prog.ctrl & CELL_GCM_SHADER_CONTROL_32_BITS_EXPORTS ? "r2" : "h4" },
|
||||
{ "ocol2", m_prog.ctrl & CELL_GCM_SHADER_CONTROL_32_BITS_EXPORTS ? "r3" : "h6" },
|
||||
{ "ocol3", m_prog.ctrl & CELL_GCM_SHADER_CONTROL_32_BITS_EXPORTS ? "r4" : "h8" },
|
||||
};
|
||||
|
||||
const bool float_type = (m_ctrl & CELL_GCM_SHADER_CONTROL_32_BITS_EXPORTS) || !device_props.has_native_half_support;
|
||||
const bool float_type = (m_prog.ctrl & CELL_GCM_SHADER_CONTROL_32_BITS_EXPORTS) || !device_props.has_native_half_support;
|
||||
const auto reg_type = float_type ? "vec4" : getHalfTypeName(4);
|
||||
for (uint i = 0; i < std::size(table); ++i)
|
||||
{
|
||||
if (m_parr.HasParam(PF_PARAM_NONE, reg_type, table[i].second))
|
||||
OS << "layout(location=" << i << ") out vec4 " << table[i].first << ";\n";
|
||||
if (!m_parr.HasParam(PF_PARAM_NONE, reg_type, table[i].second))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (i >= m_prog.mrt_buffers_count)
|
||||
{
|
||||
// Dead writes. Declare as temp variables for DCE to clean up.
|
||||
OS << "vec4 " << table[i].first << "; // Unused\n";
|
||||
continue;
|
||||
}
|
||||
|
||||
OS << "layout(location=" << i << ") out vec4 " << table[i].first << ";\n";
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -190,7 +201,8 @@ void GLFragmentDecompilerThread::insertConstants(std::stringstream & OS)
|
|||
" uvec4 stipple_pattern[8];\n"
|
||||
"};\n\n"
|
||||
|
||||
"#define texture_base_index 0\n\n";
|
||||
"#define texture_base_index 0\n"
|
||||
"#define TEX_PARAM(index) texture_parameters[index]\n\n";
|
||||
}
|
||||
|
||||
void GLFragmentDecompilerThread::insertGlobalFunctions(std::stringstream &OS)
|
||||
|
|
@ -207,16 +219,24 @@ void GLFragmentDecompilerThread::insertGlobalFunctions(std::stringstream &OS)
|
|||
m_shader_props.require_srgb_to_linear = properties.has_upg;
|
||||
m_shader_props.require_linear_to_srgb = properties.has_pkg;
|
||||
m_shader_props.require_fog_read = properties.in_register_mask & in_fogc;
|
||||
m_shader_props.emulate_coverage_tests = !rsx::get_renderer_backend_config().supports_hw_a2c_1spp;
|
||||
m_shader_props.emulate_shadow_compare = device_props.emulate_depth_compare;
|
||||
|
||||
m_shader_props.low_precision_tests = ::gl::get_driver_caps().vendor_NVIDIA && !(m_prog.ctrl & RSX_SHADER_CONTROL_ATTRIBUTE_INTERPOLATION);
|
||||
m_shader_props.disable_early_discard = !::gl::get_driver_caps().vendor_NVIDIA;
|
||||
m_shader_props.supports_native_fp16 = device_props.has_native_half_support;
|
||||
m_shader_props.ROP_output_rounding = g_cfg.video.shader_precision != gpu_preset_level::low;
|
||||
|
||||
m_shader_props.ROP_output_rounding = (g_cfg.video.shader_precision != gpu_preset_level::low) && !!(m_prog.ctrl & RSX_SHADER_CONTROL_8BIT_FRAMEBUFFER);
|
||||
m_shader_props.ROP_sRGB_packing = !!(m_prog.ctrl & RSX_SHADER_CONTROL_SRGB_FRAMEBUFFER);
|
||||
m_shader_props.ROP_alpha_test = !!(m_prog.ctrl & RSX_SHADER_CONTROL_ALPHA_TEST);
|
||||
m_shader_props.ROP_alpha_to_coverage_test = !!(m_prog.ctrl & RSX_SHADER_CONTROL_ALPHA_TO_COVERAGE);
|
||||
m_shader_props.ROP_polygon_stipple_test = !!(m_prog.ctrl & RSX_SHADER_CONTROL_POLYGON_STIPPLE);
|
||||
m_shader_props.ROP_discard = !!(m_prog.ctrl & RSX_SHADER_CONTROL_USES_KIL);
|
||||
|
||||
m_shader_props.require_tex1D_ops = properties.has_tex1D;
|
||||
m_shader_props.require_tex2D_ops = properties.has_tex2D;
|
||||
m_shader_props.require_tex3D_ops = properties.has_tex3D;
|
||||
m_shader_props.require_shadowProj_ops = properties.shadow_sampler_mask != 0 && properties.has_texShadowProj;
|
||||
m_shader_props.require_alpha_kill = !!(m_prog.ctrl & RSX_SHADER_CONTROL_TEXTURE_ALPHA_KILL);
|
||||
|
||||
glsl::insert_glsl_legacy_function(OS, m_shader_props);
|
||||
}
|
||||
|
|
@ -224,7 +244,7 @@ void GLFragmentDecompilerThread::insertGlobalFunctions(std::stringstream &OS)
|
|||
void GLFragmentDecompilerThread::insertMainStart(std::stringstream & OS)
|
||||
{
|
||||
std::set<std::string> output_registers;
|
||||
if (m_ctrl & CELL_GCM_SHADER_CONTROL_32_BITS_EXPORTS)
|
||||
if (m_prog.ctrl & CELL_GCM_SHADER_CONTROL_32_BITS_EXPORTS)
|
||||
{
|
||||
output_registers = { "r0", "r2", "r3", "r4" };
|
||||
}
|
||||
|
|
@ -233,7 +253,7 @@ void GLFragmentDecompilerThread::insertMainStart(std::stringstream & OS)
|
|||
output_registers = { "h0", "h4", "h6", "h8" };
|
||||
}
|
||||
|
||||
if (m_ctrl & CELL_GCM_SHADER_CONTROL_DEPTH_EXPORT)
|
||||
if (m_prog.ctrl & CELL_GCM_SHADER_CONTROL_DEPTH_EXPORT)
|
||||
{
|
||||
output_registers.insert("r1");
|
||||
}
|
||||
|
|
@ -315,20 +335,28 @@ void GLFragmentDecompilerThread::insertMainEnd(std::stringstream & OS)
|
|||
|
||||
OS << "\n" << " fs_main();\n\n";
|
||||
|
||||
if (m_prog.ctrl & RSX_SHADER_CONTROL_DISABLE_EARLY_Z)
|
||||
{
|
||||
// This is effectively pointless code, but good enough to trick the GPU to skip early Z
|
||||
OS <<
|
||||
" // Insert pseudo-barrier sequence to disable early-Z\n"
|
||||
" gl_FragDepth = gl_FragCoord.z;\n\n";
|
||||
}
|
||||
|
||||
glsl::insert_rop(OS, m_shader_props);
|
||||
|
||||
if (m_ctrl & CELL_GCM_SHADER_CONTROL_DEPTH_EXPORT)
|
||||
if (m_prog.ctrl & CELL_GCM_SHADER_CONTROL_DEPTH_EXPORT)
|
||||
{
|
||||
if (m_parr.HasParam(PF_PARAM_NONE, "vec4", "r1"))
|
||||
{
|
||||
//Depth writes are always from a fp32 register. See issues section on nvidia's NV_fragment_program spec
|
||||
//https://www.khronos.org/registry/OpenGL/extensions/NV/NV_fragment_program.txt
|
||||
// Depth writes are always from a fp32 register. See issues section on nvidia's NV_fragment_program spec
|
||||
// https://www.khronos.org/registry/OpenGL/extensions/NV/NV_fragment_program.txt
|
||||
OS << " gl_FragDepth = r1.z;\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
//Input not declared. Leave commented to assist in debugging the shader
|
||||
OS << " //gl_FragDepth = r1.z;\n";
|
||||
// Input not declared. Leave commented to assist in debugging the shader
|
||||
OS << " // gl_FragDepth = r1.z;\n";
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -301,6 +301,7 @@ void GLGSRender::init_buffers(rsx::framebuffer_creation_context context, bool /*
|
|||
}
|
||||
|
||||
m_graphics_state.set(rsx::rtt_config_valid);
|
||||
on_framebuffer_layout_updated();
|
||||
|
||||
check_zcull_status(true);
|
||||
set_viewport();
|
||||
|
|
|
|||
|
|
@ -300,6 +300,7 @@ namespace gl
|
|||
}
|
||||
|
||||
builder << "\n"
|
||||
"#define TEX_PARAM(index) texture_parameters[index + texture_base_index]\n"
|
||||
"#define IS_TEXTURE_RESIDENT(index) (texture_handles[index] < 0xFF)\n"
|
||||
"#define SAMPLER1D(index) sampler1D_array[texture_handles[index]]\n"
|
||||
"#define SAMPLER2D(index) sampler2D_array[texture_handles[index]]\n"
|
||||
|
|
|
|||
|
|
@ -38,9 +38,6 @@ void gl::init()
|
|||
#ifdef __unix__
|
||||
glewExperimental = true;
|
||||
glewInit();
|
||||
#ifdef HAVE_X11
|
||||
glxewInit();
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -89,27 +89,25 @@ namespace rsx
|
|||
rsx::reservation_lock<true> rsx_lock(dst_address, data_length);
|
||||
|
||||
if (RSX(ctx)->fifo_ctrl->last_cmd() & RSX_METHOD_NON_INCREMENT_CMD_MASK) [[unlikely]]
|
||||
{
|
||||
// Move last 32 bits
|
||||
reinterpret_cast<u32*>(dst)[0] = reinterpret_cast<const u32*>(src)[count - 1];
|
||||
RSX(ctx)->invalidate_fragment_program(dst_dma, dst_offset, 4);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (dst_dma & CELL_GCM_LOCATION_MAIN)
|
||||
{
|
||||
// May overlap
|
||||
std::memmove(dst, src, data_length);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Never overlaps
|
||||
std::memcpy(dst, src, data_length);
|
||||
}
|
||||
|
||||
RSX(ctx)->invalidate_fragment_program(dst_dma, dst_offset, count * 4);
|
||||
// Move last 32 bits
|
||||
reinterpret_cast<u32*>(dst)[0] = reinterpret_cast<const u32*>(src)[count - 1];
|
||||
RSX(ctx)->invalidate_fragment_program(dst_dma, dst_offset, 4);
|
||||
return;
|
||||
}
|
||||
|
||||
if (dst_dma & CELL_GCM_LOCATION_MAIN)
|
||||
{
|
||||
// May overlap
|
||||
std::memmove(dst, src, data_length);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Never overlaps
|
||||
std::memcpy(dst, src, data_length);
|
||||
}
|
||||
|
||||
RSX(ctx)->invalidate_fragment_program(dst_dma, dst_offset, count * 4);
|
||||
break;
|
||||
}
|
||||
case blit_engine::transfer_destination_format::r5g6b5:
|
||||
|
|
@ -129,33 +127,33 @@ namespace rsx
|
|||
rsx::reservation_lock<true> rsx_lock(dst_address, data_length);
|
||||
|
||||
auto convert = [](u32 input) -> u16
|
||||
{
|
||||
// Input is considered to be ARGB8
|
||||
u32 r = (input >> 16) & 0xFF;
|
||||
u32 g = (input >> 8) & 0xFF;
|
||||
u32 b = input & 0xFF;
|
||||
{
|
||||
// Input is considered to be ARGB8
|
||||
u32 r = (input >> 16) & 0xFF;
|
||||
u32 g = (input >> 8) & 0xFF;
|
||||
u32 b = input & 0xFF;
|
||||
|
||||
r = (r * 32) / 255;
|
||||
g = (g * 64) / 255;
|
||||
b = (b * 32) / 255;
|
||||
return static_cast<u16>((r << 11) | (g << 5) | b);
|
||||
};
|
||||
r = (r * 32) / 255;
|
||||
g = (g * 64) / 255;
|
||||
b = (b * 32) / 255;
|
||||
return static_cast<u16>((r << 11) | (g << 5) | b);
|
||||
};
|
||||
|
||||
if (RSX(ctx)->fifo_ctrl->last_cmd() & RSX_METHOD_NON_INCREMENT_CMD_MASK) [[unlikely]]
|
||||
{
|
||||
// Move last 16 bits
|
||||
dst[0] = convert(src[count - 1]);
|
||||
RSX(ctx)->invalidate_fragment_program(dst_dma, dst_offset, 2);
|
||||
break;
|
||||
}
|
||||
|
||||
for (u32 i = 0; i < count; i++)
|
||||
{
|
||||
dst[i] = convert(src[i]);
|
||||
}
|
||||
|
||||
RSX(ctx)->invalidate_fragment_program(dst_dma, dst_offset, count * 2);
|
||||
{
|
||||
// Move last 16 bits
|
||||
dst[0] = convert(src[count - 1]);
|
||||
RSX(ctx)->invalidate_fragment_program(dst_dma, dst_offset, 2);
|
||||
break;
|
||||
}
|
||||
|
||||
for (u32 i = 0; i < count; i++)
|
||||
{
|
||||
dst[i] = convert(src[i]);
|
||||
}
|
||||
|
||||
RSX(ctx)->invalidate_fragment_program(dst_dma, dst_offset, count * 2);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
|
|
|
|||
|
|
@ -250,8 +250,12 @@ namespace rsx
|
|||
const auto current = REGS(ctx)->decode<NV4097_SET_SURFACE_FORMAT>(arg);
|
||||
const auto previous = REGS(ctx)->decode<NV4097_SET_SURFACE_FORMAT>(REGS(ctx)->latch);
|
||||
|
||||
if (*current.antialias() != *previous.antialias() || // Antialias control has changed, update ROP parameters
|
||||
current.is_integer_color_format() != previous.is_integer_color_format()) // The type of color format also requires ROP control update
|
||||
if (current.is_integer_color_format() != previous.is_integer_color_format()) // Different ROP emulation
|
||||
{
|
||||
RSX(ctx)->m_graphics_state |= rsx::pipeline_state::fragment_program_state_dirty;
|
||||
}
|
||||
|
||||
if (*current.antialias() != *previous.antialias()) // Antialias control has changed, update ROP parameters
|
||||
{
|
||||
RSX(ctx)->m_graphics_state |= rsx::pipeline_state::fragment_state_dirty;
|
||||
}
|
||||
|
|
@ -302,6 +306,34 @@ namespace rsx
|
|||
REGS(ctx)->decode(reg, REGS(ctx)->latch);
|
||||
}
|
||||
|
||||
void set_aa_control(context* ctx, u32 reg, u32 arg)
|
||||
{
|
||||
const auto latch = REGS(ctx)->latch;
|
||||
if (arg == latch)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Reconfigure pipeline.
|
||||
RSX(ctx)->m_graphics_state |= rsx::pipeline_config_dirty;
|
||||
|
||||
// If we support A2C in hardware, leave the rest upto the hardware. The pipeline config should take care of it.
|
||||
const auto& backend_config = RSX(ctx)->get_backend_config();
|
||||
if (backend_config.supports_hw_a2c &&
|
||||
backend_config.supports_hw_a2c_1spp)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// No A2C hardware support or partial hardware support. Invalidate the current program if A2C state changed.
|
||||
const auto a2c_old = REGS(ctx)->decode<NV4097_SET_ANTI_ALIASING_CONTROL>(latch).msaa_alpha_to_coverage();
|
||||
const auto a2c_new = REGS(ctx)->decode<NV4097_SET_ANTI_ALIASING_CONTROL>(arg).msaa_alpha_to_coverage();
|
||||
if (a2c_old != a2c_new)
|
||||
{
|
||||
RSX(ctx)->m_graphics_state |= rsx::fragment_program_state_dirty;
|
||||
}
|
||||
}
|
||||
|
||||
///// Draw call setup (vertex, etc)
|
||||
|
||||
void set_array_element16(context* ctx, u32, u32 arg)
|
||||
|
|
|
|||
|
|
@ -87,6 +87,8 @@ namespace rsx
|
|||
|
||||
void set_transform_constant_load(context* ctx, u32 reg, u32 arg);
|
||||
|
||||
void set_aa_control(context* ctx, u32 reg, u32 arg);
|
||||
|
||||
#define RSX(ctx) ctx->rsxthr
|
||||
#define REGS(ctx) (&rsx::method_registers)
|
||||
|
||||
|
|
|
|||
|
|
@ -80,9 +80,17 @@ namespace rsx
|
|||
add_checkbox(&g_cfg.io.keep_pads_connected, localized_string_id::HOME_MENU_SETTINGS_INPUT_KEEP_PADS_CONNECTED);
|
||||
add_checkbox(&g_cfg.io.show_move_cursor, localized_string_id::HOME_MENU_SETTINGS_INPUT_SHOW_PS_MOVE_CURSOR);
|
||||
|
||||
if (g_cfg.io.camera == camera_handler::qt)
|
||||
switch (g_cfg.io.camera)
|
||||
{
|
||||
#ifdef HAVE_SDL3
|
||||
case camera_handler::sdl:
|
||||
#endif
|
||||
case camera_handler::qt:
|
||||
add_dropdown(&g_cfg.io.camera_flip_option, localized_string_id::HOME_MENU_SETTINGS_INPUT_CAMERA_FLIP);
|
||||
break;
|
||||
case camera_handler::fake:
|
||||
case camera_handler::null:
|
||||
break;
|
||||
}
|
||||
|
||||
add_dropdown(&g_cfg.io.pad_mode, localized_string_id::HOME_MENU_SETTINGS_INPUT_PAD_MODE);
|
||||
|
|
|
|||
|
|
@ -8,6 +8,24 @@
|
|||
#include "Utilities/StrUtil.h"
|
||||
#include "Utilities/Thread.h"
|
||||
|
||||
template <>
|
||||
void fmt_class_string<rsx::overlays::media_list_dialog::media_type>::format(std::string& out, u64 arg)
|
||||
{
|
||||
format_enum(out, arg, [](rsx::overlays::media_list_dialog::media_type arg)
|
||||
{
|
||||
switch (arg)
|
||||
{
|
||||
case rsx::overlays::media_list_dialog::media_type::invalid: return "invalid";
|
||||
case rsx::overlays::media_list_dialog::media_type::directory: return "directory";
|
||||
case rsx::overlays::media_list_dialog::media_type::audio: return "audio";
|
||||
case rsx::overlays::media_list_dialog::media_type::video: return "video";
|
||||
case rsx::overlays::media_list_dialog::media_type::photo: return "photo";
|
||||
}
|
||||
|
||||
return unknown;
|
||||
});
|
||||
}
|
||||
|
||||
namespace rsx
|
||||
{
|
||||
namespace overlays
|
||||
|
|
@ -203,7 +221,7 @@ namespace rsx
|
|||
return result;
|
||||
}
|
||||
|
||||
s32 media_list_dialog::show(media_entry* root, media_entry& result, const std::string& title, u32 focused, bool enable_overlay)
|
||||
s32 media_list_dialog::show(std::shared_ptr<media_entry> root, media_entry& result, const std::string& title, u32 focused, bool enable_overlay)
|
||||
{
|
||||
auto ref = g_fxo->get<display_manager>().get(uid);
|
||||
|
||||
|
|
@ -237,7 +255,7 @@ namespace rsx
|
|||
{
|
||||
focused = 0;
|
||||
ensure(static_cast<size_t>(return_code) < m_media->children.size());
|
||||
m_media = &m_media->children[return_code];
|
||||
m_media = m_media->children[return_code];
|
||||
rsx_log.notice("Media dialog: selected entry: %d ('%s')", return_code, m_media->path);
|
||||
continue;
|
||||
}
|
||||
|
|
@ -287,9 +305,10 @@ namespace rsx
|
|||
m_list = std::make_unique<list_view>(virtual_width - 2 * 20, 540);
|
||||
m_list->set_pos(20, 85);
|
||||
|
||||
for (const media_entry& child : m_media->children)
|
||||
for (const auto& child : m_media->children)
|
||||
{
|
||||
std::unique_ptr<overlay_element> entry = std::make_unique<media_list_entry>(child);
|
||||
ensure(!!child);
|
||||
std::unique_ptr<overlay_element> entry = std::make_unique<media_list_entry>(*child);
|
||||
m_list->add_entry(entry);
|
||||
}
|
||||
|
||||
|
|
@ -321,9 +340,11 @@ namespace rsx
|
|||
static constexpr auto thread_name = "MediaList Thread"sv;
|
||||
};
|
||||
|
||||
void parse_media_recursive(u32 depth, const std::string& media_path, const std::string& name, media_list_dialog::media_type type, media_list_dialog::media_entry& current_entry)
|
||||
void parse_media_recursive(u32 depth, u32 max_depth, const std::string& media_path, const std::string& name, media_list_dialog::media_type type, std::shared_ptr<media_list_dialog::media_entry> current_entry)
|
||||
{
|
||||
if (depth++ > music_selection_context::max_depth)
|
||||
ensure(!!current_entry);
|
||||
|
||||
if (depth++ > max_depth && max_depth != umax)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
|
@ -339,26 +360,27 @@ namespace rsx
|
|||
|
||||
const std::string unescaped_name = vfs::unescape(dir_entry.name);
|
||||
|
||||
media_list_dialog::media_entry new_entry{};
|
||||
parse_media_recursive(depth, media_path + "/" + dir_entry.name, unescaped_name, type, new_entry);
|
||||
if (new_entry.type != media_list_dialog::media_type::invalid)
|
||||
auto new_entry = std::make_shared<media_list_dialog::media_entry>();
|
||||
parse_media_recursive(depth, max_depth, media_path + "/" + dir_entry.name, unescaped_name, type, new_entry);
|
||||
if (new_entry->type != media_list_dialog::media_type::invalid)
|
||||
{
|
||||
new_entry.parent = ¤t_entry;
|
||||
new_entry.index = ::narrow<u32>(current_entry.children.size());
|
||||
current_entry.children.emplace_back(std::move(new_entry));
|
||||
rsx_log.notice("parse_media_recursive: found '%s' (type=%s)", dir_entry.name, new_entry->type);
|
||||
new_entry->parent = current_entry;
|
||||
new_entry->index = ::narrow<u32>(current_entry->children.size());
|
||||
current_entry->children.emplace_back(std::move(new_entry));
|
||||
}
|
||||
}
|
||||
|
||||
// Only keep directories that contain valid entries
|
||||
if (current_entry.children.empty())
|
||||
if (current_entry->children.empty())
|
||||
{
|
||||
rsx_log.notice("parse_media_recursive: No matches in directory '%s'", media_path);
|
||||
}
|
||||
else
|
||||
{
|
||||
rsx_log.notice("parse_media_recursive: Found %d matches in directory '%s'", current_entry.children.size(), media_path);
|
||||
current_entry.type = media_list_dialog::media_type::directory;
|
||||
current_entry.info.path = media_path;
|
||||
rsx_log.notice("parse_media_recursive: Found %d matches in directory '%s'", current_entry->children.size(), media_path);
|
||||
current_entry->type = media_list_dialog::media_type::directory;
|
||||
current_entry->info.path = media_path;
|
||||
}
|
||||
}
|
||||
else
|
||||
|
|
@ -370,20 +392,20 @@ namespace rsx
|
|||
auto [success, info] = utils::get_media_info(media_path, av_media_type);
|
||||
if (success)
|
||||
{
|
||||
current_entry.type = type;
|
||||
current_entry.info = std::move(info);
|
||||
current_entry->type = type;
|
||||
current_entry->info = std::move(info);
|
||||
rsx_log.notice("parse_media_recursive: Found media '%s'", media_path);
|
||||
}
|
||||
}
|
||||
|
||||
if (current_entry.type != media_list_dialog::media_type::invalid)
|
||||
if (current_entry->type != media_list_dialog::media_type::invalid)
|
||||
{
|
||||
current_entry.path = media_path;
|
||||
current_entry.name = name;
|
||||
current_entry->path = media_path;
|
||||
current_entry->name = name;
|
||||
}
|
||||
}
|
||||
|
||||
error_code show_media_list_dialog(media_list_dialog::media_type type, const std::string& path, const std::string& title, std::function<void(s32 status, utils::media_info info)> on_finished)
|
||||
error_code show_media_list_dialog(media_list_dialog::media_type type, u32 max_depth, const std::string& path, const std::string& title, std::function<void(s32 status, utils::media_info info)> on_finished)
|
||||
{
|
||||
rsx_log.todo("show_media_list_dialog(type=%d, path='%s', title='%s', on_finished=%d)", static_cast<s32>(type), path, title, !!on_finished);
|
||||
|
||||
|
|
@ -394,12 +416,12 @@ namespace rsx
|
|||
|
||||
g_fxo->get<named_thread<media_list_dialog_thread>>()([=]()
|
||||
{
|
||||
media_list_dialog::media_entry root_media_entry{};
|
||||
root_media_entry.type = media_list_dialog::media_type::directory;
|
||||
auto root_media_entry = std::make_shared<media_list_dialog::media_entry>();
|
||||
root_media_entry->type = media_list_dialog::media_type::directory;
|
||||
|
||||
if (fs::is_dir(path))
|
||||
{
|
||||
parse_media_recursive(0, path, title, type, root_media_entry);
|
||||
parse_media_recursive(0, max_depth, path, title, type, root_media_entry);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -412,7 +434,7 @@ namespace rsx
|
|||
|
||||
if (auto manager = g_fxo->try_get<rsx::overlays::display_manager>())
|
||||
{
|
||||
result = manager->create<rsx::overlays::media_list_dialog>()->show(&root_media_entry, media, title, focused, true);
|
||||
result = manager->create<rsx::overlays::media_list_dialog>()->show(root_media_entry, media, title, focused, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
|||
|
|
@ -29,8 +29,8 @@ namespace rsx
|
|||
utils::media_info info;
|
||||
u32 index = 0;
|
||||
|
||||
media_entry* parent = nullptr;
|
||||
std::vector<media_entry> children;
|
||||
std::shared_ptr<media_entry> parent;
|
||||
std::vector<std::shared_ptr<media_entry>> children;
|
||||
};
|
||||
|
||||
media_list_dialog();
|
||||
|
|
@ -39,7 +39,7 @@ namespace rsx
|
|||
|
||||
compiled_resource get_compiled() override;
|
||||
|
||||
s32 show(media_entry* root, media_entry& result, const std::string& title, u32 focused, bool enable_overlay);
|
||||
s32 show(std::shared_ptr<media_entry> root, media_entry& result, const std::string& title, u32 focused, bool enable_overlay);
|
||||
|
||||
private:
|
||||
void reload(const std::string& title, u32 focused);
|
||||
|
|
@ -53,7 +53,7 @@ namespace rsx
|
|||
std::unique_ptr<image_info> icon_data;
|
||||
};
|
||||
|
||||
media_entry* m_media = nullptr;
|
||||
std::shared_ptr<media_entry> m_media;
|
||||
|
||||
std::unique_ptr<overlay_element> m_dim_background;
|
||||
std::unique_ptr<list_view> m_list;
|
||||
|
|
@ -61,6 +61,6 @@ namespace rsx
|
|||
std::unique_ptr<label> m_no_media_text;
|
||||
};
|
||||
|
||||
error_code show_media_list_dialog(media_list_dialog::media_type type, const std::string& path, const std::string& title, std::function<void(s32 status, utils::media_info info)> on_finished);
|
||||
error_code show_media_list_dialog(media_list_dialog::media_type type, u32 max_depth, const std::string& path, const std::string& title, std::function<void(s32 status, utils::media_info info)> on_finished);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -84,7 +84,6 @@ std::vector<RegisterRef> get_fragment_program_output_set(u32 ctrl, u32 mrt_count
|
|||
FragmentProgramDecompiler::FragmentProgramDecompiler(const RSXFragmentProgram &prog, u32& size)
|
||||
: m_size(size)
|
||||
, m_prog(prog)
|
||||
, m_ctrl(prog.ctrl)
|
||||
{
|
||||
m_size = 0;
|
||||
}
|
||||
|
|
@ -551,18 +550,22 @@ template<typename T> std::string FragmentProgramDecompiler::GetSRC(T src)
|
|||
{
|
||||
std::string ret;
|
||||
u32 precision_modifier = 0;
|
||||
u32 register_index = umax;
|
||||
|
||||
if constexpr (std::is_same_v<T, SRC0>)
|
||||
{
|
||||
precision_modifier = src1.src0_prec_mod;
|
||||
register_index = 0;
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, SRC1>)
|
||||
{
|
||||
precision_modifier = src1.src1_prec_mod;
|
||||
register_index = 1;
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, SRC2>)
|
||||
{
|
||||
precision_modifier = src1.src2_prec_mod;
|
||||
register_index = 2;
|
||||
}
|
||||
|
||||
switch (src.reg_type)
|
||||
|
|
@ -645,18 +648,29 @@ template<typename T> std::string FragmentProgramDecompiler::GetSRC(T src)
|
|||
{
|
||||
// TEX0 - TEX9
|
||||
// Texcoord 2d mask seems to reset the last 2 arguments to 0 and w if set
|
||||
|
||||
// Opt: Skip emitting w dependency unless w coord is actually being sampled
|
||||
ensure(register_index != umax);
|
||||
const auto lane_mask = FP::get_src_vector_lane_mask_shuffled(m_prog, m_instruction, register_index);
|
||||
const auto touches_z = !!(lane_mask & (1u << 2));
|
||||
const bool touches_w = !!(lane_mask & (1u << 3));
|
||||
|
||||
const u8 texcoord = u8(register_id) - 4;
|
||||
if (m_prog.texcoord_is_point_coord(texcoord))
|
||||
{
|
||||
// Point sprite coord generation. Stacks with the 2D override mask.
|
||||
if (m_prog.texcoord_is_2d(texcoord))
|
||||
if (!m_prog.texcoord_is_2d(texcoord))
|
||||
{
|
||||
ret += getFloatTypeName(4) + "(gl_PointCoord, 0., in_w)";
|
||||
properties.has_w_access = true;
|
||||
ret += getFloatTypeName(4) + "(gl_PointCoord, 1., 0.)";
|
||||
}
|
||||
else if (!touches_w)
|
||||
{
|
||||
ret += getFloatTypeName(4) + "(gl_PointCoord, 0., 0.)";
|
||||
}
|
||||
else
|
||||
{
|
||||
ret += getFloatTypeName(4) + "(gl_PointCoord, 1., 0.)";
|
||||
ret += getFloatTypeName(4) + "(gl_PointCoord, 0., in_w)";
|
||||
properties.has_w_access = true;
|
||||
}
|
||||
}
|
||||
else if (src2.perspective_corr)
|
||||
|
|
@ -673,14 +687,19 @@ template<typename T> std::string FragmentProgramDecompiler::GetSRC(T src)
|
|||
}
|
||||
else
|
||||
{
|
||||
if (m_prog.texcoord_is_2d(texcoord))
|
||||
const bool skip_zw_load = !touches_z && !touches_w;
|
||||
if (!m_prog.texcoord_is_2d(texcoord) || skip_zw_load)
|
||||
{
|
||||
ret += getFloatTypeName(4) + "(" + reg_var + ".xy, 0., in_w)";
|
||||
properties.has_w_access = true;
|
||||
ret += reg_var;
|
||||
}
|
||||
else if (!touches_w)
|
||||
{
|
||||
ret += getFloatTypeName(4) + "(" + reg_var + ".xy, 0., 0.)";
|
||||
}
|
||||
else
|
||||
{
|
||||
ret += reg_var;
|
||||
ret += getFloatTypeName(4) + "(" + reg_var + ".xy, 0., in_w)";
|
||||
properties.has_w_access = true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
|
@ -785,7 +804,7 @@ std::string FragmentProgramDecompiler::BuildCode()
|
|||
// Shader validation
|
||||
// Shader must at least write to one output for the body to be considered valid
|
||||
|
||||
const bool fp16_out = !(m_ctrl & CELL_GCM_SHADER_CONTROL_32_BITS_EXPORTS);
|
||||
const bool fp16_out = !(m_prog.ctrl & CELL_GCM_SHADER_CONTROL_32_BITS_EXPORTS);
|
||||
const std::string float4_type = (fp16_out && device_props.has_native_half_support)? getHalfTypeName(4) : getFloatTypeName(4);
|
||||
const std::string init_value = float4_type + "(0.)";
|
||||
std::array<std::string, 4> output_register_names;
|
||||
|
|
@ -794,7 +813,7 @@ std::string FragmentProgramDecompiler::BuildCode()
|
|||
std::stringstream main_epilogue;
|
||||
|
||||
// Check depth export
|
||||
if (m_ctrl & CELL_GCM_SHADER_CONTROL_DEPTH_EXPORT)
|
||||
if (m_prog.ctrl & CELL_GCM_SHADER_CONTROL_DEPTH_EXPORT)
|
||||
{
|
||||
// Hw tests show that the depth export register is default-initialized to 0 and not wpos.z!!
|
||||
m_parr.AddParam(PF_PARAM_NONE, getFloatTypeName(4), "r1", init_value);
|
||||
|
|
|
|||
|
|
@ -99,7 +99,6 @@ class FragmentProgramDecompiler
|
|||
|
||||
protected:
|
||||
const RSXFragmentProgram &m_prog;
|
||||
u32 m_ctrl = 0;
|
||||
|
||||
/** returns the type name of float vectors.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -216,12 +216,12 @@ namespace glsl
|
|||
enabled_options.push_back("_32_BIT_OUTPUT");
|
||||
}
|
||||
|
||||
if (!props.fp32_outputs)
|
||||
if (props.ROP_sRGB_packing)
|
||||
{
|
||||
enabled_options.push_back("_ENABLE_FRAMEBUFFER_SRGB");
|
||||
}
|
||||
|
||||
if (props.disable_early_discard)
|
||||
if (props.disable_early_discard && props.ROP_discard)
|
||||
{
|
||||
enabled_options.push_back("_DISABLE_EARLY_DISCARD");
|
||||
}
|
||||
|
|
@ -231,7 +231,15 @@ namespace glsl
|
|||
enabled_options.push_back("_ENABLE_ROP_OUTPUT_ROUNDING");
|
||||
}
|
||||
|
||||
enabled_options.push_back("_ENABLE_POLYGON_STIPPLE");
|
||||
if (props.ROP_alpha_test)
|
||||
{
|
||||
enabled_options.push_back("_ENABLE_ALPHA_TEST");
|
||||
}
|
||||
|
||||
if (props.ROP_polygon_stipple_test)
|
||||
{
|
||||
enabled_options.push_back("_ENABLE_POLYGON_STIPPLE");
|
||||
}
|
||||
}
|
||||
|
||||
// Import common header
|
||||
|
|
@ -276,12 +284,12 @@ namespace glsl
|
|||
return;
|
||||
}
|
||||
|
||||
if (props.emulate_coverage_tests)
|
||||
if (props.ROP_alpha_to_coverage_test)
|
||||
{
|
||||
enabled_options.push_back("_EMULATE_COVERAGE_TEST");
|
||||
enabled_options.push_back("_ENABLE_ALPHA_TO_COVERAGE_TEST");
|
||||
}
|
||||
|
||||
if (!props.fp32_outputs || props.require_linear_to_srgb)
|
||||
if (props.ROP_sRGB_packing || props.require_linear_to_srgb)
|
||||
{
|
||||
enabled_options.push_back("_ENABLE_LINEAR_TO_SRGB");
|
||||
}
|
||||
|
|
@ -296,6 +304,11 @@ namespace glsl
|
|||
enabled_options.push_back("_ENABLE_WPOS");
|
||||
}
|
||||
|
||||
if (props.ROP_alpha_test || (props.require_msaa_ops && props.require_tex_shadow_ops))
|
||||
{
|
||||
enabled_options.push_back("_ENABLE_COMPARISON_FUNC");
|
||||
}
|
||||
|
||||
if (props.require_fog_read)
|
||||
{
|
||||
program_common::define_glsl_constants<rsx::fog_mode>(OS,
|
||||
|
|
@ -385,6 +398,11 @@ namespace glsl
|
|||
enabled_options.push_back("_ENABLE_SHADOWPROJ");
|
||||
}
|
||||
|
||||
if (props.require_alpha_kill)
|
||||
{
|
||||
enabled_options.push_back("_ENABLE_TEXTURE_ALPHA_KILL");
|
||||
}
|
||||
|
||||
program_common::define_glsl_switches(OS, enabled_options);
|
||||
enabled_options.clear();
|
||||
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ vec4 fetch_fog_value(const in uint mode)
|
|||
}
|
||||
#endif
|
||||
|
||||
#ifdef _EMULATE_COVERAGE_TEST
|
||||
#ifdef _ENABLE_ALPHA_TO_COVERAGE_TEST
|
||||
// Purely stochastic
|
||||
bool coverage_test_passes(const in vec4 _sample)
|
||||
{
|
||||
|
|
@ -109,6 +109,7 @@ vec4 srgb_to_linear(const in vec4 cs)
|
|||
}
|
||||
#endif
|
||||
|
||||
#ifdef _ENABLE_COMPARISON_FUNC
|
||||
// Required by all fragment shaders for alpha test
|
||||
bool comparison_passes(const in float a, const in float b, const in uint func)
|
||||
{
|
||||
|
|
@ -125,5 +126,6 @@ bool comparison_passes(const in float a, const in float b, const in uint func)
|
|||
case 7: return true; //always
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
)"
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
R"(
|
||||
#define ZS_READ(index, coord) vec2(texture(TEX_NAME(index), coord).r, float(texture(TEX_NAME_STENCIL(index), coord).x))
|
||||
#define TEX1D_Z24X8_RGBA8(index, coord1) _process_texel(convert_z24x8_to_rgba8(ZS_READ(index, COORD_SCALE1(index, coord1)), texture_parameters[index + texture_base_index].remap, TEX_FLAGS(index)), TEX_FLAGS(index))
|
||||
#define TEX2D_Z24X8_RGBA8(index, coord2) _process_texel(convert_z24x8_to_rgba8(ZS_READ(index, COORD_SCALE2(index, coord2)), texture_parameters[index + texture_base_index].remap, TEX_FLAGS(index)), TEX_FLAGS(index))
|
||||
#define TEX3D_Z24X8_RGBA8(index, coord3) _process_texel(convert_z24x8_to_rgba8(ZS_READ(index, COORD_SCALE3(index, coord3)), texture_parameters[index + texture_base_index].remap, TEX_FLAGS(index)), TEX_FLAGS(index))
|
||||
#define TEX1D_Z24X8_RGBA8(index, coord1) _process_texel(convert_z24x8_to_rgba8(ZS_READ(index, COORD_SCALE1(index, coord1)), TEX_PARAM(index).remap, TEX_FLAGS(index)), TEX_FLAGS(index))
|
||||
#define TEX2D_Z24X8_RGBA8(index, coord2) _process_texel(convert_z24x8_to_rgba8(ZS_READ(index, COORD_SCALE2(index, coord2)), TEX_PARAM(index).remap, TEX_FLAGS(index)), TEX_FLAGS(index))
|
||||
#define TEX3D_Z24X8_RGBA8(index, coord3) _process_texel(convert_z24x8_to_rgba8(ZS_READ(index, COORD_SCALE3(index, coord3)), TEX_PARAM(index).remap, TEX_FLAGS(index)), TEX_FLAGS(index))
|
||||
|
||||
// NOTE: Memory layout is fetched as byteswapped BGRA [GBAR] (GOW collection, DS2, DeS)
|
||||
// The A component (Z) is useless (should contain stencil8 or just 1)
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
R"(
|
||||
#define ZCOMPARE_FUNC(index) _get_bits(TEX_FLAGS(index), DEPTH_COMPARE, 3)
|
||||
#define ZS_READ_MS(index, coord) vec2(sampleTexture2DMS(TEX_NAME(index), coord, index).r, float(sampleTexture2DMS(TEX_NAME_STENCIL(index), coord, index).x))
|
||||
#define TEX2D_MS(index, coord2) _process_texel(sampleTexture2DMS(TEX_NAME(index), coord2, index), TEX_FLAGS(index))
|
||||
#define TEX2D_SHADOW_MS(index, coord3) vec4(comparison_passes(sampleTexture2DMS(TEX_NAME(index), coord3.xy, index).x, coord3.z, ZCOMPARE_FUNC(index)))
|
||||
#define ZS_READ_MS(index, coord) vec2(sampleTexture2DMS(TEX_NAME(index), coord, TEX_PARAM(index)).r, float(sampleTexture2DMS(TEX_NAME_STENCIL(index), coord, TEX_PARAM(index)).x))
|
||||
#define TEX2D_MS(index, coord2) _process_texel(sampleTexture2DMS(TEX_NAME(index), coord2, TEX_PARAM(index)), TEX_FLAGS(index))
|
||||
#define TEX2D_SHADOW_MS(index, coord3) vec4(comparison_passes(sampleTexture2DMS(TEX_NAME(index), coord3.xy, TEX_PARAM(index)).x, coord3.z, ZCOMPARE_FUNC(index)))
|
||||
#define TEX2D_SHADOWPROJ_MS(index, coord4) TEX2D_SHADOW_MS(index, (coord4.xyz / coord4.w))
|
||||
#define TEX2D_Z24X8_RGBA8_MS(index, coord2) _process_texel(convert_z24x8_to_rgba8(ZS_READ_MS(index, coord2), texture_parameters[index + texture_base_index].remap, TEX_FLAGS(index)), TEX_FLAGS(index))
|
||||
#define TEX2D_Z24X8_RGBA8_MS(index, coord2) _process_texel(convert_z24x8_to_rgba8(ZS_READ_MS(index, coord2), TEX_PARAM(index).remap, TEX_FLAGS(index)), TEX_FLAGS(index))
|
||||
|
||||
vec3 compute2x2DownsampleWeights(const in float coord, const in float uv_step, const in float actual_step)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
R"(
|
||||
vec4 texelFetch2DMS(in _MSAA_SAMPLER_TYPE_ tex, const in vec2 sample_count, const in ivec2 icoords, const in int index, const in ivec2 offset)
|
||||
vec4 texelFetch2DMS(in _MSAA_SAMPLER_TYPE_ tex, const in vec2 sample_count, const in ivec2 icoords, const in ivec2 offset)
|
||||
{
|
||||
const vec2 resolve_coords = vec2(icoords + offset);
|
||||
const vec2 aa_coords = floor(resolve_coords / sample_count); // AA coords = real_coords / sample_count
|
||||
|
|
@ -8,15 +8,15 @@ vec4 texelFetch2DMS(in _MSAA_SAMPLER_TYPE_ tex, const in vec2 sample_count, cons
|
|||
return texelFetch(tex, ivec2(aa_coords), int(sample_index));
|
||||
}
|
||||
|
||||
vec4 sampleTexture2DMS(in _MSAA_SAMPLER_TYPE_ tex, const in vec2 coords, const in int index)
|
||||
vec4 sampleTexture2DMS(in _MSAA_SAMPLER_TYPE_ tex, const in vec2 coords, const in sampler_info tex_params)
|
||||
{
|
||||
const uint flags = TEX_FLAGS(index);
|
||||
const vec2 scaled_coords = COORD_SCALE2(index, coords);
|
||||
const uint flags = tex_params.flags;
|
||||
const vec2 scaled_coords = _texcoord_xform(coords, tex_params);
|
||||
const vec2 normalized_coords = texture2DMSCoord(scaled_coords, flags);
|
||||
const vec2 sample_count = vec2(2., textureSamples(tex) * 0.5);
|
||||
const vec2 image_size = textureSize(tex) * sample_count;
|
||||
const ivec2 icoords = ivec2(normalized_coords * image_size);
|
||||
const vec4 sample0 = texelFetch2DMS(tex, sample_count, icoords, index, ivec2(0));
|
||||
const vec4 sample0 = texelFetch2DMS(tex, sample_count, icoords, ivec2(0));
|
||||
|
||||
if (_get_bits(flags, FILTERED_MAG_BIT, 2) == 0)
|
||||
{
|
||||
|
|
@ -35,7 +35,7 @@ vec4 sampleTexture2DMS(in _MSAA_SAMPLER_TYPE_ tex, const in vec2 coords, const i
|
|||
|
||||
vec4 a, b;
|
||||
float factor;
|
||||
const vec4 sample2 = texelFetch2DMS(tex, sample_count, icoords, index, ivec2(0, 1)); // Top left
|
||||
const vec4 sample2 = texelFetch2DMS(tex, sample_count, icoords, ivec2(0, 1)); // Top left
|
||||
|
||||
if (no_filter.x)
|
||||
{
|
||||
|
|
@ -46,21 +46,21 @@ vec4 sampleTexture2DMS(in _MSAA_SAMPLER_TYPE_ tex, const in vec2 coords, const i
|
|||
else
|
||||
{
|
||||
// Filter required, sample more data
|
||||
const vec4 sample1 = texelFetch2DMS(tex, sample_count, icoords, index, ivec2(1, 0)); // Bottom right
|
||||
const vec4 sample3 = texelFetch2DMS(tex, sample_count, icoords, index, ivec2(1, 1)); // Top right
|
||||
const vec4 sample1 = texelFetch2DMS(tex, sample_count, icoords, ivec2(1, 0)); // Bottom right
|
||||
const vec4 sample3 = texelFetch2DMS(tex, sample_count, icoords, ivec2(1, 1)); // Top right
|
||||
|
||||
if (actual_step.x > uv_step.x)
|
||||
{
|
||||
// Downscale in X, centered
|
||||
const vec3 weights = compute2x2DownsampleWeights(normalized_coords.x, uv_step.x, actual_step.x);
|
||||
|
||||
const vec4 sample4 = texelFetch2DMS(tex, sample_count, icoords, index, ivec2(2, 0)); // Further bottom right
|
||||
a = fma(sample0, weights.xxxx, sample1 * weights.y) + (sample4 * weights.z); // Weighted sum
|
||||
const vec4 sample4 = texelFetch2DMS(tex, sample_count, icoords, ivec2(2, 0)); // Further bottom right
|
||||
a = fma(sample0, weights.xxxx, sample1 * weights.y) + (sample4 * weights.z); // Weighted sum
|
||||
|
||||
if (!no_filter.y)
|
||||
{
|
||||
const vec4 sample5 = texelFetch2DMS(tex, sample_count, icoords, index, ivec2(2, 1)); // Further top right
|
||||
b = fma(sample2, weights.xxxx, sample3 * weights.y) + (sample5 * weights.z); // Weighted sum
|
||||
const vec4 sample5 = texelFetch2DMS(tex, sample_count, icoords, ivec2(2, 1)); // Further top right
|
||||
b = fma(sample2, weights.xxxx, sample3 * weights.y) + (sample5 * weights.z); // Weighted sum
|
||||
}
|
||||
}
|
||||
else if (actual_step.x < uv_step.x)
|
||||
|
|
|
|||
|
|
@ -24,17 +24,17 @@ R"(
|
|||
uint _texture_flag_override = 0;
|
||||
#define _enable_texture_expand() _texture_flag_override = SIGN_EXPAND_MASK
|
||||
#define _disable_texture_expand() _texture_flag_override = 0
|
||||
#define TEX_FLAGS(index) (texture_parameters[index + texture_base_index].flags | _texture_flag_override)
|
||||
#define TEX_FLAGS(index) (TEX_PARAM(index).flags | _texture_flag_override)
|
||||
#else
|
||||
#define TEX_FLAGS(index) texture_parameters[index + texture_base_index].flags
|
||||
#define TEX_FLAGS(index) TEX_PARAM(index).flags
|
||||
#endif
|
||||
|
||||
#define TEX_NAME(index) tex##index
|
||||
#define TEX_NAME_STENCIL(index) tex##index##_stencil
|
||||
|
||||
#define COORD_SCALE1(index, coord1) _texcoord_xform(coord1, texture_parameters[index + texture_base_index])
|
||||
#define COORD_SCALE2(index, coord2) _texcoord_xform(coord2, texture_parameters[index + texture_base_index])
|
||||
#define COORD_SCALE3(index, coord3) _texcoord_xform(coord3, texture_parameters[index + texture_base_index])
|
||||
#define COORD_SCALE1(index, coord1) _texcoord_xform(coord1, TEX_PARAM(index))
|
||||
#define COORD_SCALE2(index, coord2) _texcoord_xform(coord2, TEX_PARAM(index))
|
||||
#define COORD_SCALE3(index, coord3) _texcoord_xform(coord3, TEX_PARAM(index))
|
||||
#define COORD_PROJ1(index, coord2) COORD_SCALE1(index, coord2.x / coord2.y)
|
||||
#define COORD_PROJ2(index, coord3) COORD_SCALE2(index, coord3.xy / coord3.z)
|
||||
#define COORD_PROJ3(index, coord4) COORD_SCALE3(index, coord4.xyz / coord4.w)
|
||||
|
|
@ -57,9 +57,9 @@ R"(
|
|||
|
||||
#ifdef _ENABLE_SHADOW
|
||||
#ifdef _EMULATED_TEXSHADOW
|
||||
#define SHADOW_COORD(index, coord3) _texcoord_xform_shadow(coord3, texture_parameters[index + texture_base_index])
|
||||
#define SHADOW_COORD4(index, coord4) _texcoord_xform_shadow(coord4, texture_parameters[index + texture_base_index])
|
||||
#define SHADOW_COORD_PROJ(index, coord4) _texcoord_xform_shadow(coord4.xyz / coord4.w, texture_parameters[index + texture_base_index])
|
||||
#define SHADOW_COORD(index, coord3) _texcoord_xform_shadow(coord3, TEX_PARAM(index))
|
||||
#define SHADOW_COORD4(index, coord4) _texcoord_xform_shadow(coord4, TEX_PARAM(index))
|
||||
#define SHADOW_COORD_PROJ(index, coord4) _texcoord_xform_shadow(coord4.xyz / coord4.w, TEX_PARAM(index))
|
||||
|
||||
#define TEX2D_SHADOW(index, coord3) texture(TEX_NAME(index), SHADOW_COORD(index, coord3))
|
||||
#define TEX3D_SHADOW(index, coord4) texture(TEX_NAME(index), SHADOW_COORD4(index, coord4))
|
||||
|
|
@ -188,6 +188,7 @@ vec4 _process_texel(in vec4 rgba, const in uint control_bits)
|
|||
return rgba;
|
||||
}
|
||||
|
||||
#ifdef _ENABLE_TEXTURE_ALPHA_KILL
|
||||
if (_test_bit(control_bits, ALPHAKILL))
|
||||
{
|
||||
// Alphakill
|
||||
|
|
@ -197,6 +198,7 @@ vec4 _process_texel(in vec4 rgba, const in uint control_bits)
|
|||
return rgba;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (_test_bit(control_bits, RENORMALIZE))
|
||||
{
|
||||
|
|
|
|||
|
|
@ -8,43 +8,33 @@ R"(
|
|||
#endif
|
||||
|
||||
#ifdef _ENABLE_FRAMEBUFFER_SRGB
|
||||
if (_test_bit(rop_control, SRGB_FRAMEBUFFER_BIT))
|
||||
{
|
||||
col0.rgb = _mrt_color_t(linear_to_srgb(col0)).rgb;
|
||||
col1.rgb = _mrt_color_t(linear_to_srgb(col1)).rgb;
|
||||
col2.rgb = _mrt_color_t(linear_to_srgb(col2)).rgb;
|
||||
col3.rgb = _mrt_color_t(linear_to_srgb(col3)).rgb;
|
||||
}
|
||||
col0.rgb = _mrt_color_t(linear_to_srgb(col0)).rgb;
|
||||
col1.rgb = _mrt_color_t(linear_to_srgb(col1)).rgb;
|
||||
col2.rgb = _mrt_color_t(linear_to_srgb(col2)).rgb;
|
||||
col3.rgb = _mrt_color_t(linear_to_srgb(col3)).rgb;
|
||||
#endif
|
||||
|
||||
#ifdef _ENABLE_ROP_OUTPUT_ROUNDING
|
||||
if (_test_bit(rop_control, INT_FRAMEBUFFER_BIT))
|
||||
{
|
||||
col0 = round_to_8bit(col0);
|
||||
col1 = round_to_8bit(col1);
|
||||
col2 = round_to_8bit(col2);
|
||||
col3 = round_to_8bit(col3);
|
||||
}
|
||||
col0 = round_to_8bit(col0);
|
||||
col1 = round_to_8bit(col1);
|
||||
col2 = round_to_8bit(col2);
|
||||
col3 = round_to_8bit(col3);
|
||||
#endif
|
||||
|
||||
// Post-output stages
|
||||
// Alpha Testing
|
||||
if (_test_bit(rop_control, ALPHA_TEST_ENABLE_BIT))
|
||||
#ifdef _ENABLE_ALPHA_TEST
|
||||
const uint alpha_func = _get_bits(rop_control, ALPHA_TEST_FUNC_OFFSET, ALPHA_TEST_FUNC_LENGTH);
|
||||
if (!comparison_passes(col0.a, alpha_ref, alpha_func))
|
||||
{
|
||||
const uint alpha_func = _get_bits(rop_control, ALPHA_TEST_FUNC_OFFSET, ALPHA_TEST_FUNC_LENGTH);
|
||||
if (!comparison_passes(col0.a, alpha_ref, alpha_func))
|
||||
{
|
||||
discard;
|
||||
}
|
||||
discard;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef _EMULATE_COVERAGE_TEST
|
||||
if (_test_bit(rop_control, ALPHA_TO_COVERAGE_ENABLE_BIT))
|
||||
#ifdef _ENABLE_ALPHA_TO_COVERAGE_TEST
|
||||
if (!coverage_test_passes(col0))
|
||||
{
|
||||
if (!_test_bit(rop_control, MSAA_WRITE_ENABLE_BIT) || !coverage_test_passes(col0))
|
||||
{
|
||||
discard;
|
||||
}
|
||||
discard;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
|
|
|||
|
|
@ -1,24 +1,21 @@
|
|||
R"(
|
||||
|
||||
#ifdef _ENABLE_POLYGON_STIPPLE
|
||||
if (_test_bit(rop_control, POLYGON_STIPPLE_ENABLE_BIT))
|
||||
{
|
||||
// Convert x,y to linear address
|
||||
const uvec2 stipple_coord = uvec2(gl_FragCoord.xy) % uvec2(32, 32);
|
||||
const uint address = stipple_coord.y * 32u + stipple_coord.x;
|
||||
const uint bit_offset = (address & 31u);
|
||||
#ifdef VULKAN
|
||||
// In vulkan we have a unified array with a dynamic offset
|
||||
const uint word_index = _get_bits(address, 7, 3) + _fs_stipple_pattern_array_offset;
|
||||
#else
|
||||
const uint word_index = _get_bits(address, 7, 3);
|
||||
#endif
|
||||
const uint sub_index = _get_bits(address, 5, 2);
|
||||
// Convert x,y to linear address
|
||||
const uvec2 stipple_coord = uvec2(gl_FragCoord.xy) % uvec2(32, 32);
|
||||
const uint address = stipple_coord.y * 32u + stipple_coord.x;
|
||||
const uint bit_offset = (address & 31u);
|
||||
#ifdef VULKAN
|
||||
// In vulkan we have a unified array with a dynamic offset
|
||||
const uint word_index = _get_bits(address, 7, 3) + _fs_stipple_pattern_array_offset;
|
||||
#else
|
||||
const uint word_index = _get_bits(address, 7, 3);
|
||||
#endif
|
||||
const uint sub_index = _get_bits(address, 5, 2);
|
||||
|
||||
if (!_test_bit(stipple_pattern[word_index][sub_index], int(bit_offset)))
|
||||
{
|
||||
_kill();
|
||||
}
|
||||
if (!_test_bit(stipple_pattern[word_index][sub_index], int(bit_offset)))
|
||||
{
|
||||
_kill();
|
||||
}
|
||||
#endif
|
||||
|
||||
|
|
|
|||
|
|
@ -36,12 +36,18 @@ namespace glsl
|
|||
bool require_srgb_to_linear : 1;
|
||||
bool require_linear_to_srgb : 1;
|
||||
bool require_fog_read : 1;
|
||||
bool emulate_coverage_tests : 1;
|
||||
bool emulate_shadow_compare : 1;
|
||||
bool low_precision_tests : 1;
|
||||
bool disable_early_discard : 1;
|
||||
bool supports_native_fp16 : 1;
|
||||
|
||||
// ROP control flags
|
||||
bool ROP_output_rounding : 1;
|
||||
bool ROP_sRGB_packing : 1;
|
||||
bool ROP_alpha_test : 1;
|
||||
bool ROP_alpha_to_coverage_test : 1;
|
||||
bool ROP_polygon_stipple_test : 1;
|
||||
bool ROP_discard : 1;
|
||||
|
||||
// Texturing spec
|
||||
bool require_texture_ops : 1; // Global switch to enable/disable all texture code
|
||||
|
|
@ -53,5 +59,6 @@ namespace glsl
|
|||
bool require_tex2D_ops : 1; // Include 2D texture stuff
|
||||
bool require_tex3D_ops : 1; // Include 3D texture stuff (including cubemap)
|
||||
bool require_shadowProj_ops : 1; // Include shadow2DProj projection textures (1D is unsupported anyway)
|
||||
bool require_alpha_kill : 1; // Include alpha kill checking code
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1681,10 +1681,24 @@ namespace rsx
|
|||
return;
|
||||
}
|
||||
|
||||
auto set_zeta_write_enabled = [&](bool state)
|
||||
{
|
||||
if (state == m_framebuffer_layout.zeta_write_enabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_graphics_state & rsx::zeta_address_is_cyclic)
|
||||
{
|
||||
m_graphics_state |= rsx::fragment_program_state_dirty;
|
||||
}
|
||||
m_framebuffer_layout.zeta_write_enabled = state;
|
||||
};
|
||||
|
||||
auto evaluate_depth_buffer_state = [&]()
|
||||
{
|
||||
m_framebuffer_layout.zeta_write_enabled =
|
||||
(rsx::method_registers.depth_test_enabled() && rsx::method_registers.depth_write_enabled());
|
||||
const bool zeta_write_en = (rsx::method_registers.depth_test_enabled() && rsx::method_registers.depth_write_enabled());
|
||||
set_zeta_write_enabled(zeta_write_en);
|
||||
};
|
||||
|
||||
auto evaluate_stencil_buffer_state = [&]()
|
||||
|
|
@ -1707,7 +1721,7 @@ namespace rsx
|
|||
rsx::method_registers.back_stencil_op_zfail() != rsx::stencil_op::keep);
|
||||
}
|
||||
|
||||
m_framebuffer_layout.zeta_write_enabled = (mask && active_write_op);
|
||||
set_zeta_write_enabled(mask && active_write_op);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -1726,14 +1740,7 @@ namespace rsx
|
|||
}
|
||||
}
|
||||
|
||||
if (::size32(mrt_buffers) != current_fragment_program.mrt_buffers_count &&
|
||||
!m_graphics_state.test(rsx::pipeline_state::fragment_program_dirty) &&
|
||||
!is_current_program_interpreted())
|
||||
{
|
||||
// Notify that we should recompile the FS
|
||||
m_graphics_state |= rsx::pipeline_state::fragment_program_state_dirty;
|
||||
}
|
||||
|
||||
on_framebuffer_layout_updated();
|
||||
return any_found;
|
||||
};
|
||||
|
||||
|
|
@ -1831,6 +1838,22 @@ namespace rsx
|
|||
}
|
||||
}
|
||||
|
||||
void thread::on_framebuffer_layout_updated()
|
||||
{
|
||||
if (m_graphics_state.test(rsx::fragment_program_state_dirty))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const auto target = m_ctx->register_state->surface_color_target();
|
||||
if (rsx::utility::get_mrt_buffers_count(target) == current_fragment_program.mrt_buffers_count)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
m_graphics_state |= rsx::fragment_program_state_dirty;
|
||||
}
|
||||
|
||||
bool thread::get_scissor(areau& region, bool clip_viewport)
|
||||
{
|
||||
if (!m_graphics_state.test(rsx::pipeline_state::scissor_config_state_dirty))
|
||||
|
|
@ -2052,220 +2075,287 @@ namespace rsx
|
|||
|
||||
m_graphics_state.clear(rsx::pipeline_state::fragment_program_dirty);
|
||||
|
||||
current_fragment_program.ctrl = m_ctx->register_state->shader_control() & (CELL_GCM_SHADER_CONTROL_32_BITS_EXPORTS | CELL_GCM_SHADER_CONTROL_DEPTH_EXPORT);
|
||||
current_fragment_program.ctrl = m_ctx->register_state->shader_control() & (CELL_GCM_SHADER_CONTROL_32_BITS_EXPORTS | CELL_GCM_SHADER_CONTROL_DEPTH_EXPORT | RSX_SHADER_CONTROL_USES_KIL);
|
||||
current_fragment_program.texcoord_control_mask = m_ctx->register_state->texcoord_control_mask();
|
||||
current_fragment_program.two_sided_lighting = m_ctx->register_state->two_side_light_en();
|
||||
current_fragment_program.mrt_buffers_count = rsx::utility::get_mrt_buffers_count(m_ctx->register_state->surface_color_target());
|
||||
|
||||
if (method_registers.current_draw_clause.classify_mode() == primitive_class::polygon)
|
||||
if (m_ctx->register_state->current_draw_clause.classify_mode() == primitive_class::polygon)
|
||||
{
|
||||
if (!backend_config.supports_normalized_barycentrics)
|
||||
{
|
||||
current_fragment_program.ctrl |= RSX_SHADER_CONTROL_ATTRIBUTE_INTERPOLATION;
|
||||
}
|
||||
|
||||
if (m_ctx->register_state->alpha_test_enabled())
|
||||
{
|
||||
current_fragment_program.ctrl |= RSX_SHADER_CONTROL_ALPHA_TEST;
|
||||
}
|
||||
|
||||
if (m_ctx->register_state->polygon_stipple_enabled())
|
||||
{
|
||||
current_fragment_program.ctrl |= RSX_SHADER_CONTROL_POLYGON_STIPPLE;
|
||||
}
|
||||
|
||||
if (m_ctx->register_state->msaa_alpha_to_coverage_enabled())
|
||||
{
|
||||
const bool is_multiple_samples = m_ctx->register_state->surface_antialias() != rsx::surface_antialiasing::center_1_sample;
|
||||
if (!backend_config.supports_hw_a2c || (!is_multiple_samples && !backend_config.supports_hw_a2c_1spp))
|
||||
{
|
||||
// Emulation required
|
||||
current_fragment_program.ctrl |= RSX_SHADER_CONTROL_ALPHA_TO_COVERAGE;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (method_registers.point_sprite_enabled() &&
|
||||
method_registers.current_draw_clause.primitive == primitive_type::points)
|
||||
else if (m_ctx->register_state->point_sprite_enabled() &&
|
||||
m_ctx->register_state->current_draw_clause.primitive == primitive_type::points)
|
||||
{
|
||||
// Set high word of the control mask to store point sprite control
|
||||
current_fragment_program.texcoord_control_mask |= u32(method_registers.point_sprite_control_mask()) << 16;
|
||||
current_fragment_program.texcoord_control_mask |= u32(m_ctx->register_state->point_sprite_control_mask()) << 16;
|
||||
}
|
||||
|
||||
// Check if framebuffer is actually an XRGB format and not a WZYX format
|
||||
switch (m_ctx->register_state->surface_color())
|
||||
{
|
||||
case rsx::surface_color_format::w16z16y16x16:
|
||||
case rsx::surface_color_format::w32z32y32x32:
|
||||
case rsx::surface_color_format::x32:
|
||||
// These behave very differently from "normal" formats.
|
||||
break;
|
||||
default:
|
||||
// Integer framebuffer formats. These can support sRGB output as well as some special rules for output quantization.
|
||||
current_fragment_program.ctrl |= RSX_SHADER_CONTROL_8BIT_FRAMEBUFFER;
|
||||
if (!(current_fragment_program.ctrl & CELL_GCM_SHADER_CONTROL_32_BITS_EXPORTS) && // Cannot output sRGB from 32-bit registers
|
||||
m_ctx->register_state->framebuffer_srgb_enabled())
|
||||
{
|
||||
current_fragment_program.ctrl |= RSX_SHADER_CONTROL_SRGB_FRAMEBUFFER;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
const bool zeta_was_cyclic = m_graphics_state & rsx::zeta_address_is_cyclic;
|
||||
m_graphics_state.clear(rsx::zeta_address_is_cyclic);
|
||||
|
||||
for (u32 textures_ref = current_fp_metadata.referenced_textures_mask, i = 0; textures_ref; textures_ref >>= 1, ++i)
|
||||
{
|
||||
if (!(textures_ref & 1)) continue;
|
||||
|
||||
auto &tex = rsx::method_registers.fragment_textures[i];
|
||||
auto &tex = m_ctx->register_state->fragment_textures[i];
|
||||
current_fp_texture_state.clear(i);
|
||||
|
||||
if (tex.enabled() && sampler_descriptors[i]->format_class != RSX_FORMAT_CLASS_UNDEFINED)
|
||||
if (!tex.enabled() || sampler_descriptors[i]->format_class == RSX_FORMAT_CLASS_UNDEFINED)
|
||||
{
|
||||
std::memcpy(current_fragment_program.texture_params[i].scale, sampler_descriptors[i]->texcoord_xform.scale, 6 * sizeof(f32));
|
||||
current_fragment_program.texture_params[i].remap = tex.remap();
|
||||
|
||||
m_graphics_state |= rsx::pipeline_state::fragment_texture_state_dirty;
|
||||
|
||||
u32 texture_control = 0;
|
||||
current_fp_texture_state.set_dimension(sampler_descriptors[i]->image_type, i);
|
||||
|
||||
if (sampler_descriptors[i]->texcoord_xform.clamp)
|
||||
{
|
||||
std::memcpy(current_fragment_program.texture_params[i].clamp_min, sampler_descriptors[i]->texcoord_xform.clamp_min, 4 * sizeof(f32));
|
||||
texture_control |= (1 << rsx::texture_control_bits::CLAMP_TEXCOORDS_BIT);
|
||||
}
|
||||
|
||||
if (tex.alpha_kill_enabled())
|
||||
{
|
||||
//alphakill can be ignored unless a valid comparison function is set
|
||||
texture_control |= (1 << texture_control_bits::ALPHAKILL);
|
||||
}
|
||||
|
||||
//const u32 texaddr = rsx::get_address(tex.offset(), tex.location());
|
||||
const u32 raw_format = tex.format();
|
||||
const u32 format = raw_format & ~(CELL_GCM_TEXTURE_LN | CELL_GCM_TEXTURE_UN);
|
||||
|
||||
if (raw_format & CELL_GCM_TEXTURE_UN)
|
||||
{
|
||||
if (tex.min_filter() == rsx::texture_minify_filter::nearest ||
|
||||
tex.mag_filter() == rsx::texture_magnify_filter::nearest)
|
||||
{
|
||||
// Subpixel offset so that (X + bias) * scale will round correctly.
|
||||
// This is done to work around fdiv precision issues in some GPUs (NVIDIA)
|
||||
// We apply the simplification where (x + bias) * z = xz + zbias here.
|
||||
constexpr auto subpixel_bias = 0.01f;
|
||||
current_fragment_program.texture_params[i].bias[0] += (subpixel_bias * current_fragment_program.texture_params[i].scale[0]);
|
||||
current_fragment_program.texture_params[i].bias[1] += (subpixel_bias * current_fragment_program.texture_params[i].scale[1]);
|
||||
current_fragment_program.texture_params[i].bias[2] += (subpixel_bias * current_fragment_program.texture_params[i].scale[2]);
|
||||
}
|
||||
}
|
||||
|
||||
if (backend_config.supports_hw_msaa && sampler_descriptors[i]->samples > 1)
|
||||
{
|
||||
current_fp_texture_state.multisampled_textures |= (1 << i);
|
||||
texture_control |= (static_cast<u32>(tex.zfunc()) << texture_control_bits::DEPTH_COMPARE_OP);
|
||||
texture_control |= (static_cast<u32>(tex.mag_filter() != rsx::texture_magnify_filter::nearest) << texture_control_bits::FILTERED_MAG);
|
||||
texture_control |= (static_cast<u32>(tex.min_filter() != rsx::texture_minify_filter::nearest) << texture_control_bits::FILTERED_MIN);
|
||||
texture_control |= (((tex.format() & CELL_GCM_TEXTURE_UN) >> 6) << texture_control_bits::UNNORMALIZED_COORDS);
|
||||
|
||||
if (rsx::is_texcoord_wrapping_mode(tex.wrap_s()))
|
||||
{
|
||||
texture_control |= (1 << texture_control_bits::WRAP_S);
|
||||
}
|
||||
|
||||
if (rsx::is_texcoord_wrapping_mode(tex.wrap_t()))
|
||||
{
|
||||
texture_control |= (1 << texture_control_bits::WRAP_T);
|
||||
}
|
||||
|
||||
if (rsx::is_texcoord_wrapping_mode(tex.wrap_r()))
|
||||
{
|
||||
texture_control |= (1 << texture_control_bits::WRAP_R);
|
||||
}
|
||||
}
|
||||
|
||||
if (sampler_descriptors[i]->format_class != RSX_FORMAT_CLASS_COLOR)
|
||||
{
|
||||
switch (sampler_descriptors[i]->format_class)
|
||||
{
|
||||
case RSX_FORMAT_CLASS_DEPTH16_FLOAT:
|
||||
case RSX_FORMAT_CLASS_DEPTH24_FLOAT_X8_PACK32:
|
||||
texture_control |= (1 << texture_control_bits::DEPTH_FLOAT);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
switch (format)
|
||||
{
|
||||
case CELL_GCM_TEXTURE_A8R8G8B8:
|
||||
case CELL_GCM_TEXTURE_D8R8G8B8:
|
||||
{
|
||||
// Emulate bitcast in shader
|
||||
current_fp_texture_state.redirected_textures |= (1 << i);
|
||||
const auto float_en = (sampler_descriptors[i]->format_class == RSX_FORMAT_CLASS_DEPTH24_FLOAT_X8_PACK32)? 1 : 0;
|
||||
texture_control |= (float_en << texture_control_bits::DEPTH_FLOAT);
|
||||
break;
|
||||
}
|
||||
case CELL_GCM_TEXTURE_X16:
|
||||
{
|
||||
// A simple way to quickly read DEPTH16 data without shadow comparison
|
||||
break;
|
||||
}
|
||||
case CELL_GCM_TEXTURE_DEPTH16:
|
||||
case CELL_GCM_TEXTURE_DEPTH24_D8:
|
||||
case CELL_GCM_TEXTURE_DEPTH16_FLOAT:
|
||||
case CELL_GCM_TEXTURE_DEPTH24_D8_FLOAT:
|
||||
{
|
||||
// Natively supported Z formats with shadow comparison feature
|
||||
const auto compare_mode = tex.zfunc();
|
||||
if (!tex.alpha_kill_enabled() &&
|
||||
compare_mode < rsx::comparison_function::always &&
|
||||
compare_mode > rsx::comparison_function::never)
|
||||
{
|
||||
current_fp_texture_state.shadow_textures |= (1 << i);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
rsx_log.error("Depth texture bound to pipeline with unexpected format 0x%X", format);
|
||||
}
|
||||
}
|
||||
else if (!backend_config.supports_hw_renormalization /* &&
|
||||
tex.min_filter() == rsx::texture_minify_filter::nearest &&
|
||||
tex.mag_filter() == rsx::texture_magnify_filter::nearest*/)
|
||||
{
|
||||
// FIXME: This check should only apply to point-sampled textures. However, it severely regresses some games (id tech 5).
|
||||
// This is because even when filtering is active, the error from the PS3 texture expansion still applies.
|
||||
// A proper fix is to expand these formats into BGRA8 when high texture precision is required. That requires different GUI settings and inflation shaders, so it will be handled separately.
|
||||
|
||||
switch (format)
|
||||
{
|
||||
case CELL_GCM_TEXTURE_A1R5G5B5:
|
||||
case CELL_GCM_TEXTURE_A4R4G4B4:
|
||||
case CELL_GCM_TEXTURE_D1R5G5B5:
|
||||
case CELL_GCM_TEXTURE_R5G5B5A1:
|
||||
case CELL_GCM_TEXTURE_R5G6B5:
|
||||
case CELL_GCM_TEXTURE_R6G5B5:
|
||||
texture_control |= (1 << texture_control_bits::RENORMALIZE);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (rsx::is_int8_remapped_format(format))
|
||||
{
|
||||
// Special operations applied to 8-bit formats such as gamma correction and sign conversion
|
||||
// NOTE: The unsigned_remap=bias flag being set flags the texture as being compressed normal (2n-1 / BX2) (UE3)
|
||||
// NOTE: The ARGB8_signed flag means to reinterpret the raw bytes as signed. This is different than unsigned_remap=bias which does range decompression.
|
||||
// This is a separate method of setting the format to signed mode without doing so per-channel
|
||||
// Precedence = SNORM > GAMMA > UNSIGNED_REMAP (See Resistance 3 for GAMMA/BX2 relationship, UE3 for BX2 effect)
|
||||
|
||||
const u32 argb8_signed = tex.argb_signed(); // _SNROM
|
||||
const u32 gamma = tex.gamma() & ~argb8_signed; // _SRGB
|
||||
const u32 unsigned_remap = (tex.unsigned_remap() == CELL_GCM_TEXTURE_UNSIGNED_REMAP_NORMAL)? 0u : (~(gamma | argb8_signed) & 0xF); // _BX2
|
||||
u32 argb8_convert = gamma;
|
||||
|
||||
// The options are mutually exclusive
|
||||
ensure((argb8_signed & gamma) == 0);
|
||||
ensure((argb8_signed & unsigned_remap) == 0);
|
||||
ensure((gamma & unsigned_remap) == 0);
|
||||
|
||||
// Helper function to apply a per-channel mask based on an input mask
|
||||
const auto apply_sign_convert_mask = [&](u32 mask, u32 bit_offset)
|
||||
{
|
||||
// TODO: Use actual remap mask to account for 0 and 1 overrides in default mapping
|
||||
// TODO: Replace this clusterfuck of texture control with matrix transformation
|
||||
const auto remap_ctrl = (tex.remap() >> 8) & 0xAA;
|
||||
if (remap_ctrl == 0xAA)
|
||||
{
|
||||
argb8_convert |= (mask & 0xFu) << bit_offset;
|
||||
return;
|
||||
}
|
||||
|
||||
if ((remap_ctrl & 0x03) == 0x02) argb8_convert |= (mask & 0x1u) << bit_offset;
|
||||
if ((remap_ctrl & 0x0C) == 0x08) argb8_convert |= (mask & 0x2u) << bit_offset;
|
||||
if ((remap_ctrl & 0x30) == 0x20) argb8_convert |= (mask & 0x4u) << bit_offset;
|
||||
if ((remap_ctrl & 0xC0) == 0x80) argb8_convert |= (mask & 0x8u) << bit_offset;
|
||||
};
|
||||
|
||||
if (argb8_signed)
|
||||
{
|
||||
// Apply integer sign extension from uint8 to sint8 and renormalize
|
||||
apply_sign_convert_mask(argb8_signed, texture_control_bits::SEXT_OFFSET);
|
||||
}
|
||||
|
||||
if (unsigned_remap)
|
||||
{
|
||||
// Apply sign expansion, compressed normal-map style (2n - 1)
|
||||
apply_sign_convert_mask(unsigned_remap, texture_control_bits::EXPAND_OFFSET);
|
||||
}
|
||||
|
||||
texture_control |= argb8_convert;
|
||||
}
|
||||
|
||||
current_fragment_program.texture_params[i].control = texture_control;
|
||||
continue;
|
||||
}
|
||||
|
||||
std::memcpy(
|
||||
current_fragment_program.texture_params[i].scale,
|
||||
sampler_descriptors[i]->texcoord_xform.scale,
|
||||
sizeof(sampler_descriptors[i]->texcoord_xform.scale) * 2); // Copy scale and bias together
|
||||
|
||||
current_fragment_program.texture_params[i].remap = tex.remap();
|
||||
|
||||
m_graphics_state |= rsx::pipeline_state::fragment_texture_state_dirty;
|
||||
|
||||
u32 texture_control = 0;
|
||||
current_fp_texture_state.set_dimension(sampler_descriptors[i]->image_type, i);
|
||||
|
||||
if (sampler_descriptors[i]->texcoord_xform.clamp)
|
||||
{
|
||||
std::memcpy(
|
||||
current_fragment_program.texture_params[i].clamp_min,
|
||||
sampler_descriptors[i]->texcoord_xform.clamp_min,
|
||||
sizeof(sampler_descriptors[i]->texcoord_xform.clamp_min) * 2); // Copy clamp_min and clamp_max together
|
||||
|
||||
texture_control |= (1 << rsx::texture_control_bits::CLAMP_TEXCOORDS_BIT);
|
||||
}
|
||||
|
||||
if (tex.alpha_kill_enabled())
|
||||
{
|
||||
//alphakill can be ignored unless a valid comparison function is set
|
||||
texture_control |= (1 << texture_control_bits::ALPHAKILL);
|
||||
current_fragment_program.ctrl |= RSX_SHADER_CONTROL_TEXTURE_ALPHA_KILL;
|
||||
}
|
||||
|
||||
//const u32 texaddr = rsx::get_address(tex.offset(), tex.location());
|
||||
const u32 raw_format = tex.format();
|
||||
const u32 format = raw_format & ~(CELL_GCM_TEXTURE_LN | CELL_GCM_TEXTURE_UN);
|
||||
|
||||
if (raw_format & CELL_GCM_TEXTURE_UN)
|
||||
{
|
||||
if (tex.min_filter() == rsx::texture_minify_filter::nearest ||
|
||||
tex.mag_filter() == rsx::texture_magnify_filter::nearest)
|
||||
{
|
||||
// Subpixel offset so that (X + bias) * scale will round correctly.
|
||||
// This is done to work around fdiv precision issues in some GPUs (NVIDIA)
|
||||
// We apply the simplification where (x + bias) * z = xz + zbias here.
|
||||
constexpr auto subpixel_bias = 0.01f;
|
||||
current_fragment_program.texture_params[i].bias[0] += (subpixel_bias * current_fragment_program.texture_params[i].scale[0]);
|
||||
current_fragment_program.texture_params[i].bias[1] += (subpixel_bias * current_fragment_program.texture_params[i].scale[1]);
|
||||
current_fragment_program.texture_params[i].bias[2] += (subpixel_bias * current_fragment_program.texture_params[i].scale[2]);
|
||||
}
|
||||
}
|
||||
|
||||
if (backend_config.supports_hw_msaa && sampler_descriptors[i]->samples > 1)
|
||||
{
|
||||
current_fp_texture_state.multisampled_textures |= (1 << i);
|
||||
texture_control |= (static_cast<u32>(tex.zfunc()) << texture_control_bits::DEPTH_COMPARE_OP);
|
||||
texture_control |= (static_cast<u32>(tex.mag_filter() != rsx::texture_magnify_filter::nearest) << texture_control_bits::FILTERED_MAG);
|
||||
texture_control |= (static_cast<u32>(tex.min_filter() != rsx::texture_minify_filter::nearest) << texture_control_bits::FILTERED_MIN);
|
||||
texture_control |= (((tex.format() & CELL_GCM_TEXTURE_UN) >> 6) << texture_control_bits::UNNORMALIZED_COORDS);
|
||||
|
||||
if (rsx::is_texcoord_wrapping_mode(tex.wrap_s()))
|
||||
{
|
||||
texture_control |= (1 << texture_control_bits::WRAP_S);
|
||||
}
|
||||
|
||||
if (rsx::is_texcoord_wrapping_mode(tex.wrap_t()))
|
||||
{
|
||||
texture_control |= (1 << texture_control_bits::WRAP_T);
|
||||
}
|
||||
|
||||
if (rsx::is_texcoord_wrapping_mode(tex.wrap_r()))
|
||||
{
|
||||
texture_control |= (1 << texture_control_bits::WRAP_R);
|
||||
}
|
||||
}
|
||||
|
||||
if (sampler_descriptors[i]->format_class != RSX_FORMAT_CLASS_COLOR)
|
||||
{
|
||||
switch (sampler_descriptors[i]->format_class)
|
||||
{
|
||||
case RSX_FORMAT_CLASS_DEPTH16_FLOAT:
|
||||
case RSX_FORMAT_CLASS_DEPTH24_FLOAT_X8_PACK32:
|
||||
texture_control |= (1 << texture_control_bits::DEPTH_FLOAT);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
switch (format)
|
||||
{
|
||||
case CELL_GCM_TEXTURE_A8R8G8B8:
|
||||
case CELL_GCM_TEXTURE_D8R8G8B8:
|
||||
{
|
||||
// Emulate bitcast in shader
|
||||
current_fp_texture_state.redirected_textures |= (1 << i);
|
||||
const auto float_en = (sampler_descriptors[i]->format_class == RSX_FORMAT_CLASS_DEPTH24_FLOAT_X8_PACK32)? 1 : 0;
|
||||
texture_control |= (float_en << texture_control_bits::DEPTH_FLOAT);
|
||||
break;
|
||||
}
|
||||
case CELL_GCM_TEXTURE_X16:
|
||||
{
|
||||
// A simple way to quickly read DEPTH16 data without shadow comparison
|
||||
break;
|
||||
}
|
||||
case CELL_GCM_TEXTURE_DEPTH16:
|
||||
case CELL_GCM_TEXTURE_DEPTH24_D8:
|
||||
case CELL_GCM_TEXTURE_DEPTH16_FLOAT:
|
||||
case CELL_GCM_TEXTURE_DEPTH24_D8_FLOAT:
|
||||
{
|
||||
// Natively supported Z formats with shadow comparison feature
|
||||
const auto compare_mode = tex.zfunc();
|
||||
if (!tex.alpha_kill_enabled() &&
|
||||
compare_mode < rsx::comparison_function::always &&
|
||||
compare_mode > rsx::comparison_function::never)
|
||||
{
|
||||
current_fp_texture_state.shadow_textures |= (1 << i);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
rsx_log.error("Depth texture bound to pipeline with unexpected format 0x%X", format);
|
||||
}
|
||||
|
||||
if (sampler_descriptors[i]->is_cyclic_reference &&
|
||||
m_framebuffer_layout.zeta_address != 0 &&
|
||||
!g_cfg.video.strict_rendering_mode &&
|
||||
g_cfg.video.shader_precision != gpu_preset_level::low)
|
||||
{
|
||||
m_graphics_state |= rsx::zeta_address_is_cyclic;
|
||||
|
||||
if (!(current_fragment_program.ctrl & (CELL_GCM_SHADER_CONTROL_DEPTH_EXPORT | RSX_SHADER_CONTROL_META_USES_DISCARD)) &&
|
||||
m_framebuffer_layout.zeta_write_enabled)
|
||||
{
|
||||
current_fragment_program.ctrl |= RSX_SHADER_CONTROL_DISABLE_EARLY_Z;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (!backend_config.supports_hw_renormalization /* &&
|
||||
tex.min_filter() == rsx::texture_minify_filter::nearest &&
|
||||
tex.mag_filter() == rsx::texture_magnify_filter::nearest*/)
|
||||
{
|
||||
// FIXME: This check should only apply to point-sampled textures. However, it severely regresses some games (id tech 5).
|
||||
// This is because even when filtering is active, the error from the PS3 texture expansion still applies.
|
||||
// A proper fix is to expand these formats into BGRA8 when high texture precision is required. That requires different GUI settings and inflation shaders, so it will be handled separately.
|
||||
|
||||
switch (format)
|
||||
{
|
||||
case CELL_GCM_TEXTURE_A1R5G5B5:
|
||||
case CELL_GCM_TEXTURE_A4R4G4B4:
|
||||
case CELL_GCM_TEXTURE_D1R5G5B5:
|
||||
case CELL_GCM_TEXTURE_R5G5B5A1:
|
||||
case CELL_GCM_TEXTURE_R5G6B5:
|
||||
case CELL_GCM_TEXTURE_R6G5B5:
|
||||
texture_control |= (1 << texture_control_bits::RENORMALIZE);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (rsx::is_int8_remapped_format(format))
|
||||
{
|
||||
// Special operations applied to 8-bit formats such as gamma correction and sign conversion
|
||||
// NOTE: The unsigned_remap=bias flag being set flags the texture as being compressed normal (2n-1 / BX2) (UE3)
|
||||
// NOTE: The ARGB8_signed flag means to reinterpret the raw bytes as signed. This is different than unsigned_remap=bias which does range decompression.
|
||||
// This is a separate method of setting the format to signed mode without doing so per-channel
|
||||
// Precedence = SNORM > GAMMA > UNSIGNED_REMAP (See Resistance 3 for GAMMA/BX2 relationship, UE3 for BX2 effect)
|
||||
|
||||
const u32 argb8_signed = tex.argb_signed(); // _SNROM
|
||||
const u32 gamma = tex.gamma() & ~argb8_signed; // _SRGB
|
||||
const u32 unsigned_remap = (tex.unsigned_remap() == CELL_GCM_TEXTURE_UNSIGNED_REMAP_NORMAL)? 0u : (~(gamma | argb8_signed) & 0xF); // _BX2
|
||||
u32 argb8_convert = gamma;
|
||||
|
||||
// The options are mutually exclusive
|
||||
ensure((argb8_signed & gamma) == 0);
|
||||
ensure((argb8_signed & unsigned_remap) == 0);
|
||||
ensure((gamma & unsigned_remap) == 0);
|
||||
|
||||
// Helper function to apply a per-channel mask based on an input mask
|
||||
const auto apply_sign_convert_mask = [&](u32 mask, u32 bit_offset)
|
||||
{
|
||||
// TODO: Use actual remap mask to account for 0 and 1 overrides in default mapping
|
||||
// TODO: Replace this clusterfuck of texture control with matrix transformation
|
||||
const auto remap_ctrl = (tex.remap() >> 8) & 0xAA;
|
||||
if (remap_ctrl == 0xAA)
|
||||
{
|
||||
argb8_convert |= (mask & 0xFu) << bit_offset;
|
||||
return;
|
||||
}
|
||||
|
||||
if ((remap_ctrl & 0x03) == 0x02) argb8_convert |= (mask & 0x1u) << bit_offset;
|
||||
if ((remap_ctrl & 0x0C) == 0x08) argb8_convert |= (mask & 0x2u) << bit_offset;
|
||||
if ((remap_ctrl & 0x30) == 0x20) argb8_convert |= (mask & 0x4u) << bit_offset;
|
||||
if ((remap_ctrl & 0xC0) == 0x80) argb8_convert |= (mask & 0x8u) << bit_offset;
|
||||
};
|
||||
|
||||
if (argb8_signed)
|
||||
{
|
||||
// Apply integer sign extension from uint8 to sint8 and renormalize
|
||||
apply_sign_convert_mask(argb8_signed, texture_control_bits::SEXT_OFFSET);
|
||||
}
|
||||
|
||||
if (unsigned_remap)
|
||||
{
|
||||
// Apply sign expansion, compressed normal-map style (2n - 1)
|
||||
apply_sign_convert_mask(unsigned_remap, texture_control_bits::EXPAND_OFFSET);
|
||||
}
|
||||
|
||||
texture_control |= argb8_convert;
|
||||
}
|
||||
|
||||
current_fragment_program.texture_params[i].control = texture_control;
|
||||
}
|
||||
|
||||
// Update texture configuration
|
||||
|
|
@ -2275,13 +2365,20 @@ namespace rsx
|
|||
if (current_fragment_program.ctrl & CELL_GCM_SHADER_CONTROL_DEPTH_EXPORT)
|
||||
{
|
||||
//Check that the depth stage is not disabled
|
||||
if (!rsx::method_registers.depth_test_enabled())
|
||||
if (!m_ctx->register_state->depth_test_enabled())
|
||||
{
|
||||
rsx_log.trace("FS exports depth component but depth test is disabled (INVALID_OPERATION)");
|
||||
}
|
||||
}
|
||||
|
||||
m_program_cache_hint.invalidate_fragment_program(current_fragment_program);
|
||||
|
||||
if (zeta_was_cyclic && zeta_was_cyclic != m_graphics_state.test(rsx::zeta_address_is_cyclic))
|
||||
{
|
||||
// Forced "fall-out" barrier. This is a special case for Z buffers because they can be cyclic without writes.
|
||||
// That condition can cause early-Z in a later call to introduce data hazard in previous cyclic draws.
|
||||
m_graphics_state |= rsx::zeta_address_cyclic_barrier;
|
||||
}
|
||||
}
|
||||
|
||||
bool thread::invalidate_fragment_program(u32 dst_dma, u32 dst_offset, u32 size)
|
||||
|
|
|
|||
|
|
@ -257,6 +257,10 @@ namespace rsx
|
|||
void get_framebuffer_layout(rsx::framebuffer_creation_context context, framebuffer_layout &layout);
|
||||
bool get_scissor(areau& region, bool clip_viewport);
|
||||
|
||||
// Notify framebuffer layout has been committed.
|
||||
// FIXME: This should not be here
|
||||
void on_framebuffer_layout_updated();
|
||||
|
||||
RSXVertexProgram current_vertex_program = {};
|
||||
RSXFragmentProgram current_fragment_program = {};
|
||||
|
||||
|
|
|
|||
|
|
@ -65,38 +65,51 @@ namespace vk
|
|||
case VK_IMAGE_LAYOUT_GENERAL:
|
||||
case VK_IMAGE_LAYOUT_ATTACHMENT_FEEDBACK_LOOP_OPTIMAL_EXT:
|
||||
ensure(sampler_state->upload_context == rsx::texture_upload_context::framebuffer_storage);
|
||||
if (!sampler_state->is_cyclic_reference)
|
||||
if (sampler_state->is_cyclic_reference) [[ unlikely ]]
|
||||
{
|
||||
// This was used in a cyclic ref before, but is missing a barrier
|
||||
// No need for a full stall, use a custom barrier instead
|
||||
VkPipelineStageFlags src_stage;
|
||||
VkAccessFlags src_access;
|
||||
if (raw->aspect() == VK_IMAGE_ASPECT_COLOR_BIT)
|
||||
{
|
||||
src_stage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
|
||||
src_access = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
|
||||
}
|
||||
else
|
||||
{
|
||||
src_stage = VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
|
||||
src_access = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
|
||||
}
|
||||
|
||||
vk::insert_image_memory_barrier(
|
||||
cmd,
|
||||
raw->value,
|
||||
raw->current_layout, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
|
||||
src_stage, dst_stage,
|
||||
src_access, VK_ACCESS_SHADER_READ_BIT,
|
||||
{ raw->aspect(), 0, 1, 0, 1 });
|
||||
|
||||
raw->current_layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
||||
// Nothing to do
|
||||
break;
|
||||
}
|
||||
|
||||
// This was used in a cyclic ref before, but is missing a barrier
|
||||
// No need for a full stall, use a custom barrier instead
|
||||
VkPipelineStageFlags src_stage;
|
||||
VkAccessFlags src_access;
|
||||
if (raw->aspect() == VK_IMAGE_ASPECT_COLOR_BIT)
|
||||
{
|
||||
src_stage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
|
||||
src_access = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
|
||||
}
|
||||
else
|
||||
{
|
||||
src_stage = VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
|
||||
src_access = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
|
||||
}
|
||||
|
||||
vk::insert_image_memory_barrier(
|
||||
cmd,
|
||||
raw->value,
|
||||
raw->current_layout, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
|
||||
src_stage, dst_stage,
|
||||
src_access, VK_ACCESS_SHADER_READ_BIT,
|
||||
{ raw->aspect(), 0, 1, 0, 1 });
|
||||
|
||||
raw->current_layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
||||
break;
|
||||
case VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL:
|
||||
case VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL:
|
||||
ensure(sampler_state->upload_context == rsx::texture_upload_context::framebuffer_storage);
|
||||
raw->change_layout(cmd, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
|
||||
if (!sampler_state->is_cyclic_reference) [[ likely ]]
|
||||
{
|
||||
// Standard pre-read barrier.
|
||||
raw->change_layout(cmd, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
|
||||
break;
|
||||
}
|
||||
|
||||
// Normally this shouldn't happen. But that is only guaranteed if the attachment state never changes outside of RTT rebind interrupts.
|
||||
// If the shader changes between binds to "disable" the cyclic nature, we could end up here.
|
||||
// Draw 1 (cyllic) -> texture_barrier -> Draw 2 (no textures) -> attachment_optimal -> Draw 3 (cylic again, no new data) -> incorrect layout.
|
||||
vk::as_rtt(raw)->texture_barrier(cmd);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -1044,6 +1057,15 @@ void VKGSRender::end()
|
|||
if (auto ds = std::get<1>(m_rtts.m_bound_depth_stencil))
|
||||
{
|
||||
ds->write_barrier(*m_current_command_buffer);
|
||||
|
||||
if (m_graphics_state.test(rsx::zeta_address_cyclic_barrier) &&
|
||||
ds->current_layout != VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL)
|
||||
{
|
||||
// We actually need to end the subpass as a minimum. Without this, early-Z optimiazations in following draws will clobber reads from previous draws and cause flickering.
|
||||
// Since we're ending the subpass, might as well restore DCC/HiZ for extra performance
|
||||
ds->change_layout(*m_current_command_buffer, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL);
|
||||
ds->reset_surface_counters();
|
||||
}
|
||||
}
|
||||
|
||||
for (auto &rtt : m_rtts.m_bound_render_targets)
|
||||
|
|
@ -1054,6 +1076,8 @@ void VKGSRender::end()
|
|||
}
|
||||
}
|
||||
|
||||
m_graphics_state.clear(rsx::zeta_address_cyclic_barrier);
|
||||
|
||||
m_frame_stats.setup_time += m_profiler.duration();
|
||||
|
||||
// Now bind the shader resources. It is important that this takes place after the barriers so that we don't end up with stale descriptors
|
||||
|
|
|
|||
|
|
@ -144,23 +144,33 @@ void VKFragmentDecompilerThread::insertOutputs(std::stringstream & OS)
|
|||
{
|
||||
const std::pair<std::string, std::string> table[] =
|
||||
{
|
||||
{ "ocol0", m_ctrl & CELL_GCM_SHADER_CONTROL_32_BITS_EXPORTS ? "r0" : "h0" },
|
||||
{ "ocol1", m_ctrl & CELL_GCM_SHADER_CONTROL_32_BITS_EXPORTS ? "r2" : "h4" },
|
||||
{ "ocol2", m_ctrl & CELL_GCM_SHADER_CONTROL_32_BITS_EXPORTS ? "r3" : "h6" },
|
||||
{ "ocol3", m_ctrl & CELL_GCM_SHADER_CONTROL_32_BITS_EXPORTS ? "r4" : "h8" },
|
||||
{ "ocol0", m_prog.ctrl & CELL_GCM_SHADER_CONTROL_32_BITS_EXPORTS ? "r0" : "h0" },
|
||||
{ "ocol1", m_prog.ctrl & CELL_GCM_SHADER_CONTROL_32_BITS_EXPORTS ? "r2" : "h4" },
|
||||
{ "ocol2", m_prog.ctrl & CELL_GCM_SHADER_CONTROL_32_BITS_EXPORTS ? "r3" : "h6" },
|
||||
{ "ocol3", m_prog.ctrl & CELL_GCM_SHADER_CONTROL_32_BITS_EXPORTS ? "r4" : "h8" },
|
||||
};
|
||||
|
||||
//NOTE: We do not skip outputs, the only possible combinations are a(0), b(0), ab(0,1), abc(0,1,2), abcd(0,1,2,3)
|
||||
// NOTE: We do not skip outputs, the only possible combinations are a(0), b(0), ab(0,1), abc(0,1,2), abcd(0,1,2,3)
|
||||
u8 output_index = 0;
|
||||
const bool float_type = (m_ctrl & CELL_GCM_SHADER_CONTROL_32_BITS_EXPORTS) || !device_props.has_native_half_support;
|
||||
const bool float_type = (m_prog.ctrl & CELL_GCM_SHADER_CONTROL_32_BITS_EXPORTS) || !device_props.has_native_half_support;
|
||||
const auto reg_type = float_type ? "vec4" : getHalfTypeName(4);
|
||||
for (uint i = 0; i < std::size(table); ++i)
|
||||
{
|
||||
if (m_parr.HasParam(PF_PARAM_NONE, reg_type, table[i].second))
|
||||
if (!m_parr.HasParam(PF_PARAM_NONE, reg_type, table[i].second))
|
||||
{
|
||||
OS << "layout(location=" << std::to_string(output_index++) << ") " << "out vec4 " << table[i].first << ";\n";
|
||||
vk_prog->output_color_masks[i] = -1;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (i >= m_prog.mrt_buffers_count)
|
||||
{
|
||||
// Dead writes. Declare as temp variables for DCE to clean up.
|
||||
OS << "vec4 " << table[i].first << "; // Unused\n";
|
||||
vk_prog->output_color_masks[i] = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
OS << "layout(location=" << std::to_string(output_index++) << ") " << "out vec4 " << table[i].first << ";\n";
|
||||
vk_prog->output_color_masks[i] = -1;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -308,16 +318,24 @@ void VKFragmentDecompilerThread::insertGlobalFunctions(std::stringstream &OS)
|
|||
m_shader_props.require_srgb_to_linear = properties.has_upg;
|
||||
m_shader_props.require_linear_to_srgb = properties.has_pkg;
|
||||
m_shader_props.require_fog_read = properties.in_register_mask & in_fogc;
|
||||
m_shader_props.emulate_coverage_tests = g_cfg.video.antialiasing_level == msaa_level::none;
|
||||
m_shader_props.emulate_shadow_compare = device_props.emulate_depth_compare;
|
||||
|
||||
m_shader_props.low_precision_tests = device_props.has_low_precision_rounding && !(m_prog.ctrl & RSX_SHADER_CONTROL_ATTRIBUTE_INTERPOLATION);
|
||||
m_shader_props.disable_early_discard = !vk::is_NVIDIA(vk::get_driver_vendor());
|
||||
m_shader_props.supports_native_fp16 = device_props.has_native_half_support;
|
||||
m_shader_props.ROP_output_rounding = g_cfg.video.shader_precision != gpu_preset_level::low;
|
||||
|
||||
m_shader_props.ROP_output_rounding = (g_cfg.video.shader_precision != gpu_preset_level::low) && !!(m_prog.ctrl & RSX_SHADER_CONTROL_8BIT_FRAMEBUFFER);
|
||||
m_shader_props.ROP_sRGB_packing = !!(m_prog.ctrl & RSX_SHADER_CONTROL_SRGB_FRAMEBUFFER);
|
||||
m_shader_props.ROP_alpha_test = !!(m_prog.ctrl & RSX_SHADER_CONTROL_ALPHA_TEST);
|
||||
m_shader_props.ROP_alpha_to_coverage_test = !!(m_prog.ctrl & RSX_SHADER_CONTROL_ALPHA_TO_COVERAGE);
|
||||
m_shader_props.ROP_polygon_stipple_test = !!(m_prog.ctrl & RSX_SHADER_CONTROL_POLYGON_STIPPLE);
|
||||
m_shader_props.ROP_discard = !!(m_prog.ctrl & RSX_SHADER_CONTROL_USES_KIL);
|
||||
|
||||
m_shader_props.require_tex1D_ops = properties.has_tex1D;
|
||||
m_shader_props.require_tex2D_ops = properties.has_tex2D;
|
||||
m_shader_props.require_tex3D_ops = properties.has_tex3D;
|
||||
m_shader_props.require_shadowProj_ops = properties.shadow_sampler_mask != 0 && properties.has_texShadowProj;
|
||||
m_shader_props.require_alpha_kill = !!(m_prog.ctrl & RSX_SHADER_CONTROL_TEXTURE_ALPHA_KILL);
|
||||
|
||||
// Declare global constants
|
||||
if (m_shader_props.require_fog_read)
|
||||
|
|
@ -336,7 +354,8 @@ void VKFragmentDecompilerThread::insertGlobalFunctions(std::stringstream &OS)
|
|||
}
|
||||
|
||||
OS <<
|
||||
"#define texture_base_index _fs_texture_base_index\n\n";
|
||||
"#define texture_base_index _fs_texture_base_index\n"
|
||||
"#define TEX_PARAM(index) texture_parameters_##index\n\n";
|
||||
|
||||
glsl::insert_glsl_legacy_function(OS, m_shader_props);
|
||||
}
|
||||
|
|
@ -344,7 +363,7 @@ void VKFragmentDecompilerThread::insertGlobalFunctions(std::stringstream &OS)
|
|||
void VKFragmentDecompilerThread::insertMainStart(std::stringstream & OS)
|
||||
{
|
||||
std::set<std::string> output_registers;
|
||||
if (m_ctrl & CELL_GCM_SHADER_CONTROL_32_BITS_EXPORTS)
|
||||
if (m_prog.ctrl & CELL_GCM_SHADER_CONTROL_32_BITS_EXPORTS)
|
||||
{
|
||||
output_registers = { "r0", "r2", "r3", "r4" };
|
||||
}
|
||||
|
|
@ -353,7 +372,7 @@ void VKFragmentDecompilerThread::insertMainStart(std::stringstream & OS)
|
|||
output_registers = { "h0", "h4", "h6", "h8" };
|
||||
}
|
||||
|
||||
if (m_ctrl & CELL_GCM_SHADER_CONTROL_DEPTH_EXPORT)
|
||||
if (m_prog.ctrl & CELL_GCM_SHADER_CONTROL_DEPTH_EXPORT)
|
||||
{
|
||||
output_registers.insert("r1");
|
||||
}
|
||||
|
|
@ -422,6 +441,16 @@ void VKFragmentDecompilerThread::insertMainStart(std::stringstream & OS)
|
|||
if (properties.in_register_mask & in_spec_color)
|
||||
OS << " vec4 spec_color = gl_FrontFacing ? spec_color1 : spec_color0;\n";
|
||||
}
|
||||
|
||||
for (u16 i = 0, mask = (properties.common_access_sampler_mask | properties.shadow_sampler_mask); mask != 0; ++i, mask >>= 1)
|
||||
{
|
||||
if (!(mask & 1))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
OS << " const sampler_info texture_parameters_" << i << " = texture_parameters[texture_base_index + " << i << "];\n";
|
||||
}
|
||||
}
|
||||
|
||||
void VKFragmentDecompilerThread::insertMainEnd(std::stringstream & OS)
|
||||
|
|
@ -431,18 +460,29 @@ void VKFragmentDecompilerThread::insertMainEnd(std::stringstream & OS)
|
|||
OS << "void main()\n";
|
||||
OS << "{\n";
|
||||
|
||||
// FIXME: Workaround
|
||||
OS <<
|
||||
" const uint rop_control = fs_contexts[_fs_context_offset].rop_control;\n"
|
||||
" const float alpha_ref = fs_contexts[_fs_context_offset].alpha_ref;\n\n";
|
||||
if (m_prog.ctrl & RSX_SHADER_CONTROL_ALPHA_TEST)
|
||||
{
|
||||
OS <<
|
||||
" const uint rop_control = fs_contexts[_fs_context_offset].rop_control;\n"
|
||||
" const float alpha_ref = fs_contexts[_fs_context_offset].alpha_ref;\n\n";
|
||||
}
|
||||
|
||||
::glsl::insert_rop_init(OS);
|
||||
|
||||
OS << "\n" << " fs_main();\n\n";
|
||||
|
||||
if (m_prog.ctrl & RSX_SHADER_CONTROL_DISABLE_EARLY_Z)
|
||||
{
|
||||
// This is effectively unreachable code, but good enough to trick the GPU to skip early Z
|
||||
// For vulkan, depth export has stronger semantics than discard.
|
||||
OS <<
|
||||
" // Insert pseudo-barrier sequence to disable early-Z\n"
|
||||
" gl_FragDepth = gl_FragCoord.z;\n\n";
|
||||
}
|
||||
|
||||
glsl::insert_rop(OS, m_shader_props);
|
||||
|
||||
if (m_ctrl & CELL_GCM_SHADER_CONTROL_DEPTH_EXPORT)
|
||||
if (m_prog.ctrl & CELL_GCM_SHADER_CONTROL_DEPTH_EXPORT)
|
||||
{
|
||||
if (m_parr.HasParam(PF_PARAM_NONE, "vec4", "r1"))
|
||||
{
|
||||
|
|
|
|||
|
|
@ -2469,7 +2469,7 @@ void VKGSRender::prepare_rtts(rsx::framebuffer_creation_context context)
|
|||
m_surface_info[i].samples = samples;
|
||||
}
|
||||
|
||||
//Process depth surface as well
|
||||
// Process depth surface as well
|
||||
{
|
||||
if (m_depth_surface_info.pitch && g_cfg.video.write_depth_buffer)
|
||||
{
|
||||
|
|
@ -2486,7 +2486,7 @@ void VKGSRender::prepare_rtts(rsx::framebuffer_creation_context context)
|
|||
m_depth_surface_info.samples = samples;
|
||||
}
|
||||
|
||||
//Bind created rtts as current fbo...
|
||||
// Bind created rtts as current fbo...
|
||||
const auto draw_buffers = rsx::utility::get_rtt_indexes(m_framebuffer_layout.target);
|
||||
m_draw_buffers.clear();
|
||||
m_fbo_images.clear();
|
||||
|
|
@ -2637,6 +2637,7 @@ void VKGSRender::prepare_rtts(rsx::framebuffer_creation_context context)
|
|||
|
||||
set_viewport();
|
||||
set_scissor(clipped_scissor);
|
||||
on_framebuffer_layout_updated();
|
||||
|
||||
check_zcull_status(true);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -264,6 +264,7 @@ namespace vk
|
|||
}
|
||||
|
||||
builder << "\n"
|
||||
"#define TEX_PARAM(index) texture_parameters[index + texture_base_index]\n"
|
||||
"#define IS_TEXTURE_RESIDENT(index) true\n"
|
||||
"#define SAMPLER1D(index) sampler1D_array[index]\n"
|
||||
"#define SAMPLER2D(index) sampler2D_array[index]\n"
|
||||
|
|
|
|||
|
|
@ -102,7 +102,7 @@ namespace vk
|
|||
multidraw_support.max_batch_size = 65536;
|
||||
|
||||
optional_features_support.barycentric_coords = !!shader_barycentric_info.fragmentShaderBarycentric;
|
||||
optional_features_support.framebuffer_loops = !!fbo_loops_info.attachmentFeedbackLoopLayout;
|
||||
optional_features_support.framebuffer_loops = !!fbo_loops_info.attachmentFeedbackLoopLayout && get_driver_vendor() != driver_vendor::AMD;
|
||||
optional_features_support.extended_device_fault = !!device_fault_info.deviceFault;
|
||||
|
||||
features = features2.features;
|
||||
|
|
|
|||
|
|
@ -454,9 +454,22 @@ namespace gcm
|
|||
RSX_SHADER_CONTROL_UNKNOWN1 = 0x8000, // seemingly set when srgb packer is used??
|
||||
|
||||
// Custom
|
||||
RSX_SHADER_CONTROL_ATTRIBUTE_INTERPOLATION = 0x10000, // Rasterizing triangles and not lines or points
|
||||
RSX_SHADER_CONTROL_INSTANCED_CONSTANTS = 0x20000, // Support instance ID offsets when loading constants
|
||||
RSX_SHADER_CONTROL_INTERPRETER_MODEL = 0x40000, // Compile internals expecting interpreter
|
||||
RSX_SHADER_CONTROL_ATTRIBUTE_INTERPOLATION = 0x0010000, // Rasterizing triangles and not lines or points
|
||||
RSX_SHADER_CONTROL_INSTANCED_CONSTANTS = 0x0020000, // Support instance ID offsets when loading constants
|
||||
RSX_SHADER_CONTROL_INTERPRETER_MODEL = 0x0040000, // Compile internals expecting interpreter
|
||||
|
||||
RSX_SHADER_CONTROL_8BIT_FRAMEBUFFER = 0x0080000, // Quantize outputs to 8-bit FBO
|
||||
RSX_SHADER_CONTROL_SRGB_FRAMEBUFFER = 0x0100000, // Outputs are SRGB. We could reuse UNKNOWN1 but we just keep the namespaces separate.
|
||||
|
||||
RSX_SHADER_CONTROL_TEXTURE_ALPHA_KILL = 0x0200000, // Uses alpha kill on texture input
|
||||
RSX_SHADER_CONTROL_ALPHA_TEST = 0x0400000, // Uses alpha test on the outputs
|
||||
RSX_SHADER_CONTROL_POLYGON_STIPPLE = 0x0800000, // Uses polygon stipple for dithered rendering
|
||||
RSX_SHADER_CONTROL_ALPHA_TO_COVERAGE = 0x1000000, // Alpha to coverage
|
||||
|
||||
RSX_SHADER_CONTROL_DISABLE_EARLY_Z = 0x2000000, // Do not allow early-Z optimizations on this shader
|
||||
|
||||
// Meta
|
||||
RSX_SHADER_CONTROL_META_USES_DISCARD = (RSX_SHADER_CONTROL_USES_KIL | RSX_SHADER_CONTROL_TEXTURE_ALPHA_KILL | RSX_SHADER_CONTROL_ALPHA_TEST | RSX_SHADER_CONTROL_POLYGON_STIPPLE | RSX_SHADER_CONTROL_ALPHA_TO_COVERAGE)
|
||||
};
|
||||
|
||||
// GCM Reports
|
||||
|
|
|
|||
|
|
@ -716,9 +716,8 @@ namespace rsx
|
|||
state_signals[NV4097_SET_POINT_SIZE] = rsx::vertex_state_dirty;
|
||||
state_signals[NV4097_SET_ALPHA_FUNC] = rsx::fragment_state_dirty;
|
||||
state_signals[NV4097_SET_ALPHA_REF] = rsx::fragment_state_dirty;
|
||||
state_signals[NV4097_SET_ALPHA_TEST_ENABLE] = rsx::fragment_state_dirty;
|
||||
state_signals[NV4097_SET_ANTI_ALIASING_CONTROL] = rsx::fragment_state_dirty | rsx::pipeline_config_dirty;
|
||||
state_signals[NV4097_SET_SHADER_PACKER] = rsx::fragment_state_dirty;
|
||||
state_signals[NV4097_SET_ALPHA_TEST_ENABLE] = rsx::fragment_program_state_dirty;
|
||||
state_signals[NV4097_SET_SHADER_PACKER] = rsx::fragment_program_state_dirty;
|
||||
state_signals[NV4097_SET_SHADER_WINDOW] = rsx::fragment_state_dirty;
|
||||
state_signals[NV4097_SET_FOG_MODE] = rsx::fragment_state_dirty;
|
||||
state_signals[NV4097_SET_SCISSOR_HORIZONTAL] = rsx::scissor_config_state_dirty;
|
||||
|
|
@ -733,7 +732,7 @@ namespace rsx
|
|||
state_signals[NV4097_SET_VIEWPORT_OFFSET + 0] = rsx::vertex_state_dirty;
|
||||
state_signals[NV4097_SET_VIEWPORT_OFFSET + 1] = rsx::vertex_state_dirty;
|
||||
state_signals[NV4097_SET_VIEWPORT_OFFSET + 2] = rsx::vertex_state_dirty;
|
||||
state_signals[NV4097_SET_POLYGON_STIPPLE] = rsx::fragment_state_dirty;
|
||||
state_signals[NV4097_SET_POLYGON_STIPPLE] = rsx::fragment_program_state_dirty;
|
||||
state_signals[NV4097_SET_POLYGON_STIPPLE_PATTERN + 0] = rsx::polygon_stipple_pattern_dirty;
|
||||
state_signals[NV4097_SET_POLYGON_STIPPLE_PATTERN + 1] = rsx::polygon_stipple_pattern_dirty;
|
||||
state_signals[NV4097_SET_POLYGON_STIPPLE_PATTERN + 2] = rsx::polygon_stipple_pattern_dirty;
|
||||
|
|
@ -1714,6 +1713,7 @@ namespace rsx
|
|||
bind(NV4097_SET_BLEND_EQUATION, nv4097::set_blend_equation);
|
||||
bind(NV4097_SET_BLEND_FUNC_SFACTOR, nv4097::set_blend_factor);
|
||||
bind(NV4097_SET_BLEND_FUNC_DFACTOR, nv4097::set_blend_factor);
|
||||
bind(NV4097_SET_ANTI_ALIASING_CONTROL, nv4097::set_aa_control);
|
||||
|
||||
//NV308A (0xa400..0xbffc!)
|
||||
bind_array(NV308A_COLOR, 1, 256 * 7, nv308a::color::impl);
|
||||
|
|
|
|||
|
|
@ -101,6 +101,7 @@ struct EmuCallbacks
|
|||
std::function<std::string(localized_string_id, const char*)> get_localized_string;
|
||||
std::function<std::u32string(localized_string_id, const char*)> get_localized_u32string;
|
||||
std::function<std::string(const cfg::_base*, u32)> get_localized_setting;
|
||||
std::function<std::string(std::string_view)> get_photo_path;
|
||||
std::function<void(const std::string&, std::optional<f32>)> play_sound;
|
||||
std::function<bool(const std::string&, std::string&, s32&, s32&, s32&)> get_image_info; // (filename, sub_type, width, height, CellSearchOrientation)
|
||||
std::function<bool(const std::string&, s32, s32, s32&, s32&, u8*, bool)> get_scaled_image; // (filename, target_width, target_height, width, height, dst, force_fit)
|
||||
|
|
|
|||
|
|
@ -267,6 +267,7 @@ struct cfg_root : cfg::node
|
|||
cfg::_enum<fake_camera_type> camera_type{ this, "Camera type", fake_camera_type::unknown };
|
||||
cfg::_enum<camera_flip> camera_flip_option{ this, "Camera flip", camera_flip::none, true };
|
||||
cfg::string camera_id{ this, "Camera ID", "Default", true };
|
||||
cfg::string sdl_camera_id{ this, "SDL Camera ID", "Default", true };
|
||||
cfg::_enum<move_handler> move{ this, "Move", move_handler::null, true };
|
||||
cfg::_enum<buzz_handler> buzz{ this, "Buzz emulated controller", buzz_handler::null };
|
||||
cfg::_enum<turntable_handler> turntable{this, "Turntable emulated controller", turntable_handler::null};
|
||||
|
|
|
|||
|
|
@ -358,6 +358,9 @@ void fmt_class_string<camera_handler>::format(std::string& out, u64 arg)
|
|||
case camera_handler::null: return "Null";
|
||||
case camera_handler::fake: return "Fake";
|
||||
case camera_handler::qt: return "Qt";
|
||||
#ifdef HAVE_SDL3
|
||||
case camera_handler::sdl: return "SDL";
|
||||
#endif
|
||||
}
|
||||
|
||||
return unknown;
|
||||
|
|
|
|||
|
|
@ -116,7 +116,10 @@ enum class camera_handler
|
|||
{
|
||||
null,
|
||||
fake,
|
||||
qt
|
||||
qt,
|
||||
#ifdef HAVE_SDL3
|
||||
sdl,
|
||||
#endif
|
||||
};
|
||||
|
||||
enum class camera_flip
|
||||
|
|
|
|||
BIN
rpcs3/Icons/rpcn.png
Normal file
BIN
rpcs3/Icons/rpcn.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
245
rpcs3/Input/camera_video_sink.cpp
Normal file
245
rpcs3/Input/camera_video_sink.cpp
Normal file
|
|
@ -0,0 +1,245 @@
|
|||
#include "stdafx.h"
|
||||
#include "camera_video_sink.h"
|
||||
|
||||
#include "Emu/Cell/Modules/cellCamera.h"
|
||||
#include "Emu/system_config.h"
|
||||
|
||||
LOG_CHANNEL(camera_log, "Camera");
|
||||
|
||||
camera_video_sink::camera_video_sink(bool front_facing)
|
||||
: m_front_facing(front_facing)
|
||||
{
|
||||
}
|
||||
|
||||
camera_video_sink::~camera_video_sink()
|
||||
{
|
||||
}
|
||||
|
||||
bool camera_video_sink::present(u32 src_width, u32 src_height, u32 src_pitch, u32 src_bytes_per_pixel, std::function<const u8*(u32)> src_line_ptr)
|
||||
{
|
||||
ensure(!!src_line_ptr);
|
||||
|
||||
const u64 new_size = m_bytesize;
|
||||
image_buffer& image_buffer = m_image_buffer[m_write_index];
|
||||
|
||||
// Reset buffer if necessary
|
||||
if (image_buffer.data.size() != new_size)
|
||||
{
|
||||
image_buffer.data.clear();
|
||||
}
|
||||
|
||||
// Create buffer if necessary
|
||||
if (image_buffer.data.empty() && new_size > 0)
|
||||
{
|
||||
image_buffer.data.resize(new_size);
|
||||
image_buffer.width = m_width;
|
||||
image_buffer.height = m_height;
|
||||
}
|
||||
|
||||
if (!image_buffer.data.empty() && src_width && src_height)
|
||||
{
|
||||
// Convert image to proper layout
|
||||
// TODO: check if pixel format and bytes per pixel match and convert if necessary
|
||||
// TODO: implement or improve more conversions
|
||||
|
||||
const u32 width = std::min<u32>(image_buffer.width, src_width);
|
||||
const u32 height = std::min<u32>(image_buffer.height, src_height);
|
||||
|
||||
switch (m_format)
|
||||
{
|
||||
case CELL_CAMERA_RAW8: // The game seems to expect BGGR
|
||||
{
|
||||
// Let's use a very simple algorithm to convert the image to raw BGGR
|
||||
u8* dst = image_buffer.data.data();
|
||||
|
||||
for (u32 y = 0; y < height; y++)
|
||||
{
|
||||
const u8* src = src_line_ptr(y);
|
||||
const u8* srcu = src_line_ptr(std::max<s32>(0, y - 1));
|
||||
const u8* srcd = src_line_ptr(std::min(height - 1, y + 1));
|
||||
const bool is_top_pixel = (y % 2) == 0;
|
||||
|
||||
// We apply gaussian blur to get better demosaicing results later when debayering again
|
||||
const auto blurred = [&](s32 x, s32 c)
|
||||
{
|
||||
const s32 i = x * 4 + c;
|
||||
const s32 il = std::max(0, x - 1) * 4 + c;
|
||||
const s32 ir = std::min<s32>(width - 1, x + 1) * 4 + c;
|
||||
const s32 sum =
|
||||
srcu[i] +
|
||||
src[il] + 4 * src[i] + src[ir] +
|
||||
srcd[i];
|
||||
return static_cast<u8>(std::clamp((sum + 4) / 8, 0, 255));
|
||||
};
|
||||
|
||||
// Split loops (roughly twice the performance by removing one condition)
|
||||
if (is_top_pixel)
|
||||
{
|
||||
for (u32 x = 0; x < width; x++, dst++)
|
||||
{
|
||||
const bool is_left_pixel = (x % 2) == 0;
|
||||
|
||||
if (is_left_pixel)
|
||||
{
|
||||
*dst = blurred(x, 2); // Blue
|
||||
}
|
||||
else
|
||||
{
|
||||
*dst = blurred(x, 1); // Green
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (u32 x = 0; x < width; x++, dst++)
|
||||
{
|
||||
const bool is_left_pixel = (x % 2) == 0;
|
||||
|
||||
if (is_left_pixel)
|
||||
{
|
||||
*dst = blurred(x, 1); // Green
|
||||
}
|
||||
else
|
||||
{
|
||||
*dst = blurred(x, 0); // Red
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
//case CELL_CAMERA_YUV422:
|
||||
case CELL_CAMERA_Y0_U_Y1_V:
|
||||
case CELL_CAMERA_V_Y1_U_Y0:
|
||||
{
|
||||
// Simple RGB to Y0_U_Y1_V conversion from stackoverflow.
|
||||
constexpr s32 yuv_bytes_per_pixel = 2;
|
||||
const s32 yuv_pitch = image_buffer.width * yuv_bytes_per_pixel;
|
||||
|
||||
const s32 y0_offset = (m_format == CELL_CAMERA_Y0_U_Y1_V) ? 0 : 3;
|
||||
const s32 u_offset = (m_format == CELL_CAMERA_Y0_U_Y1_V) ? 1 : 2;
|
||||
const s32 y1_offset = (m_format == CELL_CAMERA_Y0_U_Y1_V) ? 2 : 1;
|
||||
const s32 v_offset = (m_format == CELL_CAMERA_Y0_U_Y1_V) ? 3 : 0;
|
||||
|
||||
for (u32 y = 0; y < height; y++)
|
||||
{
|
||||
const u8* src = src_line_ptr(y);
|
||||
u8* yuv_row_ptr = &image_buffer.data[y * yuv_pitch];
|
||||
|
||||
for (u32 x = 0; x < width - 1; x += 2, src += 8)
|
||||
{
|
||||
const f32 r1 = src[0];
|
||||
const f32 g1 = src[1];
|
||||
const f32 b1 = src[2];
|
||||
const f32 r2 = src[4];
|
||||
const f32 g2 = src[5];
|
||||
const f32 b2 = src[6];
|
||||
|
||||
const f32 y0 = (0.257f * r1) + (0.504f * g1) + (0.098f * b1) + 16.0f;
|
||||
const f32 u = -(0.148f * r1) - (0.291f * g1) + (0.439f * b1) + 128.0f;
|
||||
const f32 v = (0.439f * r1) - (0.368f * g1) - (0.071f * b1) + 128.0f;
|
||||
const f32 y1 = (0.257f * r2) + (0.504f * g2) + (0.098f * b2) + 16.0f;
|
||||
|
||||
const s32 yuv_index = x * yuv_bytes_per_pixel;
|
||||
yuv_row_ptr[yuv_index + y0_offset] = static_cast<u8>(std::clamp(y0, 0.0f, 255.0f));
|
||||
yuv_row_ptr[yuv_index + u_offset] = static_cast<u8>(std::clamp( u, 0.0f, 255.0f));
|
||||
yuv_row_ptr[yuv_index + y1_offset] = static_cast<u8>(std::clamp(y1, 0.0f, 255.0f));
|
||||
yuv_row_ptr[yuv_index + v_offset] = static_cast<u8>(std::clamp( v, 0.0f, 255.0f));
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CELL_CAMERA_JPG:
|
||||
case CELL_CAMERA_RGBA:
|
||||
case CELL_CAMERA_RAW10:
|
||||
case CELL_CAMERA_YUV420:
|
||||
case CELL_CAMERA_FORMAT_UNKNOWN:
|
||||
default:
|
||||
const u32 bytes_per_line = src_bytes_per_pixel * src_width;
|
||||
if (src_pitch == bytes_per_line)
|
||||
{
|
||||
std::memcpy(image_buffer.data.data(), src_line_ptr(0), std::min<usz>(image_buffer.data.size(), src_height * bytes_per_line));
|
||||
}
|
||||
else
|
||||
{
|
||||
for (u32 y = 0, pos = 0; y < src_height && pos < image_buffer.data.size(); y++, pos += bytes_per_line)
|
||||
{
|
||||
std::memcpy(&image_buffer.data[pos], src_line_ptr(y), std::min<usz>(image_buffer.data.size() - pos, bytes_per_line));
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
camera_log.trace("Wrote image to video surface. index=%d, m_frame_number=%d, width=%d, height=%d, bytesize=%d",
|
||||
m_write_index, m_frame_number.load(), m_width, m_height, m_bytesize);
|
||||
|
||||
// Toggle write/read index
|
||||
std::lock_guard lock(m_mutex);
|
||||
image_buffer.frame_number = m_frame_number++;
|
||||
m_write_index = read_index();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void camera_video_sink::set_format(s32 format, u32 bytesize)
|
||||
{
|
||||
camera_log.notice("Setting format: format=%d, bytesize=%d", format, bytesize);
|
||||
|
||||
m_format = format;
|
||||
m_bytesize = bytesize;
|
||||
}
|
||||
|
||||
void camera_video_sink::set_resolution(u32 width, u32 height)
|
||||
{
|
||||
camera_log.notice("Setting resolution: width=%d, height=%d", width, height);
|
||||
|
||||
m_width = width;
|
||||
m_height = height;
|
||||
}
|
||||
|
||||
void camera_video_sink::set_mirrored(bool mirrored)
|
||||
{
|
||||
camera_log.notice("Setting mirrored: mirrored=%d", mirrored);
|
||||
|
||||
m_mirrored = mirrored;
|
||||
}
|
||||
|
||||
u64 camera_video_sink::frame_number() const
|
||||
{
|
||||
return m_frame_number.load();
|
||||
}
|
||||
|
||||
void camera_video_sink::get_image(u8* buf, u64 size, u32& width, u32& height, u64& frame_number, u64& bytes_read)
|
||||
{
|
||||
// Lock read buffer
|
||||
std::lock_guard lock(m_mutex);
|
||||
const image_buffer& image_buffer = m_image_buffer[read_index()];
|
||||
|
||||
width = image_buffer.width;
|
||||
height = image_buffer.height;
|
||||
frame_number = image_buffer.frame_number;
|
||||
|
||||
// Copy to out buffer
|
||||
if (buf && !image_buffer.data.empty())
|
||||
{
|
||||
bytes_read = std::min<u64>(image_buffer.data.size(), size);
|
||||
std::memcpy(buf, image_buffer.data.data(), bytes_read);
|
||||
|
||||
if (image_buffer.data.size() != size)
|
||||
{
|
||||
camera_log.error("Buffer size mismatch: in=%d, out=%d. Cropping to incoming size. Please contact a developer.", size, image_buffer.data.size());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
bytes_read = 0;
|
||||
}
|
||||
}
|
||||
|
||||
u32 camera_video_sink::read_index() const
|
||||
{
|
||||
// The read buffer index cannot be the same as the write index
|
||||
return (m_write_index + 1u) % ::narrow<u32>(m_image_buffer.size());
|
||||
}
|
||||
|
||||
43
rpcs3/Input/camera_video_sink.h
Normal file
43
rpcs3/Input/camera_video_sink.h
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
#pragma once
|
||||
|
||||
#include <mutex>
|
||||
|
||||
class camera_video_sink
|
||||
{
|
||||
public:
|
||||
camera_video_sink(bool front_facing);
|
||||
virtual ~camera_video_sink();
|
||||
|
||||
void set_format(s32 format, u32 bytesize);
|
||||
void set_resolution(u32 width, u32 height);
|
||||
void set_mirrored(bool mirrored);
|
||||
|
||||
u64 frame_number() const;
|
||||
|
||||
bool present(u32 src_width, u32 src_height, u32 src_pitch, u32 src_bytes_per_pixel, std::function<const u8*(u32)> src_line_ptr);
|
||||
|
||||
void get_image(u8* buf, u64 size, u32& width, u32& height, u64& frame_number, u64& bytes_read);
|
||||
|
||||
protected:
|
||||
u32 read_index() const;
|
||||
|
||||
bool m_front_facing = false;
|
||||
bool m_mirrored = false; // Set by cellCamera
|
||||
s32 m_format = 2; // CELL_CAMERA_RAW8, set by cellCamera
|
||||
u32 m_bytesize = 0;
|
||||
u32 m_width = 640;
|
||||
u32 m_height = 480;
|
||||
|
||||
std::mutex m_mutex;
|
||||
atomic_t<u64> m_frame_number{0};
|
||||
u32 m_write_index{0};
|
||||
|
||||
struct image_buffer
|
||||
{
|
||||
u64 frame_number = 0;
|
||||
u32 width = 0;
|
||||
u32 height = 0;
|
||||
std::vector<u8> data;
|
||||
};
|
||||
std::array<image_buffer, 2> m_image_buffer;
|
||||
};
|
||||
490
rpcs3/Input/sdl_camera_handler.cpp
Normal file
490
rpcs3/Input/sdl_camera_handler.cpp
Normal file
|
|
@ -0,0 +1,490 @@
|
|||
#ifdef HAVE_SDL3
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "sdl_camera_handler.h"
|
||||
#include "sdl_camera_video_sink.h"
|
||||
#include "sdl_instance.h"
|
||||
#include "Emu/system_config.h"
|
||||
#include "Emu/System.h"
|
||||
#include "Emu/Io/camera_config.h"
|
||||
|
||||
LOG_CHANNEL(camera_log, "Camera");
|
||||
|
||||
#if !(SDL_VERSION_ATLEAST(3, 4, 0))
|
||||
namespace SDL_CameraPermissionState
|
||||
{
|
||||
constexpr int SDL_CAMERA_PERMISSION_STATE_DENIED = -1;
|
||||
constexpr int SDL_CAMERA_PERMISSION_STATE_PENDING = 0;
|
||||
constexpr int SDL_CAMERA_PERMISSION_STATE_APPROVED = 1;
|
||||
}
|
||||
#endif
|
||||
|
||||
template <>
|
||||
void fmt_class_string<SDL_CameraSpec>::format(std::string& out, u64 arg)
|
||||
{
|
||||
const SDL_CameraSpec& spec = get_object(arg);
|
||||
out += fmt::format("format=0x%x, colorspace=0x%x, width=%d, height=%d, framerate_numerator=%d, framerate_denominator=%d, fps=%f",
|
||||
static_cast<u32>(spec.format), static_cast<u32>(spec.colorspace), spec.width, spec.height,
|
||||
spec.framerate_numerator, spec.framerate_denominator, spec.framerate_numerator / static_cast<f32>(spec.framerate_denominator));
|
||||
}
|
||||
|
||||
std::vector<std::string> sdl_camera_handler::get_drivers()
|
||||
{
|
||||
std::vector<std::string> drivers;
|
||||
|
||||
if (const int num_drivers = SDL_GetNumCameraDrivers(); num_drivers > 0)
|
||||
{
|
||||
for (int i = 0; i < num_drivers; i++)
|
||||
{
|
||||
if (const char* driver = SDL_GetCameraDriver(i))
|
||||
{
|
||||
camera_log.notice("Found driver: %s", driver);
|
||||
drivers.push_back(driver);
|
||||
continue;
|
||||
}
|
||||
|
||||
camera_log.error("Failed to get driver %d. SDL Error: %s", i, SDL_GetError());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
camera_log.error("No SDL camera drivers found");
|
||||
}
|
||||
|
||||
return drivers;
|
||||
}
|
||||
|
||||
std::map<SDL_CameraID, std::string> sdl_camera_handler::get_cameras()
|
||||
{
|
||||
int camera_count = 0;
|
||||
if (SDL_CameraID* cameras = SDL_GetCameras(&camera_count))
|
||||
{
|
||||
std::map<SDL_CameraID, std::string> camera_map;
|
||||
|
||||
for (int i = 0; i < camera_count && cameras[i]; i++)
|
||||
{
|
||||
if (const char* name = SDL_GetCameraName(cameras[i]))
|
||||
{
|
||||
camera_log.notice("Found camera: name=%s", name);
|
||||
camera_map[cameras[i]] = name;
|
||||
continue;
|
||||
}
|
||||
|
||||
camera_log.error("Found camera (Failed to get name. SDL Error: %s", SDL_GetError());
|
||||
}
|
||||
|
||||
SDL_free(cameras);
|
||||
|
||||
if (camera_map.empty())
|
||||
{
|
||||
camera_log.notice("No SDL cameras found");
|
||||
}
|
||||
|
||||
return camera_map;
|
||||
}
|
||||
|
||||
camera_log.error("Could not get cameras! SDL Error: %s", SDL_GetError());
|
||||
return {};
|
||||
}
|
||||
|
||||
sdl_camera_handler::sdl_camera_handler() : camera_handler_base()
|
||||
{
|
||||
if (!g_cfg_camera.load())
|
||||
{
|
||||
camera_log.notice("Could not load camera config. Using defaults.");
|
||||
}
|
||||
|
||||
if (!sdl_instance::get_instance().initialize())
|
||||
{
|
||||
camera_log.error("Could not initialize SDL");
|
||||
return;
|
||||
}
|
||||
|
||||
// List available camera drivers
|
||||
sdl_camera_handler::get_drivers();
|
||||
|
||||
// List available cameras
|
||||
sdl_camera_handler::get_cameras();
|
||||
}
|
||||
|
||||
sdl_camera_handler::~sdl_camera_handler()
|
||||
{
|
||||
Emu.BlockingCallFromMainThread([&]()
|
||||
{
|
||||
close_camera();
|
||||
});
|
||||
}
|
||||
|
||||
void sdl_camera_handler::reset()
|
||||
{
|
||||
m_video_sink.reset();
|
||||
|
||||
if (m_camera)
|
||||
{
|
||||
SDL_CloseCamera(m_camera);
|
||||
m_camera = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void sdl_camera_handler::open_camera()
|
||||
{
|
||||
camera_log.notice("Loading camera");
|
||||
|
||||
if (const std::string camera_id = g_cfg.io.sdl_camera_id.to_string();
|
||||
m_camera_id != camera_id)
|
||||
{
|
||||
camera_log.notice("Switching camera from %s to %s", m_camera_id, camera_id);
|
||||
camera_log.notice("Stopping old camera...");
|
||||
if (m_camera)
|
||||
{
|
||||
set_expected_state(camera_handler_state::open);
|
||||
reset();
|
||||
}
|
||||
m_camera_id = camera_id;
|
||||
}
|
||||
|
||||
// List available cameras
|
||||
int camera_count = 0;
|
||||
SDL_CameraID* cameras = SDL_GetCameras(&camera_count);
|
||||
|
||||
if (!cameras)
|
||||
{
|
||||
camera_log.error("Could not get cameras! SDL Error: %s", SDL_GetError());
|
||||
set_state(camera_handler_state::closed);
|
||||
return;
|
||||
}
|
||||
|
||||
if (camera_count <= 0)
|
||||
{
|
||||
camera_log.error("No cameras found");
|
||||
set_state(camera_handler_state::closed);
|
||||
SDL_free(cameras);
|
||||
return;
|
||||
}
|
||||
|
||||
m_sdl_camera_id = 0;
|
||||
|
||||
if (m_camera_id == g_cfg.io.sdl_camera_id.def)
|
||||
{
|
||||
m_sdl_camera_id = cameras[0];
|
||||
}
|
||||
else if (!m_camera_id.empty())
|
||||
{
|
||||
for (int i = 0; i < camera_count && cameras[i]; i++)
|
||||
{
|
||||
if (const char* name = SDL_GetCameraName(cameras[i]))
|
||||
{
|
||||
if (m_camera_id == name)
|
||||
{
|
||||
m_sdl_camera_id = cameras[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SDL_free(cameras);
|
||||
|
||||
if (!m_sdl_camera_id)
|
||||
{
|
||||
camera_log.error("Camera %s not found", m_camera_id);
|
||||
set_state(camera_handler_state::closed);
|
||||
return;
|
||||
}
|
||||
|
||||
std::string camera_id;
|
||||
|
||||
if (const char* name = SDL_GetCameraName(m_sdl_camera_id))
|
||||
{
|
||||
camera_log.notice("Using camera: name=%s", name);
|
||||
camera_id = name;
|
||||
}
|
||||
|
||||
SDL_CameraSpec used_spec
|
||||
{
|
||||
.format = SDL_PixelFormat::SDL_PIXELFORMAT_RGBA32,
|
||||
.colorspace = SDL_Colorspace::SDL_COLORSPACE_RGB_DEFAULT,
|
||||
.width = static_cast<int>(m_width),
|
||||
.height = static_cast<int>(m_height),
|
||||
.framerate_numerator = 30,
|
||||
.framerate_denominator = 1
|
||||
};
|
||||
|
||||
int num_formats = 0;
|
||||
if (SDL_CameraSpec** specs = SDL_GetCameraSupportedFormats(m_sdl_camera_id, &num_formats))
|
||||
{
|
||||
if (num_formats <= 0)
|
||||
{
|
||||
camera_log.error("No SDL camera specs found");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Load selected settings from config file
|
||||
bool success = false;
|
||||
cfg_camera::camera_setting cfg_setting = g_cfg_camera.get_camera_setting(fmt::format("%s", camera_handler::sdl), camera_id, success);
|
||||
|
||||
if (success)
|
||||
{
|
||||
camera_log.notice("Found config entry for camera \"%s\" (m_camera_id='%s')", camera_id, m_camera_id);
|
||||
|
||||
// List all available settings and choose the proper value if possible.
|
||||
constexpr double epsilon = 0.001;
|
||||
success = false;
|
||||
|
||||
for (int i = 0; i < num_formats; i++)
|
||||
{
|
||||
if (!specs[i]) continue;
|
||||
|
||||
const SDL_CameraSpec& spec = *specs[i];
|
||||
const f64 fps = spec.framerate_numerator / static_cast<f64>(spec.framerate_denominator);
|
||||
|
||||
if (spec.width == cfg_setting.width &&
|
||||
spec.height == cfg_setting.height &&
|
||||
fps >= (cfg_setting.min_fps - epsilon) &&
|
||||
fps <= (cfg_setting.min_fps + epsilon) &&
|
||||
fps >= (cfg_setting.max_fps - epsilon) &&
|
||||
fps <= (cfg_setting.max_fps + epsilon) &&
|
||||
spec.format == static_cast<SDL_PixelFormat>(cfg_setting.format) &&
|
||||
spec.colorspace == static_cast<SDL_Colorspace>(cfg_setting.colorspace))
|
||||
{
|
||||
// Apply settings.
|
||||
camera_log.notice("Setting camera spec: %s", spec);
|
||||
|
||||
// TODO: SDL converts the image for us. We would have to do this manually if we want to use other formats.
|
||||
//used_spec = spec;
|
||||
used_spec.width = spec.width;
|
||||
used_spec.height = spec.height;
|
||||
used_spec.framerate_numerator = spec.framerate_numerator;
|
||||
used_spec.framerate_denominator = spec.framerate_denominator;
|
||||
success = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!success)
|
||||
{
|
||||
camera_log.warning("No matching camera setting available for the camera config: max_fps=%f, width=%d, height=%d, format=%d, colorspace=%d",
|
||||
cfg_setting.max_fps, cfg_setting.width, cfg_setting.height, cfg_setting.format, cfg_setting.colorspace);
|
||||
}
|
||||
}
|
||||
|
||||
if (!success)
|
||||
{
|
||||
camera_log.notice("Using default camera spec: %s", used_spec);
|
||||
}
|
||||
}
|
||||
SDL_free(specs);
|
||||
}
|
||||
else
|
||||
{
|
||||
camera_log.error("No SDL camera specs found. SDL Error: %s", SDL_GetError());
|
||||
}
|
||||
|
||||
reset();
|
||||
|
||||
camera_log.notice("Requesting camera spec: %s", used_spec);
|
||||
|
||||
m_camera = SDL_OpenCamera(m_sdl_camera_id, &used_spec);
|
||||
|
||||
if (!m_camera)
|
||||
{
|
||||
if (!m_camera_id.empty()) camera_log.notice("Camera disabled");
|
||||
else camera_log.error("No camera found");
|
||||
set_state(camera_handler_state::closed);
|
||||
return;
|
||||
}
|
||||
|
||||
if (const char* driver = SDL_GetCurrentCameraDriver())
|
||||
{
|
||||
camera_log.notice("Using driver: %s", driver);
|
||||
}
|
||||
|
||||
if (SDL_CameraSpec spec {}; SDL_GetCameraFormat(m_camera, &spec))
|
||||
{
|
||||
camera_log.notice("Using camera spec: %s", spec);
|
||||
}
|
||||
else
|
||||
{
|
||||
camera_log.error("Could not get camera spec. SDL Error: %s", SDL_GetError());
|
||||
}
|
||||
|
||||
const SDL_CameraPosition position = SDL_GetCameraPosition(m_sdl_camera_id);
|
||||
const bool front_facing = position == SDL_CameraPosition::SDL_CAMERA_POSITION_FRONT_FACING;
|
||||
|
||||
if (const SDL_PropertiesID property_id = SDL_GetCameraProperties(m_camera); property_id != 0)
|
||||
{
|
||||
if (!SDL_EnumerateProperties(property_id, [](void* /*userdata*/, SDL_PropertiesID /*props*/, const char* name)
|
||||
{
|
||||
if (name) camera_log.notice("SDL camera property available: %s", name);
|
||||
}, nullptr))
|
||||
{
|
||||
camera_log.warning("SDL_EnumerateProperties failed. SDL Error: %s", SDL_GetError());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
camera_log.warning("SDL_GetCameraProperties failed. SDL Error: %s", SDL_GetError());
|
||||
}
|
||||
|
||||
m_video_sink = std::make_unique<sdl_camera_video_sink>(front_facing, m_camera);
|
||||
m_video_sink->set_resolution(m_width, m_height);
|
||||
m_video_sink->set_format(m_format, m_bytesize);
|
||||
m_video_sink->set_mirrored(m_mirrored);
|
||||
|
||||
set_state(camera_handler_state::open);
|
||||
}
|
||||
|
||||
void sdl_camera_handler::close_camera()
|
||||
{
|
||||
camera_log.notice("Unloading camera");
|
||||
|
||||
if (!m_camera)
|
||||
{
|
||||
if (m_camera_id.empty()) camera_log.notice("Camera disabled");
|
||||
else camera_log.error("No camera found");
|
||||
set_state(camera_handler_state::closed);
|
||||
return;
|
||||
}
|
||||
|
||||
// Unload/close camera
|
||||
reset();
|
||||
|
||||
set_state(camera_handler_state::closed);
|
||||
}
|
||||
|
||||
void sdl_camera_handler::start_camera()
|
||||
{
|
||||
camera_log.notice("Starting camera");
|
||||
|
||||
if (!m_camera)
|
||||
{
|
||||
if (m_camera_id.empty()) camera_log.notice("Camera disabled");
|
||||
else camera_log.error("No camera found");
|
||||
set_state(camera_handler_state::closed);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto camera_permission = SDL_GetCameraPermissionState(m_camera);
|
||||
switch (camera_permission)
|
||||
{
|
||||
case SDL_CameraPermissionState::SDL_CAMERA_PERMISSION_STATE_DENIED:
|
||||
camera_log.error("Camera permission denied");
|
||||
set_state(camera_handler_state::closed);
|
||||
reset();
|
||||
return;
|
||||
case SDL_CameraPermissionState::SDL_CAMERA_PERMISSION_STATE_PENDING:
|
||||
// TODO: try to get permission
|
||||
break;
|
||||
case SDL_CameraPermissionState::SDL_CAMERA_PERMISSION_STATE_APPROVED:
|
||||
break;
|
||||
default:
|
||||
fmt::throw_exception("Unknown SDL_CameraPermissionState %d", static_cast<s32>(camera_permission));
|
||||
}
|
||||
|
||||
// Start camera. We will start receiving frames now.
|
||||
set_state(camera_handler_state::running);
|
||||
}
|
||||
|
||||
void sdl_camera_handler::stop_camera()
|
||||
{
|
||||
camera_log.notice("Stopping camera");
|
||||
|
||||
if (!m_camera)
|
||||
{
|
||||
if (m_camera_id.empty()) camera_log.notice("Camera disabled");
|
||||
else camera_log.error("No camera found");
|
||||
set_state(camera_handler_state::closed);
|
||||
return;
|
||||
}
|
||||
|
||||
// Stop camera. The camera will still be drawing power.
|
||||
set_expected_state(camera_handler_state::open);
|
||||
}
|
||||
|
||||
void sdl_camera_handler::set_format(s32 format, u32 bytesize)
|
||||
{
|
||||
m_format = format;
|
||||
m_bytesize = bytesize;
|
||||
|
||||
if (m_video_sink)
|
||||
{
|
||||
m_video_sink->set_format(m_format, m_bytesize);
|
||||
}
|
||||
}
|
||||
|
||||
void sdl_camera_handler::set_frame_rate(u32 frame_rate)
|
||||
{
|
||||
m_frame_rate = frame_rate;
|
||||
}
|
||||
|
||||
void sdl_camera_handler::set_resolution(u32 width, u32 height)
|
||||
{
|
||||
m_width = width;
|
||||
m_height = height;
|
||||
|
||||
if (m_video_sink)
|
||||
{
|
||||
m_video_sink->set_resolution(m_width, m_height);
|
||||
}
|
||||
}
|
||||
|
||||
void sdl_camera_handler::set_mirrored(bool mirrored)
|
||||
{
|
||||
m_mirrored = mirrored;
|
||||
|
||||
if (m_video_sink)
|
||||
{
|
||||
m_video_sink->set_mirrored(m_mirrored);
|
||||
}
|
||||
}
|
||||
|
||||
u64 sdl_camera_handler::frame_number() const
|
||||
{
|
||||
return m_video_sink ? m_video_sink->frame_number() : 0;
|
||||
}
|
||||
|
||||
camera_handler_base::camera_handler_state sdl_camera_handler::get_image(u8* buf, u64 size, u32& width, u32& height, u64& frame_number, u64& bytes_read)
|
||||
{
|
||||
width = 0;
|
||||
height = 0;
|
||||
frame_number = 0;
|
||||
bytes_read = 0;
|
||||
|
||||
if (const std::string camera_id = g_cfg.io.sdl_camera_id.to_string();
|
||||
m_camera_id != camera_id)
|
||||
{
|
||||
camera_log.notice("Switching cameras");
|
||||
set_state(camera_handler_state::closed);
|
||||
return camera_handler_state::closed;
|
||||
}
|
||||
|
||||
if (m_camera_id.empty())
|
||||
{
|
||||
camera_log.notice("Camera disabled");
|
||||
set_state(camera_handler_state::closed);
|
||||
return camera_handler_state::closed;
|
||||
}
|
||||
|
||||
if (!m_camera || !m_video_sink)
|
||||
{
|
||||
camera_log.fatal("Error: camera invalid");
|
||||
set_state(camera_handler_state::closed);
|
||||
return camera_handler_state::closed;
|
||||
}
|
||||
|
||||
// Backup current state. State may change through events.
|
||||
const camera_handler_state current_state = get_state();
|
||||
|
||||
if (current_state == camera_handler_state::running)
|
||||
{
|
||||
m_video_sink->get_image(buf, size, width, height, frame_number, bytes_read);
|
||||
}
|
||||
else
|
||||
{
|
||||
camera_log.error("Camera not running (m_state=%d)", static_cast<int>(current_state));
|
||||
}
|
||||
|
||||
return current_state;
|
||||
}
|
||||
|
||||
#endif
|
||||
49
rpcs3/Input/sdl_camera_handler.h
Normal file
49
rpcs3/Input/sdl_camera_handler.h
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
#pragma once
|
||||
|
||||
#ifdef HAVE_SDL3
|
||||
|
||||
#include "Emu/Io/camera_handler_base.h"
|
||||
|
||||
#ifndef _MSC_VER
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wold-style-cast"
|
||||
#endif
|
||||
#include "SDL3/SDL.h"
|
||||
#ifndef _MSC_VER
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
|
||||
#include <map>
|
||||
|
||||
class sdl_camera_video_sink;
|
||||
|
||||
class sdl_camera_handler : public camera_handler_base
|
||||
{
|
||||
public:
|
||||
sdl_camera_handler();
|
||||
virtual ~sdl_camera_handler();
|
||||
|
||||
void open_camera() override;
|
||||
void close_camera() override;
|
||||
void start_camera() override;
|
||||
void stop_camera() override;
|
||||
void set_format(s32 format, u32 bytesize) override;
|
||||
void set_frame_rate(u32 frame_rate) override;
|
||||
void set_resolution(u32 width, u32 height) override;
|
||||
void set_mirrored(bool mirrored) override;
|
||||
u64 frame_number() const override;
|
||||
camera_handler_state get_image(u8* buf, u64 size, u32& width, u32& height, u64& frame_number, u64& bytes_read) override;
|
||||
|
||||
static std::vector<std::string> get_drivers();
|
||||
static std::map<SDL_CameraID, std::string> get_cameras();
|
||||
|
||||
private:
|
||||
void reset();
|
||||
|
||||
std::string m_camera_id;
|
||||
SDL_CameraID m_sdl_camera_id = 0;
|
||||
SDL_Camera* m_camera = nullptr;
|
||||
std::unique_ptr<sdl_camera_video_sink> m_video_sink;
|
||||
};
|
||||
|
||||
#endif
|
||||
168
rpcs3/Input/sdl_camera_video_sink.cpp
Normal file
168
rpcs3/Input/sdl_camera_video_sink.cpp
Normal file
|
|
@ -0,0 +1,168 @@
|
|||
#ifdef HAVE_SDL3
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "sdl_camera_video_sink.h"
|
||||
#include "Utilities/Thread.h"
|
||||
#include "Emu/system_config.h"
|
||||
|
||||
LOG_CHANNEL(camera_log, "Camera");
|
||||
|
||||
sdl_camera_video_sink::sdl_camera_video_sink(bool front_facing, SDL_Camera* camera)
|
||||
: camera_video_sink(front_facing), m_camera(camera)
|
||||
{
|
||||
ensure(m_camera);
|
||||
|
||||
m_thread = std::make_unique<std::thread>(&sdl_camera_video_sink::run, this);
|
||||
}
|
||||
|
||||
sdl_camera_video_sink::~sdl_camera_video_sink()
|
||||
{
|
||||
m_terminate = true;
|
||||
|
||||
if (m_thread && m_thread->joinable())
|
||||
{
|
||||
m_thread->join();
|
||||
m_thread.reset();
|
||||
}
|
||||
}
|
||||
|
||||
void sdl_camera_video_sink::present(SDL_Surface* frame)
|
||||
{
|
||||
const int bytes_per_pixel = SDL_BYTESPERPIXEL(frame->format);
|
||||
const u32 src_width_in_bytes = std::max(0, frame->w * bytes_per_pixel);
|
||||
const u32 dst_width_in_bytes = std::max<u32>(0, m_width * bytes_per_pixel);
|
||||
const u8* pixels = reinterpret_cast<const u8*>(frame->pixels);
|
||||
|
||||
bool use_buffer = false;
|
||||
|
||||
// Scale image if necessary
|
||||
const bool scale_image = m_width > 0 && m_height > 0 && m_width != static_cast<u32>(frame->w) && m_height != static_cast<u32>(frame->h);
|
||||
|
||||
// Determine image flip
|
||||
const camera_flip flip_setting = g_cfg.io.camera_flip_option;
|
||||
|
||||
bool flip_horizontally = m_front_facing; // Front facing cameras are flipped already
|
||||
if (flip_setting == camera_flip::horizontal || flip_setting == camera_flip::both)
|
||||
{
|
||||
flip_horizontally = !flip_horizontally;
|
||||
}
|
||||
if (m_mirrored) // Set by the game
|
||||
{
|
||||
flip_horizontally = !flip_horizontally;
|
||||
}
|
||||
|
||||
bool flip_vertically = false;
|
||||
if (flip_setting == camera_flip::vertical || flip_setting == camera_flip::both)
|
||||
{
|
||||
flip_vertically = !flip_vertically;
|
||||
}
|
||||
|
||||
// Flip image if necessary
|
||||
if (flip_horizontally || flip_vertically || scale_image)
|
||||
{
|
||||
m_buffer.resize(m_height * dst_width_in_bytes);
|
||||
use_buffer = true;
|
||||
|
||||
if (m_width > 0 && m_height > 0 && frame->w > 0 && frame->h > 0)
|
||||
{
|
||||
const f32 scale_x = frame->w / static_cast<f32>(m_width);
|
||||
const f32 scale_y = frame->h / static_cast<f32>(m_height);
|
||||
|
||||
if (flip_horizontally && flip_vertically)
|
||||
{
|
||||
for (u32 y = 0; y < m_height; y++)
|
||||
{
|
||||
const u32 src_y = frame->h - static_cast<u32>(scale_y * y) - 1;
|
||||
const u8* src = pixels + src_y * src_width_in_bytes;
|
||||
u8* dst = &m_buffer[y * dst_width_in_bytes];
|
||||
|
||||
for (u32 x = 0; x < m_width; x++)
|
||||
{
|
||||
const u32 src_x = frame->w - static_cast<u32>(scale_x * x) - 1;
|
||||
std::memcpy(dst + x * bytes_per_pixel, src + src_x * bytes_per_pixel, bytes_per_pixel);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (flip_horizontally)
|
||||
{
|
||||
for (u32 y = 0; y < m_height; y++)
|
||||
{
|
||||
const u32 src_y = static_cast<u32>(scale_y * y);
|
||||
const u8* src = pixels + src_y * src_width_in_bytes;
|
||||
u8* dst = &m_buffer[y * dst_width_in_bytes];
|
||||
|
||||
for (u32 x = 0; x < m_width; x++)
|
||||
{
|
||||
const u32 src_x = frame->w - static_cast<u32>(scale_x * x) - 1;
|
||||
std::memcpy(dst + x * bytes_per_pixel, src + src_x * bytes_per_pixel, bytes_per_pixel);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (flip_vertically)
|
||||
{
|
||||
for (u32 y = 0; y < m_height; y++)
|
||||
{
|
||||
const u32 src_y = frame->h - static_cast<u32>(scale_y * y) - 1;
|
||||
const u8* src = pixels + src_y * src_width_in_bytes;
|
||||
u8* dst = &m_buffer[y * dst_width_in_bytes];
|
||||
|
||||
for (u32 x = 0; x < m_width; x++)
|
||||
{
|
||||
const u32 src_x = static_cast<u32>(scale_x * x);
|
||||
std::memcpy(dst + x * bytes_per_pixel, src + src_x * bytes_per_pixel, bytes_per_pixel);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (u32 y = 0; y < m_height; y++)
|
||||
{
|
||||
const u32 src_y = static_cast<u32>(scale_y * y);
|
||||
const u8* src = pixels + src_y * src_width_in_bytes;
|
||||
u8* dst = &m_buffer[y * dst_width_in_bytes];
|
||||
|
||||
for (u32 x = 0; x < m_width; x++)
|
||||
{
|
||||
const u32 src_x = static_cast<u32>(scale_x * x);
|
||||
std::memcpy(dst + x * bytes_per_pixel, src + src_x * bytes_per_pixel, bytes_per_pixel);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (use_buffer)
|
||||
{
|
||||
camera_video_sink::present(m_width, m_height, dst_width_in_bytes, bytes_per_pixel, [src = m_buffer.data(), dst_width_in_bytes](u32 y){ return src + y * dst_width_in_bytes; });
|
||||
}
|
||||
else
|
||||
{
|
||||
camera_video_sink::present(frame->w, frame->h, frame->pitch, bytes_per_pixel, [pixels, pitch = frame->pitch](u32 y){ return pixels + y * pitch; });
|
||||
}
|
||||
}
|
||||
|
||||
void sdl_camera_video_sink::run()
|
||||
{
|
||||
thread_base::set_name("SDL Capture Thread");
|
||||
|
||||
camera_log.notice("SDL Capture Thread started");
|
||||
|
||||
while (!m_terminate)
|
||||
{
|
||||
// Copy latest image into out buffer.
|
||||
u64 timestamp_ns = 0;
|
||||
SDL_Surface* frame = SDL_AcquireCameraFrame(m_camera, ×tamp_ns);
|
||||
if (!frame)
|
||||
{
|
||||
// No new frame
|
||||
std::this_thread::sleep_for(100us);
|
||||
continue;
|
||||
}
|
||||
|
||||
present(frame);
|
||||
|
||||
SDL_ReleaseCameraFrame(m_camera, frame);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
34
rpcs3/Input/sdl_camera_video_sink.h
Normal file
34
rpcs3/Input/sdl_camera_video_sink.h
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
#pragma once
|
||||
|
||||
#ifdef HAVE_SDL3
|
||||
|
||||
#include "Input/camera_video_sink.h"
|
||||
|
||||
#ifndef _MSC_VER
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wold-style-cast"
|
||||
#endif
|
||||
#include "SDL3/SDL.h"
|
||||
#ifndef _MSC_VER
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
|
||||
#include <thread>
|
||||
|
||||
class sdl_camera_video_sink final : public camera_video_sink
|
||||
{
|
||||
public:
|
||||
sdl_camera_video_sink(bool front_facing, SDL_Camera* camera);
|
||||
virtual ~sdl_camera_video_sink();
|
||||
|
||||
private:
|
||||
void present(SDL_Surface* frame);
|
||||
void run();
|
||||
|
||||
std::vector<u8> m_buffer;
|
||||
atomic_t<bool> m_terminate = false;
|
||||
SDL_Camera* m_camera = nullptr;
|
||||
std::unique_ptr<std::thread> m_thread;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -102,7 +102,7 @@ bool sdl_instance::initialize_impl()
|
|||
set_hint(SDL_HINT_JOYSTICK_HIDAPI_PS3, "1");
|
||||
#endif
|
||||
|
||||
if (!SDL_Init(SDL_INIT_GAMEPAD | SDL_INIT_HAPTIC))
|
||||
if (!SDL_Init(SDL_INIT_GAMEPAD | SDL_INIT_HAPTIC | SDL_INIT_CAMERA))
|
||||
{
|
||||
sdl_log.error("Could not initialize! SDL Error: %s", SDL_GetError());
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -42,8 +42,8 @@
|
|||
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||
<AdditionalIncludeDirectories>..\3rdparty\miniupnp\miniupnp\miniupnpc\include;..\3rdparty\wolfssl\wolfssl;..\3rdparty\flatbuffers\include;..\3rdparty\libusb\libusb\libusb;..\3rdparty\yaml-cpp\yaml-cpp\include;..\3rdparty\SoundTouch\soundtouch\include;..\3rdparty\rtmidi\rtmidi;..\3rdparty\zlib\zlib;$(SolutionDir)build\lib\$(Configuration)-$(Platform)\llvm_build\include;$(SolutionDir)build\lib_ext\$(Configuration)-$(Platform)\llvm_build\include;$(SolutionDir)build\lib_ext\$(Configuration)-$(Platform)\llvm\include;$(SolutionDir)build\lib_ext\$(Configuration)-$(Platform)\llvm_build\include;$(VULKAN_SDK)\Include;..\3rdparty\zstd\zstd\lib;$(SolutionDir)3rdparty\fusion\fusion\Fusion;$(SolutionDir)3rdparty\wolfssl\extra\win32;$(SolutionDir)3rdparty\libsdl-org\SDL\include;$(SolutionDir)3rdparty\glslang\glslang;$(SolutionDir)3rdparty\curl\curl\include</AdditionalIncludeDirectories>
|
||||
<Optimization Condition="'$(Configuration)|$(Platform)'=='Release|x64'">MaxSpeed</Optimization>
|
||||
<PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">AL_LIBTYPE_STATIC;MINIUPNP_STATICLIB;HAVE_VULKAN;HAVE_SDL3;ZLIB_CONST;WOLFSSL_USER_SETTINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|x64'">AL_LIBTYPE_STATIC;MINIUPNP_STATICLIB;HAVE_VULKAN;HAVE_SDL3;ZLIB_CONST;WOLFSSL_USER_SETTINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">AL_LIBTYPE_STATIC;MINIUPNP_STATICLIB;HAVE_VULKAN;HAVE_SDL3;ZLIB_CONST;WOLFSSL_USER_SETTINGS;CURL_STATICLIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|x64'">AL_LIBTYPE_STATIC;MINIUPNP_STATICLIB;HAVE_VULKAN;HAVE_SDL3;ZLIB_CONST;WOLFSSL_USER_SETTINGS;CURL_STATICLIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<AdditionalModuleDependencies Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">%(AdditionalModuleDependencies)</AdditionalModuleDependencies>
|
||||
<AdditionalModuleDependencies Condition="'$(Configuration)|$(Platform)'=='Release|x64'">%(AdditionalModuleDependencies)</AdditionalModuleDependencies>
|
||||
</ClCompile>
|
||||
|
|
@ -627,6 +627,8 @@
|
|||
<ClInclude Include="Emu\IPC_socket.h" />
|
||||
<ClInclude Include="Emu\localized_string.h" />
|
||||
<ClInclude Include="Emu\localized_string_id.h" />
|
||||
<ClInclude Include="Emu\NP\clans_client.h" />
|
||||
<ClInclude Include="Emu\NP\clans_config.h" />
|
||||
<ClInclude Include="Emu\NP\fb_helpers.h" />
|
||||
<ClInclude Include="Emu\NP\generated\np2_structs_generated.h" />
|
||||
<ClInclude Include="Emu\NP\np_contexts.h" />
|
||||
|
|
|
|||
|
|
@ -1396,6 +1396,12 @@
|
|||
<ClCompile Include="Emu\Io\ps_move_data.cpp">
|
||||
<Filter>Emu\Io</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Emu\NP\clans_config.cpp">
|
||||
<Filter>Emu\NP</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Emu\NP\clans_client.cpp">
|
||||
<Filter>Emu\NP</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="Crypto\aes.h">
|
||||
|
|
@ -2806,6 +2812,12 @@
|
|||
<ClInclude Include="Emu\Io\ps_move_data.h">
|
||||
<Filter>Emu\Io</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Emu\NP\clans_client.h">
|
||||
<Filter>Emu\NP</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Emu\NP\clans_config.h">
|
||||
<Filter>Emu\NP</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="Emu\RSX\Program\GLSLSnippets\GPUDeswizzle.glsl">
|
||||
|
|
|
|||
|
|
@ -102,6 +102,9 @@ void headless_application::InitializeCallbacks()
|
|||
return std::make_shared<null_camera_handler>();
|
||||
}
|
||||
case camera_handler::qt:
|
||||
#ifdef HAVE_SDL3
|
||||
case camera_handler::sdl:
|
||||
#endif
|
||||
{
|
||||
fmt::throw_exception("Headless mode can not be used with this camera handler. Current handler: %s", g_cfg.io.camera.get());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
#include "Emu/Io/Null/NullMouseHandler.h"
|
||||
#include "Emu/Io/KeyboardHandler.h"
|
||||
#include "Emu/Io/MouseHandler.h"
|
||||
#include "Emu/VFS.h"
|
||||
#include "Input/basic_keyboard_handler.h"
|
||||
#include "Input/basic_mouse_handler.h"
|
||||
#include "Input/raw_mouse_handler.h"
|
||||
|
|
@ -36,6 +37,7 @@
|
|||
#include "Emu/Audio/FAudio/faudio_enumerator.h"
|
||||
#endif
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QFileInfo> // This shouldn't be outside rpcs3qt...
|
||||
#include <QImageReader> // This shouldn't be outside rpcs3qt...
|
||||
#include <QStandardPaths> // This shouldn't be outside rpcs3qt...
|
||||
|
|
@ -377,5 +379,33 @@ EmuCallbacks main_application::CreateCallbacks()
|
|||
|
||||
callbacks.enable_gamemode = [](bool enabled){ enable_gamemode(enabled); };
|
||||
|
||||
callbacks.get_photo_path = [](std::string_view title)
|
||||
{
|
||||
const QDateTime date_time = QDateTime::currentDateTime();
|
||||
const QDate date = date_time.date();
|
||||
const QTime time = date_time.time();
|
||||
|
||||
std::string_view extension = ".png";
|
||||
if (const auto extension_start = title.find_last_of('.');
|
||||
extension_start != umax)
|
||||
{
|
||||
extension = title.substr(extension_start);
|
||||
title = title.substr(0, extension_start);
|
||||
}
|
||||
|
||||
std::string suffix = std::string(extension);
|
||||
const std::string path = vfs::get(fmt::format("/dev_hdd0/photo/%04d/%02d/%02d/%s %02d-%02d-%04d %02d-%02d-%02d",
|
||||
date.year(), date.month(), date.day(), vfs::escape(title, true),
|
||||
date.day(), date.month(), date.year(), time.hour(), time.minute(), time.second()));
|
||||
|
||||
u32 counter = 0;
|
||||
while (!Emu.IsStopped() && fs::is_file(path + suffix))
|
||||
{
|
||||
suffix = fmt::format(" %d%s", ++counter, extension);
|
||||
}
|
||||
|
||||
return path + suffix;
|
||||
};
|
||||
|
||||
return callbacks;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,5 +17,6 @@
|
|||
<file>Icons/combo_config_bordered.png</file>
|
||||
<file>rpcs3.svg</file>
|
||||
<file>Icons/DualShock_3.svg</file>
|
||||
<file>Icons/rpcn.png</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
|
|
|||
|
|
@ -77,7 +77,7 @@
|
|||
<DisableSpecificWarnings>4577;4467;4281;%(DisableSpecificWarnings)</DisableSpecificWarnings>
|
||||
<ObjectFileName>$(IntDir)</ObjectFileName>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
<PreprocessorDefinitions>_WINDOWS;UNICODE;WIN32;WIN64;WIN32_LEAN_AND_MEAN;HAVE_VULKAN;HAVE_OPENCV;CV_IGNORE_DEBUG_BUILD_GUARD;MINIUPNP_STATICLIB;ZLIB_CONST;AL_LIBTYPE_STATIC;WOLFSSL_USER_SETTINGS;HAVE_SDL3;WITH_DISCORD_RPC;QT_NO_DEBUG;QT_WIDGETS_LIB;QT_GUI_LIB;QT_CORE_LIB;NDEBUG;QT_CONCURRENT_LIB;QT_MULTIMEDIA_LIB;QT_MULTIMEDIAWIDGETS_LIB;QT_SVG_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<PreprocessorDefinitions>_WINDOWS;UNICODE;WIN32;WIN64;WIN32_LEAN_AND_MEAN;HAVE_VULKAN;HAVE_OPENCV;CV_IGNORE_DEBUG_BUILD_GUARD;MINIUPNP_STATICLIB;ZLIB_CONST;AL_LIBTYPE_STATIC;WOLFSSL_USER_SETTINGS;HAVE_SDL3;WITH_DISCORD_RPC;QT_NO_DEBUG;QT_WIDGETS_LIB;QT_GUI_LIB;QT_CORE_LIB;NDEBUG;QT_CONCURRENT_LIB;QT_MULTIMEDIA_LIB;QT_MULTIMEDIAWIDGETS_LIB;QT_SVG_LIB;CURL_STATICLIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<PreprocessToFile>false</PreprocessToFile>
|
||||
<ProgramDataBaseFileName>$(IntDir)vc$(PlatformToolsetVersion).pdb</ProgramDataBaseFileName>
|
||||
<RuntimeTypeInfo>true</RuntimeTypeInfo>
|
||||
|
|
@ -110,7 +110,7 @@
|
|||
</ResourceCompile>
|
||||
<PostBuildEvent>
|
||||
<Command>
|
||||
$(QTDIR)\bin\windeployqt6 --no-compiler-runtime --no-opengl-sw --no-patchqt --no-translations --no-quick --no-system-d3d-compiler --no-system-dxc-compiler --no-quick-import --plugindir "$(TargetDir)qt6\plugins" --release "$(TargetPath)"
|
||||
$(QTDIR)\bin\windeployqt6 --no-compiler-runtime --no-opengl-sw --no-patchqt --no-quick --no-system-d3d-compiler --no-system-dxc-compiler --no-quick-import --plugindir "$(TargetDir)qt6\plugins" --translationdir "$(TargetDir)qt6\translations" --release "$(TargetPath)"
|
||||
xcopy /y /d "$(SolutionDir)3rdparty\opencv\opencv\opencv412\build\x64\bin\opencv_world4120.dll" "$(OutDir)"
|
||||
</Command>
|
||||
</PostBuildEvent>
|
||||
|
|
@ -138,7 +138,7 @@
|
|||
<DisableSpecificWarnings>4577;4467;4281;%(DisableSpecificWarnings)</DisableSpecificWarnings>
|
||||
<ObjectFileName>$(IntDir)</ObjectFileName>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<PreprocessorDefinitions>_WINDOWS;UNICODE;WIN32;WIN64;WIN32_LEAN_AND_MEAN;HAVE_VULKAN;HAVE_OPENCV;CV_IGNORE_DEBUG_BUILD_GUARD;MINIUPNP_STATICLIB;ZLIB_CONST;AL_LIBTYPE_STATIC;WOLFSSL_USER_SETTINGS;HAVE_SDL3;QT_WIDGETS_LIB;QT_GUI_LIB;QT_CORE_LIB;QT_CONCURRENT_LIB;QT_MULTIMEDIA_LIB;QT_MULTIMEDIAWIDGETS_LIB;QT_SVG_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<PreprocessorDefinitions>_WINDOWS;UNICODE;WIN32;WIN64;WIN32_LEAN_AND_MEAN;HAVE_VULKAN;HAVE_OPENCV;CV_IGNORE_DEBUG_BUILD_GUARD;MINIUPNP_STATICLIB;ZLIB_CONST;AL_LIBTYPE_STATIC;WOLFSSL_USER_SETTINGS;HAVE_SDL3;QT_WIDGETS_LIB;QT_GUI_LIB;QT_CORE_LIB;QT_CONCURRENT_LIB;QT_MULTIMEDIA_LIB;QT_MULTIMEDIAWIDGETS_LIB;QT_SVG_LIB;CURL_STATICLIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<PreprocessToFile>false</PreprocessToFile>
|
||||
<RuntimeTypeInfo>true</RuntimeTypeInfo>
|
||||
<SuppressStartupBanner>true</SuppressStartupBanner>
|
||||
|
|
@ -169,7 +169,7 @@
|
|||
</ResourceCompile>
|
||||
<PostBuildEvent>
|
||||
<Command>
|
||||
$(QTDIR)\bin\windeployqt6 --no-compiler-runtime --no-opengl-sw --no-patchqt --no-translations --no-quick --no-system-d3d-compiler --no-system-dxc-compiler --no-quick-import --plugindir "$(TargetDir)qt6\plugins" --debug "$(TargetPath)"
|
||||
$(QTDIR)\bin\windeployqt6 --no-compiler-runtime --no-opengl-sw --no-patchqt --no-quick --no-system-d3d-compiler --no-system-dxc-compiler --no-quick-import --plugindir "$(TargetDir)qt6\plugins" --translationdir "$(TargetDir)qt6\translations" --debug "$(TargetPath)"
|
||||
xcopy /y /d "$(SolutionDir)3rdparty\opencv\opencv\opencv412\build\x64\bin\opencv_world4120.dll" "$(OutDir)"
|
||||
</Command>
|
||||
</PostBuildEvent>
|
||||
|
|
@ -190,6 +190,7 @@
|
|||
<ItemGroup>
|
||||
<ClCompile Include="display_sleep_control.cpp" />
|
||||
<ClCompile Include="gamemode_control.cpp" />
|
||||
<ClCompile Include="Input\camera_video_sink.cpp" />
|
||||
<ClCompile Include="Input\dualsense_pad_handler.cpp" />
|
||||
<ClCompile Include="Input\gui_pad_thread.cpp" />
|
||||
<ClCompile Include="Input\hid_pad_handler.cpp" />
|
||||
|
|
@ -200,6 +201,8 @@
|
|||
<ClCompile Include="Input\raw_mouse_handler.cpp" />
|
||||
<ClCompile Include="Input\ps_move_handler.cpp" />
|
||||
<ClCompile Include="Input\sdl_pad_handler.cpp" />
|
||||
<ClCompile Include="Input\sdl_camera_handler.cpp" />
|
||||
<ClCompile Include="Input\sdl_camera_video_sink.cpp" />
|
||||
<ClCompile Include="Input\sdl_instance.cpp" />
|
||||
<ClCompile Include="Input\skateboard_pad_handler.cpp" />
|
||||
<ClCompile Include="main.cpp" />
|
||||
|
|
@ -944,6 +947,7 @@
|
|||
<ClInclude Include="Input\basic_mouse_handler.h" />
|
||||
<ClInclude Include="display_sleep_control.h" />
|
||||
<ClInclude Include="gamemode_control.h" />
|
||||
<ClInclude Include="Input\camera_video_sink.h" />
|
||||
<ClInclude Include="Input\ds3_pad_handler.h" />
|
||||
<ClInclude Include="Input\ds4_pad_handler.h" />
|
||||
<ClInclude Include="Input\dualsense_pad_handler.h" />
|
||||
|
|
@ -1068,6 +1072,8 @@
|
|||
<ClInclude Include="Input\raw_mouse_handler.h" />
|
||||
<ClInclude Include="Input\ps_move_handler.h" />
|
||||
<ClInclude Include="Input\sdl_pad_handler.h" />
|
||||
<ClInclude Include="Input\sdl_camera_handler.h" />
|
||||
<ClInclude Include="Input\sdl_camera_video_sink.h" />
|
||||
<ClInclude Include="Input\sdl_instance.h" />
|
||||
<ClInclude Include="Input\skateboard_pad_handler.h" />
|
||||
<ClInclude Include="main_application.h" />
|
||||
|
|
@ -2230,4 +2236,4 @@
|
|||
<UserProperties MocDir=".\QTGeneratedFiles\$(ConfigurationName)" Qt5Version_x0020_x64="$(DefaultQtVersion)" RccDir=".\QTGeneratedFiles" UicDir=".\QTGeneratedFiles" />
|
||||
</VisualStudio>
|
||||
</ProjectExtensions>
|
||||
</Project>
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -915,9 +915,6 @@
|
|||
<ClCompile Include="rpcs3qt\rpcn_settings_dialog.cpp">
|
||||
<Filter>Gui\rpcn</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="rpcs3qt\clans_settings_dialog.cpp">
|
||||
<Filter>Gui\clans</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="rpcs3qt\sendmessage_dialog_frame.cpp">
|
||||
<Filter>Gui\message dialog</Filter>
|
||||
</ClCompile>
|
||||
|
|
@ -1245,6 +1242,18 @@
|
|||
<ClCompile Include="gamemode_control.cpp">
|
||||
<Filter>rpcs3</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="rpcs3qt\clans_settings_dialog.cpp">
|
||||
<Filter>Gui\rpcn</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Input\camera_video_sink.cpp">
|
||||
<Filter>Io\camera</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Input\sdl_camera_handler.cpp">
|
||||
<Filter>Io\camera</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Input\sdl_camera_video_sink.cpp">
|
||||
<Filter>Io\camera</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="Input\ds4_pad_handler.h">
|
||||
|
|
@ -1475,6 +1484,15 @@
|
|||
<ClInclude Include="rpcs3qt\custom_tree_widget.h">
|
||||
<Filter>Gui\widgets</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Input\camera_video_sink.h">
|
||||
<Filter>Io\camera</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Input\sdl_camera_handler.h">
|
||||
<Filter>Io\camera</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Input\sdl_camera_video_sink.h">
|
||||
<Filter>Io\camera</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="resource.h">
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ namespace rpcs3
|
|||
// Currently accessible by Windows and Linux build scripts, see implementations when doing MACOSX
|
||||
const utils::version& get_version()
|
||||
{
|
||||
static constexpr utils::version version{ 0, 0, 38, utils::version_type::alpha, 1, RPCS3_GIT_VERSION };
|
||||
static constexpr utils::version version{ 0, 0, 39, utils::version_type::alpha, 1, RPCS3_GIT_VERSION };
|
||||
return version;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -146,6 +146,7 @@ add_library(rpcs3_ui STATIC
|
|||
|
||||
../Input/basic_keyboard_handler.cpp
|
||||
../Input/basic_mouse_handler.cpp
|
||||
../Input/camera_video_sink.cpp
|
||||
../Input/ds3_pad_handler.cpp
|
||||
../Input/ds4_pad_handler.cpp
|
||||
../Input/dualsense_pad_handler.cpp
|
||||
|
|
@ -162,8 +163,10 @@ add_library(rpcs3_ui STATIC
|
|||
../Input/ps_move_tracker.cpp
|
||||
../Input/raw_mouse_config.cpp
|
||||
../Input/raw_mouse_handler.cpp
|
||||
../Input/sdl_pad_handler.cpp
|
||||
../Input/sdl_camera_handler.cpp
|
||||
../Input/sdl_camera_video_sink.cpp
|
||||
../Input/sdl_instance.cpp
|
||||
../Input/sdl_pad_handler.cpp
|
||||
../Input/skateboard_pad_handler.cpp
|
||||
../Input/xinput_pad_handler.cpp
|
||||
|
||||
|
|
|
|||
|
|
@ -3,11 +3,19 @@
|
|||
#include "ui_camera_settings_dialog.h"
|
||||
#include "permissions.h"
|
||||
#include "Emu/Io/camera_config.h"
|
||||
#include "Emu/System.h"
|
||||
#include "Emu/system_config.h"
|
||||
|
||||
#include <QCameraDevice>
|
||||
#include <QMediaDevices>
|
||||
#include <QMessageBox>
|
||||
#include <QPushButton>
|
||||
#include <QVideoSink>
|
||||
|
||||
#ifdef HAVE_SDL3
|
||||
#include "Input/sdl_instance.h"
|
||||
#include "Input/sdl_camera_handler.h"
|
||||
#endif
|
||||
|
||||
LOG_CHANNEL(camera_log, "Camera");
|
||||
|
||||
|
|
@ -53,6 +61,81 @@ void fmt_class_string<QVideoFrameFormat::PixelFormat>::format(std::string& out,
|
|||
});
|
||||
}
|
||||
|
||||
#ifdef HAVE_SDL3
|
||||
static QString sdl_pixelformat_to_string(SDL_PixelFormat format)
|
||||
{
|
||||
switch (format)
|
||||
{
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_UNKNOWN: return "UNKNOWN";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_INDEX1LSB: return "INDEX1LSB";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_INDEX1MSB: return "INDEX1MSB";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_INDEX2LSB: return "INDEX2LSB";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_INDEX2MSB: return "INDEX2MSB";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_INDEX4LSB: return "INDEX4LSB";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_INDEX4MSB: return "INDEX4MSB";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_INDEX8: return "INDEX8";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_RGB332: return "RGB332";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_XRGB4444: return "XRGB4444";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_XBGR4444: return "XBGR4444";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_XRGB1555: return "XRGB1555";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_XBGR1555: return "XBGR1555";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_ARGB4444: return "ARGB4444";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_RGBA4444: return "RGBA4444";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_ABGR4444: return "ABGR4444";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_BGRA4444: return "BGRA4444";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_ARGB1555: return "ARGB1555";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_RGBA5551: return "RGBA5551";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_ABGR1555: return "ABGR1555";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_BGRA5551: return "BGRA5551";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_RGB565: return "RGB565";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_BGR565: return "BGR565";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_RGB24: return "RGB24";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_BGR24: return "BGR24";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_XRGB8888: return "XRGB8888";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_RGBX8888: return "RGBX8888";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_XBGR8888: return "XBGR8888";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_BGRX8888: return "BGRX8888";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_ARGB8888: return "ARGB8888";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_RGBA8888: return "RGBA8888";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_ABGR8888: return "ABGR8888";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_BGRA8888: return "BGRA8888";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_XRGB2101010: return "XRGB2101010";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_XBGR2101010: return "XBGR2101010";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_ARGB2101010: return "ARGB2101010";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_ABGR2101010: return "ABGR2101010";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_RGB48: return "RGB48";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_BGR48: return "BGR48";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_RGBA64: return "RGBA64";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_ARGB64: return "ARGB64";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_BGRA64: return "BGRA64";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_ABGR64: return "ABGR64";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_RGB48_FLOAT: return "RGB48_FLOAT";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_BGR48_FLOAT: return "BGR48_FLOAT";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_RGBA64_FLOAT: return "RGBA64_FLOAT";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_ARGB64_FLOAT: return "ARGB64_FLOAT";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_BGRA64_FLOAT: return "BGRA64_FLOAT";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_ABGR64_FLOAT: return "ABGR64_FLOAT";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_RGB96_FLOAT: return "RGB96_FLOAT";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_BGR96_FLOAT: return "BGR96_FLOAT";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_RGBA128_FLOAT: return "RGBA128_FLOAT";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_ARGB128_FLOAT: return "ARGB128_FLOAT";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_BGRA128_FLOAT: return "BGRA128_FLOAT";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_ABGR128_FLOAT: return "ABGR128_FLOAT";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_YV12: return "YV12";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_IYUV: return "IYUV";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_YUY2: return "YUY2";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_UYVY: return "UYVY";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_YVYU: return "YVYU";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_NV12: return "NV12";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_NV21: return "NV21";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_P010: return "P010";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_EXTERNAL_OES: return "EXTERNAL_OES";
|
||||
case SDL_PixelFormat::SDL_PIXELFORMAT_MJPG: return "MJPG";
|
||||
default: return QObject::tr("Unknown: %0").arg(static_cast<int>(format));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
Q_DECLARE_METATYPE(QCameraDevice);
|
||||
|
||||
camera_settings_dialog::camera_settings_dialog(QWidget* parent)
|
||||
|
|
@ -61,15 +144,16 @@ camera_settings_dialog::camera_settings_dialog(QWidget* parent)
|
|||
{
|
||||
ui->setupUi(this);
|
||||
|
||||
setAttribute(Qt::WA_DeleteOnClose);
|
||||
|
||||
load_config();
|
||||
|
||||
for (const QCameraDevice& camera_info : QMediaDevices::videoInputs())
|
||||
{
|
||||
if (camera_info.isNull()) continue;
|
||||
ui->combo_camera->addItem(camera_info.description(), QVariant::fromValue(camera_info));
|
||||
camera_log.notice("Found camera: '%s'", camera_info.description());
|
||||
}
|
||||
ui->combo_handlers->addItem("Qt", QVariant::fromValue(static_cast<int>(camera_handler::qt)));
|
||||
#ifdef HAVE_SDL3
|
||||
ui->combo_handlers->addItem("SDL", QVariant::fromValue(static_cast<int>(camera_handler::sdl)));
|
||||
#endif
|
||||
|
||||
connect(ui->combo_handlers, &QComboBox::currentIndexChanged, this, &camera_settings_dialog::handle_handler_change);
|
||||
connect(ui->combo_camera, &QComboBox::currentIndexChanged, this, &camera_settings_dialog::handle_camera_change);
|
||||
connect(ui->combo_settings, &QComboBox::currentIndexChanged, this, &camera_settings_dialog::handle_settings_change);
|
||||
connect(ui->buttonBox, &QDialogButtonBox::clicked, [this](QAbstractButton* button)
|
||||
|
|
@ -85,33 +169,183 @@ camera_settings_dialog::camera_settings_dialog(QWidget* parent)
|
|||
}
|
||||
});
|
||||
|
||||
if (ui->combo_camera->count() == 0)
|
||||
{
|
||||
ui->combo_camera->setEnabled(false);
|
||||
ui->combo_settings->setEnabled(false);
|
||||
ui->buttonBox->button(QDialogButtonBox::Save)->setEnabled(false);
|
||||
ui->buttonBox->button(QDialogButtonBox::Apply)->setEnabled(false);
|
||||
}
|
||||
else
|
||||
const int handler_index = ui->combo_handlers->findData(static_cast<int>(g_cfg.io.camera.get()));
|
||||
ui->combo_handlers->setCurrentIndex(std::max(0, handler_index));
|
||||
}
|
||||
|
||||
camera_settings_dialog::~camera_settings_dialog()
|
||||
{
|
||||
reset_cameras();
|
||||
}
|
||||
|
||||
void camera_settings_dialog::enable_combos()
|
||||
{
|
||||
const bool is_enabled = ui->combo_camera->count() > 0;
|
||||
|
||||
ui->combo_camera->setEnabled(is_enabled);
|
||||
ui->combo_settings->setEnabled(is_enabled);
|
||||
ui->buttonBox->button(QDialogButtonBox::Save)->setEnabled(is_enabled);
|
||||
ui->buttonBox->button(QDialogButtonBox::Apply)->setEnabled(is_enabled);
|
||||
|
||||
if (is_enabled)
|
||||
{
|
||||
// TODO: show camera ID somewhere
|
||||
ui->combo_camera->setCurrentIndex(0);
|
||||
}
|
||||
}
|
||||
|
||||
camera_settings_dialog::~camera_settings_dialog()
|
||||
void camera_settings_dialog::reset_cameras()
|
||||
{
|
||||
m_media_capture_session.reset();
|
||||
m_camera.reset();
|
||||
|
||||
#ifdef HAVE_SDL3
|
||||
m_video_frame_input.reset();
|
||||
|
||||
if (m_sdl_thread)
|
||||
{
|
||||
auto& thread = *m_sdl_thread;
|
||||
thread = thread_state::aborting;
|
||||
thread();
|
||||
m_sdl_thread.reset();
|
||||
}
|
||||
|
||||
if (m_sdl_camera)
|
||||
{
|
||||
SDL_CloseCamera(m_sdl_camera);
|
||||
m_sdl_camera = nullptr;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void camera_settings_dialog::handle_handler_change(int index)
|
||||
{
|
||||
reset_cameras();
|
||||
|
||||
if (index < 0 || !ui->combo_handlers->itemData(index).canConvert<int>())
|
||||
{
|
||||
ui->combo_settings->clear();
|
||||
ui->combo_camera->clear();
|
||||
enable_combos();
|
||||
return;
|
||||
}
|
||||
|
||||
m_handler = static_cast<camera_handler>(ui->combo_handlers->itemData(index).value<int>());
|
||||
|
||||
ui->combo_settings->blockSignals(true);
|
||||
ui->combo_camera->blockSignals(true);
|
||||
|
||||
ui->combo_settings->clear();
|
||||
ui->combo_camera->clear();
|
||||
|
||||
switch (m_handler)
|
||||
{
|
||||
case camera_handler::qt:
|
||||
{
|
||||
for (const QCameraDevice& camera_info : QMediaDevices::videoInputs())
|
||||
{
|
||||
if (camera_info.isNull()) continue;
|
||||
ui->combo_camera->addItem(camera_info.description(), QVariant::fromValue(camera_info));
|
||||
camera_log.notice("Found camera: '%s'", camera_info.description());
|
||||
}
|
||||
break;
|
||||
}
|
||||
#ifdef HAVE_SDL3
|
||||
case camera_handler::sdl:
|
||||
{
|
||||
if (!sdl_instance::get_instance().initialize())
|
||||
{
|
||||
camera_log.error("Could not initialize SDL");
|
||||
break;
|
||||
}
|
||||
|
||||
// Log camera drivers
|
||||
sdl_camera_handler::get_drivers();
|
||||
|
||||
// Get cameras
|
||||
const std::map<SDL_CameraID, std::string> cameras = sdl_camera_handler::get_cameras();
|
||||
|
||||
// Add cameras
|
||||
for (const auto& [camera_id, name] : cameras)
|
||||
{
|
||||
ui->combo_camera->addItem(QString::fromStdString(name), QVariant::fromValue(static_cast<u32>(camera_id)));
|
||||
}
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
default:
|
||||
fmt::throw_exception("Unexpected camera handler %d", static_cast<int>(m_handler));
|
||||
}
|
||||
|
||||
ui->combo_settings->blockSignals(false);
|
||||
ui->combo_camera->blockSignals(false);
|
||||
|
||||
enable_combos();
|
||||
}
|
||||
|
||||
void camera_settings_dialog::handle_camera_change(int index)
|
||||
{
|
||||
if (index < 0 || !ui->combo_camera->itemData(index).canConvert<QCameraDevice>())
|
||||
if (index < 0)
|
||||
{
|
||||
ui->combo_settings->clear();
|
||||
return;
|
||||
}
|
||||
|
||||
const QCameraDevice camera_info = ui->combo_camera->itemData(index).value<QCameraDevice>();
|
||||
reset_cameras();
|
||||
|
||||
switch (m_handler)
|
||||
{
|
||||
case camera_handler::qt:
|
||||
handle_qt_camera_change(ui->combo_camera->itemData(index));
|
||||
break;
|
||||
#ifdef HAVE_SDL3
|
||||
case camera_handler::sdl:
|
||||
handle_sdl_camera_change(ui->combo_camera->itemText(index), ui->combo_camera->itemData(index));
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
fmt::throw_exception("Unexpected camera handler %d", static_cast<int>(m_handler));
|
||||
}
|
||||
}
|
||||
|
||||
void camera_settings_dialog::handle_settings_change(int index)
|
||||
{
|
||||
if (index < 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!gui::utils::check_camera_permission(this,
|
||||
[this, index](){ handle_settings_change(index); },
|
||||
[this](){ QMessageBox::warning(this, tr("Camera permissions denied!"), tr("RPCS3 has no permissions to access cameras on this device.")); }))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
switch (m_handler)
|
||||
{
|
||||
case camera_handler::qt:
|
||||
handle_qt_settings_change(ui->combo_settings->itemData(index));
|
||||
break;
|
||||
#ifdef HAVE_SDL3
|
||||
case camera_handler::sdl:
|
||||
handle_sdl_settings_change(ui->combo_settings->itemData(index));
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
fmt::throw_exception("Unexpected camera handler %d", static_cast<int>(m_handler));
|
||||
}
|
||||
}
|
||||
|
||||
void camera_settings_dialog::handle_qt_camera_change(const QVariant& item_data)
|
||||
{
|
||||
if (!item_data.canConvert<QCameraDevice>())
|
||||
{
|
||||
ui->combo_settings->clear();
|
||||
return;
|
||||
}
|
||||
|
||||
const QCameraDevice camera_info = item_data.value<QCameraDevice>();
|
||||
|
||||
if (camera_info.isNull())
|
||||
{
|
||||
|
|
@ -119,10 +353,10 @@ void camera_settings_dialog::handle_camera_change(int index)
|
|||
return;
|
||||
}
|
||||
|
||||
m_camera.reset(new QCamera(camera_info));
|
||||
m_media_capture_session.reset(new QMediaCaptureSession(nullptr));
|
||||
m_camera = std::make_unique<QCamera>(camera_info);
|
||||
m_media_capture_session = std::make_unique<QMediaCaptureSession>(nullptr);
|
||||
m_media_capture_session->setCamera(m_camera.get());
|
||||
m_media_capture_session->setVideoSink(ui->videoWidget->videoSink());
|
||||
m_media_capture_session->setVideoOutput(ui->videoWidget);
|
||||
|
||||
if (!m_camera->isAvailable())
|
||||
{
|
||||
|
|
@ -173,14 +407,14 @@ void camera_settings_dialog::handle_camera_change(int index)
|
|||
int index = 0;
|
||||
bool success = false;
|
||||
const std::string key = camera_info.id().toStdString();
|
||||
cfg_camera::camera_setting cfg_setting = g_cfg_camera.get_camera_setting(key, success);
|
||||
const cfg_camera::camera_setting cfg_setting = g_cfg_camera.get_camera_setting(fmt::format("%s", camera_handler::qt), key, success);
|
||||
|
||||
if (success)
|
||||
{
|
||||
camera_log.notice("Found config entry for camera \"%s\"", key);
|
||||
|
||||
// Select matching drowdown entry
|
||||
const double epsilon = 0.001;
|
||||
// Select matching dropdown entry
|
||||
constexpr double epsilon = 0.001;
|
||||
|
||||
for (int i = 0; i < ui->combo_settings->count(); i++)
|
||||
{
|
||||
|
|
@ -202,19 +436,10 @@ void camera_settings_dialog::handle_camera_change(int index)
|
|||
|
||||
ui->combo_settings->setCurrentIndex(std::max<int>(0, index));
|
||||
ui->combo_settings->setEnabled(true);
|
||||
|
||||
// Update config to match user interface outcome
|
||||
const QCameraFormat setting = ui->combo_settings->currentData().value<QCameraFormat>();
|
||||
cfg_setting.width = setting.resolution().width();
|
||||
cfg_setting.height = setting.resolution().height();
|
||||
cfg_setting.min_fps = setting.minFrameRate();
|
||||
cfg_setting.max_fps = setting.maxFrameRate();
|
||||
cfg_setting.format = static_cast<int>(setting.pixelFormat());
|
||||
g_cfg_camera.set_camera_setting(key, cfg_setting);
|
||||
}
|
||||
}
|
||||
|
||||
void camera_settings_dialog::handle_settings_change(int index)
|
||||
void camera_settings_dialog::handle_qt_settings_change(const QVariant& item_data)
|
||||
{
|
||||
if (!m_camera)
|
||||
{
|
||||
|
|
@ -227,33 +452,251 @@ void camera_settings_dialog::handle_settings_change(int index)
|
|||
return;
|
||||
}
|
||||
|
||||
if (!gui::utils::check_camera_permission(this,
|
||||
[this, index](){ handle_settings_change(index); },
|
||||
[this](){ QMessageBox::warning(this, tr("Camera permissions denied!"), tr("RPCS3 has no permissions to access cameras on this device.")); }))
|
||||
if (item_data.canConvert<QCameraFormat>() && ui->combo_camera->currentData().canConvert<QCameraDevice>())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (index >= 0 && ui->combo_settings->itemData(index).canConvert<QCameraFormat>() && ui->combo_camera->currentData().canConvert<QCameraDevice>())
|
||||
{
|
||||
const QCameraFormat setting = ui->combo_settings->itemData(index).value<QCameraFormat>();
|
||||
const QCameraFormat setting = item_data.value<QCameraFormat>();
|
||||
if (!setting.isNull())
|
||||
{
|
||||
m_camera->setCameraFormat(setting);
|
||||
}
|
||||
|
||||
cfg_camera::camera_setting cfg_setting;
|
||||
cfg_camera::camera_setting cfg_setting {};
|
||||
cfg_setting.width = setting.resolution().width();
|
||||
cfg_setting.height = setting.resolution().height();
|
||||
cfg_setting.min_fps = setting.minFrameRate();
|
||||
cfg_setting.max_fps = setting.maxFrameRate();
|
||||
cfg_setting.format = static_cast<int>(setting.pixelFormat());
|
||||
g_cfg_camera.set_camera_setting(ui->combo_camera->currentData().value<QCameraDevice>().id().toStdString(), cfg_setting);
|
||||
cfg_setting.colorspace = 0;
|
||||
g_cfg_camera.set_camera_setting(fmt::format("%s", camera_handler::qt), ui->combo_camera->currentData().value<QCameraDevice>().id().toStdString(), cfg_setting);
|
||||
}
|
||||
|
||||
m_camera->start();
|
||||
}
|
||||
|
||||
#ifdef HAVE_SDL3
|
||||
void camera_settings_dialog::handle_sdl_camera_change(const QString& name, const QVariant& item_data)
|
||||
{
|
||||
if (!item_data.canConvert<u32>())
|
||||
{
|
||||
ui->combo_settings->clear();
|
||||
return;
|
||||
}
|
||||
|
||||
const u32 camera_id = item_data.value<u32>();
|
||||
|
||||
if (!camera_id)
|
||||
{
|
||||
ui->combo_settings->clear();
|
||||
return;
|
||||
}
|
||||
|
||||
ui->combo_settings->blockSignals(true);
|
||||
ui->combo_settings->clear();
|
||||
|
||||
std::vector<SDL_CameraSpec> settings;
|
||||
|
||||
int num_formats = 0;
|
||||
if (SDL_CameraSpec** specs = SDL_GetCameraSupportedFormats(camera_id, &num_formats))
|
||||
{
|
||||
if (num_formats <= 0)
|
||||
{
|
||||
camera_log.error("No SDL camera specs found");
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < num_formats; i++)
|
||||
{
|
||||
if (!specs[i]) continue;
|
||||
settings.push_back(*specs[i]);
|
||||
}
|
||||
}
|
||||
SDL_free(specs);
|
||||
}
|
||||
else
|
||||
{
|
||||
camera_log.error("No SDL camera specs found. SDL Error: %s", SDL_GetError());
|
||||
}
|
||||
|
||||
std::sort(settings.begin(), settings.end(), [](const SDL_CameraSpec& l, const SDL_CameraSpec& r) -> bool
|
||||
{
|
||||
const f32 l_fps = l.framerate_numerator / static_cast<f32>(l.framerate_denominator);
|
||||
const f32 r_fps = r.framerate_numerator / static_cast<f32>(r.framerate_denominator);
|
||||
|
||||
if (l.width > r.width) return true;
|
||||
if (l.width < r.width) return false;
|
||||
if (l.height > r.height) return true;
|
||||
if (l.height < r.height) return false;
|
||||
if (l_fps > r_fps) return true;
|
||||
if (l_fps < r_fps) return false;
|
||||
if (l.format > r.format) return true;
|
||||
if (l.format < r.format) return false;
|
||||
if (l.colorspace > r.colorspace) return true;
|
||||
if (l.colorspace < r.colorspace) return false;
|
||||
return false;
|
||||
});
|
||||
|
||||
for (const SDL_CameraSpec& setting : settings)
|
||||
{
|
||||
const f32 fps = setting.framerate_numerator / static_cast<f32>(setting.framerate_denominator);
|
||||
const QString description = tr("%0x%1, %2 FPS, Format=%3")
|
||||
.arg(setting.width)
|
||||
.arg(setting.height)
|
||||
.arg(fps)
|
||||
.arg(sdl_pixelformat_to_string(setting.format));
|
||||
ui->combo_settings->addItem(description, QVariant::fromValue(setting));
|
||||
}
|
||||
ui->combo_settings->blockSignals(false);
|
||||
|
||||
if (ui->combo_settings->count() == 0)
|
||||
{
|
||||
ui->combo_settings->setEnabled(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Load selected settings from config file
|
||||
int index = 0;
|
||||
bool success = false;
|
||||
cfg_camera::camera_setting cfg_setting = g_cfg_camera.get_camera_setting(fmt::format("%s", camera_handler::sdl), name.toStdString(), success);
|
||||
|
||||
if (success)
|
||||
{
|
||||
camera_log.notice("Found config entry for camera \"%s\"", name);
|
||||
|
||||
// Select matching dropdown entry
|
||||
constexpr double epsilon = 0.001;
|
||||
|
||||
for (int i = 0; i < ui->combo_settings->count(); i++)
|
||||
{
|
||||
const QVariant var = ui->combo_settings->itemData(i);
|
||||
if (!var.canConvert<SDL_CameraSpec>())
|
||||
{
|
||||
camera_log.error("Failed to convert itemData to SDL_CameraSpec");
|
||||
continue;
|
||||
}
|
||||
|
||||
const SDL_CameraSpec tmp = var.value<SDL_CameraSpec>();
|
||||
const f32 fps = tmp.framerate_numerator / static_cast<f32>(tmp.framerate_denominator);
|
||||
|
||||
if (tmp.width == cfg_setting.width &&
|
||||
tmp.height == cfg_setting.height &&
|
||||
fps >= (cfg_setting.min_fps - epsilon) &&
|
||||
fps <= (cfg_setting.min_fps + epsilon) &&
|
||||
fps >= (cfg_setting.max_fps - epsilon) &&
|
||||
fps <= (cfg_setting.max_fps + epsilon) &&
|
||||
tmp.format == static_cast<SDL_PixelFormat>(cfg_setting.format) &&
|
||||
tmp.colorspace == static_cast<SDL_Colorspace>(cfg_setting.colorspace))
|
||||
{
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_sdl_camera_id = camera_id;
|
||||
|
||||
ui->combo_settings->setCurrentIndex(std::max<int>(0, index));
|
||||
ui->combo_settings->setEnabled(true);
|
||||
}
|
||||
|
||||
void camera_settings_dialog::handle_sdl_settings_change(const QVariant& item_data)
|
||||
{
|
||||
reset_cameras();
|
||||
|
||||
if (item_data.canConvert<SDL_CameraSpec>())
|
||||
{
|
||||
// TODO: SDL converts the image for us. We would have to do this manually if we want to use other formats.
|
||||
const SDL_CameraSpec setting = item_data.value<SDL_CameraSpec>();
|
||||
const SDL_CameraSpec used_spec
|
||||
{
|
||||
.format = SDL_PixelFormat::SDL_PIXELFORMAT_RGBA32,
|
||||
.colorspace = SDL_Colorspace::SDL_COLORSPACE_RGB_DEFAULT,
|
||||
.width = setting.width,
|
||||
.height = setting.height,
|
||||
.framerate_numerator = setting.framerate_numerator,
|
||||
.framerate_denominator = setting.framerate_denominator
|
||||
};
|
||||
|
||||
m_sdl_camera = SDL_OpenCamera(m_sdl_camera_id, &used_spec);
|
||||
|
||||
m_video_frame_input = std::make_unique<QVideoFrameInput>();
|
||||
|
||||
m_media_capture_session = std::make_unique<QMediaCaptureSession>(nullptr);
|
||||
m_media_capture_session->setVideoFrameInput(m_video_frame_input.get());
|
||||
m_media_capture_session->setVideoOutput(ui->videoWidget);
|
||||
|
||||
connect(this, &camera_settings_dialog::sdl_frame_ready, m_video_frame_input.get(), [this]()
|
||||
{
|
||||
// It was observed that connecting sendVideoFrame directly can soft-lock the software.
|
||||
// So let's just create the video frame here and call it manually.
|
||||
std::unique_lock lock(m_sdl_image_mutex, std::defer_lock);
|
||||
if (lock.try_lock() && m_video_frame_input && !m_sdl_image.isNull())
|
||||
{
|
||||
const QVideoFrame video_frame(m_sdl_image);
|
||||
if (video_frame.isValid())
|
||||
{
|
||||
m_video_frame_input->sendVideoFrame(video_frame);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const f32 fps = setting.framerate_numerator / static_cast<f32>(setting.framerate_denominator);
|
||||
|
||||
cfg_camera::camera_setting cfg_setting {};
|
||||
cfg_setting.width = setting.width;
|
||||
cfg_setting.height = setting.height;
|
||||
cfg_setting.min_fps = fps;
|
||||
cfg_setting.max_fps = fps;
|
||||
cfg_setting.format = static_cast<int>(setting.format);
|
||||
cfg_setting.colorspace = static_cast<int>(setting.colorspace);
|
||||
g_cfg_camera.set_camera_setting(fmt::format("%s", camera_handler::sdl), ui->combo_camera->currentText().toStdString(), cfg_setting);
|
||||
}
|
||||
|
||||
if (!m_sdl_camera)
|
||||
{
|
||||
camera_log.error("Failed to open SDL camera %d. SDL Error: %s", m_sdl_camera_id, SDL_GetError());
|
||||
QMessageBox::warning(this, tr("Camera not available"), tr("The selected camera is not available.\nIt might be blocked by another application."));
|
||||
return;
|
||||
}
|
||||
|
||||
m_sdl_thread = std::make_unique<named_thread<std::function<void()>>>("GUI SDL Capture Thread", [this](){ run_sdl(); });
|
||||
}
|
||||
|
||||
void camera_settings_dialog::run_sdl()
|
||||
{
|
||||
camera_log.notice("GUI SDL Capture Thread started");
|
||||
|
||||
while (thread_ctrl::state() != thread_state::aborting)
|
||||
{
|
||||
// Copy latest image into out buffer.
|
||||
u64 timestamp_ns = 0;
|
||||
SDL_Surface* frame = SDL_AcquireCameraFrame(m_sdl_camera, ×tamp_ns);
|
||||
if (!frame)
|
||||
{
|
||||
// No new frame
|
||||
thread_ctrl::wait_for(1000);
|
||||
continue;
|
||||
}
|
||||
|
||||
{
|
||||
// Map image
|
||||
const QImage::Format format = SDL_ISPIXELFORMAT_ALPHA(frame->format) ? QImage::Format_RGBA8888 : QImage::Format_RGB888;
|
||||
const QImage image = QImage(reinterpret_cast<const u8*>(frame->pixels), frame->w, frame->h, format);
|
||||
|
||||
// Copy image to prevent memory access violations
|
||||
{
|
||||
std::lock_guard lock(m_sdl_image_mutex);
|
||||
m_sdl_image = image.copy();
|
||||
}
|
||||
|
||||
// Notify UI
|
||||
Q_EMIT sdl_frame_ready();
|
||||
}
|
||||
|
||||
SDL_ReleaseCameraFrame(m_sdl_camera, frame);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void camera_settings_dialog::load_config()
|
||||
{
|
||||
if (!g_cfg_camera.load())
|
||||
|
|
|
|||
|
|
@ -1,8 +1,25 @@
|
|||
#pragma once
|
||||
|
||||
#include "Emu/system_config_types.h"
|
||||
#include "Utilities/Thread.h"
|
||||
|
||||
#include <QCamera>
|
||||
#include <QDialog>
|
||||
#include <QMediaCaptureSession>
|
||||
#include <QVideoFrameInput>
|
||||
|
||||
#include <mutex>
|
||||
|
||||
#ifdef HAVE_SDL3
|
||||
#ifndef _MSC_VER
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wold-style-cast"
|
||||
#endif
|
||||
#include "SDL3/SDL.h"
|
||||
#ifndef _MSC_VER
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
#endif
|
||||
|
||||
namespace Ui
|
||||
{
|
||||
|
|
@ -17,15 +34,40 @@ public:
|
|||
camera_settings_dialog(QWidget* parent = nullptr);
|
||||
virtual ~camera_settings_dialog();
|
||||
|
||||
Q_SIGNALS:
|
||||
void sdl_frame_ready();
|
||||
|
||||
private Q_SLOTS:
|
||||
void handle_handler_change(int index);
|
||||
void handle_camera_change(int index);
|
||||
void handle_settings_change(int index);
|
||||
|
||||
private:
|
||||
void enable_combos();
|
||||
void reset_cameras();
|
||||
|
||||
void load_config();
|
||||
void save_config();
|
||||
|
||||
void handle_qt_camera_change(const QVariant& item_data);
|
||||
void handle_qt_settings_change(const QVariant& item_data);
|
||||
|
||||
#ifdef HAVE_SDL3
|
||||
void handle_sdl_camera_change(const QString& name, const QVariant& item_data);
|
||||
void handle_sdl_settings_change(const QVariant& item_data);
|
||||
|
||||
void run_sdl();
|
||||
|
||||
SDL_Camera* m_sdl_camera = nullptr;
|
||||
SDL_CameraID m_sdl_camera_id = 0;
|
||||
QImage m_sdl_image;
|
||||
std::mutex m_sdl_image_mutex;
|
||||
std::unique_ptr<named_thread<std::function<void()>>> m_sdl_thread;
|
||||
std::unique_ptr<QVideoFrameInput> m_video_frame_input;
|
||||
#endif
|
||||
|
||||
std::unique_ptr<Ui::camera_settings_dialog> ui;
|
||||
std::unique_ptr<QCamera> m_camera;
|
||||
std::unique_ptr<QMediaCaptureSession> m_media_capture_session;
|
||||
camera_handler m_handler = camera_handler::qt;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -15,7 +15,23 @@
|
|||
</property>
|
||||
<layout class="QVBoxLayout" name="mainLayout" stretch="0,1,0">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="settingsLayout" stretch="1,2">
|
||||
<layout class="QHBoxLayout" name="settingsLayout" stretch="0,1,2">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="gbHandler">
|
||||
<property name="title">
|
||||
<string>Handler</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="handler_layout">
|
||||
<item>
|
||||
<widget class="QComboBox" name="combo_handlers">
|
||||
<property name="placeholderText">
|
||||
<string>No handlers found</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="gbCamera">
|
||||
<property name="title">
|
||||
|
|
@ -75,10 +91,10 @@
|
|||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
<enum>Qt::Orientation::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Save</set>
|
||||
<set>QDialogButtonBox::StandardButton::Apply|QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Save</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
|
|
|||
|
|
@ -129,16 +129,27 @@ void cheat_engine::save() const
|
|||
cheat_file.write(out.c_str(), out.size());
|
||||
}
|
||||
|
||||
void cheat_engine::import_cheats_from_str(std::string_view str_cheats)
|
||||
bool cheat_engine::import_cheats_from_str(std::string_view str_cheats)
|
||||
{
|
||||
const auto cheats_vec = fmt::split_sv(str_cheats, {"^^^"});
|
||||
|
||||
std::vector<cheat_info> valid_cheats;
|
||||
|
||||
for (const auto& cheat_line : cheats_vec)
|
||||
{
|
||||
cheat_info new_cheat;
|
||||
if (new_cheat.from_str(cheat_line))
|
||||
cheats[new_cheat.game][new_cheat.offset] = new_cheat;
|
||||
if (!new_cheat.from_str(cheat_line))
|
||||
return false;
|
||||
|
||||
valid_cheats.push_back(std::move(new_cheat));
|
||||
}
|
||||
|
||||
for (const cheat_info& new_cheat : valid_cheats)
|
||||
{
|
||||
cheats[new_cheat.game][new_cheat.offset] = new_cheat;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string cheat_engine::export_cheats_to_str() const
|
||||
|
|
@ -677,7 +688,7 @@ cheat_manager_dialog::cheat_manager_dialog(QWidget* parent)
|
|||
{
|
||||
const int row = sel->row();
|
||||
|
||||
if (rows.count(row))
|
||||
if (rows.contains(row))
|
||||
continue;
|
||||
|
||||
g_cheat.erase(tbl_cheats->item(row, cheat_table_columns::title)->text().toStdString(), tbl_cheats->item(row, cheat_table_columns::offset)->data(Qt::UserRole).toUInt());
|
||||
|
|
@ -690,7 +701,11 @@ cheat_manager_dialog::cheat_manager_dialog(QWidget* parent)
|
|||
connect(import_cheats, &QAction::triggered, [this]()
|
||||
{
|
||||
QClipboard* clipboard = QGuiApplication::clipboard();
|
||||
g_cheat.import_cheats_from_str(clipboard->text().toStdString());
|
||||
if (!g_cheat.import_cheats_from_str(clipboard->text().toStdString()))
|
||||
{
|
||||
QMessageBox::warning(this, tr("Failure"), tr("Failed to import cheats."));
|
||||
return;
|
||||
}
|
||||
update_cheat_list();
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ public:
|
|||
cheat_info* get(const std::string& game, const u32 offset);
|
||||
bool erase(const std::string& game, const u32 offset);
|
||||
|
||||
void import_cheats_from_str(std::string_view str_cheats);
|
||||
bool import_cheats_from_str(std::string_view str_cheats);
|
||||
std::string export_cheats_to_str() const;
|
||||
void save() const;
|
||||
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue