mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-29 08:41:20 +00:00
Compare commits
80
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
019929587c | ||
|
|
f68c77d785 | ||
|
|
b5ba482fc2 | ||
|
|
9860c2bbf8 | ||
|
|
ee3749d9b6 | ||
|
|
3fad77fd8b | ||
|
|
d6b2abe26f | ||
|
|
79530b4c5e | ||
|
|
d8a4412498 | ||
|
|
5ed5fdd36e | ||
|
|
ef83ba65ad | ||
|
|
d30dca78be | ||
|
|
66f1a82365 | ||
|
|
eb8a5504a3 | ||
|
|
8a5e5c5476 | ||
|
|
03c55e5712 | ||
|
|
5d1ad31c49 | ||
|
|
13fcb3a133 | ||
|
|
d6dac92da1 | ||
|
|
90471836f7 | ||
|
|
8252caee04 | ||
|
|
8b095caefe | ||
|
|
8dc9b9d532 | ||
|
|
e40438f95e | ||
|
|
a6a56c8a44 | ||
|
|
4b074cf9e9 | ||
|
|
7c9e18e73b | ||
|
|
d2df2cad74 | ||
|
|
b2a80a3a96 | ||
|
|
cfb8f7b246 | ||
|
|
d792dfa885 | ||
|
|
12ff571609 | ||
|
|
d3c182f385 | ||
|
|
bf4dcdce52 | ||
|
|
476204cd4b | ||
|
|
f9e56ef8a0 | ||
|
|
61761b65f2 | ||
|
|
ba55ba5dc5 | ||
|
|
c997cdb837 | ||
|
|
fe4f9423e3 | ||
|
|
81af130147 | ||
|
|
ada0514084 | ||
|
|
d7da244475 | ||
|
|
549495575c | ||
|
|
c3cc8454b9 | ||
|
|
b8ad1d8c4b | ||
|
|
cfb110e0aa | ||
|
|
e930657bf6 | ||
|
|
0ad9149c90 | ||
|
|
25f2699523 | ||
|
|
a849f25376 | ||
|
|
fa51da4d76 | ||
|
|
6b79b7a003 | ||
|
|
102a10f0fe | ||
|
|
8d66c4ef6d | ||
|
|
8cecaf4e30 | ||
|
|
82b2711cdf | ||
|
|
35a275e97d | ||
|
|
303a0203b6 | ||
|
|
cfbaee07f0 | ||
|
|
fe45265fcd | ||
|
|
8b294eb5b8 | ||
|
|
07c9978ac6 | ||
|
|
b61b3f2a40 | ||
|
|
001dfea501 | ||
|
|
f100f785da | ||
|
|
ba1854bbdc | ||
|
|
ea6aae8577 | ||
|
|
5b7d3343cf | ||
|
|
394e638905 | ||
|
|
c2b7b0b58c | ||
|
|
1d2f59dfa4 | ||
|
|
007d7ea2eb | ||
|
|
95d3d9d3ad | ||
|
|
bfc8cd0e0b | ||
|
|
165234b159 | ||
|
|
6493674726 | ||
|
|
cbedf5707f | ||
|
|
57c5b206ca | ||
|
|
87b4417aca |
No files matched your search
@@ -43,9 +43,6 @@ jobs:
|
||||
- name: Setup Visual Studio environment
|
||||
uses: ilammy/msvc-dev-cmd@v1
|
||||
|
||||
- name: Setup Ninja
|
||||
uses: seanmiddleditch/gha-setup-ninja@v5
|
||||
|
||||
- name: Locate vcpkg
|
||||
id: vcpkg
|
||||
shell: pwsh
|
||||
@@ -155,7 +152,7 @@ jobs:
|
||||
|
||||
- name: Install build dependencies
|
||||
shell: bash
|
||||
run: brew install glslang ninja
|
||||
run: brew install glslang
|
||||
|
||||
- name: Install Qt 6.10.3
|
||||
uses: jurplel/install-qt-action@v4
|
||||
@@ -317,8 +314,8 @@ jobs:
|
||||
libxkbcommon-dev \
|
||||
libxrandr-dev \
|
||||
libxss-dev \
|
||||
libxtst-dev \
|
||||
lld \
|
||||
ninja-build \
|
||||
pkg-config \
|
||||
wayland-protocols
|
||||
|
||||
@@ -363,7 +360,7 @@ jobs:
|
||||
-DCMAKE_PREFIX_PATH="$Qt6_DIR" 2>&1 | tee _Build/configure.log
|
||||
exit "${PIPESTATUS[0]}"
|
||||
|
||||
- name: Verify SDL2 backends
|
||||
- name: Verify SDL3 backends
|
||||
shell: bash
|
||||
run: |
|
||||
status=0
|
||||
|
||||
+3
-3
@@ -1,7 +1,7 @@
|
||||
[submodule "3rdparty/SDL2"]
|
||||
path = 3rdparty/SDL2
|
||||
[submodule "3rdparty/SDL3"]
|
||||
path = 3rdparty/SDL3
|
||||
url = https://github.com/libsdl-org/SDL.git
|
||||
branch = SDL2
|
||||
branch = release-3.4.x
|
||||
[submodule "3rdparty/magic_enum"]
|
||||
path = 3rdparty/magic_enum
|
||||
url = https://github.com/Neargye/magic_enum.git
|
||||
|
||||
Vendored
+4
-1
@@ -29,7 +29,7 @@ FetchContent_Declare(
|
||||
FetchContent_MakeAvailable(xbyak zydis)
|
||||
|
||||
set(SDL_SHARED OFF CACHE BOOL "" FORCE)
|
||||
add_subdirectory(SDL2)
|
||||
add_subdirectory(SDL3)
|
||||
|
||||
add_library(Vulkan-Headers INTERFACE)
|
||||
add_library(Vulkan::Headers ALIAS Vulkan-Headers)
|
||||
@@ -86,6 +86,9 @@ set(SPDLOG_BUILD_WARNINGS OFF CACHE BOOL "" FORCE)
|
||||
set(SPDLOG_INSTALL OFF CACHE BOOL "" FORCE)
|
||||
set(SPDLOG_FMT_EXTERNAL ON CACHE BOOL "" FORCE)
|
||||
set(SPDLOG_NO_EXCEPTIONS ON CACHE BOOL "" FORCE)
|
||||
if(WIN32)
|
||||
set(SPDLOG_WCHAR_FILENAMES ON CACHE BOOL "" FORCE)
|
||||
endif()
|
||||
add_subdirectory(spdlog EXCLUDE_FROM_ALL)
|
||||
|
||||
if (NOT TARGET FFmpeg::ffmpeg)
|
||||
|
||||
Vendored
-1
Submodule 3rdparty/SDL2 deleted from 4b69833bc5.
+1
Submodule 3rdparty/SDL3 added at fa2c02bb6e.
+31
-3
@@ -1,4 +1,4 @@
|
||||
cmake_minimum_required(VERSION 3.12...4.3)
|
||||
cmake_minimum_required(VERSION 3.16...4.3)
|
||||
|
||||
if(CMAKE_VERSION VERSION_GREATER_EQUAL 4.0)
|
||||
set(CMAKE_POLICY_VERSION_MINIMUM 3.5 CACHE STRING "Minimum policy version for bundled dependencies")
|
||||
@@ -12,6 +12,22 @@ endif()
|
||||
|
||||
project(Kyty)
|
||||
|
||||
find_program(SCCACHE_PROGRAM sccache)
|
||||
find_program(CCACHE_PROGRAM ccache)
|
||||
|
||||
foreach(lang IN ITEMS C CXX)
|
||||
# An explicitly empty launcher disables caching for this language.
|
||||
if(NOT DEFINED CMAKE_${lang}_COMPILER_LAUNCHER)
|
||||
if(SCCACHE_PROGRAM)
|
||||
set(CMAKE_${lang}_COMPILER_LAUNCHER "${SCCACHE_PROGRAM}")
|
||||
message(STATUS "sccache enabled for ${lang}: ${SCCACHE_PROGRAM}")
|
||||
elseif(CCACHE_PROGRAM)
|
||||
set(CMAKE_${lang}_COMPILER_LAUNCHER "${CCACHE_PROGRAM}")
|
||||
message(STATUS "ccache enabled for ${lang}: ${CCACHE_PROGRAM}")
|
||||
endif()
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
set(KYTY_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/src")
|
||||
set(KYTY_TESTS_DIR "${CMAKE_CURRENT_SOURCE_DIR}/tests")
|
||||
set(KYTY_THIRD_PARTY_DIR "${CMAKE_CURRENT_SOURCE_DIR}/3rdparty")
|
||||
@@ -315,7 +331,7 @@ if (KYTY_CLANG_CL)
|
||||
set_source_files_properties(${kyty_emulator_src} PROPERTIES COMPILE_FLAGS "-Wno-pragma-pack -Wno-deprecated-declarations -D_TIMESPEC_DEFINED")
|
||||
endif()
|
||||
|
||||
set(kyty_emulator_link_libraries common Vulkan::Headers spirv-tools-opt spirv-tools SDL2-static imgui xxhash FFmpeg::ffmpeg fmt::fmt nlohmann_json::nlohmann_json LibAtrac9 kyty_git_version Zydis::Zydis xbyak::xbyak)
|
||||
set(kyty_emulator_link_libraries common Vulkan::Headers spirv-tools-opt spirv-tools SDL3::SDL3-static imgui xxhash FFmpeg::ffmpeg fmt::fmt nlohmann_json::nlohmann_json LibAtrac9 kyty_git_version Zydis::Zydis xbyak::xbyak)
|
||||
|
||||
# Linux system libraries.
|
||||
if(LINUX)
|
||||
@@ -325,7 +341,6 @@ endif()
|
||||
|
||||
set(inc_headers
|
||||
${KYTY_SOURCE_DIR}
|
||||
${KYTY_THIRD_PARTY_DIR}/SDL2/include
|
||||
${KYTY_THIRD_PARTY_DIR}/VulkanMemoryAllocator/include
|
||||
${KYTY_THIRD_PARTY_DIR}/SPIRV-Tools/include
|
||||
${KYTY_THIRD_PARTY_DIR}/SPIRV-Headers/include
|
||||
@@ -450,6 +465,12 @@ add_executable(audio_out2_port_tests EXCLUDE_FROM_ALL
|
||||
target_link_libraries(audio_out2_port_tests common fmt::fmt)
|
||||
target_include_directories(audio_out2_port_tests PRIVATE ${inc_headers})
|
||||
|
||||
add_executable(save_data_memory_tests EXCLUDE_FROM_ALL
|
||||
"${KYTY_TESTS_DIR}/SaveDataMemoryTests.cpp"
|
||||
)
|
||||
target_link_libraries(save_data_memory_tests common fmt::fmt)
|
||||
target_include_directories(save_data_memory_tests PRIVATE ${inc_headers})
|
||||
|
||||
add_executable(event_queue_lifetime_tests EXCLUDE_FROM_ALL
|
||||
"${KYTY_TESTS_DIR}/EventQueueLifetimeTests.cpp"
|
||||
"${KYTY_SOURCE_DIR}/kernel/eventQueue.cpp"
|
||||
@@ -534,6 +555,7 @@ if(BUILD_TESTING)
|
||||
add_test(NAME kernel_file_system COMMAND $<TARGET_FILE:kernel_file_system_tests>)
|
||||
add_test(NAME sync_on_address COMMAND $<TARGET_FILE:sync_on_address_tests>)
|
||||
add_test(NAME audio_out2_port COMMAND $<TARGET_FILE:audio_out2_port_tests>)
|
||||
add_test(NAME save_data_memory COMMAND $<TARGET_FILE:save_data_memory_tests>)
|
||||
add_test(NAME shader_recompiler_compute COMMAND $<TARGET_FILE:shader_recompiler_compute_tests>)
|
||||
add_test(NAME shader_recompiler_alignbyte
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --alignbyte-only)
|
||||
@@ -658,6 +680,12 @@ elseif(WIN32 OR LINUX)
|
||||
set_target_properties(kyty_emulator PROPERTIES LINK_FLAGS "${KYTY_LD_OPTIONS} -Wl,-Map=${KYTY_EMULATOR_MAP_LINK_PATH}")
|
||||
endif()
|
||||
|
||||
if(LINUX)
|
||||
target_link_options(kyty_emulator PRIVATE
|
||||
$<$<CONFIG:Release>:-Wl,--strip-all>
|
||||
)
|
||||
endif()
|
||||
|
||||
if(APPLE)
|
||||
# The emulator writes x86-64 trampolines/PLT stubs into memory and executes them, and
|
||||
# runs guest code under Rosetta. That requires the JIT / unsigned-executable-memory
|
||||
|
||||
@@ -166,15 +166,15 @@ The finished application and its runtime dependencies will be placed in
|
||||
|
||||
### Building on Linux
|
||||
|
||||
Install the toolchain and the libraries the bundled SDL2 needs. Without the audio, Wayland and
|
||||
udev development packages SDL2 quietly configures itself without those backends, and the resulting
|
||||
Install the toolchain and the libraries the bundled SDL3 needs. Without the audio, Wayland and
|
||||
udev development packages SDL3 quietly configures itself without those backends, and the resulting
|
||||
build has no working sound and no gamepad hotplug:
|
||||
|
||||
```bash
|
||||
sudo apt-get install --no-install-recommends \
|
||||
clang lld ninja-build cmake git glslang-tools pkg-config \
|
||||
libgl1-mesa-dev libx11-dev libxcursor-dev libxext-dev libxfixes-dev \
|
||||
libxi-dev libxrandr-dev libxss-dev libxkbcommon-dev \
|
||||
libxi-dev libxrandr-dev libxss-dev libxtst-dev libxkbcommon-dev \
|
||||
libasound2-dev libpulse-dev libudev-dev libdbus-1-dev libwayland-dev wayland-protocols
|
||||
```
|
||||
|
||||
@@ -204,7 +204,7 @@ The CMake source root is the repository root.
|
||||
|
||||
### Building on NixOS
|
||||
|
||||
A development shell provides Clang, CMake, Ninja, Qt 6, the Vulkan headers, and the SDL2 backend
|
||||
A development shell provides Clang, CMake, Ninja, Qt 6, the Vulkan headers, and the SDL3 backend
|
||||
libraries. Enter it and configure exactly as on other Linux distributions; the shell exports
|
||||
`CMAKE_PREFIX_PATH` and `QT_PLUGIN_PATH`, so the `-DCMAKE_PREFIX_PATH="$Qt6_DIR"` argument is not
|
||||
needed:
|
||||
|
||||
+3
-2
@@ -1,8 +1,7 @@
|
||||
{ pkgs }:
|
||||
|
||||
let
|
||||
# Build-time dependencies. Every -dev package the bundled SDL2 probes for must
|
||||
# be listed here, otherwise SDL2 quietly drops the audio/Wayland/udev backends.
|
||||
# Build-time dependencies for the bundled SDL3 audio, video and input backends.
|
||||
buildDeps = with pkgs; [
|
||||
qt6.qtbase # Concurrent, Network, Widgets (launcher)
|
||||
vulkan-headers
|
||||
@@ -18,6 +17,7 @@ let
|
||||
libXi
|
||||
libXrandr
|
||||
libXScrnSaver
|
||||
libXtst
|
||||
libxkbcommon
|
||||
# Wayland
|
||||
wayland
|
||||
@@ -46,6 +46,7 @@ let
|
||||
libXi
|
||||
libXrandr
|
||||
libXScrnSaver
|
||||
libXtst
|
||||
libxkbcommon
|
||||
wayland
|
||||
libdecor
|
||||
|
||||
@@ -135,6 +135,10 @@ bool ReadbackLinearImagesEnabled() {
|
||||
return g_config->readback_linear_images;
|
||||
}
|
||||
|
||||
bool TessellationEnabled() {
|
||||
return g_config->tessellation_enabled;
|
||||
}
|
||||
|
||||
bool PlayGoHackEnabled() {
|
||||
return g_config->playgo_hack_enabled;
|
||||
}
|
||||
|
||||
@@ -66,6 +66,7 @@ struct ConfigOptions {
|
||||
bool gpu_assisted_validation_enabled = false;
|
||||
bool renderdoc_enabled = false;
|
||||
bool readback_linear_images = false;
|
||||
bool tessellation_enabled = false;
|
||||
bool playgo_hack_enabled = false;
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
bool red_zone_protection_enabled = false;
|
||||
@@ -110,6 +111,7 @@ bool GpuAssistedValidationEnabled();
|
||||
|
||||
bool RenderDocEnabled();
|
||||
bool ReadbackLinearImagesEnabled();
|
||||
bool TessellationEnabled();
|
||||
bool PlayGoHackEnabled();
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
bool RedZoneProtectionEnabled();
|
||||
|
||||
+49
-109
@@ -245,88 +245,78 @@ void File::Printf(const char* format, ...) {
|
||||
Write(s.data(), static_cast<uint32_t>(s.size()));
|
||||
}
|
||||
|
||||
static std::filesystem::path WithoutTrailingSeparator(
|
||||
const std::filesystem::path& path) {
|
||||
if (!path.empty() && !path.has_filename() &&
|
||||
path != path.root_path()) {
|
||||
return path.parent_path();
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
|
||||
bool File::IsDirectoryExisting(const std::filesystem::path& path) {
|
||||
auto path_str = PathToGenericString(path);
|
||||
return SysFileIsDirectoryExisting(path_str.ends_with("/") ? Common::RemoveLast(path_str, 1)
|
||||
: path_str);
|
||||
return SysFileIsDirectoryExisting(WithoutTrailingSeparator(path));
|
||||
}
|
||||
|
||||
bool File::IsFileExisting(const std::filesystem::path& name) {
|
||||
return SysFileIsFileExisting(name);
|
||||
}
|
||||
|
||||
bool File::CreateDirectory(
|
||||
const std::filesystem::path& path) // @suppress("Member declaration not found")
|
||||
{
|
||||
auto path_str = PathToGenericString(path);
|
||||
return SysFileCreateDirectory(path_str.ends_with("/") ? Common::RemoveLast(path_str, 1)
|
||||
: path_str);
|
||||
bool File::CreateDirectory(const std::filesystem::path& path) {
|
||||
return SysFileCreateDirectory(
|
||||
WithoutTrailingSeparator(path));
|
||||
}
|
||||
|
||||
bool File::DeleteDirectory(const std::filesystem::path& path) {
|
||||
auto path_str = PathToGenericString(path);
|
||||
return SysFileDeleteDirectory(path_str.ends_with("/") ? Common::RemoveLast(path_str, 1)
|
||||
: path_str);
|
||||
return SysFileDeleteDirectory(
|
||||
WithoutTrailingSeparator(path));
|
||||
}
|
||||
|
||||
bool File::CreateDirectories(const std::filesystem::path& path) {
|
||||
std::string real_path = Common::ReplaceChar(PathToGenericString(path), '\\', '/');
|
||||
const auto normalized_path =
|
||||
WithoutTrailingSeparator(path);
|
||||
|
||||
std::vector<std::string> list = Common::Split(real_path, "/");
|
||||
auto current_path = normalized_path.root_path();
|
||||
|
||||
std::string p;
|
||||
for (const auto& component:
|
||||
normalized_path.relative_path()) {
|
||||
current_path /= component;
|
||||
|
||||
for (uint32_t si = 0; si < list.size(); si++) {
|
||||
const std::string& s = list[si];
|
||||
if (IsDirectoryExisting(current_path)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (si != 0 || real_path.starts_with("/")) {
|
||||
p += "/";
|
||||
}
|
||||
if (!CreateDirectory(current_path)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
p += s;
|
||||
|
||||
if (IsDirectoryExisting(p)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!CreateDirectory(p)) // @suppress("Invalid arguments")
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool File::DeleteDirectories(const std::filesystem::path& path) {
|
||||
std::string real_path = Common::ReplaceChar(PathToGenericString(path), '\\', '/');
|
||||
const auto normalized_path =
|
||||
WithoutTrailingSeparator(path);
|
||||
|
||||
std::vector<std::string> list = Common::Split(real_path, "/");
|
||||
std::vector<std::filesystem::path> directories;
|
||||
auto current_path = normalized_path.root_path();
|
||||
|
||||
std::string p;
|
||||
std::vector<std::string> list2;
|
||||
for (const auto& component:
|
||||
normalized_path.relative_path()) {
|
||||
current_path /= component;
|
||||
directories.push_back(current_path);
|
||||
}
|
||||
|
||||
for (uint32_t si = 0; si < list.size(); si++) {
|
||||
const std::string& s = list[si];
|
||||
for (auto it = directories.rbegin();
|
||||
it != directories.rend(); ++it) {
|
||||
if (!DeleteDirectory(*it)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (si != 0 || real_path.starts_with("/")) {
|
||||
p += "/";
|
||||
}
|
||||
|
||||
p += s;
|
||||
|
||||
list2.push_back(p);
|
||||
}
|
||||
|
||||
uint32_t num = list2.size();
|
||||
|
||||
for (uint32_t si = num - 1; si < num; si--) {
|
||||
if (!DeleteDirectory(list2[si])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool File::DeleteFile(
|
||||
@@ -455,51 +445,6 @@ bool File::SetLastAccessAndWriteTimeUTC(const std::filesystem::path& name, const
|
||||
return SysFileSetLastAccessAndWriteTimeUtc(name, af, wf);
|
||||
}
|
||||
|
||||
std::vector<File::FindInfo> File::FindFiles(const std::filesystem::path& path) {
|
||||
std::vector<sys_file_find_t> files;
|
||||
|
||||
SysFileFindFiles(path, files);
|
||||
|
||||
auto path_str = PathToGenericString(path);
|
||||
uint32_t len = static_cast<uint32_t>(path_str.size());
|
||||
|
||||
if (!path_str.ends_with("/") && !path_str.ends_with("\\")) {
|
||||
len++;
|
||||
}
|
||||
|
||||
std::vector<File::FindInfo> ret;
|
||||
ret.reserve(files.size());
|
||||
|
||||
for (const auto& f: files) {
|
||||
File::FindInfo r {};
|
||||
|
||||
r.path_with_name = f.path_with_name;
|
||||
r.rel_path_with_name = Common::Mid(PathToGenericString(f.path_with_name), len);
|
||||
r.size = f.size;
|
||||
|
||||
SysTimeStruct at {};
|
||||
SysTimeStruct wt {};
|
||||
|
||||
SysFileToSystemTimeUtc(f.last_access_time, at);
|
||||
SysFileToSystemTimeUtc(f.last_write_time, wt);
|
||||
|
||||
if (!at.is_invalid && !wt.is_invalid) {
|
||||
r.last_access_time = DateTime(Date(at.Year, at.Month, at.Day),
|
||||
Time(at.Hour, at.Minute, at.Second, at.Milliseconds));
|
||||
r.last_write_time = DateTime(Date(wt.Year, wt.Month, wt.Day),
|
||||
Time(wt.Hour, wt.Minute, wt.Second, wt.Milliseconds));
|
||||
}
|
||||
|
||||
// printf("%s, %s, %" PRIu64", %s, %s\n", r.path_with_name.c_str(),
|
||||
// r.rel_path_with_name.c_str(), r.size, r.last_access_time.ToString().c_str(),
|
||||
// r.last_write_time.ToString().c_str());
|
||||
|
||||
ret.push_back(std::move(r));
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::vector<File::DirEntry> File::GetDirEntries(const std::filesystem::path& path) {
|
||||
std::vector<sys_dir_entry_t> files;
|
||||
|
||||
@@ -508,13 +453,8 @@ std::vector<File::DirEntry> File::GetDirEntries(const std::filesystem::path& pat
|
||||
std::vector<File::DirEntry> ret;
|
||||
ret.reserve(files.size());
|
||||
|
||||
for (const auto& f: files) {
|
||||
File::DirEntry r {};
|
||||
|
||||
r.name = f.name;
|
||||
r.is_file = f.is_file;
|
||||
|
||||
ret.push_back(std::move(r));
|
||||
for (auto& f: files) {
|
||||
ret.emplace_back(std::move(f.name), f.is_file);
|
||||
}
|
||||
|
||||
return ret;
|
||||
|
||||
@@ -30,14 +30,6 @@ class File {
|
||||
public:
|
||||
enum class Mode { Read, Write, ReadWrite, WriteRead };
|
||||
|
||||
struct FindInfo {
|
||||
std::filesystem::path path_with_name;
|
||||
std::filesystem::path rel_path_with_name;
|
||||
DateTime last_access_time;
|
||||
DateTime last_write_time;
|
||||
uint64_t size;
|
||||
};
|
||||
|
||||
struct DirEntry {
|
||||
std::string name;
|
||||
bool is_file;
|
||||
@@ -98,7 +90,6 @@ public:
|
||||
static bool SetLastAccessAndWriteTimeUTC(const std::filesystem::path& name,
|
||||
const DateTime& access, const DateTime& write);
|
||||
|
||||
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 RenameFile(const std::filesystem::path& src, const std::filesystem::path& dst);
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/emulatorConfig.h"
|
||||
#include "common/stringUtils.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <filesystem>
|
||||
@@ -47,7 +46,7 @@ std::shared_ptr<spdlog::logger> MakeFileLogger(std::string name
|
||||
}
|
||||
|
||||
auto sink =
|
||||
std::make_shared<spdlog::sinks::basic_file_sink_mt>(Common::PathToString(path), true);
|
||||
std::make_shared<spdlog::sinks::basic_file_sink_mt>(path.native(), true);
|
||||
return MakeLogger(std::move(name), std::move(sink));
|
||||
}
|
||||
|
||||
|
||||
@@ -18,14 +18,6 @@ enum sys_file_cache_type_t {
|
||||
SYS_FILE_CACHE_SEQUENTIAL_SCAN = 2 // NOLINT(readability-identifier-naming)
|
||||
};
|
||||
|
||||
// NOLINTNEXTLINE(readability-identifier-naming)
|
||||
struct sys_file_find_t {
|
||||
std::filesystem::path path_with_name;
|
||||
SysFileTimeStruct last_access_time;
|
||||
SysFileTimeStruct last_write_time;
|
||||
uint64_t size;
|
||||
};
|
||||
|
||||
// NOLINTNEXTLINE(readability-identifier-naming)
|
||||
struct sys_dir_entry_t {
|
||||
std::string name;
|
||||
@@ -75,8 +67,6 @@ bool SysFileSetLastWriteTimeUtc(const std::filesystem::path& name, SysFileTimeSt
|
||||
bool SysFileSetLastAccessAndWriteTimeUtc(const std::filesystem::path& name,
|
||||
SysFileTimeStruct& access, SysFileTimeStruct& write);
|
||||
// NOLINTNEXTLINE(google-runtime-references)
|
||||
void SysFileFindFiles(const std::filesystem::path& path, std::vector<sys_file_find_t>& out);
|
||||
// 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 SysFileRenameFile(const std::filesystem::path& src, const std::filesystem::path& dst);
|
||||
|
||||
@@ -610,51 +610,6 @@ bool SysFileSetLastAccessAndWriteTimeUtc(const std::filesystem::path& name,
|
||||
// }
|
||||
}
|
||||
|
||||
// Recursively collect regular files.
|
||||
void SysFileFindFiles(const std::filesystem::path& path, std::vector<sys_file_find_t>& out) {
|
||||
auto real_path = get_internal_name(path);
|
||||
|
||||
DIR* dir = opendir(real_path.string().c_str());
|
||||
if (dir == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const dirent* entry = readdir(dir); entry != nullptr; entry = readdir(dir)) {
|
||||
const std::string file_name(entry->d_name);
|
||||
|
||||
if (file_name == "." || file_name == "..") {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto child = real_path / file_name;
|
||||
struct stat s {};
|
||||
|
||||
// lstat, so a symlink is never followed into a cycle during the recursive walk.
|
||||
if (0 != lstat(child.string().c_str(), &s)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (S_ISDIR(s.st_mode)) {
|
||||
SysFileFindFiles(child, out);
|
||||
} else if (S_ISREG(s.st_mode)) {
|
||||
sys_file_find_t r {};
|
||||
|
||||
r.path_with_name = child;
|
||||
r.size = static_cast<uint64_t>(s.st_size);
|
||||
r.last_access_time.is_invalid = false;
|
||||
r.last_access_time.time = s.st_atime;
|
||||
r.last_access_time.nanos = KYTY_STAT_ATIME_NS(s);
|
||||
r.last_write_time.is_invalid = false;
|
||||
r.last_write_time.time = s.st_mtime;
|
||||
r.last_write_time.nanos = KYTY_STAT_MTIME_NS(s);
|
||||
|
||||
out.push_back(r);
|
||||
}
|
||||
}
|
||||
|
||||
closedir(dir);
|
||||
}
|
||||
|
||||
// Keep "." and ".." to match FindFirstFileW.
|
||||
void SysFileGetDents(const std::filesystem::path& path, std::vector<sys_dir_entry_t>& out) {
|
||||
auto real_path = get_internal_name(path);
|
||||
|
||||
@@ -57,10 +57,10 @@ static DWORD GetCacheAccessType(sys_file_cache_type_t t) {
|
||||
}
|
||||
|
||||
if (t == SYS_FILE_CACHE_SEQUENTIAL_SCAN) {
|
||||
return SYS_FILE_CACHE_SEQUENTIAL_SCAN;
|
||||
return FILE_FLAG_SEQUENTIAL_SCAN;
|
||||
}
|
||||
|
||||
return SYS_FILE_CACHE_AUTO;
|
||||
return FILE_ATTRIBUTE_NORMAL;
|
||||
}
|
||||
|
||||
void SysFileRead(void* data, uint32_t size, sys_file_t& f, uint32_t* bytes_read) {
|
||||
@@ -476,66 +476,11 @@ bool SysFileSetLastAccessAndWriteTimeUtc(const std::filesystem::path& name,
|
||||
return ok;
|
||||
}
|
||||
|
||||
void SysFileFindFiles(const std::filesystem::path& path, std::vector<sys_file_find_t>& out) {
|
||||
std::string real_path = Common::ReplaceChar(Common::PathToGenericString(path), '\\', '/');
|
||||
if (!real_path.ends_with("/")) {
|
||||
real_path += "/";
|
||||
}
|
||||
|
||||
std::string pattern = real_path + "*";
|
||||
|
||||
HANDLE h = nullptr;
|
||||
WIN32_FIND_DATAW data;
|
||||
|
||||
auto wide_pattern = std::filesystem::path(pattern).wstring();
|
||||
h = FindFirstFileW(wide_pattern.c_str(), &data);
|
||||
|
||||
if (h == INVALID_HANDLE_VALUE) {
|
||||
return;
|
||||
}
|
||||
|
||||
do {
|
||||
std::filesystem::path file_name(data.cFileName);
|
||||
auto file_name_str = Common::PathToString(file_name);
|
||||
|
||||
if (file_name_str == "." || file_name_str == "..") {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((data.dwFileAttributes & static_cast<DWORD>(FILE_ATTRIBUTE_DIRECTORY)) != 0u) {
|
||||
SysFileFindFiles(std::filesystem::path(real_path) / file_name, out);
|
||||
} else {
|
||||
sys_file_find_t r {};
|
||||
|
||||
r.path_with_name = std::filesystem::path(real_path) / file_name;
|
||||
r.size = (static_cast<uint64_t>(data.nFileSizeHigh) << 32u) +
|
||||
static_cast<uint64_t>(data.nFileSizeLow);
|
||||
r.last_access_time.is_invalid = false;
|
||||
r.last_access_time.time = data.ftLastAccessTime;
|
||||
r.last_write_time.is_invalid = false;
|
||||
r.last_write_time.time = data.ftLastWriteTime;
|
||||
|
||||
out.push_back(r);
|
||||
}
|
||||
|
||||
} while (FindNextFileW(h, &data) != 0);
|
||||
|
||||
FindClose(h);
|
||||
}
|
||||
|
||||
void SysFileGetDents(const std::filesystem::path& path, std::vector<sys_dir_entry_t>& out) {
|
||||
std::string real_path = Common::ReplaceChar(Common::PathToGenericString(path), '\\', '/');
|
||||
if (!real_path.ends_with("/")) {
|
||||
real_path += "/";
|
||||
}
|
||||
const auto pattern = path / L"*";
|
||||
|
||||
std::string pattern = real_path + "*";
|
||||
|
||||
HANDLE h = nullptr;
|
||||
WIN32_FIND_DATAW data;
|
||||
|
||||
auto wide_pattern = std::filesystem::path(pattern).wstring();
|
||||
h = FindFirstFileW(wide_pattern.c_str(), &data);
|
||||
WIN32_FIND_DATAW data {};
|
||||
HANDLE h = FindFirstFileW(pattern.c_str(), &data);
|
||||
|
||||
if (h == INVALID_HANDLE_VALUE) {
|
||||
return;
|
||||
@@ -549,7 +494,7 @@ void SysFileGetDents(const std::filesystem::path& path, std::vector<sys_dir_entr
|
||||
r.is_file = ((data.dwFileAttributes & static_cast<DWORD>(FILE_ATTRIBUTE_DIRECTORY)) == 0u);
|
||||
r.name = Common::PathToString(file_name);
|
||||
|
||||
out.push_back(r);
|
||||
out.push_back(std::move(r));
|
||||
|
||||
} while (FindNextFileW(h, &data) != 0);
|
||||
|
||||
|
||||
@@ -34,6 +34,14 @@ inline std::string PathToGenericString(const std::filesystem::path& path) {
|
||||
#endif
|
||||
}
|
||||
|
||||
inline std::filesystem::path PathFromUtf8(std::string_view text) {
|
||||
#if defined(__cpp_char8_t)
|
||||
return std::filesystem::path(std::u8string(text.begin(), text.end()));
|
||||
#else
|
||||
return std::filesystem::u8path(text.begin(), text.end());
|
||||
#endif
|
||||
}
|
||||
|
||||
inline bool EqualNoCase(std::string_view lhs, std::string_view rhs) {
|
||||
if (lhs.size() != rhs.size()) {
|
||||
return false;
|
||||
|
||||
@@ -73,6 +73,9 @@ public:
|
||||
void SetIndexBufferSize(uint32_t index_buffer_size);
|
||||
void SetDrawIndirectArgsBaseAddress(uint64_t draw_indirect_args_base_addr);
|
||||
void SetDispatchIndirectArgsBaseAddress(uint64_t dispatch_indirect_args_base_addr);
|
||||
[[nodiscard]] uint64_t GetDispatchIndirectArgsBaseAddress() const {
|
||||
return m_dispatch_indirect_args_base_addr;
|
||||
}
|
||||
void SetNumInstances(uint32_t num_instances);
|
||||
void DrawIndex(DrawIndexArgs args);
|
||||
void DrawIndexOffset(uint32_t index_offset, uint32_t index_count);
|
||||
@@ -99,7 +102,7 @@ public:
|
||||
void TriggerEopEventAtEndOfPipe(uint32_t interrupt_context_id);
|
||||
void DispatchDirect(uint32_t thread_group_x, uint32_t thread_group_y, uint32_t thread_group_z,
|
||||
uint32_t mode);
|
||||
void DispatchIndirect(uint32_t data_offset, uint32_t mode);
|
||||
void DispatchIndirect(uint64_t args_addr, uint32_t mode);
|
||||
void WaitFlipDone(uint32_t video_out_handle, uint32_t display_buffer_index);
|
||||
void TriggerEvent(uint32_t event_type, uint32_t event_index, uint64_t event_address = 0);
|
||||
|
||||
@@ -148,7 +151,6 @@ private:
|
||||
void SuspendPm4();
|
||||
CommandScheduler& GetScheduler() const { return m_renderer.GetCommandScheduler(); }
|
||||
CommandBuffer& CurrentBuffer() { return GetScheduler().Current(); }
|
||||
void CheckBuffer() const { GetScheduler().CheckActive(); }
|
||||
|
||||
RenderContext& m_renderer;
|
||||
HW::Context m_ctx;
|
||||
|
||||
@@ -1331,26 +1331,13 @@ KYTY_CP_OP_PARSER(CpOpDispatchIndirect) {
|
||||
EXIT_NOT_IMPLEMENTED(cmd_id != 0xc0011600 && cmd_id != 0xc0021600);
|
||||
|
||||
if (cmd_id == 0xc0021600) {
|
||||
struct DispatchIndirectArgs {
|
||||
uint32_t thread_group_x;
|
||||
uint32_t thread_group_y;
|
||||
uint32_t thread_group_z;
|
||||
};
|
||||
|
||||
auto* args = reinterpret_cast<const DispatchIndirectArgs*>(
|
||||
buffer[0] | (static_cast<uint64_t>(buffer[1]) << 32u));
|
||||
uint32_t mode = buffer[2];
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(args == nullptr);
|
||||
cp.DispatchDirect(args->thread_group_x, args->thread_group_y, args->thread_group_z, mode);
|
||||
|
||||
cp.DispatchIndirect(buffer[0] | (static_cast<uint64_t>(buffer[1]) << 32u), buffer[2]);
|
||||
return 3;
|
||||
}
|
||||
|
||||
uint32_t data_offset = buffer[0];
|
||||
uint32_t mode = buffer[1];
|
||||
|
||||
cp.DispatchIndirect(data_offset, mode);
|
||||
const auto base_addr = cp.GetDispatchIndirectArgsBaseAddress();
|
||||
EXIT_NOT_IMPLEMENTED(base_addr == 0);
|
||||
cp.DispatchIndirect(base_addr + buffer[0], buffer[1]);
|
||||
|
||||
return 2;
|
||||
}
|
||||
@@ -2017,6 +2004,14 @@ KYTY_CP_OP_PARSER(CpOpIndirectCxRegs) {
|
||||
auto pfunc = g_hw_ctx_indirect_func[cmd_offset & (Pm4::CX_NUM - 1)];
|
||||
|
||||
if (pfunc == nullptr) {
|
||||
if (raw_cmd_offset == 0x24au && value == 0u) {
|
||||
static std::atomic_flag logged = ATOMIC_FLAG_INIT;
|
||||
if (!logged.test_and_set(std::memory_order_relaxed)) {
|
||||
Log::WriteToConsoleAndLog(
|
||||
"\t diagnostic: ignoring indirect CX {0x24a, 0}; hardware effect unresolved\n");
|
||||
}
|
||||
continue;
|
||||
}
|
||||
EXIT("unknown cx reg at %05" PRIx32 ": 0x%" PRIx32 "\n", num_dw - dw, cmd_offset);
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,10 @@ enum class PrimitiveType : uint32_t {
|
||||
kPolygon = 21,
|
||||
};
|
||||
|
||||
constexpr bool IsRectList(PrimitiveType type) {
|
||||
return type == PrimitiveType::kRectList || type == PrimitiveType::kRectListLegacy;
|
||||
}
|
||||
|
||||
enum class IndexType : uint32_t {
|
||||
kIndex16 = 0,
|
||||
kIndex32 = 1,
|
||||
|
||||
@@ -874,13 +874,11 @@ void CommandProcessor::SetPredication(uint32_t condition, uint32_t op, uint32_t
|
||||
}
|
||||
|
||||
void CommandProcessor::DrawIndex(DrawIndexArgs args) {
|
||||
CheckBuffer();
|
||||
|
||||
args.index_type_and_size = m_index_type_and_size;
|
||||
if (args.instance_count == 0) {
|
||||
args.instance_count = m_num_instances;
|
||||
}
|
||||
if (args.base_vertex != 0 || args.first_instance != 0) {
|
||||
if (GraphicsRunDebugDumpEnabled() && (args.base_vertex != 0 || args.first_instance != 0)) {
|
||||
LOGF("\t draw indexed offsets: base_vertex = %" PRId32 ", first_instance = %" PRIu32 "\n",
|
||||
args.base_vertex, args.first_instance);
|
||||
}
|
||||
@@ -912,17 +910,6 @@ void CommandProcessor::DrawIndirect(uint32_t data_offset, uint32_t draw_initiato
|
||||
if (!indexed) {
|
||||
DrawIndirectArgs args {};
|
||||
std::memcpy(&args, args_addr, sizeof(args));
|
||||
if (args.instance_count != 1u || args.start_vertex_location != 0u ||
|
||||
args.start_instance_location != 0u) {
|
||||
static std::atomic<uint32_t> log_count {0};
|
||||
if (log_count.fetch_add(1) < 64) {
|
||||
LOGF("\t warning: partial DrawIndirect args: vertex_count=%" PRIu32
|
||||
", instance_count=%" PRIu32 ", start_vertex=%" PRIu32
|
||||
", start_instance=%" PRIu32 "\n",
|
||||
args.vertex_count_per_instance, args.instance_count,
|
||||
args.start_vertex_location, args.start_instance_location);
|
||||
}
|
||||
}
|
||||
m_num_instances = args.instance_count;
|
||||
DrawIndexAuto({.vertex_count = args.vertex_count_per_instance,
|
||||
.instance_count = args.instance_count,
|
||||
@@ -934,16 +921,6 @@ void CommandProcessor::DrawIndirect(uint32_t data_offset, uint32_t draw_initiato
|
||||
|
||||
DrawIndexedIndirectArgs args {};
|
||||
std::memcpy(&args, args_addr, sizeof(args));
|
||||
if (args.base_vertex_location != 0u || args.start_instance_location != 0u) {
|
||||
static std::atomic<uint32_t> log_count {0};
|
||||
if (log_count.fetch_add(1) < 64) {
|
||||
LOGF("\t warning: partial DrawIndexIndirect args: index_count=%" PRIu32
|
||||
", instance_count=%" PRIu32 ", start_index=%" PRIu32 ", base_vertex=%" PRIu32
|
||||
", start_instance=%" PRIu32 "\n",
|
||||
args.index_count_per_instance, args.instance_count, args.start_index_location,
|
||||
args.base_vertex_location, args.start_instance_location);
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t index_size = 0;
|
||||
switch (m_index_type_and_size) {
|
||||
@@ -1005,17 +982,6 @@ void CommandProcessor::DrawIndirectMulti(uint32_t data_offset, uint32_t max_coun
|
||||
|
||||
if (!indexed) {
|
||||
auto* args = reinterpret_cast<const DrawIndirectArgs*>(args_addr);
|
||||
if (args->instance_count != 1u || args->start_vertex_location != 0u ||
|
||||
args->start_instance_location != 0u) {
|
||||
static std::atomic<uint32_t> log_count {0};
|
||||
if (log_count.fetch_add(1) < 64) {
|
||||
LOGF("\t warning: partial DrawIndirectMulti args[%u]: vertex_count=%" PRIu32
|
||||
", instance_count=%" PRIu32 ", start_vertex=%" PRIu32
|
||||
", start_instance=%" PRIu32 "\n",
|
||||
i, args->vertex_count_per_instance, args->instance_count,
|
||||
args->start_vertex_location, args->start_instance_location);
|
||||
}
|
||||
}
|
||||
m_num_instances = args->instance_count;
|
||||
DrawIndexAuto({.vertex_count = args->vertex_count_per_instance,
|
||||
.instance_count = args->instance_count,
|
||||
@@ -1026,17 +992,6 @@ void CommandProcessor::DrawIndirectMulti(uint32_t data_offset, uint32_t max_coun
|
||||
}
|
||||
|
||||
auto* args = reinterpret_cast<const DrawIndexedIndirectArgs*>(args_addr);
|
||||
if (args->base_vertex_location != 0u || args->start_instance_location != 0u) {
|
||||
static std::atomic<uint32_t> log_count {0};
|
||||
if (log_count.fetch_add(1) < 64) {
|
||||
LOGF("\t warning: partial DrawIndexIndirectMulti args[%u]: index_count=%" PRIu32
|
||||
", instance_count=%" PRIu32 ", start_index=%" PRIu32 ", base_vertex=%" PRIu32
|
||||
", start_instance=%" PRIu32 "\n",
|
||||
i, args->index_count_per_instance, args->instance_count,
|
||||
args->start_index_location, args->base_vertex_location,
|
||||
args->start_instance_location);
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t index_size = 0;
|
||||
switch (m_index_type_and_size) {
|
||||
@@ -1082,7 +1037,6 @@ void CommandProcessor::DispatchDirect(uint32_t thread_group_x, uint32_t thread_g
|
||||
// uint32_t local_z = 1;
|
||||
|
||||
{
|
||||
CheckBuffer();
|
||||
frame_num = m_renderer.GetGpu().GetFrameNum();
|
||||
if (GraphicsRunDebugDumpEnabled()) {
|
||||
static std::atomic<uint32_t> log_count {0};
|
||||
@@ -1133,24 +1087,18 @@ void CommandProcessor::DispatchDirect(uint32_t thread_group_x, uint32_t thread_g
|
||||
}*/
|
||||
}
|
||||
|
||||
void CommandProcessor::DispatchIndirect(uint32_t data_offset, uint32_t mode) {
|
||||
struct DispatchIndirectArgs {
|
||||
uint32_t thread_group_x;
|
||||
uint32_t thread_group_y;
|
||||
uint32_t thread_group_z;
|
||||
};
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(m_dispatch_indirect_args_base_addr == 0);
|
||||
|
||||
const auto args_addr = m_dispatch_indirect_args_base_addr + data_offset;
|
||||
auto* args = reinterpret_cast<const DispatchIndirectArgs*>(args_addr);
|
||||
|
||||
DispatchDirect(args->thread_group_x, args->thread_group_y, args->thread_group_z, mode);
|
||||
void CommandProcessor::DispatchIndirect(uint64_t args_addr, uint32_t mode) {
|
||||
EXIT_NOT_IMPLEMENTED(args_addr == 0 || (args_addr & 3u) != 0);
|
||||
if ((mode & Pm4::COMPUTE_DISPATCH_INITIATOR_USE_THREAD_DIMENSIONS) != 0) {
|
||||
const auto* args = reinterpret_cast<const vk::DispatchIndirectCommand*>(args_addr);
|
||||
DispatchDirect(args->x, args->y, args->z, mode);
|
||||
return;
|
||||
}
|
||||
m_sh_ctx.SetCsWaveSize(Pm4::ComputeWaveSize(mode));
|
||||
m_renderer.GetRenderExecutor().DispatchIndirect(m_submit_id, CurrentBuffer(), args_addr, mode);
|
||||
}
|
||||
|
||||
void CommandProcessor::DrawIndexAuto(DrawAutoArgs args) {
|
||||
CheckBuffer();
|
||||
|
||||
if (args.instance_count == 0) {
|
||||
args.instance_count = m_num_instances;
|
||||
}
|
||||
@@ -1172,7 +1120,7 @@ void CommandProcessor::WriteAtEndOfPipe(uint32_t cache_policy, uint32_t event_wr
|
||||
uint32_t interrupt_context_id) {
|
||||
static_assert(sizeof(T) == sizeof(uint32_t) || sizeof(T) == sizeof(uint64_t));
|
||||
|
||||
CheckBuffer();
|
||||
auto& command = CurrentBuffer();
|
||||
|
||||
if (GraphicsRunDebugDumpEnabled()) {
|
||||
const auto bits = static_cast<unsigned>(sizeof(T) * 8u);
|
||||
@@ -1203,7 +1151,7 @@ void CommandProcessor::WriteAtEndOfPipe(uint32_t cache_policy, uint32_t event_wr
|
||||
case 0x03: with_interrupt = false; break;
|
||||
case 0x01:
|
||||
if (!IsAsyncComputeQueue()) {
|
||||
Sync::TriggerEopEventAtEndOfPipe(CurrentBuffer(), m_interrupt_event_id,
|
||||
Sync::TriggerEopEventAtEndOfPipe(command, m_interrupt_event_id,
|
||||
interrupt_context_id);
|
||||
return;
|
||||
}
|
||||
@@ -1220,17 +1168,17 @@ void CommandProcessor::WriteAtEndOfPipe(uint32_t cache_policy, uint32_t event_wr
|
||||
|
||||
if (with_interrupt) {
|
||||
if (with_writeback) {
|
||||
Sync::WriteAtEndOfPipeWithInterruptWriteBack32(m_submit_id, CurrentBuffer(), dst,
|
||||
Sync::WriteAtEndOfPipeWithInterruptWriteBack32(m_submit_id, command, dst,
|
||||
data, m_interrupt_event_id,
|
||||
interrupt_context_id);
|
||||
} else {
|
||||
Sync::WriteAtEndOfPipeWithInterrupt32(m_submit_id, CurrentBuffer(), dst, data,
|
||||
Sync::WriteAtEndOfPipeWithInterrupt32(m_submit_id, command, dst, data,
|
||||
m_interrupt_event_id, interrupt_context_id);
|
||||
}
|
||||
} else if (with_writeback) {
|
||||
Sync::WriteAtEndOfPipeWithWriteBack32(m_submit_id, CurrentBuffer(), dst, data);
|
||||
Sync::WriteAtEndOfPipeWithWriteBack32(m_submit_id, command, dst, data);
|
||||
} else {
|
||||
Sync::WriteAtEndOfPipe32(m_submit_id, CurrentBuffer(), dst, data);
|
||||
Sync::WriteAtEndOfPipe32(m_submit_id, command, dst, data);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1242,7 +1190,7 @@ void CommandProcessor::WriteAtEndOfPipe(uint32_t cache_policy, uint32_t event_wr
|
||||
SynchronizeGpu();
|
||||
Sync::ReadGds(*m_renderer.GetBufferCache().GetGdsBuffer(), dst, value & 0xffffu,
|
||||
value >> 16u);
|
||||
Sync::WriteAtEndOfPipeGds32(m_submit_id, CurrentBuffer(), dst, value & 0xffffu,
|
||||
Sync::WriteAtEndOfPipeGds32(m_submit_id, command, dst, value & 0xffffu,
|
||||
value >> 16u);
|
||||
if (with_interrupt) {
|
||||
m_renderer.TriggerInterrupt(m_interrupt_event_id, interrupt_context_id);
|
||||
@@ -1271,18 +1219,18 @@ void CommandProcessor::WriteAtEndOfPipe(uint32_t cache_policy, uint32_t event_wr
|
||||
if (with_interrupt) {
|
||||
if (with_writeback) {
|
||||
Sync::WriteAtEndOfPipeWithInterruptWriteBack64(
|
||||
m_submit_id, CurrentBuffer(), dst, value, m_interrupt_event_id,
|
||||
m_submit_id, command, dst, value, m_interrupt_event_id,
|
||||
interrupt_context_id);
|
||||
} else {
|
||||
Sync::WriteAtEndOfPipeWithInterrupt64(m_submit_id, CurrentBuffer(), dst,
|
||||
Sync::WriteAtEndOfPipeWithInterrupt64(m_submit_id, command, dst,
|
||||
value, m_interrupt_event_id,
|
||||
interrupt_context_id);
|
||||
}
|
||||
} else if (with_writeback) {
|
||||
Sync::WriteAtEndOfPipeWithWriteBack64(m_submit_id, CurrentBuffer(), dst,
|
||||
Sync::WriteAtEndOfPipeWithWriteBack64(m_submit_id, command, dst,
|
||||
value);
|
||||
} else {
|
||||
Sync::WriteAtEndOfPipe64(m_submit_id, CurrentBuffer(), dst, value);
|
||||
Sync::WriteAtEndOfPipe64(m_submit_id, command, dst, value);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1363,10 +1311,10 @@ void CommandProcessor::WriteAtEndOfPipe(uint32_t cache_policy, uint32_t event_wr
|
||||
(eop_event_type == 0x28 && event_index == 0x00)) {
|
||||
if (with_interrupt) {
|
||||
Sync::WriteAtEndOfPipeWithInterrupt64(
|
||||
m_submit_id, CurrentBuffer(), dst, clock, m_interrupt_event_id,
|
||||
m_submit_id, command, dst, clock, m_interrupt_event_id,
|
||||
interrupt_context_id);
|
||||
} else {
|
||||
Sync::WriteAtEndOfPipeClockCounter(m_submit_id, CurrentBuffer(),
|
||||
Sync::WriteAtEndOfPipeClockCounter(m_submit_id, command,
|
||||
dst, clock);
|
||||
}
|
||||
return;
|
||||
@@ -1378,11 +1326,11 @@ void CommandProcessor::WriteAtEndOfPipe(uint32_t cache_policy, uint32_t event_wr
|
||||
(eop_event_type == 0x28 && event_index == 0x00)) {
|
||||
if (with_interrupt) {
|
||||
Sync::WriteAtEndOfPipeWithInterruptWriteBack64(
|
||||
m_submit_id, CurrentBuffer(), dst, clock, m_interrupt_event_id,
|
||||
m_submit_id, command, dst, clock, m_interrupt_event_id,
|
||||
interrupt_context_id);
|
||||
} else {
|
||||
Sync::WriteAtEndOfPipeClockCounterWithWriteBack(
|
||||
m_submit_id, CurrentBuffer(), dst, clock);
|
||||
m_submit_id, command, dst, clock);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -1420,8 +1368,6 @@ void CommandProcessor::WriteAtEndOfPipe64(uint32_t cache_policy, uint32_t event_
|
||||
}
|
||||
|
||||
void CommandProcessor::EmitGlobalBarrier() {
|
||||
CheckBuffer();
|
||||
|
||||
Common::LockGuard lock(m_renderer.GetMutex());
|
||||
|
||||
vk::MemoryBarrier2 barrier {};
|
||||
@@ -1438,8 +1384,6 @@ void CommandProcessor::EmitGlobalBarrier() {
|
||||
}
|
||||
|
||||
void CommandProcessor::TriggerEopEventAtEndOfPipe(uint32_t interrupt_context_id) {
|
||||
CheckBuffer();
|
||||
|
||||
Sync::TriggerEopEventAtEndOfPipe(CurrentBuffer(), m_interrupt_event_id, interrupt_context_id);
|
||||
}
|
||||
|
||||
@@ -1523,8 +1467,6 @@ void CommandProcessor::TriggerEvent(uint32_t event_type, uint32_t event_index,
|
||||
}
|
||||
|
||||
void CommandProcessor::Flip() {
|
||||
CheckBuffer();
|
||||
|
||||
if (GraphicsRunDebugDumpEnabled()) {
|
||||
LOGF("CommandProcessor::Flip()\n");
|
||||
}
|
||||
@@ -1538,7 +1480,7 @@ void CommandProcessor::Flip() {
|
||||
}
|
||||
|
||||
void CommandProcessor::Flip(void* dst_gpu_addr, uint32_t value) {
|
||||
CheckBuffer();
|
||||
auto& command = CurrentBuffer();
|
||||
|
||||
if (GraphicsRunDebugDumpEnabled()) {
|
||||
LOGF("CommandProcessor::Flip()\n"
|
||||
@@ -1548,7 +1490,6 @@ void CommandProcessor::Flip(void* dst_gpu_addr, uint32_t value) {
|
||||
}
|
||||
|
||||
std::memcpy(dst_gpu_addr, &value, sizeof(value));
|
||||
auto& command = CurrentBuffer();
|
||||
auto request = Sync::PrepareVideoOutFlip(command, m_flip.handle, m_flip.index, m_flip.flip_mode,
|
||||
m_flip.flip_arg);
|
||||
Sync::WriteAtEndOfPipeWithFlip32(m_submit_id, command, static_cast<uint32_t*>(dst_gpu_addr),
|
||||
@@ -1559,7 +1500,7 @@ void CommandProcessor::Flip(void* dst_gpu_addr, uint32_t value) {
|
||||
|
||||
void CommandProcessor::FlipWithInterrupt(uint32_t eop_event_type, uint32_t cache_action,
|
||||
void* dst_gpu_addr, uint32_t value) {
|
||||
CheckBuffer();
|
||||
auto& command = CurrentBuffer();
|
||||
|
||||
if (GraphicsRunDebugDumpEnabled()) {
|
||||
LOGF("CommandProcessor::FlipWithInterrupt()\n"
|
||||
@@ -1574,7 +1515,6 @@ void CommandProcessor::FlipWithInterrupt(uint32_t eop_event_type, uint32_t cache
|
||||
EXIT("unknown event type\n");
|
||||
}
|
||||
std::memcpy(dst_gpu_addr, &value, sizeof(value));
|
||||
auto& command = CurrentBuffer();
|
||||
auto request = Sync::PrepareVideoOutFlip(command, m_flip.handle, m_flip.index, m_flip.flip_mode,
|
||||
m_flip.flip_arg);
|
||||
Sync::WriteAtEndOfPipeWithInterruptWriteBackFlip32(
|
||||
@@ -1584,7 +1524,7 @@ void CommandProcessor::FlipWithInterrupt(uint32_t eop_event_type, uint32_t cache
|
||||
}
|
||||
|
||||
void CommandProcessor::PrepareCpuFlip(uint64_t request_id) {
|
||||
CheckBuffer();
|
||||
auto& command = CurrentBuffer();
|
||||
if (g_current_processor != nullptr) {
|
||||
EXIT("invalid graphics-thread CPU flip preparation\n");
|
||||
}
|
||||
@@ -1594,7 +1534,7 @@ void CommandProcessor::PrepareCpuFlip(uint64_t request_id) {
|
||||
};
|
||||
ProcessorScope processor_scope(*this);
|
||||
|
||||
m_renderer.GetVideoOut().PrepareFlip(request_id, CurrentBuffer());
|
||||
m_renderer.GetVideoOut().PrepareFlip(request_id, command);
|
||||
GetScheduler().Flush();
|
||||
m_renderer.GetVideoOut().CompleteFlip(request_id);
|
||||
}
|
||||
|
||||
@@ -910,6 +910,7 @@ constexpr uint32_t COMPUTE_PGM_RSRC1_WGP_MODE_MASK = 0x1;
|
||||
constexpr uint32_t COMPUTE_PGM_RSRC1_FWD_PROGRESS_SHIFT = 31;
|
||||
constexpr uint32_t COMPUTE_PGM_RSRC1_FWD_PROGRESS_MASK = 0x1;
|
||||
|
||||
constexpr uint32_t COMPUTE_DISPATCH_INITIATOR_USE_THREAD_DIMENSIONS = 1u << 5u;
|
||||
constexpr uint32_t COMPUTE_DISPATCH_INITIATOR_CS_W32_EN_SHIFT = 15;
|
||||
constexpr uint32_t COMPUTE_DISPATCH_INITIATOR_CS_W32_EN_MASK = 0x1;
|
||||
|
||||
|
||||
+2
-2
@@ -129,7 +129,7 @@ bool BufferCache::DownloadBufferMemory(Buffer& buffer, uint64_t vaddr, uint64_t
|
||||
|
||||
const auto [mapped, offset] = m_download_buffer.Map(total_size, 64);
|
||||
if (mapped == nullptr) {
|
||||
EXIT("BufferCache: download exceeds 32 MiB staging buffer capacity\n");
|
||||
EXIT("BufferCache: download exceeds 64 MiB staging buffer capacity\n");
|
||||
}
|
||||
m_download_buffer.Commit();
|
||||
for (auto& copy: copies) {
|
||||
@@ -183,7 +183,7 @@ BufferCache::BufferCache(GraphicContext& graphics, CommandScheduler& scheduler,
|
||||
m_memory_tracker(page_manager),
|
||||
m_staging_buffer(graphics, scheduler, MemoryUsage::Upload, 512 * MiB),
|
||||
m_stream_buffer(graphics, scheduler, MemoryUsage::Stream, 64 * MiB),
|
||||
m_download_buffer(graphics, scheduler, MemoryUsage::Download, 32 * MiB),
|
||||
m_download_buffer(graphics, scheduler, MemoryUsage::Download, 64 * MiB),
|
||||
m_device_buffer(graphics, scheduler, MemoryUsage::DeviceLocal, 128 * MiB),
|
||||
m_texture_cache(texture_cache) {
|
||||
std::memset(m_gds_buffer.Mapped().data(), 0, static_cast<size_t>(m_gds_buffer.Size()));
|
||||
|
||||
+68
-20
@@ -138,14 +138,15 @@ void NameImageBinding(GraphicContext& graphics, Image& image, vk::ImageView view
|
||||
}
|
||||
|
||||
[[nodiscard]] std::vector<vk::BufferImageCopy> BuildDepthCopies(const ImageInfo& info,
|
||||
uint64_t slice_stride) {
|
||||
uint64_t slice_stride,
|
||||
vk::ImageAspectFlags aspect) {
|
||||
std::vector<vk::BufferImageCopy> copies(info.resources.layers);
|
||||
for (uint32_t layer = 0; layer < info.resources.layers; ++layer) {
|
||||
auto& copy = copies[layer];
|
||||
copy.bufferOffset = slice_stride * layer;
|
||||
copy.bufferRowLength = info.pitch;
|
||||
copy.bufferImageHeight = info.extent.height;
|
||||
copy.imageSubresource = {vk::ImageAspectFlagBits::eDepth, 0, layer, 1};
|
||||
copy.imageSubresource = {aspect, 0, layer, 1};
|
||||
copy.imageExtent = {info.extent.width, info.extent.height, 1};
|
||||
}
|
||||
return copies;
|
||||
@@ -1031,16 +1032,17 @@ TextureCache::ImageDownload TextureCache::BuildDownload(const Image& image) cons
|
||||
}
|
||||
|
||||
void TextureCache::UploadImage(Image& image, Buffer& source, uint64_t source_offset) {
|
||||
const auto& info = image.info;
|
||||
const auto binding = UploadBinding(image);
|
||||
auto& destination = image.depth_id ? m_slot_images[image.depth_id] : image;
|
||||
const auto binding = image.depth_id ? BindingType::DepthTarget : UploadBinding(image);
|
||||
const auto upload = [&](std::vector<vk::BufferImageCopy>& copies, TileManager::Result linear) {
|
||||
for (auto& copy: copies) {
|
||||
copy.bufferOffset += linear.offset;
|
||||
}
|
||||
image.Upload(copies, linear.buffer, linear.offset, linear.size);
|
||||
destination.Upload(copies, linear.buffer, linear.offset, linear.size);
|
||||
};
|
||||
|
||||
if (binding != BindingType::DepthTarget) {
|
||||
const auto& info = image.info;
|
||||
auto transfer = BuildTextureTransfer(image, binding, TransferDirection::Upload);
|
||||
if (!transfer.valid) {
|
||||
EXIT("TextureCache: invalid texture upload: binding=%u addr=0x%016" PRIx64
|
||||
@@ -1064,21 +1066,32 @@ void TextureCache::UploadImage(Image& image, Buffer& source, uint64_t source_off
|
||||
return;
|
||||
}
|
||||
|
||||
if (info.samples != 1 || image.backing.samples != 1 ||
|
||||
// The stencil plane has its own row pitch and shares the native image
|
||||
// with the depth plane.
|
||||
auto info = destination.info;
|
||||
if (image.depth_id) {
|
||||
info.data = image.info.data;
|
||||
info.guest_format = Prospero::BufferFormat::k8UInt;
|
||||
info.bytes_per_block = 1;
|
||||
if (info.IsTiled()) info.pitch = TileGetDepthPitch(info.extent.width, 1, 0);
|
||||
}
|
||||
if (info.samples != 1 || destination.backing.samples != 1 ||
|
||||
info.resources.layers == 0 || info.data.size % info.resources.layers != 0 ||
|
||||
Prospero::NumBytesPerElement(info.guest_format) != info.bytes_per_block) {
|
||||
EXIT("TextureCache: invalid depth upload\n");
|
||||
}
|
||||
const auto layers = info.resources.layers;
|
||||
const auto full_slice_size = info.data.size / layers;
|
||||
auto copies = BuildDepthCopies(info, full_slice_size);
|
||||
auto copies = BuildDepthCopies(info, full_slice_size, image.depth_id
|
||||
? vk::ImageAspectFlagBits::eStencil
|
||||
: vk::ImageAspectFlagBits::eDepth);
|
||||
TileManager::Result linear {source.Handle(), source_offset, source.Size() - source_offset};
|
||||
if (info.IsTiled()) {
|
||||
const auto tiles = BuildDepthTiles(info);
|
||||
linear =
|
||||
m_tiler.Detile(source.Handle(), source_offset, info.data.size, info.data.size, tiles);
|
||||
}
|
||||
const auto transfer_bytes = DepthAspectTransferBytes(info.pixel_format);
|
||||
const auto transfer_bytes = image.depth_id ? 1u : DepthAspectTransferBytes(info.pixel_format);
|
||||
if (transfer_bytes != info.bytes_per_block) {
|
||||
const uint64_t texels_per_slice = static_cast<uint64_t>(info.pitch) * info.extent.height;
|
||||
EXIT_NOT_IMPLEMENTED(info.bytes_per_block != sizeof(uint16_t) ||
|
||||
@@ -1202,8 +1215,13 @@ void TextureCache::MaterializeDccClear(ImageId id, const ImageDesc& desc,
|
||||
}
|
||||
|
||||
void TextureCache::RefreshImage(ImageId id) {
|
||||
TrackImage(id);
|
||||
auto& image = m_slot_images[id];
|
||||
if (image.depth_id &&
|
||||
(m_slot_images[image.depth_id].info.metadata.stencil_compressed ||
|
||||
m_slot_images[image.depth_id].info.samples != 1)) {
|
||||
return;
|
||||
}
|
||||
TrackImage(id);
|
||||
if (image.IsMaybeCpuDirty()) {
|
||||
const auto hash = image.HashGuestEdges();
|
||||
if (image.NeedsMaybeCpuHash()) {
|
||||
@@ -1225,7 +1243,7 @@ void TextureCache::RefreshImage(ImageId id) {
|
||||
InitializeImage(id);
|
||||
}
|
||||
|
||||
void TextureCache::AssociateStencil(ImageId depth_id, GuestRange stencil) {
|
||||
ImageId TextureCache::AssociateStencil(ImageId depth_id, GuestRange stencil) {
|
||||
if (!stencil.Valid()) {
|
||||
EXIT("TextureCache: invalid stencil association range\n");
|
||||
}
|
||||
@@ -1251,6 +1269,7 @@ void TextureCache::AssociateStencil(ImageId depth_id, GuestRange stencil) {
|
||||
auto& record = m_slot_images[association];
|
||||
TouchImage(record);
|
||||
record.depth_id = depth_id;
|
||||
return association;
|
||||
}
|
||||
|
||||
ImageId TextureCache::FindImage(ImageDesc& desc, bool exact_format) {
|
||||
@@ -1396,6 +1415,19 @@ vk::ImageView TextureCache::FindTexture(ImageId id, const ImageDesc& desc) {
|
||||
}
|
||||
if (!image.info.data.Empty()) {
|
||||
RefreshImage(id);
|
||||
if (image.info.HasStencil() &&
|
||||
desc.info.data.address >= image.info.stencil.address &&
|
||||
desc.info.data.End() <= image.info.stencil.End()) {
|
||||
for (const auto stencil_id:
|
||||
FindImagesInRegion(image.info.stencil.address, image.info.stencil.size, false)) {
|
||||
const auto* stencil = m_slot_images.try_get(stencil_id);
|
||||
if (stencil != nullptr && stencil->depth_id == id &&
|
||||
stencil->info.data == image.info.stencil) {
|
||||
RefreshImage(stencil_id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
switch (desc.type) {
|
||||
case BindingType::Texture: break;
|
||||
@@ -1444,16 +1476,17 @@ vk::ImageView TextureCache::FindDepthTarget(ImageId id, const ImageDesc& desc) {
|
||||
TouchImage(image);
|
||||
image.MarkGpuModified();
|
||||
image.usage.depth_target = true;
|
||||
RefreshImage(id);
|
||||
image.info.stencil = desc.info.stencil;
|
||||
image.info.metadata = desc.info.metadata;
|
||||
if (desc.info.HasMetadata()) {
|
||||
image.info.metadata = desc.info.metadata;
|
||||
m_surface_metas.emplace(desc.info.metadata.range.address,
|
||||
MetaDataInfo {.type = MetaDataInfo::Type::HTile,
|
||||
.clear_mask = image.info.htile_clear_mask});
|
||||
}
|
||||
RefreshImage(id);
|
||||
CommitGpuWrite(image);
|
||||
if (desc.info.HasStencil()) {
|
||||
AssociateStencil(id, desc.info.stencil);
|
||||
RefreshImage(AssociateStencil(id, desc.info.stencil));
|
||||
}
|
||||
const auto view = image.FindView(desc.view_info);
|
||||
NameImageBinding(m_graphics, image, view, desc.type, desc.view_info);
|
||||
@@ -1468,11 +1501,16 @@ void TextureCache::MarkGpuWritten(ImageId id) {
|
||||
}
|
||||
TrackImage(id);
|
||||
CommitGpuWrite(image);
|
||||
if (image.info.HasStencil()) {
|
||||
const auto stencil_id = AssociateStencil(id, image.info.stencil);
|
||||
TrackImage(stencil_id);
|
||||
CommitGpuWrite(m_slot_images[stencil_id]);
|
||||
}
|
||||
}
|
||||
|
||||
void TextureCache::CommitGpuWrite(Image& image) {
|
||||
if (image.depth_id || image.backing.image == nullptr) {
|
||||
EXIT("TextureCache: stencil association cannot own image contents\n");
|
||||
if (!image.depth_id && image.backing.image == nullptr) {
|
||||
EXIT("TextureCache: GPU writes require a native image or stencil association\n");
|
||||
}
|
||||
image.ClearBufferModified();
|
||||
if (image.IsCpuDirty()) {
|
||||
@@ -1556,9 +1594,10 @@ void TextureCache::ClearImage(CommandBuffer& command, ImageId id, vk::Format for
|
||||
range.layerCount == 0 || range.baseArrayLayer >= layers ||
|
||||
range.layerCount > layers - range.baseArrayLayer ||
|
||||
(range.aspectMask & aspects) != range.aspectMask);
|
||||
const bool full_image = range.aspectMask == aspects && range.baseMipLevel == 0 &&
|
||||
range.levelCount == image.info.resources.levels &&
|
||||
range.baseArrayLayer == 0 && range.layerCount == layers;
|
||||
const bool full_subresources = range.baseMipLevel == 0 &&
|
||||
range.levelCount == image.info.resources.levels &&
|
||||
range.baseArrayLayer == 0 && range.layerCount == layers;
|
||||
const bool full_image = range.aspectMask == aspects && full_subresources;
|
||||
TrackImage(id);
|
||||
if (!full_image && (image.IsBufferModified() || image.IsCpuDirty())) {
|
||||
InitializeImage(id);
|
||||
@@ -1566,6 +1605,15 @@ void TextureCache::ClearImage(CommandBuffer& command, ImageId id, vk::Format for
|
||||
EXIT("TextureCache: image clear retained guest ownership\n");
|
||||
}
|
||||
}
|
||||
if (image.info.HasStencil() && (range.aspectMask & vk::ImageAspectFlagBits::eStencil)) {
|
||||
const auto stencil_id = AssociateStencil(id, image.info.stencil);
|
||||
if (!full_subresources) {
|
||||
RefreshImage(stencil_id);
|
||||
} else {
|
||||
TrackImage(stencil_id);
|
||||
CommitGpuWrite(m_slot_images[stencil_id]);
|
||||
}
|
||||
}
|
||||
command.EndRendering();
|
||||
// Transfer clears use the backing format; aliased clears must encode through their view.
|
||||
if (format != image.backing.format || (image.info.IsVolume() && !full_image)) {
|
||||
@@ -1638,7 +1686,7 @@ void TextureCache::DownloadDepth(Image& image, Buffer& destination, uint64_t des
|
||||
EXIT_NOT_IMPLEMENTED(transfer_slice > UINT64_MAX / layers);
|
||||
const uint64_t transfer_size = transfer_slice * layers;
|
||||
EXIT_NOT_IMPLEMENTED(guest_slice > full_slice_size);
|
||||
auto copies = BuildDepthCopies(info, full_slice_size);
|
||||
auto copies = BuildDepthCopies(info, full_slice_size, vk::ImageAspectFlagBits::eDepth);
|
||||
if (transfer_bytes == info.bytes_per_block) {
|
||||
if (!info.IsTiled()) {
|
||||
for (auto& copy: copies) {
|
||||
@@ -1831,7 +1879,7 @@ void TextureCache::InvalidateMemoryFromGPU(uint64_t address, uint64_t size) {
|
||||
std::scoped_lock lock {m_lock};
|
||||
for (const auto id: FindImagesInRegion(address, size, true)) {
|
||||
auto& image = m_slot_images[id];
|
||||
if (image.depth_id || !image.Overlaps(address, size)) {
|
||||
if (!image.Overlaps(address, size)) {
|
||||
continue;
|
||||
}
|
||||
if (image.IsGpuModified()) {
|
||||
|
||||
+1
-1
@@ -157,7 +157,7 @@ private:
|
||||
void RefreshCopySource(ImageId id);
|
||||
[[nodiscard]] bool CopyD16(Image& destination, Image& source);
|
||||
void CopyImage(ImageId destination, ImageId source);
|
||||
void AssociateStencil(ImageId depth, GuestRange stencil);
|
||||
[[nodiscard]] ImageId AssociateStencil(ImageId depth, GuestRange stencil);
|
||||
void CopyImageMip(ImageId destination, ImageId source, uint32_t mip, uint32_t layer);
|
||||
void ValidateImageDesc(const ImageDesc& desc) const;
|
||||
|
||||
|
||||
@@ -209,17 +209,16 @@ static void RtCheck(const HW::RenderTarget& rt) {
|
||||
}
|
||||
if (rt.info.fmask_compression_enable) {
|
||||
EXIT_NOT_IMPLEMENTED(rt.attrib.num_samples == 0 && rt.attrib.num_fragments == 0);
|
||||
// Native MSAA stores expanded samples, independent of FMASK metadata compression.
|
||||
static bool logged = false;
|
||||
if (!logged) {
|
||||
LOGF("RenderTarget: using native Vulkan MSAA without guest FMASK metadata, "
|
||||
LOGF("RenderTarget: using expanded native Vulkan MSAA samples, "
|
||||
"fmask=0x%016" PRIx64 "\n",
|
||||
rt.fmask.addr);
|
||||
logged = true;
|
||||
}
|
||||
}
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(rt.info.fmask_data_compression_disable != false);
|
||||
|
||||
if (rt.info.cmask_fast_clear_enable || rt.info.dcc_compression_enable) {
|
||||
static bool logged = false;
|
||||
if (!logged) {
|
||||
|
||||
@@ -184,17 +184,18 @@ bool IsSupportedDepthTextureEncoding(const ShaderTextureResource& descriptor, bo
|
||||
}
|
||||
const bool full = common && descriptor.fields[4] == field4_expected &&
|
||||
descriptor.fields[5] == field5_expected;
|
||||
if (!full || (descriptor.fields[6] == 0 && descriptor.fields[7] != 0) ||
|
||||
if (!full ||
|
||||
(descriptor.MsaaDepth() && !IsMultisampledTexture(descriptor.Type()))) {
|
||||
return false;
|
||||
}
|
||||
if (descriptor.fields[6] == 0) {
|
||||
const auto metadata_control = descriptor.fields[6] & 0x00ffffffu;
|
||||
if (metadata_control == 0) {
|
||||
return true;
|
||||
}
|
||||
constexpr uint32_t htile_control = 0x00280000u;
|
||||
const uint32_t expected_control = htile_control | (descriptor.MsaaDepth() ? (1u << 10u) : 0u);
|
||||
const auto metadata_addr = descriptor.MetaAddr() << 8u;
|
||||
return (descriptor.fields[6] & 0x00ffffffu) == expected_control && metadata_addr != 0 &&
|
||||
return metadata_control == expected_control && metadata_addr != 0 &&
|
||||
metadata_addr < TRACKER_ADDRESS_SIZE && (metadata_addr & 0x7fffu) == 0 &&
|
||||
descriptor.TileMode() == Prospero::TileMode::kDepth;
|
||||
}
|
||||
@@ -252,10 +253,10 @@ static bool IsSupportedStorageTextureDescriptor(const ShaderRecompiler::IR::Imag
|
||||
(is_color_2d_array && descriptor.BaseArray5() <= descriptor.Depth());
|
||||
const bool is_2d =
|
||||
resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2D && valid_2d_slice;
|
||||
// Storage cube coordinates address individual faces, including partial cube views.
|
||||
const bool is_cube = resource.cube && descriptor.Type() == Prospero::ImageType::kCube &&
|
||||
descriptor.Width5() == descriptor.Height5() &&
|
||||
descriptor.BaseArray5() <= descriptor.Depth() &&
|
||||
(descriptor.Depth() - descriptor.BaseArray5() + 1u) % 6u == 0;
|
||||
descriptor.BaseArray5() <= descriptor.Depth();
|
||||
const bool is_2d_array =
|
||||
resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2DArray &&
|
||||
((!resource.cube && is_color_2d_array && descriptor.BaseArray5() <= descriptor.Depth()) ||
|
||||
@@ -550,8 +551,14 @@ TextureBinding RenderExecutor::ResolveTexture(const ShaderRecompiler::IR::ImageR
|
||||
const bool multisampled = IsMultisampledTexture(type);
|
||||
const auto max_mip = resource.r128 ? last_level : descriptor.MaxMip();
|
||||
const auto physical_levels = multisampled ? 1u : static_cast<uint32_t>(max_mip) + 1u;
|
||||
// IMAGE_STORE addresses BASE_LEVEL; only IMAGE_STORE_MIP selects other view mips.
|
||||
const bool single_storage_mip =
|
||||
storage && resource.mip_mode != ShaderRecompiler::IR::ImageMipMode::DynamicStorage;
|
||||
const auto view_levels = multisampled || single_storage_mip
|
||||
? 1u
|
||||
: static_cast<uint32_t>(last_level - base_level) + 1u;
|
||||
const auto levels =
|
||||
multisampled ? 1u : std::max(physical_levels, static_cast<uint32_t>(last_level) + 1u);
|
||||
multisampled ? 1u : std::max(physical_levels, base_level + view_levels);
|
||||
const auto tile = descriptor.TileMode();
|
||||
const bool depth_tile = tile == Prospero::TileMode::kDepth;
|
||||
const bool msaa_tile = depth_tile || tile == Prospero::TileMode::kRenderTarget;
|
||||
@@ -574,8 +581,6 @@ TextureBinding RenderExecutor::ResolveTexture(const ShaderRecompiler::IR::ImageR
|
||||
descriptor.fields[6], descriptor.fields[7]);
|
||||
}
|
||||
const auto samples = multisampled ? 1u << last_level : 1u;
|
||||
const auto view_levels =
|
||||
multisampled ? 1u : static_cast<uint32_t>(last_level - base_level) + 1u;
|
||||
const auto depth = static_cast<uint32_t>(descriptor.Depth()) + 1u;
|
||||
const auto format = descriptor.Format();
|
||||
const auto surface_format = TextureGetSurfaceFormatInfo(format);
|
||||
@@ -748,7 +753,7 @@ PreparedBindings RenderExecutor::PrepareBindings(const ShaderStageRuntime& runti
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
EXIT_IF(!runtime);
|
||||
const auto& program = *runtime.program;
|
||||
const auto& snapshot = runtime.resources;
|
||||
const auto& snapshot = *runtime.resources;
|
||||
PreparedBindings prepared;
|
||||
prepared.runtime = &runtime;
|
||||
prepared.images.reserve(program.info.images.size());
|
||||
@@ -777,7 +782,7 @@ void RenderExecutor::FindBuffers(PreparedBindings& prepared) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
EXIT_IF(prepared.runtime == nullptr || !*prepared.runtime);
|
||||
const auto& program = *prepared.runtime->program;
|
||||
const auto& snapshot = prepared.runtime->resources;
|
||||
const auto& snapshot = *prepared.runtime->resources;
|
||||
auto& cache = m_context.GetBufferCache();
|
||||
|
||||
prepared.buffer_sources.clear();
|
||||
@@ -799,7 +804,7 @@ void RenderExecutor::RebindBuffers(PreparedBindings& prepared) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
EXIT_IF(prepared.runtime == nullptr || !*prepared.runtime);
|
||||
const auto& program = *prepared.runtime->program;
|
||||
const auto& snapshot = prepared.runtime->resources;
|
||||
const auto& snapshot = *prepared.runtime->resources;
|
||||
const auto& layout = program.bindings;
|
||||
EXIT_IF(prepared.buffer_sources.size() != program.info.buffers.size());
|
||||
|
||||
@@ -834,7 +839,7 @@ void RenderExecutor::RebindImages(PreparedBindings& prepared) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
EXIT_IF(prepared.runtime == nullptr || !*prepared.runtime);
|
||||
const auto& program = *prepared.runtime->program;
|
||||
const auto& snapshot = prepared.runtime->resources;
|
||||
const auto& snapshot = *prepared.runtime->resources;
|
||||
auto& images = prepared.images;
|
||||
EXIT_IF(images.size() != program.info.images.size());
|
||||
auto& texture_cache = m_context.GetTextureCache();
|
||||
@@ -866,11 +871,7 @@ void RenderExecutor::RebindImages(PreparedBindings& prepared) {
|
||||
}
|
||||
binding.image_view = binding.mip_views.front();
|
||||
} else {
|
||||
auto desc = binding.desc;
|
||||
if (desc.type == TextureCache::BindingType::Storage) {
|
||||
desc.view_info.level_count = 1;
|
||||
}
|
||||
binding.image_view = texture_cache.FindTexture(binding.image_id, desc);
|
||||
binding.image_view = texture_cache.FindTexture(binding.image_id, binding.desc);
|
||||
}
|
||||
auto& image = texture_cache.GetImage(binding.image_id);
|
||||
const bool storage = binding.desc.type == TextureCache::BindingType::Storage;
|
||||
|
||||
@@ -92,9 +92,9 @@ void PipelineCacheLog(fmt::format_string<Args...> format, Args&&... args) {
|
||||
Log::WriteToConsoleAndLog(message);
|
||||
}
|
||||
|
||||
bool ReadShaderGuestMemory(void*, uint64_t address, uint32_t* value) {
|
||||
return value != nullptr &&
|
||||
Libs::LibKernel::Memory::TryReadGpuCleanBacking(address, value, sizeof(*value));
|
||||
bool ReadShaderGuestMemory(void*, uint64_t address, std::span<uint32_t> values) {
|
||||
return !values.empty() &&
|
||||
Libs::LibKernel::Memory::TryReadGpuCleanBacking(address, values.data(), values.size_bytes());
|
||||
}
|
||||
|
||||
void DumpShaderSpirv(const char* stage_name, uint64_t shader_hash,
|
||||
@@ -200,8 +200,10 @@ struct PipelineCache::ProgramCache {
|
||||
permutations.reserve(8);
|
||||
}
|
||||
|
||||
ShaderRecompiler::IR::ResourcePlan resource_plan;
|
||||
std::vector<Permutation> permutations;
|
||||
ShaderRecompiler::IR::ResourcePlan resource_plan;
|
||||
ShaderRecompiler::IR::ResourceSnapshot resources;
|
||||
ShaderRecompiler::IR::ResourceSpecialization specialization;
|
||||
std::vector<Permutation> permutations;
|
||||
};
|
||||
|
||||
struct ProgramKeyHash {
|
||||
@@ -274,33 +276,33 @@ struct PipelineCache::ProgramCache {
|
||||
stage = ShaderType::Compute;
|
||||
}
|
||||
|
||||
const auto user_data = std::span(params.user_data).first(params.user_data_count);
|
||||
lookup_key.stage = stage;
|
||||
lookup_key.hash = params.hash;
|
||||
lookup_key.user_data_count = static_cast<uint32_t>(params.user_data.size());
|
||||
lookup_key.user_data_count = params.user_data_count;
|
||||
lookup_key.code_size = static_cast<uint32_t>(params.code.size());
|
||||
BuildStageStaticKey(input_info, lookup_key.static_state);
|
||||
auto entry = programs.find(lookup_key);
|
||||
ShaderRecompiler::IR::ResourceSnapshot resources;
|
||||
ShaderRecompiler::IR::ResourceSpecialization specialization;
|
||||
const ShaderRecompiler::IR::SrtRuntime runtime {
|
||||
.user_data = params.user_data,
|
||||
.user_data = user_data,
|
||||
.shader_base = params.Base(),
|
||||
.read_specialization_memory = ReadShaderGuestMemory,
|
||||
};
|
||||
if (entry != programs.end()) {
|
||||
EXIT_IF(!ShaderRecompiler::IR::MaterializeResources(
|
||||
entry->second.resource_plan, runtime, resources, specialization));
|
||||
entry->second.resource_plan, runtime, entry->second.resources,
|
||||
entry->second.specialization));
|
||||
if (const auto permutation = std::ranges::find_if(
|
||||
entry->second.permutations, [&](const Permutation& candidate) {
|
||||
const auto& layout = candidate.program.bindings;
|
||||
return layout.push_data_start_dword ==
|
||||
ShaderRecompiler::IR::PushData::StartFor(
|
||||
push_data_cursor, layout.ShaderDataDwords()) &&
|
||||
candidate.specialization == specialization;
|
||||
candidate.specialization == entry->second.specialization;
|
||||
});
|
||||
permutation != entry->second.permutations.end()) {
|
||||
input_info.stage = {.program = &permutation->program,
|
||||
.resources = std::move(resources)};
|
||||
.resources = &entry->second.resources};
|
||||
permutation->program.bindings.AdvancePushData(push_data_cursor);
|
||||
return permutation->handle;
|
||||
}
|
||||
@@ -328,7 +330,7 @@ struct PipelineCache::ProgramCache {
|
||||
ShaderRecompiler::CompileOptions options;
|
||||
options.stage = stage;
|
||||
options.shader_hash = params.hash;
|
||||
options.user_data = params.user_data;
|
||||
options.user_data = user_data;
|
||||
options.back_code = params.back_code;
|
||||
options.dump_ir = Config::GetShaderLogDirection() != Config::LogDirection::Silent;
|
||||
options.early_dump = options.dump_ir;
|
||||
@@ -337,6 +339,7 @@ struct PipelineCache::ProgramCache {
|
||||
|
||||
if constexpr (std::is_same_v<InputInfo, ShaderVertexInputInfo>) {
|
||||
options.user_data_base = 8;
|
||||
options.wave_size = input_info.wave_size;
|
||||
if (stage == ShaderType::Mesh || stage == ShaderType::TessellationControl) {
|
||||
options.user_data_base = 0;
|
||||
options.wave_size = stage == ShaderType::Mesh ? input_info.mesh.wave_size : 64u;
|
||||
@@ -346,15 +349,16 @@ struct PipelineCache::ProgramCache {
|
||||
}
|
||||
auto translated = ShaderRecompiler::TranslateProgram(params.code, options);
|
||||
if (entry == programs.end()) {
|
||||
auto resource_plan = ShaderRecompiler::IR::ExtractResourcePlan(translated.program);
|
||||
EXIT_IF(!ShaderRecompiler::IR::MaterializeResources(resource_plan, runtime, resources,
|
||||
specialization));
|
||||
entry = programs.try_emplace(lookup_key, std::move(resource_plan)).first;
|
||||
entry = programs.try_emplace(lookup_key,
|
||||
ShaderRecompiler::IR::ExtractResourcePlan(translated.program)).first;
|
||||
EXIT_IF(!ShaderRecompiler::IR::MaterializeResources(
|
||||
entry->second.resource_plan, runtime, entry->second.resources,
|
||||
entry->second.specialization));
|
||||
}
|
||||
entry->second.permutations.push_back(CompilePermutation(
|
||||
params, options, std::move(translated), std::move(specialization), push_data_cursor));
|
||||
params, options, std::move(translated), entry->second.specialization, push_data_cursor));
|
||||
const auto& permutation = entry->second.permutations.back();
|
||||
input_info.stage = {.program = &permutation.program, .resources = std::move(resources)};
|
||||
input_info.stage = {.program = &permutation.program, .resources = &entry->second.resources};
|
||||
permutation.program.bindings.AdvancePushData(push_data_cursor);
|
||||
|
||||
std::array<size_t, static_cast<size_t>(ShaderType::TessellationEvaluation) + 1> counts {};
|
||||
@@ -593,6 +597,21 @@ PipelineCache::GraphicsPrograms PipelineCache::GetGraphicsPrograms(
|
||||
ShaderParams pixel_params;
|
||||
if (pixel_active) {
|
||||
pixel_params = PrepareProgram(pixel_regs, sh, target_export_mapping, pixel_info);
|
||||
const auto& blend = context.GetBlendControl(0);
|
||||
const auto is_dual_source = [](uint8_t factor) {
|
||||
return factor >= static_cast<uint8_t>(Prospero::BlendFactor::kSrc1Color) &&
|
||||
factor <= static_cast<uint8_t>(Prospero::BlendFactor::kOneMinusSrc1Alpha);
|
||||
};
|
||||
pixel_info.dual_source_blending =
|
||||
blend.enable && !context.GetRenderTarget(0).info.blend_bypass &&
|
||||
(is_dual_source(blend.color_srcblend) || is_dual_source(blend.color_destblend) ||
|
||||
(blend.separate_alpha_blend &&
|
||||
(is_dual_source(blend.alpha_srcblend) || is_dual_source(blend.alpha_destblend))));
|
||||
if (pixel_info.dual_source_blending) {
|
||||
// MRT1 supplies a second blend source for the same render target as MRT0.
|
||||
pixel_info.target_output_mode[1] = pixel_info.target_output_mode[0];
|
||||
pixel_info.target_export_mapping[1] = pixel_info.target_export_mapping[0];
|
||||
}
|
||||
}
|
||||
if (context.GetClipControl().clip_disable) {
|
||||
const auto& viewport = context.GetScreenViewport().viewports[0];
|
||||
@@ -740,8 +759,7 @@ PipelineCache::Pipeline& PipelineCache::GetGraphicsPipeline(
|
||||
static_params.depth_bounds_test_enable = depth.depth_bounds_test_enable;
|
||||
static_params.depth_min_bounds = depth.depth_min_bounds;
|
||||
static_params.depth_max_bounds = depth.depth_max_bounds;
|
||||
const bool rect_list =
|
||||
command.GetUserConfig().GetPrimType() == Prospero::PrimitiveType::kRectList;
|
||||
const bool rect_list = Prospero::IsRectList(command.GetUserConfig().GetPrimType());
|
||||
static_params.cull_back = !rect_list && mc.cull_back;
|
||||
static_params.cull_front = !rect_list && mc.cull_front;
|
||||
static_params.face = mc.face;
|
||||
|
||||
@@ -90,7 +90,7 @@ vk::BufferMemoryBarrier MakeGdsDependency(vk::Buffer buffer) {
|
||||
bool HasShaderBufferWrites(const ShaderStageRuntime& runtime) {
|
||||
EXIT_IF(!runtime);
|
||||
const auto& program = *runtime.program;
|
||||
const auto& resources = runtime.resources;
|
||||
const auto& resources = *runtime.resources;
|
||||
EXIT_IF(resources.buffers.size() != program.info.buffers.size());
|
||||
bool has_writes = false;
|
||||
for (uint32_t i = 0; i < program.info.buffers.size(); i++) {
|
||||
|
||||
@@ -45,6 +45,7 @@ enum class CommandBufferDebugOp : uint32_t {
|
||||
EopFlip,
|
||||
EopWriteBackFlip,
|
||||
EopOnlyFlip,
|
||||
DispatchIndirect,
|
||||
Unknown,
|
||||
};
|
||||
|
||||
@@ -156,6 +157,8 @@ public:
|
||||
|
||||
void DispatchDirect(uint64_t submit_id, CommandBuffer& buffer, uint32_t thread_group_x,
|
||||
uint32_t thread_group_y, uint32_t thread_group_z, uint32_t mode);
|
||||
void DispatchIndirect(uint64_t submit_id, CommandBuffer& buffer, uint64_t args_addr,
|
||||
uint32_t mode);
|
||||
|
||||
[[nodiscard]] PreparedBindings PrepareBindings(const ShaderStageRuntime& runtime);
|
||||
void FindBuffers(PreparedBindings& bindings);
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "graphics/guest_gpu/gpu_defs.h"
|
||||
#include "graphics/guest_gpu/graphicsRun.h"
|
||||
#include "graphics/guest_gpu/hardwareContext.h"
|
||||
#include "graphics/guest_gpu/pm4.h"
|
||||
#include "graphics/host_gpu/graphicContext.h"
|
||||
#include "graphics/host_gpu/renderer/image/imageInfo.h"
|
||||
#include "graphics/host_gpu/renderer/pipeline/descriptors.h"
|
||||
@@ -51,7 +52,7 @@ static bool FillSourcesDisjoint(std::span<const ShaderRecompiler::IR::Descriptor
|
||||
bool RenderExecutor::TryConsumeComputeMetaClear(const ShaderComputeInputInfo& input,
|
||||
const CommandBuffer& buffer) {
|
||||
const auto& program = *input.stage.program;
|
||||
const auto& resources = input.stage.resources;
|
||||
const auto& resources = *input.stage.resources;
|
||||
if (resources.buffers.size() != program.info.buffers.size()) {
|
||||
EXIT("compute runtime buffer count does not match shader metadata\n");
|
||||
}
|
||||
@@ -85,7 +86,7 @@ bool ResolveComputeBufferFill(const ShaderComputeInputInfo& input, uint32_t grou
|
||||
uint32_t group_y, uint32_t group_z, uint32_t mode,
|
||||
ShaderBufferResource& resolved_descriptor, uint32_t& resolved_clear,
|
||||
uint64_t& resolved_size) {
|
||||
const auto& resources = input.stage.resources;
|
||||
const auto& resources = *input.stage.resources;
|
||||
const auto& fill = resources.uniform_fill;
|
||||
if (fill.kind != ShaderRecompiler::IR::UniformFillKind::Buffer) {
|
||||
return false;
|
||||
@@ -128,7 +129,7 @@ bool RenderExecutor::TryConsumeComputeImageClear(const ShaderComputeInputInfo& i
|
||||
CommandBuffer& command, uint32_t group_x,
|
||||
uint32_t group_y, uint32_t group_z, uint32_t mode) {
|
||||
const auto& program = *input.stage.program;
|
||||
const auto& resources = input.stage.resources;
|
||||
const auto& resources = *input.stage.resources;
|
||||
const auto& fill = resources.uniform_fill;
|
||||
auto& cache = command.GetContext().GetTextureCache();
|
||||
if (fill.kind == ShaderRecompiler::IR::UniformFillKind::Image) {
|
||||
@@ -262,7 +263,7 @@ void RenderExecutor::DispatchDirect(uint64_t submit_id, CommandBuffer& buffer,
|
||||
}
|
||||
|
||||
const auto& program = *input_info.stage.program;
|
||||
const auto& resources = input_info.stage.resources;
|
||||
const auto& resources = *input_info.stage.resources;
|
||||
if (TryConsumeComputeMetaClear(input_info, buffer)) {
|
||||
ResetBindings();
|
||||
return;
|
||||
@@ -395,4 +396,60 @@ void RenderExecutor::DispatchDirect(uint64_t submit_id, CommandBuffer& buffer,
|
||||
ResetBindings();
|
||||
}
|
||||
|
||||
void RenderExecutor::DispatchIndirect(uint64_t submit_id, CommandBuffer& buffer,
|
||||
uint64_t args_addr, uint32_t mode) {
|
||||
EXIT_IF(buffer.IsInvalid() || args_addr == 0 || (args_addr & 3u) != 0 ||
|
||||
(mode & Pm4::COMPUTE_DISPATCH_INITIATOR_USE_THREAD_DIMENSIONS) != 0);
|
||||
m_context.GetCommandScheduler().PopPendingOperations();
|
||||
buffer.SetDebugInfo(static_cast<uint32_t>(CommandBufferDebugOp::DispatchIndirect), submit_id,
|
||||
static_cast<uint32_t>(args_addr), static_cast<uint32_t>(args_addr >> 32u),
|
||||
0, mode, buffer.GetShaders().GetCs().cs_regs.data_addr);
|
||||
Common::LockGuard lock(m_context.GetMutex());
|
||||
const auto& cs_regs = buffer.GetShaders().GetCs();
|
||||
if (cs_regs.cs_regs.data_addr == 0) {
|
||||
return;
|
||||
}
|
||||
ShaderComputeInputInfo input_info {};
|
||||
const auto compute_program = m_context.GetPipelineCache().GetComputeProgram(
|
||||
cs_regs, buffer.GetRegisters().GetShaderRegisters(), input_info);
|
||||
buffer.EndRendering();
|
||||
auto& pipeline = m_context.GetPipelineCache().GetComputePipeline(input_info, compute_program);
|
||||
auto bindings = PrepareBindings(input_info.stage);
|
||||
FindBuffers(bindings);
|
||||
const auto& program = *input_info.stage.program;
|
||||
if (program.info.uses_dma) {
|
||||
m_context.PrepareBda();
|
||||
}
|
||||
RebindImages(bindings);
|
||||
// Acquiring arguments can merge cache buffers; finalize shader bindings afterward.
|
||||
const auto [args_buffer, args_offset] = m_context.GetBufferCache().ObtainBuffer(
|
||||
args_addr, sizeof(vk::DispatchIndirectCommand), false);
|
||||
EXIT_IF(args_buffer == nullptr || (args_offset & 3u) != 0);
|
||||
RebindBuffers(bindings);
|
||||
PreparedBindings* descriptor_stage = &bindings;
|
||||
CommitBindings(buffer, vk::PipelineBindPoint::eCompute, pipeline,
|
||||
std::span {&descriptor_stage, 1u});
|
||||
const auto vk_buffer = buffer.Handle();
|
||||
const bool has_storage_writes = HasShaderBufferWrites(input_info.stage) ||
|
||||
std::any_of(program.info.images.begin(), program.info.images.end(), [](const auto& image) {
|
||||
return image.written && image.resource_class ==
|
||||
ShaderRecompiler::IR::ImageResourceClass::Storage;
|
||||
});
|
||||
if (has_storage_writes) {
|
||||
ShaderWriteHazardBarrier(vk_buffer, vk::PipelineStageFlagBits::eComputeShader);
|
||||
}
|
||||
vk::MemoryBarrier barrier {};
|
||||
barrier.srcAccessMask = vk::AccessFlagBits::eShaderWrite | vk::AccessFlagBits::eTransferWrite;
|
||||
barrier.dstAccessMask = vk::AccessFlagBits::eIndirectCommandRead;
|
||||
vk_buffer.pipelineBarrier(vk::PipelineStageFlagBits::eAllGraphics |
|
||||
vk::PipelineStageFlagBits::eComputeShader |
|
||||
vk::PipelineStageFlagBits::eTransfer,
|
||||
vk::PipelineStageFlagBits::eDrawIndirect, {},
|
||||
1, &barrier, 0, nullptr, 0, nullptr);
|
||||
vk_buffer.bindPipeline(vk::PipelineBindPoint::eCompute, pipeline.pipeline);
|
||||
vk_buffer.dispatchIndirect(args_buffer->Handle(), args_offset);
|
||||
ShaderAccessBarrier(vk_buffer, vk::PipelineStageFlagBits::eComputeShader);
|
||||
ResetBindings();
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#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"
|
||||
@@ -57,7 +58,7 @@ std::pair<int32_t, uint32_t> ResolveDrawOffsets(uint32_t index_offset,
|
||||
|
||||
EXIT_IF(!vs_input_info.stage);
|
||||
const auto& program = *vs_input_info.stage.program;
|
||||
const auto& resources = vs_input_info.stage.resources;
|
||||
const auto& resources = *vs_input_info.stage.resources;
|
||||
if (index_offset == 0 &&
|
||||
program.info.vertex_offset_sgpr >= static_cast<int32_t>(program.user_data_base)) {
|
||||
const auto index =
|
||||
@@ -460,7 +461,6 @@ RenderState RenderExecutor::AcquireRenderTargets(CommandBuffer& buffer, RenderCo
|
||||
state.height = std::numeric_limits<uint32_t>::max();
|
||||
state.num_layers = std::numeric_limits<uint32_t>::max();
|
||||
state.num_color_attachments = 0;
|
||||
uint32_t attachment_samples = 0;
|
||||
for (uint32_t i = 0; i < color_count; i++) {
|
||||
auto& target = colors[i];
|
||||
EXIT_IF(!target.image_id);
|
||||
@@ -481,12 +481,6 @@ RenderState RenderExecutor::AcquireRenderTargets(CommandBuffer& buffer, RenderCo
|
||||
target.desc.view_info.base_level, target.desc.view_info.base_layer,
|
||||
target.desc.view_info.layer_count);
|
||||
EXIT_IF(image.backing.samples != target.desc.info.samples || image_view == nullptr);
|
||||
if (attachment_samples == 0) {
|
||||
attachment_samples = target.desc.info.samples;
|
||||
} else if (attachment_samples != target.desc.info.samples) {
|
||||
EXIT("mixed color attachment sample counts are unsupported: %u and %u\n",
|
||||
attachment_samples, target.desc.info.samples);
|
||||
}
|
||||
const auto& view = target.desc.view_info;
|
||||
const auto layout = image.binding.is_bound ? vk::ImageLayout::eGeneral
|
||||
: vk::ImageLayout::eColorAttachmentOptimal;
|
||||
@@ -535,12 +529,6 @@ RenderState RenderExecutor::AcquireRenderTargets(CommandBuffer& buffer, RenderCo
|
||||
image.info.data.address, depth.desc.view_info.base_layer,
|
||||
depth.desc.view_info.layer_count);
|
||||
EXIT_IF(image_view == nullptr || image.backing.samples != depth.desc.info.samples);
|
||||
if (attachment_samples == 0) {
|
||||
attachment_samples = depth.desc.info.samples;
|
||||
} else if (attachment_samples != depth.desc.info.samples) {
|
||||
EXIT("mixed color/depth sample counts are unsupported: %u and %u\n", attachment_samples,
|
||||
depth.desc.info.samples);
|
||||
}
|
||||
const bool feedback = depth.depth_write_enable && pixel &&
|
||||
std::ranges::any_of(pixel->images, [&](const TextureBinding& binding) {
|
||||
if (binding.image_id != depth.image_id ||
|
||||
@@ -591,9 +579,6 @@ RenderState RenderExecutor::AcquireRenderTargets(CommandBuffer& buffer, RenderCo
|
||||
const auto& limits = buffer.GetGraphics().GetPhysicalDeviceProperties().limits;
|
||||
state.width = limits.maxFramebufferWidth;
|
||||
state.height = limits.maxFramebufferHeight;
|
||||
} else if (attachment_samples == 0 ||
|
||||
vulkan_sample_count(attachment_samples) == vk::SampleCountFlagBits {}) {
|
||||
EXIT("render state has no valid attachments\n");
|
||||
}
|
||||
if (state.num_layers == std::numeric_limits<uint32_t>::max()) {
|
||||
state.num_layers = 1;
|
||||
@@ -789,8 +774,7 @@ static void SetDrawDebugPhase(CommandBuffer& buffer, uint64_t submit_id, const D
|
||||
draw.instance_count, draw.first_instance);
|
||||
}
|
||||
|
||||
static bool GetDrawTopology(const HW::UserConfig& ucfg, bool auto_draw,
|
||||
vk::PrimitiveTopology& topology) {
|
||||
static bool GetDrawTopology(const HW::UserConfig& ucfg, vk::PrimitiveTopology& topology) {
|
||||
|
||||
topology = vk::PrimitiveTopology::ePointList;
|
||||
|
||||
@@ -813,14 +797,11 @@ static bool GetDrawTopology(const HW::UserConfig& ucfg, bool auto_draw,
|
||||
topology = vk::PrimitiveTopology::eTriangleStrip;
|
||||
break;
|
||||
case Prospero::PrimitiveType::kPatch:
|
||||
if (!Config::TessellationEnabled()) return false;
|
||||
[[fallthrough]];
|
||||
case Prospero::PrimitiveType::kRectList:
|
||||
topology = vk::PrimitiveTopology::ePatchList;
|
||||
break;
|
||||
case Prospero::PrimitiveType::kRectListLegacy:
|
||||
if (!auto_draw) {
|
||||
EXIT("unknown primitive type: %u\n", static_cast<uint32_t>(ucfg.GetPrimType()));
|
||||
}
|
||||
topology = vk::PrimitiveTopology::eTriangleStrip;
|
||||
topology = vk::PrimitiveTopology::ePatchList;
|
||||
break;
|
||||
case Prospero::PrimitiveType::kQuadListLegacy:
|
||||
topology = vk::PrimitiveTopology::eTriangleFan;
|
||||
@@ -999,8 +980,7 @@ static void LogDrawStateIfNeeded(const CommandBuffer& buffer, const DrawCallInfo
|
||||
return;
|
||||
}
|
||||
|
||||
if (!draw.IsIndexed() &&
|
||||
buffer.GetUserConfig().GetPrimType() != Prospero::PrimitiveType::kRectListLegacy) {
|
||||
if (!draw.IsIndexed() && !Prospero::IsRectList(buffer.GetUserConfig().GetPrimType())) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1013,8 +993,7 @@ static void LogDrawStateIfNeeded(const CommandBuffer& buffer, const DrawCallInfo
|
||||
}
|
||||
|
||||
static void EmitDrawPrimitives(const HW::UserConfig& ucfg, vk::CommandBuffer vk_buffer,
|
||||
const ShaderVertexInputInfo& vs_input_info, const DrawCallInfo& draw,
|
||||
const DrawEmitInfo& emit) {
|
||||
const DrawCallInfo& draw, const DrawEmitInfo& emit) {
|
||||
switch (ucfg.GetPrimType()) {
|
||||
case Prospero::PrimitiveType::kPointList:
|
||||
case Prospero::PrimitiveType::kLineList:
|
||||
@@ -1023,6 +1002,7 @@ static void EmitDrawPrimitives(const HW::UserConfig& ucfg, vk::CommandBuffer vk_
|
||||
case Prospero::PrimitiveType::kTriFan:
|
||||
case Prospero::PrimitiveType::kTriStrip:
|
||||
case Prospero::PrimitiveType::kRectList:
|
||||
case Prospero::PrimitiveType::kRectListLegacy:
|
||||
case Prospero::PrimitiveType::kPatch:
|
||||
if (draw.IsIndexed()) {
|
||||
vk_buffer.drawIndexed(draw.index_count, draw.instance_count, 0, emit.vertex_offset,
|
||||
@@ -1032,14 +1012,6 @@ static void EmitDrawPrimitives(const HW::UserConfig& ucfg, vk::CommandBuffer vk_
|
||||
emit.first_instance);
|
||||
}
|
||||
break;
|
||||
case Prospero::PrimitiveType::kRectListLegacy:
|
||||
if (draw.IsIndexed()) {
|
||||
EXIT("unknown primitive type: %u\n", static_cast<uint32_t>(ucfg.GetPrimType()));
|
||||
}
|
||||
// Sarah
|
||||
EXIT_NOT_IMPLEMENTED(!(draw.index_count == 3 && vs_input_info.buffers_num == 0));
|
||||
vk_buffer.draw(4, draw.instance_count, emit.first_vertex, emit.first_instance);
|
||||
break;
|
||||
case Prospero::PrimitiveType::kQuadListLegacy:
|
||||
EXIT_NOT_IMPLEMENTED((draw.index_count & 0x3u) != 0);
|
||||
for (uint32_t i = 0; i < draw.index_count; i += 4) {
|
||||
@@ -1121,10 +1093,6 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, CommandBuffer& buff
|
||||
vertex_bindings = AcquireVertexBuffers(buffer, state.vertex_info[0]);
|
||||
index_binding = PrepareIndexBuffer(buffer, index_source);
|
||||
}
|
||||
const auto rendering =
|
||||
AcquireRenderTargets(buffer, state.color_info, state.color_count, state.depth_info,
|
||||
bindings.pixel);
|
||||
|
||||
if (draw.IsIndexed()) {
|
||||
LogDrawPhase(draw.Name(), "CreatePipeline");
|
||||
}
|
||||
@@ -1132,6 +1100,9 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, CommandBuffer& buff
|
||||
std::span {state.color_info, state.color_count}, state.depth_info, vertex_stages, buffer,
|
||||
state.ps_active ? &state.ps_input_info : nullptr, topology, primitive_restart_enable,
|
||||
state.programs);
|
||||
const auto rendering =
|
||||
AcquireRenderTargets(buffer, state.color_info, state.color_count, state.depth_info,
|
||||
bindings.pixel);
|
||||
|
||||
// Resource preparation above may synchronously finish and restart the scheduler. From this
|
||||
// point onward, every operation targets the current command buffer and cannot touch guest
|
||||
@@ -1182,7 +1153,7 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, CommandBuffer& buff
|
||||
if (mesh_active) {
|
||||
vk_buffer.drawMeshTasksEXT(mesh_groups, draw.instance_count, 1);
|
||||
} else {
|
||||
EmitDrawPrimitives(ucfg, vk_buffer, state.vertex_info[0], draw, emit);
|
||||
EmitDrawPrimitives(ucfg, vk_buffer, draw, emit);
|
||||
}
|
||||
|
||||
if (!draw.IsIndexed()) {
|
||||
@@ -1258,7 +1229,7 @@ void RenderExecutor::DrawIndex(uint64_t submit_id, CommandBuffer& buffer,
|
||||
hw_check(buffer);
|
||||
|
||||
vk::PrimitiveTopology topology = vk::PrimitiveTopology::ePointList;
|
||||
if (!GetDrawTopology(ucfg, false, topology)) {
|
||||
if (!GetDrawTopology(ucfg, topology)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1368,7 +1339,7 @@ void RenderExecutor::DrawAuto(uint64_t submit_id, CommandBuffer& buffer, const D
|
||||
args.vertex_count, args.instance_count, args.first_instance};
|
||||
|
||||
vk::PrimitiveTopology topology = vk::PrimitiveTopology::ePointList;
|
||||
if (!GetDrawTopology(ucfg, true, topology)) {
|
||||
if (!GetDrawTopology(ucfg, topology)) {
|
||||
ResetBindings();
|
||||
return;
|
||||
}
|
||||
@@ -1378,7 +1349,7 @@ void RenderExecutor::DrawAuto(uint64_t submit_id, CommandBuffer& buffer, const D
|
||||
return;
|
||||
}
|
||||
|
||||
const bool rect_list = ucfg.GetPrimType() == Prospero::PrimitiveType::kRectList;
|
||||
const bool rect_list = Prospero::IsRectList(ucfg.GetPrimType());
|
||||
if (rect_list && state.vertex_info[0].buffers_num == 0 &&
|
||||
state.vertex_info[0].stage.program->param_export_mask == 0 &&
|
||||
state.ps_input_info.input_num != 0) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "graphics/presentation/systemOverlay.h"
|
||||
|
||||
#include "SDL.h"
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/stringUtils.h"
|
||||
#include "graphics/host_gpu/graphicContext.h"
|
||||
@@ -176,6 +177,7 @@ bool g_input_multiline = false;
|
||||
bool g_input_lifecycle_active = false;
|
||||
bool g_controller_captured = false;
|
||||
OverlaySession g_session;
|
||||
SDL_Window* g_input_window = nullptr;
|
||||
|
||||
void ClearInputEvents() {
|
||||
std::scoped_lock lock(g_input_mutex);
|
||||
@@ -212,7 +214,7 @@ void RetryVisibilityWakeup() {
|
||||
}
|
||||
SDL_Event event {};
|
||||
event.type = type;
|
||||
if (SDL_PushEvent(&event) > 0) {
|
||||
if (SDL_PushEvent(&event)) {
|
||||
g_missing_visibility_wakeups--;
|
||||
}
|
||||
}
|
||||
@@ -252,7 +254,7 @@ void RefreshVisibility() {
|
||||
std::scoped_lock input_lock(g_input_mutex);
|
||||
g_visibility_updates.push_back({snapshot.session, visible, capture_controller,
|
||||
capture_keyboard, text_input, multiline});
|
||||
if (g_missing_visibility_wakeups != 0 || SDL_PushEvent(&event) <= 0) {
|
||||
if (g_missing_visibility_wakeups != 0 || !SDL_PushEvent(&event)) {
|
||||
g_missing_visibility_wakeups++;
|
||||
}
|
||||
}
|
||||
@@ -268,16 +270,16 @@ void OnDialogVisibilityChanged(bool, uint64_t) {
|
||||
|
||||
uint32_t ExternalKeyStatus(SDL_Keymod modifiers, bool character_valid) {
|
||||
uint32_t status = 0x00000001 | (character_valid ? 0x00000002 : 0);
|
||||
if ((modifiers & KMOD_LCTRL) != 0) status |= 0x00000100;
|
||||
if ((modifiers & KMOD_LSHIFT) != 0) status |= 0x00000200;
|
||||
if ((modifiers & KMOD_LALT) != 0) status |= 0x00000400;
|
||||
if ((modifiers & KMOD_LGUI) != 0) status |= 0x00000800;
|
||||
if ((modifiers & KMOD_RCTRL) != 0) status |= 0x00001000;
|
||||
if ((modifiers & KMOD_RSHIFT) != 0) status |= 0x00002000;
|
||||
if ((modifiers & KMOD_RALT) != 0) status |= 0x00004000;
|
||||
if ((modifiers & KMOD_RGUI) != 0) status |= 0x00008000;
|
||||
if ((modifiers & KMOD_NUM) != 0) status |= 0x00010000;
|
||||
if ((modifiers & KMOD_CAPS) != 0) status |= 0x00020000;
|
||||
if ((modifiers & SDL_KMOD_LCTRL) != 0) status |= 0x00000100;
|
||||
if ((modifiers & SDL_KMOD_LSHIFT) != 0) status |= 0x00000200;
|
||||
if ((modifiers & SDL_KMOD_LALT) != 0) status |= 0x00000400;
|
||||
if ((modifiers & SDL_KMOD_LGUI) != 0) status |= 0x00000800;
|
||||
if ((modifiers & SDL_KMOD_RCTRL) != 0) status |= 0x00001000;
|
||||
if ((modifiers & SDL_KMOD_RSHIFT) != 0) status |= 0x00002000;
|
||||
if ((modifiers & SDL_KMOD_RALT) != 0) status |= 0x00004000;
|
||||
if ((modifiers & SDL_KMOD_RGUI) != 0) status |= 0x00008000;
|
||||
if ((modifiers & SDL_KMOD_NUM) != 0) status |= 0x00010000;
|
||||
if ((modifiers & SDL_KMOD_CAPS) != 0) status |= 0x00020000;
|
||||
return status;
|
||||
}
|
||||
|
||||
@@ -392,14 +394,14 @@ float AlignmentPivot(Ime::Alignment alignment) {
|
||||
|
||||
ImGuiKey ControllerButtonToKey(int button) {
|
||||
switch (button) {
|
||||
case SDL_CONTROLLER_BUTTON_A: return ImGuiKey_GamepadFaceDown;
|
||||
case SDL_CONTROLLER_BUTTON_B: return ImGuiKey_GamepadFaceRight;
|
||||
case SDL_CONTROLLER_BUTTON_X: return ImGuiKey_GamepadFaceLeft;
|
||||
case SDL_CONTROLLER_BUTTON_Y: return ImGuiKey_GamepadFaceUp;
|
||||
case SDL_CONTROLLER_BUTTON_DPAD_LEFT: return ImGuiKey_GamepadDpadLeft;
|
||||
case SDL_CONTROLLER_BUTTON_DPAD_RIGHT: return ImGuiKey_GamepadDpadRight;
|
||||
case SDL_CONTROLLER_BUTTON_DPAD_UP: return ImGuiKey_GamepadDpadUp;
|
||||
case SDL_CONTROLLER_BUTTON_DPAD_DOWN: return ImGuiKey_GamepadDpadDown;
|
||||
case SDL_GAMEPAD_BUTTON_SOUTH: return ImGuiKey_GamepadFaceDown;
|
||||
case SDL_GAMEPAD_BUTTON_EAST: return ImGuiKey_GamepadFaceRight;
|
||||
case SDL_GAMEPAD_BUTTON_WEST: return ImGuiKey_GamepadFaceLeft;
|
||||
case SDL_GAMEPAD_BUTTON_NORTH: return ImGuiKey_GamepadFaceUp;
|
||||
case SDL_GAMEPAD_BUTTON_DPAD_LEFT: return ImGuiKey_GamepadDpadLeft;
|
||||
case SDL_GAMEPAD_BUTTON_DPAD_RIGHT: return ImGuiKey_GamepadDpadRight;
|
||||
case SDL_GAMEPAD_BUTTON_DPAD_UP: return ImGuiKey_GamepadDpadUp;
|
||||
case SDL_GAMEPAD_BUTTON_DPAD_DOWN: return ImGuiKey_GamepadDpadDown;
|
||||
default: return ImGuiKey_None;
|
||||
}
|
||||
}
|
||||
@@ -415,9 +417,11 @@ void CheckVulkanResult(VkResult result) {
|
||||
|
||||
} // namespace
|
||||
|
||||
void InitializeSystemOverlayInput() {
|
||||
void InitializeSystemOverlayInput(SDL_Window* window) {
|
||||
EXIT_IF(window == nullptr);
|
||||
const Uint32 type = SDL_RegisterEvents(1);
|
||||
EXIT_IF(type == static_cast<Uint32>(-1));
|
||||
g_input_window = window;
|
||||
{
|
||||
std::scoped_lock lock(g_visibility_mutex);
|
||||
g_input_lifecycle_active = true;
|
||||
@@ -453,9 +457,10 @@ void ShutdownSystemOverlayInput() {
|
||||
g_input_controller = false;
|
||||
g_input_keyboard = false;
|
||||
g_input_multiline = false;
|
||||
if (SDL_IsTextInputActive() == SDL_TRUE) {
|
||||
SDL_StopTextInput();
|
||||
if (g_input_window != nullptr && SDL_TextInputActive(g_input_window)) {
|
||||
SDL_StopTextInput(g_input_window);
|
||||
}
|
||||
g_input_window = nullptr;
|
||||
}
|
||||
|
||||
SystemOverlayVisualState GetSystemOverlayVisualState() noexcept {
|
||||
@@ -487,13 +492,13 @@ bool ProcessSystemOverlayInput(const SDL_Event& event) {
|
||||
g_input_keyboard = update.capture_keyboard;
|
||||
g_input_multiline = update.multiline;
|
||||
if (update.text_input) {
|
||||
SDL_StartTextInput();
|
||||
} else if (SDL_IsTextInputActive() == SDL_TRUE) {
|
||||
SDL_StopTextInput();
|
||||
SDL_StartTextInput(g_input_window);
|
||||
} else if (SDL_TextInputActive(g_input_window)) {
|
||||
SDL_StopTextInput(g_input_window);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (event.type == SDL_CONTROLLERDEVICEREMOVED) {
|
||||
if (event.type == SDL_EVENT_GAMEPAD_REMOVED) {
|
||||
if (g_input_active && g_input_controller) {
|
||||
QueueInput({InputKind::ResetController, g_input_session, 0, 0.0f, 0.0f});
|
||||
}
|
||||
@@ -505,26 +510,26 @@ bool ProcessSystemOverlayInput(const SDL_Event& event) {
|
||||
|
||||
const auto session = g_input_session;
|
||||
const uint64_t generation = session.generation;
|
||||
const bool controller_event = event.type == SDL_CONTROLLERBUTTONDOWN ||
|
||||
event.type == SDL_CONTROLLERBUTTONUP ||
|
||||
event.type == SDL_CONTROLLERAXISMOTION;
|
||||
const bool controller_event = event.type == SDL_EVENT_GAMEPAD_BUTTON_DOWN ||
|
||||
event.type == SDL_EVENT_GAMEPAD_BUTTON_UP ||
|
||||
event.type == SDL_EVENT_GAMEPAD_AXIS_MOTION;
|
||||
if (controller_event && !g_input_controller) {
|
||||
return false;
|
||||
}
|
||||
const bool keyboard_event = event.type == SDL_TEXTINPUT || event.type == SDL_TEXTEDITING ||
|
||||
event.type == SDL_KEYDOWN || event.type == SDL_KEYUP;
|
||||
const bool keyboard_event = event.type == SDL_EVENT_TEXT_INPUT || event.type == SDL_EVENT_TEXT_EDITING ||
|
||||
event.type == SDL_EVENT_KEY_DOWN || event.type == SDL_EVENT_KEY_UP;
|
||||
if (keyboard_event && !g_input_keyboard) {
|
||||
return false;
|
||||
}
|
||||
if (keyboard_event && session.kind == OverlayKind::Error) {
|
||||
if (event.type == SDL_KEYDOWN && event.key.repeat == 0 &&
|
||||
(event.key.keysym.sym == SDLK_RETURN || event.key.keysym.sym == SDLK_KP_ENTER)) {
|
||||
if (event.type == SDL_EVENT_KEY_DOWN && !event.key.repeat &&
|
||||
(event.key.key == SDLK_RETURN || event.key.key == SDLK_KP_ENTER)) {
|
||||
ErrorDialog::HostAccept(generation);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
switch (event.type) {
|
||||
case SDL_TEXTINPUT: {
|
||||
case SDL_EVENT_TEXT_INPUT: {
|
||||
const auto text = Utf8ToUtf16(event.text.text);
|
||||
if (!text.empty()) {
|
||||
auto input = MakeExternalInput(Ime::ExternalAction::Text, g_last_external_keycode,
|
||||
@@ -535,26 +540,24 @@ bool ProcessSystemOverlayInput(const SDL_Event& event) {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
case SDL_TEXTEDITING: return true;
|
||||
case SDL_KEYDOWN: {
|
||||
g_last_external_keycode = static_cast<uint16_t>(event.key.keysym.scancode);
|
||||
g_last_external_status =
|
||||
ExternalKeyStatus(static_cast<SDL_Keymod>(event.key.keysym.mod), false);
|
||||
case SDL_EVENT_TEXT_EDITING: return true;
|
||||
case SDL_EVENT_KEY_DOWN: {
|
||||
g_last_external_keycode = static_cast<uint16_t>(event.key.scancode);
|
||||
g_last_external_status = ExternalKeyStatus(event.key.mod, false);
|
||||
auto action = Ime::ExternalAction::Text;
|
||||
bool queue = true;
|
||||
if (event.key.keysym.sym == SDLK_BACKSPACE) {
|
||||
if (event.key.key == SDLK_BACKSPACE) {
|
||||
action = Ime::ExternalAction::Backspace;
|
||||
} else if (event.key.keysym.sym == SDLK_LEFT) {
|
||||
} else if (event.key.key == SDLK_LEFT) {
|
||||
action = Ime::ExternalAction::MoveLeft;
|
||||
} else if (event.key.keysym.sym == SDLK_RIGHT) {
|
||||
} else if (event.key.key == SDLK_RIGHT) {
|
||||
action = Ime::ExternalAction::MoveRight;
|
||||
} else if (event.key.keysym.sym == SDLK_ESCAPE) {
|
||||
} else if (event.key.key == SDLK_ESCAPE) {
|
||||
action = Ime::ExternalAction::Cancel;
|
||||
} else if (event.key.keysym.sym == SDLK_RETURN ||
|
||||
event.key.keysym.sym == SDLK_KP_ENTER) {
|
||||
} else if (event.key.key == SDLK_RETURN || event.key.key == SDLK_KP_ENTER) {
|
||||
action =
|
||||
g_input_multiline ? Ime::ExternalAction::Newline : Ime::ExternalAction::Accept;
|
||||
} else if (event.key.keysym.sym == SDLK_TAB) {
|
||||
} else if (event.key.key == SDLK_TAB) {
|
||||
action = Ime::ExternalAction::None;
|
||||
} else {
|
||||
queue = false;
|
||||
@@ -566,28 +569,28 @@ bool ProcessSystemOverlayInput(const SDL_Event& event) {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
case SDL_KEYUP: return true;
|
||||
case SDL_CONTROLLERBUTTONDOWN:
|
||||
case SDL_CONTROLLERBUTTONUP:
|
||||
QueueInput({InputKind::Button, session, event.cbutton.button,
|
||||
event.type == SDL_CONTROLLERBUTTONDOWN ? 1.0f : 0.0f, 0.0f});
|
||||
case SDL_EVENT_KEY_UP: return true;
|
||||
case SDL_EVENT_GAMEPAD_BUTTON_DOWN:
|
||||
case SDL_EVENT_GAMEPAD_BUTTON_UP:
|
||||
QueueInput({InputKind::Button, session, event.gbutton.button,
|
||||
event.type == SDL_EVENT_GAMEPAD_BUTTON_DOWN ? 1.0f : 0.0f, 0.0f});
|
||||
return true;
|
||||
case SDL_CONTROLLERAXISMOTION:
|
||||
QueueInput({InputKind::Axis, session, event.caxis.axis,
|
||||
static_cast<float>(event.caxis.value), 0.0f});
|
||||
case SDL_EVENT_GAMEPAD_AXIS_MOTION:
|
||||
QueueInput({InputKind::Axis, session, event.gaxis.axis,
|
||||
static_cast<float>(event.gaxis.value), 0.0f});
|
||||
return true;
|
||||
case SDL_MOUSEMOTION:
|
||||
case SDL_EVENT_MOUSE_MOTION:
|
||||
QueueInput({InputKind::MousePosition, session, 0, static_cast<float>(event.motion.x),
|
||||
static_cast<float>(event.motion.y)});
|
||||
return true;
|
||||
case SDL_MOUSEBUTTONDOWN:
|
||||
case SDL_MOUSEBUTTONUP:
|
||||
case SDL_EVENT_MOUSE_BUTTON_DOWN:
|
||||
case SDL_EVENT_MOUSE_BUTTON_UP:
|
||||
QueueInput({InputKind::MousePosition, session, 0, static_cast<float>(event.button.x),
|
||||
static_cast<float>(event.button.y)});
|
||||
QueueInput({InputKind::MouseButton, session, event.button.button,
|
||||
event.type == SDL_MOUSEBUTTONDOWN ? 1.0f : 0.0f, 0.0f});
|
||||
event.type == SDL_EVENT_MOUSE_BUTTON_DOWN ? 1.0f : 0.0f, 0.0f});
|
||||
return true;
|
||||
case SDL_MOUSEWHEEL:
|
||||
case SDL_EVENT_MOUSE_WHEEL:
|
||||
QueueInput({InputKind::MouseWheel, session, 0, static_cast<float>(event.wheel.x),
|
||||
static_cast<float>(event.wheel.y)});
|
||||
return true;
|
||||
@@ -681,9 +684,9 @@ struct SystemOverlay::Impl {
|
||||
case InputKind::Button: {
|
||||
const bool down = event.x != 0.0f;
|
||||
if (session.kind == OverlayKind::Ime && down) {
|
||||
if (event.id == SDL_CONTROLLER_BUTTON_B) {
|
||||
if (event.id == SDL_GAMEPAD_BUTTON_EAST) {
|
||||
Ime::HostCancel(session.generation);
|
||||
} else if (event.id == SDL_CONTROLLER_BUTTON_Y) {
|
||||
} else if (event.id == SDL_GAMEPAD_BUTTON_NORTH) {
|
||||
Ime::HostBackspace(session.generation);
|
||||
}
|
||||
}
|
||||
@@ -695,19 +698,19 @@ struct SystemOverlay::Impl {
|
||||
}
|
||||
case InputKind::Axis: {
|
||||
const float value = event.x / (event.x < 0.0f ? 32768.0f : 32767.0f);
|
||||
if (event.id == SDL_CONTROLLER_AXIS_LEFTX) {
|
||||
if (event.id == SDL_GAMEPAD_AXIS_LEFTX) {
|
||||
io.AddKeyAnalogEvent(ImGuiKey_GamepadLStickLeft, value < -0.25f,
|
||||
std::max(-value, 0.0f));
|
||||
io.AddKeyAnalogEvent(ImGuiKey_GamepadLStickRight, value > 0.25f,
|
||||
std::max(value, 0.0f));
|
||||
} else if (event.id == SDL_CONTROLLER_AXIS_LEFTY) {
|
||||
} else if (event.id == SDL_GAMEPAD_AXIS_LEFTY) {
|
||||
io.AddKeyAnalogEvent(ImGuiKey_GamepadLStickUp, value < -0.25f,
|
||||
std::max(-value, 0.0f));
|
||||
io.AddKeyAnalogEvent(ImGuiKey_GamepadLStickDown, value > 0.25f,
|
||||
std::max(value, 0.0f));
|
||||
} else if (event.id == SDL_CONTROLLER_AXIS_RIGHTX) {
|
||||
} else if (event.id == SDL_GAMEPAD_AXIS_RIGHTX) {
|
||||
right_stick.x = std::abs(value) > 0.2f ? value : 0.0f;
|
||||
} else if (event.id == SDL_CONTROLLER_AXIS_RIGHTY) {
|
||||
} else if (event.id == SDL_GAMEPAD_AXIS_RIGHTY) {
|
||||
right_stick.y = std::abs(value) > 0.2f ? value : 0.0f;
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <memory>
|
||||
|
||||
union SDL_Event;
|
||||
struct SDL_Window;
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
@@ -17,7 +18,7 @@ struct SystemOverlayVisualState {
|
||||
uint64_t revision;
|
||||
};
|
||||
|
||||
void InitializeSystemOverlayInput();
|
||||
void InitializeSystemOverlayInput(SDL_Window* window);
|
||||
void ShutdownSystemOverlayInput();
|
||||
bool ProcessSystemOverlayInput(const SDL_Event& event);
|
||||
SystemOverlayVisualState GetSystemOverlayVisualState() noexcept;
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include "kernel/pthread.h"
|
||||
#include "libs/errno.h"
|
||||
#include "libs/libs.h"
|
||||
#include "loader/systemContent.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
@@ -1654,8 +1655,12 @@ KYTY_SYSV_ABI int VideoOutGetOutputStatus(int handle, VideoOutOutputStatus* stat
|
||||
return VIDEO_OUT_ERROR_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
int32_t attribute3 = 0;
|
||||
Loader::SystemContentParamSfoGetInt("ATTRIBUTE3", &attribute3);
|
||||
ctx->mutex.Lock();
|
||||
status->resolution = (ctx->width >= 3840 || ctx->height >= 2160 ? 2u : 1u);
|
||||
// Primary output reports 4K unless param.json Video-out Info enables resolution detection.
|
||||
status->resolution =
|
||||
((attribute3 & 4) != 0 && ctx->width < 3840 && ctx->height < 2160 ? 1u : 2u);
|
||||
status->dynamicRange = 1;
|
||||
status->refreshRate =
|
||||
(ctx->output_mode == VIDEO_OUT_OUTPUT_MODE_119_88HZ || Config::GetVblankFrequency() >= 119
|
||||
@@ -1670,6 +1675,21 @@ KYTY_SYSV_ABI int VideoOutGetOutputStatus(int handle, VideoOutOutputStatus* stat
|
||||
return OK;
|
||||
}
|
||||
|
||||
KYTY_SYSV_ABI int VideoOutGetVrrStatus(int handle, int32_t* status) {
|
||||
PRINT_NAME();
|
||||
|
||||
if (status == nullptr) {
|
||||
return VIDEO_OUT_ERROR_INVALID_ADDRESS;
|
||||
}
|
||||
if (DriverState().Get(handle) == nullptr) {
|
||||
return VIDEO_OUT_ERROR_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
// Kyty currently presents at a fixed refresh rate and does not negotiate VRR.
|
||||
*status = 0;
|
||||
return OK;
|
||||
}
|
||||
|
||||
static int ValidateOutputConfig(int handle, uint64_t mode, const VideoOutOutputOptions* options,
|
||||
void* reserved_ptr, uint64_t reserved) {
|
||||
if (!DriverState().IsOpened(handle)) {
|
||||
|
||||
@@ -87,6 +87,7 @@ KYTY_SYSV_ABI int VideoOutGetEventData(const LibKernel::EventQueue::KernelEvent*
|
||||
KYTY_SYSV_ABI int VideoOutGetEventCount(const LibKernel::EventQueue::KernelEvent* ev);
|
||||
KYTY_SYSV_ABI int VideoOutWaitVblank(int handle);
|
||||
KYTY_SYSV_ABI int VideoOutGetOutputStatus(int handle, VideoOutOutputStatus* status);
|
||||
KYTY_SYSV_ABI int VideoOutGetVrrStatus(int handle, int32_t* status);
|
||||
KYTY_SYSV_ABI int VideoOutInitializeOutputOptions(VideoOutOutputOptions* options);
|
||||
KYTY_SYSV_ABI int VideoOutIsOutputSupported(int handle, uint64_t mode,
|
||||
const VideoOutOutputOptions* options,
|
||||
|
||||
@@ -1,12 +1,7 @@
|
||||
#include "graphics/presentation/window/hostInput.h"
|
||||
|
||||
#include "SDL_error.h"
|
||||
#include "SDL_events.h"
|
||||
#include "SDL_keyboard.h"
|
||||
#include "SDL_keycode.h"
|
||||
#include "SDL_mouse.h"
|
||||
#include "SDL_timer.h"
|
||||
#include "SDL_video.h"
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/emulatorConfig.h"
|
||||
#include "common/logging/log.h"
|
||||
@@ -80,6 +75,7 @@ struct MouseJoystickState {
|
||||
};
|
||||
|
||||
MouseJoystickState g_mouse;
|
||||
SDL_Window* g_mouse_window = nullptr;
|
||||
|
||||
std::size_t ControlFromName(std::string_view name) {
|
||||
const auto info = std::find_if(CONTROL_INFO.begin(), CONTROL_INFO.end(),
|
||||
@@ -141,9 +137,8 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
const bool reserved = binding.key == SDLK_ESCAPE || binding.key == SDLK_SPACE ||
|
||||
binding.key == SDLK_F1 || binding.key == SDLK_F7 ||
|
||||
binding.key == SDLK_F11;
|
||||
const bool reserved = binding.key == SDLK_ESCAPE || binding.key == SDLK_F1 ||
|
||||
binding.key == SDLK_F7 || binding.key == SDLK_F11;
|
||||
if (binding.control == INVALID_CONTROL || reserved ||
|
||||
(binding.key == SDLK_UNKNOWN && binding.mouse_button == 0)) {
|
||||
EXIT("Invalid input mapping: %s\n", value.c_str());
|
||||
@@ -214,14 +209,14 @@ uint32_t DefaultKeyboardButton(int key_code) {
|
||||
case SDLK_LEFT: return Controller::PAD_BUTTON_LEFT;
|
||||
case SDLK_DOWN: return Controller::PAD_BUTTON_DOWN;
|
||||
case SDLK_RIGHT: return Controller::PAD_BUTTON_RIGHT;
|
||||
case SDLK_j: return Controller::PAD_BUTTON_CROSS;
|
||||
case SDLK_i: return Controller::PAD_BUTTON_TRIANGLE;
|
||||
case SDLK_k: return Controller::PAD_BUTTON_SQUARE;
|
||||
case SDLK_l: return Controller::PAD_BUTTON_CIRCLE;
|
||||
case SDLK_q: return Controller::PAD_BUTTON_L1;
|
||||
case SDLK_e: return Controller::PAD_BUTTON_R1;
|
||||
case SDLK_z: return Controller::PAD_BUTTON_L2;
|
||||
case SDLK_c: return Controller::PAD_BUTTON_R2;
|
||||
case SDLK_J: return Controller::PAD_BUTTON_CROSS;
|
||||
case SDLK_I: return Controller::PAD_BUTTON_TRIANGLE;
|
||||
case SDLK_K: return Controller::PAD_BUTTON_SQUARE;
|
||||
case SDLK_L: return Controller::PAD_BUTTON_CIRCLE;
|
||||
case SDLK_Q: return Controller::PAD_BUTTON_L1;
|
||||
case SDLK_E: return Controller::PAD_BUTTON_R1;
|
||||
case SDLK_Z: return Controller::PAD_BUTTON_L2;
|
||||
case SDLK_C: return Controller::PAD_BUTTON_R2;
|
||||
case SDLK_LSHIFT: return Controller::PAD_BUTTON_L3;
|
||||
case SDLK_LCTRL: return Controller::PAD_BUTTON_R3;
|
||||
case SDLK_RETURN:
|
||||
@@ -285,35 +280,35 @@ void DefaultKeyboardInput(int key_code, bool down) {
|
||||
switch (NormalizeKey(static_cast<SDL_Keycode>(key_code))) {
|
||||
case SDLK_BACKSPACE: SetTouchPad(0.25f, down); return;
|
||||
case SDLK_TAB: SetTouchPad(0.75f, down); return;
|
||||
case SDLK_a:
|
||||
case SDLK_A:
|
||||
left.left = down;
|
||||
SetStickAxis(Controller::Axis::LeftX, left.left, left.right);
|
||||
return;
|
||||
case SDLK_d:
|
||||
case SDLK_D:
|
||||
left.right = down;
|
||||
SetStickAxis(Controller::Axis::LeftX, left.left, left.right);
|
||||
return;
|
||||
case SDLK_w:
|
||||
case SDLK_W:
|
||||
left.up = down;
|
||||
SetStickAxis(Controller::Axis::LeftY, left.up, left.down);
|
||||
return;
|
||||
case SDLK_s:
|
||||
case SDLK_S:
|
||||
left.down = down;
|
||||
SetStickAxis(Controller::Axis::LeftY, left.up, left.down);
|
||||
return;
|
||||
case SDLK_f:
|
||||
case SDLK_F:
|
||||
right.left = down;
|
||||
SetStickAxis(Controller::Axis::RightX, right.left, right.right);
|
||||
return;
|
||||
case SDLK_h:
|
||||
case SDLK_H:
|
||||
right.right = down;
|
||||
SetStickAxis(Controller::Axis::RightX, right.left, right.right);
|
||||
return;
|
||||
case SDLK_t:
|
||||
case SDLK_T:
|
||||
right.up = down;
|
||||
SetStickAxis(Controller::Axis::RightY, right.up, right.down);
|
||||
return;
|
||||
case SDLK_g:
|
||||
case SDLK_G:
|
||||
right.down = down;
|
||||
SetStickAxis(Controller::Axis::RightY, right.up, right.down);
|
||||
return;
|
||||
@@ -321,11 +316,11 @@ void DefaultKeyboardInput(int key_code, bool down) {
|
||||
}
|
||||
}
|
||||
|
||||
void MouseToJoystick(int delta_x, int delta_y) {
|
||||
void MouseToJoystick(float delta_x, float delta_y) {
|
||||
const double distance = std::hypot(delta_x, delta_y);
|
||||
const double scale =
|
||||
std::clamp(distance * GetInputMap().MouseSensitivity() + 16.0, 64.0, 128.0) / distance;
|
||||
const auto map_axis = [scale](int delta) {
|
||||
const auto map_axis = [scale](float delta) {
|
||||
return std::clamp(128 + static_cast<int>(std::lround(delta * scale)), 0, 255);
|
||||
};
|
||||
Controller::SetRightStick(Controller::HOST_INPUT_CONTROLLER_ID, map_axis(delta_x),
|
||||
@@ -341,16 +336,45 @@ void CenterMouseStick() {
|
||||
}
|
||||
|
||||
bool SetRelativeMouseMode(bool enabled) {
|
||||
if (SDL_SetRelativeMouseMode(enabled ? SDL_TRUE : SDL_FALSE) == 0) {
|
||||
if (SDL_SetWindowRelativeMouseMode(g_mouse_window, enabled)) {
|
||||
return true;
|
||||
}
|
||||
LOGF("Mouse-to-joystick relative mode failed: %s\n", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
int PollMouse(uint64_t now_ms) {
|
||||
if (now_ms < g_mouse.next_poll) {
|
||||
return static_cast<int>(g_mouse.next_poll - now_ms);
|
||||
}
|
||||
g_mouse.next_poll = now_ms + MOUSE_POLL_INTERVAL_MS;
|
||||
|
||||
float delta_x = 0;
|
||||
float delta_y = 0;
|
||||
SDL_GetRelativeMouseState(&delta_x, &delta_y);
|
||||
if (delta_x == 0 && delta_y == 0) {
|
||||
CenterMouseStick();
|
||||
} else {
|
||||
MouseToJoystick(delta_x, delta_y);
|
||||
g_mouse.output = true;
|
||||
}
|
||||
return MOUSE_POLL_INTERVAL_MS;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void HostInputInit() {
|
||||
void HostInputInit(SDL_Window* window) {
|
||||
GetInputMap();
|
||||
g_mouse_window = window;
|
||||
}
|
||||
|
||||
void HostInputShutdown() {
|
||||
if (g_mouse.enabled) {
|
||||
SetRelativeMouseMode(false);
|
||||
CenterMouseStick();
|
||||
g_mouse = {};
|
||||
}
|
||||
g_mouse_window = nullptr;
|
||||
}
|
||||
|
||||
void HostInputKey(int key_code, bool down) {
|
||||
@@ -381,51 +405,33 @@ void HostInputToggleMouseToJoystick() {
|
||||
if (!SetRelativeMouseMode(true)) {
|
||||
return;
|
||||
}
|
||||
int ignored_x = 0;
|
||||
int ignored_y = 0;
|
||||
SDL_GetRelativeMouseState(&ignored_x, &ignored_y);
|
||||
g_mouse.enabled = true;
|
||||
g_mouse.next_poll = SDL_GetTicks64() + MOUSE_POLL_INTERVAL_MS;
|
||||
g_mouse.enabled = true;
|
||||
LOGF("Mouse to right stick: enabled (F7 to release)\n");
|
||||
}
|
||||
|
||||
int PollMouse(uint64_t now_ms) {
|
||||
if (now_ms < g_mouse.next_poll) {
|
||||
return static_cast<int>(g_mouse.next_poll - now_ms);
|
||||
}
|
||||
g_mouse.next_poll = now_ms + MOUSE_POLL_INTERVAL_MS;
|
||||
|
||||
int delta_x = 0;
|
||||
int delta_y = 0;
|
||||
SDL_GetRelativeMouseState(&delta_x, &delta_y);
|
||||
if (delta_x == 0 && delta_y == 0) {
|
||||
CenterMouseStick();
|
||||
return MOUSE_POLL_INTERVAL_MS;
|
||||
}
|
||||
|
||||
MouseToJoystick(delta_x, delta_y);
|
||||
g_mouse.output = true;
|
||||
return MOUSE_POLL_INTERVAL_MS;
|
||||
}
|
||||
|
||||
bool HostInputWaitEvent(SDL_Event* event) {
|
||||
if (!g_mouse.enabled || SDL_GetKeyboardFocus() == nullptr) {
|
||||
bool has_event;
|
||||
if (!g_mouse.enabled || SDL_GetKeyboardFocus() != g_mouse_window) {
|
||||
g_mouse.next_poll = 0;
|
||||
CenterMouseStick();
|
||||
if (SDL_WaitEvent(event) == 0) {
|
||||
has_event = SDL_WaitEvent(event);
|
||||
if (!has_event) {
|
||||
EXIT("%s\n", SDL_GetError());
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
if (g_mouse.next_poll == 0) {
|
||||
SDL_GetRelativeMouseState(nullptr, nullptr);
|
||||
g_mouse.next_poll = SDL_GetTicks() + MOUSE_POLL_INTERVAL_MS;
|
||||
}
|
||||
has_event = SDL_WaitEventTimeout(event, PollMouse(SDL_GetTicks()));
|
||||
}
|
||||
|
||||
const int timeout_ms = PollMouse(SDL_GetTicks64());
|
||||
SDL_ClearError();
|
||||
if (SDL_WaitEventTimeout(event, timeout_ms) != 0) {
|
||||
return true;
|
||||
if (has_event && event->type == SDL_EVENT_WINDOW_FOCUS_LOST &&
|
||||
event->window.windowID == SDL_GetWindowID(g_mouse_window)) {
|
||||
g_mouse.next_poll = 0;
|
||||
CenterMouseStick();
|
||||
}
|
||||
if (SDL_GetError()[0] != '\0') {
|
||||
EXIT("%s\n", SDL_GetError());
|
||||
}
|
||||
return false;
|
||||
return has_event;
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
@@ -3,11 +3,13 @@
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
struct SDL_Window;
|
||||
union SDL_Event;
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
void HostInputInit();
|
||||
void HostInputInit(SDL_Window* window);
|
||||
void HostInputShutdown();
|
||||
void HostInputKey(int key_code, bool down);
|
||||
void HostInputMouseButton(uint8_t mouse_button, bool down);
|
||||
void HostInputToggleMouseToJoystick();
|
||||
|
||||
@@ -279,6 +279,7 @@ public:
|
||||
|
||||
void Create();
|
||||
void Recreate(bool surface_lost = false);
|
||||
[[nodiscard]] bool NeedsResize() const;
|
||||
[[nodiscard]] Status AcquireNextImage();
|
||||
[[nodiscard]] bool PrepareSystemOverlay();
|
||||
void RecordPresentCommands(CommandBuffer& command, VulkanImage& source,
|
||||
@@ -298,6 +299,8 @@ private:
|
||||
vk::SwapchainKHR m_handle = nullptr;
|
||||
vk::Format m_format = vk::Format::eUndefined;
|
||||
vk::Extent2D m_extent {};
|
||||
// Drawable pixel size observed when this swapchain was created.
|
||||
vk::Extent2D m_window_extent {};
|
||||
std::vector<vk::Image> m_images;
|
||||
std::vector<vk::ImageView> m_image_views;
|
||||
std::vector<vk::Semaphore> m_image_acquired;
|
||||
@@ -357,19 +360,25 @@ void Swapchain::Create() {
|
||||
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);
|
||||
// Capture the size before querying surface capabilities. If the window changes
|
||||
// during creation, the next presentation will detect the newer size.
|
||||
{
|
||||
Common::LockGuard lock(m_window.mutex);
|
||||
m_window_extent = {graphics.screen_width, graphics.screen_height};
|
||||
}
|
||||
EXIT_IF(m_window_extent.width == 0);
|
||||
EXIT_IF(m_window_extent.height == 0);
|
||||
m_window.RefreshSurfaceCapabilities();
|
||||
const auto& surface = m_window.surface_capabilities;
|
||||
EXIT_NOT_IMPLEMENTED(surface.formats.empty());
|
||||
|
||||
m_extent = surface.capabilities.currentExtent;
|
||||
if (m_extent.width == std::numeric_limits<uint32_t>::max()) {
|
||||
m_extent.width =
|
||||
std::clamp(graphics.screen_width, surface.capabilities.minImageExtent.width,
|
||||
std::clamp(m_window_extent.width, surface.capabilities.minImageExtent.width,
|
||||
surface.capabilities.maxImageExtent.width);
|
||||
m_extent.height =
|
||||
std::clamp(graphics.screen_height, surface.capabilities.minImageExtent.height,
|
||||
std::clamp(m_window_extent.height, surface.capabilities.minImageExtent.height,
|
||||
surface.capabilities.maxImageExtent.height);
|
||||
}
|
||||
uint32_t image_count = surface.capabilities.minImageCount + 1;
|
||||
@@ -473,8 +482,8 @@ void Swapchain::Create() {
|
||||
graphics.device.createSemaphore(&semaphore_info, nullptr, &m_render_complete[i]),
|
||||
"create swapchain render-complete semaphore");
|
||||
}
|
||||
m_image_index = static_cast<uint32_t>(-1);
|
||||
m_frame_index = 0;
|
||||
m_image_index = static_cast<uint32_t>(-1);
|
||||
m_frame_index = 0;
|
||||
}
|
||||
|
||||
Swapchain::~Swapchain() {
|
||||
@@ -515,11 +524,12 @@ void Swapchain::Destroy() {
|
||||
graphics.device.destroySwapchainKHR(m_handle, nullptr);
|
||||
}
|
||||
|
||||
m_handle = nullptr;
|
||||
m_format = vk::Format::eUndefined;
|
||||
m_extent = {};
|
||||
m_image_index = static_cast<uint32_t>(-1);
|
||||
m_frame_index = 0;
|
||||
m_handle = nullptr;
|
||||
m_format = vk::Format::eUndefined;
|
||||
m_extent = {};
|
||||
m_window_extent = {};
|
||||
m_image_index = static_cast<uint32_t>(-1);
|
||||
m_frame_index = 0;
|
||||
m_images.clear();
|
||||
m_image_views.clear();
|
||||
m_image_acquired.clear();
|
||||
@@ -532,15 +542,22 @@ void Swapchain::Recreate(bool 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(); });
|
||||
EXIT_IF(!SDL_RunOnMainThread(
|
||||
[](void* window) { static_cast<WindowContext*>(window)->RecreateSurface(); },
|
||||
&m_window, true));
|
||||
#else
|
||||
m_window.RecreateSurface();
|
||||
#endif
|
||||
}
|
||||
m_window.RefreshSurfaceCapabilities();
|
||||
Create();
|
||||
}
|
||||
|
||||
bool Swapchain::NeedsResize() const {
|
||||
Common::LockGuard lock(m_window.mutex);
|
||||
return m_window_extent.width != m_window.graphic_ctx.screen_width ||
|
||||
m_window_extent.height != m_window.graphic_ctx.screen_height;
|
||||
}
|
||||
|
||||
Swapchain::Status Swapchain::AcquireNextImage() {
|
||||
EXIT_IF(m_handle == nullptr || m_frame_index >= m_image_acquired.size());
|
||||
m_image_index = static_cast<uint32_t>(-1);
|
||||
@@ -766,6 +783,10 @@ void Presenter::Present(Frame& frame, bool reuse) {
|
||||
|
||||
const auto overlay_visual = GetSystemOverlayVisualState();
|
||||
auto& swapchain = m_impl->swapchain;
|
||||
// Some window systems keep presenting an old swapchain after a resize.
|
||||
if (swapchain.NeedsResize()) {
|
||||
m_impl->RecoverSwapchain(Swapchain::Status::Recreate);
|
||||
}
|
||||
for (uint32_t attempt = 0; attempt < 2; attempt++) {
|
||||
auto status = swapchain.AcquireNextImage();
|
||||
if (status != Swapchain::Status::Success) {
|
||||
|
||||
@@ -1,20 +1,6 @@
|
||||
#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 <SDL3/SDL.h>
|
||||
#include <SDL3/SDL_vulkan.h>
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/common.h"
|
||||
#include "common/emulatorConfig.h"
|
||||
@@ -610,10 +596,11 @@ static vk::Device VulkanCreateDevice(GraphicContext& graphics,
|
||||
graphics.mesh_shader_properties.maxMeshOutputPrimitives,
|
||||
graphics.mesh_shader_properties.maxMeshSharedMemorySize);
|
||||
}
|
||||
// VulkanFindPhysicalDevice already checked the required creation features. These two
|
||||
// VulkanFindPhysicalDevice already checked the required creation features. These
|
||||
// requirements are specific to this creation path and are not part of device selection.
|
||||
EXIT_NOT_IMPLEMENTED(supported_features2.features.shaderInt64 != VK_TRUE);
|
||||
EXIT_NOT_IMPLEMENTED(supported_features2.features.vertexPipelineStoresAndAtomics != VK_TRUE);
|
||||
EXIT_NOT_IMPLEMENTED(supported_features2.features.dualSrcBlend != VK_TRUE);
|
||||
vk::PhysicalDeviceFeatures device_features {};
|
||||
device_features.fragmentStoresAndAtomics = VK_TRUE;
|
||||
device_features.samplerAnisotropy = VK_TRUE;
|
||||
@@ -624,6 +611,7 @@ static vk::Device VulkanCreateDevice(GraphicContext& graphics,
|
||||
device_features.shaderStorageImageWriteWithoutFormat = VK_TRUE;
|
||||
device_features.shaderImageGatherExtended = VK_TRUE;
|
||||
device_features.independentBlend = VK_TRUE;
|
||||
device_features.dualSrcBlend = VK_TRUE;
|
||||
device_features.tessellationShader = VK_TRUE;
|
||||
device_features.sampleRateShading = VK_TRUE;
|
||||
device_features.depthBiasClamp = VK_TRUE;
|
||||
@@ -698,25 +686,13 @@ static vk::Device VulkanCreateDevice(GraphicContext& graphics,
|
||||
return device;
|
||||
}
|
||||
|
||||
static void VulkanGetExtensions(SDL_Window* window, VulkanExtensions& r) {
|
||||
EXIT_IF(window == nullptr);
|
||||
|
||||
static void VulkanGetExtensions(VulkanExtensions& r) {
|
||||
uint32_t required_extensions_count = 0;
|
||||
|
||||
auto sdl_result = SDL_Vulkan_GetInstanceExtensions(window, &required_extensions_count, nullptr);
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(sdl_result == SDL_FALSE);
|
||||
const char* const* extensions = SDL_Vulkan_GetInstanceExtensions(&required_extensions_count);
|
||||
EXIT_NOT_IMPLEMENTED(extensions == nullptr);
|
||||
EXIT_NOT_IMPLEMENTED(required_extensions_count == 0);
|
||||
|
||||
r.required_extensions =
|
||||
std::vector<const char*>(required_extensions_count); // @suppress("Ambiguous problem")
|
||||
|
||||
sdl_result = SDL_Vulkan_GetInstanceExtensions(window, &required_extensions_count,
|
||||
r.required_extensions.data());
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(sdl_result == SDL_FALSE);
|
||||
EXIT_NOT_IMPLEMENTED(required_extensions_count == 0);
|
||||
EXIT_NOT_IMPLEMENTED(required_extensions_count != r.required_extensions.size());
|
||||
r.required_extensions.assign(extensions, extensions + required_extensions_count);
|
||||
|
||||
r.available_extensions =
|
||||
EnumerateVulkan<vk::ExtensionProperties>( // @suppress("Ambiguous problem")
|
||||
@@ -897,7 +873,7 @@ void WindowContext::CreateVulkan() {
|
||||
VULKAN_HPP_DEFAULT_DISPATCHER.init(get_instance_proc_addr);
|
||||
|
||||
VulkanExtensions r;
|
||||
VulkanGetExtensions(window, r);
|
||||
VulkanGetExtensions(r);
|
||||
VulkanCheckInstanceVersion();
|
||||
|
||||
vk::ApplicationInfo app_info {};
|
||||
@@ -984,8 +960,8 @@ void WindowContext::CreateVulkan() {
|
||||
}
|
||||
|
||||
vk::SurfaceKHR::CType native_surface = VK_NULL_HANDLE;
|
||||
if (SDL_Vulkan_CreateSurface(window, static_cast<vk::Instance::CType>(graphic_ctx.instance),
|
||||
&native_surface) == SDL_FALSE) {
|
||||
if (!SDL_Vulkan_CreateSurface(window, static_cast<vk::Instance::CType>(graphic_ctx.instance),
|
||||
nullptr, &native_surface)) {
|
||||
EXIT("Could not create a Vulkan surface");
|
||||
}
|
||||
surface = native_surface;
|
||||
@@ -1098,8 +1074,8 @@ void WindowContext::RecreateSurface() {
|
||||
surface = nullptr;
|
||||
}
|
||||
vk::SurfaceKHR::CType native_surface = VK_NULL_HANDLE;
|
||||
if (SDL_Vulkan_CreateSurface(window, static_cast<vk::Instance::CType>(graphic_ctx.instance),
|
||||
&native_surface) == SDL_FALSE) {
|
||||
if (!SDL_Vulkan_CreateSurface(window, static_cast<vk::Instance::CType>(graphic_ctx.instance),
|
||||
nullptr, &native_surface)) {
|
||||
EXIT("Could not recreate the Vulkan surface: %s\n", SDL_GetError());
|
||||
}
|
||||
surface = native_surface;
|
||||
@@ -1135,7 +1111,7 @@ WindowContext::~WindowContext() {
|
||||
SDL_DestroyWindow(window);
|
||||
window = nullptr;
|
||||
}
|
||||
SDL_QuitSubSystem(SDL_INIT_VIDEO | SDL_INIT_GAMECONTROLLER);
|
||||
SDL_QuitSubSystem(SDL_INIT_VIDEO | SDL_INIT_GAMEPAD);
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
@@ -1,23 +1,7 @@
|
||||
#include "graphics/presentation/window.h"
|
||||
|
||||
#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"
|
||||
#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 <SDL3/SDL.h>
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/common.h"
|
||||
#include "common/emulatorConfig.h"
|
||||
@@ -27,6 +11,7 @@
|
||||
#include "common/systemInfo.h"
|
||||
#include "common/threads.h"
|
||||
#include "common/timer.h"
|
||||
#include "common/stringUtils.h"
|
||||
#include "graphics/host_gpu/graphicContext.h"
|
||||
#include "graphics/host_gpu/renderer/render.h"
|
||||
#include "graphics/host_gpu/renderer/renderContext.h"
|
||||
@@ -45,6 +30,7 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <vulkan/vk_platform.h>
|
||||
#include <filesystem>
|
||||
|
||||
#define STB_IMAGE_IMPLEMENTATION
|
||||
#define STBI_NO_SIMD
|
||||
@@ -70,39 +56,39 @@ struct EventKeyboard {
|
||||
|
||||
static uint32_t ControllerButtonToPadButton(int button) {
|
||||
switch (button) {
|
||||
case SDL_CONTROLLER_BUTTON_A: return Controller::PAD_BUTTON_CROSS;
|
||||
case SDL_CONTROLLER_BUTTON_B: return Controller::PAD_BUTTON_CIRCLE;
|
||||
case SDL_CONTROLLER_BUTTON_X: return Controller::PAD_BUTTON_SQUARE;
|
||||
case SDL_CONTROLLER_BUTTON_Y: return Controller::PAD_BUTTON_TRIANGLE;
|
||||
case SDL_CONTROLLER_BUTTON_START: return Controller::PAD_BUTTON_OPTIONS;
|
||||
case SDL_CONTROLLER_BUTTON_LEFTSTICK: return Controller::PAD_BUTTON_L3;
|
||||
case SDL_CONTROLLER_BUTTON_RIGHTSTICK: return Controller::PAD_BUTTON_R3;
|
||||
case SDL_CONTROLLER_BUTTON_LEFTSHOULDER: return Controller::PAD_BUTTON_L1;
|
||||
case SDL_CONTROLLER_BUTTON_RIGHTSHOULDER: return Controller::PAD_BUTTON_R1;
|
||||
case SDL_CONTROLLER_BUTTON_DPAD_UP: return Controller::PAD_BUTTON_UP;
|
||||
case SDL_CONTROLLER_BUTTON_DPAD_DOWN: return Controller::PAD_BUTTON_DOWN;
|
||||
case SDL_CONTROLLER_BUTTON_DPAD_LEFT: return Controller::PAD_BUTTON_LEFT;
|
||||
case SDL_CONTROLLER_BUTTON_DPAD_RIGHT: return Controller::PAD_BUTTON_RIGHT;
|
||||
case SDL_CONTROLLER_BUTTON_TOUCHPAD: return Controller::PAD_BUTTON_TOUCH_PAD;
|
||||
case SDL_GAMEPAD_BUTTON_SOUTH: return Controller::PAD_BUTTON_CROSS;
|
||||
case SDL_GAMEPAD_BUTTON_EAST: return Controller::PAD_BUTTON_CIRCLE;
|
||||
case SDL_GAMEPAD_BUTTON_WEST: return Controller::PAD_BUTTON_SQUARE;
|
||||
case SDL_GAMEPAD_BUTTON_NORTH: return Controller::PAD_BUTTON_TRIANGLE;
|
||||
case SDL_GAMEPAD_BUTTON_START: return Controller::PAD_BUTTON_OPTIONS;
|
||||
case SDL_GAMEPAD_BUTTON_LEFT_STICK: return Controller::PAD_BUTTON_L3;
|
||||
case SDL_GAMEPAD_BUTTON_RIGHT_STICK: return Controller::PAD_BUTTON_R3;
|
||||
case SDL_GAMEPAD_BUTTON_LEFT_SHOULDER: return Controller::PAD_BUTTON_L1;
|
||||
case SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER: return Controller::PAD_BUTTON_R1;
|
||||
case SDL_GAMEPAD_BUTTON_DPAD_UP: return Controller::PAD_BUTTON_UP;
|
||||
case SDL_GAMEPAD_BUTTON_DPAD_DOWN: return Controller::PAD_BUTTON_DOWN;
|
||||
case SDL_GAMEPAD_BUTTON_DPAD_LEFT: return Controller::PAD_BUTTON_LEFT;
|
||||
case SDL_GAMEPAD_BUTTON_DPAD_RIGHT: return Controller::PAD_BUTTON_RIGHT;
|
||||
case SDL_GAMEPAD_BUTTON_TOUCHPAD: return Controller::PAD_BUTTON_TOUCH_PAD;
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static Controller::Axis ControllerAxisFromSdl(int axis_id) {
|
||||
switch (axis_id) {
|
||||
case SDL_CONTROLLER_AXIS_LEFTX: return Controller::Axis::LeftX;
|
||||
case SDL_CONTROLLER_AXIS_LEFTY: return Controller::Axis::LeftY;
|
||||
case SDL_CONTROLLER_AXIS_RIGHTX: return Controller::Axis::RightX;
|
||||
case SDL_CONTROLLER_AXIS_RIGHTY: return Controller::Axis::RightY;
|
||||
case SDL_CONTROLLER_AXIS_TRIGGERLEFT: return Controller::Axis::TriggerLeft;
|
||||
case SDL_CONTROLLER_AXIS_TRIGGERRIGHT: return Controller::Axis::TriggerRight;
|
||||
case SDL_GAMEPAD_AXIS_LEFTX: return Controller::Axis::LeftX;
|
||||
case SDL_GAMEPAD_AXIS_LEFTY: return Controller::Axis::LeftY;
|
||||
case SDL_GAMEPAD_AXIS_RIGHTX: return Controller::Axis::RightX;
|
||||
case SDL_GAMEPAD_AXIS_RIGHTY: return Controller::Axis::RightY;
|
||||
case SDL_GAMEPAD_AXIS_LEFT_TRIGGER: return Controller::Axis::TriggerLeft;
|
||||
case SDL_GAMEPAD_AXIS_RIGHT_TRIGGER: return Controller::Axis::TriggerRight;
|
||||
default: return Controller::Axis::AxisMax;
|
||||
}
|
||||
}
|
||||
|
||||
static bool ControllerAxisIsTrigger(int axis_id) {
|
||||
return axis_id == SDL_CONTROLLER_AXIS_TRIGGERLEFT ||
|
||||
axis_id == SDL_CONTROLLER_AXIS_TRIGGERRIGHT;
|
||||
return axis_id == SDL_GAMEPAD_AXIS_LEFT_TRIGGER ||
|
||||
axis_id == SDL_GAMEPAD_AXIS_RIGHT_TRIGGER;
|
||||
}
|
||||
|
||||
static int ControllerAxisValueFromSdl(int axis_id, int axis_value) {
|
||||
@@ -163,22 +149,6 @@ struct EventController {
|
||||
double timestamp_seconds;
|
||||
};
|
||||
|
||||
enum class DisplayOrientation {
|
||||
Unknown, /* The display orientation can't be determined */
|
||||
Landscape, /* The display is in landscape mode, with the right side up, relative to portrait
|
||||
mode */
|
||||
LandscapeFlipped, /* The display is in landscape mode, with the left side up, relative to
|
||||
portrait mode */
|
||||
Portrait, /* The display is in portrait mode */
|
||||
PortraitFlipped, /* The display is in portrait mode, upside down */
|
||||
|
||||
DisplayEventOrientation = 0xF0
|
||||
};
|
||||
|
||||
struct EventDisplay {
|
||||
DisplayOrientation orientation;
|
||||
};
|
||||
|
||||
namespace {
|
||||
|
||||
std::unique_ptr<WindowContext> g_window;
|
||||
@@ -211,16 +181,13 @@ static void ToggleDesktopFullscreen() {
|
||||
}
|
||||
|
||||
const auto flags = static_cast<uint32_t>(SDL_GetWindowFlags(g_window->window));
|
||||
const bool fullscreen =
|
||||
(flags & static_cast<uint32_t>(SDL_WINDOW_FULLSCREEN_DESKTOP)) != 0u;
|
||||
const auto mode =
|
||||
fullscreen ? 0u : static_cast<uint32_t>(SDL_WINDOW_FULLSCREEN_DESKTOP);
|
||||
if (SDL_SetWindowFullscreen(g_window->window, mode) != 0) {
|
||||
const bool fullscreen = (flags & SDL_WINDOW_FULLSCREEN) != 0u;
|
||||
if (!SDL_SetWindowFullscreen(g_window->window, !fullscreen)) {
|
||||
LOGF("Toggle fullscreen failed: %s\n", SDL_GetError());
|
||||
}
|
||||
}
|
||||
|
||||
static void GameEventKeyboard(WindowLoopState& game, const EventKeyboard& key) {
|
||||
static void GameEventKeyboard(const EventKeyboard& key) {
|
||||
static SDL_Keycode fullscreen_key = SDLK_UNKNOWN;
|
||||
|
||||
#ifdef KYTY_DBG_INPUT
|
||||
@@ -233,7 +200,6 @@ static void GameEventKeyboard(WindowLoopState& game, const EventKeyboard& key) {
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS || KYTY_PLATFORM == KYTY_PLATFORM_LINUX
|
||||
if (key.down) {
|
||||
switch (key.key_code) {
|
||||
case SDLK_SPACE: SetPause(game, !game.paused.load(std::memory_order_acquire)); break;
|
||||
case SDLK_F1:
|
||||
if (!key.repeat) {
|
||||
RenderDocRequestCapture();
|
||||
@@ -246,7 +212,7 @@ static void GameEventKeyboard(WindowLoopState& game, const EventKeyboard& key) {
|
||||
break;
|
||||
case SDLK_RETURN:
|
||||
case SDLK_KP_ENTER:
|
||||
if (!key.repeat && (key.mod & KMOD_ALT) != 0) {
|
||||
if (!key.repeat && (key.mod & SDL_KMOD_ALT) != 0) {
|
||||
ToggleDesktopFullscreen();
|
||||
fullscreen_key = key.key_code;
|
||||
}
|
||||
@@ -339,15 +305,15 @@ static void GameEventController([[maybe_unused]] const EventController& f) {
|
||||
#endif
|
||||
|
||||
if (f.added) {
|
||||
auto* pad = SDL_GameControllerOpen(f.id);
|
||||
auto* pad = SDL_OpenGamepad(f.id);
|
||||
EXIT_NOT_IMPLEMENTED(pad == nullptr);
|
||||
int id = SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(pad));
|
||||
int id = SDL_GetJoystickID(SDL_GetGamepadJoystick(pad));
|
||||
Controller::Connect(id);
|
||||
}
|
||||
|
||||
if (f.removed) {
|
||||
Controller::Disconnect(f.id);
|
||||
SDL_GameControllerClose(SDL_GameControllerFromInstanceID(f.id));
|
||||
SDL_CloseGamepad(SDL_GetGamepadFromID(f.id));
|
||||
}
|
||||
|
||||
if (f.down || f.up) {
|
||||
@@ -399,81 +365,73 @@ void WindowContext::Resize(uint32_t new_width, uint32_t new_height) {
|
||||
|
||||
void WindowContext::ProcessWindowEvent(const SDL_WindowEvent& event) {
|
||||
const auto& window_event = event;
|
||||
switch (window_event.event) {
|
||||
case SDL_WINDOWEVENT_SHOWN:
|
||||
switch (window_event.type) {
|
||||
case SDL_EVENT_WINDOW_SHOWN:
|
||||
LOGF("Window %" PRIu32 " shown\n", window_event.windowID);
|
||||
break;
|
||||
|
||||
case SDL_WINDOWEVENT_HIDDEN:
|
||||
case SDL_EVENT_WINDOW_HIDDEN:
|
||||
LOGF("Window %" PRIu32 " hidden\n", window_event.windowID);
|
||||
break;
|
||||
|
||||
case SDL_WINDOWEVENT_EXPOSED:
|
||||
case SDL_EVENT_WINDOW_EXPOSED:
|
||||
LOGF("Window %" PRIu32 " exposed\n", window_event.windowID);
|
||||
break;
|
||||
|
||||
case SDL_WINDOWEVENT_MOVED:
|
||||
case SDL_EVENT_WINDOW_MOVED:
|
||||
LOGF("Window %" PRIu32 " moved to %" PRId32 ",%" PRId32 "\n", window_event.windowID,
|
||||
window_event.data1, window_event.data2);
|
||||
break;
|
||||
|
||||
case SDL_WINDOWEVENT_RESIZED:
|
||||
case SDL_EVENT_WINDOW_RESIZED:
|
||||
LOGF("Window %" PRIu32 " resized to %" PRId32 "x%" PRId32 "\n", window_event.windowID,
|
||||
window_event.data1, window_event.data2);
|
||||
|
||||
LOGF("m: %d\n", static_cast<int>(SDL_ThreadID()));
|
||||
Resize(window_event.data1, window_event.data2);
|
||||
|
||||
break;
|
||||
|
||||
case SDL_WINDOWEVENT_SIZE_CHANGED:
|
||||
case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED:
|
||||
LOGF("Window %" PRIu32 " size changed to %" PRId32 "x%" PRId32 "\n",
|
||||
window_event.windowID, window_event.data1, window_event.data2);
|
||||
|
||||
LOGF("m: %d\n", static_cast<int>(SDL_ThreadID()));
|
||||
Resize(window_event.data1, window_event.data2);
|
||||
|
||||
break;
|
||||
|
||||
case SDL_WINDOWEVENT_MINIMIZED:
|
||||
case SDL_EVENT_WINDOW_MINIMIZED:
|
||||
LOGF("Window %" PRIu32 " minimized\n", window_event.windowID);
|
||||
break;
|
||||
case SDL_WINDOWEVENT_MAXIMIZED:
|
||||
case SDL_EVENT_WINDOW_MAXIMIZED:
|
||||
LOGF("Window %" PRIu32 " maximized\n", window_event.windowID);
|
||||
break;
|
||||
case SDL_WINDOWEVENT_RESTORED:
|
||||
case SDL_EVENT_WINDOW_RESTORED:
|
||||
LOGF("Window %" PRIu32 " restored\n", window_event.windowID);
|
||||
break;
|
||||
case SDL_WINDOWEVENT_ENTER:
|
||||
case SDL_EVENT_WINDOW_MOUSE_ENTER:
|
||||
LOGF("Mouse entered window %" PRIu32 "\n", window_event.windowID);
|
||||
break;
|
||||
case SDL_WINDOWEVENT_LEAVE:
|
||||
case SDL_EVENT_WINDOW_MOUSE_LEAVE:
|
||||
LOGF("Mouse left window %" PRIu32 "\n", window_event.windowID);
|
||||
break;
|
||||
case SDL_WINDOWEVENT_FOCUS_GAINED:
|
||||
case SDL_EVENT_WINDOW_FOCUS_GAINED:
|
||||
LOGF("Window %" PRIu32 " gained keyboard focus\n", window_event.windowID);
|
||||
break;
|
||||
case SDL_WINDOWEVENT_FOCUS_LOST:
|
||||
case SDL_EVENT_WINDOW_FOCUS_LOST:
|
||||
LOGF("Window %" PRIu32 " lost keyboard focus\n", window_event.windowID);
|
||||
break;
|
||||
case SDL_WINDOWEVENT_CLOSE:
|
||||
case SDL_EVENT_WINDOW_CLOSE_REQUESTED:
|
||||
LOGF("Window %" PRIu32 " closed\n", window_event.windowID);
|
||||
break;
|
||||
default:
|
||||
LOGF("Window %" PRIu32 " got unknown event %" PRIu8 "\n", window_event.windowID,
|
||||
window_event.event);
|
||||
LOGF("Window %" PRIu32 " got unknown event %" PRIu32 "\n", window_event.windowID,
|
||||
static_cast<uint32_t>(window_event.type));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void WindowContext::ProcessDisplayEvent(const SDL_DisplayEvent& display) {
|
||||
bool sdl = false;
|
||||
|
||||
switch (display.event) {
|
||||
case SDL_DISPLAYEVENT_ORIENTATION: sdl = true; [[fallthrough]];
|
||||
case static_cast<Uint8>(DisplayOrientation::DisplayEventOrientation): {
|
||||
LOGF("Display %" PRIu32 "[%s] changed orientation to %d - ", display.display,
|
||||
sdl ? "SDL" : "Kyty", static_cast<int>(display.data1));
|
||||
switch (display.type) {
|
||||
case SDL_EVENT_DISPLAY_ORIENTATION: {
|
||||
LOGF("Display %" PRIu32 " changed orientation to %d - ", display.displayID,
|
||||
static_cast<int>(display.data1));
|
||||
|
||||
switch (display.data1) {
|
||||
case SDL_ORIENTATION_UNKNOWN: LOGF("UNKNOWN\n"); break;
|
||||
@@ -487,8 +445,8 @@ void WindowContext::ProcessDisplayEvent(const SDL_DisplayEvent& display) {
|
||||
break;
|
||||
}
|
||||
default:
|
||||
LOGF("Display %" PRIu32 " got unknown event 0x%" PRIx8 "\n", display.display,
|
||||
display.event);
|
||||
LOGF("Display %" PRIu32 " got unknown event 0x%" PRIx32 "\n", display.displayID,
|
||||
static_cast<uint32_t>(display.type));
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -496,10 +454,9 @@ void WindowContext::ProcessDisplayEvent(const SDL_DisplayEvent& display) {
|
||||
void WindowContext::ProcessEvent(double time_s) {
|
||||
auto& game = loop;
|
||||
auto* event = &game.event;
|
||||
EXIT_IF(SDL_GetEventState(SDL_DISPLAYEVENT) != SDL_ENABLE);
|
||||
if ((event->type == SDL_KEYDOWN || event->type == SDL_KEYUP) &&
|
||||
event->key.keysym.sym == SDLK_F7) {
|
||||
if (event->type == SDL_KEYDOWN && event->key.repeat == 0) {
|
||||
if ((event->type == SDL_EVENT_KEY_DOWN || event->type == SDL_EVENT_KEY_UP) &&
|
||||
event->key.key == SDLK_F7) {
|
||||
if (event->type == SDL_EVENT_KEY_DOWN && event->key.repeat == 0) {
|
||||
HostInputToggleMouseToJoystick();
|
||||
}
|
||||
return;
|
||||
@@ -507,59 +464,63 @@ void WindowContext::ProcessEvent(double time_s) {
|
||||
if (ProcessSystemOverlayInput(*event)) {
|
||||
return;
|
||||
}
|
||||
if (event->type >= SDL_EVENT_WINDOW_FIRST && event->type <= SDL_EVENT_WINDOW_LAST) {
|
||||
ProcessWindowEvent(event->window);
|
||||
return;
|
||||
}
|
||||
if (event->type >= SDL_EVENT_DISPLAY_FIRST && event->type <= SDL_EVENT_DISPLAY_LAST) {
|
||||
ProcessDisplayEvent(event->display);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (event->type) {
|
||||
case SDL_QUIT: GameEventQuit(game); break;
|
||||
case SDL_EVENT_QUIT: GameEventQuit(game); break;
|
||||
|
||||
case SDL_APP_TERMINATING: GameEventTerminate(game); break;
|
||||
case SDL_EVENT_TERMINATING: GameEventTerminate(game); break;
|
||||
|
||||
case SDL_APP_LOWMEMORY: GameEventLowMemory(); break;
|
||||
case SDL_EVENT_LOW_MEMORY: GameEventLowMemory(); break;
|
||||
|
||||
case SDL_APP_WILLENTERBACKGROUND: GameEventWillEnterBackground(game); break;
|
||||
case SDL_EVENT_WILL_ENTER_BACKGROUND: GameEventWillEnterBackground(game); break;
|
||||
|
||||
case SDL_APP_DIDENTERBACKGROUND: GameEventDidEnterBackground(); break;
|
||||
case SDL_EVENT_DID_ENTER_BACKGROUND: GameEventDidEnterBackground(); break;
|
||||
|
||||
case SDL_APP_WILLENTERFOREGROUND: GameEventWillEnterForeground(); break;
|
||||
case SDL_EVENT_WILL_ENTER_FOREGROUND: GameEventWillEnterForeground(); break;
|
||||
|
||||
case SDL_APP_DIDENTERFOREGROUND: GameEventDidEnterForeground(game); break;
|
||||
case SDL_EVENT_DID_ENTER_FOREGROUND: GameEventDidEnterForeground(game); break;
|
||||
|
||||
case SDL_KEYDOWN:
|
||||
case SDL_KEYUP: {
|
||||
case SDL_EVENT_KEY_DOWN:
|
||||
case SDL_EVENT_KEY_UP: {
|
||||
EventKeyboard key {};
|
||||
|
||||
key.down = (event->type == SDL_KEYDOWN);
|
||||
key.up = (event->type == SDL_KEYUP);
|
||||
key.pressed = (event->key.state == SDL_PRESSED);
|
||||
key.released = (event->key.state == SDL_RELEASED);
|
||||
key.down = (event->type == SDL_EVENT_KEY_DOWN);
|
||||
key.up = (event->type == SDL_EVENT_KEY_UP);
|
||||
key.pressed = event->key.down;
|
||||
key.released = !event->key.down;
|
||||
key.repeat = (event->key.repeat != 0u);
|
||||
key.scan_code = event->key.keysym.scancode;
|
||||
key.key_code = event->key.keysym.sym;
|
||||
key.mod = event->key.keysym.mod;
|
||||
key.scan_code = event->key.scancode;
|
||||
key.key_code = event->key.key;
|
||||
key.mod = event->key.mod;
|
||||
key.timestamp_seconds = time_s;
|
||||
|
||||
GameEventKeyboard(game, key);
|
||||
GameEventKeyboard(key);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case SDL_WINDOWEVENT: ProcessWindowEvent(event->window); break;
|
||||
|
||||
case SDL_DISPLAYEVENT: ProcessDisplayEvent(event->display); break;
|
||||
|
||||
case SDL_MOUSEBUTTONDOWN:
|
||||
case SDL_MOUSEBUTTONUP: {
|
||||
case SDL_EVENT_MOUSE_BUTTON_DOWN:
|
||||
case SDL_EVENT_MOUSE_BUTTON_UP: {
|
||||
EventMouse mb {};
|
||||
|
||||
mb.down = (event->button.type == SDL_MOUSEBUTTONDOWN);
|
||||
mb.up = (event->button.type == SDL_MOUSEBUTTONUP);
|
||||
mb.down = (event->button.type == SDL_EVENT_MOUSE_BUTTON_DOWN);
|
||||
mb.up = (event->button.type == SDL_EVENT_MOUSE_BUTTON_UP);
|
||||
mb.left = (event->button.button == SDL_BUTTON_LEFT);
|
||||
mb.middle = (event->button.button == SDL_BUTTON_MIDDLE);
|
||||
mb.right = (event->button.button == SDL_BUTTON_RIGHT);
|
||||
mb.x1 = (event->button.button == SDL_BUTTON_X1);
|
||||
mb.x2 = (event->button.button == SDL_BUTTON_X2);
|
||||
mb.touch = (event->button.which == SDL_TOUCH_MOUSEID);
|
||||
mb.pressed = (event->button.state == SDL_PRESSED);
|
||||
mb.released = (event->button.state == SDL_RELEASED);
|
||||
mb.pressed = event->button.down;
|
||||
mb.released = !event->button.down;
|
||||
mb.num_of_clicks = event->button.clicks;
|
||||
mb.wheel = false;
|
||||
mb.x = event->button.x;
|
||||
@@ -574,7 +535,7 @@ void WindowContext::ProcessEvent(double time_s) {
|
||||
break;
|
||||
}
|
||||
|
||||
case SDL_MOUSEWHEEL: {
|
||||
case SDL_EVENT_MOUSE_WHEEL: {
|
||||
EventMouse mb {};
|
||||
|
||||
mb.down = false;
|
||||
@@ -601,7 +562,7 @@ void WindowContext::ProcessEvent(double time_s) {
|
||||
break;
|
||||
}
|
||||
|
||||
case SDL_MOUSEMOTION: {
|
||||
case SDL_EVENT_MOUSE_MOTION: {
|
||||
EventMouse mb {};
|
||||
|
||||
mb.down = false;
|
||||
@@ -628,16 +589,16 @@ void WindowContext::ProcessEvent(double time_s) {
|
||||
break;
|
||||
}
|
||||
|
||||
case SDL_FINGERMOTION:
|
||||
case SDL_FINGERDOWN:
|
||||
case SDL_FINGERUP: {
|
||||
case SDL_EVENT_FINGER_MOTION:
|
||||
case SDL_EVENT_FINGER_DOWN:
|
||||
case SDL_EVENT_FINGER_UP: {
|
||||
EventFinger f {};
|
||||
|
||||
f.down = (event->tfinger.type == SDL_FINGERDOWN);
|
||||
f.up = (event->tfinger.type == SDL_FINGERUP);
|
||||
f.motion = (event->tfinger.type == SDL_FINGERMOTION);
|
||||
f.finger_id = static_cast<int>(event->tfinger.fingerId);
|
||||
f.touch_id = static_cast<int>(event->tfinger.touchId);
|
||||
f.down = (event->tfinger.type == SDL_EVENT_FINGER_DOWN);
|
||||
f.up = (event->tfinger.type == SDL_EVENT_FINGER_UP);
|
||||
f.motion = (event->tfinger.type == SDL_EVENT_FINGER_MOTION);
|
||||
f.finger_id = static_cast<int>(event->tfinger.fingerID);
|
||||
f.touch_id = static_cast<int>(event->tfinger.touchID);
|
||||
f.x = event->tfinger.x;
|
||||
f.y = event->tfinger.y;
|
||||
f.dx = event->tfinger.dx;
|
||||
@@ -650,13 +611,13 @@ void WindowContext::ProcessEvent(double time_s) {
|
||||
break;
|
||||
}
|
||||
|
||||
case SDL_CONTROLLERAXISMOTION: {
|
||||
case SDL_EVENT_GAMEPAD_AXIS_MOTION: {
|
||||
EventController c {};
|
||||
|
||||
c.id = event->caxis.which;
|
||||
c.button = SDL_CONTROLLER_BUTTON_INVALID;
|
||||
c.axis_id = event->caxis.axis;
|
||||
c.axis_value = event->caxis.value;
|
||||
c.id = event->gaxis.which;
|
||||
c.button = SDL_GAMEPAD_BUTTON_INVALID;
|
||||
c.axis_id = event->gaxis.axis;
|
||||
c.axis_value = event->gaxis.value;
|
||||
c.down = false;
|
||||
c.up = false;
|
||||
c.added = false;
|
||||
@@ -672,22 +633,22 @@ void WindowContext::ProcessEvent(double time_s) {
|
||||
break;
|
||||
}
|
||||
|
||||
case SDL_CONTROLLERBUTTONDOWN:
|
||||
case SDL_CONTROLLERBUTTONUP: {
|
||||
case SDL_EVENT_GAMEPAD_BUTTON_DOWN:
|
||||
case SDL_EVENT_GAMEPAD_BUTTON_UP: {
|
||||
EventController c {};
|
||||
|
||||
c.id = event->cbutton.which;
|
||||
c.button = event->cbutton.button;
|
||||
c.axis_id = SDL_CONTROLLER_AXIS_INVALID;
|
||||
c.id = event->gbutton.which;
|
||||
c.button = event->gbutton.button;
|
||||
c.axis_id = SDL_GAMEPAD_AXIS_INVALID;
|
||||
c.axis_value = 0;
|
||||
c.down = (event->cbutton.type == SDL_CONTROLLERBUTTONDOWN);
|
||||
c.up = (event->cbutton.type == SDL_CONTROLLERBUTTONUP);
|
||||
c.down = event->gbutton.down;
|
||||
c.up = !event->gbutton.down;
|
||||
c.added = false;
|
||||
c.removed = false;
|
||||
c.remapped = false;
|
||||
c.axis = false;
|
||||
c.pressed = (event->cbutton.state == SDL_PRESSED);
|
||||
c.released = (event->cbutton.state == SDL_RELEASED);
|
||||
c.pressed = event->gbutton.down;
|
||||
c.released = !event->gbutton.down;
|
||||
c.timestamp_seconds = time_s;
|
||||
|
||||
GameEventController(c);
|
||||
@@ -695,44 +656,44 @@ void WindowContext::ProcessEvent(double time_s) {
|
||||
break;
|
||||
}
|
||||
|
||||
case SDL_CONTROLLERTOUCHPADDOWN:
|
||||
case SDL_CONTROLLERTOUCHPADMOTION:
|
||||
case SDL_CONTROLLERTOUCHPADUP:
|
||||
if (event->ctouchpad.touchpad == 0) {
|
||||
Controller::SetTouchPad(event->ctouchpad.which, event->ctouchpad.finger,
|
||||
event->ctouchpad.type != SDL_CONTROLLERTOUCHPADUP,
|
||||
event->ctouchpad.x, event->ctouchpad.y);
|
||||
case SDL_EVENT_GAMEPAD_TOUCHPAD_DOWN:
|
||||
case SDL_EVENT_GAMEPAD_TOUCHPAD_MOTION:
|
||||
case SDL_EVENT_GAMEPAD_TOUCHPAD_UP:
|
||||
if (event->gtouchpad.touchpad == 0) {
|
||||
Controller::SetTouchPad(event->gtouchpad.which, event->gtouchpad.finger,
|
||||
event->gtouchpad.type != SDL_EVENT_GAMEPAD_TOUCHPAD_UP,
|
||||
event->gtouchpad.x, event->gtouchpad.y);
|
||||
}
|
||||
break;
|
||||
|
||||
case SDL_CONTROLLERSENSORUPDATE: {
|
||||
const auto& sensor = event->csensor;
|
||||
case SDL_EVENT_GAMEPAD_SENSOR_UPDATE: {
|
||||
const auto& sensor = event->gsensor;
|
||||
if (sensor.sensor == SDL_SENSOR_ACCEL || sensor.sensor == SDL_SENSOR_GYRO) {
|
||||
Controller::SetSensor(sensor.which,
|
||||
sensor.sensor == SDL_SENSOR_ACCEL ? Controller::Sensor::Accel
|
||||
: Controller::Sensor::Gyro,
|
||||
sensor.data,
|
||||
sensor.timestamp_us != 0
|
||||
? sensor.timestamp_us
|
||||
: static_cast<uint64_t>(sensor.timestamp) * 1000);
|
||||
sensor.sensor_timestamp != 0
|
||||
? sensor.sensor_timestamp / 1000
|
||||
: sensor.timestamp / 1000);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case SDL_CONTROLLERDEVICEADDED:
|
||||
case SDL_CONTROLLERDEVICEREMOVED:
|
||||
case SDL_CONTROLLERDEVICEREMAPPED: {
|
||||
case SDL_EVENT_GAMEPAD_ADDED:
|
||||
case SDL_EVENT_GAMEPAD_REMOVED:
|
||||
case SDL_EVENT_GAMEPAD_REMAPPED: {
|
||||
EventController c {};
|
||||
|
||||
c.id = event->cdevice.which;
|
||||
c.button = SDL_CONTROLLER_BUTTON_INVALID;
|
||||
c.axis_id = SDL_CONTROLLER_AXIS_INVALID;
|
||||
c.id = event->gdevice.which;
|
||||
c.button = SDL_GAMEPAD_BUTTON_INVALID;
|
||||
c.axis_id = SDL_GAMEPAD_AXIS_INVALID;
|
||||
c.axis_value = 0;
|
||||
c.down = false;
|
||||
c.up = false;
|
||||
c.added = (event->cdevice.type == SDL_CONTROLLERDEVICEADDED);
|
||||
c.removed = (event->cdevice.type == SDL_CONTROLLERDEVICEREMOVED);
|
||||
c.remapped = (event->cdevice.type == SDL_CONTROLLERDEVICEREMAPPED);
|
||||
c.added = (event->gdevice.type == SDL_EVENT_GAMEPAD_ADDED);
|
||||
c.removed = (event->gdevice.type == SDL_EVENT_GAMEPAD_REMOVED);
|
||||
c.remapped = (event->gdevice.type == SDL_EVENT_GAMEPAD_REMAPPED);
|
||||
c.axis = false;
|
||||
c.pressed = false;
|
||||
c.released = false;
|
||||
@@ -745,47 +706,6 @@ void WindowContext::ProcessEvent(double time_s) {
|
||||
}
|
||||
}
|
||||
|
||||
void WindowContext::RunOnMainThread(std::function<void()> task) {
|
||||
if (Common::Thread::IsMainThread()) {
|
||||
task();
|
||||
return;
|
||||
}
|
||||
|
||||
uint64_t ticket = 0;
|
||||
{
|
||||
Common::LockGuard lock(main_task_mutex);
|
||||
main_tasks.push_back(std::move(task));
|
||||
ticket = ++main_tasks_queued;
|
||||
}
|
||||
|
||||
// Wake the main loop in case it is blocked in SDL_WaitEvent.
|
||||
SDL_Event event {};
|
||||
event.type = SDL_USEREVENT;
|
||||
SDL_PushEvent(&event);
|
||||
|
||||
Common::LockGuard lock(main_task_mutex);
|
||||
while (main_tasks_run < ticket) {
|
||||
main_task_done.Wait(&main_task_mutex);
|
||||
}
|
||||
}
|
||||
|
||||
void WindowContext::DrainMainThreadTasks() {
|
||||
std::vector<std::function<void()>> tasks;
|
||||
{
|
||||
Common::LockGuard lock(main_task_mutex);
|
||||
tasks.swap(main_tasks);
|
||||
}
|
||||
if (tasks.empty()) {
|
||||
return;
|
||||
}
|
||||
for (auto& task: tasks) {
|
||||
task();
|
||||
}
|
||||
Common::LockGuard lock(main_task_mutex);
|
||||
main_tasks_run += tasks.size();
|
||||
main_task_done.SignalAll();
|
||||
}
|
||||
|
||||
void WindowContext::Run() {
|
||||
Common::Timer timer;
|
||||
timer.Start();
|
||||
@@ -795,7 +715,6 @@ void WindowContext::Run() {
|
||||
loop.paused.store(false, std::memory_order_release);
|
||||
|
||||
while (!loop.need_exit) {
|
||||
DrainMainThreadTasks();
|
||||
if (loop.paused.load(std::memory_order_acquire)) {
|
||||
if (!timer.IsPaused()) {
|
||||
timer.Pause();
|
||||
@@ -819,17 +738,15 @@ static void WindowCreate(WindowContext& context) {
|
||||
int width = static_cast<int>(context.graphic_ctx.screen_width);
|
||||
int height = static_cast<int>(context.graphic_ctx.screen_height);
|
||||
|
||||
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE, "1");
|
||||
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, "1");
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
SDL_SetHint(SDL_HINT_WINDOWS_DPI_SCALING, "0");
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_LINUX
|
||||
// RenderDoc can hide Wayland Vulkan surface support; prefer X11 for captures.
|
||||
if (Config::RenderDocEnabled()) {
|
||||
SDL_SetHint(SDL_HINT_VIDEO_DRIVER, "x11,wayland");
|
||||
}
|
||||
#endif
|
||||
|
||||
if (SDL_InitSubSystem(SDL_INIT_VIDEO | SDL_INIT_GAMECONTROLLER) < 0) {
|
||||
if (!SDL_InitSubSystem(SDL_INIT_VIDEO | SDL_INIT_GAMEPAD)) {
|
||||
EXIT("%s\n", SDL_GetError());
|
||||
}
|
||||
HostInputInit();
|
||||
InitializeSystemOverlayInput();
|
||||
|
||||
LOGF("WindowCreate(): width = %d, height = %d\n", width, height);
|
||||
|
||||
@@ -839,15 +756,14 @@ static void WindowCreate(WindowContext& context) {
|
||||
// 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) {
|
||||
if (const 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) {
|
||||
SDL_setenv_unsafe("SDL_VULKAN_LIBRARY", moltenvk_path.c_str(), 0) == 0) {
|
||||
LOGF("Vulkan loader: %s\n", moltenvk_path.c_str());
|
||||
}
|
||||
}
|
||||
@@ -860,21 +776,22 @@ static void WindowCreate(WindowContext& context) {
|
||||
window_flags |= static_cast<uint32_t>(SDL_WINDOW_BORDERLESS);
|
||||
}
|
||||
#endif
|
||||
context.window = SDL_CreateWindow(KYTY_SDL_WINDOW_CAPTION, SDL_WINDOWPOS_CENTERED,
|
||||
SDL_WINDOWPOS_CENTERED, width, height, window_flags);
|
||||
context.window = SDL_CreateWindow(KYTY_SDL_WINDOW_CAPTION, width, height, window_flags);
|
||||
|
||||
if (context.window == nullptr) {
|
||||
EXIT("%s\n", SDL_GetError());
|
||||
}
|
||||
|
||||
SDL_SetWindowResizable(context.window, SDL_FALSE);
|
||||
SDL_SetWindowPosition(context.window, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED);
|
||||
HostInputInit(context.window);
|
||||
InitializeSystemOverlayInput(context.window);
|
||||
context.UpdateIcon();
|
||||
}
|
||||
|
||||
uint32_t WindowContext::InitialWindowFlags(bool fullscreen) noexcept {
|
||||
auto flags = static_cast<uint32_t>(SDL_WINDOW_VULKAN);
|
||||
auto flags = static_cast<uint32_t>(SDL_WINDOW_VULKAN | SDL_WINDOW_RESIZABLE);
|
||||
if (fullscreen) {
|
||||
flags |= static_cast<uint32_t>(SDL_WINDOW_FULLSCREEN_DESKTOP);
|
||||
flags |= static_cast<uint32_t>(SDL_WINDOW_FULLSCREEN);
|
||||
}
|
||||
return flags;
|
||||
}
|
||||
@@ -905,6 +822,7 @@ void WindowRun() {
|
||||
|
||||
void WindowShutdown() {
|
||||
if (g_window != nullptr) {
|
||||
HostInputShutdown();
|
||||
Controller::EmergencyShutdown();
|
||||
g_window.reset();
|
||||
}
|
||||
@@ -940,15 +858,15 @@ struct WindowIcon {
|
||||
void* pixels = nullptr;
|
||||
|
||||
~WindowIcon() {
|
||||
SDL_FreeSurface(surface);
|
||||
SDL_DestroySurface(surface);
|
||||
stbi_image_free(pixels);
|
||||
}
|
||||
};
|
||||
|
||||
static void WindowLoadPngIcon(const std::string& path, WindowIcon* icon) {
|
||||
static void WindowLoadPngIcon(const std::filesystem::path& path, WindowIcon* icon) {
|
||||
Common::File f;
|
||||
if (!f.Open(path, Common::File::Mode::Read)) {
|
||||
EXIT("Can't open icon file %s\n", path.c_str());
|
||||
EXIT("Can't open icon file %s\n", Common::PathToString(path).c_str());
|
||||
}
|
||||
|
||||
int width = 0;
|
||||
@@ -964,8 +882,8 @@ static void WindowLoadPngIcon(const std::string& path, WindowIcon* icon) {
|
||||
|
||||
EXIT_IF(icon->pixels == nullptr);
|
||||
|
||||
icon->surface = SDL_CreateRGBSurfaceWithFormatFrom(icon->pixels, width, height, 32, width * 4,
|
||||
SDL_PIXELFORMAT_RGBA32);
|
||||
icon->surface = SDL_CreateSurfaceFrom(width, height, SDL_PIXELFORMAT_RGBA32, icon->pixels,
|
||||
width * 4);
|
||||
EXIT_NOT_IMPLEMENTED(icon->surface == nullptr);
|
||||
}
|
||||
|
||||
@@ -974,7 +892,7 @@ void WindowContext::UpdateIcon() {
|
||||
static bool icon_loaded = false;
|
||||
|
||||
if (!icon_loaded) {
|
||||
std::string icon_path;
|
||||
std::filesystem::path icon_path;
|
||||
if (Loader::SystemContentGetIconPath(&icon_path)) {
|
||||
WindowLoadPngIcon(icon_path, &icon);
|
||||
}
|
||||
@@ -1027,7 +945,16 @@ void WindowContext::UpdateTitle() {
|
||||
(has_title_id ? ", " : ""), (has_app_ver ? app_ver : ""), (has_app_ver ? " " : ""),
|
||||
device_name, processor_name, frame_num, current_fps);
|
||||
|
||||
RunOnMainThread([this, text = std::move(text)] { SDL_SetWindowTitle(window, text.c_str()); });
|
||||
struct TitleUpdate {
|
||||
SDL_Window* window;
|
||||
std::string* text;
|
||||
} update {window, &text};
|
||||
EXIT_IF(!SDL_RunOnMainThread(
|
||||
[](void* data) {
|
||||
auto& title = *static_cast<TitleUpdate*>(data);
|
||||
SDL_SetWindowTitle(title.window, title.text->c_str());
|
||||
},
|
||||
&update, true));
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
@@ -1,15 +1,14 @@
|
||||
#ifndef EMULATOR_SRC_GRAPHICS_PRESENTATION_WINDOW_WINDOWINTERNAL_H_
|
||||
#define EMULATOR_SRC_GRAPHICS_PRESENTATION_WINDOW_WINDOWINTERNAL_H_
|
||||
|
||||
#include "SDL_events.h"
|
||||
#include "SDL_video.h"
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include "common/threads.h"
|
||||
#include "graphics/host_gpu/graphicContext.h"
|
||||
#include "graphics/host_gpu/vulkanCommon.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
@@ -48,9 +47,6 @@ struct WindowContext {
|
||||
void ProcessDisplayEvent(const SDL_DisplayEvent& event);
|
||||
void ProcessEvent(double time_seconds);
|
||||
void Run();
|
||||
// SDL window operations must complete on the main thread.
|
||||
void RunOnMainThread(std::function<void()> task);
|
||||
void DrainMainThreadTasks();
|
||||
|
||||
GraphicContext graphic_ctx;
|
||||
SDL_Window* window = nullptr;
|
||||
@@ -61,12 +57,6 @@ struct WindowContext {
|
||||
WindowLoopState loop;
|
||||
|
||||
Common::Mutex mutex;
|
||||
|
||||
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
|
||||
};
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
@@ -132,12 +132,23 @@ void ValidateNativeProgram(const IR::Program& program) {
|
||||
program.memory_info[index].planning_only;
|
||||
});
|
||||
};
|
||||
const auto indirect_buffer_handle = [&](const IR::Inst& handle) {
|
||||
return program.info.uses_dma && handle.NumArgs() == 4u && !handle.Uses().empty() &&
|
||||
std::ranges::all_of(handle.Uses(), [&](const IR::Use& use) {
|
||||
if (IR::BufferAccessOf(use.user->GetOpcode()) != IR::BufferAccess::Read) {
|
||||
return false;
|
||||
}
|
||||
const auto index = use.user->Flags<IR::MemoryFlags>().index;
|
||||
return index < program.memory_info.size() &&
|
||||
program.memory_info[index].kind == IR::ResourceKind::IndirectBuffer;
|
||||
});
|
||||
};
|
||||
for (const auto* block: program.blocks) {
|
||||
for (const auto& inst: *block) {
|
||||
const auto dense = inst.Flags<uint32_t>();
|
||||
switch (inst.GetOpcode()) {
|
||||
case IR::ValueOpcode::GetBufferResource:
|
||||
if (planning_only_handle(inst)) {
|
||||
if (planning_only_handle(inst) || indirect_buffer_handle(inst)) {
|
||||
break;
|
||||
}
|
||||
if (dense >= program.info.buffers.size()) {
|
||||
@@ -212,6 +223,9 @@ Emitter::SpirvRequirements Emitter::AnalyzeProgramRequirements(const IR::Program
|
||||
Fail(program, "buffer operation has invalid memory metadata");
|
||||
}
|
||||
const auto& memory = program.memory_info[memory_index];
|
||||
if (memory.kind == IR::ResourceKind::IndirectBuffer) {
|
||||
requirements.subgroup_local_invocation_id = true;
|
||||
}
|
||||
if (memory.kind == IR::ResourceKind::Buffer) {
|
||||
if (memory.resource >= program.info.buffers.size()) {
|
||||
Fail(program, "buffer operation has invalid resource metadata");
|
||||
|
||||
@@ -138,7 +138,7 @@ uint32_t EmitDppWriteCondition(ValueEmitContext& ctx, const IR::DppMoveFlags& fl
|
||||
state.builder.AddFunction(spv::OpLogicalAnd, TypeBool(state), masks_ok, bank_ok, row_ok);
|
||||
uint32_t writable = masks_ok;
|
||||
if (!flags.bound_control) {
|
||||
const auto target = EmitDppTargetLane(state, flags.control);
|
||||
const auto target = EmitDppTargetLane(state, flags);
|
||||
const auto bounded = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpLogicalAnd, TypeBool(state), bounded, writable,
|
||||
target.valid);
|
||||
@@ -559,6 +559,10 @@ void EmitVoid(ValueEmitContext&) {}
|
||||
|
||||
void EmitBarrier(EmitterState& state) {
|
||||
const auto tessellation = state.program.stage == ShaderType::TessellationControl;
|
||||
if (!tessellation && ShaderWorkgroupInput(state.program.stage, state.input_info) == nullptr) {
|
||||
// Independent graphics invocations have no native workgroup left to synchronize.
|
||||
return;
|
||||
}
|
||||
const auto memory_scope = tessellation ? spv::ScopeInvocation : spv::ScopeWorkgroup;
|
||||
const auto semantics = tessellation ? spv::MemorySemanticsMaskNone
|
||||
: spv::MemorySemanticsAcquireReleaseMask |
|
||||
@@ -611,7 +615,7 @@ uint32_t EmitUndefU1(EmitterState& state, const IR::Inst& inst) {
|
||||
uint32_t EmitDppMoveU32(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
auto& state = ctx.state;
|
||||
const auto flags = inst.Flags<IR::DppMoveFlags>();
|
||||
const auto target = EmitDppTargetLane(state, flags.control);
|
||||
const auto target = EmitDppTargetLane(state, flags);
|
||||
const auto shuffled = ctx.Shuffle(inst, 0, target.lane);
|
||||
if (flags.fetch_inactive) {
|
||||
return shuffled;
|
||||
|
||||
@@ -2,24 +2,26 @@
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter {
|
||||
|
||||
DppTargetLane EmitDppQuadPermTargetLane(EmitterState& state, uint32_t subid, uint32_t control) {
|
||||
const auto quad_base = state.builder.AllocateId();
|
||||
DppTargetLane EmitDppPermTargetLane(EmitterState& state, uint32_t subid, uint32_t control,
|
||||
uint32_t lane_bits) {
|
||||
const auto lane_mask = (1u << lane_bits) - 1u;
|
||||
const auto group_base = state.builder.AllocateId();
|
||||
const auto lane = state.builder.AllocateId();
|
||||
const auto shift = state.builder.AllocateId();
|
||||
const auto selected0 = state.builder.AllocateId();
|
||||
const auto selected = state.builder.AllocateId();
|
||||
const auto target = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpBitwiseAnd, TypeU32(state), quad_base, subid,
|
||||
ConstantU32(state, 0xfffffffcu));
|
||||
state.builder.AddFunction(spv::OpBitwiseAnd, TypeU32(state), group_base, subid,
|
||||
ConstantU32(state, ~lane_mask));
|
||||
state.builder.AddFunction(spv::OpBitwiseAnd, TypeU32(state), lane, subid,
|
||||
ConstantU32(state, 3));
|
||||
state.builder.AddFunction(spv::OpShiftLeftLogical, TypeU32(state), shift, lane,
|
||||
ConstantU32(state, 1));
|
||||
ConstantU32(state, lane_mask));
|
||||
state.builder.AddFunction(spv::OpIMul, TypeU32(state), shift, lane,
|
||||
ConstantU32(state, lane_bits));
|
||||
state.builder.AddFunction(spv::OpShiftRightLogical, TypeU32(state), selected0,
|
||||
ConstantU32(state, control), shift);
|
||||
state.builder.AddFunction(spv::OpBitwiseAnd, TypeU32(state), selected, selected0,
|
||||
ConstantU32(state, 3));
|
||||
state.builder.AddFunction(spv::OpBitwiseOr, TypeU32(state), target, quad_base, selected);
|
||||
ConstantU32(state, lane_mask));
|
||||
state.builder.AddFunction(spv::OpBitwiseOr, TypeU32(state), target, group_base, selected);
|
||||
return {target, ConstantBool(state, true)};
|
||||
}
|
||||
|
||||
@@ -89,10 +91,14 @@ DppTargetLane EmitDppMirrorTargetLane(EmitterState& state, uint32_t subid, bool
|
||||
return {target, ConstantBool(state, true)};
|
||||
}
|
||||
|
||||
DppTargetLane EmitDppTargetLane(EmitterState& state, uint32_t control) {
|
||||
DppTargetLane EmitDppTargetLane(EmitterState& state, const IR::DppMoveFlags& flags) {
|
||||
const auto subid = EmitSubgroupLocalInvocationId(state);
|
||||
const auto control = flags.control;
|
||||
if (flags.dpp8) {
|
||||
return EmitDppPermTargetLane(state, subid, control, 3u);
|
||||
}
|
||||
if (control <= 0xffu) {
|
||||
return EmitDppQuadPermTargetLane(state, subid, control);
|
||||
return EmitDppPermTargetLane(state, subid, control, 2u);
|
||||
}
|
||||
if (control >= 0x101u && control <= 0x10fu) {
|
||||
return EmitDppRowShiftTargetLane(state, subid, control & 0xfu, true);
|
||||
|
||||
@@ -63,10 +63,10 @@ uint32_t AddressF32(ValueEmitContext& ctx, const IR::MemoryInfo& mem, const IR::
|
||||
: Unary(ctx.state, spv::OpBitcast, TypeF32(ctx.state), value);
|
||||
}
|
||||
|
||||
ImageSampleLayout Layout(const IR::MemoryInfo& mem, ImageDimension dimension) {
|
||||
ImageSampleLayout Layout(const IR::MemoryInfo& mem) {
|
||||
ImageSampleLayout layout;
|
||||
uint32_t cursor = 0;
|
||||
const auto& info = ImageDimensionInfoFor(dimension);
|
||||
const auto& info = ImageDimensionInfoFor(mem.image_dimension);
|
||||
if (HasFlag(mem, Decoder::ImageSampleFlagOffset)) layout.offset = cursor++;
|
||||
if (HasFlag(mem, Decoder::ImageSampleFlagBias)) layout.bias = cursor++;
|
||||
if (HasFlag(mem, Decoder::ImageSampleFlagCompare)) layout.dref = cursor++;
|
||||
@@ -104,8 +104,7 @@ uint32_t CubeLayer(EmitterState& state, uint32_t value) {
|
||||
}
|
||||
|
||||
uint32_t CoordF32(ValueEmitContext& ctx, const IR::MemoryInfo& mem, const IR::Inst& address,
|
||||
uint32_t first, uint32_t components) {
|
||||
const bool cube = ctx.state.program.info.images.at(mem.resource).cube;
|
||||
uint32_t first, uint32_t components, bool cube = false) {
|
||||
auto x = AddressF32(ctx, mem, address, first);
|
||||
if (components == 1u) return x;
|
||||
auto y = mem.image_address_components > first + 1u ? AddressF32(ctx, mem, address, first + 1u)
|
||||
@@ -576,7 +575,8 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
const auto lod = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
spv::OpImageQueryLod, TypeF32Vector(state, 2), lod, sampled,
|
||||
CoordF32(ctx, mem, *address, 0, ImageDimensionInfoFor(dimension).spatial_components));
|
||||
CoordF32(ctx, mem, *address, 0, ImageDimensionInfoFor(dimension).spatial_components,
|
||||
image.cube));
|
||||
uint32_t values[4] = {ConstantU32(state, 0), ConstantU32(state, 0), ConstantU32(state, 0),
|
||||
ConstantU32(state, 0)};
|
||||
for (uint32_t index = 0; index < 2u; index++) {
|
||||
@@ -637,7 +637,7 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
if (op == IR::ValueOpcode::ImageSampleRaw || op == IR::ValueOpcode::ImageGatherRaw) {
|
||||
const auto dimension = image.dimension;
|
||||
const auto& dimension_info = ImageDimensionInfoFor(dimension);
|
||||
const auto layout = Layout(mem, dimension);
|
||||
const auto layout = Layout(mem);
|
||||
const auto numeric_class = image.numeric_class;
|
||||
const bool dref = HasFlag(mem, Decoder::ImageSampleFlagCompare);
|
||||
if (dref && state.program.info.images[mem.resource].conversion_format !=
|
||||
@@ -645,9 +645,9 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
ctx.Fail(inst, "uses depth comparison with a packed integer image");
|
||||
return;
|
||||
}
|
||||
const auto coord =
|
||||
CoordF32(ctx, mem, *address, layout.coord, dimension_info.coordinate_components);
|
||||
if (op == IR::ValueOpcode::ImageGatherRaw) {
|
||||
const auto coord = CoordF32(ctx, mem, *address, layout.coord,
|
||||
dimension_info.coordinate_components, image.cube);
|
||||
if (HasFlag(mem, Decoder::ImageSampleFlagLod)) {
|
||||
static std::atomic_flag warned = ATOMIC_FLAG_INIT;
|
||||
if (!warned.test_and_set(std::memory_order_relaxed)) {
|
||||
@@ -752,6 +752,11 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
operands.push_back(AddressF32(ctx, mem, *address, layout.bias));
|
||||
}
|
||||
const auto EmitSample = [&](uint32_t resource) {
|
||||
const auto& candidate = state.program.info.images[resource];
|
||||
const auto coord =
|
||||
CoordF32(ctx, mem, *address, layout.coord,
|
||||
ImageDimensionInfoFor(candidate.dimension).coordinate_components,
|
||||
candidate.cube);
|
||||
const auto sampled = MakeSampledImage(state, resource, mem.sampler);
|
||||
const auto sample = state.builder.AllocateId();
|
||||
std::vector<uint32_t> sample_operands {result_type, sample, sampled, coord};
|
||||
|
||||
@@ -327,7 +327,8 @@ void EmitMeshAllocate(ValueEmitContext& ctx, const IR::Inst& inst);
|
||||
uint32_t MeshOutputPointer(EmitterState& state, IR::StageOutputKind kind, uint32_t index = 0);
|
||||
uint32_t MeshPrimitivePointer(EmitterState& state);
|
||||
|
||||
DppTargetLane EmitDppQuadPermTargetLane(EmitterState& state, uint32_t subid, uint32_t control);
|
||||
DppTargetLane EmitDppPermTargetLane(EmitterState& state, uint32_t subid, uint32_t control,
|
||||
uint32_t lane_bits);
|
||||
|
||||
DppTargetLane EmitDppRowShiftTargetLane(EmitterState& state, uint32_t subid, uint32_t amount,
|
||||
bool left);
|
||||
@@ -336,7 +337,7 @@ DppTargetLane EmitDppRowRotateRightTargetLane(EmitterState& state, uint32_t subi
|
||||
|
||||
DppTargetLane EmitDppMirrorTargetLane(EmitterState& state, uint32_t subid, bool half_row);
|
||||
|
||||
DppTargetLane EmitDppTargetLane(EmitterState& state, uint32_t control);
|
||||
DppTargetLane EmitDppTargetLane(EmitterState& state, const IR::DppMoveFlags& flags);
|
||||
|
||||
uint32_t EmitSubgroupLocalInvocationId(EmitterState& state);
|
||||
|
||||
@@ -493,22 +494,18 @@ template <typename Fn>
|
||||
uint32_t EmitValueOrDefaultIfCondition(EmitterState& state, uint32_t condition, uint32_t type,
|
||||
uint32_t default_value, Fn&& fn) {
|
||||
const auto then_label = state.builder.AllocateId();
|
||||
const auto then_exit = state.builder.AllocateId();
|
||||
const auto else_label = state.builder.AllocateId();
|
||||
const auto header = state.current_label;
|
||||
const auto merge_label = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpSelectionMerge, merge_label, spv::SelectionControlMaskNone);
|
||||
state.builder.AddFunction(spv::OpBranchConditional, condition, then_label, else_label);
|
||||
state.builder.AddFunction(spv::OpBranchConditional, condition, then_label, merge_label);
|
||||
EmitLabel(state, then_label);
|
||||
const auto then_value = fn();
|
||||
state.builder.AddFunction(spv::OpBranch, then_exit);
|
||||
EmitLabel(state, then_exit);
|
||||
state.builder.AddFunction(spv::OpBranch, merge_label);
|
||||
EmitLabel(state, else_label);
|
||||
const auto then_exit = state.current_label;
|
||||
state.builder.AddFunction(spv::OpBranch, merge_label);
|
||||
EmitLabel(state, merge_label);
|
||||
const auto value = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpPhi, type, value, then_value, then_exit, default_value,
|
||||
else_label);
|
||||
header);
|
||||
return value;
|
||||
}
|
||||
|
||||
|
||||
@@ -27,61 +27,71 @@ uint32_t EmitDsMaskedLaneRead(EmitterState& state, uint32_t source, uint32_t tar
|
||||
return Select(state, TypeU32(state), source_active, shuffled, ConstantU32(state, 0));
|
||||
}
|
||||
|
||||
uint32_t BufferByteAddress(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryInfo& mem,
|
||||
uint32_t index, uint32_t offset, uint32_t soffset) {
|
||||
auto& state = ctx.state;
|
||||
const uint32_t packed = StorageBufferPackedStride(state, mem);
|
||||
const uint32_t stride = packed & 0x3fffu;
|
||||
const bool swizzle = stride != 0u && ((packed >> 14u) & 1u) != 0u;
|
||||
if (((packed >> 20u) & 1u) != 0u) {
|
||||
const auto lane = Binary(state, spv::OpBitwiseAnd, TypeU32(state),
|
||||
EmitSubgroupLocalInvocationId(state), ConstantU32(state, 63));
|
||||
index = Binary(state, spv::OpIAdd, TypeU32(state), index, lane);
|
||||
}
|
||||
if (mem.offset != 0u) {
|
||||
offset = Binary(state, spv::OpIAdd, TypeU32(state), offset, ConstantU32(state, mem.offset));
|
||||
}
|
||||
struct BufferAddress {
|
||||
uint32_t offset;
|
||||
uint32_t byte;
|
||||
};
|
||||
|
||||
uint32_t address = 0;
|
||||
if (!swizzle) {
|
||||
if (stride == 0u) {
|
||||
address = offset;
|
||||
} else {
|
||||
const auto indexed = stride == 1u ? index
|
||||
: Binary(state, spv::OpIMul, TypeU32(state), index,
|
||||
ConstantU32(state, stride));
|
||||
address = Binary(state, spv::OpIAdd, TypeU32(state), indexed, offset);
|
||||
}
|
||||
} else {
|
||||
const uint32_t stride_enum = (packed >> 16u) & 3u;
|
||||
const uint32_t index_stride = 8u << stride_enum;
|
||||
const auto index_msb = Binary(state, spv::OpShiftRightLogical, TypeU32(state), index,
|
||||
ConstantU32(state, stride_enum + 3u));
|
||||
const auto index_lsb = Binary(state, spv::OpBitwiseAnd, TypeU32(state), index,
|
||||
ConstantU32(state, index_stride - 1u));
|
||||
const auto offset_msb =
|
||||
BufferAddress CalculateBufferAddress(EmitterState& state, uint32_t index, uint32_t offset,
|
||||
uint32_t soffset, uint32_t immediate, uint32_t stride,
|
||||
uint32_t swizzle, uint32_t index_stride) {
|
||||
const auto zero = ConstantU32(state, 0);
|
||||
const auto one = ConstantU32(state, 1);
|
||||
const auto add = [&](uint32_t lhs, uint32_t rhs) {
|
||||
return lhs == zero ? rhs : rhs == zero ? lhs
|
||||
: Binary(state, spv::OpIAdd, TypeU32(state), lhs, rhs);
|
||||
};
|
||||
const auto mul = [&](uint32_t lhs, uint32_t rhs) {
|
||||
return lhs == zero || rhs == zero ? zero
|
||||
: lhs == one ? rhs
|
||||
: rhs == one ? lhs
|
||||
: Binary(state, spv::OpIMul, TypeU32(state), lhs, rhs);
|
||||
};
|
||||
if (immediate != 0u) {
|
||||
offset = add(offset, ConstantU32(state, immediate));
|
||||
}
|
||||
auto address = add(mul(index, stride), offset);
|
||||
if (swizzle != 0u) {
|
||||
const auto index_shift =
|
||||
Binary(state, spv::OpIAdd, TypeU32(state), index_stride, ConstantU32(state, 3));
|
||||
const auto indices = Binary(state, spv::OpShiftLeftLogical, TypeU32(state),
|
||||
ConstantU32(state, 1), index_shift);
|
||||
const auto index_msb =
|
||||
Binary(state, spv::OpShiftRightLogical, TypeU32(state), index, index_shift);
|
||||
const auto index_lsb =
|
||||
Binary(state, spv::OpBitwiseAnd, TypeU32(state), index,
|
||||
Binary(state, spv::OpISub, TypeU32(state), indices, ConstantU32(state, 1)));
|
||||
const auto offset_msb =
|
||||
Binary(state, spv::OpBitwiseAnd, TypeU32(state), offset, ConstantU32(state, ~3u));
|
||||
const auto offset_lsb =
|
||||
Binary(state, spv::OpBitwiseAnd, TypeU32(state), offset, ConstantU32(state, 3u));
|
||||
const auto indexed_msb = stride == 1u ? index_msb
|
||||
: Binary(state, spv::OpIMul, TypeU32(state),
|
||||
index_msb, ConstantU32(state, stride));
|
||||
const auto msb = Binary(state, spv::OpIMul, TypeU32(state),
|
||||
Binary(state, spv::OpIAdd, TypeU32(state), indexed_msb, offset_msb),
|
||||
ConstantU32(state, index_stride));
|
||||
const auto lsb = Binary(state, spv::OpIAdd, TypeU32(state),
|
||||
Binary(state, spv::OpShiftLeftLogical, TypeU32(state), index_lsb,
|
||||
ConstantU32(state, 2u)),
|
||||
offset_lsb);
|
||||
address = Binary(state, spv::OpIAdd, TypeU32(state), msb, lsb);
|
||||
const auto msb = mul(add(mul(index_msb, stride), offset_msb), indices);
|
||||
const auto lsb = add(Binary(state, spv::OpShiftLeftLogical, TypeU32(state), index_lsb,
|
||||
ConstantU32(state, 2u)), offset_lsb);
|
||||
address = Select(state, TypeU32(state), swizzle, add(msb, lsb), address);
|
||||
}
|
||||
return {offset, add(address, soffset)};
|
||||
}
|
||||
|
||||
const auto soffset_value = inst.Arg(3).Resolve();
|
||||
if (soffset_value.IsImmediate() && soffset_value.GetType() == IR::Type::U32 &&
|
||||
soffset_value.U32() == 0u) {
|
||||
return address;
|
||||
uint32_t BufferLane(EmitterState& state) {
|
||||
return Binary(state, spv::OpBitwiseAnd, TypeU32(state), EmitSubgroupLocalInvocationId(state),
|
||||
ConstantU32(state, 63));
|
||||
}
|
||||
|
||||
uint32_t BufferByteAddress(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryInfo& mem) {
|
||||
auto& state = ctx.state;
|
||||
const auto packed = StorageBufferPackedStride(state, mem);
|
||||
const auto stride = packed & 0x3fffu;
|
||||
auto index = ctx.Arg(inst, 1);
|
||||
if ((packed & (1u << 20u)) != 0u) {
|
||||
index = Binary(state, spv::OpIAdd, TypeU32(state), index, BufferLane(state));
|
||||
}
|
||||
return Binary(state, spv::OpIAdd, TypeU32(state), address, soffset);
|
||||
const bool swizzle = stride != 0u && (packed & (1u << 14u)) != 0u;
|
||||
return CalculateBufferAddress(state, index, ctx.Arg(inst, 2), ctx.Arg(inst, 3), mem.offset,
|
||||
ConstantU32(state, stride),
|
||||
swizzle ? ConstantBool(state, true) : 0u,
|
||||
ConstantU32(state, (packed >> 16u) & 3u))
|
||||
.byte;
|
||||
}
|
||||
|
||||
uint32_t AddU64Low(EmitterState& state, uint32_t low, uint32_t high, uint32_t add_low,
|
||||
@@ -196,11 +206,8 @@ uint32_t LoadBdaDword(ValueEmitContext& ctx, uint32_t address) {
|
||||
});
|
||||
}
|
||||
|
||||
uint32_t LoadBda(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryInfo& mem,
|
||||
uint32_t bits) {
|
||||
auto& state = ctx.state;
|
||||
const auto address = GuestAddress(ctx, inst, mem);
|
||||
const auto active = ctx.Arg(inst, inst.NumArgs() - 1);
|
||||
uint32_t LoadBda(ValueEmitContext& ctx, uint32_t address, uint32_t active, uint32_t bits) {
|
||||
auto& state = ctx.state;
|
||||
return EmitValueOrZeroIfCondition(state, active, [&]() {
|
||||
const auto aligned = Binary(state, spv::OpBitwiseAnd, TypeScalarU64(state), address,
|
||||
ConstantDeviceAddress(state, ~uint64_t {3}));
|
||||
@@ -236,8 +243,7 @@ uint32_t LoadBda(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryIn
|
||||
|
||||
uint32_t ByteAddress(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryInfo& mem) {
|
||||
if (mem.kind == IR::ResourceKind::Buffer) {
|
||||
return BufferByteAddress(ctx, inst, mem, ctx.Arg(inst, 1), ctx.Arg(inst, 2),
|
||||
ctx.Arg(inst, 3));
|
||||
return BufferByteAddress(ctx, inst, mem);
|
||||
}
|
||||
if (mem.kind == IR::ResourceKind::Lds || mem.kind == IR::ResourceKind::Gds) {
|
||||
if (mem.offset == 0u) {
|
||||
@@ -734,12 +740,86 @@ void FormattedStore(ValueEmitContext& ctx, const IR::Inst& inst, const IR::Memor
|
||||
});
|
||||
}
|
||||
|
||||
uint32_t LoadIndirectBuffer(ValueEmitContext& ctx, const IR::Inst& inst, uint32_t components) {
|
||||
auto& state = ctx.state;
|
||||
const auto& handle = *inst.Arg(0).ResolveInstruction();
|
||||
const auto word1 = ctx.Arg(handle, 1);
|
||||
const auto records = ctx.Arg(handle, 2);
|
||||
const auto word3 = ctx.Arg(handle, 3);
|
||||
const auto field = [&](uint32_t word, uint32_t first, uint32_t count) {
|
||||
return EmitBitFieldUExtract(state, word, ConstantU32(state, first),
|
||||
ConstantU32(state, count));
|
||||
};
|
||||
const auto nonzero = [&](uint32_t value) {
|
||||
return Binary(state, spv::OpINotEqual, TypeBool(state), value, ConstantU32(state, 0));
|
||||
};
|
||||
const auto has_dword = [&](uint32_t offset, uint32_t size) {
|
||||
return AndCondition(
|
||||
state,
|
||||
Binary(state, spv::OpUGreaterThanEqual, TypeBool(state), size, ConstantU32(state, 4)),
|
||||
Binary(state, spv::OpULessThanEqual, TypeBool(state), offset,
|
||||
Binary(state, spv::OpISub, TypeU32(state), size, ConstantU32(state, 4))));
|
||||
};
|
||||
const auto stride = field(word1, 16, 14);
|
||||
const auto swizzle = AndCondition(state, nonzero(stride), nonzero(field(word1, 31, 1)));
|
||||
const auto index_stride = field(word3, 21, 2);
|
||||
const auto add_tid = nonzero(field(word3, 23, 1));
|
||||
const auto index =
|
||||
Binary(state, spv::OpIAdd, TypeU32(state), ctx.Arg(inst, 1),
|
||||
Select(state, TypeU32(state), add_tid, BufferLane(state), ConstantU32(state, 0)));
|
||||
const auto soffset = ctx.Arg(inst, 3);
|
||||
const auto base = DeviceAddressFromWords(state, ctx.Arg(handle, 0), field(word1, 0, 16));
|
||||
const auto valid_format = nonzero(field(word3, 12, 7));
|
||||
const auto mode = field(word3, 28, 2);
|
||||
const auto index_in_bounds = Binary(state, spv::OpULessThan, TypeBool(state), index, records);
|
||||
const auto scalar_in_bounds =
|
||||
Binary(state, spv::OpULessThanEqual, TypeBool(state), soffset, records);
|
||||
const auto raw_records = Binary(state, spv::OpISub, TypeU32(state), records, soffset);
|
||||
const auto raw_index_in_bounds =
|
||||
Binary(state, spv::OpULessThan, TypeBool(state), index, raw_records);
|
||||
std::array<uint32_t, 4> values {};
|
||||
for (uint32_t component = 0; component < components; component++) {
|
||||
const auto address = CalculateBufferAddress(state, index, ctx.Arg(inst, 2), soffset,
|
||||
ctx.Memory(inst).offset + component * 4u,
|
||||
stride, swizzle, index_stride);
|
||||
// RDNA2 raw DWORD vectors check each component, unlike formatted accesses.
|
||||
const auto structured_bounds =
|
||||
AndCondition(state, index_in_bounds,
|
||||
Binary(state, spv::OpULessThan, TypeBool(state), address.offset, stride));
|
||||
// OOB_SELECT=3 reduces NUM_RECORDS by SOFFSET before the offset/index check.
|
||||
const auto raw_bounds = AndCondition(
|
||||
state, scalar_in_bounds,
|
||||
Select(state, TypeBool(state), swizzle,
|
||||
AndCondition(state, raw_index_in_bounds, has_dword(address.offset, stride)),
|
||||
has_dword(address.offset, raw_records)));
|
||||
auto in_bounds =
|
||||
Select(state, TypeBool(state),
|
||||
Binary(state, spv::OpIEqual, TypeBool(state), mode, ConstantU32(state, 0)),
|
||||
structured_bounds, index_in_bounds);
|
||||
in_bounds = Select(
|
||||
state, TypeBool(state),
|
||||
Binary(state, spv::OpULessThan, TypeBool(state), mode, ConstantU32(state, 2)),
|
||||
in_bounds,
|
||||
Select(state, TypeBool(state),
|
||||
Binary(state, spv::OpIEqual, TypeBool(state), mode, ConstantU32(state, 2)),
|
||||
nonzero(records), raw_bounds));
|
||||
const auto guest =
|
||||
Binary(state, spv::OpIAdd, TypeScalarU64(state), base,
|
||||
Unary(state, spv::OpUConvert, TypeScalarU64(state), address.byte));
|
||||
values[component] = LoadBda(ctx, guest, AndCondition(state, valid_format, in_bounds), 32u);
|
||||
}
|
||||
return ConstructU32Composite(state, components, values);
|
||||
}
|
||||
|
||||
uint32_t LoadWideBuffer(ValueEmitContext& ctx, const IR::Inst& inst, uint32_t components) {
|
||||
auto& state = ctx.state;
|
||||
return EmitValueOrDefaultIfCondition(
|
||||
state, ctx.Arg(inst, inst.NumArgs() - 1), TypeU32Composite(state, components),
|
||||
ConstantU32CompositeZero(state, components), [&]() {
|
||||
const auto mem = ctx.Memory(inst);
|
||||
if (mem.kind == IR::ResourceKind::IndirectBuffer) {
|
||||
return LoadIndirectBuffer(ctx, inst, components);
|
||||
}
|
||||
const auto resource = PrepareMemoryResourceAccess(state, mem);
|
||||
const auto info = Format::GetFormatInfo(
|
||||
mem.formatted ? BufferFormat(ctx, mem) : Prospero::BufferFormat::kInvalid);
|
||||
@@ -1088,7 +1168,8 @@ void EmitLoadMemory(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
value = LoadWideShared(ctx, inst, shared_components);
|
||||
else if (address_info.access == IR::AddressAccess::Read &&
|
||||
mem.kind != IR::ResourceKind::Scratch)
|
||||
value = LoadBda(ctx, inst, mem, address_info.data_bits);
|
||||
value = LoadBda(ctx, GuestAddress(ctx, inst, mem), ctx.Arg(inst, inst.NumArgs() - 1),
|
||||
address_info.data_bits);
|
||||
else if (op == IR::ValueOpcode::LoadBufferU32 && mem.formatted)
|
||||
value = FormattedLoad(ctx, inst, mem);
|
||||
else if (inst.GetType() == IR::Type::U8)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h"
|
||||
#include "graphics/shader/recompiler/backend/spirv/spirvEmitterInstructions.h"
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter {
|
||||
namespace {
|
||||
@@ -127,10 +127,7 @@ void EmitMeshEntryPoint(EmitterState& state) {
|
||||
state.builder.AddFunction(spv::OpFunctionCall, TypeVoid(state), state.builder.AllocateId(),
|
||||
state.mesh_guest_func);
|
||||
// All guest waves finish before the uniform Vulkan allocation and output stores.
|
||||
state.builder.AddFunction(spv::OpControlBarrier, ConstantU32(state, spv::ScopeWorkgroup),
|
||||
ConstantU32(state, spv::ScopeWorkgroup),
|
||||
ConstantU32(state, spv::MemorySemanticsAcquireReleaseMask |
|
||||
spv::MemorySemanticsWorkgroupMemoryMask));
|
||||
EmitBarrier(state);
|
||||
const auto vertices = MeshLoad(state, state.mesh_allocation, spv::StorageClassWorkgroup,
|
||||
TypeU32(state), ConstantU32(state, 0));
|
||||
const auto primitives = MeshLoad(state, state.mesh_allocation, spv::StorageClassWorkgroup,
|
||||
|
||||
@@ -553,8 +553,17 @@ void DefineOutputs(EmitterState& state) {
|
||||
const auto type = uint_output ? TypeU32Vector(state, 4) : TypeF32Vector(state, 4);
|
||||
binding.variable_id = DefineInterfaceVariable(state, type, spv::StorageClassOutput,
|
||||
binding.debug_name.c_str());
|
||||
const bool dual_source = binding.kind == IR::StageOutputKind::Mrt &&
|
||||
state.program.stage == ShaderType::Pixel &&
|
||||
state.input_info.pixel->dual_source_blending;
|
||||
EXIT_NOT_IMPLEMENTED(dual_source && binding.index > 1);
|
||||
state.builder.AddAnnotation(spv::OpDecorate, binding.variable_id,
|
||||
spv::DecorationLocation, binding.location);
|
||||
spv::DecorationLocation,
|
||||
dual_source ? 0u : binding.location);
|
||||
if (dual_source) {
|
||||
state.builder.AddAnnotation(spv::OpDecorate, binding.variable_id,
|
||||
spv::DecorationIndex, binding.index);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,7 +74,8 @@ constexpr OpcodeMap SOPC_OPCODE_LIST[] = {
|
||||
{0x03u, Opcode::S_CMP_GE_I32}, {0x04u, Opcode::S_CMP_LT_I32}, {0x05u, Opcode::S_CMP_LE_I32},
|
||||
{0x06u, Opcode::S_CMP_EQ_U32}, {0x07u, Opcode::S_CMP_LG_U32}, {0x08u, Opcode::S_CMP_GT_U32},
|
||||
{0x09u, Opcode::S_CMP_GE_U32}, {0x0au, Opcode::S_CMP_LT_U32}, {0x0bu, Opcode::S_CMP_LE_U32},
|
||||
{0x0cu, Opcode::S_BITCMP0_B32}, {0x0du, Opcode::S_BITCMP1_B32}, {0x12u, Opcode::S_CMP_EQ_U64},
|
||||
{0x0cu, Opcode::S_BITCMP0_B32}, {0x0du, Opcode::S_BITCMP1_B32},
|
||||
{0x0eu, Opcode::S_BITCMP0_B64}, {0x0fu, Opcode::S_BITCMP1_B64}, {0x12u, Opcode::S_CMP_EQ_U64},
|
||||
{0x13u, Opcode::S_CMP_LG_U64},
|
||||
};
|
||||
|
||||
|
||||
@@ -472,8 +472,9 @@ std::string OperandToString(const Operand& operand) {
|
||||
text += ".clamp";
|
||||
}
|
||||
if (operand.dpp) {
|
||||
text += fmt::format(".dpp(ctrl=0x{:x},fi={},bc={})", operand.dpp_ctrl,
|
||||
operand.dpp_fetch_inactive ? 1u : 0u, operand.dpp_bound_ctrl ? 1u : 0u);
|
||||
text += fmt::format(".{}(ctrl=0x{:x},fi={},bc={})", operand.dpp8 ? "dpp8" : "dpp",
|
||||
operand.dpp_ctrl, operand.dpp_fetch_inactive ? 1u : 0u,
|
||||
operand.dpp_bound_ctrl ? 1u : 0u);
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
@@ -73,6 +73,8 @@ enum class Opcode {
|
||||
S_SUB_I32,
|
||||
S_BITCMP0_B32,
|
||||
S_BITCMP1_B32,
|
||||
S_BITCMP0_B64,
|
||||
S_BITCMP1_B64,
|
||||
S_BITSET0_B32,
|
||||
S_BITSET1_B32,
|
||||
S_BITSET0_B64,
|
||||
@@ -228,6 +230,7 @@ enum class Opcode {
|
||||
V_CVT_PKRTZ_F16_F32,
|
||||
V_CVT_PK_U8_F32,
|
||||
V_MAD_F32,
|
||||
V_MAD_I16,
|
||||
V_MAD_I32_I24,
|
||||
V_MAD_U32_U24,
|
||||
V_MAD_U64_U32,
|
||||
@@ -300,6 +303,7 @@ enum class Opcode {
|
||||
V_SUBREV_NC_U32,
|
||||
V_ADD_NC_U16,
|
||||
V_SUB_NC_U16,
|
||||
V_MUL_LO_U16,
|
||||
V_MAX_U16,
|
||||
V_MAX_I16,
|
||||
V_MIN_U16,
|
||||
@@ -380,6 +384,7 @@ enum class Opcode {
|
||||
V_CMP_GE_F16,
|
||||
V_CMP_NGT_F16,
|
||||
V_CMP_NEQ_F16,
|
||||
V_CMP_NLT_F16,
|
||||
V_CMPX_LT_F16,
|
||||
V_CMPX_EQ_F16,
|
||||
V_CMPX_LE_F16,
|
||||
@@ -667,6 +672,7 @@ struct Operand {
|
||||
bool absolute = false;
|
||||
bool clamp = false;
|
||||
bool dpp = false;
|
||||
bool dpp8 = false;
|
||||
};
|
||||
|
||||
struct Instruction {
|
||||
|
||||
@@ -247,7 +247,7 @@ constexpr VopcOpcodeInfo VOPC_OPCODE_LIST[] = {
|
||||
{0xcau, Opcode::V_CMP_EQ_F16}, {0xcbu, Opcode::V_CMP_LE_F16},
|
||||
{0xccu, Opcode::V_CMP_GT_F16}, {0xcdu, Opcode::V_CMP_LG_F16},
|
||||
{0xceu, Opcode::V_CMP_GE_F16}, {0xebu, Opcode::V_CMP_NGT_F16},
|
||||
{0xedu, Opcode::V_CMP_NEQ_F16},
|
||||
{0xedu, Opcode::V_CMP_NEQ_F16}, {0xeeu, Opcode::V_CMP_NLT_F16},
|
||||
{0xd9u, Opcode::V_CMPX_LT_F16}, {0xdau, Opcode::V_CMPX_EQ_F16},
|
||||
{0xdbu, Opcode::V_CMPX_LE_F16}, {0xdcu, Opcode::V_CMPX_GT_F16},
|
||||
{0xdeu, Opcode::V_CMPX_GE_F16}, {0xfbu, Opcode::V_CMPX_NGT_F16},
|
||||
@@ -295,6 +295,7 @@ constexpr OpcodeMap VOP3_OPCODE_LIST[] = {
|
||||
{0x300u, Opcode::V_LSHRREV_B64},
|
||||
{0x303u, Opcode::V_ADD_NC_U16},
|
||||
{0x304u, Opcode::V_SUB_NC_U16},
|
||||
{0x305u, Opcode::V_MUL_LO_U16},
|
||||
{0x307u, Opcode::V_LSHRREV_B16},
|
||||
{0x308u, Opcode::V_ASHRREV_I16},
|
||||
{0x309u, Opcode::V_MAX_U16},
|
||||
@@ -311,6 +312,7 @@ constexpr OpcodeMap VOP3_OPCODE_LIST[] = {
|
||||
{0x345u, Opcode::V_XAD_U32},
|
||||
{0x346u, Opcode::V_LSHL_ADD_U32},
|
||||
{0x347u, Opcode::V_ADD_LSHL_U32},
|
||||
{0x35eu, Opcode::V_MAD_I16},
|
||||
{0x360u, Opcode::V_READLANE_B32},
|
||||
{0x361u, Opcode::V_WRITELANE_B32},
|
||||
{0x362u, Opcode::V_LDEXP_F32},
|
||||
@@ -423,13 +425,14 @@ bool IsNativeVop3F16TernaryOpcode(Opcode opcode) {
|
||||
}
|
||||
|
||||
bool IsNativeVop3I16TernaryOpcode(Opcode opcode) {
|
||||
return opcode == Opcode::V_MED3_I16;
|
||||
return opcode == Opcode::V_MED3_I16 || opcode == Opcode::V_MAD_I16;
|
||||
}
|
||||
|
||||
bool IsNativeVop3B16BinaryOpcode(Opcode opcode) {
|
||||
switch (opcode) {
|
||||
case Opcode::V_ADD_NC_U16:
|
||||
case Opcode::V_SUB_NC_U16:
|
||||
case Opcode::V_MUL_LO_U16:
|
||||
case Opcode::V_MAX_U16:
|
||||
case Opcode::V_MAX_I16:
|
||||
case Opcode::V_MIN_U16:
|
||||
@@ -518,13 +521,14 @@ 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_NOT_B32, SdwaSelBytes() | SdwaSelWords() | SdwaSelFull(), SdwaSelBytes() | SdwaSelWords(),
|
||||
SdwaSelFull(), 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, SdwaSelBytes() | SdwaSelWords() | SdwaSelFull(), SdwaSelWords(),
|
||||
SdwaSelBytes() | SdwaSelWords() | SdwaSelFull(), true},
|
||||
{Opcode::V_CVT_F16_U16, SdwaSelWords() | SdwaSelFull(), SdwaSelWords(),
|
||||
SdwaSelWords() | SdwaSelFull(), false},
|
||||
{Opcode::V_CVT_F16_U16, SdwaSelBytes() | SdwaSelWords() | SdwaSelFull(), SdwaSelWords(),
|
||||
SdwaSelBytes() | SdwaSelWords() | SdwaSelFull(), false},
|
||||
{Opcode::V_CVT_U16_F16, SdwaSelWords() | SdwaSelFull(), SdwaSelWords(),
|
||||
SdwaSelWords() | SdwaSelFull(), true},
|
||||
{Opcode::V_CVT_F16_I16, SdwaSelWords() | SdwaSelFull(), SdwaSelWords(),
|
||||
@@ -614,13 +618,14 @@ bool SupportsVop1Clamp(Opcode opcode) {
|
||||
}
|
||||
|
||||
bool ValidateVop1Sdwa(Instruction& inst, uint32_t opcode, uint32_t modifier) {
|
||||
const auto dst_sel = (modifier >> 8u) & 0x7u;
|
||||
const auto dst_u = (modifier >> 11u) & 0x3u;
|
||||
const auto clamp = (modifier >> 13u) & 0x1u;
|
||||
const auto omod = (modifier >> 14u) & 0x3u;
|
||||
const auto src0_sel = (modifier >> 16u) & 0x7u;
|
||||
const auto src0_neg = (modifier >> 20u) & 0x1u;
|
||||
const auto src0_abs = (modifier >> 21u) & 0x1u;
|
||||
const auto dst_sel = (modifier >> 8u) & 0x7u;
|
||||
const auto dst_u = (modifier >> 11u) & 0x3u;
|
||||
const auto clamp = (modifier >> 13u) & 0x1u;
|
||||
const auto omod = (modifier >> 14u) & 0x3u;
|
||||
const auto src0_sel = (modifier >> 16u) & 0x7u;
|
||||
const auto src0_sext = (modifier >> 19u) & 0x1u;
|
||||
const auto src0_neg = (modifier >> 20u) & 0x1u;
|
||||
const auto src0_abs = (modifier >> 21u) & 0x1u;
|
||||
|
||||
if (src0_sel > 6u || dst_sel > 6u) {
|
||||
SetUnsupported(inst, Family::VOP1, opcode, "VOP1 SDWA selector is invalid");
|
||||
@@ -636,7 +641,8 @@ bool ValidateVop1Sdwa(Instruction& inst, uint32_t opcode, uint32_t modifier) {
|
||||
"VOP1 SDWA destination selector is not supported");
|
||||
return false;
|
||||
}
|
||||
if (!IsVop1SdwaSourceSupported(inst.opcode, src0_sel, src0_neg != 0u, src0_abs != 0u)) {
|
||||
if (!IsVop1SdwaSourceSupported(inst.opcode, src0_sel, src0_neg != 0u, src0_abs != 0u) ||
|
||||
(inst.opcode == Opcode::V_CVT_F16_U16 && src0_sel <= 3u && src0_sext != 0u)) {
|
||||
SetUnsupported(inst, Family::VOP1, opcode, "VOP1 SDWA source selector is not supported");
|
||||
return false;
|
||||
}
|
||||
@@ -682,10 +688,15 @@ void DecodeVop1Sdwa(uint32_t pc, std::span<const uint32_t> code, uint32_t word_i
|
||||
ReadLiteralOperands(code, word_index, inst);
|
||||
}
|
||||
|
||||
void ApplyDppModifier(Operand& operand, uint32_t modifier) {
|
||||
void ApplyDppModifier(Operand& operand, uint32_t modifier, uint32_t encoding) {
|
||||
operand.dpp = true;
|
||||
if (encoding == 233u) {
|
||||
operand.dpp8 = true;
|
||||
operand.dpp_ctrl = modifier >> 8u;
|
||||
return;
|
||||
}
|
||||
operand.negate = ((modifier >> 20u) & 0x1u) != 0u;
|
||||
operand.absolute = ((modifier >> 21u) & 0x1u) != 0u;
|
||||
operand.dpp = true;
|
||||
operand.dpp_ctrl = (modifier >> 8u) & 0x1ffu;
|
||||
operand.dpp_fetch_inactive = ((modifier >> 18u) & 0x1u) != 0u;
|
||||
operand.dpp_bound_ctrl = ((modifier >> 19u) & 0x1u) != 0u;
|
||||
@@ -706,7 +717,7 @@ void DecodeVop1Dpp(uint32_t pc, std::span<const uint32_t> code, uint32_t word_in
|
||||
DecodeVectorGpr(vdst, inst.dst);
|
||||
}
|
||||
DecodeScalarSource(src0 + 256u, pc, inst.src0);
|
||||
ApplyDppModifier(inst.src0, modifier);
|
||||
ApplyDppModifier(inst.src0, modifier, code[word_index] & 0x1ffu);
|
||||
inst.src_count = 1;
|
||||
|
||||
if (!IsVop1FloatSourceOpcode(inst.opcode) && (inst.src0.negate || inst.src0.absolute)) {
|
||||
@@ -823,6 +834,7 @@ bool IsVopcFloatCompareOpcode(Opcode opcode) {
|
||||
case Opcode::V_CMP_GE_F16:
|
||||
case Opcode::V_CMP_NGT_F16:
|
||||
case Opcode::V_CMP_NEQ_F16:
|
||||
case Opcode::V_CMP_NLT_F16:
|
||||
case Opcode::V_CMPX_LT_F16:
|
||||
case Opcode::V_CMPX_EQ_F16:
|
||||
case Opcode::V_CMPX_LE_F16:
|
||||
@@ -1044,7 +1056,7 @@ void DecodeVop2Dpp(uint32_t pc, std::span<const uint32_t> code, uint32_t word_in
|
||||
DecodeVectorGpr(vdst, inst.dst);
|
||||
DecodeVectorGpr(vsrc1, inst.src1);
|
||||
DecodeScalarSource(src0 + 256u, pc, inst.src0);
|
||||
ApplyDppModifier(inst.src0, modifier);
|
||||
ApplyDppModifier(inst.src0, modifier, code[word_index] & 0x1ffu);
|
||||
inst.src1.negate = ((modifier >> 22u) & 0x1u) != 0u;
|
||||
inst.src1.absolute = ((modifier >> 23u) & 0x1u) != 0u;
|
||||
const bool packed_fmac = inst.opcode == Opcode::V_PK_FMAC_F16;
|
||||
@@ -1153,7 +1165,7 @@ void DecodeVopcDpp(uint32_t pc, std::span<const uint32_t> code, uint32_t word_in
|
||||
DecodeVectorGpr(vsrc1, inst.src1);
|
||||
DecodeScalarSource(src0 + 256u, pc, inst.src0);
|
||||
inst.dst.kind = IsVopcCompareExec(inst.opcode) ? OperandKind::ExecLo : OperandKind::VccLo;
|
||||
ApplyDppModifier(inst.src0, modifier);
|
||||
ApplyDppModifier(inst.src0, modifier, code[word_index] & 0x1ffu);
|
||||
inst.src1.negate = ((modifier >> 22u) & 0x1u) != 0u;
|
||||
inst.src1.absolute = ((modifier >> 23u) & 0x1u) != 0u;
|
||||
inst.src_count = 2;
|
||||
@@ -1169,6 +1181,7 @@ uint32_t NativeVop3SourceCount(Opcode opcode) {
|
||||
case Opcode::V_MUL_I32_I24:
|
||||
case Opcode::V_ADD_NC_U16:
|
||||
case Opcode::V_SUB_NC_U16:
|
||||
case Opcode::V_MUL_LO_U16:
|
||||
case Opcode::V_MAX_U16:
|
||||
case Opcode::V_MAX_I16:
|
||||
case Opcode::V_MIN_U16:
|
||||
@@ -1353,7 +1366,8 @@ bool SupportsNativeVop3ResultModifiers(Opcode opcode) {
|
||||
}
|
||||
|
||||
bool SupportsNativeVop3Clamp(Opcode opcode) {
|
||||
return SupportsNativeVop3ResultModifiers(opcode) || UsesInexactClampControl(opcode);
|
||||
return SupportsNativeVop3ResultModifiers(opcode) || UsesInexactClampControl(opcode) ||
|
||||
opcode == Opcode::V_MAD_I16;
|
||||
}
|
||||
|
||||
bool HasUnsupportedNativeVop3Modifiers(Opcode opcode, bool permlane, bool mad_mix,
|
||||
@@ -1374,7 +1388,7 @@ bool HasUnsupportedNativeVop3Modifiers(Opcode opcode, bool permlane, bool mad_mi
|
||||
return opcode != Opcode::V_FMA_F16 && (clamp != 0u || omod != 0u);
|
||||
}
|
||||
if (IsNativeVop3I16TernaryOpcode(opcode)) {
|
||||
return abs != 0u || clamp != 0u || omod != 0u || neg != 0u;
|
||||
return abs != 0u || (clamp != 0u && !clamp_modifier) || omod != 0u || neg != 0u;
|
||||
}
|
||||
if (IsNativeVop3B16BinaryOpcode(opcode)) {
|
||||
return abs != 0u || clamp != 0u || omod != 0u || neg != 0u;
|
||||
@@ -1508,6 +1522,7 @@ void DecodeVop1(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index
|
||||
return;
|
||||
}
|
||||
switch (src0) {
|
||||
case 233u: DecodeVop1Dpp(pc, code, word_index, opcode, vdst, inst); return;
|
||||
case 249u: DecodeVop1Sdwa(pc, code, word_index, opcode, vdst, inst); return;
|
||||
case 250u: DecodeVop1Dpp(pc, code, word_index, opcode, vdst, inst); return;
|
||||
default: break;
|
||||
|
||||
@@ -166,9 +166,9 @@ void Translator::V_CVT_PK_U8_F32(const Decoder::Instruction& inst) {
|
||||
}
|
||||
|
||||
void Translator::V_PACK_B32_F16(const Decoder::Instruction& inst) {
|
||||
const auto low = ReadF16LaneBits(inst.src0, false);
|
||||
const auto low = Read16LaneBits(inst.src0, false);
|
||||
const auto high =
|
||||
ir.ShiftLeftLogical(ReadF16LaneBits(inst.src1, false), IR::U32(IR::Value(16u)));
|
||||
ir.ShiftLeftLogical(Read16LaneBits(inst.src1, false), IR::U32(IR::Value(16u)));
|
||||
WriteOperand(DestinationOperand(inst), ir.BitwiseOr(low, high));
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,18 @@ bool Translator::Integer16Binary(const Decoder::Instruction& inst, IR::ValueOpco
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Translator::V_MAD_I16(const Decoder::Instruction& inst) {
|
||||
const auto lhs = ReadU16AsU32(inst.src0, true);
|
||||
const auto rhs = ReadU16AsU32(inst.src1, true);
|
||||
auto result = ir.IAdd(ir.IMul(lhs, rhs), ReadU16AsU32(inst.src2, true));
|
||||
if (inst.dst.clamp) {
|
||||
result = IR::U32(ir.Emit(IR::ValueOpcode::SMax32, {result, IR::Value(0xffff8000u)}));
|
||||
result = IR::U32(ir.Emit(IR::ValueOpcode::SMin32, {result, IR::Value(0x7fffu)}));
|
||||
}
|
||||
Write16Bits(DestinationOperand(inst), ir.BitwiseAnd(result, IR::U32(IR::Value(0xffffu))));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Translator::V_MED3_I16(const Decoder::Instruction& inst) {
|
||||
const auto result = IR::U32(ir.Emit(
|
||||
IR::ValueOpcode::SMedTri32, {ReadU16AsU32(inst.src0, true), ReadU16AsU32(inst.src1, true),
|
||||
@@ -495,6 +507,18 @@ bool Translator::S_BITCMP_B32(const Decoder::Instruction& inst, bool expected) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Translator::S_BITCMP_B64(const Decoder::Instruction& inst, bool expected) {
|
||||
const auto value = ReadU32Pair(inst.src0);
|
||||
const auto offset = ir.BitwiseAnd(ReadU32(inst.src1), IR::U32(IR::Value(63u)));
|
||||
const auto word_bit = ir.BitwiseAnd(offset, IR::U32(IR::Value(31u)));
|
||||
const auto word =
|
||||
ir.Select(ir.ULessThan(offset, IR::U32(IR::Value(32u))), value[0], value[1]);
|
||||
const auto bit =
|
||||
IR::U32(ir.Emit(IR::ValueOpcode::BitFieldUExtract, {word, word_bit, IR::Value(1u)}));
|
||||
WriteCompareResult(inst.dst, ir.IEqual(bit, IR::U32(IR::Value(expected ? 1u : 0u))));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Translator::V_ALIGNBIT_B32(const Decoder::Instruction& inst) {
|
||||
const auto hi = ReadU32(inst.src0);
|
||||
const auto lo = ReadU32(inst.src1);
|
||||
|
||||
@@ -34,6 +34,7 @@ Decoder::Operand OffsetDecodedRegister(const Decoder::Operand& operand, uint32_t
|
||||
result.negate_hi = false;
|
||||
result.absolute = false;
|
||||
result.dpp_ctrl = 0;
|
||||
result.dpp8 = false;
|
||||
result.dpp_row_mask = 0xf;
|
||||
result.dpp_bank_mask = 0xf;
|
||||
result.explicit_sdwa_dst = false;
|
||||
|
||||
@@ -208,6 +208,8 @@ bool Translator::EmitScalar(const Decoder::Instruction& inst) {
|
||||
case O::S_BFE_U64: return S_BFE_U64(inst);
|
||||
case O::S_BITCMP0_B32: return S_BITCMP_B32(inst, false);
|
||||
case O::S_BITCMP1_B32: return S_BITCMP_B32(inst, true);
|
||||
case O::S_BITCMP0_B64: return S_BITCMP_B64(inst, false);
|
||||
case O::S_BITCMP1_B64: return S_BITCMP_B64(inst, true);
|
||||
case O::S_PACK_LL_B32_B16: return PackB16(inst, false, false);
|
||||
case O::S_PACK_LH_B32_B16: return PackB16(inst, false, true);
|
||||
case O::S_PACK_HH_B32_B16: return PackB16(inst, true, true);
|
||||
|
||||
@@ -9,6 +9,17 @@
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::Frontend {
|
||||
|
||||
static IR::DppMoveFlags DppFlags(const Decoder::Operand& operand) {
|
||||
return {
|
||||
.control = operand.dpp_ctrl,
|
||||
.row_mask = operand.dpp_row_mask,
|
||||
.bank_mask = operand.dpp_bank_mask,
|
||||
.fetch_inactive = operand.dpp_fetch_inactive,
|
||||
.bound_control = operand.dpp_bound_ctrl,
|
||||
.dpp8 = operand.dpp8,
|
||||
};
|
||||
}
|
||||
|
||||
const Decoder::Operand& Translator::SourceAt(const Decoder::Instruction& inst, uint32_t index) {
|
||||
switch (index) {
|
||||
case 0: return inst.src0;
|
||||
@@ -30,6 +41,7 @@ Decoder::Operand Translator::DestinationOperand(const Decoder::Instruction& inst
|
||||
continue;
|
||||
}
|
||||
destination.dpp = true;
|
||||
destination.dpp8 = source.dpp8;
|
||||
destination.dpp_ctrl = source.dpp_ctrl;
|
||||
destination.dpp_row_mask = source.dpp_row_mask;
|
||||
destination.dpp_bank_mask = source.dpp_bank_mask;
|
||||
@@ -106,6 +118,7 @@ Decoder::Operand Translator::PlainOperand(const Decoder::Operand& operand) {
|
||||
result.dpp_fetch_inactive = false;
|
||||
result.dpp_bound_ctrl = false;
|
||||
result.dpp = false;
|
||||
result.dpp8 = false;
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -167,14 +180,8 @@ IR::U32 Translator::ReadScalarCode(uint32_t code) {
|
||||
|
||||
IR::U32 Translator::ApplyBitSourceModifiers(const Decoder::Operand& operand, IR::U32 value) {
|
||||
if (operand.dpp) {
|
||||
const IR::DppMoveFlags flags {
|
||||
.control = static_cast<uint16_t>(operand.dpp_ctrl),
|
||||
.row_mask = static_cast<uint8_t>(operand.dpp_row_mask),
|
||||
.bank_mask = static_cast<uint8_t>(operand.dpp_bank_mask),
|
||||
.fetch_inactive = operand.dpp_fetch_inactive,
|
||||
.bound_control = operand.dpp_bound_ctrl,
|
||||
};
|
||||
value = IR::U32(ir.Emit(IR::ValueOpcode::DppMoveU32, {value, ir.GetExec()}, flags));
|
||||
value =
|
||||
IR::U32(ir.Emit(IR::ValueOpcode::DppMoveU32, {value, ir.GetExec()}, DppFlags(operand)));
|
||||
}
|
||||
if (operand.sdwa_sel != 6u) {
|
||||
uint32_t offset = 0;
|
||||
@@ -301,15 +308,8 @@ void Translator::WriteRawU32(const Decoder::Operand& operand, IR::U32 value) {
|
||||
const auto reg = static_cast<IR::VectorReg>(operand.reg);
|
||||
const auto old = ir.GetVectorReg(reg);
|
||||
if (operand.dpp) {
|
||||
const IR::DppMoveFlags flags {
|
||||
.control = static_cast<uint16_t>(operand.dpp_ctrl),
|
||||
.row_mask = static_cast<uint8_t>(operand.dpp_row_mask),
|
||||
.bank_mask = static_cast<uint8_t>(operand.dpp_bank_mask),
|
||||
.fetch_inactive = operand.dpp_fetch_inactive,
|
||||
.bound_control = operand.dpp_bound_ctrl,
|
||||
};
|
||||
value = IR::U32(
|
||||
ir.Emit(IR::ValueOpcode::DppUpdateU32, {value, old, ir.GetExec()}, flags));
|
||||
value = IR::U32(ir.Emit(IR::ValueOpcode::DppUpdateU32, {value, old, ir.GetExec()},
|
||||
DppFlags(operand)));
|
||||
} else {
|
||||
value = ir.Select(ir.GetExec(), value, old);
|
||||
}
|
||||
@@ -541,10 +541,7 @@ IR::U32 Translator::ReadU16LaneRaw(const Decoder::Operand& operand, bool high_la
|
||||
|
||||
IR::U32 Translator::ReadU16LaneAsU32(const Decoder::Operand& operand, bool high_lane,
|
||||
bool sign_extend) {
|
||||
auto value = ReadU16LaneRaw(operand, high_lane);
|
||||
if (high_lane ? operand.negate_hi : operand.negate) {
|
||||
value = ir.BitwiseAnd(ir.ISub(IR::U32(IR::Value(0u)), value), IR::U32(IR::Value(0xffffu)));
|
||||
}
|
||||
auto value = Read16LaneBits(operand, high_lane);
|
||||
if (sign_extend || operand.sdwa_sext) {
|
||||
value = IR::U32(
|
||||
ir.Emit(IR::ValueOpcode::BitFieldSExtract, {value, IR::Value(0u), IR::Value(16u)}));
|
||||
@@ -556,12 +553,13 @@ IR::U32 Translator::ReadU16AsU32(const Decoder::Operand& operand, bool sign_exte
|
||||
return ReadU16LaneAsU32(operand, false, sign_extend);
|
||||
}
|
||||
|
||||
IR::U32 Translator::ReadF16LaneBits(const Decoder::Operand& operand, bool high_lane) {
|
||||
IR::U32 Translator::Read16LaneBits(const Decoder::Operand& operand, bool high_lane) {
|
||||
auto value = ReadU16LaneRaw(operand, high_lane);
|
||||
if (operand.absolute) {
|
||||
value = ir.BitwiseAnd(value, IR::U32(IR::Value(0x7fffu)));
|
||||
}
|
||||
if (high_lane ? operand.negate_hi : operand.negate) {
|
||||
// RDNA2 source NEG flips the sign bit, including packed integer operands.
|
||||
value = ir.BitwiseXor(value, IR::U32(IR::Value(0x8000u)));
|
||||
}
|
||||
return value;
|
||||
@@ -1227,6 +1225,12 @@ IR::Program TranslateProgram(const Decoder::Program& decoded, const CFG::Graph&
|
||||
builtin(IR::StageInputKind::PackedAncillary));
|
||||
}
|
||||
} else if (options.stage == ShaderType::Vertex) {
|
||||
// Vulkan owns primitive assembly; each vertex subgroup is one NGG wave.
|
||||
// Keep its full lane extent: mbcnt(-1) uses lane ordinals, not active counts.
|
||||
entry_ir.SetScalarReg(static_cast<IR::ScalarReg>(2),
|
||||
IR::U32(IR::Value(options.wave_size << 12u)));
|
||||
entry_ir.SetScalarReg(static_cast<IR::ScalarReg>(3),
|
||||
IR::U32(IR::Value((1u << 28u) | options.wave_size)));
|
||||
entry_ir.SetVectorReg(static_cast<IR::VectorReg>(5),
|
||||
builtin(IR::StageInputKind::VertexIndex));
|
||||
entry_ir.SetVectorReg(static_cast<IR::VectorReg>(8),
|
||||
|
||||
@@ -44,7 +44,7 @@ private:
|
||||
IR::U32 ReadU16LaneRaw(const Decoder::Operand& operand, bool high_lane);
|
||||
IR::U32 ReadU16LaneAsU32(const Decoder::Operand& operand, bool high_lane, bool sign_extend);
|
||||
IR::U32 ReadU16AsU32(const Decoder::Operand& operand, bool sign_extend);
|
||||
IR::U32 ReadF16LaneBits(const Decoder::Operand& operand, bool high_lane);
|
||||
IR::U32 Read16LaneBits(const Decoder::Operand& operand, bool high_lane);
|
||||
std::array<IR::U32, 2> ExtractU64(IR::U64 value);
|
||||
void WriteU32Pair(const Decoder::Operand& operand, const std::array<IR::U32, 2>& value);
|
||||
IR::U32 ConditionBit(const Decoder::Operand& operand);
|
||||
@@ -164,6 +164,7 @@ private:
|
||||
bool FloatCube(const Decoder::Instruction& inst, uint32_t result_kind);
|
||||
bool Integer16Shift(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool arithmetic);
|
||||
bool Integer16Binary(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool sign);
|
||||
bool V_MAD_I16(const Decoder::Instruction& inst);
|
||||
bool V_MED3_I16(const Decoder::Instruction& inst);
|
||||
bool PackedInteger16Shift(const Decoder::Instruction& inst, IR::ValueOpcode opcode,
|
||||
bool arithmetic);
|
||||
@@ -204,6 +205,7 @@ private:
|
||||
bool V_BFE_U32(const Decoder::Instruction& inst, bool sign);
|
||||
bool V_BFI_B32(const Decoder::Instruction& inst);
|
||||
bool S_BITCMP_B32(const Decoder::Instruction& inst, bool expected);
|
||||
bool S_BITCMP_B64(const Decoder::Instruction& inst, bool expected);
|
||||
bool V_ALIGNBIT_B32(const Decoder::Instruction& inst);
|
||||
bool V_ALIGNBYTE_B32(const Decoder::Instruction& inst);
|
||||
bool V_LSHL_ADD_U32(const Decoder::Instruction& inst);
|
||||
|
||||
@@ -276,6 +276,9 @@ bool Translator::EmitVector(const Decoder::Instruction& inst) {
|
||||
case O::V_CMPX_NEQ_F16:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordNotEqual32, true, true);
|
||||
return true;
|
||||
case O::V_CMP_NLT_F16:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordGreaterThanEqual32, true, false);
|
||||
return true;
|
||||
case O::V_CMPX_NLT_F16:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordGreaterThanEqual32, true, true);
|
||||
return true;
|
||||
@@ -324,6 +327,8 @@ bool Translator::EmitVector(const Decoder::Instruction& inst) {
|
||||
case O::V_ADD_NC_I16: return Integer16Binary(inst, IR::ValueOpcode::IAdd32, false);
|
||||
case O::V_SUB_NC_U16:
|
||||
case O::V_SUB_NC_I16: return Integer16Binary(inst, IR::ValueOpcode::ISub32, false);
|
||||
case O::V_MUL_LO_U16: return Integer16Binary(inst, IR::ValueOpcode::IMul32, false);
|
||||
case O::V_MAD_I16: return V_MAD_I16(inst);
|
||||
case O::V_MED3_I16: return V_MED3_I16(inst);
|
||||
case O::V_MIN_I16: return Integer16Binary(inst, IR::ValueOpcode::SMin32, true);
|
||||
case O::V_MAX_I16: return Integer16Binary(inst, IR::ValueOpcode::SMax32, true);
|
||||
|
||||
@@ -447,13 +447,20 @@ void ValidateProgram(const Program& program, bool require_ssa) {
|
||||
ValueOpcodeName(inst.GetOpcode())));
|
||||
}
|
||||
const auto& memory = program.memory_info[memory_index];
|
||||
if (memory.kind != ResourceKind::Buffer &&
|
||||
memory.kind != ResourceKind::ScalarBuffer) {
|
||||
const bool vector_buffer = memory.kind == ResourceKind::Buffer ||
|
||||
memory.kind == ResourceKind::IndirectBuffer;
|
||||
if (!vector_buffer && memory.kind != ResourceKind::ScalarBuffer) {
|
||||
return Fail(fmt::format("{} has a non-buffer resource kind",
|
||||
ValueOpcodeName(inst.GetOpcode())));
|
||||
}
|
||||
if (memory.kind == ResourceKind::IndirectBuffer &&
|
||||
(memory.formatted || memory.typed ||
|
||||
(inst.GetOpcode() != ValueOpcode::LoadBufferU32x2 &&
|
||||
inst.GetOpcode() != ValueOpcode::LoadBufferU32x4))) {
|
||||
return Fail("indirect buffer requires a raw DWORD x2/x4 load");
|
||||
}
|
||||
if (buffer_components > 1u &&
|
||||
(memory.kind != ResourceKind::Buffer || memory.data_bits != 32u ||
|
||||
(!vector_buffer || memory.data_bits != 32u ||
|
||||
memory.data_dwords != buffer_components || memory.component_index != 0u)) {
|
||||
return Fail(fmt::format("{} has inconsistent native-wide metadata",
|
||||
ValueOpcodeName(inst.GetOpcode())));
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
#include <array>
|
||||
#include <bit>
|
||||
#include <deque>
|
||||
#include <list>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
@@ -27,6 +28,7 @@ enum class ResourceKind {
|
||||
ScalarBuffer,
|
||||
ScalarAddress,
|
||||
Buffer,
|
||||
IndirectBuffer,
|
||||
Flat,
|
||||
Global,
|
||||
Scratch,
|
||||
@@ -457,11 +459,13 @@ struct BlockInfo {
|
||||
|
||||
struct DescriptorSource {
|
||||
struct IndirectImage {
|
||||
uint32_t material_source = 0;
|
||||
uint32_t heap_source = 0;
|
||||
uint32_t material_source = UINT32_MAX;
|
||||
uint32_t table_source = 0;
|
||||
uint32_t selector_stride = 0;
|
||||
uint32_t selector_offset = 0;
|
||||
uint32_t key_arg = 0;
|
||||
uint32_t table_offset = 0;
|
||||
uint32_t key_count = 0;
|
||||
|
||||
bool operator==(const IndirectImage& other) const = default;
|
||||
};
|
||||
@@ -505,9 +509,19 @@ struct UniformFillPlan {
|
||||
std::array<Value, 4> values;
|
||||
};
|
||||
|
||||
// Immutable runtime resource analysis retained by the shader cache. It owns descriptor/SRT,
|
||||
// uniform condition and fill values without retaining translated blocks.
|
||||
// Resource analysis retained by the shader cache. It owns immutable descriptor/SRT,
|
||||
// condition and fill values without translated blocks, plus reusable evaluation scratch.
|
||||
struct ResourcePlan {
|
||||
struct EvaluationContext {
|
||||
struct Entry {
|
||||
uint64_t value = 0;
|
||||
uint64_t generation = 0;
|
||||
};
|
||||
|
||||
std::vector<Entry> values;
|
||||
uint64_t generation = 0;
|
||||
};
|
||||
|
||||
ResourcePlan() = default;
|
||||
~ResourcePlan();
|
||||
|
||||
@@ -524,7 +538,6 @@ struct ResourcePlan {
|
||||
std::vector<MemoryInfo> memory_info;
|
||||
std::vector<DescriptorSource> descriptor_sources;
|
||||
std::vector<ResourceBlock> control_flow;
|
||||
std::vector<uint32_t> materialization_sources;
|
||||
std::vector<SrtRead> srt_reads;
|
||||
std::vector<uint8_t> clean_flat_slots;
|
||||
bool requires_specialization_memory = false;
|
||||
@@ -532,6 +545,14 @@ struct ResourcePlan {
|
||||
bool resource_tracking_complete = false;
|
||||
ShaderInfo info;
|
||||
UniformFillPlan uniform_fill;
|
||||
// GPU-thread scratch for nested clean/EXEC memos, activity and material keys.
|
||||
mutable std::deque<EvaluationContext> evaluation_contexts;
|
||||
mutable uint32_t evaluation_value_count = 0;
|
||||
mutable uint32_t evaluation_depth = 0;
|
||||
mutable std::vector<uint8_t> active_sources;
|
||||
mutable std::vector<uint8_t> visited_blocks;
|
||||
mutable std::vector<uint32_t> pending_blocks;
|
||||
mutable std::vector<uint32_t> material_keys;
|
||||
};
|
||||
|
||||
struct Program: ResourcePlan {
|
||||
|
||||
@@ -121,6 +121,13 @@ public:
|
||||
[[nodiscard]] Block* PhiBlock(size_t index) const;
|
||||
[[nodiscard]] Block* Parent() const;
|
||||
[[nodiscard]] const std::vector<Use>& Uses() const;
|
||||
// Runtime indices belong to the resource plan that owns this instruction.
|
||||
[[nodiscard]] uint32_t EvaluationIndex(uint32_t& count) const {
|
||||
if (evaluation_index == UINT32_MAX) {
|
||||
evaluation_index = count++;
|
||||
}
|
||||
return evaluation_index;
|
||||
}
|
||||
|
||||
void SetParent(Block* block);
|
||||
void SetArg(size_t index, Value value);
|
||||
@@ -155,6 +162,7 @@ private:
|
||||
std::vector<Value> args;
|
||||
std::vector<Block*> phi_blocks;
|
||||
std::vector<Use> uses;
|
||||
mutable uint32_t evaluation_index = UINT32_MAX;
|
||||
};
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::IR
|
||||
@@ -34,11 +34,12 @@ struct ImageOpcodeInfo {
|
||||
};
|
||||
|
||||
struct DppMoveFlags {
|
||||
uint16_t control = 0;
|
||||
uint8_t row_mask = 0xf;
|
||||
uint8_t bank_mask = 0xf;
|
||||
uint32_t control : 24 = 0;
|
||||
uint32_t row_mask : 4 = 0xf;
|
||||
uint32_t bank_mask : 4 = 0xf;
|
||||
bool fetch_inactive = false;
|
||||
bool bound_control = false;
|
||||
bool dpp8 = false;
|
||||
};
|
||||
static_assert(sizeof(DppMoveFlags) <= sizeof(uint64_t));
|
||||
static_assert(std::is_trivially_copyable_v<DppMoveFlags>);
|
||||
|
||||
@@ -21,18 +21,6 @@ namespace {
|
||||
constexpr uint64_t AddressMask = 0x0000ffffffffffffull;
|
||||
constexpr uint64_t MaxIndirectImageProbes = 65536u;
|
||||
|
||||
struct IndirectImage {
|
||||
uint32_t resource = 0;
|
||||
std::vector<uint32_t> keys;
|
||||
std::vector<uint32_t> candidates;
|
||||
std::vector<DescriptorValue> descriptors;
|
||||
};
|
||||
|
||||
struct MaterializedSnapshot {
|
||||
ResourceSnapshot resources;
|
||||
std::vector<IndirectImage> indirect_images;
|
||||
};
|
||||
|
||||
bool SpecializationFail(std::string_view message) {
|
||||
std::fprintf(stderr, "shader resource specialization failed: %.*s\n",
|
||||
static_cast<int>(message.size()), message.data());
|
||||
@@ -74,6 +62,13 @@ bool NullImageDescriptor(const DescriptorValue& descriptor) {
|
||||
}
|
||||
|
||||
bool ValidImageDescriptor(const DescriptorValue& descriptor, bool r128 = false) {
|
||||
const auto& words = descriptor.dwords;
|
||||
// Reject texture descriptors with nonzero reserved bits.
|
||||
if ((words[1] & 0x20000000u) != 0u || (words[2] & 0x70003000u) != 0u ||
|
||||
(!r128 && ((words[4] & 0xe000e000u) != 0u || (words[5] & 0xf9000000u) != 0u ||
|
||||
(words[6] & 0x00007b00u) != 0u))) {
|
||||
return false;
|
||||
}
|
||||
const auto type = static_cast<Prospero::ImageType>((descriptor.dwords[3] >> 28u) & 0xfu);
|
||||
const auto format = static_cast<Prospero::BufferFormat>((descriptor.dwords[1] >> 20u) & 0x1ffu);
|
||||
if (type < Prospero::ImageType::kColor1D || format == Prospero::BufferFormat::kInvalid ||
|
||||
@@ -84,6 +79,13 @@ bool ValidImageDescriptor(const DescriptorValue& descriptor, bool r128 = false)
|
||||
type != Prospero::ImageType::kColor2DMsaa) {
|
||||
return false;
|
||||
}
|
||||
const bool array = type == Prospero::ImageType::kColor1DArray ||
|
||||
type == Prospero::ImageType::kColor2DArray ||
|
||||
type == Prospero::ImageType::kColor2DMsaaArray ||
|
||||
type == Prospero::ImageType::kCube;
|
||||
if (array && ((words[4] >> 16u) & 0x1fffu) > (words[4] & 0x1fffu)) {
|
||||
return false;
|
||||
}
|
||||
if (type == Prospero::ImageType::kColor2DMsaa ||
|
||||
type == Prospero::ImageType::kColor2DMsaaArray) {
|
||||
const auto base_level = (descriptor.dwords[3] >> 12u) & 0xfu;
|
||||
@@ -173,200 +175,155 @@ void MarkCleanFlatSlots(const ResourcePlan& program, const DescriptorSource* sou
|
||||
}
|
||||
}
|
||||
|
||||
bool ReadSpecializationWord(const SrtRuntime& runtime, uint64_t address, uint32_t& word) {
|
||||
return runtime.read_specialization_memory != nullptr &&
|
||||
runtime.read_specialization_memory(runtime.userdata, address, &word);
|
||||
}
|
||||
|
||||
bool ReadScalarBufferWord(const ShaderBufferResource& descriptor, uint32_t dynamic_offset,
|
||||
uint32_t immediate_offset, const SrtRuntime& runtime, uint32_t& word) {
|
||||
const auto byte_offset = static_cast<uint64_t>(dynamic_offset) + immediate_offset;
|
||||
const auto aligned = byte_offset & ~uint64_t {3};
|
||||
const auto size = descriptor.GetSize();
|
||||
if (aligned > size || size - aligned < sizeof(uint32_t)) {
|
||||
word = 0;
|
||||
bool ReadScalarTable(uint64_t base, uint64_t size, uint32_t dynamic_offset,
|
||||
const SrtRuntime& runtime, std::span<uint32_t> words) {
|
||||
const auto offset = static_cast<uint64_t>(dynamic_offset) & ~uint64_t {3};
|
||||
const auto count = std::min<uint64_t>(words.size(), offset < size ? (size - offset) / 4u : 0u);
|
||||
std::ranges::fill(words.subspan(count), 0u);
|
||||
if (count == 0u) {
|
||||
return true;
|
||||
}
|
||||
const auto base = descriptor.Base48() & ~uint64_t {3};
|
||||
if (aligned > AddressMask - base) {
|
||||
base &= AddressMask & ~uint64_t {3};
|
||||
if (offset > AddressMask - base) {
|
||||
return false;
|
||||
}
|
||||
const auto address = base + aligned;
|
||||
if (!ReadSpecializationWord(runtime, address, word)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
const auto address = base + offset;
|
||||
const auto prefix = words.first(count);
|
||||
return prefix.size_bytes() - 1u <= AddressMask - address &&
|
||||
runtime.read_specialization_memory != nullptr &&
|
||||
runtime.read_specialization_memory(runtime.userdata, address, prefix);
|
||||
}
|
||||
|
||||
bool MaterializeIndirectImage(const DescriptorSource::IndirectImage& indirect,
|
||||
const DescriptorValue& material_value,
|
||||
const DescriptorValue& heap_value, bool r128,
|
||||
const SrtRuntime& runtime, IndirectImage& result) {
|
||||
ShaderBufferResource material;
|
||||
ShaderBufferResource heap;
|
||||
if (!DecodeBufferDescriptor(material_value, material) ||
|
||||
!DecodeBufferDescriptor(heap_value, heap)) {
|
||||
bool MaterializeIndirectImage(const ResourcePlan& program,
|
||||
const DescriptorSource::IndirectImage& indirect,
|
||||
const DescriptorValue& material_value,
|
||||
const DescriptorValue& table_value, uint32_t image_index,
|
||||
const SrtRuntime& runtime, ResourceSnapshot& snapshot,
|
||||
ResourceSpecialization& specialization) {
|
||||
uint64_t table_base = 0;
|
||||
uint64_t table_size = UINT64_MAX; // Scalar addresses have no buffer descriptor bounds.
|
||||
ShaderBufferResource table;
|
||||
if (table_value.dword_count == 2u) {
|
||||
table_base = (static_cast<uint64_t>(table_value.dwords[1]) << 32u) | table_value.dwords[0];
|
||||
} else if (DecodeBufferDescriptor(table_value, table)) {
|
||||
table_base = table.Base48();
|
||||
table_size = table.GetSize();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
if (material.Stride() != indirect.selector_stride) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// S_BUFFER_LOAD ignores vector-buffer swizzle/add-thread fields. The shader computes the
|
||||
// record stride explicitly; enumerate every wrapped 32-bit offset that can pass bounds.
|
||||
const auto period = uint64_t {1} << 32u;
|
||||
const auto step = std::gcd<uint64_t>(indirect.selector_stride, period);
|
||||
const auto residue = static_cast<uint64_t>(indirect.selector_offset) % step;
|
||||
const auto size = material.GetSize();
|
||||
const auto limit = std::min<uint64_t>(UINT32_MAX, size + 3u);
|
||||
const auto probe_count = residue <= limit ? (limit - residue) / step + 1u : 0u;
|
||||
if (probe_count > MaxIndirectImageProbes) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<uint32_t> keys {0u};
|
||||
std::unordered_set<uint32_t> seen {0u};
|
||||
keys.reserve(static_cast<size_t>(probe_count) + 1u);
|
||||
seen.reserve(static_cast<size_t>(probe_count) + 1u);
|
||||
for (uint64_t offset = residue; offset <= limit && probe_count != 0u; offset += step) {
|
||||
uint32_t key = 0;
|
||||
if (!ReadScalarBufferWord(material, static_cast<uint32_t>(offset), 0u, runtime, key)) {
|
||||
auto& keys = program.material_keys;
|
||||
keys.clear();
|
||||
if (indirect.material_source == UINT32_MAX) {
|
||||
if (table_value.dword_count != 2u || indirect.key_count == 0u ||
|
||||
indirect.key_count > MaxIndirectImageProbes) {
|
||||
return false;
|
||||
}
|
||||
if (seen.insert(key).second) {
|
||||
keys.resize(indirect.key_count);
|
||||
std::iota(keys.begin(), keys.end(), 0u);
|
||||
} else {
|
||||
ShaderBufferResource material;
|
||||
if (!DecodeBufferDescriptor(material_value, material) || table_value.dword_count != 4u ||
|
||||
material.Stride() != indirect.selector_stride) {
|
||||
return false;
|
||||
}
|
||||
// Enumerate every wrapped scalar-buffer offset that can pass the descriptor bounds.
|
||||
const auto step = std::gcd<uint64_t>(indirect.selector_stride, uint64_t {1} << 32u);
|
||||
const auto residue = static_cast<uint64_t>(indirect.selector_offset) % step;
|
||||
const auto limit = std::min<uint64_t>(UINT32_MAX, material.GetSize() + 3u);
|
||||
const auto probe_count = residue <= limit ? (limit - residue) / step + 1u : 0u;
|
||||
if (probe_count > MaxIndirectImageProbes) {
|
||||
return false;
|
||||
}
|
||||
keys.reserve(static_cast<size_t>(probe_count) + 1u);
|
||||
keys.push_back(0u);
|
||||
for (uint64_t offset = residue; offset <= limit && probe_count != 0u; offset += step) {
|
||||
uint32_t key = 0;
|
||||
if (!ReadScalarTable(material.Base48(), material.GetSize(),
|
||||
static_cast<uint32_t>(offset), runtime, {&key, 1})) {
|
||||
return false;
|
||||
}
|
||||
keys.push_back(key);
|
||||
if (limit - offset < step) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (limit - offset < step) {
|
||||
break;
|
||||
}
|
||||
std::ranges::sort(keys);
|
||||
keys.erase(std::unique(keys.begin(), keys.end()), keys.end());
|
||||
}
|
||||
|
||||
IndirectImage next;
|
||||
next.keys = std::move(keys);
|
||||
next.candidates.reserve(next.keys.size());
|
||||
next.descriptors.reserve(
|
||||
std::min(next.keys.size(), static_cast<size_t>(ShaderInfo::MaxImages)));
|
||||
for (const auto key: next.keys) {
|
||||
const auto children_begin = snapshot.images.size();
|
||||
const auto mapping_offset = snapshot.flattened_srt.size();
|
||||
const auto root_image = specialization.images[image_index];
|
||||
snapshot.flattened_srt.resize(mapping_offset + 1u + keys.size() * 2u);
|
||||
snapshot.flattened_srt[mapping_offset] = static_cast<uint32_t>(keys.size());
|
||||
for (uint32_t entry = 0; entry < keys.size(); ++entry) {
|
||||
const auto key = keys[entry];
|
||||
DescriptorValue candidate;
|
||||
candidate.dword_count = 8u;
|
||||
const auto heap_offset = key << 5u;
|
||||
for (uint32_t dword = 0; dword < candidate.dword_count; dword++) {
|
||||
if (!ReadScalarBufferWord(heap, heap_offset, dword * sizeof(uint32_t), runtime,
|
||||
candidate.dwords[dword])) {
|
||||
return false;
|
||||
}
|
||||
candidate.dword_count = 8u;
|
||||
const auto table_offset = (key << 5u) + indirect.table_offset;
|
||||
if (!ReadScalarTable(table_base, table_size, table_offset, runtime, candidate.dwords)) {
|
||||
return false;
|
||||
}
|
||||
if (NullImageDescriptor(candidate) || !ValidImageDescriptor(candidate, r128)) {
|
||||
if (NullImageDescriptor(candidate) ||
|
||||
!ValidImageDescriptor(candidate, program.info.images[image_index].r128)) {
|
||||
candidate.dwords.fill(0);
|
||||
}
|
||||
const auto found = std::ranges::find(next.descriptors, candidate);
|
||||
if (found == next.descriptors.end()) {
|
||||
if (next.descriptors.size() >= ShaderInfo::MaxImages) {
|
||||
return false;
|
||||
uint32_t ordinal = 0;
|
||||
if (entry == 0) {
|
||||
snapshot.images[image_index] = candidate;
|
||||
} else if (snapshot.images[image_index] != candidate) {
|
||||
const auto found = std::find(snapshot.images.begin() + children_begin,
|
||||
snapshot.images.end(), candidate);
|
||||
ordinal = static_cast<uint32_t>(found - snapshot.images.begin() - children_begin + 1u);
|
||||
if (found == snapshot.images.end()) {
|
||||
if (snapshot.images.size() >= ShaderInfo::MaxImages) {
|
||||
return false;
|
||||
}
|
||||
snapshot.images.push_back(candidate);
|
||||
auto child = root_image;
|
||||
child.indirect_root = image_index;
|
||||
specialization.images.push_back(child);
|
||||
}
|
||||
next.descriptors.push_back(candidate);
|
||||
next.candidates.push_back(static_cast<uint32_t>(next.descriptors.size() - 1u));
|
||||
} else {
|
||||
next.candidates.push_back(static_cast<uint32_t>(found - next.descriptors.begin()));
|
||||
}
|
||||
snapshot.flattened_srt[mapping_offset + 1u + entry * 2u] = key;
|
||||
snapshot.flattened_srt[mapping_offset + 2u + entry * 2u] = ordinal;
|
||||
}
|
||||
if (snapshot.images.size() == children_begin) {
|
||||
snapshot.flattened_srt.resize(mapping_offset);
|
||||
} else {
|
||||
auto& root = specialization.images[image_index];
|
||||
root.indirect_root = image_index;
|
||||
root.indirect_mapping_offset = static_cast<uint32_t>(mapping_offset);
|
||||
root.indirect_search_iterations = std::bit_width(keys.size());
|
||||
}
|
||||
result = std::move(next);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
static bool MaterializeSnapshot(const ResourcePlan& program, const SrtRuntime& runtime,
|
||||
MaterializedSnapshot& snapshot) {
|
||||
if (!program.resource_tracking_complete) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (program.requires_specialization_memory && runtime.read_specialization_memory == nullptr) {
|
||||
return false;
|
||||
}
|
||||
std::vector<DescriptorValue> values;
|
||||
std::vector<uint32_t> flattened_srt;
|
||||
std::vector<uint8_t> active_sources;
|
||||
if (!EvaluateRuntimeSources(program, program.materialization_sources, runtime, values,
|
||||
flattened_srt, program.clean_flat_slots, active_sources)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto& next = snapshot.resources;
|
||||
const auto& fill = program.uniform_fill;
|
||||
const auto words = fill.fill.words;
|
||||
std::array<uint32_t, 4> stored {};
|
||||
if (words != 0 &&
|
||||
EvaluateUniformValues(program, std::span(fill.values).first(words), runtime,
|
||||
std::span(stored).first(words)) &&
|
||||
std::all_of(stored.begin(), stored.begin() + words,
|
||||
[&](uint32_t value) { return value == stored[0]; })) {
|
||||
next.uniform_fill = fill.fill;
|
||||
next.uniform_fill.value = stored[0];
|
||||
}
|
||||
auto cursor = values.begin();
|
||||
next.buffers.assign(cursor, cursor + program.info.buffers.size());
|
||||
cursor += program.info.buffers.size();
|
||||
next.flattened_srt = std::move(flattened_srt);
|
||||
next.images.resize(program.info.images.size());
|
||||
for (uint32_t image_index = 0; image_index < program.info.images.size(); image_index++) {
|
||||
const auto& image = program.info.images[image_index];
|
||||
const auto* source = Source(program, image.source);
|
||||
if (source != nullptr && source->indirect_image.has_value()) {
|
||||
if (!active_sources[image.source]) {
|
||||
next.images[image_index].dword_count = 8u;
|
||||
continue;
|
||||
}
|
||||
const std::array requests {source->indirect_image->material_source,
|
||||
source->indirect_image->heap_source};
|
||||
SrtRuntime clean_runtime = runtime;
|
||||
clean_runtime.read_memory = runtime.read_specialization_memory;
|
||||
std::vector<DescriptorValue> tables;
|
||||
if (!EvaluateDescriptorSources(program, requests, clean_runtime, tables)) {
|
||||
return false;
|
||||
}
|
||||
const auto& material = tables[0];
|
||||
const auto& heap = tables[1];
|
||||
IndirectImage table;
|
||||
if (!MaterializeIndirectImage(*source->indirect_image, material, heap, image.r128,
|
||||
runtime, table)) {
|
||||
return false;
|
||||
}
|
||||
next.images[image_index] = table.descriptors[table.candidates[0]];
|
||||
if (table.descriptors.size() > 1u) {
|
||||
table.resource = image_index;
|
||||
snapshot.indirect_images.push_back(std::move(table));
|
||||
}
|
||||
} else {
|
||||
auto descriptor = *cursor++;
|
||||
if (!ValidImageDescriptor(descriptor, image.r128)) {
|
||||
descriptor.dwords.fill(0);
|
||||
}
|
||||
next.images[image_index] = descriptor;
|
||||
}
|
||||
}
|
||||
next.samplers.assign(cursor, cursor + program.info.samplers.size());
|
||||
next.user_data.assign(runtime.user_data.begin(), runtime.user_data.end());
|
||||
return true;
|
||||
}
|
||||
|
||||
struct SamplerPlan {
|
||||
std::array<uint32_t, ShaderInfo::MaxSamplers> point_sampler {};
|
||||
uint32_t sampler_count = 0;
|
||||
};
|
||||
|
||||
struct ImageRemap {
|
||||
explicit ImageRemap(const ResourceSpecialization& specialization) {
|
||||
for (const auto& image: specialization.images) {
|
||||
indices.push_back(image.fmask ? UINT32_MAX : count++);
|
||||
explicit ImageRemap(const ResourceSpecialization& specialization)
|
||||
: source_count(static_cast<uint32_t>(specialization.images.size())) {
|
||||
EXIT_IF(specialization.images.size() > indices.size());
|
||||
for (uint32_t index = 0; index < source_count; index++) {
|
||||
indices[index] = specialization.images[index].fmask ? UINT32_MAX : count++;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t operator[](uint32_t index) const {
|
||||
EXIT_IF(index >= source_count);
|
||||
return indices[index];
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void Apply(std::vector<T>& images) const {
|
||||
EXIT_IF(images.size() != indices.size());
|
||||
for (uint32_t index = 0; index < indices.size(); index++) {
|
||||
EXIT_IF(images.size() != source_count);
|
||||
for (uint32_t index = 0; index < source_count; index++) {
|
||||
if (indices[index] != UINT32_MAX && indices[index] != index) {
|
||||
images[indices[index]] = std::move(images[index]);
|
||||
}
|
||||
@@ -374,75 +331,21 @@ struct ImageRemap {
|
||||
images.resize(count);
|
||||
}
|
||||
|
||||
std::vector<uint32_t> indices;
|
||||
uint32_t count = 0;
|
||||
private:
|
||||
std::array<uint32_t, ShaderInfo::MaxImages> indices;
|
||||
uint32_t source_count;
|
||||
uint32_t count = 0;
|
||||
};
|
||||
|
||||
template <typename Images>
|
||||
bool BuildSamplerPlan(const ShaderInfo& base, const Images& images, SamplerPlan& plan);
|
||||
|
||||
static bool BuildResourceSpecialization(const ResourcePlan& program, MaterializedSnapshot snapshot,
|
||||
ResourceSnapshot& specialized_snapshot,
|
||||
static bool BuildResourceSpecialization(const ResourcePlan& program, ResourceSnapshot& snapshot,
|
||||
ResourceSpecialization& specialization) {
|
||||
auto next_snapshot = std::move(snapshot.resources);
|
||||
ResourceSpecialization next_specialization;
|
||||
next_specialization.buffers.reserve(program.info.buffers.size());
|
||||
size_t image_count = program.info.images.size();
|
||||
size_t mapping_words = 0;
|
||||
for (const auto& table: snapshot.indirect_images) {
|
||||
if (table.resource >= program.info.images.size() || table.descriptors.size() < 2u ||
|
||||
image_count + table.descriptors.size() - 1u > ShaderInfo::MaxImages) {
|
||||
return SpecializationFail(
|
||||
"indirect image candidates exceed the dense image resource limit");
|
||||
}
|
||||
image_count += table.descriptors.size() - 1u;
|
||||
mapping_words += 1u + table.keys.size() * 2u;
|
||||
}
|
||||
next_snapshot.images.reserve(image_count);
|
||||
next_snapshot.flattened_srt.reserve(next_snapshot.flattened_srt.size() + mapping_words);
|
||||
next_specialization.images.reserve(image_count);
|
||||
for (const auto& image: program.info.images) {
|
||||
next_specialization.images.push_back({
|
||||
.numeric_class = image.numeric_class,
|
||||
.dimension = image.dimension,
|
||||
.mip_count = image.mip_count,
|
||||
.conversion_format = image.conversion_format,
|
||||
.shader_swizzle = image.shader_swizzle,
|
||||
.indirect_root = image.indirect_root,
|
||||
.indirect_mapping_offset = image.indirect_mapping_offset,
|
||||
.indirect_search_iterations = image.indirect_search_iterations,
|
||||
.cube = image.cube,
|
||||
});
|
||||
}
|
||||
for (const auto& table: snapshot.indirect_images) {
|
||||
const auto root_image = next_specialization.images[table.resource];
|
||||
for (uint32_t candidate = 1; candidate < table.descriptors.size(); candidate++) {
|
||||
auto image = root_image;
|
||||
image.indirect_root = table.resource;
|
||||
next_specialization.images.push_back(image);
|
||||
next_snapshot.images.push_back(table.descriptors[candidate]);
|
||||
}
|
||||
auto& root = next_specialization.images[table.resource];
|
||||
root.indirect_root = table.resource;
|
||||
root.indirect_mapping_offset = static_cast<uint32_t>(next_snapshot.flattened_srt.size());
|
||||
root.indirect_search_iterations = std::bit_width(table.keys.size());
|
||||
next_snapshot.flattened_srt.resize(next_snapshot.flattened_srt.size() + 1u +
|
||||
table.keys.size() * 2u);
|
||||
std::vector<uint32_t> order(table.keys.size());
|
||||
std::iota(order.begin(), order.end(), 0u);
|
||||
std::ranges::sort(order, {}, [&](uint32_t index) { return table.keys[index]; });
|
||||
next_snapshot.flattened_srt[root.indirect_mapping_offset] =
|
||||
static_cast<uint32_t>(table.keys.size());
|
||||
for (uint32_t entry = 0; entry < order.size(); entry++) {
|
||||
const auto source = order[entry];
|
||||
const auto offset = root.indirect_mapping_offset + 1u + entry * 2u;
|
||||
next_snapshot.flattened_srt[offset] = table.keys[source];
|
||||
next_snapshot.flattened_srt[offset + 1] = table.candidates[source];
|
||||
}
|
||||
next_snapshot.images[table.resource] = table.descriptors[0];
|
||||
}
|
||||
specialization.buffers.clear();
|
||||
specialization.buffers.reserve(program.info.buffers.size());
|
||||
for (uint32_t i = 0; i < program.info.buffers.size(); i++) {
|
||||
auto& descriptor_value = next_snapshot.buffers[i];
|
||||
auto& descriptor_value = snapshot.buffers[i];
|
||||
ShaderBufferResource descriptor;
|
||||
if (!DecodeBufferDescriptor(descriptor_value, descriptor)) {
|
||||
return SpecializationFail(fmt::format("buffer descriptor {} has invalid width", i));
|
||||
@@ -459,7 +362,7 @@ static bool BuildResourceSpecialization(const ResourcePlan& program, Materialize
|
||||
} else if (!swizzle) {
|
||||
packed_stride &= ~(3u << 16u);
|
||||
}
|
||||
next_specialization.buffers.push_back({
|
||||
specialization.buffers.push_back({
|
||||
.packed_stride = packed_stride,
|
||||
.descriptor_format = program.info.buffers[i].formatted
|
||||
? descriptor.Format()
|
||||
@@ -468,9 +371,9 @@ static bool BuildResourceSpecialization(const ResourcePlan& program, Materialize
|
||||
program.info.buffers[i].formatted ? descriptor.DstSelXYZW() : DstSel(4, 5, 6, 7),
|
||||
});
|
||||
}
|
||||
for (uint32_t i = 0; i < next_specialization.images.size(); i++) {
|
||||
const auto& descriptor = next_snapshot.images[i];
|
||||
auto& image = next_specialization.images[i];
|
||||
for (uint32_t i = 0; i < specialization.images.size(); i++) {
|
||||
const auto& descriptor = snapshot.images[i];
|
||||
auto& image = specialization.images[i];
|
||||
const auto base_index = i < program.info.images.size() ? i : image.indirect_root;
|
||||
if (base_index >= program.info.images.size()) {
|
||||
return SpecializationFail(fmt::format("image resource {} has an invalid root", i));
|
||||
@@ -545,42 +448,46 @@ static bool BuildResourceSpecialization(const ResourcePlan& program, Materialize
|
||||
static_cast<uint32_t>(format)));
|
||||
}
|
||||
}
|
||||
for (uint32_t root_index = 0; root_index < next_specialization.images.size(); root_index++) {
|
||||
auto& root = next_specialization.images[root_index];
|
||||
for (uint32_t root_index = 0; root_index < specialization.images.size(); root_index++) {
|
||||
auto& root = specialization.images[root_index];
|
||||
if (root.indirect_root != root_index) {
|
||||
continue;
|
||||
}
|
||||
const auto key_count = root.indirect_mapping_offset < next_snapshot.flattened_srt.size()
|
||||
? next_snapshot.flattened_srt[root.indirect_mapping_offset]
|
||||
const auto key_count = root.indirect_mapping_offset < snapshot.flattened_srt.size()
|
||||
? snapshot.flattened_srt[root.indirect_mapping_offset]
|
||||
: 0u;
|
||||
if (root.indirect_search_iterations == 0u || key_count < 2u ||
|
||||
static_cast<size_t>(root.indirect_mapping_offset) + 1u +
|
||||
static_cast<size_t>(key_count) * 2u >
|
||||
next_snapshot.flattened_srt.size()) {
|
||||
snapshot.flattened_srt.size()) {
|
||||
return SpecializationFail("indirect image specialization has an invalid key mapping");
|
||||
}
|
||||
uint32_t exemplar = ImageResource::NoIndirectImage;
|
||||
uint32_t resource_count = 0;
|
||||
for (uint32_t resource = 0; resource < next_specialization.images.size(); resource++) {
|
||||
if (next_specialization.images[resource].indirect_root != root_index) {
|
||||
for (uint32_t resource = 0; resource < specialization.images.size(); resource++) {
|
||||
if (specialization.images[resource].indirect_root != root_index) {
|
||||
continue;
|
||||
}
|
||||
resource_count++;
|
||||
if (exemplar == ImageResource::NoIndirectImage &&
|
||||
!NullImageDescriptor(next_snapshot.images[resource])) {
|
||||
!NullImageDescriptor(snapshot.images[resource])) {
|
||||
exemplar = resource;
|
||||
}
|
||||
}
|
||||
if (resource_count < 2u || exemplar == ImageResource::NoIndirectImage) {
|
||||
return SpecializationFail("indirect image specialization has no typed candidate");
|
||||
}
|
||||
const auto& image_class = next_specialization.images[exemplar];
|
||||
for (uint32_t candidate = 0; candidate < next_specialization.images.size(); candidate++) {
|
||||
auto& image = next_specialization.images[candidate];
|
||||
const auto& image_class = specialization.images[exemplar];
|
||||
const auto is_2d = [](Decoder::ImageDimension dimension) {
|
||||
return dimension == Decoder::ImageDimension::Dim2D ||
|
||||
dimension == Decoder::ImageDimension::Dim2DArray;
|
||||
};
|
||||
for (uint32_t candidate = 0; candidate < specialization.images.size(); candidate++) {
|
||||
auto& image = specialization.images[candidate];
|
||||
if (image.indirect_root != root_index) {
|
||||
continue;
|
||||
}
|
||||
if (NullImageDescriptor(next_snapshot.images[candidate])) {
|
||||
if (NullImageDescriptor(snapshot.images[candidate])) {
|
||||
image.numeric_class = image_class.numeric_class;
|
||||
image.dimension = image_class.dimension;
|
||||
image.mip_count = image_class.mip_count;
|
||||
@@ -588,12 +495,13 @@ static bool BuildResourceSpecialization(const ResourcePlan& program, Materialize
|
||||
image.shader_swizzle = image_class.shader_swizzle;
|
||||
image.cube = image_class.cube;
|
||||
}
|
||||
const bool same_coordinates = image.dimension == image_class.dimension &&
|
||||
image.cube == image_class.cube;
|
||||
if (image.numeric_class != image_class.numeric_class ||
|
||||
image.dimension != image_class.dimension ||
|
||||
(!same_coordinates && !(is_2d(image.dimension) && is_2d(image_class.dimension))) ||
|
||||
image.mip_count != image_class.mip_count ||
|
||||
image.conversion_format != image_class.conversion_format ||
|
||||
image.shader_swizzle != image_class.shader_swizzle ||
|
||||
image.cube != image_class.cube) {
|
||||
image.shader_swizzle != image_class.shader_swizzle) {
|
||||
return SpecializationFail(
|
||||
fmt::format("indirect image table at pc 0x{:08x} has incompatible candidates",
|
||||
program.info.images[root_index].first_use_pc));
|
||||
@@ -601,18 +509,16 @@ static bool BuildResourceSpecialization(const ResourcePlan& program, Materialize
|
||||
}
|
||||
}
|
||||
SamplerPlan sampler_plan;
|
||||
if (!BuildSamplerPlan(program.info, next_specialization.images, sampler_plan)) {
|
||||
if (!BuildSamplerPlan(program.info, specialization.images, sampler_plan)) {
|
||||
return SpecializationFail("specialized sampler layout exceeds its resource limit");
|
||||
}
|
||||
for (uint32_t index = 0; index < program.info.samplers.size(); index++) {
|
||||
const auto target = sampler_plan.point_sampler[index];
|
||||
if (target != UINT32_MAX && target >= program.info.samplers.size()) {
|
||||
next_snapshot.samplers.push_back(next_snapshot.samplers[index]);
|
||||
snapshot.samplers.push_back(snapshot.samplers[index]);
|
||||
}
|
||||
}
|
||||
ImageRemap(next_specialization).Apply(next_snapshot.images);
|
||||
specialization = std::move(next_specialization);
|
||||
specialized_snapshot = std::move(next_snapshot);
|
||||
ImageRemap(specialization).Apply(snapshot.images);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -926,31 +832,16 @@ ResourcePlan ExtractResourcePlan(const Program& program) {
|
||||
for (uint32_t i = 0; i < plan.uniform_fill.fill.words; ++i) {
|
||||
plan.uniform_fill.values[i] = Clone(plan.uniform_fill.values[i]);
|
||||
}
|
||||
plan.materialization_sources.reserve(plan.info.buffers.size() + plan.info.images.size() +
|
||||
plan.info.samplers.size());
|
||||
for (const auto& buffer: plan.info.buffers) {
|
||||
plan.materialization_sources.push_back(buffer.source);
|
||||
}
|
||||
for (const auto& image: plan.info.images) {
|
||||
const auto* source = Source(plan, image.source);
|
||||
if (source != nullptr && source->indirect_image.has_value()) {
|
||||
plan.requires_specialization_memory = true;
|
||||
} else {
|
||||
plan.materialization_sources.push_back(image.source);
|
||||
}
|
||||
}
|
||||
for (const auto& sampler: plan.info.samplers) {
|
||||
plan.materialization_sources.push_back(sampler.source);
|
||||
}
|
||||
plan.clean_flat_slots.resize(plan.srt_reads.size());
|
||||
for (const auto& image: plan.info.images) {
|
||||
const auto* source = Source(plan, image.source);
|
||||
if (source == nullptr || !source->indirect_image.has_value()) {
|
||||
continue;
|
||||
}
|
||||
plan.requires_specialization_memory = true;
|
||||
MarkCleanFlatSlots(plan, Source(plan, source->indirect_image->material_source),
|
||||
plan.clean_flat_slots);
|
||||
MarkCleanFlatSlots(plan, Source(plan, source->indirect_image->heap_source),
|
||||
MarkCleanFlatSlots(plan, Source(plan, source->indirect_image->table_source),
|
||||
plan.clean_flat_slots);
|
||||
}
|
||||
return plan;
|
||||
@@ -958,11 +849,99 @@ ResourcePlan ExtractResourcePlan(const Program& program) {
|
||||
|
||||
bool MaterializeResources(const ResourcePlan& program, const SrtRuntime& runtime,
|
||||
ResourceSnapshot& snapshot, ResourceSpecialization& specialization) {
|
||||
MaterializedSnapshot materialized;
|
||||
if (!MaterializeSnapshot(program, runtime, materialized)) {
|
||||
if (!program.resource_tracking_complete ||
|
||||
(program.requires_specialization_memory && runtime.read_specialization_memory == nullptr)) {
|
||||
return false;
|
||||
}
|
||||
return BuildResourceSpecialization(program, std::move(materialized), snapshot, specialization);
|
||||
SrtWalker clean(program, CleanRuntime(runtime));
|
||||
SrtWalker walker(program, runtime, program.clean_flat_slots, &clean);
|
||||
const auto active = clean.FindActiveSources();
|
||||
if (!walker.RefreshFlatBuffer(snapshot.flattened_srt)) {
|
||||
return false;
|
||||
}
|
||||
snapshot.uniform_fill = {};
|
||||
const auto& fill = program.uniform_fill;
|
||||
const auto words = fill.fill.words;
|
||||
std::array<uint32_t, 4> stored {};
|
||||
bool uniform_fill = words != 0;
|
||||
for (uint32_t i = 0; i < words && uniform_fill; ++i) {
|
||||
uniform_fill = clean.Evaluate(fill.values[i], stored[i]) && stored[i] == stored[0];
|
||||
}
|
||||
if (uniform_fill) {
|
||||
snapshot.uniform_fill = fill.fill;
|
||||
snapshot.uniform_fill.value = stored[0];
|
||||
}
|
||||
const auto evaluate = [&](uint32_t source, DescriptorValue& value) {
|
||||
if (source >= program.descriptor_sources.size()) {
|
||||
return false;
|
||||
}
|
||||
if (active.empty() || active[source]) {
|
||||
return walker.EvaluateDescriptor(source, value);
|
||||
}
|
||||
value = {};
|
||||
value.dword_count = program.descriptor_sources[source].dword_count;
|
||||
return true;
|
||||
};
|
||||
snapshot.buffers.resize(program.info.buffers.size());
|
||||
for (uint32_t i = 0; i < program.info.buffers.size(); ++i) {
|
||||
if (!evaluate(program.info.buffers[i].source, snapshot.buffers[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
snapshot.images.resize(program.info.images.size());
|
||||
specialization.images.clear();
|
||||
specialization.images.reserve(program.info.images.size());
|
||||
for (const auto& image: program.info.images) {
|
||||
specialization.images.push_back({
|
||||
.numeric_class = image.numeric_class,
|
||||
.dimension = image.dimension,
|
||||
.mip_count = image.mip_count,
|
||||
.conversion_format = image.conversion_format,
|
||||
.shader_swizzle = image.shader_swizzle,
|
||||
.indirect_root = image.indirect_root,
|
||||
.indirect_mapping_offset = image.indirect_mapping_offset,
|
||||
.indirect_search_iterations = image.indirect_search_iterations,
|
||||
.cube = image.cube,
|
||||
});
|
||||
}
|
||||
for (uint32_t i = 0; i < program.info.images.size(); ++i) {
|
||||
const auto& image = program.info.images[i];
|
||||
const auto* source = Source(program, image.source);
|
||||
if (source == nullptr) {
|
||||
return false;
|
||||
}
|
||||
if (source->indirect_image.has_value()) {
|
||||
snapshot.images[i] = {.dword_count = 8u};
|
||||
if (!active.empty() && !active[image.source]) {
|
||||
continue;
|
||||
}
|
||||
const auto& indirect = *source->indirect_image;
|
||||
DescriptorValue material;
|
||||
DescriptorValue table;
|
||||
if ((indirect.material_source != UINT32_MAX &&
|
||||
!clean.EvaluateDescriptor(indirect.material_source, material)) ||
|
||||
!clean.EvaluateDescriptor(indirect.table_source, table) ||
|
||||
!MaterializeIndirectImage(program, indirect, material, table, i, runtime, snapshot,
|
||||
specialization)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (!evaluate(image.source, snapshot.images[i])) {
|
||||
return false;
|
||||
}
|
||||
if (!ValidImageDescriptor(snapshot.images[i], image.r128)) {
|
||||
snapshot.images[i].dwords.fill(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
snapshot.samplers.resize(program.info.samplers.size());
|
||||
for (uint32_t i = 0; i < program.info.samplers.size(); ++i) {
|
||||
if (!evaluate(program.info.samplers[i].source, snapshot.samplers[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
snapshot.user_data.assign(runtime.user_data.begin(), runtime.user_data.end());
|
||||
return BuildResourceSpecialization(program, snapshot, specialization);
|
||||
}
|
||||
|
||||
void ApplyResourceSpecialization(Program& program, const ResourceSpecialization& specialization) {
|
||||
@@ -1075,29 +1054,29 @@ void ApplyResourceSpecialization(Program& program, const ResourceSpecialization&
|
||||
for (auto* block: program.blocks) {
|
||||
for (auto& inst: *block) {
|
||||
if (inst.GetOpcode() == ValueOpcode::GetImageResource) {
|
||||
inst.SetFlags(image_remap.indices.at(inst.Flags<uint32_t>()));
|
||||
inst.SetFlags(image_remap[inst.Flags<uint32_t>()]);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (auto& memory: memory_info) {
|
||||
if (memory.kind == ResourceKind::Image && !memory.planning_only) {
|
||||
memory.resource = image_remap.indices.at(memory.resource);
|
||||
memory.resource = image_remap[memory.resource];
|
||||
}
|
||||
}
|
||||
for (auto& buffer: buffers) {
|
||||
if (buffer.image_alias != BufferResource::NoImageAlias) {
|
||||
buffer.image_alias = image_remap.indices.at(buffer.image_alias);
|
||||
buffer.image_alias = image_remap[buffer.image_alias];
|
||||
}
|
||||
}
|
||||
for (auto& pair: sampled_pairs) {
|
||||
pair.image = image_remap.indices.at(pair.image);
|
||||
pair.image = image_remap[pair.image];
|
||||
}
|
||||
for (auto& image: images) {
|
||||
if (image.indirect_root != ImageResource::NoIndirectImage) {
|
||||
image.indirect_root = image_remap.indices.at(image.indirect_root);
|
||||
image.indirect_root = image_remap[image.indirect_root];
|
||||
}
|
||||
for (auto& resource: image.indirect_resources) {
|
||||
resource = image_remap.indices.at(resource);
|
||||
resource = image_remap[resource];
|
||||
}
|
||||
}
|
||||
image_remap.Apply(images);
|
||||
|
||||
@@ -39,8 +39,7 @@ struct ResourceSpecialization {
|
||||
// its values and is independent of the translated shader CFG.
|
||||
ResourcePlan ExtractResourcePlan(const Program& program);
|
||||
|
||||
// Resolves and specializes the immutable resource plan in one transaction. On failure both
|
||||
// destinations are unchanged.
|
||||
// Refreshes cached resources and specialization in place. A failed refresh must not be used.
|
||||
bool MaterializeResources(const ResourcePlan& program, const SrtRuntime& runtime,
|
||||
ResourceSnapshot& snapshot, ResourceSpecialization& specialization);
|
||||
|
||||
|
||||
@@ -169,7 +169,7 @@ private:
|
||||
std::abort();
|
||||
}
|
||||
|
||||
Value LowerDescriptorPhi(Value value) {
|
||||
Value LowerDescriptorPhi(Value value, const Block* use) {
|
||||
value = value.Resolve();
|
||||
const auto* phi = value.TryInstruction();
|
||||
if (m_shader_writes || phi == nullptr || phi->GetOpcode() != ValueOpcode::Phi ||
|
||||
@@ -177,17 +177,53 @@ private:
|
||||
m_program.blocks.size() != m_program.block_info.size()) {
|
||||
return value;
|
||||
}
|
||||
for (const auto& [original, selected]: m_descriptor_selections) {
|
||||
if (original == phi) {
|
||||
return selected;
|
||||
}
|
||||
}
|
||||
const auto* merge = phi->Parent();
|
||||
const auto* merge = phi->Parent();
|
||||
const auto* branch = phi->PhiBlock(0);
|
||||
if (merge == nullptr || branch == nullptr || phi->PhiBlock(1) == nullptr ||
|
||||
branch == phi->PhiBlock(1)) {
|
||||
return value;
|
||||
}
|
||||
// Structurization can merge the descriptor and its use predicate in parallel Phis.
|
||||
// Match their incoming blocks to exclude only edges that cannot reach this use.
|
||||
if (use != nullptr && use->ImmPredecessors().size() == 1u &&
|
||||
use->ImmPredecessors()[0] == merge) {
|
||||
const auto merge_it = std::ranges::find(m_program.blocks, merge);
|
||||
const auto use_it = std::ranges::find(m_program.blocks, use);
|
||||
if (merge_it != m_program.blocks.end() && use_it != m_program.blocks.end()) {
|
||||
const auto& info = m_program.block_info[merge_it - m_program.blocks.begin()];
|
||||
const auto& term = info.terminator;
|
||||
const auto id = m_program.block_info[use_it - m_program.blocks.begin()].id;
|
||||
const auto* condition = info.condition.Resolve().TryInstruction();
|
||||
if (term.kind == CFG::TerminatorKind::ConditionalBranch &&
|
||||
term.true_block != term.false_block &&
|
||||
(id == term.true_block || id == term.false_block) && condition != nullptr &&
|
||||
condition->GetOpcode() == ValueOpcode::Phi && condition->GetType() == Type::U1 &&
|
||||
condition->Parent() == merge && condition->NumArgs() == 2u &&
|
||||
condition->NumPhiBlocks() == 2u) {
|
||||
const bool taken = id == term.true_block;
|
||||
for (uint32_t skipped = 0; skipped < 2u; skipped++) {
|
||||
const auto excluded = condition->Arg(skipped).Resolve();
|
||||
const auto included = condition->Arg(skipped ^ 1u).Resolve();
|
||||
if (!excluded.IsImmediate() || excluded.GetType() != Type::U1 ||
|
||||
excluded.U1() == taken ||
|
||||
(included.IsImmediate() && included.U1() != taken)) {
|
||||
continue;
|
||||
}
|
||||
for (uint32_t selected = 0; selected < 2u; selected++) {
|
||||
if (phi->PhiBlock(selected) == condition->PhiBlock(skipped ^ 1u) &&
|
||||
phi->PhiBlock(selected ^ 1u) == condition->PhiBlock(skipped)) {
|
||||
return phi->Arg(selected);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const auto& [original, selected]: m_descriptor_selections) {
|
||||
if (original == phi) {
|
||||
return selected;
|
||||
}
|
||||
}
|
||||
if (branch->ImmSuccessors().size() != 2u) {
|
||||
if (branch->ImmPredecessors().size() != 1u) {
|
||||
return value;
|
||||
@@ -247,7 +283,7 @@ private:
|
||||
}
|
||||
descriptor.dword_count = width;
|
||||
for (uint32_t i = 0; i < width; i++) {
|
||||
descriptor.dwords[i] = LowerDescriptorPhi(handle.Arg(i));
|
||||
descriptor.dwords[i] = LowerDescriptorPhi(handle.Arg(i), handle.Parent());
|
||||
}
|
||||
if (sample_adjust) {
|
||||
descriptor.dwords[3] = CanonicalizeSampleAdjustDword3(descriptor.dwords[3]);
|
||||
@@ -308,16 +344,26 @@ private:
|
||||
}
|
||||
|
||||
const MemoryInfo* ScalarReadMemory(const Inst& read, uint32_t& index) const {
|
||||
if (read.GetOpcode() != ValueOpcode::ReadConstBuffer || read.NumArgs() != 2u) {
|
||||
const bool address = read.GetOpcode() == ValueOpcode::LoadAddressU32;
|
||||
if (!(address ? read.NumArgs() == 4u
|
||||
: read.GetOpcode() == ValueOpcode::ReadConstBuffer && read.NumArgs() == 2u)) {
|
||||
return nullptr;
|
||||
}
|
||||
if (address) {
|
||||
const auto high = read.Arg(2).Resolve();
|
||||
const auto enabled = read.Arg(3).Resolve();
|
||||
if (!high.IsImmediate() || high.GetType() != Type::U32 || high.U32() != 0u ||
|
||||
!enabled.IsImmediate() || enabled.GetType() != Type::U1 || !enabled.U1()) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
index = read.Flags<MemoryFlags>().index;
|
||||
if (index >= m_program.memory_info.size()) {
|
||||
return nullptr;
|
||||
}
|
||||
const auto& memory = m_program.memory_info[index];
|
||||
return memory.kind == ResourceKind::ScalarBuffer && memory.data_bits == 32u &&
|
||||
memory.data_dwords == 1u
|
||||
return memory.kind == (address ? ResourceKind::ScalarAddress : ResourceKind::ScalarBuffer) &&
|
||||
memory.data_bits == 32u && memory.data_dwords == 1u
|
||||
? &memory
|
||||
: nullptr;
|
||||
}
|
||||
@@ -340,18 +386,16 @@ private:
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MakeRuntimeBufferSource(const Inst& handle, uint32_t pc, uint32_t& source,
|
||||
DescriptorSource& descriptor) {
|
||||
if (handle.GetOpcode() != ValueOpcode::GetBufferResource) {
|
||||
bool MakeRuntimeTableSource(const Inst& handle, uint32_t pc,
|
||||
DescriptorSource& descriptor) {
|
||||
const auto width = handle.GetOpcode() == ValueOpcode::GetBufferResource ? 4u
|
||||
: handle.GetOpcode() == ValueOpcode::GetAddressResource ? 2u : 0u;
|
||||
if (width == 0u) {
|
||||
return false;
|
||||
}
|
||||
MakeSource(handle, 4u, false, false, descriptor, pc);
|
||||
MakeSource(handle, width, false, false, descriptor, pc);
|
||||
uint32_t bad_dword = 0;
|
||||
if (!ValidateSource(descriptor, bad_dword)) {
|
||||
return false;
|
||||
}
|
||||
source = InternSource(descriptor);
|
||||
return true;
|
||||
return ValidateSource(descriptor, bad_dword);
|
||||
}
|
||||
|
||||
bool MatchMaterialOffset(Value value, Value& selector, uint32_t& stride,
|
||||
@@ -388,106 +432,183 @@ private:
|
||||
selector_inst->GetOpcode() == ValueOpcode::ReadFirstLane;
|
||||
}
|
||||
|
||||
bool NonzeroOnEntry(Value value, const Block* block) const {
|
||||
if (m_program.blocks.size() != m_program.block_info.size()) {
|
||||
return false;
|
||||
}
|
||||
// Each unique predecessor must execute before this use. Stop at joins: an
|
||||
// unrelated comparison is not a bound on FindILsb's zero-input sentinel.
|
||||
for (size_t depth = 0; block != nullptr && depth < m_program.blocks.size(); ++depth) {
|
||||
if (block->ImmPredecessors().size() != 1u) {
|
||||
return false;
|
||||
}
|
||||
const auto* previous = block->ImmPredecessors()[0];
|
||||
const auto current_it = std::ranges::find(m_program.blocks, block);
|
||||
const auto previous_it = std::ranges::find(m_program.blocks, previous);
|
||||
if (current_it == m_program.blocks.end() || previous_it == m_program.blocks.end()) {
|
||||
return false;
|
||||
}
|
||||
const auto& info = m_program.block_info[previous_it - m_program.blocks.begin()];
|
||||
const auto id = m_program.block_info[current_it - m_program.blocks.begin()].id;
|
||||
const auto& term = info.terminator;
|
||||
if (term.kind == CFG::TerminatorKind::ConditionalBranch &&
|
||||
(term.true_block == id) != (term.false_block == id)) {
|
||||
bool positive = term.true_block == id;
|
||||
const auto* test = info.condition.Resolve().TryInstruction();
|
||||
while (test != nullptr && test->GetOpcode() == ValueOpcode::LogicalNot) {
|
||||
positive = !positive;
|
||||
test = test->Arg(0).Resolve().TryInstruction();
|
||||
}
|
||||
if (test != nullptr && test->NumArgs() == 2u &&
|
||||
((test->GetOpcode() == ValueOpcode::INotEqual32 && positive) ||
|
||||
(test->GetOpcode() == ValueOpcode::IEqual32 && !positive))) {
|
||||
for (uint32_t arg = 0; arg < 2u; ++arg) {
|
||||
uint32_t immediate;
|
||||
if (ImmediateU32(test->Arg(arg), immediate) && immediate == 0u &&
|
||||
EquivalentValue(m_program, test->Arg(arg ^ 1u), value)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
block = previous;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MatchTableOffset(Value value, Value& key, uint32_t& offset) const {
|
||||
offset = 0;
|
||||
for (;;) {
|
||||
const auto* inst = value.Resolve().TryInstruction();
|
||||
if (inst == nullptr || inst->NumArgs() != 2u) {
|
||||
return false;
|
||||
}
|
||||
uint32_t immediate;
|
||||
if (inst->GetOpcode() == ValueOpcode::ShiftLeftLogical32 &&
|
||||
ImmediateU32(inst->Arg(1), immediate) && immediate == 5u) {
|
||||
key = inst->Arg(0).Resolve();
|
||||
return key.GetType() == Type::U32;
|
||||
}
|
||||
if (inst->GetOpcode() != ValueOpcode::IAdd32) {
|
||||
return false;
|
||||
}
|
||||
if (ImmediateU32(inst->Arg(0), immediate)) {
|
||||
value = inst->Arg(1);
|
||||
} else if (ImmediateU32(inst->Arg(1), immediate)) {
|
||||
value = inst->Arg(0);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
// These additions are shader U32 arithmetic, before the scalar memory offset.
|
||||
offset += immediate;
|
||||
}
|
||||
}
|
||||
|
||||
bool TryMakeIndirectImage(Inst& handle, uint32_t pc, IndirectImagePlan& plan) {
|
||||
if (handle.GetOpcode() != ValueOpcode::GetImageResource || handle.NumArgs() != 8u) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::array<Inst*, 8> heap_reads {};
|
||||
Inst* heap_handle = nullptr;
|
||||
Value heap_offset;
|
||||
for (uint32_t dword = 0; dword < heap_reads.size(); dword++) {
|
||||
heap_reads[dword] = handle.Arg(dword).Resolve().TryInstruction();
|
||||
if (heap_reads[dword] == nullptr) {
|
||||
Inst* table_handle = nullptr;
|
||||
Value key;
|
||||
uint32_t table_offset = 0;
|
||||
for (uint32_t dword = 0; dword < plan.reads.size(); ++dword) {
|
||||
auto* read = handle.Arg(dword).Resolve().TryInstruction();
|
||||
if (read == nullptr) {
|
||||
return false;
|
||||
}
|
||||
uint32_t memory_index = 0;
|
||||
const auto* memory = ScalarReadMemory(*heap_reads[dword], memory_index);
|
||||
if (memory == nullptr || memory->offset != dword * sizeof(uint32_t) ||
|
||||
!MemoryIndexBelongsTo(memory_index, *heap_reads[dword])) {
|
||||
uint32_t memory_index = 0;
|
||||
const auto* memory = ScalarReadMemory(*read, memory_index);
|
||||
if (memory == nullptr || memory->offset > INT32_MAX || (memory->offset & 3u) != 0u ||
|
||||
!MemoryIndexBelongsTo(memory_index, *read)) {
|
||||
return false;
|
||||
}
|
||||
auto* current_handle = heap_reads[dword]->Arg(0).Resolve().TryInstruction();
|
||||
auto* current_handle = read->Arg(0).Resolve().TryInstruction();
|
||||
Value current_key;
|
||||
uint32_t offset = 0;
|
||||
if (current_handle == nullptr ||
|
||||
(heap_handle != nullptr && current_handle != heap_handle)) {
|
||||
current_handle->GetOpcode() != (memory->kind == ResourceKind::ScalarAddress
|
||||
? ValueOpcode::GetAddressResource
|
||||
: ValueOpcode::GetBufferResource) ||
|
||||
(memory->kind == ResourceKind::ScalarAddress && read->Parent() != handle.Parent()) ||
|
||||
(table_handle != nullptr &&
|
||||
!EquivalentValue(m_program, Value(table_handle), Value(current_handle))) ||
|
||||
!MatchTableOffset(read->Arg(1), current_key, offset) ||
|
||||
memory->offset > UINT32_MAX - offset) {
|
||||
return false;
|
||||
}
|
||||
heap_handle = current_handle;
|
||||
offset += memory->offset;
|
||||
if (dword == 0u) {
|
||||
heap_offset = heap_reads[dword]->Arg(1).Resolve();
|
||||
} else if (!EquivalentValue(m_program, heap_offset, heap_reads[dword]->Arg(1))) {
|
||||
key = current_key;
|
||||
table_offset = offset;
|
||||
} else if (!EquivalentValue(m_program, key, current_key) ||
|
||||
static_cast<uint64_t>(table_offset) + dword * sizeof(uint32_t) != offset) {
|
||||
return false;
|
||||
}
|
||||
plan.memory[dword] = memory_index;
|
||||
plan.reads[dword] = heap_reads[dword];
|
||||
}
|
||||
|
||||
const auto* shift = heap_offset.TryInstruction();
|
||||
uint32_t shift_amount = 0;
|
||||
if (shift == nullptr || shift->GetOpcode() != ValueOpcode::ShiftLeftLogical32 ||
|
||||
shift->NumArgs() != 2u || !ImmediateU32(shift->Arg(1), shift_amount) ||
|
||||
shift_amount != 5u) {
|
||||
return false;
|
||||
}
|
||||
auto* material_read = shift->Arg(0).Resolve().TryInstruction();
|
||||
if (material_read == nullptr) {
|
||||
return false;
|
||||
}
|
||||
uint32_t material_memory_index = 0;
|
||||
const auto* material_memory = ScalarReadMemory(*material_read, material_memory_index);
|
||||
if (material_memory == nullptr || material_memory->offset != 0u ||
|
||||
!MemoryIndexBelongsTo(material_memory_index, *material_read)) {
|
||||
return false;
|
||||
}
|
||||
auto* material_handle = material_read->Arg(0).Resolve().TryInstruction();
|
||||
if (material_handle == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Value selector;
|
||||
uint32_t selector_stride = 0;
|
||||
uint32_t selector_offset = 0;
|
||||
if (!MatchMaterialOffset(material_read->Arg(1), selector, selector_stride,
|
||||
selector_offset)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::array<const Inst*, 1> material_users {shift};
|
||||
std::array<const Inst*, 8> heap_users {};
|
||||
std::copy(heap_reads.begin(), heap_reads.end(), heap_users.begin());
|
||||
const std::array<const Inst*, 1> image_users {&handle};
|
||||
if (!UsesOnly(*material_read, material_users) || !UsesOnly(*shift, heap_users)) {
|
||||
return false;
|
||||
}
|
||||
for (const auto* read: heap_reads) {
|
||||
table_handle = current_handle;
|
||||
const std::array<const Inst*, 1> image_users {&handle};
|
||||
if (!UsesOnly(*read, image_users)) {
|
||||
return false;
|
||||
}
|
||||
plan.memory[dword] = memory_index;
|
||||
plan.reads[dword] = read;
|
||||
}
|
||||
|
||||
DescriptorSource material_source;
|
||||
DescriptorSource heap_source;
|
||||
uint32_t material_source_index = 0;
|
||||
uint32_t heap_source_index = 0;
|
||||
if (!MakeRuntimeBufferSource(*material_handle, pc, material_source_index,
|
||||
material_source) ||
|
||||
!MakeRuntimeBufferSource(*heap_handle, pc, heap_source_index, heap_source)) {
|
||||
DescriptorSource table_source;
|
||||
if (!MakeRuntimeTableSource(*table_handle, pc, table_source)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
DescriptorSource material_source;
|
||||
DescriptorSource::IndirectImage indirect;
|
||||
indirect.table_offset = table_offset;
|
||||
if (table_source.dword_count == 2u) {
|
||||
const auto* selector = key.Resolve().TryInstruction();
|
||||
if (m_shader_writes || selector == nullptr ||
|
||||
selector->GetOpcode() != ValueOpcode::FindILsb32 || selector->NumArgs() != 1u ||
|
||||
!NonzeroOnEntry(selector->Arg(0), handle.Parent()) ||
|
||||
(table_offset & 3u) != 0u || table_offset > UINT32_MAX - (32u * 32u - 1u)) {
|
||||
return false;
|
||||
}
|
||||
indirect.key_count = 32u;
|
||||
} else {
|
||||
auto* material_read = key.Resolve().TryInstruction();
|
||||
uint32_t material_memory_index = 0;
|
||||
const auto* memory = material_read != nullptr
|
||||
? ScalarReadMemory(*material_read, material_memory_index) : nullptr;
|
||||
if (table_offset != 0u || memory == nullptr || memory->kind != ResourceKind::ScalarBuffer ||
|
||||
memory->offset != 0u || !MemoryIndexBelongsTo(material_memory_index, *material_read)) {
|
||||
return false;
|
||||
}
|
||||
Value selector;
|
||||
if (!MatchMaterialOffset(material_read->Arg(1), selector, indirect.selector_stride,
|
||||
indirect.selector_offset)) {
|
||||
return false;
|
||||
}
|
||||
const auto* shift = plan.reads[0]->Arg(1).Resolve().TryInstruction();
|
||||
const std::array<const Inst*, 1> material_users {shift};
|
||||
if (!UsesOnly(*material_read, material_users) || !UsesOnly(*shift, plan.reads)) {
|
||||
return false;
|
||||
}
|
||||
const auto* material_handle = material_read->Arg(0).Resolve().TryInstruction();
|
||||
if (material_handle == nullptr ||
|
||||
material_handle->GetOpcode() != ValueOpcode::GetBufferResource ||
|
||||
!MakeRuntimeTableSource(*material_handle, pc, material_source)) {
|
||||
return false;
|
||||
}
|
||||
indirect.material_source = InternSource(material_source);
|
||||
}
|
||||
indirect.table_source = InternSource(table_source);
|
||||
DescriptorSource image_source;
|
||||
image_source.dword_count = 8u;
|
||||
std::copy(material_source.dwords.begin(), material_source.dwords.begin() + 4u,
|
||||
image_source.dwords.begin());
|
||||
std::copy(heap_source.dwords.begin(), heap_source.dwords.begin() + 4u,
|
||||
image_source.dwords.begin() + 4u);
|
||||
image_source.indirect_image = DescriptorSource::IndirectImage {
|
||||
material_source_index, heap_source_index, selector_stride, selector_offset, 0u};
|
||||
|
||||
image_source.dwords.fill(Value(0u));
|
||||
std::copy_n(material_source.dwords.begin(), material_source.dword_count,
|
||||
image_source.dwords.begin());
|
||||
std::copy_n(table_source.dwords.begin(), table_source.dword_count,
|
||||
image_source.dwords.begin() + 4u);
|
||||
image_source.indirect_image = indirect;
|
||||
plan.handle = &handle;
|
||||
plan.source = InternSource(image_source);
|
||||
plan.key = Value(material_read);
|
||||
plan.roots = image_source.dwords;
|
||||
plan.key = key;
|
||||
plan.roots = image_source.dwords;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -526,7 +647,7 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
void GetHandle(Value value, ValueOpcode expected, uint32_t width, uint32_t pc, Inst*& handle,
|
||||
bool GetHandle(Value value, ValueOpcode expected, uint32_t width, uint32_t pc, Inst*& handle,
|
||||
uint32_t& source, bool sampler = false, bool sample_adjust = false) {
|
||||
handle = value.Resolve().TryInstruction();
|
||||
if (handle == nullptr || handle->GetOpcode() != expected) {
|
||||
@@ -535,21 +656,17 @@ private:
|
||||
DescriptorSource descriptor;
|
||||
MakeSource(*handle, width, sampler, sample_adjust, descriptor, pc);
|
||||
uint32_t bad_dword = 0;
|
||||
if (expected == ValueOpcode::GetImageResource) {
|
||||
for (; bad_dword < descriptor.dword_count; bad_dword++) {
|
||||
const auto* value = descriptor.dwords[bad_dword].Resolve().TryInstruction();
|
||||
if (value != nullptr && value->GetOpcode() == ValueOpcode::ReadConstBuffer) {
|
||||
Fail(pc, fmt::format("{} dword {} is not a valid runtime value",
|
||||
ValueOpcodeName(expected), bad_dword));
|
||||
}
|
||||
}
|
||||
bad_dword = 0;
|
||||
}
|
||||
if (!ValidateSource(descriptor, bad_dword)) {
|
||||
if (expected == ValueOpcode::GetBufferResource &&
|
||||
std::all_of(descriptor.dwords.begin(), descriptor.dwords.begin() + width,
|
||||
[](Value word) { return word.Resolve().GetType() == Type::U32; })) {
|
||||
return false;
|
||||
}
|
||||
Fail(pc, fmt::format("{} dword {} is not a valid runtime value",
|
||||
ValueOpcodeName(expected), bad_dword));
|
||||
}
|
||||
source = InternSource(descriptor);
|
||||
return true;
|
||||
}
|
||||
|
||||
void ValidateAddressHandle(Value value, uint32_t pc) const {
|
||||
@@ -720,7 +837,18 @@ private:
|
||||
uint32_t resource = 0;
|
||||
|
||||
if (buffer != BufferAccess::None) {
|
||||
GetHandle(inst.Arg(0), ValueOpcode::GetBufferResource, 4, flags.pc, handle, source);
|
||||
if (!GetHandle(inst.Arg(0), ValueOpcode::GetBufferResource, 4, flags.pc, handle,
|
||||
source)) {
|
||||
if (memory.kind != ResourceKind::Buffer || memory.formatted || memory.typed ||
|
||||
(op != ValueOpcode::LoadBufferU32x2 && op != ValueOpcode::LoadBufferU32x4)) {
|
||||
Fail(flags.pc,
|
||||
"buffer descriptor is not a valid runtime value; GPU-selected access "
|
||||
"requires a raw DWORD x2/x4 load");
|
||||
}
|
||||
m_program.memory_info[flags.index].kind = ResourceKind::IndirectBuffer;
|
||||
m_info.uses_dma = true;
|
||||
return;
|
||||
}
|
||||
resource = AddBuffer(source, memory, op, flags.pc);
|
||||
if (resource == UINT32_MAX) {
|
||||
Fail(flags.pc, "buffer resource limit exceeded");
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -9,7 +9,7 @@ namespace Libs::Graphics::ShaderRecompiler::IR {
|
||||
|
||||
class Value;
|
||||
|
||||
using SrtMemoryReader = bool (*)(void* userdata, uint64_t address, uint32_t* value);
|
||||
using SrtMemoryReader = bool (*)(void* userdata, uint64_t address, std::span<uint32_t> values);
|
||||
|
||||
struct SrtRuntime {
|
||||
std::span<const uint32_t> user_data;
|
||||
@@ -26,26 +26,42 @@ enum class RuntimeValueType { Any, Integer };
|
||||
void BuildSrtPlan(Program& program);
|
||||
bool ValidateRuntimeValue(const ResourcePlan& program, Value value,
|
||||
RuntimeValueType type = RuntimeValueType::Any);
|
||||
bool EvaluateUniformValues(const ResourcePlan& program, std::span<const Value> values,
|
||||
const SrtRuntime& runtime, std::span<uint32_t> results);
|
||||
// Uses the strict reader for values that affect shader specialization.
|
||||
SrtRuntime CleanRuntime(SrtRuntime runtime);
|
||||
|
||||
bool EvaluateDescriptorSource(const ResourcePlan& program, uint32_t source,
|
||||
const SrtRuntime& runtime, DescriptorValue& result);
|
||||
// One memoized evaluation session shared by the entire shader resource refresh.
|
||||
class SrtWalker {
|
||||
public:
|
||||
SrtWalker(const ResourcePlan& program, const SrtRuntime& runtime,
|
||||
std::span<const uint8_t> clean_flat_slots = {}, SrtWalker* clean_evaluator = nullptr,
|
||||
Value active_mask = {});
|
||||
~SrtWalker();
|
||||
SrtWalker(const SrtWalker&) = delete;
|
||||
SrtWalker& operator=(const SrtWalker&) = delete;
|
||||
|
||||
// Evaluates one runtime snapshot transactionally. Scalar values and ReadConst results shared by
|
||||
// several descriptors are memoized once across the batch.
|
||||
bool EvaluateDescriptorSources(const ResourcePlan& program, std::span<const uint32_t> sources,
|
||||
const SrtRuntime& runtime, std::vector<DescriptorValue>& results);
|
||||
bool Evaluate(Value value, uint32_t& result);
|
||||
bool EvaluateDescriptor(uint32_t source, DescriptorValue& result);
|
||||
// An empty span means that all sources are active.
|
||||
std::span<const uint8_t> FindActiveSources();
|
||||
bool RefreshFlatBuffer(std::vector<uint32_t>& flat);
|
||||
|
||||
// Evaluates potentially reachable descriptor sources and the flattened immediate SRT with one
|
||||
// memoized scalar walk. Inactive descriptors are zero; on failure no destination is changed.
|
||||
bool EvaluateRuntimeSources(const ResourcePlan& program, std::span<const uint32_t> sources,
|
||||
const SrtRuntime& runtime, std::vector<DescriptorValue>& results,
|
||||
std::vector<uint32_t>& flat, std::span<const uint8_t> clean_flat_slots,
|
||||
std::vector<uint8_t>& active_sources);
|
||||
private:
|
||||
static ResourcePlan::EvaluationContext& AcquireContext(const ResourcePlan& program);
|
||||
static float Float32(uint64_t bits);
|
||||
bool EvaluateWide(Value value, uint64_t& result);
|
||||
bool Arg(const Inst& inst, size_t index, uint64_t& result);
|
||||
bool EvaluatePhi(const Inst& inst, uint64_t& result);
|
||||
bool EvaluateExtract(const Inst& inst, uint64_t& result);
|
||||
bool EvaluateRawRead(const Inst& inst, uint64_t& result);
|
||||
bool EvaluateInst(const Inst& inst, uint64_t& result);
|
||||
|
||||
bool WalkSrt(const ResourcePlan& program, const SrtRuntime& runtime,
|
||||
std::vector<uint32_t>& flat);
|
||||
const ResourcePlan& m_program;
|
||||
SrtRuntime m_runtime;
|
||||
std::span<const uint8_t> m_clean_flat_slots;
|
||||
SrtWalker* m_clean_evaluator = nullptr;
|
||||
Value m_active_mask;
|
||||
ResourcePlan::EvaluationContext& m_context;
|
||||
};
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::IR
|
||||
|
||||
|
||||
@@ -108,7 +108,7 @@ static uint64_t GetDeclaredShaderHash(uint64_t shader_addr) {
|
||||
|
||||
static ShaderParams GetShaderParams(uint64_t shader_addr, const char* label, uint64_t declared_hash,
|
||||
std::span<const uint32_t> user_data,
|
||||
const ShaderMappedData& data) {
|
||||
const ShaderMappedData& data, uint32_t user_data_base = 0) {
|
||||
if (data.code_size_bytes == 0 || data.code_size_bytes % sizeof(uint32_t) != 0) {
|
||||
EXIT("%s hash=0x%016" PRIx64 " shader=0x%016" PRIx64
|
||||
" has invalid AGC shader_size=0x%08" PRIx32 "\n",
|
||||
@@ -116,12 +116,16 @@ static ShaderParams GetShaderParams(uint64_t shader_addr, const char* label, uin
|
||||
}
|
||||
const auto code_words = data.code_size_bytes / sizeof(uint32_t);
|
||||
const auto code = std::span {reinterpret_cast<const uint32_t*>(shader_addr), code_words};
|
||||
return {
|
||||
.code = code,
|
||||
.user_data = std::vector<uint32_t>(user_data.begin(), user_data.end()),
|
||||
.hash = declared_hash != 0 ? declared_hash
|
||||
: XXH3_64bits(code.data(), code.size_bytes()),
|
||||
ShaderParams params {
|
||||
.code = code,
|
||||
.user_data_count = static_cast<uint32_t>(user_data.size()) + user_data_base,
|
||||
.hash = declared_hash != 0 ? declared_hash
|
||||
: XXH3_64bits(code.data(), code.size_bytes()),
|
||||
};
|
||||
EXIT_IF(user_data.size() > HW::UserSgprInfo::SGPRS_MAX ||
|
||||
params.user_data_count > params.user_data.size());
|
||||
std::copy(user_data.begin(), user_data.end(), params.user_data.begin() + user_data_base);
|
||||
return params;
|
||||
}
|
||||
|
||||
#if 0
|
||||
@@ -651,6 +655,7 @@ void BuildStageStaticKey(const ShaderVertexInputInfo& info, std::vector<uint32_t
|
||||
key.push_back(static_cast<uint32_t>(info.fetch_attrib_reg));
|
||||
key.push_back(static_cast<uint32_t>(info.fetch_buffer_reg));
|
||||
key.push_back(info.resources_num);
|
||||
key.push_back(info.wave_size);
|
||||
key.push_back(info.scratch_size_dwords);
|
||||
key.push_back(info.pa_cl_vs_out_cntl);
|
||||
key.push_back(static_cast<uint32_t>(info.clip_space.enabled));
|
||||
@@ -721,6 +726,7 @@ void BuildStageStaticKey(const ShaderPixelInputInfo& info, std::vector<uint32_t>
|
||||
key.push_back(static_cast<uint32_t>(info.ps_depth_export_enable));
|
||||
key.push_back(static_cast<uint32_t>(info.ps_sample_mask_export_enable));
|
||||
key.push_back(static_cast<uint32_t>(info.ps_early_z));
|
||||
key.push_back(static_cast<uint32_t>(info.dual_source_blending));
|
||||
key.insert(key.end(), std::begin(info.target_output_mode), std::end(info.target_output_mode));
|
||||
for (uint32_t base = 0; base < info.target_export_mapping.size(); base += 4u) {
|
||||
uint32_t packed = 0;
|
||||
@@ -754,20 +760,22 @@ ShaderParams PrepareProgram(const HW::VertexShaderInfo& regs, const HW::Context&
|
||||
const HW::UserConfig& user_config, ShaderVertexInputInfo& info) {
|
||||
const auto& sh = context.GetShaderRegisters();
|
||||
const auto data = ShaderGetMappedData(regs.es_regs.data_addr, "ShaderGetInputInfoVS():");
|
||||
const bool merged = (context.GetShaderStages() & 0x20u) != 0;
|
||||
auto params = GetShaderParams(
|
||||
regs.es_regs.data_addr, "ShaderRecompiler VS",
|
||||
GetDeclaredShaderHash(regs.es_regs.data_addr),
|
||||
std::span<const uint32_t>(regs.gs_user_sgpr.value, regs.gs_regs.rsrc2.user_sgpr), data);
|
||||
if ((context.GetShaderStages() & 0x20u) == 0) {
|
||||
std::span<const uint32_t>(regs.gs_user_sgpr.value, regs.gs_regs.rsrc2.user_sgpr), data,
|
||||
merged ? 8u : 0u);
|
||||
if (!merged) {
|
||||
if (!ShaderGetStaticVertexInputInfo(regs.es_regs.data_addr, regs.gs_user_sgpr,
|
||||
regs.gs_regs.rsrc2.user_sgpr, sh, data, info)) {
|
||||
EXIT("failed to prepare vertex shader program\n");
|
||||
}
|
||||
info.wave_size = (context.GetShaderStages() & 0x00400000u) != 0 ? 32u : 64u;
|
||||
return params;
|
||||
}
|
||||
// NGG user SGPRs start at s8; a separately compiled GS back half also receives
|
||||
// its user-data pointer in s0:s1.
|
||||
params.user_data.insert(params.user_data.begin(), 8u, 0u);
|
||||
info = {};
|
||||
info.logical_stage = ShaderType::Mesh;
|
||||
info.pa_cl_vs_out_cntl = sh.m_paClVsOutCntl;
|
||||
@@ -838,14 +846,13 @@ PrepareTessellationPrograms(const HW::VertexShaderInfo& regs, const HW::Context&
|
||||
GetShaderParams(regs.ls_regs.data_addr, "ShaderRecompiler LS",
|
||||
GetDeclaredShaderHash(regs.ls_regs.data_addr), local_users, local),
|
||||
GetShaderParams(regs.hs_regs.data_addr, "ShaderRecompiler HS",
|
||||
GetDeclaredShaderHash(regs.hs_regs.data_addr), local_users, control),
|
||||
GetDeclaredShaderHash(regs.hs_regs.data_addr), local_users, control, 8u),
|
||||
GetShaderParams(regs.es_regs.data_addr, "ShaderRecompiler TES",
|
||||
GetDeclaredShaderHash(regs.es_regs.data_addr), evaluation_users,
|
||||
evaluation),
|
||||
};
|
||||
// The fused HS back half receives its separate user-data address in s0:s1.
|
||||
// RDNA2 reserves s0:s7 before the native HS user SGPRs.
|
||||
params[1].user_data.insert(params[1].user_data.begin(), 8u, 0u);
|
||||
params[1].user_data[0] = static_cast<uint32_t>(regs.hs_regs.user_data_addr);
|
||||
params[1].user_data[1] = static_cast<uint32_t>(regs.hs_regs.user_data_addr >> 32u);
|
||||
|
||||
|
||||
@@ -51,10 +51,10 @@ struct CompiledShaderInfo;
|
||||
|
||||
struct ShaderStageRuntime {
|
||||
const ShaderRecompiler::IR::CompiledShaderInfo* program = nullptr;
|
||||
ShaderRecompiler::IR::ResourceSnapshot resources;
|
||||
const ShaderRecompiler::IR::ResourceSnapshot* resources = nullptr;
|
||||
|
||||
[[nodiscard]] explicit operator bool() const {
|
||||
return program != nullptr;
|
||||
return program != nullptr && resources != nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -134,6 +134,7 @@ struct ShaderVertexInputInfo {
|
||||
int fetch_attrib_reg = 0;
|
||||
int fetch_buffer_reg = 0;
|
||||
int buffers_num = 0;
|
||||
uint32_t wave_size = 64;
|
||||
uint32_t scratch_size_dwords = 0;
|
||||
uint32_t pa_cl_vs_out_cntl = 0;
|
||||
ShaderClipSpaceTransform clip_space;
|
||||
@@ -175,6 +176,7 @@ struct ShaderPixelInputInfo {
|
||||
bool ps_depth_export_enable = false;
|
||||
bool ps_sample_mask_export_enable = false;
|
||||
bool ps_sample_shading = false;
|
||||
bool dual_source_blending = false;
|
||||
bool ps_early_z = false;
|
||||
bool ps_execute_on_noop = false;
|
||||
ShaderStageRuntime stage;
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
#include "graphics/shader/shader.h"
|
||||
|
||||
#include <array>
|
||||
#include <span>
|
||||
#include <vector>
|
||||
|
||||
@@ -15,7 +16,8 @@ class UserConfig;
|
||||
|
||||
struct ShaderParams {
|
||||
std::span<const uint32_t> code;
|
||||
std::vector<uint32_t> user_data;
|
||||
std::array<uint32_t, 40> user_data {}; // 32 user SGPRs plus the merged-stage s0:s7 prefix.
|
||||
uint32_t user_data_count = 0;
|
||||
uint64_t hash = 0;
|
||||
std::span<const uint32_t> back_code;
|
||||
|
||||
|
||||
+177
-6
@@ -64,6 +64,7 @@ struct File {
|
||||
std::filesystem::path real_name;
|
||||
std::atomic_bool opened;
|
||||
std::atomic_bool directory;
|
||||
std::atomic_bool readable;
|
||||
std::atomic_bool writable;
|
||||
std::atomic_bool append;
|
||||
std::atomic_bool sync_writes;
|
||||
@@ -175,6 +176,7 @@ int FileDescriptors::CreateDescriptor() {
|
||||
auto* file = new File {};
|
||||
file->opened = false;
|
||||
file->directory = false;
|
||||
file->readable = false;
|
||||
file->writable = false;
|
||||
file->append = false;
|
||||
file->sync_writes = false;
|
||||
@@ -251,16 +253,15 @@ void FileDescriptors::CloseAll() {
|
||||
void MountPoints::Mount(const std::filesystem::path& folder, const std::string& point) {
|
||||
Common::LockGuard lock(m_mutex);
|
||||
|
||||
auto folder_str = Common::FixDirectorySlash(Common::PathToGenericString(folder));
|
||||
auto point_str = Common::FixDirectorySlash(point);
|
||||
|
||||
Umount(point_str);
|
||||
|
||||
MountPair p;
|
||||
p.dir = folder_str;
|
||||
p.dir = folder;
|
||||
p.point = point_str;
|
||||
|
||||
m_mount_pairs.push_back(p);
|
||||
m_mount_pairs.push_back(std::move(p));
|
||||
}
|
||||
|
||||
void MountPoints::Umount(const std::string& folder_or_point) {
|
||||
@@ -270,7 +271,7 @@ void MountPoints::Umount(const std::string& folder_or_point) {
|
||||
|
||||
const auto it = std::find_if(
|
||||
m_mount_pairs.begin(), m_mount_pairs.end(), [&folder_or_point_str](const MountPair& p) {
|
||||
return Common::PathToGenericString(p.dir) == folder_or_point_str ||
|
||||
return Common::FixDirectorySlash(Common::PathToGenericString(p.dir)) == folder_or_point_str ||
|
||||
p.point == folder_or_point_str;
|
||||
});
|
||||
if (it != m_mount_pairs.end()) {
|
||||
@@ -343,14 +344,17 @@ std::filesystem::path MountPoints::ResolvePath(const std::string& mounted_name)
|
||||
while (rel_path.starts_with('/')) {
|
||||
rel_path = Common::RemoveFirst(rel_path, 1);
|
||||
}
|
||||
|
||||
const auto native_rel_path = Common::PathFromUtf8(rel_path);
|
||||
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
if (HasWindowsForbiddenFilenameCharacter(rel_path)) {
|
||||
::printf("FileSystem: Windows-incompatible guest filename: %s\n",
|
||||
mounted_name.c_str());
|
||||
}
|
||||
return p.dir / rel_path;
|
||||
return p.dir / native_rel_path;
|
||||
#else
|
||||
return ResolvePathIgnoringCase(p.dir / rel_path);
|
||||
return ResolvePathIgnoringCase(p.dir / native_rel_path);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -440,6 +444,7 @@ int KYTY_SYSV_ABI KernelOpen(const char* path, int flags, uint16_t mode) {
|
||||
EXIT_IF(file == nullptr || file->opened || file->directory);
|
||||
|
||||
file->name = path;
|
||||
file->readable = rw_mode != Common::File::Mode::Write;
|
||||
file->writable = rw_mode != Common::File::Mode::Read;
|
||||
file->append = append;
|
||||
file->sync_writes = fsync || sync || dsync;
|
||||
@@ -759,6 +764,106 @@ int64_t KYTY_SYSV_ABI KernelPread(int d, void* buf, size_t nbytes, int64_t offse
|
||||
return bytes_read;
|
||||
}
|
||||
|
||||
static int ValidateIovecs(const KernelIovec* iov, int iovcnt, int64_t offset,
|
||||
std::vector<KernelIovec>* buffers, size_t* total) {
|
||||
constexpr int MaxIov = 1024;
|
||||
if (iovcnt < 0 || iovcnt > MaxIov || offset < 0) {
|
||||
return KERNEL_ERROR_EINVAL;
|
||||
}
|
||||
if (iov == nullptr && iovcnt != 0) {
|
||||
return KERNEL_ERROR_EFAULT;
|
||||
}
|
||||
|
||||
if (iovcnt != 0) {
|
||||
buffers->assign(iov, iov + iovcnt);
|
||||
}
|
||||
*total = 0;
|
||||
for (const auto& buffer: *buffers) {
|
||||
if (buffer.iov_len > INT_MAX - *total) {
|
||||
return KERNEL_ERROR_EINVAL;
|
||||
}
|
||||
if (buffer.iov_base == nullptr && buffer.iov_len != 0) {
|
||||
return KERNEL_ERROR_EFAULT;
|
||||
}
|
||||
*total += buffer.iov_len;
|
||||
}
|
||||
return OK;
|
||||
}
|
||||
|
||||
int64_t KYTY_SYSV_ABI KernelPreadv(int d, const KernelIovec* iov, int iovcnt, int64_t offset) {
|
||||
PRINT_NAME();
|
||||
|
||||
std::vector<KernelIovec> buffers;
|
||||
size_t total = 0;
|
||||
const int error = ValidateIovecs(iov, iovcnt, offset, &buffers, &total);
|
||||
if (error != OK) {
|
||||
return error;
|
||||
}
|
||||
|
||||
if (d < DESCRIPTOR_MIN) {
|
||||
return KERNEL_ERROR_EBADF;
|
||||
}
|
||||
if (::Libs::Network::Net::IsSocket(d)) {
|
||||
return KERNEL_ERROR_ESPIPE;
|
||||
}
|
||||
auto* file = g_files->GetFile(d);
|
||||
if (file == nullptr || !file->opened || !file->readable) {
|
||||
return KERNEL_ERROR_EBADF;
|
||||
}
|
||||
|
||||
Common::LockGuard lock(file->mutex);
|
||||
if (file->directory) {
|
||||
return KERNEL_ERROR_EISDIR;
|
||||
}
|
||||
if (total == 0) {
|
||||
return 0;
|
||||
}
|
||||
if (file->special == SpecialFile::Random) {
|
||||
for (const auto& buffer: buffers) {
|
||||
if (buffer.iov_len != 0) {
|
||||
Memory::InvalidateMemory(reinterpret_cast<uint64_t>(buffer.iov_base),
|
||||
buffer.iov_len);
|
||||
FillRandomBuffer(buffer.iov_base, buffer.iov_len);
|
||||
}
|
||||
}
|
||||
return static_cast<int64_t>(total);
|
||||
}
|
||||
if (file->f.IsInvalid()) {
|
||||
return KERNEL_ERROR_EIO;
|
||||
}
|
||||
|
||||
const auto position = file->f.Tell();
|
||||
const auto file_size = file->f.Size();
|
||||
if (!file->f.Seek(static_cast<uint64_t>(offset))) {
|
||||
return KERNEL_ERROR_EIO;
|
||||
}
|
||||
auto remaining = static_cast<uint64_t>(offset) < file_size ? file_size - offset : 0;
|
||||
int64_t bytes_read = 0;
|
||||
for (const auto& buffer: buffers) {
|
||||
if (remaining == 0) {
|
||||
break;
|
||||
}
|
||||
if (buffer.iov_len == 0) {
|
||||
continue;
|
||||
}
|
||||
const auto count = static_cast<uint32_t>(std::min<uint64_t>(buffer.iov_len, remaining));
|
||||
Memory::InvalidateMemory(reinterpret_cast<uint64_t>(buffer.iov_base), count);
|
||||
uint32_t bytes = 0;
|
||||
file->f.Read(buffer.iov_base, count, &bytes);
|
||||
bytes_read += bytes;
|
||||
remaining -= bytes;
|
||||
if (bytes < count) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!file->f.Seek(position)) {
|
||||
return KERNEL_ERROR_EIO;
|
||||
}
|
||||
LOGF("\tReadv %" PRId64 " bytes (pos = %" PRId64 ", iovcnt = %d) from: %s\n", bytes_read,
|
||||
offset, iovcnt, Common::PathToString(file->real_name).c_str());
|
||||
return bytes_read;
|
||||
}
|
||||
|
||||
int64_t KYTY_SYSV_ABI KernelPwrite(int d, const void* buf, size_t nbytes, int64_t offset) {
|
||||
PRINT_NAME();
|
||||
|
||||
@@ -812,6 +917,72 @@ int64_t KYTY_SYSV_ABI KernelPwrite(int d, const void* buf, size_t nbytes, int64_
|
||||
return bytes_written;
|
||||
}
|
||||
|
||||
int64_t KYTY_SYSV_ABI KernelPwritev(int d, const KernelIovec* iov, int iovcnt, int64_t offset) {
|
||||
PRINT_NAME();
|
||||
|
||||
std::vector<KernelIovec> buffers;
|
||||
size_t total = 0;
|
||||
const int error = ValidateIovecs(iov, iovcnt, offset, &buffers, &total);
|
||||
if (error != OK) {
|
||||
return error;
|
||||
}
|
||||
|
||||
if (d < DESCRIPTOR_MIN) {
|
||||
return KERNEL_ERROR_EBADF;
|
||||
}
|
||||
if (::Libs::Network::Net::IsSocket(d)) {
|
||||
return KERNEL_ERROR_ESPIPE;
|
||||
}
|
||||
auto* file = g_files->GetFile(d);
|
||||
if (file == nullptr || !file->opened || !file->writable) {
|
||||
return KERNEL_ERROR_EBADF;
|
||||
}
|
||||
Common::LockGuard lock(file->mutex);
|
||||
if (file->directory) {
|
||||
return KERNEL_ERROR_EISDIR;
|
||||
}
|
||||
if (file->special != SpecialFile::None) {
|
||||
return KERNEL_ERROR_EINVAL;
|
||||
}
|
||||
if (total == 0) {
|
||||
return 0;
|
||||
}
|
||||
if (file->f.IsInvalid()) {
|
||||
return KERNEL_ERROR_EIO;
|
||||
}
|
||||
|
||||
const auto position = file->f.Tell();
|
||||
const auto target = (file->append ? file->f.Size() : static_cast<uint64_t>(offset));
|
||||
if (target > static_cast<uint64_t>(INT64_MAX) - total) {
|
||||
return KERNEL_ERROR_EFBIG;
|
||||
}
|
||||
if (!file->f.Seek(target)) {
|
||||
return KERNEL_ERROR_EIO;
|
||||
}
|
||||
|
||||
int64_t bytes_written = 0;
|
||||
for (const auto& buffer: buffers) {
|
||||
if (buffer.iov_len == 0) {
|
||||
continue;
|
||||
}
|
||||
uint32_t bytes = 0;
|
||||
file->f.Write(buffer.iov_base, static_cast<uint32_t>(buffer.iov_len), &bytes);
|
||||
bytes_written += bytes;
|
||||
if (bytes < buffer.iov_len) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
const bool flushed = !file->sync_writes || file->f.Flush();
|
||||
const bool restored = file->f.Seek(position);
|
||||
if (!flushed || !restored) {
|
||||
return KERNEL_ERROR_EIO;
|
||||
}
|
||||
|
||||
LOGF("\tWritev %" PRId64 " bytes (pos = %" PRId64 ", iovcnt = %d) to: %s\n", bytes_written,
|
||||
offset, iovcnt, Common::PathToString(file->real_name).c_str());
|
||||
return bytes_written;
|
||||
}
|
||||
|
||||
int64_t KYTY_SYSV_ABI KernelLseek(int d, int64_t offset, int whence) {
|
||||
PRINT_NAME();
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "common/stringUtils.h"
|
||||
#include "kernel/pthread.h"
|
||||
|
||||
#include <cstddef>
|
||||
#include <filesystem>
|
||||
|
||||
namespace Libs::LibKernel::FileSystem {
|
||||
@@ -32,6 +33,14 @@ struct FileStat {
|
||||
unsigned int: (8 / 2) * (16 - static_cast<int>(sizeof(KernelTimespec)));
|
||||
};
|
||||
|
||||
struct KernelIovec {
|
||||
void* iov_base;
|
||||
size_t iov_len;
|
||||
};
|
||||
|
||||
static_assert(sizeof(KernelIovec) == 16);
|
||||
static_assert(offsetof(KernelIovec, iov_len) == 8);
|
||||
|
||||
void Initialize();
|
||||
void Shutdown();
|
||||
void EmergencyShutdown();
|
||||
@@ -51,8 +60,10 @@ int KYTY_SYSV_ABI KernelOpen(const char* path, int flags, uint16_t mode);
|
||||
int KYTY_SYSV_ABI KernelClose(int d);
|
||||
int64_t KYTY_SYSV_ABI KernelRead(int d, void* buf, size_t nbytes);
|
||||
int64_t KYTY_SYSV_ABI KernelPread(int d, void* buf, size_t nbytes, int64_t offset);
|
||||
int64_t KYTY_SYSV_ABI KernelPreadv(int d, const KernelIovec* iov, int iovcnt, int64_t offset);
|
||||
int64_t KYTY_SYSV_ABI KernelWrite(int d, const void* buf, size_t nbytes);
|
||||
int64_t KYTY_SYSV_ABI KernelPwrite(int d, const void* buf, size_t nbytes, int64_t offset);
|
||||
int64_t KYTY_SYSV_ABI KernelPwritev(int d, const KernelIovec* iov, int iovcnt, int64_t offset);
|
||||
int64_t KYTY_SYSV_ABI KernelLseek(int d, int64_t offset, int whence);
|
||||
int KYTY_SYSV_ABI KernelStat(const char* path, FileStat* sb);
|
||||
int KYTY_SYSV_ABI KernelFstat(int d, FileStat* sb);
|
||||
|
||||
@@ -20,7 +20,7 @@ add_executable(launcher WIN32
|
||||
)
|
||||
|
||||
|
||||
target_link_libraries(launcher Qt6::Concurrent Qt6::Network Qt6::Widgets Vulkan::Headers SDL2-static kyty_git_version)
|
||||
target_link_libraries(launcher Qt6::Concurrent Qt6::Network Qt6::Widgets Vulkan::Headers SDL3::SDL3-static kyty_git_version)
|
||||
if(WIN32)
|
||||
target_link_libraries(launcher setupapi)
|
||||
endif()
|
||||
|
||||
@@ -167,6 +167,12 @@
|
||||
<property name="text"><string>Enable readback</string></property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="1">
|
||||
<widget class="QCheckBox" name="checkBox_tessellation">
|
||||
<property name="toolTip"><string>Draw tessellated geometry. Disabled by default; unsupported tessellation may cause crashes.</string></property>
|
||||
<property name="text"><string>Enable tessellation support (experimental)</string></property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
@@ -275,13 +281,13 @@
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkBox_amd_cpu">
|
||||
<property name="toolTip"><string>Patch CPU instructions that differ between AMD and the host CPU.</string></property>
|
||||
<property name="text"><string>AMD CPU patch (EXPERIMENTAL)</string></property>
|
||||
<property name="text"><string>AMD CPU patch (experimental)</string></property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkBox_red_zone_protection">
|
||||
<property name="toolTip"><string>Helps prevent crashes in some games on Windows</string></property>
|
||||
<property name="text"><string>Windows SysV red zone crash protection (Experimental)</string></property>
|
||||
<property name="text"><string>Windows SysV red zone crash protection (experimental)</string></property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
|
||||
@@ -44,6 +44,28 @@
|
||||
<property name="spacing">
|
||||
<number>4</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QToolButton" name="refresh_button">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>36</width>
|
||||
<height>36</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="iconSize">
|
||||
<size>
|
||||
<width>32</width>
|
||||
<height>32</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="autoRaise">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="global_settings_button">
|
||||
<property name="minimumSize">
|
||||
@@ -245,6 +267,14 @@
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
<action name="refresh_action">
|
||||
<property name="text">
|
||||
<string>Refresh game list</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Rescan game folders for new games</string>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path fill="#000" d="M17.65 6.35A7.96 7.96 0 0 0 12 4a8 8 0 1 0 7.73 10h-2.08A6 6 0 1 1 12 6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z"/></svg>
|
||||
|
After Width: | Height: | Size: 232 B |
@@ -92,6 +92,7 @@ public:
|
||||
int gpu_index = -1;
|
||||
bool fullscreen_enabled = false;
|
||||
bool readback_linear_images = false;
|
||||
bool tessellation_enabled = false;
|
||||
int vblank_frequency = 60;
|
||||
int console_language = DEFAULT_CONSOLE_LANGUAGE;
|
||||
bool vulkan_validation_enabled = false;
|
||||
@@ -122,6 +123,7 @@ public:
|
||||
gpu_index = other.gpu_index;
|
||||
fullscreen_enabled = other.fullscreen_enabled;
|
||||
readback_linear_images = other.readback_linear_images;
|
||||
tessellation_enabled = other.tessellation_enabled;
|
||||
vblank_frequency = other.vblank_frequency;
|
||||
console_language = other.console_language;
|
||||
vulkan_validation_enabled = other.vulkan_validation_enabled;
|
||||
@@ -142,7 +144,7 @@ public:
|
||||
host_input_mapping = other.host_input_mapping;
|
||||
}
|
||||
|
||||
void CopyFrom(const Configuration& other) {
|
||||
void CopyGameInfoFrom(const Configuration& other) {
|
||||
name = other.name;
|
||||
title_id = other.title_id;
|
||||
gameVersion = other.gameVersion;
|
||||
@@ -152,8 +154,6 @@ public:
|
||||
custom_settings = other.custom_settings;
|
||||
game_status = other.game_status;
|
||||
game_comment = other.game_comment;
|
||||
CopyEmulatorSettingsFrom(other);
|
||||
elf = other.elf;
|
||||
}
|
||||
|
||||
void WriteSettings(QSettings* s) const {
|
||||
@@ -169,6 +169,7 @@ public:
|
||||
KYTY_CFG_SET(gpu_index);
|
||||
KYTY_CFG_SET(fullscreen_enabled);
|
||||
KYTY_CFG_SET(readback_linear_images);
|
||||
KYTY_CFG_SET(tessellation_enabled);
|
||||
KYTY_CFG_SET(vblank_frequency);
|
||||
KYTY_CFG_SET(console_language);
|
||||
KYTY_CFG_SET(vulkan_validation_enabled);
|
||||
@@ -210,6 +211,7 @@ public:
|
||||
}
|
||||
KYTY_CFG_GET(fullscreen_enabled);
|
||||
KYTY_CFG_GET(readback_linear_images);
|
||||
KYTY_CFG_GET(tessellation_enabled);
|
||||
vblank_frequency = s->value("vblank_frequency", vblank_frequency).toInt();
|
||||
console_language = s->value("console_language", console_language).toInt();
|
||||
if (console_language < 0 || console_language > MAX_CONSOLE_LANGUAGE) {
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
#include <QStringList>
|
||||
#include <QWidget>
|
||||
|
||||
#include <memory>
|
||||
|
||||
class ConfigurationItem;
|
||||
class CompatibilityDatabase;
|
||||
class QEvent;
|
||||
@@ -30,10 +32,9 @@ public:
|
||||
[[nodiscard]] const ConfigurationItem* GetSelectedItem() const { return m_selected_item; }
|
||||
ConfigurationItem* GetSelectedItem() { return m_selected_item; }
|
||||
|
||||
[[nodiscard]] const QString& GetSettingsFile() const { return m_settings_file; }
|
||||
[[nodiscard]] const QStringList& GetHostInputMapping() const {
|
||||
return m_global_info.host_input_mapping;
|
||||
}
|
||||
[[nodiscard]] const QString& GetSettingsFile() const { return m_settings_file; }
|
||||
[[nodiscard]] std::unique_ptr<Configuration>
|
||||
CreateConfiguration(const ConfigurationItem& item) const;
|
||||
|
||||
bool EnsureGameDirectory();
|
||||
void ScanGameDirectory();
|
||||
@@ -64,7 +65,6 @@ protected slots:
|
||||
void filter_configurations(const QString& text);
|
||||
|
||||
private:
|
||||
void ClearCustomSettings(ConfigurationItem* item);
|
||||
void SelectItem(QTreeWidgetItem* witem);
|
||||
void ApplyCompatibility();
|
||||
void UpdateToolbarIcons();
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
<file alias="edit-configuration.svg">icons/edit-configuration.svg</file>
|
||||
<file alias="global-settings.svg">icons/global-settings.svg</file>
|
||||
<file alias="input-mapping.svg">icons/input-mapping.svg</file>
|
||||
<file alias="refresh.svg">icons/refresh.svg</file>
|
||||
<file alias="remove-configuration.svg">icons/remove-configuration.svg</file>
|
||||
</qresource>
|
||||
<qresource prefix="/styles">
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#include "common/emulatorConfig.h"
|
||||
#include "configuration.h"
|
||||
#include "mandatoryLineEdit.h"
|
||||
#include "SDL.h"
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <QAbstractItemView>
|
||||
#include <QCheckBox>
|
||||
@@ -173,17 +173,18 @@ void ConfigurationEditDialog::Init(const Configuration& info) {
|
||||
microphone->clear();
|
||||
microphone->addItem(tr("None"), QString {});
|
||||
microphone->setToolTip(tr("Microphone used by games. None supplies silence."));
|
||||
SDL_SetMainReady();
|
||||
if (SDL_InitSubSystem(SDL_INIT_AUDIO) == 0) {
|
||||
const int device_count = SDL_GetNumAudioDevices(SDL_TRUE);
|
||||
if (SDL_InitSubSystem(SDL_INIT_AUDIO)) {
|
||||
int device_count = 0;
|
||||
SDL_AudioDeviceID* devices = SDL_GetAudioRecordingDevices(&device_count);
|
||||
for (int i = 0; i < device_count; i++) {
|
||||
if (const auto* device = SDL_GetAudioDeviceName(i, SDL_TRUE); device != nullptr) {
|
||||
if (const auto* device = SDL_GetAudioDeviceName(devices[i]); device != nullptr) {
|
||||
const auto name = QString::fromUtf8(device);
|
||||
if (microphone->findData(name) < 0) {
|
||||
microphone->addItem(name, name);
|
||||
}
|
||||
}
|
||||
}
|
||||
SDL_free(devices);
|
||||
SDL_QuitSubSystem(SDL_INIT_AUDIO);
|
||||
} else {
|
||||
microphone->setToolTip(tr("Microphones could not be listed: %1")
|
||||
@@ -230,6 +231,7 @@ void ConfigurationEditDialog::Init(const Configuration& info) {
|
||||
: 0);
|
||||
m_ui->checkBox_fullscreen->setChecked(info.fullscreen_enabled);
|
||||
m_ui->checkBox_readback->setChecked(info.readback_linear_images);
|
||||
m_ui->checkBox_tessellation->setChecked(info.tessellation_enabled);
|
||||
m_ui->spinBox_vblank_frequency->setValue(info.vblank_frequency);
|
||||
m_ui->comboBox_console_language->clear();
|
||||
m_ui->comboBox_console_language->addItems(CONSOLE_LANGUAGE_NAMES);
|
||||
@@ -375,6 +377,7 @@ static void UpdateInfo(Configuration& info, Ui::ConfigurationEditDialog& ui) {
|
||||
info.gpu_index = ui.comboBox_gpu->currentIndex() - 1;
|
||||
info.fullscreen_enabled = ui.checkBox_fullscreen->isChecked();
|
||||
info.readback_linear_images = ui.checkBox_readback->isChecked();
|
||||
info.tessellation_enabled = ui.checkBox_tessellation->isChecked();
|
||||
info.vblank_frequency = ui.spinBox_vblank_frequency->value();
|
||||
info.console_language = ui.comboBox_console_language->currentIndex();
|
||||
info.vulkan_validation_enabled = ui.checkBox_vulkan_validation->isChecked();
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include <QAbstractItemModel>
|
||||
#include <QAbstractItemView>
|
||||
#include <QAction>
|
||||
#include <QApplication>
|
||||
#include <QComboBox>
|
||||
#include <QCoreApplication>
|
||||
#include <QCursor>
|
||||
@@ -26,11 +27,13 @@
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonParseError>
|
||||
#include <QKeySequence>
|
||||
#include <QLineEdit>
|
||||
#include <QMenu>
|
||||
#include <QMessageBox>
|
||||
#include <QPainter>
|
||||
#include <QPalette>
|
||||
#include <QPointer>
|
||||
#include <QPushButton>
|
||||
#include <QRegularExpression>
|
||||
#include <QSet>
|
||||
@@ -201,6 +204,10 @@ ConfigurationListWidget::ConfigurationListWidget(QWidget* parent)
|
||||
m_ui->setupUi(this);
|
||||
ConfigureGameList(m_ui);
|
||||
|
||||
m_ui->refresh_action->setShortcuts(QKeySequence::Refresh);
|
||||
m_ui->refresh_button->setDefaultAction(m_ui->refresh_action);
|
||||
addAction(m_ui->refresh_action);
|
||||
|
||||
UpdateToolbarIcons();
|
||||
m_ui->global_settings_button->setToolTip(tr("Edit global settings and game folders"));
|
||||
m_ui->input_mapping_button->setToolTip(tr("Edit global input mapping"));
|
||||
@@ -221,7 +228,10 @@ ConfigurationListWidget::ConfigurationListWidget(QWidget* parent)
|
||||
m_ui->cfgs_list->setColumnWidth(GAME_PATH_COLUMN, 320);
|
||||
m_ui->cfgs_list->setColumnWidth(GAME_STATUS_COLUMN, 150);
|
||||
m_ui->cfgs_list->setColumnWidth(GAME_COMMENT_COLUMN, 240);
|
||||
m_ui->cfgs_list->sortItems(GAME_NAME_COLUMN, Qt::AscendingOrder);
|
||||
|
||||
connect(m_ui->refresh_action, &QAction::triggered, this,
|
||||
&ConfigurationListWidget::ScanGameDirectory);
|
||||
connect(m_ui->global_settings_button, &QToolButton::clicked, this,
|
||||
&ConfigurationListWidget::edit_global_settings);
|
||||
connect(m_ui->input_mapping_button, &QToolButton::clicked, this,
|
||||
@@ -276,6 +286,8 @@ void ConfigurationListWidget::UpdateToolbarIcons() {
|
||||
button->setIcon(QIcon(pixmap));
|
||||
};
|
||||
|
||||
set_icon(m_ui->refresh_button, QStringLiteral(":/icons/refresh.svg"));
|
||||
m_ui->refresh_action->setIcon(m_ui->refresh_button->icon());
|
||||
set_icon(m_ui->global_settings_button, QStringLiteral(":/icons/global-settings.svg"));
|
||||
set_icon(m_ui->input_mapping_button, QStringLiteral(":/icons/input-mapping.svg"));
|
||||
set_icon(m_ui->edit_button, QStringLiteral(":/icons/edit-configuration.svg"));
|
||||
@@ -398,10 +410,17 @@ void ConfigurationListWidget::ApplyCompatibility() {
|
||||
}
|
||||
}
|
||||
|
||||
static Configuration* CloneConfiguration(const Configuration& source) {
|
||||
auto* ret = new Configuration;
|
||||
ret->CopyFrom(source);
|
||||
return ret;
|
||||
std::unique_ptr<Configuration>
|
||||
ConfigurationListWidget::CreateConfiguration(const ConfigurationItem& item) const {
|
||||
auto info = std::make_unique<Configuration>();
|
||||
const auto* custom = m_custom_infos.value(item.GetInfo().game_path);
|
||||
info->CopyGameInfoFrom(item.GetInfo());
|
||||
info->CopyEmulatorSettingsFrom(custom != nullptr ? *custom : m_global_info);
|
||||
if (custom != nullptr && !custom->elf.isEmpty()) {
|
||||
info->elf = custom->elf;
|
||||
}
|
||||
info->host_input_mapping = m_global_info.host_input_mapping;
|
||||
return info;
|
||||
}
|
||||
|
||||
struct GameMetadata {
|
||||
@@ -516,9 +535,6 @@ static void SetGameFiles(Configuration& info, const QString& game_dir, const QSt
|
||||
if (info.name.isEmpty()) {
|
||||
info.name = game.dirName();
|
||||
}
|
||||
if (info.elf.isEmpty()) {
|
||||
info.elf = QStringLiteral("eboot.bin");
|
||||
}
|
||||
}
|
||||
|
||||
static Configuration* FindCustomInfo(QMap<QString, Configuration*>* custom_infos,
|
||||
@@ -564,11 +580,32 @@ bool ConfigurationListWidget::HasValidGameDirectory() const {
|
||||
}
|
||||
|
||||
void ConfigurationListWidget::ScanGameDirectory() {
|
||||
// Commit an active inline editor before destroying its row or replacing its data.
|
||||
if (auto* focused = QApplication::focusWidget();
|
||||
focused != nullptr && m_ui->cfgs_list->isAncestorOf(focused)) {
|
||||
m_ui->cfgs_list->setFocus();
|
||||
}
|
||||
|
||||
const auto selected_path =
|
||||
m_selected_item != nullptr ? m_selected_item->GetInfo().game_path : QString();
|
||||
const bool sorting_enabled = m_ui->cfgs_list->isSortingEnabled();
|
||||
const int sort_column = m_ui->cfgs_list->sortColumn();
|
||||
const auto sort_order = m_ui->cfgs_list->header()->sortIndicatorOrder();
|
||||
const QSignalBlocker blocker(m_ui->cfgs_list);
|
||||
m_ui->cfgs_list->setSortingEnabled(false);
|
||||
m_selected_item = nullptr;
|
||||
m_ui->cfgs_list->clear();
|
||||
m_ui->cfgs_list->SetBackgroundImage({});
|
||||
m_ui->edit_button->setEnabled(false);
|
||||
m_ui->delete_button->setEnabled(false);
|
||||
|
||||
// MainDialog tracks the running item, so keep its identity even if its
|
||||
// directory is no longer present. Other rows can be rebuilt from disk.
|
||||
QMap<QString, ConfigurationItem*> running_items;
|
||||
for (int index = m_ui->cfgs_list->topLevelItemCount() - 1; index >= 0; index--) {
|
||||
auto* item = static_cast<ConfigurationItem*>(m_ui->cfgs_list->topLevelItem(index));
|
||||
if (item->IsRunning()) {
|
||||
running_items.insert(PathKey(item->GetInfo().game_path), item);
|
||||
} else {
|
||||
delete item;
|
||||
}
|
||||
}
|
||||
|
||||
const QString eboot_name = QStringLiteral("eboot.bin");
|
||||
QSet<QString> found_games;
|
||||
@@ -601,16 +638,9 @@ void ConfigurationListWidget::ScanGameDirectory() {
|
||||
auto metadata = GetGameMetadata(
|
||||
game_dir.filePath(QStringLiteral("sce_sys/param.json")), game_dir.dirName());
|
||||
|
||||
auto info = std::make_unique<Configuration>();
|
||||
auto* custom = FindCustomInfo(&m_custom_infos, game_path, legacy_game_path);
|
||||
if (custom != nullptr) {
|
||||
info->CopyFrom(*custom);
|
||||
info->custom_settings = true;
|
||||
} else {
|
||||
info->CopyEmulatorSettingsFrom(m_global_info);
|
||||
info->custom_settings = false;
|
||||
}
|
||||
|
||||
auto info = std::make_unique<Configuration>();
|
||||
info->custom_settings =
|
||||
FindCustomInfo(&m_custom_infos, game_path, legacy_game_path) != nullptr;
|
||||
SetGameFiles(*info, game_dir.absolutePath(), game_path, metadata);
|
||||
const auto* compatibility = m_compatibility->Find(info->title_id);
|
||||
if (compatibility != nullptr) {
|
||||
@@ -618,10 +648,20 @@ void ConfigurationListWidget::ScanGameDirectory() {
|
||||
info->game_comment = compatibility->comment;
|
||||
}
|
||||
|
||||
if (auto* item = running_items.value(game_key); item != nullptr) {
|
||||
// Refresh metadata without losing the running row's identity.
|
||||
const QSignalBlocker status_blocker(item->GetStatusCombo());
|
||||
item->GetInfo().CopyGameInfoFrom(*info);
|
||||
item->Update();
|
||||
item->SetCompatibilityEditable(m_compatibility->IsLocal() &&
|
||||
!item->GetInfo().title_id.trimmed().isEmpty());
|
||||
continue;
|
||||
}
|
||||
|
||||
auto* item = new ConfigurationItem(std::move(info), m_ui->cfgs_list);
|
||||
item->SetCompatibilityEditable(m_compatibility->IsLocal() &&
|
||||
!item->GetInfo().title_id.trimmed().isEmpty());
|
||||
connect(item->GetStatusCombo(), &QComboBox::currentIndexChanged, this,
|
||||
connect(item->GetStatusCombo(), &QComboBox::currentIndexChanged, item,
|
||||
[this, item](int /*index*/) {
|
||||
if (!m_compatibility->IsLocal()) {
|
||||
return;
|
||||
@@ -638,7 +678,7 @@ void ConfigurationListWidget::ScanGameDirectory() {
|
||||
m_ui->cfgs_list->setCurrentItem(item);
|
||||
SelectItem(item);
|
||||
});
|
||||
connect(item->GetCommentEdit(), &QLineEdit::editingFinished, this, [this, item]() {
|
||||
connect(item->GetCommentEdit(), &QLineEdit::editingFinished, item, [this, item]() {
|
||||
if (!m_compatibility->IsLocal()) {
|
||||
return;
|
||||
}
|
||||
@@ -664,8 +704,22 @@ void ConfigurationListWidget::ScanGameDirectory() {
|
||||
}
|
||||
}
|
||||
|
||||
m_ui->cfgs_list->sortItems(GAME_NAME_COLUMN, Qt::AscendingOrder);
|
||||
m_ui->cfgs_list->setSortingEnabled(sorting_enabled);
|
||||
if (sorting_enabled) {
|
||||
m_ui->cfgs_list->sortItems(sort_column, sort_order);
|
||||
}
|
||||
filter_configurations(m_ui->search_line_edit->text());
|
||||
|
||||
ConfigurationItem* selected_item = nullptr;
|
||||
for (int index = 0; index < m_ui->cfgs_list->topLevelItemCount(); index++) {
|
||||
auto* item = static_cast<ConfigurationItem*>(m_ui->cfgs_list->topLevelItem(index));
|
||||
if (!item->isHidden() && item->GetInfo().game_path == selected_path) {
|
||||
selected_item = item;
|
||||
break;
|
||||
}
|
||||
}
|
||||
m_ui->cfgs_list->setCurrentItem(selected_item);
|
||||
SelectItem(selected_item);
|
||||
}
|
||||
|
||||
void ConfigurationListWidget::edit_configuration() {
|
||||
@@ -674,16 +728,19 @@ void ConfigurationListWidget::edit_configuration() {
|
||||
return;
|
||||
}
|
||||
|
||||
ConfigurationEditDialog dlg(item->GetInfo(), this);
|
||||
auto info = CreateConfiguration(*item);
|
||||
ConfigurationEditDialog dlg(*info, this);
|
||||
dlg.setWindowTitle(tr("Edit game settings"));
|
||||
|
||||
if (dlg.exec() == QDialog::Accepted) {
|
||||
info->custom_settings = true;
|
||||
item->GetInfo().custom_settings = true;
|
||||
auto game_path = item->GetInfo().game_path;
|
||||
auto game_path = info->game_path;
|
||||
delete m_custom_infos.take(game_path);
|
||||
m_custom_infos.insert(game_path, CloneConfiguration(item->GetInfo()));
|
||||
m_custom_infos.insert(game_path, info.release());
|
||||
WriteSettings();
|
||||
ScanGameDirectory();
|
||||
item->Update();
|
||||
SelectItem(item);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -695,9 +752,11 @@ void ConfigurationListWidget::delete_configuartion() {
|
||||
|
||||
if (QMessageBox::Yes == QMessageBox::question(this, tr("Clear custom settings"),
|
||||
tr("Do you want to clear custom settings?"))) {
|
||||
ClearCustomSettings(item);
|
||||
delete m_custom_infos.take(item->GetInfo().game_path);
|
||||
item->GetInfo().custom_settings = false;
|
||||
WriteSettings();
|
||||
ScanGameDirectory();
|
||||
item->Update();
|
||||
SelectItem(item);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -712,9 +771,13 @@ void ConfigurationListWidget::edit_global_settings() {
|
||||
|
||||
if (dlg.exec() == QDialog::Accepted) {
|
||||
m_global_info.CopyEmulatorSettingsFrom(info);
|
||||
m_game_dirs = NormalizeGameDirectories(dlg.GetGameDirectories());
|
||||
const auto game_dirs = NormalizeGameDirectories(dlg.GetGameDirectories());
|
||||
const bool game_dirs_changed = game_dirs != m_game_dirs;
|
||||
m_game_dirs = game_dirs;
|
||||
WriteSettings();
|
||||
ScanGameDirectory();
|
||||
if (game_dirs_changed) {
|
||||
ScanGameDirectory();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -726,10 +789,6 @@ void ConfigurationListWidget::edit_input_mapping() {
|
||||
}
|
||||
}
|
||||
|
||||
void ConfigurationListWidget::ClearCustomSettings(ConfigurationItem* item) {
|
||||
delete m_custom_infos.take(item->GetInfo().game_path);
|
||||
}
|
||||
|
||||
void ConfigurationListWidget::ViewTrophies() {
|
||||
auto* current = static_cast<ConfigurationItem*>(m_ui->cfgs_list->currentItem());
|
||||
auto* item = current != nullptr ? current : m_selected_item;
|
||||
@@ -871,12 +930,13 @@ void ConfigurationListWidget::show_context_menu(const QPoint& pos) {
|
||||
connect(action_view_trophies, &QAction::triggered, this,
|
||||
&ConfigurationListWidget::ViewTrophies);
|
||||
QAction* action_patches = menu.addAction(tr("Cheats (experimental)..."));
|
||||
connect(action_patches, &QAction::triggered, this, [this, item]() {
|
||||
if (item != nullptr) {
|
||||
auto* dialog = new PatchesDialog(item->GetInfo(), this);
|
||||
dialog->show();
|
||||
}
|
||||
});
|
||||
connect(action_patches, &QAction::triggered, this,
|
||||
[this, item = QPointer<ConfigurationItem>(item)]() {
|
||||
if (item != nullptr) {
|
||||
auto* dialog = new PatchesDialog(item->GetInfo(), this);
|
||||
dialog->show();
|
||||
}
|
||||
});
|
||||
action_patches->setVisible(item != nullptr &&
|
||||
PatchesDialog::IsSupportedTitleId(item->GetInfo().title_id));
|
||||
QAction* action_remove_save_data =
|
||||
|
||||
@@ -90,6 +90,7 @@ QString KeyName(const QKeyEvent& event) {
|
||||
}
|
||||
|
||||
switch (key) {
|
||||
case Qt::Key_Space: return QStringLiteral("Space");
|
||||
case Qt::Key_Return:
|
||||
case Qt::Key_Enter: return QStringLiteral("Return");
|
||||
case Qt::Key_Backspace: return QStringLiteral("Backspace");
|
||||
@@ -132,8 +133,7 @@ public:
|
||||
|
||||
auto* layout = new QVBoxLayout(this);
|
||||
m_label = new QLabel(
|
||||
tr("Press a key or mouse button.\nSpace, F1, F7, and F11 are reserved; Esc cancels."),
|
||||
this);
|
||||
tr("Press a key or mouse button.\nF1, F7, and F11 are reserved; Esc cancels."), this);
|
||||
m_label->setAlignment(Qt::AlignCenter);
|
||||
layout->addWidget(m_label);
|
||||
}
|
||||
@@ -149,8 +149,8 @@ protected:
|
||||
reject();
|
||||
return;
|
||||
}
|
||||
if (event->key() == Qt::Key_Space || event->key() == Qt::Key_F1 ||
|
||||
event->key() == Qt::Key_F7 || event->key() == Qt::Key_F11) {
|
||||
if (event->key() == Qt::Key_F1 || event->key() == Qt::Key_F7 ||
|
||||
event->key() == Qt::Key_F11) {
|
||||
m_label->setText(tr("That key is reserved by the emulator."));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -226,6 +226,9 @@ static QStringList CreateEmulatorArgs(const Configuration& info) {
|
||||
args << "--fullscreen";
|
||||
}
|
||||
args << "--readback-linear-images" << BoolArg(info.readback_linear_images);
|
||||
if (info.tessellation_enabled) {
|
||||
args << "--tessellation";
|
||||
}
|
||||
args << "--vblank-frequency" << QString::number(info.vblank_frequency);
|
||||
args << "--console-language" << QString::number(info.console_language);
|
||||
args << "--vulkan-validation" << BoolArg(info.vulkan_validation_enabled);
|
||||
@@ -490,10 +493,8 @@ void MainDialogPrivate::Run() {
|
||||
|
||||
m_running_item->SetRunning(true);
|
||||
|
||||
Configuration info;
|
||||
info.CopyFrom(m_running_item->GetInfo());
|
||||
info.host_input_mapping = m_ui->widget->GetHostInputMapping();
|
||||
m_main_dialog->RunInterpreter(&m_process, info);
|
||||
auto info = m_ui->widget->CreateConfiguration(*m_running_item);
|
||||
m_main_dialog->RunInterpreter(&m_process, *info);
|
||||
|
||||
Update();
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
PatchesDialog::PatchesDialog(const Configuration& game, QWidget* parent)
|
||||
: QDialog(parent), m_title_id(game.title_id.trimmed().toUpper()) {
|
||||
setAttribute(Qt::WA_DeleteOnClose);
|
||||
setWindowTitle(tr("Cheats (Experimental) - %1").arg(game.name));
|
||||
setWindowTitle(tr("Cheats (experimental) - %1").arg(game.name));
|
||||
resize(640, 480);
|
||||
|
||||
auto* layout = new QVBoxLayout(this);
|
||||
|
||||
+3
-8
@@ -849,8 +849,9 @@ int KYTY_SYSV_ABI AjmMemoryUnregister(uint32_t context, void* ptr) {
|
||||
int KYTY_SYSV_ABI AjmBatchInitialize(void* buffer, size_t size, AjmBatchInfo* info) {
|
||||
PRINT_NAME();
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(buffer == nullptr);
|
||||
EXIT_NOT_IMPLEMENTED(info == nullptr);
|
||||
if (buffer == nullptr || info == nullptr) {
|
||||
return AJM_ERROR_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
info->buffer = buffer;
|
||||
info->offset = 0;
|
||||
@@ -1195,12 +1196,6 @@ int KYTY_SYSV_ABI AjmBatchJobRunSplit(AjmBatchInfo* info, uint32_t instance, uin
|
||||
(void)AjmGetInstanceFormat(instance, nullptr, &decoder, &gapless);
|
||||
AjmWriteSideband(flags, sideband_output, sideband_output_size, decoder, gapless, decode_result);
|
||||
|
||||
LOGF("\t instance = 0x%08" PRIx32 ", flags = 0x%016" PRIx64 ", input_buffers = 0x%016" PRIx64
|
||||
", input_num = 0x%016" PRIx64 ", output_buffers = 0x%016" PRIx64
|
||||
", output_num = 0x%016" PRIx64 "\n",
|
||||
instance, flags, reinterpret_cast<uint64_t>(input_buffers),
|
||||
static_cast<uint64_t>(input_buffers_num), reinterpret_cast<uint64_t>(output_buffers),
|
||||
static_cast<uint64_t>(output_buffers_num));
|
||||
return AjmAppendJob(info, 16 * (input_buffers_num + output_buffers_num) + 32, "run-split");
|
||||
}
|
||||
|
||||
|
||||
+32
-22
@@ -38,9 +38,9 @@ static constexpr uint32_t AJM_DEC_M4AAC_MAX_SAMPLING_FREQ_INDEX = 11;
|
||||
|
||||
class AjmAacDecoder final: public AjmDecoder {
|
||||
public:
|
||||
AjmAacDecoder(uint32_t channels, uint32_t sample_rate, AjmSampleEncoding encoding,
|
||||
AjmAacDecoder(uint32_t max_channels, uint32_t sample_rate, AjmSampleEncoding encoding,
|
||||
uint64_t flags)
|
||||
: AjmDecoder(channels, sample_rate, encoding), m_flags(flags) {
|
||||
: AjmDecoder(0, sample_rate, encoding), m_max_channels(max_channels), m_flags(flags) {
|
||||
m_codec = avcodec_find_decoder(AV_CODEC_ID_AAC);
|
||||
if (m_codec == nullptr) {
|
||||
LOGF("AJM AAC: avcodec_find_decoder failed\n");
|
||||
@@ -94,6 +94,11 @@ public:
|
||||
((m_flags & AJM_INSTANCE_FLAG_DEC_M4AAC_ENABLE_NONDELAY_OUTPUT) != 0 ? 0u : 2u);
|
||||
m_total_decoded_samples = 0;
|
||||
m_bitrate = 0;
|
||||
m_channels = 0;
|
||||
m_raw_configured = false;
|
||||
if (m_config_number == AJM_DEC_M4AAC_CONFIG_NUMBER_RAW) {
|
||||
m_sample_rate = SamplingRateFromIndex(m_sampling_freq_index);
|
||||
}
|
||||
if (m_is_initialized) {
|
||||
OpenDecoder();
|
||||
}
|
||||
@@ -189,26 +194,6 @@ private:
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_config_number == AJM_DEC_M4AAC_CONFIG_NUMBER_RAW) {
|
||||
const auto sample_index =
|
||||
std::min(m_sampling_freq_index, AJM_DEC_M4AAC_MAX_SAMPLING_FREQ_INDEX);
|
||||
const auto channels = std::clamp<uint32_t>(m_channels, 1u, AJM_DEC_M4AAC_MAX_CHANNELS);
|
||||
constexpr uint32_t audio_object_type = 2;
|
||||
|
||||
auto* extradata = static_cast<uint8_t*>(av_mallocz(2 + AV_INPUT_BUFFER_PADDING_SIZE));
|
||||
if (extradata == nullptr) {
|
||||
Release();
|
||||
return;
|
||||
}
|
||||
|
||||
extradata[0] = static_cast<uint8_t>((audio_object_type << 3u) | (sample_index >> 1u));
|
||||
extradata[1] = static_cast<uint8_t>(((sample_index & 0x1u) << 7u) |
|
||||
(std::min(channels, 7u) << 3u));
|
||||
m_codec_context->extradata = extradata;
|
||||
m_codec_context->extradata_size = 2;
|
||||
SetFormat(channels, SamplingRateFromIndex(sample_index), m_sample_encoding);
|
||||
}
|
||||
|
||||
if (avcodec_open2(m_codec_context, m_codec, nullptr) < 0) {
|
||||
LOGF("AJM AAC: decoder initialization failed\n");
|
||||
Release();
|
||||
@@ -222,6 +207,10 @@ private:
|
||||
}
|
||||
|
||||
const auto channels = static_cast<uint32_t>(std::max(frame->ch_layout.nb_channels, 1));
|
||||
if (channels > m_max_channels) {
|
||||
result->result = AJM_RESULT_TOO_MANY_CHANNELS;
|
||||
return false;
|
||||
}
|
||||
const auto bpf = channels * AjmBytesPerSample(m_sample_encoding);
|
||||
if (bpf == 0) {
|
||||
result->result = AJM_RESULT_INVALID_PARAMETER;
|
||||
@@ -306,6 +295,25 @@ private:
|
||||
return false;
|
||||
}
|
||||
std::memcpy(packet->data, packet_data, static_cast<size_t>(packet_size));
|
||||
if (m_config_number == AJM_DEC_M4AAC_CONFIG_NUMBER_RAW && !m_raw_configured) {
|
||||
// Only indexed stereo starts with a channel pair; the instance limit is not
|
||||
// the stream's channel count. A leading SCE remains ambiguous.
|
||||
const auto channels = (packet_data[0] >> 5u) == 1u
|
||||
? 2u
|
||||
: std::clamp(m_max_channels, 1u, AJM_DEC_M4AAC_MAX_CHANNELS);
|
||||
auto* extradata = av_packet_new_side_data(packet, AV_PKT_DATA_NEW_EXTRADATA, 2);
|
||||
if (extradata == nullptr) {
|
||||
av_packet_free(&packet);
|
||||
result->result = AJM_RESULT_CODEC_ERROR | AJM_RESULT_FATAL;
|
||||
return false;
|
||||
}
|
||||
constexpr uint32_t audio_object_type = 2;
|
||||
extradata[0] = static_cast<uint8_t>((audio_object_type << 3u) |
|
||||
(m_sampling_freq_index >> 1u));
|
||||
extradata[1] = static_cast<uint8_t>(((m_sampling_freq_index & 1u) << 7u) |
|
||||
(std::min(channels, 7u) << 3u));
|
||||
m_raw_configured = true;
|
||||
}
|
||||
|
||||
int rc = avcodec_send_packet(m_codec_context, packet);
|
||||
av_packet_free(&packet);
|
||||
@@ -353,12 +361,14 @@ private:
|
||||
|
||||
const AVCodec* m_codec = nullptr;
|
||||
AVCodecContext* m_codec_context = nullptr;
|
||||
const uint32_t m_max_channels;
|
||||
uint64_t m_flags = 0;
|
||||
uint32_t m_config_number = AJM_DEC_M4AAC_CONFIG_NUMBER_ADTS;
|
||||
uint32_t m_sampling_freq_index = 3;
|
||||
uint32_t m_skip_frames = 2;
|
||||
uint32_t m_bitrate = 0;
|
||||
bool m_is_initialized = false;
|
||||
bool m_raw_configured = false;
|
||||
};
|
||||
|
||||
} // namespace Libs::Audio::Ajm
|
||||
@@ -464,6 +464,14 @@ private:
|
||||
const auto* chunk = input + offset;
|
||||
const auto chunk_size = static_cast<size_t>(AjmReadLe32(chunk + 4));
|
||||
const auto payload = offset + 8;
|
||||
// The data chunk describes the whole stream; this job may contain only its prefix.
|
||||
if (AjmFourCcEquals(chunk, 'd', 'a', 't', 'a')) {
|
||||
*data_offset = payload;
|
||||
result->input_consumed = payload;
|
||||
result->format = GetFormat();
|
||||
result->total_decoded_samples = m_total_decoded_samples;
|
||||
return true;
|
||||
}
|
||||
if (payload > input_size || chunk_size > input_size - payload) {
|
||||
result->result = AJM_RESULT_PARTIAL_INPUT;
|
||||
return false;
|
||||
@@ -496,12 +504,6 @@ private:
|
||||
LOGF("AJM ATRAC9 gapless: total=%" PRIu32 ", skip=%" PRIu16 "\n",
|
||||
gapless_decode.total_samples, gapless_decode.skip_samples);
|
||||
}
|
||||
} else if (AjmFourCcEquals(chunk, 'd', 'a', 't', 'a')) {
|
||||
*data_offset = payload;
|
||||
result->input_consumed = payload;
|
||||
result->format = GetFormat();
|
||||
result->total_decoded_samples = m_total_decoded_samples;
|
||||
return true;
|
||||
}
|
||||
|
||||
const auto padded_size = chunk_size + (chunk_size & 1u);
|
||||
|
||||
@@ -72,6 +72,7 @@ constexpr int32_t AJM_RESULT_INVALID_DATA = 0x00000002;
|
||||
constexpr int32_t AJM_RESULT_INVALID_PARAMETER = 0x00000004;
|
||||
constexpr int32_t AJM_RESULT_PARTIAL_INPUT = 0x00000008;
|
||||
constexpr int32_t AJM_RESULT_NOT_ENOUGH_ROOM = 0x00000010;
|
||||
constexpr int32_t AJM_RESULT_TOO_MANY_CHANNELS = 0x00000040;
|
||||
constexpr int32_t AJM_RESULT_CODEC_ERROR = 0x40000000;
|
||||
constexpr int32_t AJM_RESULT_FATAL = static_cast<int32_t>(0x80000000u);
|
||||
|
||||
|
||||
+643
-226
File diff suppressed because it is too large.
Load diff
+1
-1
@@ -336,7 +336,7 @@ int KYTY_SYSV_ABI Ngs2RackGetVoiceHandle(uintptr_t rack_handle, uint32_t voice_i
|
||||
uintptr_t* handle);
|
||||
int KYTY_SYSV_ABI Ngs2VoiceControl(uintptr_t voice_handle, const Ngs2VoiceParamHeader* param_list);
|
||||
int KYTY_SYSV_ABI Ngs2VoiceRunCommands(uintptr_t voice_handle, const void* commands,
|
||||
uint32_t num_commands, uint32_t flags);
|
||||
size_t num_commands);
|
||||
int KYTY_SYSV_ABI Ngs2VoiceGetState(uintptr_t voice_handle, Ngs2VoiceState* state,
|
||||
size_t state_size);
|
||||
int KYTY_SYSV_ABI Ngs2VoiceGetStateFlags(uintptr_t voice_handle, uint32_t* state_flags);
|
||||
|
||||
+108
-84
@@ -117,6 +117,13 @@ struct AvPlayerEventReplacement {
|
||||
void* object_pointer = nullptr;
|
||||
AvPlayerEventCallback event_callback = nullptr;
|
||||
};
|
||||
static void emit_event(AvPlayerEventReplacement event, int32_t id, void* data = nullptr) {
|
||||
if (event.event_callback != nullptr) {
|
||||
LOGF("\t event_id = %d\n", id);
|
||||
event.event_callback(event.object_pointer, id, 0, data);
|
||||
}
|
||||
}
|
||||
|
||||
struct AvPlayerUri {
|
||||
const char* name = nullptr;
|
||||
uint32_t length = 0;
|
||||
@@ -639,9 +646,9 @@ private:
|
||||
|
||||
class Source {
|
||||
public:
|
||||
Source(AvPlayerMemAllocator m, AvPlayerFileReplacement f, int32_t video_buffers, bool vdec2,
|
||||
uint32_t demux_buffer_size)
|
||||
: mem(m), file(f),
|
||||
Source(AvPlayerMemAllocator m, AvPlayerFileReplacement f, AvPlayerEventReplacement e,
|
||||
int32_t video_buffers, bool vdec2, uint32_t demux_buffer_size)
|
||||
: mem(m), file(f), event(e),
|
||||
max_video_buffers(std::clamp(video_buffers <= 0 ? 2 : video_buffers, 2, 16)),
|
||||
use_vdec2(vdec2),
|
||||
video_packets(
|
||||
@@ -699,7 +706,7 @@ public:
|
||||
int Enable(uint32_t id) {
|
||||
std::scoped_lock lifecycle_lock(lifecycle_mutex);
|
||||
std::lock_guard state_lock(mutex);
|
||||
if (!stopped) {
|
||||
if (state == State::Playing) {
|
||||
return AVPLAYER_ERROR_OPERATION_FAILED;
|
||||
}
|
||||
if (fmt == nullptr || id >= fmt->nb_streams) {
|
||||
@@ -717,7 +724,7 @@ public:
|
||||
int Disable(uint32_t id) {
|
||||
std::scoped_lock lifecycle_lock(lifecycle_mutex);
|
||||
std::lock_guard state_lock(mutex);
|
||||
if (!stopped) {
|
||||
if (state == State::Playing) {
|
||||
return AVPLAYER_ERROR_OPERATION_FAILED;
|
||||
}
|
||||
if (video_id == static_cast<int>(id)) {
|
||||
@@ -748,9 +755,11 @@ public:
|
||||
return AVPLAYER_ERROR_OPERATION_FAILED;
|
||||
}
|
||||
time = CurrentTimeNoLock();
|
||||
restart = !stopped;
|
||||
restart = state == State::Playing;
|
||||
was_paused = paused;
|
||||
stopped = true;
|
||||
if (restart) {
|
||||
state = State::Ready;
|
||||
}
|
||||
}
|
||||
if (restart) {
|
||||
StopWorkers();
|
||||
@@ -771,7 +780,7 @@ public:
|
||||
int StartImpl(uint64_t ms, bool paused_after_start = false, bool show_seek_frame = false) {
|
||||
{
|
||||
std::lock_guard lock(mutex);
|
||||
stopped = true;
|
||||
state = State::Ready;
|
||||
}
|
||||
StopWorkers();
|
||||
interrupt_io = false;
|
||||
@@ -784,14 +793,17 @@ public:
|
||||
AutoEnable();
|
||||
}
|
||||
if (!video_id && !audio_id) {
|
||||
state = State::Stopped;
|
||||
return AVPLAYER_ERROR_OPERATION_FAILED;
|
||||
}
|
||||
if (!OpenCodecs()) {
|
||||
ResetNoLock(true);
|
||||
state = State::Stopped;
|
||||
return AVPLAYER_ERROR_OPERATION_FAILED;
|
||||
}
|
||||
if (!AllocateBuffers()) {
|
||||
ResetNoLock(true);
|
||||
state = State::Stopped;
|
||||
return AVPLAYER_ERROR_NO_MEMORY;
|
||||
}
|
||||
SeekNoLock(ms);
|
||||
@@ -802,12 +814,11 @@ public:
|
||||
pause_time = clock_start;
|
||||
seek_video_frame_pending =
|
||||
paused_after_start && show_seek_frame && video_id.has_value();
|
||||
stopped = false;
|
||||
}
|
||||
|
||||
if (!StartWorkers()) {
|
||||
std::lock_guard lock(mutex);
|
||||
stopped = true;
|
||||
state = State::Stopped;
|
||||
ResetNoLock(true);
|
||||
result = AVPLAYER_ERROR_OPERATION_FAILED;
|
||||
}
|
||||
@@ -817,28 +828,39 @@ public:
|
||||
return result;
|
||||
}
|
||||
int Stop() {
|
||||
std::scoped_lock lifecycle_lock(lifecycle_mutex);
|
||||
bool was_playing_video = false;
|
||||
bool was_playing_video = false;
|
||||
AvPlayerEventReplacement stop_event {};
|
||||
{
|
||||
std::lock_guard lock(mutex);
|
||||
was_playing_video = !stopped && video_id.has_value();
|
||||
stopped = true;
|
||||
}
|
||||
StopWorkers();
|
||||
{
|
||||
std::lock_guard lock(mutex);
|
||||
ResetNoLock();
|
||||
std::scoped_lock lifecycle_lock(lifecycle_mutex);
|
||||
{
|
||||
std::lock_guard lock(mutex);
|
||||
if (state == State::Playing) {
|
||||
was_playing_video = video_id.has_value();
|
||||
stop_event = event;
|
||||
}
|
||||
state = State::Stopped;
|
||||
}
|
||||
StopWorkers();
|
||||
{
|
||||
std::lock_guard lock(mutex);
|
||||
ResetNoLock();
|
||||
}
|
||||
}
|
||||
if (was_playing_video) {
|
||||
::printf("AvPlayer video stopped\n");
|
||||
}
|
||||
emit_event(stop_event, AVPLAYER_EVENT_STATE_STOP);
|
||||
return 0;
|
||||
}
|
||||
void Pause() {
|
||||
int Pause() {
|
||||
std::scoped_lock lifecycle_lock(lifecycle_mutex);
|
||||
std::lock_guard lock(mutex);
|
||||
if (state != State::Playing) {
|
||||
return AVPLAYER_ERROR_OPERATION_FAILED;
|
||||
}
|
||||
paused = true;
|
||||
pause_time = std::chrono::steady_clock::now();
|
||||
return 0;
|
||||
}
|
||||
void Resume() {
|
||||
std::scoped_lock lifecycle_lock(lifecycle_mutex);
|
||||
@@ -860,9 +882,8 @@ public:
|
||||
}
|
||||
bool Active() const {
|
||||
std::lock_guard lock(mutex);
|
||||
return !stopped && !pipeline_failed &&
|
||||
(!demux_eof || !video_done || !audio_done || !video_frames.Empty() ||
|
||||
!audio_frames.Empty());
|
||||
return state == State::Ready ||
|
||||
(state == State::Playing && !pipeline_failed && !DrainedNoLock());
|
||||
}
|
||||
uint64_t CurrentTime() const {
|
||||
std::lock_guard lock(mutex);
|
||||
@@ -873,12 +894,15 @@ public:
|
||||
bool was_paused = false;
|
||||
{
|
||||
std::lock_guard lock(mutex);
|
||||
if (state != State::Playing) {
|
||||
return AVPLAYER_ERROR_OPERATION_FAILED;
|
||||
}
|
||||
was_paused = paused;
|
||||
}
|
||||
return StartImpl(ms, was_paused, true);
|
||||
}
|
||||
uint64_t CurrentTimeNoLock() const {
|
||||
if (stopped) {
|
||||
if (state != State::Playing) {
|
||||
return 0;
|
||||
}
|
||||
using namespace std::chrono;
|
||||
@@ -915,7 +939,7 @@ public:
|
||||
return false;
|
||||
}
|
||||
std::lock_guard lock(mutex);
|
||||
if (stopped || !video_id) {
|
||||
if (state != State::Playing || !video_id) {
|
||||
return false;
|
||||
}
|
||||
const bool deliver_seek_frame = paused && seek_video_frame_pending;
|
||||
@@ -955,7 +979,8 @@ public:
|
||||
return false;
|
||||
}
|
||||
std::lock_guard lock(mutex);
|
||||
if (paused || stopped || !audio_id || trick_speed != AVPLAYER_TRICK_SPEED_NORMAL) {
|
||||
if (paused || state != State::Playing || !audio_id ||
|
||||
trick_speed != AVPLAYER_TRICK_SPEED_NORMAL) {
|
||||
return false;
|
||||
}
|
||||
auto frame = audio_frames.TryPop();
|
||||
@@ -988,6 +1013,12 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
enum class State { Ready, Playing, Stopped };
|
||||
|
||||
bool DrainedNoLock() const {
|
||||
return demux_eof && video_done && audio_done && video_frames.Empty() &&
|
||||
audio_frames.Empty();
|
||||
}
|
||||
void RecordLoopBoundary(const ReadyFrame& frame) {
|
||||
if (frame.timestamp_offset > last_output_loop_offset) {
|
||||
last_output_loop_offset = frame.timestamp_offset;
|
||||
@@ -1002,23 +1033,17 @@ private:
|
||||
streamer.reset();
|
||||
}
|
||||
void ResetNoLock(bool retain_output_buffers = false) {
|
||||
std::optional<ReadyFrame> delivered_video;
|
||||
std::optional<ReadyFrame> delivered_audio;
|
||||
std::deque<std::unique_ptr<GuestBuffer>> in_flight_video;
|
||||
if (retain_output_buffers) {
|
||||
delivered_video = std::move(current_video);
|
||||
delivered_audio = std::move(current_audio);
|
||||
in_flight_video = std::move(retired_video);
|
||||
}
|
||||
video_packets.Clear();
|
||||
audio_packets.Clear();
|
||||
video_frames.Clear();
|
||||
audio_frames.Clear();
|
||||
video_buffers.Clear();
|
||||
audio_buffers.Clear();
|
||||
retired_video.clear();
|
||||
current_video.reset();
|
||||
current_audio.reset();
|
||||
if (!retain_output_buffers) {
|
||||
retired_video.clear();
|
||||
current_video.reset();
|
||||
current_audio.reset();
|
||||
}
|
||||
if (sws != nullptr) {
|
||||
sws_freeContext(sws);
|
||||
sws = nullptr;
|
||||
@@ -1042,9 +1067,6 @@ private:
|
||||
last_audio_ts = 0;
|
||||
last_output_loop_offset = 0;
|
||||
pending_loop_warnings = 0;
|
||||
current_video = std::move(delivered_video);
|
||||
current_audio = std::move(delivered_audio);
|
||||
retired_video = std::move(in_flight_video);
|
||||
}
|
||||
void AutoEnable() {
|
||||
for (uint32_t i = 0; i < fmt->nb_streams; i++) {
|
||||
@@ -1174,6 +1196,7 @@ private:
|
||||
return true;
|
||||
}
|
||||
bool StartWorkers() {
|
||||
std::unique_lock lock(mutex);
|
||||
worker_stop = false;
|
||||
interrupt_io = false;
|
||||
pipeline_failed = false;
|
||||
@@ -1183,9 +1206,11 @@ private:
|
||||
if ((video_id && !CreateWorker(video_thread, VideoDecoderEntry, "AvPlayerVideoDecoder")) ||
|
||||
(audio_id && !CreateWorker(audio_thread, AudioDecoderEntry, "AvPlayerAudioDecoder")) ||
|
||||
!CreateWorker(demux_thread, DemuxEntry, "AvPlayerDemuxer")) {
|
||||
lock.unlock();
|
||||
StopWorkers();
|
||||
return false;
|
||||
}
|
||||
state = State::Playing;
|
||||
return true;
|
||||
}
|
||||
void NotifyWorkers() {
|
||||
@@ -1195,8 +1220,11 @@ private:
|
||||
audio_buffers.Notify();
|
||||
}
|
||||
void StopWorkers() {
|
||||
worker_stop = true;
|
||||
interrupt_io = true;
|
||||
{
|
||||
std::lock_guard lock(mutex);
|
||||
worker_stop = true;
|
||||
interrupt_io = true;
|
||||
}
|
||||
NotifyWorkers();
|
||||
if (demux_thread != nullptr) {
|
||||
LibKernel::PthreadJoin(demux_thread, nullptr);
|
||||
@@ -1212,9 +1240,12 @@ private:
|
||||
}
|
||||
}
|
||||
void FailPipeline() {
|
||||
pipeline_failed = true;
|
||||
worker_stop = true;
|
||||
interrupt_io = true;
|
||||
{
|
||||
std::lock_guard lock(mutex);
|
||||
pipeline_failed = true;
|
||||
worker_stop = true;
|
||||
interrupt_io = true;
|
||||
}
|
||||
NotifyWorkers();
|
||||
}
|
||||
void Demux() {
|
||||
@@ -1381,15 +1412,8 @@ private:
|
||||
return use_vdec2 ? static_cast<uint32_t>(s->codecpar->height)
|
||||
: align_up(static_cast<uint32_t>(s->codecpar->height), 16);
|
||||
}
|
||||
float Aspect(AVStream* s) const {
|
||||
if (s->codecpar->height == 0) {
|
||||
return 0.0f;
|
||||
}
|
||||
double r = static_cast<double>(s->codecpar->width) / s->codecpar->height;
|
||||
if (s->sample_aspect_ratio.num > 0 && s->sample_aspect_ratio.den > 0) {
|
||||
r *= av_q2d(s->sample_aspect_ratio);
|
||||
}
|
||||
return static_cast<float>(r);
|
||||
float Aspect(AVRational ratio) const {
|
||||
return ratio.num > 0 && ratio.den > 0 ? static_cast<float>(av_q2d(ratio)) : 0.0f;
|
||||
}
|
||||
uint64_t Duration(AVStream* s) const {
|
||||
return s->duration != AV_NOPTS_VALUE
|
||||
@@ -1402,14 +1426,14 @@ private:
|
||||
std::memset(v, 0, sizeof(*v));
|
||||
v->width = Width(s);
|
||||
v->height = Height(s);
|
||||
v->aspect_ratio = Aspect(s);
|
||||
v->aspect_ratio = Aspect(s->sample_aspect_ratio);
|
||||
lang(v->language_code, s->metadata);
|
||||
}
|
||||
void FillVideoEx(AVStream* s, AvPlayerVideoEx* v) const {
|
||||
void FillVideoEx(AVStream* s, AvPlayerVideoEx* v, AVRational aspect) const {
|
||||
std::memset(v, 0, sizeof(*v));
|
||||
v->width = Width(s);
|
||||
v->height = Height(s);
|
||||
v->aspect_ratio = Aspect(s);
|
||||
v->aspect_ratio = Aspect(aspect);
|
||||
v->pitch = VideoPitch(s);
|
||||
v->luma_bit_depth = 8;
|
||||
v->chroma_bit_depth = 8;
|
||||
@@ -1445,7 +1469,7 @@ private:
|
||||
FillVideo(s, &d->video);
|
||||
}
|
||||
if (ex) {
|
||||
FillVideoEx(s, &ex->video);
|
||||
FillVideoEx(s, &ex->video, s->sample_aspect_ratio);
|
||||
}
|
||||
break;
|
||||
case AVMEDIA_TYPE_AUDIO:
|
||||
@@ -1530,7 +1554,7 @@ private:
|
||||
info->time_stamp = to_ms(
|
||||
src->best_effort_timestamp != AV_NOPTS_VALUE ? src->best_effort_timestamp : src->pts,
|
||||
s->time_base);
|
||||
FillVideoEx(s, &info->details.video);
|
||||
FillVideoEx(s, &info->details.video, src->sample_aspect_ratio);
|
||||
info->details.video.crop_left_offset = static_cast<uint32_t>(src->crop_left);
|
||||
info->details.video.crop_right_offset =
|
||||
static_cast<uint32_t>(src->crop_right + (pitch - src->width));
|
||||
@@ -1607,6 +1631,7 @@ private:
|
||||
}
|
||||
AvPlayerMemAllocator mem;
|
||||
AvPlayerFileReplacement file;
|
||||
AvPlayerEventReplacement event;
|
||||
int max_video_buffers = 2;
|
||||
bool use_vdec2 = false;
|
||||
AvPlayerSourceType source_type = AvPlayerSourceUnknown;
|
||||
@@ -1642,7 +1667,7 @@ private:
|
||||
std::atomic_bool audio_done {true};
|
||||
std::mutex lifecycle_mutex;
|
||||
mutable std::mutex mutex;
|
||||
bool stopped = true;
|
||||
State state = State::Ready;
|
||||
bool paused = false;
|
||||
bool seek_video_frame_pending = false;
|
||||
std::atomic_bool loop {false};
|
||||
@@ -1664,6 +1689,7 @@ struct AvPlayerInternal {
|
||||
AvPlayerPostInitData post_init {};
|
||||
bool auto_start = false;
|
||||
int32_t video_buffers = 2;
|
||||
uint32_t sync_mode = 0;
|
||||
uint32_t start_bandwidth = 0;
|
||||
uint32_t minimum_bandwidth = 0;
|
||||
uint32_t maximum_bandwidth = 0;
|
||||
@@ -1673,19 +1699,13 @@ struct AvPlayerInternal {
|
||||
static bool valid_allocators(const AvPlayerMemAllocator& m) {
|
||||
return m.allocate && m.deallocate && m.allocate_texture && m.deallocate_texture;
|
||||
}
|
||||
static void emit_event(AvPlayerInternal* h, int32_t id, void* data = nullptr) {
|
||||
if (h != nullptr && h->event.event_callback != nullptr) {
|
||||
LOGF("\t event_id = %d\n", id);
|
||||
h->event.event_callback(h->event.object_pointer, id, 0, data);
|
||||
}
|
||||
}
|
||||
static void pump_warnings(AvPlayerInternal* h) {
|
||||
if (h == nullptr || h->source == nullptr) {
|
||||
return;
|
||||
}
|
||||
while (auto w = h->source->TakeWarning()) {
|
||||
int32_t warning = *w;
|
||||
emit_event(h, AVPLAYER_EVENT_WARNING_ID, &warning);
|
||||
emit_event(h->event, AVPLAYER_EVENT_WARNING_ID, &warning);
|
||||
}
|
||||
}
|
||||
static AvPlayerInternal* create_player(const AvPlayerMemAllocator& mem,
|
||||
@@ -1713,17 +1733,18 @@ static int add_source(AvPlayerInternal* h, const std::string& filename, AvPlayer
|
||||
}
|
||||
bool vdec2 =
|
||||
h->post_init.video_decoder_init.decoder_type.video_type == AvPlayerVideoDecoderSoftware2;
|
||||
auto s = std::make_unique<Source>(h->mem, h->file, h->video_buffers, vdec2,
|
||||
auto s = std::make_unique<Source>(h->mem, h->file, h->event, h->video_buffers, vdec2,
|
||||
h->post_init.demux_video_buffer_size);
|
||||
if (auto rc = s->Init(filename, type); rc < 0) {
|
||||
return rc;
|
||||
}
|
||||
s->SetSync(h->sync_mode);
|
||||
h->source = std::move(s);
|
||||
emit_event(h, AVPLAYER_EVENT_STATE_READY);
|
||||
emit_event(h->event, AVPLAYER_EVENT_STATE_READY);
|
||||
if (h->auto_start) {
|
||||
auto rc = h->source->Start();
|
||||
if (rc == 0) {
|
||||
emit_event(h, AVPLAYER_EVENT_STATE_PLAY);
|
||||
emit_event(h->event, AVPLAYER_EVENT_STATE_PLAY);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
@@ -1872,7 +1893,7 @@ int KYTY_SYSV_ABI AvPlayerStart(AvPlayerInternal* h) {
|
||||
}
|
||||
auto rc = h->source->Start();
|
||||
if (rc == 0) {
|
||||
emit_event(h, AVPLAYER_EVENT_STATE_PLAY);
|
||||
emit_event(h->event, AVPLAYER_EVENT_STATE_PLAY);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
@@ -1887,7 +1908,7 @@ int KYTY_SYSV_ABI AvPlayerStartEx(AvPlayerInternal* h, const void* start_info_ex
|
||||
: static_cast<const AvPlayerStartInfoEx*>(start_info_ex)->start_time_milliseconds;
|
||||
auto rc = h->source->Start(ms);
|
||||
if (rc == 0) {
|
||||
emit_event(h, AVPLAYER_EVENT_STATE_PLAY);
|
||||
emit_event(h->event, AVPLAYER_EVENT_STATE_PLAY);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
@@ -1896,18 +1917,18 @@ int KYTY_SYSV_ABI AvPlayerStop(AvPlayerInternal* h) {
|
||||
if (h == nullptr || h->source == nullptr) {
|
||||
return AVPLAYER_ERROR_INVALID_PARAMS;
|
||||
}
|
||||
auto rc = h->source->Stop();
|
||||
emit_event(h, AVPLAYER_EVENT_STATE_STOP);
|
||||
return rc;
|
||||
return h->source->Stop();
|
||||
}
|
||||
int KYTY_SYSV_ABI AvPlayerPause(AvPlayerInternal* h) {
|
||||
PRINT_NAME();
|
||||
if (h == nullptr || h->source == nullptr) {
|
||||
return AVPLAYER_ERROR_INVALID_PARAMS;
|
||||
}
|
||||
h->source->Pause();
|
||||
emit_event(h, AVPLAYER_EVENT_STATE_PAUSE);
|
||||
return 0;
|
||||
auto rc = h->source->Pause();
|
||||
if (rc == 0) {
|
||||
emit_event(h->event, AVPLAYER_EVENT_STATE_PAUSE);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
int KYTY_SYSV_ABI AvPlayerResume(AvPlayerInternal* h) {
|
||||
PRINT_NAME();
|
||||
@@ -1915,7 +1936,7 @@ int KYTY_SYSV_ABI AvPlayerResume(AvPlayerInternal* h) {
|
||||
return AVPLAYER_ERROR_INVALID_PARAMS;
|
||||
}
|
||||
h->source->Resume();
|
||||
emit_event(h, AVPLAYER_EVENT_STATE_PLAY);
|
||||
emit_event(h->event, AVPLAYER_EVENT_STATE_PLAY);
|
||||
return 0;
|
||||
}
|
||||
int KYTY_SYSV_ABI AvPlayerSetLooping(AvPlayerInternal* h, Bool loop) {
|
||||
@@ -1941,13 +1962,16 @@ int KYTY_SYSV_ABI AvPlayerSetTrickSpeed(AvPlayerInternal* h, int32_t trick_speed
|
||||
}
|
||||
int KYTY_SYSV_ABI AvPlayerSetAvSyncMode(AvPlayerInternal* h, uint32_t sync_mode) {
|
||||
PRINT_NAME();
|
||||
if (h == nullptr || h->source == nullptr) {
|
||||
if (h == nullptr) {
|
||||
return AVPLAYER_ERROR_INVALID_PARAMS;
|
||||
}
|
||||
if (sync_mode > 1) {
|
||||
return AVPLAYER_ERROR_NOT_SUPPORTED;
|
||||
}
|
||||
h->source->SetSync(sync_mode);
|
||||
h->sync_mode = sync_mode;
|
||||
if (h->source != nullptr) {
|
||||
h->source->SetSync(sync_mode);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
int KYTY_SYSV_ABI AvPlayerSetAvailableBandwidth(AvPlayerInternal* h, uint32_t start_bandwidth,
|
||||
@@ -2003,7 +2027,7 @@ Bool KYTY_SYSV_ABI AvPlayerGetAudioData(AvPlayerInternal* h, AvPlayerFrameInfo*
|
||||
}
|
||||
Bool KYTY_SYSV_ABI AvPlayerIsActive(AvPlayerInternal* h) {
|
||||
PRINT_NAME();
|
||||
return h != nullptr && h->source != nullptr && h->source->Active() ? 1 : 0;
|
||||
return h != nullptr && (h->source == nullptr || h->source->Active());
|
||||
}
|
||||
uint64_t KYTY_SYSV_ABI AvPlayerCurrentTime(AvPlayerInternal* h) {
|
||||
PRINT_NAME();
|
||||
@@ -2017,7 +2041,7 @@ int KYTY_SYSV_ABI AvPlayerJumpToTime(AvPlayerInternal* h, uint64_t time_ms) {
|
||||
auto rc = h->source->Jump(time_ms);
|
||||
if (rc == 0) {
|
||||
int32_t w = AVPLAYER_WARNING_JUMP_COMPLETE;
|
||||
emit_event(h, AVPLAYER_EVENT_WARNING_ID, &w);
|
||||
emit_event(h->event, AVPLAYER_EVENT_WARNING_ID, &w);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
+32
-18
@@ -1,6 +1,6 @@
|
||||
#include "libs/controller.h"
|
||||
|
||||
#include "SDL.h"
|
||||
#include <SDL3/SDL.h>
|
||||
#include "common/assert.h"
|
||||
#include "common/common.h"
|
||||
#include "common/emulatorConfig.h"
|
||||
@@ -293,11 +293,11 @@ void GameController::Connect(int id) {
|
||||
m_connected_ids.push_back(id);
|
||||
|
||||
if (id != HOST_INPUT_CONTROLLER_ID) {
|
||||
if (auto* pad = SDL_GameControllerFromInstanceID(static_cast<SDL_JoystickID>(id));
|
||||
if (auto* pad = SDL_GetGamepadFromID(static_cast<SDL_JoystickID>(id));
|
||||
pad != nullptr) {
|
||||
for (auto sensor: {SDL_SENSOR_ACCEL, SDL_SENSOR_GYRO}) {
|
||||
if (SDL_GameControllerHasSensor(pad, sensor) &&
|
||||
SDL_GameControllerSetSensorEnabled(pad, sensor, SDL_TRUE) != 0) {
|
||||
if (SDL_GamepadHasSensor(pad, sensor) &&
|
||||
!SDL_SetGamepadSensorEnabled(pad, sensor, true)) {
|
||||
LOGF("\t enabling controller sensor failed: %s\n", SDL_GetError());
|
||||
}
|
||||
}
|
||||
@@ -519,22 +519,22 @@ void GameController::ResetInputState() {
|
||||
void GameController::ReleaseHostPads() {
|
||||
Common::LockGuard lock(m_mutex);
|
||||
|
||||
std::vector<SDL_GameController*> pads;
|
||||
std::vector<SDL_Gamepad*> pads;
|
||||
for (const auto id: m_connected_ids) {
|
||||
if (id == HOST_INPUT_CONTROLLER_ID) {
|
||||
continue;
|
||||
}
|
||||
if (auto* pad = SDL_GameControllerFromInstanceID(static_cast<SDL_JoystickID>(id));
|
||||
if (auto* pad = SDL_GetGamepadFromID(static_cast<SDL_JoystickID>(id));
|
||||
pad != nullptr) {
|
||||
if (SDL_GameControllerGetType(pad) == SDL_CONTROLLER_TYPE_PS5) {
|
||||
if (SDL_GetGamepadType(pad) == SDL_GAMEPAD_TYPE_PS5) {
|
||||
DualSenseEffects effect {};
|
||||
effect.enable_bits = 0x0c;
|
||||
effect.right_trigger[0] = 0x05;
|
||||
effect.left_trigger[0] = 0x05;
|
||||
(void)SDL_GameControllerSendEffect(pad, &effect, sizeof(effect));
|
||||
(void)SDL_SendGamepadEffect(pad, &effect, sizeof(effect));
|
||||
}
|
||||
(void)SDL_GameControllerRumble(pad, 0, 0, 0);
|
||||
(void)SDL_GameControllerSetLED(pad, 0, 0, 0);
|
||||
(void)SDL_RumbleGamepad(pad, 0, 0, 0);
|
||||
(void)SDL_SetGamepadLED(pad, 0, 0, 0);
|
||||
pads.push_back(pad);
|
||||
}
|
||||
}
|
||||
@@ -542,7 +542,7 @@ void GameController::ReleaseHostPads() {
|
||||
if (!pads.empty()) {
|
||||
SDL_Delay(RELEASE_FLUSH_MS);
|
||||
for (auto* pad: pads) {
|
||||
SDL_GameControllerClose(pad);
|
||||
SDL_CloseGamepad(pad);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -556,23 +556,23 @@ void GameController::SetVibration(uint8_t large_motor, uint8_t small_motor) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto* pad = SDL_GameControllerFromInstanceID(static_cast<SDL_JoystickID>(m_active_id));
|
||||
auto* pad = SDL_GetGamepadFromID(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) {
|
||||
if (!SDL_RumbleGamepad(pad, large, small, RUMBLE_DURATION_MS)) {
|
||||
LOGF("\t rumble failed: %s\n", SDL_GetError());
|
||||
}
|
||||
}
|
||||
|
||||
void GameController::SetLightBar(uint8_t r, uint8_t g, uint8_t b) {
|
||||
Common::LockGuard lock(m_mutex);
|
||||
if (auto* pad = SDL_GameControllerFromInstanceID(static_cast<SDL_JoystickID>(m_active_id));
|
||||
if (auto* pad = SDL_GetGamepadFromID(static_cast<SDL_JoystickID>(m_active_id));
|
||||
pad != nullptr) {
|
||||
(void)SDL_GameControllerSetLED(pad, r, g, b);
|
||||
(void)SDL_SetGamepadLED(pad, r, g, b);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -599,9 +599,9 @@ bool GameController::SetTriggerEffect(const PadTriggerEffectParam& param) {
|
||||
}
|
||||
|
||||
Common::LockGuard lock(m_mutex);
|
||||
auto* pad = SDL_GameControllerFromInstanceID(static_cast<SDL_JoystickID>(m_active_id));
|
||||
if (pad != nullptr && SDL_GameControllerGetType(pad) == SDL_CONTROLLER_TYPE_PS5) {
|
||||
(void)SDL_GameControllerSendEffect(pad, &effect, sizeof(effect));
|
||||
auto* pad = SDL_GetGamepadFromID(static_cast<SDL_JoystickID>(m_active_id));
|
||||
if (pad != nullptr && SDL_GetGamepadType(pad) == SDL_GAMEPAD_TYPE_PS5) {
|
||||
(void)SDL_SendGamepadEffect(pad, &effect, sizeof(effect));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -812,6 +812,20 @@ int KYTY_SYSV_ABI PadGetControllerInformation(int handle, PadControllerInformati
|
||||
return OK;
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI PadIsRemoteController(int handle, bool* is_remote) {
|
||||
PRINT_NAME();
|
||||
|
||||
if (handle != 1) {
|
||||
return PAD_ERROR_INVALID_HANDLE;
|
||||
}
|
||||
if (is_remote == nullptr) {
|
||||
return PAD_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
*is_remote = false;
|
||||
return OK;
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI PadReadState(int handle, PadData* data) {
|
||||
PRINT_NAME();
|
||||
|
||||
|
||||
@@ -81,6 +81,7 @@ int KYTY_SYSV_ABI PadSetMotionSensorState(int handle, bool enable);
|
||||
int KYTY_SYSV_ABI PadSetAngularVelocityDeadbandState(int handle, bool enable);
|
||||
int KYTY_SYSV_ABI PadResetOrientation(int handle);
|
||||
int KYTY_SYSV_ABI PadGetControllerInformation(int handle, PadControllerInformation* info);
|
||||
int KYTY_SYSV_ABI PadIsRemoteController(int handle, bool* is_remote);
|
||||
int KYTY_SYSV_ABI PadReadState(int handle, PadData* data);
|
||||
int KYTY_SYSV_ABI PadRead(int handle, PadData* data, int num);
|
||||
int KYTY_SYSV_ABI PadSetVibration(int handle, const PadVibrationParam* param);
|
||||
|
||||
@@ -1052,7 +1052,7 @@ static int32_t KYTY_SYSV_ABI AudiodecDecode(int32_t handle, AudiodecCtrl* ctrl)
|
||||
ctrl->pPcmItem->uiPcmSize = static_cast<uint32_t>(result.output_written);
|
||||
audiodec_fill_info(ctrl, decoder);
|
||||
auto* info = static_cast<AudiodecM4aacInfo*>(ctrl->pBsiInfo);
|
||||
info->iResult = (result.result & AJM_RESULT_CODEC_ERROR) != 0
|
||||
info->iResult = (result.result & (AJM_RESULT_CODEC_ERROR | AJM_RESULT_TOO_MANY_CHANNELS)) != 0
|
||||
? AUDIODEC_M4AAC_RESULT_DECODE_ERROR
|
||||
: (result.result & AJM_RESULT_PARTIAL_INPUT) != 0
|
||||
? AUDIODEC_M4AAC_RESULT_INSUFFICIENT_DATA : 0;
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
#include "libs/libs.h"
|
||||
#include "loader/symbolDatabase.h"
|
||||
|
||||
#include <SDL_stdinc.h>
|
||||
#include <SDL3/SDL_stdinc.h>
|
||||
#include <array>
|
||||
#include <cstring>
|
||||
|
||||
|
||||
@@ -3021,7 +3021,9 @@ LIB_DEFINE(InitLibKernel_1_FS) {
|
||||
LIB_FUNC("Cg4srZ6TKbU", FileSystem::KernelRead);
|
||||
LIB_FUNC("4wSze92BhLI", FileSystem::KernelWrite);
|
||||
LIB_FUNC("+r3rMFwItV4", FileSystem::KernelPread);
|
||||
LIB_FUNC("yTj62I7kw4s", FileSystem::KernelPreadv);
|
||||
LIB_FUNC("nKWi-N2HBV4", FileSystem::KernelPwrite);
|
||||
LIB_FUNC("mBd4AfLP+u8", FileSystem::KernelPwritev);
|
||||
LIB_FUNC("eV9wAD2riIA", FileSystem::KernelStat);
|
||||
LIB_FUNC("kBwCPsYX-m4", FileSystem::KernelFstat);
|
||||
LIB_FUNC("AUXVxWeJU-A", FileSystem::KernelUnlink);
|
||||
|
||||
+85
-30
@@ -8,22 +8,13 @@
|
||||
#include "loader/symbolDatabase.h"
|
||||
|
||||
#include <cctype>
|
||||
#include <cstddef>
|
||||
#include <cstring>
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
|
||||
#define NET_CALL(func) \
|
||||
[&]() { \
|
||||
auto result = func; \
|
||||
if (result < 0) { \
|
||||
*GetNetErrorAddr() = result; \
|
||||
} \
|
||||
return result; \
|
||||
}()
|
||||
|
||||
namespace Libs {
|
||||
|
||||
namespace Network::Net {
|
||||
@@ -43,8 +34,15 @@ KYTY_SYSV_ABI int* GetNetErrorAddr() {
|
||||
return &g_net_errno;
|
||||
}
|
||||
|
||||
static int FinishNetCall(int result) {
|
||||
if (result < 0) {
|
||||
g_net_errno = result - Network::NET_ERROR_EPERM + 1;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static int KYTY_SYSV_ABI NetInit() {
|
||||
return NET_CALL(Net::NetInit());
|
||||
return FinishNetCall(Net::NetInit());
|
||||
}
|
||||
|
||||
static int PosixToNetError(int error) {
|
||||
@@ -66,13 +64,7 @@ static int PosixToNetError(int error) {
|
||||
}
|
||||
|
||||
static int FinishSocketCall(int result) {
|
||||
if (result >= 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
const int net_error = PosixToNetError(*Posix::GetErrorAddr());
|
||||
*GetNetErrorAddr() = net_error;
|
||||
return net_error;
|
||||
return FinishNetCall(result < 0 ? PosixToNetError(*Posix::GetErrorAddr()) : result);
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI NetSocket(const char* name, int family, int type, int protocol) {
|
||||
@@ -102,48 +94,48 @@ int KYTY_SYSV_ABI NetGetsockname(int s, void* addr, uint32_t* addrlen) {
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI NetPoolCreate(const char* name, int size, int flags) {
|
||||
return NET_CALL(Net::NetPoolCreate(name, size, flags));
|
||||
return FinishNetCall(Net::NetPoolCreate(name, size, flags));
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI NetPoolDestroy(int memid) {
|
||||
return NET_CALL(Net::NetPoolDestroy(memid));
|
||||
return FinishNetCall(Net::NetPoolDestroy(memid));
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI NetResolverCreate(const char* name, int memid, int flags) {
|
||||
return NET_CALL(Net::NetResolverCreate(name, memid, flags));
|
||||
return FinishNetCall(Net::NetResolverCreate(name, memid, flags));
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI NetResolverDestroy(int rid) {
|
||||
return NET_CALL(Net::NetResolverDestroy(rid));
|
||||
return FinishNetCall(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));
|
||||
return FinishNetCall(Net::NetResolverStartNtoa(rid, hostname, addr, timeout, retry, flags));
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI NetInetPton(int af, const char* src, void* dst) {
|
||||
return NET_CALL(Net::NetInetPton(af, src, dst));
|
||||
return FinishNetCall(Net::NetInetPton(af, src, dst));
|
||||
}
|
||||
|
||||
const char* KYTY_SYSV_ABI NetInetNtop(int af, const void* src, char* dst, uint32_t size) {
|
||||
const char* result = Net::NetInetNtop(af, src, dst, size);
|
||||
if (result == nullptr) {
|
||||
*GetNetErrorAddr() = PosixToNetError(*Posix::GetErrorAddr());
|
||||
FinishNetCall(PosixToNetError(*Posix::GetErrorAddr()));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI NetEtherNtostr(const Net::NetEtherAddr* n, char* str, size_t len) {
|
||||
return NET_CALL(Net::NetEtherNtostr(n, str, len));
|
||||
return FinishNetCall(Net::NetEtherNtostr(n, str, len));
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI NetGetMacAddress(Net::NetEtherAddr* addr, int flags) {
|
||||
return NET_CALL(Net::NetGetMacAddress(addr, flags));
|
||||
return FinishNetCall(Net::NetGetMacAddress(addr, flags));
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI NetGetSockInfo(int s, void* info, int n, int flags) {
|
||||
return NET_CALL(Net::NetGetSockInfo(s, info, n, flags));
|
||||
return FinishNetCall(Net::NetGetSockInfo(s, info, n, flags));
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI NetEpollCreate(const char* name, int flags) {
|
||||
@@ -163,7 +155,7 @@ int KYTY_SYSV_ABI NetEpollDestroy(int eid) {
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI NetSocketClose(int s) {
|
||||
return NET_CALL(Net::SocketClose(s));
|
||||
return FinishNetCall(Net::SocketClose(s));
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI NetSetsockopt(int s, int level, int optname, const void* optval,
|
||||
@@ -1465,7 +1457,7 @@ LIB_DEFINE(InitNet_1_NpManager) {
|
||||
LIB_FUNC("Ec63y59l9tw", NpManager::NpSetNpTitleId);
|
||||
LIB_FUNC("A2CQ3kgSopQ", NpManager::NpSetContentRestriction);
|
||||
LIB_FUNC("VfRSmPmj8Q8", NpManager::NpRegisterStateCallback);
|
||||
LIB_FUNC("qQJfO8HAiaY", NpManager::NpRegisterStateCallback);
|
||||
LIB_FUNC("qQJfO8HAiaY", NpManager::NpRegisterStateCallbackA);
|
||||
LIB_FUNC("M3wFXbYQtAA", NpManager::NpUnregisterStateCallback);
|
||||
LIB_FUNC("uFJpaKNBAj4", NpManager::NpRegisterGamePresenceCallback);
|
||||
LIB_FUNC("GImICnh+boA", NpManager::NpRegisterPlusEventCallback);
|
||||
@@ -1536,6 +1528,10 @@ struct NpEntitlementAccessAddcontEntitlementInfo {
|
||||
uint32_t download_status;
|
||||
};
|
||||
|
||||
struct NpEntitlementAccessEntitlementKey {
|
||||
uint8_t data[16];
|
||||
};
|
||||
|
||||
static constexpr NpEntitlementAccessAddcontEntitlementInfo NP_ENTITLEMENT_ACCESS_ADDON_LIST[] = {
|
||||
{{{"85y-je"}, {}}, 3, 4}, // GTA V hash 0xf4315381
|
||||
{{{"5d5c48"}, {}}, 3, 4}, // GTA V hash 0x961c34b0
|
||||
@@ -1622,12 +1618,31 @@ static int KYTY_SYSV_ABI NpEntitlementAccessGetAddcontEntitlementInfo(
|
||||
return NP_ENTITLEMENT_ACCESS_ERROR_NO_ENTITLEMENT;
|
||||
}
|
||||
|
||||
static int KYTY_SYSV_ABI NpEntitlementAccessGetEntitlementKey(
|
||||
uint32_t service_label, const NpUnifiedEntitlementLabel* entitlement_label,
|
||||
NpEntitlementAccessEntitlementKey* key) {
|
||||
PRINT_NAME();
|
||||
|
||||
LOGF("\t service_label = %" PRIu32 "\n"
|
||||
"\t entitlement_label = 0x%016" PRIx64 "\n"
|
||||
"\t key = 0x%016" PRIx64 "\n",
|
||||
service_label, reinterpret_cast<uint64_t>(entitlement_label),
|
||||
reinterpret_cast<uint64_t>(key));
|
||||
|
||||
if (entitlement_label == nullptr || key == nullptr) {
|
||||
return NP_ENTITLEMENT_ACCESS_ERROR_PARAMETER;
|
||||
}
|
||||
|
||||
return NP_ENTITLEMENT_ACCESS_ERROR_NO_ENTITLEMENT;
|
||||
}
|
||||
|
||||
LIB_DEFINE(InitNet_1_NpEntitlementAccess) {
|
||||
LIB_FUNC("jO8DM8oyego", LibNpEntitlementAccess::NpEntitlementAccessInitialize);
|
||||
LIB_FUNC("lPDO62PpJIA", LibNpEntitlementAccess::NpEntitlementAccessGetSkuFlag);
|
||||
LIB_FUNC("TFyU+KFBv54",
|
||||
LibNpEntitlementAccess::NpEntitlementAccessGetAddcontEntitlementInfoList);
|
||||
LIB_FUNC("xddD23+8TfQ", LibNpEntitlementAccess::NpEntitlementAccessGetAddcontEntitlementInfo);
|
||||
LIB_FUNC("5LiMEPuW0DQ", LibNpEntitlementAccess::NpEntitlementAccessGetEntitlementKey);
|
||||
}
|
||||
|
||||
} // namespace LibNpEntitlementAccess
|
||||
@@ -3730,6 +3745,21 @@ namespace LibGameLiveStreaming {
|
||||
|
||||
LIB_VERSION("GameLiveStreaming", 1, "GameLiveStreaming", 1, 1);
|
||||
|
||||
constexpr int GAME_LIVE_STREAMING_ERROR_INVALID_PARAM = -2136997886;
|
||||
|
||||
struct GameLiveStreamingStatus2 {
|
||||
int32_t user_id;
|
||||
bool is_on_air;
|
||||
uint32_t spectator_count;
|
||||
uint32_t text_message_count;
|
||||
uint32_t command_message_count;
|
||||
uint8_t reserved[52];
|
||||
};
|
||||
|
||||
static_assert(sizeof(GameLiveStreamingStatus2) == 72);
|
||||
static_assert(offsetof(GameLiveStreamingStatus2, is_on_air) == 4);
|
||||
static_assert(offsetof(GameLiveStreamingStatus2, spectator_count) == 8);
|
||||
|
||||
static int KYTY_SYSV_ABI GameLiveStreamingInitialize(size_t heap_size) {
|
||||
PRINT_NAME();
|
||||
|
||||
@@ -3744,9 +3774,34 @@ static int KYTY_SYSV_ABI GameLiveStreamingTerminate() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int KYTY_SYSV_ABI GameLiveStreamingGetCurrentStatus2(GameLiveStreamingStatus2* status) {
|
||||
PRINT_NAME();
|
||||
|
||||
if (status == nullptr) {
|
||||
return GAME_LIVE_STREAMING_ERROR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
std::memset(status, 0, sizeof(*status));
|
||||
status->user_id = -1;
|
||||
return OK;
|
||||
}
|
||||
|
||||
static int KYTY_SYSV_ABI GameLiveStreamingGetSocialFeedbackMessagesCount(int type, uint32_t* count) {
|
||||
PRINT_NAME();
|
||||
|
||||
if (type < 1 || type > 3 || count == nullptr) {
|
||||
return GAME_LIVE_STREAMING_ERROR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
*count = 0;
|
||||
return OK;
|
||||
}
|
||||
|
||||
LIB_DEFINE(InitPlatform_1_GameLiveStreaming) {
|
||||
LIB_FUNC("kvYEw2lBndk", LibGameLiveStreaming::GameLiveStreamingInitialize);
|
||||
LIB_FUNC("9yK6Fk8mKOQ", LibGameLiveStreaming::GameLiveStreamingTerminate);
|
||||
LIB_FUNC("lK8dLBNp9OE", LibGameLiveStreaming::GameLiveStreamingGetCurrentStatus2);
|
||||
LIB_FUNC("yeQKjHETi40", LibGameLiveStreaming::GameLiveStreamingGetSocialFeedbackMessagesCount);
|
||||
}
|
||||
|
||||
} // namespace LibGameLiveStreaming
|
||||
|
||||
Loaded 100 of 117 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user