Compare commits

...
75 Commits
Author SHA1 Message Date
Ani cd814f8c92 rpcs3_version: Bump to 0.0.42 2026-07-31 20:14:38 +02:00
Vishrut Sachan 354e1170ae overlay: Add trophy list sorting options 2026-07-31 19:20:38 +02:00
Ani ca3e5b268e ci/bsd: Compile with clang 22 2026-07-31 18:41:29 +02:00
Walter 27f0e879d3 [SPU LLVM] Shorten FI's dependency chain
Shortens the dependency chain in the `FI` instruction by calculating the exponent adjustment and clamping separately from the mantissa. They can be safely combine as `+-FLT_MAX` overwrites the mantissa as all its bits are set.

Also fixes an AVX512 SNaN clamping issue and poor code gen on pre AVX2 targets.
2026-07-31 10:16:32 +03:00
Megamouse ed195ef077 rsx: fix some msvc warnings 2026-07-31 07:41:40 +02:00
Ani f1a858f351 ci: Fetch llvm-devel on FreeBSD
Required to have latest clang-devel
2026-07-30 22:39:41 +02:00
Ani b57cca4ae4 ci: Specify LLVM22 on FreeBSD build
RPCS3 does not yet support building with newer LLVM
2026-07-30 22:39:41 +02:00
Ani 75ec6a8dca ci: Update FreeBSD to 14.4 2026-07-30 22:39:41 +02:00
kd-11 76de5ba764 rsx: Fix interpreter error spam due to incorrect format lookup 2026-07-30 20:12:21 +03:00
kd-11 ff162b9c99 rsx/doc: Update comments regarding the WCB requirement hack 2026-07-30 18:02:06 +03:00
kd-11 5017397c4e gl/vk: Implement section load from CPU during subresource gather operations 2026-07-30 18:02:06 +03:00
kd-11 4c52ab803c rsx: Refactor texture layout decoder to support arbitrary image sections as input 2026-07-30 18:02:06 +03:00
kd-11 d277003ef7 rsx: Skip invalid mip levels during texture upload 2026-07-30 18:02:06 +03:00
kd-11 c2abae56cd rsx: Refactor temporary subresource handling 2026-07-30 18:02:06 +03:00
kd-11 a5bbb23273 rsx: Set data load flags when slice gathering is incomplete 2026-07-30 18:02:06 +03:00
kd-11 a2c78dd32c rsx: Set bg load flag when re-assembling textures on the GPU 2026-07-30 18:02:06 +03:00
Megamouse a7fe472583 Allow to skip empty strings in fmt::merge 2026-07-30 02:35:53 +02:00
Sanjay Govind bad72a3844 add missing vids and pids for wii MPA devices 2026-07-30 01:43:13 +02:00
Darkhost1999 81431604ec UI/UX: Add Categories Broken and Completed to Filter Game List (#19106)
Adds filtering options to manage the game list
The existing option `hide` does not feel sufficient because there are
different reasons a user may want to hide a game

Adds option Broken
Let's the user define games as broken.

Adds an option for completed
Let's the user define games as completed.

This uses copy pasta of existing code.
AI was not used. I used github search, control + F, git blame, and
intellisense.
I understood build errors, made necessary edits as needed, compiled and
tested the code.

The options show_hidden, show_broken, show_completed do not show up in
the current_settings.ini file until you toggle the option under view.
This happens on Master as well with the option show_hidden. The settings
show up in the ini file in the order that they are created this is not a
bug it is working by design.
2026-07-30 01:11:19 +02:00
primetime43 884652109c Mark invite list dirty only on add
Move `m_list_dirty = true` inside the successful insert path in `add_game_invite`, so the friends invite overlay only refreshes when a new invite is actually added instead of on duplicate/no-op updates.
2026-07-29 19:10:39 +02:00
primetime43 709016a3ab Fix friends invite lifecycle in overlay
Track RPCN invite messages in a dedicated in-memory list, append new invites from the callback, and remove invites immediately after accept/dismiss so stale entries are not reprocessed on reload. Also make `np_handler::select_invitation` return `bool` consistently and clear selected invitation data if the system notification fails.
2026-07-29 19:10:39 +02:00
primetime43 50c78cb0e1 Fix style issues to follow RPCS3 guide
Fix style issues to follow RPCS3 guide
2026-07-29 19:10:39 +02:00
primetime43 d4a37a1dd4 Add game invite handling to friends overlay #18417
Split the friends dialog invite flow into friend requests and game invitations, add a dedicated game invite page, and wire accept/reject actions with the proper prompts. Accepting now routes through a new `np_handler::select_invitation` helper that validates invite messages, notifies the game, and marks invites as used. Also added locking around selected-message state in `np_handler` to make selection access thread-safe, plus new localized strings for the added UI text.
2026-07-29 19:10:39 +02:00
Antonino Di Guardoandkd-11 cf9133abed fix 1st regression on black textures in Sonic Generations
Fix OOB read in inner MSAA texel lookup for the software filter.

---------

Co-authored-by: kd-11 <[email protected]>
2026-07-28 22:22:36 +00:00
isztldav b6ee57853f Fix value assignment in iota16
iota16() discarded the _mm_add_epi16 result, so `values` never advanced and every 8-element block was written as 0..7 instead of a rising sequence
2026-07-28 21:39:19 +00:00
Megamouse b778355c68 Remove outdated LLVM ifdefs 2026-07-28 21:24:05 +02:00
Megamouse 831a078e3f Replace some abort() calls with fmt::throw_exception 2026-07-28 18:41:37 +02:00
Megamouse 0d999c08c4 vk: remove unused setting 2026-07-28 17:39:20 +03:00
Elad d3abc6eee3 Improve logging of RawSPU MMIO
Reduce spam and add value.
2026-07-28 12:16:35 +03:00
Elad 942feb64a3 Fix Emu.IsVsh() to fix liblv2.sprx load for PS1 classics 2026-07-28 12:16:35 +03:00
Elad 5e8ba021ac SPU/ARM64: Implement RawSPU MMIO on ARM 2026-07-28 12:16:35 +03:00
Megamouse 22d9cd0424 Identify DLC based on package_type 2026-07-28 07:28:05 +02:00
Megamouse dbc0ab4821 Use localization for title and changelog if possible 2026-07-28 07:28:05 +02:00
Megamouse 4bd4fc6a5e Update fusion to 1.3.2 2026-07-27 16:18:26 +02:00
Megamouse 9173ff1657 Fix some warnings 2026-07-27 16:18:26 +02:00
Megamouse 4add14cb72 Fix recording encoder selection
Codecs are identified by name, not by ID.
Let's keep the ID as fallback instead.
2026-07-27 16:18:26 +02:00
Megamouse 7a3deb649f Fix package install failure warning message 2026-07-27 13:33:27 +02:00
kd-11 a1deb2921c vk: Port MSAA and D24S8 redirection flattening logic from ogl 2026-07-27 01:40:53 +03:00
kd-11 3ac6ce68d4 gl: Handle edge cases in the interpreter sampler flattener
- Handle edge case where both MSAA and redirect are set
- Properly configure the descriptor for caching
- Cleanup and simplification
2026-07-27 01:40:53 +03:00
kd-11 7919f44b9b rsx: Autogenerate image attributes from RSXTexture wrappers 2026-07-27 01:40:53 +03:00
kd-11 0705c1b0c4 rsx: Minor fixes to improve subresource cache coherency 2026-07-27 01:40:53 +03:00
kd-11 709acc39ca rsx: Minor refactor to prep for surface cache access to texture cache helpers 2026-07-27 01:40:53 +03:00
kd-11 d3ef2822f8 gl: Flatten redirected depth and MSAA images to 1spp color images for interpreter 2026-07-27 01:40:53 +03:00
kd-11 3164e6fe46 gl: Fix interpreter setup when no textures are in use 2026-07-27 01:40:53 +03:00
Megamouse 24fbafdb1d Update llvm to 22.1.8 2026-07-26 18:42:53 +02:00
Megamouse 428599535b VS: Make cmake build commands multiline 2026-07-26 18:42:53 +02:00
kd-11 79822e1ac5 rsx: Minor optimization in LIT emulation 2026-07-26 16:03:08 +03:00
kd-11 4332190805 rsxfp/interpreter - Fix IFE decoding 2026-07-26 16:03:08 +03:00
kd-11 0e44b93c69 rsxfp/interpreter - Fix 0 iteration edge case in IFE 2026-07-26 16:03:08 +03:00
kd-11 7a24caf715 rsxvp/interpreter - Fix CLB stack overflow 2026-07-26 16:03:08 +03:00
kd-11 04dd333f8d rsxfp/interpreter - Handle loop with 0 iterations 2026-07-26 16:03:08 +03:00
kd-11 969913e3a7 rsxvp/interpreter - Guard against stack overflow during subroutine calls 2026-07-26 16:03:08 +03:00
kd-11 2e1e416c35 rsxvp/interpreter - rewrite register output blocks 2026-07-26 16:03:08 +03:00
kd-11 e59af03ae2 rsx: Re-implement support for LIT instruction 2026-07-26 16:03:08 +03:00
kd-11 84b905dd69 rsxfp/interpreter - Flush zero to destination register if instruction is unimplemented. 2026-07-26 16:03:08 +03:00
kd-11 429f1fbcbb rsxfp/interpreter - Clamp SQRT inputs to match decompiler 2026-07-26 16:03:08 +03:00
kd-11 22f8cb040a rsxfp/interpreter - Update PK16/UP16 to match fixed variants in the decompiler 2026-07-26 16:03:08 +03:00
kd-11 88569563d8 rsxfp/interpreter - Fix loop counter bug 2026-07-26 16:03:08 +03:00
kd-11 28b3deaf8e rsxfp/interpreter - Fix commit of CC register writes to take SAT and SCALE into account
- Requires HWTEST but this matches the decompiler behavior
2026-07-26 16:03:08 +03:00
kd-11 9e2b54e6ac rsxfp/interpreter - Avoid OOB writes in register init loop 2026-07-26 16:03:08 +03:00
kd-11 696fdc3059 rsxfp/interpreter - Fix typo in LRP instruction emulation
- RSX-native instruction has the operands reversed
2026-07-26 16:03:08 +03:00
kd-11 2484984a31 rsxfp/interpreter - Check for instruction cond before executing RET/BRK 2026-07-26 16:03:08 +03:00
kd-11 cb4914754a rsxfp/interpreter - Fix IFE ELSE address computation 2026-07-26 16:03:08 +03:00
kd-11 b39b7a23dc rsxvp/interpreter - Rebase BRA input address before jump 2026-07-26 16:03:08 +03:00
kd-11 11e147c646 rsxvp/interpreter - Fix oob writes when flushing instruction outputs to export registers 2026-07-26 16:03:08 +03:00
kd-11 1e14e7521b rsxvp/interpreter - Fix oob writes if ARL data is corrupted 2026-07-26 16:03:08 +03:00
kd-11 a013b60cde rsxvp/interpreter - Fix CAL/RET stack underflow bug 2026-07-26 16:03:08 +03:00
kd-11 24ba41a3e1 rsxvp/interpreter - Fix computation of instruction addresses 2026-07-26 16:03:08 +03:00
Megamouse 7a90d09cfe Qt: move guest memory dump code to own class 2026-07-23 10:58:18 +02:00
caner 5ecfc95c13 Qt: add guest memory dump utility (#19068)
This adds a tool that writes a snapshot of the full PS3 guest address
space to disk for offline analysis.

This tool produces a flat image that any hex editor or script can index
directly by guest address. It dumps allocated PS3 guest memory only, not
RPCS3 host-process memory, host registers, or GPU-side state.

What it does:

1. Pauses emulation and waits up to five seconds for every PPU, SPU, and
RSX thread to settle. If they do not settle, nothing is written.
2. Copies every allocated guest page into an intermediate buffer on a
named worker thread, then writes it to a sparse `guest_memory.bin` whose
file offset equals the PS3 effective address. Reads go through
`vm::g_sudo_addr` so guest protected pages are captured too.
3. Saves each live SPU thread's 256 KB local store to its own file, with
its ID, LV2 ID, PC, and type recorded.
4. Writes `manifest.json`, listing every allocated region with its
addresses and permissions, the storage mode, and the SPU local-store
table.
5. Resumes emulation.

The menu entry is under Utilities next to Memory Viewer and is enabled
while a game is loaded. Free space is checked before writing. If sparse
file setup fails, the incomplete attempt is removed and the user can
choose another destination or cancel. Failed or cancelled dumps remove
their output folder instead of leaving a partial image.

Tested on Windows using an NTFS destination with a build from this
branch: dumped a running game with 479,535,104 allocated guest bytes
across 76 regions and five SPU local stores. The image was sparse, its
offsets matched guest addresses, and emulation resumed cleanly. The
sparse file failure path was also exercised with a temporary local test:
choosing another location reopened the destination picker, while
cancelling aborted the attempt without leaving partial output. Runtime
behavior on Linux and macOS is untested.

The first draft of this implementation was AI assisted. I reviewed and
adjusted the code and tested the Windows/NTFS path myself. I am happy to
rework anything or answer any questions.
2026-07-23 00:08:05 +00:00
Megamouse 8604f1c5d8 cli: allow to boot last savestate of a game 2026-07-22 09:13:41 +02:00
RipleyTom 8b05c8cc1f bugfixes 2026-07-21 21:49:00 +03:00
Elad d75543a5b1 LLVM: Add short fallback for get_known_bits
Supports: when value is the immediate result of either a bitwise OR operation or a bitwise AND when either operands is a constant.

Prevents some false positives when the value has PHI nodes in its ancestors.
2026-07-21 21:49:00 +03:00
RipleyTom 2aeb08f929 Fix: get_known_bits fix 2026-07-21 21:49:00 +03:00
Elad 85c59207f7 LLVM: Improve v128 constant extraction 2026-07-21 20:35:56 +03:00
99 changed files with 3283 additions and 665 deletions
+1 -1
View File
@@ -8,7 +8,7 @@ sed -i '' 's/quarterly/latest/' /etc/pkg/FreeBSD.conf
export ASSUME_ALWAYS_YES=true
pkg info # debug
# WITH_LLVM
# WITH_LLVM and Clang compiler
pkg install "llvm$LLVM_COMPILER_VER"
# Mandatory dependencies (qtX-base is pulled via qtX-multimedia)
+11 -11
View File
@@ -30,23 +30,23 @@ jobs:
matrix:
include:
- os: ubuntu-24.04
docker_img: "rpcs3/rpcs3-ci-jammy:1.14"
docker_img: "rpcs3/rpcs3-ci-jammy:2.0"
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.14"
docker_img: "rpcs3/rpcs3-ci-jammy:2.0"
build_sh: "/rpcs3/.ci/build-linux.sh"
compiler: gcc
- os: ubuntu-24.04-arm
docker_img: "rpcs3/rpcs3-ci-jammy-aarch64:1.14"
docker_img: "rpcs3/rpcs3-ci-jammy-aarch64:2.0"
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.14"
docker_img: "rpcs3/rpcs3-ci-jammy-aarch64:2.0"
build_sh: "/rpcs3/.ci/build-linux-aarch64.sh"
compiler: gcc
name: RPCS3 Linux ${{ matrix.os }} ${{ matrix.compiler }}
@@ -136,7 +136,7 @@ jobs:
CCACHE_DIR: /tmp/ccache_dir
QT_VER: '6.11.1'
QT_VER_MAIN: '6'
LLVM_COMPILER_VER: '21'
LLVM_COMPILER_VER: '22'
RELEASE_MESSAGE: ../GitHubReleaseMessage.txt
UPLOAD_COMMIT_HASH: ${{ matrix.UPLOAD_COMMIT_HASH }}
UPLOAD_REPO_FULL_NAME: ${{ matrix.UPLOAD_REPO_FULL_NAME }}
@@ -215,7 +215,7 @@ jobs:
QT_VER: '6.11.1'
QT_VER_MSVC: 'msvc2022'
QT_DATE: '202605090529'
LLVM_VER: '19.1.7'
LLVM_VER: '22.1.8'
VULKAN_VER: '1.4.341.1'
VULKAN_SDK_SHA: 'bcf2d75aa9556889ab974858666e20b3655b6055a0db704ccb47279ff33b5bfe'
CCACHE_SHA: '859141059ac950e1e8cd042c66f842f26b9e3a62a1669a69fe6ba180cb58bbdf'
@@ -443,10 +443,10 @@ jobs:
env:
CCACHE_DIR: ${{ github.workspace }}/ccache
QT_VER_MAIN: '6'
LLVM_COMPILER_VER: '-devel'
CC: 'clang-devel'
CXX: 'clang++-devel'
LLVM_CONFIG: 'llvm-config-devel'
LLVM_COMPILER_VER: '22'
LLVM_CONFIG: 'llvm-config22'
CC: 'clang22'
CXX: 'clang++22'
steps:
- name: Checkout repository
@@ -469,7 +469,7 @@ jobs:
envs: 'QT_VER_MAIN LLVM_COMPILER_VER CCACHE_DIR CC CXX LLVM_CONFIG'
usesh: true
copyback: false
release: "14.3"
release: "14.4"
run: .ci/install-freebsd.sh && .ci/build-freebsd.sh
- name: Save Build Ccache
+35 -2
View File
@@ -49,11 +49,44 @@
<PropertyGroup Label="UserMacros">
<CmakeReleaseCLI>call vsdevcmd.bat -arch=amd64
cd "$(SolutionDir)build\tmp\$(ProjectName)-$(Configuration)-$(Platform)"
cmake -G Ninja -DCMAKE_CXX_COMPILER="cl.exe" -DCMAKE_C_COMPILER="cl.exe" -DCMAKE_BUILD_TYPE="Release" -DCMAKE_INSTALL_PREFIX="./Release" -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded -DCMAKE_SYSTEM_VERSION=10.0 -DLIBTYPE=STATIC -DFORCE_STATIC_VCRT=true -DALSOFT_UTILS=false -DALSOFT_EXAMPLES=false -DALSOFT_INSTALL=false -DALSOFT_INSTALL_CONFIG=false -DALSOFT_INSTALL_HRTF_DATA=false -DALSOFT_INSTALL_AMBDEC_PRESETS=false -DALSOFT_INSTALL_EXAMPLES=false -DALSOFT_INSTALL_UTILS=false "$(SolutionDir)3rdparty\OpenAL\openal-soft"
cmake -G Ninja ^
-DCMAKE_CXX_COMPILER="cl.exe" ^
-DCMAKE_C_COMPILER="cl.exe" ^
-DCMAKE_BUILD_TYPE="Release" ^
-DCMAKE_INSTALL_PREFIX="./Release" ^
-DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded ^
-DCMAKE_SYSTEM_VERSION=10.0 ^
-DLIBTYPE=STATIC ^
-DFORCE_STATIC_VCRT=true ^
-DALSOFT_UTILS=false ^
-DALSOFT_EXAMPLES=false ^
-DALSOFT_INSTALL=false ^
-DALSOFT_INSTALL_CONFIG=false ^
-DALSOFT_INSTALL_HRTF_DATA=false ^
-DALSOFT_INSTALL_AMBDEC_PRESETS=false ^
-DALSOFT_INSTALL_EXAMPLES=false ^
-DALSOFT_INSTALL_UTILS=false ^
"$(SolutionDir)3rdparty\OpenAL\openal-soft"
</CmakeReleaseCLI>
<CmakeDebugCLI>call vsdevcmd.bat -arch=amd64
cd "$(SolutionDir)build\tmp\$(ProjectName)-$(Configuration)-$(Platform)"
cmake -G Ninja -DCMAKE_CXX_COMPILER="cl.exe" -DCMAKE_C_COMPILER="cl.exe" -DCMAKE_BUILD_TYPE="Debug" -DCMAKE_INSTALL_PREFIX="./Debug" -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreadedDebug -DCMAKE_SYSTEM_VERSION=10.0 -DLIBTYPE=STATIC -DALSOFT_UTILS=false -DALSOFT_EXAMPLES=false -DALSOFT_INSTALL=false -DALSOFT_INSTALL_CONFIG=false -DALSOFT_INSTALL_HRTF_DATA=false -DALSOFT_INSTALL_AMBDEC_PRESETS=false -DALSOFT_INSTALL_EXAMPLES=false -DALSOFT_INSTALL_UTILS=false "$(SolutionDir)3rdparty\OpenAL\openal-soft"
cmake -G Ninja ^
-DCMAKE_CXX_COMPILER="cl.exe" ^
-DCMAKE_C_COMPILER="cl.exe" ^
-DCMAKE_BUILD_TYPE="Debug" ^
-DCMAKE_INSTALL_PREFIX="./Debug" ^
-DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreadedDebug ^
-DCMAKE_SYSTEM_VERSION=10.0 ^
-DLIBTYPE=STATIC ^
-DALSOFT_UTILS=false ^
-DALSOFT_EXAMPLES=false ^
-DALSOFT_INSTALL=false ^
-DALSOFT_INSTALL_CONFIG=false ^
-DALSOFT_INSTALL_HRTF_DATA=false ^
-DALSOFT_INSTALL_AMBDEC_PRESETS=false ^
-DALSOFT_INSTALL_EXAMPLES=false ^
-DALSOFT_INSTALL_UTILS=false ^
"$(SolutionDir)3rdparty\OpenAL\openal-soft"
</CmakeDebugCLI>
<CmakeCopyCLI>
echo Copying..
+22 -2
View File
@@ -39,10 +39,30 @@
<PropertyGroup Label="UserMacros">
<CmakeGenerator>"Visual Studio $(VisualStudioVersion.Substring(0,2))"</CmakeGenerator>
<CmakeReleaseCLI>call vsdevcmd.bat -arch=amd64
cmake -G $(CmakeGenerator) -A x64 -DCMAKE_BUILD_TYPE="Release" -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded -DGLSLANG_TESTS=OFF -DENABLE_GLSLANG_BINARIES=OFF -DBUILD_EXTERNAL=OFF -DENABLE_SPVREMAPPER=OFF -DENABLE_HLSL=OFF -DENABLE_OPT=OFF -S glslang -B "$(SolutionDir)build\tmp\$(ProjectName)-$(Configuration)-$(Platform)"
cmake -G $(CmakeGenerator) -A x64 ^
-DCMAKE_BUILD_TYPE="Release" ^
-DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded ^
-DGLSLANG_TESTS=OFF ^
-DENABLE_GLSLANG_BINARIES=OFF ^
-DBUILD_EXTERNAL=OFF ^
-DENABLE_SPVREMAPPER=OFF ^
-DENABLE_HLSL=OFF ^
-DENABLE_OPT=OFF ^
-S glslang ^
-B "$(SolutionDir)build\tmp\$(ProjectName)-$(Configuration)-$(Platform)"
</CmakeReleaseCLI>
<CmakeDebugCLI>call vsdevcmd.bat -arch=amd64
cmake -G $(CmakeGenerator) -A x64 -DCMAKE_BUILD_TYPE="Debug" -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreadedDebug -DGLSLANG_TESTS=OFF -DENABLE_GLSLANG_BINARIES=OFF -DBUILD_EXTERNAL=OFF -DENABLE_SPVREMAPPER=OFF -DENABLE_HLSL=OFF -DENABLE_OPT=OFF -S glslang -B "$(SolutionDir)build\tmp\$(ProjectName)-$(Configuration)-$(Platform)"
cmake -G $(CmakeGenerator) -A x64 ^
-DCMAKE_BUILD_TYPE="Debug" ^
-DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreadedDebug ^
-DGLSLANG_TESTS=OFF ^
-DENABLE_GLSLANG_BINARIES=OFF ^
-DBUILD_EXTERNAL=OFF ^
-DENABLE_SPVREMAPPER=OFF ^
-DENABLE_HLSL=OFF ^
-DENABLE_OPT=OFF ^
-S glslang ^
-B "$(SolutionDir)build\tmp\$(ProjectName)-$(Configuration)-$(Platform)"
</CmakeDebugCLI>
<CmakeCopyCLI>
echo Copying..
+1 -1
View File
@@ -51,7 +51,7 @@ if(WITH_LLVM)
set(STATIC_LINK_LLVM ON CACHE BOOL "Link against LLVM statically. This will get set to ON if you build LLVM from the submodule." FORCE)
find_package(LLVM 19.1 CONFIG)
find_package(LLVM 22.1 CONFIG)
if(NOT LLVM_FOUND)
message(FATAL_ERROR "Couldn't build LLVM from the submodule. You might need to run `git submodule update --init`")
endif()
+56 -2
View File
@@ -40,11 +40,65 @@
<PropertyGroup Label="UserMacros">
<CmakeReleaseCLI>call vsdevcmd.bat -arch=amd64
cd "$(SolutionDir)build\tmp\llvm_build-$(Configuration)-$(Platform)"
cmake -G Ninja -DCMAKE_CXX_COMPILER="cl.exe" -DCMAKE_C_COMPILER="cl.exe" -DCMAKE_BUILD_TYPE="Release" -DCMAKE_INSTALL_PREFIX="$(SolutionDir)build\lib\$(Configuration)-$(Platform)\llvm_build" -DCMAKE_SYSTEM_VERSION=10.0 -DCMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION=$(WindowsTargetPlatformVersion) -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded -DLLVM_TARGETS_TO_BUILD=X86 -DLLVM_DEFAULT_TARGET_TRIPLE:STRING=x86_64-pc-windows-msvc -DLLVM_HOST_TRIPLE:STRING=x86_64-pc-windows-msvc -DLLVM_BUILD_RUNTIME=OFF -DLLVM_BUILD_TOOLS=OFF -DLLVM_BUILD_BENCHMARKS=OFF -DLLVM_BUILD_DOCS=OFF -DLLVM_BUILD_EXAMPLES=OFF -DLLVM_BUILD_TESTS=OFF\ -DLLVM_INCLUDE_TOOLS=OFF -DLLVM_INCLUDE_DOCS=OFF -DLLVM_INCLUDE_EXAMPLES=OFF -DLLVM_INCLUDE_TESTS=OFF -DLLVM_INCLUDE_BENCHMARKS=OFF -DLLVM_INCLUDE_UTILS=OFF -DLLVM_INSTALL_UTILS=OFF -DLLVM_ENABLE_DIA_SDK=OFF -DLLVM_ENABLE_PDB=OFF -DLLVM_USE_INTEL_JITEVENTS=ON "$(SolutionDir)3rdparty\llvm\llvm\llvm"
cmake -G Ninja ^
-DCMAKE_CXX_COMPILER="cl.exe" ^
-DCMAKE_C_COMPILER="cl.exe" ^
-DCMAKE_BUILD_TYPE="Release" ^
-DCMAKE_INSTALL_PREFIX="$(SolutionDir)build\lib\$(Configuration)-$(Platform)\llvm_build" ^
-DCMAKE_SYSTEM_VERSION=10.0 ^
-DCMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION=$(WindowsTargetPlatformVersion) ^
-DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded ^
-DLLVM_TARGETS_TO_BUILD=X86 ^
-DLLVM_DEFAULT_TARGET_TRIPLE:STRING=x86_64-pc-windows-msvc ^
-DLLVM_HOST_TRIPLE:STRING=x86_64-pc-windows-msvc ^
-DLLVM_BUILD_RUNTIME=OFF ^
-DLLVM_BUILD_TOOLS=OFF ^
-DLLVM_BUILD_BENCHMARKS=OFF ^
-DLLVM_BUILD_DOCS=OFF ^
-DLLVM_BUILD_EXAMPLES=OFF ^
-DLLVM_BUILD_TESTS=OFF ^
-DLLVM_INCLUDE_TOOLS=OFF ^
-DLLVM_INCLUDE_DOCS=OFF ^
-DLLVM_INCLUDE_EXAMPLES=OFF ^
-DLLVM_INCLUDE_TESTS=OFF ^
-DLLVM_INCLUDE_BENCHMARKS=OFF ^
-DLLVM_INCLUDE_UTILS=OFF ^
-DLLVM_INSTALL_UTILS=OFF ^
-DLLVM_ENABLE_DIA_SDK=OFF ^
-DLLVM_ENABLE_PDB=OFF ^
-DLLVM_USE_INTEL_JITEVENTS=ON ^
"$(SolutionDir)3rdparty\llvm\llvm\llvm"
</CmakeReleaseCLI>
<CmakeDebugCLI>call vsdevcmd.bat -arch=amd64
cd "$(SolutionDir)build\tmp\llvm_build-$(Configuration)-$(Platform)"
cmake -G Ninja -DCMAKE_CXX_COMPILER="cl.exe" -DCMAKE_C_COMPILER="cl.exe" -DCMAKE_BUILD_TYPE="Debug" -DCMAKE_INSTALL_PREFIX="$(SolutionDir)build\lib\$(Configuration)-$(Platform)\llvm_build" -DCMAKE_SYSTEM_VERSION=10.0 -DCMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION=$(WindowsTargetPlatformVersion) -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreadedDebugDLL -DLLVM_TARGETS_TO_BUILD=X86 -DLLVM_DEFAULT_TARGET_TRIPLE:STRING=x86_64-pc-windows-msvc -DLLVM_HOST_TRIPLE:STRING=x86_64-pc-windows-msvc -DLLVM_BUILD_RUNTIME=OFF -DLLVM_BUILD_TOOLS=OFF -DLLVM_BUILD_BENCHMARKS=OFF -DLLVM_BUILD_DOCS=OFF -DLLVM_BUILD_EXAMPLES=OFF -DLLVM_BUILD_TESTS=OFF\ -DLLVM_INCLUDE_TOOLS=OFF -DLLVM_INCLUDE_DOCS=OFF -DLLVM_INCLUDE_EXAMPLES=OFF -DLLVM_INCLUDE_TESTS=OFF -DLLVM_INCLUDE_BENCHMARKS=OFF -DLLVM_INCLUDE_UTILS=OFF -DLLVM_INSTALL_UTILS=OFF -DLLVM_ENABLE_DIA_SDK=OFF -DLLVM_ENABLE_PDB=OFF -DLLVM_USE_INTEL_JITEVENTS=ON "$(SolutionDir)3rdparty\llvm\llvm\llvm"
cmake -G Ninja ^
-DCMAKE_CXX_COMPILER="cl.exe" ^
-DCMAKE_C_COMPILER="cl.exe" ^
-DCMAKE_BUILD_TYPE="Debug" ^
-DCMAKE_INSTALL_PREFIX="$(SolutionDir)build\lib\$(Configuration)-$(Platform)\llvm_build" ^
-DCMAKE_SYSTEM_VERSION=10.0 ^
-DCMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION=$(WindowsTargetPlatformVersion) ^
-DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreadedDebugDLL ^
-DLLVM_TARGETS_TO_BUILD=X86 ^
-DLLVM_DEFAULT_TARGET_TRIPLE:STRING=x86_64-pc-windows-msvc ^
-DLLVM_HOST_TRIPLE:STRING=x86_64-pc-windows-msvc ^
-DLLVM_BUILD_RUNTIME=OFF ^
-DLLVM_BUILD_TOOLS=OFF ^
-DLLVM_BUILD_BENCHMARKS=OFF ^
-DLLVM_BUILD_DOCS=OFF ^
-DLLVM_BUILD_EXAMPLES=OFF ^
-DLLVM_BUILD_TESTS=OFF ^
-DLLVM_INCLUDE_TOOLS=OFF ^
-DLLVM_INCLUDE_DOCS=OFF ^
-DLLVM_INCLUDE_EXAMPLES=OFF ^
-DLLVM_INCLUDE_TESTS=OFF ^
-DLLVM_INCLUDE_BENCHMARKS=OFF ^
-DLLVM_INCLUDE_UTILS=OFF ^
-DLLVM_INSTALL_UTILS=OFF ^
-DLLVM_ENABLE_DIA_SDK=OFF ^
-DLLVM_ENABLE_PDB=OFF ^
-DLLVM_USE_INTEL_JITEVENTS=ON ^
"$(SolutionDir)3rdparty\llvm\llvm\llvm"
</CmakeDebugCLI>
<CmakeCleanCLI>
echo Cleaning..
+56 -2
View File
@@ -40,11 +40,65 @@
<PropertyGroup Label="UserMacros">
<CmakeReleaseCLI>call vsdevcmd.bat -arch=amd64
cd "$(SolutionDir)build\tmp\llvm_build-$(Configuration)-$(Platform)"
cmake -G Ninja -DCMAKE_CXX_COMPILER="clang-cl.exe" -DCMAKE_C_COMPILER="clang-cl.exe" -DCMAKE_BUILD_TYPE="Release" -DCMAKE_INSTALL_PREFIX="$(SolutionDir)build\lib\$(Configuration)-$(Platform)\llvm_build" -DCMAKE_SYSTEM_VERSION=10.0 -DCMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION=$(WindowsTargetPlatformVersion) -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded -DLLVM_TARGETS_TO_BUILD=X86 -DLLVM_DEFAULT_TARGET_TRIPLE:STRING=x86_64-pc-windows-msvc -DLLVM_HOST_TRIPLE:STRING=x86_64-pc-windows-msvc -DLLVM_BUILD_RUNTIME=OFF -DLLVM_BUILD_TOOLS=OFF -DLLVM_BUILD_BENCHMARKS=OFF -DLLVM_BUILD_DOCS=OFF -DLLVM_BUILD_EXAMPLES=OFF -DLLVM_BUILD_TESTS=OFF\ -DLLVM_INCLUDE_TOOLS=OFF -DLLVM_INCLUDE_DOCS=OFF -DLLVM_INCLUDE_EXAMPLES=OFF -DLLVM_INCLUDE_TESTS=OFF -DLLVM_INCLUDE_BENCHMARKS=OFF -DLLVM_INCLUDE_UTILS=OFF -DLLVM_INSTALL_UTILS=OFF -DLLVM_ENABLE_DIA_SDK=OFF -DLLVM_ENABLE_PDB=OFF -DLLVM_USE_INTEL_JITEVENTS=ON "$(SolutionDir)3rdparty\llvm\llvm\llvm"
cmake -G Ninja ^
-DCMAKE_CXX_COMPILER="clang-cl.exe" ^
-DCMAKE_C_COMPILER="clang-cl.exe" ^
-DCMAKE_BUILD_TYPE="Release" ^
-DCMAKE_INSTALL_PREFIX="$(SolutionDir)build\lib\$(Configuration)-$(Platform)\llvm_build" ^
-DCMAKE_SYSTEM_VERSION=10.0 ^
-DCMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION=$(WindowsTargetPlatformVersion) ^
-DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded ^
-DLLVM_TARGETS_TO_BUILD=X86 ^
-DLLVM_DEFAULT_TARGET_TRIPLE:STRING=x86_64-pc-windows-msvc ^
-DLLVM_HOST_TRIPLE:STRING=x86_64-pc-windows-msvc ^
-DLLVM_BUILD_RUNTIME=OFF ^
-DLLVM_BUILD_TOOLS=OFF ^
-DLLVM_BUILD_BENCHMARKS=OFF ^
-DLLVM_BUILD_DOCS=OFF ^
-DLLVM_BUILD_EXAMPLES=OFF ^
-DLLVM_BUILD_TESTS=OFF ^
-DLLVM_INCLUDE_TOOLS=OFF ^
-DLLVM_INCLUDE_DOCS=OFF ^
-DLLVM_INCLUDE_EXAMPLES=OFF ^
-DLLVM_INCLUDE_TESTS=OFF ^
-DLLVM_INCLUDE_BENCHMARKS=OFF ^
-DLLVM_INCLUDE_UTILS=OFF ^
-DLLVM_INSTALL_UTILS=OFF ^
-DLLVM_ENABLE_DIA_SDK=OFF ^
-DLLVM_ENABLE_PDB=OFF ^
-DLLVM_USE_INTEL_JITEVENTS=ON ^
"$(SolutionDir)3rdparty\llvm\llvm\llvm"
</CmakeReleaseCLI>
<CmakeDebugCLI>call vsdevcmd.bat -arch=amd64
cd "$(SolutionDir)build\tmp\llvm_build-$(Configuration)-$(Platform)"
cmake -G Ninja -DCMAKE_CXX_COMPILER="clang-cl.exe" -DCMAKE_C_COMPILER="clang-cl.exe" -DCMAKE_BUILD_TYPE="Debug" -DCMAKE_INSTALL_PREFIX="$(SolutionDir)build\lib\$(Configuration)-$(Platform)\llvm_build" -DCMAKE_SYSTEM_VERSION=10.0 -DCMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION=$(WindowsTargetPlatformVersion) -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreadedDebugDLL -DLLVM_TARGETS_TO_BUILD=X86 -DLLVM_DEFAULT_TARGET_TRIPLE:STRING=x86_64-pc-windows-msvc -DLLVM_HOST_TRIPLE:STRING=x86_64-pc-windows-msvc -DLLVM_BUILD_RUNTIME=OFF -DLLVM_BUILD_TOOLS=OFF -DLLVM_BUILD_BENCHMARKS=OFF -DLLVM_BUILD_DOCS=OFF -DLLVM_BUILD_EXAMPLES=OFF -DLLVM_BUILD_TESTS=OFF\ -DLLVM_INCLUDE_TOOLS=OFF -DLLVM_INCLUDE_DOCS=OFF -DLLVM_INCLUDE_EXAMPLES=OFF -DLLVM_INCLUDE_TESTS=OFF -DLLVM_INCLUDE_BENCHMARKS=OFF -DLLVM_INCLUDE_UTILS=OFF -DLLVM_INSTALL_UTILS=OFF -DLLVM_ENABLE_DIA_SDK=OFF -DLLVM_ENABLE_PDB=OFF -DLLVM_USE_INTEL_JITEVENTS=ON "$(SolutionDir)3rdparty\llvm\llvm\llvm"
cmake -G Ninja ^
-DCMAKE_CXX_COMPILER="clang-cl.exe" ^
-DCMAKE_C_COMPILER="clang-cl.exe" ^
-DCMAKE_BUILD_TYPE="Debug" ^
-DCMAKE_INSTALL_PREFIX="$(SolutionDir)build\lib\$(Configuration)-$(Platform)\llvm_build" ^
-DCMAKE_SYSTEM_VERSION=10.0 ^
-DCMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION=$(WindowsTargetPlatformVersion) ^
-DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreadedDebugDLL ^
-DLLVM_TARGETS_TO_BUILD=X86 ^
-DLLVM_DEFAULT_TARGET_TRIPLE:STRING=x86_64-pc-windows-msvc ^
-DLLVM_HOST_TRIPLE:STRING=x86_64-pc-windows-msvc ^
-DLLVM_BUILD_RUNTIME=OFF ^
-DLLVM_BUILD_TOOLS=OFF ^
-DLLVM_BUILD_BENCHMARKS=OFF ^
-DLLVM_BUILD_DOCS=OFF ^
-DLLVM_BUILD_EXAMPLES=OFF ^
-DLLVM_BUILD_TESTS=OFF ^
-DLLVM_INCLUDE_TOOLS=OFF ^
-DLLVM_INCLUDE_DOCS=OFF ^
-DLLVM_INCLUDE_EXAMPLES=OFF ^
-DLLVM_INCLUDE_TESTS=OFF ^
-DLLVM_INCLUDE_BENCHMARKS=OFF ^
-DLLVM_INCLUDE_UTILS=OFF ^
-DLLVM_INSTALL_UTILS=OFF ^
-DLLVM_ENABLE_DIA_SDK=OFF ^
-DLLVM_ENABLE_PDB=OFF ^
-DLLVM_USE_INTEL_JITEVENTS=ON ^
"$(SolutionDir)3rdparty\llvm\llvm\llvm"
</CmakeDebugCLI>
<CmakeCleanCLI>
echo Cleaning..
+44 -2
View File
@@ -41,12 +41,54 @@
<CmakeReleaseCLI>call vsdevcmd.bat -arch=amd64
if not exist "$(SolutionDir)build\tmp\protobuf_build-$(Configuration)-$(Platform)" mkdir "$(SolutionDir)build\tmp\protobuf_build-$(Configuration)-$(Platform)"
cd "$(SolutionDir)build\tmp\protobuf_build-$(Configuration)-$(Platform)"
cmake -G Ninja -DCMAKE_CXX_COMPILER="cl.exe" -DCMAKE_C_COMPILER="cl.exe" -DCMAKE_CXX_STANDARD=20 -DCMAKE_BUILD_TYPE="Release" -DCMAKE_INSTALL_PREFIX="$(SolutionDir)build\lib\$(Configuration)-$(Platform)\protobuf_build" -DCMAKE_SYSTEM_VERSION=10.0 -DCMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION=$(WindowsTargetPlatformVersion) -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded -Dprotobuf_INSTALL=ON -Dprotobuf_BUILD_TESTS=OFF -Dprotobuf_BUILD_CONFORMANCE=OFF -Dprotobuf_BUILD_EXAMPLES=OFF -Dprotobuf_BUILD_PROTOBUF_BINARIES=ON -Dprotobuf_BUILD_PROTOC_BINARIES=OFF -Dprotobuf_BUILD_LIBPROTOC=OFF -Dprotobuf_BUILD_LIBUPB=OFF -Dprotobuf_ALLOW_CCACHE=ON -Dprotobuf_FORCE_FETCH_DEPENDENCIES=ON -Dprotobuf_WITH_ZLIB=OFF -Dprotobuf_MSVC_STATIC_RUNTIME=ON "$(SolutionDir)3rdparty\protobuf\protobuf"
cmake -G Ninja ^
-DCMAKE_CXX_COMPILER="cl.exe" ^
-DCMAKE_C_COMPILER="cl.exe" ^
-DCMAKE_CXX_STANDARD=20 ^
-DCMAKE_BUILD_TYPE="Release" ^
-DCMAKE_INSTALL_PREFIX="$(SolutionDir)build\lib\$(Configuration)-$(Platform)\protobuf_build" ^
-DCMAKE_SYSTEM_VERSION=10.0 ^
-DCMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION=$(WindowsTargetPlatformVersion) ^
-DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded ^
-Dprotobuf_INSTALL=ON ^
-Dprotobuf_BUILD_TESTS=OFF ^
-Dprotobuf_BUILD_CONFORMANCE=OFF ^
-Dprotobuf_BUILD_EXAMPLES=OFF ^
-Dprotobuf_BUILD_PROTOBUF_BINARIES=ON ^
-Dprotobuf_BUILD_PROTOC_BINARIES=OFF ^
-Dprotobuf_BUILD_LIBPROTOC=OFF ^
-Dprotobuf_BUILD_LIBUPB=OFF ^
-Dprotobuf_ALLOW_CCACHE=ON ^
-Dprotobuf_FORCE_FETCH_DEPENDENCIES=ON ^
-Dprotobuf_WITH_ZLIB=OFF ^
-Dprotobuf_MSVC_STATIC_RUNTIME=ON ^
"$(SolutionDir)3rdparty\protobuf\protobuf"
</CmakeReleaseCLI>
<CmakeDebugCLI>call vsdevcmd.bat -arch=amd64
if not exist "$(SolutionDir)build\tmp\protobuf_build-$(Configuration)-$(Platform)" mkdir "$(SolutionDir)build\tmp\protobuf_build-$(Configuration)-$(Platform)"
cd "$(SolutionDir)build\tmp\protobuf_build-$(Configuration)-$(Platform)"
cmake -G Ninja -DCMAKE_CXX_COMPILER="cl.exe" -DCMAKE_C_COMPILER="cl.exe" -DCMAKE_CXX_STANDARD=20 -DCMAKE_BUILD_TYPE="Debug" -DCMAKE_INSTALL_PREFIX="$(SolutionDir)build\lib\$(Configuration)-$(Platform)\protobuf_build" -DCMAKE_SYSTEM_VERSION=10.0 -DCMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION=$(WindowsTargetPlatformVersion) -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreadedDebugDLL -Dprotobuf_INSTALL=ON -Dprotobuf_BUILD_TESTS=OFF -Dprotobuf_BUILD_CONFORMANCE=OFF -Dprotobuf_BUILD_EXAMPLES=OFF -Dprotobuf_BUILD_PROTOBUF_BINARIES=ON -Dprotobuf_BUILD_PROTOC_BINARIES=OFF -Dprotobuf_BUILD_LIBPROTOC=OFF -Dprotobuf_BUILD_LIBUPB=OFF -Dprotobuf_ALLOW_CCACHE=ON -Dprotobuf_FORCE_FETCH_DEPENDENCIES=ON -Dprotobuf_WITH_ZLIB=OFF -Dprotobuf_MSVC_STATIC_RUNTIME=OFF "$(SolutionDir)3rdparty\protobuf\protobuf"
cmake -G Ninja ^
-DCMAKE_CXX_COMPILER="cl.exe" ^
-DCMAKE_C_COMPILER="cl.exe" ^
-DCMAKE_CXX_STANDARD=20 ^
-DCMAKE_BUILD_TYPE="Debug" ^
-DCMAKE_INSTALL_PREFIX="$(SolutionDir)build\lib\$(Configuration)-$(Platform)\protobuf_build" ^
-DCMAKE_SYSTEM_VERSION=10.0 ^
-DCMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION=$(WindowsTargetPlatformVersion) ^
-DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreadedDebugDLL ^
-Dprotobuf_INSTALL=ON ^
-Dprotobuf_BUILD_TESTS=OFF ^
-Dprotobuf_BUILD_CONFORMANCE=OFF ^
-Dprotobuf_BUILD_EXAMPLES=OFF ^
-Dprotobuf_BUILD_PROTOBUF_BINARIES=ON ^
-Dprotobuf_BUILD_PROTOC_BINARIES=OFF ^
-Dprotobuf_BUILD_LIBPROTOC=OFF ^
-Dprotobuf_BUILD_LIBUPB=OFF ^
-Dprotobuf_ALLOW_CCACHE=ON ^
-Dprotobuf_FORCE_FETCH_DEPENDENCIES=ON ^
-Dprotobuf_WITH_ZLIB=OFF ^
-Dprotobuf_MSVC_STATIC_RUNTIME=OFF ^
"$(SolutionDir)3rdparty\protobuf\protobuf"
</CmakeDebugCLI>
<CmakeCleanCLI>
echo Cleaning..
+1 -1
View File
@@ -132,7 +132,7 @@ Start **Visual Studio**, click on `Open a project or solution` and select the `r
**NOTE:** The recommended build configuration is `Release`. (On older revisions: `Release - LLVM`)
To speed up the compilation time, you may want to download and extract to `<rpcs3_root>\build\lib_ext\<$(Configuration)>-x64` (e.g. `c:\rpcs3\build\lib_ext\Release-x64`; the path needs to be created) the following precompiled lib:
- [LLVM libs](https://github.com/RPCS3/llvm-mirror/releases/download/custom-build-win-19.1.7/llvmlibs_mt.7z)
- [LLVM libs](https://github.com/RPCS3/llvm-mirror/releases/download/custom-build-win-22.1.8/llvmlibs_mt.7z)
**NOTES:**
- `<$(Configuration)>` can assume values `Release` or `Debug`.
+5
View File
@@ -707,6 +707,11 @@ namespace cfg
return *m_value.load().get();
}
std::string get() const
{
return *m_value.load().get();
}
std::string def_to_string() const override
{
return def;
+12
View File
@@ -26,6 +26,7 @@ std::string g_android_cache_dir;
#include <cwchar>
#include <Windows.h>
#include <winioctl.h>
static std::unique_ptr<wchar_t[]> to_wchar(std::string_view source)
{
@@ -1963,6 +1964,17 @@ fs::native_handle fs::file::get_handle() const
#endif
}
bool fs::set_sparse([[maybe_unused]] const fs::file& file)
{
#ifdef _WIN32
FILE_SET_SPARSE_BUFFER sparse{TRUE};
DWORD returned = 0;
return DeviceIoControl(file.get_handle(), FSCTL_SET_SPARSE, &sparse, sizeof(sparse), nullptr, 0, &returned, nullptr) != FALSE;
#else
return true;
#endif
}
fs::file_id fs::file::get_id() const
{
if (m_file)
+3
View File
@@ -496,6 +496,9 @@ namespace fs
}
};
// Enable sparse-file semantics when required by the host platform.
bool set_sparse(const file& file);
class dir final
{
std::unique_ptr<dir_base> m_dir{};
-8
View File
@@ -707,11 +707,7 @@ jit_compiler::jit_compiler(const std::unordered_map<std::string, u64>& _link, st
std::string result;
auto null_mod = std::make_unique<llvm::Module> ("null_", *m_context);
#if LLVM_VERSION_MAJOR >= 21 && (LLVM_VERSION_MINOR >= 1 || LLVM_VERSION_MAJOR >= 22)
null_mod->setTargetTriple(llvm::Triple(jit_compiler::triple1()));
#else
null_mod->setTargetTriple(jit_compiler::triple1());
#endif
std::unique_ptr<llvm::RTDyldMemoryManager> mem;
@@ -725,11 +721,7 @@ jit_compiler::jit_compiler(const std::unordered_map<std::string, u64>& _link, st
else
{
mem = std::make_unique<MemoryManager2>(std::move(symbols_cement));
#if LLVM_VERSION_MAJOR >= 21 && (LLVM_VERSION_MINOR >= 1 || LLVM_VERSION_MAJOR >= 22)
null_mod->setTargetTriple(llvm::Triple(jit_compiler::triple2()));
#else
null_mod->setTargetTriple(jit_compiler::triple2());
#endif
}
}
else
+25 -3
View File
@@ -161,7 +161,7 @@ namespace fmt
std::string_view trim_back_sv(std::string_view source, std::string_view values = " \t");
template <typename T>
std::string merge(const T& source, std::string_view separator)
std::string merge(const T& source, std::string_view separator, bool is_skip_empty = false)
{
if (source.empty())
{
@@ -180,6 +180,23 @@ namespace fmt
auto it = source.begin();
auto end = source.end();
if (is_skip_empty)
{
for (; it != end; ++it)
{
if (it->empty()) continue;
if (!result.empty() && !separator.empty())
{
result.append(separator);
}
result.append(*it);
}
return result;
}
for (--end; it != end; ++it)
{
result.append(*it);
@@ -192,7 +209,7 @@ namespace fmt
}
template <typename T>
std::string merge(std::initializer_list<T> sources, std::string_view separator)
std::string merge(std::initializer_list<T> sources, std::string_view separator, bool is_skip_empty = false)
{
if (!sources.size())
{
@@ -213,6 +230,11 @@ namespace fmt
for (const auto& v : sources)
{
if (v.empty()) continue;
std::string sub = fmt::merge(v, separator, is_skip_empty);
if (sub.empty()) continue;
if (first)
{
first = false;
@@ -222,7 +244,7 @@ namespace fmt
result.append(separator);
}
result.append(fmt::merge(v, separator));
result.append(std::move(sub));
}
return result;
+403 -9
View File
@@ -1250,20 +1250,296 @@ usz get_x64_access_size(x64_context* context, x64_op_t op, x64_reg_t reg, usz d_
#elif defined(ARCH_ARM64)
#if defined(__APPLE__)
#ifdef _WIN32
#define RIP(context) (reinterpret_cast<CONTEXT*>((context))->Pc)
#define GPR(context, index) (reinterpret_cast<CONTEXT*>((context))->X[index])
#elif defined(__APPLE__)
// https://github.com/bombela/backward-cpp/issues/200
#define RIP(context) ((context)->uc_mcontext->__ss.__pc)
#define GPR(context, index) ((context)->uc_mcontext->__ss.__x[(index)])
#elif defined(__FreeBSD__)
#define RIP(context) ((context)->uc_mcontext.mc_gpregs.gp_elr)
#define GPR(context, index) ((context)->uc_mcontext.mc_gpregs.gp_x[(index)])
#elif defined(__NetBSD__)
#define RIP(context) ((context)->uc_mcontext.__gregs[_REG_PC])
#define GPR(context, index) ((context)->uc_mcontext.__gregs[(index)])
#elif defined(__OpenBSD__)
#define RIP(context) ((context)->sc_elr)
#define GPR(context, index) ((context)->sc_x[(index)])
#else
#define RIP(context) ((context)->uc_mcontext.pc)
#define GPR(context, index) ((context)->uc_mcontext.regs[(index)])
#endif
#endif /* ARCH_ */
enum mem_a64_op_t
{
A64_INVALID = 0,
A64_LOAD,
A64_STORE,
};
struct a64_mem_info_t
{
mem_a64_op_t op;
u32 mem_size; // Bytes accessed in memory
u32 reg_size; // Register width (4 or 8 bytes)
u32 reg_num;
bool reg_signed;
};
a64_mem_info_t decode_a64_mem_inst(u32 inst)
{
a64_mem_info_t r{ A64_INVALID, 0, 0, inst % 32, false };
// Exclude SIMD/FP loads/stores
if ((inst >> 26) & 1)
{
return r;
}
// Scalar load/store immediate, unsigned offset variants only:
// size[31:30]
// V[26]
// opc[23:22]
// class bits[29:24] = 111001
if ((inst & 0x3B000000) == 0x39000000)
{
const u32 size = (inst >> 30) & 3;
const u32 opc = (inst >> 22) & 3;
r.mem_size = 1u << size;
switch (opc)
{
case 0:
{
// STR
r.op = A64_STORE;
r.reg_size = r.mem_size;
return r;
}
case 1:
{
// LDR unsigned zero-extend
// size=3 (64-bit) -> Xt; everything else -> Wt
r.op = A64_LOAD;
r.reg_size = (size == 3) ? 8u : 4u;
r.reg_signed = false;
return r;
}
case 2:
case 3:
{
if (size == 3)
{
return r;
}
if (size == 2 && opc == 3)
{
// Invalid LDRSW
return r;
}
// LDRSB/LDRSH/LDRSW
// size determines extension type:
// 00 LDRSB
// 01 LDRSH
// 10 LDRSW
r.op = A64_LOAD;
if (size == 2)
{
// LDUSW
r.reg_size = 8;
}
else
{
// LDRSB/LDRSH
// opc=2 -> Wt, opc=3 -> Xt
r.reg_size = (opc == 3) ? 4 : 8;
}
r.reg_signed = true;
return r;
}
default:
return r;
}
}
// Scalar load/store unscaled immediate (LDUR/STUR)
// size[31:30]
// V[26]
// opc[23:22]
if ((inst & 0x3B200C00u) == 0x38000000u)
{
const u32 size = (inst >> 30) & 3;
const u32 opc = (inst >> 22) & 3;
r.mem_size = 1u << size;
switch (opc)
{
case 0:
{
// STURB/STURH/STUR Wt/STUR Xt
r.op = A64_STORE;
// Source register width
r.reg_size = r.mem_size;
return r;
}
case 1:
{
// LDURB/LDURH/LDUR Wt/LDUR Xt
r.op = A64_LOAD;
// Destination register width
r.reg_size = (size == 3) ? 8 : 4;
r.reg_signed = false;
return r;
}
case 2:
case 3:
{
// LDURSB/LDURSH/LDURSW
if (size == 3)
{
return r;
}
r.op = A64_LOAD;
r.reg_signed = true;
if (size == 2)
{
// LDURSW
r.reg_size = 8;
}
else
{
// LDURSB/LDURSH
// opc=2 -> Wt, opc=3 -> Xt
r.reg_size = (opc == 3) ? 4 : 8;
}
return r;
}
default:
return r;
}
}
//
// Literal loads:
//
// LDR Wt, label
// LDR Xt, label
// LDRSW Xt, label
//
// This is not needed for MMIO (which is the only use for this function)
// if ((inst & 0x3B000000) == 0x18000000)
// {
// u32 opc = (inst >> 30) & 3;
// r.op = A64_LOAD;
// switch (opc)
// {
// case 0: // LDR Wt literal
// {
// r.mem_size = 4;
// r.reg_size = 4;
// return r;
// }
// case 1: // LDR Xt literal
// {
// r.mem_size = 8;
// r.reg_size = 8;
// return r;
// }
// case 2: // LDRSW literal
// {
// r.mem_size = 4;
// r.reg_size = 8;
// r.reg_signed = true;
// return r;
// }
// default:
// {
// break;
// }
// }
// }
return r;
}
void put_a64_reg_value(ucontext_t* context, u32 reg_index, u32 reg_size, bool reg_signed, u32 mem_size, u64 value)
{
ensure(mem_size == 1 || mem_size == 2 || mem_size == 4 || mem_size == 8);
ensure(reg_size == 1 || reg_size == 2 || reg_size == 4 || reg_size == 8);
ensure(reg_size >= mem_size);
ensure(reg_index < 32);
if (reg_index == 31)
{
// XZR "register"
ensure(false);
}
auto make_mask = [](u32 bytes) -> u64
{
if (bytes == 8)
{
return umax;
}
const u64 bits = bytes * 8;
return (u64{1} << bits) - 1;
};
// Mask for sign-extending the value
const u64 sign_bit = value & (make_mask(mem_size) / 2 + 1);
const u64 sign_mask = (reg_signed && sign_bit != 0 && reg_size > mem_size) ? (make_mask(reg_size) & ~make_mask(mem_size)) : 0;
u64 temp_reg_value = 0;
temp_reg_value |= (value & make_mask(mem_size)); // Set value (adjusted by size)
temp_reg_value |= sign_mask; // Apply sign-extension
GPR(context, reg_index) = temp_reg_value;
}
u64 get_a64_reg_value(ucontext_t* context, u32 reg_index, u32 reg_size)
{
ensure(reg_size == 1 || reg_size == 2 || reg_size == 4 || reg_size == 8);
ensure(reg_index < 32);
if (reg_index == 31)
{
// XZR "register"
return 0;
}
auto make_mask = [](u32 bytes) -> u64
{
if (bytes == 8)
{
return umax;
}
const u64 bits = bytes * 8;
return (u64{1} << bits) - 1;
};
return (GPR(context, reg_index) & make_mask(reg_size));
}
#endif /* ARCH_ARM64 */
namespace rsx
{
@@ -1478,11 +1754,11 @@ bool handle_access_violation(u32 addr, bool is_writing, bool is_exec, ucontext_t
if (a_size == 4)
{
value = stx::se_storage<u32>::swap(value);
value = std::bit_cast<be_t<u32>>(value);
}
else if (a_size == 2)
{
value = stx::se_storage<u16>::swap(value);
value = std::bit_cast<be_t<u16>>(static_cast<u16>(value));
}
else
{
@@ -1556,8 +1832,126 @@ bool handle_access_violation(u32 addr, bool is_writing, bool is_exec, ucontext_t
g_tls_fault_spu++;
return true;
} while (0);
#else
static_cast<void>(context);
#elif defined(ARCH_ARM64)
const u8* const code = reinterpret_cast<u8*>(RIP(context));
const u32 instruction = read_from_ptr_unsafe<u32>(code);
const auto [op, mem_size, reg_size, reg_index, reg_signed] = decode_a64_mem_inst(instruction);
auto report_opcode = [&]()
{
sig_log.error("decode_a64_mem_inst(%p): unsupported opcode: %s", code, +std::bit_cast<be_t<u32>>(instruction));
};
if (0x1'0000'0000ull - addr < mem_size)
{
sig_log.error("Invalid mem_size (0x%llx)", mem_size);
report_opcode();
return false;
}
// check if address is RawSPU MMIO register
do if (addr - RAW_SPU_BASE_ADDR < (6 * RAW_SPU_OFFSET) && (addr % RAW_SPU_OFFSET) >= RAW_SPU_PROB_OFFSET)
{
auto thread = idm::get_unlocked<named_thread<spu_thread>>(spu_thread::find_raw_spu((addr - RAW_SPU_BASE_ADDR) / RAW_SPU_OFFSET));
if (!thread || is_exec)
{
break;
}
if (!mem_size)
{
sig_log.error("Invalid or unsupported instruction (reg=%d, mem_size=%lld, reg_size=0x%llx)", reg_index, mem_size, reg_size);
report_opcode();
return false;
}
bool handled = true;
switch (op)
{
case A64_LOAD:
{
u32 value;
const u32 addr_aligned = addr & -4;
if (addr % 4 + mem_size > 4)
{
handled = false;
break;
}
if (is_writing || !thread->read_reg(addr_aligned, value))
{
return false;
}
// Adjust value for 8-bit and 16-bit reads
value >>= ((4 - mem_size) * 8) - ((addr % 4) * 8);
value &= mem_size == 4 ? u32{umax} : ((1u << (mem_size * 8)) - 1);
if (mem_size == 4)
{
value = std::bit_cast<be_t<u32>>(value);
}
else if (mem_size == 2)
{
value = std::bit_cast<be_t<u16>>(static_cast<u16>(value));
}
else
{
ensure(mem_size == 1);
}
// Update register value
put_a64_reg_value(context, reg_index, reg_size, reg_signed, mem_size, value);
break;
}
case A64_STORE:
{
if (mem_size != 4)
{
// Might be unimplemented, such as writing MFC proxy EAL+EAH using 64-bit store
handled = false;
break;
}
if (!is_writing)
{
return false;
}
const u64 reg_value = get_a64_reg_value(context, reg_index, reg_size);
const u32 val32 = static_cast<u32>(reg_value);
if (!thread->write_reg(addr, std::bit_cast<be_t<u32>>(val32)))
{
return false;
}
break;
}
default:
{
sig_log.error("Invalid or unsupported operation (reg=%d, mem_size=%lld, reg_size=0x%llx)", reg_index, mem_size, reg_size);
report_opcode();
return false;
}
}
if (!handled)
{
sig_log.error("Invalid or unsupported operation (reg=%d, mem_size=%lld, reg_size=0x%llx)", reg_index, mem_size, reg_size);
report_opcode();
break;
}
// skip processed instruction
RIP(context) = reinterpret_cast<std::remove_cvref_t<decltype(RIP(context))>>(reinterpret_cast<const char*>(RIP(context)) + 4);
g_tls_fault_spu++;
return true;
} while (0);
#endif /* ARCH_ */
const auto required_page_perms = (is_writing ? vm::page_writable : vm::page_readable) + (is_exec ? vm::page_executable : 0);
@@ -2133,7 +2527,7 @@ static void signal_handler(int /*sig*/, siginfo_t* info, void* uct) noexcept
}
#else
const u32 insn = is_executing ? 0 : *reinterpret_cast<u32*>(RIP(context));
const u32 insn = is_executing ? 0 : read_from_ptr_unsafe<u32>(RIP(context));
const bool is_writing =
(insn & 0xbfff0000) == 0x0c000000 || // STR <Wt>, [<Xn>, #<imm>] (store word with immediate offset)
(insn & 0xbfe00000) == 0x0c800000 || // STP <Wt1>, <Wt2>, [<Xn>, #<imm>] (store pair of registers with immediate offset)
@@ -2339,8 +2733,8 @@ void thread_base::start()
pthread_attr_t attrs;
pthread_t thread_id{};
struct sched_param sp;
memset(&sp, 0, sizeof(struct sched_param));
sp.sched_priority=99;
memset(&sp, 0, sizeof(struct sched_param));
sp.sched_priority=99;
pthread_attr_init(&attrs);
pthread_attr_setstacksize(&attrs, 0x800000);
+11 -1
View File
@@ -36,6 +36,7 @@
avutil.lib;
swresample.lib;
swscale.lib;
LLVMABI.lib;
LLVMAggressiveInstCombine.lib;
LLVMAnalysis.lib;
LLVMAsmParser.lib;
@@ -44,9 +45,10 @@
LLVMBitReader.lib;
LLVMBitstreamReader.lib;
LLVMBitWriter.lib;
LLVMCAS.lib;
LLVMCFGuard.lib;
LLVMCGData.lib;
LLVMCodeGen.lib;
LLVMCodeGenData.lib;
LLVMCodeGenTypes.lib;
LLVMCore.lib;
LLVMCoroutines.lib;
@@ -55,12 +57,15 @@
LLVMDebugInfoCodeView.lib;
LLVMDebuginfod.lib;
LLVMDebugInfoDWARF.lib;
LLVMDebugInfoDWARFLowLevel.lib;
LLVMDebugInfoGSYM.lib;
LLVMDebugInfoLogicalView.lib;
LLVMDebugInfoMSF.lib;
LLVMDebugInfoPDB.lib;
LLVMDemangle.lib;
LLVMDlltoolDriver.lib;
LLVMDTLTO.lib;
LLVMDWARFCFIChecker.lib;
LLVMDWARFLinker.lib;
LLVMDWARFLinkerClassic.lib;
LLVMDWARFLinkerParallel.lib;
@@ -68,6 +73,8 @@
LLVMExecutionEngine.lib;
LLVMExtensions.lib;
LLVMFileCheck.lib;
LLVMFrontendAtomic.lib;
LLVMFrontendDirective.lib;
LLVMFrontendDriver.lib;
LLVMFrontendHLSL.lib;
LLVMFrontendOffloading.lib;
@@ -107,6 +114,7 @@
LLVMOrcShared.lib;
LLVMOrcTargetProcess.lib;
LLVMPasses.lib;
LLVMPlugins.lib;
LLVMProfileData.lib;
LLVMRemarks.lib;
LLVMRuntimeDyld.lib;
@@ -114,12 +122,14 @@
LLVMScalarOpts.lib;
LLVMSelectionDAG.lib;
LLVMSupport.lib;
LLVMSupportLSP.lib;
LLVMSymbolize.lib;
LLVMTableGen.lib;
LLVMTableGenBasic.lib;
LLVMTableGenCommon.lib;
LLVMTarget.lib;
LLVMTargetParser.lib;
LLVMTelemetry.lib;
LLVMTextAPI.lib;
LLVMTextAPIBinaryReader.lib;
LLVMTransformUtils.lib;
+48 -4
View File
@@ -255,7 +255,27 @@ bool package_reader::read_metadata()
if (packet.size == sizeof(m_metadata.package_type))
{
archive_read(&m_metadata.package_type, sizeof(m_metadata.package_type));
std::vector<std::string> package_flags;
if (m_metadata.package_type & pkg_flag::PKG_FLAG_0x01) package_flags.push_back("0x01");
if (m_metadata.package_type & pkg_flag::PKG_FLAG_EBOOT) package_flags.push_back("EBOOT");
if (m_metadata.package_type & pkg_flag::PKG_FLAG_REQUIRE_LICENSE) package_flags.push_back("REQUIRE_LICENSE");
if (m_metadata.package_type & pkg_flag::PKG_FLAG_HDD_MC) package_flags.push_back("HDD_MC");
if (m_metadata.package_type & pkg_flag::PKG_FLAG_PATCH) package_flags.push_back("PATCH");
if (m_metadata.package_type & pkg_flag::PKG_FLAG_0x20) package_flags.push_back("0x20");
if (m_metadata.package_type & pkg_flag::PKG_FLAG_RENAME_DIRECTORY) package_flags.push_back("RENAME_DIRECTORY");
if (m_metadata.package_type & pkg_flag::PKG_FLAG_EDAT) package_flags.push_back("EDAT");
if (m_metadata.package_type & pkg_flag::PKG_FLAG_0x100) package_flags.push_back("0x100");
if (m_metadata.package_type & pkg_flag::PKG_FLAG_EMULATOR) package_flags.push_back("EMULATOR");
if (m_metadata.package_type & pkg_flag::PKG_FLAG_VSH_MODULE) package_flags.push_back("VSH_MODULE");
if (m_metadata.package_type & pkg_flag::PKG_FLAG_DISC_BOUND) package_flags.push_back("DISC_BOUND");
if (m_metadata.package_type & pkg_flag::PKG_FLAG_UNKNOWN) package_flags.push_back("UNKNOWN");
if (m_metadata.package_type & pkg_flag::PKG_FLAG_PS_VITA_CARD) package_flags.push_back("PS_VITA_CARD");
if (m_metadata.package_type & pkg_flag::PKG_FLAG_PS_VITA_NON_GAME) package_flags.push_back("PS_VITA_NON_GAME");
if (m_metadata.package_type & pkg_flag::PKG_FLAG_0x8000) package_flags.push_back("0x8000");
pkg_log.notice("Metadata: Package Type = 0x%x = %d", m_metadata.package_type, m_metadata.package_type);
pkg_log.notice("Metadata: Package Flags = %s", package_flags.empty() ? "{}" : fmt::merge(package_flags, ", "));
continue;
}
else
@@ -653,7 +673,6 @@ bool package_reader::read_param_sfo()
return false;
}
// TODO: maybe also check if VERSION matches
package_install_result package_reader::check_target_app_version() const
{
if (!m_is_valid)
@@ -698,7 +717,13 @@ package_install_result package_reader::check_target_app_version() const
{
// We are unable to compare anything with the target app version
pkg_log.error("A target app version is required (%s), but no PARAM.SFO was found for %s. (path='%s', error=%s)", target_app_ver, title_id, sfo_path, fs::g_tls_error);
return {package_install_result::error_type::app_version, {std::string(target_app_ver)}};
return {
.error = package_install_result::error_type::app_version,
.version = {
.app_ver = std::string(app_ver),
.expected = std::string(target_app_ver)
}
};
}
// There is nothing we need to compare, so we may install the package
@@ -727,6 +752,12 @@ package_install_result package_reader::check_target_app_version() const
if (target_app_ver.empty())
{
if (!(m_metadata.package_type & pkg_flag::PKG_FLAG_PATCH))
{
// This should be a DLC. Let's allow DLCs even with smaller APP_VER.
return {package_install_result::error_type::no_error};
}
// This is most likely the first patch. Let's make sure its version is high enough for the installed game.
const double new_version = std::strtod(app_ver.data(), &ev1);
@@ -744,7 +775,13 @@ package_install_result package_reader::check_target_app_version() const
}
pkg_log.error("The new app version (%s) is smaller than the installed app version (%s)", app_ver, installed_app_ver);
return {package_install_result::error_type::app_version, {std::string(app_ver), std::string(installed_app_ver)}};
return {
.error = package_install_result::error_type::app_version,
.version = {
.app_ver = std::string(app_ver),
.installed = std::string(installed_app_ver)
}
};
}
// Check if the installed app version matches the target app version
@@ -764,7 +801,14 @@ package_install_result package_reader::check_target_app_version() const
}
pkg_log.error("The installed app version (%s) does not match the target app version (%s)", installed_app_ver, target_app_ver);
return {package_install_result::error_type::app_version, {std::string(target_app_ver), std::string(installed_app_ver)}};
return {
.error = package_install_result::error_type::app_version,
.version = {
.app_ver = std::string(app_ver),
.expected = std::string(target_app_ver),
.installed = std::string(installed_app_ver)
}
};
}
bool package_reader::set_install_path()
+34 -11
View File
@@ -42,7 +42,7 @@ enum : u32
PKG_FILE_ENTRY_KNOWN_BITS = 0xff | PKG_FILE_ENTRY_PSP | PKG_FILE_ENTRY_OVERWRITE,
};
enum : u32
enum pkg_content_type : u32
{
PKG_CONTENT_TYPE_UNKNOWN_1 = 0x01, // ?
PKG_CONTENT_TYPE_UNKNOWN_2 = 0x02, // ?
@@ -63,18 +63,39 @@ enum : u32
PKG_CONTENT_TYPE_VMC = 0x11, // VMC
PKG_CONTENT_TYPE_PS2_CLASSIC = 0x12, // ?PS2Classic? Seen on PS2 classic
PKG_CONTENT_TYPE_UNKNOWN_5 = 0x13, // ?
PKG_CONTENT_TYPE_PSP_REMASTERED = 0x14, // ?
PKG_CONTENT_TYPE_PSP_REMASTERED = 0x14, // PSP Remastered
PKG_CONTENT_TYPE_PSP2_GD = 0x15, // PSVita Game Data
PKG_CONTENT_TYPE_PSP2_AC = 0x16, // PSVita Additional Content
PKG_CONTENT_TYPE_PSP2_LA = 0x17, // PSVita LiveArea
PKG_CONTENT_TYPE_PSM_1 = 0x18, // PSVita PSM ?
PKG_CONTENT_TYPE_PSM = 0x18, // PSVita PSM
PKG_CONTENT_TYPE_WT = 0x19, // Web TV ?
PKG_CONTENT_TYPE_UNKNOWN_6 = 0x1A, // ?
PKG_CONTENT_TYPE_UNKNOWN_7 = 0x1B, // ?
PKG_CONTENT_TYPE_UNKNOWN_8 = 0x1C, // ?
PKG_CONTENT_TYPE_PSM_2 = 0x1D, // PSVita PSM ?
PKG_CONTENT_TYPE_UNKNOWN_9 = 0x1E, // ?
PKG_CONTENT_TYPE_PS4_GD = 0x1A, // PS4 GameData
PKG_CONTENT_TYPE_PS4_AC = 0x1B, // PS4 Additional Content
PKG_CONTENT_TYPE_PS4_AL = 0x1C, // PS4 Additional License
PKG_CONTENT_TYPE_PSM_UNITY = 0x1D, // PSVita PSM for Unity
PKG_CONTENT_TYPE_PS4_DP = 0x1E, // PS4 Delta patch
PKG_CONTENT_TYPE_PSP2_THEME = 0x1F, // PSVita Theme
PKG_CONTENT_TYPE_PS5_GAME_DATA = 0x20, // PS5 GameData
};
enum pkg_flag : u32
{
PKG_FLAG_0x01 = 0x01,
PKG_FLAG_EBOOT = 0x02,
PKG_FLAG_REQUIRE_LICENSE = 0x04,
PKG_FLAG_HDD_MC = 0x08,
PKG_FLAG_PATCH = 0x10,
PKG_FLAG_0x20 = 0x20,
PKG_FLAG_RENAME_DIRECTORY = 0x40,
PKG_FLAG_EDAT = 0x80,
PKG_FLAG_0x100 = 0x100,
PKG_FLAG_EMULATOR = 0x200,
PKG_FLAG_VSH_MODULE = 0x400,
PKG_FLAG_DISC_BOUND = 0x800,
PKG_FLAG_UNKNOWN = 0x1000,
PKG_FLAG_PS_VITA_CARD = 0x2000,
PKG_FLAG_PS_VITA_NON_GAME = 0x4000,
PKG_FLAG_0x8000 = 0x8000,
};
// Structs
@@ -305,10 +326,11 @@ struct package_install_result
app_version,
other
} error = error_type::no_error;
struct version
struct versions
{
std::string app_ver;
std::string expected;
std::string found;
std::string installed;
} version;
};
@@ -348,6 +370,7 @@ public:
bool is_valid() const { return m_is_valid; }
const PKGHeader& get_header() const { return m_header; }
const PKGMetaData& get_metadata() const { return m_metadata; }
package_install_result check_target_app_version() const;
static package_install_result extract_data(std::deque<package_reader>& readers, std::deque<std::string>& bootable_paths);
const psf::registry& get_psf() const { return m_psf; }
@@ -357,7 +380,7 @@ public:
void abort_extract();
fs::file &file()
fs::file& file()
{
return m_file;
}
+11
View File
@@ -29,6 +29,17 @@ if(USE_ASAN)
set_source_files_properties(../../Utilities/Thread.cpp PROPERTIES COMPILE_DEFINITIONS USE_ASAN)
endif()
# GCC overemits weak type information when using LTO
# This causes an issue with LLVM 22 which is build without RTTI
# Issue is specifically related to vfs::ProxyFileSystem
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
set_source_files_properties(
Cell/PPUThread.cpp
Cell/SPULLVMRecompiler.cpp
PROPERTIES COMPILE_OPTIONS -fno-lto
)
endif()
if(NOT USE_SYSTEM_OPENAL)
target_compile_definitions(rpcs3_emu PUBLIC AL_LIBTYPE_STATIC)
endif()
+133 -12
View File
@@ -71,11 +71,7 @@ cpu_translator::cpu_translator(llvm::Module* _module, bool is_be)
result = m_ir->CreateInsertElement(v, m_ir->CreateExtractElement(data0, m_ir->CreateExtractElement(mask, i)), i);
v->addIncoming(result, loop);
m_ir->CreateCondBr(m_ir->CreateICmpULT(i, m_ir->getInt32(16)), loop, next);
#if LLVM_VERSION_MAJOR >= 21 || (LLVM_VERSION_MAJOR == 20 && LLVM_VERSION_MINOR >= 1)
m_ir->SetInsertPoint(next->getFirstNonPHIIt());
#else
m_ir->SetInsertPoint(next->getFirstNonPHI());
#endif
result = m_ir->CreateSelect(m_ir->CreateICmpSLT(index, zeros), zeros, result);
return result;
@@ -260,9 +256,7 @@ llvm::Value* cpu_translator::bitcast(llvm::Value* val, llvm::Type* type, std::so
}
// Skip use iteration for values that don't have use lists
#if LLVM_VERSION_MAJOR >= 21
if (source_val->hasUseList())
#endif
{
for (llvm::Value* it_val : source_val->uses())
{
@@ -287,12 +281,10 @@ llvm::Value* cpu_translator::bitcast(llvm::Value* val, llvm::Type* type, std::so
}
// Check if bci has use list before accessing use_begin()
#if LLVM_VERSION_MAJOR >= 21
if (!bci->hasUseList())
{
break;
}
#endif
if (bci->use_begin() == bci->use_end())
{
@@ -317,6 +309,9 @@ llvm::Value* cpu_translator::bitcast(llvm::Value* val, llvm::Type* type, std::so
template <>
std::pair<bool, v128> cpu_translator::get_const_vector<v128>(llvm::Value* c, u32 _pos, u32 _line)
{
// Bitcasts do not matter
c = peek_through_bitcasts(c);
v128 result{};
if (!llvm::isa<llvm::Constant>(c))
@@ -581,10 +576,8 @@ void cpu_translator::erase_stores(llvm::ArrayRef<llvm::Value*> args)
for (auto v : args)
{
// Skip use iteration for values that don't have use lists
#if LLVM_VERSION_MAJOR >= 21
if (!v->hasUseList())
continue;
#endif
for (llvm::Value* i : v->uses())
{
@@ -594,10 +587,8 @@ void cpu_translator::erase_stores(llvm::ArrayRef<llvm::Value*> args)
while (i && (bci = llvm::dyn_cast<llvm::CastInst>(i)) && bci->getOpcode() == llvm::Instruction::BitCast)
{
// Check if bci has use list before accessing use_begin()
#if LLVM_VERSION_MAJOR >= 21
if (!bci->hasUseList())
break;
#endif
i = *bci->use_begin();
}
@@ -610,4 +601,134 @@ void cpu_translator::erase_stores(llvm::ArrayRef<llvm::Value*> args)
}
}
llvm::KnownBits cpu_translator::get_known_bits_fallback(llvm::Value* value)
{
// TODO: Improve it - add support for integer addition/subtraction and more stuff
const auto type = value->getType();
if (!type->isVectorTy())
{
if (llvm::isa<llvm::IntegerType>(type))
{
if (auto bin_inst = llvm::dyn_cast<llvm::BinaryOperator>(value))
{
llvm::Value* lhs = ensure(bin_inst->getOperand(0));
llvm::Value* rhs = ensure(bin_inst->getOperand(1));
llvm::ConstantInt* constant_value = llvm::dyn_cast<llvm::ConstantInt>(rhs) ? llvm::dyn_cast<llvm::ConstantInt>(rhs) : llvm::dyn_cast<llvm::ConstantInt>(lhs);
if (!constant_value)
{
return llvm::KnownBits(type->getScalarSizeInBits());
}
if (bin_inst->getOpcode() == llvm::Instruction::Or)
{
llvm::KnownBits ret(type->getScalarSizeInBits());
ret.One = constant_value->getValue();
return ret;
}
if (bin_inst->getOpcode() == llvm::Instruction::And)
{
llvm::KnownBits ret(type->getScalarSizeInBits());
ret.Zero = constant_value->getValue();
ret.Zero.flipAllBits();
return ret;
}
return llvm::KnownBits(type->getScalarSizeInBits());
}
}
fmt::throw_exception("Bad KnownBits type: i%ux", type->getScalarSizeInBits());
}
if (auto v = llvm::cast<llvm::FixedVectorType>(type); v->getScalarSizeInBits() * v->getNumElements() != 128)
{
// Unsupported
return llvm::KnownBits(type->getScalarSizeInBits());
}
const auto original_value = peek_through_bitcasts(value);
auto bin_inst = llvm::dyn_cast<llvm::BinaryOperator>(original_value);
if (!bin_inst)
{
return llvm::KnownBits(type->getScalarSizeInBits());
}
llvm::Value* lhs = ensure(bin_inst->getOperand(0));
llvm::Value* rhs = ensure(bin_inst->getOperand(1));
llvm::Value* constant_value = llvm::dyn_cast<llvm::ConstantDataVector>(rhs) ? llvm::dyn_cast<llvm::ConstantDataVector>(rhs) : llvm::dyn_cast<llvm::ConstantDataVector>(lhs);
if (!constant_value)
{
return llvm::KnownBits(value->getType()->getScalarSizeInBits());
}
const auto [ok, v128_const] = get_const_vector(constant_value, -1);
ensure(ok);
llvm::APInt all_lanes{};
llvm::APInt any_lanes{};
auto combine_bits = [&](const auto& array, u32 size)
{
auto all = +array[0];
auto any = +array[0];
for (u32 i = 1; i < size; i++)
{
all &= +array[i];
any |= +array[i];
}
return std::make_pair(all, any);
};
if (type->getScalarType()->isIntegerTy(8))
{
const auto [all, any] = combine_bits(v128_const._u8, 16);
all_lanes = llvm::APInt(8, all);
any_lanes = llvm::APInt(8, any);
}
else if (type->getScalarType()->isIntegerTy(16))
{
const auto [all, any] = combine_bits(v128_const._u16, 8);
all_lanes = llvm::APInt(16, all);
any_lanes = llvm::APInt(16, any);
}
else if (type->getScalarType()->isIntegerTy(32))
{
const auto [all, any] = combine_bits(v128_const._u32, 4);
all_lanes = llvm::APInt(32, all);
any_lanes = llvm::APInt(32, any);
}
else // if (type->getScalarType()->isIntegerTy(64))
{
return llvm::KnownBits(type->getScalarSizeInBits());
}
if (bin_inst->getOpcode() == llvm::Instruction::Or)
{
llvm::KnownBits ret(type->getScalarSizeInBits());
ret.One = all_lanes;
return ret;
}
if (bin_inst->getOpcode() == llvm::Instruction::And)
{
llvm::KnownBits ret(type->getScalarSizeInBits());
ret.Zero = any_lanes;
ret.Zero.flipAllBits();
return ret;
}
return llvm::KnownBits(type->getScalarSizeInBits());
}
#endif
+46 -32
View File
@@ -27,9 +27,7 @@
#include "llvm/IR/Module.h"
#include "llvm/Target/TargetMachine.h"
#include "llvm/Support/KnownBits.h"
#if LLVM_VERSION_MAJOR >= 21
#include "llvm/Support/KnownFPClass.h"
#endif
#include "llvm/Analysis/SimplifyQuery.h"
#include "llvm/Analysis/ConstantFolding.h"
#include "llvm/Analysis/ValueTracking.h"
@@ -1180,11 +1178,7 @@ struct llvm_fshl
static llvm::Function* get_fshl(llvm::IRBuilder<>* ir)
{
const auto _module = ir->GetInsertBlock()->getParent()->getParent();
#if LLVM_VERSION_MAJOR >= 21 || (LLVM_VERSION_MAJOR == 20 && LLVM_VERSION_MINOR >= 1)
return llvm::Intrinsic::getOrInsertDeclaration(_module, llvm::Intrinsic::fshl, {llvm_value_t<T>::get_type(ir->getContext())});
#else
return llvm::Intrinsic::getDeclaration(_module, llvm::Intrinsic::fshl, {llvm_value_t<T>::get_type(ir->getContext())});
#endif
}
static llvm::Value* fold(llvm::IRBuilder<>* ir, llvm::Value* v1, llvm::Value* v2, llvm::Value* v3)
@@ -1256,11 +1250,7 @@ struct llvm_fshr
static llvm::Function* get_fshr(llvm::IRBuilder<>* ir)
{
const auto _module = ir->GetInsertBlock()->getParent()->getParent();
#if LLVM_VERSION_MAJOR >= 21 || (LLVM_VERSION_MAJOR == 20 && LLVM_VERSION_MINOR >= 1)
return llvm::Intrinsic::getOrInsertDeclaration(_module, llvm::Intrinsic::fshr, {llvm_value_t<T>::get_type(ir->getContext())});
#else
return llvm::Intrinsic::getDeclaration(_module, llvm::Intrinsic::fshr, {llvm_value_t<T>::get_type(ir->getContext())});
#endif
}
static llvm::Value* fold(llvm::IRBuilder<>* ir, llvm::Value* v1, llvm::Value* v2, llvm::Value* v3)
@@ -2366,11 +2356,7 @@ struct llvm_add_sat
static llvm::Function* get_add_sat(llvm::IRBuilder<>* ir)
{
const auto _module = ir->GetInsertBlock()->getParent()->getParent();
#if LLVM_VERSION_MAJOR >= 21 || (LLVM_VERSION_MAJOR == 20 && LLVM_VERSION_MINOR >= 1)
return llvm::Intrinsic::getOrInsertDeclaration(_module, intr, {llvm_value_t<T>::get_type(ir->getContext())});
#else
return llvm::Intrinsic::getDeclaration(_module, intr, {llvm_value_t<T>::get_type(ir->getContext())});
#endif
}
llvm::Value* eval(llvm::IRBuilder<>* ir) const
@@ -2453,11 +2439,7 @@ struct llvm_sub_sat
static llvm::Function* get_sub_sat(llvm::IRBuilder<>* ir)
{
const auto _module = ir->GetInsertBlock()->getParent()->getParent();
#if LLVM_VERSION_MAJOR >= 21 || (LLVM_VERSION_MAJOR == 20 && LLVM_VERSION_MINOR >= 1)
return llvm::Intrinsic::getOrInsertDeclaration(_module, intr, {llvm_value_t<T>::get_type(ir->getContext())});
#else
return llvm::Intrinsic::getDeclaration(_module, intr, {llvm_value_t<T>::get_type(ir->getContext())});
#endif
}
llvm::Value* eval(llvm::IRBuilder<>* ir) const
@@ -3804,11 +3786,7 @@ public:
llvm::Function* get_intrinsic(llvm::Intrinsic::ID id)
{
const auto _module = m_ir->GetInsertBlock()->getParent()->getParent();
#if LLVM_VERSION_MAJOR >= 21 || (LLVM_VERSION_MAJOR == 20 && LLVM_VERSION_MINOR >= 1)
return llvm::Intrinsic::getOrInsertDeclaration(_module, id, {get_type<Types>()...});
#else
return llvm::Intrinsic::getDeclaration(_module, id, {get_type<Types>()...});
#endif
}
template <typename T1, typename T2, typename T3>
@@ -3852,13 +3830,8 @@ public:
const auto data1 = b.eval(m_ir);
const auto data2 = c.eval(m_ir);
#if LLVM_VERSION_MAJOR >= 22
// LLVM 22+ changed the intrinsic signature from v4i32 to v16i8 for operands 2 and 3
result.value = m_ir->CreateCall(get_intrinsic(llvm::Intrinsic::x86_avx512_vpdpbusd_128),
{data0, m_ir->CreateBitCast(data1, get_type<u8[16]>()), m_ir->CreateBitCast(data2, get_type<u8[16]>())});
#else
result.value = m_ir->CreateCall(get_intrinsic(llvm::Intrinsic::x86_avx512_vpdpbusd_128), {data0, data1, data2});
#endif
return result;
}
@@ -4271,10 +4244,55 @@ template <typename T1, typename T2, typename T3>
template <typename T = v128>
llvm::Constant* make_const_vector(T, llvm::Type*, u32 = __builtin_LINE());
// IR is emitted in a single pass: phi nodes may still be missing their back-edge incoming
// values, so any known bits computeKnownBits derives through a phi are unsound for the
// final IR. Whether a phi is complete cannot be queried (the CFG edges from not-yet-emitted
// predecessors don't exist either), so reject every value whose bits may derive from a phi.
static bool is_known_bits_safe(llvm::Value* value)
{
llvm::SmallPtrSet<const llvm::Value*, 32> visited;
llvm::SmallVector<const llvm::Value*, 32> worklist{value};
while (!worklist.empty())
{
const llvm::Value* v = worklist.pop_back_val();
if (!visited.insert(v).second)
{
continue;
}
if (llvm::isa<llvm::PHINode>(v) || visited.size() > 256)
{
return false;
}
// Loads don't propagate operand bits; constants and arguments are leaves
if (auto i = llvm::dyn_cast<llvm::Instruction>(v); i && !llvm::isa<llvm::LoadInst>(i))
{
for (const llvm::Use& op : i->operands())
{
worklist.push_back(op.get());
}
}
}
return true;
}
llvm::KnownBits get_known_bits_fallback(llvm::Value* value);
template <typename T>
llvm::KnownBits get_known_bits(T a)
{
return llvm::computeKnownBits(a.eval(m_ir), m_module->getDataLayout());
llvm::Value* value = a.eval(m_ir);
if (!is_known_bits_safe(value))
{
return get_known_bits_fallback(value);
}
return llvm::computeKnownBits(value, m_module->getDataLayout());
}
template <typename T>
@@ -4288,12 +4306,8 @@ template <typename T1, typename T2, typename T3>
{
static_assert(depth <= llvm::MaxAnalysisRecursionDepth, "Depth parameter can only decrease search. Default is max.");
#if LLVM_VERSION_MAJOR >= 21
const llvm::SimplifyQuery SQ(m_module->getDataLayout());
return llvm::computeKnownFPClass(a.eval(m_ir), interested_classes, SQ, llvm::MaxAnalysisRecursionDepth - depth);
#else
return llvm::computeKnownFPClass(a.eval(m_ir), m_module->getDataLayout(), interested_classes, llvm::MaxAnalysisRecursionDepth - depth);
#endif
}
private:
+2 -2
View File
@@ -1290,11 +1290,11 @@ error_code cellRecOpen(vm::cptr<char> pDirName, vm::cptr<char> pFileName, vm::cp
rec.encoder->set_path(vfs::get(rec.param.filename));
rec.encoder->set_framerate(rec.fps);
rec.encoder->set_video_bitrate(rec.video_bps);
rec.encoder->set_video_codec(rec.video_codec_id);
rec.encoder->set_video_codec(rec.video_codec_id, "");
rec.encoder->set_sample_rate(rec.sample_rate);
rec.encoder->set_audio_channels(rec.channels);
rec.encoder->set_audio_bitrate(rec.audio_bps);
rec.encoder->set_audio_codec(rec.audio_codec_id);
rec.encoder->set_audio_codec(rec.audio_codec_id, "");
rec.encoder->set_output_format(rec.output_format);
sysutil_register_cb([&rec](ppu_thread& ppu) -> s32
+1 -5
View File
@@ -5188,7 +5188,7 @@ bool ppu_initialize(const ppu_module<lv2_obj>& info, bool check_only, u64 file_s
settings += ppu_settings::daz_and_ftz;
// Write version, hash, CPU, settings
fmt::append(obj_name, "v7-kusa-%s-%s-%s.obj", fmt::base57(output, 16), fmt::base57(settings), jit_compiler::cpu(g_cfg.core.llvm_cpu.to_string()));
fmt::append(obj_name, "v8-kusa-%s-%s-%s.obj", fmt::base57(output, 16), fmt::base57(settings), jit_compiler::cpu(g_cfg.core.llvm_cpu.to_string()));
}
if (cpu ? cpu->state.all_of(cpu_flag::exit) : Emu.IsStopped())
@@ -5586,11 +5586,7 @@ static void ppu_initialize2(jit_compiler& jit, const ppu_module<lv2_obj>& module
std::unique_ptr<Module> _module = std::make_unique<Module>(obj_name, jit.get_context());
// Initialize target
#if LLVM_VERSION_MAJOR >= 21 && (LLVM_VERSION_MINOR >= 1 || LLVM_VERSION_MAJOR >= 22)
_module->setTargetTriple(Triple(jit_compiler::triple1()));
#else
_module->setTargetTriple(jit_compiler::triple1());
#endif
_module->setDataLayout(jit.get_engine().getTargetMachine()->createDataLayout());
// Initialize translator
+87 -4
View File
@@ -2,6 +2,7 @@
#include "Emu/IdManager.h"
#include "Loader/ELF.h"
#include "util/asm.hpp"
#include "timers.hpp"
#include "SPUThread.h"
@@ -39,8 +40,67 @@ bool spu_thread::read_reg(const u32 addr, u32& value)
{
const u32 offset = addr - (RAW_SPU_BASE_ADDR + RAW_SPU_OFFSET * index) - RAW_SPU_PROB_OFFSET;
spu_log.trace("RawSPU[%u]: Read32(0x%x, offset=0x%x)", index, addr, offset);
raw_spu_log_stats_t stats{};
stats.mmio_offset = offset;
const auto [old_stats, is_changed] = mmio_stats.fetch_op([&](raw_spu_log_stats_t& old)
{
if (old.mmio_offset == offset)
{
const u64 current = get_system_time();
if (current - old.mmio_time >= 1500)
{
old.mmio_time = current;
return true;
}
return false;
}
old.mmio_offset = offset;
return true;
});
std::string log_message = fmt::format("RawSPU[%u]: read_reg(0x%x, offset=0x%x)", index, addr, offset);
if (is_changed)
{
spu_log.trace("%s", log_message);
}
struct logger_end_t
{
u32 offset;
const u32* value;
atomic_t<raw_spu_log_stats_t>* stats;
std::string log_message;
~logger_end_t() noexcept
{
const auto [old, value_changed] = stats->fetch_op([&](raw_spu_log_stats_t& old)
{
if (old.mmio_offset == offset)
{
if (old.mmio_value != *value)
{
const u64 current = get_system_time();
old.mmio_time = current;
old.mmio_value = *value;
return true;
}
}
return false;
});
if (value_changed)
{
spu_log.trace("%s: value=0x%x", log_message, *value);
}
}
} logger_end{offset, &value, &mmio_stats, std::move(log_message)};
switch (offset)
{
case MFC_CMDStatus_offs:
@@ -202,7 +262,7 @@ bool spu_thread::read_reg(const u32 addr, u32& value)
}
}
spu_log.error("RawSPU[%u]: Read32(0x%x): unknown/illegal offset (0x%x)", index, addr, offset);
spu_log.error("RawSPU[%u]: read_reg(0x%x): unknown/illegal offset (0x%x)", index, addr, offset);
return false;
}
@@ -210,7 +270,30 @@ bool spu_thread::write_reg(const u32 addr, const u32 value)
{
const u32 offset = addr - (RAW_SPU_BASE_ADDR + RAW_SPU_OFFSET * index) - RAW_SPU_PROB_OFFSET;
spu_log.trace("RawSPU[%u]: Write32(0x%x, offset=0x%x, value=0x%x)", index, addr, offset, value);
const auto [old_stats, is_changed] = mmio_stats.fetch_op([&](raw_spu_log_stats_t& old)
{
if (old.mmio_offset == offset && old.mmio_value == value)
{
const u64 current = get_system_time();
if (current - old.mmio_time >= 500)
{
old.mmio_time = current;
return true;
}
return false;
}
old.mmio_offset = offset;
old.mmio_value = value;
return true;
});
if (is_changed)
{
spu_log.trace("RawSPU[%u]: write_reg(0x%x, offset=0x%x, value=0x%x)", index, addr, offset, value);
}
switch (offset)
{
@@ -365,7 +448,7 @@ bool spu_thread::write_reg(const u32 addr, const u32 value)
}
}
spu_log.error("RawSPU[%u]: Write32(0x%x, value=0x%x): unknown/illegal offset (0x%x)", index, addr, value, offset);
spu_log.error("RawSPU[%u]: write_reg(0x%x, value=0x%x): unknown/illegal offset (0x%x)", index, addr, value, offset);
return false;
}
+29 -30
View File
@@ -567,6 +567,10 @@ class spu_llvm_recompiler : public spu_recompiler_base, public cpu_translator
return _ptr(m_thread, ::offset32(offset_args...));
}
#if defined(__GNUC__) && !defined(__clang__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wnonnull"
#endif
template <typename T>
T* spu_mem_attr(T* inst)
{
@@ -600,6 +604,9 @@ class spu_llvm_recompiler : public spu_recompiler_base, public cpu_translator
return inst;
}
#if defined(__GNUC__) && !defined(__clang__)
#pragma GCC diagnostic pop
#endif
// Return default register type
llvm::Type* get_reg_type(u32 index)
@@ -1733,11 +1740,7 @@ public:
// Create LLVM module
std::unique_ptr<Module> _module = std::make_unique<Module>(m_hash + ".obj", m_context);
#if LLVM_VERSION_MAJOR >= 21 && (LLVM_VERSION_MINOR >= 1 || LLVM_VERSION_MAJOR >= 22)
_module->setTargetTriple(Triple(jit_compiler::triple2()));
#else
_module->setTargetTriple(jit_compiler::triple2());
#endif
_module->setDataLayout(m_jit.get_engine().getTargetMachine()->createDataLayout());
m_module = _module.get();
@@ -3663,11 +3666,7 @@ public:
{
if (b2 != bqbi)
{
#if LLVM_VERSION_MAJOR >= 21 || (LLVM_VERSION_MAJOR == 20 && LLVM_VERSION_MINOR >= 1)
auto ins = &*b2->block->getFirstNonPHIIt();
#else
auto ins = b2->block->getFirstNonPHI();
#endif
if (b2->bb->preds.size() == 1)
{
@@ -4039,11 +4038,7 @@ public:
// Create LLVM module
std::unique_ptr<Module> _module = std::make_unique<Module>("spu_interpreter.obj", m_context);
#if LLVM_VERSION_MAJOR >= 21 && (LLVM_VERSION_MINOR >= 1 || LLVM_VERSION_MAJOR >= 22)
_module->setTargetTriple(Triple(jit_compiler::triple2()));
#else
_module->setTargetTriple(jit_compiler::triple2());
#endif
_module->setDataLayout(m_jit.get_engine().getTargetMachine()->createDataLayout());
m_module = _module.get();
@@ -7805,6 +7800,8 @@ public:
// Avoid pessimation when full clamping isn't needed
if (m_use_avx512 && !(known.isKnownNeverNaN() && (known.isKnownNeverPosInfinity() || known.isKnownNeverNegInfinity())))
{
// Fails to clamp SNaN
// Normally doesn't cause issues as SNaN frequently gets quieted beforehand
return eval(vrangeps(v, fsplat<f32[4]>(std::bit_cast<f32, u32>(0x7f7fffff)), 0x2, 0xff));
}
@@ -8847,13 +8844,17 @@ public:
const auto a = bitcast<u32[4]>(value<f32[4]>(ci->getOperand(0)));
const auto b = bitcast<u32[4]>(value<f32[4]>(ci->getOperand(1)));
// The AVX512 path in `clamp_smax` doesn't properly clamp SNaN
// Normally this is fine, but it can cause issues if we generate them from normalized inputs
const auto base = (b & 0x007ffc00u) << 9; // Base fraction
const auto ymul = (b & 0x3ff) * (a & 0x7ffff); // Step fraction * Y fraction (fixed point at 2^-32)
const auto comparison = (ymul > base); // Should exponent be adjusted?
const auto bnew = (base - ymul) >> (zext<u32[4]>(comparison) ^ 9); // Shift one less bit if exponent is adjusted
const auto base_result = (b & 0xff800000u) | (bnew & ~0xff800000u); // Inject old sign and exponent
const auto adjustment = bitcast<u32[4]>(sext<s32[4]>(comparison)) & (1 << 23); // exponent adjustement for negative bnew
return clamp_smax(eval(bitcast<f32[4]>(base_result - adjustment)));
const auto comparison = bitcast<u32[4]>(sext<s32[4]>(ymul > base)); // Should exponent be adjusted?
const auto bnew = (base - ymul) >> (comparison + 9); // Shift one less bit if exponent is adjusted
const auto adjustment = comparison & (1 << 23); // exponent adjustement for negative bnew
const auto adjust_expo = (b & 0xff800000u) - adjustment;
const auto result_expo = clamp_smax(eval(bitcast<f32[4]>(adjust_expo)));
return bitcast<f32[4]>(bitcast<u32[4]>(result_expo) | (bnew & 0x007fffffu)); // clamped overwrites mantissa
});
const auto [a, b] = get_vrs<f32[4]>(op.ra, op.rb);
@@ -8892,15 +8893,13 @@ public:
}
}
b = eval(b | fix_exponent | a_sign);
const auto base = (b & 0x007ffc00u) << 9; // Base fraction
const auto ymul = (b & 0x3ff) * (a & 0x7ffff); // Step fraction * Y fraction (fixed point at 2^-32)
const auto comparison = (ymul > base); // Should exponent be adjusted?
const auto bnew = (base - ymul) >> (zext<u32[4]>(comparison) ^ 9); // Shift one less bit if exponent is adjusted
const auto base_result = (b & 0xff800000u) | (bnew & ~0xff800000u); // Inject old sign and exponent
const auto adjustment = bitcast<u32[4]>(sext<s32[4]>(comparison)) & (1 << 23); // exponent adjustement for negative bnew
return clamp_smax(eval(bitcast<f32[4]>(base_result - adjustment)));
const auto comparison = bitcast<u32[4]>(sext<s32[4]>(ymul > base)); // Should exponent be adjusted?
const auto bnew = (base - ymul) >> (comparison + 9); // Shift one less bit if exponent is adjusted
const auto adjust_expo = (fix_exponent | a_sign) - (comparison & (1 << 23)); // exponent adjustement for negative bnew
const auto result_expo = clamp_smax(eval(bitcast<f32[4]>(adjust_expo)));
return bitcast<f32[4]>(bitcast<u32[4]>(result_expo) | (bnew & 0x007fffffu)); // clamped overwrites mantissa
});
register_intrinsic("spu_rsqrte", [&](llvm::CallInst* ci)
@@ -8923,15 +8922,15 @@ public:
final_fraction = eval(insert(final_fraction, i, r_fraction));
}
const auto b = eval(final_fraction | final_exponent);
const auto b = final_fraction;
const auto base = (b & 0x007ffc00u) << 9; // Base fraction
const auto ymul = (b & 0x3ff) * (a & 0x7ffff); // Step fraction * Y fraction (fixed point at 2^-32)
const auto comparison = (ymul > base); // Should exponent be adjusted?
const auto bnew = (base - ymul) >> (zext<u32[4]>(comparison) ^ 9); // Shift one less bit if exponent is adjusted
const auto base_result = (b & 0xff800000u) | (bnew & ~0xff800000u); // Inject old sign and exponent
const auto adjustment = bitcast<u32[4]>(sext<s32[4]>(comparison)) & (1 << 23); // exponent adjustement for negative bnew
return clamp_smax(eval(bitcast<f32[4]>(base_result - adjustment)));
const auto comparison = bitcast<u32[4]>(sext<s32[4]>(ymul > base)); // Should exponent be adjusted?
const auto bnew = (base - ymul) >> (comparison + 9); // Shift one less bit if exponent is adjusted
const auto adjust_expo = final_exponent - (comparison & (1 << 23)); // exponent adjustement for negative bnew
const auto result_expo = clamp_positive_smax(eval(bitcast<f32[4]>(adjust_expo)));
return bitcast<f32[4]>(bitcast<u32[4]>(result_expo) | (bnew & 0x007fffffu)); // clamped overwrites mantissa
});
break;
}
+9
View File
@@ -826,6 +826,15 @@ public:
u64 mfc_dump_idx = 0;
static constexpr u32 max_mfc_dump_idx = 4096;
struct alignas(16) raw_spu_log_stats_t
{
u32 mmio_offset = umax;
u32 mmio_value = umax;
u64 mmio_time = umax;
};
atomic_t<raw_spu_log_stats_t> mmio_stats{};
bool in_cpu_work = false;
bool allow_interrupts_in_cpu_work = false;
u8 cpu_work_iteration_count = 0;
+7 -5
View File
@@ -191,13 +191,15 @@ error_code sys_rsx_memory_allocate(cpu_thread& cpu, vm::ptr<u32> mem_handle, vm:
cpu.state += cpu_flag::wait;
if (vm::falloc(rsx::constants::local_mem_base, size, vm::video))
const u32 mem_size = static_cast<u32>(size);
if (vm::falloc(rsx::constants::local_mem_base, mem_size, vm::video))
{
rsx::get_current_renderer()->local_mem_size = size;
rsx::get_current_renderer()->local_mem_size = mem_size;
if (u32 addr = rsx::get_current_renderer()->driver_info)
{
vm::_ptr<RsxDriverInfo>(addr)->memory_size = size;
vm::_ptr<RsxDriverInfo>(addr)->memory_size = mem_size;
}
*mem_addr = rsx::constants::local_mem_base;
@@ -435,7 +437,7 @@ error_code sys_rsx_context_iomap(cpu_thread& cpu, u32 context_id, u64 io, u64 ea
}
// Wait until we have no active RSX locks and reserve iomap for use. Must do so before acquiring vm lock to avoid deadlocks
rsx::reservation_lock<true> rsx_lock(ea, size);
rsx::reservation_lock<true> rsx_lock(::narrow<u32>(ea), static_cast<u32>(size));
vm::writer_lock rlock;
@@ -446,7 +448,7 @@ error_code sys_rsx_context_iomap(cpu_thread& cpu, u32 context_id, u64 io, u64 ea
return CELL_EINVAL;
}
if ((addr == ea || !(addr % 0x1000'0000)) && idm::check_unlocked<sys_vm_t>(sys_vm_t::find_id(addr)))
if ((addr == ea || !(addr % 0x1000'0000)) && idm::check_unlocked<sys_vm_t>(sys_vm_t::find_id(::narrow<u32>(addr))))
{
// Virtual memory is disallowed
return CELL_EINVAL;
+6 -5
View File
@@ -976,10 +976,11 @@ void PadDevice::reset_orientation()
// Initialize Fusion
ahrs = std::make_shared<FusionAhrs>();
FusionAhrsInitialise(ahrs.get());
ahrs->settings.convention = FusionConvention::FusionConventionEnu;
ahrs->settings.gain = 0.0f; // If gain is set, the algorithm tries to adjust the orientation over time.
FusionAhrsSetSettings(ahrs.get(), &ahrs->settings);
FusionAhrsRestart(ahrs.get());
FusionAhrsSettings settings = fusionAhrsDefaultSettings;
settings.convention = FusionConvention::FusionConventionEnu;
settings.gain = 0.0f; // If gain is set, the algorithm tries to adjust the orientation over time.
FusionAhrsSetSettings(ahrs.get(), &settings);
}
void PadDevice::update_orientation(ps_move_data& move_data)
@@ -996,7 +997,7 @@ void PadDevice::update_orientation(ps_move_data& move_data)
// The ps move handler's axis may differ from the Fusion axis, so we have to map them correctly.
// Don't ask how the axis work. It's basically been trial and error.
ensure(ahrs->settings.convention == FusionConvention::FusionConventionEnu); // East-North-Up
ensure(ahrs->convention == FusionConvention::FusionConventionEnu); // East-North-Up
const FusionVector accelerometer{
.axis {
+4 -2
View File
@@ -16,7 +16,8 @@ struct cfg_recording final : cfg::node
cfg::uint<640, 7680> width{this, "Width", 1280};
cfg::uint<360, 4320> height{this, "Height", 720};
cfg::uint<0, 192> pixel_format{this, "AVPixelFormat", 0}; // AVPixelFormat::AV_PIX_FMT_YUV420P
cfg::uint<0, 0xFFFF> video_codec{this, "AVCodecID", 12}; // AVCodecID::AV_CODEC_ID_MPEG4
cfg::string codec_name{this, "Video Codec", ""};
cfg::uint<0, 0xFFFF> codec_id{this, "AVCodecID", 12}; // AVCodecID::AV_CODEC_ID_MPEG4
cfg::uint<1'000'000, 60'000'000> video_bps{this, "Video Bitrate", 4'000'000};
cfg::uint<0, 3> max_b_frames{this, "Max B-Frames", 2};
cfg::uint<1, 120> gop_size{this, "Group of Pictures Size", 30};
@@ -27,7 +28,8 @@ struct cfg_recording final : cfg::node
{
node_audio(cfg::node* _this) : cfg::node(_this, "Audio") {}
cfg::uint<0x10000, 0x17000> audio_codec{this, "AVCodecID", 86018}; // AVCodecID::AV_CODEC_ID_AAC
cfg::string codec_name{this, "Audio Codec", ""};
cfg::uint<0x10000, 0x17000> codec_id{this, "AVCodecID", 86018}; // AVCodecID::AV_CODEC_ID_AAC
cfg::uint<64'000, 320'000> audio_bps{this, "Audio Bitrate", 192'000};
} audio{ this };
+16
View File
@@ -107,6 +107,22 @@ void usb_device_passthrough::patch_descriptors()
device._device.idVendor = 0x12BA; // SCEA
device._device.idProduct = 0x2530; // Harmonix Real Guitar
break;
case 0x3138: // Harmonix MPA in Drums Mode - Wii
device._device.idVendor = 0x12BA; // SCEA
device._device.idProduct = 0x0218; // Harmonix MPA in Drums Mode
break;
case 0x3338: // Harmonix MPA in Keyboard Mode - Wii
device._device.idVendor = 0x12BA; // SCEA
device._device.idProduct = 0x2338; // Harmonix MPA in Keyboard Mode
break;
case 0x3438: // Harmonix MPA in Button Guitar Mode - Wii
device._device.idVendor = 0x12BA; // SCEA
device._device.idProduct = 0x2438; // Harmonix MPA in Button Guitar Mode
break;
case 0x3538: // Harmonix MPA in Real Guitar Mode - Wii
device._device.idVendor = 0x12BA; // SCEA
device._device.idProduct = 0x2538; // Harmonix MPA in Real Guitar Mode
break;
default:
break;
}
+29
View File
@@ -1045,6 +1045,8 @@ namespace np
void np_handler::set_message_selected(SceNpBasicAttachmentDataId id, u64 msg_id)
{
std::lock_guard lock(m_mutex_selected_messages);
switch (id)
{
case SCE_NP_BASIC_SELECTED_INVITATION_DATA:
@@ -1060,6 +1062,8 @@ namespace np
std::optional<shared_ptr<std::pair<std::string, message_data>>> np_handler::get_message_selected(SceNpBasicAttachmentDataId id)
{
std::lock_guard lock(m_mutex_selected_messages);
switch (id)
{
case SCE_NP_BASIC_SELECTED_INVITATION_DATA:
@@ -1079,6 +1083,8 @@ namespace np
void np_handler::clear_message_selected(SceNpBasicAttachmentDataId id)
{
std::lock_guard lock(m_mutex_selected_messages);
switch (id)
{
case SCE_NP_BASIC_SELECTED_INVITATION_DATA:
@@ -1100,6 +1106,29 @@ namespace np
get_rpcn()->send_message(msg_data, npids);
}
bool np_handler::select_invitation(u64 msg_id)
{
const auto message = get_message(msg_id);
if (!message || message.value()->second.mainType != SCE_NP_BASIC_MESSAGE_MAIN_TYPE_INVITE)
{
rpcn_log.error("Cannot select invalid invitation: msg_id=%d", msg_id);
return false;
}
set_message_selected(SCE_NP_BASIC_SELECTED_INVITATION_DATA, msg_id);
if (sysutil_send_system_cmd(CELL_SYSUTIL_NP_INVITATION_SELECTED, 0) <= 0)
{
clear_message_selected(SCE_NP_BASIC_SELECTED_INVITATION_DATA);
rpcn_log.error("Failed to notify the game about selected invitation: msg_id=%d", msg_id);
return false;
}
get_rpcn()->mark_message_used(msg_id);
return true;
}
void np_handler::operator()()
{
if (g_cfg.net.psn_status != np_psn_status::psn_rpcn)
+2
View File
@@ -155,6 +155,7 @@ namespace np
std::optional<shared_ptr<std::pair<std::string, message_data>>> get_message_selected(SceNpBasicAttachmentDataId id);
void clear_message_selected(SceNpBasicAttachmentDataId id);
void send_message(const message_data& msg_data, const std::set<std::string>& npids);
bool select_invitation(u64 msg_id);
// Those should probably be under match2 ctx
vm::ptr<SceNpMatching2RoomEventCallback> room_event_cb{}; // Room events
@@ -440,6 +441,7 @@ namespace np
gui_cache_manager gui_cache;
// Messages related
shared_mutex m_mutex_selected_messages;
std::optional<u64> selected_invite_id{};
std::optional<u64> selected_message_id{};
+1 -1
View File
@@ -470,7 +470,7 @@ namespace
for (; (i + step) <= count; i += step, vec_ptr++)
{
_mm_stream_si128(vec_ptr, values);
_mm_add_epi16(values, vec_step);
values = _mm_add_epi16(values, vec_step);
}
#endif
for (; i < count; ++i)
+108 -27
View File
@@ -749,27 +749,15 @@ std::tuple<u16, u16, u8> get_height_depth_layer(const RsxTextureType &tex)
}
fmt::throw_exception("Unsupported texture dimension");
}
}
template<typename RsxTextureType>
std::vector<rsx::subresource_layout> get_subresources_layout_impl(const RsxTextureType &texture)
// Common implementation for packing texture definitions into subresource layout arrays
std::vector<rsx::subresource_layout> get_subresources_layout_impl(
const std::byte* pixels,
u32 format,
u16 w, u16 h, u16 depth,
u8 layer, u16 mipmap_count, u32 pitch,
bool is_swizzled, bool has_border)
{
u16 w = texture.width();
u16 h;
u16 depth;
u8 layer;
std::tie(h, depth, layer) = get_height_depth_layer(texture);
const auto format = texture.format() & ~(CELL_GCM_TEXTURE_LN | CELL_GCM_TEXTURE_UN);
auto pitch = texture.pitch();
const u32 texaddr = rsx::get_address(texture.offset(), texture.location());
auto pixels = vm::_ptr<const std::byte>(texaddr);
const bool is_swizzled = !(texture.format() & CELL_GCM_TEXTURE_LN);
const bool has_border = !texture.border_type();
if (!is_swizzled)
{
if (const auto packed_pitch = rsx::get_format_packed_pitch(format, w, has_border, false); pitch < packed_pitch) [[unlikely]]
@@ -790,10 +778,10 @@ std::vector<rsx::subresource_layout> get_subresources_layout_impl(const RsxTextu
switch (format)
{
case CELL_GCM_TEXTURE_B8:
return get_subresources_layout_impl<1, u8>(pixels, w, h, depth, layer, texture.get_exact_mipmap_count(), pitch, !is_swizzled, has_border);
return get_subresources_layout_impl<1, u8>(pixels, w, h, depth, layer, mipmap_count, pitch, !is_swizzled, has_border);
case CELL_GCM_TEXTURE_COMPRESSED_B8R8_G8R8:
case CELL_GCM_TEXTURE_COMPRESSED_R8B8_R8G8:
return get_subresources_layout_impl<2, u32>(pixels, w, h, depth, layer, texture.get_exact_mipmap_count(), pitch, !is_swizzled, has_border);
return get_subresources_layout_impl<2, u32>(pixels, w, h, depth, layer, mipmap_count, pitch, !is_swizzled, has_border);
case CELL_GCM_TEXTURE_COMPRESSED_HILO8:
case CELL_GCM_TEXTURE_COMPRESSED_HILO_S8:
case CELL_GCM_TEXTURE_DEPTH16:
@@ -806,7 +794,7 @@ std::vector<rsx::subresource_layout> get_subresources_layout_impl(const RsxTextu
case CELL_GCM_TEXTURE_R6G5B5:
case CELL_GCM_TEXTURE_G8B8:
case CELL_GCM_TEXTURE_X16:
return get_subresources_layout_impl<1, u16>(pixels, w, h, depth, layer, texture.get_exact_mipmap_count(), pitch, !is_swizzled, has_border);
return get_subresources_layout_impl<1, u16>(pixels, w, h, depth, layer, mipmap_count, pitch, !is_swizzled, has_border);
case CELL_GCM_TEXTURE_DEPTH24_D8: // Untested
case CELL_GCM_TEXTURE_DEPTH24_D8_FLOAT: // Untested
case CELL_GCM_TEXTURE_D8R8G8B8:
@@ -814,19 +802,36 @@ std::vector<rsx::subresource_layout> get_subresources_layout_impl(const RsxTextu
case CELL_GCM_TEXTURE_Y16_X16:
case CELL_GCM_TEXTURE_Y16_X16_FLOAT:
case CELL_GCM_TEXTURE_X32_FLOAT:
return get_subresources_layout_impl<1, u32>(pixels, w, h, depth, layer, texture.get_exact_mipmap_count(), pitch, !is_swizzled, has_border);
return get_subresources_layout_impl<1, u32>(pixels, w, h, depth, layer, mipmap_count, pitch, !is_swizzled, has_border);
case CELL_GCM_TEXTURE_W16_Z16_Y16_X16_FLOAT:
return get_subresources_layout_impl<1, u64>(pixels, w, h, depth, layer, texture.get_exact_mipmap_count(), pitch, !is_swizzled, has_border);
return get_subresources_layout_impl<1, u64>(pixels, w, h, depth, layer, mipmap_count, pitch, !is_swizzled, has_border);
case CELL_GCM_TEXTURE_W32_Z32_Y32_X32_FLOAT:
return get_subresources_layout_impl<1, u128>(pixels, w, h, depth, layer, texture.get_exact_mipmap_count(), pitch, !is_swizzled, has_border);
return get_subresources_layout_impl<1, u128>(pixels, w, h, depth, layer, mipmap_count, pitch, !is_swizzled, has_border);
case CELL_GCM_TEXTURE_COMPRESSED_DXT1:
return get_subresources_layout_impl<4, u64>(pixels, w, h, depth, layer, texture.get_exact_mipmap_count(), pitch, !is_swizzled, false);
return get_subresources_layout_impl<4, u64>(pixels, w, h, depth, layer, mipmap_count, pitch, !is_swizzled, false);
case CELL_GCM_TEXTURE_COMPRESSED_DXT23:
case CELL_GCM_TEXTURE_COMPRESSED_DXT45:
return get_subresources_layout_impl<4, u128>(pixels, w, h, depth, layer, texture.get_exact_mipmap_count(), pitch, !is_swizzled, false);
return get_subresources_layout_impl<4, u128>(pixels, w, h, depth, layer, mipmap_count, pitch, !is_swizzled, false);
}
fmt::throw_exception("Wrong format 0x%x", format);
}
}
template<typename RsxTextureType>
std::vector<rsx::subresource_layout> get_subresources_layout_impl(const RsxTextureType &texture)
{
const auto [h, depth, layer] = get_height_depth_layer(texture);
const u32 texaddr = rsx::get_address(texture.offset(), texture.location());
return get_subresources_layout_impl(
vm::_ptr<const std::byte>(texaddr),
texture.format() & ~(CELL_GCM_TEXTURE_LN | CELL_GCM_TEXTURE_UN),
texture.width(), h, depth, layer,
texture.get_exact_mipmap_count(),
texture.pitch(),
!(texture.format() & CELL_GCM_TEXTURE_LN),
!texture.border_type());
}
namespace rsx
{
@@ -866,6 +871,40 @@ namespace rsx
return get_subresources_layout_impl(texture);
}
std::vector<rsx::subresource_layout> get_subresources_layout(const rsx::image_section_attributes_t& attrs, rsx::texture_dimension_extended type)
{
u16 height = attrs.height;
u16 depth = 1;
u8 layer = 1;
switch (type)
{
case rsx::texture_dimension_extended::texture_dimension_1d:
height = 1;
break;
case rsx::texture_dimension_extended::texture_dimension_2d:
break;
case rsx::texture_dimension_extended::texture_dimension_cubemap:
layer = 6;
break;
case rsx::texture_dimension_extended::texture_dimension_3d:
depth = attrs.depth;
break;
default:
fmt::throw_exception("Unsupported texture dimension");
}
// NOTE: This variant goes through the hv pointer.
return get_subresources_layout_impl(
vm::get_super_ptr<const std::byte>(attrs.address),
attrs.gcm_format,
attrs.width, height, depth, layer,
attrs.mipmaps,
attrs.pitch,
attrs.swizzled,
false);
}
texture_memory_info upload_texture_subresource(rsx::io_buffer& dst_buffer, const rsx::subresource_layout& src_layout, int format, bool is_swizzled, texture_uploader_capabilities& caps)
{
u16 w = src_layout.width_in_block;
@@ -1703,6 +1742,48 @@ namespace rsx
}
}
rsx::surface_color_format get_compatible_surface_color_format(u32 gcm_format)
{
switch (gcm_format)
{
case CELL_GCM_TEXTURE_R5G6B5:
return rsx::surface_color_format::r5g6b5;
case CELL_GCM_TEXTURE_A8R8G8B8:
return rsx::surface_color_format::a8b8g8r8;
case CELL_GCM_TEXTURE_W16_Z16_Y16_X16_FLOAT:
return rsx::surface_color_format::w16z16y16x16;
case CELL_GCM_TEXTURE_W32_Z32_Y32_X32_FLOAT:
return rsx::surface_color_format::w32z32y32x32;
case CELL_GCM_TEXTURE_A1R5G5B5:
return rsx::surface_color_format::x1r5g5b5_o1r5g5b5;
case CELL_GCM_TEXTURE_B8:
return rsx::surface_color_format::b8;
case CELL_GCM_TEXTURE_G8B8:
return rsx::surface_color_format::g8b8;
case CELL_GCM_TEXTURE_X32_FLOAT:
return rsx::surface_color_format::x32;
default:
fmt::throw_exception("Unhandled surface format 0x%x", gcm_format);
}
}
rsx::surface_depth_format2 get_compatible_surface_depth_format(u32 gcm_format)
{
switch (gcm_format)
{
case RSX_FORMAT_CLASS_DEPTH16_UNORM:
return rsx::surface_depth_format2::z16_uint;
case RSX_FORMAT_CLASS_DEPTH24_UNORM_X8_PACK32:
return rsx::surface_depth_format2::z24s8_uint;
case RSX_FORMAT_CLASS_DEPTH16_FLOAT:
return rsx::surface_depth_format2::z16_float;
case RSX_FORMAT_CLASS_DEPTH24_FLOAT_X8_PACK32:
return rsx::surface_depth_format2::z24s8_float;
default:
fmt::throw_exception("Invalid depth format 0x%x", gcm_format);
}
}
u32 get_max_depth_value(rsx::surface_depth_format2 format)
{
return get_format_block_size_in_bytes(format) == 2 ? 0xFFFF : 0xFFFFFF;
+19
View File
@@ -284,6 +284,21 @@ namespace rsx
usz alignment;
};
struct image_section_attributes_t
{
u32 address;
u32 gcm_format;
u32 pitch;
u16 width;
u16 height;
u16 depth;
u16 mipmaps;
u16 slice_h;
u8 bpp;
bool swizzled;
bool edge_clamped;
};
/**
* Get size to store texture in a linear fashion.
* Storage is assumed to use a rowPitchAlignment boundary for every row of texture.
@@ -298,6 +313,7 @@ namespace rsx
*/
std::vector<subresource_layout> get_subresources_layout(const rsx::fragment_texture &texture);
std::vector<subresource_layout> get_subresources_layout(const rsx::vertex_texture &texture);
std::vector<subresource_layout> get_subresources_layout(const image_section_attributes_t& attrs, texture_dimension_extended type);
texture_memory_info upload_texture_subresource(rsx::io_buffer& dst_buffer, const subresource_layout &src_layout, int format, bool is_swizzled, texture_uploader_capabilities& caps);
@@ -350,6 +366,9 @@ namespace rsx
std::pair<u32, bool> get_compatible_gcm_format(rsx::surface_color_format format);
std::pair<u32, bool> get_compatible_gcm_format(rsx::surface_depth_format2 format);
rsx::surface_color_format get_compatible_surface_color_format(u32 gcm_format);
rsx::surface_depth_format2 get_compatible_surface_depth_format(u32 gcm_format);
format_class classify_format(rsx::surface_depth_format2 format);
format_class classify_format(u32 gcm_format);
+27 -24
View File
@@ -152,6 +152,7 @@ namespace rsx
utils::address_range32 cache_range;
bool do_not_cache = false;
bool force_bg_load = false;
deferred_subresource() = default;
@@ -481,8 +482,7 @@ namespace rsx
/**
* Virtual Methods
*/
virtual image_view_type create_temporary_subresource_view(commandbuffer_type&, image_resource_type* src, u32 gcm_format, u16 x, u16 y, u16 w, u16 h, const texture_channel_remap_t& remap_vector) = 0;
virtual image_view_type create_temporary_subresource_view(commandbuffer_type&, image_storage_type* src, u32 gcm_format, u16 x, u16 y, u16 w, u16 h, const texture_channel_remap_t& remap_vector) = 0;
virtual image_view_type create_temporary_subresource_view(commandbuffer_type&, const deferred_subresource& desc) = 0;
virtual void release_temporary_subresource(image_view_type rsc) = 0;
virtual section_storage_type* create_new_texture(commandbuffer_type&, const address_range32 &rsx_range, u16 width, u16 height, u16 depth, u16 mipmaps, u32 pitch, u32 gcm_format,
rsx::texture_upload_context context, rsx::texture_dimension_extended type, bool swizzled, component_order swizzle_flags, rsx::flags32_t flags) = 0;
@@ -491,10 +491,10 @@ namespace rsx
virtual section_storage_type* create_nul_section(commandbuffer_type&, const address_range32 &rsx_range, const image_section_attributes_t& attrs, const GCM_tile_reference& tile, bool memory_load) = 0;
virtual void set_component_order(section_storage_type& section, u32 gcm_format, component_order expected) = 0;
virtual void insert_texture_barrier(commandbuffer_type&, image_storage_type* tex, bool strong_ordering = true) = 0;
virtual image_view_type generate_cubemap_from_images(commandbuffer_type&, u32 gcm_format, u16 size, const rsx::simple_array<copy_region_descriptor>& sources, const texture_channel_remap_t& remap_vector) = 0;
virtual image_view_type generate_3d_from_2d_images(commandbuffer_type&, u32 gcm_format, u16 width, u16 height, u16 depth, const rsx::simple_array<copy_region_descriptor>& sources, const texture_channel_remap_t& remap_vector) = 0;
virtual image_view_type generate_atlas_from_images(commandbuffer_type&, u32 gcm_format, u16 width, u16 height, const rsx::simple_array<copy_region_descriptor>& sections_to_copy, const texture_channel_remap_t& remap_vector) = 0;
virtual image_view_type generate_2d_mipmaps_from_images(commandbuffer_type&, u32 gcm_format, u16 width, u16 height, const rsx::simple_array<copy_region_descriptor>& sections_to_copy, const texture_channel_remap_t& remap_vector) = 0;
virtual image_view_type generate_cubemap_from_images(commandbuffer_type&, const deferred_subresource& desc) = 0;
virtual image_view_type generate_3d_from_2d_images(commandbuffer_type&, const deferred_subresource& desc) = 0;
virtual image_view_type generate_atlas_from_images(commandbuffer_type&, const deferred_subresource& desc) = 0;
virtual image_view_type generate_2d_mipmaps_from_images(commandbuffer_type&, const deferred_subresource& desc) = 0;
virtual void update_image_contents(commandbuffer_type&, image_view_type dst, image_resource_type src, u16 width, u16 height) = 0;
virtual bool render_target_format_is_compatible(image_storage_type* tex, u32 gcm_format) = 0;
virtual void prepare_for_dma_transfers(commandbuffer_type&) = 0;
@@ -1709,7 +1709,8 @@ namespace rsx
if (found_desc.external_handle != desc.external_handle ||
found_desc.op != desc.op ||
found_desc.x != desc.x || found_desc.y != desc.y ||
found_desc.width != desc.width || found_desc.height != desc.height)
found_desc.width != desc.width || found_desc.height != desc.height ||
found_desc.gcm_format != desc.gcm_format)
continue;
if (desc.op == deferred_request_command::copy_image_dynamic)
@@ -1726,7 +1727,7 @@ namespace rsx
{
case deferred_request_command::cubemap_gather:
{
result = generate_cubemap_from_images(cmd, desc.gcm_format, desc.width, desc.sections_to_copy, desc.remap);
result = generate_cubemap_from_images(cmd, desc);
break;
}
case deferred_request_command::cubemap_unwrap:
@@ -1761,12 +1762,15 @@ namespace rsx
}
}
result = generate_cubemap_from_images(cmd, desc.gcm_format, desc.width, sections, desc.remap);
auto unwrap_desc = desc;
unwrap_desc.sections_to_copy = std::move(sections);
result = generate_cubemap_from_images(cmd, unwrap_desc);
break;
}
case deferred_request_command::_3d_gather:
{
result = generate_3d_from_2d_images(cmd, desc.gcm_format, desc.width, desc.height, desc.depth, desc.sections_to_copy, desc.remap);
result = generate_3d_from_2d_images(cmd, desc);
break;
}
case deferred_request_command::_3d_unwrap:
@@ -1792,24 +1796,27 @@ namespace rsx
};
}
result = generate_3d_from_2d_images(cmd, desc.gcm_format, desc.width, desc.height, desc.depth, sections, desc.remap);
auto unwrap_desc = desc;
unwrap_desc.sections_to_copy = std::move(sections);
result = generate_3d_from_2d_images(cmd, unwrap_desc);
break;
}
case deferred_request_command::atlas_gather:
case deferred_request_command::blit_image_static:
{
result = generate_atlas_from_images(cmd, desc.gcm_format, desc.width, desc.height, desc.sections_to_copy, desc.remap);
result = generate_atlas_from_images(cmd, desc);
break;
}
case deferred_request_command::copy_image_static:
case deferred_request_command::copy_image_dynamic:
{
result = create_temporary_subresource_view(cmd, &desc.external_handle, desc.gcm_format, desc.x, desc.y, desc.width, desc.height, desc.remap);
result = create_temporary_subresource_view(cmd, desc);
break;
}
case deferred_request_command::mipmap_gather:
{
result = generate_2d_mipmaps_from_images(cmd, desc.gcm_format, desc.width, desc.height, desc.sections_to_copy, desc.remap);
result = generate_2d_mipmaps_from_images(cmd, desc);
break;
}
default:
@@ -2111,19 +2118,15 @@ namespace rsx
return {};
}
bool result_is_valid;
if (_pool == 0 && !g_cfg.video.write_color_buffers && !g_cfg.video.write_depth_buffer)
bool result_is_valid = result.atlas_covers_target_area(section_count == 1 ? 99 : 90);
if (_pool == 0 && !result_is_valid && !g_cfg.video.write_color_buffers && !g_cfg.video.write_depth_buffer)
{
// HACK: Avoid WCB requirement for some games with wrongly declared sampler dimensions.
// TODO: Some games may render a small region (e.g 1024x256x2) and sample a huge texture (e.g 1024x1024).
// Avoid WCB requirement for some games with wrongly declared sampler dimensions.
// Some games may render a small region (e.g 1024x256x2) and sample a huge texture (e.g 1024x1024).
// Seen in APF2k8 - this causes missing bits to be reuploaded from CPU which can cause WCB requirement.
// Properly fix this by introducing partial data upload into the surface cache in such cases and making RCB/RDB
// enabled by default. Blit engine already handles this correctly.
// We work around the issue by forcing a background data load on the subresource to fill the missing data hole.
result_is_valid = true;
}
else
{
result_is_valid = result.atlas_covers_target_area(section_count == 1 ? 99 : 90);
result.external_subresource_desc.force_bg_load = true;
}
if (!result_is_valid)
+9 -21
View File
@@ -46,21 +46,6 @@ namespace rsx
blit_image_static, // Variant of the copy command that does scaling instead of copying
};
struct image_section_attributes_t
{
u32 address;
u32 gcm_format;
u32 pitch;
u16 width;
u16 height;
u16 depth;
u16 mipmaps;
u16 slice_h;
u8 bpp;
bool swizzled;
bool edge_clamped;
};
struct blit_op_result
{
bool succeeded = false;
@@ -248,7 +233,7 @@ namespace rsx
}
template<typename commandbuffer_type, typename section_storage_type, typename copy_region_type, typename surface_store_list_type>
void gather_texture_slices(
bool gather_texture_slices(
commandbuffer_type& cmd,
rsx::simple_array<copy_region_type>& out,
const surface_store_list_type& fbos,
@@ -535,6 +520,8 @@ namespace rsx
rsx_log.warning("Could not gather textures into an atlas; using CPU fallback...");
}
}
return found_slices == count;
}
template<typename render_target_type>
@@ -761,12 +748,13 @@ namespace rsx
}
texptr->memory_barrier(cmd, rsx::surface_access::transfer_read);
auto format_class = rsx::classify_format(attr2.gcm_format);
if (extended_dimension == rsx::texture_dimension_extended::texture_dimension_3d)
{
return{ texptr->get_surface(rsx::surface_access::transfer_read), deferred_request_command::_3d_unwrap,
attr2, {},
texture_upload_context::framebuffer_storage, texptr->format_class(), scale,
texture_upload_context::framebuffer_storage, format_class, scale,
rsx::texture_dimension_extended::texture_dimension_3d, decoded_remap };
}
@@ -774,7 +762,7 @@ namespace rsx
return{ texptr->get_surface(rsx::surface_access::transfer_read), deferred_request_command::cubemap_unwrap,
attr2, {},
texture_upload_context::framebuffer_storage, texptr->format_class(), scale,
texture_upload_context::framebuffer_storage, format_class, scale,
rsx::texture_dimension_extended::texture_dimension_cubemap, decoded_remap };
}
@@ -858,7 +846,7 @@ namespace rsx
upload_context, format_class, scale,
rsx::texture_dimension_extended::texture_dimension_cubemap, decoded_remap };
gather_texture_slices(cmd, desc.external_subresource_desc.sections_to_copy, fbos, local, attr, 6, is_depth);
desc.external_subresource_desc.force_bg_load = !gather_texture_slices(cmd, desc.external_subresource_desc.sections_to_copy, fbos, local, attr, 6, is_depth);
return desc;
}
else if (extended_dimension == rsx::texture_dimension_extended::texture_dimension_3d && attr.depth > 1)
@@ -871,7 +859,7 @@ namespace rsx
upload_context, format_class, scale,
rsx::texture_dimension_extended::texture_dimension_3d, decoded_remap };
gather_texture_slices(cmd, desc.external_subresource_desc.sections_to_copy, fbos, local, attr, attr.depth, is_depth);
desc.external_subresource_desc.force_bg_load = !gather_texture_slices(cmd, desc.external_subresource_desc.sections_to_copy, fbos, local, attr, attr.depth, is_depth);
return desc;
}
@@ -890,7 +878,7 @@ namespace rsx
attr2, {}, upload_context, format_class,
scale, rsx::texture_dimension_extended::texture_dimension_2d, decoded_remap };
gather_texture_slices(cmd, result.external_subresource_desc.sections_to_copy, fbos, local, attr, 1, is_depth);
result.external_subresource_desc.force_bg_load = !gather_texture_slices(cmd, result.external_subresource_desc.sections_to_copy, fbos, local, attr, 1, is_depth);
result.simplify();
return result;
}
+83 -4
View File
@@ -494,6 +494,58 @@ void GLGSRender::bind_texture_env()
gl::command_context cmd{ gl_state };
const bool is_interpreter = m_shader_interpreter.is_interpreter(m_program);
auto decay_view_for_interpreter = [&](
const rsx::image_section_attributes_t& attr,
gl::texture_cache::sampled_image_descriptor* desc,
gl::texture_view* base,
const rsx::texture_channel_remap_t& decoded_remap,
bool is_msaa,
bool is_redirected) -> gl::texture_view*
{
if (!is_msaa && !is_redirected)
{
return base;
}
if (is_redirected && desc->image_type > rsx::texture_dimension_extended::texture_dimension_2d)
{
// Cannot handle redirect on 3D or cubemap with the interpreter.
const auto target = gl::get_target(desc->image_type);
return m_null_textures[target]->get_view(rsx::default_remap_vector);
}
using deferred_subresource_t = gl::texture_cache::deferred_subresource;
auto image = static_cast<gl::viewable_image*>(base->image());
if (is_msaa)
{
// MSAA resolve
auto rtt = gl::as_rtt(base->image());
rtt->memory_barrier(cmd, rsx::surface_access::transfer_read);
image = rtt->get_surface(rsx::surface_access::transfer_read);
}
if (is_redirected)
{
// Force bitcast
deferred_subresource_t flatten_op{};
flatten_op.address = desc->ref_address;
flatten_op.external_handle = image;
flatten_op.op = desc->is_cyclic_reference
? rsx::deferred_request_command::copy_image_dynamic
: rsx::deferred_request_command::copy_image_static;
flatten_op.width = flatten_op.external_handle->width();
flatten_op.height = flatten_op.external_handle->height();
flatten_op.depth = 1;
flatten_op.gcm_format = desc->format_ex.format();
flatten_op.remap = decoded_remap;
flatten_op.cache_range = utils::address_range32::start_length(desc->ref_address, attr.pitch * attr.height);
return m_gl_texture_cache.create_temporary_subresource(cmd, flatten_op);
}
return image->get_view(decoded_remap, base->aspect());
};
for (u32 textures_ref = current_fp_metadata.referenced_textures_mask, i = 0; textures_ref; textures_ref >>= 1, ++i)
{
if (!(textures_ref & 1))
@@ -505,8 +557,8 @@ void GLGSRender::bind_texture_env()
gl::texture_view* stencil_mirror = nullptr;
auto sampler_state = static_cast<gl::texture_cache::sampled_image_descriptor*>(fs_sampler_state[i].get());
if (rsx::method_registers.fragment_textures[i].enabled() &&
sampler_state->validate())
auto& tex = rsx::method_registers.fragment_textures[i];
if (tex.enabled() && sampler_state->validate())
{
if (primary_view = sampler_state->image_handle; !primary_view) [[unlikely]]
{
@@ -533,7 +585,26 @@ void GLGSRender::bind_texture_env()
if (is_interpreter) [[ unlikely ]]
{
m_shader_interpreter.bind_fragment_texture(i, primary_view->handle(), *sampler_state);
// Interpreter does not support MSAA or DEPTH->RGBA conversion a.k.a aspect redirection
auto view = primary_view;
if (primary_view->aspect() != gl::image_aspect::color || primary_view->image()->samples() != 1)
{
const auto mask = (1u << i);
const bool is_redirected = !!(current_fragment_program.texture_state.redirected_textures & mask);
const bool is_msaa = !!(current_fragment_program.texture_state.multisampled_textures & mask);
if (is_redirected || is_msaa)
{
view = decay_view_for_interpreter(
tex.attributes(),
sampler_state,
primary_view,
tex.decoded_remap(),
is_msaa,
is_redirected);
}
}
m_shader_interpreter.bind_fragment_texture(i, view->handle(), *sampler_state);
continue;
}
@@ -576,7 +647,15 @@ void GLGSRender::bind_texture_env()
if (is_interpreter)
{
m_shader_interpreter.flush_texture_bindings();
if (current_fp_metadata.referenced_textures_mask)
{
m_shader_interpreter.flush_fragment_texture_bindings();
}
if (current_vp_metadata.referenced_textures_mask)
{
m_shader_interpreter.flush_vertex_texture_bindings();
}
}
if (current_fragment_program.ctrl & RSX_SHADER_CONTROL_EMULATE_DEPTH_COMPARE)
+30 -7
View File
@@ -295,7 +295,7 @@ namespace gl
}
}
u32 len;
u32 len {};
ParamArray arr;
std::string shader_str;
RSXFragmentProgram frag;
@@ -408,6 +408,13 @@ namespace gl
" uvec4 fp_instructions[];\n"
"};\n\n";
const ::glsl::shader_properties properties
{
.domain = ::glsl::program_domain::glsl_fragment_program,
.require_lit_emulation = true,
};
::glsl::insert_glsl_legacy_function(builder, properties);
builder << program_common::interpreter::get_fragment_interpreter();
const std::string s = builder.str();
@@ -486,10 +493,15 @@ namespace gl
data->prog->uniforms[0] = GL_STREAM_BUFFER_START + 0;
data->prog->uniforms[1] = GL_STREAM_BUFFER_START + 1;
// Initialize texture bindings
if (compiler_options & COMPILER_OPT_ENABLE_TEXTURES)
{
// Initialize texture bindings
flush_texture_bindings(data->prog.get());
flush_fragment_texture_bindings(data->prog.get());
}
if (compiler_options & COMPILER_OPT_ENABLE_VTX_TEXTURES)
{
flush_vertex_texture_bindings(data->prog.get());
}
data->flags &= ~CACHED_PIPE_UNINITIALIZED;
@@ -518,7 +530,7 @@ namespace gl
}
}
void shader_interpreter::flush_texture_bindings(glsl::program* program)
void shader_interpreter::flush_fragment_texture_bindings(glsl::program* program)
{
using enum rsx::texture_dimension_extended;
@@ -529,9 +541,15 @@ namespace gl
}
const bool is_bound_interpreter = m_current_interpreter && m_current_interpreter->prog.get() == program;
const u32 dirty_mask = is_bound_interpreter
? 0xff
: m_texture_bindings.dirty;
u32 dirty_mask = m_texture_bindings.dirty;
if (is_bound_interpreter) [[ likely ]]
{
// Check if we even support textures
dirty_mask = (m_current_interpreter->build_compiler_options & COMPILER_OPT_ENABLE_TEXTURES)
? 0xff
: 0x0;
}
if (!dirty_mask)
{
@@ -557,4 +575,9 @@ namespace gl
m_texture_bindings.dirty = 0;
}
}
void shader_interpreter::flush_vertex_texture_bindings(glsl::program* program)
{
// TODO
}
}
+2 -1
View File
@@ -132,7 +132,8 @@ namespace gl
// Update texture bindings based on the incoming descriptor structures
void bind_fragment_texture(int i, handle64_t handle, const rsx::sampled_image_descriptor_base& descriptor);
void flush_texture_bindings(glsl::program* program = nullptr);
void flush_fragment_texture_bindings(glsl::program* program = nullptr);
void flush_vertex_texture_bindings(glsl::program* program = nullptr);
glsl::program* get(const interpreter::program_metadata& fp_metadata, u32 vp_ctrl, u32 fp_ctrl);
bool is_interpreter(const glsl::program* program) const;
+12
View File
@@ -664,6 +664,12 @@ namespace gl
for (const rsx::subresource_layout& layout : input_layouts)
{
if (layout.level >= dst->levels())
{
rsx_log.error("Invalid subresource definition for the output texture. Mip level does not exist.");
continue;
}
rsx::io_buffer io_buf = staging_buffer;
upload_texture_subresource(io_buf, layout, format, is_swizzled, caps);
@@ -741,6 +747,12 @@ namespace gl
for (const rsx::subresource_layout& layout : input_layouts)
{
if (layout.level >= dst->levels())
{
rsx_log.error("Invalid subresource definition for the output texture. Mip level does not exist.");
continue;
}
if (driver_caps.ARB_compute_shader_supported)
{
u64 row_pitch = rsx::align2<u64, u64>(layout.width_in_block * block_size_in_bytes, caps.alignment);
+6
View File
@@ -189,6 +189,12 @@ namespace gl
return dst->get_view(remap);
}
void texture_cache::initialize_subresource_from_memory(gl::command_context& cmd, gl::texture* dst, const deferred_subresource& desc, rsx::texture_dimension_extended type) const
{
const auto subresources_layout = rsx::get_subresources_layout(desc, type);
gl::upload_texture(cmd, dst, desc.gcm_format, desc.swizzled, subresources_layout);
}
void texture_cache::copy_transfer_regions_impl(gl::command_context& cmd, gl::texture* dst_image, const rsx::simple_array<copy_region_descriptor>& sources) const
{
const auto dst_bpp = dst_image->pitch() / dst_image->width();
+42 -29
View File
@@ -494,6 +494,8 @@ namespace gl
m_temporary_surfaces.clear();
}
void initialize_subresource_from_memory(gl::command_context& cmd, gl::texture* dst, const deferred_subresource& desc, rsx::texture_dimension_extended type) const;
gl::texture_view* create_temporary_subresource_impl(gl::command_context& cmd, gl::texture* src, GLenum sized_internal_fmt, GLenum dst_type, u32 gcm_format,
u16 x, u16 y, u16 width, u16 height, u16 depth, u8 mipmaps, const rsx::texture_channel_remap_t& remap, bool copy);
@@ -571,56 +573,67 @@ namespace gl
protected:
gl::texture_view* create_temporary_subresource_view(gl::command_context& cmd, gl::texture** src, u32 gcm_format, u16 x, u16 y, u16 w, u16 h,
const rsx::texture_channel_remap_t& remap_vector) override
gl::texture_view* create_temporary_subresource_view(gl::command_context& cmd, const deferred_subresource& desc) override
{
return create_temporary_subresource_impl(cmd, *src, GL_NONE, GL_TEXTURE_2D, gcm_format, x, y, w, h, 1, 1, remap_vector, true);
return create_temporary_subresource_impl(cmd, desc.external_handle, static_cast<GLenum>(desc.external_handle->get_internal_format()),
GL_TEXTURE_2D, desc.gcm_format, desc.x, desc.y, desc.width, desc.height, 1, 1, desc.remap, true);
}
gl::texture_view* create_temporary_subresource_view(gl::command_context& cmd, gl::texture* src, u32 gcm_format, u16 x, u16 y, u16 w, u16 h,
const rsx::texture_channel_remap_t& remap_vector) override
gl::texture_view* generate_cubemap_from_images(gl::command_context& cmd, const deferred_subresource& desc) override
{
return create_temporary_subresource_impl(cmd, src, static_cast<GLenum>(src->get_internal_format()),
GL_TEXTURE_2D, gcm_format, x, y, w, h, 1, 1, remap_vector, true);
}
auto _template = get_template_from_collection_impl(desc.sections_to_copy);
const u8 mip_count = 1 + desc.sections_to_copy.reduce(0, FN(std::max<u8>(x, y.level)));
auto result = create_temporary_subresource_impl(cmd, _template, GL_NONE, GL_TEXTURE_CUBE_MAP, desc.gcm_format, 0, 0, desc.width, desc.height, 1, mip_count, desc.remap, false);
gl::texture_view* generate_cubemap_from_images(gl::command_context& cmd, u32 gcm_format, u16 size, const rsx::simple_array<copy_region_descriptor>& sources, const rsx::texture_channel_remap_t& remap_vector) override
{
auto _template = get_template_from_collection_impl(sources);
const u8 mip_count = 1 + sources.reduce(0, FN(std::max<u8>(x, y.level)));
auto result = create_temporary_subresource_impl(cmd, _template, GL_NONE, GL_TEXTURE_CUBE_MAP, gcm_format, 0, 0, size, size, 1, mip_count, remap_vector, false);
if (desc.force_bg_load)
{
initialize_subresource_from_memory(cmd, result->image(), desc, rsx::texture_dimension_extended::texture_dimension_cubemap);
}
copy_transfer_regions_impl(cmd, result->image(), sources);
copy_transfer_regions_impl(cmd, result->image(), desc.sections_to_copy);
return result;
}
gl::texture_view* generate_3d_from_2d_images(gl::command_context& cmd, u32 gcm_format, u16 width, u16 height, u16 depth, const rsx::simple_array<copy_region_descriptor>& sources, const rsx::texture_channel_remap_t& remap_vector) override
gl::texture_view* generate_3d_from_2d_images(gl::command_context& cmd, const deferred_subresource& desc) override
{
auto _template = get_template_from_collection_impl(sources);
auto result = create_temporary_subresource_impl(cmd, _template, GL_NONE, GL_TEXTURE_3D, gcm_format, 0, 0, width, height, depth, 1, remap_vector, false);
auto _template = get_template_from_collection_impl(desc.sections_to_copy);
auto result = create_temporary_subresource_impl(cmd, _template, GL_NONE, GL_TEXTURE_3D, desc.gcm_format, 0, 0, desc.width, desc.height, desc.depth, 1, desc.remap, false);
copy_transfer_regions_impl(cmd, result->image(), sources);
if (desc.force_bg_load)
{
initialize_subresource_from_memory(cmd, result->image(), desc, rsx::texture_dimension_extended::texture_dimension_3d);
}
copy_transfer_regions_impl(cmd, result->image(), desc.sections_to_copy);
return result;
}
gl::texture_view* generate_atlas_from_images(gl::command_context& cmd, u32 gcm_format, u16 width, u16 height, const rsx::simple_array<copy_region_descriptor>& sections_to_copy,
const rsx::texture_channel_remap_t& remap_vector) override
gl::texture_view* generate_atlas_from_images(gl::command_context& cmd, const deferred_subresource& desc) override
{
auto _template = get_template_from_collection_impl(sections_to_copy);
auto result = create_temporary_subresource_impl(cmd, _template, GL_NONE, GL_TEXTURE_2D, gcm_format, 0, 0, width, height, 1, 1, remap_vector, false);
auto _template = get_template_from_collection_impl(desc.sections_to_copy);
auto result = create_temporary_subresource_impl(cmd, _template, GL_NONE, GL_TEXTURE_2D, desc.gcm_format, 0, 0, desc.width, desc.height, 1, 1, desc.remap, false);
copy_transfer_regions_impl(cmd, result->image(), sections_to_copy);
if (desc.force_bg_load)
{
initialize_subresource_from_memory(cmd, result->image(), desc, rsx::texture_dimension_extended::texture_dimension_2d);
}
copy_transfer_regions_impl(cmd, result->image(), desc.sections_to_copy);
return result;
}
gl::texture_view* generate_2d_mipmaps_from_images(gl::command_context& cmd, u32 gcm_format, u16 width, u16 height, const rsx::simple_array<copy_region_descriptor>& sections_to_copy,
const rsx::texture_channel_remap_t& remap_vector) override
gl::texture_view* generate_2d_mipmaps_from_images(gl::command_context& cmd, const deferred_subresource& desc) override
{
const auto mipmaps = ::narrow<u8>(sections_to_copy.size());
auto _template = get_template_from_collection_impl(sections_to_copy);
auto result = create_temporary_subresource_impl(cmd, _template, GL_NONE, GL_TEXTURE_2D, gcm_format, 0, 0, width, height, 1, mipmaps, remap_vector, false);
const auto mipmaps = ::narrow<u8>(desc.sections_to_copy.size());
auto _template = get_template_from_collection_impl(desc.sections_to_copy);
auto result = create_temporary_subresource_impl(cmd, _template, GL_NONE, GL_TEXTURE_2D, desc.gcm_format, 0, 0, desc.width, desc.height, 1, mipmaps, desc.remap, false);
copy_transfer_regions_impl(cmd, result->image(), sections_to_copy);
if (desc.force_bg_load)
{
initialize_subresource_from_memory(cmd, result->image(), desc, rsx::texture_dimension_extended::texture_dimension_2d);
}
copy_transfer_regions_impl(cmd, result->image(), desc.sections_to_copy);
return result;
}
@@ -1,6 +1,7 @@
#include "stdafx.h"
#include "../overlay_manager.h"
#include "overlay_friends_list_dialog.h"
#include "Emu/NP/np_handler.h"
#include "Emu/NP/rpcn_config.h"
#include "Emu/vfs_config.h"
@@ -14,12 +15,14 @@ namespace rsx
dlg->callback_handler(ntype, username, status);
}
void game_invite_callback(void* param, const shared_ptr<std::pair<std::string, message_data>> new_msg, u64 msg_id)
{
auto* dlg = static_cast<friends_list_dialog*>(param);
dlg->message_callback_handler(new_msg, msg_id);
}
friends_list_dialog::friends_list_entry::friends_list_entry(friends_list_dialog_page page, std::string_view username, const rpcn::friend_online_data& data)
{
std::unique_ptr<overlay_element> image = std::make_unique<image_view>();
image->set_size(160, 110);
image->set_padding(36, 36, 11, 11); // Square image, 88x88
std::string avatar_path = g_cfg_vfs.get_dev_flash() + "vsh/resource/explore/user/";
std::string text;
@@ -44,13 +47,17 @@ namespace rsx
}
break;
}
case friends_list_dialog_page::invites:
case friends_list_dialog_page::friend_requests:
{
// We use "online" to show whether an invite was sent or received
avatar_path += data.online ? "012.png" : "011.png";
text = get_localized_string(data.online ? localized_string_id::HOME_MENU_FRIENDS_REQUEST_RECEIVED : localized_string_id::HOME_MENU_FRIENDS_REQUEST_SENT);
break;
}
case friends_list_dialog_page::game_invites:
{
fmt::throw_exception("Unexpected game invitation in friend entry constructor");
}
case friends_list_dialog_page::blocked:
{
avatar_path += "010.png";
@@ -59,6 +66,30 @@ namespace rsx
}
}
init(username, text, std::move(avatar_path));
}
friends_list_dialog::friends_list_entry::friends_list_entry(std::string_view username, const message_data& message)
{
std::string text = message.subject;
if (!message.body.empty())
{
if (!text.empty())
text += '\n';
text += message.body;
}
init(username, text, g_cfg_vfs.get_dev_flash() + "vsh/resource/explore/user/012.png");
}
void friends_list_dialog::friends_list_entry::init(std::string_view username, std::string_view text, std::string avatar_path)
{
std::unique_ptr<overlay_element> image = std::make_unique<image_view>();
image->set_size(160, 110);
image->set_padding(36, 36, 11, 11); // Square image, 88x88
if (fs::exists(avatar_path))
{
icon_data = std::make_unique<image_info>(avatar_path);
@@ -180,7 +211,9 @@ namespace rsx
if (!m_list || m_list->m_items.empty())
break;
if (button_press == pad_button::triangle && m_current_page != friends_list_dialog_page::invites)
if (button_press == pad_button::triangle &&
m_current_page != friends_list_dialog_page::friend_requests &&
m_current_page != friends_list_dialog_page::game_invites)
break;
const usz index = static_cast<usz>(m_list->get_selected_index());
@@ -222,7 +255,7 @@ namespace rsx
}
break;
}
case friends_list_dialog_page::invites:
case friends_list_dialog_page::friend_requests:
{
// Get selected user
usz user_index = 0;
@@ -279,6 +312,40 @@ namespace rsx
}
break;
}
case friends_list_dialog_page::game_invites:
{
if (index >= m_game_invites.size() || !m_message_box || m_message_box->visible)
break;
const auto& [message_id, message] = ::at32(m_game_invites, index);
ensure(message);
const localized_string_id prompt = button_press == pad_button::cross ?
localized_string_id::HOME_MENU_FRIENDS_ACCEPT_GAME_INVITE_MSG :
localized_string_id::HOME_MENU_FRIENDS_REJECT_GAME_INVITE_MSG;
if (button_press == pad_button::cross)
{
m_message_box->show(get_localized_string(prompt, message->first.c_str()), [this, message_id]()
{
if (g_fxo->get<named_thread<np::np_handler>>().select_invitation(message_id))
{
remove_game_invite(message_id);
}
});
}
else
{
m_message_box->show(get_localized_string(prompt, message->first.c_str()), [this, message_id]()
{
m_rpcn->mark_message_used(message_id);
remove_game_invite(message_id);
});
}
refresh();
break;
}
case friends_list_dialog_page::blocked:
{
// Get selected user
@@ -315,8 +382,9 @@ namespace rsx
case pad_button::square:
switch (m_current_page)
{
case friends_list_dialog_page::friends: m_current_page = friends_list_dialog_page::invites; break;
case friends_list_dialog_page::invites: m_current_page = friends_list_dialog_page::blocked; break;
case friends_list_dialog_page::friends: m_current_page = friends_list_dialog_page::friend_requests; break;
case friends_list_dialog_page::friend_requests: m_current_page = friends_list_dialog_page::game_invites; break;
case friends_list_dialog_page::game_invites: m_current_page = friends_list_dialog_page::blocked; break;
case friends_list_dialog_page::blocked: m_current_page = friends_list_dialog_page::friends; break;
}
m_list_dirty = true;
@@ -382,7 +450,8 @@ namespace rsx
switch (m_current_page)
{
case friends_list_dialog_page::friends: title_id = localized_string_id::HOME_MENU_FRIENDS; break;
case friends_list_dialog_page::invites: title_id = localized_string_id::HOME_MENU_FRIENDS_REQUESTS; break;
case friends_list_dialog_page::friend_requests: title_id = localized_string_id::HOME_MENU_FRIENDS_REQUESTS; break;
case friends_list_dialog_page::game_invites: title_id = localized_string_id::HOME_MENU_FRIENDS_GAME_INVITES; break;
case friends_list_dialog_page::blocked: title_id = localized_string_id::HOME_MENU_FRIENDS_BLOCKED; break;
}
m_description->set_text(get_localized_string(title_id));
@@ -404,16 +473,20 @@ namespace rsx
{
result.add(m_list->get_compiled());
if (!m_list->m_items.empty() && m_current_page == friends_list_dialog_page::invites)
if (!m_list->m_items.empty())
{
// Get selected user
const usz index = static_cast<usz>(m_list->get_selected_index());
if (index < m_friend_data.requests_received.size())
if (m_current_page == friends_list_dialog_page::friend_requests && index < m_friend_data.requests_received.size())
{
m_extra_btn.set_text(get_localized_string(localized_string_id::HOME_MENU_FRIENDS_REJECT_REQUEST));
result.add(m_extra_btn.get_compiled());
}
else if (m_current_page == friends_list_dialog_page::game_invites && index < m_game_invites.size())
{
m_extra_btn.set_text(get_localized_string(localized_string_id::HOME_MENU_FRIENDS_REJECT_GAME_INVITE));
result.add(m_extra_btn.get_compiled());
}
}
}
result.add(m_page_btn.get_compiled());
@@ -447,10 +520,38 @@ namespace rsx
}
}
void friends_list_dialog::message_callback_handler(const shared_ptr<std::pair<std::string, message_data>>& message, u64 message_id)
{
std::lock_guard lock(m_list_mutex);
if (std::none_of(m_game_invite_messages.cbegin(), m_game_invite_messages.cend(), [message_id](const game_invite& invite)
{
return invite.first == message_id;
}))
{
m_game_invite_messages.emplace_back(message_id, message);
m_list_dirty = true;
}
}
void friends_list_dialog::remove_game_invite(u64 message_id)
{
std::lock_guard lock(m_list_mutex);
const auto has_message_id = [message_id](const game_invite& invite)
{
return invite.first == message_id;
};
std::erase_if(m_game_invite_messages, has_message_id);
std::erase_if(m_game_invites, has_message_id);
m_list_dirty = true;
}
void friends_list_dialog::reload()
{
std::vector<std::unique_ptr<overlay_element>> entries;
std::string selected_user;
u64 selected_message_id = 0;
s32 selected_index = 0;
bool rpcn_connected = true;
@@ -474,7 +575,7 @@ namespace rsx
}
break;
}
case friends_list_dialog_page::invites:
case friends_list_dialog_page::friend_requests:
{
for (const std::string& username : m_friend_data.requests_received)
{
@@ -497,6 +598,14 @@ namespace rsx
}
break;
}
case friends_list_dialog_page::game_invites:
{
if (old_index >= 0 && static_cast<usz>(old_index) < m_game_invites.size())
{
selected_message_id = ::at32(m_game_invites, old_index).first;
}
break;
}
case friends_list_dialog_page::blocked:
{
for (const std::string& username : m_friend_data.blocked)
@@ -578,7 +687,7 @@ namespace rsx
}
break;
}
case friends_list_dialog_page::invites:
case friends_list_dialog_page::friend_requests:
{
for (const std::string& username : m_friend_data.requests_received)
{
@@ -602,6 +711,38 @@ namespace rsx
}
break;
}
case friends_list_dialog_page::game_invites:
{
m_game_invites.clear();
if (!rpcn_connected)
break;
auto& nph = g_fxo->get<named_thread<np::np_handler>>();
if (!nph.basic_handler_registered)
break;
const SceNpCommunicationId context = nph.get_basic_handler_context();
for (const auto& [message_id, message] : m_game_invite_messages)
{
ensure(message);
if (std::strncmp(message->second.commId.data, context.data, sizeof(context.data) - 1) != 0)
continue;
if (message_id == selected_message_id)
{
selected_index = ::size32(entries);
}
m_game_invites.emplace_back(message_id, message);
std::unique_ptr<overlay_element> entry = std::make_unique<friends_list_entry>(message->first, message->second);
entries.emplace_back(std::move(entry));
}
break;
}
case friends_list_dialog_page::blocked:
{
for (const std::string& username : m_friend_data.blocked)
@@ -656,7 +797,20 @@ namespace rsx
m_rpcn = rpcn::rpcn_client::get_instance(0);
{
std::lock_guard lock(m_list_mutex);
m_game_invite_messages.clear();
m_game_invites.clear();
m_list_dirty = true;
}
m_rpcn->register_friend_cb(friend_callback, this);
const auto game_invites = m_rpcn->get_messages_and_register_cb(SCE_NP_BASIC_MESSAGE_MAIN_TYPE_INVITE, true, game_invite_callback, this);
for (const auto& [message_id, message] : game_invites)
{
message_callback_handler(message, message_id);
}
m_description->set_text(get_localized_string(localized_string_id::HOME_MENU_FRIENDS));
m_description->auto_resize();
@@ -681,6 +835,9 @@ namespace rsx
notify->wait(0, atomic_wait_timeout{1'000'000});
}
m_rpcn->remove_message_cb(game_invite_callback, this);
m_rpcn->remove_friend_cb(friend_callback, this);
return CELL_OK;
}
@@ -13,7 +13,8 @@ namespace rsx
enum class friends_list_dialog_page
{
friends,
invites,
friend_requests,
game_invites,
blocked
};
@@ -24,11 +25,15 @@ namespace rsx
{
private:
std::unique_ptr<image_info> icon_data;
void init(std::string_view username, std::string_view text, std::string avatar_path);
public:
friends_list_entry(friends_list_dialog_page page, std::string_view username, const rpcn::friend_online_data& data);
friends_list_entry(std::string_view username, const message_data& message);
};
using game_invite = std::pair<u64, shared_ptr<std::pair<std::string, message_data>>>;
std::mutex m_list_mutex;
std::vector<u32> m_entry_ids;
std::unique_ptr<overlay_element> m_dim_background;
@@ -42,11 +47,14 @@ namespace rsx
std::shared_ptr<rpcn::rpcn_client> m_rpcn;
rpcn::friend_data m_friend_data;
std::vector<game_invite> m_game_invite_messages;
std::vector<game_invite> m_game_invites;
atomic_t<bool> m_list_dirty { true };
atomic_t<friends_list_dialog_page> m_current_page { friends_list_dialog_page::friends };
atomic_t<friends_list_dialog_page> m_last_page { friends_list_dialog_page::friends };
void reload();
void remove_game_invite(u64 message_id);
public:
friends_list_dialog();
@@ -59,6 +67,7 @@ namespace rsx
error_code show(bool enable_overlay, std::function<void(s32 status)> on_close);
void callback_handler(rpcn::NotificationType ntype, const std::string& username, bool status);
void message_callback_handler(const shared_ptr<std::pair<std::string, message_data>>& message, u64 message_id);
static bool rpcn_configured();
};
@@ -103,6 +103,13 @@ namespace rsx
m_description->auto_resize();
m_description->back_color.a = 0.f;
m_sort_button = std::make_unique<image_button>();
m_sort_button->set_text(localized_string_id::HOME_MENU_TROPHY_SORT_GAME_DEFAULT);
m_sort_button->set_image_resource(resource_config::standard_image_resource::triangle);
m_sort_button->set_size(120, 30);
m_sort_button->set_pos(560, trophy_list_y + trophy_list_h + 20);
m_sort_button->set_font("Arial", 16);
m_show_hidden_trophies_button = std::make_unique<image_button>();
m_show_hidden_trophies_button->set_text(m_show_hidden_trophies ? localized_string_id::HOME_MENU_TROPHY_HIDE_HIDDEN_TROPHIES : localized_string_id::HOME_MENU_TROPHY_SHOW_HIDDEN_TROPHIES);
m_show_hidden_trophies_button->set_image_resource(resource_config::standard_image_resource::square);
@@ -139,6 +146,19 @@ namespace rsx
m_show_hidden_trophies = !m_show_hidden_trophies;
m_list_dirty = true;
break;
case pad_button::triangle:
{
// Cycle through sort modes
switch (m_sort_mode)
{
case trophy_sort_mode::game_default: m_sort_mode = trophy_sort_mode::not_earned; break;
case trophy_sort_mode::not_earned: m_sort_mode = trophy_sort_mode::earned_date; break;
case trophy_sort_mode::earned_date: m_sort_mode = trophy_sort_mode::grade; break;
case trophy_sort_mode::grade: m_sort_mode = trophy_sort_mode::game_default; break;
}
m_list_dirty = true;
break;
}
case pad_button::dpad_up:
case pad_button::ls_up:
m_list->select_previous();
@@ -189,6 +209,20 @@ namespace rsx
m_show_hidden_trophies_last = m_show_hidden_trophies;
}
if (m_sort_mode_last != m_sort_mode)
{
localized_string_id sort_label_id;
switch (m_sort_mode)
{
case trophy_sort_mode::not_earned: sort_label_id = localized_string_id::HOME_MENU_TROPHY_SORT_NOT_EARNED; break;
case trophy_sort_mode::earned_date: sort_label_id = localized_string_id::HOME_MENU_TROPHY_SORT_EARNED_DATE; break;
case trophy_sort_mode::grade: sort_label_id = localized_string_id::HOME_MENU_TROPHY_SORT_GRADE; break;
default: sort_label_id = localized_string_id::HOME_MENU_TROPHY_SORT_GAME_DEFAULT; break;
}
m_sort_button->set_text(sort_label_id);
m_sort_mode_last = m_sort_mode;
}
compiled_resource result;
result.add(m_dim_background->get_compiled());
if (m_list_dirty.exchange(false))
@@ -200,6 +234,7 @@ namespace rsx
result.add(m_list->get_compiled());
}
result.add(m_description->get_compiled());
result.add(m_sort_button->get_compiled());
result.add(m_show_hidden_trophies_button->get_compiled());
fade_animation.apply(result);
@@ -311,13 +346,10 @@ namespace rsx
rsx_log.trace("Reloading Trophy List Overlay with %s %s", m_trophy_data->game_name, m_trophy_data->path);
std::string selected_trophy;
s32 selected_index = 0;
s32 selected_index = m_list ? m_list->get_selected_index() : 0;
const overlay_element* old_trophy = m_list ? m_list->get_selected_entry() : nullptr;
const s32 old_trophy_id = old_trophy ? static_cast<const trophy_list_entry*>(old_trophy)->trophy_id : 0;
std::vector<std::unique_ptr<overlay_element>> entries;
const int all_trophies = m_trophy_data->trop_usr->GetTrophiesCount();
const int unlocked_trophies = m_trophy_data->trop_usr->GetUnlockedTrophiesCount();
const int percentage = (all_trophies > 0) ? (100 * unlocked_trophies / all_trophies) : 0;
@@ -331,6 +363,17 @@ namespace rsx
const std::string hidden_title = get_localized_string(localized_string_id::HOME_MENU_TROPHY_HIDDEN_TITLE);
const std::string hidden_description = get_localized_string(localized_string_id::HOME_MENU_TROPHY_HIDDEN_DESCRIPTION);
struct trophy_entry_data
{
SceNpTrophyDetails details{};
std::string icon_path;
bool unlocked = false;
bool platinum_relevant = false;
u64 timestamp = 0;
};
std::vector<trophy_entry_data> trophy_entries;
for (std::shared_ptr<rXmlNode> n = trophy_base ? trophy_base->GetChildren() : nullptr; n; n = n->GetNext())
{
// Only show trophies.
@@ -340,44 +383,43 @@ namespace rsx
}
// Get data (stolen graciously from sceNpTrophy.cpp)
SceNpTrophyDetails details{};
details.trophyId = atoi(n->GetAttribute("id").c_str());
details.hidden = n->GetAttribute("hidden")[0] == 'y';
trophy_entry_data entry{};
entry.details.trophyId = atoi(n->GetAttribute("id").c_str());
entry.details.hidden = n->GetAttribute("hidden")[0] == 'y';
const bool unlocked = m_trophy_data->trop_usr->GetTrophyUnlockState(details.trophyId);
const bool hide_trophy = details.hidden && !unlocked && !m_show_hidden_trophies;
entry.unlocked = m_trophy_data->trop_usr->GetTrophyUnlockState(entry.details.trophyId);
entry.timestamp = m_trophy_data->trop_usr->GetTrophyTimestamp(entry.details.trophyId);
if (details.trophyId == old_trophy_id)
const bool hide_trophy = entry.details.hidden && !entry.unlocked && !m_show_hidden_trophies;
if (hide_trophy)
{
// Select this entry if the trophy is visible. Use the previous index otherwise.
const s32 index = static_cast<s32>(entries.size());
selected_index = hide_trophy ? std::max(0, index - 1) : index;
continue;
}
if (hide_trophy)
if (m_sort_mode == trophy_sort_mode::not_earned && entry.unlocked)
{
continue;
}
// Get platinum link id (we assume there only exists one platinum trophy per game for now)
const s32 platinum_link_id = atoi(n->GetAttribute("pid").c_str());
const bool platinum_relevant = platinum_link_id >= 0;
entry.platinum_relevant = platinum_link_id >= 0;
// Get trophy type
switch (n->GetAttribute("ttype")[0])
{
case 'B': details.trophyGrade = SCE_NP_TROPHY_GRADE_BRONZE; break;
case 'S': details.trophyGrade = SCE_NP_TROPHY_GRADE_SILVER; break;
case 'G': details.trophyGrade = SCE_NP_TROPHY_GRADE_GOLD; break;
case 'P': details.trophyGrade = SCE_NP_TROPHY_GRADE_PLATINUM; break;
case 'B': entry.details.trophyGrade = SCE_NP_TROPHY_GRADE_BRONZE; break;
case 'S': entry.details.trophyGrade = SCE_NP_TROPHY_GRADE_SILVER; break;
case 'G': entry.details.trophyGrade = SCE_NP_TROPHY_GRADE_GOLD; break;
case 'P': entry.details.trophyGrade = SCE_NP_TROPHY_GRADE_PLATINUM; break;
default: rsx_log.warning("Unknown trophy grade %s", n->GetAttribute("ttype")); break;
}
// Get name and detail
if (details.hidden && !unlocked)
if (entry.details.hidden && !entry.unlocked)
{
strcpy_trunc(details.name, hidden_title);
strcpy_trunc(details.description, hidden_description);
strcpy_trunc(entry.details.name, hidden_title);
strcpy_trunc(entry.details.description, hidden_description);
}
else
{
@@ -386,19 +428,50 @@ namespace rsx
const std::string name = n2->GetName();
if (name == "name")
{
strcpy_trunc(details.name, n2->GetNodeContent());
strcpy_trunc(entry.details.name, n2->GetNodeContent());
}
else if (name == "detail")
{
strcpy_trunc(details.description, n2->GetNodeContent());
strcpy_trunc(entry.details.description, n2->GetNodeContent());
}
}
}
const auto icon_path_it = m_trophy_data->trophy_image_paths.find(details.trophyId);
const auto icon_path_it = m_trophy_data->trophy_image_paths.find(entry.details.trophyId);
entry.icon_path = (icon_path_it != m_trophy_data->trophy_image_paths.cend()) ? icon_path_it->second : "";
std::unique_ptr<overlay_element> entry = std::make_unique<trophy_list_entry>(details, icon_path_it != m_trophy_data->trophy_image_paths.cend() ? icon_path_it->second : "", !unlocked, platinum_relevant);
entries.emplace_back(std::move(entry));
trophy_entries.emplace_back(std::move(entry));
}
switch (m_sort_mode)
{
case trophy_sort_mode::earned_date:
std::stable_sort(trophy_entries.begin(), trophy_entries.end(), [](const trophy_entry_data& a, const trophy_entry_data& b)
{
if (a.unlocked != b.unlocked) return a.unlocked > b.unlocked;
return a.timestamp > b.timestamp;
});
break;
case trophy_sort_mode::grade:
std::stable_sort(trophy_entries.begin(), trophy_entries.end(), [](const trophy_entry_data& a, const trophy_entry_data& b)
{
return a.details.trophyGrade < b.details.trophyGrade;
});
break;
default:
break;
}
std::vector<std::unique_ptr<overlay_element>> entries;
for (const trophy_entry_data& entry : trophy_entries)
{
if (entry.details.trophyId == old_trophy_id)
{
selected_index = static_cast<s32>(entries.size());
}
entries.emplace_back(std::make_unique<trophy_list_entry>(entry.details, entry.icon_path, !entry.unlocked, entry.platinum_relevant));
}
// Recreate list
@@ -20,6 +20,14 @@ namespace rsx
std::string path;
};
enum class trophy_sort_mode
{
game_default,
not_earned,
earned_date,
grade,
};
struct trophy_list_dialog : public user_interface
{
private:
@@ -29,6 +37,7 @@ namespace rsx
std::unique_ptr<overlay_element> m_dim_background;
std::unique_ptr<list_view> m_list;
std::unique_ptr<label> m_description;
std::unique_ptr<image_button> m_sort_button;
std::unique_ptr<image_button> m_show_hidden_trophies_button;
animation_color_interpolate fade_animation;
@@ -37,6 +46,8 @@ namespace rsx
atomic_t<bool> m_list_dirty { true };
bool m_show_hidden_trophies = false;
bool m_show_hidden_trophies_last = false;
trophy_sort_mode m_sort_mode = trophy_sort_mode::game_default;
trophy_sort_mode m_sort_mode_last = trophy_sort_mode::game_default;
public:
trophy_list_dialog();
@@ -98,8 +98,7 @@ std::string CgBinaryDisasm::GetCondDisAsm() const
else if (swizzle == ".yyyy"sv) swizzle = ".y";
else if (swizzle == ".zzzz"sv) swizzle = ".z";
else if (swizzle == ".wwww"sv) swizzle = ".w";
if (swizzle == ".xyzw"sv)
else if (swizzle == ".xyzw"sv)
{
swizzle.clear();
}
@@ -229,7 +228,7 @@ template<typename T> std::string CgBinaryDisasm::GetSrcDisAsm(T src)
void CgBinaryDisasm::TaskFP()
{
m_size = 0;
u32* data = reinterpret_cast<u32*>(&m_buffer[m_offset]);
const u32* data = reinterpret_cast<const u32*>(&m_buffer[m_offset]);
ensure((m_buffer.size() - m_offset) % sizeof(u32) == 0);
enum
+9 -11
View File
@@ -173,11 +173,9 @@ void CgBinaryDisasm::BuildShaderBody(bool include_glsl)
std::vector<u32> be_data;
// Swap bytes. FP decompiler expects input in BE
for (u32* ptr = reinterpret_cast<u32*>(m_buffer.data() + m_offset),
*end = reinterpret_cast<u32*>(m_buffer.data() + m_buffer.size());
ptr < end; ++ptr)
for (usz i = m_offset; i < m_buffer.size(); i += sizeof(u32))
{
be_data.push_back(std::bit_cast<be_t<u32>>(*ptr));
be_data.push_back(std::bit_cast<be_t<u32>>(read_from_ptr<u32>(m_buffer, i)));
}
RSXFragmentProgram rsx_prog;
@@ -209,13 +207,13 @@ void CgBinaryDisasm::BuildShaderBody(bool include_glsl)
CgBinaryParameterOffset offset = prog.parameterArray;
for (u32 i = 0; i < prog.parameterCount; i++)
{
auto& vparam = GetCgRef<CgBinaryParameter>(offset);
const auto& vparam = GetCgRef<CgBinaryParameter>(offset);
std::string param_type = GetCgParamType(vparam.type) + " ";
std::string param_name = GetCgParamName(vparam.name) + " ";
std::string param_res = GetCgParamRes(vparam.res) + " ";
std::string param_semantic = GetCgParamSemantic(vparam.semantic) + " ";
std::string param_const = GetCgParamValue(vparam.embeddedConst, vparam.name);
const std::string param_type = GetCgParamType(vparam.type) + " ";
const std::string param_name = GetCgParamName(vparam.name) + " ";
const std::string param_res = GetCgParamRes(vparam.res) + " ";
const std::string param_semantic = GetCgParamSemantic(vparam.semantic) + " ";
const std::string param_const = GetCgParamValue(vparam.embeddedConst, vparam.name);
fmt::append(m_arb_shader, "#%d%s%s%s%s\n", i, param_type, param_name, param_semantic, param_const);
@@ -226,7 +224,7 @@ void CgBinaryDisasm::BuildShaderBody(bool include_glsl)
m_offset = prog.ucode;
ensure((m_buffer.size() - m_offset) % sizeof(u32) == 0);
u32* vdata = reinterpret_cast<u32*>(&m_buffer[m_offset]);
const u32* vdata = reinterpret_cast<const u32*>(&m_buffer[m_offset]);
m_data.resize(prog.ucodeSize / sizeof(u32));
std::memcpy(m_data.data(), vdata, prog.ucodeSize);
TaskVP();
+1 -1
View File
@@ -199,7 +199,7 @@ public:
template<typename T>
T& GetCgRef(const u32 offset)
{
return reinterpret_cast<T&>(m_buffer[offset]);
return reinterpret_cast<T&>(::at32(m_buffer, offset));
}
static std::string GetCgParamType(u32 type);
+12 -14
View File
@@ -4,14 +4,12 @@
void CgBinaryDisasm::AddScaCodeDisasm(const std::string& code)
{
ensure((m_sca_opcode < 21));
m_arb_shader += rsx_vp_sca_op_names[m_sca_opcode] + code + " ";
m_arb_shader += ::at32(rsx_vp_sca_op_names, m_sca_opcode) + code + " ";
}
void CgBinaryDisasm::AddVecCodeDisasm(const std::string& code)
{
ensure((m_vec_opcode < 26));
m_arb_shader += rsx_vp_vec_op_names[m_vec_opcode] + code + " ";
m_arb_shader += ::at32(rsx_vp_vec_op_names, m_vec_opcode) + code + " ";
}
std::string CgBinaryDisasm::GetMaskDisasm(bool is_sca) const
@@ -107,8 +105,6 @@ std::string CgBinaryDisasm::GetDSTDisasm(bool is_sca) const
std::string CgBinaryDisasm::GetSRCDisasm(const u32 n) const
{
ensure(n < 3);
std::string ret;
static constexpr std::array<std::string_view, 16> reg_table =
@@ -121,11 +117,13 @@ std::string CgBinaryDisasm::GetSRCDisasm(const u32 n) const
"in_tc4", "in_tc5", "in_tc6", "in_tc7"
};
switch (src[n].reg_type)
const SRC& s = ::at32(src, n);
switch (s.reg_type)
{
case 1: //temp
ret += 'R';
ret += std::to_string(src[n].tmp_src);
ret += std::to_string(s.tmp_src);
break;
case 2: //input
if (d1.input_src < reg_table.size())
@@ -143,7 +141,7 @@ std::string CgBinaryDisasm::GetSRCDisasm(const u32 n) const
break;
default:
rsx_log.fatal("Bad src%u reg type: %d", n, u32{ src[n].reg_type });
rsx_log.fatal("Bad src%u reg type: %d", n, u32{ s.reg_type });
break;
}
@@ -152,10 +150,10 @@ std::string CgBinaryDisasm::GetSRCDisasm(const u32 n) const
std::string swizzle;
swizzle.reserve(5);
swizzle += '.';
swizzle += f[src[n].swz_x];
swizzle += f[src[n].swz_y];
swizzle += f[src[n].swz_z];
swizzle += f[src[n].swz_w];
swizzle += f[s.swz_x];
swizzle += f[s.swz_y];
swizzle += f[s.swz_z];
swizzle += f[s.swz_w];
if (swizzle == ".xxxx") swizzle = ".x";
else if (swizzle == ".yyyy") swizzle = ".y";
@@ -178,7 +176,7 @@ std::string CgBinaryDisasm::GetSRCDisasm(const u32 n) const
}
if (abs) ret = "|" + ret + "|";
if (src[n].neg) ret = "-" + ret;
if (s.neg) ret = "-" + ret;
return ret;
}
@@ -960,13 +960,13 @@ std::string FragmentProgramDecompiler::BuildCode()
}
OS <<
"#define _builtin_lit lit_legacy\n"
"#define _builtin_log2 log2\n"
"#define _builtin_normalize(x) (length(x) > 0? normalize(x) : x)\n" // HACK!! Workaround for some games that generate NaNs unless texture filtering exactly matches PS3 (BFBC)
"#define _builtin_sqrt(x) sqrt(abs(x))\n"
"#define _builtin_rcp(x) (1. / x)\n"
"#define _builtin_rsq(x) (1. / _builtin_sqrt(x))\n"
"#define _builtin_div(x, y) (x / y)\n";
"#define _builtin_div(x, y) (x / y)\n"
"#define _builtin_lerp mix\n";
if (device_props.has_low_precision_rounding)
{
@@ -1118,8 +1118,11 @@ bool FragmentProgramDecompiler::handle_sct_scb(u32 opcode)
SetDst("_builtin_lit($0)");
properties.has_lit_op = true;
return true;
case RSX_FP_OPCODE_LIF: SetDst("$Ty(1.0, $0.y, ($0.y > 0 ? exp2($0.w) : 0.0), 1.0)", OPFLAGS::op_extern); return true;
case RSX_FP_OPCODE_LRP: SetDst("$Ty($2 * (1 - $0) + $1 * $0)", OPFLAGS::skip_type_cast); return true;
case RSX_FP_OPCODE_LIF:
SetDst("_builtin_lif($0)");
properties.has_lit_op = true;
return true;
case RSX_FP_OPCODE_LRP: SetDst("_builtin_lerp($2, $1, $0)", OPFLAGS::skip_type_cast); return true;
case RSX_FP_OPCODE_LG2: SetDst("_builtin_log2($0.x).xxxx"); return true;
// Pack operations. See https://www.khronos.org/registry/OpenGL/extensions/NV/NV_fragment_program.txt
// PK2 = PK2H (2 16-bit floats)
+6 -6
View File
@@ -43,8 +43,6 @@ namespace glsl
{
switch (elementCount)
{
default:
abort();
case 1:
return "float";
case 2:
@@ -53,6 +51,8 @@ namespace glsl
return "vec3";
case 4:
return "vec4";
default:
fmt::throw_exception("Unexpected element count %d", elementCount);
}
}
@@ -60,8 +60,6 @@ namespace glsl
{
switch (elementCount)
{
default:
abort();
case 1:
return "float16_t";
case 2:
@@ -70,6 +68,8 @@ namespace glsl
return "f16vec3";
case 4:
return "f16vec4";
default:
fmt::throw_exception("Unexpected element count %d", elementCount);
}
}
@@ -474,8 +474,6 @@ namespace glsl
{
switch (f)
{
default:
abort();
case FUNCTION::DP2:
return "$Ty(dot($0.xy, $1.xy))";
case FUNCTION::DP2A:
@@ -578,6 +576,8 @@ namespace glsl
return "textureLod($t, $0.xyz, 0)";
case FUNCTION::VERTEX_TEXTURE_FETCH2DMS:
return "texelFetch($t, ivec2($0.xy * textureSize($t)), 0)";
default:
fmt::throw_exception("Unexpected function request: %d", static_cast<int>(f));
}
rsx_log.error("Unexpected function request: %d", static_cast<int>(f));
@@ -101,6 +101,7 @@ uint ur0, ur1; // GP unsigned register (scalar)
uvec4 uvr0; // GP unsigned register (vector)
bvec4 bvr0, bvr1; // GP boolean register (vector)
float sr0; // GP scalar register
int ir0; // GP scalar register (scalar)
vec4 vrr; // value return (dst register)
vec4 s0, s1, s2; // instruction src (src0, src1, src2)
@@ -439,17 +440,6 @@ void write_dst(const in vec4 value)
uvr0 = uvec4(uint(1 << 9), uint(1 << 10), uint(1 << 11), uint(1 << 12));
bvr0 = bvec4(uvr0 & inst.words.xxxx);
if (TEST_INST_BIT(0, 8)) // SET COND
{
ur0 = GET_INST_BITS(1, 30, 1);
reg_mov(cc[ur0], value, bvr0);
}
if (TEST_INST_BIT(0, 30)) // NO DEST
{
return;
}
ur1 = GET_INST_BITS(2, 28, 3);
sr0 = modifier_scale[ur1];
vr0 = value * sr0;
@@ -467,6 +457,18 @@ void write_dst(const in vec4 value)
bvr0 = bvec4(uvec4(bvr0) & uvec4(bvr1));
}
// FIXME - HWTEST: Are CC registers affected by scale and SAT modifiers?
if (TEST_INST_BIT(0, 8)) // SET COND
{
ur0 = GET_INST_BITS(1, 30, 1);
reg_mov(cc[ur0], vr0, bvr0);
}
if (TEST_INST_BIT(0, 30)) // NO DEST
{
return;
}
ur1 = GET_INST_BITS(0, 1, 6);
if (TEST_INST_BIT(0, 7))
{
@@ -484,14 +486,18 @@ void initialize()
// NOTE: Register count is the number of 'full' registers that will be consumed. Hardware seems to do some renaming.
// NOTE: Attempting to zero-initialize all the registers will slow things to a crawl!
uint register_count = GET_BITS(shader_control, 24, 6);
ur0 = 0, ur1 = 0;
while (register_count > 0)
{
regs32[ur0++] = vr_zero;
regs16[ur1++] = vr_zero;
regs16[ur1++] = vr_zero;
register_count--;
const uint register_count = GET_BITS(shader_control, 24, 6);
const uint regs32_count = min(register_count, 48u);
const uint regs16_count = min(register_count << 1u, 48u);
ur0 = regs32_count;
while (ur0 > 0) {
regs32[--ur0] = vr_zero;
}
ur0 = regs16_count;
while (ur0 > 0) {
regs16[--ur0] = vr_zero;
}
// Fog coord
@@ -609,23 +615,24 @@ void main()
//case RSX_FP_OPCODE_CAL:
// Function call not yet found in the wild for this hw class
case RSX_FP_OPCODE_RET:
inst.end = true;
if (check_cond()) inst.end = true;
continue;
case RSX_FP_OPCODE_IFE:
ur0 = GET_INST_BITS(2, 0, 31); // ELSE addr
if (check_cond())
{
// Go down IF path
if (inst.words.z < inst.words.w)
// We've entered the IF block. Set up an exit trap to skip the ELSE block.
if (ur0 < inst.words.w) // If ELSE address is before ENDIF address..
{
test_addr = int(inst.words.z >> 2);
jump_addr = int(inst.words.w >> 2);
test_addr = int(ur0 >> 2u); // When we reach ELSE block...
jump_addr = int(inst.words.w >> 2u); // Jump to ENDIF
}
// If simple IF..ENDIF, do nothing
}
else
{
// Go to ELSE path
ip = int(inst.words.z >> 2);
// Go to ELSE path. If ELSE is not provided, it matches ENDIF address.
ip = int(ur0 >> 2u);
inst_length = 0;
}
continue;
@@ -633,18 +640,29 @@ void main()
case RSX_FP_OPCODE_REP:
if (check_cond())
{
counter = int(GET_INST_BITS(2, 2, 8) - GET_INST_BITS(2, 10, 8));
counter /= int(GET_INST_BITS(2, 19, 8));
loop_start_addr = ip + 1;
loop_end_addr = int(inst.words.w >> 2);
}
else
{
ip = int(inst.words.w >> 2);
inst_length = 0;
ur0 = GET_INST_BITS(2, 10, 8); // Start
ur1 = GET_INST_BITS(2, 2, 8); // End
if (ur1 > ur0)
{
counter = int(ur1 - ur0 - 1); // RANGE
ir0 = int(GET_INST_BITS(2, 19, 8)); // STEP
counter = max(counter, 0) / max(ir0, 1); // ITERATIONS
loop_start_addr = ip + 1;
loop_end_addr = int(inst.words.w >> 2);
continue;
}
}
// Failed cond check or 0 iterations encoded
ip = int(inst.words.w >> 2);
inst_length = 0;
continue;
case RSX_FP_OPCODE_BRK:
if (!check_cond())
{
continue;
}
if (loop_end_addr > 0)
{
ip = loop_end_addr;
@@ -694,7 +712,7 @@ void main()
case RSX_FP_OPCODE_RCP:
vrr = (1.f / s0.xxxx); break;
case RSX_FP_OPCODE_RSQ:
vrr = inversesqrt(s0.xxxx); break;
vrr = inversesqrt(abs(s0.x)).xxxx; break;
case RSX_FP_OPCODE_EX2:
vrr = exp2(s0.xxxx); break;
case RSX_FP_OPCODE_LG2:
@@ -708,7 +726,11 @@ void main()
case RSX_FP_OPCODE_SIN:
vrr = sin(s0.xxxx); break;
case RSX_FP_OPCODE_NRM:
vrr.xyz = normalize(s0.xyz); break;
vrr = normalize(s0.xyz).xyzz; break;
case RSX_FP_OPCODE_LIT:
vrr = _builtin_lit(s0); break;
case RSX_FP_OPCODE_LIF:
vrr = _builtin_lif(s0); break;
#ifdef WITH_TEXTURES
case RSX_FP_OPCODE_TEX:
@@ -723,7 +745,7 @@ void main()
case RSX_FP_OPCODE_PK4:
vrr = vec4(uintBitsToFloat(packSnorm4x8(s0))); break;
case RSX_FP_OPCODE_PK16:
vrr = vec4(uintBitsToFloat(packSnorm2x16(s0.xy))); break;
vrr = vec4(uintBitsToFloat(packUnorm2x16(s0.xy))); break;
case RSX_FP_OPCODE_PKG:
// Should be similar to PKB but with gamma correction, see description of PK4UBG in khronos page
case RSX_FP_OPCODE_PKB:
@@ -733,7 +755,7 @@ void main()
case RSX_FP_OPCODE_UP4:
vrr = unpackSnorm4x8(floatBitsToUint(s0.x)); break;
case RSX_FP_OPCODE_UP16:
vrr = unpackSnorm2x16(floatBitsToUint(s0.x)).xyxy; break;
vrr = unpackUnorm2x16(floatBitsToUint(s0.x)).xyxy; break;
case RSX_FP_OPCODE_UPG:
// Same as UPB with gamma correction
case RSX_FP_OPCODE_UPB:
@@ -784,7 +806,7 @@ void main()
vrr = s0 / s1.xxxx; break;
case RSX_FP_OPCODE_DIVSQ:
bvr0 = bvec4(s0);
sr0 = inversesqrt(s1.x);
sr0 = inversesqrt(abs(s1.x));
vr0 = s0 * sr0;
vrr = select(s0, vr0, bvr0);
break;
@@ -814,18 +836,19 @@ void main()
case RSX_FP_OPCODE_MAD:
vrr = fma(s0, s1, s2); break;
case RSX_FP_OPCODE_LRP:
vrr = mix(s1, s2, s0); break;
vrr = mix(s2, s1, s0); break;
case RSX_FP_OPCODE_DP2A:
vrr = dot(s0.xy, s1.xy).xxxx + s2.xxxx; break;
default:
// Fallback - just write zero
vrr = vr_zero;
}
}
#if 0
// Other
case RSX_FP_OPCODE_BEM:
case RSX_FP_OPCODE_BEMLUM:
case RSX_FP_OPCODE_LIT:
case RSX_FP_OPCODE_LIF:
case RSX_FP_OPCODE_TIMESWTEX:
//Other (missing in HW)
//case RSX_FP_OPCODE_BEM:
//case RSX_FP_OPCODE_BEMLUM:
//case RSX_FP_OPCODE_TIMESWTEX:
#endif
write_dst(vrr);
}
@@ -62,6 +62,8 @@ layout(location=0) out vec4 dest[16];
#define reg_mov(d, s, m) d = mix(d, s, m)
#define MAX_STACK_DEPTH 8
struct D0
{
uint addr_swz;
@@ -230,28 +232,36 @@ vec4 get_cond()
return shuffle(cc[d0.cond_reg_sel_1], d0.swizzle);
}
void write_sca(in float value)
void write_sca(in vec4 value)
{
if (d0.saturate)
{
value = clamp(value, 0, 1);
}
if (d3.sca_dst_tmp == 0x3f)
const bool write_out_reg = !d0.vec_result;
const bool write_gpr_reg = d3.sca_dst_tmp != 0x3f;
// NOTE: SCA does not support double register writes.
if (write_gpr_reg)
{
if (!d0.vec_result)
{
reg_mov(dest[d3.dst], vec4(value), d3.sca_mask);
}
else
{
reg_mov(cc[d0.cond_reg_sel_1], vec4(value), d3.sca_mask);
}
// Write to GPR. Most likely scenario.
reg_mov(temp[d3.sca_dst_tmp], value, d3.sca_mask);
return;
}
else
if (write_out_reg)
{
reg_mov(temp[d3.sca_dst_tmp], vec4(value), d3.sca_mask);
if (d3.dst < 16)
{
reg_mov(dest[d3.dst], value, d3.sca_mask);
}
return;
}
// Not writing to temp or out register. Update CC
reg_mov(cc[d0.cond_reg_sel_1], value, d3.sca_mask);
}
void write_vec(in vec4 value)
@@ -268,22 +278,32 @@ void write_vec(in vec4 value)
write_mask = bvec4(uvec4(write_mask) & uvec4(mask));
}
if (d0.dst_tmp == 0x3f && !d0.vec_result)
if (d0.dst_tmp != 0x3f)
{
reg_mov(cc[d0.cond_reg_sel_1], value, write_mask);
// GPR write
reg_mov(temp[d0.dst_tmp], value, write_mask);
if (!d0.vec_result)
{
return;
}
// Fallthrough - VEC engine allows double write to one output register and one GPR
}
else
if (d0.vec_result)
{
if (d0.vec_result && d3.dst < 16)
if (d3.dst < 16)
{
reg_mov(dest[d3.dst], value, write_mask);
}
if (d0.dst_tmp != 0x3f)
{
reg_mov(temp[d0.dst_tmp], value, write_mask);
}
return;
}
// Writes to neither GPR nor OUT reg. Update CC
reg_mov(cc[d0.cond_reg_sel_1], value, write_mask);
}
void write_output(const in int oid, const in int mask_bit)
@@ -308,9 +328,10 @@ ivec4 read_addr_reg()
int branch_addr()
{
uint addr_h = GET_BITS(instr.z, 0, 6);
uint addr_l = GET_BITS(instr.w, 29, 3);
return int((addr_h << 3) + addr_l);
const uint addr_2 = GET_BITS(instr.x, 23, 1); // Allows indexing beyond 512 instructions. Some games push upto 544 instructions on CEX firmware.
const uint addr_1 = GET_BITS(instr.z, 0, 6);
const uint addr_0 = GET_BITS(instr.w, 29, 3);
return int((addr_2 << 9) + (addr_1 << 3) + addr_0); // NOTE: Encoded jump addresses are already rebased by the VP analyser pass.
}
bool static_branch()
@@ -410,13 +431,13 @@ void main()
dest[i] = vec4(0., 0., 0., 1.);
}
int callstack[8];
int callstack[MAX_STACK_DEPTH];
int stack_ptr = 0;
int current_instruction = 0;
int current_instruction = int(entry - base_address);
d3.end = false;
while (current_instruction < 512)
while (current_instruction >= 0 && current_instruction < 544) // NOTE: CEX can hold upto 544 instructions
{
if (d3.end)
{
@@ -442,7 +463,7 @@ void main()
if (vec_opcode == RSX_VEC_OPCODE_ARL)
{
a[d0.dst_tmp] = ivec4(read_src(0));
a[d0.dst_tmp & 0x1u] = ivec4(read_src(0));
}
else if (vec_opcode != RSX_VEC_OPCODE_NOP)
{
@@ -475,7 +496,13 @@ void main()
write_vec(value);
}
if (sca_opcode != RSX_SCA_OPCODE_NOP)
if (sca_opcode == RSX_SCA_OPCODE_LIT)
{
// Ridiculously hacky instruction. It is a vector opcode, not scalar
// TODO: More HWTEST
write_sca(_builtin_lit(read_src(2)));
}
else if (sca_opcode != RSX_SCA_OPCODE_NOP)
{
float value = read_src(2).x;
switch (sca_opcode)
@@ -486,7 +513,6 @@ void main()
case RSX_SCA_OPCODE_RSQ: value = 1.0 / sqrt(value); break;
case RSX_SCA_OPCODE_EXP: value = exp(value); break;
case RSX_SCA_OPCODE_LOG: value = log(value); break;
//case RSX_SCA_OPCODE_LIT: value = lit_legacy(value); break;
case RSX_SCA_OPCODE_LG2: value = log2(value); break;
case RSX_SCA_OPCODE_EX2: value = exp2(value); break;
case RSX_SCA_OPCODE_SIN: value = sin(value); break;
@@ -494,7 +520,7 @@ void main()
case RSX_SCA_OPCODE_BRA:
// Jump by address register
if (dynamic_branch()) current_instruction = int(read_addr_reg().x);
if (dynamic_branch()) current_instruction = int(read_addr_reg().x - base_address);
continue;
case RSX_SCA_OPCODE_BRI:
// Jump immediate
@@ -504,7 +530,8 @@ void main()
// Call immediate
if (dynamic_branch())
{
callstack[stack_ptr] = current_instruction;
if (stack_ptr == MAX_STACK_DEPTH) return;
callstack[stack_ptr] = current_instruction; // Already incremented, points to next
stack_ptr++;
current_instruction = branch_addr();
}
@@ -517,8 +544,7 @@ void main()
if (dynamic_branch())
{
if (stack_ptr == 0) return;
current_instruction = callstack[stack_ptr];
stack_ptr--;
current_instruction = callstack[--stack_ptr];
}
continue;
case RSX_SCA_OPCODE_BRB:
@@ -532,6 +558,7 @@ void main()
// Call by boolean mask
if (static_branch())
{
if (stack_ptr == MAX_STACK_DEPTH) return;
callstack[stack_ptr] = current_instruction;
stack_ptr++;
current_instruction = branch_addr();
@@ -541,7 +568,7 @@ void main()
//case RSX_SCA_OPCODE_POP:
}
write_sca(value);
write_sca(vec4(value));
}
}
@@ -1,10 +1,12 @@
R"(
vec4 texelFetch2DMS(in _MSAA_SAMPLER_TYPE_ tex, const in vec2 sample_count, const in ivec2 icoords, const in ivec2 offset)
vec4 texelFetch2DMS(in _MSAA_SAMPLER_TYPE_ tex, const in ivec2 clamp_bounds, const in vec2 sample_count, const in ivec2 icoords, const in ivec2 offset)
{
const vec2 resolve_coords = vec2(icoords + offset);
const vec2 aa_coords = floor(resolve_coords / sample_count); // AA coords = real_coords / sample_count
const vec2 sample_loc = fma(aa_coords, -sample_count, resolve_coords); // Sample ID = real_coords % sample_count
const vec2 resolve_coords = vec2(clamp(icoords + offset, ivec2(0), clamp_bounds)); // Clamp to edge. Input offset is always zero or positive.
const vec2 aa_coords = floor(resolve_coords / sample_count); // AA coords = real_coords / sample_count
const vec2 sample_loc = fma(aa_coords, -sample_count, resolve_coords); // Sample ID = real_coords % sample_count
const float sample_index = fma(sample_loc.y, sample_count.y, sample_loc.x);
// TODO: Hack. Filtering will break when sampling sub-pixel sample ids in wrap mode.
return texelFetch(tex, ivec2(aa_coords), int(sample_index));
}
@@ -14,9 +16,10 @@ vec4 sampleTexture2DMS(in _MSAA_SAMPLER_TYPE_ tex, const in vec2 coords, const i
const vec2 scaled_coords = _texcoord_xform(coords, tex_params);
const vec2 normalized_coords = texture2DMSCoord(scaled_coords, flags);
const vec2 sample_count = vec2(2., textureSamples(tex) * 0.5);
const vec2 image_size = textureSize(tex) * sample_count;
const ivec2 image_size = ivec2(textureSize(tex) * sample_count);
const ivec2 clamp_bounds = image_size - ivec2(1);
const ivec2 icoords = ivec2(normalized_coords * image_size);
const vec4 sample0 = texelFetch2DMS(tex, sample_count, icoords, ivec2(0));
const vec4 sample0 = texelFetch2DMS(tex, clamp_bounds, sample_count, icoords, ivec2(0));
if (_get_bits(flags, FILTERED_MAG_BIT, 2) == 0)
{
@@ -35,7 +38,7 @@ vec4 sampleTexture2DMS(in _MSAA_SAMPLER_TYPE_ tex, const in vec2 coords, const i
vec4 a, b;
float factor;
const vec4 sample2 = texelFetch2DMS(tex, sample_count, icoords, ivec2(0, 1)); // Top left
const vec4 sample2 = texelFetch2DMS(tex, clamp_bounds, sample_count, icoords, ivec2(0, 1)); // Top left
if (no_filter.x)
{
@@ -46,21 +49,21 @@ vec4 sampleTexture2DMS(in _MSAA_SAMPLER_TYPE_ tex, const in vec2 coords, const i
else
{
// Filter required, sample more data
const vec4 sample1 = texelFetch2DMS(tex, sample_count, icoords, ivec2(1, 0)); // Bottom right
const vec4 sample3 = texelFetch2DMS(tex, sample_count, icoords, ivec2(1, 1)); // Top right
const vec4 sample1 = texelFetch2DMS(tex, clamp_bounds, sample_count, icoords, ivec2(1, 0)); // Bottom right
const vec4 sample3 = texelFetch2DMS(tex, clamp_bounds, sample_count, icoords, ivec2(1, 1)); // Top right
if (actual_step.x > uv_step.x)
{
// Downscale in X, centered
const vec3 weights = compute2x2DownsampleWeights(normalized_coords.x, uv_step.x, actual_step.x);
const vec4 sample4 = texelFetch2DMS(tex, sample_count, icoords, ivec2(2, 0)); // Further bottom right
a = fma(sample0, weights.xxxx, sample1 * weights.y) + (sample4 * weights.z); // Weighted sum
const vec4 sample4 = texelFetch2DMS(tex, clamp_bounds, sample_count, icoords, ivec2(2, 0)); // Further bottom right
a = fma(sample0, weights.xxxx, sample1 * weights.y) + (sample4 * weights.z); // Weighted sum
if (!no_filter.y)
{
const vec4 sample5 = texelFetch2DMS(tex, sample_count, icoords, ivec2(2, 1)); // Further top right
b = fma(sample2, weights.xxxx, sample3 * weights.y) + (sample5 * weights.z); // Weighted sum
const vec4 sample5 = texelFetch2DMS(tex, clamp_bounds, sample_count, icoords, ivec2(2, 1)); // Further top right
b = fma(sample2, weights.xxxx, sample3 * weights.y) + (sample5 * weights.z); // Weighted sum
}
}
else if (actual_step.x < uv_step.x)
@@ -12,15 +12,31 @@ R"(
#define CMP_FIXUP(a) (a)
#endif
#define _builtin_approx_pow(a, b) exp2((b) * log2(a))
#ifdef _ENABLE_LIT_EMULATION
vec4 lit_legacy(const in vec4 val)
// LIT is well documented in NV extension documents. See https://registry.khronos.org/OpenGL/extensions/NV/NV_vertex_program.txt
// In RSXFP the LIT instruction is unimplemented in hw. In it's place we have LIF and full LIT is emulated using at least 3 instructions.
vec4 _builtin_lit(const in vec4 values)
{
vec4 clamped_val = vec4(max(val.xy, vec2(0.)), val.zw);
return vec4(
1.,
clamped_val.x,
exp2(clamped_val.w * log2(max(clamped_val.y, 0.0000000001))) * sign(clamped_val.x),
1.);
// We clamp t.y to 1e-10 to avoid NaN in approx_pow when y=0 and w=0. This matches the spec's pow(x, 0) == 1 requirement.
vec4 t = vec4(max(values.xy, vec2(0.f, 1e-10)), values.zw);
return vec4(1.f,
t.x,
t.x > 0.f ? _builtin_approx_pow(t.y, t.w) : 0.f,
1.f);
}
// LIT d, t on RSXFP becomes:
// t.xy = max(t.xy, 0)
// t.w = t.w * log2(t.y)
// LIF d, t
vec4 _builtin_lif(const in vec4 t)
{
return vec4(1.f,
t.y,
t.y > 0.f ? exp2(t.w) : 0.f,
1.f);
}
#endif
+2 -2
View File
@@ -120,9 +120,9 @@ namespace program_common::interpreter
// Prepare outputs
interpreter_variants_t result;
for (const auto& vs_opt : vs_masks)
for (const u32 vs_opt : vs_masks)
{
for (const auto& fs_opt : fs_masks)
for (const u32 fs_opt : fs_masks)
{
interpreter_pipeline_variant_t variant{};
variant.vs_opts.shader_opt = vs_opt;
@@ -25,12 +25,12 @@ std::string VertexProgramDecompiler::GetMask(bool is_sca) const
return ret.empty() || ret == "xyzw" ? "" : ("." + ret);
}
std::string VertexProgramDecompiler::GetVecMask()
std::string VertexProgramDecompiler::GetVecMask() const
{
return GetMask(false);
}
std::string VertexProgramDecompiler::GetScaMask()
std::string VertexProgramDecompiler::GetScaMask() const
{
return GetMask(true);
}
@@ -486,7 +486,7 @@ std::string VertexProgramDecompiler::Decompile()
{
// Call function
// NOTE: Addresses are assumed to have been patched
m_call_stack.push(i+1);
m_call_stack.push(i + 1);
AddCode(condition);
AddCode("{");
m_cur_instr->open_scopes++;
@@ -660,7 +660,7 @@ std::string VertexProgramDecompiler::Decompile()
case RSX_SCA_OPCODE_EXP: SetDSTSca("exp($s)"); break;
case RSX_SCA_OPCODE_LOG: SetDSTSca("log($s)"); break;
case RSX_SCA_OPCODE_LIT:
SetDSTSca("lit_legacy($s)");
SetDSTSca("_builtin_lit($s)");
properties.has_lit_op = true;
break;
case RSX_SCA_OPCODE_BRA:
@@ -680,8 +680,8 @@ std::string VertexProgramDecompiler::Decompile()
//TODO
rsx_log.error("BRA opcode found in subroutine!");
}
break;
}
break;
case RSX_SCA_OPCODE_BRI: // works differently (BRI o[1].x(TR) L0;)
{
if (m_call_stack.empty())
@@ -701,8 +701,8 @@ std::string VertexProgramDecompiler::Decompile()
//TODO
rsx_log.error("BRI opcode found in subroutine!");
}
break;
}
break;
case RSX_SCA_OPCODE_CAL:
// works same as BRI
AddCode("//CAL");
@@ -67,8 +67,8 @@ struct VertexProgramDecompiler
static std::string NotZeroPositive(const std::string& code);
std::string GetMask(bool is_sca) const;
std::string GetVecMask();
std::string GetScaMask();
std::string GetVecMask() const;
std::string GetScaMask() const;
std::string GetDST(bool is_sca = false);
std::string GetSRC(u32 n);
std::string GetTex();
+2 -2
View File
@@ -33,7 +33,7 @@ namespace rsx
std::memcpy(dst, src, sizeof(TIU_slot) * 4); return;
default:
break;
};
}
const auto start = std::countr_zero(mask);
const auto end = 16 - std::countl_zero(mask);
@@ -174,7 +174,7 @@ namespace rsx
}
// Check for any hits
for (auto& idx : constant_ids)
for (u16 idx : constant_ids)
{
if (idx >= first_index && idx <= last_index)
{
+7 -7
View File
@@ -16,14 +16,14 @@ namespace rsx
{
struct TIU_slot
{
float scale[3];
float bias[3];
float clamp_min[2];
float clamp_max[2];
u32 remap;
u32 control;
float scale[3] {};
float bias[3] {};
float clamp_min[2] {};
float clamp_max[2] {};
u32 remap {};
u32 control {};
}
slots_[16]; // QT headers will collide with any variable named 'slots' because reasons
slots_[16] {}; // QT headers will collide with any variable named 'slots' because reasons
TIU_slot& operator[](u32 index) { return slots_[index]; }
+15
View File
@@ -455,6 +455,21 @@ namespace rsx
return registers[NV4097_SET_TEXTURE_CONTROL3 + m_index] & 0xfffff;
}
image_section_attributes_t fragment_texture::attributes() const
{
const auto _format = format() & ~(CELL_GCM_TEXTURE_UN | CELL_GCM_TEXTURE_LN);
return {
.address = offset(),
.gcm_format = _format,
.pitch = pitch(),
.width = width(),
.height = height(),
.depth = depth(),
.mipmaps = mipmap(),
.bpp = rsx::get_format_block_size_in_bytes(_format)
};
}
u32 vertex_texture::offset() const
{
return registers[NV4097_SET_VERTEX_TEXTURE_OFFSET + (m_index * 8)] & 0x7FFFFFFF;
+3
View File
@@ -5,6 +5,7 @@
namespace rsx
{
struct texture_format_ex;
struct image_section_attributes_t;
class fragment_texture
{
@@ -85,6 +86,8 @@ namespace rsx
u16 depth() const;
u32 pitch() const;
image_section_attributes_t attributes() const;
};
class vertex_texture
+92 -8
View File
@@ -829,6 +829,59 @@ bool VKGSRender::bind_interpreter_texture_env()
bool out_of_memory = false;
auto decay_view_for_interpreter = [&](
const rsx::image_section_attributes_t& attr,
vk::texture_cache::sampled_image_descriptor* desc,
vk::image_view* base,
const rsx::texture_channel_remap_t& decoded_remap,
bool is_msaa,
bool is_redirected) -> vk::image_view*
{
if (!is_msaa && !is_redirected)
{
return base;
}
if (is_redirected && desc->image_type > rsx::texture_dimension_extended::texture_dimension_2d)
{
// Cannot handle redirect on 3D or cubemap with the interpreter.
auto view_type = vk::get_view_type(desc->image_type);
return vk::null_image_view(*m_current_command_buffer, view_type);
}
using deferred_subresource_t = vk::texture_cache::deferred_subresource;
auto image = static_cast<vk::viewable_image*>(base->image());
if (is_msaa)
{
// MSAA resolve
auto rtt = vk::as_rtt(base->image());
rtt->memory_barrier(*m_current_command_buffer, rsx::surface_access::transfer_read);
image = rtt->get_surface(rsx::surface_access::transfer_read);
}
if (is_redirected)
{
// Force bitcast
deferred_subresource_t flatten_op{};
flatten_op.address = desc->ref_address;
flatten_op.external_handle = image;
flatten_op.op = desc->is_cyclic_reference
? rsx::deferred_request_command::copy_image_dynamic
: rsx::deferred_request_command::copy_image_static;
flatten_op.width = flatten_op.external_handle->width();
flatten_op.height = flatten_op.external_handle->height();
flatten_op.depth = 1;
flatten_op.gcm_format = desc->format_ex.format();
flatten_op.remap = decoded_remap;
flatten_op.cache_range = utils::address_range32::start_length(desc->ref_address, attr.pitch * attr.height);
return m_texture_cache.create_temporary_subresource(*m_current_command_buffer, flatten_op);
}
image->change_layout(*m_current_command_buffer, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
return image->get_view(decoded_remap, base->info.subresourceRange.aspectMask);
};
for (u32 textures_ref = current_fp_metadata.referenced_textures_mask, i = 0; textures_ref; textures_ref >>= 1, ++i)
{
if (!(textures_ref & 1))
@@ -837,8 +890,8 @@ bool VKGSRender::bind_interpreter_texture_env()
vk::image_view* view = nullptr;
auto sampler_state = static_cast<vk::texture_cache::sampled_image_descriptor*>(fs_sampler_state[i].get());
if (rsx::method_registers.fragment_textures[i].enabled() &&
sampler_state->validate())
auto& tex = rsx::method_registers.fragment_textures[i];
if (tex.enabled() && sampler_state->validate())
{
if (view = sampler_state->image_handle; !view)
{
@@ -848,18 +901,49 @@ bool VKGSRender::bind_interpreter_texture_env()
out_of_memory = true;
}
}
else
}
if (!view)
{
// OOM or disabled texture
continue;
}
auto primary_view = view;
// Flatten MSAA and DEPTH24S8 redirects
if (view->image()->samples() > 1 || view->info.subresourceRange.aspectMask != VK_IMAGE_ASPECT_COLOR_BIT)
{
const auto mask = (1u << i);
const bool is_redirected = !!(current_fragment_program.texture_state.redirected_textures & mask);
const bool is_msaa = !!(current_fragment_program.texture_state.multisampled_textures & mask);
if (is_redirected || is_msaa)
{
validate_image_layout_for_read_access(*m_current_command_buffer, view, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, sampler_state);
view = decay_view_for_interpreter(
tex.attributes(),
sampler_state,
view,
tex.decoded_remap(),
is_msaa,
is_redirected);
if (!view)
{
// OOM
out_of_memory = true;
continue;
}
}
}
if (view)
if (primary_view == view)
{
const int offsets[] = { 0, 16, 48, 32 };
auto& sampled_image_info = texture_env[offsets[static_cast<u32>(sampler_state->image_type)] + i];
sampled_image_info = { *view, *fs_sampler_handles[i] };
validate_image_layout_for_read_access(*m_current_command_buffer, view, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, sampler_state);
}
const int offsets[] = { 0, 16, 48, 32 };
auto& sampled_image_info = texture_env[offsets[static_cast<u32>(sampler_state->image_type)] + i];
sampled_image_info = { *view, *fs_sampler_handles[i] };
}
m_shader_interpreter.update_fragment_textures(texture_env);
+6 -4
View File
@@ -285,10 +285,11 @@ namespace vk
}
}
[[maybe_unused]] ::glsl::shader_properties properties{};
properties.domain = ::glsl::program_domain::glsl_fragment_program;
properties.require_depth_conversion = true;
properties.require_wpos = true;
::glsl::shader_properties properties
{
.domain = ::glsl::program_domain::glsl_fragment_program,
.require_lit_emulation = true,
};
u32 len;
ParamArray arr;
@@ -417,6 +418,7 @@ namespace vk
" uint rop_control = fs_contexts[_fs_context_offset].rop_control;\n"
" float alpha_ref = fs_contexts[_fs_context_offset].alpha_ref;\n\n";
::glsl::insert_glsl_legacy_function(builder, properties);
builder << program_common::interpreter::get_fragment_interpreter();
const std::string s = builder.str();
+6
View File
@@ -1003,6 +1003,12 @@ namespace vk
for (const rsx::subresource_layout &layout : subresource_layout)
{
if (layout.level >= dst_image->mipmaps())
{
rsx_log.error("Invalid subresource definition for the output texture. Mip level does not exist.");
continue;
}
const auto [row_pitch, upload_pitch_in_texel] = calculate_upload_pitch(format, heap_align, dst_image, layout, caps);
caps.alignment = row_pitch;
+48 -26
View File
@@ -3,6 +3,7 @@
#include "VKTextureCache.h"
#include "VKCompute.h"
#include "VKAsyncScheduler.h"
#include "vkutils/data_heap.h"
#include "util/asm.hpp"
@@ -755,26 +756,19 @@ namespace vk
return view;
}
vk::image_view* texture_cache::create_temporary_subresource_view(vk::command_buffer& cmd, vk::image* source, u32 gcm_format,
u16 x, u16 y, u16 w, u16 h, const rsx::texture_channel_remap_t& remap_vector)
vk::image_view* texture_cache::create_temporary_subresource_view(vk::command_buffer& cmd, const deferred_subresource& desc)
{
return create_temporary_subresource_view_impl(cmd, source, source->info.imageType, VK_IMAGE_VIEW_TYPE_2D,
gcm_format, x, y, w, h, 1, 1, remap_vector, true);
return create_temporary_subresource_view_impl(cmd, desc.external_handle, desc.external_handle->info.imageType, VK_IMAGE_VIEW_TYPE_2D,
desc.gcm_format, desc.x, desc.y, desc.width, desc.height, 1, 1, desc.remap, true);
}
vk::image_view* texture_cache::create_temporary_subresource_view(vk::command_buffer& cmd, vk::image** source, u32 gcm_format,
u16 x, u16 y, u16 w, u16 h, const rsx::texture_channel_remap_t& remap_vector)
{
return create_temporary_subresource_view(cmd, *source, gcm_format, x, y, w, h, remap_vector);
}
vk::image_view* texture_cache::generate_cubemap_from_images(vk::command_buffer& cmd, u32 gcm_format, u16 size,
const rsx::simple_array<copy_region_descriptor>& sections_to_copy, const rsx::texture_channel_remap_t& remap_vector)
vk::image_view* texture_cache::generate_cubemap_from_images(vk::command_buffer& cmd, const deferred_subresource& desc)
{
const auto& sections_to_copy = desc.sections_to_copy;
auto _template = get_template_from_collection_impl(sections_to_copy);
const u8 mip_count = 1 + sections_to_copy.reduce(0, FN(std::max<u8>(x, y.level)));
auto result = create_temporary_subresource_view_impl(cmd, _template, VK_IMAGE_TYPE_2D,
VK_IMAGE_VIEW_TYPE_CUBE, gcm_format, 0, 0, size, size, 1, mip_count, remap_vector, false);
VK_IMAGE_VIEW_TYPE_CUBE, desc.gcm_format, 0, 0, desc.width, desc.height, 1, mip_count, desc.remap, false);
if (!result)
{
@@ -787,7 +781,12 @@ namespace vk
VkImageSubresourceRange dst_range = { dst_aspect, 0, mip_count, 0, 6 };
vk::change_image_layout(cmd, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, dst_range);
if (!(dst_aspect & VK_IMAGE_ASPECT_DEPTH_BIT))
if (desc.force_bg_load)
{
// The memory load covers the whole image, no need to clear it first
initialize_subresource_from_memory(cmd, image, desc, rsx::texture_dimension_extended::texture_dimension_cubemap);
}
else if (!(dst_aspect & VK_IMAGE_ASPECT_DEPTH_BIT))
{
VkClearColorValue clear = {};
vkCmdClearColorImage(cmd, image->value, image->current_layout, &clear, 1, &dst_range);
@@ -812,12 +811,12 @@ namespace vk
return result;
}
vk::image_view* texture_cache::generate_3d_from_2d_images(vk::command_buffer& cmd, u32 gcm_format, u16 width, u16 height, u16 depth,
const rsx::simple_array<copy_region_descriptor>& sections_to_copy, const rsx::texture_channel_remap_t& remap_vector)
vk::image_view* texture_cache::generate_3d_from_2d_images(vk::command_buffer& cmd, const deferred_subresource& desc)
{
const auto& sections_to_copy = desc.sections_to_copy;
auto _template = get_template_from_collection_impl(sections_to_copy);
auto result = create_temporary_subresource_view_impl(cmd, _template, VK_IMAGE_TYPE_3D,
VK_IMAGE_VIEW_TYPE_3D, gcm_format, 0, 0, width, height, depth, 1, remap_vector, false);
VK_IMAGE_VIEW_TYPE_3D, desc.gcm_format, 0, 0, desc.width, desc.height, desc.depth, 1, desc.remap, false);
if (!result)
{
@@ -830,7 +829,12 @@ namespace vk
VkImageSubresourceRange dst_range = { dst_aspect, 0, 1, 0, 1 };
vk::change_image_layout(cmd, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, dst_range);
if (!(dst_aspect & VK_IMAGE_ASPECT_DEPTH_BIT))
if (desc.force_bg_load)
{
// The memory load covers the whole image, no need to clear it first
initialize_subresource_from_memory(cmd, image, desc, rsx::texture_dimension_extended::texture_dimension_3d);
}
else if (!(dst_aspect & VK_IMAGE_ASPECT_DEPTH_BIT))
{
VkClearColorValue clear = {};
vkCmdClearColorImage(cmd, image->value, image->current_layout, &clear, 1, &dst_range);
@@ -855,12 +859,12 @@ namespace vk
return result;
}
vk::image_view* texture_cache::generate_atlas_from_images(vk::command_buffer& cmd, u32 gcm_format, u16 width, u16 height,
const rsx::simple_array<copy_region_descriptor>& sections_to_copy, const rsx::texture_channel_remap_t& remap_vector)
vk::image_view* texture_cache::generate_atlas_from_images(vk::command_buffer& cmd, const deferred_subresource& desc)
{
const auto& sections_to_copy = desc.sections_to_copy;
auto _template = get_template_from_collection_impl(sections_to_copy);
auto result = create_temporary_subresource_view_impl(cmd, _template, VK_IMAGE_TYPE_2D,
VK_IMAGE_VIEW_TYPE_2D, gcm_format, 0, 0, width, height, 1, 1, remap_vector, false);
VK_IMAGE_VIEW_TYPE_2D, desc.gcm_format, 0, 0, desc.width, desc.height, 1, 1, desc.remap, false);
if (!result)
{
@@ -873,7 +877,12 @@ namespace vk
VkImageSubresourceRange dst_range = { dst_aspect, 0, 1, 0, 1 };
vk::change_image_layout(cmd, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, dst_range);
if (sections_to_copy[0].dst_w != width || sections_to_copy[0].dst_h != height)
if (desc.force_bg_load)
{
// The memory load covers the whole image, no need to clear it first
initialize_subresource_from_memory(cmd, image, desc, rsx::texture_dimension_extended::texture_dimension_2d);
}
else if (sections_to_copy[0].dst_w != desc.width || sections_to_copy[0].dst_h != desc.height)
{
if (!(dst_aspect & VK_IMAGE_ASPECT_DEPTH_BIT))
{
@@ -901,13 +910,13 @@ namespace vk
return result;
}
vk::image_view* texture_cache::generate_2d_mipmaps_from_images(vk::command_buffer& cmd, u32 gcm_format, u16 width, u16 height,
const rsx::simple_array<copy_region_descriptor>& sections_to_copy, const rsx::texture_channel_remap_t& remap_vector)
vk::image_view* texture_cache::generate_2d_mipmaps_from_images(vk::command_buffer& cmd, const deferred_subresource& desc)
{
const auto& sections_to_copy = desc.sections_to_copy;
const auto mipmaps = ::narrow<u8>(sections_to_copy.size());
auto _template = get_template_from_collection_impl(sections_to_copy);
auto result = create_temporary_subresource_view_impl(cmd, _template, VK_IMAGE_TYPE_2D,
VK_IMAGE_VIEW_TYPE_2D, gcm_format, 0, 0, width, height, 1, mipmaps, remap_vector, false);
VK_IMAGE_VIEW_TYPE_2D, desc.gcm_format, 0, 0, desc.width, desc.height, 1, mipmaps, desc.remap, false);
if (!result)
{
@@ -920,7 +929,12 @@ namespace vk
VkImageSubresourceRange dst_range = { dst_aspect, 0, mipmaps, 0, 1 };
vk::change_image_layout(cmd, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, dst_range);
if (!(dst_aspect & VK_IMAGE_ASPECT_DEPTH_BIT))
if (desc.force_bg_load)
{
// The memory load covers the whole image, no need to clear it first
initialize_subresource_from_memory(cmd, image, desc, rsx::texture_dimension_extended::texture_dimension_2d);
}
else if (!(dst_aspect & VK_IMAGE_ASPECT_DEPTH_BIT))
{
VkClearColorValue clear = {};
vkCmdClearColorImage(cmd, image->value, image->current_layout, &clear, 1, &dst_range);
@@ -955,6 +969,14 @@ namespace vk
vk::get_resource_manager()->dispose(disposable);
}
void texture_cache::initialize_subresource_from_memory(vk::command_buffer& cmd, vk::image* dst, const deferred_subresource& desc, rsx::texture_dimension_extended type) const
{
const auto subresources_layout = rsx::get_subresources_layout(desc, type);
const u16 layer_count = (type == rsx::texture_dimension_extended::texture_dimension_cubemap) ? 6 : 1;
vk::upload_image(cmd, dst, subresources_layout, desc.gcm_format, desc.swizzled, layer_count,
dst->aspect(), *vk::get_upload_heap(), desc.pitch, vk::upload_contents_inline);
}
void texture_cache::update_image_contents(vk::command_buffer& cmd, vk::image_view* dst_view, vk::image* src, u16 width, u16 height)
{
rsx::simple_array<copy_region_descriptor> region =
+7 -13
View File
@@ -456,26 +456,20 @@ namespace vk
vk::image_view* create_temporary_subresource_view_impl(vk::command_buffer& cmd, vk::image* source, VkImageType image_type, VkImageViewType view_type,
u32 gcm_format, u16 x, u16 y, u16 w, u16 h, u16 d, u8 mips, const rsx::texture_channel_remap_t& remap_vector, bool copy);
vk::image_view* create_temporary_subresource_view(vk::command_buffer& cmd, vk::image* source, u32 gcm_format,
u16 x, u16 y, u16 w, u16 h, const rsx::texture_channel_remap_t& remap_vector) override;
vk::image_view* create_temporary_subresource_view(vk::command_buffer& cmd, const deferred_subresource& desc) override;
vk::image_view* create_temporary_subresource_view(vk::command_buffer& cmd, vk::image** source, u32 gcm_format,
u16 x, u16 y, u16 w, u16 h, const rsx::texture_channel_remap_t& remap_vector) override;
vk::image_view* generate_cubemap_from_images(vk::command_buffer& cmd, const deferred_subresource& desc) override;
vk::image_view* generate_cubemap_from_images(vk::command_buffer& cmd, u32 gcm_format, u16 size,
const rsx::simple_array<copy_region_descriptor>& sections_to_copy, const rsx::texture_channel_remap_t& remap_vector) override;
vk::image_view* generate_3d_from_2d_images(vk::command_buffer& cmd, const deferred_subresource& desc) override;
vk::image_view* generate_3d_from_2d_images(vk::command_buffer& cmd, u32 gcm_format, u16 width, u16 height, u16 depth,
const rsx::simple_array<copy_region_descriptor>& sections_to_copy, const rsx::texture_channel_remap_t& remap_vector) override;
vk::image_view* generate_atlas_from_images(vk::command_buffer& cmd, const deferred_subresource& desc) override;
vk::image_view* generate_atlas_from_images(vk::command_buffer& cmd, u32 gcm_format, u16 width, u16 height,
const rsx::simple_array<copy_region_descriptor>& sections_to_copy, const rsx::texture_channel_remap_t& remap_vector) override;
vk::image_view* generate_2d_mipmaps_from_images(vk::command_buffer& cmd, u32 gcm_format, u16 width, u16 height,
const rsx::simple_array<copy_region_descriptor>& sections_to_copy, const rsx::texture_channel_remap_t& remap_vector) override;
vk::image_view* generate_2d_mipmaps_from_images(vk::command_buffer& cmd, const deferred_subresource& desc) override;
void release_temporary_subresource(vk::image_view* view) override;
void initialize_subresource_from_memory(vk::command_buffer& cmd, vk::image* dst, const deferred_subresource& desc, rsx::texture_dimension_extended type) const;
void update_image_contents(vk::command_buffer& cmd, vk::image_view* dst_view, vk::image* src, u16 width, u16 height) override;
cached_texture_section* create_new_texture(vk::command_buffer& cmd, const utils::address_range32& rsx_range, u16 width, u16 height, u16 depth, u16 mipmaps, u32 pitch,
+3 -1
View File
@@ -4869,7 +4869,9 @@ utils::serial* Emulator::DeserialManager() const
bool Emulator::IsVsh()
{
return g_ps3_process_info.self_info.valid && (g_ps3_process_info.self_info.prog_id_hdr.program_authority_id >> 36 == 0x1070000); // Not only VSH but also most CoreOS LV2 SELFs need the special treatment
const auto process = &g_ps3_process_info;
return process->self_info.valid && (process->self_info.prog_id_hdr.program_authority_id == 0x10700005FF000001L); // VSH.self ID
}
bool Emulator::IsValidSfb(const std::string& path)
+8
View File
@@ -189,6 +189,7 @@ enum class localized_string_id
HOME_MENU_RESUME,
HOME_MENU_FRIENDS,
HOME_MENU_FRIENDS_REQUESTS,
HOME_MENU_FRIENDS_GAME_INVITES,
HOME_MENU_FRIENDS_BLOCKED,
HOME_MENU_FRIENDS_STATUS_ONLINE,
HOME_MENU_FRIENDS_STATUS_OFFLINE,
@@ -202,6 +203,9 @@ enum class localized_string_id
HOME_MENU_FRIENDS_CANCEL_REQUEST_MSG,
HOME_MENU_FRIENDS_REJECT_REQUEST_MSG,
HOME_MENU_FRIENDS_REJECT_REQUEST,
HOME_MENU_FRIENDS_ACCEPT_GAME_INVITE_MSG,
HOME_MENU_FRIENDS_REJECT_GAME_INVITE_MSG,
HOME_MENU_FRIENDS_REJECT_GAME_INVITE,
HOME_MENU_FRIENDS_NEXT_LIST,
HOME_MENU_SETTINGS,
HOME_MENU_SETTINGS_SAVE,
@@ -303,6 +307,10 @@ enum class localized_string_id
HOME_MENU_TROPHY_GRADE_SILVER,
HOME_MENU_TROPHY_GRADE_GOLD,
HOME_MENU_TROPHY_GRADE_PLATINUM,
HOME_MENU_TROPHY_SORT_GAME_DEFAULT,
HOME_MENU_TROPHY_SORT_NOT_EARNED,
HOME_MENU_TROPHY_SORT_EARNED_DATE,
HOME_MENU_TROPHY_SORT_GRADE,
AUDIO_MUTED,
AUDIO_UNMUTED,
-1
View File
@@ -187,7 +187,6 @@ struct cfg_root : cfg::node
node_vk(cfg::node* _this) : cfg::node(_this, "Vulkan") {}
cfg::string adapter{ this, "Adapter" };
cfg::_bool force_primitive_restart{ this, "Force primitive restart flag" };
cfg::_enum<vk_exclusive_fs_mode> exclusive_fullscreen_mode{ this, "Exclusive Fullscreen Mode", vk_exclusive_fs_mode::unspecified};
cfg::_bool asynchronous_texture_streaming{ this, "Asynchronous Texture Streaming", false };
cfg::_enum<vk_gpu_scheduler_mode> asynchronous_scheduler{ this, "Asynchronous Queue Scheduler", vk_gpu_scheduler_mode::safe };
+49 -31
View File
@@ -70,9 +70,12 @@ DYNAMIC_IMPORT("ntdll.dll", NtSetTimerResolution, NTSTATUS(ULONG DesiredResoluti
#include "Emu/System.h"
#include "Emu/system_config.h"
#include "Emu/system_utils.hpp"
#include "Emu/savestate_utils.hpp"
#include "Emu/RSX/Overlays/overlay_message.h"
#include <thread>
#include <charconv>
#include <regex>
#include "util/sysinfo.hpp"
@@ -384,36 +387,37 @@ private:
};
// Arguments that force a headless application (need to be checked in create_application)
constexpr auto arg_headless = "headless";
constexpr auto arg_decrypt = "decrypt";
constexpr auto arg_headless = "headless";
constexpr auto arg_decrypt = "decrypt";
// Arguments that can be used with a gui application
constexpr auto arg_no_gui = "no-gui";
constexpr auto arg_fullscreen = "fullscreen"; // only useful with no-gui
constexpr auto arg_gs_screen = "game-screen";
constexpr auto arg_high_dpi = "hidpi";
constexpr auto arg_rounding = "dpi-rounding";
constexpr auto arg_styles = "styles";
constexpr auto arg_style = "style";
constexpr auto arg_stylesheet = "stylesheet";
constexpr auto arg_config = "config";
constexpr auto arg_input_config = "input-config"; // only useful with no-gui
constexpr auto arg_q_debug = "qDebug";
constexpr auto arg_error = "error";
constexpr auto arg_updating = "updating";
constexpr auto arg_user_id = "user-id";
constexpr auto arg_installfw = "installfw";
constexpr auto arg_installpkg = "installpkg";
constexpr auto arg_savestate = "savestate";
constexpr auto arg_rsx_capture = "rsx-capture";
constexpr auto arg_timer = "high-res-timer";
constexpr auto arg_verbose_curl = "verbose-curl";
constexpr auto arg_any_location = "allow-any-location";
constexpr auto arg_codecs = "codecs";
constexpr auto arg_no_gui = "no-gui";
constexpr auto arg_fullscreen = "fullscreen"; // only useful with no-gui
constexpr auto arg_gs_screen = "game-screen";
constexpr auto arg_high_dpi = "hidpi";
constexpr auto arg_rounding = "dpi-rounding";
constexpr auto arg_styles = "styles";
constexpr auto arg_style = "style";
constexpr auto arg_stylesheet = "stylesheet";
constexpr auto arg_config = "config";
constexpr auto arg_input_config = "input-config"; // only useful with no-gui
constexpr auto arg_q_debug = "qDebug";
constexpr auto arg_error = "error";
constexpr auto arg_updating = "updating";
constexpr auto arg_user_id = "user-id";
constexpr auto arg_installfw = "installfw";
constexpr auto arg_installpkg = "installpkg";
constexpr auto arg_savestate = "savestate";
constexpr auto arg_last_savestate = "last-savestate";
constexpr auto arg_rsx_capture = "rsx-capture";
constexpr auto arg_timer = "high-res-timer";
constexpr auto arg_verbose_curl = "verbose-curl";
constexpr auto arg_any_location = "allow-any-location";
constexpr auto arg_codecs = "codecs";
#ifdef _WIN32
constexpr auto arg_stdout = "stdout";
constexpr auto arg_stderr = "stderr";
constexpr auto arg_stdout = "stdout";
constexpr auto arg_stderr = "stderr";
#endif
constexpr auto arg_emulation_barrier = "";
@@ -839,6 +843,8 @@ int run_rpcs3(int argc, char** argv)
parser.addOption(user_id_option);
const QCommandLineOption savestate_option(arg_savestate, "Path for directly loading a savestate.", "path", "");
parser.addOption(savestate_option);
const QCommandLineOption last_savestate_option(arg_last_savestate, "Loading the last savestate of a game.", "path", "Title-ID or path");
parser.addOption(last_savestate_option);
const QCommandLineOption rsx_capture_option(arg_rsx_capture, "Path for directly loading an rsx capture.", "path", "");
parser.addOption(rsx_capture_option);
parser.addOption(QCommandLineOption(arg_q_debug, "Log qDebug to RPCS3.log."));
@@ -1196,14 +1202,26 @@ int run_rpcs3(int argc, char** argv)
}
}
if (parser.isSet(arg_savestate))
if (parser.isSet(arg_savestate) || parser.isSet(arg_last_savestate))
{
const std::string savestate_path = parser.value(savestate_option).toStdString();
sys_log.notice("Booting savestate from command line: %s", savestate_path);
std::string savestate_path;
if (!fs::is_file(savestate_path))
if (parser.isSet(arg_savestate))
{
report_fatal_error(fmt::format("No savestate file found: %s", savestate_path));
savestate_path = parser.value(savestate_option).toStdString();
sys_log.notice("Booting savestate from command line: path='%s'", savestate_path);
}
else
{
const std::string serial_or_path = parser.value(last_savestate_option).toStdString();
const bool is_serial = std::regex_match(serial_or_path, std::regex(R"(^[A-Z]{4}\d{5}$)"));
savestate_path = get_savestate_file(is_serial ? serial_or_path : "", is_serial ? "" : serial_or_path, 1);
sys_log.notice("Booting last savestate from command line: game='%s', path='%s'", serial_or_path, savestate_path);
}
if (!is_savestate_compatible(savestate_path))
{
report_fatal_error(fmt::format("No savestate file found or savestate not compatible: path='%s'", savestate_path));
}
Emu.CallFromMainThread([path = savestate_path]()
+17
View File
@@ -230,6 +230,9 @@
<ClCompile Include="QTGeneratedFiles\Debug\moc_anaglyph_settings_dialog.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="QTGeneratedFiles\Debug\moc_guest_memory_dumper.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="QTGeneratedFiles\Debug\moc_breakpoint_list.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
</ClCompile>
@@ -542,6 +545,9 @@
<ClCompile Include="QTGeneratedFiles\Release\moc_anaglyph_settings_dialog.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="QTGeneratedFiles\Release\moc_guest_memory_dumper.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="QTGeneratedFiles\Release\moc_breakpoint_list.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
</ClCompile>
@@ -845,6 +851,7 @@
<ClCompile Include="rpcs3qt\call_stack_list.cpp" />
<ClCompile Include="rpcs3qt\camera_settings_dialog.cpp" />
<ClCompile Include="rpcs3qt\emu_settings_type.cpp" />
<ClCompile Include="rpcs3qt\guest_memory_dumper.cpp" />
<ClCompile Include="rpcs3qt\gui_game_info.cpp" />
<ClCompile Include="rpcs3qt\log_level_dialog.cpp" />
<ClCompile Include="rpcs3qt\permissions.cpp" />
@@ -1208,6 +1215,16 @@
<Outputs Condition="'$(Configuration)|$(Platform)'=='Release|x64'">.\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp</Outputs>
<Command Condition="'$(Configuration)|$(Platform)'=='Release|x64'">"$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DWITH_DISCORD_RPC -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DNDEBUG -DQT_CONCURRENT_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\wolfssl\wolfssl" "-I.\..\3rdparty\curl\curl\include" "-I.\..\3rdparty\libusb\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtCore" "-I.\release" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtConcurrent"</Command>
</CustomBuild>
<CustomBuild Include="rpcs3qt\guest_memory_dumper.h">
<AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(QTDIR)\bin\moc.exe;%(FullPath)</AdditionalInputs>
<Message Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Moc%27ing %(Identity)...</Message>
<Outputs Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">.\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp</Outputs>
<Command Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">"$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DQT_CONCURRENT_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\wolfssl\wolfssl" "-I.\..\3rdparty\curl\curl\include" "-I.\..\3rdparty\libusb\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtCore" "-I.\debug" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtConcurrent"</Command>
<AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(QTDIR)\bin\moc.exe;%(FullPath)</AdditionalInputs>
<Message Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Moc%27ing %(Identity)...</Message>
<Outputs Condition="'$(Configuration)|$(Platform)'=='Release|x64'">.\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp</Outputs>
<Command Condition="'$(Configuration)|$(Platform)'=='Release|x64'">"$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DWITH_DISCORD_RPC -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DNDEBUG -DQT_CONCURRENT_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\wolfssl\wolfssl" "-I.\..\3rdparty\curl\curl\include" "-I.\..\3rdparty\libusb\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtCore" "-I.\release" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtConcurrent"</Command>
</CustomBuild>
<ClInclude Include="rpcs3qt\breakpoint_handler.h" />
<CustomBuild Include="rpcs3qt\breakpoint_list.h">
<AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(QTDIR)\bin\moc.exe;%(FullPath)</AdditionalInputs>
+19 -9
View File
@@ -585,6 +585,12 @@
<ClCompile Include="QTGeneratedFiles\Release\moc_anaglyph_settings_dialog.cpp">
<Filter>Generated Files\Release</Filter>
</ClCompile>
<ClCompile Include="QTGeneratedFiles\Debug\moc_guest_memory_dumper.cpp">
<Filter>Generated Files\Debug</Filter>
</ClCompile>
<ClCompile Include="QTGeneratedFiles\Release\moc_guest_memory_dumper.cpp">
<Filter>Generated Files\Release</Filter>
</ClCompile>
<ClCompile Include="QTGeneratedFiles\Debug\moc_breakpoint_list.cpp">
<Filter>Generated Files\Debug</Filter>
</ClCompile>
@@ -1314,6 +1320,9 @@
<ClCompile Include="rpcs3qt\anaglyph_settings_dialog.cpp">
<Filter>Gui\settings</Filter>
</ClCompile>
<ClCompile Include="rpcs3qt\guest_memory_dumper.cpp">
<Filter>Gui\utils</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="Input\ds4_pad_handler.h">
@@ -1565,7 +1574,7 @@
<ClInclude Include="rpcs3qt\steam_utils.h">
<Filter>Gui\utils</Filter>
</ClInclude>
<ClInclude Include="rpcs3qt\anaglyph_settings_dialog.h">
<ClInclude Include="rpcs3qt\render_creator.h">
<Filter>Gui\settings</Filter>
</ClInclude>
</ItemGroup>
@@ -1783,9 +1792,6 @@
<CustomBuild Include="rpcs3qt\microphone_creator.h">
<Filter>Gui\settings</Filter>
</CustomBuild>
<CustomBuild Include="rpcs3qt\render_creator.h">
<Filter>Gui\settings</Filter>
</CustomBuild>
<CustomBuild Include="rpcs3qt\patch_manager_dialog.h">
<Filter>Gui\patch manager</Filter>
</CustomBuild>
@@ -1927,6 +1933,15 @@
<CustomBuild Include="rpcs3qt\game_list_actions.h">
<Filter>Gui\game list</Filter>
</CustomBuild>
<CustomBuild Include="rpcs3qt\content_integrity.h">
<Filter>Gui\game list</Filter>
</CustomBuild>
<CustomBuild Include="rpcs3qt\anaglyph_settings_dialog.h">
<Filter>Gui\settings</Filter>
</CustomBuild>
<CustomBuild Include="rpcs3qt\guest_memory_dumper.h">
<Filter>Gui\utils</Filter>
</CustomBuild>
</ItemGroup>
<ItemGroup>
<Image Include="rpcs3.ico" />
@@ -2082,9 +2097,4 @@
<Filter>buildfiles\cmake</Filter>
</Text>
</ItemGroup>
<ItemGroup>
<CustomBuild Include="rpcs3qt\content_integrity.h">
<Filter>Gui\game list</Filter>
</CustomBuild>
</ItemGroup>
</Project>
+1 -1
View File
@@ -26,7 +26,7 @@ namespace rpcs3
const utils::version& get_version()
{
static constexpr utils::version version{ 0, 0, 41, utils::version_type::alpha, 1, RPCS3_GIT_VERSION };
static constexpr utils::version version{ 0, 0, 42, utils::version_type::alpha, 1, RPCS3_GIT_VERSION };
return version;
}
+1
View File
@@ -43,6 +43,7 @@ add_library(rpcs3_ui STATIC
game_list_grid.cpp
game_list_grid_item.cpp
game_list_table.cpp
guest_memory_dumper.cpp
gui_application.cpp
gl_gs_frame.cpp
gs_frame.cpp
+11 -11
View File
@@ -1,4 +1,5 @@
#include "game_compatibility.h"
#include "gui_application.h"
#include "gui_settings.h"
#include "downloader.h"
#include "localized.h"
@@ -268,12 +269,13 @@ compat::package_info game_compatibility::GetPkgInfo(const QString& pkg_path, con
const psf::registry& psf = reader.get_psf();
// TODO: localization of title and changelog
const std::string title_key = "TITLE";
const std::string changelog_key = "paramhip";
const std::string localized_title_key = fmt::format("TITLE_%02d", gui_application::get_language_id());
const std::string localized_changelog_key = fmt::format("paramhip_%02d", gui_application::get_language_id());
std::string_view localized_title = psf::get_string(psf, localized_title_key);
info.path = pkg_path;
info.title = QString::fromStdString(std::string(psf::get_string(psf, title_key))); // Let's read this from the psf first
info.title = QString::fromStdString(std::string(localized_title.empty() ? psf::get_string(psf, "TITLE") : localized_title));
info.title_id = QString::fromStdString(std::string(psf::get_string(psf, "TITLE_ID")));
info.category = QString::fromStdString(std::string(psf::get_string(psf, "CATEGORY")));
info.version = QString::fromStdString(std::string(psf::get_string(psf, "APP_VER")));
@@ -298,15 +300,13 @@ compat::package_info game_compatibility::GetPkgInfo(const QString& pkg_path, con
if (info.category == "GD")
{
// For now let's assume that PS3 Game Data packages are always updates or DLC.
// Update packages always seem to have an APP_VER, so let's say it's a DLC otherwise.
// Ideally this would simply be the package content type, but I am too lazy to implement this right now.
if (info.version.isEmpty())
if (reader.get_metadata().package_type & pkg_flag::PKG_FLAG_PATCH)
{
info.type = compat::package_type::dlc;
info.type = compat::package_type::update;
}
else
{
info.type = compat::package_type::update;
info.type = compat::package_type::dlc;
}
}
}
@@ -327,12 +327,12 @@ compat::package_info game_compatibility::GetPkgInfo(const QString& pkg_path, con
{
if (info.version.toStdString() == package.version)
{
if (const std::string localized_title = package.get_title(title_key); !localized_title.empty())
if (const std::string localized_title = package.get_title(localized_title_key); !localized_title.empty())
{
info.title = QString::fromStdString(localized_title);
}
if (const std::string localized_changelog = package.get_changelog(changelog_key); !localized_changelog.empty())
if (const std::string localized_changelog = package.get_changelog(localized_changelog_key); !localized_changelog.empty())
{
info.changelog = QString::fromStdString(localized_changelog);
}
+2 -2
View File
@@ -35,7 +35,7 @@ namespace compat
std::vector<pkg_changelog> changelogs;
std::vector<pkg_title> titles;
std::string get_changelog(const std::string& type) const
std::string get_changelog(std::string_view type) const
{
if (const auto it = std::find_if(changelogs.cbegin(), changelogs.cend(), [type](const pkg_changelog& cl) { return cl.type == type; });
it != changelogs.cend())
@@ -50,7 +50,7 @@ namespace compat
return "";
}
std::string get_title(const std::string& type) const
std::string get_title(std::string_view type) const
{
if (const auto it = std::find_if(titles.cbegin(), titles.cend(), [type](const pkg_title& t) { return t.type == type; });
it != titles.cend())
+85 -9
View File
@@ -327,9 +327,15 @@ void game_list_context_menu::show_single_selection_context_menu(const game_info&
manage_game_menu->addSeparator();
// Hide/rename game in game list
QAction* hide_serial = manage_game_menu->addAction(tr("&Hide In Game List"));
hide_serial->setCheckable(true);
hide_serial->setChecked(m_game_list_frame->hidden_list().contains(QString::fromStdString(serial)));
QAction* hide_hidden_serial = manage_game_menu->addAction(tr("&Hide Game In Game List"));
hide_hidden_serial->setCheckable(true);
hide_hidden_serial->setChecked(m_game_list_frame->hidden_list().contains(QString::fromStdString(serial)));
QAction* hide_broken_serial = manage_game_menu->addAction(tr("&Hide Broken Game In Game List"));
hide_broken_serial->setCheckable(true);
hide_broken_serial->setChecked(m_game_list_frame->broken_list().contains(QString::fromStdString(serial)));
QAction* hide_completed_serial = manage_game_menu->addAction(tr("&Hide Completed Game In Game List"));
hide_completed_serial->setCheckable(true);
hide_completed_serial->setChecked(m_game_list_frame->completed_list().contains(QString::fromStdString(serial)));
QAction* rename_title = manage_game_menu->addAction(tr("&Rename In Game List"));
// Edit tooltip notes/reset time played
@@ -694,7 +700,7 @@ void game_list_context_menu::show_single_selection_context_menu(const game_info&
m_game_list_frame->ShowCustomConfigIcon(gameinfo);
}
});
connect(hide_serial, &QAction::triggered, m_game_list_frame, [this, serial = QString::fromStdString(serial)](bool checked)
connect(hide_hidden_serial, &QAction::triggered, m_game_list_frame, [this, serial = QString::fromStdString(serial)](bool checked)
{
if (checked)
m_game_list_frame->hidden_list().insert(serial);
@@ -704,6 +710,26 @@ void game_list_context_menu::show_single_selection_context_menu(const game_info&
m_gui_settings->SetValue(gui::gl_hidden_list, QStringList(m_game_list_frame->hidden_list().values()));
m_game_list_frame->Refresh();
});
connect(hide_broken_serial, &QAction::triggered, m_game_list_frame, [this, serial = QString::fromStdString(serial)](bool checked)
{
if (checked)
m_game_list_frame->broken_list().insert(serial);
else
m_game_list_frame->broken_list().remove(serial);
m_gui_settings->SetValue(gui::gl_broken_list, QStringList(m_game_list_frame->broken_list().values()));
m_game_list_frame->Refresh();
});
connect(hide_completed_serial, &QAction::triggered, m_game_list_frame, [this, serial = QString::fromStdString(serial)](bool checked)
{
if (checked)
m_game_list_frame->completed_list().insert(serial);
else
m_game_list_frame->completed_list().remove(serial);
m_gui_settings->SetValue(gui::gl_completed_list, QStringList(m_game_list_frame->completed_list().values()));
m_game_list_frame->Refresh();
});
connect(create_cpu_cache, &QAction::triggered, m_game_list_frame, [this, gameinfo]
{
if (m_gui_settings->GetBootConfirmation(m_game_list_frame))
@@ -925,10 +951,10 @@ void game_list_context_menu::show_multi_selection_context_menu(const std::vector
manage_game_menu->addSeparator();
// Hide game in game list
QAction* hide_serial = manage_game_menu->addAction(tr("&Hide In Game List"));
connect(hide_serial, &QAction::triggered, m_game_list_frame, [this, games]()
QAction* hide_hidden_serial = manage_game_menu->addAction(tr("&Hide Game In Game List"));
connect(hide_hidden_serial, &QAction::triggered, m_game_list_frame, [this, games]()
{
if (QMessageBox::question(m_game_list_frame, tr("Confirm Hiding"), tr("Hide in game list?")) != QMessageBox::Yes)
if (QMessageBox::question(m_game_list_frame, tr("Confirm Hiding"), tr("Hide game in game list?")) != QMessageBox::Yes)
return;
for (const auto& game : games)
@@ -939,10 +965,38 @@ void game_list_context_menu::show_multi_selection_context_menu(const std::vector
m_gui_settings->SetValue(gui::gl_hidden_list, QStringList(m_game_list_frame->hidden_list().values()));
m_game_list_frame->Refresh();
});
QAction* hide_broken_serial = manage_game_menu->addAction(tr("&Hide Broken Game In Game List"));
connect(hide_broken_serial, &QAction::triggered, m_game_list_frame, [this, games]()
{
if (QMessageBox::question(m_game_list_frame, tr("Confirm Hiding"), tr("Hide broken game in game list?")) != QMessageBox::Yes)
return;
for (const auto& game : games)
{
m_game_list_frame->broken_list().insert(QString::fromStdString(game->info.serial));
}
m_gui_settings->SetValue(gui::gl_broken_list, QStringList(m_game_list_frame->broken_list().values()));
m_game_list_frame->Refresh();
});
QAction* hide_completed_serial = manage_game_menu->addAction(tr("&Hide Completed Game In Game List"));
connect(hide_completed_serial, &QAction::triggered, m_game_list_frame, [this, games]()
{
if (QMessageBox::question(m_game_list_frame, tr("Confirm Hiding"), tr("Hide completed game in game list?")) != QMessageBox::Yes)
return;
for (const auto& game : games)
{
m_game_list_frame->completed_list().insert(QString::fromStdString(game->info.serial));
}
m_gui_settings->SetValue(gui::gl_completed_list, QStringList(m_game_list_frame->completed_list().values()));
m_game_list_frame->Refresh();
});
// Show game in game list
QAction* show_serial = manage_game_menu->addAction(tr("&Show In Game List"));
connect(show_serial, &QAction::triggered, m_game_list_frame, [this, games]()
QAction* show_hidden_serial = manage_game_menu->addAction(tr("&Show Hidden Game In Game List"));
connect(show_hidden_serial, &QAction::triggered, m_game_list_frame, [this, games]()
{
for (const auto& game : games)
{
@@ -952,6 +1006,28 @@ void game_list_context_menu::show_multi_selection_context_menu(const std::vector
m_gui_settings->SetValue(gui::gl_hidden_list, QStringList(m_game_list_frame->hidden_list().values()));
m_game_list_frame->Refresh();
});
QAction* show_broken_serial = manage_game_menu->addAction(tr("&Show Broken Game In Game List"));
connect(show_broken_serial, &QAction::triggered, m_game_list_frame, [this, games]()
{
for (const auto& game : games)
{
m_game_list_frame->broken_list().remove(QString::fromStdString(game->info.serial));
}
m_gui_settings->SetValue(gui::gl_broken_list, QStringList(m_game_list_frame->broken_list().values()));
m_game_list_frame->Refresh();
});
QAction* show_completed_serial = manage_game_menu->addAction(tr("&Show Completed Game In Game List"));
connect(show_completed_serial, &QAction::triggered, m_game_list_frame, [this, games]()
{
for (const auto& game : games)
{
m_game_list_frame->completed_list().remove(QString::fromStdString(game->info.serial));
}
m_gui_settings->SetValue(gui::gl_completed_list, QStringList(m_game_list_frame->completed_list().values()));
m_game_list_frame->Refresh();
});
manage_game_menu->addSeparator();
+20 -2
View File
@@ -55,6 +55,8 @@ game_list_frame::game_list_frame(std::shared_ptr<gui_settings> gui_settings, std
m_col_sort_order = m_gui_settings->GetValue(gui::gl_sortAsc).toBool() ? Qt::AscendingOrder : Qt::DescendingOrder;
m_sort_column = m_gui_settings->GetValue(gui::gl_sortCol).toInt();
m_hidden_list = gui::utils::list_to_set(m_gui_settings->GetValue(gui::gl_hidden_list).toStringList());
m_broken_list = gui::utils::list_to_set(m_gui_settings->GetValue(gui::gl_broken_list).toStringList());
m_completed_list = gui::utils::list_to_set(m_gui_settings->GetValue(gui::gl_completed_list).toStringList());
m_old_layout_is_list = m_is_list_layout;
@@ -336,7 +338,9 @@ bool game_list_frame::IsEntryVisible(const game_info& game, bool search_fallback
};
const QString serial = QString::fromStdString(game->info.serial);
const bool is_visible = m_show_hidden || !m_hidden_list.contains(serial);
const bool is_visible = (m_show_hidden || !m_hidden_list.contains(serial)) &&
(m_show_broken || !m_broken_list.contains(serial)) &&
(m_show_completed || !m_completed_list.contains(serial));
return is_visible && matches_category() && SearchMatchesApp(QString::fromStdString(game->info.name), serial, search_fallback);
}
@@ -1067,9 +1071,13 @@ void game_list_frame::OnRefreshFinished()
return title1.toLower() < title2.toLower();
});
// clean up hidden games list
// clean up hidden lists (Hide, Broken, Completed)
m_hidden_list.intersect(m_serials);
m_gui_settings->SetValue(gui::gl_hidden_list, QStringList(m_hidden_list.values()));
m_broken_list.intersect(m_serials);
m_gui_settings->SetValue(gui::gl_broken_list, QStringList(m_broken_list.values()));
m_completed_list.intersect(m_serials);
m_gui_settings->SetValue(gui::gl_completed_list, QStringList(m_completed_list.values()));
m_serials.clear();
m_path_list.clear();
m_path_entries.clear();
@@ -1285,6 +1293,16 @@ void game_list_frame::SetShowHidden(bool show)
m_show_hidden = show;
}
void game_list_frame::SetShowBroken(bool show)
{
m_show_broken = show;
}
void game_list_frame::SetShowCompleted(bool show)
{
m_show_completed = show;
}
void game_list_frame::SetListMode(bool is_list)
{
m_old_layout_is_list = m_is_list_layout;
+10
View File
@@ -56,6 +56,10 @@ public:
void SetShowHidden(bool show);
void SetShowBroken(bool show);
void SetShowCompleted(bool show);
content_integrity* GetIsoIntegrity() const { return ensure(m_iso_integrity); }
content_integrity* GetPsnContentIntegrity() const { return ensure(m_psn_content_integrity); }
content_integrity* GetPsnDlcIntegrity() const { return ensure(m_psn_dlc_integrity); }
@@ -70,6 +74,8 @@ public:
std::map<QString, QString>& notes() { return m_notes; }
std::map<QString, QString>& titles() { return m_titles; }
QSet<QString>& hidden_list() { return m_hidden_list; }
QSet<QString>& broken_list() { return m_broken_list; }
QSet<QString>& completed_list() { return m_completed_list; }
bool IsEntryVisible(const game_info& game, bool search_fallback = false) const;
@@ -206,6 +212,10 @@ private:
QFutureWatcher<void> m_refresh_watcher;
QSet<QString> m_hidden_list;
bool m_show_hidden{false};
QSet<QString> m_broken_list;
bool m_show_broken{false};
QSet<QString> m_completed_list;
bool m_show_completed{false};
// Search
QString m_search_text;
+2 -2
View File
@@ -527,7 +527,7 @@ void gs_frame::toggle_recording()
m_video_encoder->set_path(video_path);
m_video_encoder->set_framerate(g_cfg_recording.video.framerate);
m_video_encoder->set_video_bitrate(g_cfg_recording.video.video_bps);
m_video_encoder->set_video_codec(g_cfg_recording.video.video_codec);
m_video_encoder->set_video_codec(g_cfg_recording.video.codec_id, g_cfg_recording.video.codec_name.get());
m_video_encoder->set_max_b_frames(g_cfg_recording.video.max_b_frames);
m_video_encoder->set_gop_size(g_cfg_recording.video.gop_size);
m_video_encoder->set_output_format(output_format);
@@ -557,7 +557,7 @@ void gs_frame::toggle_recording()
}
m_video_encoder->set_audio_bitrate(g_cfg_recording.audio.audio_bps);
m_video_encoder->set_audio_codec(g_cfg_recording.audio.audio_codec);
m_video_encoder->set_audio_codec(g_cfg_recording.audio.codec_id, g_cfg_recording.audio.codec_name.get());
m_video_encoder->encode();
if (m_video_encoder->has_error)
+479
View File
@@ -0,0 +1,479 @@
#include "stdafx.h"
#include "guest_memory_dumper.h"
#include "progress_dialog.h"
#include "Emu/Cell/PPUThread.h"
#include "Emu/Cell/SPUThread.h"
#include "Emu/Memory/vm.h"
#include "Emu/RSX/RSXThread.h"
#include "Emu/System.h"
#include <QDateTime>
#include <QDir>
#include <QElapsedTimer>
#include <QFile>
#include <QFileDialog>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QMessageBox>
#include <QPushButton>
#include <QStorageInfo>
LOG_CHANNEL(gui_log, "GUI");
extern void qt_events_aware_op(int repeat_duration_ms, std::function<bool()> wrapped_op);
guest_memory_dumper::guest_memory_dumper(QWidget* parent, bool delete_later)
: QObject(parent), m_parent(parent), m_delete_later(delete_later)
{
}
// Emu.Pause() only requests the pause: every emulated thread drains into its
// wait or stop state asynchronously. The dump must not begin until each PPU,
// SPU and RSX thread has actually settled, or it could capture torn state.
bool guest_memory_dumper::emulated_processors_quiesced()
{
bool quiesced = true;
const auto check_cpu = [&](u32, cpu_thread& cpu)
{
const auto state = +cpu.state;
if (!::is_stopped(state) && !(state & cpu_flag::wait))
{
quiesced = false;
}
};
if (g_fxo->is_init<id_manager::id_map<named_thread<ppu_thread>>>())
{
idm::select<named_thread<ppu_thread>>(check_cpu);
}
if (g_fxo->is_init<id_manager::id_map<named_thread<spu_thread>>>())
{
idm::select<named_thread<spu_thread>>(check_cpu);
}
if (const auto rsx = g_fxo->try_get<rsx::thread>())
{
check_cpu(0, *rsx);
}
return quiesced;
}
QString guest_memory_dumper::hex_u64(u64 value, int width)
{
return QStringLiteral("0x%1").arg(value, width, 16, QLatin1Char('0'));
}
void guest_memory_dumper::dump_guest_memory()
{
if (Emu.IsStopped() || Emu.IsStarting())
{
QMessageBox::warning(m_parent, tr("Dump Guest Memory"), tr("Start a game before creating a guest-memory dump."));
return;
}
const QString default_parent = QString::fromStdString(fs::get_config_dir() + "guest_memory_dumps");
QDir().mkpath(default_parent);
QString title_id = QString::fromStdString(Emu.GetTitleID());
if (title_id.isEmpty())
{
title_id = QStringLiteral("RPCS3");
}
// Remove the output folder again unless the dump ran to completion, so a
// failed or cancelled dump does not leave an empty folder or a partial
// image behind. This is declared before the output file so it destructs
// after the file is closed.
struct cleanup_guard
{
QDir dir;
bool enabled = false;
bool keep = false;
~cleanup_guard()
{
if (enabled && !keep && !dir.removeRecursively())
{
gui_log.error("Could not remove incomplete guest memory dump folder: %s", dir.absolutePath());
}
}
} cleanup;
QString picker_parent = default_parent;
QDir output;
fs::file guest_file;
bool sparse_image = false;
while (!sparse_image)
{
const QString selected_parent = QFileDialog::getExistingDirectory(m_parent, tr("Select Guest Memory Dump Folder"), picker_parent, QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks);
if (selected_parent.isEmpty())
{
return;
}
// The native folder dialog runs its own event loop, so the game may have
// stopped while the user was choosing a destination.
if (Emu.IsStopped() || Emu.IsStarting())
{
QMessageBox::warning(m_parent, tr("Dump Guest Memory"), tr("The game stopped before the guest-memory dump could begin."));
return;
}
const QString timestamp = QDateTime::currentDateTimeUtc().toString(QStringLiteral("yyyyMMdd_HHmmss_zzz"));
const QString folder_stem = QStringLiteral("%1_guest_memory_%2").arg(title_id, timestamp);
QDir parent(selected_parent);
QString folder_name = folder_stem;
for (u32 suffix = 2; parent.exists(folder_name); suffix++)
{
folder_name = QStringLiteral("%1_%2").arg(folder_stem).arg(suffix);
}
if (!parent.mkpath(folder_name))
{
QMessageBox::critical(m_parent, tr("Dump Guest Memory"), tr("Could not create the output folder:\n%0").arg(parent.filePath(folder_name)));
return;
}
output.setPath(parent.filePath(folder_name));
cleanup.dir = output;
cleanup.enabled = true;
const QString guest_path = output.filePath(QStringLiteral("guest_memory.bin"));
if (!guest_file.open(guest_path.toStdString(), fs::rewrite))
{
QMessageBox::critical(m_parent, tr("Dump Guest Memory"), tr("Could not create:\n%0").arg(guest_path));
return;
}
// POSIX file systems create holes for unwritten ranges by default; Windows
// needs the sparse attribute set explicitly.
sparse_image = fs::set_sparse(guest_file);
if (!sparse_image)
{
guest_file.close();
if (!output.removeRecursively())
{
QMessageBox::critical(m_parent, tr("Dump Guest Memory"), tr("The selected destination does not support sparse files, and the incomplete output folder could not be removed:\n%0").arg(output.absolutePath()));
return;
}
cleanup.enabled = false;
QMessageBox message(QMessageBox::Critical, tr("Dump Guest Memory"), tr("The selected destination does not support sparse files. Choose a location on an NTFS volume."), QMessageBox::NoButton, m_parent);
QPushButton* choose_button = message.addButton(tr("Choose Another Location"), QMessageBox::AcceptRole);
message.addButton(QMessageBox::Cancel);
message.setDefaultButton(choose_button);
message.exec();
if (message.clickedButton() != choose_button)
{
return;
}
picker_parent = selected_parent;
}
}
const bool resume_after_dump = Emu.IsRunning();
if (resume_after_dump && !Emu.Pause(false, false))
{
QMessageBox::critical(m_parent, tr("Dump Guest Memory"), tr("Could not pause emulation for a consistent memory snapshot."));
return;
}
struct resume_guard
{
bool enabled = false;
~resume_guard()
{
if (enabled)
{
Emu.Resume();
}
}
} resume{resume_after_dump};
bool quiesced = false;
QElapsedTimer pause_timer;
pause_timer.start();
qt_events_aware_op(5, [&]()
{
quiesced = emulated_processors_quiesced();
return quiesced || pause_timer.elapsed() >= 5000;
});
if (!quiesced)
{
QMessageBox::critical(m_parent, tr("Dump Guest Memory"), tr("One or more emulated processors did not stop within five seconds. No dump was created."));
return;
}
std::vector<guest_memory_region> regions;
guest_memory_region current{};
const auto finish_region = [&]()
{
if (current.size)
{
regions.emplace_back(current);
current = {};
}
};
// Walk the guest page table and coalesce contiguous pages with identical
// flags into regions, so the manifest mirrors the guest memory map instead
// of listing a million single pages.
for (u32 page = 0; page < guest_address_space_size / guest_page_size; page++)
{
const u32 address = static_cast<u32>(static_cast<u64>(page) * guest_page_size);
const auto [allocated, flags] = vm::get_addr_flags(address);
if (!allocated)
{
finish_region();
continue;
}
if (current.size && (current.flags != flags || static_cast<u64>(current.start) + current.size != address))
{
finish_region();
}
if (!current.size)
{
current.start = address;
current.flags = flags;
}
current.size += guest_page_size;
}
finish_region();
// SPU local store is also part of the guest map (at each thread's
// vm_offset), but a per thread copy with id, pc and type recorded in the
// manifest is much easier to analyze than digging it out of the main image.
std::vector<spu_local_store_dump> spu_dumps;
if (g_fxo->is_init<id_manager::id_map<named_thread<spu_thread>>>())
{
idm::select<named_thread<spu_thread>>([&](u32 id, spu_thread& spu)
{
spu_local_store_dump dump;
dump.id = id;
dump.lv2_id = spu.lv2_id;
dump.index = spu.index;
dump.pc = spu.pc;
dump.vm_offset = spu.vm_offset();
dump.type = static_cast<u32>(spu.get_type());
dump.name = spu.get_name();
spu_dumps.emplace_back(std::move(dump));
});
}
u64 guest_bytes = 0;
for (const auto& region : regions)
{
guest_bytes += region.size;
}
const u64 total_bytes = guest_bytes + static_cast<u64>(spu_dumps.size()) * SPU_LS_SIZE;
// Preflight the destination. A sparse image costs roughly the allocated
// bytes, so warn when even that plus some margin does not fit.
const QStorageInfo storage(output.absolutePath());
const u64 available = storage.bytesAvailable() > 0 ? static_cast<u64>(storage.bytesAvailable()) : 0;
if (const u64 needed = total_bytes + (64ull << 20); available < needed)
{
if (QMessageBox::question(m_parent, tr("Dump Guest Memory"), tr("The destination may not have enough free space (roughly %0 MiB needed, %1 MiB available).\n\nContinue anyway?").arg(needed >> 20).arg(available >> 20)) != QMessageBox::Yes)
{
return;
}
}
atomic_t<u64> completed_bytes{0};
progress_dialog progress(tr("Dump Guest Memory"), tr("Dumping PS3 guest memory..."), tr("Cancel"), 0, 1000, false, m_parent);
progress.setCancelButton(nullptr);
progress.setMinimumDuration(0);
if (!guest_file.trunc(guest_address_space_size))
{
QMessageBox::critical(m_parent, tr("Dump Guest Memory"), tr("Could not size the 4 GB address image."));
return;
}
// Read through the sudo mirror: it maps every allocated page as read/write,
// so pages the guest keeps protected (reservation notification, no access
// areas) are captured as well instead of faulting or being skipped. Copy
// into ordinary host memory before passing data to fs::file, and perform all
// guest-memory reads and file writes off the UI thread.
enum class dump_write_error
{
none,
guest_seek,
guest_write,
spu_write,
};
dump_write_error write_error = dump_write_error::none;
u64 error_address = 0;
QString error_path;
const QString output_path = output.absolutePath();
named_thread dump_thread("Guest Memory Dumper", [&]()
{
std::vector<u8> middle_buffer(dump_io_chunk_size);
for (const auto& region : regions)
{
if (guest_file.seek(region.start) != region.start)
{
write_error = dump_write_error::guest_seek;
error_address = region.start;
return;
}
for (u64 offset = 0; offset < region.size;)
{
const u64 chunk_size = std::min(dump_io_chunk_size, region.size - offset);
const u64 address = static_cast<u64>(region.start) + offset;
std::memcpy(middle_buffer.data(), vm::g_sudo_addr + address, chunk_size);
if (guest_file.write(middle_buffer.data(), chunk_size) != chunk_size)
{
write_error = dump_write_error::guest_write;
error_address = address;
return;
}
offset += chunk_size;
completed_bytes.fetch_add(chunk_size);
}
}
guest_file.close();
QDir worker_output(output_path);
for (const auto& dump : spu_dumps)
{
const QString filename = QStringLiteral("spu_%1_ls.bin").arg(dump.id, 8, 16, QLatin1Char('0'));
const QString file_path = worker_output.filePath(filename);
std::memcpy(middle_buffer.data(), vm::g_sudo_addr + dump.vm_offset, SPU_LS_SIZE);
fs::file file(file_path.toStdString(), fs::rewrite);
if (!file || file.write(middle_buffer.data(), SPU_LS_SIZE) != SPU_LS_SIZE)
{
write_error = dump_write_error::spu_write;
error_path = file_path;
return;
}
completed_bytes.fetch_add(SPU_LS_SIZE);
}
});
qt_events_aware_op(10, [&]()
{
const u64 completed = completed_bytes.load();
progress.SetValue(total_bytes ? static_cast<int>(completed * 1000 / total_bytes) : 1000);
return static_cast<thread_state>(dump_thread) == thread_state::finished;
});
dump_thread();
if (write_error == dump_write_error::guest_seek)
{
QMessageBox::critical(m_parent, tr("Dump Guest Memory"), tr("Could not seek to guest address %0 in the output file.").arg(hex_u64(error_address)));
return;
}
if (write_error == dump_write_error::guest_write)
{
QMessageBox::critical(m_parent, tr("Dump Guest Memory"), tr("Writing failed at guest address %0. The dump is incomplete.").arg(hex_u64(error_address)));
return;
}
if (write_error == dump_write_error::spu_write)
{
QMessageBox::critical(m_parent, tr("Dump Guest Memory"), tr("Could not write SPU local store:\n%0").arg(error_path));
return;
}
QJsonArray spu_json;
for (const auto& dump : spu_dumps)
{
const QString filename = QStringLiteral("spu_%1_ls.bin").arg(dump.id, 8, 16, QLatin1Char('0'));
QJsonObject item;
item.insert(QStringLiteral("file"), filename);
item.insert(QStringLiteral("id"), hex_u64(dump.id));
item.insert(QStringLiteral("lv2_id"), hex_u64(dump.lv2_id));
item.insert(QStringLiteral("index"), static_cast<int>(dump.index));
item.insert(QStringLiteral("pc"), hex_u64(dump.pc, 5));
item.insert(QStringLiteral("vm_offset"), hex_u64(dump.vm_offset));
item.insert(QStringLiteral("type"), static_cast<int>(dump.type));
item.insert(QStringLiteral("name"), QString::fromStdString(dump.name));
item.insert(QStringLiteral("size"), static_cast<int>(SPU_LS_SIZE));
spu_json.append(item);
}
QJsonArray region_json;
for (const auto& region : regions)
{
QJsonObject item;
item.insert(QStringLiteral("start"), hex_u64(region.start));
item.insert(QStringLiteral("end_exclusive"), hex_u64(static_cast<u64>(region.start) + region.size));
item.insert(QStringLiteral("size"), static_cast<double>(region.size));
item.insert(QStringLiteral("file_offset"), hex_u64(region.start));
item.insert(QStringLiteral("raw_page_flags"), static_cast<int>(region.flags));
item.insert(QStringLiteral("readable"), !!(region.flags & vm::page_readable));
item.insert(QStringLiteral("writable"), !!(region.flags & vm::page_writable));
item.insert(QStringLiteral("executable"), !!(region.flags & vm::page_executable));
region_json.append(item);
}
QJsonObject manifest;
manifest.insert(QStringLiteral("format"), QStringLiteral("rpcs3-guest-memory-dump"));
manifest.insert(QStringLiteral("version"), 1);
manifest.insert(QStringLiteral("created_utc"), QDateTime::currentDateTimeUtc().toString(Qt::ISODateWithMs));
manifest.insert(QStringLiteral("title"), QString::fromStdString(Emu.GetTitle()));
manifest.insert(QStringLiteral("title_id"), QString::fromStdString(Emu.GetTitleID()));
manifest.insert(QStringLiteral("address_space_file"), QStringLiteral("guest_memory.bin"));
manifest.insert(QStringLiteral("address_space_size"), hex_u64(guest_address_space_size, 9));
manifest.insert(QStringLiteral("page_size"), static_cast<int>(guest_page_size));
manifest.insert(QStringLiteral("allocated_bytes"), static_cast<double>(guest_bytes));
manifest.insert(QStringLiteral("file_offset_equals_guest_address"), true);
manifest.insert(QStringLiteral("sparse_image"), sparse_image);
manifest.insert(QStringLiteral("regions"), region_json);
manifest.insert(QStringLiteral("spu_local_stores"), spu_json);
QFile manifest_file(output.filePath(QStringLiteral("manifest.json")));
if (!manifest_file.open(QIODevice::WriteOnly | QIODevice::Truncate) || manifest_file.write(QJsonDocument(manifest).toJson(QJsonDocument::Indented)) < 0)
{
QMessageBox::critical(m_parent, tr("Dump Guest Memory"), tr("Could not write the dump manifest."));
return;
}
manifest_file.close();
cleanup.keep = true;
progress.SetValue(1000);
gui_log.success("Guest memory dump complete: %s (%u regions, %u SPU local stores, %u bytes allocated)", output.absolutePath(), regions.size(), spu_dumps.size(), guest_bytes);
if (resume.enabled)
{
Emu.Resume();
resume.enabled = false;
}
QMessageBox::information(m_parent, tr("Dump Guest Memory"), tr("Guest memory dump completed.\n\n%0\n\nThe 4 GB guest_memory.bin file is sparse: its file offset equals the PS3 guest address, while only allocated pages consume disk space.").arg(output.absolutePath()));
if (m_delete_later)
{
deleteLater();
}
}
+45
View File
@@ -0,0 +1,45 @@
#pragma once
#include "util/types.hpp"
#include <QWidget>
class guest_memory_dumper : QObject
{
Q_OBJECT
public:
guest_memory_dumper(QWidget* parent, bool delete_later = false);
virtual ~guest_memory_dumper() {}
void dump_guest_memory();
private:
QWidget* m_parent = nullptr;
bool m_delete_later = false;
static constexpr u64 guest_address_space_size = 0x1'0000'0000ull;
static constexpr u32 guest_page_size = 0x1000;
static constexpr u64 dump_io_chunk_size = 4 * 1024 * 1024;
struct guest_memory_region
{
u32 start = 0;
u64 size = 0;
u8 flags = 0;
};
struct spu_local_store_dump
{
u32 id = 0;
u32 lv2_id = 0;
u32 index = 0;
u32 pc = 0;
u32 vm_offset = 0;
u32 type = 0;
std::string name;
};
static bool emulated_processors_quiesced();
static QString hex_u64(u64 value, int width = 8);
};
+6
View File
@@ -222,7 +222,11 @@ namespace gui
const gui_save gl_textFactor = gui_save(game_list, "textFactor", qreal{2.0});
const gui_save gl_marginFactor = gui_save(game_list, "marginFactor", qreal{0.09});
const gui_save gl_show_hidden = gui_save(game_list, "show_hidden", false);
const gui_save gl_show_broken = gui_save(game_list, "show_broken", false);
const gui_save gl_show_completed = gui_save(game_list, "show_completed", false);
const gui_save gl_hidden_list = gui_save(game_list, "hidden_list", QStringList());
const gui_save gl_broken_list = gui_save(game_list, "broken_list", QStringList());
const gui_save gl_completed_list = gui_save(game_list, "completed_list", QStringList());
const gui_save gl_draw_compat = gui_save(game_list, "draw_compat", false);
const gui_save gl_pref_gd_icon = gui_save(game_list, "pref_gd_icon", false);
const gui_save gl_custom_icon = gui_save(game_list, "custom_icon", true);
@@ -294,6 +298,8 @@ namespace gui
const gui_save tr_show_locked = gui_save(trophy, "show_locked", true);
const gui_save tr_show_unlocked = gui_save(trophy, "show_unlocked", true);
const gui_save tr_show_hidden = gui_save(trophy, "show_hidden", false);
const gui_save tr_show_broken = gui_save(trophy, "show_broken", false);
const gui_save tr_show_completed = gui_save(trophy, "show_completed", false);
const gui_save tr_show_bronze = gui_save(trophy, "show_bronze", true);
const gui_save tr_show_silver = gui_save(trophy, "show_silver", true);
const gui_save tr_show_gold = gui_save(trophy, "show_gold", true);
+8
View File
@@ -208,6 +208,7 @@ private:
case localized_string_id::HOME_MENU_RESUME: return tr("Resume Game");
case localized_string_id::HOME_MENU_FRIENDS: return tr("Friends");
case localized_string_id::HOME_MENU_FRIENDS_REQUESTS: return tr("Pending Friend Requests");
case localized_string_id::HOME_MENU_FRIENDS_GAME_INVITES: return tr("Game Invitations");
case localized_string_id::HOME_MENU_FRIENDS_BLOCKED: return tr("Blocked Users");
case localized_string_id::HOME_MENU_FRIENDS_STATUS_ONLINE: return tr("Online");
case localized_string_id::HOME_MENU_FRIENDS_STATUS_OFFLINE: return tr("Offline");
@@ -221,6 +222,9 @@ private:
case localized_string_id::HOME_MENU_FRIENDS_CANCEL_REQUEST_MSG: return tr("Cancel Request?\n\n%0").arg(std::forward<Args>(args)...);
case localized_string_id::HOME_MENU_FRIENDS_REJECT_REQUEST_MSG: return tr("Reject Request?\n\n%0").arg(std::forward<Args>(args)...);
case localized_string_id::HOME_MENU_FRIENDS_REJECT_REQUEST: return tr("Reject Request");
case localized_string_id::HOME_MENU_FRIENDS_ACCEPT_GAME_INVITE_MSG: return tr("Accept game invitation from %0?").arg(std::forward<Args>(args)...);
case localized_string_id::HOME_MENU_FRIENDS_REJECT_GAME_INVITE_MSG: return tr("Reject game invitation from %0?").arg(std::forward<Args>(args)...);
case localized_string_id::HOME_MENU_FRIENDS_REJECT_GAME_INVITE: return tr("Reject Invitation");
case localized_string_id::HOME_MENU_FRIENDS_NEXT_LIST: return tr("Next list");
case localized_string_id::HOME_MENU_RESTART: return tr("Restart Game");
case localized_string_id::HOME_MENU_SETTINGS: return tr("Settings");
@@ -323,6 +327,10 @@ private:
case localized_string_id::HOME_MENU_TROPHY_GRADE_SILVER: return tr("Silver", "Trophy type");
case localized_string_id::HOME_MENU_TROPHY_GRADE_GOLD: return tr("Gold", "Trophy type");
case localized_string_id::HOME_MENU_TROPHY_GRADE_PLATINUM: return tr("Platinum", "Trophy type");
case localized_string_id::HOME_MENU_TROPHY_SORT_GAME_DEFAULT: return tr("Sort: Game Default");
case localized_string_id::HOME_MENU_TROPHY_SORT_NOT_EARNED: return tr("Sort: Not Earned");
case localized_string_id::HOME_MENU_TROPHY_SORT_EARNED_DATE: return tr("Sort: Earned Date");
case localized_string_id::HOME_MENU_TROPHY_SORT_GRADE: return tr("Sort: Grade");
case localized_string_id::AUDIO_MUTED: return tr("Audio muted", "Audio");
case localized_string_id::AUDIO_UNMUTED: return tr("Audio unmuted", "Audio");
case localized_string_id::AUDIO_CHANGED: return tr("Volume changed to %0", "Audio").arg(std::forward<Args>(args)...);
+43 -11
View File
@@ -48,6 +48,7 @@
#include "sound_effect_manager_dialog.h"
#include "recording_settings_dialog.h"
#include "config_database.h"
#include "guest_memory_dumper.h"
#include <thread>
#include <unordered_set>
@@ -1246,13 +1247,13 @@ bool main_window::HandlePackageInstallation(main_window* mw, QStringList file_pa
{
std::string resolved_path = Emu.GetCallbacks().resolve_path(it->first);
if (resolved_path.empty() || claimed_paths.count(resolved_path))
if (resolved_path.empty() || claimed_paths.contains(resolved_path))
{
it = paths.erase(it);
}
else
{
claimed_paths.emplace(std::move(resolved_path));
claimed_paths.insert(std::move(resolved_path));
it++;
}
}
@@ -1325,23 +1326,27 @@ bool main_window::HandlePackageInstallation(main_window* mw, QStringList file_pa
}
const bool has_expected = !result.version.expected.empty();
const bool has_found = !result.version.found.empty();
if (has_expected && has_found)
const bool has_installed = !result.version.installed.empty();
if (has_expected && has_installed)
{
QMessageBox::warning(mw, tr("Warning!"), tr("Package cannot be installed on top of the current data.\nUpdate is for version %1, but you have version %2.\n\nTried to install: %3")
.arg(QString::fromStdString(result.version.expected)).arg(QString::fromStdString(result.version.found)).arg(package->path));
QMessageBox::warning(mw, tr("Warning!"), tr("Package cannot be installed on top of the current data.\nUpdate with version %0 is for version %1, but you have version %2.\n\nTried to install: %3")
.arg(QString::fromStdString(result.version.app_ver)).arg(QString::fromStdString(result.version.expected)).arg(QString::fromStdString(result.version.installed)).arg(package->path));
}
else if (has_expected)
{
QMessageBox::warning(mw, tr("Warning!"), tr("Package cannot be installed on top of the current data.\nUpdate is for version %1, but you don't have any data installed.\n\nTried to install: %2")
.arg(QString::fromStdString(result.version.expected)).arg(package->path));
QMessageBox::warning(mw, tr("Warning!"), tr("Package cannot be installed on top of the current data.\nUpdate with version %0 is for version %1, but you don't have any data installed.\n\nTried to install: %2")
.arg(QString::fromStdString(result.version.app_ver)).arg(QString::fromStdString(result.version.expected)).arg(package->path));
}
else if (has_installed)
{
QMessageBox::warning(mw, tr("Warning!"), tr("Package cannot be installed on top of the current data.\nUpdate has version %0, but you already have version %1.\n\nTried to install: %2")
.arg(QString::fromStdString(result.version.app_ver)).arg(QString::fromStdString(result.version.installed)).arg(package->path));
}
else
{
// probably unreachable
const QString found = has_found ? tr("version %1").arg(QString::fromStdString(result.version.found)) : tr("no data installed");
QMessageBox::warning(mw, tr("Warning!"), tr("Package cannot be installed on top of the current data.\nUpdate is for unknown version, but you have version %1.\n\nTried to install: %2")
.arg(QString::fromStdString(result.version.expected)).arg(found).arg(package->path));
QMessageBox::warning(mw, tr("Warning!"), tr("Package cannot be installed on top of the current data.\nAn unexpected error occured.\n\nTried to install: %0").arg(package->path));
}
}
else
@@ -2192,6 +2197,7 @@ void main_window::EnableMenus(bool enabled) const
// Tools
ui->toolskernel_explorerAct->setEnabled(enabled);
ui->toolsmemory_viewerAct->setEnabled(enabled);
ui->toolsDumpGuestMemoryAct->setEnabled(enabled);
ui->toolsRsxDebuggerAct->setEnabled(enabled);
ui->toolsSystemCommandsAct->setEnabled(enabled);
ui->actionCreate_RSX_Capture->setEnabled(enabled);
@@ -3387,6 +3393,12 @@ void main_window::CreateConnects()
idm::make<memory_viewer_handle>(this, make_basic_ppu_disasm());
});
connect(ui->toolsDumpGuestMemoryAct, &QAction::triggered, this, [this]()
{
guest_memory_dumper* dumper = new guest_memory_dumper(this, true);
dumper->dump_guest_memory();
});
connect(ui->toolsRsxDebuggerAct, &QAction::triggered, this, [this]
{
rsx_debugger* rsx = new rsx_debugger(m_gui_settings);
@@ -3461,6 +3473,20 @@ void main_window::CreateConnects()
m_game_list_frame->Refresh();
});
connect(ui->showBrokenEntriesAct, &QAction::triggered, this, [this](bool checked)
{
m_gui_settings->SetValue(gui::gl_show_broken, checked);
m_game_list_frame->SetShowBroken(checked);
m_game_list_frame->Refresh();
});
connect(ui->showCompletedEntriesAct, &QAction::triggered, this, [this](bool checked)
{
m_gui_settings->SetValue(gui::gl_show_completed, checked);
m_game_list_frame->SetShowCompleted(checked);
m_game_list_frame->Refresh();
});
connect(ui->showCompatibilityInGridAct, &QAction::triggered, m_game_list_frame, &game_list_frame::SetShowCompatibilityInGrid);
connect(ui->refreshGameListAct, &QAction::triggered, this, [this]
@@ -3909,6 +3935,12 @@ void main_window::ConfigureGuiFromSettings()
ui->showHiddenEntriesAct->setChecked(m_gui_settings->GetValue(gui::gl_show_hidden).toBool());
m_game_list_frame->SetShowHidden(ui->showHiddenEntriesAct->isChecked()); // prevent GetValue in m_game_list_frame->LoadSettings
ui->showBrokenEntriesAct->setChecked(m_gui_settings->GetValue(gui::gl_show_broken).toBool());
m_game_list_frame->SetShowBroken(ui->showBrokenEntriesAct->isChecked()); // prevent GetValue in m_game_list_frame->LoadSettings
ui->showCompletedEntriesAct->setChecked(m_gui_settings->GetValue(gui::gl_show_completed).toBool());
m_game_list_frame->SetShowCompleted(ui->showCompletedEntriesAct->isChecked()); // prevent GetValue in m_game_list_frame->LoadSettings
ui->showCompatibilityInGridAct->setChecked(m_gui_settings->GetValue(gui::gl_draw_compat).toBool());
ui->actionPreferGameDataIcons->setChecked(m_gui_settings->GetValue(gui::gl_pref_gd_icon).toBool());
ui->showCustomIconsAct->setChecked(m_gui_settings->GetValue(gui::gl_custom_icon).toBool());
+29
View File
@@ -365,6 +365,7 @@
<addaction name="separator"/>
<addaction name="toolsSystemCommandsAct"/>
<addaction name="toolsmemory_viewerAct"/>
<addaction name="toolsDumpGuestMemoryAct"/>
<addaction name="toolskernel_explorerAct"/>
<addaction name="toolsRsxDebuggerAct"/>
<addaction name="separator"/>
@@ -428,6 +429,8 @@
<addaction name="separator"/>
<addaction name="showGameListAct"/>
<addaction name="showHiddenEntriesAct"/>
<addaction name="showBrokenEntriesAct"/>
<addaction name="showCompletedEntriesAct"/>
<addaction name="separator"/>
<addaction name="menuGame_List_View"/>
<addaction name="menuGame_List_Grid_Mode"/>
@@ -759,6 +762,17 @@
<string>Memory Viewer</string>
</property>
</action>
<action name="toolsDumpGuestMemoryAct">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Dump Guest Memory</string>
</property>
<property name="toolTip">
<string>Dump all allocated PS3 guest memory and live SPU local stores</string>
</property>
</action>
<action name="toolsRsxDebuggerAct">
<property name="enabled">
<bool>false</bool>
@@ -1183,6 +1197,21 @@
<string>Show Hidden Entries</string>
</property>
</action>
<action name="showBrokenEntriesAct">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>Show Broken Entries</string>
</property>
</action> <action name="showCompletedEntriesAct">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>Show Completed Entries</string>
</property>
</action>
<action name="actionopen_rsx_capture">
<property name="text">
<string>Open RSX Capture</string>
+54 -19
View File
@@ -24,6 +24,8 @@ extern "C" {
LOG_CHANNEL(cfg_log, "CFG");
constexpr int codec_name_role = Qt::UserRole + 1;
static std::vector<const AVCodec*> get_video_codecs(const AVOutputFormat* fmt)
{
std::vector<const AVCodec*> codecs;
@@ -142,36 +144,42 @@ recording_settings_dialog::recording_settings_dialog(QWidget* parent)
{
if (!codec) continue;
const std::string name = codec->long_name ? codec->long_name : avcodec_get_name(codec->id);
const char* codec_name = avcodec_get_name(codec->id);
const std::string name = codec->long_name ? codec->long_name : codec_name;
ui->combo_video_codec->addItem(QString::fromStdString(name), static_cast<int>(codec->id));
ui->combo_video_codec->setItemData(ui->combo_video_codec->findData(static_cast<int>(codec->id)), QString(codec_name), codec_name_role);
}
for (const AVCodec* codec : m_audio_codecs)
{
if (!codec) continue;
const std::string name = codec->long_name ? codec->long_name : avcodec_get_name(codec->id);
const char* codec_name = avcodec_get_name(codec->id);
const std::string name = codec->long_name ? codec->long_name : codec_name;
ui->combo_audio_codec->addItem(QString::fromStdString(name), static_cast<int>(codec->id));
ui->combo_audio_codec->setItemData(ui->combo_audio_codec->findData(static_cast<int>(codec->id)), QString(codec_name), codec_name_role);
}
connect(ui->combo_video_codec, &QComboBox::currentIndexChanged, this, [this](int index)
{
const QVariant var = ui->combo_video_codec->itemData(index);
if (var.canConvert<int>())
const QVariant codec_id = ui->combo_video_codec->itemData(index);
const QVariant codec_name = ui->combo_video_codec->itemData(index, codec_name_role);
if (codec_id.canConvert<int>() && codec_name.canConvert<QString>())
{
const int codec_id = var.toInt();
g_cfg_recording.video.video_codec.set(codec_id);
g_cfg_recording.video.codec_name.set(codec_name.toString().toStdString());
g_cfg_recording.video.codec_id.set(codec_id.toInt());
update_preset();
}
});
connect(ui->combo_audio_codec, &QComboBox::currentIndexChanged, this, [this](int index)
{
const QVariant var = ui->combo_audio_codec->itemData(index);
if (var.canConvert<int>())
const QVariant codec_id = ui->combo_audio_codec->itemData(index);
const QVariant codec_name = ui->combo_audio_codec->itemData(index, codec_name_role);
if (codec_id.canConvert<int>() && codec_name.canConvert<QString>())
{
const int codec_id = var.toInt();
g_cfg_recording.audio.audio_codec.set(codec_id);
g_cfg_recording.audio.codec_name.set(codec_name.toString().toStdString());
g_cfg_recording.audio.codec_id.set(codec_id.toInt());
update_preset();
}
});
@@ -279,8 +287,25 @@ void recording_settings_dialog::update_ui()
ui->combo_resolution->setCurrentIndex(ui->combo_resolution->findData(QVariant::fromValue(QPair<int, int>(g_cfg_recording.video.width.get(), g_cfg_recording.video.height.get()))));
ui->combo_framerate->setCurrentIndex(ui->combo_framerate->findData(static_cast<int>(g_cfg_recording.video.framerate.get())));
ui->combo_video_codec->setCurrentIndex(ui->combo_video_codec->findData(static_cast<int>(g_cfg_recording.video.video_codec.get())));
ui->combo_audio_codec->setCurrentIndex(ui->combo_audio_codec->findData(static_cast<int>(g_cfg_recording.audio.audio_codec.get())));
if (const int index = ui->combo_video_codec->findData(QString::fromStdString(g_cfg_recording.video.codec_name.get()), codec_name_role); index >= 0)
{
ui->combo_video_codec->setCurrentIndex(index);
}
else
{
ui->combo_video_codec->setCurrentIndex(ui->combo_video_codec->findData(static_cast<int>(g_cfg_recording.video.codec_id.get())));
}
if (const int index = ui->combo_audio_codec->findData(QString::fromStdString(g_cfg_recording.audio.codec_name.get()), codec_name_role); index >= 0)
{
ui->combo_audio_codec->setCurrentIndex(index);
}
else
{
ui->combo_audio_codec->setCurrentIndex(ui->combo_audio_codec->findData(static_cast<int>(g_cfg_recording.audio.codec_id.get())));
}
ui->spinbox_video_bitrate->setValue(g_cfg_recording.video.video_bps);
ui->spinbox_audio_bitrate->setValue(g_cfg_recording.audio.audio_bps);
ui->spinbox_gop_size->setValue(g_cfg_recording.video.gop_size);
@@ -295,8 +320,14 @@ void recording_settings_dialog::update_ui()
ui->spinbox_gop_size->blockSignals(false);
ui->spinbox_max_b_frames->blockSignals(false);
const auto get_codec_name = [](const std::vector<const AVCodec*>& codecs, u32 id)
const auto get_codec_name = [](const std::vector<const AVCodec*>& codecs, u32 id, const std::string& codec_name)
{
if (const AVCodec* codec = avcodec_find_encoder_by_name(codec_name.c_str()))
{
const std::string name = codec->long_name ? codec->long_name : avcodec_get_name(codec->id);
return name;
}
for (const AVCodec* codec : codecs)
{
if (codec && codec->id == static_cast<AVCodecID>(id))
@@ -323,9 +354,9 @@ void recording_settings_dialog::update_ui()
fmt::format("%d x %d\n%d fps\n%s\n%d\n%s\n%d\n%d\n%d",
g_cfg_recording.video.width.get(), g_cfg_recording.video.height.get(),
g_cfg_recording.video.framerate.get(),
get_codec_name(m_video_codecs, g_cfg_recording.video.video_codec.get()),
get_codec_name(m_video_codecs, g_cfg_recording.video.codec_id.get(), g_cfg_recording.video.codec_name.get()),
g_cfg_recording.video.video_bps.get(),
get_codec_name(m_audio_codecs, g_cfg_recording.audio.audio_codec.get()),
get_codec_name(m_audio_codecs, g_cfg_recording.audio.codec_id.get(), g_cfg_recording.audio.codec_name.get()),
g_cfg_recording.audio.audio_bps.get(),
g_cfg_recording.video.gop_size.get(),
g_cfg_recording.video.max_b_frames.get()
@@ -340,10 +371,12 @@ void recording_settings_dialog::select_preset(quality_preset preset, cfg_recordi
return;
}
cfg.audio.audio_codec.set(static_cast<u32>(AVCodecID::AV_CODEC_ID_AAC));
cfg.audio.codec_name.set("");
cfg.audio.codec_id.set(static_cast<u32>(AVCodecID::AV_CODEC_ID_AAC));
cfg.audio.audio_bps.set(192'000); // 192 kbps
cfg.video.video_codec.set(static_cast<u32>(AVCodecID::AV_CODEC_ID_MPEG4));
cfg.video.codec_name.set("");
cfg.video.codec_id.set(static_cast<u32>(AVCodecID::AV_CODEC_ID_MPEG4));
cfg.video.pixel_format.set(static_cast<u32>(::AV_PIX_FMT_YUV420P));
switch (preset)
@@ -437,11 +470,13 @@ recording_settings_dialog::quality_preset recording_settings_dialog::current_pre
g_cfg_recording.video.width.get() == cfg.video.width.get() &&
g_cfg_recording.video.height.get() == cfg.video.height.get() &&
g_cfg_recording.video.pixel_format.get() == cfg.video.pixel_format.get() &&
g_cfg_recording.video.video_codec.get() == cfg.video.video_codec.get() &&
g_cfg_recording.video.codec_name.get() == cfg.video.codec_name.get() &&
g_cfg_recording.video.codec_id.get() == cfg.video.codec_id.get() &&
g_cfg_recording.video.video_bps.get() == cfg.video.video_bps.get() &&
g_cfg_recording.video.max_b_frames.get() == cfg.video.max_b_frames.get() &&
g_cfg_recording.video.gop_size.get() == cfg.video.gop_size.get() &&
g_cfg_recording.audio.audio_codec.get() == cfg.audio.audio_codec.get() &&
g_cfg_recording.audio.codec_name.get() == cfg.audio.codec_name.get() &&
g_cfg_recording.audio.codec_id.get() == cfg.audio.codec_id.get() &&
g_cfg_recording.audio.audio_bps.get() == cfg.audio.audio_bps.get())
{
return preset;
+1 -1
View File
@@ -596,7 +596,7 @@ void save_manager_dialog::OnEntriesRemove()
return;
}
if (QMessageBox::question(this, tr("Delete Confirmation"), tr("Are you sure you want to delete these %n items?", "", rows.size()), QMessageBox::Yes, QMessageBox::No) == QMessageBox::Yes)
if (QMessageBox::question(this, tr("Delete Confirmation"), tr("Are you sure you want to delete these %n items?", "", static_cast<int>(rows.size())), QMessageBox::Yes, QMessageBox::No) == QMessageBox::Yes)
{
// Sort descending so removeRow() doesn't shift remaining indices.
std::sort(rows.begin(), rows.end(), std::greater<int>());
+10 -10
View File
@@ -1079,21 +1079,21 @@ void trophy_manager_dialog::StartTrophyLoadThreads()
for (int i = 0; i < count; ++i)
indices.append(i);
QFutureWatcher<void> futureWatcher;
QFutureWatcher<void> future_watcher;
progress_dialog progressDialog(tr("Loading trophies"), tr("Loading trophy data, please wait..."), tr("Cancel"), 0, 1, false, this, Qt::Dialog | Qt::WindowTitleHint | Qt::CustomizeWindowHint);
progress_dialog progress_dlg(tr("Loading trophies"), tr("Loading trophy data, please wait..."), tr("Cancel"), 0, 1, false, this, Qt::Dialog | Qt::WindowTitleHint | Qt::CustomizeWindowHint);
connect(&futureWatcher, &QFutureWatcher<void>::progressRangeChanged, &progressDialog, &QProgressDialog::setRange);
connect(&futureWatcher, &QFutureWatcher<void>::progressValueChanged, &progressDialog, &QProgressDialog::setValue);
connect(&futureWatcher, &QFutureWatcher<void>::finished, this, [this]() { RepaintUI(true); });
connect(&progressDialog, &QProgressDialog::canceled, this, [this, &futureWatcher]()
connect(&future_watcher, &QFutureWatcher<void>::progressRangeChanged, &progress_dlg, &QProgressDialog::setRange);
connect(&future_watcher, &QFutureWatcher<void>::progressValueChanged, &progress_dlg, &QProgressDialog::setValue);
connect(&future_watcher, &QFutureWatcher<void>::finished, this, [this]() { RepaintUI(true); });
connect(&progress_dlg, &QProgressDialog::canceled, this, [this, &future_watcher]()
{
futureWatcher.cancel();
future_watcher.cancel();
close(); // It's pointless to show an empty window
});
atomic_t<usz> error_count{};
futureWatcher.setFuture(QtConcurrent::map(indices, [this, &error_count, &folder_list](const int& i)
future_watcher.setFuture(QtConcurrent::map(indices, [this, &error_count, &folder_list](const int& i)
{
const std::string dir_name = folder_list.value(i).toStdString();
gui_log.trace("Loading trophy dir: %s", dir_name);
@@ -1106,9 +1106,9 @@ void trophy_manager_dialog::StartTrophyLoadThreads()
}
}));
progressDialog.exec();
progress_dlg.exec();
futureWatcher.waitForFinished();
future_watcher.waitForFinished();
if (error_count != 0)
{
+163
View File
@@ -571,11 +571,36 @@ namespace fmt
EXPECT_EQ("a"s, fmt::merge(vec{"a"}, "-"));
EXPECT_EQ("a"s, fmt::merge(vec{"a"}, " *-* "));
EXPECT_EQ("a"s, fmt::merge(vec{"a", ""}, ""));
EXPECT_EQ("a "s, fmt::merge(vec{"a", ""}, " "));
EXPECT_EQ("a-"s, fmt::merge(vec{"a", ""}, "-"));
EXPECT_EQ("a *-* "s, fmt::merge(vec{"a", ""}, " *-* "));
EXPECT_EQ("a"s, fmt::merge(vec{"a", "", ""}, ""));
EXPECT_EQ("a "s, fmt::merge(vec{"a", "", ""}, " "));
EXPECT_EQ("a--"s, fmt::merge(vec{"a", "", ""}, "-"));
EXPECT_EQ("a *-* *-* "s, fmt::merge(vec{"a", "", ""}, " *-* "));
EXPECT_EQ("a"s, fmt::merge(vec{"", "a"}, ""));
EXPECT_EQ(" a"s, fmt::merge(vec{"", "a"}, " "));
EXPECT_EQ("-a"s, fmt::merge(vec{"", "a"}, "-"));
EXPECT_EQ(" *-* a"s, fmt::merge(vec{"", "a"}, " *-* "));
EXPECT_EQ("ab"s, fmt::merge(vec{"a", "b"}, ""));
EXPECT_EQ("a b"s, fmt::merge(vec{"a", "b"}, " "));
EXPECT_EQ("a-b"s, fmt::merge(vec{"a", "b"}, "-"));
EXPECT_EQ("a *-* b"s, fmt::merge(vec{"a", "b"}, " *-* "));
EXPECT_EQ("ab"s, fmt::merge(vec{"a", "", "b"}, ""));
EXPECT_EQ("a b"s, fmt::merge(vec{"a", "", "b"}, " "));
EXPECT_EQ("a--b"s, fmt::merge(vec{"a", "", "b"}, "-"));
EXPECT_EQ("a *-* *-* b"s, fmt::merge(vec{"a", "", "b"}, " *-* "));
EXPECT_EQ("ab"s, fmt::merge(vec{"a", "", "", "b"}, ""));
EXPECT_EQ("a b"s, fmt::merge(vec{"a", "", "", "b"}, " "));
EXPECT_EQ("a---b"s, fmt::merge(vec{"a", "", "", "b"}, "-"));
EXPECT_EQ("a *-* *-* *-* b"s, fmt::merge(vec{"a", "", "", "b"}, " *-* "));
EXPECT_EQ("abc"s, fmt::merge(vec{"a", "b", "c"}, ""));
EXPECT_EQ("a b c"s, fmt::merge(vec{"a", "b", "c"}, " "));
EXPECT_EQ("a-b-c"s, fmt::merge(vec{"a", "b", "c"}, "-"));
@@ -597,6 +622,16 @@ namespace fmt
EXPECT_EQ("a"s, fmt::merge(lst{vec{"a"}}, "-"));
EXPECT_EQ("a"s, fmt::merge(lst{vec{"a"}}, " *-* "));
EXPECT_EQ("a"s, fmt::merge(lst{vec{"a"}, vec{}}, ""));
EXPECT_EQ("a"s, fmt::merge(lst{vec{"a"}, vec{}}, " "));
EXPECT_EQ("a"s, fmt::merge(lst{vec{"a"}, vec{}}, "-"));
EXPECT_EQ("a"s, fmt::merge(lst{vec{"a"}, vec{}}, " *-* "));
EXPECT_EQ("a"s, fmt::merge(lst{vec{}, vec{"a"}}, ""));
EXPECT_EQ("a"s, fmt::merge(lst{vec{}, vec{"a"}}, " "));
EXPECT_EQ("a"s, fmt::merge(lst{vec{}, vec{"a"}}, "-"));
EXPECT_EQ("a"s, fmt::merge(lst{vec{}, vec{"a"}}, " *-* "));
EXPECT_EQ("ab"s, fmt::merge(lst{vec{"a", "b"}}, ""));
EXPECT_EQ("a b"s, fmt::merge(lst{vec{"a", "b"}}, " "));
EXPECT_EQ("a-b"s, fmt::merge(lst{vec{"a", "b"}}, "-"));
@@ -612,6 +647,11 @@ namespace fmt
EXPECT_EQ("a-b"s, fmt::merge(lst{vec{"a"}, vec{"b"}}, "-"));
EXPECT_EQ("a *-* b"s, fmt::merge(lst{vec{"a"}, vec{"b"}}, " *-* "));
EXPECT_EQ("ab"s, fmt::merge(lst{vec{"a"}, vec{}, vec{"b"}}, ""));
EXPECT_EQ("a b"s, fmt::merge(lst{vec{"a"}, vec{}, vec{"b"}}, " "));
EXPECT_EQ("a-b"s, fmt::merge(lst{vec{"a"}, vec{}, vec{"b"}}, "-"));
EXPECT_EQ("a *-* b"s, fmt::merge(lst{vec{"a"}, vec{}, vec{"b"}}, " *-* "));
EXPECT_EQ("abc"s, fmt::merge(lst{vec{"a"}, vec{"b"}, vec{"c"}}, ""));
EXPECT_EQ("a b c"s, fmt::merge(lst{vec{"a"}, vec{"b"}, vec{"c"}}, " "));
EXPECT_EQ("a-b-c"s, fmt::merge(lst{vec{"a"}, vec{"b"}, vec{"c"}}, "-"));
@@ -621,6 +661,129 @@ namespace fmt
EXPECT_EQ("a 1 b 2"s, fmt::merge(lst{vec{"a", "1"}, vec{"b", "2"}}, " "));
EXPECT_EQ("a-1-b-2"s, fmt::merge(lst{vec{"a", "1"}, vec{"b", "2"}}, "-"));
EXPECT_EQ("a *-* 1 *-* b *-* 2"s, fmt::merge(lst{vec{"a", "1"}, vec{"b", "2"}}, " *-* "));
EXPECT_EQ("a1b2"s, fmt::merge(lst{vec{"a", "", "1"}, vec{"b", "2", ""}}, ""));
EXPECT_EQ("a 1 b 2 "s, fmt::merge(lst{vec{"a", "", "1"}, vec{"b", "2", ""}}, " "));
EXPECT_EQ("a--1-b-2-"s, fmt::merge(lst{vec{"a", "", "1"}, vec{"b", "2", ""}}, "-"));
EXPECT_EQ("a *-* *-* 1 *-* b *-* 2 *-* "s, fmt::merge(lst{vec{"a", "", "1"}, vec{"b", "2", ""}}, " *-* "));
}
TEST(StrUtil, Merge_SkipEmpty)
{
using vec = std::vector<std::string>;
using lst = std::initializer_list<std::vector<std::string>>;
// Vector of strings
EXPECT_EQ(""s, fmt::merge(vec{}, "", true));
EXPECT_EQ(""s, fmt::merge(vec{}, " ", true));
EXPECT_EQ(""s, fmt::merge(vec{}, "-", true));
EXPECT_EQ(""s, fmt::merge(vec{}, " *-* ", true));
EXPECT_EQ(""s, fmt::merge(vec{""}, "", true));
EXPECT_EQ(""s, fmt::merge(vec{""}, " ", true));
EXPECT_EQ(""s, fmt::merge(vec{""}, "-", true));
EXPECT_EQ(""s, fmt::merge(vec{""}, " *-* ", true));
EXPECT_EQ("a"s, fmt::merge(vec{"a"}, "", true));
EXPECT_EQ("a"s, fmt::merge(vec{"a"}, " ", true));
EXPECT_EQ("a"s, fmt::merge(vec{"a"}, "-", true));
EXPECT_EQ("a"s, fmt::merge(vec{"a"}, " *-* ", true));
EXPECT_EQ("a"s, fmt::merge(vec{"a", ""}, "", true));
EXPECT_EQ("a"s, fmt::merge(vec{"a", ""}, " ", true));
EXPECT_EQ("a"s, fmt::merge(vec{"a", ""}, "-", true));
EXPECT_EQ("a"s, fmt::merge(vec{"a", ""}, " *-* ", true));
EXPECT_EQ("a"s, fmt::merge(vec{"a", "", ""}, "", true));
EXPECT_EQ("a"s, fmt::merge(vec{"a", "", ""}, " ", true));
EXPECT_EQ("a"s, fmt::merge(vec{"a", "", ""}, "-", true));
EXPECT_EQ("a"s, fmt::merge(vec{"a", "", ""}, " *-* ", true));
EXPECT_EQ("a"s, fmt::merge(vec{"", "a"}, "", true));
EXPECT_EQ("a"s, fmt::merge(vec{"", "a"}, " ", true));
EXPECT_EQ("a"s, fmt::merge(vec{"", "a"}, "-", true));
EXPECT_EQ("a"s, fmt::merge(vec{"", "a"}, " *-* ", true));
EXPECT_EQ("ab"s, fmt::merge(vec{"a", "b"}, "", true));
EXPECT_EQ("a b"s, fmt::merge(vec{"a", "b"}, " ", true));
EXPECT_EQ("a-b"s, fmt::merge(vec{"a", "b"}, "-", true));
EXPECT_EQ("a *-* b"s, fmt::merge(vec{"a", "b"}, " *-* ", true));
EXPECT_EQ("ab"s, fmt::merge(vec{"a", "", "b"}, "", true));
EXPECT_EQ("a b"s, fmt::merge(vec{"a", "", "b"}, " ", true));
EXPECT_EQ("a-b"s, fmt::merge(vec{"a", "", "b"}, "-", true));
EXPECT_EQ("a *-* b"s, fmt::merge(vec{"a", "", "b"}, " *-* ", true));
EXPECT_EQ("ab"s, fmt::merge(vec{"a", "", "", "b"}, "", true));
EXPECT_EQ("a b"s, fmt::merge(vec{"a", "", "", "b"}, " ", true));
EXPECT_EQ("a-b"s, fmt::merge(vec{"a", "", "", "b"}, "-", true));
EXPECT_EQ("a *-* b"s, fmt::merge(vec{"a", "", "", "b"}, " *-* ", true));
EXPECT_EQ("abc"s, fmt::merge(vec{"a", "b", "c"}, "", true));
EXPECT_EQ("a b c"s, fmt::merge(vec{"a", "b", "c"}, " ", true));
EXPECT_EQ("a-b-c"s, fmt::merge(vec{"a", "b", "c"}, "-", true));
EXPECT_EQ("a *-* b *-* c"s, fmt::merge(vec{"a", "b", "c"}, " *-* ", true));
// Initializer list of vector of strings
EXPECT_EQ(""s, fmt::merge(lst{}, "", true));
EXPECT_EQ(""s, fmt::merge(lst{}, " ", true));
EXPECT_EQ(""s, fmt::merge(lst{}, "-", true));
EXPECT_EQ(""s, fmt::merge(lst{}, " *-* ", true));
EXPECT_EQ(""s, fmt::merge(lst{vec{}}, "", true));
EXPECT_EQ(""s, fmt::merge(lst{vec{}}, " ", true));
EXPECT_EQ(""s, fmt::merge(lst{vec{}}, "-", true));
EXPECT_EQ(""s, fmt::merge(lst{vec{}}, " *-* ", true));
EXPECT_EQ("a"s, fmt::merge(lst{vec{"a"}}, "", true));
EXPECT_EQ("a"s, fmt::merge(lst{vec{"a"}}, " ", true));
EXPECT_EQ("a"s, fmt::merge(lst{vec{"a"}}, "-", true));
EXPECT_EQ("a"s, fmt::merge(lst{vec{"a"}}, " *-* ", true));
EXPECT_EQ("a"s, fmt::merge(lst{vec{"a"}, vec{}}, "", true));
EXPECT_EQ("a"s, fmt::merge(lst{vec{"a"}, vec{}}, " ", true));
EXPECT_EQ("a"s, fmt::merge(lst{vec{"a"}, vec{}}, "-", true));
EXPECT_EQ("a"s, fmt::merge(lst{vec{"a"}, vec{}}, " *-* ", true));
EXPECT_EQ("a"s, fmt::merge(lst{vec{}, vec{"a"}}, "", true));
EXPECT_EQ("a"s, fmt::merge(lst{vec{}, vec{"a"}}, " ", true));
EXPECT_EQ("a"s, fmt::merge(lst{vec{}, vec{"a"}}, "-", true));
EXPECT_EQ("a"s, fmt::merge(lst{vec{}, vec{"a"}}, " *-* ", true));
EXPECT_EQ("ab"s, fmt::merge(lst{vec{"a", "b"}}, "", true));
EXPECT_EQ("a b"s, fmt::merge(lst{vec{"a", "b"}}, " ", true));
EXPECT_EQ("a-b"s, fmt::merge(lst{vec{"a", "b"}}, "-", true));
EXPECT_EQ("a *-* b"s, fmt::merge(lst{vec{"a", "b"}}, " *-* ", true));
EXPECT_EQ("abc"s, fmt::merge(lst{vec{"a", "b", "c"}}, "", true));
EXPECT_EQ("a b c"s, fmt::merge(lst{vec{"a", "b", "c"}}, " ", true));
EXPECT_EQ("a-b-c"s, fmt::merge(lst{vec{"a", "b", "c"}}, "-", true));
EXPECT_EQ("a *-* b *-* c"s, fmt::merge(lst{vec{"a", "b", "c"}}, " *-* ", true));
EXPECT_EQ("ab"s, fmt::merge(lst{vec{"a"}, vec{"b"}}, "", true));
EXPECT_EQ("a b"s, fmt::merge(lst{vec{"a"}, vec{"b"}}, " ", true));
EXPECT_EQ("a-b"s, fmt::merge(lst{vec{"a"}, vec{"b"}}, "-", true));
EXPECT_EQ("a *-* b"s, fmt::merge(lst{vec{"a"}, vec{"b"}}, " *-* ", true));
EXPECT_EQ("ab"s, fmt::merge(lst{vec{"a"}, vec{}, vec{"b"}}, "", true));
EXPECT_EQ("a b"s, fmt::merge(lst{vec{"a"}, vec{}, vec{"b"}}, " ", true));
EXPECT_EQ("a-b"s, fmt::merge(lst{vec{"a"}, vec{}, vec{"b"}}, "-", true));
EXPECT_EQ("a *-* b"s, fmt::merge(lst{vec{"a"}, vec{}, vec{"b"}}, " *-* ", true));
EXPECT_EQ("abc"s, fmt::merge(lst{vec{"a"}, vec{"b"}, vec{"c"}}, "", true));
EXPECT_EQ("a b c"s, fmt::merge(lst{vec{"a"}, vec{"b"}, vec{"c"}}, " ", true));
EXPECT_EQ("a-b-c"s, fmt::merge(lst{vec{"a"}, vec{"b"}, vec{"c"}}, "-", true));
EXPECT_EQ("a *-* b *-* c"s, fmt::merge(lst{vec{"a"}, vec{"b"}, vec{"c"}}, " *-* ", true));
EXPECT_EQ("a1b2"s, fmt::merge(lst{vec{"a", "1"}, vec{"b", "2"}}, "", true));
EXPECT_EQ("a 1 b 2"s, fmt::merge(lst{vec{"a", "1"}, vec{"b", "2"}}, " ", true));
EXPECT_EQ("a-1-b-2"s, fmt::merge(lst{vec{"a", "1"}, vec{"b", "2"}}, "-", true));
EXPECT_EQ("a *-* 1 *-* b *-* 2"s, fmt::merge(lst{vec{"a", "1"}, vec{"b", "2"}}, " *-* ", true));
EXPECT_EQ("a1b2"s, fmt::merge(lst{vec{"a", "", "1"}, vec{"b", "2", ""}}, "", true));
EXPECT_EQ("a 1 b 2"s, fmt::merge(lst{vec{"a", "", "1"}, vec{"b", "2", ""}}, " ", true));
EXPECT_EQ("a-1-b-2"s, fmt::merge(lst{vec{"a", "", "1"}, vec{"b", "2", ""}}, "-", true));
EXPECT_EQ("a *-* 1 *-* b *-* 2"s, fmt::merge(lst{vec{"a", "", "1"}, vec{"b", "2", ""}}, " *-* ", true));
}
TEST(StrUtil, GetFileExtension)
+3 -2
View File
@@ -15,7 +15,7 @@
// Giga SPU analyser regression: a brsl whose return address is a stop-trap leaves
// a dangling target edge after block cleanup, which the reg-state walk must not
// dereference. The SPU program below is made-up data (not from any game).
namespace
namespace test_spu
{
constexpr u32 SPU_STOP = 0x00000000u; // stop 0x0 — the no-return trap word
@@ -37,7 +37,6 @@ namespace
{
return 0x1a8u << 21;
}
}
TEST(SpuAnalyserGiga, ReturnToStopTrapDoesNotRangeCheckFail)
{
@@ -242,3 +241,5 @@ TEST(SpuAnalyserGiga, ReDecodeConstPropCompletes)
g_cfg.core.spu_block_size.set(saved);
}
}
+22 -24
View File
@@ -93,6 +93,13 @@ namespace utils
media_log.notice("av_log: %s", msg);
};
template <typename T>
T media_info::get_metadata([[maybe_unused]] const std::string& key, const T& def) const
{
static_assert("unimplemented");
return def;
}
template <>
std::string media_info::get_metadata(const std::string& key, const std::string& def) const
{
@@ -799,9 +806,10 @@ namespace utils
m_out_format = std::move(format);
}
void video_encoder::set_video_codec(s32 codec_id)
void video_encoder::set_video_codec(s32 codec_id, std::string_view codec_name)
{
m_video_codec_id = codec_id;
m_video_codec_name = codec_name;
}
void video_encoder::set_max_b_frames(s32 max_b_frames)
@@ -829,9 +837,10 @@ namespace utils
m_audio_bitrate_bps = bitrate;
}
void video_encoder::set_audio_codec(s32 codec_id)
void video_encoder::set_audio_codec(s32 codec_id, std::string_view codec_name)
{
m_audio_codec_id = codec_id;
m_audio_codec_name = codec_name;
}
void video_encoder::pause(bool flush)
@@ -992,9 +1001,12 @@ namespace utils
return nullptr;
};
const AVCodecID video_codec = static_cast<AVCodecID>(m_video_codec_id);
const AVCodecID audio_codec = static_cast<AVCodecID>(m_audio_codec_id);
const AVOutputFormat* out_format = find_format(video_codec, audio_codec);
const AVCodec* selected_video_codec = avcodec_find_encoder_by_name(m_video_codec_name.c_str());
const AVCodec* selected_audio_codec = avcodec_find_encoder_by_name(m_audio_codec_name.c_str());
const AVCodecID video_codec_id = selected_video_codec ? selected_video_codec->id : static_cast<AVCodecID>(m_video_codec_id);
const AVCodecID audio_codec_id = selected_audio_codec ? selected_audio_codec->id : static_cast<AVCodecID>(m_audio_codec_id);
const AVOutputFormat* out_format = find_format(video_codec_id, audio_codec_id);
if (out_format)
{
@@ -1002,7 +1014,7 @@ namespace utils
}
else
{
media_log.error("video_encoder: Could not find a format for the requested video_codec %d and audio_codec %d", m_video_codec_id, m_audio_codec_id);
media_log.error("video_encoder: Could not find a format for the requested video_codec '%s' (ID=%d) and audio_codec '%s' (ID=%d)", m_video_codec_name, m_video_codec_id, m_audio_codec_name, m_audio_codec_id);
// Fallback to some other codec
for (const AVCodec* video_codec : video_codecs)
@@ -1014,6 +1026,8 @@ namespace utils
if (out_format)
{
media_log.success("video_encoder: Found fallback output format '%s'", out_format->name);
selected_video_codec = video_codec;
selected_audio_codec = audio_codec;
break;
}
}
@@ -1046,27 +1060,11 @@ namespace utils
return;
}
const auto create_context = [this, &av](bool is_video) -> bool
const auto create_context = [this, &av, &selected_video_codec, &selected_audio_codec](bool is_video) -> bool
{
const std::string type = is_video ? "video" : "audio";
scoped_av::ctx& ctx = is_video ? av.video : av.audio;
if (is_video)
{
if (!(ctx.codec = avcodec_find_encoder(av.format_context->oformat->video_codec)))
{
media_log.error("video_encoder: avcodec_find_encoder for video failed. video_codec=%d", static_cast<int>(av.format_context->oformat->video_codec));
return false;
}
}
else
{
if (!(ctx.codec = avcodec_find_encoder(av.format_context->oformat->audio_codec)))
{
media_log.error("video_encoder: avcodec_find_encoder for audio failed. audio_codec=%d", static_cast<int>(av.format_context->oformat->audio_codec));
return false;
}
}
ctx.codec = is_video ? selected_video_codec : selected_audio_codec;
if (!(ctx.stream = avformat_new_stream(av.format_context, nullptr)))
{
+4 -2
View File
@@ -111,13 +111,13 @@ namespace utils
void set_framerate(u32 framerate);
void set_video_bitrate(u32 bitrate);
void set_output_format(frame_format format);
void set_video_codec(s32 codec_id);
void set_video_codec(s32 codec_id, std::string_view codec_name);
void set_max_b_frames(s32 max_b_frames);
void set_gop_size(s32 gop_size);
void set_sample_rate(u32 sample_rate);
void set_audio_channels(u32 channels);
void set_audio_bitrate(u32 bitrate);
void set_audio_codec(s32 codec_id);
void set_audio_codec(s32 codec_id, std::string_view codec_name);
void pause(bool flush = true) override;
void stop(bool flush = true) override;
void resume() override;
@@ -135,6 +135,7 @@ namespace utils
// Video parameters
u32 m_video_bitrate_bps = 0;
s32 m_video_codec_id = 12; // AV_CODEC_ID_MPEG4
std::string m_video_codec_name;
s32 m_max_b_frames = 2;
s32 m_gop_size = 12;
frame_format m_out_format{};
@@ -143,5 +144,6 @@ namespace utils
u32 m_channels = 2;
u32 m_audio_bitrate_bps = 320000;
s32 m_audio_codec_id = 86018; // AV_CODEC_ID_AAC
std::string m_audio_codec_name;
};
}
+1 -1
View File
@@ -1069,7 +1069,7 @@ static const bool s_tsc_freq_evaluated = []() -> bool
{
// Sleep between first and last sample
#ifdef _WIN32
Sleep(sleep_time_ms);
Sleep(static_cast<DWORD>(sleep_time_ms));
#else
usleep(sleep_time_ms * 1000);
#endif