Compare commits

...

68 Commits

Author SHA1 Message Date
Ani
974f7eb721
Merge branch 'master' into build-llvm-8462cff 2025-12-15 16:43:02 +00:00
Megamouse
cf87f24587 cellGem: improve bayer demosaicing
Some checks are pending
Generate Translation Template / Generate Translation Template (push) Waiting to run
Build RPCS3 / RPCS3 Linux ${{ matrix.os }} ${{ matrix.compiler }} (/rpcs3/.ci/build-linux-aarch64.sh, gcc, rpcs3/rpcs3-ci-jammy-aarch64:1.7, ubuntu-24.04-arm) (push) Waiting to run
Build RPCS3 / RPCS3 Linux ${{ matrix.os }} ${{ matrix.compiler }} (/rpcs3/.ci/build-linux.sh, gcc, rpcs3/rpcs3-ci-jammy:1.7, ubuntu-24.04) (push) Waiting to run
Build RPCS3 / RPCS3 Linux ${{ matrix.os }} ${{ matrix.compiler }} (a1d35836e8d45bfc6f63c26f0a3e5d46ef622fe1, rpcs3/rpcs3-binaries-linux-arm64, /rpcs3/.ci/build-linux-aarch64.sh, clang, rpcs3/rpcs3-ci-jammy-aarch64:1.7, ubuntu-24.04-arm) (push) Waiting to run
Build RPCS3 / RPCS3 Linux ${{ matrix.os }} ${{ matrix.compiler }} (d812f1254a1157c80fd402f94446310560f54e5f, rpcs3/rpcs3-binaries-linux, /rpcs3/.ci/build-linux.sh, clang, rpcs3/rpcs3-ci-jammy:1.7, ubuntu-24.04) (push) Waiting to run
Build RPCS3 / RPCS3 Mac ${{ matrix.name }} (51ae32f468089a8169aaf1567de355ff4a3e0842, rpcs3/rpcs3-binaries-mac, .ci/build-mac.sh, Intel) (push) Waiting to run
Build RPCS3 / RPCS3 Mac ${{ matrix.name }} (8e21bdbc40711a3fccd18fbf17b742348b0f4281, rpcs3/rpcs3-binaries-mac-arm64, .ci/build-mac-arm64.sh, Apple Silicon) (push) Waiting to run
Build RPCS3 / RPCS3 Windows (push) Waiting to run
Build RPCS3 / RPCS3 Windows Clang (win64, clang, clang64) (push) Waiting to run
Build RPCS3 / RPCS3 FreeBSD (push) Waiting to run
2025-12-15 14:20:34 +01:00
Megamouse
12a3818fcf Fix logging of gem configs 2025-12-15 14:20:34 +01:00
Ani
2f5843c6cf
Merge branch 'master' into build-llvm-8462cff 2025-12-15 11:56:45 +00:00
kd-11
d9da5f26c0 vk: Ignore memory pressure handling when allocating data heaps into ReBAR memory. 2025-12-15 11:53:47 +00:00
kd-11
1a3e150a62 vk: Extend memory allocation system to explicitly allow requesting no VRAM recovery on allocation fail. 2025-12-15 11:53:47 +00:00
Megamouse
c3db85c68e cellGem: Fix YUV conversions, implement gain, averaging and basic outlier detection
Some checks failed
Generate Translation Template / Generate Translation Template (push) Has been cancelled
Build RPCS3 / RPCS3 Linux ${{ matrix.os }} ${{ matrix.compiler }} (/rpcs3/.ci/build-linux-aarch64.sh, gcc, rpcs3/rpcs3-ci-jammy-aarch64:1.7, ubuntu-24.04-arm) (push) Has been cancelled
Build RPCS3 / RPCS3 Linux ${{ matrix.os }} ${{ matrix.compiler }} (/rpcs3/.ci/build-linux.sh, gcc, rpcs3/rpcs3-ci-jammy:1.7, ubuntu-24.04) (push) Has been cancelled
Build RPCS3 / RPCS3 Linux ${{ matrix.os }} ${{ matrix.compiler }} (a1d35836e8d45bfc6f63c26f0a3e5d46ef622fe1, rpcs3/rpcs3-binaries-linux-arm64, /rpcs3/.ci/build-linux-aarch64.sh, clang, rpcs3/rpcs3-ci-jammy-aarch64:1.7, ubuntu-24.04-arm) (push) Has been cancelled
Build RPCS3 / RPCS3 Linux ${{ matrix.os }} ${{ matrix.compiler }} (d812f1254a1157c80fd402f94446310560f54e5f, rpcs3/rpcs3-binaries-linux, /rpcs3/.ci/build-linux.sh, clang, rpcs3/rpcs3-ci-jammy:1.7, ubuntu-24.04) (push) Has been cancelled
Build RPCS3 / RPCS3 Mac ${{ matrix.name }} (51ae32f468089a8169aaf1567de355ff4a3e0842, rpcs3/rpcs3-binaries-mac, .ci/build-mac.sh, Intel) (push) Has been cancelled
Build RPCS3 / RPCS3 Mac ${{ matrix.name }} (8e21bdbc40711a3fccd18fbf17b742348b0f4281, rpcs3/rpcs3-binaries-mac-arm64, .ci/build-mac-arm64.sh, Apple Silicon) (push) Has been cancelled
Build RPCS3 / RPCS3 Windows (push) Has been cancelled
Build RPCS3 / RPCS3 Windows Clang (win64, clang, clang64) (push) Has been cancelled
Build RPCS3 / RPCS3 FreeBSD (push) Has been cancelled
2025-12-13 12:43:22 +01:00
kd-11
2b0456520e rsx/cfg: Fix edge case where an empty block is defined 2025-12-13 13:50:39 +03:00
Megamouse
0c455d12c9 Fix int -> float conversion warning
Some checks are pending
Generate Translation Template / Generate Translation Template (push) Waiting to run
Build RPCS3 / RPCS3 Linux ${{ matrix.os }} ${{ matrix.compiler }} (/rpcs3/.ci/build-linux-aarch64.sh, gcc, rpcs3/rpcs3-ci-jammy-aarch64:1.7, ubuntu-24.04-arm) (push) Waiting to run
Build RPCS3 / RPCS3 Linux ${{ matrix.os }} ${{ matrix.compiler }} (/rpcs3/.ci/build-linux.sh, gcc, rpcs3/rpcs3-ci-jammy:1.7, ubuntu-24.04) (push) Waiting to run
Build RPCS3 / RPCS3 Linux ${{ matrix.os }} ${{ matrix.compiler }} (a1d35836e8d45bfc6f63c26f0a3e5d46ef622fe1, rpcs3/rpcs3-binaries-linux-arm64, /rpcs3/.ci/build-linux-aarch64.sh, clang, rpcs3/rpcs3-ci-jammy-aarch64:1.7, ubuntu-24.04-arm) (push) Waiting to run
Build RPCS3 / RPCS3 Linux ${{ matrix.os }} ${{ matrix.compiler }} (d812f1254a1157c80fd402f94446310560f54e5f, rpcs3/rpcs3-binaries-linux, /rpcs3/.ci/build-linux.sh, clang, rpcs3/rpcs3-ci-jammy:1.7, ubuntu-24.04) (push) Waiting to run
Build RPCS3 / RPCS3 Mac ${{ matrix.name }} (51ae32f468089a8169aaf1567de355ff4a3e0842, rpcs3/rpcs3-binaries-mac, .ci/build-mac.sh, Intel) (push) Waiting to run
Build RPCS3 / RPCS3 Mac ${{ matrix.name }} (8e21bdbc40711a3fccd18fbf17b742348b0f4281, rpcs3/rpcs3-binaries-mac-arm64, .ci/build-mac-arm64.sh, Apple Silicon) (push) Waiting to run
Build RPCS3 / RPCS3 Windows (push) Waiting to run
Build RPCS3 / RPCS3 Windows Clang (win64, clang, clang64) (push) Waiting to run
Build RPCS3 / RPCS3 FreeBSD (push) Waiting to run
2025-12-13 03:55:44 +01:00
Megamouse
236f783466 overlays fix c++23 elifndef
This is a c++23 feature...
2025-12-13 03:55:44 +01:00
Megamouse
f739ce7323 cellGem: Implement CELL_CAMERA_RAW8 to CELL_GEM_BAYER_RESTORED_RASTERIZED
Some checks are pending
Generate Translation Template / Generate Translation Template (push) Waiting to run
Build RPCS3 / RPCS3 Linux ${{ matrix.os }} ${{ matrix.compiler }} (/rpcs3/.ci/build-linux-aarch64.sh, gcc, rpcs3/rpcs3-ci-jammy-aarch64:1.7, ubuntu-24.04-arm) (push) Waiting to run
Build RPCS3 / RPCS3 Linux ${{ matrix.os }} ${{ matrix.compiler }} (/rpcs3/.ci/build-linux.sh, gcc, rpcs3/rpcs3-ci-jammy:1.7, ubuntu-24.04) (push) Waiting to run
Build RPCS3 / RPCS3 Linux ${{ matrix.os }} ${{ matrix.compiler }} (a1d35836e8d45bfc6f63c26f0a3e5d46ef622fe1, rpcs3/rpcs3-binaries-linux-arm64, /rpcs3/.ci/build-linux-aarch64.sh, clang, rpcs3/rpcs3-ci-jammy-aarch64:1.7, ubuntu-24.04-arm) (push) Waiting to run
Build RPCS3 / RPCS3 Linux ${{ matrix.os }} ${{ matrix.compiler }} (d812f1254a1157c80fd402f94446310560f54e5f, rpcs3/rpcs3-binaries-linux, /rpcs3/.ci/build-linux.sh, clang, rpcs3/rpcs3-ci-jammy:1.7, ubuntu-24.04) (push) Waiting to run
Build RPCS3 / RPCS3 Mac ${{ matrix.name }} (51ae32f468089a8169aaf1567de355ff4a3e0842, rpcs3/rpcs3-binaries-mac, .ci/build-mac.sh, Intel) (push) Waiting to run
Build RPCS3 / RPCS3 Mac ${{ matrix.name }} (8e21bdbc40711a3fccd18fbf17b742348b0f4281, rpcs3/rpcs3-binaries-mac-arm64, .ci/build-mac-arm64.sh, Apple Silicon) (push) Waiting to run
Build RPCS3 / RPCS3 Windows (push) Waiting to run
Build RPCS3 / RPCS3 Windows Clang (win64, clang, clang64) (push) Waiting to run
Build RPCS3 / RPCS3 FreeBSD (push) Waiting to run
2025-12-12 10:31:26 +01:00
Megamouse
3c0558c822 cellGem: Implement CELL_CAMERA_RAW8 to CELL_GEM_BAYER_RESTORED_RGGB 2025-12-12 10:31:26 +01:00
Megamouse
c38146636a Update fusion to 1.2.11 2025-12-12 10:31:26 +01:00
Megamouse
26b0f822d8 ps_move_handler: enable orientation by default 2025-12-12 10:31:26 +01:00
Megamouse
ee9dc44059 ps_move_handler: fix decoding of ZCM1 sensor values 2025-12-12 10:31:26 +01:00
Megamouse
24745416c5 cellGem: fix compilation 2025-12-12 10:31:26 +01:00
Megamouse
fa4e2d1b42 ps_move_handler: fix trigger values depending on ps move version 2025-12-12 10:31:26 +01:00
Megamouse
c2284c962b move ps_move_data to own file 2025-12-12 10:31:26 +01:00
Megamouse
757e9a0493 cellGem: implement world coordinate orientation in cellGemGetState 2025-12-12 10:31:26 +01:00
Megamouse
f7cda4b2b4 cellGem: fix default orientation 2025-12-12 10:31:26 +01:00
Megamouse
c6ef09500a cellGem: fix division by zero 2025-12-12 10:31:26 +01:00
Megamouse
26ae6f0902 cellGem: fix default accelerometer value 2025-12-12 10:31:26 +01:00
Megamouse
1b01a9274c cellGem: clarify member descriptions 2025-12-12 10:31:26 +01:00
kd-11
0f1eadcab0 rsx/gtest: Drop unused function
Some checks are pending
Generate Translation Template / Generate Translation Template (push) Waiting to run
Build RPCS3 / RPCS3 Linux ${{ matrix.os }} ${{ matrix.compiler }} (/rpcs3/.ci/build-linux-aarch64.sh, gcc, rpcs3/rpcs3-ci-jammy-aarch64:1.7, ubuntu-24.04-arm) (push) Waiting to run
Build RPCS3 / RPCS3 Linux ${{ matrix.os }} ${{ matrix.compiler }} (/rpcs3/.ci/build-linux.sh, gcc, rpcs3/rpcs3-ci-jammy:1.7, ubuntu-24.04) (push) Waiting to run
Build RPCS3 / RPCS3 Linux ${{ matrix.os }} ${{ matrix.compiler }} (a1d35836e8d45bfc6f63c26f0a3e5d46ef622fe1, rpcs3/rpcs3-binaries-linux-arm64, /rpcs3/.ci/build-linux-aarch64.sh, clang, rpcs3/rpcs3-ci-jammy-aarch64:1.7, ubuntu-24.04-arm) (push) Waiting to run
Build RPCS3 / RPCS3 Linux ${{ matrix.os }} ${{ matrix.compiler }} (d812f1254a1157c80fd402f94446310560f54e5f, rpcs3/rpcs3-binaries-linux, /rpcs3/.ci/build-linux.sh, clang, rpcs3/rpcs3-ci-jammy:1.7, ubuntu-24.04) (push) Waiting to run
Build RPCS3 / RPCS3 Mac ${{ matrix.name }} (51ae32f468089a8169aaf1567de355ff4a3e0842, rpcs3/rpcs3-binaries-mac, .ci/build-mac.sh, Intel) (push) Waiting to run
Build RPCS3 / RPCS3 Mac ${{ matrix.name }} (8e21bdbc40711a3fccd18fbf17b742348b0f4281, rpcs3/rpcs3-binaries-mac-arm64, .ci/build-mac-arm64.sh, Apple Silicon) (push) Waiting to run
Build RPCS3 / RPCS3 Windows (push) Waiting to run
Build RPCS3 / RPCS3 Windows Clang (win64, clang, clang64) (push) Waiting to run
Build RPCS3 / RPCS3 FreeBSD (push) Waiting to run
2025-12-10 13:07:07 +03:00
kd-11
1ea3c121fa rsx/cfg: Fix delay-slot detection when copying from the same register index but different precision. 2025-12-10 13:07:07 +03:00
kd-11
2c6d3dde67 C++ Pro
- Different from C++ amateur.
- Addresses code review suggestions to step up the C++
2025-12-10 13:07:07 +03:00
kd-11
a412e34fa6 rsx/cfg/fp: Fix back-traversal for IF-ELSE pairs 2025-12-10 13:07:07 +03:00
kd-11
4dc52fde82 rsx/common: Implement list insert for simple_array 2025-12-10 13:07:07 +03:00
kd-11
5688573b3d rsx/fp: Fix pre-branch epilogue emit 2025-12-10 13:07:07 +03:00
kd-11
98a12de256 rsx/gtest: Enable unit tests for nix builds 2025-12-10 13:07:07 +03:00
kd-11
43d8518faa rsx/fp: Warning cleanup 2025-12-10 13:07:07 +03:00
kd-11
1e6fe1f4ab rsx/cfg/fp: Add delay-slot detection to remove unnecessary barriers
- Reduces emitted barriers by like 99%
2025-12-10 13:07:07 +03:00
kd-11
93f89b8a74 rsx/cfg: Fix instruction injection when more than one barrier is needed for a single instruction 2025-12-10 13:07:07 +03:00
kd-11
8cd241ca10 rsx/fp: Implement OR16_LO and OR16_HI instructions 2025-12-10 13:07:07 +03:00
kd-11
8d2f7ae85f rsx/cfg: Implement partial barriers for 32-bit register channels 2025-12-10 13:07:07 +03:00
kd-11
81d657a960 rsx/cfg: Fix grouping barrier16 instructions when lane is shared. 2025-12-10 13:07:07 +03:00
kd-11
4183d09a52 rsx/fp/cfg: Fix input mask for DIV and DIVSQ instructions 2025-12-10 13:07:07 +03:00
kd-11
316e01995b rsx/fp: Re-implement ROP output resolve 2025-12-10 13:07:07 +03:00
kd-11
f2913e4692 rsx/fp: Reimplement GLSL code generation from CFG 2025-12-10 13:07:07 +03:00
kd-11
d23ea4760b rsx/cfg: Handle nested IF/LOOP blocks falling out to unsuitable nodes (ELSE).
- In that case, find the node's END node and link to that instead. ELSE nodes are not reachable from children of the preceding IF.
- ELSE nodes are special this way, all other types of nodes are reachable by adjacency.
2025-12-10 13:07:07 +03:00
kd-11
26fd0510ab rsx/fp: Enable CFG passes and emit block epilogues 2025-12-10 13:07:07 +03:00
kd-11
e8e2d4b93d rsx/fp: Fix issues with FP decompiler using new CFG system 2025-12-10 13:07:07 +03:00
kd-11
a7f5514913 rsx/cfg: Fix dependency injection tests for FP 2025-12-10 13:07:07 +03:00
kd-11
3bed46b844 rsx/fp/cfg: Fix IF/ELSE and LOOP node linkage 2025-12-10 13:07:07 +03:00
kd-11
e1ec2f58bb rsx/fp/asm: Fix ELSE encoding 2025-12-10 13:07:07 +03:00
kd-11
91e19652de rsx/cfg/gtest: Rewrite CFG tests using the assembler
- Also extend IF-ELSE test to catch a broken succession chain
2025-12-10 13:07:07 +03:00
kd-11
b244c0fa0f rsx/cfg/gtest: Update unit tests 2025-12-10 13:07:07 +03:00
kd-11
f4ba548748 rsx/fp/asm: Implement asm support for branches
- Allows creating more complex command sequences from text
2025-12-10 13:07:07 +03:00
kd-11
856eaac1b6 rsx/cfg: Implement dependency injection pass with branches 2025-12-10 13:07:07 +03:00
kd-11
8ff3dda5e8 rsx/cfg: Skip literal constants when annotating instructions 2025-12-10 13:07:07 +03:00
kd-11
fa40cef0b1 rsx/fp/cfg: Insert bi-directional edges correctly during traversal 2025-12-10 13:07:07 +03:00
kd-11
65b4bcb027 rsx/fp/asm: Add support for more instructions 2025-12-10 13:07:07 +03:00
kd-11
5a8ad219ba rsx/asm: Implement dependency barrier injection pass 2025-12-10 13:07:07 +03:00
kd-11
ed397c5d67 rsx/asm: Add support for more opcodes and add tests for mixed block IO 2025-12-10 13:07:07 +03:00
kd-11
13d66f324b rsx/shaders: Fix nix builds 2025-12-10 13:07:07 +03:00
kd-11
aaaa6feb5a rsx: Add UTs for register annotation pass and fix uncovered bugs 2025-12-10 13:07:07 +03:00
kd-11
88a54e2d98 rsx/fp: Add a basic assembler to aid in test authoring 2025-12-10 13:07:07 +03:00
kd-11
9d30716aa8 rsx: Implement register annotation pass 2025-12-10 13:07:07 +03:00
kd-11
e20bae3cd7 rsx/asm: Stub out register annotation and dependency passes 2025-12-10 13:07:07 +03:00
Megamouse
aff645272f overlays/macOs: add some fallback fonts for cyrillic 2025-12-09 12:52:51 +01:00
Megamouse
27f39d2ac0 fmt: add more string_view versions of string functions 2025-12-08 19:49:42 +01:00
oltolm
c840c98e9e
bit_set.h: forward declare fmt_unveil
This change is necessary to fix the following clangd error in `StrFmt.h`

Redefinition of 'fmt_unveil'clang(redefinition)
bit_set.h(388, 8): Previous definition is here
2025-12-08 12:43:15 +02:00
RipleyTom
adcacc1119 Remove RPCN test server 2025-12-08 09:16:52 +01:00
RipleyTom
65458effa6 RPCN v1.5.0 2025-12-08 09:16:52 +01:00
Antonino Di Guardo
0f1d516d9a
Check available and required size when installing a package (#17829)
Show and check available and required size when installing a package.
Installation is not allowed, by disabling `Yes` and/or `Install` button,
if not enough disk space is detected.
On multi installation dialog box, installation size for each package is
reported in the list.

fixes #14440

</br>
</br>

### Single PKG installation - Not enough space available

![pr_single_no_space](https://github.com/user-attachments/assets/3fc83d3e-8219-4dca-a4ae-a351f3c4c662)

</br>
</br>

### Single PKG installation - Required space available

![pr_single_space](https://github.com/user-attachments/assets/84b6b4a0-ee4a-430e-b84f-60a754e0a4fa)

</br>
</br>

### Multi PKG installation - Not enough space available

![pr_no_space](https://github.com/user-attachments/assets/56af51a1-cd78-4ad5-818d-7707c0c45de4)

</br>
</br>

### Multi PKG installation - Required space available

![pr_space](https://github.com/user-attachments/assets/43cc66dd-7e98-46de-8dd3-859f8ebb2b05)

---------

Co-authored-by: Megamouse <studienricky89@googlemail.com>
2025-12-07 14:38:13 +01:00
oltolm
3f6529fecb UI: remove unnecessary QOverloads 2025-12-06 20:58:28 +01:00
schm1dtmac
b76ca8807f
(macOS) Support loading VK ICDs (in preparation for KosmicKrisp) 2025-12-06 19:21:54 +03:00
schm1dtmac
54206c62b3 Add OpenCV to macOS builds 2025-12-05 22:48:40 +01:00
123 changed files with 4661 additions and 1296 deletions

View File

@ -6,7 +6,9 @@ export HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=1
export HOMEBREW_NO_ENV_HINTS=1 export HOMEBREW_NO_ENV_HINTS=1
export HOMEBREW_NO_INSTALL_CLEANUP=1 export HOMEBREW_NO_INSTALL_CLEANUP=1
brew install -f --overwrite --quiet pipenv googletest ffmpeg@5 "llvm@$LLVM_COMPILER_VER" glew sdl3 vulkan-headers brew install -f --overwrite --quiet pipenv googletest opencv@4 ffmpeg@5 "llvm@$LLVM_COMPILER_VER" glew sdl3 vulkan-headers vulkan-loader
brew unlink --quiet ffmpeg qtbase qtsvg qtdeclarative
brew link -f --quiet "llvm@$LLVM_COMPILER_VER" ffmpeg@5 brew link -f --quiet "llvm@$LLVM_COMPILER_VER" ffmpeg@5
# moltenvk based on commit for 1.4.0 release # moltenvk based on commit for 1.4.0 release
@ -56,15 +58,14 @@ export SDL3_DIR="$BREW_PATH/opt/sdl3/lib/cmake/SDL3"
export PATH="$BREW_PATH/opt/llvm@$LLVM_COMPILER_VER/bin:$WORKDIR/qt-downloader/$QT_VER/clang_64/bin:$BREW_BIN:$BREW_SBIN:/usr/bin:/bin:/usr/sbin:/sbin:/opt/X11/bin:/Library/Apple/usr/bin:$PATH" export PATH="$BREW_PATH/opt/llvm@$LLVM_COMPILER_VER/bin:$WORKDIR/qt-downloader/$QT_VER/clang_64/bin:$BREW_BIN:$BREW_SBIN:/usr/bin:/bin:/usr/sbin:/sbin:/opt/X11/bin:/Library/Apple/usr/bin:$PATH"
export LDFLAGS="-L$BREW_PATH/lib $BREW_PATH/opt/ffmpeg@5/lib/libavcodec.dylib $BREW_PATH/opt/ffmpeg@5/lib/libavformat.dylib $BREW_PATH/opt/ffmpeg@5/lib/libavutil.dylib $BREW_PATH/opt/ffmpeg@5/lib/libswscale.dylib $BREW_PATH/opt/ffmpeg@5/lib/libswresample.dylib $BREW_PATH/opt/llvm@$LLVM_COMPILER_VER/lib/c++/libc++.1.dylib $BREW_PATH/lib/libSDL3.dylib $BREW_PATH/lib/libGLEW.dylib $BREW_PATH/opt/llvm@$LLVM_COMPILER_VER/lib/unwind/libunwind.1.dylib -Wl,-rpath,$BREW_PATH/lib" export LDFLAGS="-L$BREW_PATH/lib $BREW_PATH/opt/ffmpeg@5/lib/libavcodec.dylib $BREW_PATH/opt/ffmpeg@5/lib/libavformat.dylib $BREW_PATH/opt/ffmpeg@5/lib/libavutil.dylib $BREW_PATH/opt/ffmpeg@5/lib/libswscale.dylib $BREW_PATH/opt/ffmpeg@5/lib/libswresample.dylib $BREW_PATH/opt/llvm@$LLVM_COMPILER_VER/lib/c++/libc++.1.dylib $BREW_PATH/lib/libSDL3.dylib $BREW_PATH/lib/libGLEW.dylib $BREW_PATH/opt/llvm@$LLVM_COMPILER_VER/lib/unwind/libunwind.1.dylib -Wl,-rpath,$BREW_PATH/lib"
export CPPFLAGS="-I$BREW_PATH/include -I$BREW_PATH/include -no-pie -D__MAC_OS_X_VERSION_MIN_REQUIRED=140000" export CPPFLAGS="-I$BREW_PATH/include -no-pie -D__MAC_OS_X_VERSION_MIN_REQUIRED=140000"
export CFLAGS="-D__MAC_OS_X_VERSION_MIN_REQUIRED=140000" export CFLAGS="-D__MAC_OS_X_VERSION_MIN_REQUIRED=140000"
export LIBRARY_PATH="$BREW_PATH/lib" export LIBRARY_PATH="$BREW_PATH/lib"
export LD_LIBRARY_PATH="$BREW_PATH/lib" export LD_LIBRARY_PATH="$BREW_PATH/lib"
export VULKAN_SDK export VULKAN_SDK
VULKAN_SDK="$BREW_PATH/opt/molten-vk" VULKAN_SDK="$BREW_PATH/opt/molten-vk"
ln -s "$VULKAN_SDK/lib/libMoltenVK.dylib" "$VULKAN_SDK/lib/libvulkan.dylib" || true ln -s "$BREW_PATH/opt/vulkan-loader/lib/libvulkan.dylib" "$VULKAN_SDK/lib/libvulkan.dylib" || true
export VK_ICD_FILENAMES="$VULKAN_SDK/share/vulkan/icd.d/MoltenVK_icd.json"
export LLVM_DIR export LLVM_DIR
LLVM_DIR="$BREW_PATH/opt/llvm@$LLVM_COMPILER_VER" LLVM_DIR="$BREW_PATH/opt/llvm@$LLVM_COMPILER_VER"

View File

@ -12,7 +12,8 @@ brew link -f --overwrite --quiet "llvm@$LLVM_COMPILER_VER"
rm /usr/local/bin/{idle3.14,pip3.14,pydoc3.14,python3.14,python3.14-config} && \ rm /usr/local/bin/{idle3.14,pip3.14,pydoc3.14,python3.14,python3.14-config} && \
rm /usr/local/bin/{idle3,pip3,pydoc3,python3,python3-config} rm /usr/local/bin/{idle3,pip3,pydoc3,python3,python3-config}
arch -x86_64 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" arch -x86_64 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
arch -x86_64 /usr/local/bin/brew install -f --overwrite --quiet ffmpeg@5 "llvm@$LLVM_COMPILER_VER" glew sdl3 vulkan-headers arch -x86_64 /usr/local/bin/brew install -f --overwrite --quiet opencv@4 ffmpeg@5 "llvm@$LLVM_COMPILER_VER" glew sdl3 vulkan-headers vulkan-loader
arch -x86_64 /usr/local/bin/brew unlink --quiet ffmpeg qtbase qtsvg qtdeclarative
arch -x86_64 /usr/local/bin/brew link -f --overwrite --quiet "llvm@$LLVM_COMPILER_VER" ffmpeg@5 arch -x86_64 /usr/local/bin/brew link -f --overwrite --quiet "llvm@$LLVM_COMPILER_VER" ffmpeg@5
# moltenvk based on commit for 1.4.0 release # moltenvk based on commit for 1.4.0 release
@ -63,8 +64,7 @@ export LD_LIBRARY_PATH="$BREW_X64_PATH/opt/llvm@$LLVM_COMPILER_VER/lib:$BREW_X64
export VULKAN_SDK export VULKAN_SDK
VULKAN_SDK="$BREW_X64_PATH/opt/molten-vk" VULKAN_SDK="$BREW_X64_PATH/opt/molten-vk"
ln -s "$VULKAN_SDK/lib/libMoltenVK.dylib" "$VULKAN_SDK/lib/libvulkan.dylib" ln -s "$BREW_X64_PATH/opt/vulkan-loader/lib/libvulkan.dylib" "$VULKAN_SDK/lib/libvulkan.dylib"
export VK_ICD_FILENAMES="$VULKAN_SDK/share/vulkan/icd.d/MoltenVK_icd.json"
export LLVM_DIR export LLVM_DIR
LLVM_DIR="$BREW_X64_PATH/opt/llvm@$LLVM_COMPILER_VER" LLVM_DIR="$BREW_X64_PATH/opt/llvm@$LLVM_COMPILER_VER"

View File

@ -15,8 +15,13 @@ echo "AVVER=$AVVER" >> ../.ci/ci-vars.env
cd bin cd bin
mkdir "rpcs3.app/Contents/lib/" || true mkdir "rpcs3.app/Contents/lib/" || true
mkdir -p "rpcs3.app/Contents/Resources/vulkan/icd.d" || true
cp "$(realpath /opt/homebrew/lib/libMoltenVK.dylib)" "rpcs3.app/Contents/Frameworks/libMoltenVK.dylib"
cp "$(realpath /opt/homebrew/etc/vulkan/icd.d/MoltenVK_icd.json)" "rpcs3.app/Contents/Resources/vulkan/icd.d/MoltenVK_icd.json"
sed -i '' "s/lib\//Frameworks\//g" "rpcs3.app/Contents/Resources/vulkan/icd.d/MoltenVK_icd.json"
cp "$(realpath /opt/homebrew/opt/llvm@$LLVM_COMPILER_VER/lib/c++/libc++abi.1.0.dylib)" "rpcs3.app/Contents/Frameworks/libc++abi.1.dylib" cp "$(realpath /opt/homebrew/opt/llvm@$LLVM_COMPILER_VER/lib/c++/libc++abi.1.0.dylib)" "rpcs3.app/Contents/Frameworks/libc++abi.1.dylib"
cp "$(realpath /opt/homebrew/opt/gcc/lib/gcc/current/libgcc_s.1.1.dylib)" "rpcs3.app/Contents/Frameworks/libgcc_s.1.1.dylib"
cp "$(realpath /opt/homebrew/lib/libsharpyuv.0.dylib)" "rpcs3.app/Contents/lib/libsharpyuv.0.dylib" cp "$(realpath /opt/homebrew/lib/libsharpyuv.0.dylib)" "rpcs3.app/Contents/lib/libsharpyuv.0.dylib"
cp "$(realpath /opt/homebrew/lib/libintl.8.dylib)" "rpcs3.app/Contents/lib/libintl.8.dylib" cp "$(realpath /opt/homebrew/lib/libintl.8.dylib)" "rpcs3.app/Contents/lib/libintl.8.dylib"

View File

@ -14,10 +14,15 @@ AVVER="${COMM_TAG}-${COMM_COUNT}"
echo "AVVER=$AVVER" >> ../.ci/ci-vars.env echo "AVVER=$AVVER" >> ../.ci/ci-vars.env
cd bin cd bin
mkdir "rpcs3.app/Contents/lib/" mkdir "rpcs3.app/Contents/lib/" || true
mkdir -p "rpcs3.app/Contents/Resources/vulkan/icd.d" || true
cp "$(realpath /usr/local/lib/libMoltenVK.dylib)" "rpcs3.app/Contents/Frameworks/libMoltenVK.dylib"
cp "$(realpath /usr/local/etc/vulkan/icd.d/MoltenVK_icd.json)" "rpcs3.app/Contents/Resources/vulkan/icd.d/MoltenVK_icd.json"
sed -i '' "s/lib\//Frameworks\//g" "rpcs3.app/Contents/Resources/vulkan/icd.d/MoltenVK_icd.json"
cp "/usr/local/opt/llvm@$LLVM_COMPILER_VER/lib/c++/libc++abi.1.0.dylib" "rpcs3.app/Contents/Frameworks/libc++abi.1.dylib" cp "$(realpath /usr/local/opt/llvm@$LLVM_COMPILER_VER/lib/c++/libc++abi.1.0.dylib)" "rpcs3.app/Contents/Frameworks/libc++abi.1.dylib"
cp "/usr/local/opt/llvm@$LLVM_COMPILER_VER/lib/unwind/libunwind.1.dylib" "rpcs3.app/Contents/Frameworks/libunwind.1.dylib" cp "$(realpath /usr/local/opt/llvm@$LLVM_COMPILER_VER/lib/unwind/libunwind.1.dylib)" "rpcs3.app/Contents/Frameworks/libunwind.1.dylib"
cp "$(realpath /usr/local/opt/gcc/lib/gcc/current/libgcc_s.1.1.dylib)" "rpcs3.app/Contents/Frameworks/libgcc_s.1.1.dylib"
cp "$(realpath /usr/local/lib/libsharpyuv.0.dylib)" "rpcs3.app/Contents/lib/libsharpyuv.0.dylib" cp "$(realpath /usr/local/lib/libsharpyuv.0.dylib)" "rpcs3.app/Contents/lib/libsharpyuv.0.dylib"
cp "$(realpath /usr/local/lib/libintl.8.dylib)" "rpcs3.app/Contents/lib/libintl.8.dylib" cp "$(realpath /usr/local/lib/libintl.8.dylib)" "rpcs3.app/Contents/lib/libintl.8.dylib"

@ -1 +1 @@
Subproject commit 595bf0007ab1929570c7671f091313c8fc20644e Subproject commit 187240970746d00bbd26b0f5873ed54d2477f9f3

@ -1 +1 @@
Subproject commit 759ac5d698baefca53f1975a0bb1d2dcbdb9f836 Subproject commit 008e03eac0ac1d5f85e16f5fcaefdda3fee75cb8

View File

@ -83,7 +83,8 @@ std::string fmt::win_error_to_string(unsigned long error, void* module_handle)
if (FormatMessageW((module_handle ? FORMAT_MESSAGE_FROM_HMODULE : FORMAT_MESSAGE_FROM_SYSTEM) | FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS, if (FormatMessageW((module_handle ? FORMAT_MESSAGE_FROM_HMODULE : FORMAT_MESSAGE_FROM_SYSTEM) | FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS,
module_handle, error, 0, reinterpret_cast<LPWSTR>(&message_buffer), 0, nullptr)) module_handle, error, 0, reinterpret_cast<LPWSTR>(&message_buffer), 0, nullptr))
{ {
message = fmt::format("%s (0x%x)", fmt::trim(wchar_to_utf8(message_buffer), " \t\n\r\f\v"), error); const std::string utf8 = wchar_to_utf8(message_buffer);
message = fmt::format("%s (0x%x)", fmt::trim_sv(utf8, " \t\n\r\f\v"), error);
} }
else else
{ {
@ -823,6 +824,50 @@ std::vector<std::string> fmt::split(std::string_view source, std::initializer_li
return result; return result;
} }
std::vector<std::string_view> fmt::split_sv(std::string_view source, std::initializer_list<std::string_view> separators, bool is_skip_empty)
{
std::vector<std::string_view> result;
for (usz index = 0; index < source.size();)
{
usz pos = -1;
usz sep_size = 0;
for (auto& separator : separators)
{
if (usz pos0 = source.find(separator, index); pos0 < pos)
{
pos = pos0;
sep_size = separator.size();
}
}
if (!sep_size)
{
result.emplace_back(&source[index], source.size() - index);
return result;
}
std::string_view piece = {&source[index], pos - index};
index = pos + sep_size;
if (piece.empty() && is_skip_empty)
{
continue;
}
result.emplace_back(std::move(piece));
}
if (result.empty() && !is_skip_empty)
{
result.emplace_back();
}
return result;
}
std::string fmt::trim(const std::string& source, std::string_view values) std::string fmt::trim(const std::string& source, std::string_view values)
{ {
const usz begin = source.find_first_not_of(values); const usz begin = source.find_first_not_of(values);
@ -838,6 +883,21 @@ std::string fmt::trim(const std::string& source, std::string_view values)
return source.substr(begin, end + 1 - begin); return source.substr(begin, end + 1 - begin);
} }
std::string_view fmt::trim_sv(std::string_view source, std::string_view values)
{
const usz begin = source.find_first_not_of(values);
if (begin == source.npos)
return {};
const usz end = source.find_last_not_of(values);
if (end == source.npos)
return source.substr(begin);
return source.substr(begin, end + 1 - begin);
}
std::string fmt::trim_front(const std::string& source, std::string_view values) std::string fmt::trim_front(const std::string& source, std::string_view values)
{ {
const usz begin = source.find_first_not_of(values); const usz begin = source.find_first_not_of(values);
@ -848,12 +908,32 @@ std::string fmt::trim_front(const std::string& source, std::string_view values)
return source.substr(begin); return source.substr(begin);
} }
std::string_view fmt::trim_front_sv(std::string_view source, std::string_view values)
{
const usz begin = source.find_first_not_of(values);
if (begin == source.npos)
return {};
return source.substr(begin);
}
void fmt::trim_back(std::string& source, std::string_view values) void fmt::trim_back(std::string& source, std::string_view values)
{ {
const usz index = source.find_last_not_of(values); const usz index = source.find_last_not_of(values);
source.resize(index + 1); source.resize(index + 1);
} }
std::string_view fmt::trim_back_sv(std::string_view source, std::string_view values)
{
const usz index = source.find_last_not_of(values);
if (index == std::string_view::npos)
return {};
source.remove_suffix(source.size() - (index + 1));
return source;
}
std::string fmt::to_upper(std::string_view string) std::string fmt::to_upper(std::string_view string)
{ {
std::string result; std::string result;

View File

@ -139,57 +139,90 @@ namespace fmt
// Splits the string into a vector of strings using the separators. The vector may contain empty strings unless is_skip_empty is true. // Splits the string into a vector of strings using the separators. The vector may contain empty strings unless is_skip_empty is true.
std::vector<std::string> split(std::string_view source, std::initializer_list<std::string_view> separators, bool is_skip_empty = true); std::vector<std::string> split(std::string_view source, std::initializer_list<std::string_view> separators, bool is_skip_empty = true);
// Splits the string_view into a vector of string_views using the separators. The vector may contain empty string_views unless is_skip_empty is true.
std::vector<std::string_view> split_sv(std::string_view source, std::initializer_list<std::string_view> separators, bool is_skip_empty = true);
// Removes all preceding and trailing characters specified by 'values' from 'source'. // Removes all preceding and trailing characters specified by 'values' from 'source'.
std::string trim(const std::string& source, std::string_view values = " \t"); std::string trim(const std::string& source, std::string_view values = " \t");
// Removes all preceding and trailing characters specified by 'values' from 'source' and returns the result.
std::string_view trim_sv(std::string_view source, std::string_view values = " \t");
// Removes all preceding characters specified by 'values' from 'source'. // Removes all preceding characters specified by 'values' from 'source'.
std::string trim_front(const std::string& source, std::string_view values = " \t"); std::string trim_front(const std::string& source, std::string_view values = " \t");
// Removes all preceding characters specified by 'values' from 'source' and returns the result.
std::string_view trim_front_sv(std::string_view source, std::string_view values = " \t");
// Removes all trailing characters specified by 'values' from 'source'. // Removes all trailing characters specified by 'values' from 'source'.
void trim_back(std::string& source, std::string_view values = " \t"); void trim_back(std::string& source, std::string_view values = " \t");
// Removes all trailing characters specified by 'values' from 'source' and returns the result.
std::string_view trim_back_sv(std::string_view source, std::string_view values = " \t");
template <typename T> template <typename T>
std::string merge(const T& source, const std::string& separator) std::string merge(const T& source, std::string_view separator)
{ {
if (source.empty()) if (source.empty())
{ {
return {}; return {};
} }
usz total = (source.size() - 1) * separator.size();
for (const auto& s : source)
{
total += s.size();
}
std::string result; std::string result;
result.reserve(total);
auto it = source.begin(); auto it = source.begin();
auto end = source.end(); auto end = source.end();
for (--end; it != end; ++it) for (--end; it != end; ++it)
{ {
result += std::string{*it} + separator; result.append(*it);
if (!separator.empty())
result.append(separator);
} }
return result + std::string{source.back()}; return result.append(source.back());
} }
template <typename T> template <typename T>
std::string merge(std::initializer_list<T> sources, const std::string& separator) std::string merge(std::initializer_list<T> sources, std::string_view separator)
{ {
if (!sources.size()) if (!sources.size())
{ {
return {}; return {};
} }
usz total = (sources.size() - 1) * separator.size();
for (const auto& s : sources)
{
if (s.empty()) continue;
total += s.size() + (s.size() - 1) * separator.size();
}
std::string result; std::string result;
result.reserve(total);
bool first = true; bool first = true;
for (const auto& v : sources) for (const auto& v : sources)
{ {
if (first) if (first)
{ {
result = fmt::merge(v, separator); first = false;
first = false;
} }
else else if (!separator.empty())
{ {
result += separator + fmt::merge(v, separator); result.append(separator);
} }
result.append(fmt::merge(v, separator));
} }
return result; return result;

View File

@ -23,7 +23,6 @@ Intersection (&) and symmetric difference (^) is also available.
#include "util/types.hpp" #include "util/types.hpp"
#include "util/atomic.hpp" #include "util/atomic.hpp"
#include "Utilities/StrFmt.h"
template <typename T> template <typename T>
concept BitSetEnum = std::is_enum_v<T> && requires(T x) concept BitSetEnum = std::is_enum_v<T> && requires(T x)
@ -384,6 +383,9 @@ public:
} }
}; };
template <typename T>
struct fmt_unveil;
template <typename T> template <typename T>
struct fmt_unveil<bs_t<T>> struct fmt_unveil<bs_t<T>>
{ {

View File

@ -27,9 +27,9 @@ void fmt_class_string<cheat_type>::format(std::string& out, u64 arg)
}); });
} }
bool cheat_info::from_str(const std::string& cheat_line) bool cheat_info::from_str(std::string_view cheat_line)
{ {
auto cheat_vec = fmt::split(cheat_line, {"@@@"}, false); const auto cheat_vec = fmt::split(cheat_line, {"@@@"}, false);
s64 val64 = 0; s64 val64 = 0;
if (cheat_vec.size() != 5 || !try_to_int64(&val64, cheat_vec[2], 0, cheat_type_max - 1)) if (cheat_vec.size() != 5 || !try_to_int64(&val64, cheat_vec[2], 0, cheat_type_max - 1))

View File

@ -28,6 +28,6 @@ struct cheat_info
u32 offset{}; u32 offset{};
std::string red_script{}; std::string red_script{};
bool from_str(const std::string& cheat_line); bool from_str(std::string_view cheat_line);
std::string to_str() const; std::string to_str() const;
}; };

View File

@ -193,6 +193,7 @@ if(BUILD_RPCS3_TESTS)
tests/test_simple_array.cpp tests/test_simple_array.cpp
tests/test_address_range.cpp tests/test_address_range.cpp
tests/test_rsx_cfg.cpp tests/test_rsx_cfg.cpp
tests/test_rsx_fp_asm.cpp
) )
target_link_libraries(rpcs3_test target_link_libraries(rpcs3_test

View File

@ -598,15 +598,15 @@ bool package_reader::read_param_sfo()
const bool is_psp = (entry.type & PKG_FILE_ENTRY_PSP) != 0u; const bool is_psp = (entry.type & PKG_FILE_ENTRY_PSP) != 0u;
std::string name(entry.name_size + BUF_PADDING, '\0'); std::string name_buf(entry.name_size + BUF_PADDING, '\0');
if (usz read_size = decrypt(entry.name_offset, entry.name_size, is_psp ? PKG_AES_KEY2 : m_dec_key.data(), name.data()); read_size < entry.name_size) if (usz read_size = decrypt(entry.name_offset, entry.name_size, is_psp ? PKG_AES_KEY2 : m_dec_key.data(), name_buf.data()); read_size < entry.name_size)
{ {
pkg_log.error("PKG name could not be read (size=0x%x, offset=0x%x)", entry.name_size, entry.name_offset); pkg_log.error("PKG name could not be read (size=0x%x, offset=0x%x)", entry.name_size, entry.name_offset);
continue; continue;
} }
fmt::trim_back(name, "\0"sv); std::string_view name = fmt::trim_back_sv(name_buf, "\0"sv);
// We're looking for the PARAM.SFO file, if there is any // We're looking for the PARAM.SFO file, if there is any
if (usz ndelim = name.find_first_not_of('/'); ndelim == umax || name.substr(ndelim) != "PARAM.SFO") if (usz ndelim = name.find_first_not_of('/'); ndelim == umax || name.substr(ndelim) != "PARAM.SFO")
@ -854,18 +854,18 @@ bool package_reader::fill_data(std::map<std::string, install_entry*>& all_instal
break; break;
} }
std::string name(entry.name_size + BUF_PADDING, '\0'); std::string name_buf(entry.name_size + BUF_PADDING, '\0');
const bool is_psp = (entry.type & PKG_FILE_ENTRY_PSP) != 0u; const bool is_psp = (entry.type & PKG_FILE_ENTRY_PSP) != 0u;
if (const usz read_size = decrypt(entry.name_offset, entry.name_size, is_psp ? PKG_AES_KEY2 : m_dec_key.data(), name.data()); read_size < entry.name_size) if (const usz read_size = decrypt(entry.name_offset, entry.name_size, is_psp ? PKG_AES_KEY2 : m_dec_key.data(), name_buf.data()); read_size < entry.name_size)
{ {
num_failures++; num_failures++;
pkg_log.error("PKG name could not be read (size=0x%x, offset=0x%x)", entry.name_size, entry.name_offset); pkg_log.error("PKG name could not be read (size=0x%x, offset=0x%x)", entry.name_size, entry.name_offset);
break; break;
} }
fmt::trim_back(name, "\0"sv); std::string_view name = fmt::trim_back_sv(name_buf, "\0"sv);
std::string path = m_install_path + vfs::escape(name); std::string path = m_install_path + vfs::escape(name);

View File

@ -347,6 +347,7 @@ public:
}; };
bool is_valid() const { return m_is_valid; } bool is_valid() const { return m_is_valid; }
const PKGHeader& get_header() const { return m_header; }
package_install_result check_target_app_version() const; 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); 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; } const psf::registry& get_psf() const { return m_psf; }

