Compare commits

..
Author SHA1 Message Date
nmzik af8aecfdde shader: remove recompiler error-string propagation 2026-08-31 05:31:41 +04:00
13533bd053 cli: add --gpu <index> to select a Vulkan physical deviceFeature gpu select (#417)
* cli: add --gpu <index> to select a Vulkan physical device

Select the Vulkan physical device by enumeration index, defaulting to
automatic selection (-1). Invalid or out-of-range indices fall back to
automatic selection with a clear message, and the selected device is
logged.

Co-Authored-By: Claude <noreply@anthropic.com>

* cli: reject invalid --gpu index, translate comments, apply clang-format

An invalid --gpu index now prints an error and exits with code 1 (usage),
matching --user-id / --present-mode / --console-language. The automatic
fallback is kept only for out-of-range indices detected at runtime.
Comments translated to English and the touched files brought in line
with clang-format (pre-commit v22.1.3).

Co-Authored-By: Claude <noreply@anthropic.com>

* cli: simplify Vulkan device selection

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: nmzik <Nmzik@mail.ru>
2026-08-31 03:43:17 +04:00
Mehmet Cambazandnmzik cd0f0a428a graphics: quiet two misleading render diagnostics (#416)
* graphics: quiet two misleading render diagnostics

The three PM4 register-offset normalization messages logged once per occurrence with no
limit. They are printed in the colour reserved for problems, and normalizing the offset is
routine, so a short session buries the log in them: PAC-MAN WORLD 2 Re-PAC (PPSA18189)
emitted 24969 of them in about a minute, a fifth of a megabyte and 1.9% of every line
written. Rate-limit all three the way the surrounding diagnostics in these files already
are, which keeps the first occurrences without drowning what follows.

The EQAA check warned whenever a control register was set, including on single-sample
targets where those controls do nothing. That title never multisamples -- all 128 of its
render targets are samples=1 -- yet it still produced the warning, pointing at a cause that
could not apply. Return early when multisampling is off, matching how the neighbouring
sample-location check already reads msaa_num_samples.

Log output only; no rendering behaviour changes.

* graphics: silence normalized offset diagnostics

---------

Co-authored-by: nmzik <Nmzik@mail.ru>
2026-08-31 03:33:32 +04:00
nomolao2-cellandnmzik 6758d67942 shader: preserve raw VCC bits in S_MOV_B64 (#418)
* shader: preserve raw VCC bits in S_MOV_B64

* fix(shader): centralize scalar U64 mask provenance

---------

Co-authored-by: nmzik <Nmzik@mail.ru>
2026-08-31 03:04:16 +04:00
nmzik dc5b7c19a6 Revert "fix(agc): honor top bound for single-ended buffers"
This reverts commit 1469c53048.
2026-08-31 01:24:55 +04:00
nmzik 2a54306e8b agc: implement async command buffer jump 2026-08-30 23:55:05 +04:00
nmzik 7a6aaafe52 agc: expose async copy and wait packet sizes 2026-08-30 23:49:08 +04:00
nmzik 1469c53048 fix(agc): honor top bound for single-ended buffers 2026-08-30 23:39:18 +04:00
GodsWill Devandnmzik 9992ab18fc shader: preserve untouched half in VOP1 B16 writes (#370)
shader: preserve native 16-bit destinations

Co-authored-by: nmzik <Nmzik@mail.ru>
2026-08-30 19:33:26 +04:00
Brandon Strongandnmzik 18af08460f shader: take the high half per lane in V_PK_FMAC_F16 (#404)
Co-authored-by: nmzik <Nmzik@mail.ru>
2026-08-30 15:08:22 +04:00
nmzik 39ee9972b8 shader: V_CMPX_CLASS_F32 2026-08-30 14:32:07 +04:00
YuhiAida e500333bcd fix(agc): implement AgcDcbCopyDataGetSize (#410)
Register b5u0Jzm8TF8 for SpongeBob PPSA26893 using the native zero-argument, uint64_t size-helper ABI.

Adapted from PR #410.
2026-08-30 13:59:16 +04:00
nmzik 4498d75b9b add agc ver 13 2026-08-30 13:29:49 +04:00
Brandon Strong 1df53b169f kernel: stop sign-extending batch-map protection and type (#405) 2026-08-30 01:46:43 +04:00
Mehmet Cambazandnmzik ff61c1cecd shader: implement V_SIN_F16 (#399)
Co-authored-by: nmzik <Nmzik@mail.ru>
2026-08-30 01:32:49 +04:00
nmzik 60a01817ce loader: add support for etaHEN cheats 2026-08-29 20:48:05 +04:00
nmzik 1a87cfedb5 shader: implement V_COS_F16 2026-08-29 19:23:19 +04:00
nmzikandTechx3 5bd447d2a2 shader: implement DS_BPERMUTE_B32
Co-authored-by: Techx3 <56268269+Techx3@users.noreply.github.com>
2026-08-29 19:23:19 +04:00
Rogelio J Gomesandnmzik a4fc58e908 input: PC-style DualSense mouse/keyboard mapping (#213)
input: add mouse-to-right-stick mapping and sensitivity

Co-authored-by: nmzik <Nmzik@mail.ru>
2026-08-29 17:27:23 +04:00
nacimouallaandnmzik 4e8e335b7b macOS: full support — MoltenVK auto-discovery, portability fix, and KytyPS5.app bundle (#394)
* macOS: auto-discover bundled MoltenVK on startup

On macOS the Vulkan loader comes from MoltenVK. SDL's default
probe only searches system paths, so a bundled libMoltenVK.dylib
next to the executable is invisible under the hardened runtime.

Probe it explicitly via SDL_GetBasePath() before falling back to
SDL's default search. Respect SDL_VULKAN_LIBRARY overrides and
log which loader is used. Update README to reflect that no
environment variable is required for bundled builds.

Co-Authored-By: internal-model

* window: fix Vulkan portability enumeration on macOS

MoltenVK requires VK_KHR_portability_enumeration and
VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR to surface
portability devices via enumeratePhysicalDevices. Without the flag
the adapter is hidden on some driver versions, causing
"Could not find suitable device" failures.

Add the instance extension and flag when available, and add
diagnostic logs for all MoltenVK fallbacks (depthClipEnable,
colorWriteEnable, depthBounds, fragmentShaderBarycentric)
so black-screen diagnostics are actionable.

* macOS: add KytyPS5.app bundle packaging

Add a proper macOS application bundle when the launcher is built.
launcher becomes KytyPS5.app (CFBundleIdentifier org.kytyps5.kytyps5)
with KytyPS5 as the executable, and kyty_emulator is installed as a
helper inside Contents/MacOS so QCoreApplication::applicationDirPath()
finds it. MoltenVK is auto-discovered via SDL_GetBasePath() in that
directory, so no SDL_VULKAN_LIBRARY is required.

- src/launcher/Info.plist.in: minimal bundle plist (version from
  PROJECT_VERSION, LSMinimumSystemVersion 13.0).
- src/launcher/CMakeLists.txt: set MACOSX_BUNDLE, BUNDLE DESTINATION,
  install helper, re-sign helper with JIT entitlements, and invoke
  macdeployqt when available. Keep flat kyty_emulator for CLI.
- .github/workflows/build.yml: bundle MoltenVK into
  KytyPS5.app/Contents/MacOS when present, verify bundle artifacts
  (file/lipo/codesign), and handle bundle paths in the release zip.
- README.md: document bundle double-click and MoltenVK locations.

* launcher: fix bundle rpath for Qt frameworks

The bundle executable had @rpath references to Qt frameworks but no
LC_RPATH, causing dyld to abort at launch:

  Library not loaded: @rpath/QtConcurrent.framework
  Reason: no LC_RPATH's found

Set INSTALL_RPATH to @executable_path/../Frameworks and
BUILD_WITH_INSTALL_RPATH so the built and installed bundles
resolve Qt from Contents/Frameworks. Verified via otool -l
(LC_RPATH now present) and macdeployqt now succeeds without
"Cannot resolve rpath" errors.

* launcher: add Import game button

Add a prominent Import button to ConfigurationListWidget that lets
users select a game folder (containing eboot.bin) or a folder
containing multiple games via QFileDialog. Handles:

- Single game folder selected: adds its parent to game_dirs (Scan
  searches subdirectories, so parent is needed)
- Folder with multiple games: adds the selected folder
- Deduplication via PathKey/NormalizeGameDirectories, writes
  settings, rescans, and selects the imported game if found
- Shows info/warning dialogs when no games are found

UI: QToolButton with SP_DirOpenIcon and text Import beside the
existing toolbar, styled with a blue background for visibility.
Logic in ConfigurationListWidget::import_game().

* macOS: fix bundled loader and artifact packaging

* Revert "launcher: add Import game button"

This reverts commit 2f670ef95c.

---------

Co-authored-by: nmzik <Nmzik@mail.ru>
2026-08-29 17:08:54 +04:00
Mehmet Cambaz 947576363e kernel: explain a commit-limit failure at startup (#395)
When CreateFileMapping cannot reserve the guest backing store the log only
carries the raw Win32 error, which gives no clue that the paging file is the
thing to change. Print the size that was asked for and say so directly when the
failure is ERROR_NO_SYSTEM_RESOURCES or ERROR_COMMITMENT_LIMIT.
2026-08-29 16:20:36 +04:00
85 changed files with 4064 additions and 3822 deletions

No files matched your search

+47 -22
View File
@@ -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'
+22 -7
View File
@@ -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
+4
View File
@@ -47,6 +47,10 @@ PresentMode GetPresentMode() {
return g_config->present_mode;
}
int32_t GetGpuIndex() {
return g_config->gpu_index;
}
bool FullscreenEnabled() {
return g_config->fullscreen_enabled;
}
+2
View File
@@ -48,6 +48,7 @@ struct ConfigOptions {
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;
@@ -80,6 +81,7 @@ uint32_t GetScreenHeight();
const std::string& GetUserName();
int32_t GetUserId();
PresentMode GetPresentMode();
int32_t GetGpuIndex();
bool FullscreenEnabled();
uint32_t GetVblankFrequency();
uint32_t GetConsoleLanguage();
@@ -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;
}
+7 -2
View File
@@ -827,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) {
@@ -1192,7 +1197,7 @@ void hw_check(const CommandBuffer& 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");
@@ -895,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;
+120 -9
View File
@@ -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
+7 -3
View File
@@ -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
@@ -157,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;
@@ -942,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();
+29 -4
View File
@@ -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"
@@ -40,13 +41,12 @@
#include "loader/systemContent.h"
#include <cstdlib>
#include <fmt/format.h>
#include <memory>
#include <string>
#include <vector>
#include <vulkan/vk_platform.h>
#include <fmt/format.h>
#define STB_IMAGE_IMPLEMENTATION
#define STBI_NO_SIMD
#include "stb_image.h"
@@ -507,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;
}
@@ -783,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());
}
@@ -812,6 +819,24 @@ static void WindowCreate(WindowContext& context) {
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.
@@ -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,27 +656,20 @@ bool TryRecompile(std::span<const uint32_t> code, const CompileOptions& options,
runtime.read_memory = ReadZeroMemory;
}
runtime.userdata = options.read_memory_data;
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);
@@ -694,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
@@ -39,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;
}
@@ -91,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)];
@@ -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,7 +143,7 @@ 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:
@@ -147,29 +151,29 @@ bool ValidateNativeProgram(const IR::Program& program, std::string* error) {
break;
}
if (inst.NumArgs() != 2 || !program.info.uses_dma) {
return Fail(error, "typed address handle has invalid DMA metadata");
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,12 +181,11 @@ 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; };
@@ -192,30 +195,30 @@ bool AnalyzeProgramRequirements(IR::Program& program, std::string* error) {
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) {
return Fail(error, "writable FLAT/GLOBAL addresses require GPU ownership tracking");
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;
}
@@ -225,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;
@@ -266,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;
@@ -282,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) {
@@ -295,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;
@@ -336,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,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;
@@ -449,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 };
@@ -613,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);
@@ -864,7 +861,7 @@ 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);
@@ -30,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;
@@ -1181,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;
@@ -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,6 @@ 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) {
@@ -630,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;
}
@@ -107,6 +107,7 @@ constexpr MemoryOpcodeInfo DS_OPCODE_LIST[] = {
{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},
};
@@ -195,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;
@@ -239,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;
}
@@ -276,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;
}
@@ -314,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;
}
@@ -362,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;
}
}
@@ -395,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,
@@ -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:
@@ -179,6 +179,8 @@ 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,
@@ -349,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,
@@ -492,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,
@@ -628,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;
@@ -699,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;
}
@@ -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) {
@@ -320,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) {
@@ -878,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:
@@ -1036,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:
@@ -1067,12 +1078,7 @@ bool Translator::EmitMemory(const Decoder::Instruction& inst, std::string* error
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_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);
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;
@@ -271,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;
@@ -365,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);
@@ -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();
+94 -94
View File
@@ -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) {
+1 -2
View File
@@ -477,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;
};
@@ -523,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,44 +60,36 @@ 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());
@@ -107,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);
}
@@ -146,14 +136,7 @@ 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.uses_dma) {
@@ -178,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;
}
}
@@ -281,35 +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()) {
if (error != nullptr) {
*error = "resource snapshot does not match dense shader topology";
}
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;
}
}
@@ -317,19 +284,12 @@ 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;
}
}
@@ -345,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;
}
}
@@ -403,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)) {
@@ -419,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;
@@ -434,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;
@@ -445,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 ||
@@ -463,49 +385,25 @@ 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;
}
}
@@ -514,11 +412,8 @@ bool ValidateResourceSpecialization(const Program& program, const ResourceSnapsh
}
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;
}
@@ -527,17 +422,12 @@ bool MaterializeResources(const Program& program, const SrtRuntime& runtime,
requests.reserve(program.info.buffers.size() + program.info.images.size() * 2u +
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),
@@ -545,16 +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});
}
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;
}
@@ -581,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) {
@@ -608,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] =
@@ -649,31 +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());
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;
@@ -681,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());
@@ -720,10 +594,7 @@ 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();
@@ -734,10 +605,8 @@ bool SpecializeResources(Program& program, ResourceSnapshot& snapshot, std::stri
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;
@@ -755,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;
@@ -803,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;
@@ -823,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++) {
@@ -849,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));
}
}
}
@@ -867,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) {
@@ -891,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];
@@ -915,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 &&
@@ -937,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
@@ -22,19 +22,17 @@ struct ResourceSnapshot {
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,8 +1,8 @@
#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>
@@ -12,7 +12,7 @@
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) {
@@ -101,19 +101,17 @@ public:
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();
@@ -145,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:
@@ -173,18 +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();
}
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++) {
@@ -199,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;
}
}
@@ -286,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);
@@ -469,38 +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 ValidateAddressHandle(Value value, uint32_t pc, std::string* error) const {
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");
}
return true;
}
uint32_t AddBuffer(uint32_t source, const MemoryInfo& memory, ValueOpcode op, uint32_t pc) {
@@ -540,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 &&
@@ -593,155 +579,139 @@ private:
return static_cast<uint32_t>(m_info.samplers.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;
}
if (!ValidateAddressHandle(inst.Arg(0), flags.pc, error)) {
return false;
return;
}
ValidateAddressHandle(inst.Arg(0), flags.pc);
m_info.uses_dma = true;
return 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 {
@@ -783,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
@@ -13,43 +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;
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
@@ -22,6 +22,9 @@ struct ScalarMaskTag {
struct ExecTag {
auto operator<=>(const ExecTag&) const = default;
};
struct ExecMaskTag {
auto operator<=>(const ExecMaskTag&) const = default;
};
struct ExecLoTag {
auto operator<=>(const ExecLoTag&) const = default;
};
@@ -31,6 +34,9 @@ struct ExecHiTag {
struct VccTag {
auto operator<=>(const VccTag&) const = default;
};
struct VccMaskValidTag {
auto operator<=>(const VccMaskValidTag&) const = default;
};
struct VccLoTag {
auto operator<=>(const VccLoTag&) const = default;
};
@@ -45,9 +51,9 @@ struct GotoVariable {
auto operator<=>(const GotoVariable&) const = default;
};
using Variable =
std::variant<ScalarReg, ThreadBitScalarReg, ScalarMaskTag, VectorReg, GotoVariable, SccTag,
ExecTag, ExecLoTag, ExecHiTag, VccTag, VccLoTag, VccHiTag, M0Tag>;
using Variable = std::variant<ScalarReg, ThreadBitScalarReg, ScalarMaskTag, VectorReg, GotoVariable,
SccTag, ExecTag, ExecMaskTag, ExecLoTag, ExecHiTag, VccTag,
VccMaskValidTag, VccLoTag, VccHiTag, M0Tag>;
using ValueMap = std::unordered_map<Block*, Value>;
struct DefTable {
@@ -99,12 +105,16 @@ struct DefTable {
void Set(Block* block, SccTag, Value value) { scc[block] = value; }
const Value& Get(Block* block, ExecTag) { return exec[block]; }
void Set(Block* block, ExecTag, Value value) { exec[block] = value; }
const Value& Get(Block* block, ExecMaskTag) { return exec_mask_tag[block]; }
void Set(Block* block, ExecMaskTag, Value value) { exec_mask_tag[block] = value; }
const Value& Get(Block* block, ExecLoTag) { return exec_lo[block]; }
void Set(Block* block, ExecLoTag, Value value) { exec_lo[block] = value; }
const Value& Get(Block* block, ExecHiTag) { return exec_hi[block]; }
void Set(Block* block, ExecHiTag, Value value) { exec_hi[block] = value; }
const Value& Get(Block* block, VccTag) { return vcc[block]; }
void Set(Block* block, VccTag, Value value) { vcc[block] = value; }
const Value& Get(Block* block, VccMaskValidTag) { return vcc_mask_valid_tag[block]; }
void Set(Block* block, VccMaskValidTag, Value value) { vcc_mask_valid_tag[block] = value; }
const Value& Get(Block* block, VccLoTag) { return vcc_lo[block]; }
void Set(Block* block, VccLoTag, Value value) { vcc_lo[block] = value; }
const Value& Get(Block* block, VccHiTag) { return vcc_hi[block]; }
@@ -114,9 +124,11 @@ struct DefTable {
ValueMap scc;
ValueMap exec;
ValueMap exec_mask_tag;
ValueMap exec_lo;
ValueMap exec_hi;
ValueMap vcc;
ValueMap vcc_mask_valid_tag;
ValueMap vcc_lo;
ValueMap vcc_hi;
ValueMap m0;
@@ -144,6 +156,9 @@ ValueOpcode UndefOpcode(SccTag) {
ValueOpcode UndefOpcode(ExecTag) {
return ValueOpcode::UndefU1;
}
ValueOpcode UndefOpcode(ExecMaskTag) {
return ValueOpcode::UndefU1;
}
ValueOpcode UndefOpcode(ExecLoTag) {
return ValueOpcode::UndefU32;
}
@@ -153,6 +168,9 @@ ValueOpcode UndefOpcode(ExecHiTag) {
ValueOpcode UndefOpcode(VccTag) {
return ValueOpcode::UndefU1;
}
ValueOpcode UndefOpcode(VccMaskValidTag) {
return ValueOpcode::UndefU1;
}
ValueOpcode UndefOpcode(VccLoTag) {
return ValueOpcode::UndefU32;
}
@@ -184,6 +202,9 @@ Value InitialValue(SccTag) {
Value InitialValue(ExecTag) {
return Value(false);
}
Value InitialValue(ExecMaskTag) {
return Value(false);
}
Value InitialValue(ExecLoTag) {
return Value(0u);
}
@@ -193,6 +214,9 @@ Value InitialValue(ExecHiTag) {
Value InitialValue(VccTag) {
return Value(false);
}
Value InitialValue(VccMaskValidTag) {
return Value(false);
}
Value InitialValue(VccLoTag) {
return Value(0u);
}
@@ -351,9 +375,15 @@ void VisitInstruction(Pass& pass, Block* block, Inst& inst) {
break;
case ValueOpcode::SetScc: pass.Write(SccTag {}, block, inst.Arg(0)); break;
case ValueOpcode::SetExec: pass.Write(ExecTag {}, block, inst.Arg(0)); break;
case ValueOpcode::SetExecMaskTag:
pass.Write(ExecMaskTag {}, block, inst.Arg(0));
break;
case ValueOpcode::SetExecLo: pass.Write(ExecLoTag {}, block, inst.Arg(0)); break;
case ValueOpcode::SetExecHi: pass.Write(ExecHiTag {}, block, inst.Arg(0)); break;
case ValueOpcode::SetVcc: pass.Write(VccTag {}, block, inst.Arg(0)); break;
case ValueOpcode::SetVccMaskValidTag:
pass.Write(VccMaskValidTag {}, block, inst.Arg(0));
break;
case ValueOpcode::SetVccLo: pass.Write(VccLoTag {}, block, inst.Arg(0)); break;
case ValueOpcode::SetVccHi: pass.Write(VccHiTag {}, block, inst.Arg(0)); break;
case ValueOpcode::SetM0: pass.Write(M0Tag {}, block, inst.Arg(0)); break;
@@ -375,9 +405,15 @@ void VisitInstruction(Pass& pass, Block* block, Inst& inst) {
break;
case ValueOpcode::GetScc: inst.ReplaceUsesWith(pass.Read(SccTag {}, block)); break;
case ValueOpcode::GetExec: inst.ReplaceUsesWith(pass.Read(ExecTag {}, block)); break;
case ValueOpcode::GetExecMaskTag:
inst.ReplaceUsesWith(pass.Read(ExecMaskTag {}, block));
break;
case ValueOpcode::GetExecLo: inst.ReplaceUsesWith(pass.Read(ExecLoTag {}, block)); break;
case ValueOpcode::GetExecHi: inst.ReplaceUsesWith(pass.Read(ExecHiTag {}, block)); break;
case ValueOpcode::GetVcc: inst.ReplaceUsesWith(pass.Read(VccTag {}, block)); break;
case ValueOpcode::GetVccMaskValidTag:
inst.ReplaceUsesWith(pass.Read(VccMaskValidTag {}, block));
break;
case ValueOpcode::GetVccLo: inst.ReplaceUsesWith(pass.Read(VccLoTag {}, block)); break;
case ValueOpcode::GetVccHi: inst.ReplaceUsesWith(pass.Read(VccHiTag {}, block)); break;
case ValueOpcode::GetM0: inst.ReplaceUsesWith(pass.Read(M0Tag {}, block)); break;
+11 -18
View File
@@ -841,23 +841,22 @@ static void ApplyPixelOutputs(ShaderPixelInputInfo& info,
}
}
static bool LogPermutationMismatch(const ShaderRecompiler::IR::Program& program,
const char* stage, const std::string& error) {
static bool LogPermutationMismatch(const ShaderRecompiler::IR::Program* program,
const char* stage) {
static std::atomic<uint32_t> log_count {0};
if (Config::GraphicsDebugDumpEnabled() &&
log_count.fetch_add(1, std::memory_order_relaxed) < 64) {
LOGF("ShaderProgramCache native runtime mismatch %s shader=0x%016" PRIx64 ": %s\n",
stage, program.shader_hash, error.c_str());
LOGF("ShaderProgramCache native runtime mismatch %s shader=0x%016" PRIx64 "\n",
stage, program != nullptr ? program->shader_hash : 0);
}
return false;
}
bool MaterializeProgram(const std::shared_ptr<const ShaderRecompiler::IR::Program>& program,
const ShaderParams& params, ShaderVertexInputInfo& info) {
std::string error;
if (!ShaderMaterializeStageRuntime(program, params.user_data, params.Base(), info.stage, &error,
if (!ShaderMaterializeStageRuntime(program, params.user_data, params.Base(), info.stage,
ReadShaderGuestMemory)) {
return LogPermutationMismatch(*program, "VS", error);
return LogPermutationMismatch(program.get(), "VS");
}
ApplyVertexOutputs(info, *program);
return true;
@@ -865,10 +864,9 @@ bool MaterializeProgram(const std::shared_ptr<const ShaderRecompiler::IR::Progra
bool MaterializeProgram(const std::shared_ptr<const ShaderRecompiler::IR::Program>& program,
const ShaderParams& params, ShaderPixelInputInfo& info) {
std::string error;
if (!ShaderMaterializeStageRuntime(program, params.user_data, params.Base(), info.stage, &error,
if (!ShaderMaterializeStageRuntime(program, params.user_data, params.Base(), info.stage,
ReadShaderGuestMemory)) {
return LogPermutationMismatch(*program, "PS", error);
return LogPermutationMismatch(program.get(), "PS");
}
ApplyPixelOutputs(info, *program);
return true;
@@ -876,10 +874,9 @@ bool MaterializeProgram(const std::shared_ptr<const ShaderRecompiler::IR::Progra
bool MaterializeProgram(const std::shared_ptr<const ShaderRecompiler::IR::Program>& program,
const ShaderParams& params, ShaderComputeInputInfo& info) {
std::string error;
if (!ShaderMaterializeStageRuntime(program, params.user_data, params.Base(), info.stage, &error,
if (!ShaderMaterializeStageRuntime(program, params.user_data, params.Base(), info.stage,
ReadShaderGuestMemory)) {
return LogPermutationMismatch(*program, "CS", error);
return LogPermutationMismatch(program.get(), "CS");
}
return true;
}
@@ -1252,11 +1249,7 @@ static vk::ShaderModule CompileModule(vk::Device device, const ShaderParams& par
ShaderStageRuntime& stage) {
const auto* stage_name = ShaderStageName(options.stage);
const auto* label = ShaderStageLabel(options.stage);
ShaderRecompiler::CompileResult result;
std::string error;
if (!ShaderRecompiler::TryRecompile(params.code, options, result, &error)) {
ExitShaderRecompilerFailure(label, options.shader_hash, error.c_str());
}
auto result = ShaderRecompiler::Recompile(params.code, options);
DumpShaderRecompilerOriginal(stage_name, options.shader_hash, params.code, result.decoded_dump);
if (!SpirvValidateBinary(label, options.shader_hash, result.spirv)) {
+1 -1
View File
@@ -57,7 +57,7 @@ using ShaderSpecializationMemoryReader = bool (*)(void* userdata, uint64_t addre
bool ShaderMaterializeStageRuntime(
std::shared_ptr<const ShaderRecompiler::IR::Program> program,
std::span<const uint32_t> user_data, uint64_t shader_base, ShaderStageRuntime& stage,
std::string* error, ShaderSpecializationMemoryReader read_specialization_memory = nullptr,
ShaderSpecializationMemoryReader read_specialization_memory = nullptr,
void* read_memory_data = nullptr);
constexpr uint32_t DstSel(uint32_t x, uint32_t y = 0, uint32_t z = 0, uint32_t w = 0) {
+3 -6
View File
@@ -7,13 +7,10 @@ namespace Libs::Graphics {
bool ShaderMaterializeStageRuntime(std::shared_ptr<const ShaderRecompiler::IR::Program> program,
std::span<const uint32_t> user_data, uint64_t shader_base,
ShaderStageRuntime& stage, std::string* error,
ShaderStageRuntime& stage,
ShaderSpecializationMemoryReader read_specialization_memory,
void* read_memory_data) {
if (program == nullptr) {
if (error != nullptr) {
*error = "missing native shader plan";
}
return false;
}
ShaderRecompiler::IR::SrtRuntime runtime;
@@ -22,8 +19,8 @@ bool ShaderMaterializeStageRuntime(std::shared_ptr<const ShaderRecompiler::IR::P
runtime.read_specialization_memory = read_specialization_memory;
runtime.userdata = read_memory_data;
ShaderRecompiler::IR::ResourceSnapshot snapshot;
if (!ShaderRecompiler::IR::MaterializeResources(*program, runtime, snapshot, error) ||
!ShaderRecompiler::IR::ValidateResourceSpecialization(*program, snapshot, error)) {
if (!ShaderRecompiler::IR::MaterializeResources(*program, runtime, snapshot) ||
!ShaderRecompiler::IR::ValidateResourceSpecialization(*program, snapshot)) {
return false;
}
auto resources =
+2 -2
View File
@@ -3804,8 +3804,8 @@ int KYTY_SYSV_ABI KernelBatchMap2(KernelBatchMapEntry* entries, int num_entries,
LOGF("\t [%d] start = %p, offset = 0x%016" PRIx64 ", length = 0x%016" PRIx64
", prot = 0x%02" PRIx32 ", type = 0x%02" PRIx32 ", op = %d\n",
i, entry->start, entry->offset, entry->length,
static_cast<uint32_t>(static_cast<unsigned char>(entry->protection)),
static_cast<uint32_t>(static_cast<unsigned char>(entry->type)), entry->operation);
static_cast<uint32_t>(entry->protection), static_cast<uint32_t>(entry->type),
entry->operation);
if (entry->length == 0 || entry->operation < MAP_OP_MAP_DIRECT ||
entry->operation > MAP_OP_TYPE_PROTECT) {
+7 -7
View File
@@ -47,13 +47,13 @@ struct VirtualQueryInfo {
static_assert(sizeof(VirtualQueryInfo) == 72, "VirtualQueryInfo struct size is incorrect");
struct KernelBatchMapEntry {
void* start;
uint64_t offset;
uint64_t length;
char protection;
char type;
int16_t reserved;
int32_t operation;
void* start;
uint64_t offset;
uint64_t length;
unsigned char protection;
unsigned char type;
int16_t reserved;
int32_t operation;
};
static_assert(sizeof(KernelBatchMapEntry) == 32, "KernelBatchMapEntry struct size is incorrect");
+10 -1
View File
@@ -659,7 +659,16 @@ public:
#endif
ReserveGuestRegions();
m_backing = new GuestBackingStore(backing_size);
EXIT_IF(!m_backing->IsAvailable());
if (!m_backing->IsAvailable()) {
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
EXIT("could not reserve %" PRIu64 " MB for guest direct memory. Windows commits\n"
"this up front, so the paging file is usually what needs to be larger.\n",
backing_size / (1024U * 1024U));
#else
EXIT("could not reserve %" PRIu64 " MB for guest direct memory\n",
backing_size / (1024U * 1024U));
#endif
}
}
~GuestAddressSpace() {
+37 -1
View File
@@ -66,10 +66,46 @@ if(WIN32)
endif()
set(launcher_name "launcher")
if(APPLE)
set(launcher_name "KytyPS5")
endif()
set_target_properties(launcher PROPERTIES OUTPUT_NAME ${launcher_name})
install(TARGETS launcher DESTINATION .)
if(APPLE)
# macOS bundle: KytyPS5.app with launcher as main executable and
# kyty_emulator as helper inside Contents/MacOS. MoltenVK is found
# next to a flat executable or in the bundle's Contents/Frameworks;
# no rpath fixup is needed because SDL loads it by absolute path.
# Qt deployment (Frameworks + plugins) is handled by macdeployqt.
set_target_properties(launcher PROPERTIES
MACOSX_BUNDLE TRUE
MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/Info.plist.in"
INSTALL_RPATH "@executable_path/../Frameworks"
BUILD_WITH_INSTALL_RPATH TRUE
)
endif()
install(TARGETS launcher
BUNDLE DESTINATION .
RUNTIME DESTINATION .)
if(APPLE)
install(TARGETS kyty_emulator DESTINATION "KytyPS5.app/Contents/MacOS")
# Deploy and ad-hoc sign the Qt frameworks and plugins in the bundle.
find_program(KYTY_MACDEPLOYQT NAMES macdeployqt PATHS "${Qt6_DIR}/../../../bin" NO_DEFAULT_PATH)
if(NOT KYTY_MACDEPLOYQT)
message(FATAL_ERROR "Could not find macdeployqt")
endif()
install(CODE "
execute_process(COMMAND \"${KYTY_MACDEPLOYQT}\" \"\${CMAKE_INSTALL_PREFIX}/KytyPS5.app\"
-codesign=-
RESULT_VARIABLE _kyty_deploy_result)
if(NOT _kyty_deploy_result EQUAL 0)
message(FATAL_ERROR \"macdeployqt failed with \${_kyty_deploy_result}\")
endif()
")
endif()
if(WIN32)
install(CODE "
+17
View File
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleName</key><string>KytyPS5</string>
<key>CFBundleDisplayName</key><string>KytyPS5</string>
<key>CFBundleIdentifier</key><string>org.kytyps5.kytyps5</string>
<key>CFBundleVersion</key><string>@PROJECT_VERSION@</string>
<key>CFBundleShortVersionString</key><string>@PROJECT_VERSION@</string>
<key>CFBundlePackageType</key><string>APPL</string>
<key>CFBundleSignature</key><string>????</string>
<key>CFBundleExecutable</key><string>KytyPS5</string>
<key>LSMinimumSystemVersion</key><string>13.0</string>
<key>NSHighResolutionCapable</key><true/>
<key>NSRequiresAquaSystemAppearance</key><false/>
</dict>
</plist>
+6 -4
View File
@@ -5,6 +5,7 @@
#include <QStringList>
class QPushButton;
class QDoubleSpinBox;
class QTreeWidget;
class QTreeWidgetItem;
class QWidget;
@@ -22,10 +23,11 @@ private:
void SetBinding(QTreeWidgetItem* item, const QString& binding);
void UpdateButtons();
QTreeWidget* m_bindings = nullptr;
QPushButton* m_change_button = nullptr;
QPushButton* m_clear_button = nullptr;
bool m_customized = false;
QTreeWidget* m_bindings = nullptr;
QPushButton* m_change_button = nullptr;
QPushButton* m_clear_button = nullptr;
QDoubleSpinBox* m_sensitivity = nullptr;
bool m_custom_bindings = false;
};
#endif /* LAUNCHER_INCLUDE_INPUT_MAPPING_DIALOG_H_ */
+1 -1
View File
@@ -889,7 +889,7 @@ void ConfigurationListWidget::show_context_menu(const QPoint& pos) {
style()->standardIcon(QStyle::SP_FileDialogContentsView), tr("View trophies..."));
connect(action_view_trophies, &QAction::triggered, this,
&ConfigurationListWidget::ViewTrophies);
QAction* action_patches = menu.addAction(tr("Patches (experimental)..."));
QAction* action_patches = menu.addAction(tr("Cheats (experimental)..."));
connect(action_patches, &QAction::triggered, this, [this, item]() {
if (item != nullptr) {
auto* dialog = new PatchesDialog(item->GetInfo(), this);
+57 -19
View File
@@ -2,6 +2,7 @@
#include <QAbstractItemView>
#include <QDialogButtonBox>
#include <QDoubleSpinBox>
#include <QHBoxLayout>
#include <QHash>
#include <QHeaderView>
@@ -16,7 +17,9 @@
namespace {
constexpr int BINDING_COLUMN = 1;
constexpr int BINDING_COLUMN = 1;
constexpr auto DEFAULT_MOUSE_SENSITIVITY = 1.0;
constexpr char MOUSE_SENSITIVITY[] = "MouseSensitivity=";
struct PadControl {
const char* id;
@@ -128,7 +131,8 @@ public:
auto* layout = new QVBoxLayout(this);
m_label = new QLabel(
tr("Press a key or mouse button.\nSpace and F1 are reserved; Esc cancels."), this);
tr("Press a key or mouse button.\nSpace, F1, F7, and F11 are reserved; Esc cancels."),
this);
m_label->setAlignment(Qt::AlignCenter);
layout->addWidget(m_label);
}
@@ -144,7 +148,8 @@ protected:
reject();
return;
}
if (event->key() == Qt::Key_Space || event->key() == Qt::Key_F1) {
if (event->key() == Qt::Key_Space || event->key() == Qt::Key_F1 ||
event->key() == Qt::Key_F7 || event->key() == Qt::Key_F11) {
m_label->setText(tr("That key is reserved by the emulator."));
return;
}
@@ -177,6 +182,9 @@ private:
QHash<QString, QString> ParseMapping(const QStringList& mapping) {
QHash<QString, QString> result;
for (const auto& entry: mapping) {
if (entry.startsWith(QLatin1String(MOUSE_SENSITIVITY))) {
continue;
}
const auto separator = entry.indexOf(QLatin1Char('='));
if (separator > 0 && separator + 1 < entry.size()) {
const auto binding = entry.mid(separator + 1);
@@ -193,15 +201,42 @@ QHash<QString, QString> ParseMapping(const QStringList& mapping) {
return result;
}
double ParseMouseSensitivity(const QStringList& mapping) {
for (const auto& entry: mapping) {
if (entry.startsWith(QLatin1String(MOUSE_SENSITIVITY))) {
return entry.mid(sizeof(MOUSE_SENSITIVITY) - 1).toDouble();
}
}
return DEFAULT_MOUSE_SENSITIVITY;
}
} // namespace
InputMappingDialog::InputMappingDialog(const QStringList& mapping, QWidget* parent)
: QDialog(parent), m_customized(!mapping.isEmpty()) {
: QDialog(parent) {
setWindowTitle(tr("Input Mapping"));
resize(460, 650);
auto* layout = new QVBoxLayout(this);
layout->addWidget(new QLabel(tr("Map keyboard or mouse input to DualSense controls."), this));
layout->addWidget(
new QLabel(tr("Map keyboard or mouse buttons to DualSense controls.\n"
"Press F7 in-game to toggle mouse movement on the right stick."),
this));
const auto parsed = ParseMapping(mapping);
m_custom_bindings = !parsed.isEmpty();
auto* sensitivity_layout = new QHBoxLayout;
sensitivity_layout->addWidget(new QLabel(tr("Mouse sensitivity"), this));
m_sensitivity = new QDoubleSpinBox(this);
m_sensitivity->setRange(0.1, 5.0);
m_sensitivity->setSingleStep(0.1);
m_sensitivity->setDecimals(1);
m_sensitivity->setSuffix(QStringLiteral("x"));
m_sensitivity->setValue(ParseMouseSensitivity(mapping));
sensitivity_layout->addWidget(m_sensitivity);
sensitivity_layout->addStretch();
layout->addLayout(sensitivity_layout);
m_bindings = new QTreeWidget(this);
m_bindings->setColumnCount(2);
@@ -212,13 +247,12 @@ InputMappingDialog::InputMappingDialog(const QStringList& mapping, QWidget* pare
m_bindings->header()->setSectionResizeMode(1, QHeaderView::Stretch);
layout->addWidget(m_bindings);
const auto parsed = ParseMapping(mapping);
for (const auto& control: PAD_CONTROLS) {
auto* item = new QTreeWidgetItem(m_bindings);
item->setText(0, tr(control.label));
item->setData(0, Qt::UserRole, QString::fromLatin1(control.id));
SetBinding(item, m_customized ? parsed.value(QString::fromLatin1(control.id))
: QString::fromLatin1(control.default_binding));
SetBinding(item, m_custom_bindings ? parsed.value(QString::fromLatin1(control.id))
: QString::fromLatin1(control.default_binding));
}
m_bindings->setCurrentItem(m_bindings->topLevelItem(0));
@@ -248,16 +282,19 @@ InputMappingDialog::InputMappingDialog(const QStringList& mapping, QWidget* pare
QStringList InputMappingDialog::Mapping() const {
QStringList result;
if (!m_customized) {
return result;
}
for (int index = 0; index < m_bindings->topLevelItemCount(); index++) {
const auto* item = m_bindings->topLevelItem(index);
const auto binding = item->data(BINDING_COLUMN, Qt::UserRole).toString();
if (!binding.isEmpty()) {
result.append(item->data(0, Qt::UserRole).toString() + QLatin1Char('=') + binding);
if (m_custom_bindings) {
for (int index = 0; index < m_bindings->topLevelItemCount(); index++) {
const auto* item = m_bindings->topLevelItem(index);
const auto binding = item->data(BINDING_COLUMN, Qt::UserRole).toString();
if (!binding.isEmpty()) {
result.append(item->data(0, Qt::UserRole).toString() + QLatin1Char('=') + binding);
}
}
}
if (m_sensitivity->value() != DEFAULT_MOUSE_SENSITIVITY) {
result.append(QLatin1String(MOUSE_SENSITIVITY) +
QString::number(m_sensitivity->value(), 'f', 1));
}
return result;
}
@@ -281,12 +318,12 @@ void InputMappingDialog::ChangeBinding() {
}
}
SetBinding(item, dialog.Binding());
m_customized = true;
m_custom_bindings = true;
}
void InputMappingDialog::ClearBinding() {
SetBinding(m_bindings->currentItem(), {});
m_customized = true;
m_custom_bindings = true;
}
void InputMappingDialog::RestoreDefaults() {
@@ -294,7 +331,8 @@ void InputMappingDialog::RestoreDefaults() {
SetBinding(m_bindings->topLevelItem(index),
QString::fromLatin1(PAD_CONTROLS[index].default_binding));
}
m_customized = false;
m_sensitivity->setValue(DEFAULT_MOUSE_SENSITIVITY);
m_custom_bindings = false;
}
void InputMappingDialog::SetBinding(QTreeWidgetItem* item, const QString& binding) {
+49 -37
View File
@@ -18,7 +18,7 @@
PatchesDialog::PatchesDialog(const Configuration& game, QWidget* parent)
: QDialog(parent), m_title_id(game.title_id.trimmed().toUpper()) {
setAttribute(Qt::WA_DeleteOnClose);
setWindowTitle(tr("Patches (Experimental) - %1").arg(game.name));
setWindowTitle(tr("Cheats (Experimental) - %1").arg(game.name));
resize(640, 480);
auto* layout = new QVBoxLayout(this);
@@ -54,46 +54,50 @@ QString PatchesDialog::PatchPlanPath(const QString& title_id) {
void PatchesDialog::Load() {
QFile file(PatchPlanPath(m_title_id));
if (!file.open(QIODevice::ReadOnly)) {
m_status->setText(tr("No local patch file: %1").arg(file.fileName()));
m_status->setText(tr("No local cheat file: %1").arg(file.fileName()));
m_apply->setEnabled(false);
return;
}
const auto document = QJsonDocument::fromJson(file.readAll());
if (document.isNull() || !document.isObject()) {
m_status->setText(tr("Invalid patch JSON in %1.").arg(file.fileName()));
m_status->setText(tr("Invalid cheat JSON in %1.").arg(file.fileName()));
m_apply->setEnabled(false);
return;
}
const auto root = document.object();
const auto patches = root.value(QStringLiteral("patches")).toArray();
for (const auto& value: patches) {
const auto patch = value.toObject();
auto* item = new QListWidgetItem(patch.value(QStringLiteral("name")).toString(), m_patches);
item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
item->setCheckState(patch.value(QStringLiteral("enabled")).toBool(true) ? Qt::Checked
: Qt::Unchecked);
}
m_apply->setEnabled(!patches.isEmpty());
if (patches.isEmpty()) {
// shadPS4/ETAHen cheat files use "mods"; Kyty only reads a "patches" array.
if (root.contains(QStringLiteral("mods"))) {
m_status->setText(
tr("This file uses a \"mods\" list. Kyty expects a top-level \"patches\" "
"array in %1.")
.arg(file.fileName()));
} else if (!root.contains(QStringLiteral("patches"))) {
m_status->setText(
tr("No \"patches\" array in %1.").arg(file.fileName()));
} else {
m_status->setText(tr("Loaded 0 patch(es) from %1.").arg(file.fileName()));
}
const auto root = document.object();
if (!root.value(QStringLiteral("mods")).isArray()) {
m_status->setText(tr("Cheat JSON has no \"mods\" array in %1.").arg(file.fileName()));
m_apply->setEnabled(false);
return;
}
m_status->setText(tr("Loaded %1 patch(es) from %2.").arg(patches.size()).arg(file.fileName()));
const auto mods = root.value(QStringLiteral("mods")).toArray();
for (const auto& value: mods) {
if (!value.isObject()) {
m_status->setText(tr("Invalid cheat entry in %1.").arg(file.fileName()));
m_apply->setEnabled(false);
m_patches->clear();
return;
}
const auto mod = value.toObject();
const auto name = mod.value(QStringLiteral("name"));
const auto enabled = mod.value(QStringLiteral("enabled"));
if (!name.isString() || name.toString().isEmpty() ||
(!enabled.isUndefined() && !enabled.isBool())) {
m_status->setText(tr("Invalid cheat entry in %1.").arg(file.fileName()));
m_apply->setEnabled(false);
m_patches->clear();
return;
}
auto* item = new QListWidgetItem(name.toString(), m_patches);
item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
item->setCheckState(enabled.toBool(true) ? Qt::Checked : Qt::Unchecked);
}
m_apply->setEnabled(!mods.isEmpty());
m_status->setText(tr("Loaded %1 cheat(s) from %2.").arg(mods.size()).arg(file.fileName()));
}
void PatchesDialog::Save() {
@@ -105,20 +109,28 @@ void PatchesDialog::Save() {
auto document = QJsonDocument::fromJson(input.readAll());
input.close();
auto root = document.object();
auto patches = root.value(QStringLiteral("patches")).toArray();
for (int index = 0; index < patches.size(); index++) {
auto patch = patches[index].toObject();
patch.insert(QStringLiteral("enabled"),
m_patches->item(index)->checkState() == Qt::Checked);
patches[index] = patch;
const auto changed_message = tr("Cheat file changed; reload the dialog.");
if (!document.isObject()) {
m_status->setText(changed_message);
return;
}
root.insert(QStringLiteral("patches"), patches);
auto root = document.object();
auto mods = root.value(QStringLiteral("mods")).toArray();
if (mods.size() != m_patches->count()) {
m_status->setText(changed_message);
return;
}
for (int index = 0; index < mods.size(); index++) {
auto mod = mods[index].toObject();
mod.insert(QStringLiteral("enabled"), m_patches->item(index)->checkState() == Qt::Checked);
mods[index] = mod;
}
root.insert(QStringLiteral("mods"), mods);
document.setObject(root);
QSaveFile output(path);
if (output.open(QIODevice::WriteOnly) && output.write(document.toJson()) >= 0 &&
output.commit()) {
m_status->setText(tr("Patch selection saved."));
m_status->setText(tr("Cheat selection saved."));
}
}
+42 -2
View File
@@ -25,6 +25,7 @@
#include "libs/libs.h"
#include <algorithm>
#include <array>
#include <atomic>
#include <cmath>
#include <cstddef>
@@ -142,8 +143,8 @@ struct RegisterDefaultsStorage {
#include <fmt/format.h>
static constexpr uint32_t GRAPHICS_REGISTER_DEFAULTS_MAX_VERSION = 12;
static constexpr uint32_t GRAPHICS_REGISTER_DEFAULTS_FALLBACK_VERSION = 11;
static constexpr uint32_t GRAPHICS_REGISTER_DEFAULTS_MAX_VERSION = 13;
static constexpr uint32_t GRAPHICS_REGISTER_DEFAULTS_FALLBACK_VERSION = 13;
static constexpr uint32_t GRAPHICS_INIT_NO_FEATURE_STATE = 0;
static std::mutex g_register_defaults_mutex;
@@ -3239,6 +3240,27 @@ uint32_t KYTY_SYSV_ABI AgcAcbCondExecGetSize() {
return AgcDcbCondExecGetSize();
}
uint32_t* KYTY_SYSV_ABI AgcAcbJump(CommandBuffer* buf, uint8_t cache_policy,
const uint32_t* target, uint32_t size_in_dwords) {
if (buf == nullptr) {
return nullptr;
}
auto* cmd = buf->AllocateDW(4);
if (cmd == nullptr) {
return nullptr;
}
auto target_address = reinterpret_cast<uint64_t>(target);
cmd[0] = KYTY_PM4(4, Pm4::IT_INDIRECT_BUFFER, 0u);
cmd[1] = static_cast<uint32_t>(target_address) & ~0x3u;
cmd[2] = static_cast<uint32_t>(target_address >> 32u);
cmd[3] = 0x0f900000u | ((static_cast<uint32_t>(cache_policy) & 0x3u) << 28u) |
(size_in_dwords & 0xfffffu);
return cmd;
}
uint32_t KYTY_SYSV_ABI AgcAcbJumpGetSize() {
return 0x10u;
}
@@ -3250,6 +3272,12 @@ uint32_t* KYTY_SYSV_ABI AgcAcbWaitRegMem(CommandBuffer* buf, uint8_t size, uint8
poll_cycles);
}
uint64_t KYTY_SYSV_ABI AgcAcbWaitOnAddressGetSize(uint8_t size) {
PRINT_NAME();
return AgcDcbWaitOnAddressGetSize(size);
}
uint32_t* KYTY_SYSV_ABI AgcAcbDmaData(CommandBuffer* buf, uint8_t dst, uint8_t dst_cache_policy,
uint64_t dst_address_or_offset, uint8_t src,
uint8_t src_cache_policy,
@@ -3321,6 +3349,12 @@ uint32_t* KYTY_SYSV_ABI AgcAcbCopyData(CommandBuffer* buf, uint8_t dst, uint8_t
return cmd;
}
uint64_t KYTY_SYSV_ABI AgcAcbCopyDataGetSize() {
PRINT_NAME();
return AgcDcbCopyDataGetSize();
}
uint32_t* KYTY_SYSV_ABI AgcAcbDispatchIndirect(CommandBuffer* buf,
const volatile void* indirect_args,
uint32_t modifier) {
@@ -3439,6 +3473,12 @@ uint32_t* KYTY_SYSV_ABI AgcDcbCopyData(CommandBuffer* buf, uint8_t dst, uint8_t
return cmd;
}
uint64_t KYTY_SYSV_ABI AgcDcbCopyDataGetSize() {
PRINT_NAME();
return 6 * sizeof(uint32_t);
}
uint32_t* KYTY_SYSV_ABI AgcDcbDmaData(CommandBuffer* buf, uint8_t engine, uint8_t dst,
uint8_t dst_cache_policy, uint64_t dst_address_or_offset,
uint8_t src, uint8_t src_cache_policy,
+5
View File
@@ -204,10 +204,13 @@ uint32_t KYTY_SYSV_ABI AgcAcbAcquireMemGetSize();
uint32_t* KYTY_SYSV_ABI AgcAcbCondExec(CommandBuffer* buf, const volatile uint32_t* address,
uint32_t num_dwords);
uint32_t KYTY_SYSV_ABI AgcAcbCondExecGetSize();
uint32_t* KYTY_SYSV_ABI AgcAcbJump(CommandBuffer* buf, uint8_t cache_policy,
const uint32_t* target, uint32_t size_in_dwords);
uint32_t KYTY_SYSV_ABI AgcAcbJumpGetSize();
uint32_t* KYTY_SYSV_ABI AgcAcbWaitRegMem(CommandBuffer* buf, uint8_t size, uint8_t compare_function,
uint8_t cache_policy, const volatile void* address,
uint64_t reference, uint64_t mask, uint32_t poll_cycles);
uint64_t KYTY_SYSV_ABI AgcAcbWaitOnAddressGetSize(uint8_t size);
uint32_t* KYTY_SYSV_ABI AgcAcbDmaData(CommandBuffer* buf, uint8_t dst, uint8_t dst_cache_policy,
uint64_t dst_address_or_offset, uint8_t src,
uint8_t src_cache_policy,
@@ -218,6 +221,7 @@ uint32_t* KYTY_SYSV_ABI AgcAcbCopyData(CommandBuffer* buf, uint8_t dst, uint8_t
uint64_t dst_address, uint8_t src, uint8_t src_cache_policy,
uint64_t src_address_or_immediate, uint8_t item_size,
uint8_t write_confirm);
uint64_t KYTY_SYSV_ABI AgcAcbCopyDataGetSize();
uint32_t* KYTY_SYSV_ABI AgcAcbDispatchIndirect(CommandBuffer* buf,
const volatile void* indirect_args,
uint32_t modifier);
@@ -233,6 +237,7 @@ uint32_t* KYTY_SYSV_ABI AgcDcbCopyData(CommandBuffer* buf, uint8_t dst, uint8_t
uint64_t dst_address, uint8_t src, uint8_t src_cache_policy,
uint64_t src_address_or_immediate, uint8_t item_size,
uint8_t write_confirm);
uint64_t KYTY_SYSV_ABI AgcDcbCopyDataGetSize();
uint32_t* KYTY_SYSV_ABI AgcDcbDmaData(CommandBuffer* buf, uint8_t engine, uint8_t dst,
uint8_t dst_cache_policy, uint64_t dst_address_or_offset,
uint8_t src, uint8_t src_cache_policy,
+85 -3
View File
@@ -1,4 +1,4 @@
// No-feature, non-0x840f00 AgcGetRegisterDefaults2 dispatch, versions 0..12.
// No-feature, non-0x840f00 AgcGetRegisterDefaults2 dispatch, versions 0..13.
// Type entries keep the pointer index in bits 0..9; higher bits are AGC flags.
// g_agc_internal_reg_defaults_v0: regs=(1, 12, 0, 4), ptrs=(1, 12, 0, 2), types=15.
@@ -185,6 +185,45 @@ static CompactRegisterDefaults g_agc_internal_reg_defaults_v11 = {
g_agc_internal_reg_defaults_v11_types, 24,
};
// Version 13 takes the version-11 fallback and selects the >=13 state-0 lookup bank.
// Only two internal table-1 values differ in that bank.
static auto g_agc_internal_reg_defaults_v13_tbl1_regs = [] {
constexpr auto count = sizeof(g_agc_internal_reg_defaults_v11_tbl1_regs) /
sizeof(g_agc_internal_reg_defaults_v11_tbl1_regs[0]);
std::array<ShaderRegister, count> regs {};
std::copy_n(g_agc_internal_reg_defaults_v11_tbl1_regs, count, regs.begin());
for (auto& reg: regs) {
if (reg.offset == 0x0081) {
reg.value = 0x00000001;
} else if (reg.offset == 0x0101) {
reg.value = 0x00000003;
}
}
return regs;
}();
static CompactRegisterDefaults g_agc_internal_reg_defaults_v13 = {
g_agc_internal_reg_defaults_v11_tbl0_regs,
8,
g_agc_internal_reg_defaults_v11_tbl0_ptrs,
8,
g_agc_internal_reg_defaults_v13_tbl1_regs.data(),
12,
g_agc_internal_reg_defaults_v11_tbl1_ptrs,
12,
g_agc_internal_reg_defaults_v11_tbl2_regs,
1,
g_agc_internal_reg_defaults_v11_tbl2_ptrs,
1,
g_agc_internal_reg_defaults_v11_tbl3_regs,
6,
g_agc_internal_reg_defaults_v11_tbl3_ptrs,
3,
g_agc_internal_reg_defaults_v11_types,
24,
};
// g_agc_internal_reg_defaults_v4: regs=(2, 15, 0, 4), ptrs=(2, 15, 0, 2), types=19.
static ShaderRegister g_agc_internal_reg_defaults_v4_tbl0_regs[] = {
{0x02af, 0x00040000},
@@ -1366,8 +1405,28 @@ static uint32_t g_agc_public_reg_defaults_v11_types[] = {
0x036ac8a6, 0x000c0452, 0x00000000,
};
template <std::size_t Size>
static std::array<ShaderRegister, Size>
make_v11_plus_public_tbl0(const ShaderRegister (&source)[Size]) {
std::array<ShaderRegister, Size> regs {};
std::copy_n(source, Size, regs.begin());
for (auto& reg: regs) {
if (reg.offset == 0x0201) {
reg.value |= 0x00010000;
}
}
return regs;
}
// The common setup helper forces this table-0 patch for every version >= 11.
static auto g_agc_public_reg_defaults_v11_tbl0_effective =
make_v11_plus_public_tbl0(g_agc_public_reg_defaults_v11_tbl0_regs);
static auto g_agc_public_reg_defaults_v12_tbl0_effective =
make_v11_plus_public_tbl0(g_agc_public_reg_defaults_v10_tbl0_regs);
static CompactRegisterDefaults g_agc_public_reg_defaults_v11 = {
g_agc_public_reg_defaults_v11_tbl0_regs,
g_agc_public_reg_defaults_v11_tbl0_effective.data(),
488,
g_agc_public_reg_defaults_v11_tbl0_ptrs,
84,
@@ -1387,6 +1446,27 @@ static CompactRegisterDefaults g_agc_public_reg_defaults_v11 = {
137,
};
static CompactRegisterDefaults g_agc_public_reg_defaults_v12 = {
g_agc_public_reg_defaults_v12_tbl0_effective.data(),
482,
g_agc_public_reg_defaults_v10_tbl0_ptrs,
79,
g_agc_public_reg_defaults_v10_tbl1_regs,
159,
g_agc_public_reg_defaults_v10_tbl1_ptrs,
29,
g_agc_public_reg_defaults_v10_tbl2_regs,
55,
g_agc_public_reg_defaults_v10_tbl2_ptrs,
20,
nullptr,
0,
nullptr,
0,
g_agc_public_reg_defaults_v10_types,
128,
};
// g_agc_public_reg_defaults_v4: regs=(488, 158, 43, 0), ptrs=(80, 28, 42, 0), types=150.
static ShaderRegister g_agc_public_reg_defaults_v4_tbl0_regs[] = {
{0x0202, 0x00cc0010}, {0x0109, 0x00000008}, {0x0104, 0x00000000}, {0x008f, 0x00000000},
@@ -2825,7 +2905,8 @@ static CompactRegisterDefaults* g_agc_public_reg_defaults_by_version[] = {
&g_agc_public_reg_defaults_v9, // version 9
&g_agc_public_reg_defaults_v10, // version 10
&g_agc_public_reg_defaults_v11, // version 11
&g_agc_public_reg_defaults_v10, // version 12
&g_agc_public_reg_defaults_v12, // version 12
&g_agc_public_reg_defaults_v11, // version 13: v11 fallback with the >=13 state-0 bank
};
static CompactRegisterDefaults* g_agc_internal_reg_defaults_by_version[] = {
@@ -2842,4 +2923,5 @@ static CompactRegisterDefaults* g_agc_internal_reg_defaults_by_version[] = {
&g_agc_internal_reg_defaults_v10, // version 10
&g_agc_internal_reg_defaults_v11, // version 11
&g_agc_internal_reg_defaults_v10, // version 12
&g_agc_internal_reg_defaults_v13, // version 13
};
+19
View File
@@ -61,6 +61,7 @@ public:
void Disconnect(int id);
void Button(int id, uint32_t button, bool down);
void Axis(int id, Axis axis, int value);
void RightStick(int id, int x, int y);
void ResetInputState();
void GetConnectionInfo(bool* flag, int* count);
void SetVibration(uint8_t large_motor, uint8_t small_motor);
@@ -265,6 +266,18 @@ void GameController::Axis(int id, Controller::Axis axis, int value) {
}
}
void GameController::RightStick(int id, int x, int y) {
Common::LockGuard lock(m_mutex);
if (m_active_id == id || id == HOST_INPUT_CONTROLLER_ID) {
auto state = GetLastState();
state.time = LibKernel::KernelGetProcessTime();
state.axes[static_cast<int>(Axis::RightX)] = x;
state.axes[static_cast<int>(Axis::RightY)] = y;
AddState(state);
}
}
void GameController::ResetInputState() {
Common::LockGuard lock(m_mutex);
ControllerState state {};
@@ -371,6 +384,12 @@ void ControllerAxis(int id, Axis axis, int value) {
g_controller->Axis(id, axis, value);
}
void ControllerRightStick(int id, int x, int y) {
EXIT_IF(g_controller == nullptr);
g_controller->RightStick(id, x, y);
}
void ControllerResetInputState() {
EXIT_IF(g_controller == nullptr);
g_controller->ResetInputState();
+1
View File
@@ -58,6 +58,7 @@ void ControllerConnect(int id);
void ControllerDisconnect(int id);
void ControllerButton(int id, uint32_t button, bool down);
void ControllerAxis(int id, Axis axis, int value);
void ControllerRightStick(int id, int x, int y);
void ControllerResetInputState();
int KYTY_SYSV_ABI PadInit();
+4
View File
@@ -73,10 +73,13 @@ LIB_DEFINE(InitAgcDriver_1) {
LIB_FUNC("ewobAQeMo5k", Gen5::AgcAcbAcquireMemGetSize);
LIB_FUNC("qyM2bxYFPAk", Gen5::AgcAcbCondExec);
LIB_FUNC("ozKzBP4aki4", Gen5::AgcAcbCondExecGetSize);
LIB_FUNC("e1DFTg+Sd8U", Gen5::AgcAcbJump);
LIB_FUNC("b-oySn+G2tE", Gen5::AgcAcbJumpGetSize);
LIB_FUNC("htn36gPnBk4", Gen5::AgcAcbWaitRegMem);
LIB_FUNC("idlaArvdXEs", Gen5::AgcAcbWaitOnAddressGetSize);
LIB_FUNC("-RnpfpxIhec", Gen5::AgcAcbDmaData);
LIB_FUNC("qzMN2XKGA4k", Gen5::AgcAcbCopyData);
LIB_FUNC("CbQh3DKMSno", Gen5::AgcAcbCopyDataGetSize);
LIB_FUNC("j3EtxFkSIhQ", Gen5::AgcAcbDispatchIndirect);
LIB_FUNC("eZ4+17OQz4Q", Gen5::AgcAcbWriteData);
LIB_FUNC("xAeBOa0A3kk", Gen5::AgcAcbSetMarker);
@@ -121,6 +124,7 @@ LIB_DEFINE(InitAgcDriver_1) {
LIB_FUNC("57labkp+rSQ", Gen5::AgcDcbAcquireMem);
LIB_FUNC("-vnlTPPXPrw", Gen5::AgcDcbAcquireMemGetSize);
LIB_FUNC("1rZSWUv1IRc", Gen5::AgcDcbCopyData);
LIB_FUNC("b5u0Jzm8TF8", Gen5::AgcDcbCopyDataGetSize); // SpongeBob PPSA26893
LIB_FUNC("WmAc2MEj6Io", Gen5::AgcDcbDmaData);
LIB_FUNC("xSAR0LTcRKM", Gen5::AgcDcbJump);
LIB_FUNC("VEGu4dixjUg", Gen5::AgcDcbJumpGetSize);
+211 -139
View File
@@ -8,207 +8,235 @@
#include "loader/systemContent.h"
#include <algorithm>
#include <charconv>
#include <cstdio>
#include <cstring>
#include <fstream>
#include <memory>
#include <nlohmann/json.hpp>
#include <string>
#include <string_view>
#include <system_error>
#include <utility>
#include <vector>
namespace Loader::GamePatch {
namespace {
constexpr uint64_t kPageSize = 0x4000;
struct Write {
std::string patch_name;
uint64_t address = 0;
std::vector<uint8_t> expected;
std::vector<uint8_t> replacement;
uint64_t source_address = 0;
uint64_t target_address = 0;
std::vector<uint8_t> off;
std::vector<uint8_t> on;
};
struct Plan {
std::string title_id;
std::string game_version;
std::string version;
std::string process;
std::vector<Write> writes;
std::vector<std::string> patch_names;
std::vector<std::string> mod_names;
std::vector<uint64_t> cave_pages;
};
using Json = nlohmann::json;
std::unique_ptr<Plan> g_pending_plan;
std::vector<uint64_t> g_applied_cave_pages;
bool Fail(std::string* error, std::string message) {
*error = std::move(message);
return false;
}
const Json::string_t* StringField(const Json& object, const char* name) {
const auto value = object.find(name);
return value == object.end() ? nullptr : value->get_ptr<const Json::string_t*>();
}
const Json::array_t* ArrayField(const Json& object, const char* name) {
const auto value = object.find(name);
return value == object.end() ? nullptr : value->get_ptr<const Json::array_t*>();
}
bool ParseBytes(std::string_view text, std::vector<uint8_t>* bytes) {
bool ParseBytes(const std::string& text, std::vector<uint8_t>* bytes) {
if (text.empty() || (text.size() % 2) != 0) {
return false;
}
bytes->resize(text.size() / 2);
for (size_t index = 0; index < bytes->size(); index++) {
unsigned int value = 0;
const char* begin = text.data() + index * 2;
const auto [end, error] = std::from_chars(begin, begin + 2, value, 16);
if (error != std::errc {} || end != begin + 2) {
return false;
}
(*bytes)[index] = static_cast<uint8_t>(value);
(*bytes)[index] = static_cast<uint8_t>(std::stoul(text.substr(index * 2, 2), nullptr, 16));
}
return true;
}
bool ReadJson(const std::filesystem::path& path, Json* root, std::string* error) {
bool LoadPlan(const std::filesystem::path& path, Plan* plan, std::string* error) {
std::ifstream file(path, std::ios::binary);
if (!file) {
return Fail(error, "could not read patch plan");
return Fail(error, "could not read cheat file");
}
*root = Json::parse(file, nullptr, false);
return root->is_object() || Fail(error, "patch plan is not valid JSON");
}
bool LoadPlan(const std::filesystem::path& path, Plan* plan, std::string* error) {
Json root;
if (!ReadJson(path, &root, error)) {
return false;
const auto root = Json::parse(file, nullptr, false);
if (!root.is_object() || !root.contains("id") || !root.contains("version") ||
!root.contains("process") || !root.contains("mods")) {
return Fail(error, "expected a GoldHEN-style mods JSON file");
}
plan->title_id = root["id"].get<std::string>();
plan->version = root["version"].get<std::string>();
plan->process = root["process"].get<std::string>();
const auto* title_id = StringField(root, "title_id");
const auto* game_version = StringField(root, "game_version");
const auto* process = StringField(root, "process");
const auto* patches = ArrayField(root, "patches");
if (title_id == nullptr || game_version == nullptr || process == nullptr ||
patches == nullptr) {
return Fail(error, "invalid patch plan");
}
plan->title_id = *title_id;
plan->game_version = *game_version;
plan->process = *process;
for (const auto& patch_json: *patches) {
const auto enabled = patch_json.find("enabled");
if (enabled != patch_json.end() && enabled->is_boolean() && !enabled->get<bool>()) {
for (const auto& mod: root["mods"]) {
if (!mod.value("enabled", true)) {
continue;
}
const auto* name = StringField(patch_json, "name");
const auto* writes = ArrayField(patch_json, "writes");
if (name == nullptr || writes == nullptr) {
return Fail(error, "invalid patch entry");
}
plan->patch_names.push_back(*name);
for (const auto& write_json: *writes) {
const auto* expected = StringField(write_json, "expected");
const auto* replacement = StringField(write_json, "replacement");
if (expected == nullptr || replacement == nullptr) {
return Fail(error, "invalid patch write");
const auto name = mod["name"].get<std::string>();
plan->mod_names.push_back(name);
for (const auto& entry: mod["memory"]) {
Write write;
write.source_address = std::stoull(entry["offset"].get<std::string>(), nullptr, 16);
if (!ParseBytes(entry["off"].get<std::string>(), &write.off) ||
!ParseBytes(entry["on"].get<std::string>(), &write.on) ||
write.off.size() != write.on.size()) {
return Fail(error, "invalid mod memory bytes");
}
Write write {
.patch_name = *name,
};
if (!ParseBytes(*expected, &write.expected) ||
!ParseBytes(*replacement, &write.replacement) ||
write.expected.size() != write.replacement.size()) {
return Fail(error, "invalid patch bytes");
}
plan->writes.push_back(std::move(write));
}
}
return true;
}
bool ValidateTarget(const Plan& plan, const Program* program, std::string* error) {
if (program == nullptr || program->elf == nullptr || program->base_vaddr == 0) {
bool ValidateGame(const Plan& plan, const Program* main_program, std::string* error) {
if (main_program == nullptr || main_program->base_vaddr == 0) {
return Fail(error, "main executable is not loaded");
}
std::string title_id;
std::string game_version;
std::string version;
if (!SystemContentParamSfoGetString("TITLE_ID", &title_id) ||
!SystemContentParamSfoGetString("APP_VER", &game_version)) {
return Fail(error, "game metadata is incomplete");
}
if (!Common::EqualNoCase(program->file_name.filename().string(), plan.process) ||
!Common::EqualNoCase(title_id, plan.title_id) || game_version != plan.game_version) {
return Fail(error, "patch plan does not match the loaded game");
!SystemContentParamSfoGetString("APP_VER", &version) ||
!Common::EqualNoCase(main_program->file_name.filename().string(), plan.process) ||
!Common::EqualNoCase(title_id, plan.title_id) || version != plan.version) {
return Fail(error, "cheat file does not match the loaded game");
}
return true;
}
bool ResolveWrite(const Program& program, Write* write, std::string* error) {
const auto* ehdr = program.elf->GetEhdr();
const auto* phdr = program.elf->GetPhdr();
uint64_t match = 0;
size_t match_count = 0;
for (Elf64_Half index = 0; index < ehdr->e_phnum; index++) {
const auto& segment = phdr[index];
const bool loaded = segment.p_type == PT_LOAD || segment.p_type == PT_OS_RELRO;
if (!loaded || segment.p_filesz < write->expected.size()) {
continue;
}
const auto segment_address = program.base_vaddr + segment.p_vaddr;
const auto* begin = reinterpret_cast<const uint8_t*>(segment_address);
const auto* end = begin + segment.p_filesz;
for (auto* current = begin; current < end;) {
const auto* found =
std::search(current, end, write->expected.begin(), write->expected.end());
if (found == end) {
break;
}
const auto address = reinterpret_cast<uint64_t>(found);
if (match == 0) {
match = address;
}
match_count++;
current = found + 1;
}
void Translate(const Program& program, uint64_t source_base, uint64_t source_address,
uint64_t* target_address) {
if (source_address >= source_base) {
*target_address = program.base_vaddr + source_address - source_base;
return;
}
::printf("Game patch: found %zu entries for '%s'\n", match_count, write->patch_name.c_str());
if (match == 0) {
return Fail(error, "original bytes not found for '" + write->patch_name + "'");
}
write->address = match;
return true;
*target_address = program.base_vaddr - (source_base - source_address);
}
bool PrepareWrites(Plan* plan, const Program& program, std::string* error) {
bool IsInsideProgram(const Program& program, uint64_t address, size_t size) {
return size != 0 && address >= program.base_vaddr &&
address + size <= program.base_vaddr + program.mapped_size;
}
bool IsZero(const std::vector<uint8_t>& bytes) {
return std::all_of(bytes.begin(), bytes.end(), [](uint8_t byte) { return byte == 0; });
}
bool TranslateAndValidate(Plan* plan, const Program& program, uint64_t source_base) {
for (auto& write: plan->writes) {
if (!ResolveWrite(program, &write, error)) {
Translate(program, source_base, write.source_address, &write.target_address);
if (!IsZero(write.off) &&
(!IsInsideProgram(program, write.target_address, write.off.size()) ||
std::memcmp(reinterpret_cast<const void*>(write.target_address), write.off.data(),
write.off.size()) != 0)) {
return false;
}
}
return true;
}
bool ApplyWrites(Plan* plan, std::string* error) {
for (auto& write: plan->writes) {
const auto size = write.replacement.size();
std::memcpy(reinterpret_cast<void*>(write.address), write.replacement.data(), size);
if (!Common::VirtualMemory::FlushInstructionCache(write.address, size)) {
return Fail(error, "could not finalize patch");
bool Matches(Plan* plan, const Program& program, uint64_t* source_base) {
const auto anchor = std::find_if(plan->writes.begin(), plan->writes.end(),
[](const Write& write) { return !IsZero(write.off); });
if (anchor == plan->writes.end() || program.mapped_size < anchor->off.size()) {
return false;
}
const auto* ehdr = program.elf->GetEhdr();
const auto* phdr = program.elf->GetPhdr();
for (Elf64_Half index = 0; index < ehdr->e_phnum; index++) {
const auto& segment = phdr[index];
if ((segment.p_type != PT_LOAD && segment.p_type != PT_OS_RELRO) ||
segment.p_filesz < anchor->off.size()) {
continue;
}
const auto* begin = reinterpret_cast<const uint8_t*>(program.base_vaddr + segment.p_vaddr);
const auto* end = begin + segment.p_filesz;
for (auto* current = begin; current < end;) {
const auto* found = std::search(current, end, anchor->off.begin(), anchor->off.end());
if (found == end) {
break;
}
const auto rva = static_cast<uint64_t>(found - begin) + segment.p_vaddr;
if (anchor->source_address >= rva) {
*source_base = anchor->source_address - rva;
if (TranslateAndValidate(plan, program, *source_base)) {
return true;
}
}
current = found + 1;
}
}
return false;
}
void ReleaseCaves(std::vector<uint64_t>* cave_pages) {
for (auto page = cave_pages->rbegin(); page != cave_pages->rend(); ++page) {
EXIT_IF(!Libs::LibKernel::Memory::FreeGuestMemory(*page, kPageSize));
}
cave_pages->clear();
}
bool AllocateCave(Plan* plan, const Program& program, std::string* error) {
std::vector<uint64_t> pages;
for (const auto& write: plan->writes) {
if (IsInsideProgram(program, write.target_address, write.off.size())) {
continue;
}
if (!IsZero(write.off)) {
return Fail(error, "non-zero write is outside the matched module");
}
const auto write_end = write.target_address + write.off.size();
for (auto page = write.target_address & ~(kPageSize - 1); page < write_end;
page += kPageSize) {
if (std::find(pages.begin(), pages.end(), page) == pages.end()) {
pages.push_back(page);
}
}
}
for (const auto page: pages) {
const auto allocated = Libs::LibKernel::Memory::AllocateRuntimeMemory(
page, kPageSize, Common::VirtualMemory::Mode::ExecuteReadWrite, "game_cheat_code_cave",
true);
if (allocated != page) {
if (allocated != 0) {
Libs::LibKernel::Memory::FreeGuestMemory(allocated, kPageSize);
}
ReleaseCaves(&plan->cave_pages);
return Fail(error, "could not allocate code-cave page");
}
plan->cave_pages.push_back(page);
}
return true;
}
bool ApplyWrites(Plan* plan, const Program& program, std::string* error) {
if (!AllocateCave(plan, program, error)) {
return false;
}
for (int external = 1; external >= 0; external--) {
for (const auto& write: plan->writes) {
if (static_cast<int>(
!IsInsideProgram(program, write.target_address, write.on.size())) != external) {
continue;
}
std::memcpy(reinterpret_cast<void*>(write.target_address), write.on.data(),
write.on.size());
if (!Common::VirtualMemory::FlushInstructionCache(write.target_address,
write.on.size())) {
return Fail(error, "could not finalize cheat write");
}
}
}
return true;
@@ -216,19 +244,63 @@ bool ApplyWrites(Plan* plan, std::string* error) {
} // namespace
bool Apply(const std::filesystem::path& plan_path, Program* program) {
bool Apply(const std::filesystem::path& plan_path, Program* main_program,
const std::vector<Program*>& programs) {
Plan plan;
std::string error;
if (LoadPlan(plan_path, &plan, &error) && ValidateTarget(plan, program, &error) &&
PrepareWrites(&plan, *program, &error) && ApplyWrites(&plan, &error)) {
for (const auto& name: plan.patch_names) {
::printf("Successfully applied patch: %s\n", name.c_str());
}
if (!LoadPlan(plan_path, &plan, &error) || !ValidateGame(plan, main_program, &error)) {
::printf("Game cheat error: %s\n", error.c_str());
return false;
}
if (plan.writes.empty()) {
return true;
}
::printf("Game patch error: %s\n", error.c_str());
return false;
g_pending_plan = std::make_unique<Plan>(std::move(plan));
for (auto* program: programs) {
if (!ApplyPending(program)) {
return false;
}
if (g_pending_plan == nullptr) {
return true;
}
}
::printf("Game cheat: waiting for the matching module\n");
return true;
}
bool ApplyPending(Program* program) {
uint64_t source_base = 0;
if (g_pending_plan == nullptr || program == nullptr || program->elf == nullptr ||
!Matches(g_pending_plan.get(), *program, &source_base)) {
return true;
}
::printf("Game cheat: matched %s with source base 0x%llx\n",
program->file_name.filename().string().c_str(),
static_cast<unsigned long long>(source_base));
std::string error;
if (!ApplyWrites(g_pending_plan.get(), *program, &error)) {
ReleaseCaves(&g_pending_plan->cave_pages);
g_pending_plan.reset();
::printf("Game cheat error: %s\n", error.c_str());
return false;
}
for (const auto& name: g_pending_plan->mod_names) {
::printf("Successfully applied cheat: %s\n", name.c_str());
}
g_applied_cave_pages = std::move(g_pending_plan->cave_pages);
g_pending_plan.reset();
return true;
}
void Clear() {
if (g_pending_plan != nullptr) {
ReleaseCaves(&g_pending_plan->cave_pages);
}
g_pending_plan.reset();
ReleaseCaves(&g_applied_cave_pages);
}
} // namespace Loader::GamePatch
+5 -1
View File
@@ -4,6 +4,7 @@
#include "common/common.h"
#include <filesystem>
#include <vector>
namespace Loader {
@@ -11,7 +12,10 @@ struct Program;
namespace GamePatch {
bool Apply(const std::filesystem::path& plan_path, Program* program);
bool Apply(const std::filesystem::path& plan_path, Program* main_program,
const std::vector<Program*>& programs);
bool ApplyPending(Program* program);
void Clear();
} // namespace GamePatch
+8 -1
View File
@@ -1289,6 +1289,9 @@ void RuntimeLinker::RelocateProgram(Program* program) {
EXIT_IF(std::find(m_programs.begin(), m_programs.end(), program) == m_programs.end());
Relocate(program);
if (!GamePatch::ApplyPending(program)) {
EXIT("Failed to apply pending game cheat\n");
}
}
void RuntimeLinker::UnloadProgram(Program* program) {
@@ -1412,7 +1415,10 @@ void RuntimeLinker::Execute(const std::filesystem::path& game_patch) {
RelocateAll();
if (!game_patch.empty()) {
GamePatch::Apply(game_patch, m_programs.empty() ? nullptr : m_programs.front());
if (!GamePatch::Apply(game_patch, m_programs.empty() ? nullptr : m_programs.front(),
m_programs)) {
EXIT("Failed to apply game cheat\n");
}
}
StartAllModules();
@@ -1436,6 +1442,7 @@ void RuntimeLinker::Clear() {
// EXIT_NOT_IMPLEMENTED(!Common::Thread::IsMainThread());
Common::LockGuard lock(m_mutex);
GamePatch::Clear();
for (auto* p: m_programs) {
DeleteProgram(p);
+7 -3
View File
@@ -43,15 +43,17 @@ static void PrintUsage() {
::printf("kyty_emulator --game <dir|elf> [options]\n\n");
::printf("Options:\n");
::printf(" --game <dir|elf> Game directory or ELF to load.\n");
::printf(
" --game-patch <json> Validated patch plan to apply before entry.\n");
::printf(" --game-patch <json> ETAHen cheat file.\n");
::printf(" --screen-width <num> Window width. Default: 1280.\n");
::printf(" --screen-height <num> Window height. Default: 720.\n");
::printf(
" --user-name <name> Local user name (1-16 bytes). Default: Kyty.\n");
::printf(" --user-id <num> Local user ID. Default: %d.\n",
Config::DEFAULT_USER_ID);
::printf(" --present-mode <value> Fifo, Mailbox, or Immediate. Default: Fifo.\n");
::printf(
" --present-mode <value> Fifo, Mailbox, or Immediate. Default: Fifo.\n");
::printf(
" --gpu <index> Vulkan physical device index. Default: auto.\n");
::printf(" --fullscreen Run in borderless desktop fullscreen.\n");
::printf(" --vblank-frequency <num> Virtual vblank frequency. Default: 60.\n");
::printf(" --console-language <0-29> Console language. Default: 1 (English US).\n");
@@ -234,6 +236,8 @@ static bool ParseArgs(int argc, char* argv[], RunOptions& options, bool& show_he
::printf("invalid present mode: %s\n", value.c_str());
return false;
}
} else if (arg == "--gpu") {
options.config.gpu_index = Common::ToInt32(value);
} else if (arg == "--vblank-frequency") {
const int32_t vblank_frequency = Common::ToInt32(value);
options.config.vblank_frequency =
+164 -192
View File
@@ -29,6 +29,19 @@ void Check(bool condition, const char *message) {
}
}
template <typename F>
void CheckFatal(F &&function, std::string_view expected, const char *message) {
try {
function();
} catch (const std::runtime_error &error) {
Check(std::string_view(error.what()).find(expected) !=
std::string_view::npos,
message);
return;
}
Check(false, message);
}
struct Fixture {
Program program;
Block *block = nullptr;
@@ -75,8 +88,7 @@ struct Fixture {
}
MemoryFlags AddMemory(MemoryInfo memory, uint32_t pc) {
const auto index =
static_cast<uint32_t>(program.memory_info.size());
const auto index = static_cast<uint32_t>(program.memory_info.size());
program.memory_info.push_back(memory);
return {index, pc};
}
@@ -113,10 +125,8 @@ struct Fixture {
}
void PlanAndTrack() {
std::string error;
if (!BuildSrtPlan(program, &error) || !TrackResources(program, &error)) {
throw std::runtime_error(error);
}
BuildSrtPlan(program);
TrackResources(program);
}
};
@@ -241,10 +251,7 @@ void TestInvariantIndirectImageMaterialization() {
auto fixture = MakeIndirectImageFixture(false);
fixture->PlanAndTrack();
EliminateDeadCode(fixture->program.blocks);
std::string validation_error;
Check(
ValidateProgram(fixture->program, true, &validation_error),
"post-tracking dead-code elimination invalidated descriptor provenance");
ValidateProgram(fixture->program, true);
Check(fixture->program.info.buffers.size() == 1 &&
fixture->program.info.images.size() == 1 &&
@@ -291,7 +298,6 @@ void TestInvariantIndirectImageMaterialization() {
.userdata = &memory,
.read_specialization_memory = ReadLinearTestMemory};
ResourceSnapshot snapshot;
std::string error;
const auto same_snapshot = [](const ResourceSnapshot &lhs,
const ResourceSnapshot &rhs) {
return lhs.buffers == rhs.buffers && lhs.images == rhs.images &&
@@ -300,7 +306,7 @@ void TestInvariantIndirectImageMaterialization() {
lhs.user_data == rhs.user_data &&
lhs.indirect_images.empty() == rhs.indirect_images.empty();
};
Check(MaterializeResources(fixture->program, runtime, snapshot, &error) &&
Check(MaterializeResources(fixture->program, runtime, snapshot) &&
snapshot.images.size() == 1 &&
std::equal(image_descriptor.begin(), image_descriptor.end(),
snapshot.images[0].dwords.begin()),
@@ -308,8 +314,7 @@ void TestInvariantIndirectImageMaterialization() {
const auto prior_snapshot = snapshot;
memory.fail_address = 0x1004u;
Check(!MaterializeResources(fixture->program, runtime, snapshot, &error) &&
error.find("scalar read") != std::string::npos &&
Check(!MaterializeResources(fixture->program, runtime, snapshot) &&
same_snapshot(snapshot, prior_snapshot),
"rejected planning memory read mutated the snapshot");
memory.fail_address = UINT64_MAX;
@@ -325,12 +330,11 @@ void TestInvariantIndirectImageMaterialization() {
memory.words[(0x2020u - memory.base) / 4u + 3u] =
image_descriptor[3] ^ (1u << 28u);
ResourceSnapshot null_snapshot;
Check(
MaterializeResources(fixture->program, runtime, null_snapshot, &error) &&
null_snapshot.indirect_images.empty() &&
std::ranges::all_of(null_snapshot.images[0].dwords,
[](uint32_t dword) { return dword == 0u; }),
"stale typed null image descriptors were not canonicalized");
Check(MaterializeResources(fixture->program, runtime, null_snapshot) &&
null_snapshot.indirect_images.empty() &&
std::ranges::all_of(null_snapshot.images[0].dwords,
[](uint32_t dword) { return dword == 0u; }),
"stale typed null image descriptors were not canonicalized");
for (uint32_t dword = 0; dword < image_descriptor.size(); dword++) {
memory.words[(0x2000u - memory.base) / 4u + dword] =
@@ -341,13 +345,13 @@ void TestInvariantIndirectImageMaterialization() {
memory.words[(0x2020u - memory.base) / 4u] ^= 1u;
memory.words[(0x1000u - memory.base + 36u) / 4u] = 1u;
ResourceSnapshot dynamic_snapshot;
Check(MaterializeResources(fixture->program, runtime, dynamic_snapshot,
&error) &&
Check(MaterializeResources(fixture->program, runtime, dynamic_snapshot) &&
dynamic_snapshot.images.size() == 1 &&
dynamic_snapshot.indirect_images.size() == 1 &&
dynamic_snapshot.indirect_images[0].descriptors.size() == 2 &&
SpecializeResources(fixture->program, dynamic_snapshot, &error) &&
fixture->program.info.images.size() == 2 &&
dynamic_snapshot.indirect_images[0].descriptors.size() == 2,
"dynamic indirect image table did not materialize");
SpecializeResources(fixture->program, dynamic_snapshot);
Check(fixture->program.info.images.size() == 2 &&
fixture->program.info.images[0].indirect_root == 0 &&
fixture->program.info.images[0].indirect_mapping_capacity != 0 &&
fixture->program.info.images[0].indirect_resources.size() == 2 &&
@@ -364,27 +368,21 @@ void TestInvariantIndirectImageMaterialization() {
memory.words[(0x2000u - memory.base) / 4u] += 0x100u;
memory.words[(0x2020u - memory.base) / 4u] += 0x101u;
ResourceSnapshot rebound_snapshot;
Check(MaterializeResources(fixture->program, runtime, rebound_snapshot,
&error) &&
ValidateResourceSpecialization(fixture->program, rebound_snapshot,
&error),
Check(MaterializeResources(fixture->program, runtime, rebound_snapshot) &&
ValidateResourceSpecialization(fixture->program, rebound_snapshot),
"stable indirect key mapping did not accept changed image addresses");
memory.words[(0x2020u - memory.base) / 4u] =
memory.words[(0x2000u - memory.base) / 4u];
Check(MaterializeResources(fixture->program, runtime, rebound_snapshot,
&error) &&
ValidateResourceSpecialization(fixture->program, rebound_snapshot,
&error),
Check(MaterializeResources(fixture->program, runtime, rebound_snapshot) &&
ValidateResourceSpecialization(fixture->program, rebound_snapshot),
"runtime indirect key mapping did not accept collapsed candidates");
const auto collapsed_snapshot = rebound_snapshot;
ResourceSnapshot capacity_snapshot;
for (const uint32_t records : {1u, 3u}) {
user_data[2] = records;
Check(
MaterializeResources(fixture->program, runtime, capacity_snapshot,
&error) &&
ValidateResourceSpecialization(fixture->program, capacity_snapshot,
&error),
MaterializeResources(fixture->program, runtime, capacity_snapshot) &&
ValidateResourceSpecialization(fixture->program, capacity_snapshot),
"runtime indirect key mapping rejected a fitting material-table size");
}
user_data[2] = 2u;
@@ -398,9 +396,7 @@ void TestInvariantIndirectImageMaterialization() {
}
memory.words[(0x1000u - memory.base + 68u) / 4u] = 2u;
Check(
!MaterializeResources(fixture->program, runtime, rebound_snapshot,
&error) &&
error.find("candidate topology") != std::string::npos &&
!MaterializeResources(fixture->program, runtime, rebound_snapshot) &&
same_snapshot(rebound_snapshot, collapsed_snapshot),
"larger indirect candidate topology reused or mutated a cached snapshot");
@@ -418,26 +414,26 @@ void TestInvariantIndirectImageMaterialization() {
.read_specialization_memory =
ReadLinearTestMemory};
Check(!MaterializeResources(memory_backed->program, memory_backed_runtime,
snapshot, &error) &&
error.find("constant read failed") != std::string::npos &&
snapshot) &&
same_snapshot(snapshot, prior_snapshot),
"rejected indirect table descriptor read mutated the snapshot");
memory.fail_address = UINT64_MAX;
auto malformed = MakeIndirectImageFixture(true);
Check(BuildSrtPlan(malformed->program, &error) &&
!TrackResources(malformed->program, &error) &&
error.find("ReadFirstLane") != std::string::npos &&
!malformed->program.resource_tracking_complete &&
BuildSrtPlan(malformed->program);
CheckFatal([&] { TrackResources(malformed->program); }, "not a valid runtime value",
"malformed indirect image pattern was accepted");
Check(!malformed->program.resource_tracking_complete &&
malformed->program.info.images.empty() &&
malformed->program.descriptor_sources.empty(),
"malformed indirect image pattern was partially accepted");
auto wrapped_immediate = MakeIndirectImageFixture(false, 4u);
Check(BuildSrtPlan(wrapped_immediate->program, &error) &&
!TrackResources(wrapped_immediate->program, &error) &&
error.find("ReadFirstLane") != std::string::npos &&
!wrapped_immediate->program.resource_tracking_complete,
BuildSrtPlan(wrapped_immediate->program);
CheckFatal([&] { TrackResources(wrapped_immediate->program); },
"not a valid runtime value",
"wrapped scalar immediate entered the invariant image proof");
Check(!wrapped_immediate->program.resource_tracking_complete,
"wrapped scalar immediate entered the invariant image proof");
}
@@ -494,16 +490,12 @@ void TestDenseBufferTracking() {
second.Instruction()->Flags<uint32_t>() == 1,
"typed handles were not assigned dense indices");
Check(fixture.program.memory_info[load_flags.index].resource == 0 &&
fixture.program.memory_info[store_flags.index].resource ==
0 &&
fixture.program.memory_info[other_flags.index].resource ==
1,
fixture.program.memory_info[store_flags.index].resource == 0 &&
fixture.program.memory_info[other_flags.index].resource == 1,
"typed memory metadata was not patched to dense indices");
std::string error;
Check(!TrackResources(fixture.program, &error) &&
error.find("already tracked") != std::string::npos,
"resource tracking allowed a second mutation pass");
CheckFatal([&] { TrackResources(fixture.program); }, "already tracked",
"resource tracking allowed a second mutation pass");
}
void TestScalarAndVectorBufferAlias() {
@@ -531,8 +523,7 @@ void TestScalarAndVectorBufferAlias() {
fixture.program.info.buffers[0].scalar,
"typed scalar and vector uses of one descriptor were split");
Check(fixture.program.memory_info[scalar_flags.index].resource == 0 &&
fixture.program.memory_info[vector_flags.index].resource ==
0,
fixture.program.memory_info[vector_flags.index].resource == 0,
"scalar/vector alias did not share a dense index");
}
@@ -552,16 +543,13 @@ void TestRuntimeUnsignedMinDescriptor() {
std::array<uint32_t, 1> user_data{0xffffffffu};
SrtRuntime runtime{.user_data = user_data};
DescriptorValue value;
std::string error;
const auto source = fixture.program.info.buffers[0].source;
Check(EvaluateDescriptorSource(fixture.program, source, 0x330, runtime, value,
&error) &&
Check(EvaluateDescriptorSource(fixture.program, source, runtime, value) &&
value.dwords[3] == 0x100u,
"runtime descriptor unsigned minimum did not clamp its first operand");
user_data[0] = 0x80u;
Check(
EvaluateDescriptorSource(fixture.program, source, 0x330, runtime, value,
&error) &&
EvaluateDescriptorSource(fixture.program, source, runtime, value) &&
value.dwords[3] == 0x80u,
"runtime descriptor unsigned minimum did not preserve its first operand");
}
@@ -624,9 +612,7 @@ void TestImagesSamplersAndAliases() {
repeated.second.Instruction()->Flags<uint32_t>() == 0,
"unused sampler border colors prevented source interning");
const auto sampler_source = fixture.program.info.samplers[0].source;
Check(fixture.program.descriptor_sources[sampler_source]
.dwords[3]
.U32() == 0,
Check(fixture.program.descriptor_sources[sampler_source].dwords[3].U32() == 0,
"unused sampler border color was not canonicalized");
Check(fixture.program.info.buffers[0].image_alias == 0,
"buffer/image descriptor alias was not linked");
@@ -673,9 +659,7 @@ void TestSampleAdjustSamplerScratch() {
std::array<uint32_t, 4> user_data{4u, 1u, 2u, 0x80000abcu};
SrtRuntime runtime{.user_data = user_data};
DescriptorValue descriptor;
std::string error;
Check(EvaluateDescriptorSource(fixture.program, source, 0x1ec, runtime,
descriptor, &error) &&
Check(EvaluateDescriptorSource(fixture.program, source, runtime, descriptor) &&
descriptor.dwords[3] == 0x80000abcu,
"SampleAdjust canonicalization lost sampler border fields");
@@ -704,11 +688,9 @@ void TestSampleAdjustSamplerScratch() {
rejected.Emit(ValueOpcode::ImageSampleRaw,
{rejected_image, rejected_sampler, rejected.ImageAddress()},
rejected.AddMemory(rejected_memory, 0x200));
std::string rejected_error;
Check(BuildSrtPlan(rejected.program, &rejected_error) &&
!TrackResources(rejected.program, &rejected_error) &&
rejected_error.find("WqmMask") != std::string::npos,
message);
BuildSrtPlan(rejected.program);
CheckFatal([&] { TrackResources(rejected.program); },
"not a valid runtime value", message);
};
CheckRejected(0u, 12u,
"ordinary sampling accepted SampleAdjust reserved scratch");
@@ -778,15 +760,13 @@ void TestDynamicStorageMipTracking() {
descriptor.dword_count = 8;
ResourceSnapshot snapshot;
snapshot.images.assign(images.size(), descriptor);
std::string error;
Check(SpecializeResources(fixture.program, snapshot, &error) &&
fixture.program.info.images[1].mip_count == 3 &&
ValidateResourceSpecialization(fixture.program, snapshot, &error),
SpecializeResources(fixture.program, snapshot);
Check(fixture.program.info.images[1].mip_count == 3 &&
ValidateResourceSpecialization(fixture.program, snapshot),
"base-1 through last-3 dynamic storage range was not specialized");
ShaderComputeInputInfo compute{};
Check(CollectShaderInfo(fixture.program, {.compute = &compute}, &error) &&
AllocateBindings(fixture.program, 0, &error),
"dynamic storage mip bindings were not allocated");
CollectShaderInfo(fixture.program, {.compute = &compute});
AllocateBindings(fixture.program, 0);
const auto *storage_binding =
FindBinding(fixture.program.bindings, DescriptorBindingKind::Storage2D);
Check(storage_binding != nullptr &&
@@ -805,19 +785,19 @@ void TestDynamicStorageMipTracking() {
DescriptorValue null_descriptor{};
null_descriptor.dword_count = 8;
null_snapshot.images.push_back(null_descriptor);
Check(SpecializeResources(null_program, null_snapshot, &error) &&
null_program.info.images[0].mip_count == 1 &&
ValidateResourceSpecialization(null_program, null_snapshot, &error),
SpecializeResources(null_program, null_snapshot);
Check(null_program.info.images[0].mip_count == 1 &&
ValidateResourceSpecialization(null_program, null_snapshot),
"canonical null dynamic storage image did not retain one descriptor");
snapshot.images[1].dwords[3] =
(snapshot.images[1].dwords[3] & ~(0xfu << 16u)) | (2u << 16u);
Check(!ValidateResourceSpecialization(fixture.program, snapshot, &error),
Check(!ValidateResourceSpecialization(fixture.program, snapshot),
"a changed dynamic storage mip count reused the specialization");
snapshot.images[1].dwords[3] =
(snapshot.images[1].dwords[3] & ~((0xfu << 12u) | (0xfu << 16u))) |
(4u << 12u) | (3u << 16u);
Check(!ValidateResourceSpecialization(fixture.program, snapshot, &error),
Check(!ValidateResourceSpecialization(fixture.program, snapshot),
"an inverted dynamic storage mip range was accepted");
}
@@ -861,11 +841,9 @@ void TestSrtFlatteningAndRuntimeMemoization() {
.userdata = &memory};
std::vector<DescriptorValue> descriptors;
std::vector<uint32_t> flat;
const DescriptorSourceRequest request{fixture.program.info.buffers[0].source,
12};
std::string error;
const DescriptorSourceRequest request{fixture.program.info.buffers[0].source};
Check(EvaluateRuntimeSources(fixture.program, std::span{&request, 1}, runtime,
descriptors, flat, {}, &error),
descriptors, flat, {}),
"typed runtime source evaluation failed");
Check(descriptors.size() == 1 && descriptors[0].dwords[0] == 0xdeadbeefu &&
flat == std::vector<uint32_t>{0xdeadbeefu} && memory.reads == 1,
@@ -876,16 +854,16 @@ void TestSrtFlatteningAndRuntimeMemoization() {
descriptors = {{{1u}, 1u}};
flat = {2u};
Check(!EvaluateRuntimeSources(fixture.program, std::span{&request, 1},
runtime, descriptors, flat, {}, &error) &&
runtime, descriptors, flat, {}) &&
descriptors == std::vector<DescriptorValue>{{{1u}, 1u}} &&
flat == std::vector<uint32_t>{2u},
"runtime evaluation failure was not transactional");
ShaderComputeInputInfo compute{};
Check(CollectShaderInfo(fixture.program, {.compute = &compute}, &error) &&
AllocateBindings(fixture.program, 0, &error) &&
FindBinding(fixture.program.bindings,
DescriptorBindingKind::FlattenedSrt) != nullptr,
CollectShaderInfo(fixture.program, {.compute = &compute});
AllocateBindings(fixture.program, 0);
Check(FindBinding(fixture.program.bindings,
DescriptorBindingKind::FlattenedSrt) != nullptr,
"flattened typed SRT reads did not receive a binding");
}
@@ -919,18 +897,16 @@ void TestDynamicSrtReadRemainsExplicit() {
.read_memory = ReadTestMemory,
.userdata = &memory};
DescriptorValue value;
std::string error;
Check(EvaluateDescriptorSource(fixture.program,
fixture.program.info.buffers[0].source, 8,
runtime, value, &error) &&
fixture.program.info.buffers[0].source, runtime, value) &&
value.dwords[0] == 0xabcdef01u && memory.reads == 1,
"dynamic typed scalar descriptor source was not evaluated");
ShaderComputeInputInfo compute{};
Check(CollectShaderInfo(fixture.program, {.compute = &compute}, &error) &&
AllocateBindings(fixture.program, 0, &error) &&
FindBinding(fixture.program.bindings,
DescriptorBindingKind::FlattenedSrt) == nullptr &&
CollectShaderInfo(fixture.program, {.compute = &compute});
AllocateBindings(fixture.program, 0);
Check(FindBinding(fixture.program.bindings,
DescriptorBindingKind::FlattenedSrt) == nullptr &&
FindBinding(fixture.program.bindings,
DescriptorBindingKind::BdaPagetable) != nullptr &&
FindBinding(fixture.program.bindings,
@@ -966,12 +942,10 @@ void TestPhiValidation() {
{handle, Value(0u), Value(0u), Value(0u), Value(true)},
fixture.AddMemory(memory, 20), merge);
std::string error;
Check(BuildSrtPlan(fixture.program, &error),
"SRT planning rejected a well-formed typed phi");
Check(!TrackResources(fixture.program, &error) &&
error.find("control-dependent phi") != std::string::npos &&
!fixture.program.resource_tracking_complete &&
BuildSrtPlan(fixture.program);
CheckFatal([&] { TrackResources(fixture.program); }, "not a valid runtime value",
"control-dependent descriptor phi was accepted");
Check(!fixture.program.resource_tracking_complete &&
fixture.program.info.buffers.empty() &&
fixture.program.descriptor_sources.empty(),
"control-dependent descriptor phi was not rejected transactionally");
@@ -994,9 +968,7 @@ void TestLoopCycleEnteredThroughRuntimeValue() {
{carried, Value(0u), Value(0u), Value(0u)}, MemoryFlags{0, 12},
loop);
std::string error;
Check(BuildSrtPlan(fixture.program, &error),
"SRT planning rejected a valid loop entered through a runtime value");
BuildSrtPlan(fixture.program);
}
void TestInvariantLoopPhi() {
@@ -1023,10 +995,8 @@ void TestInvariantLoopPhi() {
std::array<uint32_t, 1> user_data{0x12345678u};
SrtRuntime runtime{.user_data = user_data};
DescriptorValue descriptor;
std::string error;
Check(EvaluateDescriptorSource(fixture.program,
fixture.program.info.buffers[0].source, 4,
runtime, descriptor, &error) &&
fixture.program.info.buffers[0].source, runtime, descriptor) &&
descriptor.dwords[0] == user_data[0],
"loop-invariant descriptor phi was not evaluated through typed SSA");
}
@@ -1057,11 +1027,9 @@ void TestDmaAddressMaterialization() {
std::array<uint32_t, 2> user_data{0x2008u, 0u};
SrtRuntime runtime{.user_data = user_data};
ResourceSnapshot snapshot;
std::string error;
Check(MaterializeResources(fixture.program, runtime, snapshot, &error),
Check(MaterializeResources(fixture.program, runtime, snapshot),
"DMA shader resources did not materialize");
Check(SpecializeResources(fixture.program, snapshot, &error),
"DMA shader resources were not specialized");
SpecializeResources(fixture.program, snapshot);
}
void TestDynamicFlatAddressesUseDma() {
@@ -1089,8 +1057,7 @@ void TestDynamicFlatAddressesUseDma() {
std::array<uint32_t, 3> user_data{0x23456780u, 1u, 1u};
SrtRuntime runtime{.user_data = user_data};
ResourceSnapshot snapshot;
std::string error;
Check(MaterializeResources(fixture.program, runtime, snapshot, &error),
Check(MaterializeResources(fixture.program, runtime, snapshot),
"exec-masked FLAT shader resources did not materialize");
Fixture mismatch;
@@ -1137,15 +1104,15 @@ void TestBufferSwizzleSpecialization() {
(1u << 24u)};
SrtRuntime runtime{.user_data = user_data};
ResourceSnapshot snapshot;
std::string error;
Check(MaterializeResources(fixture.program, runtime, snapshot, &error) &&
SpecializeResources(fixture.program, snapshot, &error) &&
fixture.program.info.buffers[0].descriptor_swizzle == swizzle &&
ValidateResourceSpecialization(fixture.program, snapshot, &error),
Check(MaterializeResources(fixture.program, runtime, snapshot),
"buffer resources did not materialize");
SpecializeResources(fixture.program, snapshot);
Check(fixture.program.info.buffers[0].descriptor_swizzle == swizzle &&
ValidateResourceSpecialization(fixture.program, snapshot),
"buffer destination selectors were not specialized");
snapshot.buffers[0].dwords[3] ^= 1u << 9u;
Check(!ValidateResourceSpecialization(fixture.program, snapshot, &error),
Check(!ValidateResourceSpecialization(fixture.program, snapshot),
"buffer swizzle change did not invalidate specialization");
}
@@ -1171,17 +1138,14 @@ void TestShaderInfoAndBindingLayout() {
ShaderComputeInputInfo compute{};
compute.dispatch_thread_dimensions = true;
std::string error;
Check(CollectShaderInfo(fixture.program, {.compute = &compute}, &error),
"typed shader info collection failed");
CollectShaderInfo(fixture.program, {.compute = &compute});
Check(fixture.program.info.has_bitwise_xor &&
!fixture.program.info.inputs.empty() &&
fixture.program.info.inputs[0].kind ==
StageInputKind::GlobalInvocationId,
"typed shader values were not reflected in shader info");
Check(AllocateBindings(fixture.program, 0, &error),
"typed binding allocation failed");
AllocateBindings(fixture.program, 0);
Check(FindBinding(fixture.program.bindings, DescriptorBindingKind::Buffers) !=
nullptr &&
FindBinding(fixture.program.bindings, DescriptorBindingKind::Gds) !=
@@ -1211,53 +1175,57 @@ void TestGraphicsPushConstantLayout() {
}
fixture.program.shader_info_complete = true;
};
std::string error;
Fixture vertex(ShaderType::Vertex);
AddUserData(vertex, 9);
Check(AllocateBindings(vertex.program, 0, &error) &&
vertex.program.bindings.push_constant_offset == 0 &&
AllocateBindings(vertex.program, 0);
Check(vertex.program.bindings.push_constant_offset == 0 &&
vertex.program.bindings.push_constant_size == 9 * sizeof(uint32_t),
"vertex shader was not placed at the start of the graphics push bank");
const auto pixel_offset = vertex.program.bindings.push_constant_size;
Fixture pixel(ShaderType::Pixel);
AddUserData(pixel, 4);
Check(AllocateBindings(pixel.program, pixel_offset, &error) &&
pixel.program.bindings.push_constant_offset == pixel_offset &&
pixel.program.bindings.push_constant_size == 4 * sizeof(uint32_t) &&
FindBinding(pixel.program.bindings, DescriptorBindingKind::UserData) ==
nullptr,
"pixel shader did not follow the vertex data in the graphics push bank");
AllocateBindings(pixel.program, pixel_offset);
Check(
pixel.program.bindings.push_constant_offset == pixel_offset &&
pixel.program.bindings.push_constant_size == 4 * sizeof(uint32_t) &&
FindBinding(pixel.program.bindings,
DescriptorBindingKind::UserData) == nullptr,
"pixel shader did not follow the vertex data in the graphics push bank");
Fixture edge(ShaderType::Pixel);
AddUserData(edge, 1);
Check(AllocateBindings(edge.program, NativePushConstantSize - sizeof(uint32_t),
&error) &&
edge.program.bindings.push_constant_size == sizeof(uint32_t),
AllocateBindings(edge.program, NativePushConstantSize - sizeof(uint32_t));
Check(edge.program.bindings.push_constant_size == sizeof(uint32_t),
"last aligned push-constant dword did not fit in the graphics bank");
Fixture spill(ShaderType::Pixel);
AddUserData(spill, 32);
Check(AllocateBindings(spill.program, pixel_offset, &error) &&
spill.program.bindings.push_constant_size == 0 &&
FindBinding(spill.program.bindings, DescriptorBindingKind::UserData) !=
nullptr,
"pixel shader overlapping the vertex push data did not spill to storage");
AllocateBindings(spill.program, pixel_offset);
Check(
spill.program.bindings.push_constant_size == 0 &&
FindBinding(spill.program.bindings,
DescriptorBindingKind::UserData) != nullptr,
"pixel shader overlapping the vertex push data did not spill to storage");
Fixture full(ShaderType::Pixel);
AddUserData(full, 1);
Check(AllocateBindings(full.program, NativePushConstantSize, &error) &&
full.program.bindings.push_constant_size == 0 &&
FindBinding(full.program.bindings, DescriptorBindingKind::UserData) !=
nullptr,
AllocateBindings(full.program, NativePushConstantSize);
Check(full.program.bindings.push_constant_size == 0 &&
FindBinding(full.program.bindings,
DescriptorBindingKind::UserData) != nullptr,
"full graphics push bank did not spill pixel user data to storage");
Fixture invalid(ShaderType::Pixel);
AddUserData(invalid, 1);
Check(!AllocateBindings(invalid.program,
NativePushConstantSize + sizeof(uint32_t), &error) &&
!invalid.program.binding_layout_complete,
CheckFatal(
[&] {
AllocateBindings(invalid.program,
NativePushConstantSize + sizeof(uint32_t));
},
"push-constant offset",
"push-constant placement beyond the graphics bank was accepted");
Check(!invalid.program.binding_layout_complete,
"push-constant placement beyond the graphics bank was accepted");
}
@@ -1273,12 +1241,11 @@ void TestResourceLimitIsTransactional() {
{handle, Value(0u), Value(0u), Value(0u), Value(true)},
fixture.AddMemory(memory, index * 4u));
}
std::string error;
Check(BuildSrtPlan(fixture.program, &error),
"SRT plan failed before resource-limit test");
Check(!TrackResources(fixture.program, &error) &&
error.find("buffer resource limit exceeded") != std::string::npos &&
!fixture.program.resource_tracking_complete &&
BuildSrtPlan(fixture.program);
CheckFatal([&] { TrackResources(fixture.program); },
"buffer resource limit exceeded",
"resource-limit failure was not reported");
Check(!fixture.program.resource_tracking_complete &&
fixture.program.info.buffers.empty() &&
fixture.program.descriptor_sources.empty(),
"resource-limit failure partially mutated typed resource state");
@@ -1293,12 +1260,11 @@ void TestMalformedMemoryKindsRejected() {
fixture.Emit(ValueOpcode::StoreAddressU32,
{address, Value(0u), Value(0u), Value(1u), Value(true)},
fixture.AddMemory(memory, 4));
std::string error;
Check(BuildSrtPlan(fixture.program, &error) &&
!TrackResources(fixture.program, &error) &&
error.find("address operation has invalid resource kind") !=
std::string::npos,
"resource tracking accepted an address opcode with buffer metadata");
BuildSrtPlan(fixture.program);
CheckFatal(
[&] { TrackResources(fixture.program); },
"address operation has invalid resource kind",
"resource tracking accepted an address opcode with buffer metadata");
}
{
Fixture fixture;
@@ -1311,12 +1277,11 @@ void TestMalformedMemoryKindsRejected() {
fixture.Emit(ValueOpcode::ImageRead,
{image, fixture.ImageAddress(), Value(true)},
fixture.AddMemory(memory, 8));
std::string error;
Check(BuildSrtPlan(fixture.program, &error) &&
!TrackResources(fixture.program, &error) &&
error.find("image operation has invalid resource kind") !=
std::string::npos,
"resource tracking accepted an image opcode with address metadata");
BuildSrtPlan(fixture.program);
CheckFatal(
[&] { TrackResources(fixture.program); },
"image operation has invalid resource kind",
"resource tracking accepted an image opcode with address metadata");
}
{
Fixture fixture;
@@ -1329,12 +1294,11 @@ void TestMalformedMemoryKindsRejected() {
fixture.Emit(ValueOpcode::ImageRead,
{image, fixture.ImageAddress(), Value(true)},
fixture.AddMemory(memory, 12));
std::string error;
Check(BuildSrtPlan(fixture.program, &error) &&
!TrackResources(fixture.program, &error) &&
error.find("image operation has invalid resource kind") !=
std::string::npos,
"resource tracking accepted a sampled read with storage metadata");
BuildSrtPlan(fixture.program);
CheckFatal(
[&] { TrackResources(fixture.program); },
"image operation has invalid resource kind",
"resource tracking accepted a sampled read with storage metadata");
}
{
Fixture fixture;
@@ -1347,12 +1311,10 @@ void TestMalformedMemoryKindsRejected() {
fixture.Emit(ValueOpcode::ImageAtomicIAdd32,
{image, fixture.ImageAddress(), Value(1u), Value(true)},
fixture.AddMemory(memory, 16));
std::string error;
Check(
BuildSrtPlan(fixture.program, &error) &&
!TrackResources(fixture.program, &error) &&
error.find("image operation has invalid resource kind") !=
std::string::npos,
BuildSrtPlan(fixture.program);
CheckFatal(
[&] { TrackResources(fixture.program); },
"image operation has invalid resource kind",
"resource tracking accepted a uint atomic with float storage metadata");
}
}
@@ -1399,14 +1361,24 @@ int main() {
// target links only fmt; keep assertion failures observable without widening
// its focused build manifest.
namespace Common {
int DbgExitHandler(const char *, int, std::string_view text) {
throw std::runtime_error(std::string(text));
}
int DbgExitHandler(const char *, int, fmt::text_style, std::string_view text) {
throw std::runtime_error(std::string(text));
}
int DbgExitIfHandler(const char *expression, const char *file, int line) {
throw std::runtime_error(std::string("typed IR assertion: ") + expression +
" at " + file + ':' + std::to_string(line));
}
int DbgNotImplementedHandler(const char *expression, const char *file, int line) {
throw std::runtime_error(std::string("typed IR not implemented: ") + expression +
" at " + file + ':' + std::to_string(line));
int DbgNotImplementedHandler(const char *expression, const char *file,
int line) {
throw std::runtime_error(std::string("typed IR not implemented: ") +
expression + " at " + file + ':' +
std::to_string(line));
}
void DbgExit(int) { throw std::runtime_error("typed IR assertion failed"); }
@@ -1415,8 +1387,8 @@ void DbgExit(int) { throw std::runtime_error("typed IR assertion failed"); }
// Keep this focused standalone target self-contained by amalgamating its small
// typed-IR implementation set.
#include "graphics/shader/recompiler/ir/Block.cpp"
#include "graphics/shader/recompiler/ir/Program.cpp"
#include "graphics/shader/recompiler/ir/Type.cpp"
#include "graphics/shader/recompiler/ir/Value.cpp"
#include "graphics/shader/recompiler/ir/Program.cpp"
#include "graphics/shader/recompiler/ir/opcodes/ValueOpcodes.cpp"
#include "graphics/shader/recompiler/ir/passes/DeadCodeElimination.cpp"
+54 -59
View File
@@ -20,6 +20,19 @@ void Check(bool condition, const char *message) {
}
}
template <typename F>
void CheckFatal(F &&function, std::string_view expected, const char *message) {
try {
function();
} catch (const std::runtime_error &error) {
Check(std::string_view(error.what()).find(expected) !=
std::string_view::npos,
message);
return;
}
Check(false, message);
}
struct Fixture {
Program program;
@@ -58,10 +71,7 @@ struct Fixture {
return static_cast<uint32_t>(program.memory_info.size() - 1u);
}
void Plan() {
std::string error;
Check(BuildSrtPlan(program, &error), error.c_str());
}
void Plan() { BuildSrtPlan(program); }
};
struct TestMemory {
@@ -112,8 +122,7 @@ void TestImmediateFlatteningAndGvn() {
TestMemory memory_image{{{0x1020u, 0xfeedbeefu}}};
SrtRuntime runtime{.read_memory = ReadMemory, .userdata = &memory_image};
std::vector<uint32_t> flat;
std::string error;
Check(WalkSrt(fixture.program, runtime, flat, &error), error.c_str());
Check(WalkSrt(fixture.program, runtime, flat), "flattened SRT walk failed");
Check(flat == std::vector<uint32_t>{0xfeedbeefu} && memory_image.reads == 1,
"flattened SRT did not evaluate its canonical read once");
}
@@ -130,8 +139,7 @@ void TestRawScalarComponentAlignment() {
TestMemory memory_image{{{0x1000u, 0x12345678u}}};
SrtRuntime runtime{.read_memory = ReadMemory, .userdata = &memory_image};
std::vector<uint32_t> flat;
std::string error;
Check(WalkSrt(fixture.program, runtime, flat, &error), error.c_str());
Check(WalkSrt(fixture.program, runtime, flat), "raw scalar SRT walk failed");
Check(
flat == std::vector<uint32_t>{0x12345678u} && memory_image.reads == 1,
"raw scalar base, immediate, and offset were not aligned independently");
@@ -141,11 +149,9 @@ void TestScalarMemoryDomainMismatchFails() {
Fixture raw;
const auto raw_memory = raw.AddMemory(ResourceKind::ScalarBuffer);
RawRead(raw, Address(raw, Value(0x1000u), Value(0u)), Value(0u), raw_memory);
std::string error;
Check(!BuildSrtPlan(raw.program, &error) &&
error.find("incompatible scalar memory metadata") !=
std::string::npos,
"raw scalar load accepted descriptor-buffer metadata");
CheckFatal([&] { BuildSrtPlan(raw.program); },
"incompatible scalar memory metadata",
"raw scalar load accepted descriptor-buffer metadata");
Fixture buffer;
const auto buffer_memory = buffer.AddMemory(ResourceKind::ScalarAddress);
@@ -154,11 +160,9 @@ void TestScalarMemoryDomainMismatchFails() {
{Value(0x1000u), Value(0u), Value(16u), Value(0u)});
buffer.EmitMemory(ValueOpcode::ReadConstBuffer, {resource, Value(0u)},
buffer_memory);
error.clear();
Check(!BuildSrtPlan(buffer.program, &error) &&
error.find("incompatible scalar memory metadata") !=
std::string::npos,
"descriptor scalar load accepted raw-address metadata");
CheckFatal([&] { BuildSrtPlan(buffer.program); },
"incompatible scalar memory metadata",
"descriptor scalar load accepted raw-address metadata");
}
void TestDynamicReadRemainsTyped() {
@@ -191,8 +195,7 @@ void TestNestedSrtWalk() {
TestMemory memory_image{{{0x1000u, 0x2000u}, {0x2000u, 0xabcdef01u}}};
SrtRuntime runtime{.read_memory = ReadMemory, .userdata = &memory_image};
std::vector<uint32_t> flat;
std::string error;
Check(WalkSrt(fixture.program, runtime, flat, &error), error.c_str());
Check(WalkSrt(fixture.program, runtime, flat), "nested SRT walk failed");
Check(flat == std::vector<uint32_t>({0x2000u, 0xabcdef01u}),
"nested typed SRT reads were not evaluated in dependency order");
}
@@ -215,10 +218,8 @@ void TestShaderBaseAndUserData() {
SrtRuntime runtime{.user_data = user_data,
.shader_base = 0x12345678abcdef00ull};
DescriptorValue result;
std::string error;
Check(EvaluateDescriptorSource(fixture.program, 0, 0x90, runtime, result,
&error),
error.c_str());
Check(EvaluateDescriptorSource(fixture.program, 0, runtime, result),
"shader-relative descriptor evaluation failed");
Check(result.dword_count == 3 && result.dwords[0] == 0xabcdef00u &&
result.dwords[1] == 0x12345678u && result.dwords[2] == 0x24u,
"shader-relative typed descriptor expression evaluated incorrectly");
@@ -242,9 +243,8 @@ void TestCarryAndBitFields() {
{.dwords = {low, high, inserted, sign}, .dword_count = 4});
DescriptorValue result;
std::string error;
Check(EvaluateDescriptorSource(fixture.program, 0, 0, {}, result, &error),
error.c_str());
Check(EvaluateDescriptorSource(fixture.program, 0, {}, result),
"carry and bit-field descriptor evaluation failed");
Check(result.dwords[0] == 1u && result.dwords[1] == 1u &&
result.dwords[2] == 0x89abcdefu && result.dwords[3] == 0xffffffffu,
"typed carry or bit-field runtime evaluation is incorrect");
@@ -267,17 +267,13 @@ void TestInvariantAndDivergentPhi() {
{.dwords = {Value(&divergent)}, .dword_count = 1});
DescriptorValue result;
std::string error;
Check(
EvaluateDescriptorSource(fixture.program, 0, 0x100, {}, result, &error) &&
result.dwords[0] == 7u,
"loop-invariant typed phi was rejected");
Check(EvaluateDescriptorSource(fixture.program, 0, {}, result) &&
result.dwords[0] == 7u,
"loop-invariant typed phi was rejected");
result.dword_count = 4;
result.dwords[0] = 0xdeadbeefu;
Check(!EvaluateDescriptorSource(fixture.program, 1, 0x104, {}, result,
&error) &&
result.dword_count == 4 && result.dwords[0] == 0xdeadbeefu &&
error.find("runtime-dependent") != std::string::npos,
Check(!EvaluateDescriptorSource(fixture.program, 1, {}, result) &&
result.dword_count == 4 && result.dwords[0] == 0xdeadbeefu,
"divergent phi did not fail transactionally");
}
@@ -318,8 +314,7 @@ void TestRuntime64BitDescriptorOps() {
{.dwords = {low, high}, .dword_count = 2});
DescriptorValue result;
std::string error;
Check(EvaluateDescriptorSource(fixture.program, 0, 0, {}, result, &error) &&
Check(EvaluateDescriptorSource(fixture.program, 0, {}, result) &&
result.dwords[0] == 0xabcdu && result.dwords[1] == 0x1235u,
"64-bit typed descriptor arithmetic evaluation is incorrect");
}
@@ -339,10 +334,9 @@ void TestConstantBufferBounds() {
TestMemory memory_image{{{0x300cu, 0xa5a5a5a5u}}};
SrtRuntime runtime{.read_memory = ReadMemory, .userdata = &memory_image};
std::vector<uint32_t> flat;
std::string error;
Check(WalkSrt(fixture.program, runtime, flat, &error) &&
Check(WalkSrt(fixture.program, runtime, flat) &&
flat == std::vector<uint32_t>{0xa5a5a5a5u},
error.c_str());
"constant-buffer SRT walk failed");
Fixture overflow;
const auto overflow_memory = overflow.AddMemory(ResourceKind::ScalarBuffer);
@@ -356,9 +350,8 @@ void TestConstantBufferBounds() {
{overflow_read, Value(0u), Value(16u), Value(0u)});
overflow.Plan();
flat = {0x55u};
Check(!WalkSrt(overflow.program, runtime, flat, &error) &&
flat == std::vector<uint32_t>{0x55u} &&
error.find("exceeds size") != std::string::npos,
Check(!WalkSrt(overflow.program, runtime, flat) &&
flat == std::vector<uint32_t>{0x55u},
"out-of-bounds constant-buffer walk was not transactional");
}
@@ -420,10 +413,9 @@ void TestControlFlowValueSurvivesReadLaneFolding() {
Libs::Graphics::ShaderRecompiler::CFG::TerminatorKind::Return;
const auto undef = fixture.Emit(ValueOpcode::UndefU32);
const auto write = fixture.Emit(
ValueOpcode::WriteLane, {undef, Value(42u), Value(5u)});
const auto read =
fixture.Emit(ValueOpcode::ReadLane, {write, Value(5u)});
const auto write =
fixture.Emit(ValueOpcode::WriteLane, {undef, Value(42u), Value(5u)});
const auto read = fixture.Emit(ValueOpcode::ReadLane, {write, Value(5u)});
entry_info.condition =
fixture.Emit(ValueOpcode::IEqual32, {read, Value(42u)});
fixture.Emit(ValueOpcode::Reference, {entry_info.condition});
@@ -435,11 +427,9 @@ void TestControlFlowValueSurvivesReadLaneFolding() {
RemoveIdentities(fixture.program.blocks);
EliminateDeadCode(fixture.program.blocks);
std::string error;
Check(entry_info.condition == Value(true) &&
ValidateProgram(fixture.program, true, &error),
error.empty() ? "folded branch condition did not survive identity removal"
: error.c_str());
Check(entry_info.condition == Value(true),
"folded branch condition did not survive identity removal");
ValidateProgram(fixture.program, true);
}
void TestUndefinedRuntimeValueFails() {
@@ -450,11 +440,8 @@ void TestUndefinedRuntimeValueFails() {
{.dwords = {undef}, .dword_count = 1});
DescriptorValue result;
result.dword_count = 3;
std::string error;
Check(!EvaluateDescriptorSource(fixture.program, 0, 0x200, {}, result,
&error) &&
result.dword_count == 3 &&
error.find("undefined typed runtime value") != std::string::npos,
Check(!EvaluateDescriptorSource(fixture.program, 0, {}, result) &&
result.dword_count == 3,
"undefined typed descriptor source did not fail transactionally");
}
@@ -464,6 +451,14 @@ namespace Common {
int DbgExitIfHandler(const char *, const char *, int) { return 1; }
int DbgExitHandler(const char *, int, std::string_view text) {
throw std::runtime_error(std::string(text));
}
int DbgExitHandler(const char *, int, fmt::text_style, std::string_view text) {
throw std::runtime_error(std::string(text));
}
void DbgExit(int) { std::abort(); }
} // namespace Common
@@ -496,9 +491,9 @@ int main() {
// Keep this focused standalone target self-contained by amalgamating its small
// typed-IR implementation set.
#include "../src/graphics/shader/recompiler/ir/Block.cpp"
#include "../src/graphics/shader/recompiler/ir/Program.cpp"
#include "../src/graphics/shader/recompiler/ir/Type.cpp"
#include "../src/graphics/shader/recompiler/ir/Value.cpp"
#include "../src/graphics/shader/recompiler/ir/Program.cpp"
#include "../src/graphics/shader/recompiler/ir/opcodes/ValueOpcodes.cpp"
#include "../src/graphics/shader/recompiler/ir/passes/ConstantPropagation.cpp"
#include "../src/graphics/shader/recompiler/ir/passes/DeadCodeElimination.cpp"
File diff suppressed because it is too large. Load diff
+49 -7
View File
@@ -52,7 +52,7 @@ SrtProgram(uint64_t address) {
ValueOpcode::LoadAddressU32,
{Value(&handle), Value(0u), Value(0u), Value(true)});
raw.SetFlags(MemoryFlags{.index = 0, .pc = 0x40});
program.srt_reads.push_back({Value(&raw), 0, 0x40});
program.srt_reads.push_back({Value(&raw), 0});
auto &srt = value_block.AppendNewInst(ValueOpcode::GetSrtResource);
auto &flat = value_block.AppendNewInst(ValueOpcode::ReadConst,
@@ -77,17 +77,38 @@ UnbasedFlatProgram() {
return std::make_shared<const Program>(std::move(program));
}
std::shared_ptr<const Libs::Graphics::ShaderRecompiler::IR::Program>
UserDataBufferProgram() {
using namespace Libs::Graphics::ShaderRecompiler::IR;
Program program;
program.stage = Libs::Graphics::ShaderType::Compute;
program.srt_plan_complete = true;
program.resource_tracking_complete = true;
auto &value_block = AddValueBlock(program);
auto &user_data = value_block.AppendNewInst(
ValueOpcode::GetUserData, {Value(static_cast<ScalarReg>(0))});
DescriptorSource source;
source.dwords[0] = Value(&user_data);
source.dwords[1] = Value(0u);
source.dwords[2] = Value(0u);
source.dwords[3] = Value(0u);
source.dword_count = 4;
program.descriptor_sources.push_back(source);
program.info.buffers.push_back({.source = 0});
return std::make_shared<const Program>(std::move(program));
}
void TestMappedSrtUsesDirectReaderByDefault() {
using namespace Libs::Graphics;
const uint32_t dword = 0x12345678;
auto cached_program = SrtProgram(reinterpret_cast<uint64_t>(&dword));
ShaderStageRuntime stage;
std::string error;
uint32_t specialization_reads = 0;
Check(ShaderMaterializeStageRuntime(cached_program, {}, 0, stage, &error,
Check(ShaderMaterializeStageRuntime(cached_program, {}, 0, stage,
RejectSpecializationRead,
&specialization_reads),
error.c_str());
"mapped SRT stage materialization failed");
Check(specialization_reads == 0,
"ordinary SRT read used the specialization reader");
Check(stage.program == cached_program && stage.resources != nullptr,
@@ -104,16 +125,36 @@ void TestUnbasedFlatCacheHitMaterializes() {
auto prior_resources =
std::make_shared<const ShaderRecompiler::IR::ResourceSnapshot>();
ShaderStageRuntime stage{prior_program, prior_resources};
std::string error;
Check(ShaderMaterializeStageRuntime(cached_program, {}, 0, stage, &error), error.c_str());
Check(ShaderMaterializeStageRuntime(cached_program, {}, 0, stage),
"unbased FLAT stage materialization failed");
Check(stage.program == cached_program && stage.resources != prior_resources,
"unbased FLAT cache hit did not publish its BDA stage snapshot");
}
void TestFailedMaterializationPreservesPriorStage() {
using namespace Libs::Graphics;
auto cached_program = UserDataBufferProgram();
auto prior_program = std::make_shared<const ShaderRecompiler::IR::Program>();
auto prior_resources =
std::make_shared<const ShaderRecompiler::IR::ResourceSnapshot>();
ShaderStageRuntime stage{prior_program, prior_resources};
Check(!ShaderMaterializeStageRuntime(cached_program, {}, 0, stage),
"missing runtime user data did not reject the cached stage");
Check(stage.program == prior_program && stage.resources == prior_resources,
"failed cache materialization replaced the prior stage");
}
} // namespace
namespace Common {
int DbgExitHandler(const char *, int, std::string_view) { std::abort(); }
int DbgExitHandler(const char *, int, fmt::text_style, std::string_view) {
std::abort();
}
int DbgExitIfHandler(const char *, const char *, int) { return 1; }
void DbgExit(int) { std::abort(); }
@@ -123,6 +164,7 @@ void DbgExit(int) { std::abort(); }
int main() {
TestMappedSrtUsesDirectReaderByDefault();
TestUnbasedFlatCacheHitMaterializes();
TestFailedMaterializationPreservesPriorStage();
std::puts("ShaderStageRuntimeTests: all cases passed");
return 0;
}
@@ -130,7 +172,7 @@ int main() {
// Keep this focused standalone target self-contained by amalgamating its small
// typed-IR implementation set.
#include "graphics/shader/recompiler/ir/Block.cpp"
#include "graphics/shader/recompiler/ir/Program.cpp"
#include "graphics/shader/recompiler/ir/Type.cpp"
#include "graphics/shader/recompiler/ir/Value.cpp"
#include "graphics/shader/recompiler/ir/Program.cpp"
#include "graphics/shader/recompiler/ir/opcodes/ValueOpcodes.cpp"
+761 -1264
View File
File diff suppressed because it is too large. Load diff