Merge branch 'main'

This commit is contained in:
Dean Galvin committed 2026-11-06 16:15:40 +13:00
commit 820ee08337
470 files changed
+26878 -3090

No files matched your search

+17 -8
View File
@@ -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';
+3
View File
@@ -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
Vendored Submodule
+1
Submodule 3rdparty/sqlite added at 57520227c9.
+8 -2
View File
@@ -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()
+2
View File
@@ -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()
+68
View File
@@ -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
View File
@@ -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();
@@ -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()
@@ -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);
+2 -2
View File
@@ -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);
+1 -1
View File
@@ -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);
+17 -7
View File
@@ -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);
+7 -1
View File
@@ -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;
}
+1 -1
View File
@@ -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;
+22 -15
View File
@@ -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);
+21 -11
View File
@@ -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) {
+3 -3
View File
@@ -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);
+6 -1
View File
@@ -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;
}
+5 -3
View File
@@ -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()
+105
View File
@@ -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;
}
}
+2 -2
View File
@@ -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;
}
+17 -7
View File
@@ -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();
}
+8 -4
View File
@@ -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() {
+7 -1
View File
@@ -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;
}
+56 -1
View File
@@ -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)
+15
View File
@@ -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)
+32 -32
View File
@@ -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);
}
+15 -4
View File
@@ -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;
}
+12 -2
View File
@@ -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()
+42 -13
View File
@@ -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;
+7 -1
View File
@@ -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);
+21 -11
View File
@@ -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) {
+40 -4
View File
@@ -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()),
+1
View File
@@ -42,6 +42,7 @@ struct Settings {
void* authInfoArgument = nullptr;
void* preSendCallback = nullptr;
void* preSendArgument = nullptr;
int cookieBox = 0;
};
struct Capacity {
+1 -1
View File
@@ -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);
+106 -1
View File
@@ -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()
+27
View File
@@ -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;
}
}
+9
View File
@@ -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*);
}
+58
View File
@@ -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;
}
}
+7
View File
@@ -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);
}
+88
View File
@@ -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();
}
}
+21
View File
@@ -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();
}
-104
View File
@@ -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__);
+1 -1
View File
@@ -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();
+145
View File
@@ -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;
}
}
+8
View File
@@ -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*);
}
+8
View File
@@ -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