Compare commits

...
Author SHA1 Message Date
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
nmzikandBlackCAT a9ae04c152 graphics: decode observed FSR setup registers
Inspired by https://github.com/KytyPS5/KytyPS5/pull/607

Co-authored-by: BlackCAT <52720489+psnwd@users.noreply.github.com>
2026-09-14 10:30:28 +04:00
nmzikandBlackCAT 8e35e1233b hmd2: initialize reprojection and virtual headset field of view
Inspired by https://github.com/KytyPS5/KytyPS5/pull/607

Co-authored-by: BlackCAT <52720489+psnwd@users.noreply.github.com>
2026-09-14 10:30:28 +04:00
nmzikandBlackCAT 5610976720 videoout: map buses to dense port slots before opening
Inspired by https://github.com/KytyPS5/KytyPS5/pull/607

Co-authored-by: BlackCAT <52720489+psnwd@users.noreply.github.com>
2026-09-14 10:30:28 +04:00
nmzikandBlackCAT 1133272668 hmd2: initialize a virtual headset with --vr
Inspired by https://github.com/KytyPS5/KytyPS5/pull/607

Co-authored-by: BlackCAT <52720489+psnwd@users.noreply.github.com>
2026-09-14 10:30:28 +04:00
nmzik bbebb6419b graphics: preserve PS5 array mip contents during cache promotion
PPSA05975
2026-09-14 06:58:32 +04:00
nmzik b67cb81359 avplayer: report loop boundaries when decoded output is delivered 2026-09-14 06:58:32 +04:00
nmzikandBlackCAT f3c82a317a kernel: support timed umtx waits through shared address deadlines
Co-authored-by: BlackCAT <52720489+psnwd@users.noreply.github.com>
2026-09-14 06:58:32 +04:00
nmzik f426d60ff1 net: use the shared host resolver for Linux name lookups 2026-09-14 06:58:32 +04:00
nmzikandAli Almohaya b2fdad9e47 memory: preserve live POSIX mappings during partial unmap
Co-authored-by: Ali Almohaya <AliAlmohaya@gmail.com>
2026-09-14 06:58:32 +04:00
nmzik 87971a28f5 gpu: isolate tessellation analysis and SPIR-V emission 2026-09-14 06:58:32 +04:00
nmzik 96d62b3367 gpu: reflect tessellation strides from memory address dataflow 2026-09-14 06:58:32 +04:00
nmzik 7516068662 gpu: compile RDNA2 tessellation through native LS HS and TES stages 2026-09-14 06:58:32 +04:00
nmzik 6d1ba5870a renderer: share graphics resource preparation across shader stages 2026-09-14 06:58:32 +04:00
nmzik 00a16f3120 gpu: retain fused hull shader user-data addresses 2026-09-14 06:58:32 +04:00
nmzik d3d7bd33f8 launcher: Add game size column 2026-09-12 17:56:44 +04:00
Mehmet Cambaz 069f928edc net: return copied bytes for truncated datagram receives (#510)
Normalize Winsock WSAEMSGSIZE on receive to the copied byte count before converting the peer address. Preserve other receive errors and oversized send failures.
2026-09-12 17:51:43 +04:00
Mininautandnmzik 0f3674f531 kernel: release descriptors when rejecting read-only truncation (#504)
Co-authored-by: nmzik <Nmzik@mail.ru>
2026-09-12 17:44:45 +04:00
125 changed files with 7620 additions and 2380 deletions

No files matched your search

+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
+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)
+26 -2
View File
@@ -171,7 +171,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 +192,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; }
+12
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;
}
@@ -55,6 +59,14 @@ bool FullscreenEnabled() {
return g_config->fullscreen_enabled;
}
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);
}
+6
View File
@@ -43,9 +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;
@@ -76,9 +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);
};
@@ -3654,12 +3659,13 @@ void GraphicsInitJmpTablesShIndirect() {
g_hw_sh_indirect_func[Pm4::SPI_GRAPHICS_SHADER_CONTROL_HS] = [](KYTY_HW_SH_INDIRECT_ARGS) {
HwShIgnoreShaderRegister(cmd_offset, value);
};
g_hw_sh_indirect_func[Pm4::SPI_SHADER_USER_DATA_ADDR_LO_HS] = [](KYTY_HW_SH_INDIRECT_ARGS) {
HwShIgnoreShaderRegister(cmd_offset, value);
};
g_hw_sh_indirect_func[Pm4::SPI_SHADER_USER_DATA_ADDR_HI_HS] = [](KYTY_HW_SH_INDIRECT_ARGS) {
HwShIgnoreShaderRegister(cmd_offset, value);
};
for (uint32_t offset = Pm4::SPI_SHADER_USER_DATA_ADDR_LO_HS;
offset <= Pm4::SPI_SHADER_USER_DATA_ADDR_HI_HS; offset++) {
g_hw_sh_indirect_func[offset] = [](KYTY_HW_SH_INDIRECT_ARGS) {
cp.GetShCtx().SetHsUserDataAddress(cmd_offset - Pm4::SPI_SHADER_USER_DATA_ADDR_LO_HS,
value);
};
}
g_hw_sh_indirect_func[Pm4::SPI_SHADER_PGM_LO_HS] = [](KYTY_HW_SH_INDIRECT_ARGS) {
auto base = cp.GetShCtx().GetVs().hs_regs.data_addr;
base &= 0xFFFFFF00000000FFull;
@@ -3818,6 +3824,25 @@ void GraphicsInitJmpTablesUcIndirect() {
for (auto& func: g_hw_uc_indirect_func) {
func = nullptr;
}
for (uint32_t i = 0; i < 4; ++i) {
g_hw_uc_indirect_func[Pm4::FSR_CONTROL_POINTS_LEFT_X + i] = [](KYTY_HW_UC_INDIRECT_ARGS) {
cp.GetUcfg().SetFsrControlPoint(0, cmd_offset - Pm4::FSR_CONTROL_POINTS_LEFT_X, value);
};
g_hw_uc_indirect_func[Pm4::FSR_CONTROL_POINTS_LEFT_Y + i] = [](KYTY_HW_UC_INDIRECT_ARGS) {
cp.GetUcfg().SetFsrControlPoint(1, cmd_offset - Pm4::FSR_CONTROL_POINTS_LEFT_Y, value);
};
}
for (uint32_t i = 0; i < 2; ++i) {
g_hw_uc_indirect_func[Pm4::FSR_ALPHA_LEFT_X + i] = [](KYTY_HW_UC_INDIRECT_ARGS) {
cp.GetUcfg().SetFsrAlpha(0, cmd_offset - Pm4::FSR_ALPHA_LEFT_X, value);
};
g_hw_uc_indirect_func[Pm4::FSR_ALPHA_LEFT_Y + i] = [](KYTY_HW_UC_INDIRECT_ARGS) {
cp.GetUcfg().SetFsrAlpha(1, cmd_offset - Pm4::FSR_ALPHA_LEFT_Y, value);
};
g_hw_uc_indirect_func[Pm4::FSR_WINDOW_LEFT + i] = [](KYTY_HW_UC_INDIRECT_ARGS) {
cp.GetUcfg().SetFsrWindow(cmd_offset - Pm4::FSR_WINDOW_LEFT, value);
};
}
g_hw_uc_indirect_func[Pm4::GE_CNTL] = [](KYTY_HW_UC_INDIRECT_ARGS) {
HW::GeControl r;
+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);
+27 -5
View File
@@ -498,7 +498,8 @@ struct LsStageRegisters {
};
struct HsStageRegisters {
uint64_t data_addr = 0;
uint64_t data_addr = 0;
uint64_t user_data_addr = 0;
HsShaderResource1 rsrc1;
HsShaderResource2 rsrc2;
};
@@ -935,6 +936,12 @@ private:
ShaderRegisters m_sh_regs;
};
struct FsrView {
uint32_t control_points[2][4] {};
uint32_t alphas[2][2] {};
uint32_t window[2] {};
};
class UserConfig {
public:
UserConfig() = default;
@@ -964,8 +971,17 @@ public:
void SetGdsOaCntl(uint32_t value) { m_gds_oa.cntl = value; }
void SetGdsOaCounter(uint32_t value) { m_gds_oa.counters[m_gds_oa.GetIndex()].counter = value; }
void SetGdsOaAddress(uint32_t value) { m_gds_oa.counters[m_gds_oa.GetIndex()].address = value; }
void SetFsrControlPoint(uint32_t axis, uint32_t index, uint32_t value) {
m_fsr_view.control_points[axis][index] = value;
}
void SetFsrAlpha(uint32_t axis, uint32_t index, uint32_t value) {
m_fsr_view.alphas[axis][index] = value;
}
void SetFsrWindow(uint32_t index, uint32_t value) { m_fsr_view.window[index] = value; }
[[nodiscard]] const FsrView& GetFsrView() const { return m_fsr_view; }
private:
FsrView m_fsr_view;
Prospero::PrimitiveType m_prim_type = Prospero::PrimitiveType::kNone;
uint32_t m_index_offset = 0;
uint32_t m_object_id = 0;
@@ -988,14 +1004,14 @@ public:
void SetEsShaderBase(uint64_t addr) { m_vs.es_regs.data_addr = addr; }
void SetLsShaderBase(uint64_t addr) { m_vs.ls_regs.data_addr = addr; }
void SetHsShaderBase(uint64_t addr) { m_vs.hs_regs.data_addr = addr; }
void SetHsUserDataAddress(uint32_t word, uint32_t value) {
SetUserDataAddressWord(m_vs.hs_regs.user_data_addr, word, value);
}
void SetHsShaderResource1(const HsShaderResource1& rsrc1) { m_vs.hs_regs.rsrc1 = rsrc1; }
void SetHsShaderResource2(const HsShaderResource2& rsrc2) { m_vs.hs_regs.rsrc2 = rsrc2; }
void SetGsShaderBase(uint64_t addr) { m_vs.gs_regs.data_addr = addr; }
void SetGsUserDataAddress(uint32_t word, uint32_t value) {
const auto shift = word * 32u;
auto& address = m_vs.gs_regs.user_data_addr;
address = (address & ~(uint64_t {0xffffffffu} << shift)) |
(static_cast<uint64_t>(value) << shift);
SetUserDataAddressWord(m_vs.gs_regs.user_data_addr, word, value);
}
void SetGsShaderResource1(const GsShaderResource1& rsrc1) { m_vs.gs_regs.rsrc1 = rsrc1; }
void SetGsShaderResource2(const GsShaderResource2& rsrc2) { m_vs.gs_regs.rsrc2 = rsrc2; }
@@ -1037,6 +1053,12 @@ public:
[[nodiscard]] const ComputeShaderInfo& GetCs() const { return m_cs; }
private:
static void SetUserDataAddressWord(uint64_t& address, uint32_t word, uint32_t value) {
const auto shift = word * 32u;
address = (address & ~(uint64_t {0xffffffffu} << shift)) |
(static_cast<uint64_t>(value) << shift);
}
VertexShaderInfo m_vs;
PixelShaderInfo m_ps;
ComputeShaderInfo m_cs;
+6
View File
@@ -987,6 +987,12 @@ constexpr uint32_t GE_USER_VGPR_EN_EN_USER_VGPR2_MASK = 0x1;
constexpr uint32_t GE_USER_VGPR_EN_EN_USER_VGPR3_SHIFT = 2;
constexpr uint32_t GE_USER_VGPR_EN_EN_USER_VGPR3_MASK = 0x1;
constexpr uint32_t FSR_WINDOW_LEFT = 0x2BC;
constexpr uint32_t FSR_CONTROL_POINTS_LEFT_X = 0x2C4;
constexpr uint32_t FSR_CONTROL_POINTS_LEFT_Y = 0x2CC;
constexpr uint32_t FSR_ALPHA_LEFT_X = 0x2E4;
constexpr uint32_t FSR_ALPHA_LEFT_Y = 0x2E8;
constexpr uint32_t TA_CS_BC_BASE_ADDR = 0x380;
constexpr uint32_t TA_CS_BC_BASE_ADDR_HI = 0x381;
constexpr uint32_t TEXTURE_GRADIENT_FACTORS = 0x383;
+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;
+131 -157
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);
}
@@ -810,17 +814,10 @@ TextureCache::OverlapResult TextureCache::ResolveOverlap(const ImageInfo& reques
const bool safe_to_delete =
current_tick - std::min(current_tick, cached.tick_accessed_last) > NumFramesBeforeRemoval;
if (requested.data.address == cached.info.data.address) {
const uint32_t requested_block = requested.bytes_per_block * requested.samples;
const uint32_t cached_block = cached.info.bytes_per_block * cached.info.samples;
if (requested.BlockExtent() != cached.info.BlockExtent() ||
requested_block != cached_block) {
if (safe_to_delete) {
FreeImage(cached_id);
}
return {merged_id};
}
const uint32_t requested_block = requested.bytes_per_block * requested.samples;
const uint32_t cached_block = cached.info.bytes_per_block * cached.info.samples;
if (requested.data.address == cached.info.data.address &&
requested.BlockExtent() == cached.info.BlockExtent() && requested_block == cached_block) {
if (const auto depth_id = ResolveDepthOverlap(requested, binding, cached_id)) {
return {depth_id};
}
@@ -850,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;
@@ -859,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
@@ -873,38 +877,27 @@ TextureCache::OverlapResult TextureCache::ResolveOverlap(const ImageInfo& reques
static_cast<uint32_t>(cached.info.tile_mode));
}
if (requested.data.address > cached.info.data.address) {
const int32_t mip = requested.MipOf(cached.info);
if (mip >= 0) {
const int32_t layer = requested.SliceOf(cached.info, mip);
if (layer >= 0) {
return {cached_id, mip, layer};
}
}
if (safe_to_delete) {
FreeImage(cached_id);
}
return {};
const int32_t requested_mip = requested.MipOf(cached.info);
if (requested_mip >= 0) {
const int32_t layer = requested.SliceOf(cached.info, requested_mip);
return {cached_id, requested_mip, layer};
}
const int32_t mip = cached.info.MipOf(requested);
if (mip >= 0) {
const int32_t layer = cached.info.SliceOf(requested, mip);
if (layer >= 0) {
if (cached.binding.is_target) {
cached.binding.needs_rebind = true;
if (merged_id) {
m_slot_images[merged_id].binding.is_target = true;
}
FreeImage(cached_id);
return {merged_id};
}
if (merged_id) {
CopyImageMip(merged_id, cached_id, static_cast<uint32_t>(mip),
static_cast<uint32_t>(layer));
FreeImage(cached_id);
}
if (!merged_id) {
return {ExpandImage(requested, cached_id)};
}
cached.binding.needs_rebind |= cached.binding.is_bound || cached.binding.is_target;
m_slot_images[merged_id].binding.is_target |= cached.binding.is_target;
CopyImageMip(merged_id, cached_id, static_cast<uint32_t>(mip),
static_cast<uint32_t>(layer));
FreeImage(cached_id);
return {merged_id};
}
if (requested.data.address >= cached.info.data.address && safe_to_delete) {
FreeImage(cached_id);
}
return {merged_id};
}
@@ -919,7 +912,14 @@ ImageId TextureCache::ExpandImage(const ImageInfo& info, ImageId source_id) {
source.binding.needs_rebind = true;
}
InitializeImage(expanded_id);
CopyImage(expanded_id, source_id);
const int32_t mip = source.info.MipOf(info);
const int32_t layer = source.info.SliceOf(info, mip);
if (layer >= 0) {
CopyImageMip(expanded_id, source_id, static_cast<uint32_t>(mip),
static_cast<uint32_t>(layer));
} else {
CopyImage(expanded_id, source_id);
}
FreeImage(source_id);
return expanded_id;
}
@@ -1135,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);
}
}
@@ -1221,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;
}
}
@@ -1246,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 {};
{
@@ -1313,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;
}
@@ -1376,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) {
@@ -1406,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);
@@ -1430,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()) {
@@ -1527,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)
@@ -1557,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;
@@ -1872,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) {
@@ -1953,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;
@@ -94,9 +94,24 @@ struct ImageInfo {
bytes_per_block == other.bytes_per_block;
}
[[nodiscard]] int32_t MipOf(const ImageInfo& container) const noexcept {
if (!IsCompatible(container) || tile_mode != container.tile_mode || resources.levels != 1 ||
container.resources.layers == 0 ||
container.resources.levels > container.mip_layout.size()) {
if (container.resources.levels > container.mip_layout.size()) {
return -1;
}
for (uint32_t level = 0; level < container.resources.levels; level++) {
if (SliceOf(container, static_cast<int32_t>(level)) >= 0) {
return static_cast<int32_t>(level);
}
}
return -1;
}
[[nodiscard]] int32_t SliceOf(const ImageInfo& container, int32_t mip) const noexcept {
if (!IsCompatible(container) || tile_mode != container.tile_mode || type != container.type ||
IsVolume() || resources.levels != 1 || resources.layers == 0 ||
container.resources.layers == 0 || mip < 0 ||
static_cast<uint32_t>(mip) >= container.resources.levels ||
container.resources.levels > container.mip_layout.size() ||
!data.Valid() || !container.data.Valid() || data.address < container.data.address ||
data.End() > container.data.End()) {
return -1;
}
if (HasStencil() != container.HasStencil() ||
@@ -105,75 +120,36 @@ struct ImageInfo {
return -1;
}
int32_t mip = -1;
for (uint32_t level = 0; level < container.resources.levels; level++) {
const auto& layout = container.mip_layout[level];
if (layout.size == 0 || layout.size % container.resources.layers != 0 ||
container.data.address > UINT64_MAX - layout.offset) {
continue;
}
const auto mip_base = container.data.address + layout.offset;
const auto slice_size = layout.size / container.resources.layers;
if (slice_size == 0 || mip_base > UINT64_MAX - layout.size) {
continue;
}
const auto mip_end = mip_base + layout.size;
if (data.address >= mip_base && data.address < mip_end &&
(data.address - mip_base) % slice_size == 0) {
mip = static_cast<int32_t>(level);
break;
}
}
if (mip < 0) {
const auto level = static_cast<uint32_t>(mip);
const auto& layout = container.mip_layout[level];
if (extent.width != std::max(container.extent.width >> level, 1u) ||
extent.height != std::max(container.extent.height >> level, 1u) ||
extent.depth != container.extent.depth || mip_layout[0].pitch != layout.pitch ||
mip_layout[0].height != layout.height || layout.size == 0 ||
layout.size % container.resources.layers != 0 ||
container.data.size % container.resources.layers != 0 ||
data.size % resources.layers != 0) {
return -1;
}
const auto level = static_cast<uint32_t>(mip);
if (extent.width != std::max(container.extent.width >> level, 1u) ||
extent.height != std::max(container.extent.height >> level, 1u)) {
// PS5 block slices contain the entire reversed mip chain.
const auto slice_stride = container.data.size / container.resources.layers;
const auto child_stride = data.size / resources.layers;
if (child_stride != layout.size / container.resources.layers ||
layout.offset >= slice_stride || child_stride > slice_stride - layout.offset ||
(resources.layers > 1 && child_stride != slice_stride)) {
return -1;
}
const auto mip_depth = std::max(container.extent.depth >> level, 1u);
if (container.type == Prospero::ImageType::kColor3D &&
type == Prospero::ImageType::kColor2D) {
if (resources.layers != mip_depth) {
return -1;
}
} else if (type != container.type) {
const auto address_delta = data.address - container.data.address;
if (address_delta < layout.offset || (address_delta - layout.offset) % slice_stride != 0) {
return -1;
}
return mip;
}
[[nodiscard]] int32_t SliceOf(const ImageInfo& container, int32_t mip) const noexcept {
if (!IsCompatible(container) || type != container.type || mip < 0 ||
static_cast<uint32_t>(mip) >= container.resources.levels ||
container.resources.levels > container.mip_layout.size() ||
container.resources.layers == 0 || data.size == 0) {
const auto layer = (address_delta - layout.offset) / slice_stride;
if (layer > INT32_MAX || layer >= container.resources.layers ||
resources.layers > container.resources.layers - layer) {
return -1;
}
const auto level = static_cast<uint32_t>(mip);
if (extent.width != std::max(container.extent.width >> level, 1u) ||
extent.height != std::max(container.extent.height >> level, 1u)) {
return -1;
}
const auto& layout = container.mip_layout[level];
if (layout.size == 0 || layout.size % container.resources.layers != 0 ||
container.data.address > UINT64_MAX - layout.offset) {
return -1;
}
const auto slice_size = layout.size / container.resources.layers;
if (slice_size == 0 || data.size % slice_size != 0) {
return -1;
}
const auto mip_base = container.data.address + layout.offset;
if (data.address < mip_base) {
return -1;
}
const auto address_delta = data.address - mip_base;
if (address_delta % data.size != 0 || address_delta / data.size > INT32_MAX) {
return -1;
}
return static_cast<int32_t>(address_delta / data.size);
return static_cast<int32_t>(layer);
}
};
@@ -183,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 = {};
@@ -191,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"
@@ -93,22 +94,15 @@ static const char* ShaderStageResourceName(ShaderType stage) {
switch (stage) {
case ShaderType::Vertex: return "Vertex";
case ShaderType::Mesh: return "Mesh";
case ShaderType::Local: return "Local";
case ShaderType::TessellationControl: return "Hull";
case ShaderType::TessellationEvaluation: return "Domain";
case ShaderType::Pixel: return "Pixel";
case ShaderType::Compute: return "Compute";
default: return "Unknown";
}
}
static vk::ShaderStageFlags NativeShaderStage(ShaderType stage) {
switch (stage) {
case ShaderType::Vertex: return vk::ShaderStageFlagBits::eVertex;
case ShaderType::Mesh: return vk::ShaderStageFlagBits::eMeshEXT;
case ShaderType::Pixel: return vk::ShaderStageFlagBits::eFragment;
case ShaderType::Compute: return vk::ShaderStageFlagBits::eCompute;
default: EXIT("unknown native shader stage\n");
}
}
static Prospero::ImageType TextureType(const ShaderTextureResource& descriptor) {
const auto type = descriptor.Type();
return type == Prospero::ImageType::kCube ? Prospero::ImageType::kColor2DArray : type;
@@ -154,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";
@@ -299,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();
}
@@ -463,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
@@ -513,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);
@@ -529,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) ||
@@ -565,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);
@@ -584,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);
@@ -597,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);
@@ -635,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};
@@ -857,32 +879,39 @@ void RenderExecutor::RebindImages(PreparedBindings& prepared) {
}
}
RenderExecutor::GraphicsBindings
RenderExecutor::PrepareGraphicsBindings(const ShaderStageRuntime& vertex,
const ShaderStageRuntime& pixel, bool pixel_active) {
GraphicsBindings bindings {
.vertex = PrepareBindings(vertex),
};
if (pixel_active) {
bindings.pixel.emplace(PrepareBindings(pixel));
void RenderExecutor::PrepareGraphicsBindings(std::span<PreparedBindings* const> stages,
std::span<RenderColorInfo> colors) {
bool uses_dma = false;
for (auto* stage: stages) {
FindBuffers(*stage);
uses_dma |= stage->runtime->program->info.uses_dma;
}
FindBuffers(bindings.vertex);
if (bindings.pixel) {
FindBuffers(*bindings.pixel);
}
if (bindings.vertex.runtime->program->info.uses_dma ||
(bindings.pixel && bindings.pixel->runtime->program->info.uses_dma)) {
if (uses_dma) {
m_context.PrepareBda();
}
RebindBuffers(bindings.vertex);
if (bindings.pixel) {
RebindBuffers(*bindings.pixel);
for (auto* stage: stages) {
RebindImages(*stage);
}
RebindImages(bindings.vertex);
if (bindings.pixel) {
RebindImages(*bindings.pixel);
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);
}
return bindings;
}
void RenderExecutor::CommitBindings(CommandBuffer& buffer,
@@ -895,9 +924,10 @@ void RenderExecutor::CommitBindings(CommandBuffer& buffer,
size_t write_count = 0;
ShaderRecompiler::IR::PushData push_data;
bool has_push_data = false;
constexpr auto GraphicsStages = vk::ShaderStageFlagBits::eVertex |
vk::ShaderStageFlagBits::eMeshEXT |
vk::ShaderStageFlagBits::eFragment;
constexpr auto GraphicsStages =
vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eMeshEXT |
vk::ShaderStageFlagBits::eTessellationControl |
vk::ShaderStageFlagBits::eTessellationEvaluation | vk::ShaderStageFlagBits::eFragment;
vk::ShaderStageFlags push_stages = pipeline_bind_point == vk::PipelineBindPoint::eGraphics
? vk::ShaderStageFlagBits::eFragment
: vk::ShaderStageFlags {};
@@ -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) {
@@ -236,6 +231,9 @@ struct PipelineCache::ProgramCache {
switch (options.stage) {
case ShaderType::Vertex: stage_name = "vs"; break;
case ShaderType::Mesh: stage_name = "ms"; break;
case ShaderType::Local: stage_name = "ls"; break;
case ShaderType::TessellationControl: stage_name = "hs"; break;
case ShaderType::TessellationEvaluation: stage_name = "ds"; break;
case ShaderType::Pixel: stage_name = "ps"; break;
case ShaderType::Compute: stage_name = "cs"; break;
default: EXIT("invalid pipeline shader stage\n");
@@ -250,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,
@@ -273,7 +266,7 @@ struct PipelineCache::ProgramCache {
uint32_t& push_data_cursor) {
ShaderType stage;
if constexpr (std::is_same_v<InputInfo, ShaderVertexInputInfo>) {
stage = input_info.mesh.threads_num[0] != 0 ? ShaderType::Mesh : ShaderType::Vertex;
stage = input_info.logical_stage;
} else if constexpr (std::is_same_v<InputInfo, ShaderPixelInputInfo>) {
stage = ShaderType::Pixel;
} else {
@@ -325,6 +318,9 @@ struct PipelineCache::ProgramCache {
switch (stage) {
case ShaderType::Vertex: label = "ShaderRecompiler VS"; break;
case ShaderType::Mesh: label = "ShaderRecompiler MS"; break;
case ShaderType::Local: label = "ShaderRecompiler LS"; break;
case ShaderType::TessellationControl: label = "ShaderRecompiler HS"; break;
case ShaderType::TessellationEvaluation: label = "ShaderRecompiler DS"; break;
case ShaderType::Pixel: label = "ShaderRecompiler PS"; break;
case ShaderType::Compute: label = "ShaderRecompiler CS"; break;
default: EXIT("invalid pipeline shader stage\n");
@@ -341,11 +337,11 @@ struct PipelineCache::ProgramCache {
if constexpr (std::is_same_v<InputInfo, ShaderVertexInputInfo>) {
options.user_data_base = 8;
if (stage == ShaderType::Mesh) {
if (stage == ShaderType::Mesh || stage == ShaderType::TessellationControl) {
options.user_data_base = 0;
options.wave_size = input_info.mesh.wave_size;
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);
@@ -361,16 +357,19 @@ struct PipelineCache::ProgramCache {
input_info.stage = {.program = &permutation.program, .resources = std::move(resources)};
permutation.program.bindings.AdvancePushData(push_data_cursor);
std::array<size_t, static_cast<size_t>(ShaderType::Mesh) + 1> counts {};
std::array<size_t, static_cast<size_t>(ShaderType::TessellationEvaluation) + 1> counts {};
for (const auto& [key, source]: programs) {
counts[static_cast<size_t>(key.stage)] += source.permutations.size();
}
// Guest geometry shaders are compiled through the host mesh stage.
std::printf("Shaders: VS %zu | PS %zu | CS %zu | GS %zu\n",
std::printf("Shaders: VS %zu | PS %zu | CS %zu | GS %zu | LS %zu | HS %zu | TES %zu\n",
counts[static_cast<size_t>(ShaderType::Vertex)],
counts[static_cast<size_t>(ShaderType::Pixel)],
counts[static_cast<size_t>(ShaderType::Compute)],
counts[static_cast<size_t>(ShaderType::Mesh)]);
counts[static_cast<size_t>(ShaderType::Mesh)],
counts[static_cast<size_t>(ShaderType::Local)],
counts[static_cast<size_t>(ShaderType::TessellationControl)],
counts[static_cast<size_t>(ShaderType::TessellationEvaluation)]);
return permutation.handle;
}
@@ -564,12 +563,18 @@ PipelineCache::GraphicsPrograms PipelineCache::GetGraphicsPrograms(
const HW::VertexShaderInfo& vertex_regs, const HW::PixelShaderInfo& pixel_regs,
const HW::ShaderRegisters& sh, const HW::Context& context, const HW::UserConfig& user_config,
std::span<const Prospero::ColorComponentMapping, 8> target_export_mapping, bool pixel_active,
ShaderVertexInputInfo& vertex_info, ShaderPixelInputInfo& pixel_info) {
const auto vertex_params = PrepareProgram(vertex_regs, context, user_config, vertex_info);
const bool mesh_active = vertex_info.mesh.threads_num[0] != 0;
std::array<ShaderVertexInputInfo, 3>& vertex_info, ShaderPixelInputInfo& pixel_info) {
const bool tess_active = user_config.GetPrimType() == Prospero::PrimitiveType::kPatch;
std::array<ShaderParams, 3> vertex_params;
if (tess_active) {
vertex_params = PrepareTessellationPrograms(vertex_regs, context, vertex_info);
} else {
vertex_params[0] = PrepareProgram(vertex_regs, context, user_config, vertex_info[0]);
}
const bool mesh_active = vertex_info[0].logical_stage == ShaderType::Mesh;
if (mesh_active) {
EXIT_NOT_IMPLEMENTED(!m_graphics.mesh_shader_enabled);
auto& mesh = vertex_info.mesh;
auto& mesh = vertex_info[0].mesh;
mesh.host_subgroup_size = m_graphics.subgroup_size;
const auto& limits = m_graphics.mesh_shader_properties;
const auto logical_threads =
@@ -592,7 +597,7 @@ PipelineCache::GraphicsPrograms PipelineCache::GetGraphicsPrograms(
if (context.GetClipControl().clip_disable) {
const auto& viewport = context.GetScreenViewport().viewports[0];
const auto& limits = m_graphics.GetPhysicalDeviceProperties().limits;
auto& clip = vertex_info.clip_space;
auto& clip = vertex_info[tess_active ? 2u : 0u].clip_space;
clip.scale[0] = viewport.xscale;
clip.scale[1] = viewport.yscale;
clip.offset[0] = viewport.xoffset;
@@ -610,7 +615,9 @@ PipelineCache::GraphicsPrograms PipelineCache::GetGraphicsPrograms(
if (pixel_active) {
result.pixel = m_program_cache->Get(pixel_params, pixel_info, push_data_cursor);
}
result.vertex = m_program_cache->Get(vertex_params, vertex_info, push_data_cursor);
for (uint32_t i = 0; i < (tess_active ? 3u : 1u); i++) {
result.vertex[i] = m_program_cache->Get(vertex_params[i], vertex_info[i], push_data_cursor);
}
return result;
}
@@ -630,10 +637,12 @@ bool PipelineStaticParameters::operator==(const PipelineStaticParameters& other)
PipelineCache::Pipeline& PipelineCache::GetGraphicsPipeline(
std::span<const RenderColorInfo> colors, const RenderDepthInfo& depth,
const ShaderVertexInputInfo& vs_input_info, CommandBuffer& command,
std::span<const ShaderVertexInputInfo> vertex_info, CommandBuffer& command,
const ShaderPixelInputInfo* ps_input_info, vk::PrimitiveTopology topology,
bool primitive_restart_enable, const ShaderProgram& vertex_program,
const ShaderProgram& pixel_program) {
bool primitive_restart_enable, const GraphicsPrograms& programs) {
const auto& vs_input_info = vertex_info.front();
const auto& vertex_program = programs.vertex[0];
const auto& pixel_program = programs.pixel;
KYTY_PROFILER_BLOCK("PipelineCache::CreatePipeline(Gfx)", profiler::colors::DeepOrangeA200);
EXIT_IF(colors.size() > RENDER_COLOR_ATTACHMENTS_MAX);
@@ -651,17 +660,22 @@ PipelineCache::Pipeline& PipelineCache::GetGraphicsPipeline(
const auto ps_id = ps_active ? pixel_program.id : 0;
GraphicsPipelineKey key {};
key.vs_shader_id = vs_id;
for (uint32_t i = 0; i < programs.vertex.size(); i++) {
key.vertex_shader_ids[i] = programs.vertex[i].id;
}
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) {
@@ -670,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);
@@ -726,10 +740,8 @@ 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 = topology == vk::PrimitiveTopology::ePatchList;
const bool rect_list =
command.GetUserConfig().GetPrimType() == Prospero::PrimitiveType::kRectList;
static_params.cull_back = !rect_list && mc.cull_back;
static_params.cull_front = !rect_list && mc.cull_front;
static_params.face = mc.face;
@@ -780,9 +792,8 @@ PipelineCache::Pipeline& PipelineCache::GetGraphicsPipeline(
auto cached = std::make_unique<Pipeline>();
LogPipelineTrace("CreatePipelineInternal begin", vs_id, ps_id);
CreatePipelineInternal(m_graphics, *cached, rendering, key.vertex_input, vs_input_info,
vertex_program, ps_input_info, pixel_program, static_params,
m_driver_cache);
CreatePipelineInternal(m_graphics, *cached, rendering, key.vertex_input, vertex_info,
ps_input_info, programs, static_params, m_driver_cache);
LogPipelineTrace("CreatePipelineInternal done", vs_id, ps_id);
EXIT_NOT_IMPLEMENTED(cached->pipeline == nullptr);
@@ -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 {};
@@ -121,8 +118,10 @@ public:
};
struct GraphicsPrograms {
ShaderProgram vertex;
std::array<ShaderProgram, 3> vertex;
ShaderProgram pixel;
[[nodiscard]] uint32_t VertexStageCount() const { return vertex[1] ? 3u : 1u; }
};
GraphicsPrograms
@@ -130,18 +129,18 @@ public:
const HW::PixelShaderInfo& pixel_regs, const HW::ShaderRegisters& sh,
const HW::Context& context, const HW::UserConfig& user_config,
std::span<const Prospero::ColorComponentMapping, 8> target_export_mapping,
bool pixel_active, ShaderVertexInputInfo& vertex_info,
bool pixel_active, std::array<ShaderVertexInputInfo, 3>& vertex_info,
ShaderPixelInputInfo& pixel_info);
ShaderProgram GetComputeProgram(const HW::ComputeShaderInfo& regs,
const HW::ShaderRegisters& sh,
ShaderComputeInputInfo& input_info);
Pipeline&
GetGraphicsPipeline(std::span<const RenderColorInfo> colors, const RenderDepthInfo& depth,
const ShaderVertexInputInfo& vs_input_info, CommandBuffer& command,
const ShaderPixelInputInfo* ps_input_info,
vk::PrimitiveTopology topology, bool primitive_restart_enable,
const ShaderProgram& vertex_program, const ShaderProgram& pixel_program);
Pipeline& GetGraphicsPipeline(std::span<const RenderColorInfo> colors,
const RenderDepthInfo& depth,
std::span<const ShaderVertexInputInfo> vertex_info,
CommandBuffer& command, const ShaderPixelInputInfo* ps_input_info,
vk::PrimitiveTopology topology, bool primitive_restart_enable,
const GraphicsPrograms& programs);
Pipeline& GetComputePipeline(const ShaderComputeInputInfo& input_info,
const ShaderProgram& compute_program);
@@ -150,13 +149,13 @@ private:
struct GraphicsPipelineKey {
PipelineRenderingState rendering;
uint64_t vs_shader_id = 0;
std::array<uint64_t, 3> vertex_shader_ids {};
uint64_t ps_shader_id = 0;
PipelineVertexInputState vertex_input;
PipelineStaticParameters static_params;
bool operator==(const GraphicsPipelineKey& other) const {
return rendering == other.rendering && vs_shader_id == other.vs_shader_id &&
return rendering == other.rendering && vertex_shader_ids == other.vertex_shader_ids &&
ps_shader_id == other.ps_shader_id && vertex_input == other.vertex_input &&
static_params == other.static_params;
}
@@ -189,7 +188,9 @@ private:
std::size_t operator()(const GraphicsPipelineKey& key) const {
std::size_t hash = 0;
PipelineKeyHash::MixRendering(hash, key.rendering);
PipelineKeyHash::Mix(hash, key.vs_shader_id);
for (const auto id: key.vertex_shader_ids) {
PipelineKeyHash::Mix(hash, id);
}
PipelineKeyHash::Mix(hash, key.ps_shader_id);
PipelineKeyHash::Mix(hash, key.vertex_input.binding_count);
for (uint32_t i = 0; i < key.vertex_input.binding_count; i++) {
@@ -219,12 +220,14 @@ private:
};
void LogPipelineTrace(const char* phase, uint64_t vertex_program_id, uint64_t pixel_program_id);
void CreatePipelineInternal(
GraphicContext& graphics, PipelineCache::Pipeline& pipeline,
const PipelineRenderingState& rendering, const PipelineVertexInputState& vertex_input,
const ShaderVertexInputInfo& vs_input_info, const ShaderProgram& vertex_program,
const ShaderPixelInputInfo* ps_input_info, const ShaderProgram& pixel_program,
const PipelineStaticParameters& static_params, vk::PipelineCache driver_cache);
void CreatePipelineInternal(GraphicContext& graphics, PipelineCache::Pipeline& pipeline,
const PipelineRenderingState& rendering,
const PipelineVertexInputState& vertex_input,
std::span<const ShaderVertexInputInfo> vertex_info,
const ShaderPixelInputInfo* ps_input_info,
const PipelineCache::GraphicsPrograms& programs,
const PipelineStaticParameters& static_params,
vk::PipelineCache driver_cache);
void CreatePipelineInternal(GraphicContext& graphics, PipelineCache::Pipeline& pipeline,
const ShaderComputeInputInfo& input_info,
vk::ShaderModule compute_module, vk::PipelineCache driver_cache);
@@ -8,6 +8,20 @@
namespace Libs::Graphics {
vk::ShaderStageFlagBits NativeShaderStage(ShaderType stage) {
switch (stage) {
case ShaderType::Local:
case ShaderType::Vertex: return vk::ShaderStageFlagBits::eVertex;
case ShaderType::Mesh: return vk::ShaderStageFlagBits::eMeshEXT;
case ShaderType::TessellationControl: return vk::ShaderStageFlagBits::eTessellationControl;
case ShaderType::TessellationEvaluation:
return vk::ShaderStageFlagBits::eTessellationEvaluation;
case ShaderType::Pixel: return vk::ShaderStageFlagBits::eFragment;
case ShaderType::Compute: return vk::ShaderStageFlagBits::eCompute;
default: EXIT("unknown native shader stage\n");
}
}
vk::PipelineStageFlags ShaderPipelineStages(vk::ShaderStageFlags stages) {
vk::PipelineStageFlags result = {};
if (stages & vk::ShaderStageFlagBits::eVertex) {
@@ -16,6 +30,12 @@ vk::PipelineStageFlags ShaderPipelineStages(vk::ShaderStageFlags stages) {
if (stages & vk::ShaderStageFlagBits::eMeshEXT) {
result |= vk::PipelineStageFlagBits::eMeshShaderEXT;
}
if (stages & vk::ShaderStageFlagBits::eTessellationControl) {
result |= vk::PipelineStageFlagBits::eTessellationControlShader;
}
if (stages & vk::ShaderStageFlagBits::eTessellationEvaluation) {
result |= vk::PipelineStageFlagBits::eTessellationEvaluationShader;
}
if (stages & vk::ShaderStageFlagBits::eFragment) {
result |= vk::PipelineStageFlagBits::eFragmentShader;
}
@@ -8,6 +8,7 @@ namespace Libs::Graphics {
struct ShaderStageRuntime;
vk::ShaderStageFlagBits NativeShaderStage(ShaderType stage);
vk::PipelineStageFlags ShaderPipelineStages(vk::ShaderStageFlags stages);
VulkanMemoryBarrier MakeShaderAccessDependency();
VulkanMemoryBarrier MakeShaderWriteHazardDependency();
@@ -8,6 +8,7 @@
#include "graphics/host_gpu/renderer/debug.h"
#include "graphics/host_gpu/renderer/pipeline/descriptors.h"
#include "graphics/host_gpu/renderer/pipeline/pipelineCache.h"
#include "graphics/host_gpu/renderer/pipeline/shaderResourceBarrier.h"
#include "graphics/host_gpu/renderer/render.h"
#include "graphics/host_gpu/renderer/renderContext.h"
#include "graphics/host_gpu/renderer/renderTarget.h"
@@ -205,12 +206,18 @@ static void CreateDescriptorLayout(GraphicContext& graphics, PipelineCache::Pipe
}
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
void CreatePipelineInternal(
GraphicContext& graphics, PipelineCache::Pipeline& pipeline,
const PipelineRenderingState& rendering, const PipelineVertexInputState& vertex_input,
const ShaderVertexInputInfo& vs_input_info, const ShaderProgram& vertex_program,
const ShaderPixelInputInfo* ps_input_info, const ShaderProgram& pixel_program,
const PipelineStaticParameters& static_params, vk::PipelineCache driver_cache) {
void CreatePipelineInternal(GraphicContext& graphics, PipelineCache::Pipeline& pipeline,
const PipelineRenderingState& rendering,
const PipelineVertexInputState& vertex_input,
std::span<const ShaderVertexInputInfo> vertex_info,
const ShaderPixelInputInfo* ps_input_info,
const PipelineCache::GraphicsPrograms& programs,
const PipelineStaticParameters& static_params,
vk::PipelineCache driver_cache) {
const auto& vs_input_info = vertex_info.front();
const auto& vertex_program = programs.vertex[0];
const auto& pixel_program = programs.pixel;
const bool tessellation = vertex_info.size() == 3;
const bool ps_active = ps_input_info != nullptr;
EXIT_IF(!vertex_program || (ps_active && !pixel_program));
const bool with_depth = rendering.depth_format != vk::Format::eUndefined ||
@@ -218,46 +225,39 @@ void CreatePipelineInternal(
EXIT_IF(!vs_input_info.stage);
const bool mesh = vs_input_info.stage.program->stage == ShaderType::Mesh;
EXIT_NOT_IMPLEMENTED(mesh && !graphics.mesh_shader_enabled);
const auto vertex_stage =
mesh ? vk::ShaderStageFlagBits::eMeshEXT : vk::ShaderStageFlagBits::eVertex;
const bool rect_list = !mesh && static_params.topology == vk::PrimitiveTopology::ePatchList;
const bool rect_list =
!mesh && !tessellation && static_params.topology == vk::PrimitiveTopology::ePatchList;
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(
rect_list && (tess_control_shader_module == nullptr || tess_eval_shader_module == nullptr));
vk::PipelineShaderStageCreateInfo shader_stages[4] = {
{.stage = vertex_stage, .module = vertex_program.module, .pName = "main"}};
uint32_t shader_stage_count = 1;
vk::PipelineShaderStageCreateInfo shader_stages[4] {};
uint32_t shader_stage_count = 0;
for (uint32_t i = 0; i < vertex_info.size(); i++) {
shader_stages[shader_stage_count++] = {.stage =
NativeShaderStage(vertex_info[i].logical_stage),
.module = programs.vertex[i].module,
.pName = "main"};
}
if (rect_list) {
shader_stages[shader_stage_count++] = {.stage =
vk::ShaderStageFlagBits::eTessellationControl,
@@ -432,15 +432,19 @@ void CreatePipelineInternal(
color_blending.pAttachments = color_blend_attachment;
std::vector<vk::DescriptorSetLayoutBinding> descriptor_bindings;
AddLayoutBindings(descriptor_bindings, *vs_input_info.stage.program, vertex_stage);
vk::ShaderStageFlags graphics_stages = vk::ShaderStageFlagBits::eFragment;
for (const auto& stage: vertex_info) {
const auto native_stage = NativeShaderStage(stage.logical_stage);
AddLayoutBindings(descriptor_bindings, *stage.stage.program, native_stage);
graphics_stages |= native_stage;
}
if (ps_active) {
EXIT_IF(!ps_input_info->stage);
AddLayoutBindings(descriptor_bindings, *ps_input_info->stage.program,
vk::ShaderStageFlagBits::eFragment);
}
CreateDescriptorLayout(graphics, pipeline, descriptor_bindings);
const auto GraphicsStages = vertex_stage | vk::ShaderStageFlagBits::eFragment;
const vk::PushConstantRange push_constants {GraphicsStages, 0,
const vk::PushConstantRange push_constants {graphics_stages, 0,
ShaderRecompiler::IR::NativePushConstantSize};
vk::PipelineLayoutCreateInfo pipeline_layout_info {};
@@ -456,8 +460,8 @@ void CreatePipelineInternal(
" 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));
@@ -473,15 +477,6 @@ void CreatePipelineInternal(
#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;
@@ -494,6 +489,8 @@ void CreatePipelineInternal(
vk::DynamicState::eDepthCompareOp,
vk::DynamicState::eDepthBiasEnable,
vk::DynamicState::eDepthBias,
vk::DynamicState::eStencilTestEnable,
vk::DynamicState::eStencilOp,
vk::DynamicState::eStencilCompareMask,
vk::DynamicState::eStencilReference,
vk::DynamicState::eStencilWriteMask,
@@ -524,8 +521,9 @@ void CreatePipelineInternal(
pipeline_info.pVertexInputState = mesh ? nullptr : &vertex_input_info;
pipeline_info.pInputAssemblyState = mesh ? nullptr : &input_assembly;
vk::PipelineTessellationStateCreateInfo tessellation_state {};
tessellation_state.patchControlPoints = 3;
pipeline_info.pTessellationState = (rect_list ? &tessellation_state : nullptr);
tessellation_state.patchControlPoints =
tessellation ? vs_input_info.tess.input_control_points : 3u;
pipeline_info.pTessellationState = (rect_list || tessellation) ? &tessellation_state : nullptr;
pipeline_info.pViewportState = &viewport_state;
pipeline_info.pRasterizationState = &rasterizer;
pipeline_info.pMultisampleState = &multisampling;
+3 -4
View File
@@ -170,15 +170,14 @@ private:
void DrawAuto(uint64_t submit_id, CommandBuffer& buffer, const DrawAutoArgs& args);
struct GraphicsBindings {
PreparedBindings vertex;
std::array<PreparedBindings, 3> vertex;
std::optional<PreparedBindings> pixel;
};
[[nodiscard]] TextureBinding ResolveTexture(const ShaderRecompiler::IR::ImageResource& resource,
const ShaderRecompiler::IR::DescriptorValue& value);
[[nodiscard]] GraphicsBindings PrepareGraphicsBindings(const ShaderStageRuntime& vertex,
const ShaderStageRuntime& pixel,
bool pixel_active);
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;
+129 -125
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
}
@@ -453,7 +435,7 @@ struct DrawRenderState {
RenderColorInfo color_info[RENDER_COLOR_ATTACHMENTS_MAX] = {};
uint32_t color_count = 0;
bool ps_active = true;
ShaderVertexInputInfo vs_input_info;
std::array<ShaderVertexInputInfo, 3> vertex_info;
ShaderPixelInputInfo ps_input_info;
PipelineCache::GraphicsPrograms programs;
};
@@ -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,
@@ -831,6 +812,7 @@ static bool GetDrawTopology(const HW::UserConfig& ucfg, bool auto_draw,
case Prospero::PrimitiveType::kTriStrip:
topology = vk::PrimitiveTopology::eTriangleStrip;
break;
case Prospero::PrimitiveType::kPatch:
case Prospero::PrimitiveType::kRectList:
topology = vk::PrimitiveTopology::ePatchList;
break;
@@ -895,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();
@@ -944,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()) {
@@ -953,7 +905,46 @@ static void RefreshShaders(CommandBuffer& buffer, const DrawCallInfo& draw,
}
state.programs = pipeline_cache.GetGraphicsPrograms(
vertex_shader_info, pixel_shader_info, shader_regs, ctx, buffer.GetUserConfig(),
target_export_mapping, state.ps_active, state.vs_input_info, state.ps_input_info);
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,
@@ -988,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);
}
}
@@ -1016,7 +1008,7 @@ static void LogDrawStateIfNeeded(const CommandBuffer& buffer, const DrawCallInfo
LogDrawTargetState(draw.Name(), state.color_info[0], state.depth_info, buffer,
state.ps_input_info, draw.index_count, 0);
}
LogDrawInputState(buffer, state.color_info[0], state.vs_input_info, index_type_and_size,
LogDrawInputState(buffer, state.color_info[0], state.vertex_info[0], index_type_and_size,
draw.index_count, index_addr);
}
@@ -1031,6 +1023,7 @@ static void EmitDrawPrimitives(const HW::UserConfig& ucfg, vk::CommandBuffer vk_
case Prospero::PrimitiveType::kTriFan:
case Prospero::PrimitiveType::kTriStrip:
case Prospero::PrimitiveType::kRectList:
case Prospero::PrimitiveType::kPatch:
if (draw.IsIndexed()) {
vk_buffer.drawIndexed(draw.index_count, draw.instance_count, 0, emit.vertex_offset,
emit.first_instance);
@@ -1069,11 +1062,19 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, CommandBuffer& buff
const DrawIndexBufferSource& index_source,
bool primitive_restart_enable) {
auto& ucfg = buffer.GetUserConfig();
const bool mesh_active = state.vs_input_info.stage.program->stage == ShaderType::Mesh;
const auto vertex_stages =
std::span {state.vertex_info.data(), state.programs.VertexStageCount()};
const bool mesh_active = state.vertex_info[0].stage.program->stage == ShaderType::Mesh;
uint32_t mesh_groups = 0;
if (mesh_active) {
const auto& mesh = state.vs_input_info.mesh;
if (primitive_restart_enable || mesh.primitives_per_group == 0) {
const auto& mesh = state.vertex_info[0].mesh;
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);
}
@@ -1100,13 +1101,24 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, CommandBuffer& buff
index_source.guest_element_size);
}
LogDrawPhase(draw.Name(), "PrepareBindings");
auto bindings = PrepareGraphicsBindings(state.vs_input_info.stage, state.ps_input_info.stage,
state.ps_active);
GraphicsBindings bindings;
std::array<PreparedBindings*, 4> descriptor_stages {};
uint32_t stage_count = 0;
for (uint32_t i = 0; i < vertex_stages.size(); i++) {
bindings.vertex[i] = PrepareBindings(state.vertex_info[i].stage);
descriptor_stages[stage_count++] = &bindings.vertex[i];
}
if (state.ps_active) {
bindings.pixel.emplace(PrepareBindings(state.ps_input_info.stage));
descriptor_stages[stage_count++] = &*bindings.pixel;
}
const auto stages = std::span {descriptor_stages.data(), stage_count};
PrepareGraphicsBindings(stages, std::span {state.color_info, state.color_count});
PreparedVertexBuffers vertex_bindings;
PreparedIndexBuffer index_binding;
if (!mesh_active) {
LogDrawPhase(draw.Name(), "PrepareVertexBuffers");
vertex_bindings = AcquireVertexBuffers(buffer, state.vs_input_info);
vertex_bindings = AcquireVertexBuffers(buffer, state.vertex_info[0]);
index_binding = PrepareIndexBuffer(buffer, index_source);
}
const auto rendering =
@@ -1117,9 +1129,9 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, CommandBuffer& buff
LogDrawPhase(draw.Name(), "CreatePipeline");
}
auto& pipeline = m_context.GetPipelineCache().GetGraphicsPipeline(
std::span {state.color_info, state.color_count}, state.depth_info, state.vs_input_info, buffer,
std::span {state.color_info, state.color_count}, state.depth_info, vertex_stages, buffer,
state.ps_active ? &state.ps_input_info : nullptr, topology, primitive_restart_enable,
state.programs.vertex, state.programs.pixel);
state.programs);
// Resource preparation above may synchronously finish and restart the scheduler. From this
// point onward, every operation targets the current command buffer and cannot touch guest
@@ -1132,13 +1144,7 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, CommandBuffer& buff
if (bindings.pixel && !draw.IsIndexed()) {
SetDrawDebugPhase(buffer, submit_id, draw, 0x300u);
}
std::array<PreparedBindings*, 2> descriptor_stages {&bindings.vertex, nullptr};
const size_t descriptor_stage_count = bindings.pixel.has_value() ? 2u : 1u;
if (bindings.pixel) {
descriptor_stages[1] = &*bindings.pixel;
}
CommitBindings(buffer, vk::PipelineBindPoint::eGraphics, pipeline,
std::span {descriptor_stages.data(), descriptor_stage_count});
CommitBindings(buffer, vk::PipelineBindPoint::eGraphics, pipeline, stages);
if (mesh_active) {
const uint32_t draw_data[] {
draw.index_count,
@@ -1155,8 +1161,7 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, CommandBuffer& buff
CommitIndexBuffer(vk_buffer, index_binding);
}
SetGraphicsDynamicParams(buffer, vk_buffer, state.vs_input_info, 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 ==
@@ -1177,16 +1182,17 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, CommandBuffer& buff
if (mesh_active) {
vk_buffer.drawMeshTasksEXT(mesh_groups, draw.instance_count, 1);
} else {
EmitDrawPrimitives(ucfg, vk_buffer, state.vs_input_info, draw, emit);
EmitDrawPrimitives(ucfg, vk_buffer, state.vertex_info[0], draw, emit);
}
if (!draw.IsIndexed()) {
SetDrawDebugPhase(buffer, submit_id, draw, 0x600u);
}
vk::PipelineStageFlags shader_write_stages = {};
if (HasShaderBufferWrites(state.vs_input_info.stage)) {
shader_write_stages |= mesh_active ? vk::PipelineStageFlagBits::eMeshShaderEXT
: vk::PipelineStageFlagBits::eVertexShader;
for (const auto& stage: vertex_stages) {
if (HasShaderBufferWrites(stage.stage)) {
shader_write_stages |= ShaderPipelineStages(NativeShaderStage(stage.logical_stage));
}
}
if (state.ps_active && HasShaderBufferWrites(state.ps_input_info.stage)) {
shader_write_stages |= vk::PipelineStageFlagBits::eFragmentShader;
@@ -1220,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;
}
@@ -1294,15 +1301,13 @@ 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);
const bool indirect = args.offset_source == DrawOffsetSource::IndirectArgs;
const auto [vertex_offset, instance_offset] =
indirect ? std::pair<int32_t, uint32_t> {0, args.first_instance}
: ResolveDrawOffsets(ucfg.GetIndexOffset(), state.vs_input_info);
: ResolveDrawOffsets(ucfg.GetIndexOffset(), state.vertex_info[0]);
DrawEmitInfo emit {};
emit.vertex_offset = vertex_offset + args.base_vertex;
@@ -1332,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;
}
@@ -1361,22 +1367,20 @@ 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};
vk::PrimitiveTopology topology = vk::PrimitiveTopology::ePointList;
if (!GetDrawTopology(ucfg, true, topology)) {
ResetBindings();
return;
}
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);
const bool rect_list = topology == vk::PrimitiveTopology::ePatchList;
if (rect_list && state.vs_input_info.buffers_num == 0 &&
state.vs_input_info.stage.program->param_export_mask == 0 &&
const bool rect_list = ucfg.GetPrimType() == Prospero::PrimitiveType::kRectList;
if (rect_list && state.vertex_info[0].buffers_num == 0 &&
state.vertex_info[0].stage.program->param_export_mask == 0 &&
state.ps_input_info.input_num != 0) {
if (graphics_debug_dump_enabled()) {
LOGF("DrawIndexAuto: skipping rect-list draw with no VS param exports and PS inputs: "
@@ -1393,7 +1397,7 @@ void RenderExecutor::DrawAuto(uint64_t submit_id, CommandBuffer& buffer, const D
const bool indirect = args.offset_source == DrawOffsetSource::IndirectArgs;
const auto [vertex_offset, instance_offset] =
indirect ? std::pair<int32_t, uint32_t> {0, args.first_instance}
: ResolveDrawOffsets(ucfg.GetIndexOffset(), state.vs_input_info);
: ResolveDrawOffsets(ucfg.GetIndexOffset(), state.vertex_info[0]);
DrawEmitInfo emit {};
emit.first_vertex = static_cast<uint32_t>(vertex_offset + static_cast<int32_t>(args.first_vertex));
emit.first_instance = instance_offset;
@@ -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,
+13 -18
View File
@@ -237,7 +237,9 @@ private:
struct VideoOutDriver::Impl {
public:
static constexpr int VIDEO_OUT_NUM_MAX = 4;
static constexpr std::array VIDEO_OUT_BUSES {
VIDEO_OUT_BUS_TYPE_MAIN, VIDEO_OUT_BUS_TYPE_OVERLAY, VIDEO_OUT_BUS_TYPE_SUB};
static constexpr int VIDEO_OUT_NUM_MAX = VIDEO_OUT_BUSES.size() + 1;
Impl(uint32_t width, uint32_t height, Graphics::Presenter& presenter)
: m_renderer(presenter.Renderer()), m_presenter(presenter), m_flip_queue(presenter) {
@@ -248,7 +250,7 @@ public:
~Impl();
KYTY_CLASS_NO_COPY(Impl);
int Open(int bus_type);
int Open(int bus_type, int index);
bool Close(int handle);
VideoOutConfig* Get(int handle);
VideoOutConfig* Get(int handle, uint64_t& generation);
@@ -610,12 +612,17 @@ void VideoOutDriver::Impl::Init(uint32_t width, uint32_t height) {
}
}
int VideoOutDriver::Impl::Open(int bus_type) {
int VideoOutDriver::Impl::Open(int bus_type, int index) {
const auto bus = std::find(VIDEO_OUT_BUSES.begin(), VIDEO_OUT_BUSES.end(), bus_type);
if (bus == VIDEO_OUT_BUSES.end()) {
return VIDEO_OUT_ERROR_INVALID_VALUE;
}
EXIT_NOT_IMPLEMENTED(index != 0);
Common::LockGuard lock(m_mutex);
const int handle = bus_type + 1;
const int handle = static_cast<int>(bus - VIDEO_OUT_BUSES.begin()) + 1;
if (m_video_out_ctx[handle].opened) {
return -1;
return VIDEO_OUT_ERROR_RESOURCE_BUSY;
}
auto& config = m_video_out_ctx[handle];
Common::LockGuard config_lock(config.mutex);
@@ -1169,21 +1176,9 @@ KYTY_SYSV_ABI int VideoOutOpen(int user_id, int bus_type, int index, const void*
PRINT_NAME();
EXIT_NOT_IMPLEMENTED(user_id != 255 && user_id != 0);
if (bus_type != VIDEO_OUT_BUS_TYPE_MAIN && bus_type != VIDEO_OUT_BUS_TYPE_OVERLAY &&
bus_type != VIDEO_OUT_BUS_TYPE_SUB) {
return VIDEO_OUT_ERROR_INVALID_VALUE;
}
EXIT_NOT_IMPLEMENTED(index != 0);
LOGF("\t param = 0x%016" PRIx64 "\n", reinterpret_cast<uint64_t>(param));
int handle = DriverState().Open(bus_type);
if (handle < 0) {
return VIDEO_OUT_ERROR_RESOURCE_BUSY;
}
return handle;
return DriverState().Open(bus_type, index);
}
KYTY_SYSV_ABI int VideoOutClose(int handle) {
@@ -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
@@ -1,4 +1,5 @@
#include "graphics/shader/recompiler/ShaderRecompiler.h"
#include "graphics/shader/recompiler/Tessellation.h"
#include "common/assert.h"
#include "common/logging/log.h"
@@ -47,6 +48,9 @@ const char* StageName(ShaderType stage) {
switch (stage) {
case ShaderType::Compute: return "CS";
case ShaderType::Vertex: return "VS";
case ShaderType::Local: return "LS";
case ShaderType::TessellationControl: return "HS";
case ShaderType::TessellationEvaluation: return "TES";
case ShaderType::Mesh: return "MS";
case ShaderType::Pixel: return "PS";
default: return "unknown";
@@ -83,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>;
@@ -211,6 +214,8 @@ EmbeddedFetchData DetectEmbeddedVertexFetch(const Decoder::Program& decoded
const ShaderVertexInputInfo* input_info,
uint32_t user_data_base, uint32_t user_data_count,
uint32_t wave_size) {
const uint32_t vertex_index_reg = input_info->logical_stage == ShaderType::Local ? 2u : 5u;
const uint32_t instance_index_reg = input_info->logical_stage == ShaderType::Local ? 5u : 8u;
EmbeddedFetchData data;
data.loads.reserve(input_info->resources_num);
int32_t vertex_offset_candidate = -1;
@@ -251,16 +256,17 @@ EmbeddedFetchData DetectEmbeddedVertexFetch(const Decoder::Program& decoded
// corresponding direct-draw offsets before fetching.
const bool vertex_index_accumulator =
IsDecodedVgpr(inst.dst) &&
(inst.dst.reg == 0 || (user_data_base == 8 && inst.dst.reg == 5));
(inst.dst.reg == 0 || (user_data_base == 8 && inst.dst.reg == vertex_index_reg));
const bool instance_index_accumulator =
IsDecodedVgpr(inst.dst) && (inst.dst.reg == (user_data_base == 8 ? 8u : 3u));
IsDecodedVgpr(inst.dst) &&
(inst.dst.reg == (user_data_base == 8 ? instance_index_reg : 3u));
uint32_t sad_zero = 0;
const bool index_offset_add =
(vertex_index_accumulator || instance_index_accumulator) &&
IsDecodedSgpr(inst.src0) &&
(vertex_index_accumulator || instance_index_accumulator) && IsDecodedSgpr(inst.src0) &&
((inst.opcode == Decoder::Opcode::V_ADD_I32 && IsDecodedVgpr(inst.src1) &&
inst.src1.reg == inst.dst.reg) ||
(user_data_base == 8 && (inst.dst.reg == 5 || inst.dst.reg == 8) &&
(user_data_base == 8 &&
(inst.dst.reg == vertex_index_reg || inst.dst.reg == instance_index_reg) &&
inst.opcode == Decoder::Opcode::V_SAD_U32 && IsDecodedVgpr(inst.src2) &&
inst.src2.reg == inst.dst.reg &&
TryDecodedOperandConstant(sgprs, inst.src1, sad_zero) && sad_zero == 0));
@@ -319,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);
}
@@ -330,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:
@@ -347,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));
@@ -365,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() &&
@@ -377,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));
@@ -391,8 +391,8 @@ EmbeddedFetchData DetectEmbeddedVertexFetch(const Decoder::Program& decoded
ClearEmbeddedFetchVectorLanes(&vector_lanes, inst.dst.reg);
}
if (IsDecodedVgpr(inst.dst) && inst.dst.reg < vgpr_is_index.size() &&
IsDecodedVgpr(inst.src0) && inst.src0.reg == 8 &&
IsDecodedVgpr(inst.src1) && inst.src1.reg == 5) {
IsDecodedVgpr(inst.src0) && inst.src0.reg == instance_index_reg &&
IsDecodedVgpr(inst.src1) && inst.src1.reg == vertex_index_reg) {
vgpr_is_index[inst.dst.reg] = true;
}
} else if (IsEmbeddedFetchAttribPropagationAlu(inst)) {
@@ -417,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);
}
@@ -440,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;
@@ -462,22 +460,9 @@ EmbeddedFetchData DetectEmbeddedVertexFetch(const Decoder::Program& decoded
Decoder::Program DecodeFusedProgram(std::span<const uint32_t> front, std::span<const uint32_t> back,
std::vector<uint32_t>& joined_code) {
EXIT_IF(back.empty());
Decoder::Program result;
uint32_t front_words = 0;
while (front_words < front.size()) {
auto& inst = result.instructions.emplace_back();
Decoder::DecodeInstruction(front, front_words, inst);
front_words += inst.word_count;
if (inst.opcode == Decoder::Opcode::S_SETPC_B64) {
EXIT_NOT_IMPLEMENTED(inst.src0.kind != Decoder::OperandKind::Sgpr ||
inst.src0.reg != 6u);
break;
}
EXIT_NOT_IMPLEMENTED(inst.opcode == Decoder::Opcode::S_ENDPGM);
}
EXIT_IF(result.instructions.empty() ||
result.instructions.back().opcode != Decoder::Opcode::S_SETPC_B64);
joined_code.assign(front.begin(), front.begin() + front_words);
auto result = Decoder::DecodeFrontProgram(front);
const auto front_words = static_cast<uint32_t>(result.code.size());
joined_code.assign(result.code.begin(), result.code.end());
joined_code.insert(joined_code.end(), back.begin(), back.end());
// The merged-stage ABI passes the back shader in s[6:7]. Give that handoff an
// ordinary CFG edge, retaining both bodies in one register and LDS lifetime.
@@ -505,7 +490,9 @@ TranslateResult TranslateProgram(std::span<const uint32_t> code, const CompileOp
EXIT("shader recompiler input is empty\n");
}
if (options.stage != ShaderType::Compute && options.stage != ShaderType::Vertex &&
options.stage != ShaderType::Pixel && options.stage != ShaderType::Mesh) {
options.stage != ShaderType::Pixel && options.stage != ShaderType::Mesh &&
options.stage != ShaderType::Local && options.stage != ShaderType::TessellationControl &&
options.stage != ShaderType::TessellationEvaluation) {
EXIT("shader recompiler received unsupported stage %u\n",
static_cast<unsigned>(options.stage));
}
@@ -525,6 +512,12 @@ TranslateResult TranslateProgram(std::span<const uint32_t> code, const CompileOp
std::vector<uint32_t> joined_code;
if (!options.back_code.empty()) {
decoded = DecodeFusedProgram(code, options.back_code, joined_code);
} else if (options.stage == ShaderType::Local) {
decoded = Decoder::DecodeFrontProgram(code);
// The separately compiled hull half runs in the next Vulkan stage.
auto& handoff = decoded.instructions.back();
handoff.opcode = Decoder::Opcode::S_ENDPGM;
handoff.src_count = 0;
} else {
Decoder::DecodeProgram(code, decoded);
}
@@ -568,8 +561,8 @@ TranslateResult TranslateProgram(std::span<const uint32_t> code, const CompileOp
}
EmbeddedFetchData embedded_fetch;
if (options.stage == ShaderType::Vertex && options.input_info.vertex != nullptr &&
options.input_info.vertex->fetch_embedded) {
if ((options.stage == ShaderType::Vertex || options.stage == ShaderType::Local) &&
options.input_info.vertex != nullptr && options.input_info.vertex->fetch_embedded) {
embedded_fetch = DetectEmbeddedVertexFetch(
decoded, options.input_info.vertex, options.user_data_base,
static_cast<uint32_t>(options.user_data.size()), options.wave_size);
@@ -608,6 +601,7 @@ TranslateResult TranslateProgram(std::span<const uint32_t> code, const CompileOp
IR::RemoveIdentities(ir.blocks);
IR::EliminateDeadCode(ir.blocks);
}
LowerTessellationMemory(ir, options);
IR::BuildSrtPlan(ir);
IR::EliminateDeadCode(ir.blocks);
IR::TrackResources(ir);
@@ -0,0 +1,330 @@
#include "graphics/shader/recompiler/Tessellation.h"
#include "common/assert.h"
#include "common/logging/log.h"
#include "graphics/shader/recompiler/ShaderRecompiler.h"
#include "graphics/shader/recompiler/frontend/decode/ShaderDecoder.h"
#include "graphics/shader/recompiler/ir/ShaderIR.h"
#include "graphics/shader/recompiler/ir/passes/ConstantPropagation.h"
#include "graphics/shader/recompiler/ir/passes/DeadCodeElimination.h"
#include <algorithm>
#include <array>
#include <unordered_map>
#include <utility>
namespace Libs::Graphics::ShaderRecompiler {
namespace {
struct TessellationAddress {
enum class Kind { Unknown, Affine, PackedControlPoint };
Kind kind = Kind::Unknown;
uint32_t coefficient = 0;
uint32_t constant = 0;
};
uint32_t ReflectTessellationStride(const Decoder::Program& program, bool local,
uint32_t control_points, uint32_t input_stride) {
using namespace Decoder;
using Address = TessellationAddress;
using Kind = Address::Kind;
std::array<Address, IR::NumVectorRegs> registers {};
registers[local ? 3u : 1u] =
local ? Address {Kind::Affine, 1u, 0u} : Address {Kind::PackedControlPoint};
const auto constant = [](uint32_t value) { return Address {Kind::Affine, 0u, value}; };
const auto read = [&](const Operand& operand) -> Address {
if (operand.absolute || operand.negate || operand.sdwa_sext || operand.op_sel ||
operand.op_sel_hi || operand.negate_hi || operand.dpp)
return {};
Address value;
if (operand.kind == OperandKind::IntegerInlineConstant ||
operand.kind == OperandKind::LiteralConstant) {
value = constant(operand.value);
} else if (operand.kind == OperandKind::Vgpr) {
value = registers.at(operand.reg);
}
// The native packed HS ID's low byte is the patch ordinal. The logical
// interface addresses one patch, as in the frontend's v1 initialization.
if (value.kind == Kind::PackedControlPoint && operand.sdwa_sel == 0u) return constant(0u);
return operand.sdwa_sel == 6u ? value : Address {};
};
const auto affine = [](uint64_t coefficient, uint64_t offset) {
return coefficient <= UINT32_MAX && offset <= UINT32_MAX
? Address {Kind::Affine, static_cast<uint32_t>(coefficient),
static_cast<uint32_t>(offset)}
: Address {};
};
const auto add = [&](Address lhs, Address rhs) {
return lhs.kind == Kind::Affine && rhs.kind == Kind::Affine
? affine(uint64_t {lhs.coefficient} + rhs.coefficient,
uint64_t {lhs.constant} + rhs.constant)
: Address {};
};
const auto multiply = [&](Address lhs, Address rhs) {
if ((lhs.kind == Kind::Affine && lhs.coefficient == 0u && lhs.constant == 0u) ||
(rhs.kind == Kind::Affine && rhs.coefficient == 0u && rhs.constant == 0u))
return constant(0u);
if (lhs.kind != Kind::Affine || rhs.kind != Kind::Affine) return Address {};
if (rhs.coefficient != 0u) std::swap(lhs, rhs);
return rhs.coefficient == 0u ? affine(uint64_t {lhs.coefficient} * rhs.constant,
uint64_t {lhs.constant} * rhs.constant)
: Address {};
};
const auto low24 = [&](Address value) {
if (value.kind == Kind::Affine && value.coefficient == 0u)
return constant(value.constant & 0xffffffu);
return value.kind == Kind::Affine &&
uint64_t {value.coefficient} * (control_points - 1u) + value.constant <=
0xffffffu
? value
: Address {};
};
uint32_t stride = 0;
for (const auto& inst: program.instructions) {
// Stage exits preserve the active path's definitions. An internal join
// would require merging register definitions.
EXIT_NOT_IMPLEMENTED(IsDirectBranch(inst.opcode) &&
inst.branch_target != program.instructions.back().pc);
const bool local_store =
inst.opcode == Opcode::DS_WRITE_B32 || inst.opcode == Opcode::DS_WRITE2_B32;
const bool buffer_store = inst.opcode == Opcode::BUFFER_STORE_DWORD ||
inst.opcode == Opcode::BUFFER_STORE_DWORDX2 ||
inst.opcode == Opcode::BUFFER_STORE_DWORDX3 ||
inst.opcode == Opcode::BUFFER_STORE_DWORDX4;
const bool control_store =
!local && buffer_store && inst.src2.kind == OperandKind::Sgpr && inst.src2.reg == 2u;
const bool control_read =
!local && (inst.opcode == Opcode::DS_READ_B32 || inst.opcode == Opcode::DS_READ2_B32);
if ((local && local_store) || control_store || control_read) {
const auto address = read(inst.src0);
if (address.kind != Kind::Affine) {
EXIT("%s tessellation address is not affine at pc 0x%08x\n", local ? "LS" : "HS",
inst.pc);
}
if (address.coefficient != 0u) {
const auto expected = control_read ? input_stride : stride;
EXIT_NOT_IMPLEMENTED((address.coefficient & 3u) != 0u ||
(expected != 0u && expected != address.coefficient));
if (!control_read) stride = address.coefficient;
}
}
// Store vdata is a source, despite occupying the decoder's dst field.
if (local_store || buffer_store || inst.dst.kind != OperandKind::Vgpr) continue;
const auto lhs = read(inst.src0);
const auto rhs = read(inst.src1);
const auto third = read(inst.src2);
Address value;
switch (inst.opcode) {
case Opcode::V_BFE_U32:
if (lhs.kind == Kind::PackedControlPoint && rhs.kind == Kind::Affine &&
rhs.coefficient == 0u && third.kind == Kind::Affine &&
third.coefficient == 0u) {
if (rhs.constant == 8u && third.constant == 5u) value = {Kind::Affine, 1u, 0u};
if (rhs.constant == 0u && third.constant == 8u) value = constant(0u);
}
break;
case Opcode::V_MUL_U32_U24: value = multiply(low24(lhs), low24(rhs)); break;
case Opcode::V_MAD_U32_U24: value = add(multiply(low24(lhs), low24(rhs)), third); break;
case Opcode::V_LSHL_ADD_U32:
if (rhs.kind == Kind::Affine && rhs.coefficient == 0u && rhs.constant < 32u)
value = add(multiply(lhs, constant(1u << rhs.constant)), third);
break;
case Opcode::V_LSHLREV_B32:
if (lhs.kind == Kind::Affine && lhs.coefficient == 0u && lhs.constant < 32u)
value = multiply(rhs, constant(1u << lhs.constant));
break;
case Opcode::V_SUB_NC_U32:
if (lhs.kind == Kind::Affine && rhs.kind == Kind::Affine &&
lhs.coefficient >= rhs.coefficient && lhs.constant >= rhs.constant)
value = affine(lhs.coefficient - rhs.coefficient, lhs.constant - rhs.constant);
break;
default: break;
}
if (inst.dst.sdwa_sel != 6u || inst.dst.op_sel || inst.dst.omod || inst.dst.clamp ||
inst.dst.dpp)
value = {};
for (uint32_t index = 0;
index < std::max(inst.data_dwords, 1u) && inst.dst.reg + index < registers.size(); index++) {
registers[inst.dst.reg + index] = {};
}
registers.at(inst.dst.reg) = value;
}
EXIT_NOT_IMPLEMENTED(stride == 0u);
return stride;
}
const IR::Inst* TessellationBufferBase(const IR::Inst& inst) {
if (IR::BufferAccessOf(inst.GetOpcode()) == IR::BufferAccess::None || inst.NumArgs() <= 3u)
return nullptr;
const auto* base = inst.Arg(3).Resolve().TryInstruction();
return base != nullptr && base->GetOpcode() == IR::ValueOpcode::TessellationBase ? base
: nullptr;
}
IR::Value ActiveAddress(IR::Block& block, IR::Block::iterator before, IR::Value value,
IR::Value predicate, std::unordered_map<IR::Inst*, IR::Value>& resolved) {
using namespace IR;
value = value.Resolve();
auto* source = value.TryInstruction();
if (source == nullptr) return value;
if (const auto found = resolved.find(source); found != resolved.end()) return found->second;
if (source->GetOpcode() == ValueOpcode::SelectU32) {
return source->Arg(0).Resolve() == predicate.Resolve()
? ActiveAddress(block, before, source->Arg(1), predicate, resolved)
: value;
}
if (source->GetOpcode() != ValueOpcode::IAdd32 && source->GetOpcode() != ValueOpcode::ISub32)
return value;
const auto lhs = ActiveAddress(block, before, source->Arg(0), predicate, resolved);
const auto rhs = ActiveAddress(block, before, source->Arg(1), predicate, resolved);
if (lhs != source->Arg(0).Resolve() || rhs != source->Arg(1).Resolve()) {
auto copy = block.PrependNewInst(before, source->GetOpcode(), {lhs, rhs},
source->Flags<uint64_t>());
value = Value(&*copy);
}
resolved.emplace(source, value);
return value;
}
} // namespace
void AnalyzeTessellationPrograms(std::span<const uint32_t> local, std::span<const uint32_t> control,
ShaderTessellationInputInfo& info) {
const auto local_program = Decoder::DecodeFrontProgram(local);
Decoder::Program control_program;
Decoder::DecodeProgram(control, control_program);
info.ls_stride = ReflectTessellationStride(local_program, true, info.input_control_points, 0u);
info.hs_stride = ReflectTessellationStride(control_program, false, info.output_control_points,
info.ls_stride);
LOGF("Tessellation interface: input_cp=%u output_cp=%u ls_stride=%u hs_stride=%u\n",
info.input_control_points, info.output_control_points, info.ls_stride, info.hs_stride);
}
void LowerTessellationMemory(IR::Program& program, const CompileOptions& options) {
using namespace IR;
if (options.stage != ShaderType::Local && options.stage != ShaderType::TessellationControl &&
options.stage != ShaderType::TessellationEvaluation) {
return;
}
const auto& tess = options.input_info.vertex->tess;
// Ring addresses can reuse data VGPRs. Their inactive values are irrelevant to
// a store guarded by the same EXEC predicate, but must remain intact elsewhere.
for (auto* block: program.blocks) {
for (auto it = block->begin(); it != block->end(); ++it) {
if (TessellationBufferBase(*it) == nullptr) continue;
std::unordered_map<Inst*, Value> resolved;
it->SetArg(
2, ActiveAddress(*block, it, it->Arg(2), it->Arg(it->NumArgs() - 1u), resolved));
}
}
ConstantPropagationPass(program.blocks);
uint32_t reads = 0, writes = 0, factors = 0;
for (auto* block: program.blocks) {
for (auto it = block->begin(); it != block->end(); ++it) {
auto& inst = *it;
const auto shared = SharedAccessOf(inst.GetOpcode());
const auto buffer = BufferAccessOf(inst.GetOpcode());
if (shared == SharedAccess::None && buffer == BufferAccess::None) {
continue;
}
const auto& memory = program.memory_info.at(inst.Flags<MemoryFlags>().index);
bool write = false;
uint32_t components = 0;
TessellationAttribute kind;
Value address, predicate;
if (shared != SharedAccess::None && memory.kind == ResourceKind::Lds) {
write = shared == SharedAccess::Write;
EXIT_NOT_IMPLEMENTED(memory.data_bits != 32u ||
(options.stage == ShaderType::Local
? !write
: options.stage != ShaderType::TessellationControl ||
shared != SharedAccess::Read));
kind = write ? TessellationAttribute::LocalOutput
: TessellationAttribute::ControlInput;
components = SharedComponentCount(inst.GetOpcode());
address = inst.Arg(0);
predicate = inst.Arg(inst.NumArgs() - 1u);
} else if (buffer != BufferAccess::None && memory.kind == ResourceKind::Buffer) {
const auto* base = TessellationBufferBase(inst);
if (base == nullptr) {
continue;
}
EXIT_NOT_IMPLEMENTED(memory.data_bits != 32u || memory.formatted || memory.idxen ||
!memory.offen || buffer == BufferAccess::Atomic);
write = buffer == BufferAccess::Write;
components = BufferComponentCount(inst.GetOpcode());
address = inst.Arg(2).Resolve();
predicate = inst.Arg(inst.NumArgs() - 1u);
if (base->Arg(0).U32() == 1u) {
EXIT_NOT_IMPLEMENTED(!write ||
options.stage != ShaderType::TessellationControl);
kind = TessellationAttribute::Factor;
factors += components;
} else {
kind = write ? TessellationAttribute::ControlOutput
: TessellationAttribute::EvaluationInput;
EXIT_NOT_IMPLEMENTED(write
? options.stage != ShaderType::TessellationControl
: options.stage != ShaderType::TessellationEvaluation);
if (address.IsImmediate() &&
address.U32() >= tess.hs_stride * tess.output_control_points) {
EXIT_NOT_IMPLEMENTED(!write);
kind = TessellationAttribute::PatchOutput;
}
}
} else {
continue;
}
const auto emit = [&](ValueOpcode opcode, std::initializer_list<Value> args) {
return Value(&*block->PrependNewInst(it, opcode, args));
};
std::array<Value, 4> values;
for (uint32_t component = 0; component < components; component++) {
const auto offset = memory.offset + 4u * component;
const auto byte_address =
offset == 0u ? address : emit(ValueOpcode::IAdd32, {address, Value(offset)});
if (write) {
Value data = shared != SharedAccess::None ? inst.Arg(component + 1u)
: components == 1u
? inst.Arg(4)
: emit(components == 2u ? ValueOpcode::CompositeExtractU32x2
: components == 3u ? ValueOpcode::CompositeExtractU32x3
: ValueOpcode::CompositeExtractU32x4,
{inst.Arg(4), Value(component)});
emit(ValueOpcode::SetTessellationAttribute,
{Value(static_cast<uint32_t>(kind)), byte_address, data, predicate});
writes++;
} else {
values[component] =
emit(ValueOpcode::GetTessellationAttribute,
{Value(static_cast<uint32_t>(kind)), byte_address, predicate});
reads++;
}
}
if (!write) {
Value replacement = values[0];
if (components == 2u)
replacement =
emit(ValueOpcode::CompositeConstructU32x2, {values[0], values[1]});
if (components == 3u)
replacement = emit(ValueOpcode::CompositeConstructU32x3,
{values[0], values[1], values[2]});
if (components == 4u)
replacement = emit(ValueOpcode::CompositeConstructU32x4,
{values[0], values[1], values[2], values[3]});
inst.ReplaceUsesWith(replacement);
}
inst.Invalidate();
}
}
ConstantPropagationPass(program.blocks);
RemoveIdentities(program.blocks);
EliminateDeadCode(program.blocks);
LOGF("%s tessellation lowering: reads=%u writes=%u factors=%u\n",
options.stage == ShaderType::Local ? "LS"
: options.stage == ShaderType::TessellationControl ? "HS"
: "TES",
reads, writes, factors);
}
} // namespace Libs::Graphics::ShaderRecompiler
@@ -0,0 +1,21 @@
#pragma once
#include <cstdint>
#include <span>
namespace Libs::Graphics {
struct ShaderTessellationInputInfo;
}
namespace Libs::Graphics::ShaderRecompiler {
struct CompileOptions;
namespace IR {
struct Program;
}
void AnalyzeTessellationPrograms(std::span<const uint32_t> local, std::span<const uint32_t> control,
ShaderTessellationInputInfo& info);
void LowerTessellationMemory(IR::Program& program, const CompileOptions& options);
} // namespace Libs::Graphics::ShaderRecompiler
@@ -277,7 +277,8 @@ Emitter::SpirvRequirements Emitter::AnalyzeProgramRequirements(const IR::Program
break;
}
case IR::ValueOpcode::LaneId:
requirements.subgroup_local_invocation_id = true;
requirements.subgroup_local_invocation_id |=
program.stage != ShaderType::TessellationControl;
break;
case IR::ValueOpcode::ImageQueryLod: requirements.compute_derivatives = true; break;
case IR::ValueOpcode::ImageGatherRaw:
@@ -306,8 +307,10 @@ std::vector<uint32_t> EmitProgram(const IR::Program& program,
using namespace Emitter;
if (program.stage != ShaderType::Compute && program.stage != ShaderType::Vertex &&
program.stage != ShaderType::Pixel && program.stage != ShaderType::Mesh) {
Fail(program, "binary SPIR-V emitter supports compute, vertex, and pixel shaders");
program.stage != ShaderType::Pixel && program.stage != ShaderType::Mesh &&
program.stage != ShaderType::Local && program.stage != ShaderType::TessellationControl &&
program.stage != ShaderType::TessellationEvaluation) {
Fail(program, "binary SPIR-V emitter received an unsupported shader stage");
}
if (!program.srt_plan_complete || !program.resource_tracking_complete ||
!program.shader_info_complete || !program.binding_layout_complete) {
@@ -250,7 +250,10 @@ void EmitStorageImageWrite(EmitterState& state, uint32_t resource, uint32_t mip_
spv::ExecutionModel ExecutionModelForStage(ShaderType stage) {
switch (stage) {
case ShaderType::Local:
case ShaderType::Vertex: return spv::ExecutionModelVertex;
case ShaderType::TessellationControl: return spv::ExecutionModelTessellationControl;
case ShaderType::TessellationEvaluation: return spv::ExecutionModelTessellationEvaluation;
case ShaderType::Mesh: return spv::ExecutionModelMeshEXT; // MeshEXT
case ShaderType::Pixel: return spv::ExecutionModelFragment;
default: return spv::ExecutionModelGLCompute;
@@ -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,11 +51,13 @@ 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 ||
kind == IR::StageInputKind::InvocationId || kind == IR::StageInputKind::PrimitiveId ||
kind == IR::StageInputKind::Layer || kind == IR::StageInputKind::SampleId) {
const auto value = state.builder.AllocateId();
const auto bits = state.builder.AllocateId();
@@ -58,7 +65,7 @@ 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::FragCoord) {
if (kind == IR::StageInputKind::FragCoord || kind == IR::StageInputKind::TessCoord) {
const auto pointer = state.builder.AllocateId();
const auto value = state.builder.AllocateId();
const auto bits = state.builder.AllocateId();
@@ -69,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;
}
@@ -138,7 +154,7 @@ uint32_t EmitAttribute(EmitterState& state, uint32_t attr, uint32_t chan) {
if (input == nullptr || input->variable_id == 0) {
return ConstantU32(state, 0);
}
if (state.program.stage == ShaderType::Vertex) {
if (state.program.stage == ShaderType::Vertex || state.program.stage == ShaderType::Local) {
return EmitVertexParameterComponentU32(state, *input, chan & 3u);
}
const auto load_per_vertex = [&](uint32_t vertex) {
@@ -498,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,
@@ -517,11 +558,19 @@ uint32_t EmitIdentity(ValueEmitContext&, uint32_t value) {
void EmitVoid(ValueEmitContext&) {}
void EmitBarrier(EmitterState& state) {
const auto semantics =
spv::MemorySemanticsAcquireReleaseMask | spv::MemorySemanticsWorkgroupMemoryMask;
const auto tessellation = state.program.stage == ShaderType::TessellationControl;
const auto memory_scope = tessellation ? spv::ScopeInvocation : spv::ScopeWorkgroup;
const auto semantics = tessellation ? spv::MemorySemanticsMaskNone
: spv::MemorySemanticsAcquireReleaseMask |
spv::MemorySemanticsWorkgroupMemoryMask;
state.builder.AddFunction(spv::OpControlBarrier, ConstantU32(state, spv::ScopeWorkgroup),
ConstantU32(state, spv::ScopeWorkgroup),
ConstantU32(state, semantics));
ConstantU32(state, memory_scope), ConstantU32(state, semantics));
}
uint32_t EmitLaneId(EmitterState& state) {
return state.program.stage == ShaderType::TessellationControl
? EmitBuiltinU32(state, IR::StageInputKind::InvocationId, 0)
: EmitSubgroupLocalInvocationId(state);
}
uint32_t EmitMeshDrawParameter(ValueEmitContext& ctx, const IR::Inst& inst) {
@@ -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) ||
@@ -155,6 +155,8 @@ inline constexpr auto EmitInstPrefetch = EmitVoid;
void EmitBarrier(EmitterState& state);
void EmitMeshAllocate(ValueEmitContext& ctx, const IR::Inst& inst);
uint32_t EmitMeshDrawParameter(ValueEmitContext& ctx, const IR::Inst& inst);
uint32_t EmitGetTessellationAttribute(ValueEmitContext& ctx, const IR::Inst& inst);
void EmitSetTessellationAttribute(ValueEmitContext& ctx, const IR::Inst& inst);
uint32_t EmitGetUserData(EmitterState& state, IR::ScalarReg reg);
uint32_t EmitGetBuiltin(ValueEmitContext& ctx, IR::Value kind, IR::Value index);
uint32_t EmitUndefU1(EmitterState& state, const IR::Inst& inst);
@@ -165,7 +167,7 @@ inline constexpr auto EmitUndefU64 = EmitUndefU1;
uint32_t EmitDppMoveU32(ValueEmitContext& ctx, const IR::Inst& inst);
uint32_t EmitDppUpdateU32(ValueEmitContext& ctx, const IR::Inst& inst);
uint32_t EmitWqmU64(EmitterState& state, uint32_t value);
inline constexpr auto EmitLaneId = EmitSubgroupLocalInvocationId;
uint32_t EmitLaneId(EmitterState& state);
uint32_t EmitBallot(ValueEmitContext& ctx, IR::Value predicate);
uint32_t EmitReadFirstLane(ValueEmitContext& ctx, const IR::Inst& inst);
uint32_t EmitReadLane(ValueEmitContext& ctx, const IR::Inst& inst);
@@ -271,6 +273,7 @@ inline constexpr auto EmitImageAtomicOr32 = EmitImage;
inline constexpr auto EmitImageAtomicXor32 = EmitImage;
void EmitUnreachable(ValueEmitContext& ctx, const IR::Inst& inst);
inline constexpr auto EmitPhi = EmitUnreachable;
inline constexpr auto EmitTessellationBase = EmitUnreachable;
inline constexpr auto EmitGetThreadBitScalarRegister = EmitUnreachable;
inline constexpr auto EmitSetThreadBitScalarRegister = EmitUnreachable;
inline constexpr auto EmitGetScalarMaskTag = EmitUnreachable;
@@ -80,6 +80,10 @@ struct EmitterState {
Builder builder;
const IR::Program& program;
ShaderStageInputInfo input_info;
std::array<uint32_t, 6> tess_variables {};
uint32_t tess_inner_variable = 0;
uint32_t tess_patch_base = 0;
const SpirvRequirements requirements;
uint32_t lane_count = 1;
uint32_t lane_half = 0;
@@ -112,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;
@@ -313,7 +318,11 @@ uint32_t ConstantU64(EmitterState& state, uint64_t value);
uint32_t ConstantU32CompositeZero(EmitterState& state, uint32_t components);
uint32_t DefineInterfaceVariable(EmitterState& state, uint32_t type, spv::StorageClass storage,
const char* name);
void DefineModule(EmitterState& state);
void DefineTessellationInterfaces(EmitterState& state);
void DefineTessellationExecutionModes(EmitterState& state);
void DefineMeshOutputs(EmitterState& state);
void EmitMeshEntryPoint(EmitterState& state);
void EmitMeshAllocate(ValueEmitContext& ctx, const IR::Inst& inst);
@@ -290,7 +290,8 @@ uint32_t GlslStd450(EmitterState& state) {
}
VertexInputScalarKind VertexParameterScalarKind(const EmitterState& state, uint32_t location) {
if (state.program.stage != ShaderType::Vertex || location >= ShaderVertexInputInfo::RES_MAX ||
if ((state.program.stage != ShaderType::Vertex && state.program.stage != ShaderType::Local) ||
location >= ShaderVertexInputInfo::RES_MAX ||
location >= static_cast<uint32_t>(state.input_info.vertex->resources_num)) {
return VertexInputScalarKind::Float;
}
@@ -333,8 +334,6 @@ uint32_t VertexParameterScalarType(EmitterState& state, VertexInputScalarKind ki
}
}
namespace {
uint32_t DefineInterfaceVariable(EmitterState& state, uint32_t type, spv::StorageClass storage,
const char* name) {
const auto variable =
@@ -344,9 +343,14 @@ uint32_t DefineInterfaceVariable(EmitterState& state, uint32_t type, spv::Storag
return variable;
}
namespace {
uint32_t BuiltInForInput(IR::StageInputKind kind) {
switch (kind) {
case IR::StageInputKind::VertexIndex: return spv::BuiltInVertexIndex;
case IR::StageInputKind::InvocationId: return spv::BuiltInInvocationId;
case IR::StageInputKind::PrimitiveId: return spv::BuiltInPrimitiveId;
case IR::StageInputKind::TessCoord: return spv::BuiltInTessCoord;
case IR::StageInputKind::InstanceIndex: return spv::BuiltInInstanceIndex;
case IR::StageInputKind::FragCoord: return spv::BuiltInFragCoord;
case IR::StageInputKind::FrontFacing: return spv::BuiltInFrontFacing;
@@ -384,9 +388,24 @@ 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:
case IR::StageInputKind::InvocationId:
case IR::StageInputKind::PrimitiveId:
case IR::StageInputKind::InstanceIndex:
case IR::StageInputKind::Layer:
case IR::StageInputKind::SampleId: type = TypeI32(state); break;
@@ -394,11 +413,13 @@ void DefineInputs(EmitterState& state) {
case IR::StageInputKind::LocalInvocationId:
case IR::StageInputKind::GlobalInvocationId: type = TypeU32Vector(state, 3); break;
case IR::StageInputKind::FragCoord: type = TypeF32Vector(state, 4); break;
case IR::StageInputKind::TessCoord:
case IR::StageInputKind::BaryCoordSmooth:
case IR::StageInputKind::BaryCoordNoPerspective: type = TypeF32Vector(state, 3); break;
case IR::StageInputKind::FrontFacing: type = TypeBool(state); break;
case IR::StageInputKind::Parameter:
if (state.program.stage == ShaderType::Vertex) {
if (state.program.stage == ShaderType::Vertex ||
state.program.stage == ShaderType::Local) {
type = VertexParameterScalarType(
state, VertexParameterScalarKind(state, input.location));
const auto components = VertexParameterComponentCount(input);
@@ -468,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) {
@@ -543,6 +573,7 @@ void DefineModule(EmitterState& state) {
state.program.info.outputs.size());
DefineInputs(state);
DefineOutputs(state);
DefineTessellationInterfaces(state);
DefineDescriptors(state);
if (state.requirements.function_lds) {
state.lds_variable = state.builder.AllocateId();
@@ -564,6 +595,10 @@ void DefineModule(EmitterState& state) {
state.builder.AddExecutionMode(state.main_func, spv::ExecutionModeOutputPrimitivesEXT,
state.input_info.vertex->mesh.max_primitives);
}
if (state.program.stage == ShaderType::TessellationControl ||
state.program.stage == ShaderType::TessellationEvaluation) {
DefineTessellationExecutionModes(state);
}
state.entry_label = state.builder.AllocateId();
state.builder.RequireCapability(spv::CapabilityShader);
@@ -0,0 +1,161 @@
#include "graphics/shader/recompiler/backend/spirv/spirvEmitterInstructions.h"
#include <algorithm>
namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter {
namespace {
uint32_t TessellationPointer(ValueEmitContext& ctx, const IR::Inst& inst) {
auto& state = ctx.state;
using Attribute = IR::TessellationAttribute;
const auto kind = static_cast<Attribute>(inst.Arg(0).U32());
const auto& tess = state.input_info.vertex->tess;
const auto variable = state.tess_variables.at(static_cast<uint32_t>(kind));
EXIT_IF(variable == 0);
const auto input = kind == Attribute::ControlInput || kind == Attribute::EvaluationInput;
const auto storage = input ? spv::StorageClassInput : spv::StorageClassOutput;
const auto pointer = state.builder.AllocateId();
if (kind == Attribute::Factor) {
EXIT_NOT_IMPLEMENTED(!inst.Arg(1).IsImmediate());
const auto index = inst.Arg(1).U32() / 4u;
const auto outer = index < 3u;
EXIT_NOT_IMPLEMENTED(index >= 4u);
state.builder.AddFunction(spv::OpAccessChain, TypePointer(state, storage, TypeF32(state)),
pointer, outer ? variable : state.tess_inner_variable,
ConstantU32(state, outer ? index : index - 3u));
return pointer;
}
auto address = ctx.Arg(inst, 1);
if (kind == Attribute::PatchOutput) {
address =
EmitBinaryU32(state, spv::OpISub, address, ConstantU32(state, state.tess_patch_base));
}
const bool local = kind == Attribute::LocalOutput || kind == Attribute::ControlInput;
const auto stride = local ? tess.ls_stride : tess.hs_stride;
const auto offset = kind == Attribute::PatchOutput ? address
: EmitBinaryU32(state, spv::OpUMod, address,
ConstantU32(state, stride));
const auto attribute =
EmitBinaryU32(state, spv::OpShiftRightLogical, offset, ConstantU32(state, 4));
const auto component =
EmitBinaryU32(state, spv::OpBitwiseAnd,
EmitBinaryU32(state, spv::OpShiftRightLogical, offset, ConstantU32(state, 2)),
ConstantU32(state, 3));
const auto type = TypePointer(state, storage, TypeU32(state));
if (kind == Attribute::LocalOutput || kind == Attribute::PatchOutput) {
state.builder.AddFunction(spv::OpAccessChain, type, pointer, variable, attribute,
component);
} else {
const auto vertex =
kind == Attribute::ControlOutput
? EmitLaneId(state)
: EmitBinaryU32(state, spv::OpUDiv, address, ConstantU32(state, stride));
state.builder.AddFunction(spv::OpAccessChain, type, pointer, variable, vertex, attribute,
component);
}
return pointer;
}
} // namespace
void DefineTessellationInterfaces(EmitterState& state) {
using Attribute = IR::TessellationAttribute;
std::array<bool, 6> used {};
uint32_t patch_begin = UINT32_MAX, patch_end = 0;
for (const auto* block: state.program.blocks) {
for (const auto& inst: *block) {
if (inst.GetOpcode() != IR::ValueOpcode::GetTessellationAttribute &&
inst.GetOpcode() != IR::ValueOpcode::SetTessellationAttribute)
continue;
const auto kind = inst.Arg(0).U32();
used.at(kind) = true;
if (kind == static_cast<uint32_t>(Attribute::PatchOutput)) {
EXIT_NOT_IMPLEMENTED(!inst.Arg(1).IsImmediate());
patch_begin = std::min(patch_begin, inst.Arg(1).U32());
patch_end = std::max(patch_end, inst.Arg(1).U32() + 4u);
}
}
}
if (std::ranges::none_of(used, [](bool value) { return value; })) return;
const auto& tess = state.input_info.vertex->tess;
const auto array = [&](uint32_t type, uint32_t count) {
return state.builder.Type(spv::OpTypeArray, type, ConstantU32(state, count));
};
for (uint32_t index = 0; index < used.size(); index++) {
if (!used[index]) continue;
const auto kind = static_cast<Attribute>(index);
const bool input = kind == Attribute::ControlInput || kind == Attribute::EvaluationInput;
const auto storage = input ? spv::StorageClassInput : spv::StorageClassOutput;
uint32_t type;
if (kind == Attribute::Factor) {
type = array(TypeF32(state), 4u);
} else if (kind == Attribute::PatchOutput) {
state.tess_patch_base = patch_begin;
type = array(TypeU32Vector(state, 4), (patch_end - patch_begin + 15u) / 16u);
} else {
const bool local = kind == Attribute::LocalOutput || kind == Attribute::ControlInput;
const auto stride = local ? tess.ls_stride : tess.hs_stride;
type = array(TypeU32Vector(state, 4), (stride + 15u) / 16u);
if (kind != Attribute::LocalOutput) {
type = array(type, local ? tess.input_control_points : tess.output_control_points);
}
}
auto& variable = state.tess_variables[index];
variable = DefineInterfaceVariable(state, type, storage, "tess_attributes");
if (kind == Attribute::Factor) {
state.builder.AddAnnotation(spv::OpDecorate, variable, spv::DecorationBuiltIn,
spv::BuiltInTessLevelOuter);
state.tess_inner_variable =
DefineInterfaceVariable(state, array(TypeF32(state), 2u), storage, "tess_inner");
state.builder.AddAnnotation(spv::OpDecorate, state.tess_inner_variable,
spv::DecorationBuiltIn, spv::BuiltInTessLevelInner);
state.builder.AddAnnotation(spv::OpDecorate, state.tess_inner_variable,
spv::DecorationPatch);
} else {
state.builder.AddAnnotation(
spv::OpDecorate, variable, spv::DecorationLocation,
kind == Attribute::PatchOutput ? (tess.hs_stride + 15u) / 16u : 0u);
}
if (kind == Attribute::Factor || kind == Attribute::PatchOutput) {
state.builder.AddAnnotation(spv::OpDecorate, variable, spv::DecorationPatch);
}
}
}
void DefineTessellationExecutionModes(EmitterState& state) {
const auto& tess = state.input_info.vertex->tess;
EXIT_NOT_IMPLEMENTED(tess.domain != 1u || tess.partitioning != 2u ||
tess.output_topology != 2u);
state.builder.RequireCapability(spv::CapabilityTessellation);
if (state.program.stage == ShaderType::TessellationControl) {
state.builder.AddExecutionMode(state.main_func, spv::ExecutionModeOutputVertices,
tess.output_control_points);
} else {
state.builder.AddExecutionMode(state.main_func, spv::ExecutionModeTriangles);
state.builder.AddExecutionMode(state.main_func, spv::ExecutionModeSpacingFractionalOdd);
state.builder.AddExecutionMode(state.main_func, spv::ExecutionModeVertexOrderCw);
}
}
uint32_t EmitGetTessellationAttribute(ValueEmitContext& ctx, const IR::Inst& inst) {
return EmitValueOrZeroIfCondition(ctx.state, ctx.Arg(inst, 2), [&] {
const auto value = ctx.state.builder.AllocateId();
ctx.state.builder.AddFunction(spv::OpLoad, TypeU32(ctx.state), value,
TessellationPointer(ctx, inst));
return value;
});
}
void EmitSetTessellationAttribute(ValueEmitContext& ctx, const IR::Inst& inst) {
EmitIfCondition(ctx.state, ctx.Arg(inst, 3), [&] {
auto value = ctx.Arg(inst, 2);
if (inst.Arg(0).U32() == static_cast<uint32_t>(IR::TessellationAttribute::Factor)) {
const auto floating = ctx.state.builder.AllocateId();
ctx.state.builder.AddFunction(spv::OpBitcast, TypeF32(ctx.state), floating, value);
value = floating;
}
ctx.state.builder.AddFunction(spv::OpStore, TessellationPointer(ctx, inst), value);
});
}
} // namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter
@@ -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:
@@ -381,6 +384,25 @@ void DecodeInstruction(std::span<const uint32_t> code, uint32_t word_index, Inst
}
}
Program DecodeFrontProgram(std::span<const uint32_t> front) {
Program result;
uint32_t front_words = 0;
while (front_words < front.size()) {
auto& inst = result.instructions.emplace_back();
DecodeInstruction(front, front_words, inst);
front_words += inst.word_count;
if (inst.opcode == Opcode::S_SETPC_B64) {
EXIT_NOT_IMPLEMENTED(inst.src0.kind != OperandKind::Sgpr || inst.src0.reg != 6u);
break;
}
EXIT_NOT_IMPLEMENTED(inst.opcode == Opcode::S_ENDPGM);
}
EXIT_IF(result.instructions.empty() ||
result.instructions.back().opcode != Opcode::S_SETPC_B64);
result.code = front.first(front_words);
return result;
}
void DecodeProgram(std::span<const uint32_t> code, Program& program) {
program.instructions.clear();
program.instructions.reserve(code.size());
@@ -535,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));
@@ -558,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,
@@ -559,6 +560,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 +583,7 @@ enum class Opcode {
S_CBRANCH_VCCNZ,
S_CBRANCH_EXECZ,
S_CBRANCH_EXECNZ,
S_CBRANCH_CDBGSYS,
S_SENDMSG,
S_SETREG_B32,
S_SLEEP,
@@ -722,6 +725,7 @@ struct Program {
Family GetInstructionFamily(uint32_t word);
// The output object must be freshly initialized.
void DecodeInstruction(std::span<const uint32_t> code, uint32_t word_index, Instruction& inst);
Program DecodeFrontProgram(std::span<const uint32_t> front);
void DecodeProgram(std::span<const uint32_t> code, Program& program);
bool IsConditionalBranch(Opcode opcode);
bool IsDirectBranch(Opcode opcode);
@@ -516,7 +516,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},
@@ -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:
@@ -898,11 +873,15 @@ void ValidateTranslateOptions(const TranslateOptions& options) {
if (options.wave_size != 32u && options.wave_size != 64u) {
EXIT("shader translation requires wave32 or wave64, got %u", options.wave_size);
}
if (options.embedded_fetch != nullptr && options.stage != ShaderType::Vertex) {
EXIT("embedded fetch requires the vertex shader stage");
if (options.embedded_fetch != nullptr && options.stage != ShaderType::Vertex &&
options.stage != ShaderType::Local) {
EXIT("embedded fetch requires a vertex or local shader");
}
switch (options.stage) {
case ShaderType::Vertex:
case ShaderType::Local:
case ShaderType::TessellationControl:
case ShaderType::TessellationEvaluation:
case ShaderType::Mesh:
if (options.input_info.vertex == nullptr) {
EXIT("vertex shader translation has no vertex input metadata");
@@ -941,6 +920,9 @@ IR::Program TranslateProgram(const Decoder::Program& decoded, const CFG::Graph&
result.user_data_count = options.user_data_count;
switch (options.stage) {
case ShaderType::Vertex:
case ShaderType::Local:
case ShaderType::TessellationControl:
case ShaderType::TessellationEvaluation:
result.scratch_dwords = options.input_info.vertex->scratch_size_dwords;
break;
case ShaderType::Mesh:
@@ -1120,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);
@@ -1164,15 +1152,53 @@ IR::Program TranslateProgram(const Decoder::Program& decoded, const CFG::Graph&
entry_ir.SetVectorReg(
static_cast<IR::VectorReg>(8),
entry_ir.IAdd(draw(2), builtin(IR::StageInputKind::WorkgroupId, 1)));
} else if (options.stage == ShaderType::Local) {
entry_ir.SetScalarReg(static_cast<IR::ScalarReg>(3), IR::U32(IR::Value(64u)));
entry_ir.SetVectorReg(static_cast<IR::VectorReg>(2),
builtin(IR::StageInputKind::VertexIndex));
entry_ir.SetVectorReg(static_cast<IR::VectorReg>(3), IR::U32(IR::Value(0u)));
entry_ir.SetVectorReg(static_cast<IR::VectorReg>(5),
builtin(IR::StageInputKind::InstanceIndex));
} else if (options.stage == ShaderType::TessellationControl) {
const auto& tess = options.input_info.vertex->tess;
entry_ir.SetScalarReg(
static_cast<IR::ScalarReg>(2),
IR::U32(entry_ir.Emit(IR::ValueOpcode::TessellationBase, {IR::Value(0u)})));
entry_ir.SetScalarReg(
static_cast<IR::ScalarReg>(4),
IR::U32(entry_ir.Emit(IR::ValueOpcode::TessellationBase, {IR::Value(1u)})));
entry_ir.SetScalarReg(static_cast<IR::ScalarReg>(3),
IR::U32(IR::Value(0x81010000u | tess.input_control_points |
(tess.output_control_points << 8u))));
entry_ir.SetVectorReg(static_cast<IR::VectorReg>(0),
builtin(IR::StageInputKind::PrimitiveId));
entry_ir.SetVectorReg(
static_cast<IR::VectorReg>(1),
entry_ir.ShiftLeftLogical(builtin(IR::StageInputKind::InvocationId),
IR::U32(IR::Value(8u))));
} else if (options.stage == ShaderType::TessellationEvaluation) {
entry_ir.SetScalarReg(static_cast<IR::ScalarReg>(3), IR::U32(IR::Value(64u)));
entry_ir.SetScalarReg(
static_cast<IR::ScalarReg>(4),
IR::U32(entry_ir.Emit(IR::ValueOpcode::TessellationBase, {IR::Value(0u)})));
entry_ir.SetVectorReg(static_cast<IR::VectorReg>(5),
builtin(IR::StageInputKind::TessCoord, 0));
entry_ir.SetVectorReg(static_cast<IR::VectorReg>(6),
builtin(IR::StageInputKind::TessCoord, 1));
entry_ir.SetVectorReg(static_cast<IR::VectorReg>(7), IR::U32(IR::Value(0u)));
entry_ir.SetVectorReg(static_cast<IR::VectorReg>(8),
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++),
@@ -1215,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);
@@ -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()) {
+19 -1
View File
@@ -150,8 +150,20 @@ struct SampledResourcePair {
bool operator==(const SampledResourcePair& other) const = default;
};
enum class TessellationAttribute {
LocalOutput,
ControlInput,
ControlOutput,
EvaluationInput,
PatchOutput,
Factor
};
enum class StageInputKind {
VertexIndex,
InvocationId,
PrimitiveId,
TessCoord,
InstanceIndex,
FragCoord,
FrontFacing,
@@ -159,6 +171,7 @@ enum class StageInputKind {
Layer,
SampleId,
BaryCoordSmooth,
BaryCoordSmoothCentroid,
BaryCoordNoPerspective,
WorkgroupId,
LocalInvocationId,
@@ -287,8 +300,12 @@ static_assert(sizeof(PushData) == 128);
constexpr uint32_t NativePushConstantSize = sizeof(PushData);
[[nodiscard]] constexpr uint32_t NativeBinding(ShaderType stage, DescriptorBindingKind kind) {
const uint32_t group = stage == ShaderType::Pixel ? 1u
: stage == ShaderType::TessellationControl ? 2u
: stage == ShaderType::TessellationEvaluation ? 3u
: 0u;
return static_cast<uint32_t>(kind) +
(stage == ShaderType::Pixel ? static_cast<uint32_t>(DescriptorBindingKind::Count) : 0u);
group * static_cast<uint32_t>(DescriptorBindingKind::Count);
}
[[nodiscard]] constexpr ImageResourceClass ImageBindingResourceClass(DescriptorBindingKind kind) {
@@ -546,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);
@@ -90,6 +90,7 @@ bool HasSideEffects(ValueOpcode opcode) {
case ValueOpcode::Reference:
case ValueOpcode::ReferenceU32:
case ValueOpcode::SetAttribute:
case ValueOpcode::SetTessellationAttribute:
case ValueOpcode::MeshAllocate:
case ValueOpcode::Barrier: return true;
default: return false;
@@ -10,6 +10,9 @@ VALUE_OPCODE(GetUserData, U32, ScalarReg)
VALUE_OPCODE(GetShaderBase, U64)
VALUE_OPCODE(MeshDrawParameter, U32, U32)
VALUE_OPCODE(MeshAllocate, Void, U32)
VALUE_OPCODE(TessellationBase, U32, U32)
VALUE_OPCODE(GetTessellationAttribute, U32, U32, U32, U1)
VALUE_OPCODE(SetTessellationAttribute, Void, U32, U32, U32, U1)
VALUE_OPCODE(GetBuiltin, U32, U32, U32)
VALUE_OPCODE(GetThreadBitScalarRegister, U1, ScalarReg)
VALUE_OPCODE(SetThreadBitScalarRegister, Void, ScalarReg, U1)
@@ -228,6 +228,15 @@ void FoldInstruction(Block& block, Block::iterator instruction,
const auto offset = Arg(inst, 1);
const auto count = Arg(inst, 2);
auto* source = value.TryInstruction();
if (source != nullptr && source->GetOpcode() == ValueOpcode::ShiftLeftLogical32 &&
IsImmediate(offset, Type::U32) && IsImmediate(count, Type::U32)) {
const auto shift = Arg(*source, 1);
if (IsImmediate(shift, Type::U32) && shift.U32() < 32u &&
offset.U32() <= shift.U32() && count.U32() <= shift.U32() - offset.U32()) {
Replace(inst, Value(0u));
return;
}
}
if (source != nullptr && source->GetOpcode() == ValueOpcode::GetBuiltin &&
source->Arg(0) == Value(static_cast<uint32_t>(StageInputKind::PackedAncillary)) &&
IsImmediate(offset, Type::U32) && IsImmediate(count, Type::U32) && count.U32() != 0u) {
@@ -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
@@ -43,6 +43,9 @@ void AddOutput(ShaderInfo& info, StageOutputKind kind, uint32_t index, uint32_t
void ValidateOptions(const Program& program, ShaderStageInputInfo input_info) {
switch (program.stage) {
case ShaderType::Vertex:
case ShaderType::Local:
case ShaderType::TessellationControl:
case ShaderType::TessellationEvaluation:
case ShaderType::Mesh:
if (input_info.vertex == nullptr) {
return Fail("vertex shader has no input metadata");
@@ -81,7 +84,8 @@ void ValidateValueReferences(const Program& program, ShaderStageInputInfo input_
!inst.Arg(1).IsImmediate() || inst.Arg(1).GetType() != Type::U32) {
return Fail("typed attribute reference is not constant");
}
if (program.stage == ShaderType::Vertex &&
if ((program.stage == ShaderType::Vertex ||
program.stage == ShaderType::Local) &&
(inst.Arg(1).U32() >= 4u ||
inst.Arg(0).U32() >=
static_cast<uint32_t>(input_info.vertex->resources_num))) {
@@ -124,6 +128,8 @@ void ValidateValueReferences(const Program& program, ShaderStageInputInfo input_
}
break;
case StageInputKind::VertexIndex:
case StageInputKind::InvocationId:
case StageInputKind::PrimitiveId:
case StageInputKind::InstanceIndex:
case StageInputKind::FrontFacing:
case StageInputKind::LocalInvocationIndex:
@@ -137,11 +143,13 @@ 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");
}
break;
case StageInputKind::TessCoord:
case StageInputKind::WorkgroupId:
case StageInputKind::LocalInvocationId:
case StageInputKind::GlobalInvocationId:
@@ -161,8 +169,10 @@ void ValidateValueReferences(const Program& program, ShaderStageInputInfo input_
}
const auto& exp = program.export_info[index];
if (exp.kind == ExportTargetKind::Position && exp.index != 0 && exp.en != 0 &&
program.stage != ShaderType::Vertex && program.stage != ShaderType::Mesh) {
return Fail("auxiliary position export requires the vertex or mesh stage");
program.stage != ShaderType::Vertex && program.stage != ShaderType::Mesh &&
program.stage != ShaderType::TessellationEvaluation) {
return Fail("auxiliary position export requires a vertex, mesh, or "
"tessellation evaluation shader");
}
break;
}
@@ -216,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]);
@@ -249,6 +271,10 @@ void CollectComputeInputs(const ShaderComputeInputInfo* compute, ShaderInfo& inf
void CollectBuiltinInputs(const Program& program, ShaderInfo& info) {
for (const auto* block: program.blocks) {
for (const auto& inst: *block) {
if (program.stage == ShaderType::TessellationControl &&
inst.GetOpcode() == ValueOpcode::LaneId) {
AddInput(info, StageInputKind::InvocationId, 0, 1, "gl_InvocationID");
}
if (inst.GetOpcode() != ValueOpcode::GetBuiltin) {
continue;
}
@@ -260,6 +286,13 @@ void CollectBuiltinInputs(const Program& program, ShaderInfo& info) {
case StageInputKind::InstanceIndex:
AddInput(info, kind, 0, 1, "gl_InstanceIndex");
break;
case StageInputKind::InvocationId:
AddInput(info, kind, 0, 1, "gl_InvocationID");
break;
case StageInputKind::PrimitiveId:
AddInput(info, kind, 0, 1, "gl_PrimitiveID");
break;
case StageInputKind::TessCoord: AddInput(info, kind, 0, 3, "gl_TessCoord"); break;
case StageInputKind::FragCoord: AddInput(info, kind, 0, 4, "gl_FragCoord"); break;
case StageInputKind::FrontFacing:
AddInput(info, kind, 0, 1, "gl_FrontFacing");
@@ -267,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");
@@ -380,7 +414,10 @@ void CollectShaderInfo(Program& program, ShaderStageInputInfo input_info) {
});
});
switch (program.stage) {
case ShaderType::Vertex: CollectVertexInputs(program, input_info.vertex, next); break;
case ShaderType::Vertex:
case ShaderType::Local: CollectVertexInputs(program, input_info.vertex, next); break;
case ShaderType::TessellationControl:
case ShaderType::TessellationEvaluation:
case ShaderType::Mesh: break;
case ShaderType::Pixel: CollectPixelInputs(program, input_info.pixel, next); break;
case ShaderType::Compute: CollectComputeInputs(input_info.compute, next); break;
@@ -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;
+93 -19
View File
@@ -11,6 +11,8 @@
#include "graphics/guest_gpu/graphicsRun.h"
#include "graphics/guest_gpu/hardwareContext.h"
#include "graphics/host_gpu/renderer/renderContext.h"
#include "graphics/shader/recompiler/ShaderRecompiler.h"
#include "graphics/shader/recompiler/Tessellation.h"
#include "graphics/shader/recompiler/frontend/decode/ShaderDecoder.h"
#include "graphics/shader/shaderCompiler.h"
#include "graphics/shader/shaderVertexMetadata.h"
@@ -511,33 +513,28 @@ static uint32_t ShaderCalcPsSystemInputBase(const HW::ShaderRegisters& regs) {
return reg;
}
static bool ShaderGetStaticInputInfoVS(const HW::VertexShaderInfo& regs,
const HW::ShaderRegisters& sh,
const ShaderMappedData& data,
ShaderVertexInputInfo& info) {
static bool ShaderGetStaticVertexInputInfo(uint64_t shader_addr, const HW::UserSgprInfo& user_sgpr,
uint32_t user_sgpr_num, const HW::ShaderRegisters& sh,
const ShaderMappedData& data,
ShaderVertexInputInfo& info) {
KYTY_PROFILER_FUNCTION();
info = {};
info.pa_cl_vs_out_cntl = sh.m_paClVsOutCntl;
EXIT_NOT_IMPLEMENTED(regs.es_regs.data_addr == 0);
uint64_t shader_addr = regs.es_regs.data_addr;
const HW::UserSgprInfo& user_sgpr = regs.gs_user_sgpr;
auto user_sgpr_num = regs.gs_regs.rsrc2.user_sgpr;
EXIT_NOT_IMPLEMENTED(shader_addr == 0);
info.scratch_size_dwords = data.scratch_size_dwords;
if (data.user_data == nullptr) {
LOGF("ShaderGetInputInfoVS(): no AGC user data for shader=0x%016" PRIx64 " es=0x%016" PRIx64
" gs=0x%016" PRIx64 " user_sgpr_num=%u\n",
shader_addr, regs.es_regs.data_addr, regs.gs_regs.data_addr,
static_cast<uint32_t>(user_sgpr_num));
LOGF("ShaderGetVertexInputInfo(): no AGC user data for shader=0x%016" PRIx64
" user_sgpr_num=%u\n",
shader_addr, user_sgpr_num);
}
ShaderVertexMetadata metadata;
std::string metadata_error;
if (!ShaderReadVertexMetadata(data, HW::UserSgprInfo::SGPRS_MAX, metadata, &metadata_error)) {
LOGF("ShaderGetInputInfoVS(): invalid AGC metadata shader=0x%016" PRIx64 ": %s\n",
LOGF("ShaderGetVertexInputInfo(): invalid AGC metadata shader=0x%016" PRIx64 ": %s\n",
shader_addr, metadata_error.c_str());
return false;
}
@@ -556,7 +553,7 @@ static bool ShaderGetStaticInputInfoVS(const HW::VertexShaderInfo& regs,
(static_cast<uint64_t>(user_sgpr.value[metadata.vertex_buffer_reg + 1]) << 32u));
if (attrib == nullptr || buffer == nullptr) {
LOGF("ShaderGetInputInfoVS(): null vertex table pointer shader=0x%016" PRIx64 "\n",
LOGF("ShaderGetVertexInputInfo(): null vertex table pointer shader=0x%016" PRIx64 "\n",
shader_addr);
return false;
}
@@ -576,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) {
@@ -673,6 +675,12 @@ void BuildStageStaticKey(const ShaderVertexInputInfo& info, std::vector<uint32_t
mesh.primitives_per_group, mesh.vertices_per_group,
mesh.max_vertices, mesh.max_primitives, mesh.provoking_vertex});
}
key.push_back(info.tess.input_control_points);
if (info.tess.input_control_points != 0) {
const auto& tess = info.tess;
key.insert(key.end(), {tess.output_control_points, tess.ls_stride, tess.hs_stride,
tess.domain, tess.partitioning, tess.output_topology});
}
for (int i = 0; i < info.resources_num; i++) {
const auto& resource = info.resources[i];
@@ -698,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));
@@ -750,7 +760,8 @@ ShaderParams PrepareProgram(const HW::VertexShaderInfo& regs, const HW::Context&
GetDeclaredShaderHash(regs.es_regs.data_addr),
std::span<const uint32_t>(regs.gs_user_sgpr.value, regs.gs_regs.rsrc2.user_sgpr), data);
if ((context.GetShaderStages() & 0x20u) == 0) {
if (!ShaderGetStaticInputInfoVS(regs, sh, data, info)) {
if (!ShaderGetStaticVertexInputInfo(regs.es_regs.data_addr, regs.gs_user_sgpr,
regs.gs_regs.rsrc2.user_sgpr, sh, data, info)) {
EXIT("failed to prepare vertex shader program\n");
}
return params;
@@ -759,6 +770,7 @@ ShaderParams PrepareProgram(const HW::VertexShaderInfo& regs, const HW::Context&
// its user-data pointer in s0:s1.
params.user_data.insert(params.user_data.begin(), 8u, 0u);
info = {};
info.logical_stage = ShaderType::Mesh;
info.pa_cl_vs_out_cntl = sh.m_paClVsOutCntl;
auto& mesh = info.mesh;
mesh.input_primitive = static_cast<uint32_t>(user_config.GetPrimType());
@@ -785,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 ||
@@ -806,6 +819,65 @@ ShaderParams PrepareProgram(const HW::VertexShaderInfo& regs, const HW::Context&
return params;
}
std::array<ShaderParams, 3>
PrepareTessellationPrograms(const HW::VertexShaderInfo& regs, const HW::Context& context,
std::array<ShaderVertexInputInfo, 3>& input_info) {
const auto& sh = context.GetShaderRegisters();
const auto local = ShaderGetMappedData(regs.ls_regs.data_addr, "ShaderGetInputInfoLS():");
const auto control = ShaderGetMappedData(regs.hs_regs.data_addr, "ShaderGetInputInfoHS():");
const auto evaluation = ShaderGetMappedData(regs.es_regs.data_addr, "ShaderGetInputInfoTES():");
EXIT_NOT_IMPLEMENTED(local.type != Prospero::ShaderBinaryType::kHsFront ||
control.type != Prospero::ShaderBinaryType::kHsBack ||
evaluation.type != Prospero::ShaderBinaryType::kGs);
EXIT_NOT_IMPLEMENTED((context.GetShaderStages() & 0x4u) == 0 ||
(context.GetShaderStages() & 0x00600020u) != 0);
EXIT_IF(regs.hs_regs.user_data_addr == 0);
const auto local_users = std::span(regs.hs_user_sgpr.value, regs.hs_regs.rsrc2.user_sgpr);
const auto evaluation_users = std::span(regs.gs_user_sgpr.value, regs.gs_regs.rsrc2.user_sgpr);
std::array<ShaderParams, 3> params {
GetShaderParams(regs.ls_regs.data_addr, "ShaderRecompiler LS",
GetDeclaredShaderHash(regs.ls_regs.data_addr), local_users, local),
GetShaderParams(regs.hs_regs.data_addr, "ShaderRecompiler HS",
GetDeclaredShaderHash(regs.hs_regs.data_addr), local_users, control),
GetShaderParams(regs.es_regs.data_addr, "ShaderRecompiler TES",
GetDeclaredShaderHash(regs.es_regs.data_addr), evaluation_users,
evaluation),
};
// The fused HS back half receives its separate user-data address in s0:s1.
// RDNA2 reserves s0:s7 before the native HS user SGPRs.
params[1].user_data.insert(params[1].user_data.begin(), 8u, 0u);
params[1].user_data[0] = static_cast<uint32_t>(regs.hs_regs.user_data_addr);
params[1].user_data[1] = static_cast<uint32_t>(regs.hs_regs.user_data_addr >> 32u);
input_info = {};
if (!ShaderGetStaticVertexInputInfo(regs.ls_regs.data_addr, regs.hs_user_sgpr,
regs.hs_regs.rsrc2.user_sgpr, sh, local, input_info[0])) {
EXIT("failed to prepare local shader program\n");
}
input_info[0].logical_stage = ShaderType::Local;
input_info[1].logical_stage = ShaderType::TessellationControl;
input_info[1].scratch_size_dwords = control.scratch_size_dwords;
input_info[2].logical_stage = ShaderType::TessellationEvaluation;
input_info[2].scratch_size_dwords = evaluation.scratch_size_dwords;
input_info[2].pa_cl_vs_out_cntl = sh.m_paClVsOutCntl;
ShaderTessellationInputInfo tess {
.input_control_points = (sh.m_vgtLsHsConfig >> 8u) & 0x3fu,
.output_control_points = (sh.m_vgtLsHsConfig >> 14u) & 0x3fu,
.domain = sh.m_vgtTfParam & 0x3u,
.partitioning = (sh.m_vgtTfParam >> 2u) & 0x3u,
.output_topology = (sh.m_vgtTfParam >> 5u) & 0x3u,
};
EXIT_IF(tess.input_control_points == 0 || tess.input_control_points > 32 ||
tess.output_control_points == 0 || tess.output_control_points > 32);
ShaderRecompiler::AnalyzeTessellationPrograms(params[0].code, params[1].code, tess);
for (auto& stage: input_info) {
stage.tess = tess;
}
return params;
}
ShaderParams PrepareProgram(
const HW::PixelShaderInfo& regs, const HW::ShaderRegisters& sh,
std::span<const Prospero::ColorComponentMapping, 8> target_export_mapping,
@@ -891,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"
@@ -903,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",
+30 -3
View File
@@ -33,7 +33,17 @@ struct ComputeShaderInfo;
struct ShaderRegisters;
} // namespace HW
enum class ShaderType { Unknown, Vertex, Pixel, Fetch, Compute, Mesh };
enum class ShaderType {
Unknown,
Vertex,
Pixel,
Fetch,
Compute,
Mesh,
Local,
TessellationControl,
TessellationEvaluation
};
namespace ShaderRecompiler::IR {
struct CompiledShaderInfo;
@@ -87,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();
@@ -99,6 +112,16 @@ struct ShaderMeshInputInfo: ShaderWorkgroupInputInfo {
}
};
struct ShaderTessellationInputInfo {
uint32_t input_control_points = 0;
uint32_t output_control_points = 0;
uint32_t ls_stride = 0;
uint32_t hs_stride = 0;
uint32_t domain = 0;
uint32_t partitioning = 0;
uint32_t output_topology = 0;
};
struct ShaderVertexInputInfo {
static constexpr int RES_MAX = 32;
@@ -106,6 +129,7 @@ struct ShaderVertexInputInfo {
ShaderVertexDestination resources_dst[RES_MAX];
ShaderVertexInputBuffer buffers[RES_MAX];
ShaderStageRuntime stage;
ShaderType logical_stage = ShaderType::Vertex;
int resources_num = 0;
int fetch_attrib_reg = 0;
int fetch_buffer_reg = 0;
@@ -114,6 +138,7 @@ struct ShaderVertexInputInfo {
uint32_t pa_cl_vs_out_cntl = 0;
ShaderClipSpaceTransform clip_space;
ShaderMeshInputInfo mesh;
ShaderTessellationInputInfo tess;
bool fetch_external = false;
bool fetch_embedded = false;
};
@@ -131,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;
+3
View File
@@ -30,6 +30,9 @@ void BuildStageStaticKey(const ShaderComputeInputInfo& input_info, std::vector<u
ShaderParams PrepareProgram(const HW::VertexShaderInfo& regs, const HW::Context& context,
const HW::UserConfig& user_config, ShaderVertexInputInfo& input_info);
std::array<ShaderParams, 3>
PrepareTessellationPrograms(const HW::VertexShaderInfo& regs, const HW::Context& context,
std::array<ShaderVertexInputInfo, 3>& input_info);
ShaderParams PrepareProgram(
const HW::PixelShaderInfo& regs, const HW::ShaderRegisters& sh,
std::span<const Prospero::ColorComponentMapping, 8> target_export_mapping,
+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);
}
+1
View File
@@ -458,6 +458,7 @@ int KYTY_SYSV_ABI KernelOpen(const char* path, int flags, uint16_t mode) {
file->real_name = g_mount_points->ResolvePath(file->name);
if (trunc && rw_mode == Common::File::Mode::Read) {
g_files->DeleteDescriptor(descriptor);
return KERNEL_ERROR_EACCES;
}
+19 -28
View File
@@ -346,7 +346,9 @@ public:
auto entry = std::prev(next);
if (vaddr + size <= entry->second.vaddr + entry->second.size) {
old = entry->second;
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
m_maps.erase(entry);
#endif
}
}
}
@@ -355,6 +357,12 @@ public:
return false;
}
const uint64_t left_size = vaddr - old.vaddr;
const uint64_t right_addr = vaddr + size;
const uint64_t old_end = old.vaddr + old.size;
const uint64_t right_size = old_end - right_addr;
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
const bool exact_unmap = (vaddr == old.vaddr && size == old.size);
if (exact_unmap) {
const bool unmapped =
@@ -370,12 +378,6 @@ public:
return true;
}
const uint64_t left_size = vaddr - old.vaddr;
const uint64_t right_addr = vaddr + size;
const uint64_t old_end = old.vaddr + old.size;
const uint64_t right_size = old_end - right_addr;
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
if (UnmapViewPreservePlaceholder(reinterpret_cast<void*>(old.vaddr), old.size)) {
bool ok = true;
if (left_size != 0 && left_size != old.size) {
@@ -418,39 +420,28 @@ public:
m_maps.emplace(old.vaddr, old);
return false;
#else
if (!UnmapViewPreservePlaceholder(reinterpret_cast<void*>(old.vaddr), old.size)) {
std::lock_guard lock(m_mutex);
m_maps.emplace(old.vaddr, old);
// Replace only the requested range so concurrent accesses and GPU watch
// protections on the surviving mappings remain intact.
if (!UnmapViewPreservePlaceholder(reinterpret_cast<void*>(vaddr), size)) {
return false;
}
bool ok = true;
std::lock_guard lock(m_mutex);
m_maps.erase(old.vaddr);
if (left_size != 0) {
ok = MapExistingPlaceholderFixed(old.vaddr, left_size, old.backing_offset, old.mode) &&
ok;
m_maps.emplace(old.vaddr,
MapEntry {old.vaddr, left_size, old.backing_offset, old.mode});
}
if (right_size != 0) {
ok = MapExistingPlaceholderFixed(right_addr, right_size,
old.backing_offset + (right_addr - old.vaddr),
old.mode) &&
ok;
}
if (!ok) {
if (left_size != 0 && Contains(old.vaddr, left_size)) {
EXIT_IF(!UnmapOne(old.vaddr, left_size));
}
if (right_size != 0 && Contains(right_addr, right_size)) {
EXIT_IF(!UnmapOne(right_addr, right_size));
}
EXIT_IF(
!MapExistingPlaceholderFixed(old.vaddr, old.size, old.backing_offset, old.mode));
return false;
m_maps.emplace(right_addr,
MapEntry {right_addr, right_size,
old.backing_offset + right_addr - old.vaddr, old.mode});
}
if (placeholder_preserved != nullptr) {
*placeholder_preserved = true;
}
return ok;
return true;
#endif
}
-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);
+20 -11
View File
@@ -47,11 +47,17 @@ struct WaitDeadline {
Clock::time_point end {};
};
[[nodiscard]] WaitDeadline MakeDeadline(std::chrono::nanoseconds timeout) {
const auto now = Clock::now();
const auto remaining = Clock::time_point::max() - now;
return {true, timeout >= remaining ? Clock::time_point::max() : now + timeout};
}
[[nodiscard]] WaitDeadline MakeDeadline(const uint32_t* timeout_micros) {
if (timeout_micros == nullptr) {
return {};
}
return {true, Clock::now() + std::chrono::microseconds(*timeout_micros)};
return MakeDeadline(std::chrono::microseconds(*timeout_micros));
}
[[nodiscard]] uint32_t GetWaitSliceMicros(const WaitDeadline& deadline, bool first_wait) {
@@ -65,7 +71,7 @@ struct WaitDeadline {
}
const auto remaining =
std::chrono::duration_cast<std::chrono::microseconds>(deadline.end - now).count();
std::chrono::ceil<std::chrono::microseconds>(deadline.end - now).count();
return static_cast<uint32_t>(std::min<int64_t>(remaining, SIGNAL_POLL_MICROS));
}
@@ -78,9 +84,8 @@ void PollSignals(signal_poll_func_t signal_poll) {
#if KYTY_PLATFORM == KYTY_PLATFORM_LINUX && !defined(__APPLE__)
template <typename T>
int WaitLinux(volatile T* address, T expected, const uint32_t* timeout_micros,
int WaitLinux(volatile T* address, T expected, const WaitDeadline& deadline,
signal_poll_func_t signal_poll) {
const auto deadline = MakeDeadline(timeout_micros);
bool first_wait = true;
for (;;) {
@@ -185,11 +190,10 @@ void UnregisterPortableWaiter(volatile void* addre
}
template <typename T>
int WaitPortable(volatile T* address, T expected, const uint32_t* timeout_micros,
int WaitPortable(volatile T* address, T expected, const WaitDeadline& deadline,
signal_poll_func_t signal_poll) {
PortableWaiter waiter;
auto entry = RegisterPortableWaiter(address, &waiter);
const auto deadline = MakeDeadline(timeout_micros);
bool first_wait = true;
int result = OK;
@@ -250,7 +254,7 @@ int WakePortable(volatile void* address, int32_t count) {
}
template <typename T>
int WaitImpl(volatile T* address, T expected, const uint32_t* timeout_micros,
int WaitImpl(volatile T* address, T expected, const WaitDeadline& deadline,
signal_poll_func_t signal_poll) {
if (!IsValidWaitAddress(address)) {
return KERNEL_ERROR_EINVAL;
@@ -258,9 +262,9 @@ int WaitImpl(volatile T* address, T expected, const uint32_t* timeout_micros,
int result = OK;
#if KYTY_PLATFORM == KYTY_PLATFORM_LINUX && !defined(__APPLE__)
result = WaitLinux(address, expected, timeout_micros, signal_poll);
result = WaitLinux(address, expected, deadline, signal_poll);
#else
result = WaitPortable(address, expected, timeout_micros, signal_poll);
result = WaitPortable(address, expected, deadline, signal_poll);
#endif
PollSignals(signal_poll);
return result;
@@ -270,12 +274,17 @@ int WaitImpl(volatile T* address, T expected, const uint32_t* timeout_micros,
int Wait32(volatile uint32_t* address, uint32_t expected, const uint32_t* timeout_micros,
signal_poll_func_t signal_poll) {
return WaitImpl(address, expected, timeout_micros, signal_poll);
return WaitImpl(address, expected, MakeDeadline(timeout_micros), signal_poll);
}
int Wait64(volatile uint64_t* address, uint64_t expected, const uint32_t* timeout_micros,
signal_poll_func_t signal_poll) {
return WaitImpl(address, expected, timeout_micros, signal_poll);
return WaitImpl(address, expected, MakeDeadline(timeout_micros), signal_poll);
}
int Wait64(volatile uint64_t* address, uint64_t expected, std::chrono::nanoseconds timeout,
signal_poll_func_t signal_poll) {
return WaitImpl(address, expected, MakeDeadline(timeout), signal_poll);
}
int Wake(volatile void* address, int32_t count) {
+4
View File
@@ -4,6 +4,8 @@
#include "common/abi.h"
#include "common/common.h"
#include <chrono>
namespace Libs::LibKernel::SyncOnAddress {
using signal_poll_func_t = void (*)();
@@ -12,6 +14,8 @@ int Wait32(volatile uint32_t* address, uint32_t expected, const uint32_t* timeou
signal_poll_func_t signal_poll = nullptr);
int Wait64(volatile uint64_t* address, uint64_t expected, const uint32_t* timeout_micros,
signal_poll_func_t signal_poll = nullptr);
int Wait64(volatile uint64_t* address, uint64_t expected, std::chrono::nanoseconds timeout,
signal_poll_func_t signal_poll = nullptr);
int Wake(volatile void* address, int32_t count);
} // namespace Libs::LibKernel::SyncOnAddress
+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 -11
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>
@@ -218,6 +218,11 @@
<string>Firmware Version</string>
</property>
</column>
<column>
<property name="text">
<string>Size</string>
</property>
</column>
<column>
<property name="text">
<string>Path</string>
+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();
+33 -1
View File
@@ -3,6 +3,7 @@
#include "common/emulatorConfig.h"
#include "configuration.h"
#include "mandatoryLineEdit.h"
#include "SDL.h"
#include <QAbstractItemView>
#include <QCheckBox>
@@ -167,6 +168,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 +213,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 +239,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 +370,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 +383,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
+36
View File
@@ -5,12 +5,15 @@
#include <QApplication>
#include <QComboBox>
#include <QDir>
#include <QDirIterator>
#include <QFileInfo>
#include <QFont>
#include <QFutureWatcher>
#include <QHBoxLayout>
#include <QIcon>
#include <QLabel>
#include <QLineEdit>
#include <QLocale>
#include <QSize>
#include <QStringList>
#include <QStyle>
@@ -18,6 +21,7 @@
#include <QVersionNumber>
#include <QWheelEvent>
#include <QWidget>
#include <QtConcurrentRun>
namespace {
@@ -26,6 +30,7 @@ enum Column {
SerialColumn,
GameVersionColumn,
FirmwareVersionColumn,
SizeColumn,
PathColumn,
StatusColumn,
CommentsColumn,
@@ -155,6 +160,34 @@ ConfigurationItem::ConfigurationItem(std::unique_ptr<Configuration> info, QTreeW
Update();
SetRunning(false);
setData(SizeColumn, Qt::UserRole, qint64(-1));
setText(SizeColumn, QStringLiteral("\u2014"));
auto* watcher = new QFutureWatcher<qint64>(this);
connect(watcher, &QFutureWatcher<qint64>::finished, this, [this, watcher]() {
const auto bytes = watcher->result();
setData(SizeColumn, Qt::UserRole, bytes);
if (bytes >= 0) {
setText(SizeColumn,
QLocale().formattedDataSize(bytes, 2, QLocale::DataSizeTraditionalFormat));
}
watcher->deleteLater();
});
watcher->setFuture(QtConcurrent::run([path = m_info->basedir]() -> qint64 {
if (path.isEmpty() || !QDir(path).exists()) {
return -1;
}
qint64 bytes = 0;
QDirIterator files(path, QDir::Files | QDir::Hidden | QDir::System | QDir::NoSymLinks,
QDirIterator::Subdirectories);
while (files.hasNext()) {
files.next();
if (files.fileInfo().isFile()) {
bytes += files.fileInfo().size();
}
}
return bytes;
}));
}
ConfigurationItem::~ConfigurationItem() = default;
@@ -193,6 +226,9 @@ bool ConfigurationItem::operator<(const QTreeWidgetItem& other) const {
const int column = treeWidget() != nullptr ? treeWidget()->sortColumn() : NameColumn;
switch (column) {
case NameColumn: return GetSortText(*m_info) < GetSortText(*other_item->m_info);
case SizeColumn:
return data(SizeColumn, Qt::UserRole).toLongLong() <
other.data(SizeColumn, Qt::UserRole).toLongLong();
case StatusColumn:
return GetStatusText(m_info->game_status) <
GetStatusText(other_item->m_info->game_status);
+5 -3
View File
@@ -61,9 +61,10 @@ constexpr int GAME_NAME_COLUMN = 0;
constexpr int GAME_SERIAL_COLUMN = 1;
constexpr int GAME_VERSION_COLUMN = 2;
constexpr int GAME_FIRMWARE_VERSION_COLUMN = 3;
constexpr int GAME_PATH_COLUMN = 4;
constexpr int GAME_STATUS_COLUMN = 5;
constexpr int GAME_COMMENT_COLUMN = 6;
constexpr int GAME_SIZE_COLUMN = 4;
constexpr int GAME_PATH_COLUMN = 5;
constexpr int GAME_STATUS_COLUMN = 6;
constexpr int GAME_COMMENT_COLUMN = 7;
static QString NormalizeGameDirectory(const QString& dir) {
const auto trimmed = dir.trimmed();
@@ -217,6 +218,7 @@ ConfigurationListWidget::ConfigurationListWidget(QWidget* parent)
m_ui->cfgs_list->setColumnWidth(GAME_SERIAL_COLUMN, 110);
m_ui->cfgs_list->setColumnWidth(GAME_VERSION_COLUMN, 120);
m_ui->cfgs_list->setColumnWidth(GAME_FIRMWARE_VERSION_COLUMN, 150);
m_ui->cfgs_list->setColumnWidth(GAME_SIZE_COLUMN, 100);
m_ui->cfgs_list->setColumnWidth(GAME_PATH_COLUMN, 320);
m_ui->cfgs_list->setColumnWidth(GAME_STATUS_COLUMN, 150);
m_ui->cfgs_list->setColumnWidth(GAME_COMMENT_COLUMN, 240);
+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";
+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();
Loaded 100 of 125 files, more files were not shown because too many files have changed in this diff. Show more