Merge branch 'master' into windows-clang

This commit is contained in:
qurious-pixel 2026-02-22 09:15:47 -08:00 committed by GitHub
commit 3e902af71b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
111 changed files with 1501 additions and 488 deletions

View file

@ -1,7 +1,7 @@
#!/bin/sh -ex
# Resource/dependency URLs
CCACHE_URL="https://github.com/ccache/ccache/releases/download/v4.11.2/ccache-4.11.2-windows-x86_64.zip"
CCACHE_URL="https://github.com/ccache/ccache/releases/download/v4.12.3/ccache-4.12.3-windows-x86_64.zip"
DEP_URLS=" \
$CCACHE_URL"

View file

@ -17,7 +17,7 @@ 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"
CCACHE_URL="https://github.com/ccache/ccache/releases/download/v4.12.3/ccache-4.12.3-windows-x86_64.zip"
DEP_URLS=" \
$QT_BASE_URL \

View file

@ -20,7 +20,7 @@ jobs:
runs-on: windows-2025
env:
COMPILER: msvc
CCACHE_SHA: '1f39f3ad5aae3fe915e99ad1302633bc8f6718e58fa7c0de2b0ba7e080f0f08c'
CCACHE_SHA: '859141059ac950e1e8cd042c66f842f26b9e3a62a1669a69fe6ba180cb58bbdf'
CCACHE_BIN_DIR: 'C:\ccache_bin'
CCACHE_DIR: 'C:\ccache'
CCACHE_INODECACHE: 'true'

View file

@ -33,23 +33,23 @@ jobs:
matrix:
include:
- os: ubuntu-24.04
docker_img: "rpcs3/rpcs3-ci-jammy:1.7"
docker_img: "rpcs3/rpcs3-ci-jammy:1.8"
build_sh: "/rpcs3/.ci/build-linux.sh"
compiler: clang
UPLOAD_COMMIT_HASH: d812f1254a1157c80fd402f94446310560f54e5f
UPLOAD_REPO_FULL_NAME: "rpcs3/rpcs3-binaries-linux"
- os: ubuntu-24.04
docker_img: "rpcs3/rpcs3-ci-jammy:1.7"
docker_img: "rpcs3/rpcs3-ci-jammy:1.8"
build_sh: "/rpcs3/.ci/build-linux.sh"
compiler: gcc
- os: ubuntu-24.04-arm
docker_img: "rpcs3/rpcs3-ci-jammy-aarch64:1.7"
docker_img: "rpcs3/rpcs3-ci-jammy-aarch64:1.8"
build_sh: "/rpcs3/.ci/build-linux-aarch64.sh"
compiler: clang
UPLOAD_COMMIT_HASH: a1d35836e8d45bfc6f63c26f0a3e5d46ef622fe1
UPLOAD_REPO_FULL_NAME: "rpcs3/rpcs3-binaries-linux-arm64"
- os: ubuntu-24.04-arm
docker_img: "rpcs3/rpcs3-ci-jammy-aarch64:1.7"
docker_img: "rpcs3/rpcs3-ci-jammy-aarch64:1.8"
build_sh: "/rpcs3/.ci/build-linux-aarch64.sh"
compiler: gcc
name: RPCS3 Linux ${{ matrix.os }} ${{ matrix.compiler }}
@ -137,7 +137,7 @@ jobs:
runs-on: macos-14
env:
CCACHE_DIR: /tmp/ccache_dir
QT_VER: '6.10.1'
QT_VER: '6.10.2'
QT_VER_MAIN: '6'
LLVM_COMPILER_VER: '21'
RELEASE_MESSAGE: ../GitHubReleaseMessage.txt
@ -216,13 +216,13 @@ jobs:
env:
COMPILER: msvc
QT_VER_MAIN: '6'
QT_VER: '6.10.1'
QT_VER: '6.10.2'
QT_VER_MSVC: 'msvc2022'
QT_DATE: '202511161843'
QT_DATE: '202601261212'
LLVM_VER: '19.1.7'
VULKAN_VER: '1.3.268.0'
VULKAN_SDK_SHA: '8459ef49bd06b697115ddd3d97c9aec729e849cd775f5be70897718a9b3b9db5'
CCACHE_SHA: '1f39f3ad5aae3fe915e99ad1302633bc8f6718e58fa7c0de2b0ba7e080f0f08c'
CCACHE_SHA: '859141059ac950e1e8cd042c66f842f26b9e3a62a1669a69fe6ba180cb58bbdf'
CCACHE_BIN_DIR: 'C:\ccache_bin'
CCACHE_DIR: 'C:\ccache'
CCACHE_INODECACHE: 'true'

2
3rdparty/7zip/7zip vendored

@ -1 +1 @@
Subproject commit 5e96a8279489832924056b1fa82f29d5837c9469
Subproject commit 839151eaaad24771892afaae6bac690e31e58384

2
3rdparty/FAudio vendored

@ -1 +1 @@
Subproject commit 633bdb772a593104414b4b103ec752567d57c3c1
Subproject commit e67d761ead486de3e69fa11705456bf94df734ca

125
3rdparty/GL/glext.h vendored
View file

@ -6,7 +6,7 @@ extern "C" {
#endif
/*
** Copyright 2013-2020 The Khronos Group Inc.
** Copyright 2013-2026 The Khronos Group Inc.
** SPDX-License-Identifier: MIT
**
** This header is generated from the Khronos OpenGL / OpenGL ES XML
@ -32,7 +32,7 @@ extern "C" {
#define GLAPI extern
#endif
#define GL_GLEXT_VERSION 20250203
#define GL_GLEXT_VERSION 20260126
#include <KHR/khrplatform.h>
@ -7358,6 +7358,47 @@ GLAPI void APIENTRY glFogCoordPointerEXT (GLenum type, GLsizei stride, const voi
#endif
#endif /* GL_EXT_fog_coord */
#ifndef GL_EXT_fragment_shading_rate
#define GL_EXT_fragment_shading_rate 1
#define GL_SHADING_RATE_1X1_PIXELS_EXT 0x96A6
#define GL_SHADING_RATE_1X2_PIXELS_EXT 0x96A7
#define GL_SHADING_RATE_2X1_PIXELS_EXT 0x96A8
#define GL_SHADING_RATE_2X2_PIXELS_EXT 0x96A9
#define GL_SHADING_RATE_1X4_PIXELS_EXT 0x96AA
#define GL_SHADING_RATE_4X1_PIXELS_EXT 0x96AB
#define GL_SHADING_RATE_4X2_PIXELS_EXT 0x96AC
#define GL_SHADING_RATE_2X4_PIXELS_EXT 0x96AD
#define GL_SHADING_RATE_4X4_PIXELS_EXT 0x96AE
#define GL_SHADING_RATE_EXT 0x96D0
#define GL_SHADING_RATE_ATTACHMENT_EXT 0x96D1
#define GL_FRAGMENT_SHADING_RATE_COMBINER_OP_KEEP_EXT 0x96D2
#define GL_FRAGMENT_SHADING_RATE_COMBINER_OP_REPLACE_EXT 0x96D3
#define GL_FRAGMENT_SHADING_RATE_COMBINER_OP_MIN_EXT 0x96D4
#define GL_FRAGMENT_SHADING_RATE_COMBINER_OP_MAX_EXT 0x96D5
#define GL_FRAGMENT_SHADING_RATE_COMBINER_OP_MUL_EXT 0x96D6
#define GL_MIN_FRAGMENT_SHADING_RATE_ATTACHMENT_TEXEL_WIDTH_EXT 0x96D7
#define GL_MAX_FRAGMENT_SHADING_RATE_ATTACHMENT_TEXEL_WIDTH_EXT 0x96D8
#define GL_MIN_FRAGMENT_SHADING_RATE_ATTACHMENT_TEXEL_HEIGHT_EXT 0x96D9
#define GL_MAX_FRAGMENT_SHADING_RATE_ATTACHMENT_TEXEL_HEIGHT_EXT 0x96DA
#define GL_MAX_FRAGMENT_SHADING_RATE_ATTACHMENT_TEXEL_ASPECT_RATIO_EXT 0x96DB
#define GL_MAX_FRAGMENT_SHADING_RATE_ATTACHMENT_LAYERS_EXT 0x96DC
#define GL_FRAGMENT_SHADING_RATE_WITH_SHADER_DEPTH_STENCIL_WRITES_SUPPORTED_EXT 0x96DD
#define GL_FRAGMENT_SHADING_RATE_WITH_SAMPLE_MASK_SUPPORTED_EXT 0x96DE
#define GL_FRAGMENT_SHADING_RATE_ATTACHMENT_WITH_DEFAULT_FRAMEBUFFER_SUPPORTED_EXT 0x96DF
#define GL_FRAGMENT_SHADING_RATE_NON_TRIVIAL_COMBINERS_SUPPORTED_EXT 0x8F6F
#define GL_FRAGMENT_SHADING_RATE_PRIMITIVE_RATE_WITH_MULTI_VIEWPORT_SUPPORTED_EXT 0x9780
typedef void (APIENTRYP PFNGLGETFRAGMENTSHADINGRATESEXTPROC) (GLsizei samples, GLsizei maxCount, GLsizei *count, GLenum *shadingRates);
typedef void (APIENTRYP PFNGLSHADINGRATEEXTPROC) (GLenum rate);
typedef void (APIENTRYP PFNGLSHADINGRATECOMBINEROPSEXTPROC) (GLenum combinerOp0, GLenum combinerOp1);
typedef void (APIENTRYP PFNGLFRAMEBUFFERSHADINGRATEEXTPROC) (GLenum target, GLenum attachment, GLuint texture, GLint baseLayer, GLsizei numLayers, GLsizei texelWidth, GLsizei texelHeight);
#ifdef GL_GLEXT_PROTOTYPES
GLAPI void APIENTRY glGetFragmentShadingRatesEXT (GLsizei samples, GLsizei maxCount, GLsizei *count, GLenum *shadingRates);
GLAPI void APIENTRY glShadingRateEXT (GLenum rate);
GLAPI void APIENTRY glShadingRateCombinerOpsEXT (GLenum combinerOp0, GLenum combinerOp1);
GLAPI void APIENTRY glFramebufferShadingRateEXT (GLenum target, GLenum attachment, GLuint texture, GLint baseLayer, GLsizei numLayers, GLsizei texelWidth, GLsizei texelHeight);
#endif
#endif /* GL_EXT_fragment_shading_rate */
#ifndef GL_EXT_framebuffer_blit
#define GL_EXT_framebuffer_blit 1
#define GL_READ_FRAMEBUFFER_EXT 0x8CA8
@ -7816,6 +7857,86 @@ GLAPI void APIENTRY glImportMemoryWin32NameEXT (GLuint memory, GLuint64 size, GL
#endif
#endif /* GL_EXT_memory_object_win32 */
#ifndef GL_EXT_mesh_shader
#define GL_EXT_mesh_shader 1
#define GL_MESH_SHADER_EXT 0x9559
#define GL_TASK_SHADER_EXT 0x955A
#define GL_MAX_MESH_UNIFORM_BLOCKS_EXT 0x8E60
#define GL_MAX_MESH_TEXTURE_IMAGE_UNITS_EXT 0x8E61
#define GL_MAX_MESH_IMAGE_UNIFORMS_EXT 0x8E62
#define GL_MAX_MESH_UNIFORM_COMPONENTS_EXT 0x8E63
#define GL_MAX_MESH_ATOMIC_COUNTER_BUFFERS_EXT 0x8E64
#define GL_MAX_MESH_ATOMIC_COUNTERS_EXT 0x8E65
#define GL_MAX_MESH_SHADER_STORAGE_BLOCKS_EXT 0x8E66
#define GL_MAX_COMBINED_MESH_UNIFORM_COMPONENTS_EXT 0x8E67
#define GL_MAX_TASK_UNIFORM_BLOCKS_EXT 0x8E68
#define GL_MAX_TASK_TEXTURE_IMAGE_UNITS_EXT 0x8E69
#define GL_MAX_TASK_IMAGE_UNIFORMS_EXT 0x8E6A
#define GL_MAX_TASK_UNIFORM_COMPONENTS_EXT 0x8E6B
#define GL_MAX_TASK_ATOMIC_COUNTER_BUFFERS_EXT 0x8E6C
#define GL_MAX_TASK_ATOMIC_COUNTERS_EXT 0x8E6D
#define GL_MAX_TASK_SHADER_STORAGE_BLOCKS_EXT 0x8E6E
#define GL_MAX_COMBINED_TASK_UNIFORM_COMPONENTS_EXT 0x8E6F
#define GL_MAX_TASK_WORK_GROUP_TOTAL_COUNT_EXT 0x9740
#define GL_MAX_MESH_WORK_GROUP_TOTAL_COUNT_EXT 0x9741
#define GL_MAX_MESH_WORK_GROUP_INVOCATIONS_EXT 0x9757
#define GL_MAX_TASK_WORK_GROUP_INVOCATIONS_EXT 0x9759
#define GL_MAX_TASK_PAYLOAD_SIZE_EXT 0x9742
#define GL_MAX_TASK_SHARED_MEMORY_SIZE_EXT 0x9743
#define GL_MAX_MESH_SHARED_MEMORY_SIZE_EXT 0x9744
#define GL_MAX_TASK_PAYLOAD_AND_SHARED_MEMORY_SIZE_EXT 0x9745
#define GL_MAX_MESH_PAYLOAD_AND_SHARED_MEMORY_SIZE_EXT 0x9746
#define GL_MAX_MESH_OUTPUT_MEMORY_SIZE_EXT 0x9747
#define GL_MAX_MESH_PAYLOAD_AND_OUTPUT_MEMORY_SIZE_EXT 0x9748
#define GL_MAX_MESH_OUTPUT_VERTICES_EXT 0x9538
#define GL_MAX_MESH_OUTPUT_PRIMITIVES_EXT 0x9756
#define GL_MAX_MESH_OUTPUT_COMPONENTS_EXT 0x9749
#define GL_MAX_MESH_OUTPUT_LAYERS_EXT 0x974A
#define GL_MAX_MESH_MULTIVIEW_VIEW_COUNT_EXT 0x9557
#define GL_MESH_OUTPUT_PER_VERTEX_GRANULARITY_EXT 0x92DF
#define GL_MESH_OUTPUT_PER_PRIMITIVE_GRANULARITY_EXT 0x9543
#define GL_MAX_PREFERRED_TASK_WORK_GROUP_INVOCATIONS_EXT 0x974B
#define GL_MAX_PREFERRED_MESH_WORK_GROUP_INVOCATIONS_EXT 0x974C
#define GL_MESH_PREFERS_LOCAL_INVOCATION_VERTEX_OUTPUT_EXT 0x974D
#define GL_MESH_PREFERS_LOCAL_INVOCATION_PRIMITIVE_OUTPUT_EXT 0x974E
#define GL_MESH_PREFERS_COMPACT_VERTEX_OUTPUT_EXT 0x974F
#define GL_MESH_PREFERS_COMPACT_PRIMITIVE_OUTPUT_EXT 0x9750
#define GL_MAX_TASK_WORK_GROUP_COUNT_EXT 0x9751
#define GL_MAX_MESH_WORK_GROUP_COUNT_EXT 0x9752
#define GL_MAX_MESH_WORK_GROUP_SIZE_EXT 0x9758
#define GL_MAX_TASK_WORK_GROUP_SIZE_EXT 0x975A
#define GL_MESH_WORK_GROUP_SIZE_EXT 0x953E
#define GL_TASK_WORK_GROUP_SIZE_EXT 0x953F
#define GL_MESH_VERTICES_OUT_EXT 0x9579
#define GL_MESH_PRIMITIVES_OUT_EXT 0x957A
#define GL_MESH_OUTPUT_TYPE_EXT 0x957B
#define GL_UNIFORM_BLOCK_REFERENCED_BY_MESH_SHADER_EXT 0x959C
#define GL_UNIFORM_BLOCK_REFERENCED_BY_TASK_SHADER_EXT 0x959D
#define GL_REFERENCED_BY_MESH_SHADER_EXT 0x95A0
#define GL_REFERENCED_BY_TASK_SHADER_EXT 0x95A1
#define GL_TASK_SHADER_INVOCATIONS_EXT 0x9753
#define GL_MESH_SHADER_INVOCATIONS_EXT 0x9754
#define GL_MESH_PRIMITIVES_GENERATED_EXT 0x9755
#define GL_MESH_SHADER_BIT_EXT 0x00000040
#define GL_TASK_SHADER_BIT_EXT 0x00000080
#define GL_MESH_SUBROUTINE_EXT 0x957C
#define GL_TASK_SUBROUTINE_EXT 0x957D
#define GL_MESH_SUBROUTINE_UNIFORM_EXT 0x957E
#define GL_TASK_SUBROUTINE_UNIFORM_EXT 0x957F
#define GL_ATOMIC_COUNTER_BUFFER_REFERENCED_BY_MESH_SHADER_EXT 0x959E
#define GL_ATOMIC_COUNTER_BUFFER_REFERENCED_BY_TASK_SHADER_EXT 0x959F
typedef void (APIENTRYP PFNGLDRAWMESHTASKSEXTPROC) (GLuint num_groups_x, GLuint num_groups_y, GLuint num_groups_z);
typedef void (APIENTRYP PFNGLDRAWMESHTASKSINDIRECTEXTPROC) (GLintptr indirect);
typedef void (APIENTRYP PFNGLMULTIDRAWMESHTASKSINDIRECTEXTPROC) (GLintptr indirect, GLsizei drawcount, GLsizei stride);
typedef void (APIENTRYP PFNGLMULTIDRAWMESHTASKSINDIRECTCOUNTEXTPROC) (GLintptr indirect, GLintptr drawcount, GLsizei maxdrawcount, GLsizei stride);
#ifdef GL_GLEXT_PROTOTYPES
GLAPI void APIENTRY glDrawMeshTasksEXT (GLuint num_groups_x, GLuint num_groups_y, GLuint num_groups_z);
GLAPI void APIENTRY glDrawMeshTasksIndirectEXT (GLintptr indirect);
GLAPI void APIENTRY glMultiDrawMeshTasksIndirectEXT (GLintptr indirect, GLsizei drawcount, GLsizei stride);
GLAPI void APIENTRY glMultiDrawMeshTasksIndirectCountEXT (GLintptr indirect, GLintptr drawcount, GLsizei maxdrawcount, GLsizei stride);
#endif
#endif /* GL_EXT_mesh_shader */
#ifndef GL_EXT_misc_attribute
#define GL_EXT_misc_attribute 1
#endif /* GL_EXT_misc_attribute */

@ -1 +1 @@
Subproject commit e495bee4cd630c9f99907a764e16edba37a4b564
Subproject commit 484857522c73318c06f18ba0a3e17525fa98c608

@ -1 +1 @@
Subproject commit 4e3f57d50f552841550a36eabbb3fbcecacb7750
Subproject commit c3e304954a9cfd154bc0dfbfea2b01cd61d6546d

View file

@ -2,8 +2,8 @@ add_library(3rdparty_protobuf INTERFACE)
if (USE_SYSTEM_PROTOBUF)
pkg_check_modules(PROTOBUF REQUIRED IMPORTED_TARGET protobuf>=33.0.0)
target_link_libraries(3rdparty_protobuf INTERFACE PkgConfig::PROTOBUF)
set(PROTOBUF_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../rpcs3/Emu/NP/generated/")
execute_process(COMMAND protoc --cpp_out="${PROTOBUF_DIR}" --proto_path="${PROTOBUF_DIR}" np2_structs.proto RESULT_VARIABLE PROTOBUF_CMD_ERROR)
set(PROTOBUF_DIR "${CMAKE_SOURCE_DIR}/rpcs3/Emu/NP/generated")
execute_process(COMMAND protoc --cpp_out=${PROTOBUF_DIR} --proto_path=${PROTOBUF_DIR} np2_structs.proto RESULT_VARIABLE PROTOBUF_CMD_ERROR)
if(PROTOBUF_CMD_ERROR AND NOT PROTOBUF_CMD_ERROR EQUAL 0)
message(FATAL_ERROR "protoc failed to regenerate protobuf files.")
endif()

@ -1 +1 @@
Subproject commit 456c68f452da09d8ca84b375faa2b1397713eaba
Subproject commit 05c44fcd18074836e21e1eda9fc02b3a4a1529b5

View file

@ -76,6 +76,7 @@
<ClCompile Include="yaml-cpp\src\exceptions.cpp" />
<ClCompile Include="yaml-cpp\src\exp.cpp">
</ClCompile>
<ClCompile Include="yaml-cpp\src\fptostring.cpp" />
<ClCompile Include="yaml-cpp\src\memory.cpp">
</ClCompile>
<ClCompile Include="yaml-cpp\src\node.cpp">

View file

@ -94,5 +94,8 @@
<ClCompile Include="yaml-cpp\src\depthguard.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="yaml-cpp\src\fptostring.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
</Project>

View file

@ -20,26 +20,26 @@ The following tools are required to build RPCS3 on Windows 10 or later:
with standalone **CMake** tool.
- [Python 3.6+](https://www.python.org/downloads/) (add to PATH)
- [Qt 6.10.1](https://www.qt.io/download-qt-installer) In case you can't download from the official installer, you can use [Another Qt installer](https://github.com/miurahr/aqtinstall) (In that case you will need to manually add the "qtmultimedia" module when installing Qt)
- [Qt 6.10.2](https://www.qt.io/download-qt-installer) In case you can't download from the official installer, you can use [Another Qt installer](https://github.com/miurahr/aqtinstall) (In that case you will need to manually add the "qtmultimedia" module when installing Qt)
- [Vulkan SDK 1.3.268.0](https://vulkan.lunarg.com/sdk/home) (see "Install the SDK" [here](https://vulkan.lunarg.com/doc/sdk/latest/windows/getting_started.html)) for now future SDKs don't work. You need precisely 1.3.268.0.
The `sln` solution available only on **Visual Studio** is the preferred building solution. It easily allows to build the **RPCS3** application in `Release` and `Debug` mode.
In order to build **RPCS3** with the `sln` solution (with **Visual Studio**), **Qt** libs need to be detected. To detect the libs:
- add and set the `QTDIR` environment variable, e.g. `<QtInstallFolder>\6.10.1\msvc2022_64\`
- add and set the `QTDIR` environment variable, e.g. `<QtInstallFolder>\6.10.2\msvc2022_64\`
- or use the [Visual Studio Qt Plugin](https://marketplace.visualstudio.com/items?itemName=TheQtCompany.QtVisualStudioTools2022)
**NOTE:** If you have issues with the **Visual Studio Qt Plugin**, you may want to uninstall it and install the [Legacy Qt Plugin](https://marketplace.visualstudio.com/items?itemName=TheQtCompany.LEGACYQtVisualStudioTools2022) instead.
In order to build **RPCS3** with the `CMake` solution (with both **Visual Studio** and standalone **CMake** tool):
- add and set the `Qt6_ROOT` environment variable to the **Qt** libs path, e.g. `<QtInstallFolder>\6.10.1\msvc2022_64\`
- add and set the `Qt6_ROOT` environment variable to the **Qt** libs path, e.g. `<QtInstallFolder>\6.10.2\msvc2022_64\`
### Linux
These are the essentials tools to build RPCS3 on Linux. Some of them can be installed through your favorite package manager:
- Clang 17+ or GCC 13+
- [CMake 3.28.0+](https://www.cmake.org/download/)
- [Qt 6.10.1](https://www.qt.io/download-qt-installer)
- [Qt 6.10.2](https://www.qt.io/download-qt-installer)
- [Vulkan SDK 1.3.268.0](https://vulkan.lunarg.com/sdk/home) (See "Install the SDK" [here](https://vulkan.lunarg.com/doc/sdk/latest/linux/getting_started.html)) for now future SDKs don't work. You need precisely 1.3.268.0.
- [SDL3](https://github.com/libsdl-org/SDL/releases) (for the FAudio backend)
@ -95,7 +95,7 @@ sudo apt-get install cmake
#### Fedora
sudo dnf install alsa-lib-devel cmake ninja-build glew glew-devel libatomic libevdev-devel libudev-devel openal-devel qt6-qtbase-devel qt6-qtbase-private-devel vulkan-devel pipewire-jack-audio-connection-kit-devel qt6-qtmultimedia-devel qt6-qtsvg-devel llvm-devel
sudo dnf install alsa-lib-devel cmake ninja-build glew glew-devel libatomic libevdev-devel libudev-devel openal-soft-devel qt6-qtbase-devel qt6-qtbase-private-devel vulkan-devel pipewire-jack-audio-connection-kit-devel qt6-qtmultimedia-devel qt6-qtsvg-devel llvm-devel libcurl-devel
#### OpenSUSE
@ -123,7 +123,7 @@ Start **Visual Studio**, click on `Open a project or solution` and select the `r
##### Configuring the Qt Plugin (if used)
1) go to `Extensions->Qt VS Tools->Qt Versions`
2) add the path to your Qt installation with compiler e.g. `<QtInstallFolder>\6.10.1\msvc2022_64`, version will fill in automatically
2) add the path to your Qt installation with compiler e.g. `<QtInstallFolder>\6.10.2\msvc2022_64`, version will fill in automatically
3) go to `Extensions->Qt VS Tools->Options->Legacy Project Format`. (Only available in the **Legacy Qt Plugin**)
4) set `Build: Run pre-build setup` to `true`. (Only available in the **Legacy Qt Plugin**)

View file

@ -13,12 +13,12 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS 11)
message(FATAL_ERROR "RPCS3 requires at least gcc-11.")
if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS 13)
message(FATAL_ERROR "RPCS3 requires at least gcc-13.")
endif()
elseif(CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS 12.0)
message(FATAL_ERROR "RPCS3 requires at least clang-12.0.")
if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS 19.0)
message(FATAL_ERROR "RPCS3 requires at least clang-19.0.")
endif()
endif()

