Compare commits

...

28 Commits

Author SHA1 Message Date
nacimoualla 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
nmzik 9da390bc0a general: explicit SEC_COMMIT (Windows), default printf logging to silent 2026-08-29 15:31:20 +04:00
nmzik ef639610e7 shader: allow SDWA add-with-carry 2026-08-29 15:31:20 +04:00
Brandon Strong 6c3b562822 graphics: support Standard64KB color target arrays (#315)
graphics: support Standard64KB render target arrays

Co-authored-by: nmzik <Nmzik@mail.ru>
2026-08-29 15:18:44 +04:00
nmzik f1413569ce fix windows build 2026-08-29 14:46:54 +04:00
nmzik fcc9dff5b9 shader: DS_WRITE_B16_D16_HI 2026-08-29 14:40:13 +04:00
nmzik d9ae3ed64c shader: V_FFBH_I32 2026-08-29 14:03:57 +04:00
nmzik 618926b124 network: implement resolver destruction 2026-08-29 13:40:56 +04:00
nmzik dca7ece2ca shader: legalize 1D level-zero gathers 2026-08-29 13:32:20 +04:00
nmzik ccecebfeff loader: report unhandled host exceptions 2026-08-29 13:01:57 +04:00
nmzik 1752f1382f graphical: add per-game Vulkan pipeline caching (driver-side improvements only for now)
Co-authored-by: Ferran <ferran.duarri@me.com>
2026-08-29 12:28:48 +04:00
nmzik 53a59434d8 shader: implement V_CMP_EQ_U64 2026-08-29 11:26:05 +04:00
GodsWill Dev 18b6c9059b shader: support DS_READ_U16_D16_HI (#356)
Decode the high-half D16 LDS read and route it through the existing unsigned-short load lowering. Represent the destination as a high-half partial write so the low 16 bits of the VGPR remain intact. Add captured-instruction decode coverage and a Vulkan execution regression that verifies the preserved half.

Co-authored-by: SueryRD <the.ai.tester2@gmail.com>
2026-08-29 10:39:45 +04:00
GodsWill Dev 715539804b shader: accept SDWA for VOP1 bit operations (#360)
Allow byte, word, and full-dword SDWA source selection for V_NOT_B32 and V_FFBL_B32. Add captured instruction tests that verify decoding, IR extraction, SPIR-V generation, and runtime results.

Co-authored-by: SueryRD <the.ai.tester2@gmail.com>
2026-08-29 10:28:28 +04:00
nmzik b02eb28fd1 shader: support SDWA byte left-shift counts 2026-08-29 10:03:13 +04:00
nmzik 07f1e77d23 shader: support SDWA byte shift counts 2026-08-29 10:03:13 +04:00
Mehmet Cambaz 6980e0863d controller: forward guest vibration to SDL rumble (#365)
Scale the SDK 8-bit motor levels to SDL’s 16-bit range and use SDL’s maximum effect duration. Keep lookup and rumble under the controller lock so hot unplug cannot invalidate the SDL pointer.
2026-08-29 04:49:20 +04:00
Mehmet Cambaz 42bfeec2e9 emulator: run cleanup handlers on the quick_exit path (#334)
Run() registers KytyClose with atexit(), but Execute() ends the process with
std::quick_exit(), which invokes at_quick_exit handlers only. Nothing registered
one, so the cleanup never ran and neither did the emergency shutdown handlers
that close open guest files and flush the log.

Register a quick-exit handler that runs the emergency shutdown. It deliberately
skips RuntimeLinker::Clear(): guest threads are still live at that point and
tearing the linker down there deadlocks against a thread parked in a condition
wait.
2026-08-29 04:44:40 +04:00
GodsWill Dev 795743ce22 graphics: accept the encoded GE_CNTL group range (#363)
graphics: stop rejecting valid GE_CNTL group sizes

GE_CNTL group sizes control guest geometry-engine scheduling rather than host wave width. Do not reject the supported vertex path for compiler-selected values above 64, and remove the corresponding stale debug warnings.
2026-08-29 04:31:35 +04:00
GodsWill Dev ccddd6fb94 user: make the local profile configurable (#347)
user: make the local user ID configurable

Co-authored-by: nmzik <Nmzik@mail.ru>
2026-08-29 04:27:49 +04:00
nmzik e559771a08 libc: isolate guest setenv from host environment. Fix linux debugging 2026-08-29 03:57:09 +04:00
nmzik b870a77176 network: enable IPv4 UDP sockets on POSIX 2026-08-29 03:57:09 +04:00
nmzik 62eaead3a3 add configurable Vulkan present modes
Co-authored-by: Techx3 <56268269+Techx3@users.noreply.github.com>
2026-08-29 03:57:09 +04:00
nmzik d7808d9cb4 add configurable local user name
Co-authored-by: Techx3 <56268269+Techx3@users.noreply.github.com>
2026-08-29 03:57:09 +04:00
nmzik 8267c422b5 launcher: add 1440p/2160p
Co-authored-by: Techx3 <56268269+Techx3@users.noreply.github.com>
2026-08-29 03:57:09 +04:00
nmzik b46e141762 dev: add Linux overrides to VS Code launch configurations 2026-08-29 03:55:29 +04:00
nmzik cfa140fb12 window: create Vulkan surface visible on main thread 2026-08-29 01:31:02 +04:00
53 changed files with 1292 additions and 405 deletions
+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'
+1
View File
@@ -5,3 +5,4 @@ build/
_Build/vscode-clang/
_Build/
cmake-build-debug/
_PipelineCache/
+37 -3
View File
@@ -10,7 +10,14 @@
"stopAtEntry": false,
"cwd": "${workspaceFolder}\\_Build\\vscode-clang\\launcher",
"environment": [],
"externalConsole": false
"externalConsole": false,
"linux": {
"type": "cppdbg",
"program": "${workspaceFolder}/_Build/vscode-clang/launcher/launcher",
"cwd": "${workspaceFolder}/_Build/vscode-clang/launcher",
"MIMode": "gdb",
"miDebuggerPath": "/usr/bin/gdb"
}
},
{
"name": "Debug kyty_emulator",
@@ -25,13 +32,40 @@
"stopAtEntry": false,
"cwd": "${workspaceFolder}\\_Build\\vscode-clang",
"environment": [],
"externalConsole": true
"externalConsole": true,
"linux": {
"type": "cppdbg",
"program": "${workspaceFolder}/_Build/vscode-clang/kyty_emulator",
"cwd": "${workspaceFolder}/_Build/vscode-clang",
"externalConsole": false,
"MIMode": "gdb",
"miDebuggerPath": "/usr/bin/gdb",
"setupCommands": [
{
"description": "Pass Kyty's internal signals to its handlers",
"text": "handle SIG37 SIGSEGV SIGBUS SIGILL nostop noprint pass",
"ignoreFailures": false
}
]
}
},
{
"name": "Attach to running kyty_emulator",
"type": "cppvsdbg",
"request": "attach",
"processId": "${command:pickProcess}"
"processId": "${command:pickProcess}",
"linux": {
"type": "cppdbg",
"MIMode": "gdb",
"miDebuggerPath": "/usr/bin/gdb",
"setupCommands": [
{
"description": "Pass Kyty's internal signals to its handlers",
"text": "handle SIG37 SIGSEGV SIGBUS SIGILL nostop noprint pass",
"ignoreFailures": false
}
]
}
}
]
}
+10
View File
@@ -524,6 +524,16 @@ if(NOT KYTY_CLANG_CL)
endif()
if(BUILD_TESTING)
add_test(NAME emulator_user_name_cli
COMMAND $<TARGET_FILE:kyty_emulator> --help --user-name 1234567890123456)
add_test(NAME emulator_user_name_too_long
COMMAND $<TARGET_FILE:kyty_emulator> --help --user-name 12345678901234567)
set_tests_properties(emulator_user_name_too_long PROPERTIES WILL_FAIL TRUE)
add_test(NAME emulator_present_mode_cli
COMMAND $<TARGET_FILE:kyty_emulator> --help --present-mode Immediate)
add_test(NAME emulator_present_mode_invalid
COMMAND $<TARGET_FILE:kyty_emulator> --help --present-mode Invalid)
set_tests_properties(emulator_present_mode_invalid PROPERTIES WILL_FAIL TRUE)
add_test(NAME ime_dialog COMMAND $<TARGET_FILE:ime_dialog_tests>)
add_test(NAME shader_cfg COMMAND $<TARGET_FILE:shader_cfg_tests>)
add_test(NAME scalar_provenance COMMAND $<TARGET_FILE:scalar_provenance_tests>)
+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
+14
View File
@@ -21,6 +21,8 @@ void Shutdown() {
void Load(const ConfigOptions& cfg) {
EXIT_IF(g_config == nullptr);
EXIT_IF(cfg.user_name.empty() || cfg.user_name.size() > MAX_USER_NAME_LENGTH);
EXIT_IF(!IsConfiguredUserIdValid(cfg.user_id));
*g_config = cfg;
}
@@ -33,6 +35,18 @@ uint32_t GetScreenHeight() {
return g_config->screen_height;
}
const std::string& GetUserName() {
return g_config->user_name;
}
int32_t GetUserId() {
return g_config->user_id;
}
PresentMode GetPresentMode() {
return g_config->present_mode;
}
bool FullscreenEnabled() {
return g_config->fullscreen_enabled;
}
+18 -1
View File
@@ -3,6 +3,7 @@
#include "common/common.h"
#include <cstddef>
#include <filesystem>
#include <string>
#include <vector>
@@ -26,14 +27,27 @@ enum class ProfilerDirection { None, Network };
enum class OutputDirection { Silent, Console, File };
enum class PresentMode { Fifo, Mailbox, Immediate };
using Keymap = std::vector<std::string>;
constexpr uint32_t DEFAULT_CONSOLE_LANGUAGE = 1;
constexpr uint32_t MAX_CONSOLE_LANGUAGE = 29;
constexpr std::size_t MAX_USER_NAME_LENGTH = 16;
constexpr int32_t DEFAULT_USER_ID = 1000;
constexpr bool IsConfiguredUserIdValid(int32_t user_id) {
constexpr int32_t USER_ID_EVERYONE = 0xfe;
constexpr int32_t USER_ID_SYSTEM = 0xff;
return user_id >= 0 && user_id != USER_ID_EVERYONE && user_id != USER_ID_SYSTEM;
}
struct ConfigOptions {
uint32_t screen_width = 1280;
uint32_t screen_height = 720;
std::string user_name = "Kyty";
int32_t user_id = DEFAULT_USER_ID;
PresentMode present_mode = PresentMode::Fifo;
bool fullscreen_enabled = false;
uint32_t vblank_frequency = 60;
uint32_t console_language = DEFAULT_CONSOLE_LANGUAGE;
@@ -45,7 +59,7 @@ struct ConfigOptions {
bool command_buffer_dump_enabled = false;
std::filesystem::path command_buffer_dump_folder = "_Buffers";
bool graphics_debug_dump_enabled = false;
OutputDirection printf_direction = OutputDirection::Console;
OutputDirection printf_direction = OutputDirection::Silent;
std::filesystem::path printf_output_file = "_kyty.txt";
ProfilerDirection profiler_direction = ProfilerDirection::None;
bool spirv_debug_printf_enabled = false;
@@ -63,6 +77,9 @@ void Load(const ConfigOptions& cfg);
uint32_t GetScreenWidth();
uint32_t GetScreenHeight();
const std::string& GetUserName();
int32_t GetUserId();
PresentMode GetPresentMode();
bool FullscreenEnabled();
uint32_t GetVblankFrequency();
uint32_t GetConsoleLanguage();
+3 -6
View File
@@ -25,9 +25,6 @@
#ifdef CopyFile
#undef CopyFile
#endif
#ifdef MoveFile
#undef MoveFile
#endif
#endif
namespace Common {
@@ -544,14 +541,14 @@ bool File::CopyFile(const std::filesystem::path& src,
return SysFileCopyFile(src, dst);
}
bool File::MoveFile(const std::filesystem::path& src,
const std::filesystem::path& dst) // @suppress("Member declaration not found")
bool File::RenameFile(const std::filesystem::path& src,
const std::filesystem::path& dst) // @suppress("Member declaration not found")
{
if (IsFileExisting(dst)) {
DeleteFile(dst); // @suppress("Invalid arguments")
}
return SysFileMoveFile(src, dst);
return SysFileRenameFile(src, dst);
}
void File::RemoveReadonly(const std::filesystem::path& name) {
+1 -4
View File
@@ -20,9 +20,6 @@
#ifdef CopyFile
#undef CopyFile
#endif
#ifdef MoveFile
#undef MoveFile
#endif
#endif
namespace Common {
@@ -107,7 +104,7 @@ public:
static std::vector<FindInfo> FindFiles(const std::filesystem::path& path);
static std::vector<DirEntry> GetDirEntries(const std::filesystem::path& path);
static bool CopyFile(const std::filesystem::path& src, const std::filesystem::path& dst);
static bool MoveFile(const std::filesystem::path& src, const std::filesystem::path& dst);
static bool RenameFile(const std::filesystem::path& src, const std::filesystem::path& dst);
static void RemoveReadonly(const std::filesystem::path& name);
KYTY_CLASS_NO_COPY(File);
+1 -1
View File
@@ -80,7 +80,7 @@ void SysFileFindFiles(const std::filesystem::path& path, std::vector<sys_file_fi
// NOLINTNEXTLINE(google-runtime-references)
void SysFileGetDents(const std::filesystem::path& path, std::vector<sys_dir_entry_t>& out);
bool SysFileCopyFile(const std::filesystem::path& src, const std::filesystem::path& dst);
bool SysFileMoveFile(const std::filesystem::path& src, const std::filesystem::path& dst);
bool SysFileRenameFile(const std::filesystem::path& src, const std::filesystem::path& dst);
void SysFileRemoveReadonly(const std::filesystem::path& name);
#endif /* KYTY_COMMON_PLATFORM_SYSFILEIO_H_ */
+1 -1
View File
@@ -693,7 +693,7 @@ bool SysFileCopyFile(const std::filesystem::path& src, const std::filesystem::pa
!error;
}
bool SysFileMoveFile(const std::filesystem::path& src, const std::filesystem::path& dst) {
bool SysFileRenameFile(const std::filesystem::path& src, const std::filesystem::path& dst) {
auto real_src = get_internal_name(src);
auto real_dst = get_internal_name(dst);
+1 -1
View File
@@ -565,7 +565,7 @@ bool SysFileCopyFile(const std::filesystem::path& src, const std::filesystem::pa
return CopyFileW(src_wide.c_str(), dst_wide.c_str(), FALSE) != 0;
}
bool SysFileMoveFile(const std::filesystem::path& src, const std::filesystem::path& dst) {
bool SysFileRenameFile(const std::filesystem::path& src, const std::filesystem::path& dst) {
auto src_wide = src.wstring();
auto dst_wide = dst.wstring();
return MoveFileW(src_wide.c_str(), dst_wide.c_str()) != 0;
+4
View File
@@ -200,6 +200,10 @@ void Run(const RunOptions& options) {
int ok = atexit(KytyClose);
EXIT_NOT_IMPLEMENTED(ok != 0);
// Guest threads are still running, so skip KytyClose() and only flush emergency state.
ok = at_quick_exit(Common::Subsystems::EmergencyShutdownActive);
EXIT_NOT_IMPLEMENTED(ok != 0);
Libs::LibKernel::FileSystem::Mount(options.app0_dir, "/app0");
Libs::LibKernel::FileSystem::Mount(options.app0_dir, "/hostapp");
+5 -2
View File
@@ -1,5 +1,6 @@
set(KYTY_GIT_VERSION "unknown")
set(KYTY_GIT_HASH "unknown")
set(KYTY_GIT_REVISION "unknown")
if(GIT_EXECUTABLE)
execute_process(
COMMAND "${GIT_EXECUTABLE}" describe --tags --always --dirty
@@ -14,16 +15,18 @@ if(GIT_EXECUTABLE)
endif()
execute_process(
COMMAND "${GIT_EXECUTABLE}" rev-parse --short=7 HEAD
COMMAND "${GIT_EXECUTABLE}" rev-parse HEAD
WORKING_DIRECTORY "${GIT_WORKING_DIRECTORY}"
OUTPUT_VARIABLE KYTY_GIT_HASH
OUTPUT_VARIABLE KYTY_GIT_REVISION
OUTPUT_STRIP_TRAILING_WHITESPACE
RESULT_VARIABLE GIT_HASH_RESULT
ERROR_QUIET
)
if(NOT GIT_HASH_RESULT EQUAL 0)
set(KYTY_GIT_HASH "unknown")
set(KYTY_GIT_REVISION "unknown")
else()
string(SUBSTRING "${KYTY_GIT_REVISION}" 0 7 KYTY_GIT_HASH)
execute_process(
COMMAND "${GIT_EXECUTABLE}" diff-index --quiet HEAD --
WORKING_DIRECTORY "${GIT_WORKING_DIRECTORY}"
@@ -219,8 +219,9 @@ void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, CommandBuffer&
rt.attrib.num_fragments, bytes_per_element, rt.cmask.addr, rt.fmask.addr,
rt.dcc_addr.addr);
}
if ((standard64 || depth_tile) && (rt.attrib3.dimension != 1 || rt.attrib3.depth != 0 ||
view.base_layer != 0 || view.image_layers != 1)) {
if ((standard64 || depth_tile) &&
(rt.attrib3.dimension != 1 || rt.attrib3.depth != 0 ||
(depth_tile && (view.base_layer != 0 || view.image_layers != 1)))) {
EXIT("unsupported 64KB texture-tiled render-target view: dimension=%u depth=%u"
" layer=%u/%u\n",
rt.attrib3.dimension, rt.attrib3.depth, view.base_layer, view.image_layers);
-9
View File
@@ -84,17 +84,8 @@ void uc_print(const char* func, const HW::UserConfig& uc) {
}
void uc_check(const HW::UserConfig& uc) {
const auto& ge_cntl = uc.GetGeControl();
const auto& user_en = uc.GetGeUserVgprEn();
if (ge_cntl.primitive_group_size > 0x0040) {
LOGF("\t warning: unsupported GE_CNTL primitive_group_size = 0x%04" PRIx16 "\n",
ge_cntl.primitive_group_size);
}
if (ge_cntl.vertex_group_size > 0x0040) {
LOGF("\t warning: unsupported GE_CNTL vertex_group_size = 0x%04" PRIx16 "\n",
ge_cntl.vertex_group_size);
}
EXIT_NOT_IMPLEMENTED(user_en.vgpr1 != false);
EXIT_NOT_IMPLEMENTED(user_en.vgpr2 != false);
EXIT_NOT_IMPLEMENTED(user_en.vgpr3 != false);
@@ -1,6 +1,7 @@
#include "graphics/host_gpu/renderer/pipeline/pipelineCache.h"
#include "common/assert.h"
#include "common/file.h"
#include "common/logging/log.h"
#include "common/profiler.h"
#include "graphics/guest_gpu/hardwareContext.h"
@@ -11,18 +12,64 @@
#include "graphics/host_gpu/renderer/render.h"
#include "graphics/host_gpu/renderer/renderContext.h"
#include "graphics/shader/shaderCompiler.h"
#include "kytyGitVersion.h"
#include "loader/systemContent.h"
#include <algorithm>
#include <atomic>
#include <cctype>
#include <cstdio>
#include <cstring>
#include <limits>
#include <span>
#include <string_view>
#include <utility>
#include <vector>
#include <fmt/format.h>
#include <xxhash.h>
namespace Libs::Graphics {
namespace {
std::string DriverCacheSignature(const vk::PhysicalDeviceProperties& properties) {
constexpr char hex[] = "0123456789abcdef";
std::string uuid(VK_UUID_SIZE * 2, '0');
for (size_t i = 0; i < VK_UUID_SIZE; i++) {
uuid[i * 2] = hex[properties.pipelineCacheUUID[i] >> 4u];
uuid[i * 2 + 1] = hex[properties.pipelineCacheUUID[i] & 0xfu];
}
return fmt::format("KytyPC1:{}:{:08x}:{:08x}:{:08x}:{}\n", KYTY_GIT_REVISION,
properties.vendorID, properties.deviceID, properties.driverVersion, uuid);
}
std::string PipelineCacheTitleId() {
std::string title_id;
if ((!Loader::SystemContentParamSfoGetString("TITLE_ID", &title_id) || title_id.empty()) &&
(!Loader::SystemContentParamSfoGetString("CONTENT_ID", &title_id) || title_id.empty())) {
return {};
}
if (!std::ranges::all_of(title_id, [](unsigned char c) {
return std::isalnum(c) != 0 || c == '-' || c == '_';
})) {
return {};
}
return title_id;
}
template <typename... Args>
void PipelineCacheLog(fmt::format_string<Args...> format, Args&&... args) {
auto message = fmt::format(format, std::forward<Args>(args)...);
message += '\n';
if (Log::GetDirection() != Log::Direction::Console) {
std::fwrite(message.data(), 1, message.size(), stdout);
std::fflush(stdout);
}
Log::Write(message);
Log::Flush();
}
void NormalizeStaticParamsForDynamicState(PipelineStaticParameters& static_params) {
static_params.viewport_scale[0] = 0.5f;
static_params.viewport_scale[1] = 0.5f;
@@ -119,9 +166,11 @@ struct PipelineCache::ProgramCache {
PipelineCache::PipelineCache(GraphicContext& graphics)
: m_graphics(graphics), m_program_cache(std::make_unique<ProgramCache>(graphics.device)) {
EXIT_NOT_IMPLEMENTED(!Common::Thread::IsMainThread());
InitializeDriverCache();
}
PipelineCache::~PipelineCache() {
Save();
auto destroy = [this](const auto& pipelines) {
for (const auto& [key, pipeline]: pipelines) {
(void)key;
@@ -138,6 +187,155 @@ PipelineCache::~PipelineCache() {
m_graphics.device.destroyShaderModule(permutation.handle.module, nullptr);
}
}
if (m_driver_cache != nullptr) {
m_graphics.device.destroyPipelineCache(m_driver_cache, nullptr);
}
}
void PipelineCache::InitializeDriverCache() {
const auto title_id = PipelineCacheTitleId();
if (title_id.empty()) {
return;
}
if (KYTY_BUILD != KYTY_BUILD_RELEASE) {
PipelineCacheLog("Vulkan pipeline cache: disabled (non-Release build)");
return;
}
const std::string_view git_hash = KYTY_GIT_HASH;
const std::string_view git_revision = KYTY_GIT_REVISION;
if (git_hash == "unknown" || git_revision == "unknown") {
PipelineCacheLog("Vulkan pipeline cache: disabled (unknown git revision)");
return;
}
if (git_hash.ends_with("-dirty")) {
PipelineCacheLog("Vulkan pipeline cache: disabled (dirty build)");
return;
}
m_driver_cache_path = std::filesystem::path("_PipelineCache") / (title_id + ".bin");
const auto path = Common::PathToString(m_driver_cache_path);
const bool cache_exists = Common::File::IsFileExisting(m_driver_cache_path);
if (cache_exists) {
PipelineCacheLog("Vulkan pipeline cache: loading {}", path);
} else {
PipelineCacheLog("Vulkan pipeline cache: initializing {}", path);
}
std::vector<uint8_t> initial_data;
if (cache_exists) {
Common::File file(m_driver_cache_path, Common::File::Mode::Read);
const auto file_size = file.IsInvalid() ? 0 : file.Size();
const auto signature = DriverCacheSignature(m_graphics.GetPhysicalDeviceProperties());
if (file_size >= signature.size() + sizeof(uint64_t) &&
file_size <= std::numeric_limits<uint32_t>::max()) {
std::string cached_signature(signature.size(), '\0');
uint64_t payload_hash = 0;
initial_data.resize(file_size - signature.size() - sizeof(payload_hash));
uint32_t signature_read = 0;
uint32_t hash_read = 0;
uint32_t payload_read = 0;
file.Read(cached_signature.data(), static_cast<uint32_t>(cached_signature.size()),
&signature_read);
file.Read(&payload_hash, sizeof(payload_hash), &hash_read);
file.Read(initial_data.data(), static_cast<uint32_t>(initial_data.size()), &payload_read);
file.Close();
if (signature_read != cached_signature.size() || hash_read != sizeof(payload_hash) ||
payload_read != initial_data.size() || cached_signature != signature ||
XXH3_64bits(initial_data.data(), initial_data.size()) != payload_hash) {
initial_data.clear();
PipelineCacheLog(
"Vulkan pipeline cache: invalidating {} (driver, emulator, or data mismatch)",
path);
}
} else {
file.Close();
PipelineCacheLog("Vulkan pipeline cache: invalidating {} (invalid file size)",
path);
}
}
vk::PipelineCacheCreateInfo create {};
create.sType = vk::StructureType::ePipelineCacheCreateInfo;
create.initialDataSize = initial_data.size();
create.pInitialData = initial_data.empty() ? nullptr : initial_data.data();
auto result = m_graphics.device.createPipelineCache(&create, nullptr, &m_driver_cache);
if (result != vk::Result::eSuccess && !initial_data.empty()) {
PipelineCacheLog("Vulkan pipeline cache: driver rejected {} ({}); starting empty", path,
VulkanToString(result));
initial_data.clear();
create.initialDataSize = 0;
create.pInitialData = nullptr;
result = m_graphics.device.createPipelineCache(&create, nullptr, &m_driver_cache);
}
if (result != vk::Result::eSuccess) {
PipelineCacheLog("Vulkan pipeline cache: disabled ({})", VulkanToString(result));
m_driver_cache = nullptr;
return;
}
if (!initial_data.empty()) {
PipelineCacheLog("Vulkan pipeline cache: loaded {} bytes from {}", initial_data.size(),
path);
} else {
PipelineCacheLog("Vulkan pipeline cache: initialized empty");
}
}
void PipelineCache::Save() {
Common::LockGuard lock(m_mutex);
if (m_driver_cache == nullptr) {
return;
}
size_t size = 0;
vk::Result result;
std::vector<uint8_t> payload;
for (uint32_t attempt = 0; attempt < 3; attempt++) {
size = 0;
result = m_graphics.device.getPipelineCacheData(m_driver_cache, &size, nullptr);
if (result != vk::Result::eSuccess || size == 0 ||
size > std::numeric_limits<uint32_t>::max()) {
break;
}
payload.resize(size);
result = m_graphics.device.getPipelineCacheData(m_driver_cache, &size, payload.data());
if (result != vk::Result::eIncomplete) {
break;
}
}
if (result != vk::Result::eSuccess || size == 0 ||
size > std::numeric_limits<uint32_t>::max()) {
PipelineCacheLog("Vulkan pipeline cache: save failed ({}, {} bytes)",
VulkanToString(result), size);
return;
}
payload.resize(size);
auto prefix = DriverCacheSignature(m_graphics.GetPhysicalDeviceProperties());
const auto payload_hash = XXH3_64bits(payload.data(), payload.size());
prefix.append(reinterpret_cast<const char*>(&payload_hash), sizeof(payload_hash));
if (!Common::File::CreateDirectories(m_driver_cache_path.parent_path())) {
PipelineCacheLog("Vulkan pipeline cache: failed to create cache directory");
return;
}
auto temp_path = m_driver_cache_path;
temp_path += ".tmp";
Common::File file;
uint32_t prefix_written = 0;
uint32_t payload_written = 0;
if (file.Create(temp_path)) {
file.Write(prefix.data(), static_cast<uint32_t>(prefix.size()), &prefix_written);
file.Write(payload.data(), static_cast<uint32_t>(payload.size()), &payload_written);
}
const bool flushed = !file.IsInvalid() && file.Flush();
file.Close();
if (prefix_written != prefix.size() || payload_written != payload.size() || !flushed ||
!Common::File::RenameFile(temp_path, m_driver_cache_path)) {
PipelineCacheLog("Vulkan pipeline cache: failed to write {}",
Common::PathToString(m_driver_cache_path));
return;
}
PipelineCacheLog("Vulkan pipeline cache: saved {} bytes to {}", payload.size(),
Common::PathToString(m_driver_cache_path));
m_graphics.device.destroyPipelineCache(m_driver_cache, nullptr);
m_driver_cache = nullptr;
}
ShaderProgram PipelineCache::GetVertexProgram(const HW::VertexShaderInfo& regs,
@@ -324,7 +522,7 @@ PipelineCache::GraphicsPipeline& PipelineCache::CreateGraphicsPipeline(
auto cached = std::make_unique<GraphicsPipeline>(p);
LogPipelineTrace("CreatePipelineInternal begin", vs_id, ps_id);
CreatePipelineInternal(m_graphics, *cached, rendering, vs_input_info, vertex_program.module,
ps_input_info, pixel_program.module, static_params);
ps_input_info, pixel_program.module, static_params, m_driver_cache);
LogPipelineTrace("CreatePipelineInternal done", vs_id, ps_id);
EXIT_NOT_IMPLEMENTED(cached->pipeline == nullptr);
@@ -360,7 +558,7 @@ PipelineCache::CreateComputePipeline(ShaderComputeInputInfo& input_info,
}
auto cached = std::make_unique<ComputePipeline>(p);
CreatePipelineInternal(m_graphics, *cached, input_info, compute_program.module);
CreatePipelineInternal(m_graphics, *cached, input_info, compute_program.module, m_driver_cache);
EXIT_NOT_IMPLEMENTED(cached->pipeline == nullptr);
EXIT_NOT_IMPLEMENTED(cached->pipeline_layout == nullptr);
@@ -10,6 +10,7 @@
#include "graphics/shader/shader.h"
#include <cstddef>
#include <filesystem>
#include <memory>
#include <span>
#include <type_traits>
@@ -109,6 +110,7 @@ public:
explicit PipelineCache(GraphicContext& graphics);
~PipelineCache();
KYTY_CLASS_NO_COPY(PipelineCache);
void Save();
struct Pipeline {
vk::PipelineLayout pipeline_layout = nullptr;
@@ -214,12 +216,16 @@ private:
GraphicContext& m_graphics;
std::unique_ptr<ProgramCache> m_program_cache;
vk::PipelineCache m_driver_cache = nullptr;
std::filesystem::path m_driver_cache_path;
std::unordered_map<GraphicsPipelineKey, std::unique_ptr<GraphicsPipeline>,
GraphicsPipelineKeyHash>
m_graphics_pipelines;
std::unordered_map<ComputePipelineKey, std::unique_ptr<ComputePipeline>, ComputePipelineKeyHash>
m_compute_pipelines;
Common::Mutex m_mutex;
void InitializeDriverCache();
};
void LogPipelineTrace(const char* phase, uint64_t vertex_program_id, uint64_t pixel_program_id);
@@ -227,10 +233,11 @@ void CreatePipelineInternal(
GraphicContext& graphics, PipelineCache::GraphicsPipeline& pipeline,
const PipelineRenderingState& rendering, const ShaderVertexInputInfo& vs_input_info,
vk::ShaderModule vertex_module, const ShaderPixelInputInfo* ps_input_info,
vk::ShaderModule pixel_module, const PipelineStaticParameters& static_params);
vk::ShaderModule pixel_module, const PipelineStaticParameters& static_params,
vk::PipelineCache driver_cache);
void CreatePipelineInternal(GraphicContext& graphics, PipelineCache::ComputePipeline& pipeline,
const ShaderComputeInputInfo& input_info,
vk::ShaderModule compute_module);
vk::ShaderModule compute_module, vk::PipelineCache driver_cache);
} // namespace Libs::Graphics
@@ -423,7 +423,8 @@ void CreatePipelineInternal(
GraphicContext& graphics, PipelineCache::GraphicsPipeline& pipeline,
const PipelineRenderingState& rendering, const ShaderVertexInputInfo& vs_input_info,
vk::ShaderModule vertex_module, const ShaderPixelInputInfo* ps_input_info,
vk::ShaderModule pixel_module, const PipelineStaticParameters& static_params) {
vk::ShaderModule pixel_module, const PipelineStaticParameters& static_params,
vk::PipelineCache driver_cache) {
const bool ps_active = ps_input_info != nullptr;
EXIT_IF(vertex_module == nullptr || (ps_active && pixel_module == nullptr));
@@ -940,7 +941,7 @@ void CreatePipelineInternal(
viewport.y, viewport.width, viewport.height, scissor.offset.x, scissor.offset.y,
scissor.extent.width, scissor.extent.height);
}
result = graphics.device.createGraphicsPipelines(nullptr, 1, &pipeline_info, nullptr,
result = graphics.device.createGraphicsPipelines(driver_cache, 1, &pipeline_info, nullptr,
&pipeline.pipeline);
if (graphics_debug_dump_enabled()) {
LOGF("PipelineTrace: vkCreateGraphicsPipelines done result=%s pipeline=%p\n",
@@ -961,7 +962,7 @@ void CreatePipelineInternal(
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
void CreatePipelineInternal(GraphicContext& graphics, PipelineCache::ComputePipeline& pipeline,
const ShaderComputeInputInfo& input_info,
vk::ShaderModule compute_module) {
vk::ShaderModule compute_module, vk::PipelineCache driver_cache) {
EXIT_IF(compute_module == nullptr);
vk::PipelineShaderStageCreateInfo comp_shader_stage_info {};
@@ -1024,7 +1025,8 @@ void CreatePipelineInternal(GraphicContext& graphics, PipelineCache::ComputePipe
LOGF("PipelineTrace: vkCreateComputePipelines begin layout=%p\n",
static_cast<void*>(pipeline.pipeline_layout));
result = graphics.device.createComputePipelines(nullptr, 1, &info, nullptr, &pipeline.pipeline);
result = graphics.device.createComputePipelines(driver_cache, 1, &info, nullptr,
&pipeline.pipeline);
LOGF("PipelineTrace: vkCreateComputePipelines done result=%s pipeline=%p\n",
VulkanToString(result).c_str(), static_cast<void*>(pipeline.pipeline));
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess);
@@ -450,15 +450,14 @@ static bool ShouldSkipGeShader(const CommandBuffer& buffer) {
const bool unsupported_stage_mask = (stages != 0 && stages != 0x02002000);
const bool unsupported_gs_stage = (vertex_info.es_regs.data_addr != 0 &&
vertex_info.gs_regs.data_addr != 0 && !ps5_ngg_vertex_path);
const bool ge_group_size =
ge_cntl.primitive_group_size > 0x0040 || ge_cntl.vertex_group_size > 0x0040;
// GE_CNTL group sizes control guest scheduling and do not constrain the host vertex path.
const bool ge_shader_regs =
(sh_regs.m_geNggSubgrpCntl != 0x00000000 && sh_regs.m_geNggSubgrpCntl != 0x00000001) ||
sh_regs.m_vgtGsMaxVertOut != 0x00000000 ||
!is_known_gs_out_prim_type(sh_regs.m_vgtGsOutPrimType) ||
sh_regs.m_geMaxOutputPerSubgroup > 0x00000040;
if (unsupported_stage_mask || unsupported_gs_stage || ge_group_size || ge_shader_regs) {
if (unsupported_stage_mask || unsupported_gs_stage || ge_shader_regs) {
static std::once_flag warning_once;
std::call_once(warning_once, [] {
std::printf("Warning: game uses unsupported graphics pipelines; some draw calls were "
+20 -71
View File
@@ -1,30 +1,9 @@
#include "SDL.h"
#include "SDL_error.h"
#include "SDL_events.h"
#include "SDL_gamecontroller.h"
#include "SDL_hints.h"
#include "SDL_joystick.h"
#include "SDL_keyboard.h"
#include "SDL_keycode.h"
#include "SDL_mouse.h"
#include "SDL_pixels.h"
#include "SDL_rwops.h"
#include "SDL_stdinc.h"
#include "SDL_surface.h"
#include "SDL_thread.h"
#include "SDL_touch.h"
#include "SDL_video.h"
#include "SDL_vulkan.h"
#include "common/assert.h"
#include "common/common.h"
#include "common/emulatorConfig.h"
#include "common/file.h"
#include "common/logging/log.h"
#include "common/profiler.h"
#include "common/stringUtils.h"
#include "common/systemInfo.h"
#include "common/threads.h"
#include "common/timer.h"
#include "graphics/host_gpu/graphicContext.h"
#include "graphics/host_gpu/renderer/render.h"
#include "graphics/host_gpu/renderer/renderContext.h"
@@ -34,19 +13,11 @@
#include "graphics/presentation/presenter.h"
#include "graphics/presentation/videoOut.h"
#include "graphics/presentation/window/windowInternal.h"
#include "libs/controller.h"
#include "loader/systemContent.h"
#include <algorithm>
#include <array>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <deque>
#include <fmt/format.h>
#include <limits>
#include <memory>
#include <string>
#include <vector>
#include <vulkan/vk_platform.h>
@@ -334,7 +305,6 @@ public:
private:
void Destroy();
void RefreshSurfaceSize();
WindowContext& m_window;
vk::SwapchainKHR m_handle = nullptr;
@@ -396,12 +366,12 @@ struct Presenter::Impl {
void Swapchain::Create() {
auto& graphics = m_window.graphic_ctx;
EXIT_IF(graphics.screen_width == 0);
EXIT_IF(graphics.screen_height == 0);
EXIT_IF(graphics.device == nullptr);
EXIT_IF(m_window.surface == nullptr);
Common::LockGuard lock(m_window.mutex);
EXIT_IF(graphics.screen_width == 0);
EXIT_IF(graphics.screen_height == 0);
const auto& surface = m_window.surface_capabilities;
EXIT_NOT_IMPLEMENTED(surface.formats.empty());
@@ -459,7 +429,21 @@ void Swapchain::Create() {
create_info.imageSharingMode = vk::SharingMode::eExclusive;
create_info.preTransform = transform;
create_info.compositeAlpha = composite;
create_info.presentMode = vk::PresentModeKHR::eFifo;
switch (Config::GetPresentMode()) {
case Config::PresentMode::Mailbox:
create_info.presentMode = vk::PresentModeKHR::eMailbox;
break;
case Config::PresentMode::Immediate:
create_info.presentMode = vk::PresentModeKHR::eImmediate;
break;
case Config::PresentMode::Fifo:
default: create_info.presentMode = vk::PresentModeKHR::eFifo; break;
}
if (std::find(surface.present_modes.begin(), surface.present_modes.end(),
create_info.presentMode) == surface.present_modes.end()) {
LOGF("warning: requested present mode is unavailable; falling back to Fifo\n");
create_info.presentMode = vk::PresentModeKHR::eFifo;
}
create_info.clipped = VK_TRUE;
RequireVulkanSuccess(graphics.device.createSwapchainKHR(&create_info, nullptr, &m_handle),
"vkCreateSwapchainKHR");
@@ -554,30 +538,18 @@ void Swapchain::Destroy() {
m_render_complete.clear();
}
void Swapchain::RefreshSurfaceSize() {
int width = 0;
int height = 0;
SDL_Vulkan_GetDrawableSize(m_window.window, &width, &height);
if (width > 0 && height > 0) {
m_window.graphic_ctx.screen_width = static_cast<uint32_t>(width);
m_window.graphic_ctx.screen_height = static_cast<uint32_t>(height);
}
m_window.RefreshSurfaceCapabilities();
}
void Swapchain::Recreate(bool surface_lost) {
Destroy();
if (surface_lost) {
#if defined(__APPLE__)
// Surface recreation goes through SDL_Vulkan_CreateSurface, which touches the
// window's view/layer and must run on the main thread on macOS.
m_window.RunOnMainThread([this] { m_window.RecreateSurface(); }, true);
m_window.RunOnMainThread([this] { m_window.RecreateSurface(); });
#else
m_window.RecreateSurface();
#endif
}
RefreshSurfaceSize();
m_window.RefreshSurfaceCapabilities();
Create();
}
@@ -803,29 +775,6 @@ void Presenter::Present(Frame& frame, bool reuse) {
KYTY_PROFILER_FUNCTION();
m_impl->frames.ValidateForPresent(&frame, reuse);
auto& window = m_impl->window;
if (window.window_hidden) {
#if defined(__APPLE__)
// AppKit traps if a window is shown off the main thread; marshal and wait so the
// swapchain below is recreated against a visible window.
window.RunOnMainThread(
[&window] {
window.UpdateIcon();
SDL_ShowWindow(window.window);
SDL_RaiseWindow(window.window);
},
true);
#else
window.UpdateIcon();
SDL_ShowWindow(window.window);
SDL_RaiseWindow(window.window);
#endif
window.window_hidden = false;
m_impl->RecoverSwapchain(Swapchain::Status::Recreate);
}
const auto ime_visual = GetImeVisualState();
auto& swapchain = m_impl->swapchain;
for (uint32_t attempt = 0; attempt < 2; attempt++) {
@@ -848,7 +797,7 @@ void Presenter::Present(Frame& frame, bool reuse) {
}
m_impl->presented_ime_revision.store(ime_visual.revision, std::memory_order_release);
window.UpdateTitle();
m_impl->window.UpdateTitle();
m_impl->frames.Release(&frame, true);
return;
}
@@ -22,7 +22,6 @@
#include "common/logging/log.h"
#include "common/profiler.h"
#include "common/stringUtils.h"
#include "common/systemInfo.h"
#include "common/threads.h"
#include "common/timer.h"
#include "graphics/host_gpu/graphicContext.h"
@@ -943,6 +942,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();
@@ -1037,10 +1048,6 @@ void WindowContext::CreateVulkan() {
}
}
memcpy(device_name, device_properties.deviceName, sizeof(device_name));
std::snprintf(processor_name, sizeof(processor_name), "%s",
Common::GetSystemInfo().ProcessorName.c_str());
VulkanInitSubgroupSizeControl(graphic_ctx.physical_device, graphic_ctx);
graphic_ctx.device = VulkanCreateDevice(graphic_ctx.physical_device, r, queue_family,
@@ -1079,7 +1086,6 @@ void WindowContext::RecreateSurface() {
EXIT("Could not recreate the Vulkan surface: %s\n", SDL_GetError());
}
surface = native_surface;
RefreshSurfaceCapabilities();
}
WindowContext::~WindowContext() {
+38 -30
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"
@@ -23,12 +24,12 @@
#include "common/file.h"
#include "common/logging/log.h"
#include "common/profiler.h"
#include "common/stringUtils.h"
#include "common/systemInfo.h"
#include "common/threads.h"
#include "common/timer.h"
#include "graphics/host_gpu/graphicContext.h"
#include "graphics/host_gpu/renderer/render.h"
#include "graphics/host_gpu/renderer/renderContext.h"
#include "graphics/host_gpu/vma.h"
#include "graphics/host_gpu/vulkanCommon.h"
#include "graphics/presentation/imeOverlay.h"
@@ -39,10 +40,8 @@
#include "libs/controller.h"
#include "loader/systemContent.h"
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <fmt/format.h>
#include <memory>
#include <string>
#include <vector>
@@ -52,8 +51,6 @@
#define STBI_NO_SIMD
#include "stb_image.h"
#include <fmt/format.h>
// IWYU pragma: no_include <intrin.h>
#define KYTY_ENABLE_DEBUG_PRINTF
@@ -197,8 +194,6 @@ std::unique_ptr<WindowContext> g_window;
} // namespace
constexpr const char* KYTY_SDL_WINDOW_CAPTION = "Game";
constexpr uint32_t KYTY_SDL_WINDOW_FLAGS =
(static_cast<uint32_t>(SDL_WINDOW_HIDDEN) | static_cast<uint32_t>(SDL_WINDOW_VULKAN));
constexpr int KYTY_SDL_WINDOWPOS_CENTERED = SDL_WINDOWPOS_CENTERED; /*NOLINT(hicpp-signed-bitwise)*/
static void SetPause(WindowLoopState& game, bool flag) {
@@ -407,6 +402,7 @@ static void GameEventDidEnterForeground(WindowLoopState& game) {
void WindowContext::Resize(uint32_t new_width, uint32_t new_height) {
EXIT_IF(new_width == 0 || new_height == 0);
Common::LockGuard lock(mutex);
graphic_ctx.screen_width = new_width;
graphic_ctx.screen_height = new_height;
}
@@ -728,8 +724,7 @@ void WindowContext::ProcessEvent(double time_s) {
}
}
#if defined(__APPLE__)
void WindowContext::RunOnMainThread(std::function<void()> task, bool wait) {
void WindowContext::RunOnMainThread(std::function<void()> task) {
if (Common::Thread::IsMainThread()) {
task();
return;
@@ -747,9 +742,6 @@ void WindowContext::RunOnMainThread(std::function<void()> task, bool wait) {
event.type = SDL_USEREVENT;
SDL_PushEvent(&event);
if (!wait) {
return;
}
Common::LockGuard lock(main_task_mutex);
while (main_tasks_run < ticket) {
main_task_done.Wait(&main_task_mutex);
@@ -772,7 +764,6 @@ void WindowContext::DrainMainThreadTasks() {
main_tasks_run += tasks.size();
main_task_done.SignalAll();
}
#endif
void WindowContext::Run() {
Common::Timer timer;
@@ -783,9 +774,7 @@ void WindowContext::Run() {
loop.paused.store(false, std::memory_order_release);
while (!loop.need_exit) {
#if defined(__APPLE__)
DrainMainThreadTasks();
#endif
if (loop.paused.load(std::memory_order_acquire)) {
if (!timer.IsPaused()) {
timer.Pause();
@@ -821,11 +810,26 @@ static void WindowCreate(WindowContext& context) {
LOGF("WindowCreate(): width = %d, height = %d\n", width, height);
uint32_t window_flags = KYTY_SDL_WINDOW_FLAGS;
if (Config::FullscreenEnabled()) {
window_flags |= static_cast<uint32_t>(SDL_WINDOW_FULLSCREEN_DESKTOP);
}
uint32_t window_flags = WindowContext::InitialWindowFlags(Config::FullscreenEnabled());
#if defined(__APPLE__)
// SDL loads Vulkan while creating a Vulkan window, so select the bundled
// MoltenVK loader before calling SDL_CreateWindow. Keep an explicit user
// override, and fall back to SDL's normal loader search when no bundle is present.
if (std::getenv("SDL_VULKAN_LIBRARY") == nullptr) {
if (char* base_path = SDL_GetBasePath(); base_path != nullptr) {
const std::string base_path_str = base_path;
SDL_free(base_path);
std::string moltenvk_path = base_path_str + "libMoltenVK.dylib";
if (!Common::File::IsFileExisting(moltenvk_path)) {
moltenvk_path = base_path_str + "../Frameworks/libMoltenVK.dylib";
}
if (Common::File::IsFileExisting(moltenvk_path) &&
SDL_setenv("SDL_VULKAN_LIBRARY", moltenvk_path.c_str(), 0) == 0) {
LOGF("Vulkan loader: %s\n", moltenvk_path.c_str());
}
}
}
// macOS 26 window chrome (CoreUI asset decode, SwiftUI titlebar) has been observed
// throwing NSExceptions under Rosetta during the first CATransaction commit. A
// borderless window skips that machinery entirely.
@@ -836,13 +840,20 @@ static void WindowCreate(WindowContext& context) {
context.window = SDL_CreateWindow(KYTY_SDL_WINDOW_CAPTION, KYTY_SDL_WINDOWPOS_CENTERED,
KYTY_SDL_WINDOWPOS_CENTERED, width, height, window_flags);
context.window_hidden = true;
if (context.window == nullptr) {
EXIT("%s\n", SDL_GetError());
}
SDL_SetWindowResizable(context.window, SDL_FALSE);
context.UpdateIcon();
}
uint32_t WindowContext::InitialWindowFlags(bool fullscreen) noexcept {
auto flags = static_cast<uint32_t>(SDL_WINDOW_VULKAN);
if (fullscreen) {
flags |= static_cast<uint32_t>(SDL_WINDOW_FULLSCREEN_DESKTOP);
}
return flags;
}
Presenter& WindowInit(uint32_t width, uint32_t height) {
@@ -866,6 +877,7 @@ void WindowRun() {
EXIT_IF(g_window == nullptr);
g_window->Run();
g_window->render_context->GetPipelineCache().Save();
}
void WindowShutdown() {
@@ -959,6 +971,7 @@ void WindowContext::UpdateTitle() {
Loader::SystemContentParamSfoGetString("TITLE_ID", title_id, sizeof(title_id));
static bool has_app_ver =
Loader::SystemContentParamSfoGetString("APP_VER", app_ver, sizeof(app_ver));
static const std::string processor_name = Common::GetSystemInfo().ProcessorName;
static uint64_t fps_start = Common::Timer::QueryPerformanceCounter();
static uint64_t frame_num = 0;
static uint64_t fps_frames = 0;
@@ -983,19 +996,14 @@ void WindowContext::UpdateTitle() {
fps_frames = 0;
}
auto fps = fmt::format(
const auto* device_name = graphic_ctx.GetPhysicalDeviceProperties().deviceName.data();
auto text = fmt::format(
"[{} | {}] {}{}{}{}{}{}[{}] [{}], frame: {}, fps: {:f}", KYTY_BUILD_LABEL, build_type,
(has_title ? title : ""), (has_title ? ", " : ""), (has_title_id ? title_id : ""),
(has_title_id ? ", " : ""), (has_app_ver ? app_ver : ""), (has_app_ver ? " " : ""),
device_name, processor_name, frame_num, current_fps);
#if defined(__APPLE__)
// AppKit traps on title changes off the main thread; fire-and-forget keeps present pacing.
RunOnMainThread([this, fps = std::move(fps)] { SDL_SetWindowTitle(window, fps.c_str()); },
false);
#else
SDL_SetWindowTitle(window, fps.c_str());
#endif
RunOnMainThread([this, text = std::move(text)] { SDL_SetWindowTitle(window, text.c_str()); });
}
} // namespace Libs::Graphics
@@ -9,13 +9,10 @@
#include <atomic>
#include <cstdint>
#include <functional>
#include <memory>
#include <vector>
#if defined(__APPLE__)
#include <functional>
#endif
namespace Libs::Graphics {
class Presenter;
@@ -39,6 +36,7 @@ struct WindowContext {
KYTY_CLASS_NO_COPY(WindowContext);
[[nodiscard]] static vk::PhysicalDeviceVulkan13Features RequiredVulkan13Features() noexcept;
[[nodiscard]] static uint32_t InitialWindowFlags(bool fullscreen) noexcept;
void CreateVulkan();
void RecreateSurface();
void RefreshSurfaceCapabilities();
@@ -49,36 +47,25 @@ struct WindowContext {
void ProcessDisplayEvent(const SDL_DisplayEvent& event);
void ProcessEvent(double time_seconds);
void Run();
#if defined(__APPLE__)
// AppKit only allows window operations (show, icon, title, view/layer changes) on the
// main thread; the present thread marshals them through the SDL main loop with these.
// wait=true blocks until the task has run on the main thread.
void RunOnMainThread(std::function<void()> task, bool wait);
// SDL window operations must complete on the main thread.
void RunOnMainThread(std::function<void()> task);
void DrainMainThreadTasks();
#endif
GraphicContext graphic_ctx;
SDL_Window* window = nullptr;
bool window_hidden = true;
vk::SurfaceKHR surface = nullptr;
SurfaceCapabilities surface_capabilities;
std::unique_ptr<RenderContext> render_context;
std::unique_ptr<Presenter> presenter;
WindowLoopState loop;
char device_name[VK_MAX_PHYSICAL_DEVICE_NAME_SIZE] = {0};
char processor_name[64] = {0};
Common::Mutex mutex;
#if defined(__APPLE__)
Common::Mutex main_task_mutex;
Common::CondVar main_task_done;
std::vector<std::function<void()>> main_tasks; // guarded by main_task_mutex
uint64_t main_tasks_queued = 0; // guarded by main_task_mutex
uint64_t main_tasks_run = 0; // guarded by main_task_mutex
#endif
};
} // namespace Libs::Graphics
@@ -439,6 +439,49 @@ uint32_t UnpackImageGather(ValueEmitContext& ctx, const IR::MemoryInfo& mem, uin
return result;
}
uint32_t EmitOneDimensionalGatherLz(ValueEmitContext& ctx, const IR::MemoryInfo& mem, uint32_t pc,
uint32_t coord, bool integer) {
auto& state = ctx.state;
state.builder.RequireCapability(CapabilityImageQuery);
const auto image = LoadSampledImageDescriptor(state, mem, pc, ImageViewKind::Dim1D);
const auto width = state.builder.AllocateId();
state.builder.AddFunction(
{OpImageQuerySizeLod, TypeU32(state), width, image, ConstantU32(state, 0)});
const auto width_f32 = state.builder.AllocateId();
state.builder.AddFunction({OpConvertUToF, TypeF32(state), width_f32, width});
const auto left = state.builder.AllocateId();
state.builder.AddFunction(
{OpExtInst, TypeF32(state), left, GlslStd450(state), GlslFloor,
Binary(state, OpFSub, TypeF32(state),
Binary(state, OpFMul, TypeF32(state), coord, width_f32),
ConstantF32(state, 0x3f000000u))});
const auto sampled = MakeSampledImage(state, mem, pc, ImageViewKind::Dim1D);
const auto vector_type = integer ? TypeU32Vector(state, 4) : TypeF32Vector(state, 4);
const auto scalar_type = integer ? TypeU32(state) : TypeF32(state);
const auto component = ImageConversionFormat(state, mem).format == Prospero::BufferFormat::kInvalid
? ImageGatherComponent(mem.dmask)
: 0u;
uint32_t values[2] {};
for (uint32_t index = 0; index < 2u; index++) {
const auto sample_coord = Binary(
state, OpFDiv, TypeF32(state),
Binary(state, OpFAdd, TypeF32(state), left,
ConstantF32(state, index == 0u ? 0x3f000000u : 0x3fc00000u)),
width_f32);
const auto texel = state.builder.AllocateId();
state.builder.AddFunction({OpImageSampleExplicitLod, vector_type, texel, sampled, sample_coord,
ImageOperandsLodMask, ZeroF32(state)});
values[index] = state.builder.AllocateId();
state.builder.AddFunction(
{OpCompositeExtract, scalar_type, values[index], texel, component});
}
const auto result = state.builder.AllocateId();
state.builder.AddFunction({OpCompositeConstruct, vector_type, result, values[0], values[1],
values[1], values[0]});
return result;
}
uint32_t PackImageTexel(ValueEmitContext& ctx, const IR::MemoryInfo& mem, uint32_t texel) {
const auto info = ImageConversionFormat(ctx.state, mem);
if (info.format == Prospero::BufferFormat::kInvalid) return texel;
@@ -613,6 +656,22 @@ bool EmitValueImage(ValueEmitContext& ctx, const IR::Inst& inst) {
const auto coord =
CoordF32(ctx, mem, *address, layout.coord, ImageViewCoordinateComponents(view));
if (op == IR::ValueOpcode::ImageGatherRaw) {
if (view == ImageViewKind::Dim1D) {
if (dref || !HasFlag(mem, Decoder::ImageSampleFlagLevelZero) ||
HasFlag(mem, Decoder::ImageSampleFlagOffset) ||
HasFlag(mem, Decoder::ImageSampleFlagGatherHorizontal)) {
ctx.Fail(inst, "has an unsupported 1D gather variant");
return true;
}
const auto sample = EmitOneDimensionalGatherLz(ctx, mem, pc, coord, integer);
ctx.Define(inst, ResultVector(ctx, UnpackImageGather(ctx, mem, sample), integer,
false, mem, true));
return true;
}
if (view == ImageViewKind::Dim1DArray) {
ctx.Fail(inst, "has an unsupported 1D-array gather");
return true;
}
const auto sampled = MakeSampledImage(state, mem, pc, view);
const auto sample = state.builder.AllocateId();
std::vector<uint32_t> words =
@@ -103,7 +103,9 @@ constexpr MemoryOpcodeInfo DS_OPCODE_LIST[] = {
{0x4du, Opcode::DS_WRITE_B64, 2, 32}, {0x4eu, Opcode::DS_WRITE2_B64, 4, 32},
{0x4fu, Opcode::DS_WRITE2ST64_B64, 4, 32}, {0x76u, Opcode::DS_READ_B64, 2, 32},
{0x77u, Opcode::DS_READ2_B64, 4, 32}, {0x78u, Opcode::DS_READ2ST64_B64, 4, 32},
{0xa1u, Opcode::DS_WRITE_B16_D16_HI, 1, 16},
{0xa6u, Opcode::DS_READ_U16_D16, 1, 16},
{0xa7u, Opcode::DS_READ_U16_D16_HI, 1, 16},
{0xb0u, Opcode::DS_WRITE_ADDTID_B32, 1, 32}, {0xb1u, Opcode::DS_READ_ADDTID_B32, 1, 32},
{0xdeu, Opcode::DS_WRITE_B96, 3, 32}, {0xdfu, Opcode::DS_WRITE_B128, 4, 32},
{0xfeu, Opcode::DS_READ_B96, 3, 32}, {0xffu, Opcode::DS_READ_B128, 4, 32},
@@ -147,6 +149,7 @@ bool IsDsWriteOpcode(Opcode opcode) {
switch (opcode) {
case Opcode::DS_WRITE_B8:
case Opcode::DS_WRITE_B16:
case Opcode::DS_WRITE_B16_D16_HI:
case Opcode::DS_WRITE2_B32:
case Opcode::DS_WRITE2ST64_B32:
case Opcode::DS_WRITE2_B64:
@@ -421,13 +424,17 @@ void DecodeDs(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index,
}
DecodeVectorGpr(vdst, inst.dst);
if (inst.opcode == Opcode::DS_READ_U16_D16) {
if (inst.opcode == Opcode::DS_READ_U16_D16 ||
inst.opcode == Opcode::DS_READ_U16_D16_HI) {
// Native D16 reads update only the selected destination half. Reuse the partial
// destination representation so typed IR translation preserves the upper word.
inst.dst.sdwa_sel = 4u;
// destination representation so typed IR translation preserves the other half.
inst.dst.sdwa_sel = inst.opcode == Opcode::DS_READ_U16_D16 ? 4u : 5u;
}
DecodeVectorGpr(addr, inst.src0);
DecodeVectorGpr(data0, inst.src1);
if (inst.opcode == Opcode::DS_WRITE_B16_D16_HI) {
inst.src1.sdwa_sel = 5u;
}
DecodeVectorGpr(data1, inst.src2);
inst.src_count = DsSourceCount(inst.opcode);
}
@@ -675,6 +675,7 @@ std::string InstructionToString(const Instruction& inst) {
case Opcode::DS_READ_B128:
case Opcode::DS_WRITE_B8:
case Opcode::DS_WRITE_B16:
case Opcode::DS_WRITE_B16_D16_HI:
case Opcode::DS_WRITE2_B32:
case Opcode::DS_WRITE2ST64_B32:
case Opcode::DS_WRITE2_B64:
@@ -185,6 +185,7 @@ enum class Opcode {
V_BFREV_B32,
V_FFBH_U32,
V_FFBL_B32,
V_FFBH_I32,
V_ADD_F32,
V_SUB_F32,
V_SUBREV_F32,
@@ -391,6 +392,7 @@ enum class Opcode {
V_CMP_GE_U32,
V_CMP_T_U32,
V_CMP_EQ_I64,
V_CMP_EQ_U64,
V_CMP_GT_U64,
V_CMP_NE_U64,
V_CMPX_NE_I64,
@@ -497,6 +499,7 @@ enum class Opcode {
DS_READ_I16,
DS_READ_U16,
DS_READ_U16_D16,
DS_READ_U16_D16_HI,
DS_READ2_B32,
DS_READ2ST64_B32,
DS_READ_B32,
@@ -507,6 +510,7 @@ enum class Opcode {
DS_READ_B128,
DS_WRITE_B8,
DS_WRITE_B16,
DS_WRITE_B16_D16_HI,
DS_WRITE2_B32,
DS_WRITE2ST64_B32,
DS_WRITE2_B64,
@@ -59,7 +59,7 @@ constexpr Vop2OpcodeInfo VOP2_OPCODE_LIST[] = {
{0x23u, Opcode::V_MBCNT_LO_U32_B32},
{0x24u, Opcode::V_MBCNT_HI_U32_B32},
{0x25u, Opcode::V_ADD_NC_U32, Vop2SdwaProfile::IntegerPartialDestination},
{0x28u, Opcode::V_ADDC_U32},
{0x28u, Opcode::V_ADDC_U32, Vop2SdwaProfile::IntegerFullDestination},
{0x2au, Opcode::V_SUBREV_CO_CI_U32},
{0x26u, Opcode::V_SUB_NC_U32, Vop2SdwaProfile::IntegerPartialDestination},
{0x27u, Opcode::V_SUBREV_NC_U32, Vop2SdwaProfile::IntegerFullDestination},
@@ -119,6 +119,7 @@ constexpr OpcodeMap VOP1_OPCODE_LIST[] = {
{0x38u, Opcode::V_BFREV_B32},
{0x39u, Opcode::V_FFBH_U32},
{0x3au, Opcode::V_FFBL_B32},
{0x3bu, Opcode::V_FFBH_I32},
{0x3fu, Opcode::V_FREXP_EXP_I32_F32},
{0x40u, Opcode::V_FREXP_MANT_F32},
{0x42u, Opcode::V_MOVRELD_B32},
@@ -169,6 +170,7 @@ constexpr OpcodeMap VOP3_ENCODED_VOP1_OPCODE_LIST[] = {
{0x38u, Opcode::V_BFREV_B32},
{0x39u, Opcode::V_FFBH_U32},
{0x3au, Opcode::V_FFBL_B32},
{0x3bu, Opcode::V_FFBH_I32},
{0x3fu, Opcode::V_FREXP_EXP_I32_F32},
{0x40u, Opcode::V_FREXP_MANT_F32},
{0x42u, Opcode::V_MOVRELD_B32},
@@ -234,7 +236,8 @@ constexpr VopcOpcodeInfo VOPC_OPCODE_LIST[] = {
{0xd1u, Opcode::V_CMPX_LT_U32}, {0xd2u, Opcode::V_CMPX_EQ_U32},
{0xd3u, Opcode::V_CMPX_LE_U32}, {0xd4u, Opcode::V_CMPX_GT_U32},
{0xd5u, Opcode::V_CMPX_NE_U32}, {0xd6u, Opcode::V_CMPX_GE_U32},
{0xe4u, Opcode::V_CMP_GT_U64, false}, {0xe5u, Opcode::V_CMP_NE_U64, false},
{0xe2u, Opcode::V_CMP_EQ_U64, false}, {0xe4u, Opcode::V_CMP_GT_U64, false},
{0xe5u, Opcode::V_CMP_NE_U64, false},
{0xf5u, Opcode::V_CMPX_NE_U64, false},
{0xc9u, Opcode::V_CMP_LT_F16}, {0xcau, Opcode::V_CMP_EQ_F16},
{0xcbu, Opcode::V_CMP_LE_F16}, {0xccu, Opcode::V_CMP_GT_F16},
@@ -515,6 +518,8 @@ constexpr Vop1SdwaRule VOP1_SDWA_RULES[] = {
{Opcode::V_CVT_F32_U32, SdwaSelBytes() | SdwaSelWords() | SdwaSelFull(), 0, 0, false},
{Opcode::V_CVT_F32_I32, SdwaSelBytes() | SdwaSelWords() | SdwaSelFull(), 0, 0, false},
{Opcode::V_CVT_F32_UBYTE0, SdwaSelBytes() | SdwaSelWords() | SdwaSelFull(), 0, 0, false},
{Opcode::V_NOT_B32, SdwaSelBytes() | SdwaSelWords() | SdwaSelFull(), 0, 0, false},
{Opcode::V_FFBL_B32, SdwaSelBytes() | SdwaSelWords() | SdwaSelFull(), 0, 0, false},
{Opcode::V_CVT_F32_F16, SdwaSelWords() | SdwaSelFull(), 0, 0, true},
{Opcode::V_CVT_F16_F32, SdwaSelFull(), SdwaSelWords(), SdwaSelFull(), false},
{Opcode::V_CVT_F16_U16, SdwaSelWords() | SdwaSelFull(), SdwaSelWords(),
@@ -940,8 +945,8 @@ constexpr Vop2SdwaRule VOP2_SDWA_RULES[] = {
true, true},
{SdwaSelFull(), SdwaSelAll(), SdwaSelAll(), false, false},
{SdwaSelAll(), SdwaSelAll(), SdwaSelAll(), true, false},
{SdwaSelFull(), SdwaSelFull(), SdwaSelWords() | SdwaSelFull(), false, false},
{SdwaSelFull(), SdwaSelFull(), SdwaSelAll(), false, false},
{SdwaSelFull(), SdwaSelAll(), SdwaSelWords() | SdwaSelFull(), false, false},
{SdwaSelFull(), SdwaSelAll(), SdwaSelAll(), false, false},
{SdwaSelWords() | SdwaSelFull(), SdwaSelAll(), SdwaSelAll(), true, false},
};
static_assert(sizeof(VOP2_SDWA_RULES) / sizeof(VOP2_SDWA_RULES[0]) ==
@@ -224,11 +224,14 @@ bool Translator::S_FF1_I32_B64(const Decoder::Instruction& inst) {
return true;
}
bool Translator::V_FFBH_U32(const Decoder::Instruction& inst) {
const auto source = ReadU32(inst.src0);
const auto msb = IR::U32(ir.Emit(IR::ValueOpcode::FindUMsb32, {source}));
bool Translator::V_FFBH_32(const Decoder::Instruction& inst, bool sign) {
const auto source = ReadU32(inst.src0);
const auto value = sign ? ir.BitwiseXor(
source, ir.ShiftRightArithmetic(source, IR::U32(IR::Value(31u))))
: source;
const auto msb = IR::U32(ir.Emit(IR::ValueOpcode::FindUMsb32, {value}));
const auto position = ir.ISub(IR::U32(IR::Value(31u)), msb);
const auto result = ir.Select(ir.INotEqual(source, IR::U32(IR::Value(0u))), position,
const auto result = ir.Select(ir.INotEqual(value, IR::U32(IR::Value(0u))), position,
IR::U32(IR::Value(0xffffffffu)));
WriteOperand(DestinationOperand(inst), result);
return true;
@@ -257,6 +257,7 @@ Decoder::Operand MemorySourceAt(const Decoder::Instruction& decoded, uint32_t in
return index == 0u ? decoded.src1 : index == 1u ? decoded.src0 : decoded.src2;
case Decoder::Opcode::DS_WRITE_B8:
case Decoder::Opcode::DS_WRITE_B16:
case Decoder::Opcode::DS_WRITE_B16_D16_HI:
case Decoder::Opcode::DS_WRITE_B32:
case Decoder::Opcode::DS_WRITE_B64:
case Decoder::Opcode::DS_WRITE_B96:
@@ -1050,6 +1051,7 @@ bool Translator::EmitMemory(const Decoder::Instruction& inst, std::string* error
case Decoder::Opcode::DS_READ_I16:
case Decoder::Opcode::DS_READ_U16:
case Decoder::Opcode::DS_READ_U16_D16:
case Decoder::Opcode::DS_READ_U16_D16_HI:
case Decoder::Opcode::DS_READ_B32:
case Decoder::Opcode::DS_READ_B64:
case Decoder::Opcode::DS_READ_B96:
@@ -1060,6 +1062,7 @@ bool Translator::EmitMemory(const Decoder::Instruction& inst, std::string* error
case Decoder::Opcode::DS_WRITE2ST64_B64: return DS_WRITE2(inst);
case Decoder::Opcode::DS_WRITE_B8:
case Decoder::Opcode::DS_WRITE_B16:
case Decoder::Opcode::DS_WRITE_B16_D16_HI:
case Decoder::Opcode::DS_WRITE_B32:
case Decoder::Opcode::DS_WRITE_B64:
case Decoder::Opcode::DS_WRITE_B96:
@@ -182,7 +182,7 @@ private:
bool V_OR3_B32(const Decoder::Instruction& inst);
bool V_XOR3_B32(const Decoder::Instruction& inst);
bool S_FF1_I32_B64(const Decoder::Instruction& inst);
bool V_FFBH_U32(const Decoder::Instruction& inst);
bool 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);
@@ -96,6 +96,7 @@ bool Translator::EmitVector(const Decoder::Instruction& inst, std::string* error
EmitIntegerCompare(inst, IR::ValueOpcode::SLessThanEqual32, IR::Type::U32, false, true);
return true;
case O::V_CMP_EQ_I64:
case O::V_CMP_EQ_U64:
EmitIntegerCompare(inst, IR::ValueOpcode::IEqual64, IR::Type::U64, false, false);
return true;
case O::V_CMP_GT_U64:
@@ -490,7 +491,8 @@ bool Translator::EmitVector(const Decoder::Instruction& inst, std::string* error
case O::V_AND_OR_B32: return V_AND_OR_B32(inst);
case O::V_OR3_B32: return V_OR3_B32(inst);
case O::V_XOR3_B32: return V_XOR3_B32(inst);
case O::V_FFBH_U32: return V_FFBH_U32(inst);
case O::V_FFBH_U32: return V_FFBH_32(inst, false);
case O::V_FFBH_I32: return V_FFBH_32(inst, true);
case O::V_MAD_I32_I24: return Integer24(inst, true, true);
case O::V_MAD_U32_U24: return Integer24(inst, false, true);
+1 -1
View File
@@ -1066,7 +1066,7 @@ int KYTY_SYSV_ABI KernelRename(const char* from, const char* to) {
Common::File::DeleteFile(real_to);
}
if (!Common::File::MoveFile(real_from, real_to)) {
if (!Common::File::RenameFile(real_from, real_to)) {
return KERNEL_ERROR_EIO;
}
+13 -4
View File
@@ -44,9 +44,9 @@ public:
explicit GuestBackingStore(uint64_t size): m_size(size) {
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
m_handle = CreateFileMappingA(INVALID_HANDLE_VALUE, nullptr, PAGE_EXECUTE_READWRITE,
static_cast<DWORD>(m_size >> 32u),
static_cast<DWORD>(m_size & 0xffffffffu), nullptr);
m_handle = CreateFileMappingA(
INVALID_HANDLE_VALUE, nullptr, PAGE_EXECUTE_READWRITE | SEC_COMMIT,
static_cast<DWORD>(m_size >> 32u), static_cast<DWORD>(m_size & 0xffffffffu), nullptr);
if (m_handle == nullptr) {
LOGF_COLOR(Log::Color::Red,
"\t direct-memory backing: CreateFileMapping failed: 0x%08" PRIx32 "\n",
@@ -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() {
+3
View File
@@ -1,7 +1,10 @@
#include "cmake_config.h"
#define KYTY_GIT_VERSION "@KYTY_GIT_VERSION@"
// Short, human-facing commit name used in build labels.
#define KYTY_GIT_HASH "@KYTY_GIT_HASH@"
// Full commit identity used for compatibility checks.
#define KYTY_GIT_REVISION "@KYTY_GIT_REVISION@"
/* Build provenance, shown in the window title and in crash reports. */
#if defined(KYTY_OFFICIAL_BUILD)
+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>
+67 -19
View File
@@ -102,27 +102,75 @@
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_user_name">
<property name="text">
<string>User name:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="MandatoryLineEdit" name="lineEdit_user_name">
<property name="toolTip">
<string>Local user name exposed to games</string>
</property>
<property name="clearButtonEnabled">
<bool>true</bool>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_user_id">
<property name="text">
<string>User ID:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QSpinBox" name="spinBox_user_id">
<property name="toolTip">
<string>Local user ID exposed to games</string>
</property>
<property name="maximum">
<number>2147483647</number>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_16">
<property name="text">
<string>Screen resolution:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<item row="3" column="1">
<widget class="QComboBox" name="comboBox_screen_resolution">
<property name="toolTip">
<string>Window resolution</string>
</property>
</widget>
</item>
<item row="2" column="0">
<item row="4" column="0">
<widget class="QLabel" name="label_present_mode">
<property name="text">
<string>Present mode:</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QComboBox" name="comboBox_present_mode">
<property name="toolTip">
<string>Vulkan swapchain presentation mode</string>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="label_vblank_frequency">
<property name="text">
<string>Vblank frequency:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<item row="5" column="1">
<widget class="QSpinBox" name="spinBox_vblank_frequency">
<property name="toolTip">
<string>Virtual display refresh rate used for frame pacing</string>
@@ -138,56 +186,56 @@
</property>
</widget>
</item>
<item row="3" column="0">
<item row="6" column="0">
<widget class="QLabel" name="label_console_language">
<property name="text">
<string>Console language:</string>
</property>
</widget>
</item>
<item row="3" column="1">
<item row="6" column="1">
<widget class="QComboBox" name="comboBox_console_language">
<property name="toolTip">
<string>Language</string>
</property>
</widget>
</item>
<item row="4" column="0">
<item row="7" column="0">
<widget class="QLabel" name="label_20">
<property name="text">
<string>Shader optimization type:</string>
</property>
</widget>
</item>
<item row="4" column="1">
<item row="7" column="1">
<widget class="QComboBox" name="comboBox_shader_optimization_type">
<property name="toolTip">
<string>Optimize shaders for code size or performance</string>
</property>
</widget>
</item>
<item row="5" column="0">
<item row="8" column="0">
<widget class="QLabel" name="label_21">
<property name="text">
<string>Shader log direction:</string>
</property>
</widget>
</item>
<item row="5" column="1">
<item row="8" column="1">
<widget class="QComboBox" name="comboBox_shader_log_direction">
<property name="toolTip">
<string>Dump shaders to file or console window. If enabled may decrease emulator performance</string>
</property>
</widget>
</item>
<item row="6" column="0">
<item row="9" column="0">
<widget class="QLabel" name="label_22">
<property name="text">
<string>Shader log folder:</string>
</property>
</widget>
</item>
<item row="6" column="1">
<item row="9" column="1">
<widget class="MandatoryLineEdit" name="lineEdit_shader_log_folder">
<property name="toolTip">
<string>Specify directory to dump shaders</string>
@@ -197,14 +245,14 @@
</property>
</widget>
</item>
<item row="7" column="0">
<item row="10" column="0">
<widget class="QLabel" name="label_24">
<property name="text">
<string>Command buffer dump folder:</string>
</property>
</widget>
</item>
<item row="7" column="1">
<item row="10" column="1">
<widget class="MandatoryLineEdit" name="lineEdit_cmd_dump_folder">
<property name="toolTip">
<string>Specify directory to dump command buffers</string>
@@ -214,28 +262,28 @@
</property>
</widget>
</item>
<item row="8" column="0">
<item row="11" column="0">
<widget class="QLabel" name="label_25">
<property name="text">
<string>Printf direction:</string>
</property>
</widget>
</item>
<item row="8" column="1">
<item row="11" column="1">
<widget class="QComboBox" name="comboBox_printf_direction">
<property name="toolTip">
<string>Print logs to file or console window. If enabled may decrease emulator performance</string>
</property>
</widget>
</item>
<item row="9" column="0">
<item row="12" column="0">
<widget class="QLabel" name="label_26">
<property name="text">
<string>Printf output file:</string>
</property>
</widget>
</item>
<item row="9" column="1">
<item row="12" column="1">
<widget class="MandatoryLineEdit" name="lineEdit_printf_file">
<property name="toolTip">
<string>Specify file to dump logs</string>
@@ -245,14 +293,14 @@
</property>
</widget>
</item>
<item row="10" column="0">
<item row="13" column="0">
<widget class="QLabel" name="label_27">
<property name="text">
<string>Profiler direction:</string>
</property>
</widget>
</item>
<item row="10" column="1">
<item row="13" column="1">
<widget class="QComboBox" name="comboBox_profiler_direction">
<property name="toolTip">
<string>Enable or disable profiler. If enabled may decrease emulator performance</string>
+25
View File
@@ -2,6 +2,7 @@
#define LAUNCHER_INCLUDE_CONFIGURATION_H_
#include "common.h"
#include "common/emulatorConfig.h"
#include <QByteArray>
#include <QChar>
@@ -54,12 +55,17 @@ public:
enum class Resolution {
R1280X720,
R1920X1080,
R2560X1440,
R3840X2160,
};
Q_ENUM(Resolution)
enum class ShaderOptimizationType { None, Size, Performance };
Q_ENUM(ShaderOptimizationType)
enum class PresentMode { Fifo, Mailbox, Immediate };
Q_ENUM(PresentMode)
enum class ShaderLogDirection { Silent, Console, File };
Q_ENUM(ShaderLogDirection)
@@ -85,6 +91,9 @@ public:
QString game_comment;
Resolution screen_resolution = Resolution::R1280X720;
QString user_name = "Kyty";
int user_id = Config::DEFAULT_USER_ID;
PresentMode present_mode = PresentMode::Fifo;
bool fullscreen_enabled = false;
int vblank_frequency = 60;
int console_language = DEFAULT_CONSOLE_LANGUAGE;
@@ -108,6 +117,9 @@ public:
void CopyEmulatorSettingsFrom(const Configuration& other) {
screen_resolution = other.screen_resolution;
user_name = other.user_name;
user_id = other.user_id;
present_mode = other.present_mode;
fullscreen_enabled = other.fullscreen_enabled;
vblank_frequency = other.vblank_frequency;
console_language = other.console_language;
@@ -148,6 +160,9 @@ public:
KYTY_CFG_SET(game_path);
KYTY_CFG_SET(custom_settings);
KYTY_CFG_SET(screen_resolution);
KYTY_CFG_SET(user_name);
KYTY_CFG_SET(user_id);
KYTY_CFG_SET(present_mode);
KYTY_CFG_SET(fullscreen_enabled);
KYTY_CFG_SET(vblank_frequency);
KYTY_CFG_SET(console_language);
@@ -175,6 +190,16 @@ public:
KYTY_CFG_GET(game_path);
KYTY_CFG_GET(custom_settings);
KYTY_CFG_GET(screen_resolution);
user_name = s->value("user_name", user_name).toString();
bool user_id_ok = false;
auto saved_user_id = s->value("user_id", user_id).toInt(&user_id_ok);
user_id = user_id_ok && Config::IsConfiguredUserIdValid(saved_user_id)
? saved_user_id
: Config::DEFAULT_USER_ID;
KYTY_CFG_GET(present_mode);
if (EnumToText(present_mode).isEmpty()) {
present_mode = PresentMode::Fifo;
}
KYTY_CFG_GET(fullscreen_enabled);
vblank_frequency = s->value("vblank_frequency", vblank_frequency).toInt();
console_language = s->value("console_language", console_language).toInt();
@@ -1,5 +1,6 @@
#include "configurationEditDialog.h"
#include "common/emulatorConfig.h"
#include "configuration.h"
#include "mandatoryLineEdit.h"
@@ -12,6 +13,7 @@
#include <QGroupBox>
#include <QHBoxLayout>
#include <QLayout>
#include <QLineEdit>
#include <QListView>
#include <QListWidget>
#include <QMessageBox>
@@ -152,7 +154,11 @@ static void ListInit(QComboBox* combo, T value) {
}
void ConfigurationEditDialog::Init(const Configuration& info) {
m_ui->lineEdit_user_name->setMaxLength(static_cast<int>(Config::MAX_USER_NAME_LENGTH));
m_ui->lineEdit_user_name->setText(info.user_name);
m_ui->spinBox_user_id->setValue(info.user_id);
ListInit(m_ui->comboBox_screen_resolution, info.screen_resolution);
ListInit(m_ui->comboBox_present_mode, info.present_mode);
m_ui->checkBox_fullscreen->setChecked(info.fullscreen_enabled);
m_ui->spinBox_vblank_frequency->setValue(info.vblank_frequency);
m_ui->comboBox_console_language->clear();
@@ -283,8 +289,12 @@ void ConfigurationEditDialog::moveEvent(QMoveEvent* event) {
}
static void UpdateInfo(Configuration& info, Ui::ConfigurationEditDialog& ui) {
info.user_name = ui.lineEdit_user_name->text().trimmed();
info.user_id = ui.spinBox_user_id->value();
info.screen_resolution =
TextToEnum<Configuration::Resolution>(ui.comboBox_screen_resolution->currentText());
info.present_mode =
TextToEnum<Configuration::PresentMode>(ui.comboBox_present_mode->currentText());
info.fullscreen_enabled = ui.checkBox_fullscreen->isChecked();
info.vblank_frequency = ui.spinBox_vblank_frequency->value();
info.console_language = ui.comboBox_console_language->currentIndex();
@@ -322,6 +332,19 @@ void ConfigurationEditDialog::save() {
return;
}
const auto user_name = m_ui->lineEdit_user_name->text().trimmed();
if (user_name.isEmpty() || user_name.toUtf8().size() > Config::MAX_USER_NAME_LENGTH) {
QMessageBox::critical(this, tr("Save failed"),
tr("User name must contain 1-16 UTF-8 bytes"));
return;
}
m_ui->lineEdit_user_name->setText(user_name);
if (!Config::IsConfiguredUserIdValid(m_ui->spinBox_user_id->value())) {
QMessageBox::critical(this, tr("Save failed"),
tr("User ID cannot be 254 (everyone) or 255 (system)"));
return;
}
update_info();
emit accept();
+3
View File
@@ -204,6 +204,9 @@ static QStringList CreateEmulatorArgs(const Configuration& info) {
args << "--screen-width" << r.at(0);
args << "--screen-height" << r.at(1);
args << "--user-name" << info.user_name;
args << "--user-id" << QString::number(info.user_id);
args << "--present-mode" << EnumToText(info.present_mode);
if (info.fullscreen_enabled) {
args << "--fullscreen";
}
+29 -2
View File
@@ -1,7 +1,9 @@
#include "libs/controller.h"
#include "SDL.h"
#include "common/assert.h"
#include "common/common.h"
#include "common/emulatorConfig.h"
#include "common/logging/log.h"
#include "common/stringUtils.h"
#include "common/threads.h"
@@ -21,6 +23,9 @@ LIB_NAME("Pad", "Pad");
constexpr int PAD_ERROR_INVALID_ARG = -2137915391; /* 0x80920001 */
constexpr int PAD_ERROR_INVALID_HANDLE = -2137915389; /* 0x80920003 */
// SDL limits rumble commands to 0xffff ms; zero strengths stop immediately.
constexpr uint32_t RUMBLE_DURATION_MS = 0xffff;
struct PadControllerInformation {
float touch_pixel_density;
uint16_t touch_resolution_x;
@@ -58,6 +63,7 @@ public:
void Axis(int id, Axis axis, int value);
void ResetInputState();
void GetConnectionInfo(bool* flag, int* count);
void SetVibration(uint8_t large_motor, uint8_t small_motor);
void ReadState(ControllerState* state, bool* flag, int* count);
int ReadStates(ControllerState* states, int states_num, bool* flag, int* count);
@@ -268,6 +274,25 @@ void GameController::ResetInputState() {
AddState(state);
}
void GameController::SetVibration(uint8_t large_motor, uint8_t small_motor) {
Common::LockGuard lock(m_mutex);
if (m_active_id == HOST_INPUT_CONTROLLER_ID) {
return;
}
auto* pad = SDL_GameControllerFromInstanceID(static_cast<SDL_JoystickID>(m_active_id));
if (pad == nullptr) {
return;
}
const auto large = static_cast<uint16_t>(large_motor * 0x101U);
const auto small = static_cast<uint16_t>(small_motor * 0x101U);
if (SDL_GameControllerRumble(pad, large, small, RUMBLE_DURATION_MS) != 0) {
LOGF("\t rumble failed: %s\n", SDL_GetError());
}
}
void GameController::GetConnectionInfo(bool* flag, int* count) {
EXIT_IF(flag == nullptr);
EXIT_IF(count == nullptr);
@@ -358,13 +383,12 @@ int KYTY_SYSV_ABI PadInit() {
}
static bool PadOpenArgsAreValid(int user_id, int type, int index) {
constexpr int user_id_initial = 1000;
constexpr int user_id_system = 0xff;
constexpr int port_type_standard = 0;
constexpr int port_type_special = 2;
constexpr int port_type_remote = 16;
const bool personal_port =
user_id == user_id_initial && (type == port_type_standard || type == port_type_special);
user_id == Config::GetUserId() && (type == port_type_standard || type == port_type_special);
const bool system_remote_control = user_id == user_id_system && type == port_type_remote;
return index == 0 && (personal_port || system_remote_control);
}
@@ -544,6 +568,9 @@ int KYTY_SYSV_ABI PadSetVibration(int handle, const PadVibrationParam* param) {
"\t small_motor = %d\n",
static_cast<int>(param->large_motor), static_cast<int>(param->small_motor));
EXIT_IF(g_controller == nullptr);
g_controller->SetVibration(param->large_motor, param->small_motor);
return OK;
}
+5 -5
View File
@@ -1,5 +1,6 @@
#include "libs/dialog.h"
#include "common/emulatorConfig.h"
#include "common/logging/log.h"
#include "common/stringUtils.h"
#include "libs/errno.h"
@@ -41,7 +42,6 @@ constexpr int LOGIN_RESULT_OK = 0;
constexpr int LOGIN_MODE_ALL_USERS = 0;
constexpr int LOGIN_MODE_NOT_LOGGED_IN = 1;
constexpr int LOGIN_USER_INVALID = -1;
constexpr int LOGIN_USER_ID = 1000;
constexpr int LOGIN_ERROR_NOT_INITIALIZED = static_cast<int>(0x81340001u);
constexpr int LOGIN_ERROR_ALREADY_INITIALIZED = static_cast<int>(0x81340002u);
@@ -67,7 +67,7 @@ static_assert(sizeof(LoginDialogResult) == 16);
static_assert(sizeof(LoginDialogParam) == 64);
static int g_login_status = LOGIN_STATUS_NONE;
static int g_login_selected_user = LOGIN_USER_ID;
static int g_login_selected_user = LOGIN_USER_INVALID;
int KYTY_SYSV_ABI LoginDialogInitialize() {
PRINT_NAME();
@@ -77,7 +77,7 @@ int KYTY_SYSV_ABI LoginDialogInitialize() {
}
g_login_status = LOGIN_STATUS_INITIALIZED;
g_login_selected_user = LOGIN_USER_ID;
g_login_selected_user = Config::GetUserId();
return OK;
}
@@ -90,7 +90,7 @@ int KYTY_SYSV_ABI LoginDialogTerminate() {
}
g_login_status = LOGIN_STATUS_NONE;
g_login_selected_user = LOGIN_USER_ID;
g_login_selected_user = LOGIN_USER_INVALID;
return OK;
}
@@ -112,7 +112,7 @@ int KYTY_SYSV_ABI LoginDialogOpen(const void* param) {
}
g_login_selected_user =
p->initial_focus != LOGIN_USER_INVALID ? p->initial_focus : LOGIN_USER_ID;
p->initial_focus != LOGIN_USER_INVALID ? p->initial_focus : Config::GetUserId();
LOGF("\t size = %d\n"
"\t mode = %d\n"
+1 -18
View File
@@ -327,25 +327,8 @@ static KYTY_SYSV_ABI int setenv(const char* name, const char* value, int overwri
return -1;
}
if (overwrite == 0 && std::getenv(name) != nullptr) {
return 0;
}
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
const auto ret = ::_putenv_s(name, value);
if (ret != 0) {
*Posix::GetErrorAddr() = ret;
return -1;
}
// Guest environment changes must not leak into emulator host libraries.
return 0;
#else
const auto ret = ::setenv(name, value, overwrite);
if (ret != 0) {
*Posix::GetErrorAddr() = errno;
return -1;
}
return 0;
#endif
}
static KYTY_SYSV_ABI int64_t libc_time(int64_t* timer) {
+5
View File
@@ -113,6 +113,10 @@ int KYTY_SYSV_ABI NetResolverCreate(const char* name, int memid, int flags) {
return NET_CALL(Net::NetResolverCreate(name, memid, flags));
}
int KYTY_SYSV_ABI NetResolverDestroy(int rid) {
return NET_CALL(Net::NetResolverDestroy(rid));
}
int KYTY_SYSV_ABI NetResolverStartNtoa(int rid, const char* hostname, void* addr, int timeout,
int retry, int flags) {
return NET_CALL(Net::NetResolverStartNtoa(rid, hostname, addr, timeout, retry, flags));
@@ -194,6 +198,7 @@ LIB_DEFINE(InitNet_1_Net) {
LIB_FUNC("dgJBaeJnGpo", LibNet::NetPoolCreate);
LIB_FUNC("K7RlrTkI-mw", LibNet::NetPoolDestroy);
LIB_FUNC("C4UgDHHPvdw", LibNet::NetResolverCreate);
LIB_FUNC("kJlYH5uMAWI", LibNet::NetResolverDestroy);
LIB_FUNC("Nd91WaWmG2w", LibNet::NetResolverStartNtoa);
LIB_FUNC("8Kcp5d-q1Uo", LibNet::NetInetPton);
LIB_FUNC("9vA2aW+CHuA", LibNet::NetInetNtop);
+16 -16
View File
@@ -1,11 +1,10 @@
#include "common/abi.h"
#include "common/assert.h"
#include "common/stringUtils.h"
#include "common/emulatorConfig.h"
#include "libs/errno.h"
#include "libs/libs.h"
#include "loader/symbolDatabase.h"
#include <cinttypes>
#include <cstdio>
#include <cstring>
@@ -57,7 +56,7 @@ static KYTY_SYSV_ABI int UserServiceGetInitialUser(int* user_id) {
EXIT_NOT_IMPLEMENTED(user_id == nullptr);
*user_id = 1000;
*user_id = Config::GetUserId();
return OK;
}
@@ -72,7 +71,7 @@ static KYTY_SYSV_ABI int UserServiceGetEvent(SceUserServiceEvent* event) {
if (!logged_in) {
logged_in = true;
event->event_type = UserServiceEventTypeLogin;
event->user_id = 1000;
event->user_id = Config::GetUserId();
return OK;
}
@@ -84,7 +83,7 @@ static KYTY_SYSV_ABI int UserServiceGetLoginUserIdList(UserServiceLoginUserIdLis
EXIT_NOT_IMPLEMENTED(user_id_list == nullptr);
user_id_list->user_id[0] = 1000;
user_id_list->user_id[0] = Config::GetUserId();
user_id_list->user_id[1] = -1;
user_id_list->user_id[2] = -1;
user_id_list->user_id[3] = -1;
@@ -93,10 +92,11 @@ static KYTY_SYSV_ABI int UserServiceGetLoginUserIdList(UserServiceLoginUserIdLis
}
static KYTY_SYSV_ABI int UserServiceGetUserName(int user_id, char* name, size_t size) {
EXIT_NOT_IMPLEMENTED(user_id != 1000);
EXIT_NOT_IMPLEMENTED(user_id != Config::GetUserId());
EXIT_NOT_IMPLEMENTED(size < 5);
int s = snprintf(name, size, "%s", "Kyty");
const auto& user_name = Config::GetUserName();
int s = snprintf(name, size, "%s", user_name.c_str());
EXIT_NOT_IMPLEMENTED(static_cast<size_t>(s) >= size);
@@ -109,7 +109,7 @@ static KYTY_SYSV_ABI int UserServiceGetUserNumber(int user_id, int32_t* number)
if (number == nullptr) {
return USER_SERVICE_ERROR_INVALID_ARGUMENT;
}
if (user_id != 1000) {
if (user_id != Config::GetUserId()) {
return USER_SERVICE_ERROR_NOT_LOGGED_IN;
}
@@ -124,7 +124,7 @@ static KYTY_SYSV_ABI int UserServiceGetGamePresets(int user_id, UserServiceGameP
if (presets == nullptr) {
return USER_SERVICE_ERROR_INVALID_ARGUMENT;
}
if (user_id != 1000) {
if (user_id != Config::GetUserId()) {
return USER_SERVICE_ERROR_NOT_LOGGED_IN;
}
@@ -145,7 +145,7 @@ static KYTY_SYSV_ABI int UserServiceGetAccessibilityVibration(int user_id, int32
if (vibration == nullptr) {
return USER_SERVICE_ERROR_INVALID_ARGUMENT;
}
if (user_id != 1000) {
if (user_id != Config::GetUserId()) {
return USER_SERVICE_ERROR_NOT_LOGGED_IN;
}
@@ -161,7 +161,7 @@ static KYTY_SYSV_ABI int UserServiceGetAccessibilityTriggerEffect(int user_
if (trigger_effect == nullptr) {
return USER_SERVICE_ERROR_INVALID_ARGUMENT;
}
if (user_id != 1000) {
if (user_id != Config::GetUserId()) {
return USER_SERVICE_ERROR_NOT_LOGGED_IN;
}
@@ -176,7 +176,7 @@ static KYTY_SYSV_ABI int UserServiceGetAgeLevel(int user_id, uint32_t* age_level
if (age_level == nullptr) {
return USER_SERVICE_ERROR_INVALID_ARGUMENT;
}
if (user_id != 1000) {
if (user_id != Config::GetUserId()) {
return USER_SERVICE_ERROR_NOT_LOGGED_IN;
}
@@ -192,7 +192,7 @@ static KYTY_SYSV_ABI int UserServiceGetAccessibilityChatTranscription(int u
if (chat_transcription == nullptr) {
return USER_SERVICE_ERROR_INVALID_ARGUMENT;
}
if (user_id != 1000) {
if (user_id != Config::GetUserId()) {
return USER_SERVICE_ERROR_NOT_LOGGED_IN;
}
@@ -208,7 +208,7 @@ UserServiceGetAccessibilityPressAndHoldDelay(int user_id, int32_t* press_and_hol
if (press_and_hold_delay == nullptr) {
return USER_SERVICE_ERROR_INVALID_ARGUMENT;
}
if (user_id != 1000) {
if (user_id != Config::GetUserId()) {
return USER_SERVICE_ERROR_NOT_LOGGED_IN;
}
@@ -224,7 +224,7 @@ static KYTY_SYSV_ABI int UserServiceGetAccessibilityZoomEnabled(int user_id
if (zoom_enabled == nullptr) {
return USER_SERVICE_ERROR_INVALID_ARGUMENT;
}
if (user_id != 1000) {
if (user_id != Config::GetUserId()) {
return USER_SERVICE_ERROR_NOT_LOGGED_IN;
}
@@ -240,7 +240,7 @@ static KYTY_SYSV_ABI int UserServiceGetAccessibilityZoomFollowFocus(int use
if (zoom_follow_focus == nullptr) {
return USER_SERVICE_ERROR_INVALID_ARGUMENT;
}
if (user_id != 1000) {
if (user_id != Config::GetUserId()) {
return USER_SERVICE_ERROR_NOT_LOGGED_IN;
}
+98 -63
View File
@@ -16,11 +16,6 @@
#include <arpa/inet.h>
#include <sys/socket.h>
#include <unistd.h>
// POSIX uses plain int file descriptors for sockets; provide the Winsock spellings
// the shared (non-guarded) code paths reference.
using SOCKET = int;
static constexpr SOCKET INVALID_SOCKET = -1;
#endif
#include "common/assert.h"
@@ -106,6 +101,7 @@ public:
int PoolCreate(const char* name, int size);
bool PoolDestroy(int memid);
int ResolverCreate(const char* name, int memid);
bool ResolverDestroy(int rid);
bool ResolverValid(int rid);
Id SslInit(uint64_t pool_size);
@@ -296,6 +292,17 @@ int Network::ResolverCreate(const char* name, int memid) {
return -1;
}
bool Network::ResolverDestroy(int rid) {
Common::LockGuard lock(m_mutex);
if (rid >= 0 && rid < RESOLVERS_MAX && m_resolvers[rid].used) {
m_resolvers[rid] = {};
return true;
}
return false;
}
bool Network::ResolverValid(int rid) {
Common::LockGuard lock(m_mutex);
@@ -823,9 +830,11 @@ struct NetEtherAddr {
#if defined(_WIN32)
using NativeSocket = SOCKET;
static constexpr NativeSocket INVALID_NATIVE_SOCKET = INVALID_SOCKET;
using SocketLength = int;
#else
using NativeSocket = int;
static constexpr NativeSocket INVALID_NATIVE_SOCKET = -1;
using SocketLength = socklen_t;
#endif
struct SocketSlot {
@@ -898,7 +907,12 @@ static bool EnsureSocketBackend() {
return true;
}
static int SetPosixSocketError(int error) {
static int SetGuestSocketError(int error) {
*Posix::GetErrorAddr() = error;
return -1;
}
static int SetHostSocketError(int error) {
int posix_error = Posix::POSIX_EIO;
#if defined(_WIN32)
switch (error) {
@@ -933,17 +947,31 @@ static int SetPosixSocketError(int error) {
default: break;
}
#else
posix_error = error;
switch (error) {
case EACCES: posix_error = Posix::POSIX_EACCES; break;
case EADDRINUSE: posix_error = Posix::POSIX_EADDRINUSE; break;
case EADDRNOTAVAIL: posix_error = Posix::POSIX_EADDRNOTAVAIL; break;
case EAFNOSUPPORT: posix_error = Posix::POSIX_EAFNOSUPPORT; break;
case EBADF: posix_error = Posix::POSIX_EBADF; break;
case EFAULT: posix_error = Posix::POSIX_EFAULT; break;
case EINVAL: posix_error = Posix::POSIX_EINVAL; break;
case EMFILE: posix_error = Posix::POSIX_EMFILE; break;
case ENFILE: posix_error = Posix::POSIX_ENFILE; break;
case ENOBUFS: posix_error = Posix::POSIX_ENOBUFS; break;
case ENOMEM: posix_error = Posix::POSIX_ENOMEM; break;
case ENOTSOCK: posix_error = Posix::POSIX_ENOTSOCK; break;
case EPROTONOSUPPORT: posix_error = Posix::POSIX_EPROTONOSUPPORT; break;
default: break;
}
#endif
*Posix::GetErrorAddr() = posix_error;
return -1;
return SetGuestSocketError(posix_error);
}
static int SetPosixSocketError() {
static int SetHostSocketError() {
#if defined(_WIN32)
return SetPosixSocketError(WSAGetLastError());
return SetHostSocketError(WSAGetLastError());
#else
return SetPosixSocketError(errno);
return SetHostSocketError(errno);
#endif
}
@@ -991,9 +1019,8 @@ static int ConvertMessageFlags(int flags) {
#endif
}
#if defined(_WIN32)
static int ConvertGuestSockaddr(const void* addr, uint32_t addrlen, sockaddr_storage* out,
int* out_len) {
SocketLength* out_len) {
EXIT_IF(out == nullptr);
EXIT_IF(out_len == nullptr);
@@ -1028,6 +1055,7 @@ static int ConvertGuestSockaddr(const void* addr, uint32_t addrlen, sockaddr_sto
return 0;
}
#if defined(_WIN32)
static int ConvertHostSockaddr(const sockaddr_storage* addr, int addrlen, void* out,
uint32_t* out_len) {
EXIT_IF(addr == nullptr);
@@ -1215,6 +1243,19 @@ int KYTY_SYSV_ABI NetResolverCreate(const char* name, int memid, int flags) {
return id;
}
int KYTY_SYSV_ABI NetResolverDestroy(int rid) {
PRINT_NAME();
LOGF("\t rid = %d\n", rid);
EXIT_IF(g_net == nullptr);
if (!g_net->ResolverDestroy(rid)) {
return NET_ERROR_EBADF;
}
return OK;
}
int KYTY_SYSV_ABI NetResolverStartNtoa(int rid, const char* hostname, void* addr, int timeout,
int retry, int flags) {
PRINT_NAME();
@@ -1364,7 +1405,7 @@ int KYTY_SYSV_ABI NetGetSockInfo(int s, void* info, int n, int flags) {
int KYTY_SYSV_ABI EpollCreate(const char* name, int flags) {
if (name == nullptr || flags != 0) {
return SetPosixSocketError(Posix::POSIX_EINVAL);
return SetGuestSocketError(Posix::POSIX_EINVAL);
}
std::lock_guard lock(g_epoll_mutex);
@@ -1378,7 +1419,7 @@ int KYTY_SYSV_ABI EpollCreate(const char* name, int flags) {
}
}
return SetPosixSocketError(Posix::POSIX_EMFILE);
return SetGuestSocketError(Posix::POSIX_EMFILE);
}
int KYTY_SYSV_ABI EpollControl(int eid, int op, int id, const NetEpollEvent* event) {
@@ -1387,22 +1428,22 @@ int KYTY_SYSV_ABI EpollControl(int eid, int op, int id, const NetEpollEvent* eve
constexpr int EPOLL_CTL_DEL = 3;
if ((op == EPOLL_CTL_ADD || op == EPOLL_CTL_MOD) && event == nullptr) {
return SetPosixSocketError(Posix::POSIX_EINVAL);
return SetGuestSocketError(Posix::POSIX_EINVAL);
}
if (op == EPOLL_CTL_DEL && event != nullptr) {
return SetPosixSocketError(Posix::POSIX_EINVAL);
return SetGuestSocketError(Posix::POSIX_EINVAL);
}
if (op < EPOLL_CTL_ADD || op > EPOLL_CTL_DEL) {
return SetPosixSocketError(Posix::POSIX_EINVAL);
return SetGuestSocketError(Posix::POSIX_EINVAL);
}
if (!IsSocket(id)) {
return SetPosixSocketError(Posix::POSIX_EBADF);
return SetGuestSocketError(Posix::POSIX_EBADF);
}
std::lock_guard lock(g_epoll_mutex);
auto* slot = GetEpollSlot(eid);
if (slot == nullptr) {
return SetPosixSocketError(Posix::POSIX_EBADF);
return SetGuestSocketError(Posix::POSIX_EBADF);
}
auto registration = std::find_if(slot->registrations.begin(), slot->registrations.end(),
@@ -1410,19 +1451,19 @@ int KYTY_SYSV_ABI EpollControl(int eid, int op, int id, const NetEpollEvent* eve
switch (op) {
case EPOLL_CTL_ADD:
if (registration != slot->registrations.end()) {
return SetPosixSocketError(Posix::POSIX_EEXIST);
return SetGuestSocketError(Posix::POSIX_EEXIST);
}
slot->registrations.push_back({id, *event});
break;
case EPOLL_CTL_MOD:
if (registration == slot->registrations.end()) {
return SetPosixSocketError(Posix::POSIX_ENOENT);
return SetGuestSocketError(Posix::POSIX_ENOENT);
}
registration->event = *event;
break;
case EPOLL_CTL_DEL:
if (registration == slot->registrations.end()) {
return SetPosixSocketError(Posix::POSIX_ENOENT);
return SetGuestSocketError(Posix::POSIX_ENOENT);
}
slot->registrations.erase(registration);
break;
@@ -1435,10 +1476,10 @@ int KYTY_SYSV_ABI EpollControl(int eid, int op, int id, const NetEpollEvent* eve
int KYTY_SYSV_ABI EpollWait(int eid, NetEpollEvent* events, int maxevents, int timeout) {
if (events == nullptr) {
return SetPosixSocketError(Posix::POSIX_EFAULT);
return SetGuestSocketError(Posix::POSIX_EFAULT);
}
if (maxevents <= 0 || timeout < -1) {
return SetPosixSocketError(Posix::POSIX_EINVAL);
return SetGuestSocketError(Posix::POSIX_EINVAL);
}
std::vector<EpollRegistration> registrations;
@@ -1446,7 +1487,7 @@ int KYTY_SYSV_ABI EpollWait(int eid, NetEpollEvent* events, int maxevents, int t
std::unique_lock lock(g_epoll_mutex);
auto* slot = GetEpollSlot(eid);
if (slot == nullptr) {
return SetPosixSocketError(Posix::POSIX_EBADF);
return SetGuestSocketError(Posix::POSIX_EBADF);
}
if (slot->registrations.empty() && timeout != 0) {
@@ -1461,7 +1502,7 @@ int KYTY_SYSV_ABI EpollWait(int eid, NetEpollEvent* events, int maxevents, int t
}
slot = GetEpollSlot(eid);
if (slot == nullptr) {
return SetPosixSocketError(Posix::POSIX_EBADF);
return SetGuestSocketError(Posix::POSIX_EBADF);
}
}
registrations = slot->registrations;
@@ -1492,7 +1533,7 @@ int KYTY_SYSV_ABI EpollWait(int eid, NetEpollEvent* events, int maxevents, int t
continue;
}
if (host_registrations.size() >= FD_SETSIZE) {
return SetPosixSocketError(Posix::POSIX_EINVAL);
return SetGuestSocketError(Posix::POSIX_EINVAL);
}
if ((registration.event.events & EPOLL_IN) != 0) {
FD_SET(socket, &host_read);
@@ -1518,7 +1559,7 @@ int KYTY_SYSV_ABI EpollWait(int eid, NetEpollEvent* events, int maxevents, int t
const int result = ::select(0, &host_read, &host_write, &host_except, host_timeout_ptr);
if (result == SOCKET_ERROR) {
return SetPosixSocketError();
return SetHostSocketError();
}
if (result == 0) {
return 0;
@@ -1552,7 +1593,7 @@ int KYTY_SYSV_ABI EpollWait(int eid, NetEpollEvent* events, int maxevents, int t
return count;
#else
(void)timeout;
return SetPosixSocketError(Posix::POSIX_ENOSYS);
return SetGuestSocketError(Posix::POSIX_ENOSYS);
#endif
}
@@ -1560,7 +1601,7 @@ int KYTY_SYSV_ABI EpollDestroy(int eid) {
std::lock_guard lock(g_epoll_mutex);
auto* slot = GetEpollSlot(eid);
if (slot == nullptr) {
return SetPosixSocketError(Posix::POSIX_EBADF);
return SetGuestSocketError(Posix::POSIX_EBADF);
}
slot->used = false;
@@ -1582,14 +1623,15 @@ int KYTY_SYSV_ABI SocketClose(int s) {
RemoveSocketFromEpolls(s);
#if defined(_WIN32)
if (closesocket(socket) == SOCKET_ERROR) {
const int result = closesocket(socket);
#else
const int result = ::close(socket);
#endif
if (result != 0) {
return NET_ERROR_EBADF;
}
return OK;
#else
return NET_ERROR_ENOSYS;
#endif
}
int KYTY_SYSV_ABI Socket(int family, int type, int protocol) {
@@ -1601,7 +1643,7 @@ int KYTY_SYSV_ABI Socket(int family, int type, int protocol) {
family, type, protocol);
if (!EnsureSocketBackend()) {
return SetPosixSocketError(Posix::POSIX_ENETDOWN);
return SetGuestSocketError(Posix::POSIX_ENETDOWN);
}
const int host_family = ConvertFamily(family);
@@ -1610,25 +1652,24 @@ int KYTY_SYSV_ABI Socket(int family, int type, int protocol) {
return -1;
}
#if defined(_WIN32)
NativeSocket socket = ::socket(host_family, type, protocol);
if (socket == INVALID_NATIVE_SOCKET) {
return SetPosixSocketError();
return SetHostSocketError();
}
const int fd = AllocSocketFd(socket);
if (fd < 0) {
#if defined(_WIN32)
closesocket(socket);
#else
::close(socket);
#endif
*Posix::GetErrorAddr() = Posix::POSIX_EMFILE;
return -1;
}
LOGF("\t fd = %d\n", fd);
return fd;
#else
*Posix::GetErrorAddr() = Posix::POSIX_ENOSYS;
return -1;
#endif
}
int KYTY_SYSV_ABI Bind(int s, const void* addr, uint32_t addrlen) {
@@ -1645,23 +1686,17 @@ int KYTY_SYSV_ABI Bind(int s, const void* addr, uint32_t addrlen) {
return -1;
}
#if defined(_WIN32)
sockaddr_storage host_addr {};
int host_addrlen = 0;
SocketLength host_addrlen = 0;
if (ConvertGuestSockaddr(addr, addrlen, &host_addr, &host_addrlen) != 0) {
return -1;
}
if (::bind(socket, reinterpret_cast<const sockaddr*>(&host_addr), host_addrlen) ==
SOCKET_ERROR) {
return SetPosixSocketError();
if (::bind(socket, reinterpret_cast<const sockaddr*>(&host_addr), host_addrlen) != 0) {
return SetHostSocketError();
}
return 0;
#else
*Posix::GetErrorAddr() = Posix::POSIX_ENOSYS;
return -1;
#endif
}
int KYTY_SYSV_ABI Connect(int s, const void* addr, uint32_t addrlen) {
@@ -1687,7 +1722,7 @@ int KYTY_SYSV_ABI Connect(int s, const void* addr, uint32_t addrlen) {
if (::connect(socket, reinterpret_cast<const sockaddr*>(&host_addr), host_addrlen) ==
SOCKET_ERROR) {
return SetPosixSocketError();
return SetHostSocketError();
}
return 0;
@@ -1712,7 +1747,7 @@ int KYTY_SYSV_ABI Listen(int s, int backlog) {
#if defined(_WIN32)
if (::listen(socket, backlog) == SOCKET_ERROR) {
return SetPosixSocketError();
return SetHostSocketError();
}
return 0;
@@ -1742,7 +1777,7 @@ int KYTY_SYSV_ABI Accept(int s, void* addr, uint32_t* addrlen) {
NativeSocket accepted =
::accept(socket, reinterpret_cast<sockaddr*>(&host_addr), &host_addrlen);
if (accepted == INVALID_NATIVE_SOCKET) {
return SetPosixSocketError();
return SetHostSocketError();
}
const int fd = AllocSocketFd(accepted);
@@ -1791,7 +1826,7 @@ int KYTY_SYSV_ABI Shutdown(int s, int how) {
#if defined(_WIN32)
if (::shutdown(socket, how) == SOCKET_ERROR) {
return SetPosixSocketError();
return SetHostSocketError();
}
return 0;
@@ -1821,7 +1856,7 @@ int KYTY_SYSV_ABI Getsockname(int s, void* addr, uint32_t* addrlen) {
int host_addrlen = sizeof(host_addr);
if (::getsockname(socket, reinterpret_cast<sockaddr*>(&host_addr), &host_addrlen) ==
SOCKET_ERROR) {
return SetPosixSocketError();
return SetHostSocketError();
}
return ConvertHostSockaddr(&host_addr, host_addrlen, addr, addrlen);
@@ -1850,7 +1885,7 @@ int KYTY_SYSV_ABI Getsockopt(int s, int level, int optname, void* optval, uint32
int len = static_cast<int>(*optlen);
if (::getsockopt(socket, ConvertSocketOptionLevel(level), optname, static_cast<char*>(optval),
&len) == SOCKET_ERROR) {
return SetPosixSocketError();
return SetHostSocketError();
}
*optlen = static_cast<uint32_t>(len);
return 0;
@@ -1881,14 +1916,14 @@ int KYTY_SYSV_ABI Setsockopt(int s, int level, int optname, const void* optval,
optlen >= sizeof(int)) {
u_long enabled = (*static_cast<const int*>(optval) != 0 ? 1 : 0);
if (ioctlsocket(socket, FIONBIO, &enabled) == SOCKET_ERROR) {
return SetPosixSocketError();
return SetHostSocketError();
}
return 0;
}
if (::setsockopt(socket, ConvertSocketOptionLevel(level), optname,
static_cast<const char*>(optval), static_cast<int>(optlen)) == SOCKET_ERROR) {
return SetPosixSocketError();
return SetHostSocketError();
}
return 0;
@@ -1941,7 +1976,7 @@ int64_t KYTY_SYSV_ABI Sendto(int s, const void* buf, uint64_t len, int flags, co
reinterpret_cast<const sockaddr*>(&host_addr), host_addrlen);
}
if (result == SOCKET_ERROR) {
return SetPosixSocketError();
return SetHostSocketError();
}
return result;
@@ -2000,7 +2035,7 @@ int64_t KYTY_SYSV_ABI Recvfrom(int s, void* buf, uint64_t len, int flags, void*
}
}
if (result == SOCKET_ERROR) {
return SetPosixSocketError();
return SetHostSocketError();
}
return result;
@@ -2101,7 +2136,7 @@ int KYTY_SYSV_ABI Select(int nfds, void* readfds, void* writefds, void* exceptfd
const int result = ::select(0, read_ptr, write_ptr, except_ptr, host_timeout_ptr);
if (result == SOCKET_ERROR) {
return SetPosixSocketError();
return SetHostSocketError();
}
GuestFdZero(readfds, nfds);
+1
View File
@@ -40,6 +40,7 @@ int KYTY_SYSV_ABI NetTerm();
int KYTY_SYSV_ABI NetPoolCreate(const char* name, int size, int flags);
int KYTY_SYSV_ABI NetPoolDestroy(int memid);
int KYTY_SYSV_ABI NetResolverCreate(const char* name, int memid, int flags);
int KYTY_SYSV_ABI NetResolverDestroy(int rid);
int KYTY_SYSV_ABI NetResolverStartNtoa(int rid, const char* hostname, void* addr, int timeout,
int retry, int flags);
int KYTY_SYSV_ABI NetInetPton(int af, const char* src, void* dst);
+4 -1
View File
@@ -801,7 +801,10 @@ static bool KytyExceptionHandler(const Common::HostException::ExceptionInfo& exc
return true;
}
}
return false;
EXIT("Unhandled host exception: type=%u code=%u pc=0x%016" PRIx64
" access=%u address=0x%016" PRIx64 "\n",
static_cast<unsigned>(info->type), info->native_code, info->exception_address,
static_cast<unsigned>(info->access_violation_type), info->access_violation_vaddr);
}
static void EncodeId64(uint16_t in_id, std::string* out_id) {
+33
View File
@@ -47,6 +47,11 @@ static void PrintUsage() {
" --game-patch <json> Validated patch plan to apply before entry.\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(" --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");
@@ -122,6 +127,17 @@ static bool ParseConsoleLanguage(const std::string& value, uint32_t& out) {
return true;
}
static bool ParseUserId(const std::string& value, int32_t& out) {
int32_t user_id = 0;
auto [end, error] = std::from_chars(value.data(), value.data() + value.size(), user_id);
if (error != std::errc {} || end != value.data() + value.size() ||
!Config::IsConfiguredUserIdValid(user_id)) {
return false;
}
out = user_id;
return true;
}
static bool ParseArgs(int argc, char* argv[], RunOptions& options, bool& show_help) {
show_help = false;
@@ -201,6 +217,23 @@ static bool ParseArgs(int argc, char* argv[], RunOptions& options, bool& show_he
options.config.screen_width = static_cast<uint32_t>(Common::ToInt32(value));
} else if (arg == "--screen-height") {
options.config.screen_height = static_cast<uint32_t>(Common::ToInt32(value));
} else if (arg == "--user-name") {
if (value.empty() || value.size() > Config::MAX_USER_NAME_LENGTH) {
::printf("invalid user name: must contain 1-%zu bytes\n",
Config::MAX_USER_NAME_LENGTH);
return false;
}
options.config.user_name = value;
} else if (arg == "--user-id") {
if (!ParseUserId(value, options.config.user_id)) {
::printf("invalid user ID: %s\n", value.c_str());
return false;
}
} else if (arg == "--present-mode") {
if (!ParseEnum(value, options.config.present_mode)) {
::printf("invalid present mode: %s\n", value.c_str());
return false;
}
} else if (arg == "--vblank-frequency") {
const int32_t vblank_frequency = Common::ToInt32(value);
options.config.vblank_frequency =
+273 -42
View File
@@ -7283,26 +7283,30 @@ public:
std::printf("[gpu] %-32s ok\n", name);
}
void CheckRenderExecutorColorStandard4TileDiscovery() {
constexpr const char *name = "RenderExecutorColorStandard4Tile";
void CheckRenderExecutorColorStandardTileDiscovery() {
constexpr const char *name = "RenderExecutorColorStandardTile";
constexpr uintptr_t base = 0x0000000203b00000ull;
constexpr uint64_t target_address = base + 0x4000;
constexpr uint64_t target_size = 0x180000;
constexpr uint64_t allocation_size = 0x184000;
constexpr uint64_t allocation_alignment = 0x4000;
constexpr uint64_t allocation_size = 0x190000;
constexpr uint64_t allocation_alignment = 0x10000;
constexpr auto format = Prospero::BufferFormat::k32Float;
constexpr auto tile = Prospero::TileMode::kStandard4KB;
struct TileCase {
Prospero::TileMode tile;
uint64_t target_offset;
uint64_t alignment;
};
constexpr std::array cases{
TileCase{Prospero::TileMode::kStandard4KB, 0x4000, 0x1000},
TileCase{Prospero::TileMode::kStandard64KB, 0x10000, 0x10000},
};
EnsureRuntimeContext();
const auto pitch = TileGetTexturePitch(format, 256, tile);
TileSizeAlign slice{};
TileGetTextureSize(format, 256, 256, 1, tile, &slice, nullptr, nullptr);
const auto odd_pitch = TileGetTexturePitch(format, 257, tile);
const auto odd_pitch =
TileGetTexturePitch(format, 257, Prospero::TileMode::kStandard4KB);
TileSizeAlign odd_slice{};
TileGetTextureSize(format, 257, 131, 1, tile, &odd_slice, nullptr, nullptr);
TileGetTextureSize(format, 257, 131, 1, Prospero::TileMode::kStandard4KB,
&odd_slice, nullptr, nullptr);
Require(name, "tile layout",
pitch == 256 && slice.size == 0x40000 && slice.align == 0x1000 &&
odd_pitch == 288 && odd_slice.size == 0x2d000 &&
odd_pitch == 288 && odd_slice.size == 0x2d000 &&
odd_slice.align == 0x1000,
"Standard4KB pitch, footprint, or block alignment is incorrect");
@@ -7311,17 +7315,29 @@ public:
Libs::LibKernel::Memory::KernelAllocateDirectMemory(
0, Libs::LibKernel::Memory::KernelGetDirectMemorySize(),
allocation_size, allocation_alignment, 0, &direct_offset) == 0,
"Standard4KB color allocation failed");
"standard-tiled color allocation failed");
void *mapped = reinterpret_cast<void *>(base);
Require(name, "direct mapping",
Libs::LibKernel::Memory::KernelMapDirectMemory(
&mapped, allocation_size, 0x3, 0x10, direct_offset,
allocation_alignment) == 0 &&
mapped == reinterpret_cast<void *>(base),
"Standard4KB color mapping failed");
"standard-tiled color mapping failed");
std::memset(mapped, 0, allocation_size);
{
for (const auto &tile_case : cases) {
const auto pitch = TileGetTexturePitch(format, 256, tile_case.tile);
TileSizeAlign slice{};
TileGetTextureSize(format, 256, 256, 1, tile_case.tile, &slice, nullptr,
nullptr);
Require(
name, "tile layout",
pitch == 256 && slice.size == 0x40000 &&
slice.align == tile_case.alignment,
"standard tile pitch, footprint, or block alignment is incorrect");
const auto target_address = base + tile_case.target_offset;
const auto target_size = slice.size * 6ull;
RenderContext context(m_runtime_context);
auto &scheduler = context.GetCommandScheduler();
HW::Context registers{};
@@ -7335,7 +7351,7 @@ public:
registers.SetColorView(
0, {.base_array_slice_index = 5, .last_array_slice_index = 5});
registers.SetColorAttrib2(0, {.height = 255, .width = 255});
registers.SetColorAttrib3(0, {.tile_mode = tile,
registers.SetColorAttrib3(0, {.tile_mode = tile_case.tile,
.dimension = 1,
.cmask_pipe_aligned = true,
.dcc_pipe_aligned = true});
@@ -7358,7 +7374,7 @@ public:
color.base_array_layer == 5 &&
color.desc.info.data.address == target_address &&
color.desc.info.data.size == target_size &&
color.desc.info.tile_mode == tile &&
color.desc.info.tile_mode == tile_case.tile &&
color.desc.info.extent == vk::Extent3D{256, 256, 1} &&
color.desc.info.resources == ImageSubresources{1, 6} &&
color.desc.info.pitch == pitch &&
@@ -7367,9 +7383,9 @@ public:
color.desc.view_info.type == vk::ImageViewType::e2D &&
color.desc.view_info.base_layer == 5 &&
color.desc.view_info.layer_count == 1 &&
(target_address & 0xffffu) == 0x4000 &&
(target_address & (tile_case.alignment - 1)) == 0 &&
image.usage.render_target && image.IsGpuModified(),
"slice 5 Standard4KB render target was not resolved");
"slice 5 standard-tiled render target was not resolved");
RenderExecutorTestAccess::ResetBindings(executor);
resources.UnmapMemory(base, allocation_size);
@@ -7378,11 +7394,11 @@ public:
Require(name, "unmap direct backing",
Libs::LibKernel::Memory::KernelMunmap(base, allocation_size) == 0,
"Standard4KB color mapping release failed");
"standard-tiled color mapping release failed");
Require(name, "release direct backing",
Libs::LibKernel::Memory::KernelReleaseDirectMemory(
direct_offset, allocation_size) == 0,
"Standard4KB color allocation release failed");
"standard-tiled color allocation release failed");
std::printf("[gpu] %-32s ok\n", name);
}
@@ -12018,6 +12034,7 @@ CoverageClass ClassifyOpcode(ShaderOpcode opcode,
case Opcode::DS_READ_I16:
case Opcode::DS_READ_U16:
case Opcode::DS_READ_U16_D16:
case Opcode::DS_READ_U16_D16_HI:
case Opcode::DS_READ2_B32:
case Opcode::DS_READ_B32:
case Opcode::DS_READ_B64:
@@ -12026,6 +12043,7 @@ CoverageClass ClassifyOpcode(ShaderOpcode opcode,
case Opcode::DS_READ_B128:
case Opcode::DS_WRITE_B8:
case Opcode::DS_WRITE_B16:
case Opcode::DS_WRITE_B16_D16_HI:
case Opcode::DS_WRITE2_B32:
case Opcode::DS_WRITE2ST64_B32:
case Opcode::DS_WRITE_B32:
@@ -13355,6 +13373,83 @@ TestCase VectorIntegerOps() {
O::S_ENDPGM}};
}
TestCase VectorFfbhI32NativeAndVop3OnGpu() {
using O = ShaderOpcode;
constexpr std::array inputs = {0x00000000u, 0x40000000u, 0x80000000u,
0x0fffffffu, 0xffff0000u, 0xfffffffeu,
0xffffffffu, 0x80000000u};
std::vector<u32> code;
for (u32 i = 0; i < inputs.size() - 1u; i++) {
AppendVMovU32(&code, 30, i * 4u);
AppendBufferLoadDword(&code, 14, 30);
code.push_back(0x7e1c770eu); // v_ffbh_i32 v14, v14
AppendStoreVgpr(&code, 14, i);
}
AppendVMovU32(&code, 30, (inputs.size() - 1u) * 4u);
AppendBufferLoadDword(&code, 14, 30);
AppendVop3(&code, 0x1bbu, 15, Vgpr(14), 0);
AppendStoreVgpr(&code, 15, inputs.size() - 1u);
AppendEnd(&code);
TestCase test;
test.name = "VectorFfbhI32NativeAndVop3OnGpu";
test.code = std::move(code);
test.initial.assign(inputs.begin(), inputs.end());
test.expected = {0xffffffffu, 1u, 1u, 4u, 16u, 31u, 0xffffffffu, 1u};
test.opcodes = {O::V_MOV_B32, O::BUFFER_LOAD_DWORD, O::V_FFBH_I32,
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
test.decoded_counts = {{"V_FFBH_I32 v14, v14", inputs.size() - 1u},
{"V_FFBH_I32 v15, v14", 1u}};
return test;
}
TestCase Vop1SdwaFfblCapturedHighWordSource() {
using O = ShaderOpcode;
std::vector<u32> code;
AppendBufferLoadDword(&code, 2, 30);
code.push_back(0x7e2074f9u);
code.push_back(0x00050602u); // v_ffbl_b32 v16, v2.word1
AppendStoreVgpr(&code, 16, 0);
AppendEnd(&code);
TestCase test;
test.name = "Vop1SdwaFfblCapturedHighWordSource";
test.code = std::move(code);
test.initial = {0x00400000u};
test.expected = {6u};
test.opcodes = {O::BUFFER_LOAD_DWORD, O::V_FFBL_B32, O::BUFFER_STORE_DWORD,
O::S_ENDPGM};
test.decoded_counts = {{"V_FFBL_B32 v16, v2.sdwa(sel=5,sext=0)", 1}};
test.ir_counts = {{" = BitFieldUExtract ", 1}, {" = FindILsb32 ", 1}};
test.required_spirv = {"OpBitFieldUExtract"};
return test;
}
TestCase Vop1SdwaNotCapturedByte0Source() {
using O = ShaderOpcode;
std::vector<u32> code;
AppendBufferLoadDword(&code, 2, 30);
code.push_back(0x7e046ef9u);
code.push_back(0x00000602u); // v_not_b32 v2, v2.byte0
AppendStoreVgpr(&code, 2, 0);
AppendEnd(&code);
TestCase test;
test.name = "Vop1SdwaNotCapturedByte0Source";
test.code = std::move(code);
test.initial = {0x12345678u};
test.expected = {0xffffff87u};
test.opcodes = {O::BUFFER_LOAD_DWORD, O::V_NOT_B32, O::BUFFER_STORE_DWORD,
O::S_ENDPGM};
test.decoded_counts = {{"V_NOT_B32 v2, v2.sdwa(sel=0,sext=0)", 1}};
test.ir_counts = {{" = BitFieldUExtract ", 1}, {" = BitwiseNot32 ", 1}};
test.required_spirv = {"OpBitFieldUExtract", "OpNot"};
return test;
}
TestCase Vop2SdwaSubNcExactByte2Destination() {
using O = ShaderOpcode;
@@ -13862,37 +13957,35 @@ TestCase VectorAddcUsesPerLaneCarryIn() {
TestCase VectorAddcWritesPerLaneCarryOut() {
using O = ShaderOpcode;
std::vector<u32> code = {
EncodeVop1(0x01, 1, 0),
EncodeVop2(0x1a, 1, InlineU32(2), 1),
EncodeVop2(0x25, 1, Vgpr(0), 1),
EncodeVopc(0xc0, Vgpr(0), 0),
};
AppendVMovLiteral(&code, 2, 0xffffffffu);
code.push_back(EncodeVop1(0x01, 3, InlineU32(1)));
code.push_back(EncodeVop2(0x28, 5, Vgpr(2), 3));
std::vector<u32> code = {EncodeVop1(0x01, 1, Vgpr(0)),
EncodeVop2(0x1a, 4, InlineU32(2), 1),
EncodeVopc(0xc7, Vgpr(0), 0)};
AppendBufferLoadDword(&code, 26, 4);
code.push_back(0x500434f9u);
code.push_back(0x0c860680u);
code.push_back(EncodeVop1(0x01, 6, 106));
code.push_back(EncodeVop2(0x1a, 4, InlineU32(2), 1));
AppendBufferStoreDword(&code, 6, 4);
AppendEnd(&code);
TestCase test;
test.name = "VectorAddcWritesPerLaneCarryOut";
test.code = code;
test.initial = {0x0000ffffu, 0u, 0x0000ffffu, 0u};
// VCC is represented as the current invocation's carry bit, not a shared
// four-lane ballot word.
test.expected = std::vector<u32>(8, 1u);
test.opcodes = {O::V_MOV_B32, O::V_LSHLREV_B32, O::V_ADD_NC_U32,
O::V_CMP_F_U32, O::V_ADDC_U32, O::BUFFER_STORE_DWORD,
test.expected = {1u, 0u, 1u, 0u};
test.opcodes = {O::V_MOV_B32, O::V_LSHLREV_B32, O::V_CMP_T_U32,
O::BUFFER_LOAD_DWORD, O::V_ADDC_U32, O::BUFFER_STORE_DWORD,
O::S_ENDPGM};
test.decoded_counts = {
{"V_ADDC_U32 v2, vcc_lo, 0, v26.sdwa(sel=4,sext=1), vcc_lo", 1}};
test.ir_counts = {{" = BitFieldSExtract ", 1}, {" = IAddCarry32 ", 2}};
test.required_spirv = {"OpBitFieldSExtract"};
test.compute_info.threads_num[0] = 4;
test.compute_info.threads_num[1] = 1;
test.compute_info.threads_num[2] = 1;
test.compute_info.group_id[0] = true;
test.compute_info.thread_ids_num = 1;
test.compute_info.workgroup_register = 0;
test.has_compute_info = true;
test.dispatch_x = 2;
return test;
}
@@ -15367,6 +15460,36 @@ TestCase VectorCompareOps() {
O::V_CMP_T_U32, O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
}
TestCase VectorVop3CompareEqU64OnGpu() {
using O = ShaderOpcode;
std::vector<u32> code;
code.push_back(EncodeVop1(0x01, 1, InlineU32(1)));
code.push_back(EncodeSop1(0x04, 106, 126)); // s_mov_b64 vcc, exec
code.push_back(0xd4e2006au);
code.push_back(0x0000d47eu); // v_cmp_eq_u64 vcc, exec, vcc
code.push_back(EncodeVop2(0x01, 2, InlineU32(0), 1));
AppendStoreVgpr(&code, 2, 0);
AppendSMovLiteral(&code, 107, 1); // high-dword mismatch
code.push_back(0xd4e2006au);
code.push_back(0x0000d47eu); // v_cmp_eq_u64 vcc, exec, vcc
code.push_back(EncodeVop2(0x01, 3, InlineU32(0), 1));
AppendStoreVgpr(&code, 3, 1);
AppendEnd(&code);
TestCase test{"VectorVop3CompareEqU64OnGpu",
code,
{},
{1, 0},
{O::V_MOV_B32, O::S_MOV_B64, O::V_CMP_EQ_U64,
O::V_CNDMASK_B32, O::S_MOV_B32, O::BUFFER_STORE_DWORD,
O::S_ENDPGM}};
test.decoded_counts = {{"V_CMP_EQ_U64 vcc_lo, exec_lo, vcc_lo", 2}};
return test;
}
TestCase VectorVop3CompareNeU64OnGpu() {
using O = ShaderOpcode;
@@ -17992,6 +18115,64 @@ TestCase DsReadU16D16CapturedPreservesHighHalf() {
return test;
}
TestCase DsWriteB16D16HiCapturedUsesHighHalf() {
using O = ShaderOpcode;
std::vector<u32> code;
AppendVMovU32(&code, 1, 0);
AppendVMovLiteral(&code, 3, 0xdeadbeefu);
code.push_back(EncodeDs0(0x0d));
code.push_back(EncodeDs1(0, 3, 1)); // ds_write_b32 v1, v3
AppendVMovU32(&code, 20, 2);
AppendVMovLiteral(&code, 2, 0x1234abcdu);
AppendVMovLiteral(&code, 0, 0x87654321u);
code.push_back(0xda840000u);
code.push_back(0x00000214u); // captured ds_write_b16_d16_hi v20, v2
code.push_back(EncodeDs0(0x36));
code.push_back(EncodeDs1(4, 0, 1)); // ds_read_b32 v4, v1
AppendStoreVgpr(&code, 4, 0);
AppendStoreVgpr(&code, 0, 1);
AppendEnd(&code);
TestCase test{"DsWriteB16D16HiCapturedUsesHighHalf",
code,
std::vector<u32>(2, 0),
{0x1234beefu, 0x87654321u},
{O::V_MOV_B32, O::DS_WRITE_B32, O::DS_WRITE_B16_D16_HI,
O::DS_READ_B32, O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
test.decoded_counts = {{"DS_WRITE_B16_D16_HI", 1}};
return test;
}
TestCase DsReadU16D16HiCapturedPreservesLowHalf() {
using O = ShaderOpcode;
std::vector<u32> code;
AppendVMovU32(&code, 1, 0);
AppendVMovLiteral(&code, 2, 0x89abcdefu);
code.push_back(EncodeDs0(0x0d));
code.push_back(EncodeDs1(0, 2, 1)); // ds_write_b32 v2, v1
AppendVMovLiteral(&code, 5, 0x12345678u);
AppendVMovU32(&code, 6, 0);
code.push_back(0xda9c0000u);
code.push_back(0x05000006u); // captured ds_read_u16_d16_hi v5, v6
AppendStoreVgpr(&code, 5, 0);
AppendEnd(&code);
TestCase test{"DsReadU16D16HiCapturedPreservesLowHalf",
code,
std::vector<u32>(1, 0),
{0xcdef5678u},
{O::V_MOV_B32, O::DS_WRITE_B32, O::DS_READ_U16_D16_HI,
O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
test.decoded_counts = {{"DS_READ_U16_D16_HI", 1}};
return test;
}
TestCase DsReadWrite2Variants() {
using O = ShaderOpcode;
@@ -18936,6 +19117,44 @@ TestCase ImageLoad1DUsesScalarCoordinate() {
return test;
}
TestCase ImageGather2DInstructionWith1DDescriptor() {
using O = ShaderOpcode;
std::vector<u32> code;
AppendVMovLiteral(&code, 20, 0x3f000000u);
AppendVMovLiteral(&code, 21, 0x3f000000u);
code.push_back(EncodeMimg0(0x47, 0x1));
code.push_back(EncodeMimg1(0, 20));
for (u32 i = 0; i < 4u; i++) {
AppendStoreVgpr(&code, i, i);
}
AppendEnd(&code);
auto image = MakeRgbaImage(4, 1);
SetRgbaPixel(&image, 4, 1, 0, 0x40000000u, 0, 0, 0);
SetRgbaPixel(&image, 4, 2, 0, 0x40400000u, 0, 0, 0);
TestCase test;
test.name = "ImageGather2DInstructionWith1DDescriptor";
test.code = code;
test.expected = {0x40000000u, 0x40400000u, 0x40400000u, 0x40000000u};
test.opcodes = {O::V_MOV_B32, O::IMAGE_GATHER4_LZ, O::BUFFER_STORE_DWORD,
O::S_ENDPGM};
test.image_width = 4;
test.image_height = 1;
test.sampled_image_rgba = image;
test.sampled_image_type = vk::ImageType::e1D;
test.sampled_image_view_type = vk::ImageViewType::e1D;
test.user_data = MakeSampledTextureData(Prospero::BufferFormat::k32Float);
test.user_data[3] = static_cast<uint32_t>(Prospero::ImageType::kColor1D)
<< 28u;
test.has_user_data = true;
test.required_spirv = {"sampled_1d", "OpImageQuerySizeLod",
"OpImageSampleExplicitLod", "Floor"};
test.forbidden_spirv = {"OpImageGather"};
return test;
}
TestCase ImageLoad1DArrayUsesLayerCoordinate() {
using O = ShaderOpcode;
@@ -20350,6 +20569,9 @@ std::vector<TestCase> MakeCases() {
AddCase(VectorMoves);
AddCase(VectorVop3MoveAppliesFloatSourceModifiers);
AddCase(VectorIntegerOps);
AddCase(VectorFfbhI32NativeAndVop3OnGpu);
AddCase(Vop1SdwaFfblCapturedHighWordSource);
AddCase(Vop1SdwaNotCapturedByte0Source);
AddCase(Vop2SdwaSubNcExactByte2Destination);
AddCase(Vop2SdwaAddNcCapturedHighWordDestination);
AddCase(Vop2SdwaAshrrevCapturedWord0SignExtends);
@@ -20411,6 +20633,7 @@ std::vector<TestCase> MakeCases() {
AddCase(VectorSinCosMaxFiniteSpecialCases);
AddCase(VectorCompareOps);
AddCase(VectorVop3CompareEqI64OnGpu);
AddCase(VectorVop3CompareEqU64OnGpu);
AddCase(VectorVop3CompareGtU64OnGpu);
AddCase(VectorVop3CompareNeU64OnGpu);
AddCase(VectorVopcCmpxNeU64CapturedExecMask);
@@ -20507,7 +20730,9 @@ std::vector<TestCase> MakeCases() {
AddCase(ScratchIsPrivatePerInvocation);
AddCase(FlatStoreVariants);
AddCase(DsReadWriteVariants);
AddCase(DsWriteB16D16HiCapturedUsesHighHalf);
AddCase(DsReadU16D16CapturedPreservesHighHalf);
AddCase(DsReadU16D16HiCapturedPreservesLowHalf);
AddCase(DsAppendConsumeUsesEncodedLdsSelector);
AddCase(DsAppendUsesEncodedGdsSelector);
AddCase(DsGdsSubdwordAndAtomicWrites);
@@ -20535,6 +20760,7 @@ std::vector<TestCase> MakeCases() {
AddCase(ImageSamplePackedUintConvertsSampleAndGather);
AddCase(ImageLoadR128IgnoresAdjacentMaskSgprs);
AddCase(ImageLoad1DUsesScalarCoordinate);
AddCase(ImageGather2DInstructionWith1DDescriptor);
AddCase(ImageLoad1DArrayUsesLayerCoordinate);
AddCase(ImageLoad1DArrayDescriptorUsesSelectedLayer);
AddCase(ImageLoadMipUsesVaddr2Lod2D);
@@ -24290,6 +24516,11 @@ int main(int argc, char **argv) {
RunCase(&vulkan, Vop2SdwaAshrrevCapturedWord0SignExtends());
return 0;
}
if (argc == 2 && std::strcmp(argv[1], "--sdwa-addc-only") == 0) {
VulkanHarness vulkan;
RunCase(&vulkan, VectorAddcWritesPerLaneCarryOut());
return 0;
}
if (argc == 2 && std::strcmp(argv[1], "--cvt-pk-i16-only") == 0) {
VulkanHarness vulkan;
RunCase(&vulkan, Vop3CvtPkI16I32Captured());
@@ -24391,9 +24622,9 @@ int main(int argc, char **argv) {
CheckStandard64RenderTargetTileRoundTrip();
return 0;
}
if (argc == 2 && std::strcmp(argv[1], "--standard4-rt-only") == 0) {
if (argc == 2 && std::strcmp(argv[1], "--standard-tile-rt-only") == 0) {
VulkanHarness vulkan;
vulkan.CheckRenderExecutorColorStandard4TileDiscovery();
vulkan.CheckRenderExecutorColorStandardTileDiscovery();
return 0;
}
if (argc == 2 && std::strcmp(argv[1], "--image-view-only") == 0) {
@@ -24514,7 +24745,7 @@ int main(int argc, char **argv) {
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
vulkan.CheckRenderExecutorColor1DDiscovery();
vulkan.CheckRenderExecutorColorVolumeDiscovery();
vulkan.CheckRenderExecutorColorStandard4TileDiscovery();
vulkan.CheckRenderExecutorColorStandardTileDiscovery();
vulkan.CheckRenderExecutorColorDepthTileDiscovery();
vulkan.CheckRenderExecutorStencilBindingDiscovery();
vulkan.CheckUnifiedTextureCacheFlow();
+82 -26
View File
@@ -4007,6 +4007,30 @@ void TestNewShaderDecoderArchitecture() {
program_direct.word_count,
"program decoder diverged from the single-instruction decoder");
const uint32_t cmp_eq_u64_code[] = {0xd4e2006au, 0x0000d47eu};
Instruction cmp_eq_u64;
Check(DecodeInstruction(cmp_eq_u64_code, 0u, cmp_eq_u64, &error),
error.c_str());
Check(cmp_eq_u64.family == Family::VOP3 &&
cmp_eq_u64.opcode == Opcode::V_CMP_EQ_U64 &&
cmp_eq_u64.dst.kind == OperandKind::VccLo &&
cmp_eq_u64.src_count == 2u &&
cmp_eq_u64.src0.kind == OperandKind::ExecLo &&
cmp_eq_u64.src1.kind == OperandKind::VccLo,
"decoder rejected the captured VOP3 V_CMP_EQ_U64 instruction");
const uint32_t ffbh_i32_code[] = {0x7e1c770eu};
Instruction ffbh_i32;
Check(DecodeInstruction(ffbh_i32_code, 0u, ffbh_i32, &error),
error.c_str());
Check(ffbh_i32.family == Family::VOP1 &&
ffbh_i32.opcode == Opcode::V_FFBH_I32 &&
ffbh_i32.dst.kind == OperandKind::Vgpr && ffbh_i32.dst.reg == 14u &&
ffbh_i32.src_count == 1u &&
ffbh_i32.src0.kind == OperandKind::Vgpr &&
ffbh_i32.src0.reg == 14u,
"decoder rejected the captured VOP1 V_FFBH_I32 instruction");
const uint32_t literal_code[] = {EncodeVop1(0x01, 2, 255u), 0x12345678u};
Instruction literal;
Check(DecodeInstruction(literal_code, 0u, literal, &error), error.c_str());
@@ -4043,6 +4067,40 @@ void TestNewShaderDecoderArchitecture() {
"decoder rejected or misdecoded captured partial-destination "
"V_ADD_NC_U32");
const uint32_t vop2_sdwa_byte_shift[] = {
0x2c0910f9u,
0x86000613u, // v_lshrrev_b32 v4, v19.byte0, 8
};
Instruction byte_shift;
Check(DecodeInstruction(vop2_sdwa_byte_shift, 0u, byte_shift, &error),
error.c_str());
Check(byte_shift.family == Family::VOP2 &&
byte_shift.opcode == Opcode::V_LSHRREV_B32 &&
byte_shift.word_count == 2u && byte_shift.dst.reg == 4u &&
byte_shift.src0.kind == OperandKind::Vgpr &&
byte_shift.src0.reg == 19u && byte_shift.src0.sdwa_sel == 0u &&
byte_shift.src1.kind == OperandKind::IntegerInlineConstant &&
byte_shift.src1.value == 8u,
"decoder rejected or misdecoded captured V_LSHRREV_B32 byte shift");
const uint32_t vop2_sdwa_byte_left_shift[] = {
0x340d02f9u,
0x86030607u, // v_lshlrev_b32 v6, v7.byte3, 1
};
Instruction byte_left_shift;
Check(DecodeInstruction(vop2_sdwa_byte_left_shift, 0u, byte_left_shift,
&error),
error.c_str());
Check(byte_left_shift.family == Family::VOP2 &&
byte_left_shift.opcode == Opcode::V_LSHLREV_B32 &&
byte_left_shift.word_count == 2u && byte_left_shift.dst.reg == 6u &&
byte_left_shift.src0.kind == OperandKind::Vgpr &&
byte_left_shift.src0.reg == 7u &&
byte_left_shift.src0.sdwa_sel == 3u &&
byte_left_shift.src1.kind == OperandKind::IntegerInlineConstant &&
byte_left_shift.src1.value == 1u,
"decoder rejected or misdecoded captured V_LSHLREV_B32 byte shift");
const uint32_t mimg_nsa[] = {EncodeMimg0(0x20, 0xf) | (3u << 1u),
EncodeMimg1(4, 0, 1, 8), 0x03020100u,
0x07060504u, 0x0b0a0908u};
@@ -4065,6 +4123,28 @@ void TestNewShaderDecoderArchitecture() {
Check(boot.opcode == Opcode::DS_SWIZZLE_B32 && boot.offset == 0xc480u,
"DS decoder rejected a captured boot-shader instruction");
const uint32_t d16_hi_ds[] = {0xda9c0000u, 0x05000006u};
Instruction d16_hi;
Check(DecodeInstruction(d16_hi_ds, 0u, d16_hi, &error), error.c_str());
Check(d16_hi.opcode == Opcode::DS_READ_U16_D16_HI &&
d16_hi.src_count == 1u && d16_hi.data_bits == 16u &&
d16_hi.dst.reg == 5u && d16_hi.dst.sdwa_sel == 5u &&
d16_hi.dst.sdwa_dst_unused == 2u && d16_hi.src0.reg == 6u,
"DS decoder rejected the captured high-half D16 read");
const uint32_t d16_hi_write_ds[] = {0xda840000u, 0x00000214u};
Instruction d16_hi_write;
Check(DecodeInstruction(d16_hi_write_ds, 0u, d16_hi_write, &error),
error.c_str());
Check(d16_hi_write.family == Family::DS &&
d16_hi_write.opcode == Opcode::DS_WRITE_B16_D16_HI &&
d16_hi_write.word_count == 2u && d16_hi_write.src_count == 2u &&
d16_hi_write.data_dwords == 1u && d16_hi_write.data_bits == 16u &&
d16_hi_write.offset == 0u && !d16_hi_write.gds &&
d16_hi_write.src0.reg == 20u && d16_hi_write.src1.reg == 2u &&
d16_hi_write.src1.sdwa_sel == 5u,
"DS decoder rejected the captured high-half D16 write");
constexpr uint32_t packed_source_selectors[][2] = {
{0xcc0e0000u, 0x0c0a0300u}, // Source 0: instruction bit 59.
{0xcc0e0000u, 0x140a0300u}, // Source 1: instruction bit 60.
@@ -4083,8 +4163,8 @@ void TestNewShaderDecoderArchitecture() {
void TestNewShaderRecompilerRejectsDppOn64BitCompares() {
const uint32_t opcodes[] = {
0xa2u, 0xb5u, 0xe4u, 0xe5u,
0xf5u}; // eq_i64, cmpx_ne_i64, gt_u64, ne_u64, cmpx_ne_u64
0xa2u, 0xb5u, 0xe2u, 0xe4u, 0xe5u,
0xf5u}; // eq_i64, cmpx_ne_i64, eq_u64, gt_u64, ne_u64, cmpx_ne_u64
for (const auto opcode : opcodes) {
const uint32_t shader[] = {
EncodeVopc(opcode, 250u, 0u), // DPP escape in SRC0
@@ -5054,30 +5134,6 @@ void TestNewShaderRecompilerNullImageUsesCanonical2DView() {
"null image retained the decoded 1D descriptor binding");
}
void TestNewShaderRecompilerRejectsOneDimensionalGather() {
constexpr std::array cases{
std::pair{0u, Prospero::ImageType::kColor1D},
std::pair{4u, Prospero::ImageType::kColor1DArray},
};
for (const auto &[dimension, type] : cases) {
const uint32_t shader[] = {
EncodeMimg0(0x47, 0x1, false, dimension),
EncodeMimg1(0, 0, 1, 0),
0xbf810000u,
};
auto user_data = ImageTestUserData(type);
auto options = MakeCompileOptions(ShaderType::Compute);
options.user_data = user_data.data();
ShaderRecompiler::CompileResult result;
std::string error;
Check(!ShaderRecompiler::TryRecompile(shader, options, result, &error) &&
Common::ContainsStr(error,
"1D image gather is not supported by SPIR-V"),
"1D image gather escaped as invalid SPIR-V");
}
}
void TestNewShaderRecompilerImageGatherVariants() {
const uint32_t shader[] = {
EncodeMimg0(0x47, 0x1),