View File

@ -12,6 +12,7 @@
#include <cstring> #include <cstring>
#include <cstdio> #include <cstdio>
#include <ctime> #include <ctime>
#include "Utilities/StrFmt.h"
#include "Utilities/StrUtil.h" #include "Utilities/StrUtil.h"
#include "Utilities/File.h" #include "Utilities/File.h"

View File

@ -411,6 +411,7 @@ target_sources(rpcs3_emu PRIVATE
Io/pad_config_types.cpp Io/pad_config_types.cpp
Io/pad_types.cpp Io/pad_types.cpp
Io/PadHandler.cpp Io/PadHandler.cpp
Io/ps_move_data.cpp
Io/rb3drums_config.cpp Io/rb3drums_config.cpp
Io/RB3MidiDrums.cpp Io/RB3MidiDrums.cpp
Io/RB3MidiGuitar.cpp Io/RB3MidiGuitar.cpp
@ -516,12 +517,15 @@ target_sources(rpcs3_emu PRIVATE
RSX/Overlays/overlay_video.cpp RSX/Overlays/overlay_video.cpp
RSX/Overlays/Shaders/shader_loading_dialog.cpp RSX/Overlays/Shaders/shader_loading_dialog.cpp
RSX/Overlays/Shaders/shader_loading_dialog_native.cpp RSX/Overlays/Shaders/shader_loading_dialog_native.cpp
RSX/Program/Assembler/FPASM.cpp
RSX/Program/Assembler/FPOpcodes.cpp
RSX/Program/Assembler/FPToCFG.cpp RSX/Program/Assembler/FPToCFG.cpp
RSX/Program/Assembler/Passes/FP/RegisterAnnotationPass.cpp
RSX/Program/Assembler/Passes/FP/RegisterDependencyPass.cpp
RSX/Program/CgBinaryProgram.cpp RSX/Program/CgBinaryProgram.cpp
RSX/Program/CgBinaryFragmentProgram.cpp RSX/Program/CgBinaryFragmentProgram.cpp
RSX/Program/CgBinaryVertexProgram.cpp RSX/Program/CgBinaryVertexProgram.cpp
RSX/Program/FragmentProgramDecompiler.cpp RSX/Program/FragmentProgramDecompiler.cpp
RSX/Program/FragmentProgramRegister.cpp
RSX/Program/GLSLCommon.cpp RSX/Program/GLSLCommon.cpp
RSX/Program/ProgramStateCache.cpp RSX/Program/ProgramStateCache.cpp
RSX/Program/program_util.cpp RSX/Program/program_util.cpp
@ -623,6 +627,11 @@ if(TARGET 3rdparty_vulkan)
RSX/VK/VKTextureCache.cpp RSX/VK/VKTextureCache.cpp
RSX/VK/VulkanAPI.cpp RSX/VK/VulkanAPI.cpp
) )
if(APPLE)
target_sources(rpcs3_emu PRIVATE
RSX/VK/vkutils/metal_layer.mm
)
endif()
endif() endif()
find_package(Threads REQUIRED) find_package(Threads REQUIRED)

View File

