mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-29 00:33:06 +00:00
Compare commits
116
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e0c055fd74 | ||
|
|
d1eee7b3a9 | ||
|
|
1342c6f1e1 | ||
|
|
6453bafd1d | ||
|
|
beb8047083 | ||
|
|
1df06e230d | ||
|
|
39a34fa343 | ||
|
|
96c64d113e | ||
|
|
66ca225c76 | ||
|
|
0de4cfbf80 | ||
|
|
42980fe6df | ||
|
|
1592cf2b1b | ||
|
|
a9166f8546 | ||
|
|
b77978fd1e | ||
|
|
4330cb9462 | ||
|
|
af8aecfdde | ||
|
|
13533bd053 | ||
|
|
cd0f0a428a | ||
|
|
6758d67942 | ||
|
|
dc5b7c19a6 | ||
|
|
2a54306e8b | ||
|
|
7a6aaafe52 | ||
|
|
1469c53048 | ||
|
|
9992ab18fc | ||
|
|
18af08460f | ||
|
|
39ee9972b8 | ||
|
|
e500333bcd | ||
|
|
4498d75b9b | ||
|
|
1df53b169f | ||
|
|
ff61c1cecd | ||
|
|
60a01817ce | ||
|
|
1a87cfedb5 | ||
|
|
5bd447d2a2 | ||
|
|
a4fc58e908 | ||
|
|
4e8e335b7b | ||
|
|
947576363e | ||
|
|
9da390bc0a | ||
|
|
ef639610e7 | ||
|
|
6c3b562822 | ||
|
|
f1413569ce | ||
|
|
fcc9dff5b9 | ||
|
|
d9ae3ed64c | ||
|
|
618926b124 | ||
|
|
dca7ece2ca | ||
|
|
ccecebfeff | ||
|
|
1752f1382f | ||
|
|
53a59434d8 | ||
|
|
18b6c9059b | ||
|
|
715539804b | ||
|
|
b02eb28fd1 | ||
|
|
07f1e77d23 | ||
|
|
6980e0863d | ||
|
|
42bfeec2e9 | ||
|
|
795743ce22 | ||
|
|
ccddd6fb94 | ||
|
|
e559771a08 | ||
|
|
b870a77176 | ||
|
|
62eaead3a3 | ||
|
|
d7808d9cb4 | ||
|
|
8267c422b5 | ||
|
|
b46e141762 | ||
|
|
cfa140fb12 | ||
|
|
c52bf450f5 | ||
|
|
4cc2f00273 | ||
|
|
ade32e7ba7 | ||
|
|
cc5c0ec6b8 | ||
|
|
c167375fe1 | ||
|
|
65871a53a6 | ||
|
|
1ff580498c | ||
|
|
ae896d0bf6 | ||
|
|
e738535294 | ||
|
|
8072718462 | ||
|
|
7adade0df5 | ||
|
|
ab50c10d08 | ||
|
|
ffa69d48de | ||
|
|
e2c840cb23 | ||
|
|
fa891536df | ||
|
|
7012634f32 | ||
|
|
663b524fb3 | ||
|
|
0b3302af70 | ||
|
|
ffd07dbfce | ||
|
|
6eed46b024 | ||
|
|
e6ad87072d | ||
|
|
dd2fbd6c45 | ||
|
|
1779142805 | ||
|
|
186621c6ff | ||
|
|
11d4c0d2a2 | ||
|
|
aa69b5b184 | ||
|
|
cb378365e1 | ||
|
|
30fa9dddc5 | ||
|
|
4d8d3b6d25 | ||
|
|
523cc17ffe | ||
|
|
825e5f1162 | ||
|
|
3c016bead1 | ||
|
|
800a7719cf | ||
|
|
e3fb05c1a8 | ||
|
|
bc9c70d50f | ||
|
|
a8fe4e7a2d | ||
|
|
bed19e5d8c | ||
|
|
4adbf40861 | ||
|
|
8d01fb1c18 | ||
|
|
a610528075 | ||
|
|
3e25eb0998 | ||
|
|
0d92ce49da | ||
|
|
4179bb5b06 | ||
|
|
d581703ab4 | ||
|
|
9241fdc32a | ||
|
|
a2baaccc6e | ||
|
|
c130d74efc | ||
|
|
5e64efce4f | ||
|
|
17c2b4cc7d | ||
|
|
bad6726f5e | ||
|
|
26c4168160 | ||
|
|
49ea1680f8 | ||
|
|
79443aa104 | ||
|
|
342fae06a6 |
No files matched your search
+47
-22
@@ -208,34 +208,66 @@ jobs:
|
||||
run: |
|
||||
archive="$RUNNER_TEMP/MoltenVK-macos.tar"
|
||||
package="$RUNNER_TEMP/MoltenVK"
|
||||
install_dir="_Build/macos/install"
|
||||
app_dir="$install_dir/KytyPS5.app"
|
||||
moltenvk="$install_dir/libMoltenVK.dylib"
|
||||
curl --fail --location --retry 3 \
|
||||
--output "$archive" \
|
||||
"https://github.com/KhronosGroup/MoltenVK/releases/download/$MOLTENVK_VERSION/MoltenVK-macos.tar"
|
||||
echo "$MOLTENVK_SHA256 $archive" | shasum -a 256 --check
|
||||
tar -xf "$archive" -C "$RUNNER_TEMP"
|
||||
test -d "$app_dir"
|
||||
install -m 755 \
|
||||
"$package/MoltenVK/dynamic/dylib/macOS/libMoltenVK.dylib" \
|
||||
_Build/macos/install/libMoltenVK.dylib
|
||||
install -m 644 "$package/LICENSE" _Build/macos/install/LICENSE.MoltenVK
|
||||
codesign --force --sign - --timestamp=none _Build/macos/install/libMoltenVK.dylib
|
||||
"$moltenvk"
|
||||
install -m 644 "$package/LICENSE" "$install_dir/LICENSE.MoltenVK"
|
||||
codesign --force --sign - --timestamp=none "$moltenvk"
|
||||
install -d -m 755 "$app_dir/Contents/Frameworks" "$app_dir/Contents/Resources"
|
||||
install -m 755 "$moltenvk" "$app_dir/Contents/Frameworks/libMoltenVK.dylib"
|
||||
install -m 644 "$package/LICENSE" "$app_dir/Contents/Resources/LICENSE.MoltenVK"
|
||||
codesign --force --sign - --timestamp=none "$app_dir"
|
||||
|
||||
- name: Verify artifacts
|
||||
shell: bash
|
||||
run: |
|
||||
file _Build/macos/install/launcher
|
||||
file _Build/macos/install/kyty_emulator
|
||||
file _Build/macos/install/libMoltenVK.dylib
|
||||
lipo _Build/macos/install/launcher -verify_arch x86_64
|
||||
lipo _Build/macos/install/kyty_emulator -verify_arch x86_64
|
||||
lipo _Build/macos/install/libMoltenVK.dylib -verify_arch x86_64
|
||||
codesign --verify --strict _Build/macos/install/kyty_emulator
|
||||
codesign --verify --strict _Build/macos/install/libMoltenVK.dylib
|
||||
install_dir="_Build/macos/install"
|
||||
app_dir="$install_dir/KytyPS5.app"
|
||||
test -d "$app_dir"
|
||||
for binary in \
|
||||
"$install_dir/kyty_emulator" \
|
||||
"$install_dir/libMoltenVK.dylib" \
|
||||
"$app_dir/Contents/MacOS/KytyPS5" \
|
||||
"$app_dir/Contents/MacOS/kyty_emulator" \
|
||||
"$app_dir/Contents/Frameworks/libMoltenVK.dylib"; do
|
||||
file "$binary"
|
||||
lipo "$binary" -verify_arch x86_64
|
||||
done
|
||||
for binary in \
|
||||
"$install_dir/kyty_emulator" \
|
||||
"$install_dir/libMoltenVK.dylib" \
|
||||
"$app_dir/Contents/MacOS/KytyPS5" \
|
||||
"$app_dir/Contents/MacOS/kyty_emulator" \
|
||||
"$app_dir/Contents/Frameworks/libMoltenVK.dylib"; do
|
||||
codesign --verify --strict "$binary"
|
||||
done
|
||||
codesign --verify --deep --strict --verbose=2 "$app_dir"
|
||||
|
||||
- name: Package macOS artifacts
|
||||
shell: bash
|
||||
run: |
|
||||
install_dir="_Build/macos/install"
|
||||
archive="$RUNNER_TEMP/KytyPS5-macOS-x86_64.zip"
|
||||
(
|
||||
cd "$install_dir"
|
||||
zip -qry "$archive" .
|
||||
)
|
||||
unzip -t "$archive"
|
||||
|
||||
- name: Upload macOS artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: KytyPS5-macOS-x86_64
|
||||
path: _Build/macos/install/**
|
||||
path: ${{ runner.temp }}/KytyPS5-macOS-x86_64.zip
|
||||
if-no-files-found: error
|
||||
|
||||
linux:
|
||||
@@ -424,16 +456,9 @@ jobs:
|
||||
if: needs.macos.result == 'success'
|
||||
shell: bash
|
||||
run: |
|
||||
macos_dir="artifacts/KytyPS5-macOS-x86_64"
|
||||
test -d "$macos_dir"
|
||||
chmod a+x \
|
||||
"$macos_dir/launcher" \
|
||||
"$macos_dir/kyty_emulator" \
|
||||
"$macos_dir/libMoltenVK.dylib"
|
||||
(
|
||||
cd "$macos_dir"
|
||||
zip -r "$GITHUB_WORKSPACE/$RELEASE_NAME-macOS-x86_64.zip" .
|
||||
)
|
||||
macos_archive="artifacts/KytyPS5-macOS-x86_64/KytyPS5-macOS-x86_64.zip"
|
||||
test -f "$macos_archive"
|
||||
cp "$macos_archive" "$RELEASE_NAME-macOS-x86_64.zip"
|
||||
|
||||
- name: Package Linux build
|
||||
if: needs.linux.result == 'success'
|
||||
|
||||
@@ -5,3 +5,4 @@ build/
|
||||
_Build/vscode-clang/
|
||||
_Build/
|
||||
cmake-build-debug/
|
||||
_PipelineCache/
|
||||
Vendored
+37
-3
@@ -10,7 +10,14 @@
|
||||
"stopAtEntry": false,
|
||||
"cwd": "${workspaceFolder}\\_Build\\vscode-clang\\launcher",
|
||||
"environment": [],
|
||||
"externalConsole": false
|
||||
"externalConsole": false,
|
||||
"linux": {
|
||||
"type": "cppdbg",
|
||||
"program": "${workspaceFolder}/_Build/vscode-clang/launcher/launcher",
|
||||
"cwd": "${workspaceFolder}/_Build/vscode-clang/launcher",
|
||||
"MIMode": "gdb",
|
||||
"miDebuggerPath": "/usr/bin/gdb"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Debug kyty_emulator",
|
||||
@@ -25,13 +32,40 @@
|
||||
"stopAtEntry": false,
|
||||
"cwd": "${workspaceFolder}\\_Build\\vscode-clang",
|
||||
"environment": [],
|
||||
"externalConsole": true
|
||||
"externalConsole": true,
|
||||
"linux": {
|
||||
"type": "cppdbg",
|
||||
"program": "${workspaceFolder}/_Build/vscode-clang/kyty_emulator",
|
||||
"cwd": "${workspaceFolder}/_Build/vscode-clang",
|
||||
"externalConsole": false,
|
||||
"MIMode": "gdb",
|
||||
"miDebuggerPath": "/usr/bin/gdb",
|
||||
"setupCommands": [
|
||||
{
|
||||
"description": "Pass Kyty's internal signals to its handlers",
|
||||
"text": "handle SIG37 SIGSEGV SIGBUS SIGILL nostop noprint pass",
|
||||
"ignoreFailures": false
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Attach to running kyty_emulator",
|
||||
"type": "cppvsdbg",
|
||||
"request": "attach",
|
||||
"processId": "${command:pickProcess}"
|
||||
"processId": "${command:pickProcess}",
|
||||
"linux": {
|
||||
"type": "cppdbg",
|
||||
"MIMode": "gdb",
|
||||
"miDebuggerPath": "/usr/bin/gdb",
|
||||
"setupCommands": [
|
||||
{
|
||||
"description": "Pass Kyty's internal signals to its handlers",
|
||||
"text": "handle SIG37 SIGSEGV SIGBUS SIGILL nostop noprint pass",
|
||||
"ignoreFailures": false
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
+30
-3
@@ -254,7 +254,7 @@ foreach(shader_name IN LISTS gpu_tiler_shader_names)
|
||||
add_custom_command(
|
||||
OUTPUT "${shader_header}"
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory "${gpu_tiler_generated_dir}"
|
||||
COMMAND "${KYTY_GLSLANG_VALIDATOR}" -V --target-env vulkan1.0 -Os
|
||||
COMMAND "${KYTY_GLSLANG_VALIDATOR}" -V --target-env vulkan1.3 -Os
|
||||
"-I${gpu_tiler_shader_dir}" -o "${shader_spv}" "${shader_source}"
|
||||
COMMAND ${CMAKE_COMMAND} -DINPUT=${shader_spv} -DOUTPUT=${shader_header}
|
||||
-DSYMBOL=${shader_symbol} -P "${KYTY_SOURCE_DIR}/embed_spirv.cmake"
|
||||
@@ -263,6 +263,22 @@ foreach(shader_name IN LISTS gpu_tiler_shader_names)
|
||||
)
|
||||
list(APPEND gpu_tiler_shader_headers "${shader_header}")
|
||||
endforeach()
|
||||
|
||||
set(fault_buffer_shader_source "${gpu_tiler_shader_dir}/fault_buffer_process.comp")
|
||||
set(fault_buffer_shader_spv "${gpu_tiler_generated_dir}/fault_buffer_process.spv")
|
||||
set(fault_buffer_shader_header "${gpu_tiler_generated_dir}/fault_buffer_process_spv.h")
|
||||
add_custom_command(
|
||||
OUTPUT "${fault_buffer_shader_header}"
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory "${gpu_tiler_generated_dir}"
|
||||
COMMAND "${KYTY_GLSLANG_VALIDATOR}" -V --target-env vulkan1.3 -Os
|
||||
-DCACHING_PAGEBITS=14 -DMAX_PAGE_FAULTS=1024
|
||||
-o "${fault_buffer_shader_spv}" "${fault_buffer_shader_source}"
|
||||
COMMAND ${CMAKE_COMMAND} -DINPUT=${fault_buffer_shader_spv} -DOUTPUT=${fault_buffer_shader_header}
|
||||
-DSYMBOL=FAULT_BUFFER_PROCESS_SPV -P "${KYTY_SOURCE_DIR}/embed_spirv.cmake"
|
||||
DEPENDS "${fault_buffer_shader_source}"
|
||||
VERBATIM
|
||||
)
|
||||
list(APPEND kyty_emulator_src "${fault_buffer_shader_header}")
|
||||
list(APPEND kyty_emulator_src ${gpu_tiler_shader_headers})
|
||||
|
||||
set(gpu_blit_generated_dir "${PROJECT_BINARY_DIR}/gpu_blit_shaders")
|
||||
@@ -278,7 +294,7 @@ foreach(shader_source IN LISTS gpu_blit_shader_sources)
|
||||
add_custom_command(
|
||||
OUTPUT "${shader_header}"
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory "${gpu_blit_generated_dir}"
|
||||
COMMAND "${KYTY_GLSLANG_VALIDATOR}" -V --target-env vulkan1.0 -Os
|
||||
COMMAND "${KYTY_GLSLANG_VALIDATOR}" -V --target-env vulkan1.3 -Os
|
||||
"-I${gpu_tiler_shader_dir}" -o "${shader_spv}" "${shader_source}"
|
||||
COMMAND ${CMAKE_COMMAND} -DINPUT=${shader_spv} -DOUTPUT=${shader_header}
|
||||
-DSYMBOL=${shader_symbol} -P "${KYTY_SOURCE_DIR}/embed_spirv.cmake"
|
||||
@@ -430,6 +446,7 @@ add_executable(resource_tracking_tests EXCLUDE_FROM_ALL
|
||||
"${KYTY_TESTS_DIR}/ResourceTrackingTests.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/guest_gpu/gpu_format.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/host_gpu/hostMemory.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/shader/shaderPixelParameter.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/passes/SrtWalker.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/passes/ResourceTracking.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/passes/ResourceMaterialization.cpp"
|
||||
@@ -483,7 +500,7 @@ set(gpu_test_ms_depth_header
|
||||
add_custom_command(
|
||||
OUTPUT "${gpu_test_ms_depth_header}"
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory "${gpu_test_generated_dir}"
|
||||
COMMAND "${KYTY_GLSLANG_VALIDATOR}" -V --target-env vulkan1.0 -Os
|
||||
COMMAND "${KYTY_GLSLANG_VALIDATOR}" -V --target-env vulkan1.3 -Os
|
||||
-o "${gpu_test_ms_depth_spv}" "${gpu_test_ms_depth_source}"
|
||||
COMMAND ${CMAKE_COMMAND} -DINPUT=${gpu_test_ms_depth_spv}
|
||||
-DOUTPUT=${gpu_test_ms_depth_header} -DSYMBOL=GPU_TEST_MS_DEPTH_SPV
|
||||
@@ -507,6 +524,16 @@ if(NOT KYTY_CLANG_CL)
|
||||
endif()
|
||||
|
||||
if(BUILD_TESTING)
|
||||
add_test(NAME emulator_user_name_cli
|
||||
COMMAND $<TARGET_FILE:kyty_emulator> --help --user-name 1234567890123456)
|
||||
add_test(NAME emulator_user_name_too_long
|
||||
COMMAND $<TARGET_FILE:kyty_emulator> --help --user-name 12345678901234567)
|
||||
set_tests_properties(emulator_user_name_too_long PROPERTIES WILL_FAIL TRUE)
|
||||
add_test(NAME emulator_present_mode_cli
|
||||
COMMAND $<TARGET_FILE:kyty_emulator> --help --present-mode Immediate)
|
||||
add_test(NAME emulator_present_mode_invalid
|
||||
COMMAND $<TARGET_FILE:kyty_emulator> --help --present-mode Invalid)
|
||||
set_tests_properties(emulator_present_mode_invalid PROPERTIES WILL_FAIL TRUE)
|
||||
add_test(NAME ime_dialog COMMAND $<TARGET_FILE:ime_dialog_tests>)
|
||||
add_test(NAME shader_cfg COMMAND $<TARGET_FILE:shader_cfg_tests>)
|
||||
add_test(NAME scalar_provenance COMMAND $<TARGET_FILE:scalar_provenance_tests>)
|
||||
|
||||
@@ -220,17 +220,23 @@ cmake --install _Build/macos --prefix _Build/macos/install
|
||||
```
|
||||
|
||||
The build re-signs `kyty_emulator` with the JIT entitlements it needs to execute translated
|
||||
guest code; no manual signing step is required.
|
||||
guest code; no manual signing step is required. When the launcher is built, the install
|
||||
also produces `_Build/macos/install/KytyPS5.app` — double-click to launch the GUI.
|
||||
A flat `kyty_emulator` is kept for CLI usage.
|
||||
|
||||
Vulkan comes from MoltenVK. Download `MoltenVK-macos.tar` from the
|
||||
[MoltenVK releases](https://github.com/KhronosGroup/MoltenVK/releases), then copy
|
||||
`MoltenVK/dynamic/dylib/macOS/libMoltenVK.dylib` next to `kyty_emulator` and ad-hoc sign it:
|
||||
`MoltenVK/dynamic/dylib/macOS/libMoltenVK.dylib` next to the flat `kyty_emulator`
|
||||
(and, for the bundle, into `KytyPS5.app/Contents/Frameworks/`) and ad-hoc sign it:
|
||||
|
||||
```bash
|
||||
codesign --force --sign - _Build/macos/install/libMoltenVK.dylib
|
||||
# For the bundle (if present):
|
||||
codesign --force --sign - _Build/macos/install/KytyPS5.app/Contents/Frameworks/libMoltenVK.dylib
|
||||
codesign --force --sign - _Build/macos/install/KytyPS5.app
|
||||
```
|
||||
|
||||
Release archives already include a signed `libMoltenVK.dylib`.
|
||||
Release archives already include a signed `libMoltenVK.dylib` (both flat and inside the bundle).
|
||||
|
||||
### Regression tests
|
||||
|
||||
@@ -274,6 +280,10 @@ To use the graphical launcher:
|
||||
./_Build/linux/install/launcher
|
||||
```
|
||||
|
||||
```bash
|
||||
open _Build/macos/install/KytyPS5.app # or double-click in Finder
|
||||
```
|
||||
|
||||
On first launch, add one or more game folders in the global settings. The launcher searches those
|
||||
folders recursively for game directories containing `eboot.bin`. Select a detected game and run it
|
||||
from the game list.
|
||||
@@ -288,12 +298,17 @@ The emulator can also be started directly with a legally obtained game directory
|
||||
./_Build/linux/install/kyty_emulator --game "/games/ExampleGame"
|
||||
```
|
||||
|
||||
On macOS, point SDL at the MoltenVK library explicitly; the hardened runtime prevents it from
|
||||
being picked up from the executable's directory:
|
||||
On macOS, the adjacent flat or app-bundled `libMoltenVK.dylib` is found automatically; no
|
||||
environment variable is required:
|
||||
|
||||
```bash
|
||||
cd _Build/macos/install
|
||||
SDL_VULKAN_LIBRARY="$PWD/libMoltenVK.dylib" ./kyty_emulator --game "/games/ExampleGame"
|
||||
./_Build/macos/install/kyty_emulator --game "/games/ExampleGame"
|
||||
```
|
||||
|
||||
To override the Vulkan loader, set `SDL_VULKAN_LIBRARY`:
|
||||
|
||||
```bash
|
||||
SDL_VULKAN_LIBRARY=/path/to/libMoltenVK.dylib ./kyty_emulator --game "/games/ExampleGame"
|
||||
```
|
||||
|
||||
Run `kyty_emulator --help` to see the available graphics, logging, validation, profiling, and
|
||||
|
||||
@@ -21,6 +21,8 @@ void Shutdown() {
|
||||
|
||||
void Load(const ConfigOptions& cfg) {
|
||||
EXIT_IF(g_config == nullptr);
|
||||
EXIT_IF(cfg.user_name.empty() || cfg.user_name.size() > MAX_USER_NAME_LENGTH);
|
||||
EXIT_IF(!IsConfiguredUserIdValid(cfg.user_id));
|
||||
|
||||
*g_config = cfg;
|
||||
}
|
||||
@@ -33,6 +35,22 @@ uint32_t GetScreenHeight() {
|
||||
return g_config->screen_height;
|
||||
}
|
||||
|
||||
const std::string& GetUserName() {
|
||||
return g_config->user_name;
|
||||
}
|
||||
|
||||
int32_t GetUserId() {
|
||||
return g_config->user_id;
|
||||
}
|
||||
|
||||
PresentMode GetPresentMode() {
|
||||
return g_config->present_mode;
|
||||
}
|
||||
|
||||
int32_t GetGpuIndex() {
|
||||
return g_config->gpu_index;
|
||||
}
|
||||
|
||||
bool FullscreenEnabled() {
|
||||
return g_config->fullscreen_enabled;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
#include "common/common.h"
|
||||
|
||||
#include <cstddef>
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
@@ -26,14 +27,28 @@ enum class ProfilerDirection { None, Network };
|
||||
|
||||
enum class OutputDirection { Silent, Console, File };
|
||||
|
||||
enum class PresentMode { Fifo, Mailbox, Immediate };
|
||||
|
||||
using Keymap = std::vector<std::string>;
|
||||
|
||||
constexpr uint32_t DEFAULT_CONSOLE_LANGUAGE = 1;
|
||||
constexpr uint32_t MAX_CONSOLE_LANGUAGE = 29;
|
||||
constexpr std::size_t MAX_USER_NAME_LENGTH = 16;
|
||||
constexpr int32_t DEFAULT_USER_ID = 1000;
|
||||
|
||||
constexpr bool IsConfiguredUserIdValid(int32_t user_id) {
|
||||
constexpr int32_t USER_ID_EVERYONE = 0xfe;
|
||||
constexpr int32_t USER_ID_SYSTEM = 0xff;
|
||||
return user_id >= 0 && user_id != USER_ID_EVERYONE && user_id != USER_ID_SYSTEM;
|
||||
}
|
||||
|
||||
struct ConfigOptions {
|
||||
uint32_t screen_width = 1280;
|
||||
uint32_t screen_height = 720;
|
||||
std::string user_name = "Kyty";
|
||||
int32_t user_id = DEFAULT_USER_ID;
|
||||
PresentMode present_mode = PresentMode::Fifo;
|
||||
int32_t gpu_index = -1;
|
||||
bool fullscreen_enabled = false;
|
||||
uint32_t vblank_frequency = 60;
|
||||
uint32_t console_language = DEFAULT_CONSOLE_LANGUAGE;
|
||||
@@ -45,7 +60,7 @@ struct ConfigOptions {
|
||||
bool command_buffer_dump_enabled = false;
|
||||
std::filesystem::path command_buffer_dump_folder = "_Buffers";
|
||||
bool graphics_debug_dump_enabled = false;
|
||||
OutputDirection printf_direction = OutputDirection::Console;
|
||||
OutputDirection printf_direction = OutputDirection::Silent;
|
||||
std::filesystem::path printf_output_file = "_kyty.txt";
|
||||
ProfilerDirection profiler_direction = ProfilerDirection::None;
|
||||
bool spirv_debug_printf_enabled = false;
|
||||
@@ -63,6 +78,10 @@ void Load(const ConfigOptions& cfg);
|
||||
|
||||
uint32_t GetScreenWidth();
|
||||
uint32_t GetScreenHeight();
|
||||
const std::string& GetUserName();
|
||||
int32_t GetUserId();
|
||||
PresentMode GetPresentMode();
|
||||
int32_t GetGpuIndex();
|
||||
bool FullscreenEnabled();
|
||||
uint32_t GetVblankFrequency();
|
||||
uint32_t GetConsoleLanguage();
|
||||
|
||||
+3
-6
@@ -25,9 +25,6 @@
|
||||
#ifdef CopyFile
|
||||
#undef CopyFile
|
||||
#endif
|
||||
#ifdef MoveFile
|
||||
#undef MoveFile
|
||||
#endif
|
||||
#endif
|
||||
|
||||
namespace Common {
|
||||
@@ -544,14 +541,14 @@ bool File::CopyFile(const std::filesystem::path& src,
|
||||
return SysFileCopyFile(src, dst);
|
||||
}
|
||||
|
||||
bool File::MoveFile(const std::filesystem::path& src,
|
||||
const std::filesystem::path& dst) // @suppress("Member declaration not found")
|
||||
bool File::RenameFile(const std::filesystem::path& src,
|
||||
const std::filesystem::path& dst) // @suppress("Member declaration not found")
|
||||
{
|
||||
if (IsFileExisting(dst)) {
|
||||
DeleteFile(dst); // @suppress("Invalid arguments")
|
||||
}
|
||||
|
||||
return SysFileMoveFile(src, dst);
|
||||
return SysFileRenameFile(src, dst);
|
||||
}
|
||||
|
||||
void File::RemoveReadonly(const std::filesystem::path& name) {
|
||||
|
||||
+1
-4
@@ -20,9 +20,6 @@
|
||||
#ifdef CopyFile
|
||||
#undef CopyFile
|
||||
#endif
|
||||
#ifdef MoveFile
|
||||
#undef MoveFile
|
||||
#endif
|
||||
#endif
|
||||
|
||||
namespace Common {
|
||||
@@ -107,7 +104,7 @@ public:
|
||||
static std::vector<FindInfo> FindFiles(const std::filesystem::path& path);
|
||||
static std::vector<DirEntry> GetDirEntries(const std::filesystem::path& path);
|
||||
static bool CopyFile(const std::filesystem::path& src, const std::filesystem::path& dst);
|
||||
static bool MoveFile(const std::filesystem::path& src, const std::filesystem::path& dst);
|
||||
static bool RenameFile(const std::filesystem::path& src, const std::filesystem::path& dst);
|
||||
static void RemoveReadonly(const std::filesystem::path& name);
|
||||
|
||||
KYTY_CLASS_NO_COPY(File);
|
||||
|
||||
@@ -80,7 +80,7 @@ void SysFileFindFiles(const std::filesystem::path& path, std::vector<sys_file_fi
|
||||
// NOLINTNEXTLINE(google-runtime-references)
|
||||
void SysFileGetDents(const std::filesystem::path& path, std::vector<sys_dir_entry_t>& out);
|
||||
bool SysFileCopyFile(const std::filesystem::path& src, const std::filesystem::path& dst);
|
||||
bool SysFileMoveFile(const std::filesystem::path& src, const std::filesystem::path& dst);
|
||||
bool SysFileRenameFile(const std::filesystem::path& src, const std::filesystem::path& dst);
|
||||
void SysFileRemoveReadonly(const std::filesystem::path& name);
|
||||
|
||||
#endif /* KYTY_COMMON_PLATFORM_SYSFILEIO_H_ */
|
||||
@@ -693,7 +693,7 @@ bool SysFileCopyFile(const std::filesystem::path& src, const std::filesystem::pa
|
||||
!error;
|
||||
}
|
||||
|
||||
bool SysFileMoveFile(const std::filesystem::path& src, const std::filesystem::path& dst) {
|
||||
bool SysFileRenameFile(const std::filesystem::path& src, const std::filesystem::path& dst) {
|
||||
auto real_src = get_internal_name(src);
|
||||
auto real_dst = get_internal_name(dst);
|
||||
|
||||
|
||||
@@ -565,7 +565,7 @@ bool SysFileCopyFile(const std::filesystem::path& src, const std::filesystem::pa
|
||||
return CopyFileW(src_wide.c_str(), dst_wide.c_str(), FALSE) != 0;
|
||||
}
|
||||
|
||||
bool SysFileMoveFile(const std::filesystem::path& src, const std::filesystem::path& dst) {
|
||||
bool SysFileRenameFile(const std::filesystem::path& src, const std::filesystem::path& dst) {
|
||||
auto src_wide = src.wstring();
|
||||
auto dst_wide = dst.wstring();
|
||||
return MoveFileW(src_wide.c_str(), dst_wide.c_str()) != 0;
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
#ifndef EMULATOR_SRC_COMMON_SLOTVECTOR_H_
|
||||
#define EMULATOR_SRC_COMMON_SLOTVECTOR_H_
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/abi.h"
|
||||
|
||||
#include <compare>
|
||||
#include <cstdint>
|
||||
#include <deque>
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
#include <optional>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace Common {
|
||||
|
||||
struct SlotId {
|
||||
static constexpr uint32_t INVALID_INDEX = std::numeric_limits<uint32_t>::max();
|
||||
|
||||
constexpr SlotId() noexcept = default;
|
||||
constexpr SlotId(uint32_t value) noexcept: index(value), generation(1) {}
|
||||
constexpr SlotId(uint32_t value, uint32_t slot_generation) noexcept
|
||||
: index(value), generation(slot_generation) {}
|
||||
|
||||
[[nodiscard]] constexpr explicit operator bool() const noexcept { return index != INVALID_INDEX; }
|
||||
constexpr auto operator<=>(const SlotId&) const noexcept = default;
|
||||
|
||||
uint32_t index = INVALID_INDEX;
|
||||
uint32_t generation = 0;
|
||||
};
|
||||
|
||||
// Stable-address slot storage for cache resources that are intentionally non-movable.
|
||||
template <typename T>
|
||||
class SlotVector {
|
||||
public:
|
||||
SlotVector() = default;
|
||||
KYTY_CLASS_NO_COPY(SlotVector);
|
||||
|
||||
[[nodiscard]] T& operator[](SlotId id) noexcept {
|
||||
EXIT_IF(!is_allocated(id));
|
||||
return *m_values[id.index].value;
|
||||
}
|
||||
|
||||
[[nodiscard]] const T& operator[](SlotId id) const noexcept {
|
||||
EXIT_IF(!is_allocated(id));
|
||||
return *m_values[id.index].value;
|
||||
}
|
||||
|
||||
[[nodiscard]] T* try_get(SlotId id) noexcept {
|
||||
return is_allocated(id) ? &*m_values[id.index].value : nullptr;
|
||||
}
|
||||
|
||||
[[nodiscard]] const T* try_get(SlotId id) const noexcept {
|
||||
return is_allocated(id) ? &*m_values[id.index].value : nullptr;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool is_allocated(SlotId id) const noexcept {
|
||||
return id && id.index < m_values.size() &&
|
||||
m_values[id.index].generation == id.generation && m_values[id.index].value.has_value();
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
[[nodiscard]] SlotId insert(Args&&... args) {
|
||||
uint32_t index = 0;
|
||||
if (m_free_list.empty()) {
|
||||
index = static_cast<uint32_t>(m_values.size());
|
||||
m_values.emplace_back();
|
||||
m_values.back().value.emplace(std::forward<Args>(args)...);
|
||||
} else {
|
||||
index = m_free_list.back();
|
||||
m_free_list.pop_back();
|
||||
EXIT_IF(m_values[index].value.has_value());
|
||||
m_values[index].value.emplace(std::forward<Args>(args)...);
|
||||
}
|
||||
++m_size;
|
||||
return SlotId {index, m_values[index].generation};
|
||||
}
|
||||
|
||||
void erase(SlotId id) noexcept {
|
||||
EXIT_IF(!is_allocated(id));
|
||||
auto& slot = m_values[id.index];
|
||||
slot.value.reset();
|
||||
if (++slot.generation == 0) {
|
||||
slot.generation = 1;
|
||||
}
|
||||
m_free_list.push_back(id.index);
|
||||
--m_size;
|
||||
}
|
||||
|
||||
[[nodiscard]] size_t size() const noexcept { return m_size; }
|
||||
[[nodiscard]] size_t capacity() const noexcept { return m_values.size(); }
|
||||
|
||||
template <typename F>
|
||||
void ForEach(F&& fn) {
|
||||
for (uint32_t index = 0; index < m_values.size(); ++index) {
|
||||
if (m_values[index].value) {
|
||||
fn(SlotId {index, m_values[index].generation}, *m_values[index].value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
void ForEach(F&& fn) const {
|
||||
for (uint32_t index = 0; index < m_values.size(); ++index) {
|
||||
if (m_values[index].value) {
|
||||
fn(SlotId {index, m_values[index].generation}, *m_values[index].value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
struct Slot {
|
||||
std::optional<T> value;
|
||||
uint32_t generation = 1;
|
||||
};
|
||||
|
||||
std::deque<Slot> m_values;
|
||||
std::vector<uint32_t> m_free_list;
|
||||
size_t m_size = 0;
|
||||
};
|
||||
|
||||
} // namespace Common
|
||||
|
||||
template <>
|
||||
struct std::hash<Common::SlotId> {
|
||||
[[nodiscard]] size_t operator()(Common::SlotId id) const noexcept {
|
||||
return std::hash<uint64_t> {}((static_cast<uint64_t>(id.generation) << 32u) | id.index);
|
||||
}
|
||||
};
|
||||
|
||||
#endif // EMULATOR_SRC_COMMON_SLOTVECTOR_H_
|
||||
+25
-2
@@ -15,6 +15,7 @@
|
||||
#include "kernel/fileSystem.h"
|
||||
#include "kernel/memory.h"
|
||||
#include "kernel/pthread.h"
|
||||
#include "kytyGitVersion.h"
|
||||
#include "libs/agc.h"
|
||||
#include "libs/audio.h"
|
||||
#include "libs/controller.h"
|
||||
@@ -26,13 +27,31 @@
|
||||
|
||||
#include <cstdlib>
|
||||
#include <filesystem>
|
||||
#include <thread>
|
||||
|
||||
namespace Emulator {
|
||||
|
||||
static void PrintSystemInfo() {
|
||||
Common::SystemInfo info = Common::GetSystemInfo();
|
||||
const Common::SystemInfo info = Common::GetSystemInfo();
|
||||
|
||||
LOGF("ProcessorName = %s\n", info.ProcessorName.c_str());
|
||||
#if defined(__APPLE__)
|
||||
static constexpr auto platform_name = "macOS";
|
||||
#elif KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
static constexpr auto platform_name = "Windows";
|
||||
#elif KYTY_PLATFORM == KYTY_PLATFORM_LINUX
|
||||
static constexpr auto platform_name = "Linux";
|
||||
#else
|
||||
static constexpr auto platform_name = "Unknown";
|
||||
#endif
|
||||
|
||||
LOGF("Build\n"
|
||||
" version: %s\n\n"
|
||||
"Host\n"
|
||||
" os: %s\n"
|
||||
" cpu: %s\n"
|
||||
" threads: %u\n\n",
|
||||
KYTY_BUILD_LABEL, platform_name, info.ProcessorName.c_str(),
|
||||
std::thread::hardware_concurrency());
|
||||
}
|
||||
|
||||
static void KytyClose() {
|
||||
@@ -181,6 +200,10 @@ void Run(const RunOptions& options) {
|
||||
int ok = atexit(KytyClose);
|
||||
EXIT_NOT_IMPLEMENTED(ok != 0);
|
||||
|
||||
// Guest threads are still running, so skip KytyClose() and only flush emergency state.
|
||||
ok = at_quick_exit(Common::Subsystems::EmergencyShutdownActive);
|
||||
EXIT_NOT_IMPLEMENTED(ok != 0);
|
||||
|
||||
Libs::LibKernel::FileSystem::Mount(options.app0_dir, "/app0");
|
||||
Libs::LibKernel::FileSystem::Mount(options.app0_dir, "/hostapp");
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
set(KYTY_GIT_VERSION "unknown")
|
||||
set(KYTY_GIT_HASH "unknown")
|
||||
set(KYTY_GIT_REVISION "unknown")
|
||||
if(GIT_EXECUTABLE)
|
||||
execute_process(
|
||||
COMMAND "${GIT_EXECUTABLE}" describe --tags --always --dirty
|
||||
@@ -14,16 +15,18 @@ if(GIT_EXECUTABLE)
|
||||
endif()
|
||||
|
||||
execute_process(
|
||||
COMMAND "${GIT_EXECUTABLE}" rev-parse --short=7 HEAD
|
||||
COMMAND "${GIT_EXECUTABLE}" rev-parse HEAD
|
||||
WORKING_DIRECTORY "${GIT_WORKING_DIRECTORY}"
|
||||
OUTPUT_VARIABLE KYTY_GIT_HASH
|
||||
OUTPUT_VARIABLE KYTY_GIT_REVISION
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
RESULT_VARIABLE GIT_HASH_RESULT
|
||||
ERROR_QUIET
|
||||
)
|
||||
if(NOT GIT_HASH_RESULT EQUAL 0)
|
||||
set(KYTY_GIT_HASH "unknown")
|
||||
set(KYTY_GIT_REVISION "unknown")
|
||||
else()
|
||||
string(SUBSTRING "${KYTY_GIT_REVISION}" 0 7 KYTY_GIT_HASH)
|
||||
execute_process(
|
||||
COMMAND "${GIT_EXECUTABLE}" diff-index --quiet HEAD --
|
||||
WORKING_DIRECTORY "${GIT_WORKING_DIRECTORY}"
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "graphics/host_gpu/renderer/renderContext.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <span>
|
||||
#include <vector>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
@@ -30,10 +31,9 @@ private:
|
||||
friend class CommandProcessor;
|
||||
|
||||
struct BufferCursor {
|
||||
uint32_t* next_packet = nullptr;
|
||||
uint32_t remaining_dw = 0;
|
||||
uint32_t total_dw = 0;
|
||||
uint32_t deferred_advance_dw = 0;
|
||||
std::span<const uint32_t> commands;
|
||||
uint32_t offset_dw = 0;
|
||||
uint32_t deferred_advance_dw = 0;
|
||||
};
|
||||
|
||||
std::vector<BufferCursor> m_buffer_stack;
|
||||
@@ -50,7 +50,8 @@ public:
|
||||
int64_t flip_arg = 0;
|
||||
};
|
||||
|
||||
explicit CommandProcessor(RenderContext& renderer): m_renderer(renderer) {}
|
||||
CommandProcessor(RenderContext& renderer, int interrupt_event_id)
|
||||
: m_renderer(renderer), m_interrupt_event_id(interrupt_event_id) {}
|
||||
~CommandProcessor() = default;
|
||||
|
||||
KYTY_CLASS_NO_COPY(CommandProcessor);
|
||||
@@ -103,7 +104,7 @@ public:
|
||||
uint32_t mode);
|
||||
void DispatchIndirect(uint32_t data_offset, uint32_t mode);
|
||||
void WaitFlipDone(uint32_t video_out_handle, uint32_t display_buffer_index);
|
||||
void TriggerEvent(uint32_t event_type, uint32_t event_index);
|
||||
void TriggerEvent(uint32_t event_type, uint32_t event_index, uint64_t event_address = 0);
|
||||
|
||||
void SetUserDataMarker(HW::UserSgprType type) { m_user_data_marker = type; }
|
||||
[[nodiscard]] HW::UserSgprType GetUserDataMarker() const { return m_user_data_marker; }
|
||||
@@ -131,13 +132,14 @@ public:
|
||||
const volatile void* address, uint32_t count_in_dwords);
|
||||
[[nodiscard]] bool ShouldSkipPredicatedPackets() const { return m_predicate_skip; }
|
||||
|
||||
Pm4ProcessResult Process(Pm4Execution& execution, uint32_t* buffer, uint32_t size_dw);
|
||||
void ProcessIndirectBuffer(uint32_t* buffer, uint32_t size_dw);
|
||||
Pm4ProcessResult Process(Pm4Execution& execution, std::span<const uint32_t> commands);
|
||||
void ProcessIndirectBuffer(std::span<const uint32_t> commands);
|
||||
|
||||
void SetFlip(const FlipInfo& flip) { m_flip = flip; }
|
||||
|
||||
[[nodiscard]] uint64_t GetSubmitId() const { return m_submit_id; }
|
||||
void SetSubmitId(uint64_t submit_id) { m_submit_id = submit_id; }
|
||||
[[nodiscard]] bool IsAsyncComputeQueue() const { return m_interrupt_event_id >= 0x20; }
|
||||
|
||||
private:
|
||||
template <typename T>
|
||||
@@ -151,10 +153,10 @@ private:
|
||||
uint32_t render_target_slice_offset, uint32_t first_vertex,
|
||||
uint32_t first_instance);
|
||||
|
||||
CommandScheduler& GetScheduler() const { return m_renderer.GetCommandScheduler(); }
|
||||
RenderCommandBuffer& CurrentBuffer() { return GetScheduler().Current(); }
|
||||
void CheckBuffer() const { GetScheduler().CheckActive(); }
|
||||
GpuResourceManager& GetGpuResources() const { return m_renderer.GetGpuResources(); }
|
||||
CommandScheduler& GetScheduler() const { return m_renderer.GetCommandScheduler(); }
|
||||
CommandBuffer& CurrentBuffer() { return GetScheduler().Current(); }
|
||||
void CheckBuffer() const { GetScheduler().CheckActive(); }
|
||||
GpuResourceManager& GetGpuResources() const { return m_renderer.GetGpuResources(); }
|
||||
|
||||
RenderContext& m_renderer;
|
||||
HW::Context m_ctx;
|
||||
@@ -177,9 +179,11 @@ private:
|
||||
|
||||
uint32_t m_const_ram[0x3000] = {0};
|
||||
|
||||
FlipInfo m_flip;
|
||||
uint64_t m_submit_id = 0;
|
||||
bool m_predicate_skip = false;
|
||||
FlipInfo m_flip;
|
||||
const int m_interrupt_event_id;
|
||||
uint64_t m_submit_id = 0;
|
||||
uint64_t m_synthetic_occlusion_counter = 0;
|
||||
bool m_predicate_skip = false;
|
||||
};
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
@@ -23,9 +23,8 @@ constexpr auto MakeContextDispatchTable() {
|
||||
g_hw_ctx_func[Pm4::DB_STENCIL_CLEAR] = HwCtxSetStencilClear;
|
||||
g_hw_ctx_func[Pm4::DB_DEPTH_CLEAR] = HwCtxSetDepthClear;
|
||||
g_hw_ctx_func[Pm4::PA_SC_SCREEN_SCISSOR_TL] = HwCtxSetScreenScissor;
|
||||
g_hw_ctx_func[Pm4::DB_Z_INFO] = HwCtxSetDepthRenderTarget;
|
||||
g_hw_ctx_func[Pm4::DB_STENCIL_INFO] = HwCtxSetStencilInfo;
|
||||
g_hw_ctx_func[Pm4::DB_HTILE_SURFACE] = HwCtxSetDepthHtileSurface;
|
||||
g_hw_ctx_func[Pm4::DB_SHADING_RATE_ENCODING] = HwCtxSetDepthShadingRateEncoding;
|
||||
g_hw_ctx_func[Pm4::PA_SU_HARDWARE_SCREEN_OFFSET] = HwCtxSetHardwareScreenOffset;
|
||||
g_hw_ctx_func[Pm4::PA_SC_WINDOW_OFFSET] = HwCtxSetWindowOffset;
|
||||
g_hw_ctx_func[Pm4::PA_SC_WINDOW_SCISSOR_TL] = HwCtxSetWindowScissor;
|
||||
@@ -75,7 +74,6 @@ constexpr auto MakeContextDispatchTable() {
|
||||
g_hw_ctx_func[Pm4::PA_SC_BINNER_CNTL_0] = HwCtxSetPaScExtendedControl;
|
||||
g_hw_ctx_func[Pm4::PA_SC_BINNER_CNTL_1] = HwCtxSetPaScExtendedControl;
|
||||
g_hw_ctx_func[Pm4::PA_SC_CONSERVATIVE_RASTERIZATION_CNTL] = HwCtxSetPaScExtendedControl;
|
||||
g_hw_ctx_func[Pm4::PA_SC_NGG_MODE_CNTL] = HwCtxSetPaScExtendedControl;
|
||||
g_hw_ctx_func[Pm4::DB_ALPHA_TO_MASK] = HwCtxSetAlphaToMask;
|
||||
g_hw_ctx_func[Pm4::VGT_DRAW_PAYLOAD_CNTL] = HwCtxSetDrawPayloadControl;
|
||||
g_hw_ctx_func[Pm4::VGT_PRIMITIVEID_RESET] = HwCtxSetPrimitiveIdReset;
|
||||
@@ -83,7 +81,6 @@ constexpr auto MakeContextDispatchTable() {
|
||||
g_hw_ctx_func[Pm4::VGT_SHADER_STAGES_EN] = HwCtxSetShaderStages;
|
||||
|
||||
for (uint32_t slot = 0; slot < 8; slot++) {
|
||||
g_hw_ctx_func[Pm4::CB_COLOR0_BASE + slot * 15] = HwCtxSetRenderTarget;
|
||||
g_hw_ctx_func[Pm4::CB_COLOR0_INFO + slot * 15] = HwCtxSetColorInfo;
|
||||
|
||||
g_hw_ctx_func[Pm4::CB_BLEND0_CONTROL + slot * 1] = HwCtxSetBlendControl;
|
||||
@@ -116,25 +113,21 @@ constexpr auto MakeShaderDispatchTable() {
|
||||
}
|
||||
|
||||
for (uint32_t slot = 0; slot < 4; slot++) {
|
||||
g_hw_sh_func[Pm4::SPI_SHADER_USER_ACCUM_PS_0 + slot] = HwShSetPsUserAccumSgpr;
|
||||
g_hw_sh_func[Pm4::SPI_SHADER_USER_ACCUM_PS_0 + slot] = HwShIgnoreUserAccumulator;
|
||||
}
|
||||
|
||||
for (uint32_t slot = 0; slot < 4; slot++) {
|
||||
g_hw_sh_func[Pm4::SPI_SHADER_USER_ACCUM_ESGS_0 + slot] = HwShSetGsUserAccumSgpr;
|
||||
g_hw_sh_func[Pm4::SPI_SHADER_USER_ACCUM_ESGS_0 + slot] = HwShIgnoreUserAccumulator;
|
||||
}
|
||||
for (uint32_t slot = 0; slot < 4; slot++) {
|
||||
g_hw_sh_func[Pm4::SPI_SHADER_USER_ACCUM_LSHS_0 + slot] = HwShSetHsUserAccumSgpr;
|
||||
g_hw_sh_func[Pm4::SPI_SHADER_USER_ACCUM_LSHS_0 + slot] = HwShIgnoreUserAccumulator;
|
||||
}
|
||||
|
||||
for (uint32_t slot = 0; Pm4::COMPUTE_USER_ACCUM_0 + slot < Pm4::COMPUTE_USER_DATA_0; slot++) {
|
||||
g_hw_sh_func[Pm4::COMPUTE_USER_ACCUM_0 + slot] = HwShSetCsUserAccumSgpr;
|
||||
for (uint32_t slot = 0; slot < 4; slot++) {
|
||||
g_hw_sh_func[Pm4::COMPUTE_USER_ACCUM_0 + slot] = HwShIgnoreUserAccumulator;
|
||||
}
|
||||
g_hw_sh_func[Pm4::COMPUTE_PGM_LO] = HwShSetCsRegisters;
|
||||
g_hw_sh_func[Pm4::COMPUTE_PGM_HI] = HwShSetCsRegisters;
|
||||
g_hw_sh_func[Pm4::COMPUTE_TBA_LO] = HwShSetCsRegisters;
|
||||
g_hw_sh_func[Pm4::COMPUTE_TBA_HI] = HwShSetCsRegisters;
|
||||
g_hw_sh_func[Pm4::COMPUTE_TMA_LO] = HwShSetCsRegisters;
|
||||
g_hw_sh_func[Pm4::COMPUTE_TMA_HI] = HwShSetCsRegisters;
|
||||
g_hw_sh_func[Pm4::COMPUTE_PGM_RSRC1] = HwShSetCsRegisters;
|
||||
g_hw_sh_func[Pm4::COMPUTE_PGM_RSRC2] = HwShSetCsRegisters;
|
||||
g_hw_sh_func[Pm4::COMPUTE_START_X] = HwShSetCsRegisters;
|
||||
@@ -144,30 +137,16 @@ constexpr auto MakeShaderDispatchTable() {
|
||||
g_hw_sh_func[Pm4::COMPUTE_NUM_THREAD_Y] = HwShSetCsRegisters;
|
||||
g_hw_sh_func[Pm4::COMPUTE_NUM_THREAD_Z] = HwShSetCsRegisters;
|
||||
g_hw_sh_func[Pm4::COMPUTE_RESOURCE_LIMITS] = HwShSetCsRegisters;
|
||||
g_hw_sh_func[Pm4::COMPUTE_DESTINATION_EN_SE0] = HwShSetCsRegisters;
|
||||
g_hw_sh_func[Pm4::COMPUTE_DESTINATION_EN_SE1] = HwShSetCsRegisters;
|
||||
g_hw_sh_func[Pm4::COMPUTE_TMPRING_SIZE] = HwShSetCsRegisters;
|
||||
g_hw_sh_func[Pm4::COMPUTE_DESTINATION_EN_SE2] = HwShSetCsRegisters;
|
||||
g_hw_sh_func[Pm4::COMPUTE_DESTINATION_EN_SE3] = HwShSetCsRegisters;
|
||||
g_hw_sh_func[Pm4::COMPUTE_PGM_RSRC3] = HwShSetCsRegisters;
|
||||
g_hw_sh_func[Pm4::COMPUTE_SHADER_CHKSUM] = HwShSetCsRegisters;
|
||||
g_hw_sh_func[Pm4::COMPUTE_DISPATCH_TUNNEL] = HwShSetCsRegisters;
|
||||
g_hw_sh_func[Pm4::SPI_SHADER_TBA_LO_PS] = HwShIgnoreRegisters;
|
||||
g_hw_sh_func[Pm4::SPI_SHADER_PGM_RSRC3_PS] = HwShIgnoreRegisters;
|
||||
g_hw_sh_func[Pm4::SPI_SHADER_PGM_RSRC4_PS] = HwShIgnoreRegisters;
|
||||
g_hw_sh_func[Pm4::SPI_SHADER_TMA_LO_PS] = HwShIgnoreRegisters;
|
||||
g_hw_sh_func[Pm4::SPI_SHADER_TMA_HI_PS] = HwShIgnoreRegisters;
|
||||
g_hw_sh_func[Pm4::SPI_SHADER_REQ_CTRL_PS] = HwShIgnoreRegisters;
|
||||
g_hw_sh_func[Pm4::SPI_SHADER_PGM_RSRC3_GS] = HwShIgnoreRegisters;
|
||||
g_hw_sh_func[Pm4::SPI_SHADER_PGM_RSRC4_GS] = HwShIgnoreRegisters;
|
||||
g_hw_sh_func[Pm4::COMPUTE_PACE_ID] = HwShSetCsRegisters;
|
||||
g_hw_sh_func[Pm4::SPI_GRAPHICS_SHADER_CONTROL_PS] = HwShIgnoreRegisters;
|
||||
g_hw_sh_func[Pm4::SPI_GRAPHICS_SHADER_CONTROL_GS] = HwShIgnoreRegisters;
|
||||
g_hw_sh_func[Pm4::SPI_SHADER_USER_DATA_ADDR_LO_GS] = HwShIgnoreRegisters;
|
||||
g_hw_sh_func[Pm4::SPI_SHADER_USER_DATA_ADDR_HI_GS] = HwShIgnoreRegisters;
|
||||
g_hw_sh_func[Pm4::SPI_SHADER_REQ_CTRL_ESGS] = HwShIgnoreRegisters;
|
||||
g_hw_sh_func[Pm4::SPI_SHADER_PGM_RSRC3_HS] = HwShIgnoreRegisters;
|
||||
g_hw_sh_func[Pm4::SPI_SHADER_PGM_RSRC4_HS] = HwShIgnoreRegisters;
|
||||
g_hw_sh_func[Pm4::SPI_GRAPHICS_SHADER_CONTROL_HS] = HwShIgnoreRegisters;
|
||||
g_hw_sh_func[Pm4::SPI_SHADER_USER_DATA_ADDR_LO_HS] = HwShIgnoreRegisters;
|
||||
g_hw_sh_func[Pm4::SPI_SHADER_USER_DATA_ADDR_HI_HS] = HwShIgnoreRegisters;
|
||||
g_hw_sh_func[Pm4::SPI_SHADER_REQ_CTRL_LSHS] = HwShIgnoreRegisters;
|
||||
|
||||
return g_hw_sh_func;
|
||||
}
|
||||
@@ -179,6 +158,7 @@ constexpr auto MakeUconfigDispatchTable() {
|
||||
g_hw_uc_func[Pm4::VGT_INDEX_TYPE] = HwUcSetIndexType;
|
||||
g_hw_uc_func[Pm4::VGT_OBJECT_ID] = HwUcSetObjectId;
|
||||
g_hw_uc_func[Pm4::TEXTURE_GRADIENT_FACTORS] = HwUcSetTextureGradientFactors;
|
||||
g_hw_uc_func[Pm4::TEXTURE_GRADIENT_CONTROL] = HwUcSetTextureGradientFactors;
|
||||
g_hw_uc_func[Pm4::IA_MULTI_VGT_PARAM] = HwUcSetIaMultiVgtParam;
|
||||
g_hw_uc_func[Pm4::GE_MULTI_PRIM_IB_RESET_EN] = HwUcSetMultiPrimIbReset;
|
||||
g_hw_uc_func[Pm4::TA_CS_BC_BASE_ADDR] = HwUcSetBorderColorTableAddr;
|
||||
|
||||
@@ -44,11 +44,9 @@ uint32_t HwCtxSetBorderColorTableAddr(CommandProcessor&, uint32_t, uint32_t, con
|
||||
uint32_t HwCtxSetStencilClear(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
|
||||
uint32_t HwCtxSetDepthClear(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
|
||||
uint32_t HwCtxSetScreenScissor(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
|
||||
uint32_t HwCtxSetDepthRenderTarget(CommandProcessor&, uint32_t, uint32_t, const uint32_t*,
|
||||
uint32_t);
|
||||
uint32_t HwCtxSetStencilInfo(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
|
||||
uint32_t HwCtxSetDepthHtileSurface(CommandProcessor&, uint32_t, uint32_t, const uint32_t*,
|
||||
uint32_t);
|
||||
uint32_t HwCtxSetDepthShadingRateEncoding(CommandProcessor&, uint32_t, uint32_t,
|
||||
const uint32_t*, uint32_t);
|
||||
uint32_t HwCtxSetHardwareScreenOffset(CommandProcessor&, uint32_t, uint32_t, const uint32_t*,
|
||||
uint32_t);
|
||||
uint32_t HwCtxSetWindowOffset(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
|
||||
@@ -89,7 +87,6 @@ uint32_t HwCtxSetDrawPayloadControl(CommandProcessor&, uint32_t, uint32_t, const
|
||||
uint32_t HwCtxSetPrimitiveIdReset(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
|
||||
uint32_t HwCtxSetObjprimIdControl(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
|
||||
uint32_t HwCtxSetShaderStages(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
|
||||
uint32_t HwCtxSetRenderTarget(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
|
||||
uint32_t HwCtxSetColorInfo(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
|
||||
uint32_t HwCtxSetBlendControl(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
|
||||
uint32_t HwCtxSetViewportScissor(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
|
||||
@@ -100,10 +97,8 @@ uint32_t HwShSetPsUserSgpr(CommandProcessor&, uint32_t, uint32_t, const uint32_t
|
||||
uint32_t HwShSetGsUserSgpr(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
|
||||
uint32_t HwShSetHsUserSgpr(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
|
||||
uint32_t HwShSetCsUserSgpr(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
|
||||
uint32_t HwShSetPsUserAccumSgpr(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
|
||||
uint32_t HwShSetGsUserAccumSgpr(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
|
||||
uint32_t HwShSetHsUserAccumSgpr(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
|
||||
uint32_t HwShSetCsUserAccumSgpr(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
|
||||
uint32_t HwShIgnoreUserAccumulator(CommandProcessor&, uint32_t, uint32_t, const uint32_t*,
|
||||
uint32_t);
|
||||
uint32_t HwShSetCsRegisters(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
|
||||
uint32_t HwShIgnoreRegisters(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
|
||||
uint32_t HwUcSetPrimitiveType(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -407,7 +407,19 @@ enum class BufferFormat : uint32_t {
|
||||
k5_5_5_1UNorm = 134,
|
||||
k1_5_5_5UNorm = 135,
|
||||
k4_4_4_4UNorm = 136,
|
||||
kFmask8_S2_F1 = 156,
|
||||
kFmask8_S4_F1 = 157,
|
||||
kFmask8_S8_F1 = 158,
|
||||
kFmask8_S2_F2 = 159,
|
||||
kFmask8_S4_F2 = 160,
|
||||
kFmask8_S4_F4 = 161,
|
||||
kFmask16_S16_F1 = 162,
|
||||
kFmask16_S8_F2 = 163,
|
||||
kFmask32_S16_F2 = 164,
|
||||
kFmask32_S8_F4 = 165,
|
||||
kFmask32_S8_F8 = 166,
|
||||
kFmask64_S16_F4 = 167,
|
||||
kFmask64_S16_F8 = 168,
|
||||
kBc1UNorm = 169,
|
||||
kBc1Srgb = 170,
|
||||
kBc2UNorm = 171,
|
||||
|
||||
@@ -68,7 +68,19 @@ constexpr FormatInfo kFormatInfo[] = {
|
||||
{BufferFormat::k5_5_5_1UNorm, 2, 0, 2, true, false},
|
||||
{BufferFormat::k1_5_5_5UNorm, 0, 0, 2, false, false},
|
||||
{BufferFormat::k4_4_4_4UNorm, 2, 0, 2, true, false},
|
||||
{BufferFormat::kFmask8_S2_F1, 1, 0, 1, true, false},
|
||||
{BufferFormat::kFmask8_S4_F1, 1, 0, 1, true, false},
|
||||
{BufferFormat::kFmask8_S8_F1, 1, 0, 1, true, false},
|
||||
{BufferFormat::kFmask8_S2_F2, 1, 0, 1, true, false},
|
||||
{BufferFormat::kFmask8_S4_F2, 1, 0, 1, true, false},
|
||||
{BufferFormat::kFmask8_S4_F4, 1, 0, 1, true, false},
|
||||
{BufferFormat::kFmask16_S16_F1, 2, 0, 2, true, false},
|
||||
{BufferFormat::kFmask16_S8_F2, 2, 0, 2, true, false},
|
||||
{BufferFormat::kFmask32_S16_F2, 4, 0, 4, true, false},
|
||||
{BufferFormat::kFmask32_S8_F4, 4, 0, 4, true, false},
|
||||
{BufferFormat::kFmask32_S8_F8, 4, 0, 4, true, false},
|
||||
{BufferFormat::kFmask64_S16_F4, 8, 0, 8, true, false},
|
||||
{BufferFormat::kFmask64_S16_F8, 8, 0, 8, true, false},
|
||||
{BufferFormat::kBc1UNorm, 0, 8, 0, true, false},
|
||||
{BufferFormat::kBc1Srgb, 0, 8, 0, true, false},
|
||||
{BufferFormat::kBc2UNorm, 0, 16, 0, true, false},
|
||||
@@ -203,7 +215,7 @@ uint32_t RenderTargetBytesPerElement(BufferFormat format) {
|
||||
}
|
||||
|
||||
bool IsFmaskTextureFormat(BufferFormat format) {
|
||||
return format == BufferFormat::kFmask8_S4_F4;
|
||||
return format >= BufferFormat::kFmask8_S2_F1 && format <= BufferFormat::kFmask64_S16_F8;
|
||||
}
|
||||
|
||||
bool IsSampledTextureFormat(BufferFormat format) {
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
#include <cstdio>
|
||||
#include <deque>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <semaphore>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
@@ -65,17 +66,10 @@ static bool GraphicsRunDebugDumpEnabled() {
|
||||
Config::GetPrintfDirection() != Config::OutputDirection::Silent;
|
||||
}
|
||||
|
||||
GuestGpu::OwnedCmdBuffer::OwnedCmdBuffer(const uint32_t* data, uint32_t count) {
|
||||
EXIT_IF(data == nullptr && count != 0);
|
||||
if (count != 0) {
|
||||
m_words.assign(data, data + count);
|
||||
}
|
||||
}
|
||||
|
||||
GuestGpu::GuestGpu(RenderContext& renderer): m_renderer(renderer) {
|
||||
EXIT_NOT_IMPLEMENTED(!Common::Thread::IsMainThread());
|
||||
GraphicsInitJmpTables();
|
||||
m_gfx_cp = std::make_unique<CommandProcessor>(renderer);
|
||||
m_gfx_cp = std::make_unique<CommandProcessor>(renderer, 0);
|
||||
m_thread = std::jthread(ThreadRun, this);
|
||||
}
|
||||
|
||||
@@ -147,39 +141,33 @@ void GuestGpu::SendCommandSync(Common::UniqueFunction<void>&& command) {
|
||||
done.acquire();
|
||||
}
|
||||
|
||||
void GuestGpu::Submit(uint32_t* cmd_draw_buffer, uint32_t num_draw_dw, uint32_t* cmd_const_buffer,
|
||||
uint32_t num_const_dw, bool trigger_agc_interrupt_on_done) {
|
||||
if (num_draw_dw == 0) {
|
||||
void GuestGpu::Submit(std::span<const uint32_t> draw_commands,
|
||||
std::span<const uint32_t> constant_commands) {
|
||||
if (draw_commands.empty()) {
|
||||
return;
|
||||
}
|
||||
EXIT_IF(cmd_draw_buffer == nullptr);
|
||||
GpuMutexLock lock(m_submission_mutex);
|
||||
Submission submission;
|
||||
submission.type = SubmissionType::Graphics;
|
||||
submission.queue_id = 0;
|
||||
submission.commands = OwnedCmdBuffer(cmd_draw_buffer, num_draw_dw);
|
||||
submission.constant_commands = OwnedCmdBuffer(cmd_const_buffer, num_const_dw);
|
||||
submission.trigger_agc_interrupt_on_done = trigger_agc_interrupt_on_done;
|
||||
submission.reset_processor = m_graphics_done;
|
||||
m_graphics_done = false;
|
||||
submission.type = SubmissionType::Graphics;
|
||||
submission.queue_id = 0;
|
||||
submission.commands = draw_commands;
|
||||
submission.constant_commands = constant_commands;
|
||||
submission.reset_processor = m_graphics_done;
|
||||
m_graphics_done = false;
|
||||
Enqueue(std::move(submission));
|
||||
}
|
||||
|
||||
void GuestGpu::SubmitCompute(uint32_t queue, uint32_t* cmd_buffer, uint32_t num_dw,
|
||||
bool trigger_agc_interrupt_on_done) {
|
||||
EXIT_IF(cmd_buffer == nullptr || num_dw == 0);
|
||||
void GuestGpu::SubmitCompute(uint32_t queue, std::span<const uint32_t> commands) {
|
||||
EXIT_IF(commands.empty());
|
||||
GpuMutexLock lock(m_submission_mutex);
|
||||
|
||||
constexpr uint32_t compute_queue_base = 0x20u;
|
||||
EXIT_NOT_IMPLEMENTED(queue < compute_queue_base ||
|
||||
queue >= compute_queue_base + ComputeQueueCount);
|
||||
EXIT_NOT_IMPLEMENTED(queue < ComputeQueueBase || queue >= ComputeQueueBase + ComputeQueueCount);
|
||||
|
||||
const auto compute_queue = queue - compute_queue_base;
|
||||
const auto compute_queue = queue - ComputeQueueBase;
|
||||
Submission submission;
|
||||
submission.type = SubmissionType::Compute;
|
||||
submission.queue_id = 1 + compute_queue;
|
||||
submission.commands = OwnedCmdBuffer(cmd_buffer, num_dw);
|
||||
submission.trigger_agc_interrupt_on_done = trigger_agc_interrupt_on_done;
|
||||
submission.type = SubmissionType::Compute;
|
||||
submission.queue_id = 1 + compute_queue;
|
||||
submission.commands = commands;
|
||||
Enqueue(std::move(submission));
|
||||
}
|
||||
|
||||
@@ -214,7 +202,7 @@ CommandProcessor& GuestGpu::GetProcessor(uint32_t queue_id) {
|
||||
}
|
||||
auto& processor = m_compute_cp[queue_id - 1];
|
||||
if (processor == nullptr) {
|
||||
processor = std::make_unique<CommandProcessor>(m_renderer);
|
||||
processor = std::make_unique<CommandProcessor>(m_renderer, ComputeQueueBase + queue_id - 1);
|
||||
}
|
||||
return *processor;
|
||||
}
|
||||
@@ -267,8 +255,7 @@ void CommandProcessor::BufferFlush() {
|
||||
}
|
||||
|
||||
void CommandProcessor::BufferFlushAndWait() {
|
||||
auto& submitted = GetScheduler().FlushAndGetSubmitted();
|
||||
submitted.WaitForFenceOnly();
|
||||
GetScheduler().FlushAndWait();
|
||||
}
|
||||
|
||||
void CommandProcessor::BufferWait() {
|
||||
@@ -582,21 +569,20 @@ bool GuestGpu::Process(Submission& submission) {
|
||||
switch (submission.type) {
|
||||
case SubmissionType::Graphics: {
|
||||
bool progressed = false;
|
||||
submission.constant_complete |= submission.constant_commands.Empty();
|
||||
submission.constant_complete |= submission.constant_commands.empty();
|
||||
for (;;) {
|
||||
bool round_progress = false;
|
||||
if (!submission.constant_complete) {
|
||||
submission.constant_complete =
|
||||
cp.Process(
|
||||
submission.constant_execution, submission.constant_commands.Data(),
|
||||
submission.constant_commands.Size()) == Pm4ProcessResult::Complete;
|
||||
cp.Process(submission.constant_execution, submission.constant_commands) ==
|
||||
Pm4ProcessResult::Complete;
|
||||
round_progress |= submission.constant_execution.MadeProgress();
|
||||
}
|
||||
cp.SetCeComplete(submission.constant_complete);
|
||||
if (!submission.command_complete) {
|
||||
submission.command_complete =
|
||||
cp.Process(submission.command_execution, submission.commands.Data(),
|
||||
submission.commands.Size()) == Pm4ProcessResult::Complete;
|
||||
cp.Process(submission.command_execution, submission.commands) ==
|
||||
Pm4ProcessResult::Complete;
|
||||
round_progress |= submission.command_execution.MadeProgress();
|
||||
}
|
||||
progressed |= round_progress;
|
||||
@@ -613,14 +599,11 @@ bool GuestGpu::Process(Submission& submission) {
|
||||
} else if (complete) {
|
||||
m_renderer.GetGpuResources().RunGarbageCollector();
|
||||
}
|
||||
if (complete && submission.trigger_agc_interrupt_on_done) {
|
||||
m_renderer.TriggerEopEvent(0);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SubmissionType::Compute: {
|
||||
const auto num_dw = submission.commands.Size();
|
||||
auto* buffer = submission.commands.Data();
|
||||
const auto num_dw = static_cast<uint32_t>(submission.commands.size());
|
||||
const auto* buffer = submission.commands.data();
|
||||
static uint32_t compute_batch_log_count = 0;
|
||||
if (first_slice && num_dw <= 128 && compute_batch_log_count++ < 32) {
|
||||
LOGF("compute direct batch: data=0x%016" PRIx64 ", num_dw=%" PRIu32 "\n",
|
||||
@@ -632,7 +615,7 @@ bool GuestGpu::Process(Submission& submission) {
|
||||
if (first_slice) {
|
||||
GraphicsDbgDumpDcb("cc", num_dw, buffer);
|
||||
}
|
||||
complete = cp.Process(submission.command_execution, buffer, num_dw) ==
|
||||
complete = cp.Process(submission.command_execution, submission.commands) ==
|
||||
Pm4ProcessResult::Complete;
|
||||
if (submission.command_execution.MadeProgress()) {
|
||||
if (complete) {
|
||||
@@ -642,9 +625,6 @@ bool GuestGpu::Process(Submission& submission) {
|
||||
} else if (complete) {
|
||||
m_renderer.GetGpuResources().RunGarbageCollector();
|
||||
}
|
||||
if (complete && submission.trigger_agc_interrupt_on_done) {
|
||||
Sync::TriggerAgcUserInterrupt();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SubmissionType::FlipPreparation:
|
||||
@@ -656,12 +636,13 @@ bool GuestGpu::Process(Submission& submission) {
|
||||
return complete;
|
||||
}
|
||||
|
||||
Pm4ProcessResult CommandProcessor::Process(Pm4Execution& execution, uint32_t* buffer,
|
||||
uint32_t size_dw) {
|
||||
Pm4ProcessResult CommandProcessor::Process(Pm4Execution& execution,
|
||||
std::span<const uint32_t> commands) {
|
||||
KYTY_PROFILER_BLOCK("CommandProcessor::Process");
|
||||
EXIT_IF(g_current_execution != nullptr);
|
||||
if (execution.m_buffer_stack.empty() && size_dw != 0) {
|
||||
execution.m_buffer_stack.push_back({buffer, size_dw, size_dw, 0});
|
||||
EXIT_IF(commands.size() > UINT32_MAX);
|
||||
if (execution.m_buffer_stack.empty() && !commands.empty()) {
|
||||
execution.m_buffer_stack.push_back({commands});
|
||||
}
|
||||
execution.m_suspended = false;
|
||||
execution.m_made_progress = false;
|
||||
@@ -686,14 +667,14 @@ Pm4ProcessResult CommandProcessor::Process(Pm4Execution& execution, uint32_t* bu
|
||||
: Pm4ProcessResult::Blocked;
|
||||
}
|
||||
|
||||
void CommandProcessor::ProcessIndirectBuffer(uint32_t* buffer, uint32_t size_dw) {
|
||||
void CommandProcessor::ProcessIndirectBuffer(std::span<const uint32_t> commands) {
|
||||
EXIT_IF(g_current_execution == nullptr);
|
||||
if (size_dw == 0) {
|
||||
if (commands.empty()) {
|
||||
return;
|
||||
}
|
||||
auto& execution = *g_current_execution;
|
||||
const auto stop_depth = execution.m_buffer_stack.size();
|
||||
execution.m_buffer_stack.push_back({buffer, size_dw, size_dw, 0});
|
||||
execution.m_buffer_stack.push_back({commands});
|
||||
ProcessPm4(execution, stop_depth);
|
||||
}
|
||||
|
||||
@@ -709,29 +690,28 @@ void CommandProcessor::ProcessPm4(Pm4Execution& execution, size_t stop_depth) {
|
||||
}
|
||||
const auto buffer_index = execution.m_buffer_stack.size() - 1;
|
||||
auto& cursor = execution.m_buffer_stack[buffer_index];
|
||||
EXIT_IF(cursor.offset_dw > cursor.commands.size());
|
||||
if (cursor.deferred_advance_dw != 0) {
|
||||
EXIT_IF(cursor.deferred_advance_dw > cursor.remaining_dw);
|
||||
cursor.next_packet += cursor.deferred_advance_dw;
|
||||
cursor.remaining_dw -= cursor.deferred_advance_dw;
|
||||
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.remaining_dw == 0) {
|
||||
if (cursor.offset_dw == cursor.commands.size()) {
|
||||
execution.m_buffer_stack.pop_back();
|
||||
continue;
|
||||
}
|
||||
|
||||
auto* const packet = cursor.next_packet;
|
||||
const auto remaining_dw = cursor.remaining_dw;
|
||||
const auto total_dw = cursor.total_dw;
|
||||
const auto packet_header = packet[0];
|
||||
const auto opcode = (packet_header >> 8u) & 0xffu;
|
||||
const auto* const packet = cursor.commands.data() + cursor.offset_dw;
|
||||
const auto total_dw = static_cast<uint32_t>(cursor.commands.size());
|
||||
const auto remaining_dw = total_dw - cursor.offset_dw;
|
||||
const auto packet_header = packet[0];
|
||||
const auto opcode = (packet_header >> 8u) & 0xffu;
|
||||
EXIT_NOT_IMPLEMENTED(remaining_dw > total_dw);
|
||||
|
||||
if (packet_header == 0x80000000u) {
|
||||
cursor.next_packet++;
|
||||
cursor.remaining_dw--;
|
||||
cursor.offset_dw++;
|
||||
execution.m_made_progress = true;
|
||||
continue;
|
||||
}
|
||||
@@ -766,8 +746,7 @@ void CommandProcessor::ProcessPm4(Pm4Execution& execution, size_t stop_depth) {
|
||||
dst, val, packet[1], packet[2]);
|
||||
}
|
||||
}
|
||||
cursor.next_packet += packet_dw;
|
||||
cursor.remaining_dw -= packet_dw;
|
||||
cursor.offset_dw += packet_dw;
|
||||
execution.m_made_progress = true;
|
||||
continue;
|
||||
}
|
||||
@@ -800,8 +779,7 @@ void CommandProcessor::ProcessPm4(Pm4Execution& execution, size_t stop_depth) {
|
||||
return;
|
||||
}
|
||||
EXIT_IF(execution.m_buffer_stack.size() != buffer_index + 1);
|
||||
execution.m_buffer_stack[buffer_index].next_packet += packet_dw;
|
||||
execution.m_buffer_stack[buffer_index].remaining_dw -= packet_dw;
|
||||
execution.m_buffer_stack[buffer_index].offset_dw += packet_dw;
|
||||
execution.m_made_progress = true;
|
||||
}
|
||||
}
|
||||
@@ -1244,7 +1222,14 @@ void CommandProcessor::WriteAtEndOfPipe(uint32_t cache_policy, uint32_t event_wr
|
||||
switch (interrupt_selector) {
|
||||
case 0x00:
|
||||
case 0x03: with_interrupt = false; break;
|
||||
case 0x01: Sync::TriggerEopEventAtEndOfPipe(CurrentBuffer(), interrupt_context_id); return;
|
||||
case 0x01:
|
||||
if (!IsAsyncComputeQueue()) {
|
||||
Sync::TriggerEopEventAtEndOfPipe(CurrentBuffer(), m_interrupt_event_id,
|
||||
interrupt_context_id);
|
||||
return;
|
||||
}
|
||||
with_interrupt = true;
|
||||
break;
|
||||
case 0x02: with_interrupt = true; break;
|
||||
default: EXIT("unknown interrupt_selector\n");
|
||||
}
|
||||
@@ -1257,10 +1242,11 @@ void CommandProcessor::WriteAtEndOfPipe(uint32_t cache_policy, uint32_t event_wr
|
||||
if (with_interrupt) {
|
||||
if (with_writeback) {
|
||||
Sync::WriteAtEndOfPipeWithInterruptWriteBack32(m_submit_id, CurrentBuffer(), dst,
|
||||
data, interrupt_context_id);
|
||||
data, m_interrupt_event_id,
|
||||
interrupt_context_id);
|
||||
} else {
|
||||
Sync::WriteAtEndOfPipeWithInterrupt32(m_submit_id, CurrentBuffer(), dst, data,
|
||||
interrupt_context_id);
|
||||
m_interrupt_event_id, interrupt_context_id);
|
||||
}
|
||||
} else if (with_writeback) {
|
||||
Sync::WriteAtEndOfPipeWithWriteBack32(m_submit_id, CurrentBuffer(), dst, data);
|
||||
@@ -1275,12 +1261,12 @@ void CommandProcessor::WriteAtEndOfPipe(uint32_t cache_policy, uint32_t event_wr
|
||||
if (eop_event_type == 0x2f && cache_action == 0x00 && event_index == 0x06) {
|
||||
auto* dst = static_cast<uint32_t*>(dst_gpu_addr);
|
||||
SynchronizeGpu();
|
||||
Sync::ReadGds(m_renderer.GetBufferCache().GetGdsBuffer(), dst, value & 0xffffu,
|
||||
Sync::ReadGds(*m_renderer.GetBufferCache().GetGdsBuffer(), dst, value & 0xffffu,
|
||||
value >> 16u);
|
||||
Sync::WriteAtEndOfPipeGds32(m_submit_id, CurrentBuffer(), dst, value & 0xffffu,
|
||||
value >> 16u);
|
||||
if (with_interrupt) {
|
||||
m_renderer.TriggerEopEvent(interrupt_context_id);
|
||||
m_renderer.TriggerInterrupt(m_interrupt_event_id, interrupt_context_id);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -1306,10 +1292,12 @@ void CommandProcessor::WriteAtEndOfPipe(uint32_t cache_policy, uint32_t event_wr
|
||||
if (with_interrupt) {
|
||||
if (with_writeback) {
|
||||
Sync::WriteAtEndOfPipeWithInterruptWriteBack64(
|
||||
m_submit_id, CurrentBuffer(), dst, value, interrupt_context_id);
|
||||
m_submit_id, CurrentBuffer(), dst, value, m_interrupt_event_id,
|
||||
interrupt_context_id);
|
||||
} else {
|
||||
Sync::WriteAtEndOfPipeWithInterrupt64(m_submit_id, CurrentBuffer(), dst,
|
||||
value, interrupt_context_id);
|
||||
value, m_interrupt_event_id,
|
||||
interrupt_context_id);
|
||||
}
|
||||
} else if (with_writeback) {
|
||||
Sync::WriteAtEndOfPipeWithWriteBack64(m_submit_id, CurrentBuffer(), dst,
|
||||
@@ -1388,26 +1376,35 @@ void CommandProcessor::WriteAtEndOfPipe(uint32_t cache_policy, uint32_t event_wr
|
||||
case 0x04:
|
||||
if constexpr (sizeof(T) == sizeof(uint64_t)) {
|
||||
const auto clock = Sync::ReadReferenceClock();
|
||||
std::memcpy(dst_gpu_addr, &clock, sizeof(clock));
|
||||
auto* dst = static_cast<uint64_t*>(dst_gpu_addr);
|
||||
std::memcpy(dst, &clock, sizeof(clock));
|
||||
switch (cache_action) {
|
||||
case 0x00:
|
||||
if (((eop_event_type == 0x04 && event_index == 0x05) ||
|
||||
(eop_event_type == 0x28 && event_index == 0x00)) &&
|
||||
!with_interrupt) {
|
||||
Sync::WriteAtEndOfPipeClockCounter(m_submit_id, CurrentBuffer(),
|
||||
static_cast<uint64_t*>(dst_gpu_addr),
|
||||
clock);
|
||||
if ((eop_event_type == 0x04 && event_index == 0x05) ||
|
||||
(eop_event_type == 0x28 && event_index == 0x00)) {
|
||||
if (with_interrupt) {
|
||||
Sync::WriteAtEndOfPipeWithInterrupt64(
|
||||
m_submit_id, CurrentBuffer(), dst, clock, m_interrupt_event_id,
|
||||
interrupt_context_id);
|
||||
} else {
|
||||
Sync::WriteAtEndOfPipeClockCounter(m_submit_id, CurrentBuffer(),
|
||||
dst, clock);
|
||||
}
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case 0x38:
|
||||
if (((eop_event_type == 0x04 &&
|
||||
(event_index == 0x00 || event_index == 0x05)) ||
|
||||
(eop_event_type == 0x28 && event_index == 0x00)) &&
|
||||
!with_interrupt) {
|
||||
Sync::WriteAtEndOfPipeClockCounterWithWriteBack(
|
||||
m_submit_id, CurrentBuffer(), static_cast<uint64_t*>(dst_gpu_addr),
|
||||
clock);
|
||||
if ((eop_event_type == 0x04 &&
|
||||
(event_index == 0x00 || event_index == 0x05)) ||
|
||||
(eop_event_type == 0x28 && event_index == 0x00)) {
|
||||
if (with_interrupt) {
|
||||
Sync::WriteAtEndOfPipeWithInterruptWriteBack64(
|
||||
m_submit_id, CurrentBuffer(), dst, clock, m_interrupt_event_id,
|
||||
interrupt_context_id);
|
||||
} else {
|
||||
Sync::WriteAtEndOfPipeClockCounterWithWriteBack(
|
||||
m_submit_id, CurrentBuffer(), dst, clock);
|
||||
}
|
||||
return;
|
||||
}
|
||||
break;
|
||||
@@ -1464,15 +1461,17 @@ void CommandProcessor::EmitGlobalBarrier() {
|
||||
void CommandProcessor::TriggerEopEventAtEndOfPipe(uint32_t interrupt_context_id) {
|
||||
CheckBuffer();
|
||||
|
||||
Sync::TriggerEopEventAtEndOfPipe(CurrentBuffer(), interrupt_context_id);
|
||||
Sync::TriggerEopEventAtEndOfPipe(CurrentBuffer(), m_interrupt_event_id, interrupt_context_id);
|
||||
}
|
||||
|
||||
void CommandProcessor::TriggerEvent(uint32_t event_type, uint32_t event_index) {
|
||||
void CommandProcessor::TriggerEvent(uint32_t event_type, uint32_t event_index,
|
||||
uint64_t event_address) {
|
||||
if (GraphicsRunDebugDumpEnabled()) {
|
||||
LOGF("CommandProcessor::TriggerEvent()\n"
|
||||
"\t event_type = 0x%08" PRIx32 "\n"
|
||||
"\t event_index = 0x%08" PRIx32 "\n",
|
||||
event_type, event_index);
|
||||
"\t event_index = 0x%08" PRIx32 "\n"
|
||||
"\t address = 0x%016" PRIx64 "\n",
|
||||
event_type, event_index, event_address);
|
||||
}
|
||||
|
||||
const auto valid_cache_event_index = event_index == 0x00000000 || event_index == 0x00000007;
|
||||
@@ -1509,12 +1508,36 @@ void CommandProcessor::TriggerEvent(uint32_t event_type, uint32_t event_index) {
|
||||
case 0x0000001a:
|
||||
case 0x0000001b:
|
||||
case 0x00000038:
|
||||
case 0x00000039:
|
||||
case 0x0000003a:
|
||||
LOGF("\t temporary: ignoring unsupported event_write type 0x%08" PRIx32
|
||||
", index 0x%08" PRIx32 "\n",
|
||||
event_type, event_index);
|
||||
break;
|
||||
case 0x00000039: {
|
||||
if (event_index != 0x00000001 || event_address == 0 || (event_address & 0x7u) != 0) {
|
||||
EXIT("invalid occlusion-counter dump: index=0x%08" PRIx32 ", address=0x%016" PRIx64
|
||||
"\n",
|
||||
event_index, event_address);
|
||||
}
|
||||
static std::once_flag warning_once;
|
||||
std::call_once(warning_once, [] {
|
||||
std::printf("Warning: game uses occlusion queries, which are currently treated as "
|
||||
"always visible; GPU usage may be higher and FPS may be lower.\n");
|
||||
});
|
||||
|
||||
// Until host occlusion queries are implemented, publish an always-visible result. The
|
||||
// PS5 layout contains one interleaved begin/end pair per DB, and bit 63 marks a result
|
||||
// ready.
|
||||
constexpr uint64_t ready_bit = 1ull << 63u;
|
||||
constexpr uint64_t counter_mask = ready_bit - 1u;
|
||||
auto* results = reinterpret_cast<volatile uint64_t*>(event_address);
|
||||
const auto value = ready_bit | m_synthetic_occlusion_counter;
|
||||
for (uint32_t db = 0; db < 16u; db++) {
|
||||
results[db * 2u] = value;
|
||||
}
|
||||
m_synthetic_occlusion_counter = (m_synthetic_occlusion_counter + 1u) & counter_mask;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
EXIT("unknown event type: 0x%08" PRIx32 ", 0x%08" PRIx32 "\n", event_type, event_index);
|
||||
}
|
||||
@@ -1577,7 +1600,7 @@ void CommandProcessor::FlipWithInterrupt(uint32_t eop_event_type, uint32_t cache
|
||||
m_flip.flip_arg);
|
||||
Sync::WriteAtEndOfPipeWithInterruptWriteBackFlip32(
|
||||
m_submit_id, command, static_cast<uint32_t*>(dst_gpu_addr), value, m_flip.handle,
|
||||
m_flip.index, m_flip.flip_mode, m_flip.flip_arg, request);
|
||||
m_flip.index, m_flip.flip_mode, m_flip.flip_arg, request, m_interrupt_event_id);
|
||||
GetScheduler().Flush();
|
||||
}
|
||||
|
||||
@@ -1598,7 +1621,7 @@ void CommandProcessor::PrepareCpuFlip(uint64_t request_id) {
|
||||
}
|
||||
|
||||
void CommandProcessor::SynchronizeGpu() {
|
||||
GetScheduler().FinishCurrent();
|
||||
GetScheduler().Finish();
|
||||
}
|
||||
|
||||
bool GuestGpu::IsGpuThread() noexcept {
|
||||
|
||||
@@ -13,8 +13,8 @@
|
||||
#include <deque>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <span>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
@@ -31,12 +31,12 @@ public:
|
||||
void SendCommand(Common::UniqueFunction<void>&& command);
|
||||
void SendCommandSync(Common::UniqueFunction<void>&& command);
|
||||
|
||||
void Submit(uint32_t* draw_commands, uint32_t draw_size_dw, uint32_t* constant_commands,
|
||||
uint32_t constant_size_dw, bool trigger_agc_interrupt_on_done = false);
|
||||
void SubmitCompute(uint32_t queue, uint32_t* commands, uint32_t size_dw,
|
||||
bool trigger_agc_interrupt_on_done = false);
|
||||
void SubmitFlipPreparation(uint64_t request_id);
|
||||
void Done();
|
||||
// Submitted command memory is borrowed and must remain valid until GPU execution completes.
|
||||
void Submit(std::span<const uint32_t> draw_commands,
|
||||
std::span<const uint32_t> constant_commands);
|
||||
void SubmitCompute(uint32_t queue, std::span<const uint32_t> commands);
|
||||
void SubmitFlipPreparation(uint64_t request_id);
|
||||
void Done();
|
||||
[[nodiscard]] int GetFrameNum() const;
|
||||
|
||||
[[nodiscard]] static bool IsGpuThread() noexcept;
|
||||
@@ -45,39 +45,24 @@ private:
|
||||
static constexpr uint32_t ComputePipeCount = 7;
|
||||
static constexpr uint32_t QueuesPerComputePipe = 8;
|
||||
static constexpr uint32_t ComputeQueueCount = ComputePipeCount * QueuesPerComputePipe;
|
||||
static constexpr uint32_t ComputeQueueBase = 0x20;
|
||||
static constexpr uint32_t QueueCount = 1 + ComputeQueueCount;
|
||||
|
||||
class OwnedCmdBuffer {
|
||||
public:
|
||||
OwnedCmdBuffer() = default;
|
||||
explicit OwnedCmdBuffer(const uint32_t* data, uint32_t count);
|
||||
|
||||
[[nodiscard]] bool Empty() const noexcept { return m_words.empty(); }
|
||||
[[nodiscard]] uint32_t Size() const noexcept {
|
||||
return static_cast<uint32_t>(m_words.size());
|
||||
}
|
||||
[[nodiscard]] uint32_t* Data() noexcept { return m_words.data(); }
|
||||
|
||||
private:
|
||||
std::vector<uint32_t> m_words;
|
||||
};
|
||||
|
||||
enum class SubmissionType { Graphics, Compute, FlipPreparation };
|
||||
|
||||
struct Submission {
|
||||
SubmissionType type = SubmissionType::Graphics;
|
||||
uint32_t queue_id = 0;
|
||||
OwnedCmdBuffer commands;
|
||||
OwnedCmdBuffer constant_commands;
|
||||
Pm4Execution command_execution;
|
||||
Pm4Execution constant_execution;
|
||||
bool trigger_agc_interrupt_on_done = false;
|
||||
bool reset_processor = false;
|
||||
bool started = false;
|
||||
bool command_complete = false;
|
||||
bool constant_complete = false;
|
||||
bool blocked = false;
|
||||
uint64_t flip_request_id = 0;
|
||||
SubmissionType type = SubmissionType::Graphics;
|
||||
uint32_t queue_id = 0;
|
||||
std::span<const uint32_t> commands;
|
||||
std::span<const uint32_t> constant_commands;
|
||||
Pm4Execution command_execution;
|
||||
Pm4Execution constant_execution;
|
||||
bool reset_processor = false;
|
||||
bool started = false;
|
||||
bool command_complete = false;
|
||||
bool constant_complete = false;
|
||||
bool blocked = false;
|
||||
uint64_t flip_request_id = 0;
|
||||
};
|
||||
|
||||
void Enqueue(Submission submission);
|
||||
|
||||
@@ -11,15 +11,6 @@ struct ColorBase {
|
||||
uint64_t addr = 0;
|
||||
};
|
||||
|
||||
struct ColorPitch {
|
||||
uint32_t pitch_div8_minus1 = 0;
|
||||
uint32_t fmask_pitch_div8_minus1 = 0;
|
||||
};
|
||||
|
||||
struct ColorSlice {
|
||||
uint32_t slice_div64_minus1 = 0;
|
||||
};
|
||||
|
||||
struct ColorView {
|
||||
uint32_t base_array_slice_index = 0;
|
||||
uint32_t last_array_slice_index = 0;
|
||||
@@ -36,20 +27,15 @@ struct ColorInfo {
|
||||
bool blend_clamp = false;
|
||||
bool blend_bypass = false;
|
||||
bool round_mode = false;
|
||||
uint32_t cmask_is_linear = 0;
|
||||
uint32_t cmask_addr_type = 0;
|
||||
bool alt_tile_mode = false;
|
||||
Prospero::ChannelLayout format = Prospero::ChannelLayout::kInvalid;
|
||||
Prospero::ChannelType channel_type = Prospero::ChannelType::kUNorm;
|
||||
Prospero::ChannelOrder channel_order = Prospero::ChannelOrder::kStandard;
|
||||
};
|
||||
|
||||
struct ColorAttrib {
|
||||
bool force_dest_alpha_to_one = false;
|
||||
Prospero::TileMode tile_mode = Prospero::TileMode::kLinear;
|
||||
Prospero::TileMode fmask_tile_mode = Prospero::TileMode::kLinear;
|
||||
uint32_t num_samples = 0;
|
||||
uint32_t num_fragments = 0;
|
||||
bool force_dest_alpha_to_one = false;
|
||||
uint32_t num_samples = 0;
|
||||
uint32_t num_fragments = 0;
|
||||
};
|
||||
|
||||
struct ColorAttrib2 {
|
||||
@@ -59,41 +45,33 @@ struct ColorAttrib2 {
|
||||
};
|
||||
|
||||
struct ColorAttrib3 {
|
||||
uint32_t depth = 0;
|
||||
Prospero::TileMode tile_mode = Prospero::TileMode::kLinear;
|
||||
uint32_t dimension = 0;
|
||||
bool cmask_pipe_aligned = false;
|
||||
bool dcc_pipe_aligned = false;
|
||||
uint32_t depth = 0;
|
||||
Prospero::TileMode tile_mode = Prospero::TileMode::kRenderTarget;
|
||||
uint32_t dimension = 0;
|
||||
bool metadata_pipe_aligned = false;
|
||||
bool write_vrs_rate_hint_to_cmask = false;
|
||||
};
|
||||
|
||||
struct ColorDccControl {
|
||||
uint32_t max_uncompressed_block_size = 0;
|
||||
uint32_t max_compressed_block_size = 0;
|
||||
uint32_t min_compressed_block_size = 0;
|
||||
uint32_t color_transform = 0;
|
||||
bool dcc_clear_key_enable = false;
|
||||
bool overwrite_combiner_disable = false;
|
||||
bool independent_64b_blocks = false;
|
||||
bool independent_128b_blocks = false;
|
||||
bool data_write_on_dcc_clear_to_reg = false;
|
||||
enum class IndependentBlockSize : uint8_t { Disabled, Bytes64, Bytes128 };
|
||||
|
||||
uint32_t max_uncompressed_block_size = 2;
|
||||
uint32_t max_compressed_block_size = 2;
|
||||
uint32_t color_transform = 0;
|
||||
bool dcc_clear_key_enable = false;
|
||||
bool overwrite_combiner_disable = false;
|
||||
IndependentBlockSize independent_block_size = IndependentBlockSize::Disabled;
|
||||
bool data_write_on_dcc_clear_to_reg = false;
|
||||
};
|
||||
|
||||
struct ColorCmask {
|
||||
uint64_t addr = 0;
|
||||
};
|
||||
|
||||
struct ColorCmaskSlice {
|
||||
uint32_t slice_minus1 = 0;
|
||||
};
|
||||
|
||||
struct ColorFmask {
|
||||
uint64_t addr = 0;
|
||||
};
|
||||
|
||||
struct ColorFmaskSlice {
|
||||
uint32_t slice_minus1 = 0;
|
||||
};
|
||||
|
||||
struct ColorClearWord0 {
|
||||
uint32_t word0 = 0;
|
||||
};
|
||||
@@ -106,15 +84,8 @@ struct ColorDccAddr {
|
||||
uint64_t addr = 0;
|
||||
};
|
||||
|
||||
struct ColorSize {
|
||||
uint32_t width = 0;
|
||||
uint32_t height = 0;
|
||||
};
|
||||
|
||||
struct RenderTarget {
|
||||
ColorBase base;
|
||||
ColorPitch pitch;
|
||||
ColorSlice slice;
|
||||
ColorView view;
|
||||
ColorInfo info;
|
||||
ColorAttrib attrib;
|
||||
@@ -122,13 +93,10 @@ struct RenderTarget {
|
||||
ColorAttrib3 attrib3;
|
||||
ColorDccControl dcc;
|
||||
ColorCmask cmask;
|
||||
ColorCmaskSlice cmask_slice;
|
||||
ColorFmask fmask;
|
||||
ColorFmaskSlice fmask_slice;
|
||||
ColorClearWord0 clear_word0;
|
||||
ColorClearWord1 clear_word1;
|
||||
ColorDccAddr dcc_addr;
|
||||
ColorSize size;
|
||||
};
|
||||
|
||||
struct DepthZInfo {
|
||||
@@ -136,7 +104,7 @@ struct DepthZInfo {
|
||||
uint32_t num_samples = 0;
|
||||
Prospero::TextureCompatiblePlaneCompression texture_compatibility =
|
||||
Prospero::TextureCompatiblePlaneCompression::kDisable;
|
||||
Prospero::ZCompareBase z_compare_base = Prospero::ZCompareBase::kZMin;
|
||||
Prospero::ZCompareBase z_compare_base = Prospero::ZCompareBase::kZMax;
|
||||
bool htile_acceleration = false;
|
||||
bool expclear_enabled = false;
|
||||
bool partially_resident = false;
|
||||
@@ -171,7 +139,7 @@ struct DepthStencilInfo {
|
||||
Prospero::TextureCompatibleStencil texture_compatibility =
|
||||
Prospero::TextureCompatibleStencil::kDisable;
|
||||
bool expclear_enabled = false;
|
||||
bool htile_stencil_disabled = false;
|
||||
bool htile_stencil_disabled = true;
|
||||
bool partially_resident = false;
|
||||
|
||||
[[nodiscard]] static DepthStencilInfo Decode(uint32_t value) {
|
||||
@@ -194,16 +162,6 @@ struct DepthStencilInfo {
|
||||
}
|
||||
};
|
||||
|
||||
struct DepthRenderTargetDepthInfo {
|
||||
uint32_t addr5_swizzle_mask = 0;
|
||||
uint32_t array_mode = 0;
|
||||
uint32_t pipe_config = 0;
|
||||
uint32_t bank_width = 0;
|
||||
uint32_t bank_height = 0;
|
||||
uint32_t macro_tile_aspect = 0;
|
||||
uint32_t num_banks = 0;
|
||||
};
|
||||
|
||||
struct DepthDepthView {
|
||||
uint32_t slice_start = 0;
|
||||
uint32_t slice_max = 0;
|
||||
@@ -218,36 +176,18 @@ struct DepthDepthSizeXY {
|
||||
bool valid = false;
|
||||
};
|
||||
|
||||
struct DepthRenderTargetHTileSurface {
|
||||
uint32_t linear = 0;
|
||||
uint32_t full_cache = 0;
|
||||
uint32_t htile_uses_preload_win = 0;
|
||||
uint32_t preload = 0;
|
||||
uint32_t prefetch_width = 0;
|
||||
uint32_t prefetch_height = 0;
|
||||
uint32_t dst_outside_zero_to_one = 0;
|
||||
};
|
||||
|
||||
struct DepthRenderTarget {
|
||||
DepthZInfo z_info;
|
||||
DepthStencilInfo stencil_info;
|
||||
DepthDepthView depth_view;
|
||||
DepthDepthSizeXY size;
|
||||
DepthRenderTargetDepthInfo depth_info;
|
||||
DepthRenderTargetHTileSurface htile_surface;
|
||||
DepthZInfo z_info;
|
||||
DepthStencilInfo stencil_info;
|
||||
DepthDepthView depth_view;
|
||||
DepthDepthSizeXY size;
|
||||
|
||||
uint64_t z_read_base_addr = 0;
|
||||
uint64_t stencil_read_base_addr = 0;
|
||||
uint64_t z_write_base_addr = 0;
|
||||
uint64_t stencil_write_base_addr = 0;
|
||||
uint32_t pitch_div8_minus1 = 0;
|
||||
uint32_t height_div8_minus1 = 0;
|
||||
bool pitch_height_valid = false;
|
||||
uint32_t slice_div64_minus1 = 0;
|
||||
uint64_t htile_data_base_addr = 0;
|
||||
uint32_t width = 0;
|
||||
uint32_t height = 0;
|
||||
bool width_height_valid = false;
|
||||
uint8_t shading_rate_encoding = 0;
|
||||
};
|
||||
|
||||
struct RenderControl {
|
||||
@@ -256,6 +196,8 @@ struct RenderControl {
|
||||
bool resummarize_enable = false;
|
||||
bool stencil_compress_disable = false;
|
||||
bool depth_compress_disable = false;
|
||||
bool copy_depth_to_color = false;
|
||||
bool copy_stencil_to_color = false;
|
||||
bool copy_centroid = false;
|
||||
uint8_t copy_sample = 0;
|
||||
};
|
||||
@@ -299,16 +241,14 @@ struct ClipControl {
|
||||
};
|
||||
|
||||
struct DepthControl {
|
||||
bool stencil_enable = false;
|
||||
bool z_enable = false;
|
||||
bool z_write_enable = false;
|
||||
bool depth_bounds_enable = false;
|
||||
uint8_t zfunc = 0;
|
||||
bool backface_enable = false;
|
||||
uint8_t stencilfunc = 0;
|
||||
uint8_t stencilfunc_bf = 0;
|
||||
bool color_writes_on_depth_fail_enable = false;
|
||||
bool color_writes_on_depth_pass_disable = false;
|
||||
bool stencil_enable = false;
|
||||
bool z_enable = false;
|
||||
bool z_write_enable = false;
|
||||
bool depth_bounds_enable = false;
|
||||
uint8_t zfunc = 0;
|
||||
bool backface_enable = false;
|
||||
uint8_t stencilfunc = 0;
|
||||
uint8_t stencilfunc_bf = 0;
|
||||
};
|
||||
|
||||
struct StencilControl {
|
||||
@@ -468,27 +408,6 @@ struct ScreenViewport {
|
||||
bool clip_rect_window_offset_enable[4] = {};
|
||||
};
|
||||
|
||||
struct VsShaderResource1 {
|
||||
uint8_t vgprs = 0;
|
||||
uint8_t sgprs = 0;
|
||||
uint8_t priority = 0;
|
||||
uint8_t float_mode = 0;
|
||||
bool dx10_clamp = false;
|
||||
bool ieee_mode = false;
|
||||
uint8_t vgpr_component_count = 0;
|
||||
bool cu_group_enable = false;
|
||||
bool require_forward_progress = false;
|
||||
bool fp16_overflow = false;
|
||||
};
|
||||
|
||||
struct VsShaderResource2 {
|
||||
bool scratch_en = false;
|
||||
uint8_t user_sgpr = 0;
|
||||
bool offchip_lds = false;
|
||||
bool streamout_enabled = false;
|
||||
uint8_t shared_vgprs = 0;
|
||||
};
|
||||
|
||||
struct HsShaderResource1 {
|
||||
uint8_t vgprs = 0;
|
||||
uint8_t priority = 0;
|
||||
@@ -511,7 +430,6 @@ struct HsShaderResource2 {
|
||||
|
||||
struct PsShaderResource1 {
|
||||
uint8_t vgprs = 0;
|
||||
uint8_t sgprs = 0;
|
||||
uint8_t priority = 0;
|
||||
uint8_t float_mode = 0;
|
||||
bool dx10_clamp = false;
|
||||
@@ -535,7 +453,6 @@ struct PsStageRegisters {
|
||||
uint64_t data_addr = 0;
|
||||
PsShaderResource1 rsrc1;
|
||||
PsShaderResource2 rsrc2;
|
||||
uint64_t chksum = 0;
|
||||
};
|
||||
|
||||
struct CsStageRegisters {
|
||||
@@ -545,12 +462,13 @@ struct CsStageRegisters {
|
||||
uint32_t num_thread_y = 0;
|
||||
uint32_t num_thread_z = 0;
|
||||
uint8_t vgprs = 0;
|
||||
uint8_t sgprs = 0;
|
||||
uint8_t priority = 0;
|
||||
uint8_t float_mode = 0;
|
||||
bool dx10_clamp = false;
|
||||
bool debug_mode = false;
|
||||
bool ieee_mode = false;
|
||||
bool require_forward_progress = false;
|
||||
bool fp16_overflow = false;
|
||||
bool bulky = false;
|
||||
bool threadgroup_configuration = false;
|
||||
uint8_t wave_size = 64;
|
||||
bool scratch_en = false;
|
||||
@@ -561,6 +479,7 @@ struct CsStageRegisters {
|
||||
bool tg_size_en = false;
|
||||
uint8_t tidig_comp_cnt = 0;
|
||||
uint16_t lds_size = 0;
|
||||
uint8_t shared_vgprs = 0;
|
||||
};
|
||||
|
||||
struct EsStageRegisters {
|
||||
@@ -568,21 +487,17 @@ struct EsStageRegisters {
|
||||
};
|
||||
|
||||
struct LsStageRegisters {
|
||||
uint64_t data_addr = 0;
|
||||
VsShaderResource1 rsrc1;
|
||||
VsShaderResource2 rsrc2;
|
||||
uint64_t data_addr = 0;
|
||||
};
|
||||
|
||||
struct HsStageRegisters {
|
||||
uint64_t data_addr = 0;
|
||||
HsShaderResource1 rsrc1;
|
||||
HsShaderResource2 rsrc2;
|
||||
uint64_t chksum = 0;
|
||||
};
|
||||
|
||||
struct GsShaderResource1 {
|
||||
uint8_t vgprs = 0;
|
||||
uint8_t sgprs = 0;
|
||||
uint8_t priority = 0;
|
||||
uint8_t float_mode = 0;
|
||||
bool dx10_clamp = false;
|
||||
@@ -608,7 +523,6 @@ struct GsStageRegisters {
|
||||
uint64_t data_addr = 0;
|
||||
GsShaderResource1 rsrc1;
|
||||
GsShaderResource2 rsrc2;
|
||||
uint64_t chksum = 0;
|
||||
};
|
||||
|
||||
struct ShaderRegisters {
|
||||
@@ -664,8 +578,7 @@ struct ShaderRegisters {
|
||||
enum class UserSgprType { Unknown, Region, Vsharp };
|
||||
|
||||
struct UserSgprInfo {
|
||||
// Changed from 16 to 64
|
||||
static constexpr int SGPRS_MAX = 64;
|
||||
static constexpr int SGPRS_MAX = 32;
|
||||
|
||||
uint32_t value[SGPRS_MAX] = {0};
|
||||
UserSgprType type[SGPRS_MAX] = {};
|
||||
@@ -712,12 +625,6 @@ public:
|
||||
void Reset() { *this = Context(); }
|
||||
|
||||
void SetColorBase(uint32_t slot, const ColorBase& base) { m_render_targets[slot].base = base; }
|
||||
void SetColorPitch(uint32_t slot, const ColorPitch& pitch) {
|
||||
m_render_targets[slot].pitch = pitch;
|
||||
}
|
||||
void SetColorSlice(uint32_t slot, const ColorSlice& slice) {
|
||||
m_render_targets[slot].slice = slice;
|
||||
}
|
||||
void SetColorView(uint32_t slot, const ColorView& view) { m_render_targets[slot].view = view; }
|
||||
void SetColorInfo(uint32_t slot, const ColorInfo& info) { m_render_targets[slot].info = info; }
|
||||
void SetColorAttrib(uint32_t slot, const ColorAttrib& attrib) {
|
||||
@@ -735,15 +642,9 @@ public:
|
||||
void SetColorCmask(uint32_t slot, const ColorCmask& cmask) {
|
||||
m_render_targets[slot].cmask = cmask;
|
||||
}
|
||||
void SetColorCmaskSlice(uint32_t slot, const ColorCmaskSlice& cmask_slice) {
|
||||
m_render_targets[slot].cmask_slice = cmask_slice;
|
||||
}
|
||||
void SetColorFmask(uint32_t slot, const ColorFmask& fmask) {
|
||||
m_render_targets[slot].fmask = fmask;
|
||||
}
|
||||
void SetColorFmaskSlice(uint32_t slot, const ColorFmaskSlice& fmask_slice) {
|
||||
m_render_targets[slot].fmask_slice = fmask_slice;
|
||||
}
|
||||
void SetColorClearWord0(uint32_t slot, const ColorClearWord0& clear_word0) {
|
||||
m_render_targets[slot].clear_word0 = clear_word0;
|
||||
}
|
||||
@@ -753,7 +654,6 @@ public:
|
||||
void SetColorDccAddr(uint32_t slot, const ColorDccAddr& dcc_addr) {
|
||||
m_render_targets[slot].dcc_addr = dcc_addr;
|
||||
}
|
||||
void SetColorSize(uint32_t slot, const ColorSize& size) { m_render_targets[slot].size = size; }
|
||||
[[nodiscard]] const RenderTarget& GetRenderTarget(uint32_t slot) const {
|
||||
return m_render_targets[slot];
|
||||
}
|
||||
@@ -1072,21 +972,16 @@ 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 SetLsShaderResource1(const VsShaderResource1& rsrc1) { m_vs.ls_regs.rsrc1 = rsrc1; }
|
||||
void SetLsShaderResource2(const VsShaderResource2& rsrc2) { m_vs.ls_regs.rsrc2 = rsrc2; }
|
||||
void SetHsShaderBase(uint64_t addr) { m_vs.hs_regs.data_addr = addr; }
|
||||
void SetHsShaderResource1(const HsShaderResource1& rsrc1) { m_vs.hs_regs.rsrc1 = rsrc1; }
|
||||
void SetHsShaderResource2(const HsShaderResource2& rsrc2) { m_vs.hs_regs.rsrc2 = rsrc2; }
|
||||
void SetHsShaderChksum(uint32_t value) { m_vs.hs_regs.chksum = value; }
|
||||
void SetGsShaderBase(uint64_t addr) { m_vs.gs_regs.data_addr = addr; }
|
||||
void SetGsShaderResource1(const GsShaderResource1& rsrc1) { m_vs.gs_regs.rsrc1 = rsrc1; }
|
||||
void SetGsShaderResource2(const GsShaderResource2& rsrc2) { m_vs.gs_regs.rsrc2 = rsrc2; }
|
||||
void SetGsShaderChksum(uint32_t value) { m_vs.gs_regs.chksum = value; }
|
||||
|
||||
void SetPsShaderBase(uint64_t addr) { m_ps.ps_regs.data_addr = addr; }
|
||||
void SetPsShaderResource1(const PsShaderResource1& rsrc1) { m_ps.ps_regs.rsrc1 = rsrc1; }
|
||||
void SetPsShaderResource2(const PsShaderResource2& rsrc2) { m_ps.ps_regs.rsrc2 = rsrc2; }
|
||||
void SetPsShaderChksum(uint32_t value) { m_ps.ps_regs.chksum = value; }
|
||||
|
||||
void SetCsShader(const CsStageRegisters& cs_regs) { m_cs.cs_regs = cs_regs; }
|
||||
void SetCsWaveSize(uint8_t wave_size) { m_cs.cs_regs.wave_size = wave_size; }
|
||||
|
||||
@@ -100,7 +100,7 @@ constexpr auto g_opcode_names = MakeOpcodeNames();
|
||||
|
||||
} // namespace
|
||||
|
||||
void DumpPm4PacketStream(Common::File* file, uint32_t* cmd_buffer, uint32_t start_dw,
|
||||
void DumpPm4PacketStream(Common::File* file, const uint32_t* cmd_buffer, uint32_t start_dw,
|
||||
uint32_t num_dw) {
|
||||
// db_dump();
|
||||
|
||||
@@ -143,9 +143,9 @@ void DumpPm4PacketStream(Common::File* file, uint32_t* cmd_buffer, uint32_t star
|
||||
if ((op == IT_SET_CONTEXT_REG_INDIRECT || op == IT_SET_SH_REG_INDIRECT ||
|
||||
op == IT_SET_UCONFIG_REG_INDIRECT) &&
|
||||
len == 4) {
|
||||
auto* indirect_buffer =
|
||||
reinterpret_cast<uint32_t*>((static_cast<uint64_t>(cmd[0]) & 0xfffffffcu) |
|
||||
(static_cast<uint64_t>(cmd[1]) << 32u));
|
||||
auto* indirect_buffer = reinterpret_cast<const uint32_t*>(
|
||||
(static_cast<uint64_t>(cmd[0]) & 0xfffffffcu) |
|
||||
(static_cast<uint64_t>(cmd[1]) << 32u));
|
||||
const uint32_t indirect_num_regs = cmd[3] & 0x3fffu;
|
||||
|
||||
for (uint32_t i = 0; indirect_buffer != nullptr && i < indirect_num_regs;
|
||||
|
||||
+36
-126
@@ -98,6 +98,10 @@ constexpr uint32_t DB_RENDER_CONTROL_DEPTH_CLEAR_ENABLE_SHIFT = 0;
|
||||
constexpr uint32_t DB_RENDER_CONTROL_DEPTH_CLEAR_ENABLE_MASK = 0x1;
|
||||
constexpr uint32_t DB_RENDER_CONTROL_STENCIL_CLEAR_ENABLE_SHIFT = 1;
|
||||
constexpr uint32_t DB_RENDER_CONTROL_STENCIL_CLEAR_ENABLE_MASK = 0x1;
|
||||
constexpr uint32_t DB_RENDER_CONTROL_COPY_DEPTH_TO_COLOR_SHIFT = 2;
|
||||
constexpr uint32_t DB_RENDER_CONTROL_COPY_DEPTH_TO_COLOR_MASK = 0x1;
|
||||
constexpr uint32_t DB_RENDER_CONTROL_COPY_STENCIL_TO_COLOR_SHIFT = 3;
|
||||
constexpr uint32_t DB_RENDER_CONTROL_COPY_STENCIL_TO_COLOR_MASK = 0x1;
|
||||
constexpr uint32_t DB_RENDER_CONTROL_RESUMMARIZE_ENABLE_SHIFT = 4;
|
||||
constexpr uint32_t DB_RENDER_CONTROL_RESUMMARIZE_ENABLE_MASK = 0x1;
|
||||
constexpr uint32_t DB_RENDER_CONTROL_STENCIL_COMPRESS_DISABLE_SHIFT = 5;
|
||||
@@ -163,22 +167,6 @@ constexpr uint32_t PA_SC_SCREEN_SCISSOR_BR_BR_Y_MASK = 0xFFFF;
|
||||
|
||||
constexpr uint32_t DB_DFSM_CONTROL = 0xE;
|
||||
|
||||
constexpr uint32_t DB_DEPTH_INFO = 0xF;
|
||||
constexpr uint32_t DB_DEPTH_INFO_ADDR5_SWIZZLE_MASK_SHIFT = 0;
|
||||
constexpr uint32_t DB_DEPTH_INFO_ADDR5_SWIZZLE_MASK_MASK = 0xF;
|
||||
constexpr uint32_t DB_DEPTH_INFO_ARRAY_MODE_SHIFT = 4;
|
||||
constexpr uint32_t DB_DEPTH_INFO_ARRAY_MODE_MASK = 0xF;
|
||||
constexpr uint32_t DB_DEPTH_INFO_PIPE_CONFIG_SHIFT = 8;
|
||||
constexpr uint32_t DB_DEPTH_INFO_PIPE_CONFIG_MASK = 0x1F;
|
||||
constexpr uint32_t DB_DEPTH_INFO_BANK_WIDTH_SHIFT = 13;
|
||||
constexpr uint32_t DB_DEPTH_INFO_BANK_WIDTH_MASK = 0x3;
|
||||
constexpr uint32_t DB_DEPTH_INFO_BANK_HEIGHT_SHIFT = 15;
|
||||
constexpr uint32_t DB_DEPTH_INFO_BANK_HEIGHT_MASK = 0x3;
|
||||
constexpr uint32_t DB_DEPTH_INFO_MACRO_TILE_ASPECT_SHIFT = 17;
|
||||
constexpr uint32_t DB_DEPTH_INFO_MACRO_TILE_ASPECT_MASK = 0x3;
|
||||
constexpr uint32_t DB_DEPTH_INFO_NUM_BANKS_SHIFT = 19;
|
||||
constexpr uint32_t DB_DEPTH_INFO_NUM_BANKS_MASK = 0x3;
|
||||
|
||||
constexpr uint32_t DB_Z_INFO = 0x10;
|
||||
constexpr uint32_t DB_Z_INFO_FORMAT_SHIFT = 0;
|
||||
constexpr uint32_t DB_Z_INFO_FORMAT_MASK = 0x3;
|
||||
@@ -222,16 +210,6 @@ constexpr uint32_t DB_STENCIL_READ_BASE = 0x13;
|
||||
constexpr uint32_t DB_Z_WRITE_BASE = 0x14;
|
||||
constexpr uint32_t DB_STENCIL_WRITE_BASE = 0x15;
|
||||
|
||||
constexpr uint32_t DB_DEPTH_SIZE = 0x16;
|
||||
constexpr uint32_t DB_DEPTH_SIZE_PITCH_TILE_MAX_SHIFT = 0;
|
||||
constexpr uint32_t DB_DEPTH_SIZE_PITCH_TILE_MAX_MASK = 0x7FF;
|
||||
constexpr uint32_t DB_DEPTH_SIZE_HEIGHT_TILE_MAX_SHIFT = 11;
|
||||
constexpr uint32_t DB_DEPTH_SIZE_HEIGHT_TILE_MAX_MASK = 0x7FF;
|
||||
|
||||
constexpr uint32_t DB_DEPTH_SLICE = 0x17;
|
||||
constexpr uint32_t DB_DEPTH_SLICE_SLICE_TILE_MAX_SHIFT = 0;
|
||||
constexpr uint32_t DB_DEPTH_SLICE_SLICE_TILE_MAX_MASK = 0x3FFFFF;
|
||||
|
||||
constexpr uint32_t DB_Z_READ_BASE_HI = 0x1A;
|
||||
constexpr uint32_t DB_STENCIL_READ_BASE_HI = 0x1B;
|
||||
constexpr uint32_t DB_Z_WRITE_BASE_HI = 0x1C;
|
||||
@@ -424,11 +402,6 @@ constexpr uint32_t DB_DEPTH_CONTROL_STENCILFUNC_SHIFT = 8
|
||||
constexpr uint32_t DB_DEPTH_CONTROL_STENCILFUNC_MASK = 0x7;
|
||||
constexpr uint32_t DB_DEPTH_CONTROL_STENCILFUNC_BF_SHIFT = 20;
|
||||
constexpr uint32_t DB_DEPTH_CONTROL_STENCILFUNC_BF_MASK = 0x7;
|
||||
constexpr uint32_t DB_DEPTH_CONTROL_ENABLE_COLOR_WRITES_ON_DEPTH_FAIL_SHIFT = 30;
|
||||
constexpr uint32_t DB_DEPTH_CONTROL_ENABLE_COLOR_WRITES_ON_DEPTH_FAIL_MASK = 0x1;
|
||||
constexpr uint32_t DB_DEPTH_CONTROL_DISABLE_COLOR_WRITES_ON_DEPTH_PASS_SHIFT = 31;
|
||||
constexpr uint32_t DB_DEPTH_CONTROL_DISABLE_COLOR_WRITES_ON_DEPTH_PASS_MASK = 0x1;
|
||||
|
||||
constexpr uint32_t DB_EQAA = 0x201;
|
||||
constexpr uint32_t DB_EQAA_MAX_ANCHOR_SAMPLES_SHIFT = 0;
|
||||
constexpr uint32_t DB_EQAA_MAX_ANCHOR_SAMPLES_MASK = 0x7;
|
||||
@@ -560,21 +533,9 @@ constexpr uint32_t VGT_DRAW_PAYLOAD_CNTL = 0x2A6;
|
||||
constexpr uint32_t VGT_ESGS_RING_ITEMSIZE = 0x2AB;
|
||||
constexpr uint32_t VGT_REUSE_OFF = 0x2AD;
|
||||
|
||||
constexpr uint32_t DB_HTILE_SURFACE = 0x2AF;
|
||||
constexpr uint32_t DB_HTILE_SURFACE_LINEAR_SHIFT = 0;
|
||||
constexpr uint32_t DB_HTILE_SURFACE_LINEAR_MASK = 0x1;
|
||||
constexpr uint32_t DB_HTILE_SURFACE_FULL_CACHE_SHIFT = 1;
|
||||
constexpr uint32_t DB_HTILE_SURFACE_FULL_CACHE_MASK = 0x1;
|
||||
constexpr uint32_t DB_HTILE_SURFACE_HTILE_USES_PRELOAD_WIN_SHIFT = 2;
|
||||
constexpr uint32_t DB_HTILE_SURFACE_HTILE_USES_PRELOAD_WIN_MASK = 0x1;
|
||||
constexpr uint32_t DB_HTILE_SURFACE_PRELOAD_SHIFT = 3;
|
||||
constexpr uint32_t DB_HTILE_SURFACE_PRELOAD_MASK = 0x1;
|
||||
constexpr uint32_t DB_HTILE_SURFACE_PREFETCH_WIDTH_SHIFT = 4;
|
||||
constexpr uint32_t DB_HTILE_SURFACE_PREFETCH_WIDTH_MASK = 0x3F;
|
||||
constexpr uint32_t DB_HTILE_SURFACE_PREFETCH_HEIGHT_SHIFT = 10;
|
||||
constexpr uint32_t DB_HTILE_SURFACE_PREFETCH_HEIGHT_MASK = 0x3F;
|
||||
constexpr uint32_t DB_HTILE_SURFACE_DST_OUTSIDE_ZERO_TO_ONE_SHIFT = 16;
|
||||
constexpr uint32_t DB_HTILE_SURFACE_DST_OUTSIDE_ZERO_TO_ONE_MASK = 0x1;
|
||||
constexpr uint32_t DB_SHADING_RATE_ENCODING = 0x2AF;
|
||||
constexpr uint32_t DB_SHADING_RATE_ENCODING_SHADING_RATE_ENCODING_SHIFT = 19;
|
||||
constexpr uint32_t DB_SHADING_RATE_ENCODING_SHADING_RATE_ENCODING_MASK = 0x3;
|
||||
|
||||
constexpr uint32_t DB_SRESULTS_COMPARE_STATE0 = 0x2B0;
|
||||
constexpr uint32_t DB_SRESULTS_COMPARE_STATE1 = 0x2B1;
|
||||
@@ -621,7 +582,6 @@ constexpr uint32_t PA_SC_SHADER_CONTROL = 0x310;
|
||||
constexpr uint32_t PA_SC_BINNER_CNTL_0 = 0x311;
|
||||
constexpr uint32_t PA_SC_BINNER_CNTL_1 = 0x312;
|
||||
constexpr uint32_t PA_SC_CONSERVATIVE_RASTERIZATION_CNTL = 0x313;
|
||||
constexpr uint32_t PA_SC_NGG_MODE_CNTL = 0x314;
|
||||
constexpr uint32_t CB_COLOR0_BASE = 0x318;
|
||||
|
||||
constexpr uint32_t CB_COLOR0_VIEW = 0x31B;
|
||||
@@ -649,24 +609,13 @@ constexpr uint32_t CB_COLOR0_INFO_BLEND_BYPASS_SHIFT = 16;
|
||||
constexpr uint32_t CB_COLOR0_INFO_BLEND_BYPASS_MASK = 0x1;
|
||||
constexpr uint32_t CB_COLOR0_INFO_ROUND_MODE_SHIFT = 18;
|
||||
constexpr uint32_t CB_COLOR0_INFO_ROUND_MODE_MASK = 0x1;
|
||||
constexpr uint32_t CB_COLOR0_INFO_CMASK_IS_LINEAR_SHIFT = 19;
|
||||
constexpr uint32_t CB_COLOR0_INFO_CMASK_IS_LINEAR_MASK = 0x1;
|
||||
constexpr uint32_t CB_COLOR0_INFO_FMASK_COMPRESSION_DISABLE_SHIFT = 26;
|
||||
constexpr uint32_t CB_COLOR0_INFO_FMASK_COMPRESSION_DISABLE_MASK = 0x1;
|
||||
constexpr uint32_t CB_COLOR0_INFO_FMASK_COMPRESS_1FRAG_ONLY_SHIFT = 27;
|
||||
constexpr uint32_t CB_COLOR0_INFO_FMASK_COMPRESS_1FRAG_ONLY_MASK = 0x1;
|
||||
constexpr uint32_t CB_COLOR0_INFO_DCC_ENABLE_SHIFT = 28;
|
||||
constexpr uint32_t CB_COLOR0_INFO_DCC_ENABLE_MASK = 0x1;
|
||||
constexpr uint32_t CB_COLOR0_INFO_CMASK_ADDR_TYPE_SHIFT = 29;
|
||||
constexpr uint32_t CB_COLOR0_INFO_CMASK_ADDR_TYPE_MASK = 0x3;
|
||||
constexpr uint32_t CB_COLOR0_INFO_ALT_TILE_MODE_SHIFT = 31;
|
||||
constexpr uint32_t CB_COLOR0_INFO_ALT_TILE_MODE_MASK = 0x1;
|
||||
|
||||
constexpr uint32_t CB_COLOR0_ATTRIB = 0x31D;
|
||||
constexpr uint32_t CB_COLOR0_ATTRIB_TILE_MODE_INDEX_SHIFT = 0;
|
||||
constexpr uint32_t CB_COLOR0_ATTRIB_TILE_MODE_INDEX_MASK = 0x1F;
|
||||
constexpr uint32_t CB_COLOR0_ATTRIB_FMASK_TILE_MODE_INDEX_SHIFT = 5;
|
||||
constexpr uint32_t CB_COLOR0_ATTRIB_FMASK_TILE_MODE_INDEX_MASK = 0x1F;
|
||||
constexpr uint32_t CB_COLOR0_ATTRIB_NUM_SAMPLES_SHIFT = 12;
|
||||
constexpr uint32_t CB_COLOR0_ATTRIB_NUM_SAMPLES_MASK = 0x7;
|
||||
constexpr uint32_t CB_COLOR0_ATTRIB_NUM_FRAGMENTS_SHIFT = 15;
|
||||
@@ -681,8 +630,6 @@ constexpr uint32_t CB_COLOR0_DCC_CONTROL_KEY_CLEAR_ENABLE_SHIFT
|
||||
constexpr uint32_t CB_COLOR0_DCC_CONTROL_KEY_CLEAR_ENABLE_MASK = 0x1;
|
||||
constexpr uint32_t CB_COLOR0_DCC_CONTROL_MAX_UNCOMPRESSED_BLOCK_SIZE_SHIFT = 2;
|
||||
constexpr uint32_t CB_COLOR0_DCC_CONTROL_MAX_UNCOMPRESSED_BLOCK_SIZE_MASK = 0x3;
|
||||
constexpr uint32_t CB_COLOR0_DCC_CONTROL_MIN_COMPRESSED_BLOCK_SIZE_SHIFT = 4;
|
||||
constexpr uint32_t CB_COLOR0_DCC_CONTROL_MIN_COMPRESSED_BLOCK_SIZE_MASK = 0x1;
|
||||
constexpr uint32_t CB_COLOR0_DCC_CONTROL_MAX_COMPRESSED_BLOCK_SIZE_SHIFT = 5;
|
||||
constexpr uint32_t CB_COLOR0_DCC_CONTROL_MAX_COMPRESSED_BLOCK_SIZE_MASK = 0x3;
|
||||
constexpr uint32_t CB_COLOR0_DCC_CONTROL_COLOR_TRANSFORM_SHIFT = 7;
|
||||
@@ -739,31 +686,25 @@ constexpr uint32_t CB_COLOR0_ATTRIB3_CMASK_PIPE_ALIGNED_SHIFT = 26;
|
||||
constexpr uint32_t CB_COLOR0_ATTRIB3_CMASK_PIPE_ALIGNED_MASK = 0x1;
|
||||
constexpr uint32_t CB_COLOR0_ATTRIB3_DCC_PIPE_ALIGNED_SHIFT = 30;
|
||||
constexpr uint32_t CB_COLOR0_ATTRIB3_DCC_PIPE_ALIGNED_MASK = 0x1;
|
||||
constexpr uint32_t CB_COLOR0_ATTRIB3_WRITE_VRS_RATE_HINT_TO_CMASK_SHIFT = 31;
|
||||
constexpr uint32_t CB_COLOR0_ATTRIB3_WRITE_VRS_RATE_HINT_TO_CMASK_MASK = 0x1;
|
||||
|
||||
constexpr uint32_t CB_COLOR7_ATTRIB3 = 0x3BF;
|
||||
|
||||
/* Fake codes. Don't exist on real HW */
|
||||
|
||||
constexpr uint32_t CX_NOP = 0x800003FF;
|
||||
constexpr uint32_t CX_NOP = 0xDB;
|
||||
|
||||
constexpr uint32_t CX_NUM = 0x3FF + 1;
|
||||
|
||||
/* Shader registers */
|
||||
|
||||
constexpr uint32_t SPI_SHADER_TBA_LO_PS = 0x0;
|
||||
constexpr uint32_t SPI_SHADER_PGM_RSRC4_PS = 0x1;
|
||||
constexpr uint32_t SPI_SHADER_TMA_LO_PS = 0x2;
|
||||
constexpr uint32_t SPI_SHADER_TMA_HI_PS = 0x3;
|
||||
constexpr uint32_t SPI_SHADER_PGM_CHKSUM_PS = 0x6;
|
||||
constexpr uint32_t SPI_SHADER_PGM_RSRC3_PS = 0x7;
|
||||
constexpr uint32_t SPI_SHADER_PGM_LO_PS = 0x8;
|
||||
constexpr uint32_t SPI_SHADER_PGM_HI_PS = 0x9;
|
||||
constexpr uint32_t SPI_SHADER_PACE_ID_PS = 0x6;
|
||||
constexpr uint32_t SPI_GRAPHICS_SHADER_CONTROL_PS = 0x7;
|
||||
constexpr uint32_t SPI_SHADER_PGM_LO_PS = 0x8;
|
||||
constexpr uint32_t SPI_SHADER_PGM_HI_PS = 0x9;
|
||||
|
||||
constexpr uint32_t SPI_SHADER_PGM_RSRC1_PS = 0xA;
|
||||
constexpr uint32_t SPI_SHADER_PGM_RSRC1_PS_VGPRS_SHIFT = 0;
|
||||
constexpr uint32_t SPI_SHADER_PGM_RSRC1_PS_VGPRS_MASK = 0x3F;
|
||||
constexpr uint32_t SPI_SHADER_PGM_RSRC1_PS_SGPRS_SHIFT = 6;
|
||||
constexpr uint32_t SPI_SHADER_PGM_RSRC1_PS_SGPRS_MASK = 0xF;
|
||||
constexpr uint32_t SPI_SHADER_PGM_RSRC1_PS_PRIORITY_SHIFT = 10;
|
||||
constexpr uint32_t SPI_SHADER_PGM_RSRC1_PS_PRIORITY_MASK = 0x3;
|
||||
constexpr uint32_t SPI_SHADER_PGM_RSRC1_PS_FLOAT_MODE_SHIFT = 12;
|
||||
@@ -800,14 +741,11 @@ constexpr uint32_t SPI_SHADER_PGM_RSRC2_PS_SHARED_VGPR_CNT_MASK = 0xF;
|
||||
constexpr uint32_t SPI_SHADER_USER_DATA_PS_0 = 0xC;
|
||||
constexpr uint32_t SPI_SHADER_USER_DATA_PS_15 = 0x1B;
|
||||
constexpr uint32_t SPI_SHADER_USER_DATA_PS_31 = 0x2B;
|
||||
constexpr uint32_t SPI_SHADER_REQ_CTRL_PS = 0x30;
|
||||
constexpr uint32_t SPI_SHADER_USER_ACCUM_PS_0 = 0x32;
|
||||
constexpr uint32_t SPI_SHADER_USER_ACCUM_PS_3 = 0x35;
|
||||
|
||||
constexpr uint32_t SPI_SHADER_PGM_RSRC1_VS_VGPRS_SHIFT = 0;
|
||||
constexpr uint32_t SPI_SHADER_PGM_RSRC1_VS_VGPRS_MASK = 0x3F;
|
||||
constexpr uint32_t SPI_SHADER_PGM_RSRC1_VS_SGPRS_SHIFT = 6;
|
||||
constexpr uint32_t SPI_SHADER_PGM_RSRC1_VS_SGPRS_MASK = 0xF;
|
||||
constexpr uint32_t SPI_SHADER_PGM_RSRC1_VS_PRIORITY_SHIFT = 10;
|
||||
constexpr uint32_t SPI_SHADER_PGM_RSRC1_VS_PRIORITY_MASK = 0x3;
|
||||
constexpr uint32_t SPI_SHADER_PGM_RSRC1_VS_FLOAT_MODE_SHIFT = 12;
|
||||
@@ -838,19 +776,20 @@ constexpr uint32_t SPI_SHADER_PGM_RSRC2_VS_USER_SGPR_MSB_MASK = 0x1;
|
||||
constexpr uint32_t SPI_SHADER_PGM_RSRC2_VS_SHARED_VGPR_CNT_SHIFT = 28;
|
||||
constexpr uint32_t SPI_SHADER_PGM_RSRC2_VS_SHARED_VGPR_CNT_MASK = 0xF;
|
||||
|
||||
constexpr uint32_t SPI_SHADER_PGM_CHKSUM_GS = 0x80;
|
||||
constexpr uint32_t SPI_SHADER_PACE_ID_GS = 0x80;
|
||||
// AGC compiler records use this numeric tag as opaque metadata. It is not a shader checksum
|
||||
// register and intentionally has no checksum-state consumer in HardwareContext.
|
||||
constexpr uint32_t SPI_SHADER_PGM_CHKSUM_GS = SPI_SHADER_PACE_ID_GS;
|
||||
constexpr uint32_t SPI_SHADER_PGM_RSRC4_GS = 0x81;
|
||||
constexpr uint32_t SPI_SHADER_USER_DATA_ADDR_LO_GS = 0x82;
|
||||
constexpr uint32_t SPI_SHADER_USER_DATA_ADDR_HI_GS = 0x83;
|
||||
constexpr uint32_t SPI_SHADER_PGM_RSRC3_GS = 0x87;
|
||||
constexpr uint32_t SPI_GRAPHICS_SHADER_CONTROL_GS = 0x87;
|
||||
constexpr uint32_t SPI_SHADER_PGM_LO_GS = 0x88;
|
||||
constexpr uint32_t SPI_SHADER_PGM_HI_GS = 0x89;
|
||||
|
||||
constexpr uint32_t SPI_SHADER_PGM_RSRC1_GS = 0x8A;
|
||||
constexpr uint32_t SPI_SHADER_PGM_RSRC1_GS_VGPRS_SHIFT = 0;
|
||||
constexpr uint32_t SPI_SHADER_PGM_RSRC1_GS_VGPRS_MASK = 0x3F;
|
||||
constexpr uint32_t SPI_SHADER_PGM_RSRC1_GS_SGPRS_SHIFT = 6;
|
||||
constexpr uint32_t SPI_SHADER_PGM_RSRC1_GS_SGPRS_MASK = 0xF;
|
||||
constexpr uint32_t SPI_SHADER_PGM_RSRC1_GS_PRIORITY_SHIFT = 10;
|
||||
constexpr uint32_t SPI_SHADER_PGM_RSRC1_GS_PRIORITY_MASK = 0x3;
|
||||
constexpr uint32_t SPI_SHADER_PGM_RSRC1_GS_FLOAT_MODE_SHIFT = 12;
|
||||
@@ -891,22 +830,15 @@ constexpr uint32_t SPI_SHADER_PGM_RSRC2_GS_SHARED_VGPR_CNT_MASK = 0xF;
|
||||
constexpr uint32_t SPI_SHADER_USER_DATA_GS_0 = 0x8C;
|
||||
constexpr uint32_t SPI_SHADER_USER_DATA_GS_15 = 0x9B;
|
||||
constexpr uint32_t SPI_SHADER_USER_DATA_GS_31 = 0xAB;
|
||||
constexpr uint32_t SPI_SHADER_REQ_CTRL_ESGS = 0xB0;
|
||||
constexpr uint32_t SPI_SHADER_USER_ACCUM_ESGS_0 = 0xB2;
|
||||
constexpr uint32_t SPI_SHADER_USER_ACCUM_ESGS_3 = 0xB5;
|
||||
constexpr uint32_t SPI_SHADER_PGM_RSRC2_ES_GS = 0xBC;
|
||||
constexpr uint32_t SPI_SHADER_PGM_LO_ES = 0xC8;
|
||||
constexpr uint32_t SPI_SHADER_PGM_HI_ES = 0xC9;
|
||||
constexpr uint32_t SPI_SHADER_PGM_RSRC1_ES = 0xCA;
|
||||
constexpr uint32_t SPI_SHADER_PGM_RSRC2_ES = 0xCB;
|
||||
constexpr uint32_t SPI_SHADER_USER_DATA_ES_0 = 0xCC;
|
||||
constexpr uint32_t SPI_SHADER_USER_DATA_ES_15 = 0xDB;
|
||||
constexpr uint32_t SPI_SHADER_USER_DATA_ES_31 = 0xEB;
|
||||
// Retained only as an opaque AGC compiler-record tag; 0x100 is not a PS5 SH register.
|
||||
constexpr uint32_t SPI_SHADER_PGM_CHKSUM_HS = 0x100;
|
||||
constexpr uint32_t SPI_SHADER_PGM_RSRC4_HS = 0x101;
|
||||
constexpr uint32_t SPI_SHADER_USER_DATA_ADDR_LO_HS = 0x102;
|
||||
constexpr uint32_t SPI_SHADER_USER_DATA_ADDR_HI_HS = 0x103;
|
||||
constexpr uint32_t SPI_SHADER_PGM_RSRC3_HS = 0x107;
|
||||
constexpr uint32_t SPI_GRAPHICS_SHADER_CONTROL_HS = 0x107;
|
||||
constexpr uint32_t SPI_SHADER_PGM_LO_HS = 0x108;
|
||||
constexpr uint32_t SPI_SHADER_PGM_HI_HS = 0x109;
|
||||
constexpr uint32_t SPI_SHADER_PGM_RSRC1_HS = 0x10A;
|
||||
@@ -944,13 +876,10 @@ constexpr uint32_t SPI_SHADER_PGM_RSRC2_HS_SHARED_VGPR_CNT_MASK = 0xF;
|
||||
constexpr uint32_t SPI_SHADER_USER_DATA_HS_0 = 0x10C;
|
||||
constexpr uint32_t SPI_SHADER_USER_DATA_HS_15 = 0x11B;
|
||||
constexpr uint32_t SPI_SHADER_USER_DATA_HS_31 = 0x12B;
|
||||
constexpr uint32_t SPI_SHADER_REQ_CTRL_LSHS = 0x130;
|
||||
constexpr uint32_t SPI_SHADER_USER_ACCUM_LSHS_0 = 0x132;
|
||||
constexpr uint32_t SPI_SHADER_USER_ACCUM_LSHS_3 = 0x135;
|
||||
constexpr uint32_t SPI_SHADER_PGM_LO_LS = 0x148;
|
||||
constexpr uint32_t SPI_SHADER_PGM_HI_LS = 0x149;
|
||||
constexpr uint32_t SPI_SHADER_PGM_RSRC1_LS = 0x14A;
|
||||
constexpr uint32_t SPI_SHADER_PGM_RSRC2_LS = 0x14B;
|
||||
constexpr uint32_t COMPUTE_START_X = 0x204;
|
||||
constexpr uint32_t COMPUTE_START_Y = 0x205;
|
||||
constexpr uint32_t COMPUTE_START_Z = 0x206;
|
||||
@@ -959,28 +888,26 @@ constexpr uint32_t COMPUTE_NUM_THREAD_Y = 0x208;
|
||||
constexpr uint32_t COMPUTE_NUM_THREAD_Z = 0x209;
|
||||
constexpr uint32_t COMPUTE_PGM_LO = 0x20C;
|
||||
constexpr uint32_t COMPUTE_PGM_HI = 0x20D;
|
||||
constexpr uint32_t COMPUTE_TBA_LO = 0x20E;
|
||||
constexpr uint32_t COMPUTE_TBA_HI = 0x20F;
|
||||
constexpr uint32_t COMPUTE_TMA_LO = 0x210;
|
||||
constexpr uint32_t COMPUTE_TMA_HI = 0x211;
|
||||
|
||||
constexpr uint32_t COMPUTE_PGM_RSRC1 = 0x212;
|
||||
constexpr uint32_t COMPUTE_PGM_RSRC1_VGPRS_SHIFT = 0;
|
||||
constexpr uint32_t COMPUTE_PGM_RSRC1_VGPRS_MASK = 0x3F;
|
||||
constexpr uint32_t COMPUTE_PGM_RSRC1_SGPRS_SHIFT = 6;
|
||||
constexpr uint32_t COMPUTE_PGM_RSRC1_SGPRS_MASK = 0xF;
|
||||
constexpr uint32_t COMPUTE_PGM_RSRC1_PRIORITY_SHIFT = 10;
|
||||
constexpr uint32_t COMPUTE_PGM_RSRC1_PRIORITY_MASK = 0x3;
|
||||
constexpr uint32_t COMPUTE_PGM_RSRC1_FLOAT_MODE_SHIFT = 12;
|
||||
constexpr uint32_t COMPUTE_PGM_RSRC1_FLOAT_MODE_MASK = 0xFF;
|
||||
constexpr uint32_t COMPUTE_PGM_RSRC1_DX10_CLAMP_SHIFT = 21;
|
||||
constexpr uint32_t COMPUTE_PGM_RSRC1_DX10_CLAMP_MASK = 0x1;
|
||||
constexpr uint32_t COMPUTE_PGM_RSRC1_DEBUG_MODE_SHIFT = 22;
|
||||
constexpr uint32_t COMPUTE_PGM_RSRC1_DEBUG_MODE_MASK = 0x1;
|
||||
constexpr uint32_t COMPUTE_PGM_RSRC1_IEEE_MODE_SHIFT = 23;
|
||||
constexpr uint32_t COMPUTE_PGM_RSRC1_IEEE_MODE_MASK = 0x1;
|
||||
constexpr uint32_t COMPUTE_PGM_RSRC1_BULKY_SHIFT = 24;
|
||||
constexpr uint32_t COMPUTE_PGM_RSRC1_BULKY_MASK = 0x1;
|
||||
constexpr uint32_t COMPUTE_PGM_RSRC1_FP16_OVFL_SHIFT = 26;
|
||||
constexpr uint32_t COMPUTE_PGM_RSRC1_FP16_OVFL_MASK = 0x1;
|
||||
constexpr uint32_t COMPUTE_PGM_RSRC1_WGP_MODE_SHIFT = 29;
|
||||
constexpr uint32_t COMPUTE_PGM_RSRC1_WGP_MODE_MASK = 0x1;
|
||||
constexpr uint32_t COMPUTE_PGM_RSRC1_FWD_PROGRESS_SHIFT = 31;
|
||||
constexpr uint32_t COMPUTE_PGM_RSRC1_FWD_PROGRESS_MASK = 0x1;
|
||||
|
||||
constexpr uint32_t COMPUTE_DISPATCH_INITIATOR_CS_W32_EN_SHIFT = 15;
|
||||
constexpr uint32_t COMPUTE_DISPATCH_INITIATOR_CS_W32_EN_MASK = 0x1;
|
||||
@@ -1011,21 +938,16 @@ constexpr uint32_t COMPUTE_PGM_RSRC2_LDS_SIZE_SHIFT = 15;
|
||||
constexpr uint32_t COMPUTE_PGM_RSRC2_LDS_SIZE_MASK = 0x1FF;
|
||||
|
||||
constexpr uint32_t COMPUTE_RESOURCE_LIMITS = 0x215;
|
||||
constexpr uint32_t COMPUTE_DESTINATION_EN_SE0 = 0x216;
|
||||
constexpr uint32_t COMPUTE_DESTINATION_EN_SE1 = 0x217;
|
||||
constexpr uint32_t COMPUTE_TMPRING_SIZE = 0x218;
|
||||
constexpr uint32_t COMPUTE_DESTINATION_EN_SE2 = 0x219;
|
||||
constexpr uint32_t COMPUTE_DESTINATION_EN_SE3 = 0x21A;
|
||||
constexpr uint32_t COMPUTE_USER_ACCUM_0 = 0x224;
|
||||
constexpr uint32_t COMPUTE_PGM_RSRC3 = 0x228;
|
||||
constexpr uint32_t COMPUTE_SHADER_CHKSUM = 0x22A;
|
||||
constexpr uint32_t COMPUTE_PGM_RSRC3_SHARED_VGPRS_SHIFT = 0;
|
||||
constexpr uint32_t COMPUTE_PGM_RSRC3_SHARED_VGPRS_MASK = 0xF;
|
||||
constexpr uint32_t COMPUTE_PACE_ID = 0x22A;
|
||||
constexpr uint32_t COMPUTE_USER_DATA_0 = 0x240;
|
||||
constexpr uint32_t COMPUTE_USER_DATA_15 = 0x24F;
|
||||
constexpr uint32_t COMPUTE_DISPATCH_TUNNEL = 0x27D;
|
||||
|
||||
/* Fake codes. Don't exist on real HW */
|
||||
|
||||
constexpr uint32_t SH_NOP = 0x800002FF;
|
||||
constexpr uint32_t SH_NOP = 0x280;
|
||||
|
||||
constexpr uint32_t SH_NUM = 0x2FF + 1;
|
||||
|
||||
@@ -1042,6 +964,7 @@ constexpr uint32_t GE_INDX_OFFSET = 0x24A;
|
||||
constexpr uint32_t GE_MULTI_PRIM_IB_RESET_EN = 0x24B;
|
||||
constexpr uint32_t VGT_HS_OFFCHIP_PARAM = 0x24F;
|
||||
constexpr uint32_t VGT_TF_MEMORY_BASE = 0x250;
|
||||
constexpr uint32_t IA_MULTI_VGT_PARAM = 0x258;
|
||||
|
||||
constexpr uint32_t GE_CNTL = 0x25B;
|
||||
constexpr uint32_t GE_CNTL_PRIM_GRP_SIZE_SHIFT = 0;
|
||||
@@ -1063,32 +986,19 @@ 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 IA_MULTI_VGT_PARAM = 0x258;
|
||||
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 = 0x382;
|
||||
constexpr uint32_t TEXTURE_GRADIENT_FACTORS = 0x383;
|
||||
constexpr uint32_t TEXTURE_GRADIENT_CONTROL = 0x384;
|
||||
constexpr uint32_t GDS_OA_CNTL = 0x41D;
|
||||
constexpr uint32_t GDS_OA_COUNTER = 0x41E;
|
||||
constexpr uint32_t GDS_OA_ADDRESS = 0x41F;
|
||||
|
||||
/* Fake codes. Don't exist on real HW */
|
||||
|
||||
constexpr uint32_t FSR_EXTEND_SUBPIXEL_ROUNDING = 0x80003FF4;
|
||||
constexpr uint32_t FSR_ALPHA_VALUE0 = 0x80003FF5;
|
||||
constexpr uint32_t FSR_ALPHA_VALUE1 = 0x80003FF6;
|
||||
constexpr uint32_t FSR_CONTROL_POINT0 = 0x80003FF7;
|
||||
constexpr uint32_t FSR_CONTROL_POINT1 = 0x80003FF8;
|
||||
constexpr uint32_t FSR_CONTROL_POINT2 = 0x80003FF9;
|
||||
constexpr uint32_t FSR_CONTROL_POINT3 = 0x80003FFA;
|
||||
constexpr uint32_t FSR_WINDOW0 = 0x80003FFB;
|
||||
constexpr uint32_t FSR_WINDOW1 = 0x80003FFC;
|
||||
constexpr uint32_t TEXTURE_GRADIENT_CONTROL = 0x80003FFD;
|
||||
constexpr uint32_t MEMORY_MAPPING_MASK = 0x80003FFE;
|
||||
constexpr uint32_t UC_NOP = 0x80003FFF;
|
||||
constexpr uint32_t UC_NOP = 0xA2;
|
||||
|
||||
constexpr uint32_t UC_NUM = 0x3FFF + 1;
|
||||
|
||||
void DumpPm4PacketStream(Common::File* file, uint32_t* cmd_buffer, uint32_t start_dw,
|
||||
void DumpPm4PacketStream(Common::File* file, const uint32_t* cmd_buffer, uint32_t start_dw,
|
||||
uint32_t num_dw);
|
||||
|
||||
} // namespace Libs::Graphics::Pm4
|
||||
|
||||
@@ -3,20 +3,88 @@
|
||||
|
||||
#include "common/abi.h"
|
||||
#include "common/common.h"
|
||||
#include "common/threads.h"
|
||||
#include "graphics/host_gpu/vulkanCommon.h" // IWYU pragma: export
|
||||
#include "graphics/host_gpu/vulkanInstance.h"
|
||||
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#include <tuple>
|
||||
#include <vector>
|
||||
#include <vk_mem_alloc.h>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
class CommandBuffer;
|
||||
struct VulkanBuffer;
|
||||
struct VulkanImage;
|
||||
struct VulkanMemory;
|
||||
|
||||
struct GraphicContext: public VulkanInstance {
|
||||
inline constexpr uint32_t VULKAN_TARGET_API_VERSION = VK_API_VERSION_1_3;
|
||||
|
||||
struct GraphicContext {
|
||||
vk::Instance instance = nullptr;
|
||||
vk::DebugUtilsMessengerEXT debug_messenger = nullptr;
|
||||
vk::PhysicalDevice physical_device = nullptr;
|
||||
vk::PhysicalDeviceProperties physical_device_properties = {};
|
||||
vk::PhysicalDeviceMemoryProperties physical_device_memory_properties = {};
|
||||
vk::Device device = nullptr;
|
||||
VmaAllocator allocator = nullptr;
|
||||
bool memory_budget_ext_enabled = false;
|
||||
bool rt_extensions_enabled = false;
|
||||
bool compute_subgroup_size_control_enabled = false;
|
||||
bool compute_wave64_supported = false;
|
||||
bool sample_rate_shading_enabled = false;
|
||||
uint32_t subgroup_size = 0;
|
||||
uint32_t min_subgroup_size = 0;
|
||||
uint32_t max_subgroup_size = 0;
|
||||
uint32_t max_push_descriptors = 0;
|
||||
vk::ShaderStageFlags required_subgroup_size_stages = {};
|
||||
Common::Mutex queue_mutex;
|
||||
uint32_t queue_family = static_cast<uint32_t>(-1);
|
||||
vk::Queue queue = nullptr;
|
||||
|
||||
[[nodiscard]] const vk::PhysicalDeviceProperties& GetPhysicalDeviceProperties() const {
|
||||
return physical_device_properties;
|
||||
}
|
||||
|
||||
[[nodiscard]] const vk::PhysicalDeviceMemoryProperties&
|
||||
GetPhysicalDeviceMemoryProperties() const {
|
||||
return physical_device_memory_properties;
|
||||
}
|
||||
|
||||
[[nodiscard]] vk::FormatProperties GetFormatProperties(vk::Format format) const {
|
||||
std::scoped_lock lock(m_format_properties_mutex);
|
||||
auto [it, inserted] = m_format_properties.try_emplace(format);
|
||||
if (inserted) {
|
||||
physical_device.getFormatProperties(format, &it->second);
|
||||
}
|
||||
return it->second;
|
||||
}
|
||||
|
||||
[[nodiscard]] vk::Result GetImageFormatProperties(vk::Format format, vk::ImageType type,
|
||||
vk::ImageTiling tiling,
|
||||
vk::ImageUsageFlags usage,
|
||||
vk::ImageCreateFlags flags,
|
||||
vk::ImageFormatProperties* properties) const {
|
||||
using Key = std::tuple<vk::Format, vk::ImageType, vk::ImageTiling, vk::ImageUsageFlags,
|
||||
vk::ImageCreateFlags>;
|
||||
std::scoped_lock lock(m_image_format_properties_mutex);
|
||||
auto [it, inserted] =
|
||||
m_image_format_properties.try_emplace(Key {format, type, tiling, usage, flags});
|
||||
if (inserted) {
|
||||
it->second.first = physical_device.getImageFormatProperties(format, type, tiling, usage,
|
||||
flags, &it->second.second);
|
||||
}
|
||||
if (properties != nullptr) {
|
||||
*properties = it->second.second;
|
||||
}
|
||||
return it->second.first;
|
||||
}
|
||||
|
||||
[[nodiscard]] vk::DeviceSize StorageMinAlignment() const {
|
||||
const auto alignment = physical_device_properties.limits.minStorageBufferOffsetAlignment;
|
||||
return alignment != 0 ? alignment : 1;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool CreateAllocator();
|
||||
void DestroyAllocator();
|
||||
void LogMemoryBudget() const;
|
||||
@@ -24,7 +92,6 @@ struct GraphicContext: public VulkanInstance {
|
||||
[[nodiscard]] uint64_t GetDeviceMemoryUsage() const;
|
||||
[[nodiscard]] uint64_t GetTotalMemoryBudget() const;
|
||||
void CreateBuffer(uint64_t size, VulkanBuffer& buffer);
|
||||
void DeleteBuffer(VulkanBuffer& buffer);
|
||||
[[nodiscard]] bool CreateImage(const vk::ImageCreateInfo& info, VulkanImage& image);
|
||||
void DeleteImage(VulkanImage& image);
|
||||
void MapMemory(VulkanMemory& memory, void*& data);
|
||||
@@ -36,6 +103,15 @@ struct GraphicContext: public VulkanInstance {
|
||||
|
||||
uint32_t screen_width = 0;
|
||||
uint32_t screen_height = 0;
|
||||
|
||||
private:
|
||||
mutable std::mutex m_format_properties_mutex;
|
||||
mutable std::map<vk::Format, vk::FormatProperties> m_format_properties;
|
||||
mutable std::mutex m_image_format_properties_mutex;
|
||||
mutable std::map<std::tuple<vk::Format, vk::ImageType, vk::ImageTiling, vk::ImageUsageFlags,
|
||||
vk::ImageCreateFlags>,
|
||||
std::pair<vk::Result, vk::ImageFormatProperties>>
|
||||
m_image_format_properties;
|
||||
};
|
||||
|
||||
struct VulkanMemory {
|
||||
|
||||
@@ -18,8 +18,8 @@ MemoryTracker::~MemoryTracker() = default;
|
||||
#if KYTY_BUILD == KYTY_BUILD_DEBUG
|
||||
void MemoryTracker::ValidateGpuDirtyPages(const RangeSet& dirty, uint64_t vaddr, uint64_t size,
|
||||
const char* operation) const noexcept {
|
||||
if (vaddr == 0 || size == 0 || size > UINT64_MAX - vaddr ||
|
||||
(vaddr & (TRACKER_PAGE_SIZE - 1)) != 0 || (size & (TRACKER_PAGE_SIZE - 1)) != 0) {
|
||||
if (!GuestRange {vaddr, size}.Valid() || (vaddr & (TRACKER_PAGE_SIZE - 1)) != 0 ||
|
||||
(size & (TRACKER_PAGE_SIZE - 1)) != 0) {
|
||||
EXIT("MemoryTracker: invalid dirty-page validation range\n");
|
||||
}
|
||||
for (auto page = vaddr; page < vaddr + size; page += TRACKER_PAGE_SIZE) {
|
||||
@@ -37,9 +37,6 @@ void MemoryTracker::ValidateGpuDirtyPages(const RangeSet& dirty, uint64_t vaddr,
|
||||
void MemoryTracker::ValidateGpuDirtyOwnership(const RangeSet& dirty, uint64_t vaddr, uint64_t size,
|
||||
const char* operation) {
|
||||
ValidateRange(vaddr, size);
|
||||
if (vaddr + size > UINT64_MAX - (TRACKER_PAGE_SIZE - 1)) {
|
||||
EXIT("MemoryTracker: dirty ownership range alignment overflow\n");
|
||||
}
|
||||
const auto begin = vaddr & ~(TRACKER_PAGE_SIZE - 1);
|
||||
const auto end = (vaddr + size + TRACKER_PAGE_SIZE - 1) & ~(TRACKER_PAGE_SIZE - 1);
|
||||
for (auto page = begin; page < end; page += TRACKER_PAGE_SIZE) {
|
||||
@@ -56,8 +53,7 @@ void MemoryTracker::ValidateGpuDirtyOwnership(const RangeSet& dirty, uint64_t va
|
||||
#endif
|
||||
|
||||
void MemoryTracker::ValidateRange(uint64_t vaddr, uint64_t size) {
|
||||
if (vaddr == 0 || size == 0 || vaddr >= TRACKER_ADDRESS_SIZE ||
|
||||
size > TRACKER_ADDRESS_SIZE - vaddr) {
|
||||
if (!GuestRange {vaddr, size}.Valid()) {
|
||||
EXIT("invalid memory tracker range\n");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,7 +110,7 @@ private:
|
||||
};
|
||||
|
||||
void ValidateRange(uint64_t vaddr, uint64_t size) {
|
||||
if (vaddr == 0 || size == 0 || vaddr >= ADDRESS_SIZE || size > ADDRESS_SIZE - vaddr) {
|
||||
if (!GuestRange {vaddr, size}.Valid()) {
|
||||
Fatal("invalid range vaddr=0x%016" PRIx64 ", size=0x%016" PRIx64, vaddr, size);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,12 +53,30 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
void Clear() { m_ranges.clear(); }
|
||||
|
||||
template <typename Func>
|
||||
void ForEach(Func&& func) const {
|
||||
for (const auto& [begin, end]: m_ranges) {
|
||||
func(begin, end);
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] std::vector<Range> Intersections(uint64_t address, uint64_t size) const {
|
||||
std::vector<Range> result;
|
||||
ForEachIntersection(address, size, [&result](Range range) { result.push_back(range); });
|
||||
return result;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool Intersects(uint64_t address, uint64_t size) const {
|
||||
const auto end = End(address, size);
|
||||
auto it = m_ranges.lower_bound(address);
|
||||
if (it != m_ranges.begin() && std::prev(it)->second > address) {
|
||||
return true;
|
||||
}
|
||||
return it != m_ranges.end() && it->first < end;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool Contains(uint64_t address, uint64_t size) const {
|
||||
const auto end = End(address, size);
|
||||
auto it = m_ranges.upper_bound(address);
|
||||
@@ -89,7 +107,7 @@ public:
|
||||
|
||||
private:
|
||||
static uint64_t End(uint64_t address, uint64_t size) {
|
||||
if (address == 0 || size == 0 || size > UINT64_MAX - address) {
|
||||
if (size == 0 || size > UINT64_MAX - address) {
|
||||
EXIT("invalid range-set address or size\n");
|
||||
}
|
||||
return address + size;
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
#include "common/bitArray.h"
|
||||
#include "common/common.h"
|
||||
|
||||
#include <compare>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
constexpr uint64_t TRACKER_PAGE_SIZE = 4ull * 1024ull;
|
||||
@@ -11,6 +13,20 @@ constexpr uint64_t TRACKER_REGION_SIZE = 4ull * 1024ull * 1024ull;
|
||||
constexpr uint64_t TRACKER_ADDRESS_SIZE = 1ull << 40u;
|
||||
constexpr size_t TRACKER_REGION_PAGES = TRACKER_REGION_SIZE / TRACKER_PAGE_SIZE;
|
||||
|
||||
struct GuestRange {
|
||||
uint64_t address = 0;
|
||||
uint64_t size = 0;
|
||||
|
||||
[[nodiscard]] constexpr bool Empty() const noexcept { return address == 0 && size == 0; }
|
||||
[[nodiscard]] constexpr bool Valid() const noexcept {
|
||||
return address != 0 && size != 0 && address < TRACKER_ADDRESS_SIZE &&
|
||||
size <= TRACKER_ADDRESS_SIZE - address;
|
||||
}
|
||||
[[nodiscard]] constexpr bool ValidOrEmpty() const noexcept { return Empty() || Valid(); }
|
||||
[[nodiscard]] constexpr uint64_t End() const noexcept { return address + size; }
|
||||
auto operator<=>(const GuestRange&) const = default;
|
||||
};
|
||||
|
||||
enum class DirtySource { Cpu, Gpu };
|
||||
using RegionBits = Common::BitArray<TRACKER_REGION_PAGES>;
|
||||
static_assert(sizeof(RegionBits) == TRACKER_REGION_PAGES / 8);
|
||||
|
||||
+386
-454
File diff suppressed because it is too large.
Load diff
+59
-54
@@ -3,12 +3,15 @@
|
||||
|
||||
#include "common/abi.h"
|
||||
#include "common/common.h"
|
||||
#include "common/lruCache.h"
|
||||
#include "common/slotVector.h"
|
||||
#include "graphics/host_gpu/memoryTracker.h"
|
||||
#include "graphics/host_gpu/rangeSet.h"
|
||||
#include "graphics/host_gpu/renderer/cache/faultManager.h"
|
||||
#include "graphics/host_gpu/renderer/cache/multiLevelPageTable.h"
|
||||
#include "graphics/host_gpu/renderer/cache/streamBuffer.h"
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <span>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
@@ -16,93 +19,95 @@
|
||||
namespace Libs::Graphics {
|
||||
|
||||
struct GraphicContext;
|
||||
class CommandBuffer;
|
||||
class CommandScheduler;
|
||||
class TextureCache;
|
||||
|
||||
struct BufferBinding {
|
||||
std::shared_ptr<void> owner;
|
||||
vk::Buffer buffer = nullptr;
|
||||
uint64_t offset = 0;
|
||||
};
|
||||
|
||||
struct ImageBufferSource {
|
||||
Buffer* buffer = nullptr;
|
||||
uint64_t offset = 0;
|
||||
};
|
||||
using BufferId = Common::SlotId;
|
||||
inline constexpr BufferId NULL_BUFFER_ID {0};
|
||||
|
||||
class BufferCache {
|
||||
public:
|
||||
static constexpr uint64_t CACHING_PAGE_SIZE = 16ull * 1024ull;
|
||||
static constexpr uint64_t GetBufferOffset(uint64_t vaddr) {
|
||||
return vaddr & (CACHING_PAGE_SIZE - 1);
|
||||
}
|
||||
static constexpr uint32_t CACHING_PAGEBITS = 14;
|
||||
static constexpr uint64_t CACHING_PAGESIZE = uint64_t {1} << CACHING_PAGEBITS;
|
||||
static constexpr uint64_t CACHING_NUMPAGES = uint64_t {1} << (40 - CACHING_PAGEBITS);
|
||||
static constexpr uint64_t BDA_PAGETABLE_SIZE =
|
||||
CACHING_NUMPAGES * sizeof(vk::DeviceAddress);
|
||||
|
||||
BufferCache(GraphicContext& graphics, CommandScheduler& scheduler, PageManager& page_manager,
|
||||
TextureCache& texture_cache);
|
||||
~BufferCache();
|
||||
KYTY_CLASS_NO_COPY(BufferCache);
|
||||
|
||||
void InvalidateMemory(uint64_t vaddr, uint64_t size);
|
||||
void ReadMemory(uint64_t vaddr, uint64_t size, bool is_write = false);
|
||||
void UnmapMemory(uint64_t vaddr, uint64_t size);
|
||||
[[nodiscard]] BufferBinding ObtainBuffer(CommandBuffer& command, uint64_t vaddr, uint64_t size,
|
||||
bool is_written = false, bool is_read = true,
|
||||
bool is_formatted = false);
|
||||
[[nodiscard]] StreamBuffer& GetUtilityBuffer(MemoryUsage usage) noexcept;
|
||||
[[nodiscard]] Buffer& GetGdsBuffer() noexcept { return m_gds_buffer; }
|
||||
[[nodiscard]] const Buffer& GetGdsBuffer() const noexcept { return m_gds_buffer; }
|
||||
[[nodiscard]] BufferBinding UploadTransient(const void* data, uint64_t size,
|
||||
uint64_t alignment);
|
||||
[[nodiscard]] std::shared_ptr<Buffer> ObtainNullBuffer();
|
||||
[[nodiscard]] ImageBufferSource ObtainBufferForImage(uint64_t vaddr, uint64_t size);
|
||||
void FillBuffer(uint64_t vaddr, uint64_t size, uint32_t value, bool is_gds = false);
|
||||
void CopyBuffer(uint64_t dst_vaddr, uint64_t src_vaddr, uint64_t size, bool dst_gds = false,
|
||||
bool src_gds = false);
|
||||
void InvalidateMemory(uint64_t vaddr, uint64_t size);
|
||||
void ReadMemory(uint64_t vaddr, uint64_t size, bool is_write = false);
|
||||
[[nodiscard]] Buffer& GetBuffer(BufferId id) { return m_slot_buffers[id]; }
|
||||
[[nodiscard]] BufferId FindBuffer(uint64_t vaddr, uint64_t size);
|
||||
[[nodiscard]] std::pair<Buffer*, uint64_t> ObtainBuffer(uint64_t vaddr, uint64_t size,
|
||||
bool is_written,
|
||||
bool is_texel_buffer = false,
|
||||
BufferId id = {});
|
||||
[[nodiscard]] StreamBuffer& GetUtilityBuffer(MemoryUsage usage) noexcept {
|
||||
switch (usage) {
|
||||
case MemoryUsage::Upload: return m_staging_buffer;
|
||||
case MemoryUsage::Stream: return m_stream_buffer;
|
||||
case MemoryUsage::Download: return m_download_buffer;
|
||||
case MemoryUsage::DeviceLocal: return m_device_buffer;
|
||||
}
|
||||
EXIT("BufferCache: invalid utility-buffer usage\n");
|
||||
}
|
||||
[[nodiscard]] const Buffer* GetGdsBuffer() const noexcept { return &m_gds_buffer; }
|
||||
[[nodiscard]] Buffer* GetBdaPageTableBuffer() noexcept { return &m_bda_pagetable_buffer; }
|
||||
[[nodiscard]] Buffer* GetFaultBuffer() noexcept { return m_fault_manager.GetFaultBuffer(); }
|
||||
[[nodiscard]] std::pair<Buffer*, uint64_t> ObtainBufferForImage(uint64_t vaddr, uint64_t size);
|
||||
void FillBuffer(uint64_t vaddr, uint64_t size, uint32_t value, bool is_gds);
|
||||
void CopyBuffer(uint64_t dst_vaddr, uint64_t src_vaddr, uint64_t size, bool dst_gds,
|
||||
bool src_gds);
|
||||
// Cache-index and exact dirty-range queries require GPU-thread serialization.
|
||||
[[nodiscard]] bool IsRegionRegistered(uint64_t vaddr, uint64_t size);
|
||||
[[nodiscard]] bool HasGpuDirtyBytes(uint64_t vaddr, uint64_t size);
|
||||
[[nodiscard]] bool IsRegionCpuModified(uint64_t vaddr, uint64_t size);
|
||||
[[nodiscard]] bool IsRegionGpuModified(uint64_t vaddr, uint64_t size);
|
||||
void ProcessFaultBuffer();
|
||||
void SynchronizeBuffersInRange(uint64_t vaddr, uint64_t size);
|
||||
void RunGarbageCollector();
|
||||
|
||||
private:
|
||||
friend struct BufferCacheTestAccess;
|
||||
|
||||
struct CacheRange {
|
||||
uint64_t address = 0;
|
||||
uint64_t size = 0;
|
||||
};
|
||||
struct CachedBuffer;
|
||||
struct DownloadCopy;
|
||||
struct DownloadRange;
|
||||
struct RetiredBuffer;
|
||||
using PageTable = MultiLevelPageTable<BufferId, CACHING_PAGEBITS, 40, 16>;
|
||||
static_assert(CACHING_PAGESIZE == (uint64_t {1} << PageTable::kPageBits));
|
||||
static constexpr uint64_t DOWNLOAD_ALIGNMENT = 64;
|
||||
[[nodiscard]] static uint64_t AlignDown(uint64_t value) noexcept;
|
||||
[[nodiscard]] static uint64_t AlignUp(uint64_t value);
|
||||
[[nodiscard]] static constexpr uint64_t AlignDownload(uint64_t size) noexcept {
|
||||
return (size + DOWNLOAD_ALIGNMENT - 1) & ~(DOWNLOAD_ALIGNMENT - 1);
|
||||
}
|
||||
[[nodiscard]] static std::pair<uint64_t, uint64_t> DownloadEnvelope(const DownloadCopy& copy);
|
||||
[[nodiscard]] static bool ResolveOverlap(CacheRange& merged, CacheRange candidate) noexcept;
|
||||
void Upload(CommandBuffer& command, Buffer& destination, uint64_t destination_offset,
|
||||
const void* source, uint64_t size);
|
||||
[[nodiscard]] CachedBuffer& GetOrCreateBuffer(CommandBuffer& command, uint64_t vaddr,
|
||||
uint64_t size);
|
||||
[[nodiscard]] bool SynchronizeBuffer(CommandBuffer& command, Buffer& buffer, uint64_t vaddr,
|
||||
uint64_t size, bool is_written, bool is_texel_buffer);
|
||||
void WriteDataBuffer(Buffer& buffer, uint64_t address, const void* source, uint64_t size);
|
||||
void TouchBuffer(const Buffer& buffer);
|
||||
[[nodiscard]] BufferId CreateBuffer(uint64_t vaddr, uint64_t size);
|
||||
void Register(BufferId id);
|
||||
void Unregister(BufferId id);
|
||||
template <bool insert>
|
||||
void ChangeRegister(BufferId id);
|
||||
void DeleteBuffer(BufferId id);
|
||||
[[nodiscard]] bool SynchronizeBuffer(Buffer& buffer, uint64_t vaddr, uint64_t size,
|
||||
bool is_written, bool is_texel_buffer);
|
||||
[[nodiscard]] vk::Buffer UploadCopies(Buffer& buffer, std::span<vk::BufferCopy> copies,
|
||||
uint64_t total_size);
|
||||
[[nodiscard]] bool SynchronizeBufferFromImage(Buffer& buffer, uint64_t vaddr, uint64_t size);
|
||||
[[nodiscard]] std::vector<DownloadRange> RecordDownloads(std::span<const DownloadCopy> copies);
|
||||
void PublishDownloads(std::span<const DownloadRange> downloads);
|
||||
void QueueGarbageDownload(std::span<const DownloadCopy> copies, RetiredBuffer retire);
|
||||
void DownloadBufferMemory(std::span<const DownloadCopy> copies);
|
||||
void WriteHostMemory(uint64_t vaddr, std::span<const uint8_t> data);
|
||||
void ReadMemoryOnGpu(uint64_t vaddr, uint64_t size, bool is_write);
|
||||
|
||||
GraphicContext& m_graphics;
|
||||
CommandScheduler& m_scheduler;
|
||||
FaultManager m_fault_manager;
|
||||
Buffer m_gds_buffer;
|
||||
std::shared_ptr<Buffer> m_null_buffer;
|
||||
std::map<uint64_t, std::unique_ptr<CachedBuffer>> m_buffers;
|
||||
Buffer m_bda_pagetable_buffer;
|
||||
Common::SlotVector<Buffer> m_slot_buffers;
|
||||
Common::LeastRecentlyUsedCache<BufferId, uint64_t> m_lru_cache;
|
||||
std::map<uint64_t, BufferId> m_buffers;
|
||||
PageTable m_page_table;
|
||||
RangeSet m_gpu_modified_ranges;
|
||||
MemoryTracker m_memory_tracker;
|
||||
StreamBuffer m_staging_buffer;
|
||||
@@ -110,7 +115,7 @@ private:
|
||||
StreamBuffer m_download_buffer;
|
||||
StreamBuffer m_device_buffer;
|
||||
TextureCache& m_texture_cache;
|
||||
uint64_t m_total_used_memory = 0;
|
||||
uint64_t m_total_used_memory = 0;
|
||||
uint64_t m_trigger_gc_memory = 1ull * 1024 * 1024 * 1024;
|
||||
uint64_t m_critical_gc_memory = 2ull * 1024 * 1024 * 1024;
|
||||
uint64_t m_gc_tick = 0;
|
||||
|
||||
@@ -0,0 +1,167 @@
|
||||
#include "graphics/host_gpu/renderer/cache/faultManager.h"
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "gpu_tiler_shaders/fault_buffer_process_spv.h"
|
||||
#include "graphics/host_gpu/graphicContext.h"
|
||||
#include "graphics/host_gpu/renderer/cache/bufferCache.h"
|
||||
#include "graphics/host_gpu/renderer/commandScheduler.h"
|
||||
#include "graphics/host_gpu/vulkanCommon.h"
|
||||
|
||||
#include <bit>
|
||||
#include <cinttypes>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr size_t MaxPageFaults = 1024;
|
||||
constexpr size_t PageFaultAreaSize = MaxPageFaults * sizeof(uint64_t);
|
||||
|
||||
} // namespace
|
||||
|
||||
FaultManager::FaultManager(GraphicContext& graphics, CommandScheduler& scheduler,
|
||||
BufferCache& buffer_cache, uint32_t caching_pagebits,
|
||||
uint64_t caching_num_pages)
|
||||
: m_graphics(graphics), m_scheduler(scheduler), m_buffer_cache(buffer_cache),
|
||||
m_caching_pagesize(uint64_t {1} << caching_pagebits),
|
||||
m_caching_num_pages(caching_num_pages), m_fault_buffer_size(caching_num_pages / 8),
|
||||
m_fault_buffer(graphics, scheduler, MemoryUsage::DeviceLocal, 0, AllFlags,
|
||||
m_fault_buffer_size),
|
||||
m_download_buffer(graphics, scheduler, MemoryUsage::Download, 0, AllFlags,
|
||||
MaxPendingFaults * PageFaultAreaSize) {
|
||||
SetVulkanObjectNameF(m_graphics.device, m_fault_buffer.Handle(), "Fault Buffer");
|
||||
|
||||
const vk::DescriptorSetLayoutBinding bindings[] {
|
||||
{0, vk::DescriptorType::eStorageBuffer, 1, vk::ShaderStageFlagBits::eCompute, nullptr},
|
||||
{1, vk::DescriptorType::eStorageBuffer, 1, vk::ShaderStageFlagBits::eCompute, nullptr},
|
||||
};
|
||||
vk::DescriptorSetLayoutCreateInfo layout_info {};
|
||||
layout_info.sType = vk::StructureType::eDescriptorSetLayoutCreateInfo;
|
||||
layout_info.flags = vk::DescriptorSetLayoutCreateFlagBits::ePushDescriptorKHR;
|
||||
layout_info.bindingCount = std::size(bindings);
|
||||
layout_info.pBindings = bindings;
|
||||
RequireVulkanSuccess(
|
||||
m_graphics.device.createDescriptorSetLayout(&layout_info, nullptr,
|
||||
&m_fault_process_desc_layout),
|
||||
"create fault-buffer descriptor layout");
|
||||
|
||||
vk::ShaderModuleCreateInfo module_info {};
|
||||
module_info.sType = vk::StructureType::eShaderModuleCreateInfo;
|
||||
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");
|
||||
|
||||
vk::PipelineLayoutCreateInfo pipeline_layout_info {};
|
||||
pipeline_layout_info.sType = vk::StructureType::ePipelineLayoutCreateInfo;
|
||||
pipeline_layout_info.setLayoutCount = 1;
|
||||
pipeline_layout_info.pSetLayouts = &m_fault_process_desc_layout;
|
||||
RequireVulkanSuccess(
|
||||
m_graphics.device.createPipelineLayout(&pipeline_layout_info, nullptr,
|
||||
&m_fault_process_pipeline_layout),
|
||||
"create fault-buffer pipeline layout");
|
||||
|
||||
vk::PipelineShaderStageCreateInfo stage {};
|
||||
stage.sType = vk::StructureType::ePipelineShaderStageCreateInfo;
|
||||
stage.stage = vk::ShaderStageFlagBits::eCompute;
|
||||
stage.module = module;
|
||||
stage.pName = "main";
|
||||
vk::ComputePipelineCreateInfo pipeline_info {};
|
||||
pipeline_info.sType = vk::StructureType::eComputePipelineCreateInfo;
|
||||
pipeline_info.stage = stage;
|
||||
pipeline_info.layout = m_fault_process_pipeline_layout;
|
||||
const auto result = m_graphics.device.createComputePipelines(
|
||||
nullptr, 1, &pipeline_info, nullptr, &m_fault_process_pipeline);
|
||||
m_graphics.device.destroyShaderModule(module, nullptr);
|
||||
RequireVulkanSuccess(result, "create fault-buffer pipeline");
|
||||
SetVulkanObjectNameF(m_graphics.device, m_fault_process_pipeline, "Fault Buffer Parser");
|
||||
}
|
||||
|
||||
FaultManager::~FaultManager() {
|
||||
m_graphics.device.destroyPipeline(m_fault_process_pipeline, nullptr);
|
||||
m_graphics.device.destroyPipelineLayout(m_fault_process_pipeline_layout, nullptr);
|
||||
m_graphics.device.destroyDescriptorSetLayout(m_fault_process_desc_layout, nullptr);
|
||||
}
|
||||
|
||||
void FaultManager::ProcessFaultBuffer() {
|
||||
if (const auto wait_tick = m_fault_areas[m_current_area]; wait_tick != 0) {
|
||||
m_scheduler.Wait(wait_tick);
|
||||
m_scheduler.PopPendingOperations();
|
||||
}
|
||||
|
||||
const auto offset = m_current_area * PageFaultAreaSize;
|
||||
auto* mapped = m_download_buffer.Mapped().data() + offset;
|
||||
std::memset(mapped, 0, PageFaultAreaSize);
|
||||
m_download_buffer.Flush(offset, PageFaultAreaSize);
|
||||
|
||||
vk::BufferMemoryBarrier2 pre_barrier {};
|
||||
pre_barrier.sType = vk::StructureType::eBufferMemoryBarrier2;
|
||||
pre_barrier.srcStageMask = vk::PipelineStageFlagBits2::eAllCommands;
|
||||
pre_barrier.srcAccessMask = vk::AccessFlagBits2::eShaderWrite;
|
||||
pre_barrier.dstStageMask = vk::PipelineStageFlagBits2::eComputeShader;
|
||||
pre_barrier.dstAccessMask = vk::AccessFlagBits2::eShaderRead;
|
||||
pre_barrier.buffer = m_fault_buffer.Handle();
|
||||
pre_barrier.offset = 0;
|
||||
pre_barrier.size = m_fault_buffer_size;
|
||||
auto post_barrier = pre_barrier;
|
||||
post_barrier.srcStageMask = vk::PipelineStageFlagBits2::eComputeShader;
|
||||
post_barrier.srcAccessMask = vk::AccessFlagBits2::eShaderWrite;
|
||||
post_barrier.dstStageMask = vk::PipelineStageFlagBits2::eAllCommands;
|
||||
post_barrier.dstAccessMask = vk::AccessFlagBits2::eShaderWrite;
|
||||
|
||||
const vk::DescriptorBufferInfo infos[] {
|
||||
{m_fault_buffer.Handle(), 0, m_fault_buffer_size},
|
||||
{m_download_buffer.Handle(), offset, PageFaultAreaSize},
|
||||
};
|
||||
std::array<vk::WriteDescriptorSet, 2> writes {};
|
||||
for (uint32_t index = 0; index < writes.size(); ++index) {
|
||||
writes[index].sType = vk::StructureType::eWriteDescriptorSet;
|
||||
writes[index].dstBinding = index;
|
||||
writes[index].descriptorCount = 1;
|
||||
writes[index].descriptorType = vk::DescriptorType::eStorageBuffer;
|
||||
writes[index].pBufferInfo = &infos[index];
|
||||
}
|
||||
|
||||
m_scheduler.EndRendering();
|
||||
auto command = m_scheduler.Current().Handle();
|
||||
vk::DependencyInfo dependency {};
|
||||
dependency.sType = vk::StructureType::eDependencyInfo;
|
||||
dependency.dependencyFlags = vk::DependencyFlagBits::eByRegion;
|
||||
dependency.bufferMemoryBarrierCount = 1;
|
||||
dependency.pBufferMemoryBarriers = &pre_barrier;
|
||||
command.pipelineBarrier2(dependency);
|
||||
command.bindPipeline(vk::PipelineBindPoint::eCompute, m_fault_process_pipeline);
|
||||
command.pushDescriptorSetKHR(vk::PipelineBindPoint::eCompute,
|
||||
m_fault_process_pipeline_layout, 0, writes);
|
||||
const auto num_threads = m_caching_num_pages / 32;
|
||||
const auto num_workgroups = (num_threads + 63) / 64;
|
||||
command.dispatch(static_cast<uint32_t>(num_workgroups), 1, 1);
|
||||
dependency.pBufferMemoryBarriers = &post_barrier;
|
||||
command.pipelineBarrier2(dependency);
|
||||
|
||||
const auto area = m_current_area;
|
||||
m_scheduler.DeferOperation([this, mapped, offset, area] {
|
||||
m_download_buffer.Invalidate(offset, PageFaultAreaSize);
|
||||
m_fault_ranges.Clear();
|
||||
const auto* faults = std::bit_cast<const uint64_t*>(mapped);
|
||||
const auto count = static_cast<uint32_t>(faults[0]);
|
||||
for (uint32_t index = 1; index <= count; ++index) {
|
||||
m_fault_ranges.Add(faults[index], m_caching_pagesize);
|
||||
LOGF("Accessed non-GPU cached memory at 0x%016" PRIx64 "\n", faults[index]);
|
||||
}
|
||||
m_fault_ranges.ForEach([this](uint64_t start, uint64_t end) {
|
||||
EXIT_IF(end - start > std::numeric_limits<uint32_t>::max());
|
||||
(void)m_buffer_cache.FindBuffer(start, end - start);
|
||||
});
|
||||
m_fault_areas[area] = 0;
|
||||
});
|
||||
|
||||
m_fault_areas[m_current_area++] = m_scheduler.CurrentTick();
|
||||
m_current_area %= MaxPendingFaults;
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
@@ -0,0 +1,46 @@
|
||||
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_CACHE_FAULTMANAGER_H_
|
||||
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_CACHE_FAULTMANAGER_H_
|
||||
|
||||
#include "common/abi.h"
|
||||
#include "graphics/host_gpu/rangeSet.h"
|
||||
#include "graphics/host_gpu/renderer/cache/streamBuffer.h"
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
class BufferCache;
|
||||
|
||||
class FaultManager {
|
||||
static constexpr size_t MaxPendingFaults = 8;
|
||||
|
||||
public:
|
||||
FaultManager(GraphicContext& graphics, CommandScheduler& scheduler, BufferCache& buffer_cache,
|
||||
uint32_t caching_pagebits, uint64_t caching_num_pages);
|
||||
~FaultManager();
|
||||
KYTY_CLASS_NO_COPY(FaultManager);
|
||||
|
||||
[[nodiscard]] Buffer* GetFaultBuffer() noexcept { return &m_fault_buffer; }
|
||||
void ProcessFaultBuffer();
|
||||
|
||||
private:
|
||||
GraphicContext& m_graphics;
|
||||
CommandScheduler& m_scheduler;
|
||||
BufferCache& m_buffer_cache;
|
||||
RangeSet m_fault_ranges;
|
||||
uint64_t m_caching_pagesize;
|
||||
uint64_t m_caching_num_pages;
|
||||
uint64_t m_fault_buffer_size;
|
||||
Buffer m_fault_buffer;
|
||||
Buffer m_download_buffer;
|
||||
std::array<uint64_t, MaxPendingFaults> m_fault_areas {};
|
||||
uint32_t m_current_area = 0;
|
||||
vk::DescriptorSetLayout m_fault_process_desc_layout = nullptr;
|
||||
vk::Pipeline m_fault_process_pipeline = nullptr;
|
||||
vk::PipelineLayout m_fault_process_pipeline_layout = nullptr;
|
||||
};
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_CACHE_FAULTMANAGER_H_
|
||||
+15
-4
@@ -35,8 +35,7 @@ bool GpuResourceManager::InvalidateMemory(uint64_t vaddr, uint64_t size) {
|
||||
}
|
||||
|
||||
bool GpuResourceManager::IsMapped(uint64_t vaddr, uint64_t size) const noexcept {
|
||||
if (vaddr == 0 || size == 0 || vaddr >= TRACKER_ADDRESS_SIZE ||
|
||||
size > TRACKER_ADDRESS_SIZE - vaddr) {
|
||||
if (!GuestRange {vaddr, size}.Valid()) {
|
||||
return false;
|
||||
}
|
||||
std::shared_lock lock(m_mapped_ranges_mutex);
|
||||
@@ -60,10 +59,10 @@ void GpuResourceManager::UnmapMemory(uint64_t vaddr, uint64_t size) {
|
||||
const auto unmap = [this, vaddr, size] {
|
||||
if (m_scheduler.Active()) {
|
||||
const auto tick = m_scheduler.CurrentTick();
|
||||
m_scheduler.FinishCurrent();
|
||||
m_scheduler.Finish();
|
||||
m_scheduler.WaitPriorityOperations(tick);
|
||||
}
|
||||
m_buffer_cache.UnmapMemory(vaddr, size);
|
||||
m_buffer_cache.InvalidateMemory(vaddr, size);
|
||||
m_texture_cache.UnmapMemory(vaddr, size);
|
||||
m_page_manager.OnGpuUnmap(vaddr, size);
|
||||
std::lock_guard lock(m_mapped_ranges_mutex);
|
||||
@@ -76,7 +75,19 @@ void GpuResourceManager::UnmapMemory(uint64_t vaddr, uint64_t size) {
|
||||
m_gpu->SendCommandSync(unmap);
|
||||
}
|
||||
|
||||
void GpuResourceManager::PrepareBda() {
|
||||
std::shared_lock lock(m_mapped_ranges_mutex);
|
||||
m_mapped_ranges.ForEach([this](uint64_t start, uint64_t end) {
|
||||
m_buffer_cache.SynchronizeBuffersInRange(start, end - start);
|
||||
});
|
||||
m_fault_process_pending = true;
|
||||
}
|
||||
|
||||
void GpuResourceManager::RunGarbageCollector() {
|
||||
if (m_fault_process_pending) {
|
||||
m_fault_process_pending = false;
|
||||
m_buffer_cache.ProcessFaultBuffer();
|
||||
}
|
||||
m_texture_cache.ProcessDownloadImages();
|
||||
m_texture_cache.RunGarbageCollector();
|
||||
m_buffer_cache.RunGarbageCollector();
|
||||
|
||||
@@ -30,6 +30,7 @@ public:
|
||||
[[nodiscard]] bool IsMapped(uint64_t vaddr, uint64_t size) const noexcept;
|
||||
void MapMemory(uint64_t vaddr, uint64_t size);
|
||||
void UnmapMemory(uint64_t vaddr, uint64_t size);
|
||||
void PrepareBda();
|
||||
void RunGarbageCollector();
|
||||
|
||||
private:
|
||||
@@ -40,6 +41,7 @@ private:
|
||||
mutable std::shared_mutex m_mapped_ranges_mutex;
|
||||
RangeSet m_mapped_ranges;
|
||||
GuestGpu* m_gpu = nullptr;
|
||||
bool m_fault_process_pending = false;
|
||||
};
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
+26
-11
@@ -73,8 +73,12 @@ Buffer::Buffer(GraphicContext& graphics, CommandScheduler& scheduler, MemoryUsag
|
||||
buffer_info.usage = flags;
|
||||
buffer_info.sharingMode = vk::SharingMode::eExclusive;
|
||||
|
||||
const bool with_bda = bool(flags & vk::BufferUsageFlagBits::eShaderDeviceAddress);
|
||||
const VmaAllocationCreateFlags bda_flag =
|
||||
with_bda ? VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT : 0;
|
||||
VmaAllocationCreateInfo allocation_info {};
|
||||
allocation_info.flags = VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT | AllocationFlags(usage);
|
||||
allocation_info.flags =
|
||||
VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT | bda_flag | AllocationFlags(usage);
|
||||
allocation_info.usage = AllocationUsage(usage);
|
||||
allocation_info.preferredFlags = usage == MemoryUsage::DeviceLocal
|
||||
? VkMemoryPropertyFlags {}
|
||||
@@ -99,6 +103,13 @@ Buffer::Buffer(GraphicContext& graphics, CommandScheduler& scheduler, MemoryUsag
|
||||
m_buffer->memory.type = allocation_result.memoryType;
|
||||
m_buffer->memory.unique_id = VulkanNextMemoryUniqueId();
|
||||
graphics.device.getBufferMemoryRequirements(m_buffer->buffer, &m_buffer->memory.requirements);
|
||||
if (static_cast<bool>(flags & vk::BufferUsageFlagBits::eShaderDeviceAddress)) {
|
||||
vk::BufferDeviceAddressInfo address_info {};
|
||||
address_info.sType = vk::StructureType::eBufferDeviceAddressInfo;
|
||||
address_info.buffer = m_buffer->buffer;
|
||||
m_device_address = graphics.device.getBufferAddress(address_info);
|
||||
EXIT_IF(m_device_address == 0);
|
||||
}
|
||||
|
||||
VkMemoryPropertyFlags properties = 0;
|
||||
vmaGetAllocationMemoryProperties(graphics.allocator, m_buffer->memory.allocation, &properties);
|
||||
@@ -122,6 +133,11 @@ vk::Buffer Buffer::Handle() const noexcept {
|
||||
return m_buffer->buffer;
|
||||
}
|
||||
|
||||
vk::DeviceAddress Buffer::BufferDeviceAddress() const noexcept {
|
||||
EXIT_IF(m_device_address == 0);
|
||||
return m_device_address;
|
||||
}
|
||||
|
||||
bool Buffer::IsInBounds(uint64_t address, uint64_t size) const noexcept {
|
||||
return address >= m_cpu_address && size <= m_size && address - m_cpu_address <= m_size - size;
|
||||
}
|
||||
@@ -141,6 +157,15 @@ void Buffer::Flush(uint64_t offset, uint64_t size) {
|
||||
}
|
||||
}
|
||||
|
||||
void Buffer::Invalidate(uint64_t offset, uint64_t size) {
|
||||
EXIT_IF(m_usage != MemoryUsage::Download || offset > m_size || size > m_size - offset);
|
||||
if (!m_is_coherent && size != 0) {
|
||||
const auto result =
|
||||
vmaInvalidateAllocation(m_graphics->allocator, m_buffer->memory.allocation, offset, size);
|
||||
EXIT_NOT_IMPLEMENTED(static_cast<vk::Result>(result) != vk::Result::eSuccess);
|
||||
}
|
||||
}
|
||||
|
||||
vk::BufferMemoryBarrier Buffer::Barrier(uint64_t offset, uint64_t size, vk::AccessFlags source,
|
||||
vk::AccessFlags destination) const {
|
||||
if (Handle() == nullptr || size == 0 || offset > m_size || size > m_size - offset) {
|
||||
@@ -310,16 +335,6 @@ void StreamBuffer::Commit() {
|
||||
watch.tick = tick;
|
||||
}
|
||||
|
||||
void StreamBuffer::Invalidate(uint64_t offset, uint64_t size) {
|
||||
EXIT_IF(Usage() != MemoryUsage::Download || offset > Size() || size > Size() - offset);
|
||||
if (IsCoherent() || size == 0) {
|
||||
return;
|
||||
}
|
||||
const auto result = vmaInvalidateAllocation(Graphics().allocator,
|
||||
NativeBuffer().memory.allocation, offset, size);
|
||||
EXIT_NOT_IMPLEMENTED(static_cast<vk::Result>(result) != vk::Result::eSuccess);
|
||||
}
|
||||
|
||||
uint64_t StreamBuffer::Copy(const void* source, uint64_t size, uint64_t alignment) {
|
||||
EXIT_IF(source == nullptr);
|
||||
const auto [data, offset] = Map(size, alignment);
|
||||
|
||||
+7
-3
@@ -48,12 +48,14 @@ public:
|
||||
[[nodiscard]] bool IsCoherent() const noexcept { return m_is_coherent; }
|
||||
[[nodiscard]] MemoryUsage Usage() const noexcept { return m_usage; }
|
||||
[[nodiscard]] uint64_t CpuAddress() const noexcept { return m_cpu_address; }
|
||||
[[nodiscard]] vk::DeviceAddress BufferDeviceAddress() const noexcept;
|
||||
[[nodiscard]] uint64_t Offset(uint64_t address) const noexcept {
|
||||
return address - m_cpu_address;
|
||||
}
|
||||
[[nodiscard]] bool IsInBounds(uint64_t address, uint64_t size) const noexcept;
|
||||
void Write(uint64_t offset, const void* source, uint64_t size);
|
||||
void Flush(uint64_t offset, uint64_t size);
|
||||
void Invalidate(uint64_t offset, uint64_t size);
|
||||
void CopyFrom(CommandBuffer& command, const Buffer& source, uint64_t source_offset,
|
||||
uint64_t destination_offset, uint64_t size,
|
||||
vk::AccessFlags source_before = vk::AccessFlagBits::eMemoryWrite,
|
||||
@@ -65,6 +67,10 @@ public:
|
||||
vk::AccessFlagBits::eMemoryWrite);
|
||||
void Fill(uint64_t offset, uint64_t size, uint32_t value);
|
||||
|
||||
// BufferCache state lives directly on the resource.
|
||||
bool is_deleted = false;
|
||||
size_t lru_id = 0;
|
||||
|
||||
protected:
|
||||
[[nodiscard]] GraphicContext& Graphics() const noexcept { return *m_graphics; }
|
||||
[[nodiscard]] CommandScheduler& Scheduler() const noexcept { return *m_scheduler; }
|
||||
@@ -80,6 +86,7 @@ private:
|
||||
MemoryUsage m_usage = MemoryUsage::DeviceLocal;
|
||||
uint64_t m_cpu_address = 0;
|
||||
uint64_t m_size = 0;
|
||||
vk::DeviceAddress m_device_address = 0;
|
||||
std::unique_ptr<VulkanBuffer> m_buffer;
|
||||
std::span<uint8_t> m_mapped;
|
||||
bool m_is_coherent = false;
|
||||
@@ -93,9 +100,6 @@ public:
|
||||
[[nodiscard]] std::pair<uint8_t*, uint64_t> Map(uint64_t size, uint64_t alignment = 0,
|
||||
bool allow_wait = true);
|
||||
void Commit();
|
||||
// Download mappings become visible to the CPU only after their GPU completion tick is free.
|
||||
// Call this from the scheduler's deferred completion operation before reading Mapped().
|
||||
void Invalidate(uint64_t offset, uint64_t size);
|
||||
[[nodiscard]] uint64_t Copy(const void* source, uint64_t size, uint64_t alignment = 0);
|
||||
|
||||
private:
|
||||
|
||||
+222
-359
File diff suppressed because it is too large.
Load diff
+33
-50
@@ -4,17 +4,17 @@
|
||||
#include "common/abi.h"
|
||||
#include "common/common.h"
|
||||
#include "common/lruCache.h"
|
||||
#include "common/slotVector.h"
|
||||
#include "graphics/host_gpu/pageManager.h"
|
||||
#include "graphics/host_gpu/regionManager.h"
|
||||
#include "graphics/host_gpu/renderer/cache/multiLevelPageTable.h"
|
||||
#include "graphics/host_gpu/renderer/image/blitHelper.h"
|
||||
#include "graphics/host_gpu/renderer/image/image.h"
|
||||
#include "graphics/host_gpu/renderer/image/tiler.h"
|
||||
|
||||
#include <compare>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <utility>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
@@ -25,8 +25,6 @@ class BufferCache;
|
||||
class CommandBuffer;
|
||||
class CommandScheduler;
|
||||
class RenderExecutor;
|
||||
class StreamBuffer;
|
||||
class TileManager;
|
||||
struct TextureCacheTestAccess;
|
||||
|
||||
class TextureCache {
|
||||
@@ -57,15 +55,17 @@ public:
|
||||
[[nodiscard]] vk::ImageView FindTexture(ImageId id, const ImageDesc& desc);
|
||||
[[nodiscard]] vk::ImageView FindRenderTarget(ImageId id, const ImageDesc& desc);
|
||||
[[nodiscard]] vk::ImageView FindDepthTarget(ImageId id, const ImageDesc& desc);
|
||||
[[nodiscard]] Image& GetImage(ImageId id);
|
||||
[[nodiscard]] const Image& GetImage(ImageId id) const;
|
||||
void MarkGpuWritten(ImageId id);
|
||||
[[nodiscard]] Image& GetImage(ImageId id) {
|
||||
auto& image = m_slot_images[id];
|
||||
TouchImage(image);
|
||||
return image;
|
||||
}
|
||||
void MarkGpuWritten(ImageId id);
|
||||
|
||||
[[nodiscard]] bool ClearImageFromBuffer(CommandBuffer& command, uint64_t address, uint64_t size,
|
||||
uint32_t packed_clear);
|
||||
void InvalidateMemory(uint64_t address, uint64_t size);
|
||||
[[nodiscard]] bool InvalidateMemoryFromGPU(uint64_t address, uint64_t size,
|
||||
bool formatted_buffer_write = false);
|
||||
void InvalidateMemoryFromGPU(uint64_t address, uint64_t size);
|
||||
[[nodiscard]] RegionInfo QueryRegion(uint64_t address, uint64_t size);
|
||||
|
||||
[[nodiscard]] bool IsMeta(uint64_t address);
|
||||
@@ -86,16 +86,11 @@ private:
|
||||
struct ColorTransferPlan;
|
||||
struct DownloadPlan;
|
||||
|
||||
struct Slot {
|
||||
std::shared_ptr<Image> image;
|
||||
uint32_t generation = 1;
|
||||
};
|
||||
|
||||
struct MetaDataInfo {
|
||||
// A guest metadata-fill dispatch may initialize DCC before its render target is bound.
|
||||
// PendingDcc retains that exact fill until FindRenderTarget classifies the address,
|
||||
// without exposing an unconfirmed buffer address to the normal metadata heuristics.
|
||||
// Keep all surface metadata in this one shadPS4-style entry so CMask/FMask can be
|
||||
// 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 };
|
||||
|
||||
@@ -114,26 +109,21 @@ private:
|
||||
using ImageIds = InlinePageOwnerList<ImageId, 16>;
|
||||
using ImagePageTable = MultiLevelPageTable<ImageIds, 20, 40, 10>;
|
||||
|
||||
[[nodiscard]] Image& ResolveImage(ImageId id);
|
||||
[[nodiscard]] const Image& ResolveImage(ImageId id) const;
|
||||
[[nodiscard]] std::shared_ptr<Image> ResolveOwner(ImageId id) const;
|
||||
[[nodiscard]] ImageId InsertImage(const ImageInfo& info);
|
||||
[[nodiscard]] ImageId GetNullImage(const ImageDesc& desc);
|
||||
void RegisterImage(ImageId id);
|
||||
void UnregisterImage(ImageId id);
|
||||
void DeleteImage(ImageId id);
|
||||
void FreeImage(ImageId id);
|
||||
void RetainImage(CommandBuffer& command, ImageId id);
|
||||
void TouchImage(Image& image);
|
||||
void TrackImage(ImageId id);
|
||||
void TrackImageHead(ImageId id);
|
||||
void TrackImageTail(ImageId id);
|
||||
void UntrackImage(ImageId id);
|
||||
void UntrackImageHead(ImageId id);
|
||||
void UntrackImageTail(ImageId id);
|
||||
void MarkAsMaybeDirty(ImageId id, Image& image);
|
||||
void TrackImageDownload(ImageId id);
|
||||
void TrackImageDownloadLocked(ImageId id, Image& image);
|
||||
[[nodiscard]] ImageId InsertImage(const ImageInfo& info);
|
||||
[[nodiscard]] ImageId GetNullImage(const ImageDesc& desc);
|
||||
void RegisterImage(ImageId id);
|
||||
void UnregisterImage(ImageId id);
|
||||
void DeleteImage(ImageId id);
|
||||
void FreeImage(ImageId id);
|
||||
void TouchImage(Image& image);
|
||||
void TrackImage(ImageId id);
|
||||
void TrackImageHead(ImageId id);
|
||||
void TrackImageTail(ImageId id);
|
||||
void UntrackImage(ImageId id);
|
||||
void UntrackImageHead(ImageId id);
|
||||
void UntrackImageTail(ImageId id);
|
||||
void MarkAsMaybeDirty(ImageId id, Image& image);
|
||||
void TrackImageDownload(ImageId id, Image& image);
|
||||
[[nodiscard]] static bool SameBacking(const ImageInfo& cached, const ImageInfo& requested,
|
||||
bool exact_format);
|
||||
[[nodiscard]] static BindingType UploadBinding(const Image& image);
|
||||
@@ -162,31 +152,24 @@ private:
|
||||
[[nodiscard]] bool CopyD16(Image& destination, Image& source);
|
||||
void CopyImage(ImageId destination, ImageId source);
|
||||
void AssociateStencil(ImageId depth, GuestRange stencil);
|
||||
void AssociateStencilLocked(ImageId depth, GuestRange stencil);
|
||||
void CopyImageMip(ImageId destination, ImageId source, uint32_t mip, uint32_t layer);
|
||||
void ValidateImageDesc(const ImageDesc& desc) const;
|
||||
|
||||
void InvalidateCpuAliases(uint64_t address, uint64_t size);
|
||||
void ClearGpuModified(ImageId id);
|
||||
|
||||
void DownloadImage(ImageId id);
|
||||
[[nodiscard]] bool TryDownloadImage(ImageId id);
|
||||
[[nodiscard]] std::pair<uint8_t*, uint64_t> MapDownload(uint64_t size, uint64_t alignment);
|
||||
void QueueDownload(GuestRange range, StreamBuffer& download, uint8_t* mapped, uint64_t offset);
|
||||
void InvalidateCpuAliases(uint64_t address, uint64_t size);
|
||||
[[nodiscard]] bool TryDownloadImage(ImageId id);
|
||||
|
||||
GraphicContext& m_graphics;
|
||||
CommandScheduler& m_scheduler;
|
||||
TrackingSpinLock m_lock;
|
||||
PageManager& m_page_manager;
|
||||
BlitHelper m_blit_helper;
|
||||
std::unique_ptr<TileManager> m_tiler;
|
||||
TileManager m_tiler;
|
||||
BufferCache& m_buffer_cache;
|
||||
std::vector<Slot> m_slots;
|
||||
std::vector<uint32_t> m_free_slots;
|
||||
Common::SlotVector<Image> m_slot_images;
|
||||
ImagePageTable m_image_page_table;
|
||||
std::map<vk::Format, ImageId> m_null_images;
|
||||
std::unordered_map<vk::Format, ImageId> m_null_images;
|
||||
Common::LeastRecentlyUsedCache<ImageId, uint64_t> m_lru_cache;
|
||||
std::set<ImageId> m_download_images;
|
||||
std::unordered_set<ImageId> m_download_images;
|
||||
std::map<uint64_t, MetaDataInfo> m_surface_metas;
|
||||
uint64_t m_total_used_memory = 0;
|
||||
uint64_t m_trigger_gc_memory = 0;
|
||||
|
||||
@@ -9,12 +9,12 @@
|
||||
#include "graphics/host_gpu/graphicContext.h"
|
||||
#include "graphics/host_gpu/renderer/debug.h"
|
||||
#include "graphics/host_gpu/renderer/image/textureCommon.h"
|
||||
#include "graphics/host_gpu/renderer/pipeline/descriptorCache.h"
|
||||
#include "graphics/host_gpu/renderer/render.h"
|
||||
#include "graphics/host_gpu/renderer/renderContext.h"
|
||||
#include "graphics/host_gpu/vulkanCommon.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
@@ -44,7 +44,7 @@ static void ResolveDccClearInfo(RenderColorInfo& info, vk::Format format, bool h
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
|
||||
void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, RenderCommandBuffer& buffer,
|
||||
void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, CommandBuffer& buffer,
|
||||
RenderColorInfo& r,
|
||||
uint32_t render_target_slice_offset,
|
||||
uint32_t render_target_slot, bool ignore_target_mask,
|
||||
@@ -144,12 +144,23 @@ void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, RenderCommandB
|
||||
uint32_t pitch = 0;
|
||||
uint64_t size = 0;
|
||||
bool tile = false;
|
||||
const bool volume = rt.attrib3.dimension == 2;
|
||||
if (rt.attrib3.dimension != 1 && !volume) {
|
||||
static constexpr std::array image_types {Prospero::ImageType::kColor1D,
|
||||
Prospero::ImageType::kColor2D,
|
||||
Prospero::ImageType::kColor3D};
|
||||
if (rt.attrib3.dimension >= image_types.size()) {
|
||||
EXIT("unsupported render-target dimension: %u\n", rt.attrib3.dimension);
|
||||
}
|
||||
const auto image_type = image_types[rt.attrib3.dimension];
|
||||
const bool is_1d = image_type == Prospero::ImageType::kColor1D;
|
||||
const bool volume = image_type == Prospero::ImageType::kColor3D;
|
||||
if (is_1d && rt.attrib2.height != 0) {
|
||||
EXIT("1D render target has nonzero height: %u\n", rt.attrib2.height);
|
||||
}
|
||||
if (!volume && rt.attrib3.depth != 0) {
|
||||
EXIT("2D render target has nonzero depth: %u\n", rt.attrib3.depth);
|
||||
EXIT("non-3D render target has nonzero depth: %u\n", rt.attrib3.depth);
|
||||
}
|
||||
if (is_1d && samples != 1) {
|
||||
EXIT("multisampled 1D render targets are unsupported\n");
|
||||
}
|
||||
if (volume && samples != 1) {
|
||||
EXIT("multisampled 3D render targets are unsupported\n");
|
||||
@@ -193,13 +204,11 @@ void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, RenderCommandB
|
||||
if (texture_tile &&
|
||||
(!TileGetTextureBlockLayout(transfer_format, rt.attrib3.tile_mode, volume,
|
||||
texture_tile_layout) ||
|
||||
rt.pitch.pitch_div8_minus1 != 0 ||
|
||||
(rt.base.addr & (texture_tile_layout.block.block_size - 1u)) != 0 ||
|
||||
rt.info.fmask_compression_enable || rt.info.fmask_data_compression_disable ||
|
||||
rt.info.fmask_one_frag_mode || rt.info.cmask_fast_clear_enable ||
|
||||
rt.info.dcc_compression_enable || rt.info.cmask_is_linear != 0 ||
|
||||
rt.info.cmask_addr_type != 0 || rt.info.alt_tile_mode || rt.cmask.addr != 0 ||
|
||||
rt.fmask.addr != 0 || rt.dcc_addr.addr != 0 || rt.dcc.data_write_on_dcc_clear_to_reg)) {
|
||||
rt.info.dcc_compression_enable || rt.cmask.addr != 0 || rt.fmask.addr != 0 ||
|
||||
rt.dcc_addr.addr != 0 || rt.dcc.data_write_on_dcc_clear_to_reg)) {
|
||||
EXIT("unsupported texture-tiled render target: addr=0x%016" PRIx64 " tile=%u"
|
||||
" dimension=%u depth=%u levels=%u layer=%u/%u samples=%u fragments=%u bpe=%u"
|
||||
" cmask=0x%016" PRIx64 " fmask=0x%016" PRIx64 " dcc=0x%016" PRIx64 "\n",
|
||||
@@ -208,15 +217,14 @@ void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, RenderCommandB
|
||||
rt.attrib.num_fragments, bytes_per_element, rt.cmask.addr, rt.fmask.addr,
|
||||
rt.dcc_addr.addr);
|
||||
}
|
||||
if ((standard64 || depth_tile) && (rt.attrib3.dimension != 1 || rt.attrib3.depth != 0 ||
|
||||
view.base_layer != 0 || view.image_layers != 1)) {
|
||||
if ((standard64 || depth_tile) &&
|
||||
(rt.attrib3.dimension != 1 || rt.attrib3.depth != 0 ||
|
||||
(depth_tile && (view.base_layer != 0 || view.image_layers != 1)))) {
|
||||
EXIT("unsupported 64KB texture-tiled render-target view: dimension=%u depth=%u"
|
||||
" layer=%u/%u\n",
|
||||
rt.attrib3.dimension, rt.attrib3.depth, view.base_layer, view.image_layers);
|
||||
}
|
||||
if (rt.pitch.pitch_div8_minus1 != 0) {
|
||||
pitch = (rt.pitch.pitch_div8_minus1 + 1u) << 3u;
|
||||
} else if (tile) {
|
||||
if (tile) {
|
||||
if (volume) {
|
||||
pitch = TileGetTexturePitch(transfer_format, width, rt.attrib3.tile_mode);
|
||||
} else if (texture_tile) {
|
||||
@@ -282,12 +290,6 @@ void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, RenderCommandB
|
||||
mip_sizes[0] = {static_cast<uint32_t>(size), 0, 0, 0, 0, 0};
|
||||
mip_padded[0] = {pitch, height};
|
||||
}
|
||||
if (rt.slice.slice_div64_minus1 != 0 &&
|
||||
(static_cast<uint64_t>(rt.slice.slice_div64_minus1) + 1u) * 64u != size) {
|
||||
EXIT("render-target slice span mismatch: encoded=0x%016" PRIx64 " derived=0x%016" PRIx64
|
||||
"\n",
|
||||
(static_cast<uint64_t>(rt.slice.slice_div64_minus1) + 1u) * 64u, size);
|
||||
}
|
||||
if (size == 0 || (!volume && size > UINT64_MAX / view.image_layers)) {
|
||||
EXIT("render-target memory footprint is invalid\n");
|
||||
}
|
||||
@@ -297,7 +299,7 @@ void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, RenderCommandB
|
||||
if (backing_size == 0) {
|
||||
EXIT("render-target backing is empty\n");
|
||||
}
|
||||
if (backing_size > TRACKER_ADDRESS_SIZE - rt.base.addr) {
|
||||
if (!GuestRange {rt.base.addr, backing_size}.Valid()) {
|
||||
EXIT("render-target backing range is invalid\n");
|
||||
}
|
||||
|
||||
@@ -326,7 +328,7 @@ void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, RenderCommandB
|
||||
desc.info.data = {rt.base.addr, backing_size};
|
||||
desc.info.pixel_format = target_format.format;
|
||||
desc.info.guest_format = transfer_format;
|
||||
desc.info.type = volume ? Prospero::ImageType::kColor3D : Prospero::ImageType::kColor2D;
|
||||
desc.info.type = image_type;
|
||||
desc.info.extent = {width, height, depth};
|
||||
desc.info.resources = {levels, volume ? 1u : view.image_layers};
|
||||
desc.info.pitch = pitch;
|
||||
@@ -365,8 +367,13 @@ void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, RenderCommandB
|
||||
};
|
||||
}
|
||||
desc.view_info.format = target_format.format;
|
||||
desc.view_info.type =
|
||||
view.layer_count == 1 ? vk::ImageViewType::e2D : vk::ImageViewType::e2DArray;
|
||||
if (is_1d) {
|
||||
desc.view_info.type =
|
||||
view.layer_count == 1 ? vk::ImageViewType::e1D : vk::ImageViewType::e1DArray;
|
||||
} else {
|
||||
desc.view_info.type =
|
||||
view.layer_count == 1 ? vk::ImageViewType::e2D : vk::ImageViewType::e2DArray;
|
||||
}
|
||||
desc.view_info.aspect = vk::ImageAspectFlagBits::eColor;
|
||||
desc.view_info.base_level = rt.view.current_mip_level;
|
||||
desc.view_info.level_count = 1;
|
||||
|
||||
@@ -10,8 +10,6 @@
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
class RenderCommandBuffer;
|
||||
|
||||
enum class RenderColorType {
|
||||
NoColorOutput,
|
||||
RenderTexture,
|
||||
|
||||
@@ -1,96 +1,97 @@
|
||||
#include "graphics/host_gpu/renderer/commandScheduler.h"
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "graphics/host_gpu/graphicContext.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
#include <optional>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
static thread_local CommandScheduler* g_deferred_callback_scheduler = nullptr;
|
||||
|
||||
void CommandSlot::Reset() {
|
||||
EXIT_IF(buffer == nullptr);
|
||||
const auto result = buffer.reset(vk::CommandBufferResetFlagBits::eReleaseResources);
|
||||
if (result != vk::Result::eSuccess) {
|
||||
EXIT("failed to reset Vulkan command buffer: %s (%d)\n", VulkanToString(result).c_str(),
|
||||
static_cast<int>(result));
|
||||
}
|
||||
namespace {
|
||||
|
||||
void ReportVulkanFatal(const char* what, vk::Result result, uint64_t tick, uint32_t debug_op,
|
||||
uint64_t debug_submit, uint32_t arg0, uint32_t arg1, uint32_t arg2,
|
||||
uint32_t arg3, uint64_t arg4) {
|
||||
LOGF("%s failed: %s (%d), tick=%" PRIu64 " debug_op=%u debug_submit=%" PRIu64
|
||||
" args=%u,%u,%u,%u,0x%016" PRIx64 "\n",
|
||||
what, VulkanToString(result).c_str(), static_cast<int>(result), tick, debug_op,
|
||||
debug_submit, arg0, arg1, arg2, arg3, arg4);
|
||||
std::printf("%s failed: %s (%d), tick=%" PRIu64 " debug_op=%u debug_submit=%" PRIu64
|
||||
" args=%u,%u,%u,%u,0x%016" PRIx64 "\n",
|
||||
what, VulkanToString(result).c_str(), static_cast<int>(result), tick, debug_op,
|
||||
debug_submit, arg0, arg1, arg2, arg3, arg4);
|
||||
std::fflush(stdout);
|
||||
}
|
||||
|
||||
CommandScheduler::CommandPool::~CommandPool() {
|
||||
Destroy();
|
||||
}
|
||||
|
||||
void CommandScheduler::CommandPool::Create(GraphicContext& graphics) {
|
||||
EXIT_IF(m_pool != nullptr || m_graphics != nullptr ||
|
||||
graphics.queue_family == static_cast<uint32_t>(-1));
|
||||
m_graphics = &graphics;
|
||||
} // namespace
|
||||
|
||||
CommandScheduler::CommandPool::CommandPool(GraphicContext& graphics, MasterSemaphore& master)
|
||||
: m_graphics(graphics), m_master(master) {
|
||||
EXIT_IF(graphics.queue_family == static_cast<uint32_t>(-1));
|
||||
vk::CommandPoolCreateInfo create {};
|
||||
create.sType = vk::StructureType::eCommandPoolCreateInfo;
|
||||
create.queueFamilyIndex = graphics.queue_family;
|
||||
create.flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer;
|
||||
create.flags = vk::CommandPoolCreateFlagBits::eTransient |
|
||||
vk::CommandPoolCreateFlagBits::eResetCommandBuffer;
|
||||
const auto result = graphics.device.createCommandPool(&create, nullptr, &m_pool);
|
||||
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess || m_pool == nullptr);
|
||||
}
|
||||
|
||||
CommandSlot* CommandScheduler::CommandPool::CreateSlot() {
|
||||
EXIT_IF(m_graphics == nullptr);
|
||||
auto& graphics = *m_graphics;
|
||||
CommandScheduler::CommandPool::~CommandPool() {
|
||||
m_graphics.device.destroyCommandPool(m_pool, nullptr);
|
||||
}
|
||||
|
||||
size_t CommandScheduler::CommandPool::Grow() {
|
||||
const auto first = m_ticks.size();
|
||||
m_ticks.resize(first + GrowStep);
|
||||
m_buffers.resize(first + GrowStep);
|
||||
|
||||
vk::CommandBufferAllocateInfo allocate {};
|
||||
allocate.sType = vk::StructureType::eCommandBufferAllocateInfo;
|
||||
allocate.commandPool = m_pool;
|
||||
allocate.level = vk::CommandBufferLevel::ePrimary;
|
||||
allocate.commandBufferCount = 1;
|
||||
vk::CommandBuffer buffer = nullptr;
|
||||
EXIT_IF(graphics.device.allocateCommandBuffers(&allocate, &buffer) != vk::Result::eSuccess);
|
||||
|
||||
vk::FenceCreateInfo fence_create {};
|
||||
fence_create.sType = vk::StructureType::eFenceCreateInfo;
|
||||
fence_create.flags = vk::FenceCreateFlagBits::eSignaled;
|
||||
vk::Fence fence = nullptr;
|
||||
if (graphics.device.createFence(&fence_create, nullptr, &fence) != vk::Result::eSuccess) {
|
||||
graphics.device.freeCommandBuffers(m_pool, 1, &buffer);
|
||||
EXIT("failed to create command-buffer fence\n");
|
||||
}
|
||||
|
||||
auto& slot = m_slots.emplace_back();
|
||||
slot.pool_mutex = &m_mutex;
|
||||
slot.id = static_cast<uint32_t>(m_slots.size() - 1);
|
||||
slot.buffer = buffer;
|
||||
slot.fence = fence;
|
||||
return &slot;
|
||||
allocate.commandBufferCount = static_cast<uint32_t>(GrowStep);
|
||||
EXIT_IF(m_graphics.device.allocateCommandBuffers(&allocate, m_buffers.data() + first) !=
|
||||
vk::Result::eSuccess);
|
||||
return first;
|
||||
}
|
||||
|
||||
CommandSlot* CommandScheduler::CommandPool::Allocate(GraphicContext& graphics) {
|
||||
Common::LockGuard lock(m_mutex);
|
||||
if (m_pool == nullptr) {
|
||||
Create(graphics);
|
||||
}
|
||||
EXIT_IF(m_graphics != &graphics);
|
||||
auto found = std::ranges::find_if(m_slots, [](const auto& slot) { return !slot.busy; });
|
||||
auto* slot = found != m_slots.end() ? &*found : CreateSlot();
|
||||
slot->busy = true;
|
||||
slot->Reset();
|
||||
return slot;
|
||||
}
|
||||
vk::CommandBuffer CommandScheduler::CommandPool::Commit() {
|
||||
auto gpu_tick = m_master.KnownGpuTick();
|
||||
const auto search = [this, &gpu_tick](size_t begin, size_t end) -> std::optional<size_t> {
|
||||
for (size_t index = begin; index < end; ++index) {
|
||||
if (gpu_tick >= m_ticks[index]) {
|
||||
m_ticks[index] = m_master.CurrentTick();
|
||||
return index;
|
||||
}
|
||||
}
|
||||
return std::nullopt;
|
||||
};
|
||||
|
||||
void CommandScheduler::CommandPool::Destroy() {
|
||||
Common::LockGuard lock(m_mutex);
|
||||
if (m_pool == nullptr) {
|
||||
return;
|
||||
auto found = search(m_hint, m_ticks.size());
|
||||
if (!found) {
|
||||
m_master.Refresh();
|
||||
gpu_tick = m_master.KnownGpuTick();
|
||||
found = search(m_hint, m_ticks.size());
|
||||
}
|
||||
EXIT_IF(std::ranges::any_of(m_slots, [](const auto& slot) { return slot.busy; }));
|
||||
EXIT_IF(m_graphics == nullptr);
|
||||
for (const auto& slot: m_slots) {
|
||||
m_graphics->device.destroyFence(slot.fence, nullptr);
|
||||
if (!found) {
|
||||
found = search(0, m_hint);
|
||||
}
|
||||
m_graphics->device.destroyCommandPool(m_pool, nullptr);
|
||||
m_slots.clear();
|
||||
m_pool = nullptr;
|
||||
m_graphics = nullptr;
|
||||
if (!found) {
|
||||
found = Grow();
|
||||
m_ticks[*found] = m_master.CurrentTick();
|
||||
}
|
||||
|
||||
m_hint = *found + 1;
|
||||
if (m_hint == m_ticks.size()) {
|
||||
m_hint = 0;
|
||||
}
|
||||
return m_buffers[*found];
|
||||
}
|
||||
|
||||
bool CommandScheduler::InDeferredOperation() noexcept {
|
||||
@@ -99,10 +100,8 @@ bool CommandScheduler::InDeferredOperation() noexcept {
|
||||
|
||||
CommandScheduler::CommandScheduler(RenderContext& context, GraphicContext& graphics)
|
||||
: m_master(graphics), m_context(context), m_graphics(graphics),
|
||||
m_priority_thread([this](std::stop_token stop) { PriorityOperationsThread(stop); }) {
|
||||
m_buffers.reserve(CommandBufferGrowStep);
|
||||
m_buffer_ticks.reserve(CommandBufferGrowStep);
|
||||
}
|
||||
m_command_pool(graphics, m_master), m_command(*this),
|
||||
m_priority_thread([this](std::stop_token stop) { PriorityOperationsThread(stop); }) {}
|
||||
|
||||
CommandScheduler::~CommandScheduler() {
|
||||
Shutdown();
|
||||
@@ -128,9 +127,11 @@ void CommandScheduler::Shutdown() {
|
||||
}
|
||||
m_operation_state = OperationState::Draining;
|
||||
}
|
||||
if (Active() && m_recording) {
|
||||
Finish();
|
||||
if (!m_command.IsInvalid()) {
|
||||
Submit();
|
||||
}
|
||||
m_master.Wait(CurrentTick() - 1);
|
||||
PopPendingOperations();
|
||||
DrainPriorityOperations();
|
||||
m_priority_thread.request_stop();
|
||||
m_operation_available.notify_all();
|
||||
@@ -156,14 +157,10 @@ void CommandScheduler::Begin(HW::Context& registers, HW::UserConfig& user_config
|
||||
m_user_config = &user_config;
|
||||
m_shaders = &shaders;
|
||||
|
||||
if (!Active()) {
|
||||
m_current = static_cast<int>(GrowCommandBuffers());
|
||||
}
|
||||
|
||||
BindCurrent();
|
||||
if (!m_recording) {
|
||||
Current().Begin();
|
||||
m_recording = true;
|
||||
if (m_command.IsInvalid()) {
|
||||
BeginNext();
|
||||
} else {
|
||||
BindCurrent();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,7 +169,7 @@ void CommandScheduler::BeginRendering(const RenderState& state) {
|
||||
}
|
||||
|
||||
void CommandScheduler::EndRendering() {
|
||||
if (Active() && m_recording) {
|
||||
if (Active() && !m_command.IsInvalid()) {
|
||||
Current().EndRendering();
|
||||
}
|
||||
}
|
||||
@@ -183,55 +180,40 @@ void CommandScheduler::Flush() {
|
||||
}
|
||||
|
||||
void CommandScheduler::Flush(SubmitInfo& submit) {
|
||||
SubmitCurrent(submit);
|
||||
Submit(submit);
|
||||
BeginNext();
|
||||
}
|
||||
|
||||
CommandBuffer& CommandScheduler::FlushAndGetSubmitted() {
|
||||
SubmitInfo submit;
|
||||
auto& submitted = SubmitCurrent(submit);
|
||||
void CommandScheduler::FlushAndWait() {
|
||||
const auto tick = Submit();
|
||||
m_master.Wait(tick);
|
||||
BeginNext();
|
||||
return submitted;
|
||||
}
|
||||
|
||||
void CommandScheduler::Finish() {
|
||||
CheckActive();
|
||||
const auto tick = CurrentTick();
|
||||
if (m_recording) {
|
||||
SubmitInfo submit;
|
||||
SubmitCurrent(submit);
|
||||
if (!m_command.IsInvalid()) {
|
||||
Submit();
|
||||
}
|
||||
for (auto& buffer: m_buffers) {
|
||||
buffer->WaitForFenceAndReset();
|
||||
}
|
||||
m_master.Wait(tick);
|
||||
m_master.Wait(CurrentTick() - 1);
|
||||
BeginNext();
|
||||
PopPendingOperations();
|
||||
BindCurrent();
|
||||
Current().Begin();
|
||||
m_recording = true;
|
||||
}
|
||||
|
||||
void CommandScheduler::FinishCurrent() {
|
||||
SubmitInfo submit;
|
||||
auto& submitted = SubmitCurrent(submit);
|
||||
submitted.WaitForFenceAndReset();
|
||||
m_master.Refresh();
|
||||
PopPendingOperations();
|
||||
submitted.Begin();
|
||||
m_recording = true;
|
||||
}
|
||||
|
||||
void CommandScheduler::Wait(uint64_t tick) {
|
||||
CheckActive();
|
||||
EXIT_IF(tick > CurrentTick());
|
||||
if (tick >= CurrentTick()) {
|
||||
if (tick == CurrentTick()) {
|
||||
CheckActive();
|
||||
// A stream-buffer wrap can wait while a draw is being prepared through a reference to
|
||||
// Current(). Recycle the same command object so that reference remains valid.
|
||||
FinishCurrent();
|
||||
return;
|
||||
// Current(). The wrapper stays stable while its pooled Vulkan buffer is retired. Deferred
|
||||
// resources are released only at the next GPU operation boundary.
|
||||
const auto submitted_tick = Submit();
|
||||
EXIT_IF(submitted_tick != tick);
|
||||
m_master.Wait(tick);
|
||||
BeginNext();
|
||||
} else {
|
||||
m_master.Wait(tick);
|
||||
}
|
||||
m_master.Wait(tick);
|
||||
PopPendingOperations();
|
||||
}
|
||||
|
||||
void CommandScheduler::PopPendingOperations() {
|
||||
@@ -243,17 +225,18 @@ void CommandScheduler::PopPendingOperations(bool refresh_gpu_tick) {
|
||||
m_master.Refresh();
|
||||
}
|
||||
for (;;) {
|
||||
Common::UniqueFunction<void> callback;
|
||||
PendingOperation operation;
|
||||
{
|
||||
std::lock_guard lock(m_operation_mutex);
|
||||
if (m_pending_operations.empty() ||
|
||||
!m_master.IsFree(m_pending_operations.front().tick)) {
|
||||
return;
|
||||
}
|
||||
callback = std::move(m_pending_operations.front().callback);
|
||||
operation = std::move(m_pending_operations.front());
|
||||
m_pending_operations.pop();
|
||||
}
|
||||
RunOperation(std::move(callback));
|
||||
WaitPriorityOperations(operation.tick);
|
||||
RunOperation(std::move(operation.callback));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -359,86 +342,81 @@ bool CommandScheduler::IsFree(uint64_t tick) {
|
||||
return m_master.IsFree(tick);
|
||||
}
|
||||
|
||||
CommandSlot* CommandScheduler::AllocateCommandBuffer() {
|
||||
return m_command_pool.Allocate(m_graphics);
|
||||
}
|
||||
|
||||
uint64_t CommandScheduler::NextSubmitSequence() noexcept {
|
||||
return m_submit_sequence.fetch_add(1, std::memory_order_relaxed) + 1;
|
||||
}
|
||||
|
||||
void CommandScheduler::CheckActive() const {
|
||||
EXIT_IF(!Active() || static_cast<size_t>(m_current) >= m_buffers.size());
|
||||
EXIT_IF(!Active());
|
||||
}
|
||||
|
||||
RenderCommandBuffer& CommandScheduler::Current() const {
|
||||
CommandBuffer& CommandScheduler::Current() {
|
||||
CheckActive();
|
||||
EXIT_IF(m_buffers[m_current] == nullptr);
|
||||
return *m_buffers[m_current];
|
||||
return m_command;
|
||||
}
|
||||
|
||||
void CommandScheduler::BindCurrent() const {
|
||||
void CommandScheduler::BindCurrent() {
|
||||
EXIT_IF(m_registers == nullptr || m_user_config == nullptr || m_shaders == nullptr);
|
||||
Current().Bind(*m_registers, *m_user_config, *m_shaders);
|
||||
m_command.Bind(*m_registers, *m_user_config, *m_shaders);
|
||||
}
|
||||
|
||||
CommandBuffer& CommandScheduler::SubmitCurrent(SubmitInfo& submit) {
|
||||
CheckActive();
|
||||
EXIT_IF(!m_recording);
|
||||
auto& submitted = Current();
|
||||
submitted.End();
|
||||
const auto signal_tick = m_master.NextTick();
|
||||
submit.AddSignal(m_master.Handle(), signal_tick);
|
||||
submitted.Execute(submit);
|
||||
m_buffer_ticks[static_cast<size_t>(m_current)] = signal_tick;
|
||||
m_recording = false;
|
||||
return submitted;
|
||||
CommandBuffer& CommandScheduler::BeginCommand() {
|
||||
EXIT_IF(!m_command.IsInvalid());
|
||||
m_command.m_buffer = m_command_pool.Commit();
|
||||
m_command.Begin();
|
||||
return m_command;
|
||||
}
|
||||
|
||||
int CommandScheduler::FindReusableBuffer(uint64_t gpu_tick) const {
|
||||
EXIT_IF(m_buffer_ticks.size() != m_buffers.size());
|
||||
const auto buffer_count = m_buffers.size();
|
||||
for (size_t offset = 1; offset <= buffer_count; ++offset) {
|
||||
const auto candidate = (static_cast<size_t>(m_current) + offset) % buffer_count;
|
||||
if (gpu_tick >= m_buffer_ticks[candidate]) {
|
||||
return static_cast<int>(candidate);
|
||||
}
|
||||
uint64_t CommandScheduler::Submit(SubmitInfo submit) {
|
||||
EXIT_IF(m_command.IsInvalid());
|
||||
EXIT_IF(submit.num_wait_semaphores > SubmitInfo::MaxSemaphores ||
|
||||
submit.num_signal_semaphores >= SubmitInfo::MaxSemaphores);
|
||||
|
||||
m_command.End();
|
||||
const auto buffer = m_command.m_buffer;
|
||||
auto& graphics = m_graphics;
|
||||
EXIT_IF(graphics.queue == nullptr);
|
||||
|
||||
vk::Result result;
|
||||
uint64_t tick;
|
||||
{
|
||||
Common::LockGuard lock(graphics.queue_mutex);
|
||||
tick = m_master.NextTick();
|
||||
submit.AddSignal(m_master.Handle(), tick);
|
||||
|
||||
vk::TimelineSemaphoreSubmitInfo timeline_info {};
|
||||
timeline_info.sType = vk::StructureType::eTimelineSemaphoreSubmitInfo;
|
||||
timeline_info.waitSemaphoreValueCount = submit.num_wait_semaphores;
|
||||
timeline_info.pWaitSemaphoreValues = submit.wait_ticks.data();
|
||||
timeline_info.signalSemaphoreValueCount = submit.num_signal_semaphores;
|
||||
timeline_info.pSignalSemaphoreValues = submit.signal_ticks.data();
|
||||
|
||||
vk::SubmitInfo submit_info {};
|
||||
submit_info.sType = vk::StructureType::eSubmitInfo;
|
||||
submit_info.pNext = &timeline_info;
|
||||
submit_info.waitSemaphoreCount = submit.num_wait_semaphores;
|
||||
submit_info.pWaitSemaphores = submit.wait_semaphores.data();
|
||||
submit_info.pWaitDstStageMask = submit.wait_stages.data();
|
||||
submit_info.commandBufferCount = 1;
|
||||
submit_info.pCommandBuffers = &buffer;
|
||||
submit_info.signalSemaphoreCount = submit.num_signal_semaphores;
|
||||
submit_info.pSignalSemaphores = submit.signal_semaphores.data();
|
||||
|
||||
result = graphics.queue.submit(1, &submit_info, nullptr);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
size_t CommandScheduler::GrowCommandBuffers() {
|
||||
const auto first = m_buffers.size();
|
||||
const auto end = first + CommandBufferGrowStep;
|
||||
m_buffers.reserve(end);
|
||||
m_buffer_ticks.reserve(end);
|
||||
for (size_t i = first; i < end; ++i) {
|
||||
m_buffers.emplace_back(std::make_unique<RenderCommandBuffer>(*this));
|
||||
m_buffer_ticks.push_back(0);
|
||||
if (result != vk::Result::eSuccess) {
|
||||
ReportVulkanFatal("vkQueueSubmit", result, tick, m_command.m_debug_op,
|
||||
m_command.m_debug_submit_id, m_command.m_debug_arg0,
|
||||
m_command.m_debug_arg1, m_command.m_debug_arg2, m_command.m_debug_arg3,
|
||||
m_command.m_debug_arg4);
|
||||
}
|
||||
return first;
|
||||
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess);
|
||||
|
||||
m_command.m_buffer = nullptr;
|
||||
return tick;
|
||||
}
|
||||
|
||||
void CommandScheduler::BeginNext() {
|
||||
EXIT_IF(m_recording);
|
||||
|
||||
auto candidate = FindReusableBuffer(m_master.KnownGpuTick());
|
||||
bool refreshed = false;
|
||||
if (candidate < 0) {
|
||||
m_master.Refresh();
|
||||
refreshed = true;
|
||||
candidate = FindReusableBuffer(m_master.KnownGpuTick());
|
||||
}
|
||||
if (candidate < 0) {
|
||||
candidate = static_cast<int>(GrowCommandBuffers());
|
||||
}
|
||||
|
||||
m_current = candidate;
|
||||
Current().WaitForFenceAndReset();
|
||||
PopPendingOperations(!refreshed);
|
||||
EXIT_IF(!m_command.IsInvalid());
|
||||
BindCurrent();
|
||||
Current().Begin();
|
||||
m_recording = true;
|
||||
BeginCommand();
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
@@ -6,11 +6,7 @@
|
||||
#include "graphics/host_gpu/renderer/masterSemaphore.h"
|
||||
#include "graphics/host_gpu/renderer/render.h"
|
||||
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
#include <deque>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
|
||||
#include <queue>
|
||||
@@ -20,16 +16,6 @@
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
struct CommandSlot {
|
||||
Common::Mutex* pool_mutex = nullptr;
|
||||
uint32_t id = 0;
|
||||
vk::CommandBuffer buffer = nullptr;
|
||||
vk::Fence fence = nullptr;
|
||||
bool busy = false;
|
||||
|
||||
void Reset();
|
||||
};
|
||||
|
||||
class CommandScheduler {
|
||||
public:
|
||||
CommandScheduler(RenderContext& context, GraphicContext& graphics);
|
||||
@@ -41,9 +27,10 @@ public:
|
||||
void EndRendering();
|
||||
void Flush();
|
||||
void Flush(SubmitInfo& submit);
|
||||
CommandBuffer& FlushAndGetSubmitted();
|
||||
void FlushAndWait();
|
||||
void Finish();
|
||||
void FinishCurrent();
|
||||
CommandBuffer& BeginCommand();
|
||||
uint64_t Submit(SubmitInfo submit = {});
|
||||
// Deferred callbacks can observe an externally owned drain, but cannot initiate shutdown:
|
||||
// the priority runner cannot join itself.
|
||||
void Shutdown();
|
||||
@@ -55,34 +42,35 @@ public:
|
||||
void DeferPriorityOperation(Common::UniqueFunction<void>&& operation);
|
||||
[[nodiscard]] static bool InDeferredOperation() noexcept;
|
||||
|
||||
[[nodiscard]] bool Active() const noexcept { return m_current >= 0; }
|
||||
void CheckActive() const;
|
||||
RenderCommandBuffer& Current() const;
|
||||
[[nodiscard]] uint64_t CurrentTick() const noexcept { return m_master.CurrentTick(); }
|
||||
[[nodiscard]] bool IsFree(uint64_t tick);
|
||||
[[nodiscard]] RenderContext& Context() const noexcept { return m_context; }
|
||||
[[nodiscard]] GraphicContext& Graphics() const noexcept { return m_graphics; }
|
||||
[[nodiscard]] bool Active() const noexcept { return m_registers != nullptr; }
|
||||
void CheckActive() const;
|
||||
CommandBuffer& Current();
|
||||
[[nodiscard]] uint64_t CurrentTick() const noexcept { return m_master.CurrentTick(); }
|
||||
[[nodiscard]] bool IsFree(uint64_t tick);
|
||||
[[nodiscard]] MasterSemaphore& GetMasterSemaphore() noexcept { return m_master; }
|
||||
[[nodiscard]] RenderContext& Context() const noexcept { return m_context; }
|
||||
[[nodiscard]] GraphicContext& Graphics() const noexcept { return m_graphics; }
|
||||
|
||||
private:
|
||||
static constexpr size_t CommandBufferGrowStep = 4;
|
||||
|
||||
class CommandPool {
|
||||
public:
|
||||
CommandPool() = default;
|
||||
CommandPool(GraphicContext& graphics, MasterSemaphore& master);
|
||||
~CommandPool();
|
||||
KYTY_CLASS_NO_COPY(CommandPool);
|
||||
|
||||
CommandSlot* Allocate(GraphicContext& graphics);
|
||||
vk::CommandBuffer Commit();
|
||||
|
||||
private:
|
||||
void Create(GraphicContext& graphics);
|
||||
CommandSlot* CreateSlot();
|
||||
void Destroy();
|
||||
static constexpr size_t GrowStep = 4;
|
||||
|
||||
GraphicContext* m_graphics = nullptr;
|
||||
Common::Mutex m_mutex;
|
||||
vk::CommandPool m_pool = nullptr;
|
||||
std::deque<CommandSlot> m_slots;
|
||||
size_t Grow();
|
||||
|
||||
GraphicContext& m_graphics;
|
||||
MasterSemaphore& m_master;
|
||||
vk::CommandPool m_pool = nullptr;
|
||||
std::vector<vk::CommandBuffer> m_buffers;
|
||||
std::vector<uint64_t> m_ticks;
|
||||
size_t m_hint = 0;
|
||||
};
|
||||
|
||||
enum class OperationState { Open, Draining, Closed };
|
||||
@@ -92,39 +80,28 @@ private:
|
||||
uint64_t tick = 0;
|
||||
};
|
||||
|
||||
void BindCurrent() const;
|
||||
CommandBuffer& SubmitCurrent(SubmitInfo& submit);
|
||||
void BeginNext();
|
||||
void PopPendingOperations(bool refresh_gpu_tick);
|
||||
[[nodiscard]] int FindReusableBuffer(uint64_t gpu_tick) const;
|
||||
[[nodiscard]] size_t GrowCommandBuffers();
|
||||
void PriorityOperationsThread(std::stop_token stop);
|
||||
void RunOperation(Common::UniqueFunction<void>&& operation);
|
||||
[[nodiscard]] CommandSlot* AllocateCommandBuffer();
|
||||
[[nodiscard]] uint64_t NextSubmitSequence() noexcept;
|
||||
void BindCurrent();
|
||||
void BeginNext();
|
||||
void PopPendingOperations(bool refresh_gpu_tick);
|
||||
void PriorityOperationsThread(std::stop_token stop);
|
||||
void RunOperation(Common::UniqueFunction<void>&& operation);
|
||||
|
||||
MasterSemaphore m_master;
|
||||
RenderContext& m_context;
|
||||
GraphicContext& m_graphics;
|
||||
CommandPool m_command_pool;
|
||||
std::vector<std::unique_ptr<RenderCommandBuffer>> m_buffers;
|
||||
std::vector<uint64_t> m_buffer_ticks;
|
||||
std::queue<PendingOperation> m_pending_operations;
|
||||
std::queue<PendingOperation> m_priority_operations;
|
||||
std::mutex m_operation_mutex;
|
||||
std::condition_variable m_operation_available;
|
||||
std::jthread m_priority_thread;
|
||||
bool m_priority_active = false;
|
||||
uint64_t m_priority_active_tick = 0;
|
||||
OperationState m_operation_state = OperationState::Open;
|
||||
int m_current = -1;
|
||||
bool m_recording = false;
|
||||
HW::Context* m_registers = nullptr;
|
||||
HW::UserConfig* m_user_config = nullptr;
|
||||
HW::Shader* m_shaders = nullptr;
|
||||
std::atomic<uint64_t> m_submit_sequence = 0;
|
||||
|
||||
friend class CommandBuffer;
|
||||
MasterSemaphore m_master;
|
||||
RenderContext& m_context;
|
||||
GraphicContext& m_graphics;
|
||||
CommandPool m_command_pool;
|
||||
CommandBuffer m_command;
|
||||
std::queue<PendingOperation> m_pending_operations;
|
||||
std::queue<PendingOperation> m_priority_operations;
|
||||
std::mutex m_operation_mutex;
|
||||
std::condition_variable m_operation_available;
|
||||
std::jthread m_priority_thread;
|
||||
bool m_priority_active = false;
|
||||
uint64_t m_priority_active_tick = 0;
|
||||
OperationState m_operation_state = OperationState::Open;
|
||||
HW::Context* m_registers = nullptr;
|
||||
HW::UserConfig* m_user_config = nullptr;
|
||||
HW::Shader* m_shaders = nullptr;
|
||||
};
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
#include "common/assert.h"
|
||||
#include "common/common.h"
|
||||
#include "common/emulatorConfig.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/profiler.h"
|
||||
#include "common/threads.h"
|
||||
#include "graphics/host_gpu/graphicContext.h"
|
||||
@@ -9,7 +7,6 @@
|
||||
#include "graphics/host_gpu/renderer/debug.h"
|
||||
#include "graphics/host_gpu/renderer/depthRenderTarget.h"
|
||||
#include "graphics/host_gpu/renderer/image/imageView.h"
|
||||
#include "graphics/host_gpu/renderer/pipeline/descriptorCache.h"
|
||||
#include "graphics/host_gpu/renderer/render.h"
|
||||
#include "graphics/host_gpu/renderer/renderContext.h"
|
||||
#include "graphics/host_gpu/vma.h"
|
||||
@@ -17,118 +14,29 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <bit>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
namespace Libs::Graphics {
|
||||
|
||||
namespace {
|
||||
|
||||
void ReportVulkanFatal(const char* what, vk::Result result, uint32_t slot, uint64_t submit_seq,
|
||||
uint32_t debug_op, uint64_t debug_submit, uint32_t arg0, uint32_t arg1,
|
||||
uint32_t arg2, uint32_t arg3, uint64_t arg4) {
|
||||
LOGF("%s failed: %s (%d), slot=%u submit_seq=%" PRIu64 " debug_op=%u debug_submit=%" PRIu64
|
||||
" args=%u,%u,%u,%u,0x%016" PRIx64 "\n",
|
||||
what, VulkanToString(result).c_str(), static_cast<int>(result), slot, submit_seq, debug_op,
|
||||
debug_submit, arg0, arg1, arg2, arg3, arg4);
|
||||
std::printf("%s failed: %s (%d), slot=%u submit_seq=%" PRIu64 " debug_op=%u debug_submit=%" PRIu64
|
||||
" args=%u,%u,%u,%u,0x%016" PRIx64 "\n",
|
||||
what, VulkanToString(result).c_str(), static_cast<int>(result), slot, submit_seq,
|
||||
debug_op, debug_submit, arg0, arg1, arg2, arg3, arg4);
|
||||
std::fflush(stdout);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
FenceResourceRetainer::~FenceResourceRetainer() {
|
||||
if (!m_resources.empty()) {
|
||||
EXIT("fence resource retainer destroyed before release\n");
|
||||
}
|
||||
}
|
||||
|
||||
void FenceResourceRetainer::Retain(std::shared_ptr<void> resource) {
|
||||
if (resource == nullptr) {
|
||||
EXIT("cannot retain a null fence resource\n");
|
||||
}
|
||||
if (std::ranges::none_of(m_resources, [&resource](const auto& retained) {
|
||||
return retained.get() == resource.get();
|
||||
})) {
|
||||
m_resources.push_back(std::move(resource));
|
||||
}
|
||||
}
|
||||
|
||||
void FenceResourceRetainer::ReleaseAfterFence() noexcept {
|
||||
m_resources.clear();
|
||||
}
|
||||
|
||||
CommandBuffer::CommandBuffer(CommandScheduler& scheduler)
|
||||
: m_context(scheduler.Context()), m_scheduler(scheduler), m_graphics(scheduler.Graphics()),
|
||||
m_slot(scheduler.AllocateCommandBuffer()) {}
|
||||
|
||||
CommandBuffer::~CommandBuffer() {
|
||||
Release();
|
||||
}
|
||||
: m_context(scheduler.Context()), m_graphics(scheduler.Graphics()) {}
|
||||
|
||||
bool CommandBuffer::IsInvalid() const {
|
||||
return m_slot == nullptr;
|
||||
return m_buffer == nullptr;
|
||||
}
|
||||
|
||||
vk::CommandBuffer CommandBuffer::Handle() const {
|
||||
EXIT_IF(IsInvalid());
|
||||
|
||||
const auto handle = m_slot->buffer;
|
||||
EXIT_IF(handle == nullptr);
|
||||
return handle;
|
||||
return m_buffer;
|
||||
}
|
||||
|
||||
void CommandBuffer::Release() {
|
||||
EXIT_IF(IsInvalid());
|
||||
|
||||
Common::LockGuard lock(*m_slot->pool_mutex);
|
||||
|
||||
WaitForFence();
|
||||
|
||||
m_slot->busy = false;
|
||||
m_slot->Reset();
|
||||
ReleaseResourcesAfterFence();
|
||||
m_slot = nullptr;
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(!IsInvalid());
|
||||
}
|
||||
|
||||
void CommandBuffer::RetireBufferAfterFence(std::unique_ptr<VulkanBuffer> buffer) {
|
||||
if (IsInvalid() || m_execute || buffer == nullptr || buffer->buffer == nullptr) {
|
||||
EXIT("cannot retire a buffer on an invalid or submitted command buffer\n");
|
||||
}
|
||||
m_retired_buffers.push_back(std::move(buffer));
|
||||
}
|
||||
|
||||
void CommandBuffer::RetainResourceUntilFence(std::shared_ptr<void> resource) {
|
||||
if (IsInvalid() || m_execute) {
|
||||
EXIT("cannot retain a resource on an invalid or submitted command buffer\n");
|
||||
}
|
||||
m_fence_resources.Retain(std::move(resource));
|
||||
}
|
||||
|
||||
void CommandBuffer::RecycleDescriptorAfterFence(VulkanDescriptorSet& set) {
|
||||
m_descriptor_sets_after_fence.push_back(&set);
|
||||
}
|
||||
|
||||
void CommandBuffer::RecycleDescriptorsAfterFence() {
|
||||
for (auto* set: m_descriptor_sets_after_fence) {
|
||||
m_context.GetDescriptorCache().Recycle(*set);
|
||||
}
|
||||
m_descriptor_sets_after_fence.clear();
|
||||
}
|
||||
|
||||
void CommandBuffer::Begin() const {
|
||||
EXIT_IF(m_rendering);
|
||||
void CommandBuffer::Begin() {
|
||||
EXIT_IF(m_rendering || IsInvalid());
|
||||
auto buffer = Handle();
|
||||
|
||||
vk::CommandBufferBeginInfo begin_info {};
|
||||
begin_info.sType = vk::StructureType::eCommandBufferBeginInfo;
|
||||
begin_info.pNext = nullptr;
|
||||
begin_info.flags = {};
|
||||
begin_info.flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit;
|
||||
begin_info.pInheritanceInfo = nullptr;
|
||||
|
||||
auto result = buffer.begin(&begin_info);
|
||||
@@ -156,122 +64,6 @@ void CommandBuffer::SetDebugInfo(uint32_t op, uint64_t submit_id, uint32_t arg0,
|
||||
m_debug_arg4 = arg4;
|
||||
}
|
||||
|
||||
void CommandBuffer::Execute(const SubmitInfo& submit) {
|
||||
EXIT_IF(IsInvalid());
|
||||
EXIT_IF(m_execute);
|
||||
EXIT_IF(submit.num_wait_semaphores > SubmitInfo::MaxSemaphores ||
|
||||
submit.num_signal_semaphores > SubmitInfo::MaxSemaphores);
|
||||
|
||||
auto buffer = Handle();
|
||||
auto fence = m_slot->fence;
|
||||
|
||||
vk::TimelineSemaphoreSubmitInfo timeline_info {};
|
||||
timeline_info.sType = vk::StructureType::eTimelineSemaphoreSubmitInfo;
|
||||
timeline_info.waitSemaphoreValueCount = submit.num_wait_semaphores;
|
||||
timeline_info.pWaitSemaphoreValues = submit.wait_ticks.data();
|
||||
timeline_info.signalSemaphoreValueCount = submit.num_signal_semaphores;
|
||||
timeline_info.pSignalSemaphoreValues = submit.signal_ticks.data();
|
||||
|
||||
vk::SubmitInfo submit_info {};
|
||||
submit_info.sType = vk::StructureType::eSubmitInfo;
|
||||
submit_info.pNext = &timeline_info;
|
||||
submit_info.waitSemaphoreCount = submit.num_wait_semaphores;
|
||||
submit_info.pWaitSemaphores = submit.wait_semaphores.data();
|
||||
submit_info.pWaitDstStageMask = submit.wait_stages.data();
|
||||
submit_info.commandBufferCount = 1;
|
||||
submit_info.pCommandBuffers = &buffer;
|
||||
submit_info.signalSemaphoreCount = submit.num_signal_semaphores;
|
||||
submit_info.pSignalSemaphores = submit.signal_semaphores.data();
|
||||
|
||||
auto& graphics = m_graphics;
|
||||
EXIT_IF(graphics.queue == nullptr);
|
||||
|
||||
auto result = graphics.device.resetFences(1, &fence);
|
||||
if (result != vk::Result::eSuccess) {
|
||||
ReportVulkanFatal("vkResetFences (before submit)", result, m_slot->id, m_submit_seq,
|
||||
m_debug_op, m_debug_submit_id, m_debug_arg0, m_debug_arg1, m_debug_arg2,
|
||||
m_debug_arg3, m_debug_arg4);
|
||||
}
|
||||
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess);
|
||||
|
||||
if (Config::GraphicsDebugDumpEnabled()) {
|
||||
LOGF("vkQueueSubmit begin: slot=%u waits=%u signals=%u debug_op=%u debug_submit=%" PRIu64
|
||||
" args=%u,%u,%u,%u,0x%016" PRIx64 "\n",
|
||||
m_slot->id, submit.num_wait_semaphores, submit.num_signal_semaphores, m_debug_op,
|
||||
m_debug_submit_id, m_debug_arg0, m_debug_arg1, m_debug_arg2, m_debug_arg3,
|
||||
m_debug_arg4);
|
||||
}
|
||||
|
||||
{
|
||||
Common::LockGuard lock(graphics.queue_mutex);
|
||||
m_submit_seq = m_scheduler.NextSubmitSequence();
|
||||
result = graphics.queue.submit(1, &submit_info, fence);
|
||||
}
|
||||
|
||||
m_execute = true;
|
||||
m_fence_waited = false;
|
||||
|
||||
if (result != vk::Result::eSuccess) {
|
||||
ReportVulkanFatal("vkQueueSubmit", result, m_slot->id, m_submit_seq, m_debug_op,
|
||||
m_debug_submit_id, m_debug_arg0, m_debug_arg1, m_debug_arg2, m_debug_arg3,
|
||||
m_debug_arg4);
|
||||
}
|
||||
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess);
|
||||
}
|
||||
|
||||
void CommandBuffer::WaitForFence() {
|
||||
FinalizeFence(false);
|
||||
}
|
||||
|
||||
void CommandBuffer::WaitForFenceOnly() {
|
||||
EXIT_IF(IsInvalid());
|
||||
if (!m_execute || m_fence_waited) {
|
||||
return;
|
||||
}
|
||||
auto device = m_graphics.device;
|
||||
auto result = device.waitForFences(1, &m_slot->fence, VK_TRUE, UINT64_MAX);
|
||||
if (result != vk::Result::eSuccess) {
|
||||
ReportVulkanFatal("vkWaitForFences", result, m_slot->id, m_submit_seq, m_debug_op,
|
||||
m_debug_submit_id, m_debug_arg0, m_debug_arg1, m_debug_arg2, m_debug_arg3,
|
||||
m_debug_arg4);
|
||||
}
|
||||
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess);
|
||||
m_fence_waited = true;
|
||||
}
|
||||
|
||||
void CommandBuffer::WaitForFenceAndReset() {
|
||||
FinalizeFence(true);
|
||||
}
|
||||
|
||||
void CommandBuffer::FinalizeFence(bool reset_recording) {
|
||||
const bool was_executed = m_execute;
|
||||
WaitForFenceOnly();
|
||||
if (was_executed) {
|
||||
m_execute = false;
|
||||
m_fence_waited = false;
|
||||
if (reset_recording) {
|
||||
Common::LockGuard lock(*m_slot->pool_mutex);
|
||||
m_slot->Reset();
|
||||
}
|
||||
}
|
||||
if (was_executed) {
|
||||
ReleaseResourcesAfterFence();
|
||||
}
|
||||
DeleteBuffersAfterFence();
|
||||
}
|
||||
|
||||
void CommandBuffer::ReleaseResourcesAfterFence() {
|
||||
RecycleDescriptorsAfterFence();
|
||||
m_fence_resources.ReleaseAfterFence();
|
||||
}
|
||||
|
||||
void CommandBuffer::DeleteBuffersAfterFence() {
|
||||
for (const auto& buffer: m_retired_buffers) {
|
||||
m_graphics.DeleteBuffer(*buffer);
|
||||
}
|
||||
m_retired_buffers.clear();
|
||||
}
|
||||
|
||||
void CommandBuffer::BeginRendering(const RenderState& state) const {
|
||||
EXIT_IF(state.width == 0 || state.height == 0 || state.num_layers == 0 ||
|
||||
state.num_color_attachments > RENDER_COLOR_ATTACHMENTS_MAX);
|
||||
|
||||
@@ -27,7 +27,7 @@ static bool RenderTargetMaskHasMrt(uint32_t mask) {
|
||||
return (mask & ~0x0fu) != 0;
|
||||
}
|
||||
|
||||
static bool RenderTargetMaskHasBoundMrt(const RenderCommandBuffer& buffer) {
|
||||
static bool RenderTargetMaskHasBoundMrt(const CommandBuffer& buffer) {
|
||||
const auto& hw = buffer.GetRegisters();
|
||||
const auto mask = hw.GetRenderTargetMask();
|
||||
|
||||
@@ -45,7 +45,7 @@ static bool RenderTargetMaskHasBoundMrt(const RenderCommandBuffer& buffer) {
|
||||
return bound_targets > 1;
|
||||
}
|
||||
|
||||
uint32_t render_target_first_bound_slot(const RenderCommandBuffer& buffer) {
|
||||
uint32_t render_target_first_bound_slot(const CommandBuffer& buffer) {
|
||||
const auto& hw = buffer.GetRegisters();
|
||||
const auto mask = hw.GetRenderTargetMask();
|
||||
for (uint32_t i = 0; i < 8; i++) {
|
||||
@@ -84,17 +84,8 @@ void uc_print(const char* func, const HW::UserConfig& uc) {
|
||||
}
|
||||
|
||||
void uc_check(const HW::UserConfig& uc) {
|
||||
const auto& ge_cntl = uc.GetGeControl();
|
||||
const auto& user_en = uc.GetGeUserVgprEn();
|
||||
|
||||
if (ge_cntl.primitive_group_size > 0x0040) {
|
||||
LOGF("\t warning: unsupported GE_CNTL primitive_group_size = 0x%04" PRIx16 "\n",
|
||||
ge_cntl.primitive_group_size);
|
||||
}
|
||||
if (ge_cntl.vertex_group_size > 0x0040) {
|
||||
LOGF("\t warning: unsupported GE_CNTL vertex_group_size = 0x%04" PRIx16 "\n",
|
||||
ge_cntl.vertex_group_size);
|
||||
}
|
||||
EXIT_NOT_IMPLEMENTED(user_en.vgpr1 != false);
|
||||
EXIT_NOT_IMPLEMENTED(user_en.vgpr2 != false);
|
||||
EXIT_NOT_IMPLEMENTED(user_en.vgpr3 != false);
|
||||
@@ -106,17 +97,11 @@ void sh_print(const char* func, const HW::Shader& /*uc*/) {
|
||||
|
||||
std::vector<std::string> rt_print(const char* func, const HW::RenderTarget& rt) {
|
||||
std::vector<std::string> dst;
|
||||
dst.reserve(53);
|
||||
dst.reserve(40);
|
||||
|
||||
dst.push_back(fmt::format("{}\n", func));
|
||||
|
||||
dst.push_back(fmt::format("\t base.addr = 0x{:016x}\n", rt.base.addr));
|
||||
dst.push_back(
|
||||
fmt::format("\t pitch.pitch_div8_minus1 = 0x{:08x}\n", rt.pitch.pitch_div8_minus1));
|
||||
dst.push_back(fmt::format("\t pitch.fmask_pitch_div8_minus1 = 0x{:08x}\n",
|
||||
rt.pitch.fmask_pitch_div8_minus1));
|
||||
dst.push_back(fmt::format("\t slice.slice_div64_minus1 = 0x{:08x}\n",
|
||||
rt.slice.slice_div64_minus1));
|
||||
dst.push_back(fmt::format("\t view.base_array_slice_index = 0x{:08x}\n",
|
||||
rt.view.base_array_slice_index));
|
||||
dst.push_back(fmt::format("\t view.last_array_slice_index = 0x{:08x}\n",
|
||||
@@ -137,12 +122,6 @@ std::vector<std::string> rt_print(const char* func, const HW::RenderTarget& rt)
|
||||
rt.info.cmask_fast_clear_enable ? "true" : "false"));
|
||||
dst.push_back(fmt::format("\t info.dcc_compression_enable = {}\n",
|
||||
rt.info.dcc_compression_enable ? "true" : "false"));
|
||||
dst.push_back(
|
||||
fmt::format("\t info.cmask_is_linear = 0x{:08x}\n", rt.info.cmask_is_linear));
|
||||
dst.push_back(
|
||||
fmt::format("\t info.cmask_addr_type = 0x{:08x}\n", rt.info.cmask_addr_type));
|
||||
dst.push_back(fmt::format("\t info.alt_tile_mode = {}\n",
|
||||
rt.info.alt_tile_mode ? "true" : "false"));
|
||||
dst.push_back(fmt::format("\t info.format = 0x{:08x}\n",
|
||||
static_cast<uint32_t>(rt.info.format)));
|
||||
dst.push_back(fmt::format("\t info.channel_type = 0x{:08x}\n",
|
||||
@@ -157,10 +136,6 @@ std::vector<std::string> rt_print(const char* func, const HW::RenderTarget& rt)
|
||||
rt.info.round_mode ? "true" : "false"));
|
||||
dst.push_back(fmt::format("\t attrib.force_dest_alpha_to_one = {}\n",
|
||||
rt.attrib.force_dest_alpha_to_one ? "true" : "false"));
|
||||
dst.push_back(fmt::format("\t attrib.tile_mode = 0x{:08x}\n",
|
||||
static_cast<uint32_t>(rt.attrib.tile_mode)));
|
||||
dst.push_back(fmt::format("\t attrib.fmask_tile_mode = 0x{:08x}\n",
|
||||
static_cast<uint32_t>(rt.attrib.fmask_tile_mode)));
|
||||
dst.push_back(
|
||||
fmt::format("\t attrib.num_samples = 0x{:08x}\n", rt.attrib.num_samples));
|
||||
dst.push_back(
|
||||
@@ -175,42 +150,32 @@ std::vector<std::string> rt_print(const char* func, const HW::RenderTarget& rt)
|
||||
static_cast<uint32_t>(rt.attrib3.tile_mode)));
|
||||
dst.push_back(
|
||||
fmt::format("\t attrib3.dimension = 0x{:08x}\n", rt.attrib3.dimension));
|
||||
dst.push_back(fmt::format("\t attrib3.cmask_pipe_aligned = {}\n",
|
||||
rt.attrib3.cmask_pipe_aligned ? "true" : "false"));
|
||||
dst.push_back(fmt::format("\t attrib3.dcc_pipe_aligned = {}\n",
|
||||
rt.attrib3.dcc_pipe_aligned ? "true" : "false"));
|
||||
dst.push_back(fmt::format("\t attrib3.metadata_pipe_aligned = {}\n",
|
||||
rt.attrib3.metadata_pipe_aligned ? "true" : "false"));
|
||||
dst.push_back(fmt::format("\t attrib3.write_vrs_rate_hint_to_cmask = {}\n",
|
||||
rt.attrib3.write_vrs_rate_hint_to_cmask ? "true" : "false"));
|
||||
dst.push_back(fmt::format("\t dcc.max_uncompressed_block_size = 0x{:08x}\n",
|
||||
rt.dcc.max_uncompressed_block_size));
|
||||
dst.push_back(fmt::format("\t dcc.max_compressed_block_size = 0x{:08x}\n",
|
||||
rt.dcc.max_compressed_block_size));
|
||||
dst.push_back(fmt::format("\t dcc.min_compressed_block_size = 0x{:08x}\n",
|
||||
rt.dcc.min_compressed_block_size));
|
||||
dst.push_back(
|
||||
fmt::format("\t dcc.color_transform = 0x{:08x}\n", rt.dcc.color_transform));
|
||||
dst.push_back(fmt::format("\t dcc.overwrite_combiner_disable = {}\n",
|
||||
rt.dcc.overwrite_combiner_disable ? "true" : "false"));
|
||||
dst.push_back(fmt::format("\t dcc.independent_64b_blocks = {}\n",
|
||||
rt.dcc.independent_64b_blocks ? "true" : "false"));
|
||||
dst.push_back(fmt::format("\t dcc.independent_128b_blocks = {}\n",
|
||||
rt.dcc.independent_128b_blocks ? "true" : "false"));
|
||||
dst.push_back(fmt::format("\t dcc.independent_block_size = 0x{:02x}\n",
|
||||
static_cast<uint8_t>(rt.dcc.independent_block_size)));
|
||||
dst.push_back(fmt::format("\t data_write_on_dcc_clear_to_reg = {}\n",
|
||||
rt.dcc.data_write_on_dcc_clear_to_reg ? "true" : "false"));
|
||||
dst.push_back(fmt::format("\t dcc.dcc_clear_key_enable = {}\n",
|
||||
rt.dcc.dcc_clear_key_enable ? "true" : "false"));
|
||||
dst.push_back(fmt::format("\t cmask.addr = 0x{:016x}\n", rt.cmask.addr));
|
||||
dst.push_back(fmt::format("\t cmask_slice.slice_minus1 = 0x{:08x}\n",
|
||||
rt.cmask_slice.slice_minus1));
|
||||
dst.push_back(fmt::format("\t fmask.addr = 0x{:016x}\n", rt.fmask.addr));
|
||||
dst.push_back(fmt::format("\t fmask_slice.slice_minus1 = 0x{:08x}\n",
|
||||
rt.fmask_slice.slice_minus1));
|
||||
dst.push_back(
|
||||
fmt::format("\t clear_word0.word0 = 0x{:08x}\n", rt.clear_word0.word0));
|
||||
dst.push_back(
|
||||
fmt::format("\t clear_word1.word1 = 0x{:08x}\n", rt.clear_word1.word1));
|
||||
dst.push_back(
|
||||
fmt::format("\t dcc_addr.addr = 0x{:016x}\n", rt.dcc_addr.addr));
|
||||
dst.push_back(fmt::format("\t size.width = 0x{:08x}\n", rt.size.width));
|
||||
dst.push_back(fmt::format("\t size.height = 0x{:08x}\n", rt.size.height));
|
||||
|
||||
return dst;
|
||||
}
|
||||
@@ -240,13 +205,8 @@ static bool RenderIsColorDimension(uint32_t dimension) {
|
||||
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
|
||||
static void RtCheck(const HW::RenderTarget& rt) {
|
||||
if (rt.base.addr != 0) {
|
||||
// bool render_to_texture = (rt.attrib.tile_mode == 0x0d);
|
||||
// EXIT_NOT_IMPLEMENTED(rt.base_addr == 0);
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(rt.pitch.pitch_div8_minus1 != 0);
|
||||
EXIT_NOT_IMPLEMENTED(rt.pitch.fmask_pitch_div8_minus1 != 0);
|
||||
EXIT_NOT_IMPLEMENTED(rt.slice.slice_div64_minus1 != 0);
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(rt.view.base_array_slice_index > rt.view.last_array_slice_index);
|
||||
if (rt.view.base_array_slice_index != 0x00000000 ||
|
||||
rt.view.last_array_slice_index != 0x00000000) {
|
||||
@@ -266,12 +226,6 @@ static void RtCheck(const HW::RenderTarget& rt) {
|
||||
logged = true;
|
||||
}
|
||||
}
|
||||
const bool native_2x_one_frag = rt.info.fmask_compression_enable &&
|
||||
!rt.info.fmask_data_compression_disable &&
|
||||
rt.info.cmask_fast_clear_enable && rt.fmask.addr != 0 &&
|
||||
rt.cmask.addr != 0 && rt.attrib.num_samples == 1 &&
|
||||
rt.attrib.num_fragments == 1;
|
||||
EXIT_NOT_IMPLEMENTED(rt.info.fmask_one_frag_mode && !native_2x_one_frag);
|
||||
if (rt.info.fmask_compression_enable) {
|
||||
EXIT_NOT_IMPLEMENTED(rt.attrib.num_samples == 0 && rt.attrib.num_fragments == 0);
|
||||
static bool logged = false;
|
||||
@@ -296,22 +250,6 @@ static void RtCheck(const HW::RenderTarget& rt) {
|
||||
logged = true;
|
||||
}
|
||||
}
|
||||
if (rt.info.cmask_is_linear != 0x00000000 || rt.info.cmask_addr_type != 0x00000000) {
|
||||
static bool logged = false;
|
||||
if (!logged) {
|
||||
LOGF("RenderTarget: temporary: ignoring PS5 CMASK metadata fields "
|
||||
"is_linear=%u addr_type=%u\n",
|
||||
rt.info.cmask_is_linear, rt.info.cmask_addr_type);
|
||||
logged = true;
|
||||
}
|
||||
}
|
||||
if (rt.info.alt_tile_mode) {
|
||||
static bool logged = false;
|
||||
if (!logged) {
|
||||
LOGF("RenderTarget: temporary: ignoring alternate tile mode flag\n");
|
||||
logged = true;
|
||||
}
|
||||
}
|
||||
if (rt.info.blend_bypass) {
|
||||
static bool logged = false;
|
||||
if (!logged) {
|
||||
@@ -338,8 +276,6 @@ static void RtCheck(const HW::RenderTarget& rt) {
|
||||
logged = true;
|
||||
}
|
||||
}
|
||||
// EXIT_NOT_IMPLEMENTED(rt.tile_mode != 0x0000000a);
|
||||
// EXIT_NOT_IMPLEMENTED(rt.fmask_tile_mode != 0x0000000a);
|
||||
if (rt.attrib.num_samples != 0x00000000 || rt.attrib.num_fragments != 0x00000000) {
|
||||
static bool logged = false;
|
||||
if (!logged) {
|
||||
@@ -374,34 +310,22 @@ static void RtCheck(const HW::RenderTarget& rt) {
|
||||
if (!RenderIsColorDimension(rt.attrib3.dimension)) {
|
||||
EXIT("unknown PS5 render-target dimension: 0x%08" PRIx32 "\n", rt.attrib3.dimension);
|
||||
}
|
||||
if (!rt.attrib3.cmask_pipe_aligned) {
|
||||
if (!rt.attrib3.metadata_pipe_aligned) {
|
||||
static bool logged = false;
|
||||
if (!logged) {
|
||||
LOGF("RenderTarget: temporary: accepting unaligned PS5 CMASK pipe flag\n");
|
||||
logged = true;
|
||||
}
|
||||
}
|
||||
if (!rt.attrib3.dcc_pipe_aligned) {
|
||||
static bool logged = false;
|
||||
if (!logged) {
|
||||
LOGF("RenderTarget: temporary: accepting unaligned PS5 DCC pipe flag\n");
|
||||
LOGF("RenderTarget: temporary: accepting unaligned PS5 metadata pipe flag\n");
|
||||
logged = true;
|
||||
}
|
||||
}
|
||||
EXIT_NOT_IMPLEMENTED(rt.attrib3.write_vrs_rate_hint_to_cmask);
|
||||
|
||||
// EXIT_NOT_IMPLEMENTED(rt.dcc_max_uncompressed_block_size != 0x00000002);
|
||||
// EXIT_NOT_IMPLEMENTED(rt.dcc.max_compressed_block_size != 0x00000000);
|
||||
EXIT_NOT_IMPLEMENTED(rt.dcc.min_compressed_block_size != 0x00000000);
|
||||
// EXIT_NOT_IMPLEMENTED(rt.dcc.color_transform != 0x00000000);
|
||||
EXIT_NOT_IMPLEMENTED(rt.dcc.overwrite_combiner_disable != false);
|
||||
// EXIT_NOT_IMPLEMENTED(rt.dcc.force_independent_blocks != false);
|
||||
// EXIT_NOT_IMPLEMENTED(rt.dcc.independent_128b_blocks != false);
|
||||
// EXIT_NOT_IMPLEMENTED(rt.dcc.data_write_on_dcc_clear_to_reg != false);
|
||||
EXIT_NOT_IMPLEMENTED(rt.dcc.dcc_clear_key_enable != false);
|
||||
if (rt.cmask.addr != 0x0000000000000000 || rt.cmask_slice.slice_minus1 != 0x00000000 ||
|
||||
rt.fmask.addr != 0x0000000000000000 ||
|
||||
(rt.fmask_slice.slice_minus1 != 0x00000000 &&
|
||||
rt.fmask_slice.slice_minus1 != rt.slice.slice_div64_minus1) ||
|
||||
if (rt.cmask.addr != 0x0000000000000000 || rt.fmask.addr != 0x0000000000000000 ||
|
||||
rt.dcc_addr.addr != 0x0000000000000000) {
|
||||
static bool logged = false;
|
||||
if (!logged) {
|
||||
@@ -411,9 +335,6 @@ static void RtCheck(const HW::RenderTarget& rt) {
|
||||
logged = true;
|
||||
}
|
||||
}
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(rt.size.width != 0);
|
||||
EXIT_NOT_IMPLEMENTED(rt.size.height != 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -433,37 +354,20 @@ static void ZPrint(const char* func, const HW::DepthRenderTarget& z) {
|
||||
"\t stencil_info.htile_stencil_disabled = %s\n"
|
||||
"\t stencil_info.expclear_enabled = %s\n"
|
||||
"\t stencil_info.partially_resident = %s\n"
|
||||
"\t depth_info.addr5_swizzle_mask = 0x%08" PRIx32 "\n"
|
||||
"\t depth_info.array_mode = 0x%08" PRIx32 "\n"
|
||||
"\t depth_info.pipe_config = 0x%08" PRIx32 "\n"
|
||||
"\t depth_info.bank_width = 0x%08" PRIx32 "\n"
|
||||
"\t depth_info.bank_height = 0x%08" PRIx32 "\n"
|
||||
"\t depth_info.macro_tile_aspect = 0x%08" PRIx32 "\n"
|
||||
"\t depth_info.num_banks = 0x%08" PRIx32 "\n"
|
||||
"\t depth_view.slice_start = 0x%08" PRIx32 "\n"
|
||||
"\t depth_view.slice_max = 0x%08" PRIx32 "\n"
|
||||
"\t depth_view.current_mip_level = 0x%02" PRIx8 "\n"
|
||||
"\t depth_view.depth_write_disable = %s\n"
|
||||
"\t depth_view.stencil_write_disable = %s\n"
|
||||
"\t htile_surface.linear = 0x%08" PRIx32 "\n"
|
||||
"\t htile_surface.full_cache = 0x%08" PRIx32 "\n"
|
||||
"\t htile_surface.htile_uses_preload_win = 0x%08" PRIx32 "\n"
|
||||
"\t htile_surface.preload = 0x%08" PRIx32 "\n"
|
||||
"\t htile_surface.prefetch_width = 0x%08" PRIx32 "\n"
|
||||
"\t htile_surface.prefetch_height = 0x%08" PRIx32 "\n"
|
||||
"\t htile_surface.dst_outside_zero_to_one = 0x%08" PRIx32 "\n"
|
||||
"\t z_read_base_addr = 0x%016" PRIx64 "\n"
|
||||
"\t stencil_read_base_addr = 0x%016" PRIx64 "\n"
|
||||
"\t z_write_base_addr = 0x%016" PRIx64 "\n"
|
||||
"\t stencil_write_base_addr = 0x%016" PRIx64 "\n"
|
||||
"\t pitch_div8_minus1 = 0x%08" PRIx32 "\n"
|
||||
"\t height_div8_minus1 = 0x%08" PRIx32 "\n"
|
||||
"\t slice_div64_minus1 = 0x%08" PRIx32 "\n"
|
||||
"\t htile_data_base_addr = 0x%016" PRIx64 "\n"
|
||||
"\t width = 0x%08" PRIx32 "\n"
|
||||
"\t height = 0x%08" PRIx32 "\n"
|
||||
"\t shading_rate_encoding = 0x%02" PRIx8 "\n"
|
||||
"\t size.x_max = 0x%04" PRIx16 "\n"
|
||||
"\t size.y_max = 0x%04" PRIx16 "\n",
|
||||
"\t size.y_max = 0x%04" PRIx16 "\n"
|
||||
"\t size.valid = %s\n",
|
||||
static_cast<uint32_t>(z.z_info.format), z.z_info.num_samples,
|
||||
static_cast<uint32_t>(z.z_info.texture_compatibility),
|
||||
z.z_info.htile_acceleration ? "true" : "false",
|
||||
@@ -474,30 +378,27 @@ static void ZPrint(const char* func, const HW::DepthRenderTarget& z) {
|
||||
static_cast<uint32_t>(z.stencil_info.texture_compatibility),
|
||||
z.stencil_info.htile_stencil_disabled ? "true" : "false",
|
||||
z.stencil_info.expclear_enabled ? "true" : "false",
|
||||
z.stencil_info.partially_resident ? "true" : "false", z.depth_info.addr5_swizzle_mask,
|
||||
z.depth_info.array_mode, z.depth_info.pipe_config, z.depth_info.bank_width,
|
||||
z.depth_info.bank_height, z.depth_info.macro_tile_aspect, z.depth_info.num_banks,
|
||||
z.depth_view.slice_start, z.depth_view.slice_max, z.depth_view.current_mip_level,
|
||||
z.stencil_info.partially_resident ? "true" : "false", z.depth_view.slice_start,
|
||||
z.depth_view.slice_max, z.depth_view.current_mip_level,
|
||||
z.depth_view.depth_write_disable ? "true" : "false",
|
||||
z.depth_view.stencil_write_disable ? "true" : "false", z.htile_surface.linear,
|
||||
z.htile_surface.full_cache, z.htile_surface.htile_uses_preload_win,
|
||||
z.htile_surface.preload, z.htile_surface.prefetch_width, z.htile_surface.prefetch_height,
|
||||
z.htile_surface.dst_outside_zero_to_one, z.z_read_base_addr, z.stencil_read_base_addr,
|
||||
z.z_write_base_addr, z.stencil_write_base_addr, z.pitch_div8_minus1, z.height_div8_minus1,
|
||||
z.slice_div64_minus1, z.htile_data_base_addr, z.width, z.height, z.size.x_max,
|
||||
z.size.y_max);
|
||||
z.depth_view.stencil_write_disable ? "true" : "false", z.z_read_base_addr,
|
||||
z.stencil_read_base_addr, z.z_write_base_addr, z.stencil_write_base_addr,
|
||||
z.htile_data_base_addr, z.shading_rate_encoding, z.size.x_max, z.size.y_max,
|
||||
z.size.valid ? "true" : "false");
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
|
||||
static void ZCheck(const HW::DepthRenderTarget& z, const HW::DepthControl& dc,
|
||||
const HW::RenderControl& rc) {
|
||||
const bool depth_active =
|
||||
dc.z_enable || dc.z_write_enable || dc.depth_bounds_enable || rc.depth_clear_enable;
|
||||
const bool stencil_active = dc.stencil_enable || rc.stencil_clear_enable;
|
||||
const bool depth_active = dc.z_enable || dc.z_write_enable || dc.depth_bounds_enable ||
|
||||
rc.depth_clear_enable || rc.copy_depth_to_color;
|
||||
const bool stencil_active =
|
||||
dc.stencil_enable || rc.stencil_clear_enable || rc.copy_stencil_to_color;
|
||||
if (!depth_active && !stencil_active) {
|
||||
return;
|
||||
}
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(rc.copy_depth_to_color || rc.copy_stencil_to_color);
|
||||
EXIT_NOT_IMPLEMENTED(!z.z_info.HasValidTextureCompatibility());
|
||||
EXIT_NOT_IMPLEMENTED(!z.stencil_info.HasValidTextureCompatibility());
|
||||
if (z.z_info.format == Prospero::DepthFormat::kInvalid) {
|
||||
@@ -534,21 +435,7 @@ static void ZCheck(const HW::DepthRenderTarget& z, const HW::DepthControl& dc,
|
||||
|
||||
if (z.z_info.format != Prospero::DepthFormat::kInvalid ||
|
||||
z.stencil_info.format != Prospero::StencilFormat::kInvalid) {
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(z.depth_info.addr5_swizzle_mask != 0x00000000);
|
||||
EXIT_NOT_IMPLEMENTED(z.depth_info.array_mode != 0x00000000);
|
||||
EXIT_NOT_IMPLEMENTED(z.depth_info.pipe_config != 0x00000000);
|
||||
EXIT_NOT_IMPLEMENTED(z.depth_info.bank_width != 0x00000000);
|
||||
EXIT_NOT_IMPLEMENTED(z.depth_info.bank_height != 0x00000000);
|
||||
EXIT_NOT_IMPLEMENTED(z.depth_info.macro_tile_aspect != 0x00000000);
|
||||
EXIT_NOT_IMPLEMENTED(z.depth_info.num_banks != 0x00000000);
|
||||
EXIT_NOT_IMPLEMENTED(z.htile_surface.linear != 0x00000000);
|
||||
EXIT_NOT_IMPLEMENTED(z.htile_surface.full_cache != 0x00000000);
|
||||
EXIT_NOT_IMPLEMENTED(z.htile_surface.htile_uses_preload_win != 0x00000000);
|
||||
EXIT_NOT_IMPLEMENTED(z.htile_surface.preload != 0x00000000);
|
||||
EXIT_NOT_IMPLEMENTED(z.htile_surface.prefetch_width != 0x00000000);
|
||||
EXIT_NOT_IMPLEMENTED(z.htile_surface.prefetch_height != 0x00000000);
|
||||
EXIT_NOT_IMPLEMENTED(z.htile_surface.dst_outside_zero_to_one != 0x00000000);
|
||||
EXIT_NOT_IMPLEMENTED(z.shading_rate_encoding != 0);
|
||||
|
||||
if (z.depth_view.current_mip_level != 0x00000000) {
|
||||
static std::atomic<uint32_t> log_count {0};
|
||||
@@ -570,23 +457,8 @@ static void ZCheck(const HW::DepthRenderTarget& z, const HW::DepthControl& dc,
|
||||
EXIT_NOT_IMPLEMENTED(!z.depth_view.stencil_write_disable &&
|
||||
z.stencil_read_base_addr != z.stencil_write_base_addr);
|
||||
EXIT_NOT_IMPLEMENTED(!z.depth_view.depth_write_disable && z.z_write_base_addr == 0);
|
||||
// EXIT_NOT_IMPLEMENTED(z.pitch_div8_minus1 != 0x000000ff);
|
||||
// EXIT_NOT_IMPLEMENTED(z.height_div8_minus1 != 0x0000008f);
|
||||
// EXIT_NOT_IMPLEMENTED(z.slice_div64_minus1 != 0x00008fff);
|
||||
// EXIT_NOT_IMPLEMENTED(z.htile_data_base_addr == 0);
|
||||
// EXIT_NOT_IMPLEMENTED(z.width != 0x00000780);
|
||||
// EXIT_NOT_IMPLEMENTED(z.height != 0x00000438);
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(z.width != 0);
|
||||
EXIT_NOT_IMPLEMENTED(z.height != 0);
|
||||
if (z.size.x_max == 0 || z.size.y_max == 0) {
|
||||
static std::atomic<uint32_t> log_count {0};
|
||||
if (log_count.fetch_add(1) < 16) {
|
||||
LOGF("DepthTarget: temporary: inferring PS5 default depth extent x_max=0x%04" PRIx16
|
||||
", y_max=0x%04" PRIx16 "\n",
|
||||
z.size.x_max, z.size.y_max);
|
||||
}
|
||||
}
|
||||
EXIT_NOT_IMPLEMENTED(!z.size.valid);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -769,8 +641,6 @@ static void DPrint(const char* func, const HW::DepthControl& c, const HW::Stenci
|
||||
"\t backface_enable = %s\n"
|
||||
"\t stencilfunc = %" PRIu8 "\n"
|
||||
"\t stencilfunc_bf = %" PRIu8 "\n"
|
||||
"\t color_writes_on_depth_fail_enable = %s\n"
|
||||
"\t color_writes_on_depth_pass_disable = %s\n"
|
||||
"\t stencil_fail = %" PRIu8 "\n"
|
||||
"\t stencil_zpass = %" PRIu8 "\n"
|
||||
"\t stencil_zfail = %" PRIu8 "\n"
|
||||
@@ -787,12 +657,11 @@ static void DPrint(const char* func, const HW::DepthControl& c, const HW::Stenci
|
||||
"\t stencil_opval_bf = %" PRIu8 "\n",
|
||||
c.stencil_enable ? "true" : "false", c.z_enable ? "true" : "false",
|
||||
c.z_write_enable ? "true" : "false", c.depth_bounds_enable ? "true" : "false", c.zfunc,
|
||||
c.backface_enable ? "true" : "false", c.stencilfunc, c.stencilfunc_bf,
|
||||
c.color_writes_on_depth_fail_enable ? "true" : "false",
|
||||
c.color_writes_on_depth_pass_disable ? "true" : "false", s.stencil_fail, s.stencil_zpass,
|
||||
s.stencil_zfail, s.stencil_fail_bf, s.stencil_zpass_bf, s.stencil_zfail_bf,
|
||||
sm.stencil_testval, sm.stencil_mask, sm.stencil_writemask, sm.stencil_opval,
|
||||
sm.stencil_testval_bf, sm.stencil_mask_bf, sm.stencil_writemask_bf, sm.stencil_opval_bf);
|
||||
c.backface_enable ? "true" : "false", c.stencilfunc, c.stencilfunc_bf, s.stencil_fail,
|
||||
s.stencil_zpass, s.stencil_zfail, s.stencil_fail_bf, s.stencil_zpass_bf,
|
||||
s.stencil_zfail_bf, sm.stencil_testval, sm.stencil_mask, sm.stencil_writemask,
|
||||
sm.stencil_opval, sm.stencil_testval_bf, sm.stencil_mask_bf, sm.stencil_writemask_bf,
|
||||
sm.stencil_opval_bf);
|
||||
}
|
||||
|
||||
static void DCheck(const HW::DepthControl& c, const HW::StencilControl& s,
|
||||
@@ -804,8 +673,6 @@ static void DCheck(const HW::DepthControl& c, const HW::StencilControl& s,
|
||||
// Back-face stencil state is handled separately when enabled.
|
||||
// EXIT_NOT_IMPLEMENTED(c.stencilfunc != 0);
|
||||
// EXIT_NOT_IMPLEMENTED(c.stencilfunc_bf != 0);
|
||||
EXIT_NOT_IMPLEMENTED(c.color_writes_on_depth_fail_enable != false);
|
||||
EXIT_NOT_IMPLEMENTED(c.color_writes_on_depth_pass_disable != false);
|
||||
// EXIT_NOT_IMPLEMENTED(s.stencil_fail != 0);
|
||||
// EXIT_NOT_IMPLEMENTED(s.stencil_zpass != 0);
|
||||
// EXIT_NOT_IMPLEMENTED(s.stencil_zfail != 0);
|
||||
@@ -837,7 +704,12 @@ static void EqaaPrint(const char* func, const HW::EqaaControl& c) {
|
||||
c.static_anchor_associations ? "true" : "false");
|
||||
}
|
||||
|
||||
static void EqaaCheck(const HW::EqaaControl& c) {
|
||||
static void EqaaCheck(const HW::EqaaControl& c, const HW::AaConfig& cf) {
|
||||
// The EQAA controls only take effect while multisampling is on, so a single-sample
|
||||
// target leaves them inert and has nothing to warn about.
|
||||
if (cf.msaa_num_samples == 0) {
|
||||
return;
|
||||
}
|
||||
if (c.max_anchor_samples != 0 || c.ps_iter_samples != 0 || c.mask_export_num_samples != 0 ||
|
||||
c.alpha_to_mask_num_samples != 0 || c.high_quality_intersections ||
|
||||
c.incoherent_eqaa_reads || c.interpolate_comp_z || c.static_anchor_associations) {
|
||||
@@ -889,12 +761,10 @@ void LogDrawPhase(const char* draw_name, const char* phase) {
|
||||
}
|
||||
}
|
||||
|
||||
void LogPipelineTrace(const char* phase, uint32_t vs_hash0, uint32_t vs_crc32, uint32_t ps_hash0,
|
||||
uint32_t ps_crc32) {
|
||||
void LogPipelineTrace(const char* phase, uint64_t vertex_program_id, uint64_t pixel_program_id) {
|
||||
if (graphics_debug_dump_enabled()) {
|
||||
LOGF("PipelineTrace: %s VS=0x%08" PRIx32 "/0x%08" PRIx32 " PS=0x%08" PRIx32 "/0x%08" PRIx32
|
||||
"\n",
|
||||
phase, vs_hash0, vs_crc32, ps_hash0, ps_crc32);
|
||||
LOGF("PipelineTrace: %s VS=%" PRIu64 " PS=%" PRIu64 "\n", phase, vertex_program_id,
|
||||
pixel_program_id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1158,7 +1028,7 @@ ScissorRect calc_final_scissor(const HW::ScreenViewport& vp, const HW::ScanModeC
|
||||
return ScissorRectClamp(final, extent.width, extent.height);
|
||||
}
|
||||
|
||||
void hw_check(const RenderCommandBuffer& buffer) {
|
||||
void hw_check(const CommandBuffer& buffer) {
|
||||
const auto& hw = buffer.GetRegisters();
|
||||
const auto rt_slot = render_target_first_bound_slot(buffer);
|
||||
const auto& rt = hw.GetRenderTarget(rt_slot);
|
||||
@@ -1204,7 +1074,7 @@ void hw_check(const RenderCommandBuffer& buffer) {
|
||||
log_phase("blend");
|
||||
BcCheck(bc, bclr, cc);
|
||||
log_phase("eqaa");
|
||||
EqaaCheck(eqaa);
|
||||
EqaaCheck(eqaa, ac);
|
||||
log_phase("aa");
|
||||
AaCheck(aa, ac);
|
||||
log_phase("done");
|
||||
@@ -1231,7 +1101,7 @@ void hw_check(const RenderCommandBuffer& buffer) {
|
||||
// EXIT_NOT_IMPLEMENTED(hw.GetStencilClearValue() != 0);
|
||||
}
|
||||
|
||||
void hw_print(const RenderCommandBuffer& buffer) {
|
||||
void hw_print(const CommandBuffer& buffer) {
|
||||
const auto& hw = buffer.GetRegisters();
|
||||
const auto rt_slot = render_target_first_bound_slot(buffer);
|
||||
const auto& rt = hw.GetRenderTarget(rt_slot);
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
class RenderCommandBuffer;
|
||||
class CommandBuffer;
|
||||
|
||||
namespace HW {
|
||||
class Context;
|
||||
@@ -28,15 +28,15 @@ struct ScissorRect {
|
||||
};
|
||||
|
||||
uint32_t render_target_mask_slot(uint32_t mask, uint32_t slot);
|
||||
uint32_t render_target_first_bound_slot(const RenderCommandBuffer& buffer);
|
||||
uint32_t render_target_first_bound_slot(const CommandBuffer& buffer);
|
||||
bool graphics_debug_dump_enabled();
|
||||
void uc_print(const char* func, const HW::UserConfig& uc);
|
||||
void uc_check(const HW::UserConfig& uc);
|
||||
void sh_print(const char* func, const HW::Shader& uc);
|
||||
std::vector<std::string> rt_print(const char* func, const HW::RenderTarget& rt);
|
||||
bool RenderIsColorTileModeLinear(Prospero::TileMode tile_mode);
|
||||
void hw_print(const RenderCommandBuffer& buffer);
|
||||
void hw_check(const RenderCommandBuffer& buffer);
|
||||
void hw_print(const CommandBuffer& buffer);
|
||||
void hw_check(const CommandBuffer& buffer);
|
||||
void LogDrawPhase(const char* draw_name, const char* phase);
|
||||
ScissorRect calc_final_scissor(const HW::ScreenViewport& vp, const HW::ScanModeControl& smc,
|
||||
vk::Extent2D extent);
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
#include "graphics/host_gpu/renderer/debug.h"
|
||||
#include "graphics/host_gpu/renderer/image/imageView.h"
|
||||
#include "graphics/host_gpu/renderer/image/textureCommon.h"
|
||||
#include "graphics/host_gpu/renderer/pipeline/descriptorCache.h"
|
||||
#include "graphics/host_gpu/renderer/render.h"
|
||||
#include "graphics/host_gpu/renderer/renderContext.h"
|
||||
#include "graphics/host_gpu/vulkanCommon.h"
|
||||
@@ -71,8 +70,8 @@ static bool UsesStencilOpValue(uint8_t fail, uint8_t pass, uint8_t depth_fail) {
|
||||
return fail == replace_op || pass == replace_op || depth_fail == replace_op;
|
||||
}
|
||||
|
||||
[[nodiscard]] static vk::Format ResolveHostDepthAttachmentFormat(const RenderCommandBuffer& buffer,
|
||||
const DepthFormatPolicy& policy,
|
||||
[[nodiscard]] static vk::Format ResolveHostDepthAttachmentFormat(const CommandBuffer& buffer,
|
||||
const DepthFormatPolicy& policy,
|
||||
bool has_stencil,
|
||||
uint32_t samples) {
|
||||
auto& graphics = buffer.GetGraphics();
|
||||
@@ -98,7 +97,7 @@ static bool UsesStencilOpValue(uint8_t fail, uint8_t pass, uint8_t depth_fail) {
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
|
||||
void RenderExecutor::ResolveRenderDepthTarget(uint64_t submit_id, RenderCommandBuffer& buffer,
|
||||
void RenderExecutor::ResolveRenderDepthTarget(uint64_t submit_id, CommandBuffer& buffer,
|
||||
RenderDepthInfo& r) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
(void)submit_id;
|
||||
@@ -109,9 +108,10 @@ void RenderExecutor::ResolveRenderDepthTarget(uint64_t submit_id, RenderCommandB
|
||||
const auto& sc = hw.GetStencilControl();
|
||||
const auto& sm = hw.GetStencilMask();
|
||||
const bool has_stencil = z.stencil_info.format != Prospero::StencilFormat::kInvalid;
|
||||
const bool depth_active =
|
||||
dc.z_enable || dc.z_write_enable || dc.depth_bounds_enable || rc.depth_clear_enable;
|
||||
const bool stencil_active = has_stencil && (dc.stencil_enable || rc.stencil_clear_enable);
|
||||
const bool depth_active = dc.z_enable || dc.z_write_enable || dc.depth_bounds_enable ||
|
||||
rc.depth_clear_enable || rc.copy_depth_to_color;
|
||||
const bool stencil_active =
|
||||
has_stencil && (dc.stencil_enable || rc.stencil_clear_enable || rc.copy_stencil_to_color);
|
||||
if (!depth_active && !stencil_active) {
|
||||
return;
|
||||
}
|
||||
@@ -128,22 +128,14 @@ void RenderExecutor::ResolveRenderDepthTarget(uint64_t submit_id, RenderCommandB
|
||||
!z.stencil_info.expclear_enabled && !z.stencil_info.partially_resident &&
|
||||
z.depth_view.slice_start == 0 && z.depth_view.slice_max == 0 &&
|
||||
z.depth_view.current_mip_level == 0 && !z.depth_view.depth_write_disable &&
|
||||
!z.depth_view.stencil_write_disable && z.depth_info.addr5_swizzle_mask == 0 &&
|
||||
z.depth_info.array_mode == 0 && z.depth_info.pipe_config == 0 &&
|
||||
z.depth_info.bank_width == 0 && z.depth_info.bank_height == 0 &&
|
||||
z.depth_info.macro_tile_aspect == 0 && z.depth_info.num_banks == 0 &&
|
||||
z.htile_surface.linear == 0 && z.htile_surface.full_cache == 0 &&
|
||||
z.htile_surface.htile_uses_preload_win == 0 && z.htile_surface.preload == 0 &&
|
||||
z.htile_surface.prefetch_width == 0 && z.htile_surface.prefetch_height == 0 &&
|
||||
z.htile_surface.dst_outside_zero_to_one == 0 && z.z_read_base_addr == 0 &&
|
||||
z.z_write_base_addr == 0 && z.stencil_read_base_addr == 0 &&
|
||||
z.stencil_write_base_addr == 0 && z.htile_data_base_addr == 0 &&
|
||||
!z.depth_view.stencil_write_disable && z.z_read_base_addr == 0 && z.z_write_base_addr == 0 &&
|
||||
z.stencil_read_base_addr == 0 && z.stencil_write_base_addr == 0 &&
|
||||
z.htile_data_base_addr == 0 &&
|
||||
// DB_DEPTH_SIZE_XY is independent state and may remain programmed after the attachment
|
||||
// formats and addresses are unbound. A zero encoding is the valid 1x1 value, so its
|
||||
// presence alone must not manufacture a depth attachment.
|
||||
!z.z_info.htile_acceleration && !z.width_height_valid && !z.pitch_height_valid &&
|
||||
z.size.x_max == 0 && z.size.y_max == 0 && z.pitch_div8_minus1 == 0 &&
|
||||
z.height_div8_minus1 == 0 && z.slice_div64_minus1 == 0 && z.width == 0 && z.height == 0;
|
||||
!z.z_info.htile_acceleration && z.shading_rate_encoding == 0 && z.size.x_max == 0 &&
|
||||
z.size.y_max == 0;
|
||||
if (attachment_unbound) {
|
||||
static std::atomic_bool logged = false;
|
||||
if (!logged.exchange(true, std::memory_order_relaxed)) {
|
||||
@@ -166,17 +158,12 @@ void RenderExecutor::ResolveRenderDepthTarget(uint64_t submit_id, RenderCommandB
|
||||
DepthFatal("invalid depth view: base=%u last=%u", z.depth_view.slice_start,
|
||||
z.depth_view.slice_max);
|
||||
}
|
||||
if (rc.copy_centroid || rc.copy_sample != 0 || z.z_info.expclear_enabled ||
|
||||
if (rc.copy_depth_to_color || rc.copy_stencil_to_color || rc.copy_centroid ||
|
||||
rc.copy_sample != 0 || z.z_info.expclear_enabled ||
|
||||
z.stencil_info.expclear_enabled || z.z_info.partially_resident ||
|
||||
z.stencil_info.partially_resident || z.z_info.max_mip_level != 0 ||
|
||||
z.depth_view.current_mip_level != 0 || z.depth_info.addr5_swizzle_mask != 0 ||
|
||||
z.depth_info.array_mode != 0 || z.depth_info.pipe_config != 0 ||
|
||||
z.depth_info.bank_width != 0 || z.depth_info.bank_height != 0 ||
|
||||
z.depth_info.macro_tile_aspect != 0 || z.depth_info.num_banks != 0 ||
|
||||
z.htile_surface.linear != 0 || z.htile_surface.full_cache != 0 ||
|
||||
z.htile_surface.htile_uses_preload_win != 0 || z.htile_surface.preload != 0 ||
|
||||
z.htile_surface.prefetch_width != 0 || z.htile_surface.prefetch_height != 0 ||
|
||||
z.htile_surface.dst_outside_zero_to_one != 0 || z.z_read_base_addr == 0 ||
|
||||
z.depth_view.current_mip_level != 0 || z.shading_rate_encoding != 0 ||
|
||||
z.z_read_base_addr == 0 ||
|
||||
(!z.depth_view.depth_write_disable && z.z_write_base_addr != z.z_read_base_addr) ||
|
||||
(z.z_read_base_addr & 0xffffu) != 0 ||
|
||||
dc.zfunc > static_cast<uint8_t>(vk::CompareOp::eAlways)) {
|
||||
@@ -190,10 +177,7 @@ void RenderExecutor::ResolveRenderDepthTarget(uint64_t submit_id, RenderCommandB
|
||||
(z.stencil_read_base_addr & 0xffffu) != 0) {
|
||||
DepthFatal("unsupported stencil attachment state");
|
||||
}
|
||||
} else if (z.stencil_read_base_addr != 0 || z.stencil_write_base_addr != 0 ||
|
||||
!htile_stencil_compat ||
|
||||
z.stencil_info.texture_compatibility !=
|
||||
Prospero::TextureCompatibleStencil::kDisable) {
|
||||
} else if (z.stencil_read_base_addr != 0 || z.stencil_write_base_addr != 0) {
|
||||
DepthFatal("stencil state without an active stencil attachment");
|
||||
}
|
||||
if (has_htile) {
|
||||
@@ -204,18 +188,13 @@ void RenderExecutor::ResolveRenderDepthTarget(uint64_t submit_id, RenderCommandB
|
||||
DepthFatal("HTile clear tracking supports at most 32 slices");
|
||||
}
|
||||
}
|
||||
const bool size_xy_valid = z.size.valid;
|
||||
const bool wh_valid = z.width_height_valid && z.width != 0 && z.height != 0;
|
||||
if (!size_xy_valid && !wh_valid) {
|
||||
if (!z.size.valid) {
|
||||
DepthFatal("missing depth extent");
|
||||
}
|
||||
const uint32_t width = size_xy_valid ? static_cast<uint32_t>(z.size.x_max) + 1u : z.width;
|
||||
const uint32_t height = size_xy_valid ? static_cast<uint32_t>(z.size.y_max) + 1u : z.height;
|
||||
if (width > 16384 || height > 16384 ||
|
||||
(size_xy_valid && wh_valid && (width != z.width || height != z.height)) ||
|
||||
(!z.pitch_height_valid &&
|
||||
(z.pitch_div8_minus1 != 0 || z.height_div8_minus1 != 0 || z.slice_div64_minus1 != 0))) {
|
||||
DepthFatal("inconsistent depth extent or encoded layout");
|
||||
const uint32_t width = static_cast<uint32_t>(z.size.x_max) + 1u;
|
||||
const uint32_t height = static_cast<uint32_t>(z.size.y_max) + 1u;
|
||||
if (width > 16384 || height > 16384) {
|
||||
DepthFatal("invalid depth extent");
|
||||
}
|
||||
const auto* policy = FindDepthFormatPolicy(z.z_info.format);
|
||||
if (policy == nullptr) {
|
||||
@@ -230,11 +209,6 @@ void RenderExecutor::ResolveRenderDepthTarget(uint64_t submit_id, RenderCommandB
|
||||
const auto guest_format = policy->guest_format;
|
||||
const uint32_t bytes = policy->bytes_per_element;
|
||||
const auto pitch = TileGetDepthPitch(width, bytes, z.z_info.num_samples);
|
||||
if (z.pitch_height_valid && ((static_cast<uint64_t>(z.pitch_div8_minus1) + 1u) * 8u != pitch ||
|
||||
(static_cast<uint64_t>(z.height_div8_minus1) + 1u) * 8u !=
|
||||
((static_cast<uint64_t>(height) + 7u) & ~7ull))) {
|
||||
DepthFatal("encoded depth pitch or height mismatch");
|
||||
}
|
||||
TileSizeAlign depth_size {};
|
||||
TileSizeAlign stencil_size {};
|
||||
TileSizeAlign htile_size {};
|
||||
@@ -245,14 +219,6 @@ void RenderExecutor::ResolveRenderDepthTarget(uint64_t submit_id, RenderCommandB
|
||||
(has_htile != (htile_size.align == 32768 && htile_size.size != 0))) {
|
||||
DepthFatal("unsupported depth/stencil/HTile footprint");
|
||||
}
|
||||
if (z.pitch_height_valid &&
|
||||
(static_cast<uint64_t>(z.slice_div64_minus1) + 1u) * 64u != depth_size.size) {
|
||||
DepthFatal("depth footprint mismatch: extent=%ux%u pitch=%u expected=0x%016" PRIx64
|
||||
" align=0x%016" PRIx64 " encoded_valid=%u encoded=0x%016" PRIx64,
|
||||
width, height, pitch, depth_size.size, depth_size.align,
|
||||
z.pitch_height_valid ? 1u : 0u,
|
||||
(static_cast<uint64_t>(z.slice_div64_minus1) + 1u) * 64u);
|
||||
}
|
||||
if (depth_size.size > UINT64_MAX / view.image_layers ||
|
||||
stencil_size.size > UINT64_MAX / view.image_layers ||
|
||||
htile_size.size > UINT64_MAX / view.image_layers) {
|
||||
@@ -261,9 +227,9 @@ void RenderExecutor::ResolveRenderDepthTarget(uint64_t submit_id, RenderCommandB
|
||||
const auto depth_backing_size = depth_size.size * view.image_layers;
|
||||
const auto stencil_backing_size = stencil_size.size * view.image_layers;
|
||||
const auto htile_backing_size = htile_size.size * view.image_layers;
|
||||
if (depth_backing_size > TRACKER_ADDRESS_SIZE - z.z_read_base_addr ||
|
||||
(has_stencil && stencil_backing_size > TRACKER_ADDRESS_SIZE - z.stencil_read_base_addr) ||
|
||||
(has_htile && htile_backing_size > TRACKER_ADDRESS_SIZE - z.htile_data_base_addr)) {
|
||||
if (!GuestRange {z.z_read_base_addr, depth_backing_size}.Valid() ||
|
||||
(has_stencil && !GuestRange {z.stencil_read_base_addr, stencil_backing_size}.Valid()) ||
|
||||
(has_htile && !GuestRange {z.htile_data_base_addr, htile_backing_size}.Valid())) {
|
||||
DepthFatal("layered depth backing range is invalid");
|
||||
}
|
||||
r.htile = has_htile;
|
||||
@@ -290,8 +256,8 @@ void RenderExecutor::ResolveRenderDepthTarget(uint64_t submit_id, RenderCommandB
|
||||
|
||||
r.stencil_clear_enable =
|
||||
has_stencil && rc.stencil_clear_enable && !z.depth_view.stencil_write_disable;
|
||||
r.stencil_clear_value = hw.GetStencilClearValue();
|
||||
r.stencil_test_enable = has_stencil && dc.stencil_enable;
|
||||
r.stencil_clear_value = hw.GetStencilClearValue();
|
||||
r.stencil_test_enable = has_stencil && dc.stencil_enable;
|
||||
if (r.stencil_test_enable) {
|
||||
const bool stencil_ops_disabled =
|
||||
rc.stencil_clear_enable || z.depth_view.stencil_write_disable;
|
||||
|
||||
@@ -11,11 +11,11 @@
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
class RenderCommandBuffer;
|
||||
|
||||
// Without a stencil plane, Hi-Stencil fields are inactive. An active plane is compatible when
|
||||
// Hi-Stencil is disabled or HTile backing is present.
|
||||
inline constexpr bool depth_htile_stencil_acceleration_compatible(bool has_stencil, bool has_htile,
|
||||
bool htile_stencil_disabled) {
|
||||
return htile_stencil_disabled || (has_stencil && has_htile);
|
||||
return !has_stencil || htile_stencil_disabled || has_htile;
|
||||
}
|
||||
|
||||
struct RenderDepthInfo {
|
||||
|
||||
@@ -73,10 +73,8 @@ namespace {
|
||||
return usage;
|
||||
}
|
||||
|
||||
void ValidateRange(GuestRange range, const char* name) {
|
||||
if ((range.address == 0) != (range.size == 0) ||
|
||||
(range.address != 0 && (range.address >= TRACKER_ADDRESS_SIZE ||
|
||||
range.size > TRACKER_ADDRESS_SIZE - range.address))) {
|
||||
void ValidateOptionalRange(GuestRange range, const char* name) {
|
||||
if (!range.ValidOrEmpty()) {
|
||||
EXIT("invalid %s image range: address=0x%016llx size=0x%016llx\n", name,
|
||||
static_cast<unsigned long long>(range.address),
|
||||
static_cast<unsigned long long>(range.size));
|
||||
@@ -567,13 +565,13 @@ void Image::CopyMip(Image& source, uint32_t mip, uint32_t layer) {
|
||||
namespace ImageOps {
|
||||
|
||||
void Validate(const ImageInfo& info) {
|
||||
ValidateRange(info.data, "data");
|
||||
ValidateRange(info.stencil, "stencil");
|
||||
ValidateOptionalRange(info.data, "data");
|
||||
ValidateOptionalRange(info.stencil, "stencil");
|
||||
|
||||
if (info.pixel_format == vk::Format::eUndefined) {
|
||||
const bool metadata_empty =
|
||||
info.metadata.range.address == 0 && info.metadata.range.size == 0 &&
|
||||
info.metadata.kind == ImageMetadataKind::None && info.metadata.control == 0 &&
|
||||
info.metadata.range.Empty() && info.metadata.kind == ImageMetadataKind::None &&
|
||||
info.metadata.control == 0 &&
|
||||
info.metadata.compression == VideoOutCompression::Uncompressed &&
|
||||
!info.metadata.stencil_compressed;
|
||||
if (info.data.Empty() || info.HasStencil() || !metadata_empty || info.extent.width == 0 ||
|
||||
@@ -620,16 +618,14 @@ void Validate(const ImageInfo& info) {
|
||||
}
|
||||
switch (info.metadata.kind) {
|
||||
case ImageMetadataKind::None:
|
||||
if (info.metadata.range.address != 0 || info.metadata.range.size != 0 ||
|
||||
info.metadata.control != 0 ||
|
||||
if (!info.metadata.range.Empty() || info.metadata.control != 0 ||
|
||||
info.metadata.compression != VideoOutCompression::Uncompressed ||
|
||||
info.metadata.stencil_compressed) {
|
||||
EXIT("metadata-free image has metadata state\n");
|
||||
}
|
||||
break;
|
||||
case ImageMetadataKind::Htile:
|
||||
ValidateRange(info.metadata.range, "HTILE");
|
||||
if (info.metadata.range.Empty() ||
|
||||
if (!info.metadata.range.Valid() ||
|
||||
info.metadata.compression != VideoOutCompression::Uncompressed) {
|
||||
EXIT("invalid HTILE metadata\n");
|
||||
}
|
||||
@@ -736,15 +732,10 @@ Image::~Image() {
|
||||
if (m_graphics == nullptr) {
|
||||
return;
|
||||
}
|
||||
{
|
||||
std::lock_guard lock(views.mutex);
|
||||
for (auto& cached: views.views) {
|
||||
if (cached.view != nullptr) {
|
||||
m_graphics->device.destroyImageView(cached.view, nullptr);
|
||||
cached.view = nullptr;
|
||||
}
|
||||
for (const auto& cached: views) {
|
||||
if (cached.view != nullptr) {
|
||||
m_graphics->device.destroyImageView(cached.view, nullptr);
|
||||
}
|
||||
views.views.clear();
|
||||
}
|
||||
if (backing.image != nullptr) {
|
||||
m_graphics->DeleteImage(backing);
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_IMAGE_H_
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/slotVector.h"
|
||||
#include "graphics/host_gpu/graphicContext.h"
|
||||
#include "graphics/host_gpu/renderer/image/imageInfo.h"
|
||||
|
||||
#include <compare>
|
||||
#include <limits>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
#include <utility>
|
||||
@@ -19,29 +19,13 @@ class Buffer;
|
||||
class CommandScheduler;
|
||||
struct ImageTestAccess;
|
||||
|
||||
struct ImageId {
|
||||
uint32_t index = std::numeric_limits<uint32_t>::max();
|
||||
uint32_t generation = 0;
|
||||
|
||||
[[nodiscard]] explicit operator bool() const noexcept {
|
||||
return index != std::numeric_limits<uint32_t>::max();
|
||||
}
|
||||
auto operator<=>(const ImageId&) const = default;
|
||||
};
|
||||
using ImageId = Common::SlotId;
|
||||
|
||||
struct CachedImageView {
|
||||
ImageViewInfo info;
|
||||
vk::ImageView view = nullptr;
|
||||
};
|
||||
|
||||
struct ImageViewCache {
|
||||
ImageViewCache() = default;
|
||||
KYTY_CLASS_NO_COPY(ImageViewCache);
|
||||
|
||||
std::mutex mutex;
|
||||
std::vector<CachedImageView> views;
|
||||
};
|
||||
|
||||
struct ImageUsage {
|
||||
bool texture = false;
|
||||
bool storage = false;
|
||||
@@ -157,7 +141,7 @@ public:
|
||||
|
||||
ImageInfo info;
|
||||
VulkanImage backing;
|
||||
ImageViewCache views;
|
||||
std::vector<CachedImageView> views;
|
||||
ImageUsage usage;
|
||||
ImageBinding binding;
|
||||
bool registered = false;
|
||||
|
||||
@@ -15,18 +15,6 @@
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
struct GuestRange {
|
||||
uint64_t address = 0;
|
||||
uint64_t size = 0;
|
||||
|
||||
[[nodiscard]] constexpr bool Empty() const noexcept { return address == 0 || size == 0; }
|
||||
[[nodiscard]] constexpr bool Valid() const noexcept {
|
||||
return !Empty() && address < TRACKER_ADDRESS_SIZE && size <= TRACKER_ADDRESS_SIZE - address;
|
||||
}
|
||||
[[nodiscard]] constexpr uint64_t End() const noexcept { return address + size; }
|
||||
auto operator<=>(const GuestRange&) const = default;
|
||||
};
|
||||
|
||||
enum class VideoOutCompression : uint8_t { Uncompressed, Dcc256_256_0, Dcc256_64_64, Unsupported };
|
||||
|
||||
enum class ImageMetadataKind : uint8_t { None, Htile, Dcc };
|
||||
@@ -443,6 +431,10 @@ IsSupportedDisplayRenderTargetTileMode(Prospero::TileMode tile_mode) noexcept {
|
||||
return encoded <= 0.04045f ? encoded / 12.92f : std::pow((encoded + 0.055f) / 1.055f, 2.4f);
|
||||
};
|
||||
switch (format) {
|
||||
// A single-plane float target carries its clear as raw float bits, the same encoding the
|
||||
// depth decoder below uses. Without this the clear is discarded and the target keeps stale
|
||||
// contents.
|
||||
case vk::Format::eR32Sfloat: next.float32[0] = std::bit_cast<float>(packed); break;
|
||||
case vk::Format::eR32Uint: next.uint32[0] = packed; break;
|
||||
case vk::Format::eR32Sint: next.int32[0] = static_cast<int32_t>(packed); break;
|
||||
case vk::Format::eR8G8B8A8Srgb:
|
||||
|
||||
@@ -4,8 +4,6 @@
|
||||
#include "graphics/host_gpu/graphicContext.h"
|
||||
#include "graphics/host_gpu/renderer/image/image.h"
|
||||
|
||||
#include <mutex>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
namespace {
|
||||
@@ -357,8 +355,7 @@ vk::ImageView Image::FindView(const ImageViewInfo& view_info) {
|
||||
image.layers);
|
||||
}
|
||||
|
||||
std::lock_guard lock(views.mutex);
|
||||
for (const auto& cached: views.views) {
|
||||
for (const auto& cached: views) {
|
||||
if (cached.info == normalized) {
|
||||
return cached.view;
|
||||
}
|
||||
@@ -394,7 +391,7 @@ vk::ImageView Image::FindView(const ImageViewInfo& view_info) {
|
||||
view_info.level_count, view_info.base_layer, view_info.layer_count,
|
||||
static_cast<vk::ImageUsageFlags::MaskType>(view_info.usage));
|
||||
}
|
||||
views.views.push_back({normalized, view});
|
||||
views.push_back({normalized, view});
|
||||
return view;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,318 +0,0 @@
|
||||
#include "graphics/host_gpu/renderer/pipeline/descriptorCache.h"
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/profiler.h"
|
||||
#include "graphics/shader/recompiler/ir/ShaderIR.h"
|
||||
|
||||
#include <array>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
namespace {
|
||||
|
||||
using BindingKind = ShaderRecompiler::IR::DescriptorBindingKind;
|
||||
|
||||
vk::ShaderStageFlags StageFlags(DescriptorCache::Stage stage) {
|
||||
switch (stage) {
|
||||
case DescriptorCache::Stage::Vertex: return vk::ShaderStageFlagBits::eVertex;
|
||||
case DescriptorCache::Stage::Pixel: return vk::ShaderStageFlagBits::eFragment;
|
||||
case DescriptorCache::Stage::Compute: return vk::ShaderStageFlagBits::eCompute;
|
||||
default: EXIT("unknown descriptor stage\n");
|
||||
}
|
||||
}
|
||||
|
||||
bool IsSampledImage(BindingKind kind) {
|
||||
switch (kind) {
|
||||
case BindingKind::Sampled1D:
|
||||
case BindingKind::Sampled1DArray:
|
||||
case BindingKind::Sampled2D:
|
||||
case BindingKind::Sampled2DArray:
|
||||
case BindingKind::Sampled2DMsaa:
|
||||
case BindingKind::Sampled2DMsaaArray:
|
||||
case BindingKind::Sampled3D:
|
||||
case BindingKind::SampledUint1D:
|
||||
case BindingKind::SampledUint1DArray:
|
||||
case BindingKind::SampledUint2D:
|
||||
case BindingKind::SampledUint2DArray:
|
||||
case BindingKind::SampledUint2DMsaa:
|
||||
case BindingKind::SampledUint2DMsaaArray:
|
||||
case BindingKind::SampledUint3D: return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool IsStorageImage(BindingKind kind) {
|
||||
switch (kind) {
|
||||
case BindingKind::Storage1D:
|
||||
case BindingKind::Storage1DArray:
|
||||
case BindingKind::Storage2D:
|
||||
case BindingKind::Storage2DArray:
|
||||
case BindingKind::Storage3D:
|
||||
case BindingKind::StorageUint1D:
|
||||
case BindingKind::StorageUint1DArray:
|
||||
case BindingKind::StorageUint2D:
|
||||
case BindingKind::StorageUint2DArray:
|
||||
case BindingKind::StorageUint3D:
|
||||
case BindingKind::StorageAtomic1D:
|
||||
case BindingKind::StorageAtomic1DArray:
|
||||
case BindingKind::StorageAtomic2D:
|
||||
case BindingKind::StorageAtomic2DArray:
|
||||
case BindingKind::StorageAtomic3D: return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
vk::DescriptorType DescriptorType(BindingKind kind) {
|
||||
if (kind == BindingKind::Samplers) {
|
||||
return vk::DescriptorType::eSampler;
|
||||
}
|
||||
if (IsSampledImage(kind)) {
|
||||
return vk::DescriptorType::eSampledImage;
|
||||
}
|
||||
if (IsStorageImage(kind)) {
|
||||
return vk::DescriptorType::eStorageImage;
|
||||
}
|
||||
return vk::DescriptorType::eStorageBuffer;
|
||||
}
|
||||
|
||||
uint32_t DescriptorCount(const ShaderRecompiler::IR::DescriptorBinding& binding) {
|
||||
return binding.resources.empty() ? 1u : static_cast<uint32_t>(binding.resources.size());
|
||||
}
|
||||
|
||||
std::vector<uint32_t> LayoutKey(DescriptorCache::Stage stage,
|
||||
const ShaderRecompiler::IR::Program& program) {
|
||||
std::vector<uint32_t> key;
|
||||
key.reserve(1u + program.bindings.descriptors.size() * 4u);
|
||||
key.push_back(static_cast<uint32_t>(stage));
|
||||
for (const auto& binding: program.bindings.descriptors) {
|
||||
key.push_back(static_cast<uint32_t>(binding.kind));
|
||||
key.push_back(binding.binding);
|
||||
key.push_back(DescriptorCount(binding));
|
||||
key.push_back(static_cast<uint32_t>(DescriptorType(binding.kind)));
|
||||
}
|
||||
return key;
|
||||
}
|
||||
|
||||
vk::DescriptorBufferInfo BufferInfo(const BufferView& view) {
|
||||
EXIT_IF(view.buffer == nullptr);
|
||||
return {view.buffer, view.offset, view.range};
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
vk::DescriptorImageInfo DescriptorCache::MakeImageInfo(const TextureBinding& texture,
|
||||
uint32_t element) {
|
||||
const auto view =
|
||||
texture.mip_views.empty()
|
||||
? (element == 0u ? texture.image_view : vk::ImageView {})
|
||||
: (element < texture.mip_views.size() ? texture.mip_views[element] : vk::ImageView {});
|
||||
EXIT_IF(!texture.image_id || view == nullptr || texture.layout == vk::ImageLayout::eUndefined);
|
||||
return {nullptr, view, texture.layout};
|
||||
}
|
||||
|
||||
DescriptorCache::~DescriptorCache() {
|
||||
for (auto& [layout, sets]: m_free_sets_by_layout) {
|
||||
(void)layout;
|
||||
for (auto* set: sets) {
|
||||
delete set;
|
||||
}
|
||||
}
|
||||
for (const auto& [key, layout]: m_descriptor_set_layouts) {
|
||||
(void)key;
|
||||
m_graphics.device.destroyDescriptorSetLayout(layout, nullptr);
|
||||
}
|
||||
for (const auto& pool: m_pools) {
|
||||
m_graphics.device.destroyDescriptorPool(pool.pool, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
vk::DescriptorSetLayout
|
||||
DescriptorCache::GetDescriptorSetLayoutInternal(Stage stage,
|
||||
const ShaderRecompiler::IR::Program& program) {
|
||||
const auto key = LayoutKey(stage, program);
|
||||
if (const auto found = m_descriptor_set_layouts.find(key);
|
||||
found != m_descriptor_set_layouts.end()) {
|
||||
return found->second;
|
||||
}
|
||||
|
||||
std::vector<vk::DescriptorSetLayoutBinding> bindings;
|
||||
bindings.reserve(program.bindings.descriptors.size());
|
||||
for (const auto& descriptor: program.bindings.descriptors) {
|
||||
bindings.push_back({descriptor.binding, DescriptorType(descriptor.kind),
|
||||
DescriptorCount(descriptor), StageFlags(stage), nullptr});
|
||||
}
|
||||
if (bindings.empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
vk::DescriptorSetLayoutCreateInfo info {};
|
||||
info.sType = vk::StructureType::eDescriptorSetLayoutCreateInfo;
|
||||
info.bindingCount = static_cast<uint32_t>(bindings.size());
|
||||
info.pBindings = bindings.data();
|
||||
vk::DescriptorSetLayout layout = nullptr;
|
||||
const auto result = m_graphics.device.createDescriptorSetLayout(&info, nullptr, &layout);
|
||||
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess || layout == nullptr);
|
||||
m_descriptor_set_layouts.emplace(key, layout);
|
||||
return layout;
|
||||
}
|
||||
|
||||
void DescriptorCache::CreatePool() {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
constexpr uint32_t MaxSets = 512;
|
||||
const vk::DescriptorPoolSize sizes[] = {
|
||||
{vk::DescriptorType::eStorageBuffer,
|
||||
MaxSets * (ShaderRecompiler::IR::ShaderInfo::MaxBuffers +
|
||||
ShaderRecompiler::IR::ShaderInfo::MaxAddresses + 3u)},
|
||||
{vk::DescriptorType::eSampledImage, MaxSets * ShaderRecompiler::IR::ShaderInfo::MaxImages},
|
||||
{vk::DescriptorType::eStorageImage, MaxSets * ShaderRecompiler::IR::ShaderInfo::MaxImages},
|
||||
{vk::DescriptorType::eSampler, MaxSets * ShaderRecompiler::IR::ShaderInfo::MaxSamplers},
|
||||
};
|
||||
vk::DescriptorPoolCreateInfo info {};
|
||||
info.sType = vk::StructureType::eDescriptorPoolCreateInfo;
|
||||
info.poolSizeCount = static_cast<uint32_t>(std::size(sizes));
|
||||
info.pPoolSizes = sizes;
|
||||
info.maxSets = MaxSets;
|
||||
const auto pool_id = static_cast<int>(m_pools.size());
|
||||
auto& pool = m_pools.emplace_back();
|
||||
const auto result = m_graphics.device.createDescriptorPool(&info, nullptr, &pool.pool);
|
||||
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess || pool.pool == nullptr);
|
||||
pool.next_free_pool = m_first_free_pool;
|
||||
m_first_free_pool = pool_id;
|
||||
}
|
||||
|
||||
VulkanDescriptorSet* DescriptorCache::Allocate(Stage stage,
|
||||
const ShaderRecompiler::IR::Program& program) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
Common::LockGuard lock(m_mutex);
|
||||
const auto layout = GetDescriptorSetLayoutInternal(stage, program);
|
||||
EXIT_IF(layout == nullptr);
|
||||
auto& free_sets = m_free_sets_by_layout[layout];
|
||||
if (!free_sets.empty()) {
|
||||
auto* result = free_sets.back();
|
||||
free_sets.pop_back();
|
||||
return result;
|
||||
}
|
||||
|
||||
constexpr uint32_t Batch = 32;
|
||||
for (int attempt = 0; attempt < 2; attempt++) {
|
||||
for (int pool_id = m_first_free_pool; pool_id != -1;
|
||||
pool_id = m_pools[pool_id].next_free_pool) {
|
||||
auto& pool = m_pools[pool_id];
|
||||
std::array<vk::DescriptorSetLayout, Batch> layouts;
|
||||
layouts.fill(layout);
|
||||
std::array<vk::DescriptorSet, Batch> sets {};
|
||||
vk::DescriptorSetAllocateInfo info {};
|
||||
info.sType = vk::StructureType::eDescriptorSetAllocateInfo;
|
||||
info.descriptorPool = pool.pool;
|
||||
info.descriptorSetCount = Batch;
|
||||
info.pSetLayouts = layouts.data();
|
||||
if (m_graphics.device.allocateDescriptorSets(&info, sets.data()) ==
|
||||
vk::Result::eSuccess) {
|
||||
free_sets.reserve(free_sets.size() + Batch - 1u);
|
||||
for (uint32_t i = 0; i + 1u < Batch; i++) {
|
||||
free_sets.push_back(new VulkanDescriptorSet {sets[i], layout, pool_id});
|
||||
}
|
||||
return new VulkanDescriptorSet {sets.back(), layout, pool_id};
|
||||
}
|
||||
m_first_free_pool = pool.next_free_pool;
|
||||
break;
|
||||
}
|
||||
CreatePool();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void DescriptorCache::Recycle(VulkanDescriptorSet& set) {
|
||||
EXIT_IF(set.set == nullptr || set.layout == nullptr);
|
||||
Common::LockGuard lock(m_mutex);
|
||||
m_free_sets_by_layout[set.layout].push_back(&set);
|
||||
}
|
||||
|
||||
VulkanDescriptorSet& DescriptorCache::GetDescriptor(Stage stage,
|
||||
const ShaderRecompiler::IR::Program& program,
|
||||
const NativeDescriptors& data) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
EXIT_IF(data.buffers.size() != program.info.buffers.size() ||
|
||||
data.images.size() != program.info.images.size() ||
|
||||
data.samplers.size() != program.info.samplers.size() ||
|
||||
data.addresses.size() != program.info.addresses.size());
|
||||
auto* set = Allocate(stage, program);
|
||||
EXIT_NOT_IMPLEMENTED(set == nullptr);
|
||||
|
||||
size_t descriptor_count = 0;
|
||||
for (const auto& binding: program.bindings.descriptors) {
|
||||
descriptor_count += DescriptorCount(binding);
|
||||
}
|
||||
std::vector<vk::DescriptorBufferInfo> buffer_infos;
|
||||
std::vector<vk::DescriptorImageInfo> image_infos;
|
||||
std::vector<vk::WriteDescriptorSet> writes;
|
||||
buffer_infos.reserve(descriptor_count);
|
||||
image_infos.reserve(descriptor_count);
|
||||
writes.reserve(program.bindings.descriptors.size());
|
||||
std::vector<uint32_t> image_occurrences(data.images.size());
|
||||
|
||||
for (const auto& binding: program.bindings.descriptors) {
|
||||
vk::WriteDescriptorSet write {};
|
||||
write.sType = vk::StructureType::eWriteDescriptorSet;
|
||||
write.dstSet = set->set;
|
||||
write.dstBinding = binding.binding;
|
||||
write.descriptorType = DescriptorType(binding.kind);
|
||||
write.descriptorCount = DescriptorCount(binding);
|
||||
const auto buffer_start = buffer_infos.size();
|
||||
const auto image_start = image_infos.size();
|
||||
switch (binding.kind) {
|
||||
case BindingKind::Buffers:
|
||||
for (const auto resource: binding.resources) {
|
||||
buffer_infos.push_back(BufferInfo(data.buffers.at(resource)));
|
||||
}
|
||||
break;
|
||||
case BindingKind::AddressMemory:
|
||||
for (const auto resource: binding.resources) {
|
||||
buffer_infos.push_back(BufferInfo(data.addresses.at(resource)));
|
||||
}
|
||||
break;
|
||||
case BindingKind::FlattenedSrt:
|
||||
buffer_infos.push_back(BufferInfo(data.flattened_srt));
|
||||
break;
|
||||
case BindingKind::UserData: buffer_infos.push_back(BufferInfo(data.user_data)); break;
|
||||
case BindingKind::Gds: buffer_infos.push_back(BufferInfo(data.gds)); break;
|
||||
case BindingKind::Samplers:
|
||||
for (const auto resource: binding.resources) {
|
||||
const auto sampler = data.samplers.at(resource);
|
||||
EXIT_IF(sampler == nullptr);
|
||||
image_infos.push_back({sampler, nullptr, vk::ImageLayout::eUndefined});
|
||||
}
|
||||
break;
|
||||
default: {
|
||||
for (const auto resource: binding.resources) {
|
||||
const auto& texture = data.images.at(resource);
|
||||
image_infos.push_back(MakeImageInfo(texture, image_occurrences.at(resource)++));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (buffer_infos.size() != buffer_start) {
|
||||
write.pBufferInfo = buffer_infos.data() + buffer_start;
|
||||
}
|
||||
if (image_infos.size() != image_start) {
|
||||
write.pImageInfo = image_infos.data() + image_start;
|
||||
}
|
||||
writes.push_back(write);
|
||||
}
|
||||
for (uint32_t i = 0; i < data.images.size(); i++) {
|
||||
const auto expected = data.images[i].mip_views.empty()
|
||||
? 1u
|
||||
: static_cast<uint32_t>(data.images[i].mip_views.size());
|
||||
EXIT_IF(image_occurrences[i] != expected);
|
||||
}
|
||||
m_graphics.device.updateDescriptorSets(static_cast<uint32_t>(writes.size()), writes.data(), 0,
|
||||
nullptr);
|
||||
return *set;
|
||||
}
|
||||
|
||||
vk::DescriptorSetLayout
|
||||
DescriptorCache::GetDescriptorSetLayout(Stage stage, const ShaderRecompiler::IR::Program& program) {
|
||||
Common::LockGuard lock(m_mutex);
|
||||
return GetDescriptorSetLayoutInternal(stage, program);
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
@@ -1,116 +0,0 @@
|
||||
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_DESCRIPTORCACHE_H_
|
||||
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_DESCRIPTORCACHE_H_
|
||||
|
||||
#include "common/abi.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/common.h"
|
||||
#include "common/threads.h"
|
||||
#include "graphics/host_gpu/graphicContext.h"
|
||||
#include "graphics/host_gpu/renderer/cache/textureCache.h"
|
||||
#include "graphics/host_gpu/vulkanCommon.h"
|
||||
#include "graphics/shader/shaderBindings.h"
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <span>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
namespace ShaderRecompiler::IR {
|
||||
struct DescriptorValue;
|
||||
struct ImageResource;
|
||||
struct Program;
|
||||
struct ResourceSnapshot;
|
||||
} // namespace ShaderRecompiler::IR
|
||||
|
||||
class CommandBuffer;
|
||||
struct DescriptorCacheTestAccess;
|
||||
struct ShaderStageRuntime;
|
||||
|
||||
struct VulkanDescriptorSet {
|
||||
vk::DescriptorSet set = nullptr;
|
||||
vk::DescriptorSetLayout layout = nullptr;
|
||||
int pool_id = -1;
|
||||
};
|
||||
|
||||
struct BufferView {
|
||||
std::shared_ptr<void> owner;
|
||||
vk::Buffer buffer = nullptr;
|
||||
vk::DeviceSize offset = 0;
|
||||
vk::DeviceSize range = VK_WHOLE_SIZE;
|
||||
};
|
||||
|
||||
class DescriptorCache {
|
||||
public:
|
||||
enum class Stage { Unknown, Vertex, Pixel, Compute };
|
||||
|
||||
struct TextureBinding {
|
||||
ImageId image_id;
|
||||
vk::ImageView image_view = nullptr;
|
||||
TextureCache::ImageDesc desc;
|
||||
vk::ImageLayout layout = vk::ImageLayout::eUndefined;
|
||||
std::vector<vk::ImageView> mip_views;
|
||||
};
|
||||
|
||||
struct NativeDescriptors {
|
||||
std::vector<BufferView> buffers;
|
||||
std::vector<TextureBinding> images;
|
||||
std::vector<vk::Sampler> samplers;
|
||||
std::vector<BufferView> addresses;
|
||||
BufferView gds;
|
||||
BufferView flattened_srt;
|
||||
BufferView user_data;
|
||||
};
|
||||
|
||||
struct PreparedBindings {
|
||||
std::shared_ptr<const ShaderRecompiler::IR::Program> program;
|
||||
std::shared_ptr<const ShaderRecompiler::IR::ResourceSnapshot> snapshot;
|
||||
NativeDescriptors resources;
|
||||
std::vector<uint32_t> flattened_srt;
|
||||
std::vector<uint32_t> user_data;
|
||||
vk::ShaderStageFlags shader_stage;
|
||||
Stage stage = Stage::Unknown;
|
||||
bool committed = false;
|
||||
};
|
||||
|
||||
explicit DescriptorCache(GraphicContext& graphics): m_graphics(graphics) {
|
||||
EXIT_NOT_IMPLEMENTED(!Common::Thread::IsMainThread());
|
||||
}
|
||||
~DescriptorCache();
|
||||
KYTY_CLASS_NO_COPY(DescriptorCache);
|
||||
|
||||
vk::DescriptorSetLayout GetDescriptorSetLayout(Stage stage,
|
||||
const ShaderRecompiler::IR::Program& program);
|
||||
void Recycle(VulkanDescriptorSet& set);
|
||||
VulkanDescriptorSet& GetDescriptor(Stage stage, const ShaderRecompiler::IR::Program& program,
|
||||
const NativeDescriptors& descriptors);
|
||||
|
||||
private:
|
||||
friend struct DescriptorCacheTestAccess;
|
||||
|
||||
struct Pool {
|
||||
vk::DescriptorPool pool = nullptr;
|
||||
int next_free_pool = -1;
|
||||
};
|
||||
|
||||
static vk::DescriptorImageInfo MakeImageInfo(const TextureBinding& texture,
|
||||
uint32_t element = 0);
|
||||
void CreatePool();
|
||||
VulkanDescriptorSet* Allocate(Stage stage, const ShaderRecompiler::IR::Program& program);
|
||||
vk::DescriptorSetLayout
|
||||
GetDescriptorSetLayoutInternal(Stage stage, const ShaderRecompiler::IR::Program& program);
|
||||
|
||||
GraphicContext& m_graphics;
|
||||
Common::Mutex m_mutex;
|
||||
std::vector<Pool> m_pools;
|
||||
int m_first_free_pool = -1;
|
||||
std::unordered_map<vk::DescriptorSetLayout, std::vector<VulkanDescriptorSet*>>
|
||||
m_free_sets_by_layout;
|
||||
std::map<std::vector<uint32_t>, vk::DescriptorSetLayout> m_descriptor_set_layouts;
|
||||
};
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_DESCRIPTORCACHE_H_
|
||||
@@ -0,0 +1,97 @@
|
||||
#include "graphics/host_gpu/renderer/pipeline/descriptorHeap.h"
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/profiler.h"
|
||||
#include "graphics/host_gpu/graphicContext.h"
|
||||
#include "graphics/host_gpu/renderer/masterSemaphore.h"
|
||||
|
||||
namespace Libs::Graphics {
|
||||
namespace {
|
||||
|
||||
constexpr uint32_t DescriptorHeapCount = 1024;
|
||||
constexpr std::array DescriptorPoolSizes = {
|
||||
vk::DescriptorPoolSize {vk::DescriptorType::eStorageBuffer, 8192},
|
||||
vk::DescriptorPoolSize {vk::DescriptorType::eSampledImage, 8192},
|
||||
vk::DescriptorPoolSize {vk::DescriptorType::eStorageImage, 1024},
|
||||
vk::DescriptorPoolSize {vk::DescriptorType::eSampler, 1024},
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
DescriptorHeap::DescriptorHeap(GraphicContext& graphics, MasterSemaphore& master_semaphore)
|
||||
: m_graphics(graphics), m_master_semaphore(master_semaphore) {
|
||||
CreatePool();
|
||||
}
|
||||
|
||||
DescriptorHeap::~DescriptorHeap() {
|
||||
m_graphics.device.destroyDescriptorPool(m_current_pool, nullptr);
|
||||
for (const auto& [pool, tick]: m_pending_pools) {
|
||||
m_master_semaphore.Wait(tick);
|
||||
m_graphics.device.destroyDescriptorPool(pool, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
vk::DescriptorSet DescriptorHeap::Commit(vk::DescriptorSetLayout layout) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
EXIT_IF(layout == nullptr);
|
||||
|
||||
auto& batch = m_sets[layout];
|
||||
if (batch.size != 0) {
|
||||
return batch.sets[--batch.size];
|
||||
}
|
||||
if (Allocate(layout, batch)) {
|
||||
return batch.sets[--batch.size];
|
||||
}
|
||||
|
||||
m_pending_pools.emplace_back(m_current_pool, m_master_semaphore.CurrentTick());
|
||||
if (const auto& [pool, tick] = m_pending_pools.front(); m_master_semaphore.IsFree(tick)) {
|
||||
m_current_pool = pool;
|
||||
m_pending_pools.pop_front();
|
||||
EXIT_IF(m_graphics.device.resetDescriptorPool(m_current_pool, {}) != vk::Result::eSuccess);
|
||||
} else {
|
||||
CreatePool();
|
||||
}
|
||||
|
||||
m_sets.clear();
|
||||
auto& fresh_batch = m_sets[layout];
|
||||
EXIT_IF(!Allocate(layout, fresh_batch));
|
||||
return fresh_batch.sets[--fresh_batch.size];
|
||||
}
|
||||
|
||||
bool DescriptorHeap::Allocate(vk::DescriptorSetLayout layout, Batch& batch) {
|
||||
std::array<vk::DescriptorSetLayout, DescriptorSetBatch> layouts;
|
||||
layouts.fill(layout);
|
||||
|
||||
vk::DescriptorSetAllocateInfo allocate {};
|
||||
allocate.sType = vk::StructureType::eDescriptorSetAllocateInfo;
|
||||
allocate.descriptorPool = m_current_pool;
|
||||
allocate.pSetLayouts = layouts.data();
|
||||
|
||||
for (;;) {
|
||||
allocate.descriptorSetCount = batch.allocation;
|
||||
const auto result = m_graphics.device.allocateDescriptorSets(&allocate, batch.sets.data());
|
||||
if (result == vk::Result::eSuccess) {
|
||||
batch.size = batch.allocation;
|
||||
return true;
|
||||
}
|
||||
EXIT_IF(result != vk::Result::eErrorOutOfPoolMemory &&
|
||||
result != vk::Result::eErrorFragmentedPool);
|
||||
if (batch.allocation == 1) {
|
||||
return false;
|
||||
}
|
||||
batch.allocation /= 2;
|
||||
}
|
||||
}
|
||||
|
||||
void DescriptorHeap::CreatePool() {
|
||||
vk::DescriptorPoolCreateInfo create {};
|
||||
create.sType = vk::StructureType::eDescriptorPoolCreateInfo;
|
||||
create.flags = {};
|
||||
create.maxSets = DescriptorHeapCount;
|
||||
create.poolSizeCount = static_cast<uint32_t>(DescriptorPoolSizes.size());
|
||||
create.pPoolSizes = DescriptorPoolSizes.data();
|
||||
EXIT_IF(m_graphics.device.createDescriptorPool(&create, nullptr, &m_current_pool) !=
|
||||
vk::Result::eSuccess);
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
@@ -0,0 +1,45 @@
|
||||
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_DESCRIPTORHEAP_H_
|
||||
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_DESCRIPTORHEAP_H_
|
||||
|
||||
#include "common/common.h"
|
||||
#include "graphics/host_gpu/vulkanCommon.h"
|
||||
|
||||
#include <array>
|
||||
#include <deque>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
struct GraphicContext;
|
||||
class MasterSemaphore;
|
||||
|
||||
class DescriptorHeap {
|
||||
public:
|
||||
DescriptorHeap(GraphicContext& graphics, MasterSemaphore& master_semaphore);
|
||||
~DescriptorHeap();
|
||||
KYTY_CLASS_NO_COPY(DescriptorHeap);
|
||||
|
||||
[[nodiscard]] vk::DescriptorSet Commit(vk::DescriptorSetLayout layout);
|
||||
|
||||
private:
|
||||
static constexpr uint32_t DescriptorSetBatch = 32;
|
||||
|
||||
struct Batch {
|
||||
std::array<vk::DescriptorSet, DescriptorSetBatch> sets {};
|
||||
uint32_t size = 0;
|
||||
uint32_t allocation = DescriptorSetBatch;
|
||||
};
|
||||
|
||||
[[nodiscard]] bool Allocate(vk::DescriptorSetLayout layout, Batch& batch);
|
||||
void CreatePool();
|
||||
|
||||
GraphicContext& m_graphics;
|
||||
MasterSemaphore& m_master_semaphore;
|
||||
vk::DescriptorPool m_current_pool = nullptr;
|
||||
std::deque<std::pair<vk::DescriptorPool, uint64_t>> m_pending_pools;
|
||||
std::unordered_map<vk::DescriptorSetLayout, Batch> m_sets;
|
||||
};
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_DESCRIPTORHEAP_H_
|
||||
@@ -17,7 +17,6 @@
|
||||
#include "graphics/host_gpu/renderer/debug.h"
|
||||
#include "graphics/host_gpu/renderer/image/imageView.h"
|
||||
#include "graphics/host_gpu/renderer/image/textureCommon.h"
|
||||
#include "graphics/host_gpu/renderer/pipeline/descriptorCache.h"
|
||||
#include "graphics/host_gpu/renderer/pipeline/shaderResourceBarrier.h"
|
||||
#include "graphics/host_gpu/renderer/render.h"
|
||||
#include "graphics/host_gpu/renderer/renderContext.h"
|
||||
@@ -45,10 +44,99 @@
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
namespace {
|
||||
|
||||
using BindingKind = ShaderRecompiler::IR::DescriptorBindingKind;
|
||||
|
||||
bool IsSampledImage(BindingKind kind) {
|
||||
switch (kind) {
|
||||
case BindingKind::Sampled1D:
|
||||
case BindingKind::Sampled1DArray:
|
||||
case BindingKind::Sampled2D:
|
||||
case BindingKind::Sampled2DArray:
|
||||
case BindingKind::Sampled2DMsaa:
|
||||
case BindingKind::Sampled2DMsaaArray:
|
||||
case BindingKind::Sampled3D:
|
||||
case BindingKind::SampledUint1D:
|
||||
case BindingKind::SampledUint1DArray:
|
||||
case BindingKind::SampledUint2D:
|
||||
case BindingKind::SampledUint2DArray:
|
||||
case BindingKind::SampledUint2DMsaa:
|
||||
case BindingKind::SampledUint2DMsaaArray:
|
||||
case BindingKind::SampledUint3D: return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool IsStorageImage(BindingKind kind) {
|
||||
switch (kind) {
|
||||
case BindingKind::Storage1D:
|
||||
case BindingKind::Storage1DArray:
|
||||
case BindingKind::Storage2D:
|
||||
case BindingKind::Storage2DArray:
|
||||
case BindingKind::Storage3D:
|
||||
case BindingKind::StorageUint1D:
|
||||
case BindingKind::StorageUint1DArray:
|
||||
case BindingKind::StorageUint2D:
|
||||
case BindingKind::StorageUint2DArray:
|
||||
case BindingKind::StorageUint3D:
|
||||
case BindingKind::StorageAtomic1D:
|
||||
case BindingKind::StorageAtomic1DArray:
|
||||
case BindingKind::StorageAtomic2D:
|
||||
case BindingKind::StorageAtomic2DArray:
|
||||
case BindingKind::StorageAtomic3D: return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
vk::DescriptorType NativeDescriptorType(BindingKind kind) {
|
||||
if (kind == BindingKind::Samplers) {
|
||||
return vk::DescriptorType::eSampler;
|
||||
}
|
||||
if (IsSampledImage(kind)) {
|
||||
return vk::DescriptorType::eSampledImage;
|
||||
}
|
||||
if (IsStorageImage(kind)) {
|
||||
return vk::DescriptorType::eStorageImage;
|
||||
}
|
||||
return vk::DescriptorType::eStorageBuffer;
|
||||
}
|
||||
|
||||
uint32_t NativeDescriptorCount(const ShaderRecompiler::IR::DescriptorBinding& binding) {
|
||||
return binding.resources.empty() ? 1u : static_cast<uint32_t>(binding.resources.size());
|
||||
}
|
||||
|
||||
vk::DescriptorImageInfo MakeImageInfo(const TextureBinding& texture, uint32_t element) {
|
||||
const auto view =
|
||||
texture.mip_views.empty()
|
||||
? (element == 0u ? texture.image_view : vk::ImageView {})
|
||||
: (element < texture.mip_views.size() ? texture.mip_views[element] : vk::ImageView {});
|
||||
EXIT_IF(!texture.image_id || view == nullptr || texture.layout == vk::ImageLayout::eUndefined);
|
||||
return {nullptr, view, texture.layout};
|
||||
}
|
||||
|
||||
static const char* ShaderStageResourceName(ShaderType stage) {
|
||||
switch (stage) {
|
||||
case ShaderType::Vertex: return "Vertex";
|
||||
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::Pixel: return vk::ShaderStageFlagBits::eFragment;
|
||||
case ShaderType::Compute: return vk::ShaderStageFlagBits::eCompute;
|
||||
default: EXIT("unknown native shader stage\n");
|
||||
}
|
||||
}
|
||||
|
||||
static void BindNullStorageBuffer(RenderContext& context, BufferView& dst) {
|
||||
auto owner = context.GetBufferCache().ObtainNullBuffer();
|
||||
dst.buffer = owner->Handle();
|
||||
dst.owner = std::move(owner);
|
||||
dst.buffer = context.GetBufferCache().GetBuffer(NULL_BUFFER_ID).Handle();
|
||||
dst.offset = 0;
|
||||
dst.range = 16;
|
||||
}
|
||||
@@ -79,10 +167,11 @@ static bool IsMultisampledTexture(Prospero::ImageType type) {
|
||||
type == Prospero::ImageType::kColor2DMsaaArray;
|
||||
}
|
||||
|
||||
static BufferView NativeStorageBuffer(RenderContext& context, CommandBuffer& command_buffer,
|
||||
static BufferView NativeStorageBuffer(RenderContext& context,
|
||||
const ShaderBufferResource& descriptor,
|
||||
const ShaderRecompiler::IR::BufferResource& resource,
|
||||
uint32_t& buffer_offset) {
|
||||
ShaderType stage, uint32_t slot, uint32_t& buffer_offset,
|
||||
BufferId id) {
|
||||
BufferView result;
|
||||
buffer_offset = 0;
|
||||
|
||||
@@ -104,60 +193,26 @@ static BufferView NativeStorageBuffer(RenderContext& context, CommandBuffer& com
|
||||
size > graphics.GetPhysicalDeviceProperties().limits.maxStorageBufferRange) {
|
||||
EXIT("storage buffer range or device alignment is unsupported\n");
|
||||
}
|
||||
auto binding = context.GetBufferCache().ObtainBuffer(
|
||||
command_buffer, address, size, resource.written, resource.read, resource.formatted);
|
||||
const auto aligned_offset = binding.offset - binding.offset % alignment;
|
||||
const auto adjustment = binding.offset - aligned_offset;
|
||||
auto [buffer, offset] = context.GetBufferCache().ObtainBuffer(address, size, resource.written,
|
||||
resource.formatted, id);
|
||||
const auto aligned_offset = offset - offset % alignment;
|
||||
const auto adjustment = offset - aligned_offset;
|
||||
const auto max_range = graphics.GetPhysicalDeviceProperties().limits.maxStorageBufferRange;
|
||||
if (adjustment % sizeof(uint32_t) != 0 || adjustment >= 256 || size > max_range - adjustment) {
|
||||
EXIT("storage buffer offset adjustment is unsupported\n");
|
||||
}
|
||||
buffer_offset = static_cast<uint32_t>(adjustment);
|
||||
result.owner = std::move(binding.owner);
|
||||
result.buffer = binding.buffer;
|
||||
result.buffer = buffer->Handle();
|
||||
result.offset = aligned_offset;
|
||||
result.range = static_cast<vk::DeviceSize>(size + adjustment);
|
||||
return result;
|
||||
}
|
||||
|
||||
static BufferView
|
||||
NativeAddressBuffer(RenderContext& context, CommandBuffer& command_buffer,
|
||||
const ShaderRecompiler::IR::AddressResource& resource,
|
||||
const ShaderRecompiler::IR::ResourceSnapshot::Address& address,
|
||||
uint32_t& address_offset) {
|
||||
BufferView result;
|
||||
address_offset = 0;
|
||||
if (address.binding_base == 0) {
|
||||
BindNullStorageBuffer(context, result);
|
||||
return result;
|
||||
if (resource.formatted && resource.written) {
|
||||
context.GetTextureCache().InvalidateMemoryFromGPU(address, size);
|
||||
}
|
||||
if (resource.written) {
|
||||
EXIT("writable address resources are unsupported\n");
|
||||
}
|
||||
const auto limit =
|
||||
resource.kind == ShaderRecompiler::IR::ResourceKind::Flat
|
||||
? ShaderRecompiler::IR::FlatAddressWindowSize
|
||||
: static_cast<uint64_t>(
|
||||
context.GetGraphics().GetPhysicalDeviceProperties().limits.maxStorageBufferRange);
|
||||
uint64_t size = 0;
|
||||
const auto access = HostMemoryAccess::Mapped;
|
||||
if (!HostMemoryQueryRange(address.binding_base, limit, access, size)) {
|
||||
EXIT("address resource is not host-accessible: base=0x%016" PRIx64 "\n",
|
||||
address.binding_base);
|
||||
}
|
||||
const auto& graphics = context.GetGraphics();
|
||||
const auto alignment = graphics.StorageMinAlignment();
|
||||
auto binding =
|
||||
context.GetBufferCache().ObtainBuffer(command_buffer, address.binding_base, size);
|
||||
const auto aligned_offset = binding.offset - binding.offset % alignment;
|
||||
const auto adjustment = binding.offset - aligned_offset;
|
||||
const auto max_range = graphics.GetPhysicalDeviceProperties().limits.maxStorageBufferRange;
|
||||
const auto visible_size = std::min(size, static_cast<uint64_t>(max_range - adjustment));
|
||||
address_offset = static_cast<uint32_t>(adjustment);
|
||||
result.owner = std::move(binding.owner);
|
||||
result.buffer = binding.buffer;
|
||||
result.offset = aligned_offset;
|
||||
result.range = static_cast<vk::DeviceSize>(visible_size + adjustment);
|
||||
SetVulkanObjectNameF(
|
||||
graphics.device, result.buffer,
|
||||
"Kyty.{}.StorageBuffer[slot={} guest=0x{:016x} size=0x{:x} access={} formatted={}]",
|
||||
ShaderStageResourceName(stage), slot, address, size,
|
||||
resource.written ? (resource.read ? "ReadWrite" : "Write") : "Read", resource.formatted);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -458,10 +513,9 @@ void ValidateStorageTexture(const ShaderRecompiler::IR::ImageResource& resource,
|
||||
const bool raw_sint_storage = format == Prospero::BufferFormat::k32SInt && uint_resource &&
|
||||
resource.written && !resource.read && !resource.atomic;
|
||||
const bool format_ok =
|
||||
raw_sint_storage ||
|
||||
(Prospero::IsSampledTextureFormat(format) &&
|
||||
uint_resource == Prospero::IsUintTextureFormat(format) &&
|
||||
(!resource.atomic || format == Prospero::BufferFormat::k32UInt));
|
||||
raw_sint_storage || (Prospero::IsSampledTextureFormat(format) &&
|
||||
uint_resource == Prospero::IsUintTextureFormat(format) &&
|
||||
(!resource.atomic || format == Prospero::BufferFormat::k32UInt));
|
||||
if (resource_ok && descriptor_ok && encoding_ok && format_ok && size != 0) {
|
||||
return;
|
||||
}
|
||||
@@ -568,8 +622,8 @@ static void PopulateTextureMipLayout(ImageInfo& info) {
|
||||
|
||||
static ImageViewInfo TextureViewInfo(const ShaderRecompiler::IR::ImageResource& resource,
|
||||
const ShaderTextureResource& descriptor, vk::Format format,
|
||||
bool shader_conversion, bool storage,
|
||||
uint32_t view_levels, uint32_t image_layers) {
|
||||
bool shader_conversion, bool storage, uint32_t view_levels,
|
||||
uint32_t image_layers) {
|
||||
ImageViewInfo view {};
|
||||
view.format = format;
|
||||
view.aspect = vk::ImageAspectFlagBits::eColor;
|
||||
@@ -624,9 +678,8 @@ static ImageViewInfo TextureViewInfo(const ShaderRecompiler::IR::ImageResource&
|
||||
return view;
|
||||
}
|
||||
|
||||
DescriptorCache::TextureBinding
|
||||
RenderExecutor::ResolveTexture(const ShaderRecompiler::IR::ImageResource& resource,
|
||||
const ShaderRecompiler::IR::DescriptorValue& value) {
|
||||
TextureBinding RenderExecutor::ResolveTexture(const ShaderRecompiler::IR::ImageResource& resource,
|
||||
const ShaderRecompiler::IR::DescriptorValue& value) {
|
||||
ShaderTextureResource descriptor;
|
||||
CopyNativeDescriptor(value, descriptor.fields);
|
||||
const bool storage = resource.written;
|
||||
@@ -684,10 +737,9 @@ RenderExecutor::ResolveTexture(const ShaderRecompiler::IR::ImageResource& reso
|
||||
const bool shader_conversion =
|
||||
surface_format.conversion_format != Prospero::BufferFormat::kInvalid;
|
||||
const bool sampled_numeric_class =
|
||||
storage ||
|
||||
(Prospero::IsSampledTextureFormat(format) &&
|
||||
(resource.kind == ShaderRecompiler::IR::ResourceKind::ImageUint) ==
|
||||
Prospero::IsUintTextureFormat(format));
|
||||
storage || (Prospero::IsSampledTextureFormat(format) &&
|
||||
(resource.kind == ShaderRecompiler::IR::ResourceKind::ImageUint) ==
|
||||
Prospero::IsUintTextureFormat(format));
|
||||
if (!storage &&
|
||||
(resource.kind == ShaderRecompiler::IR::ResourceKind::Image ||
|
||||
resource.kind == ShaderRecompiler::IR::ResourceKind::ImageUint) &&
|
||||
@@ -752,7 +804,7 @@ RenderExecutor::ResolveTexture(const ShaderRecompiler::IR::ImageResource& reso
|
||||
view_levels, desc.info.resources.layers);
|
||||
desc.type = storage ? TextureCache::BindingType::Storage : TextureCache::BindingType::Texture;
|
||||
|
||||
auto id = texture_cache.FindImage(desc, shader_conversion);
|
||||
auto id = texture_cache.FindImage(desc, shader_conversion);
|
||||
auto* image = &texture_cache.GetImage(id);
|
||||
const bool stencil_association = static_cast<bool>(image->depth_id);
|
||||
if (stencil_association) {
|
||||
@@ -794,22 +846,19 @@ static vk::Sampler NativeSampler(RenderContext& context,
|
||||
return context.GetSamplerCache().GetSampler(descriptor);
|
||||
}
|
||||
|
||||
static BufferView NativeUpload(RenderContext& context, CommandBuffer& command_buffer,
|
||||
std::span<const uint32_t> data) {
|
||||
static BufferView NativeUpload(RenderContext& context, std::span<const uint32_t> data) {
|
||||
EXIT_IF(data.empty());
|
||||
EXIT_IF(command_buffer.IsInvalid() || command_buffer.IsExecute());
|
||||
auto binding = context.GetBufferCache().UploadTransient(data.data(), data.size_bytes(), 256);
|
||||
return {.owner = std::move(binding.owner),
|
||||
.buffer = binding.buffer,
|
||||
.offset = binding.offset,
|
||||
.range = data.size_bytes()};
|
||||
auto& command_buffer = context.GetCommandScheduler().Current();
|
||||
EXIT_IF(command_buffer.IsInvalid());
|
||||
auto& buffer = context.GetBufferCache().GetUtilityBuffer(MemoryUsage::Stream);
|
||||
const auto offset = buffer.Copy(data.data(), data.size_bytes(), 256);
|
||||
return {.buffer = buffer.Handle(), .offset = offset, .range = data.size_bytes()};
|
||||
}
|
||||
|
||||
void RenderExecutor::TrackImageBinding(ImageId id) {
|
||||
auto image = m_context.GetTextureCache().ResolveOwner(id);
|
||||
EXIT_IF(image == nullptr);
|
||||
if (std::ranges::find(m_bound_images, image) == m_bound_images.end()) {
|
||||
m_bound_images.push_back(std::move(image));
|
||||
EXIT_IF(m_context.GetTextureCache().m_slot_images.try_get(id) == nullptr);
|
||||
if (std::ranges::find(m_bound_images, id) == m_bound_images.end()) {
|
||||
m_bound_images.push_back(id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -833,31 +882,28 @@ void RenderExecutor::BindRenderTarget(ImageId id) {
|
||||
}
|
||||
|
||||
void RenderExecutor::ResetBindings() {
|
||||
for (const auto& image: m_bound_images) {
|
||||
image->binding = {};
|
||||
for (const auto id: m_bound_images) {
|
||||
if (auto* image = m_context.GetTextureCache().m_slot_images.try_get(id); image != nullptr) {
|
||||
image->binding = {};
|
||||
}
|
||||
}
|
||||
m_bound_images.clear();
|
||||
}
|
||||
|
||||
DescriptorCache::PreparedBindings RenderExecutor::PrepareBindings(CommandBuffer& buffer,
|
||||
const ShaderStageRuntime& runtime,
|
||||
vk::ShaderStageFlags shader_stage,
|
||||
DescriptorCache::Stage stage) {
|
||||
PreparedBindings RenderExecutor::PrepareBindings(const ShaderStageRuntime& runtime) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
EXIT_IF(!runtime);
|
||||
const auto& program = *runtime.program;
|
||||
const auto& snapshot = *runtime.resources;
|
||||
std::string error;
|
||||
if (!ShaderRecompiler::IR::ValidateResourceSpecialization(program, snapshot, &error)) {
|
||||
EXIT("invalid native shader runtime snapshot: %s\n", error.c_str());
|
||||
if (!ShaderRecompiler::IR::ValidateResourceSpecialization(program, snapshot)) {
|
||||
EXIT("invalid native shader runtime snapshot: hash=0x%016" PRIx64 " stage=%u\n",
|
||||
program.shader_hash, static_cast<unsigned>(program.stage));
|
||||
}
|
||||
|
||||
DescriptorCache::PreparedBindings prepared;
|
||||
prepared.program = runtime.program;
|
||||
prepared.snapshot = runtime.resources;
|
||||
prepared.shader_stage = shader_stage;
|
||||
prepared.stage = stage;
|
||||
auto& descriptors = prepared.resources;
|
||||
PreparedBindings prepared;
|
||||
prepared.program = runtime.program.get();
|
||||
prepared.snapshot = runtime.resources.get();
|
||||
auto& descriptors = prepared.resources;
|
||||
descriptors.buffers.reserve(program.info.buffers.size());
|
||||
descriptors.images.reserve(program.info.images.size());
|
||||
for (uint32_t i = 0; i < program.info.images.size(); i++) {
|
||||
@@ -882,19 +928,46 @@ DescriptorCache::PreparedBindings RenderExecutor::PrepareBindings(CommandBuffer&
|
||||
prepared.user_data.resize(program.bindings.ShaderDataDwords());
|
||||
if (ShaderRecompiler::IR::FindBinding(
|
||||
program.bindings, ShaderRecompiler::IR::DescriptorBindingKind::Gds) != nullptr) {
|
||||
descriptors.gds.buffer = m_context.GetBufferCache().GetGdsBuffer().Handle();
|
||||
descriptors.gds.buffer = m_context.GetBufferCache().GetGdsBuffer()->Handle();
|
||||
}
|
||||
return prepared;
|
||||
}
|
||||
|
||||
void RenderExecutor::RebindBuffers(CommandBuffer& buffer,
|
||||
DescriptorCache::PreparedBindings& prepared) {
|
||||
void RenderExecutor::FindBuffers(PreparedBindings& prepared) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
EXIT_IF(prepared.program == nullptr || prepared.snapshot == nullptr);
|
||||
const auto& program = *prepared.program;
|
||||
const auto& snapshot = *prepared.snapshot;
|
||||
auto& cache = m_context.GetBufferCache();
|
||||
|
||||
prepared.buffer_ids.clear();
|
||||
prepared.buffer_ids.reserve(program.info.buffers.size());
|
||||
for (uint32_t i = 0; i < program.info.buffers.size(); i++) {
|
||||
ShaderBufferResource descriptor;
|
||||
CopyNativeDescriptor(snapshot.buffers[i], descriptor.fields);
|
||||
const auto address = descriptor.Base48();
|
||||
const auto stride = descriptor.Stride();
|
||||
const auto records = descriptor.NumRecords();
|
||||
EXIT_IF(stride != 0 && records > UINT64_MAX / stride);
|
||||
const auto requested_size = stride != 0 ? static_cast<uint64_t>(stride) * records : records;
|
||||
if (address == 0 || requested_size == 0) {
|
||||
prepared.buffer_ids.emplace_back();
|
||||
continue;
|
||||
}
|
||||
const auto size = Libs::LibKernel::Memory::ClampRangeSize(address, requested_size);
|
||||
prepared.buffer_ids.push_back(cache.FindBuffer(address, size));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void RenderExecutor::RebindBuffers(PreparedBindings& prepared) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
EXIT_IF(prepared.program == nullptr || prepared.snapshot == nullptr);
|
||||
const auto& program = *prepared.program;
|
||||
const auto& snapshot = *prepared.snapshot;
|
||||
auto& resources = prepared.resources;
|
||||
const auto& layout = program.bindings;
|
||||
EXIT_IF(prepared.buffer_ids.size() != program.info.buffers.size());
|
||||
|
||||
resources.buffers.clear();
|
||||
resources.buffers.reserve(program.info.buffers.size());
|
||||
@@ -909,23 +982,21 @@ void RenderExecutor::RebindBuffers(CommandBuffer& buffer,
|
||||
ShaderBufferResource descriptor;
|
||||
CopyNativeDescriptor(snapshot.buffers[i], descriptor.fields);
|
||||
uint32_t buffer_offset = 0;
|
||||
resources.buffers.push_back(NativeStorageBuffer(m_context, buffer, descriptor,
|
||||
program.info.buffers[i], buffer_offset));
|
||||
resources.buffers.push_back(NativeStorageBuffer(m_context, descriptor,
|
||||
program.info.buffers[i], program.stage, i,
|
||||
buffer_offset, prepared.buffer_ids[i]));
|
||||
pack_memory_offset(i, buffer_offset);
|
||||
}
|
||||
resources.addresses.clear();
|
||||
resources.addresses.reserve(program.info.addresses.size());
|
||||
for (uint32_t i = 0; i < program.info.addresses.size(); i++) {
|
||||
uint32_t address_offset = 0;
|
||||
resources.addresses.push_back(NativeAddressBuffer(
|
||||
m_context, buffer, program.info.addresses[i], snapshot.addresses[i], address_offset));
|
||||
pack_memory_offset(static_cast<uint32_t>(program.info.buffers.size()) + i,
|
||||
address_offset);
|
||||
if (!prepared.flattened_srt.empty()) {
|
||||
resources.flattened_srt = NativeUpload(m_context, prepared.flattened_srt);
|
||||
}
|
||||
if (ShaderRecompiler::IR::FindBinding(
|
||||
program.bindings, ShaderRecompiler::IR::DescriptorBindingKind::UserData) != nullptr) {
|
||||
resources.user_data = NativeUpload(m_context, prepared.user_data);
|
||||
}
|
||||
}
|
||||
|
||||
void RenderExecutor::RebindImages(CommandBuffer& buffer,
|
||||
DescriptorCache::PreparedBindings& prepared) {
|
||||
void RenderExecutor::RebindImages(PreparedBindings& prepared) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
EXIT_IF(prepared.program == nullptr || prepared.snapshot == nullptr);
|
||||
const auto& program = *prepared.program;
|
||||
@@ -934,7 +1005,7 @@ void RenderExecutor::RebindImages(CommandBuffer& buffer,
|
||||
EXIT_IF(images.size() != program.info.images.size());
|
||||
auto& texture_cache = m_context.GetTextureCache();
|
||||
for (uint32_t i = 0; i < program.info.images.size(); i++) {
|
||||
const auto old_image = texture_cache.ResolveOwner(images[i].image_id);
|
||||
const auto old_image = texture_cache.m_slot_images.try_get(images[i].image_id);
|
||||
if (old_image == nullptr || (!old_image->registered && !old_image->info.data.Empty()) ||
|
||||
old_image->binding.needs_rebind) {
|
||||
if (old_image != nullptr) {
|
||||
@@ -944,6 +1015,8 @@ void RenderExecutor::RebindImages(CommandBuffer& buffer,
|
||||
BindImage(images[i].image_id,
|
||||
images[i].desc.type == TextureCache::BindingType::Storage);
|
||||
}
|
||||
}
|
||||
for (uint32_t i = 0; i < program.info.images.size(); i++) {
|
||||
auto& binding = images[i];
|
||||
binding.mip_views.clear();
|
||||
const auto& resource = program.info.images[i];
|
||||
@@ -973,121 +1046,221 @@ void RenderExecutor::RebindImages(CommandBuffer& buffer,
|
||||
}
|
||||
|
||||
RenderExecutor::GraphicsBindings
|
||||
RenderExecutor::PrepareGraphicsBindings(CommandBuffer& buffer, const ShaderStageRuntime& vertex,
|
||||
RenderExecutor::PrepareGraphicsBindings(const ShaderStageRuntime& vertex,
|
||||
const ShaderStageRuntime& pixel, bool pixel_active) {
|
||||
GraphicsBindings bindings {
|
||||
.vertex = PrepareBindings(buffer, vertex, vk::ShaderStageFlagBits::eVertex,
|
||||
DescriptorCache::Stage::Vertex),
|
||||
.vertex = PrepareBindings(vertex),
|
||||
};
|
||||
RebindBuffers(buffer, bindings.vertex);
|
||||
RebindImages(buffer, bindings.vertex);
|
||||
if (pixel_active) {
|
||||
bindings.pixel.emplace(PrepareBindings(buffer, pixel, vk::ShaderStageFlagBits::eFragment,
|
||||
DescriptorCache::Stage::Pixel));
|
||||
RebindBuffers(buffer, *bindings.pixel);
|
||||
RebindImages(buffer, *bindings.pixel);
|
||||
bindings.pixel.emplace(PrepareBindings(pixel));
|
||||
}
|
||||
FindBuffers(bindings.vertex);
|
||||
if (bindings.pixel) {
|
||||
FindBuffers(*bindings.pixel);
|
||||
}
|
||||
if (bindings.vertex.program->info.uses_dma ||
|
||||
(bindings.pixel && bindings.pixel->program->info.uses_dma)) {
|
||||
m_context.GetGpuResources().PrepareBda();
|
||||
}
|
||||
RebindBuffers(bindings.vertex);
|
||||
if (bindings.pixel) {
|
||||
RebindBuffers(*bindings.pixel);
|
||||
}
|
||||
RebindImages(bindings.vertex);
|
||||
if (bindings.pixel) {
|
||||
RebindImages(*bindings.pixel);
|
||||
}
|
||||
return bindings;
|
||||
}
|
||||
|
||||
static void RetainBindings(CommandBuffer& buffer, DescriptorCache::NativeDescriptors& resources) {
|
||||
if (resources.flattened_srt.owner != nullptr) {
|
||||
buffer.RetainResourceUntilFence(std::move(resources.flattened_srt.owner));
|
||||
}
|
||||
if (resources.user_data.owner != nullptr) {
|
||||
buffer.RetainResourceUntilFence(std::move(resources.user_data.owner));
|
||||
}
|
||||
for (auto& view: resources.buffers) {
|
||||
if (view.owner != nullptr) {
|
||||
buffer.RetainResourceUntilFence(std::move(view.owner));
|
||||
}
|
||||
}
|
||||
for (auto& view: resources.addresses) {
|
||||
if (view.owner != nullptr) {
|
||||
buffer.RetainResourceUntilFence(std::move(view.owner));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RenderExecutor::CommitBindings(CommandBuffer& buffer,
|
||||
vk::PipelineBindPoint pipeline_bind_point,
|
||||
vk::PipelineLayout layout,
|
||||
DescriptorCache::PreparedBindings& prepared) {
|
||||
const PipelineCache::Pipeline& pipeline,
|
||||
std::span<PreparedBindings* const> prepared_bindings) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
EXIT_IF(prepared.program == nullptr || prepared.stage == DescriptorCache::Stage::Unknown ||
|
||||
prepared.committed);
|
||||
const auto& program = *prepared.program;
|
||||
auto& descriptors = prepared.resources;
|
||||
if (!prepared.flattened_srt.empty()) {
|
||||
descriptors.flattened_srt = NativeUpload(m_context, buffer, prepared.flattened_srt);
|
||||
}
|
||||
if (ShaderRecompiler::IR::FindBinding(
|
||||
program.bindings, ShaderRecompiler::IR::DescriptorBindingKind::UserData) != nullptr) {
|
||||
descriptors.user_data = NativeUpload(m_context, buffer, prepared.user_data);
|
||||
}
|
||||
RetainBindings(buffer, descriptors);
|
||||
|
||||
auto vk_buffer = buffer.Handle();
|
||||
const auto shader_stages = ShaderPipelineStages(prepared.shader_stage);
|
||||
if (descriptors.gds.buffer != nullptr) {
|
||||
buffer.EndRendering();
|
||||
const auto barrier = MakeGdsDependency(descriptors.gds.buffer);
|
||||
vk_buffer.pipelineBarrier(
|
||||
vk::PipelineStageFlagBits::eHost | vk::PipelineStageFlagBits::eTransfer |
|
||||
vk::PipelineStageFlagBits::eAllGraphics | vk::PipelineStageFlagBits::eComputeShader,
|
||||
shader_stages, vk::DependencyFlags {}, 0, nullptr, 1, &barrier, 0, nullptr);
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < program.info.images.size(); i++) {
|
||||
auto& image = m_context.GetTextureCache().GetImage(descriptors.images[i].image_id);
|
||||
auto& binding = descriptors.images[i];
|
||||
const auto& view = binding.desc.view_info;
|
||||
const ImageSubresourceRange range {view.base_level, view.level_count, view.base_layer,
|
||||
view.layer_count};
|
||||
const bool storage = binding.desc.type == TextureCache::BindingType::Storage;
|
||||
if (image.info.data.Empty()) {
|
||||
image.Transit(vk::ImageLayout::eGeneral,
|
||||
storage
|
||||
? vk::AccessFlagBits2::eShaderRead | vk::AccessFlagBits2::eShaderWrite
|
||||
: vk::AccessFlagBits2::eShaderRead,
|
||||
range, vk_buffer);
|
||||
} else if ((image.binding.force_general || image.binding.is_target) &&
|
||||
!image.info.IsDepth()) {
|
||||
const vk::AccessFlags2 storage_access = image.binding.shader_write
|
||||
? vk::AccessFlagBits2::eShaderWrite
|
||||
: vk::AccessFlags2 {};
|
||||
image.Transit(vk::ImageLayout::eGeneral,
|
||||
vk::AccessFlagBits2::eShaderRead | storage_access |
|
||||
vk::AccessFlagBits2::eColorAttachmentRead |
|
||||
vk::AccessFlagBits2::eColorAttachmentWrite,
|
||||
{}, vk_buffer);
|
||||
} else if (storage) {
|
||||
image.Transit(vk::ImageLayout::eGeneral,
|
||||
vk::AccessFlagBits2::eShaderRead | vk::AccessFlagBits2::eShaderWrite,
|
||||
range, vk_buffer);
|
||||
} else {
|
||||
image.Transit(image.info.IsDepth() ? vk::ImageLayout::eDepthStencilReadOnlyOptimal
|
||||
: vk::ImageLayout::eShaderReadOnlyOptimal,
|
||||
vk::AccessFlagBits2::eShaderRead, range, vk_buffer);
|
||||
auto vk_buffer = buffer.Handle();
|
||||
size_t descriptor_count = 0;
|
||||
size_t write_count = 0;
|
||||
constexpr auto GraphicsStages =
|
||||
vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment;
|
||||
const auto push_constant_stages = pipeline_bind_point == vk::PipelineBindPoint::eGraphics
|
||||
? vk::ShaderStageFlags {GraphicsStages}
|
||||
: vk::ShaderStageFlags {vk::ShaderStageFlagBits::eCompute};
|
||||
for (const auto* prepared: prepared_bindings) {
|
||||
EXIT_IF(prepared == nullptr || prepared->program == nullptr ||
|
||||
prepared->snapshot == nullptr || prepared->committed);
|
||||
write_count += prepared->program->bindings.descriptors.size();
|
||||
for (const auto& binding: prepared->program->bindings.descriptors) {
|
||||
descriptor_count += NativeDescriptorCount(binding);
|
||||
}
|
||||
binding.layout = image.backing.state.layout;
|
||||
const auto shader_stage = NativeShaderStage(prepared->program->stage);
|
||||
EXIT_IF((pipeline_bind_point == vk::PipelineBindPoint::eGraphics &&
|
||||
(shader_stage & GraphicsStages) == vk::ShaderStageFlags {}) ||
|
||||
(pipeline_bind_point == vk::PipelineBindPoint::eCompute &&
|
||||
shader_stage != vk::ShaderStageFlagBits::eCompute));
|
||||
}
|
||||
m_descriptor_buffers.clear();
|
||||
m_descriptor_images.clear();
|
||||
m_descriptor_writes.clear();
|
||||
m_push_constants.fill(0);
|
||||
m_descriptor_buffers.reserve(descriptor_count);
|
||||
m_descriptor_images.reserve(descriptor_count);
|
||||
m_descriptor_writes.reserve(write_count);
|
||||
|
||||
if (!program.bindings.descriptors.empty()) {
|
||||
auto& set =
|
||||
m_context.GetDescriptorCache().GetDescriptor(prepared.stage, program, descriptors);
|
||||
vk_buffer.bindDescriptorSets(pipeline_bind_point, layout, program.bindings.descriptor_set,
|
||||
1, &set.set, 0, nullptr);
|
||||
buffer.RecycleDescriptorAfterFence(set);
|
||||
for (auto* prepared: prepared_bindings) {
|
||||
const auto& program = *prepared->program;
|
||||
auto& descriptors = prepared->resources;
|
||||
const auto shader_stage = NativeShaderStage(program.stage);
|
||||
const auto shader_stages = ShaderPipelineStages(shader_stage);
|
||||
if (descriptors.gds.buffer != nullptr) {
|
||||
buffer.EndRendering();
|
||||
const auto barrier = MakeGdsDependency(descriptors.gds.buffer);
|
||||
vk_buffer.pipelineBarrier(
|
||||
vk::PipelineStageFlagBits::eHost | vk::PipelineStageFlagBits::eTransfer |
|
||||
vk::PipelineStageFlagBits::eAllGraphics |
|
||||
vk::PipelineStageFlagBits::eComputeShader,
|
||||
shader_stages, vk::DependencyFlags {}, 0, nullptr, 1, &barrier, 0, nullptr);
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < program.info.images.size(); i++) {
|
||||
auto& image = m_context.GetTextureCache().GetImage(descriptors.images[i].image_id);
|
||||
auto& binding = descriptors.images[i];
|
||||
const auto& view = binding.desc.view_info;
|
||||
const ImageSubresourceRange range {view.base_level, view.level_count, view.base_layer,
|
||||
view.layer_count};
|
||||
const bool storage = binding.desc.type == TextureCache::BindingType::Storage;
|
||||
if (image.info.data.Empty()) {
|
||||
image.Transit(vk::ImageLayout::eGeneral,
|
||||
storage ? vk::AccessFlagBits2::eShaderRead |
|
||||
vk::AccessFlagBits2::eShaderWrite
|
||||
: vk::AccessFlagBits2::eShaderRead,
|
||||
range, vk_buffer);
|
||||
} else if ((image.binding.force_general || image.binding.is_target) &&
|
||||
!image.info.IsDepth()) {
|
||||
const vk::AccessFlags2 storage_access = image.binding.shader_write
|
||||
? vk::AccessFlagBits2::eShaderWrite
|
||||
: vk::AccessFlags2 {};
|
||||
image.Transit(vk::ImageLayout::eGeneral,
|
||||
vk::AccessFlagBits2::eShaderRead | storage_access |
|
||||
vk::AccessFlagBits2::eColorAttachmentRead |
|
||||
vk::AccessFlagBits2::eColorAttachmentWrite,
|
||||
{}, vk_buffer);
|
||||
} else if (storage) {
|
||||
image.Transit(vk::ImageLayout::eGeneral,
|
||||
vk::AccessFlagBits2::eShaderRead | vk::AccessFlagBits2::eShaderWrite,
|
||||
range, vk_buffer);
|
||||
} else {
|
||||
image.Transit(image.info.IsDepth() ? vk::ImageLayout::eDepthStencilReadOnlyOptimal
|
||||
: vk::ImageLayout::eShaderReadOnlyOptimal,
|
||||
vk::AccessFlagBits2::eShaderRead, range, vk_buffer);
|
||||
}
|
||||
binding.layout = image.backing.state.layout;
|
||||
}
|
||||
|
||||
m_image_occurrences.assign(descriptors.images.size(), 0);
|
||||
for (const auto& binding: program.bindings.descriptors) {
|
||||
vk::WriteDescriptorSet write {};
|
||||
write.sType = vk::StructureType::eWriteDescriptorSet;
|
||||
write.dstBinding = ShaderRecompiler::IR::NativeBinding(program.stage, binding.kind);
|
||||
write.descriptorType = NativeDescriptorType(binding.kind);
|
||||
write.descriptorCount = NativeDescriptorCount(binding);
|
||||
const auto buffer_start = m_descriptor_buffers.size();
|
||||
const auto image_start = m_descriptor_images.size();
|
||||
switch (binding.kind) {
|
||||
case BindingKind::Buffers:
|
||||
for (const auto resource: binding.resources) {
|
||||
const auto& view = descriptors.buffers.at(resource);
|
||||
EXIT_IF(view.buffer == nullptr);
|
||||
m_descriptor_buffers.emplace_back(view.buffer, view.offset, view.range);
|
||||
}
|
||||
break;
|
||||
case BindingKind::BdaPagetable:
|
||||
case BindingKind::FaultBuffer: {
|
||||
auto& cache = m_context.GetBufferCache();
|
||||
const auto* bda_buffer = binding.kind == BindingKind::BdaPagetable
|
||||
? cache.GetBdaPageTableBuffer()
|
||||
: cache.GetFaultBuffer();
|
||||
m_descriptor_buffers.emplace_back(bda_buffer->Handle(), 0, bda_buffer->Size());
|
||||
break;
|
||||
}
|
||||
case BindingKind::FlattenedSrt:
|
||||
case BindingKind::UserData:
|
||||
case BindingKind::Gds: {
|
||||
const auto& view =
|
||||
binding.kind == BindingKind::FlattenedSrt ? descriptors.flattened_srt
|
||||
: binding.kind == BindingKind::UserData ? descriptors.user_data
|
||||
: descriptors.gds;
|
||||
EXIT_IF(view.buffer == nullptr);
|
||||
m_descriptor_buffers.emplace_back(view.buffer, view.offset, view.range);
|
||||
break;
|
||||
}
|
||||
case BindingKind::Samplers:
|
||||
for (const auto resource: binding.resources) {
|
||||
const auto sampler = descriptors.samplers.at(resource);
|
||||
EXIT_IF(sampler == nullptr);
|
||||
m_descriptor_images.emplace_back(sampler, nullptr,
|
||||
vk::ImageLayout::eUndefined);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
for (const auto resource: binding.resources) {
|
||||
m_descriptor_images.push_back(MakeImageInfo(
|
||||
descriptors.images.at(resource), m_image_occurrences.at(resource)++));
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (m_descriptor_buffers.size() != buffer_start) {
|
||||
write.pBufferInfo = m_descriptor_buffers.data() + buffer_start;
|
||||
}
|
||||
if (m_descriptor_images.size() != image_start) {
|
||||
write.pImageInfo = m_descriptor_images.data() + image_start;
|
||||
}
|
||||
m_descriptor_writes.push_back(write);
|
||||
}
|
||||
for (uint32_t i = 0; i < descriptors.images.size(); i++) {
|
||||
const auto expected =
|
||||
descriptors.images[i].mip_views.empty()
|
||||
? 1u
|
||||
: static_cast<uint32_t>(descriptors.images[i].mip_views.size());
|
||||
EXIT_IF(m_image_occurrences[i] != expected);
|
||||
}
|
||||
|
||||
if (program.bindings.push_constant_size != 0) {
|
||||
const auto offset = program.bindings.push_constant_offset;
|
||||
EXIT_IF(offset % sizeof(uint32_t) != 0 ||
|
||||
program.bindings.push_constant_size !=
|
||||
prepared->user_data.size() * sizeof(uint32_t) ||
|
||||
offset + program.bindings.push_constant_size >
|
||||
ShaderRecompiler::IR::NativePushConstantSize);
|
||||
std::copy(prepared->user_data.begin(), prepared->user_data.end(),
|
||||
m_push_constants.begin() + offset / sizeof(uint32_t));
|
||||
}
|
||||
}
|
||||
if (program.bindings.push_constant_size != 0) {
|
||||
EXIT_IF(program.bindings.push_constant_size !=
|
||||
prepared.user_data.size() * sizeof(uint32_t));
|
||||
vk_buffer.pushConstants(layout, prepared.shader_stage,
|
||||
program.bindings.push_constant_offset,
|
||||
program.bindings.push_constant_size, prepared.user_data.data());
|
||||
vk_buffer.pushConstants(pipeline.pipeline_layout, push_constant_stages, 0,
|
||||
ShaderRecompiler::IR::NativePushConstantSize,
|
||||
m_push_constants.data());
|
||||
|
||||
if (!m_descriptor_writes.empty()) {
|
||||
EXIT_IF(pipeline.descriptor_set_layout == nullptr);
|
||||
if (pipeline.uses_push_descriptors) {
|
||||
vk_buffer.pushDescriptorSetKHR(pipeline_bind_point, pipeline.pipeline_layout, 0,
|
||||
static_cast<uint32_t>(m_descriptor_writes.size()),
|
||||
m_descriptor_writes.data());
|
||||
} else {
|
||||
const auto set = m_context.GetDescriptorHeap().Commit(pipeline.descriptor_set_layout);
|
||||
for (auto& write: m_descriptor_writes) {
|
||||
write.dstSet = set;
|
||||
}
|
||||
m_context.GetGraphics().device.updateDescriptorSets(
|
||||
static_cast<uint32_t>(m_descriptor_writes.size()), m_descriptor_writes.data(), 0,
|
||||
nullptr);
|
||||
vk_buffer.bindDescriptorSets(pipeline_bind_point, pipeline.pipeline_layout, 0, 1, &set,
|
||||
0, nullptr);
|
||||
}
|
||||
}
|
||||
for (auto* prepared: prepared_bindings) {
|
||||
prepared->committed = true;
|
||||
}
|
||||
prepared.committed = true;
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
@@ -2,6 +2,8 @@
|
||||
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_DESCRIPTORS_H_
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "graphics/host_gpu/renderer/cache/bufferCache.h"
|
||||
#include "graphics/host_gpu/renderer/cache/textureCache.h"
|
||||
#include "graphics/host_gpu/renderer/image/image.h"
|
||||
#include "graphics/host_gpu/vulkanCommon.h"
|
||||
#include "graphics/shader/recompiler/ir/ShaderIR.h"
|
||||
@@ -10,9 +12,54 @@
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
namespace ShaderRecompiler::IR {
|
||||
struct ResourceSnapshot;
|
||||
}
|
||||
|
||||
struct BufferView {
|
||||
vk::Buffer buffer = nullptr;
|
||||
vk::DeviceSize offset = 0;
|
||||
vk::DeviceSize range = VK_WHOLE_SIZE;
|
||||
};
|
||||
|
||||
struct TextureBinding {
|
||||
ImageId image_id;
|
||||
vk::ImageView image_view = nullptr;
|
||||
TextureCache::ImageDesc desc;
|
||||
vk::ImageLayout layout = vk::ImageLayout::eUndefined;
|
||||
std::vector<vk::ImageView> mip_views;
|
||||
};
|
||||
|
||||
struct NativeDescriptors {
|
||||
std::vector<BufferView> buffers;
|
||||
std::vector<TextureBinding> images;
|
||||
std::vector<vk::Sampler> samplers;
|
||||
BufferView gds;
|
||||
BufferView flattened_srt;
|
||||
BufferView user_data;
|
||||
};
|
||||
|
||||
struct PreparedBindings {
|
||||
const ShaderRecompiler::IR::Program* program = nullptr;
|
||||
const ShaderRecompiler::IR::ResourceSnapshot* snapshot = nullptr;
|
||||
NativeDescriptors resources;
|
||||
std::vector<BufferId> buffer_ids;
|
||||
std::vector<uint32_t> flattened_srt;
|
||||
std::vector<uint32_t> user_data;
|
||||
bool committed = false;
|
||||
};
|
||||
|
||||
[[nodiscard]] vk::DescriptorType
|
||||
NativeDescriptorType(ShaderRecompiler::IR::DescriptorBindingKind kind);
|
||||
[[nodiscard]] uint32_t
|
||||
NativeDescriptorCount(const ShaderRecompiler::IR::DescriptorBinding& binding);
|
||||
[[nodiscard]] vk::DescriptorImageInfo MakeImageInfo(const TextureBinding& texture,
|
||||
uint32_t element = 0);
|
||||
|
||||
template <typename T>
|
||||
[[nodiscard]] T DecodeNativeDescriptor(const ShaderRecompiler::IR::DescriptorValue& value) {
|
||||
static_assert(std::is_trivially_copyable_v<T>);
|
||||
@@ -34,11 +81,9 @@ ResolveTargetTextureView(const ShaderRecompiler::IR::ImageResource& resource,
|
||||
Prospero::ImageType type, uint32_t base_layer, uint32_t image_layers);
|
||||
|
||||
[[nodiscard]] bool IsSupportedDepthTargetDescriptor(const ShaderTextureResource& descriptor,
|
||||
const Image& image,
|
||||
bool r128 = false);
|
||||
const Image& image, bool r128 = false);
|
||||
[[nodiscard]] bool IsSupportedDepthTextureEncoding(const ShaderTextureResource& descriptor,
|
||||
const Image& image,
|
||||
bool r128 = false);
|
||||
const Image& image, bool r128 = false);
|
||||
[[nodiscard]] bool
|
||||
IsSupportedSampledVideoOutView(const ShaderRecompiler::IR::ImageResource& resource,
|
||||
const ShaderTextureResource& descriptor, const Image& image);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "graphics/host_gpu/renderer/pipeline/pipelineCache.h"
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/file.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/profiler.h"
|
||||
#include "graphics/guest_gpu/hardwareContext.h"
|
||||
@@ -10,16 +11,65 @@
|
||||
#include "graphics/host_gpu/renderer/image/imageView.h"
|
||||
#include "graphics/host_gpu/renderer/render.h"
|
||||
#include "graphics/host_gpu/renderer/renderContext.h"
|
||||
#include "graphics/shader/shaderCompiler.h"
|
||||
#include "kytyGitVersion.h"
|
||||
#include "loader/systemContent.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <cctype>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
#include <span>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <xxhash.h>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
namespace {
|
||||
|
||||
std::string DriverCacheSignature(const vk::PhysicalDeviceProperties& properties) {
|
||||
constexpr char hex[] = "0123456789abcdef";
|
||||
std::string uuid(VK_UUID_SIZE * 2, '0');
|
||||
for (size_t i = 0; i < VK_UUID_SIZE; i++) {
|
||||
uuid[i * 2] = hex[properties.pipelineCacheUUID[i] >> 4u];
|
||||
uuid[i * 2 + 1] = hex[properties.pipelineCacheUUID[i] & 0xfu];
|
||||
}
|
||||
return fmt::format("KytyPC1:{}:{:08x}:{:08x}:{:08x}:{}\n", KYTY_GIT_REVISION,
|
||||
properties.vendorID, properties.deviceID, properties.driverVersion, uuid);
|
||||
}
|
||||
|
||||
std::string PipelineCacheTitleId() {
|
||||
std::string title_id;
|
||||
if ((!Loader::SystemContentParamSfoGetString("TITLE_ID", &title_id) || title_id.empty()) &&
|
||||
(!Loader::SystemContentParamSfoGetString("CONTENT_ID", &title_id) || title_id.empty())) {
|
||||
return {};
|
||||
}
|
||||
if (!std::ranges::all_of(title_id, [](unsigned char c) {
|
||||
return std::isalnum(c) != 0 || c == '-' || c == '_';
|
||||
})) {
|
||||
return {};
|
||||
}
|
||||
return title_id;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
void NormalizeStaticParamsForDynamicState(PipelineStaticParameters& static_params) {
|
||||
static_params.viewport_scale[0] = 0.5f;
|
||||
static_params.viewport_scale[1] = 0.5f;
|
||||
@@ -36,16 +86,283 @@ void NormalizeStaticParamsForDynamicState(PipelineStaticParameters& static_param
|
||||
|
||||
} // namespace
|
||||
|
||||
struct PipelineCache::ProgramCache {
|
||||
struct SourceKey {
|
||||
ShaderType stage = ShaderType::Unknown;
|
||||
uint64_t hash = 0;
|
||||
|
||||
bool operator==(const SourceKey&) const = default;
|
||||
};
|
||||
|
||||
struct Permutation {
|
||||
std::vector<uint32_t> static_key;
|
||||
std::shared_ptr<const ShaderRecompiler::IR::Program> program;
|
||||
ShaderProgram handle;
|
||||
};
|
||||
|
||||
struct SourceKeyHash {
|
||||
std::size_t operator()(const SourceKey& key) const {
|
||||
std::size_t hash = static_cast<std::size_t>(key.stage);
|
||||
PipelineKeyHash::Mix(hash, static_cast<std::size_t>(key.hash));
|
||||
if constexpr (sizeof(std::size_t) < sizeof(uint64_t)) {
|
||||
PipelineKeyHash::Mix(hash, static_cast<std::size_t>(key.hash >> 32u));
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
};
|
||||
|
||||
static uint64_t MixId(uint64_t hash, uint64_t value) {
|
||||
return hash ^ (value + 0x9e3779b97f4a7c15ull + (hash << 6u) + (hash >> 2u));
|
||||
}
|
||||
|
||||
static constexpr std::size_t MaxStaticKeyWords =
|
||||
5 + ShaderVertexInputInfo::RES_MAX * 17;
|
||||
|
||||
template <typename InputInfo>
|
||||
ShaderProgram Get(const ShaderParams& params, InputInfo& input_info) {
|
||||
constexpr ShaderType stage = [] {
|
||||
if constexpr (std::is_same_v<InputInfo, ShaderVertexInputInfo>) {
|
||||
return ShaderType::Vertex;
|
||||
} else if constexpr (std::is_same_v<InputInfo, ShaderPixelInputInfo>) {
|
||||
return ShaderType::Pixel;
|
||||
} else {
|
||||
static_assert(std::is_same_v<InputInfo, ShaderComputeInputInfo>);
|
||||
return ShaderType::Compute;
|
||||
}
|
||||
}();
|
||||
|
||||
BuildStageStaticKey(input_info, key_scratch);
|
||||
auto& permutations = programs[{stage, params.hash}];
|
||||
for (const auto& permutation: permutations) {
|
||||
if (permutation.static_key == key_scratch &&
|
||||
MaterializeProgram(permutation.program, params, input_info)) {
|
||||
return permutation.handle;
|
||||
}
|
||||
}
|
||||
|
||||
const auto module = CompileProgram(device, params, input_info);
|
||||
EXIT_IF(module == nullptr || !input_info.stage);
|
||||
uint64_t id = MixId(MixId(params.hash, static_cast<uint64_t>(stage)), permutations.size());
|
||||
if (id == 0) {
|
||||
id = 1;
|
||||
}
|
||||
const ShaderProgram handle {.id = id, .module = module};
|
||||
permutations.push_back({key_scratch, input_info.stage.program, handle});
|
||||
|
||||
std::printf("Num compiled %u shaders\n", ++num_compiled);
|
||||
return handle;
|
||||
}
|
||||
|
||||
explicit ProgramCache(vk::Device device): device(device) {
|
||||
key_scratch.reserve(MaxStaticKeyWords);
|
||||
}
|
||||
|
||||
std::unordered_map<SourceKey, std::vector<Permutation>, SourceKeyHash> programs;
|
||||
std::vector<uint32_t> key_scratch;
|
||||
vk::Device device;
|
||||
uint32_t num_compiled = 0;
|
||||
};
|
||||
|
||||
PipelineCache::PipelineCache(GraphicContext& graphics)
|
||||
: m_graphics(graphics), m_program_cache(std::make_unique<ProgramCache>(graphics.device)) {
|
||||
EXIT_NOT_IMPLEMENTED(!Common::Thread::IsMainThread());
|
||||
InitializeDriverCache();
|
||||
}
|
||||
|
||||
PipelineCache::~PipelineCache() {
|
||||
Save();
|
||||
auto destroy = [this](const auto& pipelines) {
|
||||
for (const auto& [key, pipeline]: pipelines) {
|
||||
(void)key;
|
||||
m_graphics.device.destroyPipeline(pipeline->pipeline, nullptr);
|
||||
m_graphics.device.destroyPipelineLayout(pipeline->pipeline_layout, nullptr);
|
||||
m_graphics.device.destroyDescriptorSetLayout(pipeline->descriptor_set_layout, nullptr);
|
||||
}
|
||||
};
|
||||
destroy(m_graphics_pipelines);
|
||||
destroy(m_compute_pipelines);
|
||||
for (const auto& [key, permutations]: m_program_cache->programs) {
|
||||
(void)key;
|
||||
for (const auto& permutation: permutations) {
|
||||
m_graphics.device.destroyShaderModule(permutation.handle.module, nullptr);
|
||||
}
|
||||
}
|
||||
if (m_driver_cache != nullptr) {
|
||||
m_graphics.device.destroyPipelineCache(m_driver_cache, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void PipelineCache::InitializeDriverCache() {
|
||||
const auto title_id = PipelineCacheTitleId();
|
||||
if (title_id.empty()) {
|
||||
return;
|
||||
}
|
||||
if (KYTY_BUILD != KYTY_BUILD_RELEASE) {
|
||||
PipelineCacheLog("Vulkan pipeline cache: disabled (non-Release build)");
|
||||
return;
|
||||
}
|
||||
const std::string_view git_hash = KYTY_GIT_HASH;
|
||||
const std::string_view git_revision = KYTY_GIT_REVISION;
|
||||
if (git_hash == "unknown" || git_revision == "unknown") {
|
||||
PipelineCacheLog("Vulkan pipeline cache: disabled (unknown git revision)");
|
||||
return;
|
||||
}
|
||||
if (git_hash.ends_with("-dirty")) {
|
||||
PipelineCacheLog("Vulkan pipeline cache: disabled (dirty build)");
|
||||
return;
|
||||
}
|
||||
|
||||
m_driver_cache_path = std::filesystem::path("_PipelineCache") / (title_id + ".bin");
|
||||
const auto path = Common::PathToString(m_driver_cache_path);
|
||||
const bool cache_exists = Common::File::IsFileExisting(m_driver_cache_path);
|
||||
if (cache_exists) {
|
||||
PipelineCacheLog("Vulkan pipeline cache: loading {}", path);
|
||||
} else {
|
||||
PipelineCacheLog("Vulkan pipeline cache: initializing {}", path);
|
||||
}
|
||||
std::vector<uint8_t> initial_data;
|
||||
if (cache_exists) {
|
||||
Common::File file(m_driver_cache_path, Common::File::Mode::Read);
|
||||
const auto file_size = file.IsInvalid() ? 0 : file.Size();
|
||||
const auto signature = DriverCacheSignature(m_graphics.GetPhysicalDeviceProperties());
|
||||
if (file_size >= signature.size() + sizeof(uint64_t) &&
|
||||
file_size <= std::numeric_limits<uint32_t>::max()) {
|
||||
std::string cached_signature(signature.size(), '\0');
|
||||
uint64_t payload_hash = 0;
|
||||
initial_data.resize(file_size - signature.size() - sizeof(payload_hash));
|
||||
uint32_t signature_read = 0;
|
||||
uint32_t hash_read = 0;
|
||||
uint32_t payload_read = 0;
|
||||
file.Read(cached_signature.data(), static_cast<uint32_t>(cached_signature.size()),
|
||||
&signature_read);
|
||||
file.Read(&payload_hash, sizeof(payload_hash), &hash_read);
|
||||
file.Read(initial_data.data(), static_cast<uint32_t>(initial_data.size()), &payload_read);
|
||||
file.Close();
|
||||
if (signature_read != cached_signature.size() || hash_read != sizeof(payload_hash) ||
|
||||
payload_read != initial_data.size() || cached_signature != signature ||
|
||||
XXH3_64bits(initial_data.data(), initial_data.size()) != payload_hash) {
|
||||
initial_data.clear();
|
||||
PipelineCacheLog(
|
||||
"Vulkan pipeline cache: invalidating {} (driver, emulator, or data mismatch)",
|
||||
path);
|
||||
}
|
||||
} else {
|
||||
file.Close();
|
||||
PipelineCacheLog("Vulkan pipeline cache: invalidating {} (invalid file size)",
|
||||
path);
|
||||
}
|
||||
}
|
||||
|
||||
vk::PipelineCacheCreateInfo create {};
|
||||
create.sType = vk::StructureType::ePipelineCacheCreateInfo;
|
||||
create.initialDataSize = initial_data.size();
|
||||
create.pInitialData = initial_data.empty() ? nullptr : initial_data.data();
|
||||
auto result = m_graphics.device.createPipelineCache(&create, nullptr, &m_driver_cache);
|
||||
if (result != vk::Result::eSuccess && !initial_data.empty()) {
|
||||
PipelineCacheLog("Vulkan pipeline cache: driver rejected {} ({}); starting empty", path,
|
||||
VulkanToString(result));
|
||||
initial_data.clear();
|
||||
create.initialDataSize = 0;
|
||||
create.pInitialData = nullptr;
|
||||
result = m_graphics.device.createPipelineCache(&create, nullptr, &m_driver_cache);
|
||||
}
|
||||
if (result != vk::Result::eSuccess) {
|
||||
PipelineCacheLog("Vulkan pipeline cache: disabled ({})", VulkanToString(result));
|
||||
m_driver_cache = nullptr;
|
||||
return;
|
||||
}
|
||||
if (!initial_data.empty()) {
|
||||
PipelineCacheLog("Vulkan pipeline cache: loaded {} bytes from {}", initial_data.size(),
|
||||
path);
|
||||
} else {
|
||||
PipelineCacheLog("Vulkan pipeline cache: initialized empty");
|
||||
}
|
||||
}
|
||||
|
||||
void PipelineCache::Save() {
|
||||
Common::LockGuard lock(m_mutex);
|
||||
if (m_driver_cache == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
size_t size = 0;
|
||||
vk::Result result;
|
||||
std::vector<uint8_t> payload;
|
||||
for (uint32_t attempt = 0; attempt < 3; attempt++) {
|
||||
size = 0;
|
||||
result = m_graphics.device.getPipelineCacheData(m_driver_cache, &size, nullptr);
|
||||
if (result != vk::Result::eSuccess || size == 0 ||
|
||||
size > std::numeric_limits<uint32_t>::max()) {
|
||||
break;
|
||||
}
|
||||
payload.resize(size);
|
||||
result = m_graphics.device.getPipelineCacheData(m_driver_cache, &size, payload.data());
|
||||
if (result != vk::Result::eIncomplete) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (result != vk::Result::eSuccess || size == 0 ||
|
||||
size > std::numeric_limits<uint32_t>::max()) {
|
||||
PipelineCacheLog("Vulkan pipeline cache: save failed ({}, {} bytes)",
|
||||
VulkanToString(result), size);
|
||||
return;
|
||||
}
|
||||
payload.resize(size);
|
||||
auto prefix = DriverCacheSignature(m_graphics.GetPhysicalDeviceProperties());
|
||||
const auto payload_hash = XXH3_64bits(payload.data(), payload.size());
|
||||
prefix.append(reinterpret_cast<const char*>(&payload_hash), sizeof(payload_hash));
|
||||
if (!Common::File::CreateDirectories(m_driver_cache_path.parent_path())) {
|
||||
PipelineCacheLog("Vulkan pipeline cache: failed to create cache directory");
|
||||
return;
|
||||
}
|
||||
auto temp_path = m_driver_cache_path;
|
||||
temp_path += ".tmp";
|
||||
Common::File file;
|
||||
uint32_t prefix_written = 0;
|
||||
uint32_t payload_written = 0;
|
||||
if (file.Create(temp_path)) {
|
||||
file.Write(prefix.data(), static_cast<uint32_t>(prefix.size()), &prefix_written);
|
||||
file.Write(payload.data(), static_cast<uint32_t>(payload.size()), &payload_written);
|
||||
}
|
||||
const bool flushed = !file.IsInvalid() && file.Flush();
|
||||
file.Close();
|
||||
if (prefix_written != prefix.size() || payload_written != payload.size() || !flushed ||
|
||||
!Common::File::RenameFile(temp_path, m_driver_cache_path)) {
|
||||
PipelineCacheLog("Vulkan pipeline cache: failed to write {}",
|
||||
Common::PathToString(m_driver_cache_path));
|
||||
return;
|
||||
}
|
||||
PipelineCacheLog("Vulkan pipeline cache: saved {} bytes to {}", payload.size(),
|
||||
Common::PathToString(m_driver_cache_path));
|
||||
m_graphics.device.destroyPipelineCache(m_driver_cache, nullptr);
|
||||
m_driver_cache = nullptr;
|
||||
}
|
||||
|
||||
ShaderProgram PipelineCache::GetVertexProgram(const HW::VertexShaderInfo& regs,
|
||||
const HW::ShaderRegisters& sh,
|
||||
ShaderVertexInputInfo& input_info) {
|
||||
const auto params = PrepareProgram(regs, sh, input_info);
|
||||
Common::LockGuard lock(m_mutex);
|
||||
return m_program_cache->Get(params, input_info);
|
||||
}
|
||||
|
||||
ShaderProgram PipelineCache::GetPixelProgram(
|
||||
const HW::PixelShaderInfo& regs, const HW::ShaderRegisters& sh,
|
||||
const ShaderVertexInputInfo& vertex_info,
|
||||
std::span<const Prospero::ColorComponentMapping, 8> target_export_mapping,
|
||||
ShaderPixelInputInfo& input_info) {
|
||||
const auto params = PrepareProgram(regs, sh, vertex_info, target_export_mapping, input_info);
|
||||
Common::LockGuard lock(m_mutex);
|
||||
return m_program_cache->Get(params, input_info);
|
||||
}
|
||||
|
||||
ShaderProgram PipelineCache::GetComputeProgram(const HW::ComputeShaderInfo& regs,
|
||||
const HW::ShaderRegisters& sh,
|
||||
ShaderComputeInputInfo& input_info) {
|
||||
input_info.needs_lds_barriers = !m_graphics.compute_wave64_supported;
|
||||
const auto params = PrepareProgram(regs, sh, input_info);
|
||||
Common::LockGuard lock(m_mutex);
|
||||
return m_program_cache->Get(params, input_info);
|
||||
}
|
||||
|
||||
bool PipelineStaticParameters::operator==(const PipelineStaticParameters& other) const noexcept {
|
||||
@@ -53,24 +370,22 @@ bool PipelineStaticParameters::operator==(const PipelineStaticParameters& other)
|
||||
}
|
||||
|
||||
PipelineCache::GraphicsPipeline& PipelineCache::CreateGraphicsPipeline(
|
||||
RenderColorInfo* colors, uint32_t color_count, RenderDepthInfo& depth,
|
||||
ShaderVertexInputInfo& vs_input_info, RenderCommandBuffer& command,
|
||||
ShaderPixelInputInfo* ps_input_info, vk::PrimitiveTopology topology,
|
||||
bool primitive_restart_enable, bool ps_active, std::span<const uint32_t> vs_spirv,
|
||||
std::span<const uint32_t> ps_spirv) {
|
||||
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) {
|
||||
KYTY_PROFILER_BLOCK("PipelineCache::CreatePipeline(Gfx)", profiler::colors::DeepOrangeA200);
|
||||
|
||||
EXIT_IF(colors == nullptr);
|
||||
EXIT_IF(color_count > RENDER_COLOR_ATTACHMENTS_MAX);
|
||||
EXIT_IF(vs_spirv.empty());
|
||||
EXIT_IF(ps_active && ps_spirv.empty());
|
||||
EXIT_IF(colors.size() > RENDER_COLOR_ATTACHMENTS_MAX);
|
||||
EXIT_IF(!vertex_program);
|
||||
const bool ps_active = ps_input_info != nullptr;
|
||||
EXIT_IF(ps_active && !pixel_program);
|
||||
const auto color_count = static_cast<uint32_t>(colors.size());
|
||||
|
||||
Common::LockGuard lock(m_mutex);
|
||||
auto& ctx = command.GetRegisters();
|
||||
auto& sh_ctx = command.GetShaders();
|
||||
auto& ctx = command.GetRegisters();
|
||||
|
||||
const auto& vertex_info = sh_ctx.GetVs();
|
||||
const auto& ps_regs = sh_ctx.GetPs();
|
||||
const HW::BlendColor& bclr = ctx.GetBlendColor();
|
||||
uint32_t color_mask[RENDER_COLOR_ATTACHMENTS_MAX] = {};
|
||||
for (uint32_t i = 0; i < color_count; i++) {
|
||||
@@ -81,11 +396,8 @@ PipelineCache::GraphicsPipeline& PipelineCache::CreateGraphicsPipeline(
|
||||
}
|
||||
const HW::ModeControl& mc = ctx.GetModeControl();
|
||||
|
||||
auto vs_id = ShaderGetIdVS(vertex_info, vs_input_info, true);
|
||||
ShaderId ps_id {};
|
||||
if (ps_active) {
|
||||
ps_id = ShaderGetIdPS(ps_regs, *ps_input_info, true);
|
||||
}
|
||||
const auto vs_id = vertex_program.id;
|
||||
const auto ps_id = ps_active ? pixel_program.id : 0;
|
||||
|
||||
PipelineStaticParameters static_params {};
|
||||
GraphicsPipeline p {};
|
||||
@@ -201,20 +513,17 @@ PipelineCache::GraphicsPipeline& PipelineCache::CreateGraphicsPipeline(
|
||||
if (ps_active) {
|
||||
ShaderDbgDumpInputInfo(*ps_input_info);
|
||||
}
|
||||
LOGF("PipelineTrace: shader binaries VS=0x%08" PRIx32 "/0x%08" PRIx32 " words=%" PRIu64
|
||||
" PS=0x%08" PRIx32 "/0x%08" PRIx32 " words=%" PRIu64 "\n",
|
||||
vs_id.hash0, vs_id.crc32, static_cast<uint64_t>(vs_spirv.size()), ps_id.hash0,
|
||||
ps_id.crc32, static_cast<uint64_t>(ps_spirv.size()));
|
||||
LOGF("PipelineTrace: shader modules VS=%" PRIu64 " module=%p PS=%" PRIu64
|
||||
" module=%p\n",
|
||||
vs_id, static_cast<void*>(vertex_program.module), ps_id,
|
||||
static_cast<void*>(pixel_program.module));
|
||||
}
|
||||
|
||||
auto cached = std::make_unique<GraphicsPipeline>(p);
|
||||
LogPipelineTrace("CreatePipelineInternal begin", vs_id.hash0, vs_id.crc32, ps_id.hash0,
|
||||
ps_id.crc32);
|
||||
CreatePipelineInternal(m_graphics, m_descriptor_cache, *cached, rendering, vs_input_info,
|
||||
vs_spirv, ps_input_info, ps_spirv, static_params, vs_id.hash0,
|
||||
vs_id.crc32, ps_id.hash0, ps_id.crc32, ps_active);
|
||||
LogPipelineTrace("CreatePipelineInternal done", vs_id.hash0, vs_id.crc32, ps_id.hash0,
|
||||
ps_id.crc32);
|
||||
LogPipelineTrace("CreatePipelineInternal begin", vs_id, ps_id);
|
||||
CreatePipelineInternal(m_graphics, *cached, rendering, vs_input_info, vertex_program.module,
|
||||
ps_input_info, pixel_program.module, static_params, m_driver_cache);
|
||||
LogPipelineTrace("CreatePipelineInternal done", vs_id, ps_id);
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(cached->pipeline == nullptr);
|
||||
EXIT_NOT_IMPLEMENTED(cached->pipeline_layout == nullptr);
|
||||
@@ -226,19 +535,16 @@ PipelineCache::GraphicsPipeline& PipelineCache::CreateGraphicsPipeline(
|
||||
}
|
||||
|
||||
PipelineCache::ComputePipeline&
|
||||
PipelineCache::CreateComputePipeline(ShaderComputeInputInfo& input_info,
|
||||
const HW::ComputeShaderInfo& cs_regs,
|
||||
std::span<const uint32_t> cs_spirv) {
|
||||
PipelineCache::CreateComputePipeline(ShaderComputeInputInfo& input_info,
|
||||
const ShaderProgram& compute_program) {
|
||||
KYTY_PROFILER_BLOCK("PipelineCache::CreatePipeline(Compute)", profiler::colors::RedA100);
|
||||
|
||||
EXIT_IF(cs_spirv.empty());
|
||||
EXIT_IF(!compute_program);
|
||||
|
||||
Common::LockGuard lock(m_mutex);
|
||||
|
||||
auto cs_id = ShaderGetIdCS(cs_regs, input_info, true);
|
||||
|
||||
ComputePipeline p {};
|
||||
p.cs_shader_id = cs_id;
|
||||
p.cs_shader_id = compute_program.id;
|
||||
|
||||
ComputePipelineKey key {};
|
||||
key.cs_shader_id = p.cs_shader_id;
|
||||
@@ -252,7 +558,7 @@ PipelineCache::CreateComputePipeline(ShaderComputeInputInfo& input_info,
|
||||
}
|
||||
|
||||
auto cached = std::make_unique<ComputePipeline>(p);
|
||||
CreatePipelineInternal(m_graphics, m_descriptor_cache, *cached, input_info, cs_spirv);
|
||||
CreatePipelineInternal(m_graphics, *cached, input_info, compute_program.module, m_driver_cache);
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(cached->pipeline == nullptr);
|
||||
EXIT_NOT_IMPLEMENTED(cached->pipeline_layout == nullptr);
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "graphics/shader/shader.h"
|
||||
|
||||
#include <cstddef>
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
#include <span>
|
||||
#include <type_traits>
|
||||
@@ -20,8 +21,7 @@ namespace Libs::Graphics {
|
||||
struct GraphicContext;
|
||||
struct RenderColorInfo;
|
||||
struct RenderDepthInfo;
|
||||
class RenderCommandBuffer;
|
||||
class DescriptorCache;
|
||||
class CommandBuffer;
|
||||
|
||||
namespace HW {
|
||||
class Context;
|
||||
@@ -96,44 +96,66 @@ struct PipelineRenderingState {
|
||||
bool operator==(const PipelineRenderingState&) const = default;
|
||||
};
|
||||
|
||||
struct ShaderProgram {
|
||||
uint64_t id = 0;
|
||||
vk::ShaderModule module = nullptr;
|
||||
|
||||
explicit operator bool() const {
|
||||
return id != 0 && module != nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
class PipelineCache {
|
||||
public:
|
||||
PipelineCache(GraphicContext& graphics, DescriptorCache& descriptor_cache)
|
||||
: m_graphics(graphics), m_descriptor_cache(descriptor_cache) {
|
||||
EXIT_NOT_IMPLEMENTED(!Common::Thread::IsMainThread());
|
||||
}
|
||||
explicit PipelineCache(GraphicContext& graphics);
|
||||
~PipelineCache();
|
||||
KYTY_CLASS_NO_COPY(PipelineCache);
|
||||
void Save();
|
||||
|
||||
struct Pipeline {
|
||||
vk::PipelineLayout pipeline_layout = nullptr;
|
||||
vk::Pipeline pipeline = nullptr;
|
||||
vk::PipelineLayout pipeline_layout = nullptr;
|
||||
vk::Pipeline pipeline = nullptr;
|
||||
vk::DescriptorSetLayout descriptor_set_layout = nullptr;
|
||||
bool uses_push_descriptors = false;
|
||||
};
|
||||
|
||||
struct GraphicsPipeline: Pipeline {
|
||||
ShaderId vs_shader_id;
|
||||
ShaderId ps_shader_id;
|
||||
uint64_t vs_shader_id = 0;
|
||||
uint64_t ps_shader_id = 0;
|
||||
};
|
||||
|
||||
struct ComputePipeline: Pipeline {
|
||||
ShaderId cs_shader_id;
|
||||
uint64_t cs_shader_id = 0;
|
||||
};
|
||||
|
||||
GraphicsPipeline&
|
||||
CreateGraphicsPipeline(RenderColorInfo* colors, uint32_t color_count, RenderDepthInfo& depth,
|
||||
ShaderVertexInputInfo& vs_input_info, RenderCommandBuffer& command,
|
||||
ShaderPixelInputInfo* ps_input_info, vk::PrimitiveTopology topology,
|
||||
bool primitive_restart_enable, bool ps_active,
|
||||
std::span<const uint32_t> vs_spirv, std::span<const uint32_t> ps_spirv);
|
||||
ComputePipeline& CreateComputePipeline(ShaderComputeInputInfo& input_info,
|
||||
const HW::ComputeShaderInfo& cs_regs,
|
||||
std::span<const uint32_t> cs_spirv);
|
||||
ShaderProgram GetVertexProgram(const HW::VertexShaderInfo& regs,
|
||||
const HW::ShaderRegisters& sh,
|
||||
ShaderVertexInputInfo& input_info);
|
||||
ShaderProgram
|
||||
GetPixelProgram(const HW::PixelShaderInfo& regs, const HW::ShaderRegisters& sh,
|
||||
const ShaderVertexInputInfo& vertex_info,
|
||||
std::span<const Prospero::ColorComponentMapping, 8> target_export_mapping,
|
||||
ShaderPixelInputInfo& input_info);
|
||||
ShaderProgram GetComputeProgram(const HW::ComputeShaderInfo& regs,
|
||||
const HW::ShaderRegisters& sh,
|
||||
ShaderComputeInputInfo& input_info);
|
||||
|
||||
GraphicsPipeline& CreateGraphicsPipeline(
|
||||
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);
|
||||
ComputePipeline& CreateComputePipeline(ShaderComputeInputInfo& input_info,
|
||||
const ShaderProgram& compute_program);
|
||||
|
||||
private:
|
||||
struct ProgramCache;
|
||||
|
||||
struct GraphicsPipelineKey {
|
||||
PipelineRenderingState rendering;
|
||||
ShaderId vs_shader_id;
|
||||
ShaderId ps_shader_id;
|
||||
uint64_t vs_shader_id = 0;
|
||||
uint64_t ps_shader_id = 0;
|
||||
PipelineStaticParameters static_params;
|
||||
|
||||
bool operator==(const GraphicsPipelineKey& other) const {
|
||||
@@ -143,7 +165,7 @@ private:
|
||||
};
|
||||
|
||||
struct ComputePipelineKey {
|
||||
ShaderId cs_shader_id;
|
||||
uint64_t cs_shader_id = 0;
|
||||
|
||||
bool operator==(const ComputePipelineKey& other) const {
|
||||
return cs_shader_id == other.cs_shader_id;
|
||||
@@ -156,15 +178,6 @@ private:
|
||||
(hash >> 2u);
|
||||
}
|
||||
|
||||
static void MixShaderId(std::size_t& hash, const ShaderId& id) {
|
||||
Mix(hash, id.hash0);
|
||||
Mix(hash, id.crc32);
|
||||
Mix(hash, id.ids.size());
|
||||
for (auto value: id.ids) {
|
||||
Mix(hash, value);
|
||||
}
|
||||
}
|
||||
|
||||
static void MixStaticParams(std::size_t& hash, const PipelineStaticParameters& params) {
|
||||
const auto* bytes = reinterpret_cast<const uint8_t*>(¶ms);
|
||||
for (std::size_t i = 0; i < sizeof(params); i++) {
|
||||
@@ -186,8 +199,8 @@ private:
|
||||
std::size_t operator()(const GraphicsPipelineKey& key) const {
|
||||
std::size_t hash = 0;
|
||||
PipelineKeyHash::MixRendering(hash, key.rendering);
|
||||
PipelineKeyHash::MixShaderId(hash, key.vs_shader_id);
|
||||
PipelineKeyHash::MixShaderId(hash, key.ps_shader_id);
|
||||
PipelineKeyHash::Mix(hash, key.vs_shader_id);
|
||||
PipelineKeyHash::Mix(hash, key.ps_shader_id);
|
||||
PipelineKeyHash::MixStaticParams(hash, key.static_params);
|
||||
return hash;
|
||||
}
|
||||
@@ -196,34 +209,35 @@ private:
|
||||
struct ComputePipelineKeyHash {
|
||||
std::size_t operator()(const ComputePipelineKey& key) const {
|
||||
std::size_t hash = 0;
|
||||
PipelineKeyHash::MixShaderId(hash, key.cs_shader_id);
|
||||
PipelineKeyHash::Mix(hash, key.cs_shader_id);
|
||||
return hash;
|
||||
}
|
||||
};
|
||||
|
||||
GraphicContext& m_graphics;
|
||||
DescriptorCache& m_descriptor_cache;
|
||||
GraphicContext& m_graphics;
|
||||
std::unique_ptr<ProgramCache> m_program_cache;
|
||||
vk::PipelineCache m_driver_cache = nullptr;
|
||||
std::filesystem::path m_driver_cache_path;
|
||||
std::unordered_map<GraphicsPipelineKey, std::unique_ptr<GraphicsPipeline>,
|
||||
GraphicsPipelineKeyHash>
|
||||
m_graphics_pipelines;
|
||||
std::unordered_map<ComputePipelineKey, std::unique_ptr<ComputePipeline>, ComputePipelineKeyHash>
|
||||
m_compute_pipelines;
|
||||
Common::Mutex m_mutex;
|
||||
|
||||
void InitializeDriverCache();
|
||||
};
|
||||
|
||||
void LogPipelineTrace(const char* phase, uint32_t vs_hash0, uint32_t vs_crc32, uint32_t ps_hash0,
|
||||
uint32_t ps_crc32);
|
||||
void LogPipelineTrace(const char* phase, uint64_t vertex_program_id, uint64_t pixel_program_id);
|
||||
void CreatePipelineInternal(
|
||||
GraphicContext& graphics, DescriptorCache& descriptor_cache,
|
||||
PipelineCache::GraphicsPipeline& pipeline, const PipelineRenderingState& rendering,
|
||||
const ShaderVertexInputInfo& vs_input_info, std::span<const uint32_t> vs_shader,
|
||||
const ShaderPixelInputInfo* ps_input_info, std::span<const uint32_t> ps_shader,
|
||||
const PipelineStaticParameters& static_params, uint32_t vs_hash0, uint32_t vs_crc32,
|
||||
uint32_t ps_hash0, uint32_t ps_crc32, bool ps_active);
|
||||
void CreatePipelineInternal(GraphicContext& graphics, DescriptorCache& descriptor_cache,
|
||||
PipelineCache::ComputePipeline& pipeline,
|
||||
const ShaderComputeInputInfo& input_info,
|
||||
std::span<const uint32_t> cs_shader);
|
||||
GraphicContext& graphics, PipelineCache::GraphicsPipeline& pipeline,
|
||||
const PipelineRenderingState& rendering, const ShaderVertexInputInfo& vs_input_info,
|
||||
vk::ShaderModule vertex_module, const ShaderPixelInputInfo* ps_input_info,
|
||||
vk::ShaderModule pixel_module, const PipelineStaticParameters& static_params,
|
||||
vk::PipelineCache driver_cache);
|
||||
void CreatePipelineInternal(GraphicContext& graphics, PipelineCache::ComputePipeline& pipeline,
|
||||
const ShaderComputeInputInfo& input_info,
|
||||
vk::ShaderModule compute_module, vk::PipelineCache driver_cache);
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
#include "graphics/host_gpu/renderer/pipeline/shaderSubgroup.h"
|
||||
|
||||
#include "graphics/shader/recompiler/backend/spirv/SpirvEmitter.h"
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
ShaderSubgroupCapabilities::ShaderSubgroupCapabilities(const GraphicContext& graphics)
|
||||
: subgroup_size(graphics.subgroup_size), min_subgroup_size(graphics.min_subgroup_size),
|
||||
max_subgroup_size(graphics.max_subgroup_size),
|
||||
required_subgroup_size_stages(graphics.required_subgroup_size_stages),
|
||||
subgroup_size_control_enabled(graphics.subgroup_size_control_enabled) {}
|
||||
|
||||
ShaderLaneMaskMode SelectGraphicsLaneMaskMode(uint32_t guest_wave_size) {
|
||||
// A 64-wide guest wave can't assume the host GPU's lane layout matches
|
||||
// GCN's. AMD RDNA also reports a 64-wide subgroup but uses a different
|
||||
// lane/pixel layout, which corrupted rendering under "native wave" mode.
|
||||
// Per-invocation emulation works regardless of the host's lane layout.
|
||||
if (guest_wave_size == 64u) {
|
||||
return ShaderLaneMaskMode::PerInvocation;
|
||||
}
|
||||
return ShaderLaneMaskMode::NativeWave;
|
||||
}
|
||||
|
||||
ShaderSubgroupConfiguration ConfigureShaderSubgroup(const ShaderSubgroupCapabilities& capabilities,
|
||||
vk::ShaderStageFlagBits stage,
|
||||
const ShaderRecompiler::IR::Program& program) {
|
||||
const auto guest_wave_size = program.wave_size;
|
||||
if ((guest_wave_size != 32u && guest_wave_size != 64u) ||
|
||||
!program.spirv_requirements.has_value()) {
|
||||
return {};
|
||||
}
|
||||
if (stage != vk::ShaderStageFlagBits::eCompute) {
|
||||
const auto expected = SelectGraphicsLaneMaskMode(guest_wave_size);
|
||||
if (program.lane_mask_mode != expected || (capabilities.subgroup_size != guest_wave_size &&
|
||||
expected != ShaderLaneMaskMode::PerInvocation)) {
|
||||
return {};
|
||||
}
|
||||
return {expected == ShaderLaneMaskMode::NativeWave
|
||||
? ShaderSubgroupMode::Natural
|
||||
: ShaderSubgroupMode::PerInvocationGraphics,
|
||||
0};
|
||||
}
|
||||
if (program.lane_mask_mode != ShaderLaneMaskMode::NativeWave) {
|
||||
return {};
|
||||
}
|
||||
if (capabilities.subgroup_size == guest_wave_size) {
|
||||
return {ShaderSubgroupMode::Natural, 0};
|
||||
}
|
||||
if (capabilities.subgroup_size_control_enabled &&
|
||||
(capabilities.required_subgroup_size_stages & stage) &&
|
||||
guest_wave_size >= capabilities.min_subgroup_size &&
|
||||
guest_wave_size <= capabilities.max_subgroup_size) {
|
||||
return {ShaderSubgroupMode::Controlled, guest_wave_size};
|
||||
}
|
||||
return {ShaderSubgroupMode::FlattenedMasks, 0};
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
@@ -1,40 +0,0 @@
|
||||
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_SHADERSUBGROUP_H_
|
||||
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_SHADERSUBGROUP_H_
|
||||
|
||||
#include "graphics/host_gpu/graphicContext.h"
|
||||
#include "graphics/shader/recompiler/ir/ShaderIR.h"
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
enum class ShaderSubgroupMode {
|
||||
Natural,
|
||||
Controlled,
|
||||
PerInvocationGraphics,
|
||||
FlattenedMasks,
|
||||
Unsupported
|
||||
};
|
||||
|
||||
struct ShaderSubgroupConfiguration {
|
||||
ShaderSubgroupMode mode = ShaderSubgroupMode::Unsupported;
|
||||
uint32_t required_size = 0;
|
||||
};
|
||||
|
||||
struct ShaderSubgroupCapabilities {
|
||||
explicit ShaderSubgroupCapabilities(const GraphicContext& graphics);
|
||||
|
||||
uint32_t subgroup_size = 0;
|
||||
uint32_t min_subgroup_size = 0;
|
||||
uint32_t max_subgroup_size = 0;
|
||||
vk::ShaderStageFlags required_subgroup_size_stages;
|
||||
bool subgroup_size_control_enabled = false;
|
||||
};
|
||||
|
||||
ShaderLaneMaskMode SelectGraphicsLaneMaskMode(uint32_t guest_wave_size);
|
||||
|
||||
ShaderSubgroupConfiguration ConfigureShaderSubgroup(const ShaderSubgroupCapabilities& capabilities,
|
||||
vk::ShaderStageFlagBits stage,
|
||||
const ShaderRecompiler::IR::Program& program);
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_SHADERSUBGROUP_H_
|
||||
@@ -6,9 +6,8 @@
|
||||
#include "graphics/guest_gpu/gpu_defs.h"
|
||||
#include "graphics/host_gpu/graphicContext.h"
|
||||
#include "graphics/host_gpu/renderer/debug.h"
|
||||
#include "graphics/host_gpu/renderer/pipeline/descriptorCache.h"
|
||||
#include "graphics/host_gpu/renderer/pipeline/descriptors.h"
|
||||
#include "graphics/host_gpu/renderer/pipeline/pipelineCache.h"
|
||||
#include "graphics/host_gpu/renderer/pipeline/shaderSubgroup.h"
|
||||
#include "graphics/host_gpu/renderer/render.h"
|
||||
#include "graphics/host_gpu/renderer/renderContext.h"
|
||||
#include "graphics/host_gpu/renderer/renderTarget.h"
|
||||
@@ -20,6 +19,7 @@
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
#include <span>
|
||||
#include <vector>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
@@ -389,116 +389,53 @@ static vk::BlendOp GetBlendOp(uint32_t op) {
|
||||
return vk::BlendOp::eAdd;
|
||||
}
|
||||
|
||||
static void CreateLayout(DescriptorCache& descriptor_cache,
|
||||
std::span<vk::DescriptorSetLayout> set_layouts, uint32_t& set_layouts_num,
|
||||
std::span<vk::PushConstantRange> push_constant_info,
|
||||
uint32_t& push_constant_info_num,
|
||||
const ShaderRecompiler::IR::Program& program,
|
||||
vk::ShaderStageFlags vk_stage, DescriptorCache::Stage stage) {
|
||||
const auto& bindings = program.bindings;
|
||||
const bool need_descriptor = !bindings.descriptors.empty();
|
||||
|
||||
if (bindings.push_constant_size != 0) {
|
||||
auto index = push_constant_info_num;
|
||||
|
||||
push_constant_info[index].stageFlags = vk_stage;
|
||||
push_constant_info[index].offset = bindings.push_constant_offset;
|
||||
push_constant_info[index].size = bindings.push_constant_size;
|
||||
push_constant_info_num++;
|
||||
}
|
||||
|
||||
if (need_descriptor) {
|
||||
EXIT_IF(bindings.descriptor_set != set_layouts_num);
|
||||
|
||||
set_layouts[set_layouts_num] = descriptor_cache.GetDescriptorSetLayout(stage, program);
|
||||
set_layouts_num++;
|
||||
static void AddLayoutBindings(std::vector<vk::DescriptorSetLayoutBinding>& descriptor_bindings,
|
||||
const ShaderRecompiler::IR::Program& program,
|
||||
vk::ShaderStageFlagBits stage) {
|
||||
for (const auto& binding: program.bindings.descriptors) {
|
||||
descriptor_bindings.push_back(
|
||||
{ShaderRecompiler::IR::NativeBinding(program.stage, binding.kind),
|
||||
NativeDescriptorType(binding.kind), NativeDescriptorCount(binding), stage, nullptr});
|
||||
}
|
||||
}
|
||||
|
||||
static void ConfigureSubgroupSize(const GraphicContext& graphics, vk::ShaderStageFlagBits vk_stage,
|
||||
const ShaderRecompiler::IR::Program& program,
|
||||
vk::PipelineShaderStageRequiredSubgroupSizeCreateInfo& required,
|
||||
vk::PipelineShaderStageCreateInfo& stage) {
|
||||
const auto config =
|
||||
ConfigureShaderSubgroup(ShaderSubgroupCapabilities {graphics}, vk_stage, program);
|
||||
switch (config.mode) {
|
||||
case ShaderSubgroupMode::Natural: return;
|
||||
case ShaderSubgroupMode::PerInvocationGraphics: {
|
||||
static std::atomic<uint32_t> log_count {0};
|
||||
if (log_count.fetch_add(1, std::memory_order_relaxed) < 16) {
|
||||
LOGF("Vulkan running %u-lane graphics shader stage 0x%08x with per-invocation "
|
||||
"EXEC/VCC on the host-native subgroup\n",
|
||||
program.wave_size, static_cast<uint32_t>(vk_stage));
|
||||
}
|
||||
return;
|
||||
}
|
||||
case ShaderSubgroupMode::FlattenedMasks: {
|
||||
static std::atomic<uint32_t> log_count {0};
|
||||
if (log_count.fetch_add(1, std::memory_order_relaxed) < 16) {
|
||||
LOGF("Vulkan running %u-lane shader stage 0x%08x with flattened per-invocation "
|
||||
"EXEC/VCC\n",
|
||||
program.wave_size, static_cast<uint32_t>(vk_stage));
|
||||
}
|
||||
return;
|
||||
}
|
||||
case ShaderSubgroupMode::Controlled: break;
|
||||
case ShaderSubgroupMode::Unsupported:
|
||||
default:
|
||||
EXIT("Vulkan cannot run %u-lane shader stage 0x%08x: default=%u min=%u max=%u "
|
||||
"controlled_stages=0x%08x\n",
|
||||
program.wave_size, static_cast<uint32_t>(vk_stage), graphics.subgroup_size,
|
||||
graphics.min_subgroup_size, graphics.max_subgroup_size,
|
||||
static_cast<vk::ShaderStageFlags::MaskType>(
|
||||
graphics.required_subgroup_size_stages));
|
||||
static void CreateDescriptorLayout(GraphicContext& graphics, PipelineCache::Pipeline& pipeline,
|
||||
std::span<const vk::DescriptorSetLayoutBinding> bindings) {
|
||||
uint32_t descriptor_count = 0;
|
||||
for (const auto& binding: bindings) {
|
||||
descriptor_count += binding.descriptorCount;
|
||||
}
|
||||
pipeline.uses_push_descriptors = descriptor_count <= graphics.max_push_descriptors;
|
||||
|
||||
required.sType = vk::StructureType::ePipelineShaderStageRequiredSubgroupSizeCreateInfo;
|
||||
required.requiredSubgroupSize = config.required_size;
|
||||
stage.pNext = &required;
|
||||
vk::DescriptorSetLayoutCreateInfo create {};
|
||||
create.sType = vk::StructureType::eDescriptorSetLayoutCreateInfo;
|
||||
create.flags = pipeline.uses_push_descriptors
|
||||
? vk::DescriptorSetLayoutCreateFlagBits::ePushDescriptorKHR
|
||||
: vk::DescriptorSetLayoutCreateFlags {};
|
||||
create.bindingCount = static_cast<uint32_t>(bindings.size());
|
||||
create.pBindings = bindings.data();
|
||||
EXIT_IF(graphics.device.createDescriptorSetLayout(
|
||||
&create, nullptr, &pipeline.descriptor_set_layout) != vk::Result::eSuccess);
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
|
||||
void CreatePipelineInternal(
|
||||
GraphicContext& graphics, DescriptorCache& descriptor_cache,
|
||||
PipelineCache::GraphicsPipeline& pipeline, const PipelineRenderingState& rendering,
|
||||
const ShaderVertexInputInfo& vs_input_info, std::span<const uint32_t> vs_shader,
|
||||
const ShaderPixelInputInfo* ps_input_info, std::span<const uint32_t> ps_shader,
|
||||
const PipelineStaticParameters& static_params, uint32_t vs_hash0, uint32_t vs_crc32,
|
||||
uint32_t ps_hash0, uint32_t ps_crc32, bool ps_active) {
|
||||
EXIT_IF(ps_active && ps_input_info == nullptr);
|
||||
GraphicContext& graphics, PipelineCache::GraphicsPipeline& pipeline,
|
||||
const PipelineRenderingState& rendering, const ShaderVertexInputInfo& vs_input_info,
|
||||
vk::ShaderModule vertex_module, const ShaderPixelInputInfo* ps_input_info,
|
||||
vk::ShaderModule pixel_module, const PipelineStaticParameters& static_params,
|
||||
vk::PipelineCache driver_cache) {
|
||||
const bool ps_active = ps_input_info != nullptr;
|
||||
EXIT_IF(vertex_module == nullptr || (ps_active && pixel_module == nullptr));
|
||||
|
||||
const bool rect_list = static_params.topology == vk::PrimitiveTopology::ePatchList;
|
||||
|
||||
vk::ShaderModule vert_shader_module = nullptr;
|
||||
vk::ShaderModule tess_control_shader_module = nullptr;
|
||||
vk::ShaderModule tess_eval_shader_module = nullptr;
|
||||
vk::ShaderModule frag_shader_module = nullptr;
|
||||
|
||||
vk::ShaderModuleCreateInfo create_info {};
|
||||
|
||||
create_info.sType = vk::StructureType::eShaderModuleCreateInfo;
|
||||
create_info.pNext = nullptr;
|
||||
create_info.flags = {};
|
||||
|
||||
create_info.codeSize = vs_shader.size() * 4;
|
||||
create_info.pCode = vs_shader.data();
|
||||
auto result = graphics.device.createShaderModule(&create_info, nullptr, &vert_shader_module);
|
||||
if (graphics_debug_dump_enabled()) {
|
||||
LOGF("PipelineTrace: vkCreateShaderModule VS done result=%s module=%p\n",
|
||||
VulkanToString(result).c_str(), static_cast<void*>(vert_shader_module));
|
||||
}
|
||||
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess);
|
||||
|
||||
if (ps_active) {
|
||||
create_info.codeSize = ps_shader.size() * 4;
|
||||
create_info.pCode = ps_shader.data();
|
||||
result = graphics.device.createShaderModule(&create_info, nullptr, &frag_shader_module);
|
||||
if (graphics_debug_dump_enabled()) {
|
||||
LOGF("PipelineTrace: vkCreateShaderModule PS done result=%s module=%p\n",
|
||||
VulkanToString(result).c_str(), static_cast<void*>(frag_shader_module));
|
||||
}
|
||||
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess);
|
||||
}
|
||||
vk::Result result {};
|
||||
if (rect_list) {
|
||||
const auto shaders =
|
||||
BuildRectListShaders(vs_input_info, ps_active ? ps_input_info : nullptr);
|
||||
@@ -523,40 +460,27 @@ void CreatePipelineInternal(
|
||||
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess);
|
||||
}
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(vert_shader_module == nullptr);
|
||||
EXIT_NOT_IMPLEMENTED(
|
||||
rect_list && (tess_control_shader_module == nullptr || tess_eval_shader_module == nullptr));
|
||||
EXIT_NOT_IMPLEMENTED(ps_active && frag_shader_module == nullptr);
|
||||
|
||||
vk::PipelineShaderStageCreateInfo vert_shader_stage_info {};
|
||||
vk::PipelineShaderStageRequiredSubgroupSizeCreateInfo vert_subgroup_size {};
|
||||
vk::PipelineShaderStageCreateInfo vert_shader_stage_info {};
|
||||
|
||||
vert_shader_stage_info.sType = vk::StructureType::ePipelineShaderStageCreateInfo;
|
||||
vert_shader_stage_info.pNext = nullptr;
|
||||
vert_shader_stage_info.flags = {};
|
||||
vert_shader_stage_info.stage = vk::ShaderStageFlagBits::eVertex;
|
||||
vert_shader_stage_info.module = vert_shader_module;
|
||||
vert_shader_stage_info.module = vertex_module;
|
||||
vert_shader_stage_info.pName = "main";
|
||||
vert_shader_stage_info.pSpecializationInfo = nullptr;
|
||||
EXIT_IF(!vs_input_info.stage);
|
||||
ConfigureSubgroupSize(graphics, vk::ShaderStageFlagBits::eVertex, *vs_input_info.stage.program,
|
||||
vert_subgroup_size, vert_shader_stage_info);
|
||||
|
||||
vk::PipelineShaderStageCreateInfo frag_shader_stage_info {};
|
||||
vk::PipelineShaderStageRequiredSubgroupSizeCreateInfo frag_subgroup_size {};
|
||||
vk::PipelineShaderStageCreateInfo frag_shader_stage_info {};
|
||||
frag_shader_stage_info.sType = vk::StructureType::ePipelineShaderStageCreateInfo;
|
||||
frag_shader_stage_info.pNext = nullptr;
|
||||
frag_shader_stage_info.flags = {};
|
||||
frag_shader_stage_info.stage = vk::ShaderStageFlagBits::eFragment;
|
||||
frag_shader_stage_info.module = frag_shader_module;
|
||||
frag_shader_stage_info.module = pixel_module;
|
||||
frag_shader_stage_info.pName = "main";
|
||||
frag_shader_stage_info.pSpecializationInfo = nullptr;
|
||||
if (ps_active) {
|
||||
EXIT_IF(!ps_input_info->stage);
|
||||
ConfigureSubgroupSize(graphics, vk::ShaderStageFlagBits::eFragment,
|
||||
*ps_input_info->stage.program, frag_subgroup_size,
|
||||
frag_shader_stage_info);
|
||||
}
|
||||
|
||||
vk::PipelineShaderStageCreateInfo tess_control_shader_stage_info {};
|
||||
tess_control_shader_stage_info.sType = vk::StructureType::ePipelineShaderStageCreateInfo;
|
||||
@@ -599,9 +523,10 @@ void CreatePipelineInternal(
|
||||
|
||||
uint32_t attr_size = 4;
|
||||
auto registers_num = vs_input_info.resources_dst[index].registers_num;
|
||||
auto used_components = (vs_input_info.resource_fetch_components[index] > 0
|
||||
? vs_input_info.resource_fetch_components[index]
|
||||
: registers_num);
|
||||
const auto compiled_components =
|
||||
vs_input_info.stage.program->info.vertex_fetch_components[index];
|
||||
auto used_components =
|
||||
compiled_components > 0 ? static_cast<int>(compiled_components) : registers_num;
|
||||
GetInputFormat(vs_input_info.resources[index], input_attr[index].format, attr_size,
|
||||
static_cast<uint32_t>(used_components));
|
||||
|
||||
@@ -877,40 +802,36 @@ void CreatePipelineInternal(
|
||||
color_blending.blendConstants[2] = static_params.blend_color_blue;
|
||||
color_blending.blendConstants[3] = static_params.blend_color_alpha;
|
||||
|
||||
vk::DescriptorSetLayout set_layouts[2] = {};
|
||||
uint32_t set_layouts_num = 0;
|
||||
|
||||
vk::PushConstantRange push_constant_info[2];
|
||||
uint32_t push_constant_info_num = 0;
|
||||
|
||||
EXIT_IF(!vs_input_info.stage);
|
||||
CreateLayout(descriptor_cache, set_layouts, set_layouts_num, push_constant_info,
|
||||
push_constant_info_num, *vs_input_info.stage.program,
|
||||
vk::ShaderStageFlagBits::eVertex, DescriptorCache::Stage::Vertex);
|
||||
std::vector<vk::DescriptorSetLayoutBinding> descriptor_bindings;
|
||||
AddLayoutBindings(descriptor_bindings, *vs_input_info.stage.program,
|
||||
vk::ShaderStageFlagBits::eVertex);
|
||||
if (ps_active) {
|
||||
EXIT_IF(!ps_input_info->stage);
|
||||
CreateLayout(descriptor_cache, set_layouts, set_layouts_num, push_constant_info,
|
||||
push_constant_info_num, *ps_input_info->stage.program,
|
||||
vk::ShaderStageFlagBits::eFragment, DescriptorCache::Stage::Pixel);
|
||||
AddLayoutBindings(descriptor_bindings, *ps_input_info->stage.program,
|
||||
vk::ShaderStageFlagBits::eFragment);
|
||||
}
|
||||
CreateDescriptorLayout(graphics, pipeline, descriptor_bindings);
|
||||
constexpr auto GraphicsStages =
|
||||
vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment;
|
||||
const vk::PushConstantRange push_constants {
|
||||
GraphicsStages, 0, ShaderRecompiler::IR::NativePushConstantSize};
|
||||
|
||||
vk::PipelineLayoutCreateInfo pipeline_layout_info {};
|
||||
pipeline_layout_info.sType = vk::StructureType::ePipelineLayoutCreateInfo;
|
||||
pipeline_layout_info.pNext = nullptr;
|
||||
pipeline_layout_info.flags = {};
|
||||
pipeline_layout_info.setLayoutCount = set_layouts_num;
|
||||
pipeline_layout_info.pSetLayouts = (set_layouts_num > 0 ? set_layouts : nullptr);
|
||||
pipeline_layout_info.pushConstantRangeCount = push_constant_info_num;
|
||||
pipeline_layout_info.pPushConstantRanges =
|
||||
push_constant_info_num > 0 ? push_constant_info : nullptr;
|
||||
pipeline_layout_info.setLayoutCount = 1;
|
||||
pipeline_layout_info.pSetLayouts = &pipeline.descriptor_set_layout;
|
||||
pipeline_layout_info.pushConstantRangeCount = 1;
|
||||
pipeline_layout_info.pPushConstantRanges = &push_constants;
|
||||
|
||||
EXIT_IF(pipeline.pipeline_layout != nullptr);
|
||||
|
||||
if (graphics_debug_dump_enabled()) {
|
||||
LOGF("PipelineTrace: vkCreatePipelineLayout begin VS=0x%08" PRIx32 "/0x%08" PRIx32
|
||||
" PS=0x%08" PRIx32 "/0x%08" PRIx32 " set_layouts=%" PRIu32 " push_constants=%" PRIu32
|
||||
"\n",
|
||||
vs_hash0, vs_crc32, ps_hash0, ps_crc32, set_layouts_num, push_constant_info_num);
|
||||
LOGF("PipelineTrace: vkCreatePipelineLayout begin VS=%" PRIu64 " PS=%" PRIu64
|
||||
" set_layouts=1 push_constants=%" PRIu32 "\n",
|
||||
pipeline.vs_shader_id, pipeline.ps_shader_id, 1u);
|
||||
}
|
||||
result = graphics.device.createPipelineLayout(&pipeline_layout_info, nullptr,
|
||||
&pipeline.pipeline_layout);
|
||||
@@ -1009,17 +930,18 @@ void CreatePipelineInternal(
|
||||
EXIT_IF(pipeline.pipeline != nullptr);
|
||||
|
||||
if (graphics_debug_dump_enabled()) {
|
||||
LOGF("PipelineTrace: vkCreateGraphicsPipelines begin VS=0x%08" PRIx32 "/0x%08" PRIx32
|
||||
" PS=0x%08" PRIx32 "/0x%08" PRIx32 " topology=%" PRIu32 " color_mask=0x%08" PRIx32
|
||||
LOGF("PipelineTrace: vkCreateGraphicsPipelines begin VS=%" PRIu64 " PS=%" PRIu64
|
||||
" topology=%" PRIu32 " color_mask=0x%08" PRIx32
|
||||
" depth=%s blend=%s dyn_states=%" PRIu32 " viewport=%f,%f %fx%f"
|
||||
" scissor=%d,%d %ux%u\n",
|
||||
vs_hash0, vs_crc32, ps_hash0, ps_crc32, static_cast<uint32_t>(static_params.topology),
|
||||
pipeline.vs_shader_id, pipeline.ps_shader_id,
|
||||
static_cast<uint32_t>(static_params.topology),
|
||||
static_params.color_mask[0], (static_params.with_depth ? "true" : "false"),
|
||||
(static_params.blend_enable[0] ? "true" : "false"), dynamic_states_count, viewport.x,
|
||||
viewport.y, viewport.width, viewport.height, scissor.offset.x, scissor.offset.y,
|
||||
scissor.extent.width, scissor.extent.height);
|
||||
}
|
||||
result = graphics.device.createGraphicsPipelines(nullptr, 1, &pipeline_info, nullptr,
|
||||
result = graphics.device.createGraphicsPipelines(driver_cache, 1, &pipeline_info, nullptr,
|
||||
&pipeline.pipeline);
|
||||
if (graphics_debug_dump_enabled()) {
|
||||
LOGF("PipelineTrace: vkCreateGraphicsPipelines done result=%s pipeline=%p\n",
|
||||
@@ -1029,40 +951,19 @@ void CreatePipelineInternal(
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(pipeline.pipeline == nullptr);
|
||||
|
||||
if (frag_shader_module != nullptr) {
|
||||
graphics.device.destroyShaderModule(frag_shader_module, nullptr);
|
||||
}
|
||||
if (tess_control_shader_module != nullptr) {
|
||||
graphics.device.destroyShaderModule(tess_control_shader_module, nullptr);
|
||||
}
|
||||
if (tess_eval_shader_module != nullptr) {
|
||||
graphics.device.destroyShaderModule(tess_eval_shader_module, nullptr);
|
||||
}
|
||||
graphics.device.destroyShaderModule(vert_shader_module, nullptr);
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
|
||||
void CreatePipelineInternal(GraphicContext& graphics, DescriptorCache& descriptor_cache,
|
||||
PipelineCache::ComputePipeline& pipeline,
|
||||
const ShaderComputeInputInfo& input_info,
|
||||
std::span<const uint32_t> cs_shader) {
|
||||
vk::ShaderModule comp_shader_module = nullptr;
|
||||
|
||||
vk::ShaderModuleCreateInfo create_info {};
|
||||
|
||||
create_info.sType = vk::StructureType::eShaderModuleCreateInfo;
|
||||
create_info.pNext = nullptr;
|
||||
create_info.flags = {};
|
||||
create_info.codeSize = cs_shader.size() * 4;
|
||||
create_info.pCode = cs_shader.data();
|
||||
LOGF("PipelineTrace: vkCreateShaderModule CS begin words=%" PRIu64 "\n",
|
||||
static_cast<uint64_t>(cs_shader.size()));
|
||||
auto result = graphics.device.createShaderModule(&create_info, nullptr, &comp_shader_module);
|
||||
LOGF("PipelineTrace: vkCreateShaderModule CS done result=%s module=%p\n",
|
||||
VulkanToString(result).c_str(), static_cast<void*>(comp_shader_module));
|
||||
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess);
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(comp_shader_module == nullptr);
|
||||
void CreatePipelineInternal(GraphicContext& graphics, PipelineCache::ComputePipeline& pipeline,
|
||||
const ShaderComputeInputInfo& input_info,
|
||||
vk::ShaderModule compute_module, vk::PipelineCache driver_cache) {
|
||||
EXIT_IF(compute_module == nullptr);
|
||||
|
||||
vk::PipelineShaderStageCreateInfo comp_shader_stage_info {};
|
||||
vk::PipelineShaderStageRequiredSubgroupSizeCreateInfo comp_subgroup_size {};
|
||||
@@ -1070,40 +971,41 @@ void CreatePipelineInternal(GraphicContext& graphics, DescriptorCache& descripto
|
||||
comp_shader_stage_info.pNext = nullptr;
|
||||
comp_shader_stage_info.flags = {};
|
||||
comp_shader_stage_info.stage = vk::ShaderStageFlagBits::eCompute;
|
||||
comp_shader_stage_info.module = comp_shader_module;
|
||||
comp_shader_stage_info.module = compute_module;
|
||||
comp_shader_stage_info.pName = "main";
|
||||
comp_shader_stage_info.pSpecializationInfo = nullptr;
|
||||
EXIT_IF(!input_info.stage);
|
||||
ConfigureSubgroupSize(graphics, vk::ShaderStageFlagBits::eCompute, *input_info.stage.program,
|
||||
comp_subgroup_size, comp_shader_stage_info);
|
||||
const auto wave_size = input_info.stage.program->wave_size;
|
||||
if (graphics.compute_subgroup_size_control_enabled &&
|
||||
wave_size >= graphics.min_subgroup_size && wave_size <= graphics.max_subgroup_size) {
|
||||
comp_subgroup_size.sType =
|
||||
vk::StructureType::ePipelineShaderStageRequiredSubgroupSizeCreateInfo;
|
||||
comp_subgroup_size.requiredSubgroupSize = wave_size;
|
||||
comp_shader_stage_info.pNext = &comp_subgroup_size;
|
||||
}
|
||||
|
||||
vk::DescriptorSetLayout set_layouts[1] = {};
|
||||
uint32_t set_layouts_num = 0;
|
||||
|
||||
vk::PushConstantRange push_constant_info[1];
|
||||
uint32_t push_constant_info_num = 0;
|
||||
|
||||
EXIT_IF(!input_info.stage);
|
||||
CreateLayout(descriptor_cache, set_layouts, set_layouts_num, push_constant_info,
|
||||
push_constant_info_num, *input_info.stage.program,
|
||||
vk::ShaderStageFlagBits::eCompute, DescriptorCache::Stage::Compute);
|
||||
std::vector<vk::DescriptorSetLayoutBinding> descriptor_bindings;
|
||||
AddLayoutBindings(descriptor_bindings, *input_info.stage.program,
|
||||
vk::ShaderStageFlagBits::eCompute);
|
||||
CreateDescriptorLayout(graphics, pipeline, descriptor_bindings);
|
||||
const vk::PushConstantRange push_constants {vk::ShaderStageFlagBits::eCompute, 0,
|
||||
ShaderRecompiler::IR::NativePushConstantSize};
|
||||
|
||||
vk::PipelineLayoutCreateInfo pipeline_layout_info {};
|
||||
pipeline_layout_info.sType = vk::StructureType::ePipelineLayoutCreateInfo;
|
||||
pipeline_layout_info.pNext = nullptr;
|
||||
pipeline_layout_info.flags = {};
|
||||
pipeline_layout_info.setLayoutCount = set_layouts_num;
|
||||
pipeline_layout_info.pSetLayouts = (set_layouts_num > 0 ? set_layouts : nullptr);
|
||||
pipeline_layout_info.pushConstantRangeCount = push_constant_info_num;
|
||||
pipeline_layout_info.pPushConstantRanges =
|
||||
push_constant_info_num > 0 ? push_constant_info : nullptr;
|
||||
pipeline_layout_info.setLayoutCount = 1;
|
||||
pipeline_layout_info.pSetLayouts = &pipeline.descriptor_set_layout;
|
||||
pipeline_layout_info.pushConstantRangeCount = 1;
|
||||
pipeline_layout_info.pPushConstantRanges = &push_constants;
|
||||
|
||||
EXIT_IF(pipeline.pipeline_layout != nullptr);
|
||||
|
||||
LOGF("PipelineTrace: vkCreatePipelineLayout CS begin set_layouts=%u push_constants=%u\n",
|
||||
set_layouts_num, push_constant_info_num);
|
||||
result = graphics.device.createPipelineLayout(&pipeline_layout_info, nullptr,
|
||||
&pipeline.pipeline_layout);
|
||||
LOGF("PipelineTrace: vkCreatePipelineLayout CS begin set_layouts=1 push_constants=%u\n",
|
||||
1u);
|
||||
auto result = graphics.device.createPipelineLayout(&pipeline_layout_info, nullptr,
|
||||
&pipeline.pipeline_layout);
|
||||
LOGF("PipelineTrace: vkCreatePipelineLayout CS done result=%s layout=%p\n",
|
||||
VulkanToString(result).c_str(), static_cast<void*>(pipeline.pipeline_layout));
|
||||
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess);
|
||||
@@ -1123,14 +1025,13 @@ void CreatePipelineInternal(GraphicContext& graphics, DescriptorCache& descripto
|
||||
|
||||
LOGF("PipelineTrace: vkCreateComputePipelines begin layout=%p\n",
|
||||
static_cast<void*>(pipeline.pipeline_layout));
|
||||
result = graphics.device.createComputePipelines(nullptr, 1, &info, nullptr, &pipeline.pipeline);
|
||||
result = graphics.device.createComputePipelines(driver_cache, 1, &info, nullptr,
|
||||
&pipeline.pipeline);
|
||||
LOGF("PipelineTrace: vkCreateComputePipelines done result=%s pipeline=%p\n",
|
||||
VulkanToString(result).c_str(), static_cast<void*>(pipeline.pipeline));
|
||||
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess);
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(pipeline.pipeline == nullptr);
|
||||
|
||||
graphics.device.destroyShaderModule(comp_shader_module, nullptr);
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
@@ -4,12 +4,14 @@
|
||||
#include "common/abi.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/common.h"
|
||||
#include "graphics/host_gpu/renderer/pipeline/descriptorCache.h"
|
||||
#include "graphics/host_gpu/renderer/pipeline/descriptors.h"
|
||||
#include "graphics/host_gpu/renderer/pipeline/pipelineCache.h"
|
||||
#include "graphics/host_gpu/renderer/renderTarget.h"
|
||||
#include "graphics/host_gpu/vulkanCommon.h"
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
#include <vector>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
@@ -23,9 +25,6 @@ class Shader;
|
||||
struct GraphicContext;
|
||||
struct ShaderBufferResource;
|
||||
struct ShaderComputeInputInfo;
|
||||
struct CommandSlot;
|
||||
struct VulkanBuffer;
|
||||
struct VulkanDescriptorSet;
|
||||
struct RenderDepthInfo;
|
||||
struct RenderColorInfo;
|
||||
struct DrawCallInfo;
|
||||
@@ -49,20 +48,6 @@ enum class CommandBufferDebugOp : uint32_t {
|
||||
Unknown,
|
||||
};
|
||||
|
||||
class FenceResourceRetainer {
|
||||
public:
|
||||
FenceResourceRetainer() = default;
|
||||
~FenceResourceRetainer();
|
||||
KYTY_CLASS_NO_COPY(FenceResourceRetainer);
|
||||
|
||||
void Retain(std::shared_ptr<void> resource);
|
||||
void ReleaseAfterFence() noexcept;
|
||||
[[nodiscard]] bool Empty() const noexcept { return m_resources.empty(); }
|
||||
|
||||
private:
|
||||
std::vector<std::shared_ptr<void>> m_resources;
|
||||
};
|
||||
|
||||
struct SubmitInfo {
|
||||
static constexpr uint32_t MaxSemaphores = 3;
|
||||
|
||||
@@ -91,78 +76,52 @@ struct SubmitInfo {
|
||||
|
||||
class CommandBuffer {
|
||||
public:
|
||||
explicit CommandBuffer(CommandScheduler& scheduler);
|
||||
~CommandBuffer();
|
||||
~CommandBuffer() = default;
|
||||
|
||||
KYTY_CLASS_NO_COPY(CommandBuffer);
|
||||
|
||||
[[nodiscard]] bool IsInvalid() const;
|
||||
|
||||
void Begin() const;
|
||||
void End() const;
|
||||
void Execute(const SubmitInfo& submit = {});
|
||||
void SetDebugInfo(uint32_t op, uint64_t submit_id, uint32_t arg0 = 0, uint32_t arg1 = 0,
|
||||
uint32_t arg2 = 0, uint32_t arg3 = 0, uint64_t arg4 = 0);
|
||||
void BeginRendering(const RenderState& state) const;
|
||||
void EndRendering() const;
|
||||
void WaitForFenceOnly();
|
||||
void WaitForFence();
|
||||
void WaitForFenceAndReset();
|
||||
void RetireBufferAfterFence(std::unique_ptr<VulkanBuffer> buffer);
|
||||
void RetainResourceUntilFence(std::shared_ptr<void> resource);
|
||||
void RecycleDescriptorAfterFence(VulkanDescriptorSet& set);
|
||||
|
||||
[[nodiscard]] vk::CommandBuffer Handle() const;
|
||||
[[nodiscard]] GraphicContext& GetGraphics() const noexcept { return m_graphics; }
|
||||
[[nodiscard]] RenderContext& GetContext() const noexcept { return m_context; }
|
||||
[[nodiscard]] bool IsExecute() const { return m_execute; }
|
||||
[[nodiscard]] HW::Context& GetRegisters() const noexcept { return *m_registers; }
|
||||
[[nodiscard]] HW::UserConfig& GetUserConfig() const noexcept { return *m_user_config; }
|
||||
[[nodiscard]] HW::Shader& GetShaders() const noexcept { return *m_shaders; }
|
||||
|
||||
private:
|
||||
void Release();
|
||||
void FinalizeFence(bool reset_recording);
|
||||
void ReleaseResourcesAfterFence();
|
||||
void DeleteBuffersAfterFence();
|
||||
void RecycleDescriptorsAfterFence();
|
||||
|
||||
RenderContext& m_context;
|
||||
CommandScheduler& m_scheduler;
|
||||
GraphicContext& m_graphics;
|
||||
CommandSlot* m_slot = nullptr;
|
||||
bool m_execute = false;
|
||||
bool m_fence_waited = false;
|
||||
uint64_t m_submit_seq = 0;
|
||||
uint32_t m_debug_op = 0;
|
||||
uint64_t m_debug_submit_id = 0;
|
||||
uint32_t m_debug_arg0 = 0;
|
||||
uint32_t m_debug_arg1 = 0;
|
||||
uint32_t m_debug_arg2 = 0;
|
||||
uint32_t m_debug_arg3 = 0;
|
||||
uint64_t m_debug_arg4 = 0;
|
||||
std::vector<std::unique_ptr<VulkanBuffer>> m_retired_buffers;
|
||||
FenceResourceRetainer m_fence_resources;
|
||||
std::vector<VulkanDescriptorSet*> m_descriptor_sets_after_fence;
|
||||
mutable RenderState m_render_state;
|
||||
mutable bool m_rendering = false;
|
||||
};
|
||||
|
||||
class RenderCommandBuffer final: public CommandBuffer {
|
||||
public:
|
||||
explicit RenderCommandBuffer(CommandScheduler& scheduler): CommandBuffer(scheduler) {}
|
||||
|
||||
explicit CommandBuffer(CommandScheduler& scheduler);
|
||||
void Bind(HW::Context& registers, HW::UserConfig& user_config, HW::Shader& shaders) noexcept {
|
||||
m_registers = ®isters;
|
||||
m_user_config = &user_config;
|
||||
m_shaders = &shaders;
|
||||
}
|
||||
|
||||
[[nodiscard]] HW::Context& GetRegisters() const noexcept { return *m_registers; }
|
||||
[[nodiscard]] HW::UserConfig& GetUserConfig() const noexcept { return *m_user_config; }
|
||||
[[nodiscard]] HW::Shader& GetShaders() const noexcept { return *m_shaders; }
|
||||
void Begin();
|
||||
void End() const;
|
||||
|
||||
private:
|
||||
HW::Context* m_registers = nullptr;
|
||||
HW::UserConfig* m_user_config = nullptr;
|
||||
HW::Shader* m_shaders = nullptr;
|
||||
RenderContext& m_context;
|
||||
GraphicContext& m_graphics;
|
||||
vk::CommandBuffer m_buffer = nullptr;
|
||||
uint32_t m_debug_op = 0;
|
||||
uint64_t m_debug_submit_id = 0;
|
||||
uint32_t m_debug_arg0 = 0;
|
||||
uint32_t m_debug_arg1 = 0;
|
||||
uint32_t m_debug_arg2 = 0;
|
||||
uint32_t m_debug_arg3 = 0;
|
||||
uint64_t m_debug_arg4 = 0;
|
||||
mutable RenderState m_render_state;
|
||||
mutable bool m_rendering = false;
|
||||
HW::Context* m_registers = nullptr;
|
||||
HW::UserConfig* m_user_config = nullptr;
|
||||
HW::Shader* m_shaders = nullptr;
|
||||
|
||||
friend class CommandScheduler;
|
||||
};
|
||||
|
||||
class RenderExecutor {
|
||||
@@ -170,67 +129,69 @@ public:
|
||||
explicit RenderExecutor(RenderContext& context): m_context(context) {}
|
||||
KYTY_CLASS_NO_COPY(RenderExecutor);
|
||||
|
||||
void DrawIndex(uint64_t submit_id, RenderCommandBuffer& buffer, uint32_t index_type_and_size,
|
||||
void DrawIndex(uint64_t submit_id, CommandBuffer& buffer, uint32_t index_type_and_size,
|
||||
uint32_t index_count, const void* index_addr, uint32_t flags, uint32_t type,
|
||||
uint32_t instance_count = 1, uint32_t render_target_slice_offset = 0,
|
||||
int32_t vertex_offset_add = 0, uint32_t first_instance = 0);
|
||||
void DrawAuto(uint64_t submit_id, RenderCommandBuffer& buffer, uint32_t index_count,
|
||||
uint32_t flags, uint32_t render_target_slice_offset = 0,
|
||||
uint32_t instance_count = 1, uint32_t first_vertex = 0,
|
||||
uint32_t first_instance = 0);
|
||||
void DispatchDirect(uint64_t submit_id, RenderCommandBuffer& buffer, uint32_t thread_group_x,
|
||||
void DrawAuto(uint64_t submit_id, CommandBuffer& buffer, uint32_t index_count, uint32_t flags,
|
||||
uint32_t render_target_slice_offset = 0, uint32_t instance_count = 1,
|
||||
uint32_t first_vertex = 0, uint32_t first_instance = 0);
|
||||
void DispatchDirect(uint64_t submit_id, CommandBuffer& buffer, uint32_t thread_group_x,
|
||||
uint32_t thread_group_y, uint32_t thread_group_z, uint32_t mode);
|
||||
|
||||
[[nodiscard]] DescriptorCache::PreparedBindings
|
||||
PrepareBindings(CommandBuffer& buffer, const ShaderStageRuntime& runtime,
|
||||
vk::ShaderStageFlags shader_stage, DescriptorCache::Stage stage);
|
||||
void RebindBuffers(CommandBuffer& buffer, DescriptorCache::PreparedBindings& bindings);
|
||||
void RebindImages(CommandBuffer& buffer, DescriptorCache::PreparedBindings& bindings);
|
||||
[[nodiscard]] PreparedBindings PrepareBindings(const ShaderStageRuntime& runtime);
|
||||
void FindBuffers(PreparedBindings& bindings);
|
||||
void RebindBuffers(PreparedBindings& bindings);
|
||||
void RebindImages(PreparedBindings& bindings);
|
||||
void CommitBindings(CommandBuffer& buffer, vk::PipelineBindPoint pipeline_bind_point,
|
||||
vk::PipelineLayout layout, DescriptorCache::PreparedBindings& bindings);
|
||||
const PipelineCache::Pipeline& pipeline,
|
||||
std::span<PreparedBindings* const> bindings);
|
||||
|
||||
private:
|
||||
struct GraphicsBindings {
|
||||
DescriptorCache::PreparedBindings vertex;
|
||||
std::optional<DescriptorCache::PreparedBindings> pixel;
|
||||
PreparedBindings vertex;
|
||||
std::optional<PreparedBindings> pixel;
|
||||
};
|
||||
|
||||
[[nodiscard]] DescriptorCache::TextureBinding
|
||||
ResolveTexture(const ShaderRecompiler::IR::ImageResource& resource,
|
||||
const ShaderRecompiler::IR::DescriptorValue& value);
|
||||
[[nodiscard]] GraphicsBindings PrepareGraphicsBindings(CommandBuffer& buffer,
|
||||
const ShaderStageRuntime& vertex,
|
||||
[[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 ResolveRenderColorTarget(uint64_t submit_id, RenderCommandBuffer& buffer,
|
||||
void ResolveRenderColorTarget(uint64_t submit_id, CommandBuffer& buffer,
|
||||
RenderColorInfo& target, uint32_t render_target_slice_offset = 0,
|
||||
uint32_t render_target_slot = UINT32_MAX,
|
||||
bool ignore_target_mask = false, bool exact_format = false);
|
||||
void ResolveRenderDepthTarget(uint64_t submit_id, RenderCommandBuffer& buffer,
|
||||
void ResolveRenderDepthTarget(uint64_t submit_id, CommandBuffer& buffer,
|
||||
RenderDepthInfo& target);
|
||||
[[nodiscard]] bool PrepareDrawRenderState(uint64_t submit_id, RenderCommandBuffer& buffer,
|
||||
[[nodiscard]] bool PrepareDrawRenderState(uint64_t submit_id, CommandBuffer& buffer,
|
||||
const DrawCallInfo& draw,
|
||||
uint32_t render_target_slice_offset,
|
||||
bool log_setup_phases, DrawRenderState& state);
|
||||
void ExecutePreparedDraw(uint64_t submit_id, RenderCommandBuffer& buffer,
|
||||
const DrawCallInfo& draw, DrawRenderState& state,
|
||||
vk::PrimitiveTopology topology, const DrawEmitInfo& emit,
|
||||
const DrawIndexBufferSource& index_source,
|
||||
bool primitive_restart_enable, bool log_pipeline_phase,
|
||||
bool set_bind_debug, bool set_auto_debug);
|
||||
void ExecutePreparedDraw(uint64_t submit_id, CommandBuffer& buffer, const DrawCallInfo& draw,
|
||||
DrawRenderState& state, vk::PrimitiveTopology topology,
|
||||
const DrawEmitInfo& emit, const DrawIndexBufferSource& index_source,
|
||||
bool primitive_restart_enable, bool log_pipeline_phase,
|
||||
bool set_bind_debug, bool set_auto_debug);
|
||||
[[nodiscard]] RenderState AcquireRenderTargets(CommandBuffer& buffer, RenderColorInfo* colors,
|
||||
uint32_t color_count, RenderDepthInfo& depth);
|
||||
[[nodiscard]] bool ResolveColorTargets(uint64_t submit_id, RenderCommandBuffer& buffer,
|
||||
[[nodiscard]] bool ResolveColorTargets(uint64_t submit_id, CommandBuffer& buffer,
|
||||
uint32_t render_target_slice_offset);
|
||||
void BindImage(ImageId id, bool storage);
|
||||
void BindRenderTarget(ImageId id);
|
||||
void TrackImageBinding(ImageId id);
|
||||
void ResetBindings();
|
||||
[[nodiscard]] bool TryConsumeComputeMetaClear(const ShaderComputeInputInfo& input,
|
||||
const RenderCommandBuffer& buffer);
|
||||
const CommandBuffer& buffer);
|
||||
|
||||
RenderContext& m_context;
|
||||
std::vector<std::shared_ptr<Image>> m_bound_images;
|
||||
RenderContext& m_context;
|
||||
std::vector<ImageId> m_bound_images;
|
||||
std::vector<vk::DescriptorBufferInfo> m_descriptor_buffers;
|
||||
std::vector<vk::DescriptorImageInfo> m_descriptor_images;
|
||||
std::vector<vk::WriteDescriptorSet> m_descriptor_writes;
|
||||
std::vector<uint32_t> m_image_occurrences;
|
||||
std::array<uint32_t, ShaderRecompiler::IR::NativePushConstantSize / sizeof(uint32_t)>
|
||||
m_push_constants {};
|
||||
|
||||
friend struct RenderExecutorTestAccess;
|
||||
};
|
||||
|
||||
@@ -11,11 +11,9 @@
|
||||
#include "graphics/guest_gpu/hardwareContext.h"
|
||||
#include "graphics/host_gpu/graphicContext.h"
|
||||
#include "graphics/host_gpu/renderer/image/imageInfo.h"
|
||||
#include "graphics/host_gpu/renderer/pipeline/descriptorCache.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/pipeline/shaderSubgroup.h"
|
||||
#include "graphics/host_gpu/renderer/render.h"
|
||||
#include "graphics/host_gpu/renderer/renderContext.h"
|
||||
#include "graphics/host_gpu/vulkanCommon.h"
|
||||
@@ -46,7 +44,7 @@ static uint64_t BufferDescriptorSize(const ShaderBufferResource& descriptor) {
|
||||
}
|
||||
|
||||
bool RenderExecutor::TryConsumeComputeMetaClear(const ShaderComputeInputInfo& input,
|
||||
const RenderCommandBuffer& buffer) {
|
||||
const CommandBuffer& buffer) {
|
||||
const auto& program = *input.stage.program;
|
||||
const auto& resources = *input.stage.resources;
|
||||
if (resources.buffers.size() != program.info.buffers.size()) {
|
||||
@@ -83,9 +81,8 @@ bool ResolveComputeImageClear(const ShaderComputeInputInfo& input, uint32_t grou
|
||||
const auto& program = *input.stage.program;
|
||||
const auto& resources = *input.stage.resources;
|
||||
if (program.info.buffers.size() != 1 || resources.buffers.size() != 1 ||
|
||||
!program.info.images.empty() || !program.info.samplers.empty() ||
|
||||
!program.info.addresses.empty() || !resources.images.empty() ||
|
||||
!resources.samplers.empty() || !resources.addresses.empty()) {
|
||||
!program.info.images.empty() || !program.info.samplers.empty() || program.info.uses_dma ||
|
||||
!resources.images.empty() || !resources.samplers.empty()) {
|
||||
return false;
|
||||
}
|
||||
const auto& resource = program.info.buffers.front();
|
||||
@@ -165,10 +162,11 @@ static bool TryConsumeComputeImageClear(const ShaderComputeInputInfo& input, Com
|
||||
return true;
|
||||
}
|
||||
|
||||
void RenderExecutor::DispatchDirect(uint64_t submit_id, RenderCommandBuffer& buffer,
|
||||
void RenderExecutor::DispatchDirect(uint64_t submit_id, CommandBuffer& buffer,
|
||||
uint32_t thread_group_x, uint32_t thread_group_y,
|
||||
uint32_t thread_group_z, uint32_t mode) {
|
||||
EXIT_IF(buffer.IsInvalid());
|
||||
m_context.GetCommandScheduler().PopPendingOperations();
|
||||
auto& ctx = buffer.GetRegisters();
|
||||
auto& sh_ctx = buffer.GetShaders();
|
||||
|
||||
@@ -209,16 +207,12 @@ void RenderExecutor::DispatchDirect(uint64_t submit_id, RenderCommandBuffer& buf
|
||||
const auto& cs_regs = sh_ctx.GetCs();
|
||||
const auto& sh_regs = ctx.GetShaderRegisters();
|
||||
|
||||
ShaderComputeInputInfo input_info {};
|
||||
std::span<const uint32_t> cs_shader;
|
||||
if (!ShaderCompileInfoCS(cs_regs, sh_regs, input_info, cs_shader)) {
|
||||
EXIT("ShaderCompileInfoCS failed for dispatch with CS shader 0x%016" PRIx64 "\n",
|
||||
cs_regs.cs_regs.data_addr);
|
||||
}
|
||||
|
||||
ShaderComputeInputInfo input_info {};
|
||||
const bool use_thread_dimensions = (mode & DISPATCH_INITIATOR_USE_THREAD_DIMENSIONS) != 0;
|
||||
input_info.dispatch_thread_dimensions = use_thread_dimensions;
|
||||
const auto compute_program =
|
||||
m_context.GetPipelineCache().GetComputeProgram(cs_regs, sh_regs, input_info);
|
||||
if (use_thread_dimensions) {
|
||||
input_info.dispatch_thread_dimensions = true;
|
||||
input_info.dispatch_threads_num[0] = thread_group_x;
|
||||
input_info.dispatch_threads_num[1] = thread_group_y;
|
||||
input_info.dispatch_threads_num[2] = thread_group_z;
|
||||
@@ -333,14 +327,19 @@ void RenderExecutor::DispatchDirect(uint64_t submit_id, RenderCommandBuffer& buf
|
||||
|
||||
buffer.EndRendering();
|
||||
auto& pipeline =
|
||||
m_context.GetPipelineCache().CreateComputePipeline(input_info, sh_ctx.GetCs(), cs_shader);
|
||||
auto bindings = PrepareBindings(buffer, input_info.stage, vk::ShaderStageFlagBits::eCompute,
|
||||
DescriptorCache::Stage::Compute);
|
||||
RebindBuffers(buffer, bindings);
|
||||
RebindImages(buffer, bindings);
|
||||
m_context.GetPipelineCache().CreateComputePipeline(input_info, compute_program);
|
||||
auto bindings = PrepareBindings(input_info.stage);
|
||||
FindBuffers(bindings);
|
||||
if (program.info.uses_dma) {
|
||||
m_context.GetGpuResources().PrepareBda();
|
||||
}
|
||||
RebindBuffers(bindings);
|
||||
RebindImages(bindings);
|
||||
|
||||
auto vk_buffer = buffer.Handle();
|
||||
CommitBindings(buffer, vk::PipelineBindPoint::eCompute, pipeline.pipeline_layout, bindings);
|
||||
auto vk_buffer = buffer.Handle();
|
||||
PreparedBindings* descriptor_stage = &bindings;
|
||||
CommitBindings(buffer, vk::PipelineBindPoint::eCompute, pipeline,
|
||||
std::span {&descriptor_stage, 1u});
|
||||
bool has_storage_writes = HasShaderBufferWrites(input_info.stage);
|
||||
has_storage_writes =
|
||||
std::any_of(program.info.images.begin(), program.info.images.end(),
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
#include "common/logging/log.h"
|
||||
#include "graphics/guest_gpu/graphicsRun.h"
|
||||
#include "graphics/presentation/videoOut.h"
|
||||
#include "kernel/pthread.h"
|
||||
#include "libs/errno.h"
|
||||
|
||||
#include <algorithm>
|
||||
@@ -13,8 +12,9 @@ namespace Libs::Graphics {
|
||||
|
||||
RenderContext::RenderContext(GraphicContext& graphics)
|
||||
: m_graphics(graphics), m_render_executor(*this), m_command_scheduler(*this, graphics),
|
||||
m_descriptor_cache(graphics), m_pipeline_cache(graphics, m_descriptor_cache),
|
||||
m_sampler_cache(graphics), m_gpu_resources(graphics, m_command_scheduler) {
|
||||
m_descriptor_heap(graphics, m_command_scheduler.GetMasterSemaphore()),
|
||||
m_pipeline_cache(graphics), m_sampler_cache(graphics),
|
||||
m_gpu_resources(graphics, m_command_scheduler) {
|
||||
EXIT_NOT_IMPLEMENTED(!Common::Thread::IsMainThread());
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ void RenderContext::ShutdownGpu() {
|
||||
}
|
||||
if (m_video_out != nullptr) {
|
||||
if (m_command_scheduler.Active()) {
|
||||
m_command_scheduler.FinishCurrent();
|
||||
m_command_scheduler.Finish();
|
||||
}
|
||||
m_command_scheduler.DrainPriorityOperations();
|
||||
m_video_out = nullptr;
|
||||
@@ -55,60 +55,54 @@ VideoOut::VideoOutDriver& RenderContext::GetVideoOut() const {
|
||||
return *m_video_out;
|
||||
}
|
||||
|
||||
void RenderContext::AddEopEq(LibKernel::EventQueue::KernelEqueue eq, int id) {
|
||||
auto queue = LibKernel::EventQueue::KernelPinEqueue(eq);
|
||||
if (!queue) {
|
||||
void RenderContext::AddInterruptEq(LibKernel::EventQueue::KernelEqueue eq, int event_id) {
|
||||
Common::LockGuard lock(m_interrupt_mutex);
|
||||
|
||||
auto it = std::find_if(
|
||||
m_interrupt_eqs.begin(), m_interrupt_eqs.end(),
|
||||
[eq, event_id](const auto& entry) { return entry.eq == eq && entry.event_id == event_id; });
|
||||
if (it != m_interrupt_eqs.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Common::LockGuard lock(m_eop_mutex);
|
||||
|
||||
auto it = std::find_if(m_eop_eqs.begin(), m_eop_eqs.end(), [eq, id](const auto& entry) {
|
||||
return entry.eq == eq && entry.id == id;
|
||||
});
|
||||
if (it != m_eop_eqs.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_eop_eqs.push_back({eq, std::move(queue), id});
|
||||
m_interrupt_eqs.push_back({eq, event_id});
|
||||
}
|
||||
|
||||
void RenderContext::DeleteEopEq(LibKernel::EventQueue::KernelEqueue eq, int id) {
|
||||
Common::LockGuard lock(m_eop_mutex);
|
||||
void RenderContext::DeleteInterruptEq(LibKernel::EventQueue::KernelEqueue eq, int event_id) {
|
||||
Common::LockGuard lock(m_interrupt_mutex);
|
||||
|
||||
auto it = std::find_if(m_eop_eqs.begin(), m_eop_eqs.end(), [eq, id](const auto& entry) {
|
||||
return entry.eq == eq && entry.id == id;
|
||||
});
|
||||
if (it == m_eop_eqs.end()) {
|
||||
auto it = std::find_if(
|
||||
m_interrupt_eqs.begin(), m_interrupt_eqs.end(),
|
||||
[eq, event_id](const auto& entry) { return entry.eq == eq && entry.event_id == event_id; });
|
||||
if (it == m_interrupt_eqs.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_eop_eqs.erase(it);
|
||||
m_interrupt_eqs.erase(it);
|
||||
}
|
||||
|
||||
void RenderContext::TriggerEopEvent(uint32_t context_id) {
|
||||
std::vector<EopEqRegistration> registrations;
|
||||
void RenderContext::TriggerInterrupt(int event_id, uint32_t context_id) {
|
||||
std::vector<InterruptEqRegistration> registrations;
|
||||
{
|
||||
Common::LockGuard lock(m_eop_mutex);
|
||||
registrations = m_eop_eqs;
|
||||
Common::LockGuard lock(m_interrupt_mutex);
|
||||
for (const auto& registration: m_interrupt_eqs) {
|
||||
if (registration.event_id == event_id) {
|
||||
registrations.push_back(registration);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& registration: registrations) {
|
||||
const auto result = LibKernel::EventQueue::KernelTriggerEvent(
|
||||
registration.eq, static_cast<uintptr_t>(registration.id),
|
||||
registration.eq, static_cast<uintptr_t>(registration.event_id),
|
||||
LibKernel::EventQueue::KERNEL_EVFILT_GRAPHICS,
|
||||
reinterpret_cast<void*>(static_cast<uintptr_t>(context_id)));
|
||||
if (result == LibKernel::KERNEL_ERROR_EBADF || result == LibKernel::KERNEL_ERROR_ENOENT) {
|
||||
DeleteEopEq(registration.eq, registration.id);
|
||||
DeleteInterruptEq(registration.eq, registration.event_id);
|
||||
continue;
|
||||
}
|
||||
EXIT_NOT_IMPLEMENTED(result != OK);
|
||||
}
|
||||
|
||||
auto tsc = LibKernel::KernelReadTsc();
|
||||
auto result = LibKernel::EventQueue::KernelTriggerUserEventForAll(AGC_USER_INTERRUPT_EVENT,
|
||||
reinterpret_cast<void*>(tsc));
|
||||
EXIT_NOT_IMPLEMENTED(result != OK && result != LibKernel::KERNEL_ERROR_ENOENT);
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
@@ -10,7 +10,7 @@
|
||||
#include "graphics/host_gpu/renderer/cache/samplerCache.h"
|
||||
#include "graphics/host_gpu/renderer/cache/textureCache.h"
|
||||
#include "graphics/host_gpu/renderer/commandScheduler.h"
|
||||
#include "graphics/host_gpu/renderer/pipeline/descriptorCache.h"
|
||||
#include "graphics/host_gpu/renderer/pipeline/descriptorHeap.h"
|
||||
#include "graphics/host_gpu/renderer/pipeline/pipelineCache.h"
|
||||
#include "kernel/eventQueue.h"
|
||||
|
||||
@@ -23,7 +23,6 @@ class VideoOutDriver;
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
constexpr int AGC_USER_INTERRUPT_EVENT = 0x1800;
|
||||
class GuestGpu;
|
||||
|
||||
class RenderContext {
|
||||
@@ -41,37 +40,36 @@ public:
|
||||
Common::Mutex& GetMutex() { return m_mutex; }
|
||||
CommandScheduler& GetCommandScheduler() { return m_command_scheduler; }
|
||||
PipelineCache& GetPipelineCache() { return m_pipeline_cache; }
|
||||
DescriptorCache& GetDescriptorCache() { return m_descriptor_cache; }
|
||||
DescriptorHeap& GetDescriptorHeap() { return m_descriptor_heap; }
|
||||
SamplerCache& GetSamplerCache() { return m_sampler_cache; }
|
||||
GpuResourceManager& GetGpuResources() { return m_gpu_resources; }
|
||||
BufferCache& GetBufferCache() { return m_gpu_resources.GetBufferCache(); }
|
||||
TextureCache& GetTextureCache() { return m_gpu_resources.GetTextureCache(); }
|
||||
RenderExecutor& GetRenderExecutor() { return m_render_executor; }
|
||||
|
||||
void AddEopEq(LibKernel::EventQueue::KernelEqueue eq, int id);
|
||||
void DeleteEopEq(LibKernel::EventQueue::KernelEqueue eq, int id);
|
||||
void TriggerEopEvent(uint32_t context_id);
|
||||
void AddInterruptEq(LibKernel::EventQueue::KernelEqueue eq, int event_id);
|
||||
void DeleteInterruptEq(LibKernel::EventQueue::KernelEqueue eq, int event_id);
|
||||
void TriggerInterrupt(int event_id, uint32_t context_id);
|
||||
|
||||
private:
|
||||
struct EopEqRegistration {
|
||||
LibKernel::EventQueue::KernelEqueue eq = LibKernel::EventQueue::KERNEL_EQUEUE_INVALID;
|
||||
LibKernel::EventQueue::KernelEqueueRef queue;
|
||||
int id = 0;
|
||||
struct InterruptEqRegistration {
|
||||
LibKernel::EventQueue::KernelEqueue eq = LibKernel::EventQueue::KERNEL_EQUEUE_INVALID;
|
||||
int event_id = 0;
|
||||
};
|
||||
|
||||
GraphicContext& m_graphics;
|
||||
Common::Mutex m_mutex;
|
||||
RenderExecutor m_render_executor;
|
||||
CommandScheduler m_command_scheduler;
|
||||
DescriptorCache m_descriptor_cache;
|
||||
DescriptorHeap m_descriptor_heap;
|
||||
PipelineCache m_pipeline_cache;
|
||||
SamplerCache m_sampler_cache;
|
||||
GpuResourceManager m_gpu_resources;
|
||||
std::unique_ptr<GuestGpu> m_gpu;
|
||||
VideoOut::VideoOutDriver* m_video_out = nullptr;
|
||||
|
||||
Common::Mutex m_eop_mutex;
|
||||
std::vector<EopEqRegistration> m_eop_eqs;
|
||||
Common::Mutex m_interrupt_mutex;
|
||||
std::vector<InterruptEqRegistration> m_interrupt_eqs;
|
||||
};
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
@@ -15,10 +15,8 @@
|
||||
#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/pipeline/descriptorCache.h"
|
||||
#include "graphics/host_gpu/renderer/pipeline/pipelineCache.h"
|
||||
#include "graphics/host_gpu/renderer/pipeline/shaderResourceBarrier.h"
|
||||
#include "graphics/host_gpu/renderer/pipeline/shaderSubgroup.h"
|
||||
#include "graphics/host_gpu/renderer/render.h"
|
||||
#include "graphics/host_gpu/renderer/renderContext.h"
|
||||
#include "graphics/host_gpu/vulkanCommon.h"
|
||||
@@ -105,7 +103,7 @@ static bool IsDualSourceBlendFactor(uint32_t factor) {
|
||||
}
|
||||
|
||||
static void LogFramebufferSkip(const char* draw_name, const RenderColorInfo& color,
|
||||
const RenderDepthInfo& depth, const RenderCommandBuffer& buffer,
|
||||
const RenderDepthInfo& depth, const CommandBuffer& buffer,
|
||||
uint32_t index_count, uint32_t flags) {
|
||||
const auto& ctx = buffer.GetRegisters();
|
||||
const auto& ucfg = buffer.GetUserConfig();
|
||||
@@ -128,7 +126,7 @@ static void LogFramebufferSkip(const char* draw_name, const RenderColorInfo& col
|
||||
static_cast<uint32_t>(ucfg.GetPrimType()), index_count, flags);
|
||||
}
|
||||
|
||||
static void LogMrtState(const char* draw_name, const RenderCommandBuffer& buffer,
|
||||
static void LogMrtState(const char* draw_name, const CommandBuffer& buffer,
|
||||
const ShaderPixelInputInfo& ps_input_info) {
|
||||
const auto& ctx = buffer.GetRegisters();
|
||||
const auto& sh_regs = ctx.GetShaderRegisters();
|
||||
@@ -142,18 +140,6 @@ static void LogMrtState(const char* draw_name, const RenderCommandBuffer& buffer
|
||||
(bc0.separate_alpha_blend && (IsDualSourceBlendFactor(bc0.alpha_srcblend) ||
|
||||
IsDualSourceBlendFactor(bc0.alpha_destblend)));
|
||||
|
||||
for (uint32_t i = 1; i < 8; i++) {
|
||||
const auto& rt = ctx.GetRenderTarget(i);
|
||||
if (rt.base.addr != 0 || ps_input_info.target_output_mode[i] != 0 ||
|
||||
((rt_mask >> (i * 4u)) & 0x0fu) != 0 || ((cb_shader_mask >> (i * 4u)) & 0x0fu) != 0) {
|
||||
interesting = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!interesting) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto log_id = g_mrt_state_log_count.fetch_add(1);
|
||||
if (log_id >= 32) {
|
||||
return;
|
||||
@@ -191,7 +177,7 @@ static void LogMrtState(const char* draw_name, const RenderCommandBuffer& buffer
|
||||
}
|
||||
|
||||
static void LogDrawTargetState(const char* draw_name, const RenderColorInfo& color,
|
||||
const RenderDepthInfo& depth, const RenderCommandBuffer& buffer,
|
||||
const RenderDepthInfo& depth, const CommandBuffer& buffer,
|
||||
const ShaderPixelInputInfo& ps_input_info, uint32_t index_count,
|
||||
uint32_t flags) {
|
||||
const auto& ctx = buffer.GetRegisters();
|
||||
@@ -245,7 +231,7 @@ static void LogDrawTargetState(const char* draw_name, const RenderColorInfo& col
|
||||
LogMrtState(draw_name, buffer, ps_input_info);
|
||||
}
|
||||
|
||||
static void LogDrawInputState(const RenderCommandBuffer& buffer, const RenderColorInfo& color,
|
||||
static void LogDrawInputState(const CommandBuffer& buffer, const RenderColorInfo& color,
|
||||
const ShaderVertexInputInfo& vs_input_info,
|
||||
uint32_t index_type_and_size, uint32_t index_count,
|
||||
const void* index_addr) {
|
||||
@@ -323,17 +309,14 @@ static void LogDrawInputState(const RenderCommandBuffer& buffer, const RenderCol
|
||||
}
|
||||
}
|
||||
|
||||
static PipelineDynamicParameters BuildGraphicsDynamicParams(const RenderCommandBuffer& buffer,
|
||||
const RenderColorInfo* colors,
|
||||
uint32_t color_count,
|
||||
const RenderDepthInfo& depth) {
|
||||
static void SetGraphicsDynamicParams(const CommandBuffer& buffer, vk::CommandBuffer vk_buffer,
|
||||
const RenderColorInfo* colors, uint32_t color_count,
|
||||
const RenderDepthInfo& depth) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
|
||||
EXIT_IF(colors == nullptr);
|
||||
const auto& ctx = buffer.GetRegisters();
|
||||
|
||||
PipelineDynamicParameters ret {};
|
||||
ret.color_write_count = color_count;
|
||||
ret.stencil_test_enable = depth.stencil_test_enable;
|
||||
|
||||
const auto& vp = ctx.GetScreenViewport();
|
||||
vk::Extent2D framebuffer_extent {};
|
||||
if (color_count > 0 && colors[0].image_id) {
|
||||
@@ -347,68 +330,22 @@ static PipelineDynamicParameters BuildGraphicsDynamicParams(const RenderCommandB
|
||||
|
||||
const auto final_scissor = calc_final_scissor(vp, ctx.GetScanModeControl(), framebuffer_extent);
|
||||
|
||||
ret.viewport_scale[0] = vp.viewports[0].xscale;
|
||||
ret.viewport_scale[1] = vp.viewports[0].yscale;
|
||||
ret.viewport_scale[2] = vp.viewports[0].zscale;
|
||||
ret.viewport_offset[0] = vp.viewports[0].xoffset;
|
||||
ret.viewport_offset[1] = vp.viewports[0].yoffset;
|
||||
ret.viewport_offset[2] = vp.viewports[0].zoffset;
|
||||
ret.scissor_ltrb[0] = final_scissor.left;
|
||||
ret.scissor_ltrb[1] = final_scissor.top;
|
||||
ret.scissor_ltrb[2] = final_scissor.right;
|
||||
ret.scissor_ltrb[3] = final_scissor.bottom;
|
||||
ret.line_width = ctx.GetLineWidth();
|
||||
ret.stencil_front = depth.stencil_dynamic_front;
|
||||
ret.stencil_back = depth.stencil_dynamic_back;
|
||||
|
||||
const auto& mode = ctx.GetModeControl();
|
||||
const auto& poly_offset = ctx.GetPolyOffset();
|
||||
const bool use_front = mode.poly_offset_front_enable && !mode.cull_front;
|
||||
const bool use_back = mode.poly_offset_back_enable && !mode.cull_back;
|
||||
ret.depth_bias_enable = use_front || use_back;
|
||||
if (ret.depth_bias_enable) {
|
||||
// Vulkan has one bias for both faces. Prefer a visible front face when both are enabled.
|
||||
const float guest_constant_factor =
|
||||
use_front ? poly_offset.front_offset : poly_offset.back_offset;
|
||||
ret.depth_bias_constant_factor =
|
||||
ConvertPolygonOffsetConstantFactor(guest_constant_factor, poly_offset, depth.format);
|
||||
ret.depth_bias_clamp = poly_offset.clamp;
|
||||
ret.depth_bias_slope_factor =
|
||||
(use_front ? poly_offset.front_scale : poly_offset.back_scale) / 16.0f;
|
||||
}
|
||||
|
||||
// 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 < RENDER_COLOR_ATTACHMENTS_MAX; i++) {
|
||||
ret.color_write_enable[i] =
|
||||
(i < color_count &&
|
||||
render_target_mask_slot(ctx.GetRenderTargetMask(), colors[i].target_slot) != 0);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void SetDynamicParams(const RenderCommandBuffer& buffer, vk::CommandBuffer vk_buffer,
|
||||
const PipelineDynamicParameters& dynamic_params) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
|
||||
vk::Viewport viewport {};
|
||||
viewport.x = dynamic_params.viewport_offset[0] - dynamic_params.viewport_scale[0];
|
||||
viewport.y = dynamic_params.viewport_offset[1] - dynamic_params.viewport_scale[1];
|
||||
viewport.width = dynamic_params.viewport_scale[0] * 2.0f;
|
||||
viewport.height = dynamic_params.viewport_scale[1] * 2.0f;
|
||||
viewport.minDepth = dynamic_params.viewport_offset[2];
|
||||
viewport.maxDepth = dynamic_params.viewport_scale[2] + dynamic_params.viewport_offset[2];
|
||||
viewport.x = vp.viewports[0].xoffset - vp.viewports[0].xscale;
|
||||
viewport.y = vp.viewports[0].yoffset - vp.viewports[0].yscale;
|
||||
viewport.width = vp.viewports[0].xscale * 2.0f;
|
||||
viewport.height = vp.viewports[0].yscale * 2.0f;
|
||||
viewport.minDepth = vp.viewports[0].zoffset;
|
||||
viewport.maxDepth = vp.viewports[0].zscale + vp.viewports[0].zoffset;
|
||||
vk_buffer.setViewport(0, 1, &viewport);
|
||||
|
||||
vk::Rect2D scissor {};
|
||||
scissor.offset = {dynamic_params.scissor_ltrb[0], dynamic_params.scissor_ltrb[1]};
|
||||
scissor.extent = {
|
||||
static_cast<uint32_t>(dynamic_params.scissor_ltrb[2] - dynamic_params.scissor_ltrb[0]),
|
||||
static_cast<uint32_t>(dynamic_params.scissor_ltrb[3] - dynamic_params.scissor_ltrb[1])};
|
||||
scissor.offset = {final_scissor.left, final_scissor.top};
|
||||
scissor.extent = {static_cast<uint32_t>(final_scissor.right - final_scissor.left),
|
||||
static_cast<uint32_t>(final_scissor.bottom - final_scissor.top)};
|
||||
vk_buffer.setScissor(0, 1, &scissor);
|
||||
|
||||
float line_width = dynamic_params.line_width;
|
||||
float line_width = ctx.GetLineWidth();
|
||||
if (line_width != 1.0f) {
|
||||
static bool logged = false;
|
||||
if (!logged) {
|
||||
@@ -421,26 +358,36 @@ static void SetDynamicParams(const RenderCommandBuffer& buffer, vk::CommandBuffe
|
||||
}
|
||||
vk_buffer.setLineWidth(line_width);
|
||||
|
||||
vk_buffer.setDepthBiasEnable(dynamic_params.depth_bias_enable ? VK_TRUE : VK_FALSE);
|
||||
if (dynamic_params.depth_bias_enable) {
|
||||
vk_buffer.setDepthBias(dynamic_params.depth_bias_constant_factor,
|
||||
dynamic_params.depth_bias_clamp,
|
||||
dynamic_params.depth_bias_slope_factor);
|
||||
const auto& mode = ctx.GetModeControl();
|
||||
const auto& poly_offset = ctx.GetPolyOffset();
|
||||
const bool use_front = mode.poly_offset_front_enable && !mode.cull_front;
|
||||
const bool use_back = mode.poly_offset_back_enable && !mode.cull_back;
|
||||
const bool depth_bias_enable = use_front || use_back;
|
||||
vk_buffer.setDepthBiasEnable(depth_bias_enable ? VK_TRUE : VK_FALSE);
|
||||
if (depth_bias_enable) {
|
||||
// Vulkan has one bias for both faces. Prefer a visible front face when both are enabled.
|
||||
const float guest_constant_factor =
|
||||
use_front ? poly_offset.front_offset : poly_offset.back_offset;
|
||||
const float constant_factor =
|
||||
ConvertPolygonOffsetConstantFactor(guest_constant_factor, poly_offset, depth.format);
|
||||
const float slope_factor =
|
||||
(use_front ? poly_offset.front_scale : poly_offset.back_scale) / 16.0f;
|
||||
vk_buffer.setDepthBias(constant_factor, poly_offset.clamp, slope_factor);
|
||||
}
|
||||
|
||||
if (dynamic_params.stencil_test_enable) {
|
||||
if (depth.stencil_test_enable) {
|
||||
vk_buffer.setStencilCompareMask(vk::StencilFaceFlagBits::eFront,
|
||||
dynamic_params.stencil_front.compareMask);
|
||||
depth.stencil_dynamic_front.compareMask);
|
||||
vk_buffer.setStencilCompareMask(vk::StencilFaceFlagBits::eBack,
|
||||
dynamic_params.stencil_back.compareMask);
|
||||
depth.stencil_dynamic_back.compareMask);
|
||||
vk_buffer.setStencilWriteMask(vk::StencilFaceFlagBits::eFront,
|
||||
dynamic_params.stencil_front.writeMask);
|
||||
depth.stencil_dynamic_front.writeMask);
|
||||
vk_buffer.setStencilWriteMask(vk::StencilFaceFlagBits::eBack,
|
||||
dynamic_params.stencil_back.writeMask);
|
||||
depth.stencil_dynamic_back.writeMask);
|
||||
vk_buffer.setStencilReference(vk::StencilFaceFlagBits::eFront,
|
||||
dynamic_params.stencil_front.reference);
|
||||
depth.stencil_dynamic_front.reference);
|
||||
vk_buffer.setStencilReference(vk::StencilFaceFlagBits::eBack,
|
||||
dynamic_params.stencil_back.reference);
|
||||
depth.stencil_dynamic_back.reference);
|
||||
}
|
||||
|
||||
#if defined(__APPLE__)
|
||||
@@ -448,11 +395,15 @@ static void SetDynamicParams(const RenderCommandBuffer& buffer, vk::CommandBuffe
|
||||
// eColorWriteEnableEXT dynamic state and relies on the static colorWriteMask instead.
|
||||
#else
|
||||
vk::Bool32 enable[RENDER_COLOR_ATTACHMENTS_MAX] = {};
|
||||
for (uint32_t i = 0; i < dynamic_params.color_write_count; i++) {
|
||||
enable[i] = (dynamic_params.color_write_enable[i] ? VK_TRUE : VK_FALSE);
|
||||
// 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;
|
||||
}
|
||||
if (dynamic_params.color_write_count != 0) {
|
||||
vk_buffer.setColorWriteEnableEXT(dynamic_params.color_write_count, enable);
|
||||
if (color_count != 0) {
|
||||
vk_buffer.setColorWriteEnableEXT(color_count, enable);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -460,7 +411,7 @@ static void SetDynamicParams(const RenderCommandBuffer& buffer, vk::CommandBuffe
|
||||
static bool DrawHasValidVertexShader(const HW::Shader& sh_ctx) {
|
||||
|
||||
const auto& vs = sh_ctx.GetVs();
|
||||
return vs.gs_regs.chksum != 0 && ShaderAddressValid(vs.es_regs.data_addr);
|
||||
return ShaderAddressValid(vs.es_regs.data_addr);
|
||||
}
|
||||
|
||||
static bool PixelShaderHasDepthOrCoverageSideEffects(const HW::ShaderRegisters& sh_regs) {
|
||||
@@ -470,7 +421,7 @@ static bool PixelShaderHasDepthOrCoverageSideEffects(const HW::ShaderRegisters&
|
||||
db.shader_execute_on_noop;
|
||||
}
|
||||
|
||||
static bool ShouldSkipGeShader(const RenderCommandBuffer& buffer) {
|
||||
static bool ShouldSkipGeShader(const CommandBuffer& buffer) {
|
||||
const auto& ctx = buffer.GetRegisters();
|
||||
const auto& ucfg = buffer.GetUserConfig();
|
||||
const auto& sh_ctx = buffer.GetShaders();
|
||||
@@ -492,22 +443,20 @@ static bool ShouldSkipGeShader(const RenderCommandBuffer& buffer) {
|
||||
};
|
||||
|
||||
const bool ps5_ngg_vertex_path = stages == 0x02002000 && vertex_info.es_regs.data_addr != 0 &&
|
||||
vertex_info.gs_regs.chksum != 0 &&
|
||||
sh_regs.m_vgtGsMaxVertOut == 0x00000000 &&
|
||||
is_known_gs_out_prim_type(sh_regs.m_vgtGsOutPrimType);
|
||||
|
||||
const bool unsupported_stage_mask = (stages != 0 && stages != 0x02002000);
|
||||
const bool unsupported_gs_stage = (vertex_info.es_regs.data_addr != 0 &&
|
||||
vertex_info.gs_regs.data_addr != 0 && !ps5_ngg_vertex_path);
|
||||
const bool ge_group_size =
|
||||
ge_cntl.primitive_group_size > 0x0040 || ge_cntl.vertex_group_size > 0x0040;
|
||||
// GE_CNTL group sizes control guest scheduling and do not constrain the host vertex path.
|
||||
const bool ge_shader_regs =
|
||||
(sh_regs.m_geNggSubgrpCntl != 0x00000000 && sh_regs.m_geNggSubgrpCntl != 0x00000001) ||
|
||||
sh_regs.m_vgtGsMaxVertOut != 0x00000000 ||
|
||||
!is_known_gs_out_prim_type(sh_regs.m_vgtGsOutPrimType) ||
|
||||
sh_regs.m_geMaxOutputPerSubgroup > 0x00000040;
|
||||
|
||||
if (unsupported_stage_mask || unsupported_gs_stage || ge_group_size || ge_shader_regs) {
|
||||
if (unsupported_stage_mask || unsupported_gs_stage || ge_shader_regs) {
|
||||
static std::once_flag warning_once;
|
||||
std::call_once(warning_once, [] {
|
||||
std::printf("Warning: game uses unsupported graphics pipelines; some draw calls were "
|
||||
@@ -532,15 +481,15 @@ static bool ShouldSkipGeShader(const RenderCommandBuffer& buffer) {
|
||||
}
|
||||
|
||||
struct DrawRenderState {
|
||||
RenderDepthInfo depth_info;
|
||||
RenderColorInfo color_info[RENDER_COLOR_ATTACHMENTS_MAX] = {};
|
||||
uint32_t color_count = 0;
|
||||
bool ps_active = true;
|
||||
RenderState rendering;
|
||||
ShaderVertexInputInfo vs_input_info;
|
||||
ShaderPixelInputInfo ps_input_info;
|
||||
std::span<const uint32_t> vs_shader;
|
||||
std::span<const uint32_t> ps_shader;
|
||||
RenderDepthInfo depth_info;
|
||||
RenderColorInfo color_info[RENDER_COLOR_ATTACHMENTS_MAX] = {};
|
||||
uint32_t color_count = 0;
|
||||
bool ps_active = true;
|
||||
RenderState rendering;
|
||||
ShaderVertexInputInfo vs_input_info;
|
||||
ShaderPixelInputInfo ps_input_info;
|
||||
ShaderProgram vertex_program;
|
||||
ShaderProgram pixel_program;
|
||||
};
|
||||
|
||||
struct DrawCallInfo {
|
||||
@@ -553,8 +502,7 @@ struct DrawCallInfo {
|
||||
};
|
||||
|
||||
static bool ResolveDccAttachmentClear(TextureCache& cache, const RenderColorInfo& target,
|
||||
const ImageViewInfo& view,
|
||||
vk::ClearColorValue& clear_value) {
|
||||
const ImageViewInfo& view, vk::ClearColorValue& clear_value) {
|
||||
if (target.desc.info.metadata.kind != ImageMetadataKind::Dcc) {
|
||||
return false;
|
||||
}
|
||||
@@ -630,7 +578,7 @@ RenderState RenderExecutor::AcquireRenderTargets(CommandBuffer& buffer, RenderCo
|
||||
for (uint32_t i = 0; i < color_count; i++) {
|
||||
auto& target = colors[i];
|
||||
EXIT_IF(!target.image_id);
|
||||
const auto old_image = cache.ResolveOwner(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) {
|
||||
@@ -641,6 +589,15 @@ RenderState RenderExecutor::AcquireRenderTargets(CommandBuffer& buffer, RenderCo
|
||||
}
|
||||
target.image_view = cache.FindRenderTarget(target.image_id, target.desc);
|
||||
auto& image = cache.GetImage(target.image_id);
|
||||
SetVulkanObjectNameF(m_context.GetGraphics().device, image.backing.image,
|
||||
"Kyty.MRT{}.Image[guest=0x{:016x} size=0x{:x} format={}]",
|
||||
target.target_slot, image.info.data.address, image.info.data.size,
|
||||
static_cast<uint32_t>(image.info.pixel_format));
|
||||
SetVulkanObjectNameF(m_context.GetGraphics().device, target.image_view,
|
||||
"Kyty.MRT{}.View[guest=0x{:016x} mip={} layer={}+{}]",
|
||||
target.target_slot, image.info.data.address,
|
||||
target.desc.view_info.base_level, target.desc.view_info.base_layer,
|
||||
target.desc.view_info.layer_count);
|
||||
EXIT_IF(image.backing.samples != target.samples || target.image_view == nullptr);
|
||||
if (attachment_samples == 0) {
|
||||
attachment_samples = target.samples;
|
||||
@@ -657,15 +614,15 @@ RenderState RenderExecutor::AcquireRenderTargets(CommandBuffer& buffer, RenderCo
|
||||
ImageSubresourceRange {view.base_level, view.level_count, view.base_layer,
|
||||
view.layer_count},
|
||||
buffer.Handle());
|
||||
state.width = std::min(state.width, target.extent.width);
|
||||
state.height = std::min(state.height, target.extent.height);
|
||||
state.num_layers = std::min(state.num_layers, view.layer_count);
|
||||
auto& attachment = state.color_attachments[i];
|
||||
attachment.image_view = target.image_view;
|
||||
attachment.image_layout = layout;
|
||||
attachment.clear_value = target.color_clear_value.uint32;
|
||||
state.width = std::min(state.width, target.extent.width);
|
||||
state.height = std::min(state.height, target.extent.height);
|
||||
state.num_layers = std::min(state.num_layers, view.layer_count);
|
||||
auto& attachment = state.color_attachments[i];
|
||||
attachment.image_view = target.image_view;
|
||||
attachment.image_layout = layout;
|
||||
attachment.clear_value = target.color_clear_value.uint32;
|
||||
vk::ClearColorValue metadata_clear_value {};
|
||||
const bool metadata_clear =
|
||||
const bool metadata_clear =
|
||||
ResolveDccAttachmentClear(cache, target, view, metadata_clear_value);
|
||||
if (metadata_clear) {
|
||||
attachment.clear_value = metadata_clear_value.uint32;
|
||||
@@ -673,7 +630,7 @@ RenderState RenderExecutor::AcquireRenderTargets(CommandBuffer& buffer, RenderCo
|
||||
attachment.is_clear = target.color_clear_enable || metadata_clear;
|
||||
}
|
||||
if (depth.image_id) {
|
||||
const auto owner = cache.ResolveOwner(depth.image_id);
|
||||
const auto owner = cache.m_slot_images.try_get(depth.image_id);
|
||||
if (owner == nullptr || !owner->registered || owner->binding.needs_rebind) {
|
||||
EXIT("depth target changed after render-state discovery\n");
|
||||
}
|
||||
@@ -690,6 +647,14 @@ RenderState RenderExecutor::AcquireRenderTargets(CommandBuffer& buffer, RenderCo
|
||||
EXIT("failed to consume HTile clear state\n");
|
||||
}
|
||||
auto& image = cache.GetImage(depth.image_id);
|
||||
SetVulkanObjectNameF(m_context.GetGraphics().device, image.backing.image,
|
||||
"Kyty.DepthTarget.Image[guest=0x{:016x} size=0x{:x} format={}]",
|
||||
image.info.data.address, image.info.data.size,
|
||||
static_cast<uint32_t>(image.info.pixel_format));
|
||||
SetVulkanObjectNameF(m_context.GetGraphics().device, depth.image_view,
|
||||
"Kyty.DepthTarget.View[guest=0x{:016x} layer={}+{}]",
|
||||
image.info.data.address, depth.desc.view_info.base_layer,
|
||||
depth.desc.view_info.layer_count);
|
||||
EXIT_IF(depth.image_view == nullptr || image.backing.samples != depth.samples);
|
||||
if (attachment_samples == 0) {
|
||||
attachment_samples = depth.samples;
|
||||
@@ -739,11 +704,12 @@ RenderState RenderExecutor::AcquireRenderTargets(CommandBuffer& buffer, RenderCo
|
||||
return state;
|
||||
}
|
||||
|
||||
static bool DrawHasActivePixelShader(const RenderCommandBuffer& buffer) {
|
||||
static bool DrawHasActivePixelShader(const CommandBuffer& buffer) {
|
||||
const auto& ctx = buffer.GetRegisters();
|
||||
const auto& sh_regs = ctx.GetShaderRegisters();
|
||||
const bool has_color_output = (ctx.GetRenderTargetMask() & sh_regs.m_cbShaderMask) != 0;
|
||||
return has_color_output || PixelShaderHasDepthOrCoverageSideEffects(sh_regs);
|
||||
return ShaderAddressValid(buffer.GetShaders().GetPs().ps_regs.data_addr) &&
|
||||
(has_color_output || PixelShaderHasDepthOrCoverageSideEffects(sh_regs));
|
||||
}
|
||||
|
||||
enum class CbColorMode : uint8_t {
|
||||
@@ -755,7 +721,7 @@ enum class CbColorMode : uint8_t {
|
||||
DccDecompress = 6,
|
||||
};
|
||||
|
||||
static bool ConsumeMetadataColorOperation(const RenderCommandBuffer& buffer) {
|
||||
static bool ConsumeMetadataColorOperation(const CommandBuffer& buffer) {
|
||||
const auto& ctx = buffer.GetRegisters();
|
||||
const auto mode = ctx.GetColorControl().mode;
|
||||
// These special modes run color-buffer metadata or decompression operations. The shader is a
|
||||
@@ -783,11 +749,10 @@ struct DrawIndexBufferSource {
|
||||
};
|
||||
|
||||
struct PreparedIndexBuffer {
|
||||
std::shared_ptr<void> owner;
|
||||
vk::Buffer buffer = nullptr;
|
||||
uint64_t size = 0;
|
||||
vk::DeviceSize offset = 0;
|
||||
vk::IndexType type = vk::IndexType::eUint16;
|
||||
vk::Buffer buffer = nullptr;
|
||||
uint64_t size = 0;
|
||||
vk::DeviceSize offset = 0;
|
||||
vk::IndexType type = vk::IndexType::eUint16;
|
||||
};
|
||||
|
||||
static uint64_t VertexBufferDescriptorSize(const ShaderVertexInputBuffer& buffer) {
|
||||
@@ -796,10 +761,10 @@ static uint64_t VertexBufferDescriptorSize(const ShaderVertexInputBuffer& buffer
|
||||
}
|
||||
|
||||
struct VertexBufferRange {
|
||||
uint64_t base_address = 0;
|
||||
uint64_t requested_end = 0;
|
||||
uint64_t acquired_end = 0;
|
||||
BufferBinding binding;
|
||||
uint64_t base_address = 0;
|
||||
uint64_t requested_end = 0;
|
||||
uint64_t acquired_end = 0;
|
||||
std::pair<Buffer*, uint64_t> binding;
|
||||
|
||||
[[nodiscard]] uint64_t RequestedSize() const { return requested_end - base_address; }
|
||||
};
|
||||
@@ -807,14 +772,12 @@ struct VertexBufferRange {
|
||||
struct PreparedVertexBuffers {
|
||||
static constexpr uint32_t MaxBuffers = ShaderVertexInputInfo::RES_MAX;
|
||||
|
||||
std::array<vk::Buffer, MaxBuffers> buffers {};
|
||||
std::array<vk::DeviceSize, MaxBuffers> offsets {};
|
||||
std::array<std::shared_ptr<void>, MaxBuffers> owners {};
|
||||
uint32_t count = 0;
|
||||
uint32_t owner_count = 0;
|
||||
std::array<vk::Buffer, MaxBuffers> buffers {};
|
||||
std::array<vk::DeviceSize, MaxBuffers> offsets {};
|
||||
uint32_t count = 0;
|
||||
};
|
||||
|
||||
static PreparedVertexBuffers AcquireVertexBuffers(RenderCommandBuffer& buffer,
|
||||
static PreparedVertexBuffers AcquireVertexBuffers(CommandBuffer& buffer,
|
||||
const ShaderVertexInputInfo& vs_input_info) {
|
||||
EXIT_IF(vs_input_info.buffers_num < 0 ||
|
||||
vs_input_info.buffers_num > ShaderVertexInputInfo::RES_MAX);
|
||||
@@ -861,21 +824,22 @@ static PreparedVertexBuffers AcquireVertexBuffers(RenderCommandBuffer& b
|
||||
const auto size =
|
||||
Libs::LibKernel::Memory::ClampRangeSize(range.base_address, range.RequestedSize());
|
||||
range.acquired_end = range.base_address + size;
|
||||
range.binding = cache.ObtainBuffer(buffer, range.base_address, size);
|
||||
range.binding = cache.ObtainBuffer(range.base_address, size, false);
|
||||
SetVulkanObjectNameF(
|
||||
buffer.GetContext().GetGraphics().device, range.binding.first->Handle(),
|
||||
"Kyty.VertexBufferRange[guest=0x{:016x} size=0x{:x}]", range.base_address, size);
|
||||
}
|
||||
|
||||
// Rebuild slot bindings, offsetting non-empty slots into their acquired merged range.
|
||||
PreparedVertexBuffers prepared;
|
||||
prepared.count = static_cast<uint32_t>(vs_input_info.buffers_num);
|
||||
std::shared_ptr<Buffer> null_owner;
|
||||
vk::Buffer null_buffer = nullptr;
|
||||
prepared.count = static_cast<uint32_t>(vs_input_info.buffers_num);
|
||||
vk::Buffer null_buffer = nullptr;
|
||||
for (int i = 0; i < vs_input_info.buffers_num; i++) {
|
||||
const auto& vertex = vs_input_info.buffers[i];
|
||||
const auto size = VertexBufferDescriptorSize(vertex);
|
||||
if (size == 0) {
|
||||
if (null_owner == nullptr) {
|
||||
null_owner = cache.ObtainNullBuffer();
|
||||
null_buffer = null_owner->Handle();
|
||||
if (null_buffer == nullptr) {
|
||||
null_buffer = cache.GetBuffer(NULL_BUFFER_ID).Handle();
|
||||
}
|
||||
prepared.buffers[i] = null_buffer;
|
||||
prepared.offsets[i] = 0;
|
||||
@@ -892,24 +856,19 @@ static PreparedVertexBuffers AcquireVertexBuffers(RenderCommandBuffer& b
|
||||
vertex.addr);
|
||||
}
|
||||
|
||||
prepared.buffers[i] = range->binding.buffer;
|
||||
prepared.offsets[i] = range->binding.offset + vertex.addr - range->base_address;
|
||||
prepared.buffers[i] = range->binding.first->Handle();
|
||||
prepared.offsets[i] = range->binding.second + vertex.addr - range->base_address;
|
||||
SetVulkanObjectNameF(
|
||||
buffer.GetContext().GetGraphics().device, prepared.buffers[i],
|
||||
"Kyty.VertexBuffer[slot={} guest=0x{:016x} size=0x{:x} stride={} records={}]", i,
|
||||
vertex.addr, size, vertex.stride, vertex.num_records);
|
||||
}
|
||||
|
||||
if (null_owner != nullptr) {
|
||||
prepared.owners[prepared.owner_count++] = std::move(null_owner);
|
||||
}
|
||||
for (uint32_t i = 0; i < merged_count; i++) {
|
||||
if (merged_ranges[i].binding.owner != nullptr) {
|
||||
EXIT_IF(prepared.owner_count >= PreparedVertexBuffers::MaxBuffers);
|
||||
prepared.owners[prepared.owner_count++] = std::move(merged_ranges[i].binding.owner);
|
||||
}
|
||||
}
|
||||
return prepared;
|
||||
}
|
||||
|
||||
static void SetDrawDebugPhase(RenderCommandBuffer& buffer, uint64_t submit_id,
|
||||
const DrawCallInfo& draw, uint32_t phase) {
|
||||
static void SetDrawDebugPhase(CommandBuffer& buffer, uint64_t submit_id, const DrawCallInfo& draw,
|
||||
uint32_t phase) {
|
||||
EXIT_IF(draw.name == nullptr);
|
||||
|
||||
buffer.SetDebugInfo(static_cast<uint32_t>(draw.debug_op), submit_id, phase, draw.index_count,
|
||||
@@ -957,8 +916,8 @@ static bool GetDrawTopology(const HW::UserConfig& ucfg, bool auto_draw,
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool ResolvePrimitiveRestart(const RenderCommandBuffer& buffer,
|
||||
vk::PrimitiveTopology topology, uint32_t index_type_and_size) {
|
||||
static bool ResolvePrimitiveRestart(const CommandBuffer& buffer, vk::PrimitiveTopology topology,
|
||||
uint32_t index_type_and_size) {
|
||||
const auto control = buffer.GetUserConfig().GetPrimitiveResetControl();
|
||||
EXIT_NOT_IMPLEMENTED((control & ~0x3u) != 0);
|
||||
if ((control & 0x1u) == 0) {
|
||||
@@ -992,7 +951,7 @@ static bool ResolvePrimitiveRestart(const RenderCommandBuffer& buffer,
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RenderExecutor::PrepareDrawRenderState(uint64_t submit_id, RenderCommandBuffer& buffer,
|
||||
bool RenderExecutor::PrepareDrawRenderState(uint64_t submit_id, CommandBuffer& buffer,
|
||||
const DrawCallInfo& draw,
|
||||
uint32_t render_target_slice_offset,
|
||||
bool log_setup_phases, DrawRenderState& state) {
|
||||
@@ -1032,7 +991,7 @@ bool RenderExecutor::PrepareDrawRenderState(uint64_t submit_id, RenderCommandBuf
|
||||
return true;
|
||||
}
|
||||
|
||||
static void RefreshShaders(RenderCommandBuffer& buffer, const DrawCallInfo& draw, bool log_phases,
|
||||
static void RefreshShaders(CommandBuffer& buffer, const DrawCallInfo& draw, bool log_phases,
|
||||
DrawRenderState& state) {
|
||||
EXIT_IF(draw.name == nullptr);
|
||||
auto& ctx = buffer.GetRegisters();
|
||||
@@ -1042,37 +1001,33 @@ static void RefreshShaders(RenderCommandBuffer& buffer, const DrawCallInfo& draw
|
||||
const auto& pixel_shader_info = sh_ctx.GetPs();
|
||||
const auto& shader_regs = ctx.GetShaderRegisters();
|
||||
|
||||
state.vs_shader = {};
|
||||
state.ps_shader = {};
|
||||
state.ps_input_info = {};
|
||||
state.vertex_program = {};
|
||||
state.pixel_program = {};
|
||||
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;
|
||||
}
|
||||
const auto lane_mask_mode = SelectGraphicsLaneMaskMode(64u);
|
||||
|
||||
if (log_phases) {
|
||||
LogDrawPhase(draw.name, "ShaderCompileInfoVS");
|
||||
}
|
||||
if (!ShaderCompileInfoVS(vertex_shader_info, shader_regs, lane_mask_mode, state.vs_input_info,
|
||||
state.vs_shader)) {
|
||||
EXIT("ShaderCompileInfoVS failed for draw %s\n", draw.name);
|
||||
LogDrawPhase(draw.name, "GetVertexProgram");
|
||||
}
|
||||
auto& pipeline_cache = buffer.GetContext().GetPipelineCache();
|
||||
state.vertex_program =
|
||||
pipeline_cache.GetVertexProgram(vertex_shader_info, shader_regs, state.vs_input_info);
|
||||
|
||||
if (!state.ps_active) {
|
||||
return;
|
||||
}
|
||||
if (log_phases) {
|
||||
LogDrawPhase(draw.name, "ShaderCompileInfoPS");
|
||||
}
|
||||
if (!ShaderCompileInfoPS(pixel_shader_info, shader_regs, lane_mask_mode, state.vs_input_info,
|
||||
target_export_mapping, state.ps_input_info, state.ps_shader)) {
|
||||
EXIT("ShaderCompileInfoPS failed for draw %s\n", draw.name);
|
||||
LogDrawPhase(draw.name, "GetPixelProgram");
|
||||
}
|
||||
state.pixel_program =
|
||||
pipeline_cache.GetPixelProgram(pixel_shader_info, shader_regs, state.vs_input_info,
|
||||
target_export_mapping, state.ps_input_info);
|
||||
}
|
||||
|
||||
static PreparedVertexBuffers PrepareVertexBuffers(uint64_t submit_id, RenderCommandBuffer& buffer,
|
||||
static PreparedVertexBuffers PrepareVertexBuffers(uint64_t submit_id, CommandBuffer& buffer,
|
||||
const DrawCallInfo& draw,
|
||||
const ShaderVertexInputInfo& vs_input_info) {
|
||||
EXIT_IF(draw.name == nullptr);
|
||||
@@ -1082,7 +1037,7 @@ static PreparedVertexBuffers PrepareVertexBuffers(uint64_t submit_id, RenderComm
|
||||
return AcquireVertexBuffers(buffer, vs_input_info);
|
||||
}
|
||||
|
||||
static PreparedIndexBuffer PrepareIndexBuffer(RenderCommandBuffer& buffer,
|
||||
static PreparedIndexBuffer PrepareIndexBuffer(CommandBuffer& buffer,
|
||||
const DrawIndexBufferSource& source) {
|
||||
PreparedIndexBuffer prepared;
|
||||
if (!source.enabled) {
|
||||
@@ -1092,28 +1047,29 @@ static PreparedIndexBuffer PrepareIndexBuffer(RenderCommandBuffer& buffe
|
||||
prepared.size = source.size;
|
||||
prepared.type = source.type;
|
||||
if (source.host_data != nullptr) {
|
||||
auto binding =
|
||||
buffer.GetContext().GetBufferCache().UploadTransient(source.host_data, source.size, 16);
|
||||
prepared.owner = std::move(binding.owner);
|
||||
prepared.buffer = binding.buffer;
|
||||
prepared.offset = binding.offset;
|
||||
auto& stream = buffer.GetContext().GetBufferCache().GetUtilityBuffer(MemoryUsage::Stream);
|
||||
prepared.offset = stream.Copy(source.host_data, source.size, 16);
|
||||
prepared.buffer = stream.Handle();
|
||||
} else {
|
||||
auto binding =
|
||||
buffer.GetContext().GetBufferCache().ObtainBuffer(buffer, source.address, source.size);
|
||||
prepared.owner = std::move(binding.owner);
|
||||
prepared.buffer = binding.buffer;
|
||||
prepared.offset = binding.offset;
|
||||
auto [buffer_ptr, offset] =
|
||||
buffer.GetContext().GetBufferCache().ObtainBuffer(source.address, source.size, false);
|
||||
prepared.buffer = buffer_ptr->Handle();
|
||||
prepared.offset = offset;
|
||||
}
|
||||
if (source.host_data != nullptr) {
|
||||
SetVulkanObjectNameF(buffer.GetContext().GetGraphics().device, prepared.buffer,
|
||||
"Kyty.IndexBuffer[guest=transient size=0x{:x} type={}]", source.size,
|
||||
static_cast<uint32_t>(source.type));
|
||||
} else {
|
||||
SetVulkanObjectNameF(buffer.GetContext().GetGraphics().device, prepared.buffer,
|
||||
"Kyty.IndexBuffer[guest=0x{:016x} size=0x{:x} type={}]",
|
||||
source.address, source.size, static_cast<uint32_t>(source.type));
|
||||
}
|
||||
return prepared;
|
||||
}
|
||||
|
||||
static void CommitVertexBuffers(RenderCommandBuffer& buffer, vk::CommandBuffer vk_buffer,
|
||||
PreparedVertexBuffers& prepared) {
|
||||
for (uint32_t i = 0; i < prepared.owner_count; i++) {
|
||||
if (prepared.owners[i] != nullptr) {
|
||||
buffer.RetainResourceUntilFence(std::move(prepared.owners[i]));
|
||||
}
|
||||
}
|
||||
static void CommitVertexBuffers(vk::CommandBuffer vk_buffer,
|
||||
const PreparedVertexBuffers& prepared) {
|
||||
for (uint32_t i = 0; i < prepared.count; i++) {
|
||||
EXIT_IF(prepared.buffers[i] == nullptr);
|
||||
}
|
||||
@@ -1123,19 +1079,15 @@ static void CommitVertexBuffers(RenderCommandBuffer& buffer, vk::CommandBuffer v
|
||||
}
|
||||
}
|
||||
|
||||
static void CommitIndexBuffer(RenderCommandBuffer& buffer, vk::CommandBuffer vk_buffer,
|
||||
PreparedIndexBuffer& prepared) {
|
||||
static void CommitIndexBuffer(vk::CommandBuffer vk_buffer, const PreparedIndexBuffer& prepared) {
|
||||
if (prepared.size == 0) {
|
||||
return;
|
||||
}
|
||||
if (prepared.owner != nullptr) {
|
||||
buffer.RetainResourceUntilFence(std::move(prepared.owner));
|
||||
}
|
||||
EXIT_IF(prepared.buffer == nullptr);
|
||||
vk_buffer.bindIndexBuffer(prepared.buffer, prepared.offset, prepared.type);
|
||||
}
|
||||
|
||||
static void LogDrawStateIfNeeded(const RenderCommandBuffer& buffer, const DrawCallInfo& draw,
|
||||
static void LogDrawStateIfNeeded(const CommandBuffer& buffer, const DrawCallInfo& draw,
|
||||
const DrawRenderState& state, bool always_log,
|
||||
bool force_legacy_rect_log, uint32_t index_type_and_size,
|
||||
const void* index_addr) {
|
||||
@@ -1203,7 +1155,7 @@ static void EmitDrawPrimitives(const HW::UserConfig& ucfg, vk::CommandBuffer vk_
|
||||
}
|
||||
}
|
||||
|
||||
void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, RenderCommandBuffer& buffer,
|
||||
void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, CommandBuffer& buffer,
|
||||
const DrawCallInfo& draw, DrawRenderState& state,
|
||||
vk::PrimitiveTopology topology, const DrawEmitInfo& emit,
|
||||
const DrawIndexBufferSource& index_source,
|
||||
@@ -1213,8 +1165,8 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, RenderCommandBuffer
|
||||
auto& ucfg = buffer.GetUserConfig();
|
||||
|
||||
LogDrawPhase(draw.name, "PrepareBindings");
|
||||
auto bindings = PrepareGraphicsBindings(buffer, state.vs_input_info.stage,
|
||||
state.ps_input_info.stage, state.ps_active);
|
||||
auto bindings = PrepareGraphicsBindings(state.vs_input_info.stage, state.ps_input_info.stage,
|
||||
state.ps_active);
|
||||
auto vertex_bindings = PrepareVertexBuffers(submit_id, buffer, draw, state.vs_input_info);
|
||||
auto index_binding = PrepareIndexBuffer(buffer, index_source);
|
||||
state.rendering =
|
||||
@@ -1224,9 +1176,9 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, RenderCommandBuffer
|
||||
LogDrawPhase(draw.name, "CreatePipeline");
|
||||
}
|
||||
auto& pipeline = m_context.GetPipelineCache().CreateGraphicsPipeline(
|
||||
state.color_info, state.color_count, state.depth_info, state.vs_input_info, buffer,
|
||||
&state.ps_input_info, topology, primitive_restart_enable, state.ps_active, state.vs_shader,
|
||||
state.ps_shader);
|
||||
std::span {state.color_info, state.color_count}, state.depth_info, state.vs_input_info, buffer,
|
||||
state.ps_active ? &state.ps_input_info : nullptr, topology, primitive_restart_enable,
|
||||
state.vertex_program, state.pixel_program);
|
||||
|
||||
// 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
|
||||
@@ -1238,21 +1190,23 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, RenderCommandBuffer
|
||||
if (set_auto_debug) {
|
||||
SetDrawDebugPhase(buffer, submit_id, draw, 0x200u);
|
||||
}
|
||||
CommitVertexBuffers(buffer, vk_buffer, vertex_bindings);
|
||||
CommitBindings(buffer, vk::PipelineBindPoint::eGraphics, pipeline.pipeline_layout,
|
||||
bindings.vertex);
|
||||
CommitVertexBuffers(vk_buffer, vertex_bindings);
|
||||
if (bindings.pixel.has_value()) {
|
||||
if (set_auto_debug) {
|
||||
SetDrawDebugPhase(buffer, submit_id, draw, 0x300u);
|
||||
}
|
||||
CommitBindings(buffer, vk::PipelineBindPoint::eGraphics, pipeline.pipeline_layout,
|
||||
*bindings.pixel);
|
||||
}
|
||||
CommitIndexBuffer(buffer, vk_buffer, index_binding);
|
||||
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});
|
||||
CommitIndexBuffer(vk_buffer, index_binding);
|
||||
|
||||
const auto dynamic_params =
|
||||
BuildGraphicsDynamicParams(buffer, state.color_info, state.color_count, state.depth_info);
|
||||
SetDynamicParams(buffer, vk_buffer, dynamic_params);
|
||||
SetGraphicsDynamicParams(buffer, vk_buffer, state.color_info, state.color_count,
|
||||
state.depth_info);
|
||||
|
||||
LogDrawPhase(draw.name, "BeginRendering");
|
||||
if (set_auto_debug) {
|
||||
@@ -1285,7 +1239,7 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, RenderCommandBuffer
|
||||
}
|
||||
}
|
||||
|
||||
void RenderExecutor::DrawIndex(uint64_t submit_id, RenderCommandBuffer& buffer,
|
||||
void RenderExecutor::DrawIndex(uint64_t submit_id, CommandBuffer& buffer,
|
||||
uint32_t index_type_and_size, uint32_t index_count,
|
||||
const void* index_addr, uint32_t flags, uint32_t type,
|
||||
uint32_t instance_count, uint32_t render_target_slice_offset,
|
||||
@@ -1293,6 +1247,7 @@ void RenderExecutor::DrawIndex(uint64_t submit_id, RenderCommandBuffer& buffer,
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
|
||||
EXIT_IF(buffer.IsInvalid());
|
||||
m_context.GetCommandScheduler().PopPendingOperations();
|
||||
auto& ucfg = buffer.GetUserConfig();
|
||||
auto& sh_ctx = buffer.GetShaders();
|
||||
|
||||
@@ -1417,13 +1372,14 @@ void RenderExecutor::DrawIndex(uint64_t submit_id, RenderCommandBuffer& buffer,
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
|
||||
void RenderExecutor::DrawAuto(uint64_t submit_id, RenderCommandBuffer& buffer, uint32_t index_count,
|
||||
void RenderExecutor::DrawAuto(uint64_t submit_id, CommandBuffer& buffer, uint32_t index_count,
|
||||
uint32_t flags, uint32_t render_target_slice_offset,
|
||||
uint32_t instance_count, uint32_t first_vertex,
|
||||
uint32_t first_instance) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
|
||||
EXIT_IF(buffer.IsInvalid());
|
||||
m_context.GetCommandScheduler().PopPendingOperations();
|
||||
auto& ucfg = buffer.GetUserConfig();
|
||||
auto& sh_ctx = buffer.GetShaders();
|
||||
|
||||
@@ -1493,7 +1449,7 @@ void RenderExecutor::DrawAuto(uint64_t submit_id, RenderCommandBuffer& buffer, u
|
||||
if (graphics_debug_dump_enabled()) {
|
||||
LOGF("DrawIndexAuto: skipping rect-list draw with no VS param exports and PS inputs: "
|
||||
"ps_inputs=%u ps=0x%016" PRIx64 " es=0x%016" PRIx64 " gs=0x%016" PRIx64 "\n",
|
||||
state.ps_input_info.input_num, sh_ctx.GetPs().ps_regs.chksum,
|
||||
state.ps_input_info.input_num, sh_ctx.GetPs().ps_regs.data_addr,
|
||||
sh_ctx.GetVs().es_regs.data_addr, sh_ctx.GetVs().gs_regs.data_addr);
|
||||
}
|
||||
ResetBindings();
|
||||
@@ -1515,7 +1471,7 @@ void RenderExecutor::DrawAuto(uint64_t submit_id, RenderCommandBuffer& buffer, u
|
||||
ResetBindings();
|
||||
}
|
||||
|
||||
bool RenderExecutor::ResolveColorTargets(uint64_t submit_id, RenderCommandBuffer& buffer,
|
||||
bool RenderExecutor::ResolveColorTargets(uint64_t submit_id, CommandBuffer& buffer,
|
||||
uint32_t render_target_slice_offset) {
|
||||
const auto& hw = buffer.GetRegisters();
|
||||
if (hw.GetColorControl().mode != 3) {
|
||||
|
||||
@@ -1,43 +1,12 @@
|
||||
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_RENDERDRAW_H_
|
||||
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_RENDERDRAW_H_
|
||||
|
||||
#include "graphics/host_gpu/renderer/renderTarget.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <type_traits>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
struct RenderColorInfo;
|
||||
struct ShaderVertexInputInfo;
|
||||
|
||||
#pragma pack(push, 1)
|
||||
|
||||
struct PipelineDynamicParameters {
|
||||
bool stencil_test_enable = false;
|
||||
bool depth_bias_enable = false;
|
||||
|
||||
float viewport_scale[3] = {};
|
||||
float viewport_offset[3] = {};
|
||||
int scissor_ltrb[4] = {0};
|
||||
|
||||
float line_width = 1.0f;
|
||||
float depth_bias_constant_factor = 0.0f;
|
||||
float depth_bias_clamp = 0.0f;
|
||||
float depth_bias_slope_factor = 0.0f;
|
||||
uint32_t color_write_count = 1;
|
||||
bool color_write_enable[RENDER_COLOR_ATTACHMENTS_MAX] = {true, true};
|
||||
|
||||
PipelineStencilDynamicState stencil_front;
|
||||
PipelineStencilDynamicState stencil_back;
|
||||
};
|
||||
|
||||
#pragma pack(pop)
|
||||
|
||||
static_assert(std::is_trivially_copyable_v<PipelineDynamicParameters>);
|
||||
static_assert(std::is_standard_layout_v<PipelineDynamicParameters>);
|
||||
static_assert(alignof(PipelineDynamicParameters) == 1);
|
||||
|
||||
[[nodiscard]] int32_t ResolveVertexOffset(uint32_t index_offset,
|
||||
const ShaderVertexInputInfo& vs_input_info);
|
||||
|
||||
|
||||
@@ -20,9 +20,7 @@
|
||||
|
||||
namespace Libs::Graphics::Sync {
|
||||
|
||||
constexpr int GRAPHICS_EVENT_QUEUED_GRAPHICS_INTERRUPT = 0x00;
|
||||
constexpr int GRAPHICS_EVENT_EOP = 0x40;
|
||||
constexpr uint64_t GRAPHICS_REFERENCE_CLOCK_FREQUENCY = 100000000;
|
||||
constexpr uint64_t GRAPHICS_REFERENCE_CLOCK_FREQUENCY = 100000000;
|
||||
|
||||
bool ScaleReferenceClock(uint64_t host_ticks, uint64_t host_frequency, uint64_t& value) {
|
||||
if (host_frequency == 0) {
|
||||
@@ -66,8 +64,9 @@ struct EndOfPipeSignal {
|
||||
std::array<uint32_t, 4> debug_args = {};
|
||||
uint64_t debug_data = 0;
|
||||
std::optional<uint64_t> destination;
|
||||
EndOfPipeCompletion completion = EndOfPipeCompletion::None;
|
||||
uint64_t completion_data = 0;
|
||||
EndOfPipeCompletion completion = EndOfPipeCompletion::None;
|
||||
uint64_t completion_data = 0;
|
||||
int interrupt_event_id = 0;
|
||||
};
|
||||
|
||||
enum class EndOfPipeWriteSize : uint32_t { Dword = 4, Qword = 8 };
|
||||
@@ -96,8 +95,10 @@ static void RecordEndOfPipeSignal(const EndOfPipeSignal& signal) {
|
||||
case EndOfPipeCompletion::None: return;
|
||||
case EndOfPipeCompletion::Interrupt: {
|
||||
const auto context_id = static_cast<uint32_t>(signal.completion_data);
|
||||
scheduler.DeferPriorityOperation(
|
||||
[&renderer, context_id] { renderer.TriggerEopEvent(context_id); });
|
||||
const auto event_id = signal.interrupt_event_id;
|
||||
scheduler.DeferPriorityOperation([&renderer, event_id, context_id] {
|
||||
renderer.TriggerInterrupt(event_id, context_id);
|
||||
});
|
||||
return;
|
||||
}
|
||||
case EndOfPipeCompletion::Flip: {
|
||||
@@ -108,9 +109,10 @@ static void RecordEndOfPipeSignal(const EndOfPipeSignal& signal) {
|
||||
}
|
||||
case EndOfPipeCompletion::FlipAndInterrupt: {
|
||||
const auto request_id = signal.completion_data;
|
||||
scheduler.DeferPriorityOperation([&renderer, request_id] {
|
||||
const auto event_id = signal.interrupt_event_id;
|
||||
scheduler.DeferPriorityOperation([&renderer, event_id, request_id] {
|
||||
renderer.GetVideoOut().CompleteFlip(request_id);
|
||||
renderer.TriggerEopEvent(0);
|
||||
renderer.TriggerInterrupt(event_id, 0);
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -135,7 +137,8 @@ static bool TriggersInterrupt(EndOfPipeWriteAction action) {
|
||||
|
||||
static void RecordEndOfPipeWrite(uint64_t submit_id, CommandBuffer& buffer, uint64_t destination,
|
||||
uint64_t value, EndOfPipeWriteSize size,
|
||||
EndOfPipeWriteAction action, uint32_t context_id = 0) {
|
||||
EndOfPipeWriteAction action, int interrupt_event_id = 0,
|
||||
uint32_t context_id = 0) {
|
||||
const auto width = static_cast<uint32_t>(size);
|
||||
const auto value_low = static_cast<uint32_t>(value);
|
||||
const auto value_high = static_cast<uint32_t>(value >> 32u);
|
||||
@@ -150,18 +153,12 @@ static void RecordEndOfPipeWrite(uint64_t submit_id, CommandBuffer& buffer, uint
|
||||
.debug_data = destination,
|
||||
.destination = destination,
|
||||
.completion = interrupt ? EndOfPipeCompletion::Interrupt : EndOfPipeCompletion::None,
|
||||
.completion_data = context_id != 0 ? context_id : value,
|
||||
.completion_data = interrupt ? context_id : value,
|
||||
.interrupt_event_id = interrupt_event_id,
|
||||
};
|
||||
RecordEndOfPipeSignal(signal);
|
||||
}
|
||||
|
||||
void TriggerAgcUserInterrupt() {
|
||||
auto tsc = LibKernel::KernelReadTsc();
|
||||
auto result = LibKernel::EventQueue::KernelTriggerUserEventForAll(AGC_USER_INTERRUPT_EVENT,
|
||||
reinterpret_cast<void*>(tsc));
|
||||
EXIT_NOT_IMPLEMENTED(result != OK && result != LibKernel::KERNEL_ERROR_ENOENT);
|
||||
}
|
||||
|
||||
void WriteAtEndOfPipe32(uint64_t submit_id, CommandBuffer& buffer, uint32_t* dst_gpu_addr,
|
||||
uint32_t value) {
|
||||
RecordEndOfPipeWrite(submit_id, buffer, reinterpret_cast<uint64_t>(dst_gpu_addr), value,
|
||||
@@ -220,31 +217,35 @@ void WriteAtEndOfPipeWithWriteBack32(uint64_t submit_id, CommandBuffer& buffer,
|
||||
}
|
||||
|
||||
void WriteAtEndOfPipeWithInterruptWriteBack64(uint64_t submit_id, CommandBuffer& buffer,
|
||||
uint64_t* dst_gpu_addr, uint64_t value,
|
||||
uint64_t* dst_gpu_addr, uint64_t value, int event_id,
|
||||
uint32_t context_id) {
|
||||
RecordEndOfPipeWrite(submit_id, buffer, reinterpret_cast<uint64_t>(dst_gpu_addr), value,
|
||||
EndOfPipeWriteSize::Qword, EndOfPipeWriteAction::InterruptWriteBack,
|
||||
context_id);
|
||||
event_id, context_id);
|
||||
}
|
||||
|
||||
void WriteAtEndOfPipeWithInterruptWriteBack32(uint64_t submit_id, CommandBuffer& buffer,
|
||||
uint32_t* dst_gpu_addr, uint32_t value,
|
||||
uint32_t* dst_gpu_addr, uint32_t value, int event_id,
|
||||
uint32_t context_id) {
|
||||
RecordEndOfPipeWrite(submit_id, buffer, reinterpret_cast<uint64_t>(dst_gpu_addr), value,
|
||||
EndOfPipeWriteSize::Dword, EndOfPipeWriteAction::InterruptWriteBack,
|
||||
context_id);
|
||||
event_id, context_id);
|
||||
}
|
||||
|
||||
void WriteAtEndOfPipeWithInterrupt64(uint64_t submit_id, CommandBuffer& buffer,
|
||||
uint64_t* dst_gpu_addr, uint64_t value, uint32_t context_id) {
|
||||
uint64_t* dst_gpu_addr, uint64_t value, int event_id,
|
||||
uint32_t context_id) {
|
||||
RecordEndOfPipeWrite(submit_id, buffer, reinterpret_cast<uint64_t>(dst_gpu_addr), value,
|
||||
EndOfPipeWriteSize::Qword, EndOfPipeWriteAction::Interrupt, context_id);
|
||||
EndOfPipeWriteSize::Qword, EndOfPipeWriteAction::Interrupt, event_id,
|
||||
context_id);
|
||||
}
|
||||
|
||||
void WriteAtEndOfPipeWithInterrupt32(uint64_t submit_id, CommandBuffer& buffer,
|
||||
uint32_t* dst_gpu_addr, uint32_t value, uint32_t context_id) {
|
||||
uint32_t* dst_gpu_addr, uint32_t value, int event_id,
|
||||
uint32_t context_id) {
|
||||
RecordEndOfPipeWrite(submit_id, buffer, reinterpret_cast<uint64_t>(dst_gpu_addr), value,
|
||||
EndOfPipeWriteSize::Dword, EndOfPipeWriteAction::Interrupt, context_id);
|
||||
EndOfPipeWriteSize::Dword, EndOfPipeWriteAction::Interrupt, event_id,
|
||||
context_id);
|
||||
}
|
||||
|
||||
uint64_t PrepareVideoOutFlip(CommandBuffer& buffer, int handle, int index, int flip_mode,
|
||||
@@ -270,18 +271,20 @@ uint64_t PrepareVideoOutFlip(CommandBuffer& buffer, int handle, int index, int f
|
||||
void WriteAtEndOfPipeWithInterruptWriteBackFlip32(uint64_t submit_id, CommandBuffer& buffer,
|
||||
uint32_t* dst_gpu_addr, uint32_t value,
|
||||
int handle, int index, int flip_mode,
|
||||
int64_t flip_arg, uint64_t request_id) {
|
||||
int64_t flip_arg, uint64_t request_id,
|
||||
int event_id) {
|
||||
const auto destination = reinterpret_cast<uint64_t>(dst_gpu_addr);
|
||||
RecordEndOfPipeSignal({
|
||||
.buffer = &buffer,
|
||||
.submit_id = submit_id,
|
||||
.debug_operation = CommandBufferDebugOp::EopWriteBackFlip,
|
||||
.debug_args = {static_cast<uint32_t>(handle), static_cast<uint32_t>(index),
|
||||
static_cast<uint32_t>(flip_mode), value},
|
||||
.debug_data = static_cast<uint64_t>(flip_arg),
|
||||
.destination = destination,
|
||||
.completion = EndOfPipeCompletion::FlipAndInterrupt,
|
||||
.completion_data = request_id,
|
||||
.buffer = &buffer,
|
||||
.submit_id = submit_id,
|
||||
.debug_operation = CommandBufferDebugOp::EopWriteBackFlip,
|
||||
.debug_args = {static_cast<uint32_t>(handle), static_cast<uint32_t>(index),
|
||||
static_cast<uint32_t>(flip_mode), value},
|
||||
.debug_data = static_cast<uint64_t>(flip_arg),
|
||||
.destination = destination,
|
||||
.completion = EndOfPipeCompletion::FlipAndInterrupt,
|
||||
.completion_data = request_id,
|
||||
.interrupt_event_id = event_id,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -316,36 +319,24 @@ void WriteAtEndOfPipeOnlyFlip(uint64_t submit_id, CommandBuffer& buffer, int han
|
||||
});
|
||||
}
|
||||
|
||||
void TriggerEopEventAtEndOfPipe(CommandBuffer& buffer, uint32_t context_id) {
|
||||
void TriggerEopEventAtEndOfPipe(CommandBuffer& buffer, int event_id, uint32_t context_id) {
|
||||
ValidateEndOfPipeSignal({.buffer = &buffer});
|
||||
auto& renderer = buffer.GetContext();
|
||||
auto& scheduler = renderer.GetCommandScheduler();
|
||||
EXIT_IF(!scheduler.Active() || &buffer != &scheduler.Current());
|
||||
scheduler.DeferPriorityOperation(
|
||||
[&renderer, context_id] { renderer.TriggerEopEvent(context_id); });
|
||||
[&renderer, event_id, context_id] { renderer.TriggerInterrupt(event_id, context_id); });
|
||||
}
|
||||
|
||||
static void EopEventResetFunc(LibKernel::EventQueue::KernelEqueueEvent* event) {
|
||||
static void InterruptEventResetFunc(LibKernel::EventQueue::KernelEqueueEvent* event) {
|
||||
EXIT_IF(event == nullptr);
|
||||
event->triggered = false;
|
||||
event->event.fflags = 0;
|
||||
event->event.data = 0;
|
||||
}
|
||||
|
||||
static void EopEventDeleteFunc(LibKernel::EventQueue::KernelEqueue eq,
|
||||
LibKernel::EventQueue::KernelEqueueEvent* event) {
|
||||
EXIT_IF(event == nullptr);
|
||||
EXIT_NOT_IMPLEMENTED(event->event.filter != LibKernel::EventQueue::KERNEL_EVFILT_GRAPHICS);
|
||||
if (event->event.ident == GRAPHICS_EVENT_QUEUED_GRAPHICS_INTERRUPT ||
|
||||
event->event.ident == GRAPHICS_EVENT_EOP) {
|
||||
auto* renderer = static_cast<RenderContext*>(event->filter.data);
|
||||
EXIT_IF(renderer == nullptr);
|
||||
renderer->DeleteEopEq(eq, static_cast<int>(event->event.ident));
|
||||
}
|
||||
}
|
||||
|
||||
static void EopEventTriggerFunc(LibKernel::EventQueue::KernelEqueueEvent* event,
|
||||
void* trigger_data) {
|
||||
static void InterruptEventTriggerFunc(LibKernel::EventQueue::KernelEqueueEvent* event,
|
||||
void* trigger_data) {
|
||||
EXIT_IF(event == nullptr);
|
||||
|
||||
auto triggered_event = event->event;
|
||||
@@ -362,35 +353,35 @@ static void EopEventTriggerFunc(LibKernel::EventQueue::KernelEqueueEvent* event,
|
||||
int AddEqEvent(RenderContext& renderer, LibKernel::EventQueue::KernelEqueue eq, int id,
|
||||
void* udata) {
|
||||
LibKernel::EventQueue::KernelEqueueEvent event;
|
||||
event.triggered = false;
|
||||
event.event.ident = static_cast<uintptr_t>(id);
|
||||
event.event.filter = LibKernel::EventQueue::KERNEL_EVFILT_GRAPHICS;
|
||||
event.event.udata = udata;
|
||||
event.event.fflags = 0;
|
||||
event.event.data = id;
|
||||
event.filter.delete_event_func = EopEventDeleteFunc;
|
||||
event.filter.reset_func = EopEventResetFunc;
|
||||
event.filter.trigger_func = EopEventTriggerFunc;
|
||||
event.filter.data = &renderer;
|
||||
event.triggered = false;
|
||||
event.event.ident = static_cast<uintptr_t>(id);
|
||||
event.event.filter = LibKernel::EventQueue::KERNEL_EVFILT_GRAPHICS;
|
||||
event.event.udata = udata;
|
||||
event.event.fflags = 0;
|
||||
event.event.data = id;
|
||||
event.filter.reset_func = InterruptEventResetFunc;
|
||||
event.filter.trigger_func = InterruptEventTriggerFunc;
|
||||
|
||||
int result = LibKernel::EventQueue::KernelAddEvent(eq, event);
|
||||
|
||||
if (result == 0 &&
|
||||
(id == GRAPHICS_EVENT_QUEUED_GRAPHICS_INTERRUPT || id == GRAPHICS_EVENT_EOP)) {
|
||||
renderer.AddEopEq(eq, id);
|
||||
if (result == 0) {
|
||||
renderer.AddInterruptEq(eq, id);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
int DeleteEqEvent(LibKernel::EventQueue::KernelEqueue eq, int id) {
|
||||
int DeleteEqEvent(RenderContext& renderer, LibKernel::EventQueue::KernelEqueue eq, int id) {
|
||||
int result = LibKernel::EventQueue::KernelDeleteEvent(
|
||||
eq, static_cast<uintptr_t>(id), LibKernel::EventQueue::KERNEL_EVFILT_GRAPHICS);
|
||||
if (result == OK || result == LibKernel::KERNEL_ERROR_ENOENT) {
|
||||
renderer.DeleteInterruptEq(eq, id);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void ReadGds(Buffer& gds, uint32_t* dst, uint32_t dw_offset, uint32_t dw_size) {
|
||||
void ReadGds(const Buffer& gds, uint32_t* dst, uint32_t dw_offset, uint32_t dw_size) {
|
||||
const auto offset = uint64_t {dw_offset} * sizeof(uint32_t);
|
||||
const auto size = uint64_t {dw_size} * sizeof(uint32_t);
|
||||
EXIT_IF(dst == nullptr || offset > gds.Size() || size > gds.Size() - offset ||
|
||||
|
||||
@@ -17,8 +17,7 @@ namespace Sync {
|
||||
uint64_t& value);
|
||||
[[nodiscard]] uint64_t ReadReferenceClock();
|
||||
|
||||
void TriggerAgcUserInterrupt();
|
||||
void TriggerEopEventAtEndOfPipe(CommandBuffer& buffer, uint32_t context_id);
|
||||
void TriggerEopEventAtEndOfPipe(CommandBuffer& buffer, int event_id, uint32_t context_id);
|
||||
|
||||
void WriteAtEndOfPipe32(uint64_t submit_id, CommandBuffer& buffer, uint32_t* dst_gpu_addr,
|
||||
uint32_t value);
|
||||
@@ -35,16 +34,16 @@ void WriteAtEndOfPipeWithWriteBack32(uint64_t submit_id, CommandBuffer& buffer,
|
||||
void WriteAtEndOfPipeWithWriteBack64(uint64_t submit_id, CommandBuffer& buffer,
|
||||
uint64_t* dst_gpu_addr, uint64_t value);
|
||||
void WriteAtEndOfPipeWithInterrupt32(uint64_t submit_id, CommandBuffer& buffer,
|
||||
uint32_t* dst_gpu_addr, uint32_t value,
|
||||
uint32_t* dst_gpu_addr, uint32_t value, int event_id,
|
||||
uint32_t context_id = 0);
|
||||
void WriteAtEndOfPipeWithInterrupt64(uint64_t submit_id, CommandBuffer& buffer,
|
||||
uint64_t* dst_gpu_addr, uint64_t value,
|
||||
uint64_t* dst_gpu_addr, uint64_t value, int event_id,
|
||||
uint32_t context_id = 0);
|
||||
void WriteAtEndOfPipeWithInterruptWriteBack32(uint64_t submit_id, CommandBuffer& buffer,
|
||||
uint32_t* dst_gpu_addr, uint32_t value,
|
||||
uint32_t* dst_gpu_addr, uint32_t value, int event_id,
|
||||
uint32_t context_id = 0);
|
||||
void WriteAtEndOfPipeWithInterruptWriteBack64(uint64_t submit_id, CommandBuffer& buffer,
|
||||
uint64_t* dst_gpu_addr, uint64_t value,
|
||||
uint64_t* dst_gpu_addr, uint64_t value, int event_id,
|
||||
uint32_t context_id = 0);
|
||||
|
||||
[[nodiscard]] uint64_t PrepareVideoOutFlip(CommandBuffer& buffer, int handle, int index,
|
||||
@@ -57,12 +56,13 @@ void WriteAtEndOfPipeWithFlip32(uint64_t submit_id, CommandBuffer& buffer, uint3
|
||||
void WriteAtEndOfPipeWithInterruptWriteBackFlip32(uint64_t submit_id, CommandBuffer& buffer,
|
||||
uint32_t* dst_gpu_addr, uint32_t value,
|
||||
int handle, int index, int flip_mode,
|
||||
int64_t flip_arg, uint64_t request_id);
|
||||
int64_t flip_arg, uint64_t request_id,
|
||||
int event_id);
|
||||
|
||||
int AddEqEvent(RenderContext& renderer, LibKernel::EventQueue::KernelEqueue eq, int id,
|
||||
void* udata);
|
||||
int DeleteEqEvent(LibKernel::EventQueue::KernelEqueue eq, int id);
|
||||
void ReadGds(Buffer& gds, uint32_t* dst, uint32_t dw_offset, uint32_t dw_size);
|
||||
int DeleteEqEvent(RenderContext& renderer, LibKernel::EventQueue::KernelEqueue eq, int id);
|
||||
void ReadGds(const Buffer& gds, uint32_t* dst, uint32_t dw_offset, uint32_t dw_size);
|
||||
|
||||
} // namespace Sync
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#version 450
|
||||
#extension GL_ARB_gpu_shader_int64 : enable
|
||||
|
||||
layout(local_size_x = 64, local_size_y = 1, local_size_z = 1) in;
|
||||
|
||||
layout(std430, binding = 0) buffer input_buf {
|
||||
uint fault_buffer[];
|
||||
};
|
||||
|
||||
layout(std430, binding = 1) buffer output_buf {
|
||||
uint64_t download_buffer[];
|
||||
};
|
||||
layout(std430, binding = 1) buffer output_buf32 {
|
||||
uint download_buffer32[];
|
||||
};
|
||||
|
||||
void main() {
|
||||
const uint id = gl_GlobalInvocationID.x;
|
||||
uint word = fault_buffer[id];
|
||||
fault_buffer[id] = 0u;
|
||||
const uint base_bit = id * 32u;
|
||||
while (word != 0u) {
|
||||
const uint store_index = atomicAdd(download_buffer32[0], 1u) + 1u;
|
||||
if (store_index >= MAX_PAGE_FAULTS) {
|
||||
return;
|
||||
}
|
||||
const uint bit = findLSB(word);
|
||||
word &= word - 1;
|
||||
const uint page = base_bit + bit;
|
||||
download_buffer[store_index] = uint64_t(page) << CACHING_PAGEBITS;
|
||||
}
|
||||
}
|
||||
@@ -72,7 +72,10 @@ bool GraphicContext::CreateAllocator() {
|
||||
info.device = device;
|
||||
info.pVulkanFunctions = &functions;
|
||||
info.vulkanApiVersion = VULKAN_TARGET_API_VERSION;
|
||||
info.flags = memory_budget_ext_enabled ? VMA_ALLOCATOR_CREATE_EXT_MEMORY_BUDGET_BIT : 0;
|
||||
info.flags = VMA_ALLOCATOR_CREATE_BUFFER_DEVICE_ADDRESS_BIT;
|
||||
if (memory_budget_ext_enabled) {
|
||||
info.flags |= VMA_ALLOCATOR_CREATE_EXT_MEMORY_BUDGET_BIT;
|
||||
}
|
||||
|
||||
const auto result = static_cast<vk::Result>(vmaCreateAllocator(&info, &allocator));
|
||||
if (result != vk::Result::eSuccess) {
|
||||
@@ -199,20 +202,6 @@ void GraphicContext::CreateBuffer(uint64_t size, VulkanBuffer& buffer) {
|
||||
VulkanTrackAllocation(buffer.memory);
|
||||
}
|
||||
|
||||
void GraphicContext::DeleteBuffer(VulkanBuffer& buffer) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
EXIT_IF(allocator == nullptr || buffer.buffer == nullptr ||
|
||||
buffer.memory.allocation == nullptr);
|
||||
|
||||
VulkanUntrackAllocation(buffer.memory);
|
||||
vmaDestroyBuffer(allocator, buffer.buffer, buffer.memory.allocation);
|
||||
buffer.buffer = nullptr;
|
||||
buffer.memory.memory = nullptr;
|
||||
buffer.memory.allocation = nullptr;
|
||||
buffer.memory.allocation_info = {};
|
||||
buffer.memory.offset = 0;
|
||||
}
|
||||
|
||||
bool GraphicContext::CreateImage(const vk::ImageCreateInfo& image_info, VulkanImage& image) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
EXIT_IF(allocator == nullptr || image.image != nullptr || image.memory.allocation != nullptr);
|
||||
|
||||
@@ -10,10 +10,13 @@
|
||||
#define VMA_STATIC_VULKAN_FUNCTIONS 0
|
||||
#define VMA_DYNAMIC_VULKAN_FUNCTIONS 1
|
||||
|
||||
#include "common/emulatorConfig.h"
|
||||
#include "graphics/guest_gpu/gpu_defs.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <fmt/format.h>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <vulkan/vulkan.hpp>
|
||||
#include <vulkan/vulkan_hash.hpp>
|
||||
@@ -29,6 +32,24 @@ std::string VulkanToString(vk::QueueFlags value);
|
||||
vk::Format VulkanFormat(Prospero::BufferFormat guest_format);
|
||||
void RequireVulkanSuccess(vk::Result result, const char* operation);
|
||||
|
||||
template <typename Handle, typename... Args>
|
||||
void SetVulkanObjectNameF(vk::Device device, Handle handle, fmt::format_string<Args...> format,
|
||||
Args&&... args) {
|
||||
if (!Config::GraphicsDebugDumpEnabled() || device == nullptr || handle == nullptr ||
|
||||
VULKAN_HPP_DEFAULT_DISPATCHER.vkSetDebugUtilsObjectNameEXT == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto name = fmt::format(format, std::forward<Args>(args)...);
|
||||
vk::DebugUtilsObjectNameInfoEXT info {};
|
||||
info.sType = vk::StructureType::eDebugUtilsObjectNameInfoEXT;
|
||||
info.objectType = Handle::objectType;
|
||||
info.objectHandle = static_cast<uint64_t>(
|
||||
reinterpret_cast<uintptr_t>(static_cast<typename Handle::CType>(handle)));
|
||||
info.pObjectName = name.c_str();
|
||||
(void)device.setDebugUtilsObjectNameEXT(&info);
|
||||
}
|
||||
|
||||
template <typename T, typename Enumerator>
|
||||
[[nodiscard]] std::vector<T> EnumerateVulkan(const char* operation, Enumerator&& enumerate) {
|
||||
for (;;) {
|
||||
|
||||
@@ -1,93 +0,0 @@
|
||||
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_VULKANINSTANCE_H_
|
||||
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_VULKANINSTANCE_H_
|
||||
|
||||
#include "common/abi.h"
|
||||
#include "common/common.h"
|
||||
#include "common/threads.h"
|
||||
#include "graphics/host_gpu/vulkanCommon.h"
|
||||
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#include <tuple>
|
||||
#include <vk_mem_alloc.h>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
inline constexpr uint32_t VULKAN_TARGET_API_VERSION = VK_API_VERSION_1_3;
|
||||
|
||||
struct VulkanInstance {
|
||||
vk::Instance instance = nullptr;
|
||||
vk::DebugUtilsMessengerEXT debug_messenger = nullptr;
|
||||
vk::PhysicalDevice physical_device = nullptr;
|
||||
vk::PhysicalDeviceProperties physical_device_properties = {};
|
||||
vk::PhysicalDeviceMemoryProperties physical_device_memory_properties = {};
|
||||
vk::Device device = nullptr;
|
||||
VmaAllocator allocator = nullptr;
|
||||
bool memory_budget_ext_enabled = false;
|
||||
bool rt_extensions_enabled = false;
|
||||
bool subgroup_size_control_enabled = false;
|
||||
bool sample_rate_shading_enabled = false;
|
||||
uint32_t subgroup_size = 0;
|
||||
uint32_t min_subgroup_size = 0;
|
||||
uint32_t max_subgroup_size = 0;
|
||||
vk::ShaderStageFlags required_subgroup_size_stages = {};
|
||||
Common::Mutex queue_mutex;
|
||||
uint32_t queue_family = static_cast<uint32_t>(-1);
|
||||
vk::Queue queue = nullptr;
|
||||
|
||||
[[nodiscard]] const vk::PhysicalDeviceProperties& GetPhysicalDeviceProperties() const {
|
||||
return physical_device_properties;
|
||||
}
|
||||
|
||||
[[nodiscard]] const vk::PhysicalDeviceMemoryProperties&
|
||||
GetPhysicalDeviceMemoryProperties() const {
|
||||
return physical_device_memory_properties;
|
||||
}
|
||||
|
||||
[[nodiscard]] vk::FormatProperties GetFormatProperties(vk::Format format) const {
|
||||
std::scoped_lock lock(m_format_properties_mutex);
|
||||
auto [it, inserted] = m_format_properties.try_emplace(format);
|
||||
if (inserted) {
|
||||
physical_device.getFormatProperties(format, &it->second);
|
||||
}
|
||||
return it->second;
|
||||
}
|
||||
|
||||
[[nodiscard]] vk::Result GetImageFormatProperties(vk::Format format, vk::ImageType type,
|
||||
vk::ImageTiling tiling,
|
||||
vk::ImageUsageFlags usage,
|
||||
vk::ImageCreateFlags flags,
|
||||
vk::ImageFormatProperties* properties) const {
|
||||
using Key = std::tuple<vk::Format, vk::ImageType, vk::ImageTiling, vk::ImageUsageFlags,
|
||||
vk::ImageCreateFlags>;
|
||||
std::scoped_lock lock(m_image_format_properties_mutex);
|
||||
auto [it, inserted] =
|
||||
m_image_format_properties.try_emplace(Key {format, type, tiling, usage, flags});
|
||||
if (inserted) {
|
||||
it->second.first = physical_device.getImageFormatProperties(format, type, tiling, usage,
|
||||
flags, &it->second.second);
|
||||
}
|
||||
if (properties != nullptr) {
|
||||
*properties = it->second.second;
|
||||
}
|
||||
return it->second.first;
|
||||
}
|
||||
|
||||
[[nodiscard]] vk::DeviceSize StorageMinAlignment() const {
|
||||
const auto alignment = physical_device_properties.limits.minStorageBufferOffsetAlignment;
|
||||
return alignment != 0 ? alignment : 1;
|
||||
}
|
||||
|
||||
private:
|
||||
mutable std::mutex m_format_properties_mutex;
|
||||
mutable std::map<vk::Format, vk::FormatProperties> m_format_properties;
|
||||
mutable std::mutex m_image_format_properties_mutex;
|
||||
mutable std::map<std::tuple<vk::Format, vk::ImageType, vk::ImageTiling, vk::ImageUsageFlags,
|
||||
vk::ImageCreateFlags>,
|
||||
std::pair<vk::Result, vk::ImageFormatProperties>>
|
||||
m_image_format_properties;
|
||||
};
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_VULKANINSTANCE_H_
|
||||
@@ -1,18 +1,24 @@
|
||||
#include "graphics/presentation/window/hostInput.h"
|
||||
|
||||
#include "SDL_error.h"
|
||||
#include "SDL_events.h"
|
||||
#include "SDL_keyboard.h"
|
||||
#include "SDL_keycode.h"
|
||||
#include "SDL_mouse.h"
|
||||
#include "SDL_timer.h"
|
||||
#include "SDL_video.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/emulatorConfig.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "libs/controller.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
#include <cstdlib>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
@@ -23,6 +29,7 @@ struct ControlInfo {
|
||||
uint32_t button = 0;
|
||||
Controller::Axis axis = Controller::Axis::AxisMax;
|
||||
bool positive = false;
|
||||
float touch_x = 0.0f;
|
||||
};
|
||||
|
||||
static constexpr std::array CONTROL_INFO = {
|
||||
@@ -41,7 +48,10 @@ static constexpr std::array CONTROL_INFO = {
|
||||
ControlInfo {"Circle", Controller::PAD_BUTTON_CIRCLE},
|
||||
ControlInfo {"Cross", Controller::PAD_BUTTON_CROSS},
|
||||
ControlInfo {"Square", Controller::PAD_BUTTON_SQUARE},
|
||||
ControlInfo {"TouchPad", Controller::PAD_BUTTON_TOUCH_PAD},
|
||||
ControlInfo {"TouchPad", Controller::PAD_BUTTON_TOUCH_PAD, Controller::Axis::AxisMax, false,
|
||||
0.25f},
|
||||
ControlInfo {"TouchPadRight", Controller::PAD_BUTTON_TOUCH_PAD, Controller::Axis::AxisMax,
|
||||
false, 0.75f},
|
||||
ControlInfo {"LeftStickLeft", 0, Controller::Axis::LeftX, false},
|
||||
ControlInfo {"LeftStickRight", 0, Controller::Axis::LeftX, true},
|
||||
ControlInfo {"LeftStickUp", 0, Controller::Axis::LeftY, false},
|
||||
@@ -60,17 +70,23 @@ struct Binding {
|
||||
std::size_t control = INVALID_CONTROL;
|
||||
};
|
||||
|
||||
constexpr int MOUSE_POLL_INTERVAL_MS = 33;
|
||||
constexpr std::string_view MOUSE_SENSITIVITY = "MouseSensitivity=";
|
||||
|
||||
struct MouseJoystickState {
|
||||
bool enabled = false;
|
||||
bool output = false;
|
||||
uint64_t next_poll = 0;
|
||||
};
|
||||
|
||||
MouseJoystickState g_mouse;
|
||||
|
||||
std::size_t ControlFromName(std::string_view name) {
|
||||
const auto info = std::find_if(CONTROL_INFO.begin(), CONTROL_INFO.end(),
|
||||
[name](const auto& item) { return item.name == name; });
|
||||
return static_cast<std::size_t>(std::distance(CONTROL_INFO.begin(), info));
|
||||
}
|
||||
|
||||
const ControlInfo& Info(std::size_t control) {
|
||||
EXIT_IF(control >= CONTROL_INFO.size());
|
||||
return CONTROL_INFO[control];
|
||||
}
|
||||
|
||||
SDL_Keycode NormalizeKey(SDL_Keycode key) {
|
||||
switch (key) {
|
||||
case SDLK_RSHIFT: return SDLK_LSHIFT;
|
||||
@@ -103,10 +119,17 @@ bool Conflicts(const Binding& first, const Binding& second) {
|
||||
|
||||
class InputMap {
|
||||
public:
|
||||
InputMap(): m_custom(!Config::GetKeymap().empty()) {
|
||||
InputMap() {
|
||||
for (const auto& value: Config::GetKeymap()) {
|
||||
const std::string_view entry = value;
|
||||
const auto split = entry.find('=');
|
||||
if (entry.starts_with(MOUSE_SENSITIVITY)) {
|
||||
const float sensitivity =
|
||||
std::strtof(value.c_str() + MOUSE_SENSITIVITY.size(), nullptr);
|
||||
m_mouse_sensitivity =
|
||||
std::clamp(std::isfinite(sensitivity) ? sensitivity : 1.0f, 0.1f, 5.0f);
|
||||
continue;
|
||||
}
|
||||
const auto split = entry.find('=');
|
||||
|
||||
Binding binding;
|
||||
if (split != std::string_view::npos) {
|
||||
@@ -118,9 +141,9 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
const bool reserved =
|
||||
binding.key == SDLK_ESCAPE || binding.key == SDLK_SPACE || binding.key == SDLK_F1 ||
|
||||
binding.key == SDLK_F11;
|
||||
const bool reserved = binding.key == SDLK_ESCAPE || binding.key == SDLK_SPACE ||
|
||||
binding.key == SDLK_F1 || binding.key == SDLK_F7 ||
|
||||
binding.key == SDLK_F11;
|
||||
if (binding.control == INVALID_CONTROL || reserved ||
|
||||
(binding.key == SDLK_UNKNOWN && binding.mouse_button == 0)) {
|
||||
EXIT("Invalid input mapping: %s\n", value.c_str());
|
||||
@@ -128,7 +151,8 @@ public:
|
||||
Add(binding);
|
||||
}
|
||||
}
|
||||
[[nodiscard]] bool Custom() const { return m_custom; }
|
||||
[[nodiscard]] bool Custom() const { return m_size != 0; }
|
||||
[[nodiscard]] float MouseSensitivity() const { return m_mouse_sensitivity; }
|
||||
|
||||
[[nodiscard]] std::size_t FindKey(int key_code) const {
|
||||
key_code = NormalizeKey(static_cast<SDL_Keycode>(key_code));
|
||||
@@ -159,8 +183,8 @@ private:
|
||||
}
|
||||
|
||||
std::array<Binding, CONTROL_INFO.size()> m_bindings {};
|
||||
std::size_t m_size = 0;
|
||||
bool m_custom;
|
||||
std::size_t m_size = 0;
|
||||
float m_mouse_sensitivity = 1.0f;
|
||||
};
|
||||
|
||||
const InputMap& GetInputMap() {
|
||||
@@ -180,6 +204,10 @@ void SetButton(uint32_t button, bool down) {
|
||||
}
|
||||
}
|
||||
|
||||
void SetTouchPad(float x, bool down) {
|
||||
Controller::ControllerTouchPad(Controller::HOST_INPUT_CONTROLLER_ID, 0, down, x, 0.5f);
|
||||
}
|
||||
|
||||
uint32_t DefaultKeyboardButton(int key_code) {
|
||||
switch (NormalizeKey(static_cast<SDL_Keycode>(key_code))) {
|
||||
case SDLK_UP: return Controller::PAD_BUTTON_UP;
|
||||
@@ -196,8 +224,6 @@ uint32_t DefaultKeyboardButton(int key_code) {
|
||||
case SDLK_LCTRL: return Controller::PAD_BUTTON_R3;
|
||||
case SDLK_RETURN:
|
||||
case SDLK_RETURN2: return Controller::PAD_BUTTON_OPTIONS;
|
||||
case SDLK_BACKSPACE:
|
||||
case SDLK_TAB: return Controller::PAD_BUTTON_TOUCH_PAD;
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
@@ -210,7 +236,12 @@ struct StickKeys {
|
||||
};
|
||||
|
||||
void SetStickAxis(Controller::Axis axis, bool negative, bool positive) {
|
||||
const int value = negative == positive ? 128 : negative ? 0 : 255;
|
||||
int value = 128;
|
||||
if (negative && !positive) {
|
||||
value = 0;
|
||||
} else if (positive && !negative) {
|
||||
value = 255;
|
||||
}
|
||||
Controller::ControllerAxis(Controller::HOST_INPUT_CONTROLLER_ID, axis, value);
|
||||
}
|
||||
|
||||
@@ -219,7 +250,11 @@ void SetControl(std::size_t control, bool down) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& info = Info(control);
|
||||
const auto& info = CONTROL_INFO[control];
|
||||
if (info.button == Controller::PAD_BUTTON_TOUCH_PAD) {
|
||||
SetTouchPad(info.touch_x, down);
|
||||
return;
|
||||
}
|
||||
if (info.button != 0) {
|
||||
SetButton(info.button, down);
|
||||
return;
|
||||
@@ -246,6 +281,8 @@ void DefaultKeyboardInput(int key_code, bool down) {
|
||||
static StickKeys right;
|
||||
|
||||
switch (NormalizeKey(static_cast<SDL_Keycode>(key_code))) {
|
||||
case SDLK_BACKSPACE: SetTouchPad(0.25f, down); return;
|
||||
case SDLK_TAB: SetTouchPad(0.75f, down); return;
|
||||
case SDLK_a:
|
||||
left.left = down;
|
||||
SetStickAxis(Controller::Axis::LeftX, left.left, left.right);
|
||||
@@ -281,6 +318,33 @@ void DefaultKeyboardInput(int key_code, bool down) {
|
||||
default: SetButton(DefaultKeyboardButton(key_code), down); return;
|
||||
}
|
||||
}
|
||||
|
||||
void MouseToJoystick(int delta_x, int delta_y) {
|
||||
const double distance = std::hypot(delta_x, delta_y);
|
||||
const double scale =
|
||||
std::clamp(distance * GetInputMap().MouseSensitivity() + 16.0, 64.0, 128.0) / distance;
|
||||
const auto map_axis = [scale](int delta) {
|
||||
return std::clamp(128 + static_cast<int>(std::lround(delta * scale)), 0, 255);
|
||||
};
|
||||
Controller::ControllerRightStick(Controller::HOST_INPUT_CONTROLLER_ID, map_axis(delta_x),
|
||||
map_axis(delta_y));
|
||||
}
|
||||
|
||||
void CenterMouseStick() {
|
||||
if (!g_mouse.output) {
|
||||
return;
|
||||
}
|
||||
Controller::ControllerRightStick(Controller::HOST_INPUT_CONTROLLER_ID, 128, 128);
|
||||
g_mouse.output = false;
|
||||
}
|
||||
|
||||
bool SetRelativeMouseMode(bool enabled) {
|
||||
if (SDL_SetRelativeMouseMode(enabled ? SDL_TRUE : SDL_FALSE) == 0) {
|
||||
return true;
|
||||
}
|
||||
LOGF("Mouse-to-joystick relative mode failed: %s\n", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void HostInputInit() {
|
||||
@@ -303,4 +367,63 @@ void HostInputMouseButton(uint8_t mouse_button, bool down) {
|
||||
}
|
||||
}
|
||||
|
||||
void HostInputToggleMouseToJoystick() {
|
||||
if (g_mouse.enabled) {
|
||||
SetRelativeMouseMode(false);
|
||||
CenterMouseStick();
|
||||
g_mouse = {};
|
||||
LOGF("Mouse to right stick: disabled\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!SetRelativeMouseMode(true)) {
|
||||
return;
|
||||
}
|
||||
int ignored_x = 0;
|
||||
int ignored_y = 0;
|
||||
SDL_GetRelativeMouseState(&ignored_x, &ignored_y);
|
||||
g_mouse.enabled = true;
|
||||
g_mouse.next_poll = SDL_GetTicks64() + MOUSE_POLL_INTERVAL_MS;
|
||||
LOGF("Mouse to right stick: enabled (F7 to release)\n");
|
||||
}
|
||||
|
||||
int PollMouse(uint64_t now_ms) {
|
||||
if (now_ms < g_mouse.next_poll) {
|
||||
return static_cast<int>(g_mouse.next_poll - now_ms);
|
||||
}
|
||||
g_mouse.next_poll = now_ms + MOUSE_POLL_INTERVAL_MS;
|
||||
|
||||
int delta_x = 0;
|
||||
int delta_y = 0;
|
||||
SDL_GetRelativeMouseState(&delta_x, &delta_y);
|
||||
if (delta_x == 0 && delta_y == 0) {
|
||||
CenterMouseStick();
|
||||
return MOUSE_POLL_INTERVAL_MS;
|
||||
}
|
||||
|
||||
MouseToJoystick(delta_x, delta_y);
|
||||
g_mouse.output = true;
|
||||
return MOUSE_POLL_INTERVAL_MS;
|
||||
}
|
||||
|
||||
bool HostInputWaitEvent(SDL_Event* event) {
|
||||
if (!g_mouse.enabled || SDL_GetKeyboardFocus() == nullptr) {
|
||||
CenterMouseStick();
|
||||
if (SDL_WaitEvent(event) == 0) {
|
||||
EXIT("%s\n", SDL_GetError());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
const int timeout_ms = PollMouse(SDL_GetTicks64());
|
||||
SDL_ClearError();
|
||||
if (SDL_WaitEventTimeout(event, timeout_ms) != 0) {
|
||||
return true;
|
||||
}
|
||||
if (SDL_GetError()[0] != '\0') {
|
||||
EXIT("%s\n", SDL_GetError());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
@@ -3,11 +3,15 @@
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
union SDL_Event;
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
void HostInputInit();
|
||||
void HostInputKey(int key_code, bool down);
|
||||
void HostInputMouseButton(uint8_t mouse_button, bool down);
|
||||
void HostInputInit();
|
||||
void HostInputKey(int key_code, bool down);
|
||||
void HostInputMouseButton(uint8_t mouse_button, bool down);
|
||||
void HostInputToggleMouseToJoystick();
|
||||
[[nodiscard]] bool HostInputWaitEvent(SDL_Event* event);
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
|
||||
@@ -1,30 +1,9 @@
|
||||
#include "SDL.h"
|
||||
#include "SDL_error.h"
|
||||
#include "SDL_events.h"
|
||||
#include "SDL_gamecontroller.h"
|
||||
#include "SDL_hints.h"
|
||||
#include "SDL_joystick.h"
|
||||
#include "SDL_keyboard.h"
|
||||
#include "SDL_keycode.h"
|
||||
#include "SDL_mouse.h"
|
||||
#include "SDL_pixels.h"
|
||||
#include "SDL_rwops.h"
|
||||
#include "SDL_stdinc.h"
|
||||
#include "SDL_surface.h"
|
||||
#include "SDL_thread.h"
|
||||
#include "SDL_touch.h"
|
||||
#include "SDL_video.h"
|
||||
#include "SDL_vulkan.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/common.h"
|
||||
#include "common/emulatorConfig.h"
|
||||
#include "common/file.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/profiler.h"
|
||||
#include "common/stringUtils.h"
|
||||
#include "common/systemInfo.h"
|
||||
#include "common/threads.h"
|
||||
#include "common/timer.h"
|
||||
#include "graphics/host_gpu/graphicContext.h"
|
||||
#include "graphics/host_gpu/renderer/render.h"
|
||||
#include "graphics/host_gpu/renderer/renderContext.h"
|
||||
@@ -34,19 +13,11 @@
|
||||
#include "graphics/presentation/presenter.h"
|
||||
#include "graphics/presentation/videoOut.h"
|
||||
#include "graphics/presentation/window/windowInternal.h"
|
||||
#include "libs/controller.h"
|
||||
#include "loader/systemContent.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <deque>
|
||||
#include <fmt/format.h>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <vulkan/vk_platform.h>
|
||||
|
||||
@@ -58,10 +29,10 @@
|
||||
namespace Libs::Graphics {
|
||||
|
||||
struct Presenter::Frame {
|
||||
VulkanImage image;
|
||||
std::unique_ptr<CommandBuffer> present_commands;
|
||||
bool busy = false;
|
||||
bool reusing_last = false;
|
||||
VulkanImage image;
|
||||
uint64_t present_tick = 0;
|
||||
bool busy = false;
|
||||
bool reusing_last = false;
|
||||
|
||||
void Configure(GraphicContext& graphics, vk::Extent2D extent, vk::Format format);
|
||||
void Transit(vk::CommandBuffer command, vk::ImageLayout layout, vk::AccessFlags2 access);
|
||||
@@ -71,12 +42,11 @@ struct Presenter::Frame {
|
||||
|
||||
class FramePool final {
|
||||
public:
|
||||
explicit FramePool(WindowContext& window): m_window(window) {}
|
||||
FramePool(WindowContext& window, CommandScheduler& scheduler)
|
||||
: m_window(window), m_scheduler(scheduler) {}
|
||||
~FramePool() {
|
||||
m_scheduler.Wait(m_scheduler.CurrentTick() - 1);
|
||||
for (auto& frame: m_frames) {
|
||||
if (frame->present_commands != nullptr) {
|
||||
frame->present_commands->WaitForFenceOnly();
|
||||
}
|
||||
if (frame->image.image != nullptr) {
|
||||
m_window.graphic_ctx.DeleteImage(frame->image);
|
||||
}
|
||||
@@ -188,15 +158,10 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
static void WaitForFrame(Presenter::Frame& frame) {
|
||||
// The producer only waits here. Reset stays on the presentation thread that owns the
|
||||
// allocating Vulkan command pool.
|
||||
if (frame.present_commands != nullptr) {
|
||||
frame.present_commands->WaitForFenceOnly();
|
||||
}
|
||||
}
|
||||
void WaitForFrame(Presenter::Frame& frame) { m_scheduler.Wait(frame.present_tick); }
|
||||
|
||||
WindowContext& m_window;
|
||||
CommandScheduler& m_scheduler;
|
||||
Common::Mutex m_mutex;
|
||||
Common::CondVar m_available;
|
||||
std::vector<std::unique_ptr<Presenter::Frame>> m_frames;
|
||||
@@ -330,7 +295,7 @@ public:
|
||||
[[nodiscard]] Status AcquireNextImage();
|
||||
[[nodiscard]] bool PrepareImeOverlay();
|
||||
void RecordPresentCommands(CommandBuffer& command, VulkanImage& source, bool draw_ime_overlay);
|
||||
void Submit(CommandBuffer& command);
|
||||
uint64_t Submit(CommandScheduler& scheduler);
|
||||
[[nodiscard]] Status Present();
|
||||
|
||||
[[nodiscard]] uint32_t ImageCount() const noexcept {
|
||||
@@ -340,7 +305,6 @@ public:
|
||||
|
||||
private:
|
||||
void Destroy();
|
||||
void RefreshSurfaceSize();
|
||||
|
||||
WindowContext& m_window;
|
||||
vk::SwapchainKHR m_handle = nullptr;
|
||||
@@ -358,7 +322,7 @@ private:
|
||||
struct Presenter::Impl {
|
||||
explicit Impl(WindowContext& owner)
|
||||
: renderer(*owner.render_context), window(owner), swapchain(owner),
|
||||
present_scheduler(renderer, owner.graphic_ctx), frames(owner) {
|
||||
present_scheduler(renderer, owner.graphic_ctx), frames(owner, present_scheduler) {
|
||||
EXIT_IF(owner.render_context == nullptr);
|
||||
swapchain.Create();
|
||||
frames.Initialize(swapchain.ImageCount(), swapchain.Format());
|
||||
@@ -402,12 +366,12 @@ struct Presenter::Impl {
|
||||
|
||||
void Swapchain::Create() {
|
||||
auto& graphics = m_window.graphic_ctx;
|
||||
EXIT_IF(graphics.screen_width == 0);
|
||||
EXIT_IF(graphics.screen_height == 0);
|
||||
EXIT_IF(graphics.device == nullptr);
|
||||
EXIT_IF(m_window.surface == nullptr);
|
||||
|
||||
Common::LockGuard lock(m_window.mutex);
|
||||
EXIT_IF(graphics.screen_width == 0);
|
||||
EXIT_IF(graphics.screen_height == 0);
|
||||
const auto& surface = m_window.surface_capabilities;
|
||||
EXIT_NOT_IMPLEMENTED(surface.formats.empty());
|
||||
|
||||
@@ -465,7 +429,21 @@ void Swapchain::Create() {
|
||||
create_info.imageSharingMode = vk::SharingMode::eExclusive;
|
||||
create_info.preTransform = transform;
|
||||
create_info.compositeAlpha = composite;
|
||||
create_info.presentMode = vk::PresentModeKHR::eFifo;
|
||||
switch (Config::GetPresentMode()) {
|
||||
case Config::PresentMode::Mailbox:
|
||||
create_info.presentMode = vk::PresentModeKHR::eMailbox;
|
||||
break;
|
||||
case Config::PresentMode::Immediate:
|
||||
create_info.presentMode = vk::PresentModeKHR::eImmediate;
|
||||
break;
|
||||
case Config::PresentMode::Fifo:
|
||||
default: create_info.presentMode = vk::PresentModeKHR::eFifo; break;
|
||||
}
|
||||
if (std::find(surface.present_modes.begin(), surface.present_modes.end(),
|
||||
create_info.presentMode) == surface.present_modes.end()) {
|
||||
LOGF("warning: requested present mode is unavailable; falling back to Fifo\n");
|
||||
create_info.presentMode = vk::PresentModeKHR::eFifo;
|
||||
}
|
||||
create_info.clipped = VK_TRUE;
|
||||
RequireVulkanSuccess(graphics.device.createSwapchainKHR(&create_info, nullptr, &m_handle),
|
||||
"vkCreateSwapchainKHR");
|
||||
@@ -560,30 +538,18 @@ void Swapchain::Destroy() {
|
||||
m_render_complete.clear();
|
||||
}
|
||||
|
||||
void Swapchain::RefreshSurfaceSize() {
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
SDL_Vulkan_GetDrawableSize(m_window.window, &width, &height);
|
||||
if (width > 0 && height > 0) {
|
||||
m_window.graphic_ctx.screen_width = static_cast<uint32_t>(width);
|
||||
m_window.graphic_ctx.screen_height = static_cast<uint32_t>(height);
|
||||
}
|
||||
|
||||
m_window.RefreshSurfaceCapabilities();
|
||||
}
|
||||
|
||||
void Swapchain::Recreate(bool surface_lost) {
|
||||
Destroy();
|
||||
if (surface_lost) {
|
||||
#if defined(__APPLE__)
|
||||
// Surface recreation goes through SDL_Vulkan_CreateSurface, which touches the
|
||||
// window's view/layer and must run on the main thread on macOS.
|
||||
m_window.RunOnMainThread([this] { m_window.RecreateSurface(); }, true);
|
||||
m_window.RunOnMainThread([this] { m_window.RecreateSurface(); });
|
||||
#else
|
||||
m_window.RecreateSurface();
|
||||
#endif
|
||||
}
|
||||
RefreshSurfaceSize();
|
||||
m_window.RefreshSurfaceCapabilities();
|
||||
Create();
|
||||
}
|
||||
|
||||
@@ -628,7 +594,6 @@ void Swapchain::RecordPresentCommands(CommandBuffer& command, VulkanImage& sourc
|
||||
}
|
||||
EXIT_IF(m_image_index >= m_images.size());
|
||||
auto vk_command = command.Handle();
|
||||
command.Begin();
|
||||
|
||||
vk::ImageMemoryBarrier to_transfer {};
|
||||
to_transfer.sType = vk::StructureType::eImageMemoryBarrier;
|
||||
@@ -699,15 +664,14 @@ void Swapchain::RecordPresentCommands(CommandBuffer& command, VulkanImage& sourc
|
||||
vk::DependencyFlagBits::eByRegion, 0, nullptr, 0, nullptr, 1,
|
||||
&to_present);
|
||||
}
|
||||
command.End();
|
||||
}
|
||||
|
||||
void Swapchain::Submit(CommandBuffer& command) {
|
||||
uint64_t Swapchain::Submit(CommandScheduler& scheduler) {
|
||||
EXIT_IF(m_frame_index >= m_image_acquired.size() || m_image_index >= m_render_complete.size());
|
||||
SubmitInfo submit;
|
||||
submit.AddWait(m_image_acquired[m_frame_index], 1, vk::PipelineStageFlagBits::eTransfer);
|
||||
submit.AddSignal(m_render_complete[m_image_index]);
|
||||
command.Execute(submit);
|
||||
return scheduler.Submit(submit);
|
||||
}
|
||||
|
||||
Swapchain::Status Swapchain::Present() {
|
||||
@@ -782,15 +746,9 @@ Presenter::Frame& Presenter::PrepareBlankFrame(uint32_t width, uint32_t height,
|
||||
EXIT_IF(producer->IsInvalid());
|
||||
frame->Clear(*producer, clear);
|
||||
} else {
|
||||
if (frame->present_commands == nullptr) {
|
||||
frame->present_commands = std::make_unique<CommandBuffer>(m_impl->present_scheduler);
|
||||
}
|
||||
auto& command = *frame->present_commands;
|
||||
command.WaitForFenceAndReset();
|
||||
command.Begin();
|
||||
auto& command = m_impl->present_scheduler.BeginCommand();
|
||||
frame->Clear(command, clear);
|
||||
command.End();
|
||||
command.Execute();
|
||||
frame->present_tick = m_impl->present_scheduler.Submit();
|
||||
}
|
||||
return *frame;
|
||||
}
|
||||
@@ -817,29 +775,6 @@ void Presenter::Present(Frame& frame, bool reuse) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
m_impl->frames.ValidateForPresent(&frame, reuse);
|
||||
|
||||
auto& window = m_impl->window;
|
||||
if (window.window_hidden) {
|
||||
#if defined(__APPLE__)
|
||||
// AppKit traps if a window is shown off the main thread; marshal and wait so the
|
||||
// swapchain below is recreated against a visible window.
|
||||
window.RunOnMainThread(
|
||||
[&window] {
|
||||
window.UpdateIcon();
|
||||
SDL_ShowWindow(window.window);
|
||||
SDL_RaiseWindow(window.window);
|
||||
},
|
||||
true);
|
||||
#else
|
||||
window.UpdateIcon();
|
||||
|
||||
SDL_ShowWindow(window.window);
|
||||
SDL_RaiseWindow(window.window);
|
||||
#endif
|
||||
|
||||
window.window_hidden = false;
|
||||
m_impl->RecoverSwapchain(Swapchain::Status::Recreate);
|
||||
}
|
||||
|
||||
const auto ime_visual = GetImeVisualState();
|
||||
auto& swapchain = m_impl->swapchain;
|
||||
for (uint32_t attempt = 0; attempt < 2; attempt++) {
|
||||
@@ -848,16 +783,12 @@ void Presenter::Present(Frame& frame, bool reuse) {
|
||||
m_impl->RecoverSwapchain(status);
|
||||
continue;
|
||||
}
|
||||
if (frame.present_commands == nullptr) {
|
||||
frame.present_commands = std::make_unique<CommandBuffer>(m_impl->present_scheduler);
|
||||
}
|
||||
{
|
||||
Common::LockGuard render_lock(m_impl->renderer.GetMutex());
|
||||
frame.present_commands->WaitForFenceAndReset();
|
||||
auto& command = *frame.present_commands;
|
||||
const bool draw_ime_overlay = ime_visual.active && swapchain.PrepareImeOverlay();
|
||||
auto& command = m_impl->present_scheduler.BeginCommand();
|
||||
const bool draw_ime_overlay = ime_visual.active && swapchain.PrepareImeOverlay();
|
||||
swapchain.RecordPresentCommands(command, frame.image, draw_ime_overlay);
|
||||
swapchain.Submit(command);
|
||||
frame.present_tick = swapchain.Submit(m_impl->present_scheduler);
|
||||
}
|
||||
status = swapchain.Present();
|
||||
if (status != Swapchain::Status::Success) {
|
||||
@@ -866,7 +797,7 @@ void Presenter::Present(Frame& frame, bool reuse) {
|
||||
}
|
||||
|
||||
m_impl->presented_ime_revision.store(ime_visual.revision, std::memory_order_release);
|
||||
window.UpdateTitle();
|
||||
m_impl->window.UpdateTitle();
|
||||
m_impl->frames.Release(&frame, true);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
#include "common/logging/log.h"
|
||||
#include "common/profiler.h"
|
||||
#include "common/stringUtils.h"
|
||||
#include "common/systemInfo.h"
|
||||
#include "common/threads.h"
|
||||
#include "common/timer.h"
|
||||
#include "graphics/host_gpu/graphicContext.h"
|
||||
@@ -158,6 +157,10 @@ static void VulkanFindPhysicalDevice(vk::Instance instance, vk::SurfaceKHR surfa
|
||||
});
|
||||
EXIT_NOT_IMPLEMENTED(devices.empty());
|
||||
|
||||
if (Config::GetGpuIndex() >= 0) {
|
||||
devices = {devices[Config::GetGpuIndex()]};
|
||||
}
|
||||
|
||||
vk::PhysicalDevice best_device = nullptr;
|
||||
uint32_t best_queue_family = static_cast<uint32_t>(-1);
|
||||
SurfaceCapabilities best_capabilities;
|
||||
@@ -199,7 +202,15 @@ static void VulkanFindPhysicalDevice(vk::Instance instance, vk::SurfaceKHR surfa
|
||||
|
||||
vk::PhysicalDeviceVulkan12Features features12 {};
|
||||
features12.sType = vk::StructureType::ePhysicalDeviceVulkan12Features;
|
||||
#if defined(__APPLE__)
|
||||
features12.pNext = &depth_clip_control;
|
||||
#else
|
||||
vk::PhysicalDeviceFragmentShaderBarycentricFeaturesKHR fragment_barycentric {};
|
||||
fragment_barycentric.sType =
|
||||
vk::StructureType::ePhysicalDeviceFragmentShaderBarycentricFeaturesKHR;
|
||||
fragment_barycentric.pNext = &depth_clip_control;
|
||||
features12.pNext = &fragment_barycentric;
|
||||
#endif
|
||||
features13.pNext = &features12;
|
||||
|
||||
device_features2.sType = vk::StructureType::ePhysicalDeviceFeatures2;
|
||||
@@ -231,6 +242,12 @@ static void VulkanFindPhysicalDevice(vk::Instance instance, vk::SurfaceKHR surfa
|
||||
skip_device = true;
|
||||
#endif
|
||||
}
|
||||
#if !defined(__APPLE__)
|
||||
if (fragment_barycentric.fragmentShaderBarycentric != VK_TRUE) {
|
||||
LOGF("fragmentShaderBarycentric is not supported\n");
|
||||
skip_device = true;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (features12.samplerMirrorClampToEdge != VK_TRUE) {
|
||||
LOGF("samplerMirrorClampToEdge is not supported\n");
|
||||
@@ -262,6 +279,22 @@ static void VulkanFindPhysicalDevice(vk::Instance instance, vk::SurfaceKHR surfa
|
||||
LOGF("depthBiasClamp is not supported\n");
|
||||
skip_device = true;
|
||||
}
|
||||
if (device_features2.features.shaderClipDistance != VK_TRUE) {
|
||||
LOGF("shaderClipDistance is not supported\n");
|
||||
skip_device = true;
|
||||
}
|
||||
if (device_features2.features.shaderCullDistance != VK_TRUE) {
|
||||
LOGF("shaderCullDistance is not supported\n");
|
||||
skip_device = true;
|
||||
}
|
||||
if (device_features2.features.largePoints != VK_TRUE) {
|
||||
LOGF("largePoints is not supported\n");
|
||||
skip_device = true;
|
||||
}
|
||||
if (features12.shaderOutputLayer != VK_TRUE) {
|
||||
LOGF("shaderOutputLayer is not supported\n");
|
||||
skip_device = true;
|
||||
}
|
||||
|
||||
if (device_features2.features.fragmentStoresAndAtomics != VK_TRUE) {
|
||||
LOGF("fragmentStoresAndAtomics is not supported\n");
|
||||
@@ -463,14 +496,19 @@ static void VulkanInitSubgroupSizeControl(vk::PhysicalDevice physical_device,
|
||||
graphics.min_subgroup_size = subgroup_size_control.minSubgroupSize;
|
||||
graphics.max_subgroup_size = subgroup_size_control.maxSubgroupSize;
|
||||
graphics.required_subgroup_size_stages = subgroup_size_control.requiredSubgroupSizeStages;
|
||||
graphics.subgroup_size_control_enabled = features13.subgroupSizeControl == VK_TRUE &&
|
||||
subgroup_size_control.minSubgroupSize <= 64 &&
|
||||
subgroup_size_control.maxSubgroupSize >= 64;
|
||||
graphics.compute_subgroup_size_control_enabled =
|
||||
features13.subgroupSizeControl == VK_TRUE &&
|
||||
(graphics.required_subgroup_size_stages & vk::ShaderStageFlagBits::eCompute) &&
|
||||
subgroup_size_control.minSubgroupSize <= 64 &&
|
||||
subgroup_size_control.maxSubgroupSize >= 64;
|
||||
graphics.compute_wave64_supported =
|
||||
graphics.subgroup_size == 64u || graphics.compute_subgroup_size_control_enabled;
|
||||
|
||||
LOGF("Vulkan subgroup: default=%u min=%u max=%u stages=0x%08x size_control=%s\n",
|
||||
LOGF("Vulkan subgroup: default=%u min=%u max=%u stages=0x%08x size_control=%s wave64=%s\n",
|
||||
graphics.subgroup_size, graphics.min_subgroup_size, graphics.max_subgroup_size,
|
||||
static_cast<vk::ShaderStageFlags::MaskType>(graphics.required_subgroup_size_stages),
|
||||
graphics.subgroup_size_control_enabled ? "true" : "false");
|
||||
graphics.compute_subgroup_size_control_enabled ? "true" : "false",
|
||||
graphics.compute_wave64_supported ? "true" : "false");
|
||||
}
|
||||
|
||||
static vk::Device VulkanCreateDevice(vk::PhysicalDevice physical_device, const VulkanExtensions& r,
|
||||
@@ -514,10 +552,6 @@ static vk::Device VulkanCreateDevice(vk::PhysicalDevice physical_device, const V
|
||||
features12.pNext = &depth_clip_control;
|
||||
features12.samplerMirrorClampToEdge = VK_TRUE;
|
||||
|
||||
vk::PhysicalDeviceSubgroupSizeControlFeatures subgroup_size_control {};
|
||||
subgroup_size_control.sType = vk::StructureType::ePhysicalDeviceSubgroupSizeControlFeatures;
|
||||
subgroup_size_control.pNext = &features12;
|
||||
|
||||
vk::PhysicalDeviceVulkan13Features supported_features13 {};
|
||||
supported_features13.sType = vk::StructureType::ePhysicalDeviceVulkan13Features;
|
||||
|
||||
@@ -526,6 +560,14 @@ static vk::Device VulkanCreateDevice(vk::PhysicalDevice physical_device, const V
|
||||
supported_features12.pNext = nullptr;
|
||||
supported_features13.pNext = &supported_features12;
|
||||
|
||||
#if !defined(__APPLE__)
|
||||
vk::PhysicalDeviceFragmentShaderBarycentricFeaturesKHR supported_fragment_barycentric {};
|
||||
supported_fragment_barycentric.sType =
|
||||
vk::StructureType::ePhysicalDeviceFragmentShaderBarycentricFeaturesKHR;
|
||||
supported_fragment_barycentric.pNext = nullptr;
|
||||
supported_features12.pNext = &supported_fragment_barycentric;
|
||||
#endif
|
||||
|
||||
const auto robustness2_ext_enabled =
|
||||
HasExtension(device_extensions, VK_EXT_ROBUSTNESS_2_EXTENSION_NAME);
|
||||
|
||||
@@ -533,7 +575,11 @@ static vk::Device VulkanCreateDevice(vk::PhysicalDevice physical_device, const V
|
||||
supported_robustness2.sType = vk::StructureType::ePhysicalDeviceRobustness2FeaturesEXT;
|
||||
supported_robustness2.pNext = nullptr;
|
||||
if (robustness2_ext_enabled) {
|
||||
#if defined(__APPLE__)
|
||||
supported_features12.pNext = &supported_robustness2;
|
||||
#else
|
||||
supported_fragment_barycentric.pNext = &supported_robustness2;
|
||||
#endif
|
||||
}
|
||||
|
||||
vk::PhysicalDeviceFeatures2 supported_features2 {};
|
||||
@@ -548,15 +594,19 @@ static vk::Device VulkanCreateDevice(vk::PhysicalDevice physical_device, const V
|
||||
supported_features13.synchronization2 != VK_TRUE);
|
||||
EXIT_NOT_IMPLEMENTED(supported_features2.features.sampleRateShading != VK_TRUE);
|
||||
EXIT_NOT_IMPLEMENTED(supported_features2.features.depthBiasClamp != VK_TRUE);
|
||||
EXIT_NOT_IMPLEMENTED(supported_features2.features.shaderClipDistance != VK_TRUE);
|
||||
EXIT_NOT_IMPLEMENTED(supported_features2.features.shaderCullDistance != VK_TRUE);
|
||||
EXIT_NOT_IMPLEMENTED(supported_features2.features.largePoints != VK_TRUE);
|
||||
EXIT_NOT_IMPLEMENTED(supported_features2.features.shaderInt64 != VK_TRUE);
|
||||
EXIT_NOT_IMPLEMENTED(supported_features2.features.vertexPipelineStoresAndAtomics != VK_TRUE);
|
||||
EXIT_NOT_IMPLEMENTED(supported_features12.shaderOutputLayer != VK_TRUE);
|
||||
EXIT_NOT_IMPLEMENTED(supported_features12.bufferDeviceAddress != VK_TRUE);
|
||||
#if !defined(__APPLE__)
|
||||
EXIT_NOT_IMPLEMENTED(supported_fragment_barycentric.fragmentShaderBarycentric != VK_TRUE);
|
||||
#endif
|
||||
features12.timelineSemaphore = VK_TRUE;
|
||||
|
||||
if (Config::GpuAssistedValidationEnabled()) {
|
||||
if (supported_features12.bufferDeviceAddress != VK_TRUE) {
|
||||
EXIT("--gpu-assisted-validation requires bufferDeviceAddress, unsupported by this "
|
||||
"device\n");
|
||||
}
|
||||
features12.bufferDeviceAddress = VK_TRUE;
|
||||
}
|
||||
features12.shaderOutputLayer = VK_TRUE;
|
||||
features12.bufferDeviceAddress = VK_TRUE;
|
||||
|
||||
vk::PhysicalDeviceFeatures device_features {};
|
||||
device_features.fragmentStoresAndAtomics = VK_TRUE;
|
||||
@@ -571,35 +621,45 @@ static vk::Device VulkanCreateDevice(vk::PhysicalDevice physical_device, const V
|
||||
device_features.tessellationShader = VK_TRUE;
|
||||
device_features.sampleRateShading = VK_TRUE;
|
||||
device_features.depthBiasClamp = VK_TRUE;
|
||||
device_features.shaderClipDistance = VK_TRUE;
|
||||
device_features.shaderCullDistance = VK_TRUE;
|
||||
device_features.largePoints = VK_TRUE;
|
||||
device_features.vertexPipelineStoresAndAtomics = VK_TRUE;
|
||||
graphics.sample_rate_shading_enabled = true;
|
||||
device_features.vertexPipelineStoresAndAtomics =
|
||||
supported_features2.features.vertexPipelineStoresAndAtomics;
|
||||
if (Config::GpuAssistedValidationEnabled()) {
|
||||
device_features.shaderInt64 = supported_features2.features.shaderInt64;
|
||||
}
|
||||
|
||||
if (graphics.subgroup_size_control_enabled &&
|
||||
supported_features13.subgroupSizeControl == VK_TRUE) {
|
||||
subgroup_size_control.subgroupSizeControl = VK_TRUE;
|
||||
}
|
||||
|
||||
const auto* base_feature_chain = (subgroup_size_control.subgroupSizeControl == VK_TRUE
|
||||
? static_cast<const void*>(&subgroup_size_control)
|
||||
: static_cast<const void*>(&features12));
|
||||
device_features.shaderInt64 = VK_TRUE;
|
||||
|
||||
vk::PhysicalDeviceRobustness2FeaturesEXT robustness2 {};
|
||||
robustness2.sType = vk::StructureType::ePhysicalDeviceRobustness2FeaturesEXT;
|
||||
robustness2.pNext = const_cast<void*>(base_feature_chain);
|
||||
#if defined(__APPLE__)
|
||||
robustness2.pNext = &features12;
|
||||
#else
|
||||
vk::PhysicalDeviceFragmentShaderBarycentricFeaturesKHR fragment_barycentric {};
|
||||
fragment_barycentric.sType =
|
||||
vk::StructureType::ePhysicalDeviceFragmentShaderBarycentricFeaturesKHR;
|
||||
fragment_barycentric.pNext = &features12;
|
||||
fragment_barycentric.fragmentShaderBarycentric = VK_TRUE;
|
||||
robustness2.pNext = &fragment_barycentric;
|
||||
#endif
|
||||
if (robustness2_ext_enabled) {
|
||||
robustness2.robustBufferAccess2 = supported_robustness2.robustBufferAccess2;
|
||||
robustness2.robustImageAccess2 = supported_robustness2.robustImageAccess2;
|
||||
robustness2.nullDescriptor = supported_robustness2.nullDescriptor;
|
||||
}
|
||||
|
||||
const bool subgroup_size_control_enabled =
|
||||
graphics.compute_subgroup_size_control_enabled &&
|
||||
supported_features13.subgroupSizeControl == VK_TRUE;
|
||||
|
||||
auto features13 = required_features13;
|
||||
features13.pNext =
|
||||
robustness2_ext_enabled ? &robustness2 : const_cast<void*>(base_feature_chain);
|
||||
features13.robustImageAccess = supported_features13.robustImageAccess;
|
||||
#if defined(__APPLE__)
|
||||
features13.pNext = robustness2_ext_enabled ? static_cast<void*>(&robustness2)
|
||||
: static_cast<void*>(&features12);
|
||||
#else
|
||||
features13.pNext = robustness2_ext_enabled ? static_cast<void*>(&robustness2)
|
||||
: static_cast<void*>(&fragment_barycentric);
|
||||
#endif
|
||||
features13.robustImageAccess = supported_features13.robustImageAccess;
|
||||
features13.subgroupSizeControl = subgroup_size_control_enabled ? VK_TRUE : VK_FALSE;
|
||||
|
||||
LOGF("Vulkan robustness: robustImageAccess=%s robustImageAccess2=%s\n",
|
||||
features13.robustImageAccess == VK_TRUE ? "true" : "false",
|
||||
@@ -843,7 +903,7 @@ void WindowContext::CreateVulkan() {
|
||||
EXIT("--spirv-debug-printf and --gpu-assisted-validation are mutually exclusive\n");
|
||||
}
|
||||
|
||||
vk::ValidationFeatureEnableEXT enabled_features[3] = {};
|
||||
vk::ValidationFeatureEnableEXT enabled_features[3] = {};
|
||||
uint32_t enabled_features_count = 0;
|
||||
#ifdef KYTY_ENABLE_BEST_PRACTICES
|
||||
enabled_features[enabled_features_count++] = vk::ValidationFeatureEnableEXT::eBestPractices;
|
||||
@@ -886,6 +946,18 @@ void WindowContext::CreateVulkan() {
|
||||
inst_info.sType = vk::StructureType::eInstanceCreateInfo;
|
||||
inst_info.pNext = (r.enable_validation_layers ? &dbg_create_info : nullptr);
|
||||
inst_info.flags = {};
|
||||
#if defined(__APPLE__)
|
||||
// MoltenVK requires VK_KHR_portability_enumeration + flag to surface
|
||||
// portability devices. Without this, enumeratePhysicalDevices hides the
|
||||
// MoltenVK adapter on some driver versions.
|
||||
if (HasExtension(r.available_extensions, VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME)) {
|
||||
if (!HasExtension(r.required_extensions, VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME)) {
|
||||
r.required_extensions.push_back(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME);
|
||||
}
|
||||
inst_info.flags |= vk::InstanceCreateFlagBits::eEnumeratePortabilityKHR;
|
||||
LOGF("Vulkan instance: enabled %s\n", VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME);
|
||||
}
|
||||
#endif
|
||||
inst_info.pApplicationInfo = &app_info;
|
||||
inst_info.enabledExtensionCount = static_cast<uint32_t>(r.required_extensions.size());
|
||||
inst_info.ppEnabledExtensionNames = r.required_extensions.data();
|
||||
@@ -931,6 +1003,7 @@ void WindowContext::CreateVulkan() {
|
||||
#else
|
||||
device_extensions.push_back(VK_EXT_DEPTH_CLIP_ENABLE_EXTENSION_NAME);
|
||||
device_extensions.push_back(VK_EXT_COLOR_WRITE_ENABLE_EXTENSION_NAME);
|
||||
device_extensions.push_back(VK_KHR_FRAGMENT_SHADER_BARYCENTRIC_EXTENSION_NAME);
|
||||
#endif
|
||||
|
||||
#ifdef KYTY_ENABLE_DEBUG_PRINTF
|
||||
@@ -948,7 +1021,15 @@ void WindowContext::CreateVulkan() {
|
||||
EXIT("Could not find suitable device");
|
||||
}
|
||||
|
||||
graphic_ctx.physical_device.getProperties(&graphic_ctx.physical_device_properties);
|
||||
vk::PhysicalDevicePushDescriptorProperties push_descriptor_properties {};
|
||||
vk::PhysicalDeviceProperties2 physical_device_properties {};
|
||||
push_descriptor_properties.sType = vk::StructureType::ePhysicalDevicePushDescriptorProperties;
|
||||
push_descriptor_properties.pNext = nullptr;
|
||||
physical_device_properties.sType = vk::StructureType::ePhysicalDeviceProperties2;
|
||||
physical_device_properties.pNext = &push_descriptor_properties;
|
||||
graphic_ctx.physical_device.getProperties2(&physical_device_properties);
|
||||
graphic_ctx.physical_device_properties = physical_device_properties.properties;
|
||||
graphic_ctx.max_push_descriptors = push_descriptor_properties.maxPushDescriptors;
|
||||
graphic_ctx.physical_device.getMemoryProperties(&graphic_ctx.physical_device_memory_properties);
|
||||
const auto& device_properties = graphic_ctx.GetPhysicalDeviceProperties();
|
||||
|
||||
@@ -971,10 +1052,6 @@ void WindowContext::CreateVulkan() {
|
||||
}
|
||||
}
|
||||
|
||||
memcpy(device_name, device_properties.deviceName, sizeof(device_name));
|
||||
std::snprintf(processor_name, sizeof(processor_name), "%s",
|
||||
Common::GetSystemInfo().ProcessorName.c_str());
|
||||
|
||||
VulkanInitSubgroupSizeControl(graphic_ctx.physical_device, graphic_ctx);
|
||||
|
||||
graphic_ctx.device = VulkanCreateDevice(graphic_ctx.physical_device, r, queue_family,
|
||||
@@ -1013,7 +1090,6 @@ void WindowContext::RecreateSurface() {
|
||||
EXIT("Could not recreate the Vulkan surface: %s\n", SDL_GetError());
|
||||
}
|
||||
surface = native_surface;
|
||||
RefreshSurfaceCapabilities();
|
||||
}
|
||||
|
||||
WindowContext::~WindowContext() {
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "SDL.h"
|
||||
#include "SDL_error.h"
|
||||
#include "SDL_events.h"
|
||||
#include "SDL_filesystem.h"
|
||||
#include "SDL_gamecontroller.h"
|
||||
#include "SDL_hints.h"
|
||||
#include "SDL_joystick.h"
|
||||
@@ -23,12 +24,12 @@
|
||||
#include "common/file.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/profiler.h"
|
||||
#include "common/stringUtils.h"
|
||||
#include "common/systemInfo.h"
|
||||
#include "common/threads.h"
|
||||
#include "common/timer.h"
|
||||
#include "graphics/host_gpu/graphicContext.h"
|
||||
#include "graphics/host_gpu/renderer/render.h"
|
||||
#include "graphics/host_gpu/renderer/renderContext.h"
|
||||
#include "graphics/host_gpu/vma.h"
|
||||
#include "graphics/host_gpu/vulkanCommon.h"
|
||||
#include "graphics/presentation/imeOverlay.h"
|
||||
@@ -39,10 +40,8 @@
|
||||
#include "libs/controller.h"
|
||||
#include "loader/systemContent.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <fmt/format.h>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
@@ -52,8 +51,6 @@
|
||||
#define STBI_NO_SIMD
|
||||
#include "stb_image.h"
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
// IWYU pragma: no_include <intrin.h>
|
||||
|
||||
#define KYTY_ENABLE_DEBUG_PRINTF
|
||||
@@ -79,7 +76,6 @@ static uint32_t ControllerButtonToPadButton(int button) {
|
||||
case SDL_CONTROLLER_BUTTON_B: return Controller::PAD_BUTTON_CIRCLE;
|
||||
case SDL_CONTROLLER_BUTTON_X: return Controller::PAD_BUTTON_SQUARE;
|
||||
case SDL_CONTROLLER_BUTTON_Y: return Controller::PAD_BUTTON_TRIANGLE;
|
||||
case SDL_CONTROLLER_BUTTON_BACK: return Controller::PAD_BUTTON_TOUCH_PAD;
|
||||
case SDL_CONTROLLER_BUTTON_START: return Controller::PAD_BUTTON_OPTIONS;
|
||||
case SDL_CONTROLLER_BUTTON_LEFTSTICK: return Controller::PAD_BUTTON_L3;
|
||||
case SDL_CONTROLLER_BUTTON_RIGHTSTICK: return Controller::PAD_BUTTON_R3;
|
||||
@@ -89,6 +85,7 @@ static uint32_t ControllerButtonToPadButton(int button) {
|
||||
case SDL_CONTROLLER_BUTTON_DPAD_DOWN: return Controller::PAD_BUTTON_DOWN;
|
||||
case SDL_CONTROLLER_BUTTON_DPAD_LEFT: return Controller::PAD_BUTTON_LEFT;
|
||||
case SDL_CONTROLLER_BUTTON_DPAD_RIGHT: return Controller::PAD_BUTTON_RIGHT;
|
||||
case SDL_CONTROLLER_BUTTON_TOUCHPAD: return Controller::PAD_BUTTON_TOUCH_PAD;
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
@@ -197,8 +194,6 @@ std::unique_ptr<WindowContext> g_window;
|
||||
} // namespace
|
||||
|
||||
constexpr const char* KYTY_SDL_WINDOW_CAPTION = "Game";
|
||||
constexpr uint32_t KYTY_SDL_WINDOW_FLAGS =
|
||||
(static_cast<uint32_t>(SDL_WINDOW_HIDDEN) | static_cast<uint32_t>(SDL_WINDOW_VULKAN));
|
||||
constexpr int KYTY_SDL_WINDOWPOS_CENTERED = SDL_WINDOWPOS_CENTERED; /*NOLINT(hicpp-signed-bitwise)*/
|
||||
|
||||
static void SetPause(WindowLoopState& game, bool flag) {
|
||||
@@ -407,6 +402,7 @@ static void GameEventDidEnterForeground(WindowLoopState& game) {
|
||||
|
||||
void WindowContext::Resize(uint32_t new_width, uint32_t new_height) {
|
||||
EXIT_IF(new_width == 0 || new_height == 0);
|
||||
Common::LockGuard lock(mutex);
|
||||
graphic_ctx.screen_width = new_width;
|
||||
graphic_ctx.screen_height = new_height;
|
||||
}
|
||||
@@ -511,6 +507,13 @@ void WindowContext::ProcessEvent(double time_s) {
|
||||
auto& game = loop;
|
||||
auto* event = &game.event;
|
||||
EXIT_IF(SDL_GetEventState(SDL_DISPLAYEVENT) != SDL_ENABLE);
|
||||
if ((event->type == SDL_KEYDOWN || event->type == SDL_KEYUP) &&
|
||||
event->key.keysym.sym == SDLK_F7) {
|
||||
if (event->type == SDL_KEYDOWN && event->key.repeat == 0) {
|
||||
HostInputToggleMouseToJoystick();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (ProcessImeInput(*event)) {
|
||||
return;
|
||||
}
|
||||
@@ -702,6 +705,16 @@ void WindowContext::ProcessEvent(double time_s) {
|
||||
break;
|
||||
}
|
||||
|
||||
case SDL_CONTROLLERTOUCHPADDOWN:
|
||||
case SDL_CONTROLLERTOUCHPADMOTION:
|
||||
case SDL_CONTROLLERTOUCHPADUP:
|
||||
if (event->ctouchpad.touchpad == 0) {
|
||||
Controller::ControllerTouchPad(event->ctouchpad.which, event->ctouchpad.finger,
|
||||
event->ctouchpad.type != SDL_CONTROLLERTOUCHPADUP,
|
||||
event->ctouchpad.x, event->ctouchpad.y);
|
||||
}
|
||||
break;
|
||||
|
||||
case SDL_CONTROLLERDEVICEADDED:
|
||||
case SDL_CONTROLLERDEVICEREMOVED:
|
||||
case SDL_CONTROLLERDEVICEREMAPPED: {
|
||||
@@ -728,8 +741,7 @@ void WindowContext::ProcessEvent(double time_s) {
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(__APPLE__)
|
||||
void WindowContext::RunOnMainThread(std::function<void()> task, bool wait) {
|
||||
void WindowContext::RunOnMainThread(std::function<void()> task) {
|
||||
if (Common::Thread::IsMainThread()) {
|
||||
task();
|
||||
return;
|
||||
@@ -747,9 +759,6 @@ void WindowContext::RunOnMainThread(std::function<void()> task, bool wait) {
|
||||
event.type = SDL_USEREVENT;
|
||||
SDL_PushEvent(&event);
|
||||
|
||||
if (!wait) {
|
||||
return;
|
||||
}
|
||||
Common::LockGuard lock(main_task_mutex);
|
||||
while (main_tasks_run < ticket) {
|
||||
main_task_done.Wait(&main_task_mutex);
|
||||
@@ -772,7 +781,6 @@ void WindowContext::DrainMainThreadTasks() {
|
||||
main_tasks_run += tasks.size();
|
||||
main_task_done.SignalAll();
|
||||
}
|
||||
#endif
|
||||
|
||||
void WindowContext::Run() {
|
||||
Common::Timer timer;
|
||||
@@ -783,9 +791,7 @@ void WindowContext::Run() {
|
||||
loop.paused.store(false, std::memory_order_release);
|
||||
|
||||
while (!loop.need_exit) {
|
||||
#if defined(__APPLE__)
|
||||
DrainMainThreadTasks();
|
||||
#endif
|
||||
if (loop.paused.load(std::memory_order_acquire)) {
|
||||
if (!timer.IsPaused()) {
|
||||
timer.Pause();
|
||||
@@ -794,8 +800,8 @@ void WindowContext::Run() {
|
||||
timer.Resume();
|
||||
}
|
||||
|
||||
if (SDL_WaitEvent(&loop.event) == 0) {
|
||||
EXIT("%s\n", SDL_GetError());
|
||||
if (!HostInputWaitEvent(&loop.event)) {
|
||||
continue;
|
||||
}
|
||||
ProcessEvent(timer.GetTimeS());
|
||||
}
|
||||
@@ -821,11 +827,26 @@ static void WindowCreate(WindowContext& context) {
|
||||
|
||||
LOGF("WindowCreate(): width = %d, height = %d\n", width, height);
|
||||
|
||||
uint32_t window_flags = KYTY_SDL_WINDOW_FLAGS;
|
||||
if (Config::FullscreenEnabled()) {
|
||||
window_flags |= static_cast<uint32_t>(SDL_WINDOW_FULLSCREEN_DESKTOP);
|
||||
}
|
||||
uint32_t window_flags = WindowContext::InitialWindowFlags(Config::FullscreenEnabled());
|
||||
#if defined(__APPLE__)
|
||||
// SDL loads Vulkan while creating a Vulkan window, so select the bundled
|
||||
// MoltenVK loader before calling SDL_CreateWindow. Keep an explicit user
|
||||
// override, and fall back to SDL's normal loader search when no bundle is present.
|
||||
if (std::getenv("SDL_VULKAN_LIBRARY") == nullptr) {
|
||||
if (char* base_path = SDL_GetBasePath(); base_path != nullptr) {
|
||||
const std::string base_path_str = base_path;
|
||||
SDL_free(base_path);
|
||||
std::string moltenvk_path = base_path_str + "libMoltenVK.dylib";
|
||||
if (!Common::File::IsFileExisting(moltenvk_path)) {
|
||||
moltenvk_path = base_path_str + "../Frameworks/libMoltenVK.dylib";
|
||||
}
|
||||
if (Common::File::IsFileExisting(moltenvk_path) &&
|
||||
SDL_setenv("SDL_VULKAN_LIBRARY", moltenvk_path.c_str(), 0) == 0) {
|
||||
LOGF("Vulkan loader: %s\n", moltenvk_path.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// macOS 26 window chrome (CoreUI asset decode, SwiftUI titlebar) has been observed
|
||||
// throwing NSExceptions under Rosetta during the first CATransaction commit. A
|
||||
// borderless window skips that machinery entirely.
|
||||
@@ -836,13 +857,20 @@ static void WindowCreate(WindowContext& context) {
|
||||
context.window = SDL_CreateWindow(KYTY_SDL_WINDOW_CAPTION, KYTY_SDL_WINDOWPOS_CENTERED,
|
||||
KYTY_SDL_WINDOWPOS_CENTERED, width, height, window_flags);
|
||||
|
||||
context.window_hidden = true;
|
||||
|
||||
if (context.window == nullptr) {
|
||||
EXIT("%s\n", SDL_GetError());
|
||||
}
|
||||
|
||||
SDL_SetWindowResizable(context.window, SDL_FALSE);
|
||||
context.UpdateIcon();
|
||||
}
|
||||
|
||||
uint32_t WindowContext::InitialWindowFlags(bool fullscreen) noexcept {
|
||||
auto flags = static_cast<uint32_t>(SDL_WINDOW_VULKAN);
|
||||
if (fullscreen) {
|
||||
flags |= static_cast<uint32_t>(SDL_WINDOW_FULLSCREEN_DESKTOP);
|
||||
}
|
||||
return flags;
|
||||
}
|
||||
|
||||
Presenter& WindowInit(uint32_t width, uint32_t height) {
|
||||
@@ -866,6 +894,7 @@ void WindowRun() {
|
||||
EXIT_IF(g_window == nullptr);
|
||||
|
||||
g_window->Run();
|
||||
g_window->render_context->GetPipelineCache().Save();
|
||||
}
|
||||
|
||||
void WindowShutdown() {
|
||||
@@ -959,6 +988,7 @@ void WindowContext::UpdateTitle() {
|
||||
Loader::SystemContentParamSfoGetString("TITLE_ID", title_id, sizeof(title_id));
|
||||
static bool has_app_ver =
|
||||
Loader::SystemContentParamSfoGetString("APP_VER", app_ver, sizeof(app_ver));
|
||||
static const std::string processor_name = Common::GetSystemInfo().ProcessorName;
|
||||
static uint64_t fps_start = Common::Timer::QueryPerformanceCounter();
|
||||
static uint64_t frame_num = 0;
|
||||
static uint64_t fps_frames = 0;
|
||||
@@ -983,19 +1013,14 @@ void WindowContext::UpdateTitle() {
|
||||
fps_frames = 0;
|
||||
}
|
||||
|
||||
auto fps = fmt::format(
|
||||
const auto* device_name = graphic_ctx.GetPhysicalDeviceProperties().deviceName.data();
|
||||
auto text = fmt::format(
|
||||
"[{} | {}] {}{}{}{}{}{}[{}] [{}], frame: {}, fps: {:f}", KYTY_BUILD_LABEL, build_type,
|
||||
(has_title ? title : ""), (has_title ? ", " : ""), (has_title_id ? title_id : ""),
|
||||
(has_title_id ? ", " : ""), (has_app_ver ? app_ver : ""), (has_app_ver ? " " : ""),
|
||||
device_name, processor_name, frame_num, current_fps);
|
||||
|
||||
#if defined(__APPLE__)
|
||||
// AppKit traps on title changes off the main thread; fire-and-forget keeps present pacing.
|
||||
RunOnMainThread([this, fps = std::move(fps)] { SDL_SetWindowTitle(window, fps.c_str()); },
|
||||
false);
|
||||
#else
|
||||
SDL_SetWindowTitle(window, fps.c_str());
|
||||
#endif
|
||||
RunOnMainThread([this, text = std::move(text)] { SDL_SetWindowTitle(window, text.c_str()); });
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
@@ -9,13 +9,10 @@
|
||||
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#if defined(__APPLE__)
|
||||
#include <functional>
|
||||
#endif
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
class Presenter;
|
||||
@@ -39,6 +36,7 @@ struct WindowContext {
|
||||
KYTY_CLASS_NO_COPY(WindowContext);
|
||||
|
||||
[[nodiscard]] static vk::PhysicalDeviceVulkan13Features RequiredVulkan13Features() noexcept;
|
||||
[[nodiscard]] static uint32_t InitialWindowFlags(bool fullscreen) noexcept;
|
||||
void CreateVulkan();
|
||||
void RecreateSurface();
|
||||
void RefreshSurfaceCapabilities();
|
||||
@@ -49,36 +47,25 @@ struct WindowContext {
|
||||
void ProcessDisplayEvent(const SDL_DisplayEvent& event);
|
||||
void ProcessEvent(double time_seconds);
|
||||
void Run();
|
||||
|
||||
#if defined(__APPLE__)
|
||||
// AppKit only allows window operations (show, icon, title, view/layer changes) on the
|
||||
// main thread; the present thread marshals them through the SDL main loop with these.
|
||||
// wait=true blocks until the task has run on the main thread.
|
||||
void RunOnMainThread(std::function<void()> task, bool wait);
|
||||
// SDL window operations must complete on the main thread.
|
||||
void RunOnMainThread(std::function<void()> task);
|
||||
void DrainMainThreadTasks();
|
||||
#endif
|
||||
|
||||
GraphicContext graphic_ctx;
|
||||
SDL_Window* window = nullptr;
|
||||
bool window_hidden = true;
|
||||
vk::SurfaceKHR surface = nullptr;
|
||||
SurfaceCapabilities surface_capabilities;
|
||||
std::unique_ptr<RenderContext> render_context;
|
||||
std::unique_ptr<Presenter> presenter;
|
||||
WindowLoopState loop;
|
||||
|
||||
char device_name[VK_MAX_PHYSICAL_DEVICE_NAME_SIZE] = {0};
|
||||
char processor_name[64] = {0};
|
||||
|
||||
Common::Mutex mutex;
|
||||
|
||||
#if defined(__APPLE__)
|
||||
Common::Mutex main_task_mutex;
|
||||
Common::CondVar main_task_done;
|
||||
std::vector<std::function<void()>> main_tasks; // guarded by main_task_mutex
|
||||
uint64_t main_tasks_queued = 0; // guarded by main_task_mutex
|
||||
uint64_t main_tasks_run = 0; // guarded by main_task_mutex
|
||||
#endif
|
||||
};
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
#include "graphics/shader/recompiler/frontend/decode/ShaderDecoder.h"
|
||||
#include "graphics/shader/recompiler/frontend/translate/Translate.h"
|
||||
#include "graphics/shader/recompiler/ir/ShaderIR.h"
|
||||
#include "graphics/shader/recompiler/ir/ValueProgram.h"
|
||||
#include "graphics/shader/recompiler/ir/passes/BindingLayout.h"
|
||||
#include "graphics/shader/recompiler/ir/passes/ConstantPropagation.h"
|
||||
#include "graphics/shader/recompiler/ir/passes/DeadCodeElimination.h"
|
||||
@@ -15,6 +14,7 @@
|
||||
#include "graphics/shader/recompiler/ir/passes/ResourceMaterialization.h"
|
||||
#include "graphics/shader/recompiler/ir/passes/ResourceTracking.h"
|
||||
#include "graphics/shader/recompiler/ir/passes/ShaderInfoCollection.h"
|
||||
#include "graphics/shader/recompiler/ir/passes/SharedMemoryBarrier.h"
|
||||
#include "graphics/shader/recompiler/ir/passes/SrtWalker.h"
|
||||
#include "graphics/shader/recompiler/ir/passes/SsaRewrite.h"
|
||||
|
||||
@@ -30,14 +30,6 @@ namespace Libs::Graphics::ShaderRecompiler {
|
||||
|
||||
namespace {
|
||||
|
||||
bool ReadZeroMemory(void*, uint64_t, uint32_t* value) {
|
||||
if (value == nullptr) {
|
||||
return false;
|
||||
}
|
||||
*value = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
const char* GetDumpLabel(const CompileOptions& options) {
|
||||
return options.dump_label != nullptr ? options.dump_label : "ShaderRecompiler";
|
||||
}
|
||||
@@ -48,7 +40,7 @@ std::string MakeIrDump(const CFG::Graph& cfg, const IR::Program& ir) {
|
||||
dump += "\nIR:\n";
|
||||
dump += fmt::format("mode={} scratch_dwords={}\n",
|
||||
ir.dispatcher_fallback ? "dispatcher" : "structured", ir.scratch_dwords);
|
||||
dump += ir.values != nullptr ? IR::ValueProgramToString(*ir.values) : IR::ProgramToString(ir);
|
||||
dump += IR::ProgramToString(ir);
|
||||
return dump;
|
||||
}
|
||||
|
||||
@@ -116,17 +108,8 @@ void ClearEmbeddedFetchVectorLanes(EmbeddedFetchVectorLanes* lanes, uint32_t reg
|
||||
lanes->erase(first, last);
|
||||
}
|
||||
|
||||
struct EmbeddedFetchLoad {
|
||||
uint32_t pc = 0;
|
||||
int attrib_id = -1;
|
||||
uint32_t components = 0;
|
||||
std::vector<uint32_t> prolog_loads;
|
||||
};
|
||||
|
||||
struct EmbeddedFetchData {
|
||||
std::vector<EmbeddedFetchLoad> loads;
|
||||
int32_t vertex_offset_sgpr = -1;
|
||||
};
|
||||
using EmbeddedFetchLoad = Frontend::EmbeddedFetchLoad;
|
||||
using EmbeddedFetchData = Frontend::EmbeddedFetchPlan;
|
||||
|
||||
bool IsDecodedSgpr(const Decoder::Operand& op) {
|
||||
return op.kind == Decoder::OperandKind::Sgpr || op.kind == Decoder::OperandKind::VccLo ||
|
||||
@@ -245,15 +228,6 @@ int BufferTableAttribFromOffset(uint32_t raw_offset, int dword) {
|
||||
return static_cast<int>((raw_offset + static_cast<uint32_t>(dword) * 4u) / 16u);
|
||||
}
|
||||
|
||||
void AppendUniquePcs(std::vector<uint32_t>& dst, const std::vector<uint32_t>& src) {
|
||||
dst.reserve(dst.size() + src.size());
|
||||
for (auto pc: src) {
|
||||
if (std::find(dst.begin(), dst.end(), pc) == dst.end()) {
|
||||
dst.push_back(pc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
EmbeddedFetchData DetectEmbeddedVertexFetch(const Decoder::Program& decoded,
|
||||
const ShaderVertexInputInfo* input_info,
|
||||
uint32_t user_data_base, uint32_t user_data_count,
|
||||
@@ -488,119 +462,16 @@ EmbeddedFetchData DetectEmbeddedVertexFetch(const Decoder::Program& decoded
|
||||
return data;
|
||||
}
|
||||
|
||||
const EmbeddedFetchLoad* FindEmbeddedFetchLoad(const std::vector<EmbeddedFetchLoad>& loads,
|
||||
uint32_t pc) {
|
||||
for (const auto& load: loads) {
|
||||
if (load.pc == pc) {
|
||||
return &load;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool EmbeddedFetchPcInList(const std::vector<uint32_t>& pcs, uint32_t pc) {
|
||||
return std::find(pcs.begin(), pcs.end(), pc) != pcs.end();
|
||||
}
|
||||
|
||||
bool IsIrFetchPrologLoad(const IR::Instruction& inst) {
|
||||
return inst.op == IR::Opcode::SLoadDword || inst.op == IR::Opcode::SBufferLoadDword;
|
||||
}
|
||||
|
||||
int ResolveEmbeddedFetchResource(const ShaderVertexInputInfo* input_info,
|
||||
const EmbeddedFetchLoad& load) {
|
||||
if (load.attrib_id >= 0 && load.attrib_id < input_info->resources_num &&
|
||||
input_info->resources_dst[load.attrib_id].attr_id == load.attrib_id) {
|
||||
return load.attrib_id;
|
||||
}
|
||||
for (int i = 0; i < input_info->resources_num; i++) {
|
||||
const auto& dst = input_info->resources_dst[i];
|
||||
if (dst.attr_id == load.attrib_id &&
|
||||
load.components <= static_cast<uint32_t>(std::max(dst.registers_num, 1))) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < input_info->resources_num; i++) {
|
||||
if (input_info->resources_dst[i].attr_id == load.attrib_id) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
uint32_t RewriteEmbeddedVertexFetches(IR::Program& ir, const ShaderVertexInputInfo* input_info,
|
||||
const std::vector<EmbeddedFetchLoad>& loads) {
|
||||
if (loads.empty()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::vector<uint32_t> prolog_pcs;
|
||||
prolog_pcs.reserve(loads.size());
|
||||
for (const auto& load: loads) {
|
||||
AppendUniquePcs(prolog_pcs, load.prolog_loads);
|
||||
}
|
||||
|
||||
auto* mutable_input_info = const_cast<ShaderVertexInputInfo*>(input_info);
|
||||
uint32_t rewritten = 0;
|
||||
for (auto& block: ir.blocks) {
|
||||
for (auto& inst: block.instructions) {
|
||||
if (IsIrFetchPrologLoad(inst) && EmbeddedFetchPcInList(prolog_pcs, inst.pc)) {
|
||||
auto pc = inst.pc;
|
||||
inst = {};
|
||||
inst.pc = pc;
|
||||
inst.op = IR::Opcode::ControlNop;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (inst.op != IR::Opcode::BufferLoadDword) {
|
||||
continue;
|
||||
}
|
||||
const auto* load = FindEmbeddedFetchLoad(loads, inst.pc);
|
||||
if (load == nullptr || inst.memory.data_dwords != load->components ||
|
||||
inst.dst.kind != IR::OperandKind::Register ||
|
||||
inst.dst.reg.file != IR::RegisterFile::Vector) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto resource_id = ResolveEmbeddedFetchResource(input_info, *load);
|
||||
if (resource_id < 0 || resource_id >= input_info->resources_num) {
|
||||
LOGF("ShaderRecompiler VS embedded fetch remap failed: pc=0x%08" PRIx32
|
||||
" attrib=%d resources=%d\n",
|
||||
inst.pc, load->attrib_id, input_info->resources_num);
|
||||
continue;
|
||||
}
|
||||
|
||||
inst.op = IR::Opcode::LoadInputF32;
|
||||
inst.input_info.attr = static_cast<uint32_t>(resource_id);
|
||||
inst.input_info.chan = 0;
|
||||
inst.input_info.component_count = load->components;
|
||||
inst.memory = {};
|
||||
inst.src_count = 0;
|
||||
mutable_input_info->resource_fetch_components[resource_id] =
|
||||
std::max(mutable_input_info->resource_fetch_components[resource_id],
|
||||
static_cast<int>(load->components));
|
||||
rewritten += load->components;
|
||||
}
|
||||
}
|
||||
|
||||
return rewritten;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool TryRecompile(std::span<const uint32_t> code, const CompileOptions& options,
|
||||
CompileResult& result, std::string* error) {
|
||||
CompileResult Recompile(std::span<const uint32_t> code, const CompileOptions& options) {
|
||||
if (code.empty()) {
|
||||
if (error != nullptr) {
|
||||
*error = "invalid shader recompiler input";
|
||||
}
|
||||
return false;
|
||||
EXIT("shader recompiler input is empty\n");
|
||||
}
|
||||
if (options.stage != ShaderType::Compute && options.stage != ShaderType::Vertex &&
|
||||
options.stage != ShaderType::Pixel) {
|
||||
if (error != nullptr) {
|
||||
*error = "shader recompiler supports compute, vertex, and pixel stages";
|
||||
}
|
||||
return false;
|
||||
EXIT("shader recompiler received unsupported stage %u\n",
|
||||
static_cast<unsigned>(options.stage));
|
||||
}
|
||||
|
||||
const auto compile_begin = std::chrono::steady_clock::now();
|
||||
@@ -615,9 +486,7 @@ bool TryRecompile(std::span<const uint32_t> code, const CompileOptions& options,
|
||||
static_cast<uint64_t>(code.size()));
|
||||
|
||||
Decoder::Program decoded;
|
||||
if (!Decoder::DecodeProgram(code, decoded, error)) {
|
||||
return false;
|
||||
}
|
||||
Decoder::DecodeProgram(code, decoded);
|
||||
LOGF("%s phase end: stage=%s hash=0x%016" PRIx64 " decode instructions=%" PRIu64
|
||||
" elapsed_ms=%" PRIu64 "\n",
|
||||
GetDumpLabel(options), StageName(options.stage), options.shader_hash,
|
||||
@@ -631,12 +500,9 @@ bool TryRecompile(std::span<const uint32_t> code, const CompileOptions& options,
|
||||
}
|
||||
}
|
||||
|
||||
CFG::Graph cfg;
|
||||
LOGF("%s phase begin: stage=%s hash=0x%016" PRIx64 " CFG BuildGraph\n", GetDumpLabel(options),
|
||||
StageName(options.stage), options.shader_hash);
|
||||
if (!CFG::BuildGraph(decoded, cfg, error)) {
|
||||
return false;
|
||||
}
|
||||
auto cfg = CFG::BuildGraph(decoded);
|
||||
LOGF("%s phase end: stage=%s hash=0x%016" PRIx64 " CFG BuildGraph blocks=%" PRIu64
|
||||
" loops=%" PRIu64 " back_edges=%" PRIu64 " elapsed_ms=%" PRIu64 "\n",
|
||||
GetDumpLabel(options), StageName(options.stage), options.shader_hash,
|
||||
@@ -649,13 +515,12 @@ bool TryRecompile(std::span<const uint32_t> code, const CompileOptions& options,
|
||||
dispatcher_reason = cfg.unsupported_reason;
|
||||
LogDispatcherFallback(options, cfg, "build", dispatcher_reason);
|
||||
} else {
|
||||
std::string structure_error;
|
||||
const auto unstructured_cfg = cfg;
|
||||
const auto unstructured_cfg = cfg;
|
||||
LOGF("%s phase begin: stage=%s hash=0x%016" PRIx64 " CFG Structurize\n",
|
||||
GetDumpLabel(options), StageName(options.stage), options.shader_hash);
|
||||
if (!CFG::Structurize(cfg, &structure_error)) {
|
||||
if (!CFG::Structurize(cfg)) {
|
||||
dispatcher_fallback = true;
|
||||
dispatcher_reason = structure_error;
|
||||
dispatcher_reason = cfg.unsupported_reason;
|
||||
const auto failure_kind = cfg.failure_kind;
|
||||
const auto failure_block = cfg.failure_block;
|
||||
LogDispatcherFallback(options, cfg, "structurize", dispatcher_reason);
|
||||
@@ -663,7 +528,7 @@ bool TryRecompile(std::span<const uint32_t> code, const CompileOptions& options,
|
||||
cfg.unsupported = true;
|
||||
cfg.failure_kind = failure_kind;
|
||||
cfg.failure_block = failure_block;
|
||||
cfg.unsupported_reason = structure_error;
|
||||
cfg.unsupported_reason = dispatcher_reason;
|
||||
} else {
|
||||
LOGF("%s structured CFG success: blocks=%" PRIu64 "\n", GetDumpLabel(options),
|
||||
static_cast<uint64_t>(cfg.blocks.size()));
|
||||
@@ -675,139 +540,105 @@ bool TryRecompile(std::span<const uint32_t> code, const CompileOptions& options,
|
||||
static_cast<uint64_t>(cfg.natural_loops.size()), phase_ms());
|
||||
}
|
||||
|
||||
IR::Program ir;
|
||||
LOGF("%s phase begin: stage=%s hash=0x%016" PRIx64 " IR LowerProgram\n", GetDumpLabel(options),
|
||||
StageName(options.stage), options.shader_hash);
|
||||
const auto lower_program = [&](IR::Program& output) {
|
||||
output = {};
|
||||
if (!IR::LowerProgram(decoded, cfg, options.stage, options.wave_size, output, error)) {
|
||||
return false;
|
||||
}
|
||||
output.lane_mask_mode = options.lane_mask_mode;
|
||||
output.shader_hash = options.shader_hash;
|
||||
output.user_data_base = options.user_data_base;
|
||||
output.user_data_count = options.user_data_count;
|
||||
output.scratch_dwords = options.scratch_dwords;
|
||||
return true;
|
||||
};
|
||||
if (!lower_program(ir)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
LOGF("%s phase end: stage=%s hash=0x%016" PRIx64 " IR LowerProgram blocks=%" PRIu64
|
||||
" elapsed_ms=%" PRIu64 "\n",
|
||||
GetDumpLabel(options), StageName(options.stage), options.shader_hash,
|
||||
static_cast<uint64_t>(ir.blocks.size()), phase_ms());
|
||||
const ShaderVertexInputInfo* vertex = nullptr;
|
||||
const ShaderPixelInputInfo* pixel = nullptr;
|
||||
const ShaderComputeInputInfo* compute = nullptr;
|
||||
switch (options.stage) {
|
||||
case ShaderType::Vertex: vertex = options.input_info.vertex; break;
|
||||
case ShaderType::Pixel: pixel = options.input_info.pixel; break;
|
||||
case ShaderType::Compute: compute = options.input_info.compute; break;
|
||||
case ShaderType::Vertex:
|
||||
vertex = options.input_info.vertex;
|
||||
break;
|
||||
case ShaderType::Pixel:
|
||||
pixel = options.input_info.pixel;
|
||||
break;
|
||||
case ShaderType::Compute:
|
||||
compute = options.input_info.compute;
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
EmbeddedFetchData embedded_fetch;
|
||||
if (options.stage == ShaderType::Vertex && vertex->fetch_embedded) {
|
||||
embedded_fetch = DetectEmbeddedVertexFetch(decoded, vertex, ir.user_data_base,
|
||||
ir.user_data_count, options.wave_size);
|
||||
auto rewritten = RewriteEmbeddedVertexFetches(ir, vertex, embedded_fetch.loads);
|
||||
if (rewritten > 0 || !embedded_fetch.loads.empty()) {
|
||||
LOGF("%s embedded vertex fetch rewrite: detected=%" PRIu64 " rewritten=%" PRIu32 "\n",
|
||||
GetDumpLabel(options), static_cast<uint64_t>(embedded_fetch.loads.size()),
|
||||
rewritten);
|
||||
embedded_fetch = DetectEmbeddedVertexFetch(decoded, vertex, options.user_data_base,
|
||||
static_cast<uint32_t>(options.user_data.size()),
|
||||
options.wave_size);
|
||||
if (!embedded_fetch.loads.empty()) {
|
||||
LOGF("%s embedded vertex fetch plan: detected=%" PRIu64 "\n", GetDumpLabel(options),
|
||||
static_cast<uint64_t>(embedded_fetch.loads.size()));
|
||||
}
|
||||
}
|
||||
ir.dispatcher_fallback = dispatcher_fallback;
|
||||
if (!dispatcher_reason.empty()) {
|
||||
ir.fallback_reason = dispatcher_reason;
|
||||
}
|
||||
|
||||
ir.values = std::make_shared<IR::ValueProgram>();
|
||||
if (!Frontend::TranslateProgram(ir, *ir.values, vertex, pixel, compute, error)) {
|
||||
return false;
|
||||
}
|
||||
ir.blocks.clear();
|
||||
IR::RewriteToSsa(ir.values->blocks);
|
||||
IR::ConstantPropagationPass(ir.values->blocks);
|
||||
for (auto& info: ir.values->block_info) {
|
||||
info.condition = info.condition.Resolve();
|
||||
info.indirect_target = info.indirect_target.Resolve();
|
||||
}
|
||||
IR::RemoveIdentities(ir.values->blocks);
|
||||
IR::EliminateDeadCode(ir.values->blocks);
|
||||
const auto read_lane_stats = IR::EliminateReadLane(*ir.values, ir.wave_size);
|
||||
Frontend::TranslateOptions translate_options {
|
||||
.stage = options.stage,
|
||||
.wave_size = options.wave_size,
|
||||
.shader_hash = options.shader_hash,
|
||||
.user_data_base = options.user_data_base,
|
||||
.user_data_count = static_cast<uint32_t>(options.user_data.size()),
|
||||
.scratch_dwords = options.scratch_dwords,
|
||||
.dispatcher_fallback = dispatcher_fallback,
|
||||
.cfg_failure_kind = cfg.failure_kind,
|
||||
.fallback_reason = dispatcher_reason.empty() ? cfg.unsupported_reason
|
||||
: dispatcher_reason,
|
||||
.vertex = vertex,
|
||||
.pixel = pixel,
|
||||
.compute = compute,
|
||||
.embedded_fetch = embedded_fetch.loads.empty() ? nullptr : &embedded_fetch,
|
||||
};
|
||||
LOGF("%s phase begin: stage=%s hash=0x%016" PRIx64 " IR TranslateProgram\n",
|
||||
GetDumpLabel(options), StageName(options.stage), options.shader_hash);
|
||||
auto ir = Frontend::TranslateProgram(decoded, cfg, translate_options);
|
||||
LOGF("%s phase end: stage=%s hash=0x%016" PRIx64 " IR TranslateProgram blocks=%" PRIu64
|
||||
" elapsed_ms=%" PRIu64 "\n",
|
||||
GetDumpLabel(options), StageName(options.stage), options.shader_hash,
|
||||
static_cast<uint64_t>(ir.blocks.size()), phase_ms());
|
||||
IR::RewriteToSsa(ir.blocks);
|
||||
IR::ConstantPropagationPass(ir.blocks);
|
||||
IR::ResolveControlFlowIdentities(ir);
|
||||
IR::RemoveIdentities(ir.blocks);
|
||||
IR::EliminateDeadCode(ir.blocks);
|
||||
const auto read_lane_stats = IR::EliminateReadLane(ir, ir.wave_size);
|
||||
if (read_lane_stats.rewritten_reads != 0) {
|
||||
LOGF("%s read-lane elimination: reads=%" PRIu32 "\n", GetDumpLabel(options),
|
||||
read_lane_stats.rewritten_reads);
|
||||
IR::ConstantPropagationPass(ir.values->blocks);
|
||||
IR::RemoveIdentities(ir.values->blocks);
|
||||
IR::EliminateDeadCode(ir.values->blocks);
|
||||
IR::ConstantPropagationPass(ir.blocks);
|
||||
IR::ResolveControlFlowIdentities(ir);
|
||||
IR::RemoveIdentities(ir.blocks);
|
||||
IR::EliminateDeadCode(ir.blocks);
|
||||
}
|
||||
std::string srt_error;
|
||||
if (!IR::BuildSrtPlan(ir, &srt_error)) {
|
||||
LOGF("%s SRT planning failed: %s\n", GetDumpLabel(options), srt_error.c_str());
|
||||
if (error != nullptr) {
|
||||
*error = std::move(srt_error);
|
||||
if (options.stage == ShaderType::Compute) {
|
||||
const auto lds_barriers =
|
||||
IR::InsertSharedMemoryBarriers(ir, ir.wave_size, *compute);
|
||||
if (lds_barriers.inserted_barriers != 0) {
|
||||
LOGF("%s wave64 LDS synchronization: barriers=%" PRIu32 "\n", GetDumpLabel(options),
|
||||
lds_barriers.inserted_barriers);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
IR::EliminateDeadCode(ir.values->blocks);
|
||||
if (!IR::TrackResources(ir, error)) {
|
||||
return false;
|
||||
}
|
||||
IR::EliminateDeadCode(ir.values->blocks);
|
||||
IR::BuildSrtPlan(ir);
|
||||
IR::EliminateDeadCode(ir.blocks);
|
||||
IR::TrackResources(ir);
|
||||
IR::EliminateDeadCode(ir.blocks);
|
||||
if (options.stage == ShaderType::Vertex) {
|
||||
ir.info.vertex_offset_sgpr = embedded_fetch.vertex_offset_sgpr;
|
||||
}
|
||||
|
||||
const IR::SrtRuntime runtime {
|
||||
.user_data = options.user_data,
|
||||
.shader_base = reinterpret_cast<uint64_t>(code.data()),
|
||||
.read_memory = options.read_memory,
|
||||
.userdata = options.read_memory_data,
|
||||
.read_specialization_memory = options.read_specialization_memory,
|
||||
};
|
||||
IR::ResourceSnapshot resources;
|
||||
if (options.resource_snapshot != nullptr) {
|
||||
resources = *options.resource_snapshot;
|
||||
} else {
|
||||
IR::SrtRuntime runtime;
|
||||
std::array<uint32_t, 64> zero_user_data {};
|
||||
if (options.user_data == nullptr) {
|
||||
for (uint32_t i = 2; i < zero_user_data.size(); i += 4) {
|
||||
zero_user_data[i] = UINT32_MAX;
|
||||
}
|
||||
}
|
||||
const auto* user_data =
|
||||
options.user_data != nullptr ? options.user_data : zero_user_data.data();
|
||||
runtime.user_data = std::span<const uint32_t>(user_data, options.user_data_count);
|
||||
runtime.shader_base = options.shader_base != 0 ? options.shader_base
|
||||
: reinterpret_cast<uint64_t>(code.data());
|
||||
runtime.read_memory = options.read_memory;
|
||||
runtime.read_specialization_memory = options.read_specialization_memory;
|
||||
if (runtime.read_memory == nullptr && options.user_data == nullptr) {
|
||||
runtime.read_memory = ReadZeroMemory;
|
||||
}
|
||||
runtime.userdata = options.read_memory_data;
|
||||
runtime.flat_memory_base = options.flat_memory_base;
|
||||
if (!IR::MaterializeResources(ir, runtime, resources, error)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!IR::SpecializeResources(ir, resources, error)) {
|
||||
return false;
|
||||
if (!IR::MaterializeResources(ir, runtime, resources)) {
|
||||
EXIT("shader resource materialization failed: stage=%s hash=0x%016" PRIx64 "\n",
|
||||
StageName(options.stage), options.shader_hash);
|
||||
}
|
||||
IR::SpecializeResources(ir, resources);
|
||||
|
||||
IR::ShaderInfoOptions info_options;
|
||||
info_options.vertex = vertex;
|
||||
info_options.pixel = pixel;
|
||||
info_options.compute = compute;
|
||||
if (!IR::CollectShaderInfo(ir, info_options, error)) {
|
||||
return false;
|
||||
}
|
||||
IR::BindingLayoutOptions layout_options;
|
||||
layout_options.descriptor_set = options.descriptor_set;
|
||||
layout_options.push_constant_offset = options.push_constant_offset;
|
||||
if (!IR::AllocateBindings(ir, layout_options, error)) {
|
||||
return false;
|
||||
}
|
||||
if (!Spirv::AnalyzeProgramRequirements(ir, error)) {
|
||||
return false;
|
||||
}
|
||||
IR::CollectShaderInfo(ir, info_options);
|
||||
IR::AllocateBindings(ir, options.push_constant_offset);
|
||||
Spirv::AnalyzeProgramRequirements(ir);
|
||||
std::string ir_dump;
|
||||
if (options.dump_ir) {
|
||||
ir_dump = MakeIrDump(cfg, ir);
|
||||
@@ -816,36 +647,22 @@ bool TryRecompile(std::span<const uint32_t> code, const CompileOptions& options,
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<uint32_t> spirv;
|
||||
std::string emit_error;
|
||||
LOGF("%s phase begin: stage=%s hash=0x%016" PRIx64 " SPIR-V EmitProgram\n",
|
||||
GetDumpLabel(options), StageName(options.stage), options.shader_hash);
|
||||
if (!Spirv::EmitProgram(ir, resources, options.input_info, spirv, &emit_error)) {
|
||||
LOGF("%s typed SPIR-V emit failed: %s\n", GetDumpLabel(options), emit_error.c_str());
|
||||
if (dispatcher_fallback && error != nullptr) {
|
||||
*error = fmt::format("dispatcher fallback failed after {}: {}",
|
||||
dispatcher_reason.c_str(), emit_error.c_str());
|
||||
LOGF("%s dispatcher fallback emit failed: %s\n", GetDumpLabel(options), error->c_str());
|
||||
} else if (error != nullptr) {
|
||||
*error = emit_error;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
auto spirv = Spirv::EmitProgram(ir, resources, options.input_info);
|
||||
LOGF("%s phase end: stage=%s hash=0x%016" PRIx64 " SPIR-V EmitProgram words=%" PRIu64
|
||||
" elapsed_ms=%" PRIu64 "\n",
|
||||
GetDumpLabel(options), StageName(options.stage), options.shader_hash,
|
||||
static_cast<uint64_t>(spirv.size()), phase_ms());
|
||||
CompileResult result;
|
||||
result.spirv = std::move(spirv);
|
||||
result.program = std::move(ir);
|
||||
result.resources = std::move(resources);
|
||||
if (options.dump_ir) {
|
||||
result.decoded_dump = std::move(decoded_dump);
|
||||
result.ir_dump = std::move(ir_dump);
|
||||
} else {
|
||||
result.decoded_dump.clear();
|
||||
result.ir_dump.clear();
|
||||
}
|
||||
return true;
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler
|
||||
@@ -6,7 +6,6 @@
|
||||
#include "graphics/shader/recompiler/ir/passes/ResourceMaterialization.h"
|
||||
#include "graphics/shader/shader.h"
|
||||
|
||||
#include <optional>
|
||||
#include <span>
|
||||
#include <vector>
|
||||
|
||||
@@ -14,24 +13,18 @@ namespace Libs::Graphics::ShaderRecompiler {
|
||||
|
||||
struct CompileOptions {
|
||||
ShaderType stage = ShaderType::Compute;
|
||||
ShaderLaneMaskMode lane_mask_mode = ShaderLaneMaskMode::NativeWave;
|
||||
uint32_t wave_size = 64;
|
||||
uint32_t user_data_base = 0;
|
||||
uint32_t user_data_count = 64;
|
||||
uint32_t scratch_dwords = 0;
|
||||
uint64_t shader_hash = 0;
|
||||
uint64_t shader_base = 0;
|
||||
std::optional<uint64_t> flat_memory_base;
|
||||
uint32_t descriptor_set = 0;
|
||||
uint32_t push_constant_offset = 0;
|
||||
uint32_t push_constant_offset = 0;
|
||||
bool dump_ir = true;
|
||||
bool early_dump = false;
|
||||
const char* dump_label = nullptr;
|
||||
const uint32_t* user_data = nullptr;
|
||||
std::span<const uint32_t> user_data;
|
||||
IR::SrtMemoryReader read_memory = nullptr;
|
||||
IR::SrtMemoryReader read_specialization_memory = nullptr;
|
||||
void* read_memory_data = nullptr;
|
||||
const IR::ResourceSnapshot* resource_snapshot = nullptr;
|
||||
ShaderStageInputInfo input_info;
|
||||
};
|
||||
|
||||
@@ -43,8 +36,8 @@ struct CompileResult {
|
||||
IR::ResourceSnapshot resources;
|
||||
};
|
||||
|
||||
bool TryRecompile(std::span<const uint32_t> code, const CompileOptions& options,
|
||||
CompileResult& result, std::string* error);
|
||||
[[nodiscard]] CompileResult Recompile(std::span<const uint32_t> code,
|
||||
const CompileOptions& options);
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler
|
||||
|
||||
|
||||
@@ -1,19 +1,23 @@
|
||||
#include "graphics/shader/recompiler/backend/spirv/SpirvEmitter.h"
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h"
|
||||
#include "graphics/shader/recompiler/ir/ValueProgram.h"
|
||||
#include "graphics/shader/shader.h"
|
||||
#include "graphics/shader/recompiler/ir/ShaderIR.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::Spirv {
|
||||
|
||||
using ShaderError::Fail;
|
||||
|
||||
namespace {
|
||||
|
||||
bool ValidateNativeProgram(const IR::Program& program, std::string* error) {
|
||||
[[noreturn]] void Fail(const IR::Program& program, const char* reason) {
|
||||
EXIT("SPIR-V validation failed: hash=0x%016" PRIx64 " stage=%u reason=%s\n",
|
||||
program.shader_hash, static_cast<unsigned>(program.stage), reason);
|
||||
std::abort();
|
||||
}
|
||||
|
||||
void ValidateNativeProgram(const IR::Program& program) {
|
||||
using Kind = IR::DescriptorBindingKind;
|
||||
constexpr auto KindCount = static_cast<size_t>(Kind::Count);
|
||||
std::array<std::vector<uint32_t>, KindCount> expected;
|
||||
@@ -36,13 +40,13 @@ bool ValidateNativeProgram(const IR::Program& program, std::string* error) {
|
||||
for (uint32_t i = 0; i < program.info.images.size(); i++) {
|
||||
const auto kind = IR::DescriptorBindingForImage(program.info.images[i]);
|
||||
if (!kind.has_value()) {
|
||||
return Fail(error, "native shader plan has an invalid image class");
|
||||
Fail(program, "native shader plan has an invalid image class");
|
||||
}
|
||||
present[static_cast<size_t>(*kind)] = true;
|
||||
const auto dynamic = program.info.images[i].mip_mode == IR::ImageMipMode::DynamicStorage;
|
||||
const auto count = dynamic ? program.info.images[i].mip_count : 1u;
|
||||
if (count == 0u || (!dynamic && program.info.images[i].mip_count != 1u)) {
|
||||
return Fail(error, "native shader plan has an invalid image mip descriptor count");
|
||||
Fail(program, "native shader plan has an invalid image mip descriptor count");
|
||||
}
|
||||
expected[static_cast<size_t>(*kind)].insert(expected[static_cast<size_t>(*kind)].end(),
|
||||
count, i);
|
||||
@@ -51,36 +55,34 @@ bool ValidateNativeProgram(const IR::Program& program, std::string* error) {
|
||||
Expect(Kind::Samplers, Dense(program.info.samplers.size()));
|
||||
}
|
||||
bool uses_gds = false;
|
||||
if (program.values != nullptr) {
|
||||
for (const auto* block: program.values->blocks) {
|
||||
for (const auto& inst: *block) {
|
||||
if (IR::SharedAccessOf(inst.GetOpcode()) == IR::SharedAccess::None) {
|
||||
continue;
|
||||
}
|
||||
const auto index = inst.Flags<IR::MemoryFlags>().index;
|
||||
if (index >= program.values->memory_info.size()) {
|
||||
return Fail(error, "shared operation has invalid memory metadata");
|
||||
}
|
||||
const auto kind = program.values->memory_info[index].kind;
|
||||
if (kind != IR::ResourceKind::Lds && kind != IR::ResourceKind::Gds) {
|
||||
return Fail(error, "shared operation has invalid resource kind");
|
||||
}
|
||||
uses_gds |= kind == IR::ResourceKind::Gds;
|
||||
for (const auto* block: program.blocks) {
|
||||
for (const auto& inst: *block) {
|
||||
if (IR::SharedAccessOf(inst.GetOpcode()) == IR::SharedAccess::None) {
|
||||
continue;
|
||||
}
|
||||
const auto index = inst.Flags<IR::MemoryFlags>().index;
|
||||
if (index >= program.memory_info.size()) {
|
||||
Fail(program, "shared operation has invalid memory metadata");
|
||||
}
|
||||
const auto kind = program.memory_info[index].kind;
|
||||
if (kind != IR::ResourceKind::Lds && kind != IR::ResourceKind::Gds) {
|
||||
Fail(program, "shared operation has invalid resource kind");
|
||||
}
|
||||
uses_gds |= kind == IR::ResourceKind::Gds;
|
||||
}
|
||||
}
|
||||
if (uses_gds) {
|
||||
Expect(Kind::Gds);
|
||||
}
|
||||
if (!program.info.addresses.empty()) {
|
||||
Expect(Kind::AddressMemory, Dense(program.info.addresses.size()));
|
||||
if (program.info.uses_dma) {
|
||||
Expect(Kind::BdaPagetable);
|
||||
Expect(Kind::FaultBuffer);
|
||||
}
|
||||
const bool uses_flattened_runtime =
|
||||
program.values != nullptr &&
|
||||
(!program.values->srt_reads.empty() ||
|
||||
!program.srt_reads.empty() ||
|
||||
std::ranges::any_of(program.info.images, [](const IR::ImageResource& image) {
|
||||
return image.indirect_mapping_capacity != 0u;
|
||||
}));
|
||||
});
|
||||
if (uses_flattened_runtime) {
|
||||
Expect(Kind::FlattenedSrt);
|
||||
}
|
||||
@@ -89,29 +91,25 @@ bool ValidateNativeProgram(const IR::Program& program, std::string* error) {
|
||||
}
|
||||
|
||||
std::array<bool, KindCount> seen {};
|
||||
for (uint32_t i = 0; i < program.bindings.descriptors.size(); i++) {
|
||||
const auto& binding = program.bindings.descriptors[i];
|
||||
const auto kind = static_cast<size_t>(binding.kind);
|
||||
for (const auto& binding: program.bindings.descriptors) {
|
||||
const auto kind = static_cast<size_t>(binding.kind);
|
||||
if (kind >= KindCount || seen[kind] || !present[kind] ||
|
||||
binding.resources != expected[kind]) {
|
||||
return Fail(error, "native descriptor groups do not match shader topology");
|
||||
}
|
||||
for (uint32_t j = 0; j < i; j++) {
|
||||
if (program.bindings.descriptors[j].binding == binding.binding) {
|
||||
return Fail(error, "native descriptor binding numbers are not unique");
|
||||
}
|
||||
Fail(program, "native descriptor groups do not match shader topology");
|
||||
}
|
||||
seen[kind] = true;
|
||||
}
|
||||
for (size_t i = 0; i < KindCount; i++) {
|
||||
if (present[i] != seen[i]) {
|
||||
return Fail(error, "native shader plan is missing a required descriptor group");
|
||||
Fail(program, "native shader plan is missing a required descriptor group");
|
||||
}
|
||||
}
|
||||
const auto has_user_storage = present[static_cast<size_t>(Kind::UserData)];
|
||||
if (program.bindings.memory_offset_dword != program.bindings.user_data_registers.size() ||
|
||||
program.bindings.memory_offset_count !=
|
||||
program.info.buffers.size() + program.info.addresses.size() ||
|
||||
if (program.bindings.push_constant_offset % sizeof(uint32_t) != 0 ||
|
||||
program.bindings.push_constant_offset + program.bindings.push_constant_size >
|
||||
IR::NativePushConstantSize ||
|
||||
program.bindings.memory_offset_dword != program.bindings.user_data_registers.size() ||
|
||||
program.bindings.memory_offset_count != program.info.buffers.size() ||
|
||||
(!has_user_storage &&
|
||||
program.bindings.push_constant_size != program.bindings.ShaderDataDwords() * 4u) ||
|
||||
(has_user_storage && program.bindings.push_constant_size != 0) ||
|
||||
@@ -120,12 +118,9 @@ bool ValidateNativeProgram(const IR::Program& program, std::string* error) {
|
||||
std::adjacent_find(program.bindings.user_data_registers.begin(),
|
||||
program.bindings.user_data_registers.end()) !=
|
||||
program.bindings.user_data_registers.end()) {
|
||||
return Fail(error, "native user-data layout is inconsistent");
|
||||
Fail(program, "native user-data layout is inconsistent");
|
||||
}
|
||||
|
||||
if (program.values == nullptr) {
|
||||
return Fail(error, "native shader plan has no typed SSA");
|
||||
}
|
||||
const auto planning_only_handle = [&](const IR::Inst& handle) {
|
||||
return !handle.Uses().empty() &&
|
||||
std::ranges::all_of(handle.Uses(), [&](const IR::Use& use) {
|
||||
@@ -135,11 +130,11 @@ bool ValidateNativeProgram(const IR::Program& program, std::string* error) {
|
||||
return false;
|
||||
}
|
||||
const auto index = use.user->Flags<IR::MemoryFlags>().index;
|
||||
return index < program.values->memory_info.size() &&
|
||||
program.values->memory_info[index].planning_only;
|
||||
return index < program.memory_info.size() &&
|
||||
program.memory_info[index].planning_only;
|
||||
});
|
||||
};
|
||||
for (const auto* block: program.values->blocks) {
|
||||
for (const auto* block: program.blocks) {
|
||||
for (const auto& inst: *block) {
|
||||
const auto dense = inst.Flags<uint32_t>();
|
||||
switch (inst.GetOpcode()) {
|
||||
@@ -148,37 +143,37 @@ bool ValidateNativeProgram(const IR::Program& program, std::string* error) {
|
||||
break;
|
||||
}
|
||||
if (dense >= program.info.buffers.size()) {
|
||||
return Fail(error, "typed buffer handle has an invalid dense resource");
|
||||
Fail(program, "typed buffer handle has an invalid dense resource");
|
||||
}
|
||||
break;
|
||||
case IR::ValueOpcode::GetAddressResource:
|
||||
if (planning_only_handle(inst)) {
|
||||
break;
|
||||
}
|
||||
if (dense >= program.info.addresses.size()) {
|
||||
return Fail(error, "typed address handle has an invalid dense resource");
|
||||
if (inst.NumArgs() != 2 || !program.info.uses_dma) {
|
||||
Fail(program, "typed address handle has invalid DMA metadata");
|
||||
}
|
||||
break;
|
||||
case IR::ValueOpcode::GetScratchResource:
|
||||
if (inst.NumArgs() != 0 || program.scratch_dwords == 0) {
|
||||
return Fail(error, "typed scratch handle has invalid shader metadata");
|
||||
Fail(program, "typed scratch handle has invalid shader metadata");
|
||||
}
|
||||
break;
|
||||
case IR::ValueOpcode::GetImageResource:
|
||||
if (dense >= program.info.images.size()) {
|
||||
return Fail(error, "typed image handle has an invalid dense resource");
|
||||
Fail(program, "typed image handle has an invalid dense resource");
|
||||
}
|
||||
break;
|
||||
case IR::ValueOpcode::GetSamplerResource:
|
||||
if (dense >= program.info.samplers.size()) {
|
||||
return Fail(error, "typed sampler handle has an invalid dense resource");
|
||||
Fail(program, "typed sampler handle has an invalid dense resource");
|
||||
}
|
||||
break;
|
||||
case IR::ValueOpcode::ReadConst: {
|
||||
const auto slot = inst.Arg(1).Resolve();
|
||||
if (!slot.IsImmediate() || slot.GetType() != IR::Type::U32 ||
|
||||
slot.U32() >= program.values->srt_reads.size()) {
|
||||
return Fail(error, "flattened SRT read has an invalid dense slot");
|
||||
slot.U32() >= program.srt_reads.size()) {
|
||||
Fail(program, "flattened SRT read has an invalid dense slot");
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -186,51 +181,45 @@ bool ValidateNativeProgram(const IR::Program& program, std::string* error) {
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool AnalyzeProgramRequirements(IR::Program& program, std::string* error) {
|
||||
void AnalyzeProgramRequirements(IR::Program& program) {
|
||||
program.spirv_requirements.reset();
|
||||
IR::SpirvRequirements requirements {};
|
||||
if (program.values == nullptr) {
|
||||
program.spirv_requirements.emplace(requirements);
|
||||
return true;
|
||||
}
|
||||
const auto MarkExactSubgroup = [&] {
|
||||
requirements.requires_exact_subgroup = true;
|
||||
requirements.subgroup_ballot = true;
|
||||
};
|
||||
for (const auto* block: program.values->blocks) {
|
||||
const auto MarkBallot = [&] { requirements.subgroup_ballot = true; };
|
||||
for (const auto* block: program.blocks) {
|
||||
for (const auto& inst: *block) {
|
||||
if (IR::AddressOpcodeInfoOf(inst.GetOpcode()).access != IR::AddressAccess::None) {
|
||||
const auto address_access = IR::AddressOpcodeInfoOf(inst.GetOpcode()).access;
|
||||
if (address_access != IR::AddressAccess::None) {
|
||||
const auto memory_index = inst.Flags<IR::MemoryFlags>().index;
|
||||
if (memory_index >= program.values->memory_info.size()) {
|
||||
return Fail(error, "address operation has invalid memory metadata");
|
||||
if (memory_index >= program.memory_info.size()) {
|
||||
Fail(program, "address operation has invalid memory metadata");
|
||||
}
|
||||
if (program.values->memory_info[memory_index].kind == IR::ResourceKind::Scratch) {
|
||||
if (program.memory_info[memory_index].kind == IR::ResourceKind::Scratch) {
|
||||
if (program.scratch_dwords == 0) {
|
||||
return Fail(error, "scratch operation has no per-thread storage");
|
||||
Fail(program, "scratch operation has no per-thread storage");
|
||||
}
|
||||
requirements.function_scratch = true;
|
||||
} else if (address_access == IR::AddressAccess::Write) {
|
||||
Fail(program, "writable FLAT/GLOBAL addresses require GPU ownership tracking");
|
||||
}
|
||||
}
|
||||
if (IR::BufferAccessOf(inst.GetOpcode()) != IR::BufferAccess::None) {
|
||||
const auto memory_index = inst.Flags<IR::MemoryFlags>().index;
|
||||
if (memory_index >= program.values->memory_info.size()) {
|
||||
return Fail(error, "buffer operation has invalid memory metadata");
|
||||
if (memory_index >= program.memory_info.size()) {
|
||||
Fail(program, "buffer operation has invalid memory metadata");
|
||||
}
|
||||
const auto& memory = program.values->memory_info[memory_index];
|
||||
const auto& memory = program.memory_info[memory_index];
|
||||
if (memory.kind == IR::ResourceKind::Buffer) {
|
||||
if (memory.resource >= program.info.buffers.size()) {
|
||||
return Fail(error, "buffer operation has invalid resource metadata");
|
||||
Fail(program, "buffer operation has invalid resource metadata");
|
||||
}
|
||||
if ((program.info.buffers[memory.resource].packed_stride & (1u << 20u)) != 0u) {
|
||||
if (program.stage != ShaderType::Compute) {
|
||||
return Fail(error, "buffer ADD_TID is only valid for compute shaders");
|
||||
Fail(program, "buffer ADD_TID is only valid for compute shaders");
|
||||
}
|
||||
requirements.requires_exact_subgroup = true;
|
||||
requirements.subgroup_local_invocation_id = true;
|
||||
}
|
||||
}
|
||||
@@ -238,29 +227,29 @@ bool AnalyzeProgramRequirements(IR::Program& program, std::string* error) {
|
||||
const auto shared_access = IR::SharedAccessOf(inst.GetOpcode());
|
||||
if (shared_access != IR::SharedAccess::None) {
|
||||
const auto index = inst.Flags<IR::MemoryFlags>().index;
|
||||
if (index >= program.values->memory_info.size()) {
|
||||
return Fail(error, "shared operation has invalid memory metadata");
|
||||
if (index >= program.memory_info.size()) {
|
||||
Fail(program, "shared operation has invalid memory metadata");
|
||||
}
|
||||
const auto kind = program.values->memory_info[index].kind;
|
||||
const auto kind = program.memory_info[index].kind;
|
||||
if (kind != IR::ResourceKind::Lds && kind != IR::ResourceKind::Gds) {
|
||||
return Fail(error, "shared operation has invalid resource kind");
|
||||
Fail(program, "shared operation has invalid resource kind");
|
||||
}
|
||||
if (program.stage != ShaderType::Compute && kind == IR::ResourceKind::Lds) {
|
||||
requirements.function_lds = true;
|
||||
}
|
||||
if (shared_access == IR::SharedAccess::Append ||
|
||||
shared_access == IR::SharedAccess::Consume) {
|
||||
MarkExactSubgroup();
|
||||
MarkBallot();
|
||||
requirements.subgroup_shuffle = true;
|
||||
requirements.subgroup_local_invocation_id = true;
|
||||
}
|
||||
}
|
||||
switch (inst.GetOpcode()) {
|
||||
case IR::ValueOpcode::Ballot: MarkExactSubgroup(); break;
|
||||
case IR::ValueOpcode::Ballot: MarkBallot(); break;
|
||||
case IR::ValueOpcode::DppMoveU32:
|
||||
case IR::ValueOpcode::ReadFirstLane:
|
||||
case IR::ValueOpcode::ReadLane: {
|
||||
MarkExactSubgroup();
|
||||
MarkBallot();
|
||||
requirements.subgroup_shuffle = true;
|
||||
if (inst.GetOpcode() == IR::ValueOpcode::DppMoveU32) {
|
||||
requirements.subgroup_local_invocation_id = true;
|
||||
@@ -270,18 +259,19 @@ bool AnalyzeProgramRequirements(IR::Program& program, std::string* error) {
|
||||
case IR::ValueOpcode::DppUpdateU32:
|
||||
case IR::ValueOpcode::WqmMask:
|
||||
case IR::ValueOpcode::WriteLane: {
|
||||
MarkExactSubgroup();
|
||||
MarkBallot();
|
||||
requirements.subgroup_local_invocation_id = true;
|
||||
break;
|
||||
}
|
||||
case IR::ValueOpcode::Permlane16U32: {
|
||||
MarkExactSubgroup();
|
||||
MarkBallot();
|
||||
requirements.subgroup_shuffle = true;
|
||||
requirements.subgroup_local_invocation_id = true;
|
||||
break;
|
||||
}
|
||||
case IR::ValueOpcode::SwizzleU32: {
|
||||
MarkExactSubgroup();
|
||||
case IR::ValueOpcode::SwizzleU32:
|
||||
case IR::ValueOpcode::BpermuteU32: {
|
||||
MarkBallot();
|
||||
requirements.subgroup_shuffle = true;
|
||||
requirements.subgroup_local_invocation_id = true;
|
||||
break;
|
||||
@@ -295,11 +285,11 @@ bool AnalyzeProgramRequirements(IR::Program& program, std::string* error) {
|
||||
break;
|
||||
case IR::ValueOpcode::SetAttribute: {
|
||||
const auto index = inst.Flags<IR::ExportFlags>().index;
|
||||
if (index >= program.values->export_info.size()) {
|
||||
return Fail(error, "attribute export has invalid metadata");
|
||||
if (index >= program.export_info.size()) {
|
||||
Fail(program, "attribute export has invalid metadata");
|
||||
}
|
||||
if (program.stage == ShaderType::Pixel &&
|
||||
program.values->export_info[index].vm) {
|
||||
program.export_info[index].vm) {
|
||||
requirements.pixel_valid_mask = true;
|
||||
}
|
||||
break;
|
||||
@@ -309,46 +299,33 @@ bool AnalyzeProgramRequirements(IR::Program& program, std::string* error) {
|
||||
}
|
||||
}
|
||||
program.spirv_requirements.emplace(requirements);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool EmitProgram(const IR::Program& program, const IR::ResourceSnapshot& resources,
|
||||
ShaderStageInputInfo input_info, std::vector<uint32_t>& spirv,
|
||||
std::string* error) {
|
||||
std::vector<uint32_t> EmitProgram(const IR::Program& program,
|
||||
const IR::ResourceSnapshot& resources,
|
||||
ShaderStageInputInfo input_info) {
|
||||
using namespace Emitter;
|
||||
|
||||
if (program.stage != ShaderType::Compute && program.stage != ShaderType::Vertex &&
|
||||
program.stage != ShaderType::Pixel) {
|
||||
SetError(error, "binary SPIR-V emitter supports compute, vertex, and pixel shaders");
|
||||
return false;
|
||||
Fail(program, "binary SPIR-V emitter supports compute, vertex, and pixel shaders");
|
||||
}
|
||||
if (!program.srt_plan_complete || !program.resource_tracking_complete ||
|
||||
!program.shader_info_complete || !program.binding_layout_complete ||
|
||||
!program.spirv_requirements.has_value()) {
|
||||
SetError(error, "SPIR-V emitter requires a fully planned native shader program");
|
||||
return false;
|
||||
Fail(program, "SPIR-V emitter requires a fully planned native shader program");
|
||||
}
|
||||
if (!IR::ValidateResourceSnapshot(program, resources, error)) {
|
||||
return false;
|
||||
if (!IR::ValidateResourceSnapshot(program, resources)) {
|
||||
Fail(program, "resource snapshot is inconsistent with the native shader plan");
|
||||
}
|
||||
if (!IR::ValidateResourceSpecialization(program, resources, error)) {
|
||||
return false;
|
||||
}
|
||||
if (!ValidateNativeProgram(program, error)) {
|
||||
return false;
|
||||
}
|
||||
if (program.values == nullptr) {
|
||||
SetError(error, "SPIR-V emitter requires planned typed SSA");
|
||||
return false;
|
||||
}
|
||||
const auto& value_program = *program.values;
|
||||
if (!IR::ValidateValueProgram(value_program, true, error)) {
|
||||
return false;
|
||||
if (!IR::ValidateResourceSpecialization(program, resources)) {
|
||||
Fail(program, "resource specialization is inconsistent with the native shader plan");
|
||||
}
|
||||
ValidateNativeProgram(program);
|
||||
IR::ValidateProgram(program, true);
|
||||
EmitterState state(program, resources, input_info);
|
||||
state.stage = program.stage;
|
||||
state.wave_size = program.wave_size;
|
||||
state.per_invocation_masks = program.lane_mask_mode == ShaderLaneMaskMode::PerInvocation;
|
||||
state.stage = program.stage;
|
||||
state.wave_size = program.wave_size;
|
||||
state.inputs.reserve(program.info.inputs.size());
|
||||
state.outputs.reserve(program.info.outputs.size());
|
||||
state.interface_variables.reserve(program.info.inputs.size() + program.info.outputs.size());
|
||||
@@ -356,17 +333,13 @@ bool EmitProgram(const IR::Program& program, const IR::ResourceSnapshot& resourc
|
||||
AllocateInputVariables(state);
|
||||
AllocateOutputVariables(state);
|
||||
DefineModule(state);
|
||||
if (!EmitValueProgram(state, value_program, error)) {
|
||||
return false;
|
||||
}
|
||||
EmitProgram(state, program);
|
||||
|
||||
auto binary = state.builder.Build();
|
||||
if (binary.empty()) {
|
||||
SetError(error, "SPIR-V builder returned an empty module");
|
||||
return false;
|
||||
Fail(program, "SPIR-V builder returned an empty module");
|
||||
}
|
||||
spirv = std::move(binary);
|
||||
return true;
|
||||
return binary;
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::Spirv
|
||||
@@ -9,10 +9,11 @@
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::Spirv {
|
||||
|
||||
bool AnalyzeProgramRequirements(IR::Program& program, std::string* error);
|
||||
void AnalyzeProgramRequirements(IR::Program& program);
|
||||
|
||||
bool EmitProgram(const IR::Program& program, const IR::ResourceSnapshot& resources,
|
||||
ShaderStageInputInfo input_info, std::vector<uint32_t>& spirv, std::string* error);
|
||||
std::vector<uint32_t> EmitProgram(const IR::Program& program,
|
||||
const IR::ResourceSnapshot& resources,
|
||||
ShaderStageInputInfo input_info);
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::Spirv
|
||||
|
||||
|
||||
@@ -223,12 +223,8 @@ F32Class EmitClassifyF32(EmitterState& state, uint32_t value) {
|
||||
const auto mantissa_bits = EmitAndConstant(state, abs_bits, 0x007fffffu);
|
||||
const auto exponent_max = EmitCompareU32Constant(state, OpIEqual, exponent_bits, 0x7f800000u);
|
||||
const auto mantissa_nonzero = EmitCompareU32Constant(state, OpINotEqual, mantissa_bits, 0);
|
||||
const auto quiet_bit_clear =
|
||||
EmitCompareU32Constant(state, OpIEqual, EmitAndConstant(state, cls.bits, 0x00400000u), 0);
|
||||
cls.nan = EmitLogicalAndBool(state, exponent_max, mantissa_nonzero);
|
||||
cls.snan = EmitLogicalAndBool(state, cls.nan, quiet_bit_clear);
|
||||
cls.zero = EmitCompareU32Constant(state, OpIEqual, abs_bits, 0);
|
||||
cls.quiet_bits = EmitOrU32(state, cls.bits, ConstantU32(state, 0x00400000u));
|
||||
cls.nan = EmitLogicalAndBool(state, exponent_max, mantissa_nonzero);
|
||||
cls.zero = EmitCompareU32Constant(state, OpIEqual, abs_bits, 0);
|
||||
return cls;
|
||||
}
|
||||
|
||||
@@ -308,12 +304,7 @@ uint32_t EmitMinMaxF32Value(EmitterState& state, uint32_t lhs, uint32_t rhs, boo
|
||||
|
||||
const auto rhs_nan_bits =
|
||||
EmitSelectValueU32(state, rhs_class.nan, lhs_class.bits, numeric_bits);
|
||||
const auto non_snan_bits =
|
||||
EmitSelectValueU32(state, lhs_class.nan, rhs_class.bits, rhs_nan_bits);
|
||||
const auto rhs_snan_bits =
|
||||
EmitSelectValueU32(state, rhs_class.snan, rhs_class.quiet_bits, non_snan_bits);
|
||||
const auto result_bits =
|
||||
EmitSelectValueU32(state, lhs_class.snan, lhs_class.quiet_bits, rhs_snan_bits);
|
||||
const auto result_bits = EmitSelectValueU32(state, lhs_class.nan, rhs_class.bits, rhs_nan_bits);
|
||||
return EmitBitcastU32ToF32(state, result_bits);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#include "graphics/guest_gpu/gpu_defs.h"
|
||||
#include "graphics/shader/recompiler/backend/spirv/SpirvEmitter.h"
|
||||
#include "graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h"
|
||||
#include "graphics/shader/recompiler/ir/ValueProgram.h"
|
||||
#include "graphics/shader/recompiler/ir/ShaderIR.h"
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter {
|
||||
|
||||
@@ -32,10 +32,9 @@ bool PixelParameterIsFlat(const EmitterState& state, uint32_t attr) {
|
||||
ShaderPixelParameterIsFlat(*state.input_info.pixel, attr);
|
||||
}
|
||||
|
||||
void SetError(std::string* error, const char* message) {
|
||||
if (error != nullptr) {
|
||||
*error = message;
|
||||
}
|
||||
bool PixelParameterIsCustom(const EmitterState& state, uint32_t attr) {
|
||||
return state.stage == ShaderType::Pixel &&
|
||||
ShaderPixelParameterIsCustom(*state.input_info.pixel, attr);
|
||||
}
|
||||
|
||||
bool HasOutput(const std::vector<OutputBinding>& outputs, IR::StageOutputKind kind,
|
||||
@@ -47,8 +46,8 @@ bool HasOutput(const std::vector<OutputBinding>& outputs, IR::StageOutputKind ki
|
||||
|
||||
void CopyProgramInputsAndOutputs(EmitterState& state, const IR::Program& program) {
|
||||
for (const auto& input: program.info.inputs) {
|
||||
state.inputs.push_back(
|
||||
{input.kind, input.location, input.component_count, 0, input.debug_name});
|
||||
state.inputs.push_back({input.kind, input.location, input.component_count, 0,
|
||||
input.debug_name, input.per_vertex});
|
||||
}
|
||||
for (const auto& output: program.info.outputs) {
|
||||
if (HasOutput(state.outputs, output.kind, output.index)) {
|
||||
@@ -59,7 +58,7 @@ void CopyProgramInputsAndOutputs(EmitterState& state, const IR::Program& program
|
||||
}
|
||||
|
||||
uint32_t OutputVariableForExport(const EmitterState& state, const IR::ExportInfo& exp) {
|
||||
if (exp.kind == IR::ExportTargetKind::Position) {
|
||||
if (exp.kind == IR::ExportTargetKind::Position && exp.index == 0) {
|
||||
return state.per_vertex_variable;
|
||||
}
|
||||
if (exp.kind == IR::ExportTargetKind::MrtZ) {
|
||||
|
||||
@@ -5,13 +5,11 @@
|
||||
namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter {
|
||||
namespace {
|
||||
|
||||
bool UserDataDwordIndex(const EmitterState& state, IR::Register reg, uint32_t& dword_index) {
|
||||
if (reg.file != IR::RegisterFile::Scalar) {
|
||||
return false;
|
||||
}
|
||||
bool UserDataDwordIndex(const EmitterState& state, IR::ScalarReg reg, uint32_t& dword_index) {
|
||||
const auto register_index = IR::RegIndex(reg);
|
||||
const auto& registers = state.program.bindings.user_data_registers;
|
||||
const auto found = std::lower_bound(registers.begin(), registers.end(), reg.index);
|
||||
if (found == registers.end() || *found != reg.index) {
|
||||
const auto found = std::lower_bound(registers.begin(), registers.end(), register_index);
|
||||
if (found == registers.end() || *found != register_index) {
|
||||
return false;
|
||||
}
|
||||
dword_index = static_cast<uint32_t>(found - registers.begin());
|
||||
@@ -58,13 +56,6 @@ uint32_t EmitWqmU64(EmitterState& state, uint32_t value) {
|
||||
return result;
|
||||
}
|
||||
|
||||
uint32_t BoolAsU32(ValueEmitContext& ctx, uint32_t value) {
|
||||
const auto result = ctx.state.builder.AllocateId();
|
||||
ctx.state.builder.AddFunction({OpSelect, TypeU32(ctx.state), result, value,
|
||||
ConstantU32(ctx.state, 1), ConstantU32(ctx.state, 0)});
|
||||
return result;
|
||||
}
|
||||
|
||||
uint32_t EmitBuiltinU32(ValueEmitContext& ctx, IR::StageInputKind kind, uint32_t component) {
|
||||
auto& state = ctx.state;
|
||||
if (kind == IR::StageInputKind::LocalInvocationIndex) {
|
||||
@@ -100,6 +91,18 @@ uint32_t EmitBuiltinU32(ValueEmitContext& ctx, IR::StageInputKind kind, uint32_t
|
||||
state.builder.AddFunction({OpBitcast, TypeU32(state), bits, value});
|
||||
return bits;
|
||||
}
|
||||
if (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({OpAccessChain,
|
||||
TypePointer(state, StorageClassInput, TypeF32(state)), pointer,
|
||||
variable, ConstantU32(state, component + 1u)});
|
||||
state.builder.AddFunction({OpLoad, TypeF32(state), value, pointer});
|
||||
state.builder.AddFunction({OpBitcast, TypeU32(state), bits, value});
|
||||
return bits;
|
||||
}
|
||||
return EmitInputComponentU32(state, kind, component);
|
||||
}
|
||||
|
||||
@@ -194,6 +197,43 @@ uint32_t EmitAttribute(ValueEmitContext& ctx, uint32_t attr, uint32_t chan) {
|
||||
if (state.stage == ShaderType::Vertex) {
|
||||
return EmitVertexParameterComponentU32(state, *input, chan & 3u);
|
||||
}
|
||||
const auto load_per_vertex = [&](uint32_t vertex) {
|
||||
const auto pointer = state.builder.AllocateId();
|
||||
const auto value = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
{OpAccessChain, TypePointer(state, StorageClassInput, TypeF32(state)), pointer,
|
||||
input->variable_id, ConstantU32(state, vertex), ConstantU32(state, chan & 3u)});
|
||||
state.builder.AddFunction({OpLoad, TypeF32(state), value, pointer});
|
||||
return value;
|
||||
};
|
||||
if (input->per_vertex) {
|
||||
const auto barycentric_kind = state.input_info.pixel->ps_no_perspective
|
||||
? IR::StageInputKind::BaryCoordNoPerspective
|
||||
: IR::StageInputKind::BaryCoordSmooth;
|
||||
const auto barycentric = InputVariableForKind(state, barycentric_kind);
|
||||
uint32_t sum = 0;
|
||||
for (uint32_t vertex = 0; vertex < 3u; vertex++) {
|
||||
const auto pointer = state.builder.AllocateId();
|
||||
const auto weight = state.builder.AllocateId();
|
||||
const auto product = state.builder.AllocateId();
|
||||
state.builder.AddFunction({OpAccessChain,
|
||||
TypePointer(state, StorageClassInput, TypeF32(state)),
|
||||
pointer, barycentric, ConstantU32(state, vertex)});
|
||||
state.builder.AddFunction({OpLoad, TypeF32(state), weight, pointer});
|
||||
state.builder.AddFunction(
|
||||
{OpFMul, TypeF32(state), product, load_per_vertex(vertex), weight});
|
||||
if (vertex == 0u) {
|
||||
sum = product;
|
||||
} else {
|
||||
const auto next = state.builder.AllocateId();
|
||||
state.builder.AddFunction({OpFAdd, TypeF32(state), next, sum, product});
|
||||
sum = next;
|
||||
}
|
||||
}
|
||||
const auto bits = state.builder.AllocateId();
|
||||
state.builder.AddFunction({OpBitcast, TypeU32(state), bits, sum});
|
||||
return bits;
|
||||
}
|
||||
const auto vector = state.builder.AllocateId();
|
||||
const auto component = state.builder.AllocateId();
|
||||
const auto bits = state.builder.AllocateId();
|
||||
@@ -203,10 +243,41 @@ uint32_t EmitAttribute(ValueEmitContext& ctx, uint32_t attr, uint32_t chan) {
|
||||
return bits;
|
||||
}
|
||||
|
||||
bool MrtUsesUint(const EmitterState& state, const IR::ExportInfo& exp) {
|
||||
return state.stage == ShaderType::Pixel && exp.kind == IR::ExportTargetKind::Mrt &&
|
||||
exp.index < std::size(state.input_info.pixel->target_output_mode) &&
|
||||
state.input_info.pixel->target_output_mode[exp.index] == 7u;
|
||||
uint32_t EmitInterpolationParameter(ValueEmitContext& ctx, uint32_t attr, uint32_t chan,
|
||||
uint32_t mode) {
|
||||
auto& state = ctx.state;
|
||||
const auto* input = InputBindingForParameter(state, attr);
|
||||
if (!input->per_vertex) {
|
||||
return EmitAttribute(ctx, attr, chan);
|
||||
}
|
||||
const auto load_vertex = [&](uint32_t vertex) {
|
||||
const auto pointer = state.builder.AllocateId();
|
||||
const auto value = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
{OpAccessChain, TypePointer(state, StorageClassInput, TypeF32(state)), pointer,
|
||||
input->variable_id, ConstantU32(state, vertex), ConstantU32(state, chan & 3u)});
|
||||
state.builder.AddFunction({OpLoad, TypeF32(state), value, pointer});
|
||||
return value;
|
||||
};
|
||||
|
||||
const auto selected_vertex = (mode + 1u) % 3u;
|
||||
uint32_t value = load_vertex(selected_vertex);
|
||||
if (!PixelParameterIsCustom(state, attr) && mode < 2u) {
|
||||
const auto delta = state.builder.AllocateId();
|
||||
state.builder.AddFunction({OpFSub, TypeF32(state), delta, value, load_vertex(0)});
|
||||
value = delta;
|
||||
}
|
||||
const auto bits = state.builder.AllocateId();
|
||||
state.builder.AddFunction({OpBitcast, TypeU32(state), bits, value});
|
||||
return bits;
|
||||
}
|
||||
|
||||
uint32_t MrtOutputMode(const EmitterState& state, const IR::ExportInfo& exp) {
|
||||
if (state.stage != ShaderType::Pixel || exp.kind != IR::ExportTargetKind::Mrt ||
|
||||
exp.index >= std::size(state.input_info.pixel->target_output_mode)) {
|
||||
return 0;
|
||||
}
|
||||
return state.input_info.pixel->target_output_mode[exp.index];
|
||||
}
|
||||
|
||||
uint32_t ExportRawComponent(ValueEmitContext& ctx, uint32_t vector, uint32_t component) {
|
||||
@@ -220,6 +291,8 @@ uint32_t ExportVector(ValueEmitContext& ctx, uint32_t data, const IR::ExportInfo
|
||||
bool uint_output) {
|
||||
auto& state = ctx.state;
|
||||
if (exp.compr && !uint_output) {
|
||||
const auto unpack =
|
||||
MrtOutputMode(state, exp) == 5u ? GlslUnpackUnorm2x16 : GlslUnpackHalf2x16;
|
||||
uint32_t f32[4] = {ConstantF32(state, 0), ConstantF32(state, 0), ConstantF32(state, 0),
|
||||
ConstantF32(state, 0x3f800000u)};
|
||||
for (uint32_t pair = 0; pair < 2u; pair++) {
|
||||
@@ -229,8 +302,8 @@ uint32_t ExportVector(ValueEmitContext& ctx, uint32_t data, const IR::ExportInfo
|
||||
const auto packed = state.builder.AllocateId();
|
||||
const auto unpacked = state.builder.AllocateId();
|
||||
state.builder.AddFunction({OpCompositeExtract, TypeU32(state), packed, data, pair});
|
||||
state.builder.AddFunction({OpExtInst, TypeF32Vector(state, 2), unpacked,
|
||||
GlslStd450(state), GlslUnpackHalf2x16, packed});
|
||||
state.builder.AddFunction(
|
||||
{OpExtInst, TypeF32Vector(state, 2), unpacked, GlslStd450(state), unpack, packed});
|
||||
for (uint32_t lane = 0; lane < 2u; lane++) {
|
||||
const auto component = pair * 2u + lane;
|
||||
if (((exp.en >> component) & 1u) != 0u) {
|
||||
@@ -292,6 +365,49 @@ uint32_t ExportVector(ValueEmitContext& ctx, uint32_t data, const IR::ExportInfo
|
||||
return vector;
|
||||
}
|
||||
|
||||
void EmitAuxPositionExport(ValueEmitContext& ctx, uint32_t data, const IR::ExportInfo& exp) {
|
||||
auto& state = ctx.state;
|
||||
for (uint32_t component = 0; component < 4; component++) {
|
||||
if ((exp.en & (1u << component)) == 0) {
|
||||
continue;
|
||||
}
|
||||
const auto output = IR::DecodePositionExportComponent(
|
||||
state.input_info.vertex->pa_cl_vs_out_cntl, exp.index, component);
|
||||
if (output.layer) {
|
||||
const auto raw = ExportRawComponent(ctx, data, component);
|
||||
const auto layer = state.builder.AllocateId();
|
||||
state.builder.AddFunction({OpBitwiseAnd, TypeU32(state), layer, raw,
|
||||
ConstantU32(state, 0x7ffu)});
|
||||
state.builder.AddFunction({OpStore, state.layer_variable, layer});
|
||||
continue;
|
||||
}
|
||||
if (!output.point_size && output.clip_distance == UINT32_MAX &&
|
||||
output.cull_distance == UINT32_MAX) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto raw = ExportRawComponent(ctx, data, component);
|
||||
const auto f32 = state.builder.AllocateId();
|
||||
state.builder.AddFunction({OpBitcast, TypeF32(state), f32, raw});
|
||||
if (output.point_size) {
|
||||
state.builder.AddFunction({OpStore, state.point_size_variable, f32});
|
||||
continue;
|
||||
}
|
||||
auto StoreDistance = [&](uint32_t variable, uint32_t index) {
|
||||
if (index == UINT32_MAX) {
|
||||
return;
|
||||
}
|
||||
const auto pointer = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
{OpAccessChain, TypePointer(state, StorageClassOutput, TypeF32(state)), pointer,
|
||||
variable, ConstantU32(state, index)});
|
||||
state.builder.AddFunction({OpStore, pointer, f32});
|
||||
};
|
||||
StoreDistance(state.clip_distance_variable, output.clip_distance);
|
||||
StoreDistance(state.cull_distance_variable, output.cull_distance);
|
||||
}
|
||||
}
|
||||
|
||||
void EmitExport(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
auto& state = ctx.state;
|
||||
const auto& exp = ctx.Export(inst);
|
||||
@@ -309,6 +425,10 @@ void EmitExport(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
}
|
||||
EmitIfCondition(state, exec, [&]() {
|
||||
const auto data = ctx.Arg(inst, 0);
|
||||
if (exp.kind == IR::ExportTargetKind::Position && exp.index != 0) {
|
||||
EmitAuxPositionExport(ctx, data, exp);
|
||||
return;
|
||||
}
|
||||
if (exp.kind == IR::ExportTargetKind::MrtZ) {
|
||||
if ((exp.en & 1u) != 0u && state.depth_variable != 0) {
|
||||
const auto raw = ExportRawComponent(ctx, data, 0);
|
||||
@@ -332,7 +452,7 @@ void EmitExport(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
if (variable == 0) {
|
||||
return;
|
||||
}
|
||||
const bool uint_output = MrtUsesUint(state, exp);
|
||||
const bool uint_output = MrtOutputMode(state, exp) == 7u;
|
||||
const auto vector_type = uint_output ? TypeU32Vector(state, 4) : TypeF32Vector(state, 4);
|
||||
auto value = ExportVector(ctx, data, exp, uint_output);
|
||||
if (state.stage == ShaderType::Pixel && exp.kind == IR::ExportTargetKind::Mrt &&
|
||||
@@ -382,7 +502,7 @@ bool EmitValueFlow(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
case IR::ValueOpcode::GetUserData: {
|
||||
const auto reg = inst.Arg(0).ScalarRegister();
|
||||
uint32_t dword = 0;
|
||||
if (!UserDataDwordIndex(state, {IR::RegisterFile::Scalar, IR::RegIndex(reg)}, dword)) {
|
||||
if (!UserDataDwordIndex(state, reg, dword)) {
|
||||
ctx.Define(inst, ConstantU32(state, 0));
|
||||
} else {
|
||||
ctx.Define(inst, EmitShaderDataDwordLoad(state, dword));
|
||||
@@ -437,16 +557,8 @@ bool EmitValueFlow(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
ctx.Define(inst, EmitSubgroupLocalInvocationId(state));
|
||||
return true;
|
||||
case IR::ValueOpcode::Ballot:
|
||||
if (state.per_invocation_masks) {
|
||||
const auto result = state.builder.AllocateId();
|
||||
state.builder.AddFunction({OpCompositeConstruct, TypeU32Vector(state, 4), result,
|
||||
BoolAsU32(ctx, ctx.Arg(inst, 0)), ConstantU32(state, 0),
|
||||
ConstantU32(state, 0), ConstantU32(state, 0)});
|
||||
ctx.Define(inst, result);
|
||||
} else {
|
||||
ctx.Emit(inst, OpGroupNonUniformBallot, IR::Type::U32x4,
|
||||
{ConstantU32(state, ScopeSubgroup), ctx.Arg(inst, 0)});
|
||||
}
|
||||
ctx.Emit(inst, OpGroupNonUniformBallot, IR::Type::U32x4,
|
||||
{ConstantU32(state, ScopeSubgroup), ctx.Arg(inst, 0)});
|
||||
return true;
|
||||
case IR::ValueOpcode::ReadFirstLane: {
|
||||
const auto ballot = state.builder.AllocateId();
|
||||
@@ -526,6 +638,10 @@ bool EmitValueFlow(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
case IR::ValueOpcode::GetAttribute:
|
||||
ctx.Define(inst, EmitAttribute(ctx, inst.Arg(0).U32(), inst.Arg(1).U32()));
|
||||
return true;
|
||||
case IR::ValueOpcode::GetInterpolationParameter:
|
||||
ctx.Define(inst, EmitInterpolationParameter(ctx, inst.Arg(0).U32(), inst.Arg(1).U32(),
|
||||
inst.Arg(2).U32()));
|
||||
return true;
|
||||
case IR::ValueOpcode::SetAttribute: EmitExport(ctx, inst); return true;
|
||||
case IR::ValueOpcode::GetShaderBase:
|
||||
// Guest S_GETPC values stay shader-relative in SPIR-V, matching the runtime ABI. The
|
||||
|
||||
@@ -439,6 +439,49 @@ uint32_t UnpackImageGather(ValueEmitContext& ctx, const IR::MemoryInfo& mem, uin
|
||||
return result;
|
||||
}
|
||||
|
||||
uint32_t EmitOneDimensionalGatherLz(ValueEmitContext& ctx, const IR::MemoryInfo& mem, uint32_t pc,
|
||||
uint32_t coord, bool integer) {
|
||||
auto& state = ctx.state;
|
||||
state.builder.RequireCapability(CapabilityImageQuery);
|
||||
const auto image = LoadSampledImageDescriptor(state, mem, pc, ImageViewKind::Dim1D);
|
||||
const auto width = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
{OpImageQuerySizeLod, TypeU32(state), width, image, ConstantU32(state, 0)});
|
||||
const auto width_f32 = state.builder.AllocateId();
|
||||
state.builder.AddFunction({OpConvertUToF, TypeF32(state), width_f32, width});
|
||||
const auto left = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
{OpExtInst, TypeF32(state), left, GlslStd450(state), GlslFloor,
|
||||
Binary(state, OpFSub, TypeF32(state),
|
||||
Binary(state, OpFMul, TypeF32(state), coord, width_f32),
|
||||
ConstantF32(state, 0x3f000000u))});
|
||||
|
||||
const auto sampled = MakeSampledImage(state, mem, pc, ImageViewKind::Dim1D);
|
||||
const auto vector_type = integer ? TypeU32Vector(state, 4) : TypeF32Vector(state, 4);
|
||||
const auto scalar_type = integer ? TypeU32(state) : TypeF32(state);
|
||||
const auto component = ImageConversionFormat(state, mem).format == Prospero::BufferFormat::kInvalid
|
||||
? ImageGatherComponent(mem.dmask)
|
||||
: 0u;
|
||||
uint32_t values[2] {};
|
||||
for (uint32_t index = 0; index < 2u; index++) {
|
||||
const auto sample_coord = Binary(
|
||||
state, OpFDiv, TypeF32(state),
|
||||
Binary(state, OpFAdd, TypeF32(state), left,
|
||||
ConstantF32(state, index == 0u ? 0x3f000000u : 0x3fc00000u)),
|
||||
width_f32);
|
||||
const auto texel = state.builder.AllocateId();
|
||||
state.builder.AddFunction({OpImageSampleExplicitLod, vector_type, texel, sampled, sample_coord,
|
||||
ImageOperandsLodMask, ZeroF32(state)});
|
||||
values[index] = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
{OpCompositeExtract, scalar_type, values[index], texel, component});
|
||||
}
|
||||
const auto result = state.builder.AllocateId();
|
||||
state.builder.AddFunction({OpCompositeConstruct, vector_type, result, values[0], values[1],
|
||||
values[1], values[0]});
|
||||
return result;
|
||||
}
|
||||
|
||||
uint32_t PackImageTexel(ValueEmitContext& ctx, const IR::MemoryInfo& mem, uint32_t texel) {
|
||||
const auto info = ImageConversionFormat(ctx.state, mem);
|
||||
if (info.format == Prospero::BufferFormat::kInvalid) return texel;
|
||||
@@ -613,6 +656,22 @@ bool EmitValueImage(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
const auto coord =
|
||||
CoordF32(ctx, mem, *address, layout.coord, ImageViewCoordinateComponents(view));
|
||||
if (op == IR::ValueOpcode::ImageGatherRaw) {
|
||||
if (view == ImageViewKind::Dim1D) {
|
||||
if (dref || !HasFlag(mem, Decoder::ImageSampleFlagLevelZero) ||
|
||||
HasFlag(mem, Decoder::ImageSampleFlagOffset) ||
|
||||
HasFlag(mem, Decoder::ImageSampleFlagGatherHorizontal)) {
|
||||
ctx.Fail(inst, "has an unsupported 1D gather variant");
|
||||
return true;
|
||||
}
|
||||
const auto sample = EmitOneDimensionalGatherLz(ctx, mem, pc, coord, integer);
|
||||
ctx.Define(inst, ResultVector(ctx, UnpackImageGather(ctx, mem, sample), integer,
|
||||
false, mem, true));
|
||||
return true;
|
||||
}
|
||||
if (view == ImageViewKind::Dim1DArray) {
|
||||
ctx.Fail(inst, "has an unsupported 1D-array gather");
|
||||
return true;
|
||||
}
|
||||
const auto sampled = MakeSampledImage(state, mem, pc, view);
|
||||
const auto sample = state.builder.AllocateId();
|
||||
std::vector<uint32_t> words =
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#include "graphics/shader/recompiler/BufferFormat.h"
|
||||
#include "graphics/shader/recompiler/backend/spirv/SpirvBuilder.h"
|
||||
#include "graphics/shader/recompiler/ir/ShaderIR.h"
|
||||
#include "graphics/shader/recompiler/ir/ValueProgram.h"
|
||||
#include "graphics/shader/recompiler/ir/ShaderIR.h"
|
||||
#include "graphics/shader/recompiler/ir/passes/BindingLayout.h"
|
||||
#include "graphics/shader/recompiler/ir/passes/ResourceMaterialization.h"
|
||||
|
||||
@@ -36,9 +36,13 @@ enum : uint32_t {
|
||||
ExecutionModeSignedZeroInfNanPreserve = 4461,
|
||||
ExecutionModeDerivativeGroupQuadsKHR = 5289,
|
||||
AddressingModelLogical = 0,
|
||||
AddressingModelPhysicalStorageBuffer64 = 5348,
|
||||
MemoryModelGLSL450 = 1,
|
||||
CapabilityShader = 1,
|
||||
CapabilityInt64 = 11,
|
||||
CapabilityImageGatherExtended = 25,
|
||||
CapabilityClipDistance = 32,
|
||||
CapabilityCullDistance = 33,
|
||||
CapabilitySampled1D = 43,
|
||||
CapabilityImage1D = 44,
|
||||
CapabilityImageQuery = 50,
|
||||
@@ -47,7 +51,10 @@ enum : uint32_t {
|
||||
CapabilityGroupNonUniformBallot = 64,
|
||||
CapabilityGroupNonUniformShuffle = 65,
|
||||
CapabilitySignedZeroInfNanPreserve = 4466,
|
||||
CapabilityShaderViewportIndexLayerEXT = 5254,
|
||||
CapabilityFragmentBarycentricKHR = 5284,
|
||||
CapabilityComputeDerivativeGroupQuadsKHR = 5288,
|
||||
CapabilityPhysicalStorageBufferAddresses = 5347,
|
||||
StorageClassUniformConstant = 0,
|
||||
StorageClassInput = 1,
|
||||
StorageClassOutput = 3,
|
||||
@@ -56,6 +63,7 @@ enum : uint32_t {
|
||||
StorageClassPushConstant = 9,
|
||||
StorageClassImage = 11,
|
||||
StorageClassStorageBuffer = 12,
|
||||
StorageClassPhysicalStorageBuffer = 5349,
|
||||
FunctionControlNone = 0,
|
||||
SelectionControlNone = 0,
|
||||
LoopControlNone = 0,
|
||||
@@ -71,10 +79,15 @@ enum : uint32_t {
|
||||
DecorationBinding = 33,
|
||||
DecorationDescriptorSet = 34,
|
||||
DecorationOffset = 35,
|
||||
DecorationPerVertexKHR = 5285,
|
||||
};
|
||||
|
||||
enum : uint32_t {
|
||||
BuiltInPosition = 0,
|
||||
BuiltInPointSize = 1,
|
||||
BuiltInClipDistance = 3,
|
||||
BuiltInCullDistance = 4,
|
||||
BuiltInLayer = 9,
|
||||
BuiltInFragCoord = 15,
|
||||
BuiltInFrontFacing = 17,
|
||||
BuiltInSampleMask = 20,
|
||||
@@ -86,6 +99,8 @@ enum : uint32_t {
|
||||
BuiltInSubgroupLocalInvocationId = 41,
|
||||
BuiltInVertexIndex = 42,
|
||||
BuiltInInstanceIndex = 43,
|
||||
BuiltInBaryCoordKHR = 5286,
|
||||
BuiltInBaryCoordNoPerspKHR = 5287,
|
||||
};
|
||||
|
||||
enum : uint32_t {
|
||||
@@ -137,7 +152,9 @@ enum : uint32_t {
|
||||
OpConstantComposite = 44,
|
||||
OpUndef = 1,
|
||||
OpFunction = 54,
|
||||
OpFunctionParameter = 55,
|
||||
OpFunctionEnd = 56,
|
||||
OpFunctionCall = 57,
|
||||
OpVariable = 59,
|
||||
OpImageTexelPointer = 60,
|
||||
OpLoad = 61,
|
||||
@@ -167,6 +184,8 @@ enum : uint32_t {
|
||||
OpConvertFToS = 110,
|
||||
OpConvertSToF = 111,
|
||||
OpConvertUToF = 112,
|
||||
OpUConvert = 113,
|
||||
OpConvertUToPtr = 120,
|
||||
OpBitcast = 124,
|
||||
OpSNegate = 126,
|
||||
OpFNegate = 127,
|
||||
@@ -245,35 +264,41 @@ enum : uint32_t {
|
||||
OpSwitch = 251,
|
||||
OpKill = 252,
|
||||
OpReturn = 253,
|
||||
OpReturnValue = 254,
|
||||
OpGroupNonUniformBallot = 339,
|
||||
OpGroupNonUniformBallotFindLSB = 343,
|
||||
OpGroupNonUniformShuffle = 345,
|
||||
};
|
||||
|
||||
enum : uint32_t {
|
||||
GlslRoundEven = 2,
|
||||
GlslTrunc = 3,
|
||||
GlslFAbs = 4,
|
||||
GlslFloor = 8,
|
||||
GlslCeil = 9,
|
||||
GlslFract = 10,
|
||||
GlslSin = 13,
|
||||
GlslCos = 14,
|
||||
GlslExp2 = 29,
|
||||
GlslLog2 = 30,
|
||||
GlslSqrt = 31,
|
||||
GlslInverseSqrt = 32,
|
||||
GlslFMin = 37,
|
||||
GlslFMax = 40,
|
||||
GlslFClamp = 43,
|
||||
GlslLdexp = 53,
|
||||
GlslFma = 50,
|
||||
GlslPackSnorm2x16 = 56,
|
||||
GlslPackUnorm2x16 = 57,
|
||||
GlslPackHalf2x16 = 58,
|
||||
GlslUnpackHalf2x16 = 62,
|
||||
GlslFindILsb = 73,
|
||||
GlslFindUMsb = 75,
|
||||
MemoryAccessAlignedMask = 0x2,
|
||||
};
|
||||
|
||||
enum : uint32_t {
|
||||
GlslRoundEven = 2,
|
||||
GlslTrunc = 3,
|
||||
GlslFAbs = 4,
|
||||
GlslFloor = 8,
|
||||
GlslCeil = 9,
|
||||
GlslFract = 10,
|
||||
GlslSin = 13,
|
||||
GlslCos = 14,
|
||||
GlslExp2 = 29,
|
||||
GlslLog2 = 30,
|
||||
GlslSqrt = 31,
|
||||
GlslInverseSqrt = 32,
|
||||
GlslFMin = 37,
|
||||
GlslFMax = 40,
|
||||
GlslFClamp = 43,
|
||||
GlslLdexp = 53,
|
||||
GlslFma = 50,
|
||||
GlslPackSnorm2x16 = 56,
|
||||
GlslPackUnorm2x16 = 57,
|
||||
GlslPackHalf2x16 = 58,
|
||||
GlslUnpackUnorm2x16 = 61,
|
||||
GlslUnpackHalf2x16 = 62,
|
||||
GlslFindILsb = 73,
|
||||
GlslFindUMsb = 75,
|
||||
};
|
||||
|
||||
struct InputBinding {
|
||||
@@ -282,6 +307,7 @@ struct InputBinding {
|
||||
uint32_t component_count = 1;
|
||||
uint32_t variable_id = 0;
|
||||
std::string debug_name;
|
||||
bool per_vertex = false;
|
||||
};
|
||||
|
||||
struct OutputBinding {
|
||||
@@ -324,41 +350,47 @@ struct EmitterState {
|
||||
: program(program_), resources(resources_), input_info(input_info_),
|
||||
requirements(*program_.spirv_requirements) {}
|
||||
|
||||
Builder builder;
|
||||
const IR::Program& program;
|
||||
const IR::ResourceSnapshot& resources;
|
||||
ShaderStageInputInfo input_info;
|
||||
const IR::SpirvRequirements& requirements;
|
||||
ShaderType stage = ShaderType::Unknown;
|
||||
uint32_t wave_size = 64;
|
||||
bool per_invocation_masks = false;
|
||||
uint32_t storage_buffer_variable = 0;
|
||||
std::array<uint32_t, IR::ShaderInfo::MaxBuffers + IR::ShaderInfo::MaxAddresses>
|
||||
memory_byte_offsets {};
|
||||
uint32_t address_memory_variable = 0;
|
||||
uint32_t gds_variable = 0;
|
||||
uint32_t gds_length = 0;
|
||||
uint32_t push_constant_variable = 0;
|
||||
uint32_t vsharp_storage_variable = 0;
|
||||
uint32_t flattened_srt_variable = 0;
|
||||
uint32_t lds_variable = 0;
|
||||
uint32_t scratch_variable = 0;
|
||||
Builder builder;
|
||||
const IR::Program& program;
|
||||
const IR::ResourceSnapshot& resources;
|
||||
ShaderStageInputInfo input_info;
|
||||
const IR::SpirvRequirements& requirements;
|
||||
ShaderType stage = ShaderType::Unknown;
|
||||
uint32_t wave_size = 64;
|
||||
uint32_t storage_buffer_variable = 0;
|
||||
std::array<uint32_t, IR::ShaderInfo::MaxBuffers> memory_byte_offsets {};
|
||||
uint32_t bda_pagetable_variable = 0;
|
||||
uint32_t fault_buffer_variable = 0;
|
||||
uint32_t bda_pointer_function = 0;
|
||||
uint32_t gds_variable = 0;
|
||||
uint32_t gds_length = 0;
|
||||
uint32_t push_constant_variable = 0;
|
||||
uint32_t vsharp_storage_variable = 0;
|
||||
uint32_t flattened_srt_variable = 0;
|
||||
uint32_t lds_variable = 0;
|
||||
uint32_t scratch_variable = 0;
|
||||
std::array<uint32_t, 2u * SampledImageViewKindCount> sampled_image_variables {};
|
||||
std::array<uint32_t,
|
||||
static_cast<uint32_t>(StorageImageClass::Count) * StorageImageViewKindCount>
|
||||
storage_image_variables {};
|
||||
uint32_t sampler_variable = 0;
|
||||
uint32_t main_func = 0;
|
||||
uint32_t entry_label = 0;
|
||||
uint32_t current_label = 0;
|
||||
uint32_t pixel_valid_mask_variable = 0;
|
||||
uint32_t subgroup_local_invocation_id_variable = 0;
|
||||
uint32_t per_vertex_variable = 0;
|
||||
uint32_t depth_variable = 0;
|
||||
uint32_t sample_mask_variable = 0;
|
||||
std::vector<InputBinding> inputs;
|
||||
std::vector<OutputBinding> outputs;
|
||||
std::vector<uint32_t> interface_variables;
|
||||
storage_image_variables {};
|
||||
uint32_t sampler_variable = 0;
|
||||
uint32_t main_func = 0;
|
||||
uint32_t entry_label = 0;
|
||||
uint32_t current_label = 0;
|
||||
uint32_t pixel_valid_mask_variable = 0;
|
||||
uint32_t subgroup_local_invocation_id_variable = 0;
|
||||
uint32_t per_vertex_variable = 0;
|
||||
uint32_t point_size_variable = 0;
|
||||
uint32_t clip_distance_variable = 0;
|
||||
uint32_t cull_distance_variable = 0;
|
||||
uint32_t layer_variable = 0;
|
||||
uint32_t clip_distance_count = 0;
|
||||
uint32_t cull_distance_count = 0;
|
||||
uint32_t depth_variable = 0;
|
||||
uint32_t sample_mask_variable = 0;
|
||||
std::vector<InputBinding> inputs;
|
||||
std::vector<OutputBinding> outputs;
|
||||
std::vector<uint32_t> interface_variables;
|
||||
};
|
||||
|
||||
uint32_t TypeVoid(EmitterState& state);
|
||||
@@ -366,6 +398,7 @@ uint32_t TypeBool(EmitterState& state);
|
||||
uint32_t TypeBoolVector(EmitterState& state, uint32_t components);
|
||||
uint32_t TypeU32(EmitterState& state);
|
||||
uint32_t TypeU64(EmitterState& state);
|
||||
uint32_t TypeDeviceAddress(EmitterState& state);
|
||||
uint32_t TypeU32Pair(EmitterState& state);
|
||||
uint32_t TypeI32(EmitterState& state);
|
||||
uint32_t TypeI32Pair(EmitterState& state);
|
||||
@@ -379,6 +412,8 @@ uint32_t TypePointer(EmitterState& state, uint32_t storage_class, uint32_t point
|
||||
uint32_t TypeFunction(EmitterState& state);
|
||||
uint32_t TypeStorageBufferPointer(EmitterState& state);
|
||||
uint32_t TypeStorageBufferElementPointer(EmitterState& state);
|
||||
uint32_t TypeDeviceAddressStoragePointer(EmitterState& state);
|
||||
uint32_t TypePhysicalU32Pointer(EmitterState& state);
|
||||
uint32_t TypePushConstantElementPointer(EmitterState& state);
|
||||
uint32_t TypeU32ArrayPointer(EmitterState& state, uint32_t storage_class, uint32_t dwords);
|
||||
uint32_t TypeU32ElementPointer(EmitterState& state, uint32_t storage_class);
|
||||
@@ -389,7 +424,7 @@ inline void EmitLabel(EmitterState& state, uint32_t label) {
|
||||
}
|
||||
|
||||
struct ValueEmitContext {
|
||||
ValueEmitContext(EmitterState& state_, const IR::ValueProgram& program_)
|
||||
ValueEmitContext(EmitterState& state_, const IR::Program& program_)
|
||||
: state(state_), program(program_) {}
|
||||
|
||||
uint32_t Def(IR::Value value);
|
||||
@@ -404,18 +439,17 @@ struct ValueEmitContext {
|
||||
const IR::MemoryInfo& Memory(const IR::Inst& inst) const;
|
||||
const IR::ExportInfo& Export(const IR::Inst& inst) const;
|
||||
uint32_t Label(const IR::Block* block) const;
|
||||
void Fail(const IR::Inst& inst, const char* reason);
|
||||
[[noreturn]] void Fail(const char* reason) const;
|
||||
[[noreturn]] void Fail(const IR::Inst& inst, const char* reason) const;
|
||||
|
||||
EmitterState& state;
|
||||
const IR::ValueProgram& program;
|
||||
const IR::Program& program;
|
||||
std::unordered_map<const IR::Inst*, uint32_t> definitions;
|
||||
std::unordered_map<const IR::Block*, uint32_t> labels;
|
||||
const std::unordered_map<const IR::Inst*, uint32_t>* dispatcher_spills = nullptr;
|
||||
std::unordered_map<const IR::Inst*, std::pair<uint32_t, uint32_t>> dispatcher_block_loads;
|
||||
const IR::Block* current_block = nullptr;
|
||||
uint32_t scratch_u32_variable = 0;
|
||||
bool failed = false;
|
||||
std::string error;
|
||||
};
|
||||
|
||||
enum class VertexInputScalarKind { Float, Sint, Uint };
|
||||
@@ -551,11 +585,9 @@ constexpr uint32_t ImageSpirvMultisampled(ImageViewKind view) {
|
||||
}
|
||||
|
||||
struct F32Class {
|
||||
uint32_t bits = 0;
|
||||
uint32_t nan = 0;
|
||||
uint32_t snan = 0;
|
||||
uint32_t zero = 0;
|
||||
uint32_t quiet_bits = 0;
|
||||
uint32_t bits = 0;
|
||||
uint32_t nan = 0;
|
||||
uint32_t zero = 0;
|
||||
};
|
||||
|
||||
uint32_t PixelParameterMappedLocation(const EmitterState& state, uint32_t attr);
|
||||
@@ -564,6 +596,8 @@ uint32_t PixelParameterLocation(const EmitterState& state, uint32_t attr);
|
||||
|
||||
bool PixelParameterIsFlat(const EmitterState& state, uint32_t attr);
|
||||
|
||||
bool PixelParameterIsCustom(const EmitterState& state, uint32_t attr);
|
||||
|
||||
VertexInputScalarKind VertexParameterScalarKind(const EmitterState& state, uint32_t location);
|
||||
|
||||
uint32_t VertexParameterComponentCount(const EmitterState& state, const InputBinding& input);
|
||||
@@ -578,8 +612,6 @@ uint32_t VertexParameterVectorOrScalarType(EmitterState& state, VertexInputScala
|
||||
uint32_t VertexParameterInputPointerType(EmitterState& state, VertexInputScalarKind kind,
|
||||
uint32_t components);
|
||||
|
||||
void SetError(std::string* error, const char* message);
|
||||
|
||||
bool HasOutput(const std::vector<OutputBinding>& outputs, IR::StageOutputKind kind, uint32_t index);
|
||||
|
||||
void CopyProgramInputsAndOutputs(EmitterState& state, const IR::Program& program);
|
||||
@@ -667,8 +699,8 @@ void AddInputAnnotationsAndNames(EmitterState& state);
|
||||
|
||||
void AddOutputAnnotationsAndNames(EmitterState& state);
|
||||
|
||||
void DecorateDescriptor(EmitterState& state, uint32_t variable, const char* name, uint32_t set,
|
||||
uint32_t binding);
|
||||
void DecorateDescriptor(EmitterState& state, uint32_t variable, const char* name,
|
||||
IR::DescriptorBindingKind kind);
|
||||
|
||||
void AddDescriptorAnnotationsAndNames(EmitterState& state);
|
||||
|
||||
@@ -829,7 +861,9 @@ bool EmitValueMemory(ValueEmitContext& ctx, const IR::Inst& inst);
|
||||
|
||||
bool EmitValueImage(ValueEmitContext& ctx, const IR::Inst& inst);
|
||||
|
||||
bool EmitValueProgram(EmitterState& state, const IR::ValueProgram& program, std::string* error);
|
||||
void EmitProgram(EmitterState& state, const IR::Program& program);
|
||||
|
||||
void DefineGetBdaPointer(EmitterState& state);
|
||||
|
||||
// These templates accept local lambdas from several emitter translation units.
|
||||
template <typename Fn>
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#include "graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h"
|
||||
|
||||
#include "graphics/host_gpu/renderer/cache/bufferCache.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter {
|
||||
@@ -28,6 +30,19 @@ uint32_t AndCondition(EmitterState& state, uint32_t lhs, uint32_t rhs) {
|
||||
return Binary(state, OpLogicalAnd, TypeBool(state), lhs, rhs);
|
||||
}
|
||||
|
||||
uint32_t EmitDsMaskedLaneRead(EmitterState& state, uint32_t source, uint32_t target,
|
||||
uint32_t exec) {
|
||||
const auto shuffled = state.builder.AllocateId();
|
||||
state.builder.AddFunction({OpGroupNonUniformShuffle, TypeU32(state), shuffled,
|
||||
ConstantU32(state, ScopeSubgroup), source, target});
|
||||
const auto source_exec = state.builder.AllocateId();
|
||||
state.builder.AddFunction({OpGroupNonUniformShuffle, TypeBool(state), source_exec,
|
||||
ConstantU32(state, ScopeSubgroup), exec, target});
|
||||
const auto source_active =
|
||||
AndCondition(state, source_exec, EmitSubgroupLaneActiveBool(state, target));
|
||||
return Select(state, TypeU32(state), source_active, shuffled, ConstantU32(state, 0));
|
||||
}
|
||||
|
||||
uint32_t BufferByteAddress(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryInfo& mem,
|
||||
uint32_t index, uint32_t offset, uint32_t soffset) {
|
||||
auto& state = ctx.state;
|
||||
@@ -95,60 +110,146 @@ uint32_t AddU64Low(EmitterState& state, uint32_t low, uint32_t high, uint32_t ad
|
||||
return result;
|
||||
}
|
||||
|
||||
uint32_t AddressByteAddress(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryInfo& mem,
|
||||
uint32_t low, uint32_t high) {
|
||||
uint32_t ScratchByteAddress(ValueEmitContext& ctx, const IR::MemoryInfo& mem, uint32_t low,
|
||||
uint32_t high) {
|
||||
auto& state = ctx.state;
|
||||
auto immediate = static_cast<int32_t>(mem.offset);
|
||||
if (mem.kind == IR::ResourceKind::ScalarAddress) {
|
||||
immediate = static_cast<int32_t>(static_cast<uint32_t>(immediate) & ~3u);
|
||||
low = Binary(state, OpBitwiseAnd, TypeU32(state), low, ConstantU32(state, ~3u));
|
||||
}
|
||||
const auto immediate_low = ConstantU32(state, static_cast<uint32_t>(immediate));
|
||||
const auto immediate_high = ConstantU32(state, immediate < 0 ? UINT32_MAX : 0u);
|
||||
if (mem.kind == IR::ResourceKind::Scratch) {
|
||||
low = AddU64Low(state, low, high, immediate_low, immediate_high, high);
|
||||
const auto valid = Binary(state, OpIEqual, TypeBool(state), high, ConstantU32(state, 0));
|
||||
return Select(state, TypeU32(state), valid, low, ConstantU32(state, UINT32_MAX));
|
||||
}
|
||||
const auto base = state.program.info.addresses[mem.resource].specialized_base;
|
||||
if (mem.kind == IR::ResourceKind::Flat ||
|
||||
(mem.kind == IR::ResourceKind::Global &&
|
||||
state.program.info.addresses[mem.resource].unbased)) {
|
||||
low = AddU64Low(state, low, high, immediate_low, immediate_high, high);
|
||||
const auto base_low = ConstantU32(state, static_cast<uint32_t>(base));
|
||||
const auto base_high = ConstantU32(state, static_cast<uint32_t>(base >> 32u));
|
||||
const auto relative = Binary(state, OpISub, TypeU32(state), low, base_low);
|
||||
const auto borrow = Binary(state, OpULessThan, TypeBool(state), low, base_low);
|
||||
const auto relative_high = Binary(
|
||||
state, OpISub, TypeU32(state), Binary(state, OpISub, TypeU32(state), high, base_high),
|
||||
Select(state, TypeU32(state), borrow, ConstantU32(state, 1), ConstantU32(state, 0)));
|
||||
const auto valid =
|
||||
Binary(state, OpIEqual, TypeBool(state), relative_high, ConstantU32(state, 0));
|
||||
return Select(state, TypeU32(state), valid, relative, ConstantU32(state, UINT32_MAX));
|
||||
}
|
||||
const auto initial = base + static_cast<uint64_t>(static_cast<int64_t>(immediate));
|
||||
auto relative_high = ConstantU32(state, static_cast<uint32_t>(initial >> 32u));
|
||||
const auto relative = AddU64Low(state, ConstantU32(state, static_cast<uint32_t>(initial)),
|
||||
relative_high, low, ConstantU32(state, 0), relative_high);
|
||||
low = AddU64Low(state, low, high, immediate_low, immediate_high, high);
|
||||
const auto valid =
|
||||
Binary(state, OpIEqual, TypeBool(state), relative_high, ConstantU32(state, 0));
|
||||
return Select(state, TypeU32(state), valid, relative, ConstantU32(state, UINT32_MAX));
|
||||
Binary(state, OpIEqual, TypeBool(state), high, ConstantU32(state, 0));
|
||||
return Select(state, TypeU32(state), valid, low, ConstantU32(state, UINT32_MAX));
|
||||
}
|
||||
|
||||
uint32_t AddAddressMemoryOffset(ValueEmitContext& ctx, const IR::MemoryInfo& mem,
|
||||
uint32_t address) {
|
||||
auto& state = ctx.state;
|
||||
const auto binding =
|
||||
ResourceForDescriptor(state, IR::DescriptorBindingKind::AddressMemory, mem.resource);
|
||||
const auto offset =
|
||||
state.memory_byte_offsets[state.program.info.buffers.size() + binding.array_index];
|
||||
const auto corrected = Binary(state, OpIAdd, TypeU32(state), address, offset);
|
||||
const auto not_invalid =
|
||||
Binary(state, OpINotEqual, TypeBool(state), address, ConstantU32(state, UINT32_MAX));
|
||||
const auto no_overflow =
|
||||
Binary(state, OpUGreaterThanEqual, TypeBool(state), corrected, address);
|
||||
return Select(state, TypeU32(state), AndCondition(state, not_invalid, no_overflow), corrected,
|
||||
ConstantU32(state, UINT32_MAX));
|
||||
uint32_t ConstantDeviceAddress(EmitterState& state, uint64_t value) {
|
||||
return state.builder.Constant(OpConstant, TypeDeviceAddress(state),
|
||||
{static_cast<uint32_t>(value),
|
||||
static_cast<uint32_t>(value >> 32u)});
|
||||
}
|
||||
|
||||
uint32_t DeviceAddressFromWords(EmitterState& state, uint32_t low, uint32_t high) {
|
||||
const auto low64 = Unary(state, OpUConvert, TypeDeviceAddress(state), low);
|
||||
const auto high64 = Binary(state, OpShiftLeftLogical, TypeDeviceAddress(state),
|
||||
Unary(state, OpUConvert, TypeDeviceAddress(state), high),
|
||||
ConstantDeviceAddress(state, 32));
|
||||
return Binary(state, OpBitwiseOr, TypeDeviceAddress(state), low64, high64);
|
||||
}
|
||||
|
||||
uint32_t GuestAddress(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryInfo& mem) {
|
||||
auto& state = ctx.state;
|
||||
auto low = ctx.Arg(inst, 1);
|
||||
if (mem.kind == IR::ResourceKind::ScalarAddress) {
|
||||
low = Binary(state, OpBitwiseAnd, TypeU32(state), low, ConstantU32(state, ~3u));
|
||||
}
|
||||
uint32_t address = 0;
|
||||
if (mem.address_is_full) {
|
||||
address = DeviceAddressFromWords(state, low, ctx.Arg(inst, 2));
|
||||
} else {
|
||||
const auto* handle = inst.Arg(0).Resolve().TryInstruction();
|
||||
if (handle == nullptr || handle->GetOpcode() != IR::ValueOpcode::GetAddressResource ||
|
||||
handle->NumArgs() != 2) {
|
||||
ctx.Fail(inst, "has no address base pair");
|
||||
return ConstantDeviceAddress(state, 0);
|
||||
}
|
||||
const auto base = DeviceAddressFromWords(state, ctx.Arg(*handle, 0), ctx.Arg(*handle, 1));
|
||||
address = Binary(state, OpIAdd, TypeDeviceAddress(state), base,
|
||||
Unary(state, OpUConvert, TypeDeviceAddress(state), low));
|
||||
}
|
||||
auto immediate = static_cast<int32_t>(mem.offset);
|
||||
if (mem.kind == IR::ResourceKind::ScalarAddress) {
|
||||
immediate = static_cast<int32_t>(static_cast<uint32_t>(immediate) & ~3u);
|
||||
}
|
||||
return immediate == 0
|
||||
? address
|
||||
: Binary(state, OpIAdd, TypeDeviceAddress(state), address,
|
||||
ConstantDeviceAddress(state,
|
||||
static_cast<uint64_t>(static_cast<int64_t>(immediate))));
|
||||
}
|
||||
|
||||
uint32_t FaultElementPointer(EmitterState& state, uint32_t index) {
|
||||
const auto pointer = state.builder.AllocateId();
|
||||
state.builder.AddFunction({OpAccessChain, TypeStorageBufferElementPointer(state), pointer,
|
||||
state.fault_buffer_variable, ConstantU32(state, 0), index});
|
||||
return pointer;
|
||||
}
|
||||
|
||||
void RecordBdaFault(EmitterState& state, uint32_t page) {
|
||||
const auto word = Binary(state, OpShiftRightLogical, TypeU32(state), page,
|
||||
ConstantU32(state, 5));
|
||||
const auto bit = Binary(
|
||||
state, OpShiftLeftLogical, TypeU32(state), ConstantU32(state, 1),
|
||||
Binary(state, OpBitwiseAnd, TypeU32(state), page, ConstantU32(state, 31)));
|
||||
const auto pointer = FaultElementPointer(state, word);
|
||||
const auto value = state.builder.AllocateId();
|
||||
state.builder.AddFunction({OpLoad, TypeU32(state), value, pointer});
|
||||
state.builder.AddFunction(
|
||||
{OpStore, pointer, Binary(state, OpBitwiseOr, TypeU32(state), value, bit)});
|
||||
}
|
||||
|
||||
uint32_t GetBdaPointer(ValueEmitContext& ctx, uint32_t address) {
|
||||
auto& state = ctx.state;
|
||||
const auto result = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
{OpFunctionCall, TypeDeviceAddress(state), result, state.bda_pointer_function, address});
|
||||
return result;
|
||||
}
|
||||
|
||||
uint32_t LoadBdaDword(ValueEmitContext& ctx, uint32_t address) {
|
||||
auto& state = ctx.state;
|
||||
const auto bda = GetBdaPointer(ctx, address);
|
||||
const auto present = Binary(state, OpINotEqual, TypeBool(state), bda,
|
||||
ConstantDeviceAddress(state, 0));
|
||||
return EmitValueOrZeroIfCondition(state, present, [&]() {
|
||||
const auto pointer = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
{OpConvertUToPtr, TypePhysicalU32Pointer(state), pointer, bda});
|
||||
const auto value = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
{OpLoad, TypeU32(state), value, pointer, MemoryAccessAlignedMask, sizeof(uint32_t)});
|
||||
return value;
|
||||
});
|
||||
}
|
||||
|
||||
uint32_t LoadBda(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryInfo& mem,
|
||||
uint32_t bits) {
|
||||
auto& state = ctx.state;
|
||||
const auto address = GuestAddress(ctx, inst, mem);
|
||||
const auto active = ctx.Arg(inst, inst.NumArgs() - 1);
|
||||
return EmitValueOrZeroIfCondition(state, active, [&]() {
|
||||
const auto aligned = Binary(state, OpBitwiseAnd, TypeDeviceAddress(state), address,
|
||||
ConstantDeviceAddress(state, ~uint64_t {3}));
|
||||
const auto first = LoadBdaDword(ctx, aligned);
|
||||
const auto byte = Binary(state, OpBitwiseAnd, TypeU32(state),
|
||||
Unary(state, OpUConvert, TypeU32(state), address),
|
||||
ConstantU32(state, 3));
|
||||
const auto crosses = bits == 8u
|
||||
? ConstantBool(state, false)
|
||||
: Binary(state, bits == 16u ? OpUGreaterThan : OpINotEqual,
|
||||
TypeBool(state), byte,
|
||||
ConstantU32(state, bits == 16u ? 2u : 0u));
|
||||
const auto second = EmitValueOrZeroIfCondition(state, crosses, [&]() {
|
||||
return LoadBdaDword(
|
||||
ctx, Binary(state, OpIAdd, TypeDeviceAddress(state), aligned,
|
||||
ConstantDeviceAddress(state, sizeof(uint32_t))));
|
||||
});
|
||||
const auto shift = Binary(state, OpShiftLeftLogical, TypeU32(state), byte,
|
||||
ConstantU32(state, 3));
|
||||
const auto upper_shift = Binary(
|
||||
state, OpShiftLeftLogical, TypeU32(state),
|
||||
Binary(state, OpBitwiseAnd, TypeU32(state),
|
||||
Binary(state, OpISub, TypeU32(state), ConstantU32(state, 4), byte),
|
||||
ConstantU32(state, 3)),
|
||||
ConstantU32(state, 3));
|
||||
const auto merged = Binary(
|
||||
state, OpBitwiseOr, TypeU32(state),
|
||||
Binary(state, OpShiftRightLogical, TypeU32(state), first, shift),
|
||||
Binary(state, OpShiftLeftLogical, TypeU32(state), second, upper_shift));
|
||||
return bits == 32u
|
||||
? merged
|
||||
: Binary(state, OpBitwiseAnd, TypeU32(state), merged,
|
||||
ConstantU32(state, bits == 8u ? 0xffu : 0xffffu));
|
||||
});
|
||||
}
|
||||
|
||||
uint32_t ByteAddress(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryInfo& mem) {
|
||||
@@ -163,11 +264,10 @@ uint32_t ByteAddress(ValueEmitContext& ctx, const IR::Inst& inst, const IR::Memo
|
||||
return Binary(ctx.state, OpIAdd, TypeU32(ctx.state), ctx.Arg(inst, 0),
|
||||
ConstantU32(ctx.state, mem.offset));
|
||||
}
|
||||
const auto address = AddressByteAddress(ctx, inst, mem, ctx.Arg(inst, 1), ctx.Arg(inst, 2));
|
||||
if (mem.kind == IR::ResourceKind::Scratch) {
|
||||
return address;
|
||||
if (mem.kind != IR::ResourceKind::Scratch) {
|
||||
EXIT("physical address memory must use the BDA emitter\n");
|
||||
}
|
||||
return AddAddressMemoryOffset(ctx, mem, address);
|
||||
return ScratchByteAddress(ctx, mem, ctx.Arg(inst, 1), ctx.Arg(inst, 2));
|
||||
}
|
||||
|
||||
uint32_t DwordIndex(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryInfo& mem) {
|
||||
@@ -436,12 +536,12 @@ void StoreSubwordInBounds(ValueEmitContext& ctx, const IR::MemoryInfo& mem,
|
||||
Unary(ctx.state, OpNot, TypeU32(ctx.state), mask)),
|
||||
value);
|
||||
};
|
||||
if (mem.kind == IR::ResourceKind::Lds || mem.kind == IR::ResourceKind::Gds) {
|
||||
AtomicUpdate(ctx.state, pointer, mem.kind, merge);
|
||||
} else {
|
||||
if (mem.kind == IR::ResourceKind::Scratch) {
|
||||
const auto old = ctx.state.builder.AllocateId();
|
||||
ctx.state.builder.AddFunction({OpLoad, TypeU32(ctx.state), old, pointer});
|
||||
ctx.state.builder.AddFunction({OpStore, pointer, merge(old)});
|
||||
} else {
|
||||
AtomicUpdate(ctx.state, pointer, mem.kind, merge);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -672,11 +772,9 @@ uint32_t AppendConsume(ValueEmitContext& ctx, const IR::Inst& inst, bool append)
|
||||
const auto high = state.builder.AllocateId();
|
||||
state.builder.AddFunction({OpCompositeExtract, TypeU32(state), low, ballot, 0});
|
||||
state.builder.AddFunction({OpCompositeExtract, TypeU32(state), high, ballot, 1});
|
||||
const auto count_low = state.per_invocation_masks ? low : ctx.Arg(inst, 2);
|
||||
const auto count_high = state.per_invocation_masks ? high : ctx.Arg(inst, 3);
|
||||
const auto count =
|
||||
Binary(state, OpIAdd, TypeU32(state), Unary(state, OpBitCount, TypeU32(state), count_low),
|
||||
Unary(state, OpBitCount, TypeU32(state), count_high));
|
||||
Binary(state, OpIAdd, TypeU32(state), Unary(state, OpBitCount, TypeU32(state), low),
|
||||
Unary(state, OpBitCount, TypeU32(state), high));
|
||||
const auto first = state.builder.AllocateId();
|
||||
state.builder.AddFunction({OpGroupNonUniformBallotFindLSB, TypeU32(state), first,
|
||||
ConstantU32(state, ScopeSubgroup), ballot});
|
||||
@@ -925,6 +1023,58 @@ void StoreWideShared(ValueEmitContext& ctx, const IR::Inst& inst, uint32_t compo
|
||||
|
||||
} // namespace
|
||||
|
||||
void DefineGetBdaPointer(EmitterState& state) {
|
||||
if (!state.program.info.uses_dma) {
|
||||
return;
|
||||
}
|
||||
const auto type = TypeDeviceAddress(state);
|
||||
const auto function_type = state.builder.Type(OpTypeFunction, {type, type});
|
||||
state.bda_pointer_function = state.builder.AllocateId();
|
||||
const auto address = state.builder.AllocateId();
|
||||
const auto entry_label = state.builder.AllocateId();
|
||||
state.builder.AddName(state.bda_pointer_function, "get_bda_pointer");
|
||||
state.builder.AddFunction(
|
||||
{OpFunction, type, state.bda_pointer_function, FunctionControlNone, function_type});
|
||||
state.builder.AddFunction({OpFunctionParameter, type, address});
|
||||
EmitLabel(state, entry_label);
|
||||
|
||||
const auto page64 = Binary(
|
||||
state, OpShiftRightLogical, type, address,
|
||||
ConstantDeviceAddress(state, BufferCache::CACHING_PAGEBITS));
|
||||
const auto page = Unary(state, OpUConvert, TypeU32(state), page64);
|
||||
const auto entry_pointer = state.builder.AllocateId();
|
||||
state.builder.AddFunction({OpAccessChain, TypeDeviceAddressStoragePointer(state), entry_pointer,
|
||||
state.bda_pagetable_variable, ConstantU32(state, 0), page});
|
||||
const auto base = state.builder.AllocateId();
|
||||
state.builder.AddFunction({OpLoad, type, base, entry_pointer});
|
||||
const auto missing =
|
||||
Binary(state, OpIEqual, TypeBool(state), base, ConstantDeviceAddress(state, 0));
|
||||
const auto fault_label = state.builder.AllocateId();
|
||||
const auto available_label = state.builder.AllocateId();
|
||||
const auto merge_label = state.builder.AllocateId();
|
||||
state.builder.AddFunction({OpSelectionMerge, merge_label, SelectionControlNone});
|
||||
state.builder.AddFunction(
|
||||
{OpBranchConditional, missing, fault_label, available_label});
|
||||
|
||||
EmitLabel(state, fault_label);
|
||||
RecordBdaFault(state, page);
|
||||
state.builder.AddFunction({OpBranch, merge_label});
|
||||
|
||||
EmitLabel(state, available_label);
|
||||
const auto offset = Binary(
|
||||
state, OpBitwiseAnd, type, address,
|
||||
ConstantDeviceAddress(state, BufferCache::CACHING_PAGESIZE - 1));
|
||||
const auto available = Binary(state, OpIAdd, type, base, offset);
|
||||
state.builder.AddFunction({OpBranch, merge_label});
|
||||
|
||||
EmitLabel(state, merge_label);
|
||||
const auto result = state.builder.AllocateId();
|
||||
state.builder.AddFunction({OpPhi, type, result, ConstantDeviceAddress(state, 0), fault_label,
|
||||
available, available_label});
|
||||
state.builder.AddFunction({OpReturnValue, result});
|
||||
state.builder.AddFunction({OpFunctionEnd});
|
||||
}
|
||||
|
||||
bool EmitValueMemory(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
auto& state = ctx.state;
|
||||
const auto op = inst.GetOpcode();
|
||||
@@ -983,6 +1133,10 @@ bool EmitValueMemory(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
}
|
||||
const auto address_info = IR::AddressOpcodeInfoOf(op);
|
||||
const bool load_address = address_info.access == IR::AddressAccess::Read;
|
||||
if (load_address && ctx.Memory(inst).kind != IR::ResourceKind::Scratch) {
|
||||
ctx.Define(inst, LoadBda(ctx, inst, ctx.Memory(inst), address_info.data_bits));
|
||||
return true;
|
||||
}
|
||||
const bool load_buffer = op == IR::ValueOpcode::LoadBufferU8 ||
|
||||
op == IR::ValueOpcode::LoadBufferU16 ||
|
||||
op == IR::ValueOpcode::LoadBufferU32;
|
||||
@@ -1040,24 +1194,27 @@ bool EmitValueMemory(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
ctx.Define(inst, AppendConsume(ctx, inst, op == IR::ValueOpcode::DataAppend));
|
||||
return true;
|
||||
}
|
||||
if (op == IR::ValueOpcode::SwizzleU32) {
|
||||
ctx.state.builder.AddFunction({OpStore, ctx.scratch_u32_variable, ctx.Arg(inst, 0)});
|
||||
const auto source = ctx.state.builder.AllocateId();
|
||||
ctx.state.builder.AddFunction(
|
||||
{OpLoad, TypeU32(ctx.state), source, ctx.scratch_u32_variable});
|
||||
const auto target =
|
||||
EmitDsSwizzleTargetLane(state, EmitSubgroupLocalInvocationId(state),
|
||||
inst.Arg(1).IsImmediate() ? inst.Arg(1).U32() : 0);
|
||||
const auto shuffled = state.builder.AllocateId();
|
||||
state.builder.AddFunction({OpGroupNonUniformShuffle, TypeU32(state), shuffled,
|
||||
ConstantU32(state, ScopeSubgroup), source, target});
|
||||
const auto source_exec = state.builder.AllocateId();
|
||||
state.builder.AddFunction({OpGroupNonUniformShuffle, TypeBool(state), source_exec,
|
||||
ConstantU32(state, ScopeSubgroup), ctx.Arg(inst, 2), target});
|
||||
const auto source_active =
|
||||
AndCondition(state, source_exec, EmitSubgroupLaneActiveBool(state, target));
|
||||
ctx.Define(inst,
|
||||
Select(state, TypeU32(state), source_active, shuffled, ConstantU32(state, 0)));
|
||||
if (op == IR::ValueOpcode::SwizzleU32 || op == IR::ValueOpcode::BpermuteU32) {
|
||||
uint32_t source = ctx.Arg(inst, 0);
|
||||
uint32_t target = 0;
|
||||
if (op == IR::ValueOpcode::SwizzleU32) {
|
||||
state.builder.AddFunction({OpStore, ctx.scratch_u32_variable, source});
|
||||
source = state.builder.AllocateId();
|
||||
state.builder.AddFunction({OpLoad, TypeU32(state), source, ctx.scratch_u32_variable});
|
||||
target = EmitDsSwizzleTargetLane(state, EmitSubgroupLocalInvocationId(state),
|
||||
inst.Arg(1).IsImmediate() ? inst.Arg(1).U32() : 0);
|
||||
} else {
|
||||
const auto index = Binary(
|
||||
state, OpBitwiseAnd, TypeU32(state),
|
||||
Binary(state, OpShiftRightLogical, TypeU32(state), ctx.Arg(inst, 1),
|
||||
ConstantU32(state, 2)),
|
||||
ConstantU32(state, 31));
|
||||
const auto base =
|
||||
Binary(state, OpBitwiseAnd, TypeU32(state), EmitSubgroupLocalInvocationId(state),
|
||||
ConstantU32(state, ~31u));
|
||||
target = Binary(state, OpBitwiseOr, TypeU32(state), base, index);
|
||||
}
|
||||
ctx.Define(inst, EmitDsMaskedLaneRead(state, source, target, ctx.Arg(inst, 2)));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -108,20 +108,8 @@ MemoryResourceAccess PrepareMemoryResourceAccess(EmitterState& state, const IR::
|
||||
return access;
|
||||
case IR::ResourceKind::ScalarAddress:
|
||||
case IR::ResourceKind::Flat:
|
||||
case IR::ResourceKind::Global: {
|
||||
if (state.address_memory_variable == 0) {
|
||||
ExitDescriptorBindingFailure(state, IR::DescriptorBindingKind::AddressMemory,
|
||||
mem.resource,
|
||||
"address memory binding was not emitted");
|
||||
}
|
||||
const auto binding = ResourceForDescriptor(
|
||||
state, IR::DescriptorBindingKind::AddressMemory, mem.resource);
|
||||
access.object_pointer = state.builder.AllocateId();
|
||||
state.builder.AddFunction({OpAccessChain, TypeStorageBufferPointer(state),
|
||||
access.object_pointer, state.address_memory_variable,
|
||||
ConstantU32(state, binding.array_index)});
|
||||
break;
|
||||
}
|
||||
case IR::ResourceKind::Global:
|
||||
EXIT("physical address memory must use the BDA emitter\n");
|
||||
case IR::ResourceKind::ScalarBuffer:
|
||||
case IR::ResourceKind::Buffer: {
|
||||
if (state.storage_buffer_variable == 0) {
|
||||
|
||||
@@ -28,6 +28,10 @@ uint32_t TypeU64(EmitterState& state) {
|
||||
return TypeU32Vector(state, 2);
|
||||
}
|
||||
|
||||
uint32_t TypeDeviceAddress(EmitterState& state) {
|
||||
return state.builder.Type(OpTypeInt, {64, 0});
|
||||
}
|
||||
|
||||
uint32_t TypeU32Pair(EmitterState& state) {
|
||||
const auto element = TypeU32(state);
|
||||
return state.builder.Type(OpTypeStruct, {element, element});
|
||||
@@ -90,6 +94,14 @@ uint32_t TypeStorageBufferElementPointer(EmitterState& state) {
|
||||
return TypePointer(state, StorageClassStorageBuffer, TypeU32(state));
|
||||
}
|
||||
|
||||
uint32_t TypeDeviceAddressStoragePointer(EmitterState& state) {
|
||||
return TypePointer(state, StorageClassStorageBuffer, TypeDeviceAddress(state));
|
||||
}
|
||||
|
||||
uint32_t TypePhysicalU32Pointer(EmitterState& state) {
|
||||
return TypePointer(state, StorageClassPhysicalStorageBuffer, TypeU32(state));
|
||||
}
|
||||
|
||||
uint32_t TypePushConstantElementPointer(EmitterState& state) {
|
||||
return TypePointer(state, StorageClassPushConstant, TypeU32(state));
|
||||
}
|
||||
@@ -130,6 +142,19 @@ uint32_t SampleMaskArrayType(EmitterState& state) {
|
||||
return state.builder.Type(OpTypeArray, {TypeI32(state), ConstantU32(state, 1)});
|
||||
}
|
||||
|
||||
uint32_t BdaPagetableType(EmitterState& state) {
|
||||
const auto array = state.builder.DecoratedType(
|
||||
OpTypeRuntimeArray, {TypeDeviceAddress(state)},
|
||||
{{OpDecorate, {DecorationArrayStride, sizeof(uint64_t)}}});
|
||||
return state.builder.DecoratedType(
|
||||
OpTypeStruct, {array},
|
||||
{{OpMemberDecorate, {0, DecorationOffset, 0}}, {OpDecorate, {DecorationBlock}}});
|
||||
}
|
||||
|
||||
uint32_t F32ArrayType(EmitterState& state, uint32_t count) {
|
||||
return state.builder.Type(OpTypeArray, {TypeF32(state), ConstantU32(state, count)});
|
||||
}
|
||||
|
||||
void DefineDescriptorVariables(EmitterState& state) {
|
||||
if (DescriptorBinding(state, IR::DescriptorBindingKind::Buffers) != nullptr) {
|
||||
const auto count =
|
||||
@@ -139,13 +164,14 @@ void DefineDescriptorVariables(EmitterState& state) {
|
||||
state.storage_buffer_variable =
|
||||
state.builder.DefineGlobalVariable(pointer_type, StorageClassStorageBuffer);
|
||||
}
|
||||
if (DescriptorBinding(state, IR::DescriptorBindingKind::AddressMemory) != nullptr) {
|
||||
const auto count =
|
||||
ConstantU32(state, DescriptorCount(state, IR::DescriptorBindingKind::AddressMemory));
|
||||
const auto array_type = state.builder.Type(OpTypeArray, {StorageBufferType(state), count});
|
||||
const auto pointer_type = TypePointer(state, StorageClassStorageBuffer, array_type);
|
||||
state.address_memory_variable =
|
||||
state.builder.DefineGlobalVariable(pointer_type, StorageClassStorageBuffer);
|
||||
if (DescriptorBinding(state, IR::DescriptorBindingKind::BdaPagetable) != nullptr) {
|
||||
state.bda_pagetable_variable = state.builder.DefineGlobalVariable(
|
||||
TypePointer(state, StorageClassStorageBuffer, BdaPagetableType(state)),
|
||||
StorageClassStorageBuffer);
|
||||
}
|
||||
if (DescriptorBinding(state, IR::DescriptorBindingKind::FaultBuffer) != nullptr) {
|
||||
state.fault_buffer_variable = state.builder.DefineGlobalVariable(
|
||||
TypeStorageBufferPointer(state), StorageClassStorageBuffer);
|
||||
}
|
||||
if (state.program.bindings.push_constant_size != 0) {
|
||||
const auto pointer_type =
|
||||
@@ -420,6 +446,23 @@ void AllocateOutputVariables(EmitterState& state) {
|
||||
binding.variable_id =
|
||||
AllocateSharedOutputVariable(state, state.per_vertex_variable);
|
||||
break;
|
||||
case IR::StageOutputKind::PointSize:
|
||||
binding.variable_id =
|
||||
AllocateSharedOutputVariable(state, state.point_size_variable);
|
||||
break;
|
||||
case IR::StageOutputKind::ClipDistance:
|
||||
binding.variable_id =
|
||||
AllocateSharedOutputVariable(state, state.clip_distance_variable);
|
||||
state.clip_distance_count = std::max(state.clip_distance_count, binding.index + 1);
|
||||
break;
|
||||
case IR::StageOutputKind::CullDistance:
|
||||
binding.variable_id =
|
||||
AllocateSharedOutputVariable(state, state.cull_distance_variable);
|
||||
state.cull_distance_count = std::max(state.cull_distance_count, binding.index + 1);
|
||||
break;
|
||||
case IR::StageOutputKind::Layer:
|
||||
binding.variable_id = AllocateSharedOutputVariable(state, state.layer_variable);
|
||||
break;
|
||||
case IR::StageOutputKind::Depth:
|
||||
binding.variable_id = AllocateSharedOutputVariable(state, state.depth_variable);
|
||||
break;
|
||||
@@ -441,6 +484,8 @@ uint32_t BuiltInForInput(IR::StageInputKind kind) {
|
||||
case IR::StageInputKind::InstanceIndex: return BuiltInInstanceIndex;
|
||||
case IR::StageInputKind::FragCoord: return BuiltInFragCoord;
|
||||
case IR::StageInputKind::FrontFacing: return BuiltInFrontFacing;
|
||||
case IR::StageInputKind::BaryCoordSmooth: return BuiltInBaryCoordKHR;
|
||||
case IR::StageInputKind::BaryCoordNoPerspective: return BuiltInBaryCoordNoPerspKHR;
|
||||
case IR::StageInputKind::WorkgroupId: return BuiltInWorkgroupId;
|
||||
case IR::StageInputKind::LocalInvocationId: return BuiltInLocalInvocationId;
|
||||
case IR::StageInputKind::LocalInvocationIndex: return BuiltInLocalInvocationIndex;
|
||||
@@ -464,11 +509,14 @@ void AddInputAnnotationsAndNames(EmitterState& state) {
|
||||
state.builder.AddName(input.variable_id, input.debug_name.c_str());
|
||||
if (input.kind == IR::StageInputKind::Parameter) {
|
||||
const auto flat = PixelParameterIsFlat(state, input.location);
|
||||
if (flat) {
|
||||
if (input.per_vertex) {
|
||||
state.builder.AddAnnotation(
|
||||
{OpDecorate, input.variable_id, DecorationPerVertexKHR});
|
||||
} else if (flat) {
|
||||
state.builder.AddAnnotation({OpDecorate, input.variable_id, DecorationFlat});
|
||||
}
|
||||
if (state.stage == ShaderType::Pixel && state.input_info.pixel->ps_no_perspective &&
|
||||
!flat) {
|
||||
!flat && !input.per_vertex) {
|
||||
state.builder.AddAnnotation(
|
||||
{OpDecorate, input.variable_id, DecorationNoPerspective});
|
||||
}
|
||||
@@ -490,6 +538,16 @@ void AddOutputAnnotationsAndNames(EmitterState& state) {
|
||||
state.builder.AddName(PerVertexType(state), "gl_PerVertex");
|
||||
state.builder.AddName(state.per_vertex_variable, "outPerVertex");
|
||||
}
|
||||
auto AddBuiltIn = [&](uint32_t variable, const char* name, uint32_t builtin) {
|
||||
if (variable != 0) {
|
||||
state.builder.AddName(variable, name);
|
||||
state.builder.AddAnnotation({OpDecorate, variable, DecorationBuiltIn, builtin});
|
||||
}
|
||||
};
|
||||
AddBuiltIn(state.point_size_variable, "gl_PointSize", BuiltInPointSize);
|
||||
AddBuiltIn(state.clip_distance_variable, "gl_ClipDistance", BuiltInClipDistance);
|
||||
AddBuiltIn(state.cull_distance_variable, "gl_CullDistance", BuiltInCullDistance);
|
||||
AddBuiltIn(state.layer_variable, "gl_Layer", BuiltInLayer);
|
||||
if (state.depth_variable != 0) {
|
||||
state.builder.AddName(state.depth_variable, "gl_FragDepth");
|
||||
state.builder.AddAnnotation(
|
||||
@@ -510,30 +568,31 @@ void AddOutputAnnotationsAndNames(EmitterState& state) {
|
||||
}
|
||||
}
|
||||
|
||||
void DecorateDescriptor(EmitterState& state, uint32_t variable, const char* name, uint32_t set,
|
||||
uint32_t binding) {
|
||||
void DecorateDescriptor(EmitterState& state, uint32_t variable, const char* name,
|
||||
IR::DescriptorBindingKind kind) {
|
||||
if (variable == 0) {
|
||||
return;
|
||||
}
|
||||
state.builder.AddName(variable, name);
|
||||
state.builder.AddAnnotation({OpDecorate, variable, DecorationDescriptorSet, set});
|
||||
state.builder.AddAnnotation({OpDecorate, variable, DecorationBinding, binding});
|
||||
state.builder.AddAnnotation({OpDecorate, variable, DecorationDescriptorSet, 0});
|
||||
state.builder.AddAnnotation(
|
||||
{OpDecorate, variable, DecorationBinding, IR::NativeBinding(state.program.stage, kind)});
|
||||
}
|
||||
|
||||
void AddDescriptorAnnotationsAndNames(EmitterState& state) {
|
||||
const auto set = state.program.bindings.descriptor_set;
|
||||
auto Decorate = [&](uint32_t variable, const char* name, IR::DescriptorBindingKind kind) {
|
||||
const auto* binding = DescriptorBinding(state, kind);
|
||||
if (binding != nullptr) {
|
||||
DecorateDescriptor(state, variable, name, set, binding->binding);
|
||||
}
|
||||
auto Decorate = [&](uint32_t variable, const char* name, IR::DescriptorBindingKind kind) {
|
||||
DecorateDescriptor(state, variable, name, kind);
|
||||
};
|
||||
if (state.storage_buffer_variable != 0) {
|
||||
Decorate(state.storage_buffer_variable, "buffers", IR::DescriptorBindingKind::Buffers);
|
||||
}
|
||||
if (state.address_memory_variable != 0) {
|
||||
Decorate(state.address_memory_variable, "address_memory",
|
||||
IR::DescriptorBindingKind::AddressMemory);
|
||||
if (state.bda_pagetable_variable != 0) {
|
||||
Decorate(state.bda_pagetable_variable, "bda_pagetable",
|
||||
IR::DescriptorBindingKind::BdaPagetable);
|
||||
}
|
||||
if (state.fault_buffer_variable != 0) {
|
||||
Decorate(state.fault_buffer_variable, "fault_buffer",
|
||||
IR::DescriptorBindingKind::FaultBuffer);
|
||||
}
|
||||
constexpr const char* SampledNames[] = {"sampled_1d",
|
||||
"sampled_1d_array",
|
||||
@@ -570,7 +629,7 @@ void AddDescriptorAnnotationsAndNames(EmitterState& state) {
|
||||
"storage_atomic_2d_array",
|
||||
"storage_atomic_3d"};
|
||||
for (uint32_t i = 0; i < state.storage_image_variables.size(); i++) {
|
||||
const auto view = static_cast<ImageViewKind>(i % StorageImageViewKindCount);
|
||||
const auto view = static_cast<ImageViewKind>(i % StorageImageViewKindCount);
|
||||
const auto image_class = static_cast<StorageImageClass>(i / StorageImageViewKindCount);
|
||||
Decorate(state.storage_image_variables[i], StorageNames[i],
|
||||
StorageBindingKind(image_class, view));
|
||||
@@ -588,17 +647,13 @@ void AddDescriptorAnnotationsAndNames(EmitterState& state) {
|
||||
}
|
||||
|
||||
void AddVsharpAnnotationsAndNames(EmitterState& state) {
|
||||
const auto& bind = state.program.bindings;
|
||||
if (state.push_constant_variable != 0) {
|
||||
state.builder.AddName(PushConstantBlockType(state), "BufferResource");
|
||||
state.builder.AddName(state.push_constant_variable, "vsharp");
|
||||
}
|
||||
if (state.vsharp_storage_variable != 0) {
|
||||
const auto* storage = DescriptorBinding(state, IR::DescriptorBindingKind::UserData);
|
||||
if (storage != nullptr) {
|
||||
DecorateDescriptor(state, state.vsharp_storage_variable, "user_data",
|
||||
bind.descriptor_set, storage->binding);
|
||||
}
|
||||
DecorateDescriptor(state, state.vsharp_storage_variable, "user_data",
|
||||
IR::DescriptorBindingKind::UserData);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -615,6 +670,21 @@ void DefineModule(EmitterState& state) {
|
||||
|
||||
state.builder.RequireCapability(CapabilityShader);
|
||||
state.builder.RequireCapability(CapabilitySignedZeroInfNanPreserve);
|
||||
if (state.program.info.uses_dma) {
|
||||
state.builder.RequireCapability(CapabilityInt64);
|
||||
state.builder.RequireCapability(CapabilityPhysicalStorageBufferAddresses);
|
||||
state.builder.RequireExtension("SPV_KHR_physical_storage_buffer");
|
||||
}
|
||||
if (state.clip_distance_variable != 0) {
|
||||
state.builder.RequireCapability(CapabilityClipDistance);
|
||||
}
|
||||
if (state.cull_distance_variable != 0) {
|
||||
state.builder.RequireCapability(CapabilityCullDistance);
|
||||
}
|
||||
if (state.layer_variable != 0) {
|
||||
state.builder.RequireCapability(CapabilityShaderViewportIndexLayerEXT);
|
||||
state.builder.RequireExtension("SPV_EXT_shader_viewport_index_layer");
|
||||
}
|
||||
if (state.requirements.image_gather_extended) {
|
||||
state.builder.RequireCapability(CapabilityImageGatherExtended);
|
||||
}
|
||||
@@ -632,8 +702,21 @@ void DefineModule(EmitterState& state) {
|
||||
state.builder.RequireCapability(CapabilityComputeDerivativeGroupQuadsKHR);
|
||||
state.builder.RequireExtension("SPV_KHR_compute_shader_derivatives");
|
||||
}
|
||||
const bool fragment_barycentric =
|
||||
state.stage == ShaderType::Pixel &&
|
||||
std::any_of(state.inputs.begin(), state.inputs.end(), [](const InputBinding& input) {
|
||||
return input.per_vertex || input.kind == IR::StageInputKind::BaryCoordSmooth ||
|
||||
input.kind == IR::StageInputKind::BaryCoordNoPerspective;
|
||||
});
|
||||
if (fragment_barycentric) {
|
||||
state.builder.RequireCapability(CapabilityFragmentBarycentricKHR);
|
||||
state.builder.RequireExtension("SPV_KHR_fragment_shader_barycentric");
|
||||
}
|
||||
state.builder.RequireExtension("SPV_KHR_float_controls");
|
||||
state.builder.AddMemoryModel({AddressingModelLogical, MemoryModelGLSL450});
|
||||
state.builder.AddMemoryModel(
|
||||
{state.program.info.uses_dma ? AddressingModelPhysicalStorageBuffer64
|
||||
: AddressingModelLogical,
|
||||
MemoryModelGLSL450});
|
||||
state.builder.AddEntryPoint(ExecutionModelForStage(state.stage), state.main_func, "main",
|
||||
state.interface_variables);
|
||||
// GCN/RDNA arithmetic preserves 32-bit signed zero, infinity, and NaN. Declaring that
|
||||
@@ -696,6 +779,10 @@ void DefineModule(EmitterState& state) {
|
||||
case IR::StageInputKind::FragCoord:
|
||||
ptr_type = TypePointer(state, StorageClassInput, TypeF32Vector(state, 4));
|
||||
break;
|
||||
case IR::StageInputKind::BaryCoordSmooth:
|
||||
case IR::StageInputKind::BaryCoordNoPerspective:
|
||||
ptr_type = TypePointer(state, StorageClassInput, TypeF32Vector(state, 3));
|
||||
break;
|
||||
case IR::StageInputKind::FrontFacing:
|
||||
ptr_type = TypePointer(state, StorageClassInput, TypeBool(state));
|
||||
break;
|
||||
@@ -704,6 +791,10 @@ void DefineModule(EmitterState& state) {
|
||||
const auto kind = VertexParameterScalarKind(state, input.location);
|
||||
const auto components = VertexParameterComponentCount(state, input);
|
||||
ptr_type = VertexParameterInputPointerType(state, kind, components);
|
||||
} else if (input.per_vertex) {
|
||||
const auto array_type = state.builder.Type(
|
||||
OpTypeArray, {TypeF32Vector(state, 4), ConstantU32(state, 3)});
|
||||
ptr_type = TypePointer(state, StorageClassInput, array_type);
|
||||
} else {
|
||||
ptr_type = TypePointer(state, StorageClassInput, TypeF32Vector(state, 4));
|
||||
}
|
||||
@@ -717,6 +808,30 @@ void DefineModule(EmitterState& state) {
|
||||
state.per_vertex_variable, TypePointer(state, StorageClassOutput, PerVertexType(state)),
|
||||
StorageClassOutput);
|
||||
}
|
||||
if (state.point_size_variable != 0) {
|
||||
state.builder.DefineGlobalVariable(
|
||||
state.point_size_variable, TypePointer(state, StorageClassOutput, TypeF32(state)),
|
||||
StorageClassOutput);
|
||||
}
|
||||
if (state.clip_distance_variable != 0) {
|
||||
state.builder.DefineGlobalVariable(
|
||||
state.clip_distance_variable,
|
||||
TypePointer(state, StorageClassOutput,
|
||||
F32ArrayType(state, state.clip_distance_count)),
|
||||
StorageClassOutput);
|
||||
}
|
||||
if (state.cull_distance_variable != 0) {
|
||||
state.builder.DefineGlobalVariable(
|
||||
state.cull_distance_variable,
|
||||
TypePointer(state, StorageClassOutput,
|
||||
F32ArrayType(state, state.cull_distance_count)),
|
||||
StorageClassOutput);
|
||||
}
|
||||
if (state.layer_variable != 0) {
|
||||
state.builder.DefineGlobalVariable(
|
||||
state.layer_variable, TypePointer(state, StorageClassOutput, TypeU32(state)),
|
||||
StorageClassOutput);
|
||||
}
|
||||
for (const auto& binding: state.outputs) {
|
||||
if (binding.kind == IR::StageOutputKind::Parameter ||
|
||||
binding.kind == IR::StageOutputKind::Mrt) {
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
#include "graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h"
|
||||
|
||||
#include "common/assert.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <bit>
|
||||
#include <fmt/format.h>
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include <unordered_set>
|
||||
@@ -79,9 +80,9 @@ void StoreDispatcherPhiEdge(ValueEmitContext& ctx, const DispatcherFunctionState
|
||||
}
|
||||
}
|
||||
|
||||
const IR::Block* TargetBlock(const IR::ValueProgram& program, uint32_t id) {
|
||||
const IR::Block* TargetBlock(const IR::Program& program, uint32_t id) {
|
||||
const auto found = std::ranges::find_if(
|
||||
program.block_info, [&](const IR::ValueBlockInfo& info) { return info.id == id; });
|
||||
program.block_info, [&](const IR::BlockInfo& info) { return info.id == id; });
|
||||
if (found == program.block_info.end()) {
|
||||
return nullptr;
|
||||
}
|
||||
@@ -94,7 +95,7 @@ void EmitReturn(ValueEmitContext& ctx) {
|
||||
}
|
||||
|
||||
void EmitStructuredTerminator(ValueEmitContext& ctx, const IR::Block* block,
|
||||
const IR::ValueBlockInfo& info) {
|
||||
const IR::BlockInfo& info) {
|
||||
const auto& term = info.terminator;
|
||||
const auto emit_merge = [&]() {
|
||||
if (term.loop_header) {
|
||||
@@ -150,7 +151,7 @@ void EmitDispatcherTarget(ValueEmitContext& ctx, const DispatcherFunctionState&
|
||||
}
|
||||
|
||||
uint32_t EmitDispatcherNextPc(ValueEmitContext& ctx, const DispatcherFunctionState& dispatcher,
|
||||
const IR::Block* block, const IR::ValueBlockInfo& info) {
|
||||
const IR::Block* block, const IR::BlockInfo& info) {
|
||||
const auto& term = info.terminator;
|
||||
switch (term.kind) {
|
||||
case CFG::TerminatorKind::Branch:
|
||||
@@ -196,57 +197,50 @@ uint32_t EmitDispatcherNextPc(ValueEmitContext& ctx, const DispatcherFunctionSta
|
||||
}
|
||||
}
|
||||
|
||||
bool EmitDirectInstruction(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
void EmitDirectInstruction(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
if (EmitValueFlow(ctx, inst) || EmitValueAlu(ctx, inst) || EmitValueMemory(ctx, inst) ||
|
||||
EmitValueImage(ctx, inst)) {
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
ctx.Fail(inst, "has no direct SPIR-V emitter");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool EmitStructuredInstruction(ValueEmitContext& ctx, StructuredFunctionState& structured,
|
||||
void EmitStructuredInstruction(ValueEmitContext& ctx, StructuredFunctionState& structured,
|
||||
const IR::Inst& inst) {
|
||||
if (inst.GetOpcode() == IR::ValueOpcode::Phi) {
|
||||
const auto type = ctx.TypeId(inst.GetType());
|
||||
if (type == 0 || inst.NumArgs() == 0) {
|
||||
ctx.Fail(inst, "has no native SPIR-V representation");
|
||||
return false;
|
||||
}
|
||||
for (size_t index = 0; index < inst.NumArgs(); index++) {
|
||||
const auto* predecessor = inst.PhiBlock(index);
|
||||
if (predecessor == nullptr || !ctx.labels.contains(predecessor)) {
|
||||
ctx.Fail(inst, "has a predecessor outside the structured function");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
structured.deferred_phis.push_back(
|
||||
{ctx.state.builder.AddDeferredPhi(type, ctx.Result(inst), inst.NumArgs()), &inst});
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
return EmitDirectInstruction(ctx, inst);
|
||||
EmitDirectInstruction(ctx, inst);
|
||||
}
|
||||
|
||||
bool EmitDispatcherInstruction(ValueEmitContext& ctx, const DispatcherFunctionState& dispatcher,
|
||||
void EmitDispatcherInstruction(ValueEmitContext& ctx, const DispatcherFunctionState& dispatcher,
|
||||
const IR::Inst& inst) {
|
||||
if (inst.GetOpcode() == IR::ValueOpcode::Phi) {
|
||||
const auto type = ctx.TypeId(inst.GetType());
|
||||
if (type == 0) {
|
||||
ctx.Fail(inst, "cannot be loaded by the dispatcher");
|
||||
return false;
|
||||
}
|
||||
ctx.state.builder.AddFunction(
|
||||
{OpLoad, type, ctx.Result(inst), dispatcher.spills.at(&inst)});
|
||||
return true;
|
||||
}
|
||||
if (!EmitDirectInstruction(ctx, inst)) {
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
EmitDirectInstruction(ctx, inst);
|
||||
if (const auto found = dispatcher.spills.find(&inst); found != dispatcher.spills.end()) {
|
||||
ctx.state.builder.AddFunction(
|
||||
{OpStore, found->second, ctx.Def(IR::Value(const_cast<IR::Inst*>(&inst)))});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename EmitInstruction>
|
||||
@@ -258,14 +252,11 @@ void EmitBlock(ValueEmitContext& ctx, const IR::Block* block, EmitInstruction&&
|
||||
if (inst.GetOpcode() == IR::ValueOpcode::Phi) {
|
||||
if (emitted_non_phi) {
|
||||
ctx.Fail(inst, "appears after a non-Phi instruction");
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
emitted_non_phi = true;
|
||||
}
|
||||
if (!emit_instruction(inst)) {
|
||||
return;
|
||||
}
|
||||
emit_instruction(inst);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -275,15 +266,10 @@ void PatchStructuredPhis(ValueEmitContext& ctx, StructuredFunctionState& structu
|
||||
const auto* predecessor = deferred.instruction->PhiBlock(index);
|
||||
const auto found = structured.block_exit_labels.find(predecessor);
|
||||
if (found == structured.block_exit_labels.end()) {
|
||||
ctx.failed = true;
|
||||
ctx.error = "typed Phi predecessor was not emitted";
|
||||
return;
|
||||
ctx.Fail(*deferred.instruction, "has a predecessor that was not emitted");
|
||||
}
|
||||
ctx.state.builder.PatchDeferredPhi(
|
||||
deferred.phi, index, ctx.Def(deferred.instruction->Arg(index)), found->second);
|
||||
if (ctx.failed) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -291,20 +277,15 @@ void PatchStructuredPhis(ValueEmitContext& ctx, StructuredFunctionState& structu
|
||||
void EmitStructuredFunction(ValueEmitContext& ctx) {
|
||||
StructuredFunctionState structured;
|
||||
ctx.state.builder.AddFunction({OpBranch, ctx.Label(ctx.program.blocks.front())});
|
||||
for (size_t index = 0; index < ctx.program.blocks.size() && !ctx.failed; index++) {
|
||||
for (size_t index = 0; index < ctx.program.blocks.size(); index++) {
|
||||
const auto* block = ctx.program.blocks[index];
|
||||
EmitBlock(ctx, block, [&](const IR::Inst& inst) {
|
||||
return EmitStructuredInstruction(ctx, structured, inst);
|
||||
EmitStructuredInstruction(ctx, structured, inst);
|
||||
});
|
||||
if (ctx.failed) {
|
||||
break;
|
||||
}
|
||||
structured.block_exit_labels.emplace(block, ctx.state.current_label);
|
||||
EmitStructuredTerminator(ctx, block, ctx.program.block_info[index]);
|
||||
}
|
||||
if (!ctx.failed) {
|
||||
PatchStructuredPhis(ctx, structured);
|
||||
}
|
||||
PatchStructuredPhis(ctx, structured);
|
||||
}
|
||||
|
||||
void EmitDispatcherFunction(ValueEmitContext& ctx, const DispatcherFunctionState& dispatcher) {
|
||||
@@ -312,11 +293,8 @@ void EmitDispatcherFunction(ValueEmitContext& ctx, const DispatcherFunctionState
|
||||
const auto* entry = ctx.program.blocks.front();
|
||||
state.builder.AddFunction({OpBranch, ctx.Label(entry)});
|
||||
EmitBlock(ctx, entry, [&](const IR::Inst& inst) {
|
||||
return EmitDispatcherInstruction(ctx, dispatcher, inst);
|
||||
EmitDispatcherInstruction(ctx, dispatcher, inst);
|
||||
});
|
||||
if (ctx.failed) {
|
||||
return;
|
||||
}
|
||||
const auto initial_pc =
|
||||
EmitDispatcherNextPc(ctx, dispatcher, entry, ctx.program.block_info.front());
|
||||
const auto initial_parent = state.current_label;
|
||||
@@ -347,22 +325,16 @@ void EmitDispatcherFunction(ValueEmitContext& ctx, const DispatcherFunctionState
|
||||
std::vector<uint32_t> next_pc_words {OpPhi, TypeU32(state), next_pc,
|
||||
ConstantU32(state, UINT32_MAX), dispatcher.select_label};
|
||||
|
||||
for (size_t index = 1; index < ctx.program.blocks.size() && !ctx.failed; index++) {
|
||||
for (size_t index = 1; index < ctx.program.blocks.size(); index++) {
|
||||
EmitBlock(ctx, ctx.program.blocks[index], [&](const IR::Inst& inst) {
|
||||
return EmitDispatcherInstruction(ctx, dispatcher, inst);
|
||||
EmitDispatcherInstruction(ctx, dispatcher, inst);
|
||||
});
|
||||
if (ctx.failed) {
|
||||
break;
|
||||
}
|
||||
const auto selected = EmitDispatcherNextPc(ctx, dispatcher, ctx.program.blocks[index],
|
||||
ctx.program.block_info[index]);
|
||||
next_pc_words.push_back(selected);
|
||||
next_pc_words.push_back(state.current_label);
|
||||
state.builder.AddFunction({OpBranch, dispatcher.after_switch_label});
|
||||
}
|
||||
if (ctx.failed) {
|
||||
return;
|
||||
}
|
||||
EmitLabel(state, dispatcher.after_switch_label);
|
||||
state.builder.AddFunction(next_pc_words);
|
||||
state.builder.AddFunction({OpBranch, dispatcher.continue_label});
|
||||
@@ -408,9 +380,7 @@ uint32_t ValueEmitContext::Def(IR::Value value) {
|
||||
}
|
||||
const auto* inst = value.ResolveInstruction();
|
||||
if (inst == nullptr) {
|
||||
failed = true;
|
||||
error = "direct SPIR-V emitter received a non-value argument";
|
||||
return ConstantU32(state, 0);
|
||||
Fail("direct SPIR-V emitter received a non-value argument");
|
||||
}
|
||||
if (dispatcher_spills != nullptr && current_block != nullptr &&
|
||||
inst->Parent() != current_block) {
|
||||
@@ -469,9 +439,7 @@ uint32_t ValueEmitContext::Define(const IR::Inst& inst, uint32_t value) {
|
||||
uint32_t ValueEmitContext::ResourceIndex(IR::Value value, IR::ValueOpcode opcode) {
|
||||
const auto* inst = value.ResolveInstruction();
|
||||
if (inst == nullptr || inst->GetOpcode() != opcode) {
|
||||
failed = true;
|
||||
error = "typed resource handle has the wrong producer";
|
||||
return 0;
|
||||
Fail("typed resource handle has the wrong producer");
|
||||
}
|
||||
return inst->Flags<uint32_t>();
|
||||
}
|
||||
@@ -479,9 +447,7 @@ uint32_t ValueEmitContext::ResourceIndex(IR::Value value, IR::ValueOpcode opcode
|
||||
const IR::Inst* ValueEmitContext::ImageAddress(IR::Value value) {
|
||||
const auto* inst = value.ResolveInstruction();
|
||||
if (inst == nullptr || inst->GetOpcode() != IR::ValueOpcode::MakeImageAddress) {
|
||||
failed = true;
|
||||
error = "typed image address was not constructed by MakeImageAddress";
|
||||
return nullptr;
|
||||
Fail("typed image address was not constructed by MakeImageAddress");
|
||||
}
|
||||
return inst;
|
||||
}
|
||||
@@ -498,12 +464,20 @@ uint32_t ValueEmitContext::Label(const IR::Block* block) const {
|
||||
return labels.at(block);
|
||||
}
|
||||
|
||||
void ValueEmitContext::Fail(const IR::Inst& inst, const char* reason) {
|
||||
failed = true;
|
||||
error = fmt::format("typed opcode {} {}", IR::ValueOpcodeName(inst.GetOpcode()), reason);
|
||||
[[noreturn]] void ValueEmitContext::Fail(const char* reason) const {
|
||||
EXIT("SPIR-V emission failed: hash=0x%016" PRIx64 " stage=%u reason=%s\n",
|
||||
program.shader_hash, static_cast<unsigned>(program.stage), reason);
|
||||
std::abort();
|
||||
}
|
||||
|
||||
bool EmitValueProgram(EmitterState& state, const IR::ValueProgram& program, std::string* error) {
|
||||
[[noreturn]] void ValueEmitContext::Fail(const IR::Inst& inst, const char* reason) const {
|
||||
EXIT("SPIR-V emission failed: hash=0x%016" PRIx64 " stage=%u opcode=%s reason=%s\n",
|
||||
program.shader_hash, static_cast<unsigned>(program.stage),
|
||||
IR::ValueOpcodeName(inst.GetOpcode()), reason);
|
||||
std::abort();
|
||||
}
|
||||
|
||||
void EmitProgram(EmitterState& state, const IR::Program& program) {
|
||||
ValueEmitContext ctx(state, program);
|
||||
std::optional<DispatcherFunctionState> dispatcher;
|
||||
if (state.stage == ShaderType::Pixel && state.requirements.pixel_valid_mask) {
|
||||
@@ -562,10 +536,7 @@ bool EmitValueProgram(EmitterState& state, const IR::ValueProgram& program, std:
|
||||
dispatch.merge_label = state.builder.AllocateId();
|
||||
ctx.dispatcher_spills = &dispatch.spills;
|
||||
}
|
||||
if (ctx.failed) {
|
||||
SetError(error, ctx.error.c_str());
|
||||
return false;
|
||||
}
|
||||
DefineGetBdaPointer(state);
|
||||
for (const auto* block: program.blocks) {
|
||||
if (std::ranges::any_of(*block, [](const IR::Inst& inst) {
|
||||
return inst.GetOpcode() == IR::ValueOpcode::SwizzleU32 ||
|
||||
@@ -629,11 +600,6 @@ bool EmitValueProgram(EmitterState& state, const IR::ValueProgram& program, std:
|
||||
EmitStructuredFunction(ctx);
|
||||
}
|
||||
state.builder.AddFunction({OpFunctionEnd});
|
||||
if (ctx.failed) {
|
||||
SetError(error, ctx.error.c_str());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter
|
||||
@@ -1,5 +1,7 @@
|
||||
#include "graphics/shader/recompiler/frontend/cfg/ShaderCFG.h"
|
||||
|
||||
#include "common/assert.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <fmt/format.h>
|
||||
#include <iterator>
|
||||
@@ -24,19 +26,18 @@ struct SetpcTargetInfo {
|
||||
std::vector<uint32_t> selector_target_pcs;
|
||||
};
|
||||
|
||||
void SetError(std::string* error, const std::string& message) {
|
||||
if (error != nullptr) {
|
||||
*error = message;
|
||||
}
|
||||
}
|
||||
|
||||
void SetFailure(Graph& graph, FailureKind kind, uint32_t block_id, const std::string& message,
|
||||
std::string* error) {
|
||||
void SetFailure(Graph& graph, FailureKind kind, uint32_t block_id, const std::string& message) {
|
||||
graph.unsupported = true;
|
||||
graph.failure_kind = kind;
|
||||
graph.failure_block = block_id;
|
||||
graph.unsupported_reason = message;
|
||||
SetError(error, message);
|
||||
}
|
||||
|
||||
[[noreturn]] void ExitBuildFailure(Graph& graph, FailureKind kind, uint32_t block_id,
|
||||
const std::string& message) {
|
||||
SetFailure(graph, kind, block_id, message);
|
||||
EXIT("shader CFG build failed: %s", message.c_str());
|
||||
std::abort();
|
||||
}
|
||||
|
||||
uint32_t InstructionEndPc(const Instruction& inst) {
|
||||
@@ -1267,7 +1268,7 @@ bool MergeLeavesContainingLoop(const Graph& graph, uint32_t header, uint32_t mer
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CanonicalizeNaturalLoops(Graph& graph, std::string* error) {
|
||||
bool CanonicalizeNaturalLoops(Graph& graph) {
|
||||
const auto rewrite_budget = graph.blocks.size() * 2u + 16u;
|
||||
for (size_t rewrite = 0; rewrite < rewrite_budget; rewrite++) {
|
||||
bool changed = false;
|
||||
@@ -1322,19 +1323,18 @@ bool CanonicalizeNaturalLoops(Graph& graph, std::string* error) {
|
||||
}
|
||||
|
||||
SetFailure(graph, FailureKind::StructuredControlFlow, graph.entry_block,
|
||||
"CFG loop canonicalization exceeded rewrite budget", error);
|
||||
"CFG loop canonicalization exceeded rewrite budget");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IsolateSemanticLoopHeaders(Graph& graph, std::string* error) {
|
||||
bool IsolateSemanticLoopHeaders(Graph& graph) {
|
||||
const auto isolation_budget = graph.natural_loops.size() + 1u;
|
||||
for (size_t isolation = 0; isolation < isolation_budget; isolation++) {
|
||||
const auto loop = std::find_if(graph.natural_loops.begin(), graph.natural_loops.end(),
|
||||
[&](const auto& value) {
|
||||
const auto* header = graph.FindBlock(value.header);
|
||||
return header != nullptr &&
|
||||
header->inst_begin != header->inst_end;
|
||||
});
|
||||
const auto loop = std::find_if(
|
||||
graph.natural_loops.begin(), graph.natural_loops.end(), [&](const auto& value) {
|
||||
const auto* header = graph.FindBlock(value.header);
|
||||
return header != nullptr && header->inst_begin != header->inst_end;
|
||||
});
|
||||
if (loop == graph.natural_loops.end()) {
|
||||
return true;
|
||||
}
|
||||
@@ -1342,18 +1342,17 @@ bool IsolateSemanticLoopHeaders(Graph& graph, std::string* error) {
|
||||
// SPIR-V requires OpLoopMerge and its branch to remain in the loop header's
|
||||
// physical block. Keep that header as a dedicated control node, exactly like
|
||||
// the separate Loop node in shadPS4's structured AST. Guest instructions live
|
||||
// in the body so later lowering may introduce bounds/EXEC control flow without
|
||||
// in the body so later translation may introduce bounds/EXEC control flow without
|
||||
// displacing OpLoopMerge into a helper-created block.
|
||||
if (!IsolateSemanticLoopHeader(graph, loop->header)) {
|
||||
SetFailure(graph, FailureKind::StructuredControlFlow, loop->header,
|
||||
fmt::format("failed to isolate semantic loop header {}", loop->header),
|
||||
error);
|
||||
fmt::format("failed to isolate semantic loop header {}", loop->header));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
SetFailure(graph, FailureKind::StructuredControlFlow, graph.entry_block,
|
||||
"CFG semantic loop-header isolation exceeded rewrite budget", error);
|
||||
"CFG semantic loop-header isolation exceeded rewrite budget");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1453,7 +1452,7 @@ std::vector<uint32_t> SelectionRegion(const Graph& graph, const BasicBlock& head
|
||||
return region;
|
||||
}
|
||||
|
||||
bool SplitOneSelectionMerge(Graph& graph, std::string* error) {
|
||||
bool SplitOneSelectionMerge(Graph& graph) {
|
||||
std::vector<uint32_t> loop_headers;
|
||||
loop_headers.reserve(graph.natural_loops.size());
|
||||
for (const auto& loop: graph.natural_loops) {
|
||||
@@ -1501,8 +1500,7 @@ bool SplitOneSelectionMerge(Graph& graph, std::string* error) {
|
||||
graph, FailureKind::StructuredControlFlow, block_id,
|
||||
fmt::format("selection header block {} has externally entered region block {}; "
|
||||
"semantic block cloning is disabled",
|
||||
block_id, *external),
|
||||
error);
|
||||
block_id, *external));
|
||||
return false;
|
||||
}
|
||||
const auto construct_blocks = DominatedBlocks(graph, block_id, merge);
|
||||
@@ -1514,11 +1512,11 @@ bool SplitOneSelectionMerge(Graph& graph, std::string* error) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SplitSharedMergeBlocks(Graph& graph, std::string* error) {
|
||||
bool SplitSharedMergeBlocks(Graph& graph) {
|
||||
const auto original_block_count = static_cast<uint32_t>(graph.blocks.size());
|
||||
const auto split_budget = std::max<uint32_t>(16u, original_block_count * 4u);
|
||||
for (uint32_t splits = 0; splits < split_budget; splits++) {
|
||||
if (!SplitOneLoopMerge(graph) && !SplitOneSelectionMerge(graph, error)) {
|
||||
if (!SplitOneLoopMerge(graph) && !SplitOneSelectionMerge(graph)) {
|
||||
return !graph.unsupported;
|
||||
}
|
||||
RebuildPredecessors(graph);
|
||||
@@ -1528,8 +1526,7 @@ bool SplitSharedMergeBlocks(Graph& graph, std::string* error) {
|
||||
fmt::format("CFG shared merge splitting exceeded budget: original_blocks={} "
|
||||
"current_blocks={} split_budget={}",
|
||||
original_block_count, static_cast<uint64_t>(graph.blocks.size()),
|
||||
split_budget),
|
||||
error);
|
||||
split_budget));
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1875,7 +1872,8 @@ bool RouteSharedSelectionArm(Graph& graph, uint32_t route_variable) {
|
||||
return false;
|
||||
}
|
||||
const auto block_count = static_cast<uint32_t>(graph.blocks.size());
|
||||
for (uint32_t block_id = 0; block_id < block_count; block_id++) {
|
||||
// Route later/deeper candidates first so earlier shared arms remain structured.
|
||||
for (uint32_t block_id = block_count; block_id-- > 0;) {
|
||||
GotoRouteBlocks route;
|
||||
if (RouteOneSharedArm(graph, block_count, block_id, route_variable, route)) {
|
||||
PlaceGotoRouteBlocks(graph, block_count, route);
|
||||
@@ -1889,12 +1887,11 @@ bool RouteSharedSelectionArm(Graph& graph, uint32_t route_variable) {
|
||||
|
||||
} // namespace
|
||||
|
||||
bool BuildGraph(const Decoder::Program& program, Graph& graph, std::string* error) {
|
||||
graph = {};
|
||||
|
||||
Graph BuildGraph(const Decoder::Program& program) {
|
||||
Graph graph;
|
||||
if (program.instructions.empty()) {
|
||||
SetError(error, "cannot build CFG for empty shader");
|
||||
return false;
|
||||
ExitBuildFailure(graph, FailureKind::InvalidLabel, UINT32_MAX,
|
||||
"cannot build CFG for empty shader");
|
||||
}
|
||||
|
||||
const auto first_pc = program.instructions.front().pc;
|
||||
@@ -1904,11 +1901,10 @@ bool BuildGraph(const Decoder::Program& program, Graph& graph, std::string* erro
|
||||
for (const auto& inst: program.instructions) {
|
||||
instruction_pcs.insert(inst.pc);
|
||||
if (inst.opcode == Opcode::UNSUPPORTED) {
|
||||
SetFailure(graph, FailureKind::UnsupportedInstruction, UINT32_MAX,
|
||||
fmt::format("unsupported decoded instruction in CFG at pc 0x{:08x}: {}",
|
||||
inst.pc, Decoder::InstructionToString(inst).c_str()),
|
||||
error);
|
||||
return false;
|
||||
ExitBuildFailure(
|
||||
graph, FailureKind::UnsupportedInstruction, UINT32_MAX,
|
||||
fmt::format("unsupported decoded instruction in CFG at pc 0x{:08x}: {}", inst.pc,
|
||||
Decoder::InstructionToString(inst).c_str()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1922,11 +1918,9 @@ bool BuildGraph(const Decoder::Program& program, Graph& graph, std::string* erro
|
||||
const auto next_pc = InstructionEndPc(inst);
|
||||
if (IsBranch(inst.opcode)) {
|
||||
if (!IsValidTarget(inst.branch_target, instruction_pcs, first_pc, end_pc)) {
|
||||
SetFailure(graph, FailureKind::InvalidBranchTarget, UINT32_MAX,
|
||||
fmt::format("branch at pc 0x{:08x} targets invalid pc 0x{:08x}", inst.pc,
|
||||
inst.branch_target),
|
||||
error);
|
||||
return false;
|
||||
ExitBuildFailure(graph, FailureKind::InvalidBranchTarget, UINT32_MAX,
|
||||
fmt::format("branch at pc 0x{:08x} targets invalid pc 0x{:08x}",
|
||||
inst.pc, inst.branch_target));
|
||||
}
|
||||
labels.insert(inst.branch_target);
|
||||
if (next_pc <= end_pc) {
|
||||
@@ -1935,21 +1929,19 @@ bool BuildGraph(const Decoder::Program& program, Graph& graph, std::string* erro
|
||||
} else if (inst.opcode == Opcode::S_SETPC_B64) {
|
||||
SetpcTargetInfo target_info;
|
||||
if (!ResolveSetpcTargets(program, i, target_info)) {
|
||||
SetFailure(graph, FailureKind::InvalidBranchTarget, UINT32_MAX,
|
||||
fmt::format("unsupported dynamic S_SETPC_B64 at pc 0x{:08x}", inst.pc),
|
||||
error);
|
||||
return false;
|
||||
ExitBuildFailure(
|
||||
graph, FailureKind::InvalidBranchTarget, UINT32_MAX,
|
||||
fmt::format("unsupported dynamic S_SETPC_B64 at pc 0x{:08x}", inst.pc));
|
||||
}
|
||||
const auto& target_pcs = target_info.indirect
|
||||
? target_info.target_pcs
|
||||
: std::vector<uint32_t> {target_info.target};
|
||||
for (const auto target: target_pcs) {
|
||||
if (!IsValidTarget(target, instruction_pcs, first_pc, end_pc)) {
|
||||
SetFailure(graph, FailureKind::InvalidBranchTarget, UINT32_MAX,
|
||||
fmt::format("S_SETPC_B64 at pc 0x{:08x} targets invalid pc 0x{:08x}",
|
||||
inst.pc, target),
|
||||
error);
|
||||
return false;
|
||||
ExitBuildFailure(
|
||||
graph, FailureKind::InvalidBranchTarget, UINT32_MAX,
|
||||
fmt::format("S_SETPC_B64 at pc 0x{:08x} targets invalid pc 0x{:08x}",
|
||||
inst.pc, target));
|
||||
}
|
||||
labels.insert(target);
|
||||
}
|
||||
@@ -1970,10 +1962,9 @@ bool BuildGraph(const Decoder::Program& program, Graph& graph, std::string* erro
|
||||
continue;
|
||||
}
|
||||
if (start != end_pc && !instruction_pcs.contains(start)) {
|
||||
SetFailure(graph, FailureKind::InvalidLabel, UINT32_MAX,
|
||||
fmt::format("CFG label does not start on an instruction: 0x{:08x}", start),
|
||||
error);
|
||||
return false;
|
||||
ExitBuildFailure(
|
||||
graph, FailureKind::InvalidLabel, UINT32_MAX,
|
||||
fmt::format("CFG label does not start on an instruction: 0x{:08x}", start));
|
||||
}
|
||||
|
||||
BasicBlock block;
|
||||
@@ -2045,11 +2036,10 @@ bool BuildGraph(const Decoder::Program& program, Graph& graph, std::string* erro
|
||||
block.terminator.true_block = pc_to_block.at(last.branch_target);
|
||||
const auto fallthrough = pc_to_block.find(next_pc);
|
||||
if (fallthrough == pc_to_block.end()) {
|
||||
SetFailure(graph, FailureKind::MissingFallthrough, block.id,
|
||||
fmt::format("conditional branch at pc 0x{:08x} has no fallthrough block",
|
||||
last.pc),
|
||||
error);
|
||||
return false;
|
||||
ExitBuildFailure(
|
||||
graph, FailureKind::MissingFallthrough, block.id,
|
||||
fmt::format("conditional branch at pc 0x{:08x} has no fallthrough block",
|
||||
last.pc));
|
||||
}
|
||||
block.terminator.false_block = fallthrough->second;
|
||||
} else {
|
||||
@@ -2130,27 +2120,26 @@ bool BuildGraph(const Decoder::Program& program, Graph& graph, std::string* erro
|
||||
graph.unsupported = false;
|
||||
}
|
||||
|
||||
return true;
|
||||
return graph;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
bool StructurizeImpl(Graph& graph, std::string* error) {
|
||||
bool StructurizeImpl(Graph& graph) {
|
||||
if (graph.unsupported || graph.irreducible) {
|
||||
if (graph.unsupported_reason.empty()) {
|
||||
graph.unsupported_reason = "unsupported CFG";
|
||||
}
|
||||
SetError(error, graph.unsupported_reason);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!CanonicalizeNaturalLoops(graph, error)) {
|
||||
if (!CanonicalizeNaturalLoops(graph)) {
|
||||
return false;
|
||||
}
|
||||
if (!SplitSharedMergeBlocks(graph, error)) {
|
||||
if (!SplitSharedMergeBlocks(graph)) {
|
||||
return false;
|
||||
}
|
||||
if (!IsolateSemanticLoopHeaders(graph, error)) {
|
||||
if (!IsolateSemanticLoopHeaders(graph)) {
|
||||
return false;
|
||||
}
|
||||
ClearStructuredTerminators(graph);
|
||||
@@ -2163,8 +2152,7 @@ bool StructurizeImpl(Graph& graph, std::string* error) {
|
||||
graph, FailureKind::StructuredControlFlow, header,
|
||||
fmt::format(
|
||||
"duplicate structured merge block {} for header {} (already used by header {})",
|
||||
merge, header, it->second),
|
||||
error);
|
||||
merge, header, it->second));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@@ -2175,16 +2163,14 @@ bool StructurizeImpl(Graph& graph, std::string* error) {
|
||||
if (header == nullptr || loop.merge == UINT32_MAX || loop.continue_block == UINT32_MAX) {
|
||||
SetFailure(
|
||||
graph, FailureKind::StructuredControlFlow, loop.header,
|
||||
fmt::format("loop at block {} has no structured merge/continue", loop.header),
|
||||
error);
|
||||
fmt::format("loop at block {} has no structured merge/continue", loop.header));
|
||||
return false;
|
||||
}
|
||||
if (header->inst_begin != header->inst_end ||
|
||||
header->terminator.kind != TerminatorKind::Branch) {
|
||||
SetFailure(graph, FailureKind::StructuredControlFlow, loop.header,
|
||||
fmt::format("loop header {} is not a dedicated empty control block",
|
||||
loop.header),
|
||||
error);
|
||||
SetFailure(
|
||||
graph, FailureKind::StructuredControlFlow, loop.header,
|
||||
fmt::format("loop header {} is not a dedicated empty control block", loop.header));
|
||||
return false;
|
||||
}
|
||||
if (!reserve_merge_block(loop.header, loop.merge)) {
|
||||
@@ -2207,8 +2193,7 @@ bool StructurizeImpl(Graph& graph, std::string* error) {
|
||||
const auto merge = FindSelectionMerge(graph, block);
|
||||
if (merge == UINT32_MAX) {
|
||||
SetFailure(graph, FailureKind::StructuredControlFlow, block.id,
|
||||
fmt::format("conditional block {} has no structured merge", block.id),
|
||||
error);
|
||||
fmt::format("conditional block {} has no structured merge", block.id));
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -2223,16 +2208,15 @@ bool StructurizeImpl(Graph& graph, std::string* error) {
|
||||
|
||||
} // namespace
|
||||
|
||||
bool Structurize(Graph& graph, std::string* error) {
|
||||
bool Structurize(Graph& graph) {
|
||||
Graph original = graph;
|
||||
if (StructurizeImpl(graph, error)) {
|
||||
if (StructurizeImpl(graph)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
Graph failed_graph = std::move(graph);
|
||||
std::string failed_error = error != nullptr ? *error : std::string {};
|
||||
graph = std::move(original);
|
||||
const auto route_budget = static_cast<uint32_t>(graph.blocks.size());
|
||||
Graph failed_graph = std::move(graph);
|
||||
graph = std::move(original);
|
||||
const auto route_budget = static_cast<uint32_t>(graph.blocks.size());
|
||||
// Apply one route at a time and retry. Eagerly routing every matching diamond can
|
||||
// rewrite unrelated selections that were already structurally valid.
|
||||
for (uint32_t route_variable = 0; route_variable < route_budget; route_variable++) {
|
||||
@@ -2240,18 +2224,12 @@ bool Structurize(Graph& graph, std::string* error) {
|
||||
break;
|
||||
}
|
||||
Graph routed = graph;
|
||||
if (error != nullptr) {
|
||||
error->clear();
|
||||
}
|
||||
if (StructurizeImpl(routed, error)) {
|
||||
if (StructurizeImpl(routed)) {
|
||||
graph = std::move(routed);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
graph = std::move(failed_graph);
|
||||
if (error != nullptr) {
|
||||
*error = std::move(failed_error);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -108,8 +108,8 @@ struct Graph {
|
||||
uint32_t FindNearestCommonPostDominator(uint32_t block_a, uint32_t block_b) const;
|
||||
};
|
||||
|
||||
bool BuildGraph(const Decoder::Program& program, Graph& graph, std::string* error);
|
||||
bool Structurize(Graph& graph, std::string* error);
|
||||
Graph BuildGraph(const Decoder::Program& program);
|
||||
bool Structurize(Graph& graph);
|
||||
std::string BranchConditionToString(BranchCondition condition);
|
||||
std::string FailureKindToString(FailureKind kind);
|
||||
std::string GraphToString(const Graph& graph);
|
||||
|
||||
@@ -357,8 +357,8 @@ void DecodeMimg(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index
|
||||
|
||||
DecodeVectorGpr(vdata, inst.dst);
|
||||
DecodeVectorGpr(vaddr, inst.src0);
|
||||
DecodeScalarSource(srsrc * 4u, pc, inst.src1, nullptr);
|
||||
DecodeScalarSource(ssamp * 4u, pc, inst.src2, nullptr);
|
||||
DecodeScalarSource(srsrc * 4u, pc, inst.src1);
|
||||
DecodeScalarSource(ssamp * 4u, pc, inst.src2);
|
||||
inst.src_count = 3;
|
||||
}
|
||||
|
||||
|
||||
@@ -103,7 +103,11 @@ constexpr MemoryOpcodeInfo DS_OPCODE_LIST[] = {
|
||||
{0x4du, Opcode::DS_WRITE_B64, 2, 32}, {0x4eu, Opcode::DS_WRITE2_B64, 4, 32},
|
||||
{0x4fu, Opcode::DS_WRITE2ST64_B64, 4, 32}, {0x76u, Opcode::DS_READ_B64, 2, 32},
|
||||
{0x77u, Opcode::DS_READ2_B64, 4, 32}, {0x78u, Opcode::DS_READ2ST64_B64, 4, 32},
|
||||
{0xa1u, Opcode::DS_WRITE_B16_D16_HI, 1, 16},
|
||||
{0xa6u, Opcode::DS_READ_U16_D16, 1, 16},
|
||||
{0xa7u, Opcode::DS_READ_U16_D16_HI, 1, 16},
|
||||
{0xb0u, Opcode::DS_WRITE_ADDTID_B32, 1, 32}, {0xb1u, Opcode::DS_READ_ADDTID_B32, 1, 32},
|
||||
{0xb3u, Opcode::DS_BPERMUTE_B32, 1, 32},
|
||||
{0xdeu, Opcode::DS_WRITE_B96, 3, 32}, {0xdfu, Opcode::DS_WRITE_B128, 4, 32},
|
||||
{0xfeu, Opcode::DS_READ_B96, 3, 32}, {0xffu, Opcode::DS_READ_B128, 4, 32},
|
||||
};
|
||||
@@ -146,6 +150,7 @@ bool IsDsWriteOpcode(Opcode opcode) {
|
||||
switch (opcode) {
|
||||
case Opcode::DS_WRITE_B8:
|
||||
case Opcode::DS_WRITE_B16:
|
||||
case Opcode::DS_WRITE_B16_D16_HI:
|
||||
case Opcode::DS_WRITE2_B32:
|
||||
case Opcode::DS_WRITE2ST64_B32:
|
||||
case Opcode::DS_WRITE2_B64:
|
||||
@@ -191,6 +196,7 @@ uint32_t DsSourceCount(Opcode opcode) {
|
||||
case Opcode::DS_WRITE2ST64_B64:
|
||||
case Opcode::DS_MIN_F32:
|
||||
case Opcode::DS_MAX_F32: return 3u;
|
||||
case Opcode::DS_BPERMUTE_B32: return 2u;
|
||||
case Opcode::DS_READ_ADDTID_B32:
|
||||
case Opcode::DS_CONSUME:
|
||||
case Opcode::DS_APPEND: return 0u;
|
||||
@@ -235,11 +241,11 @@ void DecodeSmem(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index
|
||||
MarkMemoryUnsupported(inst, Family::SMEM, opcode, "SMEM opcode is not implemented");
|
||||
}
|
||||
|
||||
DecodeScalarDestination(sdst, pc, inst.dst, nullptr);
|
||||
DecodeScalarDestination(sdst, pc, inst.dst);
|
||||
// SMEM encodes SBASE in SGPR pairs. Scalar-buffer loads still use the same
|
||||
// pair index; their descriptor operand consumes four SGPRs from that base.
|
||||
DecodeScalarSource(sbase * 2u, pc, inst.src0, nullptr);
|
||||
DecodeScalarSource(soffset, pc, inst.src1, nullptr);
|
||||
DecodeScalarSource(sbase * 2u, pc, inst.src0);
|
||||
DecodeScalarSource(soffset, pc, inst.src1);
|
||||
inst.src_count = 2;
|
||||
}
|
||||
|
||||
@@ -272,8 +278,8 @@ void DecodeMubuf(uint32_t pc, std::span<const uint32_t> code, uint32_t word_inde
|
||||
|
||||
DecodeVectorGpr(vdata, inst.dst);
|
||||
DecodeVectorGpr(vaddr, inst.src0);
|
||||
DecodeScalarSource(srsrc * 4u, pc, inst.src1, nullptr);
|
||||
DecodeScalarSource(soffset, pc, inst.src2, nullptr);
|
||||
DecodeScalarSource(srsrc * 4u, pc, inst.src1);
|
||||
DecodeScalarSource(soffset, pc, inst.src2);
|
||||
inst.src_count = 3;
|
||||
}
|
||||
|
||||
@@ -310,8 +316,8 @@ void DecodeMtbuf(uint32_t pc, std::span<const uint32_t> code, uint32_t word_inde
|
||||
|
||||
DecodeVectorGpr(vdata, inst.dst);
|
||||
DecodeVectorGpr(vaddr, inst.src0);
|
||||
DecodeScalarSource(srsrc * 4u, pc, inst.src1, nullptr);
|
||||
DecodeScalarSource(soffset, pc, inst.src2, nullptr);
|
||||
DecodeScalarSource(srsrc * 4u, pc, inst.src1);
|
||||
DecodeScalarSource(soffset, pc, inst.src2);
|
||||
inst.src_count = 3;
|
||||
}
|
||||
|
||||
@@ -358,7 +364,7 @@ void DecodeFlat(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index
|
||||
DecodeVectorGpr(addr + 1u, inst.src1);
|
||||
inst.src_count = 2;
|
||||
} else {
|
||||
DecodeScalarSource(saddr, pc, inst.src1, nullptr);
|
||||
DecodeScalarSource(saddr, pc, inst.src1);
|
||||
inst.src_count = 2;
|
||||
}
|
||||
}
|
||||
@@ -391,9 +397,10 @@ void DecodeDs(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index,
|
||||
SetUnsupported(inst, Family::DS, opcode, "DS swizzle FFT mode is not implemented");
|
||||
}
|
||||
if (inst.gds &&
|
||||
(inst.opcode == Opcode::DS_SWIZZLE_B32 || inst.opcode == Opcode::DS_WRITE_ADDTID_B32 ||
|
||||
(inst.opcode == Opcode::DS_SWIZZLE_B32 || inst.opcode == Opcode::DS_BPERMUTE_B32 ||
|
||||
inst.opcode == Opcode::DS_WRITE_ADDTID_B32 ||
|
||||
inst.opcode == Opcode::DS_READ_ADDTID_B32)) {
|
||||
SetUnsupported(inst, Family::DS, opcode, "DS swizzle/addtid is available only for LDS");
|
||||
SetUnsupported(inst, Family::DS, opcode, "DS lane operation is available only for LDS");
|
||||
}
|
||||
if (inst.opcode == Opcode::DS_WRITE_ADDTID_B32 && data1 != 0u) {
|
||||
SetUnsupported(inst, Family::DS, opcode,
|
||||
@@ -420,8 +427,17 @@ void DecodeDs(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index,
|
||||
}
|
||||
|
||||
DecodeVectorGpr(vdst, inst.dst);
|
||||
if (inst.opcode == Opcode::DS_READ_U16_D16 ||
|
||||
inst.opcode == Opcode::DS_READ_U16_D16_HI) {
|
||||
// Native D16 reads update only the selected destination half. Reuse the partial
|
||||
// destination representation so typed IR translation preserves the other half.
|
||||
inst.dst.sdwa_sel = inst.opcode == Opcode::DS_READ_U16_D16 ? 4u : 5u;
|
||||
}
|
||||
DecodeVectorGpr(addr, inst.src0);
|
||||
DecodeVectorGpr(data0, inst.src1);
|
||||
if (inst.opcode == Opcode::DS_WRITE_B16_D16_HI) {
|
||||
inst.src1.sdwa_sel = 5u;
|
||||
}
|
||||
DecodeVectorGpr(data1, inst.src2);
|
||||
inst.src_count = DsSourceCount(inst.opcode);
|
||||
}
|
||||
|
||||
Loaded 100 of 194 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user