View file

@ -398,12 +398,11 @@ namespace fs
class windows_file final : public file_base
{
HANDLE m_handle;
atomic_t<u64> m_pos;
atomic_t<u64> m_pos {0};
public:
windows_file(HANDLE handle)
: m_handle(handle)
, m_pos(0)
{
}
@ -417,10 +416,10 @@ namespace fs
stat_t get_stat() override
{
FILE_BASIC_INFO basic_info;
FILE_BASIC_INFO basic_info {};
ensure(GetFileInformationByHandleEx(m_handle, FileBasicInfo, &basic_info, sizeof(FILE_BASIC_INFO))); // "file::stat"
stat_t info;
stat_t info {};
info.is_directory = (basic_info.FileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
info.is_writable = (basic_info.FileAttributes & FILE_ATTRIBUTE_READONLY) == 0;
info.size = this->size();
@ -441,7 +440,7 @@ namespace fs
bool trunc(u64 length) override
{
FILE_END_OF_FILE_INFO _eof;
FILE_END_OF_FILE_INFO _eof {};
_eof.EndOfFile.QuadPart = length;
if (!SetFileInformationByHandle(m_handle, FileEndOfFileInfo, &_eof, sizeof(_eof)))
@ -563,6 +562,7 @@ namespace fs
u64 size() override
{
// NOTE: this can fail if we access a mounted empty drive (e.g. after unmounting an iso).
LARGE_INTEGER size;
ensure(GetFileSizeEx(m_handle, &size)); // "file::size"
@ -579,7 +579,7 @@ namespace fs
file_id id{"windows_file"};
id.data.resize(sizeof(FILE_ID_INFO));
FILE_ID_INFO info;
FILE_ID_INFO info {};
if (!GetFileInformationByHandleEx(m_handle, FileIdInfo, &info, sizeof(info)))
{
@ -625,7 +625,7 @@ namespace fs
struct ::stat file_info;
ensure(::fstat(m_fd, &file_info) == 0); // "file::stat"
stat_t info;
stat_t info {};
info.is_directory = S_ISDIR(file_info.st_mode);
info.is_writable = file_info.st_mode & 0200; // HACK: approximation
info.size = file_info.st_size;
@ -1656,6 +1656,16 @@ fs::file::file(const std::string& path, bs_t<open_mode> mode)
return;
}
// Check if the handle is actually valid.
// This can fail on empty mounted drives (e.g. with ERROR_NOT_READY or ERROR_INVALID_FUNCTION).
BY_HANDLE_FILE_INFORMATION info;
if (!GetFileInformationByHandle(handle, &info))
{
CloseHandle(handle);
g_tls_error = to_error(GetLastError());
return;
}
m_file = std::make_unique<windows_file>(handle);
#else
int flags = O_CLOEXEC; // Ensures all files are closed on execl for auto updater

View file

@ -66,13 +66,13 @@ namespace fs
// File attributes (TODO)
struct stat_t
{
bool is_directory;
bool is_symlink;
bool is_writable;
u64 size;
s64 atime;
s64 mtime;
s64 ctime;
bool is_directory = false;
bool is_symlink = false;
bool is_writable = false;
u64 size = 0;
s64 atime = 0;
s64 mtime = 0;
s64 ctime = 0;
using enable_bitcopy = std::true_type;

View file

@ -14,6 +14,10 @@
#define CAN_OVERCOMMIT
#endif
#if defined(__APPLE__)
#include <mutex>
#endif
LOG_CHANNEL(jit_log, "JIT");
void jit_announce(uptr func, usz size, std::string_view name)

View file

@ -688,6 +688,30 @@ jit_compiler::jit_compiler(const std::unordered_map<std::string, u64>& _link, co
mem = std::make_unique<MemoryManager1>(std::move(symbols_cement));
}
std::vector<std::string> attributes;
#if defined(ARCH_ARM64)
if (utils::has_sha3())
attributes.push_back("+sha3");
else
attributes.push_back("-sha3");
if (utils::has_dotprod())
attributes.push_back("+dotprod");
else
attributes.push_back("-dotprod");
if (utils::has_sve())
attributes.push_back("+sve");
else
attributes.push_back("-sve");
if (utils::has_sve2())
attributes.push_back("+sve2");
else
attributes.push_back("-sve2");
#endif
{
m_engine.reset(llvm::EngineBuilder(std::move(null_mod))
.setErrorStr(&result)
@ -699,6 +723,7 @@ jit_compiler::jit_compiler(const std::unordered_map<std::string, u64>& _link, co
//.setCodeModel(llvm::CodeModel::Large)
#endif
.setRelocationModel(llvm::Reloc::Model::PIC_)
.setMAttrs(attributes)
.setMCPU(m_cpu)
.create());
}

View file

@ -8,7 +8,6 @@
#include "Emu/RSX/RSXThread.h"
#include "Thread.h"
#include "Utilities/JIT.h"
#include <thread>
#include <cfenv>
#ifdef ARCH_ARM64

View file

@ -4,6 +4,7 @@
#include "util/atomic.hpp"
#include "util/shared_ptr.hpp"
#include <thread>
#include <string>
// Hardware core layout

View file

@ -13,7 +13,7 @@
<ItemDefinitionGroup>
<ClCompile>
<LanguageStandard Condition = "'$(VisualStudioVersion.Substring(0,2))'&lt;'17'">stdcpplatest</LanguageStandard>
<LanguageStandard Condition = "'$(VisualStudioVersion.Substring(0,2))'&gt;='17'">stdcpp20</LanguageStandard>
<LanguageStandard Condition = "'$(VisualStudioVersion.Substring(0,2))'&gt;='17'">stdcpp23</LanguageStandard>
<PreprocessorDefinitions>_SILENCE_CXX17_ITERATOR_BASE_CLASS_DEPRECATION_WARNING=1;_HAS_EXCEPTIONS=0;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ExceptionHandling>false</ExceptionHandling>
<AdditionalOptions>-d2FH4- %(AdditionalOptions)</AdditionalOptions>

View file

@ -8,7 +8,7 @@ include(${CMAKE_CURRENT_SOURCE_DIR}/git-version.cmake)
include(ConfigureCompiler)
include(CheckFunctionExists)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD 23)
if(UNIX AND NOT APPLE AND NOT ANDROID)
add_compile_definitions(DATADIR="${CMAKE_INSTALL_FULL_DATADIR}/rpcs3")

View file

@ -634,7 +634,7 @@ u32 microphone_device::capture_audio()
if (ALCenum err = alcGetError(micdevice.device); err != ALC_NO_ERROR)
{
cellMic.error("Error getting number of captured samples of device '%s' (error=%s)", micdevice.name, fmt::alc_error{micdevice.device, err});
return CELL_MICIN_ERROR_FATAL;
return 0;
}
num_samples = std::min<u32>(num_samples, samples_in);

View file

@ -238,7 +238,7 @@ public:
if (over_size > Size)
{
m_tail += (over_size - Size);
if (m_tail > Size)
if (m_tail >= Size)
m_tail -= Size;
m_used = Size;

View file

@ -876,39 +876,42 @@ static NEVER_INLINE error_code savedata_op(ppu_thread& ppu, u32 operation, u32 v
// Sort the entries
{
const u32 order = setList->sortOrder;
const u32 type = setList->sortType;
std::sort(save_entries.begin(), save_entries.end(), [order, type](const SaveDataEntry& entry1, const SaveDataEntry& entry2) -> bool
auto comp = [type](const SaveDataEntry& entry1, const SaveDataEntry& entry2) -> bool
{
const bool mtime_lower = entry1.mtime < entry2.mtime;
const bool mtime_equal = entry1.mtime == entry2.mtime;
const bool subtitle_lower = entry1.subtitle < entry2.subtitle;
const bool subtitle_equal = entry1.subtitle == entry2.subtitle;
const bool revert_order = order == CELL_SAVEDATA_SORTORDER_DESCENT;
if (type == CELL_SAVEDATA_SORTTYPE_MODIFIEDTIME)
{
if (mtime_equal)
{
return subtitle_lower != revert_order;
return subtitle_lower;
}
return mtime_lower != revert_order;
return mtime_lower;
}
else if (type == CELL_SAVEDATA_SORTTYPE_SUBTITLE)
{
if (subtitle_equal)
{
return mtime_lower != revert_order;
return mtime_lower;
}
return subtitle_lower != revert_order;
return subtitle_lower;
}
ensure(false);
return true;
});
};
if (setList->sortOrder == CELL_SAVEDATA_SORTORDER_ASCENT)
std::sort(save_entries.begin(), save_entries.end(), comp);
else
std::sort(save_entries.rbegin(), save_entries.rend(), comp);
}
// Fill the listGet->dirList array

View file

@ -1462,7 +1462,12 @@ error_code cellVdecGetPicItem(ppu_thread& ppu, u32 handle, vm::pptr<CellVdecPicI
struct all_info_t
{
CellVdecPicItem picItem;
std::aligned_union_t<0, CellVdecAvcInfo, CellVdecDivxInfo, CellVdecMpeg2Info> picInfo;
union
{
CellVdecAvcInfo avcInfo;
CellVdecDivxInfo divxInfo;
CellVdecMpeg2Info mpeg2Info;
} picInfo;
};
AVFrame* frame{};

View file

@ -1199,7 +1199,7 @@ error_code _sceNpBasicSendMessage(vm::cptr<SceNpId> to, vm::cptr<void> data, u32
.msgFeatures = {},
.data = std::vector<u8>(static_cast<const u8*>(data.get_ptr()), static_cast<const u8*>(data.get_ptr()) + size)};
std::set<std::string> npids;
npids.insert(std::string(to->handle.data));
npids.insert(np::npid_to_string(*to));
nph.send_message(msg_data, npids);
@ -1228,7 +1228,7 @@ error_code sceNpBasicSendMessageGui(ppu_thread& ppu, vm::cptr<SceNpBasicMessageD
sceNp.notice("sceNpBasicSendMessageGui: msgId: %d, mainType: %d, subType: %d, msgFeatures: %d, count: %d, npids: *0x%x", msg->msgId, msg->mainType, msg->subType, msg->msgFeatures, msg->count, msg->npids);
for (u32 i = 0; i < msg->count && msg->npids; i++)
{
sceNp.trace("sceNpBasicSendMessageGui: NpId[%d] = %s", i, static_cast<char*>(&msg->npids[i].handle.data[0]));
sceNp.trace("sceNpBasicSendMessageGui: NpId[%d] = %s", i, np::npid_to_string(msg->npids[i]));
}
sceNp.notice("sceNpBasicSendMessageGui: subject: %s", msg->subject);
sceNp.notice("sceNpBasicSendMessageGui: body: %s", msg->body);
@ -1398,7 +1398,7 @@ error_code sceNpBasicSendMessageGui(ppu_thread& ppu, vm::cptr<SceNpBasicMessageD
{
for (u32 i = 0; i < msg->count; i++)
{
npids.insert(std::string(msg->npids[i].handle.data));
npids.insert(np::npid_to_string(msg->npids[i]));
}
}
@ -7144,7 +7144,7 @@ error_code sceNpUtilCanonicalizeNpIdForPsp(vm::ptr<SceNpId> npId)
error_code sceNpUtilCmpNpId(vm::ptr<SceNpId> id1, vm::ptr<SceNpId> id2)
{
sceNp.trace("sceNpUtilCmpNpId(id1=*0x%x(%s), id2=*0x%x(%s))", id1, id1 ? id1->handle.data : "", id2, id2 ? id2->handle.data : "");
sceNp.trace("sceNpUtilCmpNpId(id1=*0x%x(%s), id2=*0x%x(%s))", id1, id1 ? np::npid_to_string(*id1) : std::string(), id2, id2 ? np::npid_to_string(*id2) : std::string());
if (!id1 || !id2)
{

View file

@ -992,7 +992,7 @@ error_code sys_net_bnet_sendto(ppu_thread& ppu, s32 s, vm::cptr<void> buf, u32 l
fmt::throw_exception("sys_net_bnet_sendto(s=%d): unknown flags (0x%x)", flags);
}
if (addr && addrlen < 8)
if (addr && addrlen < sizeof(sys_net_sockaddr))
{
sys_net.error("sys_net_bnet_sendto(s=%d): bad addrlen (%u)", s, addrlen);
return -SYS_NET_EINVAL;

View file

@ -107,7 +107,7 @@ enum lv2_ip_option : s32
SYS_NET_IP_MULTICAST_LOOP = 11,
SYS_NET_IP_ADD_MEMBERSHIP = 12,
SYS_NET_IP_DROP_MEMBERSHIP = 13,
SYS_NET_IP_TTLCHK = 23,
SYS_NET_IP_TTLCHK = 23, // This is probably the equivalent of IP_MINTTL on FreeBSD
SYS_NET_IP_MAXTTL = 24,
SYS_NET_IP_DONTFRAG = 26
};

View file

@ -551,12 +551,14 @@ std::tuple<s32, lv2_socket::sockopt_data, u32> lv2_socket_native::getsockopt(s32
}
case SYS_NET_IP_TTLCHK:
{
sys_net.error("sys_net_bnet_getsockopt(IPPROTO_IP, SYS_NET_IP_TTLCHK): stubbed option");
out_val._int = min_ttl;
out_len = sizeof(s32);
return {CELL_OK, out_val, out_len};
}
case SYS_NET_IP_MAXTTL:
{
sys_net.error("sys_net_bnet_getsockopt(IPPROTO_IP, SYS_NET_IP_MAXTTL): stubbed option");
out_val._int = max_ttl;
out_len = sizeof(s32);
return {CELL_OK, out_val, out_len};
}
case SYS_NET_IP_DONTFRAG:
@ -834,13 +836,13 @@ s32 lv2_socket_native::setsockopt(s32 level, s32 optname, const std::vector<u8>&
}
case SYS_NET_IP_TTLCHK:
{
sys_net.error("sys_net_bnet_setsockopt(s=%d, IPPROTO_IP): Stubbed option (0x%x) (SYS_NET_IP_TTLCHK)", lv2_id, optname);
break;
min_ttl = native_int;
return {};
}
case SYS_NET_IP_MAXTTL:
{
sys_net.error("sys_net_bnet_setsockopt(s=%d, IPPROTO_IP): Stubbed option (0x%x) (SYS_NET_IP_MAXTTL)", lv2_id, optname);
break;
max_ttl = native_int;
return {};
}
case SYS_NET_IP_DONTFRAG:
{
@ -910,7 +912,7 @@ std::optional<std::tuple<s32, std::vector<u8>, sys_net_sockaddr>> lv2_socket_nat
{
auto& nph = g_fxo->get<named_thread<np::np_handler>>();
const auto packet = dnshook.get_dns_packet(lv2_id);
ensure(packet.size() < len);
ensure(packet.size() <= len);
memcpy(res_buf.data(), packet.data(), packet.size());
native_addr.ss_family = AF_INET;
(reinterpret_cast<::sockaddr_in*>(&native_addr))->sin_port = std::bit_cast<u16, be_t<u16>>(53); // htons(53)
@ -1069,18 +1071,20 @@ std::optional<s32> lv2_socket_native::sendmsg(s32 flags, const sys_net_msghdr& m
return {-SYS_NET_ECONNRESET};
}
std::vector<u8> buf_copy;
for (int i = 0; i < msg.msg_iovlen; i++)
{
auto iov_base = msg.msg_iov[i].iov_base;
const u32 len = msg.msg_iov[i].iov_len;
const std::vector<u8> buf_copy(vm::_ptr<const char>(iov_base.addr()), vm::_ptr<const char>(iov_base.addr()) + len);
const auto* src = vm::_ptr<const char>(iov_base.addr());
buf_copy.insert(buf_copy.end(), src, src + len);
}
native_result = ::send(native_socket, reinterpret_cast<const char*>(buf_copy.data()), ::narrow<int>(buf_copy.size()), native_flags);
native_result = ::send(native_socket, reinterpret_cast<const char*>(buf_copy.data()), ::narrow<int>(buf_copy.size()), native_flags);
if (native_result >= 0)
{
return {native_result};
}
if (native_result >= 0)
{
return {native_result};
}
result = get_last_error(!so_nbio && (flags & SYS_NET_MSG_DONTWAIT) == 0);
@ -1232,16 +1236,16 @@ bool lv2_socket_native::is_socket_connected()
return false;
}
fd_set readfds, writefds;
struct timeval timeout{0, 0}; // Zero timeout
pollfd pfd{};
pfd.fd = native_socket;
pfd.events = POLLIN | POLLOUT;
FD_ZERO(&readfds);
FD_ZERO(&writefds);
FD_SET(native_socket, &readfds);
FD_SET(native_socket, &writefds);
// Use select to check for readability and writability
const int result = ::select(1, &readfds, &writefds, NULL, &timeout);
// Use poll to check for readability and writability
#ifdef _WIN32
const int result = WSAPoll(&pfd, 1, 0);
#else
const int result = ::poll(&pfd, 1, 0);
#endif
if (result < 0)
{
@ -1250,5 +1254,5 @@ bool lv2_socket_native::is_socket_connected()
}
// Socket is connected if it's readable or writable
return FD_ISSET(native_socket, &readfds) || FD_ISSET(native_socket, &writefds);
return (pfd.revents & (POLLIN | POLLOUT)) != 0;
}

View file

@ -70,6 +70,10 @@ private:
s32 so_reuseaddr = 0;
s32 so_reuseport = 0;
#endif
// Those values come from FreeBSD
s32 min_ttl = 1;
s32 max_ttl = 64;
u16 bound_port = 0;
bool feign_tcp_conn_failure = false; // Savestate load related
};

View file

@ -112,7 +112,6 @@ public:
// reply is late, increases rtt
auto& msg = it->second;
const auto addr = msg.dst_addr.sin_addr.s_addr;
rtt_info rtt = rtts[msg.sock_id];
// Only increases rtt once per loop(in case a big number of packets are sent at once)
if (!rtt_increased.count(msg.sock_id))
@ -120,7 +119,7 @@ public:
rtt.num_retries += 1;
// Increases current rtt by 10%
rtt.rtt_time += (rtt.rtt_time / 10);
rtts[addr] = rtt;
rtts[msg.sock_id] = rtt;
rtt_increased.emplace(msg.sock_id);
}
@ -625,7 +624,7 @@ std::tuple<bool, s32, shared_ptr<lv2_socket>, sys_net_sockaddr> lv2_socket_p2ps:
sys_net_sockaddr ps3_addr{};
auto* paddr = reinterpret_cast<sys_net_sockaddr_in_p2p*>(&ps3_addr);
lv2_socket_p2ps* sock_client = reinterpret_cast<lv2_socket_p2ps*>(idm::check_unlocked<lv2_socket>(p2ps_client));
auto sock_client = static_cast<shared_ptr<lv2_socket_p2ps>>(idm::get_unlocked<lv2_socket>(p2ps_client));
{
std::lock_guard lock(sock_client->mutex);
paddr->sin_family = SYS_NET_AF_INET;

View file

@ -249,8 +249,9 @@ bool nt_p2p_port::recv_data()
auto& bound_sockets = ::at32(bound_p2p_vports, dst_vport);
for (const auto sock_id : bound_sockets)
for (auto it = bound_sockets.begin(); it != bound_sockets.end();)
{
s32 sock_id = *it;
const auto sock = idm::check<lv2_socket>(sock_id, [&](lv2_socket& sock)
{
ensure(sock.get_type() == SYS_NET_SOCK_DGRAM_P2P);
@ -262,12 +263,17 @@ bool nt_p2p_port::recv_data()
if (!sock)
{
sys_net.error("Socket %d found in bound_p2p_vports didn't exist!", sock_id);
bound_sockets.erase(sock_id);
it = bound_sockets.erase(it);
if (bound_sockets.empty())
{
bound_p2p_vports.erase(dst_vport);
break;
}
}
else
{
it++;
}
}
return true;

View file

@ -118,6 +118,7 @@ static int clock_gettime(int clk_id, struct timespec* tp)
#ifndef _WIN32
#include <exception>
#include <sys/time.h>
static struct timespec start_time = []()

View file

@ -556,6 +556,8 @@ usb_handler_thread::usb_handler_thread()
switch (g_cfg.audio.microphone_type)
{
case microphone_handler::null:
break;
case microphone_handler::standard:
usb_devices.push_back(std::make_shared<usb_device_mic>(0, get_new_location(), MicType::Logitech));
break;

View file

@ -33,12 +33,12 @@ void MouseHandlerBase::save(utils::serial& ar)
bool MouseHandlerBase::is_time_for_update(double elapsed_time_ms)
{
steady_clock::time_point now = steady_clock::now();
const double elapsed_ms = (now - last_update).count() / 1'000'000.;
const steady_clock::time_point now = steady_clock::now();
const double elapsed_ms = (now - m_last_update).count() / 1'000'000.;
if (elapsed_ms > elapsed_time_ms)
{
last_update = now;
m_last_update = now;
return true;
}
return false;

View file

@ -128,7 +128,7 @@ class MouseHandlerBase
protected:
MouseInfo m_info{};
std::vector<Mouse> m_mice;
steady_clock::time_point last_update{};
steady_clock::time_point m_last_update{};
bool is_time_for_update(double elapsed_time_ms = 10.0); // 4-10 ms, let's use 10 for now

View file

@ -464,7 +464,7 @@ namespace clan
clan.append_child("ticket").text().set(ticket.c_str());
clan.append_child("id").text().set(clan_id);
const std::string jid_str = fmt::format(JID_FORMAT, np_id.handle.data);
const std::string jid_str = fmt::format(JID_FORMAT, np::npid_to_string(np_id));
clan.append_child("jid").text().set(jid_str.c_str());
pugi::xml_document response = pugi::xml_document();
@ -656,7 +656,7 @@ namespace clan
clan.append_child("ticket").text().set(ticket.c_str());
clan.append_child("id").text().set(clan_id);
const std::string jid_str = fmt::format(JID_FORMAT, np_id.handle.data);
const std::string jid_str = fmt::format(JID_FORMAT, np::npid_to_string(np_id));
clan.append_child("jid").text().set(jid_str.c_str());
pugi::xml_document response = pugi::xml_document();
@ -674,7 +674,7 @@ namespace clan
clan.append_child("ticket").text().set(ticket.c_str());
clan.append_child("id").text().set(clan_id);
const std::string jid_str = fmt::format(JID_FORMAT, np_id.handle.data);
const std::string jid_str = fmt::format(JID_FORMAT, np::npid_to_string(np_id));
clan.append_child("jid").text().set(jid_str.c_str());
pugi::xml_document response = pugi::xml_document();
@ -832,7 +832,7 @@ namespace clan
clan.append_child("ticket").text().set(ticket.c_str());
clan.append_child("id").text().set(clan_id);
const std::string jid_str = fmt::format(JID_FORMAT, np_id.handle.data);
const std::string jid_str = fmt::format(JID_FORMAT, np::npid_to_string(np_id));
clan.append_child("jid").text().set(jid_str.c_str());
pugi::xml_document response = pugi::xml_document();
@ -850,7 +850,7 @@ namespace clan
clan.append_child("ticket").text().set(ticket.c_str());
clan.append_child("id").text().set(clan_id);
const std::string jid_str = fmt::format(JID_FORMAT, np_id.handle.data);
const std::string jid_str = fmt::format(JID_FORMAT, np::npid_to_string(np_id));
clan.append_child("jid").text().set(jid_str.c_str());
pugi::xml_document response = pugi::xml_document();
@ -868,7 +868,7 @@ namespace clan
clan.append_child("ticket").text().set(ticket.c_str());
clan.append_child("id").text().set(clan_id);
const std::string jid_str = fmt::format(JID_FORMAT, np_id.handle.data);
const std::string jid_str = fmt::format(JID_FORMAT, np::npid_to_string(np_id));
clan.append_child("jid").text().set(jid_str.c_str());
pugi::xml_document response = pugi::xml_document();
@ -902,7 +902,7 @@ namespace clan
clan.append_child("id").text().set(clan_id);
pugi::xml_node role = clan.append_child("onlinename");
role.text().set(nph.get_npid().handle.data);
role.text().set(np::npid_to_string(nph.get_npid()).c_str());
pugi::xml_node description = clan.append_child("description");
description.text().set(info.description);
@ -990,7 +990,7 @@ namespace clan
clan.append_child("ticket").text().set(ticket.c_str());
clan.append_child("id").text().set(clan_id);
const std::string jid_str = fmt::format(JID_FORMAT, np_id.handle.data);
const std::string jid_str = fmt::format(JID_FORMAT, np::npid_to_string(np_id));
clan.append_child("jid").text().set(jid_str.c_str());
pugi::xml_document response = pugi::xml_document();
@ -1008,7 +1008,7 @@ namespace clan
clan.append_child("ticket").text().set(ticket.c_str());
clan.append_child("id").text().set(clan_id);
const std::string jid_str = fmt::format(JID_FORMAT, np_id.handle.data);
const std::string jid_str = fmt::format(JID_FORMAT, np::npid_to_string(np_id));
clan.append_child("jid").text().set(jid_str.c_str());
pugi::xml_node role_node = clan.append_child("role");

View file

@ -377,7 +377,7 @@ namespace np
if (!rooms.contains(room_id))
{
np_cache.error("np_cache::get_memberid cache miss room_id: room_id(%d)/npid(%s)", room_id, static_cast<const char*>(npid.handle.data));
np_cache.error("np_cache::get_memberid cache miss room_id: room_id(%d)/npid(%s)", room_id, np::npid_to_string(npid));
return std::nullopt;
}
@ -389,7 +389,7 @@ namespace np
return id;
}
np_cache.error("np_cache::get_memberid cache miss member_id: room_id(%d)/npid(%s)", room_id, static_cast<const char*>(npid.handle.data));
np_cache.error("np_cache::get_memberid cache miss member_id: room_id(%d)/npid(%s)", room_id, np::npid_to_string(npid));
return std::nullopt;
}

View file

@ -46,7 +46,7 @@ error_code generic_async_transaction_context::wait_for_completion()
return *result;
}
completion_cond.wait(lock);
completion_cond.wait(lock, [this] { return result.has_value(); });
return *result;
}

View file

@ -1024,7 +1024,7 @@ namespace np
}
}
nph_log.notice("basic_event: event:%d, from:%s(%s), size:%d", *event, static_cast<char*>(from->userId.handle.data), static_cast<char*>(from->name.data), *size);
nph_log.notice("basic_event: event:%d, from:%s(%s), size:%d", *event, np::npid_to_string(from->userId), static_cast<char*>(from->name.data), *size);
return CELL_OK;
}
@ -1361,7 +1361,7 @@ namespace np
player_history& np_handler::get_player_and_set_timestamp(const SceNpId& npid, u64 timestamp)
{
std::string npid_str = std::string(npid.handle.data);
std::string npid_str = np::npid_to_string(npid);
if (!players_history.contains(npid_str))
{
@ -1641,7 +1641,7 @@ namespace np
return SCE_NP_BASIC_ERROR_NOT_CONNECTED;
}
auto friend_infos = rpcn->get_friend_presence_by_npid(std::string(npid.handle.data));
auto friend_infos = rpcn->get_friend_presence_by_npid(np::npid_to_string(npid));
if (!friend_infos)
{
return SCE_NP_BASIC_ERROR_INVALID_ARGUMENT;

View file

@ -89,6 +89,13 @@ namespace np
// npid->reserved[0] = 1;
}
std::string npid_to_string(const SceNpId& npid)
{
char npid_str[17]{};
std::memcpy(npid_str, npid.handle.data, 16);
return std::string(npid_str);
}
void string_to_online_name(std::string_view str, SceNpOnlineName& online_name)
{
memset(&online_name, 0, sizeof(online_name));

View file

@ -13,6 +13,7 @@ namespace np
std::optional<SceNpCommunicationId> string_to_communication_id(std::string_view str);
void string_to_npid(std::string_view str, SceNpId& npid);
std::string npid_to_string(const SceNpId& npid);
void string_to_online_name(std::string_view str, SceNpOnlineName& online_name);
void string_to_avatar_url(std::string_view str, SceNpAvatarUrl& avatar_url);
void strings_to_userinfo(std::string_view npid, std::string_view online_name, std::string_view avatar_url, SceNpUserInfo& user_info);

View file

@ -41,7 +41,7 @@ namespace np
return;
}
rpcn_log.notice("Received notification that user %s(%d) joined the room(%d)", notif_data->roomMemberDataInternal->userInfo.npId.handle.data, notif_data->roomMemberDataInternal->memberId, room_id);
rpcn_log.notice("Received notification that user %s(%d) joined the room(%d)", np::npid_to_string(notif_data->roomMemberDataInternal->userInfo.npId), notif_data->roomMemberDataInternal->memberId, room_id);
extra_nps::print_SceNpMatching2RoomMemberDataInternal(notif_data->roomMemberDataInternal.get_ptr());
// We initiate signaling if necessary
@ -54,7 +54,7 @@ namespace np
const u16 member_id = notif_data->roomMemberDataInternal->memberId;
const SceNpId& npid = notif_data->roomMemberDataInternal->userInfo.npId;
rpcn_log.notice("Join notification told to connect to member(%d=%s) of room(%d): %s:%d", member_id, reinterpret_cast<const char*>(npid.handle.data), room_id, ip_to_string(addr_p2p), port_p2p);
rpcn_log.notice("Join notification told to connect to member(%d=%s) of room(%d): %s:%d", member_id, np::npid_to_string(npid), room_id, ip_to_string(addr_p2p), port_p2p);
// Attempt Signaling
auto& sigh = g_fxo->get<named_thread<signaling_handler>>();
@ -98,7 +98,7 @@ namespace np
return;
}
rpcn_log.notice("Received notification that user %s(%d) left the room(%d)", notif_data->roomMemberDataInternal->userInfo.npId.handle.data, notif_data->roomMemberDataInternal->memberId, room_id);
rpcn_log.notice("Received notification that user %s(%d) left the room(%d)", np::npid_to_string(notif_data->roomMemberDataInternal->userInfo.npId), notif_data->roomMemberDataInternal->memberId, room_id);
extra_nps::print_SceNpMatching2RoomMemberDataInternal(notif_data->roomMemberDataInternal.get_ptr());
if (room_event_cb)
@ -204,7 +204,7 @@ namespace np
return;
}
rpcn_log.notice("Received notification that user's %s(%d) room (%d) data was updated", notif_data->newRoomMemberDataInternal->userInfo.npId.handle.data, notif_data->newRoomMemberDataInternal->memberId, room_id);
rpcn_log.notice("Received notification that user's %s(%d) room (%d) data was updated", np::npid_to_string(notif_data->newRoomMemberDataInternal->userInfo.npId), notif_data->newRoomMemberDataInternal->memberId, room_id);
extra_nps::print_SceNpMatching2RoomMemberDataInternal(notif_data->newRoomMemberDataInternal.get_ptr());
if (room_event_cb)

View file

@ -310,7 +310,7 @@ namespace np
if (npid_res != CELL_OK)
continue;
rpcn_log.notice("JoinRoomResult told to connect to member(%d=%s) of room(%d): %s:%d", member_id, reinterpret_cast<const char*>(npid_p2p->handle.data), room_id, ip_to_string(addr_p2p), port_p2p);
rpcn_log.notice("JoinRoomResult told to connect to member(%d=%s) of room(%d): %s:%d", member_id, np::npid_to_string(*npid_p2p), room_id, ip_to_string(addr_p2p), port_p2p);
// Attempt Signaling
auto& sigh = g_fxo->get<named_thread<signaling_handler>>();
@ -951,13 +951,16 @@ namespace np
{
thread_base::set_name("NP Trans Worker");
auto res = trans_ctx->wake_cond.wait_for(lock, std::chrono::microseconds(trans_ctx->timeout));
bool has_value = trans_ctx->wake_cond.wait_for(lock, std::chrono::microseconds(trans_ctx->timeout), [&]
{
return trans_ctx->result.has_value();
});
{
std::lock_guard lock_threads(this->mutex_async_transactions);
this->async_transactions.erase(req_id);
}
if (res == std::cv_status::timeout)
if (!has_value)
{
trans_ctx->result = SCE_NP_COMMUNITY_ERROR_TIMEOUT;
return;

View file

@ -2,6 +2,7 @@
#include "stdafx.h"
#include <span>
#include "np_structs_extra.h"
#include "np_helpers.h"
LOG_CHANNEL(sceNp);
LOG_CHANNEL(sceNp2);
@ -13,7 +14,7 @@ namespace extra_nps
void print_SceNpUserInfo2(const SceNpUserInfo2* user)
{
sceNp2.warning("SceNpUserInfo2:");
sceNp2.warning("npid: %s", static_cast<const char*>(user->npId.handle.data));
sceNp2.warning("npid: %s", np::npid_to_string(user->npId));
sceNp2.warning("onlineName: *0x%x(%s)", user->onlineName, user->onlineName ? static_cast<const char*>(user->onlineName->data) : "");
sceNp2.warning("avatarUrl: *0x%x(%s)", user->avatarUrl, user->avatarUrl ? static_cast<const char*>(user->avatarUrl->data) : "");
}
@ -208,7 +209,7 @@ namespace extra_nps
{
sceNp2.warning("SceNpMatching2RoomMemberDataInternal:");
sceNp2.warning("next: *0x%x", member->next);
sceNp2.warning("npId: %s", member->userInfo.npId.handle.data);
sceNp2.warning("npId: %s", np::npid_to_string(member->userInfo.npId));
sceNp2.warning("onlineName: %s", member->userInfo.onlineName ? member->userInfo.onlineName->data : "");
sceNp2.warning("avatarUrl: %s", member->userInfo.avatarUrl ? member->userInfo.avatarUrl->data : "");
sceNp2.warning("joinDate: %lld", member->joinDate.tick);
@ -460,7 +461,7 @@ namespace extra_nps
void print_SceNpScoreRankData(const SceNpScoreRankData* data)
{
sceNp.warning("sceNpScoreRankData:");
sceNp.warning("npId: %s", static_cast<const char*>(data->npId.handle.data));
sceNp.warning("npId: %s", np::npid_to_string(data->npId));
sceNp.warning("onlineName: %s", static_cast<const char*>(data->onlineName.data));
sceNp.warning("pcId: %d", data->pcId);
sceNp.warning("serialRank: %d", data->serialRank);
@ -474,7 +475,7 @@ namespace extra_nps
void print_SceNpScoreRankData_deprecated(const SceNpScoreRankData_deprecated* data)
{
sceNp.warning("sceNpScoreRankData_deprecated:");
sceNp.warning("npId: %s", static_cast<const char*>(data->npId.handle.data));
sceNp.warning("npId: %s", np::npid_to_string(data->npId));
sceNp.warning("onlineName: %s", static_cast<const char*>(data->onlineName.data));
sceNp.warning("serialRank: %d", data->serialRank);
sceNp.warning("rank: %d", data->rank);
@ -542,7 +543,7 @@ namespace extra_nps
void print_SceNpUserInfo(const SceNpUserInfo* data)
{
sceNp.warning("userId: %s", data->userId.handle.data);
sceNp.warning("userId: %s", np::npid_to_string(data->userId));
sceNp.warning("name: %s", data->name.data);
sceNp.warning("icon: %s", data->icon.data);
}
@ -576,7 +577,7 @@ namespace extra_nps
if (data->kick_actor)
{
sceNp.warning("kick_actor: %s", data->kick_actor->handle.data);
sceNp.warning("kick_actor: %s", np::npid_to_string(*data->kick_actor));
}
sceNp.warning("opt: 0x%x", data->kick_actor);

View file

@ -263,7 +263,7 @@ namespace np
for (u32 i = 0; i < room_info->memberList.membersNum; i++)
{
SceNpMatching2RoomMemberDataInternal* sce_member = &room_info->memberList.members[i];
if (strcmp(sce_member->userInfo.npId.handle.data, npid.handle.data) == 0)
if (strncmp(sce_member->userInfo.npId.handle.data, npid.handle.data, 16) == 0)
{
room_info->memberList.me = room_info->memberList.members + i;
edata.add_relocation<SceNpMatching2RoomMemberDataInternal>(room_info->memberList.me);

View file

@ -896,7 +896,7 @@ namespace rpcn
return error_and_disconnect("Failed to send all the bytes");
}
res = 0;
continue;
}
n_sent += res;
}
@ -1055,6 +1055,8 @@ namespace rpcn
found = found->ai_next;
}
freeaddrinfo(addr_info);
if (!found_ipv4)
{
rpcn_log.error("connect: Failed to find IPv4 for %s", host);
@ -1156,7 +1158,7 @@ namespace rpcn
if (!connected || terminate)
{
state = rpcn_state::failure_other;
return true;
return false;
}
if (received_version != RPCN_PROTOCOL_VERSION)
@ -1467,7 +1469,7 @@ namespace rpcn
return error;
}
bool rpcn_client::add_friend(const std::string& friend_username)
std::optional<ErrorType> rpcn_client::add_friend(const std::string& friend_username)
{
std::vector<u8> data;
std::copy(friend_username.begin(), friend_username.end(), std::back_inserter(data));
@ -1478,19 +1480,18 @@ namespace rpcn
std::vector<u8> packet_data;
if (!forge_send_reply(CommandType::AddFriend, req_id, data, packet_data))
{
return false;
return std::nullopt;
}
vec_stream reply(packet_data);
auto error = static_cast<ErrorType>(reply.get<u8>());
const auto error = static_cast<ErrorType>(reply.get<u8>());
if (error != rpcn::ErrorType::NoError)
{
return false;
}
if (error == ErrorType::NoError)
rpcn_log.success("add_friend(\"%s\") succeeded", friend_username);
else
rpcn_log.error("add_friend(\"%s\") failed with error: %s", error);
rpcn_log.success("You have successfully added \"%s\" as a friend", friend_username);
return true;
return error;
}
bool rpcn_client::remove_friend(const std::string& friend_username)
@ -1755,7 +1756,7 @@ namespace rpcn
{
continue;
}
pb_req.add_alloweduser(req->allowedUser[i].handle.data);
pb_req.add_alloweduser(np::npid_to_string(req->allowedUser[i]));
}
}
@ -1767,7 +1768,7 @@ namespace rpcn
{
continue;
}
pb_req.add_blockeduser(req->blockedUser[i].handle.data);
pb_req.add_blockeduser(np::npid_to_string(req->blockedUser[i]));
}
}
@ -2266,7 +2267,7 @@ namespace rpcn
for (usz i = 0; i < npids.size(); i++)
{
auto* npid_entry = pb_req.add_npids();
npid_entry->set_npid(static_cast<const char*>(npids[i].first.handle.data));
npid_entry->set_npid(np::npid_to_string(npids[i].first));
npid_entry->set_pcid(npids[i].second);
}
@ -2317,7 +2318,7 @@ namespace rpcn
{
np2_structs::GetScoreGameDataRequest pb_req;
pb_req.set_boardid(board_id);
pb_req.set_npid(reinterpret_cast<const char*>(npid.handle.data));
pb_req.set_npid(np::npid_to_string(npid));
pb_req.set_pcid(pc_id);
std::string serialized;
@ -2413,7 +2414,7 @@ namespace rpcn
if (option->isLastChangedAuthorId)
{
pb_req.set_islastchangedauthorid(option->isLastChangedAuthorId->handle.data);
pb_req.set_islastchangedauthorid(np::npid_to_string(*option->isLastChangedAuthorId));
}
}
@ -2442,7 +2443,7 @@ namespace rpcn
if (option->isLastChangedAuthorId)
{
pb_req.set_islastchangedauthorid(option->isLastChangedAuthorId->handle.data);
pb_req.set_islastchangedauthorid(np::npid_to_string(*option->isLastChangedAuthorId));
}
if (option->compareValue)
@ -2498,7 +2499,7 @@ namespace rpcn
if (option->isLastChangedAuthorId)
{
pb_req.set_islastchangedauthorid(option->isLastChangedAuthorId->handle.data);
pb_req.set_islastchangedauthorid(np::npid_to_string(*option->isLastChangedAuthorId));
}
}

View file

@ -79,6 +79,14 @@ public:
res.push_back(vec[i]);
i++;
}
// Make sure we hit terminating 0
if (i >= vec.size())
{
error = true;
return {};
}
i++;
if (!empty && res.empty())
@ -293,7 +301,7 @@ namespace rpcn
ErrorType send_reset_token(std::string_view npid, std::string_view email);
ErrorType reset_password(std::string_view npid, std::string_view token, std::string_view password);
ErrorType delete_account();
bool add_friend(const std::string& friend_username);
std::optional<ErrorType> add_friend(const std::string& friend_username);
bool remove_friend(const std::string& friend_username);
u32 get_num_friends();

View file

@ -256,9 +256,7 @@ void signaling_handler::process_incoming_messages()
addr.s_addr = op_addr;
char ip_str[16];
inet_ntop(AF_INET, &addr, ip_str, sizeof(ip_str));
std::string_view npid(sp->npid.handle.data);
sign_log.trace("SP %s from %s:%d(npid: %s)", sp->command, ip_str, op_port, npid);
sign_log.trace("SP %s from %s:%d(npid: %s)", sp->command, ip_str, op_port, np::npid_to_string(sp->npid));
}
bool reply = false, schedule_repeat = false;
@ -426,9 +424,10 @@ void signaling_handler::operator()()
if (sig.sig_info->time_last_msg_recvd < now - 60s && cmd != signal_info)
{
// We had no connection to opponent for 60 seconds, consider the connection dead
auto retire_info = sig.sig_info;
sign_log.notice("Timeout disconnection");
update_si_status(sig.sig_info, SCE_NP_SIGNALING_CONN_STATUS_INACTIVE, SCE_NP_SIGNALING_ERROR_TIMEOUT);
retire_packet(sig.sig_info, signal_ping); // Retire ping packet if necessary
update_si_status(retire_info, SCE_NP_SIGNALING_CONN_STATUS_INACTIVE, SCE_NP_SIGNALING_ERROR_TIMEOUT);
retire_packet(retire_info, signal_ping); // Retire ping packet if necessary
break; // qpackets has been emptied of all packets for this user so we're requeuing
}
@ -674,9 +673,7 @@ std::shared_ptr<signaling_info> signaling_handler::get_signaling_ptr(const signa
{
u32 conn_id;
char npid_buf[17]{};
memcpy(npid_buf, sp->npid.handle.data, 16);
std::string npid(npid_buf);
std::string npid = np::npid_to_string(sp->npid);
if (!npid_to_conn_id.contains(npid))
return nullptr;
@ -784,7 +781,7 @@ void signaling_handler::send_information_packets(u32 addr, u16 port, const SceNp
u32 signaling_handler::get_always_conn_id(const SceNpId& npid)
{
std::string npid_str(reinterpret_cast<const char*>(npid.handle.data));
std::string npid_str = np::npid_to_string(npid);
if (npid_to_conn_id.contains(npid_str))
return ::at32(npid_to_conn_id, npid_str);
@ -810,9 +807,8 @@ u32 signaling_handler::init_sig1(const SceNpId& npid)
sig_peers[conn_id]->conn_status = SCE_NP_SIGNALING_CONN_STATUS_PENDING;
// Request peer infos from RPCN
std::string npid_str(reinterpret_cast<const char*>(npid.handle.data));
auto& nph = g_fxo->get<named_thread<np::np_handler>>();
nph.req_sign_infos(npid_str, conn_id);
nph.req_sign_infos(np::npid_to_string(npid), conn_id);
}
return conn_id;
@ -839,7 +835,7 @@ std::optional<u32> signaling_handler::get_conn_id_from_npid(const SceNpId& npid)
{
std::lock_guard lock(data_mutex);
std::string npid_str(reinterpret_cast<const char*>(npid.handle.data));
std::string npid_str = np::npid_to_string(npid);
if (npid_to_conn_id.contains(npid_str))
return ::at32(npid_to_conn_id, npid_str);

View file

@ -1200,27 +1200,59 @@ namespace rsx
fmt::throw_exception("Unknown format 0x%x", texture_format);
}
bool is_int8_remapped_format(u32 format)
rsx::flags32_t get_format_features(u32 texture_format)
{
switch (format)
switch (texture_format)
{
case CELL_GCM_TEXTURE_B8:
case CELL_GCM_TEXTURE_A1R5G5B5:
case CELL_GCM_TEXTURE_A4R4G4B4:
case CELL_GCM_TEXTURE_R5G6B5:
case CELL_GCM_TEXTURE_A8R8G8B8:
case CELL_GCM_TEXTURE_COMPRESSED_DXT1:
case CELL_GCM_TEXTURE_COMPRESSED_DXT23:
case CELL_GCM_TEXTURE_COMPRESSED_DXT45:
case CELL_GCM_TEXTURE_G8B8:
case CELL_GCM_TEXTURE_COMPRESSED_B8R8_G8R8:
case CELL_GCM_TEXTURE_COMPRESSED_R8B8_R8G8:
case CELL_GCM_TEXTURE_R6G5B5:
case CELL_GCM_TEXTURE_R5G5B5A1:
case CELL_GCM_TEXTURE_D1R5G5B5:
case CELL_GCM_TEXTURE_D8R8G8B8:
// Base texture formats - everything is supported
return RSX_FORMAT_FEATURE_SIGNED_COMPONENTS | RSX_FORMAT_FEATURE_GAMMA_CORRECTION | RSX_FORMAT_FEATURE_BIASED_NORMALIZATION;
case CELL_GCM_TEXTURE_DEPTH24_D8:
case CELL_GCM_TEXTURE_DEPTH24_D8_FLOAT:
case CELL_GCM_TEXTURE_DEPTH16:
case CELL_GCM_TEXTURE_DEPTH16_FLOAT:
// Depth textures will hang the hardware if BX2 or GAMMA is active. ARGB8_SIGNED has no impact.
// UNSIGNED_REMAP=BIASED works on all formats including the float variants.
return RSX_FORMAT_FEATURE_BIASED_NORMALIZATION;
case CELL_GCM_TEXTURE_X16:
// X16 - GAMMA causes hangs. ARGB8_SIGNED is ignored. UNSIGNED_REMAP=BIASED works.
return RSX_FORMAT_FEATURE_BIASED_NORMALIZATION | RSX_FORMAT_FEATURE_16BIT_CHANNELS;
case CELL_GCM_TEXTURE_Y16_X16:
// X16 | Y16 - GAMMA causes hangs. ARGB8_SIGNED works. UNSIGNED_REMAP=BIASED also works.
return RSX_FORMAT_FEATURE_SIGNED_COMPONENTS | RSX_FORMAT_FEATURE_BIASED_NORMALIZATION | RSX_FORMAT_FEATURE_16BIT_CHANNELS;
case CELL_GCM_TEXTURE_COMPRESSED_HILO8:
// GAMMA causes GPU hangs. ARGB8_SIGNED is ignored. UNSIGNED_REMAP=BIASED works.
return RSX_FORMAT_FEATURE_BIASED_NORMALIZATION;
case CELL_GCM_TEXTURE_COMPRESSED_HILO_S8:
// GAMMA causes hangs. Other flags ignored.
return 0;
case CELL_GCM_TEXTURE_W16_Z16_Y16_X16_FLOAT:
case CELL_GCM_TEXTURE_W32_Z32_Y32_X32_FLOAT:
case CELL_GCM_TEXTURE_X32_FLOAT:
case CELL_GCM_TEXTURE_Y16_X16_FLOAT:
// NOTE: Special data formats (XY, HILO, DEPTH) are not RGB formats
return false;
default:
return true;
// Floating point textures. Nothing works.
return 0;
}
fmt::throw_exception("Unknown format 0x%x", texture_format);
}
/**

View file

@ -9,6 +9,8 @@
namespace rsx
{
using flags32_t = u32;
enum texture_upload_context : u32
{
shader_read = 1,
@ -125,6 +127,32 @@ namespace rsx
using namespace format_class_;
enum format_features : u8
{
RSX_FORMAT_FEATURE_SIGNED_COMPONENTS = (1 << 0),
RSX_FORMAT_FEATURE_BIASED_NORMALIZATION = (1 << 1),
RSX_FORMAT_FEATURE_GAMMA_CORRECTION = (1 << 2),
RSX_FORMAT_FEATURE_16BIT_CHANNELS = (1 << 3), // Complements RSX_FORMAT_FEATURE_SIGNED_COMPONENTS
};
using enum format_features;
struct texture_format_ex
{
texture_format_ex() = default;
texture_format_ex(u32 bits)
: format_bits(bits)
{}
bool valid() const { return format_bits != 0; }
u32 format() const { return format_bits & ~(CELL_GCM_TEXTURE_LN | CELL_GCM_TEXTURE_UN); }
//private:
u32 format_bits = 0;
u32 features = 0;
u32 texel_remap_control = 0;
};
// Sampled image descriptor
class sampled_image_descriptor_base
{
@ -167,6 +195,7 @@ namespace rsx
u64 surface_cache_tag = 0;
texcoord_xform_t texcoord_xform;
texture_format_ex format_ex;
};
struct typeless_xfer
@ -257,7 +286,12 @@ namespace rsx
u8 get_format_sample_count(rsx::surface_antialiasing antialias);
u32 get_max_depth_value(rsx::surface_depth_format2 format);
bool is_depth_stencil_format(rsx::surface_depth_format2 format);
bool is_int8_remapped_format(u32 format); // Returns true if the format is treated as INT8 by the RSX remapper.
/**
* Format feature support. There is not simple format to determine what is supported here, results are from hw tests
* Returns a bitmask of supported features.
*/
rsx::flags32_t get_format_features(u32 texture_format);
/**
* Returns number of texel rows encoded in one pitch-length line of bytes

View file

@ -2,6 +2,7 @@
#include "GLGSRender.h"
#include "../rsx_methods.h"
#include "../Common/BufferUtils.h"
#include "../Program/GLSLCommon.h"
#include "Emu/RSX/NV47/HW/context_accessors.define.h"
@ -315,6 +316,8 @@ void GLGSRender::load_texture_env()
if (sampler_state->validate())
{
sampler_state->format_ex = tex.format_ex();
if (m_textures_dirty[i])
{
m_fs_sampler_states[i].apply(tex, fs_sampler_state[i].get());
@ -324,12 +327,17 @@ void GLGSRender::load_texture_env()
m_graphics_state |= rsx::fragment_program_state_dirty;
}
if (const auto texture_format = tex.format() & ~(CELL_GCM_TEXTURE_UN | CELL_GCM_TEXTURE_LN);
sampler_state->format_class != rsx::classify_format(texture_format) &&
(texture_format == CELL_GCM_TEXTURE_A8R8G8B8 || texture_format == CELL_GCM_TEXTURE_D8R8G8B8))
const auto texture_format = sampler_state->format_ex.format();
// Depth format redirected to BGRA8 resample stage. Do not filter to avoid bits leaking.
// If accurate graphics are desired, force a bitcast to COLOR as a workaround.
const bool is_depth_reconstructed = sampler_state->format_class != rsx::classify_format(texture_format) &&
(texture_format == CELL_GCM_TEXTURE_A8R8G8B8 || texture_format == CELL_GCM_TEXTURE_D8R8G8B8);
// SNORM conversion required in shader. Do not interpolate to avoid introducing discontinuities due to how negative numbers work
const bool is_snorm = (sampler_state->format_ex.texel_remap_control & rsx::texture_control_bits::SEXT_MASK) != 0;
if (is_depth_reconstructed || is_snorm)
{
// Depth format redirected to BGRA8 resample stage. Do not filter to avoid bits leaking.
// If accurate graphics are desired, force a bitcast to COLOR as a workaround.
m_fs_sampler_states[i].set_parameteri(GL_TEXTURE_MIN_FILTER, GL_NEAREST);
m_fs_sampler_states[i].set_parameteri(GL_TEXTURE_MAG_FILTER, GL_NEAREST);
}

View file

@ -1351,7 +1351,7 @@ void GLGSRender::notify_tile_unbound(u32 tile)
}
}
bool GLGSRender::release_GCM_label(u32 address, u32 args)
bool GLGSRender::release_GCM_label(u32 type, u32 address, u32 args)
{
if (!backend_config.supports_host_gpu_labels)
{
@ -1360,7 +1360,7 @@ bool GLGSRender::release_GCM_label(u32 address, u32 args)
auto host_ctx = ensure(m_host_dma_ctrl->host_ctx());
if (host_ctx->texture_loads_completed())
if (type == NV4097_TEXTURE_READ_SEMAPHORE_RELEASE && host_ctx->texture_loads_completed())
{
// We're about to poll waiting for GPU state, ensure the context is still valid.
gl::check_state();

View file

@ -206,7 +206,7 @@ public:
void discard_occlusion_query(rsx::reports::occlusion_query_info* query) override;
// DMA
bool release_GCM_label(u32 address, u32 data) override;
bool release_GCM_label(u32 type, u32 address, u32 data) override;
void enqueue_host_context_write(u32 offset, u32 size, const void* data);
void on_guest_texture_read();

View file

@ -4,6 +4,7 @@
#include "Emu/RSX/RSXThread.h"
#include "Emu/RSX/Core/RSXReservationLock.hpp"
#include "Emu/RSX/Common/tiled_dma_copy.hpp"
#include "Emu/RSX/Host/MM.h"
#include "context_accessors.define.h"
@ -581,9 +582,11 @@ namespace rsx
const u16 out_h = REGS(ctx)->blit_engine_output_height();
// Lock here. RSX cannot execute any locking operations from this point, including ZCULL read barriers
const u32 read_length = src.pitch * src.height;
const u32 write_length = dst.pitch * dst.clip_height;
auto res = ::rsx::reservation_lock<true>(
dst.rsx_address, dst.pitch * dst.clip_height,
src.rsx_address, src.pitch * src.height);
dst.rsx_address, write_length,
src.rsx_address, read_length);
if (!g_cfg.video.force_cpu_blit_processing &&
(dst.dma == CELL_GCM_CONTEXT_DMA_MEMORY_FRAME_BUFFER || src.dma == CELL_GCM_CONTEXT_DMA_MEMORY_FRAME_BUFFER) &&
@ -593,6 +596,14 @@ namespace rsx
return;
}
// Conservative MM flush
rsx::simple_array<utils::address_range64> flush_mm_ranges =
{
utils::address_range64::start_length(reinterpret_cast<u64>(dst.pixels), write_length),
utils::address_range64::start_length(reinterpret_cast<u64>(src.pixels), read_length)
};
rsx::mm_flush(flush_mm_ranges);
std::vector<u8> mirror_tmp;
bool src_is_temp = false;
@ -619,7 +630,7 @@ namespace rsx
const bool interpolate = in_inter == blit_engine::transfer_interpolator::foh;
auto real_dst = dst.pixels;
const auto tiled_region = RSX(ctx)->get_tiled_memory_region(utils::address_range32::start_length(dst.rsx_address, dst.pitch * dst.clip_height));
const auto tiled_region = RSX(ctx)->get_tiled_memory_region(utils::address_range32::start_length(dst.rsx_address, write_length));
std::vector<u8> tmp;
if (tiled_region)

View file

@ -86,7 +86,7 @@ namespace rsx
RSX(ctx)->performance_counters.idle_time += (get_system_time() - start);
}
void semaphore_release(context* ctx, u32 /*reg*/, u32 arg)
void semaphore_release(context* ctx, u32 reg, u32 arg)
{
const u32 offset = REGS(ctx)->semaphore_offset_406e();
@ -122,7 +122,7 @@ namespace rsx
arg = 1;
}
util::write_gcm_label<false, true>(ctx, addr, arg);
util::write_gcm_label<false, true>(ctx, reg, addr, arg);
}
}
}

View file

@ -690,7 +690,7 @@ namespace rsx
});
}
void texture_read_semaphore_release(context* ctx, u32 /*reg*/, u32 arg)
void texture_read_semaphore_release(context* ctx, u32 reg, u32 arg)
{
// Pipeline barrier seems to be equivalent to a SHADER_READ stage barrier.
// Ideally the GPU only needs to have cached all textures declared up to this point before writing the label.
@ -715,15 +715,15 @@ namespace rsx
if (g_cfg.video.strict_rendering_mode) [[ unlikely ]]
{
util::write_gcm_label<true, true>(ctx, addr, arg);
util::write_gcm_label<true, true>(ctx, reg, addr, arg);
}
else
{
util::write_gcm_label<true, false>(ctx, addr, arg);
util::write_gcm_label<true, false>(ctx, reg, addr, arg);
}
}
void back_end_write_semaphore_release(context* ctx, u32 /*reg*/, u32 arg)
void back_end_write_semaphore_release(context* ctx, u32 reg, u32 arg)
{
// Full pipeline barrier. GPU must flush pipeline before writing the label
@ -744,7 +744,7 @@ namespace rsx
}
const u32 val = (arg & 0xff00ff00) | ((arg & 0xff) << 16) | ((arg >> 16) & 0xff);
util::write_gcm_label<true, true>(ctx, addr, val);
util::write_gcm_label<true, true>(ctx, reg, addr, val);
}
void sync(context* ctx, u32, u32)