@ -276,6 +276,8 @@ public:
u64 start_timestamp_us = 0; u64 start_timestamp_us = 0;
std::array<ps_move_data, CELL_GEM_MAX_NUM> fake_move_data {}; // No need to be in savestate
atomic_t<u32> m_wake_up = 0; atomic_t<u32> m_wake_up = 0;
atomic_t<u32> m_done = 0; atomic_t<u32> m_done = 0;
@ -624,9 +626,9 @@ public:
cellGem.notice("Could not load mouse gem config. Using defaults."); cellGem.notice("Could not load mouse gem config. Using defaults.");
} }
cellGem.notice("Real gem config=\n", g_cfg_gem_real.to_string()); cellGem.notice("Real gem config=%s", g_cfg_gem_real.to_string());
cellGem.notice("Fake gem config=\n", g_cfg_gem_fake.to_string()); cellGem.notice("Fake gem config=%s", g_cfg_gem_fake.to_string());
cellGem.notice("Mouse gem config=\n", g_cfg_gem_mouse.to_string()); cellGem.notice("Mouse gem config=%s", g_cfg_gem_mouse.to_string());
} }
}; };
@ -696,22 +698,165 @@ namespace gem
{ {
} }
static inline u8 Y(u8 r, u8 g, u8 b) { return static_cast<u8>(0.299f * r + 0.587f * g + 0.114f * b); } YUV(const u8 rgb[3])
static inline u8 U(u8 r, u8 g, u8 b) { return static_cast<u8>(-0.14713f * r - 0.28886f * g + 0.436f * b); } {
static inline u8 V(u8 r, u8 g, u8 b) { return static_cast<u8>(0.615f * r - 0.51499f * g - 0.10001f * b); } const u8 r = rgb[0];
const u8 g = rgb[1];
const u8 b = rgb[2];
y = Y(r, g, b);
u = U(r, g, b);
v = V(r, g, b);
}
static inline u8 Y(u8 r, u8 g, u8 b) { return static_cast<u8>(std::clamp(0.299f * r + 0.587f * g + 0.114f * b, 0.0f, 255.0f)); }
static inline u8 U(u8 r, u8 g, u8 b) { return static_cast<u8>(std::clamp(-0.169f * r - 0.331f * g + 0.499f * b + 128, 0.0f, 255.0f)); }
static inline u8 V(u8 r, u8 g, u8 b) { return static_cast<u8>(std::clamp(0.499f * r - 0.460f * g - 0.040f * b + 128, 0.0f, 255.0f)); }
}; };
bool convert_image_format(CellCameraFormat input_format, CellGemVideoConvertFormatEnum output_format, template <bool use_gain>
const std::vector<u8>& video_data_in, u32 width, u32 height, static inline void debayer_raw8_impl(const u8* src, u8* dst, u8 alpha, f32 gain_r, f32 gain_g, f32 gain_b)
u8* video_data_out, u32 video_data_out_size, std::string_view caller)
{ {
if (output_format != CELL_GEM_NO_VIDEO_OUTPUT && !video_data_out) constexpr u32 in_pitch = 640;
constexpr u32 out_pitch = 640 * 4;
// HamiltonAdams demosaicing
for (s32 y = 0; y < 480; y++)
{
const bool is_even_y = (y % 2) == 0;
const u8* srcc = src + y * in_pitch;
const u8* srcu = src + std::max(0, y - 1) * in_pitch;
const u8* srcd = src + std::min(480 - 1, y + 1) * in_pitch;
u8* dst0 = dst + y * out_pitch;
// Split loops (roughly twice the performance by removing one condition)
if (is_even_y)
{
for (s32 x = 0; x < 640; x++, dst0 += 4)
{
const bool is_even_x = (x % 2) == 0;
const int xl = std::max(0, x - 1);
const int xr = std::min(640 - 1, x + 1);
u8 r, b, g;
if (is_even_x)
{
// Blue pixel
const u8 up = srcu[x];
const u8 down = srcd[x];
const u8 left = srcc[xl];
const u8 right = srcc[xr];
const int dh = std::abs(int(left) - int(right));
const int dv = std::abs(int(up) - int(down));
r = (srcu[xl] + srcu[xr] + srcd[xl] + srcd[xr]) / 4;
if (dh < dv)
g = (left + right) / 2;
else if (dv < dh)
g = (up + down) / 2;
else
g = (up + down + left + right) / 4;
b = srcc[x];
}
else
{
// Green (on blue row)
r = (srcu[x] + srcd[x]) / 2;
g = srcc[x];
b = (srcc[xl] + srcc[xr]) / 2;
}
if constexpr (use_gain)
{
dst0[0] = static_cast<u8>(std::clamp(r * gain_r, 0.0f, 255.0f));
dst0[1] = static_cast<u8>(std::clamp(b * gain_b, 0.0f, 255.0f));
dst0[2] = static_cast<u8>(std::clamp(g * gain_g, 0.0f, 255.0f));
}
else
{
dst0[0] = r;
dst0[1] = g;
dst0[2] = b;
}
dst0[3] = alpha;
}
}
else
{
for (s32 x = 0; x < 640; x++, dst0 += 4)
{
const bool is_even_x = (x % 2) == 0;
const int xl = std::max(0, x - 1);
const int xr = std::min(640 - 1, x + 1);
u8 r, b, g;
if (is_even_x)
{
// Green (on red row)
r = (srcc[xl] + srcc[xr]) / 2;
g = srcc[x];
b = (srcu[x] + srcd[x]) / 2;
}
else
{
// Red pixel
const u8 up = srcu[x];
const u8 down = srcd[x];
const u8 left = srcc[xl];
const u8 right = srcc[xr];
const int dh = std::abs(int(left) - int(right));
const int dv = std::abs(int(up) - int(down));
r = srcc[x];
if (dh < dv)
g = (left + right) / 2;
else if (dv < dh)
g = (up + down) / 2;
else
g = (up + down + left + right) / 4;
b = (srcu[xl] + srcu[xr] + srcd[xl] + srcd[xr]) / 4;
}
if constexpr (use_gain)
{
dst0[0] = static_cast<u8>(std::clamp(r * gain_r, 0.0f, 255.0f));
dst0[1] = static_cast<u8>(std::clamp(b * gain_b, 0.0f, 255.0f));
dst0[2] = static_cast<u8>(std::clamp(g * gain_g, 0.0f, 255.0f));
}
else
{
dst0[0] = r;
dst0[1] = g;
dst0[2] = b;
}
dst0[3] = alpha;
}
}
}
}
static void debayer_raw8(const u8* src, u8* dst, u8 alpha, f32 gain_r, f32 gain_g, f32 gain_b)
{
if (gain_r != 1.0f || gain_g != 1.0f || gain_b != 1.0f)
debayer_raw8_impl<true>(src, dst, alpha, gain_r, gain_g, gain_b);
else
debayer_raw8_impl<false>(src, dst, alpha, gain_r, gain_g, gain_b);
}
bool convert_image_format(CellCameraFormat input_format, const CellGemVideoConvertAttribute& vc,
const std::vector<u8>& video_data_in, u32 width, u32 height,
u8* video_data_out, u32 video_data_out_size, u8* buffer_memory,
std::string_view caller)
{
if (vc.output_format != CELL_GEM_NO_VIDEO_OUTPUT && !video_data_out)
{ {
return false; return false;
} }
const u32 required_in_size = get_buffer_size_by_format(static_cast<s32>(input_format), width, height); const u32 required_in_size = get_buffer_size_by_format(static_cast<s32>(input_format), width, height);
const s32 required_out_size = cellGemGetVideoConvertSize(output_format); const s32 required_out_size = cellGemGetVideoConvertSize(vc.output_format);
if (video_data_in.size() != required_in_size) if (video_data_in.size() != required_in_size)
{ {
@ -721,7 +866,7 @@ namespace gem
if (required_out_size < 0 || video_data_out_size != static_cast<u32>(required_out_size)) if (required_out_size < 0 || video_data_out_size != static_cast<u32>(required_out_size))
{ {
cellGem.error("convert: out_size unknown: required=%d, actual=%d, format %d (called from %s)", required_out_size, video_data_out_size, output_format, caller); cellGem.error("convert: out_size unknown: required=%d, actual=%d, format %d (called from %s)", required_out_size, video_data_out_size, vc.output_format, caller);
return false; return false;
} }
@ -730,7 +875,121 @@ namespace gem
return false; return false;
} }
switch (output_format) thread_local std::vector<u8> corrected_buffer;
thread_local std::vector<u8> combined_buffer;
thread_local std::vector<u8> conversion_buffer;
const u8* src_data = video_data_in.data();
const u8 alpha = vc.alpha;
const f32 gain_r = vc.gain * vc.blue_gain;
const f32 gain_g = vc.gain * vc.green_gain;
const f32 gain_b = vc.gain * vc.red_gain;
// Only RAW8 should be relevant for cellGem unless I'm mistaken
if (input_format == CELL_CAMERA_RAW8)
{
// TODO: CELL_GEM_AUTO_WHITE_BALANCE
// TODO: CELL_GEM_GAMMA_BOOST
// Correct outliers
if (vc.conversion_flags & CELL_GEM_FILTER_OUTLIER_PIXELS)
{
corrected_buffer.resize(width * height);
for (u32 y = 0; y < height; y++)
{
const u8* src = src_data + y * 640;
u8* dst = &corrected_buffer[y * 640];
for (u32 x = 0; x < width; x++, src++)
{
// Let's just say these 2 are outliers
if (const u8 val = *src; val > 0 && val < 255)
{
*dst++ = val;
continue;
}
// Just take the 4 neighbours for now
s32 sum = 0;
if (y >= 2) sum += *(src - (2 * 640));
if (x >= 2) sum += *(src - 2);
if (x < 638) sum += *(src + 2);
if (y < 478) sum += *(src + (2 * 640));
*dst++ = sum / 4; // Ignore count. It will only be less than 4 on the edges
}
}
src_data = corrected_buffer.data();
}
// Combine with previous frame
if (buffer_memory && (vc.conversion_flags & CELL_GEM_COMBINE_PREVIOUS_INPUT_FRAME))
{
combined_buffer.resize(width * height);
for (u32 i = 0; i < combined_buffer.size(); i++)
{
const u8 val = src_data[i];
u8& old = buffer_memory[i];
combined_buffer[i] = (old + val) / 2;
old = val;
}
src_data = combined_buffer.data();
}
switch (vc.output_format)
{
case CELL_GEM_YUV_640x480:
case CELL_GEM_YUV422_640x480:
case CELL_GEM_YUV411_640x480:
{
// Let's debayer the image first for YUV formats
conversion_buffer.resize(cellGemGetVideoConvertSize(CELL_GEM_RGBA_640x480));
debayer_raw8(src_data, conversion_buffer.data(), alpha, gain_r, gain_g, gain_b);
src_data = conversion_buffer.data();
input_format = CELL_CAMERA_RGBA;
width = 640;
height = 480;
break;
}
case CELL_GEM_BAYER_RESTORED:
case CELL_GEM_BAYER_RESTORED_RGGB:
case CELL_GEM_BAYER_RESTORED_RASTERIZED:
{
// Let's apply gain
if (gain_r != 1.0f || gain_g != 1.0f || gain_b != 1.0f)
{
conversion_buffer.resize(cellGemGetVideoConvertSize(CELL_GEM_RGBA_640x480));
const f32 bggr_gains[2][2] = {{gain_b, gain_g}, {gain_g, gain_r}};
const u8* src = src_data;
u8* dst = conversion_buffer.data();
for (u32 y = 0; y < 480; y++)
{
const f32* gains = bggr_gains[y % 2];
for (u32 x = 0; x < 640; x++)
{
*dst++ = static_cast<u8>(std::clamp(*src++ * gains[x % 2], 0.0f, 255.0f));
}
}
src_data = conversion_buffer.data();
}
break;
}
default:
break;
}
}
switch (vc.output_format)
{ {
case CELL_GEM_RGBA_640x480: // RGBA output; 640*480*4-byte output buffer required case CELL_GEM_RGBA_640x480: // RGBA output; 640*480*4-byte output buffer required
{ {
@ -738,51 +997,18 @@ namespace gem
{ {
case CELL_CAMERA_RAW8: case CELL_CAMERA_RAW8:
{ {
const u32 in_pitch = width; debayer_raw8(src_data, video_data_out, alpha, gain_r, gain_g, gain_b);
const u32 out_pitch = width * 4;
for (u32 y = 0; y < height - 1; y += 2)
{
const u8* src0 = &video_data_in[y * in_pitch];
const u8* src1 = src0 + in_pitch;
u8* dst0 = video_data_out + y * out_pitch;
u8* dst1 = dst0 + out_pitch;
for (u32 x = 0; x < width - 1; x += 2, src0 += 2, src1 += 2, dst0 += 8, dst1 += 8)
{
const u8 b = src0[0];
const u8 g0 = src0[1];
const u8 g1 = src1[0];
const u8 r = src1[1];
const u8 top[4] = { r, g0, b, 255 };
const u8 bottom[4] = { r, g1, b, 255 };
// Top-Left
std::memcpy(dst0, top, 4);
// Top-Right Pixel
std::memcpy(dst0 + 4, top, 4);
// Bottom-Left Pixel
std::memcpy(dst1, bottom, 4);
// Bottom-Right Pixel
std::memcpy(dst1 + 4, bottom, 4);
}
}
break; break;
} }
case CELL_CAMERA_RGBA: case CELL_CAMERA_RGBA:
{ {
std::memcpy(video_data_out, video_data_in.data(), std::min<usz>(required_in_size, required_out_size)); std::memcpy(video_data_out, src_data, std::min<usz>(required_in_size, required_out_size));
break; break;
} }
default: default:
{ {
cellGem.error("Unimplemented: Converting %s to %s (called from %s)", input_format, output_format, caller); cellGem.error("Unimplemented: Converting %s to %s (called from %s)", input_format, vc.output_format, caller);
std::memcpy(video_data_out, video_data_in.data(), std::min<usz>(required_in_size, required_out_size)); std::memcpy(video_data_out, src_data, std::min<usz>(required_in_size, required_out_size));
return false; return false;
} }
} }
@ -792,17 +1018,18 @@ namespace gem
{ {
if (input_format == CELL_CAMERA_RAW8) if (input_format == CELL_CAMERA_RAW8)
{ {
std::memcpy(video_data_out, video_data_in.data(), std::min<usz>(required_in_size, required_out_size)); std::memcpy(video_data_out, src_data, std::min<usz>(required_in_size, required_out_size));
} }
else else
{ {
cellGem.error("Unimplemented: Converting %s to %s (called from %s)", input_format, output_format, caller); cellGem.error("Unimplemented: Converting %s to %s (called from %s)", input_format, vc.output_format, caller);
return false; return false;
} }
break; break;
} }
case CELL_GEM_YUV_640x480: // YUV output; 640*480+640*480+640*480-byte output buffer required (contiguous) case CELL_GEM_YUV_640x480: // YUV output; 640*480+640*480+640*480-byte output buffer required (contiguous)
{ {
// YUV 4:4:4 planar. 1 value each per pixel
const u32 yuv_pitch = width; const u32 yuv_pitch = width;
u8* dst_y = video_data_out; u8* dst_y = video_data_out;
@ -813,61 +1040,21 @@ namespace gem
{ {
case CELL_CAMERA_RAW8: case CELL_CAMERA_RAW8:
{ {
const u32 in_pitch = width; fmt::throw_exception("Unreachable: should already be debayered");
for (u32 y = 0; y < height - 1; y += 2)
{
const u8* src0 = &video_data_in[y * in_pitch];
const u8* src1 = src0 + in_pitch;
u8* dst_y0 = dst_y + y * yuv_pitch;
u8* dst_y1 = dst_y0 + yuv_pitch;
u8* dst_u0 = dst_u + y * yuv_pitch;
u8* dst_u1 = dst_u0 + yuv_pitch;
u8* dst_v0 = dst_v + y * yuv_pitch;
u8* dst_v1 = dst_v0 + yuv_pitch;
for (u32 x = 0; x < width - 1; x += 2, src0 += 2, src1 += 2, dst_y0 += 2, dst_y1 += 2, dst_u0 += 2, dst_u1 += 2, dst_v0 += 2, dst_v1 += 2)
{
const u8 b = src0[0];
const u8 g0 = src0[1];
const u8 g1 = src1[0];
const u8 r = src1[1];
// Convert RGBA to YUV
const YUV yuv_top = YUV(r, g0, b);
const YUV yuv_bottom = YUV(r, g1, b);
dst_y0[0] = dst_y0[1] = yuv_top.y;
dst_y1[0] = dst_y1[1] = yuv_bottom.y;
dst_u0[0] = dst_u0[1] = yuv_top.u;
dst_u1[0] = dst_u1[1] = yuv_bottom.u;
dst_v0[0] = dst_v0[1] = yuv_top.v;
dst_v1[0] = dst_v1[1] = yuv_bottom.v;
}
}
break; break;
} }
case CELL_CAMERA_RGBA: case CELL_CAMERA_RGBA:
{ {
const u32 in_pitch = width / 4; const u32 in_pitch = width * 4;
for (u32 y = 0; y < height; y++) for (u32 y = 0; y < height; y++)
{ {
const u8* src = &video_data_in[y * in_pitch]; const u8* src = src_data + y * in_pitch;
for (u32 x = 0; x < width; x++, src += 4) for (u32 x = 0; x < width; x++, src += 4)
{ {
const u8 r = src[0];
const u8 g = src[1];
const u8 b = src[2];
// Convert RGBA to YUV // Convert RGBA to YUV
const YUV yuv = YUV(r, g, b); const YUV yuv = YUV(src);
*dst_y++ = yuv.y; *dst_y++ = yuv.y;
*dst_u++ = yuv.u; *dst_u++ = yuv.u;
@ -878,8 +1065,8 @@ namespace gem
} }
default: default:
{ {
cellGem.error("Unimplemented: Converting %s to %s (called from %s)", input_format, output_format, caller); cellGem.error("Unimplemented: Converting %s to %s (called from %s)", input_format, vc.output_format, caller);
std::memcpy(video_data_out, video_data_in.data(), std::min<usz>(required_in_size, required_out_size)); std::memcpy(video_data_out, src_data, std::min<usz>(required_in_size, required_out_size));
return false; return false;
} }
} }
@ -887,6 +1074,7 @@ namespace gem
} }
case CELL_GEM_YUV422_640x480: // YUV output; 640*480+320*480+320*480-byte output buffer required (contiguous) case CELL_GEM_YUV422_640x480: // YUV output; 640*480+320*480+320*480-byte output buffer required (contiguous)
{ {
// YUV 4:2:2 planar. 1 Y value per pixel, 1 U/V value per 2 horizontal pixels
const u32 y_pitch = width; const u32 y_pitch = width;
const u32 uv_pitch = width / 2; const u32 uv_pitch = width / 2;
@ -898,43 +1086,7 @@ namespace gem
{ {
case CELL_CAMERA_RAW8: case CELL_CAMERA_RAW8:
{ {
const u32 in_pitch = width; fmt::throw_exception("Unreachable: should already be debayered");
for (u32 y = 0; y < height - 1; y += 2)
{
const u8* src0 = &video_data_in[y * in_pitch];
const u8* src1 = src0 + in_pitch;
u8* dst_y0 = dst_y + y * y_pitch;
u8* dst_y1 = dst_y0 + y_pitch;
u8* dst_u0 = dst_u + y * uv_pitch;
u8* dst_u1 = dst_u0 + uv_pitch;
u8* dst_v0 = dst_v + y * uv_pitch;
u8* dst_v1 = dst_v0 + uv_pitch;
for (u32 x = 0; x < width - 1; x += 2, src0 += 2, src1 += 2, dst_y0 += 2, dst_y1 += 2)
{
const u8 b = src0[0];
const u8 g0 = src0[1];
const u8 g1 = src1[0];
const u8 r = src1[1];
// Convert RGBA to YUV
const YUV yuv_top = YUV(r, g0, b);
const YUV yuv_bottom = YUV(r, g1, b);
dst_y0[0] = dst_y0[1] = yuv_top.y;
dst_y1[0] = dst_y1[1] = yuv_bottom.y;
*dst_u0++ = yuv_top.u;
*dst_u1++ = yuv_bottom.u;
*dst_v0++ = yuv_top.v;
*dst_v1++ = yuv_bottom.v;
}
}
break; break;
} }
case CELL_CAMERA_RGBA: case CELL_CAMERA_RGBA:
@ -943,33 +1095,28 @@ namespace gem
for (u32 y = 0; y < height; y++) for (u32 y = 0; y < height; y++)
{ {
const u8* src = &video_data_in[y * in_pitch]; const u8* src = src_data + y * in_pitch;
for (u32 x = 0; x < width - 1; x += 2, src += 8, dst_y += 2) for (u32 x = 0; x < width - 1; x += 2, src += 8, dst_y += 2)
{ {
const u8 r_0 = src[0];
const u8 g_0 = src[1];
const u8 b_0 = src[2];
const u8 r_1 = src[4];
const u8 g_1 = src[5];
const u8 b_1 = src[6];
// Convert RGBA to YUV // Convert RGBA to YUV
const YUV yuv_0 = YUV(r_0, g_0, b_0); const YUV yuv_0 = YUV(src);
const u8 y_1 = YUV::Y(r_1, g_1, b_1); const YUV yuv_1 = YUV(src + 4);
dst_y[0] = yuv_0.y; dst_y[0] = yuv_0.y;
dst_y[1] = y_1; dst_y[1] = yuv_1.y;
*dst_u++ = yuv_0.u;
*dst_v++ = yuv_0.v; // Average U/V from 2 horizontal pixels
*dst_u++ = (yuv_0.u + yuv_1.u) / 2;
*dst_v++ = (yuv_0.v + yuv_1.v) / 2;
} }
} }
break; break;
} }
default: default:
{ {
cellGem.error("Unimplemented: Converting %s to %s (called from %s)", input_format, output_format, caller); cellGem.error("Unimplemented: Converting %s to %s (called from %s)", input_format, vc.output_format, caller);
std::memcpy(video_data_out, video_data_in.data(), std::min<usz>(required_in_size, required_out_size)); std::memcpy(video_data_out, src_data, std::min<usz>(required_in_size, required_out_size));
return false; return false;
} }
} }
@ -977,109 +1124,54 @@ namespace gem
} }
case CELL_GEM_YUV411_640x480: // YUV411 output; 640*480+320*240+320*240-byte output buffer required (contiguous) case CELL_GEM_YUV411_640x480: // YUV411 output; 640*480+320*240+320*240-byte output buffer required (contiguous)
{ {
const u32 y_pitch = width; // YUV 4:1:1 planar. 1 Y value per pixel, 1 U/V value per 2x2 pixel block
const u32 uv_pitch = width / 4;
u8* dst_y = video_data_out; u8* dst_y = video_data_out;
u8* dst_u = dst_y + y_pitch * height; u8* dst_u = dst_y + 640 * 480;
u8* dst_v = dst_u + uv_pitch * height; u8* dst_v = dst_u + 320 * 240;
switch (input_format) switch (input_format)
{ {
case CELL_CAMERA_RAW8: case CELL_CAMERA_RAW8:
{ {
const u32 in_pitch = width; fmt::throw_exception("Unreachable: should already be debayered");
for (u32 y = 0; y < height - 1; y += 2)
{
const u8* src0 = &video_data_in[y * in_pitch];
const u8* src1 = src0 + in_pitch;
u8* dst_y0 = dst_y + y * y_pitch;
u8* dst_y1 = dst_y0 + y_pitch;
u8* dst_u0 = dst_u + y * uv_pitch;
u8* dst_u1 = dst_u0 + uv_pitch;
u8* dst_v0 = dst_v + y * uv_pitch;
u8* dst_v1 = dst_v0 + uv_pitch;
for (u32 x = 0; x < width - 3; x += 4, src0 += 4, src1 += 4, dst_y0 += 4, dst_y1 += 4)
{
const u8 b_left = src0[0];
const u8 g0_left = src0[1];
const u8 b_right = src0[2];
const u8 g0_right = src0[3];
const u8 g1_left = src1[0];
const u8 r_left = src1[1];
const u8 g1_right = src1[2];
const u8 r_right = src1[3];
// Convert RGBA to YUV
const YUV yuv_top_left = YUV(r_left, g0_left, b_left); // Re-used for top-right
const u8 y_top_right = YUV::Y(r_right, g0_right, b_right);
const YUV yuv_bottom_left = YUV(r_left, g1_left, b_left); // Re-used for bottom-right
const u8 y_bottom_right = YUV::Y(r_right, g1_right, b_right);
dst_y0[0] = dst_y0[1] = yuv_top_left.y;
dst_y0[2] = dst_y0[3] = y_top_right;
dst_y1[0] = dst_y1[1] = yuv_bottom_left.y;
dst_y1[2] = dst_y1[3] = y_bottom_right;
*dst_u0++ = yuv_top_left.u;
*dst_u1++ = yuv_bottom_left.u;
*dst_v0++ = yuv_top_left.v;
*dst_v1++ = yuv_bottom_left.v;
}
}
break; break;
} }
case CELL_CAMERA_RGBA: case CELL_CAMERA_RGBA:
{ {
const u32 in_pitch = width * 4; const u32 in_pitch = width * 4;
for (u32 y = 0; y < height; y++) // 2 rows at a time to get a 2x2 pixel block
for (u32 y = 0; y < height - 1; y += 2)
{ {
const u8* src = &video_data_in[y * in_pitch]; const u8* src = src_data + y * in_pitch;
const u8* src2 = src + in_pitch;
u8* dst_y1 = dst_y + y * 640;
u8* dst_y2 = dst_y1 + 640;
for (u32 x = 0; x < width - 3; x += 4, src += 16, dst_y += 4) for (u32 x = 0; x < width - 1; x += 2, src += 8, src2 += 8, dst_y1 += 2, dst_y2 += 2)
{ {
const u8 r_0 = src[0];
const u8 g_0 = src[1];
const u8 b_0 = src[2];
const u8 r_1 = src[4];
const u8 g_1 = src[5];
const u8 b_1 = src[6];
const u8 r_2 = src[8];
const u8 g_2 = src[9];
const u8 b_2 = src[10];
const u8 r_3 = src[12];
const u8 g_3 = src[13];
const u8 b_3 = src[14];
// Convert RGBA to YUV // Convert RGBA to YUV
const YUV yuv_0 = YUV(r_0, g_0, b_0); const YUV yuv_0 = YUV(src);
const u8 y_1 = YUV::Y(r_1, g_1, b_1); const YUV yuv_1 = YUV(src + 4);
const u8 y_2 = YUV::Y(r_2, g_2, b_2); const YUV yuv_2 = YUV(src2);
const u8 y_3 = YUV::Y(r_3, g_3, b_3); const YUV yuv_3 = YUV(src2 + 4);
dst_y[0] = yuv_0.y; dst_y1[0] = yuv_0.y;
dst_y[1] = y_1; dst_y1[1] = yuv_1.y;
dst_y[2] = y_2; dst_y2[0] = yuv_2.y;
dst_y[3] = y_3; dst_y2[1] = yuv_3.y;
*dst_u++ = yuv_0.u;
*dst_v++ = yuv_0.v; // Average U/V from 2x2 pixel block
*dst_u++ = (yuv_0.u + yuv_1.u + yuv_2.u + yuv_3.u) / 4;
*dst_v++ = (yuv_0.v + yuv_1.v + yuv_2.v + yuv_3.v) / 4;
} }
} }
break; break;
} }
default: default:
{ {
cellGem.error("Unimplemented: Converting %s to %s (called from %s)", input_format, output_format, caller); cellGem.error("Unimplemented: Converting %s to %s (called from %s)", input_format, vc.output_format, caller);
std::memcpy(video_data_out, video_data_in.data(), std::min<usz>(required_in_size, required_out_size)); std::memcpy(video_data_out, src_data, std::min<usz>(required_in_size, required_out_size));
return false; return false;
} }
} }
@ -1096,7 +1188,7 @@ namespace gem
for (u32 y = 0; y < height - 1; y += 2) for (u32 y = 0; y < height - 1; y += 2)
{ {
const u8* src0 = &video_data_in[y * in_pitch]; const u8* src0 = src_data + y * in_pitch;
const u8* src1 = src0 + in_pitch; const u8* src1 = src0 + in_pitch;
u8* dst0 = video_data_out + (y / 2) * out_pitch; u8* dst0 = video_data_out + (y / 2) * out_pitch;
@ -1109,8 +1201,8 @@ namespace gem
const u8 g1 = src1[0]; const u8 g1 = src1[0];
const u8 r = src1[1]; const u8 r = src1[1];
const u8 top[4] = { r, g0, b, 255 }; const u8 top[4] = { r, g0, b, alpha };
const u8 bottom[4] = { r, g1, b, 255 }; const u8 bottom[4] = { r, g1, b, alpha };
// Top-Left // Top-Left
std::memcpy(dst0, top, 4); std::memcpy(dst0, top, 4);
@ -1128,7 +1220,7 @@ namespace gem
for (u32 y = 0; y < height / 2; y++) for (u32 y = 0; y < height / 2; y++)
{ {
const u8* src = &video_data_in[y * 2 * in_pitch]; const u8* src = src_data + y * 2 * in_pitch;
u8* dst = video_data_out + y * out_pitch; u8* dst = video_data_out + y * out_pitch;
for (u32 x = 0; x < width / 2; x++, src += 4 * 2, dst += 4) for (u32 x = 0; x < width / 2; x++, src += 4 * 2, dst += 4)
@ -1140,19 +1232,97 @@ namespace gem
} }
default: default:
{ {
cellGem.error("Unimplemented: Converting %s to %s (called from %s)", input_format, output_format, caller); cellGem.error("Unimplemented: Converting %s to %s (called from %s)", input_format, vc.output_format, caller);
std::memcpy(video_data_out, video_data_in.data(), std::min<usz>(required_in_size, required_out_size)); std::memcpy(video_data_out, src_data, std::min<usz>(required_in_size, required_out_size));
return false; return false;
} }
} }
break; break;
} }
case CELL_GEM_BAYER_RESTORED_RGGB: // Restored Bayer output, 2x2 pixels rearranged into 320x240 RG1G2B case CELL_GEM_BAYER_RESTORED_RGGB: // Restored Bayer output, 2x2 pixels rearranged into 320x240 RG1G2B
{
if (input_format == CELL_CAMERA_RAW8)
{
const u32 dst_w = std::min(320u, width / 2);
const u32 dst_h = std::min(240u, height / 2);
const u32 in_pitch = width;
constexpr u32 out_pitch = 320 * 4;
for (u32 y = 0; y < dst_h; y++)
{
const u8* src0 = src_data + y * 2 * in_pitch;
const u8* src1 = src0 + in_pitch;
u8* dst = video_data_out + y * out_pitch;
for (u32 x = 0; x < dst_w; x++, src0 += 2, src1 += 2, dst += 4)
{
const u8 b = src0[0];
const u8 g0 = src0[1];
const u8 g1 = src1[0];
const u8 r = src1[1];
dst[0] = r;
dst[1] = g0;
dst[2] = g1;
dst[3] = b;
}
}
}
else
{
cellGem.error("Unimplemented: Converting %s to %s (called from %s)", input_format, vc.output_format, caller);
std::memcpy(video_data_out, src_data, std::min<usz>(required_in_size, required_out_size));
return false;
}
break;
}
case CELL_GEM_BAYER_RESTORED_RASTERIZED: // Restored Bayer output, R,G1,G2,B rearranged into 4 contiguous 320x240 1-channel rasters case CELL_GEM_BAYER_RESTORED_RASTERIZED: // Restored Bayer output, R,G1,G2,B rearranged into 4 contiguous 320x240 1-channel rasters
{ {
cellGem.error("Unimplemented: Converting %s to %s (called from %s)", input_format, output_format, caller); if (input_format == CELL_CAMERA_RAW8)
std::memcpy(video_data_out, video_data_in.data(), std::min<usz>(required_in_size, required_out_size)); {
return false; const u32 dst_w = std::min(320u, width / 2);
const u32 dst_h = std::min(240u, height / 2);
const u32 in_pitch = width;
constexpr u32 out_plane = 320 * 240;
constexpr u32 out_pitch = 320;
u8* dst_plane_r = video_data_out;
u8* dst_plane_g1 = video_data_out + out_plane;
u8* dst_plane_g2 = video_data_out + out_plane * 2;
u8* dst_plane_b = video_data_out + out_plane * 3;
for (u32 y = 0; y < dst_h; y++)
{
const u8* src0 = src_data + y * 2 * in_pitch;
const u8* src1 = src0 + in_pitch;
u8* dst_r = dst_plane_r + y * out_pitch;
u8* dst_g1 = dst_plane_g1 + y * out_pitch;
u8* dst_g2 = dst_plane_g2 + y * out_pitch;
u8* dst_b = dst_plane_b + y * out_pitch;
for (u32 x = 0; x < dst_w; x++, src0 += 2, src1 += 2)
{
const u8 b = src0[0];
const u8 g0 = src0[1];
const u8 g1 = src1[0];
const u8 r = src1[1];
dst_r[x] = r;
dst_g1[x] = g0;
dst_g2[x] = g1;
dst_b[x] = b;
}
}
}
else
{
cellGem.error("Unimplemented: Converting %s to %s (called from %s)", input_format, vc.output_format, caller);
std::memcpy(video_data_out, src_data, std::min<usz>(required_in_size, required_out_size));
return false;
}
break;
} }
case CELL_GEM_NO_VIDEO_OUTPUT: // Disable video output case CELL_GEM_NO_VIDEO_OUTPUT: // Disable video output
{ {
@ -1161,7 +1331,7 @@ namespace gem
} }
default: default:
{ {
cellGem.error("Trying to convert %s to %s (called from %s)", input_format, output_format, caller); cellGem.error("Trying to convert %s to %s (called from %s)", input_format, vc.output_format, caller);
return false; return false;
} }
} }
@ -1340,13 +1510,15 @@ void gem_config_data::operator()()
const auto& shared_data = g_fxo->get<gem_camera_shared>(); const auto& shared_data = g_fxo->get<gem_camera_shared>();
if (gem::convert_image_format(shared_data.format, vc.output_format, video_data_in, shared_data.width, shared_data.height, vc_attribute.video_data_out ? vc_attribute.video_data_out.get_ptr() : nullptr, video_data_out_size, "cellGem")) if (gem::convert_image_format(shared_data.format, vc, video_data_in, shared_data.width, shared_data.height,
vc.video_data_out ? vc.video_data_out.get_ptr() : nullptr, video_data_out_size,
vc.buffer_memory ? vc.buffer_memory.get_ptr() : nullptr, "cellGem"))
{ {
cellGem.trace("Converted video frame of format %s to %s", shared_data.format.load(), vc.output_format.get()); cellGem.trace("Converted video frame of format %s to %s", shared_data.format.load(), vc.output_format.get());
if (g_cfg.io.paint_move_spheres) if (g_cfg.io.paint_move_spheres)
{ {
paint_spheres(vc.output_format, shared_data.width, shared_data.height, vc_attribute.video_data_out ? vc_attribute.video_data_out.get_ptr() : nullptr, video_data_out_size); paint_spheres(vc.output_format, shared_data.width, shared_data.height, vc.video_data_out ? vc.video_data_out.get_ptr() : nullptr, video_data_out_size);
} }
} }
@ -1617,7 +1789,7 @@ static inline void pos_to_gem_image_state(u32 gem_num, gem_config::gem_controlle
const f32 scaling_width = x_max / static_cast<f32>(shared_data.width); const f32 scaling_width = x_max / static_cast<f32>(shared_data.width);
const f32 scaling_height = y_max / static_cast<f32>(shared_data.height); const f32 scaling_height = y_max / static_cast<f32>(shared_data.height);
const f32 mmPerPixel = CELL_GEM_SPHERE_RADIUS_MM / controller.radius; const f32 mmPerPixel = controller.radius <= 0.0f ? 0.0f : (CELL_GEM_SPHERE_RADIUS_MM / controller.radius);
// Image coordinates in pixels // Image coordinates in pixels
const f32 image_x = static_cast<f32>(x_pos) / scaling_width; const f32 image_x = static_cast<f32>(x_pos) / scaling_width;
@ -1657,7 +1829,7 @@ static inline void pos_to_gem_image_state(u32 gem_num, gem_config::gem_controlle
} }
} }
static inline void pos_to_gem_state(u32 gem_num, gem_config::gem_controller& controller, vm::ptr<CellGemState>& gem_state, s32 x_pos, s32 y_pos, s32 x_max, s32 y_max, const ps_move_data& move_data) static inline void pos_to_gem_state(u32 gem_num, gem_config::gem_controller& controller, vm::ptr<CellGemState>& gem_state, s32 x_pos, s32 y_pos, s32 x_max, s32 y_max, ps_move_data& move_data)
{ {
const auto& shared_data = g_fxo->get<gem_camera_shared>(); const auto& shared_data = g_fxo->get<gem_camera_shared>();
@ -1670,7 +1842,7 @@ static inline void pos_to_gem_state(u32 gem_num, gem_config::gem_controller& con
const f32 scaling_width = x_max / static_cast<f32>(shared_data.width); const f32 scaling_width = x_max / static_cast<f32>(shared_data.width);
const f32 scaling_height = y_max / static_cast<f32>(shared_data.height); const f32 scaling_height = y_max / static_cast<f32>(shared_data.height);
const f32 mmPerPixel = CELL_GEM_SPHERE_RADIUS_MM / controller.radius; const f32 mmPerPixel = controller.radius <= 0.0f ? 0.0f : (CELL_GEM_SPHERE_RADIUS_MM / controller.radius);
// Image coordinates in pixels // Image coordinates in pixels
const f32 image_x = static_cast<f32>(x_pos) / scaling_width; const f32 image_x = static_cast<f32>(x_pos) / scaling_width;
@ -1703,10 +1875,10 @@ static inline void pos_to_gem_state(u32 gem_num, gem_config::gem_controller& con
// Calculate orientation // Calculate orientation
if (g_cfg.io.move == move_handler::real || (g_cfg.io.move == move_handler::fake && move_data.orientation_enabled)) if (g_cfg.io.move == move_handler::real || (g_cfg.io.move == move_handler::fake && move_data.orientation_enabled))
{ {
gem_state->quat[0] = move_data.quaternion[0]; // x gem_state->quat[0] = move_data.quaternion.x();
gem_state->quat[1] = move_data.quaternion[1]; // y gem_state->quat[1] = move_data.quaternion.y();
gem_state->quat[2] = move_data.quaternion[2]; // z gem_state->quat[2] = move_data.quaternion.z();
gem_state->quat[3] = move_data.quaternion[3]; // w gem_state->quat[3] = move_data.quaternion.w();
} }
else else
{ {
@ -1733,6 +1905,17 @@ static inline void pos_to_gem_state(u32 gem_num, gem_config::gem_controller& con
gem_state->quat[3] = q_w; gem_state->quat[3] = q_w;
} }
if constexpr (!ps_move_data::use_imu_for_velocity)
{
move_data.update_velocity(shared_data.frame_timestamp_us, gem_state->pos);
for (u32 i = 0; i < 3; i++)
{
gem_state->vel[i] = move_data.vel_world[i];
gem_state->accel[i] = move_data.accel_world[i];
}
}
// Update visibility for fake handlers // Update visibility for fake handlers
if (g_cfg.io.move != move_handler::real) if (g_cfg.io.move != move_handler::real)
{ {
@ -1906,9 +2089,17 @@ static void ps_move_pos_to_gem_state(u32 gem_num, gem_config::gem_controller& co
if constexpr (std::is_same_v<T, vm::ptr<CellGemState>>) if constexpr (std::is_same_v<T, vm::ptr<CellGemState>>)
{ {
gem_state->temperature = pad->move_data.temperature; gem_state->temperature = pad->move_data.temperature;
gem_state->accel[0] = pad->move_data.accelerometer_x * 1000; // linear velocity in mm/s²
gem_state->accel[1] = pad->move_data.accelerometer_y * 1000; // linear velocity in mm/s² for (u32 i = 0; i < 3; i++)
gem_state->accel[2] = pad->move_data.accelerometer_z * 1000; // linear velocity in mm/s² {
if constexpr (ps_move_data::use_imu_for_velocity)
{
gem_state->vel[i] = pad->move_data.vel_world[i];
gem_state->accel[i] = pad->move_data.accel_world[i];
}
gem_state->angvel[i] = pad->move_data.angvel_world[i];
gem_state->angaccel[i] = pad->move_data.angaccel_world[i];
}
pos_to_gem_state(gem_num, controller, gem_state, info.x_pos, info.y_pos, info.x_max, info.y_max, pad->move_data); pos_to_gem_state(gem_num, controller, gem_state, info.x_pos, info.y_pos, info.x_max, info.y_max, pad->move_data);
} }
@ -2147,7 +2338,8 @@ static void mouse_pos_to_gem_state(u32 mouse_no, gem_config::gem_controller& con
if constexpr (std::is_same_v<T, vm::ptr<CellGemState>>) if constexpr (std::is_same_v<T, vm::ptr<CellGemState>>)
{ {
pos_to_gem_state(mouse_no, controller, gem_state, mouse.x_pos, mouse.y_pos, mouse.x_max, mouse.y_max, {}); ps_move_data& move_data = ::at32(g_fxo->get<gem_config>().fake_move_data, mouse_no);
pos_to_gem_state(mouse_no, controller, gem_state, mouse.x_pos, mouse.y_pos, mouse.x_max, mouse.y_max, move_data);
} }
else if constexpr (std::is_same_v<T, vm::ptr<CellGemImageState>>) else if constexpr (std::is_same_v<T, vm::ptr<CellGemImageState>>)
{ {
@ -2215,7 +2407,8 @@ static void gun_pos_to_gem_state(u32 gem_no, gem_config::gem_controller& control
if constexpr (std::is_same_v<T, vm::ptr<CellGemState>>) if constexpr (std::is_same_v<T, vm::ptr<CellGemState>>)
{ {
pos_to_gem_state(gem_no, controller, gem_state, x_pos, y_pos, x_max, y_max, {}); ps_move_data& move_data = ::at32(g_fxo->get<gem_config>().fake_move_data, gem_no);
pos_to_gem_state(gem_no, controller, gem_state, x_pos, y_pos, x_max, y_max, move_data);
} }
else if constexpr (std::is_same_v<T, vm::ptr<CellGemImageState>>) else if constexpr (std::is_same_v<T, vm::ptr<CellGemImageState>>)
{ {
@ -2769,7 +2962,7 @@ error_code cellGemGetInertialState(u32 gem_num, u32 state_flag, u64 timestamp, v
inertial_state->timestamp = (get_guest_system_time() - gem.start_timestamp_us); inertial_state->timestamp = (get_guest_system_time() - gem.start_timestamp_us);
inertial_state->counter = gem.inertial_counter++; inertial_state->counter = gem.inertial_counter++;
inertial_state->accelerometer[0] = 10; // Current gravity in m/s² inertial_state->accelerometer[2] = 1.0f; // Current gravity in G units (9.81 == 1 unit)
switch (g_cfg.io.move) switch (g_cfg.io.move)
{ {
@ -2786,12 +2979,12 @@ error_code cellGemGetInertialState(u32 gem_num, u32 state_flag, u64 timestamp, v
if (pad && pad->is_connected() && !pad->is_copilot()) if (pad && pad->is_connected() && !pad->is_copilot())
{ {
inertial_state->temperature = pad->move_data.temperature; inertial_state->temperature = pad->move_data.temperature;
inertial_state->accelerometer[0] = pad->move_data.accelerometer_x;
inertial_state->accelerometer[1] = pad->move_data.accelerometer_y; for (u32 i = 0; i < 3; i++)
inertial_state->accelerometer[2] = pad->move_data.accelerometer_z; {
inertial_state->gyro[0] = pad->move_data.gyro_x; inertial_state->accelerometer[i] = pad->move_data.accelerometer[i];
inertial_state->gyro[1] = pad->move_data.gyro_y; inertial_state->gyro[i] = pad->move_data.gyro[i];
inertial_state->gyro[2] = pad->move_data.gyro_z; }
} }
} }

View File

@ -246,15 +246,15 @@ struct CellGemInfo
// z increases towards user (away from the camera) // z increases towards user (away from the camera)
struct CellGemState struct CellGemState
{ {
be_t<f32> pos[4]; // center of sphere (mm) be_t<f32> pos[4]; // center of sphere in world coordinates (mm)
be_t<f32> vel[4]; // velocity of sphere (mm/s) be_t<f32> vel[4]; // velocity of sphere in world coordinates (mm/s)
be_t<f32> accel[4]; // acceleration of sphere (mm/s²) be_t<f32> accel[4]; // acceleration of sphere in world coordinates (mm/s²)
be_t<f32> quat[4]; // quaternion orientation (x,y,z,w) of controller relative to default (facing the camera with buttons up) be_t<f32> quat[4]; // quaternion orientation (x,y,z,w) of controller relative to default (facing the camera with buttons up)
be_t<f32> angvel[4]; // angular velocity of controller (radians/s) be_t<f32> angvel[4]; // angular velocity of controller in world coordinates (radians/s)
be_t<f32> angaccel[4]; // angular acceleration of controller (radians/s²) be_t<f32> angaccel[4]; // angular acceleration of controller in world coordinates (radians/s²)
be_t<f32> handle_pos[4]; // center of controller handle (mm) be_t<f32> handle_pos[4]; // center of controller handle in world coordinates (mm)
be_t<f32> handle_vel[4]; // velocity of controller handle (mm/s) be_t<f32> handle_vel[4]; // velocity of controller handle in world coordinates (mm/s)
be_t<f32> handle_accel[4]; // acceleration of controller handle (mm/s²) be_t<f32> handle_accel[4]; // acceleration of controller handle in world coordinates (mm/s²)
CellGemPadData pad; CellGemPadData pad;
CellGemExtPortData ext; CellGemExtPortData ext;
be_t<u64> timestamp; // system_time_t (microseconds) be_t<u64> timestamp; // system_time_t (microseconds)

View File

@ -7320,7 +7320,7 @@ spu_program spu_recompiler_base::analyse(const be_t<u32>* ls, u32 entry_point, s
} }
// spu_log.success("PUTLLC0 Pattern Detected! (put_pc=0x%x, %s) (putllc0=%d, putllc16+0=%d, all=%d)", pattern.put_pc, func_hash, ++stats.nowrite, ++stats.single, +stats.all); // spu_log.success("PUTLLC0 Pattern Detected! (put_pc=0x%x, %s) (putllc0=%d, putllc16+0=%d, all=%d)", pattern.put_pc, func_hash, ++stats.nowrite, ++stats.single, +stats.all);
// add_pattern(false, inst_attr::putllc0, pattern.put_pc - lsa, value.data); // add_pattern(inst_attr::putllc0, pattern.put_pc - lsa, value.data);
continue; continue;
} }
@ -7411,7 +7411,7 @@ spu_program spu_recompiler_base::analyse(const be_t<u32>* ls, u32 entry_point, s
if (allow_pattern) if (allow_pattern)
{ {
add_pattern(false, inst_attr::putllc16, pattern.put_pc - result.entry_point, value.data); add_pattern(inst_attr::putllc16, pattern.put_pc - result.entry_point, value.data);
} }
spu_log.success("PUTLLC16 Pattern Detected! (mem_count=%d, put_pc=0x%x, pc_rel=%d, offset=0x%x, const=%u, two_regs=%d, reg=%u, runtime=%d, 0x%x-%s, pattern-hash=%s) (putllc0=%d, putllc16+0=%d, all=%d)" spu_log.success("PUTLLC16 Pattern Detected! (mem_count=%d, put_pc=0x%x, pc_rel=%d, offset=0x%x, const=%u, two_regs=%d, reg=%u, runtime=%d, 0x%x-%s, pattern-hash=%s) (putllc0=%d, putllc16+0=%d, all=%d)"
@ -7433,7 +7433,7 @@ spu_program spu_recompiler_base::analyse(const be_t<u32>* ls, u32 entry_point, s
if (inst_attr attr = m_inst_attrs[(read_pc - entry_point) / 4]; attr == inst_attr::none) if (inst_attr attr = m_inst_attrs[(read_pc - entry_point) / 4]; attr == inst_attr::none)
{ {
add_pattern(false, inst_attr::rchcnt_loop, read_pc - result.entry_point, 0); add_pattern(inst_attr::rchcnt_loop, read_pc - result.entry_point, 0);
spu_log.error("Channel Loop Pattern Detected! Report to developers! (read_pc=0x%x, branch_pc=0x%x, branch_target=0x%x, 0x%x-%s)", read_pc, pattern.branch_pc, pattern.branch_target, entry_point, func_hash); spu_log.error("Channel Loop Pattern Detected! Report to developers! (read_pc=0x%x, branch_pc=0x%x, branch_target=0x%x, 0x%x-%s)", read_pc, pattern.branch_pc, pattern.branch_target, entry_point, func_hash);
} }
@ -8519,7 +8519,7 @@ std::array<reg_state_t, s_reg_max>& block_reg_info::evaluate_start_state(const s
return walkby_state; return walkby_state;
} }
void spu_recompiler_base::add_pattern(bool fill_all, inst_attr attr, u32 start, u64 info) void spu_recompiler_base::add_pattern(inst_attr attr, u32 start, u64 info)
{ {
m_patterns[start] = pattern_info{info}; m_patterns[start] = pattern_info{info};
m_inst_attrs[start / 4] = attr; m_inst_attrs[start / 4] = attr;

View File

@ -402,7 +402,7 @@ protected:
std::unordered_map<u32, pattern_info> m_patterns; std::unordered_map<u32, pattern_info> m_patterns;
void add_pattern(bool fill_all, inst_attr attr, u32 start, u64 info); void add_pattern(inst_attr attr, u32 start, u64 info);
private: private:
// For private use // For private use

View File

@ -321,7 +321,7 @@ static void LIBUSB_CALL log_cb(libusb_context* /*ctx*/, enum libusb_log_level le
if (!str) if (!str)
return; return;
const std::string msg = fmt::trim(str, " \t\n"); const std::string_view msg = fmt::trim_sv(str, " \t\n");
switch (level) switch (level)
{ {
@ -555,7 +555,8 @@ usb_handler_thread::usb_handler_thread()
usb_devices.push_back(std::make_shared<usb_device_vfs>(usb_info, get_new_location())); usb_devices.push_back(std::make_shared<usb_device_vfs>(usb_info, get_new_location()));
} }
const std::vector<std::string> devices_list = fmt::split(g_cfg.io.midi_devices.to_string(), { "@@@" }); const std::string midi_devices = g_cfg.io.midi_devices.to_string();
const std::vector<std::string_view> devices_list = fmt::split_sv(midi_devices, { "@@@" });
for (usz index = 0; index < std::min(max_midi_devices, devices_list.size()); index++) for (usz index = 0; index < std::min(max_midi_devices, devices_list.size()); index++)
{ {
const midi_device device = midi_device::from_string(::at32(devices_list, index)); const midi_device device = midi_device::from_string(::at32(devices_list, index));

View File

@ -874,12 +874,12 @@ void PadHandlerBase::set_raw_orientation(ps_move_data& move_data, f32 accel_x, f
// The default position is flat on the ground, pointing forward. // The default position is flat on the ground, pointing forward.
// The accelerometers constantly measure G forces. // The accelerometers constantly measure G forces.
// The gyros measure changes in orientation and will reset when the device isn't moved anymore. // The gyros measure changes in orientation and will reset when the device isn't moved anymore.
move_data.accelerometer_x = -accel_x; // move_data: Increases if the device is rolled to the left move_data.accelerometer.x() = -accel_x; // move_data: Increases if the device is rolled to the left
move_data.accelerometer_y = accel_z; // move_data: Increases if the device is pitched upwards move_data.accelerometer.y() = accel_z; // move_data: Increases if the device is pitched upwards
move_data.accelerometer_z = accel_y; // move_data: Increases if the device is moved upwards move_data.accelerometer.z() = accel_y; // move_data: Increases if the device is moved upwards
move_data.gyro_x = degree_to_rad(-gyro_x); // move_data: Increases if the device is pitched upwards move_data.gyro.x() = degree_to_rad(-gyro_x); // move_data: Increases if the device is pitched upwards
move_data.gyro_y = degree_to_rad(gyro_z); // move_data: Increases if the device is rolled to the right move_data.gyro.y() = degree_to_rad(gyro_z); // move_data: Increases if the device is rolled to the right
move_data.gyro_z = degree_to_rad(-gyro_y); // move_data: Increases if the device is yawed to the left move_data.gyro.z() = degree_to_rad(-gyro_y); // move_data: Increases if the device is yawed to the left
} }
void PadHandlerBase::set_raw_orientation(Pad& pad) void PadHandlerBase::set_raw_orientation(Pad& pad)
@ -950,7 +950,7 @@ void PadDevice::update_orientation(ps_move_data& move_data)
// Get elapsed time since last update // Get elapsed time since last update
const u64 now_us = get_system_time(); const u64 now_us = get_system_time();
const float elapsed_sec = (last_ahrs_update_time_us == 0) ? 0.0f : ((now_us - last_ahrs_update_time_us) / 1'000'000.0f); const f32 elapsed_sec = (last_ahrs_update_time_us == 0) ? 0.0f : ((now_us - last_ahrs_update_time_us) / 1'000'000.0f);
last_ahrs_update_time_us = now_us; last_ahrs_update_time_us = now_us;
// The ps move handler's axis may differ from the Fusion axis, so we have to map them correctly. // The ps move handler's axis may differ from the Fusion axis, so we have to map them correctly.
@ -959,17 +959,17 @@ void PadDevice::update_orientation(ps_move_data& move_data)
const FusionVector accelerometer{ const FusionVector accelerometer{
.axis { .axis {
.x = -move_data.accelerometer_x, .x = -move_data.accelerometer.x(),
.y = +move_data.accelerometer_y, .y = +move_data.accelerometer.y(),
.z = +move_data.accelerometer_z .z = +move_data.accelerometer.z()
} }
}; };
const FusionVector gyroscope{ const FusionVector gyroscope{
.axis { .axis {
.x = +PadHandlerBase::rad_to_degree(move_data.gyro_x), .x = +PadHandlerBase::rad_to_degree(move_data.gyro.x()),
.y = +PadHandlerBase::rad_to_degree(move_data.gyro_z), .y = +PadHandlerBase::rad_to_degree(move_data.gyro.z()),
.z = -PadHandlerBase::rad_to_degree(move_data.gyro_y) .z = -PadHandlerBase::rad_to_degree(move_data.gyro.y())
} }
}; };
@ -979,9 +979,9 @@ void PadDevice::update_orientation(ps_move_data& move_data)
{ {
magnetometer = FusionVector{ magnetometer = FusionVector{
.axis { .axis {
.x = move_data.magnetometer_x, .x = move_data.magnetometer.x(),
.y = move_data.magnetometer_y, .y = move_data.magnetometer.y(),
.z = move_data.magnetometer_z .z = move_data.magnetometer.z()
} }
}; };
} }
@ -995,4 +995,5 @@ void PadDevice::update_orientation(ps_move_data& move_data)
move_data.quaternion[1] = quaternion.array[2]; move_data.quaternion[1] = quaternion.array[2];
move_data.quaternion[2] = quaternion.array[3]; move_data.quaternion[2] = quaternion.array[3];
move_data.quaternion[3] = quaternion.array[0]; move_data.quaternion[3] = quaternion.array[0];
move_data.update_orientation(elapsed_sec);
} }

View File

@ -205,7 +205,7 @@ protected:
std::set<T> key_codes; std::set<T> key_codes;
const std::string& def = cfg_string.def; const std::string& def = cfg_string.def;
const std::vector<std::string> names = cfg_pad::get_buttons(cfg_string); const std::vector<std::string> names = cfg_pad::get_buttons(cfg_string.to_string());
T def_code = umax; T def_code = umax;
for (const std::string& nam : names) for (const std::string& nam : names)

View File

@ -177,7 +177,7 @@ Note str_to_note(const std::string_view name)
std::optional<std::pair<Id, Note>> parse_midi_override(const std::string_view config) std::optional<std::pair<Id, Note>> parse_midi_override(const std::string_view config)
{ {
auto split = fmt::split(config, {"="}); const auto split = fmt::split_sv(config, {"="});
if (split.size() != 2) if (split.size() != 2)
{ {
return {}; return {};
@ -236,8 +236,9 @@ std::unordered_map<Id, Note> create_id_to_note_mapping()
}; };
// Apply configured overrides. // Apply configured overrides.
const std::vector<std::string> segments = fmt::split(g_cfg_rb3drums.midi_overrides.to_string(), {","}); const std::string midi_overrides = g_cfg_rb3drums.midi_overrides.to_string();
for (const std::string& segment : segments) const std::vector<std::string_view> segments = fmt::split_sv(midi_overrides, {","});
for (const std::string_view& segment : segments)
{ {
if (const auto midi_override = parse_midi_override(segment)) if (const auto midi_override = parse_midi_override(segment))
{ {
@ -259,7 +260,7 @@ std::vector<u8> parse_combo(const std::string_view name, const std::string_view
return {}; return {};
} }
std::vector<u8> notes; std::vector<u8> notes;
const auto& note_names = fmt::split(csv, {","}); const auto note_names = fmt::split_sv(csv, {","});
for (const auto& note_name : note_names) for (const auto& note_name : note_names)
{ {
const auto note = str_to_note(note_name); const auto note = str_to_note(note_name);

View File

@ -36,7 +36,7 @@ void cfg_camera::save() const
} }
} }
cfg_camera::camera_setting cfg_camera::get_camera_setting(const std::string& camera, bool& success) cfg_camera::camera_setting cfg_camera::get_camera_setting(std::string_view camera, bool& success)
{ {
camera_setting setting; camera_setting setting;
const std::string value = cameras.get_value(camera); const std::string value = cameras.get_value(camera);
@ -64,7 +64,7 @@ std::string cfg_camera::camera_setting::to_string() const
return fmt::format("%d,%d,%f,%f,%d", width, height, min_fps, max_fps, format); return fmt::format("%d,%d,%f,%f,%d", width, height, min_fps, max_fps, format);
} }
void cfg_camera::camera_setting::from_string(const std::string& text) void cfg_camera::camera_setting::from_string(std::string_view text)
{ {
if (text.empty()) if (text.empty())
{ {

View File

@ -19,9 +19,9 @@ struct cfg_camera final : cfg::node
static constexpr u32 member_count = 5; static constexpr u32 member_count = 5;
std::string to_string() const; std::string to_string() const;
void from_string(const std::string& text); void from_string(std::string_view text);
}; };
camera_setting get_camera_setting(const std::string& camera, bool& success); camera_setting get_camera_setting(std::string_view camera, bool& success);
void set_camera_setting(const std::string& camera, const camera_setting& setting); void set_camera_setting(const std::string& camera, const camera_setting& setting);
const std::string path; const std::string path;

View File

@ -27,11 +27,11 @@ void fmt_class_string<midi_device>::format(std::string& out, u64 arg)
fmt::append(out, "%sßßß%s", obj.type, obj.name); fmt::append(out, "%sßßß%s", obj.type, obj.name);
} }
midi_device midi_device::from_string(const std::string& str) midi_device midi_device::from_string(std::string_view str)
{ {
midi_device res{}; midi_device res{};
if (const std::vector<std::string> parts = fmt::split(str, {"ßßß"}); !parts.empty()) if (const std::vector<std::string_view> parts = fmt::split_sv(str, {"ßßß"}); !parts.empty())
{ {
u64 result; u64 result;

View File

@ -1,6 +1,7 @@
#pragma once #pragma once
#include <string> #include <string>
#include <string_view>
static constexpr usz max_midi_devices = 3; static constexpr usz max_midi_devices = 3;
@ -17,5 +18,5 @@ struct midi_device
midi_device_type type{}; midi_device_type type{};
std::string name; std::string name;
static midi_device from_string(const std::string& str); static midi_device from_string(std::string_view str);
}; };

View File

@ -5,7 +5,7 @@
extern std::string g_input_config_override; extern std::string g_input_config_override;
std::vector<std::string> cfg_pad::get_buttons(const std::string& str) std::vector<std::string> cfg_pad::get_buttons(std::string_view str)
{ {
std::vector<std::string> vec = fmt::split(str, {","}); std::vector<std::string> vec = fmt::split(str, {","});

View File

@ -25,7 +25,7 @@ struct cfg_pad final : cfg::node
cfg_pad() {}; cfg_pad() {};
cfg_pad(node* owner, const std::string& name) : cfg::node(owner, name) {} cfg_pad(node* owner, const std::string& name) : cfg::node(owner, name) {}
static std::vector<std::string> get_buttons(const std::string& str); static std::vector<std::string> get_buttons(std::string_view str);
static std::string get_buttons(std::vector<std::string> vec); static std::string get_buttons(std::vector<std::string> vec);
u8 get_motor_speed(VibrateMotor& motor, f32 multiplier) const; u8 get_motor_speed(VibrateMotor& motor, f32 multiplier) const;

View File

@ -159,20 +159,6 @@ u32 get_axis_keycode(u32 offset, u16 value)
} }
} }
void ps_move_data::reset_sensors()
{
quaternion = default_quaternion;
accelerometer_x = 0.0f;
accelerometer_y = 0.0f;
accelerometer_z = 0.0f;
gyro_x = 0.0f;
gyro_y = 0.0f;
gyro_z = 0.0f;
magnetometer_x = 0.0f;
magnetometer_y = 0.0f;
magnetometer_z = 0.0f;
}
bool Pad::get_pressure_intensity_button_active(bool is_toggle_mode, u32 player_id) bool Pad::get_pressure_intensity_button_active(bool is_toggle_mode, u32 player_id)
{ {
if (m_pressure_intensity_button_index < 0) if (m_pressure_intensity_button_index < 0)

View File

@ -2,7 +2,8 @@
#include "util/types.hpp" #include "util/types.hpp"
#include "util/endian.hpp" #include "util/endian.hpp"
#include "Emu/Io/pad_config_types.h" #include "pad_config_types.h"
#include "ps_move_data.h"
#include <map> #include <map>
#include <set> #include <set>
@ -469,38 +470,6 @@ struct VibrateMotor
{} {}
}; };
struct ps_move_data
{
u32 external_device_id = 0;
std::array<u8, 38> external_device_read{}; // CELL_GEM_EXTERNAL_PORT_DEVICE_INFO_SIZE
std::array<u8, 40> external_device_write{}; // CELL_GEM_EXTERNAL_PORT_OUTPUT_SIZE
std::array<u8, 5> external_device_data{};
bool external_device_connected = false;
bool external_device_read_requested = false;
bool external_device_write_requested = false;
bool calibration_requested = false;
bool calibration_succeeded = false;
bool magnetometer_enabled = false;
bool orientation_enabled = false;
static constexpr std::array<f32, 4> default_quaternion { 1.0f, 0.0f, 0.0f, 0.0f };
std::array<f32, 4> quaternion = default_quaternion; // quaternion orientation (x,y,z,w) of controller relative to default (facing the camera with buttons up)
f32 accelerometer_x = 0.0f; // linear velocity in m/s²
f32 accelerometer_y = 0.0f; // linear velocity in m/s²
f32 accelerometer_z = 0.0f; // linear velocity in m/s²
f32 gyro_x = 0.0f; // angular velocity in rad/s
f32 gyro_y = 0.0f; // angular velocity in rad/s
f32 gyro_z = 0.0f; // angular velocity in rad/s
f32 magnetometer_x = 0.0f;
f32 magnetometer_y = 0.0f;
f32 magnetometer_z = 0.0f;
s16 temperature = 0;
void reset_sensors();
};
struct Pad struct Pad
{ {
const pad_handler m_pad_handler; const pad_handler m_pad_handler;

View File

@ -0,0 +1,137 @@
#include "stdafx.h"
#include "ps_move_data.h"
const ps_move_data::vect<4> ps_move_data::default_quaternion = ps_move_data::vect<4>({ 0.0f, 0.0f, 0.0f, 1.0f });
ps_move_data::ps_move_data()
: quaternion(default_quaternion)
{
}
void ps_move_data::reset_sensors()
{
quaternion = default_quaternion;
accelerometer = {};
gyro = {};
prev_gyro = {};
angular_acceleration = {};
magnetometer = {};
//prev_pos_world = {}; // probably no reset needed ?
vel_world = {};
prev_vel_world = {};
accel_world = {};
angvel_world = {};
angaccel_world = {};
}
void ps_move_data::update_orientation(f32 delta_time)
{
if (!delta_time)
return;
// Rotate vector v by quaternion q
const auto rotate_vector = [](const vect<4>& q, const vect<3>& v)
{
const vect<4> qv({0.0f, v.x(), v.y(), v.z()});
const vect<4> q_inv({q.w(), -q.x(), -q.y(), -q.z()});
// t = q * v
vect<4> t;
t.w() = -q.x() * qv.x() - q.y() * qv.y() - q.z() * qv.z();
t.x() = q.w() * qv.x() + q.y() * qv.z() - q.z() * qv.y();
t.y() = q.w() * qv.y() - q.x() * qv.z() + q.z() * qv.x();
t.z() = q.w() * qv.z() + q.x() * qv.y() - q.y() * qv.x();
// r = t * q_inv
vect<4> r;
r.w() = -t.x() * q_inv.x() - t.y() * q_inv.y() - t.z() * q_inv.z();
r.x() = t.w() * q_inv.x() + t.y() * q_inv.z() - t.z() * q_inv.y();
r.y() = t.w() * q_inv.y() - t.x() * q_inv.z() + t.z() * q_inv.x();
r.z() = t.w() * q_inv.z() + t.x() * q_inv.y() - t.y() * q_inv.x();
return vect<3>({r.x(), r.y(), r.z()});
};
if constexpr (use_imu_for_velocity)
{
// Gravity in world frame
constexpr f32 gravity = 9.81f;
constexpr vect<3> g({0.0f, 0.0f, -gravity});
// Rotate gravity into sensor frame
const vect<3> g_sensor = rotate_vector(quaternion, g);
// Remove gravity
vect<3> linear_local;
for (u32 i = 0; i < 3; i++)
{
linear_local[i] = (accelerometer[i] * gravity) - g_sensor[i];
}
// Linear acceleration (rotate to world coordinates)
accel_world = rotate_vector(quaternion, linear_local);
// convert to mm/s²
for (u32 i = 0; i < 3; i++)
{
accel_world[i] *= 1000.0f;
}
// Linear velocity (integrate acceleration)
for (u32 i = 0; i < 3; i++)
{
vel_world[i] = prev_vel_world[i] + accel_world[i] * delta_time;
}
prev_vel_world = vel_world;
}
// Compute raw angular acceleration
for (u32 i = 0; i < 3; i++)
{
const f32 alpha = (gyro[i] - prev_gyro[i]) / delta_time;
// Filtering
constexpr f32 weight = 0.8f;
constexpr f32 weight_inv = 1.0f - weight;
angular_acceleration[i] = weight * angular_acceleration[i] + weight_inv * alpha;
}
// Angular velocity (rotate to world coordinates)
angvel_world = rotate_vector(quaternion, gyro);
// Angular acceleration (rotate to world coordinates)
angaccel_world = rotate_vector(quaternion, angular_acceleration);
prev_gyro = gyro;
}
void ps_move_data::update_velocity(u64 timestamp, be_t<f32> pos_world[4])
{
if constexpr (use_imu_for_velocity)
return;
if (last_velocity_update_time_us == timestamp)
return;
// Get elapsed time since last update
const f32 delta_time = (last_velocity_update_time_us == 0) ? 0.0f : ((timestamp - last_velocity_update_time_us) / 1'000'000.0f);
last_velocity_update_time_us = timestamp;
if (!delta_time)
return;
for (u32 i = 0; i < 3; i++)
{
// Linear velocity
constexpr f32 weight = 0.8f;
constexpr f32 weight_inv = 1.0f - weight;
vel_world[i] = weight * ((pos_world[i] - prev_pos_world[i]) / delta_time) + weight_inv * prev_vel_world[i];
prev_pos_world[i] = pos_world[i];
// Linear acceleration
accel_world[i] = (vel_world[i] - prev_vel_world[i]) / delta_time;
}
prev_vel_world = vel_world;
}

View File

@ -0,0 +1,75 @@
#pragma once
struct ps_move_data
{
template <int Size, typename T = f32>
struct vect
{
public:
constexpr vect() = default;
constexpr vect(const std::array<T, Size>& vec) : data(vec) {};
template <typename I>
T& operator[](I i) { return data[i]; }
template <typename I>
const T& operator[](I i) const { return data[i]; }
T x() const requires (Size >= 1) { return data[0]; }
T y() const requires (Size >= 2) { return data[1]; }
T z() const requires (Size >= 3) { return data[2]; }
T w() const requires (Size >= 4) { return data[3]; }
T& x() requires (Size >= 1) { return data[0]; }
T& y() requires (Size >= 2) { return data[1]; }
T& z() requires (Size >= 3) { return data[2]; }
T& w() requires (Size >= 4) { return data[3]; }
private:
std::array<T, Size> data {};
};
ps_move_data();
u32 external_device_id = 0;
std::array<u8, 38> external_device_read{}; // CELL_GEM_EXTERNAL_PORT_DEVICE_INFO_SIZE
std::array<u8, 40> external_device_write{}; // CELL_GEM_EXTERNAL_PORT_OUTPUT_SIZE
std::array<u8, 5> external_device_data{};
bool external_device_connected = false;
bool external_device_read_requested = false;
bool external_device_write_requested = false;
bool calibration_requested = false;
bool calibration_succeeded = false;
bool magnetometer_enabled = false;
bool orientation_enabled = false;
// Disable IMU tracking of velocity and acceleration (massive drift)
static constexpr bool use_imu_for_velocity = false;
u64 last_velocity_update_time_us = 0;
static const vect<4> default_quaternion;
vect<4> quaternion {}; // quaternion orientation (x,y,z,w) of controller relative to default (facing the camera with buttons up)
// Raw values (local)
vect<3> accelerometer {}; // linear acceleration in G units (9.81 = 1 unit)
vect<3> gyro {}; // angular velocity in rad/s
vect<3> prev_gyro {}; // previous angular velocity in rad/s
vect<3> angular_acceleration {}; // angular acceleration in rad/s²
vect<3> magnetometer {};
// In world coordinates
vect<3> prev_pos_world {};
vect<3> vel_world {}; // velocity of sphere in world coordinates (mm/s)
vect<3> prev_vel_world {}; // previous velocity of sphere in world coordinates (mm/s)
vect<3> accel_world {}; // acceleration of sphere in world coordinates (mm/s²)
vect<3> angvel_world {}; // angular velocity of controller in world coordinates (radians/s)
vect<3> angaccel_world {}; // angular acceleration of controller in world coordinates (radians/s²)
s16 temperature = 0;
void reset_sensors();
void update_orientation(f32 delta_time);
void update_velocity(u64 timestamp, be_t<f32> pos_world[4]);
};

View File

@ -206,7 +206,7 @@ table SetRoomDataInternalRequest {
flagAttr:uint32; flagAttr:uint32;
roomBinAttrInternal:[BinAttr]; roomBinAttrInternal:[BinAttr];
passwordConfig:[RoomGroupPasswordConfig]; passwordConfig:[RoomGroupPasswordConfig];
passwordSlotMask:uint64; passwordSlotMask:[uint64];
ownerPrivilegeRank:[uint16]; ownerPrivilegeRank:[uint16];
} }

View File

@ -8,9 +8,9 @@
// Ensure the included flatbuffers.h is the same version as when this file was // Ensure the included flatbuffers.h is the same version as when this file was
// generated, otherwise it may not be compatible. // generated, otherwise it may not be compatible.
static_assert(FLATBUFFERS_VERSION_MAJOR == 24 && static_assert(FLATBUFFERS_VERSION_MAJOR == 25 &&
FLATBUFFERS_VERSION_MINOR == 3 && FLATBUFFERS_VERSION_MINOR == 9 &&
FLATBUFFERS_VERSION_REVISION == 25, FLATBUFFERS_VERSION_REVISION == 23,
"Non-compatible flatbuffers version included"); "Non-compatible flatbuffers version included");
struct SignalingAddr; struct SignalingAddr;
@ -2762,8 +2762,8 @@ struct SetRoomDataInternalRequest FLATBUFFERS_FINAL_CLASS : private ::flatbuffer
const ::flatbuffers::Vector<::flatbuffers::Offset<RoomGroupPasswordConfig>> *passwordConfig() const { const ::flatbuffers::Vector<::flatbuffers::Offset<RoomGroupPasswordConfig>> *passwordConfig() const {
return GetPointer<const ::flatbuffers::Vector<::flatbuffers::Offset<RoomGroupPasswordConfig>> *>(VT_PASSWORDCONFIG); return GetPointer<const ::flatbuffers::Vector<::flatbuffers::Offset<RoomGroupPasswordConfig>> *>(VT_PASSWORDCONFIG);
} }
uint64_t passwordSlotMask() const { const ::flatbuffers::Vector<uint64_t> *passwordSlotMask() const {
return GetField<uint64_t>(VT_PASSWORDSLOTMASK, 0); return GetPointer<const ::flatbuffers::Vector<uint64_t> *>(VT_PASSWORDSLOTMASK);
} }
const ::flatbuffers::Vector<uint16_t> *ownerPrivilegeRank() const { const ::flatbuffers::Vector<uint16_t> *ownerPrivilegeRank() const {
return GetPointer<const ::flatbuffers::Vector<uint16_t> *>(VT_OWNERPRIVILEGERANK); return GetPointer<const ::flatbuffers::Vector<uint16_t> *>(VT_OWNERPRIVILEGERANK);
@ -2779,7 +2779,8 @@ struct SetRoomDataInternalRequest FLATBUFFERS_FINAL_CLASS : private ::flatbuffer
VerifyOffset(verifier, VT_PASSWORDCONFIG) && VerifyOffset(verifier, VT_PASSWORDCONFIG) &&
verifier.VerifyVector(passwordConfig()) && verifier.VerifyVector(passwordConfig()) &&
verifier.VerifyVectorOfTables(passwordConfig()) && verifier.VerifyVectorOfTables(passwordConfig()) &&
VerifyField<uint64_t>(verifier, VT_PASSWORDSLOTMASK, 8) && VerifyOffset(verifier, VT_PASSWORDSLOTMASK) &&
verifier.VerifyVector(passwordSlotMask()) &&
VerifyOffset(verifier, VT_OWNERPRIVILEGERANK) && VerifyOffset(verifier, VT_OWNERPRIVILEGERANK) &&
verifier.VerifyVector(ownerPrivilegeRank()) && verifier.VerifyVector(ownerPrivilegeRank()) &&
verifier.EndTable(); verifier.EndTable();
@ -2805,8 +2806,8 @@ struct SetRoomDataInternalRequestBuilder {
void add_passwordConfig(::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset<RoomGroupPasswordConfig>>> passwordConfig) { void add_passwordConfig(::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset<RoomGroupPasswordConfig>>> passwordConfig) {
fbb_.AddOffset(SetRoomDataInternalRequest::VT_PASSWORDCONFIG, passwordConfig); fbb_.AddOffset(SetRoomDataInternalRequest::VT_PASSWORDCONFIG, passwordConfig);
} }
void add_passwordSlotMask(uint64_t passwordSlotMask) { void add_passwordSlotMask(::flatbuffers::Offset<::flatbuffers::Vector<uint64_t>> passwordSlotMask) {
fbb_.AddElement<uint64_t>(SetRoomDataInternalRequest::VT_PASSWORDSLOTMASK, passwordSlotMask, 0); fbb_.AddOffset(SetRoomDataInternalRequest::VT_PASSWORDSLOTMASK, passwordSlotMask);
} }
void add_ownerPrivilegeRank(::flatbuffers::Offset<::flatbuffers::Vector<uint16_t>> ownerPrivilegeRank) { void add_ownerPrivilegeRank(::flatbuffers::Offset<::flatbuffers::Vector<uint16_t>> ownerPrivilegeRank) {
fbb_.AddOffset(SetRoomDataInternalRequest::VT_OWNERPRIVILEGERANK, ownerPrivilegeRank); fbb_.AddOffset(SetRoomDataInternalRequest::VT_OWNERPRIVILEGERANK, ownerPrivilegeRank);
@ -2829,12 +2830,12 @@ inline ::flatbuffers::Offset<SetRoomDataInternalRequest> CreateSetRoomDataIntern
uint32_t flagAttr = 0, uint32_t flagAttr = 0,
::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset<BinAttr>>> roomBinAttrInternal = 0, ::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset<BinAttr>>> roomBinAttrInternal = 0,
::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset<RoomGroupPasswordConfig>>> passwordConfig = 0, ::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset<RoomGroupPasswordConfig>>> passwordConfig = 0,
uint64_t passwordSlotMask = 0, ::flatbuffers::Offset<::flatbuffers::Vector<uint64_t>> passwordSlotMask = 0,
::flatbuffers::Offset<::flatbuffers::Vector<uint16_t>> ownerPrivilegeRank = 0) { ::flatbuffers::Offset<::flatbuffers::Vector<uint16_t>> ownerPrivilegeRank = 0) {
SetRoomDataInternalRequestBuilder builder_(_fbb); SetRoomDataInternalRequestBuilder builder_(_fbb);
builder_.add_passwordSlotMask(passwordSlotMask);
builder_.add_roomId(roomId); builder_.add_roomId(roomId);
builder_.add_ownerPrivilegeRank(ownerPrivilegeRank); builder_.add_ownerPrivilegeRank(ownerPrivilegeRank);
builder_.add_passwordSlotMask(passwordSlotMask);
builder_.add_passwordConfig(passwordConfig); builder_.add_passwordConfig(passwordConfig);
builder_.add_roomBinAttrInternal(roomBinAttrInternal); builder_.add_roomBinAttrInternal(roomBinAttrInternal);
builder_.add_flagAttr(flagAttr); builder_.add_flagAttr(flagAttr);
@ -2849,10 +2850,11 @@ inline ::flatbuffers::Offset<SetRoomDataInternalRequest> CreateSetRoomDataIntern
uint32_t flagAttr = 0, uint32_t flagAttr = 0,
const std::vector<::flatbuffers::Offset<BinAttr>> *roomBinAttrInternal = nullptr, const std::vector<::flatbuffers::Offset<BinAttr>> *roomBinAttrInternal = nullptr,
const std::vector<::flatbuffers::Offset<RoomGroupPasswordConfig>> *passwordConfig = nullptr, const std::vector<::flatbuffers::Offset<RoomGroupPasswordConfig>> *passwordConfig = nullptr,
uint64_t passwordSlotMask = 0, const std::vector<uint64_t> *passwordSlotMask = nullptr,
const std::vector<uint16_t> *ownerPrivilegeRank = nullptr) { const std::vector<uint16_t> *ownerPrivilegeRank = nullptr) {
auto roomBinAttrInternal__ = roomBinAttrInternal ? _fbb.CreateVector<::flatbuffers::Offset<BinAttr>>(*roomBinAttrInternal) : 0; auto roomBinAttrInternal__ = roomBinAttrInternal ? _fbb.CreateVector<::flatbuffers::Offset<BinAttr>>(*roomBinAttrInternal) : 0;
auto passwordConfig__ = passwordConfig ? _fbb.CreateVector<::flatbuffers::Offset<RoomGroupPasswordConfig>>(*passwordConfig) : 0; auto passwordConfig__ = passwordConfig ? _fbb.CreateVector<::flatbuffers::Offset<RoomGroupPasswordConfig>>(*passwordConfig) : 0;
auto passwordSlotMask__ = passwordSlotMask ? _fbb.CreateVector<uint64_t>(*passwordSlotMask) : 0;
auto ownerPrivilegeRank__ = ownerPrivilegeRank ? _fbb.CreateVector<uint16_t>(*ownerPrivilegeRank) : 0; auto ownerPrivilegeRank__ = ownerPrivilegeRank ? _fbb.CreateVector<uint16_t>(*ownerPrivilegeRank) : 0;
return CreateSetRoomDataInternalRequest( return CreateSetRoomDataInternalRequest(
_fbb, _fbb,
@ -2861,7 +2863,7 @@ inline ::flatbuffers::Offset<SetRoomDataInternalRequest> CreateSetRoomDataIntern
flagAttr, flagAttr,
roomBinAttrInternal__, roomBinAttrInternal__,
passwordConfig__, passwordConfig__,
passwordSlotMask, passwordSlotMask__,
ownerPrivilegeRank__); ownerPrivilegeRank__);
} }

View File

@ -28,7 +28,8 @@ namespace np
dnshook::dnshook() dnshook::dnshook()
{ {
// Init switch map for dns // Init switch map for dns
auto swaps = fmt::split(g_cfg.net.swap_list.to_string(), {"&&"}); const std::string swap_list = g_cfg.net.swap_list.to_string();
const auto swaps = fmt::split_sv(swap_list, {"&&"});
for (usz i = 0; i < swaps.size(); i++) for (usz i = 0; i < swaps.size(); i++)
{ {
auto host_and_ip = fmt::split(swaps[i], {"="}); auto host_and_ip = fmt::split(swaps[i], {"="});

View File

@ -255,7 +255,7 @@ namespace rpcn
rpcn_log.notice("online: %s, pr_com_id: %s, pr_title: %s, pr_status: %s, pr_comment: %s, pr_data: %s", online ? "true" : "false", pr_com_id.data, pr_title, pr_status, pr_comment, fmt::buf_to_hexstring(pr_data.data(), pr_data.size())); rpcn_log.notice("online: %s, pr_com_id: %s, pr_title: %s, pr_status: %s, pr_comment: %s, pr_data: %s", online ? "true" : "false", pr_com_id.data, pr_title, pr_status, pr_comment, fmt::buf_to_hexstring(pr_data.data(), pr_data.size()));
} }
constexpr u32 RPCN_PROTOCOL_VERSION = 26; constexpr u32 RPCN_PROTOCOL_VERSION = 27;
constexpr usz RPCN_HEADER_SIZE = 15; constexpr usz RPCN_HEADER_SIZE = 15;
const char* error_to_explanation(rpcn::ErrorType error) const char* error_to_explanation(rpcn::ErrorType error)
@ -2019,9 +2019,13 @@ namespace rpcn
} }
final_grouppasswordconfig_vec = builder.CreateVector(davec); final_grouppasswordconfig_vec = builder.CreateVector(davec);
} }
u64 final_passwordSlotMask = 0;
flatbuffers::Offset<flatbuffers::Vector<u64>> final_passwordSlotMask;
if (req->passwordSlotMask) if (req->passwordSlotMask)
final_passwordSlotMask = *req->passwordSlotMask; {
const u64 value = *req->passwordSlotMask;
final_passwordSlotMask = builder.CreateVector(&value, 1);
}
flatbuffers::Offset<flatbuffers::Vector<u16>> final_ownerprivilege_vec; flatbuffers::Offset<flatbuffers::Vector<u16>> final_ownerprivilege_vec;
if (req->ownerPrivilegeRankNum && req->ownerPrivilegeRank) if (req->ownerPrivilegeRankNum && req->ownerPrivilegeRank)

View File

@ -79,7 +79,8 @@ std::string cfg_rpcn::get_host() const
std::vector<std::pair<std::string, std::string>> cfg_rpcn::get_hosts() std::vector<std::pair<std::string, std::string>> cfg_rpcn::get_hosts()
{ {
std::vector<std::pair<std::string, std::string>> vec_hosts; std::vector<std::pair<std::string, std::string>> vec_hosts;
auto hosts_list = fmt::split(hosts.to_string(), {"|||"}); const std::string host_str = hosts.to_string();
const auto hosts_list = fmt::split_sv(host_str, {"|||"});
for (const auto& cur_host : hosts_list) for (const auto& cur_host : hosts_list)
{ {
@ -190,9 +191,8 @@ bool cfg_rpcn::add_host(std::string_view new_description, std::string_view new_h
bool cfg_rpcn::del_host(std::string_view del_description, std::string_view del_host) bool cfg_rpcn::del_host(std::string_view del_description, std::string_view del_host)
{ {
// Do not delete default servers // Do not delete default server
if ((del_description == "Official RPCN Server" && del_host == "np.rpcs3.net") || if (del_description == "Official RPCN Server" && del_host == "np.rpcs3.net")
(del_description == "RPCN Test Server" && del_host == "test-np.rpcs3.net"))
{ {
return true; return true;
} }

View File

@ -9,7 +9,7 @@ struct cfg_rpcn : cfg::node
cfg::string npid{this, "NPID", ""}; cfg::string npid{this, "NPID", ""};
cfg::string password{this, "Password", ""}; cfg::string password{this, "Password", ""};
cfg::string token{this, "Token", ""}; cfg::string token{this, "Token", ""};
cfg::string hosts{this, "Hosts", "Official RPCN Server|np.rpcs3.net|||RPCN Test Server|test-np.rpcs3.net"}; cfg::string hosts{this, "Hosts", "Official RPCN Server|np.rpcs3.net"};
cfg::_bool ipv6_support{this, "IPv6 support", true}; cfg::_bool ipv6_support{this, "IPv6 support", true};
void load(); void load();

View File

@ -337,7 +337,7 @@ namespace rsx
AUDIT(_loc < _size); AUDIT(_loc < _size);
const auto remaining = (_size - _loc); const auto remaining = (_size - _loc);
memmove(pos + 1, pos, remaining * sizeof(Ty)); std::memmove(pos + 1, pos, remaining * sizeof(Ty));
*pos = val; *pos = val;
_size++; _size++;
@ -365,7 +365,7 @@ namespace rsx
AUDIT(_loc < _size); AUDIT(_loc < _size);
const u32 remaining = (_size - _loc); const u32 remaining = (_size - _loc);
memmove(pos + 1, pos, remaining * sizeof(Ty)); std::memmove(pos + 1, pos, remaining * sizeof(Ty));
*pos = val; *pos = val;
_size++; _size++;
@ -373,6 +373,31 @@ namespace rsx
return pos; return pos;
} }
iterator insert(iterator where, span_like<Ty> auto const& values)
{
ensure(where >= _data);
const auto _loc = offset(where);
const auto in_size = static_cast<u32>(values.size());
const auto in_size_bytes = in_size * sizeof(Ty);
reserve(_size + in_size);
if (_loc >= _size)
{
where = _data + _size;
std::memcpy(where, values.data(), in_size_bytes);
_size += in_size;
return where;
}
const u32 remaining_bytes = (_size - _loc) * sizeof(Ty);
where = _data + _loc;
std::memmove(where + in_size, where, remaining_bytes);
std::memmove(where, values.data(), in_size_bytes);
_size += in_size;
return where;
}
void operator += (const rsx::simple_array<Ty>& that) void operator += (const rsx::simple_array<Ty>& that)
{ {
const auto old_size = _size; const auto old_size = _size;

View File

@ -120,9 +120,15 @@ namespace rsx
{ {
result.font_names.emplace_back("Arial.ttf"); result.font_names.emplace_back("Arial.ttf");
result.font_names.emplace_back("arial.ttf"); result.font_names.emplace_back("arial.ttf");
#ifndef _WIN32 #ifdef __APPLE__
result.font_names.emplace_back("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf"); // ubuntu result.font_names.emplace_back("DejaVuSans.ttf");
result.font_names.emplace_back("/usr/share/fonts/TTF/DejaVuSans.ttf"); // arch result.font_names.emplace_back("NotoSans-Regular.ttf");
result.font_names.emplace_back("Roboto-Regular.ttf");
result.font_names.emplace_back("OpenSans-Regular.ttf");
result.font_names.emplace_back("FreeSans.ttf");
#elif !defined(_WIN32)
result.font_names.emplace_back("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf"); // ubuntu
result.font_names.emplace_back("/usr/share/fonts/TTF/DejaVuSans.ttf"); // arch
#endif #endif
// Attempt to load a font from dev_flash as a last resort // Attempt to load a font from dev_flash as a last resort
result.font_names.emplace_back("SCE-PS3-VR-R-LATIN.TTF"); result.font_names.emplace_back("SCE-PS3-VR-R-LATIN.TTF");

View File

@ -34,6 +34,11 @@ namespace rsx::assembler
} }
}; };
struct CFGPass
{
virtual void run(FlowGraph& graph) = 0;
};
FlowGraph deconstruct_fragment_program(const RSXFragmentProgram& prog); FlowGraph deconstruct_fragment_program(const RSXFragmentProgram& prog);
} }

View File

@ -0,0 +1,455 @@
#include "stdafx.h"
#include "FPASM.h"
#include "Emu/RSX/Program/RSXFragmentProgram.h"
#include <stack>
#ifndef _WIN32
#define sscanf_s sscanf
#endif
namespace rsx::assembler
{
struct FP_opcode_encoding_t
{
FP_opcode op;
bool exec_if_lt;
bool exec_if_eq;
bool exec_if_gt;
bool set_cond;
};
static std::unordered_map<std::string_view, FP_opcode_encoding_t> s_opcode_lookup
{
// Arithmetic
{ "NOP", { .op = RSX_FP_OPCODE_NOP, .exec_if_lt = true, .exec_if_eq = true, .exec_if_gt = true, .set_cond = false } },
{ "MOV", { .op = RSX_FP_OPCODE_MOV, .exec_if_lt = true, .exec_if_eq = true, .exec_if_gt = true, .set_cond = false } },
{ "MUL", { .op = RSX_FP_OPCODE_MUL, .exec_if_lt = true, .exec_if_eq = true, .exec_if_gt = true, .set_cond = false } },
{ "ADD", { .op = RSX_FP_OPCODE_ADD, .exec_if_lt = true, .exec_if_eq = true, .exec_if_gt = true, .set_cond = false } },
{ "MAD", { .op = RSX_FP_OPCODE_MAD, .exec_if_lt = true, .exec_if_eq = true, .exec_if_gt = true, .set_cond = false } },
{ "FMA", { .op = RSX_FP_OPCODE_MAD, .exec_if_lt = true, .exec_if_eq = true, .exec_if_gt = true, .set_cond = false } },
{ "DP3", { .op = RSX_FP_OPCODE_DP3, .exec_if_lt = true, .exec_if_eq = true, .exec_if_gt = true, .set_cond = false } },
{ "DP4", { .op = RSX_FP_OPCODE_DP4, .exec_if_lt = true, .exec_if_eq = true, .exec_if_gt = true, .set_cond = false } },
// Constant load
{ "SFL", {.op = RSX_FP_OPCODE_SFL, .exec_if_lt = true, .exec_if_eq = true, .exec_if_gt = true, .set_cond = false } },
{ "STR", {.op = RSX_FP_OPCODE_STR, .exec_if_lt = true, .exec_if_eq = true, .exec_if_gt = true, .set_cond = false } },
// Pack-unpack operations are great for testing dependencies
{ "PKH", { .op = RSX_FP_OPCODE_PK2, .exec_if_lt = true, .exec_if_eq = true, .exec_if_gt = true, .set_cond = false } },
{ "UPH", { .op = RSX_FP_OPCODE_UP2, .exec_if_lt = true, .exec_if_eq = true, .exec_if_gt = true, .set_cond = false } },
{ "PK16U", { .op = RSX_FP_OPCODE_PK16, .exec_if_lt = true, .exec_if_eq = true, .exec_if_gt = true, .set_cond = false } },
{ "UP16U", { .op = RSX_FP_OPCODE_UP16, .exec_if_lt = true, .exec_if_eq = true, .exec_if_gt = true, .set_cond = false } },
{ "PK8U", { .op = RSX_FP_OPCODE_PKB, .exec_if_lt = true, .exec_if_eq = true, .exec_if_gt = true, .set_cond = false } },
{ "UP8U", { .op = RSX_FP_OPCODE_UPB, .exec_if_lt = true, .exec_if_eq = true, .exec_if_gt = true, .set_cond = false } },
{ "PK8G", { .op = RSX_FP_OPCODE_PKG, .exec_if_lt = true, .exec_if_eq = true, .exec_if_gt = true, .set_cond = false } },
{ "UP8G", { .op = RSX_FP_OPCODE_UPG, .exec_if_lt = true, .exec_if_eq = true, .exec_if_gt = true, .set_cond = false } },
{ "PK8S", { .op = RSX_FP_OPCODE_PK4, .exec_if_lt = true, .exec_if_eq = true, .exec_if_gt = true, .set_cond = false } },
{ "UP8S", { .op = RSX_FP_OPCODE_UP4, .exec_if_lt = true, .exec_if_eq = true, .exec_if_gt = true, .set_cond = false } },
// Basic conditionals
{ "IF.LT", { .op = RSX_FP_OPCODE_IFE, .exec_if_lt = true, .exec_if_eq = false, .exec_if_gt = false, .set_cond = false } },
{ "IF.LE", { .op = RSX_FP_OPCODE_IFE, .exec_if_lt = true, .exec_if_eq = true, .exec_if_gt = false, .set_cond = false } },
{ "IF.EQ", { .op = RSX_FP_OPCODE_IFE, .exec_if_lt = false, .exec_if_eq = true, .exec_if_gt = false, .set_cond = false } },
{ "IF.GE", { .op = RSX_FP_OPCODE_IFE, .exec_if_lt = false, .exec_if_eq = true, .exec_if_gt = true, .set_cond = false } },
{ "IF.GT", { .op = RSX_FP_OPCODE_IFE, .exec_if_lt = false, .exec_if_eq = false, .exec_if_gt = true, .set_cond = false } },
{ "SLT", { .op = RSX_FP_OPCODE_SLT, .exec_if_lt = false, .exec_if_eq = false, .exec_if_gt = false, .set_cond = true } },
{ "SEQ", { .op = RSX_FP_OPCODE_SEQ, .exec_if_lt = false, .exec_if_eq = false, .exec_if_gt = false, .set_cond = true } },
{ "SGT", { .op = RSX_FP_OPCODE_SGT, .exec_if_lt = false, .exec_if_eq = false, .exec_if_gt = false, .set_cond = true } },
// TODO: Add more
};
Instruction* FPIR::load(const RegisterRef& ref, int operand, Instruction* prev)
{
Instruction* target = prev;
if (!target)
{
m_instructions.push_back({});
target = &m_instructions.back();
}
SRC_Common src{ .HEX = target->bytecode[operand + 1] };
src.reg_type = RSX_FP_REGISTER_TYPE_TEMP;
src.fp16 = ref.reg.f16 ? 1 : 0;
src.tmp_reg_index = static_cast<u32>(ref.reg.id);
src.swizzle_x = 0;
src.swizzle_y = 1;
src.swizzle_z = 2;
src.swizzle_w = 3;
target->bytecode[operand + 1] = src.HEX;
return target;
}
Instruction* FPIR::load(const std::array<f32, 4>& constants, int operand, Instruction* prev)
{
Instruction* target = prev;
if (!target)
{
m_instructions.push_back({});
target = &m_instructions.back();
}
// Unsupported for now
ensure(target->length == 4, "FPIR cannot encode more than one constant load per instruction");
SRC_Common src{ .HEX = target->bytecode[operand + 1] };
src.reg_type = RSX_FP_REGISTER_TYPE_CONSTANT;
target->bytecode[operand + 1] = src.HEX;
src.swizzle_x = 0;
src.swizzle_y = 1;
src.swizzle_z = 2;
src.swizzle_w = 3;
// Embed literal constant
std::memcpy(&target->bytecode[4], constants.data(), 4 * sizeof(u32));
target->length = 8;
return target;
}
Instruction* FPIR::store(const RegisterRef& ref, Instruction* prev)
{
Instruction* target = prev;
if (!target)
{
m_instructions.push_back({});
target = &m_instructions.back();
}
OPDEST dst{ .HEX = target->bytecode[0] };
dst.dest_reg = static_cast<u32>(ref.reg.id);
dst.fp16 = ref.reg.f16 ? 1 : 0;
dst.write_mask = ref.mask;
dst.prec = ref.reg.f16 ? RSX_FP_PRECISION_HALF : RSX_FP_PRECISION_REAL;
target->bytecode[0] = dst.HEX;
return target;
}
void FPIR::mov(const RegisterRef& dst, f32 constant)
{
Instruction* inst = store(dst);
inst = load(std::array<f32, 4>{ constant, constant, constant, constant }, 0);
inst->opcode = RSX_FP_OPCODE_MOV;
}
void FPIR::mov(const RegisterRef& dst, const RegisterRef& src)
{
Instruction* inst = store(dst);
inst = load(src, 0);
inst->opcode = RSX_FP_OPCODE_MOV;
}
void FPIR::add(const RegisterRef& dst, const std::array<f32, 4>& constants)
{
Instruction* inst = store(dst);
inst = load(constants, 0);
inst->opcode = RSX_FP_OPCODE_ADD;
}
void FPIR::add(const RegisterRef& dst, const RegisterRef& src)
{
Instruction* inst = store(dst);
inst = load(src, 0);
inst->opcode = RSX_FP_OPCODE_ADD;
}
const std::vector<Instruction>& FPIR::instructions() const
{
return m_instructions;
}
std::vector<u32> FPIR::compile() const
{
std::vector<u32> result;
result.reserve(m_instructions.size() * 4);
for (const auto& inst : m_instructions)
{
const auto src = reinterpret_cast<const be_t<u16>*>(inst.bytecode);
for (u32 j = 0; j < inst.length; ++j)
{
const u16 low = src[j * 2];
const u16 hi = src[j * 2 + 1];
const u32 word = static_cast<u16>(low) | (static_cast<u32>(hi) << 16u);
result.push_back(word);
}
}
return result;
}
FPIR FPIR::from_source(std::string_view asm_)
{
std::vector<std::string> instructions = fmt::split(asm_, { "\n", ";" });
if (instructions.empty())
{
return {};
}
auto transform_inst = [](std::string_view s)
{
std::string result;
result.reserve(s.size());
bool literal = false;
for (const auto& c : s)
{
if (c == ' ')
{
if (!literal && !result.empty() && result.back() != ',')
{
result += ','; // Replace token separator space with comma
}
continue;
}
if (std::isspace(c))
{
continue;
}
if (!literal && c == '{')
{
literal = true;
}
if (literal && c == '}')
{
literal = false;
}
if (c == ',')
{
result += (literal ? '|' : ',');
continue;
}
result += c;
}
return result;
};
auto decode_instruction = [&](std::string_view inst, std::string& op, std::string& dst, std::vector<std::string>& sources)
{
const auto i = transform_inst(inst);
if (i.empty())
{
return;
}
const auto tokens = fmt::split(i, { "," });
ensure(!tokens.empty(), "Invalid input");
op = tokens.front();
if (tokens.size() > 1)
{
dst = tokens[1];
}
for (size_t n = 2; n < tokens.size(); ++n)
{
sources.push_back(tokens[n]);
}
};
auto get_ref = [](std::string_view reg)
{
ensure(reg.length() > 1, "Invalid register specifier");
const auto parts = fmt::split(reg, { "." });
ensure(parts.size() > 0 && parts.size() <= 2);
const auto index = std::stoi(parts[0].substr(1));
RegisterRef ref
{
.reg { .id = index, .f16 = false },
.mask = 0x0F
};
if (parts.size() > 1 && parts[1].length() > 0)
{
// FIXME: No swizzles for now, just lane masking
ref.mask = 0;
if (parts[1].find("x") != std::string::npos) ref.mask |= (1u << 0);
if (parts[1].find("y") != std::string::npos) ref.mask |= (1u << 1);
if (parts[1].find("z") != std::string::npos) ref.mask |= (1u << 2);
if (parts[1].find("w") != std::string::npos) ref.mask |= (1u << 3);
}
if (reg[0] == 'H' || reg[0] == 'h')
{
ref.reg.f16 = true;
}
return ref;
};
auto get_constants = [](std::string_view reg) -> std::array<f32, 4>
{
float x, y, z, w;
if (sscanf_s(reg.data(), "#{%f|%f|%f|%f}", &x, &y, &z, &w) == 4)
{
return { x, y, z, w };
}
if (sscanf_s(reg.data(), "#{%f}", &x) == 1)
{
return { x, x, x, x };
}
fmt::throw_exception("Invalid constant literal");
};
auto encode_branch_else = [](Instruction* inst, u32 end)
{
SRC1 src1{ .HEX = inst->bytecode[2] };
src1.else_offset = static_cast<u32>(end);
inst->bytecode[2] = src1.HEX;
};
auto encode_branch_end = [](Instruction *inst, u32 end)
{
SRC2 src2 { .HEX = inst->bytecode[3] };
src2.end_offset = static_cast<u32>(end);
inst->bytecode[3] = src2.HEX;
SRC1 src1{ .HEX = inst->bytecode[2] };
if (!src1.else_offset)
{
src1.else_offset = static_cast<u32>(end);
inst->bytecode[2] = src1.HEX;
}
};
auto encode_opcode = [](std::string_view op, Instruction* inst)
{
OPDEST d0 { .HEX = inst->bytecode[0] };
SRC0 s0 { .HEX = inst->bytecode[1] };
SRC1 s1 { .HEX = inst->bytecode[2] };
const auto found = s_opcode_lookup.find(op);
if (found == s_opcode_lookup.end())
{
fmt::throw_exception("Unhandled instruction '%s'", op);
}
const auto& encoding = found->second;
inst->opcode = encoding.op;
d0.opcode = encoding.op & 0x3F;
s1.opcode_hi = (encoding.op > 0x3F)? 1 : 0;
s0.exec_if_eq = encoding.exec_if_eq ? 1 : 0;
s0.exec_if_gr = encoding.exec_if_gt ? 1 : 0;
s0.exec_if_lt = encoding.exec_if_lt ? 1 : 0;
d0.set_cond = encoding.set_cond ? 1 : 0;
inst->bytecode[0] = d0.HEX;
inst->bytecode[1] = s0.HEX;
inst->bytecode[2] = s1.HEX;
};
std::string op, dst;
std::vector<std::string> sources;
std::stack<size_t> if_ops;
std::stack<size_t> loop_ops;
u32 pc = 0;
FPIR ir{};
for (const auto& instruction : instructions)
{
op.clear();
dst.clear();
sources.clear();
decode_instruction(instruction, op, dst, sources);
if (op.empty())
{
continue;
}
if (op.starts_with("IF."))
{
if_ops.push(ir.m_instructions.size());
}
else if (op == "LOOP")
{
loop_ops.push(ir.m_instructions.size());
}
else if (op == "ELSE")
{
ensure(!if_ops.empty());
encode_branch_else(&ir.m_instructions[if_ops.top()], pc);
continue;
}
else if (op == "ENDIF")
{
ensure(!if_ops.empty());
encode_branch_end(&ir.m_instructions[if_ops.top()], pc);
if_ops.pop();
continue;
}
else if (op == "ENDLOOP")
{
ensure(!loop_ops.empty());
encode_branch_end(&ir.m_instructions[loop_ops.top()], pc);
loop_ops.pop();
continue;
}
ir.m_instructions.push_back({});
Instruction* target = &ir.m_instructions.back();
pc += 4;
encode_opcode(op, target);
ensure(sources.size() == FP::get_operand_count(static_cast<FP_opcode>(target->opcode)), "Invalid operand count for opcode");
if (dst.empty())
{
OPDEST dst{ .HEX = target->bytecode[0] };
dst.no_dest = 1;
target->bytecode[0] = dst.HEX;
}
else
{
ir.store(get_ref(dst), target);
}
int operand = 0;
bool has_literal = false;
for (const auto& source : sources)
{
if (source.front() == '#')
{
const auto literal = get_constants(source);
ir.load(literal, operand++, target);
has_literal = true;
continue;
}
ir.load(get_ref(source), operand++, target);
}
if (has_literal)
{
pc += 4;
}
}
if (!ir.m_instructions.empty())
{
OPDEST d0{ .HEX = ir.m_instructions.back().bytecode[0] };
d0.end = 1;
ir.m_instructions.back().bytecode[0] = d0.HEX;
}
return ir;
}
}

View File

@ -0,0 +1,29 @@
#pragma once
#include "IR.h"
namespace rsx::assembler
{
class FPIR
{
public:
void mov(const RegisterRef& dst, f32 constant);
void mov(const RegisterRef& dst, const RegisterRef& src);
void add(const RegisterRef& dst, const std::array<f32, 4>& constants);
void add(const RegisterRef& dst, const RegisterRef& src);
const std::vector<Instruction>& instructions() const;
std::vector<u32> compile() const;
static FPIR from_source(std::string_view asm_);
private:
Instruction* load(const RegisterRef& reg, int operand, Instruction* target = nullptr);
Instruction* load(const std::array<f32, 4>& constants, int operand, Instruction* target = nullptr);
Instruction* store(const RegisterRef& reg, Instruction* target = nullptr);
std::vector<Instruction> m_instructions;
};
}

View File

@ -0,0 +1,428 @@
#include "stdafx.h"
#include "FPOpcodes.h"
#include "Emu/RSX/Common/simple_array.hpp"
#include "Emu/RSX/Program/RSXFragmentProgram.h"
#include <unordered_set>
namespace rsx::assembler::FP
{
u8 get_operand_count(FP_opcode opcode)
{
switch (opcode)
{
case RSX_FP_OPCODE_NOP:
return 0;
case RSX_FP_OPCODE_MOV:
return 1;
case RSX_FP_OPCODE_MUL:
case RSX_FP_OPCODE_ADD:
return 2;
case RSX_FP_OPCODE_MAD:
return 3;
case RSX_FP_OPCODE_DP3:
case RSX_FP_OPCODE_DP4:
return 2;
case RSX_FP_OPCODE_DST:
return 2;
case RSX_FP_OPCODE_MIN:
case RSX_FP_OPCODE_MAX:
return 2;
case RSX_FP_OPCODE_SLT:
case RSX_FP_OPCODE_SGE:
case RSX_FP_OPCODE_SLE:
case RSX_FP_OPCODE_SGT:
case RSX_FP_OPCODE_SNE:
case RSX_FP_OPCODE_SEQ:
return 2;
case RSX_FP_OPCODE_FRC:
case RSX_FP_OPCODE_FLR:
return 1;
case RSX_FP_OPCODE_KIL:
return 0;
case RSX_FP_OPCODE_PK4:
case RSX_FP_OPCODE_UP4:
return 1;
case RSX_FP_OPCODE_DDX:
case RSX_FP_OPCODE_DDY:
return 1;
case RSX_FP_OPCODE_TEX:
case RSX_FP_OPCODE_TXD:
case RSX_FP_OPCODE_TXP:
return 1;
case RSX_FP_OPCODE_RCP:
case RSX_FP_OPCODE_RSQ:
case RSX_FP_OPCODE_EX2:
case RSX_FP_OPCODE_LG2:
return 1;
case RSX_FP_OPCODE_LIT:
return 1;
case RSX_FP_OPCODE_LRP:
return 3;
case RSX_FP_OPCODE_STR:
case RSX_FP_OPCODE_SFL:
return 0;
case RSX_FP_OPCODE_COS:
case RSX_FP_OPCODE_SIN:
return 1;
case RSX_FP_OPCODE_PK2:
case RSX_FP_OPCODE_UP2:
return 1;
case RSX_FP_OPCODE_PKB:
case RSX_FP_OPCODE_UPB:
case RSX_FP_OPCODE_PK16:
case RSX_FP_OPCODE_UP16:
case RSX_FP_OPCODE_PKG:
case RSX_FP_OPCODE_UPG:
return 1;
case RSX_FP_OPCODE_DP2A:
return 3;
case RSX_FP_OPCODE_TXL:
case RSX_FP_OPCODE_TXB:
return 2;
case RSX_FP_OPCODE_DP2:
return 2;
case RSX_FP_OPCODE_NRM:
return 1;
case RSX_FP_OPCODE_DIV:
case RSX_FP_OPCODE_DIVSQ:
return 2;
case RSX_FP_OPCODE_LIF:
return 1;
case RSX_FP_OPCODE_FENCT:
case RSX_FP_OPCODE_FENCB:
case RSX_FP_OPCODE_BRK:
case RSX_FP_OPCODE_CAL:
case RSX_FP_OPCODE_IFE:
case RSX_FP_OPCODE_LOOP:
case RSX_FP_OPCODE_REP:
case RSX_FP_OPCODE_RET:
// Flow control. Special registers are provided for these outside the common file
return 0;
// The rest are unimplemented and not encountered in real software.
// TODO: Probe these on real PS3 and figure out what they actually do.
case RSX_FP_OPCODE_POW:
fmt::throw_exception("Unimplemented POW instruction."); // Unused
case RSX_FP_OPCODE_BEM:
case RSX_FP_OPCODE_TEXBEM:
case RSX_FP_OPCODE_TXPBEM:
case RSX_FP_OPCODE_BEMLUM:
fmt::throw_exception("Unimplemented BEM class instruction"); // Unused
case RSX_FP_OPCODE_REFL:
return 2;
case RSX_FP_OPCODE_TIMESWTEX:
fmt::throw_exception("Unimplemented TIMESWTEX instruction"); // Unused
default:
break;
}
return 0;
}
// Returns a lane mask for the given operand.
// The lane mask is the fixed function hardware lane so swizzles need to be applied on top to resolve the real data channel.
u32 get_src_vector_lane_mask(const RSXFragmentProgram& prog, const Instruction* instruction, u32 operand)
{
constexpr u32 x = 0b0001;
constexpr u32 y = 0b0010;
constexpr u32 z = 0b0100;
constexpr u32 w = 0b1000;
constexpr u32 xy = 0b0011;
constexpr u32 xyz = 0b0111;
constexpr u32 xyzw = 0b1111;
const auto decode = [&](const rsx::simple_array<u32>& masks) -> u32
{
return operand < masks.size()
? masks[operand]
: 0u;
};
auto opcode = static_cast<FP_opcode>(instruction->opcode);
if (operand >= get_operand_count(opcode))
{
return 0;
}
OPDEST d0 { .HEX = instruction->bytecode[0] };
const u32 dst_write_mask = d0.no_dest ? 0 : d0.write_mask;
switch (opcode)
{
case RSX_FP_OPCODE_NOP:
return 0;
case RSX_FP_OPCODE_MOV:
case RSX_FP_OPCODE_MUL:
case RSX_FP_OPCODE_ADD:
case RSX_FP_OPCODE_MAD:
return xyzw & dst_write_mask;
case RSX_FP_OPCODE_DP3:
return xyz;
case RSX_FP_OPCODE_DP4:
return xyzw;
case RSX_FP_OPCODE_DST:
return decode({ y | z, y | w });
case RSX_FP_OPCODE_MIN:
case RSX_FP_OPCODE_MAX:
return xyzw & dst_write_mask;
case RSX_FP_OPCODE_SLT:
case RSX_FP_OPCODE_SGE:
case RSX_FP_OPCODE_SLE:
case RSX_FP_OPCODE_SGT:
case RSX_FP_OPCODE_SNE:
case RSX_FP_OPCODE_SEQ:
return xyzw & dst_write_mask;
case RSX_FP_OPCODE_FRC:
case RSX_FP_OPCODE_FLR:
return xyzw & dst_write_mask;
case RSX_FP_OPCODE_KIL:
return 0;
case RSX_FP_OPCODE_PK4:
return xyzw;
case RSX_FP_OPCODE_UP4:
return x;
case RSX_FP_OPCODE_DDX:
case RSX_FP_OPCODE_DDY:
return xyzw & dst_write_mask;
case RSX_FP_OPCODE_TEX:
case RSX_FP_OPCODE_TXD:
switch (prog.get_texture_dimension(d0.tex_num))
{
case rsx::texture_dimension_extended::texture_dimension_1d:
return x;
case rsx::texture_dimension_extended::texture_dimension_2d:
return xy;
case rsx::texture_dimension_extended::texture_dimension_3d:
case rsx::texture_dimension_extended::texture_dimension_cubemap:
return xyz;
default:
return 0;
}
case RSX_FP_OPCODE_TXP:
switch (prog.get_texture_dimension(d0.tex_num))
{
case rsx::texture_dimension_extended::texture_dimension_1d:
return xy;
case rsx::texture_dimension_extended::texture_dimension_2d:
return xyz;
case rsx::texture_dimension_extended::texture_dimension_3d:
case rsx::texture_dimension_extended::texture_dimension_cubemap:
return xyzw;
default:
return 0;
}
case RSX_FP_OPCODE_RCP:
case RSX_FP_OPCODE_RSQ:
case RSX_FP_OPCODE_EX2:
case RSX_FP_OPCODE_LG2:
return x;
case RSX_FP_OPCODE_LIT:
return xyzw;
case RSX_FP_OPCODE_LRP:
return xyzw & dst_write_mask;
case RSX_FP_OPCODE_STR:
case RSX_FP_OPCODE_SFL:
return xyzw & dst_write_mask;
case RSX_FP_OPCODE_COS:
case RSX_FP_OPCODE_SIN:
return x;
case RSX_FP_OPCODE_PK2:
return xy;
case RSX_FP_OPCODE_UP2:
return x;
case RSX_FP_OPCODE_PKB:
return xyzw;
case RSX_FP_OPCODE_UPB:
return x;
case RSX_FP_OPCODE_PK16:
return xy;
case RSX_FP_OPCODE_UP16:
return x;
case RSX_FP_OPCODE_PKG:
return xyzw;
case RSX_FP_OPCODE_UPG:
return x;
case RSX_FP_OPCODE_DP2A:
return decode({ xy, xy, x });
case RSX_FP_OPCODE_TXL:
case RSX_FP_OPCODE_TXB:
return decode({ xy, x });
case RSX_FP_OPCODE_REFL:
return xyzw;
case RSX_FP_OPCODE_DP2:
return xy;
case RSX_FP_OPCODE_NRM:
return xyz;
case RSX_FP_OPCODE_DIV:
case RSX_FP_OPCODE_DIVSQ:
return decode({ xyzw, x }) & dst_write_mask;
case RSX_FP_OPCODE_LIF:
return decode({ y | w });
case RSX_FP_OPCODE_FENCT:
case RSX_FP_OPCODE_FENCB:
case RSX_FP_OPCODE_BRK:
case RSX_FP_OPCODE_CAL:
case RSX_FP_OPCODE_IFE:
case RSX_FP_OPCODE_LOOP:
case RSX_FP_OPCODE_REP:
case RSX_FP_OPCODE_RET:
// Flow control. Special registers are provided for these outside the common file
return 0;
case RSX_FP_OPCODE_POW:
fmt::throw_exception("Unimplemented POW instruction."); // Unused ??
case RSX_FP_OPCODE_BEM:
case RSX_FP_OPCODE_TEXBEM:
case RSX_FP_OPCODE_TXPBEM:
case RSX_FP_OPCODE_BEMLUM:
fmt::throw_exception("Unimplemented BEM class instruction"); // Unused
case RSX_FP_OPCODE_TIMESWTEX:
fmt::throw_exception("Unimplemented TIMESWTEX instruction"); // Unused
default:
break;
}
return 0;
}
// Resolved vector lane mask with swizzles applied.
u32 get_src_vector_lane_mask_shuffled(const RSXFragmentProgram& prog, const Instruction* instruction, u32 operand)
{
// Brute-force this. There's only 16 permutations.
constexpr u32 x = 0b0001;
constexpr u32 y = 0b0010;
constexpr u32 z = 0b0100;
constexpr u32 w = 0b1000;
const u32 lane_mask = get_src_vector_lane_mask(prog, instruction, operand);
if (!lane_mask)
{
return lane_mask;
}
// Now we resolve matching lanes.
// This sequence can be drastically sped up using lookup tables but that will come later.
std::unordered_set<u32> inputs;
SRC_Common src { .HEX = instruction->bytecode[operand + 1] };
if (src.reg_type != RSX_FP_REGISTER_TYPE_TEMP)
{
return 0;
}
if (lane_mask & x) inputs.insert(src.swizzle_x);
if (lane_mask & y) inputs.insert(src.swizzle_y);
if (lane_mask & z) inputs.insert(src.swizzle_z);
if (lane_mask & w) inputs.insert(src.swizzle_w);
u32 result = 0;
if (inputs.contains(0)) result |= x;
if (inputs.contains(1)) result |= y;
if (inputs.contains(2)) result |= z;
if (inputs.contains(3)) result |= w;
return result;
}
bool is_delay_slot(const Instruction* instruction)
{
OPDEST dst { .HEX = instruction->bytecode[0] };
SRC0 src0 { .HEX = instruction->bytecode[1] };
SRC1 src1{ .HEX = instruction->bytecode[2] };
if (dst.opcode != RSX_FP_OPCODE_MOV || // These slots are always populated with MOV
dst.no_dest || // Must have a sink
src0.reg_type != RSX_FP_REGISTER_TYPE_TEMP || // Must read from reg
dst.dest_reg != src0.tmp_reg_index || // Must be a write-to-self
dst.fp16 || // Always full lane. We need to collect more data on this but it won't matter
dst.saturate || // Precision modifier
(dst.prec != RSX_FP_PRECISION_REAL &&
dst.prec != RSX_FP_PRECISION_UNKNOWN)) // Cannot have precision modifiers
{
return false;
}
// Check if we have precision modifiers on the source
if (src0.abs || src0.neg || src1.scale)
{
return false;
}
if (dst.mask_x && src0.swizzle_x != 0) return false;
if (dst.mask_y && src0.swizzle_y != 1) return false;
if (dst.mask_z && src0.swizzle_z != 2) return false;
if (dst.mask_w && src0.swizzle_w != 3) return false;
return true;
}
RegisterRef get_src_register(const RSXFragmentProgram& prog, const Instruction* instruction, u32 operand)
{
SRC_Common src{ .HEX = instruction->bytecode[operand + 1] };
if (src.reg_type != RSX_FP_REGISTER_TYPE_TEMP)
{
return {};
}
const u32 read_lanes = get_src_vector_lane_mask_shuffled(prog, instruction, operand);
if (!read_lanes)
{
return {};
}
RegisterRef ref{ .mask = read_lanes };
Register& reg = ref.reg;
reg.f16 = !!src.fp16;
reg.id = src.tmp_reg_index;
return ref;
}
RegisterRef get_dst_register(const Instruction* instruction)
{
OPDEST dst { .HEX = instruction->bytecode[0] };
if (dst.no_dest)
{
return {};
}
RegisterRef ref{ .mask = dst.write_mask };
ref.reg.f16 = dst.fp16;
ref.reg.id = dst.dest_reg;
return ref;
}
// Convert vector mask to file range
rsx::simple_array<u32> get_register_file_range(const RegisterRef& reg)
{
if (!reg.mask)
{
return {};
}
constexpr u32 register_file_max_len = 48 * 8; // H0 - H47, R0 - R23
const u32 lane_width = reg.reg.f16 ? 2 : 4;
const u32 file_offset = reg.reg.id * lane_width * 4;
ensure(file_offset < register_file_max_len, "Invalid register index");
rsx::simple_array<u32> result{};
auto insert_lane = [&](u32 word_offset)
{
for (u32 i = 0; i < lane_width; ++i)
{
result.push_back(file_offset + (word_offset * lane_width) + i);
}
};
if (reg.x) insert_lane(0);
if (reg.y) insert_lane(1);
if (reg.z) insert_lane(2);
if (reg.w) insert_lane(3);
return result;
}
}

View File

@ -0,0 +1,111 @@
#pragma once
#include "IR.h"
#include "Emu/RSX/Common/simple_array.hpp"
struct RSXFragmentProgram;
namespace rsx::assembler
{
enum FP_opcode
{
RSX_FP_OPCODE_NOP = 0x00, // No-Operation
RSX_FP_OPCODE_MOV = 0x01, // Move
RSX_FP_OPCODE_MUL = 0x02, // Multiply
RSX_FP_OPCODE_ADD = 0x03, // Add
RSX_FP_OPCODE_MAD = 0x04, // Multiply-Add
RSX_FP_OPCODE_DP3 = 0x05, // 3-component Dot Product
RSX_FP_OPCODE_DP4 = 0x06, // 4-component Dot Product
RSX_FP_OPCODE_DST = 0x07, // Distance
RSX_FP_OPCODE_MIN = 0x08, // Minimum
RSX_FP_OPCODE_MAX = 0x09, // Maximum
RSX_FP_OPCODE_SLT = 0x0A, // Set-If-LessThan
RSX_FP_OPCODE_SGE = 0x0B, // Set-If-GreaterEqual
RSX_FP_OPCODE_SLE = 0x0C, // Set-If-LessEqual
RSX_FP_OPCODE_SGT = 0x0D, // Set-If-GreaterThan
RSX_FP_OPCODE_SNE = 0x0E, // Set-If-NotEqual
RSX_FP_OPCODE_SEQ = 0x0F, // Set-If-Equal
RSX_FP_OPCODE_FRC = 0x10, // Fraction (fract)
RSX_FP_OPCODE_FLR = 0x11, // Floor
RSX_FP_OPCODE_KIL = 0x12, // Kill fragment
RSX_FP_OPCODE_PK4 = 0x13, // Pack four signed 8-bit values
RSX_FP_OPCODE_UP4 = 0x14, // Unpack four signed 8-bit values
RSX_FP_OPCODE_DDX = 0x15, // Partial-derivative in x (Screen space derivative w.r.t. x)
RSX_FP_OPCODE_DDY = 0x16, // Partial-derivative in y (Screen space derivative w.r.t. y)
RSX_FP_OPCODE_TEX = 0x17, // Texture lookup
RSX_FP_OPCODE_TXP = 0x18, // Texture sample with projection (Projective texture lookup)
RSX_FP_OPCODE_TXD = 0x19, // Texture sample with partial differentiation (Texture lookup with derivatives)
RSX_FP_OPCODE_RCP = 0x1A, // Reciprocal
RSX_FP_OPCODE_RSQ = 0x1B, // Reciprocal Square Root
RSX_FP_OPCODE_EX2 = 0x1C, // Exponentiation base 2
RSX_FP_OPCODE_LG2 = 0x1D, // Log base 2
RSX_FP_OPCODE_LIT = 0x1E, // Lighting coefficients
RSX_FP_OPCODE_LRP = 0x1F, // Linear Interpolation
RSX_FP_OPCODE_STR = 0x20, // Set-If-True
RSX_FP_OPCODE_SFL = 0x21, // Set-If-False
RSX_FP_OPCODE_COS = 0x22, // Cosine
RSX_FP_OPCODE_SIN = 0x23, // Sine
RSX_FP_OPCODE_PK2 = 0x24, // Pack two 16-bit floats
RSX_FP_OPCODE_UP2 = 0x25, // Unpack two 16-bit floats
RSX_FP_OPCODE_POW = 0x26, // Power
RSX_FP_OPCODE_PKB = 0x27, // Pack bytes
RSX_FP_OPCODE_UPB = 0x28, // Unpack bytes
RSX_FP_OPCODE_PK16 = 0x29, // Pack 16 bits
RSX_FP_OPCODE_UP16 = 0x2A, // Unpack 16
RSX_FP_OPCODE_BEM = 0x2B, // Bump-environment map (a.k.a. 2D coordinate transform)
RSX_FP_OPCODE_PKG = 0x2C, // Pack with sRGB transformation
RSX_FP_OPCODE_UPG = 0x2D, // Unpack gamma
RSX_FP_OPCODE_DP2A = 0x2E, // 2-component dot product with scalar addition
RSX_FP_OPCODE_TXL = 0x2F, // Texture sample with explicit LOD
RSX_FP_OPCODE_TXB = 0x31, // Texture sample with bias
RSX_FP_OPCODE_TEXBEM = 0x33,
RSX_FP_OPCODE_TXPBEM = 0x34,
RSX_FP_OPCODE_BEMLUM = 0x35,
RSX_FP_OPCODE_REFL = 0x36, // Reflection vector
RSX_FP_OPCODE_TIMESWTEX = 0x37,
RSX_FP_OPCODE_DP2 = 0x38, // 2-component dot product
RSX_FP_OPCODE_NRM = 0x39, // Normalize
RSX_FP_OPCODE_DIV = 0x3A, // Division
RSX_FP_OPCODE_DIVSQ = 0x3B, // Divide by Square Root
RSX_FP_OPCODE_LIF = 0x3C, // Final part of LIT
RSX_FP_OPCODE_FENCT = 0x3D, // Fence T?
RSX_FP_OPCODE_FENCB = 0x3E, // Fence B?
RSX_FP_OPCODE_BRK = 0x40, // Break
RSX_FP_OPCODE_CAL = 0x41, // Subroutine call
RSX_FP_OPCODE_IFE = 0x42, // If
RSX_FP_OPCODE_LOOP = 0x43, // Loop
RSX_FP_OPCODE_REP = 0x44, // Repeat
RSX_FP_OPCODE_RET = 0x45, // Return
// Custom opcodes for dependency injection
RSX_FP_OPCODE_OR16_LO = 0x46, // Performs a 16-bit OR, taking one register channel as input and overwriting low 16 bits of the output
RSX_FP_OPCODE_OR16_HI = 0x47, // Same as the lo variant but now overwrites the high 16-bit block
};
namespace FP
{
// Returns number of operands consumed by an instruction
u8 get_operand_count(FP_opcode opcode);
// Returns a lane mask for the given operand.
// The lane mask is the fixed function hardware lane so swizzles need to be applied on top to resolve the real data channel.
u32 get_src_vector_lane_mask(const RSXFragmentProgram& prog, const Instruction* instruction, u32 operand);
// Resolved vector lane mask with swizzles applied.
u32 get_src_vector_lane_mask_shuffled(const RSXFragmentProgram& prog, const Instruction* instruction, u32 operand);
// Returns true on delay slot instructions.
bool is_delay_slot(const Instruction* instruction);
// Generate register references
RegisterRef get_src_register(const RSXFragmentProgram& prog, const Instruction* instruction, u32 operand);
RegisterRef get_dst_register(const Instruction* instruction);
// Convert vector mask to file ranges
rsx::simple_array<u32> get_register_file_range(const RegisterRef& reg);
// Compile a register file annotated blob to register references
std::vector<RegisterRef> compile_register_file(const std::array<char, 48 * 8>& file);
}
}

View File

@ -1,5 +1,4 @@
#include "stdafx.h" #include "stdafx.h"
#include "CFG.h" #include "CFG.h"
#include "Emu/RSX/Common/simple_array.hpp" #include "Emu/RSX/Common/simple_array.hpp"
@ -75,8 +74,19 @@ namespace rsx::assembler
{ {
if (auto found = find_block_for_pc(id)) if (auto found = find_block_for_pc(id))
{ {
parent->insert_succ(found, edge_type); auto succ = found;
found->insert_pred(parent, edge_type); if (found->is_of_type(EdgeType::ELSE) &&
(edge_type == EdgeType::ENDIF || edge_type == EdgeType::ENDLOOP))
{
// If we landed on an "ELSE" node, link to its "ENDIF" counterpart
auto if_parent = found->pred.front().from;
auto endif_edge = std::find_if(if_parent->succ.begin(), if_parent->succ.end(), FN(x.type == EdgeType::ENDIF));
ensure(endif_edge != if_parent->succ.end(), "CFG: Invalid ELSE node");
succ = endif_edge->to;
}
parent->insert_succ(succ, edge_type);
succ->insert_pred(parent, edge_type);
return found; return found;
} }
@ -101,6 +111,43 @@ namespace rsx::assembler
if (found) if (found)
{ {
auto front_edge = std::find_if(bb->pred.begin(), bb->pred.end(), FN(x.type != EdgeType::ENDIF && x.type != EdgeType::ENDLOOP));
if (front_edge != bb->pred.end())
{
auto parent = ensure(front_edge->from);
switch (front_edge->type)
{
case EdgeType::IF:
case EdgeType::ELSE:
{
// Find the merge node from the parent.
auto succ = std::find_if(parent->succ.begin(), parent->succ.end(), FN(x.type == EdgeType::ENDIF));
ensure(succ != parent->succ.end(), "CFG: Broken IF linkage. Please report to developers.");
bb->insert_succ(succ->to, EdgeType::ENDIF);
succ->to->insert_pred(bb, EdgeType::ENDIF);
break;
}
case EdgeType::LOOP:
{
// Find the merge node from the parent
auto succ = std::find_if(parent->succ.begin(), parent->succ.end(), FN(x.type == EdgeType::ENDLOOP));
ensure(succ != parent->succ.end(), "CFG: Broken LOOP linkage. Please report to developers.");
bb->insert_succ(succ->to, EdgeType::ENDLOOP);
succ->to->insert_pred(bb, EdgeType::ENDLOOP);
break;
}
default:
// Missing an edge type?
rsx_log.error("CFG: Unexpected block exit. Report to developers.");
break;
}
}
else if (bb->pred.empty())
{
// Impossible situation.
rsx_log.error("CFG: Child block has no parent but has successor! Report to developers.");
}
bb = *found; bb = *found;
} }
@ -113,7 +160,7 @@ namespace rsx::assembler
src2.HEX = decoded._u32[3]; src2.HEX = decoded._u32[3];
end = !!dst.end; end = !!dst.end;
const u32 opcode = dst.opcode | (src1.opcode_is_branch << 6); const u32 opcode = dst.opcode | (src1.opcode_hi << 6);
if (opcode == RSX_FP_OPCODE_NOP) if (opcode == RSX_FP_OPCODE_NOP)
{ {
@ -126,6 +173,7 @@ namespace rsx::assembler
std::memcpy(ir_inst.bytecode, &decoded._u32[0], 16); std::memcpy(ir_inst.bytecode, &decoded._u32[0], 16);
ir_inst.length = 4; ir_inst.length = 4;
ir_inst.addr = pc * 16; ir_inst.addr = pc * 16;
ir_inst.opcode = opcode;
switch (opcode) switch (opcode)
{ {
@ -146,22 +194,45 @@ namespace rsx::assembler
case RSX_FP_OPCODE_IFE: case RSX_FP_OPCODE_IFE:
{ {
// Inserts if and else and end blocks // Inserts if and else and end blocks
auto parent = bb; const u32 end_addr = src2.end_offset >> 2u;
bb = safe_insert_block(parent, pc + 1, EdgeType::IF); const u32 else_addr = src1.else_offset >> 2u;
if (src2.end_offset != src1.else_offset) if (end_addr == pc + 1u)
{ {
else_blocks.push_back(safe_insert_block(parent, src1.else_offset >> 2, EdgeType::ELSE)); // NOP. Empty IF block
bb->instructions.pop_back();
break;
} }
end_blocks.push_back(safe_insert_block(parent, src2.end_offset >> 2, EdgeType::ENDIF));
if (else_addr > end_addr)
{
// Our systems support this, but it is not verified on real hardware.
rsx_log.error("CFG: Non-contiguous branch detected. Report to developers.");
}
auto parent = bb;
bb = safe_insert_block(parent, pc + 1u, EdgeType::IF);
if (end_addr != else_addr)
{
else_blocks.push_back(safe_insert_block(parent, else_addr, EdgeType::ELSE));
}
end_blocks.push_back(safe_insert_block(parent, end_addr, EdgeType::ENDIF));
break; break;
} }
case RSX_FP_OPCODE_LOOP: case RSX_FP_OPCODE_LOOP:
case RSX_FP_OPCODE_REP: case RSX_FP_OPCODE_REP:
{ {
// Inserts for and end blocks // Inserts for and end blocks
const u32 end_addr = src2.end_offset >> 2u;
if (end_addr == pc + 1u)
{
// NOP. Empty LOOP block
bb->instructions.pop_back();
break;
}
auto parent = bb; auto parent = bb;
bb = safe_insert_block(parent, pc + 1, EdgeType::LOOP); bb = safe_insert_block(parent, pc + 1u, EdgeType::LOOP);
end_blocks.push_back(safe_insert_block(parent, src2.end_offset >> 2, EdgeType::ENDLOOP)); end_blocks.push_back(safe_insert_block(parent, end_addr, EdgeType::ENDLOOP));
break; break;
} }
default: default:
@ -174,6 +245,7 @@ namespace rsx::assembler
ir_inst.length += 4; ir_inst.length += 4;
pc++; pc++;
} }
break;
} }
pc++; pc++;

View File

@ -10,6 +10,16 @@ namespace rsx::assembler
{ {
int id = 0; int id = 0;
bool f16 = false; bool f16 = false;
bool operator == (const Register& other) const
{
return id == other.id && f16 == other.f16;
}
std::string to_string() const
{
return std::string(f16 ? "H" : "R") + std::to_string(id);
}
}; };
struct RegisterRef struct RegisterRef
@ -19,7 +29,7 @@ namespace rsx::assembler
// Vector information // Vector information
union union
{ {
u32 mask; u32 mask = 0;
struct struct
{ {
@ -29,6 +39,16 @@ namespace rsx::assembler
bool w : 1; bool w : 1;
}; };
}; };
operator bool() const
{
return !!mask;
}
bool operator == (const RegisterRef& other) const
{
return reg == other.reg && mask == other.mask;
}
}; };
struct Instruction struct Instruction
@ -71,6 +91,7 @@ namespace rsx::assembler
struct BasicBlock struct BasicBlock
{ {
u32 id = 0; u32 id = 0;
std::vector<Instruction> instructions; // Program instructions for the RSX processor std::vector<Instruction> instructions; // Program instructions for the RSX processor
std::vector<FlowEdge> succ; // Forward edges. Sorted closest first. std::vector<FlowEdge> succ; // Forward edges. Sorted closest first.
std::vector<FlowEdge> pred; // Back edges. Sorted closest first. std::vector<FlowEdge> pred; // Back edges. Sorted closest first.
@ -78,6 +99,9 @@ namespace rsx::assembler
std::vector<Instruction> prologue; // Prologue, created by passes std::vector<Instruction> prologue; // Prologue, created by passes
std::vector<Instruction> epilogue; // Epilogue, created by passes std::vector<Instruction> epilogue; // Epilogue, created by passes
std::vector<RegisterRef> input_list; // Register inputs.
std::vector<RegisterRef> clobber_list; // Clobbered outputs
FlowEdge* insert_succ(BasicBlock* b, EdgeType type = EdgeType::NONE) FlowEdge* insert_succ(BasicBlock* b, EdgeType type = EdgeType::NONE)
{ {
FlowEdge e{ .type = type, .from = this, .to = b }; FlowEdge e{ .type = type, .from = this, .to = b };
@ -91,5 +115,25 @@ namespace rsx::assembler
pred.push_back(e); pred.push_back(e);
return &pred.back(); return &pred.back();
} }
bool is_of_type(EdgeType type) const
{
return pred.size() == 1 &&
pred.front().type == type;
}
bool has_sibling_of_type(EdgeType type) const
{
if (pred.size() != 1)
{
return false;
}
auto source_node = pred.front().from;
return std::find_if(
source_node->succ.begin(),
source_node->succ.end(),
FN(x.type == type)) != source_node->succ.end();
}
}; };
} }

View File

@ -0,0 +1,230 @@
#include "stdafx.h"
#include "RegisterAnnotationPass.h"
#include "Emu/RSX/Program/Assembler/FPOpcodes.h"
#include "Emu/RSX/Program/RSXFragmentProgram.h"
#include <span>
#include <unordered_map>
namespace rsx::assembler::FP
{
static constexpr u32 register_file_length = 48 * 8; // 24 F32 or 48 F16 registers
static constexpr char content_unknown = 0;
static constexpr char content_float32 = 'R';
static constexpr char content_float16 = 'H';
static constexpr char content_dual = 'D';
bool is_delay_slot(const Instruction& instruction)
{
const OPDEST dst{ .HEX = instruction.bytecode[0] };
const SRC0 src0{ .HEX = instruction.bytecode[1] };
const SRC1 src1{ .HEX = instruction.bytecode[2] };
if (dst.opcode != RSX_FP_OPCODE_MOV || // These slots are always populated with MOV
dst.no_dest || // Must have a sink
src0.reg_type != RSX_FP_REGISTER_TYPE_TEMP || // Must read from reg
dst.dest_reg != src0.tmp_reg_index || // Must be a write-to-self
dst.fp16 != src0.fp16 || // Must really be the same register
src0.abs || src0.neg ||
dst.saturate) // Precision modifier
{
return false;
}
switch (dst.prec)
{
case RSX_FP_PRECISION_REAL:
case RSX_FP_PRECISION_UNKNOWN:
break;
case RSX_FP_PRECISION_HALF:
if (!src0.fp16) return false;
break;
case RSX_FP_PRECISION_FIXED12:
case RSX_FP_PRECISION_FIXED9:
case RSX_FP_PRECISION_SATURATE:
return false;
}
// Check if we have precision modifiers on the source
if (src0.abs || src0.neg || src1.scale)
{
return false;
}
if (dst.mask_x && src0.swizzle_x != 0) return false;
if (dst.mask_y && src0.swizzle_y != 1) return false;
if (dst.mask_z && src0.swizzle_z != 2) return false;
if (dst.mask_w && src0.swizzle_w != 3) return false;
return true;
}
std::vector<RegisterRef> compile_register_file(const std::array<char, 48 * 8>& file)
{
std::vector<RegisterRef> results;
// F16 register processing
for (int reg16 = 0; reg16 < 48; ++reg16)
{
const u32 offset = reg16 * 8;
auto word = *reinterpret_cast<const u64*>(&file[offset]);
if (!word) [[ likely ]]
{
// Trivial rejection, very commonly hit.
continue;
}
RegisterRef ref{ .reg {.id = reg16, .f16 = true } };
ref.x = (file[offset] == content_dual || file[offset] == content_float16);
ref.y = (file[offset + 2] == content_dual || file[offset + 2] == content_float16);
ref.z = (file[offset + 4] == content_dual || file[offset + 4] == content_float16);
ref.w = (file[offset + 6] == content_dual || file[offset + 6] == content_float16);
if (ref)
{
results.push_back(std::move(ref));
}
}
// Helper to check a span for 32-bit access
auto match_any_32 = [](const std::span<const char> lanes)
{
return std::any_of(lanes.begin(), lanes.end(), FN(x == content_dual || x == content_float32));
};
// F32 register processing
for (int reg32 = 0; reg32 < 24; ++reg32)
{
const u32 offset = reg32 * 16;
auto word0 = *reinterpret_cast<const u64*>(&file[offset]);
auto word1 = *reinterpret_cast<const u64*>(&file[offset + 8]);
if (!word0 && !word1) [[ likely ]]
{
// Trivial rejection, very commonly hit.
continue;
}
RegisterRef ref{ .reg {.id = reg32, .f16 = false } };
if (word0)
{
ref.x = match_any_32({ &file[offset], 4 });
ref.y = match_any_32({ &file[offset + 4], 4 });
}
if (word1)
{
ref.z = match_any_32({ &file[offset + 8], 4 });
ref.w = match_any_32({ &file[offset + 12], 4 });
}
if (ref)
{
results.push_back(std::move(ref));
}
}
return results;
}
// Decay instructions into register references
void annotate_instructions(BasicBlock* block, const RSXFragmentProgram& prog, bool skip_delay_slots)
{
for (auto& instruction : block->instructions)
{
if (skip_delay_slots && is_delay_slot(instruction))
{
continue;
}
const u32 operand_count = get_operand_count(static_cast<FP_opcode>(instruction.opcode));
for (u32 i = 0; i < operand_count; i++)
{
RegisterRef reg = get_src_register(prog, &instruction, i);
if (!reg.mask)
{
// Likely a literal constant
continue;
}
instruction.srcs.push_back(std::move(reg));
}
RegisterRef dst = get_dst_register(&instruction);
if (dst)
{
instruction.dsts.push_back(std::move(dst));
}
}
}
// Annotate each block with input and output lanes (read and clobber list)
void annotate_block_io(BasicBlock* block)
{
alignas(16) std::array<char, register_file_length> output_register_file;
alignas(16) std::array<char, register_file_length> input_register_file; // We'll eventually replace with a bitfield mask, but for ease of debugging, we use char for now
std::memset(output_register_file.data(), content_unknown, register_file_length);
std::memset(input_register_file.data(), content_unknown, register_file_length);
for (const auto& instruction : block->instructions)
{
for (const auto& src : instruction.srcs)
{
const auto read_bytes = get_register_file_range(src);
const char expected_type = src.reg.f16 ? content_float16 : content_float32;
for (const auto& index : read_bytes)
{
if (output_register_file[index] != content_unknown)
{
// Something already wrote to this lane
continue;
}
if (input_register_file[index] == expected_type)
{
// We already know about this input
continue;
}
if (input_register_file[index] == 0)
{
// Not known, tag as input
input_register_file[index] = expected_type;
continue;
}
// Collision on the lane
input_register_file[index] = content_dual;
}
}
if (!instruction.dsts.empty())
{
const auto& dst = instruction.dsts.front();
const auto write_bytes = get_register_file_range(dst);
const char expected_type = dst.reg.f16 ? content_float16 : content_float32;
for (const auto& index : write_bytes)
{
output_register_file[index] = expected_type;
}
}
}
// Compile the input and output refs into register references
block->clobber_list = compile_register_file(output_register_file);
block->input_list = compile_register_file(input_register_file);
}
void RegisterAnnotationPass::run(FlowGraph& graph)
{
for (auto& block : graph.blocks)
{
annotate_instructions(&block, m_prog, m_config.skip_delay_slots);
annotate_block_io(&block);
}
}
}

View File

@ -0,0 +1,34 @@
#pragma once
#include "../../CFG.h"
struct RSXFragmentProgram;
namespace rsx::assembler::FP
{
struct RegisterAnnotationPassOptions
{
bool skip_delay_slots = false; // When enabled, detect delay slots and ignore annotating them.
};
// The annotation pass annotates each basic block with 2 pieces of information:
// 1. The "input" register list for a block.
// 2. The "output" register list for a block (clobber list).
// The information can be used by other passes to set up prologue/epilogue on each block.
// The pass also populates register reference members of each instruction, such as the input and output lanes.
class RegisterAnnotationPass : public CFGPass
{
public:
RegisterAnnotationPass(
const RSXFragmentProgram& prog,
const RegisterAnnotationPassOptions& options = {})
: m_prog(prog), m_config(options)
{}
void run(FlowGraph& graph) override;
private:
const RSXFragmentProgram& m_prog;
RegisterAnnotationPassOptions m_config;
};
}

View File

@ -0,0 +1,490 @@
#include "stdafx.h"
#include "RegisterDependencyPass.h"
#include "Emu/RSX/Program/Assembler/FPOpcodes.h"
#include "Emu/RSX/Program/RSXFragmentProgram.h"
#include <unordered_map>
#include <unordered_set>
namespace rsx::assembler::FP
{
static constexpr u32 register_file_length = 48 * 8; // 24 F32 or 48 F16 registers
static constexpr char content_unknown = 0;
static constexpr char content_float32 = 'R';
static constexpr char content_float16 = 'H';
static constexpr char content_dual = 'D';
using register_file_t = std::array<char, register_file_length>;
struct DependencyPassContext
{
std::unordered_map<BasicBlock*, register_file_t> exec_register_map;
std::unordered_map<BasicBlock*, register_file_t> sync_register_map;
};
enum Register32BarrierFlags
{
NONE = 0,
OR_WORD0 = 1,
OR_WORD1 = 2,
DEFAULT = OR_WORD0 | OR_WORD1
};
struct RegisterBarrier32
{
RegisterRef ref;
u32 flags[4];
};
std::vector<RegisterRef> decode_lanes16(const std::unordered_set<u32>& lanes)
{
std::vector<RegisterRef> result;
for (u32 index = 0, file_offset = 0; index < 48; ++index, file_offset += 8)
{
// Each register has 4 16-bit lanes
u32 mask = 0;
if (lanes.contains(file_offset + 0)) mask |= (1u << 0);
if (lanes.contains(file_offset + 2)) mask |= (1u << 1);
if (lanes.contains(file_offset + 4)) mask |= (1u << 2);
if (lanes.contains(file_offset + 6)) mask |= (1u << 3);
if (mask == 0)
{
continue;
}
RegisterRef ref{ .reg{.id = static_cast<int>(index), .f16 = true } };
ref.mask = mask;
result.push_back(std::move(ref));
}
return result;
}
std::vector<RegisterBarrier32> decode_lanes32(const std::unordered_set<u32>& lanes)
{
std::vector<RegisterBarrier32> result;
for (u32 index = 0, file_offset = 0; index < 48; ++index, file_offset += 16)
{
// Each register has 8 16-bit lanes
RegisterBarrier32 barrier{};
auto& ref = barrier.ref;
for (u32 lane = 0; lane < 16; lane += 2)
{
if (!lanes.contains(file_offset + lane))
{
continue;
}
const u32 ch = (lane / 4);
const u32 flags = (lane & 3)
? Register32BarrierFlags::OR_WORD1
: Register32BarrierFlags::OR_WORD0;
ref.mask |= (1u << ch);
barrier.flags[ch] |= flags;
}
if (ref.mask == 0)
{
continue;
}
ref.reg = {.id = static_cast<int>(index), .f16 = false };
result.push_back(std::move(barrier));
}
return result;
}
std::vector<Instruction> build_barrier32(const RegisterBarrier32& barrier)
{
// Upto 4 instructions are needed per 32-bit register
// R0.x = packHalf2x16(H0.xy)
// R0.y = packHalf2x16(H0.zw);
// R0.z = packHalf2x16(H1.xy);
// R0.w = packHalf2x16(H1.zw);
std::vector<Instruction> result;
for (u32 mask = barrier.ref.mask, ch = 0; mask > 0; mask >>= 1, ++ch)
{
if (!(mask & 1))
{
continue;
}
const auto& reg = barrier.ref.reg;
const auto reg_id = reg.id;
Instruction instruction{};
OPDEST dst{};
dst.prec = RSX_FP_PRECISION_REAL;
dst.fp16 = 0;
dst.dest_reg = reg_id;
dst.write_mask = (1u << ch);
const u32 src_reg_id = (ch / 2) + (reg_id * 2);
const bool is_word0 = !(ch & 1); // Only even
SRC0 src0{};
if (is_word0)
{
src0.swizzle_x = 0;
src0.swizzle_y = 1;
}
else
{
src0.swizzle_x = 2;
src0.swizzle_y = 3;
}
src0.swizzle_z = 2;
src0.swizzle_w = 3;
src0.reg_type = RSX_FP_REGISTER_TYPE_TEMP;
src0.tmp_reg_index = src_reg_id;
src0.fp16 = 1;
// Prepare source 1 to match the output in case we need to encode an OR
SRC1 src1{};
src1.reg_type = RSX_FP_REGISTER_TYPE_TEMP;
src1.tmp_reg_index = reg_id;
src1.swizzle_x = ch;
src1.swizzle_y = ch;
src1.swizzle_z = ch;
src1.swizzle_w = ch;
u32 opcode = 0;
switch (barrier.flags[ch])
{
case Register32BarrierFlags::DEFAULT:
opcode = RSX_FP_OPCODE_PK2;
break;
case Register32BarrierFlags::OR_WORD0:
opcode = RSX_FP_OPCODE_OR16_LO;
// Swap inputs
std::swap(src0.HEX, src1.HEX);
break;
case Register32BarrierFlags::OR_WORD1:
opcode = RSX_FP_OPCODE_OR16_HI;
src0.swizzle_x = src0.swizzle_y;
std::swap(src0.HEX, src1.HEX);
break;
case Register32BarrierFlags::NONE:
default:
fmt::throw_exception("Unexpected lane barrier with no mask.");
}
dst.opcode = opcode & 0x3F;
src1.opcode_hi = (opcode > 0x3F) ? 1 : 0;
src0.exec_if_eq = src0.exec_if_gr = src0.exec_if_lt = 1;
instruction.opcode = opcode;
instruction.bytecode[0] = dst.HEX;
instruction.bytecode[1] = src0.HEX;
instruction.bytecode[2] = src1.HEX;
Register src_reg{ .id = static_cast<int>(src_reg_id), .f16 = true };
instruction.srcs.push_back({ .reg = src_reg, .mask = 0xF });
instruction.dsts.push_back({ .reg{ .id = reg_id, .f16 = false }, .mask = (1u << ch) });
result.push_back(std::move(instruction));
}
return result;
}
std::vector<Instruction> build_barrier16(const RegisterRef& reg)
{
// H0.xy = unpackHalf2x16(R0.x)
// H0.zw = unpackHalf2x16(R0.y)
// H1.xy = unpackHalf2x16(R0.z)
// H1.zw = unpackHalf2x16(R0.w)
std::vector<Instruction> result;
for (u32 mask = reg.mask, ch = 0; mask > 0; mask >>= 1, ++ch)
{
if (!(mask & 1))
{
continue;
}
Instruction instruction{};
OPDEST dst{};
dst.opcode = RSX_FP_OPCODE_UP2;
dst.prec = RSX_FP_PRECISION_HALF;
dst.fp16 = 1;
dst.dest_reg = reg.reg.id;
dst.write_mask = 1u << ch;
const u32 src_reg_id = reg.reg.id / 2;
const bool is_odd_reg = !!(reg.reg.id & 1);
const bool is_odd_ch = !!(ch & 1);
const bool is_word0 = ch < 2;
// If we're an even channel, we should also write the next channel (y/w)
if (!is_odd_ch && (mask & 2))
{
mask >>= 1;
++ch;
dst.write_mask |= (1u << ch);
}
SRC0 src0{};
src0.exec_if_eq = src0.exec_if_gr = src0.exec_if_lt = 1;
if (is_word0)
{
src0.swizzle_x = is_odd_reg ? 2 : 0;
}
else
{
src0.swizzle_x = is_odd_reg ? 3 : 1;
}
src0.swizzle_y = 1;
src0.swizzle_z = 2;
src0.swizzle_w = 3;
src0.reg_type = RSX_FP_REGISTER_TYPE_TEMP;
src0.tmp_reg_index = src_reg_id;
instruction.opcode = dst.opcode;
instruction.bytecode[0] = dst.HEX;
instruction.bytecode[1] = src0.HEX;
Register src_reg{ .id = static_cast<int>(src_reg_id), .f16 = true };
instruction.srcs.push_back({ .reg = src_reg, .mask = 0xF });
instruction.dsts.push_back({ .reg{.id = reg.reg.id, .f16 = false }, .mask = dst.write_mask });
result.push_back(std::move(instruction));
}
return result;
}
std::vector<Instruction> resolve_dependencies(const std::unordered_set<u32>& lanes, bool f16)
{
std::vector<Instruction> result;
if (f16)
{
const auto regs = decode_lanes16(lanes);
for (const auto& ref : regs)
{
auto instructions = build_barrier16(ref);
result.insert(result.end(), instructions.begin(), instructions.end());
}
return result;
}
const auto barriers = decode_lanes32(lanes);
for (const auto& barrier : barriers)
{
auto instructions = build_barrier32(barrier);
result.insert(result.end(), std::make_move_iterator(instructions.begin()), std::make_move_iterator(instructions.end()));
}
return result;
}
void insert_dependency_barriers(DependencyPassContext& ctx, BasicBlock* block)
{
register_file_t& register_file = ctx.exec_register_map[block];
std::memset(register_file.data(), content_unknown, register_file_length);
std::unordered_set<u32> barrier16;
std::unordered_set<u32> barrier32;
// This subpass does not care about the prologue and epilogue and assumes each block is unique.
for (auto it = block->instructions.begin(); it != block->instructions.end(); ++it)
{
auto& inst = *it;
barrier16.clear();
barrier32.clear();
for (const auto& src : inst.srcs)
{
const auto read_bytes = get_register_file_range(src);
const char expected_type = src.reg.f16 ? content_float16 : content_float32;
for (const auto& index : read_bytes)
{
if (register_file[index] == content_unknown)
{
// Skip input
continue;
}
if (register_file[index] == expected_type || register_file[index] == content_dual)
{
// Match - nothing to do
continue;
}
// Collision on the lane
register_file[index] = content_dual;
(src.reg.f16 ? barrier16 : barrier32).insert(index);
}
}
for (const auto& dst : inst.dsts)
{
const auto write_bytes = get_register_file_range(dst);
const char expected_type = dst.reg.f16 ? content_float16 : content_float32;
for (const auto& index : write_bytes)
{
register_file[index] = expected_type;
}
}
// We need to inject some barrier instructions
if (!barrier16.empty())
{
auto barrier16_in = decode_lanes16(barrier16);
std::vector<Instruction> instructions;
instructions.reserve(barrier16_in.size());
for (const auto& reg : barrier16_in)
{
auto barrier = build_barrier16(reg);
instructions.insert(instructions.end(), std::make_move_iterator(barrier.begin()), std::make_move_iterator(barrier.end()));
}
it = block->instructions.insert(it, std::make_move_iterator(instructions.begin()), std::make_move_iterator(instructions.end()));
std::advance(it, instructions.size());
}
if (!barrier32.empty())
{
auto barrier32_in = decode_lanes32(barrier32);
std::vector<Instruction> instructions;
instructions.reserve(barrier32_in.size());
for (const auto& reg : barrier32_in)
{
auto barrier = build_barrier32(reg);
instructions.insert(instructions.end(), std::make_move_iterator(barrier.begin()), std::make_move_iterator(barrier.end()));
}
it = block->instructions.insert(it, std::make_move_iterator(instructions.begin()), std::make_move_iterator(instructions.end()));
std::advance(it, instructions.size());
}
}
}
void insert_block_register_dependency(DependencyPassContext& ctx, BasicBlock* block, const std::unordered_set<u32>& lanes, bool f16)
{
std::unordered_set<u32> clobbered_lanes;
std::unordered_set<u32> lanes_to_search;
for (auto& back_edge : block->pred)
{
auto target = back_edge.from;
// Quick check - if we've reached an IF-ELSE anchor, don't traverse upwards.
// The IF and ELSE edges are already a complete set and will bre processed before this node.
if (back_edge.type == EdgeType::ENDIF &&
&back_edge == &block->pred.back() &&
target->succ.size() == 3 &&
target->succ[1].type == EdgeType::ELSE &&
target->succ[2].type == EdgeType::ENDIF &&
target->succ[2].to == block)
{
return;
}
// Did this target even clobber our register?
ensure(ctx.exec_register_map.find(target) != ctx.exec_register_map.end(), "Block has not been pre-processed");
if (ctx.sync_register_map.find(target) == ctx.sync_register_map.end())
{
auto& blob = ctx.sync_register_map[target];
std::memset(blob.data(), content_unknown, register_file_length);
}
auto& sync_register_file = ctx.sync_register_map[target];
const auto& exec_register_file = ctx.exec_register_map[target];
const auto clobber_type = f16 ? content_float32 : content_float16;
lanes_to_search.clear();
clobbered_lanes.clear();
for (auto& lane : lanes)
{
if (exec_register_file[lane] == clobber_type &&
sync_register_file[lane] == content_unknown)
{
clobbered_lanes.insert(lane);
sync_register_file[lane] = content_dual;
continue;
}
if (exec_register_file[lane] == content_unknown)
{
lanes_to_search.insert(lane);
}
}
if (!clobbered_lanes.empty())
{
auto instructions = resolve_dependencies(clobbered_lanes, f16);
target->epilogue.insert(target->epilogue.end(), std::make_move_iterator(instructions.begin()), std::make_move_iterator(instructions.end()));
}
if (lanes_to_search.empty())
{
continue;
}
// We have some missing lanes. Search upwards
if (!target->pred.empty())
{
// We only need to search the last predecessor which is the true "root" of the branch
insert_block_register_dependency(ctx, target, lanes_to_search, f16);
}
}
}
void insert_block_dependencies(DependencyPassContext& ctx, BasicBlock* block)
{
auto range_from_ref = [](const RegisterRef& ref)
{
const auto range = get_register_file_range(ref);
std::unordered_set<u32> result;
for (const auto& value : range)
{
result.insert(value);
}
return result;
};
for (auto& ref : block->input_list)
{
const auto range = range_from_ref(ref);
insert_block_register_dependency(ctx, block, range, ref.reg.f16);
}
}
void RegisterDependencyPass::run(FlowGraph& graph)
{
DependencyPassContext ctx{};
// First, run intra-block dependency
for (auto& block : graph.blocks)
{
insert_dependency_barriers(ctx, &block);
}
// Then, create prologue/epilogue instructions
// Traverse the list in reverse order to bubble up dependencies correctly.
for (auto it = graph.blocks.rbegin(); it != graph.blocks.rend(); ++it)
{
insert_block_dependencies(ctx, &(*it));
}
}
}

View File

@ -0,0 +1,15 @@
#pragma once
#include "../../CFG.h"
namespace rsx::assembler::FP
{
// The register dependency pass identifies data hazards for each basic block and injects barrier instructions.
// Real PS3 does not have explicit barriers, but does instead often use delay slots or fence instructions to stall until a specific hardware unit clears the fence to advance.
// For decompiled shaders, we have the problem that aliasing is not real and is instead simulated. We do not have access to unions on the GPU without really nasty tricks.
class RegisterDependencyPass : public CFGPass
{
public:
void run(FlowGraph& graph) override;
};
}

View File

@ -273,7 +273,7 @@ void CgBinaryDisasm::TaskFP()
src2.HEX = GetData(data[3]); src2.HEX = GetData(data[3]);
m_step = 4 * sizeof(u32); m_step = 4 * sizeof(u32);
m_opcode = dst.opcode | (src1.opcode_is_branch << 6); m_opcode = dst.opcode | (src1.opcode_hi << 6);
auto SCT = [&]() auto SCT = [&]()
{ {

View File

@ -3,12 +3,19 @@
#include "FragmentProgramDecompiler.h" #include "FragmentProgramDecompiler.h"
#include "ProgramStateCache.h" #include "ProgramStateCache.h"
#include "Assembler/Passes/FP/RegisterAnnotationPass.h"
#include "Assembler/Passes/FP/RegisterDependencyPass.h"
#include "Emu/system_config.h"
#include <algorithm> #include <algorithm>
namespace rsx namespace rsx
{ {
namespace fragment_program namespace fragment_program
{ {
using namespace rsx::assembler;
static const std::string reg_table[] = static const std::string reg_table[] =
{ {
"wpos", "wpos",
@ -17,10 +24,33 @@ namespace rsx
"tc0", "tc1", "tc2", "tc3", "tc4", "tc5", "tc6", "tc7", "tc8", "tc9", "tc0", "tc1", "tc2", "tc3", "tc4", "tc5", "tc6", "tc7", "tc8", "tc9",
"ssa" "ssa"
}; };
static const std::vector<RegisterRef> s_fp32_output_set =
{
{.reg {.id = 0, .f16 = false }, .mask = 0xf },
{.reg {.id = 2, .f16 = false }, .mask = 0xf },
{.reg {.id = 3, .f16 = false }, .mask = 0xf },
{.reg {.id = 4, .f16 = false }, .mask = 0xf },
};
static const std::vector<RegisterRef> s_fp16_output_set =
{
{.reg {.id = 0, .f16 = true }, .mask = 0xf },
{.reg {.id = 4, .f16 = true }, .mask = 0xf },
{.reg {.id = 6, .f16 = true }, .mask = 0xf },
{.reg {.id = 8, .f16 = true }, .mask = 0xf },
};
static const RegisterRef s_z_export_reg =
{
.reg {.id = 1, .f16 = false },
.mask = (1u << 2)
};
} }
} }
using namespace rsx::fragment_program; using namespace rsx::fragment_program;
using namespace rsx::assembler;
// SIMD vector lanes // SIMD vector lanes
enum VectorLane : u8 enum VectorLane : u8
@ -31,6 +61,26 @@ enum VectorLane : u8
W = 3, W = 3,
}; };
std::vector<RegisterRef> get_fragment_program_output_set(u32 ctrl, u32 mrt_count)
{
std::vector<RegisterRef> result;
if (mrt_count > 0)
{
result = (ctrl & CELL_GCM_SHADER_CONTROL_32_BITS_EXPORTS)
? s_fp32_output_set
: s_fp16_output_set;
result.resize(mrt_count);
}
if (ctrl & CELL_GCM_SHADER_CONTROL_DEPTH_EXPORT)
{
result.push_back(s_z_export_reg);
}
return result;
}
FragmentProgramDecompiler::FragmentProgramDecompiler(const RSXFragmentProgram &prog, u32& size) FragmentProgramDecompiler::FragmentProgramDecompiler(const RSXFragmentProgram &prog, u32& size)
: m_size(size) : m_size(size)
, m_prog(prog) , m_prog(prog)
@ -151,8 +201,6 @@ void FragmentProgramDecompiler::SetDst(std::string code, u32 flags)
} }
const u32 reg_index = dst.fp16 ? (dst.dest_reg >> 1) : dst.dest_reg; const u32 reg_index = dst.fp16 ? (dst.dest_reg >> 1) : dst.dest_reg;
ensure(reg_index < temp_registers.size());
if (dst.opcode == RSX_FP_OPCODE_MOV && if (dst.opcode == RSX_FP_OPCODE_MOV &&
src0.reg_type == RSX_FP_REGISTER_TYPE_TEMP && src0.reg_type == RSX_FP_REGISTER_TYPE_TEMP &&
src0.tmp_reg_index == reg_index) src0.tmp_reg_index == reg_index)
@ -165,8 +213,6 @@ void FragmentProgramDecompiler::SetDst(std::string code, u32 flags)
return; return;
} }
} }
temp_registers[reg_index].tag(dst.dest_reg, !!dst.fp16, dst.mask_x, dst.mask_y, dst.mask_z, dst.mask_w);
} }
void FragmentProgramDecompiler::AddFlowOp(const std::string& code) void FragmentProgramDecompiler::AddFlowOp(const std::string& code)
@ -522,26 +568,7 @@ template<typename T> std::string FragmentProgramDecompiler::GetSRC(T src)
switch (src.reg_type) switch (src.reg_type)
{ {
case RSX_FP_REGISTER_TYPE_TEMP: case RSX_FP_REGISTER_TYPE_TEMP:
if (src.fp16 && precision_modifier == RSX_FP_PRECISION_HALF)
if (!src.fp16)
{
if (dst.opcode == RSX_FP_OPCODE_UP16 ||
dst.opcode == RSX_FP_OPCODE_UP2 ||
dst.opcode == RSX_FP_OPCODE_UP4 ||
dst.opcode == RSX_FP_OPCODE_UPB ||
dst.opcode == RSX_FP_OPCODE_UPG)
{
auto &reg = temp_registers[src.tmp_reg_index];
if (reg.requires_gather(src.swizzle_x))
{
properties.has_gather_op = true;
AddReg(src.tmp_reg_index, src.fp16);
ret = getFloatTypeName(4) + reg.gather_r();
break;
}
}
}
else if (precision_modifier == RSX_FP_PRECISION_HALF)
{ {
// clamp16() is not a cheap operation when emulated; avoid at all costs // clamp16() is not a cheap operation when emulated; avoid at all costs
precision_modifier = RSX_FP_PRECISION_REAL; precision_modifier = RSX_FP_PRECISION_REAL;
@ -762,7 +789,6 @@ std::string FragmentProgramDecompiler::BuildCode()
const std::string float4_type = (fp16_out && device_props.has_native_half_support)? getHalfTypeName(4) : getFloatTypeName(4); const std::string float4_type = (fp16_out && device_props.has_native_half_support)? getHalfTypeName(4) : getFloatTypeName(4);
const std::string init_value = float4_type + "(0.)"; const std::string init_value = float4_type + "(0.)";
std::array<std::string, 4> output_register_names; std::array<std::string, 4> output_register_names;
std::array<u32, 4> ouput_register_indices = { 0, 2, 3, 4 };
// Holder for any "cleanup" before exiting main // Holder for any "cleanup" before exiting main
std::stringstream main_epilogue; std::stringstream main_epilogue;
@ -772,17 +798,6 @@ std::string FragmentProgramDecompiler::BuildCode()
{ {
// Hw tests show that the depth export register is default-initialized to 0 and not wpos.z!! // Hw tests show that the depth export register is default-initialized to 0 and not wpos.z!!
m_parr.AddParam(PF_PARAM_NONE, getFloatTypeName(4), "r1", init_value); m_parr.AddParam(PF_PARAM_NONE, getFloatTypeName(4), "r1", init_value);
auto& r1 = temp_registers[1];
if (r1.requires_gather(VectorLane::Z))
{
// r1.zw was not written to
properties.has_gather_op = true;
main_epilogue << " r1.z = " << float4_type << r1.gather_r() << ".z;\n";
// Emit debug warning. Useful to diagnose regressions, but should be removed in future.
rsx_log.warning("ROP reads from shader depth without writing to it. Final value will be gathered.");
}
} }
// Add the color output registers. They are statically written to and have guaranteed initialization (except r1.z which == wpos.z) // Add the color output registers. They are statically written to and have guaranteed initialization (except r1.z which == wpos.z)
@ -810,33 +825,6 @@ std::string FragmentProgramDecompiler::BuildCode()
continue; continue;
} }
const auto block_index = ouput_register_indices[n];
auto& r = temp_registers[block_index];
if (fp16_out)
{
// Check if we need a split/extract op
if (r.requires_split(0))
{
main_epilogue << " " << reg_name << " = " << float4_type << r.split_h0() << ";\n";
// Emit debug warning. Useful to diagnose regressions, but should be removed in future.
rsx_log.warning("ROP reads from %s without writing to it. Final value will be extracted from the 32-bit register.", reg_name);
}
continue;
}
if (!r.requires_gather128())
{
// Nothing to do
continue;
}
// We need to gather the data from existing registers
main_epilogue << " " << reg_name << " = " << float4_type << r.gather_r() << ";\n";
properties.has_gather_op = true;
// Emit debug warning. Useful to diagnose regressions, but should be removed in future. // Emit debug warning. Useful to diagnose regressions, but should be removed in future.
rsx_log.warning("ROP reads from %s without writing to it. Final value will be gathered.", reg_name); rsx_log.warning("ROP reads from %s without writing to it. Final value will be gathered.", reg_name);
} }
@ -1024,28 +1012,6 @@ std::string FragmentProgramDecompiler::BuildCode()
OS << Format(divsq_func); OS << Format(divsq_func);
} }
// Declare register gather/merge if needed
if (properties.has_gather_op)
{
std::string float2 = getFloatTypeName(2);
OS << float4 << " gather(" << float4 << " _h0, " << float4 << " _h1)\n";
OS << "{\n";
OS << " float x = uintBitsToFloat(packHalf2x16(_h0.xy));\n";
OS << " float y = uintBitsToFloat(packHalf2x16(_h0.zw));\n";
OS << " float z = uintBitsToFloat(packHalf2x16(_h1.xy));\n";
OS << " float w = uintBitsToFloat(packHalf2x16(_h1.zw));\n";
OS << " return " << float4 << "(x, y, z, w);\n";
OS << "}\n\n";
OS << float2 << " gather(" << float4 << " _h)\n";
OS << "{\n";
OS << " float x = uintBitsToFloat(packHalf2x16(_h.xy));\n";
OS << " float y = uintBitsToFloat(packHalf2x16(_h.zw));\n";
OS << " return " << float2 << "(x, y);\n";
OS << "}\n\n";
}
if (properties.has_dynamic_register_load) if (properties.has_dynamic_register_load)
{ {
OS << OS <<
@ -1149,6 +1115,14 @@ bool FragmentProgramDecompiler::handle_sct_scb(u32 opcode)
return true; return true;
case RSX_FP_OPCODE_PKB: SetDst(getFloatTypeName(4) + "(uintBitsToFloat(packUnorm4x8($0)))"); return true; case RSX_FP_OPCODE_PKB: SetDst(getFloatTypeName(4) + "(uintBitsToFloat(packUnorm4x8($0)))"); return true;
case RSX_FP_OPCODE_SIN: SetDst("sin($0.xxxx)"); return true; case RSX_FP_OPCODE_SIN: SetDst("sin($0.xxxx)"); return true;
// Custom ISA extensions for 16-bit OR
case RSX_FP_OPCODE_OR16_HI:
SetDst("$float4(uintBitsToFloat((floatBitsToUint($0.x) & 0x0000ffff) | (packHalf2x16($1.xx) & 0xffff0000)))");
return true;
case RSX_FP_OPCODE_OR16_LO:
SetDst("$float4(uintBitsToFloat((floatBitsToUint($0.x) & 0xffff0000) | (packHalf2x16($1.xx) & 0x0000ffff)))");
return true;
} }
return false; return false;
} }
@ -1295,7 +1269,37 @@ bool FragmentProgramDecompiler::handle_tex_srb(u32 opcode)
std::string FragmentProgramDecompiler::Decompile() std::string FragmentProgramDecompiler::Decompile()
{ {
const auto graph = rsx::assembler::deconstruct_fragment_program(m_prog); auto graph = deconstruct_fragment_program(m_prog);
if (!graph.blocks.empty())
{
// The RSX CFG is missing the output block. We inject a fake tail block that ingests the ROP outputs.
BasicBlock* rop_block = nullptr;
BasicBlock* tail_block = &graph.blocks.back();
if (tail_block->instructions.empty())
{
// Merge block. Use this directly
rop_block = tail_block;
}
else
{
graph.blocks.push_back({});
rop_block = &graph.blocks.back();
tail_block->insert_succ(rop_block);
rop_block->insert_pred(tail_block);
}
const auto rop_inputs = get_fragment_program_output_set(m_prog.ctrl, m_prog.mrt_buffers_count);
rop_block->input_list.insert(rop_block->input_list.end(), rop_inputs.begin(), rop_inputs.end());
FP::RegisterAnnotationPass annotation_pass{ m_prog, { .skip_delay_slots = true } };
FP::RegisterDependencyPass dependency_pass{};
annotation_pass.run(graph);
dependency_pass.run(graph);
}
m_size = 0; m_size = 0;
m_location = 0; m_location = 0;
m_loop_count = 0; m_loop_count = 0;
@ -1303,57 +1307,105 @@ std::string FragmentProgramDecompiler::Decompile()
m_is_valid_ucode = true; m_is_valid_ucode = true;
m_constant_offsets.clear(); m_constant_offsets.clear();
enum // For GLSL scope wind/unwind. We store the min scope depth and loop count for each block and "unwind" to it.
// This should recover information lost when multiple nodes converge on a single merge node or even skip a merge node as is the case with "ELSE" nodes.
std::unordered_map<const BasicBlock*, std::pair<int, u32>> block_data;
auto push_block_info = [&](const BasicBlock* block)
{ {
FORCE_NONE, u32 loop = m_loop_count;
FORCE_SCT, int level = m_code_level;
FORCE_SCB,
auto found = block_data.find(block);
if (found != block_data.end())
{
level = std::min(level, found->second.first);
loop = std::min(loop, found->second.second);
}
block_data[block] = { level, loop };
}; };
int forced_unit = FORCE_NONE; auto emit_block = [&](const std::vector<Instruction>& instructions)
{
for (auto& inst : instructions)
{
m_instruction = &inst;
dst.HEX = inst.bytecode[0];
src0.HEX = inst.bytecode[1];
src1.HEX = inst.bytecode[2];
src2.HEX = inst.bytecode[3];
ensure(handle_tex_srb(inst.opcode) || handle_sct_scb(inst.opcode), "Unsupported operation");
}
};
for (const auto &block : graph.blocks) for (const auto &block : graph.blocks)
{ {
// TODO: Handle block prologue if any auto found = block_data.find(&block);
if (found != block_data.end())
{
const auto [level, loop] = found->second;
for (int i = m_code_level; i > level; i--)
{
m_code_level--;
AddCode("}");
}
m_loop_count = loop;
}
if (!block.pred.empty()) if (!block.pred.empty())
{ {
// CFG guarantees predecessors are sorted, closest one first // Predecessors are always sorted closest last.
for (const auto& pred : block.pred) // This gives some adjacency info and tells us how the previous block connects to this one.
const auto& pred = block.pred.back();
switch (pred.type)
{ {
switch (pred.type) case EdgeType::LOOP:
{ m_loop_count++;
case rsx::assembler::EdgeType::ENDLOOP: [[ fallthrough ]];
m_loop_count--; case EdgeType::IF:
[[ fallthrough ]]; AddCode("{");
case rsx::assembler::EdgeType::ENDIF: m_code_level++;
m_code_level--; break;
AddCode("}"); case EdgeType::ELSE:
break; AddCode("else");
case rsx::assembler::EdgeType::LOOP: AddCode("{");
m_loop_count++; m_code_level++;
[[ fallthrough ]]; break;
case rsx::assembler::EdgeType::IF: case EdgeType::ENDIF:
// Instruction will be inserted by the SIP decoder case EdgeType::ENDLOOP:
AddCode("{"); // Pure merge block?
m_code_level++; break;
break; case EdgeType::NONE:
case rsx::assembler::EdgeType::ELSE: ensure(block.instructions.empty());
// This one needs more testing break;
m_code_level--; default:
AddCode("}"); fmt::throw_exception("Unhandled edge type %d", static_cast<int>(pred.type));
AddCode("else"); break;
AddCode("{");
m_code_level++;
break;
default:
// Start a new block anyway
fmt::throw_exception("Unexpected block found");
}
} }
} }
if (!block.prologue.empty())
{
AddCode("// Prologue");
emit_block(block.prologue);
}
const bool early_epilogue =
!block.epilogue.empty() &&
!block.succ.empty() &&
(block.succ.front().type == EdgeType::IF || block.succ.front().type == EdgeType::LOOP);
for (const auto& inst : block.instructions) for (const auto& inst : block.instructions)
{ {
if (early_epilogue && &inst == &block.instructions.back())
{
AddCode("// Epilogue");
emit_block(block.epilogue);
}
m_instruction = &inst; m_instruction = &inst;
dst.HEX = inst.bytecode[0]; dst.HEX = inst.bytecode[0];
@ -1363,11 +1415,9 @@ std::string FragmentProgramDecompiler::Decompile()
opflags = 0; opflags = 0;
const u32 opcode = dst.opcode | (src1.opcode_is_branch << 6);
auto SIP = [&]() auto SIP = [&]()
{ {
switch (opcode) switch (m_instruction->opcode)
{ {
case RSX_FP_OPCODE_BRK: case RSX_FP_OPCODE_BRK:
if (m_loop_count) AddFlowOp("break"); if (m_loop_count) AddFlowOp("break");
@ -1377,12 +1427,10 @@ std::string FragmentProgramDecompiler::Decompile()
rsx_log.error("Unimplemented SIP instruction: CAL"); rsx_log.error("Unimplemented SIP instruction: CAL");
break; break;
case RSX_FP_OPCODE_FENCT: case RSX_FP_OPCODE_FENCT:
AddCode("//FENCT"); AddCode("// FENCT");
forced_unit = FORCE_SCT;
break; break;
case RSX_FP_OPCODE_FENCB: case RSX_FP_OPCODE_FENCB:
AddCode("//FENCB"); AddCode("// FENCB");
forced_unit = FORCE_SCB;
break; break;
case RSX_FP_OPCODE_IFE: case RSX_FP_OPCODE_IFE:
AddCode("if($cond)"); AddCode("if($cond)");
@ -1406,7 +1454,7 @@ std::string FragmentProgramDecompiler::Decompile()
return true; return true;
}; };
switch (opcode) switch (m_instruction->opcode)
{ {
case RSX_FP_OPCODE_NOP: case RSX_FP_OPCODE_NOP:
break; break;
@ -1415,19 +1463,10 @@ std::string FragmentProgramDecompiler::Decompile()
AddFlowOp("_kill()"); AddFlowOp("_kill()");
break; break;
default: default:
int prev_force_unit = forced_unit;
// Some instructions do not respect forced unit
// Tested with Tales of Vesperia
if (SIP()) break; if (SIP()) break;
if (handle_tex_srb(opcode)) break; if (handle_tex_srb(m_instruction->opcode)) break;
if (handle_sct_scb(m_instruction->opcode)) break;
// FENCT/FENCB do not actually reject instructions if they dont match the forced unit rsx_log.error("Unknown/illegal instruction: 0x%x", m_instruction->opcode);
// Looks like they are optimization hints and not hard-coded forced paths
if (handle_sct_scb(opcode)) break;
forced_unit = FORCE_NONE;
rsx_log.error("Unknown/illegal instruction: 0x%x (forced unit %d)", opcode, prev_force_unit);
break; break;
} }
@ -1435,16 +1474,28 @@ std::string FragmentProgramDecompiler::Decompile()
if (dst.end) break; if (dst.end) break;
} }
// TODO: Handle block epilogue if needed if (!early_epilogue && !block.epilogue.empty())
{
AddCode("// Epilogue");
emit_block(block.epilogue);
}
for (auto& succ : block.succ)
{
switch (succ.type)
{
case EdgeType::ENDIF:
case EdgeType::ENDLOOP:
case EdgeType::ELSE:
push_block_info(succ.to);
break;
default:
break;
}
}
} }
while (m_code_level > 1) ensure(m_code_level == 1);
{
rsx_log.error("Hanging block found at end of shader. Malformed shader?");
m_code_level--;
AddCode("}");
}
// flush m_code_level // flush m_code_level
m_code_level = 1; m_code_level = 1;

View File

@ -1,6 +1,5 @@
#pragma once #pragma once
#include "ShaderParam.h" #include "ShaderParam.h"
#include "FragmentProgramRegister.h"
#include "RSXFragmentProgram.h" #include "RSXFragmentProgram.h"
#include "Assembler/CFG.h" #include "Assembler/CFG.h"
@ -53,8 +52,6 @@ class FragmentProgramDecompiler
int m_code_level; int m_code_level;
std::unordered_map<u32, u32> m_constant_offsets; std::unordered_map<u32, u32> m_constant_offsets;
std::array<rsx::MixedPrecisionRegister, 64> temp_registers;
std::string GetMask() const; std::string GetMask() const;
void SetDst(std::string code, u32 flags = 0); void SetDst(std::string code, u32 flags = 0);
@ -175,7 +172,6 @@ public:
// Decoded properties (out) // Decoded properties (out)
bool has_lit_op = false; bool has_lit_op = false;
bool has_gather_op = false;
bool has_no_output = false; bool has_no_output = false;
bool has_discard_op = false; bool has_discard_op = false;
bool has_tex_op = false; bool has_tex_op = false;

View File

@ -1,196 +0,0 @@
#include "stdafx.h"
#include "FragmentProgramRegister.h"
namespace rsx
{
MixedPrecisionRegister::MixedPrecisionRegister()
{
std::fill(content_mask.begin(), content_mask.end(), data_type_bits::undefined);
}
void MixedPrecisionRegister::tag_h0(bool x, bool y, bool z, bool w)
{
if (x) content_mask[0] = data_type_bits::f16;
if (y) content_mask[1] = data_type_bits::f16;
if (z) content_mask[2] = data_type_bits::f16;
if (w) content_mask[3] = data_type_bits::f16;
}
void MixedPrecisionRegister::tag_h1(bool x, bool y, bool z, bool w)
{
if (x) content_mask[4] = data_type_bits::f16;
if (y) content_mask[5] = data_type_bits::f16;
if (z) content_mask[6] = data_type_bits::f16;
if (w) content_mask[7] = data_type_bits::f16;
}
void MixedPrecisionRegister::tag_r(bool x, bool y, bool z, bool w)
{
if (x) content_mask[0] = content_mask[1] = data_type_bits::f32;
if (y) content_mask[2] = content_mask[3] = data_type_bits::f32;
if (z) content_mask[4] = content_mask[5] = data_type_bits::f32;
if (w) content_mask[6] = content_mask[7] = data_type_bits::f32;
}
void MixedPrecisionRegister::tag(u32 index, bool is_fp16, bool x, bool y, bool z, bool w)
{
if (file_index == umax)
{
// First-time use. Initialize...
const u32 real_index = is_fp16 ? (index >> 1) : index;
file_index = real_index;
}
if (is_fp16)
{
ensure((index / 2) == file_index);
if (index & 1)
{
tag_h1(x, y, z, w);
return;
}
tag_h0(x, y, z, w);
return;
}
tag_r(x, y, z, w);
}
std::string MixedPrecisionRegister::gather_r() const
{
const auto half_index = file_index << 1;
const std::string reg = "r" + std::to_string(file_index);
const std::string gather_half_regs[] = {
"gather(h" + std::to_string(half_index) + ")",
"gather(h" + std::to_string(half_index + 1) + ")"
};
std::string outputs[4];
for (int ch = 0; ch < 4; ++ch)
{
// FIXME: This approach ignores mixed register bits. Not ideal!!!!
const auto channel0 = content_mask[ch * 2];
const auto is_fp16_ch = channel0 == content_mask[ch * 2 + 1] && channel0 == data_type_bits::f16;
outputs[ch] = is_fp16_ch ? gather_half_regs[ch / 2] : reg;
}
// Grouping. Only replace relevant bits...
if (outputs[0] == outputs[1]) outputs[0] = "";
if (outputs[2] == outputs[3]) outputs[2] = "";
// Assemble
bool group = false;
std::string result = "";
constexpr std::string_view swz_mask = "xyzw";
for (int ch = 0; ch < 4; ++ch)
{
if (outputs[ch].empty())
{
group = true;
continue;
}
if (!result.empty())
{
result += ", ";
}
if (group)
{
ensure(ch > 0);
group = false;
if (outputs[ch] == reg)
{
result += reg + "." + swz_mask[ch - 1] + swz_mask[ch];
continue;
}
result += outputs[ch];
continue;
}
const int subch = outputs[ch] == reg ? ch : (ch % 2); // Avoid .xyxy.z and other such ugly swizzles
result += outputs[ch] + "." + swz_mask[subch];
}
// Optimize dual-gather (128-bit gather) to use special function
const std::string double_gather = gather_half_regs[0] + ", " + gather_half_regs[1];
if (result == double_gather)
{
result = "gather(h" + std::to_string(half_index) + ", h" + std::to_string(half_index + 1) + ")";
}
return "(" + result + ")";
}
std::string MixedPrecisionRegister::fetch_halfreg(u32 word_index) const
{
// Reads half-word 0 (H16x4) from a full real (R32x4) register
constexpr std::string_view swz_mask = "xyzw";
const std::string reg = "r" + std::to_string(file_index);
const std::string hreg = "h" + std::to_string(file_index * 2 + word_index);
const std::string word0_bits = "floatBitsToUint(" + reg + "." + swz_mask[word_index * 2] + ")";
const std::string word1_bits = "floatBitsToUint(" + reg + "." + swz_mask[word_index * 2 + 1] + ")";
const std::string words[] = {
"unpackHalf2x16(" + word0_bits + ")",
"unpackHalf2x16(" + word1_bits + ")"
};
// Assemble
std::string outputs[4];
ensure(word_index <= 1);
const int word_offset = word_index * 4;
for (int ch = 0; ch < 4; ++ch)
{
outputs[ch] = content_mask[ch + word_offset] == data_type_bits::f32
? words[ch / 2]
: hreg;
}
// Grouping. Only replace relevant bits...
if (outputs[0] == outputs[1]) outputs[0] = "";
if (outputs[2] == outputs[3]) outputs[2] = "";
// Assemble
bool group = false;
std::string result = "";
for (int ch = 0; ch < 4; ++ch)
{
if (outputs[ch].empty())
{
group = true;
continue;
}
if (!result.empty())
{
result += ", ";
}
if (group)
{
ensure(ch > 0);
group = false;
result += outputs[ch];
if (outputs[ch] == hreg)
{
result += std::string(".") + swz_mask[ch - 1] + swz_mask[ch];
}
continue;
}
const int subch = outputs[ch] == hreg ? ch : (ch % 2); // Avoid .xyxy.z and other such ugly swizzles
result += outputs[ch] + "." + swz_mask[subch];
}
return "(" + result + ")";
}
}

View File

@ -1,111 +0,0 @@
#pragma once
#include <util/types.hpp>
namespace rsx
{
class MixedPrecisionRegister
{
enum data_type_bits
{
undefined = 0,
f16 = 1,
f32 = 2
};
std::array<data_type_bits, 8> content_mask; // Content details for each half-word
u32 file_index = umax;
void tag_h0(bool x, bool y, bool z, bool w);
void tag_h1(bool x, bool y, bool z, bool w);
void tag_r(bool x, bool y, bool z, bool w);
std::string fetch_halfreg(u32 word_index) const;
public:
MixedPrecisionRegister();
void tag(u32 index, bool is_fp16, bool x, bool y, bool z, bool w);
std::string gather_r() const;
std::string split_h0() const
{
return fetch_halfreg(0);
}
std::string split_h1() const
{
return fetch_halfreg(1);
}
// Getters
// Return true if all values are unwritten to (undefined)
bool floating() const
{
return file_index == umax;
}
// Return true if the first half register is all undefined
bool floating_h0() const
{
return content_mask[0] == content_mask[1] &&
content_mask[1] == content_mask[2] &&
content_mask[2] == content_mask[3] &&
content_mask[3] == data_type_bits::undefined;
}
// Return true if the second half register is all undefined
bool floating_h1() const
{
return content_mask[4] == content_mask[5] &&
content_mask[5] == content_mask[6] &&
content_mask[6] == content_mask[7] &&
content_mask[7] == data_type_bits::undefined;
}
// Return true if any of the half-words are 16-bit
bool requires_gather(u8 channel) const
{
// Data fetched from the single precision register requires merging of the two half registers
const auto channel_offset = channel * 2;
ensure(channel_offset <= 6);
return (content_mask[channel_offset] == data_type_bits::f16 || content_mask[channel_offset + 1] == data_type_bits::f16);
}
// Return true if the entire 128-bit register is filled with 2xfp16x4 data words
bool requires_gather128() const
{
// Full 128-bit check
for (const auto& ch : content_mask)
{
if (ch == data_type_bits::f16)
{
return true;
}
}
return false;
}
// Return true if the half-register is polluted with fp32 data
bool requires_split(u32 word_index) const
{
const u32 content_offset = word_index * 4;
for (u32 i = 0; i < 4; ++i)
{
if (content_mask[content_offset + i] == data_type_bits::f32)
{
return true;
}
}
return false;
}
};
}

View File

@ -1,6 +1,7 @@
#pragma once #pragma once
#include "program_util.h" #include "program_util.h"
#include "Assembler/FPOpcodes.h"
#include <string> #include <string>
#include <vector> #include <vector>
@ -23,76 +24,7 @@ enum register_precision
RSX_FP_PRECISION_UNKNOWN = 5 // Unknown what this actually does; seems to do nothing on hwtests but then why would their compiler emit it? RSX_FP_PRECISION_UNKNOWN = 5 // Unknown what this actually does; seems to do nothing on hwtests but then why would their compiler emit it?
}; };
enum fp_opcode using enum rsx::assembler::FP_opcode;
{
RSX_FP_OPCODE_NOP = 0x00, // No-Operation
RSX_FP_OPCODE_MOV = 0x01, // Move
RSX_FP_OPCODE_MUL = 0x02, // Multiply
RSX_FP_OPCODE_ADD = 0x03, // Add
RSX_FP_OPCODE_MAD = 0x04, // Multiply-Add
RSX_FP_OPCODE_DP3 = 0x05, // 3-component Dot Product
RSX_FP_OPCODE_DP4 = 0x06, // 4-component Dot Product
RSX_FP_OPCODE_DST = 0x07, // Distance
RSX_FP_OPCODE_MIN = 0x08, // Minimum
RSX_FP_OPCODE_MAX = 0x09, // Maximum
RSX_FP_OPCODE_SLT = 0x0A, // Set-If-LessThan
RSX_FP_OPCODE_SGE = 0x0B, // Set-If-GreaterEqual
RSX_FP_OPCODE_SLE = 0x0C, // Set-If-LessEqual
RSX_FP_OPCODE_SGT = 0x0D, // Set-If-GreaterThan
RSX_FP_OPCODE_SNE = 0x0E, // Set-If-NotEqual
RSX_FP_OPCODE_SEQ = 0x0F, // Set-If-Equal
RSX_FP_OPCODE_FRC = 0x10, // Fraction (fract)
RSX_FP_OPCODE_FLR = 0x11, // Floor
RSX_FP_OPCODE_KIL = 0x12, // Kill fragment
RSX_FP_OPCODE_PK4 = 0x13, // Pack four signed 8-bit values
RSX_FP_OPCODE_UP4 = 0x14, // Unpack four signed 8-bit values
RSX_FP_OPCODE_DDX = 0x15, // Partial-derivative in x (Screen space derivative w.r.t. x)
RSX_FP_OPCODE_DDY = 0x16, // Partial-derivative in y (Screen space derivative w.r.t. y)
RSX_FP_OPCODE_TEX = 0x17, // Texture lookup
RSX_FP_OPCODE_TXP = 0x18, // Texture sample with projection (Projective texture lookup)
RSX_FP_OPCODE_TXD = 0x19, // Texture sample with partial differentiation (Texture lookup with derivatives)
RSX_FP_OPCODE_RCP = 0x1A, // Reciprocal
RSX_FP_OPCODE_RSQ = 0x1B, // Reciprocal Square Root
RSX_FP_OPCODE_EX2 = 0x1C, // Exponentiation base 2
RSX_FP_OPCODE_LG2 = 0x1D, // Log base 2
RSX_FP_OPCODE_LIT = 0x1E, // Lighting coefficients
RSX_FP_OPCODE_LRP = 0x1F, // Linear Interpolation
RSX_FP_OPCODE_STR = 0x20, // Set-If-True
RSX_FP_OPCODE_SFL = 0x21, // Set-If-False
RSX_FP_OPCODE_COS = 0x22, // Cosine
RSX_FP_OPCODE_SIN = 0x23, // Sine
RSX_FP_OPCODE_PK2 = 0x24, // Pack two 16-bit floats
RSX_FP_OPCODE_UP2 = 0x25, // Unpack two 16-bit floats
RSX_FP_OPCODE_POW = 0x26, // Power
RSX_FP_OPCODE_PKB = 0x27, // Pack bytes
RSX_FP_OPCODE_UPB = 0x28, // Unpack bytes
RSX_FP_OPCODE_PK16 = 0x29, // Pack 16 bits
RSX_FP_OPCODE_UP16 = 0x2A, // Unpack 16
RSX_FP_OPCODE_BEM = 0x2B, // Bump-environment map (a.k.a. 2D coordinate transform)
RSX_FP_OPCODE_PKG = 0x2C, // Pack with sRGB transformation
RSX_FP_OPCODE_UPG = 0x2D, // Unpack gamma
RSX_FP_OPCODE_DP2A = 0x2E, // 2-component dot product with scalar addition
RSX_FP_OPCODE_TXL = 0x2F, // Texture sample with explicit LOD
RSX_FP_OPCODE_TXB = 0x31, // Texture sample with bias
RSX_FP_OPCODE_TEXBEM = 0x33,
RSX_FP_OPCODE_TXPBEM = 0x34,
RSX_FP_OPCODE_BEMLUM = 0x35,
RSX_FP_OPCODE_REFL = 0x36, // Reflection vector
RSX_FP_OPCODE_TIMESWTEX = 0x37,
RSX_FP_OPCODE_DP2 = 0x38, // 2-component dot product
RSX_FP_OPCODE_NRM = 0x39, // Normalize
RSX_FP_OPCODE_DIV = 0x3A, // Division
RSX_FP_OPCODE_DIVSQ = 0x3B, // Divide by Square Root
RSX_FP_OPCODE_LIF = 0x3C, // Final part of LIT
RSX_FP_OPCODE_FENCT = 0x3D, // Fence T?
RSX_FP_OPCODE_FENCB = 0x3E, // Fence B?
RSX_FP_OPCODE_BRK = 0x40, // Break
RSX_FP_OPCODE_CAL = 0x41, // Subroutine call
RSX_FP_OPCODE_IFE = 0x42, // If
RSX_FP_OPCODE_LOOP = 0x43, // Loop
RSX_FP_OPCODE_REP = 0x44, // Repeat
RSX_FP_OPCODE_RET = 0x45 // Return
};
union OPDEST union OPDEST
{ {
@ -116,6 +48,12 @@ union OPDEST
u32 no_dest : 1; u32 no_dest : 1;
u32 saturate : 1; // _sat u32 saturate : 1; // _sat
}; };
struct
{
u32 : 9;
u32 write_mask : 4;
};
}; };
union SRC0 union SRC0
@ -164,7 +102,7 @@ union SRC1
u32 src1_prec_mod : 3; // Precision modifier for src1 (CoD:MW series) u32 src1_prec_mod : 3; // Precision modifier for src1 (CoD:MW series)
u32 src2_prec_mod : 3; // Precision modifier for src2 (unproven, should affect MAD instruction) u32 src2_prec_mod : 3; // Precision modifier for src2 (unproven, should affect MAD instruction)
u32 scale : 3; u32 scale : 3;
u32 opcode_is_branch : 1; u32 opcode_hi : 1; // Opcode high bit
}; };
struct struct
@ -207,6 +145,23 @@ union SRC2
}; };
}; };
union SRC_Common
{
u32 HEX;
struct
{
u32 reg_type : 2;
u32 tmp_reg_index : 6;
u32 fp16 : 1;
u32 swizzle_x : 2;
u32 swizzle_y : 2;
u32 swizzle_z : 2;
u32 swizzle_w : 2;
u32 neg : 1;
};
};
constexpr const char* rsx_fp_input_attr_regs[] = constexpr const char* rsx_fp_input_attr_regs[] =
{ {
"WPOS", "COL0", "COL1", "FOGC", "TEX0", "WPOS", "COL0", "COL1", "FOGC", "TEX0",

View File

@ -244,10 +244,10 @@ public:
std::vector<std::string> swizzles; std::vector<std::string> swizzles;
ShaderVariable() = default; ShaderVariable() = default;
ShaderVariable(const std::string& var) ShaderVariable(std::string_view var)
{ {
// Separate 'double destination' variables 'X=Y=SRC' // Separate 'double destination' variables 'X=Y=SRC'
std::string simple_var; std::string_view simple_var;
const auto eq_pos = var.find('='); const auto eq_pos = var.find('=');
if (eq_pos != umax) if (eq_pos != umax)
@ -267,11 +267,11 @@ public:
simple_var = simple_var.substr(brace_pos); simple_var = simple_var.substr(brace_pos);
} }
auto var_blocks = fmt::split(simple_var, { "." }); const auto var_blocks = fmt::split_sv(simple_var, { "." });
ensure((!var_blocks.empty())); ensure((!var_blocks.empty()));
name = prefix + var_blocks[0]; name = prefix + std::string(var_blocks[0]);
if (var_blocks.size() == 1) if (var_blocks.size() == 1)
{ {

View File

@ -4,7 +4,7 @@
#ifdef _WIN32 #ifdef _WIN32
#define VK_USE_PLATFORM_WIN32_KHR #define VK_USE_PLATFORM_WIN32_KHR
#elif defined(__APPLE__) #elif defined(__APPLE__)
#define VK_USE_PLATFORM_MACOS_MVK #define VK_USE_PLATFORM_METAL_EXT
#elif defined(ANDROID) #elif defined(ANDROID)
#define VK_USE_PLATFORM_ANDROID_KHR #define VK_USE_PLATFORM_ANDROID_KHR
#else #else
@ -29,7 +29,7 @@
// Undefine header configuration variables // Undefine header configuration variables
#undef VK_USE_PLATFORM_WIN32_KHR #undef VK_USE_PLATFORM_WIN32_KHR
#undef VK_USE_PLATFORM_MACOS_MVK #undef VK_USE_PLATFORM_METAL_EXT
#undef VK_USE_PLATFORM_ANDROID_KHR #undef VK_USE_PLATFORM_ANDROID_KHR
#undef VK_USE_PLATFORM_XLIB_KHR #undef VK_USE_PLATFORM_XLIB_KHR
#undef VK_USE_PLATFORM_WAYLAND_KHR #undef VK_USE_PLATFORM_WAYLAND_KHR

View File

@ -50,6 +50,7 @@ namespace vk
: m_device(dev) : m_device(dev)
{ {
const bool nullable = !!(flags & VK_BUFFER_CREATE_ALLOW_NULL_RPCS3); const bool nullable = !!(flags & VK_BUFFER_CREATE_ALLOW_NULL_RPCS3);
const bool no_vmem_recovery = !!(flags & VK_BUFFER_CREATE_IGNORE_VMEM_PRESSURE_RPCS3);
info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
info.flags = flags & ~VK_BUFFER_CREATE_SPECIAL_FLAGS_RPCS3; info.flags = flags & ~VK_BUFFER_CREATE_SPECIAL_FLAGS_RPCS3;
@ -69,18 +70,27 @@ namespace vk
fmt::throw_exception("No compatible memory type was found!"); fmt::throw_exception("No compatible memory type was found!");
} }
memory = std::make_unique<memory_block>(m_device, memory_reqs.size, memory_reqs.alignment, allocation_type_info, allocation_pool, nullable); memory_allocation_request request
{
.size = memory_reqs.size,
.alignment = memory_reqs.alignment,
.memory_type = &allocation_type_info,
.pool = allocation_pool,
.throw_on_fail = !nullable,
.recover_vmem_on_fail = !no_vmem_recovery
};
memory = std::make_unique<memory_block>(m_device, request);
if (auto device_memory = memory->get_vk_device_memory(); if (auto device_memory = memory->get_vk_device_memory();
device_memory != VK_NULL_HANDLE) device_memory != VK_NULL_HANDLE)
{ {
vkBindBufferMemory(dev, value, device_memory, memory->get_vk_device_memory_offset()); vkBindBufferMemory(dev, value, device_memory, memory->get_vk_device_memory_offset());
return;
} }
else
{ ensure(nullable);
ensure(nullable); vkDestroyBuffer(m_device, value, nullptr);
vkDestroyBuffer(m_device, value, nullptr); value = VK_NULL_HANDLE;
value = VK_NULL_HANDLE;
}
} }
buffer::buffer(const vk::render_device& dev, VkBufferUsageFlags usage, void* host_pointer, u64 size) buffer::buffer(const vk::render_device& dev, VkBufferUsageFlags usage, void* host_pointer, u64 size)

View File

@ -9,9 +9,10 @@ namespace vk
{ {
enum : u32 enum : u32
{ {
VK_BUFFER_CREATE_ALLOW_NULL_RPCS3 = 0x80000000, VK_BUFFER_CREATE_ALLOW_NULL_RPCS3 = 0x10000000, // If we cannot allocate memory for the buffer, just return an empty but valid object with a null handle.
VK_BUFFER_CREATE_IGNORE_VMEM_PRESSURE_RPCS3 = 0x20000000, // If we cannot allocate memory for the buffer, do not run recovery routine to recover VRAM. Crash or return empty handle immediately instead.
VK_BUFFER_CREATE_SPECIAL_FLAGS_RPCS3 = (VK_BUFFER_CREATE_ALLOW_NULL_RPCS3) VK_BUFFER_CREATE_SPECIAL_FLAGS_RPCS3 = (VK_BUFFER_CREATE_ALLOW_NULL_RPCS3 | VK_BUFFER_CREATE_IGNORE_VMEM_PRESSURE_RPCS3)
}; };
struct buffer_view : public unique_resource struct buffer_view : public unique_resource

View File

@ -53,7 +53,7 @@ namespace vk
VkFlags create_flags = 0; VkFlags create_flags = 0;
if (m_prefer_writethrough) if (m_prefer_writethrough)
{ {
create_flags |= VK_BUFFER_CREATE_ALLOW_NULL_RPCS3; create_flags |= (VK_BUFFER_CREATE_ALLOW_NULL_RPCS3 | VK_BUFFER_CREATE_IGNORE_VMEM_PRESSURE_RPCS3);
} }
heap = std::make_unique<buffer>(*g_render_device, size, memory_index, memory_flags, usage, create_flags, VMM_ALLOCATION_POOL_SYSTEM); heap = std::make_unique<buffer>(*g_render_device, size, memory_index, memory_flags, usage, create_flags, VMM_ALLOCATION_POOL_SYSTEM);
@ -146,7 +146,7 @@ namespace vk
VkFlags create_flags = 0; VkFlags create_flags = 0;
if (m_prefer_writethrough) if (m_prefer_writethrough)
{ {
create_flags |= VK_BUFFER_CREATE_ALLOW_NULL_RPCS3; create_flags |= (VK_BUFFER_CREATE_ALLOW_NULL_RPCS3 | VK_BUFFER_CREATE_IGNORE_VMEM_PRESSURE_RPCS3);
} }
heap = std::make_unique<buffer>(*g_render_device, aligned_new_size, memory_index, memory_flags, usage, create_flags, VMM_ALLOCATION_POOL_SYSTEM); heap = std::make_unique<buffer>(*g_render_device, aligned_new_size, memory_index, memory_flags, usage, create_flags, VMM_ALLOCATION_POOL_SYSTEM);

View File

@ -3,6 +3,9 @@
#include "util/logs.hpp" #include "util/logs.hpp"
#include "Emu/system_config.h" #include "Emu/system_config.h"
#include <vulkan/vulkan_core.h> #include <vulkan/vulkan_core.h>
#ifdef __APPLE__
#include <vulkan/vulkan_beta.h>
#endif
namespace vk namespace vk
{ {
@ -555,6 +558,10 @@ namespace vk
{ {
requested_extensions.push_back(VK_EXT_DEVICE_FAULT_EXTENSION_NAME); requested_extensions.push_back(VK_EXT_DEVICE_FAULT_EXTENSION_NAME);
} }
#ifdef __APPLE__
requested_extensions.push_back(VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME);
#endif
enabled_features.robustBufferAccess = VK_TRUE; enabled_features.robustBufferAccess = VK_TRUE;
enabled_features.fullDrawIndexUint32 = VK_TRUE; enabled_features.fullDrawIndexUint32 = VK_TRUE;

View File

@ -128,7 +128,16 @@ namespace vk
fmt::throw_exception("No compatible memory type was found!"); fmt::throw_exception("No compatible memory type was found!");
} }
memory = std::make_shared<vk::memory_block>(m_device, memory_req.size, memory_req.alignment, allocation_type_info, allocation_pool, nullable); memory_allocation_request alloc_request
{
.size = memory_req.size,
.alignment = memory_req.alignment,
.memory_type = &allocation_type_info,
.pool = allocation_pool,
.throw_on_fail = !nullable
};
memory = std::make_shared<vk::memory_block>(m_device, alloc_request);
if (auto device_mem = memory->get_vk_device_memory(); if (auto device_mem = memory->get_vk_device_memory();
device_mem != VK_NULL_HANDLE) [[likely]] device_mem != VK_NULL_HANDLE) [[likely]]
{ {

View File

@ -124,10 +124,11 @@ namespace vk
} }
#ifdef __APPLE__ #ifdef __APPLE__
extensions.push_back(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME);
extensions.push_back(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME);
if (support.is_supported(VK_EXT_LAYER_SETTINGS_EXTENSION_NAME)) if (support.is_supported(VK_EXT_LAYER_SETTINGS_EXTENSION_NAME))
{ {
extensions.push_back(VK_EXT_LAYER_SETTINGS_EXTENSION_NAME); extensions.push_back(VK_EXT_LAYER_SETTINGS_EXTENSION_NAME);
layers.push_back(kMVKMoltenVKDriverLayerName);
mvk_settings.push_back(VkLayerSettingEXT{ kMVKMoltenVKDriverLayerName, "MVK_CONFIG_RESUME_LOST_DEVICE", VK_LAYER_SETTING_TYPE_BOOL32_EXT, 1, &setting_true }); mvk_settings.push_back(VkLayerSettingEXT{ kMVKMoltenVKDriverLayerName, "MVK_CONFIG_RESUME_LOST_DEVICE", VK_LAYER_SETTING_TYPE_BOOL32_EXT, 1, &setting_true });
mvk_settings.push_back(VkLayerSettingEXT{ kMVKMoltenVKDriverLayerName, "MVK_CONFIG_FAST_MATH_ENABLED", VK_LAYER_SETTING_TYPE_INT32_EXT, 1, &setting_fast_math }); mvk_settings.push_back(VkLayerSettingEXT{ kMVKMoltenVKDriverLayerName, "MVK_CONFIG_FAST_MATH_ENABLED", VK_LAYER_SETTING_TYPE_INT32_EXT, 1, &setting_fast_math });
@ -154,7 +155,7 @@ namespace vk
#ifdef _WIN32 #ifdef _WIN32
extensions.push_back(VK_KHR_WIN32_SURFACE_EXTENSION_NAME); extensions.push_back(VK_KHR_WIN32_SURFACE_EXTENSION_NAME);
#elif defined(__APPLE__) #elif defined(__APPLE__)
extensions.push_back(VK_MVK_MACOS_SURFACE_EXTENSION_NAME); extensions.push_back(VK_EXT_METAL_SURFACE_EXTENSION_NAME);
#else #else
bool found_surface_ext = false; bool found_surface_ext = false;
#ifdef HAVE_X11 #ifdef HAVE_X11
@ -187,15 +188,32 @@ namespace vk
if (g_cfg.video.debug_output) if (g_cfg.video.debug_output)
layers.push_back("VK_LAYER_KHRONOS_validation"); layers.push_back("VK_LAYER_KHRONOS_validation");
} }
#ifdef __APPLE__
// MoltenVK's ICD will not be detected without these extensions enabled.
else
{
extensions_loaded = true;
extensions.push_back(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME);
extensions.push_back(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME);
}
#endif
VkInstanceCreateInfo instance_info = {}; VkInstanceCreateInfo instance_info = {};
instance_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; instance_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
instance_info.pApplicationInfo = &app; instance_info.pApplicationInfo = &app;
instance_info.enabledLayerCount = static_cast<u32>(layers.size()); instance_info.enabledLayerCount = static_cast<u32>(layers.size());
instance_info.ppEnabledLayerNames = layers.data(); instance_info.ppEnabledLayerNames = layers.data();
#ifdef __APPLE__
instance_info.enabledExtensionCount = static_cast<u32>(extensions.size());
instance_info.ppEnabledExtensionNames = extensions.data();
#else
instance_info.enabledExtensionCount = fast ? 0 : static_cast<u32>(extensions.size()); instance_info.enabledExtensionCount = fast ? 0 : static_cast<u32>(extensions.size());
instance_info.ppEnabledExtensionNames = fast ? nullptr : extensions.data(); instance_info.ppEnabledExtensionNames = fast ? nullptr : extensions.data();
#endif
instance_info.pNext = next_info; instance_info.pNext = next_info;
#ifdef __APPLE__
instance_info.flags |= VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR;
#endif
if (VkResult result = vkCreateInstance(&instance_info, nullptr, &m_instance); result != VK_SUCCESS) if (VkResult result = vkCreateInstance(&instance_info, nullptr, &m_instance); result != VK_SUCCESS)
{ {

View File

@ -224,7 +224,7 @@ namespace vk
vmaDestroyAllocator(m_allocator); vmaDestroyAllocator(m_allocator);
} }
mem_allocator_vk::mem_handle_t mem_allocator_vma::alloc(u64 block_sz, u64 alignment, const memory_type_info& memory_type, vmm_allocation_pool pool, bool throw_on_fail) mem_allocator_vk::mem_handle_t mem_allocator_vma::alloc(const memory_allocation_request& request)
{ {
VmaAllocation vma_alloc; VmaAllocation vma_alloc;
VkMemoryRequirements mem_req = {}; VkMemoryRequirements mem_req = {};
@ -233,11 +233,11 @@ namespace vk
auto do_vma_alloc = [&]() -> std::tuple<VkResult, u32> auto do_vma_alloc = [&]() -> std::tuple<VkResult, u32>
{ {
for (const auto& memory_type_index : memory_type) for (const auto& memory_type_index : *request.memory_type)
{ {
mem_req.memoryTypeBits = 1u << memory_type_index; mem_req.memoryTypeBits = 1u << memory_type_index;
mem_req.size = ::align2(block_sz, alignment); mem_req.size = ::align2(request.size, request.alignment);
mem_req.alignment = alignment; mem_req.alignment = request.alignment;
create_info.memoryTypeBits = 1u << memory_type_index; create_info.memoryTypeBits = 1u << memory_type_index;
create_info.flags = m_allocation_flags; create_info.flags = m_allocation_flags;
@ -256,26 +256,29 @@ namespace vk
const auto [status, type] = do_vma_alloc(); const auto [status, type] = do_vma_alloc();
if (status == VK_SUCCESS) if (status == VK_SUCCESS)
{ {
vmm_notify_memory_allocated(vma_alloc, type, block_sz, pool); vmm_notify_memory_allocated(vma_alloc, type, request.size, request.pool);
return vma_alloc; return vma_alloc;
} }
} }
const auto severity = (throw_on_fail) ? rsx::problem_severity::fatal : rsx::problem_severity::severe; if (request.recover_vmem_on_fail)
if (error_code == VK_ERROR_OUT_OF_DEVICE_MEMORY &&
vmm_handle_memory_pressure(severity))
{ {
// Out of memory. Try again. const auto severity = (request.throw_on_fail) ? rsx::problem_severity::fatal : rsx::problem_severity::severe;
const auto [status, type] = do_vma_alloc(); if (error_code == VK_ERROR_OUT_OF_DEVICE_MEMORY &&
if (status == VK_SUCCESS) vmm_handle_memory_pressure(severity))
{ {
rsx_log.warning("Renderer ran out of video memory but successfully recovered."); // Out of memory. Try again.
vmm_notify_memory_allocated(vma_alloc, type, block_sz, pool); const auto [status, type] = do_vma_alloc();
return vma_alloc; if (status == VK_SUCCESS)
{
rsx_log.warning("Renderer ran out of video memory but successfully recovered.");
vmm_notify_memory_allocated(vma_alloc, type, request.size, request.pool);
return vma_alloc;
}
} }
} }
if (!throw_on_fail) if (!request.throw_on_fail)
{ {
return VK_NULL_HANDLE; return VK_NULL_HANDLE;
} }
@ -361,18 +364,18 @@ namespace vk
m_allocation_flags = VMA_ALLOCATION_CREATE_STRATEGY_MIN_TIME_BIT; m_allocation_flags = VMA_ALLOCATION_CREATE_STRATEGY_MIN_TIME_BIT;
} }
mem_allocator_vk::mem_handle_t mem_allocator_vk::alloc(u64 block_sz, u64 /*alignment*/, const memory_type_info& memory_type, vmm_allocation_pool pool, bool throw_on_fail) mem_allocator_vk::mem_handle_t mem_allocator_vk::alloc(const memory_allocation_request& request)
{ {
VkResult error_code = VK_ERROR_UNKNOWN; VkResult error_code = VK_ERROR_UNKNOWN;
VkDeviceMemory memory; VkDeviceMemory memory;
VkMemoryAllocateInfo info = {}; VkMemoryAllocateInfo info = {};
info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
info.allocationSize = block_sz; info.allocationSize = request.size;
auto do_vk_alloc = [&]() -> std::tuple<VkResult, u32> auto do_vk_alloc = [&]() -> std::tuple<VkResult, u32>
{ {
for (const auto& memory_type_index : memory_type) for (const auto& memory_type_index : *request.memory_type)
{ {
info.memoryTypeIndex = memory_type_index; info.memoryTypeIndex = memory_type_index;
error_code = vkAllocateMemory(m_device, &info, nullptr, &memory); error_code = vkAllocateMemory(m_device, &info, nullptr, &memory);
@ -389,26 +392,29 @@ namespace vk
const auto [status, type] = do_vk_alloc(); const auto [status, type] = do_vk_alloc();
if (status == VK_SUCCESS) if (status == VK_SUCCESS)
{ {
vmm_notify_memory_allocated(memory, type, block_sz, pool); vmm_notify_memory_allocated(memory, type, request.size, request.pool);
return memory; return memory;
} }
} }
const auto severity = (throw_on_fail) ? rsx::problem_severity::fatal : rsx::problem_severity::severe; if (request.recover_vmem_on_fail)
if (error_code == VK_ERROR_OUT_OF_DEVICE_MEMORY &&
vmm_handle_memory_pressure(severity))
{ {
// Out of memory. Try again. const auto severity = (request.throw_on_fail) ? rsx::problem_severity::fatal : rsx::problem_severity::severe;
const auto [status, type] = do_vk_alloc(); if (error_code == VK_ERROR_OUT_OF_DEVICE_MEMORY &&
if (status == VK_SUCCESS) vmm_handle_memory_pressure(severity))
{ {
rsx_log.warning("Renderer ran out of video memory but successfully recovered."); // Out of memory. Try again.
vmm_notify_memory_allocated(memory, type, block_sz, pool); const auto [status, type] = do_vk_alloc();
return memory; if (status == VK_SUCCESS)
{
rsx_log.warning("Renderer ran out of video memory but successfully recovered.");
vmm_notify_memory_allocated(memory, type, request.size, request.pool);
return memory;
}
} }
} }
if (!throw_on_fail) if (!request.throw_on_fail)
{ {
return VK_NULL_HANDLE; return VK_NULL_HANDLE;
} }
@ -455,11 +461,11 @@ namespace vk
return g_render_device->get_allocator(); return g_render_device->get_allocator();
} }
memory_block::memory_block(VkDevice dev, u64 block_sz, u64 alignment, const memory_type_info& memory_type, vmm_allocation_pool pool, bool nullable) memory_block::memory_block(VkDevice dev, const memory_allocation_request& alloc_request)
: m_device(dev), m_size(block_sz) : m_device(dev), m_size(alloc_request.size)
{ {
m_mem_allocator = get_current_mem_allocator(); m_mem_allocator = get_current_mem_allocator();
m_mem_handle = m_mem_allocator->alloc(block_sz, alignment, memory_type, pool, !nullable); m_mem_handle = m_mem_allocator->alloc(alloc_request);
} }
memory_block::~memory_block() memory_block::~memory_block()

View File

@ -66,6 +66,16 @@ namespace vk
u64 size; u64 size;
}; };
struct memory_allocation_request
{
u64 size = 0;
u64 alignment = 1;
const memory_type_info* memory_type = nullptr;
vmm_allocation_pool pool = VMM_ALLOCATION_POOL_UNDEFINED;
bool throw_on_fail = true;
bool recover_vmem_on_fail = true;
};
class mem_allocator_base class mem_allocator_base
{ {
public: public:
@ -76,7 +86,7 @@ namespace vk
virtual void destroy() = 0; virtual void destroy() = 0;
virtual mem_handle_t alloc(u64 block_sz, u64 alignment, const memory_type_info& memory_type, vmm_allocation_pool pool, bool throw_on_fail) = 0; virtual mem_handle_t alloc(const memory_allocation_request& request) = 0;
virtual void free(mem_handle_t mem_handle) = 0; virtual void free(mem_handle_t mem_handle) = 0;
virtual void* map(mem_handle_t mem_handle, u64 offset, u64 size) = 0; virtual void* map(mem_handle_t mem_handle, u64 offset, u64 size) = 0;
virtual void unmap(mem_handle_t mem_handle) = 0; virtual void unmap(mem_handle_t mem_handle) = 0;
@ -104,7 +114,7 @@ namespace vk
void destroy() override; void destroy() override;
mem_handle_t alloc(u64 block_sz, u64 alignment, const memory_type_info& memory_type, vmm_allocation_pool pool, bool throw_on_fail) override; mem_handle_t alloc(const memory_allocation_request& request) override;
void free(mem_handle_t mem_handle) override; void free(mem_handle_t mem_handle) override;
void* map(mem_handle_t mem_handle, u64 offset, u64 /*size*/) override; void* map(mem_handle_t mem_handle, u64 offset, u64 /*size*/) override;
@ -134,7 +144,7 @@ namespace vk
void destroy() override {} void destroy() override {}
mem_handle_t alloc(u64 block_sz, u64 /*alignment*/, const memory_type_info& memory_type, vmm_allocation_pool pool, bool throw_on_fail) override; mem_handle_t alloc(const memory_allocation_request& request) override;
void free(mem_handle_t mem_handle) override; void free(mem_handle_t mem_handle) override;
void* map(mem_handle_t mem_handle, u64 offset, u64 size) override; void* map(mem_handle_t mem_handle, u64 offset, u64 size) override;
@ -147,7 +157,7 @@ namespace vk
struct memory_block struct memory_block
{ {
memory_block(VkDevice dev, u64 block_sz, u64 alignment, const memory_type_info& memory_type, vmm_allocation_pool pool, bool nullable = false); memory_block(VkDevice dev, const memory_allocation_request& alloc_request);
virtual ~memory_block(); virtual ~memory_block();
virtual VkDeviceMemory get_vk_device_memory(); virtual VkDeviceMemory get_vk_device_memory();

View File

@ -0,0 +1,2 @@
#pragma once
void* GetCAMetalLayerFromMetalView(void* view);

View File

@ -0,0 +1,10 @@
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wold-style-cast"
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
#pragma GCC diagnostic ignored "-Wmissing-declarations"
#import <Foundation/Foundation.h>
#import <AppKit/AppKit.h>
#import <QuartzCore/QuartzCore.h>
void* GetCAMetalLayerFromMetalView(void* view) { return ((NSView*)view).layer; }
#pragma GCC diagnostic pop

View File

@ -1,6 +1,7 @@
#pragma once #pragma once
#include "swapchain_core.h" #include "swapchain_core.h"
#include "metal_layer.h"
namespace vk namespace vk
{ {
@ -12,11 +13,11 @@ namespace vk
VkSurfaceKHR make_WSI_surface(VkInstance vk_instance, display_handle_t window_handle, WSI_config* /*config*/) VkSurfaceKHR make_WSI_surface(VkInstance vk_instance, display_handle_t window_handle, WSI_config* /*config*/)
{ {
VkSurfaceKHR result = VK_NULL_HANDLE; VkSurfaceKHR result = VK_NULL_HANDLE;
VkMacOSSurfaceCreateInfoMVK createInfo = {}; VkMetalSurfaceCreateInfoEXT createInfo = {};
createInfo.sType = VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK; createInfo.sType = VK_STRUCTURE_TYPE_METAL_SURFACE_CREATE_INFO_EXT;
createInfo.pView = window_handle; createInfo.pLayer = GetCAMetalLayerFromMetalView(window_handle);
CHECK_RESULT(vkCreateMacOSSurfaceMVK(vk_instance, &createInfo, NULL, &result)); CHECK_RESULT(vkCreateMetalSurfaceEXT(vk_instance, &createInfo, NULL, &result));
return result; return result;
} }
#endif #endif

View File

@ -1866,8 +1866,8 @@ game_boot_result Emulator::Load(const std::string& title_id, bool is_disc_patch,
// PS1 Classic located in dev_hdd0/game // PS1 Classic located in dev_hdd0/game
sys_log.notice("PS1 Game: %s, %s", m_title_id, m_title); sys_log.notice("PS1 Game: %s, %s", m_title_id, m_title);
const std::string tail = m_path.substr(hdd0_game.size()); const std::string_view tail = std::string_view(m_path).substr(hdd0_game.size());
const std::string dirname = fmt::trim_front(tail, fs::delim).substr(0, tail.find_first_of(fs::delim)); const std::string dirname = std::string(fmt::trim_front_sv(tail, fs::delim).substr(0, tail.find_first_of(fs::delim)));
const std::string game_path = "/dev_hdd0/game/" + dirname; const std::string game_path = "/dev_hdd0/game/" + dirname;
argv.resize(9); argv.resize(9);
@ -1894,8 +1894,8 @@ game_boot_result Emulator::Load(const std::string& title_id, bool is_disc_patch,
// PSP Remaster located in dev_hdd0/game // PSP Remaster located in dev_hdd0/game
sys_log.notice("PSP Remaster Game: %s, %s", m_title_id, m_title); sys_log.notice("PSP Remaster Game: %s, %s", m_title_id, m_title);
const std::string tail = m_path.substr(hdd0_game.size()); const std::string_view tail = std::string_view(m_path).substr(hdd0_game.size());
const std::string dirname = fmt::trim_front(tail, fs::delim).substr(0, tail.find_first_of(fs::delim)); const std::string dirname = std::string(fmt::trim_front_sv(tail, fs::delim).substr(0, tail.find_first_of(fs::delim)));
const std::string game_path = "/dev_hdd0/game/" + dirname; const std::string game_path = "/dev_hdd0/game/" + dirname;
argv.resize(2); argv.resize(2);
@ -1913,7 +1913,7 @@ game_boot_result Emulator::Load(const std::string& title_id, bool is_disc_patch,
// Add HG games not in HDD0 to games.yml // Add HG games not in HDD0 to games.yml
[[maybe_unused]] const games_config::result res = m_games_config.add_external_hdd_game(m_title_id, game_dir); [[maybe_unused]] const games_config::result res = m_games_config.add_external_hdd_game(m_title_id, game_dir);
const std::string dir = fmt::trim(game_dir.substr(fs::get_parent_dir_view(game_dir).size() + 1), fs::delim); const std::string dir = std::string(fmt::trim_sv(std::string_view(game_dir).substr(fs::get_parent_dir_view(game_dir).size() + 1), fs::delim));
vfs::mount("/dev_hdd0/game/" + dir, game_dir + '/'); vfs::mount("/dev_hdd0/game/" + dir, game_dir + '/');
} }
} }
@ -2265,10 +2265,10 @@ game_boot_result Emulator::Load(const std::string& title_id, bool is_disc_patch,
auto unescape = [](std::string_view path) auto unescape = [](std::string_view path)
{ {
// Unescape from host FS // Unescape from host FS
std::vector<std::string> escaped = fmt::split(path, {std::string_view{&fs::delim[0], 1}, std::string_view{&fs::delim[1], 1}}); const std::vector<std::string_view> escaped = fmt::split_sv(path, {std::string_view{&fs::delim[0], 1}, std::string_view{&fs::delim[1], 1}});
std::vector<std::string> result; std::vector<std::string> result;
for (auto& sv : escaped) for (const auto& sv : escaped)
result.emplace_back(vfs::unescape(sv)); result.push_back(vfs::unescape(sv));
return fmt::merge(result, "/"); return fmt::merge(result, "/");
}; };
@ -2315,7 +2315,7 @@ game_boot_result Emulator::Load(const std::string& title_id, bool is_disc_patch,
game_dir = game_dir.substr(0, game_dir.size() - 4); game_dir = game_dir.substr(0, game_dir.size() - 4);
} }
const std::string dir = fmt::trim(game_dir.substr(fs::get_parent_dir_view(game_dir).size() + 1), fs::delim); const std::string dir = std::string(fmt::trim_sv(std::string_view(game_dir).substr(fs::get_parent_dir_view(game_dir).size() + 1), fs::delim));
m_dir = "/dev_hdd0/game/" + dir + '/'; m_dir = "/dev_hdd0/game/" + dir + '/';
argv[0] = m_dir + unescape(resolved_path.substr(GetCallbacks().resolve_path(game_dir).size())); argv[0] = m_dir + unescape(resolved_path.substr(GetCallbacks().resolve_path(game_dir).size()));

View File

@ -137,7 +137,7 @@ bool vfs::unmount(std::string_view vpath)
return false; return false;
} }
const std::vector<std::string> entry_list = fmt::split(vpath, {"/"}); const std::vector<std::string_view> entry_list = fmt::split_sv(vpath, {"/"});
if (entry_list.empty()) if (entry_list.empty())
{ {
@ -166,7 +166,7 @@ bool vfs::unmount(std::string_view vpath)
} }
// Get the current name based on the depth // Get the current name based on the depth
const std::string& name = ::at32(entry_list, depth); const std::string_view name = ::at32(entry_list, depth);
// Go through all children of this node // Go through all children of this node
for (auto it = dir.dirs.begin(); it != dir.dirs.end();) for (auto it = dir.dirs.begin(); it != dir.dirs.end();)
@ -456,10 +456,10 @@ std::string vfs::retrieve(std::string_view path, const vfs_directory* node, std:
auto unescape_path = [](std::string_view path) auto unescape_path = [](std::string_view path)
{ {
// Unescape from host FS // Unescape from host FS
std::vector<std::string> escaped = fmt::split(path, {std::string_view{&fs::delim[0], 1}, std::string_view{&fs::delim[1], 1}}); const std::vector<std::string_view> escaped = fmt::split_sv(path, {std::string_view{&fs::delim[0], 1}, std::string_view{&fs::delim[1], 1}});
std::vector<std::string> result; std::vector<std::string> result;
for (auto& sv : escaped) for (const auto& sv : escaped)
result.emplace_back(vfs::unescape(sv)); result.push_back(vfs::unescape(sv));
return fmt::merge(result, "/"); return fmt::merge(result, "/");
}; };

View File

@ -184,6 +184,11 @@ namespace rpcs3::utils
return g_cfg_vfs.get(g_cfg_vfs.dev_bdvd, get_emu_dir()); return g_cfg_vfs.get(g_cfg_vfs.dev_bdvd, get_emu_dir());
} }
std::string get_hdd0_game_dir()
{
return get_hdd0_dir() + "game/";
}
u64 get_cache_disk_usage() u64 get_cache_disk_usage()
{ {
if (const u64 data_size = fs::get_dir_size(rpcs3::utils::get_cache_dir(), 1); data_size != umax) if (const u64 data_size = fs::get_dir_size(rpcs3::utils::get_cache_dir(), 1); data_size != umax)

View File

@ -34,6 +34,8 @@ namespace rpcs3::utils
std::string get_flash3_dir(); std::string get_flash3_dir();
std::string get_bdvd_dir(); std::string get_bdvd_dir();
std::string get_hdd0_game_dir();
// Cache directories and disk usage // Cache directories and disk usage
u64 get_cache_disk_usage(); u64 get_cache_disk_usage();
std::string get_cache_dir(); std::string get_cache_dir();

View File

@ -1346,7 +1346,7 @@ bool evdev_joystick_handler::bindPadToDevice(std::shared_ptr<Pad> pad)
const auto find_buttons = [&](const cfg::string& name) -> std::set<u32> const auto find_buttons = [&](const cfg::string& name) -> std::set<u32>
{ {
const std::vector<std::string> names = cfg_pad::get_buttons(name); const std::vector<std::string> names = cfg_pad::get_buttons(name.to_string());
// In evdev we store indices to an EvdevButton vector in our pad objects instead of the usual key codes. // In evdev we store indices to an EvdevButton vector in our pad objects instead of the usual key codes.
std::set<u32> indices; std::set<u32> indices;

View File

@ -835,7 +835,7 @@ std::string keyboard_pad_handler::GetKeyName(const u32& keyCode)
std::set<u32> keyboard_pad_handler::GetKeyCodes(const cfg::string& cfg_string) std::set<u32> keyboard_pad_handler::GetKeyCodes(const cfg::string& cfg_string)
{ {
std::set<u32> key_codes; std::set<u32> key_codes;
for (const std::string& key_name : cfg_pad::get_buttons(cfg_string)) for (const std::string& key_name : cfg_pad::get_buttons(cfg_string.to_string()))
{ {
if (u32 code = GetKeyCode(QString::fromStdString(key_name)); code != Qt::NoButton) if (u32 code = GetKeyCode(QString::fromStdString(key_name)); code != Qt::NoButton)
{ {

View File

@ -148,7 +148,7 @@ std::vector<pad_list_entry> mm_joystick_handler::list_devices()
template <typename T> template <typename T>
std::set<T> mm_joystick_handler::find_keys(const cfg::string& cfg_string) const std::set<T> mm_joystick_handler::find_keys(const cfg::string& cfg_string) const
{ {
return find_keys<T>(cfg_pad::get_buttons(cfg_string)); return find_keys<T>(cfg_pad::get_buttons(cfg_string.to_string()));
} }
template <typename T> template <typename T>

View File

@ -176,6 +176,9 @@ void ps_move_handler::init_config(cfg_pad* cfg)
cfg->ltriggerthreshold.def = 0; // between 0 and 255 cfg->ltriggerthreshold.def = 0; // between 0 and 255
cfg->rtriggerthreshold.def = 0; // between 0 and 255 cfg->rtriggerthreshold.def = 0; // between 0 and 255
// We have to enable orientation by default
cfg->orientation_enabled.def = true;
// apply defaults // apply defaults
cfg->from_default(); cfg->from_default();
} }
@ -608,7 +611,17 @@ std::unordered_map<u64, u16> ps_move_handler::get_button_values(const std::share
const u16 extra_buttons = input.sequence_number << 8 | input.buttons_3; const u16 extra_buttons = input.sequence_number << 8 | input.buttons_3;
key_buf[ps_move_key_codes::ps] = (extra_buttons & button_flags::ps) ? 255 : 0; key_buf[ps_move_key_codes::ps] = (extra_buttons & button_flags::ps) ? 255 : 0;
key_buf[ps_move_key_codes::move] = (extra_buttons & button_flags::move) ? 255 : 0; key_buf[ps_move_key_codes::move] = (extra_buttons & button_flags::move) ? 255 : 0;
key_buf[ps_move_key_codes::t] = (extra_buttons & button_flags::t) ? input.trigger_2 : 0;
u16 trigger = 0;
if (extra_buttons & button_flags::t)
{
switch (dev->model)
{
case ps_move_model::ZCM1: trigger = (input.trigger_1 + input.trigger_2) / 2; break;
case ps_move_model::ZCM2: trigger = input.trigger_1; break;
}
}
key_buf[ps_move_key_codes::t] = trigger;
dev->battery_level = input.battery_level; dev->battery_level = input.battery_level;
@ -675,12 +688,20 @@ void ps_move_handler::get_extended_info(const pad_ensemble& binding)
if (dev->model == ps_move_model::ZCM1) if (dev->model == ps_move_model::ZCM1)
{ {
accel_x -= static_cast<f32>(zero_shift); const auto decode_16bit = [](s16 val)
accel_y -= static_cast<f32>(zero_shift); {
accel_z -= static_cast<f32>(zero_shift); const u8* data = reinterpret_cast<const u8*>(&val);
gyro_x -= static_cast<f32>(zero_shift); const u8 low = data[0] & 0xFF;
gyro_y -= static_cast<f32>(zero_shift); const u8 high = data[1] & 0xFF;
gyro_z -= static_cast<f32>(zero_shift); const s32 res = (low | (high << 8)) - zero_shift;
return static_cast<f32>(res);
};
accel_x = decode_16bit(input.accel_x_1);
accel_y = decode_16bit(input.accel_y_1);
accel_z = decode_16bit(input.accel_z_1);
gyro_x = decode_16bit(input.gyro_x_1);
gyro_y = decode_16bit(input.gyro_y_1);
gyro_z = decode_16bit(input.gyro_z_1);
} }
if (!device->config || !device->config->orientation_enabled) if (!device->config || !device->config->orientation_enabled)
@ -711,21 +732,21 @@ void ps_move_handler::get_extended_info(const pad_ensemble& binding)
gyro_z /= MOVE_ONE_G; gyro_z /= MOVE_ONE_G;
} }
pad->move_data.accelerometer_x = accel_x; pad->move_data.accelerometer.x() = accel_x;
pad->move_data.accelerometer_y = accel_y; pad->move_data.accelerometer.y() = accel_y;
pad->move_data.accelerometer_z = accel_z; pad->move_data.accelerometer.z() = accel_z;
pad->move_data.gyro_x = gyro_x; pad->move_data.gyro.x() = gyro_x;
pad->move_data.gyro_y = gyro_y; pad->move_data.gyro.y() = gyro_y;
pad->move_data.gyro_z = gyro_z; pad->move_data.gyro.z() = gyro_z;
if (dev->model == ps_move_model::ZCM1) if (dev->model == ps_move_model::ZCM1)
{ {
const ps_move_input_report_ZCM1& input_zcm1 = dev->input_report_ZCM1; const ps_move_input_report_ZCM1& input_zcm1 = dev->input_report_ZCM1;
#define TWELVE_BIT_SIGNED(x) (((x) & 0x800) ? (-(((~(x)) & 0xFFF) + 1)) : (x)) #define TWELVE_BIT_SIGNED(x) (((x) & 0x800) ? (-(((~(x)) & 0xFFF) + 1)) : (x))
pad->move_data.magnetometer_x = static_cast<f32>(TWELVE_BIT_SIGNED(((input.magnetometer_x & 0x0F) << 8) | input_zcm1.magnetometer_x2)); pad->move_data.magnetometer.x() = static_cast<f32>(TWELVE_BIT_SIGNED(((input.magnetometer_x & 0x0F) << 8) | input_zcm1.magnetometer_x2));
pad->move_data.magnetometer_y = static_cast<f32>(TWELVE_BIT_SIGNED((input_zcm1.magnetometer_y << 4) | (input_zcm1.magnetometer_yz & 0xF0) >> 4)); pad->move_data.magnetometer.y() = static_cast<f32>(TWELVE_BIT_SIGNED((input_zcm1.magnetometer_y << 4) | (input_zcm1.magnetometer_yz & 0xF0) >> 4));
pad->move_data.magnetometer_z = static_cast<f32>(TWELVE_BIT_SIGNED(((input_zcm1.magnetometer_yz & 0x0F) << 8) | input_zcm1.magnetometer_z)); pad->move_data.magnetometer.z() = static_cast<f32>(TWELVE_BIT_SIGNED(((input_zcm1.magnetometer_yz & 0x0F) << 8) | input_zcm1.magnetometer_z));
} }
} }

View File

@ -13,14 +13,21 @@ LOG_CHANNEL(ps_move);
namespace gem namespace gem
{ {
extern bool convert_image_format(CellCameraFormat input_format, CellGemVideoConvertFormatEnum output_format, extern bool convert_image_format(CellCameraFormat input_format, const CellGemVideoConvertAttribute& vc,
const std::vector<u8>& video_data_in, u32 width, u32 height, const std::vector<u8>& video_data_in, u32 width, u32 height,
u8* video_data_out, u32 video_data_out_size, std::string_view caller); u8* video_data_out, u32 video_data_out_size, u8* buffer_memory,
std::string_view caller);
} }
template <bool DiagnosticsEnabled> template <bool DiagnosticsEnabled>
ps_move_tracker<DiagnosticsEnabled>::ps_move_tracker() ps_move_tracker<DiagnosticsEnabled>::ps_move_tracker()
{ {
m_vc_attr.alpha = 255;
m_vc_attr.gain = 1.0f;
m_vc_attr.red_gain = 1.0f;
m_vc_attr.green_gain = 1.0f;
m_vc_attr.blue_gain = 1.0f;
init_workers(); init_workers();
} }
@ -238,7 +245,9 @@ void ps_move_tracker<DiagnosticsEnabled>::convert_image(s32 output_format)
m_image_binary[index].resize(size); m_image_binary[index].resize(size);
} }
if (gem::convert_image_format(CellCameraFormat{m_format}, CellGemVideoConvertFormatEnum{output_format}, m_image_data, width, height, m_image_rgba.data(), ::size32(m_image_rgba), "gemTracker")) m_vc_attr.output_format = CellGemVideoConvertFormatEnum{output_format};
if (gem::convert_image_format(CellCameraFormat{m_format}, m_vc_attr, m_image_data, width, height, m_image_rgba.data(), ::size32(m_image_rgba), nullptr, "gemTracker"))
{ {
ps_move.trace("Converted video frame of format %s to %s", CellCameraFormat{m_format}, CellGemVideoConvertFormatEnum{output_format}); ps_move.trace("Converted video frame of format %s to %s", CellCameraFormat{m_format}, CellGemVideoConvertFormatEnum{output_format});
} }

View File

@ -77,6 +77,8 @@ private:
void draw_sphere_size_range(f32 result_radius); void draw_sphere_size_range(f32 result_radius);
CellGemVideoConvertAttribute m_vc_attr {};
u32 m_width = 0; u32 m_width = 0;
u32 m_height = 0; u32 m_height = 0;
s32 m_format = 0; s32 m_format = 0;

View File

@ -96,7 +96,7 @@ namespace disc
for (usz i = 0; i < lines.size(); i++) for (usz i = 0; i < lines.size(); i++)
{ {
const std::string& line = lines[i]; const std::string_view line = lines[i];
const usz pos = line.find('='); const usz pos = line.find('=');
if (pos == umax) if (pos == umax)
@ -104,12 +104,12 @@ namespace disc
continue; continue;
} }
const std::string key = fmt::trim(line.substr(0, pos)); const std::string_view key = fmt::trim_sv(line.substr(0, pos));
std::string value; std::string_view value;
if (pos != (line.size() - 1)) if (pos != (line.size() - 1))
{ {
value = fmt::trim(line.substr(pos + 1)); value = fmt::trim_sv(line.substr(pos + 1));
} }
if (value.empty() && i != (lines.size() - 1) && line.size() != 1) if (value.empty() && i != (lines.size() - 1) && line.size() != 1)

View File

@ -91,6 +91,7 @@
<ClCompile Include="Emu\Io\midi_config_types.cpp" /> <ClCompile Include="Emu\Io\midi_config_types.cpp" />
<ClCompile Include="Emu\Io\MouseHandler.cpp" /> <ClCompile Include="Emu\Io\MouseHandler.cpp" />
<ClCompile Include="Emu\Io\pad_types.cpp" /> <ClCompile Include="Emu\Io\pad_types.cpp" />
<ClCompile Include="Emu\Io\ps_move_data.cpp" />
<ClCompile Include="Emu\Io\rb3drums_config.cpp" /> <ClCompile Include="Emu\Io\rb3drums_config.cpp" />
<ClCompile Include="Emu\Io\RB3MidiDrums.cpp" /> <ClCompile Include="Emu\Io\RB3MidiDrums.cpp" />
<ClCompile Include="Emu\Io\RB3MidiGuitar.cpp" /> <ClCompile Include="Emu\Io\RB3MidiGuitar.cpp" />
@ -156,8 +157,11 @@
<ClCompile Include="Emu\RSX\Overlays\Shaders\shader_loading_dialog.cpp" /> <ClCompile Include="Emu\RSX\Overlays\Shaders\shader_loading_dialog.cpp" />
<ClCompile Include="Emu\RSX\Overlays\Shaders\shader_loading_dialog_native.cpp" /> <ClCompile Include="Emu\RSX\Overlays\Shaders\shader_loading_dialog_native.cpp" />
<ClCompile Include="Emu\RSX\Overlays\Trophies\overlay_trophy_list_dialog.cpp" /> <ClCompile Include="Emu\RSX\Overlays\Trophies\overlay_trophy_list_dialog.cpp" />
<ClCompile Include="Emu\RSX\Program\Assembler\FPASM.cpp" />
<ClCompile Include="Emu\RSX\Program\Assembler\FPOpcodes.cpp" />
<ClCompile Include="Emu\RSX\Program\Assembler\FPToCFG.cpp" /> <ClCompile Include="Emu\RSX\Program\Assembler\FPToCFG.cpp" />
<ClCompile Include="Emu\RSX\Program\FragmentProgramRegister.cpp" /> <ClCompile Include="Emu\RSX\Program\Assembler\Passes\FP\RegisterAnnotationPass.cpp" />
<ClCompile Include="Emu\RSX\Program\Assembler\Passes\FP\RegisterDependencyPass.cpp" />
<ClCompile Include="Emu\RSX\Program\ProgramStateCache.cpp" /> <ClCompile Include="Emu\RSX\Program\ProgramStateCache.cpp" />
<ClCompile Include="Emu\RSX\Program\program_util.cpp" /> <ClCompile Include="Emu\RSX\Program\program_util.cpp" />
<ClCompile Include="Emu\RSX\Program\SPIRVCommon.cpp" /> <ClCompile Include="Emu\RSX\Program\SPIRVCommon.cpp" />
@ -601,6 +605,7 @@
<ClInclude Include="Emu\Io\pad_config.h" /> <ClInclude Include="Emu\Io\pad_config.h" />
<ClInclude Include="Emu\Io\pad_config_types.h" /> <ClInclude Include="Emu\Io\pad_config_types.h" />
<ClInclude Include="Emu\Io\pad_types.h" /> <ClInclude Include="Emu\Io\pad_types.h" />
<ClInclude Include="Emu\Io\ps_move_data.h" />
<ClInclude Include="Emu\Io\rb3drums_config.h" /> <ClInclude Include="Emu\Io\rb3drums_config.h" />
<ClInclude Include="Emu\Io\RB3MidiDrums.h" /> <ClInclude Include="Emu\Io\RB3MidiDrums.h" />
<ClInclude Include="Emu\Io\RB3MidiGuitar.h" /> <ClInclude Include="Emu\Io\RB3MidiGuitar.h" />
@ -701,8 +706,11 @@
<ClInclude Include="Emu\RSX\Overlays\overlay_video.h" /> <ClInclude Include="Emu\RSX\Overlays\overlay_video.h" />
<ClInclude Include="Emu\RSX\Overlays\Trophies\overlay_trophy_list_dialog.h" /> <ClInclude Include="Emu\RSX\Overlays\Trophies\overlay_trophy_list_dialog.h" />
<ClInclude Include="Emu\RSX\Program\Assembler\CFG.h" /> <ClInclude Include="Emu\RSX\Program\Assembler\CFG.h" />
<ClInclude Include="Emu\RSX\Program\Assembler\FPASM.h" />
<ClInclude Include="Emu\RSX\Program\Assembler\FPOpcodes.h" />
<ClInclude Include="Emu\RSX\Program\Assembler\IR.h" /> <ClInclude Include="Emu\RSX\Program\Assembler\IR.h" />
<ClInclude Include="Emu\RSX\Program\FragmentProgramRegister.h" /> <ClInclude Include="Emu\RSX\Program\Assembler\Passes\FP\RegisterAnnotationPass.h" />
<ClInclude Include="Emu\RSX\Program\Assembler\Passes\FP\RegisterDependencyPass.h" />
<ClInclude Include="Emu\RSX\Program\GLSLTypes.h" /> <ClInclude Include="Emu\RSX\Program\GLSLTypes.h" />
<ClInclude Include="Emu\RSX\Program\ProgramStateCache.h" /> <ClInclude Include="Emu\RSX\Program\ProgramStateCache.h" />
<ClInclude Include="Emu\RSX\Program\program_util.h" /> <ClInclude Include="Emu\RSX\Program\program_util.h" />

View File

@ -136,6 +136,12 @@
<Filter Include="Emu\GPU\RSX\Program\Assembler"> <Filter Include="Emu\GPU\RSX\Program\Assembler">
<UniqueIdentifier>{d99df916-8a99-428b-869a-9f14ac0ab411}</UniqueIdentifier> <UniqueIdentifier>{d99df916-8a99-428b-869a-9f14ac0ab411}</UniqueIdentifier>
</Filter> </Filter>
<Filter Include="Emu\GPU\RSX\Program\Assembler\Passes">
<UniqueIdentifier>{d13db076-47e4-45b9-bb8a-6b711ea40622}</UniqueIdentifier>
</Filter>
<Filter Include="Emu\GPU\RSX\Program\Assembler\Passes\FP">
<UniqueIdentifier>{7fb59544-9761-4b4a-bb04-07deb43cf3c2}</UniqueIdentifier>
</Filter>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClCompile Include="Crypto\aes.cpp"> <ClCompile Include="Crypto\aes.cpp">
@ -1354,9 +1360,6 @@
<ClCompile Include="Emu\Cell\ErrorCodes.cpp"> <ClCompile Include="Emu\Cell\ErrorCodes.cpp">
<Filter>Emu\Cell</Filter> <Filter>Emu\Cell</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="Emu\RSX\Program\FragmentProgramRegister.cpp">
<Filter>Emu\GPU\RSX\Program</Filter>
</ClCompile>
<ClCompile Include="util\emu_utils.cpp"> <ClCompile Include="util\emu_utils.cpp">
<Filter>Utilities</Filter> <Filter>Utilities</Filter>
</ClCompile> </ClCompile>
@ -1378,6 +1381,21 @@
<ClCompile Include="Emu\RSX\Program\Assembler\FPToCFG.cpp"> <ClCompile Include="Emu\RSX\Program\Assembler\FPToCFG.cpp">
<Filter>Emu\GPU\RSX\Program\Assembler</Filter> <Filter>Emu\GPU\RSX\Program\Assembler</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="Emu\RSX\Program\Assembler\Passes\FP\RegisterAnnotationPass.cpp">
<Filter>Emu\GPU\RSX\Program\Assembler\Passes\FP</Filter>
</ClCompile>
<ClCompile Include="Emu\RSX\Program\Assembler\Passes\FP\RegisterDependencyPass.cpp">
<Filter>Emu\GPU\RSX\Program\Assembler\Passes\FP</Filter>
</ClCompile>
<ClCompile Include="Emu\RSX\Program\Assembler\FPOpcodes.cpp">
<Filter>Emu\GPU\RSX\Program\Assembler</Filter>
</ClCompile>
<ClCompile Include="Emu\RSX\Program\Assembler\FPASM.cpp">
<Filter>Emu\GPU\RSX\Program\Assembler</Filter>
</ClCompile>
<ClCompile Include="Emu\Io\ps_move_data.cpp">
<Filter>Emu\Io</Filter>
</ClCompile>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClInclude Include="Crypto\aes.h"> <ClInclude Include="Crypto\aes.h">
@ -2746,9 +2764,6 @@
<ClInclude Include="Emu\Audio\audio_utils.h"> <ClInclude Include="Emu\Audio\audio_utils.h">
<Filter>Emu\Audio</Filter> <Filter>Emu\Audio</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="Emu\RSX\Program\FragmentProgramRegister.h">
<Filter>Emu\GPU\RSX\Program</Filter>
</ClInclude>
<ClInclude Include="util\video_source.h"> <ClInclude Include="util\video_source.h">
<Filter>Utilities</Filter> <Filter>Utilities</Filter>
</ClInclude> </ClInclude>
@ -2776,6 +2791,21 @@
<ClInclude Include="Emu\RSX\Program\Assembler\IR.h"> <ClInclude Include="Emu\RSX\Program\Assembler\IR.h">
<Filter>Emu\GPU\RSX\Program\Assembler</Filter> <Filter>Emu\GPU\RSX\Program\Assembler</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="Emu\RSX\Program\Assembler\FPOpcodes.h">
<Filter>Emu\GPU\RSX\Program\Assembler</Filter>
</ClInclude>
<ClInclude Include="Emu\RSX\Program\Assembler\Passes\FP\RegisterAnnotationPass.h">
<Filter>Emu\GPU\RSX\Program\Assembler\Passes\FP</Filter>
</ClInclude>
<ClInclude Include="Emu\RSX\Program\Assembler\Passes\FP\RegisterDependencyPass.h">
<Filter>Emu\GPU\RSX\Program\Assembler\Passes\FP</Filter>
</ClInclude>
<ClInclude Include="Emu\RSX\Program\Assembler\FPASM.h">
<Filter>Emu\GPU\RSX\Program\Assembler</Filter>
</ClInclude>
<ClInclude Include="Emu\Io\ps_move_data.h">
<Filter>Emu\Io</Filter>
</ClInclude>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="Emu\RSX\Program\GLSLSnippets\GPUDeswizzle.glsl"> <None Include="Emu\RSX\Program\GLSLSnippets\GPUDeswizzle.glsl">

View File

@ -17,11 +17,11 @@ namespace rpcs3
std::pair<std::string, std::string> get_commit_and_hash() std::pair<std::string, std::string> get_commit_and_hash()
{ {
const auto commit_and_hash = fmt::split(RPCS3_GIT_VERSION, {"-"}); auto commit_and_hash = fmt::split(RPCS3_GIT_VERSION, {"-"});
if (commit_and_hash.size() != 2) if (commit_and_hash.size() != 2)
return std::make_pair("0", "00000000"); return std::make_pair("0", "00000000");
return std::make_pair(commit_and_hash[0], commit_and_hash[1]); return std::make_pair(std::move(commit_and_hash[0]), std::move(commit_and_hash[1]));
} }
// TODO: Make this accessible from cmake and keep in sync with MACOSX_BUNDLE_BUNDLE_VERSION. // TODO: Make this accessible from cmake and keep in sync with MACOSX_BUNDLE_BUNDLE_VERSION.

View File

@ -70,8 +70,8 @@ camera_settings_dialog::camera_settings_dialog(QWidget* parent)
camera_log.notice("Found camera: '%s'", camera_info.description()); camera_log.notice("Found camera: '%s'", camera_info.description());
} }
connect(ui->combo_camera, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &camera_settings_dialog::handle_camera_change); connect(ui->combo_camera, &QComboBox::currentIndexChanged, this, &camera_settings_dialog::handle_camera_change);
connect(ui->combo_settings, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &camera_settings_dialog::handle_settings_change); connect(ui->combo_settings, &QComboBox::currentIndexChanged, this, &camera_settings_dialog::handle_settings_change);
connect(ui->buttonBox, &QDialogButtonBox::clicked, [this](QAbstractButton* button) connect(ui->buttonBox, &QDialogButtonBox::clicked, [this](QAbstractButton* button)
{ {
if (button == ui->buttonBox->button(QDialogButtonBox::Save)) if (button == ui->buttonBox->button(QDialogButtonBox::Save))

View File

@ -129,11 +129,11 @@ void cheat_engine::save() const
cheat_file.write(out.c_str(), out.size()); cheat_file.write(out.c_str(), out.size());
} }
void cheat_engine::import_cheats_from_str(const std::string& str_cheats) void cheat_engine::import_cheats_from_str(std::string_view str_cheats)
{ {
auto cheats_vec = fmt::split(str_cheats, {"^^^"}); const auto cheats_vec = fmt::split_sv(str_cheats, {"^^^"});
for (auto& cheat_line : cheats_vec) for (const auto& cheat_line : cheats_vec)
{ {
cheat_info new_cheat; cheat_info new_cheat;
if (new_cheat.from_str(cheat_line)) if (new_cheat.from_str(cheat_line))

View File

@ -25,7 +25,7 @@ public:
cheat_info* get(const std::string& game, const u32 offset); cheat_info* get(const std::string& game, const u32 offset);
bool erase(const std::string& game, const u32 offset); bool erase(const std::string& game, const u32 offset);
void import_cheats_from_str(const std::string& str_cheats); void import_cheats_from_str(std::string_view str_cheats);
std::string export_cheats_to_str() const; std::string export_cheats_to_str() const;
void save() const; void save() const;

View File

@ -126,7 +126,7 @@ debugger_frame::debugger_frame(std::shared_ptr<gui_settings> gui_settings, QWidg
m_regs->setLineWrapMode(QPlainTextEdit::NoWrap); m_regs->setLineWrapMode(QPlainTextEdit::NoWrap);
m_regs->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard); m_regs->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard);
m_regs->setContextMenuPolicy(Qt::CustomContextMenu); m_regs->setContextMenuPolicy(Qt::CustomContextMenu);
m_debugger_list->setFont(m_mono); m_debugger_list->setFont(m_mono);
m_misc_state->setFont(m_mono); m_misc_state->setFont(m_mono);
m_regs->setFont(m_mono); m_regs->setFont(m_mono);
@ -180,7 +180,7 @@ debugger_frame::debugger_frame(std::shared_ptr<gui_settings> gui_settings, QWidg
m_choice_units->clearFocus(); m_choice_units->clearFocus();
}); });
connect(m_choice_units, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [&](){ m_is_spu_disasm_mode = false; OnSelectUnit(); }); connect(m_choice_units, &QComboBox::currentIndexChanged, this, [&](){ m_is_spu_disasm_mode = false; OnSelectUnit(); });
connect(this, &QDockWidget::visibilityChanged, this, &debugger_frame::EnableUpdateTimer); connect(this, &QDockWidget::visibilityChanged, this, &debugger_frame::EnableUpdateTimer);
connect(m_debugger_list, &debugger_list::BreakpointRequested, m_breakpoint_list, &breakpoint_list::HandleBreakpointRequest); connect(m_debugger_list, &debugger_list::BreakpointRequested, m_breakpoint_list, &breakpoint_list::HandleBreakpointRequest);
@ -1082,7 +1082,7 @@ void debugger_frame::UpdateUnitList()
if (reselected_index != umax) if (reselected_index != umax)
{ {
// Include no-thread at index 0 // Include no-thread at index 0
m_choice_units->setCurrentIndex(::narrow<s32>(reselected_index + 1)); m_choice_units->setCurrentIndex(::narrow<s32>(reselected_index + 1));
} }
} }

View File

@ -433,7 +433,7 @@ minifig_creator_dialog::minifig_creator_dialog(QWidget* parent)
setLayout(vbox_panel); setLayout(vbox_panel);
connect(combo_figlist, QOverload<int>::of(&QComboBox::currentIndexChanged), [=](int index) connect(combo_figlist, &QComboBox::currentIndexChanged, [=](int index)
{ {
const u16 fig_info = combo_figlist->itemData(index).toUInt(); const u16 fig_info = combo_figlist->itemData(index).toUInt();
if (fig_info != 0xFFFF) if (fig_info != 0xFFFF)
@ -486,7 +486,7 @@ minifig_creator_dialog::minifig_creator_dialog(QWidget* parent)
connect(btn_cancel, &QAbstractButton::clicked, this, &QDialog::reject); connect(btn_cancel, &QAbstractButton::clicked, this, &QDialog::reject);
connect(co_compl, QOverload<const QString&>::of(&QCompleter::activated), [=](const QString& text) connect(co_compl, qOverload<const QString&>(&QCompleter::activated), [=](const QString& text)
{ {
combo_figlist->setCurrentIndex(combo_figlist->findText(text)); combo_figlist->setCurrentIndex(combo_figlist->findText(text));
}); });

View File

@ -375,7 +375,7 @@ void emu_settings::EnhanceComboBox(QComboBox* combobox, emu_settings_type type,
combobox->setCurrentIndex(index); combobox->setCurrentIndex(index);
connect(combobox, QOverload<int>::of(&QComboBox::currentIndexChanged), combobox, [this, is_ranged, combobox, type](int index) connect(combobox, &QComboBox::currentIndexChanged, combobox, [this, is_ranged, combobox, type](int index)
{ {
if (index < 0) return; if (index < 0) return;

View File

@ -302,7 +302,7 @@ void emulated_pad_settings_dialog::add_tabs(QTabWidget* tabs)
combo->setCurrentIndex(combo->findData(static_cast<int>(saved_btn_id))); combo->setCurrentIndex(combo->findData(static_cast<int>(saved_btn_id)));
connect(combo, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [this, player, id, combo](int index) connect(combo, &QComboBox::currentIndexChanged, this, [this, player, id, combo](int index)
{ {
if (index < 0 || !combo) if (index < 0 || !combo)
return; return;

View File

@ -272,6 +272,10 @@ compat::package_info game_compatibility::GetPkgInfo(const QString& pkg_path, gam
info.category = QString::fromStdString(std::string(psf::get_string(psf, "CATEGORY"))); info.category = QString::fromStdString(std::string(psf::get_string(psf, "CATEGORY")));
info.version = QString::fromStdString(std::string(psf::get_string(psf, "APP_VER"))); info.version = QString::fromStdString(std::string(psf::get_string(psf, "APP_VER")));
// Technically, there is no specific package's header info providing its installation size on disk.
// We use "data_size" header as an approximation (a bit larger) for this purpose
info.data_size = reader.get_header().data_size.value();
if (!info.category.isEmpty()) if (!info.category.isEmpty())
{ {
const Localized localized; const Localized localized;

View File

@ -1,5 +1,7 @@
#pragma once #pragma once
#include "util/types.hpp"
#include <memory> #include <memory>
#include <QJsonObject> #include <QJsonObject>
@ -108,6 +110,7 @@ namespace compat
QString version; // May be empty QString version; // May be empty
QString category; // HG, DG, GD etc. QString category; // HG, DG, GD etc.
QString local_cat; // Localized category QString local_cat; // Localized category
u64 data_size = 0; // Installation size
package_type type = package_type::other; // The type of package (Update, DLC or other) package_type type = package_type::other; // The type of package (Update, DLC or other)
}; };

View File

@ -185,13 +185,13 @@ game_list_frame::game_list_frame(std::shared_ptr<gui_settings> gui_settings, std
connect(m_game_list, &QTableWidget::customContextMenuRequested, this, &game_list_frame::ShowContextMenu); connect(m_game_list, &QTableWidget::customContextMenuRequested, this, &game_list_frame::ShowContextMenu);
connect(m_game_list, &QTableWidget::itemSelectionChanged, this, &game_list_frame::ItemSelectionChangedSlot); connect(m_game_list, &QTableWidget::itemSelectionChanged, this, &game_list_frame::ItemSelectionChangedSlot);
connect(m_game_list, &QTableWidget::itemDoubleClicked, this, QOverload<QTableWidgetItem*>::of(&game_list_frame::doubleClickedSlot)); connect(m_game_list, &QTableWidget::itemDoubleClicked, this, qOverload<QTableWidgetItem*>(&game_list_frame::doubleClickedSlot));
connect(m_game_list->horizontalHeader(), &QHeaderView::sectionClicked, this, &game_list_frame::OnColClicked); connect(m_game_list->horizontalHeader(), &QHeaderView::sectionClicked, this, &game_list_frame::OnColClicked);
connect(m_game_grid, &QWidget::customContextMenuRequested, this, &game_list_frame::ShowContextMenu); connect(m_game_grid, &QWidget::customContextMenuRequested, this, &game_list_frame::ShowContextMenu);
connect(m_game_grid, &game_list_grid::ItemSelectionChanged, this, &game_list_frame::NotifyGameSelection); connect(m_game_grid, &game_list_grid::ItemSelectionChanged, this, &game_list_frame::NotifyGameSelection);
connect(m_game_grid, &game_list_grid::ItemDoubleClicked, this, QOverload<const game_info&>::of(&game_list_frame::doubleClickedSlot)); connect(m_game_grid, &game_list_grid::ItemDoubleClicked, this, qOverload<const game_info&>(&game_list_frame::doubleClickedSlot));
connect(m_game_compat, &game_compatibility::DownloadStarted, this, [this]() connect(m_game_compat, &game_compatibility::DownloadStarted, this, [this]()
{ {
@ -1383,7 +1383,7 @@ void game_list_frame::ShowContextMenu(const QPoint& pos)
const std::string hdd1_cache_base_dir = rpcs3::utils::get_hdd1_dir() + "caches/"; const std::string hdd1_cache_base_dir = rpcs3::utils::get_hdd1_dir() + "caches/";
const bool has_hdd1_cache_dir = !GetDirListBySerial(hdd1_cache_base_dir, current_game.serial).empty(); const bool has_hdd1_cache_dir = !GetDirListBySerial(hdd1_cache_base_dir, current_game.serial).empty();
if (has_hdd1_cache_dir) if (has_hdd1_cache_dir)
{ {
QAction* remove_hdd1_cache = remove_menu->addAction(tr("&Remove HDD1 Cache")); QAction* remove_hdd1_cache = remove_menu->addAction(tr("&Remove HDD1 Cache"));
@ -1856,7 +1856,7 @@ void game_list_frame::ShowContextMenu(const QPoint& pos)
{ {
text += tr("\nCurrent free disk space: %0\n").arg(gui::utils::format_byte_size(stat.avail_free)); text += tr("\nCurrent free disk space: %0\n").arg(gui::utils::format_byte_size(stat.avail_free));
} }
if (has_data_dir) if (has_data_dir)
{ {
text += tr("\nPermanently remove %0 and selected (optional) contents from drive?\n").arg(is_disc_game ? tr("Game Data") : gameinfo->localized_category); text += tr("\nPermanently remove %0 and selected (optional) contents from drive?\n").arg(is_disc_game ? tr("Game Data") : gameinfo->localized_category);

View File

@ -448,7 +448,7 @@ figure_creator_dialog::figure_creator_dialog(QWidget* parent, u8 slot)
for (const auto& [figure, entry] : list_figures) for (const auto& [figure, entry] : list_figures)
{ {
const auto& [num, figure_name] = entry; const auto& [num, figure_name] = entry;
// Apply series filter (0 = all, 1-3 = specific series) // Apply series filter (0 = all, 1-3 = specific series)
if (series_filter != 0 && num != series_filter) if (series_filter != 0 && num != series_filter)
continue; continue;
@ -486,7 +486,7 @@ figure_creator_dialog::figure_creator_dialog(QWidget* parent, u8 slot)
co_compl->setFilterMode(Qt::MatchContains); co_compl->setFilterMode(Qt::MatchContains);
combo_figlist->setCompleter(co_compl); combo_figlist->setCompleter(co_compl);
connect(co_compl, QOverload<const QString&>::of(&QCompleter::activated), [=](const QString& text) connect(co_compl, qOverload<const QString&>(&QCompleter::activated), [=](const QString& text)
{ {
combo_figlist->setCurrentIndex(combo_figlist->findText(text)); combo_figlist->setCurrentIndex(combo_figlist->findText(text));
}); });
@ -536,12 +536,12 @@ figure_creator_dialog::figure_creator_dialog(QWidget* parent, u8 slot)
filter_group->addButton(btn_series2, 2); // ID 2 for series 2 filter_group->addButton(btn_series2, 2); // ID 2 for series 2
filter_group->addButton(btn_series3, 3); // ID 3 for series 3 filter_group->addButton(btn_series3, 3); // ID 3 for series 3
connect(filter_group, QOverload<int>::of(&QButtonGroup::idClicked), [=](int id) connect(filter_group, &QButtonGroup::idClicked, [=](int id)
{ {
populate_combo(id); populate_combo(id);
}); });
connect(combo_figlist, QOverload<int>::of(&QComboBox::currentIndexChanged), [=](int index) connect(combo_figlist, &QComboBox::currentIndexChanged, [=](int index)
{ {
const u32 fig_info = combo_figlist->itemData(index).toUInt(); const u32 fig_info = combo_figlist->itemData(index).toUInt();
if (fig_info != 0xFFFFFFFF) if (fig_info != 0xFFFFFFFF)

View File

@ -160,7 +160,7 @@ kamen_rider_creator_dialog::kamen_rider_creator_dialog(QWidget* parent)
setLayout(vbox_panel); setLayout(vbox_panel);
connect(combo_figlist, QOverload<int>::of(&QComboBox::currentIndexChanged), [=](int index) connect(combo_figlist, &QComboBox::currentIndexChanged, [=](int index)
{ {
const u16 fig_info = combo_figlist->itemData(index).toUInt(); const u16 fig_info = combo_figlist->itemData(index).toUInt();
if (fig_info != 0xFFFF) if (fig_info != 0xFFFF)
@ -243,7 +243,7 @@ kamen_rider_creator_dialog::kamen_rider_creator_dialog(QWidget* parent)
connect(btn_cancel, &QAbstractButton::clicked, this, &QDialog::reject); connect(btn_cancel, &QAbstractButton::clicked, this, &QDialog::reject);
connect(co_compl, QOverload<const QString&>::of(&QCompleter::activated), [=](const QString& text) connect(co_compl, qOverload<const QString&>(&QCompleter::activated), [=](const QString& text)
{ {
combo_figlist->setCurrentText(text); combo_figlist->setCurrentText(text);
combo_figlist->setCurrentIndex(combo_figlist->findText(text)); combo_figlist->setCurrentIndex(combo_figlist->findText(text));

View File

@ -860,11 +860,28 @@ bool main_window::InstallPackages(QStringList file_paths, bool from_boot)
info.changelog = tr("Changelog:\n%0", "Block for Changelog").arg(info.changelog); info.changelog = tr("Changelog:\n%0", "Block for Changelog").arg(info.changelog);
} }
const QString info_string = QStringLiteral("%0\n\n%1%2%3%4").arg(file_info.fileName()).arg(info.title).arg(info.local_cat).arg(info.title_id).arg(info.version); u64 free_space = 0;
QString message = tr("Do you want to install this package?\n\n%0").arg(info_string);
QMessageBox mb(QMessageBox::Icon::Question, tr("PKG Decrypter / Installer"), message, QMessageBox::Yes | QMessageBox::No, this); // Retrieve disk space info on data path's drive
if (fs::device_stat stat{}; fs::statfs(rpcs3::utils::get_hdd0_dir(), stat))
{
free_space = stat.avail_free;
}
const QString installation_info =
tr("Installation path: %0\nAvailable disk space: %1%2\nRequired disk space: %3")
.arg(rpcs3::utils::get_hdd0_game_dir())
.arg(gui::utils::format_byte_size(free_space))
.arg(info.data_size <= free_space ? QString() : tr(" - <b>NOT ENOUGH SPACE</b>"))
.arg(gui::utils::format_byte_size(info.data_size));
const QString info_string = QStringLiteral("%0\n\n%1%2%3%4").arg(file_info.fileName()).arg(info.title).arg(info.local_cat).arg(info.title_id).arg(info.version);
QString message = tr("Do you want to install this package?\n\n%0\n\n%1").arg(info_string).arg(installation_info);
QMessageBox mb(QMessageBox::Icon::Question, tr("PKG Decrypter / Installer"), gui::utils::make_paragraph(message), QMessageBox::Yes | QMessageBox::No, this);
mb.setDefaultButton(QMessageBox::No); mb.setDefaultButton(QMessageBox::No);
mb.setTextFormat(Qt::RichText); // Support HTML tags
mb.button(QMessageBox::Yes)->setEnabled(info.data_size <= free_space);
if (!info.changelog.isEmpty()) if (!info.changelog.isEmpty())
{ {

View File

@ -115,11 +115,7 @@ u64 memory_viewer_panel::OnSearch(std::string wstr, u32 mode)
} }
// Concat strings // Concat strings
wstr.clear(); wstr = fmt::merge(parts, {});
for (const std::string& part : parts)
{
wstr += part;
}
if (const usz pos = wstr.find_first_not_of(hex_chars); pos != umax) if (const usz pos = wstr.find_first_not_of(hex_chars); pos != umax)
{ {

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