Merge branch 'master' into windows-clang

This commit is contained in:
qurious-pixel 2026-01-03 09:07:00 -08:00 committed by GitHub
commit f8453e7758
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
123 changed files with 4196 additions and 1925 deletions

View file

@ -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

View file

@ -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

View file

@ -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"

View file

@ -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

View file

@ -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

View file

@ -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"

View file

@ -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

View file

@ -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()

@ -1 +1 @@
Subproject commit 0e5e98e4ac8adae92e4f7653dd6eee17aa9c8791
Subproject commit 75c00596307bf05ba7bbc8c7022836bf52f17477

@ -1 +1 @@
Subproject commit 49363adcfaf098748d7a4c8c624ad8c45a8c3a86
Subproject commit 4e3f57d50f552841550a36eabbb3fbcecacb7750

@ -1 +1 @@
Subproject commit 7f3ae3d57459e59943a4ecfefc8f6277ec6bf540
Subproject commit a962f40bbba175e9716557a25d5d7965f134a3d3

View file

@ -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" />

View file

@ -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" />

View file

@ -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

View file

@ -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;
}

View file

@ -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>
)

View file

@ -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)

View file

@ -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;

View file

@ -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

View file

@ -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

View file

@ -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));
}

View file

@ -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

View file

@ -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)
{

View file

@ -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

View file

@ -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];
};

View file

@ -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;

View file

@ -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>>())

View file

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

View file

@ -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);
}

View file

@ -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;

View file

@ -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

View file

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

View file

@ -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

View file

@ -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;

View file

@ -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};

View file

@ -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;
}

View file

@ -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)

View file

@ -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;
}
}

View file

@ -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

View file

@ -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

View file

@ -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();

View file

@ -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,

View file

@ -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)

View file

@ -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";
}
}

View file

@ -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();

View file

@ -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"

View file

@ -38,9 +38,6 @@ void gl::init()
#ifdef __unix__
glewExperimental = true;
glewInit();
#ifdef HAVE_X11
glxewInit();
#endif
#endif
}

View file

@ -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:
{

View file

@ -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)

View file

@ -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)

View file

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

View file

@ -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 = &current_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
{

View file

@ -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);
}
}

View file

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

View file

@ -99,7 +99,6 @@ class FragmentProgramDecompiler
protected:
const RSXFragmentProgram &m_prog;
u32 m_ctrl = 0;
/** returns the type name of float vectors.
*/

View file

@ -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();

View file

@ -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
)"

View file

@ -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)

View file

@ -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)
{

View file

@ -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)

View file

@ -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))
{

View file

@ -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

View file

@ -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

View file

@ -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
};
};

View file

@ -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)

View file

@ -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 = {};

View file

@ -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

View file

@ -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"))
{

View file

@ -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);
}

View file

@ -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"

View file

@ -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;

View file

@ -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

View file

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

View file

@ -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)

View file

@ -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};

View file

@ -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;

View file

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View 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());
}

View 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;
};

View 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

View 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

View 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, &timestamp_ns);
if (!frame)
{
// No new frame
std::this_thread::sleep_for(100us);
continue;
}
present(frame);
SDL_ReleaseCameraFrame(m_camera, frame);
}
}
#endif

View 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

View file

@ -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;

View file

@ -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" />

View file

@ -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">

View file

@ -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());
}

View file

@ -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;
}

View file

@ -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>

View file

@ -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>

View file

@ -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">

View file

@ -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;
}

View file

@ -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

View file

@ -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, &timestamp_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())

View file

@ -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;
};

View file

@ -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>

View file

@ -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();
});

View file

@ -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