View file

@ -13,13 +13,13 @@ namespace rsx
namespace util
{
template <bool FlushDMA, bool FlushPipe>
static void write_gcm_label(context* ctx, u32 address, u32 data)
static void write_gcm_label(context* ctx, u32 type, u32 address, u32 data)
{
const bool is_flip_sema = (address == (RSX(ctx)->label_addr + 0x10) || address == (RSX(ctx)->device_addr + 0x30));
if (!is_flip_sema)
{
// First, queue the GPU work. If it flushes the queue for us, the following routines will be faster.
const bool handled = RSX(ctx)->get_backend_config().supports_host_gpu_labels && RSX(ctx)->release_GCM_label(address, data);
const bool handled = RSX(ctx)->get_backend_config().supports_host_gpu_labels && RSX(ctx)->release_GCM_label(type, address, data);
if (vm::_ref<RsxSemaphore>(address) == data)
{

View file

@ -1192,7 +1192,7 @@ bool FragmentProgramDecompiler::handle_tex_srb(u32 opcode)
if (dst.exp_tex)
{
properties.has_exp_tex_op = true;
AddCode("_enable_texture_expand();");
AddCode("_enable_texture_expand($_i);");
}
// Shadow proj

View file

@ -337,21 +337,21 @@ namespace glsl
// Declare special texture control flags
program_common::define_glsl_constants<rsx::texture_control_bits>(OS,
{
{ "GAMMA_R_BIT " , rsx::texture_control_bits::GAMMA_R },
{ "GAMMA_G_BIT " , rsx::texture_control_bits::GAMMA_G },
{ "GAMMA_B_BIT " , rsx::texture_control_bits::GAMMA_B },
{ "GAMMA_A_BIT " , rsx::texture_control_bits::GAMMA_A },
{ "EXPAND_R_BIT" , rsx::texture_control_bits::EXPAND_R },
{ "EXPAND_G_BIT" , rsx::texture_control_bits::EXPAND_G },
{ "EXPAND_B_BIT" , rsx::texture_control_bits::EXPAND_B },
{ "EXPAND_A_BIT" , rsx::texture_control_bits::EXPAND_A },
{ "SEXT_R_BIT" , rsx::texture_control_bits::SEXT_R },
{ "SEXT_G_BIT" , rsx::texture_control_bits::SEXT_G },
{ "SEXT_B_BIT" , rsx::texture_control_bits::SEXT_B },
{ "SEXT_A_BIT" , rsx::texture_control_bits::SEXT_A },
{ "WRAP_S_BIT", rsx::texture_control_bits::WRAP_S },
{ "WRAP_T_BIT", rsx::texture_control_bits::WRAP_T },
{ "WRAP_R_BIT", rsx::texture_control_bits::WRAP_R },
{ "GAMMA_R_BIT ", rsx::texture_control_bits::GAMMA_R },
{ "GAMMA_G_BIT ", rsx::texture_control_bits::GAMMA_G },
{ "GAMMA_B_BIT ", rsx::texture_control_bits::GAMMA_B },
{ "GAMMA_A_BIT ", rsx::texture_control_bits::GAMMA_A },
{ "EXPAND_R_BIT", rsx::texture_control_bits::EXPAND_R },
{ "EXPAND_G_BIT", rsx::texture_control_bits::EXPAND_G },
{ "EXPAND_B_BIT", rsx::texture_control_bits::EXPAND_B },
{ "EXPAND_A_BIT", rsx::texture_control_bits::EXPAND_A },
{ "SEXT_R_BIT", rsx::texture_control_bits::SEXT_R },
{ "SEXT_G_BIT", rsx::texture_control_bits::SEXT_G },
{ "SEXT_B_BIT", rsx::texture_control_bits::SEXT_B },
{ "SEXT_A_BIT", rsx::texture_control_bits::SEXT_A },
{ "WRAP_S_BIT", rsx::texture_control_bits::WRAP_S },
{ "WRAP_T_BIT", rsx::texture_control_bits::WRAP_T },
{ "WRAP_R_BIT", rsx::texture_control_bits::WRAP_R },
{ "ALPHAKILL ", rsx::texture_control_bits::ALPHAKILL },
{ "RENORMALIZE ", rsx::texture_control_bits::RENORMALIZE },
@ -360,7 +360,12 @@ namespace glsl
{ "FILTERED_MAG_BIT", rsx::texture_control_bits::FILTERED_MAG },
{ "FILTERED_MIN_BIT", rsx::texture_control_bits::FILTERED_MIN },
{ "INT_COORDS_BIT ", rsx::texture_control_bits::UNNORMALIZED_COORDS },
{ "CLAMP_COORDS_BIT", rsx::texture_control_bits::CLAMP_TEXCOORDS_BIT }
{ "CLAMP_COORDS_BIT", rsx::texture_control_bits::CLAMP_TEXCOORDS_BIT },
{ "FORMAT_FEATURE_SIGNED_BIT", rsx::texture_control_bits::FF_SIGNED_BIT },
{ "FORMAT_FEATURE_GAMMA_BIT", rsx::texture_control_bits::FF_GAMMA_BIT },
{ "FORMAT_FEATURE_BIASED_RENORMALIZATION_BIT", rsx::texture_control_bits::FF_BIASED_RENORM_BIT },
{ "FORMAT_FEATURE_16BIT_CHANNELS_BIT", rsx::texture_control_bits::FF_16BIT_CHANNELS_BIT }
});
if (props.require_texture_expand)

View file

@ -37,12 +37,17 @@ namespace rsx
WRAP_S,
WRAP_T,
WRAP_R,
FF_SIGNED_BIT,
FF_BIASED_RENORM_BIT,
FF_GAMMA_BIT,
FF_16BIT_CHANNELS_BIT,
GAMMA_CTRL_MASK = (1 << GAMMA_R) | (1 << GAMMA_G) | (1 << GAMMA_B) | (1 << GAMMA_A),
EXPAND_MASK = (1 << EXPAND_R) | (1 << EXPAND_G) | (1 << EXPAND_B) | (1 << EXPAND_A),
EXPAND_OFFSET = EXPAND_A,
SEXT_MASK = (1 << SEXT_R) | (1 << SEXT_G) | (1 << SEXT_B) | (1 << SEXT_A),
SEXT_OFFSET = SEXT_A
SEXT_OFFSET = SEXT_A,
FORMAT_FEATURES_OFFSET = FF_SIGNED_BIT,
};
enum ROP_control_bits : u32

View file

@ -20,13 +20,34 @@ R"(
#define SEXT_MASK (SEXT_R_MASK | SEXT_G_MASK | SEXT_B_MASK | SEXT_A_MASK)
#define FILTERED_MASK (FILTERED_MAG_BIT | FILTERED_MIN_BIT)
#define FORMAT_FEATURE_SIGNED (1 << FORMAT_FEATURE_SIGNED_BIT)
#define FORMAT_FEATURE_GAMMA (1 << FORMAT_FEATURE_GAMMA_BIT)
#define FORMAT_FEATURE_BIASED_RENORMALIZATION (1 << FORMAT_FEATURE_BIASED_RENORMALIZATION_BIT)
#define FORMAT_FEATURE_16BIT_CHANNELS (1 << FORMAT_FEATURE_16BIT_CHANNELS_BIT)
#define FORMAT_FEATURE_MASK (FORMAT_FEATURE_SIGNED | FORMAT_FEATURE_GAMMA | FORMAT_FEATURE_BIASED_RENORMALIZATION | FORMAT_FEATURE_16BIT_CHANNELS)
#ifdef _ENABLE_TEXTURE_EXPAND
// NOTE: BX2 expansion overrides GAMMA correction
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) (TEX_PARAM(index).flags | _texture_flag_override)
uint _texture_flag_erase = 0;
bool _texture_bx2_active = false;
#define _enable_texture_expand(index) \
do { \
if (_test_bit(TEX_PARAM(index).flags, FORMAT_FEATURE_BIASED_RENORMALIZATION_BIT)) { \
_texture_flag_override = SIGN_EXPAND_MASK & (_get_bits(TEX_PARAM(index).remap, 16, 4) << EXPAND_A_BIT); \
_texture_flag_erase = GAMMA_CTRL_MASK; \
_texture_bx2_active = true; \
} \
} while (false)
#define _disable_texture_expand() \
do { \
_texture_flag_override = 0; \
_texture_flag_erase = 0; \
_texture_bx2_active = false; \
} while (false)
#define TEX_FLAGS(index) ((TEX_PARAM(index).flags & ~(_texture_flag_erase)) | _texture_flag_override)
#else
#define TEX_FLAGS(index) TEX_PARAM(index).flags
#define TEX_FLAGS(index) (TEX_PARAM(index).flags)
#endif
#define TEX_NAME(index) tex##index
@ -175,15 +196,24 @@ vec4 _texcoord_xform_shadow(const in vec4 coord4, const in sampler_info params)
vec4 _sext_unorm8x4(const in vec4 x)
{
// TODO: Handle clamped sign-extension
const vec4 bits = floor(fma(x, vec4(255.f), vec4(0.5f)));
const bvec4 sign_check = lessThan(bits, vec4(128.f));
const vec4 ret = _select(bits - 256.f, bits, sign_check);
return ret / 127.f;
const uint shift = 32 - 8; // sext 8-bit value into 32-bit container
const uvec4 ubits = uvec4(floor(fma(x, vec4(255.f), vec4(0.5f))));
const ivec4 ibits = ivec4(ubits << shift);
return (ibits >> shift) / 127.f;
}
vec4 _sext_unorm16x4(const in vec4 x)
{
// TODO: Handle clamped sign-extension
const uint shift = 32 - 16; // sext 16-bit value into 32-bit container
const uvec4 ubits = uvec4(floor(fma(x, vec4(65535.f), vec4(0.5f))));
const ivec4 ibits = ivec4(ubits << shift);
return (ibits >> shift) / 32767.f;
}
vec4 _process_texel(in vec4 rgba, const in uint control_bits)
{
if (control_bits == 0)
if ((control_bits & ~FORMAT_FEATURE_MASK) == 0u)
{
return rgba;
}
@ -210,31 +240,46 @@ vec4 _process_texel(in vec4 rgba, const in uint control_bits)
uvec4 mask;
vec4 convert;
uint op_mask = control_bits & uint(SIGN_EXPAND_MASK);
if (op_mask != 0u)
{
// Expand to signed normalized by decompressing the signal
mask = uvec4(op_mask) & uvec4(EXPAND_R_MASK, EXPAND_G_MASK, EXPAND_B_MASK, EXPAND_A_MASK);
convert = (rgba * 2.f - 1.f);
rgba = _select(rgba, convert, notEqual(mask, uvec4(0)));
}
uint op_mask = control_bits & uint(SEXT_MASK);
uint ch_mask = 0xFu;
op_mask = control_bits & uint(SEXT_MASK);
if (op_mask != 0u)
{
// Sign-extend the input signal
mask = uvec4(op_mask) & uvec4(SEXT_R_MASK, SEXT_G_MASK, SEXT_B_MASK, SEXT_A_MASK);
convert = _sext_unorm8x4(rgba);
if (_test_bit(control_bits, FORMAT_FEATURE_16BIT_CHANNELS_BIT))
convert = _sext_unorm16x4(rgba);
else
convert = _sext_unorm8x4(rgba);
rgba = _select(rgba, convert, notEqual(mask, uvec4(0)));
ch_mask &= ~(op_mask >> SEXT_A_BIT);
}
op_mask = control_bits & uint(GAMMA_CTRL_MASK);
op_mask = control_bits & uint(GAMMA_CTRL_MASK) & (ch_mask << GAMMA_A_BIT);
if (op_mask != 0u)
{
// Gamma correction
mask = uvec4(op_mask) & uvec4(GAMMA_R_MASK, GAMMA_G_MASK, GAMMA_B_MASK, GAMMA_A_MASK);
convert = srgb_to_linear(rgba);
return _select(rgba, convert, notEqual(mask, uvec4(0)));
rgba = _select(rgba, convert, notEqual(mask, uvec4(0)));
ch_mask &= ~(op_mask >> GAMMA_A_BIT);
}
op_mask = control_bits & uint(SIGN_EXPAND_MASK) & (ch_mask << EXPAND_A_BIT);
if (op_mask != 0u)
{
// Expand to signed normalized by decompressing the signal
mask = uvec4(op_mask) & uvec4(EXPAND_R_MASK, EXPAND_G_MASK, EXPAND_B_MASK, EXPAND_A_MASK);
#ifdef _ENABLE_TEXTURE_EXPAND
if (_texture_bx2_active)
convert = (rgba * 2.f - 1.f);
else
#endif
if (_test_bit(control_bits, FORMAT_FEATURE_16BIT_CHANNELS_BIT))
convert = (floor(fma(rgba, vec4(65535.f), vec4(0.5f))) - 32768.f) / 32767.f;
else
convert = (floor(fma(rgba, vec4(255.f), vec4(0.5f))) - 128.f) / 127.f;
rgba = _select(rgba, convert, notEqual(mask, uvec4(0)));
}
return rgba;

View file

@ -9,7 +9,7 @@ namespace glsl
glsl_compute_program = 2,
// Meta
glsl_invalid_program = 0xff
glsl_invalid_program = 7
};
enum glsl_rules : unsigned char

View file

@ -1,5 +1,6 @@
#include "stdafx.h"
#include "ProgramStateCache.h"
#include "FragmentProgramDecompiler.h"
#include "Emu/system_config.h"
#include "Emu/RSX/Core/RSXDriverState.h"
#include "util/sysinfo.hpp"
@ -637,58 +638,51 @@ fragment_program_utils::fragment_program_metadata fragment_program_utils::analys
while (true)
{
const auto inst = v128::loadu(instBuffer, index);
const auto d0 = OPDEST::from_be32(inst._u32[0]);
const auto opcode = static_cast<rsx::assembler::FP_opcode>(d0.opcode);
// Check for opcode high bit which indicates a branch instructions (opcode 0x40...0x45)
if (inst._u32[2] & (1 << 23))
switch (opcode)
{
case RSX_FP_OPCODE_TEX:
case RSX_FP_OPCODE_TEXBEM:
case RSX_FP_OPCODE_TXP:
case RSX_FP_OPCODE_TXPBEM:
case RSX_FP_OPCODE_TXD:
case RSX_FP_OPCODE_TXB:
case RSX_FP_OPCODE_TXL:
result.referenced_textures_mask |= (1 << d0.tex_num);
result.has_tex_bx2_conv |= !!d0.exp_tex;
break;
case RSX_FP_OPCODE_PK4:
case RSX_FP_OPCODE_UP4:
case RSX_FP_OPCODE_PK2:
case RSX_FP_OPCODE_UP2:
case RSX_FP_OPCODE_PKB:
case RSX_FP_OPCODE_UPB:
case RSX_FP_OPCODE_PK16:
case RSX_FP_OPCODE_UP16:
case RSX_FP_OPCODE_PKG:
case RSX_FP_OPCODE_UPG:
result.has_pack_instructions = true;
break;
case RSX_FP_OPCODE_BRK:
case RSX_FP_OPCODE_CAL:
case RSX_FP_OPCODE_IFE:
case RSX_FP_OPCODE_LOOP:
case RSX_FP_OPCODE_REP:
case RSX_FP_OPCODE_RET:
// NOTE: Jump instructions are not yet proved to work outside of loops and if/else blocks
// Otherwise we would need to follow the execution chain
result.has_branch_instructions = true;
break;
}
else
{
const u32 opcode = (inst._u32[0] >> 16) & 0x3F;
if (opcode)
{
switch (opcode)
{
case RSX_FP_OPCODE_TEX:
case RSX_FP_OPCODE_TEXBEM:
case RSX_FP_OPCODE_TXP:
case RSX_FP_OPCODE_TXPBEM:
case RSX_FP_OPCODE_TXD:
case RSX_FP_OPCODE_TXB:
case RSX_FP_OPCODE_TXL:
{
//Bits 17-20 of word 1, swapped within u16 sections
//Bits 16-23 are swapped into the upper 8 bits (24-31)
const u32 tex_num = (inst._u32[0] >> 25) & 15;
result.referenced_textures_mask |= (1 << tex_num);
break;
}
case RSX_FP_OPCODE_PK4:
case RSX_FP_OPCODE_UP4:
case RSX_FP_OPCODE_PK2:
case RSX_FP_OPCODE_UP2:
case RSX_FP_OPCODE_PKB:
case RSX_FP_OPCODE_UPB:
case RSX_FP_OPCODE_PK16:
case RSX_FP_OPCODE_UP16:
case RSX_FP_OPCODE_PKG:
case RSX_FP_OPCODE_UPG:
{
result.has_pack_instructions = true;
break;
}
}
}
if (is_any_src_constant(inst))
{
//Instruction references constant, skip one slot occupied by data
index++;
result.program_constants_buffer_length += 16;
}
if (rsx::assembler::FP::get_operand_count(opcode) > 0 &&
is_any_src_constant(inst))
{
// Instruction references constant, skip one slot occupied by data
index++;
result.program_constants_buffer_length += 16;
}
index++;

View file

@ -59,6 +59,7 @@ namespace program_hash_util
bool has_pack_instructions;
bool has_branch_instructions;
bool has_tex_bx2_conv;
bool is_nop_shader; // Does this affect Z-pass testing???
};

View file

@ -54,6 +54,14 @@ union OPDEST
u32 : 9;
u32 write_mask : 4;
};
static OPDEST from_be32(u32 be_word)
{
const u32 _hex =
((be_word & 0x00FF00FF) << 8) |
((be_word & 0xFF00FF00) >> 8);
return OPDEST{ .HEX = _hex };
}
};
union SRC0

View file

@ -2,8 +2,24 @@
#include "RSXTexture.h"
#include "rsx_utils.h"
#include "Common/TextureUtils.h"
#include "Program/GLSLCommon.h"
#include "Emu/system_config.h"
#include "util/simd.hpp"
#if !defined(_MSC_VER)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wold-style-cast"
#endif
#if defined(ARCH_ARM64)
#if !defined(_MSC_VER)
#pragma GCC diagnostic ignored "-Wstrict-aliasing"
#endif
#undef FORCE_INLINE
#include "Emu/CPU/sse2neon.h"
#endif
namespace rsx
{
@ -49,6 +65,65 @@ namespace rsx
return ((registers[NV4097_SET_TEXTURE_FORMAT + (m_index * 8)] >> 8) & 0xff);
}
texture_format_ex fragment_texture::format_ex() const
{
const auto format_bits = format();
const auto base_format = format_bits & ~(CELL_GCM_TEXTURE_UN | CELL_GCM_TEXTURE_LN);
const auto format_features = rsx::get_format_features(base_format);
if (format_features == 0)
{
return { format_bits };
}
// 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/BX2
// Games using mixed flags: (See Resistance 3 for GAMMA/BX2 relationship, UE3 for BX2 effect)
u32 argb_signed_ = 0;
u32 unsigned_remap_ = 0;
u32 gamma_ = 0;
if (format_features & RSX_FORMAT_FEATURE_SIGNED_COMPONENTS)
{
// Tests show this is applied pre-readout. It's just a property of the incoming bytes and is therefore subject to remap.
argb_signed_ = decoded_remap().shuffle_mask_bits(argb_signed());
}
if (format_features & RSX_FORMAT_FEATURE_GAMMA_CORRECTION)
{
// Tests show this is applied post-readout. It's a property of the final value stored in the register and is not remapped.
// NOTE: GAMMA correction has no algorithmic effect on constants (0 and 1) so we need not mask it out for correctness.
gamma_ = gamma() & ~(argb_signed_);
}
if (format_features & RSX_FORMAT_FEATURE_BIASED_NORMALIZATION)
{
// The renormalization flag applies to all channels. It is weaker than the other flags.
// This applies on input and is subject to remap overrides
if (unsigned_remap() == CELL_GCM_TEXTURE_UNSIGNED_REMAP_BIASED)
{
unsigned_remap_ = decoded_remap().shuffle_mask_bits(0xFu) & ~(argb_signed_ | gamma_);
}
}
u32 format_convert = gamma_;
// The options are mutually exclusive
ensure((argb_signed_ & gamma_) == 0);
ensure((argb_signed_ & unsigned_remap_) == 0);
ensure((gamma_ & unsigned_remap_) == 0);
// NOTE: Hardware tests show that remapping bypasses the channel swizzles completely
format_convert |= (argb_signed_ << texture_control_bits::SEXT_OFFSET);
format_convert |= (unsigned_remap_ << texture_control_bits::EXPAND_OFFSET);
texture_format_ex result { format_bits };
result.features = format_features;
result.texel_remap_control = format_convert;
return result;
}
bool fragment_texture::is_compressed_format() const
{
int texture_format = format() & ~(CELL_GCM_TEXTURE_LN | CELL_GCM_TEXTURE_UN);
@ -291,7 +366,68 @@ namespace rsx
u32 fragment_texture::border_color() const
{
return registers[NV4097_SET_TEXTURE_BORDER_COLOR + (m_index * 8)];
const u32 raw = registers[NV4097_SET_TEXTURE_BORDER_COLOR + (m_index * 8)];
const u32 sext = argb_signed();
if (!sext) [[ likely ]]
{
return raw;
}
// Border color is broken on PS3. The SNORM behavior is completely broken and behaves like BIASED renormalization instead.
// To solve the mismatch, we need to first do a bit expansion on the value then store it as sign extended. The second part is a natural part of numbers on a binary system, so we only need to do the former.
// Note that the input color is in BE order (BGRA) so we reverse the mask to match.
static constexpr u32 expand4_lut[16] =
{
0x00000000u, // 0000
0xFF000000u, // 0001
0x00FF0000u, // 0010
0xFFFF0000u, // 0011
0x0000FF00u, // 0100
0xFF00FF00u, // 0101
0x00FFFF00u, // 0110
0xFFFFFF00u, // 0111
0x000000FFu, // 1000
0xFF0000FFu, // 1001
0x00FF00FFu, // 1010
0xFFFF00FFu, // 1011
0x0000FFFFu, // 1100
0xFF00FFFFu, // 1101
0x00FFFFFFu, // 1110
0xFFFFFFFFu // 1111
};
// Bit pattern expand
const u32 mask = expand4_lut[sext];
// Now we perform the compensation operation
// BIAS operation = (V - 128 / 127)
// Load
const __m128i _0 = _mm_setzero_si128();
const __m128i _128 = _mm_set1_epi32(128);
// Explode the bytes.
__m128i v = _mm_cvtsi32_si128(raw);
v = _mm_unpacklo_epi8(v, _0);
v = _mm_unpacklo_epi16(v, _0);
// Conversion: x = (y - 128)
v = _mm_sub_epi32(v, _128);
// Convert to signed encoding (reverse sext)
v = _mm_slli_epi32(v, 24);
v = _mm_srli_epi32(v, 24);
// Pack down
v = _mm_packs_epi32(v, _0);
v = _mm_packus_epi16(v, _0);
// Read
const u32 conv = _mm_cvtsi128_si32(v);
// Merge
return (conv & mask) | (raw & ~mask);
}
color4f fragment_texture::remapped_border_color() const

View file

@ -4,6 +4,8 @@
namespace rsx
{
struct texture_format_ex;
class fragment_texture
{
protected:
@ -33,6 +35,7 @@ namespace rsx
// cubemap as a separate dimension.
rsx::texture_dimension_extended get_extended_texture_dimension() const;
u8 format() const;
texture_format_ex format_ex() const;
bool is_compressed_format() const;
u16 mipmap() const;

View file

@ -2321,55 +2321,17 @@ namespace rsx
}
}
if (rsx::is_int8_remapped_format(format))
if (const auto& format_ex = sampler_descriptors[i]->format_ex; format_ex.features != 0)
{
// 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)
texture_control |= format_ex.texel_remap_control;
texture_control |= format_ex.features << texture_control_bits::FORMAT_FEATURES_OFFSET;
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)
if (current_fp_metadata.has_tex_bx2_conv)
{
// 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);
const u32 remap_hi = tex.decoded_remap().shuffle_mask_bits(0xFu);
current_fragment_program.texture_params[i].remap &= ~(0xFu << 16u);
current_fragment_program.texture_params[i].remap |= (remap_hi << 16u);
}
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;

View file

@ -380,7 +380,7 @@ namespace rsx
flags32_t read_barrier(u32 memory_address, u32 memory_range, bool unconditional);
virtual void write_barrier(u32 /*memory_address*/, u32 /*memory_range*/) {}
virtual void sync_hint(FIFO::interrupt_hint hint, reports::sync_hint_payload_t payload);
virtual bool release_GCM_label(u32 /*address*/, u32 /*value*/) { return false; }
virtual bool release_GCM_label(u32 /*type*/, u32 /*address*/, u32 /*value*/) { return false; }
protected:

View file

@ -1,5 +1,6 @@
#include "stdafx.h"
#include "../Common/BufferUtils.h"
#include "../Program/GLSLCommon.h"
#include "../rsx_methods.h"
#include "VKAsyncScheduler.h"
@ -275,7 +276,7 @@ void VKGSRender::load_texture_env()
auto get_border_color = [&](const rsx::Texture auto& tex)
{
return m_device->get_custom_border_color_support().require_border_color_remap
return m_device->get_custom_border_color_support().require_border_color_remap
? tex.remapped_border_color()
: rsx::decode_border_color(tex.border_color());
};
@ -307,6 +308,8 @@ void VKGSRender::load_texture_env()
if (sampler_state->validate())
{
sampler_state->format_ex = tex.format_ex();
if (sampler_state->is_cyclic_reference)
{
check_for_cyclic_refs |= true;
@ -324,7 +327,7 @@ void VKGSRender::load_texture_env()
f32 min_lod = 0.f, max_lod = 0.f;
f32 lod_bias = 0.f;
const u32 texture_format = tex.format() & ~(CELL_GCM_TEXTURE_UN | CELL_GCM_TEXTURE_LN);
const u32 texture_format = sampler_state->format_ex.format();
VkBool32 compare_enabled = VK_FALSE;
VkCompareOp depth_compare_mode = VK_COMPARE_OP_NEVER;
@ -350,7 +353,8 @@ void VKGSRender::load_texture_env()
if (sampler_state->format_class == RSX_FORMAT_CLASS_COLOR) [[likely]]
{
// Most PS3-like formats can be linearly filtered without problem
can_sample_linear = true;
// Exclude textures that require SNORM conversion however
can_sample_linear = (sampler_state->format_ex.texel_remap_control & rsx::texture_control_bits::SEXT_MASK) == 0;
}
else if (sampler_state->format_class != rsx::classify_format(texture_format) &&
(texture_format == CELL_GCM_TEXTURE_A8R8G8B8 || texture_format == CELL_GCM_TEXTURE_D8R8G8B8))

View file

@ -1541,7 +1541,7 @@ std::pair<volatile vk::host_data_t*, VkBuffer> VKGSRender::map_host_object_data(
return { m_host_dma_ctrl->host_ctx(), m_host_object_data->value };
}
bool VKGSRender::release_GCM_label(u32 address, u32 args)
bool VKGSRender::release_GCM_label(u32 type, u32 address, u32 args)
{
if (!backend_config.supports_host_gpu_labels)
{
@ -1550,7 +1550,7 @@ bool VKGSRender::release_GCM_label(u32 address, u32 args)
auto host_ctx = ensure(m_host_dma_ctrl->host_ctx());
if (host_ctx->texture_loads_completed())
if (type == NV4097_TEXTURE_READ_SEMAPHORE_RELEASE && host_ctx->texture_loads_completed())
{
// All texture loads already seen by the host GPU
// Wait for all previously submitted labels to be flushed
@ -1572,13 +1572,10 @@ bool VKGSRender::release_GCM_label(u32 address, u32 args)
const auto release_event_id = host_ctx->on_label_acquire();
vk::insert_global_memory_barrier(*m_current_command_buffer);
if (host_ctx->has_unflushed_texture_loads())
{
if (vk::is_renderpass_open(*m_current_command_buffer))
{
vk::end_renderpass(*m_current_command_buffer);
}
vkCmdUpdateBuffer(*m_current_command_buffer, mapping.second->value, mapping.first, 4, &write_data);
flush_command_queue();
}

View file

@ -221,7 +221,7 @@ private:
void frame_context_cleanup(vk::frame_context_t *ctx);
void advance_queued_frames();
void present(vk::frame_context_t *ctx);
void reinitialize_swapchain();
bool reinitialize_swapchain();
vk::viewable_image* get_present_source(vk::present_surface_info* info, const rsx::avconf& avconfig);
@ -254,7 +254,7 @@ public:
// Sync
void write_barrier(u32 address, u32 range) override;
void sync_hint(rsx::FIFO::interrupt_hint hint, rsx::reports::sync_hint_payload_t payload) override;
bool release_GCM_label(u32 address, u32 data) override;
bool release_GCM_label(u32 type, u32 address, u32 data) override;
void begin_occlusion_query(rsx::reports::occlusion_query_info* query) override;
void end_occlusion_query(rsx::reports::occlusion_query_info* query) override;

View file

@ -33,7 +33,7 @@ namespace
}
}
void VKGSRender::reinitialize_swapchain()
bool VKGSRender::reinitialize_swapchain()
{
m_swapchain_dims.width = m_frame->client_width();
m_swapchain_dims.height = m_frame->client_height();
@ -44,7 +44,7 @@ void VKGSRender::reinitialize_swapchain()
if (m_swapchain_dims.width == 0 || m_swapchain_dims.height == 0)
{
swapchain_unavailable = true;
return;
return false;
}
// NOTE: This operation will create a hard sync point
@ -97,7 +97,7 @@ void VKGSRender::reinitialize_swapchain()
{
rsx_log.warning("Swapchain initialization failed. Request ignored [%dx%d]", m_swapchain_dims.width, m_swapchain_dims.height);
swapchain_unavailable = true;
return;
return false;
}
// Re-initialize CPU frame contexts
@ -135,6 +135,7 @@ void VKGSRender::reinitialize_swapchain()
swapchain_unavailable = false;
should_reinitialize_swapchain = false;
return true;
}
void VKGSRender::present(vk::frame_context_t *ctx)
@ -426,11 +427,32 @@ void VKGSRender::flip(const rsx::display_flip_info_t& info)
if (swapchain_unavailable || should_reinitialize_swapchain)
{
reinitialize_swapchain();
// Reinitializing the swapchain is a failable operation. However, not all failures are fatal (e.g minimized window).
// In the worst case, we can have the driver refuse to create the swapchain while we already deleted the previous one.
// In such scenarios, we have to retry a few times before giving up as we cannot proceed without a swapchain.
for (int i = 0; i < 10; ++i)
{
if (reinitialize_swapchain() || m_current_frame)
{
// If m_current_frame exists, then the initialization failure is non-fatal. Proceed as usual.
break;
}
if (Emu.IsStopped())
{
m_frame->flip(m_context);
rsx::thread::flip(info);
return;
}
std::this_thread::sleep_for(100ms);
}
}
m_profiler.start();
ensure(m_current_frame, "Invalid swapchain setup. Resizing the game window failed.");
if (m_current_frame == &m_aux_frame_context)
{
m_current_frame = &m_frame_context_storage[m_current_queue_index];
@ -582,6 +604,7 @@ void VKGSRender::flip(const rsx::display_flip_info_t& info)
rsx_log.warning("vkAcquireNextImageKHR failed with VK_ERROR_OUT_OF_DATE_KHR. Flip request ignored until surface is recreated.");
swapchain_unavailable = true;
reinitialize_swapchain();
ensure(m_current_frame, "Could not reinitialize swapchain after VK_ERROR_OUT_OF_DATE_KHR signal!");
continue;
default:
vk::die_with_error(status);

View file

@ -182,16 +182,8 @@ namespace vk
return found == m_generic_sampler_pool.end() ? nullptr : found->second.get();
}
const auto block = m_custom_color_sampler_pool.equal_range(key.base_key);
for (auto it = block.first; it != block.second; ++it)
{
if (it->second->key.border_color_key == key.border_color_key)
{
return it->second.get();
}
}
return nullptr;
const auto found = m_custom_color_sampler_pool.find(key);
return found == m_custom_color_sampler_pool.end() ? nullptr : found->second.get();
}
cached_sampler_object_t* sampler_pool_t::emplace(const sampler_pool_key_t& key, std::unique_ptr<cached_sampler_object_t>& object)
@ -204,7 +196,7 @@ namespace vk
return iterator->second.get();
}
const auto [iterator, _unused] = m_custom_color_sampler_pool.emplace(key.base_key, std::move(object));
const auto [iterator, _unused] = m_custom_color_sampler_pool.emplace(key, std::move(object));
return iterator->second.get();
}

View file

@ -64,6 +64,22 @@ namespace vk
{
u64 base_key;
u64 border_color_key;
bool operator == (const sampler_pool_key_t& that) const
{
return this->base_key == that.base_key &&
this->border_color_key == that.border_color_key;
}
};
struct sampler_pool_key_hash
{
size_t operator()(const vk::sampler_pool_key_t& k) const noexcept
{
usz result = k.base_key;
result ^= k.border_color_key + 0x9e3779b97f4a7c15ULL + (result << 6) + (result >> 2);
return result;
}
};
struct cached_sampler_object_t : public vk::sampler, public rsx::ref_counted
@ -75,7 +91,7 @@ namespace vk
class sampler_pool_t
{
std::unordered_map<u64, std::unique_ptr<cached_sampler_object_t>> m_generic_sampler_pool;
std::unordered_map<u64, std::unique_ptr<cached_sampler_object_t>> m_custom_color_sampler_pool;
std::unordered_map<sampler_pool_key_t, std::unique_ptr<cached_sampler_object_t>, sampler_pool_key_hash> m_custom_color_sampler_pool;
public:

View file

@ -39,6 +39,34 @@ namespace rsx
return remapped;
}
/**
* Remap color channel bits based on a remap vector. The output is a normalized selector of each color channel with spread.
* The input bits are an action selector. e.g a mask of channels that need to be interpreted as SNORM or BX2
* The output is a final mask on which post-sampling channels the operation applies to.
* Examples:
* - If we have remap as [ 1 R R R ] and mask of R (0010) then we get 1110. Remapper spreads 'R' action to all channels where it should apply.
*/
u32 shuffle_mask_bits(u32 bits) const
{
if (!bits || encoded == RSX_TEXTURE_REMAP_IDENTITY) [[likely]]
{
return bits;
}
u32 result = 0;
for (u8 channel = 0; channel < 4; ++channel)
{
if (control_map[channel] != CELL_GCM_TEXTURE_REMAP_REMAP || // Channel not read from input
(bits & (1u << channel_map[channel])) == 0) // Input channel is not enabled in the mask
{
continue;
}
result |= (1u << channel);
}
return result;
}
template <typename T>
requires std::is_integral_v<T> || std::is_floating_point_v<T>
std::array<T, 4> remap(const std::array<T, 4>& components) const

View file

@ -252,7 +252,7 @@ void init_fxo_for_exec(utils::serial* ar, bool full = false)
// Reserved area
if (!load_and_check_reserved(*ar, advance))
{
sys_log.error("Potential failure to load savestate: padding buyes are not 0. %s", *ar);
sys_log.error("Potential failure to load savestate: padding bytes are not 0. %s", *ar);
}
}
}
@ -310,7 +310,7 @@ static void fixup_settings(const psf::registry* _psf)
if (g_cfg.net.net_active == np_internet_status::disabled && g_cfg.net.psn_status != np_psn_status::disabled)
{
sys_log.warning("Net status was set to disconnected so psn status was disabled");
sys_log.warning("Net status was set to disconnected so PSN status was disabled.");
g_cfg.net.psn_status.set(np_psn_status::disabled);
}
}
@ -4642,7 +4642,7 @@ game_boot_result Emulator::InsertDisc(const std::string& path)
else
{
// TODO: find out where other discs are mounted
sys_log.todo("Mounting non-ps2/ps3 disc in dev_bdvd. Is this correct? (path='%s')", disc_root);
sys_log.todo("Mounting non-PS2/PS3 disc in dev_bdvd. Is this correct? (path='%s')", disc_root);
ensure(vfs::mount("/dev_bdvd", disc_root));
}

