mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-29 08:41:20 +00:00
Compare commits
56
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 |
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
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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")
|
||||
@@ -508,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;
|
||||
|
||||
@@ -200,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}"
|
||||
|
||||
@@ -153,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;
|
||||
|
||||
@@ -2433,12 +2433,12 @@ KYTY_CP_OP_PARSER(CpOpIndirectShRegs) {
|
||||
auto value = indirect_buffer[1];
|
||||
|
||||
// Not sure if this is correct
|
||||
if (raw_cmd_offset != cmd_offset) {
|
||||
LOGF_COLOR(Log::Color::Red,
|
||||
"\t temporary: normalized indirect SH register offset 0x%08" PRIx32
|
||||
" -> 0x%08" PRIx32 "\n",
|
||||
raw_cmd_offset, cmd_offset);
|
||||
}
|
||||
// if (raw_cmd_offset != cmd_offset) {
|
||||
// LOGF_COLOR(Log::Color::Red,
|
||||
// "\t temporary: normalized indirect SH register offset 0x%08" PRIx32
|
||||
// " -> 0x%08" PRIx32 "\n",
|
||||
// raw_cmd_offset, cmd_offset);
|
||||
// }
|
||||
|
||||
// Not sure if this is correct
|
||||
if (cmd_offset == Pm4::SH_NOP || raw_cmd_offset == 0xffffffffu) {
|
||||
@@ -2494,12 +2494,12 @@ KYTY_CP_OP_PARSER(CpOpIndirectUcRegs) {
|
||||
auto value = indirect_buffer[1];
|
||||
|
||||
// Not sure if this is correct
|
||||
if (raw_cmd_offset != cmd_offset) {
|
||||
LOGF_COLOR(Log::Color::Red,
|
||||
"\t temporary: normalized indirect UC register offset 0x%08" PRIx32
|
||||
" -> 0x%08" PRIx32 "\n",
|
||||
raw_cmd_offset, cmd_offset);
|
||||
}
|
||||
// if (raw_cmd_offset != cmd_offset) {
|
||||
// LOGF_COLOR(Log::Color::Red,
|
||||
// "\t temporary: normalized indirect UC register offset 0x%08" PRIx32
|
||||
// " -> 0x%08" PRIx32 "\n",
|
||||
// raw_cmd_offset, cmd_offset);
|
||||
// }
|
||||
if (HwUcTrySetFakeRegister(cmd_offset, value)) {
|
||||
continue;
|
||||
}
|
||||
@@ -2855,12 +2855,12 @@ KYTY_CP_OP_PARSER(CpOpSetUconfigReg) {
|
||||
cmd_offset = raw_cmd_offset & 0x0fffffffu;
|
||||
}
|
||||
|
||||
if (raw_cmd_offset != cmd_offset) {
|
||||
LOGF_COLOR(Log::Color::Red,
|
||||
"\t temporary: normalized UC register offset 0x%08" PRIx32 " -> 0x%08" PRIx32
|
||||
"\n",
|
||||
raw_cmd_offset, cmd_offset);
|
||||
}
|
||||
// if (raw_cmd_offset != cmd_offset) {
|
||||
// LOGF_COLOR(Log::Color::Red,
|
||||
// "\t temporary: normalized UC register offset 0x%08" PRIx32 " -> 0x%08" PRIx32
|
||||
// "\n",
|
||||
// raw_cmd_offset, cmd_offset);
|
||||
// }
|
||||
if (HwUcTrySetFakeRegisterRange(cmd_offset, buffer + 1, KYTY_PM4_LEN(cmd_id) - 2u)) {
|
||||
return KYTY_PM4_LEN(cmd_id) - 1u;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -35,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 {
|
||||
|
||||
@@ -53,6 +53,15 @@ 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); });
|
||||
@@ -98,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;
|
||||
|
||||
+73
-38
@@ -51,41 +51,50 @@ struct BufferCache::DownloadCopy {
|
||||
};
|
||||
|
||||
void BufferCache::Register(BufferId id) {
|
||||
auto& buffer = m_slot_buffers[id];
|
||||
PageTable::PageRange pages {};
|
||||
EXIT_IF(!PageTable::TryGetPageRange(buffer.CpuAddress(), buffer.Size(), pages));
|
||||
for (size_t page = pages.first; page < pages.last_exclusive; ++page) {
|
||||
m_page_table[page] = id;
|
||||
}
|
||||
const auto [it, inserted] = m_buffers.emplace(buffer.CpuAddress(), id);
|
||||
(void)it;
|
||||
EXIT_IF(!inserted);
|
||||
buffer.lru_id = m_lru_cache.Insert(id, m_gc_tick);
|
||||
m_total_used_memory += buffer.Size();
|
||||
ChangeRegister<true>(id);
|
||||
}
|
||||
|
||||
void BufferCache::Unregister(BufferId id) {
|
||||
ChangeRegister<false>(id);
|
||||
}
|
||||
|
||||
template <bool insert>
|
||||
void BufferCache::ChangeRegister(BufferId id) {
|
||||
auto& buffer = m_slot_buffers[id];
|
||||
if (buffer.is_deleted) {
|
||||
return;
|
||||
}
|
||||
PageTable::PageRange pages {};
|
||||
EXIT_IF(!PageTable::TryGetPageRange(buffer.CpuAddress(), buffer.Size(), pages));
|
||||
for (size_t page = pages.first; page < pages.last_exclusive; ++page) {
|
||||
auto* owner = m_page_table.Find(page);
|
||||
if (owner != nullptr && *owner == id) {
|
||||
*owner = {};
|
||||
if constexpr (insert) {
|
||||
m_page_table[page] = id;
|
||||
} else {
|
||||
m_page_table[page] = {};
|
||||
}
|
||||
}
|
||||
const auto found = m_buffers.find(buffer.CpuAddress());
|
||||
EXIT_IF(found == m_buffers.end() || found->second != id);
|
||||
m_buffers.erase(found);
|
||||
if (buffer.Size() > m_total_used_memory) {
|
||||
EXIT("BufferCache: allocation accounting underflow\n");
|
||||
const auto size_pages = pages.last_exclusive - pages.first;
|
||||
if constexpr (insert) {
|
||||
const auto [it, inserted] = m_buffers.emplace(buffer.CpuAddress(), id);
|
||||
(void)it;
|
||||
EXIT_IF(!inserted);
|
||||
m_total_used_memory += buffer.Size();
|
||||
buffer.lru_id = m_lru_cache.Insert(id, m_gc_tick);
|
||||
std::vector<vk::DeviceAddress> addresses;
|
||||
addresses.reserve(size_pages);
|
||||
for (uint64_t i = 0; i < size_pages; ++i) {
|
||||
addresses.push_back(buffer.BufferDeviceAddress() + (i << CACHING_PAGEBITS));
|
||||
}
|
||||
WriteDataBuffer(m_bda_pagetable_buffer, pages.first * sizeof(vk::DeviceAddress),
|
||||
addresses.data(), addresses.size() * sizeof(vk::DeviceAddress));
|
||||
} else {
|
||||
const auto found = m_buffers.find(buffer.CpuAddress());
|
||||
EXIT_IF(found == m_buffers.end() || found->second != id);
|
||||
m_buffers.erase(found);
|
||||
EXIT_IF(buffer.Size() > m_total_used_memory);
|
||||
m_total_used_memory -= buffer.Size();
|
||||
m_lru_cache.Free(buffer.lru_id);
|
||||
m_bda_pagetable_buffer.Fill(pages.first * sizeof(vk::DeviceAddress),
|
||||
size_pages * sizeof(vk::DeviceAddress), 0);
|
||||
buffer.is_deleted = true;
|
||||
}
|
||||
m_total_used_memory -= buffer.Size();
|
||||
m_lru_cache.Free(buffer.lru_id);
|
||||
buffer.is_deleted = true;
|
||||
}
|
||||
|
||||
void BufferCache::TouchBuffer(const Buffer& buffer) {
|
||||
@@ -184,16 +193,21 @@ void BufferCache::DownloadBufferMemory(std::span<const DownloadCopy> copies) {
|
||||
|
||||
BufferCache::BufferCache(GraphicContext& graphics, CommandScheduler& scheduler,
|
||||
PageManager& page_manager, TextureCache& texture_cache)
|
||||
: m_graphics(graphics), m_scheduler(scheduler),
|
||||
m_gds_buffer(graphics, scheduler, MemoryUsage::Stream, 0, AllFlags, GdsBufferSize),
|
||||
m_memory_tracker(page_manager),
|
||||
m_staging_buffer(graphics, scheduler, MemoryUsage::Upload, 512 * MiB),
|
||||
m_stream_buffer(graphics, scheduler, MemoryUsage::Stream, 64 * MiB),
|
||||
m_download_buffer(graphics, scheduler, MemoryUsage::Download, 32 * MiB),
|
||||
m_device_buffer(graphics, scheduler, MemoryUsage::DeviceLocal, 128 * MiB),
|
||||
m_texture_cache(texture_cache) {
|
||||
: m_graphics(graphics), m_scheduler(scheduler),
|
||||
m_fault_manager(graphics, scheduler, *this, CACHING_PAGEBITS, CACHING_NUMPAGES),
|
||||
m_gds_buffer(graphics, scheduler, MemoryUsage::Stream, 0, AllFlags, GdsBufferSize),
|
||||
m_bda_pagetable_buffer(graphics, scheduler, MemoryUsage::DeviceLocal, 0, AllFlags,
|
||||
BDA_PAGETABLE_SIZE),
|
||||
m_memory_tracker(page_manager),
|
||||
m_staging_buffer(graphics, scheduler, MemoryUsage::Upload, 512 * MiB),
|
||||
m_stream_buffer(graphics, scheduler, MemoryUsage::Stream, 64 * MiB),
|
||||
m_download_buffer(graphics, scheduler, MemoryUsage::Download, 32 * MiB),
|
||||
m_device_buffer(graphics, scheduler, MemoryUsage::DeviceLocal, 128 * MiB),
|
||||
m_texture_cache(texture_cache) {
|
||||
std::memset(m_gds_buffer.Mapped().data(), 0, static_cast<size_t>(m_gds_buffer.Size()));
|
||||
m_gds_buffer.Flush(0, m_gds_buffer.Size());
|
||||
SetVulkanObjectNameF(m_graphics.device, m_bda_pagetable_buffer.Handle(),
|
||||
"BDA Page Table Buffer");
|
||||
const auto null_id =
|
||||
m_slot_buffers.insert(m_graphics, m_scheduler, MemoryUsage::DeviceLocal, 0, AllFlags, 16);
|
||||
EXIT_IF(null_id != NULL_BUFFER_ID);
|
||||
@@ -316,8 +330,8 @@ BufferId BufferCache::FindBuffer(uint64_t vaddr, uint64_t size) {
|
||||
BufferId BufferCache::CreateBuffer(uint64_t vaddr, uint64_t size) {
|
||||
auto& command = m_scheduler.Current();
|
||||
EXIT_IF(command.IsInvalid());
|
||||
auto begin = vaddr & ~(CACHING_PAGE_SIZE - 1);
|
||||
auto end = (vaddr + size + CACHING_PAGE_SIZE - 1) & ~(CACHING_PAGE_SIZE - 1);
|
||||
auto begin = vaddr & ~(CACHING_PAGESIZE - 1);
|
||||
auto end = (vaddr + size + CACHING_PAGESIZE - 1) & ~(CACHING_PAGESIZE - 1);
|
||||
auto first = m_buffers.lower_bound(begin);
|
||||
if (first != m_buffers.begin()) {
|
||||
const auto previous = std::prev(first);
|
||||
@@ -333,8 +347,9 @@ BufferId BufferCache::CreateBuffer(uint64_t vaddr, uint64_t size) {
|
||||
end = std::max(end, buffer.CpuAddress() + buffer.Size());
|
||||
}
|
||||
|
||||
const auto id = m_slot_buffers.insert(m_graphics, m_scheduler, MemoryUsage::DeviceLocal, begin,
|
||||
AllFlags, end - begin);
|
||||
const auto id = m_slot_buffers.insert(
|
||||
m_graphics, m_scheduler, MemoryUsage::DeviceLocal, begin,
|
||||
AllFlags | vk::BufferUsageFlagBits::eShaderDeviceAddress, end - begin);
|
||||
auto& buffer = m_slot_buffers[id];
|
||||
SetVulkanObjectNameF(m_graphics.device, buffer.Handle(),
|
||||
"Kyty.GameBuffer[guest=0x{:016x} size=0x{:x}]", begin, end - begin);
|
||||
@@ -433,7 +448,7 @@ std::pair<Buffer*, uint64_t> BufferCache::ObtainBuffer(uint64_t vaddr, uint64_t
|
||||
EXIT("BufferCache: buffer request requires a recording command buffer\n");
|
||||
}
|
||||
|
||||
if (!is_written && size <= CACHING_PAGE_SIZE &&
|
||||
if (!is_written && size <= CACHING_PAGESIZE &&
|
||||
!m_memory_tracker.IsRegionGpuModified(vaddr, size) &&
|
||||
m_memory_tracker.IsRegionCpuModified(vaddr, size)) {
|
||||
const auto alignment = std::max<uint64_t>(
|
||||
@@ -737,4 +752,24 @@ void BufferCache::RunGarbageCollector() {
|
||||
}
|
||||
}
|
||||
|
||||
void BufferCache::ProcessFaultBuffer() {
|
||||
m_fault_manager.ProcessFaultBuffer();
|
||||
}
|
||||
|
||||
void BufferCache::SynchronizeBuffersInRange(uint64_t vaddr, uint64_t size) {
|
||||
const auto end = vaddr + size;
|
||||
auto it = m_buffers.upper_bound(vaddr);
|
||||
if (it != m_buffers.begin()) {
|
||||
--it;
|
||||
}
|
||||
for (; it != m_buffers.end() && it->first < end; ++it) {
|
||||
auto& buffer = m_slot_buffers[it->second];
|
||||
const auto start = std::max(buffer.CpuAddress(), vaddr);
|
||||
const auto finish = std::min(buffer.CpuAddress() + buffer.Size(), end);
|
||||
if (start < finish) {
|
||||
(void)SynchronizeBuffer(buffer, start, finish - start, false, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
+16
-3
@@ -7,6 +7,7 @@
|
||||
#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"
|
||||
|
||||
@@ -26,7 +27,11 @@ inline constexpr BufferId NULL_BUFFER_ID {0};
|
||||
|
||||
class BufferCache {
|
||||
public:
|
||||
static constexpr uint64_t CACHING_PAGE_SIZE = 16ull * 1024ull;
|
||||
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);
|
||||
@@ -51,6 +56,8 @@ public:
|
||||
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,
|
||||
@@ -60,14 +67,16 @@ public:
|
||||
[[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 DownloadCopy;
|
||||
using PageTable = MultiLevelPageTable<BufferId, 14, 40, 16>;
|
||||
static_assert(CACHING_PAGE_SIZE == (uint64_t {1} << PageTable::kPageBits));
|
||||
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 constexpr uint64_t AlignDownload(uint64_t size) noexcept {
|
||||
return (size + DOWNLOAD_ALIGNMENT - 1) & ~(DOWNLOAD_ALIGNMENT - 1);
|
||||
@@ -78,6 +87,8 @@ private:
|
||||
[[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);
|
||||
@@ -90,7 +101,9 @@ private:
|
||||
|
||||
GraphicContext& m_graphics;
|
||||
CommandScheduler& m_scheduler;
|
||||
FaultManager m_fault_manager;
|
||||
Buffer m_gds_buffer;
|
||||
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;
|
||||
|
||||
@@ -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_
|
||||
@@ -76,7 +76,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);
|
||||
|
||||
+3
-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,
|
||||
@@ -84,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;
|
||||
@@ -97,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:
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include "graphics/host_gpu/vulkanCommon.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
@@ -43,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,
|
||||
@@ -143,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");
|
||||
@@ -207,8 +219,9 @@ 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);
|
||||
@@ -325,7 +338,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;
|
||||
@@ -364,8 +377,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,
|
||||
|
||||
@@ -196,8 +196,8 @@ void CommandScheduler::Finish() {
|
||||
Submit();
|
||||
}
|
||||
m_master.Wait(CurrentTick() - 1);
|
||||
PopPendingOperations();
|
||||
BeginNext();
|
||||
PopPendingOperations();
|
||||
}
|
||||
|
||||
void CommandScheduler::Wait(uint64_t tick) {
|
||||
@@ -346,7 +346,7 @@ void CommandScheduler::CheckActive() const {
|
||||
EXIT_IF(!Active());
|
||||
}
|
||||
|
||||
RenderCommandBuffer& CommandScheduler::Current() {
|
||||
CommandBuffer& CommandScheduler::Current() {
|
||||
CheckActive();
|
||||
return m_command;
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ public:
|
||||
|
||||
[[nodiscard]] bool Active() const noexcept { return m_registers != nullptr; }
|
||||
void CheckActive() const;
|
||||
RenderCommandBuffer& Current();
|
||||
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; }
|
||||
@@ -90,7 +90,7 @@ private:
|
||||
RenderContext& m_context;
|
||||
GraphicContext& m_graphics;
|
||||
CommandPool m_command_pool;
|
||||
RenderCommandBuffer m_command;
|
||||
CommandBuffer m_command;
|
||||
std::queue<PendingOperation> m_pending_operations;
|
||||
std::queue<PendingOperation> m_priority_operations;
|
||||
std::mutex m_operation_mutex;
|
||||
|
||||
@@ -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);
|
||||
@@ -266,11 +257,10 @@ 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;
|
||||
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);
|
||||
@@ -837,7 +827,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 +884,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 +1151,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 +1197,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 +1224,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);
|
||||
|
||||
@@ -70,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();
|
||||
@@ -97,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;
|
||||
|
||||
@@ -11,8 +11,6 @@
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
class RenderCommandBuffer;
|
||||
|
||||
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);
|
||||
|
||||
@@ -216,48 +216,6 @@ static BufferView NativeStorageBuffer(RenderContext&
|
||||
return result;
|
||||
}
|
||||
|
||||
static BufferView
|
||||
NativeAddressBuffer(RenderContext& context, const ShaderRecompiler::IR::AddressResource& resource,
|
||||
const ShaderRecompiler::IR::ResourceSnapshot::Address& address,
|
||||
ShaderType stage, uint32_t slot, uint32_t& address_offset, BufferId id) {
|
||||
BufferView result;
|
||||
address_offset = 0;
|
||||
if (address.binding_base == 0) {
|
||||
BindNullStorageBuffer(context, result);
|
||||
return result;
|
||||
}
|
||||
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 [buffer, offset] =
|
||||
context.GetBufferCache().ObtainBuffer(address.binding_base, size, false, false, id);
|
||||
const auto aligned_offset = offset - offset % alignment;
|
||||
const auto adjustment = 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.buffer = buffer->Handle();
|
||||
result.offset = aligned_offset;
|
||||
result.range = static_cast<vk::DeviceSize>(visible_size + adjustment);
|
||||
SetVulkanObjectNameF(graphics.device, result.buffer,
|
||||
"Kyty.{}.AddressBuffer[slot={} guest=0x{:016x} size=0x{:x}]",
|
||||
ShaderStageResourceName(stage), slot, address.binding_base, visible_size);
|
||||
return result;
|
||||
}
|
||||
|
||||
static bool IsSupportedSampledColorResource(const ShaderRecompiler::IR::ImageResource& resource) {
|
||||
bool supported_dimension = false;
|
||||
switch (resource.dimension) {
|
||||
@@ -937,9 +895,9 @@ PreparedBindings RenderExecutor::PrepareBindings(const ShaderStageRuntime& runti
|
||||
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));
|
||||
}
|
||||
|
||||
PreparedBindings prepared;
|
||||
@@ -1000,25 +958,6 @@ void RenderExecutor::FindBuffers(PreparedBindings& prepared) {
|
||||
prepared.buffer_ids.push_back(cache.FindBuffer(address, size));
|
||||
}
|
||||
|
||||
prepared.address_ids.clear();
|
||||
prepared.address_ids.reserve(program.info.addresses.size());
|
||||
for (uint32_t i = 0; i < program.info.addresses.size(); i++) {
|
||||
const auto& resource = program.info.addresses[i];
|
||||
const auto& address = snapshot.addresses[i];
|
||||
if (address.binding_base == 0) {
|
||||
prepared.address_ids.emplace_back();
|
||||
continue;
|
||||
}
|
||||
EXIT_IF(resource.written);
|
||||
const auto limit = resource.kind == ShaderRecompiler::IR::ResourceKind::Flat
|
||||
? ShaderRecompiler::IR::FlatAddressWindowSize
|
||||
: static_cast<uint64_t>(m_context.GetGraphics()
|
||||
.GetPhysicalDeviceProperties()
|
||||
.limits.maxStorageBufferRange);
|
||||
uint64_t size = 0;
|
||||
EXIT_IF(!HostMemoryQueryRange(address.binding_base, limit, HostMemoryAccess::Mapped, size));
|
||||
prepared.address_ids.push_back(cache.FindBuffer(address.binding_base, size));
|
||||
}
|
||||
}
|
||||
|
||||
void RenderExecutor::RebindBuffers(PreparedBindings& prepared) {
|
||||
@@ -1028,8 +967,7 @@ void RenderExecutor::RebindBuffers(PreparedBindings& prepared) {
|
||||
const auto& snapshot = *prepared.snapshot;
|
||||
auto& resources = prepared.resources;
|
||||
const auto& layout = program.bindings;
|
||||
EXIT_IF(prepared.buffer_ids.size() != program.info.buffers.size() ||
|
||||
prepared.address_ids.size() != program.info.addresses.size());
|
||||
EXIT_IF(prepared.buffer_ids.size() != program.info.buffers.size());
|
||||
|
||||
resources.buffers.clear();
|
||||
resources.buffers.reserve(program.info.buffers.size());
|
||||
@@ -1049,15 +987,6 @@ void RenderExecutor::RebindBuffers(PreparedBindings& prepared) {
|
||||
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, program.info.addresses[i],
|
||||
snapshot.addresses[i], program.stage, i,
|
||||
address_offset, prepared.address_ids[i]));
|
||||
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);
|
||||
}
|
||||
@@ -1129,6 +1058,10 @@ RenderExecutor::PrepareGraphicsBindings(const ShaderStageRuntime& 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);
|
||||
@@ -1241,13 +1174,15 @@ void RenderExecutor::CommitBindings(CommandBuffer& buffer,
|
||||
m_descriptor_buffers.emplace_back(view.buffer, view.offset, view.range);
|
||||
}
|
||||
break;
|
||||
case BindingKind::AddressMemory:
|
||||
for (const auto resource: binding.resources) {
|
||||
const auto& view = descriptors.addresses.at(resource);
|
||||
EXIT_IF(view.buffer == nullptr);
|
||||
m_descriptor_buffers.emplace_back(view.buffer, view.offset, view.range);
|
||||
}
|
||||
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: {
|
||||
|
||||
@@ -38,7 +38,6 @@ 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;
|
||||
@@ -49,7 +48,6 @@ struct PreparedBindings {
|
||||
const ShaderRecompiler::IR::ResourceSnapshot* snapshot = nullptr;
|
||||
NativeDescriptors resources;
|
||||
std::vector<BufferId> buffer_ids;
|
||||
std::vector<BufferId> address_ids;
|
||||
std::vector<uint32_t> flattened_srt;
|
||||
std::vector<uint32_t> user_data;
|
||||
bool committed = false;
|
||||
|
||||
@@ -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,7 +86,91 @@ 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;
|
||||
@@ -47,6 +181,188 @@ PipelineCache::~PipelineCache() {
|
||||
};
|
||||
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 {
|
||||
@@ -54,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++) {
|
||||
@@ -82,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 {};
|
||||
@@ -202,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, *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);
|
||||
@@ -227,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;
|
||||
@@ -253,7 +558,7 @@ PipelineCache::CreateComputePipeline(ShaderComputeInputInfo& input_info,
|
||||
}
|
||||
|
||||
auto cached = std::make_unique<ComputePipeline>(p);
|
||||
CreatePipelineInternal(m_graphics, *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,7 +21,7 @@ namespace Libs::Graphics {
|
||||
struct GraphicContext;
|
||||
struct RenderColorInfo;
|
||||
struct RenderDepthInfo;
|
||||
class RenderCommandBuffer;
|
||||
class CommandBuffer;
|
||||
|
||||
namespace HW {
|
||||
class Context;
|
||||
@@ -95,13 +96,21 @@ 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:
|
||||
explicit PipelineCache(GraphicContext& graphics): m_graphics(graphics) {
|
||||
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;
|
||||
@@ -111,29 +120,42 @@ public:
|
||||
};
|
||||
|
||||
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,31 +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;
|
||||
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, 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);
|
||||
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,
|
||||
std::span<const uint32_t> cs_shader);
|
||||
vk::ShaderModule compute_module, vk::PipelineCache driver_cache);
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
|
||||
@@ -422,43 +422,20 @@ static void CreateDescriptorLayout(GraphicContext& graphics, PipelineCache::Pipe
|
||||
void CreatePipelineInternal(
|
||||
GraphicContext& graphics, 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);
|
||||
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);
|
||||
@@ -483,10 +460,8 @@ 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 {};
|
||||
|
||||
@@ -494,7 +469,7 @@ void CreatePipelineInternal(
|
||||
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;
|
||||
|
||||
@@ -503,7 +478,7 @@ void CreatePipelineInternal(
|
||||
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;
|
||||
|
||||
@@ -854,9 +829,9 @@ void CreatePipelineInternal(
|
||||
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=1 push_constants=%" PRIu32 "\n",
|
||||
vs_hash0, vs_crc32, ps_hash0, ps_crc32, 1u);
|
||||
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);
|
||||
@@ -955,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",
|
||||
@@ -975,39 +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, 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);
|
||||
vk::ShaderModule compute_module, vk::PipelineCache driver_cache) {
|
||||
EXIT_IF(compute_module == nullptr);
|
||||
|
||||
vk::PipelineShaderStageCreateInfo comp_shader_stage_info {};
|
||||
vk::PipelineShaderStageRequiredSubgroupSizeCreateInfo comp_subgroup_size {};
|
||||
@@ -1015,7 +971,7 @@ void CreatePipelineInternal(GraphicContext& graphics, PipelineCache::ComputePipe
|
||||
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);
|
||||
@@ -1048,8 +1004,8 @@ void CreatePipelineInternal(GraphicContext& graphics, PipelineCache::ComputePipe
|
||||
|
||||
LOGF("PipelineTrace: vkCreatePipelineLayout CS begin set_layouts=1 push_constants=%u\n",
|
||||
1u);
|
||||
result = graphics.device.createPipelineLayout(&pipeline_layout_info, nullptr,
|
||||
&pipeline.pipeline_layout);
|
||||
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);
|
||||
@@ -1069,14 +1025,13 @@ void CreatePipelineInternal(GraphicContext& graphics, PipelineCache::ComputePipe
|
||||
|
||||
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
|
||||
@@ -90,11 +90,18 @@ public:
|
||||
[[nodiscard]] vk::CommandBuffer Handle() const;
|
||||
[[nodiscard]] GraphicContext& GetGraphics() const noexcept { return m_graphics; }
|
||||
[[nodiscard]] RenderContext& GetContext() const noexcept { return m_context; }
|
||||
|
||||
protected:
|
||||
explicit CommandBuffer(CommandScheduler& scheduler);
|
||||
[[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:
|
||||
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;
|
||||
}
|
||||
|
||||
void Begin();
|
||||
void End() const;
|
||||
|
||||
@@ -109,29 +116,10 @@ private:
|
||||
uint32_t m_debug_arg3 = 0;
|
||||
uint64_t m_debug_arg4 = 0;
|
||||
mutable RenderState m_render_state;
|
||||
mutable bool m_rendering = false;
|
||||
|
||||
friend class CommandScheduler;
|
||||
};
|
||||
|
||||
class RenderCommandBuffer final: public CommandBuffer {
|
||||
public:
|
||||
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; }
|
||||
|
||||
private:
|
||||
explicit RenderCommandBuffer(CommandScheduler& scheduler): CommandBuffer(scheduler) {}
|
||||
|
||||
HW::Context* m_registers = nullptr;
|
||||
HW::UserConfig* m_user_config = nullptr;
|
||||
HW::Shader* m_shaders = nullptr;
|
||||
mutable bool m_rendering = false;
|
||||
HW::Context* m_registers = nullptr;
|
||||
HW::UserConfig* m_user_config = nullptr;
|
||||
HW::Shader* m_shaders = nullptr;
|
||||
|
||||
friend class CommandScheduler;
|
||||
};
|
||||
@@ -141,15 +129,14 @@ 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]] PreparedBindings PrepareBindings(const ShaderStageRuntime& runtime);
|
||||
@@ -171,32 +158,31 @@ private:
|
||||
[[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<ImageId> m_bound_images;
|
||||
|
||||
@@ -44,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()) {
|
||||
@@ -81,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();
|
||||
@@ -163,7 +162,7 @@ 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());
|
||||
@@ -208,18 +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,
|
||||
!m_context.GetGraphics().compute_wave64_supported, 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;
|
||||
@@ -334,9 +327,12 @@ void RenderExecutor::DispatchDirect(uint64_t submit_id, RenderCommandBuffer& buf
|
||||
|
||||
buffer.EndRendering();
|
||||
auto& pipeline =
|
||||
m_context.GetPipelineCache().CreateComputePipeline(input_info, sh_ctx.GetCs(), cs_shader);
|
||||
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);
|
||||
|
||||
|
||||
@@ -103,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();
|
||||
@@ -126,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();
|
||||
@@ -177,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();
|
||||
@@ -231,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) {
|
||||
@@ -309,7 +309,7 @@ static void LogDrawInputState(const RenderCommandBuffer& buffer, const RenderCol
|
||||
}
|
||||
}
|
||||
|
||||
static void SetGraphicsDynamicParams(const RenderCommandBuffer& buffer, vk::CommandBuffer vk_buffer,
|
||||
static void SetGraphicsDynamicParams(const CommandBuffer& buffer, vk::CommandBuffer vk_buffer,
|
||||
const RenderColorInfo* colors, uint32_t color_count,
|
||||
const RenderDepthInfo& depth) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
@@ -421,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();
|
||||
@@ -450,15 +450,14 @@ static bool ShouldSkipGeShader(const RenderCommandBuffer& buffer) {
|
||||
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 "
|
||||
@@ -483,15 +482,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 {
|
||||
@@ -706,7 +705,7 @@ 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;
|
||||
@@ -723,7 +722,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
|
||||
@@ -779,7 +778,7 @@ struct PreparedVertexBuffers {
|
||||
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);
|
||||
@@ -869,8 +868,8 @@ static PreparedVertexBuffers AcquireVertexBuffers(RenderCommandBuffer& b
|
||||
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,
|
||||
@@ -918,8 +917,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) {
|
||||
@@ -953,7 +952,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) {
|
||||
@@ -993,7 +992,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();
|
||||
@@ -1003,35 +1002,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;
|
||||
}
|
||||
if (log_phases) {
|
||||
LogDrawPhase(draw.name, "ShaderCompileInfoVS");
|
||||
}
|
||||
if (!ShaderCompileInfoVS(vertex_shader_info, shader_regs, 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, 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);
|
||||
@@ -1041,7 +1038,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) {
|
||||
@@ -1091,7 +1088,7 @@ static void CommitIndexBuffer(vk::CommandBuffer vk_buffer, const PreparedIndexBu
|
||||
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) {
|
||||
@@ -1159,7 +1156,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,
|
||||
@@ -1180,9 +1177,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
|
||||
@@ -1243,7 +1240,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,
|
||||
@@ -1376,7 +1373,7 @@ 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) {
|
||||
@@ -1475,7 +1472,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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -1,95 +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 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;
|
||||
}
|
||||
|
||||
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 {
|
||||
|
||||
@@ -60,6 +66,17 @@ 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; });
|
||||
@@ -103,10 +120,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 +142,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 +152,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 +184,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() {
|
||||
@@ -281,6 +306,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 +355,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>
|
||||
|
||||
@@ -334,7 +305,6 @@ public:
|
||||
|
||||
private:
|
||||
void Destroy();
|
||||
void RefreshSurfaceSize();
|
||||
|
||||
WindowContext& m_window;
|
||||
vk::SwapchainKHR m_handle = nullptr;
|
||||
@@ -396,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());
|
||||
|
||||
@@ -459,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");
|
||||
@@ -554,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();
|
||||
}
|
||||
|
||||
@@ -803,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,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;
|
||||
@@ -594,20 +597,16 @@ static vk::Device VulkanCreateDevice(vk::PhysicalDevice physical_device, const V
|
||||
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;
|
||||
features12.shaderOutputLayer = 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.bufferDeviceAddress = VK_TRUE;
|
||||
|
||||
vk::PhysicalDeviceFeatures device_features {};
|
||||
device_features.fragmentStoresAndAtomics = VK_TRUE;
|
||||
@@ -625,12 +624,9 @@ static vk::Device VulkanCreateDevice(vk::PhysicalDevice physical_device, const V
|
||||
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;
|
||||
}
|
||||
device_features.shaderInt64 = VK_TRUE;
|
||||
|
||||
vk::PhysicalDeviceRobustness2FeaturesEXT robustness2 {};
|
||||
robustness2.sType = vk::StructureType::ePhysicalDeviceRobustness2FeaturesEXT;
|
||||
@@ -950,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();
|
||||
@@ -1044,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,
|
||||
@@ -1086,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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -728,8 +731,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 +749,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 +771,6 @@ void WindowContext::DrainMainThreadTasks() {
|
||||
main_tasks_run += tasks.size();
|
||||
main_task_done.SignalAll();
|
||||
}
|
||||
#endif
|
||||
|
||||
void WindowContext::Run() {
|
||||
Common::Timer timer;
|
||||
@@ -783,9 +781,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 +790,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 +817,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 +847,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 +884,7 @@ void WindowRun() {
|
||||
EXIT_IF(g_window == nullptr);
|
||||
|
||||
g_window->Run();
|
||||
g_window->render_context->GetPipelineCache().Save();
|
||||
}
|
||||
|
||||
void WindowShutdown() {
|
||||
@@ -959,6 +978,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 +1003,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
|
||||
|
||||
@@ -472,20 +472,14 @@ EmbeddedFetchData DetectEmbeddedVertexFetch(const Decoder::Program& decoded
|
||||
|
||||
} // 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();
|
||||
@@ -500,9 +494,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,
|
||||
@@ -516,12 +508,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,
|
||||
@@ -534,13 +523,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);
|
||||
@@ -548,7 +536,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()));
|
||||
@@ -560,14 +548,28 @@ bool TryRecompile(std::span<const uint32_t> code, const CompileOptions& options,
|
||||
static_cast<uint64_t>(cfg.natural_loops.size()), phase_ms());
|
||||
}
|
||||
|
||||
IR::Program ir;
|
||||
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;
|
||||
if (vertex == nullptr) {
|
||||
EXIT("vertex shader recompilation has no input metadata\n");
|
||||
}
|
||||
break;
|
||||
case ShaderType::Pixel:
|
||||
pixel = options.input_info.pixel;
|
||||
if (pixel == nullptr) {
|
||||
EXIT("pixel shader recompilation has no input metadata\n");
|
||||
}
|
||||
break;
|
||||
case ShaderType::Compute:
|
||||
compute = options.input_info.compute;
|
||||
if (compute == nullptr) {
|
||||
EXIT("compute shader recompilation has no input metadata\n");
|
||||
}
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
EmbeddedFetchData embedded_fetch;
|
||||
@@ -597,9 +599,7 @@ bool TryRecompile(std::span<const uint32_t> code, const CompileOptions& options,
|
||||
};
|
||||
LOGF("%s phase begin: stage=%s hash=0x%016" PRIx64 " IR TranslateProgram\n",
|
||||
GetDumpLabel(options), StageName(options.stage), options.shader_hash);
|
||||
if (!Frontend::TranslateProgram(decoded, cfg, translate_options, ir, error)) {
|
||||
return false;
|
||||
}
|
||||
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,
|
||||
@@ -626,18 +626,9 @@ bool TryRecompile(std::span<const uint32_t> code, const CompileOptions& options,
|
||||
lds_barriers.inserted_barriers);
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
IR::BuildSrtPlan(ir);
|
||||
IR::EliminateDeadCode(ir.blocks);
|
||||
if (!IR::TrackResources(ir, error)) {
|
||||
return false;
|
||||
}
|
||||
IR::TrackResources(ir);
|
||||
IR::EliminateDeadCode(ir.blocks);
|
||||
if (options.stage == ShaderType::Vertex) {
|
||||
ir.info.vertex_offset_sgpr = embedded_fetch.vertex_offset_sgpr;
|
||||
@@ -665,28 +656,20 @@ bool TryRecompile(std::span<const uint32_t> code, const CompileOptions& options,
|
||||
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::MaterializeResources(ir, runtime, resources)) {
|
||||
EXIT("shader resource materialization failed: stage=%s hash=0x%016" PRIx64 "\n",
|
||||
StageName(options.stage), options.shader_hash);
|
||||
}
|
||||
}
|
||||
if (!IR::SpecializeResources(ir, resources, error)) {
|
||||
return false;
|
||||
}
|
||||
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;
|
||||
}
|
||||
if (!IR::AllocateBindings(ir, options.push_constant_offset, 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);
|
||||
@@ -695,36 +678,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>
|
||||
|
||||
@@ -20,7 +19,6 @@ struct CompileOptions {
|
||||
uint32_t scratch_dwords = 0;
|
||||
uint64_t shader_hash = 0;
|
||||
uint64_t shader_base = 0;
|
||||
std::optional<uint64_t> flat_memory_base;
|
||||
uint32_t push_constant_offset = 0;
|
||||
bool dump_ir = true;
|
||||
bool early_dump = false;
|
||||
@@ -41,8 +39,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/ShaderIR.h"
|
||||
#include "graphics/shader/shader.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);
|
||||
@@ -58,11 +62,11 @@ bool ValidateNativeProgram(const IR::Program& program, std::string* error) {
|
||||
}
|
||||
const auto index = inst.Flags<IR::MemoryFlags>().index;
|
||||
if (index >= program.memory_info.size()) {
|
||||
return Fail(error, "shared operation has invalid memory metadata");
|
||||
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) {
|
||||
return Fail(error, "shared operation has invalid resource kind");
|
||||
Fail(program, "shared operation has invalid resource kind");
|
||||
}
|
||||
uses_gds |= kind == IR::ResourceKind::Gds;
|
||||
}
|
||||
@@ -70,8 +74,9 @@ bool ValidateNativeProgram(const IR::Program& program, std::string* error) {
|
||||
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.srt_reads.empty() ||
|
||||
@@ -90,13 +95,13 @@ bool ValidateNativeProgram(const IR::Program& program, std::string* error) {
|
||||
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");
|
||||
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)];
|
||||
@@ -104,8 +109,7 @@ bool ValidateNativeProgram(const IR::Program& program, std::string* error) {
|
||||
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() + program.info.addresses.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) ||
|
||||
@@ -114,7 +118,7 @@ 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");
|
||||
}
|
||||
|
||||
const auto planning_only_handle = [&](const IR::Inst& handle) {
|
||||
@@ -139,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.srt_reads.size()) {
|
||||
return Fail(error, "flattened SRT read has an invalid dense slot");
|
||||
Fail(program, "flattened SRT read has an invalid dense slot");
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -177,42 +181,44 @@ 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 {};
|
||||
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.memory_info.size()) {
|
||||
return Fail(error, "address operation has invalid memory metadata");
|
||||
Fail(program, "address operation has invalid memory metadata");
|
||||
}
|
||||
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.memory_info.size()) {
|
||||
return Fail(error, "buffer operation has invalid memory metadata");
|
||||
Fail(program, "buffer operation has invalid memory metadata");
|
||||
}
|
||||
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.subgroup_local_invocation_id = true;
|
||||
}
|
||||
@@ -222,11 +228,11 @@ bool AnalyzeProgramRequirements(IR::Program& program, std::string* error) {
|
||||
if (shared_access != IR::SharedAccess::None) {
|
||||
const auto index = inst.Flags<IR::MemoryFlags>().index;
|
||||
if (index >= program.memory_info.size()) {
|
||||
return Fail(error, "shared operation has invalid memory metadata");
|
||||
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) {
|
||||
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;
|
||||
@@ -263,7 +269,8 @@ bool AnalyzeProgramRequirements(IR::Program& program, std::string* error) {
|
||||
requirements.subgroup_local_invocation_id = true;
|
||||
break;
|
||||
}
|
||||
case IR::ValueOpcode::SwizzleU32: {
|
||||
case IR::ValueOpcode::SwizzleU32:
|
||||
case IR::ValueOpcode::BpermuteU32: {
|
||||
MarkBallot();
|
||||
requirements.subgroup_shuffle = true;
|
||||
requirements.subgroup_local_invocation_id = true;
|
||||
@@ -279,7 +286,7 @@ bool AnalyzeProgramRequirements(IR::Program& program, std::string* error) {
|
||||
case IR::ValueOpcode::SetAttribute: {
|
||||
const auto index = inst.Flags<IR::ExportFlags>().index;
|
||||
if (index >= program.export_info.size()) {
|
||||
return Fail(error, "attribute export has invalid metadata");
|
||||
Fail(program, "attribute export has invalid metadata");
|
||||
}
|
||||
if (program.stage == ShaderType::Pixel &&
|
||||
program.export_info[index].vm) {
|
||||
@@ -292,37 +299,30 @@ 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 (!IR::ValidateProgram(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;
|
||||
@@ -333,17 +333,13 @@ bool EmitProgram(const IR::Program& program, const IR::ResourceSnapshot& resourc
|
||||
AllocateInputVariables(state);
|
||||
AllocateOutputVariables(state);
|
||||
DefineModule(state);
|
||||
if (!EmitProgram(state, 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
|
||||
|
||||
|
||||
@@ -37,12 +37,6 @@ bool PixelParameterIsCustom(const EmitterState& state, uint32_t attr) {
|
||||
ShaderPixelParameterIsCustom(*state.input_info.pixel, attr);
|
||||
}
|
||||
|
||||
void SetError(std::string* error, const char* message) {
|
||||
if (error != nullptr) {
|
||||
*error = message;
|
||||
}
|
||||
}
|
||||
|
||||
bool HasOutput(const std::vector<OutputBinding>& outputs, IR::StageOutputKind kind,
|
||||
uint32_t index) {
|
||||
return std::any_of(outputs.begin(), outputs.end(), [kind, index](const OutputBinding& binding) {
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -36,8 +36,10 @@ enum : uint32_t {
|
||||
ExecutionModeSignedZeroInfNanPreserve = 4461,
|
||||
ExecutionModeDerivativeGroupQuadsKHR = 5289,
|
||||
AddressingModelLogical = 0,
|
||||
AddressingModelPhysicalStorageBuffer64 = 5348,
|
||||
MemoryModelGLSL450 = 1,
|
||||
CapabilityShader = 1,
|
||||
CapabilityInt64 = 11,
|
||||
CapabilityImageGatherExtended = 25,
|
||||
CapabilityClipDistance = 32,
|
||||
CapabilityCullDistance = 33,
|
||||
@@ -52,6 +54,7 @@ enum : uint32_t {
|
||||
CapabilityShaderViewportIndexLayerEXT = 5254,
|
||||
CapabilityFragmentBarycentricKHR = 5284,
|
||||
CapabilityComputeDerivativeGroupQuadsKHR = 5288,
|
||||
CapabilityPhysicalStorageBufferAddresses = 5347,
|
||||
StorageClassUniformConstant = 0,
|
||||
StorageClassInput = 1,
|
||||
StorageClassOutput = 3,
|
||||
@@ -60,6 +63,7 @@ enum : uint32_t {
|
||||
StorageClassPushConstant = 9,
|
||||
StorageClassImage = 11,
|
||||
StorageClassStorageBuffer = 12,
|
||||
StorageClassPhysicalStorageBuffer = 5349,
|
||||
FunctionControlNone = 0,
|
||||
SelectionControlNone = 0,
|
||||
LoopControlNone = 0,
|
||||
@@ -148,7 +152,9 @@ enum : uint32_t {
|
||||
OpConstantComposite = 44,
|
||||
OpUndef = 1,
|
||||
OpFunction = 54,
|
||||
OpFunctionParameter = 55,
|
||||
OpFunctionEnd = 56,
|
||||
OpFunctionCall = 57,
|
||||
OpVariable = 59,
|
||||
OpImageTexelPointer = 60,
|
||||
OpLoad = 61,
|
||||
@@ -178,6 +184,8 @@ enum : uint32_t {
|
||||
OpConvertFToS = 110,
|
||||
OpConvertSToF = 111,
|
||||
OpConvertUToF = 112,
|
||||
OpUConvert = 113,
|
||||
OpConvertUToPtr = 120,
|
||||
OpBitcast = 124,
|
||||
OpSNegate = 126,
|
||||
OpFNegate = 127,
|
||||
@@ -256,11 +264,16 @@ enum : uint32_t {
|
||||
OpSwitch = 251,
|
||||
OpKill = 252,
|
||||
OpReturn = 253,
|
||||
OpReturnValue = 254,
|
||||
OpGroupNonUniformBallot = 339,
|
||||
OpGroupNonUniformBallotFindLSB = 343,
|
||||
OpGroupNonUniformShuffle = 345,
|
||||
};
|
||||
|
||||
enum : uint32_t {
|
||||
MemoryAccessAlignedMask = 0x2,
|
||||
};
|
||||
|
||||
enum : uint32_t {
|
||||
GlslRoundEven = 2,
|
||||
GlslTrunc = 3,
|
||||
@@ -345,9 +358,10 @@ struct EmitterState {
|
||||
ShaderType stage = ShaderType::Unknown;
|
||||
uint32_t wave_size = 64;
|
||||
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;
|
||||
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;
|
||||
@@ -384,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);
|
||||
@@ -397,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);
|
||||
@@ -422,7 +439,8 @@ 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::Program& program;
|
||||
@@ -432,8 +450,6 @@ struct ValueEmitContext {
|
||||
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 };
|
||||
@@ -596,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);
|
||||
@@ -847,7 +861,9 @@ bool EmitValueMemory(ValueEmitContext& ctx, const IR::Inst& inst);
|
||||
|
||||
bool EmitValueImage(ValueEmitContext& ctx, const IR::Inst& inst);
|
||||
|
||||
bool EmitProgram(EmitterState& state, const IR::Program& 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) {
|
||||
@@ -923,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();
|
||||
@@ -981,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;
|
||||
@@ -1038,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,15 @@ 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)});
|
||||
}
|
||||
@@ -143,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 =
|
||||
@@ -564,9 +586,13 @@ void AddDescriptorAnnotationsAndNames(EmitterState& state) {
|
||||
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",
|
||||
@@ -644,6 +670,11 @@ 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);
|
||||
}
|
||||
@@ -682,7 +713,10 @@ void DefineModule(EmitterState& state) {
|
||||
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
|
||||
|
||||
@@ -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>
|
||||
@@ -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 EmitProgram(EmitterState& state, const IR::Program& 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 EmitProgram(EmitterState& state, const IR::Program& program, std::string* e
|
||||
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 EmitProgram(EmitterState& state, const IR::Program& program, std::string* e
|
||||
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;
|
||||
}
|
||||
@@ -1346,14 +1346,13 @@ bool IsolateSemanticLoopHeaders(Graph& graph, std::string* error) {
|
||||
// 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;
|
||||
}
|
||||
|
||||
@@ -1890,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;
|
||||
@@ -1905,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()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1923,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) {
|
||||
@@ -1936,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);
|
||||
}
|
||||
@@ -1971,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;
|
||||
@@ -2046,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 {
|
||||
@@ -2131,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);
|
||||
@@ -2164,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;
|
||||
@@ -2176,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)) {
|
||||
@@ -2208,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;
|
||||
}
|
||||
|
||||
@@ -2224,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++) {
|
||||
@@ -2241,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,8 +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},
|
||||
};
|
||||
@@ -147,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:
|
||||
@@ -192,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;
|
||||
@@ -236,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;
|
||||
}
|
||||
|
||||
@@ -273,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;
|
||||
}
|
||||
|
||||
@@ -311,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;
|
||||
}
|
||||
|
||||
@@ -359,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;
|
||||
}
|
||||
}
|
||||
@@ -392,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,
|
||||
@@ -421,13 +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) {
|
||||
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 upper word.
|
||||
inst.dst.sdwa_sel = 4u;
|
||||
// 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);
|
||||
}
|
||||
|
||||
@@ -105,21 +105,18 @@ constexpr auto SOPK_OPS = Detail::MakeOpcodeTable<0x20>(SOPK_OPCODE_LIST);
|
||||
constexpr auto SOPC_OPS = Detail::MakeOpcodeTable<0x80>(SOPC_OPCODE_LIST);
|
||||
constexpr auto SOPP_OPS = Detail::MakeOpcodeTable<0x80>(SOPP_OPCODE_LIST);
|
||||
|
||||
bool DecodeBinarySources(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index,
|
||||
Instruction& inst, uint32_t ssrc0, uint32_t ssrc1, std::string* error) {
|
||||
if (!DecodeScalarSource(ssrc0, pc, inst.src0, error) ||
|
||||
!DecodeScalarSource(ssrc1, pc, inst.src1, error)) {
|
||||
return false;
|
||||
}
|
||||
void DecodeBinarySources(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index,
|
||||
Instruction& inst, uint32_t ssrc0, uint32_t ssrc1) {
|
||||
DecodeScalarSource(ssrc0, pc, inst.src0);
|
||||
DecodeScalarSource(ssrc1, pc, inst.src1);
|
||||
inst.src_count = 2;
|
||||
ReadLiteralOperands(code, word_index, inst);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool DecodeSop1(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index, Instruction& inst,
|
||||
std::string* error) {
|
||||
void DecodeSop1(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index,
|
||||
Instruction& inst) {
|
||||
const uint32_t word = code[word_index];
|
||||
const uint32_t opcode = (word >> 8u) & 0xffu;
|
||||
const uint32_t ssrc0 = word & 0xffu;
|
||||
@@ -134,35 +131,31 @@ bool DecodeSop1(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index
|
||||
|
||||
if (inst.opcode == Opcode::UNSUPPORTED) {
|
||||
SetUnsupported(inst, Family::SOP1, opcode, "SOP1 opcode is not implemented");
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
|
||||
switch (inst.opcode) {
|
||||
case Opcode::S_GETPC_B64:
|
||||
inst.src_count = 0;
|
||||
return DecodeScalarDestination(sdst, pc, inst.dst, error);
|
||||
DecodeScalarDestination(sdst, pc, inst.dst);
|
||||
return;
|
||||
case Opcode::S_SETPC_B64:
|
||||
inst.src_count = 1;
|
||||
inst.dst.kind = OperandKind::Null;
|
||||
if (!DecodeScalarSource(ssrc0, pc, inst.src0, error)) {
|
||||
return false;
|
||||
}
|
||||
DecodeScalarSource(ssrc0, pc, inst.src0);
|
||||
ReadLiteralOperands(code, word_index, inst);
|
||||
return true;
|
||||
return;
|
||||
default: break;
|
||||
}
|
||||
|
||||
if (!DecodeScalarSource(ssrc0, pc, inst.src0, error) ||
|
||||
!DecodeScalarDestination(sdst, pc, inst.dst, error)) {
|
||||
return false;
|
||||
}
|
||||
DecodeScalarSource(ssrc0, pc, inst.src0);
|
||||
DecodeScalarDestination(sdst, pc, inst.dst);
|
||||
inst.src_count = 1;
|
||||
ReadLiteralOperands(code, word_index, inst);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DecodeSop2(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index, Instruction& inst,
|
||||
std::string* error) {
|
||||
void DecodeSop2(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index,
|
||||
Instruction& inst) {
|
||||
const uint32_t word = code[word_index];
|
||||
const uint32_t opcode = (word >> 23u) & 0x7fu;
|
||||
const uint32_t ssrc1 = (word >> 8u) & 0xffu;
|
||||
@@ -178,17 +171,15 @@ bool DecodeSop2(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index
|
||||
|
||||
if (inst.opcode == Opcode::UNSUPPORTED) {
|
||||
SetUnsupported(inst, Family::SOP2, opcode, "SOP2 opcode is not implemented");
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!DecodeScalarDestination(sdst, pc, inst.dst, error)) {
|
||||
return false;
|
||||
}
|
||||
return DecodeBinarySources(pc, code, word_index, inst, ssrc0, ssrc1, error);
|
||||
DecodeScalarDestination(sdst, pc, inst.dst);
|
||||
DecodeBinarySources(pc, code, word_index, inst, ssrc0, ssrc1);
|
||||
}
|
||||
|
||||
bool DecodeSopk(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index, Instruction& inst,
|
||||
std::string* error) {
|
||||
void DecodeSopk(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index,
|
||||
Instruction& inst) {
|
||||
const uint32_t word = code[word_index];
|
||||
const uint32_t opcode = (word >> 23u) & 0x1fu;
|
||||
const uint32_t sdst = (word >> 16u) & 0x7fu;
|
||||
@@ -207,18 +198,18 @@ bool DecodeSopk(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index
|
||||
|
||||
if (inst.opcode == Opcode::UNSUPPORTED) {
|
||||
SetUnsupported(inst, Family::SOPK, opcode, "SOPK opcode is not implemented");
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
|
||||
switch (inst.opcode) {
|
||||
case Opcode::S_MOVK_I32: return DecodeScalarDestination(sdst, pc, inst.dst, error);
|
||||
case Opcode::S_MOVK_I32: DecodeScalarDestination(sdst, pc, inst.dst); return;
|
||||
case Opcode::S_WAITCNT: {
|
||||
const uint32_t waitcnt = word & 0xffffu;
|
||||
inst.dst.kind = OperandKind::Null;
|
||||
inst.src0.signed_val = static_cast<int32_t>(waitcnt);
|
||||
inst.src0.value = waitcnt;
|
||||
inst.src_count = 1;
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
case Opcode::S_SETREG_B32:
|
||||
inst.dst.kind = OperandKind::Null;
|
||||
@@ -226,26 +217,25 @@ bool DecodeSopk(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index
|
||||
inst.src1.value = word & 0xffffu;
|
||||
inst.src1.signed_val = static_cast<int32_t>(imm);
|
||||
inst.src_count = 2;
|
||||
return DecodeScalarSource(sdst, pc, inst.src0, error);
|
||||
DecodeScalarSource(sdst, pc, inst.src0);
|
||||
return;
|
||||
default: break;
|
||||
}
|
||||
|
||||
inst.src1 = inst.src0;
|
||||
if (!DecodeScalarSource(sdst, pc, inst.src0, error)) {
|
||||
return false;
|
||||
}
|
||||
DecodeScalarSource(sdst, pc, inst.src0);
|
||||
if (inst.opcode == Opcode::S_ADD_I32 || inst.opcode == Opcode::S_MULK_I32) {
|
||||
inst.src_count = 2;
|
||||
return DecodeScalarDestination(sdst, pc, inst.dst, error);
|
||||
DecodeScalarDestination(sdst, pc, inst.dst);
|
||||
return;
|
||||
}
|
||||
|
||||
inst.dst.kind = OperandKind::Scc;
|
||||
inst.src_count = 2;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DecodeSopc(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index, Instruction& inst,
|
||||
std::string* error) {
|
||||
void DecodeSopc(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index,
|
||||
Instruction& inst) {
|
||||
const uint32_t word = code[word_index];
|
||||
const uint32_t ssrc1 = (word >> 8u) & 0xffu;
|
||||
const uint32_t ssrc0 = word & 0xffu;
|
||||
@@ -261,10 +251,10 @@ bool DecodeSopc(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index
|
||||
|
||||
if (inst.opcode == Opcode::UNSUPPORTED) {
|
||||
SetUnsupported(inst, Family::SOPC, opcode, "SOPC opcode is not implemented");
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
|
||||
return DecodeBinarySources(pc, code, word_index, inst, ssrc0, ssrc1, error);
|
||||
DecodeBinarySources(pc, code, word_index, inst, ssrc0, ssrc1);
|
||||
}
|
||||
|
||||
void DecodeSopp(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index,
|
||||
|
||||
@@ -5,14 +5,14 @@
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::Decoder {
|
||||
|
||||
bool DecodeSop1(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index, Instruction& inst,
|
||||
std::string* error);
|
||||
bool DecodeSop2(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index, Instruction& inst,
|
||||
std::string* error);
|
||||
bool DecodeSopk(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index, Instruction& inst,
|
||||
std::string* error);
|
||||
bool DecodeSopc(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index, Instruction& inst,
|
||||
std::string* error);
|
||||
void DecodeSop1(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index,
|
||||
Instruction& inst);
|
||||
void DecodeSop2(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index,
|
||||
Instruction& inst);
|
||||
void DecodeSopk(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index,
|
||||
Instruction& inst);
|
||||
void DecodeSopc(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index,
|
||||
Instruction& inst);
|
||||
void DecodeSopp(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index,
|
||||
Instruction& inst);
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "graphics/shader/recompiler/frontend/decode/ShaderDecoder.h"
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "graphics/shader/recompiler/frontend/decode/ExportOps.h"
|
||||
#include "graphics/shader/recompiler/frontend/decode/ImageOps.h"
|
||||
#include "graphics/shader/recompiler/frontend/decode/MemoryOps.h"
|
||||
@@ -17,12 +18,6 @@ uint32_t FloatBits(float value) {
|
||||
return std::bit_cast<uint32_t>(value);
|
||||
}
|
||||
|
||||
void SetError(std::string* error, const char* message) {
|
||||
if (error != nullptr) {
|
||||
*error = message;
|
||||
}
|
||||
}
|
||||
|
||||
bool HasLiteral(const Instruction& inst) {
|
||||
return inst.src0.kind == OperandKind::LiteralConstant ||
|
||||
inst.src1.kind == OperandKind::LiteralConstant ||
|
||||
@@ -216,86 +211,76 @@ const char* ImageDimensionToString(ImageDimension dimension) {
|
||||
}
|
||||
}
|
||||
|
||||
bool DecodeScalarSource(uint32_t code, uint32_t pc, Operand& operand, std::string* error) {
|
||||
void DecodeScalarSource(uint32_t code, uint32_t pc, Operand& operand) {
|
||||
operand = {};
|
||||
|
||||
if (code <= 105u) {
|
||||
operand.kind = OperandKind::Sgpr;
|
||||
operand.reg = code;
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
if (code >= 128u && code <= 192u) {
|
||||
operand.kind = OperandKind::IntegerInlineConstant;
|
||||
operand.signed_val = static_cast<int32_t>(code - 128u);
|
||||
operand.value = static_cast<uint32_t>(operand.signed_val);
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
if (code >= 193u && code <= 208u) {
|
||||
operand.kind = OperandKind::IntegerInlineConstant;
|
||||
operand.signed_val = 192 - static_cast<int32_t>(code);
|
||||
operand.value = static_cast<uint32_t>(operand.signed_val);
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
if (code >= 240u && code <= 247u) {
|
||||
constexpr float values[] = {0.5f, -0.5f, 1.0f, -1.0f, 2.0f, -2.0f, 4.0f, -4.0f};
|
||||
operand.kind = OperandKind::FloatInlineConstant;
|
||||
operand.float_val = values[code - 240u];
|
||||
operand.value = FloatBits(operand.float_val);
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
if (code >= 256u && code <= 511u) {
|
||||
DecodeVectorGpr(code - 256u, operand);
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
|
||||
switch (code) {
|
||||
case 106u: operand.kind = OperandKind::VccLo; return true;
|
||||
case 107u: operand.kind = OperandKind::VccHi; return true;
|
||||
case 124u: operand.kind = OperandKind::M0; return true;
|
||||
case 125u: operand.kind = OperandKind::Null; return true;
|
||||
case 126u: operand.kind = OperandKind::ExecLo; return true;
|
||||
case 127u: operand.kind = OperandKind::ExecHi; return true;
|
||||
case 239u: operand.kind = OperandKind::PopsExitingWaveId; return true;
|
||||
case 106u: operand.kind = OperandKind::VccLo; return;
|
||||
case 107u: operand.kind = OperandKind::VccHi; return;
|
||||
case 124u: operand.kind = OperandKind::M0; return;
|
||||
case 125u: operand.kind = OperandKind::Null; return;
|
||||
case 126u: operand.kind = OperandKind::ExecLo; return;
|
||||
case 127u: operand.kind = OperandKind::ExecHi; return;
|
||||
case 239u: operand.kind = OperandKind::PopsExitingWaveId; return;
|
||||
case 248u:
|
||||
operand.kind = OperandKind::FloatInlineConstant;
|
||||
operand.float_val = 0.15915494309189535f;
|
||||
operand.value = FloatBits(operand.float_val);
|
||||
return true;
|
||||
case 251u: operand.kind = OperandKind::VccZ; return true;
|
||||
case 252u: operand.kind = OperandKind::ExecZ; return true;
|
||||
case 253u: operand.kind = OperandKind::Scc; return true;
|
||||
case 255u: operand.kind = OperandKind::LiteralConstant; return true;
|
||||
default:
|
||||
if (error != nullptr) {
|
||||
*error = fmt::format("unsupported scalar source operand 0x{:08x} at pc 0x{:08x}",
|
||||
code, pc);
|
||||
}
|
||||
return false;
|
||||
return;
|
||||
case 251u: operand.kind = OperandKind::VccZ; return;
|
||||
case 252u: operand.kind = OperandKind::ExecZ; return;
|
||||
case 253u: operand.kind = OperandKind::Scc; return;
|
||||
case 255u: operand.kind = OperandKind::LiteralConstant; return;
|
||||
default: EXIT("unsupported scalar source operand 0x%08x at pc 0x%08x", code, pc);
|
||||
}
|
||||
}
|
||||
|
||||
bool DecodeScalarDestination(uint32_t code, uint32_t pc, Operand& operand, std::string* error) {
|
||||
void DecodeScalarDestination(uint32_t code, uint32_t pc, Operand& operand) {
|
||||
operand = {};
|
||||
|
||||
if (code <= 105u) {
|
||||
operand.kind = OperandKind::Sgpr;
|
||||
operand.reg = code;
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
|
||||
switch (code) {
|
||||
case 106u: operand.kind = OperandKind::VccLo; return true;
|
||||
case 107u: operand.kind = OperandKind::VccHi; return true;
|
||||
case 124u: operand.kind = OperandKind::M0; return true;
|
||||
case 125u: operand.kind = OperandKind::Null; return true;
|
||||
case 126u: operand.kind = OperandKind::ExecLo; return true;
|
||||
case 127u: operand.kind = OperandKind::ExecHi; return true;
|
||||
default:
|
||||
if (error != nullptr) {
|
||||
*error = fmt::format(
|
||||
"unsupported scalar destination operand 0x{:08x} at pc 0x{:08x}", code, pc);
|
||||
}
|
||||
return false;
|
||||
case 106u: operand.kind = OperandKind::VccLo; return;
|
||||
case 107u: operand.kind = OperandKind::VccHi; return;
|
||||
case 124u: operand.kind = OperandKind::M0; return;
|
||||
case 125u: operand.kind = OperandKind::Null; return;
|
||||
case 126u: operand.kind = OperandKind::ExecLo; return;
|
||||
case 127u: operand.kind = OperandKind::ExecHi; return;
|
||||
default: EXIT("unsupported scalar destination operand 0x%08x at pc 0x%08x", code, pc);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -368,39 +353,33 @@ Family GetInstructionFamily(uint32_t word) {
|
||||
}
|
||||
}
|
||||
|
||||
bool DecodeInstruction(std::span<const uint32_t> code, uint32_t word_index, Instruction& inst,
|
||||
std::string* error) {
|
||||
void DecodeInstruction(std::span<const uint32_t> code, uint32_t word_index, Instruction& inst) {
|
||||
const uint32_t pc = word_index * sizeof(uint32_t);
|
||||
switch (GetInstructionFamily(code[word_index])) {
|
||||
case Family::SOP1: return DecodeSop1(pc, code, word_index, inst, error);
|
||||
case Family::SOP2: return DecodeSop2(pc, code, word_index, inst, error);
|
||||
case Family::SOPK: return DecodeSopk(pc, code, word_index, inst, error);
|
||||
case Family::SOPC: return DecodeSopc(pc, code, word_index, inst, error);
|
||||
case Family::SOPP: DecodeSopp(pc, code, word_index, inst); return true;
|
||||
case Family::VOP1: return DecodeVop1(pc, code, word_index, inst, error);
|
||||
case Family::VOP2: return DecodeVop2(pc, code, word_index, inst, error);
|
||||
case Family::VOP3: return DecodeVop3(pc, code, word_index, inst, error);
|
||||
case Family::VOP3P: return DecodeVop3p(pc, code, word_index, inst, error);
|
||||
case Family::VOPC: return DecodeVopc(pc, code, word_index, inst, error);
|
||||
case Family::VINTRP: DecodeVintrp(pc, code, word_index, inst); return true;
|
||||
case Family::SMEM: DecodeSmem(pc, code, word_index, inst); return true;
|
||||
case Family::MUBUF: DecodeMubuf(pc, code, word_index, inst); return true;
|
||||
case Family::MTBUF: DecodeMtbuf(pc, code, word_index, inst); return true;
|
||||
case Family::FLAT: DecodeFlat(pc, code, word_index, inst); return true;
|
||||
case Family::DS: DecodeDs(pc, code, word_index, inst); return true;
|
||||
case Family::MIMG: DecodeMimg(pc, code, word_index, inst); return true;
|
||||
case Family::EXP: DecodeExp(pc, code, word_index, inst); return true;
|
||||
case Family::SOP1: DecodeSop1(pc, code, word_index, inst); return;
|
||||
case Family::SOP2: DecodeSop2(pc, code, word_index, inst); return;
|
||||
case Family::SOPK: DecodeSopk(pc, code, word_index, inst); return;
|
||||
case Family::SOPC: DecodeSopc(pc, code, word_index, inst); return;
|
||||
case Family::SOPP: DecodeSopp(pc, code, word_index, inst); return;
|
||||
case Family::VOP1: DecodeVop1(pc, code, word_index, inst); return;
|
||||
case Family::VOP2: DecodeVop2(pc, code, word_index, inst); return;
|
||||
case Family::VOP3: DecodeVop3(pc, code, word_index, inst); return;
|
||||
case Family::VOP3P: DecodeVop3p(pc, code, word_index, inst); return;
|
||||
case Family::VOPC: DecodeVopc(pc, code, word_index, inst); return;
|
||||
case Family::VINTRP: DecodeVintrp(pc, code, word_index, inst); return;
|
||||
case Family::SMEM: DecodeSmem(pc, code, word_index, inst); return;
|
||||
case Family::MUBUF: DecodeMubuf(pc, code, word_index, inst); return;
|
||||
case Family::MTBUF: DecodeMtbuf(pc, code, word_index, inst); return;
|
||||
case Family::FLAT: DecodeFlat(pc, code, word_index, inst); return;
|
||||
case Family::DS: DecodeDs(pc, code, word_index, inst); return;
|
||||
case Family::MIMG: DecodeMimg(pc, code, word_index, inst); return;
|
||||
case Family::EXP: DecodeExp(pc, code, word_index, inst); return;
|
||||
default:
|
||||
if (error != nullptr) {
|
||||
*error =
|
||||
fmt::format("unknown RDNA2 instruction family at pc 0x{:08x}, raw=0x{:08x}", pc,
|
||||
code[word_index]);
|
||||
}
|
||||
return false;
|
||||
EXIT("unknown RDNA2 instruction family at pc 0x%08x, raw=0x%08x", pc, code[word_index]);
|
||||
}
|
||||
}
|
||||
|
||||
bool DecodeProgram(std::span<const uint32_t> code, Program& program, std::string* error) {
|
||||
void DecodeProgram(std::span<const uint32_t> code, Program& program) {
|
||||
program.instructions.clear();
|
||||
program.instructions.reserve(code.size());
|
||||
program.code = code;
|
||||
@@ -408,10 +387,7 @@ bool DecodeProgram(std::span<const uint32_t> code, Program& program, std::string
|
||||
std::vector<bool> branch_targets;
|
||||
for (uint32_t word_index = 0; word_index < code.size();) {
|
||||
program.instructions.emplace_back();
|
||||
if (!DecodeInstruction(code, word_index, program.instructions.back(), error)) {
|
||||
program.instructions.pop_back();
|
||||
return false;
|
||||
}
|
||||
DecodeInstruction(code, word_index, program.instructions.back());
|
||||
|
||||
const auto& inst = program.instructions.back();
|
||||
word_index += inst.word_count;
|
||||
@@ -425,12 +401,11 @@ bool DecodeProgram(std::span<const uint32_t> code, Program& program, std::string
|
||||
}
|
||||
if (inst.opcode == Opcode::S_ENDPGM &&
|
||||
(word_index >= code.size() || branch_targets.empty() || !branch_targets[word_index])) {
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
SetError(error, "shader decode reached the code boundary before S_ENDPGM");
|
||||
return false;
|
||||
EXIT("shader decode reached the code boundary before S_ENDPGM");
|
||||
}
|
||||
|
||||
std::string OperandToString(const Operand& operand) {
|
||||
@@ -661,6 +636,7 @@ std::string InstructionToString(const Instruction& inst) {
|
||||
case Opcode::DS_MIN_F32:
|
||||
case Opcode::DS_MAX_F32:
|
||||
case Opcode::DS_SWIZZLE_B32:
|
||||
case Opcode::DS_BPERMUTE_B32:
|
||||
case Opcode::DS_READ_I8:
|
||||
case Opcode::DS_READ_U8:
|
||||
case Opcode::DS_READ_I16:
|
||||
@@ -675,6 +651,7 @@ std::string InstructionToString(const Instruction& inst) {
|
||||
case Opcode::DS_READ_B128:
|
||||
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:
|
||||
|
||||
@@ -179,12 +179,15 @@ enum class Opcode {
|
||||
V_CEIL_F16,
|
||||
V_TRUNC_F16,
|
||||
V_RNDNE_F16,
|
||||
V_SIN_F16,
|
||||
V_COS_F16,
|
||||
V_SIN_F32,
|
||||
V_COS_F32,
|
||||
V_NOT_B32,
|
||||
V_BFREV_B32,
|
||||
V_FFBH_U32,
|
||||
V_FFBL_B32,
|
||||
V_FFBH_I32,
|
||||
V_ADD_F32,
|
||||
V_SUB_F32,
|
||||
V_SUBREV_F32,
|
||||
@@ -348,6 +351,7 @@ enum class Opcode {
|
||||
V_CMP_GE_I32,
|
||||
V_CMP_T_I32,
|
||||
V_CMP_CLASS_F32,
|
||||
V_CMPX_CLASS_F32,
|
||||
V_CMP_LT_I16,
|
||||
V_CMP_EQ_I16,
|
||||
V_CMP_LE_I16,
|
||||
@@ -366,6 +370,7 @@ enum class Opcode {
|
||||
V_CMPX_LE_F16,
|
||||
V_CMPX_GT_F16,
|
||||
V_CMPX_GE_F16,
|
||||
V_CMPX_NGT_F16,
|
||||
V_CMPX_NEQ_F16,
|
||||
V_CMPX_NLT_F16,
|
||||
V_CMPX_LT_I32,
|
||||
@@ -378,6 +383,7 @@ enum class Opcode {
|
||||
V_CMP_EQ_U16,
|
||||
V_CMP_LE_U16,
|
||||
V_CMP_GT_U16,
|
||||
V_CMPX_GT_U16,
|
||||
V_CMP_NE_U16,
|
||||
V_CMP_GE_U16,
|
||||
V_CMP_F_U32,
|
||||
@@ -389,6 +395,7 @@ enum class Opcode {
|
||||
V_CMP_GE_U32,
|
||||
V_CMP_T_U32,
|
||||
V_CMP_EQ_I64,
|
||||
V_CMP_EQ_U64,
|
||||
V_CMP_GT_U64,
|
||||
V_CMP_NE_U64,
|
||||
V_CMPX_NE_I64,
|
||||
@@ -488,6 +495,7 @@ enum class Opcode {
|
||||
DS_MIN_F32,
|
||||
DS_MAX_F32,
|
||||
DS_SWIZZLE_B32,
|
||||
DS_BPERMUTE_B32,
|
||||
DS_CONSUME,
|
||||
DS_APPEND,
|
||||
DS_READ_I8,
|
||||
@@ -495,6 +503,7 @@ enum class Opcode {
|
||||
DS_READ_I16,
|
||||
DS_READ_U16,
|
||||
DS_READ_U16_D16,
|
||||
DS_READ_U16_D16_HI,
|
||||
DS_READ2_B32,
|
||||
DS_READ2ST64_B32,
|
||||
DS_READ_B32,
|
||||
@@ -505,6 +514,7 @@ enum class Opcode {
|
||||
DS_READ_B128,
|
||||
DS_WRITE_B8,
|
||||
DS_WRITE_B16,
|
||||
DS_WRITE_B16_D16_HI,
|
||||
DS_WRITE2_B32,
|
||||
DS_WRITE2ST64_B32,
|
||||
DS_WRITE2_B64,
|
||||
@@ -622,6 +632,7 @@ struct Operand {
|
||||
uint32_t dpp_ctrl = 0;
|
||||
uint32_t dpp_row_mask = 0xf;
|
||||
uint32_t dpp_bank_mask = 0xf;
|
||||
bool explicit_sdwa_dst = false;
|
||||
bool sdwa_sext = false;
|
||||
bool dpp_fetch_inactive = false;
|
||||
bool dpp_bound_ctrl = false;
|
||||
@@ -693,12 +704,11 @@ struct Program {
|
||||
// Code spans are trusted to contain complete instructions, valid branch targets, and 32-bit PCs.
|
||||
Family GetInstructionFamily(uint32_t word);
|
||||
// The output object must be freshly initialized.
|
||||
bool DecodeInstruction(std::span<const uint32_t> code, uint32_t word_index, Instruction& inst,
|
||||
std::string* error);
|
||||
bool DecodeProgram(std::span<const uint32_t> code, Program& program, std::string* error);
|
||||
void DecodeInstruction(std::span<const uint32_t> code, uint32_t word_index, Instruction& inst);
|
||||
void DecodeProgram(std::span<const uint32_t> code, Program& program);
|
||||
|
||||
bool DecodeScalarSource(uint32_t code, uint32_t pc, Operand& operand, std::string* error);
|
||||
bool DecodeScalarDestination(uint32_t code, uint32_t pc, Operand& operand, std::string* error);
|
||||
void DecodeScalarSource(uint32_t code, uint32_t pc, Operand& operand);
|
||||
void DecodeScalarDestination(uint32_t code, uint32_t pc, Operand& operand);
|
||||
void DecodeVectorGpr(uint32_t reg, Operand& operand);
|
||||
void ReadLiteralOperands(std::span<const uint32_t> code, uint32_t word_index, Instruction& inst);
|
||||
void SetRawWords(Instruction& inst, std::span<const uint32_t> code, uint32_t word_index,
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -5,16 +5,16 @@
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::Decoder {
|
||||
|
||||
bool DecodeVop2(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index, Instruction& inst,
|
||||
std::string* error);
|
||||
bool DecodeVop1(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index, Instruction& inst,
|
||||
std::string* error);
|
||||
bool DecodeVopc(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index, Instruction& inst,
|
||||
std::string* error);
|
||||
bool DecodeVop3(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index, Instruction& inst,
|
||||
std::string* error);
|
||||
bool DecodeVop3p(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index,
|
||||
Instruction& inst, std::string* error);
|
||||
void DecodeVop2(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index,
|
||||
Instruction& inst);
|
||||
void DecodeVop1(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index,
|
||||
Instruction& inst);
|
||||
void DecodeVopc(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index,
|
||||
Instruction& inst);
|
||||
void DecodeVop3(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index,
|
||||
Instruction& inst);
|
||||
void DecodeVop3p(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index,
|
||||
Instruction& inst);
|
||||
void DecodeVintrp(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index,
|
||||
Instruction& inst);
|
||||
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
#include "common/assert.h"
|
||||
#include "graphics/shader/recompiler/frontend/translate/Translator.h"
|
||||
#include "graphics/shader/shader.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <fmt/format.h>
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::Frontend {
|
||||
|
||||
@@ -47,14 +46,13 @@ IR::ExportFlags Translator::AddExportInfo(const Decoder::Instruction& inst) {
|
||||
return {.index = index, .pc = inst.pc};
|
||||
}
|
||||
|
||||
bool Translator::TranslateEmbeddedFetch(const Decoder::Instruction& inst, uint32_t attribute,
|
||||
void Translator::TranslateEmbeddedFetch(const Decoder::Instruction& inst, uint32_t attribute,
|
||||
uint32_t component_count) {
|
||||
for (uint32_t component = 0; component < component_count; component++) {
|
||||
const auto value =
|
||||
ir.Emit(IR::ValueOpcode::GetAttribute, {IR::Value(attribute), IR::Value(component)});
|
||||
WriteOperand(OffsetOperand(inst.dst, component), value);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void Translator::V_INTERP_P1_F32() {}
|
||||
@@ -65,25 +63,20 @@ void Translator::V_INTERP_P2_F32(const Decoder::Instruction& inst) {
|
||||
WriteOperand(inst.dst, value);
|
||||
}
|
||||
|
||||
bool Translator::V_INTERP_MOV_F32(const Decoder::Instruction& inst, std::string* error) {
|
||||
void Translator::V_INTERP_MOV_F32(const Decoder::Instruction& inst) {
|
||||
if (inst.src0.value >= 3u) {
|
||||
return ShaderError::Fail(error,
|
||||
fmt::format("v_interp_mov_f32 mode {} is reserved at pc 0x{:08x}",
|
||||
inst.src0.value, inst.pc));
|
||||
EXIT("v_interp_mov_f32 mode %u is reserved at pc 0x%08x", inst.src0.value, inst.pc);
|
||||
}
|
||||
const auto value = ir.Emit(
|
||||
IR::ValueOpcode::GetInterpolationParameter,
|
||||
{IR::Value(inst.src1.value), IR::Value(inst.src2.value), IR::Value(inst.src0.value)});
|
||||
WriteOperand(inst.dst, value);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Translator::EXP(const Decoder::Instruction& inst, std::string* error) {
|
||||
void Translator::EXP(const Decoder::Instruction& inst) {
|
||||
uint32_t index = 0;
|
||||
if (ExportTargetKindFromTarget(inst.exp.target, index) == IR::ExportTargetKind::Unknown) {
|
||||
return ShaderError::Fail(error,
|
||||
fmt::format("unsupported EXP target 0x{:02x} at pc 0x{:08x}",
|
||||
inst.exp.target, inst.pc));
|
||||
EXIT("unsupported EXP target 0x%02x at pc 0x%08x", inst.exp.target, inst.pc);
|
||||
}
|
||||
std::array<IR::Value, 4> components {IR::Value(0u), IR::Value(0u), IR::Value(0u),
|
||||
IR::Value(0u)};
|
||||
@@ -93,14 +86,13 @@ bool Translator::EXP(const Decoder::Instruction& inst, std::string* error) {
|
||||
const auto data = ir.Emit(IR::ValueOpcode::CompositeConstructU32x4,
|
||||
{components[0], components[1], components[2], components[3]});
|
||||
ir.Emit(IR::ValueOpcode::SetAttribute, {data, ir.GetExec()}, AddExportInfo(inst));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Translator::EmitInterpolation(const Decoder::Instruction& inst, std::string* error) {
|
||||
bool Translator::EmitInterpolation(const Decoder::Instruction& inst) {
|
||||
switch (inst.opcode) {
|
||||
case Decoder::Opcode::V_INTERP_P1_F32: V_INTERP_P1_F32(); return true;
|
||||
case Decoder::Opcode::V_INTERP_P2_F32: V_INTERP_P2_F32(inst); return true;
|
||||
case Decoder::Opcode::V_INTERP_MOV_F32: return V_INTERP_MOV_F32(inst, error);
|
||||
case Decoder::Opcode::V_INTERP_MOV_F32: V_INTERP_MOV_F32(inst); return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ void Translator::EmitCompareResult(const Decoder::Instruction& inst, IR::U1 valu
|
||||
if (cmpx) {
|
||||
const auto mask = BallotMask(masked);
|
||||
ir.SetExec(masked);
|
||||
ir.SetExecMaskTag(IR::U1(IR::Value(true)));
|
||||
ir.SetExecLo(mask[0]);
|
||||
ir.SetExecHi(mask[1]);
|
||||
return;
|
||||
@@ -55,11 +56,11 @@ void Translator::EmitFloatOrderedCompare(const Decoder::Instruction& inst, bool
|
||||
EmitCompareResult(inst, ordered ? ir.LogicalNot(unordered) : unordered, false, false);
|
||||
}
|
||||
|
||||
void Translator::V_CMP_CLASS_F32(const Decoder::Instruction& inst) {
|
||||
void Translator::EmitFloatClassCompare(const Decoder::Instruction& inst, bool cmpx) {
|
||||
const auto value = ReadOperand(inst.src0, IR::Type::F32);
|
||||
const auto mask = ReadOperand(inst.src1, IR::Type::U32);
|
||||
EmitCompareResult(inst, IR::U1(ir.Emit(IR::ValueOpcode::FPCmpClass32, {value, mask})), false,
|
||||
false);
|
||||
cmpx);
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::Frontend
|
||||
@@ -1,3 +1,4 @@
|
||||
#include "common/assert.h"
|
||||
#include "graphics/shader/recompiler/frontend/translate/Translator.h"
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::Frontend {
|
||||
@@ -23,18 +24,28 @@ Decoder::Operand ConditionOperand(Decoder::OperandKind kind) {
|
||||
|
||||
void Translator::S_SAVEEXEC(const Decoder::Instruction& inst, IR::ValueOpcode operation,
|
||||
bool negate_exec, bool negate_source, bool write_64) {
|
||||
if (write_64) {
|
||||
const auto exec_operand = ConditionOperand(Decoder::OperandKind::ExecLo);
|
||||
const auto old = ReadScalarU64(exec_operand);
|
||||
const auto bit_operation = operation == IR::ValueOpcode::LogicalAnd
|
||||
? IR::ValueOpcode::BitwiseAnd32
|
||||
: IR::ValueOpcode::BitwiseOr32;
|
||||
const auto result = BinaryScalarU64(old, ReadScalarU64(inst.src0), operation, bit_operation,
|
||||
negate_exec, negate_source, false);
|
||||
WriteScalarU64(inst.dst, old);
|
||||
WriteScalarU64(exec_operand, result);
|
||||
ir.SetScc(ScalarU64NonZero(result));
|
||||
return;
|
||||
}
|
||||
const auto old = ir.GetExec();
|
||||
const auto src = ReadMask(inst.src0);
|
||||
const auto lhs = negate_exec ? ir.LogicalNot(old) : old;
|
||||
const auto rhs = negate_source ? ir.LogicalNot(src) : src;
|
||||
const auto result = IR::U1(ir.Emit(operation, {lhs, rhs}));
|
||||
if (write_64) {
|
||||
WriteMask64(inst.dst, old);
|
||||
} else {
|
||||
WriteMask(inst.dst, old);
|
||||
}
|
||||
WriteMask(inst.dst, old);
|
||||
const auto mask = BallotMask(result);
|
||||
ir.SetExec(result);
|
||||
ir.SetExecMaskTag(IR::U1(IR::Value(true)));
|
||||
ir.SetExecLo(mask[0]);
|
||||
ir.SetExecHi(mask[1]);
|
||||
ir.SetScc(result);
|
||||
@@ -153,12 +164,12 @@ void Translator::S_INST_PREFETCH() {
|
||||
ir.Emit(IR::ValueOpcode::InstPrefetch);
|
||||
}
|
||||
|
||||
bool Translator::S_GETPC_B64(const Decoder::Instruction& inst, std::string* error) {
|
||||
void Translator::S_GETPC_B64(const Decoder::Instruction& inst) {
|
||||
const auto base = IR::U64(ir.Emit(IR::ValueOpcode::GetShaderBase));
|
||||
const auto address = IR::U64(
|
||||
ir.Emit(IR::ValueOpcode::IAdd64, {base, IR::Value(static_cast<uint64_t>(inst.pc) + 4u)}));
|
||||
if (inst.dst.kind == Decoder::OperandKind::Null) {
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
auto high = PlainOperand(inst.dst);
|
||||
switch (inst.dst.kind) {
|
||||
@@ -169,25 +180,19 @@ bool Translator::S_GETPC_B64(const Decoder::Instruction& inst, std::string* erro
|
||||
high.kind = Decoder::OperandKind::VccLo;
|
||||
high.reg = 0u;
|
||||
} else {
|
||||
if (error != nullptr) {
|
||||
*error = "S_GETPC_B64 destination does not name a valid scalar pair";
|
||||
}
|
||||
return false;
|
||||
EXIT("S_GETPC_B64 destination does not name a valid scalar pair at pc 0x%08x",
|
||||
inst.pc);
|
||||
}
|
||||
break;
|
||||
case Decoder::OperandKind::VccLo: high.kind = Decoder::OperandKind::VccHi; break;
|
||||
case Decoder::OperandKind::M0: high.kind = Decoder::OperandKind::Null; break;
|
||||
case Decoder::OperandKind::ExecLo: high.kind = Decoder::OperandKind::ExecHi; break;
|
||||
default:
|
||||
if (error != nullptr) {
|
||||
*error = "S_GETPC_B64 destination does not name a valid scalar pair";
|
||||
}
|
||||
return false;
|
||||
EXIT("S_GETPC_B64 destination does not name a valid scalar pair at pc 0x%08x", inst.pc);
|
||||
}
|
||||
const auto words = ExtractU64(address);
|
||||
WriteOperand(inst.dst, words[0]);
|
||||
WriteOperand(high, words[1]);
|
||||
return true;
|
||||
}
|
||||
|
||||
void Translator::S_CSELECT_B32(const Decoder::Instruction& inst) {
|
||||
@@ -196,30 +201,21 @@ void Translator::S_CSELECT_B32(const Decoder::Instruction& inst) {
|
||||
}
|
||||
|
||||
void Translator::S_CSELECT_B64(const Decoder::Instruction& inst) {
|
||||
const auto condition = ir.GetScc();
|
||||
const auto lhs = ReadU32Pair(inst.src0);
|
||||
const auto rhs = ReadU32Pair(inst.src1);
|
||||
const auto selected_mask = IR::U1(
|
||||
ir.Emit(IR::ValueOpcode::SelectU1, {condition, ReadMask(inst.src0), ReadMask(inst.src1)}));
|
||||
const auto selected_mask_valid =
|
||||
IR::U1(ir.Emit(IR::ValueOpcode::SelectU1,
|
||||
{condition, ReadMaskValid(inst.src0), ReadMaskValid(inst.src1)}));
|
||||
if (IsExecOrVcc(inst.dst)) {
|
||||
WriteMask64(inst.dst, selected_mask);
|
||||
return;
|
||||
}
|
||||
WriteU32Pair(inst.dst,
|
||||
{ir.Select(condition, lhs[0], rhs[0]), ir.Select(condition, lhs[1], rhs[1])});
|
||||
if (inst.dst.kind == Decoder::OperandKind::Sgpr) {
|
||||
const auto dst = static_cast<IR::ScalarReg>(inst.dst.reg);
|
||||
ir.SetThreadBitScalarReg(dst, selected_mask);
|
||||
ir.SetScalarMaskTag(dst, selected_mask_valid);
|
||||
}
|
||||
const auto condition = ir.GetScc();
|
||||
const auto lhs = ReadScalarU64(inst.src0);
|
||||
const auto rhs = ReadScalarU64(inst.src1);
|
||||
WriteScalarU64(inst.dst, SelectScalarU64(condition, lhs, rhs));
|
||||
}
|
||||
|
||||
void Translator::MOV_B32(const Decoder::Instruction& inst, bool apply_float_modifiers) {
|
||||
if (inst.src0.kind == Decoder::OperandKind::Sgpr &&
|
||||
inst.dst.kind == Decoder::OperandKind::Sgpr && inst.src0.reg == inst.dst.reg) {
|
||||
return;
|
||||
}
|
||||
if (IsExecOrVcc(inst.src0) && IsExecOrVcc(inst.dst)) {
|
||||
WriteMask(inst.dst, ReadMask(inst.src0));
|
||||
if (inst.src0.kind != inst.dst.kind) {
|
||||
WriteRawU32(inst.dst, ReadRawU32(inst.src0));
|
||||
}
|
||||
} else if (apply_float_modifiers && (inst.src0.negate || inst.src0.absolute)) {
|
||||
WriteOperand(DestinationOperand(inst), ReadOperand(inst.src0, IR::Type::F32));
|
||||
} else {
|
||||
@@ -228,67 +224,28 @@ void Translator::MOV_B32(const Decoder::Instruction& inst, bool apply_float_modi
|
||||
}
|
||||
|
||||
void Translator::S_MOV_B64(const Decoder::Instruction& inst) {
|
||||
if (IsExecOrVcc(inst.dst) || IsExecOrVcc(inst.src0)) {
|
||||
WriteMask64(inst.dst, ReadMask(inst.src0));
|
||||
return;
|
||||
}
|
||||
const bool scalar_copy =
|
||||
inst.dst.kind == Decoder::OperandKind::Sgpr && inst.src0.kind == Decoder::OperandKind::Sgpr;
|
||||
IR::U1 source_mask;
|
||||
IR::U1 source_mask_valid;
|
||||
if (scalar_copy) {
|
||||
source_mask = ir.GetThreadBitScalarReg(static_cast<IR::ScalarReg>(inst.src0.reg));
|
||||
source_mask_valid = ir.GetScalarMaskTag(static_cast<IR::ScalarReg>(inst.src0.reg));
|
||||
}
|
||||
WriteU32Pair(inst.dst, ReadU32Pair(inst.src0));
|
||||
if (scalar_copy) {
|
||||
ir.SetThreadBitScalarReg(static_cast<IR::ScalarReg>(inst.dst.reg), source_mask);
|
||||
ir.SetScalarMaskTag(static_cast<IR::ScalarReg>(inst.dst.reg), source_mask_valid);
|
||||
}
|
||||
WriteScalarU64(inst.dst, ReadScalarU64(inst.src0));
|
||||
}
|
||||
|
||||
void Translator::S_WQM_B64(const Decoder::Instruction& inst) {
|
||||
if (!IsExecOrVcc(inst.dst) && !IsExecOrVcc(inst.src0)) {
|
||||
const auto mask_valid = ReadMaskValid(inst.src0);
|
||||
const auto invocation_result =
|
||||
IR::U1(ir.Emit(IR::ValueOpcode::WqmMask, {ReadMask(inst.src0)}));
|
||||
const auto result =
|
||||
IR::U64(ir.Emit(IR::ValueOpcode::WqmU64, {ReadOperand(inst.src0, IR::Type::U64)}));
|
||||
WriteOperand(DestinationOperand(inst), result);
|
||||
if (inst.dst.kind == Decoder::OperandKind::Sgpr) {
|
||||
const auto dst = static_cast<IR::ScalarReg>(inst.dst.reg);
|
||||
ir.SetThreadBitScalarReg(dst, invocation_result);
|
||||
ir.SetScalarMaskTag(dst, mask_valid);
|
||||
const auto raw_nonzero =
|
||||
IR::U1(ir.Emit(IR::ValueOpcode::INotEqual64, {result, IR::Value(uint64_t {0})}));
|
||||
ir.SetScc(IR::U1(
|
||||
ir.Emit(IR::ValueOpcode::SelectU1, {mask_valid, invocation_result, raw_nonzero})));
|
||||
} else {
|
||||
ir.SetScc(
|
||||
IR::U1(ir.Emit(IR::ValueOpcode::INotEqual64, {result, IR::Value(uint64_t {0})})));
|
||||
}
|
||||
return;
|
||||
}
|
||||
const auto result = IR::U1(ir.Emit(IR::ValueOpcode::WqmMask, {ReadMask(inst.src0)}));
|
||||
WriteMask64(inst.dst, result);
|
||||
ir.SetScc(result);
|
||||
const auto source = ReadScalarU64(inst.src0);
|
||||
const auto raw = ExtractU64(
|
||||
IR::U64(ir.Emit(IR::ValueOpcode::WqmU64, {ir.ConstructU64(source.raw[0], source.raw[1])})));
|
||||
const auto invocation = IR::U1(ir.Emit(IR::ValueOpcode::WqmMask, {source.invocation}));
|
||||
const auto result = MakeScalarU64Result(raw, invocation, source.mask_valid);
|
||||
WriteScalarU64(inst.dst, result);
|
||||
ir.SetScc(ScalarU64NonZero(result));
|
||||
}
|
||||
|
||||
bool Translator::V_MOVRELS_B32(const Decoder::Instruction& inst, std::string* error) {
|
||||
void Translator::V_MOVRELS_B32(const Decoder::Instruction& inst) {
|
||||
if (inst.dst.kind != Decoder::OperandKind::Vgpr ||
|
||||
inst.src0.kind != Decoder::OperandKind::Vgpr) {
|
||||
if (error != nullptr) {
|
||||
*error = "V_MOVRELS_B32 requires VGPR source and destination";
|
||||
}
|
||||
return false;
|
||||
EXIT("V_MOVRELS_B32 requires VGPR source and destination at pc 0x%08x", inst.pc);
|
||||
}
|
||||
if (inst.dst.sdwa_sel != 6u || inst.dst.omod != 0u || inst.dst.clamp ||
|
||||
inst.src0.sdwa_sel != 6u || inst.src0.sdwa_sext || inst.src0.negate || inst.src0.absolute ||
|
||||
inst.src0.dpp) {
|
||||
if (error != nullptr) {
|
||||
*error = "V_MOVRELS_B32 modifiers are not implemented";
|
||||
}
|
||||
return false;
|
||||
EXIT("V_MOVRELS_B32 modifiers are not implemented at pc 0x%08x", inst.pc);
|
||||
}
|
||||
const auto base = inst.src0.reg;
|
||||
const auto m0 = ir.BitwiseAnd(ReadU32(ConditionOperand(Decoder::OperandKind::M0)),
|
||||
@@ -299,23 +256,16 @@ bool Translator::V_MOVRELS_B32(const Decoder::Instruction& inst, std::string* er
|
||||
selected = ir.Select(match, ir.GetVectorReg(static_cast<IR::VectorReg>(index)), selected);
|
||||
}
|
||||
WriteOperand(DestinationOperand(inst), selected);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Translator::V_MOVRELD_B32(const Decoder::Instruction& inst, std::string* error) {
|
||||
void Translator::V_MOVRELD_B32(const Decoder::Instruction& inst) {
|
||||
if (inst.dst.kind != Decoder::OperandKind::Vgpr) {
|
||||
if (error != nullptr) {
|
||||
*error = "V_MOVRELD_B32 requires VGPR destination";
|
||||
}
|
||||
return false;
|
||||
EXIT("V_MOVRELD_B32 requires VGPR destination at pc 0x%08x", inst.pc);
|
||||
}
|
||||
if (inst.dst.sdwa_sel != 6u || inst.dst.omod != 0u || inst.dst.clamp ||
|
||||
inst.src0.sdwa_sel != 6u || inst.src0.sdwa_sext || inst.src0.negate || inst.src0.absolute ||
|
||||
inst.src0.dpp) {
|
||||
if (error != nullptr) {
|
||||
*error = "V_MOVRELD_B32 modifiers are not implemented";
|
||||
}
|
||||
return false;
|
||||
EXIT("V_MOVRELD_B32 modifiers are not implemented at pc 0x%08x", inst.pc);
|
||||
}
|
||||
const auto base = inst.dst.reg;
|
||||
const auto value = ReadU32(inst.src0);
|
||||
@@ -327,7 +277,6 @@ bool Translator::V_MOVRELD_B32(const Decoder::Instruction& inst, std::string* er
|
||||
const auto write = ir.LogicalAnd(ir.GetExec(), match);
|
||||
ir.SetVectorReg(reg, ir.Select(write, value, ir.GetVectorReg(reg)));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void Translator::V_READFIRSTLANE_B32(const Decoder::Instruction& inst) {
|
||||
|
||||
@@ -141,8 +141,10 @@ void Translator::V_CVT_OFF_F32_I4(const Decoder::Instruction& inst) {
|
||||
}
|
||||
|
||||
void Translator::V_CVT_PKRTZ_F16_F32(const Decoder::Instruction& inst) {
|
||||
const auto lhs = ReadOperand(SourceAt(inst, 0), IR::Type::F32);
|
||||
const auto rhs = ReadOperand(SourceAt(inst, 1), IR::Type::F32);
|
||||
const auto lhs = ApplyF32ResultModifiers(
|
||||
inst.dst, IR::F32(ReadOperand(SourceAt(inst, 0), IR::Type::F32)));
|
||||
const auto rhs = ApplyF32ResultModifiers(
|
||||
inst.dst, IR::F32(ReadOperand(SourceAt(inst, 1), IR::Type::F32)));
|
||||
WriteOperand(DestinationOperand(inst), ir.Emit(IR::ValueOpcode::PackFloat2x16Rtz, {lhs, rhs}));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,23 +1,18 @@
|
||||
#include "common/assert.h"
|
||||
#include "graphics/shader/recompiler/frontend/translate/Translator.h"
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::Frontend {
|
||||
|
||||
bool Translator::TranslateInstruction(const Decoder::Instruction& inst, std::string* error) {
|
||||
void Translator::TranslateInstruction(const Decoder::Instruction& inst) {
|
||||
current_opcode = inst.opcode;
|
||||
current_pc = inst.pc;
|
||||
|
||||
switch (inst.opcode) {
|
||||
case Decoder::Opcode::UNKNOWN:
|
||||
case Decoder::Opcode::COUNT:
|
||||
if (error != nullptr) {
|
||||
*error = "decoded opcode has no IR translation";
|
||||
}
|
||||
return false;
|
||||
EXIT("decoded opcode has no IR translation at pc 0x%08x", inst.pc);
|
||||
case Decoder::Opcode::UNSUPPORTED:
|
||||
if (error != nullptr) {
|
||||
*error = "unsupported decoded instruction: " + Decoder::InstructionToString(inst);
|
||||
}
|
||||
return false;
|
||||
EXIT("unsupported decoded instruction: %s", Decoder::InstructionToString(inst).c_str());
|
||||
default: break;
|
||||
}
|
||||
|
||||
@@ -27,27 +22,30 @@ bool Translator::TranslateInstruction(const Decoder::Instruction& inst, std::str
|
||||
case Decoder::Family::SOP2:
|
||||
case Decoder::Family::SOPK:
|
||||
case Decoder::Family::SOPC:
|
||||
case Decoder::Family::SOPP: translated = EmitScalar(inst, error); break;
|
||||
case Decoder::Family::SOPP: translated = EmitScalar(inst); break;
|
||||
case Decoder::Family::VOP1:
|
||||
case Decoder::Family::VOP2:
|
||||
case Decoder::Family::VOP3:
|
||||
case Decoder::Family::VOP3P:
|
||||
case Decoder::Family::VOPC: translated = EmitVector(inst, error); break;
|
||||
case Decoder::Family::VOPC: translated = EmitVector(inst); break;
|
||||
case Decoder::Family::SMEM:
|
||||
case Decoder::Family::MUBUF:
|
||||
case Decoder::Family::MTBUF:
|
||||
case Decoder::Family::FLAT:
|
||||
case Decoder::Family::DS:
|
||||
case Decoder::Family::MIMG: translated = EmitMemory(inst, error); break;
|
||||
case Decoder::Family::VINTRP: translated = EmitInterpolation(inst, error); break;
|
||||
case Decoder::Family::EXP: translated = EXP(inst, error); break;
|
||||
case Decoder::Family::MIMG: translated = EmitMemory(inst); break;
|
||||
case Decoder::Family::VINTRP: translated = EmitInterpolation(inst); break;
|
||||
case Decoder::Family::EXP:
|
||||
EXP(inst);
|
||||
translated = true;
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
if (!translated && error != nullptr && error->empty()) {
|
||||
*error = "decoded opcode has no IR translation";
|
||||
if (!translated) {
|
||||
EXIT("opcode %s at pc 0x%08x has no IR translation",
|
||||
Decoder::InstructionToString(inst).c_str(), inst.pc);
|
||||
}
|
||||
return translated;
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::Frontend
|
||||
@@ -63,6 +63,42 @@ bool Translator::Float16Unary(const Decoder::Instruction& inst, IR::ValueOpcode
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Translator::Float16Trig(const Decoder::Instruction& inst, IR::ValueOpcode opcode) {
|
||||
const auto argument = ReadF16AsF32(inst.src0);
|
||||
const auto magnitude =
|
||||
ir.BitwiseAnd(ir.BitCastU32(argument), IR::U32(IR::Value(0x7fffffffu)));
|
||||
auto result = IR::F32(ir.Emit(opcode, {argument}));
|
||||
if (opcode == IR::ValueOpcode::FPSin) {
|
||||
const auto fraction = IR::F32(ir.Emit(IR::ValueOpcode::FPFract32, {argument}));
|
||||
const auto whole = IR::U1(
|
||||
ir.Emit(IR::ValueOpcode::FPOrdEqual32, {fraction, IR::Value::F32(0.0f)}));
|
||||
const auto half = IR::U1(
|
||||
ir.Emit(IR::ValueOpcode::FPOrdEqual32, {fraction, IR::Value::F32(0.5f)}));
|
||||
const auto nonzero = ir.INotEqual(magnitude, IR::U32(IR::Value(0u)));
|
||||
const auto cardinal = ir.LogicalOr(ir.LogicalAnd(whole, nonzero), half);
|
||||
result = IR::F32(ir.Emit(
|
||||
IR::ValueOpcode::SelectF32, {cardinal, IR::Value::F32(0.0f), result}));
|
||||
} else {
|
||||
// The architectural quarter-cycle result is exactly zero. Evaluating cos(pi/2) with a
|
||||
// rounded F32 PI can otherwise produce a half-precision subnormal instead.
|
||||
const auto fraction = IR::F32(ir.Emit(IR::ValueOpcode::FPFract32, {argument}));
|
||||
const auto quarter = IR::U1(
|
||||
ir.Emit(IR::ValueOpcode::FPOrdEqual32, {fraction, IR::Value::F32(0.25f)}));
|
||||
const auto three_quarters = IR::U1(
|
||||
ir.Emit(IR::ValueOpcode::FPOrdEqual32, {fraction, IR::Value::F32(0.75f)}));
|
||||
result = IR::F32(
|
||||
ir.Emit(IR::ValueOpcode::SelectF32,
|
||||
{ir.LogicalOr(quarter, three_quarters), IR::Value::F32(0.0f), result}));
|
||||
}
|
||||
|
||||
const auto infinite = ir.IEqual(magnitude, IR::U32(IR::Value(0x7f800000u)));
|
||||
result = ApplyF32ResultModifiers(inst.dst, result);
|
||||
const auto bits = PackHalf2x16(result, IR::F32(IR::Value::F32(0.0f)));
|
||||
const auto invalid = IR::U32(IR::Value(inst.dst.clamp ? 0u : 0xfe00u));
|
||||
WriteU16(DestinationOperand(inst), ir.Select(infinite, invalid, bits));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Translator::Float16Binary(const Decoder::Instruction& inst, IR::ValueOpcode opcode,
|
||||
bool reverse) {
|
||||
const auto lhs = ReadF16AsF32(reverse ? inst.src1 : inst.src0);
|
||||
|
||||
@@ -77,68 +77,15 @@ bool Translator::PackedInteger16MinMax(const Decoder::Instruction& inst, IR::Val
|
||||
return true;
|
||||
}
|
||||
|
||||
IR::U1 Translator::U64MaskBinary(const Decoder::Instruction& inst, IR::ValueOpcode opcode,
|
||||
bool negate_rhs, bool negate_result) {
|
||||
const auto lhs = ReadMask(inst.src0);
|
||||
auto rhs = ReadMask(inst.src1);
|
||||
if (negate_rhs) {
|
||||
rhs = ir.LogicalNot(rhs);
|
||||
}
|
||||
auto result = IR::U1(ir.Emit(opcode, {lhs, rhs}));
|
||||
return negate_result ? ir.LogicalNot(result) : result;
|
||||
}
|
||||
|
||||
bool Translator::S_U64_MASK(const Decoder::Instruction& inst, IR::ValueOpcode logical_opcode,
|
||||
IR::ValueOpcode bit_opcode, bool negate_rhs, bool negate_result,
|
||||
bool unary) {
|
||||
const auto invocation_result =
|
||||
unary ? ir.LogicalNot(ReadMask(inst.src0))
|
||||
: U64MaskBinary(inst, logical_opcode, negate_rhs, negate_result);
|
||||
const auto is_exec_or_vcc = [](const Decoder::Operand& operand) {
|
||||
switch (operand.kind) {
|
||||
case Decoder::OperandKind::ExecLo:
|
||||
case Decoder::OperandKind::ExecHi:
|
||||
case Decoder::OperandKind::VccLo:
|
||||
case Decoder::OperandKind::VccHi: return true;
|
||||
default: return false;
|
||||
}
|
||||
};
|
||||
if (is_exec_or_vcc(inst.dst) || is_exec_or_vcc(inst.src0) ||
|
||||
(inst.src_count > 1u && is_exec_or_vcc(inst.src1))) {
|
||||
WriteMask64(inst.dst, invocation_result);
|
||||
ir.SetScc(invocation_result);
|
||||
return true;
|
||||
}
|
||||
|
||||
const auto lhs = ReadU32Pair(inst.src0);
|
||||
auto mask_valid = ReadMaskValid(inst.src0);
|
||||
if (inst.src_count > 1u) {
|
||||
mask_valid = ir.LogicalAnd(mask_valid, ReadMaskValid(inst.src1));
|
||||
}
|
||||
std::array<IR::U32, 2> result;
|
||||
if (unary) {
|
||||
result = {ir.BitwiseNot(lhs[0]), ir.BitwiseNot(lhs[1])};
|
||||
} else {
|
||||
const auto rhs = ReadU32Pair(inst.src1);
|
||||
for (uint32_t component = 0; component < 2u; component++) {
|
||||
auto value = ir.Emit(
|
||||
bit_opcode, {lhs[component], negate_rhs ? IR::Value(ir.BitwiseNot(rhs[component]))
|
||||
: IR::Value(rhs[component])});
|
||||
result[component] = negate_result ? ir.BitwiseNot(IR::U32(value)) : IR::U32(value);
|
||||
}
|
||||
}
|
||||
WriteU32Pair(inst.dst, result);
|
||||
if (inst.dst.kind == Decoder::OperandKind::Sgpr) {
|
||||
const auto dst = static_cast<IR::ScalarReg>(inst.dst.reg);
|
||||
ir.SetThreadBitScalarReg(dst, invocation_result);
|
||||
ir.SetScalarMaskTag(dst, mask_valid);
|
||||
const auto raw_nonzero =
|
||||
ir.INotEqual(ir.BitwiseOr(result[0], result[1]), IR::U32(IR::Value(0u)));
|
||||
ir.SetScc(IR::U1(
|
||||
ir.Emit(IR::ValueOpcode::SelectU1, {mask_valid, invocation_result, raw_nonzero})));
|
||||
} else {
|
||||
ir.SetScc(ir.INotEqual(ir.BitwiseOr(result[0], result[1]), IR::U32(IR::Value(0u))));
|
||||
}
|
||||
const auto lhs = ReadScalarU64(inst.src0);
|
||||
const auto result = unary ? NotScalarU64(lhs)
|
||||
: BinaryScalarU64(lhs, ReadScalarU64(inst.src1), logical_opcode,
|
||||
bit_opcode, false, negate_rhs, negate_result);
|
||||
WriteScalarU64(inst.dst, result);
|
||||
ir.SetScc(ScalarU64NonZero(result));
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -224,11 +171,14 @@ bool Translator::S_FF1_I32_B64(const Decoder::Instruction& inst) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Translator::V_FFBH_U32(const Decoder::Instruction& inst) {
|
||||
const auto source = ReadU32(inst.src0);
|
||||
const auto msb = IR::U32(ir.Emit(IR::ValueOpcode::FindUMsb32, {source}));
|
||||
bool Translator::V_FFBH_32(const Decoder::Instruction& inst, bool sign) {
|
||||
const auto source = ReadU32(inst.src0);
|
||||
const auto value = sign ? ir.BitwiseXor(
|
||||
source, ir.ShiftRightArithmetic(source, IR::U32(IR::Value(31u))))
|
||||
: source;
|
||||
const auto msb = IR::U32(ir.Emit(IR::ValueOpcode::FindUMsb32, {value}));
|
||||
const auto position = ir.ISub(IR::U32(IR::Value(31u)), msb);
|
||||
const auto result = ir.Select(ir.INotEqual(source, IR::U32(IR::Value(0u))), position,
|
||||
const auto result = ir.Select(ir.INotEqual(value, IR::U32(IR::Value(0u))), position,
|
||||
IR::U32(IR::Value(0xffffffffu)));
|
||||
WriteOperand(DestinationOperand(inst), result);
|
||||
return true;
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
#include "common/magicEnum.h"
|
||||
#include "graphics/shader/recompiler/frontend/translate/Translator.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <fmt/format.h>
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::Frontend {
|
||||
|
||||
@@ -37,6 +35,7 @@ Decoder::Operand OffsetDecodedRegister(const Decoder::Operand& operand, uint32_t
|
||||
result.dpp_ctrl = 0;
|
||||
result.dpp_row_mask = 0xf;
|
||||
result.dpp_bank_mask = 0xf;
|
||||
result.explicit_sdwa_dst = false;
|
||||
result.dpp_fetch_inactive = false;
|
||||
result.dpp_bound_ctrl = false;
|
||||
result.dpp = false;
|
||||
@@ -56,11 +55,14 @@ uint32_t ResourceIndexFromOperand(const Decoder::Operand& operand) {
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t RawScalarLoadBase(const Decoder::Operand& operand) {
|
||||
if (operand.kind == Decoder::OperandKind::Sgpr) {
|
||||
return operand.reg;
|
||||
Decoder::Operand RawScalarLoadBase(const Decoder::Operand& operand) {
|
||||
if (operand.kind != Decoder::OperandKind::M0) {
|
||||
return operand;
|
||||
}
|
||||
return operand.kind == Decoder::OperandKind::VccLo ? 106u : 0u;
|
||||
auto result = operand;
|
||||
// The compressed SBASE value 62 decodes as code 124, but means NULL for S_LOAD.
|
||||
result.kind = Decoder::OperandKind::Null;
|
||||
return result;
|
||||
}
|
||||
|
||||
ResourceKind FlatSegmentResourceKind(uint32_t segment) {
|
||||
@@ -136,12 +138,10 @@ IR::MemoryInfo MemoryInfoFromDecoded(const Decoder::Instruction& decoded) {
|
||||
memory.offen = decoded.offen;
|
||||
memory.resource = ResourceIndexFromOperand(decoded.src1);
|
||||
memory.sampler = ResourceIndexFromOperand(decoded.src2);
|
||||
if (memory.kind == ResourceKind::ScalarAddress) {
|
||||
memory.resource = RawScalarLoadBase(decoded.src0);
|
||||
} else if (memory.kind == ResourceKind::ScalarBuffer) {
|
||||
if (memory.kind == ResourceKind::ScalarBuffer) {
|
||||
memory.resource = ResourceIndexFromOperand(decoded.src0);
|
||||
} else if (memory.kind == ResourceKind::Lds || memory.kind == ResourceKind::Gds ||
|
||||
memory.kind == ResourceKind::Scratch) {
|
||||
} else if (IsAddressResourceKind(memory.kind) || memory.kind == ResourceKind::Lds ||
|
||||
memory.kind == ResourceKind::Gds) {
|
||||
memory.resource = 0;
|
||||
memory.sampler = 0;
|
||||
}
|
||||
@@ -259,6 +259,7 @@ Decoder::Operand MemorySourceAt(const Decoder::Instruction& decoded, uint32_t in
|
||||
return index == 0u ? decoded.src1 : index == 1u ? decoded.src0 : decoded.src2;
|
||||
case Decoder::Opcode::DS_WRITE_B8:
|
||||
case Decoder::Opcode::DS_WRITE_B16:
|
||||
case Decoder::Opcode::DS_WRITE_B16_D16_HI:
|
||||
case Decoder::Opcode::DS_WRITE_B32:
|
||||
case Decoder::Opcode::DS_WRITE_B64:
|
||||
case Decoder::Opcode::DS_WRITE_B96:
|
||||
@@ -321,8 +322,9 @@ Translator::AddressOperands Translator::ReadAddressOperands(const Decoder::Instr
|
||||
return {GetAddressResource(low, high), low, high};
|
||||
}
|
||||
|
||||
IR::Value Translator::GetScalarAddressResource(uint32_t base) {
|
||||
return GetAddressResource(ReadScalarCode(base), ReadScalarCode(base + 1u));
|
||||
IR::Value Translator::GetScalarAddressResource(const Decoder::Operand& base) {
|
||||
const auto raw = ReadU32Pair(base);
|
||||
return GetAddressResource(raw[0], raw[1]);
|
||||
}
|
||||
|
||||
IR::Value Translator::GetImageResource(const IR::MemoryInfo& memory) {
|
||||
@@ -421,7 +423,7 @@ IR::Value Translator::NarrowSubdword(IR::U32 value, uint32_t bits) {
|
||||
bool Translator::S_LOAD(const Decoder::Instruction& inst, bool raw) {
|
||||
const auto memory = MemoryInfoFromDecoded(inst);
|
||||
const auto resource =
|
||||
raw ? GetScalarAddressResource(memory.resource) : GetBufferResource(memory);
|
||||
raw ? GetScalarAddressResource(RawScalarLoadBase(inst.src0)) : GetBufferResource(memory);
|
||||
const auto offset = ReadU32(inst.src1);
|
||||
std::array<IR::Value, 16> loaded {};
|
||||
for (uint32_t component = 0; component < memory.data_dwords; component++) {
|
||||
@@ -879,7 +881,14 @@ bool Translator::DS_SWIZZLE_B32(const Decoder::Instruction& inst) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Translator::EmitMemory(const Decoder::Instruction& inst, std::string* error) {
|
||||
bool Translator::DS_BPERMUTE_B32(const Decoder::Instruction& inst) {
|
||||
const auto address = ir.IAdd(ReadU32(inst.src0), IR::U32(IR::Value(inst.offset)));
|
||||
WriteOperand(inst.dst, ir.Emit(IR::ValueOpcode::BpermuteU32,
|
||||
{ReadU32(inst.src1), address, ir.GetExec()}));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Translator::EmitMemory(const Decoder::Instruction& inst) {
|
||||
switch (inst.opcode) {
|
||||
case Decoder::Opcode::S_LOAD_DWORD:
|
||||
case Decoder::Opcode::S_LOAD_DWORDX2:
|
||||
@@ -1037,6 +1046,7 @@ bool Translator::EmitMemory(const Decoder::Instruction& inst, std::string* error
|
||||
case Decoder::Opcode::DS_MAX_F32:
|
||||
return DS_MINMAX_F32(inst, IR::ValueOpcode::SharedAtomicFMax32);
|
||||
case Decoder::Opcode::DS_SWIZZLE_B32: return DS_SWIZZLE_B32(inst);
|
||||
case Decoder::Opcode::DS_BPERMUTE_B32: return DS_BPERMUTE_B32(inst);
|
||||
case Decoder::Opcode::DS_CONSUME:
|
||||
return DS_APPEND_CONSUME(inst, IR::ValueOpcode::DataConsume);
|
||||
case Decoder::Opcode::DS_APPEND:
|
||||
@@ -1052,6 +1062,7 @@ bool Translator::EmitMemory(const Decoder::Instruction& inst, std::string* error
|
||||
case Decoder::Opcode::DS_READ_I16:
|
||||
case Decoder::Opcode::DS_READ_U16:
|
||||
case Decoder::Opcode::DS_READ_U16_D16:
|
||||
case Decoder::Opcode::DS_READ_U16_D16_HI:
|
||||
case Decoder::Opcode::DS_READ_B32:
|
||||
case Decoder::Opcode::DS_READ_B64:
|
||||
case Decoder::Opcode::DS_READ_B96:
|
||||
@@ -1062,16 +1073,12 @@ bool Translator::EmitMemory(const Decoder::Instruction& inst, std::string* error
|
||||
case Decoder::Opcode::DS_WRITE2ST64_B64: return DS_WRITE2(inst);
|
||||
case Decoder::Opcode::DS_WRITE_B8:
|
||||
case Decoder::Opcode::DS_WRITE_B16:
|
||||
case Decoder::Opcode::DS_WRITE_B16_D16_HI:
|
||||
case Decoder::Opcode::DS_WRITE_B32:
|
||||
case Decoder::Opcode::DS_WRITE_B64:
|
||||
case Decoder::Opcode::DS_WRITE_B96:
|
||||
case Decoder::Opcode::DS_WRITE_B128: return DS_WRITE(inst);
|
||||
default:
|
||||
if (error != nullptr) {
|
||||
*error = fmt::format("memory-family opcode has no specialized IR translation: {}",
|
||||
magic_enum::enum_name(inst.opcode));
|
||||
}
|
||||
return false;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,14 +2,14 @@
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::Frontend {
|
||||
|
||||
bool Translator::EmitScalar(const Decoder::Instruction& inst, std::string* error) {
|
||||
bool Translator::EmitScalar(const Decoder::Instruction& inst) {
|
||||
using O = Decoder::Opcode;
|
||||
switch (inst.opcode) {
|
||||
case O::S_MOV_B32:
|
||||
case O::S_MOVK_I32: MOV_B32(inst, false); return true;
|
||||
case O::S_MOV_B64: S_MOV_B64(inst); return true;
|
||||
case O::S_WQM_B64: S_WQM_B64(inst); return true;
|
||||
case O::S_GETPC_B64: return S_GETPC_B64(inst, error);
|
||||
case O::S_GETPC_B64: S_GETPC_B64(inst); return true;
|
||||
case O::S_SETPC_B64: return true;
|
||||
case O::S_CSELECT_B32: S_CSELECT_B32(inst); return true;
|
||||
case O::S_CSELECT_B64: S_CSELECT_B64(inst); return true;
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
#include "common/assert.h"
|
||||
#include "graphics/shader/recompiler/frontend/translate/Translator.h"
|
||||
#include "graphics/shader/shader.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <fmt/format.h>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::Frontend {
|
||||
|
||||
using ShaderError::Fail;
|
||||
|
||||
Decoder::Operand Translator::SourceAt(const Decoder::Instruction& inst, uint32_t index) {
|
||||
switch (index) {
|
||||
case 0: return inst.src0;
|
||||
@@ -104,6 +102,7 @@ Decoder::Operand Translator::PlainOperand(const Decoder::Operand& operand) {
|
||||
result.dpp_ctrl = 0;
|
||||
result.dpp_row_mask = 0xf;
|
||||
result.dpp_bank_mask = 0xf;
|
||||
result.explicit_sdwa_dst = false;
|
||||
result.dpp_fetch_inactive = false;
|
||||
result.dpp_bound_ctrl = false;
|
||||
result.dpp = false;
|
||||
@@ -152,11 +151,8 @@ IR::U32 Translator::ReadScalarCode(uint32_t code) {
|
||||
case 106u: return ir.GetVccLo();
|
||||
case 107u: return ir.GetVccHi();
|
||||
case 124u: return ir.GetM0();
|
||||
case 126u:
|
||||
case 127u: {
|
||||
const auto mask = BallotMask(ir.GetExec());
|
||||
return mask[code - 126u];
|
||||
}
|
||||
case 126u: return ir.GetExecLo();
|
||||
case 127u: return ir.GetExecHi();
|
||||
default: return IR::U32(IR::Value(0u));
|
||||
}
|
||||
}
|
||||
@@ -316,19 +312,23 @@ void Translator::WriteRawU32(const Decoder::Operand& operand, IR::U32 value) {
|
||||
case Decoder::OperandKind::VccLo:
|
||||
ir.SetVccLo(IR::U32(value));
|
||||
ir.SetVcc(ThreadBit(IR::U32(value)));
|
||||
ir.SetVccMaskValidTag(IR::U1(IR::Value(false)));
|
||||
break;
|
||||
case Decoder::OperandKind::VccHi:
|
||||
ir.SetVccHi(IR::U32(value));
|
||||
ir.SetVcc(ThreadBit(ir.GetVccLo()));
|
||||
ir.SetVccMaskValidTag(IR::U1(IR::Value(false)));
|
||||
break;
|
||||
case Decoder::OperandKind::M0: ir.SetM0(IR::U32(value)); break;
|
||||
case Decoder::OperandKind::ExecLo:
|
||||
ir.SetExecLo(IR::U32(value));
|
||||
ir.SetExec(ThreadBit(IR::U32(value)));
|
||||
ir.SetExecMaskTag(IR::U1(IR::Value(false)));
|
||||
break;
|
||||
case Decoder::OperandKind::ExecHi:
|
||||
ir.SetExecHi(IR::U32(value));
|
||||
ir.SetExec(ThreadBit(ir.GetExecLo()));
|
||||
ir.SetExecMaskTag(IR::U1(IR::Value(false)));
|
||||
break;
|
||||
case Decoder::OperandKind::Scc:
|
||||
ir.SetScc(ir.INotEqual(value, IR::U32(IR::Value(0u))));
|
||||
@@ -373,6 +373,7 @@ void Translator::WriteOperand(const Decoder::Operand& operand, IR::Value value)
|
||||
case Decoder::OperandKind::ExecHi: {
|
||||
const auto mask = BallotMask(IR::U1(value));
|
||||
ir.SetExec(IR::U1(value));
|
||||
ir.SetExecMaskTag(IR::U1(IR::Value(true)));
|
||||
ir.SetExecLo(mask[0]);
|
||||
ir.SetExecHi(mask[1]);
|
||||
return;
|
||||
@@ -381,6 +382,7 @@ void Translator::WriteOperand(const Decoder::Operand& operand, IR::Value value)
|
||||
case Decoder::OperandKind::VccHi: {
|
||||
const auto mask = BallotMask(IR::U1(value));
|
||||
ir.SetVcc(IR::U1(value));
|
||||
ir.SetVccMaskValidTag(IR::U1(IR::Value(true)));
|
||||
ir.SetVccLo(mask[0]);
|
||||
ir.SetVccHi(mask[1]);
|
||||
return;
|
||||
@@ -392,12 +394,12 @@ void Translator::WriteOperand(const Decoder::Operand& operand, IR::Value value)
|
||||
}
|
||||
}
|
||||
if (type == IR::Type::U16) {
|
||||
WriteRawU32(operand, IR::U32(ir.Emit(IR::ValueOpcode::ConvertU32U16, {IR::U16(value)})));
|
||||
Write16Bits(operand, IR::U32(ir.Emit(IR::ValueOpcode::ConvertU32U16, {IR::U16(value)})));
|
||||
return;
|
||||
}
|
||||
if (type == IR::Type::F16) {
|
||||
const auto bits = IR::U16(ir.Emit(IR::ValueOpcode::BitCastU16F16, {value}));
|
||||
WriteRawU32(operand, IR::U32(ir.Emit(IR::ValueOpcode::ConvertU32U16, {bits})));
|
||||
Write16Bits(operand, IR::U32(ir.Emit(IR::ValueOpcode::ConvertU32U16, {bits})));
|
||||
return;
|
||||
}
|
||||
if (type == IR::Type::U64) {
|
||||
@@ -417,38 +419,32 @@ IR::U32 Translator::PackHalf2x16(IR::F32 low, IR::F32 high) {
|
||||
return IR::U32(ir.Emit(IR::ValueOpcode::PackHalf2x16, {pair}));
|
||||
}
|
||||
|
||||
void Translator::Write16Bits(const Decoder::Operand& operand, IR::U32 value) {
|
||||
auto destination = operand;
|
||||
// Native GFX10 16-bit results preserve the unselected half. Plain/DPP destinations use the
|
||||
// low half; native VOP3 may already select either half. Explicit SDWA retains encoded DST_U.
|
||||
if (!destination.explicit_sdwa_dst) {
|
||||
if (destination.sdwa_sel == 6u) {
|
||||
destination.sdwa_sel = 4u;
|
||||
}
|
||||
destination.sdwa_dst_unused = 2u;
|
||||
}
|
||||
destination.omod = 0u;
|
||||
destination.op_sel = false;
|
||||
destination.clamp = false;
|
||||
WriteRawU32(destination, ir.BitwiseAnd(value, IR::U32(IR::Value(0xffffu))));
|
||||
}
|
||||
|
||||
void Translator::WriteF16(const Decoder::Operand& operand, IR::F32 value) {
|
||||
value = ApplyF32ResultModifiers(operand, value);
|
||||
const auto half = IR::F16(ir.Emit(IR::ValueOpcode::ConvertF16F32, {value}));
|
||||
const auto bits = IR::U32(ir.Emit(IR::ValueOpcode::ConvertU32U16,
|
||||
{IR::U16(ir.Emit(IR::ValueOpcode::BitCastU16F16, {half}))}));
|
||||
auto merged = bits;
|
||||
if (operand.sdwa_sel == 4u || operand.sdwa_sel == 5u) {
|
||||
merged = IR::U32(ir.Emit(IR::ValueOpcode::BitFieldInsert,
|
||||
{ReadRawU32(PlainOperand(operand)), bits,
|
||||
IR::Value(operand.sdwa_sel == 5u ? 16u : 0u), IR::Value(16u)}));
|
||||
}
|
||||
auto raw = operand;
|
||||
raw.sdwa_sel = 6u;
|
||||
raw.omod = 0u;
|
||||
raw.op_sel = false;
|
||||
raw.clamp = false;
|
||||
WriteRawU32(raw, merged);
|
||||
Write16Bits(operand, bits);
|
||||
}
|
||||
|
||||
void Translator::WriteU16(const Decoder::Operand& operand, IR::U32 value) {
|
||||
auto merged = ir.BitwiseAnd(value, IR::U32(IR::Value(0xffffu)));
|
||||
if (operand.sdwa_sel == 4u || operand.sdwa_sel == 5u) {
|
||||
merged = IR::U32(ir.Emit(IR::ValueOpcode::BitFieldInsert,
|
||||
{ReadRawU32(PlainOperand(operand)), merged,
|
||||
IR::Value(operand.sdwa_sel == 5u ? 16u : 0u), IR::Value(16u)}));
|
||||
}
|
||||
auto raw = operand;
|
||||
raw.sdwa_sel = 6u;
|
||||
raw.omod = 0u;
|
||||
raw.op_sel = false;
|
||||
raw.clamp = false;
|
||||
WriteRawU32(raw, merged);
|
||||
Write16Bits(operand, value);
|
||||
}
|
||||
|
||||
IR::U32 Translator::ReadU32(const Decoder::Operand& operand) {
|
||||
@@ -587,11 +583,13 @@ void Translator::WriteU32Pair(const Decoder::Operand& operand,
|
||||
switch (operand.kind) {
|
||||
case Decoder::OperandKind::ExecLo:
|
||||
ir.SetExec(thread_bit);
|
||||
ir.SetExecMaskTag(IR::U1(IR::Value(false)));
|
||||
ir.SetExecLo(value[0]);
|
||||
ir.SetExecHi(value[1]);
|
||||
return;
|
||||
case Decoder::OperandKind::VccLo:
|
||||
ir.SetVcc(thread_bit);
|
||||
ir.SetVccMaskValidTag(IR::U1(IR::Value(false)));
|
||||
ir.SetVccLo(value[0]);
|
||||
ir.SetVccHi(value[1]);
|
||||
return;
|
||||
@@ -602,6 +600,87 @@ void Translator::WriteU32Pair(const Decoder::Operand& operand,
|
||||
WriteRawU32(OffsetOperand(operand, 1), value[1]);
|
||||
}
|
||||
|
||||
Translator::ScalarU64 Translator::ReadScalarU64(const Decoder::Operand& operand) {
|
||||
return {ReadU32Pair(operand), ReadMask(operand), ReadMaskValid(operand)};
|
||||
}
|
||||
|
||||
Translator::ScalarU64 Translator::MakeScalarU64Mask(IR::U1 value) {
|
||||
return {BallotMask(value), value, IR::U1(IR::Value(true))};
|
||||
}
|
||||
|
||||
Translator::ScalarU64 Translator::MakeScalarU64Result(const std::array<IR::U32, 2>& raw,
|
||||
IR::U1 invocation, IR::U1 mask_valid) {
|
||||
return {raw,
|
||||
IR::U1(ir.Emit(IR::ValueOpcode::SelectU1, {mask_valid, invocation, ThreadBit(raw[0])})),
|
||||
mask_valid};
|
||||
}
|
||||
|
||||
Translator::ScalarU64 Translator::SelectScalarU64(IR::U1 condition, const ScalarU64& lhs,
|
||||
const ScalarU64& rhs) {
|
||||
return {
|
||||
{ir.Select(condition, lhs.raw[0], rhs.raw[0]),
|
||||
ir.Select(condition, lhs.raw[1], rhs.raw[1])},
|
||||
IR::U1(ir.Emit(IR::ValueOpcode::SelectU1, {condition, lhs.invocation, rhs.invocation})),
|
||||
IR::U1(ir.Emit(IR::ValueOpcode::SelectU1, {condition, lhs.mask_valid, rhs.mask_valid}))};
|
||||
}
|
||||
|
||||
Translator::ScalarU64 Translator::NotScalarU64(const ScalarU64& value) {
|
||||
return MakeScalarU64Result({ir.BitwiseNot(value.raw[0]), ir.BitwiseNot(value.raw[1])},
|
||||
ir.LogicalNot(value.invocation), value.mask_valid);
|
||||
}
|
||||
|
||||
Translator::ScalarU64 Translator::BinaryScalarU64(const ScalarU64& lhs, const ScalarU64& rhs,
|
||||
IR::ValueOpcode logical_opcode,
|
||||
IR::ValueOpcode bit_opcode, bool negate_lhs,
|
||||
bool negate_rhs, bool negate_result) {
|
||||
auto lhs_mask = negate_lhs ? ir.LogicalNot(lhs.invocation) : lhs.invocation;
|
||||
auto rhs_mask = negate_rhs ? ir.LogicalNot(rhs.invocation) : rhs.invocation;
|
||||
auto mask = IR::U1(ir.Emit(logical_opcode, {lhs_mask, rhs_mask}));
|
||||
if (negate_result) {
|
||||
mask = ir.LogicalNot(mask);
|
||||
}
|
||||
std::array<IR::U32, 2> raw;
|
||||
for (uint32_t component = 0; component < raw.size(); component++) {
|
||||
auto lhs_raw = negate_lhs ? ir.BitwiseNot(lhs.raw[component]) : lhs.raw[component];
|
||||
auto rhs_raw = negate_rhs ? ir.BitwiseNot(rhs.raw[component]) : rhs.raw[component];
|
||||
raw[component] = IR::U32(ir.Emit(bit_opcode, {lhs_raw, rhs_raw}));
|
||||
if (negate_result) {
|
||||
raw[component] = ir.BitwiseNot(raw[component]);
|
||||
}
|
||||
}
|
||||
return MakeScalarU64Result(raw, mask, ir.LogicalAnd(lhs.mask_valid, rhs.mask_valid));
|
||||
}
|
||||
|
||||
void Translator::WriteScalarU64(const Decoder::Operand& operand, const ScalarU64& value) {
|
||||
WriteU32Pair(operand, value.raw);
|
||||
switch (operand.kind) {
|
||||
case Decoder::OperandKind::ExecLo:
|
||||
case Decoder::OperandKind::ExecHi:
|
||||
ir.SetExec(value.invocation);
|
||||
ir.SetExecMaskTag(value.mask_valid);
|
||||
break;
|
||||
case Decoder::OperandKind::VccLo:
|
||||
case Decoder::OperandKind::VccHi:
|
||||
ir.SetVcc(value.invocation);
|
||||
ir.SetVccMaskValidTag(value.mask_valid);
|
||||
break;
|
||||
case Decoder::OperandKind::Sgpr: {
|
||||
const auto reg = static_cast<IR::ScalarReg>(operand.reg);
|
||||
ir.SetThreadBitScalarReg(reg, value.invocation);
|
||||
ir.SetScalarMaskTag(reg, value.mask_valid);
|
||||
break;
|
||||
}
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
IR::U1 Translator::ScalarU64NonZero(const ScalarU64& value) {
|
||||
const auto raw_nonzero =
|
||||
ir.INotEqual(ir.BitwiseOr(value.raw[0], value.raw[1]), IR::U32(IR::Value(0u)));
|
||||
return IR::U1(
|
||||
ir.Emit(IR::ValueOpcode::SelectU1, {value.mask_valid, value.invocation, raw_nonzero}));
|
||||
}
|
||||
|
||||
IR::U1 Translator::ThreadBit(IR::U32 low) {
|
||||
return ir.INotEqual(low, IR::U32(IR::Value(0u)));
|
||||
}
|
||||
@@ -657,9 +736,9 @@ IR::U1 Translator::ReadMaskValid(const Decoder::Operand& operand) {
|
||||
case Decoder::OperandKind::Sgpr:
|
||||
return ir.GetScalarMaskTag(static_cast<IR::ScalarReg>(operand.reg));
|
||||
case Decoder::OperandKind::ExecLo:
|
||||
case Decoder::OperandKind::ExecHi:
|
||||
case Decoder::OperandKind::ExecHi: return ir.GetExecMaskTag();
|
||||
case Decoder::OperandKind::VccLo:
|
||||
case Decoder::OperandKind::VccHi:
|
||||
case Decoder::OperandKind::VccHi: return ir.GetVccMaskValidTag();
|
||||
case Decoder::OperandKind::VccZ:
|
||||
case Decoder::OperandKind::ExecZ:
|
||||
case Decoder::OperandKind::Scc: return IR::U1(IR::Value(true));
|
||||
@@ -692,6 +771,7 @@ void Translator::WriteMask(const Decoder::Operand& operand, IR::U1 value) {
|
||||
case Decoder::OperandKind::ExecHi: {
|
||||
const auto mask = BallotMask(value);
|
||||
ir.SetExec(value);
|
||||
ir.SetExecMaskTag(IR::U1(IR::Value(true)));
|
||||
ir.SetExecLo(mask[0]);
|
||||
ir.SetExecHi(mask[1]);
|
||||
return;
|
||||
@@ -700,6 +780,7 @@ void Translator::WriteMask(const Decoder::Operand& operand, IR::U1 value) {
|
||||
case Decoder::OperandKind::VccHi: {
|
||||
const auto mask = BallotMask(value);
|
||||
ir.SetVcc(value);
|
||||
ir.SetVccMaskValidTag(IR::U1(IR::Value(true)));
|
||||
ir.SetVccLo(mask[0]);
|
||||
ir.SetVccHi(mask[1]);
|
||||
return;
|
||||
@@ -713,25 +794,7 @@ void Translator::WriteMask(const Decoder::Operand& operand, IR::U1 value) {
|
||||
}
|
||||
|
||||
void Translator::WriteMask64(const Decoder::Operand& operand, IR::U1 value) {
|
||||
if (operand.kind != Decoder::OperandKind::Sgpr) {
|
||||
WriteMask(operand, value);
|
||||
return;
|
||||
}
|
||||
const auto reg = static_cast<IR::ScalarReg>(operand.reg);
|
||||
const auto mask = BallotMask(value);
|
||||
ir.SetThreadBitScalarReg(reg, value);
|
||||
ir.SetScalarMaskTag(reg, IR::U1(IR::Value(true)));
|
||||
if (IR::RegIndex(reg) > 0u) {
|
||||
ir.SetScalarMaskTag(static_cast<IR::ScalarReg>(IR::RegIndex(reg) - 1u),
|
||||
IR::U1(IR::Value(false)));
|
||||
}
|
||||
ir.SetScalarReg(reg, mask[0]);
|
||||
if (IR::RegIndex(reg) + 1u < IR::NumScalarRegs) {
|
||||
const auto high = static_cast<IR::ScalarReg>(IR::RegIndex(reg) + 1u);
|
||||
ir.SetScalarReg(high, mask[1]);
|
||||
ir.SetThreadBitScalarReg(high, IR::U1(IR::Value(false)));
|
||||
ir.SetScalarMaskTag(high, IR::U1(IR::Value(false)));
|
||||
}
|
||||
WriteScalarU64(operand, MakeScalarU64Mask(value));
|
||||
}
|
||||
|
||||
void Translator::WriteCompareResult(const Decoder::Operand& operand, IR::U1 value) {
|
||||
@@ -745,11 +808,10 @@ void Translator::WriteCompareResult(const Decoder::Operand& operand, IR::U1 valu
|
||||
WriteMask(operand, ir.LogicalAnd(ir.GetExec(), value));
|
||||
}
|
||||
|
||||
bool Translator::AddBranchCondition(const CFG::BasicBlock& source, IR::BlockInfo& info,
|
||||
std::string* error) {
|
||||
void Translator::AddBranchCondition(const CFG::BasicBlock& source, IR::BlockInfo& info) {
|
||||
if (source.terminator.goto_value >= 0) {
|
||||
if (source.terminator.goto_variable == UINT32_MAX) {
|
||||
return Fail(error, fmt::format("block {} sets an invalid goto variable", source.id));
|
||||
EXIT("block %u sets an invalid goto variable", source.id);
|
||||
}
|
||||
ir.SetGotoVariable(source.terminator.goto_variable,
|
||||
IR::U1(IR::Value(source.terminator.goto_value != 0)));
|
||||
@@ -761,13 +823,13 @@ bool Translator::AddBranchCondition(const CFG::BasicBlock& source, IR::BlockInfo
|
||||
info.indirect_target =
|
||||
ir.GetScalarReg(static_cast<IR::ScalarReg>(source.terminator.indirect_pc_sgpr));
|
||||
} else {
|
||||
return Fail(error, fmt::format("block {} has no indirect branch selector", source.id));
|
||||
EXIT("block %u has no indirect branch selector", source.id);
|
||||
}
|
||||
ir.Emit(IR::ValueOpcode::ReferenceU32, {info.indirect_target});
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
if (source.terminator.kind != CFG::TerminatorKind::ConditionalBranch) {
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
// EXEC and VCC are invocation-local Boolean masks. Branching on that Boolean lets inactive
|
||||
// invocations leave the region without reconstructing a host-subgroup mask.
|
||||
@@ -782,17 +844,15 @@ bool Translator::AddBranchCondition(const CFG::BasicBlock& source, IR::BlockInfo
|
||||
case CFG::BranchCondition::ExecNonZero: condition = ir.GetExec(); break;
|
||||
case CFG::BranchCondition::GotoVariable:
|
||||
if (source.terminator.goto_variable == UINT32_MAX) {
|
||||
return Fail(error,
|
||||
fmt::format("block {} reads an invalid goto variable", source.id));
|
||||
EXIT("block %u reads an invalid goto variable", source.id);
|
||||
}
|
||||
condition = ir.GetGotoVariable(source.terminator.goto_variable);
|
||||
break;
|
||||
case CFG::BranchCondition::Unknown:
|
||||
return Fail(error, fmt::format("block {} has an unknown branch condition", source.id));
|
||||
EXIT("block %u has an unknown branch condition", source.id);
|
||||
}
|
||||
info.condition = condition;
|
||||
ir.Emit(IR::ValueOpcode::Reference, {condition});
|
||||
return true;
|
||||
}
|
||||
|
||||
namespace {
|
||||
@@ -914,47 +974,42 @@ void IncludeInstructionVectorRegisters(const Decoder::Instruction& inst, uint32_
|
||||
}
|
||||
}
|
||||
|
||||
bool ValidateTranslateOptions(const TranslateOptions& options, std::string* error) {
|
||||
void ValidateTranslateOptions(const TranslateOptions& options) {
|
||||
if (options.wave_size != 32u && options.wave_size != 64u) {
|
||||
return Fail(error, "shader translation requires wave32 or wave64");
|
||||
EXIT("shader translation requires wave32 or wave64, got %u", options.wave_size);
|
||||
}
|
||||
switch (options.stage) {
|
||||
case ShaderType::Vertex:
|
||||
return options.vertex != nullptr ||
|
||||
Fail(error, "vertex shader translation has no vertex input metadata");
|
||||
if (options.vertex == nullptr) {
|
||||
EXIT("vertex shader translation has no vertex input metadata");
|
||||
}
|
||||
return;
|
||||
case ShaderType::Pixel:
|
||||
return options.pixel != nullptr ||
|
||||
Fail(error, "pixel shader translation has no pixel input metadata");
|
||||
if (options.pixel == nullptr) {
|
||||
EXIT("pixel shader translation has no pixel input metadata");
|
||||
}
|
||||
return;
|
||||
case ShaderType::Compute:
|
||||
return options.compute != nullptr ||
|
||||
Fail(error, "compute shader translation has no compute input metadata");
|
||||
default: return Fail(error, "shader translation has an unsupported stage");
|
||||
if (options.compute == nullptr) {
|
||||
EXIT("compute shader translation has no compute input metadata");
|
||||
}
|
||||
return;
|
||||
default:
|
||||
EXIT("shader translation has unsupported stage %u",
|
||||
static_cast<uint32_t>(options.stage));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool TranslateProgram(const Decoder::Program& decoded, const CFG::Graph& cfg,
|
||||
const TranslateOptions& options, IR::Program& result, std::string* error) {
|
||||
result = {};
|
||||
struct ResetOnFailure {
|
||||
IR::Program& program;
|
||||
bool complete = false;
|
||||
|
||||
~ResetOnFailure() {
|
||||
if (!complete) {
|
||||
program = {};
|
||||
}
|
||||
}
|
||||
} reset_on_failure {result};
|
||||
|
||||
if (!ValidateTranslateOptions(options, error)) {
|
||||
return false;
|
||||
}
|
||||
IR::Program TranslateProgram(const Decoder::Program& decoded, const CFG::Graph& cfg,
|
||||
const TranslateOptions& options) {
|
||||
ValidateTranslateOptions(options);
|
||||
if (cfg.blocks.empty()) {
|
||||
return Fail(error, "cannot translate an empty CFG");
|
||||
EXIT("cannot translate an empty CFG");
|
||||
}
|
||||
|
||||
IR::Program result;
|
||||
result.stage = options.stage;
|
||||
result.wave_size = options.wave_size;
|
||||
result.shader_hash = options.shader_hash;
|
||||
@@ -972,8 +1027,8 @@ bool TranslateProgram(const Decoder::Program& decoded, const CFG::Graph& cfg,
|
||||
for (const auto& cfg_block: cfg.blocks) {
|
||||
for (uint32_t index = cfg_block.inst_begin; index < cfg_block.inst_end; index++) {
|
||||
if (index >= decoded.instructions.size()) {
|
||||
return Fail(error,
|
||||
"CFG block references an instruction outside the decoded program");
|
||||
EXIT("CFG block %u references instruction %u outside decoded program of size %zu",
|
||||
cfg_block.id, index, decoded.instructions.size());
|
||||
}
|
||||
const auto& instruction = decoded.instructions[index];
|
||||
if (IsCodeTableLoad(cfg, instruction.pc)) {
|
||||
@@ -990,7 +1045,7 @@ bool TranslateProgram(const Decoder::Program& decoded, const CFG::Graph& cfg,
|
||||
return block.id;
|
||||
})->id;
|
||||
if (max_id == UINT32_MAX) {
|
||||
return Fail(error, "cannot allocate a typed entry block id");
|
||||
EXIT("cannot allocate a typed entry block id");
|
||||
}
|
||||
CFG::Terminator terminator;
|
||||
terminator.kind = CFG::TerminatorKind::Branch;
|
||||
@@ -1004,7 +1059,7 @@ bool TranslateProgram(const Decoder::Program& decoded, const CFG::Graph& cfg,
|
||||
block_indices.reserve(cfg.blocks.size());
|
||||
for (const auto& source_block: cfg.blocks) {
|
||||
if (!block_indices.emplace(source_block.id, result.blocks.size()).second) {
|
||||
return Fail(error, fmt::format("CFG contains duplicate block id {}", source_block.id));
|
||||
EXIT("CFG contains duplicate block id %u", source_block.id);
|
||||
}
|
||||
result.block_storage.push_back(std::make_unique<IR::Block>());
|
||||
result.blocks.push_back(result.block_storage.back().get());
|
||||
@@ -1016,8 +1071,7 @@ bool TranslateProgram(const Decoder::Program& decoded, const CFG::Graph& cfg,
|
||||
for (const auto successor: source_block.successors) {
|
||||
const auto target = block_indices.find(successor);
|
||||
if (target == block_indices.end()) {
|
||||
return Fail(error, fmt::format("CFG block {} has unknown successor {}",
|
||||
source_block.id, successor));
|
||||
EXIT("CFG block %u has unknown successor %u", source_block.id, successor);
|
||||
}
|
||||
result.blocks[source_index]->AddBranch(result.blocks[target->second]);
|
||||
}
|
||||
@@ -1041,6 +1095,7 @@ bool TranslateProgram(const Decoder::Program& decoded, const CFG::Graph& cfg,
|
||||
entry_ir.SetScalarMaskTag(reg, IR::U1(IR::Value(false)));
|
||||
}
|
||||
entry_ir.SetExec(IR::U1(IR::Value(true)));
|
||||
entry_ir.SetExecMaskTag(IR::U1(IR::Value(true)));
|
||||
entry_ir.SetExecLo(IR::U32(IR::Value(1u)));
|
||||
entry_ir.SetExecHi(IR::U32(IR::Value(0u)));
|
||||
if (options.stage == ShaderType::Compute) {
|
||||
@@ -1115,7 +1170,7 @@ bool TranslateProgram(const Decoder::Program& decoded, const CFG::Graph& cfg,
|
||||
}
|
||||
}
|
||||
for (const auto& cfg_block: cfg.blocks) {
|
||||
const auto typed_index = block_indices.at(cfg_block.id);
|
||||
const auto typed_index = block_indices.at(cfg_block.id);
|
||||
Translator translator(result, result.blocks[typed_index], vector_limit, options.wave_size);
|
||||
for (uint32_t index = cfg_block.inst_begin; index < cfg_block.inst_end; index++) {
|
||||
const auto& instruction = decoded.instructions[index];
|
||||
@@ -1131,44 +1186,27 @@ bool TranslateProgram(const Decoder::Program& decoded, const CFG::Graph& cfg,
|
||||
instruction.data_dwords == embedded->components &&
|
||||
instruction.dst.kind == Decoder::OperandKind::Vgpr) {
|
||||
if (options.vertex == nullptr) {
|
||||
return Fail(error, "embedded vertex fetch plan has no vertex input metadata");
|
||||
EXIT("embedded vertex fetch plan has no vertex input metadata");
|
||||
}
|
||||
const auto resource = ResolveEmbeddedFetchResource(*options.vertex, *embedded);
|
||||
if (resource < 0 || resource >= options.vertex->resources_num) {
|
||||
return Fail(
|
||||
error,
|
||||
fmt::format(
|
||||
"embedded vertex fetch at 0x{:08x} has no resource for attribute {}",
|
||||
instruction.pc, embedded->attrib_id));
|
||||
EXIT("embedded vertex fetch at 0x%08x has no resource for attribute %d",
|
||||
instruction.pc, embedded->attrib_id);
|
||||
}
|
||||
result.info.vertex_fetch_components[static_cast<size_t>(resource)] =
|
||||
static_cast<uint8_t>(std::max<uint32_t>(
|
||||
result.info.vertex_fetch_components[static_cast<size_t>(resource)],
|
||||
embedded->components));
|
||||
if (!translator.TranslateEmbeddedFetch(instruction, static_cast<uint32_t>(resource),
|
||||
embedded->components)) {
|
||||
return false;
|
||||
}
|
||||
translator.TranslateEmbeddedFetch(instruction, static_cast<uint32_t>(resource),
|
||||
embedded->components);
|
||||
continue;
|
||||
}
|
||||
if (!translator.TranslateInstruction(instruction, error)) {
|
||||
if (error == nullptr || error->empty()) {
|
||||
return Fail(error, fmt::format("opcode {} at 0x{:08x} has no IR translation",
|
||||
magic_enum::enum_name(instruction.opcode),
|
||||
instruction.pc));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!translator.AddBranchCondition(cfg_block, result.block_info[typed_index], error)) {
|
||||
return false;
|
||||
translator.TranslateInstruction(instruction);
|
||||
}
|
||||
translator.AddBranchCondition(cfg_block, result.block_info[typed_index]);
|
||||
}
|
||||
if (!IR::ValidateProgram(result, false, error)) {
|
||||
return false;
|
||||
}
|
||||
reset_on_failure.complete = true;
|
||||
return true;
|
||||
IR::ValidateProgram(result, false);
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::Frontend
|
||||
@@ -38,7 +38,7 @@ struct TranslateOptions {
|
||||
const EmbeddedFetchPlan* embedded_fetch = nullptr;
|
||||
};
|
||||
|
||||
bool TranslateProgram(const Decoder::Program& decoded, const CFG::Graph& cfg,
|
||||
const TranslateOptions& options, IR::Program& result, std::string* error);
|
||||
IR::Program TranslateProgram(const Decoder::Program& decoded, const CFG::Graph& cfg,
|
||||
const TranslateOptions& options);
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::Frontend
|
||||
@@ -13,12 +13,20 @@ public:
|
||||
: program(program), ir(block), current_vector_limit(vector_limit),
|
||||
current_wave_size(wave_size) {}
|
||||
|
||||
bool TranslateInstruction(const Decoder::Instruction& inst, std::string* error);
|
||||
bool TranslateEmbeddedFetch(const Decoder::Instruction& inst, uint32_t attribute,
|
||||
void TranslateInstruction(const Decoder::Instruction& inst);
|
||||
void TranslateEmbeddedFetch(const Decoder::Instruction& inst, uint32_t attribute,
|
||||
uint32_t component_count);
|
||||
bool AddBranchCondition(const CFG::BasicBlock& source, IR::BlockInfo& info, std::string* error);
|
||||
void AddBranchCondition(const CFG::BasicBlock& source, IR::BlockInfo& info);
|
||||
|
||||
private:
|
||||
struct ScalarU64 {
|
||||
std::array<IR::U32, 2> raw;
|
||||
// Raw words and Kyty's per-invocation mask model remain independent.
|
||||
// mask_valid selects invocation for logical consumers and SCC.
|
||||
IR::U1 invocation;
|
||||
IR::U1 mask_valid;
|
||||
};
|
||||
|
||||
Decoder::Operand SourceAt(const Decoder::Instruction& inst, uint32_t index);
|
||||
Decoder::Operand DestinationOperand(const Decoder::Instruction& inst);
|
||||
Decoder::Operand OffsetOperand(const Decoder::Operand& operand, uint32_t offset);
|
||||
@@ -34,6 +42,7 @@ private:
|
||||
IR::F32 ApplyF32ResultModifiers(const Decoder::Operand& operand, IR::F32 value);
|
||||
void WriteOperand(const Decoder::Operand& operand, IR::Value value);
|
||||
IR::U32 PackHalf2x16(IR::F32 low, IR::F32 high);
|
||||
void Write16Bits(const Decoder::Operand& operand, IR::U32 value);
|
||||
void WriteF16(const Decoder::Operand& operand, IR::F32 value);
|
||||
void WriteU16(const Decoder::Operand& operand, IR::U32 value);
|
||||
IR::U32 ReadU32(const Decoder::Operand& operand);
|
||||
@@ -47,14 +56,25 @@ private:
|
||||
IR::U32 ReadU16AsU32(const Decoder::Operand& operand, bool sign_extend);
|
||||
IR::U32 ReadF16LaneBits(const Decoder::Operand& operand, bool high_lane);
|
||||
std::array<IR::U32, 2> ExtractU64(IR::U64 value);
|
||||
void WriteU32Pair(const Decoder::Operand& operand, const std::array<IR::U32, 2>& value);
|
||||
IR::U1 ReadCondition(const Decoder::Operand& operand);
|
||||
IR::U32 ConditionBit(const Decoder::Operand& operand);
|
||||
IR::U1 ReadMask(const Decoder::Operand& operand);
|
||||
IR::U1 ReadMaskValid(const Decoder::Operand& operand);
|
||||
void WriteMask(const Decoder::Operand& operand, IR::U1 value);
|
||||
void WriteMask64(const Decoder::Operand& operand, IR::U1 value);
|
||||
void WriteCompareResult(const Decoder::Operand& operand, IR::U1 value);
|
||||
void WriteU32Pair(const Decoder::Operand& operand, const std::array<IR::U32, 2>& value);
|
||||
ScalarU64 ReadScalarU64(const Decoder::Operand& operand);
|
||||
ScalarU64 MakeScalarU64Mask(IR::U1 value);
|
||||
ScalarU64 MakeScalarU64Result(const std::array<IR::U32, 2>& raw, IR::U1 invocation,
|
||||
IR::U1 mask_valid);
|
||||
ScalarU64 SelectScalarU64(IR::U1 condition, const ScalarU64& lhs, const ScalarU64& rhs);
|
||||
ScalarU64 NotScalarU64(const ScalarU64& value);
|
||||
ScalarU64 BinaryScalarU64(const ScalarU64& lhs, const ScalarU64& rhs,
|
||||
IR::ValueOpcode logical_opcode, IR::ValueOpcode bit_opcode,
|
||||
bool negate_lhs, bool negate_rhs, bool negate_result);
|
||||
void WriteScalarU64(const Decoder::Operand& operand, const ScalarU64& value);
|
||||
IR::U1 ScalarU64NonZero(const ScalarU64& value);
|
||||
IR::U1 ReadCondition(const Decoder::Operand& operand);
|
||||
IR::U32 ConditionBit(const Decoder::Operand& operand);
|
||||
IR::U1 ReadMask(const Decoder::Operand& operand);
|
||||
IR::U1 ReadMaskValid(const Decoder::Operand& operand);
|
||||
void WriteMask(const Decoder::Operand& operand, IR::U1 value);
|
||||
void WriteMask64(const Decoder::Operand& operand, IR::U1 value);
|
||||
void WriteCompareResult(const Decoder::Operand& operand, IR::U1 value);
|
||||
|
||||
IR::MemoryFlags AddMemoryInfo(const IR::MemoryInfo& memory, uint32_t pc);
|
||||
IR::ExportFlags AddExportInfo(const Decoder::Instruction& inst);
|
||||
@@ -72,7 +92,7 @@ private:
|
||||
IR::U32 GetResourceDword(uint32_t index, uint32_t dword);
|
||||
IR::Value GetBufferResource(const IR::MemoryInfo& memory);
|
||||
IR::Value GetAddressResource(IR::Value low, IR::Value high);
|
||||
IR::Value GetScalarAddressResource(uint32_t base);
|
||||
IR::Value GetScalarAddressResource(const Decoder::Operand& base);
|
||||
IR::Value GetImageResource(const IR::MemoryInfo& memory);
|
||||
IR::Value GetSamplerResource(const IR::MemoryInfo& memory);
|
||||
IR::Value MakeImageAddress(const Decoder::Instruction& inst, const Decoder::Operand& base);
|
||||
@@ -109,6 +129,7 @@ private:
|
||||
bool DS_APPEND_CONSUME(const Decoder::Instruction& inst, IR::ValueOpcode opcode);
|
||||
bool DS_ADDTID(const Decoder::Instruction& inst, bool write);
|
||||
bool DS_SWIZZLE_B32(const Decoder::Instruction& inst);
|
||||
bool DS_BPERMUTE_B32(const Decoder::Instruction& inst);
|
||||
|
||||
IR::F32 SelectF32(IR::U1 condition, IR::F32 true_value, IR::F32 false_value);
|
||||
IR::U32 ConvertF32ToU32Saturated(IR::F32 value, float upper_bound, float safe_upper,
|
||||
@@ -126,7 +147,7 @@ private:
|
||||
void EmitFloatCompare(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool half,
|
||||
bool cmpx);
|
||||
void EmitFloatOrderedCompare(const Decoder::Instruction& inst, bool ordered);
|
||||
void V_CMP_CLASS_F32(const Decoder::Instruction& inst);
|
||||
void EmitFloatClassCompare(const Decoder::Instruction& inst, bool cmpx);
|
||||
void V_CVT_F32_UBYTE(const Decoder::Instruction& inst, uint32_t byte_index);
|
||||
void V_CVT_F32_U32(const Decoder::Instruction& inst);
|
||||
void V_CVT_F32_I32(const Decoder::Instruction& inst);
|
||||
@@ -148,6 +169,7 @@ private:
|
||||
bool quiet_snan);
|
||||
bool Float16Unary(const Decoder::Instruction& inst, IR::ValueOpcode opcode,
|
||||
bool invalid_negative);
|
||||
bool Float16Trig(const Decoder::Instruction& inst, IR::ValueOpcode opcode);
|
||||
bool Float16Binary(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool reverse);
|
||||
bool Float16Ternary(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool accumulator,
|
||||
bool mix);
|
||||
@@ -172,28 +194,26 @@ private:
|
||||
bool PackedInteger16MinMax(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool sign);
|
||||
bool S_U64_MASK(const Decoder::Instruction& inst, IR::ValueOpcode logical_opcode,
|
||||
IR::ValueOpcode bit_opcode, bool negate_rhs, bool negate_result, bool unary);
|
||||
IR::U1 U64MaskBinary(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool negate_rhs,
|
||||
bool negate_result);
|
||||
bool SimpleInteger(const Decoder::Instruction& inst, IR::ValueOpcode opcode, IR::Type type,
|
||||
bool reverse, bool mask_shift_count, bool update_scc);
|
||||
bool ComposedIntegerBinary(const Decoder::Instruction& inst, IR::ValueOpcode opcode,
|
||||
bool negate_rhs, bool negate_result, bool update_scc);
|
||||
bool V_AND_OR_B32(const Decoder::Instruction& inst);
|
||||
bool V_OR3_B32(const Decoder::Instruction& inst);
|
||||
bool V_XOR3_B32(const Decoder::Instruction& inst);
|
||||
bool S_FF1_I32_B64(const Decoder::Instruction& inst);
|
||||
bool V_FFBH_U32(const Decoder::Instruction& inst);
|
||||
bool S_FLBIT_I32_B64(const Decoder::Instruction& inst);
|
||||
bool Integer24(const Decoder::Instruction& inst, bool sign, bool addend);
|
||||
bool V_MAD_U64_U32(const Decoder::Instruction& inst);
|
||||
bool V_SAD_U32(const Decoder::Instruction& inst);
|
||||
bool V_ADD3_U32(const Decoder::Instruction& inst);
|
||||
bool S_BITSET_B32(const Decoder::Instruction& inst, bool set);
|
||||
bool V_BCNT_U32_B32(const Decoder::Instruction& inst);
|
||||
bool V_MBCNT_U32_B32(const Decoder::Instruction& inst, bool low);
|
||||
bool S_BITREPLICATE_B64_B32(const Decoder::Instruction& inst);
|
||||
bool S_QUADMASK_B64(const Decoder::Instruction& inst);
|
||||
bool BFM_B32(const Decoder::Instruction& inst);
|
||||
bool SimpleInteger(const Decoder::Instruction& inst, IR::ValueOpcode opcode, IR::Type type,
|
||||
bool reverse, bool mask_shift_count, bool update_scc);
|
||||
bool ComposedIntegerBinary(const Decoder::Instruction& inst, IR::ValueOpcode opcode,
|
||||
bool negate_rhs, bool negate_result, bool update_scc);
|
||||
bool V_AND_OR_B32(const Decoder::Instruction& inst);
|
||||
bool V_OR3_B32(const Decoder::Instruction& inst);
|
||||
bool V_XOR3_B32(const Decoder::Instruction& inst);
|
||||
bool S_FF1_I32_B64(const Decoder::Instruction& inst);
|
||||
bool V_FFBH_32(const Decoder::Instruction& inst, bool sign);
|
||||
bool S_FLBIT_I32_B64(const Decoder::Instruction& inst);
|
||||
bool Integer24(const Decoder::Instruction& inst, bool sign, bool addend);
|
||||
bool V_MAD_U64_U32(const Decoder::Instruction& inst);
|
||||
bool V_SAD_U32(const Decoder::Instruction& inst);
|
||||
bool V_ADD3_U32(const Decoder::Instruction& inst);
|
||||
bool S_BITSET_B32(const Decoder::Instruction& inst, bool set);
|
||||
bool V_BCNT_U32_B32(const Decoder::Instruction& inst);
|
||||
bool V_MBCNT_U32_B32(const Decoder::Instruction& inst, bool low);
|
||||
bool S_BITREPLICATE_B64_B32(const Decoder::Instruction& inst);
|
||||
bool S_QUADMASK_B64(const Decoder::Instruction& inst);
|
||||
bool BFM_B32(const Decoder::Instruction& inst);
|
||||
IR::U32 RightMask32(IR::U32 count);
|
||||
IR::U64 RightMask64(IR::U32 count);
|
||||
bool S_BFM_B64(const Decoder::Instruction& inst);
|
||||
@@ -226,27 +246,27 @@ private:
|
||||
void S_SENDMSG();
|
||||
void S_TTRACEDATA();
|
||||
void S_INST_PREFETCH();
|
||||
bool S_GETPC_B64(const Decoder::Instruction& inst, std::string* error);
|
||||
void S_GETPC_B64(const Decoder::Instruction& inst);
|
||||
void S_CSELECT_B32(const Decoder::Instruction& inst);
|
||||
void S_CSELECT_B64(const Decoder::Instruction& inst);
|
||||
void MOV_B32(const Decoder::Instruction& inst, bool apply_float_modifiers);
|
||||
void S_MOV_B64(const Decoder::Instruction& inst);
|
||||
void S_WQM_B64(const Decoder::Instruction& inst);
|
||||
bool V_MOVRELS_B32(const Decoder::Instruction& inst, std::string* error);
|
||||
bool V_MOVRELD_B32(const Decoder::Instruction& inst, std::string* error);
|
||||
void V_MOVRELS_B32(const Decoder::Instruction& inst);
|
||||
void V_MOVRELD_B32(const Decoder::Instruction& inst);
|
||||
void V_READFIRSTLANE_B32(const Decoder::Instruction& inst);
|
||||
void V_READLANE_B32(const Decoder::Instruction& inst);
|
||||
void V_WRITELANE_B32(const Decoder::Instruction& inst);
|
||||
void V_PERMLANE16_B32(const Decoder::Instruction& inst, bool x16);
|
||||
void V_INTERP_P1_F32();
|
||||
void V_INTERP_P2_F32(const Decoder::Instruction& inst);
|
||||
bool V_INTERP_MOV_F32(const Decoder::Instruction& inst, std::string* error);
|
||||
bool EXP(const Decoder::Instruction& inst, std::string* error);
|
||||
void V_INTERP_MOV_F32(const Decoder::Instruction& inst);
|
||||
void EXP(const Decoder::Instruction& inst);
|
||||
|
||||
bool EmitScalar(const Decoder::Instruction& inst, std::string* error);
|
||||
bool EmitVector(const Decoder::Instruction& inst, std::string* error);
|
||||
bool EmitInterpolation(const Decoder::Instruction& inst, std::string* error);
|
||||
bool EmitMemory(const Decoder::Instruction& inst, std::string* error);
|
||||
bool EmitScalar(const Decoder::Instruction& inst);
|
||||
bool EmitVector(const Decoder::Instruction& inst);
|
||||
bool EmitInterpolation(const Decoder::Instruction& inst);
|
||||
bool EmitMemory(const Decoder::Instruction& inst);
|
||||
|
||||
IR::Program& program;
|
||||
IR::IREmitter ir;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::Frontend {
|
||||
|
||||
bool Translator::EmitVector(const Decoder::Instruction& inst, std::string* error) {
|
||||
bool Translator::EmitVector(const Decoder::Instruction& inst) {
|
||||
using O = Decoder::Opcode;
|
||||
switch (inst.opcode) {
|
||||
case O::V_NOP: return true;
|
||||
@@ -13,8 +13,8 @@ bool Translator::EmitVector(const Decoder::Instruction& inst, std::string* error
|
||||
case O::V_SUBREV_CO_CI_U32: SUBB_U32(inst, true); return true;
|
||||
|
||||
case O::V_MOV_B32: MOV_B32(inst, true); return true;
|
||||
case O::V_MOVRELS_B32: return V_MOVRELS_B32(inst, error);
|
||||
case O::V_MOVRELD_B32: return V_MOVRELD_B32(inst, error);
|
||||
case O::V_MOVRELS_B32: V_MOVRELS_B32(inst); return true;
|
||||
case O::V_MOVRELD_B32: V_MOVRELD_B32(inst); return true;
|
||||
case O::V_READFIRSTLANE_B32: V_READFIRSTLANE_B32(inst); return true;
|
||||
case O::V_READLANE_B32: V_READLANE_B32(inst); return true;
|
||||
case O::V_WRITELANE_B32: V_WRITELANE_B32(inst); return true;
|
||||
@@ -96,6 +96,7 @@ bool Translator::EmitVector(const Decoder::Instruction& inst, std::string* error
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::SLessThanEqual32, IR::Type::U32, false, true);
|
||||
return true;
|
||||
case O::V_CMP_EQ_I64:
|
||||
case O::V_CMP_EQ_U64:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::IEqual64, IR::Type::U64, false, false);
|
||||
return true;
|
||||
case O::V_CMP_GT_U64:
|
||||
@@ -124,6 +125,9 @@ bool Translator::EmitVector(const Decoder::Instruction& inst, std::string* error
|
||||
case O::V_CMP_GT_U16:
|
||||
EmitInteger16Compare(inst, IR::ValueOpcode::UGreaterThan32, false, false);
|
||||
return true;
|
||||
case O::V_CMPX_GT_U16:
|
||||
EmitInteger16Compare(inst, IR::ValueOpcode::UGreaterThan32, false, true);
|
||||
return true;
|
||||
case O::V_CMP_GE_U16:
|
||||
EmitInteger16Compare(inst, IR::ValueOpcode::UGreaterThanEqual32, false, false);
|
||||
return true;
|
||||
@@ -253,6 +257,9 @@ bool Translator::EmitVector(const Decoder::Instruction& inst, std::string* error
|
||||
case O::V_CMPX_LE_F16:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdLessThanEqual32, true, true);
|
||||
return true;
|
||||
case O::V_CMPX_NGT_F16:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordLessThanEqual32, true, true);
|
||||
return true;
|
||||
case O::V_CMP_NEQ_F16:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordNotEqual32, true, false);
|
||||
return true;
|
||||
@@ -264,7 +271,8 @@ bool Translator::EmitVector(const Decoder::Instruction& inst, std::string* error
|
||||
return true;
|
||||
case O::V_CMP_O_F32: EmitFloatOrderedCompare(inst, true); return true;
|
||||
case O::V_CMP_U_F32: EmitFloatOrderedCompare(inst, false); return true;
|
||||
case O::V_CMP_CLASS_F32: V_CMP_CLASS_F32(inst); return true;
|
||||
case O::V_CMP_CLASS_F32: EmitFloatClassCompare(inst, false); return true;
|
||||
case O::V_CMPX_CLASS_F32: EmitFloatClassCompare(inst, true); return true;
|
||||
|
||||
case O::V_CVT_F32_UBYTE0: V_CVT_F32_UBYTE(inst, 0); return true;
|
||||
case O::V_CVT_F32_UBYTE1: V_CVT_F32_UBYTE(inst, 1); return true;
|
||||
@@ -358,6 +366,8 @@ bool Translator::EmitVector(const Decoder::Instruction& inst, std::string* error
|
||||
case O::V_CEIL_F16: return Float16Unary(inst, IR::ValueOpcode::FPCeil32, false);
|
||||
case O::V_TRUNC_F16: return Float16Unary(inst, IR::ValueOpcode::FPTrunc32, false);
|
||||
case O::V_RNDNE_F16: return Float16Unary(inst, IR::ValueOpcode::FPRoundEven32, false);
|
||||
case O::V_SIN_F16: return Float16Trig(inst, IR::ValueOpcode::FPSin);
|
||||
case O::V_COS_F16: return Float16Trig(inst, IR::ValueOpcode::FPCos);
|
||||
case O::V_MIN3_F16: return Float16Ternary(inst, IR::ValueOpcode::FPMinTri32, false, false);
|
||||
case O::V_MAX3_F16: return Float16Ternary(inst, IR::ValueOpcode::FPMaxTri32, false, false);
|
||||
case O::V_MED3_F16: return Float16Ternary(inst, IR::ValueOpcode::FPMedTri32, false, false);
|
||||
@@ -484,7 +494,8 @@ bool Translator::EmitVector(const Decoder::Instruction& inst, std::string* error
|
||||
case O::V_AND_OR_B32: return V_AND_OR_B32(inst);
|
||||
case O::V_OR3_B32: return V_OR3_B32(inst);
|
||||
case O::V_XOR3_B32: return V_XOR3_B32(inst);
|
||||
case O::V_FFBH_U32: return V_FFBH_U32(inst);
|
||||
case O::V_FFBH_U32: return V_FFBH_32(inst, false);
|
||||
case O::V_FFBH_I32: return V_FFBH_32(inst, true);
|
||||
|
||||
case O::V_MAD_I32_I24: return Integer24(inst, true, true);
|
||||
case O::V_MAD_U32_U24: return Integer24(inst, false, true);
|
||||
|
||||
@@ -76,6 +76,14 @@ void IREmitter::SetExec(U1 value) {
|
||||
Emit(ValueOpcode::SetExec, {value});
|
||||
}
|
||||
|
||||
U1 IREmitter::GetExecMaskTag() {
|
||||
return U1(Emit(ValueOpcode::GetExecMaskTag));
|
||||
}
|
||||
|
||||
void IREmitter::SetExecMaskTag(U1 value) {
|
||||
Emit(ValueOpcode::SetExecMaskTag, {value});
|
||||
}
|
||||
|
||||
U32 IREmitter::GetExecLo() {
|
||||
return U32(Emit(ValueOpcode::GetExecLo));
|
||||
}
|
||||
@@ -100,6 +108,14 @@ void IREmitter::SetVcc(U1 value) {
|
||||
Emit(ValueOpcode::SetVcc, {value});
|
||||
}
|
||||
|
||||
U1 IREmitter::GetVccMaskValidTag() {
|
||||
return U1(Emit(ValueOpcode::GetVccMaskValidTag));
|
||||
}
|
||||
|
||||
void IREmitter::SetVccMaskValidTag(U1 value) {
|
||||
Emit(ValueOpcode::SetVccMaskValidTag, {value});
|
||||
}
|
||||
|
||||
U32 IREmitter::GetVccLo() {
|
||||
return U32(Emit(ValueOpcode::GetVccLo));
|
||||
}
|
||||
|
||||
@@ -39,12 +39,16 @@ public:
|
||||
void SetScc(U1 value);
|
||||
U1 GetExec();
|
||||
void SetExec(U1 value);
|
||||
U1 GetExecMaskTag();
|
||||
void SetExecMaskTag(U1 value);
|
||||
U32 GetExecLo();
|
||||
void SetExecLo(U32 value);
|
||||
U32 GetExecHi();
|
||||
void SetExecHi(U32 value);
|
||||
U1 GetVcc();
|
||||
void SetVcc(U1 value);
|
||||
U1 GetVccMaskValidTag();
|
||||
void SetVccMaskValidTag(U1 value);
|
||||
U32 GetVccLo();
|
||||
void SetVccLo(U32 value);
|
||||
U32 GetVccHi();
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#include "common/assert.h"
|
||||
#include "graphics/shader/recompiler/ir/ShaderIR.h"
|
||||
|
||||
#include "graphics/shader/shader.h"
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <map>
|
||||
#include <new>
|
||||
@@ -11,10 +10,13 @@
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::IR {
|
||||
|
||||
using ShaderError::Fail;
|
||||
|
||||
namespace {
|
||||
|
||||
[[noreturn]] void Fail(std::string_view message) {
|
||||
EXIT("shader IR validation failed: %s", std::string(message).c_str());
|
||||
std::abort();
|
||||
}
|
||||
|
||||
bool IsRegisterStatePseudo(ValueOpcode opcode) {
|
||||
switch (opcode) {
|
||||
case ValueOpcode::GetThreadBitScalarRegister:
|
||||
@@ -31,12 +33,16 @@ bool IsRegisterStatePseudo(ValueOpcode opcode) {
|
||||
case ValueOpcode::SetScc:
|
||||
case ValueOpcode::GetExec:
|
||||
case ValueOpcode::SetExec:
|
||||
case ValueOpcode::GetExecMaskTag:
|
||||
case ValueOpcode::SetExecMaskTag:
|
||||
case ValueOpcode::GetExecLo:
|
||||
case ValueOpcode::SetExecLo:
|
||||
case ValueOpcode::GetExecHi:
|
||||
case ValueOpcode::SetExecHi:
|
||||
case ValueOpcode::GetVcc:
|
||||
case ValueOpcode::SetVcc:
|
||||
case ValueOpcode::GetVccMaskValidTag:
|
||||
case ValueOpcode::SetVccMaskValidTag:
|
||||
case ValueOpcode::GetVccLo:
|
||||
case ValueOpcode::SetVccLo:
|
||||
case ValueOpcode::GetVccHi:
|
||||
@@ -149,10 +155,10 @@ Value ResolveInvariantPhi(const Program& program, Value value) {
|
||||
return invariant;
|
||||
}
|
||||
|
||||
bool ValidateProgram(const Program& program, bool require_ssa, std::string* error) {
|
||||
void ValidateProgram(const Program& program, bool require_ssa) {
|
||||
if (program.blocks.size() != program.block_info.size() ||
|
||||
program.blocks.size() != program.block_storage.size()) {
|
||||
return Fail(error, "value IR block storage is inconsistent");
|
||||
return Fail("value IR block storage is inconsistent");
|
||||
}
|
||||
std::unordered_set<const Block*> blocks;
|
||||
std::unordered_map<const Block*, size_t> block_indices;
|
||||
@@ -162,26 +168,26 @@ bool ValidateProgram(const Program& program, bool require_ssa, std::string* erro
|
||||
const auto* block = program.blocks[block_index];
|
||||
if (block == nullptr || program.block_storage[block_index] == nullptr ||
|
||||
block != program.block_storage[block_index].get()) {
|
||||
return Fail(error, "value IR block pointer is inconsistent");
|
||||
return Fail("value IR block pointer is inconsistent");
|
||||
}
|
||||
if (!blocks.insert(block).second) {
|
||||
return Fail(error, "value IR block pointer is duplicated");
|
||||
return Fail("value IR block pointer is duplicated");
|
||||
}
|
||||
block_indices.emplace(block, block_index);
|
||||
if (program.block_info[block_index].id == UINT32_MAX) {
|
||||
return Fail(error, "value IR block uses the reserved exit id");
|
||||
return Fail("value IR block uses the reserved exit id");
|
||||
}
|
||||
if (!blocks_by_id.emplace(program.block_info[block_index].id, block).second) {
|
||||
return Fail(error, "value IR block id is duplicated");
|
||||
return Fail("value IR block id is duplicated");
|
||||
}
|
||||
for (const auto& inst: *block) {
|
||||
if (!instructions.insert(&inst).second) {
|
||||
return Fail(error, "value IR instruction is duplicated");
|
||||
return Fail("value IR instruction is duplicated");
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!program.blocks.empty() && !program.blocks.front()->ImmPredecessors().empty()) {
|
||||
return Fail(error, "value IR entry block has a predecessor");
|
||||
return Fail("value IR entry block has a predecessor");
|
||||
}
|
||||
|
||||
for (size_t block_index = 0; block_index < program.blocks.size(); block_index++) {
|
||||
@@ -189,28 +195,28 @@ bool ValidateProgram(const Program& program, bool require_ssa, std::string* erro
|
||||
std::unordered_set<const Block*> predecessors;
|
||||
for (const auto* predecessor: block->ImmPredecessors()) {
|
||||
if (predecessor == nullptr || !blocks.contains(predecessor)) {
|
||||
return Fail(error, "value IR block has a foreign predecessor");
|
||||
return Fail("value IR block has a foreign predecessor");
|
||||
}
|
||||
if (!predecessors.insert(predecessor).second) {
|
||||
return Fail(error, "value IR block predecessor is duplicated");
|
||||
return Fail("value IR block predecessor is duplicated");
|
||||
}
|
||||
if (std::ranges::find(predecessor->ImmSuccessors(), block) ==
|
||||
predecessor->ImmSuccessors().end()) {
|
||||
return Fail(error, "value IR predecessor edge is not reciprocal");
|
||||
return Fail("value IR predecessor edge is not reciprocal");
|
||||
}
|
||||
}
|
||||
|
||||
std::unordered_set<const Block*> successors;
|
||||
for (const auto* successor: block->ImmSuccessors()) {
|
||||
if (successor == nullptr || !blocks.contains(successor)) {
|
||||
return Fail(error, "value IR block has a foreign successor");
|
||||
return Fail("value IR block has a foreign successor");
|
||||
}
|
||||
if (!successors.insert(successor).second) {
|
||||
return Fail(error, "value IR block successor is duplicated");
|
||||
return Fail("value IR block successor is duplicated");
|
||||
}
|
||||
if (std::ranges::find(successor->ImmPredecessors(), block) ==
|
||||
successor->ImmPredecessors().end()) {
|
||||
return Fail(error, "value IR successor edge is not reciprocal");
|
||||
return Fail("value IR successor edge is not reciprocal");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -234,41 +240,40 @@ bool ValidateProgram(const Program& program, bool require_ssa, std::string* erro
|
||||
switch (terminator.kind) {
|
||||
case CFG::TerminatorKind::Branch:
|
||||
if (!add_target(terminator.true_block)) {
|
||||
return Fail(error, "value IR branch target is missing");
|
||||
return Fail("value IR branch target is missing");
|
||||
}
|
||||
break;
|
||||
case CFG::TerminatorKind::ConditionalBranch:
|
||||
if (!add_target(terminator.true_block) || !add_target(terminator.false_block)) {
|
||||
return Fail(error, "value IR conditional branch target is missing");
|
||||
return Fail("value IR conditional branch target is missing");
|
||||
}
|
||||
if (!validate_control_value(program.block_info[block_index].condition, Type::U1)) {
|
||||
return Fail(error, "value IR conditional branch condition is invalid");
|
||||
return Fail("value IR conditional branch condition is invalid");
|
||||
}
|
||||
break;
|
||||
case CFG::TerminatorKind::IndirectBranch: {
|
||||
if (!validate_control_value(program.block_info[block_index].indirect_target,
|
||||
Type::U32)) {
|
||||
return Fail(error, "value IR indirect branch selector is invalid");
|
||||
return Fail("value IR indirect branch selector is invalid");
|
||||
}
|
||||
std::unordered_set<uint32_t> indirect_targets;
|
||||
for (const auto target: terminator.indirect_targets) {
|
||||
if (!indirect_targets.insert(target).second) {
|
||||
return Fail(error, "value IR indirect branch target is duplicated");
|
||||
return Fail("value IR indirect branch target is duplicated");
|
||||
}
|
||||
if (!add_target(target)) {
|
||||
return Fail(error, "value IR indirect branch target is missing");
|
||||
return Fail("value IR indirect branch target is missing");
|
||||
}
|
||||
}
|
||||
if (terminator.indirect_selector_values.size() !=
|
||||
terminator.indirect_selector_targets.size()) {
|
||||
return Fail(error, "value IR indirect selector table is inconsistent");
|
||||
return Fail("value IR indirect selector table is inconsistent");
|
||||
}
|
||||
for (const auto target: terminator.indirect_selector_targets) {
|
||||
const auto found = blocks_by_id.find(target);
|
||||
if (found == blocks_by_id.end() ||
|
||||
!expected_successors.contains(found->second)) {
|
||||
return Fail(error,
|
||||
"value IR indirect selector target is not a CFG successor");
|
||||
return Fail("value IR indirect selector target is not a CFG successor");
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -280,73 +285,73 @@ bool ValidateProgram(const Program& program, bool require_ssa, std::string* erro
|
||||
!blocks_by_id.contains(terminator.merge_block)) ||
|
||||
(terminator.continue_block != UINT32_MAX &&
|
||||
!blocks_by_id.contains(terminator.continue_block))) {
|
||||
return Fail(error, "value IR structured control target is missing");
|
||||
return Fail("value IR structured control target is missing");
|
||||
}
|
||||
if (successors != expected_successors) {
|
||||
return Fail(error, "value IR terminator and successor graph disagree");
|
||||
return Fail("value IR terminator and successor graph disagree");
|
||||
}
|
||||
|
||||
bool saw_non_phi = false;
|
||||
for (const auto& inst: *block) {
|
||||
if (inst.Parent() != block) {
|
||||
return Fail(error, "value IR instruction has the wrong parent block");
|
||||
return Fail("value IR instruction has the wrong parent block");
|
||||
}
|
||||
if (inst.GetOpcode() == ValueOpcode::Phi) {
|
||||
if (saw_non_phi) {
|
||||
return Fail(error, "value IR Phi appears after a non-Phi instruction");
|
||||
return Fail("value IR Phi appears after a non-Phi instruction");
|
||||
}
|
||||
if (inst.NumPhiBlocks() != inst.NumArgs()) {
|
||||
return Fail(error, "value IR Phi parent table is inconsistent");
|
||||
return Fail("value IR Phi parent table is inconsistent");
|
||||
}
|
||||
if (inst.NumArgs() == 0) {
|
||||
return Fail(error, "value IR Phi has no incoming values");
|
||||
return Fail("value IR Phi has no incoming values");
|
||||
}
|
||||
std::unordered_set<const Block*> incoming_blocks;
|
||||
for (size_t arg_index = 0; arg_index < inst.NumArgs(); arg_index++) {
|
||||
const auto* predecessor = inst.PhiBlock(arg_index);
|
||||
if (predecessor == nullptr || !blocks.contains(predecessor) ||
|
||||
!predecessors.contains(predecessor)) {
|
||||
return Fail(error, "value IR Phi has a foreign or non-predecessor parent");
|
||||
return Fail("value IR Phi has a foreign or non-predecessor parent");
|
||||
}
|
||||
if (!incoming_blocks.insert(predecessor).second) {
|
||||
return Fail(error, "value IR Phi parent is duplicated");
|
||||
return Fail("value IR Phi parent is duplicated");
|
||||
}
|
||||
if (inst.Arg(arg_index).GetType() != inst.GetType()) {
|
||||
return Fail(error, "value IR Phi incoming type does not match its result");
|
||||
return Fail("value IR Phi incoming type does not match its result");
|
||||
}
|
||||
}
|
||||
if (incoming_blocks != predecessors) {
|
||||
return Fail(error, "value IR Phi does not cover every predecessor");
|
||||
return Fail("value IR Phi does not cover every predecessor");
|
||||
}
|
||||
} else {
|
||||
saw_non_phi = true;
|
||||
}
|
||||
if (require_ssa && IsRegisterStatePseudo(inst.GetOpcode())) {
|
||||
return Fail(error, fmt::format("register-state pseudo {} survived SSA rewrite",
|
||||
ValueOpcodeName(inst.GetOpcode())));
|
||||
return Fail(fmt::format("register-state pseudo {} survived SSA rewrite",
|
||||
ValueOpcodeName(inst.GetOpcode())));
|
||||
}
|
||||
if (inst.GetOpcode() != ValueOpcode::Phi && inst.GetOpcode() != ValueOpcode::Identity &&
|
||||
inst.GetType() == Type::Opaque) {
|
||||
return Fail(error, fmt::format("untyped opcode {} survived translation",
|
||||
ValueOpcodeName(inst.GetOpcode())));
|
||||
return Fail(fmt::format("untyped opcode {} survived translation",
|
||||
ValueOpcodeName(inst.GetOpcode())));
|
||||
}
|
||||
const bool fixed_signature =
|
||||
inst.GetOpcode() != ValueOpcode::Phi && inst.GetOpcode() != ValueOpcode::Identity;
|
||||
if (fixed_signature && inst.NumArgs() != NumArgsOf(inst.GetOpcode())) {
|
||||
return Fail(error, fmt::format("{} has {} arguments, expected {}",
|
||||
ValueOpcodeName(inst.GetOpcode()), inst.NumArgs(),
|
||||
NumArgsOf(inst.GetOpcode())));
|
||||
return Fail(fmt::format("{} has {} arguments, expected {}",
|
||||
ValueOpcodeName(inst.GetOpcode()), inst.NumArgs(),
|
||||
NumArgsOf(inst.GetOpcode())));
|
||||
}
|
||||
if (inst.GetOpcode() == ValueOpcode::ReadConstBuffer) {
|
||||
const auto memory_index = inst.Flags<MemoryFlags>().index;
|
||||
if (memory_index >= program.memory_info.size()) {
|
||||
return Fail(error, fmt::format("{} has an invalid memory-info index",
|
||||
ValueOpcodeName(inst.GetOpcode())));
|
||||
return Fail(fmt::format("{} has an invalid memory-info index",
|
||||
ValueOpcodeName(inst.GetOpcode())));
|
||||
}
|
||||
const auto& memory = program.memory_info[memory_index];
|
||||
if (memory.kind != ResourceKind::ScalarBuffer) {
|
||||
return Fail(error, fmt::format("{} has an invalid scalar-memory resource kind",
|
||||
ValueOpcodeName(inst.GetOpcode())));
|
||||
return Fail(fmt::format("{} has an invalid scalar-memory resource kind",
|
||||
ValueOpcodeName(inst.GetOpcode())));
|
||||
}
|
||||
const bool valid_group_width =
|
||||
memory.component_count == 1u || memory.component_count == 2u ||
|
||||
@@ -354,23 +359,23 @@ bool ValidateProgram(const Program& program, bool require_ssa, std::string* erro
|
||||
memory.component_count == 16u;
|
||||
if (memory.data_bits != 32u || memory.data_dwords != 1u || !valid_group_width ||
|
||||
memory.component_index >= memory.component_count) {
|
||||
return Fail(error, fmt::format("{} has inconsistent scalar-memory metadata",
|
||||
ValueOpcodeName(inst.GetOpcode())));
|
||||
return Fail(fmt::format("{} has inconsistent scalar-memory metadata",
|
||||
ValueOpcodeName(inst.GetOpcode())));
|
||||
}
|
||||
}
|
||||
const auto address_info = AddressOpcodeInfoOf(inst.GetOpcode());
|
||||
if (address_info.access != AddressAccess::None) {
|
||||
const auto memory_index = inst.Flags<MemoryFlags>().index;
|
||||
if (memory_index >= program.memory_info.size()) {
|
||||
return Fail(error, fmt::format("{} has an invalid memory-info index",
|
||||
ValueOpcodeName(inst.GetOpcode())));
|
||||
return Fail(fmt::format("{} has an invalid memory-info index",
|
||||
ValueOpcodeName(inst.GetOpcode())));
|
||||
}
|
||||
const auto& memory = program.memory_info[memory_index];
|
||||
if (!IsAddressResourceKind(memory.kind) ||
|
||||
(memory.kind == ResourceKind::ScalarAddress &&
|
||||
inst.GetOpcode() != ValueOpcode::LoadAddressU32)) {
|
||||
return Fail(error, fmt::format("{} has an invalid address resource kind",
|
||||
ValueOpcodeName(inst.GetOpcode())));
|
||||
return Fail(fmt::format("{} has an invalid address resource kind",
|
||||
ValueOpcodeName(inst.GetOpcode())));
|
||||
}
|
||||
const bool scalar_address = memory.kind == ResourceKind::ScalarAddress;
|
||||
const bool valid_group_width =
|
||||
@@ -382,50 +387,50 @@ bool ValidateProgram(const Program& program, bool require_ssa, std::string* erro
|
||||
if (memory.data_bits != address_info.data_bits || memory.data_dwords != 1u ||
|
||||
!valid_group_width || memory.component_index >= memory.component_count ||
|
||||
memory.sampler != 0u) {
|
||||
return Fail(error, fmt::format("{} has inconsistent address-memory metadata",
|
||||
ValueOpcodeName(inst.GetOpcode())));
|
||||
return Fail(fmt::format("{} has inconsistent address-memory metadata",
|
||||
ValueOpcodeName(inst.GetOpcode())));
|
||||
}
|
||||
}
|
||||
const auto buffer_components = BufferComponentCount(inst.GetOpcode());
|
||||
if (buffer_components != 0u) {
|
||||
const auto memory_index = inst.Flags<MemoryFlags>().index;
|
||||
if (memory_index >= program.memory_info.size()) {
|
||||
return Fail(error, fmt::format("{} has an invalid memory-info index",
|
||||
ValueOpcodeName(inst.GetOpcode())));
|
||||
return Fail(fmt::format("{} has an invalid memory-info index",
|
||||
ValueOpcodeName(inst.GetOpcode())));
|
||||
}
|
||||
const auto& memory = program.memory_info[memory_index];
|
||||
if (memory.kind != ResourceKind::Buffer &&
|
||||
memory.kind != ResourceKind::ScalarBuffer) {
|
||||
return Fail(error, fmt::format("{} has a non-buffer resource kind",
|
||||
ValueOpcodeName(inst.GetOpcode())));
|
||||
return Fail(fmt::format("{} has a non-buffer resource kind",
|
||||
ValueOpcodeName(inst.GetOpcode())));
|
||||
}
|
||||
if (buffer_components > 1u &&
|
||||
(memory.kind != ResourceKind::Buffer || memory.data_bits != 32u ||
|
||||
memory.data_dwords != buffer_components || memory.component_index != 0u)) {
|
||||
return Fail(error, fmt::format("{} has inconsistent native-wide metadata",
|
||||
ValueOpcodeName(inst.GetOpcode())));
|
||||
return Fail(fmt::format("{} has inconsistent native-wide metadata",
|
||||
ValueOpcodeName(inst.GetOpcode())));
|
||||
}
|
||||
if (buffer_components == 1u &&
|
||||
(inst.GetOpcode() == ValueOpcode::LoadBufferU32 ||
|
||||
inst.GetOpcode() == ValueOpcode::StoreBufferU32) &&
|
||||
memory.data_dwords != 1u) {
|
||||
return Fail(error, fmt::format("{} retains scalar-sibling width metadata",
|
||||
ValueOpcodeName(inst.GetOpcode())));
|
||||
return Fail(fmt::format("{} retains scalar-sibling width metadata",
|
||||
ValueOpcodeName(inst.GetOpcode())));
|
||||
}
|
||||
}
|
||||
const auto shared_components = SharedComponentCount(inst.GetOpcode());
|
||||
if (shared_components != 0u) {
|
||||
const auto memory_index = inst.Flags<MemoryFlags>().index;
|
||||
if (memory_index >= program.memory_info.size()) {
|
||||
return Fail(error, fmt::format("{} has an invalid memory-info index",
|
||||
ValueOpcodeName(inst.GetOpcode())));
|
||||
return Fail(fmt::format("{} has an invalid memory-info index",
|
||||
ValueOpcodeName(inst.GetOpcode())));
|
||||
}
|
||||
const auto& memory = program.memory_info[memory_index];
|
||||
if ((memory.kind != ResourceKind::Lds && memory.kind != ResourceKind::Gds) ||
|
||||
memory.resource != 0u || memory.sampler != 0u || memory.component_count == 0u ||
|
||||
memory.component_index >= memory.component_count) {
|
||||
return Fail(error, fmt::format("{} has invalid shared-memory metadata",
|
||||
ValueOpcodeName(inst.GetOpcode())));
|
||||
return Fail(fmt::format("{} has invalid shared-memory metadata",
|
||||
ValueOpcodeName(inst.GetOpcode())));
|
||||
}
|
||||
uint32_t expected_bits = 32u;
|
||||
if (inst.GetOpcode() == ValueOpcode::LoadSharedU8 ||
|
||||
@@ -437,26 +442,26 @@ bool ValidateProgram(const Program& program, bool require_ssa, std::string* erro
|
||||
}
|
||||
if (memory.data_bits != expected_bits || memory.data_dwords != shared_components ||
|
||||
(shared_components > 1u && memory.component_index != 0u)) {
|
||||
return Fail(error, fmt::format("{} has inconsistent shared-memory width",
|
||||
ValueOpcodeName(inst.GetOpcode())));
|
||||
return Fail(fmt::format("{} has inconsistent shared-memory width",
|
||||
ValueOpcodeName(inst.GetOpcode())));
|
||||
}
|
||||
}
|
||||
const auto image_info = ImageOpcodeInfoOf(inst.GetOpcode());
|
||||
if (image_info.access != ImageAccess::None) {
|
||||
const auto memory_index = inst.Flags<MemoryFlags>().index;
|
||||
if (memory_index >= program.memory_info.size()) {
|
||||
return Fail(error, fmt::format("{} has an invalid memory-info index",
|
||||
ValueOpcodeName(inst.GetOpcode())));
|
||||
return Fail(fmt::format("{} has an invalid memory-info index",
|
||||
ValueOpcodeName(inst.GetOpcode())));
|
||||
}
|
||||
const auto& memory = program.memory_info[memory_index];
|
||||
if (!ImageResourceKindMatches(memory.kind, image_info.resource_class)) {
|
||||
return Fail(error, fmt::format("{} has invalid image-memory metadata",
|
||||
ValueOpcodeName(inst.GetOpcode())));
|
||||
return Fail(fmt::format("{} has invalid image-memory metadata",
|
||||
ValueOpcodeName(inst.GetOpcode())));
|
||||
}
|
||||
}
|
||||
if (inst.GetOpcode() == ValueOpcode::SetAttribute &&
|
||||
inst.Flags<ExportFlags>().index >= program.export_info.size()) {
|
||||
return Fail(error, "SetAttribute has an invalid export-info index");
|
||||
return Fail("SetAttribute has an invalid export-info index");
|
||||
}
|
||||
uint32_t composite_components = 0u;
|
||||
switch (inst.GetOpcode()) {
|
||||
@@ -469,25 +474,24 @@ bool ValidateProgram(const Program& program, bool require_ssa, std::string* erro
|
||||
if (composite_components != 0u &&
|
||||
(!inst.Arg(1).IsImmediate() || inst.Arg(1).GetType() != Type::U32 ||
|
||||
inst.Arg(1).U32() >= composite_components)) {
|
||||
return Fail(error, fmt::format("{} has an invalid component index",
|
||||
ValueOpcodeName(inst.GetOpcode())));
|
||||
return Fail(fmt::format("{} has an invalid component index",
|
||||
ValueOpcodeName(inst.GetOpcode())));
|
||||
}
|
||||
for (size_t arg_index = 0; arg_index < inst.NumArgs(); arg_index++) {
|
||||
const auto arg = inst.Arg(arg_index);
|
||||
if (arg.IsEmpty()) {
|
||||
return Fail(error, fmt::format("{} has an empty argument",
|
||||
ValueOpcodeName(inst.GetOpcode())));
|
||||
return Fail(
|
||||
fmt::format("{} has an empty argument", ValueOpcodeName(inst.GetOpcode())));
|
||||
}
|
||||
if (fixed_signature && arg.GetType() != ArgTypeOf(inst.GetOpcode(), arg_index)) {
|
||||
return Fail(error,
|
||||
fmt::format("{} argument {} has type {}, expected {}",
|
||||
return Fail(fmt::format("{} argument {} has type {}, expected {}",
|
||||
ValueOpcodeName(inst.GetOpcode()), arg_index,
|
||||
TypeName(arg.GetType()),
|
||||
TypeName(ArgTypeOf(inst.GetOpcode(), arg_index))));
|
||||
}
|
||||
if (const auto* definition = arg.TryInstruction();
|
||||
definition != nullptr && !instructions.contains(definition)) {
|
||||
return Fail(error, "value IR argument has a foreign definition");
|
||||
return Fail("value IR argument has a foreign definition");
|
||||
}
|
||||
if (const auto* definition = arg.TryInstruction(); definition != nullptr) {
|
||||
const auto& uses = definition->Uses();
|
||||
@@ -495,8 +499,7 @@ bool ValidateProgram(const Program& program, bool require_ssa, std::string* erro
|
||||
return candidate.user == &inst && candidate.operand == arg_index;
|
||||
});
|
||||
if (use == uses.end()) {
|
||||
return Fail(error,
|
||||
fmt::format("{} argument {} is absent from {} reverse uses",
|
||||
return Fail(fmt::format("{} argument {} is absent from {} reverse uses",
|
||||
ValueOpcodeName(inst.GetOpcode()), arg_index,
|
||||
ValueOpcodeName(definition->GetOpcode())));
|
||||
}
|
||||
@@ -506,7 +509,7 @@ bool ValidateProgram(const Program& program, bool require_ssa, std::string* erro
|
||||
}
|
||||
|
||||
if (program.blocks.empty()) {
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<bool> reachable(program.blocks.size(), false);
|
||||
@@ -523,7 +526,7 @@ bool ValidateProgram(const Program& program, bool require_ssa, std::string* erro
|
||||
}
|
||||
}
|
||||
if (!std::ranges::all_of(reachable, [](bool value) { return value; })) {
|
||||
return Fail(error, "value IR contains an unreachable block");
|
||||
return Fail("value IR contains an unreachable block");
|
||||
}
|
||||
|
||||
std::vector<std::vector<bool>> dominators(program.blocks.size(),
|
||||
@@ -577,28 +580,25 @@ bool ValidateProgram(const Program& program, bool require_ssa, std::string* erro
|
||||
const auto* predecessor = inst.PhiBlock(arg_index);
|
||||
if (definition->Parent() != predecessor &&
|
||||
!dominates(definition->Parent(), predecessor)) {
|
||||
return Fail(error,
|
||||
"value IR Phi incoming definition does not dominate its edge");
|
||||
return Fail("value IR Phi incoming definition does not dominate its edge");
|
||||
}
|
||||
} else if (definition->Parent() == block) {
|
||||
if (instruction_positions.at(definition) >= instruction_positions.at(&inst)) {
|
||||
return Fail(error,
|
||||
"value IR instruction uses a same-block definition before it");
|
||||
return Fail("value IR instruction uses a same-block definition before it");
|
||||
}
|
||||
} else if (!dominates(definition->Parent(), block)) {
|
||||
return Fail(error, "value IR instruction definition does not dominate its use");
|
||||
return Fail("value IR instruction definition does not dominate its use");
|
||||
}
|
||||
}
|
||||
}
|
||||
const auto& info = program.block_info[block_index];
|
||||
if (!info.condition.IsEmpty() && !control_dominates(info.condition, block)) {
|
||||
return Fail(error, "value IR branch condition definition does not dominate its use");
|
||||
return Fail("value IR branch condition definition does not dominate its use");
|
||||
}
|
||||
if (!info.indirect_target.IsEmpty() && !control_dominates(info.indirect_target, block)) {
|
||||
return Fail(error, "value IR indirect selector definition does not dominate its use");
|
||||
return Fail("value IR indirect selector definition does not dominate its use");
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void ResolveControlFlowIdentities(Program& program) {
|
||||
|
||||
@@ -176,20 +176,6 @@ struct SampledResourcePair {
|
||||
bool operator==(const SampledResourcePair& other) const = default;
|
||||
};
|
||||
|
||||
struct AddressResource {
|
||||
uint32_t source = UINT32_MAX;
|
||||
uint32_t first_use_pc = 0;
|
||||
ResourceKind kind = ResourceKind::Flat;
|
||||
int32_t min_offset = 0;
|
||||
uint64_t specialized_base = 0;
|
||||
bool unbased = true;
|
||||
bool read = false;
|
||||
bool written = false;
|
||||
bool atomic = false;
|
||||
|
||||
bool operator==(const AddressResource& other) const = default;
|
||||
};
|
||||
|
||||
enum class StageInputKind {
|
||||
VertexIndex,
|
||||
InstanceIndex,
|
||||
@@ -321,7 +307,8 @@ enum class DescriptorBindingKind {
|
||||
StorageAtomic3D,
|
||||
Samplers,
|
||||
Gds,
|
||||
AddressMemory,
|
||||
BdaPagetable,
|
||||
FaultBuffer,
|
||||
FlattenedSrt,
|
||||
UserData,
|
||||
Count,
|
||||
@@ -431,13 +418,11 @@ struct BindingLayout {
|
||||
|
||||
struct ShaderInfo {
|
||||
static constexpr uint32_t MaxBuffers = 32;
|
||||
static constexpr uint32_t MaxAddresses = 32;
|
||||
static constexpr uint32_t MaxImages = 32;
|
||||
static constexpr uint32_t MaxSamplers = 32;
|
||||
static constexpr uint32_t MaxSampledPairs = 64;
|
||||
|
||||
std::vector<BufferResource> buffers;
|
||||
std::vector<AddressResource> addresses;
|
||||
std::vector<ImageResource> images;
|
||||
std::vector<SamplerResource> samplers;
|
||||
std::vector<SampledResourcePair> sampled_pairs;
|
||||
@@ -446,6 +431,7 @@ struct ShaderInfo {
|
||||
std::array<uint8_t, 32> vertex_fetch_components {};
|
||||
int32_t vertex_offset_sgpr = -1;
|
||||
bool has_bitwise_xor = false;
|
||||
bool uses_dma = false;
|
||||
|
||||
bool operator==(const ShaderInfo& other) const = default;
|
||||
};
|
||||
@@ -491,7 +477,6 @@ struct DescriptorSource {
|
||||
struct SrtRead {
|
||||
Value value;
|
||||
uint32_t flat_offset = 0;
|
||||
uint32_t use_pc = 0;
|
||||
|
||||
bool operator==(const SrtRead& other) const = default;
|
||||
};
|
||||
@@ -537,7 +522,7 @@ struct Program {
|
||||
|
||||
std::string ProgramToString(const Program& program);
|
||||
|
||||
bool ValidateProgram(const Program& program, bool require_ssa, std::string* error);
|
||||
void ValidateProgram(const Program& program, bool require_ssa);
|
||||
void ResolveControlFlowIdentities(Program& program);
|
||||
bool EquivalentValue(const Program& program, Value left, Value right);
|
||||
Value ResolveInvariantPhi(const Program& program, Value value);
|
||||
|
||||
@@ -23,12 +23,16 @@ VALUE_OPCODE(GetScc, U1)
|
||||
VALUE_OPCODE(SetScc, Void, U1)
|
||||
VALUE_OPCODE(GetExec, U1)
|
||||
VALUE_OPCODE(SetExec, Void, U1)
|
||||
VALUE_OPCODE(GetExecMaskTag, U1)
|
||||
VALUE_OPCODE(SetExecMaskTag, Void, U1)
|
||||
VALUE_OPCODE(GetExecLo, U32)
|
||||
VALUE_OPCODE(SetExecLo, Void, U32)
|
||||
VALUE_OPCODE(GetExecHi, U32)
|
||||
VALUE_OPCODE(SetExecHi, Void, U32)
|
||||
VALUE_OPCODE(GetVcc, U1)
|
||||
VALUE_OPCODE(SetVcc, Void, U1)
|
||||
VALUE_OPCODE(GetVccMaskValidTag, U1)
|
||||
VALUE_OPCODE(SetVccMaskValidTag, Void, U1)
|
||||
VALUE_OPCODE(GetVccLo, U32)
|
||||
VALUE_OPCODE(SetVccLo, Void, U32)
|
||||
VALUE_OPCODE(GetVccHi, U32)
|
||||
@@ -188,6 +192,7 @@ VALUE_OPCODE(ReadFirstLane, U32, U32, U1)
|
||||
VALUE_OPCODE(ReadLane, U32, U32, U32)
|
||||
VALUE_OPCODE(WriteLane, U32, U32, U32, U32)
|
||||
VALUE_OPCODE(Permlane16U32, U32, U32, U32, U32, U1)
|
||||
VALUE_OPCODE(BpermuteU32, U32, U32, U32, U1)
|
||||
|
||||
// Typed shader resources and memory operations. Descriptor state is represented by SSA values;
|
||||
// dense bindings are assigned by resource tracking.
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "graphics/shader/recompiler/ir/passes/BindingLayout.h"
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "graphics/shader/recompiler/ir/ShaderIR.h"
|
||||
|
||||
#include <algorithm>
|
||||
@@ -9,12 +10,17 @@
|
||||
namespace Libs::Graphics::ShaderRecompiler::IR {
|
||||
namespace {
|
||||
|
||||
constexpr auto FirstImageBinding = DescriptorBindingKind::Sampled1D;
|
||||
constexpr auto FirstImageBinding = DescriptorBindingKind::Sampled1D;
|
||||
constexpr uint32_t ImageBindingCount =
|
||||
static_cast<uint32_t>(DescriptorBindingKind::StorageAtomic3D) -
|
||||
static_cast<uint32_t>(FirstImageBinding) + 1u;
|
||||
|
||||
bool CollectUserData(const Program& program, std::vector<uint32_t>& result) {
|
||||
[[noreturn]] void BindingFail(const char* message) {
|
||||
EXIT("shader binding layout failed: %s", message);
|
||||
std::abort();
|
||||
}
|
||||
|
||||
std::vector<uint32_t> CollectUserData(const Program& program) {
|
||||
std::array<bool, NumScalarRegs> registers {};
|
||||
for (const auto* block: program.blocks) {
|
||||
for (const auto& inst: *block) {
|
||||
@@ -22,22 +28,22 @@ bool CollectUserData(const Program& program, std::vector<uint32_t>& result) {
|
||||
continue;
|
||||
}
|
||||
if (inst.Arg(0).GetType() != Type::ScalarReg) {
|
||||
return false;
|
||||
BindingFail("typed shader contains an invalid user-data register");
|
||||
}
|
||||
const auto index = RegIndex(inst.Arg(0).ScalarRegister());
|
||||
if (index >= NumScalarRegs) {
|
||||
return false;
|
||||
BindingFail("typed shader contains an invalid user-data register");
|
||||
}
|
||||
registers[index] = true;
|
||||
}
|
||||
}
|
||||
result.clear();
|
||||
std::vector<uint32_t> result;
|
||||
for (uint32_t index = 0; index < registers.size(); index++) {
|
||||
if (registers[index]) {
|
||||
result.push_back(index);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
return result;
|
||||
}
|
||||
|
||||
void AddBinding(BindingLayout& layout, DescriptorBindingKind kind,
|
||||
@@ -45,8 +51,8 @@ void AddBinding(BindingLayout& layout, DescriptorBindingKind kind,
|
||||
layout.descriptors.push_back({kind, std::move(resources)});
|
||||
}
|
||||
|
||||
bool UsesGds(const Program& program, bool& uses_gds) {
|
||||
uses_gds = false;
|
||||
bool UsesGds(const Program& program) {
|
||||
bool uses_gds = false;
|
||||
for (const auto* block: program.blocks) {
|
||||
for (const auto& inst: *block) {
|
||||
if (SharedAccessOf(inst.GetOpcode()) == SharedAccess::None) {
|
||||
@@ -54,47 +60,38 @@ bool UsesGds(const Program& program, bool& uses_gds) {
|
||||
}
|
||||
const auto index = inst.Flags<MemoryFlags>().index;
|
||||
if (index >= program.memory_info.size()) {
|
||||
return false;
|
||||
BindingFail("typed shader contains invalid shared-memory metadata");
|
||||
}
|
||||
const auto kind = program.memory_info[index].kind;
|
||||
if (kind != ResourceKind::Lds && kind != ResourceKind::Gds) {
|
||||
return false;
|
||||
BindingFail("typed shader contains invalid shared-memory metadata");
|
||||
}
|
||||
uses_gds |= kind == ResourceKind::Gds;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
return uses_gds;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool AllocateBindings(Program& program, uint32_t push_constant_offset, std::string* error) {
|
||||
void AllocateBindings(Program& program, uint32_t push_constant_offset) {
|
||||
if (!program.shader_info_complete || program.binding_layout_complete) {
|
||||
if (error != nullptr) {
|
||||
*error = !program.shader_info_complete ? "shader info is not ready"
|
||||
: "binding layout already allocated";
|
||||
}
|
||||
return false;
|
||||
EXIT("shader binding layout failed: %s", !program.shader_info_complete
|
||||
? "shader info is not ready"
|
||||
: "binding layout already allocated");
|
||||
}
|
||||
if (push_constant_offset > NativePushConstantSize ||
|
||||
push_constant_offset % sizeof(uint32_t) != 0) {
|
||||
if (error != nullptr) {
|
||||
*error = "push-constant offset exceeds the Vulkan minimum guarantee or is unaligned";
|
||||
}
|
||||
return false;
|
||||
EXIT("shader binding layout failed: push-constant offset %u exceeds the Vulkan minimum "
|
||||
"guarantee or is unaligned",
|
||||
push_constant_offset);
|
||||
}
|
||||
|
||||
BindingLayout next;
|
||||
next.push_constant_offset = push_constant_offset;
|
||||
if (!CollectUserData(program, next.user_data_registers)) {
|
||||
if (error != nullptr) {
|
||||
*error = "typed shader contains an invalid user-data register";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
next.user_data_registers = CollectUserData(program);
|
||||
next.memory_offset_dword = static_cast<uint32_t>(next.user_data_registers.size());
|
||||
next.memory_offset_count =
|
||||
static_cast<uint32_t>(program.info.buffers.size() + program.info.addresses.size());
|
||||
next.memory_offset_count = static_cast<uint32_t>(program.info.buffers.size());
|
||||
|
||||
if (!program.info.buffers.empty()) {
|
||||
std::vector<uint32_t> resources(program.info.buffers.size());
|
||||
@@ -108,26 +105,18 @@ bool AllocateBindings(Program& program, uint32_t push_constant_offset, std::stri
|
||||
for (uint32_t i = 0; i < program.info.images.size(); i++) {
|
||||
const auto kind = DescriptorBindingForImage(program.info.images[i]);
|
||||
if (!kind.has_value()) {
|
||||
if (error != nullptr) {
|
||||
*error = "shader info contains an invalid image binding class";
|
||||
}
|
||||
return false;
|
||||
EXIT("shader binding layout failed: image %u has an invalid binding class", i);
|
||||
}
|
||||
const auto group = static_cast<uint32_t>(*kind) - static_cast<uint32_t>(FirstImageBinding);
|
||||
if (group >= image_groups.size()) {
|
||||
if (error != nullptr) {
|
||||
*error = "shader info contains an unmapped image binding class";
|
||||
}
|
||||
return false;
|
||||
EXIT("shader binding layout failed: image %u has an unmapped binding class", i);
|
||||
}
|
||||
auto& resources = image_groups[group];
|
||||
const auto dynamic = program.info.images[i].mip_mode == ImageMipMode::DynamicStorage;
|
||||
const auto count = dynamic ? program.info.images[i].mip_count : 1u;
|
||||
if (count == 0u || (!dynamic && program.info.images[i].mip_count != 1u)) {
|
||||
if (error != nullptr) {
|
||||
*error = "image has an invalid specialized mip descriptor count";
|
||||
}
|
||||
return false;
|
||||
EXIT("shader binding layout failed: image %u has invalid specialized mip count %u", i,
|
||||
program.info.images[i].mip_count);
|
||||
}
|
||||
resources.insert(resources.end(), count, i);
|
||||
}
|
||||
@@ -147,22 +136,12 @@ bool AllocateBindings(Program& program, uint32_t push_constant_offset, std::stri
|
||||
}
|
||||
AddBinding(next, DescriptorBindingKind::Samplers, std::move(resources));
|
||||
}
|
||||
bool uses_gds = false;
|
||||
if (!UsesGds(program, uses_gds)) {
|
||||
if (error != nullptr) {
|
||||
*error = "typed shader contains invalid shared-memory metadata";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (uses_gds) {
|
||||
if (UsesGds(program)) {
|
||||
AddBinding(next, DescriptorBindingKind::Gds);
|
||||
}
|
||||
if (!program.info.addresses.empty()) {
|
||||
std::vector<uint32_t> resources(program.info.addresses.size());
|
||||
for (uint32_t i = 0; i < resources.size(); i++) {
|
||||
resources[i] = i;
|
||||
}
|
||||
AddBinding(next, DescriptorBindingKind::AddressMemory, std::move(resources));
|
||||
if (program.info.uses_dma) {
|
||||
AddBinding(next, DescriptorBindingKind::BdaPagetable);
|
||||
AddBinding(next, DescriptorBindingKind::FaultBuffer);
|
||||
}
|
||||
const bool uses_flattened_runtime =
|
||||
!program.srt_reads.empty() ||
|
||||
@@ -182,7 +161,6 @@ bool AllocateBindings(Program& program, uint32_t push_constant_offset, std::stri
|
||||
|
||||
program.bindings = std::move(next);
|
||||
program.binding_layout_complete = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
const DescriptorBinding* FindBinding(const BindingLayout& layout, DescriptorBindingKind kind) {
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::IR {
|
||||
|
||||
bool AllocateBindings(Program& program, uint32_t push_constant_offset, std::string* error);
|
||||
void AllocateBindings(Program& program, uint32_t push_constant_offset);
|
||||
|
||||
const DescriptorBinding* FindBinding(const BindingLayout& layout, DescriptorBindingKind kind);
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "graphics/shader/recompiler/ir/passes/ResourceMaterialization.h"
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "graphics/guest_gpu/gpu_format.h"
|
||||
#include "graphics/shader/recompiler/ir/ShaderIR.h"
|
||||
#include "graphics/shader/shaderBindings.h"
|
||||
@@ -18,6 +19,11 @@ namespace {
|
||||
constexpr uint64_t AddressMask = 0x0000ffffffffffffull;
|
||||
constexpr uint64_t MaxIndirectImageProbes = 65536u;
|
||||
|
||||
[[noreturn]] void SpecializationFail(std::string_view message) {
|
||||
EXIT("shader resource specialization failed: %s", std::string(message).c_str());
|
||||
std::abort();
|
||||
}
|
||||
|
||||
Decoder::ImageDimension DescriptorDimension(const DescriptorValue& descriptor,
|
||||
Decoder::ImageDimension requested) {
|
||||
const bool is_array = requested == Decoder::ImageDimension::Dim1DArray ||
|
||||
@@ -79,7 +85,7 @@ uint32_t DescriptorImageSwizzle(const DescriptorValue& descriptor) {
|
||||
|
||||
Prospero::BufferFormat ImageConversionFormat(Prospero::BufferFormat format) {
|
||||
return Prospero::RemapTextureFormat(format) != format ? format
|
||||
: Prospero::BufferFormat::kInvalid;
|
||||
: Prospero::BufferFormat::kInvalid;
|
||||
}
|
||||
|
||||
bool DescriptorIsCube(const DescriptorValue& descriptor) {
|
||||
@@ -153,8 +159,7 @@ bool ReadSpecializationWord(const SrtRuntime& runtime, uint64_t address, uint32_
|
||||
}
|
||||
|
||||
bool ReadScalarBufferWord(const ShaderBufferResource& descriptor, uint32_t dynamic_offset,
|
||||
uint32_t immediate_offset, const SrtRuntime& runtime, uint32_t& word,
|
||||
std::string* error, uint32_t use_pc) {
|
||||
uint32_t immediate_offset, const SrtRuntime& runtime, uint32_t& word) {
|
||||
const auto byte_offset = static_cast<uint64_t>(dynamic_offset) + immediate_offset;
|
||||
const auto aligned = byte_offset & ~uint64_t {3};
|
||||
const auto size = ScalarBufferSize(descriptor);
|
||||
@@ -164,20 +169,10 @@ bool ReadScalarBufferWord(const ShaderBufferResource& descriptor, uint32_t dynam
|
||||
}
|
||||
const auto base = descriptor.Base48() & ~uint64_t {3};
|
||||
if (aligned > AddressMask - base) {
|
||||
if (error != nullptr) {
|
||||
*error = fmt::format("indirect image scalar read at pc 0x{:08x} exceeds the "
|
||||
"48-bit address space",
|
||||
use_pc);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
const auto address = base + aligned;
|
||||
if (!ReadSpecializationWord(runtime, address, word)) {
|
||||
if (error != nullptr) {
|
||||
*error = fmt::format("indirect image scalar read at pc 0x{:08x} failed at "
|
||||
"0x{:016x}",
|
||||
use_pc, address);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@@ -186,23 +181,14 @@ bool ReadScalarBufferWord(const ShaderBufferResource& descriptor, uint32_t dynam
|
||||
bool MaterializeIndirectImage(const DescriptorSource::IndirectImage& indirect,
|
||||
const DescriptorValue& material_value,
|
||||
const DescriptorValue& heap_value, const SrtRuntime& runtime,
|
||||
ResourceSnapshot::IndirectImage& result, std::string* error,
|
||||
uint32_t use_pc) {
|
||||
ResourceSnapshot::IndirectImage& result) {
|
||||
ShaderBufferResource material;
|
||||
ShaderBufferResource heap;
|
||||
if (!DecodeBufferDescriptor(material_value, material) ||
|
||||
!DecodeBufferDescriptor(heap_value, heap)) {
|
||||
if (error != nullptr) {
|
||||
*error = fmt::format("indirect image tables at pc 0x{:08x} have invalid descriptors",
|
||||
use_pc);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (material.Stride() != indirect.selector_stride) {
|
||||
if (error != nullptr) {
|
||||
*error =
|
||||
fmt::format("indirect image material table at pc 0x{:08x} changed stride", use_pc);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -215,11 +201,6 @@ bool MaterializeIndirectImage(const DescriptorSource::IndirectImage& indirect,
|
||||
const auto limit = std::min<uint64_t>(UINT32_MAX, size + 3u);
|
||||
const auto probe_count = residue <= limit ? (limit - residue) / step + 1u : 0u;
|
||||
if (probe_count > MaxIndirectImageProbes) {
|
||||
if (error != nullptr) {
|
||||
*error = fmt::format("indirect image material table at pc 0x{:08x} requires {} "
|
||||
"bounded probes",
|
||||
use_pc, probe_count);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -227,8 +208,7 @@ bool MaterializeIndirectImage(const DescriptorSource::IndirectImage& indirect,
|
||||
std::unordered_set<uint32_t> seen {0u};
|
||||
for (uint64_t offset = residue; offset <= limit && probe_count != 0u; offset += step) {
|
||||
uint32_t key = 0;
|
||||
if (!ReadScalarBufferWord(material, static_cast<uint32_t>(offset), 0u, runtime, key, error,
|
||||
use_pc)) {
|
||||
if (!ReadScalarBufferWord(material, static_cast<uint32_t>(offset), 0u, runtime, key)) {
|
||||
return false;
|
||||
}
|
||||
if (seen.insert(key).second) {
|
||||
@@ -248,7 +228,7 @@ bool MaterializeIndirectImage(const DescriptorSource::IndirectImage& indirect,
|
||||
const auto heap_offset = key << 5u;
|
||||
for (uint32_t dword = 0; dword < candidate.dword_count; dword++) {
|
||||
if (!ReadScalarBufferWord(heap, heap_offset, dword * sizeof(uint32_t), runtime,
|
||||
candidate.dwords[dword], error, use_pc)) {
|
||||
candidate.dwords[dword])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -267,13 +247,6 @@ bool MaterializeIndirectImage(const DescriptorSource::IndirectImage& indirect,
|
||||
return true;
|
||||
}
|
||||
|
||||
uint64_t AddressSpecialization(const AddressResource& resource,
|
||||
const ResourceSnapshot::Address& snapshot) {
|
||||
return resource.kind == ResourceKind::Flat || resource.unbased
|
||||
? snapshot.binding_base
|
||||
: snapshot.guest_base - snapshot.binding_base;
|
||||
}
|
||||
|
||||
size_t FlattenedRuntimeDwords(const Program& program) {
|
||||
size_t size = program.srt_reads.size();
|
||||
for (uint32_t resource = 0; resource < program.info.images.size(); resource++) {
|
||||
@@ -288,36 +261,22 @@ size_t FlattenedRuntimeDwords(const Program& program) {
|
||||
|
||||
} // namespace
|
||||
|
||||
bool ValidateResourceSnapshot(const Program& program, const ResourceSnapshot& snapshot,
|
||||
std::string* error) {
|
||||
bool ValidateResourceSnapshot(const Program& program, const ResourceSnapshot& snapshot) {
|
||||
if (!program.resource_tracking_complete) {
|
||||
if (error != nullptr) {
|
||||
*error = "shader resources were not tracked";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (snapshot.buffers.size() != program.info.buffers.size() ||
|
||||
snapshot.images.size() != program.info.images.size() ||
|
||||
snapshot.samplers.size() != program.info.samplers.size() ||
|
||||
snapshot.addresses.size() != program.info.addresses.size()) {
|
||||
if (error != nullptr) {
|
||||
*error = "resource snapshot does not match dense shader topology";
|
||||
}
|
||||
snapshot.samplers.size() != program.info.samplers.size()) {
|
||||
return false;
|
||||
}
|
||||
if (snapshot.flattened_srt.size() != FlattenedRuntimeDwords(program)) {
|
||||
if (error != nullptr) {
|
||||
*error = "flattened SRT snapshot does not match the shader plan";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (program.binding_layout_complete) {
|
||||
for (const auto reg: program.bindings.user_data_registers) {
|
||||
if (reg < program.user_data_base ||
|
||||
reg - program.user_data_base >= snapshot.user_data.size()) {
|
||||
if (error != nullptr) {
|
||||
*error = fmt::format("runtime snapshot is missing user SGPR {}", reg);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -325,32 +284,17 @@ bool ValidateResourceSnapshot(const Program& program, const ResourceSnapshot& sn
|
||||
for (uint32_t i = 0; i < program.info.buffers.size(); i++) {
|
||||
const auto alias = program.info.buffers[i].image_alias;
|
||||
if (alias != BufferResource::NoImageAlias && alias >= program.info.images.size()) {
|
||||
if (error != nullptr) {
|
||||
*error = fmt::format("buffer resource {} has invalid image alias {}", i, alias);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
const auto CheckWidth = [&](const auto& values, uint32_t width, const char* kind) {
|
||||
const auto CheckWidth = [&](const auto& values, uint32_t width) {
|
||||
for (uint32_t i = 0; i < values.size(); i++) {
|
||||
if (values[i].dword_count != width) {
|
||||
if (error != nullptr) {
|
||||
*error = fmt::format("{} descriptor {} has {} dwords", kind, i,
|
||||
values[i].dword_count);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
for (uint32_t i = 0; i < snapshot.addresses.size(); i++) {
|
||||
if (snapshot.addresses[i].binding_base > snapshot.addresses[i].guest_base) {
|
||||
if (error != nullptr) {
|
||||
*error = fmt::format("address resource {} binds above its guest base", i);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
std::unordered_set<uint32_t> indirect_resources;
|
||||
for (const auto& table: snapshot.indirect_images) {
|
||||
const auto* source = table.resource < program.info.images.size()
|
||||
@@ -361,56 +305,37 @@ bool ValidateResourceSnapshot(const Program& program, const ResourceSnapshot& sn
|
||||
table.keys.empty() || table.keys.size() != table.candidates.size() ||
|
||||
table.capacity < table.keys.size() || table.descriptors.empty() ||
|
||||
!indirect_resources.insert(table.resource).second) {
|
||||
if (error != nullptr) {
|
||||
*error = "indirect image snapshot does not match the shader plan";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
for (const auto candidate: table.candidates) {
|
||||
if (candidate >= table.descriptors.size()) {
|
||||
if (error != nullptr) {
|
||||
*error = "indirect image snapshot has an invalid candidate";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (snapshot.images[table.resource] != table.descriptors[table.candidates[0]]) {
|
||||
if (error != nullptr) {
|
||||
*error = "indirect image snapshot has an inconsistent root descriptor";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return CheckWidth(snapshot.buffers, 4, "buffer") && CheckWidth(snapshot.images, 8, "image") &&
|
||||
CheckWidth(snapshot.samplers, 4, "sampler");
|
||||
return CheckWidth(snapshot.buffers, 4) && CheckWidth(snapshot.images, 8) &&
|
||||
CheckWidth(snapshot.samplers, 4);
|
||||
}
|
||||
|
||||
bool ValidateResourceSpecialization(const Program& program, const ResourceSnapshot& snapshot,
|
||||
std::string* error) {
|
||||
if (!ValidateResourceSnapshot(program, snapshot, error)) {
|
||||
bool ValidateResourceSpecialization(const Program& program, const ResourceSnapshot& snapshot) {
|
||||
if (!ValidateResourceSnapshot(program, snapshot)) {
|
||||
return false;
|
||||
}
|
||||
if (!snapshot.indirect_images.empty()) {
|
||||
if (error != nullptr) {
|
||||
*error = "indirect image snapshot was not specialized";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
for (uint32_t i = 0; i < program.info.buffers.size(); i++) {
|
||||
const auto& buffer = program.info.buffers[i];
|
||||
ShaderBufferResource descriptor;
|
||||
if (!DecodeBufferDescriptor(snapshot.buffers[i], descriptor)) {
|
||||
if (error != nullptr) {
|
||||
*error = fmt::format("buffer descriptor {} has invalid width", i);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (buffer.packed_stride != descriptor.PackedStride() ||
|
||||
buffer.descriptor_format != descriptor.Format() ||
|
||||
buffer.descriptor_swizzle != descriptor.DstSelXYZW()) {
|
||||
if (error != nullptr) {
|
||||
*error = fmt::format("buffer descriptor {} no longer matches specialization", i);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -419,9 +344,6 @@ bool ValidateResourceSpecialization(const Program& program, const ResourceSnapsh
|
||||
const auto& descriptor = snapshot.images[i];
|
||||
const auto mip_count = StorageMipCount(image, descriptor);
|
||||
if (mip_count == 0u || mip_count != image.mip_count) {
|
||||
if (error != nullptr) {
|
||||
*error = fmt::format("storage image descriptor {} changed mip range", i);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (NullImageDescriptor(descriptor)) {
|
||||
@@ -435,10 +357,6 @@ bool ValidateResourceSpecialization(const Program& program, const ResourceSnapsh
|
||||
program.info.images[root].conversion_format != image.conversion_format ||
|
||||
program.info.images[root].shader_swizzle != image.shader_swizzle ||
|
||||
program.info.images[root].cube != image.cube) {
|
||||
if (error != nullptr) {
|
||||
*error = fmt::format(
|
||||
"null indirect image descriptor {} changed binding class", i);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
@@ -450,10 +368,6 @@ bool ValidateResourceSpecialization(const Program& program, const ResourceSnapsh
|
||||
}
|
||||
if (image.dimension != Decoder::ImageDimension::Dim2D || image.cube ||
|
||||
!canonical_kind) {
|
||||
if (error != nullptr) {
|
||||
*error = fmt::format(
|
||||
"image descriptor {} no longer matches canonical null specialization", i);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
@@ -461,14 +375,6 @@ bool ValidateResourceSpecialization(const Program& program, const ResourceSnapsh
|
||||
const auto dimension = DescriptorDimension(descriptor, image.dimension);
|
||||
if (dimension == Decoder::ImageDimension::Unknown || dimension != image.dimension ||
|
||||
DescriptorIsCube(descriptor) != image.cube) {
|
||||
if (error != nullptr) {
|
||||
*error =
|
||||
fmt::format("image descriptor {} no longer matches specialized dimension: "
|
||||
"{:08x},{:08x},{:08x},{:08x},{:08x},{:08x},{:08x},{:08x}",
|
||||
i, descriptor.dwords[0], descriptor.dwords[1], descriptor.dwords[2],
|
||||
descriptor.dwords[3], descriptor.dwords[4], descriptor.dwords[5],
|
||||
descriptor.dwords[6], descriptor.dwords[7]);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (image.kind == ResourceKind::Image || image.kind == ResourceKind::ImageUint ||
|
||||
@@ -479,90 +385,49 @@ bool ValidateResourceSpecialization(const Program& program, const ResourceSnapsh
|
||||
const auto format =
|
||||
static_cast<Prospero::BufferFormat>((descriptor.dwords[1] >> 20u) & 0x1ffu);
|
||||
if (image.atomic && format != Prospero::BufferFormat::k32UInt) {
|
||||
if (error != nullptr) {
|
||||
*error = fmt::format(
|
||||
"atomic image descriptor {} changed to unsupported format {}", i,
|
||||
static_cast<uint32_t>(format));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
const auto conversion_format = ImageConversionFormat(format);
|
||||
if (image.conversion_format != conversion_format) {
|
||||
if (error != nullptr) {
|
||||
*error = fmt::format("image descriptor {} changed format conversion", i);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if ((storage || conversion_format != Prospero::BufferFormat::kInvalid) &&
|
||||
image.shader_swizzle != DescriptorImageSwizzle(descriptor)) {
|
||||
if (error != nullptr) {
|
||||
*error = fmt::format("image descriptor {} changed swizzle", i);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
const bool raw_sint_storage =
|
||||
storage && format == Prospero::BufferFormat::k32SInt && image.written &&
|
||||
!image.read && !image.atomic;
|
||||
const bool uint_descriptor =
|
||||
Prospero::IsUintTextureFormat(format) || raw_sint_storage;
|
||||
const auto uint_program = image.kind == ResourceKind::ImageUint ||
|
||||
image.kind == ResourceKind::StorageImageUint;
|
||||
const bool raw_sint_storage = storage && format == Prospero::BufferFormat::k32SInt &&
|
||||
image.written && !image.read && !image.atomic;
|
||||
const bool uint_descriptor = Prospero::IsUintTextureFormat(format) || raw_sint_storage;
|
||||
const auto uint_program = image.kind == ResourceKind::ImageUint ||
|
||||
image.kind == ResourceKind::StorageImageUint;
|
||||
if (uint_descriptor != uint_program && !(image.atomic && uint_program)) {
|
||||
if (error != nullptr) {
|
||||
*error = fmt::format(
|
||||
"image descriptor {} no longer matches specialized format: "
|
||||
"{:08x},{:08x},{:08x},{:08x},{:08x},{:08x},{:08x},{:08x}",
|
||||
i, descriptor.dwords[0], descriptor.dwords[1], descriptor.dwords[2],
|
||||
descriptor.dwords[3], descriptor.dwords[4], descriptor.dwords[5],
|
||||
descriptor.dwords[6], descriptor.dwords[7]);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (image.depth_compare && uint_program) {
|
||||
if (error != nullptr) {
|
||||
*error = fmt::format("integer image descriptor {} uses depth comparison", i);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (uint32_t i = 0; i < program.info.addresses.size(); i++) {
|
||||
if (program.info.addresses[i].specialized_base !=
|
||||
AddressSpecialization(program.info.addresses[i], snapshot.addresses[i])) {
|
||||
if (error != nullptr) {
|
||||
*error = fmt::format("address resource {} no longer matches specialization", i);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MaterializeResources(const Program& program, const SrtRuntime& runtime,
|
||||
ResourceSnapshot& snapshot, std::string* error) {
|
||||
ResourceSnapshot& snapshot) {
|
||||
if (!program.resource_tracking_complete) {
|
||||
if (error != nullptr) {
|
||||
*error = "shader resources were not tracked";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<DescriptorSourceRequest> requests;
|
||||
std::vector<uint8_t> clean_flat_slots(program.srt_reads.size());
|
||||
requests.reserve(program.info.buffers.size() + program.info.images.size() * 2u +
|
||||
program.info.samplers.size() + program.info.addresses.size());
|
||||
program.info.samplers.size());
|
||||
for (const auto& buffer: program.info.buffers) {
|
||||
requests.push_back({buffer.source, buffer.first_use_pc});
|
||||
requests.push_back({buffer.source});
|
||||
}
|
||||
for (const auto& image: program.info.images) {
|
||||
const auto* source = Source(program, image.source);
|
||||
if (source != nullptr && source->indirect_image.has_value()) {
|
||||
if (runtime.read_specialization_memory == nullptr) {
|
||||
if (error != nullptr) {
|
||||
*error = fmt::format("indirect image table at pc 0x{:08x} has no clean "
|
||||
"memory reader",
|
||||
image.first_use_pc);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
MarkCleanFlatSlots(program, Source(program, source->indirect_image->material_source),
|
||||
@@ -570,22 +435,16 @@ bool MaterializeResources(const Program& program, const SrtRuntime& runtime,
|
||||
MarkCleanFlatSlots(program, Source(program, source->indirect_image->heap_source),
|
||||
clean_flat_slots);
|
||||
} else {
|
||||
requests.push_back({image.source, image.first_use_pc});
|
||||
requests.push_back({image.source});
|
||||
}
|
||||
}
|
||||
for (const auto& sampler: program.info.samplers) {
|
||||
requests.push_back({sampler.source, sampler.first_use_pc});
|
||||
requests.push_back({sampler.source});
|
||||
}
|
||||
for (const auto& address: program.info.addresses) {
|
||||
if (!address.unbased) {
|
||||
requests.push_back({address.source, address.first_use_pc});
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<DescriptorValue> values;
|
||||
std::vector<uint32_t> flattened_srt;
|
||||
if (!EvaluateRuntimeSources(program, requests, runtime, values, flattened_srt, clean_flat_slots,
|
||||
error)) {
|
||||
if (!EvaluateRuntimeSources(program, requests, runtime, values, flattened_srt,
|
||||
clean_flat_slots)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -612,20 +471,19 @@ bool MaterializeResources(const Program& program, const SrtRuntime& runtime,
|
||||
continue;
|
||||
}
|
||||
const std::array requests {
|
||||
DescriptorSourceRequest {source->indirect_image->material_source,
|
||||
image.first_use_pc},
|
||||
DescriptorSourceRequest {source->indirect_image->heap_source, image.first_use_pc}};
|
||||
DescriptorSourceRequest {source->indirect_image->material_source},
|
||||
DescriptorSourceRequest {source->indirect_image->heap_source}};
|
||||
SrtRuntime clean_runtime = runtime;
|
||||
clean_runtime.read_memory = runtime.read_specialization_memory;
|
||||
std::vector<DescriptorValue> tables;
|
||||
if (!EvaluateDescriptorSources(program, requests, clean_runtime, tables, error)) {
|
||||
if (!EvaluateDescriptorSources(program, requests, clean_runtime, tables)) {
|
||||
return false;
|
||||
}
|
||||
const auto& material = tables[0];
|
||||
const auto& heap = tables[1];
|
||||
ResourceSnapshot::IndirectImage table;
|
||||
if (!MaterializeIndirectImage(*source->indirect_image, material, heap, runtime, table,
|
||||
error, image.first_use_pc)) {
|
||||
if (!MaterializeIndirectImage(*source->indirect_image, material, heap, runtime,
|
||||
table)) {
|
||||
return false;
|
||||
}
|
||||
if (image.indirect_root == ImageResource::NoIndirectImage) {
|
||||
@@ -639,20 +497,12 @@ bool MaterializeResources(const Program& program, const SrtRuntime& runtime,
|
||||
}
|
||||
if (table.keys.size() > image.indirect_mapping_capacity ||
|
||||
table.descriptors.size() > image.indirect_resources.size()) {
|
||||
if (error != nullptr) {
|
||||
*error = fmt::format(
|
||||
"indirect image table at pc 0x{:08x} changed candidate topology",
|
||||
image.first_use_pc);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
for (uint32_t candidate = 0; candidate < image.indirect_resources.size(); candidate++) {
|
||||
const auto resource = image.indirect_resources[candidate];
|
||||
if (resource >= program.info.images.size() ||
|
||||
program.info.images[resource].indirect_root != image_index) {
|
||||
if (error != nullptr) {
|
||||
*error = "indirect image specialization has an invalid candidate resource";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
next.images[resource] =
|
||||
@@ -680,62 +530,26 @@ bool MaterializeResources(const Program& program, const SrtRuntime& runtime,
|
||||
}
|
||||
}
|
||||
if (std::ranges::find(image_written, uint8_t {0}) != image_written.end()) {
|
||||
if (error != nullptr) {
|
||||
*error = "indirect image specialization left an unmaterialized candidate";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
next.samplers.assign(cursor, cursor + program.info.samplers.size());
|
||||
cursor += program.info.samplers.size();
|
||||
for (const auto& address: program.info.addresses) {
|
||||
if (!address.unbased) {
|
||||
const auto value = *cursor++;
|
||||
auto base = (static_cast<uint64_t>(value.dwords[0]) |
|
||||
static_cast<uint64_t>(value.dwords[1]) << 32u) &
|
||||
AddressMask;
|
||||
if (address.kind == ResourceKind::ScalarAddress) {
|
||||
base &= ~uint64_t {3};
|
||||
}
|
||||
const auto before = static_cast<uint64_t>(-static_cast<int64_t>(address.min_offset));
|
||||
uint64_t binding_base = 0;
|
||||
if (address.kind == ResourceKind::Flat) {
|
||||
binding_base = base & ~(FlatAddressWindowSize - 1u);
|
||||
} else if (base >= before) {
|
||||
binding_base = base - before;
|
||||
}
|
||||
next.addresses.push_back({base, binding_base});
|
||||
} else {
|
||||
if (!runtime.flat_memory_base.has_value()) {
|
||||
if (error != nullptr) {
|
||||
*error = fmt::format("unbased {} address at pc 0x{:08x} requires runtime "
|
||||
"guest-address translation",
|
||||
address.kind == ResourceKind::Flat ? "FLAT" : "global",
|
||||
address.first_use_pc);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
next.addresses.push_back({*runtime.flat_memory_base, *runtime.flat_memory_base});
|
||||
}
|
||||
}
|
||||
next.user_data.assign(runtime.user_data.begin(), runtime.user_data.end());
|
||||
if (!ValidateResourceSnapshot(program, next, error)) {
|
||||
if (!ValidateResourceSnapshot(program, next)) {
|
||||
return false;
|
||||
}
|
||||
snapshot = std::move(next);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SpecializeResources(Program& program, ResourceSnapshot& snapshot, std::string* error) {
|
||||
void SpecializeResources(Program& program, ResourceSnapshot& snapshot) {
|
||||
if (!program.resource_tracking_complete || program.shader_info_complete ||
|
||||
program.binding_layout_complete) {
|
||||
if (error != nullptr) {
|
||||
*error = !program.resource_tracking_complete ? "shader resources were not tracked"
|
||||
: "resource specialization is too late";
|
||||
}
|
||||
return false;
|
||||
return SpecializationFail(!program.resource_tracking_complete
|
||||
? "shader resources were not tracked"
|
||||
: "resource specialization is too late");
|
||||
}
|
||||
if (!ValidateResourceSnapshot(program, snapshot, error)) {
|
||||
return false;
|
||||
if (!ValidateResourceSnapshot(program, snapshot)) {
|
||||
return SpecializationFail("invalid resource snapshot");
|
||||
}
|
||||
|
||||
auto next = program.info;
|
||||
@@ -743,10 +557,8 @@ bool SpecializeResources(Program& program, ResourceSnapshot& snapshot, std::stri
|
||||
for (const auto& table: snapshot.indirect_images) {
|
||||
if (table.resource >= next.images.size() || table.descriptors.size() < 2u ||
|
||||
next.images.size() + table.descriptors.size() - 1u > ShaderInfo::MaxImages) {
|
||||
if (error != nullptr) {
|
||||
*error = "indirect image candidates exceed the dense image resource limit";
|
||||
}
|
||||
return false;
|
||||
return SpecializationFail(
|
||||
"indirect image candidates exceed the dense image resource limit");
|
||||
}
|
||||
const auto root_image = next.images[table.resource];
|
||||
std::vector<uint32_t> resources(table.descriptors.size());
|
||||
@@ -782,28 +594,19 @@ bool SpecializeResources(Program& program, ResourceSnapshot& snapshot, std::stri
|
||||
for (uint32_t i = 0; i < next.buffers.size(); i++) {
|
||||
ShaderBufferResource descriptor;
|
||||
if (!DecodeBufferDescriptor(next_snapshot.buffers[i], descriptor)) {
|
||||
if (error != nullptr) {
|
||||
*error = fmt::format("buffer descriptor {} has invalid width", i);
|
||||
}
|
||||
return false;
|
||||
return SpecializationFail(fmt::format("buffer descriptor {} has invalid width", i));
|
||||
}
|
||||
next.buffers[i].packed_stride = descriptor.PackedStride();
|
||||
next.buffers[i].descriptor_format = descriptor.Format();
|
||||
next.buffers[i].descriptor_swizzle = descriptor.DstSelXYZW();
|
||||
}
|
||||
for (uint32_t i = 0; i < next.addresses.size(); i++) {
|
||||
next.addresses[i].specialized_base =
|
||||
AddressSpecialization(next.addresses[i], next_snapshot.addresses[i]);
|
||||
}
|
||||
for (uint32_t i = 0; i < next.images.size(); i++) {
|
||||
const auto& descriptor = next_snapshot.images[i];
|
||||
auto& image = next.images[i];
|
||||
image.mip_count = StorageMipCount(image, descriptor);
|
||||
if (image.mip_count == 0u) {
|
||||
if (error != nullptr) {
|
||||
*error = fmt::format("storage image descriptor {} has an invalid mip range", i);
|
||||
}
|
||||
return false;
|
||||
return SpecializationFail(
|
||||
fmt::format("storage image descriptor {} has an invalid mip range", i));
|
||||
}
|
||||
if (NullImageDescriptor(descriptor)) {
|
||||
image.dimension = Decoder::ImageDimension::Dim2D;
|
||||
@@ -821,37 +624,31 @@ bool SpecializeResources(Program& program, ResourceSnapshot& snapshot, std::stri
|
||||
}
|
||||
const auto descriptor_dimension = DescriptorDimension(descriptor, image.dimension);
|
||||
if (descriptor_dimension == Decoder::ImageDimension::Unknown) {
|
||||
if (error != nullptr) {
|
||||
*error = fmt::format(
|
||||
"image descriptor {} has unsupported type {}: {:08x},{:08x},{:08x},{:08x},"
|
||||
"{:08x},{:08x},{:08x},{:08x}",
|
||||
i, (descriptor.dwords[3] >> 28u) & 0xfu, descriptor.dwords[0],
|
||||
descriptor.dwords[1], descriptor.dwords[2], descriptor.dwords[3],
|
||||
descriptor.dwords[4], descriptor.dwords[5], descriptor.dwords[6],
|
||||
descriptor.dwords[7]);
|
||||
}
|
||||
return false;
|
||||
return SpecializationFail(fmt::format(
|
||||
"image descriptor {} has unsupported type {}: {:08x},{:08x},{:08x},{:08x},"
|
||||
"{:08x},{:08x},{:08x},{:08x}",
|
||||
i, (descriptor.dwords[3] >> 28u) & 0xfu, descriptor.dwords[0], descriptor.dwords[1],
|
||||
descriptor.dwords[2], descriptor.dwords[3], descriptor.dwords[4],
|
||||
descriptor.dwords[5], descriptor.dwords[6], descriptor.dwords[7]));
|
||||
}
|
||||
image.dimension = descriptor_dimension;
|
||||
image.cube = DescriptorIsCube(descriptor);
|
||||
const auto format =
|
||||
static_cast<Prospero::BufferFormat>((descriptor.dwords[1] >> 20u) & 0x1ffu);
|
||||
if (image.atomic && format != Prospero::BufferFormat::k32UInt) {
|
||||
if (error != nullptr) {
|
||||
*error = fmt::format("atomic image descriptor {} uses unsupported format {}", i,
|
||||
static_cast<uint32_t>(format));
|
||||
}
|
||||
return false;
|
||||
return SpecializationFail(
|
||||
fmt::format("atomic image descriptor {} uses unsupported format {}", i,
|
||||
static_cast<uint32_t>(format)));
|
||||
}
|
||||
const bool storage = image.kind == ResourceKind::StorageImage ||
|
||||
image.kind == ResourceKind::StorageImageUint;
|
||||
const bool storage = image.kind == ResourceKind::StorageImage ||
|
||||
image.kind == ResourceKind::StorageImageUint;
|
||||
image.conversion_format = ImageConversionFormat(format);
|
||||
if (storage || image.conversion_format != Prospero::BufferFormat::kInvalid) {
|
||||
image.shader_swizzle = DescriptorImageSwizzle(descriptor);
|
||||
}
|
||||
const bool raw_sint_storage = storage && format == Prospero::BufferFormat::k32SInt &&
|
||||
image.written && !image.read && !image.atomic;
|
||||
const bool uint_image = Prospero::IsUintTextureFormat(format) || raw_sint_storage;
|
||||
const bool uint_image = Prospero::IsUintTextureFormat(format) || raw_sint_storage;
|
||||
if (uint_image) {
|
||||
switch (image.kind) {
|
||||
case ResourceKind::Image: image.kind = ResourceKind::ImageUint; break;
|
||||
@@ -869,19 +666,13 @@ bool SpecializeResources(Program& program, ResourceSnapshot& snapshot, std::stri
|
||||
static_cast<size_t>(root.indirect_mapping_offset) + 1u +
|
||||
static_cast<size_t>(root.indirect_mapping_capacity) * 2u >
|
||||
next_snapshot.flattened_srt.size()) {
|
||||
if (error != nullptr) {
|
||||
*error = "indirect image specialization has an invalid key mapping";
|
||||
}
|
||||
return false;
|
||||
return SpecializationFail("indirect image specialization has an invalid key mapping");
|
||||
}
|
||||
uint32_t exemplar = ImageResource::NoIndirectImage;
|
||||
for (const auto resource: root.indirect_resources) {
|
||||
if (resource >= next.images.size() ||
|
||||
next.images[resource].indirect_root != root_index) {
|
||||
if (error != nullptr) {
|
||||
*error = "indirect image specialization has an invalid candidate";
|
||||
}
|
||||
return false;
|
||||
return SpecializationFail("indirect image specialization has an invalid candidate");
|
||||
}
|
||||
if (!NullImageDescriptor(next_snapshot.images[resource])) {
|
||||
exemplar = resource;
|
||||
@@ -889,10 +680,7 @@ bool SpecializeResources(Program& program, ResourceSnapshot& snapshot, std::stri
|
||||
}
|
||||
}
|
||||
if (exemplar == ImageResource::NoIndirectImage) {
|
||||
if (error != nullptr) {
|
||||
*error = "indirect image specialization has no typed candidate";
|
||||
}
|
||||
return false;
|
||||
return SpecializationFail("indirect image specialization has no typed candidate");
|
||||
}
|
||||
const auto& image_class = next.images[exemplar];
|
||||
for (uint32_t candidate = 0; candidate < next.images.size(); candidate++) {
|
||||
@@ -915,12 +703,9 @@ bool SpecializeResources(Program& program, ResourceSnapshot& snapshot, std::stri
|
||||
image.shader_swizzle != image_class.shader_swizzle ||
|
||||
image.depth_compare != image_class.depth_compare ||
|
||||
image.cube != image_class.cube) {
|
||||
if (error != nullptr) {
|
||||
*error = fmt::format(
|
||||
"indirect image table at pc 0x{:08x} has incompatible candidates",
|
||||
root.first_use_pc);
|
||||
}
|
||||
return false;
|
||||
return SpecializationFail(
|
||||
fmt::format("indirect image table at pc 0x{:08x} has incompatible candidates",
|
||||
root.first_use_pc));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -933,11 +718,8 @@ bool SpecializeResources(Program& program, ResourceSnapshot& snapshot, std::stri
|
||||
std::vector<SamplerUsage> sampler_usage(next.samplers.size());
|
||||
for (const auto& pair: next.sampled_pairs) {
|
||||
if (pair.image >= next.images.size() || pair.sampler >= next.samplers.size()) {
|
||||
if (error != nullptr) {
|
||||
*error = fmt::format("sampled resource pair at pc 0x{:08x} is invalid",
|
||||
pair.first_use_pc);
|
||||
}
|
||||
return false;
|
||||
return SpecializationFail(
|
||||
fmt::format("sampled resource pair at pc 0x{:08x} is invalid", pair.first_use_pc));
|
||||
}
|
||||
auto& usage = sampler_usage[pair.sampler];
|
||||
if (next.images[pair.image].conversion_format != Prospero::BufferFormat::kInvalid) {
|
||||
@@ -957,10 +739,8 @@ bool SpecializeResources(Program& program, ResourceSnapshot& snapshot, std::stri
|
||||
continue;
|
||||
}
|
||||
if (next.samplers.size() >= ShaderInfo::MaxSamplers) {
|
||||
if (error != nullptr) {
|
||||
*error = "point-filter sampler variants exceed the dense sampler resource limit";
|
||||
}
|
||||
return false;
|
||||
return SpecializationFail(
|
||||
"point-filter sampler variants exceed the dense sampler resource limit");
|
||||
}
|
||||
point_sampler[i] = static_cast<uint32_t>(next.samplers.size());
|
||||
auto sampler = next.samplers[i];
|
||||
@@ -981,19 +761,13 @@ bool SpecializeResources(Program& program, ResourceSnapshot& snapshot, std::stri
|
||||
}
|
||||
const auto index = inst.Flags<MemoryFlags>().index;
|
||||
if (index >= memory_info.size()) {
|
||||
if (error != nullptr) {
|
||||
*error = fmt::format("typed image instruction has invalid memory metadata {}",
|
||||
index);
|
||||
}
|
||||
return false;
|
||||
return SpecializationFail(
|
||||
fmt::format("typed image instruction has invalid memory metadata {}", index));
|
||||
}
|
||||
auto& memory = memory_info[index];
|
||||
if (memory.resource >= next.images.size()) {
|
||||
if (error != nullptr) {
|
||||
*error = fmt::format("typed image instruction has invalid resource {}",
|
||||
memory.resource);
|
||||
}
|
||||
return false;
|
||||
return SpecializationFail(fmt::format(
|
||||
"typed image instruction has invalid resource {}", memory.resource));
|
||||
}
|
||||
const auto& image = next.images[memory.resource];
|
||||
if (image_opcode.needs_sampler &&
|
||||
@@ -1003,22 +777,18 @@ bool SpecializeResources(Program& program, ResourceSnapshot& snapshot, std::stri
|
||||
}
|
||||
if (image.indirect_root == memory.resource &&
|
||||
inst.GetOpcode() != ValueOpcode::ImageSampleRaw) {
|
||||
if (error != nullptr) {
|
||||
*error = fmt::format(
|
||||
"indirect image table at pc 0x{:08x} is used by unsupported {}",
|
||||
inst.Flags<MemoryFlags>().pc, ValueOpcodeName(inst.GetOpcode()));
|
||||
}
|
||||
return false;
|
||||
return SpecializationFail(
|
||||
fmt::format("indirect image table at pc 0x{:08x} is used by unsupported {}",
|
||||
inst.Flags<MemoryFlags>().pc, ValueOpcodeName(inst.GetOpcode())));
|
||||
}
|
||||
memory.kind = image.kind;
|
||||
memory.image_dimension = image.dimension;
|
||||
memory.image_cube = image.cube;
|
||||
}
|
||||
}
|
||||
program.info = std::move(next);
|
||||
program.info = std::move(next);
|
||||
program.memory_info = std::move(memory_info);
|
||||
snapshot = std::move(next_snapshot);
|
||||
return true;
|
||||
snapshot = std::move(next_snapshot);
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::IR
|
||||
@@ -5,15 +5,7 @@
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::IR {
|
||||
|
||||
constexpr uint64_t FlatAddressWindowSize = 0x10000;
|
||||
|
||||
struct ResourceSnapshot {
|
||||
struct Address {
|
||||
uint64_t guest_base = 0;
|
||||
uint64_t binding_base = 0;
|
||||
|
||||
bool operator==(const Address& other) const = default;
|
||||
};
|
||||
struct IndirectImage {
|
||||
uint32_t resource = 0;
|
||||
uint32_t capacity = 0;
|
||||
@@ -25,25 +17,22 @@ struct ResourceSnapshot {
|
||||
std::vector<DescriptorValue> buffers;
|
||||
std::vector<DescriptorValue> images;
|
||||
std::vector<DescriptorValue> samplers;
|
||||
std::vector<Address> addresses;
|
||||
std::vector<uint32_t> flattened_srt;
|
||||
std::vector<uint32_t> user_data;
|
||||
std::vector<IndirectImage> indirect_images;
|
||||
};
|
||||
|
||||
bool ValidateResourceSnapshot(const Program& program, const ResourceSnapshot& snapshot,
|
||||
std::string* error);
|
||||
bool ValidateResourceSpecialization(const Program& program, const ResourceSnapshot& snapshot,
|
||||
std::string* error);
|
||||
bool ValidateResourceSnapshot(const Program& program, const ResourceSnapshot& snapshot);
|
||||
bool ValidateResourceSpecialization(const Program& program, const ResourceSnapshot& snapshot);
|
||||
|
||||
// Resolves the immutable dense resource topology against one runtime user-data/SRT snapshot.
|
||||
// On failure the destination is unchanged.
|
||||
bool MaterializeResources(const Program& program, const SrtRuntime& runtime,
|
||||
ResourceSnapshot& snapshot, std::string* error);
|
||||
ResourceSnapshot& snapshot);
|
||||
|
||||
// Applies runtime descriptor shape/format facts to a copied dense topology before layout and
|
||||
// emission. On failure the program is unchanged.
|
||||
bool SpecializeResources(Program& program, ResourceSnapshot& snapshot, std::string* error);
|
||||
// emission. Invalid specializations are fatal.
|
||||
void SpecializeResources(Program& program, ResourceSnapshot& snapshot);
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::IR
|
||||
|
||||
|
||||
@@ -1,19 +1,18 @@
|
||||
#include "graphics/shader/recompiler/ir/passes/ResourceTracking.h"
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "graphics/shader/recompiler/ir/ShaderIR.h"
|
||||
#include "graphics/shader/recompiler/ir/passes/SrtWalker.h"
|
||||
#include "graphics/shader/shader.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <fmt/format.h>
|
||||
#include <span>
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::IR {
|
||||
namespace {
|
||||
|
||||
constexpr uint32_t SamplerBorderClampMask = (1u << 2u) | (1u << 5u) | (1u << 8u);
|
||||
constexpr uint32_t SamplerBorderClampMask = (1u << 2u) | (1u << 5u) | (1u << 8u);
|
||||
constexpr uint32_t SamplerDword3ReservedMask = 0x3ffff000u;
|
||||
|
||||
uint32_t PossibleU32Bits(Value value) {
|
||||
@@ -96,25 +95,23 @@ class Tracker {
|
||||
public:
|
||||
explicit Tracker(Program& program): m_program(program), m_info(program.info) {
|
||||
m_info.buffers.clear();
|
||||
m_info.addresses.clear();
|
||||
m_info.images.clear();
|
||||
m_info.samplers.clear();
|
||||
m_info.sampled_pairs.clear();
|
||||
m_info.uses_dma = false;
|
||||
}
|
||||
|
||||
bool Run(std::string* error) {
|
||||
void Run() {
|
||||
if (m_program.resource_tracking_complete) {
|
||||
return Fail(0, error, "resources already tracked");
|
||||
Fail(0, "resources already tracked");
|
||||
}
|
||||
if (!m_program.srt_plan_complete) {
|
||||
return Fail(0, error, "SRT plan is not ready");
|
||||
Fail(0, "SRT plan is not ready");
|
||||
}
|
||||
PlanIndirectImages();
|
||||
for (auto* block: m_program.blocks) {
|
||||
for (auto& inst: *block) {
|
||||
if (!Collect(inst, error)) {
|
||||
return false;
|
||||
}
|
||||
Collect(inst);
|
||||
}
|
||||
}
|
||||
LinkImageAliases();
|
||||
@@ -146,10 +143,9 @@ public:
|
||||
return std::ranges::find(plan.reads, inst) != plan.reads.end();
|
||||
});
|
||||
});
|
||||
m_program.descriptor_sources = std::move(m_sources);
|
||||
m_program.descriptor_sources = std::move(m_sources);
|
||||
m_program.info = std::move(m_info);
|
||||
m_program.resource_tracking_complete = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -174,102 +170,19 @@ private:
|
||||
std::array<const Inst*, 8> reads {};
|
||||
};
|
||||
|
||||
bool Fail(uint32_t pc, std::string* error, const std::string& reason) const {
|
||||
return ShaderError::Fail(
|
||||
error, fmt::format("shader resource tracking: hash=0x{:016x} stage={} pc=0x{:08x} {}",
|
||||
m_program.shader_hash, StageName(m_program.stage), pc, reason));
|
||||
[[noreturn]] void Fail(uint32_t pc, const std::string& reason) const {
|
||||
const auto message =
|
||||
fmt::format("shader resource tracking: hash=0x{:016x} stage={} pc=0x{:08x} {}",
|
||||
m_program.shader_hash, StageName(m_program.stage), pc, reason);
|
||||
EXIT("%s", message.c_str());
|
||||
std::abort();
|
||||
}
|
||||
|
||||
struct AddressPart {
|
||||
Value value;
|
||||
bool rooted = false;
|
||||
};
|
||||
|
||||
AddressPart FindAddressPart(Value value, Value active,
|
||||
std::unordered_set<const Inst*>& visiting) const {
|
||||
value = value.Resolve();
|
||||
if (value.IsImmediate()) {
|
||||
return {value, false};
|
||||
}
|
||||
const auto* inst = value.TryInstruction();
|
||||
if (inst == nullptr || !visiting.insert(inst).second) {
|
||||
return {};
|
||||
}
|
||||
const auto finish = [&](AddressPart part) {
|
||||
visiting.erase(inst);
|
||||
return part;
|
||||
};
|
||||
std::string reason;
|
||||
if (ValidateRuntimeValue(m_program, value, reason)) {
|
||||
return finish({value, true});
|
||||
}
|
||||
const auto merge = [&](AddressPart left, AddressPart right, bool require_both) {
|
||||
if (left.value.IsEmpty() || right.value.IsEmpty()) {
|
||||
return require_both ? AddressPart {} : (left.value.IsEmpty() ? right : left);
|
||||
}
|
||||
return EquivalentValue(m_program, left.value, right.value)
|
||||
? AddressPart {left.value, left.rooted || right.rooted}
|
||||
: AddressPart {};
|
||||
};
|
||||
switch (inst->GetOpcode()) {
|
||||
case ValueOpcode::SelectU32:
|
||||
if (EquivalentValue(m_program, inst->Arg(0), active)) {
|
||||
return finish(FindAddressPart(inst->Arg(1), active, visiting));
|
||||
}
|
||||
return finish(merge(FindAddressPart(inst->Arg(1), active, visiting),
|
||||
FindAddressPart(inst->Arg(2), active, visiting), true));
|
||||
case ValueOpcode::Phi: {
|
||||
const auto invariant = ResolveInvariantPhi(m_program, value);
|
||||
return finish(invariant.IsEmpty() ? AddressPart {}
|
||||
: FindAddressPart(invariant, active, visiting));
|
||||
}
|
||||
case ValueOpcode::IAdd32:
|
||||
case ValueOpcode::ISub32: {
|
||||
const auto left = FindAddressPart(inst->Arg(0), active, visiting);
|
||||
const auto right = FindAddressPart(inst->Arg(1), active, visiting);
|
||||
if (left.rooted == right.rooted ||
|
||||
(inst->GetOpcode() == ValueOpcode::ISub32 && right.rooted)) {
|
||||
return finish({});
|
||||
}
|
||||
return finish(left.rooted ? left : right);
|
||||
}
|
||||
case ValueOpcode::CompositeExtractU32x2: {
|
||||
const auto* source = inst->Arg(0).ResolveInstruction();
|
||||
if (source == nullptr || source->GetOpcode() != ValueOpcode::IAddCarry32) {
|
||||
return finish({});
|
||||
}
|
||||
const auto left = FindAddressPart(source->Arg(0), active, visiting);
|
||||
const auto right = FindAddressPart(source->Arg(1), active, visiting);
|
||||
if (left.rooted == right.rooted) {
|
||||
return finish({});
|
||||
}
|
||||
return finish(left.rooted ? left : right);
|
||||
}
|
||||
default: return finish({});
|
||||
}
|
||||
}
|
||||
|
||||
bool MakeFlatAddressSource(const Inst& handle, Value active,
|
||||
DescriptorSource& descriptor) const {
|
||||
std::unordered_set<const Inst*> visiting;
|
||||
auto low = FindAddressPart(handle.Arg(0), active, visiting);
|
||||
visiting.clear();
|
||||
auto high = FindAddressPart(handle.Arg(1), active, visiting);
|
||||
if ((!low.rooted && !high.rooted) || low.value.IsEmpty() || high.value.IsEmpty()) {
|
||||
return false;
|
||||
}
|
||||
descriptor.dword_count = 2;
|
||||
descriptor.dwords[0] = low.value;
|
||||
descriptor.dwords[1] = high.value;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MakeSource(const Inst& handle, uint32_t width, bool sampler, bool sample_adjust,
|
||||
DescriptorSource& descriptor, uint32_t pc, std::string* error) const {
|
||||
void MakeSource(const Inst& handle, uint32_t width, bool sampler, bool sample_adjust,
|
||||
DescriptorSource& descriptor, uint32_t pc) const {
|
||||
if (handle.NumArgs() != width) {
|
||||
return Fail(pc, error,
|
||||
fmt::format("{} has {} descriptor dwords, expected {}",
|
||||
ValueOpcodeName(handle.GetOpcode()), handle.NumArgs(), width));
|
||||
Fail(pc, fmt::format("{} has {} descriptor dwords, expected {}",
|
||||
ValueOpcodeName(handle.GetOpcode()), handle.NumArgs(), width));
|
||||
}
|
||||
descriptor.dword_count = width;
|
||||
for (uint32_t i = 0; i < width; i++) {
|
||||
@@ -284,18 +197,15 @@ private:
|
||||
// Border color and its table index are unused unless a clamp axis selects border mode.
|
||||
descriptor.dwords[3] = Value(0u);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ValidateSource(const DescriptorSource& descriptor, std::string& reason,
|
||||
uint32_t& bad_dword) const {
|
||||
bool ValidateSource(const DescriptorSource& descriptor, uint32_t& bad_dword) const {
|
||||
for (uint32_t i = 0; i < descriptor.dword_count; i++) {
|
||||
bad_dword = i;
|
||||
if (descriptor.dwords[i].Resolve().GetType() != Type::U32) {
|
||||
reason = "is not U32";
|
||||
return false;
|
||||
}
|
||||
if (!ValidateRuntimeValue(m_program, descriptor.dwords[i], reason)) {
|
||||
if (!ValidateRuntimeValue(m_program, descriptor.dwords[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -371,13 +281,12 @@ private:
|
||||
|
||||
bool MakeRuntimeBufferSource(const Inst& handle, uint32_t pc, uint32_t& source,
|
||||
DescriptorSource& descriptor) {
|
||||
if (handle.GetOpcode() != ValueOpcode::GetBufferResource ||
|
||||
!MakeSource(handle, 4u, false, false, descriptor, pc, nullptr)) {
|
||||
if (handle.GetOpcode() != ValueOpcode::GetBufferResource) {
|
||||
return false;
|
||||
}
|
||||
std::string reason;
|
||||
uint32_t bad_dword = 0;
|
||||
if (!ValidateSource(descriptor, reason, bad_dword)) {
|
||||
MakeSource(handle, 4u, false, false, descriptor, pc);
|
||||
uint32_t bad_dword = 0;
|
||||
if (!ValidateSource(descriptor, bad_dword)) {
|
||||
return false;
|
||||
}
|
||||
source = InternSource(descriptor);
|
||||
@@ -554,66 +463,30 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
bool GetHandle(Value value, ValueOpcode expected, uint32_t width, uint32_t pc, Inst*& handle,
|
||||
uint32_t& source, std::string* error, bool sampler = false,
|
||||
bool sample_adjust = false) {
|
||||
void GetHandle(Value value, ValueOpcode expected, uint32_t width, uint32_t pc, Inst*& handle,
|
||||
uint32_t& source, bool sampler = false, bool sample_adjust = false) {
|
||||
handle = value.Resolve().TryInstruction();
|
||||
if (handle == nullptr || handle->GetOpcode() != expected) {
|
||||
return Fail(pc, error,
|
||||
fmt::format("memory operation requires {}", ValueOpcodeName(expected)));
|
||||
Fail(pc, fmt::format("memory operation requires {}", ValueOpcodeName(expected)));
|
||||
}
|
||||
DescriptorSource descriptor;
|
||||
if (!MakeSource(*handle, width, sampler, sample_adjust, descriptor, pc, error)) {
|
||||
return false;
|
||||
}
|
||||
std::string reason;
|
||||
uint32_t bad_dword = 0;
|
||||
if (!ValidateSource(descriptor, reason, bad_dword)) {
|
||||
return Fail(
|
||||
pc, error,
|
||||
fmt::format("{} dword {} {}", ValueOpcodeName(expected), bad_dword, reason));
|
||||
MakeSource(*handle, width, sampler, sample_adjust, descriptor, pc);
|
||||
uint32_t bad_dword = 0;
|
||||
if (!ValidateSource(descriptor, bad_dword)) {
|
||||
Fail(pc, fmt::format("{} dword {} is not a valid runtime value",
|
||||
ValueOpcodeName(expected), bad_dword));
|
||||
}
|
||||
source = InternSource(descriptor);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GetAddressHandle(const Inst& memory_inst, Value value, uint32_t pc, Inst*& handle,
|
||||
uint32_t& source, bool& unbased, std::string* error) {
|
||||
handle = value.Resolve().TryInstruction();
|
||||
void ValidateAddressHandle(Value value, uint32_t pc) const {
|
||||
const auto* handle = value.Resolve().TryInstruction();
|
||||
if (handle == nullptr || handle->GetOpcode() != ValueOpcode::GetAddressResource) {
|
||||
return Fail(pc, error, "address operation requires GetAddressResource");
|
||||
Fail(pc, "address operation requires GetAddressResource");
|
||||
}
|
||||
if (handle->NumArgs() != 2) {
|
||||
return Fail(pc, error, "GetAddressResource must have two address dwords");
|
||||
Fail(pc, "GetAddressResource must have two address dwords");
|
||||
}
|
||||
DescriptorSource descriptor;
|
||||
const auto& memory = m_program.memory_info[memory_inst.Flags<MemoryFlags>().index];
|
||||
if (memory.address_is_full) {
|
||||
const auto active = memory_inst.Arg(memory_inst.NumArgs() - 1u);
|
||||
if (memory.kind != ResourceKind::Flat ||
|
||||
!MakeFlatAddressSource(*handle, active, descriptor)) {
|
||||
unbased = true;
|
||||
source = UINT32_MAX;
|
||||
return true;
|
||||
}
|
||||
} else if (!MakeSource(*handle, 2, false, false, descriptor, pc, error)) {
|
||||
return false;
|
||||
}
|
||||
std::string reason;
|
||||
uint32_t bad_dword = 0;
|
||||
if (!ValidateSource(descriptor, reason, bad_dword)) {
|
||||
if (memory.address_is_full) {
|
||||
unbased = true;
|
||||
source = UINT32_MAX;
|
||||
return true;
|
||||
}
|
||||
return Fail(
|
||||
pc, error,
|
||||
fmt::format("scalar memory base dword {} is unresolved: {}", bad_dword, reason));
|
||||
}
|
||||
unbased = false;
|
||||
source = InternSource(descriptor);
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32_t AddBuffer(uint32_t source, const MemoryInfo& memory, ValueOpcode op, uint32_t pc) {
|
||||
@@ -653,7 +526,7 @@ private:
|
||||
const auto mip = MipMode(memory);
|
||||
const bool depth = (memory.image_sample_flags & Decoder::ImageSampleFlagCompare) != 0;
|
||||
for (uint32_t i = 0; i < m_info.images.size(); i++) {
|
||||
auto& image = m_info.images[i];
|
||||
auto& image = m_info.images[i];
|
||||
const bool compatible_kind = image.kind == memory.kind || (IsStorageImage(image.kind) &&
|
||||
IsStorageImage(memory.kind));
|
||||
if (image.source == source && compatible_kind &&
|
||||
@@ -706,193 +579,139 @@ private:
|
||||
return static_cast<uint32_t>(m_info.samplers.size() - 1);
|
||||
}
|
||||
|
||||
uint32_t AddAddress(uint32_t source, bool unbased, const MemoryInfo& memory,
|
||||
AddressAccess access, uint32_t pc) {
|
||||
auto immediate = static_cast<int32_t>(memory.offset);
|
||||
if (memory.kind == ResourceKind::ScalarAddress) {
|
||||
immediate = static_cast<int32_t>(static_cast<uint32_t>(immediate) & ~3u);
|
||||
}
|
||||
const auto min_offset = unbased ? 0 : std::min(immediate, 0);
|
||||
for (uint32_t i = 0; i < m_info.addresses.size(); i++) {
|
||||
auto& address = m_info.addresses[i];
|
||||
if (address.source == source && address.unbased == unbased &&
|
||||
address.kind == memory.kind) {
|
||||
address.first_use_pc = std::min(address.first_use_pc, pc);
|
||||
address.min_offset = std::min(address.min_offset, min_offset);
|
||||
address.read = address.read || access == AddressAccess::Read;
|
||||
address.written = address.written || access == AddressAccess::Write;
|
||||
return i;
|
||||
}
|
||||
}
|
||||
if (m_info.addresses.size() >= ShaderInfo::MaxAddresses) {
|
||||
return UINT32_MAX;
|
||||
}
|
||||
AddressResource address;
|
||||
address.source = source;
|
||||
address.first_use_pc = pc;
|
||||
address.kind = memory.kind;
|
||||
address.min_offset = min_offset;
|
||||
address.unbased = unbased;
|
||||
address.read = access == AddressAccess::Read;
|
||||
address.written = access == AddressAccess::Write;
|
||||
m_info.addresses.push_back(address);
|
||||
return static_cast<uint32_t>(m_info.addresses.size() - 1);
|
||||
}
|
||||
|
||||
bool AddSampledPair(uint32_t image, uint32_t sampler, uint32_t pc, std::string* error) {
|
||||
void AddSampledPair(uint32_t image, uint32_t sampler, uint32_t pc) {
|
||||
for (auto& pair: m_info.sampled_pairs) {
|
||||
if (pair.image == image && pair.sampler == sampler) {
|
||||
pair.first_use_pc = std::min(pair.first_use_pc, pc);
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (m_info.sampled_pairs.size() >= ShaderInfo::MaxSampledPairs) {
|
||||
return Fail(pc, error, "sampled image/sampler pair limit exceeded");
|
||||
Fail(pc, "sampled image/sampler pair limit exceeded");
|
||||
}
|
||||
m_info.sampled_pairs.push_back({image, sampler, pc});
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AddHandlePatch(Inst* handle, uint32_t resource, uint32_t pc, std::string* error) {
|
||||
void AddHandlePatch(Inst* handle, uint32_t resource, uint32_t pc) {
|
||||
for (const auto& patch: m_handle_patches) {
|
||||
if (patch.handle == handle) {
|
||||
return patch.resource == resource ||
|
||||
Fail(pc, error,
|
||||
fmt::format("{} is reused with incompatible resource classes",
|
||||
ValueOpcodeName(handle->GetOpcode())));
|
||||
if (patch.resource != resource) {
|
||||
Fail(pc, fmt::format("{} is reused with incompatible resource classes",
|
||||
ValueOpcodeName(handle->GetOpcode())));
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
m_handle_patches.push_back({handle, resource});
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AddMemoryPatch(uint32_t index, uint32_t resource, uint32_t sampler, bool has_sampler,
|
||||
uint32_t pc, std::string* error) {
|
||||
void AddMemoryPatch(uint32_t index, uint32_t resource, uint32_t sampler, bool has_sampler,
|
||||
uint32_t pc) {
|
||||
for (auto& patch: m_memory_patches) {
|
||||
if (patch.index != index) {
|
||||
continue;
|
||||
}
|
||||
if (patch.resource != resource ||
|
||||
(has_sampler && patch.has_sampler && patch.sampler != sampler)) {
|
||||
return Fail(pc, error, "memory metadata is reused with incompatible resources");
|
||||
Fail(pc, "memory metadata is reused with incompatible resources");
|
||||
}
|
||||
if (has_sampler) {
|
||||
patch.sampler = sampler;
|
||||
patch.has_sampler = true;
|
||||
}
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
m_memory_patches.push_back({index, resource, sampler, has_sampler});
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Collect(Inst& inst, std::string* error) {
|
||||
void Collect(Inst& inst) {
|
||||
const auto op = inst.GetOpcode();
|
||||
const auto buffer = BufferAccessOf(op);
|
||||
const auto address_info = AddressOpcodeInfoOf(op);
|
||||
const auto image_info = ImageOpcodeInfoOf(op);
|
||||
if (buffer == BufferAccess::None && address_info.access == AddressAccess::None &&
|
||||
image_info.access == ImageAccess::None) {
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
const auto flags = inst.Flags<MemoryFlags>();
|
||||
if (flags.index >= m_program.memory_info.size()) {
|
||||
return Fail(flags.pc, error,
|
||||
fmt::format("memory metadata index {} is out of range", flags.index));
|
||||
Fail(flags.pc, fmt::format("memory metadata index {} is out of range", flags.index));
|
||||
}
|
||||
if (inst.NumArgs() == 0) {
|
||||
return Fail(flags.pc, error, "memory operation has no resource handle");
|
||||
Fail(flags.pc, "memory operation has no resource handle");
|
||||
}
|
||||
const auto& memory = m_program.memory_info[flags.index];
|
||||
if (memory.planning_only || IsIndirectPlanningMemory(flags.index)) {
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
Inst* handle = nullptr;
|
||||
uint32_t source = 0;
|
||||
uint32_t resource = 0;
|
||||
|
||||
if (buffer != BufferAccess::None) {
|
||||
if (!GetHandle(inst.Arg(0), ValueOpcode::GetBufferResource, 4, flags.pc, handle, source,
|
||||
error)) {
|
||||
return false;
|
||||
}
|
||||
GetHandle(inst.Arg(0), ValueOpcode::GetBufferResource, 4, flags.pc, handle, source);
|
||||
resource = AddBuffer(source, memory, op, flags.pc);
|
||||
if (resource == UINT32_MAX) {
|
||||
return Fail(flags.pc, error, "buffer resource limit exceeded");
|
||||
Fail(flags.pc, "buffer resource limit exceeded");
|
||||
}
|
||||
return AddHandlePatch(handle, resource, flags.pc, error) &&
|
||||
AddMemoryPatch(flags.index, resource, 0, false, flags.pc, error);
|
||||
AddHandlePatch(handle, resource, flags.pc);
|
||||
AddMemoryPatch(flags.index, resource, 0, false, flags.pc);
|
||||
return;
|
||||
}
|
||||
if (address_info.access != AddressAccess::None) {
|
||||
if (!IsAddressResourceKind(memory.kind)) {
|
||||
return Fail(flags.pc, error, "address operation has invalid resource kind");
|
||||
Fail(flags.pc, "address operation has invalid resource kind");
|
||||
}
|
||||
if (memory.kind == ResourceKind::Scratch) {
|
||||
handle = inst.Arg(0).Resolve().TryInstruction();
|
||||
if (handle == nullptr || handle->GetOpcode() != ValueOpcode::GetScratchResource ||
|
||||
handle->NumArgs() != 0) {
|
||||
return Fail(flags.pc, error, "scratch operation requires GetScratchResource");
|
||||
Fail(flags.pc, "scratch operation requires GetScratchResource");
|
||||
}
|
||||
if (m_program.scratch_dwords == 0) {
|
||||
return Fail(flags.pc, error,
|
||||
"scratch operation requires a nonzero AGC per-thread size");
|
||||
Fail(flags.pc, "scratch operation requires a nonzero AGC per-thread size");
|
||||
}
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
bool unbased = false;
|
||||
if (!GetAddressHandle(inst, inst.Arg(0), flags.pc, handle, source, unbased, error)) {
|
||||
return false;
|
||||
}
|
||||
resource = AddAddress(source, unbased, memory, address_info.access, flags.pc);
|
||||
if (resource == UINT32_MAX) {
|
||||
return Fail(flags.pc, error, "address resource limit exceeded");
|
||||
}
|
||||
return AddHandlePatch(handle, resource, flags.pc, error) &&
|
||||
AddMemoryPatch(flags.index, resource, 0, false, flags.pc, error);
|
||||
ValidateAddressHandle(inst.Arg(0), flags.pc);
|
||||
m_info.uses_dma = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ImageResourceKindMatches(memory.kind, image_info.resource_class)) {
|
||||
return Fail(flags.pc, error, "image operation has invalid resource kind");
|
||||
Fail(flags.pc, "image operation has invalid resource kind");
|
||||
}
|
||||
handle = inst.Arg(0).Resolve().TryInstruction();
|
||||
const auto* indirect = handle != nullptr ? FindIndirectImage(*handle) : nullptr;
|
||||
if (indirect != nullptr) {
|
||||
source = indirect->source;
|
||||
} else if (!GetHandle(inst.Arg(0), ValueOpcode::GetImageResource, 8, flags.pc, handle,
|
||||
source, error)) {
|
||||
return false;
|
||||
} else {
|
||||
GetHandle(inst.Arg(0), ValueOpcode::GetImageResource, 8, flags.pc, handle, source);
|
||||
}
|
||||
resource = AddImage(source, memory, op, flags.pc);
|
||||
if (resource == UINT32_MAX) {
|
||||
return Fail(flags.pc, error, "image resource limit exceeded");
|
||||
}
|
||||
if (!AddHandlePatch(handle, resource, flags.pc, error)) {
|
||||
return false;
|
||||
Fail(flags.pc, "image resource limit exceeded");
|
||||
}
|
||||
AddHandlePatch(handle, resource, flags.pc);
|
||||
uint32_t sampler = 0;
|
||||
if (image_info.needs_sampler) {
|
||||
if (inst.NumArgs() < 2) {
|
||||
return Fail(flags.pc, error, "sampled image operation has no sampler handle");
|
||||
Fail(flags.pc, "sampled image operation has no sampler handle");
|
||||
}
|
||||
Inst* sampler_handle = nullptr;
|
||||
uint32_t sampler_source = 0;
|
||||
Inst* sampler_handle = nullptr;
|
||||
uint32_t sampler_source = 0;
|
||||
const bool sample_adjust =
|
||||
(memory.image_sample_flags & Decoder::ImageSampleFlagAdjust) != 0;
|
||||
if (!GetHandle(inst.Arg(1), ValueOpcode::GetSamplerResource, 4, flags.pc,
|
||||
sampler_handle, sampler_source, error, true, sample_adjust)) {
|
||||
return false;
|
||||
}
|
||||
GetHandle(inst.Arg(1), ValueOpcode::GetSamplerResource, 4, flags.pc, sampler_handle,
|
||||
sampler_source, true, sample_adjust);
|
||||
sampler = AddSampler(sampler_source, flags.pc);
|
||||
if (sampler == UINT32_MAX) {
|
||||
return Fail(flags.pc, error, "sampler resource limit exceeded");
|
||||
}
|
||||
if (!AddHandlePatch(sampler_handle, sampler, flags.pc, error) ||
|
||||
!AddSampledPair(resource, sampler, flags.pc, error)) {
|
||||
return false;
|
||||
Fail(flags.pc, "sampler resource limit exceeded");
|
||||
}
|
||||
AddHandlePatch(sampler_handle, sampler, flags.pc);
|
||||
AddSampledPair(resource, sampler, flags.pc);
|
||||
}
|
||||
return AddMemoryPatch(flags.index, resource, sampler, image_info.needs_sampler, flags.pc,
|
||||
error);
|
||||
AddMemoryPatch(flags.index, resource, sampler, image_info.needs_sampler, flags.pc);
|
||||
}
|
||||
|
||||
const DescriptorSource* Source(uint32_t source) const {
|
||||
@@ -934,8 +753,8 @@ private:
|
||||
|
||||
} // namespace
|
||||
|
||||
bool TrackResources(Program& program, std::string* error) {
|
||||
return Tracker(program).Run(error);
|
||||
void TrackResources(Program& program) {
|
||||
Tracker(program).Run();
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::IR
|
||||
@@ -7,7 +7,7 @@ namespace Libs::Graphics::ShaderRecompiler::IR {
|
||||
|
||||
// Collects immutable resource topology from typed SSA handles, interns their resolved dwords in
|
||||
// descriptor_sources, then writes dense indices to handle flags and MemoryInfo.
|
||||
bool TrackResources(Program& program, std::string* error);
|
||||
void TrackResources(Program& program);
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::IR
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "graphics/shader/recompiler/ir/passes/ShaderInfoCollection.h"
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "graphics/shader/recompiler/ir/ShaderIR.h"
|
||||
|
||||
#include <algorithm>
|
||||
@@ -8,6 +9,11 @@
|
||||
namespace Libs::Graphics::ShaderRecompiler::IR {
|
||||
namespace {
|
||||
|
||||
[[noreturn]] void Fail(std::string_view message) {
|
||||
EXIT("shader info collection failed: %s", std::string(message).c_str());
|
||||
std::abort();
|
||||
}
|
||||
|
||||
void AddInput(ShaderInfo& info, StageInputKind kind, uint32_t location, uint32_t components,
|
||||
std::string name, bool per_vertex = false) {
|
||||
const auto input = std::find_if(info.inputs.begin(), info.inputs.end(), [=](const auto& value) {
|
||||
@@ -34,38 +40,38 @@ void AddOutput(ShaderInfo& info, StageOutputKind kind, uint32_t index, uint32_t
|
||||
}
|
||||
}
|
||||
|
||||
bool ValidateOptions(const Program& program, const ShaderInfoOptions& options, std::string* error) {
|
||||
auto Fail = [&](const char* message) {
|
||||
if (error != nullptr) {
|
||||
*error = message;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
void ValidateOptions(const Program& program, const ShaderInfoOptions& options) {
|
||||
switch (program.stage) {
|
||||
case ShaderType::Vertex:
|
||||
if (options.vertex == nullptr) {
|
||||
return Fail("vertex shader has no input metadata");
|
||||
}
|
||||
if (options.vertex->resources_num < 0 ||
|
||||
options.vertex->resources_num > ShaderVertexInputInfo::RES_MAX) {
|
||||
return Fail("vertex resource count is out of range");
|
||||
}
|
||||
return true;
|
||||
return;
|
||||
case ShaderType::Pixel:
|
||||
return options.pixel->input_num <= std::size(options.pixel->interpolator_settings) ||
|
||||
Fail("pixel input count is out of range");
|
||||
if (options.pixel == nullptr) {
|
||||
return Fail("pixel shader has no input metadata");
|
||||
}
|
||||
if (options.pixel->input_num > std::size(options.pixel->interpolator_settings)) {
|
||||
return Fail("pixel input count is out of range");
|
||||
}
|
||||
return;
|
||||
case ShaderType::Compute:
|
||||
return (options.compute->thread_ids_num >= 0 && options.compute->thread_ids_num <= 3) ||
|
||||
Fail("compute thread ID count is out of range");
|
||||
if (options.compute == nullptr) {
|
||||
return Fail("compute shader has no input metadata");
|
||||
}
|
||||
if (options.compute->thread_ids_num < 0 || options.compute->thread_ids_num > 3) {
|
||||
return Fail("compute thread ID count is out of range");
|
||||
}
|
||||
return;
|
||||
default: return Fail("unsupported shader stage for info collection");
|
||||
}
|
||||
}
|
||||
|
||||
bool ValidateValueReferences(const Program& program, const ShaderInfoOptions& options,
|
||||
std::string* error) {
|
||||
const auto Fail = [&](const char* message) {
|
||||
if (error != nullptr) {
|
||||
*error = message;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
void ValidateValueReferences(const Program& program, const ShaderInfoOptions& options) {
|
||||
for (const auto* block: program.blocks) {
|
||||
for (const auto& inst: *block) {
|
||||
switch (inst.GetOpcode()) {
|
||||
@@ -148,7 +154,6 @@ bool ValidateValueReferences(const Program& program, const ShaderInfoOptions& op
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void CollectVertexInputs(const Program& program, const ShaderVertexInputInfo* vertex,
|
||||
@@ -269,14 +274,8 @@ void CollectBuiltinInputs(const Program& program, ShaderInfo& info) {
|
||||
}
|
||||
}
|
||||
|
||||
bool CollectOutputs(const Program& program, const ShaderVertexInputInfo* vertex,
|
||||
const ShaderPixelInputInfo* pixel, ShaderInfo& info, std::string* error) {
|
||||
auto Fail = [&](std::string message) {
|
||||
if (error != nullptr) {
|
||||
*error = std::move(message);
|
||||
}
|
||||
return false;
|
||||
};
|
||||
void CollectOutputs(const Program& program, const ShaderVertexInputInfo* vertex,
|
||||
const ShaderPixelInputInfo* pixel, ShaderInfo& info) {
|
||||
for (const auto* block: program.blocks) {
|
||||
for (const auto& inst: *block) {
|
||||
if (inst.GetOpcode() != ValueOpcode::SetAttribute) {
|
||||
@@ -347,31 +346,23 @@ bool CollectOutputs(const Program& program, const ShaderVertexInputInfo* vertex,
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool CollectShaderInfo(Program& program, const ShaderInfoOptions& options, std::string* error) {
|
||||
void CollectShaderInfo(Program& program, const ShaderInfoOptions& options) {
|
||||
if (!program.resource_tracking_complete || program.shader_info_complete) {
|
||||
if (error != nullptr) {
|
||||
*error = !program.resource_tracking_complete ? "shader resources were not tracked"
|
||||
: "shader info already collected";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (!ValidateOptions(program, options, error)) {
|
||||
return false;
|
||||
}
|
||||
if (!ValidateValueReferences(program, options, error)) {
|
||||
return false;
|
||||
return Fail(!program.resource_tracking_complete ? "shader resources were not tracked"
|
||||
: "shader info already collected");
|
||||
}
|
||||
ValidateOptions(program, options);
|
||||
ValidateValueReferences(program, options);
|
||||
|
||||
auto next = program.info;
|
||||
next.inputs.clear();
|
||||
next.outputs.clear();
|
||||
next.has_bitwise_xor = std::any_of(
|
||||
program.blocks.begin(), program.blocks.end(), [](const auto* block) {
|
||||
next.has_bitwise_xor =
|
||||
std::any_of(program.blocks.begin(), program.blocks.end(), [](const auto* block) {
|
||||
return std::any_of(block->begin(), block->end(), [](const auto& inst) {
|
||||
return inst.GetOpcode() == ValueOpcode::BitwiseXor32;
|
||||
});
|
||||
@@ -380,15 +371,12 @@ bool CollectShaderInfo(Program& program, const ShaderInfoOptions& options, std::
|
||||
case ShaderType::Vertex: CollectVertexInputs(program, options.vertex, next); break;
|
||||
case ShaderType::Pixel: CollectPixelInputs(program, options.pixel, next); break;
|
||||
case ShaderType::Compute: CollectComputeInputs(options.compute, next); break;
|
||||
default: return false;
|
||||
default: return Fail("unsupported shader stage for info collection");
|
||||
}
|
||||
CollectBuiltinInputs(program, next);
|
||||
if (!CollectOutputs(program, options.vertex, options.pixel, next, error)) {
|
||||
return false;
|
||||
}
|
||||
CollectOutputs(program, options.vertex, options.pixel, next);
|
||||
program.info = std::move(next);
|
||||
program.shader_info_complete = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::IR
|
||||
@@ -13,7 +13,7 @@ struct ShaderInfoOptions {
|
||||
|
||||
// Completes the immutable shader interface after resource tracking. On failure Program::info and
|
||||
// all completion state remain unchanged.
|
||||
bool CollectShaderInfo(Program& program, const ShaderInfoOptions& options, std::string* error);
|
||||
void CollectShaderInfo(Program& program, const ShaderInfoOptions& options);
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::IR
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#include "graphics/shader/recompiler/ir/passes/SrtWalker.h"
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "graphics/shader/recompiler/ir/ShaderIR.h"
|
||||
#include "graphics/shader/shader.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <bit>
|
||||
@@ -120,10 +120,10 @@ class RuntimeValidator {
|
||||
public:
|
||||
explicit RuntimeValidator(const Program& program): m_program(program) {}
|
||||
|
||||
bool Run(Value value, std::string& reason) { return Validate(value, reason); }
|
||||
bool Run(Value value) { return Validate(value); }
|
||||
|
||||
private:
|
||||
bool Validate(Value value, std::string& reason) {
|
||||
bool Validate(Value value) {
|
||||
value = value.Resolve();
|
||||
const auto* inst = value.TryInstruction();
|
||||
if (inst == nullptr) {
|
||||
@@ -133,14 +133,10 @@ private:
|
||||
case Type::U16:
|
||||
case Type::U32:
|
||||
case Type::U64: return true;
|
||||
default:
|
||||
reason = fmt::format("contains a non-integer immediate of type {}",
|
||||
TypeName(value.GetType()));
|
||||
return false;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
if (!m_visiting.insert(inst).second) {
|
||||
reason = fmt::format("contains a cyclic {} value", ValueOpcodeName(inst->GetOpcode()));
|
||||
return false;
|
||||
}
|
||||
const auto finish = [&](bool valid) {
|
||||
@@ -151,25 +147,21 @@ private:
|
||||
if (op == ValueOpcode::UndefU1 || op == ValueOpcode::UndefU8 ||
|
||||
op == ValueOpcode::UndefU16 || op == ValueOpcode::UndefU32 ||
|
||||
op == ValueOpcode::UndefU64 || op == ValueOpcode::Void) {
|
||||
reason = fmt::format("contains {}", ValueOpcodeName(op));
|
||||
return finish(false);
|
||||
}
|
||||
if (op == ValueOpcode::GetUserData) {
|
||||
if (inst->NumArgs() != 1 || inst->Arg(0).GetType() != Type::ScalarReg) {
|
||||
reason = "contains a malformed GetUserData";
|
||||
return finish(false);
|
||||
}
|
||||
const auto reg = RegIndex(inst->Arg(0).ScalarRegister());
|
||||
if (reg < m_program.user_data_base ||
|
||||
reg - m_program.user_data_base >= m_program.user_data_count) {
|
||||
reason = fmt::format("references unavailable user SGPR {}", reg);
|
||||
return finish(false);
|
||||
}
|
||||
return finish(true);
|
||||
}
|
||||
if (op == ValueOpcode::GetShaderBase) {
|
||||
if (inst->NumArgs() != 0) {
|
||||
reason = "contains a malformed GetShaderBase";
|
||||
return finish(false);
|
||||
}
|
||||
return finish(true);
|
||||
@@ -177,14 +169,12 @@ private:
|
||||
if (op == ValueOpcode::Phi) {
|
||||
const auto invariant = ResolveInvariantPhi(m_program, value);
|
||||
if (invariant.IsEmpty()) {
|
||||
reason = "contains a control-dependent phi";
|
||||
return finish(false);
|
||||
}
|
||||
return finish(Validate(invariant, reason));
|
||||
return finish(Validate(invariant));
|
||||
}
|
||||
if (op == ValueOpcode::GetSrtResource) {
|
||||
if (inst->NumArgs() != 0) {
|
||||
reason = "contains a malformed GetSrtResource";
|
||||
return finish(false);
|
||||
}
|
||||
return finish(true);
|
||||
@@ -196,7 +186,6 @@ private:
|
||||
ValueOpcode::GetSrtResource ||
|
||||
!slot.IsImmediate() || slot.GetType() != Type::U32 ||
|
||||
slot.U32() >= m_program.srt_reads.size()) {
|
||||
reason = "contains a malformed flattened SRT read";
|
||||
return finish(false);
|
||||
}
|
||||
} else if (op == ValueOpcode::LoadAddressU32 || op == ValueOpcode::ReadConstBuffer) {
|
||||
@@ -206,13 +195,11 @@ private:
|
||||
const auto* handle = inst->NumArgs() != 0 ? inst->Arg(0).ResolveInstruction() : nullptr;
|
||||
if (!IsRawRead(m_program, *inst) || handle == nullptr ||
|
||||
handle->GetOpcode() != expected) {
|
||||
reason = fmt::format("contains a non-scalar {}", ValueOpcodeName(op));
|
||||
return finish(false);
|
||||
}
|
||||
} else if (op == ValueOpcode::CompositeExtractU64) {
|
||||
const auto index = inst->NumArgs() == 2 ? inst->Arg(1).Resolve() : Value {};
|
||||
if (!index.IsImmediate() || index.GetType() != Type::U32 || index.U32() >= 2u) {
|
||||
reason = "contains an invalid U64 component index";
|
||||
return finish(false);
|
||||
}
|
||||
} else if (op == ValueOpcode::CompositeExtractU32x2) {
|
||||
@@ -222,7 +209,6 @@ private:
|
||||
index.U32() >= 2u ||
|
||||
(source->GetOpcode() != ValueOpcode::CompositeConstructU32x2 &&
|
||||
source->GetOpcode() != ValueOpcode::IAddCarry32)) {
|
||||
reason = "contains an unsupported composite runtime source";
|
||||
return finish(false);
|
||||
}
|
||||
}
|
||||
@@ -233,17 +219,14 @@ private:
|
||||
: op == ValueOpcode::GetSamplerResource ? 4u
|
||||
: 2u;
|
||||
if (inst->NumArgs() != expected) {
|
||||
reason = fmt::format("contains a malformed {}", ValueOpcodeName(op));
|
||||
return finish(false);
|
||||
}
|
||||
} else if (op != ValueOpcode::ReadConst && op != ValueOpcode::ReadConstBuffer &&
|
||||
op != ValueOpcode::LoadAddressU32 && !IsRuntimeIntegerOp(op)) {
|
||||
reason =
|
||||
fmt::format("contains unsupported or control-dependent {}", ValueOpcodeName(op));
|
||||
return finish(false);
|
||||
}
|
||||
for (size_t index = 0; index < inst->NumArgs(); index++) {
|
||||
if (!Validate(inst->Arg(index), reason)) {
|
||||
if (!Validate(inst->Arg(index))) {
|
||||
return finish(false);
|
||||
}
|
||||
}
|
||||
@@ -258,7 +241,7 @@ class PlanBuilder {
|
||||
public:
|
||||
explicit PlanBuilder(Program& program): m_program(program) {}
|
||||
|
||||
bool Run(std::string* error) {
|
||||
void Run() {
|
||||
m_program.srt_reads.clear();
|
||||
m_program.dynamic_reads.clear();
|
||||
for (auto* block: m_program.blocks) {
|
||||
@@ -273,34 +256,29 @@ public:
|
||||
(op == ValueOpcode::ReadConstBuffer &&
|
||||
kind == ResourceKind::ScalarAddress);
|
||||
if (crosswired) {
|
||||
return Fail(flags.pc, error,
|
||||
fmt::format("{} has incompatible scalar memory metadata",
|
||||
ValueOpcodeName(op)));
|
||||
Fail(flags.pc,
|
||||
fmt::format("{} has incompatible scalar memory metadata",
|
||||
ValueOpcodeName(op)));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (IsDescriptorHandle(inst.GetOpcode())) {
|
||||
for (size_t index = 0; index < inst.NumArgs(); index++) {
|
||||
if (!Collect(inst.Arg(index), 0, error)) {
|
||||
return false;
|
||||
}
|
||||
Collect(inst.Arg(index), 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (auto* block: m_program.blocks) {
|
||||
for (auto& inst: *block) {
|
||||
std::string reason;
|
||||
if (inst.GetOpcode() == ValueOpcode::LoadAddressU32 && IsRawRead(m_program, inst) &&
|
||||
inst.Arg(1).Resolve().IsImmediate() &&
|
||||
ValidateRuntimeValue(m_program, Value(&inst), reason) &&
|
||||
!Collect(Value(&inst), inst.Flags<MemoryFlags>().pc, error)) {
|
||||
return false;
|
||||
ValidateRuntimeValue(m_program, Value(&inst))) {
|
||||
Collect(Value(&inst), inst.Flags<MemoryFlags>().pc);
|
||||
}
|
||||
}
|
||||
}
|
||||
PatchReads();
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -310,18 +288,20 @@ private:
|
||||
bool keep = false;
|
||||
};
|
||||
|
||||
bool Fail(uint32_t pc, std::string* error, const std::string& message) const {
|
||||
return ShaderError::Fail(error, Diagnostic(m_program, pc, message));
|
||||
[[noreturn]] void Fail(uint32_t pc, const std::string& message) const {
|
||||
const auto diagnostic = Diagnostic(m_program, pc, message);
|
||||
EXIT("shader SRT planning failed: %s", diagnostic.c_str());
|
||||
std::abort();
|
||||
}
|
||||
|
||||
bool Collect(Value value, uint32_t use_pc, std::string* error) {
|
||||
void Collect(Value value, uint32_t use_pc) {
|
||||
value = value.Resolve();
|
||||
if (value.IsImmediate()) {
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
auto* inst = value.TryInstruction();
|
||||
if (inst == nullptr) {
|
||||
return Fail(use_pc, error, "invalid typed planning value");
|
||||
Fail(use_pc, "invalid typed planning value");
|
||||
}
|
||||
const auto cycle = std::ranges::find(m_visiting, inst);
|
||||
if (cycle != m_visiting.end()) {
|
||||
@@ -329,44 +309,40 @@ private:
|
||||
return value->GetOpcode() == ValueOpcode::Phi;
|
||||
});
|
||||
if (contains_phi) {
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
return Fail(use_pc, error,
|
||||
fmt::format("cyclic typed planning value {} without a phi",
|
||||
ValueOpcodeName(inst->GetOpcode())));
|
||||
Fail(use_pc, fmt::format("cyclic typed planning value {} without a phi",
|
||||
ValueOpcodeName(inst->GetOpcode())));
|
||||
}
|
||||
if (std::ranges::find(m_visited, inst) != m_visited.end()) {
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
m_visiting.push_back(inst);
|
||||
for (size_t index = 0; index < inst->NumArgs(); index++) {
|
||||
if (!Collect(inst->Arg(index), use_pc, error)) {
|
||||
return false;
|
||||
}
|
||||
Collect(inst->Arg(index), use_pc);
|
||||
}
|
||||
m_visiting.pop_back();
|
||||
m_visited.push_back(inst);
|
||||
if (!IsRawRead(m_program, *inst)) {
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
const auto offset = inst->Arg(1).Resolve();
|
||||
if (!offset.IsImmediate() || offset.GetType() != Type::U32) {
|
||||
if (std::ranges::find(m_program.dynamic_reads, value) == m_program.dynamic_reads.end()) {
|
||||
if (std::ranges::find(m_program.dynamic_reads, value) ==
|
||||
m_program.dynamic_reads.end()) {
|
||||
m_program.dynamic_reads.push_back(value);
|
||||
}
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
use_pc = inst->Flags<MemoryFlags>().pc;
|
||||
for (uint32_t slot = 0; slot < m_program.srt_reads.size(); slot++) {
|
||||
if (EquivalentValue(m_program, value, m_program.srt_reads[slot].value)) {
|
||||
m_patches.push_back({inst, slot, false});
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
const auto slot = static_cast<uint32_t>(m_program.srt_reads.size());
|
||||
m_program.srt_reads.push_back({value, slot, use_pc});
|
||||
m_program.srt_reads.push_back({value, slot});
|
||||
m_patches.push_back({inst, slot, true});
|
||||
return true;
|
||||
}
|
||||
|
||||
void PatchReads() {
|
||||
@@ -411,14 +387,12 @@ class Evaluator {
|
||||
public:
|
||||
Evaluator(const Program& program, const SrtRuntime& runtime,
|
||||
std::span<const uint8_t> clean_flat_slots = {}, Evaluator* clean_evaluator = nullptr)
|
||||
: m_program(program), m_runtime(runtime),
|
||||
m_clean_flat_slots(clean_flat_slots), m_clean_evaluator(clean_evaluator) {}
|
||||
: m_program(program), m_runtime(runtime), m_clean_flat_slots(clean_flat_slots),
|
||||
m_clean_evaluator(clean_evaluator) {}
|
||||
|
||||
void SetUsePc(uint32_t pc) { m_use_pc = pc; }
|
||||
|
||||
bool Evaluate(Value value, uint32_t& result, std::string* error) {
|
||||
bool Evaluate(Value value, uint32_t& result) {
|
||||
uint64_t wide = 0;
|
||||
if (!EvaluateWide(value, wide, error)) {
|
||||
if (!EvaluateWide(value, wide)) {
|
||||
return false;
|
||||
}
|
||||
result = static_cast<uint32_t>(wide);
|
||||
@@ -426,11 +400,7 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
bool Fail(std::string* error, const std::string& message) const {
|
||||
return ShaderError::Fail(error, Diagnostic(m_program, m_use_pc, message));
|
||||
}
|
||||
|
||||
bool EvaluateWide(Value value, uint64_t& result, std::string* error) {
|
||||
bool EvaluateWide(Value value, uint64_t& result) {
|
||||
value = value.Resolve();
|
||||
if (value.IsImmediate()) {
|
||||
switch (value.GetType()) {
|
||||
@@ -439,23 +409,23 @@ private:
|
||||
case Type::U16: result = value.U16(); return true;
|
||||
case Type::U32: result = value.U32(); return true;
|
||||
case Type::U64: result = value.U64(); return true;
|
||||
default: return Fail(error, "non-integer immediate in runtime expression");
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
auto* inst = value.TryInstruction();
|
||||
if (inst == nullptr) {
|
||||
return Fail(error, "invalid typed runtime value");
|
||||
return false;
|
||||
}
|
||||
if (const auto found = m_cache.find(inst); found != m_cache.end()) {
|
||||
result = found->second;
|
||||
return true;
|
||||
}
|
||||
if (std::ranges::find(m_visiting, inst) != m_visiting.end()) {
|
||||
return Fail(error, "cyclic typed runtime value");
|
||||
return false;
|
||||
}
|
||||
m_visiting.push_back(inst);
|
||||
uint64_t out = 0;
|
||||
if (!EvaluateInst(*inst, out, error)) {
|
||||
if (!EvaluateInst(*inst, out)) {
|
||||
return false;
|
||||
}
|
||||
m_visiting.pop_back();
|
||||
@@ -464,28 +434,27 @@ private:
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Arg(const Inst& inst, size_t index, uint64_t& result, std::string* error) {
|
||||
return EvaluateWide(inst.Arg(index), result, error);
|
||||
bool Arg(const Inst& inst, size_t index, uint64_t& result) {
|
||||
return EvaluateWide(inst.Arg(index), result);
|
||||
}
|
||||
|
||||
bool EvaluatePhi(const Inst& inst, uint64_t& result, std::string* error) {
|
||||
bool EvaluatePhi(const Inst& inst, uint64_t& result) {
|
||||
const auto value = ResolveInvariantPhi(m_program, Value(const_cast<Inst*>(&inst)));
|
||||
return !value.IsEmpty() ? EvaluateWide(value, result, error)
|
||||
: Fail(error, "typed phi has runtime-dependent values");
|
||||
return !value.IsEmpty() && EvaluateWide(value, result);
|
||||
}
|
||||
|
||||
bool EvaluateExtract(const Inst& inst, uint64_t& result, std::string* error) {
|
||||
bool EvaluateExtract(const Inst& inst, uint64_t& result) {
|
||||
const auto index = inst.Arg(1).Resolve();
|
||||
if (!index.IsImmediate() || index.GetType() != Type::U32) {
|
||||
return Fail(error, "dynamic composite extract in runtime expression");
|
||||
return false;
|
||||
}
|
||||
const auto component = index.U32();
|
||||
if (component >= 2u) {
|
||||
return Fail(error, "unsupported composite runtime source");
|
||||
return false;
|
||||
}
|
||||
if (inst.GetOpcode() == ValueOpcode::CompositeExtractU64) {
|
||||
uint64_t packed = 0;
|
||||
if (!Arg(inst, 0, packed, error)) {
|
||||
if (!Arg(inst, 0, packed)) {
|
||||
return false;
|
||||
}
|
||||
result = static_cast<uint32_t>(packed >> (component * 32u));
|
||||
@@ -493,15 +462,15 @@ private:
|
||||
}
|
||||
const auto* source = inst.Arg(0).ResolveInstruction();
|
||||
if (source == nullptr) {
|
||||
return Fail(error, "unsupported composite runtime source");
|
||||
return false;
|
||||
}
|
||||
if (source->GetOpcode() == ValueOpcode::CompositeConstructU32x2) {
|
||||
return EvaluateWide(source->Arg(component), result, error);
|
||||
return EvaluateWide(source->Arg(component), result);
|
||||
}
|
||||
if (source->GetOpcode() == ValueOpcode::IAddCarry32) {
|
||||
uint64_t lhs = 0;
|
||||
uint64_t rhs = 0;
|
||||
if (!Arg(*source, 0, lhs, error) || !Arg(*source, 1, rhs, error)) {
|
||||
if (!Arg(*source, 0, lhs) || !Arg(*source, 1, rhs)) {
|
||||
return false;
|
||||
}
|
||||
const auto sum =
|
||||
@@ -510,24 +479,23 @@ private:
|
||||
component == 0u ? static_cast<uint32_t>(sum) : static_cast<uint32_t>(sum >> 32u);
|
||||
return true;
|
||||
}
|
||||
return Fail(error, "unsupported composite runtime source");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool EvaluateRawRead(const Inst& inst, uint64_t& result, std::string* error) {
|
||||
bool EvaluateRawRead(const Inst& inst, uint64_t& result) {
|
||||
const auto flags = inst.Flags<MemoryFlags>();
|
||||
if (flags.index >= m_program.memory_info.size()) {
|
||||
return Fail(error, "raw scalar read has invalid metadata");
|
||||
return false;
|
||||
}
|
||||
const auto& mem = m_program.memory_info[flags.index];
|
||||
const auto* handle = inst.Arg(0).ResolveInstruction();
|
||||
if (handle == nullptr) {
|
||||
return Fail(error, "raw scalar read has no descriptor handle");
|
||||
return false;
|
||||
}
|
||||
uint64_t low = 0;
|
||||
uint64_t high = 0;
|
||||
uint64_t offset = 0;
|
||||
if (!Arg(*handle, 0, low, error) || !Arg(*handle, 1, high, error) ||
|
||||
!Arg(inst, 1, offset, error)) {
|
||||
if (!Arg(*handle, 0, low) || !Arg(*handle, 1, high) || !Arg(inst, 1, offset)) {
|
||||
return false;
|
||||
}
|
||||
const auto base = ((high << 32u) | static_cast<uint32_t>(low)) & AddressMask;
|
||||
@@ -536,12 +504,11 @@ private:
|
||||
if (inst.GetOpcode() == ValueOpcode::ReadConstBuffer) {
|
||||
uint64_t records = 0;
|
||||
uint64_t word3 = 0;
|
||||
if (handle->NumArgs() != 4u || !Arg(*handle, 2, records, error) ||
|
||||
!Arg(*handle, 3, word3, error)) {
|
||||
return Fail(error, "constant-buffer descriptor has invalid width");
|
||||
if (handle->NumArgs() != 4u || !Arg(*handle, 2, records) || !Arg(*handle, 3, word3)) {
|
||||
return false;
|
||||
}
|
||||
if (immediate < 0) {
|
||||
return Fail(error, "constant-buffer read has a negative immediate offset");
|
||||
return false;
|
||||
}
|
||||
const auto byte_offset =
|
||||
static_cast<uint64_t>(immediate) + static_cast<uint32_t>(offset);
|
||||
@@ -551,21 +518,20 @@ private:
|
||||
? static_cast<uint64_t>(static_cast<uint32_t>(records))
|
||||
: static_cast<uint64_t>(stride) * static_cast<uint32_t>(records);
|
||||
if (aligned > size || size - aligned < sizeof(uint32_t)) {
|
||||
return Fail(
|
||||
error, fmt::format("constant-buffer offset {} exceeds size {}", aligned, size));
|
||||
return false;
|
||||
}
|
||||
address = ((base & ~uint64_t {3}) + byte_offset) & ~uint64_t {3};
|
||||
} else {
|
||||
const auto relative = (immediate & ~int64_t {3}) +
|
||||
static_cast<int64_t>(static_cast<uint32_t>(offset) & ~3u);
|
||||
if (!AddSignedAddress(base & ~uint64_t {3}, relative, address)) {
|
||||
return Fail(error, "raw scalar read is outside the 48-bit address space");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
uint32_t word = 0;
|
||||
if (m_runtime.read_memory != nullptr) {
|
||||
if (!m_runtime.read_memory(m_runtime.userdata, address, &word)) {
|
||||
return Fail(error, fmt::format("constant read failed at 0x{:016x}", address));
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
std::memcpy(&word, reinterpret_cast<const void*>(address), sizeof(word));
|
||||
@@ -574,28 +540,28 @@ private:
|
||||
return true;
|
||||
}
|
||||
|
||||
bool EvaluateInst(const Inst& inst, uint64_t& result, std::string* error) {
|
||||
bool EvaluateInst(const Inst& inst, uint64_t& result) {
|
||||
uint64_t a = 0;
|
||||
uint64_t b = 0;
|
||||
uint64_t c = 0;
|
||||
const auto binary = [&]() { return Arg(inst, 0, a, error) && Arg(inst, 1, b, error); };
|
||||
const auto binary = [&]() { return Arg(inst, 0, a) && Arg(inst, 1, b); };
|
||||
const auto ternary = [&]() {
|
||||
return Arg(inst, 0, a, error) && Arg(inst, 1, b, error) && Arg(inst, 2, c, error);
|
||||
return Arg(inst, 0, a) && Arg(inst, 1, b) && Arg(inst, 2, c);
|
||||
};
|
||||
switch (inst.GetOpcode()) {
|
||||
case ValueOpcode::GetUserData: {
|
||||
const auto reg = RegIndex(inst.Arg(0).ScalarRegister());
|
||||
if (reg < m_program.user_data_base ||
|
||||
reg - m_program.user_data_base >= m_runtime.user_data.size()) {
|
||||
return Fail(error, fmt::format("user SGPR {} is unavailable", reg));
|
||||
return false;
|
||||
}
|
||||
result = m_runtime.user_data[reg - m_program.user_data_base];
|
||||
return true;
|
||||
}
|
||||
case ValueOpcode::GetShaderBase: result = m_runtime.shader_base; return true;
|
||||
case ValueOpcode::Phi: return EvaluatePhi(inst, result, error);
|
||||
case ValueOpcode::Phi: return EvaluatePhi(inst, result);
|
||||
case ValueOpcode::CompositeExtractU64:
|
||||
case ValueOpcode::CompositeExtractU32x2: return EvaluateExtract(inst, result, error);
|
||||
case ValueOpcode::CompositeExtractU32x2: return EvaluateExtract(inst, result);
|
||||
case ValueOpcode::CompositeConstructU64:
|
||||
if (!binary()) {
|
||||
return false;
|
||||
@@ -607,20 +573,19 @@ private:
|
||||
const auto slot = inst.Arg(1).Resolve();
|
||||
if (!slot.IsImmediate() || slot.GetType() != Type::U32 ||
|
||||
slot.U32() >= m_program.srt_reads.size()) {
|
||||
return Fail(error, "invalid flattened SRT index");
|
||||
return false;
|
||||
}
|
||||
if (slot.U32() < m_clean_flat_slots.size() &&
|
||||
m_clean_flat_slots[slot.U32()] != 0u && m_clean_evaluator != nullptr) {
|
||||
m_clean_evaluator->SetUsePc(m_use_pc);
|
||||
return m_clean_evaluator->EvaluateWide(m_program.srt_reads[slot.U32()].value,
|
||||
result, error);
|
||||
result);
|
||||
}
|
||||
return EvaluateWide(m_program.srt_reads[slot.U32()].value, result, error);
|
||||
return EvaluateWide(m_program.srt_reads[slot.U32()].value, result);
|
||||
}
|
||||
case ValueOpcode::LoadAddressU32:
|
||||
case ValueOpcode::ReadConstBuffer:
|
||||
if (IsRawRead(m_program, inst)) {
|
||||
return EvaluateRawRead(inst, result, error);
|
||||
return EvaluateRawRead(inst, result);
|
||||
}
|
||||
break;
|
||||
case ValueOpcode::IAdd32:
|
||||
@@ -690,7 +655,7 @@ private:
|
||||
}
|
||||
return false;
|
||||
case ValueOpcode::BitwiseNot32:
|
||||
if (Arg(inst, 0, a, error)) {
|
||||
if (Arg(inst, 0, a)) {
|
||||
result = ~static_cast<uint32_t>(a);
|
||||
return true;
|
||||
}
|
||||
@@ -737,7 +702,7 @@ private:
|
||||
const auto offset = static_cast<uint32_t>(b);
|
||||
const auto width = static_cast<uint32_t>(c);
|
||||
if (offset > 32u || width > 32u - offset) {
|
||||
return Fail(error, "invalid unsigned bit-field range");
|
||||
return false;
|
||||
}
|
||||
const auto mask = width == 32u ? UINT32_MAX
|
||||
: width == 0u ? 0u
|
||||
@@ -751,7 +716,7 @@ private:
|
||||
const auto offset = static_cast<uint32_t>(b);
|
||||
const auto width = static_cast<uint32_t>(c);
|
||||
if (offset > 32u || width > 32u - offset) {
|
||||
return Fail(error, "invalid signed bit-field range");
|
||||
return false;
|
||||
}
|
||||
if (width == 0u) {
|
||||
result = 0;
|
||||
@@ -768,13 +733,13 @@ private:
|
||||
return false;
|
||||
case ValueOpcode::BitFieldInsert: {
|
||||
uint64_t d = 0;
|
||||
if (!ternary() || !Arg(inst, 3, d, error)) {
|
||||
if (!ternary() || !Arg(inst, 3, d)) {
|
||||
return false;
|
||||
}
|
||||
const auto offset = static_cast<uint32_t>(c);
|
||||
const auto width = static_cast<uint32_t>(d);
|
||||
if (offset > 32u || width > 32u - offset) {
|
||||
return Fail(error, "invalid inserted bit-field range");
|
||||
return false;
|
||||
}
|
||||
if (width == 0u) {
|
||||
result = static_cast<uint32_t>(a);
|
||||
@@ -836,7 +801,7 @@ private:
|
||||
}
|
||||
return false;
|
||||
case ValueOpcode::LogicalNot:
|
||||
if (Arg(inst, 0, a, error)) {
|
||||
if (Arg(inst, 0, a)) {
|
||||
result = a == 0u;
|
||||
return true;
|
||||
}
|
||||
@@ -845,18 +810,16 @@ private:
|
||||
case ValueOpcode::UndefU8:
|
||||
case ValueOpcode::UndefU16:
|
||||
case ValueOpcode::UndefU32:
|
||||
case ValueOpcode::UndefU64: return Fail(error, "undefined typed runtime value");
|
||||
case ValueOpcode::UndefU64: return false;
|
||||
default: break;
|
||||
}
|
||||
return Fail(error, fmt::format("unsupported typed runtime opcode {}",
|
||||
ValueOpcodeName(inst.GetOpcode())));
|
||||
return false;
|
||||
}
|
||||
|
||||
const Program& m_program;
|
||||
const SrtRuntime& m_runtime;
|
||||
std::span<const uint8_t> m_clean_flat_slots;
|
||||
Evaluator* m_clean_evaluator = nullptr;
|
||||
uint32_t m_use_pc = 0;
|
||||
std::unordered_map<const Inst*, uint64_t> m_cache;
|
||||
std::vector<const Inst*> m_visiting;
|
||||
};
|
||||
@@ -872,18 +835,12 @@ bool EvaluateRuntimeSourcesImpl(const Program& program
|
||||
std::span<const DescriptorSourceRequest> requests,
|
||||
const SrtRuntime& runtime, std::vector<DescriptorValue>& results,
|
||||
std::vector<uint32_t>& flat, bool evaluate_flat,
|
||||
std::span<const uint8_t> clean_flat_slots, std::string* error) {
|
||||
std::span<const uint8_t> clean_flat_slots) {
|
||||
if (!program.srt_plan_complete) {
|
||||
if (error != nullptr) {
|
||||
*error = Diagnostic(program, 0, "typed SRT plan is not ready");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (std::ranges::any_of(clean_flat_slots, [](uint8_t clean) { return clean != 0u; }) &&
|
||||
runtime.read_specialization_memory == nullptr) {
|
||||
if (error != nullptr) {
|
||||
*error = Diagnostic(program, 0, "clean flattened SRT read has no memory reader");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
SrtRuntime clean_runtime = runtime;
|
||||
@@ -895,17 +852,12 @@ bool EvaluateRuntimeSourcesImpl(const Program& program
|
||||
for (const auto& request: requests) {
|
||||
const auto* source = Source(program, request.source);
|
||||
if (source == nullptr) {
|
||||
if (error != nullptr) {
|
||||
*error = Diagnostic(program, request.use_pc,
|
||||
fmt::format("invalid descriptor source {}", request.source));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
evaluator.SetUsePc(request.use_pc);
|
||||
DescriptorValue value;
|
||||
value.dword_count = source->dword_count;
|
||||
for (uint32_t index = 0; index < source->dword_count; index++) {
|
||||
if (!evaluator.Evaluate(source->dwords[index], value.dwords[index], error)) {
|
||||
if (!evaluator.Evaluate(source->dwords[index], value.dwords[index])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -918,9 +870,8 @@ bool EvaluateRuntimeSourcesImpl(const Program& program
|
||||
const bool clean = read.flat_offset < clean_flat_slots.size() &&
|
||||
clean_flat_slots[read.flat_offset] != 0u;
|
||||
auto& selected = clean ? clean_evaluator : evaluator;
|
||||
selected.SetUsePc(read.use_pc);
|
||||
if (read.flat_offset >= flattened.size() ||
|
||||
!selected.Evaluate(read.value, flattened[read.flat_offset], error)) {
|
||||
!selected.Evaluate(read.value, flattened[read.flat_offset])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -934,31 +885,24 @@ bool EvaluateRuntimeSourcesImpl(const Program& program
|
||||
|
||||
} // namespace
|
||||
|
||||
bool ValidateRuntimeValue(const Program& program, Value value, std::string& reason) {
|
||||
return RuntimeValidator(program).Run(value, reason);
|
||||
bool ValidateRuntimeValue(const Program& program, Value value) {
|
||||
return RuntimeValidator(program).Run(value);
|
||||
}
|
||||
|
||||
bool BuildSrtPlan(Program& program, std::string* error) {
|
||||
void BuildSrtPlan(Program& program) {
|
||||
if (program.resource_tracking_complete) {
|
||||
if (error != nullptr) {
|
||||
*error = "cannot rebuild SRT after resource tracking";
|
||||
}
|
||||
return false;
|
||||
EXIT("shader SRT planning failed: cannot rebuild SRT after resource tracking");
|
||||
}
|
||||
program.srt_plan_complete = false;
|
||||
if (!PlanBuilder(program).Run(error)) {
|
||||
return false;
|
||||
}
|
||||
PlanBuilder(program).Run();
|
||||
program.srt_plan_complete = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool EvaluateDescriptorSource(const Program& program, uint32_t source, uint32_t use_pc,
|
||||
const SrtRuntime& runtime, DescriptorValue& result,
|
||||
std::string* error) {
|
||||
const DescriptorSourceRequest request {source, use_pc};
|
||||
bool EvaluateDescriptorSource(const Program& program, uint32_t source, const SrtRuntime& runtime,
|
||||
DescriptorValue& result) {
|
||||
const DescriptorSourceRequest request {source};
|
||||
std::vector<DescriptorValue> results;
|
||||
if (!EvaluateDescriptorSources(program, std::span {&request, 1}, runtime, results, error)) {
|
||||
if (!EvaluateDescriptorSources(program, std::span {&request, 1}, runtime, results)) {
|
||||
return false;
|
||||
}
|
||||
result = results.front();
|
||||
@@ -967,26 +911,23 @@ bool EvaluateDescriptorSource(const Program& program, uint32_t source, uint32_t
|
||||
|
||||
bool EvaluateDescriptorSources(const Program& program,
|
||||
std::span<const DescriptorSourceRequest> requests,
|
||||
const SrtRuntime& runtime, std::vector<DescriptorValue>& results,
|
||||
std::string* error) {
|
||||
const SrtRuntime& runtime, std::vector<DescriptorValue>& results) {
|
||||
std::vector<uint32_t> ignored;
|
||||
return EvaluateRuntimeSourcesImpl(program, requests, runtime, results, ignored, false, {},
|
||||
error);
|
||||
return EvaluateRuntimeSourcesImpl(program, requests, runtime, results, ignored, false, {});
|
||||
}
|
||||
|
||||
bool EvaluateRuntimeSources(const Program& program,
|
||||
std::span<const DescriptorSourceRequest> requests,
|
||||
const SrtRuntime& runtime, std::vector<DescriptorValue>& results,
|
||||
std::vector<uint32_t>& flat, std::span<const uint8_t> clean_flat_slots,
|
||||
std::string* error) {
|
||||
std::vector<uint32_t>& flat,
|
||||
std::span<const uint8_t> clean_flat_slots) {
|
||||
return EvaluateRuntimeSourcesImpl(program, requests, runtime, results, flat, true,
|
||||
clean_flat_slots, error);
|
||||
clean_flat_slots);
|
||||
}
|
||||
|
||||
bool WalkSrt(const Program& program, const SrtRuntime& runtime, std::vector<uint32_t>& flat,
|
||||
std::string* error) {
|
||||
bool WalkSrt(const Program& program, const SrtRuntime& runtime, std::vector<uint32_t>& flat) {
|
||||
std::vector<DescriptorValue> ignored;
|
||||
return EvaluateRuntimeSources(program, {}, runtime, ignored, flat, {}, error);
|
||||
return EvaluateRuntimeSources(program, {}, runtime, ignored, flat, {});
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::IR
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
#include "graphics/shader/recompiler/ir/ShaderIR.h"
|
||||
|
||||
#include <optional>
|
||||
#include <span>
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::IR {
|
||||
@@ -14,44 +13,38 @@ using SrtMemoryReader = bool (*)(void* userdata, uint64_t address, uint32_t* val
|
||||
|
||||
struct SrtRuntime {
|
||||
std::span<const uint32_t> user_data;
|
||||
uint64_t shader_base = 0;
|
||||
SrtMemoryReader read_memory = nullptr;
|
||||
void* userdata = nullptr;
|
||||
std::optional<uint64_t> flat_memory_base;
|
||||
uint64_t shader_base = 0;
|
||||
SrtMemoryReader read_memory = nullptr;
|
||||
void* userdata = nullptr;
|
||||
SrtMemoryReader read_specialization_memory = nullptr;
|
||||
};
|
||||
|
||||
struct DescriptorSourceRequest {
|
||||
uint32_t source = 0;
|
||||
uint32_t use_pc = 0;
|
||||
};
|
||||
|
||||
// Collects reachable ReadConst values. Immediate offsets receive compact flat-buffer slots;
|
||||
// dynamic offsets remain explicit and are never assigned a fake slot.
|
||||
bool BuildSrtPlan(Program& program, std::string* error);
|
||||
bool ValidateRuntimeValue(const Program& program, Value value, std::string& reason);
|
||||
void BuildSrtPlan(Program& program);
|
||||
bool ValidateRuntimeValue(const Program& program, Value value);
|
||||
|
||||
bool EvaluateDescriptorSource(const Program& program, uint32_t source, uint32_t use_pc,
|
||||
const SrtRuntime& runtime, DescriptorValue& result,
|
||||
std::string* error);
|
||||
bool EvaluateDescriptorSource(const Program& program, uint32_t source, const SrtRuntime& runtime,
|
||||
DescriptorValue& result);
|
||||
|
||||
// Evaluates one runtime snapshot transactionally. Scalar values and ReadConst results shared by
|
||||
// several descriptors are memoized once across the batch.
|
||||
bool EvaluateDescriptorSources(const Program& program,
|
||||
std::span<const DescriptorSourceRequest> requests,
|
||||
const SrtRuntime& runtime, std::vector<DescriptorValue>& results,
|
||||
std::string* error);
|
||||
const SrtRuntime& runtime, std::vector<DescriptorValue>& results);
|
||||
|
||||
// Evaluates descriptor sources and the flattened immediate SRT with one memoized scalar walk.
|
||||
// On failure neither destination is changed.
|
||||
bool EvaluateRuntimeSources(const Program& program,
|
||||
std::span<const DescriptorSourceRequest> requests,
|
||||
const SrtRuntime& runtime, std::vector<DescriptorValue>& results,
|
||||
std::vector<uint32_t>& flat, std::span<const uint8_t> clean_flat_slots,
|
||||
std::string* error);
|
||||
std::vector<uint32_t>& flat, std::span<const uint8_t> clean_flat_slots);
|
||||
|
||||
bool WalkSrt(const Program& program, const SrtRuntime& runtime, std::vector<uint32_t>& flat,
|
||||
std::string* error);
|
||||
bool WalkSrt(const Program& program, const SrtRuntime& runtime, std::vector<uint32_t>& flat);
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::IR
|
||||
|
||||
|
||||
Loaded 100 of 142 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user