mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-11-07 13:39:55 +00:00
Merge branch 'main'
This commit is contained in:
commit
820ee08337
470 files changed
+26878
-3090
No files matched your search
@@ -23,6 +23,16 @@ jobs:
|
||||
script: |
|
||||
const { owner, repo } = context.repo;
|
||||
const pull_number = context.payload.pull_request.number;
|
||||
const reopened = context.payload.action === 'reopened';
|
||||
const removeLimitLabel = async () => {
|
||||
try {
|
||||
await github.rest.issues.removeLabel({ owner, repo, issue_number: pull_number, name: 'open PR limit' });
|
||||
} catch (error) {
|
||||
if (error.status !== 404) {
|
||||
core.warning(`Could not remove the open PR limit label: ${error.message}`);
|
||||
}
|
||||
}
|
||||
};
|
||||
const { data: pr } = await github.rest.pulls.get({ owner, repo, pull_number });
|
||||
if (pr.state !== 'open' || pr.base.ref !== 'main') {
|
||||
return;
|
||||
@@ -38,13 +48,7 @@ jobs:
|
||||
core.warning(`Could not check the permission of ${context.payload.sender.login}, so the open PR limit applies: ${error.message}`);
|
||||
}
|
||||
if (maintainer) {
|
||||
try {
|
||||
await github.rest.issues.removeLabel({ owner, repo, issue_number: pull_number, name: 'open PR limit' });
|
||||
} catch (error) {
|
||||
if (error.status !== 404) {
|
||||
core.warning(`Could not remove the open PR limit label: ${error.message}`);
|
||||
}
|
||||
}
|
||||
await removeLimitLabel();
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -57,18 +61,23 @@ jobs:
|
||||
}
|
||||
const merged = mergedPRs.total_count;
|
||||
if (merged >= 20) {
|
||||
if (reopened) {
|
||||
await removeLimitLabel();
|
||||
}
|
||||
return;
|
||||
}
|
||||
const allowedOpen = merged >= 1 ? 20 : 1;
|
||||
const openPRs = await github.paginate(github.rest.pulls.list, {
|
||||
owner, repo, state: 'open', base: 'main', per_page: 100
|
||||
});
|
||||
const reopened = context.payload.action === 'reopened';
|
||||
const open = new Set(openPRs
|
||||
.filter((p) => p.user.login === pr.user.login && (reopened || p.number < pull_number))
|
||||
.map((p) => p.number));
|
||||
open.add(pull_number);
|
||||
if (open.size <= allowedOpen) {
|
||||
if (reopened) {
|
||||
await removeLimitLabel();
|
||||
}
|
||||
return;
|
||||
}
|
||||
const name = 'open PR limit';
|
||||
|
||||
@@ -76,3 +76,6 @@
|
||||
[submodule "3rdparty/quickjs"]
|
||||
path = 3rdparty/quickjs
|
||||
url = https://github.com/quickjs-ng/quickjs.git
|
||||
[submodule "3rdparty/sqlite"]
|
||||
path = 3rdparty/sqlite
|
||||
url = https://github.com/sqlite/sqlite.git
|
||||
+1
Submodule 3rdparty/sqlite added at 57520227c9.
+8
-2
@@ -362,6 +362,9 @@ if(BUILD_TESTING)
|
||||
add_test(NAME check_skips_ctest COMMAND ${Python3_EXECUTABLE}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_check_skips_ctest.py)
|
||||
set_tests_properties(check_skips_ctest PROPERTIES TIMEOUT 30)
|
||||
add_test(NAME pr_intake_reopen COMMAND ${Python3_EXECUTABLE}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_pr_intake_workflow.py)
|
||||
set_tests_properties(pr_intake_reopen PROPERTIES TIMEOUT 60)
|
||||
add_test(NAME tools_pe_inventory COMMAND ${Python3_EXECUTABLE} ${CMAKE_SOURCE_DIR}/tools/tests/test_pe_symbol_inventory.py)
|
||||
set_tests_properties(tools_pe_inventory PROPERTIES TIMEOUT 20)
|
||||
add_test(NAME check_skips COMMAND ${Python3_EXECUTABLE}
|
||||
@@ -466,7 +469,7 @@ if(BUILD_TESTING)
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_package_release.py)
|
||||
add_test(NAME web_release COMMAND ${Python3_EXECUTABLE}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_web_release.py)
|
||||
set_tests_properties(web_release PROPERTIES TIMEOUT 20)
|
||||
set_tests_properties(web_release PROPERTIES TIMEOUT 120)
|
||||
add_test(NAME web_library_assets COMMAND ${Python3_EXECUTABLE}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_build_web.py)
|
||||
set_tests_properties(web_library_assets PROPERTIES TIMEOUT 20)
|
||||
@@ -703,7 +706,8 @@ set_target_properties(usb-1.0 PROPERTIES POSITION_INDEPENDENT_CODE ON)
|
||||
|
||||
include(cmake/ZArchive.cmake)
|
||||
include(cmake/Opus.cmake)
|
||||
if(WIN32 OR APPLE)
|
||||
include(cmake/Sqlite.cmake)
|
||||
if(WIN32 OR APPLE OR (CMAKE_SYSTEM_NAME STREQUAL "Linux" AND NOT ANYPS5_ENABLE_ASAN AND NOT ANYPS5_ENABLE_TSAN))
|
||||
set(MI_BUILD_SHARED ON CACHE BOOL "" FORCE)
|
||||
set(MI_BUILD_STATIC OFF CACHE BOOL "" FORCE)
|
||||
set(MI_BUILD_OBJECT OFF CACHE BOOL "" FORCE)
|
||||
@@ -717,6 +721,8 @@ if(WIN32 OR APPLE)
|
||||
target_link_options(mimalloc PRIVATE -static
|
||||
LINKER:--wrap=malloc,--wrap=calloc,--wrap=realloc,--wrap=free,--wrap=strdup,--wrap=_strdup)
|
||||
target_sources(mimalloc PRIVATE ${CMAKE_SOURCE_DIR}/core/libs/allocator/RuntimeHeap.c)
|
||||
elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
||||
target_link_options(mimalloc PRIVATE LINKER:--as-needed)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
|
||||
@@ -39,6 +39,8 @@ add_library(jxr STATIC EXCLUDE_FROM_ALL
|
||||
${JXR_SOURCE_DIR}/jxrgluelib/JXRGluePFC.c
|
||||
${JXR_SOURCE_DIR}/jxrgluelib/JXRMeta.c)
|
||||
configure_jxr_native(jxr)
|
||||
set_source_files_properties(${JXR_SOURCE_DIR}/image/sys/strcodec.c ${JXR_SOURCE_DIR}/image/decode/segdec.c
|
||||
PROPERTIES COMPILE_DEFINITIONS _M_IA64)
|
||||
if(UNIX)
|
||||
target_link_libraries(jxr PRIVATE m)
|
||||
endif()
|
||||
@@ -0,0 +1,68 @@
|
||||
enable_language(C)
|
||||
|
||||
set(SQLITE_SOURCE_DIR ${CMAKE_SOURCE_DIR}/3rdparty/sqlite)
|
||||
set(sqliteGen ${CMAKE_BINARY_DIR}/sqlite)
|
||||
file(MAKE_DIRECTORY ${sqliteGen})
|
||||
|
||||
foreach(tool IN ITEMS lemon mkkeywordhash mksourceid)
|
||||
add_executable(sqlite_${tool} EXCLUDE_FROM_ALL ${SQLITE_SOURCE_DIR}/tool/${tool}.c)
|
||||
set_target_properties(sqlite_${tool} PROPERTIES OUTPUT_NAME ${tool} RUNTIME_OUTPUT_DIRECTORY ${sqliteGen})
|
||||
endforeach()
|
||||
add_executable(sqlite_jimsh EXCLUDE_FROM_ALL ${SQLITE_SOURCE_DIR}/autosetup/jimsh0.c)
|
||||
if(WIN32)
|
||||
target_compile_definitions(sqlite_jimsh PRIVATE HAVE__FULLPATH)
|
||||
else()
|
||||
target_compile_definitions(sqlite_jimsh PRIVATE HAVE_REALPATH)
|
||||
endif()
|
||||
set_target_properties(sqlite_jimsh PROPERTIES OUTPUT_NAME jimsh RUNTIME_OUTPUT_DIRECTORY ${sqliteGen})
|
||||
set(sqliteTcl $<TARGET_FILE:sqlite_jimsh>)
|
||||
|
||||
add_custom_command(OUTPUT ${sqliteGen}/sqlite3.h
|
||||
COMMAND ${sqliteTcl} ${SQLITE_SOURCE_DIR}/tool/mksqlite3h.tcl ${SQLITE_SOURCE_DIR} -o ${sqliteGen}/sqlite3.h
|
||||
WORKING_DIRECTORY ${sqliteGen}
|
||||
DEPENDS sqlite_jimsh sqlite_mksourceid ${SQLITE_SOURCE_DIR}/src/sqlite.h.in ${SQLITE_SOURCE_DIR}/VERSION)
|
||||
add_custom_command(OUTPUT ${sqliteGen}/parse.c ${sqliteGen}/parse.h
|
||||
COMMAND ${CMAKE_COMMAND} -E copy ${SQLITE_SOURCE_DIR}/src/parse.y ${SQLITE_SOURCE_DIR}/tool/lempar.c ${sqliteGen}
|
||||
COMMAND $<TARGET_FILE:sqlite_lemon> -S parse.y
|
||||
WORKING_DIRECTORY ${sqliteGen}
|
||||
DEPENDS sqlite_lemon ${SQLITE_SOURCE_DIR}/src/parse.y ${SQLITE_SOURCE_DIR}/tool/lempar.c)
|
||||
add_custom_command(OUTPUT ${sqliteGen}/opcodes.h
|
||||
COMMAND ${CMAKE_COMMAND} -E cat ${sqliteGen}/parse.h ${SQLITE_SOURCE_DIR}/src/vdbe.c > ${sqliteGen}/opcodes.in
|
||||
COMMAND ${sqliteTcl} ${SQLITE_SOURCE_DIR}/tool/mkopcodeh.tcl < ${sqliteGen}/opcodes.in > ${sqliteGen}/opcodes.h
|
||||
WORKING_DIRECTORY ${sqliteGen}
|
||||
DEPENDS sqlite_jimsh ${sqliteGen}/parse.h ${SQLITE_SOURCE_DIR}/src/vdbe.c)
|
||||
add_custom_command(OUTPUT ${sqliteGen}/opcodes.c
|
||||
COMMAND ${sqliteTcl} ${SQLITE_SOURCE_DIR}/tool/mkopcodec.tcl ${sqliteGen}/opcodes.h > ${sqliteGen}/opcodes.c
|
||||
WORKING_DIRECTORY ${sqliteGen}
|
||||
DEPENDS sqlite_jimsh ${sqliteGen}/opcodes.h)
|
||||
add_custom_command(OUTPUT ${sqliteGen}/keywordhash.h
|
||||
COMMAND $<TARGET_FILE:sqlite_mkkeywordhash> > ${sqliteGen}/keywordhash.h
|
||||
WORKING_DIRECTORY ${sqliteGen}
|
||||
DEPENDS sqlite_mkkeywordhash)
|
||||
add_custom_command(OUTPUT ${sqliteGen}/pragma.h
|
||||
COMMAND ${sqliteTcl} ${SQLITE_SOURCE_DIR}/tool/mkpragmatab.tcl ${sqliteGen}/pragma.h
|
||||
WORKING_DIRECTORY ${sqliteGen}
|
||||
DEPENDS sqlite_jimsh ${SQLITE_SOURCE_DIR}/tool/mkpragmatab.tcl)
|
||||
add_custom_command(OUTPUT ${sqliteGen}/ctime.c
|
||||
COMMAND ${sqliteTcl} ${SQLITE_SOURCE_DIR}/tool/mkctimec.tcl ${sqliteGen}/ctime.c
|
||||
WORKING_DIRECTORY ${sqliteGen}
|
||||
DEPENDS sqlite_jimsh ${SQLITE_SOURCE_DIR}/tool/mkctimec.tcl)
|
||||
|
||||
set(sqliteSources)
|
||||
foreach(name IN ITEMS alter analyze attach auth backup bitvec btmutex btree build callback carray complete date dbpage
|
||||
dbstat delete expr fault fkey func global hash insert json legacy loadext main malloc mem0 mem1 mem2 mem3
|
||||
mem5 memdb memjournal mutex mutex_noop mutex_unix mutex_w32 notify os os_kv os_unix os_win pager pcache
|
||||
pcache1 pragma prepare printf random resolve rowset select status table threads tokenize treeview trigger
|
||||
utf update upsert util vacuum vdbe vdbeapi vdbeaux vdbeblob vdbemem vdbesort vdbetrace vdbevtab vtab wal
|
||||
walker where wherecode whereexpr window)
|
||||
list(APPEND sqliteSources ${SQLITE_SOURCE_DIR}/src/${name}.c)
|
||||
endforeach()
|
||||
add_library(sqlite3 STATIC EXCLUDE_FROM_ALL ${sqliteSources} ${sqliteGen}/parse.c ${sqliteGen}/opcodes.c
|
||||
${sqliteGen}/ctime.c ${sqliteGen}/sqlite3.h ${sqliteGen}/keywordhash.h ${sqliteGen}/pragma.h ${sqliteGen}/opcodes.h)
|
||||
target_include_directories(sqlite3 PRIVATE ${sqliteGen} ${SQLITE_SOURCE_DIR}/src)
|
||||
target_include_directories(sqlite3 SYSTEM INTERFACE ${sqliteGen})
|
||||
target_compile_definitions(sqlite3 PRIVATE SQLITE_THREADSAFE=1 SQLITE_OMIT_LOAD_EXTENSION SQLITE_CORE)
|
||||
set_target_properties(sqlite3 PROPERTIES C_STANDARD 99 POSITION_INDEPENDENT_CODE ON C_VISIBILITY_PRESET hidden)
|
||||
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
||||
target_link_libraries(sqlite3 PRIVATE m pthread)
|
||||
endif()
|
||||
+57
-15
@@ -152,7 +152,8 @@ add_custom_command(
|
||||
COMMENT "Patching NIDs for libc"
|
||||
)
|
||||
|
||||
file(GLOB LIBS_DIRS LIST_DIRECTORIES true ${CMAKE_CURRENT_SOURCE_DIR}/prx/*)
|
||||
file(GLOB LIBS_DIRS LIST_DIRECTORIES true CONFIGURE_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/prx/*)
|
||||
file(GLOB LIBS_CMAKE_LISTS CONFIGURE_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/prx/*/CMakeLists.txt)
|
||||
list(REMOVE_ITEM LIBS_DIRS ${LIBC_DIR})
|
||||
|
||||
set(PATCHED_OUTPUTS ${LIBC_PATCHED})
|
||||
@@ -289,13 +290,14 @@ if(TARGET mimalloc AND WIN32)
|
||||
)
|
||||
list(APPEND PATCHED_OUTPUTS ${PATCHED_DIR}/mimalloc.dll ${PATCHED_DIR}/mimalloc-redirect.dll)
|
||||
elseif(TARGET mimalloc)
|
||||
set(MIMALLOC_NAME ${CMAKE_SHARED_LIBRARY_PREFIX}mimalloc${CMAKE_SHARED_LIBRARY_SUFFIX})
|
||||
add_custom_command(
|
||||
OUTPUT ${PATCHED_DIR}/libmimalloc.dylib
|
||||
COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_FILE:mimalloc> ${PATCHED_DIR}/libmimalloc.dylib
|
||||
OUTPUT ${PATCHED_DIR}/${MIMALLOC_NAME}
|
||||
COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_FILE:mimalloc> ${PATCHED_DIR}/${MIMALLOC_NAME}
|
||||
DEPENDS mimalloc
|
||||
COMMENT "Copying libmimalloc.dylib beside the libraries"
|
||||
COMMENT "Copying ${MIMALLOC_NAME} beside the libraries"
|
||||
)
|
||||
list(APPEND PATCHED_OUTPUTS ${PATCHED_DIR}/libmimalloc.dylib)
|
||||
list(APPEND PATCHED_OUTPUTS ${PATCHED_DIR}/${MIMALLOC_NAME})
|
||||
endif()
|
||||
|
||||
include(prx/libSceFreeType/BuildInfo.cmake)
|
||||
@@ -535,6 +537,46 @@ if(BUILD_TESTING)
|
||||
configure_windows_unwind(guest_kernel_sys_barrier_tests)
|
||||
add_test(NAME guest_kernel_sys_barrier COMMAND guest_kernel_sys_barrier_tests)
|
||||
set_tests_properties(guest_kernel_sys_barrier PROPERTIES TIMEOUT 20)
|
||||
add_test_executable(guest_kernel_sys_execve_tests tests/GuestKernelSysExecve.cpp)
|
||||
target_include_directories(guest_kernel_sys_execve_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
target_link_libraries(guest_kernel_sys_execve_tests PRIVATE libkernel_sys libkernel libc)
|
||||
configure_windows_unwind(guest_kernel_sys_execve_tests)
|
||||
add_test(NAME guest_kernel_sys_execve COMMAND guest_kernel_sys_execve_tests)
|
||||
set_tests_properties(guest_kernel_sys_execve PROPERTIES TIMEOUT 20)
|
||||
if(WIN32)
|
||||
set_tests_properties(guest_kernel_sys_execve PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libkernel_sys>")
|
||||
endif()
|
||||
add_test_executable(guest_kernel_sys_execvpe_tests tests/GuestKernelSysExecvpe.cpp)
|
||||
target_include_directories(guest_kernel_sys_execvpe_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
target_link_libraries(guest_kernel_sys_execvpe_tests PRIVATE libkernel_sys libkernel libc)
|
||||
configure_windows_unwind(guest_kernel_sys_execvpe_tests)
|
||||
add_test(NAME guest_kernel_sys_execvpe COMMAND guest_kernel_sys_execvpe_tests)
|
||||
set_tests_properties(guest_kernel_sys_execvpe PROPERTIES TIMEOUT 20)
|
||||
if(WIN32)
|
||||
set_tests_properties(guest_kernel_sys_execvpe PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libkernel_sys>")
|
||||
endif()
|
||||
add_test_executable(guest_kernel_sys_nanosleep_tests tests/GuestKernelSysNanosleep.cpp)
|
||||
target_include_directories(guest_kernel_sys_nanosleep_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
target_link_libraries(guest_kernel_sys_nanosleep_tests PRIVATE libkernel_sys libkernel libc)
|
||||
configure_windows_unwind(guest_kernel_sys_nanosleep_tests)
|
||||
add_test(NAME guest_kernel_sys_nanosleep COMMAND guest_kernel_sys_nanosleep_tests)
|
||||
set_tests_properties(guest_kernel_sys_nanosleep PROPERTIES TIMEOUT 20)
|
||||
if(WIN32)
|
||||
set_tests_properties(guest_kernel_sys_nanosleep PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libkernel_sys>")
|
||||
endif()
|
||||
add_test_executable(guest_kernel_sys_adjtime_tests tests/GuestKernelSysAdjtime.cpp)
|
||||
target_include_directories(guest_kernel_sys_adjtime_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
target_link_libraries(guest_kernel_sys_adjtime_tests PRIVATE libkernel_sys libkernel libc)
|
||||
configure_windows_unwind(guest_kernel_sys_adjtime_tests)
|
||||
add_test(NAME guest_kernel_sys_adjtime COMMAND guest_kernel_sys_adjtime_tests)
|
||||
set_tests_properties(guest_kernel_sys_adjtime PROPERTIES TIMEOUT 20)
|
||||
if(WIN32)
|
||||
set_tests_properties(guest_kernel_sys_adjtime PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libkernel_sys>")
|
||||
endif()
|
||||
add_test_executable(guest_kernel_sys_pthread_tests tests/GuestKernelSysPthread.cpp)
|
||||
target_include_directories(guest_kernel_sys_pthread_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
target_link_libraries(guest_kernel_sys_pthread_tests PRIVATE libkernel_sys libkernel libc)
|
||||
@@ -566,6 +608,16 @@ if(BUILD_TESTING)
|
||||
set_tests_properties(guest_firmware_constant_returns PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceAvSetting>;PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceComposite>;PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceFsInternalForVsh>;PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceHttpCache>;PATH=path_list_prepend:$<TARGET_FILE_DIR:libScePlayReady>;PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceSdma>;PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceSsl>;PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceVisionManager>;PATH=path_list_prepend:$<TARGET_FILE_DIR:libmdbg_syscore>")
|
||||
endif()
|
||||
add_test_executable(guest_bemp2sys_tests tests/GuestBemp2sys.cpp)
|
||||
target_include_directories(guest_bemp2sys_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
target_link_libraries(guest_bemp2sys_tests PRIVATE libSceBemp2sys libkernel libc)
|
||||
configure_windows_unwind(guest_bemp2sys_tests)
|
||||
add_test(NAME guest_bemp2sys COMMAND guest_bemp2sys_tests)
|
||||
set_tests_properties(guest_bemp2sys PROPERTIES TIMEOUT 20)
|
||||
if(WIN32)
|
||||
set_tests_properties(guest_bemp2sys PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceBemp2sys>")
|
||||
endif()
|
||||
add_test_executable(guest_playready_tests tests/GuestPlayReady.cpp)
|
||||
target_include_directories(guest_playready_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
target_link_libraries(guest_playready_tests PRIVATE libScePlayReady libkernel libc)
|
||||
@@ -1072,16 +1124,6 @@ if(BUILD_TESTING)
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceSystemGesture>")
|
||||
endif()
|
||||
|
||||
add_test_executable(guest_sys_util_notification_tests tests/GuestSysUtilNotification.cpp)
|
||||
target_include_directories(guest_sys_util_notification_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
target_link_libraries(guest_sys_util_notification_tests PRIVATE libSceSysUtil libkernel libc)
|
||||
configure_windows_unwind(guest_sys_util_notification_tests)
|
||||
add_test(NAME guest_sys_util_notification COMMAND guest_sys_util_notification_tests)
|
||||
if(WIN32)
|
||||
set_tests_properties(guest_sys_util_notification PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceSysUtil>")
|
||||
endif()
|
||||
|
||||
if(UNIX AND (CMAKE_SYSTEM_PROCESSOR MATCHES "^(x86_64|AMD64)$" OR CMAKE_OSX_ARCHITECTURES STREQUAL "x86_64"))
|
||||
add_subdirectory(tests/register_context)
|
||||
endif()
|
||||
|
||||
@@ -34,6 +34,7 @@ extern "C" void AgcDriverRequestScreenshot_nid_postfix();
|
||||
extern "C" void AgcDriverSuspendPoint_nid_postfix();
|
||||
extern "C" void AgcDriverSerialProgress_nid_postfix(std::uint64_t* accepted, std::uint64_t* completed);
|
||||
extern "C" std::size_t AgcDriverAwaitedWaits_nid_postfix(AgcDriver::AwaitedWait* waits, std::size_t capacity);
|
||||
extern "C" void AgcDriverWaitForPipelineJournal_nid_postfix();
|
||||
extern "C" std::uint64_t AgcDriverDispatchesAdoptedAhead_nid_postfix();
|
||||
extern "C" std::uint64_t AgcDriverDispatchesRejectedAhead_nid_postfix();
|
||||
extern "C" void AgcDriverSetAheadAdoptHook_nid_postfix(void (*hook)());
|
||||
|
||||
@@ -26,23 +26,18 @@ namespace AgcDriver::DriverDetail {
|
||||
|
||||
inline bool CaptureRegisteredViewsCurrent(std::span<const ShaderRecompiler::RegisteredImageView> views) {
|
||||
if (views.empty()) return true;
|
||||
for (std::uint32_t attempt = 0; attempt < 2u; ++attempt) {
|
||||
const auto before = GuestAllocations::GuestAllocationsGeneration_nid_postfix();
|
||||
bool current = true;
|
||||
for (const auto& view : views) {
|
||||
const auto range = GuestAllocations::GuestAllocationsLookup_nid_postfix(static_cast<std::uintptr_t>(view.address), false);
|
||||
if (view.hasRange != (range != nullptr)) {
|
||||
current = false;
|
||||
break;
|
||||
}
|
||||
if (range != nullptr && (range->address != view.rangeAddress || range->bytes != view.rangeBytes || range->allocationAddress != view.allocationAddress || range->allocationBytes != view.allocationBytes || range->backingSerial != view.backingSerial || range->readable != view.readable || range->writable != view.writable || range->gpu != view.gpu)) {
|
||||
current = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (before == GuestAllocations::GuestAllocationsGeneration_nid_postfix()) return current;
|
||||
std::vector<std::uintptr_t> addresses;
|
||||
addresses.reserve(views.size());
|
||||
for (const auto& view : views) addresses.push_back(static_cast<std::uintptr_t>(view.address));
|
||||
std::vector<std::shared_ptr<const GuestAllocations::Range>> ranges(views.size());
|
||||
GuestAllocations::GuestAllocationsLookupEach_nid_postfix(addresses.data(), addresses.size(), ranges.data());
|
||||
for (std::size_t index = 0; index < views.size(); ++index) {
|
||||
const auto& view = views[index];
|
||||
const auto& range = ranges[index];
|
||||
if (view.hasRange != (range != nullptr)) return false;
|
||||
if (range != nullptr && (range->address != view.rangeAddress || range->bytes != view.rangeBytes || range->allocationAddress != view.allocationAddress || range->allocationBytes != view.allocationBytes || range->backingSerial != view.backingSerial || range->readable != view.readable || range->writable != view.writable || range->gpu != view.gpu)) return false;
|
||||
}
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
inline constexpr std::size_t MaxDispatchVariants = 8;
|
||||
|
||||
@@ -78,6 +78,7 @@ public:
|
||||
std::pair<std::uint64_t, std::uint64_t> SerialProgress();
|
||||
std::uint64_t FlipSubmissions() const;
|
||||
std::vector<AwaitedWait> AwaitedWaits();
|
||||
void WaitForPipelineJournal();
|
||||
bool WaitIdleFor(std::chrono::milliseconds timeout);
|
||||
void RegisterVideoOutput(std::uint32_t handle, const std::shared_ptr<IVideoOutput>& output);
|
||||
void UnregisterVideoOutput(std::uint32_t handle, const std::shared_ptr<IVideoOutput>& output);
|
||||
@@ -269,7 +270,8 @@ private:
|
||||
static IndirectSource& indirectSource();
|
||||
static std::string describeIndirectSource();
|
||||
std::array<std::uint32_t, 5> readDispatchArguments(std::uint32_t queue, std::uint64_t arguments, std::uint32_t initiator);
|
||||
static bool& sampledRead();
|
||||
static bool sampledRead();
|
||||
static void setSampledRead(bool sampled);
|
||||
static bool writeEvidenceEnabled();
|
||||
static bool writerKeyedEvidence();
|
||||
static bool foreignWriters();
|
||||
@@ -469,7 +471,7 @@ private:
|
||||
std::map<std::uint32_t, QueueState> queues;
|
||||
std::map<std::uint32_t, std::shared_ptr<IVideoOutput>> outputs;
|
||||
struct EopFlip {
|
||||
std::uint32_t handle;
|
||||
FlipInfo info;
|
||||
std::shared_ptr<IFlipRequest> request;
|
||||
};
|
||||
std::map<std::uint32_t, EopFlip> eopFlips;
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
#ifndef CORE_LIBS_PRX_LIBSCEAGCDRIVER_EXECUTION_INCLUDE_THREADSCRATCH_HPP
|
||||
#define CORE_LIBS_PRX_LIBSCEAGCDRIVER_EXECUTION_INCLUDE_THREADSCRATCH_HPP
|
||||
|
||||
#include "prx/libc/include/HostThreadSlot.hpp"
|
||||
#include "ThreadOwned.hpp"
|
||||
|
||||
namespace AgcDriver {
|
||||
|
||||
template<typename T, typename Tag>
|
||||
T& ThreadScratch() {
|
||||
auto* object = HostThreadSlot<T*, Tag>::Get();
|
||||
if (object == nullptr) {
|
||||
ShaderRecompiler::ThreadOwned(object);
|
||||
HostThreadSlot<T*, Tag>::Set(object);
|
||||
}
|
||||
return *object;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -32,6 +32,11 @@ struct FlipInfo {
|
||||
std::int64_t argument;
|
||||
};
|
||||
|
||||
class EopFlipAlreadyReserved final : public std::runtime_error {
|
||||
public:
|
||||
EopFlipAlreadyReserved() : std::runtime_error("EOP flip token is already reserved for this flip") {}
|
||||
};
|
||||
|
||||
class IFlipRequest {
|
||||
public:
|
||||
virtual ~IFlipRequest() = default;
|
||||
|
||||
@@ -161,6 +161,10 @@ extern "C" std::size_t AgcDriverAwaitedWaits_nid_postfix(AgcDriver::AwaitedWait*
|
||||
return count;
|
||||
}
|
||||
|
||||
extern "C" void AgcDriverWaitForPipelineJournal_nid_postfix() {
|
||||
AgcDriver::DriverDetail::Driver::Get().WaitForPipelineJournal();
|
||||
}
|
||||
|
||||
extern "C" void AgcDriverSerialProgress_nid_postfix(std::uint64_t* accepted, std::uint64_t* completed) {
|
||||
const auto [submitted, done] = AgcDriver::DriverDetail::Driver::Get().SerialProgress();
|
||||
*accepted = submitted;
|
||||
|
||||
@@ -297,6 +297,16 @@ std::vector<AwaitedWait> Driver::AwaitedWaits() {
|
||||
return result;
|
||||
}
|
||||
|
||||
void Driver::WaitForPipelineJournal() {
|
||||
std::shared_ptr<VulkanDevice> localDevice = device.Load();
|
||||
if (localDevice == nullptr) {
|
||||
std::lock_guard gpuLock(GuestMemory::GpuMutex());
|
||||
if (device == nullptr) device = std::make_shared<VulkanDevice>();
|
||||
localDevice = device;
|
||||
}
|
||||
localDevice->WaitForPipelineJournalReplay();
|
||||
}
|
||||
|
||||
std::pair<std::uint64_t, std::uint64_t> Driver::SerialProgress() {
|
||||
std::lock_guard lock(mutex);
|
||||
return {accepted, completed};
|
||||
|
||||
@@ -2,16 +2,17 @@
|
||||
#include "prx/libSceAgcDriver/Execution/include/Driver/Draw/DrawPipeline.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/Driver/Memory/WriteEvidence.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp"
|
||||
#include "prx/libc/include/HostThreadSlot.hpp"
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
namespace AgcDriver::DriverDetail {
|
||||
|
||||
SampledReadScope::SampledReadScope(std::atomic<std::uint64_t>& reads) : previous(Driver::sampledRead()) {
|
||||
Driver::sampledRead() = Driver::writeEvidenceEnabled() && reads.fetch_add(1, std::memory_order_relaxed) % Driver::writeEvidenceSampleEvery() == 0;
|
||||
Driver::setSampledRead(Driver::writeEvidenceEnabled() && reads.fetch_add(1, std::memory_order_relaxed) % Driver::writeEvidenceSampleEvery() == 0);
|
||||
}
|
||||
|
||||
SampledReadScope::~SampledReadScope() { Driver::sampledRead() = previous; }
|
||||
SampledReadScope::~SampledReadScope() { Driver::setSampledRead(previous); }
|
||||
|
||||
void PendingView::Load() {
|
||||
if (Driver::validateLegacy()) return;
|
||||
@@ -26,9 +27,18 @@ bool PendingView::Overlaps(std::uint64_t address, std::size_t bytes) const {
|
||||
return Driver::validateLegacy() ? Graphics::Recorder::SnapshotWriteOverlaps(address, bytes) : Graphics::Recorder::SnapshotOverlaps(snapshot.get(), address, bytes);
|
||||
}
|
||||
|
||||
bool& Driver::sampledRead() {
|
||||
static thread_local bool sampled = false;
|
||||
return sampled;
|
||||
namespace {
|
||||
|
||||
struct SampledReadTag;
|
||||
|
||||
}
|
||||
|
||||
bool Driver::sampledRead() {
|
||||
return HostThreadSlot<bool, SampledReadTag>::Get();
|
||||
}
|
||||
|
||||
void Driver::setSampledRead(bool sampled) {
|
||||
HostThreadSlot<bool, SampledReadTag>::Set(sampled);
|
||||
}
|
||||
|
||||
bool Driver::writeEvidenceEnabled() {
|
||||
|
||||
@@ -24,12 +24,17 @@ std::uint32_t Driver::ReserveEopFlipAt(const FlipInfo& info, std::uint32_t token
|
||||
std::uint32_t Driver::reserveEopFlip(const FlipInfo& info, std::uint32_t token) {
|
||||
rethrowFailure();
|
||||
checkStopping();
|
||||
require(token >= EopFlipTokenBase && token <= EopFlipTokenLimit && !eopFlips.contains(token), "EOP flip token is not free");
|
||||
require(token >= EopFlipTokenBase && token <= EopFlipTokenLimit, "EOP flip token is out of range");
|
||||
const auto output = outputs.find(info.handle);
|
||||
require(output != outputs.end(), "EOP flip references an unregistered video output");
|
||||
if (const auto found = eopFlips.find(token); found != eopFlips.end()) {
|
||||
const auto& reserved = found->second.info;
|
||||
require(reserved.handle == info.handle && reserved.index == info.index && reserved.mode == info.mode && reserved.argument == info.argument, "EOP flip token is reserved for a different flip");
|
||||
throw EopFlipAlreadyReserved{};
|
||||
}
|
||||
auto request = output->second->TryReserve(info);
|
||||
if (!request) return 0;
|
||||
eopFlips.emplace(token, EopFlip{info.handle, std::move(request)});
|
||||
eopFlips.emplace(token, EopFlip{info, std::move(request)});
|
||||
nextEopToken = std::max(nextEopToken, token + 1);
|
||||
return token;
|
||||
}
|
||||
@@ -53,7 +58,7 @@ void Driver::UnregisterVideoOutput(std::uint32_t handle, const std::shared_ptr<I
|
||||
std::lock_guard lock(mutex);
|
||||
const auto it = outputs.find(handle);
|
||||
require(it != outputs.end() && it->second == output, "video output registration mismatch");
|
||||
std::erase_if(eopFlips, [handle](const auto& entry) { return entry.second.handle == handle; });
|
||||
std::erase_if(eopFlips, [handle](const auto& entry) { return entry.second.info.handle == handle; });
|
||||
outputs.erase(it);
|
||||
}
|
||||
|
||||
|
||||
@@ -50,6 +50,8 @@ FrameTiming* Driver::includeTimingSubmission(const Submission& submission, bool
|
||||
}
|
||||
|
||||
void Driver::markCompleted(std::uint64_t serial) {
|
||||
static const bool traceGpu = std::getenv("APS5_TRACE_GPU") != nullptr;
|
||||
if (traceGpu) std::fprintf(stderr, "[gpu] %.1f completed serial=%llu (in order up to %llu)\n", TraceMs(), static_cast<unsigned long long>(serial), static_cast<unsigned long long>(completed));
|
||||
completedOutOfOrder.insert(serial);
|
||||
while (!completedOutOfOrder.empty() && *completedOutOfOrder.begin() == completed + 1) {
|
||||
completed = *completedOutOfOrder.begin();
|
||||
@@ -70,6 +72,8 @@ void Driver::deferCompletion(const Submission& submission) {
|
||||
queue0Uncommitted.push_back(received);
|
||||
}
|
||||
completionDeferred() = true;
|
||||
static const bool traceGpu = std::getenv("APS5_TRACE_GPU") != nullptr;
|
||||
if (traceGpu) std::fprintf(stderr, "[gpu] %.1f deferred completion serial=%llu to the draw pipeline\n", TraceMs(), static_cast<unsigned long long>(submission.serial));
|
||||
DrawPipeline::Queue0().Enqueue([this, serial = submission.serial, received, labelWrites = submission.labelWrites, progress = submission.progress, holdsFlip = submission.holdsFlip] {
|
||||
completeSubmission(serial, received, labelWrites, progress, holdsFlip);
|
||||
}, {});
|
||||
|
||||
@@ -542,6 +542,8 @@ void Driver::Submit(const Packet* packet, std::uint32_t queue, bool cpuFlip) {
|
||||
}
|
||||
submission.shaders = shaders;
|
||||
submission.serial = accepted + 1;
|
||||
if (trace) std::fprintf(stderr, "[gpu] %.1f accepted serial=%llu queue=0x%x dwords=%zu at %p%s\n", TraceMs(), static_cast<unsigned long long>(submission.serial), queue, submission.commands.size(),
|
||||
static_cast<const void*>(descriptor.addr), cpuFlip ? " (cpu flip)" : "");
|
||||
|
||||
submission.received = ++eventSerial;
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
@@ -599,6 +601,8 @@ void Driver::SuspendPoint() {
|
||||
Submission boundary{};
|
||||
boundary.serial = accepted + 1;
|
||||
boundary.suspend = true;
|
||||
static const bool trace = std::getenv("APS5_TRACE_GPU") != nullptr;
|
||||
if (trace) std::fprintf(stderr, "[gpu] %.1f accepted serial=%llu suspend point\n", TraceMs(), static_cast<unsigned long long>(boundary.serial));
|
||||
|
||||
boundary.queue = 0;
|
||||
boundary.receivedAt = receivedAt;
|
||||
|
||||
@@ -358,7 +358,11 @@ void Validate(std::span<const std::uint32_t> packet, std::uint32_t queue) {
|
||||
}
|
||||
case 0x3f: size(4); break;
|
||||
case 0x13: case 0x2f: graphics(); size(2); break;
|
||||
case 0x26: graphics(); size(3); break;
|
||||
case 0x26:
|
||||
graphics();
|
||||
size(3);
|
||||
require((packet[1] & 1u) == 0 && packet[2] <= 0xffffu, "unsupported index base alignment or address bits");
|
||||
break;
|
||||
case 0x27:
|
||||
graphics();
|
||||
size(6);
|
||||
@@ -400,7 +404,7 @@ void Validate(std::span<const std::uint32_t> packet, std::uint32_t queue) {
|
||||
require((packet[2] >> 16u) == 0 && (packet[3] >> 16u) == 0, "start-index location semantics are not implemented");
|
||||
require(drawLocation(packet[2]) && drawLocation(packet[3]) && drawLocation(packet[4] & 0xffffu), "invalid indirect draw register location");
|
||||
require((packet[4] & ~(0xffffu | (1u << 30u) | (1u << 31u))) == 0, "indirect multi-draw control bits are not implemented");
|
||||
if ((packet[4] & (1u << 30u)) != 0) require((packet[6] & 3u) == 0 && address(packet[6], packet[7]) != 0, "invalid indirect draw count address");
|
||||
if ((packet[4] & (1u << 30u)) != 0) require((packet[6] & 3u) == 0 && address(packet[6], packet[7]) != 0 && packet[7] <= 0xffffu, "invalid indirect draw count address");
|
||||
else require(address(packet[6], packet[7]) == 0, "count address without an indirect draw count");
|
||||
require((packet[8] & 3u) == 0 && packet[8] >= (opcode == 0x2c ? 16u : 20u), "invalid indirect draw stride");
|
||||
require((packet[9] & ~0x20u) == (opcode == 0x2c ? 2u : 0u), "unsupported indirect draw initiator");
|
||||
|
||||
@@ -3,14 +3,24 @@
|
||||
#include "SDL_filesystem.h"
|
||||
#include "SDL_error.h"
|
||||
#include <atomic>
|
||||
#include <cerrno>
|
||||
#include <climits>
|
||||
#include <cstdlib>
|
||||
#include <ctime>
|
||||
#include <fstream>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <fcntl.h>
|
||||
#include <io.h>
|
||||
#include <sys/stat.h>
|
||||
#else
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
|
||||
std::atomic<bool> requested{false};
|
||||
@@ -40,10 +50,52 @@ std::string localTimestamp() {
|
||||
return text;
|
||||
}
|
||||
|
||||
std::filesystem::path unusedPath(const std::filesystem::path& folder, const std::string& stem) {
|
||||
int createExclusive(const std::filesystem::path& path) {
|
||||
#ifdef _WIN32
|
||||
return ::_wopen(path.c_str(), _O_WRONLY | _O_CREAT | _O_EXCL | _O_BINARY | _O_NOINHERIT, _S_IREAD | _S_IWRITE);
|
||||
#else
|
||||
return ::open(path.c_str(), O_WRONLY | O_CREAT | O_EXCL | O_CLOEXEC, 0666);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool writeAll(int descriptor, const std::uint8_t* data, std::size_t size) {
|
||||
while (size != 0) {
|
||||
#ifdef _WIN32
|
||||
const unsigned int chunk = size > INT_MAX ? INT_MAX : static_cast<unsigned int>(size);
|
||||
const int written = ::_write(descriptor, data, chunk);
|
||||
#else
|
||||
const auto written = ::write(descriptor, data, size);
|
||||
#endif
|
||||
if (written < 0 && errno == EINTR) continue;
|
||||
if (written <= 0) return false;
|
||||
data += written;
|
||||
size -= static_cast<std::size_t>(written);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool closeFile(int descriptor) {
|
||||
#ifdef _WIN32
|
||||
return ::_close(descriptor) == 0;
|
||||
#else
|
||||
return ::close(descriptor) == 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
std::filesystem::path writeUnusedPng(const std::filesystem::path& folder, const std::string& stem, const std::vector<std::uint8_t>& png) {
|
||||
for (int index = 0; index < 1000; ++index) {
|
||||
const auto path = folder / (index == 0 ? stem + ".png" : stem + "-" + std::to_string(index) + ".png");
|
||||
if (!std::filesystem::exists(path)) return path;
|
||||
const int descriptor = createExclusive(path);
|
||||
if (descriptor < 0) {
|
||||
if (errno == EEXIST) continue;
|
||||
throw std::runtime_error("Screenshot: cannot create output file");
|
||||
}
|
||||
const bool written = writeAll(descriptor, png.data(), png.size());
|
||||
const bool closed = closeFile(descriptor);
|
||||
if (written && closed) return path;
|
||||
std::error_code error;
|
||||
std::filesystem::remove(path, error);
|
||||
throw std::runtime_error("Screenshot: cannot write output file");
|
||||
}
|
||||
throw std::runtime_error("Screenshot: no unused file name for " + stem + " in " + folder.string());
|
||||
}
|
||||
@@ -71,11 +123,5 @@ std::filesystem::path AgcDriver::Screenshot::Write(std::string_view titleId, std
|
||||
const auto png = Decoder::Png::Encode(rgb, width, height, 3);
|
||||
const auto folder = directory();
|
||||
std::filesystem::create_directories(folder);
|
||||
const auto path = unusedPath(folder, std::string(titleId) + "-" + localTimestamp());
|
||||
std::ofstream file;
|
||||
file.exceptions(std::ios::failbit | std::ios::badbit);
|
||||
file.open(path, std::ios::binary);
|
||||
file.write(reinterpret_cast<const char*>(png.data()), static_cast<std::streamsize>(png.size()));
|
||||
file.close();
|
||||
return path;
|
||||
return writeUnusedPng(folder, std::string(titleId) + "-" + localTimestamp(), png);
|
||||
}
|
||||
@@ -2444,7 +2444,8 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window, bool compileOnly) :
|
||||
state->oversizedBuffer = std::make_unique<Graphics::Buffer>(graphicsContext(), Graphics::EmptyBufferBytes, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT);
|
||||
std::ranges::fill(state->oversizedBuffer->Bytes(), std::byte{0});
|
||||
if (!compileOnly && Pm4::GdsImportable() && Graphics::HostImportFor(graphicsContext(), Pm4::GdsAddress(), Pm4::GdsBytes, Graphics::ImportRequester::Map) == nullptr) Pm4::RejectGdsImport();
|
||||
state->pipelineCache = std::make_unique<Graphics::PipelineCache>(graphicsContext(), state->properties, window != nullptr || compileOnly);
|
||||
static const bool headlessPipelineCache = std::getenv("APS5_HEADLESS_PIPELINE_CACHE") != nullptr;
|
||||
state->pipelineCache = std::make_unique<Graphics::PipelineCache>(graphicsContext(), state->properties, window != nullptr || compileOnly || headlessPipelineCache);
|
||||
state->detiler = std::make_unique<Graphics::TextureDetiler>(graphicsContext());
|
||||
state->textureCache = std::make_unique<Graphics::TextureCache>(graphicsContext());
|
||||
state->colorTransfer = std::make_unique<Graphics::GpuColorTransfer>(graphicsContext());
|
||||
|
||||
@@ -26,6 +26,7 @@ public:
|
||||
bool ReadsOpaqueBlackBorder() const;
|
||||
bool RequiresNonSeamlessCube() const;
|
||||
static void Validate(const Context& context, const GuestSamplerResource& descriptor);
|
||||
static bool ShaderAppliesLodBias(const GuestSamplerResource& descriptor);
|
||||
static bool ReadsOpaqueBlackBorder(const GuestSamplerResource& descriptor);
|
||||
|
||||
private:
|
||||
|
||||
@@ -2412,6 +2412,7 @@ const HostImport* importRunAt(const Context& context, HostImports& state, Import
|
||||
const bool exhausted = result == VK_ERROR_OUT_OF_HOST_MEMORY || result == VK_ERROR_OUT_OF_DEVICE_MEMORY;
|
||||
if (std::string_view(step) == "vkAllocateMemory") {
|
||||
state.allocationFailed = true;
|
||||
if (!exhausted) state.allocationFailures = 0;
|
||||
if (exhausted && (++state.allocationFailures >= 3 || state.liveBytes + bytes + budget / 8 >= budget)) {
|
||||
state.ceiling = std::max<std::uint64_t>(state.liveBytes, 1);
|
||||
std::fprintf(stderr, "[gpu] host import budget lowered to the %.1f MiB live after vkAllocateMemory failed %s\n", state.liveBytes / 1048576.0, state.allocationFailures >= 3 ? "3 times in a row" : "within an eighth of the budget");
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "prx/libSceAgcDriver/Graphics/include/Sampler.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/TextureFormat.hpp"
|
||||
#include "PipelineSpecialization.hpp"
|
||||
#include <algorithm>
|
||||
#include <bit>
|
||||
#include <cstdlib>
|
||||
@@ -7,10 +8,14 @@
|
||||
|
||||
namespace AgcDriver::Graphics {
|
||||
|
||||
bool Sampler::ShaderAppliesLodBias(const GuestSamplerResource& descriptor) {
|
||||
return descriptor.lodBias < -ShaderRecompiler::PipelineSpecialization::SamplerLodBiasShaderThreshold || descriptor.lodBias > ShaderRecompiler::PipelineSpecialization::SamplerLodBiasShaderThreshold;
|
||||
}
|
||||
|
||||
void Sampler::Validate(const Context& context, const GuestSamplerResource& descriptor) {
|
||||
Require(!descriptor.anisotropyEnable || context.samplerAnisotropy, "guest sampler descriptor requests anisotropic filtering which the device does not support");
|
||||
Require(descriptor.maxAnisotropy <= context.limits.maxSamplerAnisotropy, "guest sampler descriptor requests an anisotropy ratio beyond the device limit");
|
||||
Require(descriptor.lodBias >= -context.limits.maxSamplerLodBias && descriptor.lodBias <= context.limits.maxSamplerLodBias, "guest sampler descriptor requests a LOD bias beyond the device limit");
|
||||
Require(ShaderAppliesLodBias(descriptor) || (descriptor.lodBias >= -context.limits.maxSamplerLodBias && descriptor.lodBias <= context.limits.maxSamplerLodBias), "guest sampler descriptor requests a LOD bias beyond the device limit");
|
||||
const auto mirrorsOnce = [](VkSamplerAddressMode mode) { return mode == VK_SAMPLER_ADDRESS_MODE_MIRROR_CLAMP_TO_EDGE; };
|
||||
Require(context.samplerMirrorClampToEdge || !(mirrorsOnce(descriptor.addressModeU) || mirrorsOnce(descriptor.addressModeV) || mirrorsOnce(descriptor.addressModeW)), "guest sampler descriptor uses a mirror-once clamp mode which the device does not support");
|
||||
Require(!(descriptor.unnormalizedCoordinates && descriptor.compareEnable), "guest sampler descriptor with unnormalized coordinates enables depth comparison, which is not implemented");
|
||||
@@ -36,7 +41,7 @@ namespace AgcDriver::Graphics {
|
||||
info.addressModeU = descriptor.addressModeU;
|
||||
info.addressModeV = descriptor.addressModeV;
|
||||
info.addressModeW = descriptor.addressModeW;
|
||||
info.mipLodBias = descriptor.lodBias;
|
||||
info.mipLodBias = ShaderAppliesLodBias(descriptor) ? 0.0f : descriptor.lodBias;
|
||||
info.anisotropyEnable = descriptor.anisotropyEnable ? VK_TRUE : VK_FALSE;
|
||||
info.maxAnisotropy = descriptor.maxAnisotropy;
|
||||
info.compareEnable = descriptor.compareEnable ? VK_TRUE : VK_FALSE;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include "prx/libSceAgcDriver/Execution/include/PerformanceTimer.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/CaptureTrace.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/Mutex.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/ThreadScratch.hpp"
|
||||
#include "ThreadOwned.hpp"
|
||||
#include "prx/libc/include/HostThreadLocal.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/MultisampleTarget.hpp"
|
||||
@@ -58,24 +59,18 @@ bool overlap(std::uint64_t first, std::size_t firstSize, std::uint64_t second, s
|
||||
|
||||
bool registeredImageViewsCurrent(std::span<const ShaderRecompiler::RegisteredImageView> views) {
|
||||
if (views.empty()) return true;
|
||||
for (std::uint32_t attempt = 0; attempt < 2u; ++attempt) {
|
||||
const auto before = GuestAllocations::GuestAllocationsGeneration_nid_postfix();
|
||||
bool current = true;
|
||||
for (const auto& view : views) {
|
||||
const auto range = GuestAllocations::GuestAllocationsLookup_nid_postfix(static_cast<std::uintptr_t>(view.address), false);
|
||||
if (view.hasRange != (range != nullptr)) {
|
||||
current = false;
|
||||
break;
|
||||
}
|
||||
if (range != nullptr && (range->address != view.rangeAddress || range->bytes != view.rangeBytes || range->allocationAddress != view.allocationAddress || range->allocationBytes != view.allocationBytes || range->backingSerial != view.backingSerial || range->readable != view.readable || range->writable != view.writable || range->gpu != view.gpu)) {
|
||||
current = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
const auto after = GuestAllocations::GuestAllocationsGeneration_nid_postfix();
|
||||
if (before == after) return current;
|
||||
std::vector<std::uintptr_t> addresses;
|
||||
addresses.reserve(views.size());
|
||||
for (const auto& view : views) addresses.push_back(static_cast<std::uintptr_t>(view.address));
|
||||
std::vector<std::shared_ptr<const GuestAllocations::Range>> ranges(views.size());
|
||||
GuestAllocations::GuestAllocationsLookupEach_nid_postfix(addresses.data(), addresses.size(), ranges.data());
|
||||
for (std::size_t index = 0; index < views.size(); ++index) {
|
||||
const auto& view = views[index];
|
||||
const auto& range = ranges[index];
|
||||
if (view.hasRange != (range != nullptr)) return false;
|
||||
if (range != nullptr && (range->address != view.rangeAddress || range->bytes != view.rangeBytes || range->allocationAddress != view.allocationAddress || range->allocationBytes != view.allocationBytes || range->backingSerial != view.backingSerial || range->readable != view.readable || range->writable != view.writable || range->gpu != view.gpu)) return false;
|
||||
}
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
VkComponentSwizzle ComponentSwizzleFor(std::uint8_t dstSel) {
|
||||
@@ -3409,17 +3404,17 @@ bool ShaderResources::fastRevalidate(std::uint64_t serialBefore, std::span<const
|
||||
if (validatedTextures.size() != textures.size()) return fail(FastFail::NoRecord);
|
||||
const bool unchanged = pendingSerialSeen != 0 && pendingSerialSeen == serialBefore;
|
||||
const bool keyProofs = KeyFastPath();
|
||||
thread_local std::vector<GuestMemory::UnchangedQuery>* queriesSlot = nullptr;
|
||||
auto& queries = ShaderRecompiler::ThreadOwned(queriesSlot);
|
||||
thread_local std::vector<StorageTexture::PendingQuery>* pendingSlot = nullptr;
|
||||
auto& pending = ShaderRecompiler::ThreadOwned(pendingSlot);
|
||||
struct QueriesTag {};
|
||||
auto& queries = ThreadScratch<std::vector<GuestMemory::UnchangedQuery>, QueriesTag>();
|
||||
struct PendingTag {};
|
||||
auto& pending = ThreadScratch<std::vector<StorageTexture::PendingQuery>, PendingTag>();
|
||||
// The element each pending query stands for, in query order.
|
||||
thread_local std::vector<PendingOverlap>* ownersSlot = nullptr;
|
||||
auto& owners = ShaderRecompiler::ThreadOwned(ownersSlot);
|
||||
thread_local std::vector<const StorageTexture*>* imagesSlot = nullptr;
|
||||
auto& images = ShaderRecompiler::ThreadOwned(imagesSlot);
|
||||
thread_local std::vector<DccKeys>* scannedKeysSlot = nullptr;
|
||||
auto& scannedKeys = ShaderRecompiler::ThreadOwned(scannedKeysSlot);
|
||||
struct OwnersTag {};
|
||||
auto& owners = ThreadScratch<std::vector<PendingOverlap>, OwnersTag>();
|
||||
struct ImagesTag {};
|
||||
auto& images = ThreadScratch<std::vector<const StorageTexture*>, ImagesTag>();
|
||||
struct ScannedKeysTag {};
|
||||
auto& scannedKeys = ThreadScratch<std::vector<DccKeys>, ScannedKeysTag>();
|
||||
queries.clear();
|
||||
pending.clear();
|
||||
owners.clear();
|
||||
@@ -3923,10 +3918,10 @@ bool ShaderResources::revalidateFull(std::span<const CompiledShader> shaders, Pr
|
||||
captureValidation();
|
||||
return true;
|
||||
};
|
||||
thread_local std::vector<PendingOverlap>* overlappingSlot = nullptr;
|
||||
auto& overlapping = ShaderRecompiler::ThreadOwned(overlappingSlot);
|
||||
thread_local std::vector<PendingOverlap>* refreshedSlot = nullptr;
|
||||
auto& refreshed = ShaderRecompiler::ThreadOwned(refreshedSlot);
|
||||
struct OverlappingTag {};
|
||||
auto& overlapping = ThreadScratch<std::vector<PendingOverlap>, OverlappingTag>();
|
||||
struct RefreshedTag {};
|
||||
auto& refreshed = ThreadScratch<std::vector<PendingOverlap>, RefreshedTag>();
|
||||
refreshed.clear();
|
||||
FastFail reason = FastFail::Count;
|
||||
bool accepted = false;
|
||||
@@ -3983,8 +3978,8 @@ bool ShaderResources::revalidateFull(std::span<const CompiledShader> shaders, Pr
|
||||
// The walk after the refresh, or beside a proof that accepted a foreign overlap, must
|
||||
// find every object in place and current: another object, or an upload (a moved
|
||||
// content version), is a decision the proof got wrong.
|
||||
thread_local std::vector<std::pair<const StorageTexture*, std::uint64_t>>* versionsSlot = nullptr;
|
||||
auto& versions = ShaderRecompiler::ThreadOwned(versionsSlot);
|
||||
struct VersionsTag {};
|
||||
auto& versions = ThreadScratch<std::vector<std::pair<const StorageTexture*, std::uint64_t>>, VersionsTag>();
|
||||
versions.clear();
|
||||
for (const auto& surface : validatedTextures) {
|
||||
if (surface.source != nullptr) versions.emplace_back(surface.source, surface.source->Version());
|
||||
@@ -4021,8 +4016,8 @@ bool ShaderResources::revalidateFull(std::span<const CompiledShader> shaders, Pr
|
||||
};
|
||||
if (EpochRevalidate()) {
|
||||
if ((!directRegions.empty() || !guestMemory.Writes().empty()) && !(pendingSerialSeen != 0 && pendingSerialSeen == StorageTexture::PendingSerial())) {
|
||||
thread_local std::vector<StorageTexture::PendingQuery>* regionsSlot = nullptr;
|
||||
auto& regions = ShaderRecompiler::ThreadOwned(regionsSlot);
|
||||
struct RegionsTag {};
|
||||
auto& regions = ThreadScratch<std::vector<StorageTexture::PendingQuery>, RegionsTag>();
|
||||
regions.clear();
|
||||
for (const auto& region : directRegions) regions.push_back({region.begin, region.end, nullptr, nullptr, false});
|
||||
for (const auto& [begin, end] : guestMemory.Writes()) regions.push_back({begin, end, nullptr, nullptr, false});
|
||||
@@ -5467,6 +5462,7 @@ void ShaderResources::addSamplerTable(const ShaderRecompiler::DescriptorBinding&
|
||||
auto resource = DecodeSamplerResource(elementWords);
|
||||
resource.compareEnable = binding.samplerDepthCompare.at(element);
|
||||
Sampler::Validate(context, resource);
|
||||
Require(!Sampler::ShaderAppliesLodBias(resource), "a sampler table entry with a LOD bias beyond the sampler limit needs a per-entry shader bias, which is not implemented");
|
||||
Require(!Sampler::ReadsOpaqueBlackBorder(resource), "a sampler table entry with an opaque black border needs dynamic texture BC-swizzle pairing, which is not implemented");
|
||||
Require(resource.addressModeU != VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER && resource.addressModeV != VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER && resource.addressModeW != VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER, "a sampler table entry with a border needs dynamic texture numeric-class pairing, which is not implemented");
|
||||
Require(!resource.nonSeamlessCube || context.nonSeamlessCubeMap, "a sampler table entry that disables seamless cube filtering needs VK_EXT_non_seamless_cube_map because dynamic texture/sampler pairing is not implemented");
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "prx/libc/include/General.hpp"
|
||||
#include "prx/libc/include/general/AtomicSharedPtr.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/BufferPool.hpp"
|
||||
#include "prx/libc/include/HostThreadSlot.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/Resources.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/Recorder.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp"
|
||||
@@ -1849,7 +1850,8 @@ bool FillClearColor(VkFormat format, std::uint32_t elementBytes, std::span<const
|
||||
|
||||
// The image being validated by Refresh: its own pending results are what the next dispatch wants,
|
||||
// so the comparison with guest memory must not flush them.
|
||||
thread_local const StorageTexture* refreshing = nullptr;
|
||||
struct RefreshingTag;
|
||||
using Refreshing = HostThreadSlot<const StorageTexture*, RefreshingTag>;
|
||||
|
||||
void FlushHook(std::uint64_t address, std::size_t bytes) {
|
||||
StorageTexture::FlushPending(address, bytes);
|
||||
@@ -2261,11 +2263,11 @@ bool StorageTexture::refresh(std::uint32_t firstLayer, std::uint32_t lastLayer)
|
||||
refreshProof = {};
|
||||
struct Exempt {
|
||||
const StorageTexture* previous;
|
||||
~Exempt() { refreshing = previous; }
|
||||
} exempt{refreshing};
|
||||
~Exempt() { Refreshing::Set(previous); }
|
||||
} exempt{Refreshing::Get()};
|
||||
// This image's own pending results stay on the GPU, where the next dispatch wants them (the
|
||||
// flush below and the compare through the hook skip it).
|
||||
refreshing = this;
|
||||
Refreshing::Set(this);
|
||||
NoteProved();
|
||||
// Results of other images pending in this memory must reach it first, except an alias's: its
|
||||
// units are taken on the device below (borrowUnits), so it stays pending. The keys read for
|
||||
@@ -2832,9 +2834,9 @@ bool StorageTexture::compareUntracked(std::uint64_t address, std::size_t bytes,
|
||||
if (!originalHolds(address, bytes)) return false;
|
||||
struct Exempt {
|
||||
const StorageTexture* previous;
|
||||
~Exempt() { refreshing = previous; }
|
||||
} exempt{refreshing};
|
||||
refreshing = this;
|
||||
~Exempt() { Refreshing::Set(previous); }
|
||||
} exempt{Refreshing::Get()};
|
||||
Refreshing::Set(this);
|
||||
Require(address >= descriptor.baseAddress && bytes <= guestBytes && address - descriptor.baseAddress <= guestBytes - bytes, "texture comparison exceeds its snapshot");
|
||||
Require(original.size() == guestBytes, "texture comparison has no complete snapshot");
|
||||
GuestMemory::FlushGpuWrites(address, bytes);
|
||||
@@ -4584,7 +4586,7 @@ bool StorageTexture::FlushPending(std::uint64_t address, std::size_t bytes, cons
|
||||
if (pending.flushingCount.load(std::memory_order_acquire) != 0) return false;
|
||||
if (!pending.textures.SurelyClear(address, bytes)) {
|
||||
std::lock_guard lock(pending.mutex);
|
||||
const auto* exempt = refreshing;
|
||||
const auto* exempt = Refreshing::Get();
|
||||
if (pending.textures.MayOverlap(address, bytes) && std::any_of(pending.textures.begin(), pending.textures.end(), [&](const StorageTexture* texture) { return texture != except && texture != exempt && texture->overlaps(address, bytes) && texture->pendingUnitInside(address, bytes); })) return false;
|
||||
if (pending.flushingCount.load(std::memory_order_acquire) != 0) return false;
|
||||
}
|
||||
@@ -4614,7 +4616,7 @@ bool StorageTexture::FlushPending(std::uint64_t address, std::size_t bytes, cons
|
||||
{
|
||||
auto& pending = Pending();
|
||||
std::lock_guard lock(pending.mutex);
|
||||
const auto* exempt = refreshing;
|
||||
const auto* exempt = Refreshing::Get();
|
||||
for (auto it = pending.textures.MayOverlap(address, bytes) ? pending.textures.begin() : pending.textures.end(); it != pending.textures.end();) {
|
||||
auto* texture = *it;
|
||||
if (texture != except && texture != exempt && (device == VK_NULL_HANDLE || texture->context.device == device) && texture->overlaps(address, bytes)) {
|
||||
@@ -4762,7 +4764,7 @@ bool StorageTexture::FlushPending(std::uint64_t address, std::size_t bytes, cons
|
||||
bool StorageTexture::WaitForFlushing(std::uint64_t address, std::size_t bytes, const StorageTexture* except) {
|
||||
auto& pending = Pending();
|
||||
if (pending.flushingCount.load(std::memory_order_acquire) == 0 || GuestMemory::GpuMutex().HeldByThisThread()) return false;
|
||||
const auto* exempt = refreshing;
|
||||
const auto* exempt = Refreshing::Get();
|
||||
for (bool waited = false;; waited = true) {
|
||||
{
|
||||
std::lock_guard lock(pending.mutex);
|
||||
@@ -5415,7 +5417,7 @@ std::vector<std::shared_ptr<StorageTexture>> StorageTexture::overlappingPending(
|
||||
if (pending.textures.SurelyClear(address, bytes)) return overlapping;
|
||||
std::lock_guard lock(pending.mutex);
|
||||
if (!pending.textures.MayOverlap(address, bytes)) return overlapping;
|
||||
const auto* exempt = refreshing;
|
||||
const auto* exempt = Refreshing::Get();
|
||||
for (auto* texture : pending.textures) {
|
||||
if (texture == exempt || !texture->overlaps(address, bytes) || !texture->pendingUnitInside(address, bytes)) continue;
|
||||
if (auto alive = texture->weak_from_this().lock()) overlapping.push_back(std::move(alive));
|
||||
@@ -5597,7 +5599,7 @@ std::size_t StorageTexture::DiscardPendingInside(std::uint64_t address, std::siz
|
||||
std::lock_guard lock(pending.mutex);
|
||||
std::size_t discarded = 0;
|
||||
const auto end = address + bytes;
|
||||
const auto* exempt = refreshing;
|
||||
const auto* exempt = Refreshing::Get();
|
||||
for (auto it = pending.textures.begin(); it != pending.textures.end();) {
|
||||
auto* texture = *it;
|
||||
const auto begin = texture->descriptor.baseAddress;
|
||||
|
||||
@@ -2033,6 +2033,48 @@ void importAllocationErrorTests(const Context& context, VkResult failure, bool n
|
||||
::operator delete(block, std::align_val_t{piece});
|
||||
}
|
||||
|
||||
void importMixedAllocationErrorTests(const Context& context) {
|
||||
constexpr std::size_t piece = 65536;
|
||||
constexpr std::size_t count = 16;
|
||||
auto* block = static_cast<std::uint8_t*>(::operator new(2 * count * piece, std::align_val_t{piece}));
|
||||
auto importing = context;
|
||||
importing.hostImportAlignment = piece;
|
||||
ImportAllocationProbe probe(importing);
|
||||
SetHostImportWindow(0);
|
||||
ClearHostImports(importing.device);
|
||||
{
|
||||
GuestAllocations::Mutation mutation;
|
||||
for (std::size_t index = 0; index < count; ++index) mutation.Add(block + 2 * index * piece, piece, true, true, true);
|
||||
}
|
||||
SetHostImportBudget(64 * piece);
|
||||
std::size_t next = 0;
|
||||
const auto lookup = [&] { return HostImportFor(importing, reinterpret_cast<std::uintptr_t>(block + 2 * next++ * piece), 16) != nullptr; };
|
||||
const auto counted = [] { return HostImportCounters().requesters[static_cast<std::size_t>(ImportRequester::Access)]; };
|
||||
Require(lookup(), "the mixed allocation error test's first import was not made");
|
||||
const auto before = counted();
|
||||
for (const auto failure : {VK_ERROR_OUT_OF_DEVICE_MEMORY, VK_ERROR_OUT_OF_HOST_MEMORY, VK_ERROR_INVALID_EXTERNAL_HANDLE, VK_ERROR_OUT_OF_DEVICE_MEMORY, VK_ERROR_UNKNOWN, VK_ERROR_OUT_OF_HOST_MEMORY}) {
|
||||
probe.failure = failure;
|
||||
Require(!lookup(), "an import with an injected mixed allocation failure was made");
|
||||
}
|
||||
const auto after = counted();
|
||||
Require(after.failed - before.failed == 6 && after.refused == before.refused, "interrupted OOM failures lowered the import ceiling");
|
||||
probe.failure = VK_SUCCESS;
|
||||
Require(lookup(), "nonconsecutive OOM failures prevented a later successful import");
|
||||
for (const auto failure : {VK_ERROR_OUT_OF_HOST_MEMORY, VK_ERROR_OUT_OF_DEVICE_MEMORY, VK_ERROR_OUT_OF_HOST_MEMORY}) {
|
||||
probe.failure = failure;
|
||||
Require(!lookup(), "an import with an injected consecutive OOM was made");
|
||||
}
|
||||
probe.failure = VK_SUCCESS;
|
||||
Require(!lookup(), "three consecutive OOM failures did not lower the import ceiling");
|
||||
{
|
||||
GuestAllocations::Mutation mutation;
|
||||
for (std::size_t index = 0; index < count; ++index) mutation.Remove(block + 2 * index * piece);
|
||||
}
|
||||
ClearHostImports(importing.device);
|
||||
SetHostImportBudget(0);
|
||||
::operator delete(block, std::align_val_t{piece});
|
||||
}
|
||||
|
||||
void importCeilingTests(const Context& context, const BdaTestAccess& access, bool nearBudget) {
|
||||
constexpr std::size_t piece = 65536;
|
||||
constexpr std::size_t count = 24;
|
||||
@@ -4814,6 +4856,7 @@ void RunBdaResourceTests(const Context& context, const BdaTestAccess& access) {
|
||||
evictingRefusalJoinTests(context);
|
||||
evictingRefusalStepDownTests(context);
|
||||
importDeviceBudgetTests();
|
||||
importMixedAllocationErrorTests(context);
|
||||
importCeilingTests(context, access, false);
|
||||
importCeilingTests(context, access, true);
|
||||
for (const auto failure : {VK_ERROR_OUT_OF_HOST_MEMORY, VK_ERROR_OUT_OF_DEVICE_MEMORY, VK_ERROR_INVALID_EXTERNAL_HANDLE, VK_ERROR_UNKNOWN}) {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include "execution/VulkanTestDevice.hpp"
|
||||
#include "prx/libSceAgc/DcbFlow/include/Memory.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/Driver.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/FrameCapture.hpp"
|
||||
@@ -134,7 +135,7 @@ bool equal(const std::byte* bytes, const std::vector<std::byte>& expected) {
|
||||
return std::memcmp(bytes, expected.data(), expected.size()) == 0;
|
||||
}
|
||||
|
||||
void testCaptureAndReplay(const std::filesystem::path& tool) {
|
||||
void testCaptureAndReplay(const std::filesystem::path& tool, bool pipelines) {
|
||||
const char* directory = std::getenv("APS5_FRAME_CAPTURE");
|
||||
check(directory != nullptr && *directory != '\0', "APS5_FRAME_CAPTURE is not set");
|
||||
const std::filesystem::path capture(directory);
|
||||
@@ -186,17 +187,40 @@ void testCaptureAndReplay(const std::filesystem::path& tool) {
|
||||
check(recorded(late.base), "the block committed during the capture is not in its ranges");
|
||||
check(reader.Header().find("\"late_ranges\":1") != std::string::npos, "the capture header does not count one range committed during the capture: " + reader.Header());
|
||||
|
||||
const auto firstDump = capture / "first.bin";
|
||||
const auto secondDump = capture / "second.bin";
|
||||
auto command = "\"" + tool.string() + "\" \"" + capture.string() + "\" --strict --dump " + std::to_string(at(layout.firstTarget)) + ":" + std::to_string(DataBytes) + ":\"" + firstDump.string() +
|
||||
"\" --dump " + std::to_string(at(late.firstTarget)) + ":" + std::to_string(DataBytes) + ":\"" + secondDump.string() + "\"";
|
||||
const auto firstDump = capture / "first dump.bin";
|
||||
const auto secondDump = capture / "second dump.bin";
|
||||
const auto replay = [&](const std::string& options, const std::filesystem::path& output) {
|
||||
std::filesystem::remove(firstDump);
|
||||
std::filesystem::remove(secondDump);
|
||||
auto command = "\"" + tool.string() + "\" \"" + capture.string() + "\" --strict " + options + " --dump " + std::to_string(at(layout.firstTarget)) + ":" + std::to_string(DataBytes) + ":\"" +
|
||||
firstDump.string() + "\" --dump " + std::to_string(at(late.firstTarget)) + ":" + std::to_string(DataBytes) + ":\"" + secondDump.string() + "\" > \"" + output.string() + "\" 2> \"" + output.string() + ".log\"";
|
||||
#ifdef _WIN32
|
||||
command = "\"" + command + "\"";
|
||||
command = "\"" + command + "\"";
|
||||
#endif
|
||||
const auto code = std::system(command.c_str());
|
||||
check(code == 0, "agc_frame_replay exited with " + std::to_string(code));
|
||||
check(readFile(firstDump) == first, "the replay did not reproduce the first copy");
|
||||
check(readFile(secondDump) == second, "the replay did not reproduce the second copy");
|
||||
const auto code = std::system(command.c_str());
|
||||
check(code == 0, "agc_frame_replay " + options + " exited with " + std::to_string(code));
|
||||
check(readFile(firstDump) == first, "the replay " + options + " did not reproduce the first copy");
|
||||
check(readFile(secondDump) == second, "the replay " + options + " did not reproduce the second copy");
|
||||
const auto json = readFile(output);
|
||||
return std::string(reinterpret_cast<const char*>(json.data()), json.size());
|
||||
};
|
||||
replay("", capture / "replay output.json");
|
||||
if (pipelines) {
|
||||
check(std::getenv("APS5_HEADLESS_PIPELINE_CACHE") != nullptr, "the pipeline cache switch was not inherited");
|
||||
const auto warm = capture / "warm output.json";
|
||||
replay("", warm);
|
||||
const auto warmLog = readFile(warm.string() + ".log");
|
||||
check(std::string(reinterpret_cast<const char*>(warmLog.data()), warmLog.size()).find("[pipeline-cache] loaded ") != std::string::npos, "the warm replay did not load the seeded pipeline cache");
|
||||
const auto cold = capture / "cold output.json";
|
||||
replay("--cold-pipelines", cold);
|
||||
const auto coldLog = readFile(cold.string() + ".log");
|
||||
check(std::string(reinterpret_cast<const char*>(coldLog.data()), coldLog.size()).find("[pipeline-cache] loaded ") == std::string::npos, "--cold-pipelines loaded a cache with APS5_HEADLESS_PIPELINE_CACHE inherited");
|
||||
return;
|
||||
}
|
||||
const auto repeated = replay("--repeat 3", capture / "repeat output.json");
|
||||
std::size_t passes = 0;
|
||||
for (auto found = repeated.find("\"frames\":2,\"p50\""); found != std::string::npos; found = repeated.find("\"frames\":2,\"p50\"", found + 1)) ++passes;
|
||||
check(passes == 3, "the repeated replay did not replay both frames in each of three passes: " + repeated);
|
||||
}
|
||||
|
||||
void testConcurrentCopies(const std::filesystem::path& tool) {
|
||||
@@ -261,16 +285,57 @@ void testConcurrentCopies(const std::filesystem::path& tool) {
|
||||
}
|
||||
}
|
||||
|
||||
void testReservationError(const std::filesystem::path& tool) {
|
||||
const char* directory = std::getenv("APS5_FRAME_CAPTURE");
|
||||
check(directory != nullptr && *directory != '\0', "APS5_FRAME_CAPTURE is not set");
|
||||
const std::filesystem::path capture(directory);
|
||||
std::uintptr_t base = 0;
|
||||
std::size_t size = 0;
|
||||
GuestArena::GuestArenaRange_nid_postfix(&base, &size);
|
||||
AgcDriver::FrameCaptureWriter writer(capture);
|
||||
AgcDriver::FrameCall output;
|
||||
output.kind = FrameCallKind::RegisterVideoOutput;
|
||||
output.values = {Handle};
|
||||
writer.WriteCall(output);
|
||||
AgcDriver::FrameCall reservation;
|
||||
reservation.kind = FrameCallKind::ReserveEopFlip;
|
||||
reservation.values = {Handle, static_cast<std::uint64_t>(-1), 1, 0, AgcDriver::EopFlipTokenBase};
|
||||
writer.WriteCall(reservation);
|
||||
AgcDriver::FrameCall start;
|
||||
start.kind = FrameCallKind::FrameStart;
|
||||
writer.WriteCall(start);
|
||||
output.kind = FrameCallKind::UnregisterVideoOutput;
|
||||
writer.WriteCall(output);
|
||||
output.kind = FrameCallKind::RegisterVideoOutput;
|
||||
writer.WriteCall(output);
|
||||
reservation.values[3] = 1;
|
||||
writer.WriteCall(reservation);
|
||||
writer.Finish("{\"arena_base\":" + std::to_string(base) + "}");
|
||||
const auto result = capture / "reservation error.json";
|
||||
const auto log = capture / "reservation error.log";
|
||||
auto command = "\"" + tool.string() + "\" \"" + capture.string() + "\" --repeat 2 --strict > \"" + result.string() + "\" 2> \"" + log.string() + "\"";
|
||||
#ifdef _WIN32
|
||||
command = "\"" + command + "\"";
|
||||
#endif
|
||||
check(std::system(command.c_str()) != 0, "the replay accepted an incompatible reservation on the second pass");
|
||||
const auto errors = readFile(log);
|
||||
check(std::string(reinterpret_cast<const char*>(errors.data()), errors.size()).find("EOP flip token is reserved for a different flip") != std::string::npos, "the repeated replay did not report the incompatible reservation");
|
||||
const auto report = readFile(result);
|
||||
check(std::string(reinterpret_cast<const char*>(report.data()), report.size()).find("\"p50\"") == std::string::npos, "a failed repeated reservation produced a successful replay report");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
if (argc != 2 && !(argc == 3 && std::string(argv[2]) == "concurrent")) {
|
||||
std::fprintf(stderr, "usage: agc_driver_frame_replay_tests <agc_frame_replay> [concurrent]\n");
|
||||
if (argc != 2 && !(argc == 3 && (std::string(argv[2]) == "concurrent" || std::string(argv[2]) == "pipelines" || std::string(argv[2]) == "reservation_error"))) {
|
||||
std::fprintf(stderr, "usage: agc_driver_frame_replay_tests <agc_frame_replay> [concurrent|pipelines|reservation_error]\n");
|
||||
return 2;
|
||||
}
|
||||
try {
|
||||
if (argc == 3) testConcurrentCopies(argv[1]);
|
||||
else testCaptureAndReplay(argv[1]);
|
||||
if (!OpenVulkanTestDevice()) return VulkanTestSkipped;
|
||||
if (argc == 3 && std::string(argv[2]) == "concurrent") testConcurrentCopies(argv[1]);
|
||||
else if (argc == 3 && std::string(argv[2]) == "reservation_error") testReservationError(argv[1]);
|
||||
else testCaptureAndReplay(argv[1], argc == 3);
|
||||
} catch (const std::exception& error) {
|
||||
std::fprintf(stderr, "FAILED: %s\n", error.what());
|
||||
return 1;
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
#define main frameReplayToolMain
|
||||
#include "../tools/FrameReplay.cpp"
|
||||
#undef main
|
||||
#include <windows.h>
|
||||
|
||||
namespace {
|
||||
|
||||
using AgcDriver::FrameCaptureWriter;
|
||||
|
||||
constexpr int Skip = 77;
|
||||
constexpr DWORD ChildLimit = 60000;
|
||||
|
||||
void check(bool condition, const std::string& reason) {
|
||||
if (!condition) throw std::runtime_error(reason);
|
||||
}
|
||||
|
||||
void append(const std::filesystem::path& log, const std::string& line) {
|
||||
std::ofstream file(log, std::ios::app);
|
||||
file << line << '\n';
|
||||
check(file.good(), "cannot write the relaunch log");
|
||||
}
|
||||
|
||||
int child(const std::filesystem::path& directory, int exitCode) {
|
||||
const int local = 0;
|
||||
const auto address = reinterpret_cast<std::uintptr_t>(&local) & ~std::uintptr_t{0xfff};
|
||||
if (!GuestArena::GuestArenaHostRegionOverlaps_nid_postfix(address, 4096)) return Skip;
|
||||
const char* const attempt = std::getenv("APS5_FRAME_REPLAY_ATTEMPT");
|
||||
append(directory / "log.txt", attempt == nullptr ? "none" : attempt);
|
||||
const auto capture = directory / std::to_string(GetCurrentProcessId());
|
||||
FrameCaptureWriter writer(capture);
|
||||
writer.WriteRange(address, 4096, FrameRangeKind::Watched, 3, AgcDriver::FrameRangeAccess::Readable | AgcDriver::FrameRangeAccess::Gpu);
|
||||
writer.Finish("{}");
|
||||
const FrameCaptureReader reader(capture);
|
||||
relaunchIfHostRegionBlocks(reader);
|
||||
append(directory / "log.txt", "continued");
|
||||
return exitCode;
|
||||
}
|
||||
|
||||
int start(const std::filesystem::path& directory, const char* attempt, int exitCode) {
|
||||
std::filesystem::remove_all(directory);
|
||||
std::filesystem::create_directories(directory);
|
||||
check(_putenv_s("APS5_FRAME_REPLAY_ATTEMPT", attempt == nullptr ? "" : attempt) == 0, "cannot set the relaunch count");
|
||||
std::wstring self(32768, L'\0');
|
||||
self.resize(GetModuleFileNameW(nullptr, self.data(), static_cast<DWORD>(self.size())));
|
||||
std::wstring commandLine = L"\"" + self + L"\" child \"" + directory.wstring() + L"\" " + std::to_wstring(exitCode);
|
||||
STARTUPINFOW startup{};
|
||||
startup.cb = sizeof(startup);
|
||||
PROCESS_INFORMATION process{};
|
||||
check(CreateProcessW(nullptr, commandLine.data(), nullptr, nullptr, TRUE, 0, nullptr, nullptr, &startup, &process) != 0, "cannot start the replay child");
|
||||
CloseHandle(process.hThread);
|
||||
const bool exited = WaitForSingleObject(process.hProcess, ChildLimit) == WAIT_OBJECT_0;
|
||||
if (!exited) TerminateProcess(process.hProcess, 1);
|
||||
DWORD code = 1;
|
||||
GetExitCodeProcess(process.hProcess, &code);
|
||||
CloseHandle(process.hProcess);
|
||||
check(exited, "the replay child did not exit");
|
||||
return static_cast<int>(code);
|
||||
}
|
||||
|
||||
std::vector<std::string> lines(const std::filesystem::path& path) {
|
||||
std::vector<std::string> result;
|
||||
std::ifstream file(path);
|
||||
for (std::string line; std::getline(file, line);) result.push_back(line);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool expectRun(const std::filesystem::path& directory, const char* attempt, int exitCode, int expected, const std::vector<std::string>& log) {
|
||||
const int code = start(directory, attempt, exitCode);
|
||||
if (code == Skip) return false;
|
||||
const std::string name = attempt == nullptr ? "no count" : attempt;
|
||||
check(code == expected, name + ": exited with " + std::to_string(code));
|
||||
check(lines(directory / "log.txt") == log, name + ": wrong process sequence");
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
try {
|
||||
if (argc == 4 && std::string_view(argv[1]) == "child") return child(argv[2], std::atoi(argv[3]));
|
||||
check(argc == 2, "usage: agc_driver_frame_replay_relaunch_tests <directory>");
|
||||
const std::filesystem::path directory = argv[1];
|
||||
const std::vector<std::string> full{"none", "1", "2", "3", "4", "5", "6", "7", "8", "continued"};
|
||||
if (!expectRun(directory, nullptr, 0, 0, full)) {
|
||||
std::puts("skipped: the stack is not an arena host region");
|
||||
return Skip;
|
||||
}
|
||||
check(expectRun(directory, nullptr, 42, 42, full), "the failing run skipped");
|
||||
check(expectRun(directory, "7", 0, 0, {"7", "8", "continued"}), "the last relaunch skipped");
|
||||
check(expectRun(directory, "8", 5, 5, {"8", "continued"}), "the exhausted count skipped");
|
||||
for (const char* const malformed : {"-1", "-2147483648", "9", "99999999999", "abc", "1x", " 1", "+1"}) check(expectRun(directory, malformed, 0, 2, {malformed}), "a malformed count skipped");
|
||||
std::puts("frame replay relaunch tests passed");
|
||||
return 0;
|
||||
} catch (const std::exception& error) {
|
||||
std::fprintf(stderr, "FAILED: %s\n", error.what());
|
||||
return argc == 4 ? 2 : 1;
|
||||
}
|
||||
}
|
||||
@@ -2,9 +2,11 @@
|
||||
#include "../tools/FrameReplay.cpp"
|
||||
#undef main
|
||||
#include "prx/libSceAgcDriver/Execution/include/FrameRecorder.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/Presentation.hpp"
|
||||
#include "prx/libc/include/GuestWriteWatch.hpp"
|
||||
#include <string_view>
|
||||
#include <barrier>
|
||||
#include <new>
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -183,6 +185,94 @@ void testEopTokens() {
|
||||
AgcDriverUnregisterVideoOutput_nid_postfix(7, output);
|
||||
}
|
||||
|
||||
class ReservationOutput final : public AgcDriver::IVideoOutput {
|
||||
public:
|
||||
explicit ReservationOutput(Frames& frames) : frames(frames) {}
|
||||
std::shared_ptr<AgcDriver::IFlipRequest> Reserve(const AgcDriver::FlipInfo&) override {
|
||||
++reservations;
|
||||
if (failure) std::rethrow_exception(failure);
|
||||
return available ? std::make_shared<ReplayFlip>(frames) : nullptr;
|
||||
}
|
||||
void Fail(std::exception_ptr error) noexcept override { frames.Failed(error); }
|
||||
std::exception_ptr failure;
|
||||
bool available = true;
|
||||
std::size_t reservations = 0;
|
||||
|
||||
private:
|
||||
Frames& frames;
|
||||
};
|
||||
|
||||
FrameCall reservationCall(std::uint32_t token) {
|
||||
FrameCall call;
|
||||
call.kind = FrameCallKind::ReserveEopFlip;
|
||||
call.values = {7, static_cast<std::uint64_t>(-1), 1, 99, token};
|
||||
return call;
|
||||
}
|
||||
|
||||
template <typename TException>
|
||||
void expectReservationFailure(Replay& replay, const FrameCall& call) {
|
||||
const auto kept = replay.keptReservations.load();
|
||||
bool failed = false;
|
||||
try { replay.runWindow(std::span(&call, 1)); }
|
||||
catch (const TException&) { failed = true; }
|
||||
check(failed, "the replay accepted a failed EOP reservation");
|
||||
check(replay.keptReservations.load() == kept, "a failed EOP reservation was counted as kept");
|
||||
}
|
||||
|
||||
void testRepeatedReservations(const std::filesystem::path& directory, bool driverFailure) {
|
||||
FrameCaptureWriter writer(directory);
|
||||
writer.Finish("{}");
|
||||
const FrameCaptureReader reader(directory);
|
||||
Options options;
|
||||
options.pace = false;
|
||||
options.release = false;
|
||||
Replay replay{options, reader};
|
||||
auto output = std::make_shared<ReservationOutput>(replay.frames);
|
||||
AgcDriverRegisterVideoOutput_nid_postfix(7, output);
|
||||
AgcDriverRegisterVideoOutput_nid_postfix(8, output);
|
||||
const auto call = reservationCall(AgcDriver::EopFlipTokenBase);
|
||||
replay.execute(call);
|
||||
expectReservationFailure<AgcDriver::EopFlipAlreadyReserved>(replay, call);
|
||||
replay.pass = 1;
|
||||
if (driverFailure) {
|
||||
AgcDriverReportFailure_nid_postfix(std::make_exception_ptr(std::runtime_error("reservation driver failure")));
|
||||
expectReservationFailure<std::runtime_error>(replay, call);
|
||||
bool failed = false;
|
||||
try { AgcDriverShutdown_nid_postfix(); }
|
||||
catch (const std::runtime_error& error) { failed = std::string_view(error.what()) == "reservation driver failure"; }
|
||||
check(failed, "the injected driver failure was not propagated at shutdown");
|
||||
return;
|
||||
}
|
||||
replay.runWindow(std::span(&call, 1));
|
||||
check(replay.keptReservations.load() == 1 && output->reservations == 1, "a compatible pending reservation was not kept without allocating again");
|
||||
for (std::size_t field = 0; field < 4; ++field) {
|
||||
auto different = call;
|
||||
++different.values[field];
|
||||
expectReservationFailure<std::runtime_error>(replay, different);
|
||||
}
|
||||
auto missing = call;
|
||||
missing.values[0] = 9;
|
||||
expectReservationFailure<std::runtime_error>(replay, missing);
|
||||
expectReservationFailure<std::runtime_error>(replay, reservationCall(AgcDriver::EopFlipTokenBase - 1));
|
||||
expectReservationFailure<std::runtime_error>(replay, reservationCall(AgcDriver::EopFlipTokenLimit + 1));
|
||||
const auto fresh = reservationCall(AgcDriver::EopFlipTokenBase + 1);
|
||||
output->failure = std::make_exception_ptr(std::bad_alloc{});
|
||||
expectReservationFailure<std::bad_alloc>(replay, fresh);
|
||||
output->failure = std::make_exception_ptr(std::runtime_error("reservation output failure"));
|
||||
expectReservationFailure<std::runtime_error>(replay, fresh);
|
||||
output->failure = nullptr;
|
||||
output->available = false;
|
||||
expectReservationFailure<std::runtime_error>(replay, fresh);
|
||||
output->available = true;
|
||||
AgcDriverUnregisterVideoOutput_nid_postfix(7, output);
|
||||
auto replacement = std::make_shared<ReservationOutput>(replay.frames);
|
||||
AgcDriverRegisterVideoOutput_nid_postfix(7, replacement);
|
||||
replay.runWindow(std::span(&call, 1));
|
||||
check(replay.keptReservations.load() == 1 && replacement->reservations == 1, "a closed output's token was kept after reopening");
|
||||
AgcDriverUnregisterVideoOutput_nid_postfix(7, replacement);
|
||||
AgcDriverUnregisterVideoOutput_nid_postfix(8, output);
|
||||
}
|
||||
|
||||
class WaitingOutput final : public AgcDriver::IVideoOutput {
|
||||
public:
|
||||
WaitingOutput(Frames& frames, const std::uint32_t* label) : frames(frames), label(label) {}
|
||||
@@ -320,6 +410,101 @@ void testShaderCode(const std::filesystem::path& directory, bool host, std::stri
|
||||
check(protectedHeader, "the read-only host shader was recorded with write access");
|
||||
}
|
||||
check(registered && captured == expected, "the complete separate shader code was not captured before registration");
|
||||
CapturedMemory memory(reader);
|
||||
std::map<std::uint64_t, std::vector<const FrameMemoryChunk*>> groups;
|
||||
for (const auto& chunk : reader.Chunks()) {
|
||||
if (chunk.kind == FrameMemoryKind::Delta) groups[chunk.group].push_back(&chunk);
|
||||
}
|
||||
std::optional<std::vector<std::byte>> listed;
|
||||
for (const auto& call : reader.Calls()) {
|
||||
if (call.kind == FrameCallKind::RegisterShader) {
|
||||
listed = memory.Read(address(code), CodeBytes);
|
||||
break;
|
||||
}
|
||||
if (call.kind != FrameCallKind::Memory) continue;
|
||||
if (const auto found = groups.find(call.values.at(0)); found != groups.end()) memory.Apply(found->second);
|
||||
}
|
||||
check(listed && *listed == expected, "the offline shader listing read other code than the capture holds at the registration");
|
||||
}
|
||||
|
||||
void writeInspectionCapture(const std::filesystem::path& directory) {
|
||||
constexpr std::uint64_t Commands = 0x1000000001ull;
|
||||
constexpr std::uint64_t Header = 0x2000000000ull;
|
||||
constexpr std::uint64_t Code = 0x2000010000ull;
|
||||
const std::array<std::uint32_t, 27> commands{
|
||||
0x00010000u, 0xc0053c00u, 0xc0064900u, 0x80000000u, 0xffff1000u,
|
||||
0xc0053c00u, 0x13u, 0x12345678u, 0x9abcdef0u, 0x10203040u, 0xf0f0f0f0u, 4u,
|
||||
0xc0064900u, 1u, 2u, 3u, 4u, 5u, 6u, 7u,
|
||||
0xc0033700u, 0x500u, 0xdeadbeefu, 0x12u, 0x1337u,
|
||||
0x00030000u, 0xc0053c00u};
|
||||
std::array<std::uint8_t, 16> code{0, 1, 2, 3, 4, 5, 6, 7, 255, 128, 13, 10, 39, 36, 38, 0};
|
||||
Shader shader{};
|
||||
shader.code = reinterpret_cast<const void*>(static_cast<std::uintptr_t>(Code));
|
||||
shader.shader_size = code.size();
|
||||
FrameCaptureWriter writer(directory);
|
||||
writer.WriteRange(Commands, 6, FrameRangeKind::Host, ReadWrite, FrameRangeAccess::Readable);
|
||||
writer.WriteRange(Commands + 6, sizeof(commands) - 6, FrameRangeKind::Host, ReadWrite, FrameRangeAccess::Readable);
|
||||
writer.WriteRange(Header, sizeof(shader), FrameRangeKind::Host, ReadWrite, FrameRangeAccess::Readable);
|
||||
writer.WriteRange(Code, code.size(), FrameRangeKind::Host, ReadWrite, FrameRangeAccess::Readable);
|
||||
writer.WriteSnapshot(Header, std::as_bytes(std::span(&shader, 1)));
|
||||
writer.WriteSnapshot(Code, std::as_bytes(std::span(code)));
|
||||
writer.WriteDelta(1, 0, Commands, std::as_bytes(std::span(commands)));
|
||||
const std::uint32_t reference = 0x55667788u;
|
||||
writer.WriteDelta(2, 0, Commands + 9 * sizeof(std::uint32_t), std::as_bytes(std::span(&reference, 1)));
|
||||
code[0] = 0x42;
|
||||
writer.WriteDelta(3, 0, Code, std::as_bytes(std::span(code)));
|
||||
FrameCall call;
|
||||
const auto write = [&](FrameCallKind kind, std::uint64_t location, std::uint64_t size, std::vector<std::uint64_t> values = {}) {
|
||||
call.kind = kind;
|
||||
++call.ordinal;
|
||||
call.address = location;
|
||||
call.size = size;
|
||||
call.values = std::move(values);
|
||||
call.thread = 7;
|
||||
call.queue = 0x42;
|
||||
call.accepted = 12;
|
||||
call.completed = 9;
|
||||
writer.WriteCall(call);
|
||||
};
|
||||
write(FrameCallKind::Submit, Commands, commands.size());
|
||||
write(FrameCallKind::Memory, 0, 0, {1});
|
||||
write(FrameCallKind::RegisterShader, Header, 0);
|
||||
write(FrameCallKind::FrameStart, 0, 0);
|
||||
write(FrameCallKind::Submit, Commands, commands.size());
|
||||
write(FrameCallKind::Memory, 0, 0, {2});
|
||||
write(FrameCallKind::Submit, Commands, commands.size());
|
||||
write(FrameCallKind::RegisterShader, Header, 0);
|
||||
write(FrameCallKind::Memory, 0, 0, {3});
|
||||
write(FrameCallKind::RegisterShader, Header, 0);
|
||||
write(FrameCallKind::Flip, 0, 0);
|
||||
write(FrameCallKind::ReserveEopFlip, 0, 0);
|
||||
write(FrameCallKind::Submit, 0x3000000000ull, 1);
|
||||
write(FrameCallKind::Submit, Commands, std::numeric_limits<std::uint64_t>::max());
|
||||
write(FrameCallKind::Submit, std::numeric_limits<std::uint64_t>::max() - 1, 1);
|
||||
write(FrameCallKind::Submit, Commands, 0);
|
||||
writer.Finish("{}");
|
||||
}
|
||||
|
||||
void testColdPipelines() {
|
||||
const auto* inherited = std::getenv("APS5_HEADLESS_PIPELINE_CACHE");
|
||||
check(inherited != nullptr && std::string_view(inherited) == "1", "pipeline cache setting was not inherited");
|
||||
Options options;
|
||||
options.coldPipelines = true;
|
||||
configureEnvironment(options);
|
||||
check(std::getenv("APS5_HEADLESS_PIPELINE_CACHE") == nullptr, "cold replay kept the inherited pipeline cache setting");
|
||||
check(std::getenv("APS5_FRAME_CAPTURE") == nullptr, "replay kept the capture setting");
|
||||
options.coldPipelines = false;
|
||||
configureEnvironment(options);
|
||||
const auto* warm = std::getenv("APS5_HEADLESS_PIPELINE_CACHE");
|
||||
check(warm != nullptr && std::string_view(warm) == "1", "warm replay did not enable the pipeline cache");
|
||||
#ifdef _WIN32
|
||||
_putenv_s("APS5_HEADLESS_PIPELINE_CACHE", "0");
|
||||
#else
|
||||
setenv("APS5_HEADLESS_PIPELINE_CACHE", "0", 1);
|
||||
#endif
|
||||
options.coldPipelines = true;
|
||||
configureEnvironment(options);
|
||||
check(std::getenv("APS5_HEADLESS_PIPELINE_CACHE") == nullptr, "cold replay kept a present zero pipeline cache setting");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -329,6 +514,10 @@ int main(int argc, char** argv) {
|
||||
check(argc == 3, "expected a test name and capture directory");
|
||||
const std::string mode = argv[1];
|
||||
const std::filesystem::path directory = argv[2];
|
||||
if (mode == "inspect_capture") {
|
||||
writeInspectionCapture(directory);
|
||||
return 0;
|
||||
}
|
||||
if (mode == "order") testMemoryOrder(directory);
|
||||
else if (mode == "zero") testReusedMemory(directory);
|
||||
else if (mode.starts_with("shader_prologue_")) testShaderCode(directory, true, mode);
|
||||
@@ -336,10 +525,12 @@ int main(int argc, char** argv) {
|
||||
else if (mode == "host_neighbors") testHostNeighbors(directory);
|
||||
else if (mode == "unwatched") testUnwatched();
|
||||
else if (mode == "tokens") testEopTokens();
|
||||
else if (mode == "reservations" || mode == "reservation_failure") testRepeatedReservations(directory, mode == "reservation_failure");
|
||||
else if (mode == "blocking") testBlockingSubmission(directory);
|
||||
else if (mode == "environment") testColdPipelines();
|
||||
else if (mode == "shader" || mode == "shader_host") testShaderCode(directory, mode == "shader_host");
|
||||
else throw std::runtime_error("unknown test");
|
||||
AgcDriverShutdown_nid_postfix();
|
||||
if (mode != "reservation_failure") AgcDriverShutdown_nid_postfix();
|
||||
std::puts("frame replay state test passed");
|
||||
return 0;
|
||||
} catch (const std::exception& error) {
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
set(capture "${DIRECTORY}/capture 'quoted' & $scene %APS5_HEADLESS_PIPELINE_CACHE% ! (1)")
|
||||
execute_process(COMMAND "${WRITER}" inspect_capture "${capture}"
|
||||
RESULT_VARIABLE result OUTPUT_VARIABLE output ERROR_VARIABLE error)
|
||||
if(NOT result STREQUAL "0")
|
||||
message(FATAL_ERROR "capture writer failed: ${result}\n${output}\n${error}")
|
||||
endif()
|
||||
if(NOT EXISTS "${capture}/calls.bin")
|
||||
message(FATAL_ERROR "capture path was not passed literally: ${capture}")
|
||||
endif()
|
||||
|
||||
function(runTool expected)
|
||||
execute_process(COMMAND "${TOOL}" "${capture}" ${ARGN}
|
||||
RESULT_VARIABLE result OUTPUT_VARIABLE output ERROR_VARIABLE error)
|
||||
string(REPLACE "\\" "/" error "${error}")
|
||||
if(NOT result STREQUAL "${expected}")
|
||||
message(FATAL_ERROR "agc_frame_replay ${ARGN}: expected ${expected}, got ${result}\n${output}\n${error}")
|
||||
endif()
|
||||
set(output "${output}" PARENT_SCOPE)
|
||||
set(error "${error}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
function(requireContains text needle)
|
||||
string(FIND "${text}" "${needle}" found)
|
||||
if(found EQUAL -1)
|
||||
message(FATAL_ERROR "missing '${needle}' in:\n${text}")
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
if(MODE STREQUAL "submits")
|
||||
runTool(0 --submits 1)
|
||||
requireContains("${output}" "#1 ordinal 5 submit thread 7 queue 0x42 serials 9/12 address 0x1000000001 dwords 27")
|
||||
requireContains("${output}" "@5 WAIT_REG_MEM function 3 space 1 address 0x9abcdef012345678 reference 0x10203040 mask 0xf0f0f0f0")
|
||||
requireContains("${output}" "@12 RELEASE_MEM 00000001 00000002 00000003 00000004 00000005 00000006 00000007")
|
||||
requireContains("${output}" "@20 WRITE_DATA 00000500 deadbeef 00000012 00001337")
|
||||
requireContains("${output}" "@25 (truncated packet: 5 dwords, 2 available)")
|
||||
requireContains("${output}" "packets: RELEASE_MEM x1 WAIT_REG_MEM x1 WRITE_DATA x1")
|
||||
if(output MATCHES "#2|ordinal 1 |reference 0x55667788|NOP|@1 WAIT_REG_MEM")
|
||||
message(FATAL_ERROR "listing included a later submission, prologue or payload packet:\n${output}")
|
||||
endif()
|
||||
runTool(0 --submits 2)
|
||||
requireContains("${output}" "#2 ordinal 7 submit")
|
||||
requireContains("${output}" "reference 0x55667788")
|
||||
runTool(0 --submits 4)
|
||||
requireContains("${output}" "#3 ordinal 11 flip")
|
||||
requireContains("${output}" "#4 ordinal 12 reserve-eop-flip")
|
||||
if(output MATCHES "#5")
|
||||
message(FATAL_ERROR "listing exceeded --submits 4:\n${output}")
|
||||
endif()
|
||||
runTool(0 --submits 8)
|
||||
requireContains("${output}" "#8 ordinal 16 submit")
|
||||
string(REGEX MATCHALL "command buffer not readable" unreadable "${output}")
|
||||
list(LENGTH unreadable count)
|
||||
if(NOT count EQUAL 3)
|
||||
message(FATAL_ERROR "missing unrecorded and overflowing buffer diagnostics:\n${output}")
|
||||
endif()
|
||||
foreach(value IN ITEMS 0 -1 +1 1junk 18446744073709551616)
|
||||
runTool(2 --submits "${value}")
|
||||
requireContains("${error}" "usage: agc_frame_replay")
|
||||
endforeach()
|
||||
runTool(2 --submits)
|
||||
elseif(MODE STREQUAL "shader_output")
|
||||
runTool(0 --shaders)
|
||||
requireContains("${output}" "\"ordinal\":3,\"in_window\":false")
|
||||
requireContains("${output}" "\"ordinal\":8,\"in_window\":true")
|
||||
requireContains("${output}" "\"hash\":\"541dd60be6dc3e20\",\"copy\":2")
|
||||
requireContains("${output}" "\"hash\":\"b6df526defd638c2\",\"copy\":1")
|
||||
requireContains("${output}" "{\"registrations\":3,\"in_window\":2,\"distinct_code\":2,\"unreadable\":0}")
|
||||
set(binaries "${DIRECTORY}/shader code 'quoted' & $scene (2)")
|
||||
runTool(0 --cold-pipelines --shader-code "${binaries}")
|
||||
file(GLOB files "${binaries}/*.bin")
|
||||
list(LENGTH files count)
|
||||
if(NOT count EQUAL 2)
|
||||
message(FATAL_ERROR "expected two distinct shader binaries, got ${count}")
|
||||
endif()
|
||||
file(READ "${binaries}/type0-541dd60be6dc3e20.bin" first HEX)
|
||||
file(READ "${binaries}/type0-b6df526defd638c2.bin" second HEX)
|
||||
if(NOT first STREQUAL "0001020304050607ff800d0a27242600" OR NOT second STREQUAL "4201020304050607ff800d0a27242600")
|
||||
message(FATAL_ERROR "shader binaries differ from the bytes at registration: ${first}, ${second}")
|
||||
endif()
|
||||
set(blocked "${DIRECTORY}/blocked output 'quoted' & $scene (3)")
|
||||
set(blockedFile "${blocked}/type0-541dd60be6dc3e20.bin")
|
||||
file(MAKE_DIRECTORY "${blockedFile}")
|
||||
runTool(2 --shader-code "${blocked}")
|
||||
requireContains("${error}" "cannot open ${blockedFile}")
|
||||
if(output MATCHES "\"registrations\"")
|
||||
message(FATAL_ERROR "failed shader output reported success JSON:\n${output}")
|
||||
endif()
|
||||
if(CMAKE_HOST_SYSTEM_NAME STREQUAL "Linux")
|
||||
set(full "${DIRECTORY}/full output")
|
||||
file(MAKE_DIRECTORY "${full}")
|
||||
set(fullFile "${full}/type0-541dd60be6dc3e20.bin")
|
||||
file(REMOVE "${fullFile}")
|
||||
file(CREATE_LINK /dev/full "${fullFile}" SYMBOLIC RESULT linked)
|
||||
if(NOT linked STREQUAL "0")
|
||||
message(FATAL_ERROR "cannot link /dev/full: ${linked}")
|
||||
endif()
|
||||
runTool(2 --shader-code "${full}")
|
||||
requireContains("${error}" "${fullFile}")
|
||||
if(NOT error MATCHES "cannot (write|flush|close)" OR output MATCHES "\"registrations\"")
|
||||
message(FATAL_ERROR "late shader output failure was not reported:\n${output}\n${error}")
|
||||
endif()
|
||||
endif()
|
||||
else()
|
||||
message(FATAL_ERROR "unknown inspection mode: ${MODE}")
|
||||
endif()
|
||||
@@ -5541,6 +5541,7 @@ struct MockVulkan {
|
||||
std::vector<MockDescriptorWrite> writes;
|
||||
std::map<VkSampler, VkSamplerCreateFlags> samplerFlags;
|
||||
std::uint32_t samplerCreateCount = 0;
|
||||
float samplerLodBias = 0.0f;
|
||||
std::vector<VkCopyDescriptorSet> copies;
|
||||
bool allowCopies = false;
|
||||
std::uint32_t boundSets = 0;
|
||||
@@ -5731,6 +5732,7 @@ VKAPI_ATTR VkResult VKAPI_CALL mockFreeDescriptorSets(VkDevice, VkDescriptorPool
|
||||
VKAPI_ATTR VkResult VKAPI_CALL mockCreateSampler(VkDevice, const VkSamplerCreateInfo* info, const VkAllocationCallbacks*, VkSampler* sampler) {
|
||||
*sampler = makeHandle<VkSampler>();
|
||||
mock.samplerFlags[*sampler] = info->flags;
|
||||
mock.samplerLodBias = info->mipLodBias;
|
||||
++mock.samplerCreateCount;
|
||||
++mock.live;
|
||||
return VK_SUCCESS;
|
||||
@@ -7579,8 +7581,9 @@ void samplerTableDeviceLimitTests(std::optional<std::uint32_t> selected = std::n
|
||||
namespace Abi = ShaderRecompiler::ImageTableAbi;
|
||||
constexpr auto count = 2u * Abi::SamplerEntryElements;
|
||||
const std::array<std::uint32_t, 4> valid{0x92u, (4u * 256u) << 12u, 0u, 0u};
|
||||
const std::array<std::array<std::uint32_t, 4>, 3> invalid{{
|
||||
const std::array<std::array<std::uint32_t, 4>, 4> invalid{{
|
||||
{0x92u, valid[1], 256u, 0u},
|
||||
{0x92u, valid[1], 0x1000u, 0u},
|
||||
{0x92u | (2u << 9u), valid[1], (2u << 20u) | (2u << 22u), 0u},
|
||||
{0x92u | (1u << 29u), valid[1], 0u, 0u}
|
||||
}};
|
||||
@@ -7634,6 +7637,32 @@ void samplerTableDeviceLimitTests(std::optional<std::uint32_t> selected = std::n
|
||||
}
|
||||
}
|
||||
|
||||
void samplerLodBiasTests() {
|
||||
mock = MockVulkan{};
|
||||
auto context = mockContext();
|
||||
context.samplerAnisotropy = true;
|
||||
context.limits.maxSamplerAnisotropy = 2.0f;
|
||||
context.limits.maxSamplerLodBias = 10.0f;
|
||||
const auto hostBias = [&](std::uint32_t field) {
|
||||
const std::array<std::uint32_t, 4> words{0x92u, (4u * 256u) << 12u, field, 0u};
|
||||
AgcDriver::Graphics::Sampler sampler(context, AgcDriver::Graphics::DecodeSamplerResource(words));
|
||||
return mock.samplerLodBias;
|
||||
};
|
||||
Require(hostBias(256u) == 1.0f, "a positive LOD bias within the device limit changed");
|
||||
Require(hostBias(0x3f00u) == -1.0f, "a negative LOD bias within the device limit changed");
|
||||
Require(hostBias(0xa00u) == 10.0f, "a LOD bias at the device limit changed");
|
||||
Require(hostBias(0x1facu) == 0.0f, "a host sampler kept a positive LOD bias the shader applies");
|
||||
Require(hostBias(0x2000u) == 0.0f, "a host sampler kept a negative LOD bias the shader applies");
|
||||
bool rejected = false;
|
||||
try {
|
||||
hostBias(0xf00u);
|
||||
} catch (const std::runtime_error&) {
|
||||
rejected = true;
|
||||
}
|
||||
Require(rejected, "a LOD bias beyond the device limit that the shader does not apply was accepted");
|
||||
Require(mock.live == 0 && mock.samplerFlags.empty(), "the sampler LOD bias checks leaked Vulkan objects");
|
||||
}
|
||||
|
||||
void hostImportBudgetTests() {
|
||||
using AgcDriver::Graphics::HostImportBudget;
|
||||
constexpr std::uint64_t MiB = 1ull << 20u;
|
||||
@@ -13538,7 +13567,7 @@ int main(int argc, char** argv) {
|
||||
imageTableSnapshotAllocationTests();
|
||||
return 0;
|
||||
}
|
||||
const std::array<std::string_view, 3> samplerCases{"--sampler-table-lod-bias", "--sampler-table-anisotropy", "--sampler-table-reduction-compare"};
|
||||
const std::array<std::string_view, 4> samplerCases{"--sampler-table-lod-bias", "--sampler-table-lod-bias-beyond-threshold", "--sampler-table-anisotropy", "--sampler-table-reduction-compare"};
|
||||
for (std::uint32_t index = 0; index < samplerCases.size(); ++index) {
|
||||
if (argc == 2 && std::string_view(argv[1]) == samplerCases[index]) {
|
||||
samplerTableDeviceLimitTests(index);
|
||||
@@ -13707,6 +13736,7 @@ int main(int argc, char** argv) {
|
||||
imageTableAllocationTests();
|
||||
imageTableSnapshotAllocationTests();
|
||||
samplerTableNonSeamlessCubeTests();
|
||||
samplerLodBiasTests();
|
||||
samplerTableDeviceLimitTests();
|
||||
tableUpdateAfterBindTests();
|
||||
misalignedShaderDataTests();
|
||||
|
||||
@@ -257,6 +257,8 @@ void testContextAndBases() {
|
||||
AgcDriver::Pm4::Validate(makePacket(0x16, {0, 0x49}), 0);
|
||||
expectFailure([&] { AgcDriver::Pm4::Validate(makePacket(0x15, {1, 1, 1, 0x51}), 0); }, "dispatch modifiers");
|
||||
expectFailure([&] { AgcDriver::Pm4::Validate(makePacket(0x16, {0, 0x51}), 0); }, "indirect dispatch modifiers");
|
||||
expectFailure([&] { AgcDriver::Pm4::Validate(makePacket(0x26, {0x1001, 1}), 0); }, "unsupported index base alignment or address bits");
|
||||
expectFailure([&] { AgcDriver::Pm4::Validate(makePacket(0x26, {0x1000, 0x10000}), 0); }, "unsupported index base alignment or address bits");
|
||||
execute(state, makePacket(0x13, {32}));
|
||||
execute(state, makePacket(0x26, {0x1000, 1}));
|
||||
execute(state, makePacket(0x2a, {1}));
|
||||
@@ -448,6 +450,8 @@ void testIndirectDraw() {
|
||||
const auto counted = AgcDriver::Pm4::ResolveDraw(makePacket(0x2c, {0, 0x280, 0x280, 0x280 | (1u << 30u), 5, low(&countValue), high(&countValue), 16, 2}), state);
|
||||
check(counted.indirect->countIndirect && counted.indirect->count == 5 && counted.indirect->countAddress == reinterpret_cast<std::uintptr_t>(&countValue) && AgcDriver::Pm4::ReadDrawCount(*counted.indirect) == 2, "indirect draw count mismatch");
|
||||
expectFailure([&] { AgcDriver::Pm4::ReadDrawCount(*local.indirect); }, "count address");
|
||||
expectFailure([&] { AgcDriver::Pm4::Validate(makePacket(0x2c, {0, 0x280, 0x280, 0x280 | (1u << 30u), 5, low(&countValue), 0x10000, 16, 2}), 0); }, "invalid indirect draw count address");
|
||||
expectFailure([&] { AgcDriver::Pm4::Validate(makePacket(0x38, {0, 0x280, 0x280, 0x280 | (1u << 30u), 5, low(&countValue), 0x10000, 20, 0}), 0); }, "invalid indirect draw count address");
|
||||
}
|
||||
|
||||
bool sameDraw(const AgcDriver::Pm4::DrawParameters& left, const AgcDriver::Pm4::DrawParameters& right) {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#include "prx/libSceAgcDriver/Execution/include/Screenshot.hpp"
|
||||
#include "Decoder/Png.hpp"
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <exception>
|
||||
@@ -11,6 +13,7 @@
|
||||
#include <random>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
@@ -54,6 +57,30 @@ void writesRgbPng(const std::filesystem::path& directory) {
|
||||
require(image->pixels == expected, "the screenshot holds the frame's colors as opaque RGB");
|
||||
}
|
||||
|
||||
void concurrentWritesClaimUniquePaths() {
|
||||
constexpr std::size_t writerCount = 32;
|
||||
constexpr int attempts = 20;
|
||||
const std::array<std::byte, 4> bgra{std::byte{0x10}, std::byte{0x20}, std::byte{0x30}, std::byte{0x00}};
|
||||
for (int attempt = 0; attempt < attempts; ++attempt) {
|
||||
std::atomic<std::size_t> ready{0};
|
||||
std::atomic<bool> start{false};
|
||||
std::array<std::filesystem::path, writerCount> paths;
|
||||
std::array<std::thread, writerCount> writers;
|
||||
for (std::size_t index = 0; index < writerCount; ++index) {
|
||||
writers[index] = std::thread([&, index] {
|
||||
ready.fetch_add(1, std::memory_order_release);
|
||||
while (!start.load(std::memory_order_acquire)) std::this_thread::yield();
|
||||
paths[index] = AgcDriver::Screenshot::Write("RACE00001", 1, 1, bgra);
|
||||
});
|
||||
}
|
||||
while (ready.load(std::memory_order_acquire) != writerCount) std::this_thread::yield();
|
||||
start.store(true, std::memory_order_release);
|
||||
for (auto& writer : writers) writer.join();
|
||||
std::sort(paths.begin(), paths.end());
|
||||
require(std::adjacent_find(paths.begin(), paths.end()) == paths.end(), "concurrent screenshots claim unique paths");
|
||||
}
|
||||
}
|
||||
|
||||
void rejectsInvalidFrames() {
|
||||
const std::array<std::byte, 4> pixel{};
|
||||
const auto rejects = [](auto write) {
|
||||
@@ -78,6 +105,7 @@ int main() try {
|
||||
setScreenshotDirectory(directory);
|
||||
requestsMerge();
|
||||
writesRgbPng(directory);
|
||||
concurrentWritesClaimUniquePaths();
|
||||
rejectsInvalidFrames();
|
||||
std::filesystem::remove_all(root);
|
||||
std::cout << "agc screenshot tests passed\n";
|
||||
|
||||
@@ -2454,6 +2454,82 @@ void verifyAllocationChurnSnapshot() {
|
||||
}
|
||||
}
|
||||
|
||||
void verifyRegisteredViewsUnderUnrelatedChurn() {
|
||||
using namespace ShaderRecompiler;
|
||||
constexpr std::size_t RangeBytes = 65536u;
|
||||
constexpr std::uint32_t Checks = 20000u;
|
||||
alignas(4096) static std::array<std::uint8_t, RangeBytes> texels{};
|
||||
alignas(4096) static std::array<std::uint8_t, 4096> unrelated{};
|
||||
{
|
||||
GuestAllocations::Mutation mutation;
|
||||
mutation.Add(texels.data(), texels.size(), true, false, true);
|
||||
}
|
||||
const auto view = [] {
|
||||
const auto range = GuestAllocations::GuestAllocationsLookup_nid_postfix(reinterpret_cast<std::uintptr_t>(texels.data()), false);
|
||||
require(range != nullptr, "registered view churn: the image range was not registered");
|
||||
RegisteredImageView captured;
|
||||
captured.address = reinterpret_cast<std::uintptr_t>(texels.data()) + 256u;
|
||||
captured.hasRange = true;
|
||||
captured.rangeAddress = range->address;
|
||||
captured.rangeBytes = range->bytes;
|
||||
captured.allocationAddress = range->allocationAddress;
|
||||
captured.allocationBytes = range->allocationBytes;
|
||||
captured.backingSerial = range->backingSerial;
|
||||
captured.readable = range->readable;
|
||||
captured.writable = range->writable;
|
||||
captured.gpu = range->gpu;
|
||||
return captured;
|
||||
}();
|
||||
std::array<RegisteredImageView, 3> views{view, view, view};
|
||||
views[1].address = view.address + 4096u;
|
||||
views[2].address = view.address + 8192u;
|
||||
std::atomic<bool> stop{false};
|
||||
std::atomic<std::uint64_t> mutations{0};
|
||||
std::thread churn([&] {
|
||||
while (!stop.load(std::memory_order_acquire)) {
|
||||
{
|
||||
GuestAllocations::Mutation mutation;
|
||||
mutation.Add(unrelated.data(), unrelated.size(), true, true, true);
|
||||
}
|
||||
{
|
||||
GuestAllocations::Mutation mutation;
|
||||
mutation.Remove(unrelated.data());
|
||||
}
|
||||
mutations.fetch_add(2u, std::memory_order_relaxed);
|
||||
}
|
||||
});
|
||||
std::uint32_t rejected = 0u;
|
||||
const auto generationBefore = GuestAllocations::GuestAllocationsGeneration_nid_postfix();
|
||||
while (mutations.load(std::memory_order_relaxed) < 64u) std::this_thread::yield();
|
||||
for (std::uint32_t check = 0; check < Checks; ++check) {
|
||||
if (!AgcDriver::DriverDetail::CaptureRegisteredViewsCurrent(views)) ++rejected;
|
||||
}
|
||||
stop.store(true, std::memory_order_release);
|
||||
churn.join();
|
||||
const auto generationAfter = GuestAllocations::GuestAllocationsGeneration_nid_postfix();
|
||||
require(generationAfter - generationBefore >= 64u, "registered view churn: the unrelated mappings did not move the allocation generation");
|
||||
const auto rejectedMessage = "registered view churn: " + std::to_string(rejected) + " of " + std::to_string(Checks) + " checks rejected views whose own range never changed while only an unrelated allocation moved";
|
||||
require(rejected == 0u, rejectedMessage.c_str());
|
||||
{
|
||||
GuestAllocations::Mutation mutation;
|
||||
mutation.Remove(texels.data());
|
||||
}
|
||||
require(!AgcDriver::DriverDetail::CaptureRegisteredViewsCurrent(views), "registered view churn: views were accepted after their range was removed");
|
||||
{
|
||||
GuestAllocations::Mutation mutation;
|
||||
mutation.Add(texels.data(), texels.size(), true, false, true);
|
||||
}
|
||||
require(!AgcDriver::DriverDetail::CaptureRegisteredViewsCurrent(views), "registered view churn: views were accepted after their range was mapped again with a new backing");
|
||||
views[0].backingSerial = GuestAllocations::GuestAllocationsLookup_nid_postfix(reinterpret_cast<std::uintptr_t>(texels.data()), false)->backingSerial;
|
||||
require(!AgcDriver::DriverDetail::CaptureRegisteredViewsCurrent(views), "registered view churn: views were accepted while one of them still names the old backing");
|
||||
views[1].backingSerial = views[2].backingSerial = views[0].backingSerial;
|
||||
require(AgcDriver::DriverDetail::CaptureRegisteredViewsCurrent(views), "registered view churn: views matching the new backing were rejected");
|
||||
{
|
||||
GuestAllocations::Mutation mutation;
|
||||
mutation.Remove(texels.data());
|
||||
}
|
||||
}
|
||||
|
||||
void verifySnapshotAllocationMoves() {
|
||||
using namespace ShaderRecompiler;
|
||||
constexpr std::size_t RangeBytes = 65536u;
|
||||
@@ -9949,6 +10025,42 @@ void verifyUnusedUnnormalizedSampler() {
|
||||
require(msaaArrayAllocation.bindings.size() == 1u && msaaArrayAllocation.bindings[0].imageShape == DescriptorImageShape::Image2DMultisampleArray, "multisampled array image resource did not receive Image2DMultisampleArray descriptor shape");
|
||||
}
|
||||
|
||||
void verifyDsSwizzleFftMode() {
|
||||
using namespace ShaderRecompiler;
|
||||
static constexpr std::array<std::uint32_t, 7> capabilities{spv::CapabilityShader, spv::CapabilityInt64, spv::CapabilityPhysicalStorageBufferAddresses, spv::CapabilityStorageBuffer8BitAccess, spv::CapabilityGroupNonUniform, spv::CapabilityGroupNonUniformBallot, spv::CapabilityGroupNonUniformShuffle};
|
||||
static constexpr std::array<std::string_view, 2> extensions{"SPV_KHR_physical_storage_buffer", "SPV_KHR_8bit_storage"};
|
||||
static std::array<std::uint32_t, 32> output{};
|
||||
const auto address = static_cast<std::uint64_t>(reinterpret_cast<std::uintptr_t>(output.data()));
|
||||
const std::array<std::uint32_t, 4> userData{static_cast<std::uint32_t>(address), static_cast<std::uint32_t>((address >> 32u) & 0xffffu), 128u, 0x31016facu};
|
||||
const auto compiled = [&userData](std::uint32_t offset) {
|
||||
const std::vector<std::uint32_t> code{0xd8d40000u | offset, 0x01000000u, 0x34040082u, 0xbf8cc07fu, 0xe0701000u, 0x80000102u, 0xbf810000u};
|
||||
RecompileRequest request{};
|
||||
request.shader = {ShaderStage::Compute, 0x26000u, code, 0, {}};
|
||||
request.context.waveSize = 32;
|
||||
request.context.userDataBaseRegister = 0;
|
||||
request.context.userData = userData;
|
||||
request.context.compute = ShaderComputeStageInfo{{32u, 1u, 1u}, 0u, {false, false, false}, false, 1u};
|
||||
request.target.vulkanVersion = 0x00401000u;
|
||||
request.target.spirvVersion = 0x00010300u;
|
||||
request.target.subgroupSize = 32u;
|
||||
request.target.supportedCapabilities = capabilities;
|
||||
request.target.supportedExtensions = extensions;
|
||||
request.target.bdaAbiVersion = BdaAbi::Version;
|
||||
request.layout.pushConstantSizeBytes = 128;
|
||||
request.useCache = false;
|
||||
return Recompile(request).spirv.Copy();
|
||||
};
|
||||
const auto reversesLanes = [](std::span<const std::uint32_t> spirv) {
|
||||
for (std::size_t cursor = 5; cursor < spirv.size() && (spirv[cursor] >> 16u) != 0u; cursor += spirv[cursor] >> 16u) {
|
||||
if ((spirv[cursor] & 0xffffu) == spv::OpBitReverse) return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
for (const auto offset : {0xe000u, 0xe010u, 0xe018u, 0xe001u}) require(reversesLanes(compiled(offset)), "ds_swizzle FFT mode: an offset with a mask below 0x1f did not reverse the lane bits");
|
||||
for (const auto offset : {0xe01fu, 0xc000u, 0xc021u, 0xdfffu}) require(!reversesLanes(compiled(offset)), "ds_swizzle FFT mode: mask 0x1f or a rotate offset reversed the lane bits");
|
||||
require(compiled(0xffe0u) == compiled(0xe000u) && compiled(0xf550u) == compiled(0xe010u), "ds_swizzle FFT mode: offset bits 12:5 changed the swizzle");
|
||||
}
|
||||
|
||||
void verifyWaveUniformValues() {
|
||||
using namespace ShaderRecompiler;
|
||||
IrProgram program;
|
||||
@@ -11581,6 +11693,11 @@ int main(int argc, char** argv) {
|
||||
std::cout << "Image table keys, maps, records and registration passed\n";
|
||||
return 0;
|
||||
}
|
||||
if (argc == 2 && std::string_view(argv[1]) == "--registered-view-churn") {
|
||||
verifyRegisteredViewsUnderUnrelatedChurn();
|
||||
std::cout << "Registered image views stay current while unrelated allocations change\n";
|
||||
return 0;
|
||||
}
|
||||
if (argc == 2 && std::string_view(argv[1]) == "--nested-snapshot-retry") {
|
||||
verifyNestedSnapshotRetry();
|
||||
std::cout << "Nested snapshot retry preserves parent and child coverage\n";
|
||||
@@ -11709,6 +11826,7 @@ int main(int argc, char** argv) {
|
||||
verifyUnnormalizedSamplers();
|
||||
verifyUnusedUnnormalizedSampler();
|
||||
verifyNullVolumeUnnormalizedSampler();
|
||||
verifyDsSwizzleFftMode();
|
||||
verifyWaveUniformValues();
|
||||
verifyTwoLaneUniformValues();
|
||||
verifyBdaReadFallbackFunctions();
|
||||
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
if(BUILD_TESTING AND WIN32)
|
||||
add_test_executable(agc_driver_frame_replay_relaunch_tests tests/FrameReplayRelaunch.cpp Execution/src/FrameCapture.cpp)
|
||||
target_include_directories(agc_driver_frame_replay_relaunch_tests PRIVATE ${LIBS_INCLUDE_DIR} ${ANYPS5_VULKAN_HEADERS_INCLUDE_DIR})
|
||||
target_link_libraries(agc_driver_frame_replay_relaunch_tests PRIVATE libSceAgcDriver libkernel libc libzstd_static)
|
||||
target_link_options(agc_driver_frame_replay_relaunch_tests PRIVATE -Wl,--disable-high-entropy-va)
|
||||
set_target_properties(agc_driver_frame_replay_relaunch_tests PROPERTIES CXX_EXTENSIONS OFF)
|
||||
configure_windows_unwind(agc_driver_frame_replay_relaunch_tests)
|
||||
add_test(NAME agc_driver_frame_replay_relaunch COMMAND agc_driver_frame_replay_relaunch_tests "${CMAKE_CURRENT_BINARY_DIR}/frame replay relaunch")
|
||||
set_tests_properties(agc_driver_frame_replay_relaunch PROPERTIES
|
||||
TIMEOUT 120 SKIP_RETURN_CODE 77
|
||||
ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
|
||||
endif()
|
||||
+18
-2
@@ -1,10 +1,22 @@
|
||||
if(BUILD_TESTING)
|
||||
add_test_executable(agc_driver_frame_replay_state_tests tests/FrameReplayState.cpp Execution/src/FrameCapture.cpp)
|
||||
target_include_directories(agc_driver_frame_replay_state_tests PRIVATE ${LIBS_INCLUDE_DIR})
|
||||
target_include_directories(agc_driver_frame_replay_state_tests PRIVATE ${LIBS_INCLUDE_DIR} ${ANYPS5_VULKAN_HEADERS_INCLUDE_DIR})
|
||||
target_link_libraries(agc_driver_frame_replay_state_tests PRIVATE libSceAgcDriver libkernel libc libzstd_static)
|
||||
set_target_properties(agc_driver_frame_replay_state_tests PROPERTIES CXX_EXTENSIONS OFF)
|
||||
configure_windows_unwind(agc_driver_frame_replay_state_tests)
|
||||
set(agcFrameReplayModes order tokens blocking)
|
||||
add_dependencies(agc_driver_frame_replay_state_tests agc_frame_replay)
|
||||
foreach(mode IN ITEMS submits shader_output)
|
||||
add_test(NAME agc_driver_frame_replay_${mode} COMMAND ${CMAKE_COMMAND}
|
||||
"-DTOOL=$<TARGET_FILE:agc_frame_replay>" "-DWRITER=$<TARGET_FILE:agc_driver_frame_replay_state_tests>"
|
||||
"-DDIRECTORY=${CMAKE_CURRENT_BINARY_DIR}/frame-replay-${mode}" "-DMODE=${mode}"
|
||||
-P "${CMAKE_CURRENT_SOURCE_DIR}/tests/FrameReplayTool.cmake")
|
||||
set_tests_properties(agc_driver_frame_replay_${mode} PROPERTIES TIMEOUT 30)
|
||||
if(WIN32)
|
||||
set_tests_properties(agc_driver_frame_replay_${mode} PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
|
||||
endif()
|
||||
endforeach()
|
||||
set(agcFrameReplayModes order tokens blocking reservations reservation_failure environment)
|
||||
if(WIN32 OR CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
||||
list(APPEND agcFrameReplayModes zero shader shader_host shader_prologue_code shader_prologue_header shader_prologue_readonly)
|
||||
endif()
|
||||
@@ -26,6 +38,10 @@ if(BUILD_TESTING)
|
||||
set_tests_properties(agc_driver_frame_replay_${mode} PROPERTIES ENVIRONMENT
|
||||
"APS5_NO_WRITE_WATCH=1;APS5_FRAME_CAPTURE=${capture};APS5_FRAME_CAPTURE_AT_FLIP=1;APS5_FRAME_CAPTURE_FRAMES=1;APS5_FRAME_CAPTURE_MIN_FREE_BYTES=0")
|
||||
endif()
|
||||
if(mode STREQUAL "environment")
|
||||
set_tests_properties(agc_driver_frame_replay_${mode} PROPERTIES ENVIRONMENT
|
||||
"APS5_HEADLESS_PIPELINE_CACHE=1;APS5_FRAME_CAPTURE=${capture};APS5_FRAME_CAPTURE_MIN_FREE_BYTES=0")
|
||||
endif()
|
||||
if(WIN32)
|
||||
set_tests_properties(agc_driver_frame_replay_${mode} PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
|
||||
|
||||
@@ -1,23 +1,44 @@
|
||||
if(BUILD_TESTING)
|
||||
if(WIN32 OR CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
||||
add_test_executable(agc_driver_frame_replay_tests tests/FrameReplay.cpp Execution/src/FrameCapture.cpp)
|
||||
target_include_directories(agc_driver_frame_replay_tests PRIVATE ${LIBS_INCLUDE_DIR})
|
||||
target_include_directories(agc_driver_frame_replay_tests PRIVATE
|
||||
${LIBS_INCLUDE_DIR}
|
||||
${CMAKE_SOURCE_DIR}/core/shader/recompiler
|
||||
${ANYPS5_VULKAN_HEADERS_INCLUDE_DIR}
|
||||
${ANYPS5_SPIRV_HEADERS_INCLUDE_DIR}
|
||||
)
|
||||
target_link_libraries(agc_driver_frame_replay_tests PRIVATE libSceAgc libSceAgcDriver libkernel libc libzstd_static)
|
||||
set_target_properties(agc_driver_frame_replay_tests PROPERTIES CXX_EXTENSIONS OFF)
|
||||
configure_windows_unwind(agc_driver_frame_replay_tests)
|
||||
add_dependencies(agc_driver_frame_replay_tests agc_frame_replay)
|
||||
add_test(NAME agc_driver_frame_replay COMMAND agc_driver_frame_replay_tests $<TARGET_FILE:agc_frame_replay>)
|
||||
set_tests_properties(agc_driver_frame_replay PROPERTIES
|
||||
ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_FRAME_CAPTURE=${CMAKE_CURRENT_BINARY_DIR}/frame-replay-capture;APS5_FRAME_CAPTURE_AT_FLIP=1;APS5_FRAME_CAPTURE_FRAMES=2;APS5_FRAME_CAPTURE_MIN_FREE_BYTES=0"
|
||||
TIMEOUT 120)
|
||||
ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_FRAME_CAPTURE=${CMAKE_CURRENT_BINARY_DIR}/frame replay capture;APS5_FRAME_CAPTURE_AT_FLIP=1;APS5_FRAME_CAPTURE_FRAMES=2;APS5_FRAME_CAPTURE_MIN_FREE_BYTES=0"
|
||||
TIMEOUT 120 SKIP_RETURN_CODE 77)
|
||||
if(WIN32)
|
||||
set_tests_properties(agc_driver_frame_replay PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
|
||||
endif()
|
||||
add_test(NAME agc_driver_frame_replay_cold_pipelines COMMAND agc_driver_frame_replay_tests $<TARGET_FILE:agc_frame_replay> pipelines)
|
||||
set_tests_properties(agc_driver_frame_replay_cold_pipelines PROPERTIES
|
||||
ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=0;APS5_HEADLESS_PIPELINE_CACHE=1;ANYPS5_SHADER_CACHE_DIR=${CMAKE_CURRENT_BINARY_DIR}/frame replay pipeline cache;APS5_FRAME_CAPTURE=${CMAKE_CURRENT_BINARY_DIR}/frame replay cold capture;APS5_FRAME_CAPTURE_AT_FLIP=1;APS5_FRAME_CAPTURE_FRAMES=2;APS5_FRAME_CAPTURE_MIN_FREE_BYTES=0"
|
||||
TIMEOUT 30 SKIP_RETURN_CODE 77)
|
||||
if(WIN32)
|
||||
set_tests_properties(agc_driver_frame_replay_cold_pipelines PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
|
||||
endif()
|
||||
add_test(NAME agc_driver_frame_replay_reservation_error COMMAND agc_driver_frame_replay_tests $<TARGET_FILE:agc_frame_replay> reservation_error)
|
||||
set_tests_properties(agc_driver_frame_replay_reservation_error PROPERTIES
|
||||
ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_FRAME_CAPTURE=${CMAKE_CURRENT_BINARY_DIR}/frame replay reservation error;APS5_FRAME_CAPTURE_MIN_FREE_BYTES=0"
|
||||
TIMEOUT 30 SKIP_RETURN_CODE 77)
|
||||
if(WIN32)
|
||||
set_tests_properties(agc_driver_frame_replay_reservation_error PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
|
||||
endif()
|
||||
add_test(NAME agc_driver_frame_replay_concurrent COMMAND agc_driver_frame_replay_tests $<TARGET_FILE:agc_frame_replay> concurrent)
|
||||
set_tests_properties(agc_driver_frame_replay_concurrent PROPERTIES
|
||||
ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_FRAME_CAPTURE=${CMAKE_CURRENT_BINARY_DIR}/frame-replay-concurrent-capture;APS5_FRAME_CAPTURE_AT_FLIP=1;APS5_FRAME_CAPTURE_FRAMES=8;APS5_FRAME_CAPTURE_MIN_FREE_BYTES=0"
|
||||
TIMEOUT 120)
|
||||
TIMEOUT 120 SKIP_RETURN_CODE 77)
|
||||
if(WIN32)
|
||||
set_tests_properties(agc_driver_frame_replay_concurrent PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
if(BUILD_TESTING)
|
||||
add_test(NAME agc_driver_sdwa_sin_cos_f32_wave64 COMMAND agc_driver_sdwa_sin_cos_f32_tests wave64)
|
||||
set_tests_properties(agc_driver_sdwa_sin_cos_f32_wave64 PROPERTIES
|
||||
ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" SKIP_RETURN_CODE 77 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}")
|
||||
if(WIN32)
|
||||
set_tests_properties(agc_driver_sdwa_sin_cos_f32_wave64 PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
|
||||
endif()
|
||||
endif()
|
||||
@@ -30,6 +30,11 @@ if(BUILD_TESTING)
|
||||
if(WIN32)
|
||||
set_tests_properties(agc_shader_descriptor_phis PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
|
||||
endif()
|
||||
add_test(NAME agc_shader_registered_view_churn COMMAND agc_shader_memory_tests --registered-view-churn)
|
||||
set_tests_properties(agc_shader_registered_view_churn PROPERTIES TIMEOUT 60)
|
||||
if(WIN32)
|
||||
set_tests_properties(agc_shader_registered_view_churn PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
|
||||
endif()
|
||||
add_test(NAME agc_shader_nested_snapshot_retry COMMAND agc_shader_memory_tests --nested-snapshot-retry)
|
||||
set_tests_properties(agc_shader_nested_snapshot_retry PROPERTIES TIMEOUT 20)
|
||||
if(WIN32)
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
if(BUILD_TESTING)
|
||||
add_test_executable(buffer_atomic_csub_translation_tests ${LIBS_INCLUDE_DIR}/tests/BufferAtomicCsubTranslation.cpp)
|
||||
target_link_libraries(buffer_atomic_csub_translation_tests PRIVATE shader_recompiler_agc_driver libc)
|
||||
configure_windows_unwind(buffer_atomic_csub_translation_tests)
|
||||
add_test(NAME buffer_atomic_csub_translation COMMAND buffer_atomic_csub_translation_tests)
|
||||
if(WIN32)
|
||||
set_tests_properties(buffer_atomic_csub_translation PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
|
||||
endif()
|
||||
endif()
|
||||
@@ -168,16 +168,10 @@ void Check(const Workgroup& workgroup) {
|
||||
}
|
||||
}
|
||||
|
||||
void CheckNoReturnRejected(const AgcDriver::VulkanDevice& device) {
|
||||
void CheckNoReturnAccepted(const AgcDriver::VulkanDevice& device) {
|
||||
const auto userData = UserData();
|
||||
const std::string reason = "buffer_atomic_csub without glc is not supported";
|
||||
std::string failure;
|
||||
try {
|
||||
static_cast<void>(Recompile(device, NoReturnCode, Workgroups[0], userData));
|
||||
} catch (const std::exception& error) {
|
||||
failure = error.what();
|
||||
}
|
||||
Require(failure.find(reason) != std::string::npos, "buffer atomic csub: expected '" + reason + "', got '" + failure + "'");
|
||||
const auto recompiled = Recompile(device, NoReturnCode, Workgroups[0], userData);
|
||||
Require(!recompiled.spirv.empty(), "buffer atomic csub: expected valid SPIR-V for instruction without glc");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -190,7 +184,7 @@ int main() {
|
||||
Run(*device, workgroup);
|
||||
Check(workgroup);
|
||||
}
|
||||
CheckNoReturnRejected(*device);
|
||||
CheckNoReturnAccepted(*device);
|
||||
std::puts("buffer atomic csub tests passed");
|
||||
return 0;
|
||||
} catch (const std::exception& error) {
|
||||
|
||||
@@ -51,6 +51,34 @@ alignas(256) constexpr std::array<std::uint32_t, 84> Wave64Code{
|
||||
0x80011203, 0xe0781030, 0x80011603, 0xbf810000,
|
||||
};
|
||||
|
||||
alignas(256) constexpr std::array<std::uint32_t, 82> FftWave32Code{
|
||||
0x34020084, 0x34060086, 0xe0381000, 0x80000401, 0xbf8c3f70, 0x7e1402ff, 0xdeaddead, 0x7e1602ff,
|
||||
0xdeaddead, 0x7e1802ff, 0xdeaddead, 0x7e1a02ff, 0xdeaddead, 0x7e1c02ff, 0xdeaddead, 0x7e1e02ff,
|
||||
0xdeaddead, 0x7e2002ff, 0xdeaddead, 0x7e2202ff, 0xdeaddead, 0x7e2402ff, 0xdeaddead, 0x7e2602ff,
|
||||
0xdeaddead, 0x7e2802ff, 0xdeaddead, 0x7e2a02ff, 0xdeaddead, 0x7e2c02ff, 0xdeaddead, 0x7e2e02ff,
|
||||
0xdeaddead, 0x7e3002ff, 0xdeaddead, 0x7e3202ff, 0xdeaddead, 0xd8d4e000, 0x0a000004, 0xd8d4e010,
|
||||
0x0b000004, 0xd8d4e01f, 0x0c000004, 0xd8d4e018, 0x0d000004, 0xd8d4e01c, 0x0e000004, 0xd8d4e01e,
|
||||
0x0f000004, 0xd8d4e001, 0x10000004, 0xd8d4e00f, 0x11000004, 0xd8d4e015, 0x12000004, 0xd8d4e008,
|
||||
0x13000004, 0xd8d4ffe0, 0x14000004, 0xd8d4f550, 0x15000004, 0xbefe03ff, 0x33cc0ff3, 0xd8d4e000,
|
||||
0x16000004, 0xd8d4e010, 0x17000004, 0xd8d4e018, 0x18000004, 0xd8d4e001, 0x19000004, 0xbf8cc07f,
|
||||
0xbefe03c1, 0xe0781000, 0x80010a03, 0xe0781010, 0x80010e03, 0xe0781020, 0x80011203, 0xe0781030,
|
||||
0x80011603, 0xbf810000,
|
||||
};
|
||||
|
||||
alignas(256) constexpr std::array<std::uint32_t, 84> FftWave64Code{
|
||||
0x34020084, 0x34060086, 0xe0381000, 0x80000401, 0xbf8c3f70, 0x7e1402ff, 0xdeaddead, 0x7e1602ff,
|
||||
0xdeaddead, 0x7e1802ff, 0xdeaddead, 0x7e1a02ff, 0xdeaddead, 0x7e1c02ff, 0xdeaddead, 0x7e1e02ff,
|
||||
0xdeaddead, 0x7e2002ff, 0xdeaddead, 0x7e2202ff, 0xdeaddead, 0x7e2402ff, 0xdeaddead, 0x7e2602ff,
|
||||
0xdeaddead, 0x7e2802ff, 0xdeaddead, 0x7e2a02ff, 0xdeaddead, 0x7e2c02ff, 0xdeaddead, 0x7e2e02ff,
|
||||
0xdeaddead, 0x7e3002ff, 0xdeaddead, 0x7e3202ff, 0xdeaddead, 0xd8d4e000, 0x0a000004, 0xd8d4e010,
|
||||
0x0b000004, 0xd8d4e01f, 0x0c000004, 0xd8d4e018, 0x0d000004, 0xd8d4e01c, 0x0e000004, 0xd8d4e01e,
|
||||
0x0f000004, 0xd8d4e001, 0x10000004, 0xd8d4e00f, 0x11000004, 0xd8d4e015, 0x12000004, 0xd8d4e008,
|
||||
0x13000004, 0xd8d4ffe0, 0x14000004, 0xd8d4f550, 0x15000004, 0xbefe03ff, 0x33cc0ff3, 0xbeff03ff,
|
||||
0xc3a5960f, 0xd8d4e000, 0x16000004, 0xd8d4e010, 0x17000004, 0xd8d4e018, 0x18000004, 0xd8d4e001,
|
||||
0x19000004, 0xbf8cc07f, 0xbefe04c1, 0xe0781000, 0x80010a03, 0xe0781010, 0x80010e03, 0xe0781020,
|
||||
0x80011203, 0xe0781030, 0x80011603, 0xbf810000,
|
||||
};
|
||||
|
||||
constexpr std::uint32_t Rows[64][4] = {
|
||||
{0x1e7ea419u, 0x00000000u, 0x00000000u, 0x00000000u},
|
||||
{0x51c9bc70u, 0x00000000u, 0x00000000u, 0x00000000u},
|
||||
@@ -219,7 +247,109 @@ constexpr std::uint32_t Expected64[64][16] = {
|
||||
{0x89be9c1cu, 0xbef7e268u, 0x16f44881u, 0x1c593af5u, 0x8eb5140fu, 0x16f44881u, 0x16f44881u, 0x1c593af5u, 0x57241955u, 0x8eb5140fu, 0x8eb5140fu, 0xd0944996u, 0xbef7e268u, 0x00000000u, 0x00000000u, 0x00000000u}
|
||||
};
|
||||
|
||||
constexpr std::uint32_t FftExpected32[32][16] = {
|
||||
{0x1e7ea419u, 0x1e7ea419u, 0x1e7ea419u, 0x1e7ea419u, 0x1e7ea419u, 0x1e7ea419u, 0x1e7ea419u, 0x1e7ea419u, 0x1e7ea419u, 0x1e7ea419u, 0x1e7ea419u, 0x1e7ea419u, 0x1e7ea419u, 0x1e7ea419u, 0x1e7ea419u, 0x1e7ea419u},
|
||||
{0xc88b2875u, 0x1a466884u, 0x51c9bc70u, 0x8306d03bu, 0x80a4df5au, 0x51c9bc70u, 0xe255accbu, 0x51c9bc70u, 0xf38b2ffcu, 0x1a466884u, 0xc88b2875u, 0x1a466884u, 0x00000000u, 0x1a466884u, 0x8306d03bu, 0xe255accbu},
|
||||
{0x1a466884u, 0x8306d03bu, 0x80a4df5au, 0x80a4df5au, 0x51c9bc70u, 0x80a4df5au, 0x8306d03bu, 0x80a4df5au, 0x51c9bc70u, 0x8306d03bu, 0x1a466884u, 0x8306d03bu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu},
|
||||
{0xc0433cbdu, 0x99dd251du, 0xf38b2ffcu, 0xdc28ff90u, 0xf38b2ffcu, 0xf38b2ffcu, 0x9f199504u, 0xf38b2ffcu, 0xf38b2ffcu, 0x99dd251du, 0xc0433cbdu, 0x99dd251du, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu},
|
||||
{0x8306d03bu, 0x80a4df5au, 0x8306d03bu, 0x51c9bc70u, 0x8306d03bu, 0x8306d03bu, 0x80a4df5au, 0x8306d03bu, 0x8306d03bu, 0x80a4df5au, 0x8306d03bu, 0x80a4df5au, 0x8306d03bu, 0x00000000u, 0x51c9bc70u, 0x00000000u},
|
||||
{0xbb049a79u, 0x39292d22u, 0xa5aec797u, 0xa5aec797u, 0xdc28ff90u, 0xa5aec797u, 0xe5121482u, 0xa5aec797u, 0xf3f49249u, 0x39292d22u, 0xbb049a79u, 0x39292d22u, 0x00000000u, 0x39292d22u, 0xa5aec797u, 0xe5121482u},
|
||||
{0x99dd251du, 0xdc28ff90u, 0xdc28ff90u, 0xf38b2ffcu, 0xa5aec797u, 0xdc28ff90u, 0xdc28ff90u, 0xdc28ff90u, 0xa5aec797u, 0xdc28ff90u, 0x99dd251du, 0xdc28ff90u, 0x00000000u, 0xdc28ff90u, 0x00000000u, 0xdc28ff90u},
|
||||
{0x3d6c51e3u, 0x8e7aa6e9u, 0xf3f49249u, 0xf3f49249u, 0xf3f49249u, 0xf3f49249u, 0x6bad6be2u, 0xf3f49249u, 0xf3f49249u, 0x8e7aa6e9u, 0x3d6c51e3u, 0x8e7aa6e9u, 0x3d6c51e3u, 0x00000000u, 0xf3f49249u, 0x00000000u},
|
||||
{0x80a4df5au, 0x51c9bc70u, 0x1a466884u, 0x1a466884u, 0x1a466884u, 0x1a466884u, 0x51c9bc70u, 0x1a466884u, 0x1e7ea419u, 0xe255accbu, 0x80a4df5au, 0x51c9bc70u, 0x00000000u, 0x51c9bc70u, 0x1a466884u, 0x51c9bc70u},
|
||||
{0x8c3d5f16u, 0xe255accbu, 0xe255accbu, 0x99dd251du, 0x39292d22u, 0xe255accbu, 0xe255accbu, 0xe255accbu, 0xf38b2ffcu, 0xe255accbu, 0x8c3d5f16u, 0xe255accbu, 0x8c3d5f16u, 0xe255accbu, 0x00000000u, 0xe255accbu},
|
||||
{0x39292d22u, 0xa5aec797u, 0x39292d22u, 0x39292d22u, 0xe255accbu, 0x39292d22u, 0xa5aec797u, 0x39292d22u, 0x51c9bc70u, 0x9f199504u, 0x39292d22u, 0xa5aec797u, 0x39292d22u, 0xa5aec797u, 0x39292d22u, 0xa5aec797u},
|
||||
{0x96263ae6u, 0x9f199504u, 0xe5121482u, 0x8e7aa6e9u, 0xe5121482u, 0xe5121482u, 0x9f199504u, 0xe5121482u, 0xf38b2ffcu, 0x9f199504u, 0x96263ae6u, 0x9f199504u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u},
|
||||
{0xdc28ff90u, 0xf38b2ffcu, 0x99dd251du, 0xe255accbu, 0x99dd251du, 0x99dd251du, 0xf38b2ffcu, 0x99dd251du, 0x8306d03bu, 0xe5121482u, 0xdc28ff90u, 0xf38b2ffcu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu},
|
||||
{0xc4a334bfu, 0xe5121482u, 0x9f199504u, 0x9f199504u, 0x8e7aa6e9u, 0x9f199504u, 0xe5121482u, 0x9f199504u, 0xf3f49249u, 0xe5121482u, 0xc4a334bfu, 0xe5121482u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu},
|
||||
{0x8e7aa6e9u, 0xf3f49249u, 0x8e7aa6e9u, 0xe5121482u, 0x9f199504u, 0x8e7aa6e9u, 0xf3f49249u, 0x8e7aa6e9u, 0xa5aec797u, 0x6bad6be2u, 0x8e7aa6e9u, 0xf3f49249u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu},
|
||||
{0x9d3c7decu, 0x6bad6be2u, 0x6bad6be2u, 0x6bad6be2u, 0x6bad6be2u, 0x6bad6be2u, 0x6bad6be2u, 0x6bad6be2u, 0xf3f49249u, 0x6bad6be2u, 0x9d3c7decu, 0x6bad6be2u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu},
|
||||
{0x51c9bc70u, 0xc88b2875u, 0xc88b2875u, 0xc88b2875u, 0xc88b2875u, 0xc88b2875u, 0x1e7ea419u, 0x1e7ea419u, 0xc88b2875u, 0x1e7ea419u, 0x51c9bc70u, 0xc88b2875u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu},
|
||||
{0x9293de8fu, 0xc0433cbdu, 0x9293de8fu, 0xbb049a79u, 0x8c3d5f16u, 0x9293de8fu, 0xe255accbu, 0x51c9bc70u, 0xd7a7a3ccu, 0x1a466884u, 0x9293de8fu, 0xc0433cbdu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu},
|
||||
{0xe255accbu, 0xbb049a79u, 0x8c3d5f16u, 0x8c3d5f16u, 0x9293de8fu, 0x8c3d5f16u, 0x8306d03bu, 0x80a4df5au, 0x9293de8fu, 0x8306d03bu, 0xe255accbu, 0xbb049a79u, 0xe255accbu, 0x00000000u, 0x8c3d5f16u, 0x8306d03bu},
|
||||
{0xc5e818fau, 0x3d6c51e3u, 0xd7a7a3ccu, 0xc4a334bfu, 0xd7a7a3ccu, 0xd7a7a3ccu, 0x9f199504u, 0xf38b2ffcu, 0xd7a7a3ccu, 0x99dd251du, 0xc5e818fau, 0x3d6c51e3u, 0xc5e818fau, 0x3d6c51e3u, 0xc4a334bfu, 0x00000000u},
|
||||
{0xa5aec797u, 0x8c3d5f16u, 0xbb049a79u, 0x9293de8fu, 0xbb049a79u, 0xbb049a79u, 0x80a4df5au, 0x8306d03bu, 0xbb049a79u, 0x80a4df5au, 0xa5aec797u, 0x8c3d5f16u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu},
|
||||
{0xc6cd75e9u, 0x96263ae6u, 0xc6cd75e9u, 0xc6cd75e9u, 0xc4a334bfu, 0xc6cd75e9u, 0xe5121482u, 0xa5aec797u, 0x7dabe929u, 0x39292d22u, 0xc6cd75e9u, 0x96263ae6u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu},
|
||||
{0x9f199504u, 0xc4a334bfu, 0xc4a334bfu, 0xd7a7a3ccu, 0xc6cd75e9u, 0xc4a334bfu, 0xdc28ff90u, 0xdc28ff90u, 0xc6cd75e9u, 0xdc28ff90u, 0x9f199504u, 0xc4a334bfu, 0x00000000u, 0xc4a334bfu, 0xd7a7a3ccu, 0xdc28ff90u},
|
||||
{0x00a61f93u, 0x9d3c7decu, 0x7dabe929u, 0x7dabe929u, 0x7dabe929u, 0x7dabe929u, 0x6bad6be2u, 0xf3f49249u, 0x7dabe929u, 0x8e7aa6e9u, 0x00a61f93u, 0x9d3c7decu, 0x00a61f93u, 0x00000000u, 0x7dabe929u, 0x00000000u},
|
||||
{0xf38b2ffcu, 0x9293de8fu, 0xc0433cbdu, 0xc0433cbdu, 0xc0433cbdu, 0xc0433cbdu, 0x51c9bc70u, 0x1a466884u, 0xc88b2875u, 0xe255accbu, 0xf38b2ffcu, 0x9293de8fu, 0x00000000u, 0x00000000u, 0xc0433cbdu, 0x51c9bc70u},
|
||||
{0xd7a7a3ccu, 0xc5e818fau, 0xc5e818fau, 0x3d6c51e3u, 0x96263ae6u, 0xc5e818fau, 0xe255accbu, 0xe255accbu, 0xd7a7a3ccu, 0xe255accbu, 0xd7a7a3ccu, 0xc5e818fau, 0xd7a7a3ccu, 0xc5e818fau, 0x3d6c51e3u, 0xe255accbu},
|
||||
{0xe5121482u, 0xc6cd75e9u, 0x96263ae6u, 0x96263ae6u, 0xc5e818fau, 0x96263ae6u, 0xa5aec797u, 0x39292d22u, 0x9293de8fu, 0x9f199504u, 0xe5121482u, 0xc6cd75e9u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu},
|
||||
{0x70eb9a0au, 0x00a61f93u, 0x70eb9a0au, 0x9d3c7decu, 0x70eb9a0au, 0x70eb9a0au, 0x9f199504u, 0xe5121482u, 0xd7a7a3ccu, 0x9f199504u, 0x70eb9a0au, 0x00a61f93u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu},
|
||||
{0xf3f49249u, 0xd7a7a3ccu, 0x3d6c51e3u, 0xc5e818fau, 0x3d6c51e3u, 0x3d6c51e3u, 0xf38b2ffcu, 0x99dd251du, 0xbb049a79u, 0xe5121482u, 0xf3f49249u, 0xd7a7a3ccu, 0xf3f49249u, 0xd7a7a3ccu, 0xc5e818fau, 0x00000000u},
|
||||
{0x7dabe929u, 0x70eb9a0au, 0x00a61f93u, 0x00a61f93u, 0x9d3c7decu, 0x00a61f93u, 0xe5121482u, 0x9f199504u, 0x7dabe929u, 0xe5121482u, 0x7dabe929u, 0x70eb9a0au, 0x7dabe929u, 0x00000000u, 0x00a61f93u, 0xe5121482u},
|
||||
{0x6bad6be2u, 0x7dabe929u, 0x9d3c7decu, 0x70eb9a0au, 0x00a61f93u, 0x9d3c7decu, 0xf3f49249u, 0x8e7aa6e9u, 0xc6cd75e9u, 0x6bad6be2u, 0x6bad6be2u, 0x7dabe929u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu},
|
||||
{0x14aa4e71u, 0x14aa4e71u, 0x14aa4e71u, 0x14aa4e71u, 0x14aa4e71u, 0x14aa4e71u, 0x6bad6be2u, 0x6bad6be2u, 0x7dabe929u, 0x6bad6be2u, 0x14aa4e71u, 0x14aa4e71u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}
|
||||
};
|
||||
constexpr std::uint32_t FftExpected64[64][16] = {
|
||||
{0x1e7ea419u, 0x1e7ea419u, 0x1e7ea419u, 0x1e7ea419u, 0x1e7ea419u, 0x1e7ea419u, 0x1e7ea419u, 0x1e7ea419u, 0x1e7ea419u, 0x1e7ea419u, 0x1e7ea419u, 0x1e7ea419u, 0x1e7ea419u, 0x1e7ea419u, 0x1e7ea419u, 0x1e7ea419u},
|
||||
{0xc88b2875u, 0x1a466884u, 0x51c9bc70u, 0x8306d03bu, 0x80a4df5au, 0x51c9bc70u, 0xe255accbu, 0x51c9bc70u, 0xf38b2ffcu, 0x1a466884u, 0xc88b2875u, 0x1a466884u, 0x00000000u, 0x1a466884u, 0x8306d03bu, 0xe255accbu},
|
||||
{0x1a466884u, 0x8306d03bu, 0x80a4df5au, 0x80a4df5au, 0x51c9bc70u, 0x80a4df5au, 0x8306d03bu, 0x80a4df5au, 0x51c9bc70u, 0x8306d03bu, 0x1a466884u, 0x8306d03bu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu},
|
||||
{0xc0433cbdu, 0x99dd251du, 0xf38b2ffcu, 0xdc28ff90u, 0xf38b2ffcu, 0xf38b2ffcu, 0x9f199504u, 0xf38b2ffcu, 0xf38b2ffcu, 0x99dd251du, 0xc0433cbdu, 0x99dd251du, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu},
|
||||
{0x8306d03bu, 0x80a4df5au, 0x8306d03bu, 0x51c9bc70u, 0x8306d03bu, 0x8306d03bu, 0x80a4df5au, 0x8306d03bu, 0x8306d03bu, 0x80a4df5au, 0x8306d03bu, 0x80a4df5au, 0x8306d03bu, 0x00000000u, 0x51c9bc70u, 0x00000000u},
|
||||
{0xbb049a79u, 0x39292d22u, 0xa5aec797u, 0xa5aec797u, 0xdc28ff90u, 0xa5aec797u, 0xe5121482u, 0xa5aec797u, 0xf3f49249u, 0x39292d22u, 0xbb049a79u, 0x39292d22u, 0x00000000u, 0x39292d22u, 0xa5aec797u, 0xe5121482u},
|
||||
{0x99dd251du, 0xdc28ff90u, 0xdc28ff90u, 0xf38b2ffcu, 0xa5aec797u, 0xdc28ff90u, 0xdc28ff90u, 0xdc28ff90u, 0xa5aec797u, 0xdc28ff90u, 0x99dd251du, 0xdc28ff90u, 0x00000000u, 0xdc28ff90u, 0x00000000u, 0xdc28ff90u},
|
||||
{0x3d6c51e3u, 0x8e7aa6e9u, 0xf3f49249u, 0xf3f49249u, 0xf3f49249u, 0xf3f49249u, 0x6bad6be2u, 0xf3f49249u, 0xf3f49249u, 0x8e7aa6e9u, 0x3d6c51e3u, 0x8e7aa6e9u, 0x3d6c51e3u, 0x00000000u, 0xf3f49249u, 0x00000000u},
|
||||
{0x80a4df5au, 0x51c9bc70u, 0x1a466884u, 0x1a466884u, 0x1a466884u, 0x1a466884u, 0x51c9bc70u, 0x1a466884u, 0x1e7ea419u, 0xe255accbu, 0x80a4df5au, 0x51c9bc70u, 0x00000000u, 0x51c9bc70u, 0x1a466884u, 0x51c9bc70u},
|
||||
{0x8c3d5f16u, 0xe255accbu, 0xe255accbu, 0x99dd251du, 0x39292d22u, 0xe255accbu, 0xe255accbu, 0xe255accbu, 0xf38b2ffcu, 0xe255accbu, 0x8c3d5f16u, 0xe255accbu, 0x8c3d5f16u, 0xe255accbu, 0x00000000u, 0xe255accbu},
|
||||
{0x39292d22u, 0xa5aec797u, 0x39292d22u, 0x39292d22u, 0xe255accbu, 0x39292d22u, 0xa5aec797u, 0x39292d22u, 0x51c9bc70u, 0x9f199504u, 0x39292d22u, 0xa5aec797u, 0x39292d22u, 0xa5aec797u, 0x39292d22u, 0xa5aec797u},
|
||||
{0x96263ae6u, 0x9f199504u, 0xe5121482u, 0x8e7aa6e9u, 0xe5121482u, 0xe5121482u, 0x9f199504u, 0xe5121482u, 0xf38b2ffcu, 0x9f199504u, 0x96263ae6u, 0x9f199504u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u},
|
||||
{0xdc28ff90u, 0xf38b2ffcu, 0x99dd251du, 0xe255accbu, 0x99dd251du, 0x99dd251du, 0xf38b2ffcu, 0x99dd251du, 0x8306d03bu, 0xe5121482u, 0xdc28ff90u, 0xf38b2ffcu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu},
|
||||
{0xc4a334bfu, 0xe5121482u, 0x9f199504u, 0x9f199504u, 0x8e7aa6e9u, 0x9f199504u, 0xe5121482u, 0x9f199504u, 0xf3f49249u, 0xe5121482u, 0xc4a334bfu, 0xe5121482u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu},
|
||||
{0x8e7aa6e9u, 0xf3f49249u, 0x8e7aa6e9u, 0xe5121482u, 0x9f199504u, 0x8e7aa6e9u, 0xf3f49249u, 0x8e7aa6e9u, 0xa5aec797u, 0x6bad6be2u, 0x8e7aa6e9u, 0xf3f49249u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu},
|
||||
{0x9d3c7decu, 0x6bad6be2u, 0x6bad6be2u, 0x6bad6be2u, 0x6bad6be2u, 0x6bad6be2u, 0x6bad6be2u, 0x6bad6be2u, 0xf3f49249u, 0x6bad6be2u, 0x9d3c7decu, 0x6bad6be2u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu},
|
||||
{0x51c9bc70u, 0xc88b2875u, 0xc88b2875u, 0xc88b2875u, 0xc88b2875u, 0xc88b2875u, 0x1e7ea419u, 0x1e7ea419u, 0xc88b2875u, 0x1e7ea419u, 0x51c9bc70u, 0xc88b2875u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu},
|
||||
{0x9293de8fu, 0xc0433cbdu, 0x9293de8fu, 0xbb049a79u, 0x8c3d5f16u, 0x9293de8fu, 0xe255accbu, 0x51c9bc70u, 0xd7a7a3ccu, 0x1a466884u, 0x9293de8fu, 0xc0433cbdu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu},
|
||||
{0xe255accbu, 0xbb049a79u, 0x8c3d5f16u, 0x8c3d5f16u, 0x9293de8fu, 0x8c3d5f16u, 0x8306d03bu, 0x80a4df5au, 0x9293de8fu, 0x8306d03bu, 0xe255accbu, 0xbb049a79u, 0xe255accbu, 0x00000000u, 0x8c3d5f16u, 0x8306d03bu},
|
||||
{0xc5e818fau, 0x3d6c51e3u, 0xd7a7a3ccu, 0xc4a334bfu, 0xd7a7a3ccu, 0xd7a7a3ccu, 0x9f199504u, 0xf38b2ffcu, 0xd7a7a3ccu, 0x99dd251du, 0xc5e818fau, 0x3d6c51e3u, 0xc5e818fau, 0x3d6c51e3u, 0xc4a334bfu, 0x00000000u},
|
||||
{0xa5aec797u, 0x8c3d5f16u, 0xbb049a79u, 0x9293de8fu, 0xbb049a79u, 0xbb049a79u, 0x80a4df5au, 0x8306d03bu, 0xbb049a79u, 0x80a4df5au, 0xa5aec797u, 0x8c3d5f16u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu},
|
||||
{0xc6cd75e9u, 0x96263ae6u, 0xc6cd75e9u, 0xc6cd75e9u, 0xc4a334bfu, 0xc6cd75e9u, 0xe5121482u, 0xa5aec797u, 0x7dabe929u, 0x39292d22u, 0xc6cd75e9u, 0x96263ae6u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu},
|
||||
{0x9f199504u, 0xc4a334bfu, 0xc4a334bfu, 0xd7a7a3ccu, 0xc6cd75e9u, 0xc4a334bfu, 0xdc28ff90u, 0xdc28ff90u, 0xc6cd75e9u, 0xdc28ff90u, 0x9f199504u, 0xc4a334bfu, 0x00000000u, 0xc4a334bfu, 0xd7a7a3ccu, 0xdc28ff90u},
|
||||
{0x00a61f93u, 0x9d3c7decu, 0x7dabe929u, 0x7dabe929u, 0x7dabe929u, 0x7dabe929u, 0x6bad6be2u, 0xf3f49249u, 0x7dabe929u, 0x8e7aa6e9u, 0x00a61f93u, 0x9d3c7decu, 0x00a61f93u, 0x00000000u, 0x7dabe929u, 0x00000000u},
|
||||
{0xf38b2ffcu, 0x9293de8fu, 0xc0433cbdu, 0xc0433cbdu, 0xc0433cbdu, 0xc0433cbdu, 0x51c9bc70u, 0x1a466884u, 0xc88b2875u, 0xe255accbu, 0xf38b2ffcu, 0x9293de8fu, 0x00000000u, 0x00000000u, 0xc0433cbdu, 0x51c9bc70u},
|
||||
{0xd7a7a3ccu, 0xc5e818fau, 0xc5e818fau, 0x3d6c51e3u, 0x96263ae6u, 0xc5e818fau, 0xe255accbu, 0xe255accbu, 0xd7a7a3ccu, 0xe255accbu, 0xd7a7a3ccu, 0xc5e818fau, 0xd7a7a3ccu, 0xc5e818fau, 0x3d6c51e3u, 0xe255accbu},
|
||||
{0xe5121482u, 0xc6cd75e9u, 0x96263ae6u, 0x96263ae6u, 0xc5e818fau, 0x96263ae6u, 0xa5aec797u, 0x39292d22u, 0x9293de8fu, 0x9f199504u, 0xe5121482u, 0xc6cd75e9u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu},
|
||||
{0x70eb9a0au, 0x00a61f93u, 0x70eb9a0au, 0x9d3c7decu, 0x70eb9a0au, 0x70eb9a0au, 0x9f199504u, 0xe5121482u, 0xd7a7a3ccu, 0x9f199504u, 0x70eb9a0au, 0x00a61f93u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu},
|
||||
{0xf3f49249u, 0xd7a7a3ccu, 0x3d6c51e3u, 0xc5e818fau, 0x3d6c51e3u, 0x3d6c51e3u, 0xf38b2ffcu, 0x99dd251du, 0xbb049a79u, 0xe5121482u, 0xf3f49249u, 0xd7a7a3ccu, 0xf3f49249u, 0xd7a7a3ccu, 0xc5e818fau, 0x00000000u},
|
||||
{0x7dabe929u, 0x70eb9a0au, 0x00a61f93u, 0x00a61f93u, 0x9d3c7decu, 0x00a61f93u, 0xe5121482u, 0x9f199504u, 0x7dabe929u, 0xe5121482u, 0x7dabe929u, 0x70eb9a0au, 0x7dabe929u, 0x00000000u, 0x00a61f93u, 0xe5121482u},
|
||||
{0x6bad6be2u, 0x7dabe929u, 0x9d3c7decu, 0x70eb9a0au, 0x00a61f93u, 0x9d3c7decu, 0xf3f49249u, 0x8e7aa6e9u, 0xc6cd75e9u, 0x6bad6be2u, 0x6bad6be2u, 0x7dabe929u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu},
|
||||
{0x14aa4e71u, 0x14aa4e71u, 0x14aa4e71u, 0x14aa4e71u, 0x14aa4e71u, 0x14aa4e71u, 0x6bad6be2u, 0x6bad6be2u, 0x7dabe929u, 0x6bad6be2u, 0x14aa4e71u, 0x14aa4e71u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu},
|
||||
{0x1c593af5u, 0x1c593af5u, 0x1c593af5u, 0x1c593af5u, 0x1c593af5u, 0x1c593af5u, 0x1c593af5u, 0x1c593af5u, 0x1c593af5u, 0x1c593af5u, 0x1c593af5u, 0x1c593af5u, 0x1c593af5u, 0x1c593af5u, 0x1c593af5u, 0x1c593af5u},
|
||||
{0xfadb8908u, 0xaf67d461u, 0x49889310u, 0x731c9452u, 0xd11745adu, 0x49889310u, 0x7d836e77u, 0x49889310u, 0x1919e93au, 0xaf67d461u, 0xfadb8908u, 0xaf67d461u, 0xfadb8908u, 0x00000000u, 0x00000000u, 0x7d836e77u},
|
||||
{0xaf67d461u, 0x731c9452u, 0xd11745adu, 0xd11745adu, 0x49889310u, 0xd11745adu, 0x731c9452u, 0xd11745adu, 0x49889310u, 0x731c9452u, 0xaf67d461u, 0x731c9452u, 0x00000000u, 0x00000000u, 0xd11745adu, 0x00000000u},
|
||||
{0x837a7935u, 0x35d14880u, 0x1919e93au, 0xd0944996u, 0x1919e93au, 0x1919e93au, 0x65b00a2du, 0x1919e93au, 0x1919e93au, 0x35d14880u, 0x837a7935u, 0x35d14880u, 0x837a7935u, 0x35d14880u, 0x00000000u, 0x00000000u},
|
||||
{0x731c9452u, 0xd11745adu, 0x731c9452u, 0x49889310u, 0x731c9452u, 0x731c9452u, 0xd11745adu, 0x731c9452u, 0x731c9452u, 0xd11745adu, 0x731c9452u, 0xd11745adu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu},
|
||||
{0xce1d62e0u, 0xadec6f11u, 0x02f0ee99u, 0x02f0ee99u, 0xd0944996u, 0x02f0ee99u, 0x5071950eu, 0x02f0ee99u, 0xe4163207u, 0xadec6f11u, 0xce1d62e0u, 0xadec6f11u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu},
|
||||
{0x35d14880u, 0xd0944996u, 0xd0944996u, 0x1919e93au, 0x02f0ee99u, 0xd0944996u, 0xd0944996u, 0xd0944996u, 0x02f0ee99u, 0xd0944996u, 0x35d14880u, 0xd0944996u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu},
|
||||
{0x57241955u, 0x406329bcu, 0xe4163207u, 0xe4163207u, 0xe4163207u, 0xe4163207u, 0x59001ac9u, 0xe4163207u, 0xe4163207u, 0x406329bcu, 0x57241955u, 0x406329bcu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu},
|
||||
{0xd11745adu, 0x49889310u, 0xaf67d461u, 0xaf67d461u, 0xaf67d461u, 0xaf67d461u, 0x49889310u, 0xaf67d461u, 0x1c593af5u, 0x7d836e77u, 0xd11745adu, 0x49889310u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu},
|
||||
{0xede7bd0cu, 0x7d836e77u, 0x7d836e77u, 0x35d14880u, 0xadec6f11u, 0x7d836e77u, 0x7d836e77u, 0x7d836e77u, 0x1919e93au, 0x7d836e77u, 0xede7bd0cu, 0x7d836e77u, 0xede7bd0cu, 0x7d836e77u, 0x35d14880u, 0x7d836e77u},
|
||||
{0xadec6f11u, 0x02f0ee99u, 0xadec6f11u, 0xadec6f11u, 0x7d836e77u, 0xadec6f11u, 0x02f0ee99u, 0xadec6f11u, 0x49889310u, 0x65b00a2du, 0xadec6f11u, 0x02f0ee99u, 0xadec6f11u, 0x00000000u, 0xadec6f11u, 0x00000000u},
|
||||
{0x1389b372u, 0x65b00a2du, 0x5071950eu, 0x406329bcu, 0x5071950eu, 0x5071950eu, 0x65b00a2du, 0x5071950eu, 0x1919e93au, 0x65b00a2du, 0x1389b372u, 0x65b00a2du, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu},
|
||||
{0xd0944996u, 0x1919e93au, 0x35d14880u, 0x7d836e77u, 0x35d14880u, 0x35d14880u, 0x1919e93au, 0x35d14880u, 0x731c9452u, 0x5071950eu, 0xd0944996u, 0x1919e93au, 0x00000000u, 0x1919e93au, 0x7d836e77u, 0x1919e93au},
|
||||
{0xffe976abu, 0x5071950eu, 0x65b00a2du, 0x65b00a2du, 0x406329bcu, 0x65b00a2du, 0x5071950eu, 0x65b00a2du, 0xe4163207u, 0x5071950eu, 0xffe976abu, 0x5071950eu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu},
|
||||
{0x406329bcu, 0xe4163207u, 0x406329bcu, 0x5071950eu, 0x65b00a2du, 0x406329bcu, 0xe4163207u, 0x406329bcu, 0x02f0ee99u, 0x59001ac9u, 0x406329bcu, 0xe4163207u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu},
|
||||
{0x8eb5140fu, 0x59001ac9u, 0x59001ac9u, 0x59001ac9u, 0x59001ac9u, 0x59001ac9u, 0x59001ac9u, 0x59001ac9u, 0xe4163207u, 0x59001ac9u, 0x8eb5140fu, 0x59001ac9u, 0x8eb5140fu, 0x59001ac9u, 0x59001ac9u, 0x59001ac9u},
|
||||
{0x49889310u, 0xfadb8908u, 0xfadb8908u, 0xfadb8908u, 0xfadb8908u, 0xfadb8908u, 0x1c593af5u, 0x1c593af5u, 0xfadb8908u, 0x1c593af5u, 0x49889310u, 0xfadb8908u, 0x49889310u, 0xfadb8908u, 0xfadb8908u, 0x1c593af5u},
|
||||
{0xffb88309u, 0x837a7935u, 0xffb88309u, 0xce1d62e0u, 0xede7bd0cu, 0xffb88309u, 0x7d836e77u, 0x49889310u, 0x5b4c8012u, 0xaf67d461u, 0xffb88309u, 0x837a7935u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu},
|
||||
{0x7d836e77u, 0xce1d62e0u, 0xede7bd0cu, 0xede7bd0cu, 0xffb88309u, 0xede7bd0cu, 0x731c9452u, 0xd11745adu, 0xffb88309u, 0x731c9452u, 0x7d836e77u, 0xce1d62e0u, 0x7d836e77u, 0x00000000u, 0xede7bd0cu, 0x00000000u},
|
||||
{0xa341738cu, 0x57241955u, 0x5b4c8012u, 0xffe976abu, 0x5b4c8012u, 0x5b4c8012u, 0x65b00a2du, 0x1919e93au, 0x5b4c8012u, 0x35d14880u, 0xa341738cu, 0x57241955u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu},
|
||||
{0x02f0ee99u, 0xede7bd0cu, 0xce1d62e0u, 0xffb88309u, 0xce1d62e0u, 0xce1d62e0u, 0xd11745adu, 0x731c9452u, 0xce1d62e0u, 0xd11745adu, 0x02f0ee99u, 0xede7bd0cu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu},
|
||||
{0x60581ccau, 0x1389b372u, 0x60581ccau, 0x60581ccau, 0xffe976abu, 0x60581ccau, 0x5071950eu, 0x02f0ee99u, 0xbef7e268u, 0xadec6f11u, 0x60581ccau, 0x1389b372u, 0x60581ccau, 0x00000000u, 0x60581ccau, 0x00000000u},
|
||||
{0x65b00a2du, 0xffe976abu, 0xffe976abu, 0x5b4c8012u, 0x60581ccau, 0xffe976abu, 0xd0944996u, 0xd0944996u, 0x60581ccau, 0xd0944996u, 0x65b00a2du, 0xffe976abu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu},
|
||||
{0x16f44881u, 0x8eb5140fu, 0xbef7e268u, 0xbef7e268u, 0xbef7e268u, 0xbef7e268u, 0x59001ac9u, 0xe4163207u, 0xbef7e268u, 0x406329bcu, 0x16f44881u, 0x8eb5140fu, 0x00000000u, 0x8eb5140fu, 0xbef7e268u, 0x59001ac9u},
|
||||
{0x1919e93au, 0xffb88309u, 0x837a7935u, 0x837a7935u, 0x837a7935u, 0x837a7935u, 0x49889310u, 0xaf67d461u, 0xfadb8908u, 0x7d836e77u, 0x1919e93au, 0xffb88309u, 0x1919e93au, 0x00000000u, 0x837a7935u, 0x49889310u},
|
||||
{0x5b4c8012u, 0xa341738cu, 0xa341738cu, 0x57241955u, 0x1389b372u, 0xa341738cu, 0x7d836e77u, 0x7d836e77u, 0x5b4c8012u, 0x7d836e77u, 0x5b4c8012u, 0xa341738cu, 0x00000000u, 0xa341738cu, 0x00000000u, 0x7d836e77u},
|
||||
{0x5071950eu, 0x60581ccau, 0x1389b372u, 0x1389b372u, 0xa341738cu, 0x1389b372u, 0x02f0ee99u, 0xadec6f11u, 0xffb88309u, 0x65b00a2du, 0x5071950eu, 0x60581ccau, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu},
|
||||
{0xb91dddd9u, 0x16f44881u, 0xb91dddd9u, 0x8eb5140fu, 0xb91dddd9u, 0xb91dddd9u, 0x65b00a2du, 0x5071950eu, 0x5b4c8012u, 0x65b00a2du, 0xb91dddd9u, 0x16f44881u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu},
|
||||
{0xe4163207u, 0x5b4c8012u, 0x57241955u, 0xa341738cu, 0x57241955u, 0x57241955u, 0x1919e93au, 0x35d14880u, 0xce1d62e0u, 0x5071950eu, 0xe4163207u, 0x5b4c8012u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu},
|
||||
{0xbef7e268u, 0xb91dddd9u, 0x16f44881u, 0x16f44881u, 0x8eb5140fu, 0x16f44881u, 0x5071950eu, 0x65b00a2du, 0xbef7e268u, 0x5071950eu, 0xbef7e268u, 0xb91dddd9u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu},
|
||||
{0x59001ac9u, 0xbef7e268u, 0x8eb5140fu, 0xb91dddd9u, 0x16f44881u, 0x8eb5140fu, 0xe4163207u, 0x406329bcu, 0x60581ccau, 0x59001ac9u, 0x59001ac9u, 0xbef7e268u, 0x59001ac9u, 0xbef7e268u, 0x00000000u, 0x00000000u},
|
||||
{0x89be9c1cu, 0x89be9c1cu, 0x89be9c1cu, 0x89be9c1cu, 0x89be9c1cu, 0x89be9c1cu, 0x59001ac9u, 0x59001ac9u, 0xbef7e268u, 0x59001ac9u, 0x89be9c1cu, 0x89be9c1cu, 0x89be9c1cu, 0x89be9c1cu, 0x89be9c1cu, 0x59001ac9u}
|
||||
};
|
||||
|
||||
constexpr const char* Names[Checked] = {"bitmask_identity", "bitmask_swap8", "bitmask_broadcast", "bitmask_reverse", "bitmask_mixed", "quad_perm", "quad_perm_high_bits", "rotate_left_1", "rotate_right_3", "rotate_left_31", "rotate_masked", "rotate_high_bits", "inactive_swap8", "inactive_quad_perm", "inactive_rotate", "inactive_broadcast"};
|
||||
constexpr const char* FftNames[Checked] = {"fft_00", "fft_10", "fft_1f", "fft_18", "fft_1c", "fft_1e", "fft_01", "fft_0f", "fft_15", "fft_08", "fft_00_high_bits", "fft_10_high_bits", "inactive_fft_00", "inactive_fft_10", "inactive_fft_18", "inactive_fft_01"};
|
||||
|
||||
std::array<std::uint32_t, 4> BufferDescriptor(const void* data, std::uint32_t bytes) {
|
||||
const auto address = reinterpret_cast<std::uintptr_t>(data);
|
||||
@@ -255,11 +385,11 @@ void Run(AgcDriver::VulkanDevice& device, std::span<const std::uint32_t> code, s
|
||||
}
|
||||
|
||||
template<std::size_t Lanes>
|
||||
void Check(const std::uint32_t (&expected)[Lanes][Checked]) {
|
||||
void Check(const char* const (&names)[Checked], const std::uint32_t (&expected)[Lanes][Checked]) {
|
||||
for (std::uint32_t tid = 0; tid < Lanes; ++tid) {
|
||||
for (std::uint32_t index = 0; index < Checked; ++index) {
|
||||
const auto actual = Output[tid * Results + index];
|
||||
Require(actual == expected[tid][index], "ds_swizzle wave" + std::to_string(Lanes) + ": lane " + std::to_string(tid) + " " + Names[index] + " is " + Hex(actual) + ", expected " + Hex(expected[tid][index]));
|
||||
Require(actual == expected[tid][index], "ds_swizzle wave" + std::to_string(Lanes) + ": lane " + std::to_string(tid) + " " + names[index] + " is " + Hex(actual) + ", expected " + Hex(expected[tid][index]));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -275,11 +405,17 @@ int main() {
|
||||
return VulkanTestSkipped;
|
||||
}
|
||||
Run(*device, Wave32Code, 32, device->ComputeTarget(32));
|
||||
Check(Expected32);
|
||||
Check(Names, Expected32);
|
||||
Run(*device, FftWave32Code, 32, device->ComputeTarget(32));
|
||||
Check(FftNames, FftExpected32);
|
||||
Run(*device, Wave64Code, 64, device->Target());
|
||||
Check(Expected64);
|
||||
Check(Names, Expected64);
|
||||
Run(*device, FftWave64Code, 64, device->Target());
|
||||
Check(FftNames, FftExpected64);
|
||||
Run(*device, Wave64Code, 64, device->ComputeTarget(32));
|
||||
Check(Expected64);
|
||||
Check(Names, Expected64);
|
||||
Run(*device, FftWave64Code, 64, device->ComputeTarget(32));
|
||||
Check(FftNames, FftExpected64);
|
||||
std::puts("ds_swizzle tests passed");
|
||||
return 0;
|
||||
} catch (const std::exception& error) {
|
||||
|
||||
@@ -0,0 +1,204 @@
|
||||
#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/TextureTiling.hpp"
|
||||
#include "Recompiler.hpp"
|
||||
#include "VulkanTestDevice.hpp"
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <bit>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <iostream>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
|
||||
using AgcDriver::Graphics::Require;
|
||||
using ShaderRecompiler::ShaderStage;
|
||||
|
||||
constexpr std::uint32_t Width = 64;
|
||||
constexpr std::uint32_t Height = 16;
|
||||
constexpr std::uint32_t Size = 64;
|
||||
constexpr std::uint32_t Mips = 7;
|
||||
constexpr std::uint32_t Format8888UNorm = 56;
|
||||
constexpr std::uint32_t Type2D = 9;
|
||||
constexpr float CoordinateScale = 0.0473f;
|
||||
constexpr std::uint32_t LevelStep = 20;
|
||||
alignas(256) std::array<std::byte, Width * Height * 4> Pixels{};
|
||||
alignas(256) std::array<std::uint8_t, 262144> Texels{};
|
||||
|
||||
alignas(256) constexpr std::array<std::uint32_t, 6> VertexCode{
|
||||
0xe0382000, 0x80000005, 0xbf8c3f70, 0xf80008cf, 0x03020100, 0xbf810000,
|
||||
};
|
||||
|
||||
alignas(256) constexpr std::array<std::uint32_t, 9> BiasPixelCode{
|
||||
0x10040010, 0x06060300, 0x10060611, 0xf0940f08, 0x00610401, 0xbf8c3f70, 0xf800180f, 0x07060504, 0xbf810000,
|
||||
};
|
||||
|
||||
constexpr std::array<std::array<float, 4>, 3> Triangle{{{-1.0f, -1.0f, 0.5f, 1.0f}, {3.0f, -1.0f, 0.5f, 1.0f}, {-1.0f, 3.0f, 0.5f, 1.0f}}};
|
||||
|
||||
std::array<std::uint32_t, 4> BufferDescriptor(const void* data, std::uint32_t stride, std::uint32_t count) {
|
||||
const auto address = reinterpret_cast<std::uintptr_t>(data);
|
||||
return {static_cast<std::uint32_t>(address), static_cast<std::uint32_t>((address >> 32u) & 0xffffu) | (stride << 16u), count, 0x01016facu};
|
||||
}
|
||||
|
||||
std::array<std::uint32_t, 8> TextureDescriptor() {
|
||||
const auto address = static_cast<std::uint64_t>(reinterpret_cast<std::uintptr_t>(Texels.data()));
|
||||
return {
|
||||
static_cast<std::uint32_t>(address >> 8u),
|
||||
static_cast<std::uint32_t>((address >> 40u) & 0xffu) | (Format8888UNorm << 20u) | (((Size - 1u) & 3u) << 30u),
|
||||
((Size - 1u) >> 2u) | ((Size - 1u) << 14u),
|
||||
0xfacu | ((Mips - 1u) << 16u) | (Type2D << 28u),
|
||||
0u,
|
||||
(Mips - 1u) << 4u,
|
||||
0u,
|
||||
0u,
|
||||
};
|
||||
}
|
||||
|
||||
std::array<std::uint32_t, 4> SamplerDescriptor() {
|
||||
return {0u, 0xfffu << 12u, 1u << 26u, 0u};
|
||||
}
|
||||
|
||||
void FillTexture() {
|
||||
const auto mips = AgcDriver::Graphics::ComputeMipLayout(AgcDriver::Graphics::TextureTileMode::kLinear, Format8888UNorm, Size, Size, Mips);
|
||||
Require(AgcDriver::Graphics::ComputeSurfaceSize(mips, 1) <= Texels.size(), "image sample LOD bias sum: the mip chain does not fit the texel storage");
|
||||
std::fill(Texels.begin(), Texels.end(), 0xeeu);
|
||||
for (std::uint32_t level = 0; level < Mips; ++level) {
|
||||
const auto& mip = mips[level];
|
||||
for (std::uint32_t y = 0; y < mip.height; ++y) {
|
||||
for (std::uint32_t x = 0; x < mip.width; ++x) {
|
||||
auto* texel = &Texels[mip.tiledOffset + y * mip.pitchBytes + x * 4u];
|
||||
texel[0] = static_cast<std::uint8_t>(level * LevelStep);
|
||||
texel[1] = 0u;
|
||||
texel[2] = 0u;
|
||||
texel[3] = 255u;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Draw(AgcDriver::VulkanDevice& device, std::array<std::uint32_t, 8> texture, std::array<std::uint32_t, 4> sampler, float bias) {
|
||||
const auto& target = device.Target();
|
||||
alignas(256) std::array<std::uint32_t, 10> code{};
|
||||
std::copy_n(BiasPixelCode.begin(), 3u, code.begin());
|
||||
code[3] = 0x7e020212u;
|
||||
std::copy(BiasPixelCode.begin() + 3, BiasPixelCode.end(), code.begin() + 4);
|
||||
const auto pixelCode = std::span(code).first(10u);
|
||||
Pixels.fill(std::byte{0});
|
||||
|
||||
std::vector<std::uint32_t> vertexUserData(4, 0u);
|
||||
const auto vertexBuffer = BufferDescriptor(Triangle.data(), 16u, static_cast<std::uint32_t>(Triangle.size()));
|
||||
std::copy(vertexBuffer.begin(), vertexBuffer.end(), vertexUserData.begin());
|
||||
const std::array<ShaderRecompiler::MemoryRegion, 1> vertexMemory{{{reinterpret_cast<std::uintptr_t>(VertexCode.data()), std::as_bytes(std::span(VertexCode))}}};
|
||||
ShaderRecompiler::RecompileRequest vertex{
|
||||
{ShaderStage::Vertex, reinterpret_cast<std::uintptr_t>(VertexCode.data()), VertexCode, 0, {}},
|
||||
{64, 0, vertexUserData, std::nullopt, std::nullopt, ShaderRecompiler::ShaderVertexStageInfo{}, vertexMemory},
|
||||
target,
|
||||
{0, 0, 0, 64}
|
||||
};
|
||||
vertex.useCache = false;
|
||||
const auto vertexResult = ShaderRecompiler::Recompile(vertex);
|
||||
const auto vertexPush = static_cast<std::uint32_t>(vertexResult.pushConstants.size());
|
||||
|
||||
ShaderRecompiler::ShaderPixelStageInfo pixel{};
|
||||
pixel.wave32 = false;
|
||||
pixel.inputAddr = ShaderRecompiler::PixelInputBit(ShaderRecompiler::PixelInput::PositionX) | ShaderRecompiler::PixelInputBit(ShaderRecompiler::PixelInput::PositionY);
|
||||
pixel.posX = true;
|
||||
pixel.posY = true;
|
||||
pixel.targetOutputMode[0] = 9;
|
||||
pixel.targetExportMapping.fill(0xe4u);
|
||||
std::vector<std::uint32_t> pixelUserData(24, 0u);
|
||||
std::copy(texture.begin(), texture.end(), pixelUserData.begin() + 4);
|
||||
std::copy(sampler.begin(), sampler.end(), pixelUserData.begin() + 12);
|
||||
pixelUserData[16] = std::bit_cast<std::uint32_t>(CoordinateScale);
|
||||
pixelUserData[17] = std::bit_cast<std::uint32_t>(CoordinateScale);
|
||||
pixelUserData[18] = std::bit_cast<std::uint32_t>(bias);
|
||||
const std::array<ShaderRecompiler::MemoryRegion, 1> pixelMemory{{{reinterpret_cast<std::uintptr_t>(pixelCode.data()), std::as_bytes(std::span(pixelCode))}}};
|
||||
ShaderRecompiler::RecompileRequest fragment{
|
||||
{ShaderStage::Fragment, reinterpret_cast<std::uintptr_t>(pixelCode.data()), pixelCode, 0, {}},
|
||||
{64, 0, pixelUserData, std::nullopt, pixel, std::nullopt, pixelMemory},
|
||||
target,
|
||||
PixelPushLayout(vertexPush, target)
|
||||
};
|
||||
const auto pixelResult = ShaderRecompiler::Recompile(fragment);
|
||||
Require(!target.registrationModules || pixelResult.registrationModule, "image sample LOD bias sum did not exercise its requested registration module");
|
||||
const std::array<AgcDriver::Graphics::CompiledShader, 2> shaders{{
|
||||
{ShaderStage::Vertex, &vertexResult, 0},
|
||||
{ShaderStage::Fragment, &pixelResult, PixelPushOffset(vertexPush, target)}
|
||||
}};
|
||||
|
||||
AgcDriver::Graphics::State state{};
|
||||
state.stages = {AgcDriver::Graphics::ShaderPath::Vertex, 0u, 64u, 64u, std::nullopt, std::nullopt};
|
||||
state.color = {reinterpret_cast<std::uintptr_t>(Pixels.data()), {Width, Height}, VK_FORMAT_R8G8B8A8_UNORM, Pixels.size(), 0xe4u};
|
||||
state.colors = {state.color};
|
||||
state.hasColorTarget = true;
|
||||
state.renderExtent = {Width, Height};
|
||||
state.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
|
||||
state.viewport = {0, static_cast<float>(Height), static_cast<float>(Width), -static_cast<float>(Height), 0, 1};
|
||||
state.negativeOneToOne = false;
|
||||
state.scissor = {{0, 0}, {Width, Height}};
|
||||
state.cullMode = VK_CULL_MODE_NONE;
|
||||
state.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
|
||||
state.blend.colorWriteMask = 15;
|
||||
state.blends = {state.blend};
|
||||
state.blendConstants = {};
|
||||
const AgcDriver::Pm4::DrawParameters draw{0, static_cast<std::uint32_t>(Triangle.size()), 0, 1, 0, false};
|
||||
device.Draw(state, draw, shaders);
|
||||
device.WaitIdle();
|
||||
}
|
||||
|
||||
struct Case {
|
||||
std::uint32_t field;
|
||||
float shaderBias;
|
||||
std::uint32_t level;
|
||||
};
|
||||
|
||||
void Check(const Case& sample, bool llvmpipe) {
|
||||
const std::string label = "image sample LOD bias sum: S# field " + std::to_string(sample.field) + " with shader bias " + std::to_string(sample.shaderBias);
|
||||
std::uint32_t matching = 0;
|
||||
for (std::uint32_t pixel = 0; pixel < Width * Height; ++pixel) {
|
||||
const auto red = std::to_integer<std::uint32_t>(Pixels[pixel * 4u]);
|
||||
if (red == sample.level * LevelStep) {
|
||||
++matching;
|
||||
continue;
|
||||
}
|
||||
Require(llvmpipe, label + " read " + std::to_string(red) + ", expected level " + std::to_string(sample.level));
|
||||
}
|
||||
Require(matching * 4u >= Width * Height * 3u, label + " matched level " + std::to_string(sample.level) + " on only " + std::to_string(matching) + " of " + std::to_string(Width * Height) + " pixels");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main() {
|
||||
try {
|
||||
const auto device = OpenVulkanTestDevice();
|
||||
if (!device) return VulkanTestSkipped;
|
||||
FillTexture();
|
||||
const bool llvmpipe = device->DeviceName().find("llvmpipe") != std::string::npos;
|
||||
constexpr std::array<Case, 9> cases{{
|
||||
{0u, 0.0f, 2u},
|
||||
{0x100u, 0.0f, 3u},
|
||||
{0u, 2.0f, 4u},
|
||||
{0x1000u, -14.0f, 4u},
|
||||
{0x1facu, -29.0f, 5u},
|
||||
{0x3000u, 14.0f, 0u},
|
||||
{0x2000u, 32.0f, 2u},
|
||||
{0x1facu, 0.0f, 6u},
|
||||
{0x2000u, 0.0f, 0u},
|
||||
}};
|
||||
for (const auto& sample : cases) {
|
||||
auto sampler = SamplerDescriptor();
|
||||
sampler[2] |= sample.field;
|
||||
Draw(*device, TextureDescriptor(), sampler, sample.shaderBias);
|
||||
Check(sample, llvmpipe);
|
||||
}
|
||||
std::puts("image sample LOD bias sum tests passed");
|
||||
return 0;
|
||||
} catch (const std::exception& error) {
|
||||
std::cerr << error.what() << '\n';
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -929,7 +929,7 @@ void CheckBdaSpan(AgcDriver::VulkanDevice& device, const Block& block, std::size
|
||||
failure = error.what();
|
||||
}
|
||||
const auto deferred = log.Finish();
|
||||
if (failure.empty()) failure = deferred;
|
||||
if (failure.empty() && deferred.find("BDA access failed:") != std::string::npos) failure = deferred;
|
||||
if (refused) {
|
||||
Require(failure.find("BDA access failed:") != std::string::npos, "BDA access to a resident native multisample range was not refused: " + failure);
|
||||
Require(std::equal(before.begin(), before.end(), block.data), "a refused BDA access changed guest memory");
|
||||
|
||||
@@ -0,0 +1,279 @@
|
||||
#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp"
|
||||
#include "Recompiler.hpp"
|
||||
#include "RdnaDecoder/RdnaInstructionDecoder.hpp"
|
||||
#include "VulkanTestDevice.hpp"
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <bit>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
|
||||
using AgcDriver::Graphics::Require;
|
||||
using ShaderRecompiler::ShaderStage;
|
||||
|
||||
constexpr std::uint32_t Threads = 64;
|
||||
constexpr std::uint32_t Inputs = 16;
|
||||
constexpr std::uint32_t ResultsPerCase = 19;
|
||||
constexpr std::uint32_t Results = Inputs * ResultsPerCase;
|
||||
constexpr std::uint32_t Garbage = 0xabcd1234u;
|
||||
|
||||
alignas(256) std::array<std::uint32_t, Threads * Inputs> Input{};
|
||||
alignas(256) std::array<std::uint32_t, Threads * Results> Output{};
|
||||
|
||||
struct TestCase {
|
||||
std::uint32_t opcode;
|
||||
std::uint32_t input;
|
||||
std::uint32_t expectedFull;
|
||||
bool checkPinned;
|
||||
const char* name;
|
||||
};
|
||||
|
||||
constexpr TestCase TestCases[Inputs] = {
|
||||
{0x35u, 0x00000000u, 0x00000000u, true, "v_sin_f32 0.0"},
|
||||
{0x35u, 0x3e800000u, 0x3f800000u, true, "v_sin_f32 0.25"},
|
||||
{0x35u, 0xbe800000u, 0xbf800000u, true, "v_sin_f32 -0.25"},
|
||||
{0x35u, 0x3f400000u, 0xbf800000u, true, "v_sin_f32 0.75"},
|
||||
{0x35u, 0x3fa00000u, 0x3f800000u, true, "v_sin_f32 1.25"},
|
||||
{0x35u, 0xbf400000u, 0x3f800000u, true, "v_sin_f32 -0.75"},
|
||||
{0x35u, 0xbe000000u, 0u, false, "v_sin_f32 oracle -0.125"},
|
||||
{0x35u, 0x3d000000u, 0u, false, "v_sin_f32 oracle 0.03125"},
|
||||
{0x36u, 0x00000000u, 0x3f800000u, true, "v_cos_f32 0.0"},
|
||||
{0x36u, 0x3f000000u, 0xbf800000u, true, "v_cos_f32 0.5"},
|
||||
{0x36u, 0xbf000000u, 0xbf800000u, true, "v_cos_f32 -0.5"},
|
||||
{0x36u, 0x3f800000u, 0x3f800000u, true, "v_cos_f32 1.0"},
|
||||
{0x36u, 0x3e800000u, 0x00000000u, true, "v_cos_f32 0.25"},
|
||||
{0x36u, 0xbe800000u, 0x00000000u, true, "v_cos_f32 -0.25"},
|
||||
{0x36u, 0xbe000000u, 0u, false, "v_cos_f32 oracle -0.125"},
|
||||
{0x36u, 0x3e600000u, 0u, false, "v_cos_f32 oracle 0.21875"},
|
||||
};
|
||||
|
||||
std::string Hex(std::uint32_t value) {
|
||||
char text[16];
|
||||
std::snprintf(text, sizeof(text), "0x%08x", value);
|
||||
return text;
|
||||
}
|
||||
|
||||
std::uint32_t FieldWidth(std::uint32_t selector) {
|
||||
return selector < 4u ? 8u : selector < 6u ? 16u : 32u;
|
||||
}
|
||||
|
||||
std::uint32_t FieldOffset(std::uint32_t selector) {
|
||||
return selector < 4u ? selector * 8u : selector == 5u ? 16u : 0u;
|
||||
}
|
||||
|
||||
std::uint32_t InsertResult(std::uint32_t bits, std::uint32_t destination, std::uint32_t unused) {
|
||||
const auto width = FieldWidth(destination);
|
||||
const auto offset = FieldOffset(destination);
|
||||
const auto mask = (1u << width) - 1u;
|
||||
auto field = bits & mask;
|
||||
if (unused == 1u && (field & (1u << (width - 1u)))) field |= ~mask;
|
||||
return (field << offset) | (unused == 2u ? Garbage & ~(mask << offset) : 0u);
|
||||
}
|
||||
|
||||
std::array<std::uint32_t, 4> BufferDescriptor(const void* data, std::uint32_t count) {
|
||||
const auto address = reinterpret_cast<std::uintptr_t>(data);
|
||||
return {static_cast<std::uint32_t>(address), static_cast<std::uint32_t>((address >> 32u) & 0xffffu) | (4u << 16u), count, 0x11016facu};
|
||||
}
|
||||
|
||||
void TestInvalidSdwaSelectors() {
|
||||
constexpr std::uint32_t SinSdwa = 0x7e0000f9u | (5u << 17u) | (0x35u << 9u);
|
||||
constexpr std::uint32_t CosSdwa = 0x7e0000f9u | (5u << 17u) | (0x36u << 9u);
|
||||
|
||||
for (const auto opWord : {SinSdwa, CosSdwa}) {
|
||||
{
|
||||
const std::array<std::uint32_t, 2> words{opWord, 4u | (0u << 8u) | (2u << 11u) | (6u << 16u)};
|
||||
const auto instruction = ShaderRecompiler::DecodeRdnaInstruction(0u, words, 0u);
|
||||
Require(instruction.family == ShaderRecompiler::RdnaInstructionFamily::VOP1, "instruction must be VOP1");
|
||||
Require(instruction.destination.sdwaSel == 0u, "destination sdwaSel must be 0");
|
||||
Require(instruction.destination.sdwaDstUnused == 2u, "destination sdwaDstUnused must be 2");
|
||||
Require(instruction.destination.explicitSdwaDst, "destination explicitSdwaDst must be true");
|
||||
Require(instruction.source0.sdwaSel == 6u, "source0 sdwaSel must be 6");
|
||||
Require(!instruction.source0.negate, "source0 negate must be false");
|
||||
Require(!instruction.source0.absolute, "source0 absolute must be false");
|
||||
Require(!instruction.destination.clamp, "destination clamp must be false");
|
||||
Require(instruction.destination.omod == 0u, "destination omod must be 0");
|
||||
}
|
||||
{
|
||||
const std::array<std::uint32_t, 2> words{opWord, 4u | (4u << 8u) | (1u << 11u) | (1u << 13u) | (1u << 14u) | (6u << 16u) | (1u << 20u) | (1u << 21u)};
|
||||
const auto instruction = ShaderRecompiler::DecodeRdnaInstruction(0u, words, 0u);
|
||||
Require(instruction.destination.sdwaSel == 4u, "destination sdwaSel must be 4");
|
||||
Require(instruction.destination.sdwaDstUnused == 1u, "destination sdwaDstUnused must be 1");
|
||||
Require(instruction.destination.clamp, "destination clamp must be true");
|
||||
Require(instruction.destination.omod == 1u, "destination omod must be 1");
|
||||
Require(instruction.source0.negate, "source0 negate must be true");
|
||||
Require(instruction.source0.absolute, "source0 absolute must be true");
|
||||
}
|
||||
{
|
||||
const std::array<std::uint32_t, 2> words{opWord, 4u | (7u << 8u) | (6u << 16u)};
|
||||
bool threw = false;
|
||||
try {
|
||||
ShaderRecompiler::DecodeRdnaInstruction(0u, words, 0u);
|
||||
} catch (const std::invalid_argument&) {
|
||||
threw = true;
|
||||
}
|
||||
Require(threw, "SDWA destination selector 7 must throw std::invalid_argument");
|
||||
}
|
||||
{
|
||||
const std::array<std::uint32_t, 2> words{opWord, 4u | (0u << 8u) | (7u << 16u)};
|
||||
bool threw = false;
|
||||
try {
|
||||
ShaderRecompiler::DecodeRdnaInstruction(0u, words, 0u);
|
||||
} catch (const std::invalid_argument&) {
|
||||
threw = true;
|
||||
}
|
||||
Require(threw, "SDWA source selector 7 must throw std::invalid_argument");
|
||||
}
|
||||
{
|
||||
const std::array<std::uint32_t, 2> words{opWord, 4u | (0u << 8u) | (3u << 11u) | (6u << 16u)};
|
||||
bool threw = false;
|
||||
try {
|
||||
ShaderRecompiler::DecodeRdnaInstruction(0u, words, 0u);
|
||||
} catch (const std::invalid_argument&) {
|
||||
threw = true;
|
||||
}
|
||||
Require(threw, "SDWA destination unused 3 on byte destination must throw std::invalid_argument");
|
||||
}
|
||||
{
|
||||
const std::array<std::uint32_t, 2> words{opWord, 4u | (4u << 8u) | (3u << 11u) | (6u << 16u)};
|
||||
bool threw = false;
|
||||
try {
|
||||
ShaderRecompiler::DecodeRdnaInstruction(0u, words, 0u);
|
||||
} catch (const std::invalid_argument&) {
|
||||
threw = true;
|
||||
}
|
||||
Require(threw, "SDWA destination unused 3 on word destination must throw std::invalid_argument");
|
||||
}
|
||||
{
|
||||
const std::array<std::uint32_t, 2> words{opWord, 4u | (6u << 8u) | (3u << 11u) | (6u << 16u)};
|
||||
bool threw = false;
|
||||
try {
|
||||
ShaderRecompiler::DecodeRdnaInstruction(0u, words, 0u);
|
||||
} catch (const std::invalid_argument&) {
|
||||
threw = true;
|
||||
}
|
||||
Require(threw, "SDWA destination unused 3 on dword destination must throw std::invalid_argument");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RunExecutionTests(AgcDriver::VulkanDevice& device, std::uint32_t waveSize) {
|
||||
for (std::uint32_t lane = 0u; lane < Threads; ++lane) {
|
||||
for (std::uint32_t c = 0u; c < Inputs; ++c) {
|
||||
Input[lane * Inputs + c] = TestCases[c].input;
|
||||
}
|
||||
}
|
||||
Output.fill(0xdeadbeefu);
|
||||
|
||||
std::vector<std::uint32_t> words{
|
||||
0x160200ffu, Inputs, 0x160400ffu, Results,
|
||||
};
|
||||
|
||||
std::uint32_t resultIndex = 0u;
|
||||
for (std::uint32_t c = 0u; c < Inputs; ++c) {
|
||||
words.insert(words.end(), {0xe0302000u | (c * 4u), 0x80000401u, 0xbf8c3f70u});
|
||||
words.insert(words.end(), {0x7e0002ffu | (5u << 17u), Garbage});
|
||||
words.push_back(0x7e000000u | (5u << 17u) | (TestCases[c].opcode << 9u) | 260u);
|
||||
words.insert(words.end(), {0xe0702000u | (resultIndex * 4u), 0x80010502u});
|
||||
++resultIndex;
|
||||
|
||||
for (std::uint32_t destination = 0u; destination < 6u; ++destination) {
|
||||
for (std::uint32_t unused = 0u; unused < 3u; ++unused, ++resultIndex) {
|
||||
words.insert(words.end(), {0x7e0002ffu | (5u << 17u), Garbage});
|
||||
const auto word0 = 0x7e0000f9u | (5u << 17u) | (TestCases[c].opcode << 9u);
|
||||
const auto word1 = 4u | (destination << 8u) | (unused << 11u) | (6u << 16u);
|
||||
words.insert(words.end(), {word0, word1});
|
||||
words.insert(words.end(), {0xe0702000u | (resultIndex * 4u), 0x80010502u});
|
||||
}
|
||||
}
|
||||
}
|
||||
words.push_back(0xbf810000u);
|
||||
|
||||
std::vector<std::uint32_t> userData(8u);
|
||||
const auto inputDesc = BufferDescriptor(Input.data(), static_cast<std::uint32_t>(Input.size()));
|
||||
const auto outputDesc = BufferDescriptor(Output.data(), static_cast<std::uint32_t>(Output.size()));
|
||||
std::copy(inputDesc.begin(), inputDesc.end(), userData.begin());
|
||||
std::copy(outputDesc.begin(), outputDesc.end(), userData.begin() + 4);
|
||||
|
||||
const std::span<const std::uint32_t> code(words);
|
||||
const std::array<ShaderRecompiler::MemoryRegion, 1> memory{{{reinterpret_cast<std::uintptr_t>(code.data()), std::as_bytes(code)}}};
|
||||
const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {false, false, false}, false, 1};
|
||||
ShaderRecompiler::RecompileRequest request{
|
||||
{ShaderStage::Compute, reinterpret_cast<std::uintptr_t>(code.data()), code, 0, {}},
|
||||
{waveSize, 0, userData, compute, std::nullopt, std::nullopt, memory},
|
||||
device.Target(), {0, 0, 0, 128}
|
||||
};
|
||||
request.useCache = false;
|
||||
const auto result = ShaderRecompiler::Recompile(request);
|
||||
device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast<std::uintptr_t>(code.data()));
|
||||
device.WaitIdle();
|
||||
|
||||
std::uint32_t checkIndex = 0u;
|
||||
for (std::uint32_t c = 0u; c < Inputs; ++c) {
|
||||
const auto fullResultIndex = c * ResultsPerCase;
|
||||
for (std::uint32_t lane = 0u; lane < Threads; ++lane) {
|
||||
const auto actualFull = Output[lane * Results + checkIndex];
|
||||
if (TestCases[c].checkPinned) {
|
||||
Require(actualFull == TestCases[c].expectedFull,
|
||||
std::string(TestCases[c].name) + " full result lane " + std::to_string(lane) +
|
||||
" is " + Hex(actualFull) + ", expected " + Hex(TestCases[c].expectedFull));
|
||||
} else if (TestCases[c].opcode == 0x35u) {
|
||||
Require((actualFull >> 16u) == 0xbf35u || (actualFull >> 16u) == 0x3f35u || (actualFull >> 16u) == 0x3e47u,
|
||||
std::string(TestCases[c].name) + " full result lane " + std::to_string(lane) +
|
||||
" is " + Hex(actualFull) + ", expected high word 0xbf35, 0x3f35 or 0x3e47");
|
||||
Require((actualFull & 0xffu) != 0u, std::string(TestCases[c].name) + " must produce non-zero low byte");
|
||||
Require((actualFull & 0x80u) != 0u, std::string(TestCases[c].name) + " must have bit 7 set for SEXT");
|
||||
if ((actualFull >> 16u) == 0x3e47u) {
|
||||
Require((actualFull & 0x8000u) != 0u, std::string(TestCases[c].name) + " must have bit 15 set for word SEXT");
|
||||
}
|
||||
} else {
|
||||
Require((actualFull >> 16u) == 0x3f35u || (actualFull >> 16u) == 0x3e47u,
|
||||
std::string(TestCases[c].name) + " full result lane " + std::to_string(lane) +
|
||||
" is " + Hex(actualFull) + ", expected high word 0x3f35 or 0x3e47");
|
||||
Require((actualFull & 0xffu) != 0u, std::string(TestCases[c].name) + " must produce non-zero low byte");
|
||||
Require((actualFull & 0x80u) != 0u, std::string(TestCases[c].name) + " must have bit 7 set for SEXT");
|
||||
if ((actualFull >> 16u) == 0x3e47u) {
|
||||
Require((actualFull & 0x8000u) != 0u, std::string(TestCases[c].name) + " must have bit 15 set for word SEXT");
|
||||
}
|
||||
}
|
||||
}
|
||||
++checkIndex;
|
||||
for (std::uint32_t destination = 0u; destination < 6u; ++destination) {
|
||||
for (std::uint32_t unused = 0u; unused < 3u; ++unused, ++checkIndex) {
|
||||
for (std::uint32_t lane = 0u; lane < Threads; ++lane) {
|
||||
const auto base = TestCases[c].checkPinned ? TestCases[c].expectedFull : Output[lane * Results + fullResultIndex];
|
||||
const auto expected = InsertResult(base, destination, unused);
|
||||
const auto actual = Output[lane * Results + checkIndex];
|
||||
Require(actual == expected,
|
||||
std::string(TestCases[c].name) + " lane " + std::to_string(lane) +
|
||||
" wave" + std::to_string(waveSize) + " destination " +
|
||||
std::to_string(destination) + " unused " + std::to_string(unused) +
|
||||
" is " + Hex(actual) + ", expected " + Hex(expected));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
try {
|
||||
TestInvalidSdwaSelectors();
|
||||
const auto device = OpenVulkanTestDevice();
|
||||
if (!device) return VulkanTestSkipped;
|
||||
Require(argc == 1 || (argc == 2 && std::string(argv[1]) == "wave64"), "expected no argument or wave64");
|
||||
const auto waveSize = argc == 2 ? 64u : 32u;
|
||||
RunExecutionTests(*device, waveSize);
|
||||
return 0;
|
||||
} catch (const std::exception& error) {
|
||||
std::cerr << error.what() << '\n';
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,10 @@
|
||||
#include "VulkanTestDevice.hpp"
|
||||
#ifdef _WIN32
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
#include <windows.h>
|
||||
#endif
|
||||
#include <cstdlib>
|
||||
#include <iostream>
|
||||
#include <stdexcept>
|
||||
@@ -21,6 +27,20 @@ void Require(const bool condition, const std::string& message) {
|
||||
if (!condition) throw std::runtime_error(message);
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
bool HighIntegrity() {
|
||||
HANDLE token = nullptr;
|
||||
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token)) return false;
|
||||
alignas(TOKEN_MANDATORY_LABEL) unsigned char label[sizeof(TOKEN_MANDATORY_LABEL) + SECURITY_MAX_SID_SIZE];
|
||||
DWORD size = 0;
|
||||
const bool queried = GetTokenInformation(token, TokenIntegrityLevel, label, sizeof(label), &size) != FALSE;
|
||||
CloseHandle(token);
|
||||
if (!queried) return false;
|
||||
const PSID sid = reinterpret_cast<const TOKEN_MANDATORY_LABEL*>(label)->Label.Sid;
|
||||
return *GetSidSubAuthority(sid, *GetSidSubAuthorityCount(sid) - 1u) >= SECURITY_MANDATORY_HIGH_RID;
|
||||
}
|
||||
#endif
|
||||
|
||||
Outcome Open(std::string& message) {
|
||||
message.clear();
|
||||
try {
|
||||
@@ -61,6 +81,12 @@ int NoDriverTests() {
|
||||
SetVariable("ANYPS5_GPU", "");
|
||||
std::string message;
|
||||
const auto optional = Open(message);
|
||||
#ifdef _WIN32
|
||||
if (optional == Outcome::Device && HighIntegrity()) {
|
||||
std::cout << "skipped, the Vulkan loader ignores VK_DRIVER_FILES in an elevated process and opened " << message << '\n';
|
||||
return VulkanTestSkipped;
|
||||
}
|
||||
#endif
|
||||
Require(optional == Outcome::Skipped, "without a Vulkan driver the optional device was not skipped: " + message);
|
||||
|
||||
SetVariable("ANYPS5_REQUIRE_VULKAN", "1");
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "prx/libSceAgcDriver/Execution/include/Driver.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/FrameCapture.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/FrameDumpIo.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/Pm4.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/VideoOutput.hpp"
|
||||
#include "prx/libSceAgcDriver/Submit/include/Acb.hpp"
|
||||
#include "prx/libSceAgcDriver/Submit/include/Dcb.hpp"
|
||||
@@ -13,6 +14,7 @@
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <cerrno>
|
||||
#include <charconv>
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
#include <cstdio>
|
||||
@@ -22,7 +24,9 @@
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <future>
|
||||
#include <limits>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
@@ -68,8 +72,15 @@ struct Options {
|
||||
std::chrono::milliseconds memoryPaceTimeout{10000};
|
||||
std::chrono::milliseconds releaseAfter{3};
|
||||
std::chrono::milliseconds drainTimeout{10000};
|
||||
std::chrono::milliseconds busyDrainTimeout{600000};
|
||||
bool release = true;
|
||||
bool synthesize = true;
|
||||
std::size_t repeat = 1;
|
||||
bool shaders = false;
|
||||
std::filesystem::path shaderCode;
|
||||
std::size_t submits = 0;
|
||||
bool traceSerials = false;
|
||||
bool coldPipelines = false;
|
||||
};
|
||||
|
||||
class Frames {
|
||||
@@ -246,6 +257,21 @@ void mapHostRanges(const FrameCaptureReader& reader, Skipped& skipped) {
|
||||
#endif
|
||||
}
|
||||
|
||||
void restoreSnapshot(const FrameCaptureReader& reader, const Skipped& skipped) {
|
||||
std::vector<const FrameMemoryChunk*> snapshot;
|
||||
for (const auto& chunk : reader.Chunks()) {
|
||||
if (chunk.kind == FrameMemoryKind::Snapshot && !skipped.Overlaps(chunk)) snapshot.push_back(&chunk);
|
||||
}
|
||||
const auto workers = std::max<std::size_t>(1, std::thread::hardware_concurrency());
|
||||
for (std::size_t first = 0; first < snapshot.size(); first += workers) {
|
||||
std::vector<std::future<void>> pending;
|
||||
for (auto index = first; index < std::min(snapshot.size(), first + workers); ++index) {
|
||||
pending.push_back(std::async(std::launch::async, [&reader, chunk = snapshot[index]] { restore(reader, *chunk); }));
|
||||
}
|
||||
for (auto& future : pending) future.get();
|
||||
}
|
||||
}
|
||||
|
||||
Skipped restoreMemory(const FrameCaptureReader& reader) {
|
||||
std::uintptr_t base = 0;
|
||||
std::size_t size = 0;
|
||||
@@ -280,21 +306,18 @@ Skipped restoreMemory(const FrameCaptureReader& reader) {
|
||||
mutation.Add(pointer, bytes, (chunk.access & FrameRangeAccess::Readable) != 0, (chunk.access & FrameRangeAccess::Writable) != 0, gpu);
|
||||
}
|
||||
}
|
||||
std::vector<const FrameMemoryChunk*> snapshot;
|
||||
for (const auto& chunk : reader.Chunks()) {
|
||||
if (chunk.kind == FrameMemoryKind::Snapshot && !skipped.Overlaps(chunk)) snapshot.push_back(&chunk);
|
||||
}
|
||||
const auto workers = std::max<std::size_t>(1, std::thread::hardware_concurrency());
|
||||
for (std::size_t first = 0; first < snapshot.size(); first += workers) {
|
||||
std::vector<std::future<void>> pending;
|
||||
for (auto index = first; index < std::min(snapshot.size(), first + workers); ++index) {
|
||||
pending.push_back(std::async(std::launch::async, [&reader, chunk = snapshot[index]] { restore(reader, *chunk); }));
|
||||
}
|
||||
for (auto& future : pending) future.get();
|
||||
}
|
||||
restoreSnapshot(reader, skipped);
|
||||
return skipped;
|
||||
}
|
||||
|
||||
void resetMemory(const FrameCaptureReader& reader, const Skipped& skipped) {
|
||||
AgcDriverWaitIdle_nid_postfix();
|
||||
for (const auto& chunk : reader.Chunks()) {
|
||||
if (chunk.kind == FrameMemoryKind::Range && !skipped.Overlaps(chunk)) std::memset(reinterpret_cast<void*>(static_cast<std::uintptr_t>(chunk.address)), 0, static_cast<std::size_t>(chunk.bytes));
|
||||
}
|
||||
restoreSnapshot(reader, skipped);
|
||||
}
|
||||
|
||||
std::pair<std::uint64_t, std::uint64_t> progress() {
|
||||
std::uint64_t accepted = 0;
|
||||
std::uint64_t completed = 0;
|
||||
@@ -305,7 +328,7 @@ std::pair<std::uint64_t, std::uint64_t> progress() {
|
||||
struct Replay {
|
||||
const Options& options;
|
||||
const FrameCaptureReader& reader;
|
||||
Frames frames;
|
||||
Frames& frames = *new Frames;
|
||||
std::map<std::uint32_t, std::shared_ptr<ReplayOutput>> outputs;
|
||||
std::mutex memoryMutex;
|
||||
std::map<std::uint64_t, std::vector<const FrameMemoryChunk*>> groups;
|
||||
@@ -321,9 +344,11 @@ struct Replay {
|
||||
std::atomic<std::uint64_t> earlyReleases{0};
|
||||
std::atomic<std::uint64_t> lateReleases{0};
|
||||
std::atomic<std::uint64_t> syntheticReleases{0};
|
||||
std::atomic<std::uint64_t> keptReservations{0};
|
||||
std::atomic<std::size_t> sequenced{0};
|
||||
std::uint64_t unfinished = 0;
|
||||
std::size_t threads = 0;
|
||||
std::size_t pass = 0;
|
||||
bool recording = false;
|
||||
Clock::time_point startedAt{};
|
||||
Clock::time_point endedAt{};
|
||||
@@ -334,9 +359,24 @@ struct Replay {
|
||||
|
||||
void report() const {
|
||||
const auto [accepted, completed] = progress();
|
||||
std::fprintf(stderr, "[frame-replay] %llu submits, %zu frames ready, %.1f s since the first frame, pacing waited %.1f s (%llu timeouts), serials %llu/%llu\n",
|
||||
std::fprintf(stderr, "[frame-replay] %llu submits, %zu frames ready, %.1f s since the first frame, pacing waited %.1f s (%llu timeouts), serials %llu/%llu, blocked on%s\n",
|
||||
static_cast<unsigned long long>(submits.load()), frames.Times().size(), std::chrono::duration<double>(Clock::now() - startedAt).count(), paceWaitedSeconds(),
|
||||
static_cast<unsigned long long>(paceTimeouts.load()), static_cast<unsigned long long>(completed), static_cast<unsigned long long>(accepted));
|
||||
static_cast<unsigned long long>(paceTimeouts.load()), static_cast<unsigned long long>(completed), static_cast<unsigned long long>(accepted), describeBlocked().c_str());
|
||||
}
|
||||
|
||||
static std::string describeBlocked() {
|
||||
std::array<AgcDriver::AwaitedWait, 64> awaited{};
|
||||
const auto count = AgcDriverAwaitedWaits_nid_postfix(awaited.data(), awaited.size());
|
||||
std::string text;
|
||||
for (std::size_t slot = 0; slot < count; ++slot) {
|
||||
const auto& wait = awaited[slot];
|
||||
if (wait.address == 0) continue;
|
||||
char entry[128];
|
||||
std::snprintf(entry, sizeof(entry), " 0x%llx (function %u, reference 0x%llx, mask 0x%llx, now 0x%llx)", static_cast<unsigned long long>(wait.address), static_cast<unsigned>(wait.function),
|
||||
static_cast<unsigned long long>(wait.reference), static_cast<unsigned long long>(wait.mask), static_cast<unsigned long long>(load(wait.address, wait.bytes == 8 ? 8 : 4)));
|
||||
text += entry;
|
||||
}
|
||||
return text.empty() ? " nothing" : text;
|
||||
}
|
||||
|
||||
static bool paced(FrameCallKind kind) {
|
||||
@@ -408,21 +448,31 @@ struct Replay {
|
||||
return;
|
||||
case FrameCallKind::Submit: {
|
||||
const Packet packet{reinterpret_cast<std::uint32_t*>(static_cast<std::uintptr_t>(call.address)), static_cast<std::uint32_t>(call.size), static_cast<std::uint8_t>(call.flags), {}};
|
||||
if (call.queue == 0) sceAgcDriverSubmitDcb(&packet);
|
||||
else sceAgcDriverSubmitAcb(call.queue, &packet);
|
||||
traced(call, [&] {
|
||||
if (call.queue == 0) sceAgcDriverSubmitDcb(&packet);
|
||||
else sceAgcDriverSubmitAcb(call.queue, &packet);
|
||||
});
|
||||
if (++submits % 25 == 0) report();
|
||||
return;
|
||||
}
|
||||
case FrameCallKind::Flip:
|
||||
AgcDriverSubmitFlip_nid_postfix(flipInfo(call));
|
||||
traced(call, [&] { AgcDriverSubmitFlip_nid_postfix(flipInfo(call)); });
|
||||
return;
|
||||
case FrameCallKind::ReserveEopFlip: {
|
||||
const auto token = static_cast<std::uint32_t>(call.values.at(4));
|
||||
if (AgcDriverReserveEopFlipAt_nid_postfix(flipInfo(call), token) != token) throw std::runtime_error("frame replay: EOP flip token " + std::to_string(token) + " was not reserved");
|
||||
std::uint32_t reserved = 0;
|
||||
try {
|
||||
reserved = AgcDriverReserveEopFlipAt_nid_postfix(flipInfo(call), token);
|
||||
} catch (const AgcDriver::EopFlipAlreadyReserved&) {
|
||||
if (pass == 0) throw;
|
||||
++keptReservations;
|
||||
return;
|
||||
}
|
||||
if (reserved != token) throw std::runtime_error("frame replay: EOP flip token " + std::to_string(token) + " was not reserved");
|
||||
return;
|
||||
}
|
||||
case FrameCallKind::Suspend:
|
||||
AgcDriverSuspendPoint_nid_postfix();
|
||||
traced(call, [] { AgcDriverSuspendPoint_nid_postfix(); });
|
||||
return;
|
||||
case FrameCallKind::Memory: {
|
||||
std::lock_guard lock(memoryMutex);
|
||||
@@ -533,6 +583,8 @@ struct Replay {
|
||||
std::size_t started = 0;
|
||||
std::exception_ptr failure;
|
||||
std::atomic<bool> finished{false};
|
||||
sequenced.store(0);
|
||||
stalled.store(false);
|
||||
std::thread breaker([&] {
|
||||
if (!options.release) return;
|
||||
auto lastCompleted = progress().second;
|
||||
@@ -588,22 +640,38 @@ struct Replay {
|
||||
void drain() {
|
||||
auto lastCompleted = progress().second;
|
||||
auto lastProgress = Clock::now();
|
||||
auto lastReport = lastProgress;
|
||||
while (true) {
|
||||
const auto [accepted, completed] = progress();
|
||||
unfinished = accepted - completed;
|
||||
if (unfinished == 0) return;
|
||||
const auto now = Clock::now();
|
||||
if (completed != lastCompleted) {
|
||||
lastCompleted = completed;
|
||||
lastProgress = Clock::now();
|
||||
} else if (Clock::now() - lastProgress >= options.drainTimeout) {
|
||||
std::fprintf(stderr, "[frame-replay] %llu submissions did not complete after the last captured call (serials %llu/%llu)\n", static_cast<unsigned long long>(unfinished),
|
||||
static_cast<unsigned long long>(completed), static_cast<unsigned long long>(accepted));
|
||||
lastProgress = now;
|
||||
} else if (now - lastReport >= options.drainTimeout) {
|
||||
lastReport = now;
|
||||
std::fprintf(stderr, "[frame-replay] draining: %llu submissions pending for %.0f s (serials %llu/%llu), blocked on%s\n", static_cast<unsigned long long>(unfinished),
|
||||
std::chrono::duration<double>(now - lastProgress).count(), static_cast<unsigned long long>(completed), static_cast<unsigned long long>(accepted), describeBlocked().c_str());
|
||||
}
|
||||
if (now - lastProgress >= options.busyDrainTimeout) {
|
||||
std::fprintf(stderr, "[frame-replay] %llu submissions did not complete after the last captured call (serials %llu/%llu), blocked on%s\n", static_cast<unsigned long long>(unfinished),
|
||||
static_cast<unsigned long long>(completed), static_cast<unsigned long long>(accepted), describeBlocked().c_str());
|
||||
return;
|
||||
}
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(5));
|
||||
}
|
||||
}
|
||||
|
||||
template <typename TFunction>
|
||||
void traced(const FrameCall& call, TFunction&& function) {
|
||||
if (options.traceSerials) {
|
||||
std::fprintf(stderr, "[frame-replay] call %llu (%s, thread %llu, queue 0x%x, %llu dwords at 0x%llx)\n", static_cast<unsigned long long>(call.ordinal), std::string(AgcDriver::FrameCallName(call.kind)).c_str(),
|
||||
static_cast<unsigned long long>(call.thread), call.queue, static_cast<unsigned long long>(call.size), static_cast<unsigned long long>(call.address));
|
||||
}
|
||||
function();
|
||||
}
|
||||
|
||||
template <typename TFunction>
|
||||
void registration(TFunction&& function) {
|
||||
try {
|
||||
@@ -626,7 +694,7 @@ struct Replay {
|
||||
|
||||
int usage() {
|
||||
std::fprintf(stderr, "usage: agc_frame_replay <capture directory> [--no-pace] [--no-release] [--release-after-ms <n>] [--strict]\n"
|
||||
" [--dump <address>:<bytes>:<file>]...\n"
|
||||
" [--repeat <n>] [--cold-pipelines] [--dump <address>:<bytes>:<file>]...\n"
|
||||
" replays the driver calls and guest memory that APS5_FRAME_CAPTURE=<directory> recorded, through this\n"
|
||||
" build's AGC driver with no title running, and prints one JSON line with the frame times\n"
|
||||
" --no-pace issues calls without waiting for the in-flight depth the title had\n"
|
||||
@@ -634,6 +702,18 @@ int usage() {
|
||||
" --release-after-ms milliseconds without a completion before awaited dwords are released (3)\n"
|
||||
" --no-synthesize releases a wait only with a value the capture recorded, never with its reference value\n"
|
||||
" --strict fails on a pacing timeout or a failed registration inside the captured frames\n"
|
||||
" --repeat <n> replays the frames n times in this process, restoring the snapshot before each pass;\n"
|
||||
" with n > 1 the frame times come from passes 2 to n, after the first warmed the caches\n"
|
||||
" --cold-pipelines starts with an empty Vulkan pipeline cache instead of the title's pipeline cache file\n"
|
||||
" --shaders replays nothing and maps no guest memory; lists each shader registration with its code\n"
|
||||
" address, size and code hash as the snapshot and the captured writes before it hold them,\n"
|
||||
" and how many registrations share that code\n"
|
||||
" --shader-code <dir> as --shaders, and writes each distinct code as <dir>/type<n>-<hash>.bin\n"
|
||||
" --trace-serials prints each submission, flip and suspend point with its call ordinal and command\n"
|
||||
" address as it is issued; with APS5_TRACE_GPU the driver prints the serial it accepts\n"
|
||||
" for each address\n"
|
||||
" --submits <n> replays nothing; lists the first n submissions and flips of the captured frames with\n"
|
||||
" their queue and serials, the packets of each and the fields of its waits and releases\n"
|
||||
" --dump writes guest memory to a file after the replay (hexadecimal or decimal)\n"
|
||||
" --frames <dir> writes each recorded and replayed display buffer as reference-<n>.bmp and replay-<n>.bmp\n");
|
||||
return 2;
|
||||
@@ -714,9 +794,290 @@ FrameCheck compareFrames(const FrameCaptureReader& reader, const Options& option
|
||||
return check;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
constexpr int RelaunchAttempts = 8;
|
||||
|
||||
int relaunchAttempt() {
|
||||
const char* const text = std::getenv("APS5_FRAME_REPLAY_ATTEMPT");
|
||||
if (text == nullptr) return 0;
|
||||
const std::string_view value = text;
|
||||
int attempt = 0;
|
||||
const auto [end, error] = std::from_chars(value.data(), value.data() + value.size(), attempt);
|
||||
if (error != std::errc{} || end != value.data() + value.size() || attempt < 0 || attempt > RelaunchAttempts)
|
||||
throw std::runtime_error("frame replay: APS5_FRAME_REPLAY_ATTEMPT must be a whole number from 0 to " + std::to_string(RelaunchAttempts) + ", not '" + std::string(value) + "'");
|
||||
return attempt;
|
||||
}
|
||||
|
||||
void relaunchIfHostRegionBlocks(const FrameCaptureReader& reader) {
|
||||
const int attempt = relaunchAttempt() + 1;
|
||||
const auto& chunks = reader.Chunks();
|
||||
const auto blocked = std::find_if(chunks.begin(), chunks.end(), [](const FrameMemoryChunk& chunk) {
|
||||
return chunk.kind == FrameMemoryKind::Range && chunk.rangeKind != FrameRangeKind::Host && (chunk.access & FrameRangeAccess::Gpu) != 0 &&
|
||||
GuestArena::GuestArenaHostRegionOverlaps_nid_postfix(static_cast<std::uintptr_t>(chunk.address), static_cast<std::size_t>(chunk.bytes));
|
||||
});
|
||||
if (blocked == chunks.end()) return;
|
||||
if (attempt > RelaunchAttempts) return;
|
||||
std::fprintf(stderr, "[frame-replay] a host region sits in the captured GPU range 0x%llx+0x%llx; relaunching (%d of %d)\n", static_cast<unsigned long long>(blocked->address),
|
||||
static_cast<unsigned long long>(blocked->bytes), attempt, RelaunchAttempts);
|
||||
std::fflush(stdout);
|
||||
std::fflush(stderr);
|
||||
if (_putenv_s("APS5_FRAME_REPLAY_ATTEMPT", std::to_string(attempt).c_str()) != 0) throw std::runtime_error("frame replay: cannot pass the relaunch count to the next process");
|
||||
std::wstring commandLine = GetCommandLineW();
|
||||
STARTUPINFOW startup{};
|
||||
startup.cb = sizeof(startup);
|
||||
PROCESS_INFORMATION process{};
|
||||
if (!CreateProcessW(nullptr, commandLine.data(), nullptr, nullptr, TRUE, 0, nullptr, nullptr, &startup, &process)) throw std::runtime_error("frame replay: cannot relaunch, error " + std::to_string(GetLastError()));
|
||||
CloseHandle(process.hThread);
|
||||
WaitForSingleObject(process.hProcess, INFINITE);
|
||||
DWORD code = 2;
|
||||
GetExitCodeProcess(process.hProcess, &code);
|
||||
std::_Exit(static_cast<int>(code));
|
||||
}
|
||||
#endif
|
||||
std::uint64_t contentHash(const std::byte* bytes, std::size_t size) {
|
||||
std::uint64_t hash = 0xcbf29ce484222325ull;
|
||||
for (std::size_t index = 0; index < size; ++index) {
|
||||
hash ^= std::to_integer<std::uint64_t>(bytes[index]);
|
||||
hash *= 0x100000001b3ull;
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
class CapturedMemory {
|
||||
public:
|
||||
explicit CapturedMemory(const FrameCaptureReader& reader) : reader(reader) {
|
||||
for (const auto& chunk : reader.Chunks()) {
|
||||
if (chunk.kind == FrameMemoryKind::Snapshot) snapshots.push_back(&chunk);
|
||||
else if (chunk.kind == FrameMemoryKind::Range) ranges.push_back(&chunk);
|
||||
}
|
||||
const auto byAddress = [](const FrameMemoryChunk* a, const FrameMemoryChunk* b) { return a->address < b->address; };
|
||||
std::sort(snapshots.begin(), snapshots.end(), byAddress);
|
||||
std::sort(ranges.begin(), ranges.end(), byAddress);
|
||||
}
|
||||
|
||||
std::optional<std::vector<std::byte>> Read(std::uint64_t address, std::uint64_t bytes) {
|
||||
if (bytes > std::numeric_limits<std::size_t>::max() || bytes > std::numeric_limits<std::uint64_t>::max() - address) return std::nullopt;
|
||||
std::vector<std::byte> result(static_cast<std::size_t>(bytes));
|
||||
for (auto at = address; at < address + bytes;) {
|
||||
const auto* range = covering(ranges, at);
|
||||
if (range == nullptr) return std::nullopt;
|
||||
const auto* snapshot = covering(snapshots, at);
|
||||
auto end = std::min(address + bytes, range->address + range->bytes);
|
||||
if (snapshot != nullptr) {
|
||||
end = std::min(end, snapshot->address + snapshot->bytes);
|
||||
const auto& data = load(*snapshot);
|
||||
std::memcpy(result.data() + (at - address), data.data() + (at - snapshot->address), static_cast<std::size_t>(end - at));
|
||||
} else {
|
||||
const auto next = std::upper_bound(snapshots.begin(), snapshots.end(), at, [](std::uint64_t value, const FrameMemoryChunk* chunk) { return value < chunk->address; });
|
||||
if (next != snapshots.end()) end = std::min(end, (*next)->address);
|
||||
}
|
||||
at = end;
|
||||
}
|
||||
for (const auto* delta : deltas) {
|
||||
const auto begin = std::max(address, delta->address);
|
||||
const auto end = std::min(address + bytes, delta->address + delta->bytes);
|
||||
if (begin >= end) continue;
|
||||
const auto data = reader.Read(*delta);
|
||||
std::memcpy(result.data() + (begin - address), data.data() + (begin - delta->address), static_cast<std::size_t>(end - begin));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void Apply(std::span<const FrameMemoryChunk* const> chunks) {
|
||||
deltas.insert(deltas.end(), chunks.begin(), chunks.end());
|
||||
}
|
||||
|
||||
private:
|
||||
static const FrameMemoryChunk* covering(const std::vector<const FrameMemoryChunk*>& chunks, std::uint64_t address) {
|
||||
const auto next = std::upper_bound(chunks.begin(), chunks.end(), address, [](std::uint64_t value, const FrameMemoryChunk* chunk) { return value < chunk->address; });
|
||||
if (next == chunks.begin()) return nullptr;
|
||||
const auto* chunk = *std::prev(next);
|
||||
return address < chunk->address + chunk->bytes ? chunk : nullptr;
|
||||
}
|
||||
|
||||
const std::vector<std::byte>& load(const FrameMemoryChunk& chunk) {
|
||||
if (cached != &chunk) {
|
||||
data = reader.Read(chunk);
|
||||
cached = &chunk;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
const FrameCaptureReader& reader;
|
||||
std::vector<const FrameMemoryChunk*> snapshots;
|
||||
std::vector<const FrameMemoryChunk*> ranges;
|
||||
std::vector<const FrameMemoryChunk*> deltas;
|
||||
const FrameMemoryChunk* cached = nullptr;
|
||||
std::vector<std::byte> data;
|
||||
};
|
||||
|
||||
int listShaders(const Options& options) {
|
||||
const FrameCaptureReader reader(options.directory);
|
||||
CapturedMemory memory(reader);
|
||||
if (!options.shaderCode.empty()) std::filesystem::create_directories(options.shaderCode);
|
||||
std::map<std::uint64_t, std::vector<const FrameMemoryChunk*>> groups;
|
||||
for (const auto& chunk : reader.Chunks()) {
|
||||
if (chunk.kind == FrameMemoryKind::Delta) groups[chunk.group].push_back(&chunk);
|
||||
}
|
||||
const auto& calls = reader.Calls();
|
||||
const auto frameStart = std::find_if(calls.begin(), calls.end(), [](const FrameCall& call) { return call.kind == FrameCallKind::FrameStart; });
|
||||
std::map<std::uint64_t, std::size_t> copies;
|
||||
std::size_t listed = 0, inWindow = 0, unreadable = 0;
|
||||
for (auto call = calls.begin(); call != calls.end(); ++call) {
|
||||
if (call->kind == FrameCallKind::Memory) {
|
||||
if (const auto found = groups.find(call->values.at(0)); found != groups.end()) memory.Apply(found->second);
|
||||
continue;
|
||||
}
|
||||
if (call->kind != FrameCallKind::RegisterShader) continue;
|
||||
const auto header = memory.Read(call->address, sizeof(Shader));
|
||||
if (!header) {
|
||||
++unreadable;
|
||||
continue;
|
||||
}
|
||||
Shader fields{};
|
||||
std::memcpy(&fields, header->data(), sizeof(Shader));
|
||||
const auto code = static_cast<std::uint64_t>(reinterpret_cast<std::uintptr_t>(const_cast<const void*>(fields.code)));
|
||||
const auto bytes = fields.shader_size != 0 ? memory.Read(code, fields.shader_size) : std::nullopt;
|
||||
if (!bytes) {
|
||||
++unreadable;
|
||||
continue;
|
||||
}
|
||||
const auto hash = contentHash(bytes->data(), bytes->size());
|
||||
const auto copy = ++copies[hash];
|
||||
if (copy == 1 && !options.shaderCode.empty()) {
|
||||
char name[64];
|
||||
std::snprintf(name, sizeof(name), "type%u-%016llx.bin", static_cast<unsigned>(fields.type), static_cast<unsigned long long>(hash));
|
||||
const auto path = options.shaderCode / name;
|
||||
std::ofstream file(path, std::ios::binary | std::ios::trunc);
|
||||
if (!file.is_open()) throw std::runtime_error("frame replay: cannot open " + path.string());
|
||||
file.write(reinterpret_cast<const char*>(bytes->data()), static_cast<std::streamsize>(bytes->size()));
|
||||
if (!file) throw std::runtime_error("frame replay: cannot write " + path.string());
|
||||
file.flush();
|
||||
if (!file) throw std::runtime_error("frame replay: cannot flush " + path.string());
|
||||
file.close();
|
||||
if (!file) throw std::runtime_error("frame replay: cannot close " + path.string());
|
||||
}
|
||||
const bool window = frameStart != calls.end() && call > frameStart;
|
||||
++listed;
|
||||
inWindow += window ? 1 : 0;
|
||||
std::printf("{\"ordinal\":%llu,\"in_window\":%s,\"header\":%llu,\"code\":%llu,\"bytes\":%u,\"type\":%u,\"hash\":\"%016llx\",\"copy\":%zu}\n", static_cast<unsigned long long>(call->ordinal), window ? "true" : "false",
|
||||
static_cast<unsigned long long>(call->address), static_cast<unsigned long long>(code), fields.shader_size, static_cast<unsigned>(fields.type), static_cast<unsigned long long>(hash), copy);
|
||||
}
|
||||
std::printf("{\"registrations\":%zu,\"in_window\":%zu,\"distinct_code\":%zu,\"unreadable\":%zu}\n", listed, inWindow, copies.size(), unreadable);
|
||||
std::fflush(stdout);
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::string_view packetName(std::uint32_t opcode) {
|
||||
switch (opcode) {
|
||||
case 0x10: return "NOP";
|
||||
case 0x15: return "DISPATCH_DIRECT";
|
||||
case 0x16: return "DISPATCH_INDIRECT";
|
||||
case 0x27: return "DRAW_INDEX_2";
|
||||
case 0x2d: return "DRAW_INDEX_AUTO";
|
||||
case 0x35: return "DRAW_INDEX_OFFSET_2";
|
||||
case 0x37: return "WRITE_DATA";
|
||||
case 0x3c: return "WAIT_REG_MEM";
|
||||
case 0x3f: return "INDIRECT_BUFFER";
|
||||
case 0x46: return "EVENT_WRITE";
|
||||
case 0x49: return "RELEASE_MEM";
|
||||
case 0x50: return "DMA_DATA";
|
||||
case 0x58: return "ACQUIRE_MEM";
|
||||
case 0x69: return "SET_CONTEXT_REG";
|
||||
case 0x76: return "SET_SH_REG";
|
||||
case 0x79: return "SET_UCONFIG_REG";
|
||||
default: return {};
|
||||
}
|
||||
}
|
||||
|
||||
void describeSubmission(const FrameCall& call, CapturedMemory& memory) {
|
||||
const auto bytes = call.size <= std::numeric_limits<std::uint64_t>::max() / sizeof(std::uint32_t) ? memory.Read(call.address, call.size * sizeof(std::uint32_t)) : std::nullopt;
|
||||
if (!bytes) {
|
||||
std::printf(" (command buffer not readable)\n");
|
||||
return;
|
||||
}
|
||||
std::vector<std::uint32_t> words(static_cast<std::size_t>(call.size));
|
||||
if (!bytes->empty()) std::memcpy(words.data(), bytes->data(), bytes->size());
|
||||
std::map<std::string, std::size_t> counts;
|
||||
for (std::size_t at = 0; at < call.size;) {
|
||||
const auto header = words[at];
|
||||
const auto type = header >> 30u;
|
||||
const auto length = AgcDriver::Pm4::PacketWords(header);
|
||||
if (length > words.size() - at) {
|
||||
std::printf(" @%zu (truncated packet: %zu dwords, %zu available)\n", at, length, words.size() - at);
|
||||
break;
|
||||
}
|
||||
if (type == 3u && !AgcDriver::Pm4::FillerPacket(header)) {
|
||||
const auto opcode = (header >> 8u) & 0xffu;
|
||||
const auto name = packetName(opcode);
|
||||
char label[24];
|
||||
std::snprintf(label, sizeof(label), "0x%02x", opcode);
|
||||
const std::string key = name.empty() ? std::string(label) : std::string(name);
|
||||
const auto word = [&](std::size_t index) { return index < length ? words[at + index] : 0u; };
|
||||
if (opcode == 0x3c) {
|
||||
std::printf(" @%zu WAIT_REG_MEM function %u space %u address 0x%llx reference 0x%x mask 0x%x\n", at, word(1) & 7u, (word(1) >> 4u) & 3u,
|
||||
static_cast<unsigned long long>(word(2)) | (static_cast<unsigned long long>(word(3)) << 32u), word(4), word(5));
|
||||
} else if (opcode == 0x49 || opcode == 0x37 || opcode == 0x46) {
|
||||
std::printf(" @%zu %s", at, key.c_str());
|
||||
for (std::size_t index = 1; index < length && index < 8; ++index) std::printf(" %08x", word(index));
|
||||
std::printf("\n");
|
||||
}
|
||||
++counts[key];
|
||||
}
|
||||
at += length;
|
||||
}
|
||||
std::printf(" packets:");
|
||||
for (const auto& [name, count] : counts) std::printf(" %s x%zu", name.c_str(), count);
|
||||
std::printf("\n");
|
||||
}
|
||||
|
||||
int listSubmits(const Options& options) {
|
||||
const FrameCaptureReader reader(options.directory);
|
||||
CapturedMemory memory(reader);
|
||||
std::map<std::uint64_t, std::vector<const FrameMemoryChunk*>> groups;
|
||||
for (const auto& chunk : reader.Chunks()) {
|
||||
if (chunk.kind == FrameMemoryKind::Delta) groups[chunk.group].push_back(&chunk);
|
||||
}
|
||||
const auto& calls = reader.Calls();
|
||||
const auto frameStart = std::find_if(calls.begin(), calls.end(), [](const FrameCall& call) { return call.kind == FrameCallKind::FrameStart; });
|
||||
std::size_t listed = 0;
|
||||
for (auto call = calls.begin(); call != calls.end() && listed < options.submits; ++call) {
|
||||
if (call->kind == FrameCallKind::Memory) {
|
||||
if (const auto found = groups.find(call->values.at(0)); found != groups.end()) memory.Apply(found->second);
|
||||
continue;
|
||||
}
|
||||
if (call < frameStart) continue;
|
||||
if (call->kind != FrameCallKind::Submit && call->kind != FrameCallKind::Flip && call->kind != FrameCallKind::ReserveEopFlip) continue;
|
||||
++listed;
|
||||
std::printf("#%zu ordinal %llu %s thread %llu queue 0x%x serials %llu/%llu address 0x%llx dwords %llu\n", listed, static_cast<unsigned long long>(call->ordinal), std::string(AgcDriver::FrameCallName(call->kind)).c_str(),
|
||||
static_cast<unsigned long long>(call->thread), call->queue, static_cast<unsigned long long>(call->completed), static_cast<unsigned long long>(call->accepted),
|
||||
static_cast<unsigned long long>(call->address), static_cast<unsigned long long>(call->size));
|
||||
if (call->kind == FrameCallKind::Submit) describeSubmission(*call, memory);
|
||||
}
|
||||
std::fflush(stdout);
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct Pass {
|
||||
std::size_t firstFrame = 0;
|
||||
std::size_t frames = 0;
|
||||
double seconds = 0.0;
|
||||
std::uint64_t paceTimeouts = 0;
|
||||
std::uint64_t synthesized = 0;
|
||||
};
|
||||
|
||||
double percentile(std::vector<double> values, double fraction) {
|
||||
if (values.empty()) return 0.0;
|
||||
std::sort(values.begin(), values.end());
|
||||
return values[std::min(values.size() - 1, static_cast<std::size_t>(fraction * static_cast<double>(values.size())))];
|
||||
}
|
||||
|
||||
int run(const Options& options) {
|
||||
const auto loadStart = Clock::now();
|
||||
const FrameCaptureReader reader(options.directory);
|
||||
#ifdef _WIN32
|
||||
relaunchIfHostRegionBlocks(reader);
|
||||
#endif
|
||||
if (reader.Truncated()) std::fprintf(stderr, "[frame-replay] the capture is truncated; replaying the complete records\n");
|
||||
auto skipped = restoreMemory(reader);
|
||||
const auto loaded = Clock::now();
|
||||
@@ -728,12 +1089,48 @@ int run(const Options& options) {
|
||||
const auto& calls = reader.Calls();
|
||||
const auto frameStart = std::find_if(calls.begin(), calls.end(), [](const FrameCall& call) { return call.kind == FrameCallKind::FrameStart; });
|
||||
for (auto call = calls.begin(); call != frameStart; ++call) replay.execute(*call);
|
||||
replay.recording = true;
|
||||
replay.startedAt = Clock::now();
|
||||
if (frameStart != calls.end()) replay.runWindow(std::span(calls).subspan(static_cast<std::size_t>(frameStart - calls.begin()) + 1));
|
||||
if (frameStart == calls.end()) AgcDriverWaitIdle_nid_postfix();
|
||||
replay.endedAt = Clock::now();
|
||||
if (const auto failure = replay.frames.Failure()) std::rethrow_exception(failure);
|
||||
const auto journalStart = Clock::now();
|
||||
AgcDriverWaitForPipelineJournal_nid_postfix();
|
||||
const auto journalSeconds = std::chrono::duration<double>(Clock::now() - journalStart).count();
|
||||
std::set<std::uint32_t> initialOutputs;
|
||||
for (const auto& [handle, output] : replay.outputs) initialOutputs.insert(handle);
|
||||
std::vector<Pass> passes;
|
||||
for (std::size_t pass = 0; pass < options.repeat && (pass == 0 || replay.unfinished == 0); ++pass) {
|
||||
if (pass > 0) {
|
||||
resetMemory(reader, replay.skipped);
|
||||
replay.pass = pass;
|
||||
replay.recording = false;
|
||||
for (auto output = replay.outputs.begin(); output != replay.outputs.end();) {
|
||||
if (initialOutputs.contains(output->first)) {
|
||||
++output;
|
||||
continue;
|
||||
}
|
||||
AgcDriverUnregisterVideoOutput_nid_postfix(output->first, output->second);
|
||||
output = replay.outputs.erase(output);
|
||||
}
|
||||
for (const auto handle : initialOutputs) {
|
||||
if (replay.outputs.contains(handle)) continue;
|
||||
auto output = std::make_shared<ReplayOutput>(replay.frames);
|
||||
AgcDriverRegisterVideoOutput_nid_postfix(handle, output);
|
||||
replay.outputs.emplace(handle, std::move(output));
|
||||
}
|
||||
for (auto call = calls.begin(); call != frameStart; ++call) {
|
||||
if (call->kind == FrameCallKind::ReserveEopFlip) replay.execute(*call);
|
||||
}
|
||||
}
|
||||
Pass measured{replay.frames.Times().size(), 0, 0.0, replay.paceTimeouts.load(), replay.syntheticReleases.load()};
|
||||
replay.recording = true;
|
||||
replay.startedAt = Clock::now();
|
||||
if (frameStart != calls.end()) replay.runWindow(std::span(calls).subspan(static_cast<std::size_t>(frameStart - calls.begin()) + 1));
|
||||
else AgcDriverWaitIdle_nid_postfix();
|
||||
replay.endedAt = Clock::now();
|
||||
if (const auto failure = replay.frames.Failure()) std::rethrow_exception(failure);
|
||||
measured.frames = replay.frames.Times().size() - measured.firstFrame;
|
||||
measured.seconds = std::chrono::duration<double>(replay.endedAt - replay.startedAt).count();
|
||||
measured.paceTimeouts = replay.paceTimeouts.load() - measured.paceTimeouts;
|
||||
measured.synthesized = replay.syntheticReleases.load() - measured.synthesized;
|
||||
passes.push_back(measured);
|
||||
}
|
||||
FrameCheck check;
|
||||
if (replay.unfinished == 0) {
|
||||
for (const auto& dump : options.dumps) writeDump(dump);
|
||||
@@ -742,24 +1139,50 @@ int run(const Options& options) {
|
||||
std::fprintf(stderr, "[frame-replay] no dumps written: the GPU work did not finish\n");
|
||||
}
|
||||
const auto ready = replay.frames.Times();
|
||||
const std::size_t firstMeasured = passes.size() > 1 ? 1 : 0;
|
||||
std::vector<double> frameMs;
|
||||
for (std::size_t index = 1; index < ready.size(); ++index) frameMs.push_back(std::chrono::duration<double, std::milli>(ready[index] - ready[index - 1]).count());
|
||||
std::string frameList;
|
||||
for (const auto value : frameMs) frameList += (frameList.empty() ? "" : ",") + std::to_string(static_cast<long long>(value));
|
||||
std::sort(frameMs.begin(), frameMs.end());
|
||||
const auto percentile = [&](double fraction) { return frameMs.empty() ? 0.0 : frameMs[std::min(frameMs.size() - 1, static_cast<std::size_t>(fraction * static_cast<double>(frameMs.size())))]; };
|
||||
const auto seconds = std::chrono::duration<double>(replay.endedAt - replay.startedAt).count();
|
||||
std::printf("{\"frames\":%zu,\"replay_s\":%.3f,\"fps\":%.3f,\"frame_ms\":{\"p50\":%.3f,\"p90\":%.3f,\"max\":%.3f,\"each\":[%s]},\"submits\":%llu,\"delta_mib\":%.1f,\"pace_timeouts\":%llu,\"pace_wait_s\":%.3f,\"stale_registrations\":%llu,\"failed_registrations\":%llu,\"skipped_mib\":%.1f,\"threads\":%zu,\"released_waits\":%llu,\"of_them_from_earlier_writes\":%llu,\"of_them_synthesized\":%llu,\"unfinished\":%llu,\"frames_compared\":%zu,\"pixels_identical\":%.6f,\"max_channel_diff\":%d,\"load_s\":%.3f}\n",
|
||||
ready.size(), seconds, seconds > 0 ? static_cast<double>(ready.size()) / seconds : 0.0, percentile(0.5), percentile(0.9), frameMs.empty() ? 0.0 : frameMs.back(), frameList.c_str(),
|
||||
std::string passList;
|
||||
double seconds = 0.0;
|
||||
std::size_t frames = 0;
|
||||
for (std::size_t index = 0; index < passes.size(); ++index) {
|
||||
const auto& measured = passes[index];
|
||||
std::vector<double> passMs;
|
||||
for (auto frame = measured.firstFrame + 1; frame < measured.firstFrame + measured.frames; ++frame) passMs.push_back(std::chrono::duration<double, std::milli>(ready[frame] - ready[frame - 1]).count());
|
||||
char entry[192];
|
||||
std::snprintf(entry, sizeof(entry), "%s{\"replay_s\":%.3f,\"frames\":%zu,\"p50\":%.3f,\"pace_timeouts\":%llu,\"synthesized\":%llu}", passList.empty() ? "" : ",", measured.seconds, measured.frames,
|
||||
percentile(passMs, 0.5), static_cast<unsigned long long>(measured.paceTimeouts), static_cast<unsigned long long>(measured.synthesized));
|
||||
passList += entry;
|
||||
if (index < firstMeasured) continue;
|
||||
for (const auto value : passMs) frameList += (frameList.empty() ? "" : ",") + std::to_string(static_cast<long long>(value));
|
||||
frameMs.insert(frameMs.end(), passMs.begin(), passMs.end());
|
||||
seconds += measured.seconds;
|
||||
frames += measured.frames;
|
||||
}
|
||||
const auto slowest = frameMs.empty() ? 0.0 : *std::max_element(frameMs.begin(), frameMs.end());
|
||||
std::printf("{\"frames\":%zu,\"replay_s\":%.3f,\"fps\":%.3f,\"frame_ms\":{\"p50\":%.3f,\"p90\":%.3f,\"max\":%.3f,\"each\":[%s]},\"passes\":[%s],\"kept_reservations\":%llu,\"submits\":%llu,\"delta_mib\":%.1f,\"pace_timeouts\":%llu,\"pace_wait_s\":%.3f,\"stale_registrations\":%llu,\"failed_registrations\":%llu,\"skipped_mib\":%.1f,\"threads\":%zu,\"released_waits\":%llu,\"of_them_from_earlier_writes\":%llu,\"of_them_synthesized\":%llu,\"unfinished\":%llu,\"frames_compared\":%zu,\"pixels_identical\":%.6f,\"max_channel_diff\":%d,\"load_s\":%.3f,\"journal_s\":%.3f}\n",
|
||||
frames, seconds, seconds > 0 ? static_cast<double>(frames) / seconds : 0.0, percentile(frameMs, 0.5), percentile(frameMs, 0.9), slowest, frameList.c_str(), passList.c_str(),
|
||||
static_cast<unsigned long long>(replay.keptReservations.load()),
|
||||
static_cast<unsigned long long>(replay.submits.load()), static_cast<double>(replay.deltaBytes.load()) / (1024.0 * 1024.0), static_cast<unsigned long long>(replay.paceTimeouts.load()),
|
||||
replay.paceWaitedSeconds(), static_cast<unsigned long long>(replay.staleRegistrations.load()), static_cast<unsigned long long>(replay.failedRegistrations.load()),
|
||||
static_cast<double>(replay.skipped.bytes) / (1024.0 * 1024.0), replay.threads, static_cast<unsigned long long>(replay.earlyReleases.load()), static_cast<unsigned long long>(replay.lateReleases.load()),
|
||||
static_cast<unsigned long long>(replay.syntheticReleases.load()),
|
||||
static_cast<unsigned long long>(replay.unfinished), check.compared, check.identical, check.maxDifference, std::chrono::duration<double>(loaded - loadStart).count());
|
||||
static_cast<unsigned long long>(replay.unfinished), check.compared, check.identical, check.maxDifference, std::chrono::duration<double>(loaded - loadStart).count(), journalSeconds);
|
||||
std::fflush(stdout);
|
||||
return replay.unfinished == 0 ? 0 : 3;
|
||||
}
|
||||
|
||||
void configureEnvironment(const Options& options) {
|
||||
#ifdef _WIN32
|
||||
_putenv_s("APS5_FRAME_CAPTURE", "");
|
||||
_putenv_s("APS5_HEADLESS_PIPELINE_CACHE", options.coldPipelines ? "" : "1");
|
||||
#else
|
||||
unsetenv("APS5_FRAME_CAPTURE");
|
||||
if (options.coldPipelines) unsetenv("APS5_HEADLESS_PIPELINE_CACHE");
|
||||
else setenv("APS5_HEADLESS_PIPELINE_CACHE", "1", 1);
|
||||
#endif
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
@@ -771,27 +1194,41 @@ int main(int argc, char** argv) {
|
||||
else if (argument == "--no-synthesize") options.synthesize = false;
|
||||
else if (argument == "--release-after-ms" && index + 1 < argc) options.releaseAfter = std::chrono::milliseconds(std::strtoll(argv[++index], nullptr, 10));
|
||||
else if (argument == "--strict") options.strict = true;
|
||||
else if (argument == "--repeat" && index + 1 < argc) options.repeat = std::max<std::size_t>(1, std::strtoull(argv[++index], nullptr, 10));
|
||||
else if (argument == "--shaders") options.shaders = true;
|
||||
else if (argument == "--shader-code" && index + 1 < argc) {
|
||||
options.shaders = true;
|
||||
options.shaderCode = argv[++index];
|
||||
}
|
||||
else if (argument == "--submits" && index + 1 < argc) {
|
||||
const std::string_view value = argv[++index];
|
||||
const auto [end, error] = std::from_chars(value.data(), value.data() + value.size(), options.submits);
|
||||
if (error != std::errc{} || end != value.data() + value.size() || options.submits == 0) return usage();
|
||||
} else if (argument == "--trace-serials") options.traceSerials = true;
|
||||
else if (argument == "--cold-pipelines") options.coldPipelines = true;
|
||||
else if (argument == "--dump" && index + 1 < argc) options.dumps.push_back(parseDump(argv[++index]));
|
||||
else if (argument == "--frames" && index + 1 < argc) options.frames = argv[++index];
|
||||
else if (!argument.starts_with("-") && options.directory.empty()) options.directory = argument;
|
||||
else return usage();
|
||||
}
|
||||
if (options.directory.empty()) return usage();
|
||||
#ifdef _WIN32
|
||||
_putenv_s("APS5_FRAME_CAPTURE", "");
|
||||
#else
|
||||
unsetenv("APS5_FRAME_CAPTURE");
|
||||
#endif
|
||||
configureEnvironment(options);
|
||||
int code = 2;
|
||||
const bool listing = options.shaders || options.submits != 0;
|
||||
try {
|
||||
code = run(options);
|
||||
if (code == 0) {
|
||||
AgcDriverShutdown_nid_postfix();
|
||||
return 0;
|
||||
}
|
||||
code = options.shaders ? listShaders(options) : options.submits != 0 ? listSubmits(options) : run(options);
|
||||
} catch (const std::exception& error) {
|
||||
std::fprintf(stderr, "agc_frame_replay: %s\n", error.what());
|
||||
}
|
||||
if (!listing) {
|
||||
try {
|
||||
AgcDriverShutdown_nid_postfix();
|
||||
} catch (const std::exception& error) {
|
||||
std::fprintf(stderr, "agc_frame_replay: %s\n", error.what());
|
||||
if (code == 0) code = 2;
|
||||
}
|
||||
if (code == 0) return 0;
|
||||
}
|
||||
std::fflush(stdout);
|
||||
std::fflush(stderr);
|
||||
std::_Exit(code);
|
||||
|
||||
@@ -991,8 +991,8 @@ double APS5_VABI sceAmprSinh(double x, double y) {
|
||||
}
|
||||
|
||||
APS5_EXPORT("-vXEQdRADLI", sceAmprStrtokS);
|
||||
char* APS5_VABI sceAmprStrtokS(char* str, const char* delim, char** context) {
|
||||
return SonyStrtokS(str, delim, context);
|
||||
char* APS5_VABI sceAmprStrtokS(char* str, std::size_t* remaining, const char* delimiters, char** context) {
|
||||
return SonyStrtokS(str, remaining, delimiters, context);
|
||||
}
|
||||
|
||||
APS5_EXPORT("2UFh+YKfuzk", sceAmprCtimeS);
|
||||
|
||||
@@ -145,7 +145,7 @@ void APS5_VABI _Lock_shared_ptr_spin_lock_nid_postfix(void);
|
||||
SonyLconv* APS5_VABI sceAmprLocaleconv(void);
|
||||
unsigned __int128 APS5_VABI sceAmprUdivti3(unsigned __int128 dividend, unsigned __int128 divisor);
|
||||
double APS5_VABI sceAmprSinh(double x, double y);
|
||||
char* APS5_VABI sceAmprStrtokS(char* str, const char* delim, char** context);
|
||||
char* APS5_VABI sceAmprStrtokS(char* str, std::size_t* remaining, const char* delimiters, char** context);
|
||||
int APS5_VABI sceAmprCtimeS(char* buffer, std::size_t size, const std::int64_t* time);
|
||||
int APS5_VABI sceAmprWcscpyS(std::uint16_t* dest, std::size_t size, const std::uint16_t* src);
|
||||
void* APS5_VABI sceAmprSetConstraintHandlerS(void* handler);
|
||||
|
||||
@@ -22,6 +22,7 @@ static constexpr int SCE_APP_CONTENT_ERROR_BUSY = static_cast<int>(0x80D90003);
|
||||
static constexpr int SCE_APP_CONTENT_ERROR_NOT_MOUNTED = static_cast<int>(0x80D90004);
|
||||
static constexpr int SCE_APP_CONTENT_ERROR_NOT_FOUND = static_cast<int>(0x80D90005);
|
||||
static constexpr int SCE_APP_CONTENT_ERROR_DRM_NO_ENTITLEMENT = static_cast<int>(0x80D90007);
|
||||
static constexpr int SCE_APP_CONTENT_ERROR_GET_STORE_COUNTRY = static_cast<int>(0x80D91000);
|
||||
static constexpr uint32_t APPPARAM_ID_SKU_FLAG = 0;
|
||||
static constexpr int32_t SKU_FLAG_FULL = 3;
|
||||
|
||||
@@ -62,6 +63,7 @@ bool IsTemporaryMountPoint(const AppContentMountPoint* mountPoint) {
|
||||
return std::strncmp(mountPoint->data, TEMPORARY_MOUNT_POINT, sizeof(mountPoint->data)) == 0;
|
||||
}
|
||||
|
||||
std::atomic<std::uint64_t> download1QuotaBytes{0};
|
||||
std::atomic<std::uint64_t> download2QuotaBytes{0};
|
||||
|
||||
bool IsDownloadMountPoint(const AppContentMountPoint* mountPoint) {
|
||||
@@ -173,10 +175,10 @@ int APS5_VABI sceAppContentDownloadDataGetAvailableSpaceKb(const AppContentMount
|
||||
const std::uint64_t quotaMiB = GetAppDownloadDataSizeMiB_nid_postfix();
|
||||
if (quotaMiB > std::numeric_limits<std::uint64_t>::max() / 1024u) throw std::overflow_error("AppContent download quota exceeds KiB range");
|
||||
quotaKb = quotaMiB * 1024u;
|
||||
} else if (std::strncmp(mountPoint->data, "/download1", sizeof(mountPoint->data)) == 0) {
|
||||
quotaKb = download1QuotaBytes.load() / 1024u;
|
||||
} else if (std::strncmp(mountPoint->data, "/download2", sizeof(mountPoint->data)) == 0) {
|
||||
quotaKb = download2QuotaBytes.load() / 1024u;
|
||||
} else {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
}
|
||||
if (quotaKb == 0) {
|
||||
*availableSpaceKb = 0;
|
||||
@@ -322,8 +324,13 @@ int APS5_VABI sceAppContentDownload0Shrink(void) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAppContentDownload1Expand(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
int APS5_VABI sceAppContentDownload1Expand(size_t size) {
|
||||
if (!initialized.load()) return SCE_APP_CONTENT_ERROR_NOT_INITIALIZED;
|
||||
if (size == 0) return SCE_APP_CONTENT_ERROR_PARAMETER;
|
||||
const auto directory = ResolvePath_nid_no_patch("/download1");
|
||||
if (std::filesystem::create_directories(directory)) RecordWrittenPath_nid_no_patch(directory);
|
||||
size_t current = download1QuotaBytes.load();
|
||||
while (size > current && !download1QuotaBytes.compare_exchange_weak(current, size)) {}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -367,9 +374,12 @@ int APS5_VABI sceAppContentGetAddcontInfoListByIroTag(uint32_t iroTag, AppConten
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAppContentGetDownloadedStoreCountry(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
int APS5_VABI sceAppContentGetDownloadedStoreCountry(uint32_t serviceLabel, const NpUnifiedEntitlementLabel* entitlementLabel, void* storeCountry) {
|
||||
if (!initialized.load()) return SCE_APP_CONTENT_ERROR_NOT_INITIALIZED;
|
||||
(void)serviceLabel;
|
||||
if (!entitlementLabel || !storeCountry) return SCE_APP_CONTENT_ERROR_PARAMETER;
|
||||
if (!validEntitlementLabel(*entitlementLabel, 0x01500000)) return SCE_APP_CONTENT_ERROR_PARAMETER;
|
||||
return SCE_APP_CONTENT_ERROR_GET_STORE_COUNTRY;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAppContentGetEntitlementKey(uint32_t serviceLabel, const NpUnifiedEntitlementLabel* entitlementLabel, AppContentEntitlementKey* key) {
|
||||
|
||||
@@ -36,14 +36,14 @@ int APS5_VABI sceAppContentCheckBundleLicenseOnDisc(uint32_t serviceLabel, const
|
||||
int APS5_VABI sceAppContentAppParamGetString(void);
|
||||
int APS5_VABI sceAppContentDownload0Expand(void);
|
||||
int APS5_VABI sceAppContentDownload0Shrink(void);
|
||||
int APS5_VABI sceAppContentDownload1Expand(void);
|
||||
int APS5_VABI sceAppContentDownload1Expand(size_t size);
|
||||
int APS5_VABI sceAppContentDownload1Shrink(void);
|
||||
int APS5_VABI sceAppContentDownload2Expand(size_t size);
|
||||
int APS5_VABI sceAppContentDownload2Shrink(void);
|
||||
int APS5_VABI sceAppContentUnknown_SWVxsi_ZBlw(void);
|
||||
int APS5_VABI sceAppContentGetAddcontInfoByEntitlementId(const char* entitlementId, AppContentAddcontInfo* info);
|
||||
int APS5_VABI sceAppContentGetAddcontInfoListByIroTag(uint32_t iroTag, AppContentAddcontInfo* list, uint32_t listNum, uint32_t* hitNum);
|
||||
int APS5_VABI sceAppContentGetDownloadedStoreCountry(void);
|
||||
int APS5_VABI sceAppContentGetDownloadedStoreCountry(uint32_t serviceLabel, const NpUnifiedEntitlementLabel* entitlementLabel, void* storeCountry);
|
||||
int APS5_VABI sceAppContentGetEntitlementKey(uint32_t serviceLabel, const NpUnifiedEntitlementLabel* entitlementLabel, AppContentEntitlementKey* key);
|
||||
int APS5_VABI sceAppContentGetGameTrialsFlag(uint32_t* gameTrialsFlag);
|
||||
int APS5_VABI sceAppContentGetPftFlag(uint32_t* pftFlag);
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include "prx/libSceAudioIn/AudioInCapture.hpp"
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
#include <cerrno>
|
||||
#include "SceTypes.hpp"
|
||||
#include "prx/libc/include/General.hpp"
|
||||
#include "prx/libc/include/PreciseWait.hpp"
|
||||
@@ -199,7 +200,12 @@ int32_t APS5_VABI sceAudioInIsPadEmbeddedMic(int32_t handle) {
|
||||
|
||||
int32_t APS5_VABI sceAudioInGetGain(int32_t handle, float* gain) {
|
||||
if (!gain) return AUDIO_IN_ERROR_INVALID_POINTER;
|
||||
*gain = 1.0f;
|
||||
constexpr float defaultGain = 1.0f;
|
||||
const int savedErrno = errno;
|
||||
if (!GuestCopyOut_nid_no_patch(gain, &defaultGain, sizeof(defaultGain))) {
|
||||
errno = savedErrno;
|
||||
return AUDIO_IN_ERROR_INVALID_POINTER;
|
||||
}
|
||||
std::lock_guard lock(g_mutex);
|
||||
return find(handle) ? 0 : AUDIO_IN_ERROR_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
@@ -101,7 +101,7 @@ if(BUILD_TESTING)
|
||||
target_include_directories(audio_out_buffer_size_tests PRIVATE ${LIBS_INCLUDE_DIR} ${ANYPS5_SDL2_INCLUDE_DIR})
|
||||
target_link_libraries(audio_out_buffer_size_tests PRIVATE libc libkernel libScePad)
|
||||
configure_windows_unwind(audio_out_buffer_size_tests)
|
||||
foreach(bufferSizeMode IN ITEMS pass_through wrap mix threshold virtual_overflow invalid_format)
|
||||
foreach(bufferSizeMode IN ITEMS pass_through wrap mix threshold virtual_overflow invalid_format invalid_sample_rate)
|
||||
set(bufferSizeTest "audio_out_buffer_size_${bufferSizeMode}")
|
||||
add_test(NAME ${bufferSizeTest} COMMAND audio_out_buffer_size_tests ${bufferSizeMode})
|
||||
set_tests_properties(${bufferSizeTest} PROPERTIES TIMEOUT 5)
|
||||
|
||||
@@ -85,6 +85,12 @@ void RunInvalidFormat() {
|
||||
Require(sceAudioOutOpen(0, 0, 0, 512, 48000, 0xFF) == expectedError, "format 0xFF did not return SCE_AUDIO_OUT_ERROR_INVALID_FORMAT");
|
||||
Require(opened == 0, "an invalid format opened an audio device");
|
||||
}
|
||||
|
||||
void RunInvalidSampleRate() {
|
||||
constexpr int expectedError = -2144993272;
|
||||
Require(sceAudioOutOpen(0, 0, 0, 512, 0, 0) == expectedError, "sample rate 0 did not return SCE_AUDIO_OUT_ERROR_INVALID_PARAM");
|
||||
Require(opened == 0, "a zero sample rate opened an audio device");
|
||||
}
|
||||
}
|
||||
|
||||
int InitializeSdlAudio_nid_no_patch() { return 0; }
|
||||
@@ -125,6 +131,7 @@ int main(int argc, char** argv) {
|
||||
else if (mode == "threshold") RunOverflow(0x40000000u, 0, sizeof(std::int16_t));
|
||||
else if (mode == "virtual_overflow") RunVirtualOverflow();
|
||||
else if (mode == "invalid_format") RunInvalidFormat();
|
||||
else if (mode == "invalid_sample_rate") RunInvalidSampleRate();
|
||||
else Require(false, "invalid buffer size mode");
|
||||
std::puts("audio out buffer size tests passed");
|
||||
return 0;
|
||||
|
||||
@@ -609,14 +609,18 @@ int APS5_VABI sceAvSettingSet2dVrMode() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAvSettingSetAudioOutModeAny_() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
void APS5_VABI sceAvSettingSetAudioOutModeAny_(void* out, std::int32_t value) {
|
||||
if (out == nullptr) return;
|
||||
if (value < 0) NotImplemented_nid_no_patch(__func__);
|
||||
std::memset(out, 0xff, static_cast<std::size_t>(value));
|
||||
*static_cast<std::int32_t*>(out) = value;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAvSettingSetAudioOutModeInvalid_() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
void APS5_VABI sceAvSettingSetAudioOutModeInvalid_(void* out, std::int32_t value) {
|
||||
if (out == nullptr) return;
|
||||
if (value < 0) NotImplemented_nid_no_patch(__func__);
|
||||
std::memset(out, 0, static_cast<std::size_t>(value));
|
||||
*static_cast<std::int32_t*>(out) = value;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAvSettingSetAvOutputMode() {
|
||||
@@ -648,19 +652,22 @@ int APS5_VABI sceAvSettingSetHdmiGamutMetadata_() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAvSettingSetVideoOutModeAny_() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
void APS5_VABI sceAvSettingSetVideoOutModeAny_(void* out, std::int32_t value) {
|
||||
if (out == nullptr) return;
|
||||
if (value < 0) NotImplemented_nid_no_patch(__func__);
|
||||
std::memset(out, 0xff, static_cast<std::size_t>(value));
|
||||
*static_cast<std::int32_t*>(out) = value;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAvSettingSetVideoOutModeInvalid_() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
void APS5_VABI sceAvSettingSetVideoOutModeInvalid_(void* out, std::int32_t value) {
|
||||
if (out == nullptr) return;
|
||||
if (value < 0) NotImplemented_nid_no_patch(__func__);
|
||||
std::memset(out, 0, static_cast<std::size_t>(value));
|
||||
*static_cast<std::int32_t*>(out) = value;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAvSettingSetVideoOutSource() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
void APS5_VABI sceAvSettingSetVideoOutSource(std::int32_t* out, std::int32_t source) {
|
||||
if (out != nullptr) *out = source;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAvSettingSetVideoOutputColorEffect() {
|
||||
|
||||
@@ -118,17 +118,17 @@ int APS5_VABI sceAvSettingNotifyVideoOutMode(void);
|
||||
int APS5_VABI sceAvSettingRemoveCallbacks(void);
|
||||
int APS5_VABI sceAvSettingRemoveProcessOutputMode(void);
|
||||
int APS5_VABI sceAvSettingSet2dVrMode(void);
|
||||
int APS5_VABI sceAvSettingSetAudioOutModeAny_(void);
|
||||
int APS5_VABI sceAvSettingSetAudioOutModeInvalid_(void);
|
||||
void APS5_VABI sceAvSettingSetAudioOutModeAny_(void* out, std::int32_t value);
|
||||
void APS5_VABI sceAvSettingSetAudioOutModeInvalid_(void* out, std::int32_t value);
|
||||
int APS5_VABI sceAvSettingSetAvOutputMode(void);
|
||||
int APS5_VABI sceAvSettingSetDispclk(void);
|
||||
int APS5_VABI sceAvSettingSetDisplayBlackLevel(void);
|
||||
int APS5_VABI sceAvSettingSetHdcpMode(void);
|
||||
int APS5_VABI sceAvSettingSetHdcpStatus(void);
|
||||
int APS5_VABI sceAvSettingSetHdmiGamutMetadata_(void);
|
||||
int APS5_VABI sceAvSettingSetVideoOutModeAny_(void);
|
||||
int APS5_VABI sceAvSettingSetVideoOutModeInvalid_(void);
|
||||
int APS5_VABI sceAvSettingSetVideoOutSource(void);
|
||||
void APS5_VABI sceAvSettingSetVideoOutModeAny_(void* out, std::int32_t value);
|
||||
void APS5_VABI sceAvSettingSetVideoOutModeInvalid_(void* out, std::int32_t value);
|
||||
void APS5_VABI sceAvSettingSetVideoOutSource(std::int32_t* out, std::int32_t source);
|
||||
int APS5_VABI sceAvSettingSetVideoOutputColorEffect(void);
|
||||
int APS5_VABI sceAvSettingSimulateProcessOutputModeArbitration_(void);
|
||||
int APS5_VABI sceAvSettingTerm(void);
|
||||
|
||||
@@ -6,27 +6,37 @@
|
||||
#include "SceTypes.hpp"
|
||||
#include "prx/libc/include/General.hpp"
|
||||
|
||||
namespace {
|
||||
|
||||
void* LogFunction = nullptr;
|
||||
int LogLevel = 0;
|
||||
|
||||
struct MemoryManager {
|
||||
void* allocate = nullptr;
|
||||
void* deallocate = nullptr;
|
||||
void* reallocate = nullptr;
|
||||
};
|
||||
MemoryManager memoryManager;
|
||||
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
||||
APS5_DUMMY_FUN
|
||||
int APS5_VABI bemp2sys_logprintf_getlevel_nid_postfix(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
return LogLevel;
|
||||
}
|
||||
|
||||
int APS5_VABI bemp2sys_logprintf_setfunc_nid_postfix(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
void APS5_VABI bemp2sys_logprintf_setfunc_nid_postfix(void* function) {
|
||||
LogFunction = function;
|
||||
}
|
||||
|
||||
int APS5_VABI bemp2sys_logprintf_setlevel_nid_postfix(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
void APS5_VABI bemp2sys_logprintf_setlevel_nid_postfix(int level) {
|
||||
if (static_cast<unsigned int>(level) <= 3u) LogLevel = level;
|
||||
}
|
||||
|
||||
int APS5_VABI bemp2sys_memmanager_set_nid_postfix(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
void APS5_VABI bemp2sys_memmanager_set_nid_postfix(void* allocate, void* deallocate, void* reallocate) {
|
||||
memoryManager = {allocate, deallocate, reallocate};
|
||||
}
|
||||
|
||||
int APS5_VABI bemp2sys_packetizer_create_nid_postfix(void) {
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
extern "C" {
|
||||
|
||||
int APS5_VABI bemp2sys_logprintf_getlevel_nid_postfix(void);
|
||||
int APS5_VABI bemp2sys_logprintf_setfunc_nid_postfix(void);
|
||||
int APS5_VABI bemp2sys_logprintf_setlevel_nid_postfix(void);
|
||||
int APS5_VABI bemp2sys_memmanager_set_nid_postfix(void);
|
||||
void APS5_VABI bemp2sys_logprintf_setfunc_nid_postfix(void* function);
|
||||
void APS5_VABI bemp2sys_logprintf_setlevel_nid_postfix(int level);
|
||||
void APS5_VABI bemp2sys_memmanager_set_nid_postfix(void* allocate, void* deallocate, void* reallocate);
|
||||
int APS5_VABI bemp2sys_packetizer_create_nid_postfix(void);
|
||||
int APS5_VABI bemp2sys_packetizer_destroy_nid_postfix(void);
|
||||
int APS5_VABI bemp2sys_packetizer_init_nid_postfix(void);
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include "prx/libSceUserService/UserService.hpp"
|
||||
#include "SceTypes.hpp"
|
||||
#include <atomic>
|
||||
#include <cerrno>
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
#include <cstring>
|
||||
@@ -60,7 +61,11 @@ int APS5_VABI sceG2PDialogGetResult(CommonDialogResultPrefix* result) {
|
||||
const int status = g_status.load();
|
||||
if (status == COMMON_DIALOG_STATUS_NONE) return COMMON_DIALOG_ERROR_NOT_INITIALIZED;
|
||||
if (status != COMMON_DIALOG_STATUS_FINISHED) return COMMON_DIALOG_ERROR_NOT_FINISHED;
|
||||
result->result = COMMON_DIALOG_RESULT_USER_CANCELED;
|
||||
const std::int32_t cancelResult = COMMON_DIALOG_RESULT_USER_CANCELED;
|
||||
const int savedErrno = errno;
|
||||
const bool copied = GuestCopyOut_nid_no_patch(&result->result, &cancelResult, sizeof(cancelResult));
|
||||
errno = savedErrno;
|
||||
if (!copied) return COMMON_DIALOG_ERROR_INVALID_ARGUMENT;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -42,8 +42,10 @@ if(BUILD_TESTING)
|
||||
endif()
|
||||
|
||||
if(BUILD_TESTING)
|
||||
foreach(variant IN ITEMS unicode unicode_module japanese japanese_module wide wide_module chinese chinese_module mbc mbc_module jis2004 jis2004_module iso646 iso646_module iso2022 iso2022_module setters setters_module)
|
||||
if(variant MATCHES "^jis2004")
|
||||
foreach(variant IN ITEMS unicode unicode_module japanese japanese_module wide wide_module chinese chinese_module mbc mbc_module jis2004 jis2004_module iso646 iso646_module iso2022 iso2022_module setters setters_module jiscs jiscs_module)
|
||||
if(variant MATCHES "^jiscs")
|
||||
add_test_executable(guest_ces_${variant}_tests ${LIBS_INCLUDE_DIR}/tests/GuestCesJiscs.cpp)
|
||||
elseif(variant MATCHES "^jis2004")
|
||||
add_test_executable(guest_ces_${variant}_tests ${LIBS_INCLUDE_DIR}/tests/GuestCesJis2004.cpp)
|
||||
elseif(variant MATCHES "^setters")
|
||||
add_test_executable(guest_ces_${variant}_tests ${LIBS_INCLUDE_DIR}/tests/GuestCesProfileSetters.cpp)
|
||||
@@ -72,7 +74,7 @@ if(BUILD_TESTING)
|
||||
add_test(NAME guest_ces_${variant} COMMAND guest_ces_${variant}_tests)
|
||||
endforeach()
|
||||
if(WIN32)
|
||||
set_tests_properties(guest_ces_unicode guest_ces_unicode_module guest_ces_japanese guest_ces_japanese_module guest_ces_wide guest_ces_wide_module guest_ces_chinese guest_ces_chinese_module guest_ces_mbc guest_ces_mbc_module guest_ces_jis2004 guest_ces_jis2004_module guest_ces_iso646 guest_ces_iso646_module guest_ces_iso2022 guest_ces_iso2022_module guest_ces_setters guest_ces_setters_module PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
set_tests_properties(guest_ces_unicode guest_ces_unicode_module guest_ces_japanese guest_ces_japanese_module guest_ces_wide guest_ces_wide_module guest_ces_chinese guest_ces_chinese_module guest_ces_mbc guest_ces_mbc_module guest_ces_jis2004 guest_ces_jis2004_module guest_ces_iso646 guest_ces_iso646_module guest_ces_iso2022 guest_ces_iso2022_module guest_ces_setters guest_ces_setters_module guest_ces_jiscs guest_ces_jiscs_module PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceCesCs>")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
@@ -3,11 +3,13 @@
|
||||
#include <cstddef>
|
||||
#include <algorithm>
|
||||
#include <bit>
|
||||
#include <charconv>
|
||||
#include <cstring>
|
||||
#include <initializer_list>
|
||||
#include <iterator>
|
||||
#include <span>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
@@ -1777,6 +1779,81 @@ int utf32ToSbc(const uint32_t* utf32, const uint8_t* profile, uint8_t* sbc) {
|
||||
return scalarToSbc(loadUnit<uint32_t, BigEndian>(utf32), profile, sbc);
|
||||
}
|
||||
|
||||
std::string hexCode(uint32_t value) {
|
||||
char digits[8];
|
||||
const auto end = std::to_chars(digits, digits + sizeof digits, value, 16).ptr;
|
||||
return "0x" + std::string(digits, end);
|
||||
}
|
||||
|
||||
constexpr uint32_t JISCS_PLANE1 = 0x03010000;
|
||||
constexpr uint32_t JISCS_X0213_PLANE2 = 0x03020000;
|
||||
constexpr uint32_t JISCS_X0212 = 0x04000000;
|
||||
|
||||
bool isJiscsProfile(const void* profile) {
|
||||
return isSjis(profile) || isEucJp(profile) || isJis2004(profile);
|
||||
}
|
||||
|
||||
bool isJisGraphic(uint32_t byte) {
|
||||
return byte >= 0x21 && byte <= 0x7E;
|
||||
}
|
||||
|
||||
bool isMeasuredJiscs(uint32_t jiscs) {
|
||||
return jiscs <= 0xFF || (jiscs >> 16 <= 2 && isJisGraphic(jiscs >> 8 & 0xFF) && isJisGraphic(jiscs & 0xFF));
|
||||
}
|
||||
|
||||
JisStrict jisX0208Strictness(const void* profile) {
|
||||
return isSjisX0208(profile) ? JisStrict::Sjis : isEucJpX0208(profile) ? JisStrict::Euc : JisStrict::None;
|
||||
}
|
||||
|
||||
uint32_t jiscsCell(uint32_t base, uint32_t cell) {
|
||||
return base | (cell / 94 + 1) << 8 | (cell % 94 + 1);
|
||||
}
|
||||
|
||||
int jiscsToUnicode(const void* profile, uint32_t jiscs, uint32_t& code) {
|
||||
if (jiscs <= 0xFF) {
|
||||
const auto byte = static_cast<uint8_t>(jiscs);
|
||||
if (byte <= 0x9F) code = isSjisX0208(profile) ? sjisX0208Single(byte) : byte;
|
||||
else if (byte >= 0xA1 && byte <= 0xDF && hasSs2(profile)) code = 0xFF61 + (byte - 0xA1);
|
||||
else return CES_ERROR_UNASSIGNED_CODE;
|
||||
return 0;
|
||||
}
|
||||
if (jiscs >> 16 != 0) return CES_ERROR_ILLEGAL_CODE;
|
||||
const uint32_t index = jisIndex(static_cast<uint16_t>(jiscs));
|
||||
code = isJis2004(profile) ? JisX0213::PLANE1[index] : jisX0208ToUnicode(jisX0208Strictness(profile), index);
|
||||
return code != 0 ? 0 : CES_ERROR_UNASSIGNED_CODE;
|
||||
}
|
||||
|
||||
int unicodeToJiscs(const void* profile, uint32_t code, uint32_t& jiscs) {
|
||||
const bool strictSjis = isSjisX0208(profile);
|
||||
if (strictSjis && (code == 0x00A5 || code == 0x203E)) {
|
||||
jiscs = code == 0x00A5 ? 0x5C : 0x7E;
|
||||
return 0;
|
||||
}
|
||||
if (code <= 0x9F && !(strictSjis && (code == 0x5C || code == 0x7E))) {
|
||||
jiscs = code;
|
||||
return 0;
|
||||
}
|
||||
if (code >= 0xFF61 && code <= 0xFF9F) {
|
||||
if (!hasSs2(profile)) return CES_ERROR_UNASSIGNED_CODE;
|
||||
jiscs = 0xA1 + (code - 0xFF61);
|
||||
return 0;
|
||||
}
|
||||
if (isJis2004(profile)) {
|
||||
const auto& reverse = jis2004Reverse();
|
||||
const auto found = std::lower_bound(reverse.begin(), reverse.end(), code, [](const Jis2004Cell& entry, uint32_t value) { return entry.code < value; });
|
||||
if (found == reverse.end() || found->code != code || (found->plane == JIS_X0212_PLANE && isSjis2004(profile))) return CES_ERROR_UNASSIGNED_CODE;
|
||||
jiscs = jiscsCell(found->plane == JIS_X0212_PLANE ? JISCS_X0212 : found->plane == 2 ? JISCS_X0213_PLANE2 : JISCS_PLANE1, found->cell);
|
||||
return 0;
|
||||
}
|
||||
const JisStrict strict = jisX0208Strictness(profile);
|
||||
if (strict == JisStrict::None) code = jisAlias(code);
|
||||
const auto& reverse = jisReverse(strict);
|
||||
const auto found = std::lower_bound(reverse.begin(), reverse.end(), static_cast<uint16_t>(code), [](const JisCell& entry, uint16_t value) { return entry.code < value; });
|
||||
if (found == reverse.end() || found->code != code || (found->supplementary && !hasSs3(profile))) return CES_ERROR_UNASSIGNED_CODE;
|
||||
jiscs = jiscsCell(found->supplementary ? JISCS_X0212 : JISCS_PLANE1, found->cell);
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
@@ -2916,6 +2993,34 @@ void* APS5_VABI sceCesUcsProfileInitSJis2004X0213(void* sheet) {
|
||||
return sheet;
|
||||
}
|
||||
|
||||
int APS5_VABI sceCesJiscsToUcs(const void* profile, uint32_t jiscs, uint32_t* ucs) {
|
||||
if (!isMeasuredJiscs(jiscs)) throw std::runtime_error("sceCesJiscsToUcs: code " + hexCode(jiscs) + " is neither a single byte nor a row and cell 0x21-0x7E of planes 0-2; its form is not implemented");
|
||||
if (ucs) *ucs = 0;
|
||||
if (!profile) return CES_ERROR_INVALID_PROFILE;
|
||||
if (!isJiscsProfile(profile)) throw std::runtime_error("sceCesJiscsToUcs: a profile other than the eight JIS profiles is not implemented");
|
||||
uint32_t code = 0;
|
||||
const int result = jiscsToUnicode(profile, jiscs, code);
|
||||
if (result != 0) return result;
|
||||
if (!ucs) return CES_ERROR_INVALID_DST_BUFFER;
|
||||
*ucs = code;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceCesUcsToJiscs(uint32_t ucs, const void* profile, uint32_t* jiscs) {
|
||||
if (jiscs) *jiscs = 0;
|
||||
if (!isScalar(ucs)) return CES_ERROR_ILLEGAL_CODE;
|
||||
if (ucs > 0xFFFF && ucs >> 16 != 2) throw std::runtime_error("sceCesUcsToJiscs: code point " + hexCode(ucs) + " outside the BMP and plane 2 is not implemented");
|
||||
if (!profile) return CES_ERROR_INVALID_PROFILE;
|
||||
if (!isJiscsProfile(profile)) throw std::runtime_error("sceCesUcsToJiscs: a profile other than the eight JIS profiles is not implemented");
|
||||
if (ucs > 0xFFFF && !isJis2004(profile)) throw std::runtime_error("sceCesUcsToJiscs: plane 2 with a JIS X 0208 profile is not implemented");
|
||||
uint32_t code = 0;
|
||||
const int result = unicodeToJiscs(profile, ucs, code);
|
||||
if (result != 0) return result;
|
||||
if (!jiscs) return CES_ERROR_INVALID_DST_BUFFER;
|
||||
*jiscs = code;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void* APS5_VABI sceCesUcsProfileInitIso2022JpRfc1468(void* sheet) {
|
||||
if (!sheet) return nullptr;
|
||||
std::memcpy(sheet, ISO2022_RFC1468_TAG, sizeof(ISO2022_RFC1468_TAG));
|
||||
|
||||
@@ -22,11 +22,6 @@ int APS5_VABI sceCesIso2022JpUcsProfileSetSbcs(void) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceCesJiscsToUcs(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceCesRefersUcsProfileCp1253(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
@@ -42,9 +37,4 @@ int APS5_VABI sceCesRefersUcsProfileCp1257(void) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceCesUcsToJiscs(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -228,7 +228,7 @@ int APS5_VABI sceCesIso2022StrToUtf8Str(void* context, const uint8_t* iso, uint3
|
||||
int APS5_VABI sceCesIso2022UcsContextInit(void* context, const void* profile);
|
||||
int APS5_VABI sceCesIso2022UcsContextInitCopy(void* context, const void* source);
|
||||
int APS5_VABI sceCesJisGetLevel(uint8_t plane, uint8_t row, uint8_t cell, uint32_t* level);
|
||||
int APS5_VABI sceCesJiscsToUcs(void);
|
||||
int APS5_VABI sceCesJiscsToUcs(const void* profile, uint32_t jiscs, uint32_t* ucs);
|
||||
int APS5_VABI sceCesMbcsUcsSetMbcsReplacementCharCode(void* context, uint32_t code);
|
||||
int APS5_VABI sceCesMbcsUcsSetMbcsReplacementCharUCode(void* context, uint32_t ucode);
|
||||
const uint8_t* APS5_VABI sceCesRefersUcsProfileAscii(void);
|
||||
@@ -263,7 +263,7 @@ void* APS5_VABI sceCesUcsProfileInitIso2022JpCp50221(void* sheet);
|
||||
void* APS5_VABI sceCesUcsProfileInitIso2022JpCp50222(void* sheet);
|
||||
void* APS5_VABI sceCesUcsProfileInitIso2022JpRfc1468(void* sheet);
|
||||
void* APS5_VABI sceCesUcsProfileInitSJis2004X0213(void* sheet);
|
||||
int APS5_VABI sceCesUcsToJiscs(void);
|
||||
int APS5_VABI sceCesUcsToJiscs(uint32_t ucs, const void* profile, uint32_t* jiscs);
|
||||
int APS5_VABI sceCesUhcUcsProfileSetSbcs(void* p, const void* sbcs);
|
||||
int APS5_VABI sceCesUtf16StrGetIso2022Len(void* context, const uint16_t* utf16, uint32_t utf16max, uint32_t* utf16Len, uint32_t* isoLen);
|
||||
int APS5_VABI sceCesUtf16StrToIso2022Str(void* context, const uint16_t* utf16, uint32_t utf16max, uint32_t* utf16Len, uint8_t* iso, uint32_t isoMax, uint32_t* isoLen);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "prx/libSceCompanionHttpd/include/Api.hpp"
|
||||
#include <cerrno>
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
#include <cstring>
|
||||
@@ -37,7 +38,11 @@ extern "C" {
|
||||
|
||||
int APS5_VABI sceCompanionHttpdOptParamInitialize(CompanionHttpdOptParam* param) {
|
||||
if (param == nullptr) return CompanionHttpdErrorInvalidParam;
|
||||
std::memset(param, 0, sizeof(*param));
|
||||
CompanionHttpdOptParam zero{};
|
||||
const int savedErrno = errno;
|
||||
const bool copied = GuestCopyOut_nid_no_patch(param, &zero, sizeof(zero));
|
||||
errno = savedErrno;
|
||||
if (!copied) return CompanionHttpdErrorInvalidParam;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -190,16 +190,26 @@ int APS5_VABI sceContentBinderStart() {
|
||||
int APS5_VABI sceContentBinderTerminate() {
|
||||
return static_cast<int>(0x809D000F);
|
||||
}
|
||||
int APS5_VABI sceContentSearchGetApplicationLastUpdateId() {
|
||||
int APS5_VABI sceContentSearchGetApplicationLastUpdateId(std::int64_t* lastUpdateId) {
|
||||
if (!g_initialized.load()) return ErrorNotInitialized;
|
||||
if (lastUpdateId == nullptr) return ErrorInvalidArgument;
|
||||
*lastUpdateId = LibraryUpdateId;
|
||||
return 0;
|
||||
}
|
||||
int APS5_VABI sceContentSearchGetMyApplicationIndex(std::int32_t* index) {
|
||||
if (!g_initialized.load()) return ErrorNotInitialized;
|
||||
if (index == nullptr) return ErrorInvalidArgument;
|
||||
*index = 0;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
int APS5_VABI sceContentSearchGetMyApplicationIndex() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
int APS5_VABI sceContentSearchGetNumOfApplication() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
int APS5_VABI sceContentSearchGetNumOfApplication(const ContentSearchContentColumnSet* columnSet, std::uint32_t columnSetLength, const ContentSearchContentOrderByCondition* orderByConditions, std::uint32_t orderByConditionsLength, std::int64_t* numOfApplication, std::int64_t* lastUpdateId) {
|
||||
if (!g_initialized.load()) return ErrorNotInitialized;
|
||||
if ((columnSet == nullptr) != (columnSetLength == 0) || (orderByConditions == nullptr) != (orderByConditionsLength == 0) || numOfApplication == nullptr || lastUpdateId == nullptr) return ErrorInvalidArgument;
|
||||
ValidateColumnSets(__func__, columnSet, columnSetLength);
|
||||
ValidateOrderBy(__func__, orderByConditions, orderByConditionsLength);
|
||||
*numOfApplication = 0;
|
||||
*lastUpdateId = LibraryUpdateId;
|
||||
return 0;
|
||||
}
|
||||
int APS5_VABI sceContentSearchSearchApplication() {
|
||||
|
||||
@@ -23,9 +23,9 @@ int APS5_VABI sceContentBinderReset();
|
||||
int APS5_VABI sceContentBinderSearchDifference();
|
||||
int APS5_VABI sceContentBinderStart();
|
||||
int APS5_VABI sceContentBinderTerminate();
|
||||
int APS5_VABI sceContentSearchGetApplicationLastUpdateId();
|
||||
int APS5_VABI sceContentSearchGetMyApplicationIndex();
|
||||
int APS5_VABI sceContentSearchGetNumOfApplication();
|
||||
int APS5_VABI sceContentSearchGetApplicationLastUpdateId(std::int64_t* lastUpdateId);
|
||||
int APS5_VABI sceContentSearchGetMyApplicationIndex(std::int32_t* index);
|
||||
int APS5_VABI sceContentSearchGetNumOfApplication(const ContentSearchContentColumnSet* columnSet, std::uint32_t columnSetLength, const ContentSearchContentOrderByCondition* orderByConditions, std::uint32_t orderByConditionsLength, std::int64_t* numOfApplication, std::int64_t* lastUpdateId);
|
||||
int APS5_VABI sceContentSearchSearchApplication();
|
||||
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
#include "prx/libSceEditMp4/include/Api.hpp"
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
#include <array>
|
||||
#include <cerrno>
|
||||
#include <cstring>
|
||||
#include "SceTypes.hpp"
|
||||
#include "prx/libc/include/General.hpp"
|
||||
@@ -29,12 +31,14 @@ int APS5_VABI sceEditMp4Initialize() {
|
||||
|
||||
void APS5_VABI sceEditMp4InitializeParam(void* param) {
|
||||
if (param == nullptr) return;
|
||||
auto* bytes = static_cast<std::uint8_t*>(param);
|
||||
std::array<std::uint8_t, 0x1c> initial{};
|
||||
const std::uint64_t at10 = 0x3f;
|
||||
const std::uint32_t at18 = 700;
|
||||
std::memset(bytes, 0, 0x10);
|
||||
std::memcpy(bytes + 0x10, &at10, sizeof(at10));
|
||||
std::memcpy(bytes + 0x18, &at18, sizeof(at18));
|
||||
std::memcpy(initial.data() + 0x10, &at10, sizeof(at10));
|
||||
std::memcpy(initial.data() + 0x18, &at18, sizeof(at18));
|
||||
const int savedErrno = errno;
|
||||
GuestCopyOut_nid_no_patch(param, initial.data(), initial.size());
|
||||
errno = savedErrno;
|
||||
}
|
||||
|
||||
std::uint64_t APS5_VABI sceEditMp4QueryMemSize() {
|
||||
|
||||
@@ -624,7 +624,13 @@ int32_t APS5_VABI sceFiberRename(FiberObject* object, const char* name) {
|
||||
|
||||
int32_t APS5_VABI sceFiberOptParamInitialize(FiberOptParam* opt_param) {
|
||||
if (!opt_param) return SCE_FIBER_ERROR_NULL;
|
||||
std::memset(opt_param, 0, FIBER_OPT_PARAM_SIZE);
|
||||
const std::uint8_t zero[FIBER_OPT_PARAM_SIZE] = {};
|
||||
const auto preserved = errno;
|
||||
if (!GuestCopyOut_nid_no_patch(opt_param, zero, sizeof(zero))) {
|
||||
errno = preserved;
|
||||
return SCE_FIBER_ERROR_INVALID;
|
||||
}
|
||||
errno = preserved;
|
||||
return SCE_OK;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ add_library(libSceFreeType SHARED EXCLUDE_FROM_ALL
|
||||
src/Outline.cpp
|
||||
src/Charmap.cpp
|
||||
src/FaceInfo.cpp
|
||||
src/Stroker.cpp src/OutlineStorage.cpp src/FaceCompat.cpp src/TrackKerning.cpp
|
||||
src/Stroker.cpp src/OutlineStorage.cpp src/FaceCompat.cpp src/TrackKerning.cpp src/LcdFilter.cpp src/ModuleManagement.cpp src/OutlineTransform.cpp
|
||||
src/GuestObjects.cpp
|
||||
src/Names.cpp
|
||||
src/Trigonometry.cpp
|
||||
@@ -185,6 +185,61 @@ if(BUILD_TESTING)
|
||||
set_tests_properties(guest_freetype_track_kerning PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceFreeType>")
|
||||
endif()
|
||||
add_test_executable(guest_freetype_slot_synthesis_tests ${LIBS_INCLUDE_DIR}/tests/GuestFreeTypeSlotSynthesis.cpp)
|
||||
target_include_directories(guest_freetype_slot_synthesis_tests PRIVATE ${LIBS_INCLUDE_DIR})
|
||||
target_link_libraries(guest_freetype_slot_synthesis_tests PRIVATE libSceFreeType freetype libc)
|
||||
configure_windows_unwind(guest_freetype_slot_synthesis_tests)
|
||||
add_test(NAME guest_freetype_slot_synthesis COMMAND guest_freetype_slot_synthesis_tests)
|
||||
if(WIN32)
|
||||
set_tests_properties(guest_freetype_slot_synthesis PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceFreeType>")
|
||||
endif()
|
||||
add_test_executable(guest_freetype_bitmap_embolden_tests ${LIBS_INCLUDE_DIR}/tests/GuestFreeTypeBitmapEmbolden.cpp)
|
||||
target_include_directories(guest_freetype_bitmap_embolden_tests PRIVATE ${LIBS_INCLUDE_DIR})
|
||||
target_link_libraries(guest_freetype_bitmap_embolden_tests PRIVATE libSceFreeType freetype libc)
|
||||
configure_windows_unwind(guest_freetype_bitmap_embolden_tests)
|
||||
add_test(NAME guest_freetype_bitmap_embolden COMMAND guest_freetype_bitmap_embolden_tests)
|
||||
if(WIN32)
|
||||
set_tests_properties(guest_freetype_bitmap_embolden PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceFreeType>")
|
||||
endif()
|
||||
add_test_executable(guest_freetype_slot_own_bitmap_tests ${LIBS_INCLUDE_DIR}/tests/GuestFreeTypeSlotOwnBitmap.cpp)
|
||||
target_include_directories(guest_freetype_slot_own_bitmap_tests PRIVATE ${LIBS_INCLUDE_DIR})
|
||||
target_link_libraries(guest_freetype_slot_own_bitmap_tests PRIVATE libSceFreeType freetype libc)
|
||||
configure_windows_unwind(guest_freetype_slot_own_bitmap_tests)
|
||||
add_test(NAME guest_freetype_slot_own_bitmap COMMAND guest_freetype_slot_own_bitmap_tests)
|
||||
if(WIN32)
|
||||
set_tests_properties(guest_freetype_slot_own_bitmap PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceFreeType>")
|
||||
endif()
|
||||
|
||||
add_test_executable(guest_freetype_lcd_filter_tests ${LIBS_INCLUDE_DIR}/tests/GuestFreeTypeLcdFilter.cpp)
|
||||
target_include_directories(guest_freetype_lcd_filter_tests PRIVATE ${LIBS_INCLUDE_DIR})
|
||||
target_link_libraries(guest_freetype_lcd_filter_tests PRIVATE libSceFreeType freetype libc)
|
||||
configure_windows_unwind(guest_freetype_lcd_filter_tests)
|
||||
add_test(NAME guest_freetype_lcd_filter COMMAND guest_freetype_lcd_filter_tests)
|
||||
if(WIN32)
|
||||
set_tests_properties(guest_freetype_lcd_filter PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceFreeType>")
|
||||
endif()
|
||||
add_test_executable(guest_freetype_module_management_tests ${LIBS_INCLUDE_DIR}/tests/GuestFreeTypeModuleManagement.cpp)
|
||||
target_include_directories(guest_freetype_module_management_tests PRIVATE ${LIBS_INCLUDE_DIR})
|
||||
target_link_libraries(guest_freetype_module_management_tests PRIVATE libSceFreeType freetype libc)
|
||||
configure_windows_unwind(guest_freetype_module_management_tests)
|
||||
add_test(NAME guest_freetype_module_management COMMAND guest_freetype_module_management_tests)
|
||||
if(WIN32)
|
||||
set_tests_properties(guest_freetype_module_management PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceFreeType>")
|
||||
endif()
|
||||
add_test_executable(guest_freetype_outline_transform_tests ${LIBS_INCLUDE_DIR}/tests/GuestFreeTypeOutlineTransform.cpp)
|
||||
target_include_directories(guest_freetype_outline_transform_tests PRIVATE ${LIBS_INCLUDE_DIR})
|
||||
target_link_libraries(guest_freetype_outline_transform_tests PRIVATE libSceFreeType freetype libc)
|
||||
configure_windows_unwind(guest_freetype_outline_transform_tests)
|
||||
add_test(NAME guest_freetype_outline_transform COMMAND guest_freetype_outline_transform_tests)
|
||||
if(WIN32)
|
||||
set_tests_properties(guest_freetype_outline_transform PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceFreeType>")
|
||||
endif()
|
||||
add_test_executable(guest_freetype_charmap_tests ${LIBS_INCLUDE_DIR}/tests/GuestFreeTypeCharmap.cpp)
|
||||
target_include_directories(guest_freetype_charmap_tests PRIVATE ${LIBS_INCLUDE_DIR})
|
||||
target_link_libraries(guest_freetype_charmap_tests PRIVATE libSceFreeType freetype libc)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "prx/libSceFreeType/include/Api.hpp"
|
||||
#include "prx/libSceFreeType/include/GuestObjects.hpp"
|
||||
#include FT_OUTLINE_H
|
||||
#include FT_SYNTHESIS_H
|
||||
#include "prx/libc/include/FilesystemError.hpp"
|
||||
#include "prx/libc/include/General.hpp"
|
||||
|
||||
@@ -312,6 +313,20 @@ int APS5_VABI FT_Render_Glyph_nid_postfix(GuestGlyphSlotRec* slot, std::int32_t
|
||||
return error;
|
||||
}
|
||||
|
||||
void APS5_VABI FT_GlyphSlot_Oblique_nid_postfix(GuestGlyphSlotRec* slot) {
|
||||
GuestFace* owner = FaceOf(slot);
|
||||
if (owner) WriteBackOutline(*owner);
|
||||
FT_GlyphSlot_Oblique(owner ? owner->host->glyph : nullptr);
|
||||
if (owner) SyncSlot(*owner);
|
||||
}
|
||||
|
||||
void APS5_VABI FT_GlyphSlot_Embolden_nid_postfix(GuestGlyphSlotRec* slot) {
|
||||
GuestFace* owner = FaceOf(slot);
|
||||
if (owner) WriteBackOutline(*owner);
|
||||
FT_GlyphSlot_Embolden(owner ? owner->host->glyph : nullptr);
|
||||
if (owner) SyncSlot(*owner);
|
||||
}
|
||||
|
||||
int APS5_VABI FT_Select_Charmap_nid_postfix(GuestFaceRec* face, std::int32_t encoding) {
|
||||
GuestFace* owner = FaceOf(face);
|
||||
const FT_Error error = FT_Select_Charmap(owner ? owner->host : nullptr, static_cast<FT_Encoding>(encoding));
|
||||
|
||||
@@ -106,6 +106,9 @@ int APS5_VABI FT_Stroker_GetCounts_nid_postfix(void* stroker, std::uint32_t* poi
|
||||
int APS5_VABI FT_Stroker_CubicTo_nid_postfix(void* stroker, const FreeType::GuestVector* control1, const FreeType::GuestVector* control2, const FreeType::GuestVector* to);
|
||||
void APS5_VABI FT_Stroker_Done_nid_postfix(void* stroker);
|
||||
int APS5_VABI FT_Stroker_GetBorderCounts_nid_postfix(void* stroker, std::int32_t border, std::uint32_t* points, std::uint32_t* contours);
|
||||
int APS5_VABI FT_Stroker_ParseOutline_nid_postfix(void* stroker, const FreeType::GuestOutline* outline, std::uint8_t opened);
|
||||
void APS5_VABI FT_Stroker_ExportBorder_nid_postfix(void* stroker, std::int32_t border, FreeType::GuestOutline* outline);
|
||||
void APS5_VABI FT_Stroker_Export_nid_postfix(void* stroker, FreeType::GuestOutline* outline);
|
||||
int APS5_VABI FT_Stroker_EndSubPath_nid_postfix(void* stroker);
|
||||
int APS5_VABI FT_Stroker_LineTo_nid_postfix(void* stroker, const FreeType::GuestVector* to);
|
||||
int APS5_VABI FT_Stroker_New_nid_postfix(FreeType::GuestLibrary* library, void** stroker);
|
||||
@@ -119,9 +122,28 @@ const char* APS5_VABI FT_Get_Font_Format_nid_postfix(FreeType::GuestFaceRec* fac
|
||||
std::uint8_t APS5_VABI FT_Face_CheckTrueTypePatents_nid_postfix(FreeType::GuestFaceRec* face);
|
||||
std::uint8_t APS5_VABI FT_Face_SetUnpatentedHinting_nid_postfix(FreeType::GuestFaceRec* face, std::uint8_t value);
|
||||
int APS5_VABI FT_Get_Track_Kerning_nid_postfix(FreeType::GuestFaceRec* face, std::int64_t pointSize, std::int32_t degree, std::int64_t* kerning);
|
||||
void APS5_VABI FT_GlyphSlot_Oblique_nid_postfix(FreeType::GuestGlyphSlotRec* slot);
|
||||
void APS5_VABI FT_GlyphSlot_Embolden_nid_postfix(FreeType::GuestGlyphSlotRec* slot);
|
||||
int APS5_VABI FT_GlyphSlot_Own_Bitmap_nid_postfix(FreeType::GuestGlyphSlotRec* slot);
|
||||
int APS5_VABI FT_Bitmap_Embolden_nid_postfix(FreeType::GuestLibrary* library, FreeType::GuestBitmap* bitmap, std::int64_t xStrength, std::int64_t yStrength);
|
||||
int APS5_VABI FT_Library_SetLcdFilterWeights_nid_postfix(FreeType::GuestLibrary* library, unsigned char* weights);
|
||||
int APS5_VABI FT_Library_SetLcdFilter_nid_postfix(FreeType::GuestLibrary* library, std::int32_t filter);
|
||||
void APS5_VABI FT_Outline_Transform_nid_postfix(const FreeType::GuestOutline* outline, const FreeType::GuestMatrix* matrix);
|
||||
int APS5_VABI FT_Outline_EmboldenXY_nid_postfix(FreeType::GuestOutline* outline, std::int64_t xStrength, std::int64_t yStrength);
|
||||
int APS5_VABI FT_Outline_Embolden_nid_postfix(FreeType::GuestOutline* outline, std::int64_t strength);
|
||||
|
||||
int APS5_VABI FT_Outline_New_nid_postfix(FreeType::GuestLibrary* library, std::uint32_t numPoints, std::int32_t numContours, FreeType::GuestOutline* outline);
|
||||
int APS5_VABI FT_Outline_Done_nid_postfix(FreeType::GuestLibrary* library, FreeType::GuestOutline* outline);
|
||||
int APS5_VABI sceFreeTypeOtOutlineDone_nid_no_patch();
|
||||
int APS5_VABI sceFreeTypeOtOutlineNew_nid_no_patch();
|
||||
int APS5_VABI sceFreeTypeOtGlyphSlotEmbolden_nid_no_patch();
|
||||
int APS5_VABI sceFreeTypeOtGlyphSlotOblique_nid_no_patch();
|
||||
int APS5_VABI sceFreeTypeOtGlyphSlotOwnBitmap_nid_no_patch();
|
||||
int APS5_VABI sceFreeTypeOtLibrarySetLcdFilter_nid_no_patch();
|
||||
int APS5_VABI sceFreeTypeOtLibrarySetLcdFilterWeights_nid_no_patch();
|
||||
void* APS5_VABI FT_Get_Module_nid_postfix(FreeType::GuestLibrary* library, const char* moduleName);
|
||||
void* APS5_VABI FT_Get_Renderer_nid_postfix(FreeType::GuestLibrary* library, std::uint32_t format);
|
||||
int APS5_VABI FT_Remove_Module_nid_postfix(FreeType::GuestLibrary* library, void* module);
|
||||
void APS5_VABI FT_Add_Default_Modules_nid_postfix(FreeType::GuestLibrary* library);
|
||||
int APS5_VABI sceFreeTypeOtRemoveModule_nid_no_patch();
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
#include "prx/libSceFreeType/include/Api.hpp"
|
||||
#include "prx/libSceFreeType/include/GuestObjects.hpp"
|
||||
#include "prx/libc/include/general/VabiMacros.hpp"
|
||||
|
||||
#include FT_BITMAP_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <mutex>
|
||||
#include <stdexcept>
|
||||
#include <unordered_map>
|
||||
@@ -92,6 +94,31 @@ int APS5_VABI FT_Bitmap_Convert_nid_postfix(GuestLibrary* library, const GuestBi
|
||||
return error;
|
||||
}
|
||||
|
||||
int APS5_VABI FT_Bitmap_Embolden_nid_postfix(GuestLibrary* library, GuestBitmap* bitmap, std::int64_t xStrength, std::int64_t yStrength) {
|
||||
if (!library) return FT_Err_Invalid_Library_Handle;
|
||||
if (!bitmap) return FT_Err_Invalid_Argument;
|
||||
for (const std::int64_t strength : {xStrength, yStrength}) {
|
||||
if (strength < std::numeric_limits<FT_Pos>::min() || strength > std::numeric_limits<FT_Pos>::max()) {
|
||||
throw std::runtime_error("FT_Bitmap_Embolden: a strength outside the host FT_Pos range is not implemented");
|
||||
}
|
||||
if (strength > std::numeric_limits<FT_Pos>::max() - 32) return FT_Err_Invalid_Argument;
|
||||
constexpr std::int64_t minimumRounded = static_cast<std::int64_t>(std::numeric_limits<FT_Int>::min()) * 64 - 32;
|
||||
constexpr std::int64_t maximumRounded = static_cast<std::int64_t>(std::numeric_limits<FT_Int>::max()) * 64 + 31;
|
||||
if (strength < minimumRounded || strength > maximumRounded) return FT_Err_Invalid_Argument;
|
||||
}
|
||||
FT_Bitmap native = NativeBitmap(*bitmap);
|
||||
const FT_Error error = FT_Bitmap_Embolden(library->host, &native, static_cast<FT_Pos>(xStrength), static_cast<FT_Pos>(yStrength));
|
||||
SyncBitmap(*bitmap, native);
|
||||
return error;
|
||||
}
|
||||
|
||||
int APS5_VABI FT_GlyphSlot_Own_Bitmap_nid_postfix(GuestGlyphSlotRec* slot) {
|
||||
GuestFace* owner = FaceOf(slot);
|
||||
const FT_Error error = FT_GlyphSlot_Own_Bitmap(owner ? owner->host->glyph : nullptr);
|
||||
if (owner) SyncSlot(*owner);
|
||||
return error;
|
||||
}
|
||||
|
||||
int APS5_VABI FT_Bitmap_Done_nid_postfix(GuestLibrary* library, GuestBitmap* bitmap) {
|
||||
if (!library) return FT_Err_Invalid_Library_Handle;
|
||||
if (!bitmap) return FT_Err_Invalid_Argument;
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
#include "prx/libSceFreeType/include/Api.hpp"
|
||||
#include "prx/libSceFreeType/include/GuestObjects.hpp"
|
||||
#include "prx/libc/include/GuestArena.hpp"
|
||||
#include "prx/libc/include/general/VabiMacros.hpp"
|
||||
|
||||
#include FT_LCD_FILTER_H
|
||||
|
||||
#include <array>
|
||||
#include <cstring>
|
||||
|
||||
using namespace FreeType;
|
||||
|
||||
extern "C" {
|
||||
|
||||
int APS5_VABI FT_Library_SetLcdFilter_nid_postfix(GuestLibrary* library, std::int32_t filter) {
|
||||
return FT_Library_SetLcdFilter(library ? library->host : nullptr, static_cast<FT_LcdFilter>(filter));
|
||||
}
|
||||
|
||||
int APS5_VABI FT_Library_SetLcdFilterWeights_nid_postfix(GuestLibrary* library, unsigned char* weights) {
|
||||
FT_Library host = library ? library->host : nullptr;
|
||||
if (!weights) return FT_Library_SetLcdFilterWeights(host, nullptr);
|
||||
GuestArena::GuestArenaPrepareHostRead_nid_no_patch(weights, FT_LCD_FILTER_FIVE_TAPS);
|
||||
std::array<unsigned char, FT_LCD_FILTER_FIVE_TAPS> copy;
|
||||
std::memcpy(copy.data(), weights, copy.size());
|
||||
return FT_Library_SetLcdFilterWeights(host, copy.data());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
#include "prx/libSceFreeType/include/Api.hpp"
|
||||
#include "prx/libSceFreeType/include/GuestObjects.hpp"
|
||||
#include "prx/libc/include/general/VabiMacros.hpp"
|
||||
|
||||
#include FT_MODULE_H
|
||||
#include FT_RENDER_H
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
using namespace FreeType;
|
||||
|
||||
extern "C" {
|
||||
|
||||
void* APS5_VABI FT_Get_Module_nid_postfix(GuestLibrary* library, const char* moduleName) {
|
||||
return FT_Get_Module(library ? library->host : nullptr, moduleName);
|
||||
}
|
||||
|
||||
void* APS5_VABI FT_Get_Renderer_nid_postfix(GuestLibrary* library, std::uint32_t format) {
|
||||
return FT_Get_Renderer(library ? library->host : nullptr, static_cast<FT_Glyph_Format>(format));
|
||||
}
|
||||
|
||||
int APS5_VABI FT_Remove_Module_nid_postfix(GuestLibrary* library, void* module) {
|
||||
return FT_Remove_Module(library ? library->host : nullptr, static_cast<FT_Module>(module));
|
||||
}
|
||||
|
||||
void APS5_VABI FT_Add_Default_Modules_nid_postfix(GuestLibrary* library) {
|
||||
FT_Add_Default_Modules(library ? library->host : nullptr);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
#include "prx/libSceFreeType/include/Api.hpp"
|
||||
#include "prx/libSceFreeType/include/GuestObjects.hpp"
|
||||
#include "prx/libc/include/general/VabiMacros.hpp"
|
||||
|
||||
#include FT_OUTLINE_H
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
using namespace FreeType;
|
||||
|
||||
namespace {
|
||||
|
||||
FT_Pos HostCoordinate(std::int64_t value, const char* function) {
|
||||
if (value < std::numeric_limits<FT_Pos>::min() || value > std::numeric_limits<FT_Pos>::max()) {
|
||||
throw std::runtime_error(std::string(function) + ": a value outside the host FT_Pos range is not implemented");
|
||||
}
|
||||
return static_cast<FT_Pos>(value);
|
||||
}
|
||||
|
||||
struct NativeOutline {
|
||||
std::vector<FT_Vector> points;
|
||||
FT_Outline outline{};
|
||||
|
||||
NativeOutline(const GuestOutline& guest, const char* function) {
|
||||
points.reserve(guest.n_points);
|
||||
for (std::size_t index = 0; index < guest.n_points; ++index) {
|
||||
points.push_back({HostCoordinate(guest.points[index].x, function), HostCoordinate(guest.points[index].y, function)});
|
||||
}
|
||||
outline = {guest.n_contours, guest.n_points, points.data(), guest.tags, guest.contours, guest.flags};
|
||||
}
|
||||
|
||||
void Publish(GuestOutline& guest) const {
|
||||
for (std::size_t index = 0; index < points.size(); ++index) guest.points[index] = {points[index].x, points[index].y};
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
||||
int APS5_VABI FT_Outline_Embolden_nid_postfix(GuestOutline* outline, std::int64_t strength) {
|
||||
if (!outline) return FT_Outline_Embolden(nullptr, 0);
|
||||
NativeOutline native(*outline, __func__);
|
||||
const FT_Error error = FT_Outline_Embolden(&native.outline, HostCoordinate(strength, __func__));
|
||||
native.Publish(*outline);
|
||||
return error;
|
||||
}
|
||||
|
||||
int APS5_VABI FT_Outline_EmboldenXY_nid_postfix(GuestOutline* outline, std::int64_t xStrength, std::int64_t yStrength) {
|
||||
if (!outline) return FT_Outline_EmboldenXY(nullptr, 0, 0);
|
||||
NativeOutline native(*outline, __func__);
|
||||
const FT_Error error = FT_Outline_EmboldenXY(&native.outline, HostCoordinate(xStrength, __func__), HostCoordinate(yStrength, __func__));
|
||||
native.Publish(*outline);
|
||||
return error;
|
||||
}
|
||||
|
||||
void APS5_VABI FT_Outline_Transform_nid_postfix(const GuestOutline* outline, const GuestMatrix* matrix) {
|
||||
if (!outline || !matrix || !outline->points) return;
|
||||
NativeOutline native(*outline, __func__);
|
||||
const FT_Matrix host{HostCoordinate(matrix->xx, __func__), HostCoordinate(matrix->xy, __func__), HostCoordinate(matrix->yx, __func__),
|
||||
HostCoordinate(matrix->yy, __func__)};
|
||||
FT_Outline_Transform(&native.outline, &host);
|
||||
native.Publish(*const_cast<GuestOutline*>(outline));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,19 +1,27 @@
|
||||
#include "prx/libSceFreeType/include/Api.hpp"
|
||||
#include "prx/libSceFreeType/include/GuestObjects.hpp"
|
||||
#include "prx/libc/include/General.hpp"
|
||||
#include "prx/libc/include/GuestArena.hpp"
|
||||
#include "prx/libc/include/general/VabiMacros.hpp"
|
||||
|
||||
#include FT_STROKER_H
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
using namespace FreeType;
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr bool SharedPoints = sizeof(FT_Pos) == sizeof(std::int64_t);
|
||||
|
||||
static_assert(!SharedPoints || (sizeof(FT_Vector) == sizeof(GuestVector) && offsetof(FT_Vector, x) == offsetof(GuestVector, x) &&
|
||||
offsetof(FT_Vector, y) == offsetof(GuestVector, y)));
|
||||
|
||||
FT_Pos HostCoordinate(std::int64_t value, const char* function) {
|
||||
if (value < std::numeric_limits<FT_Pos>::min() || value > std::numeric_limits<FT_Pos>::max()) {
|
||||
throw std::runtime_error(std::string(function) + ": a value outside the host FT_Pos range is not implemented");
|
||||
@@ -38,6 +46,57 @@ void WriteCount(std::uint32_t* target, FT_UInt value) {
|
||||
*target = value;
|
||||
}
|
||||
|
||||
FT_Outline NativeOutline(const GuestOutline& outline, FT_Vector* points) {
|
||||
return {outline.n_contours, outline.n_points, points, outline.tags, outline.contours, outline.flags};
|
||||
}
|
||||
|
||||
bool FitsInt32(std::int64_t value) {
|
||||
return value >= std::numeric_limits<std::int32_t>::min() && value <= std::numeric_limits<std::int32_t>::max();
|
||||
}
|
||||
|
||||
void CheckOutlineArrays(const GuestOutline& outline, const char* function) {
|
||||
if (outline.n_contours == 0) return;
|
||||
if (!outline.points || !outline.tags || !outline.contours) NotImplemented_nid_no_patch(function);
|
||||
for (std::size_t contour = 0; contour < outline.n_contours; ++contour) {
|
||||
if (outline.contours[contour] >= outline.n_points) NotImplemented_nid_no_patch(function);
|
||||
}
|
||||
}
|
||||
|
||||
bool StrokedPointsFitInt32(const GuestOutline& outline) {
|
||||
std::int32_t last = -1;
|
||||
for (std::size_t contour = 0; contour < outline.n_contours; ++contour) {
|
||||
const std::int32_t first = last + 1;
|
||||
last = outline.contours[contour];
|
||||
if (last <= first) continue;
|
||||
for (std::int32_t index = first; index <= last; ++index) {
|
||||
if (!FitsInt32(outline.points[index].x) || !FitsInt32(outline.points[index].y)) return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<FT_Vector> NativePoints(const GuestOutline& outline, const char* function) {
|
||||
std::vector<FT_Vector> points;
|
||||
if (outline.n_contours == 0) return points;
|
||||
points.reserve(outline.n_points);
|
||||
for (std::size_t index = 0; index < outline.n_points; ++index) {
|
||||
points.push_back({HostCoordinate(outline.points[index].x, function), HostCoordinate(outline.points[index].y, function)});
|
||||
}
|
||||
return points;
|
||||
}
|
||||
|
||||
void ExportBorder(void* stroker, std::int32_t border, GuestOutline* outline, const char* function) {
|
||||
if (!stroker || !outline || (border != FT_STROKER_BORDER_LEFT && border != FT_STROKER_BORDER_RIGHT)) return;
|
||||
if constexpr (SharedPoints) {
|
||||
FT_Outline native = NativeOutline(*outline, reinterpret_cast<FT_Vector*>(outline->points));
|
||||
FT_Stroker_ExportBorder(static_cast<FT_Stroker>(stroker), static_cast<FT_StrokerBorder>(border), &native);
|
||||
outline->n_contours = native.n_contours;
|
||||
outline->n_points = native.n_points;
|
||||
} else {
|
||||
NotImplemented_nid_no_patch(function);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
@@ -108,4 +167,29 @@ int APS5_VABI FT_Stroker_CubicTo_nid_postfix(void* stroker, const GuestVector* c
|
||||
return FT_Stroker_CubicTo(static_cast<FT_Stroker>(stroker), first.pointer, second.pointer, end.pointer);
|
||||
}
|
||||
|
||||
int APS5_VABI FT_Stroker_ParseOutline_nid_postfix(void* stroker, const GuestOutline* outline, std::uint8_t opened) {
|
||||
if (!outline) return FT_Stroker_ParseOutline(static_cast<FT_Stroker>(stroker), nullptr, opened);
|
||||
if (stroker) CheckOutlineArrays(*outline, __func__);
|
||||
std::vector<FT_Vector> converted;
|
||||
FT_Vector* points = nullptr;
|
||||
if constexpr (SharedPoints) {
|
||||
if (stroker && !StrokedPointsFitInt32(*outline)) NotImplemented_nid_no_patch(__func__);
|
||||
points = reinterpret_cast<FT_Vector*>(outline->points);
|
||||
} else if (stroker) {
|
||||
converted = NativePoints(*outline, __func__);
|
||||
points = converted.data();
|
||||
}
|
||||
FT_Outline native = NativeOutline(*outline, points);
|
||||
return FT_Stroker_ParseOutline(static_cast<FT_Stroker>(stroker), &native, opened);
|
||||
}
|
||||
|
||||
void APS5_VABI FT_Stroker_ExportBorder_nid_postfix(void* stroker, std::int32_t border, GuestOutline* outline) {
|
||||
ExportBorder(stroker, border, outline, __func__);
|
||||
}
|
||||
|
||||
void APS5_VABI FT_Stroker_Export_nid_postfix(void* stroker, GuestOutline* outline) {
|
||||
ExportBorder(stroker, FT_STROKER_BORDER_LEFT, outline, __func__);
|
||||
ExportBorder(stroker, FT_STROKER_BORDER_RIGHT, outline, __func__);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -9,6 +9,20 @@ endif()
|
||||
configure_windows_unwind(libSceFreeTypeOt)
|
||||
|
||||
if(BUILD_TESTING)
|
||||
add_test_executable(guest_freetype_ot_constants_tests ${LIBS_INCLUDE_DIR}/tests/GuestFreeTypeOtConstants.cpp)
|
||||
target_include_directories(guest_freetype_ot_constants_tests PRIVATE ${LIBS_INCLUDE_DIR})
|
||||
target_link_libraries(guest_freetype_ot_constants_tests PRIVATE libSceFreeTypeOt)
|
||||
if(WIN32)
|
||||
target_link_libraries(guest_freetype_ot_constants_tests PRIVATE libc)
|
||||
endif()
|
||||
configure_windows_unwind(guest_freetype_ot_constants_tests)
|
||||
add_test(NAME guest_freetype_ot_constants COMMAND guest_freetype_ot_constants_tests)
|
||||
set_tests_properties(guest_freetype_ot_constants PROPERTIES TIMEOUT 20)
|
||||
if(WIN32)
|
||||
set_tests_properties(guest_freetype_ot_constants PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceFreeTypeOt>")
|
||||
endif()
|
||||
|
||||
add_test_executable(guest_freetype_corner_tests ${LIBS_INCLUDE_DIR}/tests/GuestFreeTypeCorner.cpp)
|
||||
target_include_directories(guest_freetype_corner_tests PRIVATE ${LIBS_INCLUDE_DIR})
|
||||
target_link_libraries(guest_freetype_corner_tests PRIVATE libSceFreeTypeOt)
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
#include "prx/libSceFreeType/include/Api.hpp"
|
||||
#include "prx/libSceFreeTypeOt/libSceFreeTypeOt.hpp"
|
||||
#include <bit>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include "SceTypes.hpp"
|
||||
#include "prx/libSceFreeType/include/Api.hpp"
|
||||
#include "prx/libSceFreeTypeOt/include/Calc.hpp"
|
||||
#include "prx/libc/include/General.hpp"
|
||||
|
||||
@@ -109,20 +108,20 @@ int APS5_VABI sceFreeTypeOtUnknown_ZhtAXOoLNyo_nid_no_patch() {
|
||||
}
|
||||
APS5_EXPORT("ZhtAXOoLNyo", sceFreeTypeOtUnknown_ZhtAXOoLNyo_nid_no_patch);
|
||||
|
||||
int APS5_VABI sceFreeTypeOtUnknown_73vg_nRH68g_nid_no_patch() {
|
||||
int APS5_VABI sceFreeTypeOtGlyphSlotEmbolden_nid_no_patch() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
}
|
||||
APS5_EXPORT("73vg+nRH68g", sceFreeTypeOtUnknown_73vg_nRH68g_nid_no_patch);
|
||||
APS5_EXPORT("73vg+nRH68g", sceFreeTypeOtGlyphSlotEmbolden_nid_no_patch);
|
||||
|
||||
int APS5_VABI sceFreeTypeOtUnknown_wBKAYO7yEXw_nid_no_patch() {
|
||||
int APS5_VABI sceFreeTypeOtGlyphSlotOblique_nid_no_patch() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
}
|
||||
APS5_EXPORT("wBKAYO7yEXw", sceFreeTypeOtUnknown_wBKAYO7yEXw_nid_no_patch);
|
||||
APS5_EXPORT("wBKAYO7yEXw", sceFreeTypeOtGlyphSlotOblique_nid_no_patch);
|
||||
|
||||
int APS5_VABI sceFreeTypeOtUnknown_nSIux4OQyJM_nid_no_patch() {
|
||||
int APS5_VABI sceFreeTypeOtGlyphSlotOwnBitmap_nid_no_patch() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
}
|
||||
APS5_EXPORT("nSIux4OQyJM", sceFreeTypeOtUnknown_nSIux4OQyJM_nid_no_patch);
|
||||
APS5_EXPORT("nSIux4OQyJM", sceFreeTypeOtGlyphSlotOwnBitmap_nid_no_patch);
|
||||
|
||||
int APS5_VABI sceFreeTypeOtGlyphCopy_nid_no_patch() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
@@ -159,15 +158,15 @@ int APS5_VABI sceFreeTypeOtHasPSGlyphNames_nid_no_patch() {
|
||||
}
|
||||
APS5_EXPORT("x8y0qzLiYOU", sceFreeTypeOtHasPSGlyphNames_nid_no_patch);
|
||||
|
||||
int APS5_VABI sceFreeTypeOtUnknown_VW4GD_TaIUc_nid_no_patch() {
|
||||
int APS5_VABI sceFreeTypeOtLibrarySetLcdFilter_nid_no_patch() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
}
|
||||
APS5_EXPORT("VW4GD-TaIUc", sceFreeTypeOtUnknown_VW4GD_TaIUc_nid_no_patch);
|
||||
APS5_EXPORT("VW4GD-TaIUc", sceFreeTypeOtLibrarySetLcdFilter_nid_no_patch);
|
||||
|
||||
int APS5_VABI sceFreeTypeOtUnknown_GiuNQjoj4fs_nid_no_patch() {
|
||||
int APS5_VABI sceFreeTypeOtLibrarySetLcdFilterWeights_nid_no_patch() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
}
|
||||
APS5_EXPORT("GiuNQjoj4fs", sceFreeTypeOtUnknown_GiuNQjoj4fs_nid_no_patch);
|
||||
APS5_EXPORT("GiuNQjoj4fs", sceFreeTypeOtLibrarySetLcdFilterWeights_nid_no_patch);
|
||||
|
||||
int APS5_VABI sceFreeTypeOtLibraryVersion_nid_no_patch() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
@@ -229,15 +228,15 @@ int APS5_VABI sceFreeTypeOtUnknown_umIqupy8UU0_nid_no_patch() {
|
||||
}
|
||||
APS5_EXPORT("umIqupy8UU0", sceFreeTypeOtUnknown_umIqupy8UU0_nid_no_patch);
|
||||
|
||||
int APS5_VABI sceFreeTypeOtUnknown_l6Cf9rb9fZQ_nid_no_patch() {
|
||||
int APS5_VABI sceFreeTypeOtOutlineEmbolden_nid_no_patch() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
}
|
||||
APS5_EXPORT("l6Cf9rb9fZQ", sceFreeTypeOtUnknown_l6Cf9rb9fZQ_nid_no_patch);
|
||||
APS5_EXPORT("l6Cf9rb9fZQ", sceFreeTypeOtOutlineEmbolden_nid_no_patch);
|
||||
|
||||
int APS5_VABI sceFreeTypeOtUnknown_BgUMBGvhATU_nid_no_patch() {
|
||||
int APS5_VABI sceFreeTypeOtOutlineEmboldenXY_nid_no_patch() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
}
|
||||
APS5_EXPORT("BgUMBGvhATU", sceFreeTypeOtUnknown_BgUMBGvhATU_nid_no_patch);
|
||||
APS5_EXPORT("BgUMBGvhATU", sceFreeTypeOtOutlineEmboldenXY_nid_no_patch);
|
||||
|
||||
int APS5_VABI sceFreeTypeOtOutlineGetBBox_nid_no_patch() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
@@ -264,10 +263,10 @@ int APS5_VABI sceFreeTypeOtUnknown_9km02mPqPJE_nid_no_patch() {
|
||||
}
|
||||
APS5_EXPORT("9km02mPqPJE", sceFreeTypeOtUnknown_9km02mPqPJE_nid_no_patch);
|
||||
|
||||
int APS5_VABI sceFreeTypeOtUnknown_0Lv6K1d47jg_nid_no_patch() {
|
||||
int APS5_VABI sceFreeTypeOtOutlineTransform_nid_no_patch() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
}
|
||||
APS5_EXPORT("0Lv6K1d47jg", sceFreeTypeOtUnknown_0Lv6K1d47jg_nid_no_patch);
|
||||
APS5_EXPORT("0Lv6K1d47jg", sceFreeTypeOtOutlineTransform_nid_no_patch);
|
||||
|
||||
int APS5_VABI sceFreeTypeOtPropertyGet_nid_no_patch() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
@@ -304,10 +303,10 @@ int APS5_VABI sceFreeTypeOtUnknown_qAPXM5DN62Q_nid_no_patch() {
|
||||
}
|
||||
APS5_EXPORT("qAPXM5DN62Q", sceFreeTypeOtUnknown_qAPXM5DN62Q_nid_no_patch);
|
||||
|
||||
int APS5_VABI sceFreeTypeOtUnknown_5ipBcLYVc2k_nid_no_patch() {
|
||||
int APS5_VABI sceFreeTypeOtRemoveModule_nid_no_patch() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
}
|
||||
APS5_EXPORT("5ipBcLYVc2k", sceFreeTypeOtUnknown_5ipBcLYVc2k_nid_no_patch);
|
||||
APS5_EXPORT("5ipBcLYVc2k", sceFreeTypeOtRemoveModule_nid_no_patch);
|
||||
|
||||
int APS5_VABI sceFreeTypeOtUnknown_8A0cAU4yBVY_nid_no_patch() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
@@ -394,15 +393,15 @@ int APS5_VABI sceFreeTypeOtStrokerEndSubPath_nid_no_patch() {
|
||||
}
|
||||
APS5_EXPORT("pBqh-Uh-gns", sceFreeTypeOtStrokerEndSubPath_nid_no_patch);
|
||||
|
||||
int APS5_VABI sceFreeTypeOtUnknown_2rcvKZfm5rI_nid_no_patch() {
|
||||
int APS5_VABI sceFreeTypeOtStrokerExport_nid_no_patch() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
}
|
||||
APS5_EXPORT("2rcvKZfm5rI", sceFreeTypeOtUnknown_2rcvKZfm5rI_nid_no_patch);
|
||||
APS5_EXPORT("2rcvKZfm5rI", sceFreeTypeOtStrokerExport_nid_no_patch);
|
||||
|
||||
int APS5_VABI sceFreeTypeOtUnknown_7cIS2cFG3PE_nid_no_patch() {
|
||||
int APS5_VABI sceFreeTypeOtStrokerExportBorder_nid_no_patch() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
}
|
||||
APS5_EXPORT("7cIS2cFG3PE", sceFreeTypeOtUnknown_7cIS2cFG3PE_nid_no_patch);
|
||||
APS5_EXPORT("7cIS2cFG3PE", sceFreeTypeOtStrokerExportBorder_nid_no_patch);
|
||||
|
||||
int APS5_VABI sceFreeTypeOtStrokerGetBorderCounts_nid_no_patch() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
@@ -424,10 +423,10 @@ int APS5_VABI sceFreeTypeOtStrokerNew_nid_no_patch() {
|
||||
}
|
||||
APS5_EXPORT("eqGCaeizvWM", sceFreeTypeOtStrokerNew_nid_no_patch);
|
||||
|
||||
int APS5_VABI sceFreeTypeOtUnknown_RvFx7dv9ThU_nid_no_patch() {
|
||||
int APS5_VABI sceFreeTypeOtStrokerParseOutline_nid_no_patch() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
}
|
||||
APS5_EXPORT("RvFx7dv9ThU", sceFreeTypeOtUnknown_RvFx7dv9ThU_nid_no_patch);
|
||||
APS5_EXPORT("RvFx7dv9ThU", sceFreeTypeOtStrokerParseOutline_nid_no_patch);
|
||||
|
||||
int APS5_VABI sceFreeTypeOtStrokerRewind_nid_no_patch() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
@@ -439,15 +438,16 @@ int APS5_VABI sceFreeTypeOtStrokerSet_nid_no_patch() {
|
||||
}
|
||||
APS5_EXPORT("B3uU5aDZn94", sceFreeTypeOtStrokerSet_nid_no_patch);
|
||||
|
||||
int APS5_VABI sceFreeTypeOtUnknown_08_mhD_AAds_nid_no_patch() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
int APS5_VABI FT_Trace_Get_Count_nid_no_patch() {
|
||||
return 0;
|
||||
}
|
||||
APS5_EXPORT("08-mhD-AAds", sceFreeTypeOtUnknown_08_mhD_AAds_nid_no_patch);
|
||||
APS5_EXPORT("08-mhD-AAds", FT_Trace_Get_Count_nid_no_patch);
|
||||
|
||||
int APS5_VABI sceFreeTypeOtUnknown_64XMq6sDxUo_nid_no_patch() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
const char* APS5_VABI FT_Trace_Get_Name_nid_no_patch(int idx) {
|
||||
static_cast<void>(idx);
|
||||
return nullptr;
|
||||
}
|
||||
APS5_EXPORT("64XMq6sDxUo", sceFreeTypeOtUnknown_64XMq6sDxUo_nid_no_patch);
|
||||
APS5_EXPORT("64XMq6sDxUo", FT_Trace_Get_Name_nid_no_patch);
|
||||
|
||||
int APS5_VABI sceFreeTypeOtUnknown_4xG87W9dTSg_nid_no_patch() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
|
||||
@@ -5,3 +5,8 @@
|
||||
#include "prx/libSceFreeTypeOt/include/Calc.hpp"
|
||||
#include "prx/libSceFreeTypeOt/include/Memory.hpp"
|
||||
#include "prx/libSceFreeTypeOt/include/Raccess.hpp"
|
||||
|
||||
extern "C" {
|
||||
int APS5_VABI FT_Trace_Get_Count_nid_no_patch();
|
||||
const char* APS5_VABI FT_Trace_Get_Name_nid_no_patch(int idx);
|
||||
}
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
#include <atomic>
|
||||
#include <cstddef>
|
||||
#include <cerrno>
|
||||
#include <cstring>
|
||||
#include <initializer_list>
|
||||
#include <stdexcept>
|
||||
@@ -113,9 +114,14 @@ std::int32_t APS5_VABI sceHmdClose(std::int32_t handle) {
|
||||
std::int32_t APS5_VABI sceHmdGetDeviceInformation(Hmd::DeviceInformation* info) {
|
||||
if (!info) return Hmd::ParameterNull;
|
||||
if (!initialized.load()) return Hmd::NotInitialized;
|
||||
*info = {};
|
||||
info->status = Hmd::DeviceStatus::NotDetected;
|
||||
info->userId = -1;
|
||||
Hmd::DeviceInformation result{};
|
||||
result.status = Hmd::DeviceStatus::NotDetected;
|
||||
result.userId = -1;
|
||||
const int savedErrno = errno;
|
||||
if (!GuestCopyOut_nid_no_patch(info, &result, sizeof(result))) {
|
||||
errno = savedErrno;
|
||||
return Hmd::ParameterNull;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -128,7 +134,12 @@ std::int32_t APS5_VABI sceHmdGetDeviceInformationByHandle(std::int32_t handle,
|
||||
|
||||
std::int32_t APS5_VABI sceHmdInternalGetDeviceStatus(Hmd::DeviceStatus* status) {
|
||||
if (!status) return Hmd::ParameterNull;
|
||||
*status = Hmd::DeviceStatus::NotDetected;
|
||||
constexpr Hmd::DeviceStatus deviceStatus = Hmd::DeviceStatus::NotDetected;
|
||||
const int savedErrno = errno;
|
||||
if (!GuestCopyOut_nid_no_patch(status, &deviceStatus, sizeof(deviceStatus))) {
|
||||
errno = savedErrno;
|
||||
return Hmd::ParameterNull;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -77,9 +77,10 @@ if(BUILD_TESTING)
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceHttp2>")
|
||||
endif()
|
||||
add_test(NAME guest_http2_http1 COMMAND guest_http2_transport_tests --http1)
|
||||
set_tests_properties(guest_http2_http1 PROPERTIES TIMEOUT 30)
|
||||
add_test(NAME guest_http2_http1_cookies COMMAND guest_http2_transport_tests --http1-cookies)
|
||||
set_tests_properties(guest_http2_http1 guest_http2_http1_cookies PROPERTIES TIMEOUT 30)
|
||||
if(WIN32)
|
||||
set_tests_properties(guest_http2_http1 PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
set_tests_properties(guest_http2_http1 guest_http2_http1_cookies PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceHttp2>")
|
||||
endif()
|
||||
add_test(NAME guest_http2_ssl_callbacks COMMAND guest_http2_transport_tests --ssl-callbacks)
|
||||
@@ -109,3 +110,12 @@ if(BUILD_TESTING)
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(BUILD_TESTING)
|
||||
add_test(NAME guest_http2_auth_status COMMAND guest_http2_transport_tests --auth-status)
|
||||
set_tests_properties(guest_http2_auth_status PROPERTIES TIMEOUT 30)
|
||||
if(WIN32)
|
||||
set_tests_properties(guest_http2_auth_status PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceHttp2>")
|
||||
endif()
|
||||
endif()
|
||||
@@ -321,10 +321,49 @@ void AppendCookie(std::string& header, const Cookie& cookie) {
|
||||
}
|
||||
|
||||
CookieBox* FindBox(int cookieBoxId) {
|
||||
if (!Http2CookieBoxExists(cookieBoxId)) return nullptr;
|
||||
return &g_cookieBoxes[cookieBoxId];
|
||||
const auto box = g_cookieBoxes.find(cookieBoxId);
|
||||
return box != g_cookieBoxes.end() ? &box->second : nullptr;
|
||||
}
|
||||
|
||||
std::vector<const Cookie*> Matches(CookieBox& box, const Url& url) {
|
||||
RemoveExpired(box, Now());
|
||||
std::vector<const Cookie*> matches;
|
||||
for (const Cookie& stored : box.cookies) {
|
||||
if (DomainMatches(url.host, stored.domain) && PathMatches(url.path, stored.path) && (!stored.secure || url.secure)) matches.push_back(&stored);
|
||||
}
|
||||
std::stable_sort(matches.begin(), matches.end(), [](const Cookie* left, const Cookie* right) { return left->path.size() > right->path.size(); });
|
||||
for (auto run = matches.begin(); run != matches.end();) {
|
||||
const auto end = std::find_if(run, matches.end(), [&](const Cookie* match) { return match->path.size() != (*run)->path.size(); });
|
||||
std::rotate(run, run + 1, end);
|
||||
run = end;
|
||||
}
|
||||
return matches;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
std::optional<std::string> Http2CookieHeader(int cookieBoxId, std::string_view url) {
|
||||
std::lock_guard lock(g_cookiesMutex);
|
||||
CookieBox* box = FindBox(cookieBoxId);
|
||||
Url parsed{};
|
||||
if (!box || ParseUrl(url, parsed) != 0) return std::nullopt;
|
||||
const auto matches = Matches(*box, parsed);
|
||||
if (matches.empty()) return std::nullopt;
|
||||
std::string header;
|
||||
for (const Cookie* match : matches) AppendCookie(header, *match);
|
||||
return header;
|
||||
}
|
||||
|
||||
void Http2StoreResponseCookie(int cookieBoxId, std::string_view url, std::string_view cookie) {
|
||||
std::lock_guard lock(g_cookiesMutex);
|
||||
CookieBox* box = FindBox(cookieBoxId);
|
||||
Url parsed{};
|
||||
if (box && ParseUrl(url, parsed) == 0) static_cast<void>(StoreCookie(*box, parsed, cookie, Now()));
|
||||
}
|
||||
|
||||
void Http2AddCookieBox(int cookieBoxId) {
|
||||
std::lock_guard lock(g_cookiesMutex);
|
||||
g_cookieBoxes.try_emplace(cookieBoxId);
|
||||
}
|
||||
|
||||
void Http2ForgetCookies(int cookieBoxId) {
|
||||
@@ -358,19 +397,9 @@ int APS5_VABI sceHttp2GetCookie(int cookieBoxId, const char* url, char* cookie,
|
||||
Url parsed{};
|
||||
if (const int result = ParseUrl(url, parsed); result == ERROR_REJECTED) return ERROR_NOT_FOUND;
|
||||
else if (result != 0) return result;
|
||||
RemoveExpired(*box, Now());
|
||||
std::vector<const Cookie*> matches;
|
||||
for (const Cookie& stored : box->cookies) {
|
||||
if (DomainMatches(parsed.host, stored.domain) && PathMatches(parsed.path, stored.path) && (!stored.secure || parsed.secure)) matches.push_back(&stored);
|
||||
}
|
||||
const auto matches = Matches(*box, parsed);
|
||||
if (matches.empty()) return ERROR_NOT_FOUND;
|
||||
if (!cookie && !required) return ERROR_INVALID_VALUE;
|
||||
std::stable_sort(matches.begin(), matches.end(), [](const Cookie* left, const Cookie* right) { return left->path.size() > right->path.size(); });
|
||||
for (auto run = matches.begin(); run != matches.end();) {
|
||||
const auto end = std::find_if(run, matches.end(), [&](const Cookie* match) { return match->path.size() != (*run)->path.size(); });
|
||||
std::rotate(run, run + 1, end);
|
||||
run = end;
|
||||
}
|
||||
std::string header;
|
||||
for (const Cookie* match : matches) AppendCookie(header, *match);
|
||||
const std::size_t size = header.size() + HEADER_SLACK;
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
bool Http2CookieBoxExists(int cookieBoxId);
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
void Http2AddCookieBox(int cookieBoxId);
|
||||
void Http2ForgetCookies(int cookieBoxId);
|
||||
void Http2FlushCookies(int cookieBoxId);
|
||||
std::optional<std::string> Http2CookieHeader(int cookieBoxId, std::string_view url);
|
||||
void Http2StoreResponseCookie(int cookieBoxId, std::string_view url, std::string_view cookie);
|
||||
@@ -66,11 +66,6 @@ static void ReleaseOwnedHandles(int owner) {
|
||||
}
|
||||
}
|
||||
|
||||
bool Http2CookieBoxExists(int cookieBoxId) {
|
||||
std::lock_guard lock(g_handlesMutex);
|
||||
return FindHandle(cookieBoxId, Http2Kind::CookieBox) != nullptr;
|
||||
}
|
||||
|
||||
static bool HasSettings(const Http2Handle& handle) {
|
||||
return handle.kind == Http2Kind::Template || handle.kind == Http2Kind::Request;
|
||||
}
|
||||
@@ -362,9 +357,15 @@ int APS5_VABI sceHttp2CreateCookieBox(std::size_t poolSize) {
|
||||
void* address = nullptr;
|
||||
if (const int result = sceKernelMapNamedFlexibleMemory(&address, mappedSize, 3, 0, "SceHttp2CookieBox"); result != 0) return result;
|
||||
std::shared_ptr<void> memory(address, [mappedSize](void* mapped) { sceKernelMunmap(reinterpret_cast<std::uint64_t>(mapped), mappedSize); });
|
||||
std::lock_guard lock(g_handlesMutex);
|
||||
const int id = g_nextHandle.fetch_add(1, std::memory_order_relaxed);
|
||||
g_handles.emplace(id, Http2Handle{Http2Kind::CookieBox, 0, poolSize, std::move(memory)});
|
||||
Http2AddCookieBox(id);
|
||||
try {
|
||||
std::lock_guard lock(g_handlesMutex);
|
||||
g_handles.emplace(id, Http2Handle{Http2Kind::CookieBox, 0, poolSize, std::move(memory)});
|
||||
} catch (...) {
|
||||
Http2ForgetCookies(id);
|
||||
throw;
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
@@ -379,7 +380,11 @@ int APS5_VABI sceHttp2DeleteCookieBox(int cookie_box_id) {
|
||||
}
|
||||
|
||||
int APS5_VABI sceHttp2SetCookieBox(int id, int cookie_box_id) {
|
||||
return Update(id, __func__, [&](auto&) { if (cookie_box_id != 0) NotImplemented_nid_no_patch("sceHttp2SetCookieBox"); });
|
||||
return Update(id, __func__, [&](auto& settings) {
|
||||
if (cookie_box_id != 0 && !FindHandle(cookie_box_id, Http2Kind::CookieBox)) return ERROR_INVALID_ID;
|
||||
settings.cookieBox = cookie_box_id;
|
||||
return 0;
|
||||
}, true);
|
||||
}
|
||||
|
||||
int APS5_VABI sceHttp2SetRequestNoContentLength(int id) {
|
||||
@@ -457,9 +462,14 @@ int APS5_VABI sceHttp2GetAutoRedirect(int id, int* enable) {
|
||||
});
|
||||
}
|
||||
|
||||
int APS5_VABI sceHttp2GetCookieBox(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
int APS5_VABI sceHttp2GetCookieBox(int id, int* cookieBoxId) {
|
||||
if (!cookieBoxId) return ERROR_NULL_POINTER;
|
||||
*cookieBoxId = 0;
|
||||
return Read(id, [&](const auto& settings) {
|
||||
if (settings.cookieBox == 0) return ERROR_NOT_FOUND;
|
||||
*cookieBoxId = settings.cookieBox;
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
|
||||
int APS5_VABI sceHttp2RedirectCacheFlush(int lib_http2_ctx_id) {
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#endif
|
||||
|
||||
#include "Transport.hpp"
|
||||
#include "Cookies.hpp"
|
||||
#include "prx/libSceHttp/include/Http.hpp"
|
||||
|
||||
#include "zlib.h"
|
||||
@@ -320,6 +321,8 @@ struct Request::State {
|
||||
std::string contentEncoding;
|
||||
bool inflatedGzip = false;
|
||||
int blockStatus = 0;
|
||||
bool hasAuthChallenge = false;
|
||||
bool hasProxyAuthChallenge = false;
|
||||
std::string location;
|
||||
bool hasLocation = false;
|
||||
int networkResult = 0;
|
||||
@@ -360,6 +363,8 @@ struct Request::State {
|
||||
|
||||
~State() { Close(); }
|
||||
|
||||
std::string Address() const { return std::string(url.secure ? "https://" : "http://") + url.authority + url.path; }
|
||||
|
||||
void Close() {
|
||||
callbackLock = nullptr;
|
||||
if (session) nghttp2_session_del(std::exchange(session, nullptr));
|
||||
@@ -650,7 +655,7 @@ struct Request::State {
|
||||
Require(sceHttpParseStatusLine(rawHeaders.data(), rawHeaders.size(), &major, &minor, &code, &reason, &reasonSize) >= 0 && major == 1 && minor >= 0 && minor <= 1,
|
||||
"sceHttp2: malformed or unsupported HTTP1 status line");
|
||||
status = code;
|
||||
hasLength = chunked = hasLocation = false;
|
||||
hasLength = chunked = hasLocation = hasAuthChallenge = hasProxyAuthChallenge = false;
|
||||
contentEncoding.clear();
|
||||
contentLength = 0;
|
||||
location.clear();
|
||||
@@ -674,6 +679,12 @@ struct Request::State {
|
||||
Require(!hasLocation, "sceHttp2: duplicate HTTP1 Location");
|
||||
hasLocation = true;
|
||||
location = value;
|
||||
} else if (name == "set-cookie" && settings.cookieBox != 0) {
|
||||
Http2StoreResponseCookie(settings.cookieBox, Address(), value);
|
||||
} else if (name == "www-authenticate") {
|
||||
hasAuthChallenge = true;
|
||||
} else if (name == "proxy-authenticate") {
|
||||
hasProxyAuthChallenge = true;
|
||||
}
|
||||
}
|
||||
if (status >= 100 && status < 200) {
|
||||
@@ -681,7 +692,8 @@ struct Request::State {
|
||||
return;
|
||||
}
|
||||
Require(status >= 200 && status <= 599 && !(hasLength && chunked), "sceHttp2: invalid or ambiguous HTTP1 framing");
|
||||
Require(!settings.auth || (status != 401 && status != 407), "sceHttp2: authentication is not implemented");
|
||||
const bool authenticationChallenge = (status == 401 && hasAuthChallenge) || (status == 407 && hasProxyAuthChallenge);
|
||||
Require(!settings.auth || !authenticationChallenge, "sceHttp2: authentication is not implemented");
|
||||
remaining = contentLength;
|
||||
ready = true;
|
||||
ended = method == "HEAD" || status == 204 || status == 304 || (hasLength && remaining == 0);
|
||||
@@ -697,7 +709,23 @@ struct Request::State {
|
||||
header += "Host: " + authority + "\r\nConnection: close\r\n";
|
||||
if (!settings.userAgent.empty() && std::none_of(settings.headers.begin(), settings.headers.end(), [](const auto& field) { return field.first == "user-agent"; }))
|
||||
header += "User-Agent: " + settings.userAgent + "\r\n";
|
||||
for (const auto& [name, value] : settings.headers) if (name != "host") header += name + ": " + value + "\r\n";
|
||||
std::string cookies;
|
||||
bool hasCookie = false;
|
||||
for (const auto& [name, value] : settings.headers) {
|
||||
if (name == "cookie") {
|
||||
if (!cookies.empty() && !value.empty()) cookies += "; ";
|
||||
cookies += value;
|
||||
hasCookie = true;
|
||||
} else if (name != "host") header += name + ": " + value + "\r\n";
|
||||
}
|
||||
if (settings.cookieBox != 0) {
|
||||
if (const auto stored = Http2CookieHeader(settings.cookieBox, Address())) {
|
||||
if (!cookies.empty()) cookies += "; ";
|
||||
cookies += *stored;
|
||||
hasCookie = true;
|
||||
}
|
||||
}
|
||||
if (hasCookie) header += "cookie: " + cookies + "\r\n";
|
||||
const bool chunkUpload = settings.noContentLength && size;
|
||||
if (chunkUpload) header += "Transfer-Encoding: chunked\r\n";
|
||||
else if (!settings.noContentLength) header += "Content-Length: " + std::to_string(size) + "\r\n";
|
||||
@@ -784,6 +812,7 @@ struct Request::State {
|
||||
if (frame->hd.stream_id == state.stream) {
|
||||
Require(!state.ready, "sceHttp2: response trailers are not implemented");
|
||||
state.blockStatus = 0;
|
||||
state.hasAuthChallenge = state.hasProxyAuthChallenge = false;
|
||||
state.hasLength = false;
|
||||
state.contentLength = 0;
|
||||
state.location.clear();
|
||||
@@ -803,6 +832,8 @@ struct Request::State {
|
||||
const std::string_view key(reinterpret_cast<const char*>(name), nameSize);
|
||||
const std::string_view text(reinterpret_cast<const char*>(value), valueSize);
|
||||
if (key == ":status") state.blockStatus = static_cast<int>(Number(text));
|
||||
if (key == "www-authenticate") state.hasAuthChallenge = true;
|
||||
if (key == "proxy-authenticate") state.hasProxyAuthChallenge = true;
|
||||
if (key == "location") {
|
||||
Require(!state.hasLocation, "sceHttp2: duplicate redirect location; console error mapping is unknown");
|
||||
state.location = text;
|
||||
@@ -812,6 +843,7 @@ struct Request::State {
|
||||
state.contentLength = Number(text);
|
||||
state.hasLength = true;
|
||||
}
|
||||
if (key == "set-cookie" && state.settings.cookieBox != 0) Http2StoreResponseCookie(state.settings.cookieBox, state.Address(), text);
|
||||
if (key == "content-encoding") state.contentEncoding = Lower(std::string(text));
|
||||
});
|
||||
}
|
||||
@@ -825,7 +857,8 @@ struct Request::State {
|
||||
if (frame->hd.stream_id != state.stream) return;
|
||||
if (frame->hd.type == NGHTTP2_HEADERS && state.blockStatus >= 200) {
|
||||
state.status = state.blockStatus;
|
||||
Require(!state.settings.auth || (state.status != 401 && state.status != 407), "sceHttp2: authentication is not implemented");
|
||||
const bool authenticationChallenge = (state.status == 401 && state.hasAuthChallenge) || (state.status == 407 && state.hasProxyAuthChallenge);
|
||||
Require(!state.settings.auth || !authenticationChallenge, "sceHttp2: authentication is not implemented");
|
||||
state.ready = true;
|
||||
}
|
||||
if ((frame->hd.type == NGHTTP2_HEADERS || frame->hd.type == NGHTTP2_DATA) &&
|
||||
@@ -894,6 +927,9 @@ struct Request::State {
|
||||
if (!settings.userAgent.empty() && std::none_of(settings.headers.begin(), settings.headers.end(), [](const auto& header) { return header.first == "user-agent"; }))
|
||||
fields.emplace_back("user-agent", settings.userAgent);
|
||||
for (const auto& header : settings.headers) if (header.first != "host") fields.push_back(header);
|
||||
if (settings.cookieBox != 0) {
|
||||
if (auto cookie = Http2CookieHeader(settings.cookieBox, Address())) fields.emplace_back("cookie", std::move(*cookie));
|
||||
}
|
||||
if (!settings.noContentLength) fields.emplace_back("content-length", std::to_string(settings.contentLength));
|
||||
std::vector<nghttp2_nv> headers;
|
||||
for (auto& [name, value] : fields) headers.push_back({reinterpret_cast<std::uint8_t*>(name.data()),
|
||||
|
||||
@@ -42,6 +42,7 @@ struct Settings {
|
||||
void* authInfoArgument = nullptr;
|
||||
void* preSendCallback = nullptr;
|
||||
void* preSendArgument = nullptr;
|
||||
int cookieBox = 0;
|
||||
};
|
||||
|
||||
struct Capacity {
|
||||
|
||||
@@ -82,7 +82,7 @@ int APS5_VABI sceHttp2CookieImport(void);
|
||||
int APS5_VABI sceHttp2GetAuthEnabled(int id, int* isEnable);
|
||||
int APS5_VABI sceHttp2GetAutoRedirect(int id, int* enable);
|
||||
int APS5_VABI sceHttp2GetCookie(int cookieBoxId, const char* url, char* cookie, std::size_t* required, std::size_t prepared);
|
||||
int APS5_VABI sceHttp2GetCookieBox(void);
|
||||
int APS5_VABI sceHttp2GetCookieBox(int id, int* cookieBoxId);
|
||||
int APS5_VABI sceHttp2GetCookieStats(int cookieBoxId, Http2CookieStats* stats);
|
||||
int APS5_VABI sceHttp2RedirectCacheFlush(int libHttp2CtxId);
|
||||
int APS5_VABI sceHttp2RemoveRequestHeader(int id, const char* name);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
add_library(libSceIcu SHARED EXCLUDE_FROM_ALL Comparisons.cpp ConstrainedFieldPosition.cpp Enumeration.cpp ErrorName.cpp Export.cpp Invariant.cpp Iterator.cpp Numeric.cpp Set.cpp Tokenize.cpp Unescape.cpp Version.cpp)
|
||||
add_library(libSceIcu SHARED EXCLUDE_FROM_ALL Comparisons.cpp ConstrainedFieldPosition.cpp Enumeration.cpp ErrorName.cpp Export.cpp Invariant.cpp Iterator.cpp Numeric.cpp Set.cpp Tokenize.cpp Unescape.cpp Version.cpp Utf32.cpp JavaUtf8.cpp Compare.cpp Search.cpp Character.cpp Configuration.cpp)
|
||||
target_include_directories(libSceIcu PRIVATE ${LIBS_INCLUDE_DIR})
|
||||
target_sources(libSceIcu PRIVATE RecoveredInventory.cpp)
|
||||
configure_windows_unwind(libSceIcu)
|
||||
@@ -9,6 +9,33 @@ target_sources(libSceIcu PRIVATE CompareIter.cpp)
|
||||
target_sources(libSceIcu PRIVATE Space.cpp)
|
||||
target_sources(libSceIcu PRIVATE Utf8.cpp)
|
||||
target_sources(libSceIcu PRIVATE Resource.cpp)
|
||||
target_sources(libSceIcu PRIVATE Locale.cpp)
|
||||
|
||||
if(BUILD_TESTING)
|
||||
add_test_executable(guest_icu_locale_tests ${CMAKE_CURRENT_SOURCE_DIR}/tests/Locale.cpp)
|
||||
target_include_directories(guest_icu_locale_tests PRIVATE ${LIBS_INCLUDE_DIR})
|
||||
target_link_libraries(guest_icu_locale_tests PRIVATE libSceIcu)
|
||||
configure_windows_unwind(guest_icu_locale_tests)
|
||||
add_test(NAME guest_icu_locale COMMAND guest_icu_locale_tests)
|
||||
set_tests_properties(guest_icu_locale PROPERTIES TIMEOUT 10)
|
||||
if(WIN32)
|
||||
set_tests_properties(guest_icu_locale PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceIcu>")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(BUILD_TESTING)
|
||||
add_test_executable(guest_icu_version_extents_tests ${LIBS_INCLUDE_DIR}/tests/GuestIcuVersionExtents.cpp)
|
||||
target_include_directories(guest_icu_version_extents_tests PRIVATE ${LIBS_INCLUDE_DIR})
|
||||
target_link_libraries(guest_icu_version_extents_tests PRIVATE libSceIcu libc)
|
||||
configure_windows_unwind(guest_icu_version_extents_tests)
|
||||
add_test(NAME guest_icu_version_extents COMMAND guest_icu_version_extents_tests)
|
||||
set_tests_properties(guest_icu_version_extents PROPERTIES TIMEOUT 20)
|
||||
if(WIN32)
|
||||
set_tests_properties(guest_icu_version_extents PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceIcu>")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(BUILD_TESTING)
|
||||
add_test_executable(guest_icu_utf8_tests ${LIBS_INCLUDE_DIR}/tests/GuestIcuUtf8.cpp)
|
||||
@@ -175,3 +202,81 @@ if(BUILD_TESTING)
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceIcu>")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(BUILD_TESTING)
|
||||
add_test_executable(guest_icu_utf32_tests ${CMAKE_CURRENT_SOURCE_DIR}/tests/Utf32.cpp)
|
||||
target_include_directories(guest_icu_utf32_tests PRIVATE ${LIBS_INCLUDE_DIR})
|
||||
target_link_libraries(guest_icu_utf32_tests PRIVATE libSceIcu)
|
||||
configure_windows_unwind(guest_icu_utf32_tests)
|
||||
add_test(NAME guest_icu_utf32 COMMAND guest_icu_utf32_tests)
|
||||
set_tests_properties(guest_icu_utf32 PROPERTIES TIMEOUT 30)
|
||||
if(WIN32)
|
||||
set_tests_properties(guest_icu_utf32 PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceIcu>")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(BUILD_TESTING)
|
||||
add_test_executable(guest_icu_java_utf8_tests ${CMAKE_CURRENT_SOURCE_DIR}/tests/JavaUtf8.cpp)
|
||||
target_include_directories(guest_icu_java_utf8_tests PRIVATE ${LIBS_INCLUDE_DIR})
|
||||
target_link_libraries(guest_icu_java_utf8_tests PRIVATE libSceIcu)
|
||||
configure_windows_unwind(guest_icu_java_utf8_tests)
|
||||
add_test(NAME guest_icu_java_utf8 COMMAND guest_icu_java_utf8_tests)
|
||||
set_tests_properties(guest_icu_java_utf8 PROPERTIES TIMEOUT 30)
|
||||
if(WIN32)
|
||||
set_tests_properties(guest_icu_java_utf8 PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceIcu>")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(BUILD_TESTING)
|
||||
add_test_executable(guest_icu_compare_tests ${CMAKE_CURRENT_SOURCE_DIR}/tests/Compare.cpp)
|
||||
target_include_directories(guest_icu_compare_tests PRIVATE ${LIBS_INCLUDE_DIR})
|
||||
target_link_libraries(guest_icu_compare_tests PRIVATE libSceIcu)
|
||||
configure_windows_unwind(guest_icu_compare_tests)
|
||||
add_test(NAME guest_icu_compare COMMAND guest_icu_compare_tests)
|
||||
set_tests_properties(guest_icu_compare PROPERTIES TIMEOUT 30)
|
||||
if(WIN32)
|
||||
set_tests_properties(guest_icu_compare PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceIcu>")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(BUILD_TESTING)
|
||||
add_test_executable(guest_icu_search_tests ${CMAKE_CURRENT_SOURCE_DIR}/tests/Search.cpp)
|
||||
target_include_directories(guest_icu_search_tests PRIVATE ${LIBS_INCLUDE_DIR})
|
||||
target_link_libraries(guest_icu_search_tests PRIVATE libSceIcu)
|
||||
configure_windows_unwind(guest_icu_search_tests)
|
||||
add_test(NAME guest_icu_search COMMAND guest_icu_search_tests)
|
||||
set_tests_properties(guest_icu_search PROPERTIES TIMEOUT 30)
|
||||
if(WIN32)
|
||||
set_tests_properties(guest_icu_search PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceIcu>")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(BUILD_TESTING)
|
||||
add_test_executable(guest_icu_character_tests ${CMAKE_CURRENT_SOURCE_DIR}/tests/Character.cpp)
|
||||
target_include_directories(guest_icu_character_tests PRIVATE ${LIBS_INCLUDE_DIR})
|
||||
target_link_libraries(guest_icu_character_tests PRIVATE libSceIcu)
|
||||
configure_windows_unwind(guest_icu_character_tests)
|
||||
add_test(NAME guest_icu_character COMMAND guest_icu_character_tests)
|
||||
set_tests_properties(guest_icu_character PROPERTIES TIMEOUT 30)
|
||||
if(WIN32)
|
||||
set_tests_properties(guest_icu_character PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceIcu>")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(BUILD_TESTING)
|
||||
add_test_executable(guest_icu_configuration_tests ${CMAKE_CURRENT_SOURCE_DIR}/tests/Configuration.cpp)
|
||||
target_include_directories(guest_icu_configuration_tests PRIVATE ${LIBS_INCLUDE_DIR})
|
||||
target_link_libraries(guest_icu_configuration_tests PRIVATE libSceIcu)
|
||||
configure_windows_unwind(guest_icu_configuration_tests)
|
||||
add_test(NAME guest_icu_configuration COMMAND guest_icu_configuration_tests)
|
||||
set_tests_properties(guest_icu_configuration PROPERTIES TIMEOUT 30)
|
||||
if(WIN32)
|
||||
set_tests_properties(guest_icu_configuration PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceIcu>")
|
||||
endif()
|
||||
endif()
|
||||
@@ -0,0 +1,27 @@
|
||||
#include "prx/libSceIcu/libSceIcu.hpp"
|
||||
#include "prx/libSceIcu/Status.hpp"
|
||||
#include <cstdint>
|
||||
|
||||
extern "C" {
|
||||
|
||||
std::int8_t APS5_VABI u_isISOControl_nid_postfix(std::int32_t character) {
|
||||
return static_cast<std::uint32_t>(character) <= 0x9f && (character <= 0x1f || character >= 0x7f);
|
||||
}
|
||||
|
||||
std::int32_t APS5_VABI u_forDigit_nid_postfix(std::int32_t digit, std::int8_t radix) {
|
||||
if (radix < 2 || radix > 36 || digit < 0 || digit >= radix) return 0;
|
||||
return digit < 10 ? '0' + digit : 'a' - 10 + digit;
|
||||
}
|
||||
|
||||
|
||||
std::int32_t APS5_VABI u_getISOComment_nid_postfix(std::int32_t, char* destination, std::int32_t capacity, std::int32_t* status) {
|
||||
if (!status || *status > 0) return 0;
|
||||
if (capacity < 0 || (capacity > 0 && !destination)) {
|
||||
*status = Icu::IllegalArgument;
|
||||
return 0;
|
||||
}
|
||||
Icu::Terminate(destination, capacity, 0, status);
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
#include "prx/libc/include/general/VabiMacros.hpp"
|
||||
#include <cstdint>
|
||||
|
||||
extern "C" {
|
||||
std::int8_t APS5_VABI u_isISOControl_nid_postfix(std::int32_t);
|
||||
std::int32_t APS5_VABI u_forDigit_nid_postfix(std::int32_t, std::int8_t);
|
||||
std::int32_t APS5_VABI u_getISOComment_nid_postfix(std::int32_t, char*, std::int32_t, std::int32_t*);
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
#include "prx/libSceIcu/libSceIcu.hpp"
|
||||
#include "prx/libSceIcu/Status.hpp"
|
||||
#include <cstdint>
|
||||
|
||||
namespace {
|
||||
|
||||
bool Lead(std::int32_t unit) { return unit >= 0xd800 && unit <= 0xdbff; }
|
||||
bool Trail(std::int32_t unit) { return unit >= 0xdc00 && unit <= 0xdfff; }
|
||||
|
||||
std::int64_t Measure(const char16_t* text) {
|
||||
std::int64_t length = 0;
|
||||
while (text[length] != 0) ++length;
|
||||
return length;
|
||||
}
|
||||
|
||||
std::int32_t CodePointOrder(const char16_t* text, std::int64_t index, std::int64_t length) {
|
||||
std::int32_t unit = text[index];
|
||||
const bool pair = (Lead(unit) && (length < 0 || index + 1 < length) && Trail(text[index + 1])) ||
|
||||
(Trail(unit) && index > 0 && Lead(text[index - 1]));
|
||||
return pair ? unit : unit - 0x2800;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
||||
std::int32_t APS5_VABI u_strCompare_nid_postfix(const char16_t* first, std::int32_t firstLength, const char16_t* second,
|
||||
std::int32_t secondLength, std::int8_t codePointOrder) {
|
||||
if (!first || firstLength < -1 || !second || secondLength < -1) return 0;
|
||||
const bool terminated = firstLength < 0 && secondLength < 0;
|
||||
std::int64_t limit1 = firstLength;
|
||||
std::int64_t limit2 = secondLength;
|
||||
std::int32_t equalResult = 0;
|
||||
std::int64_t index = 0;
|
||||
if (terminated) {
|
||||
if (first == second) return 0;
|
||||
for (; first[index] == second[index]; ++index) {
|
||||
if (first[index] == 0) return 0;
|
||||
}
|
||||
} else {
|
||||
if (limit1 < 0) limit1 = Measure(first);
|
||||
if (limit2 < 0) limit2 = Measure(second);
|
||||
const auto common = limit1 < limit2 ? limit1 : limit2;
|
||||
equalResult = limit1 < limit2 ? -1 : limit1 > limit2 ? 1 : 0;
|
||||
if (first == second) return equalResult;
|
||||
for (; index < common && first[index] == second[index]; ++index) {}
|
||||
if (index == common) return equalResult;
|
||||
}
|
||||
std::int32_t unit1 = first[index];
|
||||
std::int32_t unit2 = second[index];
|
||||
if (unit1 >= 0xd800 && unit2 >= 0xd800 && codePointOrder) {
|
||||
unit1 = CodePointOrder(first, index, terminated ? -1 : limit1);
|
||||
unit2 = CodePointOrder(second, index, terminated ? -1 : limit2);
|
||||
}
|
||||
return unit1 - unit2;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
#include "prx/libc/include/general/VabiMacros.hpp"
|
||||
#include <cstdint>
|
||||
|
||||
extern "C" {
|
||||
std::int32_t APS5_VABI u_strCompare_nid_postfix(const char16_t*, std::int32_t, const char16_t*, std::int32_t, std::int8_t);
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
#include "prx/libSceIcu/libSceIcu.hpp"
|
||||
#include "prx/libSceIcu/Status.hpp"
|
||||
#include <cstdint>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
namespace {
|
||||
|
||||
struct MemoryFunctions {
|
||||
const void* context = nullptr;
|
||||
Icu::MemoryAllocate allocate = nullptr;
|
||||
Icu::MemoryReallocate reallocate = nullptr;
|
||||
Icu::MemoryFree release = nullptr;
|
||||
};
|
||||
|
||||
std::mutex MemoryLock;
|
||||
MemoryFunctions Memory;
|
||||
std::mutex DirectoryLock;
|
||||
std::string DataDirectory;
|
||||
std::string TimeZoneDirectory;
|
||||
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
||||
void APS5_VABI u_setMutexFunctions_nid_postfix(const void*, const void*, const void*, const void*, const void*, std::int32_t* status) {
|
||||
if (!Icu::Failure(status)) *status = Icu::UnsupportedError;
|
||||
}
|
||||
|
||||
void APS5_VABI u_setAtomicIncDecFunctions_nid_postfix(const void*, const void*, const void*, std::int32_t* status) {
|
||||
if (!Icu::Failure(status)) *status = Icu::UnsupportedError;
|
||||
}
|
||||
|
||||
|
||||
void APS5_VABI u_setMemoryFunctions_nid_postfix(const void* context, Icu::MemoryAllocate allocate, Icu::MemoryReallocate reallocate,
|
||||
Icu::MemoryFree release, std::int32_t* status) {
|
||||
if (Icu::Failure(status)) return;
|
||||
if (!allocate || !reallocate || !release) {
|
||||
*status = Icu::IllegalArgument;
|
||||
return;
|
||||
}
|
||||
const std::lock_guard lock(MemoryLock);
|
||||
Memory = {context, allocate, reallocate, release};
|
||||
}
|
||||
|
||||
|
||||
const char* APS5_VABI u_getDataDirectory_nid_postfix() {
|
||||
const std::lock_guard lock(DirectoryLock);
|
||||
return DataDirectory.c_str();
|
||||
}
|
||||
|
||||
void APS5_VABI u_setDataDirectory_nid_postfix(const char* directory) {
|
||||
std::string copy = directory ? directory : "";
|
||||
const std::lock_guard lock(DirectoryLock);
|
||||
DataDirectory = std::move(copy);
|
||||
}
|
||||
|
||||
|
||||
const char* APS5_VABI u_getTimeZoneFilesDirectory_nid_postfix(std::int32_t* status) {
|
||||
if (Icu::Failure(status)) return "";
|
||||
const std::lock_guard lock(DirectoryLock);
|
||||
return TimeZoneDirectory.c_str();
|
||||
}
|
||||
|
||||
void APS5_VABI u_setTimeZoneFilesDirectory_nid_postfix(const char* path, std::int32_t* status) {
|
||||
if (Icu::Failure(status)) return;
|
||||
const std::lock_guard lock(DirectoryLock);
|
||||
TimeZoneDirectory.clear();
|
||||
if (!path) {
|
||||
*status = Icu::IllegalArgument;
|
||||
return;
|
||||
}
|
||||
TimeZoneDirectory = path;
|
||||
}
|
||||
|
||||
|
||||
void APS5_VABI u_cleanup_nid_postfix() {
|
||||
{
|
||||
const std::lock_guard lock(MemoryLock);
|
||||
Memory = {};
|
||||
}
|
||||
const std::lock_guard lock(DirectoryLock);
|
||||
DataDirectory.clear();
|
||||
TimeZoneDirectory.clear();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
#include "prx/libc/include/general/VabiMacros.hpp"
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
namespace Icu {
|
||||
using MemoryAllocate = void* (APS5_VABI*)(const void*, std::size_t);
|
||||
using MemoryReallocate = void* (APS5_VABI*)(const void*, void*, std::size_t);
|
||||
using MemoryFree = void (APS5_VABI*)(const void*, void*);
|
||||
} // namespace Icu
|
||||
|
||||
extern "C" {
|
||||
void APS5_VABI u_setMutexFunctions_nid_postfix(const void*, const void*, const void*, const void*, const void*, std::int32_t*);
|
||||
void APS5_VABI u_setAtomicIncDecFunctions_nid_postfix(const void*, const void*, const void*, std::int32_t*);
|
||||
void APS5_VABI u_setMemoryFunctions_nid_postfix(const void*, Icu::MemoryAllocate, Icu::MemoryReallocate, Icu::MemoryFree, std::int32_t*);
|
||||
const char* APS5_VABI u_getDataDirectory_nid_postfix();
|
||||
void APS5_VABI u_setDataDirectory_nid_postfix(const char*);
|
||||
const char* APS5_VABI u_getTimeZoneFilesDirectory_nid_postfix(std::int32_t*);
|
||||
void APS5_VABI u_setTimeZoneFilesDirectory_nid_postfix(const char*, std::int32_t*);
|
||||
void APS5_VABI u_cleanup_nid_postfix();
|
||||
}
|
||||
@@ -42,10 +42,6 @@ int APS5_VABI UCNV_TO_U_CALLBACK_SUBSTITUTE_nid_postfix() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
}
|
||||
|
||||
int APS5_VABI locale_getKeywordsStart_nid_postfix() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
}
|
||||
|
||||
int APS5_VABI res_countArrayItems_nid_postfix() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
}
|
||||
@@ -118,11 +114,6 @@ int APS5_VABI u_charType_nid_postfix() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
}
|
||||
|
||||
int APS5_VABI sceIcuUnknown_1SsridDRBYQ_nid_no_patch() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
}
|
||||
APS5_EXPORT("1SsridDRBYQ", sceIcuUnknown_1SsridDRBYQ_nid_no_patch);
|
||||
|
||||
int APS5_VABI u_digit_nid_postfix() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
}
|
||||
@@ -143,11 +134,6 @@ int APS5_VABI u_foldCase_nid_postfix() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
}
|
||||
|
||||
int APS5_VABI sceIcuUnknown_hI8JVrcGWhc_nid_no_patch() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
}
|
||||
APS5_EXPORT("hI8JVrcGWhc", sceIcuUnknown_hI8JVrcGWhc_nid_no_patch);
|
||||
|
||||
int APS5_VABI u_getBidiPairedBracket_nid_postfix() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
}
|
||||
@@ -160,11 +146,6 @@ int APS5_VABI u_getCombiningClass_nid_postfix() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
}
|
||||
|
||||
int APS5_VABI sceIcuUnknown_UmNS0P0_ovs_nid_no_patch() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
}
|
||||
APS5_EXPORT("UmNS0P0-ovs", sceIcuUnknown_UmNS0P0_ovs_nid_no_patch);
|
||||
|
||||
int APS5_VABI u_getDefaultConverter_nid_postfix() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
}
|
||||
@@ -173,11 +154,6 @@ int APS5_VABI u_getFC_NFKC_Closure_nid_postfix() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
}
|
||||
|
||||
int APS5_VABI sceIcuUnknown__kd2qcTMvHE_nid_no_patch() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
}
|
||||
APS5_EXPORT("+kd2qcTMvHE", sceIcuUnknown__kd2qcTMvHE_nid_no_patch);
|
||||
|
||||
int APS5_VABI u_getIntPropertyMap_nid_postfix() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
}
|
||||
@@ -214,11 +190,6 @@ int APS5_VABI u_getPropertyValueName_nid_postfix() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
}
|
||||
|
||||
int APS5_VABI sceIcuUnknown_5__ZFSRQ0_k_nid_no_patch() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
}
|
||||
APS5_EXPORT("5+-ZFSRQ0+k", sceIcuUnknown_5__ZFSRQ0_k_nid_no_patch);
|
||||
|
||||
int APS5_VABI u_hasBinaryProperty_nid_postfix() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
}
|
||||
@@ -235,11 +206,6 @@ int APS5_VABI u_isIDStart_nid_postfix() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
}
|
||||
|
||||
int APS5_VABI sceIcuUnknown_dzbKmf3Ycog_nid_no_patch() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
}
|
||||
APS5_EXPORT("dzbKmf3Ycog", sceIcuUnknown_dzbKmf3Ycog_nid_no_patch);
|
||||
|
||||
int APS5_VABI u_isJavaIDPart_nid_postfix() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
}
|
||||
@@ -324,78 +290,18 @@ int APS5_VABI u_releaseDefaultConverter_nid_postfix() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
}
|
||||
|
||||
int APS5_VABI sceIcuUnknown_yVobQd4t_iA_nid_no_patch() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
}
|
||||
APS5_EXPORT("yVobQd4t+iA", sceIcuUnknown_yVobQd4t_iA_nid_no_patch);
|
||||
|
||||
int APS5_VABI sceIcuUnknown_FNfP9dKTd5Q_nid_no_patch() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
}
|
||||
APS5_EXPORT("FNfP9dKTd5Q", sceIcuUnknown_FNfP9dKTd5Q_nid_no_patch);
|
||||
|
||||
int APS5_VABI sceIcuUnknown_TU6Yl8_mT3c_nid_no_patch() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
}
|
||||
APS5_EXPORT("TU6Yl8+mT3c", sceIcuUnknown_TU6Yl8_mT3c_nid_no_patch);
|
||||
|
||||
int APS5_VABI sceIcuUnknown_Wm0y_9IRE8I_nid_no_patch() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
}
|
||||
APS5_EXPORT("Wm0y-9IRE8I", sceIcuUnknown_Wm0y_9IRE8I_nid_no_patch);
|
||||
|
||||
int APS5_VABI sceIcuUnknown_xAo5lvA_W3o_nid_no_patch() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
}
|
||||
APS5_EXPORT("xAo5lvA+W3o", sceIcuUnknown_xAo5lvA_W3o_nid_no_patch);
|
||||
|
||||
int APS5_VABI u_strCaseCompare_nid_postfix() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
}
|
||||
|
||||
int APS5_VABI sceIcuUnknown_pgvyZZ_d1ck_nid_no_patch() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
}
|
||||
APS5_EXPORT("pgvyZZ+d1ck", sceIcuUnknown_pgvyZZ_d1ck_nid_no_patch);
|
||||
|
||||
int APS5_VABI u_strFoldCase_nid_postfix() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
}
|
||||
|
||||
int APS5_VABI sceIcuUnknown_OAFz3KoG_gA_nid_no_patch() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
}
|
||||
APS5_EXPORT("OAFz3KoG-gA", sceIcuUnknown_OAFz3KoG_gA_nid_no_patch);
|
||||
|
||||
int APS5_VABI sceIcuUnknown_6_NsFXHpWXc_nid_no_patch() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
}
|
||||
APS5_EXPORT("6+NsFXHpWXc", sceIcuUnknown_6_NsFXHpWXc_nid_no_patch);
|
||||
|
||||
int APS5_VABI sceIcuUnknown_q3mX36AQeC4_nid_no_patch() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
}
|
||||
APS5_EXPORT("q3mX36AQeC4", sceIcuUnknown_q3mX36AQeC4_nid_no_patch);
|
||||
|
||||
int APS5_VABI sceIcuUnknown_RIcNJWDYKhM_nid_no_patch() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
}
|
||||
APS5_EXPORT("RIcNJWDYKhM", sceIcuUnknown_RIcNJWDYKhM_nid_no_patch);
|
||||
|
||||
int APS5_VABI u_strToLower_nid_postfix() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
}
|
||||
|
||||
int APS5_VABI sceIcuUnknown_ZxnyBy8reV4_nid_no_patch() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
}
|
||||
APS5_EXPORT("ZxnyBy8reV4", sceIcuUnknown_ZxnyBy8reV4_nid_no_patch);
|
||||
|
||||
int APS5_VABI sceIcuUnknown_X7gbPHVoNEw_nid_no_patch() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
}
|
||||
APS5_EXPORT("X7gbPHVoNEw", sceIcuUnknown_X7gbPHVoNEw_nid_no_patch);
|
||||
|
||||
int APS5_VABI u_strToUpper_nid_postfix() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
}
|
||||
@@ -408,16 +314,6 @@ int APS5_VABI u_strncasecmp_nid_postfix() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
}
|
||||
|
||||
int APS5_VABI sceIcuUnknown_BsjbIVU27jQ_nid_no_patch() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
}
|
||||
APS5_EXPORT("BsjbIVU27jQ", sceIcuUnknown_BsjbIVU27jQ_nid_no_patch);
|
||||
|
||||
int APS5_VABI sceIcuUnknown_wezXBwKdjAY_nid_no_patch() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
}
|
||||
APS5_EXPORT("wezXBwKdjAY", sceIcuUnknown_wezXBwKdjAY_nid_no_patch);
|
||||
|
||||
|
||||
int APS5_VABI u_tolower() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
|
||||
@@ -7,7 +7,7 @@ int APS5_VABI UCNV_FROM_U_CALLBACK_ESCAPE_nid_postfix();
|
||||
int APS5_VABI UCNV_FROM_U_CALLBACK_SUBSTITUTE_nid_postfix();
|
||||
int APS5_VABI UCNV_TO_U_CALLBACK_ESCAPE_nid_postfix();
|
||||
int APS5_VABI UCNV_TO_U_CALLBACK_SUBSTITUTE_nid_postfix();
|
||||
int APS5_VABI locale_getKeywordsStart_nid_postfix();
|
||||
const char* APS5_VABI locale_getKeywordsStart_nid_postfix(const char* localeID);
|
||||
int APS5_VABI res_countArrayItems_nid_postfix();
|
||||
int APS5_VABI res_getAlias_nid_postfix();
|
||||
int APS5_VABI res_getArrayItem_nid_postfix();
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
#include "prx/libSceIcu/libSceIcu.hpp"
|
||||
#include "prx/libSceIcu/Status.hpp"
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
|
||||
namespace {
|
||||
|
||||
bool Continuation(std::uint8_t byte) {
|
||||
return byte >= 0x80 && byte <= 0xbf;
|
||||
}
|
||||
|
||||
bool ThreeByteSecond(std::uint8_t lead, std::uint8_t byte) {
|
||||
if (lead == 0xe0) return byte >= 0xa0 && byte <= 0xbf;
|
||||
if (lead == 0xed) return byte >= 0x80 && byte <= 0x9f;
|
||||
return Continuation(byte);
|
||||
}
|
||||
|
||||
bool FourByteSecond(std::uint8_t lead, std::uint8_t byte) {
|
||||
if (lead == 0xf0) return byte >= 0x90 && byte <= 0xbf;
|
||||
if (lead == 0xf4) return byte >= 0x80 && byte <= 0x8f;
|
||||
return lead >= 0xf1 && lead <= 0xf3 && Continuation(byte);
|
||||
}
|
||||
|
||||
std::int64_t Skipped(const std::uint8_t* source, std::int64_t lead, std::int64_t length) {
|
||||
const auto first = source[lead];
|
||||
const auto at = [&](std::int64_t offset) { return lead + offset < length ? source[lead + offset] : 0; };
|
||||
if (first >= 0xf0 && first <= 0xf4) {
|
||||
if (lead + 1 >= length || !FourByteSecond(first, source[lead + 1])) return 1;
|
||||
if (!Continuation(at(2))) return 2;
|
||||
if (!Continuation(at(3))) return 3;
|
||||
return 4;
|
||||
}
|
||||
if (first >= 0xe0 && first <= 0xef) {
|
||||
if (lead + 1 >= length || !ThreeByteSecond(first, source[lead + 1])) return 1;
|
||||
return 2;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
int Encoded(char16_t unit) {
|
||||
return unit != 0 && unit <= 0x7f ? 1 : unit <= 0x7ff ? 2 : 3;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
||||
char16_t* APS5_VABI u_strFromJavaModifiedUTF8WithSub_nid_postfix(char16_t* destination, std::int32_t capacity, std::int32_t* destinationLength,
|
||||
const char* text, std::int32_t sourceLength, std::int32_t substitution,
|
||||
std::int32_t* substitutions, std::int32_t* status) {
|
||||
if (Icu::Failure(status)) return nullptr;
|
||||
if ((!text && sourceLength != 0) || sourceLength < -1 || (!destination && capacity != 0) || capacity < 0 ||
|
||||
substitution > 0x10ffff || Icu::Surrogate(substitution)) {
|
||||
*status = Icu::IllegalArgument;
|
||||
return nullptr;
|
||||
}
|
||||
if (substitutions) *substitutions = 0;
|
||||
const auto* source = reinterpret_cast<const std::uint8_t*>(text);
|
||||
const std::int64_t length = sourceLength < 0 ? static_cast<std::int64_t>(std::strlen(text)) : sourceLength;
|
||||
std::int64_t written = 0;
|
||||
std::int64_t extra = 0;
|
||||
std::int32_t replaced = 0;
|
||||
for (std::int64_t index = 0; index < length;) {
|
||||
const auto lead = source[index];
|
||||
const bool writing = written < capacity;
|
||||
char16_t unit = lead;
|
||||
std::int64_t used = 1;
|
||||
bool valid = true;
|
||||
if (lead >= 0xc0 && lead <= 0xdf && index + 1 < length && Continuation(source[index + 1])) {
|
||||
unit = static_cast<char16_t>(((lead & 0x1f) << 6) | (source[index + 1] & 0x3f));
|
||||
used = 2;
|
||||
} else if (lead >= 0xe0 && lead <= 0xef && index + 2 < length && Continuation(source[index + 1]) && Continuation(source[index + 2])) {
|
||||
unit = static_cast<char16_t>(((lead & 0x0f) << 12) | ((source[index + 1] & 0x3f) << 6) | (source[index + 2] & 0x3f));
|
||||
used = 3;
|
||||
} else if (lead >= 0x80) {
|
||||
valid = false;
|
||||
}
|
||||
if (valid) {
|
||||
index += used;
|
||||
if (writing) destination[written++] = unit;
|
||||
else ++extra;
|
||||
continue;
|
||||
}
|
||||
if (substitution < 0) {
|
||||
*status = Icu::InvalidCharacter;
|
||||
return nullptr;
|
||||
}
|
||||
index += Skipped(source, index, length);
|
||||
++replaced;
|
||||
if (!writing) {
|
||||
++extra;
|
||||
} else if (substitution <= 0xffff) {
|
||||
destination[written++] = static_cast<char16_t>(substitution);
|
||||
} else {
|
||||
destination[written++] = static_cast<char16_t>(0xd7c0 + (substitution >> 10));
|
||||
if (written < capacity) destination[written++] = static_cast<char16_t>(0xdc00 + (substitution & 0x3ff));
|
||||
else ++extra;
|
||||
}
|
||||
}
|
||||
const auto required = Icu::Checked(written + extra);
|
||||
if (substitutions) *substitutions = replaced;
|
||||
if (destinationLength) *destinationLength = required;
|
||||
Icu::Terminate(destination, capacity, required, status);
|
||||
return destination;
|
||||
}
|
||||
|
||||
|
||||
char* APS5_VABI u_strToJavaModifiedUTF8_nid_postfix(char* destination, std::int32_t capacity, std::int32_t* destinationLength,
|
||||
const char16_t* source, std::int32_t sourceLength, std::int32_t* status) {
|
||||
if (Icu::Failure(status)) return nullptr;
|
||||
if ((!source && sourceLength != 0) || sourceLength < -1 || (!destination && capacity != 0) || capacity < 0) {
|
||||
*status = Icu::IllegalArgument;
|
||||
return nullptr;
|
||||
}
|
||||
std::int64_t length = sourceLength;
|
||||
if (length < 0) for (length = 0; source[length] != 0; ++length) {}
|
||||
std::int64_t written = 0;
|
||||
std::int64_t extra = 0;
|
||||
bool full = false;
|
||||
for (std::int64_t index = 0; index < length; ++index) {
|
||||
const auto unit = source[index];
|
||||
const auto size = Encoded(unit);
|
||||
if (!full && capacity - written >= size) {
|
||||
if (size == 1) {
|
||||
destination[written++] = static_cast<char>(unit);
|
||||
} else if (size == 2) {
|
||||
destination[written++] = static_cast<char>(0xc0 | (unit >> 6));
|
||||
destination[written++] = static_cast<char>(0x80 | (unit & 0x3f));
|
||||
} else {
|
||||
destination[written++] = static_cast<char>(0xe0 | (unit >> 12));
|
||||
destination[written++] = static_cast<char>(0x80 | ((unit >> 6) & 0x3f));
|
||||
destination[written++] = static_cast<char>(0x80 | (unit & 0x3f));
|
||||
}
|
||||
} else {
|
||||
full = true;
|
||||
extra += size;
|
||||
}
|
||||
}
|
||||
const auto required = Icu::Checked(written + extra);
|
||||
if (destinationLength) *destinationLength = required;
|
||||
Icu::Terminate(destination, capacity, required, status);
|
||||
return destination;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
#include "prx/libc/include/general/VabiMacros.hpp"
|
||||
#include <cstdint>
|
||||
|
||||
extern "C" {
|
||||
char16_t* APS5_VABI u_strFromJavaModifiedUTF8WithSub_nid_postfix(char16_t*, std::int32_t, std::int32_t*, const char*, std::int32_t, std::int32_t, std::int32_t*, std::int32_t*);
|
||||
char* APS5_VABI u_strToJavaModifiedUTF8_nid_postfix(char*, std::int32_t, std::int32_t*, const char16_t*, std::int32_t, std::int32_t*);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
#include "prx/libSceIcu/Identified.hpp"
|
||||
#include "prx/libc/include/General.hpp"
|
||||
#include <cstring>
|
||||
|
||||
extern "C" const char* APS5_VABI locale_getKeywordsStart_nid_postfix(const char* localeID) {
|
||||
if (!localeID) APS5_INVALID_ARG_EX;
|
||||
return std::strchr(localeID, '@');
|
||||
}
|
||||
Loaded 100 of 470 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user