View file

@ -248,7 +248,7 @@ struct cfg_root : cfg::node
cfg::string audio_device{ this, "Audio Device", "@@@default@@@", true };
cfg::_int<0, 200> volume{ this, "Master Volume", 100, true };
cfg::_bool enable_buffering{ this, "Enable Buffering", true, true };
cfg::_int <4, 250> desired_buffer_duration{ this, "Desired Audio Buffer Duration", 100, true };
cfg::_int <4, 250> desired_buffer_duration{ this, "Desired Audio Buffer Duration", 34, true };
cfg::_bool enable_time_stretching{ this, "Enable Time Stretching", false, true };
cfg::_bool disable_sampling_skip{ this, "Disable Sampling Skip", false, true };
cfg::_int<0, 100> time_stretching_threshold{ this, "Time Stretching Threshold", 75, true };

View file

@ -3,6 +3,7 @@
#include "util/types.hpp"
#include <string>
#include <set>
#include <vector>
enum class game_content_type
{

View file

@ -0,0 +1,146 @@
#include "mouse_gyro_handler.h"
#include <QEvent>
#include <QMouseEvent>
#include <QWheelEvent>
#include <QWindow>
#include <algorithm>
void mouse_gyro_handler::clear()
{
active = false;
reset = false;
gyro_x = DEFAULT_MOTION_X;
gyro_y = DEFAULT_MOTION_Y;
gyro_z = DEFAULT_MOTION_Z;
}
bool mouse_gyro_handler::toggle_enabled()
{
enabled = !enabled;
clear();
return enabled;
}
void mouse_gyro_handler::set_gyro_active()
{
active = true;
}
void mouse_gyro_handler::set_gyro_reset()
{
active = false;
reset = true;
}
void mouse_gyro_handler::set_gyro_xz(s32 off_x, s32 off_y)
{
if (!active)
return;
gyro_x = static_cast<u16>(std::clamp(off_x, 0, DEFAULT_MOTION_X * 2 - 1));
gyro_z = static_cast<u16>(std::clamp(off_y, 0, DEFAULT_MOTION_Z * 2 - 1));
}
void mouse_gyro_handler::set_gyro_y(s32 steps)
{
if (!active)
return;
gyro_y = static_cast<u16>(std::clamp(gyro_y + steps, 0, DEFAULT_MOTION_Y * 2 - 1));
}
void mouse_gyro_handler::handle_event(QEvent* ev, const QWindow& win)
{
if (!enabled)
return;
// Mouse-based motion input.
// Captures mouse events while the game window is focused.
// Updates motion sensor values via mouse position and mouse wheel while RMB is held.
// Intentionally independent of chosen pad configuration.
switch (ev->type())
{
case QEvent::MouseButtonPress:
{
auto* e = static_cast<QMouseEvent*>(ev);
if (e->button() == Qt::RightButton)
{
// Enable mouse-driven gyro emulation while RMB is held.
set_gyro_active();
}
break;
}
case QEvent::MouseButtonRelease:
{
auto* e = static_cast<QMouseEvent*>(ev);
if (e->button() == Qt::RightButton)
{
// Disable gyro emulation and request a one-shot motion reset.
set_gyro_reset();
}
break;
}
case QEvent::MouseMove:
{
auto* e = static_cast<QMouseEvent*>(ev);
// Track cursor offset from window center.
const QPoint center(win.width() / 2, win.height() / 2);
const QPoint cur = e->position().toPoint();
const s32 off_x = cur.x() - center.x() + DEFAULT_MOTION_X;
const s32 off_y = cur.y() - center.y() + DEFAULT_MOTION_Z;
// Determine motion from relative mouse position while gyro emulation is active.
set_gyro_xz(off_x, off_y);
break;
}
case QEvent::Wheel:
{
auto* e = static_cast<QWheelEvent*>(ev);
// Track mouse wheel steps.
const s32 steps = e->angleDelta().y() / 120;
// Accumulate mouse wheel steps while gyro emulation is active.
set_gyro_y(steps);
break;
}
default:
{
break;
}
}
}
void mouse_gyro_handler::apply_gyro(const std::shared_ptr<Pad>& pad)
{
if (!enabled)
return;
if (!pad || !pad->is_connected())
return;
// Inject mouse-based motion sensor values into pad sensors for gyro emulation.
// The Qt frontend maps cursor offset and wheel input to absolute motion values while RMB is held.
if (reset)
{
// RMB released → reset motion
pad->m_sensors[0].m_value = DEFAULT_MOTION_X;
pad->m_sensors[1].m_value = DEFAULT_MOTION_Y;
pad->m_sensors[2].m_value = DEFAULT_MOTION_Z;
clear();
}
else
{
// RMB held → accumulate motion
// Axes have been chosen as tested in Sly 4 minigames. Top-down view motion uses X/Z axes.
pad->m_sensors[0].m_value = gyro_x; // Mouse X → Motion X
pad->m_sensors[1].m_value = gyro_y; // Mouse Wheel → Motion Y
pad->m_sensors[2].m_value = gyro_z; // Mouse Y → Motion Z
}
}

View file

@ -0,0 +1,33 @@
#pragma once
#include "util/types.hpp"
#include "util/atomic.hpp"
#include "Emu/Io/pad_types.h"
class QEvent;
class QWindow;
// Mouse-based motion sensor emulation state.
class mouse_gyro_handler
{
private:
atomic_t<bool> enabled = false; // Whether mouse-based gyro emulation mode has been enabled by using the associated hotkey
atomic_t<bool> active = false; // Whether right mouse button is currently held (gyro active)
atomic_t<bool> reset = false; // One-shot reset request on right mouse button release
atomic_t<s32> gyro_x = DEFAULT_MOTION_X; // Accumulated from mouse X position relative to center
atomic_t<s32> gyro_y = DEFAULT_MOTION_Y; // Accumulated from mouse wheel delta
atomic_t<s32> gyro_z = DEFAULT_MOTION_Z; // Accumulated from mouse Y position relative to center
void set_gyro_active();
void set_gyro_reset();
void set_gyro_xz(s32 off_x, s32 off_y);
void set_gyro_y(s32 steps);
public:
void clear();
bool toggle_enabled();
void handle_event(QEvent* ev, const QWindow& win);
void apply_gyro(const std::shared_ptr<Pad>& pad);
};

View file

@ -81,6 +81,9 @@ void pad_thread::Init()
{
std::lock_guard lock(pad::g_pad_mutex);
// Reset mouse-based gyro state
m_mouse_gyro.clear();
// Cache old settings if possible
std::array<pad_setting, CELL_PAD_MAX_PORT_NUM> pad_settings;
for (u32 i = 0; i < CELL_PAD_MAX_PORT_NUM; i++) // max 7 pads
@ -606,6 +609,10 @@ void pad_thread::operator()()
if (Emu.IsRunning())
{
update_pad_states();
// Apply mouse-based gyro emulation.
// Intentionally bound to Player 1 only.
m_mouse_gyro.apply_gyro(m_pads[0]);
}
m_info.now_connect = connected_devices + num_ldd_pad;
@ -624,15 +631,18 @@ void pad_thread::operator()()
if (!pad->is_connected())
continue;
for (const auto& button : pad->m_buttons)
for (const Button& button : pad->m_buttons)
{
if (button.m_pressed && (
button.m_outKeyCode == CELL_PAD_CTRL_CROSS ||
button.m_outKeyCode == CELL_PAD_CTRL_CIRCLE ||
button.m_outKeyCode == CELL_PAD_CTRL_TRIANGLE ||
button.m_outKeyCode == CELL_PAD_CTRL_SQUARE ||
button.m_outKeyCode == CELL_PAD_CTRL_START ||
button.m_outKeyCode == CELL_PAD_CTRL_SELECT))
(button.m_offset == CELL_PAD_BTN_OFFSET_DIGITAL1 && (
button.m_outKeyCode == CELL_PAD_CTRL_START ||
button.m_outKeyCode == CELL_PAD_CTRL_SELECT)) ||
(button.m_offset == CELL_PAD_BTN_OFFSET_DIGITAL2 && (
button.m_outKeyCode == CELL_PAD_CTRL_CROSS ||
button.m_outKeyCode == CELL_PAD_CTRL_CIRCLE ||
button.m_outKeyCode == CELL_PAD_CTRL_TRIANGLE ||
button.m_outKeyCode == CELL_PAD_CTRL_SQUARE))
))
{
any_button_pressed = true;
break;
@ -669,7 +679,7 @@ void pad_thread::operator()()
break;
}
for (const auto& button : pad->m_buttons)
for (const Button& button : pad->m_buttons)
{
if (button.m_offset == CELL_PAD_BTN_OFFSET_DIGITAL1 && button.m_outKeyCode == CELL_PAD_CTRL_PS && button.m_pressed)
{
@ -728,7 +738,7 @@ void pad_thread::operator()()
if (!pad->is_connected())
continue;
for (const auto& button : pad->m_buttons)
for (const Button& button : pad->m_buttons)
{
if (button.m_offset == CELL_PAD_BTN_OFFSET_DIGITAL1 && button.m_outKeyCode == CELL_PAD_CTRL_START && button.m_pressed)
{

View file

@ -5,6 +5,7 @@
#include "Emu/Io/pad_types.h"
#include "Emu/Io/pad_config.h"
#include "Emu/Io/pad_config_types.h"
#include "Input/mouse_gyro_handler.h"
#include "Utilities/mutex.h"
#include <map>
@ -41,6 +42,8 @@ public:
static auto constexpr thread_name = "Pad Thread"sv;
mouse_gyro_handler& get_mouse_gyro() { return m_mouse_gyro; }
protected:
void Init();
void InitLddPad(u32 handle, const u32* port_status);
@ -67,6 +70,8 @@ private:
bool m_resume_emulation_flag = false;
bool m_ps_button_pressed = false;
atomic_t<bool> m_home_menu_open = false;
mouse_gyro_handler m_mouse_gyro;
};
namespace pad

View file

@ -13,16 +13,16 @@ void unload_iso();
struct iso_extent_info
{
u64 start;
u64 size;
u64 start = 0;
u64 size = 0;
};
struct iso_fs_metadata
{
std::string name;
s64 time;
bool is_directory;
bool has_multiple_extents;
s64 time = 0;
bool is_directory = false;
bool has_multiple_extents = false;
std::vector<iso_extent_info> extents;
u64 size() const;
@ -30,7 +30,7 @@ struct iso_fs_metadata
struct iso_fs_node
{
iso_fs_metadata metadata;
iso_fs_metadata metadata {};
std::vector<std::unique_ptr<iso_fs_node>> children;
};
@ -38,7 +38,7 @@ class iso_file : public fs::file_base
{
private:
fs::file m_file;
iso_fs_metadata m_meta;
iso_fs_metadata m_meta {};
u64 m_pos = 0;
std::pair<u64, iso_extent_info> get_extent_pos(u64 pos) const;
@ -80,7 +80,7 @@ class iso_archive
{
private:
std::string m_path;
iso_fs_node m_root;
iso_fs_node m_root {};
fs::file m_file;
public:

View file

@ -194,6 +194,7 @@
<ClCompile Include="Input\dualsense_pad_handler.cpp" />
<ClCompile Include="Input\gui_pad_thread.cpp" />
<ClCompile Include="Input\hid_pad_handler.cpp" />
<ClCompile Include="Input\mouse_gyro_handler.cpp" />
<ClCompile Include="Input\ps_move_calibration.cpp" />
<ClCompile Include="Input\ps_move_config.cpp" />
<ClCompile Include="Input\ps_move_tracker.cpp" />
@ -1079,6 +1080,7 @@
<AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(QTDIR)\bin\moc.exe;%(FullPath)</AdditionalInputs>
<AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(QTDIR)\bin\moc.exe;%(FullPath)</AdditionalInputs>
</CustomBuild>
<ClInclude Include="Input\mouse_gyro_handler.h" />
<ClInclude Include="Input\ps_move_calibration.h" />
<ClInclude Include="Input\ps_move_config.h" />
<ClInclude Include="Input\ps_move_tracker.h" />

View file

@ -1272,6 +1272,9 @@
<ClCompile Include="QTGeneratedFiles\Release\moc_game_list_context_menu.cpp">
<Filter>Generated Files\Release</Filter>
</ClCompile>
<ClCompile Include="Input\mouse_gyro_handler.cpp">
<Filter>Io</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="Input\ds4_pad_handler.h">
@ -1511,6 +1514,9 @@
<ClInclude Include="Input\sdl_camera_video_sink.h">
<Filter>Io\camera</Filter>
</ClInclude>
<ClInclude Include="Input\mouse_gyro_handler.h">
<Filter>Io</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClInclude Include="resource.h">

View file

@ -157,6 +157,7 @@ add_library(rpcs3_ui STATIC
../Input/hid_pad_handler.cpp
../Input/keyboard_pad_handler.cpp
../Input/mm_joystick_handler.cpp
../Input/mouse_gyro_handler.cpp
../Input/pad_thread.cpp
../Input/product_info.cpp
../Input/ps_move_calibration.cpp

View file

@ -99,7 +99,7 @@ void downloader::start(const std::string& url, bool follow_location, bool show_p
// The downloader's signals are expected to be disconnected and customized before start is called.
// Therefore we need to (re)connect its signal(s) here and not in the constructor.
connect(this, &downloader::signal_buffer_update, this, &downloader::handle_buffer_update);
connect(this, &downloader::signal_buffer_update, this, &downloader::handle_buffer_update, static_cast<Qt::ConnectionType>(Qt::QueuedConnection | Qt::UniqueConnection));
if (show_progress_dialog)
{
@ -169,7 +169,7 @@ usz downloader::update_buffer(char* data, usz size)
const auto old_size = m_curl_buf.size();
const auto new_size = old_size + size;
m_curl_buf.resize(static_cast<int>(new_size));
memcpy(m_curl_buf.data() + old_size, data, size);
std::memcpy(m_curl_buf.data() + old_size, data, size);
int max = 0;
@ -197,6 +197,5 @@ void downloader::handle_buffer_update(int size, int max) const
{
m_progress_dialog->SetRange(0, max > 0 ? max : m_progress_dialog->maximum());
m_progress_dialog->SetValue(size);
QApplication::processEvents();
}
}

View file

@ -25,8 +25,41 @@ namespace
{
static NEVER_INLINE void emit_data(YAML::Emitter& out, const YAML::Node& node)
{
// TODO
out << node;
if (!node || node.IsNull())
{
// I chose to output a null when nothing is present so that recursive YAML Value calls can be matched to a null value instead of nothing
out << YAML::Null;
return;
}
if (node.IsMap())
{
std::vector<std::string> keys;
keys.reserve(node.size());
// generate vector of strings to be sorted using the as function from YAML documentation
for (const auto& pair : node)
{
keys.push_back(pair.first.as<std::string>());
}
std::sort(keys.begin(), keys.end());
// recursively generate sorted maps
// alternative implementations could have stops at specified recursion levels or maybe just the first two levels would be sorted
out << YAML::BeginMap;
for (const std::string& key : keys)
{
out << YAML::Key << key;
out << YAML::Value;
emit_data(out, node[key]);
}
out << YAML::EndMap;
}
// alternatively: an else statement could be used however I wanted to follow a similar format to the += operator so the YAML Undefined class can be ignored
else if (node.IsScalar() || node.IsSequence())
{
out << node;
}
// this exists to preserve the same functionality as before where Undefined nodes would still be output, can be removed or consolidated with the else if branch
else
out << node;
}
// Incrementally load YAML

View file

@ -19,6 +19,7 @@
#include "Emu/RSX/Overlays/overlay_message.h"
#include "Emu/Io/interception.h"
#include "Emu/Io/recording_config.h"
#include "Input/pad_thread.h"
#include <QApplication>
#include <QDateTime>
@ -402,6 +403,15 @@ void gs_frame::handle_shortcut(gui::shortcuts::shortcut shortcut_key, const QKey
audio::change_volume(-5);
break;
}
case gui::shortcuts::shortcut::gw_toggle_mouse_gyro:
{
if (auto* pad_thr = pad::get_pad_thread(true))
{
const bool mouse_gyro_enabled = pad_thr->get_mouse_gyro().toggle_enabled();
gui_log.notice("Mouse-based gyro emulation %s", mouse_gyro_enabled ? "enabled" : "disabled");
}
break;
}
default:
{
break;
@ -1216,6 +1226,16 @@ bool gs_frame::event(QEvent* ev)
// This will make the cursor visible again if it was hidden by the mouse idle timeout
handle_cursor(visibility(), false, false, true);
}
// Handle events for mouse-based gyro emulation.
if (Emu.IsRunning())
{
if (auto* pad_thr = pad::get_pad_thread(true))
{
pad_thr->get_mouse_gyro().handle_event(ev, *this);
}
}
return QWindow::event(ev);
}

View file

@ -163,11 +163,6 @@ log_frame::log_frame(std::shared_ptr<gui_settings> _gui_settings, QWidget* paren
CreateAndConnectActions();
LoadSettings();
if (m_ansi_tty)
{
m_tty_ansi_highlighter = new AnsiHighlighter(m_tty->document());
}
m_timer = new QTimer(this);
connect(m_timer, &QTimer::timeout, this, &log_frame::UpdateUI);
}
@ -225,7 +220,7 @@ void log_frame::CreateAndConnectActions()
// I, for one, welcome our lambda overlord
// It's either this or a signal mapper
// Then, probably making a list of these actions so that it's easier to iterate to generate the mapper.
auto l_initAct = [this](QAction* act, logs::level logLevel)
const auto l_initAct = [this](QAction* act, logs::level logLevel)
{
act->setCheckable(true);
@ -298,7 +293,7 @@ void log_frame::CreateAndConnectActions()
if (m_ansi_tty && !m_tty_ansi_highlighter)
{
m_tty_ansi_highlighter = new AnsiHighlighter(m_tty->document());
m_tty_ansi_highlighter = new AnsiHighlighter(m_tty);
}
else if (!m_ansi_tty && m_tty_ansi_highlighter)
{
@ -607,6 +602,12 @@ void log_frame::RepaintTextColors()
html.replace(old_style, new_style);
m_log->document()->setHtml(html);
if (m_tty_ansi_highlighter)
{
m_tty_ansi_highlighter->update_colors(m_tty);
m_tty_ansi_highlighter->rehighlight();
}
}
void log_frame::UpdateUI()

View file

@ -28,7 +28,8 @@ LOG_CHANNEL(gui_log, "GUI");
log_viewer::log_viewer(std::shared_ptr<gui_settings> gui_settings)
: m_gui_settings(std::move(gui_settings))
{
setWindowTitle(tr("Log Viewer"));
update_title();
setObjectName("log_viewer");
setAttribute(Qt::WA_DeleteOnClose);
setAttribute(Qt::WA_StyledBackground);
@ -59,15 +60,33 @@ log_viewer::log_viewer(std::shared_ptr<gui_settings> gui_settings)
connect(m_log_text, &QWidget::customContextMenuRequested, this, &log_viewer::show_context_menu);
}
void log_viewer::update_title()
{
QString suffix;
if (!m_filter_term.isEmpty())
{
suffix = tr(" | Filter '%0'").arg(m_filter_term);
}
if (!m_exclude_term.isEmpty())
{
suffix += tr(" | Exclude '%0'").arg(m_exclude_term);
}
setWindowTitle(tr("Log Viewer%0").arg(suffix));
}
void log_viewer::show_context_menu(const QPoint& pos)
{
QMenu menu;
QAction* clear = new QAction(tr("&Clear"));
QAction* copy = new QAction(tr("&Copy"));
QAction* open = new QAction(tr("&Open log file"));
QAction* save = new QAction(tr("&Save filtered log"));
QAction* filter = new QAction(tr("&Filter log"));
QAction* config = new QAction(tr("&Check config"));
QAction* clear = new QAction(tr("&Clear"));
QAction* copy = new QAction(tr("&Copy"));
QAction* open = new QAction(tr("&Open log file"));
QAction* save = new QAction(tr("&Save filtered log"));
QAction* filter = new QAction(tr("&Filter log%0").arg(m_filter_term.isEmpty() ? "" : QString(" (%0)").arg(m_filter_term)));
QAction* exclude = new QAction(tr("&Exclude%0").arg(m_exclude_term.isEmpty() ? "" : QString(" (%0)").arg(m_exclude_term)));
QAction* config = new QAction(tr("&Check config"));
QAction* timestamps = new QAction(tr("&Show Timestamps"));
timestamps->setCheckable(true);
@ -91,7 +110,7 @@ void log_viewer::show_context_menu(const QPoint& pos)
QAction* trace_act = new QAction(tr("Trace"), log_level_acts);
log_level_acts->setExclusive(false);
auto init_action = [this](QAction* act, logs::level logLevel)
const auto init_action = [this](QAction* act, logs::level logLevel)
{
act->setCheckable(true);
act->setChecked(m_log_levels.test(static_cast<u32>(logLevel)));
@ -120,6 +139,7 @@ void log_viewer::show_context_menu(const QPoint& pos)
menu.addAction(open);
menu.addAction(config);
menu.addAction(filter);
menu.addAction(exclude);
menu.addAction(save);
menu.addSeparator();
menu.addAction(timestamps);
@ -187,7 +207,22 @@ void log_viewer::show_context_menu(const QPoint& pos)
connect(filter, &QAction::triggered, this, [this]()
{
m_filter_term = QInputDialog::getText(this, tr("Filter log"), tr("Enter text"), QLineEdit::EchoMode::Normal, m_filter_term);
bool ok = false;
QString filter_term = QInputDialog::getText(this, tr("Filter log"), tr("Enter text"), QLineEdit::EchoMode::Normal, m_filter_term, &ok);
if (!ok) return;
m_filter_term = std::move(filter_term);
update_title();
filter_log();
});
connect(exclude, &QAction::triggered, this, [this]()
{
bool ok = false;
QString exclude_term = QInputDialog::getText(this, tr("Exclude"), tr("Enter text (comma separated)"), QLineEdit::EchoMode::Normal, m_exclude_term, &ok);
if (!ok) return;
m_exclude_term = std::move(exclude_term);
m_exclude_terms = m_exclude_term.split(',', Qt::SkipEmptyParts);
update_title();
filter_log();
});
@ -309,7 +344,7 @@ void log_viewer::filter_log()
if (!m_log_levels.test(static_cast<u32>(logs::level::notice))) excluded_log_levels.push_back("·! ");
if (!m_log_levels.test(static_cast<u32>(logs::level::trace))) excluded_log_levels.push_back("·T ");
if (m_filter_term.isEmpty() && excluded_log_levels.empty() && m_show_timestamps && m_show_threads && !m_last_actions_only)
if (m_filter_term.isEmpty() && m_exclude_terms.isEmpty() && excluded_log_levels.empty() && m_show_timestamps && m_show_threads && !m_last_actions_only)
{
set_text_and_keep_position(m_full_log);
return;
@ -322,44 +357,49 @@ void log_viewer::filter_log()
const auto add_line = [this, &result, &excluded_log_levels, &timestamp_regexp, &thread_regexp](QString& line)
{
bool exclude_line = false;
for (const QString& log_level_prefix : excluded_log_levels)
if (!line.isEmpty())
{
if (line.startsWith(log_level_prefix))
for (QStringView log_level_prefix : excluded_log_levels)
{
exclude_line = true;
break;
if (line.startsWith(log_level_prefix))
{
return;
}
}
for (QStringView term : m_exclude_terms)
{
if (line.contains(term))
{
return;
}
}
}
if (exclude_line)
if (!m_filter_term.isEmpty() && !line.contains(m_filter_term))
{
return;
}
if (m_filter_term.isEmpty() || line.contains(m_filter_term))
if (line.isEmpty())
{
if (line.isEmpty())
{
result += "\n";
return;
}
result += "\n";
return;
}
if (!m_show_timestamps)
{
line.remove(timestamp_regexp);
}
if (!m_show_timestamps)
{
line.remove(timestamp_regexp);
}
if (!m_show_threads)
{
line.remove(thread_regexp);
}
if (!m_show_threads)
{
line.remove(thread_regexp);
}
if (!line.isEmpty())
{
result += line + "\n";
}
if (!line.isEmpty())
{
result += line + "\n";
}
};

View file

@ -23,6 +23,7 @@ private Q_SLOTS:
void show_context_menu(const QPoint& pos);
private:
void update_title();
void set_text_and_keep_position(const QString& text);
void filter_log();
bool is_valid_file(const QMimeData& md, bool save = false);
@ -30,6 +31,8 @@ private:
std::shared_ptr<gui_settings> m_gui_settings;
QString m_path_last;
QString m_filter_term;
QString m_exclude_term;
QStringList m_exclude_terms;
QString m_full_log;
QPlainTextEdit* m_log_text;
LogHighlighter* m_log_highlighter;

View file

@ -173,21 +173,45 @@ namespace gui
}
return res;
}
QColor get_foreground_color()
QColor get_foreground_color(QWidget* widget)
{
if (widget)
{
widget->ensurePolished();
return widget->palette().color(QPalette::ColorRole::WindowText);
}
QLabel dummy_color;
dummy_color.ensurePolished();
return dummy_color.palette().color(QPalette::ColorRole::WindowText);
}
QColor get_background_color()
QColor get_background_color(QWidget* widget)
{
if (widget)
{
widget->ensurePolished();
return widget->palette().color(QPalette::ColorRole::Window);
}
QLabel dummy_color;
dummy_color.ensurePolished();
return dummy_color.palette().color(QPalette::ColorRole::Window);
}
QColor adjust_color_for_background(const QColor& fg, const QColor& bg)
{
const int diff = fg.lightness() - bg.lightness();
if (std::abs(diff) >= 40)
{
return fg;
}
return (bg.lightness() < 128) ? fg.lighter(180) : fg.darker(180);
}
QColor get_label_color(const QString& object_name, const QColor& fallback_light, const QColor& fallback_dark, QPalette::ColorRole color_role)
{
if (!gui::custom_stylesheet_active || !gui::stylesheet.contains(object_name))

View file

@ -68,11 +68,14 @@ namespace gui
// Returns a list of all base names of files in dir whose complete file names contain one of the given name_filters
QStringList get_dir_entries(const QDir& dir, const QStringList& name_filters, bool full_path = false);
// Returns the foreground color of QLabel with respect to the current light/dark mode.
QColor get_foreground_color();
// Returns the foreground color of QLabel or the given widget with respect to the current light/dark mode.
QColor get_foreground_color(QWidget* widget = nullptr);
// Returns the background color of QLabel with respect to the current light/dark mode.
QColor get_background_color();
// Returns the background color of QLabel or the given widget with respect to the current light/dark mode.
QColor get_background_color(QWidget* widget = nullptr);
// Returns an adjusted color with better contrast, depending on the background.
QColor adjust_color_for_background(const QColor& fg, const QColor& bg);
// Returns the color specified by its color_role for the QLabels with object_name
QColor get_label_color(const QString& object_name, const QColor& fallback_light, const QColor& fallback_dark, QPalette::ColorRole color_role = QPalette::WindowText);

View file

@ -1262,13 +1262,10 @@ rpcn_friends_dialog::rpcn_friends_dialog(QWidget* parent)
connect(accept_request_action, &QAction::triggered, this, [this, str_sel_friend]()
{
if (!m_rpcn->add_friend(str_sel_friend))
{
QMessageBox::critical(this, tr("Error adding a friend!"), tr("An error occurred while trying to add a friend!"), QMessageBox::Ok);
}
else
if (add_friend_with_error_dialog(str_sel_friend))
{
QMessageBox::information(this, tr("Friend added!"), tr("You've successfully added a friend!"), QMessageBox::Ok);
return;
}
});
@ -1304,11 +1301,8 @@ rpcn_friends_dialog::rpcn_friends_dialog(QWidget* parent)
connect(send_friend_request_action, &QAction::triggered, this, [this, str_sel_friend]()
{
if (!m_rpcn->add_friend(str_sel_friend))
{
QMessageBox::critical(this, tr("Error sending a friend request!"), tr("An error occurred while trying to send a friend request!"), QMessageBox::Ok);
if (!add_friend_with_error_dialog(str_sel_friend))
return;
}
QString qstr_friend = QString::fromStdString(str_sel_friend);
add_update_list(m_lst_requests, qstr_friend, m_icon_request_sent, QVariant(false));
@ -1341,11 +1335,7 @@ rpcn_friends_dialog::rpcn_friends_dialog(QWidget* parent)
QMessageBox::critical(this, tr("Error validating username!"), tr("The username you entered is invalid!"), QMessageBox::Ok);
}
if (!m_rpcn->add_friend(str_friend_username))
{
QMessageBox::critical(this, tr("Error adding friend!"), tr("An error occurred while adding a friend!"), QMessageBox::Ok);
}
else
if (add_friend_with_error_dialog(str_friend_username))
{
add_update_list(m_lst_requests, QString::fromStdString(str_friend_username), m_icon_request_sent, QVariant(false));
QMessageBox::information(this, tr("Friend added!"), tr("Friend was successfully added!"), QMessageBox::Ok);
@ -1360,6 +1350,42 @@ rpcn_friends_dialog::~rpcn_friends_dialog()
m_rpcn->remove_friend_cb(friend_callback, this);
}
bool rpcn_friends_dialog::add_friend_with_error_dialog(const std::string& friend_username)
{
QString err_msg;
const auto opt_error = m_rpcn->add_friend(friend_username);
if (opt_error.has_value())
{
const auto error = opt_error.value();
if (error != rpcn::ErrorType::NoError)
{
switch (error)
{
case rpcn::ErrorType::NotFound: err_msg = tr("The specified username does not exist."); break;
case rpcn::ErrorType::InvalidInput: err_msg = tr("You cannot add yourself as a friend."); break;
case rpcn::ErrorType::Blocked: err_msg = tr("You or the other user have the other blocked."); break;
case rpcn::ErrorType::AlreadyFriend: err_msg = tr("You are already friends with this user."); break;
case rpcn::ErrorType::DbFail: err_msg = tr("A database error occurred. Please try again later."); break;
default: err_msg = tr("An unexpected error occurred."); break;
}
}
}
else
{
err_msg = tr("Failed to send the friend request.");
}
if (!err_msg.isEmpty())
{
QMessageBox::critical(this, tr("Friend Request Failed"), err_msg, QMessageBox::Ok);
return false;
}
return true;
}
bool rpcn_friends_dialog::is_ok() const
{
return m_rpcn_ok;

View file

@ -121,6 +121,7 @@ public:
private:
void add_update_list(QListWidget* list, const QString& name, const QIcon& icon, const QVariant& data);
void remove_list(QListWidget* list, const QString& name);
bool add_friend_with_error_dialog(const std::string& friend_username);
private Q_SLOTS:
void add_update_friend(const QString& name, bool status);

Some files were not shown because too many files have changed in this diff Show more