mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-29 08:41:20 +00:00
Compare commits
44
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b7a1fac898 | ||
|
|
bc193f5272 | ||
|
|
17100ec968 | ||
|
|
8ba2caf58a | ||
|
|
48b52d31b5 | ||
|
|
840b9f5753 | ||
|
|
05057c9441 | ||
|
|
af4edc2865 | ||
|
|
9d4da48320 | ||
|
|
2650478d92 | ||
|
|
18a5f04d05 | ||
|
|
da9b38af2b | ||
|
|
c045f08904 | ||
|
|
5bf9218b32 | ||
|
|
f9fd400dbb | ||
|
|
650dc1e0af | ||
|
|
59a1760427 | ||
|
|
73615c31ff | ||
|
|
6799ecbc5b | ||
|
|
35c4dca2ce | ||
|
|
6f24b031f5 | ||
|
|
0c844e4bc2 | ||
|
|
e57c37fa9c | ||
|
|
6d6666819a | ||
|
|
0fbeac6d3f | ||
|
|
5e4f6afc30 | ||
|
|
37501b5a78 | ||
|
|
b43bf9f7fb | ||
|
|
8e42e99788 | ||
|
|
539c0f7bf8 | ||
|
|
62a0222e24 | ||
|
|
6f241bef49 | ||
|
|
21d7e8e1f0 | ||
|
|
49db3ea54c | ||
|
|
4b0de1b672 | ||
|
|
8e61798bfd | ||
|
|
6ee1bf9db0 | ||
|
|
0471c9991c | ||
|
|
b10bd53dd3 | ||
|
|
4cf456fb77 | ||
|
|
22ff4693c5 | ||
|
|
a107d1e333 | ||
|
|
05f92b6422 | ||
|
|
a892097ff0 |
No files matched your search
@@ -113,14 +113,15 @@ jobs:
|
||||
shell: cmd
|
||||
run: |
|
||||
cmake --build _Build/windows --target launcher ^
|
||||
audio_out2_port_tests ime_dialog_tests lru_cache_tests virtual_memory_allocation_tests ^
|
||||
audio_out2_port_tests pad_haptics_tests ime_dialog_tests lru_cache_tests virtual_memory_allocation_tests ^
|
||||
shader_recompiler_compute_tests ^
|
||||
resource_tracking_tests scalar_provenance_tests ^
|
||||
resource_materialization_tests shader_vertex_metadata_tests --parallel
|
||||
resource_materialization_tests shader_vertex_metadata_tests archive_file_tests avplayer_file_tests --parallel
|
||||
|
||||
- name: Test
|
||||
shell: cmd
|
||||
run: |
|
||||
ctest --test-dir _Build/windows --output-on-failure -R "^(audio_out2_port|ime_dialog|lru_cache|resource_tracking|scalar_provenance|resource_materialization|shader_vertex_metadata|virtual_memory_allocation)$"
|
||||
ctest --test-dir _Build/windows --output-on-failure -R "^(audio_out2_port|pad_haptics|ime_dialog|lru_cache|resource_tracking|scalar_provenance|resource_materialization|shader_vertex_metadata|archive_file|avplayer_file|virtual_memory_allocation)$"
|
||||
|
||||
- name: Install
|
||||
shell: cmd
|
||||
@@ -201,16 +202,17 @@ jobs:
|
||||
shell: bash
|
||||
run: |
|
||||
cmake --build _Build/macos \
|
||||
--target launcher audio_out2_port_tests ime_dialog_tests lru_cache_tests virtual_memory_allocation_tests \
|
||||
--target launcher audio_out2_port_tests pad_haptics_tests ime_dialog_tests lru_cache_tests virtual_memory_allocation_tests \
|
||||
memory_tracker_tests page_manager_tests shader_recompiler_compute_tests \
|
||||
resource_tracking_tests scalar_provenance_tests \
|
||||
resource_materialization_tests shader_vertex_metadata_tests \
|
||||
resource_materialization_tests shader_vertex_metadata_tests archive_file_tests avplayer_file_tests \
|
||||
--parallel
|
||||
|
||||
- name: Test
|
||||
shell: bash
|
||||
run: |
|
||||
ctest --test-dir _Build/macos --output-on-failure \
|
||||
-R '^(audio_out2_port|ime_dialog|lru_cache|resource_tracking|scalar_provenance|resource_materialization|shader_vertex_metadata|virtual_memory_allocation)$'
|
||||
-R '^(audio_out2_port|pad_haptics|ime_dialog|lru_cache|memory_tracker|page_manager|resource_tracking|scalar_provenance|resource_materialization|shader_vertex_metadata|archive_file|avplayer_file|virtual_memory_allocation)$'
|
||||
|
||||
- name: Install
|
||||
shell: bash
|
||||
@@ -384,16 +386,16 @@ jobs:
|
||||
run: |
|
||||
cmake --build _Build/linux \
|
||||
--target launcher page_manager_tests memory_tracker_tests \
|
||||
audio_out2_port_tests ime_dialog_tests lru_cache_tests virtual_memory_allocation_tests \
|
||||
audio_out2_port_tests pad_haptics_tests ime_dialog_tests lru_cache_tests virtual_memory_allocation_tests \
|
||||
resource_tracking_tests scalar_provenance_tests \
|
||||
resource_materialization_tests shader_vertex_metadata_tests \
|
||||
resource_materialization_tests shader_vertex_metadata_tests archive_file_tests avplayer_file_tests \
|
||||
--parallel
|
||||
|
||||
- name: Test
|
||||
shell: bash
|
||||
run: |
|
||||
ctest --test-dir _Build/linux --output-on-failure \
|
||||
-R '^(audio_out2_port|ime_dialog|lru_cache|memory_tracker|page_manager|resource_tracking|scalar_provenance|resource_materialization|shader_vertex_metadata|virtual_memory_allocation)$'
|
||||
-R '^(audio_out2_port|pad_haptics|ime_dialog|lru_cache|memory_tracker|page_manager|resource_tracking|scalar_provenance|resource_materialization|shader_vertex_metadata|archive_file|avplayer_file|virtual_memory_allocation)$'
|
||||
|
||||
- name: Install
|
||||
shell: bash
|
||||
|
||||
Vendored
+48
@@ -28,7 +28,54 @@ FetchContent_Declare(
|
||||
|
||||
FetchContent_MakeAvailable(xbyak zydis)
|
||||
|
||||
# ZArchive provides random-access reads for compressed game dumps. Build only
|
||||
# the reader/writer library and its zstd dependency; the command-line tool is
|
||||
# not part of KytyPS5's runtime distribution.
|
||||
set(ZSTD_BUILD_PROGRAMS OFF CACHE BOOL "" FORCE)
|
||||
set(ZSTD_BUILD_SHARED OFF CACHE BOOL "" FORCE)
|
||||
set(ZSTD_BUILD_STATIC ON CACHE BOOL "" FORCE)
|
||||
set(ZSTD_BUILD_TESTS OFF CACHE BOOL "" FORCE)
|
||||
set(ZSTD_LEGACY_SUPPORT OFF CACHE BOOL "" FORCE)
|
||||
set(ZSTD_MULTITHREAD_SUPPORT OFF CACHE BOOL "" FORCE)
|
||||
FetchContent_Declare(
|
||||
zstd
|
||||
GIT_REPOSITORY https://github.com/facebook/zstd.git
|
||||
GIT_TAG f8745da6ff1ad1e7bab384bd1f9d742439278e99
|
||||
GIT_SHALLOW FALSE
|
||||
SOURCE_SUBDIR build/cmake
|
||||
)
|
||||
FetchContent_Declare(
|
||||
zarchive_source
|
||||
GIT_REPOSITORY https://github.com/Exzap/ZArchive.git
|
||||
GIT_TAG d2c717730092c7bf8cbb033b12fd4001b7c4d932
|
||||
GIT_SHALLOW FALSE
|
||||
PATCH_COMMAND "${GIT_EXECUTABLE}" apply --ignore-whitespace
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/patches/zarchive-reader.patch"
|
||||
SOURCE_SUBDIR kyty-library-only
|
||||
)
|
||||
FetchContent_MakeAvailable(zstd zarchive_source)
|
||||
|
||||
add_library(zarchive STATIC
|
||||
"${zarchive_source_SOURCE_DIR}/src/zarchivereader.cpp"
|
||||
"${zarchive_source_SOURCE_DIR}/src/zarchivewriter.cpp"
|
||||
"${zarchive_source_SOURCE_DIR}/src/sha_256.c"
|
||||
)
|
||||
add_library(ZArchive::zarchive ALIAS zarchive)
|
||||
target_include_directories(zarchive SYSTEM PUBLIC "${zarchive_source_SOURCE_DIR}/include")
|
||||
target_include_directories(zarchive PRIVATE "${zstd_SOURCE_DIR}/lib")
|
||||
target_link_libraries(zarchive PRIVATE libzstd_static)
|
||||
target_compile_features(zarchive PRIVATE cxx_std_20)
|
||||
if(MSVC)
|
||||
target_compile_options(zarchive PRIVATE /W0)
|
||||
else()
|
||||
target_compile_options(zarchive PRIVATE -w)
|
||||
endif()
|
||||
|
||||
set(SDL_SHARED OFF CACHE BOOL "" FORCE)
|
||||
if(APPLE)
|
||||
# SDL's built-in iconv fallback does not support CP932.
|
||||
set(SDL_SYSTEM_ICONV ON CACHE BOOL "Use system iconv for CP932 conversion" FORCE)
|
||||
endif()
|
||||
add_subdirectory(SDL3)
|
||||
|
||||
add_library(Vulkan-Headers INTERFACE)
|
||||
@@ -120,6 +167,7 @@ endif()
|
||||
|
||||
set(TRACY_STATIC ON CACHE BOOL "" FORCE)
|
||||
set(TRACY_ENABLE ON CACHE BOOL "" FORCE)
|
||||
set(TRACY_NO_CRASH_HANDLER ON CACHE BOOL "" FORCE)
|
||||
set(TRACY_ON_DEMAND ON CACHE BOOL "" FORCE)
|
||||
set(TRACY_DELAYED_INIT ON CACHE BOOL "" FORCE)
|
||||
set(TRACY_MANUAL_LIFETIME ON CACHE BOOL "" FORCE)
|
||||
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
--- a/include/zarchive/zarchivecommon.h
|
||||
+++ b/include/zarchive/zarchivecommon.h
|
||||
@@ -256,7 +256,7 @@
|
||||
|
||||
bool IsWithinValidRange(uint64_t fileSize) const
|
||||
{
|
||||
- return (offset + size) <= fileSize;
|
||||
+ return offset <= fileSize && size <= fileSize - offset;
|
||||
}
|
||||
};
|
||||
|
||||
--- a/src/zarchivereader.cpp
|
||||
+++ b/src/zarchivereader.cpp
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
static bool _ifstream_readBytes(std::ifstream& file, uint64_t offset, void* buffer, uint32_t size)
|
||||
{
|
||||
+ file.clear();
|
||||
file.seekg(offset, std::ios_base::beg);
|
||||
file.read((char*)buffer, size);
|
||||
return file.gcount() == size;
|
||||
@@ -80,9 +81,27 @@
|
||||
// verify file tree
|
||||
if (fileTree[0].IsFile())
|
||||
return nullptr; // first entry must be root directory
|
||||
- auto rootName = GetName(nameTable, fileTree[0].GetNameOffset());
|
||||
- if (!rootName.empty())
|
||||
+ if (fileTree[0].GetNameOffset() != 0x7FFFFFFF)
|
||||
return nullptr; // root node must not have a name
|
||||
+ const uint64_t dataCapacity = (uint64_t)offsetRecords.size() * _ZARCHIVE::ENTRIES_PER_OFFSETRECORD * _ZARCHIVE::COMPRESSED_BLOCK_SIZE;
|
||||
+ for (size_t i = 0; i < fileTree.size(); ++i)
|
||||
+ {
|
||||
+ auto& entry = fileTree[i];
|
||||
+ if (i != 0)
|
||||
+ {
|
||||
+ auto name = GetName(nameTable, entry.GetNameOffset());
|
||||
+ if (name.empty() || name == "." || name == ".." || name.find_first_of("/\\") != std::string_view::npos || name.find('\0') != std::string_view::npos)
|
||||
+ return nullptr;
|
||||
+ }
|
||||
+ if (entry.IsFile())
|
||||
+ {
|
||||
+ if (entry.GetFileOffset() > dataCapacity || entry.GetFileSize() > dataCapacity - entry.GetFileOffset())
|
||||
+ return nullptr;
|
||||
+ }
|
||||
+ else if (entry.directoryRecord.nodeStartIndex > fileTree.size() ||
|
||||
+ entry.directoryRecord.count > fileTree.size() - entry.directoryRecord.nodeStartIndex)
|
||||
+ return nullptr;
|
||||
+ }
|
||||
// read meta data
|
||||
// todo
|
||||
|
||||
@@ -238,7 +257,7 @@
|
||||
uint32_t stepSize = std::min<uint32_t>(remainingBytes, _ZARCHIVE::COMPRESSED_BLOCK_SIZE - blockOffset);
|
||||
CacheBlock* block = GetCachedBlock(blockIdx);
|
||||
if (!block)
|
||||
- return 0;
|
||||
+ return bytesToRead - remainingBytes;
|
||||
std::memcpy(bufferU8, block->data + blockOffset, stepSize);
|
||||
rawReadOffset += stepSize;
|
||||
remainingBytes -= stepSize;
|
||||
@@ -325,14 +344,18 @@
|
||||
// determine offset and size of compressed block
|
||||
auto& record = m_offsetRecords[recordIndex];
|
||||
uint64_t offset = record.baseOffset;
|
||||
+ if (offset > m_compressedDataSize)
|
||||
+ return false;
|
||||
for (uint32_t i = 0; i < recordSubIndex; i++)
|
||||
{
|
||||
- offset += (uint64_t)record.size[i];
|
||||
- offset++;
|
||||
+ const uint64_t size = (uint64_t)record.size[i] + 1;
|
||||
+ if (size > m_compressedDataSize - offset)
|
||||
+ return false;
|
||||
+ offset += size;
|
||||
}
|
||||
uint32_t compressedSize = (uint32_t)record.size[recordSubIndex] + 1;
|
||||
// load file data
|
||||
- if ((offset + compressedSize) > m_compressedDataSize)
|
||||
+ if (compressedSize > m_compressedDataSize - offset)
|
||||
return false;
|
||||
offset += m_compressedDataOffset;
|
||||
if (compressedSize == _ZARCHIVE::COMPRESSED_BLOCK_SIZE)
|
||||
@@ -350,7 +373,7 @@
|
||||
// returns empty view on failure
|
||||
std::string_view ZArchiveReader::GetName(const std::vector<uint8_t>& nameTable, uint32_t nameOffset)
|
||||
{
|
||||
- if (nameOffset == 0x7FFFFFFF || nameOffset > nameTable.size())
|
||||
+ if (nameOffset == 0x7FFFFFFF || nameOffset >= nameTable.size())
|
||||
return "";
|
||||
// parse header
|
||||
uint16_t nameLength = nameTable[nameOffset] & 0x7F;
|
||||
@@ -359,7 +382,7 @@
|
||||
// extended 2-byte length
|
||||
if (nameOffset + 1 >= nameTable.size())
|
||||
return "";
|
||||
- nameLength |= ((uint16_t)nameTable[nameOffset] << 7);
|
||||
+ nameLength |= ((uint16_t)nameTable[nameOffset + 1] << 7);
|
||||
nameOffset += 2;
|
||||
}
|
||||
else
|
||||
Vendored
+1724
File diff suppressed because it is too large.
Load diff
+43
-1
@@ -1,4 +1,4 @@
|
||||
cmake_minimum_required(VERSION 3.16...4.3)
|
||||
cmake_minimum_required(VERSION 3.18...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")
|
||||
@@ -407,6 +407,16 @@ add_executable(page_manager_tests EXCLUDE_FROM_ALL
|
||||
target_link_libraries(page_manager_tests common)
|
||||
target_include_directories(page_manager_tests PRIVATE ${inc_headers})
|
||||
|
||||
add_executable(archive_file_tests EXCLUDE_FROM_ALL
|
||||
"${KYTY_TESTS_DIR}/ArchiveFileTests.cpp"
|
||||
)
|
||||
target_link_libraries(archive_file_tests common ZArchive::zarchive)
|
||||
target_include_directories(archive_file_tests PRIVATE ${inc_headers})
|
||||
|
||||
add_executable(avplayer_file_tests EXCLUDE_FROM_ALL "${KYTY_TESTS_DIR}/AvPlayerFileTests.cpp")
|
||||
target_link_libraries(avplayer_file_tests common FFmpeg::ffmpeg ZArchive::zarchive)
|
||||
target_include_directories(avplayer_file_tests PRIVATE ${inc_headers})
|
||||
|
||||
add_executable(bit_array_tests EXCLUDE_FROM_ALL
|
||||
"${KYTY_TESTS_DIR}/BitArrayTests.cpp"
|
||||
)
|
||||
@@ -490,6 +500,21 @@ add_executable(save_data_memory_tests EXCLUDE_FROM_ALL
|
||||
target_link_libraries(save_data_memory_tests common fmt::fmt)
|
||||
target_include_directories(save_data_memory_tests PRIVATE ${inc_headers})
|
||||
|
||||
add_executable(png_enc_tests EXCLUDE_FROM_ALL "${KYTY_TESTS_DIR}/PngEncTests.cpp")
|
||||
target_link_libraries(png_enc_tests common fmt::fmt)
|
||||
target_include_directories(png_enc_tests PRIVATE ${inc_headers})
|
||||
|
||||
add_executable(ces_tests EXCLUDE_FROM_ALL "${KYTY_TESTS_DIR}/CesTests.cpp")
|
||||
target_link_libraries(ces_tests common fmt::fmt SDL3::SDL3-static)
|
||||
target_include_directories(ces_tests PRIVATE ${inc_headers})
|
||||
|
||||
add_executable(save_data_dialog_tests EXCLUDE_FROM_ALL
|
||||
"${KYTY_TESTS_DIR}/SaveDataDialogTests.cpp"
|
||||
"${KYTY_SOURCE_DIR}/libs/dialog.cpp"
|
||||
)
|
||||
target_link_libraries(save_data_dialog_tests common fmt::fmt)
|
||||
target_include_directories(save_data_dialog_tests PRIVATE ${inc_headers})
|
||||
|
||||
add_kyty_full_emulator_test(http_uri_parse_tests
|
||||
"${KYTY_TESTS_DIR}/HttpUriParseTests.cpp")
|
||||
|
||||
@@ -503,6 +528,7 @@ target_include_directories(event_queue_lifetime_tests PRIVATE ${inc_headers})
|
||||
|
||||
add_kyty_full_emulator_test(kernel_file_system_tests
|
||||
"${KYTY_TESTS_DIR}/KernelFileSystemTests.cpp")
|
||||
target_link_libraries(kernel_file_system_tests ZArchive::zarchive)
|
||||
|
||||
add_executable(sync_on_address_tests EXCLUDE_FROM_ALL
|
||||
"${KYTY_TESTS_DIR}/SyncOnAddressTests.cpp"
|
||||
@@ -569,6 +595,8 @@ if(BUILD_TESTING)
|
||||
add_test(NAME image_page_table COMMAND $<TARGET_FILE:image_page_table_tests>)
|
||||
add_test(NAME memory_tracker COMMAND $<TARGET_FILE:memory_tracker_tests>)
|
||||
add_test(NAME page_manager COMMAND $<TARGET_FILE:page_manager_tests>)
|
||||
add_test(NAME archive_file COMMAND $<TARGET_FILE:archive_file_tests>)
|
||||
add_test(NAME avplayer_file COMMAND $<TARGET_FILE:avplayer_file_tests>)
|
||||
add_test(NAME bit_array COMMAND $<TARGET_FILE:bit_array_tests>)
|
||||
add_test(NAME lru_cache COMMAND $<TARGET_FILE:lru_cache_tests>)
|
||||
add_test(NAME shader_vertex_metadata COMMAND $<TARGET_FILE:shader_vertex_metadata_tests>)
|
||||
@@ -581,6 +609,9 @@ if(BUILD_TESTING)
|
||||
add_test(NAME ngs2_sampler COMMAND $<TARGET_FILE:ngs2_sampler_tests>)
|
||||
add_test(NAME pad_haptics COMMAND $<TARGET_FILE:pad_haptics_tests>)
|
||||
add_test(NAME save_data_memory COMMAND $<TARGET_FILE:save_data_memory_tests>)
|
||||
add_test(NAME png_enc COMMAND $<TARGET_FILE:png_enc_tests>)
|
||||
add_test(NAME ces COMMAND $<TARGET_FILE:ces_tests>)
|
||||
add_test(NAME save_data_dialog COMMAND $<TARGET_FILE:save_data_dialog_tests>)
|
||||
add_test(NAME http_uri_parse COMMAND $<TARGET_FILE:http_uri_parse_tests>)
|
||||
add_test(NAME shader_recompiler_compute COMMAND $<TARGET_FILE:shader_recompiler_compute_tests>)
|
||||
add_test(NAME shader_recompiler_alignbyte
|
||||
@@ -618,6 +649,8 @@ if(BUILD_TESTING)
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --compute-meta-clear-only)
|
||||
add_test(NAME buffer_cache_dirty_gc
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --buffer-cache-gc-only)
|
||||
add_test(NAME color_render_target_1d
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --rt1d-only)
|
||||
|
||||
if(WIN32)
|
||||
# These tests still depend on the Windows multisample-depth path.
|
||||
@@ -636,6 +669,8 @@ if(BUILD_TESTING)
|
||||
image_page_table_tests
|
||||
memory_tracker_tests
|
||||
page_manager_tests
|
||||
archive_file_tests
|
||||
avplayer_file_tests
|
||||
bit_array_tests
|
||||
lru_cache_tests
|
||||
shader_vertex_metadata_tests
|
||||
@@ -650,6 +685,9 @@ if(BUILD_TESTING)
|
||||
ngs2_sampler_tests
|
||||
pad_haptics_tests
|
||||
save_data_memory_tests
|
||||
png_enc_tests
|
||||
ces_tests
|
||||
save_data_dialog_tests
|
||||
http_uri_parse_tests
|
||||
)
|
||||
endif()
|
||||
@@ -735,6 +773,10 @@ endif()
|
||||
install(TARGETS kyty_emulator DESTINATION .)
|
||||
if(KYTY_BUILD_LAUNCHER)
|
||||
add_subdirectory("${KYTY_SOURCE_DIR}/launcher" "${CMAKE_CURRENT_BINARY_DIR}/launcher")
|
||||
|
||||
if(LINUX)
|
||||
target_link_options(launcher PRIVATE $<$<CONFIG:Release>:-Wl,--strip-all>)
|
||||
endif()
|
||||
endif()
|
||||
if(KYTY_CLANG_CL)
|
||||
install(FILES "${KYTY_THIRD_PARTY_DIR}/winpthread/bin/libwinpthread-1.dll" DESTINATION .)
|
||||
|
||||
@@ -236,9 +236,10 @@ cmake --build _Build/linux --target launcher --parallel
|
||||
cmake --install _Build/linux --prefix _Build/linux/install
|
||||
```
|
||||
|
||||
The configure step downloads the FFmpeg prebuilts and the `xbyak`/`zydis` sources, so it needs
|
||||
network access; a fully sandboxed `nix build` would require vendoring those inputs. A Vulkan 1.3
|
||||
driver must be available at runtime (on NixOS, `hardware.graphics.enable = true`).
|
||||
The configure step downloads the FFmpeg prebuilts and the `xbyak`, `zydis`, `zstd`, and ZArchive
|
||||
sources, so it needs network access; a fully sandboxed `nix build` would require vendoring those
|
||||
inputs. A Vulkan 1.3 driver must be available at runtime (on NixOS,
|
||||
`hardware.graphics.enable = true`).
|
||||
|
||||
### Building on macOS
|
||||
|
||||
@@ -332,17 +333,21 @@ open _Build/macos/install/KytyPS5.app # or double-click in Finder
|
||||
```
|
||||
|
||||
On first launch, add one or more game folders in the global settings. The launcher searches those
|
||||
folders recursively for game directories containing `eboot.bin`. Select a detected game and run it
|
||||
from the game list.
|
||||
folders recursively for game directories containing `eboot.bin` and ZArchive (`.zar`) game dumps
|
||||
whose archive root contains `eboot.bin`. Select a detected game and run it from the game list.
|
||||
ZArchive dumps are mounted read-only and streamed directly; they do not need to be extracted first.
|
||||
|
||||
The emulator can also be started directly with a legally obtained game directory or ELF file:
|
||||
The emulator can also be started directly with a legally obtained game directory, ELF file, or
|
||||
ZArchive dump:
|
||||
|
||||
```powershell
|
||||
.\_Build\windows\install\kyty_emulator.exe --game "D:\Games\ExampleGame"
|
||||
.\_Build\windows\install\kyty_emulator.exe --game "D:\Games\ExampleGame.zar"
|
||||
```
|
||||
|
||||
```bash
|
||||
./_Build/linux/install/kyty_emulator --game "/games/ExampleGame"
|
||||
./_Build/linux/install/kyty_emulator --game "/games/ExampleGame.zar"
|
||||
```
|
||||
|
||||
On macOS, the adjacent flat or app-bundled `libMoltenVK.dylib` is found automatically; no
|
||||
|
||||
@@ -26,6 +26,7 @@ target_link_libraries(common
|
||||
cpuinfo
|
||||
kyty_git_version
|
||||
spdlog::spdlog
|
||||
ZArchive::zarchive
|
||||
)
|
||||
|
||||
get_property(inc_headers TARGET common PROPERTY INCLUDE_DIRECTORIES)
|
||||
|
||||
@@ -0,0 +1,397 @@
|
||||
#include "common/archive.h"
|
||||
|
||||
#include "common/stringUtils.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <condition_variable>
|
||||
#include <cstring>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <string_view>
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <zarchive/zarchivereader.h>
|
||||
|
||||
// POSIX hosts and MinGW's pthread-backed libstdc++ have cancellable condition waits.
|
||||
// MSVC/ClangCL and MinGW's native Win32 threading backend do not use pthread waits.
|
||||
#if !defined(_WIN32) || defined(_GLIBCXX_GCC_GTHR_POSIX_H)
|
||||
#include <pthread.h>
|
||||
#define KYTY_ARCHIVE_PTHREAD_CANCELLATION
|
||||
#endif
|
||||
|
||||
namespace Common {
|
||||
|
||||
// Backends expose immutable metadata and offset-based reads. Cursor management, path routing,
|
||||
// reader lifetime and the host-stack worker are shared by every archive format.
|
||||
class ArchiveReader {
|
||||
public:
|
||||
struct Entry {
|
||||
uint64_t id;
|
||||
uint64_t size;
|
||||
bool is_file;
|
||||
};
|
||||
|
||||
virtual ~ArchiveReader() = default;
|
||||
virtual std::optional<Entry> Find(std::string_view member) = 0;
|
||||
virtual std::vector<File::DirEntry> List(std::string_view member) = 0;
|
||||
virtual uint64_t Read(uint64_t id, uint64_t offset, uint32_t size, void* data) = 0;
|
||||
};
|
||||
|
||||
namespace {
|
||||
|
||||
class ZArchiveReaderBackend final: public ArchiveReader {
|
||||
public:
|
||||
explicit ZArchiveReaderBackend(std::unique_ptr<ZArchiveReader> reader)
|
||||
: m_reader(std::move(reader)) {}
|
||||
|
||||
std::optional<Entry> Find(std::string_view member) override {
|
||||
const auto node = m_reader->LookUp(member);
|
||||
if (node == ZARCHIVE_INVALID_NODE) {
|
||||
return {};
|
||||
}
|
||||
return Entry {node, m_reader->GetFileSize(node), m_reader->IsFile(node)};
|
||||
}
|
||||
|
||||
std::vector<File::DirEntry> List(std::string_view member) override {
|
||||
const auto node = m_reader->LookUp(member);
|
||||
if (!m_reader->IsDirectory(node)) {
|
||||
return {};
|
||||
}
|
||||
const auto count = m_reader->GetDirEntryCount(node);
|
||||
if (count > (1u << 20u)) {
|
||||
return {};
|
||||
}
|
||||
std::vector<File::DirEntry> result;
|
||||
result.reserve(count);
|
||||
for (uint32_t index = 0; index < count; ++index) {
|
||||
ZArchiveReader::DirEntry entry {};
|
||||
if (m_reader->GetDirEntry(node, index, entry)) {
|
||||
result.push_back({std::string(entry.name), entry.isFile});
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
uint64_t Read(uint64_t id, uint64_t offset, uint32_t size, void* data) override {
|
||||
// ZArchiveReader already serializes access to its stream and decompression cache.
|
||||
return m_reader->ReadFromFile(static_cast<ZArchiveNodeHandle>(id), offset, size, data);
|
||||
}
|
||||
|
||||
private:
|
||||
std::unique_ptr<ZArchiveReader> m_reader;
|
||||
};
|
||||
|
||||
std::shared_ptr<ArchiveReader> OpenZArchive(const std::filesystem::path& path) {
|
||||
auto reader = std::unique_ptr<ZArchiveReader>(ZArchiveReader::OpenFromFile(path));
|
||||
return reader ? std::make_shared<ZArchiveReaderBackend>(std::move(reader)) : nullptr;
|
||||
}
|
||||
|
||||
struct ArchiveFormat {
|
||||
std::string_view extension;
|
||||
std::shared_ptr<ArchiveReader> (*open)(const std::filesystem::path&);
|
||||
};
|
||||
|
||||
constexpr ArchiveFormat Formats[] = {{".zar", OpenZArchive}};
|
||||
using NativeView = std::basic_string_view<std::filesystem::path::value_type>;
|
||||
|
||||
const ArchiveFormat* FindFormat(NativeView path) {
|
||||
for (const auto& format: Formats) {
|
||||
if (path.size() < format.extension.size()) {
|
||||
continue;
|
||||
}
|
||||
const auto extension = path.substr(path.size() - format.extension.size());
|
||||
if (std::equal(
|
||||
extension.begin(), extension.end(), format.extension.begin(),
|
||||
[](auto a, char b) { return (a >= 'A' && a <= 'Z' ? a + ('a' - 'A') : a) == b; })) {
|
||||
return &format;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
size_t FindMarker(NativeView path) {
|
||||
// Native files use this check on every operation. The usual path has no marker and allocates
|
||||
// nothing.
|
||||
for (auto marker = path.find('!'); marker != NativeView::npos;
|
||||
marker = path.find('!', marker + 1)) {
|
||||
if ((marker + 1 == path.size() || path[marker + 1] == '/' || path[marker + 1] == '\\') &&
|
||||
FindFormat(path.substr(0, marker)) != nullptr) {
|
||||
return marker;
|
||||
}
|
||||
}
|
||||
return NativeView::npos;
|
||||
}
|
||||
|
||||
struct ParsedPath {
|
||||
std::filesystem::path archive;
|
||||
std::string member;
|
||||
};
|
||||
|
||||
std::optional<ParsedPath> ParsePath(const std::filesystem::path& path) {
|
||||
const auto& native = path.native();
|
||||
const auto marker = FindMarker(native);
|
||||
if (marker == NativeView::npos) {
|
||||
return {};
|
||||
}
|
||||
ParsedPath parsed {.archive = std::filesystem::path(native.substr(0, marker))};
|
||||
auto member = PathToGenericString(std::filesystem::path(native.substr(marker + 1)));
|
||||
std::replace(member.begin(), member.end(), '\\', '/');
|
||||
std::string_view remaining = member;
|
||||
while (!remaining.empty()) {
|
||||
const auto separator = remaining.find('/');
|
||||
const auto component = remaining.substr(0, separator);
|
||||
remaining.remove_prefix(separator == std::string_view::npos ? remaining.size() : separator + 1);
|
||||
if (component.empty() || component == ".") {
|
||||
continue;
|
||||
}
|
||||
if (component == "..") {
|
||||
if (parsed.member.empty()) {
|
||||
return {};
|
||||
}
|
||||
const auto separator = parsed.member.rfind('/');
|
||||
parsed.member.resize(separator == std::string::npos ? 0 : separator);
|
||||
} else {
|
||||
if (!parsed.member.empty()) {
|
||||
parsed.member += '/';
|
||||
}
|
||||
parsed.member += component;
|
||||
}
|
||||
}
|
||||
return parsed;
|
||||
}
|
||||
|
||||
std::mutex g_readers_mutex;
|
||||
std::unordered_map<std::filesystem::path, std::weak_ptr<ArchiveReader>> g_readers;
|
||||
|
||||
// Guest stacks can be only 64 KiB. Decompress on an ordinary host stack, then copy into guest
|
||||
// memory on the caller so GPU-tracked writes use its fault handler. A single worker bounds host
|
||||
// threads and avoids duplicating the archive's own block cache. Requests live on waiting callers.
|
||||
class ArchiveIoThread {
|
||||
public:
|
||||
static ArchiveIoThread& Instance() {
|
||||
// Guest exit can run static destructors while other guest threads are still reading.
|
||||
// Keep the worker alive until process termination, as with the guest threads themselves.
|
||||
static auto* instance = new ArchiveIoThread;
|
||||
return *instance;
|
||||
}
|
||||
|
||||
uint64_t Read(ArchiveReader& reader, uint64_t id, uint64_t offset, uint32_t size, void* data) {
|
||||
Request request {reader, id, offset, size, data};
|
||||
std::unique_lock lock(m_mutex);
|
||||
if (m_last != nullptr) {
|
||||
m_last->next = &request;
|
||||
} else {
|
||||
m_first = &request;
|
||||
}
|
||||
m_last = &request;
|
||||
m_wake.notify_one();
|
||||
request.ready.wait(lock, [&] { return request.done; });
|
||||
return request.result;
|
||||
}
|
||||
|
||||
KYTY_CLASS_NO_COPY(ArchiveIoThread);
|
||||
|
||||
private:
|
||||
struct Request {
|
||||
ArchiveReader& reader;
|
||||
uint64_t id;
|
||||
uint64_t offset;
|
||||
uint32_t size;
|
||||
void* data;
|
||||
Request* next = nullptr;
|
||||
uint64_t result = 0;
|
||||
bool done = false;
|
||||
std::condition_variable ready {};
|
||||
};
|
||||
|
||||
ArchiveIoThread() {
|
||||
std::thread([this] { Loop(); }).detach();
|
||||
}
|
||||
|
||||
void Loop() {
|
||||
for (;;) {
|
||||
Request* request;
|
||||
{
|
||||
std::unique_lock lock(m_mutex);
|
||||
m_wake.wait(lock, [this] { return m_first != nullptr; });
|
||||
request = m_first;
|
||||
m_first = request->next;
|
||||
if (m_first == nullptr) {
|
||||
m_last = nullptr;
|
||||
}
|
||||
}
|
||||
const auto result =
|
||||
request->reader.Read(request->id, request->offset, request->size, request->data);
|
||||
{
|
||||
std::lock_guard lock(m_mutex);
|
||||
request->result = result;
|
||||
request->done = true;
|
||||
// Notify before releasing the mutex: the waiting caller owns the condition
|
||||
// variable.
|
||||
request->ready.notify_one();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::mutex m_mutex;
|
||||
std::condition_variable m_wake;
|
||||
Request* m_first = nullptr;
|
||||
Request* m_last = nullptr;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
struct ArchiveFile::Private {
|
||||
std::shared_ptr<ArchiveReader> reader;
|
||||
ArchiveReader::Entry entry;
|
||||
uint64_t position = 0;
|
||||
};
|
||||
|
||||
ArchiveFile::ArchiveFile(std::unique_ptr<Private> p): m_p(std::move(p)) {}
|
||||
ArchiveFile::~ArchiveFile() = default;
|
||||
|
||||
uint64_t ArchiveFile::Size() const {
|
||||
return m_p->entry.size;
|
||||
}
|
||||
uint64_t ArchiveFile::Tell() const {
|
||||
return m_p->position;
|
||||
}
|
||||
bool ArchiveFile::Seek(uint64_t offset) {
|
||||
m_p->position = offset;
|
||||
return true;
|
||||
}
|
||||
|
||||
void ArchiveFile::Read(void* data, uint32_t size, uint32_t* bytes_read) {
|
||||
#if defined(KYTY_ARCHIVE_PTHREAD_CANCELLATION)
|
||||
// A pending cancellation must not destroy the waiting request or its TLS buffer while the
|
||||
// worker still uses them. Restore the caller's state only after the copy is complete.
|
||||
int cancellation_state = PTHREAD_CANCEL_ENABLE;
|
||||
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cancellation_state);
|
||||
#endif
|
||||
constexpr uint32_t chunk_size = 1u << 20u;
|
||||
thread_local std::vector<uint8_t> chunk;
|
||||
uint32_t read = 0;
|
||||
if (data != nullptr && size != 0 && m_p->position < Size()) {
|
||||
const auto requested =
|
||||
static_cast<uint32_t>(std::min<uint64_t>(size, Size() - m_p->position));
|
||||
if (chunk.size() < std::min(chunk_size, requested)) {
|
||||
chunk.resize(std::min(chunk_size, requested));
|
||||
}
|
||||
while (read < requested) {
|
||||
const auto step = std::min(chunk_size, requested - read);
|
||||
const auto got = ArchiveIoThread::Instance().Read(*m_p->reader, m_p->entry.id,
|
||||
m_p->position, step, chunk.data());
|
||||
if (got == 0 || got > step) {
|
||||
break;
|
||||
}
|
||||
std::memcpy(static_cast<uint8_t*>(data) + read, chunk.data(), got);
|
||||
read += static_cast<uint32_t>(got);
|
||||
m_p->position += got;
|
||||
if (got < step) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (bytes_read != nullptr) {
|
||||
*bytes_read = read;
|
||||
}
|
||||
#if defined(KYTY_ARCHIVE_PTHREAD_CANCELLATION)
|
||||
pthread_setcancelstate(cancellation_state, nullptr);
|
||||
#endif
|
||||
}
|
||||
|
||||
std::filesystem::path MakeArchivePath(const std::filesystem::path& archive,
|
||||
const std::filesystem::path& member) {
|
||||
auto root = archive;
|
||||
root += "!";
|
||||
return member.empty() ? root : root / member.relative_path();
|
||||
}
|
||||
|
||||
bool IsSupportedArchive(const std::filesystem::path& path) {
|
||||
return FindFormat(path.native()) != nullptr;
|
||||
}
|
||||
|
||||
bool IsArchivePath(const std::filesystem::path& path) {
|
||||
return FindMarker(path.native()) != NativeView::npos;
|
||||
}
|
||||
|
||||
std::filesystem::path GetArchiveHostPath(const std::filesystem::path& path) {
|
||||
const auto marker = FindMarker(path.native());
|
||||
return marker == NativeView::npos ? std::filesystem::path {}
|
||||
: std::filesystem::path(path.native().substr(0, marker));
|
||||
}
|
||||
|
||||
std::shared_ptr<ArchiveReader> OpenArchive(const std::filesystem::path& path) {
|
||||
const auto archive = IsArchivePath(path) ? GetArchiveHostPath(path) : path;
|
||||
const auto* format = FindFormat(archive.native());
|
||||
if (format == nullptr) {
|
||||
return {};
|
||||
}
|
||||
std::error_code error;
|
||||
auto key = std::filesystem::absolute(archive, error);
|
||||
if (error) {
|
||||
return {};
|
||||
}
|
||||
{
|
||||
std::lock_guard lock(g_readers_mutex);
|
||||
if (auto it = g_readers.find(key); it != g_readers.end()) {
|
||||
if (auto reader = it->second.lock()) {
|
||||
return reader;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Do not hold the cache mutex while opening an unrelated archive from disk.
|
||||
auto reader = format->open(archive);
|
||||
if (reader == nullptr) {
|
||||
return {};
|
||||
}
|
||||
std::lock_guard lock(g_readers_mutex);
|
||||
std::erase_if(g_readers, [](const auto& entry) { return entry.second.expired(); });
|
||||
auto& cached = g_readers[key];
|
||||
if (auto existing = cached.lock()) {
|
||||
return existing;
|
||||
}
|
||||
cached = reader;
|
||||
return reader;
|
||||
}
|
||||
|
||||
std::optional<File::Info> GetArchiveInfo(const std::filesystem::path& path) {
|
||||
const auto parsed = ParsePath(path);
|
||||
if (parsed) {
|
||||
if (auto reader = OpenArchive(parsed->archive)) {
|
||||
if (auto entry = reader->Find(parsed->member)) {
|
||||
return File::Info {entry->is_file, entry->size};
|
||||
}
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<File::DirEntry> GetArchiveDirEntries(const std::filesystem::path& path) {
|
||||
const auto parsed = ParsePath(path);
|
||||
if (parsed) {
|
||||
if (auto reader = OpenArchive(parsed->archive)) {
|
||||
return reader->List(parsed->member);
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
std::unique_ptr<ArchiveFile> OpenArchiveFile(const std::filesystem::path& path) {
|
||||
auto parsed = ParsePath(path);
|
||||
if (!parsed) {
|
||||
return {};
|
||||
}
|
||||
auto reader = OpenArchive(parsed->archive);
|
||||
const auto entry = reader ? reader->Find(parsed->member) : std::nullopt;
|
||||
if (!entry || !entry->is_file) {
|
||||
return {};
|
||||
}
|
||||
auto p = std::make_unique<ArchiveFile::Private>();
|
||||
p->reader = std::move(reader);
|
||||
p->entry = *entry;
|
||||
return std::unique_ptr<ArchiveFile>(new ArchiveFile(std::move(p)));
|
||||
}
|
||||
|
||||
} // namespace Common
|
||||
@@ -0,0 +1,50 @@
|
||||
#ifndef KYTY_COMMON_ARCHIVE_H_
|
||||
#define KYTY_COMMON_ARCHIVE_H_
|
||||
|
||||
#include "common/file.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace Common {
|
||||
|
||||
class ArchiveReader;
|
||||
|
||||
class ArchiveFile {
|
||||
public:
|
||||
~ArchiveFile();
|
||||
|
||||
[[nodiscard]] uint64_t Size() const;
|
||||
[[nodiscard]] uint64_t Tell() const;
|
||||
bool Seek(uint64_t offset);
|
||||
void Read(void* data, uint32_t size, uint32_t* bytes_read);
|
||||
|
||||
KYTY_CLASS_NO_COPY(ArchiveFile);
|
||||
|
||||
private:
|
||||
struct Private;
|
||||
explicit ArchiveFile(std::unique_ptr<Private> p);
|
||||
std::unique_ptr<Private> m_p;
|
||||
|
||||
friend std::unique_ptr<ArchiveFile> OpenArchiveFile(const std::filesystem::path& path);
|
||||
};
|
||||
|
||||
// Virtual paths use "archive.ext!/member" and retain native host-path encoding.
|
||||
[[nodiscard]] std::filesystem::path MakeArchivePath(const std::filesystem::path& archive,
|
||||
const std::filesystem::path& member = {});
|
||||
[[nodiscard]] bool IsSupportedArchive(const std::filesystem::path& path);
|
||||
[[nodiscard]] bool IsArchivePath(const std::filesystem::path& path);
|
||||
[[nodiscard]] std::optional<File::Info> GetArchiveInfo(const std::filesystem::path& path);
|
||||
[[nodiscard]] std::vector<File::DirEntry> GetArchiveDirEntries(const std::filesystem::path& path);
|
||||
[[nodiscard]] std::filesystem::path GetArchiveHostPath(const std::filesystem::path& path);
|
||||
[[nodiscard]] std::unique_ptr<ArchiveFile> OpenArchiveFile(const std::filesystem::path& path);
|
||||
|
||||
// Retain this handle while a mount or metadata scan is active to reuse the index and block cache.
|
||||
// Accepts either a host archive filename or a virtual path inside the archive.
|
||||
[[nodiscard]] std::shared_ptr<ArchiveReader> OpenArchive(const std::filesystem::path& path);
|
||||
|
||||
} // namespace Common
|
||||
|
||||
#endif /* KYTY_COMMON_ARCHIVE_H_ */
|
||||
@@ -47,6 +47,10 @@ const std::string& GetAudioInputDevice() {
|
||||
return g_config->audio_input_device;
|
||||
}
|
||||
|
||||
const std::optional<ControllerColor>& GetControllerColor() {
|
||||
return g_config->controller_color;
|
||||
}
|
||||
|
||||
PresentMode GetPresentMode() {
|
||||
return g_config->present_mode;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,9 @@
|
||||
#include "common/common.h"
|
||||
|
||||
#include <cstddef>
|
||||
#include <array>
|
||||
#include <filesystem>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
@@ -26,6 +28,7 @@ enum class LogDirection { Silent, Console, File };
|
||||
enum class PresentMode { Fifo, Mailbox, Immediate };
|
||||
|
||||
using Keymap = std::vector<std::string>;
|
||||
using ControllerColor = std::array<uint8_t, 3>;
|
||||
|
||||
constexpr uint32_t DEFAULT_CONSOLE_LANGUAGE = 1;
|
||||
constexpr uint32_t MAX_CONSOLE_LANGUAGE = 29;
|
||||
@@ -44,6 +47,7 @@ struct ConfigOptions {
|
||||
std::string user_name = "Kyty";
|
||||
int32_t user_id = DEFAULT_USER_ID;
|
||||
std::string audio_input_device;
|
||||
std::optional<ControllerColor> controller_color;
|
||||
PresentMode present_mode = PresentMode::Mailbox;
|
||||
int32_t gpu_index = -1;
|
||||
bool fullscreen_enabled = false;
|
||||
@@ -81,6 +85,7 @@ uint32_t GetScreenHeight();
|
||||
const std::string& GetUserName();
|
||||
int32_t GetUserId();
|
||||
const std::string& GetAudioInputDevice();
|
||||
const std::optional<ControllerColor>& GetControllerColor();
|
||||
PresentMode GetPresentMode();
|
||||
int32_t GetGpuIndex();
|
||||
bool FullscreenEnabled();
|
||||
|
||||
+140
-17
@@ -1,5 +1,6 @@
|
||||
#include "common/file.h"
|
||||
|
||||
#include "common/archive.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/common.h"
|
||||
#include "common/platform/sysFileIO.h"
|
||||
@@ -61,7 +62,8 @@ SysFileTimeStruct DateTimeToFileTimeUtc(const DateTime& date_time) {
|
||||
} // namespace
|
||||
|
||||
struct File::FilePrivate {
|
||||
sys_file_t* f;
|
||||
sys_file_t* f;
|
||||
std::unique_ptr<ArchiveFile> archive;
|
||||
};
|
||||
|
||||
File::File(): m_p(std::make_unique<FilePrivate>()) {
|
||||
@@ -85,11 +87,15 @@ File::File(const std::filesystem::path& name, Mode mode): m_p(std::make_unique<F
|
||||
}
|
||||
|
||||
bool File::IsInvalid() const {
|
||||
return m_p->f == nullptr;
|
||||
return m_p->f == nullptr && m_p->archive == nullptr;
|
||||
}
|
||||
|
||||
bool File::Create(const std::filesystem::path& name) {
|
||||
EXIT_IF(m_p->f != nullptr);
|
||||
EXIT_IF(!IsInvalid());
|
||||
|
||||
if (IsArchivePath(name)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
m_file_name = name;
|
||||
|
||||
@@ -104,9 +110,16 @@ bool File::Create(const std::filesystem::path& name) {
|
||||
}
|
||||
|
||||
bool File::Open(const std::filesystem::path& name, Mode mode) {
|
||||
EXIT_IF(m_p->f != nullptr);
|
||||
EXIT_IF(!IsInvalid());
|
||||
|
||||
m_file_name = name;
|
||||
if (IsArchivePath(name)) {
|
||||
if (mode != Mode::Read) {
|
||||
return false;
|
||||
}
|
||||
m_p->archive = OpenArchiveFile(name);
|
||||
return m_p->archive != nullptr;
|
||||
}
|
||||
|
||||
switch (mode) {
|
||||
case Mode::Read: m_p->f = SysFileOpenR(name); break;
|
||||
@@ -124,7 +137,7 @@ bool File::Open(const std::filesystem::path& name, Mode mode) {
|
||||
}
|
||||
|
||||
bool File::OpenInMem(void* buf, uint32_t buf_size) {
|
||||
EXIT_IF(m_p->f != nullptr);
|
||||
EXIT_IF(!IsInvalid());
|
||||
|
||||
m_p->f = SysFileOpen(static_cast<uint8_t*>(buf), buf_size);
|
||||
|
||||
@@ -137,7 +150,7 @@ bool File::OpenInMem(void* buf, uint32_t buf_size) {
|
||||
}
|
||||
|
||||
bool File::CreateInMem() {
|
||||
EXIT_IF(m_p->f != nullptr);
|
||||
EXIT_IF(!IsInvalid());
|
||||
|
||||
m_p->f = SysFileCreate();
|
||||
|
||||
@@ -150,6 +163,7 @@ bool File::CreateInMem() {
|
||||
}
|
||||
|
||||
void File::Close() {
|
||||
m_p->archive.reset();
|
||||
if (m_p->f != nullptr) {
|
||||
SysFileClose(m_p->f);
|
||||
m_p->f = nullptr;
|
||||
@@ -157,7 +171,11 @@ void File::Close() {
|
||||
}
|
||||
|
||||
uint64_t File::Size() const {
|
||||
EXIT_IF(m_p->f == nullptr);
|
||||
EXIT_IF(IsInvalid());
|
||||
|
||||
if (m_p->archive != nullptr) {
|
||||
return m_p->archive->Size();
|
||||
}
|
||||
|
||||
if (m_p->f == nullptr) {
|
||||
return 0;
|
||||
@@ -167,41 +185,76 @@ uint64_t File::Size() const {
|
||||
}
|
||||
|
||||
uint64_t File::Remaining() const {
|
||||
EXIT_IF(m_p->f == nullptr);
|
||||
EXIT_IF(IsInvalid());
|
||||
|
||||
return Size() - Tell();
|
||||
const auto size = Size();
|
||||
const auto position = Tell();
|
||||
return position < size ? size - position : 0;
|
||||
}
|
||||
|
||||
uint64_t File::Size(const std::filesystem::path& name) {
|
||||
if (IsArchivePath(name)) {
|
||||
const auto info = GetArchiveInfo(name);
|
||||
return info ? info->size : 0;
|
||||
}
|
||||
return SysFileSize(name);
|
||||
}
|
||||
|
||||
std::optional<File::Info> File::GetInfo(const std::filesystem::path& name) {
|
||||
if (IsArchivePath(name)) {
|
||||
return GetArchiveInfo(name);
|
||||
}
|
||||
Info info {};
|
||||
return SysFileGetInfo(name, &info.is_file, &info.size) ? std::optional(info) : std::nullopt;
|
||||
}
|
||||
|
||||
bool File::Seek(uint64_t offset) {
|
||||
EXIT_IF(m_p->f == nullptr);
|
||||
EXIT_IF(IsInvalid());
|
||||
|
||||
if (m_p->archive != nullptr) {
|
||||
return m_p->archive->Seek(offset);
|
||||
}
|
||||
|
||||
return SysFileSeek(*m_p->f, offset);
|
||||
}
|
||||
|
||||
bool File::Truncate(uint64_t size) {
|
||||
EXIT_IF(m_p->f == nullptr);
|
||||
EXIT_IF(IsInvalid());
|
||||
|
||||
if (m_p->archive != nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return SysFileTruncate(*m_p->f, size);
|
||||
}
|
||||
|
||||
bool File::Unlink() {
|
||||
EXIT_IF(m_p->f == nullptr);
|
||||
EXIT_IF(IsInvalid());
|
||||
|
||||
if (m_p->archive != nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return SysFileUnlink(*m_p->f, m_file_name);
|
||||
}
|
||||
|
||||
uint64_t File::Tell() const {
|
||||
EXIT_IF(m_p->f == nullptr);
|
||||
EXIT_IF(IsInvalid());
|
||||
|
||||
if (m_p->archive != nullptr) {
|
||||
return m_p->archive->Tell();
|
||||
}
|
||||
|
||||
return SysFileTell(*m_p->f);
|
||||
}
|
||||
|
||||
void File::Read(void* data, uint32_t size, uint32_t* bytes_read) {
|
||||
EXIT_IF(m_p->f == nullptr);
|
||||
EXIT_IF(IsInvalid());
|
||||
|
||||
if (m_p->archive != nullptr) {
|
||||
m_p->archive->Read(data, size, bytes_read);
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_p->f != nullptr) {
|
||||
SysFileRead(data, size, *m_p->f, bytes_read);
|
||||
@@ -209,7 +262,14 @@ void File::Read(void* data, uint32_t size, uint32_t* bytes_read) {
|
||||
}
|
||||
|
||||
void File::Write(const void* data, uint32_t size, uint32_t* bytes_written) {
|
||||
EXIT_IF(m_p->f == nullptr);
|
||||
EXIT_IF(IsInvalid());
|
||||
|
||||
if (m_p->archive != nullptr) {
|
||||
if (bytes_written != nullptr) {
|
||||
*bytes_written = 0;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
SysFileWrite(data, size, *m_p->f, bytes_written);
|
||||
}
|
||||
@@ -257,24 +317,41 @@ static std::filesystem::path WithoutTrailingSeparator(
|
||||
|
||||
|
||||
bool File::IsDirectoryExisting(const std::filesystem::path& path) {
|
||||
if (IsArchivePath(path)) {
|
||||
const auto info = GetArchiveInfo(path);
|
||||
return info && !info->is_file;
|
||||
}
|
||||
return SysFileIsDirectoryExisting(WithoutTrailingSeparator(path));
|
||||
}
|
||||
|
||||
bool File::IsFileExisting(const std::filesystem::path& name) {
|
||||
if (IsArchivePath(name)) {
|
||||
const auto info = GetArchiveInfo(name);
|
||||
return info && info->is_file;
|
||||
}
|
||||
return SysFileIsFileExisting(name);
|
||||
}
|
||||
|
||||
bool File::CreateDirectory(const std::filesystem::path& path) {
|
||||
if (IsArchivePath(path)) {
|
||||
return false;
|
||||
}
|
||||
return SysFileCreateDirectory(
|
||||
WithoutTrailingSeparator(path));
|
||||
}
|
||||
|
||||
bool File::DeleteDirectory(const std::filesystem::path& path) {
|
||||
if (IsArchivePath(path)) {
|
||||
return false;
|
||||
}
|
||||
return SysFileDeleteDirectory(
|
||||
WithoutTrailingSeparator(path));
|
||||
}
|
||||
|
||||
bool File::CreateDirectories(const std::filesystem::path& path) {
|
||||
if (IsArchivePath(path)) {
|
||||
return false;
|
||||
}
|
||||
const auto normalized_path =
|
||||
WithoutTrailingSeparator(path);
|
||||
|
||||
@@ -297,6 +374,9 @@ bool File::CreateDirectories(const std::filesystem::path& path) {
|
||||
}
|
||||
|
||||
bool File::DeleteDirectories(const std::filesystem::path& path) {
|
||||
if (IsArchivePath(path)) {
|
||||
return false;
|
||||
}
|
||||
const auto normalized_path =
|
||||
WithoutTrailingSeparator(path);
|
||||
|
||||
@@ -322,11 +402,18 @@ bool File::DeleteDirectories(const std::filesystem::path& path) {
|
||||
bool File::DeleteFile(
|
||||
const std::filesystem::path& name) // @suppress("Member declaration not found")
|
||||
{
|
||||
if (IsArchivePath(name)) {
|
||||
return false;
|
||||
}
|
||||
return SysFileDeleteFile(name);
|
||||
}
|
||||
|
||||
bool File::Flush() {
|
||||
EXIT_IF(m_p->f == nullptr);
|
||||
EXIT_IF(IsInvalid());
|
||||
|
||||
if (m_p->archive != nullptr) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return SysFileFlush(*m_p->f);
|
||||
}
|
||||
@@ -348,6 +435,9 @@ std::vector<std::byte> File::ReadWholeBuffer() {
|
||||
}
|
||||
|
||||
DateTime File::GetLastAccessTimeUTC(const std::filesystem::path& name) {
|
||||
if (IsArchivePath(name)) {
|
||||
return GetLastAccessTimeUTC(GetArchiveHostPath(name));
|
||||
}
|
||||
SysTimeStruct t {};
|
||||
SysFileToSystemTimeUtc(SysFileGetLastAccessTimeUtc(name), t);
|
||||
|
||||
@@ -359,6 +449,9 @@ DateTime File::GetLastAccessTimeUTC(const std::filesystem::path& name) {
|
||||
}
|
||||
|
||||
DateTime File::GetLastWriteTimeUTC(const std::filesystem::path& name) {
|
||||
if (IsArchivePath(name)) {
|
||||
return GetLastWriteTimeUTC(GetArchiveHostPath(name));
|
||||
}
|
||||
SysTimeStruct t {};
|
||||
SysFileToSystemTimeUtc(SysFileGetLastWriteTimeUtc(name), t);
|
||||
|
||||
@@ -373,6 +466,10 @@ void File::GetLastAccessAndWriteTimeUTC(const std::filesystem::path& name, DateT
|
||||
DateTime* write) {
|
||||
EXIT_IF(access == nullptr);
|
||||
EXIT_IF(write == nullptr);
|
||||
if (IsArchivePath(name)) {
|
||||
GetLastAccessAndWriteTimeUTC(GetArchiveHostPath(name), access, write);
|
||||
return;
|
||||
}
|
||||
|
||||
SysTimeStruct at {};
|
||||
SysTimeStruct wt {};
|
||||
@@ -399,7 +496,12 @@ void File::GetLastAccessAndWriteTimeUTC(DateTime* access, DateTime* write) {
|
||||
EXIT_IF(access == nullptr);
|
||||
EXIT_IF(write == nullptr);
|
||||
|
||||
EXIT_IF(m_p->f == nullptr);
|
||||
EXIT_IF(IsInvalid());
|
||||
|
||||
if (m_p->archive != nullptr) {
|
||||
GetLastAccessAndWriteTimeUTC(m_file_name, access, write);
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_p->f == nullptr) {
|
||||
*access = DateTime();
|
||||
@@ -429,23 +531,35 @@ void File::GetLastAccessAndWriteTimeUTC(DateTime* access, DateTime* write) {
|
||||
}
|
||||
|
||||
bool File::SetLastAccessTimeUTC(const std::filesystem::path& name, const DateTime& dt) {
|
||||
if (IsArchivePath(name)) {
|
||||
return false;
|
||||
}
|
||||
auto f = DateTimeToFileTimeUtc(dt);
|
||||
return SysFileSetLastAccessTimeUtc(name, f);
|
||||
}
|
||||
|
||||
bool File::SetLastWriteTimeUTC(const std::filesystem::path& name, const DateTime& dt) {
|
||||
if (IsArchivePath(name)) {
|
||||
return false;
|
||||
}
|
||||
auto f = DateTimeToFileTimeUtc(dt);
|
||||
return SysFileSetLastWriteTimeUtc(name, f);
|
||||
}
|
||||
|
||||
bool File::SetLastAccessAndWriteTimeUTC(const std::filesystem::path& name, const DateTime& access,
|
||||
const DateTime& write) {
|
||||
if (IsArchivePath(name)) {
|
||||
return false;
|
||||
}
|
||||
auto af = DateTimeToFileTimeUtc(access);
|
||||
auto wf = DateTimeToFileTimeUtc(write);
|
||||
return SysFileSetLastAccessAndWriteTimeUtc(name, af, wf);
|
||||
}
|
||||
|
||||
std::vector<File::DirEntry> File::GetDirEntries(const std::filesystem::path& path) {
|
||||
if (IsArchivePath(path)) {
|
||||
return GetArchiveDirEntries(path);
|
||||
}
|
||||
std::vector<sys_dir_entry_t> files;
|
||||
|
||||
SysFileGetDents(path, files);
|
||||
@@ -463,12 +577,18 @@ std::vector<File::DirEntry> File::GetDirEntries(const std::filesystem::path& pat
|
||||
bool File::CopyFile(const std::filesystem::path& src,
|
||||
const std::filesystem::path& dst) // @suppress("Member declaration not found")
|
||||
{
|
||||
if (IsArchivePath(src) || IsArchivePath(dst)) {
|
||||
return false;
|
||||
}
|
||||
return SysFileCopyFile(src, dst);
|
||||
}
|
||||
|
||||
bool File::RenameFile(const std::filesystem::path& src,
|
||||
const std::filesystem::path& dst) // @suppress("Member declaration not found")
|
||||
{
|
||||
if (IsArchivePath(src) || IsArchivePath(dst)) {
|
||||
return false;
|
||||
}
|
||||
if (IsFileExisting(dst)) {
|
||||
DeleteFile(dst); // @suppress("Invalid arguments")
|
||||
}
|
||||
@@ -477,6 +597,9 @@ bool File::RenameFile(const std::filesystem::path& src,
|
||||
}
|
||||
|
||||
void File::RemoveReadonly(const std::filesystem::path& name) {
|
||||
if (IsArchivePath(name)) {
|
||||
return;
|
||||
}
|
||||
SysFileRemoveReadonly(name);
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <cstddef>
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
@@ -34,6 +35,10 @@ public:
|
||||
std::string name;
|
||||
bool is_file;
|
||||
};
|
||||
struct Info {
|
||||
bool is_file;
|
||||
uint64_t size;
|
||||
};
|
||||
|
||||
File();
|
||||
explicit File(const std::filesystem::path& name);
|
||||
@@ -72,6 +77,7 @@ public:
|
||||
std::vector<std::byte> ReadWholeBuffer();
|
||||
|
||||
static uint64_t Size(const std::filesystem::path& name);
|
||||
static std::optional<Info> GetInfo(const std::filesystem::path& name);
|
||||
|
||||
static bool IsDirectoryExisting(const std::filesystem::path& path);
|
||||
static bool IsFileExisting(const std::filesystem::path& name);
|
||||
|
||||
@@ -45,6 +45,7 @@ bool SysFileTruncate(sys_file_t& f, uint64_t size); // NOLINT(google-runt
|
||||
bool SysFileUnlink(sys_file_t& f,
|
||||
const std::filesystem::path& name); // NOLINT(google-runtime-references)
|
||||
uint64_t SysFileSize(const std::filesystem::path& file_name);
|
||||
bool SysFileGetInfo(const std::filesystem::path& name, bool* is_file, uint64_t* size);
|
||||
bool SysFileIsError(sys_file_t& f); // NOLINT(google-runtime-references)
|
||||
bool SysFileIsDirectoryExisting(const std::filesystem::path& path);
|
||||
bool SysFileIsFileExisting(const std::filesystem::path& name);
|
||||
|
||||
@@ -54,7 +54,7 @@ struct sys_file_t {
|
||||
#endif
|
||||
|
||||
static std::filesystem::path get_internal_name(const std::filesystem::path& name) {
|
||||
return name.is_absolute() ? name : (std::filesystem::path(".") / name);
|
||||
return name.empty() || name.is_absolute() ? name : (std::filesystem::path(".") / name);
|
||||
}
|
||||
|
||||
// Pass access-pattern hints to the host.
|
||||
@@ -297,10 +297,21 @@ uint64_t SysFileSize(sys_file_t& f) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool SysFileGetInfo(const std::filesystem::path& name, bool* is_file, uint64_t* size) {
|
||||
const auto path = get_internal_name(name);
|
||||
struct stat info {};
|
||||
if (stat(path.c_str(), &info) != 0) {
|
||||
return false;
|
||||
}
|
||||
*is_file = !S_ISDIR(info.st_mode);
|
||||
*size = *is_file ? static_cast<uint64_t>(info.st_size) : 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
uint64_t SysFileSize(const std::filesystem::path& file_name) {
|
||||
sys_file_t* f = SysFileOpenR(file_name);
|
||||
uint64_t size = SysFileSize(*f);
|
||||
SysFileClose(f);
|
||||
bool is_file;
|
||||
uint64_t size = 0;
|
||||
SysFileGetInfo(file_name, &is_file, &size);
|
||||
return size;
|
||||
}
|
||||
|
||||
@@ -353,29 +364,15 @@ bool SysFileIsError(sys_file_t& f) {
|
||||
}
|
||||
|
||||
bool SysFileIsDirectoryExisting(const std::filesystem::path& path) {
|
||||
auto real_name = get_internal_name(path);
|
||||
auto real_name_str = real_name.string();
|
||||
|
||||
struct stat s {};
|
||||
|
||||
if (0 != stat(real_name_str.c_str(), &s)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return S_ISDIR(s.st_mode); // NOLINT
|
||||
bool is_file;
|
||||
uint64_t size;
|
||||
return SysFileGetInfo(path, &is_file, &size) && !is_file;
|
||||
}
|
||||
|
||||
bool SysFileIsFileExisting(const std::filesystem::path& name) {
|
||||
auto real_name = get_internal_name(name);
|
||||
auto real_name_str = real_name.string();
|
||||
|
||||
struct stat s {};
|
||||
|
||||
if (0 != stat(real_name_str.c_str(), &s)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !S_ISDIR(s.st_mode); // NOLINT
|
||||
bool is_file;
|
||||
uint64_t size;
|
||||
return SysFileGetInfo(name, &is_file, &size) && is_file;
|
||||
}
|
||||
|
||||
bool SysFileCreateDirectory(const std::filesystem::path& path) {
|
||||
|
||||
@@ -266,19 +266,27 @@ uint64_t SysFileSize(sys_file_t& f) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint64_t SysFileSize(const std::filesystem::path& file_name) {
|
||||
LARGE_INTEGER s;
|
||||
WIN32_FILE_ATTRIBUTE_DATA a;
|
||||
|
||||
auto wide = file_name.wstring();
|
||||
if (GetFileAttributesExW(wide.c_str(), GetFileExInfoStandard, &a) == 0) {
|
||||
return 0;
|
||||
bool SysFileGetInfo(const std::filesystem::path& name, bool* is_file, uint64_t* size) {
|
||||
const bool directory_path = !name.empty() && !name.has_filename() && name != name.root_path();
|
||||
WIN32_FILE_ATTRIBUTE_DATA info {};
|
||||
if (GetFileAttributesExW(directory_path ? name.parent_path().c_str() : name.c_str(),
|
||||
GetFileExInfoStandard, &info) == 0) {
|
||||
return false;
|
||||
}
|
||||
const bool file = (info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0;
|
||||
if (directory_path && file) {
|
||||
return false;
|
||||
}
|
||||
*is_file = file;
|
||||
*size = *is_file ? ((static_cast<uint64_t>(info.nFileSizeHigh) << 32u) | info.nFileSizeLow) : 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
s.HighPart = static_cast<LONG>(a.nFileSizeHigh);
|
||||
s.LowPart = a.nFileSizeLow;
|
||||
|
||||
return s.QuadPart;
|
||||
uint64_t SysFileSize(const std::filesystem::path& file_name) {
|
||||
bool is_file;
|
||||
uint64_t size = 0;
|
||||
SysFileGetInfo(file_name, &is_file, &size);
|
||||
return size;
|
||||
}
|
||||
|
||||
bool SysFileTruncate(sys_file_t& f, uint64_t size) {
|
||||
@@ -346,17 +354,15 @@ bool SysFileIsError(sys_file_t& f) {
|
||||
}
|
||||
|
||||
bool SysFileIsDirectoryExisting(const std::filesystem::path& path) {
|
||||
auto wide = path.wstring();
|
||||
DWORD a = GetFileAttributesW(wide.c_str());
|
||||
return a != INVALID_FILE_ATTRIBUTES &&
|
||||
((a & static_cast<DWORD>(FILE_ATTRIBUTE_DIRECTORY)) != 0u);
|
||||
bool is_file;
|
||||
uint64_t size;
|
||||
return SysFileGetInfo(path, &is_file, &size) && !is_file;
|
||||
}
|
||||
|
||||
bool SysFileIsFileExisting(const std::filesystem::path& name) {
|
||||
auto wide = name.wstring();
|
||||
DWORD a = GetFileAttributesW(wide.c_str());
|
||||
return a != INVALID_FILE_ATTRIBUTES &&
|
||||
((a & static_cast<DWORD>(FILE_ATTRIBUTE_DIRECTORY)) == 0u);
|
||||
bool is_file;
|
||||
uint64_t size;
|
||||
return SysFileGetInfo(name, &is_file, &size) && is_file;
|
||||
}
|
||||
|
||||
bool SysFileCreateDirectory(const std::filesystem::path& path) {
|
||||
|
||||
@@ -2,52 +2,13 @@
|
||||
|
||||
#include "common/emulatorConfig.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <common/TracyProtocol.hpp>
|
||||
#include <common/TracyVersion.hpp>
|
||||
#include <cstdio>
|
||||
#include <tracy/Tracy.hpp>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
|
||||
thread_local std::vector<Profiler::ScopedBlock*> g_block_stack;
|
||||
|
||||
void RemoveBlock(Profiler::ScopedBlock* block) {
|
||||
auto block_it = std::find(g_block_stack.rbegin(), g_block_stack.rend(), block);
|
||||
if (block_it != g_block_stack.rend()) {
|
||||
g_block_stack.erase(std::next(block_it).base());
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace Profiler {
|
||||
|
||||
ScopedBlock::ScopedBlock(const tracy::SourceLocationData* source_location) {
|
||||
if (tracy::ProfilerAvailable()) {
|
||||
m_zone.emplace(source_location, TRACY_CALLSTACK, true);
|
||||
g_block_stack.push_back(this);
|
||||
}
|
||||
}
|
||||
|
||||
ScopedBlock::~ScopedBlock() {
|
||||
End();
|
||||
}
|
||||
|
||||
void ScopedBlock::End() {
|
||||
if (m_zone.has_value()) {
|
||||
m_zone.reset();
|
||||
RemoveBlock(this);
|
||||
}
|
||||
}
|
||||
|
||||
void EndBlock() {
|
||||
if (!g_block_stack.empty()) {
|
||||
g_block_stack.back()->End();
|
||||
}
|
||||
}
|
||||
|
||||
void SetThreadName(const char* name) {
|
||||
if (tracy::ProfilerAvailable() && name != nullptr) {
|
||||
tracy::SetThreadName(name);
|
||||
|
||||
+4
-29
@@ -4,7 +4,6 @@
|
||||
#include "common/common.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <tracy/Tracy.hpp> // IWYU pragma: export
|
||||
|
||||
namespace profiler::colors {
|
||||
@@ -23,22 +22,6 @@ inline constexpr uint32_t DeepOrangeA200 = 0xff6e40;
|
||||
|
||||
namespace Profiler {
|
||||
|
||||
class ScopedBlock {
|
||||
public:
|
||||
explicit ScopedBlock(const tracy::SourceLocationData* source_location);
|
||||
ScopedBlock(const ScopedBlock&) = delete;
|
||||
ScopedBlock& operator=(const ScopedBlock&) = delete;
|
||||
ScopedBlock(ScopedBlock&&) = delete;
|
||||
ScopedBlock& operator=(ScopedBlock&&) = delete;
|
||||
~ScopedBlock();
|
||||
|
||||
void End();
|
||||
|
||||
private:
|
||||
std::optional<tracy::ScopedZone> m_zone;
|
||||
};
|
||||
|
||||
void EndBlock();
|
||||
void SetThreadName(const char* name);
|
||||
|
||||
void Initialize();
|
||||
@@ -66,19 +49,11 @@ struct Lifecycle {
|
||||
kyty_profiler_source_location_, line) {name, TracyFunction, TracyFile, \
|
||||
static_cast<uint32_t>(line), \
|
||||
KYTY_PROFILER_COLOR_OR_DEFAULT(0, __VA_ARGS__)}; \
|
||||
Profiler::ScopedBlock KYTY_PROFILER_CONCAT(kyty_profiler_block_, line)( \
|
||||
&KYTY_PROFILER_CONCAT(kyty_profiler_source_location_, line))
|
||||
tracy::ScopedZone KYTY_PROFILER_CONCAT(kyty_profiler_block_, line)( \
|
||||
&KYTY_PROFILER_CONCAT(kyty_profiler_source_location_, line), \
|
||||
TRACY_CALLSTACK, tracy::ProfilerAvailable())
|
||||
|
||||
#define KYTY_PROFILER_FUNCTION(...) KYTY_PROFILER_FUNCTION_IMPL(__LINE__ __VA_OPT__(, ) __VA_ARGS__)
|
||||
#define KYTY_PROFILER_FUNCTION_IMPL(line, ...) \
|
||||
static constexpr tracy::SourceLocationData KYTY_PROFILER_CONCAT( \
|
||||
kyty_profiler_source_location_, line) {nullptr, TracyFunction, TracyFile, \
|
||||
static_cast<uint32_t>(line), \
|
||||
KYTY_PROFILER_COLOR_OR_DEFAULT(0, __VA_ARGS__)}; \
|
||||
Profiler::ScopedBlock KYTY_PROFILER_CONCAT(kyty_profiler_block_, line)( \
|
||||
&KYTY_PROFILER_CONCAT(kyty_profiler_source_location_, line))
|
||||
|
||||
#define KYTY_PROFILER_END_BLOCK Profiler::EndBlock()
|
||||
#define KYTY_PROFILER_FUNCTION(...) KYTY_PROFILER_BLOCK(nullptr __VA_OPT__(, ) __VA_ARGS__)
|
||||
|
||||
#define KYTY_PROFILER_THREAD(name) Profiler::SetThreadName(name)
|
||||
|
||||
|
||||
+2
-40
@@ -2,7 +2,6 @@
|
||||
|
||||
#include "common/assert.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <cerrno>
|
||||
#include <chrono> // IWYU pragma: keep
|
||||
@@ -26,8 +25,7 @@
|
||||
#ifdef KYTY_WIN_CS
|
||||
#include <windows.h> // IWYU pragma: keep
|
||||
// IWYU pragma: no_include <winbase.h>
|
||||
constexpr DWORD KYTY_CS_SPIN_COUNT = 4000;
|
||||
constexpr uint64_t KYTY_SLEEP_SPIN_LIMIT_100NS = 500; // 50 us
|
||||
constexpr DWORD KYTY_CS_SPIN_COUNT = 4000;
|
||||
|
||||
#ifndef CREATE_WAITABLE_TIMER_HIGH_RESOLUTION
|
||||
#define CREATE_WAITABLE_TIMER_HIGH_RESOLUTION 0x00000002
|
||||
@@ -38,30 +36,6 @@ static void SleepHighResolution100ns(uint64_t units_100ns) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Keep spinning only where a kernel transition is
|
||||
// likely to cost more than the requested delay; ordinary millisecond sleeps use the
|
||||
// per-thread high-resolution waitable timer below.
|
||||
if (units_100ns <= KYTY_SLEEP_SPIN_LIMIT_100NS) {
|
||||
LARGE_INTEGER frequency {};
|
||||
LARGE_INTEGER start {};
|
||||
if (QueryPerformanceFrequency(&frequency) != 0 && QueryPerformanceCounter(&start) != 0 &&
|
||||
frequency.QuadPart > 0) {
|
||||
const auto wait_ticks =
|
||||
static_cast<LONGLONG>((static_cast<long double>(units_100ns) *
|
||||
static_cast<long double>(frequency.QuadPart)) /
|
||||
10000000.0L);
|
||||
const auto deadline = start.QuadPart + std::max<LONGLONG>(wait_ticks, 1);
|
||||
LARGE_INTEGER now {};
|
||||
do {
|
||||
if (QueryPerformanceCounter(&now) == 0) {
|
||||
break;
|
||||
}
|
||||
YieldProcessor();
|
||||
} while (now.QuadPart < deadline);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
thread_local HANDLE timer = CreateWaitableTimerExW(
|
||||
nullptr, nullptr, CREATE_WAITABLE_TIMER_HIGH_RESOLUTION, TIMER_ALL_ACCESS);
|
||||
if (timer == nullptr) {
|
||||
@@ -131,14 +105,13 @@ static SleepConditionVariableCS_func_t ResolveSleepConditionVariableCS() {
|
||||
#endif
|
||||
|
||||
#ifdef KYTY_POSIX_HIGH_RES_SLEEP
|
||||
// Spin for very short waits; use an absolute deadline for longer waits.
|
||||
// An absolute deadline preserves the requested sleep across signal interruptions.
|
||||
static void SleepHighResolutionNanos(uint64_t nanos) {
|
||||
if (nanos == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
constexpr uint64_t NANOS_PER_SEC = 1000000000;
|
||||
constexpr uint64_t SPIN_LIMIT_NS = 50000; // below this a context switch dominates
|
||||
|
||||
timespec deadline {};
|
||||
if (clock_gettime(CLOCK_MONOTONIC, &deadline) != 0) {
|
||||
@@ -150,17 +123,6 @@ static void SleepHighResolutionNanos(uint64_t nanos) {
|
||||
deadline.tv_sec += static_cast<time_t>(target_nsec / NANOS_PER_SEC);
|
||||
deadline.tv_nsec = static_cast<long>(target_nsec % NANOS_PER_SEC);
|
||||
|
||||
if (nanos <= SPIN_LIMIT_NS) {
|
||||
timespec now {};
|
||||
do {
|
||||
if (clock_gettime(CLOCK_MONOTONIC, &now) != 0) {
|
||||
return;
|
||||
}
|
||||
} while (now.tv_sec < deadline.tv_sec ||
|
||||
(now.tv_sec == deadline.tv_sec && now.tv_nsec < deadline.tv_nsec));
|
||||
return;
|
||||
}
|
||||
|
||||
while (clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &deadline, nullptr) == EINTR) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,7 +62,6 @@ public:
|
||||
|
||||
void BufferInit();
|
||||
void BufferFlush();
|
||||
void BufferFlushAndWait();
|
||||
void BufferWait();
|
||||
HW::Context& GetCtx() { return m_ctx; }
|
||||
HW::UserConfig& GetUcfg() { return m_ucfg; }
|
||||
|
||||
@@ -2266,7 +2266,6 @@ KYTY_CP_OP_PARSER(CpOpReleaseMem) {
|
||||
const bool gl2_writeback = ((gcr_cntl & GcrGl2Writeback) != 0);
|
||||
|
||||
auto trigger_interrupt = [&]() {
|
||||
bool queued = false;
|
||||
switch (interrupt_selector) {
|
||||
case 0x00:
|
||||
case 0x03: break;
|
||||
@@ -2274,13 +2273,10 @@ KYTY_CP_OP_PARSER(CpOpReleaseMem) {
|
||||
case 0x02:
|
||||
case 0x04:
|
||||
cp.TriggerEopEventAtEndOfPipe(interrupt_context_id);
|
||||
queued = true;
|
||||
cp.BufferFlush();
|
||||
break;
|
||||
default: EXIT("unknown release_mem interrupt selector\n");
|
||||
}
|
||||
if (queued) {
|
||||
cp.BufferFlush();
|
||||
}
|
||||
};
|
||||
|
||||
if (data_sel == 0 || interrupt_selector == 4) {
|
||||
@@ -2318,7 +2314,9 @@ KYTY_CP_OP_PARSER(CpOpReleaseMem) {
|
||||
cp.WriteAtEndOfPipe32(cache_policy, event_write_dest, eop_event_type, cache_action,
|
||||
event_index, event_source, dst_gpu_addr, static_cast<uint32_t>(value),
|
||||
interrupt_selector, interrupt_context_id);
|
||||
cp.BufferFlush();
|
||||
if (interrupt_selector == 0x01 || interrupt_selector == 0x02) {
|
||||
cp.BufferFlush();
|
||||
}
|
||||
|
||||
return 7;
|
||||
}
|
||||
|
||||
@@ -35,7 +35,6 @@ namespace Libs::Graphics {
|
||||
|
||||
static thread_local CommandProcessor* g_current_processor = nullptr;
|
||||
static thread_local Pm4Execution* g_current_execution = nullptr;
|
||||
static thread_local bool g_gpu_mutex_owned = false;
|
||||
static thread_local bool g_gpu_thread = false;
|
||||
static thread_local GuestGpu* g_gpu_state = nullptr;
|
||||
|
||||
@@ -54,27 +53,6 @@ struct DrawIndexedIndirectArgs {
|
||||
uint32_t start_instance_location;
|
||||
};
|
||||
|
||||
class GpuMutexLock final {
|
||||
public:
|
||||
explicit GpuMutexLock(Common::Mutex& mutex): m_mutex(mutex) {
|
||||
if (g_gpu_mutex_owned) {
|
||||
EXIT("recursive GPU mutex acquisition\n");
|
||||
}
|
||||
g_gpu_mutex_owned = true;
|
||||
m_mutex.Lock();
|
||||
}
|
||||
~GpuMutexLock() {
|
||||
if (!g_gpu_mutex_owned) {
|
||||
EXIT("invalid GPU mutex release\n");
|
||||
}
|
||||
m_mutex.Unlock();
|
||||
g_gpu_mutex_owned = false;
|
||||
}
|
||||
|
||||
private:
|
||||
Common::Mutex& m_mutex;
|
||||
};
|
||||
|
||||
static bool GraphicsRunDebugDumpEnabled() {
|
||||
return Config::GraphicsDebugDumpEnabled() &&
|
||||
Config::GetPrintfDirection() != Config::LogDirection::Silent;
|
||||
@@ -84,6 +62,7 @@ GuestGpu::GuestGpu(RenderContext& renderer): m_renderer(renderer) {
|
||||
EXIT_NOT_IMPLEMENTED(!Common::Thread::IsMainThread());
|
||||
GraphicsInitJmpTables();
|
||||
m_gfx_cp = std::make_unique<CommandProcessor>(renderer, 0);
|
||||
m_gfx_cp->Reset();
|
||||
m_thread = std::jthread(ThreadRun, this);
|
||||
}
|
||||
|
||||
@@ -160,20 +139,16 @@ void GuestGpu::Submit(std::span<const uint32_t> draw_commands,
|
||||
if (draw_commands.empty()) {
|
||||
return;
|
||||
}
|
||||
GpuMutexLock lock(m_submission_mutex);
|
||||
Submission submission;
|
||||
Submission submission;
|
||||
submission.type = SubmissionType::Graphics;
|
||||
submission.queue_id = 0;
|
||||
submission.commands = draw_commands;
|
||||
submission.constant_commands = constant_commands;
|
||||
submission.reset_processor = m_graphics_done;
|
||||
m_graphics_done = false;
|
||||
Enqueue(std::move(submission));
|
||||
}
|
||||
|
||||
void GuestGpu::SubmitCompute(uint32_t queue, std::span<const uint32_t> commands) {
|
||||
EXIT_IF(commands.empty());
|
||||
GpuMutexLock lock(m_submission_mutex);
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(queue < ComputeQueueBase || queue >= ComputeQueueBase + ComputeQueueCount);
|
||||
|
||||
@@ -186,22 +161,21 @@ void GuestGpu::SubmitCompute(uint32_t queue, std::span<const uint32_t> commands)
|
||||
}
|
||||
|
||||
void GuestGpu::SubmitFlipPreparation(uint64_t request_id) {
|
||||
GpuMutexLock lock(m_submission_mutex);
|
||||
Submission submission;
|
||||
Submission submission;
|
||||
submission.type = SubmissionType::FlipPreparation;
|
||||
submission.queue_id = 0;
|
||||
submission.reset_processor = m_graphics_done;
|
||||
submission.flip_request_id = request_id;
|
||||
m_graphics_done = false;
|
||||
Enqueue(std::move(submission));
|
||||
}
|
||||
|
||||
void GuestGpu::Done() {
|
||||
GpuMutexLock lock(m_submission_mutex);
|
||||
if (!IsGpuThread()) {
|
||||
WaitForIdle();
|
||||
}
|
||||
m_graphics_done = true;
|
||||
void GuestGpu::SuspendPoint() {
|
||||
EXIT_IF(IsGpuThread() || CommandScheduler::InDeferredOperation());
|
||||
// Do not hold a queue lock while waiting: asynchronous work may be needed to
|
||||
// finish the preceding graphics frame. The first point returns immediately.
|
||||
m_suspend_point_ready->acquire();
|
||||
Submission submission;
|
||||
submission.type = SubmissionType::SuspendPoint;
|
||||
Enqueue(std::move(submission));
|
||||
m_done_num++;
|
||||
}
|
||||
|
||||
@@ -229,6 +203,9 @@ void CommandProcessor::Reset() {
|
||||
m_context_state_pushed = false;
|
||||
m_index_type_and_size = 0;
|
||||
m_index_buffer_size = 0;
|
||||
m_index_base_addr = 0;
|
||||
m_num_instances = 1;
|
||||
m_predicate_skip = false;
|
||||
m_user_data_marker = HW::UserSgprType::Unknown;
|
||||
m_draw_indirect_args_base_addr = 0;
|
||||
m_dispatch_indirect_args_base_addr = 0;
|
||||
@@ -268,10 +245,6 @@ void CommandProcessor::BufferFlush() {
|
||||
GetScheduler().Flush();
|
||||
}
|
||||
|
||||
void CommandProcessor::BufferFlushAndWait() {
|
||||
GetScheduler().FlushAndWait();
|
||||
}
|
||||
|
||||
void CommandProcessor::BufferWait() {
|
||||
BufferInit();
|
||||
GetScheduler().Finish();
|
||||
@@ -459,6 +432,7 @@ void GuestGpu::Enqueue(Submission submission) {
|
||||
}
|
||||
|
||||
void GuestGpu::WaitForIdle() {
|
||||
EXIT_IF(IsGpuThread() || CommandScheduler::InDeferredOperation());
|
||||
Common::LockGuard lock(m_queue_mutex);
|
||||
while (m_processing || !m_commands.empty() || m_submission_count != 0) {
|
||||
m_idle.Wait(&m_queue_mutex);
|
||||
@@ -564,11 +538,7 @@ void GuestGpu::ThreadRun(void* data) {
|
||||
|
||||
bool GuestGpu::Process(Submission& submission) {
|
||||
const bool first_slice = !submission.started;
|
||||
auto& cp = GetProcessor(submission.queue_id);
|
||||
|
||||
if (first_slice && submission.reset_processor) {
|
||||
cp.Reset();
|
||||
}
|
||||
auto& cp = GetProcessor(submission.queue_id);
|
||||
|
||||
if (first_slice) {
|
||||
submission.started = true;
|
||||
@@ -645,6 +615,13 @@ bool GuestGpu::Process(Submission& submission) {
|
||||
m_renderer.RunGarbageCollector();
|
||||
cp.PrepareCpuFlip(submission.flip_request_id);
|
||||
break;
|
||||
case SubmissionType::SuspendPoint:
|
||||
cp.EmitGlobalBarrier();
|
||||
m_renderer.GetCommandScheduler().DeferPriorityOperation(
|
||||
[ready = m_suspend_point_ready] { ready->release(); });
|
||||
cp.BufferFlush();
|
||||
cp.Reset();
|
||||
break;
|
||||
}
|
||||
|
||||
return complete;
|
||||
@@ -849,9 +826,7 @@ void CommandProcessor::SetPredication(uint32_t condition, uint32_t op, uint32_t
|
||||
}
|
||||
} break;
|
||||
case 0x03:
|
||||
if (wait_op != 0) {
|
||||
BufferFlushAndWait();
|
||||
}
|
||||
// The wait selector applies only to Z-pass query readiness.
|
||||
EXIT_NOT_IMPLEMENTED(address == nullptr);
|
||||
value = *reinterpret_cast<const volatile uint64_t*>(address);
|
||||
break;
|
||||
@@ -1034,9 +1009,6 @@ void CommandProcessor::DispatchDirect(uint32_t thread_group_x, uint32_t thread_g
|
||||
m_sh_ctx.SetCsWaveSize(Pm4::ComputeWaveSize(mode));
|
||||
|
||||
uint32_t frame_num = 0;
|
||||
// uint32_t local_x = 1;
|
||||
// uint32_t local_y = 1;
|
||||
// uint32_t local_z = 1;
|
||||
|
||||
{
|
||||
frame_num = m_renderer.GetGpu().GetFrameNum();
|
||||
@@ -1058,35 +1030,9 @@ void CommandProcessor::DispatchDirect(uint32_t thread_group_x, uint32_t thread_g
|
||||
}
|
||||
}
|
||||
|
||||
const auto& cs = m_sh_ctx.GetCs().cs_regs;
|
||||
// local_x = std::max(cs.num_thread_x, 1u);
|
||||
// local_y = std::max(cs.num_thread_y, 1u);
|
||||
// local_z = std::max(cs.num_thread_z, 1u);
|
||||
m_renderer.GetRenderExecutor().DispatchDirect(m_submit_id, CurrentBuffer(), thread_group_x,
|
||||
thread_group_y, thread_group_z, mode);
|
||||
}
|
||||
|
||||
/*constexpr uint32_t DispatchInitiatorUseThreadDimensions = 1u << 5u;
|
||||
auto group_count = [](uint32_t threads, uint32_t group_size) {
|
||||
return (threads == 0
|
||||
? 0u
|
||||
: (threads + std::max(group_size, 1u) - 1u) / std::max(group_size, 1u));
|
||||
};
|
||||
|
||||
auto groups_x = thread_group_x;
|
||||
auto groups_y = thread_group_y;
|
||||
auto groups_z = thread_group_z;
|
||||
if ((mode & DispatchInitiatorUseThreadDimensions) != 0) {
|
||||
groups_x = group_count(thread_group_x, local_x);
|
||||
groups_y = group_count(thread_group_y, local_y);
|
||||
groups_z = group_count(thread_group_z, local_z);
|
||||
}
|
||||
|
||||
const uint64_t invocations =
|
||||
static_cast<uint64_t>(groups_x) * groups_y * groups_z * local_x * local_y * local_z;
|
||||
if (invocations != 0) {
|
||||
BufferFlushAndWait();
|
||||
}*/
|
||||
}
|
||||
|
||||
void CommandProcessor::DispatchIndirect(uint64_t args_addr, uint32_t mode) {
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include <deque>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <semaphore>
|
||||
#include <span>
|
||||
#include <thread>
|
||||
|
||||
@@ -32,11 +33,14 @@ public:
|
||||
void SendCommandSync(Common::UniqueFunction<void>&& command);
|
||||
|
||||
// Submitted command memory is borrowed and must remain valid until GPU execution completes.
|
||||
void Submit(std::span<const uint32_t> draw_commands,
|
||||
std::span<const uint32_t> constant_commands);
|
||||
void SubmitCompute(uint32_t queue, std::span<const uint32_t> commands);
|
||||
void SubmitFlipPreparation(uint64_t request_id);
|
||||
void Done();
|
||||
void Submit(std::span<const uint32_t> draw_commands,
|
||||
std::span<const uint32_t> constant_commands);
|
||||
void SubmitCompute(uint32_t queue, std::span<const uint32_t> commands);
|
||||
void SubmitFlipPreparation(uint64_t request_id);
|
||||
// Insert an ordered graphics drain; only a previous suspend point can block the caller.
|
||||
void SuspendPoint();
|
||||
// Wait for guest command processing, including all compute queues (not native GPU completion).
|
||||
void WaitForIdle();
|
||||
[[nodiscard]] int GetFrameNum() const;
|
||||
|
||||
[[nodiscard]] static bool IsGpuThread() noexcept;
|
||||
@@ -48,7 +52,7 @@ private:
|
||||
static constexpr uint32_t ComputeQueueBase = 0x20;
|
||||
static constexpr uint32_t QueueCount = 1 + ComputeQueueCount;
|
||||
|
||||
enum class SubmissionType { Graphics, Compute, FlipPreparation };
|
||||
enum class SubmissionType { Graphics, Compute, FlipPreparation, SuspendPoint };
|
||||
|
||||
struct Submission {
|
||||
SubmissionType type = SubmissionType::Graphics;
|
||||
@@ -57,7 +61,6 @@ private:
|
||||
std::span<const uint32_t> constant_commands;
|
||||
Pm4Execution command_execution;
|
||||
Pm4Execution constant_execution;
|
||||
bool reset_processor = false;
|
||||
bool started = false;
|
||||
bool command_complete = false;
|
||||
bool constant_complete = false;
|
||||
@@ -66,14 +69,12 @@ private:
|
||||
};
|
||||
|
||||
void Enqueue(Submission submission);
|
||||
void WaitForIdle();
|
||||
void ProcessCommands();
|
||||
bool Process(Submission& submission);
|
||||
static void ThreadRun(void* data);
|
||||
CommandProcessor& GetProcessor(uint32_t queue_id);
|
||||
|
||||
RenderContext& m_renderer;
|
||||
Common::Mutex m_submission_mutex;
|
||||
Common::Mutex m_queue_mutex;
|
||||
std::mutex m_shutdown_mutex;
|
||||
Common::CondVar m_work_available;
|
||||
@@ -84,10 +85,12 @@ private:
|
||||
uint32_t m_next_queue = 0;
|
||||
uint32_t m_submission_count = 0;
|
||||
bool m_processing = false;
|
||||
bool m_graphics_done = true;
|
||||
bool m_accepting = true;
|
||||
bool m_stopping = false;
|
||||
bool m_shutdown_complete = false;
|
||||
// Completion callbacks can outlive GuestGpu during renderer shutdown.
|
||||
std::shared_ptr<std::binary_semaphore> m_suspend_point_ready =
|
||||
std::make_shared<std::binary_semaphore>(1);
|
||||
|
||||
std::unique_ptr<CommandProcessor> m_gfx_cp;
|
||||
std::array<std::unique_ptr<CommandProcessor>, ComputeQueueCount> m_compute_cp;
|
||||
|
||||
+15
-10
@@ -129,11 +129,16 @@ bool BufferCache::DownloadBufferMemory(Buffer& buffer, uint64_t vaddr, uint64_t
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto [mapped, offset] = m_download_buffer.Map(total_size, 64);
|
||||
auto [mapped, offset] = m_download_buffer.Map(total_size, 64);
|
||||
std::unique_ptr<Buffer> temporary;
|
||||
if (mapped == nullptr) {
|
||||
EXIT("BufferCache: download exceeds 64 MiB staging buffer capacity\n");
|
||||
temporary = std::make_unique<Buffer>(m_graphics, m_scheduler, MemoryUsage::Download, 0,
|
||||
vk::BufferUsageFlagBits::eTransferDst, total_size);
|
||||
mapped = temporary->Mapped().data();
|
||||
} else {
|
||||
m_download_buffer.Commit();
|
||||
}
|
||||
m_download_buffer.Commit();
|
||||
const auto& download = temporary ? *temporary : m_download_buffer;
|
||||
for (auto& copy: copies) {
|
||||
copy.dstOffset += offset;
|
||||
}
|
||||
@@ -152,13 +157,13 @@ bool BufferCache::DownloadBufferMemory(Buffer& buffer, uint64_t vaddr, uint64_t
|
||||
native.pipelineBarrier(vk::PipelineStageFlagBits::eAllCommands,
|
||||
vk::PipelineStageFlagBits::eTransfer, {}, 0, nullptr, 1, &before, 0,
|
||||
nullptr);
|
||||
native.copyBuffer(buffer.Handle(), m_download_buffer.Handle(),
|
||||
native.copyBuffer(buffer.Handle(), download.Handle(),
|
||||
static_cast<uint32_t>(copies.size()), copies.data());
|
||||
|
||||
auto after = before;
|
||||
after.srcAccessMask = vk::AccessFlagBits::eTransferWrite;
|
||||
after.dstAccessMask = vk::AccessFlagBits::eHostRead;
|
||||
after.buffer = m_download_buffer.Handle();
|
||||
after.buffer = download.Handle();
|
||||
after.offset = offset;
|
||||
after.size = total_size;
|
||||
native.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer,
|
||||
@@ -166,8 +171,8 @@ bool BufferCache::DownloadBufferMemory(Buffer& buffer, uint64_t vaddr, uint64_t
|
||||
vk::PipelineStageFlagBits::eHost,
|
||||
{}, 0, nullptr, 1, &after, 0, nullptr);
|
||||
m_scheduler.DeferPriorityOperation([this, mapped, offset, total_size, buffer_address,
|
||||
copies = std::move(copies)] {
|
||||
m_download_buffer.Invalidate(offset, total_size);
|
||||
copies = std::move(copies), owner = std::move(temporary)] {
|
||||
(owner ? *owner : m_download_buffer).Invalidate(offset, total_size);
|
||||
for (const auto& copy: copies) {
|
||||
Libs::LibKernel::Memory::WriteBacking(buffer_address + copy.srcOffset,
|
||||
mapped + (copy.dstOffset - offset), copy.size);
|
||||
@@ -454,7 +459,8 @@ std::pair<Buffer*, uint64_t> BufferCache::ObtainBuffer(uint64_t vaddr, uint64_t
|
||||
const auto alignment = std::max<uint64_t>(
|
||||
m_graphics.physical_device_properties.limits.minUniformBufferOffsetAlignment, 1);
|
||||
auto [mapped, offset] = m_stream_buffer.Map(size, alignment, false);
|
||||
if (mapped != nullptr && Libs::LibKernel::Memory::TryReadBacking(vaddr, mapped, size)) {
|
||||
if (mapped != nullptr) {
|
||||
std::memcpy(mapped, reinterpret_cast<const void*>(vaddr), size);
|
||||
m_stream_buffer.Commit();
|
||||
return {&m_stream_buffer, offset};
|
||||
}
|
||||
@@ -490,8 +496,7 @@ std::pair<Buffer*, uint64_t> BufferCache::ObtainBufferForImage(uint64_t vaddr, u
|
||||
}
|
||||
|
||||
auto [staging, stage_offset] = m_staging_buffer.Map(size, 16);
|
||||
if (staging == nullptr || (!Libs::LibKernel::Memory::TryReadBacking(vaddr, staging, size) &&
|
||||
!Libs::LibKernel::Memory::TryReadPrtBacking(vaddr, staging, size))) {
|
||||
if (staging == nullptr || !Libs::LibKernel::Memory::TryReadSparseBacking(vaddr, staging, size)) {
|
||||
EXIT("BufferCache: failed to read mapped guest image backing\n");
|
||||
}
|
||||
m_staging_buffer.Commit();
|
||||
|
||||
+5
-2
@@ -123,8 +123,11 @@ vk::Sampler SamplerCache::GetSampler(const ShaderSamplerResource& r, bool intege
|
||||
sampler_info.addressModeU = to_vk_address_mode(r.ClampX());
|
||||
sampler_info.addressModeV = to_vk_address_mode(r.ClampY());
|
||||
sampler_info.addressModeW = to_vk_address_mode(r.ClampZ());
|
||||
sampler_info.mipLodBias =
|
||||
static_cast<float>(static_cast<int16_t>((r.LodBias() ^ 0x2000u) - 0x2000u)) / 256.0f;
|
||||
const auto max_lod_bias = m_graphics.GetPhysicalDeviceProperties().limits.maxSamplerLodBias;
|
||||
sampler_info.mipLodBias = std::clamp(
|
||||
static_cast<float>(static_cast<int16_t>((r.LodBias() ^ 0x2000u) - 0x2000u)) / 256.0f,
|
||||
-max_lod_bias, max_lod_bias
|
||||
);
|
||||
sampler_info.anisotropyEnable = (aniso ? VK_TRUE : VK_FALSE);
|
||||
sampler_info.maxAnisotropy = aniso_ratio;
|
||||
sampler_info.compareEnable = (r.DepthCompareFunc() != 0 ? VK_TRUE : VK_FALSE);
|
||||
|
||||
+8
-13
@@ -222,14 +222,6 @@ TextureCache::BindingType TextureCache::UploadBinding(const Image& image) {
|
||||
return image.usage.storage ? BindingType::Storage : BindingType::Texture;
|
||||
}
|
||||
|
||||
bool TextureCache::SafeToDownload(const Image& image) {
|
||||
if (!image.SafeToDownload()) {
|
||||
return false;
|
||||
}
|
||||
const auto range = image.info.data;
|
||||
return !m_buffer_cache.HasGpuDirtyBytes(range.address, range.size);
|
||||
}
|
||||
|
||||
ImageId TextureCache::InsertImage(const ImageInfo& info) {
|
||||
const auto id = m_slot_images.insert(m_graphics, m_scheduler, info);
|
||||
if (!info.data.Empty()) {
|
||||
@@ -1360,7 +1352,7 @@ ImageId TextureCache::FindImageFromRange(uint64_t address, uint64_t size, bool e
|
||||
if (ensure_valid && owner->depth_id) {
|
||||
owner = m_slot_images.try_get(owner->depth_id);
|
||||
}
|
||||
if (owner == nullptr || (ensure_valid && !SafeToDownload(*owner))) {
|
||||
if (owner == nullptr || (ensure_valid && !owner->SafeToDownload())) {
|
||||
continue;
|
||||
}
|
||||
matches.push_back(id);
|
||||
@@ -1751,7 +1743,7 @@ bool BufferCache::SynchronizeBufferFromImage(Buffer& buffer, uint64_t vaddr, uin
|
||||
std::scoped_lock lock {m_texture_cache.m_lock};
|
||||
auto& image = m_texture_cache.m_slot_images[selected];
|
||||
// The GPU thread owns image retirement; CPU invalidation can dirty this image after lookup.
|
||||
if (!m_texture_cache.SafeToDownload(image)) {
|
||||
if (!image.SafeToDownload()) {
|
||||
return false;
|
||||
}
|
||||
if (!buffer.IsInBounds(image.info.data.address, 1)) {
|
||||
@@ -1814,7 +1806,7 @@ bool TextureCache::DownloadImageMemory(ImageId id) {
|
||||
return false;
|
||||
}
|
||||
auto transfer = BuildDownload(image);
|
||||
if (!transfer.valid || !SafeToDownload(image)) {
|
||||
if (!transfer.valid || !image.SafeToDownload()) {
|
||||
return false;
|
||||
}
|
||||
const auto range = image.info.data;
|
||||
@@ -1875,7 +1867,10 @@ bool TextureCache::IsRegionGpuModified(uint64_t address, uint64_t size) {
|
||||
std::scoped_lock lock {m_lock};
|
||||
for (const auto id: FindImagesInRegion(address, size, false)) {
|
||||
const auto& image = m_slot_images[id];
|
||||
if (!image.depth_id && image.IsGpuModified()) {
|
||||
// PPSA17168: S_LOAD_DWORD reads shader data at an address overlapping an old
|
||||
// render target whose memory the CPU has reused. The cached image still retains
|
||||
// its earlier GPU-modified flag.
|
||||
if (!image.depth_id && image.IsGpuModified() && !image.IsDefinitelyCpuDirty()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -2001,7 +1996,7 @@ void TextureCache::RunGarbageCollector() {
|
||||
continue;
|
||||
}
|
||||
if (owner->IsGpuModified()) {
|
||||
const bool safe = SafeToDownload(*owner);
|
||||
const bool safe = owner->SafeToDownload();
|
||||
if (safe && owner->info.IsTiled()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -128,7 +128,6 @@ private:
|
||||
[[nodiscard]] static bool SameBacking(const ImageInfo& cached, const ImageInfo& requested,
|
||||
bool exact_format);
|
||||
[[nodiscard]] static BindingType UploadBinding(const Image& image);
|
||||
[[nodiscard]] bool SafeToDownload(const Image& image);
|
||||
|
||||
// Caller holds m_lock; it also serializes the per-image query epoch.
|
||||
[[nodiscard]] ImageIds FindImagesInRegion(uint64_t address, uint64_t size,
|
||||
|
||||
@@ -21,8 +21,6 @@
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
static std::atomic<uint32_t> g_render_color_log_count = 0;
|
||||
|
||||
static bool DccAlphaOnMsb(const HW::ColorInfo& info) {
|
||||
switch (info.format) {
|
||||
case Prospero::ChannelLayout::k10_10_10_2:
|
||||
@@ -262,21 +260,7 @@ void RenderExecutor::ResolveRenderColorTarget(CommandBuffer& buffer, RenderColor
|
||||
EXIT("render-target backing range is invalid\n");
|
||||
}
|
||||
|
||||
const vk::Extent2D view_extent = {std::max(width >> rt.view.current_mip_level, 1u),
|
||||
std::max(height >> rt.view.current_mip_level, 1u)};
|
||||
|
||||
auto decision_log_id = g_render_color_log_count.fetch_add(1);
|
||||
if (decision_log_id < 128) {
|
||||
LOGF("RenderColorTarget: slot=%" PRIu32 " addr=0x%010" PRIx64 " size=0x%016" PRIx64
|
||||
" extent=%ux%ux%u view_mip=%u view_extent=%ux%u levels=%u pitch=%u"
|
||||
" fmt=0x%08" PRIx32 " nfmt=0x%08" PRIx32 " order=0x%08" PRIx32 " samples=%u tile=%s\n",
|
||||
rt_slot, rt.base.addr, backing_size, width, height, depth, rt.view.current_mip_level,
|
||||
view_extent.width, view_extent.height, levels, pitch,
|
||||
static_cast<uint32_t>(rt.info.format), static_cast<uint32_t>(rt.info.channel_type),
|
||||
static_cast<uint32_t>(rt.info.channel_order), samples, tile ? "tiled" : "linear");
|
||||
}
|
||||
|
||||
TextureCache::ImageDesc desc {};
|
||||
auto& desc = r.desc;
|
||||
desc.type = TextureCache::BindingType::RenderTarget;
|
||||
desc.info.data = {rt.base.addr, backing_size};
|
||||
desc.info.pixel_format = target_format.format;
|
||||
@@ -348,7 +332,6 @@ void RenderExecutor::ResolveRenderColorTarget(CommandBuffer& buffer, RenderColor
|
||||
desc.view_info.layer_count = view.layer_count;
|
||||
desc.view_info.usage = vk::ImageUsageFlagBits::eColorAttachment;
|
||||
auto& texture_cache = m_context.GetTextureCache();
|
||||
r.desc = std::move(desc);
|
||||
r.guest_mip_level = rt.view.current_mip_level;
|
||||
r.guest_array_layer = view.base_layer;
|
||||
r.image_id = texture_cache.FindImage(r.desc, exact_format);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "graphics/host_gpu/renderer/image/image.h"
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/profiler.h"
|
||||
#include "graphics/host_gpu/renderer/cache/streamBuffer.h"
|
||||
#include "graphics/host_gpu/renderer/commandScheduler.h"
|
||||
@@ -10,7 +11,9 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
#include <fmt/format.h>
|
||||
#include <xxhash.h>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
@@ -48,12 +51,29 @@ namespace {
|
||||
}
|
||||
|
||||
[[nodiscard]] vk::ImageUsageFlags ImageUsageFlags(GraphicContext& graphics, const ImageInfo& info) {
|
||||
auto usage = vk::ImageUsageFlagBits::eTransferSrc | vk::ImageUsageFlagBits::eTransferDst;
|
||||
if (info.IsBlock()) {
|
||||
return vk::ImageUsageFlagBits::eTransferSrc | vk::ImageUsageFlagBits::eTransferDst |
|
||||
vk::ImageUsageFlagBits::eSampled;
|
||||
usage |= vk::ImageUsageFlagBits::eSampled;
|
||||
if (graphics.supports_block_texel_view) {
|
||||
const auto storage = usage | vk::ImageUsageFlagBits::eStorage;
|
||||
if (graphics.GetImageFormatProperties(info.pixel_format, HostImageType(info.type),
|
||||
vk::ImageTiling::eOptimal, storage,
|
||||
ImageCreateFlags(graphics, info),
|
||||
nullptr) == vk::Result::eSuccess) {
|
||||
usage = storage;
|
||||
} else {
|
||||
static std::atomic_flag warned = ATOMIC_FLAG_INIT;
|
||||
if (!warned.test_and_set(std::memory_order_relaxed)) {
|
||||
Log::WriteToConsoleAndLog(fmt::format(
|
||||
"Warning: format {} does not support storage access; block-compressed "
|
||||
"textures written by the guest will not render.\n",
|
||||
vk::to_string(info.pixel_format)));
|
||||
}
|
||||
}
|
||||
}
|
||||
return usage;
|
||||
}
|
||||
const auto properties = graphics.GetFormatProperties(info.pixel_format);
|
||||
auto usage = vk::ImageUsageFlagBits::eTransferSrc | vk::ImageUsageFlagBits::eTransferDst;
|
||||
if (HasFormatFeature(properties, vk::FormatFeatureFlagBits::eSampledImage)) {
|
||||
usage |= vk::ImageUsageFlagBits::eSampled;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
#include "graphics/host_gpu/renderer/pipeline/blendMapping.h"
|
||||
|
||||
#include "graphics/guest_gpu/hardwareContext.h"
|
||||
|
||||
#include <initializer_list>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
bool BlendFactorIsDualSource(uint8_t factor) {
|
||||
return factor >= static_cast<uint8_t>(Prospero::BlendFactor::kSrc1Color) &&
|
||||
factor <= static_cast<uint8_t>(Prospero::BlendFactor::kOneMinusSrc1Alpha);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
bool BlendFactorIsConstantColor(uint8_t factor) {
|
||||
return factor == static_cast<uint8_t>(Prospero::BlendFactor::kConstantColor) ||
|
||||
factor == static_cast<uint8_t>(Prospero::BlendFactor::kOneMinusConstantColor);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
BlendMappingSupport ClassifyBlendMapping(const HW::BlendControl& blend,
|
||||
const Prospero::ColorComponentMapping& mapping) {
|
||||
// Color constants are not swizzled with the exports; scalar constant alpha is unaffected.
|
||||
if (!mapping.IsIdentity() && (BlendFactorIsConstantColor(blend.color_srcblend) ||
|
||||
BlendFactorIsConstantColor(blend.color_destblend))) {
|
||||
return BlendMappingSupport::Unsupported;
|
||||
}
|
||||
if (mapping.Map(3) == 3u) {
|
||||
return BlendMappingSupport::Direct;
|
||||
}
|
||||
// Moving alpha requires the same equation for all channels.
|
||||
if (blend.separate_alpha_blend && (blend.alpha_srcblend != blend.color_srcblend ||
|
||||
blend.alpha_destblend != blend.color_destblend ||
|
||||
blend.alpha_comb_fcn != blend.color_comb_fcn)) {
|
||||
return BlendMappingSupport::Unsupported;
|
||||
}
|
||||
auto support = BlendMappingSupport::Direct;
|
||||
for (const auto factor: {blend.color_srcblend, blend.color_destblend}) {
|
||||
if (BlendFactorIsDualSource(factor)) {
|
||||
return BlendMappingSupport::Unsupported;
|
||||
}
|
||||
switch (static_cast<Prospero::BlendFactor>(factor)) {
|
||||
case Prospero::BlendFactor::kSrcAlpha:
|
||||
case Prospero::BlendFactor::kOneMinusSrcAlpha:
|
||||
support = BlendMappingSupport::SourceAlpha;
|
||||
break;
|
||||
case Prospero::BlendFactor::kDstAlpha:
|
||||
case Prospero::BlendFactor::kOneMinusDstAlpha:
|
||||
case Prospero::BlendFactor::kSrcAlphaSaturate: return BlendMappingSupport::Unsupported;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
return support;
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
@@ -0,0 +1,27 @@
|
||||
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_PIPELINE_BLENDMAPPING_H_
|
||||
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_PIPELINE_BLENDMAPPING_H_
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
namespace HW {
|
||||
struct BlendControl;
|
||||
}
|
||||
namespace Prospero {
|
||||
struct ColorComponentMapping;
|
||||
}
|
||||
|
||||
enum class BlendMappingSupport {
|
||||
Direct,
|
||||
SourceAlpha, // Requires logical alpha in the second blend source.
|
||||
Unsupported,
|
||||
};
|
||||
|
||||
bool BlendFactorIsDualSource(uint8_t factor);
|
||||
BlendMappingSupport ClassifyBlendMapping(const HW::BlendControl& blend,
|
||||
const Prospero::ColorComponentMapping& mapping);
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_PIPELINE_BLENDMAPPING_H_
|
||||
@@ -91,19 +91,6 @@ vk::DescriptorImageInfo MakeImageInfo(const TextureBinding& texture, uint32_t el
|
||||
return {nullptr, view, texture.layout};
|
||||
}
|
||||
|
||||
static const char* ShaderStageResourceName(ShaderType stage) {
|
||||
switch (stage) {
|
||||
case ShaderType::Vertex: return "Vertex";
|
||||
case ShaderType::Mesh: return "Mesh";
|
||||
case ShaderType::Local: return "Local";
|
||||
case ShaderType::TessellationControl: return "Hull";
|
||||
case ShaderType::TessellationEvaluation: return "Domain";
|
||||
case ShaderType::Pixel: return "Pixel";
|
||||
case ShaderType::Compute: return "Compute";
|
||||
default: return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
static Prospero::ImageType TextureType(const ShaderTextureResource& descriptor) {
|
||||
const auto type = descriptor.Type();
|
||||
return type == Prospero::ImageType::kCube ? Prospero::ImageType::kColor2DArray : type;
|
||||
@@ -126,8 +113,7 @@ static bool IsMultisampledTexture(Prospero::ImageType type) {
|
||||
|
||||
static vk::DescriptorBufferInfo
|
||||
NativeStorageBuffer(RenderContext& context, const PreparedBindings::BufferSource& source,
|
||||
const ShaderRecompiler::IR::BufferResource& resource, ShaderType stage,
|
||||
uint32_t slot, uint32_t& buffer_offset) {
|
||||
const ShaderRecompiler::IR::BufferResource& resource, uint32_t& buffer_offset) {
|
||||
buffer_offset = 0;
|
||||
|
||||
const auto& [address, size, id] = source;
|
||||
@@ -152,16 +138,6 @@ NativeStorageBuffer(RenderContext& context, const PreparedBindings::BufferSource
|
||||
if (resource.written) {
|
||||
context.GetTextureCache().InvalidateMemoryFromGPU(address, size);
|
||||
}
|
||||
const char* access = "Read";
|
||||
if (resource.written && resource.read) {
|
||||
access = "ReadWrite";
|
||||
} else if (resource.written) {
|
||||
access = "Write";
|
||||
}
|
||||
SetVulkanObjectNameF(
|
||||
graphics.device, result.buffer,
|
||||
"Kyty.{}.StorageBuffer[slot={} guest=0x{:016x} size=0x{:x} access={} formatted={}]",
|
||||
ShaderStageResourceName(stage), slot, address, size, access, resource.formatted);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -874,7 +850,7 @@ void RenderExecutor::RebindBuffers(PreparedBindings& prepared) {
|
||||
uint32_t buffer_offset = 0;
|
||||
prepared.buffers.push_back(NativeStorageBuffer(m_context, prepared.buffer_sources[i],
|
||||
program.info.buffers[resource],
|
||||
program.stage, resource, buffer_offset));
|
||||
buffer_offset));
|
||||
pack_memory_offset(i, buffer_offset);
|
||||
}
|
||||
if (ShaderRecompiler::IR::FindBinding(
|
||||
|
||||
@@ -5,11 +5,13 @@
|
||||
#include "common/file.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/profiler.h"
|
||||
#include "common/threads.h"
|
||||
#include "graphics/guest_gpu/hardwareContext.h"
|
||||
#include "graphics/host_gpu/renderer/colorRenderTarget.h"
|
||||
#include "graphics/host_gpu/renderer/debug.h"
|
||||
#include "graphics/host_gpu/renderer/depthRenderTarget.h"
|
||||
#include "graphics/host_gpu/renderer/image/imageView.h"
|
||||
#include "graphics/host_gpu/renderer/pipeline/blendMapping.h"
|
||||
#include "graphics/host_gpu/renderer/render.h"
|
||||
#include "graphics/host_gpu/renderer/renderContext.h"
|
||||
#include "graphics/shader/recompiler/ShaderRecompiler.h"
|
||||
@@ -165,6 +167,32 @@ bool ValidateShaderSpirv(const char* label, uint64_t shader_hash,
|
||||
|
||||
} // namespace
|
||||
|
||||
std::size_t PipelineCache::GraphicsPipelineKeyHash::operator()(const GraphicsPipelineKey& key) const {
|
||||
std::size_t hash = 0;
|
||||
PipelineKeyHash::Mix(hash, key.rendering.color_count);
|
||||
for (uint32_t i = 0; i < key.rendering.color_count; i++) {
|
||||
PipelineKeyHash::Mix(hash, static_cast<uint32_t>(key.rendering.color_formats[i]));
|
||||
}
|
||||
PipelineKeyHash::Mix(hash, static_cast<uint32_t>(key.rendering.depth_format));
|
||||
PipelineKeyHash::Mix(hash, static_cast<uint32_t>(key.rendering.stencil_format));
|
||||
for (const auto id: key.vertex_shader_ids) {
|
||||
PipelineKeyHash::Mix(hash, id);
|
||||
}
|
||||
PipelineKeyHash::Mix(hash, key.ps_shader_id);
|
||||
PipelineKeyHash::Mix(hash, key.vertex_input.binding_count);
|
||||
for (uint32_t i = 0; i < key.vertex_input.binding_count; i++) {
|
||||
PipelineKeyHash::Mix(hash, key.vertex_input.bindings[i].stride);
|
||||
PipelineKeyHash::Mix(hash, key.vertex_input.bindings[i].instance);
|
||||
}
|
||||
PipelineKeyHash::Mix(hash, key.vertex_input.attribute_count);
|
||||
for (uint32_t i = 0; i < key.vertex_input.attribute_count; i++) {
|
||||
PipelineKeyHash::Mix(hash, key.vertex_input.attributes[i].offset);
|
||||
PipelineKeyHash::Mix(hash, key.vertex_input.attributes[i].binding);
|
||||
}
|
||||
PipelineKeyHash::Mix(hash, XXH3_64bits(&key.static_params, sizeof(key.static_params)));
|
||||
return hash;
|
||||
}
|
||||
|
||||
struct PipelineCache::ProgramCache {
|
||||
struct ProgramKey {
|
||||
ShaderType stage = ShaderType::Unknown;
|
||||
@@ -206,12 +234,12 @@ struct PipelineCache::ProgramCache {
|
||||
PipelineKeyHash::Mix(hash, key.code_size);
|
||||
PipelineKeyHash::Mix(hash, key.static_state.size());
|
||||
// Bucket same-shape static variants by source. ProgramKey equality performs the one
|
||||
// exact state comparison needed on a stable hit without hashing up to 429 words first.
|
||||
// exact state comparison needed on a stable hit without hashing the full state first.
|
||||
return hash;
|
||||
}
|
||||
};
|
||||
|
||||
static constexpr std::size_t MaxStaticKeyWords = 13 + ShaderVertexInputInfo::RES_MAX * 13;
|
||||
static constexpr std::size_t MaxStaticKeyWords = 32 + ShaderVertexInputInfo::RES_MAX * 6;
|
||||
|
||||
Permutation CompilePermutation(const char* stage_name,
|
||||
const ShaderRecompiler::CompileOptions& options,
|
||||
@@ -492,7 +520,6 @@ void PipelineCache::InitializeDriverCache() {
|
||||
}
|
||||
|
||||
void PipelineCache::Save() {
|
||||
Common::LockGuard lock(m_mutex);
|
||||
if (m_driver_cache == nullptr) {
|
||||
return;
|
||||
}
|
||||
@@ -583,21 +610,30 @@ 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_params = PrepareProgram(pixel_regs, sh, target_export_mapping, pixel_info);
|
||||
const auto& blend = context.GetBlendControl(0);
|
||||
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))));
|
||||
(BlendFactorIsDualSource(blend.color_srcblend) ||
|
||||
BlendFactorIsDualSource(blend.color_destblend) ||
|
||||
(blend.separate_alpha_blend && (BlendFactorIsDualSource(blend.alpha_srcblend) ||
|
||||
BlendFactorIsDualSource(blend.alpha_destblend))));
|
||||
if (pixel_info.dual_source_blending) {
|
||||
// MRT1 supplies a second blend source for the same render target as MRT0.
|
||||
// MRT1 supplies the second blend source for target 0.
|
||||
pixel_info.target_output_mode[1] = pixel_info.target_output_mode[0];
|
||||
pixel_info.target_export_mapping[1] = pixel_info.target_export_mapping[0];
|
||||
} else if (blend.enable && !context.GetRenderTarget(0).info.blend_bypass &&
|
||||
pixel_info.target_output_mode[0] != 0 && pixel_info.target_output_mode[0] != 7 &&
|
||||
std::all_of(std::begin(pixel_info.target_output_mode) + 1,
|
||||
std::end(pixel_info.target_output_mode),
|
||||
[](uint8_t mode) { return mode == 0; }) &&
|
||||
ClassifyBlendMapping(blend, pixel_info.target_export_mapping[0]) ==
|
||||
BlendMappingSupport::SourceAlpha) {
|
||||
// Preserve logical alpha when the export mapping moves it.
|
||||
pixel_info.alpha_blend_source_remap = true;
|
||||
pixel_info.dual_source_blending = true;
|
||||
pixel_info.target_output_mode[1] = pixel_info.target_output_mode[0];
|
||||
pixel_info.target_export_mapping[1] = {};
|
||||
}
|
||||
}
|
||||
if (context.GetClipControl().clip_disable) {
|
||||
@@ -614,7 +650,6 @@ PipelineCache::GraphicsPrograms PipelineCache::GetGraphicsPrograms(
|
||||
static_cast<float>(std::min(limits.maxViewportDimensions[1], 16384u)) * 0.5f;
|
||||
clip.enabled = true;
|
||||
}
|
||||
Common::LockGuard lock(m_mutex);
|
||||
uint32_t push_data_cursor =
|
||||
mesh_active ? ShaderRecompiler::IR::PushData::MeshDrawDwordCount : 0;
|
||||
GraphicsPrograms result;
|
||||
@@ -632,7 +667,6 @@ ShaderProgram PipelineCache::GetComputeProgram(const HW::ComputeShaderInfo& regs
|
||||
ShaderComputeInputInfo& input_info) {
|
||||
input_info.host_subgroup_size = m_graphics.SupportsComputeWave64() ? 64u : 32u;
|
||||
const auto params = PrepareProgram(regs, sh, input_info);
|
||||
Common::LockGuard lock(m_mutex);
|
||||
uint32_t push_data_cursor = 0;
|
||||
return m_program_cache->Get(params, input_info, push_data_cursor);
|
||||
}
|
||||
@@ -657,7 +691,6 @@ PipelineCache::Pipeline& PipelineCache::GetGraphicsPipeline(
|
||||
EXIT_IF(ps_active && !pixel_program);
|
||||
const auto color_count = static_cast<uint32_t>(colors.size());
|
||||
|
||||
Common::LockGuard lock(m_mutex);
|
||||
auto& ctx = command.GetRegisters();
|
||||
|
||||
const HW::ModeControl& mc = ctx.GetModeControl();
|
||||
@@ -697,7 +730,24 @@ PipelineCache::Pipeline& PipelineCache::GetGraphicsPipeline(
|
||||
static_params.alpha_comb_fcn[slot] = bc.alpha_comb_fcn;
|
||||
static_params.alpha_destblend[slot] = bc.alpha_destblend;
|
||||
static_params.separate_alpha_blend[slot] = bc.separate_alpha_blend;
|
||||
static_params.blend_enable[slot] = bc.enable && !rt.info.blend_bypass;
|
||||
const bool alpha_remap =
|
||||
slot == 0 && ps_input_info != nullptr && ps_input_info->alpha_blend_source_remap;
|
||||
static_params.blend_enable[slot] = bc.enable && !rt.info.blend_bypass;
|
||||
if (static_params.blend_enable[slot] && !alpha_remap &&
|
||||
ClassifyBlendMapping(bc, colors[i].export_mapping) != BlendMappingSupport::Direct) {
|
||||
static_params.blend_enable[slot] = false;
|
||||
static std::atomic_bool warned = false;
|
||||
if (!warned.exchange(true, std::memory_order_relaxed)) {
|
||||
Log::WriteToConsoleAndLog(fmt::format(
|
||||
"Warning: blending disabled for unsupported color mapping "
|
||||
"(slot={} mapping=0x{:02x} color={}/{} alpha={}/{} separate={}).\n",
|
||||
slot, colors[i].export_mapping.packed, bc.color_srcblend, bc.color_destblend,
|
||||
bc.alpha_srcblend, bc.alpha_destblend, bc.separate_alpha_blend ? 1 : 0));
|
||||
}
|
||||
}
|
||||
if (alpha_remap) {
|
||||
static_params.blend_alpha_source_remap = true;
|
||||
}
|
||||
}
|
||||
const bool with_depth =
|
||||
depth.desc.view_info.format != vk::Format::eUndefined && static_cast<bool>(depth.image_id);
|
||||
@@ -761,24 +811,20 @@ PipelineCache::Pipeline& PipelineCache::GetGraphicsPipeline(
|
||||
vs_input_info.resources_num > ShaderVertexInputInfo::RES_MAX);
|
||||
key.vertex_input.binding_count = static_cast<uint8_t>(vs_input_info.buffers_num);
|
||||
key.vertex_input.attribute_count = static_cast<uint8_t>(vs_input_info.resources_num);
|
||||
uint32_t attributes_num = 0;
|
||||
for (int binding = 0; binding < vs_input_info.buffers_num; binding++) {
|
||||
const auto& buffer = vs_input_info.buffers[binding];
|
||||
EXIT_IF(buffer.attr_num < 0 || buffer.attr_num > ShaderVertexInputBuffer::ATTR_MAX);
|
||||
attributes_num += static_cast<uint32_t>(buffer.attr_num);
|
||||
EXIT_IF(attributes_num > static_cast<uint32_t>(vs_input_info.resources_num));
|
||||
key.vertex_input.bindings[binding] = {.stride = buffer.stride,
|
||||
.instance = buffer.fetch_index != 0};
|
||||
for (int attribute = 0; attribute < buffer.attr_num; attribute++) {
|
||||
const auto index = buffer.attr_indices[attribute];
|
||||
EXIT_IF(index < 0 || index >= vs_input_info.resources_num);
|
||||
key.vertex_input.attributes[index] = {
|
||||
.offset = buffer.attr_offsets[attribute],
|
||||
.binding = static_cast<uint8_t>(binding),
|
||||
};
|
||||
}
|
||||
}
|
||||
EXIT_IF(attributes_num != static_cast<uint32_t>(vs_input_info.resources_num));
|
||||
for (int attribute = 0; attribute < vs_input_info.resources_num; attribute++) {
|
||||
const auto binding = vs_input_info.resources_dst[attribute].buffer_index;
|
||||
EXIT_IF(binding < 0 || binding >= vs_input_info.buffers_num);
|
||||
key.vertex_input.attributes[attribute] = {
|
||||
.offset = static_cast<uint32_t>(vs_input_info.resources[attribute].Base48() -
|
||||
vs_input_info.buffers[binding].addr),
|
||||
.binding = static_cast<uint8_t>(binding),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (auto iter = m_graphics_pipelines.find(key); iter != m_graphics_pipelines.end()) {
|
||||
@@ -817,8 +863,6 @@ PipelineCache::GetComputePipeline(const ShaderComputeInputInfo& input_info,
|
||||
|
||||
EXIT_IF(!compute_program);
|
||||
|
||||
Common::LockGuard lock(m_mutex);
|
||||
|
||||
if (auto iter = m_compute_pipelines.find(compute_program.id);
|
||||
iter != m_compute_pipelines.end()) {
|
||||
return *iter->second;
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
#include "common/abi.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/common.h"
|
||||
#include "common/threads.h"
|
||||
#include "graphics/host_gpu/renderer/renderTarget.h"
|
||||
#include "graphics/host_gpu/vulkanCommon.h"
|
||||
#include "graphics/shader/shader.h"
|
||||
@@ -56,6 +55,7 @@ struct PipelineStaticParameters {
|
||||
uint8_t alpha_destblend[RENDER_COLOR_ATTACHMENTS_MAX] = {};
|
||||
bool separate_alpha_blend[RENDER_COLOR_ATTACHMENTS_MAX] = {};
|
||||
bool blend_enable[RENDER_COLOR_ATTACHMENTS_MAX] = {};
|
||||
bool blend_alpha_source_remap = false;
|
||||
|
||||
bool operator==(const PipelineStaticParameters& other) const noexcept;
|
||||
};
|
||||
@@ -65,7 +65,7 @@ struct PipelineStaticParameters {
|
||||
static_assert(std::is_trivially_copyable_v<PipelineStaticParameters>);
|
||||
static_assert(std::is_standard_layout_v<PipelineStaticParameters>);
|
||||
static_assert(alignof(PipelineStaticParameters) == 1);
|
||||
static_assert(sizeof(PipelineStaticParameters) == 125);
|
||||
static_assert(sizeof(PipelineStaticParameters) == 126);
|
||||
|
||||
struct PipelineRenderingState {
|
||||
std::array<vk::Format, RENDER_COLOR_ATTACHMENTS_MAX> color_formats {};
|
||||
@@ -103,6 +103,7 @@ struct ShaderProgram {
|
||||
explicit operator bool() const { return id != 0 && module != nullptr; }
|
||||
};
|
||||
|
||||
// The owning renderer serializes access, including saves while the GPU is running.
|
||||
class PipelineCache {
|
||||
public:
|
||||
explicit PipelineCache(GraphicContext& graphics);
|
||||
@@ -166,45 +167,10 @@ private:
|
||||
hash ^= value + static_cast<std::size_t>(0x9e3779b97f4a7c15ull) + (hash << 6u) +
|
||||
(hash >> 2u);
|
||||
}
|
||||
|
||||
static void MixStaticParams(std::size_t& hash, const PipelineStaticParameters& params) {
|
||||
const auto* bytes = reinterpret_cast<const uint8_t*>(¶ms);
|
||||
for (std::size_t i = 0; i < sizeof(params); i++) {
|
||||
Mix(hash, bytes[i]);
|
||||
}
|
||||
}
|
||||
|
||||
static void MixRendering(std::size_t& hash, const PipelineRenderingState& rendering) {
|
||||
Mix(hash, rendering.color_count);
|
||||
for (uint32_t i = 0; i < rendering.color_count; i++) {
|
||||
Mix(hash, static_cast<uint32_t>(rendering.color_formats[i]));
|
||||
}
|
||||
Mix(hash, static_cast<uint32_t>(rendering.depth_format));
|
||||
Mix(hash, static_cast<uint32_t>(rendering.stencil_format));
|
||||
}
|
||||
};
|
||||
|
||||
struct GraphicsPipelineKeyHash {
|
||||
std::size_t operator()(const GraphicsPipelineKey& key) const {
|
||||
std::size_t hash = 0;
|
||||
PipelineKeyHash::MixRendering(hash, key.rendering);
|
||||
for (const auto id: key.vertex_shader_ids) {
|
||||
PipelineKeyHash::Mix(hash, id);
|
||||
}
|
||||
PipelineKeyHash::Mix(hash, key.ps_shader_id);
|
||||
PipelineKeyHash::Mix(hash, key.vertex_input.binding_count);
|
||||
for (uint32_t i = 0; i < key.vertex_input.binding_count; i++) {
|
||||
PipelineKeyHash::Mix(hash, key.vertex_input.bindings[i].stride);
|
||||
PipelineKeyHash::Mix(hash, key.vertex_input.bindings[i].instance);
|
||||
}
|
||||
PipelineKeyHash::Mix(hash, key.vertex_input.attribute_count);
|
||||
for (uint32_t i = 0; i < key.vertex_input.attribute_count; i++) {
|
||||
PipelineKeyHash::Mix(hash, key.vertex_input.attributes[i].offset);
|
||||
PipelineKeyHash::Mix(hash, key.vertex_input.attributes[i].binding);
|
||||
}
|
||||
PipelineKeyHash::MixStaticParams(hash, key.static_params);
|
||||
return hash;
|
||||
}
|
||||
std::size_t operator()(const GraphicsPipelineKey& key) const;
|
||||
};
|
||||
|
||||
GraphicContext& m_graphics;
|
||||
@@ -214,7 +180,6 @@ private:
|
||||
std::unordered_map<GraphicsPipelineKey, std::unique_ptr<Pipeline>, GraphicsPipelineKeyHash>
|
||||
m_graphics_pipelines;
|
||||
std::unordered_map<uint64_t, std::unique_ptr<Pipeline>> m_compute_pipelines;
|
||||
Common::Mutex m_mutex;
|
||||
|
||||
void InitializeDriverCache();
|
||||
};
|
||||
|
||||
@@ -137,14 +137,17 @@ static void GetInputFormat(const ShaderBufferResource& res, vk::Format& format,
|
||||
}
|
||||
}
|
||||
|
||||
static vk::BlendFactor GetBlendFactor(uint32_t factor) {
|
||||
static vk::BlendFactor GetBlendFactor(uint32_t factor, bool remap_source_alpha) {
|
||||
switch (static_cast<Prospero::BlendFactor>(factor)) {
|
||||
case Prospero::BlendFactor::kZero: return vk::BlendFactor::eZero;
|
||||
case Prospero::BlendFactor::kOne: return vk::BlendFactor::eOne;
|
||||
case Prospero::BlendFactor::kSrcColor: return vk::BlendFactor::eSrcColor;
|
||||
case Prospero::BlendFactor::kOneMinusSrcColor: return vk::BlendFactor::eOneMinusSrcColor;
|
||||
case Prospero::BlendFactor::kSrcAlpha: return vk::BlendFactor::eSrcAlpha;
|
||||
case Prospero::BlendFactor::kOneMinusSrcAlpha: return vk::BlendFactor::eOneMinusSrcAlpha;
|
||||
case Prospero::BlendFactor::kSrcAlpha:
|
||||
return remap_source_alpha ? vk::BlendFactor::eSrc1Color : vk::BlendFactor::eSrcAlpha;
|
||||
case Prospero::BlendFactor::kOneMinusSrcAlpha:
|
||||
return remap_source_alpha ? vk::BlendFactor::eOneMinusSrc1Color
|
||||
: vk::BlendFactor::eOneMinusSrcAlpha;
|
||||
case Prospero::BlendFactor::kDstAlpha: return vk::BlendFactor::eDstAlpha;
|
||||
case Prospero::BlendFactor::kOneMinusDstAlpha: return vk::BlendFactor::eOneMinusDstAlpha;
|
||||
case Prospero::BlendFactor::kDstColor: return vk::BlendFactor::eDstColor;
|
||||
@@ -397,17 +400,20 @@ void CreatePipelineInternal(GraphicContext& graphics, PipelineCache::Pipeline& p
|
||||
color_blend_attachment[i].colorWriteMask =
|
||||
vk::ColorComponentFlags {static_params.color_mask[i]};
|
||||
color_blend_attachment[i].blendEnable = static_params.blend_enable[i] ? VK_TRUE : VK_FALSE;
|
||||
// Only target 0 can use the second blend source carrying logical alpha.
|
||||
const bool remap_source_alpha = i == 0 && static_params.blend_alpha_source_remap;
|
||||
color_blend_attachment[i].srcColorBlendFactor =
|
||||
GetBlendFactor(static_params.color_srcblend[i]);
|
||||
GetBlendFactor(static_params.color_srcblend[i], remap_source_alpha);
|
||||
color_blend_attachment[i].dstColorBlendFactor =
|
||||
GetBlendFactor(static_params.color_destblend[i]);
|
||||
GetBlendFactor(static_params.color_destblend[i], remap_source_alpha);
|
||||
color_blend_attachment[i].colorBlendOp = GetBlendOp(static_params.color_comb_fcn[i]);
|
||||
color_blend_attachment[i].srcAlphaBlendFactor =
|
||||
(static_params.separate_alpha_blend[i] ? GetBlendFactor(static_params.alpha_srcblend[i])
|
||||
: color_blend_attachment[i].srcColorBlendFactor);
|
||||
(static_params.separate_alpha_blend[i]
|
||||
? GetBlendFactor(static_params.alpha_srcblend[i], remap_source_alpha)
|
||||
: color_blend_attachment[i].srcColorBlendFactor);
|
||||
color_blend_attachment[i].dstAlphaBlendFactor =
|
||||
(static_params.separate_alpha_blend[i]
|
||||
? GetBlendFactor(static_params.alpha_destblend[i])
|
||||
? GetBlendFactor(static_params.alpha_destblend[i], remap_source_alpha)
|
||||
: color_blend_attachment[i].dstColorBlendFactor);
|
||||
color_blend_attachment[i].alphaBlendOp =
|
||||
(static_params.separate_alpha_blend[i] ? GetBlendOp(static_params.alpha_comb_fcn[i])
|
||||
|
||||
@@ -249,8 +249,8 @@ static void LogDrawInputState(const CommandBuffer& buffer, const RenderColorInfo
|
||||
for (int bi = 0; bi < vs_input_info.buffers_num; bi++) {
|
||||
const auto& b = vs_input_info.buffers[bi];
|
||||
LOGF("DrawInputState[%u]: vb[%d] addr=0x%010" PRIx64
|
||||
" stride=%u records=%u fetch_index=%u attr_num=%d\n",
|
||||
log_id, bi, b.addr, b.stride, b.num_records, b.fetch_index, b.attr_num);
|
||||
" stride=%u records=%u fetch_index=%u\n",
|
||||
log_id, bi, b.addr, b.stride, b.num_records, b.fetch_index);
|
||||
|
||||
const auto* bytes = reinterpret_cast<const uint8_t*>(b.addr);
|
||||
if (bytes != nullptr && b.stride != 0) {
|
||||
@@ -272,11 +272,13 @@ static void LogDrawInputState(const CommandBuffer& buffer, const RenderColorInfo
|
||||
raw[5], raw[6], raw[7], raw[8], flt[0], flt[1], flt[2], flt[3], flt[4], flt[5],
|
||||
flt[6], flt[7], flt[8]);
|
||||
|
||||
for (int ai = 0; ai < b.attr_num; ai++) {
|
||||
const auto res_index = b.attr_indices[ai];
|
||||
const auto& r = vs_input_info.resources[res_index];
|
||||
const auto& rd = vs_input_info.resources_dst[res_index];
|
||||
const auto offset = b.attr_offsets[ai];
|
||||
for (int ai = 0; ai < vs_input_info.resources_num; ai++) {
|
||||
const auto& r = vs_input_info.resources[ai];
|
||||
const auto& rd = vs_input_info.resources_dst[ai];
|
||||
if (rd.buffer_index != bi) {
|
||||
continue;
|
||||
}
|
||||
const auto offset = static_cast<uint32_t>(r.Base48() - b.addr);
|
||||
if (offset + 4u <= b.stride &&
|
||||
r.Format() == Prospero::BufferFormat::k8_8_8_8UNorm) {
|
||||
uint32_t packed = 0;
|
||||
@@ -296,14 +298,16 @@ static void LogDrawInputState(const CommandBuffer& buffer, const RenderColorInfo
|
||||
}
|
||||
}
|
||||
|
||||
for (int ai = 0; ai < b.attr_num; ai++) {
|
||||
const auto res_index = b.attr_indices[ai];
|
||||
const auto& r = vs_input_info.resources[res_index];
|
||||
const auto& rd = vs_input_info.resources_dst[res_index];
|
||||
LOGF("DrawInputState[%u]: attr[%d] res=%d offset=%u dst=v%d regs=%d fetch_index=%u "
|
||||
for (int ai = 0; ai < vs_input_info.resources_num; ai++) {
|
||||
const auto& r = vs_input_info.resources[ai];
|
||||
const auto& rd = vs_input_info.resources_dst[ai];
|
||||
if (rd.buffer_index != bi) {
|
||||
continue;
|
||||
}
|
||||
LOGF("DrawInputState[%u]: attr[%d] offset=%u dst=v%d regs=%d fetch_index=%u "
|
||||
"sharp=%08" PRIx32 " %08" PRIx32 " %08" PRIx32 " %08" PRIx32 "\n",
|
||||
log_id, ai, res_index, b.attr_offsets[ai], rd.register_start, rd.registers_num,
|
||||
rd.fetch_index, r.fields[0], r.fields[1], r.fields[2], r.fields[3]);
|
||||
log_id, ai, static_cast<uint32_t>(r.Base48() - b.addr), rd.register_start,
|
||||
rd.registers_num, rd.fetch_index, r.fields[0], r.fields[1], r.fields[2], r.fields[3]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -630,19 +634,22 @@ struct PreparedIndexBuffer {
|
||||
vk::IndexType type = vk::IndexType::eUint16;
|
||||
};
|
||||
|
||||
static uint64_t VertexBufferDescriptorSize(const ShaderVertexInputBuffer& buffer,
|
||||
const ShaderVertexInputInfo& info) {
|
||||
static uint64_t VertexBufferDescriptorSize(int binding, const ShaderVertexInputInfo& info) {
|
||||
const auto& buffer = info.buffers[binding];
|
||||
if (buffer.stride != 0 || buffer.num_records == 0) {
|
||||
return static_cast<uint64_t>(buffer.stride) * buffer.num_records;
|
||||
}
|
||||
|
||||
uint64_t size = 0;
|
||||
for (int i = 0; i < buffer.attr_num; i++) {
|
||||
const auto& resource = info.resources[buffer.attr_indices[i]];
|
||||
for (int i = 0; i < info.resources_num; i++) {
|
||||
if (info.resources_dst[i].buffer_index != binding) {
|
||||
continue;
|
||||
}
|
||||
const auto& resource = info.resources[i];
|
||||
// RDNA2 OOB_SELECT=2 only checks NumRecords != 0. A constant attribute still
|
||||
// fetches its entire format; NumRecords is not a byte count in this mode.
|
||||
const uint64_t extent = resource.OutOfBounds() == 2
|
||||
? static_cast<uint64_t>(buffer.attr_offsets[i]) +
|
||||
? resource.Base48() - buffer.addr +
|
||||
ShaderRecompiler::Format::GetFormatInfo(resource.Format()).byte_size
|
||||
: buffer.num_records;
|
||||
size = std::max(size, extent);
|
||||
@@ -679,7 +686,7 @@ static PreparedVertexBuffers AcquireVertexBuffers(CommandBuffer& b
|
||||
uint32_t range_count = 0;
|
||||
for (int i = 0; i < vs_input_info.buffers_num; i++) {
|
||||
const auto& vertex = vs_input_info.buffers[i];
|
||||
const auto size = VertexBufferDescriptorSize(vertex, vs_input_info);
|
||||
const auto size = VertexBufferDescriptorSize(i, vs_input_info);
|
||||
sizes[i] = size;
|
||||
if (size == 0) {
|
||||
continue;
|
||||
@@ -718,9 +725,6 @@ static PreparedVertexBuffers AcquireVertexBuffers(CommandBuffer& b
|
||||
Libs::LibKernel::Memory::ClampRangeSize(range.base_address, range.RequestedSize());
|
||||
range.acquired_end = range.base_address + size;
|
||||
range.binding = cache.ObtainBuffer(range.base_address, size, false);
|
||||
SetVulkanObjectNameF(
|
||||
buffer.GetContext().GetGraphics().device, range.binding.first->Handle(),
|
||||
"Kyty.VertexBufferRange[guest=0x{:016x} size=0x{:x}]", range.base_address, size);
|
||||
}
|
||||
|
||||
// Rebuild slot bindings, offsetting non-empty slots into their acquired merged range.
|
||||
@@ -752,10 +756,6 @@ static PreparedVertexBuffers AcquireVertexBuffers(CommandBuffer& b
|
||||
prepared.buffers[i] = range->binding.first->Handle();
|
||||
prepared.offsets[i] = range->binding.second + vertex.addr - range->base_address;
|
||||
prepared.sizes[i] = std::min(size, range->acquired_end - vertex.addr);
|
||||
SetVulkanObjectNameF(
|
||||
buffer.GetContext().GetGraphics().device, prepared.buffers[i],
|
||||
"Kyty.VertexBuffer[slot={} guest=0x{:016x} size=0x{:x} stride={} records={}]", i,
|
||||
vertex.addr, size, vertex.stride, vertex.num_records);
|
||||
}
|
||||
|
||||
return prepared;
|
||||
@@ -937,17 +937,11 @@ static PreparedIndexBuffer PrepareIndexBuffer(CommandBuffer& buffe
|
||||
auto& stream = buffer.GetContext().GetBufferCache().GetUtilityBuffer(MemoryUsage::Stream);
|
||||
prepared.offset = stream.Copy(source.host_data, source.size, 16);
|
||||
prepared.buffer = stream.Handle();
|
||||
SetVulkanObjectNameF(buffer.GetContext().GetGraphics().device, prepared.buffer,
|
||||
"Kyty.IndexBuffer[guest=transient size=0x{:x} type={}]", source.size,
|
||||
static_cast<uint32_t>(source.type));
|
||||
} else {
|
||||
auto [buffer_ptr, offset] =
|
||||
buffer.GetContext().GetBufferCache().ObtainBuffer(source.address, source.size, false);
|
||||
prepared.buffer = buffer_ptr->Handle();
|
||||
prepared.offset = offset;
|
||||
SetVulkanObjectNameF(buffer.GetContext().GetGraphics().device, prepared.buffer,
|
||||
"Kyty.IndexBuffer[guest=0x{:016x} size=0x{:x} type={}]",
|
||||
source.address, source.size, static_cast<uint32_t>(source.type));
|
||||
}
|
||||
return prepared;
|
||||
}
|
||||
|
||||
@@ -1814,6 +1814,38 @@ KYTY_SYSV_ABI int VideoOutGetOutputStatus(int handle, VideoOutOutputStatus* stat
|
||||
return OK;
|
||||
}
|
||||
|
||||
KYTY_SYSV_ABI int VideoOutAllowOutputResolutionWqhdDetection(int handle) {
|
||||
if (!DriverState().IsOpened(handle)) {
|
||||
return VIDEO_OUT_ERROR_INVALID_HANDLE;
|
||||
}
|
||||
return OK;
|
||||
}
|
||||
|
||||
KYTY_SYSV_ABI int VideoOutVrrPegToFixedRate(int handle, uint64_t arg1, uint64_t arg2) {
|
||||
PRINT_NAME();
|
||||
|
||||
static std::atomic_bool logged {false};
|
||||
if (!logged.exchange(true, std::memory_order_relaxed)) {
|
||||
LOGF("\t handle = %d\n"
|
||||
"\t arg1 = 0x%016" PRIx64 "\n"
|
||||
"\t arg2 = 0x%016" PRIx64 "\n",
|
||||
handle, arg1, arg2);
|
||||
}
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
KYTY_SYSV_ABI int VideoOutVrrUnpegFromFixedRate(int handle) {
|
||||
PRINT_NAME();
|
||||
|
||||
static std::atomic_bool logged {false};
|
||||
if (!logged.exchange(true, std::memory_order_relaxed)) {
|
||||
LOGF("\t handle = %d\n", handle);
|
||||
}
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
static int ValidateOutputConfig(int handle, uint64_t mode, const VideoOutOutputOptions* options,
|
||||
void* reserved_ptr, uint64_t reserved) {
|
||||
if (!DriverState().IsOpened(handle)) {
|
||||
|
||||
@@ -90,6 +90,9 @@ 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 VideoOutAllowOutputResolutionWqhdDetection(int handle);
|
||||
KYTY_SYSV_ABI int VideoOutVrrPegToFixedRate(int handle, uint64_t arg1, uint64_t arg2);
|
||||
KYTY_SYSV_ABI int VideoOutVrrUnpegFromFixedRate(int handle);
|
||||
KYTY_SYSV_ABI int VideoOutInitializeOutputOptions(VideoOutOutputOptions* options);
|
||||
KYTY_SYSV_ABI int VideoOutIsOutputSupported(int handle, uint64_t mode,
|
||||
const VideoOutOutputOptions* options,
|
||||
|
||||
@@ -137,11 +137,13 @@ static uint32_t VulkanFindQueueFamily(vk::PhysicalDevice device, vk::SurfaceKHR
|
||||
return static_cast<uint32_t>(-1);
|
||||
}
|
||||
|
||||
// On failure out_device is null and out_rejections says why each device was skipped.
|
||||
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
|
||||
static void VulkanFindPhysicalDevice(vk::Instance instance, vk::SurfaceKHR surface,
|
||||
const std::vector<const char*>& device_extensions,
|
||||
SurfaceCapabilities& out_capabilities,
|
||||
vk::PhysicalDevice& out_device, uint32_t& out_queue_family) {
|
||||
vk::PhysicalDevice& out_device, uint32_t& out_queue_family,
|
||||
std::string& out_rejections) {
|
||||
EXIT_IF(instance == nullptr);
|
||||
EXIT_IF(surface == nullptr);
|
||||
|
||||
@@ -170,14 +172,22 @@ static void VulkanFindPhysicalDevice(vk::Instance instance, vk::SurfaceKHR surfa
|
||||
vk::PhysicalDeviceProperties device_properties {};
|
||||
device.getProperties(&device_properties);
|
||||
|
||||
const auto reject = [&](const std::string& reason) {
|
||||
LOGF("%s\n", reason.c_str());
|
||||
out_rejections +=
|
||||
skip_device ? "; " : fmt::format("\n {}: ", device_properties.deviceName.data());
|
||||
out_rejections += reason;
|
||||
skip_device = true;
|
||||
};
|
||||
|
||||
LOGF("Vulkan device: %s\n", device_properties.deviceName.data());
|
||||
if (device_properties.apiVersion < VULKAN_TARGET_API_VERSION) {
|
||||
LOGF("Vulkan %u.%u is required, but device supports only %u.%u.%u\n",
|
||||
VK_VERSION_MAJOR(VULKAN_TARGET_API_VERSION),
|
||||
VK_VERSION_MINOR(VULKAN_TARGET_API_VERSION),
|
||||
VK_VERSION_MAJOR(device_properties.apiVersion),
|
||||
VK_VERSION_MINOR(device_properties.apiVersion),
|
||||
VK_VERSION_PATCH(device_properties.apiVersion));
|
||||
reject(fmt::format("Vulkan {}.{} is required, but device supports only {}.{}.{}",
|
||||
VK_VERSION_MAJOR(VULKAN_TARGET_API_VERSION),
|
||||
VK_VERSION_MINOR(VULKAN_TARGET_API_VERSION),
|
||||
VK_VERSION_MAJOR(device_properties.apiVersion),
|
||||
VK_VERSION_MINOR(device_properties.apiVersion),
|
||||
VK_VERSION_PATCH(device_properties.apiVersion)));
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -203,7 +213,7 @@ static void VulkanFindPhysicalDevice(vk::Instance instance, vk::SurfaceKHR surfa
|
||||
fragment_barycentric.pNext = &depth_clip_control;
|
||||
features12.pNext = &fragment_barycentric;
|
||||
#endif
|
||||
features13.pNext = &features12;
|
||||
features13.pNext = &features12;
|
||||
device_features2.pNext = &features13;
|
||||
|
||||
device.getFeatures2(&device_features2);
|
||||
@@ -212,151 +222,82 @@ static void VulkanFindPhysicalDevice(vk::Instance instance, vk::SurfaceKHR surfa
|
||||
|
||||
const auto queue_family = VulkanFindQueueFamily(device, surface);
|
||||
if (queue_family == static_cast<uint32_t>(-1)) {
|
||||
LOGF("No universal graphics, compute, and presentation queue\n");
|
||||
skip_device = true;
|
||||
reject("No universal graphics, compute, and presentation queue");
|
||||
}
|
||||
|
||||
const auto check_feature = [&](vk::Bool32 supported, const char* name,
|
||||
vk::Bool32 required = VK_TRUE) {
|
||||
if (required == VK_TRUE && supported != VK_TRUE) {
|
||||
reject(fmt::format("{} is not supported", name));
|
||||
}
|
||||
};
|
||||
|
||||
#if defined(__APPLE__)
|
||||
if (color_write_ext.colorWriteEnable != VK_TRUE) {
|
||||
LOGF("colorWriteEnable is not supported\n");
|
||||
#if !defined(__APPLE__)
|
||||
skip_device = true;
|
||||
}
|
||||
#else
|
||||
check_feature(color_write_ext.colorWriteEnable, "colorWriteEnable");
|
||||
#endif
|
||||
}
|
||||
if (image_view_min_lod.minLod != VK_TRUE) {
|
||||
LOGF("image view minLod is not supported\n");
|
||||
skip_device = true;
|
||||
}
|
||||
check_feature(image_view_min_lod.minLod, "image view minLod");
|
||||
|
||||
if (depth_clip_control.depthClipControl != VK_TRUE) {
|
||||
LOGF("depthClipControl is not supported\n");
|
||||
skip_device = true;
|
||||
}
|
||||
check_feature(depth_clip_control.depthClipControl, "depthClipControl");
|
||||
#if defined(__APPLE__)
|
||||
if (depth_clip_enable.depthClipEnable != VK_TRUE) {
|
||||
LOGF("depthClipEnable is not supported\n");
|
||||
#if !defined(__APPLE__)
|
||||
skip_device = true;
|
||||
}
|
||||
#else
|
||||
check_feature(depth_clip_enable.depthClipEnable, "depthClipEnable");
|
||||
#endif
|
||||
}
|
||||
#if !defined(__APPLE__)
|
||||
if (device_features2.features.depthClamp != VK_TRUE) {
|
||||
LOGF("depthClamp is not supported\n");
|
||||
skip_device = true;
|
||||
}
|
||||
if (fragment_barycentric.fragmentShaderBarycentric != VK_TRUE) {
|
||||
LOGF("fragmentShaderBarycentric is not supported\n");
|
||||
skip_device = true;
|
||||
}
|
||||
check_feature(device_features2.features.depthClamp, "depthClamp");
|
||||
check_feature(fragment_barycentric.fragmentShaderBarycentric, "fragmentShaderBarycentric");
|
||||
#endif
|
||||
|
||||
if (required_features12.samplerMirrorClampToEdge == VK_TRUE &&
|
||||
features12.samplerMirrorClampToEdge != VK_TRUE) {
|
||||
LOGF("samplerMirrorClampToEdge is not supported\n");
|
||||
skip_device = true;
|
||||
}
|
||||
if (required_features12.timelineSemaphore == VK_TRUE &&
|
||||
features12.timelineSemaphore != VK_TRUE) {
|
||||
LOGF("timelineSemaphore is not supported\n");
|
||||
skip_device = true;
|
||||
}
|
||||
if (required_features12.shaderOutputLayer == VK_TRUE &&
|
||||
features12.shaderOutputLayer != VK_TRUE) {
|
||||
LOGF("shaderOutputLayer is not supported\n");
|
||||
skip_device = true;
|
||||
}
|
||||
if (required_features12.shaderOutputViewportIndex == VK_TRUE &&
|
||||
features12.shaderOutputViewportIndex != VK_TRUE) {
|
||||
LOGF("shaderOutputViewportIndex is not supported\n");
|
||||
skip_device = true;
|
||||
}
|
||||
if (required_features12.bufferDeviceAddress == VK_TRUE &&
|
||||
features12.bufferDeviceAddress != VK_TRUE) {
|
||||
LOGF("bufferDeviceAddress is not supported\n");
|
||||
skip_device = true;
|
||||
}
|
||||
if (required_features12.shaderBufferInt64Atomics == VK_TRUE &&
|
||||
features12.shaderBufferInt64Atomics != VK_TRUE) {
|
||||
LOGF("shaderBufferInt64Atomics is not supported\n");
|
||||
skip_device = true;
|
||||
}
|
||||
if (features13.robustImageAccess != VK_TRUE) {
|
||||
LOGF("robustImageAccess is not supported\n");
|
||||
skip_device = true;
|
||||
}
|
||||
if (required_features13.dynamicRendering == VK_TRUE &&
|
||||
features13.dynamicRendering != VK_TRUE) {
|
||||
LOGF("dynamicRendering is not supported\n");
|
||||
skip_device = true;
|
||||
}
|
||||
if (required_features13.synchronization2 == VK_TRUE &&
|
||||
features13.synchronization2 != VK_TRUE) {
|
||||
LOGF("synchronization2 is not supported\n");
|
||||
skip_device = true;
|
||||
}
|
||||
if (device_features2.features.sampleRateShading != VK_TRUE) {
|
||||
LOGF("sampleRateShading is not supported\n");
|
||||
skip_device = true;
|
||||
}
|
||||
if (device_features2.features.depthBiasClamp != VK_TRUE) {
|
||||
LOGF("depthBiasClamp is not supported\n");
|
||||
skip_device = true;
|
||||
}
|
||||
if (device_features2.features.shaderClipDistance != VK_TRUE) {
|
||||
LOGF("shaderClipDistance is not supported\n");
|
||||
skip_device = true;
|
||||
}
|
||||
if (device_features2.features.shaderCullDistance != VK_TRUE) {
|
||||
LOGF("shaderCullDistance is not supported\n");
|
||||
skip_device = true;
|
||||
}
|
||||
if (device_features2.features.largePoints != VK_TRUE) {
|
||||
LOGF("largePoints is not supported\n");
|
||||
skip_device = true;
|
||||
}
|
||||
if (device_features2.features.multiViewport != VK_TRUE) {
|
||||
LOGF("multiViewport is not supported\n");
|
||||
skip_device = true;
|
||||
}
|
||||
if (device_features2.features.fillModeNonSolid != VK_TRUE) {
|
||||
LOGF("fillModeNonSolid is not supported\n");
|
||||
skip_device = true;
|
||||
}
|
||||
if (device_features2.features.fragmentStoresAndAtomics != VK_TRUE) {
|
||||
LOGF("fragmentStoresAndAtomics is not supported\n");
|
||||
skip_device = true;
|
||||
}
|
||||
check_feature(features12.samplerMirrorClampToEdge, "samplerMirrorClampToEdge",
|
||||
required_features12.samplerMirrorClampToEdge);
|
||||
check_feature(features12.timelineSemaphore, "timelineSemaphore",
|
||||
required_features12.timelineSemaphore);
|
||||
check_feature(features12.shaderOutputLayer, "shaderOutputLayer",
|
||||
required_features12.shaderOutputLayer);
|
||||
check_feature(features12.shaderOutputViewportIndex, "shaderOutputViewportIndex",
|
||||
required_features12.shaderOutputViewportIndex);
|
||||
check_feature(features12.bufferDeviceAddress, "bufferDeviceAddress",
|
||||
required_features12.bufferDeviceAddress);
|
||||
check_feature(features12.shaderBufferInt64Atomics, "shaderBufferInt64Atomics",
|
||||
required_features12.shaderBufferInt64Atomics);
|
||||
check_feature(features13.robustImageAccess, "robustImageAccess");
|
||||
check_feature(features13.dynamicRendering, "dynamicRendering",
|
||||
required_features13.dynamicRendering);
|
||||
check_feature(features13.synchronization2, "synchronization2",
|
||||
required_features13.synchronization2);
|
||||
check_feature(device_features2.features.sampleRateShading, "sampleRateShading");
|
||||
check_feature(device_features2.features.depthBiasClamp, "depthBiasClamp");
|
||||
check_feature(device_features2.features.shaderClipDistance, "shaderClipDistance");
|
||||
check_feature(device_features2.features.shaderCullDistance, "shaderCullDistance");
|
||||
check_feature(device_features2.features.largePoints, "largePoints");
|
||||
check_feature(device_features2.features.multiViewport, "multiViewport");
|
||||
check_feature(device_features2.features.fillModeNonSolid, "fillModeNonSolid");
|
||||
check_feature(device_features2.features.fragmentStoresAndAtomics,
|
||||
"fragmentStoresAndAtomics");
|
||||
|
||||
if (device_features2.features.samplerAnisotropy != VK_TRUE) {
|
||||
LOGF("samplerAnisotropy is not supported\n");
|
||||
skip_device = true;
|
||||
}
|
||||
if (device_features2.features.robustBufferAccess != VK_TRUE) {
|
||||
LOGF("robustBufferAccess is not supported\n");
|
||||
skip_device = true;
|
||||
}
|
||||
check_feature(device_features2.features.samplerAnisotropy, "samplerAnisotropy");
|
||||
check_feature(device_features2.features.robustBufferAccess, "robustBufferAccess");
|
||||
#if defined(__APPLE__)
|
||||
if (device_features2.features.depthBounds != VK_TRUE) {
|
||||
LOGF("depthBounds is not supported\n");
|
||||
#if !defined(__APPLE__)
|
||||
skip_device = true;
|
||||
}
|
||||
#else
|
||||
check_feature(device_features2.features.depthBounds, "depthBounds");
|
||||
#endif
|
||||
}
|
||||
if (device_features2.features.shaderStorageImageWriteWithoutFormat != VK_TRUE) {
|
||||
LOGF("shaderStorageImageWriteWithoutFormat is not supported\n");
|
||||
skip_device = true;
|
||||
}
|
||||
check_feature(device_features2.features.shaderStorageImageWriteWithoutFormat,
|
||||
"shaderStorageImageWriteWithoutFormat");
|
||||
|
||||
if (device_features2.features.shaderImageGatherExtended != VK_TRUE) {
|
||||
LOGF("shaderImageGatherExtended is not supported\n");
|
||||
skip_device = true;
|
||||
}
|
||||
check_feature(device_features2.features.shaderImageGatherExtended,
|
||||
"shaderImageGatherExtended");
|
||||
|
||||
if (device_features2.features.independentBlend != VK_TRUE) {
|
||||
LOGF("independentBlend is not supported\n");
|
||||
skip_device = true;
|
||||
}
|
||||
if (device_features2.features.tessellationShader != VK_TRUE) {
|
||||
LOGF("tessellationShader is not supported\n");
|
||||
skip_device = true;
|
||||
}
|
||||
check_feature(device_features2.features.independentBlend, "independentBlend");
|
||||
check_feature(device_features2.features.tessellationShader, "tessellationShader");
|
||||
|
||||
if (!skip_device) {
|
||||
auto available_extensions = EnumerateVulkan<vk::ExtensionProperties>(
|
||||
@@ -368,8 +309,7 @@ static void VulkanFindPhysicalDevice(vk::Instance instance, vk::SurfaceKHR surfa
|
||||
|
||||
for (const char* ext: device_extensions) {
|
||||
if (!HasExtension(available_extensions, ext)) {
|
||||
skip_device = true;
|
||||
break;
|
||||
reject(fmt::format("{} is not supported", ext));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -387,86 +327,46 @@ static void VulkanFindPhysicalDevice(vk::Instance instance, vk::SurfaceKHR surfa
|
||||
|
||||
if (!(candidate_capabilities.capabilities.supportedUsageFlags &
|
||||
vk::ImageUsageFlagBits::eTransferDst)) {
|
||||
LOGF("Surface cannot be destination of blit\n");
|
||||
skip_device = true;
|
||||
reject("Surface cannot be destination of blit");
|
||||
}
|
||||
}
|
||||
|
||||
if (!skip_device && !CheckFormat(device, vk::Format::eD32Sfloat, true,
|
||||
vk::FormatFeatureFlagBits::eDepthStencilAttachment)) {
|
||||
LOGF("Format vk::Format::eD32Sfloat cannot be used as depth buffer\n");
|
||||
skip_device = true;
|
||||
}
|
||||
|
||||
if (!skip_device && !CheckFormat(device, vk::Format::eD32SfloatS8Uint, true,
|
||||
vk::FormatFeatureFlagBits::eDepthStencilAttachment)) {
|
||||
LOGF("Format vk::Format::eD32SfloatS8Uint cannot be used as depth buffer\n");
|
||||
skip_device = true;
|
||||
}
|
||||
|
||||
if (!skip_device && !CheckFormat(device, vk::Format::eD16Unorm, true,
|
||||
vk::FormatFeatureFlagBits::eDepthStencilAttachment)) {
|
||||
LOGF("Format vk::Format::eD16Unorm cannot be used as depth buffer\n");
|
||||
skip_device = true;
|
||||
}
|
||||
|
||||
if (!skip_device && !CheckFormat(device, vk::Format::eBc3SrgbBlock, true,
|
||||
vk::FormatFeatureFlagBits::eSampledImage |
|
||||
vk::FormatFeatureFlagBits::eTransferDst)) {
|
||||
LOGF("Format vk::Format::eBc3SrgbBlock cannot be used as texture\n");
|
||||
skip_device = true;
|
||||
}
|
||||
|
||||
if (!skip_device && !CheckFormat(device, vk::Format::eR8G8B8A8Srgb, true,
|
||||
vk::FormatFeatureFlagBits::eSampledImage |
|
||||
vk::FormatFeatureFlagBits::eTransferDst)) {
|
||||
LOGF("Format vk::Format::eR8G8B8A8Srgb cannot be used as texture\n");
|
||||
skip_device = true;
|
||||
}
|
||||
|
||||
if (!skip_device && !CheckFormat(device, vk::Format::eR8Unorm, true,
|
||||
vk::FormatFeatureFlagBits::eSampledImage |
|
||||
vk::FormatFeatureFlagBits::eTransferDst)) {
|
||||
LOGF("Format vk::Format::eR8Unorm cannot be used as texture\n");
|
||||
skip_device = true;
|
||||
}
|
||||
|
||||
if (!skip_device && !CheckFormat(device, vk::Format::eR8G8Unorm, true,
|
||||
vk::FormatFeatureFlagBits::eSampledImage |
|
||||
vk::FormatFeatureFlagBits::eTransferDst)) {
|
||||
LOGF("Format vk::Format::eR8G8Unorm cannot be used as texture\n");
|
||||
skip_device = true;
|
||||
}
|
||||
|
||||
if (!skip_device && !CheckFormat(device, vk::Format::eR8G8B8A8Srgb, true,
|
||||
vk::FormatFeatureFlagBits::eStorageImage |
|
||||
vk::FormatFeatureFlagBits::eTransferDst)) {
|
||||
LOGF("Format vk::Format::eR8G8B8A8Srgb cannot be used as texture\n");
|
||||
|
||||
if (!skip_device && !CheckFormat(device, vk::Format::eR8G8B8A8Unorm, true,
|
||||
vk::FormatFeatureFlagBits::eStorageImage |
|
||||
vk::FormatFeatureFlagBits::eTransferDst)) {
|
||||
LOGF("Format vk::Format::eR8G8B8A8Unorm cannot be used as texture\n");
|
||||
skip_device = true;
|
||||
const auto check_format = [&](vk::Format format, vk::FormatFeatureFlags features,
|
||||
const char* usage) {
|
||||
if (!skip_device && !CheckFormat(device, format, true, features)) {
|
||||
reject(fmt::format("Format vk::Format::e{} cannot be used as {}",
|
||||
vk::to_string(format), usage));
|
||||
}
|
||||
};
|
||||
|
||||
const auto depth_features = vk::FormatFeatureFlagBits::eDepthStencilAttachment;
|
||||
const auto texture_features =
|
||||
vk::FormatFeatureFlagBits::eSampledImage | vk::FormatFeatureFlagBits::eTransferDst;
|
||||
const auto storage_features =
|
||||
vk::FormatFeatureFlagBits::eStorageImage | vk::FormatFeatureFlagBits::eTransferDst;
|
||||
|
||||
check_format(vk::Format::eD32Sfloat, depth_features, "depth buffer");
|
||||
check_format(vk::Format::eD32SfloatS8Uint, depth_features, "depth buffer");
|
||||
check_format(vk::Format::eD16Unorm, depth_features, "depth buffer");
|
||||
check_format(vk::Format::eBc3SrgbBlock, texture_features, "texture");
|
||||
check_format(vk::Format::eR8G8B8A8Srgb, texture_features, "texture");
|
||||
check_format(vk::Format::eR8Unorm, texture_features, "texture");
|
||||
check_format(vk::Format::eR8G8Unorm, texture_features, "texture");
|
||||
|
||||
if (!skip_device &&
|
||||
!CheckFormat(device, vk::Format::eR8G8B8A8Srgb, true, storage_features)) {
|
||||
LOGF("Format vk::Format::eR8G8B8A8Srgb cannot be used as storage image\n");
|
||||
check_format(vk::Format::eR8G8B8A8Unorm, storage_features, "storage image");
|
||||
}
|
||||
|
||||
if (!skip_device && !CheckFormat(device, vk::Format::eB8G8R8A8Srgb, true,
|
||||
vk::FormatFeatureFlagBits::eStorageImage |
|
||||
vk::FormatFeatureFlagBits::eTransferDst)) {
|
||||
LOGF("Format vk::Format::eB8G8R8A8Srgb cannot be used as texture\n");
|
||||
|
||||
if (!skip_device && !CheckFormat(device, vk::Format::eB8G8R8A8Unorm, true,
|
||||
vk::FormatFeatureFlagBits::eStorageImage |
|
||||
vk::FormatFeatureFlagBits::eTransferDst)) {
|
||||
LOGF("Format vk::Format::eB8G8R8A8Unorm cannot be used as texture\n");
|
||||
skip_device = true;
|
||||
}
|
||||
if (!skip_device &&
|
||||
!CheckFormat(device, vk::Format::eB8G8R8A8Srgb, true, storage_features)) {
|
||||
LOGF("Format vk::Format::eB8G8R8A8Srgb cannot be used as storage image\n");
|
||||
check_format(vk::Format::eB8G8R8A8Unorm, storage_features, "storage image");
|
||||
}
|
||||
|
||||
if (!skip_device && device_properties.limits.maxSamplerAnisotropy < 16.0f) {
|
||||
LOGF("maxSamplerAnisotropy < 16.0f");
|
||||
skip_device = true;
|
||||
reject("maxSamplerAnisotropy < 16.0f");
|
||||
}
|
||||
|
||||
if (skip_device) {
|
||||
@@ -520,16 +420,20 @@ static vk::Device VulkanCreateDevice(GraphicContext& graphics,
|
||||
#endif
|
||||
depth_clip_control.depthClipControl = VK_TRUE;
|
||||
|
||||
auto features12 = WindowContext::RequiredVulkan12Features();
|
||||
features12.pNext = &depth_clip_control;
|
||||
|
||||
const bool workgroup_layout_extension =
|
||||
HasExtension(device_extensions, VK_KHR_WORKGROUP_MEMORY_EXPLICIT_LAYOUT_EXTENSION_NAME);
|
||||
vk::PhysicalDeviceWorkgroupMemoryExplicitLayoutFeaturesKHR supported_workgroup_layout {};
|
||||
vk::PhysicalDeviceVulkan12Features supported_features12 {};
|
||||
supported_features12.pNext = workgroup_layout_extension ? &supported_workgroup_layout : nullptr;
|
||||
vk::PhysicalDeviceVulkan13Features supported_features13 {};
|
||||
supported_features13.pNext = &supported_features12;
|
||||
|
||||
const auto robustness2_ext_enabled =
|
||||
HasExtension(device_extensions, VK_EXT_ROBUSTNESS_2_EXTENSION_NAME);
|
||||
|
||||
vk::PhysicalDeviceRobustness2FeaturesEXT supported_robustness2 {};
|
||||
if (robustness2_ext_enabled) {
|
||||
supported_robustness2.pNext = supported_features13.pNext;
|
||||
supported_features13.pNext = &supported_robustness2;
|
||||
}
|
||||
|
||||
@@ -557,6 +461,22 @@ static vk::Device VulkanCreateDevice(GraphicContext& graphics,
|
||||
supported_features2.pNext = &provoking_vertex;
|
||||
}
|
||||
physical_device.getFeatures2(&supported_features2);
|
||||
|
||||
auto features12 = WindowContext::RequiredVulkan12Features();
|
||||
features12.shaderSharedInt64Atomics = supported_features12.shaderSharedInt64Atomics;
|
||||
vk::PhysicalDeviceWorkgroupMemoryExplicitLayoutFeaturesKHR workgroup_layout {};
|
||||
workgroup_layout.workgroupMemoryExplicitLayout =
|
||||
supported_workgroup_layout.workgroupMemoryExplicitLayout;
|
||||
workgroup_layout.pNext = &depth_clip_control;
|
||||
features12.pNext = workgroup_layout_extension ? static_cast<void*>(&workgroup_layout)
|
||||
: static_cast<void*>(&depth_clip_control);
|
||||
if (!features12.shaderSharedInt64Atomics || !workgroup_layout.workgroupMemoryExplicitLayout) {
|
||||
Log::WriteToConsoleAndLog(fmt::format(
|
||||
"WARNING: Native 64-bit LDS atomics are unavailable: shaderSharedInt64Atomics={}, "
|
||||
"workgroupMemoryExplicitLayout={}. Continuing with the selected Vulkan device.\n",
|
||||
features12.shaderSharedInt64Atomics != VK_FALSE,
|
||||
workgroup_layout.workgroupMemoryExplicitLayout != VK_FALSE));
|
||||
}
|
||||
graphics.mesh_shader_enabled = mesh_extension && supported_mesh.meshShader;
|
||||
|
||||
vk::PhysicalDeviceSubgroupSizeControlProperties subgroup_size_control {};
|
||||
@@ -1004,11 +924,13 @@ void WindowContext::CreateVulkan() {
|
||||
}
|
||||
#endif
|
||||
|
||||
std::string rejected_devices;
|
||||
VulkanFindPhysicalDevice(graphic_ctx.instance, surface, device_extensions, surface_capabilities,
|
||||
graphic_ctx.physical_device, graphic_ctx.queue_family);
|
||||
graphic_ctx.physical_device, graphic_ctx.queue_family,
|
||||
rejected_devices);
|
||||
|
||||
if (graphic_ctx.physical_device == nullptr) {
|
||||
EXIT("Could not find suitable device");
|
||||
EXIT("Could not find suitable device:%s", rejected_devices.c_str());
|
||||
}
|
||||
|
||||
vk::PhysicalDevicePushDescriptorProperties push_descriptor_properties {};
|
||||
@@ -1049,6 +971,7 @@ void WindowContext::CreateVulkan() {
|
||||
for (const auto* extension: {VK_EXT_ROBUSTNESS_2_EXTENSION_NAME,
|
||||
VK_EXT_PROVOKING_VERTEX_EXTENSION_NAME,
|
||||
VK_EXT_MESH_SHADER_EXTENSION_NAME,
|
||||
VK_KHR_WORKGROUP_MEMORY_EXPLICIT_LAYOUT_EXTENSION_NAME,
|
||||
VK_EXT_DEPTH_RANGE_UNRESTRICTED_EXTENSION_NAME}) {
|
||||
if (HasExtension(available_extensions, extension)) {
|
||||
device_extensions.push_back(extension);
|
||||
|
||||
@@ -817,6 +817,7 @@ void WindowRun() {
|
||||
EXIT_IF(g_window == nullptr);
|
||||
|
||||
g_window->Run();
|
||||
Common::LockGuard lock(g_window->render_context->GetMutex());
|
||||
g_window->render_context->GetPipelineCache().Save();
|
||||
}
|
||||
|
||||
|
||||
@@ -229,6 +229,12 @@ Emitter::SpirvRequirements Emitter::AnalyzeProgramRequirements(const IR::Program
|
||||
if (kind != IR::ResourceKind::Lds && kind != IR::ResourceKind::Gds) {
|
||||
Fail(program, "shared operation has invalid resource kind");
|
||||
}
|
||||
if (inst.GetOpcode() == IR::ValueOpcode::SharedAtomicOr64) {
|
||||
if (kind != IR::ResourceKind::Lds || program.stage != ShaderType::Compute) {
|
||||
Fail(program, "64-bit shared atomics require compute LDS");
|
||||
}
|
||||
requirements.shared_int64_atomics = true;
|
||||
}
|
||||
if (program.stage != ShaderType::Compute && program.stage != ShaderType::Mesh &&
|
||||
kind == IR::ResourceKind::Lds) {
|
||||
requirements.function_lds = true;
|
||||
|
||||
@@ -452,6 +452,11 @@ void EmitSetAttribute(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
if (exp.kind == IR::ExportTargetKind::Null || exp.en == 0u) {
|
||||
return;
|
||||
}
|
||||
// Skip dormant color exports after their valid mask; MRT1 is reserved for logical alpha.
|
||||
if (state.program.stage == ShaderType::Pixel && exp.kind == IR::ExportTargetKind::Mrt &&
|
||||
exp.index != 0 && state.input_info.pixel->alpha_blend_source_remap) {
|
||||
return;
|
||||
}
|
||||
EmitIfCondition(state, exec, [&]() {
|
||||
const auto data = ctx.Arg(inst, 0);
|
||||
if (exp.kind == IR::ExportTargetKind::Primitive) {
|
||||
@@ -492,6 +497,18 @@ void EmitSetAttribute(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
const bool uint_output = MrtOutputMode(state, exp) == 7u;
|
||||
const auto vector_type = uint_output ? TypeU32Vector(state, 4) : TypeF32Vector(state, 4);
|
||||
auto value = ExportVector(ctx, data, exp, uint_output);
|
||||
if (state.program.stage == ShaderType::Pixel && exp.kind == IR::ExportTargetKind::Mrt &&
|
||||
exp.index == 0 && !uint_output && state.input_info.pixel->alpha_blend_source_remap) {
|
||||
// Broadcast logical alpha before swizzling the primary output.
|
||||
const auto blend_output =
|
||||
OutputVariableForExport(state, {.kind = IR::ExportTargetKind::Mrt, .index = 1});
|
||||
if (blend_output != 0) {
|
||||
const auto alpha = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpVectorShuffle, vector_type, alpha, value, value,
|
||||
3u, 3u, 3u, 3u);
|
||||
state.builder.AddFunction(spv::OpStore, blend_output, alpha);
|
||||
}
|
||||
}
|
||||
if (state.program.stage == ShaderType::Pixel && exp.kind == IR::ExportTargetKind::Mrt &&
|
||||
exp.index < state.input_info.pixel->target_export_mapping.size()) {
|
||||
const auto mapping = state.input_info.pixel->target_export_mapping[exp.index];
|
||||
|
||||
@@ -222,6 +222,7 @@ void EmitLoadMemory(ValueEmitContext& ctx, const IR::Inst& inst
|
||||
void EmitStoreMemory(ValueEmitContext& ctx, const IR::Inst& inst);
|
||||
uint32_t EmitAtomic32(ValueEmitContext& ctx, const IR::Inst& inst);
|
||||
uint32_t EmitBufferAtomic64(ValueEmitContext& ctx, const IR::Inst& inst);
|
||||
void EmitSharedAtomicOr64(ValueEmitContext& ctx, const IR::Inst& inst);
|
||||
uint32_t EmitBufferFloatAtomic(ValueEmitContext& ctx, const IR::Inst& inst);
|
||||
void EmitSharedFloatAtomic(ValueEmitContext& ctx, const IR::Inst& inst);
|
||||
uint32_t EmitSharedIncDec(ValueEmitContext& ctx, const IR::Inst& inst);
|
||||
|
||||
@@ -67,6 +67,7 @@ struct SpirvRequirements {
|
||||
bool function_scratch = false;
|
||||
bool pixel_valid_mask = false;
|
||||
bool buffer_int64_atomics = false;
|
||||
bool shared_int64_atomics = false;
|
||||
bool coherent_buffers = false;
|
||||
bool float64 = false;
|
||||
};
|
||||
@@ -101,6 +102,7 @@ struct EmitterState {
|
||||
uint32_t shader_data_storage_variable = 0;
|
||||
uint32_t flattened_srt_variable = 0;
|
||||
uint32_t lds_variable = 0;
|
||||
uint32_t lds_u64_variable = 0;
|
||||
std::array<uint32_t, 2> scratch_variable {};
|
||||
std::array<uint32_t, IR::ImageBindingCount> image_variables {};
|
||||
uint32_t sampler_variable = 0;
|
||||
@@ -376,6 +378,7 @@ Prospero::BufferFormat StorageBufferFormat(const EmitterState& state, const IR::
|
||||
void EmitMemoryOffsets(EmitterState& state);
|
||||
|
||||
uint32_t LdsDwordCount(const EmitterState& state);
|
||||
void EnsureLdsStorage(EmitterState& state);
|
||||
|
||||
struct MemoryResourceAccess {
|
||||
IR::ResourceKind kind = IR::ResourceKind::None;
|
||||
|
||||
@@ -548,6 +548,7 @@ spv::Op SpirvAtomicOpcode(IR::ValueOpcode opcode) {
|
||||
case IR::ValueOpcode::SharedAtomicAnd32: return spv::OpAtomicAnd;
|
||||
case IR::ValueOpcode::BufferAtomicOr32:
|
||||
case IR::ValueOpcode::BufferAtomicOr64:
|
||||
case IR::ValueOpcode::SharedAtomicOr64:
|
||||
case IR::ValueOpcode::SharedAtomicOr32: return spv::OpAtomicOr;
|
||||
case IR::ValueOpcode::BufferAtomicXor32:
|
||||
case IR::ValueOpcode::SharedAtomicXor32: return spv::OpAtomicXor;
|
||||
@@ -1064,6 +1065,32 @@ uint32_t EmitBufferAtomic64(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
});
|
||||
}
|
||||
|
||||
void EmitSharedAtomicOr64(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
auto& state = ctx.state;
|
||||
const auto& mem = ctx.Memory(inst);
|
||||
EnsureLdsStorage(state);
|
||||
EmitIfCondition(state, ctx.Arg(inst, 2), [&]() {
|
||||
const auto address = Binary(state, spv::OpBitwiseAnd, TypeU32(state),
|
||||
ByteAddress(ctx, inst, mem), ConstantU32(state, 0xfff8u));
|
||||
const auto index = Binary(state, spv::OpShiftRightLogical, TypeU32(state), address,
|
||||
ConstantU32(state, 3u));
|
||||
const auto in_bounds = Binary(state, spv::OpULessThan, TypeBool(state), index,
|
||||
ConstantU32(state, LdsDwordCount(state) / 2u));
|
||||
EmitIfCondition(state, in_bounds, [&]() {
|
||||
const auto pointer = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpAccessChain,
|
||||
TypePointer(state, spv::StorageClassWorkgroup, TypeScalarU64(state)),
|
||||
pointer, state.lds_u64_variable, ConstantU32(state, 0), index);
|
||||
const auto value = Unary(state, spv::OpBitcast, TypeScalarU64(state), ctx.Arg(inst, 1));
|
||||
state.builder.AddFunction(
|
||||
SpirvAtomicOpcode(inst.GetOpcode()), TypeScalarU64(state), state.builder.AllocateId(),
|
||||
pointer, ConstantU32(state, spv::ScopeWorkgroup),
|
||||
ConstantU32(state, spv::MemorySemanticsAcquireReleaseMask |
|
||||
spv::MemorySemanticsWorkgroupMemoryMask), value);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
uint32_t EmitBufferFloatAtomic(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
const auto& mem = ctx.Memory(inst);
|
||||
const bool max_value = inst.GetOpcode() == IR::ValueOpcode::BufferAtomicFMax32;
|
||||
|
||||
@@ -63,16 +63,34 @@ uint32_t LdsDwordCount(const EmitterState& state) {
|
||||
return workgroup != nullptr ? workgroup->lds_size_dwords : 8192u;
|
||||
}
|
||||
|
||||
static void EnsureLdsStorage(EmitterState& state) {
|
||||
void EnsureLdsStorage(EmitterState& state) {
|
||||
if (state.lds_variable != 0) {
|
||||
return;
|
||||
}
|
||||
if (ShaderWorkgroupInput(state.program.stage, state.input_info) == nullptr) {
|
||||
EXIT("function LDS was not prepared before SPIR-V function emission\n");
|
||||
}
|
||||
state.lds_variable = state.builder.DefineGlobalVariable(
|
||||
TypeU32ArrayPointer(state, spv::StorageClassWorkgroup, LdsDwordCount(state)),
|
||||
spv::StorageClassWorkgroup);
|
||||
const auto define = [&](uint32_t type, uint32_t bytes) {
|
||||
const auto array = state.builder.DecoratedType(
|
||||
spv::OpTypeArray, {{spv::OpDecorate, {spv::DecorationArrayStride, bytes}}}, type,
|
||||
ConstantU32(state, std::max(LdsDwordCount(state) * 4u / bytes, 1u)));
|
||||
const auto block = state.builder.DecoratedType(
|
||||
spv::OpTypeStruct, {{spv::OpMemberDecorate, {0, spv::DecorationOffset, 0}},
|
||||
{spv::OpDecorate, {spv::DecorationBlock}}}, array);
|
||||
const auto variable = state.builder.DefineGlobalVariable(
|
||||
TypePointer(state, spv::StorageClassWorkgroup, block), spv::StorageClassWorkgroup);
|
||||
state.builder.AddAnnotation(spv::OpDecorate, variable, spv::DecorationAliased);
|
||||
return variable;
|
||||
};
|
||||
if (state.requirements.shared_int64_atomics) {
|
||||
state.lds_variable = define(TypeU32(state), 4u);
|
||||
state.lds_u64_variable = define(TypeScalarU64(state), 8u);
|
||||
state.builder.AddName(state.lds_u64_variable, "lds_qwords");
|
||||
} else {
|
||||
state.lds_variable = state.builder.DefineGlobalVariable(
|
||||
TypeU32ArrayPointer(state, spv::StorageClassWorkgroup, LdsDwordCount(state)),
|
||||
spv::StorageClassWorkgroup);
|
||||
}
|
||||
state.builder.AddName(state.lds_variable, "lds_dwords");
|
||||
}
|
||||
|
||||
@@ -167,6 +185,11 @@ uint32_t EmitMemoryElementPointer(EmitterState& state, const MemoryResourceAcces
|
||||
: ShaderWorkgroupInput(state.program.stage, state.input_info) != nullptr
|
||||
? spv::StorageClassWorkgroup
|
||||
: spv::StorageClassFunction;
|
||||
if (access.kind == IR::ResourceKind::Lds && state.requirements.shared_int64_atomics) {
|
||||
state.builder.AddFunction(spv::OpAccessChain, TypeU32ElementPointer(state, storage_class),
|
||||
pointer, access.object_pointer, ConstantU32(state, 0), index);
|
||||
return pointer;
|
||||
}
|
||||
state.builder.AddFunction(spv::OpAccessChain, TypeU32ElementPointer(state, storage_class),
|
||||
pointer, access.object_pointer, index);
|
||||
return pointer;
|
||||
|
||||
@@ -626,10 +626,15 @@ void DefineModule(EmitterState& state) {
|
||||
state.builder.RequireCapability(spv::CapabilityPhysicalStorageBufferAddresses);
|
||||
state.builder.RequireExtension("SPV_KHR_physical_storage_buffer");
|
||||
}
|
||||
if (state.requirements.buffer_int64_atomics) {
|
||||
if (state.requirements.buffer_int64_atomics || state.requirements.shared_int64_atomics) {
|
||||
state.builder.RequireCapability(spv::CapabilityInt64);
|
||||
state.builder.RequireCapability(spv::CapabilityInt64Atomics);
|
||||
}
|
||||
if (state.requirements.shared_int64_atomics) {
|
||||
state.builder.RequireVersion(0x00010400u);
|
||||
state.builder.RequireExtension("SPV_KHR_workgroup_memory_explicit_layout");
|
||||
state.builder.RequireCapability(spv::CapabilityWorkgroupMemoryExplicitLayoutKHR);
|
||||
}
|
||||
if (state.clip_distance_variable != 0) {
|
||||
state.builder.RequireCapability(spv::CapabilityClipDistance);
|
||||
}
|
||||
|
||||
@@ -106,6 +106,7 @@ constexpr MemoryOpcodeInfo DS_OPCODE_LIST[] = {
|
||||
{0x39u, Opcode::DS_READ_I8, 1, 8, true}, {0x3au, Opcode::DS_READ_U8, 1, 8},
|
||||
{0x3bu, Opcode::DS_READ_I16, 1, 16, true}, {0x3cu, Opcode::DS_READ_U16, 1, 16},
|
||||
{0x3du, Opcode::DS_CONSUME, 1, 32}, {0x3eu, Opcode::DS_APPEND, 1, 32},
|
||||
{0x4au, Opcode::DS_OR_B64, 2, 32},
|
||||
{0x4du, Opcode::DS_WRITE_B64, 2, 32}, {0x4eu, Opcode::DS_WRITE2_B64, 4, 32},
|
||||
{0x4fu, Opcode::DS_WRITE2ST64_B64, 4, 32}, {0x76u, Opcode::DS_READ_B64, 2, 32},
|
||||
{0x77u, Opcode::DS_READ2_B64, 4, 32}, {0x78u, Opcode::DS_READ2ST64_B64, 4, 32},
|
||||
@@ -185,6 +186,7 @@ bool IsDsAtomicOpcode(Opcode opcode) {
|
||||
case Opcode::DS_AND_B32:
|
||||
case Opcode::DS_AND_RTN_B32:
|
||||
case Opcode::DS_OR_B32:
|
||||
case Opcode::DS_OR_B64:
|
||||
case Opcode::DS_OR_RTN_B32:
|
||||
case Opcode::DS_XOR_B32:
|
||||
case Opcode::DS_XOR_RTN_B32:
|
||||
|
||||
@@ -681,6 +681,7 @@ std::string InstructionToString(const Instruction& inst) {
|
||||
case Opcode::DS_AND_B32:
|
||||
case Opcode::DS_AND_RTN_B32:
|
||||
case Opcode::DS_OR_B32:
|
||||
case Opcode::DS_OR_B64:
|
||||
case Opcode::DS_OR_RTN_B32:
|
||||
case Opcode::DS_XOR_B32:
|
||||
case Opcode::DS_XOR_RTN_B32:
|
||||
|
||||
@@ -384,6 +384,7 @@ enum class Opcode {
|
||||
V_CMP_GT_I16,
|
||||
V_CMP_NE_I16,
|
||||
V_CMP_GE_I16,
|
||||
V_CMPX_LT_I16,
|
||||
V_CMP_LT_F16,
|
||||
V_CMP_EQ_F16,
|
||||
V_CMP_LE_F16,
|
||||
@@ -399,6 +400,7 @@ enum class Opcode {
|
||||
V_CMPX_GT_F16,
|
||||
V_CMPX_GE_F16,
|
||||
V_CMPX_NGT_F16,
|
||||
V_CMPX_NLE_F16,
|
||||
V_CMPX_NEQ_F16,
|
||||
V_CMPX_NLT_F16,
|
||||
V_CMPX_LT_I32,
|
||||
@@ -527,6 +529,7 @@ enum class Opcode {
|
||||
DS_AND_B32,
|
||||
DS_AND_RTN_B32,
|
||||
DS_OR_B32,
|
||||
DS_OR_B64,
|
||||
DS_OR_RTN_B32,
|
||||
DS_XOR_B32,
|
||||
DS_XOR_RTN_B32,
|
||||
|
||||
@@ -239,6 +239,7 @@ constexpr VopcOpcodeInfo VOPC_OPCODE_LIST[] = {
|
||||
{0x92u, Opcode::V_CMPX_EQ_I32}, {0x93u, Opcode::V_CMPX_LE_I32},
|
||||
{0x94u, Opcode::V_CMPX_GT_I32}, {0x95u, Opcode::V_CMPX_NE_I32},
|
||||
{0x96u, Opcode::V_CMPX_GE_I32}, {0x98u, Opcode::V_CMPX_CLASS_F32},
|
||||
{0x99u, Opcode::V_CMPX_LT_I16, false},
|
||||
{0xa9u, Opcode::V_CMP_LT_U16}, {0xaau, Opcode::V_CMP_EQ_U16},
|
||||
{0xabu, Opcode::V_CMP_LE_U16}, {0xacu, Opcode::V_CMP_GT_U16},
|
||||
{0xadu, Opcode::V_CMP_NE_U16}, {0xaeu, Opcode::V_CMP_GE_U16},
|
||||
@@ -262,7 +263,8 @@ constexpr VopcOpcodeInfo VOPC_OPCODE_LIST[] = {
|
||||
{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},
|
||||
{0xfdu, Opcode::V_CMPX_NEQ_F16}, {0xfeu, Opcode::V_CMPX_NLT_F16},
|
||||
{0xfcu, Opcode::V_CMPX_NLE_F16}, {0xfdu, Opcode::V_CMPX_NEQ_F16},
|
||||
{0xfeu, Opcode::V_CMPX_NLT_F16},
|
||||
};
|
||||
|
||||
constexpr auto VOPC_OPS = Detail::MakeOpcodeTable<0x100>(VOPC_OPCODE_LIST);
|
||||
@@ -868,6 +870,7 @@ bool IsVopcFloatCompareOpcode(Opcode opcode) {
|
||||
case Opcode::V_CMPX_GT_F16:
|
||||
case Opcode::V_CMPX_GE_F16:
|
||||
case Opcode::V_CMPX_NGT_F16:
|
||||
case Opcode::V_CMPX_NLE_F16:
|
||||
case Opcode::V_CMPX_NEQ_F16:
|
||||
case Opcode::V_CMPX_NLT_F16:
|
||||
case Opcode::V_CMP_CLASS_F32:
|
||||
@@ -1485,6 +1488,7 @@ bool IsVopcCompareExec(Opcode opcode) {
|
||||
case Opcode::V_CMPX_GE_U32:
|
||||
case Opcode::V_CMPX_NE_I64:
|
||||
case Opcode::V_CMPX_NE_U64:
|
||||
case Opcode::V_CMPX_LT_I16:
|
||||
case Opcode::V_CMPX_LT_U16:
|
||||
case Opcode::V_CMPX_EQ_U16:
|
||||
case Opcode::V_CMPX_GT_U16:
|
||||
@@ -1494,6 +1498,7 @@ bool IsVopcCompareExec(Opcode opcode) {
|
||||
case Opcode::V_CMPX_GT_F16:
|
||||
case Opcode::V_CMPX_GE_F16:
|
||||
case Opcode::V_CMPX_NGT_F16:
|
||||
case Opcode::V_CMPX_NLE_F16:
|
||||
case Opcode::V_CMPX_NEQ_F16:
|
||||
case Opcode::V_CMPX_NLT_F16: return true;
|
||||
default: return false;
|
||||
|
||||
@@ -570,7 +570,10 @@ void Translator::DS_ATOMIC(const Decoder::Instruction& inst, IR::ValueOpcode opc
|
||||
bool returns_value) {
|
||||
const auto memory = MemoryInfoFromDecoded(inst);
|
||||
const auto address = ReadU32(MemorySourceAt(inst, 1));
|
||||
const auto result = ir.Emit(opcode, {address, ReadU32(MemorySourceAt(inst, 0)), ir.GetExec()},
|
||||
const auto data_src = MemorySourceAt(inst, 0);
|
||||
const IR::Value value =
|
||||
memory.data_dwords == 2u ? IR::Value(ReadU64(data_src)) : IR::Value(ReadU32(data_src));
|
||||
const auto result = ir.Emit(opcode, {address, value, ir.GetExec()},
|
||||
AddMemoryInfo(memory, inst.pc));
|
||||
if (returns_value) {
|
||||
WriteOperand(inst.dst, result);
|
||||
@@ -1003,6 +1006,8 @@ void Translator::EmitMemory(const Decoder::Instruction& inst) {
|
||||
return DS_ATOMIC(inst, IR::ValueOpcode::SharedAtomicAnd32, true);
|
||||
case Decoder::Opcode::DS_OR_B32:
|
||||
return DS_ATOMIC(inst, IR::ValueOpcode::SharedAtomicOr32, false);
|
||||
case Decoder::Opcode::DS_OR_B64:
|
||||
return DS_ATOMIC(inst, IR::ValueOpcode::SharedAtomicOr64, false);
|
||||
case Decoder::Opcode::DS_OR_RTN_B32:
|
||||
return DS_ATOMIC(inst, IR::ValueOpcode::SharedAtomicOr32, true);
|
||||
case Decoder::Opcode::DS_XOR_B32:
|
||||
|
||||
@@ -560,6 +560,12 @@ IR::U32 Translator::ReadU16LaneRaw(const Decoder::Operand& operand, bool high_la
|
||||
} else if (operand.sdwa_sel == 4u || operand.sdwa_sel == 5u) {
|
||||
offset = operand.sdwa_sel == 5u ? 16u : 0u;
|
||||
}
|
||||
if (width == 8u && operand.sdwa_sext) {
|
||||
// Extend the selected byte into the 16-bit operand before halfword modifiers.
|
||||
const auto value = IR::U32(
|
||||
ir.Emit(IR::ValueOpcode::BitFieldSExtract, {bits, IR::Value(offset), IR::Value(width)}));
|
||||
return ir.BitwiseAnd(value, IR::U32(IR::Value(0xffffu)));
|
||||
}
|
||||
return IR::U32(
|
||||
ir.Emit(IR::ValueOpcode::BitFieldUExtract, {bits, IR::Value(offset), IR::Value(width)}));
|
||||
}
|
||||
@@ -567,7 +573,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 = Read16LaneBits(operand, high_lane);
|
||||
if (sign_extend || operand.sdwa_sext) {
|
||||
if (sign_extend) {
|
||||
value = IR::U32(
|
||||
ir.Emit(IR::ValueOpcode::BitFieldSExtract, {value, IR::Value(0u), IR::Value(16u)}));
|
||||
}
|
||||
|
||||
@@ -156,6 +156,9 @@ void Translator::EmitVector(const Decoder::Instruction& inst) {
|
||||
case O::V_CMP_LT_I16:
|
||||
EmitInteger16Compare(inst, IR::ValueOpcode::SLessThan32, true, false);
|
||||
return;
|
||||
case O::V_CMPX_LT_I16:
|
||||
EmitInteger16Compare(inst, IR::ValueOpcode::SLessThan32, true, true);
|
||||
return;
|
||||
case O::V_CMP_LE_I16:
|
||||
EmitInteger16Compare(inst, IR::ValueOpcode::SLessThanEqual32, true, false);
|
||||
return;
|
||||
@@ -273,6 +276,9 @@ void Translator::EmitVector(const Decoder::Instruction& inst) {
|
||||
case O::V_CMPX_NGT_F16:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordLessThanEqual32, true, true);
|
||||
return;
|
||||
case O::V_CMPX_NLE_F16:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordGreaterThan32, true, true);
|
||||
return;
|
||||
case O::V_CMP_NEQ_F16:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordNotEqual32, true, false);
|
||||
return;
|
||||
|
||||
@@ -173,6 +173,7 @@ SharedAccess SharedAccessOf(ValueOpcode opcode) {
|
||||
case ValueOpcode::SharedAtomicUMax32:
|
||||
case ValueOpcode::SharedAtomicAnd32:
|
||||
case ValueOpcode::SharedAtomicOr32:
|
||||
case ValueOpcode::SharedAtomicOr64:
|
||||
case ValueOpcode::SharedAtomicXor32: return SharedAccess::Atomic;
|
||||
case ValueOpcode::DataAppend: return SharedAccess::Append;
|
||||
case ValueOpcode::DataConsume: return SharedAccess::Consume;
|
||||
@@ -182,6 +183,7 @@ SharedAccess SharedAccessOf(ValueOpcode opcode) {
|
||||
|
||||
uint32_t SharedComponentCount(ValueOpcode opcode) {
|
||||
switch (opcode) {
|
||||
case ValueOpcode::SharedAtomicOr64:
|
||||
case ValueOpcode::LoadSharedU32x2:
|
||||
case ValueOpcode::WriteSharedU32x2: return 2u;
|
||||
case ValueOpcode::LoadSharedU32x3:
|
||||
|
||||
@@ -276,6 +276,7 @@ VALUE_OPCODE(SharedAtomicSMax32, U32, U32, U32, U1)
|
||||
VALUE_OPCODE(SharedAtomicUMax32, U32, U32, U32, U1)
|
||||
VALUE_OPCODE(SharedAtomicAnd32, U32, U32, U32, U1)
|
||||
VALUE_OPCODE(SharedAtomicOr32, U32, U32, U32, U1)
|
||||
VALUE_OPCODE(SharedAtomicOr64, Void, U32, U64, U1)
|
||||
VALUE_OPCODE(SharedAtomicXor32, U32, U32, U32, U1)
|
||||
VALUE_OPCODE(DataAppend, U32, U32, U1)
|
||||
VALUE_OPCODE(DataConsume, U32, U32, U1)
|
||||
|
||||
@@ -326,6 +326,8 @@ void CollectBuiltinInputs(const Program& program, ShaderInfo& info) {
|
||||
}
|
||||
|
||||
void CollectOutputs(const Program& program, ShaderStageInputInfo input_info, ShaderInfo& info) {
|
||||
const bool alpha_remap = program.stage == ShaderType::Pixel && input_info.pixel != nullptr &&
|
||||
input_info.pixel->alpha_blend_source_remap;
|
||||
for (const auto* block: program.blocks) {
|
||||
for (const auto& inst: *block) {
|
||||
if (inst.GetOpcode() != ValueOpcode::SetAttribute) {
|
||||
@@ -385,8 +387,14 @@ void CollectOutputs(const Program& program, ShaderStageInputInfo input_info, Sha
|
||||
export_info.index, fmt::format("out_param_{}", export_info.index));
|
||||
break;
|
||||
case ExportTargetKind::Mrt:
|
||||
if (alpha_remap && export_info.index != 0) {
|
||||
break;
|
||||
}
|
||||
AddOutput(info, StageOutputKind::Mrt, export_info.index, export_info.index,
|
||||
fmt::format("out_mrt_{}", export_info.index));
|
||||
if (alpha_remap) {
|
||||
AddOutput(info, StageOutputKind::Mrt, 1, 1, "out_mrt_1");
|
||||
}
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
@@ -336,14 +336,15 @@ static void ShaderDetectBuffers(ShaderVertexInputInfo& info) {
|
||||
for (int ri = 0; ri < info.resources_num; ri++) {
|
||||
const auto& r = info.resources[ri];
|
||||
|
||||
bool merged = false;
|
||||
for (int bi = 0; bi < info.buffers_num; bi++) {
|
||||
auto& destination = info.resources_dst[ri];
|
||||
int bi = 0;
|
||||
for (; bi < info.buffers_num; bi++) {
|
||||
auto& b = info.buffers[bi];
|
||||
|
||||
uint64_t stride = b.stride;
|
||||
|
||||
if (stride == r.Stride() &&
|
||||
b.fetch_index == static_cast<uint32_t>(info.resources_dst[ri].fetch_index)) {
|
||||
b.fetch_index == destination.fetch_index) {
|
||||
uint64_t rbase = r.Base48();
|
||||
uint64_t base = std::min(rbase, b.addr);
|
||||
uint64_t offset1 = rbase - base;
|
||||
@@ -352,45 +353,33 @@ static void ShaderDetectBuffers(ShaderVertexInputInfo& info) {
|
||||
if (offset1 < stride && offset2 < stride) {
|
||||
EXIT_NOT_IMPLEMENTED(b.num_records != r.NumRecords());
|
||||
b.addr = base;
|
||||
EXIT_NOT_IMPLEMENTED(b.attr_num >= ShaderVertexInputBuffer::ATTR_MAX);
|
||||
b.attr_indices[b.attr_num++] = ri;
|
||||
merged = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!merged) {
|
||||
if (bi == info.buffers_num) {
|
||||
EXIT_NOT_IMPLEMENTED(info.buffers_num >= ShaderVertexInputInfo::RES_MAX);
|
||||
int bi = info.buffers_num++;
|
||||
info.buffers[bi].addr = r.Base48();
|
||||
info.buffers[bi].stride = r.Stride();
|
||||
info.buffers[bi].num_records = r.NumRecords();
|
||||
info.buffers[bi].fetch_index = info.resources_dst[ri].fetch_index;
|
||||
info.buffers[bi].attr_num = 1;
|
||||
info.buffers[bi].attr_indices[0] = ri;
|
||||
}
|
||||
}
|
||||
|
||||
for (int bi = 0; bi < info.buffers_num; bi++) {
|
||||
auto& b = info.buffers[bi];
|
||||
for (int ri = 0; ri < b.attr_num; ri++) {
|
||||
b.attr_offsets[ri] = info.resources[b.attr_indices[ri]].Base48() - b.addr;
|
||||
info.buffers[bi] = {.addr = r.Base48(),
|
||||
.stride = r.Stride(),
|
||||
.num_records = r.NumRecords(),
|
||||
.fetch_index = destination.fetch_index};
|
||||
info.buffers_num++;
|
||||
}
|
||||
destination.buffer_index = bi;
|
||||
}
|
||||
}
|
||||
|
||||
static void ShaderApplyAttribSemantics(ShaderVertexInputInfo& info,
|
||||
const ShaderSemantic* input_semantics,
|
||||
uint32_t num_input_semantics, const uint32_t* attrib,
|
||||
const uint32_t* buffer) {
|
||||
std::span<const ShaderSemantic> input_semantics,
|
||||
const uint32_t* attrib, const uint32_t* buffer) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
|
||||
EXIT_IF(attrib == nullptr || buffer == nullptr);
|
||||
|
||||
const bool debug_dump = Config::GraphicsDebugDumpEnabled();
|
||||
|
||||
for (uint32_t i = 0; i < num_input_semantics; i++) {
|
||||
for (uint32_t i = 0; i < input_semantics.size(); i++) {
|
||||
const auto& in = input_semantics[i];
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(in.static_vb_index == 1 || in.static_attribute == 1);
|
||||
@@ -544,8 +533,7 @@ static bool ShaderGetStaticVertexInputInfo(uint64_t shader_addr, const HW::UserS
|
||||
shader_addr);
|
||||
return false;
|
||||
}
|
||||
ShaderApplyAttribSemantics(info, metadata.input_semantics.data(),
|
||||
metadata.input_semantics_count, attrib, buffer);
|
||||
ShaderApplyAttribSemantics(info, metadata.input_semantics, attrib, buffer);
|
||||
ShaderDetectBuffers(info);
|
||||
}
|
||||
return true;
|
||||
@@ -678,15 +666,8 @@ void BuildStageStaticKey(const ShaderVertexInputInfo& info, std::vector<uint32_t
|
||||
key.push_back(destination.registers_num);
|
||||
key.push_back(destination.fetch_index);
|
||||
key.push_back(static_cast<uint32_t>(destination.attr_id));
|
||||
key.push_back(resource.Stride());
|
||||
key.push_back(static_cast<uint32_t>(resource.SwizzleEnabled()));
|
||||
key.push_back(resource.DstSelX());
|
||||
key.push_back(resource.DstSelY());
|
||||
key.push_back(resource.DstSelZ());
|
||||
key.push_back(resource.DstSelW());
|
||||
key.push_back(resource.RawFormat());
|
||||
key.push_back(resource.OutOfBounds());
|
||||
key.push_back(static_cast<uint32_t>(resource.AddTid()));
|
||||
key.push_back(resource.fields[1] & 0xbfff0000u); // Stride and swizzle enable.
|
||||
key.push_back(resource.fields[3] & 0x3087ffffu); // Channels, format, OOB, and add TID.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -712,6 +693,7 @@ void BuildStageStaticKey(const ShaderPixelInputInfo& info, std::vector<uint32_t>
|
||||
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.push_back(static_cast<uint32_t>(info.alpha_blend_source_remap));
|
||||
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;
|
||||
@@ -907,8 +889,9 @@ void ShaderDbgDumpInputInfo(const ShaderVertexInputInfo& info) {
|
||||
|
||||
LOGF("\t\t register_start = %d\n"
|
||||
"\t\t registers_num = %d\n"
|
||||
"\t\t fetch_index = %" PRIu32 "\n",
|
||||
rd.register_start, rd.registers_num, rd.fetch_index);
|
||||
"\t\t fetch_index = %" PRIu32 "\n"
|
||||
"\t\t buffer_index = %d\n",
|
||||
rd.register_start, rd.registers_num, rd.fetch_index, rd.buffer_index);
|
||||
LOGF("\t\t fields = %08" PRIx32 "%08" PRIx32 "%08" PRIx32 "%08" PRIx32 "\n",
|
||||
r.fields[3], r.fields[2], r.fields[1], r.fields[0]);
|
||||
LOGF("\t\t Base() = %" PRIx64 "\n"
|
||||
@@ -934,14 +917,8 @@ void ShaderDbgDumpInputInfo(const ShaderVertexInputInfo& info) {
|
||||
LOGF("\t\t addr = %" PRIx64 "\n"
|
||||
"\t\t stride = %" PRIu32 "\n"
|
||||
"\t\t num_records = %" PRIu32 "\n"
|
||||
"\t\t fetch_index = %" PRIu32 "\n"
|
||||
"\t\t attr_num = %" PRId32 "\n",
|
||||
r.addr, r.stride, r.num_records, r.fetch_index, r.attr_num);
|
||||
for (int j = 0; j < r.attr_num; j++) {
|
||||
LOGF("\t\t attr_indices[%d] = %d\n"
|
||||
"\t\t attr_offsets[%d] = %u\n",
|
||||
j, r.attr_indices[j], j, r.attr_offsets[j]);
|
||||
}
|
||||
"\t\t fetch_index = %" PRIu32 "\n",
|
||||
r.addr, r.stride, r.num_records, r.fetch_index);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -178,6 +178,8 @@ struct ShaderPixelInputInfo {
|
||||
bool ps_sample_mask_export_enable = false;
|
||||
bool ps_sample_shading = false;
|
||||
bool dual_source_blending = false;
|
||||
// Export logical alpha through MRT1 for blending after channel swizzling.
|
||||
bool alpha_blend_source_remap = false;
|
||||
bool ps_early_z = false;
|
||||
bool ps_execute_on_noop = false;
|
||||
ShaderStageRuntime stage;
|
||||
|
||||
@@ -174,15 +174,10 @@ struct ShaderSamplerResource {
|
||||
};
|
||||
|
||||
struct ShaderVertexInputBuffer {
|
||||
static constexpr int ATTR_MAX = 32;
|
||||
|
||||
uint64_t addr = 0;
|
||||
uint32_t stride = 0;
|
||||
uint32_t num_records = 0;
|
||||
uint32_t fetch_index = 0;
|
||||
int attr_num = 0;
|
||||
int attr_indices[ATTR_MAX] = {0};
|
||||
uint32_t attr_offsets[ATTR_MAX] = {0};
|
||||
uint64_t addr = 0;
|
||||
uint32_t stride = 0;
|
||||
uint32_t num_records = 0;
|
||||
uint32_t fetch_index = 0;
|
||||
};
|
||||
|
||||
struct ShaderVertexDestination {
|
||||
@@ -190,6 +185,7 @@ struct ShaderVertexDestination {
|
||||
int registers_num = 0;
|
||||
int attr_id = -1;
|
||||
uint32_t fetch_index = 0;
|
||||
int buffer_index = 0;
|
||||
};
|
||||
|
||||
enum class ShaderStorageUsage {
|
||||
|
||||
@@ -68,13 +68,10 @@ bool ShaderReadVertexMetadata(const ShaderMappedData& data, uint32_t max_user_sg
|
||||
return ShaderError::Fail(error, "vertex semantic count is outside the supported domain");
|
||||
}
|
||||
|
||||
const auto semantic_size =
|
||||
static_cast<uint64_t>(data.num_input_semantics) * sizeof(ShaderSemantic);
|
||||
if (data.input_semantics == nullptr) {
|
||||
return ShaderError::Fail(error, "missing vertex input semantics");
|
||||
}
|
||||
std::memcpy(next.input_semantics.data(), data.input_semantics, semantic_size);
|
||||
next.input_semantics_count = data.num_input_semantics;
|
||||
next.input_semantics = {data.input_semantics, data.num_input_semantics};
|
||||
metadata = next;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -3,19 +3,18 @@
|
||||
|
||||
#include "graphics/shader/shader.h"
|
||||
|
||||
#include <array>
|
||||
#include <span>
|
||||
#include <string>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
struct ShaderVertexMetadata {
|
||||
int vertex_buffer_reg = -1;
|
||||
int vertex_attrib_reg = -1;
|
||||
std::array<ShaderSemantic, ShaderVertexInputInfo::RES_MAX> input_semantics {};
|
||||
uint32_t input_semantics_count = 0;
|
||||
int vertex_buffer_reg = -1;
|
||||
int vertex_attrib_reg = -1;
|
||||
std::span<const ShaderSemantic> input_semantics;
|
||||
};
|
||||
|
||||
// Copies the small AGC metadata subset used by the vertex path after validating every guest range.
|
||||
// Validates the vertex metadata and returns a view into the shader header.
|
||||
bool ShaderReadVertexMetadata(const ShaderMappedData& data, uint32_t max_user_sgprs,
|
||||
ShaderVertexMetadata& metadata, std::string* error);
|
||||
|
||||
|
||||
+43
-14
@@ -1,5 +1,6 @@
|
||||
#include "kernel/fileSystem.h"
|
||||
|
||||
#include "common/archive.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/common.h"
|
||||
#include "common/dateTime.h"
|
||||
@@ -41,6 +42,7 @@ public:
|
||||
struct MountPair {
|
||||
std::filesystem::path dir;
|
||||
std::string point;
|
||||
std::shared_ptr<Common::ArchiveReader> archive;
|
||||
};
|
||||
|
||||
MountPoints() { EXIT_NOT_IMPLEMENTED(!Common::Thread::IsMainThread()); }
|
||||
@@ -260,6 +262,7 @@ void MountPoints::Mount(const std::filesystem::path& folder, const std::string&
|
||||
MountPair p;
|
||||
p.dir = folder;
|
||||
p.point = point_str;
|
||||
p.archive = Common::OpenArchive(folder);
|
||||
|
||||
m_mount_pairs.push_back(std::move(p));
|
||||
}
|
||||
@@ -346,11 +349,13 @@ std::filesystem::path MountPoints::ResolvePath(const std::string& mounted_name)
|
||||
}
|
||||
|
||||
const auto native_rel_path = Common::PathFromUtf8(rel_path);
|
||||
if (Common::IsArchivePath(p.dir)) {
|
||||
return p.dir / native_rel_path;
|
||||
}
|
||||
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
if (HasWindowsForbiddenFilenameCharacter(rel_path)) {
|
||||
::printf("FileSystem: Windows-incompatible guest filename: %s\n",
|
||||
mounted_name.c_str());
|
||||
::printf("FileSystem: Windows-incompatible guest filename: %s\n", mounted_name.c_str());
|
||||
}
|
||||
return p.dir / native_rel_path;
|
||||
#else
|
||||
@@ -358,7 +363,8 @@ std::filesystem::path MountPoints::ResolvePath(const std::string& mounted_name)
|
||||
#endif
|
||||
}
|
||||
|
||||
return mounted_name;
|
||||
// Unmounted guest paths must not fall through to the host filesystem.
|
||||
return {};
|
||||
}
|
||||
|
||||
void Initialize() {
|
||||
@@ -461,14 +467,24 @@ int KYTY_SYSV_ABI KernelOpen(const char* path, int flags, uint16_t mode) {
|
||||
}
|
||||
|
||||
file->real_name = g_mount_points->ResolvePath(file->name);
|
||||
if (file->real_name.empty()) {
|
||||
g_files->DeleteDescriptor(descriptor);
|
||||
return KERNEL_ERROR_ENOENT;
|
||||
}
|
||||
if (Common::IsArchivePath(file->real_name) &&
|
||||
(rw_mode != Common::File::Mode::Read || trunc || creat)) {
|
||||
g_files->DeleteDescriptor(descriptor);
|
||||
return KERNEL_ERROR_EROFS;
|
||||
}
|
||||
|
||||
if (trunc && rw_mode == Common::File::Mode::Read) {
|
||||
g_files->DeleteDescriptor(descriptor);
|
||||
return KERNEL_ERROR_EACCES;
|
||||
}
|
||||
|
||||
bool dir_exist = Common::File::IsDirectoryExisting(file->real_name);
|
||||
bool file_exist = Common::File::IsFileExisting(file->real_name);
|
||||
const auto info = Common::File::GetInfo(file->real_name);
|
||||
const bool dir_exist = info && !info->is_file;
|
||||
const bool file_exist = info && info->is_file;
|
||||
|
||||
// A missing path opened without O_CREAT is ENOENT
|
||||
if (!creat && !dir_exist && !file_exist) {
|
||||
@@ -666,7 +682,7 @@ int64_t KYTY_SYSV_ABI KernelWrite(int d, const void* buf, size_t nbytes) {
|
||||
|
||||
auto* file = g_files->GetFile(d);
|
||||
|
||||
if (file == nullptr) {
|
||||
if (file == nullptr || !file->opened || !file->writable) {
|
||||
return KERNEL_ERROR_EBADF;
|
||||
}
|
||||
|
||||
@@ -881,7 +897,7 @@ int64_t KYTY_SYSV_ABI KernelPwrite(int d, const void* buf, size_t nbytes, int64_
|
||||
|
||||
auto* file = g_files->GetFile(d);
|
||||
|
||||
if (file == nullptr) {
|
||||
if (file == nullptr || !file->opened || !file->writable) {
|
||||
return KERNEL_ERROR_EBADF;
|
||||
}
|
||||
|
||||
@@ -1058,15 +1074,13 @@ int KYTY_SYSV_ABI KernelStat(const char* path, FileStat* sb) {
|
||||
|
||||
auto real_file_name = g_mount_points->ResolvePath(path);
|
||||
|
||||
bool is_dir = Common::File::IsDirectoryExisting(real_file_name);
|
||||
bool is_file = Common::File::IsFileExisting(real_file_name);
|
||||
|
||||
if (!is_dir && !is_file) {
|
||||
const auto info = Common::File::GetInfo(real_file_name);
|
||||
if (!info) {
|
||||
LOGF("\t file not found\n");
|
||||
return KERNEL_ERROR_ENOENT;
|
||||
}
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(is_dir && is_file);
|
||||
const bool is_dir = !info->is_file;
|
||||
|
||||
FileStat stat {};
|
||||
stat.st_mode = 0000777u | (is_dir ? 0040000u : 0100000u);
|
||||
@@ -1080,7 +1094,7 @@ int KYTY_SYSV_ABI KernelStat(const char* path, FileStat* sb) {
|
||||
stat.st_blksize = 512;
|
||||
stat.st_blocks = stat.st_size / 512;
|
||||
} else {
|
||||
stat.st_size = static_cast<int64_t>(Common::File::Size(real_file_name));
|
||||
stat.st_size = static_cast<int64_t>(info->size);
|
||||
stat.st_blksize = 512;
|
||||
stat.st_blocks = (stat.st_size + 511) / 512;
|
||||
|
||||
@@ -1217,6 +1231,9 @@ int KYTY_SYSV_ABI KernelUnlink(const char* path) {
|
||||
}
|
||||
|
||||
auto real_file_name = g_mount_points->ResolvePath(path);
|
||||
if (Common::IsArchivePath(real_file_name)) {
|
||||
return KERNEL_ERROR_EROFS;
|
||||
}
|
||||
|
||||
bool is_dir = Common::File::IsDirectoryExisting(real_file_name);
|
||||
bool is_file = Common::File::IsFileExisting(real_file_name);
|
||||
@@ -1254,8 +1271,11 @@ int KYTY_SYSV_ABI KernelRename(const char* from, const char* to) {
|
||||
auto to_path = std::string(to);
|
||||
auto real_from = g_mount_points->ResolvePath(from_path);
|
||||
auto real_to = g_mount_points->ResolvePath(to_path);
|
||||
if (Common::IsArchivePath(real_from) || Common::IsArchivePath(real_to)) {
|
||||
return KERNEL_ERROR_EROFS;
|
||||
}
|
||||
|
||||
if (!Common::File::IsFileExisting(real_from)) {
|
||||
if (real_to.empty() || !Common::File::IsFileExisting(real_from)) {
|
||||
return KERNEL_ERROR_ENOENT;
|
||||
}
|
||||
|
||||
@@ -1335,6 +1355,12 @@ int KYTY_SYSV_ABI KernelMkdir(const char* path, uint16_t mode) {
|
||||
path, mode);
|
||||
|
||||
auto real_name = g_mount_points->ResolvePath(std::string(path));
|
||||
if (real_name.empty()) {
|
||||
return KERNEL_ERROR_ENOENT;
|
||||
}
|
||||
if (Common::IsArchivePath(real_name)) {
|
||||
return KERNEL_ERROR_EROFS;
|
||||
}
|
||||
|
||||
if (Common::File::IsDirectoryExisting(real_name)) {
|
||||
return KERNEL_ERROR_EEXIST;
|
||||
@@ -1361,6 +1387,9 @@ int KYTY_SYSV_ABI KernelRmdir(const char* path) {
|
||||
LOGF("\t path = %s\n", path);
|
||||
|
||||
auto real_name = g_mount_points->ResolvePath(std::string(path));
|
||||
if (Common::IsArchivePath(real_name)) {
|
||||
return KERNEL_ERROR_EROFS;
|
||||
}
|
||||
|
||||
if (!Common::File::IsDirectoryExisting(real_name)) {
|
||||
return KERNEL_ERROR_ENOENT;
|
||||
|
||||
@@ -54,6 +54,7 @@ struct Lifecycle {
|
||||
|
||||
void Mount(const std::filesystem::path& folder, const std::string& point);
|
||||
void Umount(const std::string& folder_or_point);
|
||||
// Returns an empty path when the guest path has no mounted filesystem.
|
||||
std::filesystem::path GetRealFilename(const std::string& mounted_file_name);
|
||||
|
||||
int KYTY_SYSV_ABI KernelOpen(const char* path, int flags, uint16_t mode);
|
||||
|
||||
+1
-12
@@ -963,18 +963,7 @@ static bool IsInPrtAperture(uint64_t address, uint64_t size = 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TryReadPrtBacking(uint64_t vaddr, void* data, uint64_t size) {
|
||||
std::vector<VirtualRanges::Range> ranges;
|
||||
if (g_guest_address_space == nullptr || g_virtual_ranges == nullptr ||
|
||||
!IsInPrtAperture(vaddr, size) || !g_virtual_ranges->QuerySpan(vaddr, size, &ranges)) {
|
||||
return false;
|
||||
}
|
||||
if (std::any_of(ranges.begin(), ranges.end(), [](const auto& range) {
|
||||
return !IsReservedRangeType(range.type) &&
|
||||
!g_guest_address_space->BackingContains(range.start, range.size);
|
||||
})) {
|
||||
return false;
|
||||
}
|
||||
bool TryReadSparseBacking(uint64_t vaddr, void* data, uint64_t size) {
|
||||
return g_guest_address_space->TryReadSparseBacking(vaddr, data, size);
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -115,7 +115,7 @@ int MapAutomaticMemory(uint64_t vaddr, size_t size, int type, int prot);
|
||||
bool TryWriteBacking(uint64_t vaddr, const void* data, uint64_t size);
|
||||
bool TryReadBacking(uint64_t vaddr, void* data, uint64_t size);
|
||||
bool TryReadGpuCleanBacking(uint64_t vaddr, void* data, uint64_t size);
|
||||
bool TryReadPrtBacking(uint64_t vaddr, void* data, uint64_t size);
|
||||
bool TryReadSparseBacking(uint64_t vaddr, void* data, uint64_t size);
|
||||
[[nodiscard]] uint64_t ClampRangeSize(uint64_t vaddr, uint64_t size);
|
||||
void WriteBacking(uint64_t vaddr, const void* data, uint64_t size) noexcept;
|
||||
void InvalidateMemory(uint64_t vaddr, uint64_t size);
|
||||
|
||||
@@ -212,14 +212,21 @@ public:
|
||||
}
|
||||
|
||||
auto* destination = static_cast<uint8_t*>(data);
|
||||
std::memset(destination, 0, size);
|
||||
auto cursor = vaddr;
|
||||
for (auto mapping = first; mapping != m_maps.end() && mapping->second.vaddr < end;
|
||||
++mapping) {
|
||||
const auto& entry = mapping->second;
|
||||
const auto start = std::max(vaddr, entry.vaddr);
|
||||
const auto bytes = std::min(end, entry.vaddr + entry.size) - start;
|
||||
const auto offset = entry.backing_offset + start - entry.vaddr;
|
||||
std::memcpy(destination + start - vaddr, m_backing_base + offset, bytes);
|
||||
if (cursor < start) {
|
||||
std::memset(destination + (cursor - vaddr), 0, start - cursor);
|
||||
}
|
||||
std::memcpy(destination + (start - vaddr), m_backing_base + offset, bytes);
|
||||
cursor = start + bytes;
|
||||
}
|
||||
if (cursor < end) {
|
||||
std::memset(destination + (cursor - vaddr), 0, end - cursor);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -731,6 +738,20 @@ public:
|
||||
}
|
||||
|
||||
bool TryReadSparseBacking(uint64_t vaddr, void* data, uint64_t size) {
|
||||
if (!Owns(vaddr, size)) {
|
||||
return false;
|
||||
}
|
||||
std::lock_guard lock(m_mutex);
|
||||
auto mapping = m_mapped.upper_bound(vaddr);
|
||||
if (mapping != m_mapped.begin()) {
|
||||
--mapping;
|
||||
}
|
||||
for (; mapping != m_mapped.end() && mapping->second.vaddr < vaddr + size; ++mapping) {
|
||||
const auto& region = mapping->second;
|
||||
if (region.type == MappingType::Private && region.vaddr + region.size > vaddr) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return m_backing->TryReadSparseBacking(vaddr, data, size);
|
||||
}
|
||||
|
||||
|
||||
+1
-30
@@ -512,15 +512,6 @@ static void SchedulerBackoffOnce() {
|
||||
#endif
|
||||
}
|
||||
|
||||
static bool SleepMicroSchedulerBackoff(uint64_t microseconds) {
|
||||
if (microseconds > 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SchedulerBackoffOnce();
|
||||
return true;
|
||||
}
|
||||
|
||||
static void SleepMicroWithSignalPoll(uint64_t microseconds) {
|
||||
if (microseconds == 0) {
|
||||
KernelDispatchPendingSignalForCurrentThread();
|
||||
@@ -529,9 +520,7 @@ static void SleepMicroWithSignalPoll(uint64_t microseconds) {
|
||||
|
||||
while (microseconds > 0) {
|
||||
const auto step = std::min<uint64_t>(microseconds, SIGNAL_APC_POLL_MICROS);
|
||||
if (!SleepMicroSchedulerBackoff(step)) {
|
||||
Common::Thread::SleepMicro(step);
|
||||
}
|
||||
Common::Thread::SleepMicro(step);
|
||||
microseconds -= step;
|
||||
KernelDispatchPendingSignalForCurrentThread();
|
||||
}
|
||||
@@ -3842,28 +3831,16 @@ void KYTY_SYSV_ABI KernelSetThreadDtors(thread_dtors_func_t dtors) {
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI KernelUsleep(KernelUseconds microseconds) {
|
||||
Common::Timer t;
|
||||
t.Start();
|
||||
SleepMicroWithSignalPoll(microseconds);
|
||||
// double ts = t.GetTimeS();
|
||||
// LOGF("\tactual: %g microseconds\n", ts * 1000000.0);
|
||||
return OK;
|
||||
}
|
||||
|
||||
unsigned int KYTY_SYSV_ABI KernelSleep(unsigned int seconds) {
|
||||
PRINT_NAME();
|
||||
LOGF("\tsleep: %u\n", seconds);
|
||||
Common::Timer t;
|
||||
t.Start();
|
||||
SleepMicroWithSignalPoll(static_cast<uint64_t>(seconds) * 1000000ull);
|
||||
double ts = t.GetTimeS();
|
||||
LOGF("\tactual: %g seconds\n", ts);
|
||||
return OK;
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI KernelNanosleep(const KernelTimespec* rqtp, KernelTimespec* rmtp) {
|
||||
PRINT_NAME();
|
||||
|
||||
if (rqtp == nullptr) {
|
||||
return KERNEL_ERROR_EFAULT;
|
||||
}
|
||||
@@ -3880,13 +3857,7 @@ int KYTY_SYSV_ABI KernelNanosleep(const KernelTimespec* rqtp, KernelTimespec* rm
|
||||
uint64_t nanos =
|
||||
static_cast<uint64_t>(rqtp->tv_sec) * 1000000000ull + static_cast<uint64_t>(rqtp->tv_nsec);
|
||||
|
||||
LOGF("\tnanosleep: %" PRIu64 "\n", nanos);
|
||||
|
||||
Common::Timer t;
|
||||
t.Start();
|
||||
SleepNanoWithSignalPoll(nanos);
|
||||
double ts = t.GetTimeS();
|
||||
LOGF("\tactual: %g nanoseconds\n", ts * 1000000000.0);
|
||||
|
||||
if (rmtp != nullptr) {
|
||||
rmtp->tv_sec = 0;
|
||||
|
||||
@@ -20,7 +20,7 @@ add_executable(launcher WIN32
|
||||
)
|
||||
|
||||
|
||||
target_link_libraries(launcher Qt6::Concurrent Qt6::Network Qt6::Widgets Vulkan::Headers SDL3::SDL3-static kyty_git_version)
|
||||
target_link_libraries(launcher Qt6::Concurrent Qt6::Network Qt6::Widgets Vulkan::Headers SDL3::SDL3-static kyty_git_version common)
|
||||
if(WIN32)
|
||||
target_link_libraries(launcher setupapi)
|
||||
endif()
|
||||
|
||||
@@ -72,6 +72,27 @@
|
||||
<property name="toolTip"><string>Language</string></property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_controller_color">
|
||||
<property name="text"><string>DualSense lightbar:</string></property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<layout class="QHBoxLayout" name="controllerColorLayout">
|
||||
<item>
|
||||
<widget class="QPushButton" name="button_controller_color">
|
||||
<property name="toolTip"><string>Choose and preview a fixed LED color. Bluetooth DualSense may take several seconds to update.</string></property>
|
||||
<property name="text"><string>Custom</string></property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="button_controller_color_reset">
|
||||
<property name="toolTip"><string>Let the game control the lightbar</string></property>
|
||||
<property name="text"><string>Default</string></property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QChar>
|
||||
#include <QColor>
|
||||
#include <QMetaEnum>
|
||||
#include <QMetaType>
|
||||
#include <QObject>
|
||||
@@ -88,6 +89,7 @@ public:
|
||||
QString user_name = "Kyty";
|
||||
int user_id = Config::DEFAULT_USER_ID;
|
||||
QString audio_input_device;
|
||||
QString controller_color;
|
||||
PresentMode present_mode = PresentMode::Mailbox;
|
||||
int gpu_index = -1;
|
||||
bool fullscreen_enabled = false;
|
||||
@@ -119,6 +121,7 @@ public:
|
||||
user_name = other.user_name;
|
||||
user_id = other.user_id;
|
||||
audio_input_device = other.audio_input_device;
|
||||
controller_color = other.controller_color;
|
||||
present_mode = other.present_mode;
|
||||
gpu_index = other.gpu_index;
|
||||
fullscreen_enabled = other.fullscreen_enabled;
|
||||
@@ -165,6 +168,7 @@ public:
|
||||
KYTY_CFG_SET(user_name);
|
||||
KYTY_CFG_SET(user_id);
|
||||
KYTY_CFG_SET(audio_input_device);
|
||||
KYTY_CFG_SET(controller_color);
|
||||
KYTY_CFG_SET(present_mode);
|
||||
KYTY_CFG_SET(gpu_index);
|
||||
KYTY_CFG_SET(fullscreen_enabled);
|
||||
@@ -204,6 +208,8 @@ public:
|
||||
? saved_user_id
|
||||
: Config::DEFAULT_USER_ID;
|
||||
audio_input_device = s->value("audio_input_device", audio_input_device).toString();
|
||||
const QColor color(s->value("controller_color", controller_color).toString());
|
||||
controller_color = color.isValid() ? color.name(QColor::HexRgb) : QString {};
|
||||
KYTY_CFG_GET(present_mode);
|
||||
gpu_index = s->value("gpu_index", -1).toInt();
|
||||
if (EnumToText(present_mode).isEmpty()) {
|
||||
|
||||
@@ -31,6 +31,9 @@ public:
|
||||
void SetGameDirectories(const QStringList& dirs);
|
||||
[[nodiscard]] QStringList GetGameDirectories() const;
|
||||
|
||||
signals:
|
||||
void PreviewControllerColor(const QString& color);
|
||||
|
||||
private:
|
||||
Ui::ConfigurationEditDialog* m_ui = nullptr;
|
||||
Configuration& m_info;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#ifndef CONFIGURATION_ITEM_H
|
||||
#define CONFIGURATION_ITEM_H
|
||||
|
||||
#include <QIcon>
|
||||
#include <QObject>
|
||||
#include <QTreeWidgetItem>
|
||||
|
||||
@@ -18,7 +19,7 @@ public:
|
||||
explicit ConfigurationItem(std::unique_ptr<Configuration> info, QTreeWidget* parent);
|
||||
~ConfigurationItem() override;
|
||||
|
||||
void Update();
|
||||
void Update(bool reload_icon = false);
|
||||
bool operator<(const QTreeWidgetItem& other) const override;
|
||||
|
||||
Configuration& GetInfo() { return *m_info; }
|
||||
@@ -35,6 +36,7 @@ private:
|
||||
void UpdateStatusIndicator();
|
||||
|
||||
std::unique_ptr<Configuration> m_info;
|
||||
QIcon m_icon;
|
||||
bool m_running = false;
|
||||
QComboBox* m_status_combo = nullptr;
|
||||
QLabel* m_status_indicator = nullptr;
|
||||
|
||||
@@ -33,6 +33,9 @@ public:
|
||||
ConfigurationItem* GetSelectedItem() { return m_selected_item; }
|
||||
|
||||
[[nodiscard]] const QString& GetSettingsFile() const { return m_settings_file; }
|
||||
[[nodiscard]] const QString& GetGlobalControllerColor() const {
|
||||
return m_global_info.controller_color;
|
||||
}
|
||||
[[nodiscard]] std::unique_ptr<Configuration>
|
||||
CreateConfiguration(const ConfigurationItem& item) const;
|
||||
|
||||
@@ -44,6 +47,7 @@ signals:
|
||||
|
||||
void Run();
|
||||
void Select();
|
||||
void PreviewControllerColor(const QString& color);
|
||||
|
||||
protected:
|
||||
void changeEvent(QEvent* event) override;
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
#ifndef CONTROLLER_LIGHTBAR_H
|
||||
#define CONTROLLER_LIGHTBAR_H
|
||||
|
||||
#include <QColor>
|
||||
#include <QHash>
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QTimer>
|
||||
|
||||
#include <SDL3/SDL_gamepad.h>
|
||||
|
||||
class ControllerLightbar final: public QObject {
|
||||
public:
|
||||
explicit ControllerLightbar(QObject* parent = nullptr);
|
||||
~ControllerLightbar() override;
|
||||
|
||||
void SetColor(const QString& color);
|
||||
void Stop();
|
||||
|
||||
private:
|
||||
void Refresh();
|
||||
void ApplyColor(SDL_Gamepad* gamepad) const;
|
||||
|
||||
QTimer m_timer;
|
||||
QHash<SDL_JoystickID, SDL_Gamepad*> m_gamepads;
|
||||
QColor m_color;
|
||||
};
|
||||
|
||||
#endif // CONTROLLER_LIGHTBAR_H
|
||||
@@ -0,0 +1,28 @@
|
||||
#ifndef GAME_CONTENT_H
|
||||
#define GAME_CONTENT_H
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
|
||||
#include <cstdint>
|
||||
#include <filesystem>
|
||||
|
||||
namespace GameContent {
|
||||
|
||||
inline constexpr uint64_t MaxMetadataSize = uint64_t {1} << 20u;
|
||||
inline constexpr uint64_t MaxImageSize = uint64_t {32} << 20u;
|
||||
inline constexpr uint64_t MaxTrophyPackageSize = uint64_t {128} << 20u;
|
||||
|
||||
[[nodiscard]] std::filesystem::path ToPath(const QString& path);
|
||||
[[nodiscard]] QString FromPath(const std::filesystem::path& path);
|
||||
[[nodiscard]] bool IsArchive(const QString& base);
|
||||
[[nodiscard]] std::filesystem::path Resolve(const QString& base, const QString& relative);
|
||||
[[nodiscard]] bool FileExists(const QString& base, const QString& relative);
|
||||
[[nodiscard]] QByteArray ReadPath(const std::filesystem::path& path, uint64_t max_size);
|
||||
[[nodiscard]] QByteArray ReadFile(const QString& base, const QString& relative, uint64_t max_size);
|
||||
[[nodiscard]] QStringList ListFiles(const QString& base, const QString& relative);
|
||||
|
||||
} // namespace GameContent
|
||||
|
||||
#endif // GAME_CONTENT_H
|
||||
@@ -1,6 +1,8 @@
|
||||
#ifndef GAME_LIST_TREE_WIDGET_H
|
||||
#define GAME_LIST_TREE_WIDGET_H
|
||||
|
||||
#include "gameContent.h"
|
||||
|
||||
#include <QPaintEvent>
|
||||
#include <QPainter>
|
||||
#include <QPalette>
|
||||
@@ -17,14 +19,15 @@ public:
|
||||
viewport()->setAutoFillBackground(false);
|
||||
}
|
||||
|
||||
void SetBackgroundImage(const QString& path) {
|
||||
if (m_path == path) {
|
||||
void SetBackgroundImage(const QString& key) {
|
||||
if (m_path == key) {
|
||||
return;
|
||||
}
|
||||
m_path = path;
|
||||
m_path = key;
|
||||
m_source = QPixmap();
|
||||
if (!path.isEmpty()) {
|
||||
m_source.load(path);
|
||||
if (!key.isEmpty()) {
|
||||
m_source.loadFromData(GameContent::ReadFile(key, QStringLiteral("sce_sys/pic0.png"),
|
||||
GameContent::MaxImageSize));
|
||||
}
|
||||
UpdateScaledBackground();
|
||||
viewport()->update();
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
#include <QAbstractItemView>
|
||||
#include <QCheckBox>
|
||||
#include <QColorDialog>
|
||||
#include <QComboBox>
|
||||
#include <QCoreApplication>
|
||||
#include <QDir>
|
||||
@@ -40,6 +41,22 @@ constexpr char SETTINGS_CFG_DIALOG[] = "ConfigurationEditDialog";
|
||||
constexpr char SETTINGS_CFG_LAST_GEOMETRY[] = "geometry";
|
||||
constexpr int GLOBAL_SETTINGS_GAME_DIRS_MIN_WIDTH = 560;
|
||||
|
||||
static void UpdateControllerColorButton(QPushButton* button, const QString& hex) {
|
||||
const QColor color(hex);
|
||||
if (!color.isValid()) {
|
||||
button->setProperty("controllerColor", QString {});
|
||||
button->setText(QObject::tr("Custom"));
|
||||
button->setStyleSheet({});
|
||||
return;
|
||||
}
|
||||
const auto normalized = color.name(QColor::HexRgb);
|
||||
button->setProperty("controllerColor", normalized);
|
||||
button->setText(normalized.toUpper());
|
||||
button->setStyleSheet(QStringLiteral("background-color: %1; color: %2;")
|
||||
.arg(normalized, color.lightness() < 128 ? QStringLiteral("white")
|
||||
: QStringLiteral("black")));
|
||||
}
|
||||
|
||||
const QStringList CONSOLE_LANGUAGE_NAMES = {
|
||||
"Japanese",
|
||||
"English (United States)",
|
||||
@@ -110,6 +127,21 @@ ConfigurationEditDialog::ConfigurationEditDialog(Configuration& info, QWidget* p
|
||||
connect(m_ui->ok_button, &QPushButton::clicked, this, &ConfigurationEditDialog::save);
|
||||
connect(m_ui->cancel_button, &QPushButton::clicked, this, &QDialog::reject);
|
||||
connect(m_ui->clear_button, &QPushButton::clicked, this, &ConfigurationEditDialog::clear);
|
||||
connect(m_ui->button_controller_color, &QPushButton::clicked, this, [this]() {
|
||||
const auto current = m_ui->button_controller_color->property("controllerColor").toString();
|
||||
const auto initial =
|
||||
current.isEmpty() ? QColor(QStringLiteral("#0070d1")) : QColor(current);
|
||||
const auto color = QColorDialog::getColor(initial, this, tr("DualSense lightbar color"));
|
||||
if (color.isValid()) {
|
||||
const auto hex = color.name(QColor::HexRgb);
|
||||
UpdateControllerColorButton(m_ui->button_controller_color, hex);
|
||||
emit PreviewControllerColor(hex);
|
||||
}
|
||||
});
|
||||
connect(m_ui->button_controller_color_reset, &QPushButton::clicked, this, [this]() {
|
||||
UpdateControllerColorButton(m_ui->button_controller_color, {});
|
||||
emit PreviewControllerColor({});
|
||||
});
|
||||
connect(m_ui->comboBox_shader_log_direction, &QComboBox::currentTextChanged, this,
|
||||
[this](const QString& text) {
|
||||
auto log = TextToEnum<Configuration::LogDirection>(text);
|
||||
@@ -169,6 +201,7 @@ void ConfigurationEditDialog::Init(const Configuration& info) {
|
||||
m_ui->lineEdit_user_name->setMaxLength(static_cast<int>(Config::MAX_USER_NAME_LENGTH));
|
||||
m_ui->lineEdit_user_name->setText(info.user_name);
|
||||
m_ui->spinBox_user_id->setValue(info.user_id);
|
||||
UpdateControllerColorButton(m_ui->button_controller_color, info.controller_color);
|
||||
auto* microphone = m_ui->comboBox_audio_input_device;
|
||||
microphone->clear();
|
||||
microphone->addItem(tr("None"), QString {});
|
||||
@@ -370,6 +403,7 @@ static void UpdateInfo(Configuration& info, Ui::ConfigurationEditDialog& ui) {
|
||||
info.user_name = ui.lineEdit_user_name->text().trimmed();
|
||||
info.user_id = ui.spinBox_user_id->value();
|
||||
info.audio_input_device = ui.comboBox_audio_input_device->currentData().toString();
|
||||
info.controller_color = ui.button_controller_color->property("controllerColor").toString();
|
||||
info.screen_resolution =
|
||||
TextToEnum<Configuration::Resolution>(ui.comboBox_screen_resolution->currentText());
|
||||
info.present_mode =
|
||||
@@ -427,6 +461,7 @@ void ConfigurationEditDialog::save() {
|
||||
void ConfigurationEditDialog::clear() {
|
||||
Configuration default_info;
|
||||
Init(default_info);
|
||||
emit PreviewControllerColor({});
|
||||
|
||||
if (m_show_game_dirs) {
|
||||
m_game_dirs_list->clear();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "configurationItem.h"
|
||||
|
||||
#include "configuration.h"
|
||||
#include "gameContent.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QComboBox>
|
||||
@@ -14,6 +15,7 @@
|
||||
#include <QLabel>
|
||||
#include <QLineEdit>
|
||||
#include <QLocale>
|
||||
#include <QPixmap>
|
||||
#include <QSize>
|
||||
#include <QStringList>
|
||||
#include <QStyle>
|
||||
@@ -154,7 +156,7 @@ ConfigurationItem::ConfigurationItem(std::unique_ptr<Configuration> info, QTreeW
|
||||
m_comment_edit->setFrame(false);
|
||||
parent->setItemWidget(this, CommentsColumn, m_comment_edit);
|
||||
|
||||
Update();
|
||||
Update(true);
|
||||
SetRunning(false);
|
||||
|
||||
setData(SizeColumn, Qt::UserRole, qint64(-1));
|
||||
@@ -170,7 +172,14 @@ ConfigurationItem::ConfigurationItem(std::unique_ptr<Configuration> info, QTreeW
|
||||
watcher->deleteLater();
|
||||
});
|
||||
watcher->setFuture(QtConcurrent::run([path = m_info->basedir]() -> qint64 {
|
||||
if (path.isEmpty() || !QDir(path).exists()) {
|
||||
if (path.isEmpty()) {
|
||||
return -1;
|
||||
}
|
||||
const QFileInfo info(path);
|
||||
if (GameContent::IsArchive(path)) {
|
||||
return info.size();
|
||||
}
|
||||
if (!QDir(path).exists()) {
|
||||
return -1;
|
||||
}
|
||||
qint64 bytes = 0;
|
||||
@@ -188,7 +197,7 @@ ConfigurationItem::ConfigurationItem(std::unique_ptr<Configuration> info, QTreeW
|
||||
|
||||
ConfigurationItem::~ConfigurationItem() = default;
|
||||
|
||||
void ConfigurationItem::Update() {
|
||||
void ConfigurationItem::Update(bool reload_icon) {
|
||||
const auto display_text = GetDisplayText(*m_info);
|
||||
const auto path = GetPathText(*m_info);
|
||||
|
||||
@@ -209,6 +218,12 @@ void ConfigurationItem::Update() {
|
||||
m_comment_edit->setText(m_info->game_comment);
|
||||
}
|
||||
|
||||
if (reload_icon) {
|
||||
const auto icon_data = GameContent::ReadFile(
|
||||
m_info->basedir, QStringLiteral("sce_sys/icon0.png"), GameContent::MaxImageSize);
|
||||
QPixmap icon;
|
||||
m_icon = !icon_data.isEmpty() && icon.loadFromData(icon_data) ? QIcon(icon) : QIcon {};
|
||||
}
|
||||
UpdateIcon();
|
||||
UpdateStatusIndicator();
|
||||
}
|
||||
@@ -267,9 +282,8 @@ void ConfigurationItem::SetCompatibilityEditable(bool editable) {
|
||||
}
|
||||
|
||||
void ConfigurationItem::UpdateIcon() {
|
||||
const QString icon_file = QDir(m_info->basedir).filePath(QStringLiteral("sce_sys/icon0.png"));
|
||||
if (QFileInfo::exists(icon_file)) {
|
||||
setIcon(NameColumn, QIcon(icon_file));
|
||||
if (!m_icon.isNull()) {
|
||||
setIcon(NameColumn, m_icon);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
#include "configurationListWidget.h"
|
||||
|
||||
#include "common/archive.h"
|
||||
#include "compatibilityDatabase.h"
|
||||
#include "configuration.h"
|
||||
#include "configurationEditDialog.h"
|
||||
#include "configurationItem.h"
|
||||
#include "gameContent.h"
|
||||
#include "gameListTreeWidget.h"
|
||||
#include "inputMappingDialog.h"
|
||||
#include "mainDialog.h"
|
||||
@@ -127,15 +129,6 @@ static QStringList SettingsStringList(const QVariant& value) {
|
||||
return text.isEmpty() ? QStringList() : QStringList({text});
|
||||
}
|
||||
|
||||
static QString GetPic0Path(const Configuration& info) {
|
||||
if (info.basedir.isEmpty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto path = QDir(info.basedir).filePath(QStringLiteral("sce_sys/pic0.png"));
|
||||
return QFileInfo::exists(path) ? QFileInfo(path).absoluteFilePath() : QString();
|
||||
}
|
||||
|
||||
static void ConfigureGameList(Ui::ConfigurationListWidget* ui) {
|
||||
ui->cfgs_list->setAlternatingRowColors(false);
|
||||
ui->cfgs_list->setAllColumnsShowFocus(true);
|
||||
@@ -490,17 +483,16 @@ static QString GetFirmwareVersion(const QJsonObject& root) {
|
||||
return version;
|
||||
}
|
||||
|
||||
static GameMetadata GetGameMetadata(const QString& param_file, const QString& fallback) {
|
||||
static GameMetadata GetGameMetadata(const QByteArray& param_data, const QString& fallback) {
|
||||
GameMetadata ret;
|
||||
ret.title_name = fallback;
|
||||
|
||||
QFile param(param_file);
|
||||
if (!param.open(QIODevice::ReadOnly)) {
|
||||
if (param_data.isEmpty()) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
QJsonParseError parse_error;
|
||||
const auto doc = QJsonDocument::fromJson(param.readAll(), &parse_error);
|
||||
const auto doc = QJsonDocument::fromJson(param_data, &parse_error);
|
||||
if (parse_error.error != QJsonParseError::NoError || !doc.isObject()) {
|
||||
return ret;
|
||||
}
|
||||
@@ -522,18 +514,18 @@ static GameMetadata GetGameMetadata(const QString& param_file, const QString& fa
|
||||
}
|
||||
|
||||
static void SetGameFiles(Configuration& info, const QString& game_dir, const QString& game_path,
|
||||
const GameMetadata& metadata) {
|
||||
QDir game(game_dir);
|
||||
const GameMetadata& metadata, bool archive) {
|
||||
const QFileInfo game(game_dir);
|
||||
|
||||
info.game_path = game_path;
|
||||
info.basedir = game.absolutePath();
|
||||
info.basedir = archive ? game.absoluteFilePath() : QDir(game_dir).absolutePath();
|
||||
info.name = metadata.title_name;
|
||||
info.title_id = metadata.title_id;
|
||||
info.gameVersion = metadata.gameVersion;
|
||||
info.firmwareVer = metadata.firmwareVer;
|
||||
|
||||
if (info.name.isEmpty()) {
|
||||
info.name = game.dirName();
|
||||
info.name = archive ? game.completeBaseName() : QDir(game_dir).dirName();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -609,6 +601,94 @@ void ConfigurationListWidget::ScanGameDirectory() {
|
||||
|
||||
const QString eboot_name = QStringLiteral("eboot.bin");
|
||||
QSet<QString> found_games;
|
||||
const auto add_game = [this, &found_games, &eboot_name,
|
||||
&running_items](const QString& base, const QString& game_path,
|
||||
const QString& legacy_game_path, const QString& fallback,
|
||||
bool archive) {
|
||||
const QString game_key = PathKey(game_path);
|
||||
if (game_key.isEmpty() || found_games.contains(game_key)) {
|
||||
return;
|
||||
}
|
||||
// Keep the index and decompression cache alive through validation, metadata and icon
|
||||
// reads, after rejecting duplicate candidates from overlapping game folders.
|
||||
const auto reader = archive ? Common::OpenArchive(GameContent::ToPath(base)) : nullptr;
|
||||
if (archive && (reader == nullptr || !GameContent::FileExists(base, eboot_name))) {
|
||||
return;
|
||||
}
|
||||
found_games.insert(game_key);
|
||||
|
||||
const auto metadata =
|
||||
GetGameMetadata(GameContent::ReadFile(base, QStringLiteral("sce_sys/param.json"),
|
||||
GameContent::MaxMetadataSize),
|
||||
fallback);
|
||||
auto info = std::make_unique<Configuration>();
|
||||
info->custom_settings =
|
||||
FindCustomInfo(&m_custom_infos, game_path, legacy_game_path) != nullptr;
|
||||
|
||||
SetGameFiles(*info, base, game_path, metadata, archive);
|
||||
const auto* compatibility = m_compatibility->Find(info->title_id);
|
||||
if (compatibility != nullptr) {
|
||||
info->game_status = compatibility->status;
|
||||
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(true);
|
||||
item->SetCompatibilityEditable(m_compatibility->IsLocal() &&
|
||||
!item->GetInfo().title_id.trimmed().isEmpty());
|
||||
return;
|
||||
}
|
||||
|
||||
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, item,
|
||||
[this, item](int /*index*/) {
|
||||
if (!m_compatibility->IsLocal()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& title_id = item->GetInfo().title_id;
|
||||
if (title_id.trimmed().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
item->GetInfo().game_status = static_cast<Configuration::GameStatus>(
|
||||
item->GetStatusCombo()->currentData().toInt());
|
||||
item->Update();
|
||||
m_compatibility->SetStatus(title_id, item->GetInfo().game_status);
|
||||
m_ui->cfgs_list->setCurrentItem(item);
|
||||
SelectItem(item);
|
||||
});
|
||||
connect(item->GetCommentEdit(), &QLineEdit::editingFinished, item, [this, item]() {
|
||||
if (!m_compatibility->IsLocal()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& title_id = item->GetInfo().title_id;
|
||||
if (title_id.trimmed().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
item->GetInfo().game_comment = item->GetCommentEdit()->text();
|
||||
item->Update();
|
||||
m_compatibility->SetComment(title_id, item->GetInfo().game_comment);
|
||||
m_ui->cfgs_list->setCurrentItem(item);
|
||||
SelectItem(item);
|
||||
});
|
||||
};
|
||||
const auto scan_archives = [&add_game](const QDir& directory, const QDir& root) {
|
||||
const auto entries = directory.entryInfoList(QDir::Files | QDir::NoSymLinks, QDir::Name);
|
||||
for (const auto& archive: entries) {
|
||||
const auto base = archive.absoluteFilePath();
|
||||
if (!Common::IsSupportedArchive(GameContent::ToPath(base))) {
|
||||
continue;
|
||||
}
|
||||
add_game(base, QDir::cleanPath(base), root.relativeFilePath(base),
|
||||
archive.completeBaseName(), true);
|
||||
}
|
||||
};
|
||||
|
||||
for (const auto& root_path: m_game_dirs) {
|
||||
QDir root(root_path);
|
||||
@@ -616,6 +696,7 @@ void ConfigurationListWidget::ScanGameDirectory() {
|
||||
continue;
|
||||
}
|
||||
|
||||
scan_archives(root, root);
|
||||
QList<QDir> pending_dirs;
|
||||
const auto root_subdirs =
|
||||
root.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot | QDir::NoSymLinks);
|
||||
@@ -625,74 +706,13 @@ void ConfigurationListWidget::ScanGameDirectory() {
|
||||
|
||||
while (!pending_dirs.isEmpty()) {
|
||||
QDir game_dir = pending_dirs.takeFirst();
|
||||
scan_archives(game_dir, root);
|
||||
|
||||
if (game_dir.exists(eboot_name)) {
|
||||
const QString game_path = NormalizeGameDirectory(game_dir.absolutePath());
|
||||
const QString legacy_game_path = root.relativeFilePath(game_dir.absolutePath());
|
||||
const QString game_key = PathKey(game_path);
|
||||
if (game_key.isEmpty() || found_games.contains(game_key)) {
|
||||
continue;
|
||||
}
|
||||
found_games.insert(game_key);
|
||||
|
||||
auto metadata = GetGameMetadata(
|
||||
game_dir.filePath(QStringLiteral("sce_sys/param.json")), game_dir.dirName());
|
||||
|
||||
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) {
|
||||
info->game_status = compatibility->status;
|
||||
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, item,
|
||||
[this, item](int /*index*/) {
|
||||
if (!m_compatibility->IsLocal()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& title_id = item->GetInfo().title_id;
|
||||
if (title_id.trimmed().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
item->GetInfo().game_status = static_cast<Configuration::GameStatus>(
|
||||
item->GetStatusCombo()->currentData().toInt());
|
||||
item->Update();
|
||||
m_compatibility->SetStatus(title_id, item->GetInfo().game_status);
|
||||
m_ui->cfgs_list->setCurrentItem(item);
|
||||
SelectItem(item);
|
||||
});
|
||||
connect(item->GetCommentEdit(), &QLineEdit::editingFinished, item, [this, item]() {
|
||||
if (!m_compatibility->IsLocal()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& title_id = item->GetInfo().title_id;
|
||||
if (title_id.trimmed().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
item->GetInfo().game_comment = item->GetCommentEdit()->text();
|
||||
item->Update();
|
||||
m_compatibility->SetComment(title_id, item->GetInfo().game_comment);
|
||||
m_ui->cfgs_list->setCurrentItem(item);
|
||||
SelectItem(item);
|
||||
});
|
||||
add_game(game_dir.absolutePath(), game_path, legacy_game_path, game_dir.dirName(),
|
||||
false);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -731,6 +751,8 @@ void ConfigurationListWidget::edit_configuration() {
|
||||
auto info = CreateConfiguration(*item);
|
||||
ConfigurationEditDialog dlg(*info, this);
|
||||
dlg.setWindowTitle(tr("Edit game settings"));
|
||||
connect(&dlg, &ConfigurationEditDialog::PreviewControllerColor, this,
|
||||
&ConfigurationListWidget::PreviewControllerColor);
|
||||
|
||||
if (dlg.exec() == QDialog::Accepted) {
|
||||
info->custom_settings = true;
|
||||
@@ -741,6 +763,8 @@ void ConfigurationListWidget::edit_configuration() {
|
||||
WriteSettings();
|
||||
item->Update();
|
||||
SelectItem(item);
|
||||
} else {
|
||||
emit Select();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -768,6 +792,8 @@ void ConfigurationListWidget::edit_global_settings() {
|
||||
ConfigurationEditDialog dlg(info, this);
|
||||
dlg.setWindowTitle(tr("Global settings"));
|
||||
dlg.SetGameDirectories(m_game_dirs);
|
||||
connect(&dlg, &ConfigurationEditDialog::PreviewControllerColor, this,
|
||||
&ConfigurationListWidget::PreviewControllerColor);
|
||||
|
||||
if (dlg.exec() == QDialog::Accepted) {
|
||||
m_global_info.CopyEmulatorSettingsFrom(info);
|
||||
@@ -779,6 +805,7 @@ void ConfigurationListWidget::edit_global_settings() {
|
||||
ScanGameDirectory();
|
||||
}
|
||||
}
|
||||
emit Select();
|
||||
}
|
||||
|
||||
void ConfigurationListWidget::edit_input_mapping() {
|
||||
@@ -805,7 +832,8 @@ void ConfigurationListWidget::open_game_folder() {
|
||||
return;
|
||||
}
|
||||
|
||||
const QDir game_dir(item->GetInfo().basedir);
|
||||
const auto base = item->GetInfo().basedir;
|
||||
const QDir game_dir(GameContent::IsArchive(base) ? QFileInfo(base).absolutePath() : base);
|
||||
if (!game_dir.exists()) {
|
||||
QMessageBox::warning(this, tr("Open game folder"), tr("Game folder does not exist."));
|
||||
return;
|
||||
@@ -896,7 +924,7 @@ void ConfigurationListWidget::SelectItem(QTreeWidgetItem* witem) {
|
||||
m_ui->edit_button->setEnabled(!item->IsRunning());
|
||||
|
||||
m_selected_item = item;
|
||||
m_ui->cfgs_list->SetBackgroundImage(GetPic0Path(item->GetInfo()));
|
||||
m_ui->cfgs_list->SetBackgroundImage(item->GetInfo().basedir);
|
||||
|
||||
emit Select();
|
||||
}
|
||||
@@ -959,7 +987,8 @@ void ConfigurationListWidget::show_context_menu(const QPoint& pos) {
|
||||
|
||||
if (item != nullptr) {
|
||||
action_run->setDisabled(item->IsRunning());
|
||||
action_open_folder->setDisabled(!QDir(item->GetInfo().basedir).exists());
|
||||
const auto& base = item->GetInfo().basedir;
|
||||
action_open_folder->setDisabled(!QDir(base).exists() && !GameContent::IsArchive(base));
|
||||
action_view_trophies->setDisabled(!has_trophy_data);
|
||||
action_remove_save_data->setDisabled(item->IsRunning() || save_data_dirs.isEmpty());
|
||||
action_edit->setDisabled(item->IsRunning());
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
#include "controllerLightbar.h"
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
ControllerLightbar::ControllerLightbar(QObject* parent): QObject(parent) {
|
||||
m_timer.setInterval(100);
|
||||
connect(&m_timer, &QTimer::timeout, this, &ControllerLightbar::Refresh);
|
||||
}
|
||||
|
||||
ControllerLightbar::~ControllerLightbar() {
|
||||
Stop();
|
||||
}
|
||||
|
||||
void ControllerLightbar::SetColor(const QString& color) {
|
||||
const QColor parsed(color);
|
||||
if (!parsed.isValid()) {
|
||||
Stop();
|
||||
return;
|
||||
}
|
||||
if (m_color == parsed) {
|
||||
return;
|
||||
}
|
||||
if (!m_timer.isActive()) {
|
||||
if (!SDL_InitSubSystem(SDL_INIT_GAMEPAD)) {
|
||||
qWarning() << "Cannot initialize controller color preview:" << SDL_GetError();
|
||||
return;
|
||||
}
|
||||
m_timer.start();
|
||||
}
|
||||
m_color = parsed;
|
||||
for (auto* gamepad: m_gamepads) {
|
||||
ApplyColor(gamepad);
|
||||
}
|
||||
Refresh();
|
||||
}
|
||||
|
||||
void ControllerLightbar::Stop() {
|
||||
if (!m_timer.isActive()) {
|
||||
return;
|
||||
}
|
||||
m_timer.stop();
|
||||
for (auto* gamepad: m_gamepads) {
|
||||
SDL_CloseGamepad(gamepad);
|
||||
}
|
||||
m_gamepads.clear();
|
||||
SDL_QuitSubSystem(SDL_INIT_GAMEPAD);
|
||||
m_color = {};
|
||||
}
|
||||
|
||||
void ControllerLightbar::Refresh() {
|
||||
// Bluetooth DualSense may defer an accepted LED command until its connection
|
||||
// animation ends. Polling events also delivers those deferred commands.
|
||||
SDL_Event event;
|
||||
while (SDL_PollEvent(&event)) {
|
||||
if (event.type == SDL_EVENT_GAMEPAD_ADDED && !m_gamepads.contains(event.gdevice.which)) {
|
||||
auto* gamepad = SDL_OpenGamepad(event.gdevice.which);
|
||||
if (gamepad == nullptr) {
|
||||
qWarning() << "Cannot open controller color preview:" << SDL_GetError();
|
||||
continue;
|
||||
}
|
||||
if (SDL_GetBooleanProperty(SDL_GetGamepadProperties(gamepad),
|
||||
SDL_PROP_GAMEPAD_CAP_RGB_LED_BOOLEAN, false)) {
|
||||
m_gamepads.insert(event.gdevice.which, gamepad);
|
||||
ApplyColor(gamepad);
|
||||
} else {
|
||||
SDL_CloseGamepad(gamepad);
|
||||
}
|
||||
} else if (event.type == SDL_EVENT_GAMEPAD_REMOVED) {
|
||||
if (auto* gamepad = m_gamepads.take(event.gdevice.which); gamepad != nullptr) {
|
||||
SDL_CloseGamepad(gamepad);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ControllerLightbar::ApplyColor(SDL_Gamepad* gamepad) const {
|
||||
if (!SDL_SetGamepadLED(gamepad, static_cast<Uint8>(m_color.red()),
|
||||
static_cast<Uint8>(m_color.green()),
|
||||
static_cast<Uint8>(m_color.blue()))) {
|
||||
qWarning() << "Cannot set controller LED:" << SDL_GetError();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
#include "gameContent.h"
|
||||
|
||||
#include "common/archive.h"
|
||||
#include "common/file.h"
|
||||
#include "common/stringUtils.h"
|
||||
|
||||
#include <QFileInfo>
|
||||
|
||||
#include <limits>
|
||||
|
||||
namespace GameContent {
|
||||
|
||||
std::filesystem::path ToPath(const QString& path) {
|
||||
#if defined(_WIN32)
|
||||
return std::filesystem::path(path.toStdWString());
|
||||
#else
|
||||
return std::filesystem::path(path.toStdString());
|
||||
#endif
|
||||
}
|
||||
|
||||
QString FromPath(const std::filesystem::path& path) {
|
||||
#if defined(_WIN32)
|
||||
return QString::fromStdWString(path.wstring());
|
||||
#else
|
||||
return QString::fromStdString(Common::PathToString(path));
|
||||
#endif
|
||||
}
|
||||
|
||||
bool IsArchive(const QString& base) {
|
||||
const QFileInfo info(base);
|
||||
return info.isFile() && Common::IsSupportedArchive(ToPath(base));
|
||||
}
|
||||
|
||||
std::filesystem::path Resolve(const QString& base, const QString& relative) {
|
||||
const auto root = ToPath(base);
|
||||
return IsArchive(base) ? Common::MakeArchivePath(root, ToPath(relative))
|
||||
: root / ToPath(relative);
|
||||
}
|
||||
|
||||
bool FileExists(const QString& base, const QString& relative) {
|
||||
return Common::File::IsFileExisting(Resolve(base, relative));
|
||||
}
|
||||
|
||||
QByteArray ReadFile(const QString& base, const QString& relative, uint64_t max_size) {
|
||||
return ReadPath(Resolve(base, relative), max_size);
|
||||
}
|
||||
|
||||
QByteArray ReadPath(const std::filesystem::path& path, uint64_t max_size) {
|
||||
Common::File file(path, Common::File::Mode::Read);
|
||||
if (file.IsInvalid()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto size = file.Size();
|
||||
if (size > max_size || size > static_cast<uint64_t>(std::numeric_limits<int>::max())) {
|
||||
return {};
|
||||
}
|
||||
|
||||
QByteArray data(static_cast<int>(size), Qt::Uninitialized);
|
||||
uint32_t bytes_read = 0;
|
||||
file.Read(data.data(), static_cast<uint32_t>(size), &bytes_read);
|
||||
return bytes_read == size ? data : QByteArray {};
|
||||
}
|
||||
|
||||
QStringList ListFiles(const QString& base, const QString& relative) {
|
||||
const auto directory = Resolve(base, relative);
|
||||
const bool archive = Common::IsArchivePath(directory);
|
||||
QStringList result;
|
||||
for (const auto& entry: Common::File::GetDirEntries(directory)) {
|
||||
if (entry.is_file) {
|
||||
const auto path = FromPath(directory / Common::PathFromUtf8(entry.name));
|
||||
if (archive || !QFileInfo(path).isSymLink()) {
|
||||
result.append(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace GameContent
|
||||
@@ -3,6 +3,8 @@
|
||||
#include "configuration.h"
|
||||
#include "configurationItem.h"
|
||||
#include "configurationListWidget.h"
|
||||
#include "controllerLightbar.h"
|
||||
#include "gameContent.h"
|
||||
#include "patchesDialog.h"
|
||||
#include "updateChecker.h"
|
||||
|
||||
@@ -87,6 +89,7 @@ private:
|
||||
Ui::MainDialog* m_ui = {nullptr};
|
||||
MainDialog* m_main_dialog = nullptr;
|
||||
UpdateChecker* m_update_checker = nullptr;
|
||||
ControllerLightbar m_lightbar;
|
||||
QString m_interpreter;
|
||||
|
||||
QProcess m_process;
|
||||
@@ -102,6 +105,7 @@ MainDialog::MainDialog(QWidget* parent): QDialog(parent), m_p(new MainDialogPriv
|
||||
}
|
||||
|
||||
MainDialogPrivate::~MainDialogPrivate() {
|
||||
m_process.disconnect(this);
|
||||
delete m_ui;
|
||||
}
|
||||
|
||||
@@ -120,6 +124,12 @@ void MainDialogPrivate::Setup(MainDialog* main_dialog) {
|
||||
connect(main_dialog, &MainDialog::Start, this, &MainDialogPrivate::FindInterpreter,
|
||||
Qt::QueuedConnection);
|
||||
connect(m_ui->widget, &ConfigurationListWidget::Select, this, &MainDialogPrivate::Update);
|
||||
connect(m_ui->widget, &ConfigurationListWidget::PreviewControllerColor, this,
|
||||
[this](const QString& color) {
|
||||
if (m_process.state() == QProcess::NotRunning) {
|
||||
m_lightbar.SetColor(color);
|
||||
}
|
||||
});
|
||||
connect(m_ui->widget, &ConfigurationListWidget::Run, this, &MainDialogPrivate::Run);
|
||||
connect(m_ui->check_updates_link, &QLabel::linkActivated, this,
|
||||
[this](const QString&) { m_update_checker->Check(true); });
|
||||
@@ -134,14 +144,14 @@ void MainDialogPrivate::Setup(MainDialog* main_dialog) {
|
||||
m_ui->widget->WriteSettings();
|
||||
});
|
||||
|
||||
connect(&m_process,
|
||||
static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished),
|
||||
[this](int /*exitCode*/, QProcess::ExitStatus /*exitStatus*/) {
|
||||
if (m_running_item != nullptr) {
|
||||
m_running_item->SetRunning(false);
|
||||
}
|
||||
Update();
|
||||
});
|
||||
connect(&m_process, &QProcess::stateChanged, this, [this](QProcess::ProcessState state) {
|
||||
if (state == QProcess::NotRunning) {
|
||||
if (m_running_item != nullptr) {
|
||||
m_running_item->SetRunning(false);
|
||||
}
|
||||
Update();
|
||||
}
|
||||
});
|
||||
|
||||
m_ui->label_settings_file->setText(tr("Settings file: ") + m_ui->widget->GetSettingsFile());
|
||||
|
||||
@@ -218,6 +228,9 @@ static QStringList CreateEmulatorArgs(const Configuration& info) {
|
||||
if (!info.audio_input_device.isEmpty()) {
|
||||
args << "--mic" << info.audio_input_device;
|
||||
}
|
||||
if (!info.controller_color.isEmpty()) {
|
||||
args << "--controller-color" << info.controller_color;
|
||||
}
|
||||
args << "--present-mode" << EnumToText(info.present_mode);
|
||||
if (info.gpu_index >= 0) {
|
||||
args << "--gpu" << QString::number(info.gpu_index);
|
||||
@@ -260,7 +273,7 @@ static QStringList CreateEmulatorArgs(const Configuration& info) {
|
||||
}
|
||||
|
||||
QString game = info.basedir;
|
||||
if (!info.elf.isEmpty()) {
|
||||
if (!info.elf.isEmpty() && !GameContent::IsArchive(info.basedir)) {
|
||||
game = QDir(info.basedir).filePath(info.elf);
|
||||
}
|
||||
args << "--game" << game;
|
||||
@@ -492,6 +505,7 @@ void MainDialogPrivate::Run() {
|
||||
}
|
||||
|
||||
m_running_item->SetRunning(true);
|
||||
m_lightbar.Stop();
|
||||
|
||||
auto info = m_ui->widget->CreateConfiguration(*m_running_item);
|
||||
m_main_dialog->RunInterpreter(&m_process, *info);
|
||||
@@ -507,10 +521,16 @@ void MainDialogPrivate::Update() {
|
||||
if (run_enabled) {
|
||||
const auto& info = item->GetInfo();
|
||||
auto dir = info.basedir;
|
||||
run_enabled = !dir.isEmpty() && QDir(dir).exists();
|
||||
run_enabled = !dir.isEmpty() && (QDir(dir).exists() || GameContent::IsArchive(dir));
|
||||
}
|
||||
|
||||
m_ui->widget->SetRunEnabled(run_enabled);
|
||||
if (m_process.state() != QProcess::NotRunning) {
|
||||
m_lightbar.Stop();
|
||||
return;
|
||||
}
|
||||
m_lightbar.SetColor(item != nullptr ? m_ui->widget->CreateConfiguration(*item)->controller_color
|
||||
: m_ui->widget->GetGlobalControllerColor());
|
||||
}
|
||||
|
||||
#include "mainDialog.moc"
|
||||
@@ -1,13 +1,14 @@
|
||||
#include "trophyViewerDialog.h"
|
||||
|
||||
#include "common/archive.h"
|
||||
#include "configuration.h"
|
||||
#include "gameContent.h"
|
||||
|
||||
#include <QAbstractItemView>
|
||||
#include <QBrush>
|
||||
#include <QByteArray>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QFileInfo>
|
||||
#include <QFont>
|
||||
#include <QHeaderView>
|
||||
@@ -39,12 +40,14 @@
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr quint32 UCP_MAGIC = 0xb228c60a;
|
||||
constexpr quint32 UCP_VERSION = 1;
|
||||
constexpr int UCP_HEADER_LEN = 0x40;
|
||||
constexpr int UCP_TOC_SKIP = 0x20;
|
||||
constexpr int UCP_ENTRY_LEN = 0x40;
|
||||
constexpr int UCP_NAME_LEN = 0x20;
|
||||
constexpr quint32 UCP_MAGIC = 0xb228c60a;
|
||||
constexpr quint32 UCP_VERSION = 1;
|
||||
constexpr int UCP_HEADER_LEN = 0x40;
|
||||
constexpr int UCP_TOC_SKIP = 0x20;
|
||||
constexpr int UCP_ENTRY_LEN = 0x40;
|
||||
constexpr int UCP_NAME_LEN = 0x20;
|
||||
constexpr quint32 UCP_MAX_FILES = 4096;
|
||||
constexpr quint64 UCP_MAX_EXTRACTED_SIZE = quint64 {64} << 20u;
|
||||
|
||||
struct UcpEntry {
|
||||
QString name;
|
||||
@@ -98,14 +101,22 @@ static QString ReadFixedString(const QByteArray& data, qsizetype offset, qsizety
|
||||
return QString::fromLatin1(data.constData() + offset, len);
|
||||
}
|
||||
|
||||
static bool IsUsedUcpEntry(const QString& name) {
|
||||
const auto lower = name.toCaseFolded();
|
||||
return lower == QStringLiteral("tropconf.json") || lower == QStringLiteral("tropmeta.json") ||
|
||||
(lower.startsWith(QStringLiteral("tropmeta_")) &&
|
||||
lower.endsWith(QStringLiteral(".json"))) ||
|
||||
(lower.startsWith(QStringLiteral("trop")) && lower.endsWith(QStringLiteral(".png")));
|
||||
}
|
||||
|
||||
static bool ReadUcp(const QString& file_name, QMap<QString, QByteArray>& files, QString& error) {
|
||||
QFile file(file_name);
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
const QByteArray data =
|
||||
GameContent::ReadPath(GameContent::ToPath(file_name), GameContent::MaxTrophyPackageSize);
|
||||
if (data.isEmpty()) {
|
||||
error = QObject::tr("Could not open %1").arg(QDir::toNativeSeparators(file_name));
|
||||
return false;
|
||||
}
|
||||
|
||||
const QByteArray data = file.readAll();
|
||||
if (data.size() < UCP_HEADER_LEN) {
|
||||
error = QObject::tr("%1 is too small to be a trophy package.")
|
||||
.arg(QFileInfo(file_name).fileName());
|
||||
@@ -127,14 +138,18 @@ static bool ReadUcp(const QString& file_name, QMap<QString, QByteArray>& files,
|
||||
}
|
||||
|
||||
const auto declared_size = qFromBigEndian<quint64>(data.constData() + 0x08);
|
||||
if (declared_size > static_cast<quint64>(data.size())) {
|
||||
if (declared_size < UCP_HEADER_LEN || declared_size > static_cast<quint64>(data.size())) {
|
||||
error = QObject::tr("%1 is truncated.").arg(QFileInfo(file_name).fileName());
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto file_count = qFromBigEndian<quint32>(data.constData() + 0x10);
|
||||
if (file_count > UCP_MAX_FILES) {
|
||||
error = QObject::tr("%1 contains too many files.").arg(QFileInfo(file_name).fileName());
|
||||
return false;
|
||||
}
|
||||
const auto toc_offset = static_cast<quint64>(qFromBigEndian<quint32>(data.constData() + 0x14));
|
||||
const auto data_size = static_cast<quint64>(data.size());
|
||||
const auto data_size = declared_size;
|
||||
|
||||
const quint64 table_size = UCP_TOC_SKIP + static_cast<quint64>(file_count) * UCP_ENTRY_LEN;
|
||||
if (toc_offset > data_size || table_size > data_size - toc_offset ||
|
||||
@@ -144,6 +159,7 @@ static bool ReadUcp(const QString& file_name, QMap<QString, QByteArray>& files,
|
||||
return false;
|
||||
}
|
||||
|
||||
quint64 extracted_size = 0;
|
||||
for (quint32 i = 0; i < file_count; i++) {
|
||||
const auto entry_offset = static_cast<qsizetype>(toc_offset + UCP_TOC_SKIP +
|
||||
static_cast<quint64>(i) * UCP_ENTRY_LEN);
|
||||
@@ -161,9 +177,22 @@ static bool ReadUcp(const QString& file_name, QMap<QString, QByteArray>& files,
|
||||
.arg(QFileInfo(file_name).fileName(), entry.name);
|
||||
return false;
|
||||
}
|
||||
if (!IsUsedUcpEntry(entry.name)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto entry_limit = entry.name.endsWith(QStringLiteral(".png"), Qt::CaseInsensitive)
|
||||
? GameContent::MaxImageSize
|
||||
: GameContent::MaxMetadataSize;
|
||||
if (entry.size > entry_limit || entry.size > UCP_MAX_EXTRACTED_SIZE - extracted_size) {
|
||||
error = QObject::tr("%1 contains an oversized entry for %2.")
|
||||
.arg(QFileInfo(file_name).fileName(), entry.name);
|
||||
return false;
|
||||
}
|
||||
|
||||
files.insert(entry.name.toCaseFolded(), data.mid(static_cast<qsizetype>(entry.offset),
|
||||
static_cast<qsizetype>(entry.size)));
|
||||
extracted_size += entry.size;
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -392,30 +421,31 @@ static QStringList FindTrophyFiles(const Configuration* info) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const QDir trophy_dir(QDir(info->basedir).filePath(QStringLiteral("sce_sys/trophy2")));
|
||||
if (!trophy_dir.exists()) {
|
||||
return {};
|
||||
QStringList files;
|
||||
for (const auto& file:
|
||||
GameContent::ListFiles(info->basedir, QStringLiteral("sce_sys/trophy2"))) {
|
||||
const auto name = QFileInfo(file).fileName();
|
||||
if (name.startsWith(QStringLiteral("trophy"), Qt::CaseInsensitive) &&
|
||||
name.endsWith(QStringLiteral(".ucp"), Qt::CaseInsensitive)) {
|
||||
files.append(file);
|
||||
}
|
||||
}
|
||||
|
||||
const auto files =
|
||||
trophy_dir.entryInfoList({QStringLiteral("Trophy*.ucp"), QStringLiteral("trophy*.ucp")},
|
||||
QDir::Files | QDir::NoSymLinks, QDir::Name | QDir::IgnoreCase);
|
||||
files.sort(Qt::CaseInsensitive);
|
||||
|
||||
QStringList trophy_files;
|
||||
QSet<QString> seen;
|
||||
for (const auto& file: files) {
|
||||
auto key = file.canonicalFilePath();
|
||||
auto key = QFileInfo(file).canonicalFilePath();
|
||||
if (key.isEmpty()) {
|
||||
key = file.absoluteFilePath();
|
||||
key = file;
|
||||
}
|
||||
key = QDir::cleanPath(key).toCaseFolded();
|
||||
|
||||
if (!seen.contains(key)) {
|
||||
seen.insert(key);
|
||||
trophy_files.append(file.absoluteFilePath());
|
||||
trophy_files.append(file);
|
||||
}
|
||||
}
|
||||
|
||||
return trophy_files;
|
||||
}
|
||||
|
||||
@@ -506,6 +536,7 @@ void TrophyViewerDialog::ShowForGame(const Configuration* info, QWidget* parent)
|
||||
}
|
||||
|
||||
bool TrophyViewerDialog::LoadGame(const Configuration& info, QString& error) {
|
||||
const auto reader = Common::OpenArchive(GameContent::ToPath(info.basedir));
|
||||
const auto trophy_files = FindTrophyFiles(&info);
|
||||
if (trophy_files.isEmpty()) {
|
||||
error = tr("No trophy package found in sce_sys/trophy2.");
|
||||
|
||||
+1
-1
@@ -1552,7 +1552,7 @@ int KYTY_SYSV_ABI AgcSuspendPoint() {
|
||||
PRINT_NAME();
|
||||
|
||||
EXIT_IF(g_renderer == nullptr);
|
||||
g_renderer->GetGpu().Done();
|
||||
g_renderer->GetGpu().SuspendPoint();
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
+1
-3
@@ -161,10 +161,8 @@ int KYTY_SYSV_ABI AjmDecAt9ParseConfigData(const void* config_data,
|
||||
return AJM_ERROR_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
uint8_t config[ATRAC9_CONFIG_DATA_SIZE] {};
|
||||
std::memcpy(config, config_data, sizeof(config));
|
||||
Atrac9CodecInfo codec_info {};
|
||||
const int init_result = Atrac9InitDecoder(handle, config);
|
||||
const int init_result = AjmAt9InitDecoder(handle, static_cast<const uint8_t*>(config_data));
|
||||
const int info_result =
|
||||
init_result == 0 ? Atrac9GetCodecInfo(handle, &codec_info) : init_result;
|
||||
Atrac9ReleaseHandle(handle);
|
||||
|
||||
@@ -18,6 +18,19 @@ extern "C" {
|
||||
|
||||
namespace Libs::Audio::Ajm {
|
||||
|
||||
inline int AjmAt9InitDecoder(void* handle, const uint8_t* config_data) {
|
||||
// Keep the guest configuration unchanged for metadata and decoder resets.
|
||||
uint8_t config[ATRAC9_CONFIG_DATA_SIZE];
|
||||
std::memcpy(config, config_data, sizeof(config));
|
||||
const auto channel_config = (config[1] >> 1u) & 7u;
|
||||
if (channel_config >= 6u) {
|
||||
// LibAtrac9 lacks vibration layouts; their blocks match ordinary mono/dual-mono.
|
||||
// Translate only the channel field, preserving the other configuration bits.
|
||||
config[1] -= 6u << 1u;
|
||||
}
|
||||
return Atrac9InitDecoder(handle, config);
|
||||
}
|
||||
|
||||
struct AjmDecAt9InitializeParameters {
|
||||
uint8_t config_data[ATRAC9_CONFIG_DATA_SIZE];
|
||||
uint32_t reserved;
|
||||
@@ -278,7 +291,7 @@ private:
|
||||
std::memcpy(m_config_data, config_data, ATRAC9_CONFIG_DATA_SIZE);
|
||||
m_has_config = true;
|
||||
|
||||
const int init_ret = Atrac9InitDecoder(m_handle, m_config_data);
|
||||
const int init_ret = AjmAt9InitDecoder(m_handle, m_config_data);
|
||||
if (init_ret != 0) {
|
||||
m_is_initialized = false;
|
||||
result->result = AJM_RESULT_CODEC_ERROR | AJM_RESULT_INVALID_DATA;
|
||||
|
||||
+22
-10
@@ -32,6 +32,7 @@ constexpr int AUDIO_OUT_PORT_TYPE_AUDIO3D = 126;
|
||||
constexpr int AUDIO_OUT_PORT_TYPE_AUX = 127;
|
||||
|
||||
constexpr uint32_t AUDIO_OUT_PARAM_FORMAT_MASK = 0x000000ffu;
|
||||
constexpr uint64_t AUDIO_OUT_TARGET_LATENCY_US = 40000;
|
||||
|
||||
constexpr int AUDIO_IN_SILENT_STATE_DEVICE_NONE = 0x1;
|
||||
constexpr uint32_t AUDIO_IN_GRAIN_MAX_ASYNC = 384;
|
||||
@@ -368,11 +369,11 @@ bool Audio::QueueSdlAudio(PortOut* port, const void* data, bool blocking) {
|
||||
|
||||
uint32_t min_queued_size = 0;
|
||||
if (blocking) {
|
||||
constexpr uint64_t target_latency_us = 40000;
|
||||
const auto buffer_us = port->freq != 0 ? (1000000ULL * port->samples_num) / port->freq : 0;
|
||||
const auto buffers =
|
||||
buffer_us != 0 ? static_cast<uint32_t>((target_latency_us + buffer_us - 1) / buffer_us)
|
||||
: 2u;
|
||||
buffer_us != 0
|
||||
? static_cast<uint32_t>((AUDIO_OUT_TARGET_LATENCY_US + buffer_us - 1) / buffer_us)
|
||||
: 2u;
|
||||
min_queued_size = prepared_size * std::clamp(buffers, 2u, 16u);
|
||||
const auto wait_start = LibKernel::KernelGetProcessTime();
|
||||
auto queued = SDL_GetAudioStreamQueued(port->stream);
|
||||
@@ -440,11 +441,14 @@ Audio::Id Audio::AudioOutOpen(int type, uint32_t samples_num, uint32_t freq, For
|
||||
port.volume[i] = 32768;
|
||||
}
|
||||
|
||||
if (type == AUDIO_OUT_PORT_TYPE_VIBRATION) {
|
||||
port.haptics = Controller::DualSenseHaptics::Open(freq);
|
||||
} else {
|
||||
// Pad speaker ports keep the main output too; it plays them whenever no DualSense does.
|
||||
if (type != AUDIO_OUT_PORT_TYPE_VIBRATION) {
|
||||
OpenSdlDevice(&port);
|
||||
}
|
||||
if (type == AUDIO_OUT_PORT_TYPE_VIBRATION || type == AUDIO_OUT_PORT_TYPE_PADSPK) {
|
||||
port.haptics =
|
||||
Controller::DualSenseHaptics::Open(freq, type == AUDIO_OUT_PORT_TYPE_PADSPK);
|
||||
}
|
||||
|
||||
return Id::Create(id);
|
||||
}
|
||||
@@ -554,14 +558,22 @@ uint32_t Audio::AudioOutOutputs(OutputParam* params, uint32_t num, bool blocking
|
||||
for (uint32_t i = 0; i < num; i++) {
|
||||
auto& port = m_out_ports[params[i].handle.GetId()];
|
||||
|
||||
if (port.type == AUDIO_OUT_PORT_TYPE_VIBRATION) {
|
||||
// Haptics never pace output; keep the stream alive against close and volume changes.
|
||||
uint64_t controller_queued_us = 0;
|
||||
if (port.haptics != nullptr) {
|
||||
// Keep the stream alive against close and volume changes.
|
||||
Common::LockGuard lock(m_mutex);
|
||||
Controller::DualSenseHaptics::Queue(
|
||||
controller_queued_us = Controller::DualSenseHaptics::Queue(
|
||||
port.haptics, Controller::GetActiveControllerId(), params[i].data, port.samples_num,
|
||||
static_cast<uint32_t>(port.channels_num), FormatIsFloat(port.format), port.volume);
|
||||
} else {
|
||||
}
|
||||
if (controller_queued_us == 0) {
|
||||
// No DualSense took it (e.g. it was unplugged); a pad speaker port plays on the main
|
||||
// output instead, and a vibration port has none.
|
||||
QueueSdlAudio(&port, params[i].data, blocking);
|
||||
} else if (blocking && port.type == AUDIO_OUT_PORT_TYPE_PADSPK &&
|
||||
controller_queued_us > AUDIO_OUT_TARGET_LATENCY_US) {
|
||||
// Vibration never paces output; the speaker is audible, so pace it like other ports.
|
||||
Common::Thread::SleepMicro(controller_queued_us - AUDIO_OUT_TARGET_LATENCY_US);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -70,6 +70,9 @@ int KYTY_SYSV_ABI AudioOut2PortGetState(AudioOut2PortHandle port, AudioOut2PortS
|
||||
int KYTY_SYSV_ABI AudioOut2GetSystemState(AudioOut2SystemState* state);
|
||||
int KYTY_SYSV_ABI AudioOut2UserCreate(uint32_t user_id, AudioOut2UserHandle* handle);
|
||||
int KYTY_SYSV_ABI AudioOut2UserDestroy(AudioOut2UserHandle handle);
|
||||
int KYTY_SYSV_ABI AudioOut2UserGetSupportedAttributes(AudioOut2UserHandle handle,
|
||||
uint32_t* context_attributes,
|
||||
uint32_t* port_attributes);
|
||||
size_t KYTY_SYSV_ABI AudioOut2GetSpeakerArrayMemorySize(uint32_t num_speakers, uint8_t is_3d,
|
||||
uint8_t is_ambisonics);
|
||||
int KYTY_SYSV_ABI AudioOut2SpeakerArrayCreate(AudioOut2SpeakerArrayHandle* handle,
|
||||
|
||||
+72
-51
@@ -1,6 +1,6 @@
|
||||
#include "common/common.h"
|
||||
#include "common/file.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/stringUtils.h"
|
||||
#include "kernel/fileSystem.h"
|
||||
#include "kernel/pthread.h"
|
||||
#include "libs/audio.h"
|
||||
@@ -9,12 +9,14 @@
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <cctype>
|
||||
#include <cerrno>
|
||||
#include <condition_variable>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <deque>
|
||||
#include <limits>
|
||||
#include <magic_enum.hpp>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
@@ -577,30 +579,40 @@ public:
|
||||
explicit FileStreamer(AvPlayerFileReplacement f): file(f) {}
|
||||
~FileStreamer() {
|
||||
if (ctx != nullptr) {
|
||||
av_freep(&ctx->buffer);
|
||||
avio_context_free(&ctx);
|
||||
}
|
||||
if (opened && file.close != nullptr) {
|
||||
if (opened) {
|
||||
file.close(file.object_pointer);
|
||||
}
|
||||
}
|
||||
bool Init(const std::string& path) {
|
||||
if (file.open == nullptr || file.close == nullptr || file.read_offset == nullptr ||
|
||||
file.size == nullptr) {
|
||||
if (file.open != nullptr) {
|
||||
if (file.close == nullptr || file.read_offset == nullptr || file.size == nullptr ||
|
||||
file.open(file.object_pointer, path.c_str()) < 0) {
|
||||
return false;
|
||||
}
|
||||
opened = true;
|
||||
size = file.size(file.object_pointer);
|
||||
} else {
|
||||
if (!local_file.Open(LibKernel::FileSystem::GetRealFilename(path),
|
||||
Common::File::Mode::Read)) {
|
||||
return false;
|
||||
}
|
||||
size = local_file.Size();
|
||||
}
|
||||
if (size == 0 || size > static_cast<uint64_t>(std::numeric_limits<int64_t>::max())) {
|
||||
return false;
|
||||
}
|
||||
if (file.open(file.object_pointer, path.c_str()) < 0) {
|
||||
return false;
|
||||
}
|
||||
opened = true;
|
||||
size = file.size(file.object_pointer);
|
||||
if (size == 0) {
|
||||
return false;
|
||||
}
|
||||
auto* buf = static_cast<uint8_t*>(av_malloc(4096));
|
||||
constexpr int buffer_size = 64 * 1024;
|
||||
auto* buf = static_cast<uint8_t*>(av_malloc(buffer_size));
|
||||
if (buf == nullptr) {
|
||||
return false;
|
||||
}
|
||||
ctx = avio_alloc_context(buf, 4096, 0, this, Read, nullptr, Seek);
|
||||
ctx = avio_alloc_context(buf, buffer_size, 0, this, Read, nullptr, Seek);
|
||||
if (ctx == nullptr) {
|
||||
av_free(buf);
|
||||
}
|
||||
return ctx != nullptr;
|
||||
}
|
||||
AVIOContext* Context() const { return ctx; }
|
||||
@@ -611,33 +623,50 @@ private:
|
||||
if (s->pos >= s->size) {
|
||||
return AVERROR_EOF;
|
||||
}
|
||||
len = static_cast<int>(std::min<uint64_t>(len, s->size - s->pos));
|
||||
auto r =
|
||||
s->file.read_offset(s->file.object_pointer, buf, s->pos, static_cast<uint32_t>(len));
|
||||
if (r <= 0) {
|
||||
return r == 0 ? AVERROR_EOF : r;
|
||||
len = static_cast<int>(std::min<uint64_t>(len, s->size - s->pos));
|
||||
int read = 0;
|
||||
if (s->opened) {
|
||||
read = s->file.read_offset(s->file.object_pointer, buf, s->pos,
|
||||
static_cast<uint32_t>(len));
|
||||
} else {
|
||||
uint32_t bytes = 0;
|
||||
s->local_file.Read(buf, static_cast<uint32_t>(len), &bytes);
|
||||
read = static_cast<int>(bytes);
|
||||
}
|
||||
s->pos += static_cast<uint64_t>(r);
|
||||
return r;
|
||||
if (read <= 0 || read > len) {
|
||||
return read < 0 ? read : AVERROR(EIO);
|
||||
}
|
||||
s->pos += static_cast<uint64_t>(read);
|
||||
return read;
|
||||
}
|
||||
static int64_t Seek(void* opaque, int64_t off, int whence) {
|
||||
static int64_t Seek(void* opaque, int64_t offset, int whence) {
|
||||
auto* s = static_cast<FileStreamer*>(opaque);
|
||||
if ((whence & AVSEEK_SIZE) != 0) {
|
||||
return static_cast<int64_t>(s->size);
|
||||
}
|
||||
int64_t p = whence == SEEK_SET
|
||||
? off
|
||||
: (whence == SEEK_CUR
|
||||
? static_cast<int64_t>(s->pos) + off
|
||||
: (whence == SEEK_END ? static_cast<int64_t>(s->size) + off : -1));
|
||||
if (p < 0) {
|
||||
return -1;
|
||||
whence &= ~AVSEEK_FORCE;
|
||||
uint64_t base = 0;
|
||||
switch (whence) {
|
||||
case SEEK_SET: break;
|
||||
case SEEK_CUR: base = s->pos; break;
|
||||
case SEEK_END: base = s->size; break;
|
||||
default: return AVERROR(EINVAL);
|
||||
}
|
||||
p = std::min<int64_t>(p, static_cast<int64_t>(s->size));
|
||||
s->pos = static_cast<uint64_t>(p);
|
||||
return p;
|
||||
const auto distance = offset < 0 ? uint64_t {0} - static_cast<uint64_t>(offset)
|
||||
: static_cast<uint64_t>(offset);
|
||||
if ((offset < 0 && distance > base) ||
|
||||
(offset >= 0 && distance > std::numeric_limits<int64_t>::max() - base)) {
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
const auto position = offset < 0 ? base - distance : base + distance;
|
||||
if (!s->opened && !s->local_file.Seek(position)) {
|
||||
return AVERROR(EIO);
|
||||
}
|
||||
s->pos = position;
|
||||
return static_cast<int64_t>(position);
|
||||
}
|
||||
AvPlayerFileReplacement file;
|
||||
Common::File local_file;
|
||||
bool opened = false;
|
||||
uint64_t pos = 0;
|
||||
uint64_t size = 0;
|
||||
@@ -675,25 +704,17 @@ public:
|
||||
return static_cast<Source*>(opaque)->interrupt_io.load() ? 1 : 0;
|
||||
};
|
||||
raw->interrupt_callback.opaque = this;
|
||||
if (file.open != nullptr) {
|
||||
streamer = std::make_unique<FileStreamer>(file);
|
||||
if (!streamer->Init(path)) {
|
||||
avformat_free_context(raw);
|
||||
return AVPLAYER_ERROR_OPERATION_FAILED;
|
||||
}
|
||||
raw->pb = streamer->Context();
|
||||
if (auto rc = avformat_open_input(&raw, nullptr, nullptr, nullptr); rc < 0) {
|
||||
LOGF("\t avformat_open_input callback failed: %s\n", fferr(rc).c_str());
|
||||
return AVPLAYER_ERROR_OPERATION_FAILED;
|
||||
}
|
||||
} else {
|
||||
auto real = LibKernel::FileSystem::GetRealFilename(std::string(path.c_str()));
|
||||
auto real_str = Common::PathToString(real);
|
||||
if (auto rc = avformat_open_input(&raw, real_str.c_str(), nullptr, nullptr); rc < 0) {
|
||||
LOGF("\t avformat_open_input failed: %s path=%s\n", fferr(rc).c_str(),
|
||||
real_str.c_str());
|
||||
return AVPLAYER_ERROR_OPERATION_FAILED;
|
||||
}
|
||||
streamer = std::make_unique<FileStreamer>(file);
|
||||
if (!streamer->Init(path)) {
|
||||
avformat_free_context(raw);
|
||||
return AVPLAYER_ERROR_OPERATION_FAILED;
|
||||
}
|
||||
raw->pb = streamer->Context();
|
||||
if (const auto rc = avformat_open_input(&raw, file.open == nullptr ? path.c_str() : nullptr,
|
||||
nullptr, nullptr);
|
||||
rc < 0) {
|
||||
LOGF("\t avformat_open_input failed: %s path=%s\n", fferr(rc).c_str(), path.c_str());
|
||||
return AVPLAYER_ERROR_OPERATION_FAILED;
|
||||
}
|
||||
fmt = raw;
|
||||
if (auto rc = avformat_find_stream_info(fmt, nullptr); rc < 0) {
|
||||
|
||||
+68
-13
@@ -133,6 +133,9 @@ private:
|
||||
int m_connected_count = 0;
|
||||
bool m_motion_enabled = true;
|
||||
uint64_t m_gyro_time = 0;
|
||||
// Accelerometer gravity direction in the reset frame.
|
||||
std::array<float, 3> m_up_reference {0.0f, 1.0f, 0.0f};
|
||||
bool m_up_reference_valid = false;
|
||||
ControllerState m_state;
|
||||
ControllerState m_states[STATES_MAX];
|
||||
bool m_obtained[STATES_MAX] {};
|
||||
@@ -297,6 +300,9 @@ void GameController::Connect(int id) {
|
||||
if (id != HOST_INPUT_CONTROLLER_ID) {
|
||||
if (auto* pad = SDL_GetGamepadFromID(static_cast<SDL_JoystickID>(id));
|
||||
pad != nullptr) {
|
||||
if (const auto& color = Config::GetControllerColor()) {
|
||||
(void)SDL_SetGamepadLED(pad, (*color)[0], (*color)[1], (*color)[2]);
|
||||
}
|
||||
for (auto sensor: {SDL_SENSOR_ACCEL, SDL_SENSOR_GYRO}) {
|
||||
if (SDL_GamepadHasSensor(pad, sensor) &&
|
||||
!SDL_SetGamepadSensorEnabled(pad, sensor, true)) {
|
||||
@@ -345,6 +351,7 @@ void GameController::CheckActive() {
|
||||
m_connected = new_connected;
|
||||
m_state = {};
|
||||
m_gyro_time = 0;
|
||||
m_up_reference_valid = false;
|
||||
m_states_num = 0;
|
||||
m_first_state = 0;
|
||||
m_next_touch_id = 1;
|
||||
@@ -436,6 +443,29 @@ void GameController::TouchPad(int id, int finger, bool down, float x, float y) {
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
using Vec3 = std::array<float, 3>;
|
||||
using Quat = std::array<float, 4>; // x, y, z, w
|
||||
|
||||
Vec3 QuatRotate(const Quat& q, const Vec3& v) {
|
||||
const Vec3 t {2.0f * (q[1] * v[2] - q[2] * v[1]), 2.0f * (q[2] * v[0] - q[0] * v[2]),
|
||||
2.0f * (q[0] * v[1] - q[1] * v[0])};
|
||||
return {v[0] + q[3] * t[0] + (q[1] * t[2] - q[2] * t[1]),
|
||||
v[1] + q[3] * t[1] + (q[2] * t[0] - q[0] * t[2]),
|
||||
v[2] + q[3] * t[2] + (q[0] * t[1] - q[1] * t[0])};
|
||||
}
|
||||
|
||||
Quat QuatConjugate(const Quat& q) {
|
||||
return {-q[0], -q[1], -q[2], q[3]};
|
||||
}
|
||||
|
||||
float Length(const Vec3& v) {
|
||||
return std::sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void GameController::Motion(int id, Sensor sensor, const float* data, uint64_t time_us) {
|
||||
Common::LockGuard lock(m_mutex);
|
||||
if (id != m_active_id || !m_motion_enabled) {
|
||||
@@ -447,20 +477,38 @@ void GameController::Motion(int id, Sensor sensor, const float* data, uint64_t t
|
||||
for (int i = 0; i < 3; i++) {
|
||||
m_state.accel[i] = data[i] / SDL_STANDARD_GRAVITY;
|
||||
}
|
||||
// Capture gravity near 1 G to limit interference from linear acceleration.
|
||||
const float magnitude = Length(m_state.accel);
|
||||
if (!m_up_reference_valid && magnitude > 0.9f && magnitude < 1.1f) {
|
||||
const Vec3 up {m_state.accel[0] / magnitude, m_state.accel[1] / magnitude,
|
||||
m_state.accel[2] / magnitude};
|
||||
m_up_reference = QuatRotate(m_state.orientation, up);
|
||||
m_up_reference_valid = true;
|
||||
}
|
||||
} else {
|
||||
std::copy_n(data, 3, m_state.gyro.begin());
|
||||
// Mahony tilt correction; heading is unobservable and reported gyro values stay raw.
|
||||
auto rate = m_state.gyro;
|
||||
const float magnitude = Length(m_state.accel);
|
||||
if (m_up_reference_valid && magnitude > 0.8f && magnitude < 1.2f) {
|
||||
const Vec3 measured {m_state.accel[0] / magnitude, m_state.accel[1] / magnitude,
|
||||
m_state.accel[2] / magnitude};
|
||||
const Vec3 predicted = QuatRotate(QuatConjugate(m_state.orientation), m_up_reference);
|
||||
rate[0] += measured[1] * predicted[2] - measured[2] * predicted[1];
|
||||
rate[1] += measured[2] * predicted[0] - measured[0] * predicted[2];
|
||||
rate[2] += measured[0] * predicted[1] - measured[1] * predicted[0];
|
||||
}
|
||||
// Do not extrapolate a single sample across lost reports (e.g. loss of window focus).
|
||||
constexpr uint64_t max_gyro_interval_us = 100000;
|
||||
if (m_gyro_time != 0 && time_us > m_gyro_time &&
|
||||
time_us - m_gyro_time <= max_gyro_interval_us) {
|
||||
const float dt = static_cast<float>(time_us - m_gyro_time) * 0.000001f;
|
||||
const float speed =
|
||||
std::sqrt(data[0] * data[0] + data[1] * data[1] + data[2] * data[2]);
|
||||
const float dt = static_cast<float>(time_us - m_gyro_time) * 0.000001f;
|
||||
const float speed = Length(rate);
|
||||
const float half_angle = speed * dt * 0.5f;
|
||||
const float scale = speed > 0.0f ? std::sin(half_angle) / speed : 0.0f;
|
||||
const float x = data[0] * scale;
|
||||
const float y = data[1] * scale;
|
||||
const float z = data[2] * scale;
|
||||
const float x = rate[0] * scale;
|
||||
const float y = rate[1] * scale;
|
||||
const float z = rate[2] * scale;
|
||||
const float w = std::cos(half_angle);
|
||||
const auto q = m_state.orientation;
|
||||
// Accumulate body-local rotation relative to connection / orientation reset.
|
||||
@@ -486,19 +534,21 @@ void GameController::Motion(int id, Sensor sensor, const float* data, uint64_t t
|
||||
void GameController::SetMotionSensorState(bool enable) {
|
||||
Common::LockGuard lock(m_mutex);
|
||||
if (m_motion_enabled != enable) {
|
||||
m_motion_enabled = enable;
|
||||
m_gyro_time = 0;
|
||||
m_state.accel = {0.0f, 1.0f, 0.0f};
|
||||
m_state.gyro = {};
|
||||
m_state.time = LibKernel::KernelGetProcessTime();
|
||||
m_motion_enabled = enable;
|
||||
m_gyro_time = 0;
|
||||
m_state.accel = {0.0f, 1.0f, 0.0f};
|
||||
m_state.gyro = {};
|
||||
m_up_reference_valid = false;
|
||||
m_state.time = LibKernel::KernelGetProcessTime();
|
||||
AddState();
|
||||
}
|
||||
}
|
||||
|
||||
void GameController::ResetOrientation() {
|
||||
Common::LockGuard lock(m_mutex);
|
||||
m_state.orientation = {0.0f, 0.0f, 0.0f, 1.0f};
|
||||
m_gyro_time = 0;
|
||||
m_state.orientation = {0.0f, 0.0f, 0.0f, 1.0f};
|
||||
m_gyro_time = 0;
|
||||
m_up_reference_valid = false;
|
||||
m_state.time = LibKernel::KernelGetProcessTime();
|
||||
AddState();
|
||||
}
|
||||
@@ -581,6 +631,11 @@ int GameController::GetActiveControllerId() {
|
||||
|
||||
void GameController::SetLightBar(uint8_t r, uint8_t g, uint8_t b) {
|
||||
Common::LockGuard lock(m_mutex);
|
||||
if (const auto& color = Config::GetControllerColor()) {
|
||||
r = (*color)[0];
|
||||
g = (*color)[1];
|
||||
b = (*color)[2];
|
||||
}
|
||||
if (auto* pad = SDL_GetGamepadFromID(static_cast<SDL_JoystickID>(m_active_id));
|
||||
pad != nullptr) {
|
||||
(void)SDL_SetGamepadLED(pad, r, g, b);
|
||||
|
||||
+29
-14
@@ -6,6 +6,7 @@
|
||||
#include "libs/errno.h"
|
||||
#include "libs/libs.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <mutex>
|
||||
@@ -306,6 +307,7 @@ LIB_NAME("SaveDataDialog", "SaveDataDialog");
|
||||
|
||||
constexpr int SAVE_STATUS_NONE = 0;
|
||||
constexpr int SAVE_STATUS_INITIALIZED = 1;
|
||||
constexpr int SAVE_STATUS_RUNNING = 2;
|
||||
constexpr int SAVE_STATUS_FINISHED = 3;
|
||||
constexpr int SAVE_RESULT_OK = 0;
|
||||
constexpr int SAVE_BUTTON_ID_OK = 1;
|
||||
@@ -351,18 +353,20 @@ struct SaveDataDialogResult {
|
||||
uint8_t reserved[32];
|
||||
};
|
||||
|
||||
static int g_save_status = SAVE_STATUS_NONE;
|
||||
static int g_save_mode = 0;
|
||||
static void* g_save_user_data = nullptr;
|
||||
static char g_save_dir_name[sizeof(SaveDataDirName::data)] {};
|
||||
static std::atomic<int> g_save_status = SAVE_STATUS_NONE;
|
||||
static bool g_save_running_polled = false;
|
||||
static int g_save_mode = 0;
|
||||
static void* g_save_user_data = nullptr;
|
||||
static char g_save_dir_name[sizeof(SaveDataDirName::data)] {};
|
||||
|
||||
int KYTY_SYSV_ABI SaveDataDialogInitialize() {
|
||||
PRINT_NAME();
|
||||
|
||||
g_save_status = SAVE_STATUS_INITIALIZED;
|
||||
g_save_mode = 0;
|
||||
g_save_user_data = nullptr;
|
||||
g_save_dir_name[0] = '\0';
|
||||
g_save_status = SAVE_STATUS_INITIALIZED;
|
||||
g_save_running_polled = false;
|
||||
g_save_mode = 0;
|
||||
g_save_user_data = nullptr;
|
||||
g_save_dir_name[0] = '\0';
|
||||
|
||||
return OK;
|
||||
}
|
||||
@@ -376,6 +380,14 @@ int KYTY_SYSV_ABI SaveDataDialogGetStatus() {
|
||||
int KYTY_SYSV_ABI SaveDataDialogUpdateStatus() {
|
||||
PRINT_NAME();
|
||||
|
||||
// Some games require a RUNNING update before FINISHED.
|
||||
if (g_save_status == SAVE_STATUS_RUNNING) {
|
||||
if (g_save_running_polled) {
|
||||
g_save_status = SAVE_STATUS_FINISHED;
|
||||
}
|
||||
g_save_running_polled = true;
|
||||
}
|
||||
|
||||
return g_save_status;
|
||||
}
|
||||
|
||||
@@ -431,7 +443,8 @@ int KYTY_SYSV_ABI SaveDataDialogOpen(const void* param) {
|
||||
}
|
||||
}
|
||||
|
||||
g_save_status = SAVE_STATUS_FINISHED;
|
||||
g_save_status = SAVE_STATUS_RUNNING;
|
||||
g_save_running_polled = false;
|
||||
|
||||
return OK;
|
||||
}
|
||||
@@ -441,7 +454,8 @@ int KYTY_SYSV_ABI SaveDataDialogClose(const void* close_param) {
|
||||
|
||||
LOGF("\t close_param = 0x%016" PRIx64 "\n", reinterpret_cast<uint64_t>(close_param));
|
||||
|
||||
g_save_status = SAVE_STATUS_FINISHED;
|
||||
g_save_status = SAVE_STATUS_FINISHED;
|
||||
g_save_running_polled = false;
|
||||
|
||||
return OK;
|
||||
}
|
||||
@@ -455,10 +469,11 @@ int KYTY_SYSV_ABI SaveDataDialogIsReadyToDisplay() {
|
||||
int KYTY_SYSV_ABI SaveDataDialogTerminate() {
|
||||
PRINT_NAME();
|
||||
|
||||
g_save_status = SAVE_STATUS_NONE;
|
||||
g_save_mode = 0;
|
||||
g_save_user_data = nullptr;
|
||||
g_save_dir_name[0] = '\0';
|
||||
g_save_status = SAVE_STATUS_NONE;
|
||||
g_save_running_polled = false;
|
||||
g_save_mode = 0;
|
||||
g_save_user_data = nullptr;
|
||||
g_save_dir_name[0] = '\0';
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
+171
-53
@@ -5,13 +5,17 @@
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
namespace Libs::Controller::DualSenseHaptics {
|
||||
|
||||
struct Stream {
|
||||
uint32_t freq = 0;
|
||||
bool speaker = false;
|
||||
int controller = -1;
|
||||
SDL_AudioStream* sdl = nullptr;
|
||||
SDL_AudioDeviceID device = 0;
|
||||
uint64_t next_check = 0;
|
||||
@@ -20,19 +24,26 @@ struct Stream {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr int FRAME_BYTES = 4 * sizeof(float);
|
||||
Common::Mutex g_mutex;
|
||||
int g_controller = -1;
|
||||
int g_playback_streams = 0;
|
||||
uint8_t g_large_motor = 0, g_small_motor = 0;
|
||||
uint64_t g_rumble_until = 0, g_haptics_until = 0;
|
||||
constexpr int FRAME_BYTES = 4 * sizeof(float);
|
||||
Common::Mutex g_mutex;
|
||||
int g_controller = -1;
|
||||
int g_haptics_streams = 0;
|
||||
uint8_t g_large_motor = 0, g_small_motor = 0;
|
||||
uint64_t g_rumble_until = 0, g_haptics_until = 0;
|
||||
int g_speaker_controller = -1;
|
||||
std::map<int, int> g_speaker_streams;
|
||||
|
||||
void ApplyRumble() { // Caller holds g_mutex; no calls into Audio or Controller.
|
||||
void ApplyRumble(bool force = false) { // Caller holds g_mutex; no calls into Audio or Controller.
|
||||
SDL_LockJoysticks();
|
||||
if (auto* pad = SDL_GetGamepadFromID(static_cast<SDL_JoystickID>(g_controller));
|
||||
pad != nullptr) {
|
||||
const auto now = SDL_GetTicks();
|
||||
const bool muted = g_haptics_until > now || g_rumble_until <= now;
|
||||
if (force && !muted) {
|
||||
// SDL caches motor strengths. Force a report after raw speaker routing changed the
|
||||
// controller's motor mode without changing that cache.
|
||||
(void)SDL_RumbleGamepad(pad, 0, 0, 0);
|
||||
}
|
||||
(void)SDL_RumbleGamepad(
|
||||
pad, muted ? 0 : g_large_motor * 0x101U, muted ? 0 : g_small_motor * 0x101U,
|
||||
g_rumble_until > now ? static_cast<uint32_t>(g_rumble_until - now) : 0);
|
||||
@@ -42,14 +53,52 @@ void ApplyRumble() { // Caller holds g_mutex; no calls into Audio or Controller.
|
||||
|
||||
void SelectController(int controller) {
|
||||
if (g_controller != controller) {
|
||||
g_haptics_until = 0;
|
||||
g_haptics_until = g_rumble_until = 0;
|
||||
ApplyRumble();
|
||||
g_controller = controller;
|
||||
g_large_motor = g_small_motor = 0;
|
||||
g_rumble_until = 0;
|
||||
}
|
||||
}
|
||||
|
||||
bool RouteSpeaker(int controller, bool speaker) { // Caller holds g_mutex.
|
||||
// Route the right channel to the speaker and mute the headphones with the values Linux uses
|
||||
// without headphones (output report 0x02). Plugged-in headphones are not checked.
|
||||
std::array<uint8_t, 38> report {};
|
||||
report[0] = 0x80; // Audio control valid
|
||||
if (speaker) {
|
||||
report[0] |= 0x20; // Speaker volume valid
|
||||
report[1] = 0x80; // Audio control 2 valid
|
||||
report[5] = 0x64; // Speaker volume
|
||||
report[7] = 0x30; // Output path: right channel to the speaker, headphones muted
|
||||
report[37] = 0x02; // Speaker preamp +6 dB
|
||||
}
|
||||
bool routed = false;
|
||||
SDL_LockJoysticks();
|
||||
if (auto* pad = SDL_GetGamepadFromID(static_cast<SDL_JoystickID>(controller)); pad != nullptr) {
|
||||
routed = SDL_SendGamepadEffect(pad, report.data(), static_cast<int>(report.size()));
|
||||
}
|
||||
SDL_UnlockJoysticks();
|
||||
if (routed && controller == g_controller) {
|
||||
ApplyRumble(true);
|
||||
}
|
||||
return routed;
|
||||
}
|
||||
|
||||
bool SelectSpeaker(int controller) { // Caller holds g_mutex; -1 restores headphone routing.
|
||||
if (g_speaker_controller == controller) {
|
||||
return true;
|
||||
}
|
||||
if (g_speaker_controller != -1) {
|
||||
RouteSpeaker(g_speaker_controller, false);
|
||||
}
|
||||
g_speaker_controller = -1;
|
||||
if (controller != -1 && !RouteSpeaker(controller, true)) {
|
||||
return false;
|
||||
}
|
||||
g_speaker_controller = controller;
|
||||
return true;
|
||||
}
|
||||
|
||||
void SDLCALL UpdateRumble(void*, SDL_AudioStream*, int, int) {
|
||||
Common::LockGuard lock(g_mutex);
|
||||
if (g_haptics_until != 0 && SDL_GetTicks() >= g_haptics_until) {
|
||||
@@ -64,22 +113,55 @@ void CloseDevice(Stream* stream) {
|
||||
SDL_DestroyAudioStream(stream->sdl);
|
||||
stream->sdl = nullptr;
|
||||
Common::LockGuard lock(g_mutex);
|
||||
if (--g_playback_streams == 0 && g_haptics_until != 0) {
|
||||
if (!stream->speaker && --g_haptics_streams == 0 && g_haptics_until != 0) {
|
||||
g_haptics_until = 0;
|
||||
ApplyRumble();
|
||||
}
|
||||
// Route back to the headphones once no speaker port plays on the controller.
|
||||
if (stream->speaker && --g_speaker_streams.at(stream->controller) == 0) {
|
||||
g_speaker_streams.erase(stream->controller);
|
||||
if (g_speaker_controller == stream->controller) {
|
||||
SelectSpeaker(-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool CanUseDevice(int controller) {
|
||||
// SDL exposes no portable association between a gamepad and its USB audio endpoint.
|
||||
// Require one wired DualSense; guessing with multiple pads could play on the wrong one.
|
||||
SDL_LockJoysticks();
|
||||
auto* pad = SDL_GetGamepadFromID(static_cast<SDL_JoystickID>(controller));
|
||||
bool usable =
|
||||
pad != nullptr && SDL_GetGamepadConnectionState(pad) == SDL_JOYSTICK_CONNECTION_WIRED;
|
||||
int count = 0;
|
||||
auto* pads = SDL_GetGamepads(&count);
|
||||
int dualsense_count = 0;
|
||||
bool found = false;
|
||||
for (int i = 0; i < count; i++) {
|
||||
if (SDL_GetGamepadTypeForID(pads[i]) == SDL_GAMEPAD_TYPE_PS5) {
|
||||
dualsense_count++;
|
||||
found |= pads[i] == static_cast<SDL_JoystickID>(controller);
|
||||
}
|
||||
}
|
||||
SDL_free(pads);
|
||||
SDL_UnlockJoysticks();
|
||||
return usable && found && dualsense_count == 1;
|
||||
}
|
||||
|
||||
SDL_AudioDeviceID FindDevice() {
|
||||
int count = 0;
|
||||
auto* devices = SDL_GetAudioPlaybackDevices(&count);
|
||||
SDL_AudioDeviceID device = 0;
|
||||
for (int i = 0; i < count && device == 0; i++) {
|
||||
for (int i = 0; i < count; i++) {
|
||||
const char* name = SDL_GetAudioDeviceName(devices[i]);
|
||||
SDL_AudioSpec spec {};
|
||||
if (name != nullptr && SDL_strcasestr(name, "DualSense") != nullptr &&
|
||||
SDL_GetAudioDeviceFormat(devices[i], &spec, nullptr) && spec.channels == 4) {
|
||||
if (device != 0) {
|
||||
device = 0; // Multiple matching endpoints cannot be associated reliably either.
|
||||
break;
|
||||
}
|
||||
device = devices[i];
|
||||
}
|
||||
}
|
||||
@@ -87,13 +169,59 @@ SDL_AudioDeviceID FindDevice() {
|
||||
return device;
|
||||
}
|
||||
|
||||
void RefreshDevice(Stream* stream, int controller) {
|
||||
if (stream->controller != controller) {
|
||||
CloseDevice(stream);
|
||||
stream->controller = controller;
|
||||
stream->next_check = 0;
|
||||
}
|
||||
const auto now = SDL_GetTicks();
|
||||
if (now < stream->next_check) {
|
||||
return;
|
||||
}
|
||||
stream->next_check = now + 2000;
|
||||
const auto device = FindDevice();
|
||||
if (stream->device == device && stream->sdl != nullptr) {
|
||||
return;
|
||||
}
|
||||
CloseDevice(stream);
|
||||
stream->device = device;
|
||||
if (device == 0) {
|
||||
return;
|
||||
}
|
||||
const SDL_AudioSpec desired {SDL_AUDIO_F32, 4, static_cast<int>(stream->freq)};
|
||||
auto* sdl = SDL_OpenAudioDeviceStream(device, &desired, UpdateRumble, nullptr);
|
||||
SDL_AudioSpec actual {};
|
||||
bool ready = sdl != nullptr &&
|
||||
SDL_GetAudioDeviceFormat(SDL_GetAudioStreamDevice(sdl), &actual, nullptr) &&
|
||||
actual.channels == 4 && SDL_ResumeAudioStreamDevice(sdl);
|
||||
if (ready) {
|
||||
Common::LockGuard lock(g_mutex);
|
||||
if (stream->speaker) {
|
||||
ready = SelectSpeaker(controller);
|
||||
if (ready) {
|
||||
g_speaker_streams[controller]++;
|
||||
}
|
||||
} else {
|
||||
g_haptics_streams++;
|
||||
}
|
||||
}
|
||||
if (ready) {
|
||||
stream->sdl = sdl;
|
||||
LOGF("DualSenseHaptics: playing on '%s'\n", SDL_GetAudioDeviceName(device));
|
||||
} else {
|
||||
SDL_DestroyAudioStream(sdl);
|
||||
LOGF("DualSenseHaptics: cannot open quad output: %s\n", SDL_GetError());
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Stream* Open(uint32_t freq) {
|
||||
Stream* Open(uint32_t freq, bool speaker) {
|
||||
if (freq == 0 || !SDL_InitSubSystem(SDL_INIT_AUDIO)) {
|
||||
return nullptr;
|
||||
}
|
||||
return new Stream {.freq = freq};
|
||||
return new Stream {.freq = freq, .speaker = speaker};
|
||||
}
|
||||
|
||||
void Close(Stream* stream) {
|
||||
@@ -104,44 +232,30 @@ void Close(Stream* stream) {
|
||||
}
|
||||
}
|
||||
|
||||
void Queue(Stream* stream, int controller, const void* data, uint32_t frames, uint32_t channels,
|
||||
bool is_float, const int* volume) {
|
||||
uint64_t Queue(Stream* stream, int controller, const void* data, uint32_t frames, uint32_t channels,
|
||||
bool is_float, const int* volume) {
|
||||
if (stream == nullptr || data == nullptr || frames == 0 || channels == 0 || volume == nullptr) {
|
||||
return;
|
||||
return 0;
|
||||
}
|
||||
if (SDL_GetGamepadTypeForID(static_cast<SDL_JoystickID>(controller)) != SDL_GAMEPAD_TYPE_PS5) {
|
||||
if (!CanUseDevice(controller)) {
|
||||
CloseDevice(stream);
|
||||
return;
|
||||
}
|
||||
const auto now = SDL_GetTicks();
|
||||
if (now >= stream->next_check) {
|
||||
stream->next_check = now + 2000;
|
||||
const auto device = FindDevice();
|
||||
if (stream->device != device || stream->sdl == nullptr) {
|
||||
CloseDevice(stream);
|
||||
stream->device = device;
|
||||
if (device != 0) {
|
||||
const SDL_AudioSpec desired {SDL_AUDIO_F32, 4, static_cast<int>(stream->freq)};
|
||||
auto* sdl = SDL_OpenAudioDeviceStream(device, &desired, UpdateRumble, nullptr);
|
||||
SDL_AudioSpec actual {};
|
||||
if (sdl != nullptr &&
|
||||
SDL_GetAudioDeviceFormat(SDL_GetAudioStreamDevice(sdl), &actual, nullptr) &&
|
||||
actual.channels == 4 && SDL_ResumeAudioStreamDevice(sdl)) {
|
||||
stream->sdl = sdl;
|
||||
{
|
||||
Common::LockGuard lock(g_mutex);
|
||||
g_playback_streams++;
|
||||
}
|
||||
LOGF("DualSenseHaptics: playing on '%s'\n", SDL_GetAudioDeviceName(device));
|
||||
} else {
|
||||
SDL_DestroyAudioStream(sdl);
|
||||
LOGF("DualSenseHaptics: cannot open quad output: %s\n", SDL_GetError());
|
||||
}
|
||||
}
|
||||
}
|
||||
stream->next_check = 0; // Look for the device as soon as a DualSense is active again.
|
||||
return 0;
|
||||
}
|
||||
RefreshDevice(stream, controller);
|
||||
if (stream->sdl == nullptr) {
|
||||
return;
|
||||
return 0;
|
||||
}
|
||||
if (stream->speaker) {
|
||||
bool routed;
|
||||
{
|
||||
Common::LockGuard lock(g_mutex);
|
||||
routed = SelectSpeaker(controller);
|
||||
}
|
||||
if (!routed) {
|
||||
CloseDevice(stream);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
stream->buffer.assign(static_cast<size_t>(frames) * 4, 0.0f);
|
||||
bool audible = false;
|
||||
@@ -152,7 +266,8 @@ void Queue(Stream* stream, int controller, const void* data, uint32_t frames, ui
|
||||
float value = is_float ? static_cast<const float*>(data)[index]
|
||||
: static_cast<const int16_t*>(data)[index] / 32768.0f;
|
||||
value *= volume[src_ch] / 32768.0f;
|
||||
stream->buffer[static_cast<size_t>(frame) * 4 + ch + 2] = value;
|
||||
// The speaker plays the right front channel; the actuators play the back ones.
|
||||
stream->buffer[static_cast<size_t>(frame) * 4 + ch + (stream->speaker ? 0 : 2)] = value;
|
||||
audible |= std::fabs(value) > 1.0f / 1024;
|
||||
}
|
||||
}
|
||||
@@ -161,26 +276,29 @@ void Queue(Stream* stream, int controller, const void* data, uint32_t frames, ui
|
||||
SDL_ClearAudioStream(stream->sdl);
|
||||
queued = 0;
|
||||
}
|
||||
const int bytes = static_cast<int>(frames * FRAME_BYTES);
|
||||
if (audible) {
|
||||
const int bytes = static_cast<int>(frames * FRAME_BYTES);
|
||||
const auto queued_us =
|
||||
static_cast<uint64_t>(queued + bytes) * 1000000 / (stream->freq * FRAME_BYTES);
|
||||
if (audible && !stream->speaker) {
|
||||
Common::LockGuard lock(g_mutex);
|
||||
if (SDL_GetGamepadTypeForID(static_cast<SDL_JoystickID>(controller)) !=
|
||||
SDL_GAMEPAD_TYPE_PS5) {
|
||||
return;
|
||||
return 0;
|
||||
}
|
||||
SelectController(controller);
|
||||
const auto playback_now = SDL_GetTicks();
|
||||
const bool starting = g_haptics_until <= playback_now;
|
||||
const auto duration = (queued + bytes) * 1000ULL / (stream->freq * FRAME_BYTES);
|
||||
g_haptics_until =
|
||||
std::max(g_haptics_until, playback_now + std::max<uint64_t>(duration, 250));
|
||||
std::max(g_haptics_until, playback_now + std::max<uint64_t>(queued_us / 1000, 250));
|
||||
if (starting) {
|
||||
ApplyRumble();
|
||||
}
|
||||
}
|
||||
if (!SDL_PutAudioStreamData(stream->sdl, stream->buffer.data(), bytes)) {
|
||||
CloseDevice(stream);
|
||||
return 0;
|
||||
}
|
||||
return queued_us;
|
||||
}
|
||||
|
||||
bool SetVibration(int controller, uint8_t large_motor, uint8_t small_motor) {
|
||||
@@ -198,8 +316,8 @@ bool SetVibration(int controller, uint8_t large_motor, uint8_t small_motor) {
|
||||
|
||||
void Shutdown() {
|
||||
Common::LockGuard lock(g_mutex);
|
||||
g_rumble_until = 0;
|
||||
SelectController(-1);
|
||||
SelectSpeaker(-1);
|
||||
}
|
||||
|
||||
} // namespace Libs::Controller::DualSenseHaptics
|
||||
@@ -7,12 +7,18 @@ namespace Libs::Controller::DualSenseHaptics {
|
||||
|
||||
struct Stream;
|
||||
|
||||
Stream* Open(uint32_t freq);
|
||||
// A speaker stream plays a pad speaker port and routes the speaker to it; the others play vibration
|
||||
// ports on the actuators.
|
||||
Stream* Open(uint32_t freq, bool speaker);
|
||||
void Close(Stream* stream);
|
||||
void Queue(Stream* stream, int controller, const void* data, uint32_t frames, uint32_t channels,
|
||||
bool is_float, const int* volume);
|
||||
// Returns how long the queued audio still plays, in microseconds, or 0 when no DualSense took it
|
||||
// (the active pad has no usable USB audio device or its speaker cannot be routed).
|
||||
// Multiple DualSenses or matching audio endpoints are ambiguous and are not selected.
|
||||
uint64_t Queue(Stream* stream, int controller, const void* data, uint32_t frames, uint32_t channels,
|
||||
bool is_float, const int* volume);
|
||||
// Returns false for other gamepad types, which retain the normal rumble path.
|
||||
bool SetVibration(int controller, uint8_t large_motor, uint8_t small_motor);
|
||||
// Also restores the headphone routing.
|
||||
void Shutdown();
|
||||
|
||||
} // namespace Libs::Controller::DualSenseHaptics
|
||||
|
||||
+313
-373
@@ -12,12 +12,18 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <deque>
|
||||
#include <limits>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
#ifdef min
|
||||
@@ -36,13 +42,6 @@ struct ResultBuffer {
|
||||
uint32_t errorOffset = 0;
|
||||
};
|
||||
|
||||
struct SubmissionState {
|
||||
uint64_t command_buffer = 0;
|
||||
uint64_t result = 0;
|
||||
int32_t execution_result = 0;
|
||||
uint32_t error_offset = 0;
|
||||
};
|
||||
|
||||
struct ResolvedPathInfo {
|
||||
int result = OK;
|
||||
uint32_t file_id = 0xffffffffu;
|
||||
@@ -53,7 +52,8 @@ struct ResolvedPathInfo {
|
||||
|
||||
static std::mutex g_mutex;
|
||||
static uint32_t g_next_submission_id = 1;
|
||||
static std::unordered_map<uint32_t, SubmissionState> g_submissions;
|
||||
static std::unordered_map<uint32_t, bool> g_submissions;
|
||||
static std::condition_variable g_submission_cv;
|
||||
static std::unordered_map<uint32_t, std::string> g_files;
|
||||
static std::unordered_map<uint32_t, uint64_t> g_file_sizes;
|
||||
static std::unordered_map<std::string, ResolvedPathInfo> g_resolved_paths;
|
||||
@@ -339,38 +339,35 @@ static int ResolvePathsCommon(const char* const* path_list, uint32_t count, uint
|
||||
return results != nullptr ? static_cast<int>(success_count) : OK;
|
||||
}
|
||||
|
||||
static uint32_t AllocateSubmissionId(uint64_t command_buffer, uint64_t result) {
|
||||
static uint32_t AllocateSubmissionId() {
|
||||
std::scoped_lock lock(g_mutex);
|
||||
|
||||
auto id = g_next_submission_id++;
|
||||
if (id == 0) {
|
||||
id = g_next_submission_id++;
|
||||
}
|
||||
g_submissions[id] = SubmissionState {command_buffer, result};
|
||||
g_submissions.emplace(id, false);
|
||||
return id;
|
||||
}
|
||||
|
||||
static void SetSubmissionResult(uint32_t submission_id, int32_t execution_result,
|
||||
uint32_t error_offset) {
|
||||
std::scoped_lock lock(g_mutex);
|
||||
auto it = g_submissions.find(submission_id);
|
||||
if (it != g_submissions.end()) {
|
||||
it->second.execution_result = execution_result;
|
||||
it->second.error_offset = error_offset;
|
||||
static void SetSubmissionComplete(uint32_t submission_id) {
|
||||
if (submission_id == 0) {
|
||||
return;
|
||||
}
|
||||
std::scoped_lock lock(g_mutex);
|
||||
g_submissions.at(submission_id) = true;
|
||||
g_submission_cv.notify_all();
|
||||
}
|
||||
|
||||
static bool CompleteSubmission(uint32_t submission_id, SubmissionState* state) {
|
||||
std::scoped_lock lock(g_mutex);
|
||||
auto it = g_submissions.find(submission_id);
|
||||
if (it == g_submissions.end()) {
|
||||
static bool CompleteSubmission(uint32_t submission_id) {
|
||||
std::unique_lock lock(g_mutex);
|
||||
if (!g_submissions.contains(submission_id)) {
|
||||
return false;
|
||||
}
|
||||
if (state != nullptr) {
|
||||
*state = it->second;
|
||||
}
|
||||
g_submissions.erase(it);
|
||||
return true;
|
||||
g_submission_cv.wait(lock, [submission_id] {
|
||||
const auto it = g_submissions.find(submission_id);
|
||||
return it == g_submissions.end() || it->second;
|
||||
});
|
||||
return g_submissions.erase(submission_id) != 0;
|
||||
}
|
||||
|
||||
static int WriteResult(void* result, int32_t execution_result = 0, uint32_t error_offset = 0) {
|
||||
@@ -389,8 +386,9 @@ static int WriteResult(void* result, int32_t execution_result = 0, uint32_t erro
|
||||
} // namespace AprShared
|
||||
|
||||
namespace LibAmpr::Ampr {
|
||||
static int ExecuteAprCommandBuffer(uint64_t command_buffer, int32_t* execution_result,
|
||||
uint32_t* error_offset);
|
||||
enum class Engine { Apr, Amm };
|
||||
static int QueueCommandBuffer(Engine engine, uint64_t command_buffer, uint32_t bytes,
|
||||
uint32_t priority, void* result, uint32_t* out_submission_id);
|
||||
}
|
||||
|
||||
namespace LibKernelApr {
|
||||
@@ -536,89 +534,37 @@ static int KYTY_SYSV_ABI ResolveFilepathsWithPrefixToIdsAndFileSizesForEach(
|
||||
AprShared::ResolvePathsCommon(path_list, count, ids, sizes, nullptr, prefix, results));
|
||||
}
|
||||
|
||||
static int KYTY_SYSV_ABI SubmitCommandBufferAndGetResult(void* command_buffer, uint64_t,
|
||||
static int KYTY_SYSV_ABI SubmitCommandBufferAndGetResult(void* command_buffer, uint32_t priority,
|
||||
void* result,
|
||||
uint32_t* out_submission_id) {
|
||||
PRINT_NAME();
|
||||
|
||||
if (command_buffer == nullptr) {
|
||||
return KernelSyscallResult(LibKernel::KERNEL_ERROR_EINVAL);
|
||||
}
|
||||
|
||||
const auto command_buffer_addr = reinterpret_cast<uint64_t>(command_buffer);
|
||||
const auto id =
|
||||
AprShared::AllocateSubmissionId(command_buffer_addr, reinterpret_cast<uint64_t>(result));
|
||||
|
||||
int32_t execution_result = OK;
|
||||
uint32_t error_offset = 0;
|
||||
auto submit_result = LibAmpr::Ampr::ExecuteAprCommandBuffer(command_buffer_addr,
|
||||
&execution_result, &error_offset);
|
||||
if (submit_result != OK) {
|
||||
return KernelSyscallResult(submit_result);
|
||||
}
|
||||
AprShared::SetSubmissionResult(id, execution_result, error_offset);
|
||||
|
||||
if (out_submission_id != nullptr) {
|
||||
if (!AprShared::WriteGuest(reinterpret_cast<uint64_t>(out_submission_id), id)) {
|
||||
return KernelSyscallResult(LibKernel::KERNEL_ERROR_EFAULT);
|
||||
}
|
||||
}
|
||||
|
||||
return KernelSyscallResult(AprShared::WriteResult(result, execution_result, error_offset));
|
||||
return KernelSyscallResult(LibAmpr::Ampr::QueueCommandBuffer(
|
||||
LibAmpr::Ampr::Engine::Apr, reinterpret_cast<uint64_t>(command_buffer), 0, priority, result,
|
||||
out_submission_id));
|
||||
}
|
||||
|
||||
static int KYTY_SYSV_ABI SubmitCommandBuffer(void* command_buffer, uint64_t) {
|
||||
static int KYTY_SYSV_ABI SubmitCommandBuffer(void* command_buffer, uint32_t priority) {
|
||||
PRINT_NAME();
|
||||
|
||||
if (command_buffer == nullptr) {
|
||||
return KernelSyscallResult(LibKernel::KERNEL_ERROR_EINVAL);
|
||||
}
|
||||
|
||||
int32_t execution_result = OK;
|
||||
uint32_t error_offset = 0;
|
||||
return KernelSyscallResult(LibAmpr::Ampr::ExecuteAprCommandBuffer(
|
||||
reinterpret_cast<uint64_t>(command_buffer), &execution_result, &error_offset));
|
||||
return KernelSyscallResult(LibAmpr::Ampr::QueueCommandBuffer(
|
||||
LibAmpr::Ampr::Engine::Apr, reinterpret_cast<uint64_t>(command_buffer), 0, priority,
|
||||
nullptr, nullptr));
|
||||
}
|
||||
|
||||
static int KYTY_SYSV_ABI SubmitCommandBufferAndGetId(void* command_buffer, uint64_t,
|
||||
static int KYTY_SYSV_ABI SubmitCommandBufferAndGetId(void* command_buffer, uint32_t priority,
|
||||
uint32_t* out_submission_id) {
|
||||
PRINT_NAME();
|
||||
|
||||
if (command_buffer == nullptr || out_submission_id == nullptr) {
|
||||
if (out_submission_id == nullptr) {
|
||||
return KernelSyscallResult(LibKernel::KERNEL_ERROR_EINVAL);
|
||||
}
|
||||
|
||||
const auto command_buffer_addr = reinterpret_cast<uint64_t>(command_buffer);
|
||||
const auto id = AprShared::AllocateSubmissionId(command_buffer_addr, 0);
|
||||
|
||||
int32_t execution_result = OK;
|
||||
uint32_t error_offset = 0;
|
||||
auto submit_result = LibAmpr::Ampr::ExecuteAprCommandBuffer(command_buffer_addr,
|
||||
&execution_result, &error_offset);
|
||||
if (submit_result != OK) {
|
||||
return KernelSyscallResult(submit_result);
|
||||
}
|
||||
AprShared::SetSubmissionResult(id, execution_result, error_offset);
|
||||
|
||||
if (!AprShared::WriteGuest(reinterpret_cast<uint64_t>(out_submission_id), id)) {
|
||||
return KernelSyscallResult(LibKernel::KERNEL_ERROR_EFAULT);
|
||||
}
|
||||
|
||||
return OK;
|
||||
return KernelSyscallResult(LibAmpr::Ampr::QueueCommandBuffer(
|
||||
LibAmpr::Ampr::Engine::Apr, reinterpret_cast<uint64_t>(command_buffer), 0, priority,
|
||||
nullptr, out_submission_id));
|
||||
}
|
||||
|
||||
static int KYTY_SYSV_ABI WaitCommandBuffer(uint32_t submission_id) {
|
||||
PRINT_NAME();
|
||||
|
||||
AprShared::SubmissionState state {};
|
||||
if (!AprShared::CompleteSubmission(submission_id, &state)) {
|
||||
return KernelSyscallResult(LibKernel::KERNEL_ERROR_ESRCH);
|
||||
}
|
||||
if (state.result != 0) {
|
||||
return KernelSyscallResult(AprShared::WriteResult(
|
||||
reinterpret_cast<void*>(state.result), state.execution_result, state.error_offset));
|
||||
}
|
||||
return OK;
|
||||
return KernelSyscallResult(
|
||||
AprShared::CompleteSubmission(submission_id) ? OK : LibKernel::KERNEL_ERROR_ESRCH);
|
||||
}
|
||||
|
||||
} // namespace Apr
|
||||
@@ -705,41 +651,45 @@ struct CommandBufferState {
|
||||
bool header_validated = false;
|
||||
bool buffer_validated = false;
|
||||
struct ReadFileCommand {
|
||||
uint64_t record_offset = 0;
|
||||
uint32_t file_id = 0;
|
||||
uint64_t destination = 0;
|
||||
uint64_t size = 0;
|
||||
uint64_t file_offset = 0;
|
||||
uint32_t file_id = 0;
|
||||
uint64_t destination = 0;
|
||||
uint64_t size = 0;
|
||||
uint64_t file_offset = 0;
|
||||
};
|
||||
struct KernelEventCommand {
|
||||
uint64_t record_offset = 0;
|
||||
uint64_t eq = 0;
|
||||
int32_t id = 0;
|
||||
uint64_t data = 0;
|
||||
uint64_t eq = 0;
|
||||
int32_t id = 0;
|
||||
uint64_t data = 0;
|
||||
};
|
||||
struct WriteAddressCommand {
|
||||
uint64_t record_offset = 0;
|
||||
uint64_t address = 0;
|
||||
uint64_t value = 0;
|
||||
uint64_t address = 0;
|
||||
uint64_t value = 0;
|
||||
};
|
||||
struct WaitAddressCommand {
|
||||
uint64_t address;
|
||||
uint64_t value;
|
||||
uint8_t compare;
|
||||
};
|
||||
struct AmmMapCommand {
|
||||
uint64_t record_offset = 0;
|
||||
AmmCommandKind kind = AmmCommandKind::MapAuto;
|
||||
uint64_t va = 0;
|
||||
uint64_t dmem_offset = 0;
|
||||
uint64_t size = 0;
|
||||
int32_t type = 0;
|
||||
int32_t prot = 0;
|
||||
uint8_t gpu_mask_id = 0;
|
||||
AmmCommandKind kind = AmmCommandKind::MapAuto;
|
||||
uint64_t va = 0;
|
||||
uint64_t dmem_offset = 0;
|
||||
uint64_t size = 0;
|
||||
int32_t type = 0;
|
||||
int32_t prot = 0;
|
||||
uint8_t gpu_mask_id = 0;
|
||||
};
|
||||
std::vector<ReadFileCommand> read_file_commands;
|
||||
std::vector<KernelEventCommand> kernel_event_commands;
|
||||
std::vector<WriteAddressCommand> write_address_commands;
|
||||
std::vector<AmmMapCommand> amm_map_commands;
|
||||
bool gather_scatter_valid = false;
|
||||
uint32_t gather_scatter_file_id = 0;
|
||||
uint64_t gather_scatter_destination = 0;
|
||||
uint64_t gather_scatter_file_offset = 0;
|
||||
using CommandData = std::variant<ReadFileCommand, KernelEventCommand, WriteAddressCommand,
|
||||
AmmMapCommand, WaitAddressCommand>;
|
||||
struct Command {
|
||||
uint64_t record_offset;
|
||||
CommandData data;
|
||||
};
|
||||
std::vector<Command> commands;
|
||||
bool gather_scatter_valid = false;
|
||||
uint32_t gather_scatter_file_id = 0;
|
||||
uint64_t gather_scatter_destination = 0;
|
||||
uint64_t gather_scatter_file_offset = 0;
|
||||
};
|
||||
|
||||
struct AmmUsageStatsData {
|
||||
@@ -757,11 +707,6 @@ static std::unordered_map<uint64_t, CommandBufferState> g_command_buffers;
|
||||
static std::unordered_map<uint64_t, uint64_t> g_command_buffer_aliases;
|
||||
using CommandBufferIterator = std::unordered_map<uint64_t, CommandBufferState>::iterator;
|
||||
|
||||
static bool HasQueuedCommands(const CommandBufferState& state) {
|
||||
return !state.read_file_commands.empty() || !state.kernel_event_commands.empty() ||
|
||||
!state.write_address_commands.empty() || !state.amm_map_commands.empty();
|
||||
}
|
||||
|
||||
static void RegisterCommandBufferAliasLocked(uint64_t command_buffer, uint64_t buffer) {
|
||||
for (auto it = g_command_buffer_aliases.begin(); it != g_command_buffer_aliases.end();) {
|
||||
if (it->second == command_buffer) {
|
||||
@@ -950,7 +895,7 @@ static bool UpdateCommandBufferTypeFlags(uint64_t command_buffer, uint32_t set_b
|
||||
static std::unordered_map<uint64_t, CommandBufferState>::iterator
|
||||
ResolveCommandBufferStateLocked(uint64_t command_buffer) {
|
||||
auto it = g_command_buffers.find(command_buffer);
|
||||
if (it != g_command_buffers.end() && HasQueuedCommands(it->second)) {
|
||||
if (it != g_command_buffers.end() && !it->second.commands.empty()) {
|
||||
return it;
|
||||
}
|
||||
|
||||
@@ -999,10 +944,7 @@ static bool WriteCommandBufferPointers(uint64_t command_buffer, uint64_t buffer,
|
||||
state.write_offset = write_offset;
|
||||
state.header_validated = true;
|
||||
state.buffer_validated = false;
|
||||
state.read_file_commands.clear();
|
||||
state.kernel_event_commands.clear();
|
||||
state.write_address_commands.clear();
|
||||
state.amm_map_commands.clear();
|
||||
state.commands.clear();
|
||||
state.gather_scatter_valid = false;
|
||||
state.gather_scatter_file_id = 0;
|
||||
state.gather_scatter_destination = 0;
|
||||
@@ -1107,7 +1049,8 @@ static bool AppendReadFileRecord(uint64_t command_buffer, uint8_t opcode, uint32
|
||||
std::memset(reinterpret_cast<void*>(state.buffer + record_offset), 0,
|
||||
static_cast<size_t>(record_size));
|
||||
std::memcpy(reinterpret_cast<void*>(state.buffer + record_offset), &opcode, sizeof(opcode));
|
||||
state.read_file_commands.push_back({record_offset, file_id, destination, size, file_offset});
|
||||
state.commands.push_back({record_offset, CommandBufferState::ReadFileCommand {
|
||||
file_id, destination, size, file_offset}});
|
||||
if (!CommitCommandBufferRecord(command_buffer, &state, record_size)) {
|
||||
return false;
|
||||
}
|
||||
@@ -1145,30 +1088,11 @@ static bool AppendKernelEventRecord(uint64_t command_buffer, uint64_t eq, int32_
|
||||
const auto record_offset = state.write_offset;
|
||||
std::memcpy(reinterpret_cast<void*>(state.buffer + record_offset), record.data(),
|
||||
record.size());
|
||||
state.kernel_event_commands.push_back({record_offset, eq, id, data});
|
||||
state.commands.push_back(
|
||||
{record_offset, CommandBufferState::KernelEventCommand {eq, id, data}});
|
||||
return CommitCommandBufferRecord(command_buffer, &state, KERNEL_EVENT_RECORD_SIZE);
|
||||
}
|
||||
|
||||
static bool AppendWriteAddressRecord(uint64_t command_buffer, uint64_t address, uint64_t value,
|
||||
uint64_t record_size = 0x20) {
|
||||
std::scoped_lock lock(g_command_buffer_mutex);
|
||||
CommandBufferIterator it;
|
||||
if (!GetOrCreateCommandBufferStateLocked(command_buffer, &it)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto& state = it->second;
|
||||
if (!EnsureCommandBufferRecordSpace(command_buffer, &state, record_size)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto record_offset = state.write_offset;
|
||||
std::memset(reinterpret_cast<void*>(state.buffer + record_offset), 0,
|
||||
static_cast<size_t>(record_size));
|
||||
state.write_address_commands.push_back({record_offset, address, value});
|
||||
return CommitCommandBufferRecord(command_buffer, &state, record_size);
|
||||
}
|
||||
|
||||
static bool ValidateAmmMapArgs(uint64_t va, uint64_t size) {
|
||||
return va != 0 && size != 0 && (va & (AMM_PAGE_SIZE - 1u)) == 0 &&
|
||||
(size & (AMM_PAGE_SIZE - 1u)) == 0 && va + size >= va;
|
||||
@@ -1226,8 +1150,8 @@ static int ExecuteAmmMapCommand(const CommandBufferState::AmmMapCommand& command
|
||||
static_cast<int64_t>(command.dmem_offset), AMM_PAGE_SIZE);
|
||||
}
|
||||
|
||||
static bool AppendAmmMapRecord(uint64_t command_buffer,
|
||||
const CommandBufferState::AmmMapCommand& cmd, uint64_t record_size) {
|
||||
static bool AppendCommandRecord(uint64_t command_buffer, CommandBufferState::CommandData command,
|
||||
uint64_t record_size) {
|
||||
std::scoped_lock lock(g_command_buffer_mutex);
|
||||
CommandBufferIterator it;
|
||||
if (!GetOrCreateCommandBufferStateLocked(command_buffer, &it)) {
|
||||
@@ -1239,149 +1163,204 @@ static bool AppendAmmMapRecord(uint64_t command_
|
||||
return false;
|
||||
}
|
||||
|
||||
auto record = cmd;
|
||||
record.record_offset = state.write_offset;
|
||||
auto* record_addr = reinterpret_cast<void*>(state.buffer + state.write_offset);
|
||||
std::memset(record_addr, 0, static_cast<size_t>(record_size));
|
||||
state.amm_map_commands.push_back(record);
|
||||
state.commands.push_back({state.write_offset, std::move(command)});
|
||||
return CommitCommandBufferRecord(command_buffer, &state, record_size);
|
||||
}
|
||||
|
||||
static int ReadHostFileToGuest(const std::string& host_path, uint64_t file_offset,
|
||||
uint64_t destination, uint64_t size, uint64_t* bytes_read);
|
||||
|
||||
static int ExecuteAprCommandBuffer(uint64_t command_buffer, int32_t* execution_result,
|
||||
uint32_t* error_offset) {
|
||||
if (execution_result == nullptr || error_offset == nullptr) {
|
||||
return LibKernel::KERNEL_ERROR_EINVAL;
|
||||
static int ExecuteCommand(const CommandBufferState::Command& entry) {
|
||||
if (const auto* payload = std::get_if<CommandBufferState::ReadFileCommand>(&entry.data)) {
|
||||
const auto& command = *payload;
|
||||
std::string host_path;
|
||||
if (!AprShared::TryGetHostPath(command.file_id, &host_path)) {
|
||||
LOGF("\tAPR submit failed for unknown file id: 0x%08" PRIx32 "\n", command.file_id);
|
||||
return LibKernel::KERNEL_ERROR_ENOENT;
|
||||
}
|
||||
|
||||
uint64_t bytes_read = 0;
|
||||
auto result = ReadHostFileToGuest(host_path, command.file_offset, command.destination,
|
||||
command.size, &bytes_read);
|
||||
if (result != OK) {
|
||||
LOGF("\tAPR submit read failed: id=0x%08" PRIx32 ", result=0x%08" PRIx32 ", path=%s\n",
|
||||
command.file_id, static_cast<uint32_t>(result), host_path.c_str());
|
||||
return result;
|
||||
}
|
||||
} else if (const auto* payload =
|
||||
std::get_if<CommandBufferState::KernelEventCommand>(&entry.data)) {
|
||||
const auto& command = *payload;
|
||||
const auto eq = static_cast<LibKernel::EventQueue::KernelEqueue>(command.eq);
|
||||
auto result = LibKernel::EventQueue::KernelTriggerUserEvent(
|
||||
eq, command.id, reinterpret_cast<void*>(command.data));
|
||||
if (result != OK) {
|
||||
LOGF("\tAPR submit event failed: eq=0x%016" PRIx64 ", id=%" PRId32
|
||||
", result=0x%08" PRIx32 "\n",
|
||||
command.eq, command.id, static_cast<uint32_t>(result));
|
||||
return result;
|
||||
}
|
||||
} else if (const auto* payload =
|
||||
std::get_if<CommandBufferState::WriteAddressCommand>(&entry.data)) {
|
||||
const auto& command = *payload;
|
||||
std::atomic_ref(*reinterpret_cast<uint64_t*>(command.address))
|
||||
.store(command.value, std::memory_order_release);
|
||||
} else if (const auto* payload = std::get_if<CommandBufferState::AmmMapCommand>(&entry.data)) {
|
||||
const auto& command = *payload;
|
||||
int result = OK;
|
||||
if (command.kind == AmmCommandKind::Unmap) {
|
||||
result = LibKernel::Memory::KernelMunmap(command.va, command.size);
|
||||
} else {
|
||||
result = ExecuteAmmMapCommand(command);
|
||||
}
|
||||
|
||||
if (result != OK) {
|
||||
LOGF("\tAMM submit command failed: kind=%u va=0x%016" PRIx64 " dmem=0x%016" PRIx64
|
||||
" size=0x%016" PRIx64 " type=%" PRId32 " prot=0x%08" PRIx32 " result=0x%08" PRIx32
|
||||
"\n",
|
||||
static_cast<uint32_t>(command.kind), command.va, command.dmem_offset, command.size,
|
||||
command.type, static_cast<uint32_t>(command.prot), static_cast<uint32_t>(result));
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return OK;
|
||||
}
|
||||
|
||||
struct Submission {
|
||||
std::vector<CommandBufferState::Command> commands;
|
||||
size_t cursor = 0;
|
||||
void* result = nullptr;
|
||||
uint32_t id = 0;
|
||||
};
|
||||
|
||||
static bool IsWaitSatisfied(const CommandBufferState::WaitAddressCommand& wait) {
|
||||
const uint64_t value =
|
||||
std::atomic_ref(*reinterpret_cast<uint64_t*>(wait.address)).load(std::memory_order_acquire);
|
||||
switch (wait.compare) {
|
||||
case 0: return value == wait.value;
|
||||
case 1: return value > wait.value;
|
||||
case 2: return value < wait.value;
|
||||
case 3: return value != wait.value;
|
||||
case 4: return static_cast<int64_t>(value - wait.value) >= 0;
|
||||
case 5: return static_cast<int64_t>(value) > static_cast<int64_t>(wait.value);
|
||||
default: return static_cast<int64_t>(value) < static_cast<int64_t>(wait.value);
|
||||
}
|
||||
}
|
||||
|
||||
class CommandEngine {
|
||||
public:
|
||||
explicit CommandEngine(size_t priorities)
|
||||
: m_queues(priorities), m_worker([this](std::stop_token stop) { Run(stop); }) {}
|
||||
|
||||
~CommandEngine() {
|
||||
std::scoped_lock lock(m_mutex);
|
||||
m_worker.request_stop();
|
||||
m_cv.notify_all();
|
||||
}
|
||||
|
||||
*execution_result = OK;
|
||||
*error_offset = 0;
|
||||
void Submit(uint32_t priority, Submission submission) {
|
||||
std::scoped_lock lock(m_mutex);
|
||||
m_queues[priority].push_back(std::move(submission));
|
||||
m_cv.notify_one();
|
||||
}
|
||||
|
||||
private:
|
||||
void Run(std::stop_token stop) {
|
||||
std::unique_lock lock(m_mutex);
|
||||
while (!stop.stop_requested()) {
|
||||
std::deque<Submission>* ready = nullptr;
|
||||
bool pending = false;
|
||||
for (auto& queue: m_queues) {
|
||||
if (queue.empty()) {
|
||||
continue;
|
||||
}
|
||||
pending = true;
|
||||
auto& submission = queue.front();
|
||||
if (submission.cursor < submission.commands.size()) {
|
||||
const auto* wait = std::get_if<CommandBufferState::WaitAddressCommand>(
|
||||
&submission.commands[submission.cursor].data);
|
||||
if (wait != nullptr && !IsWaitSatisfied(*wait)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
ready = &queue;
|
||||
break;
|
||||
}
|
||||
if (ready == nullptr) {
|
||||
if (pending) {
|
||||
// CPU and GPU fence writes do not notify the submission queue.
|
||||
m_cv.wait_for(lock, std::chrono::microseconds(100));
|
||||
} else {
|
||||
m_cv.wait(lock);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
auto& submission = ready->front();
|
||||
lock.unlock();
|
||||
int result = OK;
|
||||
uint32_t error_offset = 0;
|
||||
if (submission.cursor < submission.commands.size()) {
|
||||
const auto& command = submission.commands[submission.cursor++];
|
||||
result = ExecuteCommand(command);
|
||||
if (result != OK) {
|
||||
error_offset = static_cast<uint32_t>(command.record_offset);
|
||||
}
|
||||
}
|
||||
const bool complete = result != OK || submission.cursor == submission.commands.size();
|
||||
if (complete) {
|
||||
AprShared::WriteResult(submission.result, result, error_offset);
|
||||
AprShared::SetSubmissionComplete(submission.id);
|
||||
}
|
||||
lock.lock();
|
||||
if (complete) {
|
||||
ready->pop_front();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::mutex m_mutex;
|
||||
std::condition_variable m_cv;
|
||||
std::vector<std::deque<Submission>> m_queues;
|
||||
std::jthread m_worker;
|
||||
};
|
||||
|
||||
static int QueueCommandBuffer(Engine engine, uint64_t command_buffer, uint32_t bytes,
|
||||
uint32_t priority, void* result, uint32_t* out_submission_id) {
|
||||
const bool amm = engine == Engine::Amm;
|
||||
if (command_buffer == 0 || priority > (amm ? 2u : 6u)) {
|
||||
return LibKernel::KERNEL_ERROR_EINVAL;
|
||||
}
|
||||
if ((result != nullptr &&
|
||||
!AprShared::IsValidGuestRange(reinterpret_cast<uint64_t>(result),
|
||||
sizeof(AprShared::ResultBuffer), true)) ||
|
||||
(out_submission_id != nullptr &&
|
||||
!AprShared::IsValidGuestRange(reinterpret_cast<uint64_t>(out_submission_id),
|
||||
sizeof(uint32_t), true))) {
|
||||
return LibKernel::KERNEL_ERROR_EFAULT;
|
||||
}
|
||||
CommandBufferState state {};
|
||||
if (!TryGetCommandBufferState(command_buffer, &state)) {
|
||||
return LibKernel::KERNEL_ERROR_EFAULT;
|
||||
}
|
||||
|
||||
enum class CommandKind {
|
||||
ReadFile,
|
||||
KernelEvent,
|
||||
WriteAddress,
|
||||
AmmMap,
|
||||
};
|
||||
|
||||
struct OrderedCommand {
|
||||
uint64_t record_offset = 0;
|
||||
CommandKind kind = CommandKind::ReadFile;
|
||||
size_t index = 0;
|
||||
};
|
||||
|
||||
std::vector<OrderedCommand> ordered;
|
||||
ordered.reserve(state.read_file_commands.size() + state.kernel_event_commands.size() +
|
||||
state.write_address_commands.size() + state.amm_map_commands.size());
|
||||
for (size_t i = 0; i < state.read_file_commands.size(); i++) {
|
||||
if (state.read_file_commands[i].record_offset < state.write_offset) {
|
||||
ordered.push_back(
|
||||
{state.read_file_commands[i].record_offset, CommandKind::ReadFile, i});
|
||||
}
|
||||
const uint64_t end = amm ? bytes : state.write_offset;
|
||||
if (end == 0 || end > state.write_offset) {
|
||||
return LibKernel::KERNEL_ERROR_EINVAL;
|
||||
}
|
||||
for (size_t i = 0; i < state.kernel_event_commands.size(); i++) {
|
||||
if (state.kernel_event_commands[i].record_offset < state.write_offset) {
|
||||
ordered.push_back(
|
||||
{state.kernel_event_commands[i].record_offset, CommandKind::KernelEvent, i});
|
||||
}
|
||||
state.commands.erase(std::lower_bound(state.commands.begin(), state.commands.end(), end,
|
||||
[](const auto& command, uint64_t offset) {
|
||||
return command.record_offset < offset;
|
||||
}),
|
||||
state.commands.end());
|
||||
const uint32_t id = out_submission_id != nullptr ? AprShared::AllocateSubmissionId() : 0;
|
||||
if (out_submission_id != nullptr) {
|
||||
AprShared::WriteGuest(reinterpret_cast<uint64_t>(out_submission_id), id);
|
||||
}
|
||||
for (size_t i = 0; i < state.write_address_commands.size(); i++) {
|
||||
if (state.write_address_commands[i].record_offset < state.write_offset) {
|
||||
ordered.push_back(
|
||||
{state.write_address_commands[i].record_offset, CommandKind::WriteAddress, i});
|
||||
}
|
||||
}
|
||||
for (size_t i = 0; i < state.amm_map_commands.size(); i++) {
|
||||
if (state.amm_map_commands[i].record_offset < state.write_offset) {
|
||||
ordered.push_back({state.amm_map_commands[i].record_offset, CommandKind::AmmMap, i});
|
||||
}
|
||||
}
|
||||
|
||||
std::sort(ordered.begin(), ordered.end(), [](const OrderedCommand& a, const OrderedCommand& b) {
|
||||
return a.record_offset < b.record_offset;
|
||||
});
|
||||
|
||||
for (const auto& entry: ordered) {
|
||||
switch (entry.kind) {
|
||||
case CommandKind::ReadFile: {
|
||||
const auto& command = state.read_file_commands[entry.index];
|
||||
std::string host_path;
|
||||
if (!AprShared::TryGetHostPath(command.file_id, &host_path)) {
|
||||
LOGF("\tAPR submit failed for unknown file id: 0x%08" PRIx32 "\n",
|
||||
command.file_id);
|
||||
*execution_result = LibKernel::KERNEL_ERROR_ENOENT;
|
||||
*error_offset = static_cast<uint32_t>(command.record_offset);
|
||||
return OK;
|
||||
}
|
||||
|
||||
uint64_t bytes_read = 0;
|
||||
auto result = ReadHostFileToGuest(host_path, command.file_offset,
|
||||
command.destination, command.size, &bytes_read);
|
||||
if (result != OK) {
|
||||
LOGF("\tAPR submit read failed: id=0x%08" PRIx32 ", result=0x%08" PRIx32
|
||||
", path=%s\n",
|
||||
command.file_id, static_cast<uint32_t>(result), host_path.c_str());
|
||||
*execution_result = result;
|
||||
*error_offset = static_cast<uint32_t>(command.record_offset);
|
||||
return OK;
|
||||
}
|
||||
} break;
|
||||
case CommandKind::KernelEvent: {
|
||||
const auto& command = state.kernel_event_commands[entry.index];
|
||||
const auto eq = static_cast<LibKernel::EventQueue::KernelEqueue>(command.eq);
|
||||
auto result = LibKernel::EventQueue::KernelTriggerUserEvent(
|
||||
eq, command.id, reinterpret_cast<void*>(command.data));
|
||||
if (result != OK) {
|
||||
LOGF("\tAPR submit event failed: eq=0x%016" PRIx64 ", id=%" PRId32
|
||||
", result=0x%08" PRIx32 "\n",
|
||||
command.eq, command.id, static_cast<uint32_t>(result));
|
||||
*execution_result = result;
|
||||
*error_offset = static_cast<uint32_t>(command.record_offset);
|
||||
return OK;
|
||||
}
|
||||
} break;
|
||||
case CommandKind::WriteAddress: {
|
||||
const auto& command = state.write_address_commands[entry.index];
|
||||
if (!AprShared::WriteGuest(command.address, command.value)) {
|
||||
LOGF("\tAMPR submit write-address failed: address=0x%016" PRIx64
|
||||
" value=0x%016" PRIx64 "\n",
|
||||
command.address, command.value);
|
||||
*execution_result = LibKernel::KERNEL_ERROR_EFAULT;
|
||||
*error_offset = static_cast<uint32_t>(command.record_offset);
|
||||
return OK;
|
||||
}
|
||||
} break;
|
||||
case CommandKind::AmmMap: {
|
||||
const auto& command = state.amm_map_commands[entry.index];
|
||||
int result = OK;
|
||||
if (command.kind == AmmCommandKind::Unmap) {
|
||||
result = LibKernel::Memory::KernelMunmap(command.va, command.size);
|
||||
} else {
|
||||
result = ExecuteAmmMapCommand(command);
|
||||
}
|
||||
|
||||
if (result != OK) {
|
||||
LOGF("\tAMM submit command failed: kind=%u va=0x%016" PRIx64
|
||||
" dmem=0x%016" PRIx64 " size=0x%016" PRIx64 " type=%" PRId32
|
||||
" prot=0x%08" PRIx32 " result=0x%08" PRIx32 "\n",
|
||||
static_cast<uint32_t>(command.kind), command.va, command.dmem_offset,
|
||||
command.size, command.type, static_cast<uint32_t>(command.prot),
|
||||
static_cast<uint32_t>(result));
|
||||
*execution_result = result;
|
||||
*error_offset = static_cast<uint32_t>(command.record_offset);
|
||||
return OK;
|
||||
}
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
static CommandEngine apr_engine(7);
|
||||
static CommandEngine amm_engine(3);
|
||||
(amm ? amm_engine : apr_engine)
|
||||
.Submit(priority, {std::move(state.commands), 0, result, id});
|
||||
return OK;
|
||||
}
|
||||
|
||||
@@ -1437,8 +1416,11 @@ static int ReadHostFileToGuest(const std::string& host_path, uint64_t file_offse
|
||||
return OK;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> buffer(
|
||||
static_cast<size_t>(std::min<uint64_t>(APR_HOST_READ_CHUNK_SIZE, readable)));
|
||||
static thread_local std::vector<uint8_t> buffer;
|
||||
const auto buffer_size = static_cast<size_t>(std::min<uint64_t>(APR_HOST_READ_CHUNK_SIZE, readable));
|
||||
if (buffer.size() < buffer_size) {
|
||||
buffer.resize(buffer_size);
|
||||
}
|
||||
while (*bytes_read < readable) {
|
||||
const auto request =
|
||||
static_cast<uint32_t>(std::min<uint64_t>(buffer.size(), readable - *bytes_read));
|
||||
@@ -1935,11 +1917,20 @@ static int KYTY_SYSV_ABI CommandBufferPopMarker(void* command_buffer) {
|
||||
return AppendNoOpCommand(command_buffer, sizeof(uint32_t));
|
||||
}
|
||||
|
||||
static int KYTY_SYSV_ABI CommandBufferWaitOnAddress(void* command_buffer, volatile uint64_t*,
|
||||
uint64_t, uint8_t, uint8_t) {
|
||||
static int KYTY_SYSV_ABI CommandBufferWaitOnAddress(void* command_buffer,
|
||||
volatile uint64_t* address, uint64_t value,
|
||||
uint8_t compare, uint8_t) {
|
||||
PRINT_NAME();
|
||||
|
||||
return AppendNoOpCommand(command_buffer, 0x20);
|
||||
if (command_buffer == nullptr || address == nullptr ||
|
||||
(reinterpret_cast<uint64_t>(address) & 7) != 0 || compare > 6) {
|
||||
return LibKernel::KERNEL_ERROR_EINVAL;
|
||||
}
|
||||
return AppendCommandRecord(reinterpret_cast<uint64_t>(command_buffer),
|
||||
CommandBufferState::WaitAddressCommand {
|
||||
reinterpret_cast<uint64_t>(address), value, compare},
|
||||
0x20)
|
||||
? OK
|
||||
: LibKernel::KERNEL_ERROR_EBUSY;
|
||||
}
|
||||
|
||||
static int KYTY_SYSV_ABI CommandBufferWaitOnCounter(void* command_buffer, uint8_t, uint8_t,
|
||||
@@ -1951,11 +1942,14 @@ static int KYTY_SYSV_ABI CommandBufferWaitOnCounter(void* command_buffer, uint8_
|
||||
|
||||
static int AppendWriteAddressCommand(void* command_buffer, volatile uint64_t* address,
|
||||
uint64_t value) {
|
||||
if (command_buffer == nullptr || address == nullptr) {
|
||||
if (command_buffer == nullptr || address == nullptr ||
|
||||
(reinterpret_cast<uint64_t>(address) & 7) != 0) {
|
||||
return LibKernel::KERNEL_ERROR_EINVAL;
|
||||
}
|
||||
return AppendWriteAddressRecord(reinterpret_cast<uint64_t>(command_buffer),
|
||||
reinterpret_cast<uint64_t>(address), value)
|
||||
return AppendCommandRecord(
|
||||
reinterpret_cast<uint64_t>(command_buffer),
|
||||
CommandBufferState::WriteAddressCommand {reinterpret_cast<uint64_t>(address), value},
|
||||
0x20)
|
||||
? OK
|
||||
: LibKernel::KERNEL_ERROR_EBUSY;
|
||||
}
|
||||
@@ -2213,8 +2207,8 @@ static int KYTY_SYSV_ABI AmmCommandBufferMap(void* command_buffer, uint64_t va,
|
||||
command.type = type;
|
||||
command.prot = prot;
|
||||
|
||||
return AppendAmmMapRecord(reinterpret_cast<uint64_t>(command_buffer), command,
|
||||
AMM_MAP_RECORD_SIZE)
|
||||
return AppendCommandRecord(reinterpret_cast<uint64_t>(command_buffer), command,
|
||||
AMM_MAP_RECORD_SIZE)
|
||||
? OK
|
||||
: LibKernel::KERNEL_ERROR_EBUSY;
|
||||
}
|
||||
@@ -2236,8 +2230,8 @@ static int KYTY_SYSV_ABI AmmCommandBufferMapWithGpuMaskId(void* command_buffer,
|
||||
command.prot = prot;
|
||||
command.gpu_mask_id = gpu_mask_id;
|
||||
|
||||
return AppendAmmMapRecord(reinterpret_cast<uint64_t>(command_buffer), command,
|
||||
AMM_MAP_RECORD_SIZE)
|
||||
return AppendCommandRecord(reinterpret_cast<uint64_t>(command_buffer), command,
|
||||
AMM_MAP_RECORD_SIZE)
|
||||
? OK
|
||||
: LibKernel::KERNEL_ERROR_EBUSY;
|
||||
}
|
||||
@@ -2260,8 +2254,8 @@ static int KYTY_SYSV_ABI AmmCommandBufferMapDirect(void* command_buffer, uint64_
|
||||
command.type = type;
|
||||
command.prot = prot;
|
||||
|
||||
return AppendAmmMapRecord(reinterpret_cast<uint64_t>(command_buffer), command,
|
||||
AMM_MAP_DIRECT_RECORD_SIZE)
|
||||
return AppendCommandRecord(reinterpret_cast<uint64_t>(command_buffer), command,
|
||||
AMM_MAP_DIRECT_RECORD_SIZE)
|
||||
? OK
|
||||
: LibKernel::KERNEL_ERROR_EBUSY;
|
||||
}
|
||||
@@ -2286,8 +2280,8 @@ static int KYTY_SYSV_ABI AmmCommandBufferMapDirectWithGpuMaskId(void* command_bu
|
||||
command.prot = prot;
|
||||
command.gpu_mask_id = gpu_mask_id;
|
||||
|
||||
return AppendAmmMapRecord(reinterpret_cast<uint64_t>(command_buffer), command,
|
||||
AMM_MAP_DIRECT_RECORD_SIZE)
|
||||
return AppendCommandRecord(reinterpret_cast<uint64_t>(command_buffer), command,
|
||||
AMM_MAP_DIRECT_RECORD_SIZE)
|
||||
? OK
|
||||
: LibKernel::KERNEL_ERROR_EBUSY;
|
||||
}
|
||||
@@ -2304,8 +2298,8 @@ static int KYTY_SYSV_ABI AmmCommandBufferUnmap(void* command_buffer, uint64_t va
|
||||
command.va = va;
|
||||
command.size = size;
|
||||
|
||||
return AppendAmmMapRecord(reinterpret_cast<uint64_t>(command_buffer), command,
|
||||
AMM_UNMAP_RECORD_SIZE)
|
||||
return AppendCommandRecord(reinterpret_cast<uint64_t>(command_buffer), command,
|
||||
AMM_UNMAP_RECORD_SIZE)
|
||||
? OK
|
||||
: LibKernel::KERNEL_ERROR_EBUSY;
|
||||
}
|
||||
@@ -2369,90 +2363,36 @@ static int KYTY_SYSV_ABI AmmSetPageTablePoolOccupancyNotificationThreshold(uint3
|
||||
return OK;
|
||||
}
|
||||
|
||||
static int KYTY_SYSV_ABI AmmSubmitCommandBuffer(void* command_buffer_base, uint32_t, uint32_t) {
|
||||
static int KYTY_SYSV_ABI AmmSubmitCommandBuffer(void* command_buffer_base, uint32_t bytes,
|
||||
uint32_t priority) {
|
||||
PRINT_NAME();
|
||||
|
||||
if (command_buffer_base == nullptr) {
|
||||
return LibKernel::KERNEL_ERROR_EINVAL;
|
||||
}
|
||||
|
||||
int32_t execution_result = OK;
|
||||
uint32_t error_offset = 0;
|
||||
return ExecuteAprCommandBuffer(reinterpret_cast<uint64_t>(command_buffer_base),
|
||||
&execution_result, &error_offset);
|
||||
return QueueCommandBuffer(Engine::Amm, reinterpret_cast<uint64_t>(command_buffer_base), bytes,
|
||||
priority, nullptr, nullptr);
|
||||
}
|
||||
|
||||
static int KYTY_SYSV_ABI AmmSubmitCommandBufferAndGetId(void* command_buffer_base, uint32_t,
|
||||
uint32_t, uint32_t* out_submission_id) {
|
||||
static int KYTY_SYSV_ABI AmmSubmitCommandBufferAndGetId(void* command_buffer_base, uint32_t bytes,
|
||||
uint32_t priority,
|
||||
uint32_t* out_submission_id) {
|
||||
PRINT_NAME();
|
||||
|
||||
if (command_buffer_base == nullptr || out_submission_id == nullptr) {
|
||||
if (out_submission_id == nullptr) {
|
||||
return LibKernel::KERNEL_ERROR_EINVAL;
|
||||
}
|
||||
|
||||
const auto command_buffer_addr = reinterpret_cast<uint64_t>(command_buffer_base);
|
||||
const auto id = AprShared::AllocateSubmissionId(command_buffer_addr, 0);
|
||||
|
||||
int32_t execution_result = OK;
|
||||
uint32_t error_offset = 0;
|
||||
auto submit_result =
|
||||
ExecuteAprCommandBuffer(command_buffer_addr, &execution_result, &error_offset);
|
||||
if (submit_result != OK) {
|
||||
return submit_result;
|
||||
}
|
||||
AprShared::SetSubmissionResult(id, execution_result, error_offset);
|
||||
|
||||
if (!AprShared::WriteGuest(reinterpret_cast<uint64_t>(out_submission_id), id)) {
|
||||
return LibKernel::KERNEL_ERROR_EFAULT;
|
||||
}
|
||||
|
||||
return OK;
|
||||
return QueueCommandBuffer(Engine::Amm, reinterpret_cast<uint64_t>(command_buffer_base), bytes,
|
||||
priority, nullptr, out_submission_id);
|
||||
}
|
||||
|
||||
static int KYTY_SYSV_ABI AmmSubmitCommandBufferAndGetResult(void* command_buffer_base, uint32_t,
|
||||
uint32_t, void* result,
|
||||
static int KYTY_SYSV_ABI AmmSubmitCommandBufferAndGetResult(void* command_buffer_base,
|
||||
uint32_t bytes, uint32_t priority,
|
||||
void* result,
|
||||
uint32_t* out_submission_id) {
|
||||
PRINT_NAME();
|
||||
|
||||
if (command_buffer_base == nullptr) {
|
||||
return LibKernel::KERNEL_ERROR_EINVAL;
|
||||
}
|
||||
|
||||
const auto command_buffer_addr = reinterpret_cast<uint64_t>(command_buffer_base);
|
||||
const auto id =
|
||||
AprShared::AllocateSubmissionId(command_buffer_addr, reinterpret_cast<uint64_t>(result));
|
||||
|
||||
int32_t execution_result = OK;
|
||||
uint32_t error_offset = 0;
|
||||
auto submit_result =
|
||||
ExecuteAprCommandBuffer(command_buffer_addr, &execution_result, &error_offset);
|
||||
if (submit_result != OK) {
|
||||
return submit_result;
|
||||
}
|
||||
AprShared::SetSubmissionResult(id, execution_result, error_offset);
|
||||
|
||||
if (out_submission_id != nullptr &&
|
||||
!AprShared::WriteGuest(reinterpret_cast<uint64_t>(out_submission_id), id)) {
|
||||
return LibKernel::KERNEL_ERROR_EFAULT;
|
||||
}
|
||||
|
||||
return AprShared::WriteResult(result, execution_result, error_offset);
|
||||
return QueueCommandBuffer(Engine::Amm, reinterpret_cast<uint64_t>(command_buffer_base), bytes,
|
||||
priority, result, out_submission_id);
|
||||
}
|
||||
|
||||
static int KYTY_SYSV_ABI AmmWaitCommandBufferCompletion(uint32_t submission_id) {
|
||||
PRINT_NAME();
|
||||
|
||||
AprShared::SubmissionState state {};
|
||||
if (!AprShared::CompleteSubmission(submission_id, &state)) {
|
||||
return LibKernel::KERNEL_ERROR_ESRCH;
|
||||
}
|
||||
|
||||
if (state.result != 0) {
|
||||
return AprShared::WriteResult(reinterpret_cast<void*>(state.result), state.execution_result,
|
||||
state.error_offset);
|
||||
}
|
||||
|
||||
return OK;
|
||||
return AprShared::CompleteSubmission(submission_id) ? OK : LibKernel::KERNEL_ERROR_ESRCH;
|
||||
}
|
||||
|
||||
} // namespace Ampr
|
||||
|
||||
@@ -158,6 +158,7 @@ LIB_DEFINE(InitAudio_1_AudioOut2) {
|
||||
LIB_FUNC("bkBN+CMLwRc", AudioOut2::AudioOut2GetSystemState);
|
||||
LIB_FUNC("xywYcRB7nbQ", AudioOut2::AudioOut2UserCreate);
|
||||
LIB_FUNC("IaZXJ9M79uo", AudioOut2::AudioOut2UserDestroy);
|
||||
LIB_FUNC("iE8trxPKnAg", AudioOut2::AudioOut2UserGetSupportedAttributes);
|
||||
LIB_FUNC("G1YOKDJYX2Y", AudioOut2::AudioOut2GetSpeakerArrayMemorySize);
|
||||
LIB_FUNC("+k91hoTuoA8", AudioOut2::AudioOut2SpeakerArrayCreate);
|
||||
LIB_FUNC("erCWQR5eKiQ", AudioOut2::AudioOut2SpeakerArrayDestroy);
|
||||
|
||||
+43
-4
@@ -167,7 +167,7 @@ struct AudioOut2MasteringStatesV2 {
|
||||
|
||||
static std::atomic_uint64_t g_audioout2_next_context {1};
|
||||
static std::atomic_uint64_t g_audioout2_next_port {1};
|
||||
static std::atomic_uint64_t g_audioout2_next_user {1};
|
||||
static AudioOut2UserHandle g_audioout2_next_user = 1;
|
||||
|
||||
struct AudioOut2ContextState {
|
||||
bool used = false;
|
||||
@@ -182,6 +182,7 @@ struct AudioOut2PortStateEntry {
|
||||
bool used = false;
|
||||
AudioOut2PortHandle handle = 0;
|
||||
AudioOut2ContextHandle context = 0;
|
||||
AudioOut2UserHandle user = 0;
|
||||
uint16_t port_type = 0;
|
||||
uint32_t data_format = 0;
|
||||
uint32_t sampling_freq = 48000;
|
||||
@@ -210,6 +211,7 @@ static Common::Mutex g_audioout2_context_mutex;
|
||||
static std::array<AudioOut2ContextState, 16> g_audioout2_contexts;
|
||||
static Common::Mutex g_audioout2_port_mutex;
|
||||
static std::array<AudioOut2PortStateEntry, 256> g_audioout2_ports;
|
||||
static std::vector<AudioOut2UserHandle> g_audioout2_users;
|
||||
static Common::Mutex g_audioout2_speaker_array_mutex;
|
||||
static std::array<AudioOut2SpeakerArrayState, 32> g_audioout2_speaker_arrays;
|
||||
static Common::Mutex g_audioout2_latency_mutex;
|
||||
@@ -218,6 +220,7 @@ static std::array<AudioOut2LatencyState, 16> g_audioout2_latencies;
|
||||
static constexpr int AUDIO_OUT2_ERROR_NOT_READY = -2144960504; /* 0x80268008 */
|
||||
static constexpr int AUDIO_OUT2_ERROR_PORT_FULL = -2144960494; /* 0x80268012 */
|
||||
static constexpr int AUDIO_OUT2_ERROR_INVALID_PARAM = -2144960511; /* 0x80268001 */
|
||||
static constexpr int AUDIO_OUT2_ERROR_BUSY = -2144960505; /* 0x80268007 */
|
||||
static constexpr int AUDIO_OUT2_ERROR_MASTERING_INVALID_API_PARAM = -2144959999; /* 0x80268201 */
|
||||
static constexpr int AUDIO_OUT2_ERROR_MASTERING_INVALID_STATES_ID = -2144959996; /* 0x80268204 */
|
||||
static constexpr uint32_t AUDIO_OUT2_PORT_ATTRIBUTE_ID_PCM = 0;
|
||||
@@ -589,6 +592,12 @@ int KYTY_SYSV_ABI AudioOut2PortCreate(AudioOut2ContextHandle ctx, const AudioOut
|
||||
const auto samples_num = context_state->num_grains == 0 ? 512u : context_state->num_grains;
|
||||
|
||||
g_audioout2_port_mutex.Lock();
|
||||
if (std::find(g_audioout2_users.begin(), g_audioout2_users.end(), params->user_handle) ==
|
||||
g_audioout2_users.end()) {
|
||||
g_audioout2_port_mutex.Unlock();
|
||||
g_audioout2_context_mutex.Unlock();
|
||||
return AUDIO_OUT2_ERROR_INVALID_PARAM;
|
||||
}
|
||||
AudioOut2PortStateEntry* port_state = nullptr;
|
||||
for (auto& candidate: g_audioout2_ports) {
|
||||
if (!candidate.used) {
|
||||
@@ -601,6 +610,7 @@ int KYTY_SYSV_ABI AudioOut2PortCreate(AudioOut2ContextHandle ctx, const AudioOut
|
||||
port_state->used = true;
|
||||
port_state->handle = next_port;
|
||||
port_state->context = ctx;
|
||||
port_state->user = params->user_handle;
|
||||
port_state->port_type = params->port_type;
|
||||
port_state->data_format = params->data_format;
|
||||
port_state->sampling_freq = params->sampling_freq;
|
||||
@@ -730,9 +740,12 @@ int KYTY_SYSV_ABI AudioOut2GetSystemState(AudioOut2SystemState* state) {
|
||||
|
||||
int KYTY_SYSV_ABI AudioOut2UserCreate(uint32_t user_id, AudioOut2UserHandle* handle) {
|
||||
PRINT_NAME();
|
||||
EXIT_NOT_IMPLEMENTED(handle == nullptr);
|
||||
*handle = static_cast<AudioOut2UserHandle>(
|
||||
g_audioout2_next_user.fetch_add(1, std::memory_order_relaxed));
|
||||
if (handle == nullptr) {
|
||||
return AUDIO_OUT2_ERROR_INVALID_PARAM;
|
||||
}
|
||||
Common::LockGuard lock(g_audioout2_port_mutex);
|
||||
*handle = g_audioout2_next_user++;
|
||||
g_audioout2_users.push_back(*handle);
|
||||
LOGF("\t user_id = %" PRIu32 ", handle = 0x%016" PRIx64 "\n", user_id,
|
||||
static_cast<uint64_t>(*handle));
|
||||
return OK;
|
||||
@@ -741,6 +754,32 @@ int KYTY_SYSV_ABI AudioOut2UserCreate(uint32_t user_id, AudioOut2UserHandle* han
|
||||
int KYTY_SYSV_ABI AudioOut2UserDestroy(AudioOut2UserHandle handle) {
|
||||
PRINT_NAME();
|
||||
LOGF("\t handle = 0x%016" PRIx64 "\n", static_cast<uint64_t>(handle));
|
||||
Common::LockGuard lock(g_audioout2_port_mutex);
|
||||
const auto it = std::find(g_audioout2_users.begin(), g_audioout2_users.end(), handle);
|
||||
if (it == g_audioout2_users.end()) {
|
||||
return AUDIO_OUT2_ERROR_INVALID_PARAM;
|
||||
}
|
||||
if (std::any_of(g_audioout2_ports.begin(), g_audioout2_ports.end(),
|
||||
[handle](const auto& port) { return port.used && port.user == handle; })) {
|
||||
return AUDIO_OUT2_ERROR_BUSY;
|
||||
}
|
||||
g_audioout2_users.erase(it);
|
||||
return OK;
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI AudioOut2UserGetSupportedAttributes(AudioOut2UserHandle handle,
|
||||
uint32_t* context_attributes,
|
||||
uint32_t* port_attributes) {
|
||||
if (context_attributes == nullptr || port_attributes == nullptr) {
|
||||
return AUDIO_OUT2_ERROR_INVALID_PARAM;
|
||||
}
|
||||
Common::LockGuard lock(g_audioout2_port_mutex);
|
||||
if (std::find(g_audioout2_users.begin(), g_audioout2_users.end(), handle) ==
|
||||
g_audioout2_users.end()) {
|
||||
return AUDIO_OUT2_ERROR_INVALID_PARAM;
|
||||
}
|
||||
*context_attributes = 0;
|
||||
*port_attributes = 1u << AUDIO_OUT2_PORT_ATTRIBUTE_ID_PCM;
|
||||
return OK;
|
||||
}
|
||||
|
||||
|
||||
+68
-2
@@ -7,6 +7,16 @@
|
||||
#include <array>
|
||||
#include <cstring>
|
||||
|
||||
#if defined(_WIN32)
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#endif
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
namespace Libs {
|
||||
|
||||
namespace LibCes {
|
||||
@@ -213,6 +223,54 @@ static int KYTY_SYSV_ABI CesMbcsUcsContextInit(CesContext* context, const CesPro
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if defined(_WIN32)
|
||||
|
||||
static size_t ConvertCp932ToUtf8(const char** inbuf, size_t* inbytesleft, char** outbuf,
|
||||
size_t* outbytesleft) {
|
||||
const auto* in = reinterpret_cast<const uint8_t*>(*inbuf);
|
||||
while (*inbytesleft != 0) {
|
||||
const uint8_t lead = in[0];
|
||||
// Win32 accepts 0x80 as U+0080; the SJis1997 profile rejects this byte.
|
||||
if (lead == 0x80) {
|
||||
*inbuf = reinterpret_cast<const char*>(in);
|
||||
return SDL_ICONV_EILSEQ;
|
||||
}
|
||||
const bool is_lead_byte = (lead >= 0x81 && lead <= 0x9f) || (lead >= 0xe0 && lead <= 0xfc);
|
||||
const size_t char_len = is_lead_byte ? 2 : 1;
|
||||
if (*inbytesleft < char_len) {
|
||||
*inbuf = reinterpret_cast<const char*>(in);
|
||||
return SDL_ICONV_EINVAL;
|
||||
}
|
||||
|
||||
wchar_t wide[2];
|
||||
const int wide_len =
|
||||
MultiByteToWideChar(932, MB_ERR_INVALID_CHARS, reinterpret_cast<const char*>(in),
|
||||
static_cast<int>(char_len), wide, 2);
|
||||
if (wide_len <= 0) {
|
||||
*inbuf = reinterpret_cast<const char*>(in);
|
||||
return SDL_ICONV_EILSEQ;
|
||||
}
|
||||
|
||||
char utf8[8];
|
||||
const int utf8_len =
|
||||
WideCharToMultiByte(CP_UTF8, 0, wide, wide_len, utf8, sizeof(utf8), nullptr, nullptr);
|
||||
if (utf8_len <= 0 || static_cast<size_t>(utf8_len) > *outbytesleft) {
|
||||
*inbuf = reinterpret_cast<const char*>(in);
|
||||
return SDL_ICONV_E2BIG;
|
||||
}
|
||||
|
||||
std::memcpy(*outbuf, utf8, static_cast<size_t>(utf8_len));
|
||||
*outbuf += utf8_len;
|
||||
*outbytesleft -= static_cast<size_t>(utf8_len);
|
||||
in += char_len;
|
||||
*inbytesleft -= char_len;
|
||||
}
|
||||
*inbuf = reinterpret_cast<const char*>(in);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
static int ConvertMbcsToUtf8(CesContext* context, const uint8_t* source, uint32_t source_max,
|
||||
uint32_t* source_len, uint8_t* destination, uint32_t destination_max,
|
||||
uint32_t* destination_len, bool measure) {
|
||||
@@ -246,8 +304,10 @@ static int ConvertMbcsToUtf8(CesContext* context, const uint8_t* source, uint32_
|
||||
++source_size;
|
||||
}
|
||||
const bool bounded_end = source_max != 0 && source_size == source_max;
|
||||
const auto converter = SDL_iconv_open("UTF-8", context->profile->encoding);
|
||||
#if !defined(_WIN32)
|
||||
const auto converter = SDL_iconv_open("UTF-8", context->profile->encoding);
|
||||
EXIT_IF(converter == reinterpret_cast<SDL_iconv_t>(-1));
|
||||
#endif
|
||||
|
||||
const char* input = reinterpret_cast<const char*>(source);
|
||||
size_t input_left = source_size;
|
||||
@@ -258,7 +318,11 @@ static int ConvertMbcsToUtf8(CesContext* context, const uint8_t* source, uint32_
|
||||
char* output = measure ? scratch.data() : reinterpret_cast<char*>(destination) + produced;
|
||||
const size_t capacity = measure ? scratch.size() : destination_max - 1 - produced;
|
||||
size_t output_left = capacity;
|
||||
const auto status = SDL_iconv(converter, &input, &input_left, &output, &output_left);
|
||||
#if defined(_WIN32)
|
||||
const auto status = ConvertCp932ToUtf8(&input, &input_left, &output, &output_left);
|
||||
#else
|
||||
const auto status = SDL_iconv(converter, &input, &input_left, &output, &output_left);
|
||||
#endif
|
||||
produced += capacity - output_left;
|
||||
if (status == SDL_ICONV_E2BIG) {
|
||||
if (measure) {
|
||||
@@ -277,7 +341,9 @@ static int ConvertMbcsToUtf8(CesContext* context, const uint8_t* source, uint32_
|
||||
}
|
||||
break;
|
||||
} while (input_left != 0);
|
||||
#if !defined(_WIN32)
|
||||
SDL_iconv_close(converter);
|
||||
#endif
|
||||
|
||||
if (!measure) {
|
||||
destination[produced] = 0;
|
||||
|
||||
@@ -3834,6 +3834,27 @@ namespace LibSharePlay {
|
||||
|
||||
LIB_VERSION("SharePlay", 1, "SharePlay", 1, 1);
|
||||
|
||||
constexpr int SHARE_PLAY_ERROR_INVALID_ARGS = -2129788927;
|
||||
constexpr int SHARE_PLAY_ERROR_ALREADY_INITIALIZED = -2129788925;
|
||||
constexpr int SHARE_PLAY_ERROR_NOT_INITIALIZED = -2129788924;
|
||||
|
||||
struct SharePlayConnectionInfoA {
|
||||
int32_t status;
|
||||
int32_t mode;
|
||||
char host_online_id[20];
|
||||
char visitor_online_id[20];
|
||||
uint64_t host_account_id;
|
||||
uint64_t visitor_account_id;
|
||||
int32_t host_user_id;
|
||||
int32_t visitor_user_id;
|
||||
};
|
||||
|
||||
static_assert(sizeof(SharePlayConnectionInfoA) == 72);
|
||||
static_assert(offsetof(SharePlayConnectionInfoA, host_account_id) == 48);
|
||||
static_assert(offsetof(SharePlayConnectionInfoA, host_user_id) == 64);
|
||||
|
||||
static bool g_share_play_initialized = false;
|
||||
|
||||
static int KYTY_SYSV_ABI SharePlayInitialize(void* heap, size_t heap_size) {
|
||||
PRINT_NAME();
|
||||
|
||||
@@ -3841,18 +3862,41 @@ static int KYTY_SYSV_ABI SharePlayInitialize(void* heap, size_t heap_size) {
|
||||
"\t heap_size = %" PRIu64 "\n",
|
||||
reinterpret_cast<uint64_t>(heap), static_cast<uint64_t>(heap_size));
|
||||
|
||||
if (g_share_play_initialized) {
|
||||
return SHARE_PLAY_ERROR_ALREADY_INITIALIZED;
|
||||
}
|
||||
if (heap != nullptr && heap_size < 6u * 1024u) {
|
||||
return SHARE_PLAY_ERROR_INVALID_ARGS;
|
||||
}
|
||||
g_share_play_initialized = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int KYTY_SYSV_ABI SharePlayTerminate() {
|
||||
PRINT_NAME();
|
||||
|
||||
if (!g_share_play_initialized) {
|
||||
return SHARE_PLAY_ERROR_NOT_INITIALIZED;
|
||||
}
|
||||
g_share_play_initialized = false;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int KYTY_SYSV_ABI SharePlayGetCurrentConnectionInfoA(SharePlayConnectionInfoA* info) {
|
||||
if (!g_share_play_initialized) {
|
||||
return SHARE_PLAY_ERROR_NOT_INITIALIZED;
|
||||
}
|
||||
if (info == nullptr) {
|
||||
return SHARE_PLAY_ERROR_INVALID_ARGS;
|
||||
}
|
||||
*info = {.status = 0, .host_user_id = -1, .visitor_user_id = -1};
|
||||
return 0;
|
||||
}
|
||||
|
||||
LIB_DEFINE(InitPlatform_1_SharePlay) {
|
||||
LIB_FUNC("isruqthpYcw", LibSharePlay::SharePlayInitialize);
|
||||
LIB_FUNC("UaLjloJinow", LibSharePlay::SharePlayTerminate);
|
||||
LIB_FUNC("+MCXJlWdi+s", LibSharePlay::SharePlayGetCurrentConnectionInfoA);
|
||||
}
|
||||
|
||||
} // namespace LibSharePlay
|
||||
|
||||
@@ -0,0 +1,349 @@
|
||||
#include "common/abi.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "libs/libs.h"
|
||||
#include "loader/symbolDatabase.h"
|
||||
|
||||
#include <bit>
|
||||
#include <cstddef>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <new>
|
||||
|
||||
#define STB_IMAGE_WRITE_IMPLEMENTATION
|
||||
#define STB_IMAGE_WRITE_STATIC
|
||||
#define STBIWDEF static inline
|
||||
#define STBI_WRITE_NO_STDIO
|
||||
#include "stb_image_write.h"
|
||||
|
||||
namespace Libs {
|
||||
|
||||
LIB_VERSION("PngEnc", 1, "PngEnc", 1, 1);
|
||||
|
||||
namespace PngEnc {
|
||||
|
||||
constexpr int32_t PNG_ENC_ERROR_INVALID_ADDR = -2140602111; // 0x80690101
|
||||
constexpr int32_t PNG_ENC_ERROR_INVALID_SIZE = -2140602110; // 0x80690102
|
||||
constexpr int32_t PNG_ENC_ERROR_INVALID_PARAM = -2140602109; // 0x80690103
|
||||
constexpr int32_t PNG_ENC_ERROR_INVALID_HANDLE = -2140602108; // 0x80690104
|
||||
constexpr int32_t PNG_ENC_ERROR_DATA_OVERFLOW = -2140602096; // 0x80690110
|
||||
constexpr int32_t PNG_ENC_ERROR_FATAL = -2140602080; // 0x80690120
|
||||
|
||||
constexpr uint32_t PNG_ENC_MAX_IMAGE_WIDTH = 1000000;
|
||||
constexpr uint32_t PNG_ENC_MAX_IMAGE_HEIGHT = 1000000;
|
||||
constexpr uint32_t PNG_ENC_MAX_FILTER_NUMBER = 4;
|
||||
constexpr uint16_t PNG_ENC_COLOR_SPACE_RGB = 3;
|
||||
constexpr uint16_t PNG_ENC_COLOR_SPACE_RGBA = 19;
|
||||
constexpr uint16_t PNG_ENC_PIXEL_FORMAT_R8G8B8A8 = 0;
|
||||
constexpr uint16_t PNG_ENC_PIXEL_FORMAT_B8G8R8A8 = 1;
|
||||
constexpr uint16_t PNG_ENC_FILTER_TYPE_SUB = 1;
|
||||
constexpr uint16_t PNG_ENC_FILTER_TYPE_UP = 2;
|
||||
constexpr uint16_t PNG_ENC_FILTER_TYPE_AVERAGE = 4;
|
||||
constexpr uint16_t PNG_ENC_FILTER_TYPE_PAETH = 8;
|
||||
|
||||
struct PngEncCreateParam {
|
||||
uint32_t this_size;
|
||||
uint32_t attribute;
|
||||
uint32_t max_image_width;
|
||||
uint32_t max_filter_number;
|
||||
};
|
||||
|
||||
struct PngEncEncodeParam {
|
||||
const uint8_t* image_mem_addr;
|
||||
uint8_t* png_mem_addr;
|
||||
uint32_t image_mem_size;
|
||||
uint32_t png_mem_size;
|
||||
uint32_t image_width;
|
||||
uint32_t image_height;
|
||||
uint32_t image_pitch;
|
||||
uint16_t pixel_format;
|
||||
uint16_t color_space;
|
||||
uint16_t bit_depth;
|
||||
uint16_t clut_number;
|
||||
uint16_t filter_type;
|
||||
uint16_t compression_level;
|
||||
};
|
||||
|
||||
struct PngEncOutputInfo {
|
||||
uint32_t data_size;
|
||||
uint32_t processed_height;
|
||||
};
|
||||
|
||||
static_assert(sizeof(PngEncCreateParam) == 16);
|
||||
static_assert(sizeof(PngEncEncodeParam) == 48);
|
||||
|
||||
struct PngEncContext {
|
||||
uint64_t magic;
|
||||
uint32_t max_image_width;
|
||||
uint32_t max_filter_number;
|
||||
};
|
||||
|
||||
constexpr uint64_t PNG_ENC_CONTEXT_MAGIC = 0x4b595459504e4745ull; // KYTYPNGE
|
||||
|
||||
struct PngEncWriter {
|
||||
uint8_t* dst;
|
||||
uint32_t capacity;
|
||||
uint32_t size;
|
||||
bool overflow;
|
||||
};
|
||||
|
||||
// stb_image_write keeps its compression level and filter choice in globals
|
||||
static std::mutex g_png_enc_mutex;
|
||||
|
||||
static void PngEncWrite(void* context, void* data, int size) {
|
||||
auto* writer = static_cast<PngEncWriter*>(context);
|
||||
|
||||
if (writer->overflow || size < 0 ||
|
||||
static_cast<uint64_t>(writer->size) + static_cast<uint32_t>(size) > writer->capacity) {
|
||||
writer->overflow = true;
|
||||
return;
|
||||
}
|
||||
|
||||
std::memcpy(writer->dst + writer->size, data, static_cast<size_t>(size));
|
||||
writer->size += static_cast<uint32_t>(size);
|
||||
}
|
||||
|
||||
static int PngEncForcedFilter(uint16_t filter_type) {
|
||||
if (filter_type == (PNG_ENC_FILTER_TYPE_SUB | PNG_ENC_FILTER_TYPE_UP |
|
||||
PNG_ENC_FILTER_TYPE_AVERAGE | PNG_ENC_FILTER_TYPE_PAETH)) {
|
||||
return -1;
|
||||
}
|
||||
// stb's adaptive mode cannot restrict itself to the guest's filter mask.
|
||||
if ((filter_type & PNG_ENC_FILTER_TYPE_SUB) != 0) {
|
||||
return 1;
|
||||
}
|
||||
if ((filter_type & PNG_ENC_FILTER_TYPE_UP) != 0) {
|
||||
return 2;
|
||||
}
|
||||
if ((filter_type & PNG_ENC_FILTER_TYPE_AVERAGE) != 0) {
|
||||
return 3;
|
||||
}
|
||||
if ((filter_type & PNG_ENC_FILTER_TYPE_PAETH) != 0) {
|
||||
return 4;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int32_t ValidateCreateParam(const PngEncCreateParam* param) {
|
||||
if (param == nullptr) {
|
||||
return PNG_ENC_ERROR_INVALID_ADDR;
|
||||
}
|
||||
|
||||
LOGF("\t this_size = %" PRIu32 "\n", param->this_size);
|
||||
LOGF("\t attribute = %" PRIu32 "\n", param->attribute);
|
||||
LOGF("\t max_image_width = %" PRIu32 "\n", param->max_image_width);
|
||||
LOGF("\t max_filter_number = %" PRIu32 "\n", param->max_filter_number);
|
||||
|
||||
if (param->this_size != sizeof(PngEncCreateParam) || param->attribute != 0 ||
|
||||
param->max_filter_number > PNG_ENC_MAX_FILTER_NUMBER) {
|
||||
return PNG_ENC_ERROR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
if (param->max_image_width == 0 || param->max_image_width > PNG_ENC_MAX_IMAGE_WIDTH) {
|
||||
return PNG_ENC_ERROR_INVALID_SIZE;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int32_t KYTY_SYSV_ABI PngEncQueryMemorySize(const PngEncCreateParam* param) {
|
||||
PRINT_NAME();
|
||||
|
||||
if (int32_t result = ValidateCreateParam(param); result != 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
return sizeof(PngEncContext);
|
||||
}
|
||||
|
||||
static int32_t KYTY_SYSV_ABI PngEncCreate(const PngEncCreateParam* param, void* memory_address,
|
||||
uint32_t memory_size, void** handle) {
|
||||
PRINT_NAME();
|
||||
|
||||
LOGF("\t memory_address = %p\n", memory_address);
|
||||
LOGF("\t memory_size = %" PRIu32 "\n", memory_size);
|
||||
|
||||
if (int32_t result = ValidateCreateParam(param); result != 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
if (memory_address == nullptr || handle == nullptr) {
|
||||
return PNG_ENC_ERROR_INVALID_ADDR;
|
||||
}
|
||||
|
||||
if (memory_size < sizeof(PngEncContext)) {
|
||||
return PNG_ENC_ERROR_INVALID_SIZE;
|
||||
}
|
||||
|
||||
auto* ctx = static_cast<PngEncContext*>(memory_address);
|
||||
ctx->magic = PNG_ENC_CONTEXT_MAGIC;
|
||||
ctx->max_image_width = param->max_image_width;
|
||||
ctx->max_filter_number = param->max_filter_number;
|
||||
*handle = ctx;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int32_t KYTY_SYSV_ABI PngEncEncode(void* handle, const PngEncEncodeParam* param,
|
||||
PngEncOutputInfo* output_info) {
|
||||
PRINT_NAME();
|
||||
|
||||
if (handle == nullptr) {
|
||||
return PNG_ENC_ERROR_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
const auto* ctx = static_cast<const PngEncContext*>(handle);
|
||||
if (ctx->magic != PNG_ENC_CONTEXT_MAGIC) {
|
||||
return PNG_ENC_ERROR_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
if (param == nullptr) {
|
||||
return PNG_ENC_ERROR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
LOGF("\t image_mem_addr = %p\n", static_cast<const void*>(param->image_mem_addr));
|
||||
LOGF("\t png_mem_addr = %p\n", static_cast<const void*>(param->png_mem_addr));
|
||||
LOGF("\t image_mem_size = %" PRIu32 "\n", param->image_mem_size);
|
||||
LOGF("\t png_mem_size = %" PRIu32 "\n", param->png_mem_size);
|
||||
LOGF("\t image_width = %" PRIu32 "\n", param->image_width);
|
||||
LOGF("\t image_height = %" PRIu32 "\n", param->image_height);
|
||||
LOGF("\t image_pitch = %" PRIu32 "\n", param->image_pitch);
|
||||
LOGF("\t pixel_format = %" PRIu16 "\n", param->pixel_format);
|
||||
LOGF("\t color_space = %" PRIu16 "\n", param->color_space);
|
||||
LOGF("\t bit_depth = %" PRIu16 "\n", param->bit_depth);
|
||||
LOGF("\t filter_type = %" PRIu16 "\n", param->filter_type);
|
||||
LOGF("\t compression_level = %" PRIu16 "\n", param->compression_level);
|
||||
|
||||
if (param->image_mem_addr == nullptr || param->png_mem_addr == nullptr ||
|
||||
(reinterpret_cast<uintptr_t>(param->image_mem_addr) & 3u) != 0) {
|
||||
return PNG_ENC_ERROR_INVALID_ADDR;
|
||||
}
|
||||
|
||||
if (param->pixel_format != PNG_ENC_PIXEL_FORMAT_R8G8B8A8 &&
|
||||
param->pixel_format != PNG_ENC_PIXEL_FORMAT_B8G8R8A8) {
|
||||
return PNG_ENC_ERROR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
if (param->color_space != PNG_ENC_COLOR_SPACE_RGB &&
|
||||
param->color_space != PNG_ENC_COLOR_SPACE_RGBA) {
|
||||
return PNG_ENC_ERROR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
if (param->bit_depth != 8 || param->clut_number != 0 || param->compression_level > 9) {
|
||||
return PNG_ENC_ERROR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
constexpr uint16_t filter_mask = PNG_ENC_FILTER_TYPE_SUB | PNG_ENC_FILTER_TYPE_UP |
|
||||
PNG_ENC_FILTER_TYPE_AVERAGE | PNG_ENC_FILTER_TYPE_PAETH;
|
||||
if ((param->filter_type & ~filter_mask) != 0 ||
|
||||
static_cast<uint32_t>(std::popcount(param->filter_type)) > ctx->max_filter_number) {
|
||||
return PNG_ENC_ERROR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
const uint32_t width = param->image_width;
|
||||
const uint32_t height = param->image_height;
|
||||
if (width == 0 || height == 0 || width > ctx->max_image_width ||
|
||||
width > PNG_ENC_MAX_IMAGE_WIDTH || height > PNG_ENC_MAX_IMAGE_HEIGHT) {
|
||||
return PNG_ENC_ERROR_INVALID_SIZE;
|
||||
}
|
||||
|
||||
const int components = (param->color_space == PNG_ENC_COLOR_SPACE_RGBA ? 4 : 3);
|
||||
const uint32_t row_size = width * static_cast<uint32_t>(components);
|
||||
const uint32_t min_pitch = width * 4u;
|
||||
const uint32_t pitch = param->image_pitch;
|
||||
|
||||
// Leave room for deflate expansion and stb's signed-int buffer-capacity doubling.
|
||||
constexpr uint64_t max_filtered_size = std::numeric_limits<int>::max() / 2u;
|
||||
if (static_cast<uint64_t>(row_size + 1u) * height > max_filtered_size ||
|
||||
param->png_mem_size == 0 || pitch < min_pitch || (pitch & 3u) != 0 ||
|
||||
static_cast<uint64_t>(pitch) * height > param->image_mem_size) {
|
||||
return PNG_ENC_ERROR_INVALID_SIZE;
|
||||
}
|
||||
|
||||
const bool bgr = (param->pixel_format == PNG_ENC_PIXEL_FORMAT_B8G8R8A8);
|
||||
std::unique_ptr<uint8_t[]> pixels(new (std::nothrow)
|
||||
uint8_t[static_cast<size_t>(row_size) * height]);
|
||||
if (pixels == nullptr) {
|
||||
return PNG_ENC_ERROR_FATAL;
|
||||
}
|
||||
|
||||
for (uint32_t y = 0; y < height; y++) {
|
||||
const auto* src = param->image_mem_addr + static_cast<size_t>(y) * pitch;
|
||||
auto* dst = pixels.get() + static_cast<size_t>(y) * row_size;
|
||||
|
||||
for (uint32_t x = 0; x < width; x++) {
|
||||
dst[0] = src[bgr ? 2 : 0];
|
||||
dst[1] = src[1];
|
||||
dst[2] = src[bgr ? 0 : 2];
|
||||
if (components == 4) {
|
||||
dst[3] = src[3];
|
||||
}
|
||||
|
||||
src += 4;
|
||||
dst += components;
|
||||
}
|
||||
}
|
||||
|
||||
PngEncWriter writer {param->png_mem_addr, param->png_mem_size, 0, false};
|
||||
|
||||
int ok = 0;
|
||||
{
|
||||
std::scoped_lock lock(g_png_enc_mutex);
|
||||
|
||||
stbi_write_png_compression_level = param->compression_level;
|
||||
stbi_write_force_png_filter = PngEncForcedFilter(param->filter_type);
|
||||
|
||||
ok = stbi_write_png_to_func(PngEncWrite, &writer, static_cast<int>(width),
|
||||
static_cast<int>(height), components, pixels.get(),
|
||||
static_cast<int>(row_size));
|
||||
}
|
||||
|
||||
if (output_info != nullptr) {
|
||||
output_info->data_size = (writer.overflow ? 0 : writer.size);
|
||||
output_info->processed_height = (ok != 0 && !writer.overflow ? height : 0);
|
||||
}
|
||||
|
||||
if (ok == 0) {
|
||||
LOGF("\t encode failed\n");
|
||||
return PNG_ENC_ERROR_FATAL;
|
||||
}
|
||||
|
||||
if (writer.overflow) {
|
||||
LOGF("\t output buffer too small\n");
|
||||
return PNG_ENC_ERROR_DATA_OVERFLOW;
|
||||
}
|
||||
|
||||
LOGF("\t data_size = %" PRIu32 "\n", writer.size);
|
||||
|
||||
return static_cast<int32_t>(writer.size);
|
||||
}
|
||||
|
||||
static int32_t KYTY_SYSV_ABI PngEncDelete(void* handle) {
|
||||
PRINT_NAME();
|
||||
|
||||
if (handle == nullptr) {
|
||||
return PNG_ENC_ERROR_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
auto* ctx = static_cast<PngEncContext*>(handle);
|
||||
if (ctx->magic != PNG_ENC_CONTEXT_MAGIC) {
|
||||
return PNG_ENC_ERROR_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
ctx->magic = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace PngEnc
|
||||
|
||||
LIB_DEFINE(InitPngEnc_1) {
|
||||
PRINT_NAME_ENABLE(true);
|
||||
|
||||
LIB_FUNC("9030RnBDoh4", PngEnc::PngEncQueryMemorySize);
|
||||
LIB_FUNC("7aGTPfrqT9s", PngEnc::PngEncCreate);
|
||||
LIB_FUNC("xgDjJKpcyHo", PngEnc::PngEncEncode);
|
||||
LIB_FUNC("RUrWdwTWZy8", PngEnc::PngEncDelete);
|
||||
}
|
||||
|
||||
} // namespace Libs
|
||||
Loaded 100 of 121 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user