Compare commits

...
Author SHA1 Message Date
nmzik 4b77686566 shader: V_CMP_NGT_F16 2026-09-17 16:56:32 +04:00
c0sxm0s a6144f794b launcher: report cheat file save failures (#669)
PatchesDialog::Save silently ignored QSaveFile failures, leaving the
status label showing the old "Loaded N cheat(s)" text so the user could
not tell that their selection was not saved. Report the failure in the
status label and the log instead.
2026-09-17 16:13:35 +04:00
Mininaut bab1c3614c libc: treat negative dynamic printf precision as omitted (#625) 2026-09-17 15:18:04 +04:00
c0sxm0s de1545e19a launcher: wire cancel->reject in C++; document glslang for Windows (#663)
uic from Qt 6.12.0 crashes (0xC0000005) on any non-empty <connections>
block in .ui files. Move the cancel_button -> reject connection from
configuration_edit_dialog.ui into configurationEditDialog.cpp, matching
how the other buttons are already connected there.

Also document the glslang (glslangValidator) requirement for Windows
builds; CMake requires it via find_program(... REQUIRED).
2026-09-17 15:13:20 +04:00
Komaryandnmzik ce629e0391 input: add gyroscope and motion sensor support (#634)
input: add controller motion sensor support

Co-authored-by: nmzik <Nmzik@mail.ru>
2026-09-17 12:28:12 +04:00
BlackCAT 6c03b4aaa5 vscode: point IntelliSense at clang instead of MSVC (#564)
* vscode: point IntelliSense at clang instead of MSVC

KYTY_SYSV_ABI expands to __attribute__((sysv_abi)) with no MSVC fallback,
which is correct for a project that rejects cl.exe at configure time. Nothing
told the C/C++ extension that, so it fell back to windows-msvc-x64 and
flagged every guest-facing function in the tree with "__attribute__ is not a
type name".

Set the IntelliSense mode, compiler path and C++ standard to match the
compiler the build actually uses, and export a compile database for it to
read.

* vscode: resolve the clang toolchain through the VS developer environment

The IntelliSense settings named a specific Visual Studio install, so the
compiler and Ninja only resolved on machines with VS 18 Community under
Program Files. Let CMake Tools source the developer environment instead,
which locates the toolchain through vswhere, and drop the explicit
compilerPath so cpptools takes the compiler from compile_commands.json.

Also point C_Cpp.default.compileCommands at the build directory this
workspace actually configures, and drop CMAKE_EXPORT_COMPILE_COMMANDS
because CMake Tools already exports the database by default.
2026-09-17 11:38:40 +04:00
nmzik 0627e23e92 coderabbit: make it quieter 2026-09-17 11:26:44 +04:00
4e6f4f99d3 loader: emulate immediate SSE4a instructions on macOS (#635)
* loader: emulate immediate SSE4a instructions on macOS

* tests: simplify macOS SSE4a test integration

Keep the Darwin target, CTest registration, and aggregate dependency in
one conditional block without a separate target list. Preserve the
standalone target when BUILD_TESTING is disabled.

Lowercase the test filename and diagnostic prefix, and drop unrelated
formatting changes from the instruction emulator.

Credit Brandon Strong's related SSE4a work in
https://github.com/KytyPS5/KytyPS5/pull/467.

Co-authored-by: Brandon Strong <brandostrong@gmail.com>

---------

Co-authored-by: nmzik <Nmzik@mail.ru>
Co-authored-by: Brandon Strong <brandostrong@gmail.com>
2026-09-17 11:23:50 +04:00
Elijaandnmzik f30afc184e shader: support S_ASHR_I64 (#658)
* shader: implement S_ASHR_I64 with compute coverage

* tests: use existing compute suite for S_ASHR_I64

---------

Co-authored-by: nmzik <Nmzik@mail.ru>
2026-09-17 10:48:05 +04:00
228cf3fbf4 Build the launcher on Qt below 6.6 and cap the reference-clock log (#653)
* Fix launcher build on Qt versions below 6.6

QVulkanInstance::NoPortabilityDrivers is only available since Qt 6.6, so
guard its use the same way as the Apple-specific case.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Stop logging every reference-clock copy

CopyData of the clock counter runs on every GPU wait, so the handler wrote
one line per wait: a single SpongeBob boot that never reaches a frame logs
1312 of them, and the count grows with run time. Keep the first 64 lines
for diagnostics; the copy itself is unchanged.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Remove reference-clock logging comment

---------

Co-authored-by: YuhiAida <21134775+YuhiAida@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: nmzik <Nmzik@mail.ru>
2026-09-17 10:30:47 +04:00
nmzik 47957faa16 Match stencil associations by guest range and extent
PPSA30279
2026-09-17 09:01:36 +04:00
nmzik 235e24d44f Recognize null-destination SWAPPC shader handoffs
Credits:
BlackCAT (@psnwd):
https://github.com/KytyPS5/KytyPS5/pull/613
YuhiAida (@YuhiAida):
https://github.com/KytyPS5/KytyPS5/pull/655
2026-09-17 08:45:21 +04:00
nmzik e0ecffed70 Approximate explicit-LOD texture gathers at mip zero
Credits:
YuhiAida (@YuhiAida):
https://github.com/KytyPS5/KytyPS5/pull/588
https://github.com/KytyPS5/KytyPS5/pull/589
https://github.com/KytyPS5/KytyPS5/pull/655
frans (@francoisatt):
https://github.com/KytyPS5/KytyPS5/pull/590
BlackCAT (@psnwd):
https://github.com/KytyPS5/KytyPS5/pull/613
shadowbeat070 (@shadowbeat070):
https://github.com/KytyPS5/KytyPS5/pull/654
Pouare514 (@Pouare514):
https://github.com/KytyPS5/KytyPS5/pull/29
https://github.com/KytyPS5/KytyPS5/pull/32
2026-09-17 08:39:57 +04:00
nmzik fa913ec1a7 Accept EXPCLEAR acceleration on expanded depth attachments 2026-09-17 08:25:55 +04:00
nmzik 49b2eedd1c Fix command buffer capacity calculation
Credits:
YuhiAida (@YuhiAida):
https://github.com/KytyPS5/KytyPS5/pull/588
https://github.com/KytyPS5/KytyPS5/pull/589
https://github.com/KytyPS5/KytyPS5/pull/652
Crimson (@Crimson3076):
https://github.com/KytyPS5/KytyPS5/pull/598
2026-09-17 08:16:59 +04:00
nmzik 63fb822f8a Warn and continue mesh draws with primitive restart enabled 2026-09-16 22:33:19 +04:00
nmzikandÇağatay Güçlü f38d738ee2 Honor PS5 texture minimum LOD for streamed mip chains
Fix broken ground textures in ASTRO BOT.

Related work: https://github.com/KytyPS5/KytyPS5/pull/558

Co-authored-by: Çağatay Güçlü <33038942+defektu@users.noreply.github.com>
2026-09-16 22:33:19 +04:00
nmzikandÇağatay Güçlü eda6ba37ec Json2: new ABIs
Related work: https://github.com/KytyPS5/KytyPS5/pull/558

Co-authored-by: Çağatay Güçlü <33038942+defektu@users.noreply.github.com>
2026-09-16 22:33:19 +04:00
nmzikandÇağatay Güçlü b594f8c834 Bind only exported color targets and preserve MRT slots
Related work: https://github.com/KytyPS5/KytyPS5/pull/558

Co-authored-by: Çağatay Güçlü <33038942+defektu@users.noreply.github.com>
2026-09-16 22:33:19 +04:00
nmzikandÇağatay Güçlü bc45aaf604 Track uniform sampler choices across both conditional arms
Related work: https://github.com/KytyPS5/KytyPS5/pull/558

Co-authored-by: Çağatay Güçlü <33038942+defektu@users.noreply.github.com>
2026-09-16 22:33:19 +04:00
nmzik fb94a819c7 Structure early returns with shared loop continuations 2026-09-16 22:33:19 +04:00
nmzikandÇağatay Güçlü 8589731a27 Preserve PS5 mip-tail views and GPU data during image expansion
Related work: https://github.com/KytyPS5/KytyPS5/pull/558

Co-authored-by: Çağatay Güçlü <33038942+defektu@users.noreply.github.com>
2026-09-16 22:33:19 +04:00
nmzik c1dccd7db1 Skip zero-sized compute dispatches before shader preparation 2026-09-16 22:33:19 +04:00
nmzikandÇağatay Güçlü af3a3f4bf6 Preserve uniform conditional sampler choices in resource plans
Related work: https://github.com/KytyPS5/KytyPS5/pull/558

Co-authored-by: Çağatay Güçlü <33038942+defektu@users.noreply.github.com>
2026-09-16 22:33:19 +04:00
nmzik 349fae00d9 Emulate register-form SSE4a EXTRQ 2026-09-16 22:33:19 +04:00
Fag Fag Fag Sahur 10cae50b34 Add Nix flake and development shell (#645) 2026-09-16 22:19:41 +04:00
nmzik 5b541375a3 shader: implement S_CBRANCH_CDBGSYS opcode 2026-09-16 17:04:48 +04:00
nmzik ded6964953 Use native TLS for fiber context across thread migration 2026-09-16 11:22:05 +02:00
nmzik 9b2111dc01 Bundle FFmpeg 2026-09-16 10:13:03 +02:00
nmzik 71f52f9d90 Simplify fiber ownership and preserve small guest stacks 2026-09-16 08:53:56 +02:00
nmzik 86e5e01403 Protect Windows guest red zones when emulating VRSQRTPS 2026-09-16 06:52:36 +02:00
nmzik 77680ce9d2 Make AMD CPU patch optional 2026-09-16 09:48:50 +04:00
nmzik 32e6a433e8 Fix Json2 integer reads 2026-09-16 09:21:44 +04:00
nmzik 7c9148dd47 Support triangle fans in PS5 geometry shaders
Keep the fan center across subgroups and reuse the existing index fetch.
2026-09-16 08:42:47 +04:00
nmzik 957452d6f9 Emulate VRSQRTPS instruction 2026-09-16 08:28:36 +04:00
nmzikandAdrian Endlicher e6156bef05 Cache CI builds with sccache
Co-authored-by: Adrian Endlicher <godlike93@gmail.com>
2026-09-15 22:16:54 +04:00
nmzik 441367b1a1 Fix pixel shader input aliases 2026-09-15 20:46:51 +04:00
nmzik 629ae1ac05 Handle Z-pass predication and query waits 2026-09-15 20:15:39 +04:00
nmzik 0d9e95d96a Fix centroid interpolation weights
PPSA03792
2026-09-15 19:51:30 +04:00
nmzik 9cf7963f44 Use system FFmpeg for VP9 playback on Linux 2026-09-15 19:30:28 +04:00
nmzik acd7435d31 Support byte destinations in V_MOV_B32 2026-09-15 18:25:20 +04:00
nmzik 7af28c267d Map Z/C to L2/R2 2026-09-15 15:20:53 +04:00
nmzik 79120d53ec Handle chained PM4 buffers without recursion 2026-09-15 14:43:15 +04:00
nmzik 6a2987a7b2 Log the game title id 2026-09-15 14:09:24 +04:00
nmzik d1372abf46 Add launcher microphone selection and SDL AudioIn capture 2026-09-15 11:24:05 +04:00
nmzik 98b5d88d67 Implement AudioIn HQ open and close with paced silent input 2026-09-15 11:24:05 +04:00
nmzik 86414760e6 Cull zero homogeneous vertex positions before rasterization
Fix Playroom top right artifact on Nvidia by clipping zero-position vertices.
2026-09-15 11:24:05 +04:00
nmzik a1fbc41146 Preserve shared scalar loads during embedded vertex fetch replacement 2026-09-15 11:24:05 +04:00
nmzik 5702cfc935 Fix transfer-save mounts to honor the source title and missing data 2026-09-15 11:24:05 +04:00
nmzik 7b5a33f878 Fix PS5 front-face VGPR encoding to restore scene lighting 2026-09-15 07:01:10 +04:00
nmzik ea092a9472 Fix DCC clears through aliased image formats
Clear DCC images using the requested view format.
UNORM alpha 0xffff reads as NaN in a FLOAT view.
2026-09-15 07:01:10 +04:00
nmzikandBipin 18a1e0fadc Remove global condition waiter registry that deadlocks exception delivery
Co-authored-by: Bipin <bipinkrish4@gmail.com>
2026-09-14 21:34:01 +04:00
nmzik 437e69ef1b Remove extra DCC tracking 2026-09-14 20:29:20 +04:00
nmzik dd408ffd00 Resolve image aliases before buffer uploads 2026-09-14 20:29:20 +04:00
nmzik bce8924dd6 Use dynamic stencil state 2026-09-14 20:29:20 +04:00
nmzik fe26c1de17 Share SPIR-V module creation 2026-09-14 20:29:20 +04:00
nmzik 01df42a5f3 Fix DCC clears after metadata writes 2026-09-14 20:29:20 +04:00
nmzik 968cf3c03f Use the correct pixel shader wave size 2026-09-14 20:29:20 +04:00
nmzik 586cbd4a0d Fix separate stencil test and write values 2026-09-14 20:29:20 +04:00
nmzik 940e42f130 Share stencil setup for both faces 2026-09-14 20:29:20 +04:00
nmzik 3f80151a6f Respect vertex buffer sizes 2026-09-14 20:29:20 +04:00
nmzik 7e89b97daf Handle front shader allocation state 2026-09-14 20:29:20 +04:00
nmzik d63fb62367 Fix async compute completion interrupts 2026-09-14 20:29:20 +04:00
nmzik 29e9ea6344 Fix masked stencil replacements 2026-09-14 20:29:20 +04:00
nmzik 9a74ef15c3 Move stencil checks into op conversion 2026-09-14 20:29:20 +04:00
106 changed files with 5893 additions and 1979 deletions

No files matched your search

+37
View File
@@ -0,0 +1,37 @@
# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json
# Keep inherited review profiles, automatic reviews, tools, and pre-merge checks.
inheritance: true
reviews:
# Trim supplemental output and keep the remaining walkthrough collapsed.
high_level_summary: false
review_status: false
review_details: false
collapse_walkthrough: true
changed_files_summary: false
sequence_diagrams: false
estimate_code_review_effort: false
related_issues: false
related_prs: false
in_progress_fortune: false
poem: false
enable_prompt_for_ai_agents: false
# Hide standard finishing-touch controls; this also disables their commands.
finishing_touches:
docstrings:
enabled: false
unit_tests:
enabled: false
simplify:
enabled: false
autofix:
enabled: false
fix_ci:
enabled: false
resolve_merge_conflict:
enabled: false
chat:
art: false
+21
View File
@@ -6,7 +6,12 @@ on:
branches: [ main, master ]
pull_request:
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
env:
SCCACHE_GHA_ENABLED: "true"
KYTY_BUILD_ORIGIN: ${{ github.repository != 'KytyPS5/KytyPS5' && 'Fork' || github.event_name == 'push' && 'Official' || 'Source' }}
KYTY_BUILD_REPOSITORY: ${{ github.repository }}
@@ -78,6 +83,9 @@ jobs:
arch: win64_msvc2022_64
cache: true
- name: Setup sccache
uses: mozilla-actions/sccache-action@v0.0.11
- name: Verify toolchain
shell: cmd
run: |
@@ -100,6 +108,8 @@ jobs:
-DKYTY_RELEASE_TAG=${{ needs.metadata.outputs.release_tag }} ^
-DCMAKE_C_COMPILER=clang-cl ^
-DCMAKE_CXX_COMPILER=clang-cl ^
-DCMAKE_C_COMPILER_LAUNCHER=sccache ^
-DCMAKE_CXX_COMPILER_LAUNCHER=sccache ^
-DCMAKE_PREFIX_PATH="%Qt6_DIR%"
- name: Build
@@ -156,6 +166,9 @@ jobs:
arch: clang_64
cache: true
- name: Setup sccache
uses: mozilla-actions/sccache-action@v0.0.11
- name: Verify toolchain
shell: bash
run: |
@@ -180,6 +193,8 @@ jobs:
-DCMAKE_OSX_ARCHITECTURES=x86_64 \
-DCMAKE_C_COMPILER=clang \
-DCMAKE_CXX_COMPILER=clang++ \
-DCMAKE_C_COMPILER_LAUNCHER=sccache \
-DCMAKE_CXX_COMPILER_LAUNCHER=sccache \
-DCMAKE_PREFIX_PATH="$QT_ROOT_DIR"
- name: Build
@@ -304,6 +319,7 @@ jobs:
libxss-dev \
lld \
ninja-build \
pkg-config \
wayland-protocols
- name: Install Qt 6.10.3
@@ -315,6 +331,9 @@ jobs:
arch: linux_gcc_64
cache: true
- name: Setup sccache
uses: mozilla-actions/sccache-action@v0.0.11
- name: Verify toolchain
shell: bash
run: |
@@ -339,6 +358,8 @@ jobs:
-DKYTY_RELEASE_TAG="${{ needs.metadata.outputs.release_tag }}" \
-DCMAKE_C_COMPILER=clang \
-DCMAKE_CXX_COMPILER=clang++ \
-DCMAKE_C_COMPILER_LAUNCHER=sccache \
-DCMAKE_CXX_COMPILER_LAUNCHER=sccache \
-DCMAKE_PREFIX_PATH="$Qt6_DIR" 2>&1 | tee _Build/configure.log
exit "${PIPESTATUS[0]}"
+1 -1
View File
@@ -22,7 +22,7 @@
url = https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator.git
[submodule "3rdparty/ffmpeg-core"]
path = 3rdparty/ffmpeg-core
url = https://github.com/shadps4-emu/ext-ffmpeg-core.git
url = https://github.com/KytyPS5/ext-ffmpeg-core.git
shallow = true
[submodule "3rdparty/fmt"]
path = 3rdparty/fmt
+6 -4
View File
@@ -2,15 +2,17 @@
"cmake.sourceDirectory": "${workspaceFolder}",
"cmake.buildDirectory": "${workspaceFolder}/_Build/vscode-clang",
"cmake.generator": "Ninja",
"cmake.useVsDeveloperEnvironment": "always",
"cmake.environment": {
"CC": "clang-cl",
"CXX": "clang-cl",
"PATH": "${env:ProgramFiles}/Microsoft Visual Studio/18/Community/VC/Tools/Llvm/x64/bin;${env:ProgramFiles}/Microsoft Visual Studio/18/Community/Common7/IDE/CommonExtensions/Microsoft/CMake/Ninja;${env:PATH}"
"CXX": "clang-cl"
},
"cmake.configureSettings": {
"CMAKE_C_COMPILER": "clang-cl",
"CMAKE_CXX_COMPILER": "clang-cl",
"CMAKE_PREFIX_PATH": "C:/Qt/6.11.0/msvc2022_64"
}
},
"C_Cpp.default.intelliSenseMode": "windows-clang-x64",
"C_Cpp.default.compileCommands": "${workspaceFolder}/_Build/vscode-clang/compile_commands.json",
"C_Cpp.default.cppStandard": "c++20"
}
+16 -15
View File
@@ -317,24 +317,10 @@ endif()
set(kyty_emulator_link_libraries common Vulkan::Headers spirv-tools-opt spirv-tools SDL2-static imgui xxhash FFmpeg::ffmpeg fmt::fmt nlohmann_json::nlohmann_json LibAtrac9 kyty_git_version Zydis::Zydis xbyak::xbyak)
# Linux system libraries required by the static FFmpeg archive.
# Linux system libraries.
if(LINUX)
find_package(Threads REQUIRED)
list(APPEND kyty_emulator_link_libraries m ${CMAKE_DL_LIBS} Threads::Threads)
# Optional FFmpeg dependencies.
find_package(ZLIB)
if(ZLIB_FOUND)
list(APPEND kyty_emulator_link_libraries ZLIB::ZLIB)
endif()
find_library(KYTY_BZ2_LIBRARY bz2)
if(KYTY_BZ2_LIBRARY)
list(APPEND kyty_emulator_link_libraries ${KYTY_BZ2_LIBRARY})
endif()
find_library(KYTY_LZMA_LIBRARY lzma)
if(KYTY_LZMA_LIBRARY)
list(APPEND kyty_emulator_link_libraries ${KYTY_LZMA_LIBRARY})
endif()
endif()
set(inc_headers
@@ -615,6 +601,21 @@ if(BUILD_TESTING)
)
endif()
# Test Darwin signal-context restoration for SSE4a instructions.
if(APPLE AND (CMAKE_OSX_ARCHITECTURES STREQUAL "x86_64" OR
(NOT CMAKE_OSX_ARCHITECTURES AND CMAKE_SYSTEM_PROCESSOR MATCHES "^(x86_64|AMD64)$")))
add_executable(macos_sse4a_tests EXCLUDE_FROM_ALL
"${KYTY_TESTS_DIR}/macosSse4aTests.cpp"
"${KYTY_SOURCE_DIR}/loader/x64InstructionEmulator.cpp"
)
target_link_libraries(macos_sse4a_tests common fmt::fmt Zydis::Zydis)
target_include_directories(macos_sse4a_tests PRIVATE ${inc_headers})
if(BUILD_TESTING)
add_test(NAME macos_sse4a COMMAND $<TARGET_FILE:macos_sse4a_tests>)
set_tests_properties(macos_sse4a PROPERTIES TIMEOUT 15)
add_dependencies(kyty_tests macos_sse4a_tests)
endif()
endif()
add_executable(kyty_emulator "${KYTY_SOURCE_DIR}/main.cpp" ${kyty_emulator_src})
configure_macos_guest_address_space(kyty_emulator)
+27 -2
View File
@@ -137,6 +137,7 @@ the Vulkan/SPIR-V validation rules.
- Visual Studio 2022 or Build Tools 2022 with the **Desktop development with C++** workload and
**C++ Clang tools for Windows** component
- Qt 6 for MSVC 2022 64-bit, including Concurrent, Network, and Widgets
- [glslang](https://github.com/KhronosGroup/glslang/releases) (`glslangValidator`) on `PATH`
The Microsoft C++ compiler (`cl.exe`) is not supported; use `clang-cl`.
@@ -171,7 +172,7 @@ build has no working sound and no gamepad hotplug:
```bash
sudo apt-get install --no-install-recommends \
clang lld ninja-build cmake git glslang-tools \
clang lld ninja-build cmake git glslang-tools pkg-config \
libgl1-mesa-dev libx11-dev libxcursor-dev libxext-dev libxfixes-dev \
libxi-dev libxrandr-dev libxss-dev libxkbcommon-dev \
libasound2-dev libpulse-dev libudev-dev libdbus-1-dev libwayland-dev wayland-protocols
@@ -192,13 +193,37 @@ cmake --install _Build/linux --prefix _Build/linux/install
```
The install step copies the Qt libraries and plugins next to the binaries, so
`_Build/linux/install` runs without a matching system Qt.
`_Build/linux/install` runs without a matching system Qt. FFmpeg is linked statically
from the pinned [KytyPS5 FFmpeg core](https://github.com/KytyPS5/ext-ffmpeg-core)
release, including VP9 and WebM support. System FFmpeg packages are not required.
As on Windows, the MSVC compiler is not used; Clang is required. `cl.exe` is rejected at configure
time.
The CMake source root is the repository root.
### Building on NixOS
A development shell provides Clang, CMake, Ninja, Qt 6, the Vulkan headers, and the SDL2 backend
libraries. Enter it and configure exactly as on other Linux distributions; the shell exports
`CMAKE_PREFIX_PATH` and `QT_PLUGIN_PATH`, so the `-DCMAKE_PREFIX_PATH="$Qt6_DIR"` argument is not
needed:
```bash
nix-shell # or: nix develop
git submodule update --init --recursive
cmake -S . -B _Build/linux -G Ninja -DCMAKE_BUILD_TYPE=Release \
-DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++
cmake --build _Build/linux --target launcher --parallel
cmake --install _Build/linux --prefix _Build/linux/install
```
The configure step downloads the FFmpeg prebuilts and the `xbyak`/`zydis` sources, so it needs
network access; a fully sandboxed `nix build` would require vendoring those inputs. A Vulkan 1.3
driver must be available at runtime (on NixOS, `hardware.graphics.enable = true`).
### Building on macOS
macOS builds target x86-64 and run under Rosetta 2 on Apple Silicon, so the PS5's x86-64 game
Generated
+27
View File
@@ -0,0 +1,27 @@
{
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1789426278,
"narHash": "sha256-GnotcvKtbTMnoVx6G4E7ZdUX6487Dky74dqq3pU7iRk=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "efe6f071ede9d21c37462d2d6682d5e670099684",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"nixpkgs": "nixpkgs"
}
}
},
"root": "root",
"version": 7
}
+20
View File
@@ -0,0 +1,20 @@
{
description = "KytyPS5 development shell";
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
outputs = { self, nixpkgs }:
let
systems = [ "x86_64-linux" ];
forAllSystems = nixpkgs.lib.genAttrs systems;
in
{
devShells = forAllSystems (system:
let
pkgs = import nixpkgs { inherit system; };
in
{
default = import ./nix/devshell.nix { inherit pkgs; };
});
};
}
+84
View File
@@ -0,0 +1,84 @@
{ pkgs }:
let
# Build-time dependencies. Every -dev package the bundled SDL2 probes for must
# be listed here, otherwise SDL2 quietly drops the audio/Wayland/udev backends.
buildDeps = with pkgs; [
qt6.qtbase # Concurrent, Network, Widgets (launcher)
vulkan-headers
vulkan-loader
vulkan-tools
vulkan-validation-layers
libglvnd
# X11 / input
libX11
libXcursor
libXext
libXfixes
libXi
libXrandr
libXScrnSaver
libxkbcommon
# Wayland
wayland
wayland-protocols
libdecor
# audio + device/dbus
alsa-lib
libpulseaudio
systemd # libudev
dbus
];
# Libraries the built binaries need at runtime.
runtimeLibs = with pkgs; [
stdenv.cc.cc.lib # libstdc++ / libgcc_s
libgcc.lib
qt6.qtbase # libQt6Concurrent/Network/Widgets
vulkan-loader
libglvnd
alsa-lib
libpulseaudio
libX11
libXcursor
libXext
libXfixes
libXi
libXrandr
libXScrnSaver
libxkbcommon
wayland
libdecor
systemd
dbus
];
in
pkgs.mkShell {
name = "kytyps5";
nativeBuildInputs = with pkgs; [
clang
lld
cmake
ninja
pkg-config
git
glslang # provides glslangValidator for the bundled shaders
];
buildInputs = buildDeps;
shellHook = ''
export CMAKE_PREFIX_PATH="${pkgs.qt6.qtbase}:''${CMAKE_PREFIX_PATH:-}"
export QT_PLUGIN_PATH="${pkgs.qt6.qtbase}/lib/qt-6/plugins:${pkgs.qt6.qtbase}/lib/qt6/plugins:''${QT_PLUGIN_PATH:-}"
export LD_LIBRARY_PATH="${pkgs.lib.makeLibraryPath runtimeLibs}:''${LD_LIBRARY_PATH:-}"
if [ -z "''${KYTY_SHELL_QUIET:-}" ]; then
echo "KytyPS5 dev shell"
echo " cmake -S . -B _Build/linux -G Ninja -DCMAKE_BUILD_TYPE=Release \\"
echo " -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++"
echo " cmake --build _Build/linux --target launcher --parallel"
echo " cmake --install _Build/linux --prefix _Build/linux/install"
fi
'';
}
+4
View File
@@ -0,0 +1,4 @@
let
pkgs = import <nixpkgs> { };
in
import ./nix/devshell.nix { inherit pkgs; }
+8
View File
@@ -43,6 +43,10 @@ int32_t GetUserId() {
return g_config->user_id;
}
const std::string& GetAudioInputDevice() {
return g_config->audio_input_device;
}
PresentMode GetPresentMode() {
return g_config->present_mode;
}
@@ -59,6 +63,10 @@ bool VrEnabled() {
return g_config->vr_enabled;
}
bool AmdCpuEnabled() {
return g_config->amd_cpu_enabled;
}
uint32_t GetVblankFrequency() {
return std::clamp(g_config->vblank_frequency, 30u, 360u);
}
+4
View File
@@ -43,10 +43,12 @@ struct ConfigOptions {
uint32_t screen_height = 720;
std::string user_name = "Kyty";
int32_t user_id = DEFAULT_USER_ID;
std::string audio_input_device;
PresentMode present_mode = PresentMode::Mailbox;
int32_t gpu_index = -1;
bool fullscreen_enabled = false;
bool vr_enabled = false;
bool amd_cpu_enabled = false;
uint32_t vblank_frequency = 60;
uint32_t console_language = DEFAULT_CONSOLE_LANGUAGE;
bool vulkan_validation_enabled = false;
@@ -77,10 +79,12 @@ uint32_t GetScreenWidth();
uint32_t GetScreenHeight();
const std::string& GetUserName();
int32_t GetUserId();
const std::string& GetAudioInputDevice();
PresentMode GetPresentMode();
int32_t GetGpuIndex();
bool FullscreenEnabled();
bool VrEnabled();
bool AmdCpuEnabled();
uint32_t GetVblankFrequency();
uint32_t GetConsoleLanguage();
bool VulkanValidationEnabled();
+8
View File
@@ -129,6 +129,14 @@ static void WriteImpl(std::string_view text, fmt::text_style style = {}) {
}
}
void WriteToConsoleAndLog(std::string_view text) {
WriteImpl(text);
if (g_initialized && g_direction != Direction::Console) {
WriteStdout(text);
}
Flush();
}
void WriteFatal(std::string_view text) {
if (g_direction == Direction::Silent || !g_initialized) {
WriteStdout(text);
+1
View File
@@ -25,6 +25,7 @@ Direction GetDirection();
bool IsSilent();
void Write(std::string_view text);
void Write(fmt::text_style style, std::string_view text);
void WriteToConsoleAndLog(std::string_view text);
void WriteFatal(std::string_view text);
void WriteFatal(fmt::text_style style, std::string_view text);
void Flush();
+8 -46
View File
@@ -8,7 +8,6 @@
#include <chrono> // IWYU pragma: keep
#include <condition_variable> // IWYU pragma: keep
#include <mutex>
#include <vector>
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS && KYTY_COMPILER == KYTY_COMPILER_CLANG
#define KYTY_WIN_CS
@@ -197,37 +196,7 @@ struct CondVarPrivate {
#endif
};
static std::recursive_mutex g_cond_waiters_mutex;
static std::vector<std::pair<int, CondVarPrivate*>> g_cond_waiters;
static wait_poll_func_t g_cond_wait_poll_callback = nullptr;
static void WakeCondVar(CondVarPrivate* cond_var) {
#ifdef KYTY_WIN_CS
static auto func = ResolveWakeAllConditionVariable();
EXIT_NOT_IMPLEMENTED(func == nullptr);
func(&cond_var->m_cv);
#else
cond_var->m_cv.notify_all();
#endif
}
static void RegisterCondWaiter(CondVarPrivate* cond_var) {
std::lock_guard lock(g_cond_waiters_mutex);
g_cond_waiters.emplace_back(Thread::GetThreadIdUnique(), cond_var);
}
static void UnregisterCondWaiter(CondVarPrivate* cond_var) {
const auto thread_id = Thread::GetThreadIdUnique();
std::lock_guard lock(g_cond_waiters_mutex);
const auto it = std::find_if(g_cond_waiters.begin(), g_cond_waiters.end(),
[thread_id, cond_var](const auto& waiter) {
return waiter.first == thread_id && waiter.second == cond_var;
});
if (it != g_cond_waiters.end()) {
g_cond_waiters.erase(it);
}
}
static wait_poll_func_t g_cond_wait_poll_callback = nullptr;
struct ThreadPrivate {
ThreadPrivate(thread_func_t f, void* a): func(f), arg(a), m_thread(&Run, this) {}
@@ -365,7 +334,6 @@ CondVar::~CondVar() {
}
void CondVar::Wait(Mutex* mutex) {
RegisterCondWaiter(m_cond_var.get());
#ifndef KYTY_WIN_CS
std::unique_lock<std::recursive_mutex> cpp_lock(mutex->m_mutex->m_mutex, std::adopt_lock_t());
#endif
@@ -406,7 +374,6 @@ void CondVar::Wait(Mutex* mutex) {
}
cpp_lock.release();
#endif
UnregisterCondWaiter(m_cond_var.get());
}
void CondVar::SetWaitPollCallback(wait_poll_func_t callback) {
@@ -415,7 +382,6 @@ void CondVar::SetWaitPollCallback(wait_poll_func_t callback) {
bool CondVar::WaitFor(Mutex* mutex, uint32_t micros) {
bool ok = false;
RegisterCondWaiter(m_cond_var.get());
#ifndef KYTY_WIN_CS
std::unique_lock<std::recursive_mutex> cpp_lock(mutex->m_mutex->m_mutex, std::adopt_lock_t());
#endif
@@ -430,7 +396,6 @@ bool CondVar::WaitFor(Mutex* mutex, uint32_t micros) {
std::cv_status::no_timeout);
cpp_lock.release();
#endif
UnregisterCondWaiter(m_cond_var.get());
return ok;
}
@@ -445,16 +410,13 @@ void CondVar::Signal() {
}
void CondVar::SignalAll() {
WakeCondVar(m_cond_var.get());
}
void CondVar::SignalThread(int thread_id) {
std::lock_guard lock(g_cond_waiters_mutex);
for (const auto& waiter: g_cond_waiters) {
if (waiter.first == thread_id) {
WakeCondVar(waiter.second);
}
}
#ifdef KYTY_WIN_CS
static auto func = ResolveWakeAllConditionVariable();
EXIT_NOT_IMPLEMENTED(func == nullptr);
func(&m_cond_var->m_cv);
#else
m_cond_var->m_cv.notify_all();
#endif
}
int Thread::GetThreadIdUnique() {
-1
View File
@@ -77,7 +77,6 @@ public:
void Signal();
void SignalAll();
static void SignalThread(int thread_id);
static void SetWaitPollCallback(wait_poll_func_t callback);
KYTY_CLASS_NO_COPY(CondVar);
+4
View File
@@ -196,6 +196,10 @@ void Run(const RunOptions& options) {
ClearDebugTextureFolder();
PrintSystemInfo();
std::string title_id;
if (Loader::SystemContentParamSfoGetString("TITLE_ID", &title_id) && !title_id.empty()) {
Log::WriteToConsoleAndLog(fmt::format("Title ID: {}\n", title_id));
}
int ok = atexit(KytyClose);
EXIT_NOT_IMPLEMENTED(ok != 0);
@@ -32,11 +32,12 @@ private:
struct BufferCursor {
std::span<const uint32_t> commands;
uint32_t offset_dw = 0;
uint32_t deferred_advance_dw = 0;
uint32_t offset_dw = 0;
};
std::vector<BufferCursor> m_buffer_stack;
std::span<const uint32_t> m_next_buffer;
bool m_chain = false;
bool m_suspended = false;
bool m_made_progress = false;
};
@@ -129,7 +130,7 @@ public:
[[nodiscard]] bool ShouldSkipPredicatedPackets() const { return m_predicate_skip; }
Pm4ProcessResult Process(Pm4Execution& execution, std::span<const uint32_t> commands);
void ProcessIndirectBuffer(std::span<const uint32_t> commands);
void ProcessIndirectBuffer(std::span<const uint32_t> commands, bool chain);
void SetFlip(const FlipInfo& flip) { m_flip = flip; }
@@ -143,7 +144,7 @@ private:
uint32_t cache_action, uint32_t event_index, uint32_t event_write_source,
void* dst_gpu_addr, T value, uint32_t interrupt_selector,
uint32_t interrupt_context_id);
void ProcessPm4(Pm4Execution& execution, size_t stop_depth);
void ProcessPm4(Pm4Execution& execution);
void SuspendPm4();
CommandScheduler& GetScheduler() const { return m_renderer.GetCommandScheduler(); }
CommandBuffer& CurrentBuffer() { return GetScheduler().Current(); }
@@ -1495,10 +1495,10 @@ KYTY_CP_OP_PARSER(CpOpBranch) {
reinterpret_cast<uint64_t>(else_buffer), else_num_dw);
if (take_then) {
cp.ProcessIndirectBuffer({then_buffer, then_num_dw});
cp.ProcessIndirectBuffer({then_buffer, then_num_dw}, true);
} else if (mode == 2 && else_num_dw != 0) {
EXIT_NOT_IMPLEMENTED(else_buffer == nullptr);
cp.ProcessIndirectBuffer({else_buffer, else_num_dw});
cp.ProcessIndirectBuffer({else_buffer, else_num_dw}, true);
}
return payload_dw;
@@ -1959,7 +1959,7 @@ KYTY_CP_OP_PARSER(CpOpIndirectBuffer) {
GraphicsDbgDumpDcb("ci", indirect_num_dw, indirect_buffer);
cp.ProcessIndirectBuffer({indirect_buffer, indirect_num_dw});
cp.ProcessIndirectBuffer({indirect_buffer, indirect_num_dw}, (control & (1u << 20u)) != 0);
return 3;
}
@@ -3635,6 +3635,11 @@ void GraphicsInitJmpTablesShIndirect() {
g_hw_sh_indirect_func[Pm4::SPI_SHADER_PGM_RSRC4_GS] = [](KYTY_HW_SH_INDIRECT_ARGS) {
HwShIgnoreShaderRegister(cmd_offset, value);
};
// Toolkit state restoration emits the compiler's GS-front allocation
// metadata here. Native GS resource registers already carry that allocation.
g_hw_sh_indirect_func[0x0ca] = [](KYTY_HW_SH_INDIRECT_ARGS) {
HwShIgnoreShaderRegister(cmd_offset, value);
};
g_hw_sh_indirect_func[Pm4::SPI_GRAPHICS_SHADER_CONTROL_GS] = [](KYTY_HW_SH_INDIRECT_ARGS) {
HwShIgnoreShaderRegister(cmd_offset, value);
};
+65 -60
View File
@@ -386,8 +386,12 @@ void CommandProcessor::WriteReferenceClock(uint64_t dst_address, uint32_t num_by
}
const auto value = Sync::ReadReferenceClock();
std::memcpy(reinterpret_cast<void*>(dst_address), &value, num_bytes);
LOGF("\t copy_data reference clock: dst=0x%016" PRIx64 " value=0x%016" PRIx64 " size=%u\n",
dst_address, value, num_bytes);
static std::atomic<uint32_t> clock_log_count {0};
if (clock_log_count.fetch_add(1) < 64) {
LOGF("\t copy_data reference clock: dst=0x%016" PRIx64 " value=0x%016" PRIx64
" size=%u\n",
dst_address, value, num_bytes);
}
}
void CommandProcessor::DmaData(uint8_t engine, uint8_t dst_sel, uint8_t dst_cache_policy,
@@ -672,20 +676,16 @@ Pm4ProcessResult CommandProcessor::Process(Pm4Execution& execution,
Pm4Execution* previous_execution;
} execution_scope(*this, execution);
ProcessPm4(execution, 0);
ProcessPm4(execution);
return execution.m_buffer_stack.empty() ? Pm4ProcessResult::Complete
: Pm4ProcessResult::Blocked;
}
void CommandProcessor::ProcessIndirectBuffer(std::span<const uint32_t> commands) {
void CommandProcessor::ProcessIndirectBuffer(std::span<const uint32_t> commands, bool chain) {
EXIT_IF(g_current_execution == nullptr);
if (commands.empty()) {
return;
}
auto& execution = *g_current_execution;
const auto stop_depth = execution.m_buffer_stack.size();
execution.m_buffer_stack.push_back({commands});
ProcessPm4(execution, stop_depth);
EXIT_IF(!g_current_execution->m_next_buffer.empty());
g_current_execution->m_next_buffer = commands;
g_current_execution->m_chain = chain;
}
void CommandProcessor::SuspendPm4() {
@@ -693,21 +693,13 @@ void CommandProcessor::SuspendPm4() {
g_current_execution->m_suspended = true;
}
void CommandProcessor::ProcessPm4(Pm4Execution& execution, size_t stop_depth) {
while (execution.m_buffer_stack.size() > stop_depth) {
void CommandProcessor::ProcessPm4(Pm4Execution& execution) {
while (!execution.m_buffer_stack.empty()) {
if (g_gpu_state != nullptr) {
g_gpu_state->ProcessCommands();
}
const auto buffer_index = execution.m_buffer_stack.size() - 1;
auto& cursor = execution.m_buffer_stack[buffer_index];
auto& cursor = execution.m_buffer_stack.back();
EXIT_IF(cursor.offset_dw > cursor.commands.size());
if (cursor.deferred_advance_dw != 0) {
EXIT_IF(cursor.deferred_advance_dw > cursor.commands.size() - cursor.offset_dw);
cursor.offset_dw += cursor.deferred_advance_dw;
cursor.deferred_advance_dw = 0;
execution.m_made_progress = true;
continue;
}
if (cursor.offset_dw == cursor.commands.size()) {
execution.m_buffer_stack.pop_back();
continue;
@@ -783,14 +775,19 @@ void CommandProcessor::ProcessPm4(Pm4Execution& execution, size_t stop_depth) {
handler(*this, packet_header & ~1u, packet + 1, remaining_dw, total_dw) + 1;
EXIT_IF(packet_dw > remaining_dw);
if (execution.m_suspended) {
if (execution.m_buffer_stack.size() > buffer_index + 1) {
execution.m_buffer_stack[buffer_index].deferred_advance_dw = packet_dw;
}
return;
}
EXIT_IF(execution.m_buffer_stack.size() != buffer_index + 1);
execution.m_buffer_stack[buffer_index].offset_dw += packet_dw;
cursor.offset_dw += packet_dw;
execution.m_made_progress = true;
if (!execution.m_next_buffer.empty()) {
// Chains and taken branches reuse the fetcher; only calls retain a return cursor.
if (execution.m_chain) {
cursor = {execution.m_next_buffer};
} else {
execution.m_buffer_stack.push_back({execution.m_next_buffer});
}
execution.m_next_buffer = {};
}
}
}
@@ -825,36 +822,55 @@ void CommandProcessor::SetNumInstances(uint32_t num_instances) {
void CommandProcessor::SetPredication(uint32_t condition, uint32_t op, uint32_t wait_op,
const volatile void* address, uint32_t count_in_dwords) {
if (wait_op != 0) {
BufferFlushAndWait();
}
(void)count_in_dwords;
uint64_t value = 0;
switch (op) {
case 0x00: {
case 0x00:
m_predicate_skip = false;
} break;
case 0x03: {
return;
case 0x01: {
EXIT_NOT_IMPLEMENTED(address == nullptr);
auto value = *reinterpret_cast<const volatile uint64_t*>(address);
switch (condition) {
case 0x00: m_predicate_skip = (value != 0); break;
case 0x01: m_predicate_skip = (value == 0); break;
default: EXIT("unknown predication condition: 0x%08" PRIx32 "\n", condition);
}
static std::atomic<uint32_t> log_count {0};
if (log_count.fetch_add(1) < 128) {
LOGF("\t bool predication: addr=0x%016" PRIx64 ", value=0x%016" PRIx64
", condition=%" PRIu32 ", skip=%u, wait_op=%" PRIu32 "\n",
reinterpret_cast<uint64_t>(address), value, condition,
m_predicate_skip ? 1u : 0u, wait_op);
// One begin/end pair per DB; bit 63 marks each counter ready.
constexpr uint64_t ready_bit = 1ull << 63u;
const auto* results = reinterpret_cast<const volatile uint64_t*>(address);
for (uint32_t db = 0; db < 16u; db++) {
const auto begin = results[db * 2u];
const auto end = results[db * 2u + 1u];
if ((begin & end & ready_bit) == 0) {
if (wait_op == 0) {
SuspendPm4();
} else {
m_predicate_skip = false;
}
return;
}
value += end - begin;
}
} break;
case 0x03:
if (wait_op != 0) {
BufferFlushAndWait();
}
EXIT_NOT_IMPLEMENTED(address == nullptr);
value = *reinterpret_cast<const volatile uint64_t*>(address);
break;
default: EXIT("unknown predication op: 0x%08" PRIx32 "\n", op);
}
switch (condition) {
case 0x00: m_predicate_skip = (value != 0); break;
case 0x01: m_predicate_skip = (value == 0); break;
default: EXIT("unknown predication condition: 0x%08" PRIx32 "\n", condition);
}
if (op == 0x03) {
static std::atomic<uint32_t> log_count {0};
if (log_count.fetch_add(1) < 128) {
LOGF("\t bool predication: addr=0x%016" PRIx64 ", value=0x%016" PRIx64
", condition=%" PRIu32 ", skip=%u, wait_op=%" PRIu32 "\n",
reinterpret_cast<uint64_t>(address), value, condition,
m_predicate_skip ? 1u : 0u, wait_op);
}
}
}
void CommandProcessor::DrawIndex(DrawIndexArgs args) {
@@ -1090,17 +1106,6 @@ void CommandProcessor::DispatchDirect(uint32_t thread_group_x, uint32_t thread_g
// local_x = std::max(cs.num_thread_x, 1u);
// local_y = std::max(cs.num_thread_y, 1u);
// local_z = std::max(cs.num_thread_z, 1u);
if (cs.wave_size == 64u) {
static std::atomic_bool logged_wave64_shader {false};
if (!logged_wave64_shader.exchange(true, std::memory_order_relaxed)) {
LOGF("warning: executing wave64 compute shader cs=0x%016" PRIx64 "\n",
cs.data_addr);
std::printf("warning: executing wave64 compute shader cs=0x%016" PRIx64 "\n",
cs.data_addr);
std::fflush(stdout);
}
}
m_renderer.GetRenderExecutor().DispatchDirect(m_submit_id, CurrentBuffer(), thread_group_x,
thread_group_y, thread_group_z, mode);
}
@@ -1292,6 +1297,7 @@ void CommandProcessor::WriteAtEndOfPipe(uint32_t cache_policy, uint32_t event_wr
break;
case 0x14:
case 0x28:
case 0x2f:
if (event_index == 0x00) {
write64(false);
return;
@@ -1299,7 +1305,6 @@ void CommandProcessor::WriteAtEndOfPipe(uint32_t cache_policy, uint32_t event_wr
break;
case 0x2b:
case 0x2d:
case 0x2f:
case 0x30:
if (event_index == 0x00 && !with_interrupt) {
write64(false);
+1
View File
@@ -86,6 +86,7 @@ struct TileMipLayout {
uint32_t padded_height = 0;
uint32_t tail_x = 0;
uint32_t tail_y = 0;
bool operator==(const TileMipLayout&) const = default;
};
struct TileSurfaceLayout {
+1 -6
View File
@@ -44,12 +44,7 @@ FaultManager::FaultManager(GraphicContext& graphics, CommandScheduler& scheduler
&m_fault_process_desc_layout),
"create fault-buffer descriptor layout");
vk::ShaderModuleCreateInfo module_info {};
module_info.codeSize = std::size(FAULT_BUFFER_PROCESS_SPV) * sizeof(uint32_t);
module_info.pCode = FAULT_BUFFER_PROCESS_SPV;
vk::ShaderModule module = nullptr;
RequireVulkanSuccess(m_graphics.device.createShaderModule(&module_info, nullptr, &module),
"create fault-buffer shader module");
const auto module = CompileSPV(FAULT_BUFFER_PROCESS_SPV, m_graphics.device);
vk::PipelineLayoutCreateInfo pipeline_layout_info {};
pipeline_layout_info.setLayoutCount = 1;
+104 -119
View File
@@ -33,22 +33,24 @@ namespace {
constexpr uint64_t NumFramesBeforeRemoval = 32;
[[nodiscard]] bool DecodeDccClear(const TextureCache::ImageDesc& desc, vk::Format format,
uint32_t fill, vk::ClearColorValue& clear) {
const auto code = static_cast<uint8_t>(fill);
if (fill != static_cast<uint32_t>(code) * 0x01010101u) {
return false;
[[nodiscard]] bool DecodeDccClear(const TextureCache::ImageDesc& desc, uint8_t code,
vk::ClearColorValue& clear) {
switch (code) {
case 0x00:
case 0x20:
case 0x40:
case 0x80:
case 0xc0: break;
default: return false;
}
const auto& metadata = desc.info.metadata;
const auto format = desc.view_info.format;
if (code == 0x20) {
// Clear-to-register is a color-buffer operation; the texture pipe cannot decode it.
return desc.type == TextureCache::BindingType::RenderTarget &&
metadata.dcc_clear_register_valid &&
DecodePackedColorClear(format, metadata.dcc_clear_word, clear);
}
if (code != 0x00 && code != 0x40 && code != 0x80 && code != 0xc0) {
return false;
}
clear = {};
if (code == 0x00) {
return true;
@@ -206,6 +208,10 @@ bool TextureCache::SameBacking(const ImageInfo& cached, const ImageInfo& request
if (cached.extent != requested.extent) {
return false;
}
if (cached.resources.levels < requested.resources.levels ||
cached.resources.layers < requested.resources.layers) {
return false;
}
if (cached.samples != requested.samples) {
return false;
}
@@ -323,10 +329,8 @@ void TextureCache::DeleteImage(ImageId id) {
if (image->info.HasMetadata()) {
const auto metadata = m_surface_metas.find(image->info.metadata.range.address);
if (metadata != m_surface_metas.end() &&
((image->info.metadata.kind == ImageMetadataKind::Dcc &&
metadata->second.type == MetaDataInfo::Type::Dcc) ||
(image->info.metadata.kind == ImageMetadataKind::Htile &&
metadata->second.type == MetaDataInfo::Type::HTile))) {
image->info.metadata.kind == ImageMetadataKind::Htile &&
metadata->second.type == MetaDataInfo::Type::HTile) {
// A later binding may have reused this address for another metadata type.
m_surface_metas.erase(metadata);
}
@@ -843,6 +847,16 @@ TextureCache::OverlapResult TextureCache::ResolveOverlap(const ImageInfo& reques
ImageViewOps::FormatsCompatible(cached.info.pixel_format, requested.pixel_format)) {
return {ExpandImage(requested, cached_id)};
}
// PS5 mip tails can expose more levels without increasing the guest allocation.
if (requested.pixel_format == cached.info.pixel_format &&
requested.type == cached.info.type && requested.resources > cached.info.resources &&
(requested.data.size > cached.info.data.size ||
(requested.data.size == cached.info.data.size &&
requested.extent == cached.info.extent &&
cached.info.resources.levels > 1 &&
requested.resources.layers == cached.info.resources.layers))) {
return {ExpandImage(requested, cached_id)};
}
if (requested.pixel_format != cached.info.pixel_format ||
requested.data.size <= cached.info.data.size) {
const auto result_id = merged_id ? merged_id : cached_id;
@@ -852,9 +866,6 @@ TextureCache::OverlapResult TextureCache::ResolveOverlap(const ImageInfo& reques
? result_id
: ImageId {}};
}
if (requested.type == cached.info.type && requested.resources > cached.info.resources) {
return {ExpandImage(requested, cached_id)};
}
EXIT("TextureCache: unresolvable equal-address image overlap, address=0x%016" PRIx64
" requested=%ux%u "
"cached=%ux%u requested_size=0x%016" PRIx64 " cached_size=0x%016" PRIx64
@@ -1124,53 +1135,69 @@ void TextureCache::InitializeImage(ImageId id) {
}
}
void TextureCache::PrepareDccClear(ImageId id, const ImageDesc& desc) {
void TextureCache::MaterializeDccClear(ImageId id, const ImageDesc& desc,
uint32_t metadata_base_layer) {
if (desc.info.metadata.kind != ImageMetadataKind::Dcc) {
return;
}
auto& image = m_slot_images[id];
image.info.metadata = desc.info.metadata;
auto [entry, inserted] = m_surface_metas.try_emplace(
desc.info.metadata.range.address, MetaDataInfo {.type = MetaDataInfo::Type::Dcc});
auto& metadata = entry->second;
if (!inserted && metadata.type == MetaDataInfo::Type::PendingDcc) {
metadata.type = MetaDataInfo::Type::Dcc;
} else if (metadata.type != MetaDataInfo::Type::Dcc) {
EXIT("TextureCache: image reuses non-DCC metadata\n");
const auto range = desc.info.metadata.range;
{
std::scoped_lock lock {m_lock};
auto& image = m_slot_images[id];
image.info.metadata = desc.info.metadata;
// A native DCC allocation must not retain a reused HTile/CMask/FMask interpretation.
m_surface_metas.erase(range.address);
if (range.size == 0 || desc.info.resources.levels != 1 || image.info.resources.levels != 1) {
return;
}
}
if (metadata.clear_mask == 0 || image.info.resources.levels != 1 ||
desc.info.metadata.range.size == 0 || metadata.fill_size < desc.info.metadata.range.size) {
return;
}
vk::ClearValue clear {};
if (!DecodeDccClear(desc, image.backing.format, metadata.fill_value, clear.color)) {
return;
const auto layers = desc.info.TransferLayers();
// These one-mip surfaces use complete 4 KiB DCC metadata blocks.
constexpr uint64_t MetadataBlockSize = 0x1000;
if (!range.Valid() || range.address % MetadataBlockSize != 0 || layers == 0 ||
range.size % layers != 0 || (range.size / layers) % MetadataBlockSize != 0) {
EXIT("TextureCache: DCC slices must contain aligned 4 KiB blocks\n");
}
const auto& view = desc.view_info;
const bool volume_texture = image.info.IsVolume() && view.type == vk::ImageViewType::e3D;
const auto first = volume_texture ? 0u : view.base_layer;
const auto count = volume_texture ? std::max(image.info.extent.depth >> view.base_level, 1u)
: view.layer_count;
if (first >= 32 || count > 32 - first) {
return;
const bool volume_texture = desc.info.IsVolume() && view.type == vk::ImageViewType::e3D;
const auto first = volume_texture ? 0u : metadata_base_layer;
const auto image_first = volume_texture ? 0u : view.base_layer;
const auto count = volume_texture ? desc.info.extent.depth : view.layer_count;
if (first >= layers || count > layers - first) {
EXIT("TextureCache: DCC view exceeds its native metadata slices\n");
}
// The metadata fill covers the complete allocation. Consume each layer only after its
// native image contents exist; already materialized layers may have been rendered since.
for (uint32_t layer = first; layer < first + count;) {
if ((metadata.clear_mask & (1u << layer)) == 0) {
layer++;
// Finish native metadata writes before reading backing bytes. This can submit the scheduler,
// so discovery runs before final draw uploads and never holds the texture lock across it.
if (m_buffer_cache.IsRegionGpuModified(range.address, range.size)) {
m_buffer_cache.ReadMemory(range.address, range.size, false);
}
const auto slice_size = range.size / layers;
for (uint32_t slice = 0; slice < count; slice++) {
const auto address = range.address + slice_size * (first + slice);
uint8_t code = 0;
if (!LibKernel::Memory::TryReadBacking(address, &code, sizeof(code))) {
EXIT("TextureCache: failed to read DCC metadata backing\n");
}
vk::ClearValue clear {};
if (!DecodeDccClear(desc, code, clear.color)) {
continue;
}
const auto start = layer;
uint32_t mask = 0;
do {
mask |= 1u << layer++;
} while (layer < first + count && (metadata.clear_mask & (1u << layer)) != 0);
ClearImage(m_scheduler.Current(), id,
{vk::ImageAspectFlagBits::eColor, view.base_level, view.level_count, start,
layer - start},
clear);
metadata.clear_mask &= ~mask;
std::vector<uint8_t> bytes(slice_size);
if (!LibKernel::Memory::TryReadBacking(address, bytes.data(), bytes.size())) {
EXIT("TextureCache: failed to read DCC metadata slice\n");
}
if (!std::all_of(bytes.begin(), bytes.end(), [code](uint8_t byte) { return byte == code; })) {
continue;
}
{
std::scoped_lock lock {m_lock};
ClearImage(m_scheduler.Current(), id, view.format,
{vk::ImageAspectFlagBits::eColor, view.base_level, view.level_count,
image_first + slice, 1}, clear);
}
// Native expanded keys own consumption. Existing buffer tracking publishes this CPU
// write to future GPU readers; FillBuffer can fault and must run outside the texture lock.
m_buffer_cache.FillBuffer(address, slice_size, UINT32_MAX, false);
}
}
@@ -1210,7 +1237,8 @@ void TextureCache::AssociateStencil(ImageId depth_id, GuestRange stencil) {
ImageId association {};
for (const auto id: FindImagesInRegion(stencil.address, stencil.size, false)) {
const auto owner = m_slot_images.try_get(id);
if (owner != nullptr && owner->info.data.address == stencil.address) {
if (owner != nullptr && owner->info.data == stencil &&
owner->info.extent == depth.info.extent) {
association = id;
}
}
@@ -1235,6 +1263,7 @@ ImageId TextureCache::FindImage(ImageDesc& desc, bool exact_format) {
std::scoped_lock lock {m_lock};
return GetNullImage(desc);
}
const auto metadata_base_layer = desc.view_info.base_layer;
ImageId result {};
{
@@ -1302,6 +1331,7 @@ ImageId TextureCache::FindImage(ImageDesc& desc, bool exact_format) {
image.tick_accessed_last = m_scheduler.CurrentTick();
TouchImage(image);
}
MaterializeDccClear(result, desc, metadata_base_layer);
return result;
}
@@ -1365,7 +1395,6 @@ vk::ImageView TextureCache::FindTexture(ImageId id, const ImageDesc& desc) {
image.MarkGpuModified();
}
if (!image.info.data.Empty()) {
PrepareDccClear(id, desc);
RefreshImage(id);
}
switch (desc.type) {
@@ -1395,7 +1424,6 @@ vk::ImageView TextureCache::FindRenderTarget(ImageId id, const ImageDesc& desc)
TouchImage(image);
image.MarkGpuModified();
image.usage.render_target = true;
PrepareDccClear(id, desc);
RefreshImage(id);
CommitGpuWrite(image);
TrackImageDownload(id, image);
@@ -1419,16 +1447,9 @@ vk::ImageView TextureCache::FindDepthTarget(ImageId id, const ImageDesc& desc) {
RefreshImage(id);
if (desc.info.HasMetadata()) {
image.info.metadata = desc.info.metadata;
auto [metadata, inserted] =
m_surface_metas.try_emplace(desc.info.metadata.range.address,
MetaDataInfo {.type = MetaDataInfo::Type::HTile,
.clear_mask = image.info.htile_clear_mask});
if (!inserted && metadata->second.type != MetaDataInfo::Type::HTile) {
// PS5 allocations can reuse DCC storage as HTile while the old color image is cached.
// The depth binding defines the new type; incompatible fill state cannot carry over.
metadata->second = {.type = MetaDataInfo::Type::HTile,
.clear_mask = image.info.htile_clear_mask};
}
m_surface_metas.emplace(desc.info.metadata.range.address,
MetaDataInfo {.type = MetaDataInfo::Type::HTile,
.clear_mask = image.info.htile_clear_mask});
}
CommitGpuWrite(image);
if (desc.info.HasStencil()) {
@@ -1516,12 +1537,12 @@ bool TextureCache::ClearImageFromBuffer(CommandBuffer& command, uint64_t address
}
clear.depthStencil.stencil = stencil_clear;
}
ClearImage(command, selected,
ClearImage(command, selected, image.backing.format,
{aspect, 0, image.info.resources.levels, 0, image.info.TransferLayers()}, clear);
return true;
}
void TextureCache::ClearImage(CommandBuffer& command, ImageId id,
void TextureCache::ClearImage(CommandBuffer& command, ImageId id, vk::Format format,
const vk::ImageSubresourceRange& range, const vk::ClearValue& clear) {
auto& image = m_slot_images[id];
const auto aspects = image.info.IsDepth() ? ImageViewOps::DepthAspectMask(image.backing.format)
@@ -1546,11 +1567,12 @@ void TextureCache::ClearImage(CommandBuffer& command, ImageId id,
}
}
command.EndRendering();
if (image.info.IsVolume() && !full_image) {
// Transfer clears use the backing format; aliased clears must encode through their view.
if (format != image.backing.format || (image.info.IsVolume() && !full_image)) {
EXIT_NOT_IMPLEMENTED(range.aspectMask != vk::ImageAspectFlagBits::eColor ||
range.levelCount != 1);
ImageViewInfo view {};
view.format = image.backing.format;
view.format = format;
view.type = range.layerCount == 1 ? vk::ImageViewType::e2D : vk::ImageViewType::e2DArray;
view.base_level = range.baseMipLevel;
view.base_layer = range.baseArrayLayer;
@@ -1861,72 +1883,32 @@ void TextureCache::InvalidateCpuAliases(uint64_t address, uint64_t size) {
bool TextureCache::IsMeta(uint64_t address) {
std::scoped_lock lock {m_lock};
const auto found = m_surface_metas.find(address);
return found != m_surface_metas.end() && found->second.type != MetaDataInfo::Type::PendingDcc;
return found != m_surface_metas.end();
}
bool TextureCache::IsMetaCleared(uint64_t address, uint32_t slice, uint32_t* fill_value) {
bool TextureCache::IsMetaCleared(uint64_t address, uint32_t slice) {
std::scoped_lock lock {m_lock};
const auto found = m_surface_metas.find(address);
if (found == m_surface_metas.end() || found->second.type == MetaDataInfo::Type::PendingDcc ||
slice >= 32) {
if (found == m_surface_metas.end() || slice >= 32) {
return false;
}
if (fill_value != nullptr) {
*fill_value = found->second.fill_value;
}
return (found->second.clear_mask & (1u << slice)) != 0;
}
bool TextureCache::ClearMeta(uint64_t address) {
std::scoped_lock lock {m_lock};
const auto found = m_surface_metas.find(address);
if (found == m_surface_metas.end() || found->second.type == MetaDataInfo::Type::PendingDcc ||
found->second.type == MetaDataInfo::Type::Dcc) {
// Preserve the broad metadata-clear operation for CMask/FMask/HTile. DCC requires a
// validated fill value, so an arbitrary compute write must not clear it.
if (found == m_surface_metas.end()) {
return false;
}
found->second.clear_mask = UINT32_MAX;
return true;
}
void TextureCache::TrackDccFill(uint64_t address, uint64_t size, uint32_t fill_value) {
if (!GuestRange {address, size}.Valid()) {
EXIT("TextureCache: invalid DCC fill range\n");
}
// DCC fills use a repeated byte code. Require all four bytes of the detected dword to agree,
// and mark only recognized deferred-clear encodings as logically clear.
const auto dcc_clear_mask = [fill_value] {
const auto code = static_cast<uint8_t>(fill_value);
if (fill_value != static_cast<uint32_t>(code) * 0x01010101u) {
return 0u;
}
switch (code) {
case 0x00:
case 0x20:
case 0x40:
case 0x80:
case 0xc0: return UINT32_MAX;
default: return 0u;
}
}();
std::scoped_lock lock {m_lock};
// The guest dispatch still writes metadata. An unknown address remains PendingDcc until an
// image descriptor confirms its role; never reinterpret CMask/FMask/HTile as DCC.
const auto found = m_surface_metas.try_emplace(address).first;
if (found->second.type == MetaDataInfo::Type::PendingDcc ||
found->second.type == MetaDataInfo::Type::Dcc) {
found->second.clear_mask = dcc_clear_mask;
found->second.fill_value = fill_value;
found->second.fill_size = size;
}
}
bool TextureCache::TouchMeta(uint64_t address, uint32_t slice, bool is_clear) {
std::scoped_lock lock {m_lock};
const auto found = m_surface_metas.find(address);
if (found == m_surface_metas.end() || found->second.type == MetaDataInfo::Type::PendingDcc ||
slice >= 32) {
if (found == m_surface_metas.end() || slice >= 32) {
return false;
}
if (is_clear) {
@@ -1942,10 +1924,13 @@ void TextureCache::UnmapMemory(uint64_t address, uint64_t size) {
EXIT("TextureCache: invalid unmap range\n");
}
std::scoped_lock lock {m_lock};
const auto end = address + size;
for (auto metadata = m_surface_metas.lower_bound(address);
metadata != m_surface_metas.end() && metadata->first < end;) {
metadata = m_surface_metas.erase(metadata);
for (auto metadata = m_surface_metas.begin(); metadata != m_surface_metas.end();) {
const auto base = metadata->first;
if (base >= address && base < address + size) {
metadata = m_surface_metas.erase(metadata);
} else {
++metadata;
}
}
auto images = FindImagesInRegion(address, size, false);
for (const auto id: images) {
+8 -17
View File
@@ -64,11 +64,8 @@ public:
[[nodiscard]] bool IsRegionGpuModified(uint64_t address, uint64_t size);
[[nodiscard]] bool IsMeta(uint64_t address);
[[nodiscard]] bool IsMetaCleared(uint64_t address, uint32_t slice,
uint32_t* fill_value = nullptr);
[[nodiscard]] bool IsMetaCleared(uint64_t address, uint32_t slice);
[[nodiscard]] bool ClearMeta(uint64_t address);
// Record deferred DCC state while the original guest dispatch writes the metadata.
void TrackDccFill(uint64_t address, uint64_t size, uint32_t fill_value);
[[nodiscard]] bool TouchMeta(uint64_t address, uint32_t slice, bool is_clear);
void UnmapMemory(uint64_t address, uint64_t size);
@@ -81,17 +78,10 @@ private:
struct ImageDownload;
struct MetaDataInfo {
// A guest metadata-fill dispatch may initialize DCC before its render target is bound.
// PendingDcc retains that exact fill until an image binding classifies the address,
// without exposing an unconfirmed buffer address to the normal metadata heuristics.
// Keep all surface metadata in one entry so CMask/FMask can be
// registered beside HTile and DCC without introducing parallel tracking paths.
enum class Type : uint8_t { PendingDcc, CMask, FMask, HTile, Dcc };
enum class Type : uint8_t { CMask, FMask, HTile };
Type type = Type::PendingDcc;
uint32_t clear_mask = 0;
uint32_t fill_value = 0xffffffffu;
uint64_t fill_size = 0;
Type type;
uint32_t clear_mask = UINT32_MAX;
};
struct OverlapResult {
@@ -149,7 +139,8 @@ private:
ImageId cached);
[[nodiscard]] ImageId ExpandImage(const ImageInfo& info, ImageId source);
void RefreshImage(ImageId id);
void PrepareDccClear(ImageId id, const ImageDesc& desc);
void MaterializeDccClear(ImageId id, const ImageDesc& desc,
uint32_t metadata_base_layer);
void InitializeImage(ImageId id);
[[nodiscard]] TextureTransfer
BuildTextureTransfer(const Image& image, BindingType binding, TransferDirection direction) const;
@@ -160,8 +151,8 @@ private:
void DownloadDepth(Image& image, Buffer& destination, uint64_t destination_offset);
void CommitGpuWrite(Image& image);
// Caller holds m_lock. Volume layer ranges select depth slices.
void ClearImage(CommandBuffer& command, ImageId id, const vk::ImageSubresourceRange& range,
const vk::ClearValue& clear);
void ClearImage(CommandBuffer& command, ImageId id, vk::Format format,
const vk::ImageSubresourceRange& range, const vk::ClearValue& clear);
void PrepareImageCopy(Image& image);
void RefreshCopySource(ImageId id);
[[nodiscard]] bool CopyD16(Image& destination, Image& source);
@@ -19,6 +19,7 @@
#include "graphics/host_gpu/vulkanCommon.h"
#include <algorithm>
#include <array>
#include <atomic>
#include <cmath>
#include <cstdarg>
@@ -45,8 +46,12 @@ static vk::StencilOp ConvertStencilOp(uint8_t value, uint8_t write_mask, uint8_t
switch (static_cast<Prospero::StencilOp>(value)) {
case Prospero::StencilOp::kKeep: return vk::StencilOp::eKeep;
case Prospero::StencilOp::kZero: return vk::StencilOp::eZero;
case Prospero::StencilOp::kReplaceTest:
case Prospero::StencilOp::kReplaceOp: return vk::StencilOp::eReplace;
case Prospero::StencilOp::kReplaceTest: return vk::StencilOp::eReplace;
case Prospero::StencilOp::kReplaceOp:
if ((op_value & write_mask) == 0) {
return vk::StencilOp::eZero;
}
return vk::StencilOp::eReplace;
case Prospero::StencilOp::kAddClamp: return vk::StencilOp::eIncrementAndClamp;
case Prospero::StencilOp::kSubClamp: return vk::StencilOp::eDecrementAndClamp;
case Prospero::StencilOp::kInvert: return vk::StencilOp::eInvert;
@@ -66,9 +71,38 @@ static vk::StencilOp ConvertStencilOp(uint8_t value, uint8_t write_mask, uint8_t
}
}
static bool UsesStencilOpValue(uint8_t fail, uint8_t pass, uint8_t depth_fail) {
constexpr auto replace_op = static_cast<uint8_t>(Prospero::StencilOp::kReplaceOp);
return fail == replace_op || pass == replace_op || depth_fail == replace_op;
static vk::StencilOpState ConvertStencilState(
uint8_t compare, const std::array<uint8_t, 3>& operations, uint8_t op_value,
const vk::StencilOpState& state) {
const auto test_value = state.reference;
auto reference = test_value;
auto required_bits = state.compareMask;
if (compare == static_cast<uint8_t>(vk::CompareOp::eAlways) ||
compare == static_cast<uint8_t>(vk::CompareOp::eNever)) {
required_bits = 0;
}
std::array<vk::StencilOp, 3> converted {};
for (size_t i = 0; i < operations.size(); i++) {
converted[i] = ConvertStencilOp(operations[i], state.writeMask, op_value);
if (converted[i] != vk::StencilOp::eReplace) {
continue;
}
auto replacement = test_value;
if (static_cast<Prospero::StencilOp>(operations[i]) == Prospero::StencilOp::kReplaceOp) {
replacement = op_value;
}
if (((reference ^ replacement) & required_bits & state.writeMask) != 0) {
DepthFatal("unsupported stencil replacement: compare=%u, compare mask=0x%02" PRIx32
", write mask=0x%02" PRIx32 ", operation value=0x%02" PRIx8
", test value=0x%02" PRIx32,
compare, state.compareMask, state.writeMask, op_value, test_value);
}
// Vulkan shares one reference between comparison and every replacement on this face.
reference = (reference & ~state.writeMask) | (replacement & state.writeMask);
required_bits |= state.writeMask;
}
return {converted[0], converted[1], converted[2], static_cast<vk::CompareOp>(compare),
state.compareMask, state.writeMask, reference};
}
[[nodiscard]] static vk::Format ResolveHostDepthAttachmentFormat(const CommandBuffer& buffer,
@@ -125,8 +159,8 @@ static TextureCache::ImageDesc MakeDepthTargetDesc(const CommandBuffer& buffer,
DepthFatal("invalid depth view: base=%u last=%u", z.depth_view.slice_start,
z.depth_view.slice_max);
}
if (z.z_info.expclear_enabled || z.stencil_info.expclear_enabled ||
z.z_info.partially_resident ||
// EXPCLEAR permits an HTile acceleration state; the host attachment is already expanded.
if (z.z_info.partially_resident ||
z.stencil_info.partially_resident || z.z_info.max_mip_level != 0 ||
z.depth_view.current_mip_level != 0 || unsupported_shading_rate_encoding ||
depth_address == 0 || (depth_address & 0xffffu) != 0) {
@@ -296,31 +330,20 @@ void RenderExecutor::ResolveRenderDepthTarget(CommandBuffer& buffer, RenderDepth
const uint8_t back_write_mask = stencil_ops_disabled ? 0 : sm.stencil_writemask_bf;
if (dc.stencilfunc > static_cast<uint8_t>(vk::CompareOp::eAlways) ||
(dc.backface_enable &&
dc.stencilfunc_bf > static_cast<uint8_t>(vk::CompareOp::eAlways)) ||
(front_write_mask != 0 &&
UsesStencilOpValue(sc.stencil_fail, sc.stencil_zpass, sc.stencil_zfail) &&
sm.stencil_opval != sm.stencil_testval) ||
(dc.backface_enable && back_write_mask != 0 &&
UsesStencilOpValue(sc.stencil_fail_bf, sc.stencil_zpass_bf, sc.stencil_zfail_bf) &&
sm.stencil_opval_bf != sm.stencil_testval_bf)) {
DepthFatal("unsupported stencil compare or replacement state");
dc.stencilfunc_bf > static_cast<uint8_t>(vk::CompareOp::eAlways))) {
DepthFatal("unsupported stencil compare state");
}
r.stencil_static_front = {
ConvertStencilOp(sc.stencil_fail, front_write_mask, sm.stencil_opval),
ConvertStencilOp(sc.stencil_zpass, front_write_mask, sm.stencil_opval),
ConvertStencilOp(sc.stencil_zfail, front_write_mask, sm.stencil_opval),
static_cast<vk::CompareOp>(dc.stencilfunc)};
r.stencil_dynamic_front = {sm.stencil_mask, front_write_mask, sm.stencil_testval};
r.stencil_front = ConvertStencilState(
dc.stencilfunc, {sc.stencil_fail, sc.stencil_zpass, sc.stencil_zfail},
sm.stencil_opval, {.compareMask = sm.stencil_mask, .writeMask = front_write_mask,
.reference = sm.stencil_testval});
if (dc.backface_enable) {
r.stencil_static_back = {
ConvertStencilOp(sc.stencil_fail_bf, back_write_mask, sm.stencil_opval_bf),
ConvertStencilOp(sc.stencil_zpass_bf, back_write_mask, sm.stencil_opval_bf),
ConvertStencilOp(sc.stencil_zfail_bf, back_write_mask, sm.stencil_opval_bf),
static_cast<vk::CompareOp>(dc.stencilfunc_bf)};
r.stencil_dynamic_back = {sm.stencil_mask_bf, back_write_mask, sm.stencil_testval_bf};
r.stencil_back = ConvertStencilState(
dc.stencilfunc_bf, {sc.stencil_fail_bf, sc.stencil_zpass_bf, sc.stencil_zfail_bf},
sm.stencil_opval_bf, {.compareMask = sm.stencil_mask_bf, .writeMask = back_write_mask,
.reference = sm.stencil_testval_bf});
} else {
r.stencil_static_back = r.stencil_static_front;
r.stencil_dynamic_back = r.stencil_dynamic_front;
r.stencil_back = r.stencil_front;
}
}
auto& cache = m_context.GetTextureCache();
@@ -406,14 +429,13 @@ vk::ImageAspectFlags RenderDepthInfo::AttachmentWriteAspects() const {
return writes;
}
const auto face_writes = [&](const PipelineStencilStaticState& state,
const PipelineStencilDynamicState& dynamic) {
if (dynamic.writeMask == 0) {
const auto face_writes = [&](const vk::StencilOpState& state) {
if (state.writeMask == 0) {
return false;
}
bool can_pass = state.compareOp != vk::CompareOp::eNever;
bool can_fail = state.compareOp != vk::CompareOp::eAlways;
if (dynamic.compareMask == 0) {
if (state.compareMask == 0) {
switch (state.compareOp) {
case vk::CompareOp::eEqual:
case vk::CompareOp::eLessOrEqual:
@@ -439,8 +461,7 @@ vk::ImageAspectFlags RenderDepthInfo::AttachmentWriteAspects() const {
(can_pass && depth_fail && state.depthFailOp != vk::StencilOp::eKeep);
};
if (stencil_clear_enable ||
(stencil_test_enable && (face_writes(stencil_static_front, stencil_dynamic_front) ||
face_writes(stencil_static_back, stencil_dynamic_back)))) {
(stencil_test_enable && (face_writes(stencil_front) || face_writes(stencil_back)))) {
writes |= vk::ImageAspectFlagBits::eStencil;
}
return writes;
@@ -34,10 +34,8 @@ struct RenderDepthInfo {
bool stencil_clear_enable = false;
uint8_t stencil_clear_value = 0;
bool stencil_test_enable = false;
PipelineStencilStaticState stencil_static_front;
PipelineStencilStaticState stencil_static_back;
PipelineStencilDynamicState stencil_dynamic_front;
PipelineStencilDynamicState stencil_dynamic_back;
vk::StencilOpState stencil_front;
vk::StencilOpState stencil_back;
ImageId image_id;
[[nodiscard]] vk::ImageAspectFlags AttachmentWriteAspects() const;
@@ -10,7 +10,6 @@
#include <algorithm>
#include <array>
#include <iterator>
namespace Libs::Graphics {
@@ -37,9 +36,8 @@ BlitHelper::BlitHelper(GraphicContext& graphics, CommandScheduler& scheduler)
m_graphics.device.createPipelineLayout(&layout_info, nullptr, &m_pipeline_layout),
"create BlitHelper pipeline layout");
m_vertex_shader = CreateShader(GPU_BLIT_FS_TRIANGLE_SPV, std::size(GPU_BLIT_FS_TRIANGLE_SPV));
m_fragment_shader =
CreateShader(GPU_BLIT_COLOR_TO_MS_DEPTH_SPV, std::size(GPU_BLIT_COLOR_TO_MS_DEPTH_SPV));
m_vertex_shader = CompileSPV(GPU_BLIT_FS_TRIANGLE_SPV, m_graphics.device);
m_fragment_shader = CompileSPV(GPU_BLIT_COLOR_TO_MS_DEPTH_SPV, m_graphics.device);
}
BlitHelper::~BlitHelper() {
@@ -60,17 +58,6 @@ BlitHelper::~BlitHelper() {
}
}
vk::ShaderModule BlitHelper::CreateShader(const uint32_t* code, size_t words) const {
EXIT_IF(code == nullptr || words == 0);
vk::ShaderModuleCreateInfo create {};
create.codeSize = words * sizeof(uint32_t);
create.pCode = code;
vk::ShaderModule module = nullptr;
RequireVulkanSuccess(m_graphics.device.createShaderModule(&create, nullptr, &module),
"create BlitHelper shader module");
return module;
}
vk::Pipeline BlitHelper::GetPipeline(PipelineKey key) {
const auto cached = std::ranges::find(m_pipelines, key, &Pipeline::key);
if (cached != m_pipelines.end()) {
@@ -37,8 +37,7 @@ private:
vk::Pipeline handle = nullptr;
};
[[nodiscard]] vk::ShaderModule CreateShader(const uint32_t* code, size_t words) const;
[[nodiscard]] vk::Pipeline GetPipeline(PipelineKey key);
[[nodiscard]] vk::Pipeline GetPipeline(PipelineKey key);
GraphicContext& m_graphics;
CommandScheduler& m_scheduler;
@@ -159,6 +159,7 @@ struct ImageViewInfo {
vk::ImageAspectFlags aspect = vk::ImageAspectFlagBits::eColor;
uint32_t base_level = 0;
uint32_t level_count = 1;
uint32_t min_lod = 0; // U4.8 clamp relative to base_level.
uint32_t base_layer = 0;
uint32_t layer_count = 1;
vk::ComponentMapping mapping = {};
@@ -167,9 +168,10 @@ struct ImageViewInfo {
[[nodiscard]] bool operator==(const ImageViewInfo& rhs) const noexcept {
return format == rhs.format && type == rhs.type && aspect == rhs.aspect &&
base_level == rhs.base_level && level_count == rhs.level_count &&
base_layer == rhs.base_layer && layer_count == rhs.layer_count &&
mapping.r == rhs.mapping.r && mapping.g == rhs.mapping.g &&
mapping.b == rhs.mapping.b && mapping.a == rhs.mapping.a && usage == rhs.usage;
min_lod == rhs.min_lod && base_layer == rhs.base_layer &&
layer_count == rhs.layer_count && mapping.r == rhs.mapping.r &&
mapping.g == rhs.mapping.g && mapping.b == rhs.mapping.b &&
mapping.a == rhs.mapping.a && usage == rhs.usage;
}
};
@@ -362,6 +362,12 @@ vk::ImageView Image::FindView(const ImageViewInfo& view_info) {
if (!is_storage) {
usage.usage &= ~vk::ImageUsageFlagBits::eStorage;
}
vk::ImageViewMinLodCreateInfoEXT min_lod {};
if (normalized.min_lod != 0) {
min_lod.minLod = static_cast<float>(normalized.base_level) +
static_cast<float>(normalized.min_lod) / 256.0f;
usage.pNext = &min_lod;
}
vk::ImageViewCreateInfo create {};
create.pNext = &usage;
create.image = image.image;
+4 -18
View File
@@ -260,12 +260,8 @@ vk::Pipeline TileManager::GetPipeline(uint32_t slot) {
const uint32_t values[] {1u << element_index, direction_index};
const vk::SpecializationMapEntry entries[] {{0, 0, 4}, {1, 4, 4}};
const vk::SpecializationInfo specialization {2, entries, sizeof(values), values};
vk::ShaderModuleCreateInfo module_info {};
module_info.codeSize = shaders[family_index].words * sizeof(uint32_t);
module_info.pCode = shaders[family_index].code;
vk::ShaderModule module = nullptr;
RequireVulkanSuccess(m_graphics.device.createShaderModule(&module_info, nullptr, &module),
"create TileManager shader module");
const auto module =
CompileSPV({shaders[family_index].code, shaders[family_index].words}, m_graphics.device);
vk::PipelineShaderStageCreateInfo stage {};
stage.stage = vk::ShaderStageFlagBits::eCompute;
stage.module = module;
@@ -472,12 +468,7 @@ void TileManager::ConvertD16(Result source, Result target, D16Direction directio
code = GPU_TILER_DEMOTE_D16_SPV;
words = std::size(GPU_TILER_DEMOTE_D16_SPV);
}
vk::ShaderModuleCreateInfo module_info {};
module_info.codeSize = words * sizeof(uint32_t);
module_info.pCode = code;
vk::ShaderModule module = nullptr;
RequireVulkanSuccess(m_graphics.device.createShaderModule(&module_info, nullptr, &module),
"create D16 conversion shader module");
const auto module = CompileSPV({code, words}, m_graphics.device);
vk::PipelineShaderStageCreateInfo stage {};
stage.stage = vk::ShaderStageFlagBits::eCompute;
stage.module = module;
@@ -611,12 +602,7 @@ void TileManager::ConvertD16(Result source, Result target, D16Direction directio
void TileManager::SwapBgra16(Result input, Result output, uint32_t pixels) {
if (m_swap_bgra16 == nullptr) {
vk::ShaderModuleCreateInfo module_info {};
module_info.codeSize = std::size(GPU_TILER_SWAP_BGRA16_SPV) * sizeof(uint32_t);
module_info.pCode = GPU_TILER_SWAP_BGRA16_SPV;
vk::ShaderModule module = nullptr;
RequireVulkanSuccess(m_graphics.device.createShaderModule(&module_info, nullptr, &module),
"create BGRA16 swap shader module");
const auto module = CompileSPV(GPU_TILER_SWAP_BGRA16_SPV, m_graphics.device);
vk::PipelineShaderStageCreateInfo stage {};
stage.stage = vk::ShaderStageFlagBits::eCompute;
stage.module = module;
@@ -15,6 +15,7 @@
#include "graphics/guest_gpu/tile.h"
#include "graphics/host_gpu/graphicContext.h"
#include "graphics/host_gpu/hostMemory.h"
#include "graphics/host_gpu/renderer/colorRenderTarget.h"
#include "graphics/host_gpu/renderer/debug.h"
#include "graphics/host_gpu/renderer/image/imageView.h"
#include "graphics/host_gpu/renderer/image/textureCommon.h"
@@ -147,7 +148,7 @@ NativeStorageBuffer(RenderContext& context, const PreparedBindings::BufferSource
}
buffer_offset = static_cast<uint32_t>(adjustment);
const vk::DescriptorBufferInfo result {buffer->Handle(), aligned_offset, size + adjustment};
if (resource.formatted && resource.written) {
if (resource.written) {
context.GetTextureCache().InvalidateMemoryFromGPU(address, size);
}
const char* access = "Read";
@@ -292,13 +293,8 @@ static bool IsSupportedStorageTextureDescriptor(const ShaderRecompiler::IR::Imag
const bool supported_swizzle =
IsValidImageSwizzle(swizzle) &&
(swizzle == DstSel(4, 5, 6, 7) || !resource.read || resource.atomic);
const auto max_mip = resource.r128 ? descriptor.LastLevel() : descriptor.MaxMip();
const auto view_last_level =
resource.mip_mode == ShaderRecompiler::IR::ImageMipMode::DynamicStorage
? descriptor.LastLevel()
: std::min(descriptor.LastLevel(), max_mip);
return (is_1d || is_1d_array || is_2d || is_2d_array || is_3d) && supported_tile &&
descriptor.BaseLevel() <= view_last_level && view_last_level <= max_mip &&
descriptor.BaseLevel() <= descriptor.LastLevel() &&
descriptor.MinLod() == 0 && supported_swizzle && descriptor.BCSwizzle() == 0 &&
!descriptor.MsaaDepth();
}
@@ -456,6 +452,14 @@ static ImageViewInfo TextureViewInfo(const ShaderRecompiler::IR::ImageResource&
view.aspect = vk::ImageAspectFlagBits::eColor;
view.base_level = descriptor.BaseLevel();
view.level_count = view_levels;
if (descriptor.MinLod() > descriptor.LastLevel() * 256u) {
EXIT("texture minimum LOD exceeds last mip level: min_lod=%u last_level=%u\n",
descriptor.MinLod(), descriptor.LastLevel());
}
const auto base_lod = view.base_level * 256u;
if (descriptor.MinLod() > base_lod) {
view.min_lod = descriptor.MinLod() - base_lod;
}
view.usage = storage ? vk::ImageUsageFlagBits::eStorage : vk::ImageUsageFlagBits::eSampled;
view.mapping =
storage || surface_format.conversion_format != Prospero::BufferFormat::kInvalid
@@ -506,6 +510,21 @@ static ImageViewInfo TextureViewInfo(const ShaderRecompiler::IR::ImageResource&
return view;
}
static bool TextureViewPreservesMipLayout(const TileSurfaceDescription& description,
uint32_t view_levels) {
TileSurfaceLayout physical {};
TileSurfaceLayout view {};
auto view_description = description;
view_description.levels = view_levels;
return TileGetTiledTextureLayout(description, physical) &&
TileGetTiledTextureLayout(view_description, view) &&
physical.first_tail_level == view.first_tail_level &&
physical.block_slice_size == view.block_slice_size &&
physical.total_size == view.total_size &&
std::equal(std::begin(physical.mips), std::begin(physical.mips) + description.levels,
std::begin(view.mips));
}
TextureBinding RenderExecutor::ResolveTexture(const ShaderRecompiler::IR::ImageResource& resource,
const ShaderRecompiler::IR::DescriptorValue& value) {
auto descriptor = DecodeNativeDescriptor<ShaderTextureResource>(value);
@@ -522,24 +541,22 @@ TextureBinding RenderExecutor::ResolveTexture(const ShaderRecompiler::IR::ImageR
return {id, nullptr, std::move(desc)};
}
const auto address = descriptor.Base40();
const auto width = static_cast<uint32_t>(descriptor.Width5()) + 1u;
const auto height = static_cast<uint32_t>(descriptor.Height5()) + 1u;
const auto base_level = descriptor.BaseLevel();
const auto last_level = descriptor.LastLevel();
const auto type = TextureType(descriptor);
const bool multisampled = IsMultisampledTexture(type);
const auto max_mip = resource.r128 ? last_level : descriptor.MaxMip();
const auto levels = multisampled ? 1u : static_cast<uint32_t>(max_mip) + 1u;
const bool dynamic_storage =
storage && resource.mip_mode == ShaderRecompiler::IR::ImageMipMode::DynamicStorage;
const auto view_last_level =
!multisampled && !dynamic_storage ? std::min(last_level, max_mip) : last_level;
const auto address = descriptor.Base40();
const auto width = static_cast<uint32_t>(descriptor.Width5()) + 1u;
const auto height = static_cast<uint32_t>(descriptor.Height5()) + 1u;
const auto base_level = descriptor.BaseLevel();
const auto last_level = descriptor.LastLevel();
const auto type = TextureType(descriptor);
const bool multisampled = IsMultisampledTexture(type);
const auto max_mip = resource.r128 ? last_level : descriptor.MaxMip();
const auto physical_levels = multisampled ? 1u : static_cast<uint32_t>(max_mip) + 1u;
const auto levels =
multisampled ? 1u : std::max(physical_levels, static_cast<uint32_t>(last_level) + 1u);
const auto tile = descriptor.TileMode();
const bool depth_tile = tile == Prospero::TileMode::kDepth;
const bool msaa_tile = depth_tile || tile == Prospero::TileMode::kRenderTarget;
const bool msaa_array = type == Prospero::ImageType::kColor2DMsaaArray;
if ((!multisampled && (base_level > view_last_level || view_last_level >= levels)) ||
if ((!multisampled && base_level > last_level) ||
(multisampled &&
(base_level != 0 || last_level == 0 || last_level > 3 || max_mip != last_level ||
!msaa_tile || (descriptor.MsaaDepth() && !depth_tile) ||
@@ -558,7 +575,7 @@ TextureBinding RenderExecutor::ResolveTexture(const ShaderRecompiler::IR::ImageR
}
const auto samples = multisampled ? 1u << last_level : 1u;
const auto view_levels =
multisampled ? 1u : static_cast<uint32_t>(view_last_level - base_level) + 1u;
multisampled ? 1u : static_cast<uint32_t>(last_level - base_level) + 1u;
const auto depth = static_cast<uint32_t>(descriptor.Depth()) + 1u;
const auto format = descriptor.Format();
const auto surface_format = TextureGetSurfaceFormatInfo(format);
@@ -577,7 +594,19 @@ TextureBinding RenderExecutor::ResolveTexture(const ShaderRecompiler::IR::ImageR
type == Prospero::ImageType::kColor2DArray ||
type == Prospero::ImageType::kColor2DMsaaArray;
const auto image_layers = layered ? depth : 1u;
uint32_t pitch = 0;
if (levels > physical_levels) {
const TileSurfaceDescription physical {
format, tile, volume ? TileSurfaceDimension::Dim3D : TileSurfaceDimension::Dim2D,
width, height, volume ? depth : 1u, physical_levels, image_layers};
// Texture mip views take precedence over the resource count, but must keep its storage layout.
if (!TextureViewPreservesMipLayout(physical, levels)) {
EXIT("unsupported texture mip view changes physical layout: base=%u last=%u max=%u "
"extent=%ux%ux%u tile=%u\n",
base_level, last_level, max_mip, width, height, depth,
static_cast<uint32_t>(tile));
}
}
uint32_t pitch = 0;
TileSizeAlign size {};
if (multisampled) {
const auto bytes = Prospero::NumBytesPerElement(format);
@@ -590,8 +619,8 @@ TextureBinding RenderExecutor::ResolveTexture(const ShaderRecompiler::IR::ImageR
size.size *= image_layers;
} else {
pitch = TileGetTexturePitch(format, width, tile);
TileGetTextureTotalSize(format, width, height, volume ? depth : image_layers, levels, tile,
volume, size);
TileGetTextureTotalSize(format, width, height, volume ? depth : image_layers,
physical_levels, tile, volume, size);
}
EXIT_NOT_IMPLEMENTED(size.size == 0 || size.align == 0 ||
(address & (static_cast<uint64_t>(size.align) - 1u)) != 0);
@@ -628,7 +657,7 @@ TextureBinding RenderExecutor::ResolveTexture(const ShaderRecompiler::IR::ImageR
!desc.info.IsDepth()) {
TileSizeAlign metadata_size {};
(void)TileGetDccSize(width, height, volume ? depth : image_layers,
desc.info.bytes_per_block, levels, tile, metadata_size,
desc.info.bytes_per_block, physical_levels, tile, metadata_size,
std::countr_zero(samples));
desc.info.metadata.kind = ImageMetadataKind::Dcc;
desc.info.metadata.range = {descriptor.MetaAddr() << 8u, metadata_size.size};
@@ -850,7 +879,8 @@ void RenderExecutor::RebindImages(PreparedBindings& prepared) {
}
}
void RenderExecutor::PrepareGraphicsBindings(std::span<PreparedBindings* const> stages) {
void RenderExecutor::PrepareGraphicsBindings(std::span<PreparedBindings* const> stages,
std::span<RenderColorInfo> colors) {
bool uses_dma = false;
for (auto* stage: stages) {
FindBuffers(*stage);
@@ -859,12 +889,29 @@ void RenderExecutor::PrepareGraphicsBindings(std::span<PreparedBindings* const>
if (uses_dma) {
m_context.PrepareBda();
}
for (auto* stage: stages) {
RebindBuffers(*stage);
}
for (auto* stage: stages) {
RebindImages(*stage);
}
auto& cache = m_context.GetTextureCache();
for (auto& target: colors) {
EXIT_IF(!target.image_id);
const auto old_image = cache.m_slot_images.try_get(target.image_id);
if (old_image == nullptr || (!old_image->registered && !old_image->info.data.Empty()) ||
old_image->binding.needs_rebind) {
if (old_image != nullptr) {
old_image->binding = {};
}
target.desc.view_info.base_level = target.guest_mip_level;
target.desc.view_info.base_layer = target.guest_array_layer;
target.image_id = cache.FindImage(target.desc);
BindRenderTarget(target.image_id);
}
}
// Discovery can read back PS5 metadata and submit the scheduler. Reserve draw buffers only
// after image identities are final; attachment layout transitions follow buffer alias copies.
for (auto* stage: stages) {
RebindBuffers(*stage);
}
}
void RenderExecutor::CommitBindings(CommandBuffer& buffer,
@@ -89,12 +89,7 @@ template <typename... Args>
void PipelineCacheLog(fmt::format_string<Args...> format, Args&&... args) {
auto message = fmt::format(format, std::forward<Args>(args)...);
message += '\n';
if (Log::GetDirection() != Log::Direction::Console) {
std::fwrite(message.data(), 1, message.size(), stdout);
std::fflush(stdout);
}
Log::Write(message);
Log::Flush();
Log::WriteToConsoleAndLog(message);
}
bool ReadShaderGuestMemory(void*, uint64_t address, uint32_t* value) {
@@ -253,12 +248,7 @@ struct PipelineCache::ProgramCache {
}
DumpShaderSpirv(stage_name, options.shader_hash, result.spirv);
vk::ShaderModuleCreateInfo create_info {};
create_info.codeSize = result.spirv.size() * sizeof(uint32_t);
create_info.pCode = result.spirv.data();
vk::ShaderModule module = nullptr;
RequireVulkanSuccess(device.createShaderModule(&create_info, nullptr, &module),
"create recompiled shader module");
const auto module = CompileSPV(result.spirv, device);
EXIT_IF(module == nullptr);
if (options.dump_ir) {
LOGF("%s SPIR-V words=%" PRIu64 " wave_size=%u\n", options.dump_label,
@@ -351,7 +341,7 @@ struct PipelineCache::ProgramCache {
options.user_data_base = 0;
options.wave_size = stage == ShaderType::Mesh ? input_info.mesh.wave_size : 64u;
}
} else if constexpr (std::is_same_v<InputInfo, ShaderComputeInputInfo>) {
} else {
options.wave_size = input_info.wave_size;
}
auto translated = ShaderRecompiler::TranslateProgram(params.code, options);
@@ -676,13 +666,16 @@ PipelineCache::Pipeline& PipelineCache::GetGraphicsPipeline(
key.ps_shader_id = ps_id;
auto& static_params = key.static_params;
auto& rendering = key.rendering;
rendering.color_count = color_count;
rendering.color_count = 0;
uint32_t attachment_samples = 0;
for (uint32_t i = 0; i < color_count; i++) {
const auto slot = colors[i].target_slot;
EXIT_IF(slot >= RENDER_COLOR_ATTACHMENTS_MAX);
rendering.color_count = std::max(rendering.color_count, slot + 1);
EXIT_IF(!colors[i].image_id || colors[i].desc.view_info.format == vk::Format::eUndefined);
static_params.color_mask[i] = colors[i].export_mapping.ApplyMask(
static_params.color_mask[slot] = colors[i].export_mapping.ApplyMask(
render_target_mask_slot(ctx.GetRenderTargetMask(), colors[i].target_slot));
rendering.color_formats[i] = colors[i].desc.view_info.format;
rendering.color_formats[slot] = colors[i].desc.view_info.format;
if (attachment_samples == 0) {
attachment_samples = colors[i].desc.info.samples;
} else if (attachment_samples != colors[i].desc.info.samples) {
@@ -691,14 +684,14 @@ PipelineCache::Pipeline& PipelineCache::GetGraphicsPipeline(
}
const auto& rt = ctx.GetRenderTarget(colors[i].target_slot);
const auto& bc = ctx.GetBlendControl(colors[i].target_slot);
static_params.color_srcblend[i] = bc.color_srcblend;
static_params.color_comb_fcn[i] = bc.color_comb_fcn;
static_params.color_destblend[i] = bc.color_destblend;
static_params.alpha_srcblend[i] = bc.alpha_srcblend;
static_params.alpha_comb_fcn[i] = bc.alpha_comb_fcn;
static_params.alpha_destblend[i] = bc.alpha_destblend;
static_params.separate_alpha_blend[i] = bc.separate_alpha_blend;
static_params.blend_enable[i] = bc.enable && !rt.info.blend_bypass;
static_params.color_srcblend[slot] = bc.color_srcblend;
static_params.color_comb_fcn[slot] = bc.color_comb_fcn;
static_params.color_destblend[slot] = bc.color_destblend;
static_params.alpha_srcblend[slot] = bc.alpha_srcblend;
static_params.alpha_comb_fcn[slot] = bc.alpha_comb_fcn;
static_params.alpha_destblend[slot] = bc.alpha_destblend;
static_params.separate_alpha_blend[slot] = bc.separate_alpha_blend;
static_params.blend_enable[slot] = bc.enable && !rt.info.blend_bypass;
}
const bool with_depth =
depth.desc.view_info.format != vk::Format::eUndefined && static_cast<bool>(depth.image_id);
@@ -747,9 +740,6 @@ PipelineCache::Pipeline& PipelineCache::GetGraphicsPipeline(
static_params.depth_bounds_test_enable = depth.depth_bounds_test_enable;
static_params.depth_min_bounds = depth.depth_min_bounds;
static_params.depth_max_bounds = depth.depth_max_bounds;
static_params.stencil_test_enable = depth.stencil_test_enable;
static_params.stencil_front = depth.stencil_static_front;
static_params.stencil_back = depth.stencil_static_back;
const bool rect_list =
command.GetUserConfig().GetPrimType() == Prospero::PrimitiveType::kRectList;
static_params.cull_back = !rect_list && mc.cull_back;
@@ -42,9 +42,6 @@ struct PipelineStaticParameters {
bool depth_bounds_test_enable = false;
float depth_min_bounds = 0.0f;
float depth_max_bounds = 0.0f;
bool stencil_test_enable = false;
PipelineStencilStaticState stencil_front;
PipelineStencilStaticState stencil_back;
uint32_t color_mask[RENDER_COLOR_ATTACHMENTS_MAX] = {};
bool cull_front = false;
bool cull_back = false;
@@ -68,7 +65,7 @@ struct PipelineStaticParameters {
static_assert(std::is_trivially_copyable_v<PipelineStaticParameters>);
static_assert(std::is_standard_layout_v<PipelineStaticParameters>);
static_assert(alignof(PipelineStaticParameters) == 1);
static_assert(sizeof(PipelineStaticParameters) == 158);
static_assert(sizeof(PipelineStaticParameters) == 125);
struct PipelineRenderingState {
std::array<vk::Format, RENDER_COLOR_ATTACHMENTS_MAX> color_formats {};
@@ -231,30 +231,20 @@ void CreatePipelineInternal(GraphicContext& graphics, PipelineCache::Pipeline& p
vk::ShaderModule tess_control_shader_module = nullptr;
vk::ShaderModule tess_eval_shader_module = nullptr;
vk::ShaderModuleCreateInfo create_info {};
vk::Result result {};
if (rect_list) {
const auto shaders =
BuildRectListShaders(vs_input_info, ps_active ? ps_input_info : nullptr);
create_info.codeSize = shaders.control.size() * 4;
create_info.pCode = shaders.control.data();
result =
graphics.device.createShaderModule(&create_info, nullptr, &tess_control_shader_module);
tess_control_shader_module = CompileSPV(shaders.control, graphics.device);
if (graphics_debug_dump_enabled()) {
LOGF("PipelineTrace: vkCreateShaderModule RectList TCS done result=%s module=%p\n",
vk::to_string(result).c_str(), static_cast<void*>(tess_control_shader_module));
LOGF("PipelineTrace: vkCreateShaderModule RectList TCS done module=%p\n",
static_cast<void*>(tess_control_shader_module));
}
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess);
create_info.codeSize = shaders.evaluation.size() * 4;
create_info.pCode = shaders.evaluation.data();
result =
graphics.device.createShaderModule(&create_info, nullptr, &tess_eval_shader_module);
tess_eval_shader_module = CompileSPV(shaders.evaluation, graphics.device);
if (graphics_debug_dump_enabled()) {
LOGF("PipelineTrace: vkCreateShaderModule RectList TES done result=%s module=%p\n",
vk::to_string(result).c_str(), static_cast<void*>(tess_eval_shader_module));
LOGF("PipelineTrace: vkCreateShaderModule RectList TES done module=%p\n",
static_cast<void*>(tess_eval_shader_module));
}
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess);
}
EXIT_NOT_IMPLEMENTED(
@@ -470,8 +460,8 @@ void CreatePipelineInternal(GraphicContext& graphics, PipelineCache::Pipeline& p
" set_layouts=1 push_constants=%" PRIu32 "\n",
vertex_program.id, ps_active ? pixel_program.id : 0, 1u);
}
result = graphics.device.createPipelineLayout(&pipeline_layout_info, nullptr,
&pipeline.pipeline_layout);
auto result = graphics.device.createPipelineLayout(&pipeline_layout_info, nullptr,
&pipeline.pipeline_layout);
if (graphics_debug_dump_enabled()) {
LOGF("PipelineTrace: vkCreatePipelineLayout done result=%s layout=%p\n",
vk::to_string(result).c_str(), static_cast<void*>(pipeline.pipeline_layout));
@@ -487,15 +477,6 @@ void CreatePipelineInternal(GraphicContext& graphics, PipelineCache::Pipeline& p
#else
(static_params.depth_bounds_test_enable ? VK_TRUE : VK_FALSE);
#endif
depth_stencil_info.stencilTestEnable = (static_params.stencil_test_enable ? VK_TRUE : VK_FALSE);
depth_stencil_info.front.failOp = static_params.stencil_front.failOp;
depth_stencil_info.front.passOp = static_params.stencil_front.passOp;
depth_stencil_info.front.depthFailOp = static_params.stencil_front.depthFailOp;
depth_stencil_info.front.compareOp = static_params.stencil_front.compareOp;
depth_stencil_info.back.failOp = static_params.stencil_back.failOp;
depth_stencil_info.back.passOp = static_params.stencil_back.passOp;
depth_stencil_info.back.depthFailOp = static_params.stencil_back.depthFailOp;
depth_stencil_info.back.compareOp = static_params.stencil_back.compareOp;
depth_stencil_info.minDepthBounds = static_params.depth_min_bounds;
depth_stencil_info.maxDepthBounds = static_params.depth_max_bounds;
@@ -508,6 +489,8 @@ void CreatePipelineInternal(GraphicContext& graphics, PipelineCache::Pipeline& p
vk::DynamicState::eDepthCompareOp,
vk::DynamicState::eDepthBiasEnable,
vk::DynamicState::eDepthBias,
vk::DynamicState::eStencilTestEnable,
vk::DynamicState::eStencilOp,
vk::DynamicState::eStencilCompareMask,
vk::DynamicState::eStencilReference,
vk::DynamicState::eStencilWriteMask,
+2 -1
View File
@@ -176,7 +176,8 @@ private:
[[nodiscard]] TextureBinding ResolveTexture(const ShaderRecompiler::IR::ImageResource& resource,
const ShaderRecompiler::IR::DescriptorValue& value);
void PrepareGraphicsBindings(std::span<PreparedBindings* const> stages);
void PrepareGraphicsBindings(std::span<PreparedBindings* const> stages,
std::span<RenderColorInfo> colors);
void ResolveRenderColorTarget(CommandBuffer& buffer, RenderColorInfo& target,
uint32_t render_target_slice_offset, uint32_t render_target_slot,
bool ignore_target_mask = false, bool exact_format = false);
@@ -172,7 +172,7 @@ bool RenderExecutor::TryConsumeComputeImageClear(const ShaderComputeInputInfo& i
1, view.base_layer, view.layer_count};
vk::ClearValue clear {};
clear.depthStencil = vk::ClearDepthStencilValue {0.0f, fill.value};
cache.ClearImage(command, binding.image_id, range, clear);
cache.ClearImage(command, binding.image_id, image.backing.format, range, clear);
return true;
}
ShaderBufferResource descriptor;
@@ -183,14 +183,6 @@ bool RenderExecutor::TryConsumeComputeImageClear(const ShaderComputeInputInfo& i
return false;
}
if (!cache.ClearImageFromBuffer(command, descriptor.Base48(), size, packed_clear)) {
// Track deferred DCC state while the original dispatch writes the metadata allocation.
cache.TrackDccFill(descriptor.Base48(), size, packed_clear);
static std::atomic<uint32_t> logged_metadata_clears {0};
if (logged_metadata_clears.fetch_add(1, std::memory_order_relaxed) < 32) {
LOGF("GraphicsRenderDispatchDirect: metadata fill shader=0x%016" PRIx64
" addr=0x%016" PRIx64 " size=0x%016" PRIx64 " value=0x%08" PRIx32 "\n",
input.stage.program->shader_hash, descriptor.Base48(), size, packed_clear);
}
return false;
}
static std::atomic<uint32_t> logged_clears {0};
@@ -214,6 +206,17 @@ void RenderExecutor::DispatchDirect(uint64_t submit_id, CommandBuffer& buffer,
thread_group_x, thread_group_y, thread_group_z, mode,
sh_ctx.GetCs().cs_regs.data_addr);
if (thread_group_x == 0 || thread_group_y == 0 || thread_group_z == 0) {
static std::atomic<uint32_t> log_count {0};
if (log_count.fetch_add(1, std::memory_order_relaxed) < 32) {
LOGF("GraphicsRenderDispatchDirect: skipping zero-sized dispatch groups=%ux%ux%u "
"mode=0x%08" PRIx32 " shader=0x%016" PRIx64 "\n",
thread_group_x, thread_group_y, thread_group_z, mode,
sh_ctx.GetCs().cs_regs.data_addr);
}
return;
}
Common::LockGuard lock(m_context.GetMutex());
if (sh_ctx.GetCs().cs_regs.data_addr == 0) {
LOGF("GraphicsRenderDispatchDirect: temporary: ignoring dispatch with null CS shader, "
@@ -355,17 +358,6 @@ void RenderExecutor::DispatchDirect(uint64_t submit_id, CommandBuffer& buffer,
}
}
if (thread_group_x == 0 || thread_group_y == 0 || thread_group_z == 0) {
static std::atomic<uint32_t> log_count {0};
if (log_count.fetch_add(1, std::memory_order_relaxed) < 32) {
LOGF("GraphicsRenderDispatchDirect: skipping zero-sized dispatch groups=%ux%ux%u "
"mode=0x%08" PRIx32 " shader=0x%016" PRIx64 "\n",
thread_group_x, thread_group_y, thread_group_z, mode,
sh_ctx.GetCs().cs_regs.data_addr);
}
return;
}
buffer.EndRendering();
auto& pipeline =
m_context.GetPipelineCache().GetComputePipeline(input_info, compute_program);
@@ -374,8 +366,8 @@ void RenderExecutor::DispatchDirect(uint64_t submit_id, CommandBuffer& buffer,
if (program.info.uses_dma) {
m_context.PrepareBda();
}
RebindBuffers(bindings);
RebindImages(bindings);
RebindBuffers(bindings);
auto vk_buffer = buffer.Handle();
PreparedBindings* descriptor_stage = &bindings;
+94 -100
View File
@@ -15,6 +15,7 @@
#include "graphics/host_gpu/renderer/colorRenderTarget.h"
#include "graphics/host_gpu/renderer/debug.h"
#include "graphics/host_gpu/renderer/depthRenderTarget.h"
#include "graphics/host_gpu/renderer/image/textureCommon.h"
#include "graphics/host_gpu/renderer/pipeline/pipelineCache.h"
#include "graphics/host_gpu/renderer/pipeline/shaderResourceBarrier.h"
#include "graphics/host_gpu/renderer/render.h"
@@ -308,24 +309,12 @@ static void LogDrawInputState(const CommandBuffer& buffer, const RenderColorInfo
static void SetGraphicsDynamicParams(const CommandBuffer& buffer, vk::CommandBuffer vk_buffer,
const ShaderVertexInputInfo& vs_input_info,
const RenderColorInfo* colors, uint32_t color_count,
const RenderDepthInfo& depth) {
const RenderDepthInfo& depth, const RenderState& rendering) {
KYTY_PROFILER_FUNCTION();
EXIT_IF(colors == nullptr);
const auto& ctx = buffer.GetRegisters();
const auto& vp = ctx.GetScreenViewport();
vk::Extent2D framebuffer_extent {};
if (color_count > 0 && colors[0].image_id) {
framebuffer_extent = colors[0].Extent();
} else if (depth.image_id) {
framebuffer_extent = {depth.desc.info.extent.width, depth.desc.info.extent.height};
} else {
const auto& limits = buffer.GetGraphics().GetPhysicalDeviceProperties().limits;
framebuffer_extent = {limits.maxFramebufferWidth, limits.maxFramebufferHeight};
}
const auto& vp = ctx.GetScreenViewport();
const vk::Extent2D framebuffer_extent {rendering.width, rendering.height};
const auto& outputs = vs_input_info.stage.program->info.outputs;
const bool indexed_viewports =
std::any_of(outputs.begin(), outputs.end(), [](const auto& output) {
@@ -403,19 +392,16 @@ static void SetGraphicsDynamicParams(const CommandBuffer& buffer, vk::CommandBuf
vk_buffer.setDepthBias(constant_factor, poly_offset.clamp, slope_factor);
}
vk_buffer.setStencilTestEnable(depth.stencil_test_enable ? VK_TRUE : VK_FALSE);
if (depth.stencil_test_enable) {
vk_buffer.setStencilCompareMask(vk::StencilFaceFlagBits::eFront,
depth.stencil_dynamic_front.compareMask);
vk_buffer.setStencilCompareMask(vk::StencilFaceFlagBits::eBack,
depth.stencil_dynamic_back.compareMask);
vk_buffer.setStencilWriteMask(vk::StencilFaceFlagBits::eFront,
depth.stencil_dynamic_front.writeMask);
vk_buffer.setStencilWriteMask(vk::StencilFaceFlagBits::eBack,
depth.stencil_dynamic_back.writeMask);
vk_buffer.setStencilReference(vk::StencilFaceFlagBits::eFront,
depth.stencil_dynamic_front.reference);
vk_buffer.setStencilReference(vk::StencilFaceFlagBits::eBack,
depth.stencil_dynamic_back.reference);
const auto set_stencil = [&](vk::StencilFaceFlagBits face, const vk::StencilOpState& state) {
vk_buffer.setStencilOp(face, state.failOp, state.passOp, state.depthFailOp, state.compareOp);
vk_buffer.setStencilCompareMask(face, state.compareMask);
vk_buffer.setStencilWriteMask(face, state.writeMask);
vk_buffer.setStencilReference(face, state.reference);
};
set_stencil(vk::StencilFaceFlagBits::eFront, depth.stencil_front);
set_stencil(vk::StencilFaceFlagBits::eBack, depth.stencil_back);
}
#if defined(__APPLE__)
@@ -423,15 +409,11 @@ static void SetGraphicsDynamicParams(const CommandBuffer& buffer, vk::CommandBuf
// eColorWriteEnableEXT dynamic state and relies on the static colorWriteMask instead.
#else
vk::Bool32 enable[RENDER_COLOR_ATTACHMENTS_MAX] = {};
// Color-control operation selects special color-buffer paths, not the normal component write
// mask. Attachment availability therefore follows the target write mask.
for (uint32_t i = 0; i < color_count; i++) {
enable[i] = render_target_mask_slot(ctx.GetRenderTargetMask(), colors[i].target_slot) != 0
? VK_TRUE
: VK_FALSE;
for (uint32_t slot = 0; slot < rendering.num_color_attachments; slot++) {
enable[slot] = rendering.color_attachments[slot].image_view != nullptr;
}
if (color_count != 0) {
vk_buffer.setColorWriteEnableEXT(color_count, enable);
if (rendering.num_color_attachments != 0) {
vk_buffer.setColorWriteEnableEXT(rendering.num_color_attachments, enable);
}
#endif
}
@@ -477,19 +459,15 @@ RenderState RenderExecutor::AcquireRenderTargets(CommandBuffer& buffer, RenderCo
state.width = std::numeric_limits<uint32_t>::max();
state.height = std::numeric_limits<uint32_t>::max();
state.num_layers = std::numeric_limits<uint32_t>::max();
state.num_color_attachments = color_count;
state.num_color_attachments = 0;
uint32_t attachment_samples = 0;
for (uint32_t i = 0; i < color_count; i++) {
auto& target = colors[i];
EXIT_IF(!target.image_id);
const auto old_image = cache.m_slot_images.try_get(target.image_id);
if (old_image == nullptr || (!old_image->registered && !old_image->info.data.Empty()) ||
old_image->binding.needs_rebind) {
if (old_image != nullptr) {
old_image->binding = {};
}
target.image_id = cache.FindImage(target.desc);
BindRenderTarget(target.image_id);
const auto owner = cache.m_slot_images.try_get(target.image_id);
if (owner == nullptr || (!owner->registered && !owner->info.data.Empty()) ||
owner->binding.needs_rebind) {
EXIT("color target changed after render-state discovery\n");
}
const auto image_view = cache.FindRenderTarget(target.image_id, target.desc);
auto& image = cache.GetImage(target.image_id);
@@ -523,7 +501,8 @@ RenderState RenderExecutor::AcquireRenderTargets(CommandBuffer& buffer, RenderCo
state.width = std::min(state.width, extent.width);
state.height = std::min(state.height, extent.height);
state.num_layers = std::min(state.num_layers, view.layer_count);
auto& attachment = state.color_attachments[i];
state.num_color_attachments = std::max(state.num_color_attachments, target.target_slot + 1);
auto& attachment = state.color_attachments[target.target_slot];
attachment.image_view = image_view;
attachment.image_layout = layout;
}
@@ -709,6 +688,7 @@ struct PreparedVertexBuffers {
std::array<vk::Buffer, MaxBuffers> buffers {};
std::array<vk::DeviceSize, MaxBuffers> offsets {};
std::array<vk::DeviceSize, MaxBuffers> sizes {};
uint32_t count = 0;
};
@@ -793,6 +773,7 @@ static PreparedVertexBuffers AcquireVertexBuffers(CommandBuffer& b
prepared.buffers[i] = range->binding.first->Handle();
prepared.offsets[i] = range->binding.second + vertex.addr - range->base_address;
prepared.sizes[i] = std::min(size, range->acquired_end - vertex.addr);
SetVulkanObjectNameF(
buffer.GetContext().GetGraphics().device, prepared.buffers[i],
"Kyty.VertexBuffer[slot={} guest=0x{:016x} size=0x{:x} stride={} records={}]", i,
@@ -896,42 +877,6 @@ static bool ResolvePrimitiveRestart(const CommandBuffer& buffer,
return false;
}
bool RenderExecutor::PrepareDrawRenderState(CommandBuffer& buffer, const DrawCallInfo& draw,
uint32_t render_target_slice_offset,
DrawRenderState& state) {
auto& ctx = buffer.GetRegisters();
if (ResolveColorTargets(buffer, render_target_slice_offset)) {
return false;
}
if (draw.IsIndexed()) {
LogDrawPhase(draw.Name(), "ResolveRenderColorTarget");
}
for (uint32_t slot = 0; slot < RENDER_COLOR_ATTACHMENTS_MAX; slot++) {
if (slot == 0 || (render_target_mask_slot(ctx.GetRenderTargetMask(), slot) != 0 &&
ctx.GetRenderTarget(slot).base.addr != 0)) {
ResolveRenderColorTarget(buffer, state.color_info[state.color_count],
render_target_slice_offset, slot);
if (state.color_info[state.color_count].image_id) {
state.color_count++;
}
}
}
if (draw.IsIndexed()) {
LogDrawPhase(draw.Name(), "ResolveRenderDepthTarget");
}
ResolveRenderDepthTarget(buffer, state.depth_info);
state.ps_active = DrawHasActivePixelShader(buffer);
if (state.color_count == 0 && !state.depth_info.image_id && !state.ps_active) {
LogFramebufferSkip(draw.Name(), state.color_info[0], state.depth_info, buffer,
draw.index_count, 0);
return false;
}
return true;
}
static void RefreshShaders(CommandBuffer& buffer, const DrawCallInfo& draw,
DrawRenderState& state) {
auto& ctx = buffer.GetRegisters();
@@ -945,8 +890,14 @@ static void RefreshShaders(CommandBuffer& buffer, const DrawCallInfo& draw,
state.ps_input_info = {};
std::array<Prospero::ColorComponentMapping, RENDER_COLOR_ATTACHMENTS_MAX>
target_export_mapping {};
for (uint32_t i = 0; i < state.color_count; i++) {
target_export_mapping[state.color_info[i].target_slot] = state.color_info[i].export_mapping;
for (uint32_t slot = 0; slot < RENDER_COLOR_ATTACHMENTS_MAX; slot++) {
const auto& rt = ctx.GetRenderTarget(slot);
if (rt.base.addr != 0 && render_target_mask_slot(ctx.GetRenderTargetMask(), slot) != 0) {
target_export_mapping[slot] =
TextureGetRenderTargetFormat(rt.info.format, rt.info.channel_type,
rt.info.channel_order)
.export_mapping;
}
}
auto& pipeline_cache = buffer.GetContext().GetPipelineCache();
if (draw.IsIndexed()) {
@@ -957,6 +908,45 @@ static void RefreshShaders(CommandBuffer& buffer, const DrawCallInfo& draw,
target_export_mapping, state.ps_active, state.vertex_info, state.ps_input_info);
}
bool RenderExecutor::PrepareDrawRenderState(CommandBuffer& buffer, const DrawCallInfo& draw,
uint32_t render_target_slice_offset,
DrawRenderState& state) {
state.ps_active = DrawHasActivePixelShader(buffer);
RefreshShaders(buffer, draw, state);
uint32_t mrt_mask = 0;
if (state.ps_active) {
for (const auto& output: state.ps_input_info.stage.program->info.outputs) {
if (output.kind == ShaderRecompiler::IR::StageOutputKind::Mrt) {
mrt_mask |= 1u << output.index;
}
}
}
if (draw.IsIndexed()) {
LogDrawPhase(draw.Name(), "ResolveRenderColorTarget");
}
for (uint32_t slot = 0; slot < RENDER_COLOR_ATTACHMENTS_MAX; slot++) {
if ((mrt_mask & (1u << slot)) != 0) {
ResolveRenderColorTarget(buffer, state.color_info[state.color_count],
render_target_slice_offset, slot);
if (state.color_info[state.color_count].image_id) {
state.color_count++;
}
}
}
if (draw.IsIndexed()) {
LogDrawPhase(draw.Name(), "ResolveRenderDepthTarget");
}
ResolveRenderDepthTarget(buffer, state.depth_info);
if (state.color_count == 0 && !state.depth_info.image_id && !state.ps_active) {
LogFramebufferSkip(draw.Name(), state.color_info[0], state.depth_info, buffer,
draw.index_count, 0);
return false;
}
return true;
}
static PreparedIndexBuffer PrepareIndexBuffer(CommandBuffer& buffer,
const DrawIndexBufferSource& source) {
PreparedIndexBuffer prepared;
@@ -989,8 +979,9 @@ static void CommitVertexBuffers(vk::CommandBuffer vk_buffer,
EXIT_IF(prepared.buffers[i] == nullptr);
}
if (prepared.count != 0) {
vk_buffer.bindVertexBuffers(0, prepared.count, prepared.buffers.data(),
prepared.offsets.data());
// Guest descriptor bounds must survive allocation merging in the cache.
vk_buffer.bindVertexBuffers2(0, prepared.count, prepared.buffers.data(),
prepared.offsets.data(), prepared.sizes.data(), nullptr);
}
}
@@ -1077,7 +1068,13 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, CommandBuffer& buff
uint32_t mesh_groups = 0;
if (mesh_active) {
const auto& mesh = state.vertex_info[0].mesh;
if (primitive_restart_enable || mesh.primitives_per_group == 0) {
static std::atomic_bool restart_warned = false;
if (primitive_restart_enable && !restart_warned.exchange(true, std::memory_order_relaxed)) {
std::printf("Warning: primitive restart is not implemented for mesh shaders; "
"continuing draw (primitive=%u indexed=%u)\n",
static_cast<uint32_t>(ucfg.GetPrimType()), draw.IsIndexed());
}
if (mesh.primitives_per_group == 0) {
EXIT("unsupported mesh draw: primitive=%u indexed=%u restart=%u\n",
static_cast<uint32_t>(ucfg.GetPrimType()), draw.IsIndexed(), primitive_restart_enable);
}
@@ -1116,7 +1113,7 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, CommandBuffer& buff
descriptor_stages[stage_count++] = &*bindings.pixel;
}
const auto stages = std::span {descriptor_stages.data(), stage_count};
PrepareGraphicsBindings(stages);
PrepareGraphicsBindings(stages, std::span {state.color_info, state.color_count});
PreparedVertexBuffers vertex_bindings;
PreparedIndexBuffer index_binding;
if (!mesh_active) {
@@ -1164,8 +1161,7 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, CommandBuffer& buff
CommitIndexBuffer(vk_buffer, index_binding);
}
SetGraphicsDynamicParams(buffer, vk_buffer, vertex_stages.back(), state.color_info,
state.color_count, state.depth_info);
SetGraphicsDynamicParams(buffer, vk_buffer, vertex_stages.back(), state.depth_info, rendering);
if (m_context.GetGraphics().attachment_feedback_loop_enabled) {
vk_buffer.setAttachmentFeedbackLoopEnableEXT(
rendering.depth_stencil_attachment.image_layout ==
@@ -1230,7 +1226,8 @@ void RenderExecutor::DrawIndex(uint64_t submit_id, CommandBuffer& buffer,
return;
}
if (ConsumeMetadataColorOperation(buffer) || DepthStencilCopy(buffer)) {
if (ConsumeMetadataColorOperation(buffer) || DepthStencilCopy(buffer) ||
ResolveColorTargets(buffer, args.render_target_slice_offset)) {
ResetBindings();
return;
}
@@ -1304,8 +1301,6 @@ void RenderExecutor::DrawIndex(uint64_t submit_id, CommandBuffer& buffer,
return;
}
RefreshShaders(buffer, draw, state);
LogDrawStateIfNeeded(buffer, draw, state, args.index_type_and_size,
args.index_addr);
@@ -1342,7 +1337,8 @@ void RenderExecutor::DrawAuto(uint64_t submit_id, CommandBuffer& buffer, const D
return;
}
if (ConsumeMetadataColorOperation(buffer) || DepthStencilCopy(buffer)) {
if (ConsumeMetadataColorOperation(buffer) || DepthStencilCopy(buffer) ||
ResolveColorTargets(buffer, args.render_target_slice_offset)) {
ResetBindings();
return;
}
@@ -1371,18 +1367,16 @@ void RenderExecutor::DrawAuto(uint64_t submit_id, CommandBuffer& buffer, const D
const DrawCallInfo draw {CommandBufferDebugOp::DrawIndexAuto,
args.vertex_count, args.instance_count, args.first_instance};
DrawRenderState state {};
if (!PrepareDrawRenderState(buffer, draw, args.render_target_slice_offset, state)) {
ResetBindings();
return;
}
vk::PrimitiveTopology topology = vk::PrimitiveTopology::ePointList;
if (!GetDrawTopology(ucfg, true, topology)) {
ResetBindings();
return;
}
RefreshShaders(buffer, draw, state);
DrawRenderState state {};
if (!PrepareDrawRenderState(buffer, draw, args.render_target_slice_offset, state)) {
ResetBindings();
return;
}
const bool rect_list = ucfg.GetPrimType() == Prospero::PrimitiveType::kRectList;
if (rect_list && state.vertex_info[0].buffers_num == 0 &&
@@ -5,7 +5,6 @@
#include <array>
#include <cstdint>
#include <type_traits>
namespace Libs::Graphics {
@@ -67,33 +66,6 @@ inline constexpr TargetViewInfo ResolveTargetViewInfo(uint32_t base_layer, uint3
base_layer, last_layer - base_layer + 1u, last_layer + 1u};
}
#pragma pack(push, 1)
struct PipelineStencilStaticState {
vk::StencilOp failOp = vk::StencilOp::eKeep;
vk::StencilOp passOp = vk::StencilOp::eKeep;
vk::StencilOp depthFailOp = vk::StencilOp::eKeep;
vk::CompareOp compareOp = vk::CompareOp::eNever;
};
struct PipelineStencilDynamicState {
uint32_t compareMask = 0;
uint32_t writeMask = 0;
uint32_t reference = 0;
};
#pragma pack(pop)
static_assert(std::is_trivially_copyable_v<PipelineStencilStaticState>);
static_assert(std::is_standard_layout_v<PipelineStencilStaticState>);
static_assert(alignof(PipelineStencilStaticState) == 1);
static_assert(sizeof(PipelineStencilStaticState) ==
sizeof(vk::StencilOp) * 3 + sizeof(vk::CompareOp));
static_assert(std::is_trivially_copyable_v<PipelineStencilDynamicState>);
static_assert(std::is_standard_layout_v<PipelineStencilDynamicState>);
static_assert(alignof(PipelineStencilDynamicState) == 1);
static_assert(sizeof(PipelineStencilDynamicState) == sizeof(uint32_t) * 3);
} // namespace Libs::Graphics
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_RENDERTARGET_H_
+10
View File
@@ -123,4 +123,14 @@ void RequireVulkanSuccess(vk::Result result, const char* operation) {
}
}
vk::ShaderModule CompileSPV(std::span<const uint32_t> code, vk::Device device) {
vk::ShaderModuleCreateInfo create_info {};
create_info.codeSize = code.size_bytes();
create_info.pCode = code.data();
vk::ShaderModule module = nullptr;
RequireVulkanSuccess(device.createShaderModule(&create_info, nullptr, &module),
"create SPIR-V shader module");
return module;
}
} // namespace Libs::Graphics
+2
View File
@@ -15,6 +15,7 @@
#include <cstdint>
#include <fmt/format.h>
#include <span>
#include <string>
#include <utility>
#include <vector>
@@ -27,6 +28,7 @@ using VulkanMemoryBarrier = vk::MemoryBarrier;
vk::Format VulkanFormat(Prospero::BufferFormat guest_format);
void RequireVulkanSuccess(vk::Result result, const char* operation);
vk::ShaderModule CompileSPV(std::span<const uint32_t> code, vk::Device device);
template <typename Handle, typename... Args>
void SetVulkanObjectNameF(vk::Device device, Handle handle, fmt::format_string<Args...> format,
@@ -220,6 +220,8 @@ uint32_t DefaultKeyboardButton(int key_code) {
case SDLK_l: return Controller::PAD_BUTTON_CIRCLE;
case SDLK_q: return Controller::PAD_BUTTON_L1;
case SDLK_e: return Controller::PAD_BUTTON_R1;
case SDLK_z: return Controller::PAD_BUTTON_L2;
case SDLK_c: return Controller::PAD_BUTTON_R2;
case SDLK_LSHIFT: return Controller::PAD_BUTTON_L3;
case SDLK_LCTRL: return Controller::PAD_BUTTON_R3;
case SDLK_RETURN:
@@ -201,6 +201,8 @@ static void VulkanFindPhysicalDevice(vk::Instance instance, vk::SurfaceKHR surfa
vk::PhysicalDeviceVulkan13Features features13 {};
vk::PhysicalDeviceColorWriteEnableFeaturesEXT color_write_ext {};
vk::PhysicalDeviceImageViewMinLodFeaturesEXT image_view_min_lod {};
color_write_ext.pNext = &image_view_min_lod;
vk::PhysicalDeviceDepthClipEnableFeaturesEXT depth_clip_enable {};
depth_clip_enable.pNext = &color_write_ext;
@@ -235,6 +237,10 @@ static void VulkanFindPhysicalDevice(vk::Instance instance, vk::SurfaceKHR surfa
skip_device = true;
#endif
}
if (image_view_min_lod.minLod != VK_TRUE) {
LOGF("image view minLod is not supported\n");
skip_device = true;
}
if (depth_clip_control.depthClipControl != VK_TRUE) {
LOGF("depthClipControl is not supported\n");
@@ -514,11 +520,14 @@ static vk::Device VulkanCreateDevice(GraphicContext& graphics,
depth_clip_enable.depthClipEnable = VK_TRUE;
vk::PhysicalDeviceDepthClipControlFeaturesEXT depth_clip_control {};
vk::PhysicalDeviceImageViewMinLodFeaturesEXT image_view_min_lod {};
image_view_min_lod.minLod = VK_TRUE;
depth_clip_control.pNext = &image_view_min_lod;
// MoltenVK lacks VK_EXT_depth_clip_enable and VK_EXT_color_write_enable, so drop those
// feature structs from the chain on macOS (the renderer falls back to default depth
// clipping and static color-write masks).
#if !defined(__APPLE__)
depth_clip_control.pNext = &depth_clip_enable;
image_view_min_lod.pNext = &depth_clip_enable;
#endif
depth_clip_control.depthClipControl = VK_TRUE;
@@ -984,7 +993,8 @@ void WindowContext::CreateVulkan() {
std::vector<const char*> device_extensions = {
VK_KHR_SWAPCHAIN_EXTENSION_NAME, VK_EXT_DEPTH_CLIP_CONTROL_EXTENSION_NAME,
VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME, "VK_KHR_maintenance1"};
VK_EXT_IMAGE_VIEW_MIN_LOD_EXTENSION_NAME, VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME,
"VK_KHR_maintenance1"};
#if defined(__APPLE__)
// MoltenVK lacks VK_EXT_depth_clip_enable and VK_EXT_color_write_enable; the renderer
@@ -713,6 +713,20 @@ void WindowContext::ProcessEvent(double time_s) {
}
break;
case SDL_CONTROLLERSENSORUPDATE: {
const auto& sensor = event->csensor;
if (sensor.sensor == SDL_SENSOR_ACCEL || sensor.sensor == SDL_SENSOR_GYRO) {
Controller::SetSensor(sensor.which,
sensor.sensor == SDL_SENSOR_ACCEL ? Controller::Sensor::Accel
: Controller::Sensor::Gyro,
sensor.data,
sensor.timestamp_us != 0
? sensor.timestamp_us
: static_cast<uint64_t>(sensor.timestamp) * 1000);
}
break;
}
case SDL_CONTROLLERDEVICEADDED:
case SDL_CONTROLLERDEVICEREMOVED:
case SDL_CONTROLLERDEVICEREMAPPED: {
@@ -87,7 +87,6 @@ struct EmbeddedFetchSgprInfo {
EmbeddedFetchValueType type = EmbeddedFetchValueType::Unknown;
int attrib_id = 0;
uint32_t value = 0;
std::vector<uint32_t> prolog_loads;
};
using EmbeddedFetchVectorLanes = std::map<uint64_t, EmbeddedFetchSgprInfo>;
@@ -326,7 +325,6 @@ EmbeddedFetchData DetectEmbeddedVertexFetch(const Decoder::Program& decoded
auto& dst = sgprs[DecodedSgprReg(inst.dst)];
dst.type = EmbeddedFetchValueType::Constant;
dst.value = value;
dst.prolog_loads.clear();
} else {
ClearEmbeddedFetchSgprs(sgprs, inst.dst, 1);
}
@@ -337,7 +335,6 @@ EmbeddedFetchData DetectEmbeddedVertexFetch(const Decoder::Program& decoded
auto& dst = sgprs[DecodedSgprReg(inst.dst)];
dst.type = EmbeddedFetchValueType::Constant;
dst.value = inst.src0.value;
dst.prolog_loads.clear();
}
break;
default:
@@ -354,7 +351,6 @@ EmbeddedFetchData DetectEmbeddedVertexFetch(const Decoder::Program& decoded
auto& dst = sgprs[register_id + i];
dst.type = EmbeddedFetchValueType::Attrib;
dst.attrib_id = index + static_cast<int>(i);
dst.prolog_loads = {inst.pc};
}
} else {
ClearEmbeddedFetchSgprs(sgprs, inst.dst, DecodedDstSize(inst));
@@ -372,7 +368,6 @@ EmbeddedFetchData DetectEmbeddedVertexFetch(const Decoder::Program& decoded
dst.type = EmbeddedFetchValueType::Buffer;
dst.attrib_id =
BufferTableAttribFromOffset(raw_offset, static_cast<int>(i));
dst.prolog_loads = {inst.pc};
}
} else if (IsDecodedSgpr(inst.src1) &&
DecodedSgprReg(inst.src1) < sgprs.size() &&
@@ -384,8 +379,6 @@ EmbeddedFetchData DetectEmbeddedVertexFetch(const Decoder::Program& decoded
auto& dst = sgprs[register_id + i];
dst.type = EmbeddedFetchValueType::Buffer;
dst.attrib_id = sgprs[DecodedSgprReg(inst.src1)].attrib_id;
dst.prolog_loads = sgprs[DecodedSgprReg(inst.src1)].prolog_loads;
dst.prolog_loads.push_back(inst.pc);
}
} else {
ClearEmbeddedFetchSgprs(sgprs, inst.dst, DecodedDstSize(inst));
@@ -424,7 +417,6 @@ EmbeddedFetchData DetectEmbeddedVertexFetch(const Decoder::Program& decoded
break;
default: dst.value = src0 + src1; break;
}
dst.prolog_loads.clear();
} else {
ClearEmbeddedFetchSgprs(sgprs, inst.dst, 1);
}
@@ -447,7 +439,6 @@ EmbeddedFetchData DetectEmbeddedVertexFetch(const Decoder::Program& decoded
load.pc = inst.pc;
load.attrib_id = buffer.attrib_id;
load.components = DecodedDstSize(inst);
load.prolog_loads = buffer.prolog_loads;
}
}
break;
@@ -1,6 +1,9 @@
#include "graphics/shader/recompiler/backend/spirv/spirvEmitterInstructions.h"
#include "common/logging/log.h"
#include <algorithm>
#include <atomic>
namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter {
namespace {
@@ -38,7 +41,9 @@ uint32_t EmitBuiltinU32(EmitterState& state, IR::StageInputKind kind, uint32_t c
return EmitAddU32(state, local,
EmitBinaryU32(state, spv::OpIMul, group, ConstantU32(state, size)));
}
const auto variable = InputVariableForKind(state, kind);
const bool centroid = kind == IR::StageInputKind::BaryCoordSmoothCentroid;
const auto variable = InputVariableForKind(
state, centroid ? IR::StageInputKind::BaryCoordSmooth : kind);
if (variable == 0) {
return ConstantU32(state, 0);
}
@@ -46,8 +51,9 @@ uint32_t EmitBuiltinU32(EmitterState& state, IR::StageInputKind kind, uint32_t c
const auto value = state.builder.AllocateId();
const auto bits = state.builder.AllocateId();
state.builder.AddFunction(spv::OpLoad, TypeBool(state), value, variable);
state.builder.AddFunction(spv::OpSelect, TypeU32(state), bits, value, ConstantU32(state, 1),
ConstantU32(state, 0));
// PS5 initializes v_front_face with float +1.0/-1.0 bits.
state.builder.AddFunction(spv::OpSelect, TypeU32(state), bits, value,
ConstantU32(state, 0x3f800000u), ConstantU32(state, 0xbf800000u));
return bits;
}
if (kind == IR::StageInputKind::VertexIndex || kind == IR::StageInputKind::InstanceIndex ||
@@ -70,15 +76,24 @@ uint32_t EmitBuiltinU32(EmitterState& state, IR::StageInputKind kind, uint32_t c
state.builder.AddFunction(spv::OpBitcast, TypeU32(state), bits, value);
return bits;
}
if (kind == IR::StageInputKind::BaryCoordSmooth ||
if (centroid || kind == IR::StageInputKind::BaryCoordSmooth ||
kind == IR::StageInputKind::BaryCoordNoPerspective) {
const auto pointer = state.builder.AllocateId();
const auto value = state.builder.AllocateId();
const auto bits = state.builder.AllocateId();
state.builder.AddFunction(spv::OpAccessChain,
TypePointer(state, spv::StorageClassInput, TypeF32(state)),
pointer, variable, ConstantU32(state, component + 1u));
state.builder.AddFunction(spv::OpLoad, TypeF32(state), value, pointer);
if (centroid) {
const auto coordinates = state.builder.AllocateId();
state.builder.RequireCapability(spv::CapabilityInterpolationFunction);
state.builder.AddFunction(spv::OpExtInst, TypeF32Vector(state, 3), coordinates,
GlslStd450(state), GLSLstd450InterpolateAtCentroid, variable);
state.builder.AddFunction(spv::OpCompositeExtract, TypeF32(state), value,
coordinates, component + 1u);
} else {
const auto pointer = state.builder.AllocateId();
state.builder.AddFunction(spv::OpAccessChain,
TypePointer(state, spv::StorageClassInput, TypeF32(state)),
pointer, variable, ConstantU32(state, component + 1u));
state.builder.AddFunction(spv::OpLoad, TypeF32(state), value, pointer);
}
state.builder.AddFunction(spv::OpBitcast, TypeU32(state), bits, value);
return bits;
}
@@ -499,6 +514,31 @@ void EmitSetAttribute(ValueEmitContext& ctx, const IR::Inst& inst) {
state.builder.AddFunction(spv::OpStore, MeshOutputPointer(state, kind, exp.index),
value);
} else if (exp.kind == IR::ExportTargetKind::Position) {
if (state.invalid_position_clip_distance != UINT32_MAX) {
const auto zero = state.builder.Constant(spv::OpConstantNull, TypeF32Vector(state, 4));
const auto equal = state.builder.AllocateId();
const auto invalid = state.builder.AllocateId();
const auto distance = state.builder.AllocateId();
const auto distance_pointer = state.builder.AllocateId();
state.builder.AddFunction(spv::OpFOrdEqual, TypeBoolVector(state, 4), equal,
value, zero);
state.builder.AddFunction(spv::OpAll, TypeBool(state), invalid, equal);
// Zero at valid vertices makes a primitive containing an invalid position
// collapse to its remaining edge, before the undefined 0/0 perspective divide.
state.builder.AddFunction(spv::OpSelect, TypeF32(state), distance, invalid,
ConstantF32Value(state, -1.0f),
ConstantF32Value(state, 0.0f));
state.builder.AddFunction(
spv::OpAccessChain, TypePointer(state, spv::StorageClassOutput, TypeF32(state)),
distance_pointer, state.clip_distance_variable,
ConstantU32(state, state.invalid_position_clip_distance));
state.builder.AddFunction(spv::OpStore, distance_pointer, distance);
static std::atomic_bool logged = false;
if (!logged.exchange(true, std::memory_order_relaxed)) {
Log::WriteToConsoleAndLog(
"Shader: emitted zero-position clip guard\n");
}
}
const auto pointer = state.builder.AllocateId();
state.builder.AddFunction(
spv::OpAccessChain,
@@ -3,7 +3,9 @@
#include "graphics/shader/recompiler/frontend/decode/ImageOps.h"
#include <algorithm>
#include <atomic>
#include <bit>
#include <cstdio>
namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter {
namespace {
@@ -646,6 +648,13 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
const auto coord =
CoordF32(ctx, mem, *address, layout.coord, dimension_info.coordinate_components);
if (op == IR::ValueOpcode::ImageGatherRaw) {
if (HasFlag(mem, Decoder::ImageSampleFlagLod)) {
static std::atomic_flag warned = ATOMIC_FLAG_INIT;
if (!warned.test_and_set(std::memory_order_relaxed)) {
std::fputs("Warning: approximating IMAGE_GATHER4_L at mip level 0; explicit LOD is ignored.\n",
stderr);
}
}
if (dimension == ImageDimension::Dim1D) {
if (dref || !HasFlag(mem, Decoder::ImageSampleFlagLevelZero) ||
HasFlag(mem, Decoder::ImageSampleFlagOffset) ||
@@ -116,6 +116,7 @@ struct EmitterState {
uint32_t per_vertex_variable = 0;
uint32_t point_size_variable = 0;
uint32_t clip_distance_variable = 0;
uint32_t invalid_position_clip_distance = UINT32_MAX;
uint32_t cull_distance_variable = 0;
uint32_t layer_variable = 0;
uint32_t viewport_index_variable = 0;
@@ -388,6 +388,19 @@ void DefineInputs(EmitterState& state) {
}
}
for (auto& input: state.inputs) {
if (state.program.stage == ShaderType::Pixel &&
input.kind == IR::StageInputKind::Parameter) {
const auto location = PixelParameterLocation(state, input.location);
const auto alias = std::ranges::find_if(state.inputs, [&](const InputBinding& other) {
return other.kind == IR::StageInputKind::Parameter && other.variable_id != 0 &&
PixelParameterLocation(state, other.location) == location;
});
if (alias != state.inputs.end()) {
EXIT_IF(alias->per_vertex != input.per_vertex);
input.variable_id = alias->variable_id;
continue;
}
}
uint32_t type = TypeU32(state);
switch (input.kind) {
case IR::StageInputKind::VertexIndex:
@@ -476,6 +489,15 @@ void DefineOutputs(EmitterState& state) {
DefineMeshOutputs(state);
return;
}
if (state.program.stage == ShaderType::Vertex && clip_distance_count + cull_distance_count < 8u &&
std::ranges::any_of(state.outputs, [](const OutputBinding& output) {
return output.kind == IR::StageOutputKind::Position;
})) {
// Reserve one plane for the enabled PA_CL_CLIP_CNTL clipping-error cull.
state.invalid_position_clip_distance = clip_distance_count++;
state.outputs.push_back({{IR::StageOutputKind::ClipDistance,
state.invalid_position_clip_distance, 0, "gl_ClipDistance"}});
}
const auto BuiltIn = [&](uint32_t& variable, uint32_t type, const char* name,
spv::BuiltIn builtin) {
if (variable == 0) {
@@ -1206,13 +1206,12 @@ uint32_t FindSelectionMerge(const Graph& graph, const BasicBlock& block) {
return false_target;
}
// A return can leave a selection without reaching its merge. Keep the
// continuing arm as the merge instead of joining live state with a return.
if (graph.Dominates(block.id, false_target) &&
HasLinearPathToTerminal(graph, true_target)) {
// continuing arm as the merge. Shared continuations receive a dedicated
// gateway in SplitSharedMergeBlock before structured control is emitted.
if (HasLinearPathToTerminal(graph, true_target)) {
return false_target;
}
if (graph.Dominates(block.id, true_target) &&
HasLinearPathToTerminal(graph, false_target)) {
if (HasLinearPathToTerminal(graph, false_target)) {
return true_target;
}
}
@@ -175,6 +175,7 @@ constexpr MimgSampleInfo MIMG_SAMPLE_OPCODE_LIST[] = {
};
constexpr MimgGatherInfo MIMG_GATHER_OPCODE_LIST[] = {
{0x44u, Opcode::IMAGE_GATHER4_L, ImageSampleFlagLod},
{0x47u, Opcode::IMAGE_GATHER4_LZ, ImageSampleFlagLevelZero},
{0x48u, Opcode::IMAGE_GATHER4_C, ImageSampleFlagCompare},
{0x4fu, Opcode::IMAGE_GATHER4_C_LZ,
@@ -24,7 +24,8 @@ constexpr OpcodeMap SOP2_OPCODE_LIST[] = {
{0x1cu, Opcode::S_XNOR_B32}, {0x1du, Opcode::S_XNOR_B64},
{0x1eu, Opcode::S_LSHL_B32}, {0x1fu, Opcode::S_LSHL_B64},
{0x20u, Opcode::S_LSHR_B32}, {0x21u, Opcode::S_LSHR_B64},
{0x22u, Opcode::S_ASHR_I32}, {0x24u, Opcode::S_BFM_B32},
{0x22u, Opcode::S_ASHR_I32}, {0x23u, Opcode::S_ASHR_I64},
{0x24u, Opcode::S_BFM_B32},
{0x25u, Opcode::S_BFM_B64}, {0x26u, Opcode::S_MUL_I32},
{0x27u, Opcode::S_BFE_U32}, {0x28u, Opcode::S_BFE_I32},
{0x29u, Opcode::S_BFE_U64}, {0x2cu, Opcode::S_ABSDIFF_I32},
@@ -105,6 +106,7 @@ constexpr OpcodeMap SOPP_OPCODE_LIST[] = {
{0x10u, Opcode::S_SENDMSG},
{0x12u, Opcode::S_TRAP},
{0x16u, Opcode::S_TTRACEDATA},
{0x17u, Opcode::S_CBRANCH_CDBGSYS},
{0x20u, Opcode::S_INST_PREFETCH},
{0x23u, Opcode::S_WAITCNT_DEPCTR},
};
@@ -136,6 +138,10 @@ void DecodeSop1(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index
inst.family = Family::SOP1;
inst.opcode_id = opcode;
inst.opcode = Detail::LookupOpcode(SOP1_OPS, opcode);
if (opcode == 0x21u && sdst == 125u) {
// S_SWAPPC_B64 with NULL discards the return PC, so it is a plain jump.
inst.opcode = Opcode::S_SETPC_B64;
}
SetRawWords(inst, code, word_index, 1);
if (inst.opcode == Opcode::UNSUPPORTED) {
@@ -144,6 +144,7 @@ std::string FormatMimg(const Instruction& inst) {
}
switch (inst.opcode) {
case Opcode::IMAGE_SAMPLE:
case Opcode::IMAGE_GATHER4_L:
case Opcode::IMAGE_GATHER4_LZ:
case Opcode::IMAGE_GATHER4_C:
case Opcode::IMAGE_GATHER4_C_LZ:
@@ -187,6 +188,8 @@ std::string FormatExp(const Instruction& inst) {
bool IsConditionalBranch(Opcode opcode) {
switch (opcode) {
// DevKit NGG should be disabled.
case Opcode::S_CBRANCH_CDBGSYS: return false;
case Opcode::S_CBRANCH_SCC0:
case Opcode::S_CBRANCH_SCC1:
case Opcode::S_CBRANCH_VCCZ:
@@ -554,6 +557,7 @@ std::string InstructionToString(const Instruction& inst) {
case Opcode::S_CBRANCH_VCCNZ:
case Opcode::S_CBRANCH_EXECZ:
case Opcode::S_CBRANCH_EXECNZ:
case Opcode::S_CBRANCH_CDBGSYS:
return WithUnsupportedReason(inst, fmt::format("0x{:08x}: {} 0x{:08x}", inst.pc,
magic_enum::enum_name(inst.opcode),
inst.branch_target));
@@ -577,6 +581,7 @@ std::string InstructionToString(const Instruction& inst) {
case Opcode::IMAGE_LOAD_MIP:
case Opcode::IMAGE_GET_RESINFO:
case Opcode::IMAGE_GET_LOD:
case Opcode::IMAGE_GATHER4_L:
case Opcode::IMAGE_GATHER4_LZ:
case Opcode::IMAGE_GATHER4_C:
case Opcode::IMAGE_GATHER4_C_LZ:
@@ -106,6 +106,7 @@ enum class Opcode {
S_LSHR_B32,
S_LSHR_B64,
S_ASHR_I32,
S_ASHR_I64,
S_MUL_I32,
S_MUL_HI_U32,
S_MUL_HI_I32,
@@ -377,6 +378,7 @@ enum class Opcode {
V_CMP_GT_F16,
V_CMP_LG_F16,
V_CMP_GE_F16,
V_CMP_NGT_F16,
V_CMP_NEQ_F16,
V_CMPX_LT_F16,
V_CMPX_EQ_F16,
@@ -559,6 +561,7 @@ enum class Opcode {
IMAGE_ATOMIC_OR,
IMAGE_ATOMIC_XOR,
IMAGE_SAMPLE,
IMAGE_GATHER4_L,
IMAGE_GATHER4_LZ,
IMAGE_GATHER4_C,
IMAGE_GATHER4_C_LZ,
@@ -581,6 +584,7 @@ enum class Opcode {
S_CBRANCH_VCCNZ,
S_CBRANCH_EXECZ,
S_CBRANCH_EXECNZ,
S_CBRANCH_CDBGSYS,
S_SENDMSG,
S_SETREG_B32,
S_SLEEP,
@@ -250,7 +250,8 @@ constexpr VopcOpcodeInfo VOPC_OPCODE_LIST[] = {
{0xf5u, Opcode::V_CMPX_NE_U64, false}, {0xc9u, Opcode::V_CMP_LT_F16},
{0xcau, Opcode::V_CMP_EQ_F16}, {0xcbu, Opcode::V_CMP_LE_F16},
{0xccu, Opcode::V_CMP_GT_F16}, {0xcdu, Opcode::V_CMP_LG_F16},
{0xceu, Opcode::V_CMP_GE_F16}, {0xedu, Opcode::V_CMP_NEQ_F16},
{0xceu, Opcode::V_CMP_GE_F16}, {0xebu, Opcode::V_CMP_NGT_F16},
{0xedu, Opcode::V_CMP_NEQ_F16},
{0xd9u, Opcode::V_CMPX_LT_F16}, {0xdau, Opcode::V_CMPX_EQ_F16},
{0xdbu, Opcode::V_CMPX_LE_F16}, {0xdcu, Opcode::V_CMPX_GT_F16},
{0xdeu, Opcode::V_CMPX_GE_F16}, {0xfbu, Opcode::V_CMPX_NGT_F16},
@@ -516,7 +517,7 @@ struct Vop1SdwaRule {
};
constexpr Vop1SdwaRule VOP1_SDWA_RULES[] = {
{Opcode::V_MOV_B32, SdwaSelBytes() | SdwaSelWords() | SdwaSelFull(), SdwaSelWords(),
{Opcode::V_MOV_B32, SdwaSelBytes() | SdwaSelWords() | SdwaSelFull(), SdwaSelBytes() | SdwaSelWords(),
SdwaSelWords() | SdwaSelFull(), false},
{Opcode::V_CVT_F32_U32, SdwaSelBytes() | SdwaSelWords() | SdwaSelFull(), 0, 0, false},
{Opcode::V_CVT_F32_I32, SdwaSelBytes() | SdwaSelWords() | SdwaSelFull(), 0, 0, false},
@@ -824,6 +825,7 @@ bool IsVopcFloatCompareOpcode(Opcode opcode) {
case Opcode::V_CMP_GT_F16:
case Opcode::V_CMP_LG_F16:
case Opcode::V_CMP_GE_F16:
case Opcode::V_CMP_NGT_F16:
case Opcode::V_CMP_NEQ_F16:
case Opcode::V_CMPX_LT_F16:
case Opcode::V_CMPX_EQ_F16:
@@ -168,6 +168,21 @@ bool Translator::SimpleInteger(const Decoder::Instruction& inst, IR::ValueOpcode
return true;
}
bool Translator::S_ASHR_I64(const Decoder::Instruction& inst) {
// Signed 64-bit sources sign-extend literals; generic B64 operands zero-extend them.
const auto source =
inst.src0.kind == Decoder::OperandKind::LiteralConstant
? ir.ConstructU64(
ReadU32(inst.src0),
IR::U32(IR::Value((inst.src0.value & 0x80000000u) ? 0xffffffffu : 0u)))
: ReadU64(inst.src0);
const auto result =
ir.Emit(IR::ValueOpcode::ShiftRightArithmetic64, {source, ReadU32(inst.src1)});
WriteOperand(inst.dst, result);
ir.SetScc(IR::U1(ir.Emit(IR::ValueOpcode::INotEqual64, {result, IR::Value(uint64_t {0})})));
return true;
}
bool Translator::ComposedIntegerBinary(const Decoder::Instruction& inst, IR::ValueOpcode opcode,
bool negate_rhs, bool negate_result, bool update_scc) {
const auto lhs = ReadU32(inst.src0);
@@ -1034,6 +1034,7 @@ bool Translator::EmitMemory(const Decoder::Instruction& inst) {
case Decoder::Opcode::IMAGE_STORE:
case Decoder::Opcode::IMAGE_STORE_MIP: return IMAGE_STORE(inst);
case Decoder::Opcode::IMAGE_SAMPLE: return IMAGE_SAMPLE(inst);
case Decoder::Opcode::IMAGE_GATHER4_L:
case Decoder::Opcode::IMAGE_GATHER4_LZ:
case Decoder::Opcode::IMAGE_GATHER4_C:
case Decoder::Opcode::IMAGE_GATHER4_C_LZ:
@@ -179,6 +179,7 @@ bool Translator::EmitScalar(const Decoder::Instruction& inst) {
case O::S_LSHR_B64:
return SimpleInteger(inst, IR::ValueOpcode::ShiftRightLogical64, IR::Type::U64, false,
false, true);
case O::S_ASHR_I64: return S_ASHR_I64(inst);
case O::S_ANDN2_B32:
return ComposedIntegerBinary(inst, IR::ValueOpcode::BitwiseAnd32, true, false, true);
@@ -227,6 +228,7 @@ bool Translator::EmitScalar(const Decoder::Instruction& inst) {
case O::S_CBRANCH_VCCNZ:
case O::S_CBRANCH_EXECZ:
case O::S_CBRANCH_EXECNZ:
case O::S_CBRANCH_CDBGSYS:
case O::S_ENDPGM: return true;
default: return false;
}
@@ -789,15 +789,6 @@ const EmbeddedFetchLoad* FindEmbeddedFetchLoad(const EmbeddedFetchPlan* plan, ui
return found != plan->loads.end() ? &*found : nullptr;
}
bool IsEmbeddedFetchPrologLoad(const EmbeddedFetchPlan* plan, uint32_t pc) {
if (plan == nullptr) {
return false;
}
return std::ranges::any_of(plan->loads, [pc](const auto& load) {
return std::ranges::find(load.prolog_loads, pc) != load.prolog_loads.end();
});
}
int ResolveEmbeddedFetchResource(const ShaderVertexInputInfo& input,
const EmbeddedFetchLoad& load) {
if (load.attrib_id >= 0 && load.attrib_id < input.resources_num &&
@@ -819,22 +810,6 @@ int ResolveEmbeddedFetchResource(const ShaderVertexInputInfo& input,
return -1;
}
bool IsScalarMemoryLoad(Decoder::Opcode opcode) {
switch (opcode) {
case Decoder::Opcode::S_LOAD_DWORD:
case Decoder::Opcode::S_LOAD_DWORDX2:
case Decoder::Opcode::S_LOAD_DWORDX4:
case Decoder::Opcode::S_LOAD_DWORDX8:
case Decoder::Opcode::S_LOAD_DWORDX16:
case Decoder::Opcode::S_BUFFER_LOAD_DWORD:
case Decoder::Opcode::S_BUFFER_LOAD_DWORDX2:
case Decoder::Opcode::S_BUFFER_LOAD_DWORDX4:
case Decoder::Opcode::S_BUFFER_LOAD_DWORDX8:
case Decoder::Opcode::S_BUFFER_LOAD_DWORDX16: return true;
default: return false;
}
}
bool IsBufferDwordLoad(Decoder::Opcode opcode) {
switch (opcode) {
case Decoder::Opcode::BUFFER_LOAD_DWORD:
@@ -1127,26 +1102,32 @@ IR::Program TranslateProgram(const Decoder::Program& decoded, const CFG::Graph&
entry_ir.BitwiseOr(wave_info, entry_ir.BitwiseOr(entry_ir.ShiftLeftLogical(
primitive_count, u32(8)),
vertex_count)));
// GS adjacency addresses local ES records in LDS. Strip winding alternates
// with the global primitive number, including across subgroup boundaries.
const auto parity = mesh.input_primitive ==
static_cast<uint32_t>(Prospero::PrimitiveType::kTriStrip)
? entry_ir.BitwiseAnd(entry_ir.IAdd(primitive_chunk, local), u32(1))
: u32(0);
// GS adjacency addresses local ES records in LDS. Fans retain the draw's
// center in every subgroup; strip winding follows the global primitive.
const auto vertex = entry_ir.IMul(local, step);
const auto first = entry_ir.IAdd(vertex, parity);
const auto second = mesh.InputPrimitiveSize() >= 2u
? entry_ir.ISub(entry_ir.IAdd(vertex, u32(1)), parity)
: u32(0);
const auto third = mesh.InputPrimitiveSize() == 3u
? entry_ir.IAdd(vertex, u32(2))
: u32(0);
auto first = vertex;
auto second = u32(0);
auto third = u32(0);
if (mesh.InputPrimitiveSize() >= 2u) {
second = entry_ir.IAdd(vertex, u32(1));
}
if (mesh.InputPrimitiveSize() == 3u) {
third = entry_ir.IAdd(vertex, u32(2));
}
auto input_vertex = entry_ir.IAdd(chunk, local);
if (mesh.input_primitive == static_cast<uint32_t>(Prospero::PrimitiveType::kTriFan)) {
first = u32(0);
input_vertex = entry_ir.Select(entry_ir.IEqual(local, u32(0)), u32(0), input_vertex);
} else if (mesh.input_primitive == static_cast<uint32_t>(Prospero::PrimitiveType::kTriStrip)) {
const auto parity = entry_ir.BitwiseAnd(entry_ir.IAdd(primitive_chunk, local), u32(1));
first = entry_ir.IAdd(first, parity);
second = entry_ir.ISub(second, parity);
}
entry_ir.SetVectorReg(static_cast<IR::VectorReg>(0),
entry_ir.BitwiseOr(entry_ir.ShiftLeftLogical(first, u32(2)),
entry_ir.ShiftLeftLogical(second, u32(18))));
entry_ir.SetVectorReg(static_cast<IR::VectorReg>(1),
entry_ir.ShiftLeftLogical(third, u32(2)));
const auto input_vertex = entry_ir.IAdd(chunk, local);
const auto index_bytes = draw(3);
const auto indexed = entry_ir.INotEqual(index_bytes, u32(0));
const auto index_low = draw(4);
@@ -1209,13 +1190,15 @@ IR::Program TranslateProgram(const Decoder::Program& decoded, const CFG::Graph&
builtin(IR::StageInputKind::PrimitiveId));
} else if (options.stage == ShaderType::Pixel) {
const auto* ps = options.input_info.pixel;
if (ps->ps_perspective_center_vgpr != UINT32_MAX) {
entry_ir.SetVectorReg(static_cast<IR::VectorReg>(ps->ps_perspective_center_vgpr),
builtin(IR::StageInputKind::BaryCoordSmooth, 0));
entry_ir.SetVectorReg(
static_cast<IR::VectorReg>(ps->ps_perspective_center_vgpr + 1u),
builtin(IR::StageInputKind::BaryCoordSmooth, 1));
}
const auto barycentric_pair = [&](uint32_t reg, IR::StageInputKind kind) {
if (reg != UINT32_MAX) {
entry_ir.SetVectorReg(static_cast<IR::VectorReg>(reg), builtin(kind, 0));
entry_ir.SetVectorReg(static_cast<IR::VectorReg>(reg + 1u), builtin(kind, 1));
}
};
barycentric_pair(ps->ps_perspective_center_vgpr, IR::StageInputKind::BaryCoordSmooth);
barycentric_pair(ps->ps_perspective_centroid_vgpr,
IR::StageInputKind::BaryCoordSmoothCentroid);
uint32_t reg = ps->ps_system_input_base;
if (ps->ps_pos_x) {
entry_ir.SetVectorReg(static_cast<IR::VectorReg>(reg++),
@@ -1258,10 +1241,6 @@ IR::Program TranslateProgram(const Decoder::Program& decoded, const CFG::Graph&
if (IsCodeTableLoad(cfg, instruction.pc)) {
continue;
}
if (IsScalarMemoryLoad(instruction.opcode) &&
IsEmbeddedFetchPrologLoad(options.embedded_fetch, instruction.pc)) {
continue;
}
const auto* embedded = FindEmbeddedFetchLoad(options.embedded_fetch, instruction.pc);
if (embedded != nullptr && IsBufferDwordLoad(instruction.opcode) &&
instruction.data_dwords == embedded->components &&
@@ -13,7 +13,6 @@ struct EmbeddedFetchLoad {
uint32_t pc = 0;
int attrib_id = -1;
uint32_t components = 0;
std::vector<uint32_t> prolog_loads;
};
struct EmbeddedFetchPlan {
@@ -176,6 +176,7 @@ private:
bool negate_result);
bool SimpleInteger(const Decoder::Instruction& inst, IR::ValueOpcode opcode, IR::Type type,
bool reverse, bool mask_shift_count, bool update_scc);
bool S_ASHR_I64(const Decoder::Instruction& inst);
bool ComposedIntegerBinary(const Decoder::Instruction& inst, IR::ValueOpcode opcode,
bool negate_rhs, bool negate_result, bool update_scc);
bool V_AND_OR_B32(const Decoder::Instruction& inst);
@@ -264,6 +264,9 @@ bool Translator::EmitVector(const Decoder::Instruction& inst) {
case O::V_CMPX_LE_F16:
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdLessThanEqual32, true, true);
return true;
case O::V_CMP_NGT_F16:
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordLessThanEqual32, true, false);
return true;
case O::V_CMPX_NGT_F16:
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordLessThanEqual32, true, true);
return true;
@@ -112,6 +112,10 @@ ResourcePlan& ResourcePlan::operator=(ResourcePlan&& other) noexcept {
}
Program::~Program() {
// Planning expressions can refer to block values but outlive block storage in the base class.
for (auto& inst: value_storage) {
inst.Invalidate();
}
// Values may cross block boundaries. Detach all arguments before any block starts destroying
// its instruction storage so reverse-use links always point to live definitions.
for (auto* block: blocks) {
@@ -184,6 +188,22 @@ Value ResolveInvariantPhi(const ResourcePlan& program, Value value) {
return invariant;
}
bool HasShaderMemoryWrites(const Program& program) {
for (const auto* block: program.blocks) {
for (const auto& inst: *block) {
const auto op = inst.GetOpcode();
const auto buffer = BufferAccessOf(op);
const auto image = ImageOpcodeInfoOf(op).access;
if (buffer == BufferAccess::Write || buffer == BufferAccess::Atomic ||
image == ImageAccess::Write || image == ImageAccess::Atomic ||
AddressOpcodeInfoOf(op).access == AddressAccess::Write) {
return true;
}
}
}
return false;
}
void ValidateProgram(const Program& program, bool require_ssa) {
if (program.blocks.size() != program.block_info.size() ||
program.blocks.size() != program.block_storage.size()) {
@@ -171,6 +171,7 @@ enum class StageInputKind {
Layer,
SampleId,
BaryCoordSmooth,
BaryCoordSmoothCentroid,
BaryCoordNoPerspective,
WorkgroupId,
LocalInvocationId,
@@ -562,6 +563,7 @@ struct Program: ResourcePlan {
};
std::string ProgramToString(const Program& program);
bool HasShaderMemoryWrites(const Program& program);
void ValidateProgram(const Program& program, bool require_ssa);
void ResolveControlFlowIdentities(Program& program);
@@ -647,7 +647,8 @@ bool BuildSamplerPlan(const ShaderInfo& base, const Images& images, SamplerPlan&
}
static std::vector<ResourceBlock> ResourceControlFlow(const Program& program) {
if (program.blocks.size() != program.block_info.size()) {
// Any shader write may alias a scalar predicate read, including on a later loop visit.
if (program.blocks.size() != program.block_info.size() || HasShaderMemoryWrites(program)) {
return {};
}
std::unordered_map<uint32_t, uint32_t> indices;
@@ -687,12 +688,6 @@ static std::vector<ResourceBlock> ResourceControlFlow(const Program& program) {
const auto op = inst.GetOpcode();
const auto buffer = BufferAccessOf(op);
const auto image = ImageOpcodeInfoOf(op);
// Any shader write may alias a scalar predicate read, including on a later loop visit.
if (buffer == BufferAccess::Write || buffer == BufferAccess::Atomic ||
image.access == ImageAccess::Write || image.access == ImageAccess::Atomic ||
AddressOpcodeInfoOf(op).access == AddressAccess::Write) {
return {};
}
if (buffer == BufferAccess::None && image.access == ImageAccess::None) {
continue;
}
@@ -88,6 +88,7 @@ public:
m_info.samplers.clear();
m_info.sampled_pairs.clear();
m_info.uses_dma = false;
m_shader_writes = HasShaderMemoryWrites(program);
}
void Run() {
@@ -168,15 +169,85 @@ private:
std::abort();
}
Value LowerDescriptorPhi(Value value) {
value = value.Resolve();
const auto* phi = value.TryInstruction();
if (m_shader_writes || phi == nullptr || phi->GetOpcode() != ValueOpcode::Phi ||
phi->NumArgs() != 2u || phi->NumPhiBlocks() != 2u || phi->GetType() != Type::U32 ||
m_program.blocks.size() != m_program.block_info.size()) {
return value;
}
for (const auto& [original, selected]: m_descriptor_selections) {
if (original == phi) {
return selected;
}
}
const auto* merge = phi->Parent();
const auto* branch = phi->PhiBlock(0);
if (merge == nullptr || branch == nullptr || phi->PhiBlock(1) == nullptr ||
branch == phi->PhiBlock(1)) {
return value;
}
if (branch->ImmSuccessors().size() != 2u) {
if (branch->ImmPredecessors().size() != 1u) {
return value;
}
branch = branch->ImmPredecessors()[0];
}
if (branch == merge || branch->ImmSuccessors().size() != 2u) {
return value;
}
std::array<uint32_t, 2> target_ids;
for (uint32_t arm = 0; arm < 2; arm++) {
const auto* incoming = phi->PhiBlock(arm);
if (incoming == merge ||
(incoming != branch &&
(incoming->ImmPredecessors().size() != 1u ||
incoming->ImmPredecessors()[0] != branch ||
incoming->ImmSuccessors().size() != 1u ||
incoming->ImmSuccessors()[0] != merge))) {
return value;
}
const auto* target = incoming == branch ? merge : incoming;
const auto it = std::ranges::find(m_program.blocks, target);
if (it == m_program.blocks.end()) {
return value;
}
target_ids[arm] = m_program.block_info[it - m_program.blocks.begin()].id;
}
const auto branch_it = std::ranges::find(m_program.blocks, branch);
if (branch_it == m_program.blocks.end()) {
return value;
}
const auto& info = m_program.block_info[branch_it - m_program.blocks.begin()];
const auto& term = info.terminator;
if (term.kind != CFG::TerminatorKind::ConditionalBranch ||
!((term.true_block == target_ids[0] && term.false_block == target_ids[1]) ||
(term.false_block == target_ids[0] && term.true_block == target_ids[1])) ||
!ValidateRuntimeValue(m_program, info.condition, RuntimeValueType::Integer) ||
!ValidateRuntimeValue(m_program, phi->Arg(0)) ||
!ValidateRuntimeValue(m_program, phi->Arg(1))) {
return value;
}
// Retain a host expression; replacing the GPU Phi would break SSA dominance.
const auto true_arg = term.true_block == target_ids[0] ? 0u : 1u;
auto& selected = m_program.value_storage.emplace_back(ValueOpcode::SelectU32);
selected.SetArg(0, info.condition);
selected.SetArg(1, phi->Arg(true_arg));
selected.SetArg(2, phi->Arg(true_arg ^ 1u));
m_descriptor_selections.emplace_back(phi, Value(&selected));
return Value(&selected);
}
void MakeSource(const Inst& handle, uint32_t width, bool sampler, bool sample_adjust,
DescriptorSource& descriptor, uint32_t pc) const {
DescriptorSource& descriptor, uint32_t pc) {
if (handle.NumArgs() != width) {
Fail(pc, fmt::format("{} has {} descriptor dwords, expected {}",
ValueOpcodeName(handle.GetOpcode()), handle.NumArgs(), width));
}
descriptor.dword_count = width;
for (uint32_t i = 0; i < width; i++) {
descriptor.dwords[i] = handle.Arg(i).Resolve();
descriptor.dwords[i] = LowerDescriptorPhi(handle.Arg(i));
}
if (sample_adjust) {
descriptor.dwords[3] = CanonicalizeSampleAdjustDword3(descriptor.dwords[3]);
@@ -744,12 +815,14 @@ private:
}
}
Program& m_program;
ShaderInfo m_info;
std::vector<DescriptorSource> m_sources;
std::vector<HandlePatch> m_handle_patches;
std::vector<MemoryPatch> m_memory_patches;
std::vector<IndirectImagePlan> m_indirect_images;
Program& m_program;
ShaderInfo m_info;
std::vector<DescriptorSource> m_sources;
std::vector<HandlePatch> m_handle_patches;
std::vector<MemoryPatch> m_memory_patches;
std::vector<IndirectImagePlan> m_indirect_images;
std::vector<std::pair<const Inst*, Value>> m_descriptor_selections;
bool m_shader_writes = false;
};
} // namespace
@@ -143,6 +143,7 @@ void ValidateValueReferences(const Program& program, ShaderStageInputInfo input_
}
break;
case StageInputKind::BaryCoordSmooth:
case StageInputKind::BaryCoordSmoothCentroid:
case StageInputKind::BaryCoordNoPerspective:
if (component >= 2u) {
return Fail("typed barycentric component is out of range");
@@ -225,6 +226,18 @@ void CollectPixelInputs(const Program& program, const ShaderPixelInputInfo* pixe
}
}
}
// Aliases of a vertex output share one SPIR-V interface variable. If any
// alias reads raw vertices, interpolate the other aliases from those too.
for (uint32_t input = 0; input < pixel->input_num; input++) {
for (uint32_t alias = 0; alias < pixel->input_num; alias++) {
if (ShaderPixelParameterMappedLocation(*pixel, input) ==
ShaderPixelParameterMappedLocation(*pixel, alias) &&
ShaderPixelParameterIsFlat(*pixel, input) ==
ShaderPixelParameterIsFlat(*pixel, alias)) {
per_vertex[input] = per_vertex[input] || per_vertex[alias];
}
}
}
for (uint32_t input = 0; input < pixel->input_num; input++) {
AddInput(info, StageInputKind::Parameter, input, 4, fmt::format("in_param_{}", input),
per_vertex[input]);
@@ -287,7 +300,8 @@ void CollectBuiltinInputs(const Program& program, ShaderInfo& info) {
case StageInputKind::Layer: AddInput(info, kind, 0, 1, "gl_Layer"); break;
case StageInputKind::SampleId: AddInput(info, kind, 0, 1, "gl_SampleID"); break;
case StageInputKind::BaryCoordSmooth:
AddInput(info, kind, 0, 3, "gl_BaryCoordKHR");
case StageInputKind::BaryCoordSmoothCentroid:
AddInput(info, StageInputKind::BaryCoordSmooth, 0, 3, "gl_BaryCoordKHR");
break;
case StageInputKind::BaryCoordNoPerspective:
AddInput(info, kind, 0, 3, "gl_BaryCoordNoPerspKHR");
@@ -16,6 +16,13 @@ namespace {
constexpr uint64_t AddressMask = 0x0000ffffffffffffull;
SrtRuntime CleanRuntime(SrtRuntime runtime) {
runtime.read_memory = runtime.read_specialization_memory != nullptr
? runtime.read_specialization_memory
: +[](void*, uint64_t, uint32_t*) { return false; };
return runtime;
}
const char* StageName(ShaderType stage) {
switch (stage) {
case ShaderType::Vertex: return "vertex";
@@ -646,8 +653,10 @@ private:
case ValueOpcode::GetShaderBase: result = m_runtime.shader_base; return true;
case ValueOpcode::Phi: return EvaluatePhi(inst, result);
case ValueOpcode::ReadFirstLane: {
Evaluator active(m_program, m_runtime, m_clean_flat_slots, m_clean_evaluator,
inst.Arg(1));
const auto clean_runtime = CleanRuntime(m_runtime);
Evaluator clean_active(m_program, clean_runtime, {}, nullptr, inst.Arg(1));
Evaluator active(m_program, m_runtime, m_clean_flat_slots, &clean_active,
inst.Arg(1));
return active.EvaluateWide(inst.Arg(0), result);
}
case ValueOpcode::BitCastU32F32:
@@ -892,12 +901,13 @@ private:
}
case ValueOpcode::SelectU32:
case ValueOpcode::SelectU1:
case ValueOpcode::SelectF32:
if (ternary()) {
result = a != 0u ? b : c;
return true;
case ValueOpcode::SelectF32: {
auto& predicate = m_clean_evaluator != nullptr ? *m_clean_evaluator : *this;
if (predicate.EvaluateWide(inst.Arg(0), a)) {
return Arg(inst, a != 0u ? 1u : 2u, result);
}
return false;
}
case ValueOpcode::IEqual32:
if (binary()) {
result = static_cast<uint32_t>(a) == static_cast<uint32_t>(b);
@@ -977,7 +987,7 @@ bool EvaluateRuntimeSourcesImpl(const ResourcePlan& program, std::span<const uin
const SrtRuntime& runtime, std::vector<DescriptorValue>& results,
std::vector<uint32_t>& flat, bool evaluate_flat,
std::span<const uint8_t> clean_flat_slots,
std::vector<uint8_t>& active_sources) {
std::vector<uint8_t>& active_sources) {
if (!program.srt_plan_complete) {
return false;
}
@@ -985,8 +995,7 @@ bool EvaluateRuntimeSourcesImpl(const ResourcePlan& program, std::span<const uin
runtime.read_specialization_memory == nullptr) {
return false;
}
SrtRuntime clean_runtime = runtime;
clean_runtime.read_memory = runtime.read_specialization_memory;
const auto clean_runtime = CleanRuntime(runtime);
Evaluator clean_evaluator(program, clean_runtime);
Evaluator evaluator(program, runtime, clean_flat_slots, &clean_evaluator);
std::vector<uint8_t> active;
@@ -1081,11 +1090,8 @@ bool EvaluateUniformValues(const ResourcePlan& program, std::span<const Value> v
if (values.size() != results.size()) {
return false;
}
auto clean = runtime;
clean.read_memory = runtime.read_specialization_memory != nullptr
? runtime.read_specialization_memory
: +[](void*, uint64_t, uint32_t*) { return false; };
Evaluator evaluator(program, clean);
const auto clean = CleanRuntime(runtime);
Evaluator evaluator(program, clean);
for (size_t i = 0; i < values.size(); ++i) {
if (!evaluator.Evaluate(values[i], results[i])) {
return false;
+6 -3
View File
@@ -40,12 +40,15 @@ std::vector<Parameter> GetParameters(const ShaderVertexInputInfo& vertex_info,
}
std::vector<Parameter> parameters;
std::array<bool, 32> output_locations {};
for (const auto input: active_inputs) {
const auto input_location = ShaderPixelParameterMappedLocation(*pixel_info, input);
if ((vertex_info.stage.program->param_export_mask & (1u << input_location)) != 0) {
parameters.push_back({input_location,
ShaderPixelParameterLocation(*pixel_info, active_inputs, input),
const auto output_location = ShaderPixelParameterLocation(*pixel_info, active_inputs, input);
if ((vertex_info.stage.program->param_export_mask & (1u << input_location)) != 0 &&
!output_locations[output_location]) {
parameters.push_back({input_location, output_location,
ShaderPixelParameterIsFlat(*pixel_info, input)});
output_locations[output_location] = true;
}
}
return parameters;
+13 -3
View File
@@ -573,15 +573,20 @@ static void ShaderGetStaticInputInfoPS(
ps_info = {};
ps_info.scratch_size_dwords = data.scratch_size_dwords;
// SPI_PS_IN_CONTROL.NUM_INTERP occupies bits 5:0. Keep the remaining control
// flags in the hardware state and extract only the input count here.
// SPI_PS_IN_CONTROL: NUM_INTERP occupies bits 5:0 and PS_W32_EN is bit 15.
ps_info.input_num = sh.ps_in_control & 0x3fu;
if ((sh.ps_in_control & 0x8000u) != 0) {
ps_info.wave_size = 32;
}
EXIT_NOT_IMPLEMENTED(ps_info.input_num > std::size(ps_info.interpolator_settings));
ps_info.ps_system_input_base = ShaderCalcPsSystemInputBase(sh);
const uint32_t active_inputs = sh.ps_input_ena & sh.ps_input_addr;
if ((active_inputs & 0x00000002u) != 0) {
ps_info.ps_perspective_center_vgpr = (active_inputs & 0x00000001u) != 0 ? 2u : 0u;
}
if ((active_inputs & 0x00000004u) != 0) {
ps_info.ps_perspective_centroid_vgpr = 2u * std::popcount(active_inputs & 0x3u);
}
for (uint32_t i = 0; i < data.num_input_semantics && i < ps_info.input_num && i < 32u; i++) {
const auto& semantic = data.input_semantics[i];
if (semantic.is_custom != 0 && semantic.is_f16 == 0) {
@@ -701,9 +706,11 @@ void BuildStageStaticKey(const ShaderPixelInputInfo& info, std::vector<uint32_t>
key.clear();
key.push_back(info.scratch_size_dwords);
key.push_back(info.input_num);
key.push_back(info.wave_size);
key.push_back(info.ps_system_input_base);
key.push_back(info.custom_interpolation_mask);
key.push_back(info.ps_perspective_center_vgpr);
key.push_back(info.ps_perspective_centroid_vgpr);
key.push_back(static_cast<uint32_t>(info.ps_pos_x));
key.push_back(static_cast<uint32_t>(info.ps_pos_y));
key.push_back(static_cast<uint32_t>(info.ps_pos_z));
@@ -790,6 +797,7 @@ ShaderParams PrepareProgram(const HW::VertexShaderInfo& regs, const HW::Context&
const auto& group = user_config.GetGeControl();
if ((user_config.GetPrimType() != Prospero::PrimitiveType::kPointList &&
user_config.GetPrimType() != Prospero::PrimitiveType::kLineList &&
user_config.GetPrimType() != Prospero::PrimitiveType::kTriFan &&
user_config.GetPrimType() != Prospero::PrimitiveType::kTriStrip &&
user_config.GetPrimType() != Prospero::PrimitiveType::kTriList) ||
sh.m_vgtGsOutPrimType != 2u || sh.m_vgtGsMaxVertOut < 3u ||
@@ -955,6 +963,7 @@ void ShaderDbgDumpInputInfo(const ShaderPixelInputInfo& info) {
"\t ps_system_input_base = %u\n"
"\t custom_interpolation_mask = 0x%08" PRIx32 "\n"
"\t ps_perspective_center_vgpr = %" PRIu32 "\n"
"\t ps_perspective_centroid_vgpr = %" PRIu32 "\n"
"\t ps_pos_x = %s\n"
"\t ps_pos_y = %s\n"
"\t ps_pos_z = %s\n"
@@ -967,7 +976,8 @@ void ShaderDbgDumpInputInfo(const ShaderPixelInputInfo& info) {
"\t ps_early_z = %s\n"
"\t ps_execute_on_noop = %s\n",
info.input_num, info.ps_system_input_base, info.custom_interpolation_mask,
info.ps_perspective_center_vgpr, info.ps_pos_x ? "true" : "false",
info.ps_perspective_center_vgpr, info.ps_perspective_centroid_vgpr,
info.ps_pos_x ? "true" : "false",
info.ps_pos_y ? "true" : "false", info.ps_pos_z ? "true" : "false",
info.ps_pos_w ? "true" : "false", info.ps_front_face ? "true" : "false",
info.ps_ancillary ? "true" : "false",
+7 -2
View File
@@ -97,8 +97,11 @@ struct ShaderMeshInputInfo: ShaderWorkgroupInputInfo {
}
}
[[nodiscard]] constexpr uint32_t InputPrimitiveStep() const {
return input_primitive == static_cast<uint32_t>(Prospero::PrimitiveType::kTriStrip)
? 1u : InputPrimitiveSize();
switch (static_cast<Prospero::PrimitiveType>(input_primitive)) {
case Prospero::PrimitiveType::kTriFan:
case Prospero::PrimitiveType::kTriStrip: return 1u;
default: return InputPrimitiveSize();
}
}
[[nodiscard]] constexpr uint32_t InputPrimitiveCount(uint32_t vertices) const {
const auto size = InputPrimitiveSize();
@@ -153,9 +156,11 @@ struct ShaderComputeInputInfo: ShaderWorkgroupInputInfo {
struct ShaderPixelInputInfo {
uint32_t interpolator_settings[32] = {0};
uint32_t input_num = 0;
uint32_t wave_size = 64;
uint32_t ps_system_input_base = 0;
uint32_t custom_interpolation_mask = 0;
uint32_t ps_perspective_center_vgpr = UINT32_MAX;
uint32_t ps_perspective_centroid_vgpr = UINT32_MAX;
uint8_t target_output_mode[8] = {};
std::array<Prospero::ColorComponentMapping, 8> target_export_mapping = {};
uint32_t scratch_size_dwords = 0;
+19 -7
View File
@@ -19,19 +19,31 @@ uint32_t ShaderPixelParameterLocation(const ShaderPixelInputInfo& info,
std::span<const uint32_t> active_inputs, uint32_t input) {
std::array<bool, 32> used_locations {};
for (const auto active_input: active_inputs) {
auto location = ShaderPixelParameterMappedLocation(info, active_input);
if (location < used_locations.size() && used_locations[location]) {
location = active_input;
while (location < used_locations.size() && used_locations[location]) {
location++;
used_locations[ShaderPixelParameterMappedLocation(info, active_input)] = true;
}
std::array<uint32_t, 64> group_locations;
group_locations.fill(UINT32_MAX);
for (const auto active_input: active_inputs) {
const auto mapped = ShaderPixelParameterMappedLocation(info, active_input);
const auto group = mapped * 2u + ShaderPixelParameterIsFlat(info, active_input);
auto& location = group_locations[group];
if (location == UINT32_MAX) {
location = mapped;
// Smooth and custom interpolation read the same vertex output. Only
// differing flat/smooth rectangle outputs need separate locations.
if (group_locations[group ^ 1u] != UINT32_MAX) {
location = 0;
while (location < used_locations.size() && used_locations[location]) {
location++;
}
EXIT_NOT_IMPLEMENTED(location >= used_locations.size());
}
EXIT_NOT_IMPLEMENTED(location >= used_locations.size());
used_locations[location] = true;
}
if (active_input == input) {
return location;
}
used_locations[location] = true;
}
return ShaderPixelParameterMappedLocation(info, input);
}
-4
View File
@@ -3093,10 +3093,6 @@ Pthread PthreadSwapSelfForSignal(Pthread thread) {
return previous;
}
int PthreadGetUniqueId(Pthread thread) {
return thread != nullptr ? thread->unique_id : 0;
}
uint64_t PthreadGetHostThreadId(Pthread thread) {
return thread != nullptr ? thread->host_thread_id : 0;
}
-1
View File
@@ -110,7 +110,6 @@ int KYTY_SYSV_ABI PthreadRename(Pthread thread, const char* name);
void KYTY_SYSV_ABI PthreadYield();
int KYTY_SYSV_ABI PthreadGetthreadid();
int KYTY_SYSV_ABI PthreadGetaffinity(Pthread thread, KernelCpumask* mask);
int PthreadGetUniqueId(Pthread thread);
uint64_t PthreadGetHostThreadId(Pthread thread);
void PthreadWakeForSignal(Pthread thread);
void PthreadQueuePendingSignal(Pthread thread, int signum);
+1 -1
View File
@@ -20,7 +20,7 @@ add_executable(launcher WIN32
)
target_link_libraries(launcher Qt6::Concurrent Qt6::Network Qt6::Widgets Vulkan::Headers kyty_git_version)
target_link_libraries(launcher Qt6::Concurrent Qt6::Network Qt6::Widgets Vulkan::Headers SDL2-static kyty_git_version)
if(WIN32)
target_link_libraries(launcher setupapi)
endif()
+27 -19
View File
@@ -75,7 +75,25 @@
</layout>
</widget>
</item>
<item row="0" column="1" rowspan="2">
<item row="2" column="0">
<widget class="QGroupBox" name="audio_group">
<property name="title"><string>Audio</string></property>
<layout class="QFormLayout" name="audioLayout">
<property name="fieldGrowthPolicy">
<enum>QFormLayout::AllNonFixedFieldsGrow</enum>
</property>
<item row="0" column="0">
<widget class="QLabel" name="label_audio_input_device">
<property name="text"><string>Microphone:</string></property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="comboBox_audio_input_device"/>
</item>
</layout>
</widget>
</item>
<item row="0" column="1" rowspan="3">
<widget class="QGroupBox" name="graphics_group">
<property name="title">
<string>Graphics &amp;&amp; display</string>
@@ -152,7 +170,7 @@
</layout>
</widget>
</item>
<item row="2" column="0" colspan="2">
<item row="3" column="0" colspan="2">
<widget class="QGroupBox" name="debug_group">
<property name="title">
<string>Debugging &amp;&amp; logs</string>
@@ -253,21 +271,19 @@
<property name="title">
<string>Compatibility</string>
</property>
<layout class="QHBoxLayout" name="compatibilityLayout">
<layout class="QVBoxLayout" name="compatibilityLayout">
<item>
<widget class="QCheckBox" name="checkBox_amd_cpu">
<property name="toolTip"><string>Patch CPU instructions that differ between AMD and the host CPU.</string></property>
<property name="text"><string>AMD CPU patch (EXPERIMENTAL)</string></property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="checkBox_red_zone_protection">
<property name="toolTip"><string>Helps prevent crashes in some games on Windows</string></property>
<property name="text"><string>Windows SysV red zone crash protection (Experimental)</string></property>
</widget>
</item>
<item>
<spacer name="compatibilitySpacer">
<property name="orientation"><enum>Qt::Horizontal</enum></property>
<property name="sizeHint" stdset="0">
<size><width>40</width><height>20</height></size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
@@ -313,12 +329,4 @@
</customwidget>
</customwidgets>
<resources/>
<connections>
<connection>
<sender>cancel_button</sender>
<signal>clicked()</signal>
<receiver>ConfigurationEditDialog</receiver>
<slot>reject()</slot>
</connection>
</connections>
</ui>
+8
View File
@@ -87,6 +87,7 @@ public:
Resolution screen_resolution = Resolution::R1280X720;
QString user_name = "Kyty";
int user_id = Config::DEFAULT_USER_ID;
QString audio_input_device;
PresentMode present_mode = PresentMode::Mailbox;
int gpu_index = -1;
bool fullscreen_enabled = false;
@@ -104,6 +105,7 @@ public:
QString printf_output_file = "_kyty.txt";
bool profiler_enabled = false;
bool renderdoc_enabled = false;
bool amd_cpu_enabled = false;
#if defined(_WIN32)
bool red_zone_protection_enabled = false;
#endif
@@ -115,6 +117,7 @@ public:
screen_resolution = other.screen_resolution;
user_name = other.user_name;
user_id = other.user_id;
audio_input_device = other.audio_input_device;
present_mode = other.present_mode;
gpu_index = other.gpu_index;
fullscreen_enabled = other.fullscreen_enabled;
@@ -132,6 +135,7 @@ public:
printf_output_file = other.printf_output_file;
profiler_enabled = other.profiler_enabled;
renderdoc_enabled = other.renderdoc_enabled;
amd_cpu_enabled = other.amd_cpu_enabled;
#if defined(_WIN32)
red_zone_protection_enabled = other.red_zone_protection_enabled;
#endif
@@ -160,6 +164,7 @@ public:
KYTY_CFG_SET(screen_resolution);
KYTY_CFG_SET(user_name);
KYTY_CFG_SET(user_id);
KYTY_CFG_SET(audio_input_device);
KYTY_CFG_SET(present_mode);
KYTY_CFG_SET(gpu_index);
KYTY_CFG_SET(fullscreen_enabled);
@@ -177,6 +182,7 @@ public:
KYTY_CFG_SET(printf_output_file);
KYTY_CFG_SET(profiler_enabled);
KYTY_CFG_SET(renderdoc_enabled);
KYTY_CFG_SET(amd_cpu_enabled);
#if defined(_WIN32)
KYTY_CFG_SET(red_zone_protection_enabled);
#endif
@@ -196,6 +202,7 @@ public:
user_id = user_id_ok && Config::IsConfiguredUserIdValid(saved_user_id)
? saved_user_id
: Config::DEFAULT_USER_ID;
audio_input_device = s->value("audio_input_device", audio_input_device).toString();
KYTY_CFG_GET(present_mode);
gpu_index = s->value("gpu_index", -1).toInt();
if (EnumToText(present_mode).isEmpty()) {
@@ -219,6 +226,7 @@ public:
KYTY_CFG_GET(printf_output_file);
KYTY_CFG_GET(profiler_enabled);
KYTY_CFG_GET(renderdoc_enabled);
amd_cpu_enabled = s->value("amd_cpu_enabled", false).toBool();
#if defined(_WIN32)
red_zone_protection_enabled =
s->value("red_zone_protection_enabled", red_zone_protection_enabled).toBool();
+34 -1
View File
@@ -3,6 +3,7 @@
#include "common/emulatorConfig.h"
#include "configuration.h"
#include "mandatoryLineEdit.h"
#include "SDL.h"
#include <QAbstractItemView>
#include <QCheckBox>
@@ -107,6 +108,7 @@ ConfigurationEditDialog::ConfigurationEditDialog(Configuration& info, QWidget* p
InitGameDirectories();
connect(m_ui->ok_button, &QPushButton::clicked, this, &ConfigurationEditDialog::save);
connect(m_ui->cancel_button, &QPushButton::clicked, this, &QDialog::reject);
connect(m_ui->clear_button, &QPushButton::clicked, this, &ConfigurationEditDialog::clear);
connect(m_ui->comboBox_shader_log_direction, &QComboBox::currentTextChanged, this,
[this](const QString& text) {
@@ -167,6 +169,31 @@ void ConfigurationEditDialog::Init(const Configuration& info) {
m_ui->lineEdit_user_name->setMaxLength(static_cast<int>(Config::MAX_USER_NAME_LENGTH));
m_ui->lineEdit_user_name->setText(info.user_name);
m_ui->spinBox_user_id->setValue(info.user_id);
auto* microphone = m_ui->comboBox_audio_input_device;
microphone->clear();
microphone->addItem(tr("None"), QString {});
microphone->setToolTip(tr("Microphone used by games. None supplies silence."));
SDL_SetMainReady();
if (SDL_InitSubSystem(SDL_INIT_AUDIO) == 0) {
const int device_count = SDL_GetNumAudioDevices(SDL_TRUE);
for (int i = 0; i < device_count; i++) {
if (const auto* device = SDL_GetAudioDeviceName(i, SDL_TRUE); device != nullptr) {
const auto name = QString::fromUtf8(device);
if (microphone->findData(name) < 0) {
microphone->addItem(name, name);
}
}
}
SDL_QuitSubSystem(SDL_INIT_AUDIO);
} else {
microphone->setToolTip(tr("Microphones could not be listed: %1")
.arg(QString::fromUtf8(SDL_GetError())));
}
if (microphone->findData(info.audio_input_device) < 0) {
microphone->addItem(tr("%1 (unavailable)").arg(info.audio_input_device),
info.audio_input_device);
}
microphone->setCurrentIndex(microphone->findData(info.audio_input_device));
ListInit(m_ui->comboBox_screen_resolution, info.screen_resolution);
ListInit(m_ui->comboBox_present_mode, info.present_mode);
m_ui->comboBox_gpu->clear();
@@ -187,7 +214,7 @@ void ConfigurationEditDialog::Init(const Configuration& info) {
#endif
QVulkanInstance instance;
instance.setApiVersion(QVersionNumber(1, 3, 0));
#if !defined(__APPLE__)
#if !defined(__APPLE__) && QT_VERSION >= QT_VERSION_CHECK(6, 6, 0)
instance.setFlags(QVulkanInstance::NoPortabilityDrivers);
#endif
if (instance.create()) {
@@ -213,6 +240,10 @@ void ConfigurationEditDialog::Init(const Configuration& info) {
m_ui->checkBox_shader_validation->setChecked(info.shader_validation_enabled);
m_ui->checkBox_vulkan_validation->setChecked(info.vulkan_validation_enabled);
m_ui->checkBox_renderdoc_capture->setChecked(info.renderdoc_enabled);
m_ui->checkBox_amd_cpu->setChecked(info.amd_cpu_enabled);
#if defined(__APPLE__)
m_ui->checkBox_amd_cpu->setVisible(false);
#endif
#if defined(_WIN32)
m_ui->checkBox_red_zone_protection->setChecked(info.red_zone_protection_enabled);
#else
@@ -340,6 +371,7 @@ void ConfigurationEditDialog::resizeEvent(QResizeEvent* event) {
static void UpdateInfo(Configuration& info, Ui::ConfigurationEditDialog& ui) {
info.user_name = ui.lineEdit_user_name->text().trimmed();
info.user_id = ui.spinBox_user_id->value();
info.audio_input_device = ui.comboBox_audio_input_device->currentData().toString();
info.screen_resolution =
TextToEnum<Configuration::Resolution>(ui.comboBox_screen_resolution->currentText());
info.present_mode =
@@ -352,6 +384,7 @@ static void UpdateInfo(Configuration& info, Ui::ConfigurationEditDialog& ui) {
info.vulkan_validation_enabled = ui.checkBox_vulkan_validation->isChecked();
info.shader_validation_enabled = ui.checkBox_shader_validation->isChecked();
info.renderdoc_enabled = ui.checkBox_renderdoc_capture->isChecked();
info.amd_cpu_enabled = ui.checkBox_amd_cpu->isChecked();
#if defined(_WIN32)
info.red_zone_protection_enabled = ui.checkBox_red_zone_protection->isChecked();
#endif
+2 -2
View File
@@ -46,8 +46,8 @@ constexpr PadControl PAD_CONTROLS[] = {
{"Square", "Square", "K"},
{"L1", "L1", "Q"},
{"R1", "R1", "E"},
{"L2", "L2", ""},
{"R2", "R2", ""},
{"L2", "L2", "Z"},
{"R2", "R2", "C"},
{"L3", "L3", "Left Shift"},
{"R3", "R3", "Left Ctrl"},
{"Options", "Options", "Return"},
+6
View File
@@ -228,6 +228,9 @@ static QStringList CreateEmulatorArgs(const Configuration& info) {
args << "--screen-height" << r.at(1);
args << "--user-name" << info.user_name;
args << "--user-id" << QString::number(info.user_id);
if (!info.audio_input_device.isEmpty()) {
args << "--mic" << info.audio_input_device;
}
args << "--present-mode" << EnumToText(info.present_mode);
if (info.gpu_index >= 0) {
args << "--gpu" << QString::number(info.gpu_index);
@@ -251,6 +254,9 @@ static QStringList CreateEmulatorArgs(const Configuration& info) {
args << "--profile";
}
args << "--spirv-debug-printf" << "false";
if (info.amd_cpu_enabled) {
args << "--amd-cpu";
}
#if defined(_WIN32)
if (info.red_zone_protection_enabled) {
args << "--redzone";
+7 -3
View File
@@ -3,6 +3,7 @@
#include "configuration.h"
#include <QCoreApplication>
#include <QDebug>
#include <QDialogButtonBox>
#include <QDir>
#include <QFile>
@@ -129,8 +130,11 @@ void PatchesDialog::Save() {
document.setObject(root);
QSaveFile output(path);
if (output.open(QIODevice::WriteOnly) && output.write(document.toJson()) >= 0 &&
output.commit()) {
m_status->setText(tr("Cheat selection saved."));
if (!output.open(QIODevice::WriteOnly) || output.write(document.toJson()) < 0 ||
!output.commit()) {
qWarning() << "Could not save cheat file:" << output.errorString();
m_status->setText(tr("Could not save cheat file: %1").arg(output.errorString()));
return;
}
m_status->setText(tr("Cheat selection saved."));
}
+10 -20
View File
@@ -262,35 +262,25 @@ struct CommandBuffer {
reserved_dw);
}
[[nodiscard]] KYTY_SYSV_ABI uint32_t GetAvailableSizeDW() const {
if (cursor_up == nullptr || cursor_down == nullptr || cursor_down <= cursor_up) {
return 0;
}
auto available = static_cast<uint64_t>(cursor_down - cursor_up);
[[nodiscard]] KYTY_SYSV_ABI uint64_t GetAvailableSizeDW() const {
// Interpret the signed cursor distance as an unsigned 64-bit DWORD count.
const auto distance = static_cast<int64_t>(reinterpret_cast<uintptr_t>(cursor_down) -
reinterpret_cast<uintptr_t>(cursor_up));
const auto available = static_cast<uint64_t>(distance >> 2);
if (available <= reserved_dw) {
return 0;
}
if (available - reserved_dw > UINT32_MAX) {
LOGF_COLOR(
Log::Color::Red,
"\t command buffer has suspiciously large free space: cursor_up = 0x%016" PRIx64
", cursor_down = 0x%016" PRIx64 ", reserved_dw = %" PRIu32 "\n",
reinterpret_cast<uint64_t>(cursor_up), reinterpret_cast<uint64_t>(cursor_down),
reserved_dw);
return UINT32_MAX;
}
return static_cast<uint32_t>(available - reserved_dw);
return available - reserved_dw;
}
KYTY_SYSV_ABI bool ReserveDW(uint32_t num_dw) {
uint32_t remaining = GetAvailableSizeDW();
const uint64_t remaining = GetAvailableSizeDW();
if (num_dw > remaining) {
if (callback == nullptr) {
LOGF_COLOR(
Log::Color::Red,
"\t command buffer exhausted and has no grow callback: requested = %" PRIu32
", remaining = %" PRIu32 ", reserved_dw = %" PRIu32 "\n",
", remaining = %" PRIu64 ", reserved_dw = %" PRIu32 "\n",
num_dw, remaining, reserved_dw);
DbgDump();
return false;
@@ -300,7 +290,7 @@ struct CommandBuffer {
if (!result) {
LOGF_COLOR(Log::Color::Red,
"\t command buffer grow callback failed: requested = %" PRIu32
", remaining = %" PRIu32 ", reserved_dw = %" PRIu32 "\n",
", remaining = %" PRIu64 ", reserved_dw = %" PRIu32 "\n",
num_dw, remaining, reserved_dw);
DbgDump();
return false;
@@ -308,7 +298,7 @@ struct CommandBuffer {
if (GetAvailableSizeDW() < num_dw) {
LOGF_COLOR(Log::Color::Red,
"\t command buffer grow callback did not provide enough space: "
"requested = %" PRIu32 ", remaining = %" PRIu32
"requested = %" PRIu32 ", remaining = %" PRIu64
", reserved_dw = %" PRIu32 "\n",
num_dw, GetAvailableSizeDW(), reserved_dw);
DbgDump();
+208 -58
View File
@@ -3,6 +3,7 @@
#include "SDL.h"
#include "common/assert.h"
#include "common/common.h"
#include "common/emulatorConfig.h"
#include "common/logging/log.h"
#include "common/magicEnum.h"
#include "common/stringUtils.h"
@@ -38,6 +39,9 @@ constexpr int AUDIO_OUT_PORT_TYPE_AUX = 127;
constexpr uint32_t AUDIO_OUT_PARAM_FORMAT_MASK = 0x000000ffu;
constexpr int AUDIO_IN_SILENT_STATE_DEVICE_NONE = 0x1;
constexpr uint32_t AUDIO_IN_GRAIN_MAX_ASYNC = 384;
static bool audio_out_port_type_is_valid(int type) {
return (type >= AUDIO_OUT_PORT_TYPE_MAIN && type <= AUDIO_OUT_PORT_TYPE_PADSPK) ||
type == AUDIO_OUT_PORT_TYPE_VIBRATION || type == AUDIO_OUT_PORT_TYPE_AUDIO3D ||
@@ -89,9 +93,10 @@ public:
uint32_t AudioOutOutputs(OutputParam* params, uint32_t num, bool blocking = true);
bool AudioOutGetStatus(Id handle, int* type, int* channels_num);
Id AudioInOpen(uint32_t type, uint32_t samples_num, uint32_t freq, Format format);
bool AudioInValid(Id handle);
uint32_t AudioInInput(Id handle, void* dest);
Id AudioInOpen(uint32_t samples_num, uint32_t freq, Format format, bool asynchronous);
int AudioInClose(Id handle);
int AudioInGetSilentState(Id handle);
int AudioInInput(Id handle, void* dest);
static constexpr int OUT_PORTS_MAX = 32;
static constexpr int IN_PORTS_MAX = 8;
@@ -112,14 +117,18 @@ private:
};
struct PortIn {
bool used = false;
uint32_t type = 0;
uint32_t samples_num = 0;
uint32_t freq = 0;
Format format = Format::Unknown;
uint64_t last_input_time = 0;
bool used = false;
bool busy = false;
bool asynchronous = false;
uint32_t samples_num = 0;
uint32_t freq = 0;
uint32_t bytes_per_frame = 0;
uint64_t last_input_time = 0;
SDL_AudioDeviceID audio_device = 0;
};
PortIn* GetAudioInPort(Id handle); // Caller holds m_mutex.
Common::Mutex m_mutex;
PortOut m_out_ports[OUT_PORTS_MAX];
PortIn m_in_ports[IN_PORTS_MAX];
@@ -131,6 +140,8 @@ private:
static SDL_AudioFormat SdlFormat(Format format);
static bool OpenSdlDevice(PortOut* port);
static void CloseSdlDevice(PortOut* port);
static void OpenSdlDevice(PortIn* port, Format format);
static void CloseSdlDevice(PortIn* port);
static const void* PrepareOutputBuffer(const PortOut& port, const void* data,
std::vector<uint8_t>* buffer);
static bool QueueSdlAudio(PortOut* port, const void* data, bool blocking);
@@ -198,6 +209,9 @@ Audio::~Audio() {
for (auto& port: m_out_ports) {
CloseSdlDevice(&port);
}
for (auto& port: m_in_ports) {
CloseSdlDevice(&port);
}
}
bool Audio::FormatIsFloat(Format format) {
@@ -566,24 +580,55 @@ uint32_t Audio::AudioOutOutputs(OutputParam* params, uint32_t num, bool blocking
return first_port.samples_num;
}
Audio::Id Audio::AudioInOpen(uint32_t type, uint32_t samples_num, uint32_t freq, Format format) {
void Audio::OpenSdlDevice(PortIn* port, Format format) {
const auto& name = Config::GetAudioInputDevice();
if (name.empty()) {
return;
}
if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) {
LOGF("AudioIn: SDL init failed: %s\n", SDL_GetError());
return;
}
SDL_AudioSpec desired {};
desired.freq = static_cast<int>(port->freq);
desired.format = SdlFormat(format);
desired.channels = static_cast<Uint8>(port->bytes_per_frame / BytesPerSample(format));
desired.samples = static_cast<Uint16>(port->samples_num);
// SDL converts capture data to the guest format when allowed_changes is zero.
port->audio_device = SDL_OpenAudioDevice(name.c_str(), 1, &desired, nullptr, 0);
if (port->audio_device == 0) {
LOGF("AudioIn: cannot open '%s': %s; using silence\n", name.c_str(), SDL_GetError());
SDL_QuitSubSystem(SDL_INIT_AUDIO);
return;
}
LOGF("AudioIn: opened '%s' (%u Hz, %u ch)\n", name.c_str(), port->freq, desired.channels);
}
void Audio::CloseSdlDevice(PortIn* port) {
if (port->audio_device != 0) {
SDL_CloseAudioDevice(port->audio_device);
SDL_QuitSubSystem(SDL_INIT_AUDIO);
port->audio_device = 0;
}
}
Audio::Id Audio::AudioInOpen(uint32_t samples_num, uint32_t freq, Format format, bool asynchronous) {
Common::LockGuard lock(m_mutex);
for (int id = 0; id < IN_PORTS_MAX; id++) {
if (!m_in_ports[id].used) {
auto& port = m_in_ports[id];
port.used = true;
port.type = type;
port.samples_num = samples_num;
port.freq = freq;
port.format = format;
switch (format) {
case Format::Signed16bitMono:
case Format::Signed16bitStereo: break;
default: EXIT("unknown format");
port = {};
port.used = true;
port.asynchronous = asynchronous;
port.samples_num = samples_num;
port.freq = freq;
port.bytes_per_frame = BytesPerSample(format);
if (format == Format::Signed16bitStereo || format == Format::FloatStereo) {
port.bytes_per_frame *= 2;
}
OpenSdlDevice(&port, format);
return Id::Create(id);
}
@@ -592,31 +637,111 @@ Audio::Id Audio::AudioInOpen(uint32_t type, uint32_t samples_num, uint32_t freq,
return Id::Invalid();
}
bool Audio::AudioInValid(Id handle) {
Common::LockGuard lock(m_mutex);
return (handle.GetId() >= 0 && handle.GetId() < IN_PORTS_MAX &&
m_in_ports[handle.GetId()].used);
Audio::PortIn* Audio::GetAudioInPort(Id handle) {
if (handle.GetId() < 0 || handle.GetId() >= IN_PORTS_MAX ||
!m_in_ports[handle.GetId()].used) {
return nullptr;
}
return &m_in_ports[handle.GetId()];
}
uint32_t Audio::AudioInInput(Id handle, void* dest) {
EXIT_NOT_IMPLEMENTED(!AudioInValid(handle));
EXIT_NOT_IMPLEMENTED(dest == nullptr);
int Audio::AudioInClose(Id handle) {
Common::LockGuard lock(m_mutex);
auto* port = GetAudioInPort(handle);
if (port == nullptr) {
return AUDIO_IN_ERROR_INVALID_HANDLE;
}
if (port->busy) {
return AUDIO_IN_ERROR_BUSY;
}
CloseSdlDevice(port);
*port = {};
return OK;
}
const auto& port = m_in_ports[handle.GetId()];
int Audio::AudioInGetSilentState(Id handle) {
Common::LockGuard lock(m_mutex);
auto* port = GetAudioInPort(handle);
if (port == nullptr) {
return AUDIO_IN_ERROR_INVALID_HANDLE;
}
if (port->audio_device == 0 ||
SDL_GetAudioDeviceStatus(port->audio_device) == SDL_AUDIO_STOPPED) {
return AUDIO_IN_SILENT_STATE_DEVICE_NONE;
}
return 0;
}
uint64_t block_time = (1000000 * port.samples_num) / port.freq;
uint64_t current_time = LibKernel::KernelGetProcessTime();
int Audio::AudioInInput(Id handle, void* dest) {
PortIn snapshot;
{
Common::LockGuard lock(m_mutex);
auto* port = GetAudioInPort(handle);
if (port == nullptr) {
return AUDIO_IN_ERROR_INVALID_HANDLE;
}
if (port->busy) {
return AUDIO_IN_ERROR_BUSY;
}
port->busy = true;
snapshot = *port;
}
if (snapshot.audio_device != 0 && dest != nullptr) {
SDL_PauseAudioDevice(snapshot.audio_device, 0);
}
uint64_t next_time = m_in_ports[handle.GetId()].last_input_time + block_time;
uint64_t wait_time = (next_time > current_time ? next_time - current_time : 0);
// TODO(): Audio input is not yet implemented, so simulate audio delay
const uint64_t block_time = (1000000ULL * snapshot.samples_num) / snapshot.freq;
uint64_t wait_time = 0;
if (snapshot.asynchronous) {
if (dest != nullptr) {
wait_time = block_time;
}
} else if (snapshot.last_input_time != 0 && (snapshot.audio_device == 0 || dest == nullptr)) {
const auto now = LibKernel::KernelGetProcessTime();
const auto next = snapshot.last_input_time + block_time;
if (next > now) {
wait_time = next - now;
}
}
Common::Thread::SleepMicro(wait_time);
m_in_ports[handle.GetId()].last_input_time = LibKernel::KernelGetProcessTime();
return port.samples_num;
uint32_t frames = snapshot.samples_num;
bool failed = false;
if (dest != nullptr) {
if (snapshot.audio_device != 0) {
while (!snapshot.asynchronous &&
SDL_GetQueuedAudioSize(snapshot.audio_device) <
frames * snapshot.bytes_per_frame &&
SDL_GetAudioDeviceStatus(snapshot.audio_device) == SDL_AUDIO_PLAYING) {
Common::Thread::SleepMicro(1000);
}
failed = SDL_GetAudioDeviceStatus(snapshot.audio_device) != SDL_AUDIO_PLAYING;
if (!failed) {
if (snapshot.asynchronous) {
frames = AUDIO_IN_GRAIN_MAX_ASYNC;
}
frames = SDL_DequeueAudio(snapshot.audio_device, dest,
frames * snapshot.bytes_per_frame) /
snapshot.bytes_per_frame;
}
}
if (snapshot.audio_device == 0 || failed) {
std::memset(dest, 0, frames * snapshot.bytes_per_frame);
}
} else if (snapshot.audio_device != 0) {
SDL_PauseAudioDevice(snapshot.audio_device, 1);
SDL_ClearQueuedAudio(snapshot.audio_device);
}
{
Common::LockGuard lock(m_mutex);
auto& port = m_in_ports[handle.GetId()];
if (failed) {
LOGF("AudioIn: capture stopped; using silence\n");
CloseSdlDevice(&port);
}
port.last_input_time = dest != nullptr ? LibKernel::KernelGetProcessTime() : 0;
port.busy = false;
}
return dest != nullptr ? static_cast<int>(frames) : 0;
}
namespace AudioOut {
@@ -808,27 +933,36 @@ namespace AudioIn {
LIB_NAME("AudioIn", "AudioIn");
constexpr int AUDIO_IN_SILENT_STATE_DEVICE_NONE = 0x1;
int KYTY_SYSV_ABI AudioInOpen(int user_id, uint32_t type, uint32_t index, uint32_t len,
uint32_t freq, uint32_t param) {
PRINT_NAME();
static int OpenPort(int user_id, int type, int index, uint32_t len, uint32_t freq,
uint32_t param, bool asynchronous) {
LOGF("\t user_id = %d\n"
"\t type = %u\n"
"\t type = %d\n"
"\t index = %d\n"
"\t len = %u\n"
"\t freq = %u\n",
user_id, type, index, len, freq);
EXIT_NOT_IMPLEMENTED(type != 1);
EXIT_NOT_IMPLEMENTED(index != 0);
if (type != 0 && type != 1) {
return AUDIO_IN_ERROR_INVALID_TYPE;
}
if (index != 0) {
return AUDIO_IN_ERROR_INVALID_PARAM;
}
if (len != 128 && (asynchronous || len != 256)) {
return AUDIO_IN_ERROR_INVALID_SIZE;
}
if (freq != 48000 && (asynchronous || freq != 16000)) {
return AUDIO_IN_ERROR_INVALID_FREQ;
}
Audio::Format format = Audio::Format::Unknown;
switch (param) {
case 1: format = Audio::Format::Signed16bitMono; break;
case 2: format = Audio::Format::Signed16bitStereo; break;
case 0x10:
case 0x11: format = Audio::Format::FloatMono; break;
case 0x12: format = Audio::Format::FloatStereo; break;
default: return AUDIO_IN_ERROR_INVALID_PARAM;
}
@@ -836,7 +970,7 @@ int KYTY_SYSV_ABI AudioInOpen(int user_id, uint32_t type, uint32_t index, uint32
EXIT_IF(g_audio == nullptr);
auto id = g_audio->AudioInOpen(type, len, freq, format);
auto id = g_audio->AudioInOpen(len, freq, format, asynchronous);
if (!id.IsValid()) {
return AUDIO_IN_ERROR_PORT_FULL;
@@ -845,18 +979,35 @@ int KYTY_SYSV_ABI AudioInOpen(int user_id, uint32_t type, uint32_t index, uint32
return id.ToInt();
}
int KYTY_SYSV_ABI AudioInOpen(int user_id, int type, int index, uint32_t len,
uint32_t freq, uint32_t param) {
PRINT_NAME();
return OpenPort(user_id, type, index, len, freq, param, false);
}
int KYTY_SYSV_ABI AudioInHqOpen(int user_id, int type, int index, uint32_t len,
uint32_t freq, uint32_t param) {
PRINT_NAME();
return OpenPort(user_id, type, index, len, freq, param, true);
}
int KYTY_SYSV_ABI AudioInClose(int handle) {
PRINT_NAME();
EXIT_IF(g_audio == nullptr);
if (handle <= 0) {
return AUDIO_IN_ERROR_INVALID_HANDLE;
}
return g_audio->AudioInClose(Audio::Id(handle));
}
int KYTY_SYSV_ABI AudioInInput(int handle, void* dest) {
PRINT_NAME();
EXIT_NOT_IMPLEMENTED(dest == nullptr);
EXIT_IF(g_audio == nullptr);
if (!g_audio->AudioInValid(Audio::Id(handle))) {
if (handle <= 0) {
return AUDIO_IN_ERROR_INVALID_HANDLE;
}
return static_cast<int>(g_audio->AudioInInput(Audio::Id(handle), dest));
return g_audio->AudioInInput(Audio::Id(handle), dest);
}
int KYTY_SYSV_ABI AudioInGetSilentState(int handle) {
@@ -864,12 +1015,11 @@ int KYTY_SYSV_ABI AudioInGetSilentState(int handle) {
EXIT_IF(g_audio == nullptr);
if (!g_audio->AudioInValid(Audio::Id(handle))) {
if (handle <= 0) {
return AUDIO_IN_ERROR_INVALID_HANDLE;
}
// Audio input has no device backend yet, so every valid port receives silence.
return AUDIO_IN_SILENT_STATE_DEVICE_NONE;
return g_audio->AudioInGetSilentState(Audio::Id(handle));
}
} // namespace AudioIn
+4 -1
View File
@@ -96,8 +96,11 @@ int KYTY_SYSV_ABI AudioOut2MasteringTerm();
namespace AudioIn {
int KYTY_SYSV_ABI AudioInOpen(int user_id, uint32_t type, uint32_t index, uint32_t len,
int KYTY_SYSV_ABI AudioInOpen(int user_id, int type, int index, uint32_t len,
uint32_t freq, uint32_t param);
int KYTY_SYSV_ABI AudioInHqOpen(int user_id, int type, int index, uint32_t len,
uint32_t freq, uint32_t param);
int KYTY_SYSV_ABI AudioInClose(int handle);
int KYTY_SYSV_ABI AudioInInput(int handle, void* dest);
int KYTY_SYSV_ABI AudioInGetSilentState(int handle);
+12 -2
View File
@@ -384,6 +384,16 @@ static bool codec_is_supported_for_source(AvPlayerSourceType source_type, AVMedi
}
}
static const AVCodec* find_decoder(AVCodecID codec_id) {
const auto* decoder = avcodec_find_decoder(codec_id);
if (decoder == nullptr && codec_id == AV_CODEC_ID_VP9) {
Log::WriteToConsoleAndLog(
"WARNING: AvPlayer: The game requested VP9 video, but FFmpeg has no VP9 "
"decoder. Use FFmpeg with VP9 decoding enabled.\n");
}
return decoder;
}
struct PacketDeleter {
void operator()(AVPacket* packet) const { av_packet_free(&packet); }
};
@@ -1064,7 +1074,7 @@ private:
static_cast<int>(s->codecpar->codec_id), static_cast<uint32_t>(source_type));
return false;
}
if (avcodec_find_decoder(s->codecpar->codec_id) == nullptr) {
if (find_decoder(s->codecpar->codec_id) == nullptr) {
LOGF("\t decoder not found: stream=%d codec=%d\n", id,
static_cast<int>(s->codecpar->codec_id));
return false;
@@ -1073,7 +1083,7 @@ private:
}
bool OpenCodec(int id, AVCodecContext** out) {
auto* s = fmt->streams[id];
auto* dec = avcodec_find_decoder(s->codecpar->codec_id);
auto* dec = find_decoder(s->codecpar->codec_id);
if (dec == nullptr) {
LOGF("\t avcodec_find_decoder failed: stream=%d codec=%d\n", id,
static_cast<int>(s->codecpar->codec_id));
+112 -2
View File
@@ -13,6 +13,8 @@
#include "libs/padData.h"
#include <algorithm>
#include <array>
#include <cmath>
#include <cstring>
#include <vector>
@@ -87,6 +89,9 @@ struct ControllerState {
uint32_t buttons = 0;
int axes[static_cast<int>(Axis::AxisMax)] = {128, 128, 128, 128, 0, 0};
Touch touch[2];
std::array<float, 3> accel {0.0f, 1.0f, 0.0f};
std::array<float, 3> gyro {};
std::array<float, 4> orientation {0.0f, 0.0f, 0.0f, 1.0f};
};
class GameController {
@@ -101,6 +106,9 @@ public:
void Axis(int id, Axis axis, int value);
void RightStick(int id, int x, int y);
void TouchPad(int id, int finger, bool down, float x, float y);
void Motion(int id, Sensor sensor, const float* data, uint64_t time_us);
void SetMotionSensorState(bool enable);
void ResetOrientation();
void ResetInputState();
void ReleaseHostPads();
void GetConnectionInfo(bool* flag, int* count);
@@ -121,6 +129,8 @@ private:
int m_active_id = -1;
bool m_connected = false;
int m_connected_count = 0;
bool m_motion_enabled = true;
uint64_t m_gyro_time = 0;
ControllerState m_state;
ControllerState m_states[STATES_MAX];
bool m_obtained[STATES_MAX] {};
@@ -144,7 +154,16 @@ static void pad_fill_data(PadData* data, const ControllerState& state, bool conn
data->right_stick_y = state.axes[static_cast<int>(Axis::RightY)];
data->analog_buttons_l2 = state.axes[static_cast<int>(Axis::TriggerLeft)];
data->analog_buttons_r2 = state.axes[static_cast<int>(Axis::TriggerRight)];
data->orientation_w = 1.0f;
data->acceleration_x = state.accel[0];
data->acceleration_y = state.accel[1];
data->acceleration_z = state.accel[2];
data->angular_velocity_x = state.gyro[0];
data->angular_velocity_y = state.gyro[1];
data->angular_velocity_z = state.gyro[2];
data->orientation_x = state.orientation[0];
data->orientation_y = state.orientation[1];
data->orientation_z = state.orientation[2];
data->orientation_w = state.orientation[3];
for (const auto& touch: state.touch) {
if (touch.down) {
auto& output = data->touch_data.touch[data->touch_data.touch_num++];
@@ -273,6 +292,18 @@ void GameController::Connect(int id) {
m_connected_ids.push_back(id);
if (id != HOST_INPUT_CONTROLLER_ID) {
if (auto* pad = SDL_GameControllerFromInstanceID(static_cast<SDL_JoystickID>(id));
pad != nullptr) {
for (auto sensor: {SDL_SENSOR_ACCEL, SDL_SENSOR_GYRO}) {
if (SDL_GameControllerHasSensor(pad, sensor) &&
SDL_GameControllerSetSensorEnabled(pad, sensor, SDL_TRUE) != 0) {
LOGF("\t enabling controller sensor failed: %s\n", SDL_GetError());
}
}
}
}
CheckActive();
}
@@ -311,6 +342,7 @@ void GameController::CheckActive() {
m_active_id = new_active_id;
m_connected = new_connected;
m_state = {};
m_gyro_time = 0;
m_states_num = 0;
m_first_state = 0;
m_next_touch_id = 1;
@@ -402,9 +434,81 @@ void GameController::TouchPad(int id, int finger, bool down, float x, float y) {
}
}
void GameController::Motion(int id, Sensor sensor, const float* data, uint64_t time_us) {
Common::LockGuard lock(m_mutex);
if (id != m_active_id || !m_motion_enabled) {
return;
}
// Convert acceleration to G; angular velocity is already in rad/s.
if (sensor == Sensor::Accel) {
for (int i = 0; i < 3; i++) {
m_state.accel[i] = data[i] / SDL_STANDARD_GRAVITY;
}
} else {
std::copy_n(data, 3, m_state.gyro.begin());
// Do not extrapolate a single sample across lost reports (e.g. loss of window focus).
constexpr uint64_t max_gyro_interval_us = 100000;
if (m_gyro_time != 0 && time_us > m_gyro_time &&
time_us - m_gyro_time <= max_gyro_interval_us) {
const float dt = static_cast<float>(time_us - m_gyro_time) * 0.000001f;
const float speed =
std::sqrt(data[0] * data[0] + data[1] * data[1] + data[2] * data[2]);
const float half_angle = speed * dt * 0.5f;
const float scale = speed > 0.0f ? std::sin(half_angle) / speed : 0.0f;
const float x = data[0] * scale;
const float y = data[1] * scale;
const float z = data[2] * scale;
const float w = std::cos(half_angle);
const auto q = m_state.orientation;
// Accumulate body-local rotation relative to connection / orientation reset.
m_state.orientation = {q[3] * x + q[0] * w + q[1] * z - q[2] * y,
q[3] * y - q[0] * z + q[1] * w + q[2] * x,
q[3] * z + q[0] * y - q[1] * x + q[2] * w,
q[3] * w - q[0] * x - q[1] * y - q[2] * z};
float length = 0.0f;
for (float value: m_state.orientation) {
length += value * value;
}
length = std::sqrt(length);
for (float& value: m_state.orientation) {
value /= length;
}
}
m_gyro_time = time_us;
}
m_state.time = LibKernel::KernelGetProcessTime();
AddState();
}
void GameController::SetMotionSensorState(bool enable) {
Common::LockGuard lock(m_mutex);
if (m_motion_enabled != enable) {
m_motion_enabled = enable;
m_gyro_time = 0;
m_state.accel = {0.0f, 1.0f, 0.0f};
m_state.gyro = {};
m_state.time = LibKernel::KernelGetProcessTime();
AddState();
}
}
void GameController::ResetOrientation() {
Common::LockGuard lock(m_mutex);
m_state.orientation = {0.0f, 0.0f, 0.0f, 1.0f};
m_gyro_time = 0;
m_state.time = LibKernel::KernelGetProcessTime();
AddState();
}
void GameController::ResetInputState() {
Common::LockGuard lock(m_mutex);
m_state = {};
m_state.buttons = 0;
std::fill_n(m_state.axes, 4, 128);
std::fill_n(m_state.axes + 4, 2, 0);
for (auto& touch: m_state.touch) {
touch = {};
}
m_state.time = LibKernel::KernelGetProcessTime();
m_states_num = 0;
m_first_state = 0;
@@ -580,6 +684,10 @@ void SetTouchPad(int id, int finger, bool down, float x, float y) {
g_controller->TouchPad(id, finger, down, x, y);
}
void SetSensor(int id, Sensor sensor, const float* data, uint64_t time_us) {
g_controller->Motion(id, sensor, data, time_us);
}
void ResetInputState() {
g_controller->ResetInputState();
}
@@ -646,6 +754,7 @@ int KYTY_SYSV_ABI PadSetMotionSensorState(int handle, bool enable) {
}
LOGF("\t enable = %s\n", (enable ? "true" : "false"));
g_controller->SetMotionSensorState(enable);
return OK;
}
@@ -669,6 +778,7 @@ int KYTY_SYSV_ABI PadResetOrientation(int handle) {
return PAD_ERROR_INVALID_HANDLE;
}
g_controller->ResetOrientation();
return OK;
}
+3
View File
@@ -46,6 +46,8 @@ enum class Axis {
AxisMax
};
enum class Sensor { Accel, Gyro };
struct PadControllerInformation;
struct PadData;
struct PadVibrationParam;
@@ -69,6 +71,7 @@ void SetButton(int id, uint32_t button, bool down);
void SetAxis(int id, Axis axis, int value);
void SetRightStick(int id, int x, int y);
void SetTouchPad(int id, int finger, bool down, float x, float y);
void SetSensor(int id, Sensor sensor, const float* data, uint64_t time_us);
void ResetInputState();
int KYTY_SYSV_ABI PadInit();
+6 -1
View File
@@ -527,7 +527,12 @@ static int kyty_printf_internal(bool sn, char* sn_s, size_t sn_n, const char* fo
} else if (*format == '*') {
// const int prec = (int)va_arg(va, int);
const int prec = VaArg_int(va_list);
precision = prec > 0 ? static_cast<unsigned int>(prec) : 0U;
if (prec < 0) {
// A negative dynamic precision is treated as omitted.
flags &= ~FLAGS_PRECISION;
} else {
precision = static_cast<unsigned int>(prec);
}
format++;
}
}
+2
View File
@@ -179,6 +179,8 @@ namespace AudioIn = Audio::AudioIn;
LIB_DEFINE(InitAudio_1_AudioIn) {
LIB_FUNC("5NE8Sjc7VC8", AudioIn::AudioInOpen);
LIB_FUNC("nya-R5gDYhM", AudioIn::AudioInHqOpen);
LIB_FUNC("Jh6WbHhnI68", AudioIn::AudioInClose);
LIB_FUNC("LozEOU8+anM", AudioIn::AudioInInput);
LIB_FUNC("BohEAQ7DlUE", AudioIn::AudioInGetSilentState);
}
+11 -1
View File
@@ -38,7 +38,12 @@
#endif
extern "C" {
#include <libavutil/version.h>
#if LIBAVUTIL_VERSION_MAJOR < 61
extern const uint8_t avpriv_vga16_font[4096];
#else
const uint8_t* avpriv_vga16_font_get(void);
#endif
}
namespace Libs {
@@ -950,10 +955,15 @@ static void init_image(std::array<uint8_t, FONT_BITMAP_MAX_DIM * FONT_BITMAP_MAX
const auto width = static_cast<uint32_t>(scaled_font_width(font));
const auto height = static_cast<uint32_t>(scaled_font_height(font));
#if LIBAVUTIL_VERSION_MAJOR < 61
const auto* vga_font = avpriv_vga16_font;
#else
const auto* vga_font = avpriv_vga16_font_get();
#endif
for (uint32_t y = 0; y < height; y++) {
const uint32_t src_y = std::min<uint32_t>((y * 16u) / height, 15u);
const uint8_t row = avpriv_vga16_font[code * 16 + src_y];
const uint8_t row = vga_font[code * 16 + src_y];
for (uint32_t x = 0; x < width; x++) {
const uint32_t src_x = std::min<uint32_t>((x * 8u) / width, 7u);
if ((row & (0x80u >> src_x)) != 0) {
+61 -1
View File
@@ -41,6 +41,10 @@ struct JsonArray {
std::vector<JsonValue*>* impl;
};
struct JsonArrayIterator {
JsonValue* const* position;
};
struct JsonObject {
std::map<std::string, JsonValue*>* impl;
};
@@ -62,6 +66,7 @@ struct JsonValue {
};
static_assert(sizeof(JsonArray) == 8);
static_assert(sizeof(JsonArrayIterator) == 8);
static_assert(sizeof(JsonValue) == 32 && offsetof(JsonValue, type) == 28);
struct JsonInitParameter2 {
@@ -751,7 +756,11 @@ static const int64_t* KYTY_SYSV_ABI JsonValueGetInteger(const JsonValue* self) {
PRINT_NAME();
static const int64_t zero = 0;
return (self != nullptr && self->type == JsonValueTypeInteger ? &self->integer : &zero);
if (self != nullptr &&
(self->type == JsonValueTypeInteger || self->type == JsonValueTypeUInteger)) {
return &self->integer;
}
return &zero;
}
static const double* KYTY_SYSV_ABI JsonValueGetReal(const JsonValue* self) {
@@ -906,6 +915,51 @@ static size_t KYTY_SYSV_ABI JsonArraySize(const JsonArray* self) {
return JsonArrayImpl(self)->size();
}
// The non-trivial guest iterator is returned through an explicit result pointer.
static JsonArrayIterator* KYTY_SYSV_ABI JsonArrayBegin(JsonArrayIterator* result,
const JsonArray* self) {
PRINT_NAME();
result->position = JsonArrayImpl(self)->data();
return result;
}
static JsonArrayIterator* KYTY_SYSV_ABI JsonArrayEnd(JsonArrayIterator* result,
const JsonArray* self) {
PRINT_NAME();
const auto* impl = JsonArrayImpl(self);
result->position = impl->data();
if (!impl->empty()) {
result->position += impl->size();
}
return result;
}
static bool KYTY_SYSV_ABI JsonArrayIteratorNotEqual(const JsonArrayIterator* self,
const JsonArrayIterator* other) {
PRINT_NAME();
return self->position != other->position;
}
static JsonValue* KYTY_SYSV_ABI JsonArrayIteratorDereference(const JsonArrayIterator* self) {
PRINT_NAME();
return *self->position;
}
static JsonArrayIterator* KYTY_SYSV_ABI JsonArrayIteratorIncrement(JsonArrayIterator* self) {
PRINT_NAME();
++self->position;
return self;
}
static void KYTY_SYSV_ABI JsonArrayIteratorDtor(JsonArrayIterator*) {
PRINT_NAME();
}
static JsonObject* KYTY_SYSV_ABI JsonObjectCtor(JsonObject* self) {
PRINT_NAME();
@@ -1080,6 +1134,12 @@ LIB_DEFINE(InitNet_1_Json2) {
LIB_FUNC("3qrge7L-AU4", LibJson2::JsonValueGetReal);
LIB_FUNC("sOmU4vnx3s0", LibJson2::JsonValueDoubleCtor);
LIB_FUNC("rQGJeNjOuUk", LibJson2::JsonArraySize);
LIB_FUNC("bcH5EnFE2xY", LibJson2::JsonArrayBegin);
LIB_FUNC("WXF2ihRF+B8", LibJson2::JsonArrayEnd);
LIB_FUNC("5AZPp99ogrc", LibJson2::JsonArrayIteratorNotEqual);
LIB_FUNC("wcgr5mte7T8", LibJson2::JsonArrayIteratorDereference);
LIB_FUNC("w5+VCznos5E", LibJson2::JsonArrayIteratorIncrement);
LIB_FUNC("9yLjn46Ypfs", LibJson2::JsonArrayIteratorDtor);
LIB_FUNC("5yHuiWXo2gg", LibJson2::JsonValueSetBool);
LIB_FUNC("QxVVYhP-mvg", LibJson2::JsonValueSetInt);
LIB_FUNC("SIe1ZmW7e7s", LibJson2::JsonValueSetUInt);
+91 -166
View File
@@ -29,11 +29,9 @@
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <mutex>
#include <random>
#include <string>
#include <thread>
#include <unordered_map>
#include <vector>
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
@@ -43,9 +41,11 @@
#include <windows.h>
#elif defined(__APPLE__)
#include <csignal>
#include <pthread.h>
#include <sys/ucontext.h>
#else
#include <csignal>
#include <pthread.h>
#include <ucontext.h>
#endif
@@ -1168,7 +1168,6 @@ static int KYTY_SYSV_ABI KernelRaiseException(Pthread thread, int signum) {
return KERNEL_ERROR_EINVAL;
}
Common::CondVar::SignalThread(PthreadGetUniqueId(thread));
PthreadWakeForSignal(thread);
CloseHandle(target_thread);
return OK;
@@ -1193,7 +1192,6 @@ static int KYTY_SYSV_ABI KernelRaiseException(Pthread thread, int signum) {
return KERNEL_ERROR_EINVAL;
}
Common::CondVar::SignalThread(PthreadGetUniqueId(thread));
PthreadWakeForSignal(thread);
WaitForSignalDispatch(thread, signum);
return OK;
@@ -2358,132 +2356,71 @@ static bool FiberIsValid(const FiberObject* fiber) {
fiber->magic_end == FIBER_MAGIC_END;
}
static thread_local FiberObject* g_current_fiber = nullptr;
static thread_local FiberObject* g_thread_return_fiber = nullptr;
static thread_local FiberObject* g_starting_fiber = nullptr;
static thread_local FiberObject* g_pending_idle_fiber = nullptr;
static thread_local FiberCpuContext g_thread_fiber_context {};
static std::mutex g_fiber_owner_mutex;
static std::unordered_map<FiberObject*, uint64_t> g_fiber_owner_thread;
static std::unordered_map<uint64_t, FiberObject*> g_fiber_current_by_thread;
struct FiberThreadContext {
FiberObject* current_fiber;
FiberObject* returned_fiber;
FiberObject* pending_idle_fiber;
FiberCpuContext cpu_context;
};
static const auto g_fiber_context_key = [] {
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
const auto key = TlsAlloc();
if (key == TLS_OUT_OF_INDEXES) {
std::abort();
}
#else
pthread_key_t key;
if (pthread_key_create(&key, nullptr) != 0) {
std::abort();
}
#endif
return key;
}();
// Native lookup must stay fresh when a fiber resumes on another host thread.
static FiberThreadContext* FiberGetThreadContext() {
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
return static_cast<FiberThreadContext*>(TlsGetValue(g_fiber_context_key));
#else
return static_cast<FiberThreadContext*>(pthread_getspecific(g_fiber_context_key));
#endif
}
static void FiberSetThreadContext(FiberThreadContext* context) {
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
if (TlsSetValue(g_fiber_context_key, context) == 0) {
#else
if (pthread_setspecific(g_fiber_context_key, context) != 0) {
#endif
std::abort();
}
}
static uint32_t FiberLoadState(const FiberObject* fiber) {
auto& state = const_cast<uint32_t&>(fiber->state);
return std::atomic_ref<uint32_t>(state).load(std::memory_order_acquire);
}
static uint64_t FiberCurrentHostThreadId() {
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
return static_cast<uint64_t>(::GetCurrentThreadId());
#else
return std::hash<std::thread::id> {}(std::this_thread::get_id());
#endif
}
static void FiberSetOwner(FiberObject* fiber) {
std::lock_guard lock(g_fiber_owner_mutex);
g_fiber_owner_thread[fiber] = FiberCurrentHostThreadId();
}
static void FiberClearOwner(FiberObject* fiber) {
std::lock_guard lock(g_fiber_owner_mutex);
g_fiber_owner_thread.erase(fiber);
}
static uint64_t FiberGetOwner(FiberObject* fiber) {
std::lock_guard lock(g_fiber_owner_mutex);
auto it = g_fiber_owner_thread.find(fiber);
return it != g_fiber_owner_thread.end() ? it->second : 0;
}
static void FiberSetCurrentFiber(FiberObject* fiber) {
g_current_fiber = fiber;
std::lock_guard lock(g_fiber_owner_mutex);
const auto thread_id = FiberCurrentHostThreadId();
if (fiber != nullptr) {
g_fiber_current_by_thread[thread_id] = fiber;
} else {
g_fiber_current_by_thread.erase(thread_id);
}
}
static void FiberStoreState(FiberObject* fiber, uint32_t state) {
std::atomic_ref<uint32_t>(fiber->state).store(state, std::memory_order_release);
if (state == FIBER_STATE_RUNNING) {
FiberSetOwner(fiber);
} else {
FiberClearOwner(fiber);
}
}
static void FiberDeferIdle(FiberObject* fiber) {
g_pending_idle_fiber = fiber;
}
static void FiberCommitDeferredIdle() {
auto* fiber = g_pending_idle_fiber;
g_pending_idle_fiber = nullptr;
static void FiberCompleteSwitch(FiberObject* current) {
auto* context = FiberGetThreadContext();
context->current_fiber = current;
// Only publish IDLE after switching away from the departing fiber's stack.
auto* fiber = context->pending_idle_fiber;
context->pending_idle_fiber = nullptr;
if (fiber != nullptr) {
FiberStoreState(fiber, FIBER_STATE_IDLE);
}
}
static bool FiberCompareExchangeState(FiberObject* fiber, uint32_t expected, uint32_t desired) {
const bool ok = std::atomic_ref<uint32_t>(fiber->state)
.compare_exchange_strong(expected, desired, std::memory_order_acq_rel,
std::memory_order_acquire);
if (ok) {
if (desired == FIBER_STATE_RUNNING) {
FiberSetOwner(fiber);
} else {
FiberClearOwner(fiber);
}
}
return ok;
}
static bool FiberWaitAndEnterRunning(FiberObject* fiber, uint32_t* observed_state) {
const auto start = std::chrono::steady_clock::now();
uint32_t spin = 0;
auto& state = fiber->state;
auto ref = std::atomic_ref<uint32_t>(state);
for (;;) {
uint32_t expected = FIBER_STATE_IDLE;
if (ref.compare_exchange_strong(expected, FIBER_STATE_RUNNING, std::memory_order_acq_rel,
std::memory_order_acquire)) {
FiberSetOwner(fiber);
return true;
}
if (observed_state != nullptr) {
*observed_state = expected;
}
if (expected != FIBER_STATE_RUNNING) {
return false;
}
if (std::chrono::steady_clock::now() - start >= std::chrono::milliseconds(5)) {
return false;
}
if (spin++ < 64) {
std::this_thread::yield();
} else {
std::this_thread::sleep_for(std::chrono::microseconds(100));
}
}
}
static bool FiberRepairStaleRunningOnThisThread(FiberObject* fiber, uint32_t observed_state) {
if (observed_state != FIBER_STATE_RUNNING || fiber == g_current_fiber) {
return false;
}
const auto owner = FiberGetOwner(fiber);
if (owner != 0) {
return false;
}
return FiberCompareExchangeState(fiber, FIBER_STATE_RUNNING, FIBER_STATE_IDLE);
return std::atomic_ref<uint32_t>(fiber->state)
.compare_exchange_strong(expected, desired, std::memory_order_acq_rel,
std::memory_order_acquire);
}
static void FiberSetContextValid(FiberObject* fiber, bool valid) {
@@ -2540,45 +2477,36 @@ static void FiberRestoreContext(FiberCpuContext* ctx, uint64_t ret) {
}
#endif
[[noreturn]] static void FiberStartTrampoline();
[[noreturn]] static KYTY_SYSV_ABI void FiberStartTrampoline(FiberObject* fiber);
[[noreturn]] static void FiberStartOnGuestStack(FiberObject* fiber) {
[[noreturn]] static KYTY_SYSV_ABI void FiberStartOnGuestStack(FiberObject* fiber) {
FiberCpuContext ctx {};
const auto stack_top = reinterpret_cast<uintptr_t>(fiber->addr_context) +
static_cast<uintptr_t>(fiber->size_context);
auto rsp = (stack_top & ~static_cast<uintptr_t>(0x0f));
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
rsp -= 4u * sizeof(uint64_t);
#endif
rsp -= sizeof(uint64_t);
*reinterpret_cast<uint64_t*>(rsp) = 0;
ctx.rsp = rsp;
ctx.rip = reinterpret_cast<uint64_t>(&FiberStartTrampoline);
ctx.rdi = reinterpret_cast<uint64_t>(fiber);
g_starting_fiber = fiber;
FiberRestoreContext(&ctx, 1);
}
[[noreturn]] static void FiberStartTrampoline() {
auto* fiber = g_starting_fiber;
g_starting_fiber = nullptr;
if (fiber == nullptr) {
EXIT("Fiber start without current fiber\n");
}
FiberCommitDeferredIdle();
FiberSetCurrentFiber(fiber);
[[noreturn]] static KYTY_SYSV_ABI void FiberStartTrampoline(FiberObject* fiber) {
FiberCompleteSwitch(fiber);
fiber->entry(fiber->arg_on_initialize, fiber->arg_on_run);
FiberStoreState(fiber, FIBER_STATE_TERMINATED);
FiberSetContextValid(fiber, false);
fiber->arg_on_return = 0;
g_thread_return_fiber = fiber;
FiberSetCurrentFiber(nullptr);
auto* context = FiberGetThreadContext();
context->returned_fiber = fiber;
context->current_fiber = nullptr;
FiberRestoreContext(&g_thread_fiber_context, 1);
FiberRestoreContext(&context->cpu_context, 1);
}
int32_t KYTY_SYSV_ABI FiberInitialize(FiberObject* fiber, const char* name, FiberEntry entry,
@@ -2688,34 +2616,39 @@ int32_t KYTY_SYSV_ABI FiberRun(FiberObject* fiber, uint64_t arg_on_run, uint64_t
if (!FiberIsValid(fiber)) {
return FIBER_ERROR_INVALID;
}
if (g_current_fiber != nullptr) {
if (FiberGetThreadContext() != nullptr) {
return FIBER_ERROR_PERMISSION;
}
if (!FiberCompareExchangeState(fiber, FIBER_STATE_IDLE, FIBER_STATE_RUNNING)) {
return FIBER_ERROR_STATE;
}
fiber->arg_on_run = arg_on_run;
fiber->arg_on_return = 0;
g_thread_return_fiber = nullptr;
fiber->arg_on_run = arg_on_run;
fiber->arg_on_return = 0;
// Register on the host stack; switching fibers only reads the native TLS slot.
FiberThreadContext context {};
FiberSetThreadContext(&context);
if (FiberSaveContext(&g_thread_fiber_context) == 0) {
if (FiberSaveContext(&context.cpu_context) == 0) {
if (fiber->context_valid) {
FiberRestoreContext(&fiber->saved_context, 1);
}
FiberStartOnGuestStack(fiber);
}
FiberCommitDeferredIdle();
FiberSetCurrentFiber(nullptr);
auto* returned_fiber = (g_thread_return_fiber != nullptr ? g_thread_return_fiber : fiber);
g_thread_return_fiber = nullptr;
auto* returned_fiber = (context.returned_fiber != nullptr ? context.returned_fiber : fiber);
const auto return_value = returned_fiber->arg_on_return;
const auto return_code =
FiberLoadState(returned_fiber) == FIBER_STATE_TERMINATED ? FIBER_ERROR_STATE : OK;
// Copy results before another thread can acquire and resume the fiber.
FiberCompleteSwitch(nullptr);
FiberSetThreadContext(nullptr);
if (arg_on_return != nullptr) {
*arg_on_return = returned_fiber->arg_on_return;
*arg_on_return = return_value;
}
return (FiberLoadState(returned_fiber) == FIBER_STATE_TERMINATED ? FIBER_ERROR_STATE : OK);
return return_code;
}
int32_t KYTY_SYSV_ABI FiberSwitch(FiberObject* fiber, uint64_t arg_on_run,
@@ -2725,38 +2658,30 @@ int32_t KYTY_SYSV_ABI FiberSwitch(FiberObject* fiber, uint64_t arg_on_run,
if (!FiberIsValid(fiber)) {
return FIBER_ERROR_INVALID;
}
if (g_current_fiber == nullptr) {
auto* context = FiberGetThreadContext();
if (context == nullptr) {
return FIBER_ERROR_PERMISSION;
}
for (;;) {
uint32_t observed_state = 0;
if (FiberWaitAndEnterRunning(fiber, &observed_state)) {
break;
}
if (FiberRepairStaleRunningOnThisThread(fiber, observed_state)) {
continue;
}
if (!FiberCompareExchangeState(fiber, FIBER_STATE_IDLE, FIBER_STATE_RUNNING)) {
return FIBER_ERROR_STATE;
}
auto* caller = g_current_fiber;
auto* caller = context->current_fiber;
fiber->arg_on_run = arg_on_run;
fiber->arg_on_return = 0;
if (FiberSaveContext(&caller->saved_context) == 0) {
FiberSetContextValid(caller, true);
FiberDeferIdle(caller);
context->pending_idle_fiber = caller;
if (fiber->context_valid) {
FiberRestoreContext(&fiber->saved_context, 1);
}
FiberStartOnGuestStack(fiber);
}
FiberCommitDeferredIdle();
FiberSetCurrentFiber(caller);
FiberStoreState(caller, FIBER_STATE_RUNNING);
FiberCompleteSwitch(caller);
if (arg_on_return != nullptr) {
*arg_on_return = caller->arg_on_run;
@@ -2772,7 +2697,8 @@ int32_t KYTY_SYSV_ABI FiberGetSelf(FiberObject** fiber) {
return FIBER_ERROR_NULL;
}
*fiber = g_current_fiber;
const auto* context = FiberGetThreadContext();
*fiber = context != nullptr ? context->current_fiber : nullptr;
return OK;
}
@@ -2780,23 +2706,22 @@ int32_t KYTY_SYSV_ABI FiberGetSelf(FiberObject** fiber) {
int32_t KYTY_SYSV_ABI FiberReturnToThread(uint64_t arg_on_return, uint64_t* arg_on_run) {
PRINT_NAME();
if (g_current_fiber == nullptr) {
auto* context = FiberGetThreadContext();
if (context == nullptr) {
return FIBER_ERROR_PERMISSION;
}
auto* fiber = g_current_fiber;
auto* fiber = context->current_fiber;
fiber->arg_on_return = arg_on_return;
if (FiberSaveContext(&fiber->saved_context) == 0) {
FiberSetContextValid(fiber, true);
g_thread_return_fiber = fiber;
FiberDeferIdle(fiber);
FiberRestoreContext(&g_thread_fiber_context, 1);
context->returned_fiber = fiber;
context->pending_idle_fiber = fiber;
FiberRestoreContext(&context->cpu_context, 1);
}
FiberCommitDeferredIdle();
FiberSetCurrentFiber(fiber);
FiberStoreState(fiber, FIBER_STATE_RUNNING);
FiberCompleteSwitch(fiber);
if (arg_on_run != nullptr) {
*arg_on_run = fiber->arg_on_run;
}
+6 -6
View File
@@ -583,22 +583,22 @@ int KYTY_SYSV_ABI SaveDataTransferringMount(const SaveDataTransferringMount* mou
SaveDataMountResult* mount_result) {
PRINT_NAME();
if (mount == nullptr || mount_result == nullptr || mount->dir_name == nullptr) {
if (mount == nullptr || mount_result == nullptr || mount->title_id == nullptr ||
mount->dir_name == nullptr) {
return SAVE_DATA_ERROR_PARAMETER;
}
LOGF("\t user_id = %" PRId32 "\n"
"\t title_id = %s\n"
"\t dir_name = %s\n",
mount->user_id, mount->title_id != nullptr ? mount->title_id->data : "<null>",
mount->dir_name->data);
mount->user_id, mount->title_id->data, mount->dir_name->data);
*mount_result = {};
Common::LockGuard lock(g_mount_mutex);
const std::string dir_name = mount->dir_name->data;
const std::string mount_dir =
std::string(SAVE_DATA_DIR) + "/" + get_title_id() + "/" + dir_name;
std::string(SAVE_DATA_DIR) + "/" + mount->title_id->data + "/" + dir_name;
const int slot = g_mount_slots.FindAvailable(dir_name);
if (slot == SaveDataMountSlots::BUSY) {
return SAVE_DATA_ERROR_BUSY;
@@ -608,10 +608,10 @@ int KYTY_SYSV_ABI SaveDataTransferringMount(const SaveDataTransferringMount* mou
}
if (!Common::File::IsDirectoryExisting(mount_dir)) {
Common::File::CreateDirectories(mount_dir);
return SAVE_DATA_ERROR_NOT_FOUND;
}
return mount_save_data(slot, dir_name, mount_dir, 1, mount_result);
return mount_save_data(slot, dir_name, mount_dir, 0, mount_result);
}
int KYTY_SYSV_ABI SaveDataUmount2(uint32_t mode, const SaveDataMountPoint* mount_point) {
+5
View File
@@ -92,6 +92,11 @@ public:
[[nodiscard]] bool Initialize() {
const AVCodec* decoder = avcodec_find_decoder(GetAvCodecId(m_config.codec_type));
if (decoder == nullptr) {
if (m_config.codec_type == CODEC_TYPE_VP9) {
Log::WriteToConsoleAndLog(
"WARNING: Videodec2: The game requested VP9 video, but FFmpeg has no VP9 "
"decoder. Use FFmpeg with VP9 decoding enabled.\n");
}
LOGF("Videodec2: FFmpeg decoder is unavailable for codec type %u\n",
m_config.codec_type);
return false;
File diff suppressed because it is too large. Load diff
+6 -3
View File
@@ -14,7 +14,7 @@ namespace Loader {
struct RedZonePatchResult {
uint64_t function_count = 0;
uint64_t instruction_count = 0;
uint64_t red_zone_function_count = 0;
uint64_t red_zone_function_count = 0;
uint64_t memory_instruction_count = 0;
uint64_t short_memory_instruction_count = 0;
uint64_t patched_memory_instruction_count = 0;
@@ -22,14 +22,17 @@ struct RedZonePatchResult {
uint64_t control_flow_memory_instruction_count = 0;
uint64_t unrelocatable_memory_instruction_count = 0;
uint64_t indirect_red_zone_function_count = 0;
uint64_t reciprocal_sqrt_instruction_count = 0;
};
void RegisterRedZonePatchModule(void* module_ptr, uint64_t module_size, void* trampoline_area_ptr,
uint64_t trampoline_area_size);
void UnregisterRedZonePatchModule(void* module_ptr);
RedZonePatchResult PatchRedZoneMemoryInstructions(uint64_t segment_addr, uint64_t segment_size,
std::span<const uintptr_t> function_starts);
// Analyze and protect fault sites before replacing reciprocal roots with traps.
RedZonePatchResult PatchGuestInstructions(uint64_t segment_addr, uint64_t segment_size,
std::span<const uintptr_t> function_starts,
bool protect_memory, bool emulate_rsqrt);
bool DecodeEhFrameFunctionStarts(uint64_t eh_frame_header_addr, uint64_t eh_frame_header_size,
std::vector<uintptr_t>* function_starts);
+25 -14
View File
@@ -2014,9 +2014,11 @@ void RuntimeLinker::LoadProgramToMemory(Program* program) {
uint64_t tls_handler_size = is_shared ? 0 : Jit::SafeCall::GetSize();
EXIT_IF(tls_handler_size > UINT64_MAX - program->base_size_aligned);
program->mapped_size = program->base_size_aligned + tls_handler_size;
const bool emulate_rsqrt = Config::AmdCpuEnabled();
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
const bool use_red_zone_protection = Config::RedZoneProtectionEnabled();
const bool protect_memory_faults = Config::RedZoneProtectionEnabled();
const bool use_red_zone_protection = protect_memory_faults || emulate_rsqrt;
constexpr uint64_t RED_ZONE_TRAMPOLINE_SIZE = 8u * 1024u * 1024u;
if (use_red_zone_protection) {
EXIT_IF(RED_ZONE_TRAMPOLINE_SIZE > UINT64_MAX - program->mapped_size);
@@ -2062,9 +2064,8 @@ void RuntimeLinker::LoadProgramToMemory(Program* program) {
EXIT("Failed to install the required vectored exception handler\n");
}
// program->elf->SetBaseVAddr(program->base_vaddr);
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
std::vector<std::pair<uint64_t, uint64_t>> executable_segments;
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
uint64_t eh_frame_header_addr = 0;
uint64_t eh_frame_header_size = 0;
#endif
@@ -2090,11 +2091,7 @@ void RuntimeLinker::LoadProgramToMemory(Program* program) {
if (Common::VirtualMemory::IsExecute(mode)) {
PatchProgram(program, segment_addr, segment_memory_size);
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
if (use_red_zone_protection) {
executable_segments.emplace_back(segment_addr, segment_file_size);
}
#endif
executable_segments.emplace_back(segment_addr, segment_file_size);
}
if (!skip_protect) {
@@ -2139,16 +2136,22 @@ void RuntimeLinker::LoadProgramToMemory(Program* program) {
}
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
std::vector<uintptr_t> function_starts;
if (use_red_zone_protection) {
std::vector<uintptr_t> function_starts;
if (!DecodeEhFrameFunctionStarts(eh_frame_header_addr, eh_frame_header_size,
&function_starts)) {
LOGF("Windows guest red-zone patching could not decode function boundaries for %s\n",
Common::PathToString(program->file_name).c_str());
}
for (const auto& [segment_addr, segment_size]: executable_segments) {
}
#endif
for (const auto& [segment_addr, segment_size]: executable_segments) {
uint64_t reciprocal_sqrt_count = 0;
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
if (use_red_zone_protection) {
const auto result =
PatchRedZoneMemoryInstructions(segment_addr, segment_size, function_starts);
PatchGuestInstructions(segment_addr, segment_size, function_starts,
protect_memory_faults, emulate_rsqrt);
LOGF("Windows guest red-zone patching: %s, functions=%" PRIu64 ", red_zone=%" PRIu64
", memory=%" PRIu64 ", patched=%" PRIu64 ", short=%" PRIu64 ", stack=%" PRIu64
", control=%" PRIu64 ", unrelocatable=%" PRIu64 "\n",
@@ -2158,12 +2161,20 @@ void RuntimeLinker::LoadProgramToMemory(Program* program) {
result.stack_dependent_memory_instruction_count,
result.control_flow_memory_instruction_count,
result.unrelocatable_memory_instruction_count);
reciprocal_sqrt_count = result.reciprocal_sqrt_instruction_count;
}
#else
if (emulate_rsqrt) {
reciprocal_sqrt_count =
X64InstructionEmulator::PatchReciprocalSquareRoots(segment_addr, segment_size);
Common::VirtualMemory::FlushInstructionCache(segment_addr, segment_size);
}
Common::VirtualMemory::FlushInstructionCache(program->red_zone_trampoline_vaddr,
program->red_zone_trampoline_size);
}
#endif
if (reciprocal_sqrt_count != 0) {
LOGF("Guest VRSQRTPS emulation: %s, instructions=%" PRIu64 "\n",
Common::PathToString(program->file_name.filename()).c_str(), reciprocal_sqrt_count);
}
}
if (!is_shared) {
SetupTlsHandler(program);
+334 -330
View File
@@ -2,11 +2,19 @@
#include "common/common.h"
#include <Zydis/Zydis.h>
#include <bit>
#include <cstring>
#if !defined(__APPLE__)
#include <emmintrin.h>
#include <xmmintrin.h>
#endif
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
#include <windows.h> // IWYU pragma: keep
#elif !defined(__APPLE__)
#elif defined(__APPLE__)
#include <sys/ucontext.h>
#else
#include <sched.h>
#include <ucontext.h>
#endif
@@ -400,109 +408,170 @@ static bool ExecuteShaNiInsn(const ShaNiInsn& insn, const XmmWords& src2, const
}
}
// Keep instruction semantics shared; only access to the saved host context differs.
struct Context {
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
PCONTEXT native;
static void LoadXmmWordsWin(const M128A* xmm, XmmWords& out) {
out.w[0] = static_cast<uint32_t>(xmm->Low);
out.w[1] = static_cast<uint32_t>(xmm->Low >> 32u);
out.w[2] = static_cast<uint32_t>(xmm->High);
out.w[3] = static_cast<uint32_t>(xmm->High >> 32u);
}
[[nodiscard]] uint64_t Rip() const { return native->Rip; }
void Advance(size_t length) { native->Rip += length; }
[[nodiscard]] void* Xmm(uint8_t index) const { return &native->Xmm0 + index; }
static void StoreXmmWordsWin(M128A* xmm, const XmmWords& in) {
xmm->Low = static_cast<uint64_t>(in.w[0]) | (static_cast<uint64_t>(in.w[1]) << 32u);
xmm->High = static_cast<uint64_t>(in.w[2]) | (static_cast<uint64_t>(in.w[3]) << 32u);
void LoadGprs(uint64_t (&gpr)[16]) const {
const uint64_t registers[] = {native->Rax, native->Rcx, native->Rdx, native->Rbx,
native->Rsp, native->Rbp, native->Rsi, native->Rdi,
native->R8, native->R9, native->R10, native->R11,
native->R12, native->R13, native->R14, native->R15};
std::memcpy(gpr, registers, sizeof(gpr));
}
void ClearUpperYmm(uint8_t index) const {
if ((native->ContextFlags & CONTEXT_XSTATE) != CONTEXT_XSTATE) {
return;
}
DWORD64 features = 0;
if (!GetXStateFeaturesMask(native, &features) || (features & XSTATE_MASK_AVX) == 0) {
return; // An absent AVX component restores zeroes.
}
DWORD size = 0;
auto* ymm = static_cast<M128A*>(LocateXStateFeature(native, XSTATE_AVX, &size));
if (ymm != nullptr && size >= (index + 1u) * sizeof(M128A)) {
ymm[index] = {};
}
}
#elif defined(__APPLE__)
ucontext_t* native;
[[nodiscard]] uint64_t Rip() const {
return static_cast<uint64_t>(native->uc_mcontext->__ss.__rip);
}
void Advance(size_t length) {
native->uc_mcontext->__ss.__rip += static_cast<uint64_t>(length);
}
// Darwin names the XMM file __fpu_xmm0..__fpu_xmm15 instead of exposing an array.
[[nodiscard]] void* Xmm(uint8_t index) const {
auto* fs = &native->uc_mcontext->__fs;
switch (index) {
case 0: return &fs->__fpu_xmm0;
case 1: return &fs->__fpu_xmm1;
case 2: return &fs->__fpu_xmm2;
case 3: return &fs->__fpu_xmm3;
case 4: return &fs->__fpu_xmm4;
case 5: return &fs->__fpu_xmm5;
case 6: return &fs->__fpu_xmm6;
case 7: return &fs->__fpu_xmm7;
case 8: return &fs->__fpu_xmm8;
case 9: return &fs->__fpu_xmm9;
case 10: return &fs->__fpu_xmm10;
case 11: return &fs->__fpu_xmm11;
case 12: return &fs->__fpu_xmm12;
case 13: return &fs->__fpu_xmm13;
case 14: return &fs->__fpu_xmm14;
case 15: return &fs->__fpu_xmm15;
default: return nullptr;
}
}
#else
ucontext_t* native;
[[nodiscard]] uint64_t Rip() const {
return static_cast<uint64_t>(native->uc_mcontext.gregs[REG_RIP]);
}
void Advance(size_t length) {
native->uc_mcontext.gregs[REG_RIP] += static_cast<greg_t>(length);
}
[[nodiscard]] void* Xmm(uint8_t index) const {
if (native->uc_mcontext.fpregs == nullptr) {
return nullptr;
}
return native->uc_mcontext.fpregs->_xmm[index].element;
}
void LoadGprs(uint64_t (&gpr)[16]) const {
constexpr int registers[] = {REG_RAX, REG_RCX, REG_RDX, REG_RBX, REG_RSP, REG_RBP,
REG_RSI, REG_RDI, REG_R8, REG_R9, REG_R10, REG_R11,
REG_R12, REG_R13, REG_R14, REG_R15};
for (size_t i = 0; i < 16; ++i) {
gpr[i] = static_cast<uint64_t>(native->uc_mcontext.gregs[registers[i]]);
}
}
void ClearUpperYmm(uint8_t index) const {
// Linux signal frames use the standard XSAVE layout. An absent AVX component
// already restores the architectural initial value (all zeroes).
auto* state = reinterpret_cast<uint8_t*>(native->uc_mcontext.fpregs);
uint32_t magic = 0;
uint32_t size = 0;
uint64_t features = 0;
std::memcpy(&magic, state + 464, sizeof(magic));
if (magic != 0x46505853) {
return;
}
std::memcpy(&size, state + 480, sizeof(size));
if (size < 832) {
return;
}
std::memcpy(&features, state + 512, sizeof(features));
if ((features & 4) != 0) {
std::memset(state + 576 + index * 16, 0, 16);
}
}
#endif
};
#if !defined(__APPLE__)
static bool TryEmulateShaNi(Context& context) {
const auto* rip = reinterpret_cast<const uint8_t*>(context.Rip());
ShaNiInsn insn {};
if (!DecodeShaNiInsn(rip, insn)) {
return false;
}
const uint8_t modrm_byte = rip[insn.modrm_offset];
const uint8_t dest_index = ShaNiRegIndex(modrm_byte, insn.rex, true);
auto* dest_xmm = context.Xmm(dest_index);
auto* xmm0 = context.Xmm(0);
if (dest_xmm == nullptr || xmm0 == nullptr) {
return false;
}
XmmWords dest {};
XmmWords src2 {};
XmmWords xmm0_words {};
std::memcpy(&dest, dest_xmm, sizeof(dest));
std::memcpy(&xmm0_words, xmm0, sizeof(xmm0_words));
if (ShaNiModrmIsRegister(modrm_byte)) {
const uint8_t src_index = ShaNiRegIndex(modrm_byte, insn.rex, false);
auto* src_xmm = context.Xmm(src_index);
if (src_xmm == nullptr) {
return false;
}
std::memcpy(&src2, src_xmm, sizeof(src2));
} else {
uint64_t gpr[16] {};
const void* source = nullptr;
context.LoadGprs(gpr);
if (!ResolveShaNiMemoryAddress(rip, insn, gpr, source)) {
return false;
}
std::memcpy(&src2, source, sizeof(src2));
}
if (!ExecuteShaNiInsn(insn, src2, xmm0_words, dest)) {
return false;
}
std::memcpy(dest_xmm, &dest, sizeof(dest));
context.Advance(insn.length);
return true;
}
#endif
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
static M128A* GetContextXmm(PCONTEXT context, uint8_t index) {
if (context == nullptr || index >= 16) {
return nullptr;
}
return &context->Xmm0 + index;
}
static void LoadContextGprsWin(PCONTEXT context, uint64_t (&gpr)[16]) {
gpr[0] = context->Rax;
gpr[1] = context->Rcx;
gpr[2] = context->Rdx;
gpr[3] = context->Rbx;
gpr[4] = context->Rsp;
gpr[5] = context->Rbp;
gpr[6] = context->Rsi;
gpr[7] = context->Rdi;
gpr[8] = context->R8;
gpr[9] = context->R9;
gpr[10] = context->R10;
gpr[11] = context->R11;
gpr[12] = context->R12;
gpr[13] = context->R13;
gpr[14] = context->R14;
gpr[15] = context->R15;
}
static bool TryEmulateShaNi(PCONTEXT context) {
if (context == nullptr) {
return false;
}
const auto* rip = reinterpret_cast<const uint8_t*>(context->Rip);
ShaNiInsn insn {};
if (!DecodeShaNiInsn(rip, insn)) {
return false;
}
const uint8_t modrm_byte = rip[insn.modrm_offset];
const uint8_t dest_index = ShaNiRegIndex(modrm_byte, insn.rex, true);
auto* dest_xmm = GetContextXmm(context, dest_index);
auto* xmm0 = GetContextXmm(context, 0);
if (dest_xmm == nullptr || xmm0 == nullptr) {
return false;
}
XmmWords dest {};
XmmWords src2 {};
XmmWords xmm0_words {};
LoadXmmWordsWin(dest_xmm, dest);
LoadXmmWordsWin(xmm0, xmm0_words);
if (ShaNiModrmIsRegister(modrm_byte)) {
const uint8_t src_index = ShaNiRegIndex(modrm_byte, insn.rex, false);
auto* src_xmm = GetContextXmm(context, src_index);
if (src_xmm == nullptr) {
return false;
}
LoadXmmWordsWin(src_xmm, src2);
} else {
uint64_t gpr[16] {};
const void* source = nullptr;
LoadContextGprsWin(context, gpr);
if (!ResolveShaNiMemoryAddress(rip, insn, gpr, source)) {
return false;
}
std::memcpy(&src2, source, sizeof(src2));
}
if (!ExecuteShaNiInsn(insn, src2, xmm0_words, dest)) {
return false;
}
StoreXmmWordsWin(dest_xmm, dest);
context->Rip += insn.length;
return true;
}
static bool TryEmulateSse4a(PCONTEXT context) {
if (context == nullptr) {
return false;
}
const auto* rip = reinterpret_cast<const uint8_t*>(context->Rip);
static bool TryEmulateSse4a(Context& context) {
const auto* rip = reinterpret_cast<const uint8_t*>(context.Rip());
const uint8_t prefix = rip[0];
if (prefix != 0x66 && prefix != 0xf2) {
return false;
@@ -511,277 +580,212 @@ static bool TryEmulateSse4a(PCONTEXT context) {
size_t offset = 1;
uint8_t rex = 0;
if ((rip[offset] & 0xf0u) == 0x40u) {
rex = rip[offset];
offset++;
rex = rip[offset++];
}
if (rip[offset] != 0x0f || rip[offset + 1] != 0x78) {
if (rip[offset] != 0x0f) {
return false;
}
const bool register_extract = prefix == 0x66 && rip[offset + 1] == 0x79;
if (rip[offset + 1] != 0x78 && !register_extract) {
return false;
}
auto modrm = rip[offset + 2];
const uint8_t modrm = rip[offset + 2];
if ((modrm & 0xc0u) != 0xc0u) {
return false;
}
const uint8_t reg = ((modrm >> 3u) & 0x07u) | ((rex & 0x04u) << 1u);
const uint8_t rm = (modrm & 0x07u) | ((rex & 0x01u) << 3u);
const uint8_t length = rip[offset + 3];
const uint8_t index = rip[offset + 4];
const uint8_t reg = ((modrm >> 3u) & 0x07u) | ((rex & 0x04u) << 1u);
const uint8_t rm = (modrm & 0x07u) | ((rex & 0x01u) << 3u);
// AMD SSE4a immediate-form EXTRQ/INSERTQ. PS5 code can execute these natively on AMD hardware,
// while Intel hosts raise an illegal-instruction exception.
if (prefix == 0x66) {
auto* dst = GetContextXmm(context, rm);
if (dst == nullptr) {
return false;
}
dst->Low = ExtractBitField(dst->Low, length, index);
dst->High = 0;
context->Rip += offset + 5;
return true;
// Immediate EXTRQ encodes its destination in r/m; the two-register form uses reg.
uint8_t dest_index = reg;
if (prefix == 0x66 && !register_extract) {
dest_index = rm;
}
auto* dst = GetContextXmm(context, reg);
auto* src = GetContextXmm(context, rm);
if (dst == nullptr || src == nullptr) {
auto* dest_xmm = context.Xmm(dest_index);
auto* src_xmm = context.Xmm(rm);
if (dest_xmm == nullptr || src_xmm == nullptr) {
return false;
}
dst->Low = InsertBitField(dst->Low, src->Low, length, index);
context->Rip += offset + 5;
return true;
}
static bool TryEmulateMonitorxMwaitx(PCONTEXT context) {
if (context == nullptr) {
return false;
}
const auto* rip = reinterpret_cast<const uint8_t*>(context->Rip);
if (rip[0] != 0x0f || rip[1] != 0x01 || (rip[2] != 0xfa && rip[2] != 0xfb)) {
return false;
}
// AMD MONITORX/MWAITX are used by PS5 code in wait loops. Intel hosts can raise an illegal-
// instruction exception, so approximate them as a no-op/yield pair.
if (rip[2] == 0xfb) {
SwitchToThread();
}
context->Rip += 3;
return true;
}
#elif !defined(__APPLE__)
// Linux signal contexts expose registers through ucontext_t.
static uint32_t* GetContextXmm(ucontext_t* context, uint8_t index) {
if (context == nullptr || index >= 16) {
return nullptr;
}
auto* fpregs = context->uc_mcontext.fpregs;
if (fpregs == nullptr) {
return nullptr;
}
return static_cast<uint32_t*>(fpregs->_xmm[index].element);
}
static void LoadContextGprsLin(ucontext_t* context, uint64_t (&gpr)[16]) {
gpr[0] = static_cast<uint64_t>(context->uc_mcontext.gregs[REG_RAX]);
gpr[1] = static_cast<uint64_t>(context->uc_mcontext.gregs[REG_RCX]);
gpr[2] = static_cast<uint64_t>(context->uc_mcontext.gregs[REG_RDX]);
gpr[3] = static_cast<uint64_t>(context->uc_mcontext.gregs[REG_RBX]);
gpr[4] = static_cast<uint64_t>(context->uc_mcontext.gregs[REG_RSP]);
gpr[5] = static_cast<uint64_t>(context->uc_mcontext.gregs[REG_RBP]);
gpr[6] = static_cast<uint64_t>(context->uc_mcontext.gregs[REG_RSI]);
gpr[7] = static_cast<uint64_t>(context->uc_mcontext.gregs[REG_RDI]);
gpr[8] = static_cast<uint64_t>(context->uc_mcontext.gregs[REG_R8]);
gpr[9] = static_cast<uint64_t>(context->uc_mcontext.gregs[REG_R9]);
gpr[10] = static_cast<uint64_t>(context->uc_mcontext.gregs[REG_R10]);
gpr[11] = static_cast<uint64_t>(context->uc_mcontext.gregs[REG_R11]);
gpr[12] = static_cast<uint64_t>(context->uc_mcontext.gregs[REG_R12]);
gpr[13] = static_cast<uint64_t>(context->uc_mcontext.gregs[REG_R13]);
gpr[14] = static_cast<uint64_t>(context->uc_mcontext.gregs[REG_R14]);
gpr[15] = static_cast<uint64_t>(context->uc_mcontext.gregs[REG_R15]);
}
static void LoadXmmWordsLin(const uint32_t* xmm, XmmWords& out) {
out.w[0] = xmm[0];
out.w[1] = xmm[1];
out.w[2] = xmm[2];
out.w[3] = xmm[3];
}
static void StoreXmmWordsLin(uint32_t* xmm, const XmmWords& in) {
xmm[0] = in.w[0];
xmm[1] = in.w[1];
xmm[2] = in.w[2];
xmm[3] = in.w[3];
}
static bool TryEmulateShaNi(ucontext_t* context) {
if (context == nullptr) {
return false;
}
auto& rip_reg = context->uc_mcontext.gregs[REG_RIP];
const auto* rip = reinterpret_cast<const uint8_t*>(rip_reg);
ShaNiInsn insn {};
if (!DecodeShaNiInsn(rip, insn)) {
return false;
}
const uint8_t modrm_byte = rip[insn.modrm_offset];
const uint8_t dest_index = ShaNiRegIndex(modrm_byte, insn.rex, true);
auto* dest_xmm = GetContextXmm(context, dest_index);
auto* xmm0 = GetContextXmm(context, 0);
if (dest_xmm == nullptr || xmm0 == nullptr) {
return false;
}
XmmWords dest {};
XmmWords src2 {};
XmmWords xmm0_words {};
LoadXmmWordsLin(dest_xmm, dest);
LoadXmmWordsLin(xmm0, xmm0_words);
if (ShaNiModrmIsRegister(modrm_byte)) {
const uint8_t src_index = ShaNiRegIndex(modrm_byte, insn.rex, false);
auto* src_xmm = GetContextXmm(context, src_index);
if (src_xmm == nullptr) {
return false;
}
LoadXmmWordsLin(src_xmm, src2);
uint64_t dest[2] {};
uint64_t source = 0;
std::memcpy(dest, dest_xmm, sizeof(dest));
std::memcpy(&source, src_xmm, sizeof(source));
uint8_t length = 0;
uint8_t index = 0;
size_t instruction_length = offset + 3;
if (register_extract) {
length = static_cast<uint8_t>(source);
index = static_cast<uint8_t>(source >> 8u);
} else {
uint64_t gpr[16] {};
const void* source = nullptr;
LoadContextGprsLin(context, gpr);
if (!ResolveShaNiMemoryAddress(rip, insn, gpr, source)) {
return false;
}
std::memcpy(&src2, source, sizeof(src2));
length = rip[offset + 3];
index = rip[offset + 4];
instruction_length += 2;
}
if (!ExecuteShaNiInsn(insn, src2, xmm0_words, dest)) {
return false;
}
StoreXmmWordsLin(dest_xmm, dest);
rip_reg += static_cast<greg_t>(insn.length);
return true;
}
static uint64_t GetXmmLow(const uint32_t* xmm) {
return static_cast<uint64_t>(xmm[0]) | (static_cast<uint64_t>(xmm[1]) << 32u);
}
static void SetXmmLow(uint32_t* xmm, uint64_t value) {
xmm[0] = static_cast<uint32_t>(value);
xmm[1] = static_cast<uint32_t>(value >> 32u);
}
static void SetXmmHigh(uint32_t* xmm, uint64_t value) {
xmm[2] = static_cast<uint32_t>(value);
xmm[3] = static_cast<uint32_t>(value >> 32u);
}
static bool TryEmulateSse4a(ucontext_t* context) {
if (context == nullptr) {
return false;
}
auto& rip_reg = context->uc_mcontext.gregs[REG_RIP];
const auto* rip = reinterpret_cast<const uint8_t*>(rip_reg);
const uint8_t prefix = rip[0];
if (prefix != 0x66 && prefix != 0xf2) {
return false;
}
size_t offset = 1;
uint8_t rex = 0;
if ((rip[offset] & 0xf0u) == 0x40u) {
rex = rip[offset];
offset++;
}
if (rip[offset] != 0x0f || rip[offset + 1] != 0x78) {
return false;
}
auto modrm = rip[offset + 2];
if ((modrm & 0xc0u) != 0xc0u) {
return false;
}
const uint8_t reg = ((modrm >> 3u) & 0x07u) | ((rex & 0x04u) << 1u);
const uint8_t rm = (modrm & 0x07u) | ((rex & 0x01u) << 3u);
const uint8_t length = rip[offset + 3];
const uint8_t index = rip[offset + 4];
// AMD SSE4a immediate-form EXTRQ/INSERTQ.
if (prefix == 0x66) {
auto* dst = GetContextXmm(context, rm);
if (dst == nullptr) {
return false;
}
SetXmmLow(dst, ExtractBitField(GetXmmLow(dst), length, index));
SetXmmHigh(dst, 0);
rip_reg += static_cast<greg_t>(offset + 5);
return true;
dest[0] = ExtractBitField(dest[0], length, index);
dest[1] = 0;
} else {
dest[0] = InsertBitField(dest[0], source, length, index);
}
auto* dst = GetContextXmm(context, reg);
auto* src = GetContextXmm(context, rm);
if (dst == nullptr || src == nullptr) {
return false;
}
SetXmmLow(dst, InsertBitField(GetXmmLow(dst), GetXmmLow(src), length, index));
rip_reg += static_cast<greg_t>(offset + 5);
std::memcpy(dest_xmm, dest, sizeof(dest));
context.Advance(instruction_length);
return true;
}
static bool TryEmulateMonitorxMwaitx(ucontext_t* context) {
if (context == nullptr) {
return false;
}
#if !defined(__APPLE__)
auto& rip_reg = context->uc_mcontext.gregs[REG_RIP];
const auto* rip = reinterpret_cast<const uint8_t*>(rip_reg);
static bool TryEmulateMonitorxMwaitx(Context& context) {
const auto* rip = reinterpret_cast<const uint8_t*>(context.Rip());
if (rip[0] != 0x0f || rip[1] != 0x01 || (rip[2] != 0xfa && rip[2] != 0xfb)) {
return false;
}
// Approximate AMD MONITORX/MWAITX as no-op/yield.
if (rip[2] == 0xfb) {
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
SwitchToThread();
#else
::sched_yield();
#endif
}
rip_reg += 3;
context.Advance(3);
return true;
}
static uint32_t ReciprocalSquareRoot(uint32_t bits) {
const uint32_t magnitude = bits & 0x7fffffffu;
const uint32_t exponent = magnitude & 0x7f800000u;
if (exponent == 0) {
// RSQRT treats denormals as signed zero regardless of MXCSR.DAZ.
return (bits & 0x80000000u) | 0x7f800000u;
}
if (magnitude > 0x7f800000u) {
return bits | 0x00400000u; // Quiet NaNs without raising an exception.
}
if ((bits & 0x80000000u) != 0) {
return 0xffc00000u;
}
if (magnitude == 0x7f800000u) {
return 0;
}
// A deterministic accurate estimate meets the instruction's relative-error
// bound without relying on the host vendor's approximation table.
const __m128d input = _mm_set_sd(static_cast<double>(std::bit_cast<float>(bits)));
const __m128d result = _mm_div_sd(_mm_set_sd(1.0), _mm_sqrt_sd(input, input));
return std::bit_cast<uint32_t>(_mm_cvtss_f32(_mm_cvtsd_ss(_mm_setzero_ps(), result)));
}
static bool TryEmulateReciprocalSquareRoot(Context& context) {
const auto* rip = reinterpret_cast<const uint8_t*>(context.Rip());
size_t prefix_size = 0;
uint8_t dest_extension = 0;
uint8_t src_extension = 0;
if (rip[0] == 0xc5 && (rip[1] & 0x7fu) == 0x70u) {
prefix_size = 2;
dest_extension = (~rip[1] & 0x80u) >> 4u;
} else if (rip[0] == 0xc4 && (rip[1] & 0x1fu) == 1 && (rip[2] & 0x7fu) == 0x70u) {
prefix_size = 3;
dest_extension = (~rip[1] & 0x80u) >> 4u;
src_extension = (~rip[1] & 0x20u) >> 2u;
} else {
return false;
}
if (rip[prefix_size] != 0x52 || (rip[prefix_size + 1] & 0xc0u) != 0xc0u) {
return false;
}
const uint8_t modrm = rip[prefix_size + 1];
const uint8_t dest = ((modrm >> 3u) & 7u) | dest_extension;
const uint8_t source = (modrm & 7u) | src_extension;
auto* dest_xmm = context.Xmm(dest);
auto* src_xmm = context.Xmm(source);
if (dest_xmm == nullptr || src_xmm == nullptr) {
return false;
}
XmmWords result {};
std::memcpy(&result, src_xmm, sizeof(result));
// RSQRT ignores the rounding mode and never changes guest exception flags.
// Mask host exceptions while calculating, then restore the handler's state.
const uint32_t mxcsr = _mm_getcsr();
_mm_setcsr(0x1f80);
for (auto& word: result.w) {
word = ReciprocalSquareRoot(word);
}
_mm_setcsr(mxcsr);
std::memcpy(dest_xmm, &result, sizeof(result));
context.ClearUpperYmm(dest);
context.Advance(prefix_size + 2);
return true;
}
#endif
bool IsReciprocalSquareRoot(const ZydisDecodedInstruction& instruction,
const ZydisDecodedOperand* operands) {
return instruction.mnemonic == ZYDIS_MNEMONIC_VRSQRTPS &&
instruction.encoding == ZYDIS_INSTRUCTION_ENCODING_VEX &&
instruction.raw.vex.offset == 0 && operands[0].size == 128 &&
operands[1].type == ZYDIS_OPERAND_TYPE_REGISTER;
}
uint64_t PatchReciprocalSquareRoots(uint64_t address, uint64_t size) {
uint64_t patched = 0;
#if !defined(__APPLE__)
ZydisDecoder decoder {};
if (!ZYAN_SUCCESS(
ZydisDecoderInit(&decoder, ZYDIS_MACHINE_MODE_LONG_64, ZYDIS_STACK_WIDTH_64))) {
return 0;
}
for (uint64_t offset = 0; offset < size;) {
auto* code = reinterpret_cast<uint8_t*>(address + offset);
ZydisDecodedInstruction instruction {};
ZydisDecodedOperand operands[ZYDIS_MAX_OPERAND_COUNT] {};
if (!ZYAN_SUCCESS(
ZydisDecoderDecodeFull(&decoder, code, size - offset, &instruction, operands))) {
++offset;
continue;
}
if (IsReciprocalSquareRoot(instruction, operands)) {
// vvvv is reserved (must be 1111b). Clear one bit to route this
// otherwise intact instruction through the illegal-instruction emulator.
code[instruction.raw.vex.size - 1] &= ~0x08u;
++patched;
}
offset += instruction.length;
}
#else
(void)address;
(void)size;
#endif
return patched;
}
bool TryEmulate(void* native_context) {
if (native_context == nullptr) {
return false;
}
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
auto* context = static_cast<PCONTEXT>(native_context);
return TryEmulateMonitorxMwaitx(context) || TryEmulateSse4a(context) ||
TryEmulateShaNi(context);
#elif !defined(__APPLE__)
auto* context = static_cast<ucontext_t*>(native_context);
Context context {static_cast<PCONTEXT>(native_context)};
#elif defined(__APPLE__)
auto* saved_context = static_cast<ucontext_t*>(native_context);
if (saved_context->uc_mcontext == nullptr) {
return false;
}
Context context {saved_context};
#else
Context context {static_cast<ucontext_t*>(native_context)};
#endif
#if !defined(__APPLE__)
if (TryEmulateReciprocalSquareRoot(context)) {
return true;
}
return TryEmulateMonitorxMwaitx(context) || TryEmulateSse4a(context) ||
TryEmulateShaNi(context);
#else
(void)native_context;
return false;
return TryEmulateSse4a(context);
#endif
}
+6
View File
@@ -1,8 +1,14 @@
#ifndef KYTY_LOADER_X64_INSTRUCTION_EMULATOR_H_
#define KYTY_LOADER_X64_INSTRUCTION_EMULATOR_H_
#include <Zydis/DecoderTypes.h>
#include <cstdint>
namespace Loader::X64InstructionEmulator {
[[nodiscard]] bool IsReciprocalSquareRoot(const ZydisDecodedInstruction& instruction,
const ZydisDecodedOperand* operands);
uint64_t PatchReciprocalSquareRoots(uint64_t address, uint64_t size);
[[nodiscard]] bool TryEmulate(void* native_context);
} // namespace Loader::X64InstructionEmulator
Loaded 100 of 106 files, more files were not shown because too many files have changed in this diff. Show more