Merge branch 'main' of https://github.com/boykopovar/AnyPS5 into feat/gamelivestreaming-missing-exports

# Conflicts:
#	core/libs/prx/libSceGameLiveStreaming/Export.cpp
This commit is contained in:
Yuri Olive committed 2026-10-20 14:23:27 -03:00
commit 43e7a4792e
307 files changed
+10503 -977

No files matched your search

+11
View File
@@ -123,6 +123,10 @@ if(BUILD_TESTING)
core/shader/recompiler/IntermediateRepresentation/include)
add_test(NAME shader_ir_use_rewrite COMMAND shader_ir_use_rewrite_tests)
set_tests_properties(shader_ir_use_rewrite PROPERTIES TIMEOUT 20)
add_test_executable(pad_command_file_tests core/libs/prx/libScePad/tests/InputCommandFile.cpp)
target_include_directories(pad_command_file_tests PRIVATE core/libs)
add_test(NAME pad_command_file COMMAND pad_command_file_tests)
set_tests_properties(pad_command_file PROPERTIES TIMEOUT 20)
if(Python3_Interpreter_FOUND)
add_test(NAME shader_cache_native_paths COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/core/shader/recompiler/tests/test_shader_cache_native_paths.py
@@ -170,6 +174,8 @@ if(BUILD_TESTING)
${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_check_conventions_wrapped_declarations.py)
add_test(NAME conventions_ci_gate COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_check_conventions_ci_gate.py)
add_test(NAME conventions_git_errors COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_check_conventions_git.py)
add_test(NAME conventions_doc_links COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_check_conventions_doc_links.py)
add_test(NAME conventions_unknown_nids COMMAND ${Python3_EXECUTABLE}
@@ -178,6 +184,8 @@ if(BUILD_TESTING)
${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_check_conventions_debt_length.py)
add_test(NAME test_registration COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_test_registration.py)
add_test(NAME test_script_registration COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/tools/check_test_registration.py --scripts)
add_test(NAME build_eligibility COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_build_eligibility.py)
add_test(NAME check_skips COMMAND ${Python3_EXECUTABLE}
@@ -226,6 +234,9 @@ if(BUILD_TESTING)
add_test(NAME pad_input COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_pad_input.py)
set_tests_properties(pad_input PROPERTIES TIMEOUT 20)
add_test(NAME pad_timeline COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_pad_timeline.py)
set_tests_properties(pad_timeline PROPERTIES TIMEOUT 20)
add_test(NAME trace_digest COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_trace_digest.py)
add_test(NAME conventions_bom_shebang COMMAND ${Python3_EXECUTABLE}
+16
View File
@@ -82,6 +82,9 @@ add_library(libc SHARED EXCLUDE_FROM_ALL)
set(LIBS_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR})
add_subdirectory(${LIBC_DIR} ${CMAKE_CURRENT_BINARY_DIR}/prx/libc)
target_include_directories(libc PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
if(WIN32)
target_compile_options(libc PRIVATE "SHELL:-include ${CMAKE_CURRENT_SOURCE_DIR}/prx/libc/include/HostStandardStreams.hpp")
endif()
configure_windows_unwind(libc)
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
target_link_options(libc PRIVATE LINKER:-z,now)
@@ -139,6 +142,9 @@ foreach(LIB_DIR ${LIBS_DIRS})
configure_windows_unwind(${LIB_NAME})
endif()
if(WIN32)
target_compile_options(${LIB_NAME} PRIVATE "SHELL:-include ${CMAKE_CURRENT_SOURCE_DIR}/prx/libc/include/HostStandardStreams.hpp")
endif()
if(LIB_NAME STREQUAL "libkernel")
target_link_libraries(${LIB_NAME} PUBLIC libc)
else()
@@ -478,6 +484,16 @@ if(BUILD_TESTING)
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceRudp>")
endif()
add_test_executable(guest_rudp_poll_tests tests/GuestRudpPoll.cpp)
target_include_directories(guest_rudp_poll_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(guest_rudp_poll_tests PRIVATE libSceRudp libc)
configure_windows_unwind(guest_rudp_poll_tests)
add_test(NAME guest_rudp_poll COMMAND guest_rudp_poll_tests)
if(WIN32)
set_tests_properties(guest_rudp_poll PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceRudp>")
endif()
add_test_executable(guest_memory_pool_stats_tests tests/GuestMemoryPoolStats.cpp)
target_include_directories(guest_memory_pool_stats_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(guest_memory_pool_stats_tests PRIVATE libSceSsl libSceHttp2 libSceNet libkernel libc)
+2 -2
View File
@@ -1155,8 +1155,8 @@ struct ImeKeycode {
std::uint64_t timestamp;
};
using ImeTextFilter = std::int32_t (*)(char16_t* out_text, std::uint32_t* out_text_length, const char16_t* source_text, std::uint32_t source_text_length);
using ImeExtKeyboardFilter = int (*)(const ImeKeycode* source_keycode, std::uint16_t* out_keycode, std::uint32_t* out_status, void* reserved);
using ImeTextFilter = std::int32_t (APS5_VABI *)(char16_t* out_text, std::uint32_t* out_text_length, const char16_t* source_text, std::uint32_t source_text_length);
using ImeExtKeyboardFilter = int (APS5_VABI *)(const ImeKeycode* source_keycode, std::uint16_t* out_keycode, std::uint32_t* out_status, void* reserved);
struct ImeExtendedParam {
std::uint32_t option;
@@ -1,7 +1,9 @@
#include "prx/libSceAgc/Command/include/Packet.hpp"
#include <algorithm>
#include <cstddef>
#include <cstdio>
#include <cstring>
#include <limits>
#include <stdexcept>
#include <string>
@@ -96,8 +98,8 @@ std::uint32_t* WriteRegisterRange(CommandBuffer* buffer, std::uint32_t opcode, s
Require(count != 0 && count <= 0x3fffu && offset <= 0xffffu && count <= 0x10000u - offset, function, "invalid register range");
std::vector<std::uint32_t> snapshot;
if (values != nullptr) {
CheckAddress(reinterpret_cast<std::uintptr_t>(values), 4, function);
snapshot.assign(values, values + count);
snapshot.resize(count);
std::memcpy(snapshot.data(), values, static_cast<std::size_t>(count) * sizeof(std::uint32_t));
}
auto* packet = Allocate(buffer, count + 2u, function);
packet[0] = Header(opcode, count + 2u);
+8 -2
View File
@@ -474,8 +474,14 @@ void testRegisterRange() {
check(packet == storage.words.data() + 6 && storage.buffer.cursor_up == storage.words.data() + 12, "incorrect populated register range allocation");
check(storage.words == expected, "register values were not copied correctly");
const auto* misaligned = reinterpret_cast<const std::uint32_t*>(reinterpret_cast<const unsigned char*>(values.data()) + 1);
expectFailure([&] { sceAgcCbSetShRegisterRangeDirect(&storage.buffer, 0x8c, misaligned, 4); });
check(storage.words == expected && storage.buffer.cursor_up == storage.words.data() + 12, "misaligned register values modified command buffer");
packet = sceAgcCbSetShRegisterRangeDirect(&storage.buffer, 0x8c, misaligned, 3);
expected[12] = 0xc0037600u;
expected[13] = 0x8c;
expected[14] = 0x02000000u;
expected[15] = 0x03000000u;
expected[16] = 0x04000000u;
check(packet == storage.words.data() + 12 && storage.buffer.cursor_up == storage.words.data() + 17, "incorrect misaligned register range allocation");
check(storage.words == expected, "misaligned register values were not read byte for byte");
}
void testPacketPayloadAddress() {
@@ -8,6 +8,8 @@
extern "C" {
std::uint32_t* APS5_VABI sceAgcDcbSetCfRegisterDirect(CommandBuffer*, ShaderRegister);
std::uint32_t* APS5_VABI sceAgcDcbSetCfRegisterRangeDirect(CommandBuffer*, std::uint32_t, const std::uint32_t*, std::uint32_t);
std::uint32_t* APS5_VABI sceAgcCbSetShRegisterRangeDirect(CommandBuffer*, std::uint32_t, const std::uint32_t*, std::uint32_t);
std::uint32_t* APS5_VABI sceAgcCbSetUcRegisterRangeDirect(CommandBuffer*, std::uint32_t, const std::uint32_t*, std::uint32_t);
std::uint32_t* APS5_VABI sceAgcDcbSetCxRegisterDirect(CommandBuffer*, ShaderRegister);
std::uint32_t* APS5_VABI sceAgcDcbSetShRegisterDirect(CommandBuffer*, ShaderRegister);
std::uint32_t* APS5_VABI sceAgcDcbSetUcRegisterDirect(CommandBuffer*, ShaderRegister);
@@ -92,11 +94,37 @@ void testConfig() {
expectFailure([&] { sceAgcDcbSetCfRegisterDirect(nullptr, {0, 1}); });
expectFailure([&] { sceAgcDcbSetCfRegisterRangeDirect(&storage.buffer, 0x100u, values.data(), 0); });
expectFailure([&] { sceAgcDcbSetCfRegisterRangeDirect(&storage.buffer, 0xffffu, values.data(), 2); });
const auto* misaligned = reinterpret_cast<const std::uint32_t*>(reinterpret_cast<const unsigned char*>(values.data()) + 1);
expectFailure([&] { sceAgcDcbSetCfRegisterRangeDirect(&storage.buffer, 0x100u, misaligned, 1); });
check(storage.words == before && storage.buffer.cursor_up == reserved + 4, "invalid config register write modified buffer");
}
void testRangeFromAnyByte() {
const std::array writers{sceAgcCbSetShRegisterRangeDirect, sceAgcCbSetUcRegisterRangeDirect, sceAgcDcbSetCfRegisterRangeDirect};
const std::array headers{0xc0047600u, 0xc0047900u, 0xc0046800u};
alignas(16) std::array<unsigned char, 32> bytes{};
for (std::size_t i = 0; i < bytes.size(); ++i) bytes[i] = static_cast<unsigned char>(0x10u + i);
struct Case {
std::size_t offset;
std::array<std::uint32_t, 4> values;
};
const std::array cases{
Case{1, {0x14131211u, 0x18171615u, 0x1c1b1a19u, 0x201f1e1du}},
Case{2, {0x15141312u, 0x19181716u, 0x1d1c1b1au, 0x21201f1eu}},
Case{3, {0x16151413u, 0x1a191817u, 0x1e1d1c1bu, 0x2221201fu}},
Case{13, {0x201f1e1du, 0x24232221u, 0x28272625u, 0x2c2b2a29u}},
};
for (std::size_t i = 0; i < writers.size(); ++i) {
for (const auto& entry : cases) {
Storage storage;
storage.words.fill(0xabcdef01u);
const auto* values = reinterpret_cast<const std::uint32_t*>(bytes.data() + entry.offset);
auto* packet = writers[i](&storage.buffer, 0x40u, values, 4);
check(packet == storage.words.data() && storage.buffer.cursor_up == packet + 6, "unaligned register range allocation mismatch");
check(packet[0] == headers[i] && packet[1] == 0x40u && std::equal(entry.values.begin(), entry.values.end(), packet + 2), "unaligned register values were not read byte for byte");
check(packet[6] == 0xabcdef01u, "unaligned register range overwrote the following word");
}
}
}
void testIndirect() {
const std::array writers{sceAgcDcbSetCxRegistersIndirect, sceAgcDcbSetShRegistersIndirect, sceAgcDcbSetUcRegistersIndirect};
const std::array sizes{sceAgcDcbSetCxRegistersIndirectGetSize, sceAgcDcbSetShRegistersIndirectGetSize, sceAgcDcbSetUcRegistersIndirectGetSize};
@@ -210,6 +238,7 @@ int main() {
try {
testDirect();
testConfig();
testRangeFromAnyByte();
testIndirect();
testIndirectPlaceholder();
testDirectList();
+52 -5
View File
@@ -648,7 +648,9 @@ if(BUILD_TESTING)
if(WIN32)
add_test(NAME agc_write_tracking_metadata_commit COMMAND agc_write_tracking_tests --metadata-commit)
set_tests_properties(agc_write_tracking_metadata_commit PROPERTIES SKIP_RETURN_CODE 77 TIMEOUT 30)
set_tests_properties(agc_write_tracking agc_write_tracking_metadata_commit PROPERTIES ENVIRONMENT_MODIFICATION
add_test(NAME agc_resident_direct_remap COMMAND agc_write_tracking_tests --resident-direct-remap)
set_tests_properties(agc_resident_direct_remap PROPERTIES SKIP_RETURN_CODE 77 TIMEOUT 20)
set_tests_properties(agc_write_tracking agc_write_tracking_metadata_commit agc_resident_direct_remap PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
endif()
@@ -807,6 +809,19 @@ if(BUILD_TESTING)
endif()
endif()
add_test_executable(agc_spirv_failure_text_tests EXCLUDE_FROM_ALL tests/SpirvFailureText.cpp)
target_link_libraries(agc_spirv_failure_text_tests PRIVATE shader_recompiler_agc_driver libc)
set_target_properties(agc_spirv_failure_text_tests PROPERTIES CXX_EXTENSIONS OFF)
configure_windows_unwind(agc_spirv_failure_text_tests)
if(BUILD_TESTING)
set_target_properties(agc_spirv_failure_text_tests PROPERTIES EXCLUDE_FROM_ALL OFF)
add_test(NAME agc_spirv_failure_text COMMAND agc_spirv_failure_text_tests)
set_tests_properties(agc_spirv_failure_text PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1")
if(WIN32)
set_tests_properties(agc_spirv_failure_text PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
endif()
add_test_executable(agc_spirv_lds_atomic_lock_tests EXCLUDE_FROM_ALL tests/SpirvLdsAtomicLock.cpp)
target_link_libraries(agc_spirv_lds_atomic_lock_tests PRIVATE shader_recompiler_agc_driver libc)
set_target_properties(agc_spirv_lds_atomic_lock_tests PROPERTIES CXX_EXTENSIONS OFF)
@@ -820,6 +835,19 @@ if(BUILD_TESTING)
endif()
endif()
add_test_executable(agc_spirv_subvector_loop_begin_tests EXCLUDE_FROM_ALL tests/SpirvSubvectorLoopBegin.cpp)
target_link_libraries(agc_spirv_subvector_loop_begin_tests PRIVATE shader_recompiler_agc_driver libc)
set_target_properties(agc_spirv_subvector_loop_begin_tests PROPERTIES CXX_EXTENSIONS OFF)
configure_windows_unwind(agc_spirv_subvector_loop_begin_tests)
if(BUILD_TESTING)
set_target_properties(agc_spirv_subvector_loop_begin_tests PROPERTIES EXCLUDE_FROM_ALL OFF)
add_test(NAME agc_spirv_subvector_loop_begin COMMAND agc_spirv_subvector_loop_begin_tests)
set_tests_properties(agc_spirv_subvector_loop_begin PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1")
if(WIN32)
set_tests_properties(agc_spirv_subvector_loop_begin PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
endif()
add_test_executable(agc_layer_input_tests EXCLUDE_FROM_ALL tests/LayerInput.cpp)
target_link_libraries(agc_layer_input_tests PRIVATE shader_recompiler_agc_driver libc)
set_target_properties(agc_layer_input_tests PROPERTIES CXX_EXTENSIONS OFF)
@@ -1857,10 +1885,11 @@ if(BUILD_TESTING)
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
add_test(NAME agc_driver_undithered_alpha_to_coverage_state COMMAND agc_driver_graphics_tests --undithered-alpha-to-coverage-state)
add_test(NAME agc_driver_depth_bounds_state COMMAND agc_driver_graphics_tests --depth-bounds-state)
add_test(NAME agc_driver_msaa_dcc_state COMMAND agc_driver_graphics_tests --multisample-dcc-state)
add_test(NAME agc_driver_unused_viewport_depth COMMAND agc_driver_graphics_tests --unused-viewport-depth)
set_tests_properties(agc_driver_unused_viewport_depth PROPERTIES TIMEOUT 30)
set_tests_properties(agc_driver_undithered_alpha_to_coverage_state PROPERTIES
set_tests_properties(agc_driver_undithered_alpha_to_coverage_state agc_driver_depth_bounds_state PROPERTIES
ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" TIMEOUT 30 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}")
add_test(NAME agc_driver_alpha_to_coverage_state COMMAND agc_driver_graphics_tests --alpha-to-coverage-state)
set_tests_properties(agc_driver_alpha_to_coverage_state PROPERTIES
@@ -1878,7 +1907,7 @@ if(BUILD_TESTING)
set_tests_properties(agc_driver_dithered_alpha_to_coverage PROPERTIES
ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" SKIP_RETURN_CODE 77 TIMEOUT 30 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}")
if(WIN32)
set_tests_properties(agc_driver_alpha_to_coverage_state agc_driver_undithered_alpha_to_coverage_state agc_driver_msaa_dcc_state agc_driver_unused_viewport_depth PROPERTIES ENVIRONMENT_MODIFICATION
set_tests_properties(agc_driver_alpha_to_coverage_state agc_driver_undithered_alpha_to_coverage_state agc_driver_depth_bounds_state agc_driver_msaa_dcc_state agc_driver_unused_viewport_depth PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
set_tests_properties(agc_driver_dithered_alpha_to_coverage ${multisampledDccClearTests} PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
@@ -2101,9 +2130,17 @@ if(BUILD_TESTING)
if(WIN32)
set_tests_properties(agc_shader_disk_cache_large_file PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
add_test(NAME agc_shader_disk_cache_long_paths COMMAND agc_shader_disk_cache_tests --long-host-paths)
add_test(NAME agc_shader_disk_cache_host_stderr COMMAND agc_shader_disk_cache_tests --host-stderr)
set_tests_properties(agc_shader_disk_cache_host_stderr PROPERTIES TIMEOUT 30 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
set_tests_properties(agc_shader_disk_cache_long_paths PROPERTIES TIMEOUT 30 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
add_test(NAME agc_driver_large_tracked_snapshot COMMAND agc_driver_partially_resident_snapshot_tests --large-tracked)
set_tests_properties(agc_driver_large_tracked_snapshot PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" SKIP_RETURN_CODE 77 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" TIMEOUT 30)
if(WIN32)
set_tests_properties(agc_driver_large_tracked_snapshot PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
if(WIN32)
add_test(NAME agc_driver_untracked_snapshot_hint COMMAND agc_driver_partially_resident_snapshot_tests --untracked-hint)
set_tests_properties(agc_driver_untracked_snapshot_hint PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" SKIP_RETURN_CODE 77 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" TIMEOUT 30
@@ -2350,12 +2387,22 @@ if(BUILD_TESTING)
set_tests_properties(agc_driver_sampled_cache_budget_memory PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" SKIP_RETURN_CODE 77)
add_test(NAME agc_driver_sampled_cache_budget_without_storage_image_multisample COMMAND agc_driver_sampled_cache_budget_tests without-storage-image-multisample)
set_tests_properties(agc_driver_sampled_cache_budget_without_storage_image_multisample PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" SKIP_RETURN_CODE 77 TIMEOUT 30)
add_test(NAME agc_sampled_cache_device_selection COMMAND agc_driver_sampled_cache_budget_tests device-selection-cpu)
set_tests_properties(agc_sampled_cache_device_selection PROPERTIES SKIP_RETURN_CODE 77 TIMEOUT 30)
add_test(NAME agc_driver_sampled_cache_device_selection COMMAND agc_driver_sampled_cache_budget_tests device-selection)
set_tests_properties(agc_driver_sampled_cache_device_selection PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" SKIP_RETURN_CODE 77 TIMEOUT 30)
if(WIN32)
set_tests_properties(agc_sampled_cache_device_selection agc_driver_sampled_cache_device_selection PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
add_test(NAME agc_driver_resource_cache_storage_admission COMMAND agc_driver_sampled_cache_budget_tests resource-admission)
add_test(NAME agc_driver_resource_cache_oversized_storage_admission COMMAND agc_driver_sampled_cache_budget_tests resource-oversized)
add_test(NAME agc_driver_resource_cache_storage_clear COMMAND agc_driver_sampled_cache_budget_tests resource-clear)
set_tests_properties(agc_driver_resource_cache_storage_admission agc_driver_resource_cache_oversized_storage_admission agc_driver_resource_cache_storage_clear PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" SKIP_RETURN_CODE 77 TIMEOUT 30)
add_test(NAME agc_driver_resource_cache_replaced_texture COMMAND agc_driver_sampled_cache_budget_tests resource-replaced)
add_test(NAME agc_driver_resource_cache_lookup_changed COMMAND agc_driver_sampled_cache_budget_tests resource-lookup-changed)
set_tests_properties(agc_driver_resource_cache_storage_admission agc_driver_resource_cache_oversized_storage_admission agc_driver_resource_cache_storage_clear agc_driver_resource_cache_replaced_texture agc_driver_resource_cache_lookup_changed PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" SKIP_RETURN_CODE 77 TIMEOUT 30)
if(WIN32)
set_tests_properties(agc_driver_sampled_cache_budget_memory agc_driver_sampled_cache_budget_without_storage_image_multisample agc_driver_resource_cache_storage_admission agc_driver_resource_cache_oversized_storage_admission agc_driver_resource_cache_storage_clear PROPERTIES ENVIRONMENT_MODIFICATION
set_tests_properties(agc_driver_sampled_cache_budget_memory agc_driver_sampled_cache_budget_without_storage_image_multisample agc_driver_resource_cache_storage_admission agc_driver_resource_cache_oversized_storage_admission agc_driver_resource_cache_storage_clear agc_driver_resource_cache_replaced_texture agc_driver_resource_cache_lookup_changed PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
# The driver draw test again with the draw key off: the draw cache then keys on the decoded vertex
@@ -11,6 +11,7 @@
#include <cstdint>
#include <list>
#include <memory>
#include <utility>
#include <vector>
#include "prx/libc/include/general/AtomicSharedPtr.hpp"
@@ -45,6 +46,9 @@ void ReadUserWords(const QueueState& queue, DrawProgram& program, bool staticAbi
void InitializeMerged(const QueueState& queue, DrawProgram& program, std::uint32_t pointerBase, bool pointerRequired, bool staticAbi);
void DecodeGraphicsPrograms(DrawDecode& decoded, const QueueState& queue, const ShaderRegistry& registry, bool staticAbi, bool includeFragment);
void AppendProgramMemory(std::vector<ShaderRecompiler::MemoryRegion>& memory, const std::vector<DrawProgram>& programs);
std::vector<std::pair<std::size_t, std::size_t>> UserDataWords(const std::vector<DrawProgram>& programs, const std::vector<ShaderRecompiler::ProgramRole>& roles, std::uint32_t location);
struct PreparedGraphicsStage {
std::shared_ptr<const ShaderSnapshot> snapshot;
PreparedShaders::Entry entry;
@@ -89,7 +89,8 @@ void Validate(std::span<const std::uint32_t> packet, std::uint32_t queue);
void Execute(std::span<const std::uint32_t> packet, QueueState& queue);
inline bool IndirectRegisterOpcode(std::uint32_t opcode) { return opcode == 0x63 || opcode == 0x64 || opcode == 0x9f; }
std::vector<std::uint32_t> ReadIndirectRegisters(std::span<const std::uint32_t> packet);
void ExecuteIndirectRegisters(std::span<const std::uint32_t> packet, std::span<const std::uint32_t> pairs, QueueState& queue);
bool IndirectRegistersValid(std::span<const std::uint32_t> pairs);
void ExecuteIndirectRegisters(std::span<const std::uint32_t> packet, std::span<const std::uint32_t> pairs, QueueState& queue, bool skipInvalid = false);
bool AccessesMemory(std::uint32_t header);
// Whether an ACQUIRE_MEM packet asks only for GPU cache actions (no CPU-visible memory
// synchronization): such a packet needs a pipeline barrier, not a device drain.
@@ -103,6 +103,7 @@ public:
// Pending-label table lookup and open-batch overlap test for WAIT_REG_MEM (see Recorder).
std::optional<Graphics::Recorder::LabelHit> PendingLabel(std::uint64_t address, std::size_t bytes, std::uint64_t afterStamp, Graphics::Recorder::LabelRefusal* refusal = nullptr) const;
bool OpenWriteOverlaps(std::uint64_t address, std::size_t bytes) const;
void SealPassWriteOnly();
bool RecordedWritesSettled(std::uint64_t address, std::size_t bytes) const;
bool StoresPendingOver(std::uint64_t address, std::size_t bytes) const;
// Fills [address, address + bytes) of host-imported guest memory with a repeating 16-byte pattern,
@@ -315,6 +316,7 @@ public:
// `recipe`, when given, receives the DrawRecipe a recorded, cacheable, reusable, direct draw
// built for its draw-cache entry (design_cpu_final M8); null otherwise.
void Draw(const Graphics::State& graphics, const Pm4::DrawParameters& draw, std::span<const Graphics::CompiledShader> shaders, std::span<const Graphics::GuestMemorySnapshot> snapshots = {}, std::shared_ptr<const DrawRecipe>* recipe = nullptr);
bool DrawInputsInPlace() const;
std::optional<std::string> KnownDrawRejection(const Graphics::State& graphics, std::span<const Graphics::CompiledShader> shaders) const;
void ColorMetadataPass(const Graphics::ColorMetadataPass& pass);
// A draw-cache hit recorded from its recipe (Graphics::DrawWithRecipe), under the mutex after
@@ -216,13 +216,9 @@ DrawVerdict Driver::draw(QueueState& queue, std::span<const std::uint32_t> packe
phaseTiming.Phase(DrawRowDecode);
const auto locate = [&](std::uint32_t location) -> std::optional<std::pair<std::size_t, std::size_t>> {
if (location == 0x280u) return std::nullopt;
for (std::size_t i = 0; i < programs.size(); ++i) {
if (roles[i] == Role::Fragment || roles[i] == Role::GeometryBack || location < programs[i].userDataBase) continue;
const auto word = location - programs[i].userDataBase + (8u - programs[i].firstUserSgpr);
if (word < programs[i].userData.size()) return std::make_pair(i, static_cast<std::size_t>(word));
}
return std::nullopt;
const auto words = UserDataWords(programs, roles, location);
if (words.empty()) return std::nullopt;
return words.front();
};
if (drawParameters.indirect) {
auto& indirect = *drawParameters.indirect;
@@ -237,13 +233,11 @@ DrawVerdict Driver::draw(QueueState& queue, std::span<const std::uint32_t> packe
}
std::vector<ShaderRecompiler::MemoryRegion> memory;
memory.reserve(2 * programs.size() + 32);
AppendProgramMemory(memory, programs);
std::vector<ShaderRecompiler::LinkedProgram> linked;
linked.reserve(programs.size());
for (std::size_t i = 0; i < programs.size(); ++i) {
const auto& program = programs[i];
for (const auto& region : program.memory) {
if (!region.bytes.empty()) memory.push_back(region);
}
linked.push_back({roles[i], program.binary, program.userDataBase, program.firstUserSgpr, program.userData});
}
timing.Mark("prepare");
@@ -544,9 +538,9 @@ DrawVerdict Driver::draw(QueueState& queue, std::span<const std::uint32_t> packe
std::vector<Pm4::DrawArguments> records;
for (std::uint32_t record = 0; record < count; ++record) records.push_back(Pm4::ReadDrawArguments(indirect, record));
Graphics::CountIndirectDraw(*indirectCpu, std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - readStart).count());
const auto baseVertexWord = locate(indirect.baseVertexLocation);
const auto startInstanceWord = locate(indirect.startInstanceLocation);
const auto drawIndexWord = indirect.drawIndexEnabled ? locate(indirect.drawIndexLocation) : std::nullopt;
const auto baseVertexWords = UserDataWords(programs, roles, indirect.baseVertexLocation);
const auto startInstanceWords = UserDataWords(programs, roles, indirect.startInstanceLocation);
const auto drawIndexWords = indirect.drawIndexEnabled ? UserDataWords(programs, roles, indirect.drawIndexLocation) : std::vector<std::pair<std::size_t, std::size_t>>{};
const auto drawable = [&](const Pm4::DrawArguments& arguments) { return arguments.count != 0 && arguments.instances != 0 && (!drawParameters.indexed || arguments.firstVertexOrIndex < drawParameters.indexCount); };
std::size_t lastDrawable = records.size();
for (std::size_t record = 0; record < records.size(); ++record) {
@@ -557,14 +551,15 @@ DrawVerdict Driver::draw(QueueState& queue, std::span<const std::uint32_t> packe
if (traceIndirect) std::fprintf(stderr, "[draw] record %u: count %u instances %u first %u vertexOffset %u startInstance %u\n", record, arguments.count, arguments.instances, arguments.firstVertexOrIndex, arguments.vertexOffset, arguments.firstInstance);
if (arguments.count == 0 || arguments.instances == 0) continue;
std::set<std::size_t> patched;
const auto patch = [&](const std::optional<std::pair<std::size_t, std::size_t>>& word, std::uint32_t value) {
if (!word) return;
writablePrograms[word->first].userData[word->second] = value;
patched.insert(word->first);
const auto patch = [&](const std::vector<std::pair<std::size_t, std::size_t>>& words, std::uint32_t value) {
for (const auto& [program, word] : words) {
writablePrograms[program].userData[word] = value;
patched.insert(program);
}
};
patch(baseVertexWord, indirect.recordBytes == 20 ? arguments.vertexOffset : arguments.firstVertexOrIndex);
patch(startInstanceWord, arguments.firstInstance);
patch(drawIndexWord, record);
patch(baseVertexWords, indirect.recordBytes == 20 ? arguments.vertexOffset : arguments.firstVertexOrIndex);
patch(startInstanceWords, arguments.firstInstance);
patch(drawIndexWords, record);
if (graphics.stages.tessellation) {
for (std::size_t i = 0; i < programs.size(); ++i) {
if (programs[i].binary.stage == Stage::TessellationControl || programs[i].binary.stage == Stage::TessellationEvaluation) patched.insert(i);
@@ -46,11 +46,34 @@ void InitializeMerged(const QueueState& queue, DrawProgram& program, std::uint32
const auto address = static_cast<std::uint64_t>(low) | (static_cast<std::uint64_t>(high) << 32u);
require(address != 0 || !pointerRequired, "merged shader user-data address is null");
if (address == 0) return;
GuestMemory::CheckRange(reinterpret_cast<const void*>(address), 8, 4);
if (pointerRequired) GuestMemory::CheckRange(reinterpret_cast<const void*>(address), 8, 4);
program.userData[0] = low;
program.userData[1] = high;
}
void AppendProgramMemory(std::vector<ShaderRecompiler::MemoryRegion>& memory, const std::vector<DrawProgram>& programs) {
for (const auto& program : programs) {
for (const auto& region : program.memory) {
if (region.bytes.empty()) continue;
const bool listed = std::any_of(memory.begin(), memory.end(), [&](const ShaderRecompiler::MemoryRegion& known) { return known.guestAddress == region.guestAddress && known.bytes.data() == region.bytes.data() && known.bytes.size() == region.bytes.size(); });
if (!listed) memory.push_back(region);
}
}
}
std::vector<std::pair<std::size_t, std::size_t>> UserDataWords(const std::vector<DrawProgram>& programs, const std::vector<ShaderRecompiler::ProgramRole>& roles, std::uint32_t location) {
using Role = ShaderRecompiler::ProgramRole;
require(roles.size() == programs.size(), "draw program roles are incomplete");
std::vector<std::pair<std::size_t, std::size_t>> words;
if (location == 0x280u) return words;
for (std::size_t i = 0; i < programs.size(); ++i) {
if (roles[i] == Role::Fragment || roles[i] == Role::GeometryBack || location < programs[i].userDataBase) continue;
const auto word = location - programs[i].userDataBase + (8u - programs[i].firstUserSgpr);
if (word < programs[i].userData.size()) words.emplace_back(i, static_cast<std::size_t>(word));
}
return words;
}
void DecodeGraphicsPrograms(DrawDecode& decoded, const QueueState& queue, const ShaderRegistry& registry, bool staticAbi, bool includeFragment) {
using Stage = ShaderRecompiler::ShaderStage;
using Role = ShaderRecompiler::ProgramRole;
@@ -95,11 +118,21 @@ void DecodeGraphicsPrograms(DrawDecode& decoded, const QueueState& queue, const
programs.push_back(prepare(programAddress(base), type, stage, resources, users));
roles.push_back(role);
};
const auto& graphics = product->state;
auto& graphics = product->state;
if (graphics.stages.path == Graphics::ShaderPath::Tessellation) {
if (!staticAbi || programAddress(0x148) != 0) append(0x148, 5, Stage::Local, 0x10b, 0x10c, Role::Local);
append(0x108, 7, Stage::TessellationControl, 0x10b, 0x10c, Role::Hull);
InitializeMerged(queue, programs.back(), 0x102, true, staticAbi);
const auto localAddress = programAddress(0x148);
const auto local = !staticAbi || localAddress != 0 ? ProgramSnapshot(registry, localAddress, {3, 5}) : nullptr;
const bool mergedHull = local != nullptr && !local->header.empty() && local->type == 3;
require(graphics.stages.tessellation.has_value(), "tessellation draw has no tessellation configuration");
graphics.stages.tessellation->mergedHull = mergedHull;
if (mergedHull) {
append(0x148, 3, Stage::Local, 0x10b, 0x10c, Role::Local);
append(0x148, 3, Stage::TessellationControl, 0x10b, 0x10c, Role::Hull);
} else {
if (local != nullptr) append(0x148, 5, Stage::Local, 0x10b, 0x10c, Role::Local);
append(0x108, 7, Stage::TessellationControl, 0x10b, 0x10c, Role::Hull);
}
InitializeMerged(queue, programs.back(), 0x102, !mergedHull, staticAbi);
append(0x0c8, 2, Stage::TessellationEvaluation, 0x08b, 0x08c, Role::Domain);
} else if (graphics.stages.path == Graphics::ShaderPath::Geometry) {
const auto frontAddress = programAddress(0xc8);
@@ -28,6 +28,7 @@ void Driver::lookupDraw(const Submission& submission, const std::shared_ptr<Vulk
if (graphics.stages.tessellation) {
const auto& tess = *graphics.stages.tessellation;
for (const auto value : {tess.inputControlPoints, tess.outputControlPoints, tess.domain, tess.partitioning, tess.outputTopology, tess.patchesPerGroup, tess.primitiveGroupSize, static_cast<std::uint32_t>(tess.breakAtInstance)}) mix(value);
mix(tess.mergedHull);
}
mix(graphics.rectList);
mix(programs.size());
@@ -241,7 +241,7 @@ bool Driver::sameDecode(const DrawDecode& a, const DrawDecode& b) {
const auto sameTess = [](const std::optional<ShaderRecompiler::TessellationConfiguration>& x, const std::optional<ShaderRecompiler::TessellationConfiguration>& y) {
if (x.has_value() != y.has_value()) return false;
if (!x) return true;
return x->inputControlPoints == y->inputControlPoints && x->outputControlPoints == y->outputControlPoints && x->domain == y->domain && x->partitioning == y->partitioning && x->outputTopology == y->outputTopology && x->patchesPerGroup == y->patchesPerGroup && x->primitiveGroupSize == y->primitiveGroupSize && x->breakAtInstance == y->breakAtInstance;
return x->inputControlPoints == y->inputControlPoints && x->outputControlPoints == y->outputControlPoints && x->domain == y->domain && x->partitioning == y->partitioning && x->outputTopology == y->outputTopology && x->patchesPerGroup == y->patchesPerGroup && x->primitiveGroupSize == y->primitiveGroupSize && x->breakAtInstance == y->breakAtInstance && x->mergedHull == y->mergedHull;
};
if (s.stages.path != t.stages.path || s.stages.registerValue != t.stages.registerValue || s.stages.vertexWaveSize != t.stages.vertexWaveSize || s.stages.fragmentWaveSize != t.stages.fragmentWaveSize || !sameMesh(s.stages.mesh, t.stages.mesh) || !sameTess(s.stages.tessellation, t.stages.tessellation)) return false;
if (!sameColor(s.color, t.color) || s.colors.size() != t.colors.size() || s.blends.size() != t.blends.size()) return false;
@@ -11,9 +11,12 @@
#include "prx/libSceAgcDriver/Execution/include/PerformanceTimer.hpp"
#include "prx/libSceAgcDriver/Execution/include/Pm4.hpp"
#include "prx/libSceAgcDriver/Eq/include/Event.hpp"
#include <atomic>
#include <cstdio>
#include <cstdlib>
#include <functional>
#include <shared_mutex>
#include <utility>
namespace AgcDriver::DriverDetail {
@@ -69,6 +72,20 @@ bool keepsDrawPipeline(std::span<const std::uint32_t> packet, std::uint32_t head
}
}
bool sealsWriteOnly(std::span<const std::uint32_t> packet, std::uint32_t opcode) {
if (opcode == 0x58) return true;
if (opcode != 0x46 || packet.size() < 2) return false;
switch (packet[1] & 0x3fu) {
case 0x07: case 0x0f: case 0x10: case 0x16: case 0x2a: case 0x2c: case 0x2e: case 0x31: return true;
default: return false;
}
}
bool& drawnSinceSeal() {
static thread_local bool drawn = false;
return drawn;
}
}
void Driver::deliverGuestEopInterrupt(std::uint32_t queue) {
@@ -405,6 +422,7 @@ void Driver::execute(const Submission& submission) {
} else if (drawPacket) {
landPendingWaits(submission.queue);
packetCounters.CountDraw();
drawnSinceSeal() = true;
bool drawn = false;
timed(&WorkerProfile::drawMs, [&] {
static const bool traceDraws = std::getenv("APS5_TRACE_DRAWS") != nullptr;
@@ -447,10 +465,43 @@ void Driver::execute(const Submission& submission) {
Pm4::Execute(packet, queue);
} else if (sampleDump && !wroteOnGpu) {
dumpSampleCounters(Pm4::SampleDumpAddress(packet));
} else if (submission.queue == 0 && sealsWriteOnly(packet, opcode)) {
if (std::exchange(drawnSinceSeal(), false)) {
const auto seal = [this] {
GuestMemory::TagGpuLockSite(GuestMemory::GpuLockSite::Flush);
std::lock_guard gpuLock(GuestMemory::GpuMutex());
if (const auto localDevice = device.Load()) localDevice->SealPassWriteOnly();
};
if (pipelined && DrawPipeline::Queue0().Busy()) DrawPipeline::Queue0().Enqueue(seal, {});
else seal();
}
} else if (opcode != 0x42 && opcode != 0x46 && opcode != 0x58) {
if (!wroteOnGpu) {
if (Pm4::IndirectRegisterOpcode(opcode)) Pm4::ExecuteIndirectRegisters(packet, submission.registerLists.at(cursor), queue);
else Pm4::Execute(packet, queue);
if (Pm4::IndirectRegisterOpcode(opcode)) {
const auto& listed = submission.registerLists.at(cursor);
if (Pm4::IndirectRegistersValid(listed)) {
Pm4::ExecuteIndirectRegisters(packet, listed, queue);
} else {
landPendingWaits(submission.queue);
recordQueuedLabelsBeforeRead(submission.queue);
const auto current = Pm4::ReadIndirectRegisters(packet);
static const bool skipInvalid = std::getenv("APS5_SKIP_INVALID_INDIRECT_REGISTERS") != nullptr;
static std::atomic<bool> reported{false};
if (!reported.exchange(true)) {
std::uint32_t invalid = 0;
for (std::size_t i = 0; i < listed.size(); i += 2) {
if (!Pm4::IndirectRegistersValid(std::span(&listed[i], 1))) {
invalid = listed[i];
break;
}
}
std::fprintf(stderr, "[gpu] queue 0x%x %s at DWORD %zu: register list entry with offset dword 0x%08x read again where the packet executes%s\n", submission.queue, Pm4::Name(packet[0]).c_str(), cursor, invalid, skipInvalid ? "; invalid entries are skipped (APS5_SKIP_INVALID_INDIRECT_REGISTERS)" : "");
}
Pm4::ExecuteIndirectRegisters(packet, current, queue, skipInvalid);
}
} else {
Pm4::Execute(packet, queue);
}
if (opcode == 0x49 || opcode == 0x37) {
if (const auto label = Pm4::DecodeLabelWrite(packet)) noteLabelStore(label->address, label->Bytes(), ++eventSerial, submission.queue);
}
@@ -33,6 +33,13 @@
#include <mutex>
#include <atomic>
#include <pthread.h>
#ifdef _WIN32
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <windows.h>
#include <process.h>
#endif
#ifdef __GLIBC__
#include <malloc.h>
#endif
@@ -811,7 +818,7 @@ std::optional<ShaderRecompiler::ShaderFloatMode> DecodeFloatMode(const Registers
case 0: rsrc1 = 0x212; fp16OverflowBit = 26; break;
case 1: rsrc1 = 0x00a; fp16OverflowBit = 29; break;
case 2: case 4: case 6: rsrc1 = 0x08a; fp16OverflowBit = 31; break;
case 5: case 7: rsrc1 = 0x10a; fp16OverflowBit = 30; break;
case 3: case 5: case 7: rsrc1 = 0x10a; fp16OverflowBit = 30; break;
default: return std::nullopt;
}
const auto found = registers.find(rsrc1);
@@ -909,7 +916,7 @@ std::unique_ptr<RegisteredPreparation> PlanRegistered(const ShaderSnapshot& snap
case 1: stage = Stage::Fragment; programRegister = 0x008; resourceRegister = 0x00b; break;
case 2: stage = Stage::Vertex; programRegister = 0x0c8; resourceRegister = 0x08b; firstUser = 8; break;
case 4: stage = Stage::Mesh; programRegister = 0x0c8; resourceRegister = 0x08b; break;
case 5: return {};
case 3: case 5: return {};
case 6: stage = Stage::Mesh; programRegister = 0x088; resourceRegister = 0x08b; break;
case 7: stage = Stage::TessellationControl; programRegister = 0x108; resourceRegister = 0x10b; break;
default:
@@ -1198,7 +1205,7 @@ public:
}
void Close() {
std::vector<pthread_t> joined;
std::vector<Worker> joined;
{
std::unique_lock lock(mutex);
closed = true;
@@ -1206,16 +1213,45 @@ public:
joined.swap(workers);
}
ready.notify_all();
for (const auto worker : joined) {
if (!pthread_equal(worker, pthread_self())) pthread_join(worker, nullptr);
}
for (const auto& worker : joined) Join(worker);
}
private:
static constexpr std::size_t StackBytes = 64u << 20u;
#ifdef _WIN32
struct Worker {
HANDLE handle;
unsigned id;
};
static void Join(const Worker& worker) {
if (worker.id != GetCurrentThreadId()) WaitForSingleObject(worker.handle, INFINITE);
CloseHandle(worker.handle);
}
#else
using Worker = pthread_t;
static void Join(Worker worker) {
if (!pthread_equal(worker, pthread_self())) pthread_join(worker, nullptr);
}
#endif
explicit RegistrationPreparePool(unsigned count) {
#ifdef _WIN32
for (unsigned index = 0; index < count; ++index) {
unsigned id = 0;
const auto thread = _beginthreadex(nullptr, static_cast<unsigned>(StackBytes), &RegistrationPreparePool::WindowsMain, this, STACK_SIZE_PARAM_IS_A_RESERVATION, &id);
if (thread != 0) {
workers.push_back({reinterpret_cast<HANDLE>(thread), id});
++threads;
liveWorkers.fetch_add(1, std::memory_order_acq_rel);
}
}
#else
pthread_attr_t attributes;
pthread_attr_init(&attributes);
pthread_attr_setstacksize(&attributes, 64u << 20u);
pthread_attr_setstacksize(&attributes, StackBytes);
for (unsigned index = 0; index < count; ++index) {
pthread_t thread;
if (pthread_create(&thread, &attributes, &RegistrationPreparePool::Main, this) == 0) {
@@ -1225,8 +1261,16 @@ private:
}
}
pthread_attr_destroy(&attributes);
#endif
}
#ifdef _WIN32
static unsigned __stdcall WindowsMain(void* argument) {
Main(argument);
return 0;
}
#endif
static void* Main(void* argument) {
auto& pool = *static_cast<RegistrationPreparePool*>(argument);
if (PerformanceTrace::Configured()) PerformanceTrace::BindThread("shader prepare", TraceThread::DefaultCapacity / 8);
@@ -1271,7 +1315,7 @@ private:
std::condition_variable ready;
std::condition_variable idle;
std::deque<Task> tasks;
std::vector<pthread_t> workers;
std::vector<Worker> workers;
unsigned threads = 0;
inline static std::atomic<unsigned> liveWorkers{0};
unsigned running = 0;
@@ -1358,13 +1402,13 @@ std::vector<PreparedGraphicsStage> PrepareGraphicsStages(const DrawDecode& decod
require(expected == decoded.roles, "graphics ABI linked programs do not match the stage routing");
std::vector<ShaderRecompiler::LinkedProgram> linked;
std::vector<ShaderRecompiler::MemoryRegion> memory;
AppendProgramMemory(memory, decoded.programs);
for (std::size_t index = 0; index < decoded.programs.size(); ++index) {
const auto& program = decoded.programs[index];
require(program.snapshot != nullptr && program.codeOffset < program.snapshot->code.size(), "graphics ABI has an invalid registered entry point");
const auto code = std::span(program.snapshot->code).subspan(program.codeOffset);
require(program.binary.code.data() == code.data() && program.binary.code.size() == code.size() && program.binary.codeAddress == program.snapshot->codeAddress + program.codeOffset * sizeof(std::uint32_t), "graphics ABI entry point differs from its snapshot");
linked.push_back({decoded.roles[index], program.binary, program.userDataBase, program.firstUserSgpr, program.userData});
memory.insert(memory.end(), program.memory.begin(), program.memory.end());
}
std::vector<PreparedGraphicsStage> prepared;
std::uint32_t pushOffset = 0;
@@ -943,15 +943,29 @@ void NoteDispatchRegisters(std::span<const std::uint32_t> packet, QueueState& qu
}
}
void ExecuteIndirectRegisters(std::span<const std::uint32_t> packet, std::span<const std::uint32_t> pairs, QueueState& queue) {
bool IndirectRegistersValid(std::span<const std::uint32_t> pairs) {
for (std::size_t i = 0; i < pairs.size(); i += 2) {
if (pairs[i] == 0xffffffffu || (pairs[i] & ~0x70000000u) > 0xffffu) return false;
}
return true;
}
void ExecuteIndirectRegisters(std::span<const std::uint32_t> packet, std::span<const std::uint32_t> pairs, QueueState& queue, bool skipInvalid) {
const auto opcode = (packet[0] >> 8u) & 0xffu;
require(packet.size() == 5 && IndirectRegisterOpcode(opcode), "expected indirect register packet");
require(pairs.size() == static_cast<std::size_t>(packet[4]) * 2, "indirect register list does not match its packet");
for (std::size_t i = 0; i < pairs.size(); i += 2) registerOffset(pairs[i]);
for (std::size_t i = 0; i < pairs.size(); i += 2) writeRegister(queue, opcode, registerOffset(pairs[i]), pairs[i + 1]);
const auto usable = [&](std::uint32_t value) { return !skipInvalid || IndirectRegistersValid(std::span(&value, 1)); };
for (std::size_t i = 0; i < pairs.size(); i += 2) {
if (usable(pairs[i])) registerOffset(pairs[i]);
}
for (std::size_t i = 0; i < pairs.size(); i += 2) {
if (usable(pairs[i])) writeRegister(queue, opcode, registerOffset(pairs[i]), pairs[i + 1]);
}
if (queue.savedContext.has_value() && TraceContextState()) {
std::fprintf(stderr, "[context] indirect 0x%x:", opcode);
for (std::size_t i = 0; i < pairs.size(); i += 2) std::fprintf(stderr, " %x", registerOffset(pairs[i]));
for (std::size_t i = 0; i < pairs.size(); i += 2) {
if (usable(pairs[i])) std::fprintf(stderr, " %x", registerOffset(pairs[i]));
}
std::fprintf(stderr, "\n");
}
}
@@ -2241,6 +2241,10 @@ bool VulkanDevice::OpenWriteOverlaps(std::uint64_t address, std::size_t bytes) c
return state->recorder && state->recorder->OpenWriteOverlaps(address, bytes);
}
void VulkanDevice::SealPassWriteOnly() {
if (state->recorder) state->recorder->SealPassWriteOnly();
}
bool VulkanDevice::RecordedWritesSettled(std::uint64_t address, std::size_t bytes) const {
if (!state->recorder) return true;
const auto& recorder = *state->recorder;
@@ -3869,6 +3873,10 @@ void VulkanDevice::Draw(const Graphics::State& graphics, const Pm4::DrawParamete
if (trace) APS5_LOG_CHARS_OUT("VulkanDevice::Draw complete");
}
bool VulkanDevice::DrawInputsInPlace() const {
return state->drawInputsInPlace;
}
namespace {
// The resource cache serves dispatches unless disabled; Revalidate proves a hit by texture identity,
@@ -70,6 +70,7 @@ void ClearMultisampleTargets(VkDevice device);
void InstallResidentImageReadGuard_nid_no_patch();
void UpdateResidentImageUnmapObserver_nid_no_patch();
void RequireNoMultisampleTarget(std::uint64_t address, std::size_t bytes, const char* access);
void RequireNoMultisampleColorTarget(std::uint64_t address, std::size_t bytes, const char* access);
}
@@ -22,6 +22,7 @@ struct PassAccess {
std::span<const std::pair<VkImage, bool>> attachments;
bool anyReads = false;
bool anyWrites = false;
GuestRangeList writeOnly = {};
};
enum class PassHazard : std::uint8_t { None, ReadAfterWrite, WriteAfterWrite, WriteAfterRead, Image, UnknownRead, UnknownWrite };
@@ -32,6 +33,7 @@ public:
bool Overlaps(std::uint64_t begin, std::uint64_t end) const;
bool Empty() const { return ranges.empty(); }
std::size_t Size() const { return ranges.size(); }
GuestRangeList Ranges() const { return ranges; }
void Clear() { ranges.clear(); }
private:
@@ -43,6 +45,7 @@ public:
PassHazard Check(const PassAccess& access, bool samePass) const;
void Add(const PassAccess& access);
void BeginPass() { ++pass; }
void SealWriteOnly();
void Clear();
bool Empty() const;
@@ -55,6 +58,7 @@ private:
bool overlapsAny(const GuestRangeSet& set, GuestRangeList ranges) const;
GuestRangeSet reads;
GuestRangeSet writes;
GuestRangeSet writeOnly;
std::vector<std::pair<VkImage, bool>> images;
std::vector<Attachment> attachments;
std::uint64_t pass = 0;
@@ -69,6 +69,7 @@ public:
};
DrawPassStart StartDrawPass(std::uint64_t key, bool forced, bool fenced, const PassAccess& access);
void LeaveRenderPassOpen(std::uint64_t key, std::uint32_t timing, bool fenced, const PassAccess& access, std::function<void(VkCommandBuffer)> afterPass = {});
void SealPassWriteOnly();
// DCC "uncompressed" key stores (DccMetadata.cpp StoreUncompressedOnGpu): queued on the open
// batch and recorded as one run (one barrier pair for every queued fill) at Submit, before a
// label store (RecordStore), or before a command that writes or reads a queued range (the
@@ -234,6 +234,7 @@ public:
// The recorder's hazard tracker inputs (Recorder::NoteAccess): the written elements' guest
// ranges, the regions read in place, and the storage images with whether each is written.
const std::vector<std::pair<std::uint64_t, std::uint64_t>>& GpuWrites() const { return guestMemory.Writes(); }
const std::vector<std::pair<std::uint64_t, std::uint64_t>>& WriteOnlyRanges() const { return writeOnlyRanges; }
std::vector<std::pair<std::uint64_t, std::uint64_t>> InPlaceReads() const { return guestMemory.InPlaceReads(); }
void InPlaceReads(std::vector<std::pair<std::uint64_t, std::uint64_t>>& out) const { guestMemory.InPlaceReads(out); }
std::vector<std::pair<VkImage, bool>> StorageImages() const;
@@ -306,7 +307,7 @@ public:
bool DataWordsDiffer(const CompiledShader& shader) const;
// Why the fast proof of a Revalidate left the object to the full walk (the [rescache]
// revalidate line's reasons); Count: it did not.
enum class FastFail : std::size_t { NoRecord, Collect, Pending, Evicted, Changed, Keys, ClearedView, StorageKeys, Depth, Count };
enum class FastFail : std::size_t { NoRecord, Collect, Pending, Evicted, Changed, Keys, ClearedView, StorageKeys, Depth, Replaced, Audit, Count };
// Why a Pending failure was left to the full walk instead of the own-object refresh (T1, see
// refreshOwnObjects); Count: it was not.
enum class OwnRefreshFallback : std::size_t { Disabled, Snapshot, Keys, ForeignView, SurfaceKey, NotImported, Uncached, Rerun, Count };
@@ -344,6 +345,7 @@ private:
std::uint32_t format = 0;
int tileMode = 0;
std::uint64_t dccAddress = 0;
bool imageTable = false;
};
std::vector<DescribedRange> describedRanges;
struct Allocation {
@@ -354,6 +356,7 @@ private:
ShaderRecompiler::DescriptorRole role = ShaderRecompiler::DescriptorRole::ShaderData;
// Guest buffers: whether the shader may store to the range (see addGuestBuffer).
bool written = true;
bool writeOnly = false;
// Data buffers: the words the buffer holds once the recorded work ran (see RefreshData).
std::vector<std::uint32_t> dataWords;
std::uint32_t adjustment = 0;
@@ -589,6 +592,8 @@ private:
// Guest buffer elements bound read-only: each use of this object skips that many pending-write
// notes (counted in MarkGpuWrites for the [buffers] line).
std::size_t readOnlyBuffers = 0;
std::chrono::steady_clock::time_point lastFullWalk = std::chrono::steady_clock::now();
std::vector<std::pair<std::uint64_t, std::uint64_t>> writeOnlyRanges;
bool drawBuild = false;
std::uint64_t dispatchThreads = 0;
std::vector<std::shared_ptr<Texture>> textures;
@@ -1880,6 +1880,11 @@ void captureInputs(const Context& context, Recorder& recorder, VkCommandBuffer c
});
}
bool WriteOnlyPasses() {
static const bool enabled = std::getenv("APS5_NO_WRITE_ONLY_PASSES") == nullptr;
return enabled;
}
void recordDraw(const Context& context, const State& state, const Pm4::DrawParameters& draw, std::span<const CompiledShader> shaders, DrawInputs& inputs, RecordedDraw& record, DrawOutcome& outcome, DrawTimer& timer, double& ownWaitedMs) {
auto* recorder = record.recorder;
auto& resources = *record.resources;
@@ -1919,6 +1924,7 @@ void recordDraw(const Context& context, const State& state, const Pm4::DrawParam
auto& passReads = ShaderRecompiler::ThreadOwned(passReadsSlot);
passReads.clear();
if (!addressBased) resources.DeviceReads(passReads);
const auto deviceReads = passReads.size();
if (gpuIndirect) {
passReads.emplace_back(args->arguments, args->arguments + args->RangeBytes());
if (args->countIndirect) passReads.emplace_back(args->countAddress, args->countAddress + 4);
@@ -1936,7 +1942,29 @@ void recordDraw(const Context& context, const State& state, const Pm4::DrawParam
const bool readOnlyBda = addressReadPasses && resources.BdaReadOnly();
outcome.addressDraw = addressBased;
const bool unknownWrites = ((addressBased || resources.UsesAddressTable()) && !readOnlyBda) || resources.HasCopiedWrites();
const PassAccess passAccess{passReads, resources.GpuWrites(), passImages, passAttachments, unknownMemory, unknownWrites};
GuestRangeList passWrites = resources.GpuWrites();
GuestRangeList passWriteOnly{};
thread_local std::vector<std::pair<std::uint64_t, std::uint64_t>>* hazardWritesSlot = nullptr;
if (const auto& writeOnly = resources.WriteOnlyRanges(); !writeOnly.empty() && !unknownMemory && WriteOnlyPasses()) {
const auto listed = [&](const std::pair<std::uint64_t, std::uint64_t>& range, bool aligned) {
return std::any_of(writeOnly.begin(), writeOnly.end(), [&](const auto& only) {
if (!aligned) return only == range;
const auto begin = only.first & ~std::uint64_t{3};
const auto end = begin == only.first ? only.second : (only.second + 3) & ~std::uint64_t{3};
return range.first == begin && range.second == end;
});
};
const auto ownReads = passReads.begin() + static_cast<std::ptrdiff_t>(deviceReads);
passReads.erase(std::remove_if(passReads.begin(), ownReads, [&](const auto& range) { return listed(range, true); }), ownReads);
auto& hazardWrites = ShaderRecompiler::ThreadOwned(hazardWritesSlot);
hazardWrites.clear();
for (const auto& range : resources.GpuWrites()) {
if (!listed(range, false)) hazardWrites.push_back(range);
}
passWrites = hazardWrites;
passWriteOnly = writeOnly;
}
const PassAccess passAccess{passReads, passWrites, passImages, passAttachments, unknownMemory, unknownWrites, passWriteOnly};
const bool fenced = state.depth.has_value() || !record.proxies.empty();
const auto start = recorder->StartDrawPass(passKey, capture || readsTarget || gpuIndirect || meshIndirect, fenced, passAccess);
for (const auto& target : record.multisampleTargets) KeepMultisampleTarget_nid_no_patch(target);
@@ -203,8 +203,8 @@ GuestTextureResource DecodeTextureResource(std::span<const std::uint32_t> words)
Require(width == height, "guest cube texture descriptor is not square");
Require(baseArray <= depth, "guest cube texture descriptor has a base array past its last array slice");
if (baseArray == depth) depth += 5u;
if ((depth - baseArray + 1u) % 6u != 0) {
Require(false, "guest cube texture descriptor does not contain a multiple of 6 array slices" +
if (depth - baseArray + 1u < 6u) {
Require(false, "guest cube texture descriptor holds fewer than 6 array slices" +
describe() + " (base array " + std::to_string(baseArray) + ")");
}
break;
@@ -48,6 +48,10 @@ bool sameTarget(const ColorTarget& a, const ColorTarget& b) {
return a.surfaceAddress == b.surfaceAddress && a.format == b.format && a.extent.width == b.extent.width && a.extent.height == b.extent.height && a.samples == b.samples;
}
bool recreated(const ColorTarget& resident, const ColorTarget& use) {
return use.dccAddress != 0 && use.pipeBankXor != resident.pipeBankXor && use.address == resident.address && use.dccAddress == resident.dccAddress && use.dccControl == resident.dccControl && use.dccAlphaOnMsb == resident.dccAlphaOnMsb && use.dccPipeAligned == resident.dccPipeAligned && use.cmaskAddress == resident.cmaskAddress && use.cmaskBytes == resident.cmaskBytes && use.tileMode == resident.tileMode && use.elementBytes == resident.elementBytes;
}
VkClearColorValue fastClearColor(const ColorTarget& target) {
Require(target.elementBytes != 0 && target.elementBytes <= sizeof(target.clearWords) && 16u % target.elementBytes == 0, "the fast clear of a multisampled target over 64-bit texels is not modeled");
std::array<std::byte, 16> texels{};
@@ -223,6 +227,24 @@ public:
});
}
void Recreate(const ColorTarget& use) {
SettleStorageWrites();
target = use;
cmask = MultisampledCmask(use.cmaskAddress, use.cmaskBytes);
colorFill = MultisampledColorFill(use);
fromTexture = false;
dccRanges.clear();
metadataValid = std::make_shared<std::atomic<bool>>(true);
const auto initial = initialDccClear(use);
recordCommands(context, [&](VkCommandBuffer commands, Recorder* recorder) {
RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_TRANSFER_WRITE_BIT);
const VkImageSubresourceRange range{VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
context.Resolved(&DeviceFunctions::cmdClearColorImage, "vkCmdClearColorImage")(commands, image, VK_IMAGE_LAYOUT_GENERAL, &initial, 1, &range);
RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT);
if (recorder != nullptr) Recorder::CountBarriers(Recorder::CommandClass::Draw, 2);
});
}
void SettleStorageWrites() {
if (!storageWritten) return;
storageWritten = false;
@@ -537,6 +559,17 @@ MultisampleTarget& acquire(const Context& context, const ColorTarget& use) {
for (const auto& target : targets()) {
if (!target->active->load(std::memory_order_acquire)) continue;
if (target->context.device != context.device || !sameTarget(target->target, use)) continue;
if (recreated(target->target, use)) {
{
std::unique_lock lock(guardedMutex());
std::erase_if(guarded(), [&](const auto& range) { return range.active == target->active; });
anyGuarded().store(!guarded().empty(), std::memory_order_release);
}
target->Recreate(use);
guard(context, use, target->active, target->metadataValid);
target->ApplyFastClear(use);
return *target;
}
if (target->fromTexture && use.cmaskAddress != 0) {
target->fromTexture = false;
target->cmask = MultisampledCmask(use.cmaskAddress, use.cmaskBytes);
@@ -967,6 +1000,10 @@ void ClearMultisampleTargets(VkDevice device) {
void RequireNoMultisampleTarget(std::uint64_t address, std::size_t bytes, const char* access) {
RequireNoResidentDepthRead_nid_no_patch(address, bytes);
RequireNoMultisampleColorTarget(address, bytes, access);
}
void RequireNoMultisampleColorTarget(std::uint64_t address, std::size_t bytes, const char* access) {
if (bytes == 0 || !anyGuarded().load(std::memory_order_acquire)) return;
std::shared_lock lock(guardedMutex());
for (const auto& range : guarded()) {
@@ -34,15 +34,16 @@ bool PassHazards::overlapsAny(const GuestRangeSet& set, GuestRangeList ranges) c
PassHazard PassHazards::Check(const PassAccess& access, bool samePass) const {
const auto touchesMemory = [](const PassAccess& use) {
const auto nonEmpty = [](GuestRangeList ranges) { return std::any_of(ranges.begin(), ranges.end(), [](const auto& range) { return range.first < range.second; }); };
return use.anyReads || use.anyWrites || nonEmpty(use.reads) || nonEmpty(use.writes);
return use.anyReads || use.anyWrites || nonEmpty(use.reads) || nonEmpty(use.writes) || nonEmpty(use.writeOnly);
};
const bool earlierTouches = anyReads || anyWrites || !reads.Empty() || !writes.Empty();
const bool earlierTouches = anyReads || anyWrites || !reads.Empty() || !writes.Empty() || !writeOnly.Empty();
if ((access.anyWrites && earlierTouches) || (anyWrites && touchesMemory(access))) return PassHazard::UnknownWrite;
const bool writesRanges = std::any_of(access.writes.begin(), access.writes.end(), [](const auto& range) { return range.first < range.second; });
if ((access.anyReads && !writes.Empty()) || (anyReads && writesRanges)) return PassHazard::UnknownRead;
if (overlapsAny(writes, access.reads)) return PassHazard::ReadAfterWrite;
if (overlapsAny(writes, access.writes)) return PassHazard::WriteAfterWrite;
if (overlapsAny(reads, access.writes)) return PassHazard::WriteAfterRead;
const auto nonEmpty = [](GuestRangeList ranges) { return std::any_of(ranges.begin(), ranges.end(), [](const auto& range) { return range.first < range.second; }); };
const bool writesRanges = nonEmpty(access.writes) || nonEmpty(access.writeOnly);
if ((access.anyReads && (!writes.Empty() || !writeOnly.Empty())) || (anyReads && writesRanges)) return PassHazard::UnknownRead;
if (overlapsAny(writes, access.reads) || overlapsAny(writeOnly, access.reads)) return PassHazard::ReadAfterWrite;
if (overlapsAny(writes, access.writes) || overlapsAny(writeOnly, access.writes) || overlapsAny(writes, access.writeOnly)) return PassHazard::WriteAfterWrite;
if (overlapsAny(reads, access.writes) || overlapsAny(reads, access.writeOnly)) return PassHazard::WriteAfterRead;
const auto conflicts = [](bool earlier, bool later) { return earlier || later; };
for (const auto& [image, written] : access.images) {
if (image == VK_NULL_HANDLE) continue;
@@ -60,6 +61,7 @@ PassHazard PassHazards::Check(const PassAccess& access, bool samePass) const {
void PassHazards::Add(const PassAccess& access) {
for (const auto& [begin, end] : access.reads) reads.Insert(begin, end);
for (const auto& [begin, end] : access.writes) writes.Insert(begin, end);
for (const auto& [begin, end] : access.writeOnly) writeOnly.Insert(begin, end);
for (const auto& [image, written] : access.images) {
if (image == VK_NULL_HANDLE) continue;
const auto it = std::find_if(images.begin(), images.end(), [&](const auto& seen) { return seen.first == image; });
@@ -76,16 +78,22 @@ void PassHazards::Add(const PassAccess& access) {
anyWrites = anyWrites || access.anyWrites;
}
void PassHazards::SealWriteOnly() {
for (const auto& [begin, end] : writeOnly.Ranges()) writes.Insert(begin, end);
writeOnly.Clear();
}
void PassHazards::Clear() {
reads.Clear();
writes.Clear();
writeOnly.Clear();
images.clear();
attachments.clear();
anyReads = anyWrites = false;
}
bool PassHazards::Empty() const {
return reads.Empty() && writes.Empty() && images.empty() && attachments.empty() && !anyReads && !anyWrites;
return reads.Empty() && writes.Empty() && writeOnly.Empty() && images.empty() && attachments.empty() && !anyReads && !anyWrites;
}
}
@@ -1420,6 +1420,10 @@ void Recorder::LeaveRenderPassOpen(std::uint64_t key, std::uint32_t timing, bool
open->passHazards.Add(access);
}
void Recorder::SealPassWriteOnly() {
if (open != nullptr) open->passHazards.SealWriteOnly();
}
void Recorder::endOpenRenderPass(bool barrier) {
auto& pass = open->renderPass;
if (open->segmentActive) endSegment(*open);
@@ -120,6 +120,7 @@ struct CachedTexture {
// The untrackedKey the snapshot was last read or compared equal at, for a surface outside the
// write watch; zero when none was taken.
std::array<std::uint64_t, 4> untracked{};
std::uint64_t surfaceBytes = 0;
};
// Entries in use order (front = most recent) with a hash index by key: a lookup is O(1) and the
@@ -864,18 +865,19 @@ std::shared_ptr<Texture> cachedTexture(const Context& context, std::span<const s
if (profile) LookupOutcomes::Add(*keys != DccKeys::Uncompressed ? LookupOutcomes::SampledHitClearedView : LookupOutcomes::SampledHitView, start);
return it->texture;
}
} else if (source == nullptr && it->bytes.size() == guestBytes && it->keys == *keys) {
} else if (source == nullptr && it->surfaceBytes == guestBytes && it->keys == *keys) {
// Unwritten pages need no comparison; partially resident textures compare only committed
// pages. The compare goes through the flush hook: it waits for recorded work over the
// surface (counted, and named for the [hooksync] line).
const auto equalsCommitted = [&] {
if (it->bytes.empty()) return false;
if (Recorder::SnapshotWriteOverlaps(address, bytes)) counters.pendingReads.fetch_add(1, std::memory_order_relaxed);
const GuestMemory::ReadSiteScope site(GuestMemory::ReadSite::TextureCompare);
return GuestMemory::EqualsCommitted(address, it->bytes);
};
const auto unchanged = [&] {
if (it->committed != committed) return false;
return committed.has_value() ? !Recorder::SnapshotWriteOverlaps(address, it->bytes.size()) && GuestMemory::UnchangedCommittedSince(*committed, it->generation) : GuestMemory::UnchangedSince(address, it->bytes.size(), it->generation);
return committed.has_value() ? !Recorder::SnapshotWriteOverlaps(address, static_cast<std::size_t>(it->surfaceBytes)) && GuestMemory::UnchangedCommittedSince(*committed, it->generation) : GuestMemory::UnchangedSince(address, static_cast<std::size_t>(it->surfaceBytes), it->generation);
};
const auto kept = [&] {
if (untracked[0] == 0) return false;
@@ -899,6 +901,7 @@ std::shared_ptr<Texture> cachedTexture(const Context& context, std::span<const s
}
CachedTexture entry{key, address, std::vector<std::byte>(source != nullptr ? 0u : bytes), nullptr, *keys, snapshotGeneration};
entry.committed = std::move(committed);
entry.surfaceBytes = bytes;
if (source != nullptr) {
entry.source = source;
entry.sourceVersion = source->Version();
@@ -933,6 +936,8 @@ std::shared_ptr<Texture> cachedTexture(const Context& context, std::span<const s
}
entry.texture = std::make_shared<Texture>(context, *context.detiler, resource, components, entry.bytes, depthCompare);
entry.accounted = entry.texture->AllocationBytes();
constexpr std::size_t SnapshotCompareBytes = 256u << 10u;
if (entry.bytes.size() > SnapshotCompareBytes && (entry.generation != 0 || entry.untracked[0] != 0)) std::vector<std::byte>().swap(entry.bytes);
counters.snapshots.fetch_add(1, std::memory_order_relaxed);
}
const auto budget = sampledBudget(context, cache);
@@ -1202,7 +1207,7 @@ std::uint64_t StorageTextureBudget(const VkPhysicalDeviceMemoryProperties& memor
if (reported == nullptr || !heap || reported->heapBudget[*heap] == 0) return TextureCacheBudget(memory);
const auto budget = reported->heapBudget[*heap];
const auto usage = reported->heapUsage[*heap];
const auto reserved = usage - std::min<std::uint64_t>(usage, sampledBytes + storageBytes) + std::max<std::uint64_t>(2048ull << 20u, sampledBytes) + budget / 8u;
const auto reserved = usage - std::min<std::uint64_t>(usage, sampledBytes + storageBytes) + sampledBytes + budget / 8u;
const auto cap = std::min<std::uint64_t>(TextureCacheBudget(memory), budget / 4u);
return std::max<std::uint64_t>(512ull << 20u, std::min<std::uint64_t>(cap, budget > reserved ? budget - reserved : 0u));
}
@@ -1961,6 +1966,8 @@ void ShaderResources::buildPrepare(std::span<const CompiledShader> shaders, cons
const bool read = element < binding.bufferRead.size() && binding.bufferRead[element];
const std::uint32_t extent = element < binding.bufferExtent.size() ? binding.bufferExtent[element] : 0u;
const auto index = addGuestBuffer(std::span<const std::uint32_t>(binding.guestDescriptor).subspan(static_cast<std::size_t>(element) * 4, 4), target, indexAddress, indexBytes, written, atomic, read, extent);
const bool classified = element < binding.bufferWritten.size() && element < binding.bufferRead.size() && element < binding.bufferAtomic.size();
allocations[index].writeOnly = classified && allocations[index].guest && allocations[index].written && binding.bufferWritten[element] && !read && !atomic;
const auto& push = shader.program->pushConstants;
if (!push.empty()) {
const auto position = shader.program->memoryOffsetDword * 4u + element;
@@ -2156,6 +2163,14 @@ void ShaderResources::buildPrepare(std::span<const CompiledShader> shaders, cons
Check(context.Function<PFN_vkAllocateDescriptorSets>("vkAllocateDescriptorSets")(context.device, &allocation, &_set), "vkAllocateDescriptorSets");
}
}
writeOnlyRanges.clear();
for (const auto& item : allocations) {
if (!item.writeOnly || item.size == 0) continue;
const auto begin = item.address;
const auto end = item.address + item.size;
const bool shared = std::any_of(allocations.begin(), allocations.end(), [&](const Allocation& other) { return !other.writeOnly && other.size != 0 && other.address < end && begin < other.address + other.size; });
if (!shared) writeOnlyRanges.emplace_back(begin, end);
}
timing.descriptorsMs = phase(BuildPhase::Descriptors);
if (BuildProfiled()) {
timing.prepareMs = std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - stageStart).count();
@@ -2710,7 +2725,7 @@ namespace {
// of the reused ones, fast = every image proved current from stamps, full = the lookups were repeated.
// Why the fast path left an object to the full walk (the "fast-fail by reason" counts).
using FastFail = ShaderResources::FastFail;
constexpr std::array<const char*, static_cast<std::size_t>(FastFail::Count)> FastFailNames{"no record", "collect", "pending image", "evicted image", "memory changed", "keys", "cleared view", "storage keys", "depth surface"};
constexpr std::array<const char*, static_cast<std::size_t>(FastFail::Count)> FastFailNames{"no record", "collect", "pending image", "evicted image", "memory changed", "keys", "cleared view", "storage keys", "depth surface", "replaced texture", "audit"};
using OwnRefreshFallback = ShaderResources::OwnRefreshFallback;
constexpr std::array<const char*, static_cast<std::size_t>(OwnRefreshFallback::Count)> OwnRefreshFallbackNames{"disabled", "snapshot texture", "cleared view", "foreign view", "surface key", "not imported", "uncached", "re-run failed"};
@@ -2848,6 +2863,19 @@ bool EpochRevalidate() {
return enabled;
}
std::chrono::steady_clock::duration TemplateAuditInterval() {
static const auto interval = [] {
const char* text = std::getenv("APS5_TEMPLATE_AUDIT_MS");
return std::chrono::milliseconds(text != nullptr ? std::strtoull(text, nullptr, 10) : 1000ull);
}();
return interval;
}
bool TemplateAuditDue(std::chrono::steady_clock::time_point last) {
const auto interval = TemplateAuditInterval();
return interval.count() != 0 && std::chrono::steady_clock::now() - last >= interval;
}
}
// Turns the lookups' records into this object's per-texture validation records (see
@@ -2926,6 +2954,7 @@ bool ShaderResources::fastRevalidate(std::uint64_t serialBefore, std::span<const
if (!DepthSurfaceHolds(context, surface.resource, textures[i].get())) return fail(FastFail::Depth);
continue;
}
if (textures[i]->Released()) return fail(FastFail::Replaced);
const auto address = surface.resource.baseAddress;
const auto bytes = static_cast<std::size_t>(surface.bytes);
surface.collected = GuestMemory::CollectWrites(address, bytes);
@@ -3052,6 +3081,7 @@ bool ShaderResources::fastRevalidateEach() {
if (!DepthSurfaceHolds(context, surface.resource, textures[i].get())) return false;
continue;
}
if (textures[i]->Released()) return false;
const auto address = surface.resource.baseAddress;
const auto bytes = static_cast<std::size_t>(surface.bytes);
surface.collected = GuestMemory::CollectWrites(address, bytes);
@@ -3229,6 +3259,7 @@ bool ShaderResources::Revalidate(std::span<const CompiledShader> shaders, ProofR
const auto start = profile ? std::chrono::steady_clock::now() : std::chrono::steady_clock::time_point{};
for (const auto& region : directRegions) CompletePendingCpuWrites(context, region.begin, static_cast<std::size_t>(region.end - region.begin));
for (const auto& range : describedRanges) {
if (!range.imageTable) RequireNoMultisampleColorTarget(range.address, static_cast<std::size_t>(range.bytes), "a cached image access");
CompletePendingCpuWrites(context, range.address, static_cast<std::size_t>(range.bytes));
if (range.dccAddress != 0) CompletePendingCpuWrites(context, range.dccAddress, DccKeyBytes(range.bytes));
}
@@ -3251,6 +3282,7 @@ bool ShaderResources::Revalidate(std::span<const CompiledShader> shaders, ProofR
// they must hand back the very objects the set's views belong to. The build appended them stage
// by stage, binding by binding, so the walk repeats that order.
const auto fullWalk = [&] {
lastFullWalk = std::chrono::steady_clock::now();
lookupLog().clear();
BuildLookups lookups;
std::size_t textureIndex = 0;
@@ -3356,6 +3388,11 @@ bool ShaderResources::Revalidate(std::span<const CompiledShader> shaders, ProofR
}
if (fallback != OwnRefreshFallback::Count) countOwnRefreshFallback(fallback);
}
if (fast && TemplateAuditDue(lastFullWalk)) {
fast = false;
ownRefreshed = false;
reason = FastFail::Audit;
}
if (report != nullptr) report->path = !fast ? ProofPath::Full : ownRefreshed ? ProofPath::OwnRefreshed : ProofPath::Fast;
if (!fast) {
countFullWalk(reason);
@@ -3363,7 +3400,7 @@ bool ShaderResources::Revalidate(std::span<const CompiledShader> shaders, ProofR
const auto failure = [&] {
switch (reason) {
case FastFail::Pending: return ProofFailure::Pending;
case FastFail::Evicted: return ProofFailure::Evicted;
case FastFail::Evicted: case FastFail::Replaced: return ProofFailure::Evicted;
case FastFail::Changed: return ProofFailure::Changed;
case FastFail::Keys: case FastFail::ClearedView: case FastFail::StorageKeys: return ProofFailure::Keys;
default: return ProofFailure::Other;
@@ -4487,6 +4524,7 @@ std::shared_ptr<Texture> ShaderResources::fastTexture(const ImageRecord& record)
// between the stages marks them uncompressed).
const auto address = record.resource.baseAddress;
const auto bytes = static_cast<std::size_t>(record.guestBytes);
RequireNoMultisampleColorTarget(address, bytes != 0 ? bytes : 1u, "a texture read");
if (DepthSurfaceAt(address)) return nullptr;
if (GuestMemory::CollectWrites(address, bytes) == 0) return nullptr;
if (PendingStorageOverlaps(address, bytes, record.source.get())) return nullptr;
@@ -4825,7 +4863,7 @@ void ShaderResources::resolveImageTables() {
const auto place = [&](std::uint32_t element, std::shared_ptr<Texture> texture, const GuestTextureResource& resource, std::uint64_t guestBytes, bool firstLayer) {
textures.push_back(std::move(texture));
textureFirstLayer.push_back(firstLayer);
describedRanges.push_back({"texture", resource.baseAddress, guestBytes, resource.width, resource.height, resource.format, static_cast<int>(resource.tileMode), resource.dccAddress});
describedRanges.push_back({"texture", resource.baseAddress, guestBytes, resource.width, resource.height, resource.format, static_cast<int>(resource.tileMode), resource.dccAddress, true});
table.elementTextures[element] = textures.size() - 1u;
item->imageAllocations[static_cast<std::size_t>(table.elementBase) + element] = textures.size() - 1u;
};
@@ -4944,7 +4982,7 @@ void ShaderResources::resolveImageTables() {
storageWritten.push_back(true);
storageAtomic.push_back(false);
storageAtomic64.push_back(false);
describedRanges.push_back({"storage", resource.baseAddress, guestBytes, resource.width, resource.height, resource.format, static_cast<int>(resource.tileMode), resource.dccAddress});
describedRanges.push_back({"storage", resource.baseAddress, guestBytes, resource.width, resource.height, resource.format, static_cast<int>(resource.tileMode), resource.dccAddress, true});
bindings[storageBinding].imageAllocations[absolute] = storageTextures.size() - 1u;
continue;
}
@@ -53,6 +53,12 @@ float readFloat(const Registers& registers, std::uint32_t offset) {
return value;
}
float readDepthBound(const Registers& registers, std::uint32_t offset) {
const auto value = std::bit_cast<float>(read(registers, offset));
if (std::isnan(value)) Require(false, "NaN depth bound at DWORD " + std::to_string(offset));
return value;
}
std::string zeroMessage(std::uint32_t offset, std::uint32_t value, const char* name) {
char detail[64];
std::snprintf(detail, sizeof(detail), " (register 0x%x = 0x%08x)", offset, value);
@@ -361,8 +367,8 @@ void decodeDepth(const Registers& cx, std::uint32_t depthControl, bool testValEx
result.depthBoundsTest = (depthControl & 8u) != 0;
if (result.depthBoundsTest) {
Require(zFormat != 0, "depth bounds without a depth plane");
result.minDepthBounds = readFloat(cx, 0x008);
result.maxDepthBounds = readFloat(cx, 0x009);
result.minDepthBounds = readDepthBound(cx, 0x008);
result.maxDepthBounds = readDepthBound(cx, 0x009);
}
if (depthClear) {
result.depthTest = true;
@@ -526,7 +526,7 @@ Texture::Texture(const Context& context, TextureDetiler& detiler, const GuestTex
const auto viewLevelCount = std::min(descriptor.lastLevel, descriptor.mipCount - 1u) - descriptor.baseLevel + 1u;
const auto viewLayerCount = geometry.imageLayers - descriptor.baseArray;
if (descriptor.dimension == TextureDimension::kCube) {
Require(viewLayerCount % 6u == 0, "guest cube texture view does not contain a multiple of 6 array slices");
Require(viewLayerCount >= 6u, "guest cube texture view holds fewer than 6 array slices");
}
VkImageViewCreateInfo viewInfo{VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO};
@@ -633,7 +633,7 @@ Texture::Texture(const Context& context, const std::shared_ptr<StorageTexture>&
const auto viewLevelCount = std::min(descriptor.lastLevel, descriptor.mipCount - 1u) - descriptor.baseLevel + 1u;
const auto viewLayerCount = geometry.imageLayers - descriptor.baseArray;
if (descriptor.dimension == TextureDimension::kCube) {
Require(viewLayerCount % 6u == 0, "guest cube texture view does not contain a multiple of 6 array slices");
Require(viewLayerCount >= 6u, "guest cube texture view holds fewer than 6 array slices");
}
VkImageViewCreateInfo viewInfo{VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO};
viewInfo.image = image;
@@ -748,7 +748,7 @@ Texture::Texture(const Context& context, const std::shared_ptr<StorageTexture>&
const auto viewLevelCount = std::min(descriptor.lastLevel, descriptor.mipCount - 1u) - descriptor.baseLevel + 1u;
const auto viewLayerCount = geometry.imageLayers - descriptor.baseArray;
if (descriptor.dimension == TextureDimension::kCube) {
Require(viewLayerCount % 6u == 0, "guest cube texture view does not contain a multiple of 6 array slices");
Require(viewLayerCount >= 6u, "guest cube texture view holds fewer than 6 array slices");
}
VkImageViewCreateInfo viewInfo{VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO};
viewInfo.image = image;
@@ -1015,7 +1015,7 @@ Texture::Texture(const Context& context, const std::shared_ptr<StorageTexture>&
const auto viewLevelCount = std::min(descriptor.lastLevel, descriptor.mipCount - 1u) - descriptor.baseLevel + 1u;
const auto viewLayerCount = std::min(geometry.imageLayers, source->ImageLayers()) - descriptor.baseArray;
if (descriptor.dimension == TextureDimension::kCube) {
Require(viewLayerCount % 6u == 0, "guest cube texture view does not contain a multiple of 6 array slices");
Require(viewLayerCount >= 6u, "guest cube texture view holds fewer than 6 array slices");
}
VkImageViewCreateInfo viewInfo{VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO};
viewInfo.image = source->Image();
@@ -314,6 +314,9 @@ static void RunBdaAnchorTests(const Context& context) {
for (std::uint32_t lane = 0; lane < 32u; ++lane) lows.push_back(lane * 0x24u);
std::vector<std::uint32_t> strided;
for (std::uint32_t lane = 0; lane < lanes; ++lane) strided.push_back(lane * 16u);
auto halfword = build({{{-1, 16, 1, 1}}, false, false, false, lanes});
compare(*halfword, {guest + 0x100}, {1u});
compare(*halfword, {guest + 0x100}, {0u, 1u});
auto continuing = build({reads, false, false, false, lanes});
compare(*continuing, anchors, lows);
compare(*continuing, {guest + 0x400}, strided);
@@ -125,6 +125,26 @@ bool halvesRejoin() {
return rejoined == &word && fieldBack;
}
bool factorFieldKept(bool multiplied) {
IrProgram program;
auto& block = program.CreateBlock();
program.SetEntryBlock(block);
program.BlockOrder().push_back(&block);
IrBuilder builder(program);
builder.SetInsertionPoint(block);
auto& lane = builder.Emit(IrOpcode::LaneId, IrType::U32, {});
auto& sum = builder.Emit(IrOpcode::IAdd32, IrType::U32, {&builder.Constant(1u), &lane});
auto& masked = builder.Emit(IrOpcode::BitwiseAnd32, IrType::U32, {&sum, &builder.Constant(0xffffu)});
auto& field = builder.Emit(IrOpcode::BitFieldUExtract, IrType::U32, {&masked, &builder.Constant(0u), &builder.Constant(24u)});
auto& used = multiplied ? builder.IMul(field, field) : field;
auto& user = builder.Emit(IrOpcode::ReferenceU32, IrType::Void, {&used});
static_cast<void>(builder.Emit(IrOpcode::Return, IrType::Void, {}));
ConstantFolder().Fold(program);
const IrValue* result = user.Argument(0)->Resolve();
if (!multiplied) return result == &masked;
return result->Opcode() == IrOpcode::IMul32 && result->Argument(0)->Resolve() == &field && field.Opcode() == IrOpcode::BitFieldUExtract && field.Argument(0)->Resolve() == &masked;
}
bool reversesTo(std::uint32_t word, std::uint32_t expected) {
IrProgram program;
auto& block = program.CreateBlock();
@@ -220,6 +240,8 @@ int main() {
passed &= run("a mask under a merge of the halves reads through it", mergedHalfMask());
passed &= run("half extractions read the half that was merged in", halfExtractions());
passed &= run("the two halves of one word rejoin into the word", halvesRejoin());
passed &= run("a 24-bit field read of a value masked to 16 bits folds into the value", factorFieldKept(false));
passed &= run("a 24-bit field read that a multiply uses is kept, even for a factor masked to 16 bits", factorFieldKept(true));
return passed ? 0 : 1;
} catch (const std::exception& error) {
std::fprintf(stderr, "%s\n", error.what());
@@ -471,6 +471,31 @@ void verifyLongBranch(const std::string& name, std::span<const std::uint32_t> co
require(source != graph.blocks.end() && source->terminator.kind == TerminatorKind::Branch && graph.FindBlock(source->terminator.trueBlock).startProgramCounter == target, name + ": the jump does not branch to " + std::to_string(target));
}
void verifyScheduledJumpTable() {
const std::vector<std::uint32_t> code{
0x83888208u, 0x7e020280u, 0x8f088308u, 0x7e040280u, 0xbe8c1f00u, 0x800cff0cu, 0x00000048u,
0x820d800du, 0xf4040386u, 0x10000000u, 0xbf8cc07fu, 0xbe901f00u, 0x80100e10u, 0x82110f11u,
0xbe802010u, 0x7e020281u, 0xbf820003u, 0x7e020282u, 0xbf820001u, 0x7e020283u, 0xe0700000u,
0x80000100u, 0xbf810000u, 0x0000000cu, 0u, 0x00000014u, 0u, 0x0000001cu, 0u,
};
verifyJumpTable("scheduled two-dword table", code, 0x20u, {0xcu, 0x14u, 0x1cu}, {0x3cu, 0x44u, 0x4cu});
static_cast<void>(recompile("scheduled two-dword table", code, 32u));
static_cast<void>(recompile("scheduled two-dword table", code, 64u));
auto vcc = patched(code, 0, {0x83ea826au});
vcc = patched(std::move(vcc), 2, {0x8f6a836au});
vcc = patched(std::move(vcc), 9, {0xd4000000u});
verifyJumpTable("scheduled two-dword table through vcc", vcc, 0x20u, {0xcu, 0x14u, 0x1cu}, {0x3cu, 0x44u, 0x4cu});
static_cast<void>(recompile("scheduled two-dword table through vcc", vcc, 32u));
static_cast<void>(recompile("scheduled two-dword table through vcc", vcc, 64u));
const std::vector<std::vector<std::uint32_t>> refused{
patched(code, 1, {0xbe880380u}),
patched(code, 3, {0xbe880380u}),
patched(code, 1, {0xbf820001u}),
patched(code, 3, {0xbf850000u}),
};
for (const auto& rejected : refused) require(refusal(rejected).find("unsupported dynamic s_setpc_b64") != std::string::npos, "a rewritten or conditional two-dword table index was accepted");
}
void verifyNullSwappc() {
const std::array<std::uint32_t, 2> jump{0xbefd210cu, 0xbf810000u};
const auto swappc = RdnaInstructionDecoder{}.Decode(jump).instructions.front();
@@ -1369,6 +1394,7 @@ done:
}
try {
verifyNullSwappc();
verifyScheduledJumpTable();
VerifyUserDataTail();
VerifyConstantHighTail();
verifyLargeLoopExitFallback();
@@ -52,6 +52,7 @@
#include <random>
#include <initializer_list>
#include <iostream>
#include <limits>
#include <map>
#include <optional>
#include <set>
@@ -2602,6 +2603,28 @@ void DepthBoundsBiasTests() {
queue.context[0x009] = bits(0.75f);
auto state = AgcDriver::Graphics::DecodeState(queue);
Require(state.depthTest && state.depthBoundsTest && state.minDepthBounds == 0.25f && state.maxDepthBounds == 0.75f, "depth bounds decode changed");
const auto infinity = std::numeric_limits<float>::infinity();
for (const auto bounds : {std::array{0.0f, infinity}, std::array{-infinity, 1.0f}, std::array{-infinity, infinity}}) {
queue.context[0x008] = bits(bounds[0]);
queue.context[0x009] = bits(bounds[1]);
state = AgcDriver::Graphics::DecodeState(queue);
Require(state.depthBoundsTest && state.minDepthBounds == bounds[0] && state.maxDepthBounds == bounds[1], "infinite depth bounds changed");
AgcDriver::Graphics::Context context{};
expectFailure([&] { AgcDriver::Graphics::ValidateDepthBounds(context, state); }, "depth bounds outside [0, 1]");
context.depthRangeUnrestricted = true;
AgcDriver::Graphics::ValidateDepthBounds(context, state);
}
for (const auto offset : {0x008u, 0x009u}) {
queue.context[0x008] = bits(0.25f);
queue.context[0x009] = bits(0.75f);
queue.context[offset] = bits(std::numeric_limits<float>::quiet_NaN());
expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, "NaN depth bound");
}
queue.context[0x008] = bits(0.25f);
queue.context[0x009] = bits(0.75f);
queue.context[0x00b] = bits(infinity);
expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, "non-finite register");
queue.context[0x00b] = bits(0.0f);
queue.context[0x1b3] = 2;
queue.context[0x1b4] = 2;
const auto rejection = AgcDriver::Graphics::DrawRejection(queue, false);
@@ -6408,10 +6431,10 @@ void storageTextureBudgetTests() {
Require(StorageTextureBudget(memory, &reported, 0, 0) == 2560 * MiB, "a 10 GiB budget on the 16 GiB heap does not limit the storage image cache to a quarter of the budget");
reported.heapUsage[2] = 5 * GiB;
Require(StorageTextureBudget(memory, &reported, 1 * GiB, 2 * GiB) == 2560 * MiB, "2 GiB used outside the texture caches on a 10 GiB budget lowered the storage image cache below its quarter");
reported.heapUsage[2] = 8 * GiB;
Require(StorageTextureBudget(memory, &reported, 1 * GiB, 2 * GiB) == 1792 * MiB, "a 10 GiB budget with 5 GiB used outside the texture caches does not leave 1792 MiB after the sampled textures' 2 GiB floor and 1280 MiB of headroom");
reported.heapUsage[2] = 8 * GiB + 512 * MiB;
Require(StorageTextureBudget(memory, &reported, 1 * GiB, 2 * GiB) == 2304 * MiB, "a 10 GiB budget with 5632 MiB used outside the texture caches does not leave 2304 MiB after 1 GiB of sampled textures and 1280 MiB of headroom");
reported.heapUsage[2] = 9 * GiB;
Require(StorageTextureBudget(memory, &reported, 3 * GiB, 2 * GiB) == 1792 * MiB, "3 GiB of sampled textures were not reserved past their 2 GiB floor");
Require(StorageTextureBudget(memory, &reported, 3 * GiB, 2 * GiB) == 1792 * MiB, "3 GiB of sampled textures were not reserved in full");
reported.heapUsage[2] = 1 * GiB;
Require(StorageTextureBudget(memory, &reported, 1 * GiB, 2 * GiB) == 2560 * MiB, "usage below the cached bytes is not read as no other device memory in use");
Require(StorageTextureBudget(memory, &reported, 7 * GiB, 1 * GiB) == 1792 * MiB, "7 GiB of sampled textures with the usage below the cached bytes do not leave 1792 MiB after their reservation and 1280 MiB of headroom");
@@ -6424,6 +6447,35 @@ void storageTextureBudgetTests() {
Require(StorageTextureBudget(memory, &reported, 0, 0) == 512 * MiB, "a 1 GiB budget does not keep the 512 MiB floor");
reported.heapBudget[2] = 40 * GiB;
Require(StorageTextureBudget(memory, &reported, 0, 0) == 4 * GiB, "a budget above the heap size raised the storage image cache past a quarter of the heap");
memory.memoryHeaps[2].size = 8 * GiB;
reported.heapBudget[2] = 7189 * MiB;
reported.heapUsage[2] = 4491 * MiB;
Require(StorageTextureBudget(memory, &reported, 49 * MiB, 506 * MiB) == 7189 * MiB / 4u, "49 MiB of sampled textures on an 8 GiB heap with a 7189 MiB budget and 3936 MiB used outside the texture caches held the storage image cache below a quarter of the budget");
reported.heapUsage[2] = 6490 * MiB;
Require(StorageTextureBudget(memory, &reported, 2 * GiB, 506 * MiB) == 512 * MiB, "2 GiB of sampled textures on an 8 GiB heap with a 7189 MiB budget and 3936 MiB used outside the texture caches did not take the storage image cache back to its 512 MiB floor");
memory.memoryHeaps[2].size = 10 * GiB;
reported.heapBudget[2] = 8767 * MiB;
reported.heapUsage[2] = 7981 * MiB;
Require(StorageTextureBudget(memory, &reported, 1427 * MiB, 490 * MiB) == 512 * MiB, "a 10 GiB heap at 7981 of its 8767 MiB budget with 1427 MiB of sampled textures does not keep the storage image cache at its 512 MiB floor");
for (const auto heapGiB : {2u, 4u, 6u, 8u, 16u, 32u}) {
memory.memoryHeaps[2].size = heapGiB * GiB;
for (std::uint64_t budget = GiB; budget <= heapGiB * GiB; budget += 512 * MiB) {
reported.heapBudget[2] = budget;
for (std::uint64_t sampled = 0; sampled <= budget; sampled += 256 * MiB) {
std::uint64_t previous = 0;
for (std::uint64_t outside = 0; outside <= budget + GiB; outside += 128 * MiB) {
reported.heapUsage[2] = outside + sampled + 512 * MiB;
const auto limit = StorageTextureBudget(memory, &reported, sampled, 512 * MiB);
Require(limit >= 512 * MiB, "pressure sweep lost the storage cache floor");
Require(limit <= std::max<std::uint64_t>(512 * MiB, budget / 4u), "pressure sweep exceeded a quarter of the reported budget above the floor");
Require(limit <= std::max<std::uint64_t>(2 * GiB, heapGiB * GiB / 4u), "pressure sweep exceeded the heap storage cache cap");
if (outside != 0) Require(limit <= previous, "additional non-cache memory usage grew the storage cache budget");
if (limit > 512 * MiB) Require(outside + sampled + budget / 8u + limit <= budget, "storage cache budget consumed live sampled memory or pressure headroom");
previous = limit;
}
}
}
}
memory.memoryHeaps[0].flags = 0;
memory.memoryHeaps[2].flags = 0;
Require(StorageTextureBudget(memory, &reported, 0, 0) == 2 * GiB, "a device without a device-local heap does not keep 2 GiB of storage images");
@@ -10274,6 +10326,11 @@ int main(int argc, char** argv) {
std::cout << "Multisampled DCC state checks passed\n";
return 0;
}
if (argc == 2 && std::string_view(argv[1]) == "--depth-bounds-state") {
DepthBoundsBiasTests();
std::cout << "Depth bounds state checks passed\n";
return 0;
}
{
const AgcDriver::Graphics::Context context{};
const AgcDriver::Graphics::State state{};
@@ -290,9 +290,14 @@ void RunGuestTextureResourceTests() {
Fields cubeBadArray = cube;
cubeBadArray.baseArray = 6;
rejectFields(cubeBadArray, "base array past its last array slice");
Fields cubeNotMultiple = cube;
cubeNotMultiple.depth = 4;
rejectFields(cubeNotMultiple, "multiple of 6");
Fields cubeExtraSlice = cube;
cubeExtraSlice.depth = 6;
result = DecodeTextureResource(pack(cubeExtraSlice));
Require(result.baseArray == 0 && result.depthOrLastArray == 6, "a cube descriptor with a slice past its last whole cube lost a slice");
Require(DescribeSurface(result).imageLayers == 7, "a cube allocation with a slice past its last whole cube omitted it");
Fields cubePartial = cube;
cubePartial.depth = 4;
rejectFields(cubePartial, "fewer than 6 array slices");
Fields multisampled = base;
multisampled.typeRaw = 14;
@@ -34,8 +34,9 @@ struct Use {
Images attachments;
bool anyReads = false;
bool anyWrites = false;
Ranges writeOnly;
PassAccess View() const { return {reads, writes, images, attachments, anyReads, anyWrites}; }
PassAccess View() const { return {reads, writes, images, attachments, anyReads, anyWrites, writeOnly}; }
};
void testRangeSet() {
@@ -184,12 +185,82 @@ void testAttachments() {
}
void testWriteOnly() {
PassHazards hazards;
hazards.BeginPass();
Use feedback;
feedback.writeOnly = {{0x1000, 0x8f80}};
hazards.Add(feedback.View());
Expect(hazards.Check(feedback.View(), true) == PassHazard::None, "draws only storing to the same buffer continue the pass");
Use reader;
reader.reads = {{0x2000, 0x2100}};
Expect(hazards.Check(reader.View(), true) == PassHazard::ReadAfterWrite, "a read of write-only stores of the pass");
Use writer;
writer.writes = {{0x8000, 0x9000}};
Expect(hazards.Check(writer.View(), true) == PassHazard::WriteAfterWrite, "a read-write use after write-only stores");
Use unknownReader;
unknownReader.anyReads = true;
Expect(hazards.Check(unknownReader.View(), true) == PassHazard::UnknownRead, "unknown reads after write-only stores");
Use elsewhere;
elsewhere.writeOnly = {{0x9000, 0xa000}};
elsewhere.reads = {{0xa000, 0xb000}};
Expect(hazards.Check(elsewhere.View(), true) == PassHazard::None, "write-only stores and reads elsewhere");
hazards.Clear();
hazards.BeginPass();
Use readWrite;
readWrite.reads = {{0x1000, 0x2000}};
readWrite.writes = {{0x1000, 0x2000}};
hazards.Add(readWrite.View());
Use storesOver;
storesOver.writeOnly = {{0x1800, 0x1900}};
Expect(hazards.Check(storesOver.View(), true) == PassHazard::WriteAfterWrite, "write-only stores after a read-write use");
hazards.Clear();
hazards.BeginPass();
Use readOnly;
readOnly.reads = {{0x1000, 0x2000}};
hazards.Add(readOnly.View());
Expect(hazards.Check(storesOver.View(), true) == PassHazard::WriteAfterRead, "write-only stores after a read");
hazards.Clear();
hazards.BeginPass();
Use unknownWriter;
unknownWriter.anyWrites = true;
unknownWriter.anyReads = true;
hazards.Add(feedback.View());
Expect(hazards.Check(unknownWriter.View(), true) == PassHazard::UnknownWrite, "unknown writes after write-only stores");
hazards.Clear();
hazards.Add(unknownWriter.View());
Expect(hazards.Check(feedback.View(), true) == PassHazard::UnknownWrite, "write-only stores after unknown writes");
hazards.Clear();
Use unknownRead;
unknownRead.anyReads = true;
hazards.Add(unknownRead.View());
Expect(hazards.Check(feedback.View(), true) == PassHazard::UnknownRead, "write-only stores after unknown reads");
hazards.Clear();
hazards.BeginPass();
hazards.Add(feedback.View());
hazards.SealWriteOnly();
Expect(hazards.Check(feedback.View(), true) == PassHazard::WriteAfterWrite, "write-only stores over stores sealed by a flush");
Use afterSeal;
afterSeal.writeOnly = {{0x9000, 0xa000}};
Expect(hazards.Check(afterSeal.View(), true) == PassHazard::None, "write-only stores elsewhere after a seal");
hazards.Add(afterSeal.View());
Expect(hazards.Check(afterSeal.View(), true) == PassHazard::None, "write-only stores after a seal continue the pass among themselves");
hazards.Clear();
Expect(hazards.Check(feedback.View(), true) == PassHazard::None, "a cleared seal");
hazards.Clear();
hazards.Add(feedback.View());
Expect(!hazards.Empty(), "write-only stores are tracked");
hazards.Clear();
Expect(hazards.Empty(), "cleared write-only stores");
}
int main() {
testRangeSet();
testRanges();
testUnknown();
testImages();
testAttachments();
testWriteOnly();
if (failures != 0) {
std::fprintf(stderr, "%d pass hazard checks failed\n", failures);
return 1;
@@ -15,6 +15,7 @@
#include <chrono>
#include <condition_variable>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <filesystem>
#include <fstream>
@@ -1012,6 +1013,54 @@ void testRegisterListsReadAtSubmission() {
check(std::atomic_ref<std::uint32_t>(done).load() == 1, "register list rewritten after submission was read by the worker");
}
void testInvalidRegisterListEntries() {
std::array<std::uint32_t, 6> pairs{0x038f038fu, 0xc8e9037eu, 0x10, 41, 0x11, 42};
const auto packet = makePacket(0x9f, {low(pairs.data()), high(pairs.data()), 0x80000000, 3});
check(!AgcDriver::Pm4::IndirectRegistersValid(pairs), "an offset dword with bits 16-27 set was taken as a register offset");
check(!AgcDriver::Pm4::IndirectRegistersValid(std::array<std::uint32_t, 2>{0xffffffffu, 0}), "the sentinel was taken as a register offset");
check(AgcDriver::Pm4::IndirectRegistersValid(std::array<std::uint32_t, 4>{0x10, 1, 0x70000011u, 2}), "a valid register list was rejected");
AgcDriver::QueueState state;
expectFailure([&] { AgcDriver::Pm4::ExecuteIndirectRegisters(packet, pairs, state); }, "extended register");
check(!state.context.contains(0x10) && !state.context.contains(0x11), "a rejected register list partially changed state");
AgcDriver::Pm4::ExecuteIndirectRegisters(packet, pairs, state, true);
check(state.context.at(0x10) == 41 && state.context.at(0x11) == 42 && !state.context.contains(0x38f), "skipping an invalid entry lost the valid ones or wrote the invalid one");
}
void testPartlyUpdatedRegisterListSkipsOnlyItsInvalidEntries() {
#ifdef _WIN32
check(_putenv_s("APS5_SKIP_INVALID_INDIRECT_REGISTERS", "1") == 0, "cannot enable skipping invalid register list entries");
#else
check(setenv("APS5_SKIP_INVALID_INDIRECT_REGISTERS", "1", 1) == 0, "cannot enable skipping invalid register list entries");
#endif
alignas(8) static std::uint32_t gate = 0;
alignas(8) static std::uint32_t copied = 0;
static std::array<std::uint32_t, 4> registers{0x038f038fu, 5, 0x11, 9};
auto words = joinPackets({
makePacket(0x3c, {0x13, low(&gate), high(&gate), 1, 0xffffffffu, 0x19}),
makePacket(0x9f, {low(registers.data()), high(registers.data()), 0x80000000, 2}),
makePacket(0x40, {0x100, 0xa010, 0, low(&copied), high(&copied)})});
submitWords(words);
registers = {0x10, 77, 0x03900390u, 6};
std::atomic_ref<std::uint32_t>(gate).store(1);
AgcDriverWaitIdle_nid_postfix();
check(std::atomic_ref<std::uint32_t>(copied).load() == 77, "a register list partly updated before its packet ran lost the current valid entries");
}
void testRegisterListReadAgainAtExecution() {
alignas(8) static std::uint32_t gate = 0;
static std::uint32_t done = 0;
static std::array<std::uint32_t, 2> registers{0x038f038fu, 74};
auto words = joinPackets({
makePacket(0x3c, {0x13, low(&gate), high(&gate), 1, 0xffffffffu, 0x19}),
makePacket(0x9f, {low(registers.data()), high(registers.data()), 0x80000000, 1}),
writeWord(done, 1)});
submitWords(words);
registers[0] = 0x10;
std::atomic_ref<std::uint32_t>(gate).store(1);
AgcDriverWaitIdle_nid_postfix();
check(std::atomic_ref<std::uint32_t>(done).load() == 1, "a register list invalid at submission and valid at execution was not read again");
}
void testIndexAttributesReadAtExecution() {
alignas(8) static std::uint32_t gate = 0;
static std::uint32_t done = 0;
@@ -1854,6 +1903,9 @@ int main(int argc, char** argv) {
testUnwrittenUserData();
testDriverSubmission();
testRegisterListsReadAtSubmission();
testInvalidRegisterListEntries();
testPartlyUpdatedRegisterListSkipsOnlyItsInvalidEntries();
testRegisterListReadAgainAtExecution();
testIndexAttributesReadAtExecution();
testPredicatedSubmission();
testConditionalSubmission();
@@ -8361,6 +8361,70 @@ void singleCubeTests(const Device& device, Recorder& recorder) {
}
}
void cubeExtraSliceTests(const Device& device, Recorder& recorder) {
auto context = device.GetContext();
TextureDetiler detiler(context);
context.detiler = &detiler;
SampleProgram program(context, recorder, SAMPLE_ARRAY_SPV);
const VkComponentMapping identity{};
std::array<std::uint32_t, 8> words{0x1000u, (56u << 20u) | (3u << 30u), 15u | (63u << 14u), 0xb0000facu, 6u, 0, 0, 0};
auto resource = DecodeTextureResource(words);
const auto geometry = DescribeSurface(resource);
Require(geometry.imageLayers == 7u, "a cube with a slice past its last whole cube does not allocate it");
std::vector<std::byte> memory(static_cast<std::size_t>(geometry.guestBytes) + 256);
auto* surface = reinterpret_cast<std::byte*>((reinterpret_cast<std::uintptr_t>(memory.data()) + 255) & ~std::uintptr_t{255});
for (std::uint32_t layer = 0; layer < geometry.imageLayers; ++layer) {
std::memset(surface + geometry.GuestLayerOffset(layer), 16 * (layer + 1), static_cast<std::size_t>(geometry.layerBytes));
}
resource.baseAddress = reinterpret_cast<std::uint64_t>(surface);
const std::span<const std::byte> snapshot(surface, static_cast<std::size_t>(geometry.guestBytes));
Texture texture(context, detiler, resource, identity, snapshot);
auto storage = StorageTexture::Create(context, detiler, resource, 0);
recorder.Keep(storage);
Texture storageView(context, storage, resource, identity);
const auto wordCube = [&words](std::vector<std::byte>& backing, std::uint32_t format, const std::function<std::uint32_t(std::uint32_t)>& word) {
auto cubeWords = words;
cubeWords[1] = (format << 20u) | (3u << 30u);
auto described = DecodeTextureResource(cubeWords);
const auto layout = DescribeSurface(described);
backing.assign(static_cast<std::size_t>(layout.guestBytes) + 256, std::byte{});
auto* base = reinterpret_cast<std::byte*>((reinterpret_cast<std::uintptr_t>(backing.data()) + 255) & ~std::uintptr_t{255});
for (std::uint32_t layer = 0; layer < layout.imageLayers; ++layer) {
const auto value = word(layer);
for (std::uint64_t offset = 0; offset < layout.layerBytes; offset += sizeof(value)) std::memcpy(base + layout.GuestLayerOffset(layer) + offset, &value, sizeof(value));
}
described.baseAddress = reinterpret_cast<std::uint64_t>(base);
return described;
};
std::vector<std::byte> depthMemory;
const auto depth = wordCube(depthMemory, 22u, [](std::uint32_t layer) {
const float value = static_cast<float>(layer + 1) / 8.0f;
std::uint32_t bits = 0;
std::memcpy(&bits, &value, sizeof(bits));
return bits;
});
auto depthStorage = StorageTexture::Create(context, detiler, depth, 0);
recorder.Keep(depthStorage);
Require(Texture::CanDepthCopyFrom(*depthStorage, depth), "a cube with a slice past its last whole cube cannot take a depth copy");
Texture depthCopy(context, depthStorage, depth, identity, Texture::DepthCopyTag{});
depthCopy.RefreshDepthCopy();
constexpr std::array<std::uint32_t, 7> unsignedFloats{960u, 1024u, 1056u, 1088u, 1104u, 1120u, 1136u};
std::vector<std::byte> repackMemory;
const auto repacked = wordCube(repackMemory, 43u, [&unsignedFloats](std::uint32_t layer) { return unsignedFloats[layer] << 21u; });
auto repackStorage = StorageTexture::Create(context, detiler, repacked, 0);
recorder.Keep(repackStorage);
Require(Texture::CanRepackCopyFrom(*repackStorage, repacked), "a cube with a slice past its last whole cube cannot take a repacked copy");
Texture repackCopy(context, repackStorage, repacked, identity, Texture::RepackCopyTag{});
repackCopy.RefreshDepthCopy();
for (std::uint32_t layer = 0; layer < geometry.imageLayers; ++layer) {
const auto expected = 16.0f * (layer + 1) / 255.0f;
expectRed(program.Red(texture.View(), texture.Layout(), 0, static_cast<float>(layer)), expected, "a cube snapshot with a slice past its last whole cube sampled the wrong slice");
expectRed(program.Red(storageView.View(), storageView.Layout(), 0, static_cast<float>(layer)), expected, "a cube storage view with a slice past its last whole cube sampled the wrong slice");
expectRed(program.Red(depthCopy.View(), depthCopy.Layout(), 0, static_cast<float>(layer)), static_cast<float>(layer + 1) / 8.0f, "a cube depth copy with a slice past its last whole cube sampled the wrong slice");
expectRed(program.Red(repackCopy.View(), repackCopy.Layout(), 0, static_cast<float>(layer)), static_cast<float>(layer + 1), "a cube repacked copy with a slice past its last whole cube sampled the wrong slice");
}
}
struct SampledViewProbe {
Context& context;
PFN_vkGetDeviceProcAddr resolver;
@@ -9711,6 +9775,7 @@ int main(int argc, char** argv) {
}
if (argc == 2 && std::string_view(argv[1]) == "--cube-only") {
singleCubeTests(device, recorder);
cubeExtraSliceTests(device, recorder);
std::cout << "Single cube snapshot and storage sampling tests passed\n";
return 0;
}
@@ -9986,6 +10051,7 @@ int main(int argc, char** argv) {
firstLayerViewTests(device, recorder);
sampledStorageBaseLevelUsageTests(device, recorder);
singleCubeTests(device, recorder);
cubeExtraSliceTests(device, recorder);
atomicViewTests(device, recorder);
metadataPassTests(device, recorder);
cmaskPassTests(device, recorder);
@@ -41,6 +41,9 @@
#define WIN32_LEAN_AND_MEAN
#endif
#include <windows.h>
#include <io.h>
#include <fcntl.h>
#include "prx/libc/include/General.hpp"
#endif
namespace {
@@ -59,6 +62,40 @@ void setEnvironment(const char* name, const std::string& value) {
#endif
}
#ifdef _WIN32
void verifyHostStandardError() {
const auto directory = std::filesystem::temp_directory_path() / ("aps5-shader-host-stderr-" + std::to_string(std::random_device{}()));
setEnvironment("ANYPS5_NO_SHADER_CACHE", "0");
setEnvironment("ANYPS5_SHADER_CACHE_DIR", directory.string());
int capture[2], guest[2];
require(::_pipe(capture, 65536, _O_BINARY) == 0, "cannot create host capture");
require(::_pipe(guest, 65536, _O_BINARY) == 0, "cannot create guest capture");
const int original = ::_dup(2);
require(original >= 0 && ::_dup2(capture[1], 2) == 0, "cannot capture initial stderr");
const int saved = ::_dup(2);
require(saved >= 0 && AdoptHostStandardStream_nid_no_patch(2, saved), "cannot preserve host stderr");
require(::_dup2(guest[1], 2) == 0, "cannot redirect guest stderr");
require(ShaderDiskCache::Enabled(), "shader disk cache was disabled");
ShaderDiskCache::Shutdown();
std::fflush(stderr);
std::fflush(HostStandardErrorFile_nid_no_patch());
require(::_dup2(original, 2) == 0, "cannot restore stderr");
::_close(original);
DWORD hostBytes = 0, guestBytes = 0;
require(::PeekNamedPipe(reinterpret_cast<HANDLE>(::_get_osfhandle(capture[0])), nullptr, 0, nullptr, &hostBytes, nullptr) != 0, "cannot inspect host capture");
require(::PeekNamedPipe(reinterpret_cast<HANDLE>(::_get_osfhandle(guest[0])), nullptr, 0, nullptr, &guestBytes, nullptr) != 0, "cannot inspect guest capture");
std::string host(hostBytes, '\0');
if (hostBytes != 0) require(::_read(capture[0], host.data(), hostBytes) == static_cast<int>(hostBytes), "cannot read host capture");
::_close(capture[0]);
::_close(capture[1]);
::_close(guest[0]);
::_close(guest[1]);
std::filesystem::remove_all(directory);
require(guestBytes == 0, "shader cache diagnostic entered the guest stderr pipe");
require(host.find("[shader-disk-cache]") != std::string::npos, "shader cache diagnostic did not reach host stderr");
}
#endif
void removeCacheDirectory(const std::filesystem::path& directory) {
std::error_code error;
#ifdef _WIN32
@@ -793,6 +830,13 @@ int runLargeFileWrite() {
std::error_code error;
std::filesystem::remove_all(directory, error);
std::filesystem::create_directories(directory, error);
struct Cleanup {
std::filesystem::path directory;
~Cleanup() {
std::error_code error;
std::filesystem::remove_all(directory, error);
}
} cleanup{directory};
const auto space = std::filesystem::space(directory, error);
if (error || space.available < size + (std::uintmax_t{1} << 30u)) {
std::cout << "skipping: not enough free disk space for a file of more than 2 GiB\n";
@@ -819,6 +863,8 @@ int runLargeFileWrite() {
}).detach();
if (result.wait_for(std::chrono::seconds(200)) != std::future_status::ready) {
std::cerr << "WriteFileAtomically did not return within 200 s for a file of more than 2 GiB\n";
std::filesystem::remove_all(directory, error);
if (error) std::cerr << "cannot remove the large file test directory: " << error.message() << '\n';
std::cerr.flush();
std::_Exit(1);
}
@@ -2378,6 +2424,10 @@ int main(int argc, char** argv) {
return 0;
}
#ifdef _WIN32
if (argc == 2 && std::string_view(argv[1]) == "--host-stderr") {
verifyHostStandardError();
return 0;
}
if (argc == 2 && std::string_view(argv[1]) == "--long-host-paths") {
verifyLongHostPaths();
return 0;
@@ -850,7 +850,8 @@ void verifyOutOfRangeConstantReads() {
require(plan->info.images.size() == 2u && plan->info.samplers.size() == 1u, "out-of-range s_buffer_load: the two T#s were not planned as one image each");
AgcDriver::ShaderMemory memory({});
const auto capture = memory.Capture(request);
request.context.memory = memory.Regions();
const auto memoryRegions = memory.Regions();
request.context.memory = memoryRegions;
static_cast<void>(Recompile(request, *capture));
const auto holds = [&](const std::array<std::uint32_t, 8>& words) {
return std::ranges::any_of(capture->snapshot.images, [&](const DescriptorValue& value) {
@@ -1025,8 +1026,10 @@ void verifyGuardedDescriptorSources() {
fill(valid, mode);
AgcDriver::ShaderMemory memory({});
const auto capture = memory.Capture(storeRequest);
storeRequest.context.memory = memory.Regions();
return Recompile(storeRequest, *capture);
const auto memoryRegions = memory.Regions();
auto compileRequest = storeRequest;
compileRequest.context.memory = memoryRegions;
return Recompile(compileRequest, *capture);
};
const auto storePlan = GetResourcePlan(storeRequest);
require(storePlan->info.images.size() == 2u && storePlan->descriptorSources.size() == 1u, "guarded images: the two stores were not planned as two images of one T#");
@@ -3614,7 +3617,8 @@ void verifyDescriptorPhis() {
require(plan->info.images.size() == images && plan->info.samplers.size() == samplers && plan->info.sampledPairs.size() == 2u, "descriptor Phi: the edges were not given one resource each");
AgcDriver::ShaderMemory memory({});
const auto capture = memory.Capture(request);
request.context.memory = memory.Regions();
const auto memoryRegions = memory.Regions();
request.context.memory = memoryRegions;
const auto compiled = Recompile(request, *capture);
require(countOps(compiled->spirv, OpImageSampleExplicitLod) == 2u, "descriptor Phi: the specialized SPIR-V does not sample once per edge");
#if ANYPS5_ENABLE_SPIRV_TOOLS
@@ -3645,7 +3649,8 @@ void verifyDescriptorPhis() {
auto twoLane = makeRequest(samplerCode, 64u);
AgcDriver::ShaderMemory twoLaneMemory({});
const auto twoLaneCapture = twoLaneMemory.Capture(twoLane);
twoLane.context.memory = twoLaneMemory.Regions();
const auto twoLaneRegions = twoLaneMemory.Regions();
twoLane.context.memory = twoLaneRegions;
require(countOps(Recompile(twoLane, *twoLaneCapture)->spirv, OpImageSampleExplicitLod) == 4u, "descriptor Phi: the specialized two-lane SPIR-V does not sample once per edge and half");
auto dynamic = makeRequest(dynamicCode);
@@ -3667,7 +3672,8 @@ void verifyDescriptorPhis() {
require(sequentialPlan->info.images.size() == 2u && sequentialPlan->info.samplers.size() == 2u, "descriptor Phi: a T# and an S# chosen in separate blocks were not split into their SRT descriptors");
AgcDriver::ShaderMemory sequentialMemory({});
const auto sequentialCapture = sequentialMemory.Capture(sequential);
sequential.context.memory = sequentialMemory.Regions();
const auto sequentialRegions = sequentialMemory.Regions();
sequential.context.memory = sequentialRegions;
static_cast<void>(Recompile(sequential, *sequentialCapture));
const auto planRestoredSampler = [&](std::span<const std::uint32_t> code, const char* message) {
@@ -3676,7 +3682,8 @@ void verifyDescriptorPhis() {
require(plan->info.samplers.size() == 1u && plan->info.sampledPairs.size() == 1u, message);
AgcDriver::ShaderMemory memory({});
const auto capture = memory.Capture(request);
request.context.memory = memory.Regions();
const auto memoryRegions = memory.Regions();
request.context.memory = memoryRegions;
static_cast<void>(Recompile(request, *capture));
};
std::vector<std::uint32_t> restoredSamplerCode{0xf40c0200u, 0xfa000000u, 0xf4080500u, 0xfa000020u, 0xf4080600u, 0xfa000040u, 0xbe930380u, 0xbf8cc07fu, 0xf09c0f08u, 0x00a20400u, 0xbe900380u, 0xbe910380u, 0xbe9c0414u, 0xbe9e0416u, 0xbf068011u, 0xbf850001u, 0x80128112u, 0xbe94041cu, 0xbe96041eu, 0x80118111u, 0xbf0a8211u, 0xbf85fff6u, 0xf09c0f08u, 0x00a20000u, 0x80108110u, 0xbf0a8210u, 0xbf85fff0u, 0x80138113u, 0xbf0a8213u, 0xbf85ffecu, 0xbf8c3f70u, 0xe0700000u, 0x80060000u, 0xe0700000u, 0x80060400u, 0x7d880090u, 0xbea23c6au, 0x4a020081u, 0xe0700000u, 0x80060100u, 0xbf810000u};
@@ -3850,7 +3857,8 @@ void verifyLaneSelectedDescriptors() {
require(plan->info.images.size() == 2u && plan->info.samplers.size() == 1u, "lane-selected T#: the T#s selected per lane were not planned as one image each");
AgcDriver::ShaderMemory memory({});
const auto capture = memory.Capture(request);
request.context.memory = memory.Regions();
const auto memoryRegions = memory.Regions();
request.context.memory = memoryRegions;
static_cast<void>(Recompile(request, *capture));
const auto holds = [&](const std::array<std::uint32_t, 8>& words) {
return std::ranges::any_of(capture->snapshot.images, [&](const DescriptorValue& value) {
@@ -3896,7 +3904,8 @@ void verifyProgramCounterRelativeData() {
};
AgcDriver::ShaderMemory memory({});
static_cast<void>(memory.Capture(request));
request.context.memory = memory.Regions();
const auto memoryRegions = memory.Regions();
request.context.memory = memoryRegions;
const auto first = Recompile(request);
require(dataBase(first) == codeAddress + 56u, "program counter data: the V# does not name the data at the shader's address");
auto relocated = request;
@@ -4839,7 +4848,7 @@ void verifyPixelRequestSerialization() {
minimal.context.pixel->layerExported = true;
minimal.context.pixel->ditheredAlphaToCoverage = true;
const auto encoded = serializer.Serialize(minimal);
require(requestPrefix(encoded, 8u) == "NVNQQSYAAAA=", "new requests did not use serialization version 38");
require(requestPrefix(encoded, 8u) == "NVNQQScAAAA=", "new requests did not use serialization version 39");
const std::array<std::uint32_t, 2> fallbacks{0x40u, 0x1ac4u};
auto withFallbacks = minimal;
withFallbacks.context.linkedCallFallbacks = fallbacks;
@@ -4957,7 +4966,7 @@ void verifyPixelRequestSerialization() {
for (std::size_t bytes = 0; bytes < 45u; ++bytes) {
expectFailure([&] { static_cast<void>(serializer.Deserialize(requestPrefix(encoded, mappingOffset + bytes))); }, "truncated data", "a truncated pixel mapping, packing, dual-source, alpha-to-coverage, stencil reference export flag, primitive class, layer export, dither or front-face flag, quad pixel mask or quad sample mask was accepted");
}
for (const auto unsupported : {"NVNQQQAAAAA=", "NVNQQScAAAA="}) {
for (const auto unsupported : {"NVNQQQAAAAA=", "NVNQQSgAAAA="}) {
expectFailure([&] { static_cast<void>(serializer.Deserialize(unsupported)); }, "serialization version", "an unsupported request version was accepted");
}
static constexpr std::string_view version18Request{
@@ -5243,6 +5252,43 @@ void verifyDepthRemapRequests() {
require(key != other, "the depth remap mode did not change the shader cache key");
}
void verifyMergedHullConfiguration() {
using namespace ShaderRecompiler;
static constexpr std::array<std::uint32_t, 1> code{0xbf810000u};
const RequestSerializer serializer;
RecompileRequest request{};
request.shader = {ShaderStage::TessellationControl, 0x10000u, code, 0, {}};
request.context.waveSize = 64;
request.context.vertex = ShaderVertexStageInfo{};
request.graphics = GraphicsCompileContext{0u, {}, std::nullopt, TessellationConfiguration{3u, 3u, 1u, 2u, 2u}, {}};
const auto separate = serializer.Serialize(request);
std::vector<std::uint64_t> separateKey;
RecompileCacheKey::Build(request, separateKey);
request.graphics->tessellation->mergedHull = true;
const auto merged = serializer.Serialize(request);
std::vector<std::uint64_t> mergedKey;
RecompileCacheKey::Build(request, mergedKey);
const auto replay = serializer.Deserialize(merged);
require(replay.request.graphics.has_value() && replay.request.graphics->tessellation.has_value() && replay.request.graphics->tessellation->mergedHull, "a merged hull configuration was lost in serialization");
require(!serializer.Deserialize(separate).request.graphics->tessellation->mergedHull, "a separate hull configuration was read as merged");
auto legacy = decodeBase64(merged);
require(legacy.size() > 52u && legacy[legacy.size() - 52u] == 1u, "the merged hull flag is not the last byte of the tessellation configuration");
legacy.erase(legacy.end() - 52);
for (const auto version : {30u, 31u, 32u, 34u, 35u, 36u, 37u, 38u}) {
auto older = legacy;
older[4] = static_cast<std::uint8_t>(version);
if (version < 38u) older.erase(older.end() - 31);
if (version < 36u) older.pop_back();
if (version < 35u) older.resize(older.size() - 5u);
if (version < 32u) older.pop_back();
const auto upgraded = serializer.Deserialize(encodeBase64(older));
const auto what = "a main version " + std::to_string(version) + " tessellation request";
require(upgraded.request.graphics.has_value() && upgraded.request.graphics->tessellation.has_value() && !upgraded.request.graphics->tessellation->mergedHull, (what + " read a merged hull flag").c_str());
require(serializer.Serialize(upgraded.request) == separate, (what + " misread the fields after its tessellation configuration").c_str());
}
require(mergedKey != separateKey && RecompileCacheKey::ContextHash(request) != RecompileCacheKey::ContextHash(serializer.Deserialize(separate).request), "the cache keys ignore whether the hull program is merged");
}
void verifyLegacyPixelRequests() {
using namespace ShaderRecompiler;
static constexpr std::array<std::string_view, 14> legacyPixelRequests{
@@ -5570,7 +5616,8 @@ void verifyComputedTexelOffsets() {
request.useCache = false;
AgcDriver::ShaderMemory memory({});
static_cast<void>(memory.Capture(request));
request.context.memory = memory.Regions();
const auto memoryRegions = memory.Regions();
request.context.memory = memoryRegions;
return Recompile(request).spirv;
};
const auto sampleOperands = [](std::span<const std::uint32_t> words) {
@@ -5620,7 +5667,8 @@ void verifyShaderClockScopes() {
request.useCache = false;
AgcDriver::ShaderMemory memory({});
static_cast<void>(memory.Capture(request));
request.context.memory = memory.Regions();
const auto memoryRegions = memory.Regions();
request.context.memory = memoryRegions;
const auto words = Recompile(request).spirv;
std::map<std::uint32_t, std::uint32_t> constants;
std::vector<std::uint32_t> scopeIds;
@@ -5934,7 +5982,8 @@ void verifyUnnormalizedSamplers() {
request.layout.pushConstantSizeBytes = 128;
AgcDriver::ShaderMemory memory({});
static_cast<void>(memory.Capture(request));
request.context.memory = memory.Regions();
const auto memoryRegions = memory.Regions();
request.context.memory = memoryRegions;
return Recompile(request);
};
const auto flags = [](const RecompileResult& result, DescriptorRole role) {
@@ -6357,7 +6406,8 @@ void verifyTwoLaneUniformValues() {
request.layout.pushConstantSizeBytes = 128;
AgcDriver::ShaderMemory memory({});
const auto capture = memory.Capture(request);
request.context.memory = memory.Regions();
const auto memoryRegions = memory.Regions();
request.context.memory = memoryRegions;
const auto words = Recompile(request, *capture)->spirv;
std::size_t count = 0;
for (std::size_t cursor = 5; cursor < words.size();) {
@@ -6938,7 +6988,8 @@ void verifyGuardedNullPointers() {
const auto valid = validMemory.Capture(branch.request);
require(valid->snapshot.srtPoison.empty() && valid->plan->guardedSrtSlots.size() == 1u && slotPc(*valid->plan, valid->plan->guardedSrtSlots[0]) == 0x2cu && GuardRecords(*valid), "guarded pointer: a mapped nested pointer was poisoned, or its read has no guard");
require(valid->snapshot.flattenedSrt.at(valid->plan->guardedSrtSlots[0]) == payload, "guarded pointer: a mapped nested read does not hold its word");
branch.request.context.memory = validMemory.Regions();
const auto validRegions = validMemory.Regions();
branch.request.context.memory = validRegions;
const auto validResult = Recompile(branch.request, *valid);
require(validResult->poisonedSrtReads == 0u && faultBinding(*validResult) && validResult->bdaAbiVersion == BdaAbi::Version, "guarded pointer: a guarded program has no fault buffer");
@@ -6980,20 +7031,23 @@ void verifyGuardedNullPointers() {
const bool throughPointer = own == 0x38u && poison.address == unmapped;
require(poison.pc == 0x2cu && (pointerWord || throughPointer), "guarded pointer: a derived poison does not name the inaccessible read");
}
chain.request.context.memory = chainMemory.Regions();
const auto chainRegions = chainMemory.Regions();
chain.request.context.memory = chainRegions;
const auto chainResult = Recompile(chain.request, *chained);
indirect = 0;
point(static_cast<std::uint64_t>(reinterpret_cast<std::uintptr_t>(&indirect)));
AgcDriver::ShaderMemory nullChainMemory({});
const auto nullChain = nullChainMemory.Capture(chain.request);
require(nullChain->snapshot.srtPoison.size() == 1u && nullChain->snapshot.srtPoison[0].pc == 0x38u && nullChain->snapshot.srtPoison[0].address == 0u && GuardRecords(*nullChain), "guarded pointer: a null pointer read from memory did not poison the read through it");
chain.request.context.memory = nullChainMemory.Regions();
const auto nullChainRegions = nullChainMemory.Regions();
chain.request.context.memory = nullChainRegions;
const auto nullChainResult = Recompile(chain.request, *nullChain);
indirect = payloadAddress;
AgcDriver::ShaderMemory mappedChainMemory({});
const auto mappedChain = mappedChainMemory.Capture(chain.request);
require(mappedChain->snapshot.srtPoison.empty() && mappedChain->plan->guardedSrtSlots.size() == 3u && GuardRecords(*mappedChain), "guarded pointer: a mapped pointer chain was poisoned, or its reads have no guards");
chain.request.context.memory = mappedChainMemory.Regions();
const auto mappedChainRegions = mappedChainMemory.Regions();
chain.request.context.memory = mappedChainRegions;
const auto mappedChainResult = Recompile(chain.request, *mappedChain);
require(chainResult->poisonedSrtReads == 3u && nullChainResult->poisonedSrtReads == 1u && mappedChainResult->poisonedSrtReads == 0u, "guarded pointer: a chain result does not count its poisoned reads");
require(samePipeline(*chainResult, *mappedChainResult) && samePipeline(*nullChainResult, *mappedChainResult), "guarded pointer: a chain's poison changed the variant, the specialization or the module");
@@ -7016,7 +7070,8 @@ void verifyGuardedNullPointers() {
AgcDriver::ShaderMemory zeroMemory({});
const auto zeroCapture = zeroMemory.Capture(descriptor.request);
require(zeroCapture->snapshot.srtPoison.empty() && zeroBuffer(*zeroCapture) && GuardRecords(*zeroCapture), "guarded pointer: a zero V# read from mapped memory was poisoned or not read");
descriptor.request.context.memory = zeroMemory.Regions();
const auto zeroRegions = zeroMemory.Regions();
descriptor.request.context.memory = zeroRegions;
const auto zeroResult = Recompile(descriptor.request, *zeroCapture);
for (const auto pointer : {std::uint64_t{0}, unmapped}) {
point(pointer);
@@ -7072,7 +7127,8 @@ void verifyGuardedNullPointers() {
const auto rootlessCapture = rootlessMemory.Capture(rootless.request);
require(rootlessCapture->snapshot.nullRootReads != 0u && GuardRecords(*rootlessCapture), "guarded pointer: reads through a null user-data pointer were not counted");
require(std::all_of(rootlessCapture->snapshot.buffers.begin(), rootlessCapture->snapshot.buffers.end(), [](const DescriptorValue& value) { return std::all_of(value.dwords.begin(), value.dwords.end(), [](std::uint32_t word) { return word == 0u; }); }), "guarded pointer: a V# read through a null user-data pointer is not zero");
rootless.request.context.memory = rootlessMemory.Regions();
const auto rootlessRegions = rootlessMemory.Regions();
rootless.request.context.memory = rootlessRegions;
const auto rootlessResult = Recompile(rootless.request, *rootlessCapture);
require(rootlessResult->poisonedSrtReads >= rootlessCapture->snapshot.nullRootReads, "guarded pointer: a capture through a null user-data pointer is cacheable");
point(0u);
@@ -7412,6 +7468,7 @@ int main(int argc, char** argv) {
verifyPixelRequestSerialization();
verifyVertexInfoVersion19();
verifyDepthRemapRequests();
verifyMergedHullConfiguration();
verifyPositionExportComponents();
verifyLegacyPixelRequests();
verifyPixelExportReplay();
@@ -7613,7 +7670,8 @@ int main(int argc, char** argv) {
const auto changedCapture = changedMemory.Capture(request);
auto changed = updated;
changed.useCache = true;
changed.context.memory = changedMemory.Regions();
const auto changedRegions = changedMemory.Regions();
changed.context.memory = changedRegions;
auto before = updated;
before.useCache = true;
before.context.memory = updatedRegions;
@@ -7676,7 +7734,8 @@ int main(int argc, char** argv) {
AgcDriver::ShaderMemory nullRoot({});
const auto nullRootCapture = nullRoot.Capture(request);
require(nullRootCapture->snapshot.nullRootReads != 0u, "reads through a null user-data pointer were not counted");
request.context.memory = nullRoot.Regions();
const auto nullRootRegions = nullRoot.Regions();
request.context.memory = nullRootRegions;
require(Recompile(request, *nullRootCapture)->poisonedSrtReads >= nullRootCapture->snapshot.nullRootReads, "a capture through a null user-data pointer is cacheable");
std::cout << "Shader memory capture, strict validation and deterministic replay passed\n";
return 0;
@@ -0,0 +1,75 @@
#include "IntermediateRepresentation/IrProgram.hpp"
#include "Optimization/ShaderStageInputInfo.hpp"
#include "SpirvBackend/SpirvEmitter.hpp"
#include "PipelineSpecialization.hpp"
#include <cstdint>
#include <cstdio>
#include <exception>
#include <stdexcept>
#include <string>
#include <string_view>
namespace {
using namespace ShaderRecompiler;
void require(bool condition, const std::string& message) {
if (!condition) throw std::runtime_error(message);
}
enum class Failure { MissingComputeInput, UnloweredInstruction };
std::string emissionFailure(Failure failure) {
IrProgram program;
program.SetWaveSize(32);
program.Resources().stage = IrShaderStage::Compute;
program.Resources().shaderHash = 0x1173ae1000ull;
program.Resources().srtPlanComplete = true;
program.Resources().resourceTrackingComplete = true;
program.Metadata().shaderInfoComplete = true;
program.Metadata().bindingLayoutComplete = true;
auto& block = program.CreateBlock();
program.SetEntryBlock(block);
program.BlockOrder().push_back(&block);
program.Metadata().blockInfo.emplace_back();
if (failure == Failure::UnloweredInstruction) block.AppendInstruction(&program.CreateValue(IrOpcode::Unreachable, IrType::Void));
ShaderComputeInputInfo compute;
compute.threadsNum[0] = 32;
compute.threadsNum[1] = 1;
compute.threadsNum[2] = 1;
compute.hostSubgroupSize = 32;
compute.waveSize = 32;
ShaderStageInputInfo inputs;
if (failure != Failure::MissingComputeInput) inputs.compute = &compute;
CompiledBindingLayout bindings;
bindings.layout = program.Metadata().bindings;
SpirvTargetOptions target{};
target.vulkanVersion = 0x00401000;
target.spirvVersion = 0x00010300;
target.subgroupSize = 32;
try {
static_cast<void>(SpirvEmitter().Emit(program, inputs, bindings, target));
} catch (const std::runtime_error& error) {
return error.what();
}
throw std::runtime_error("the program emitted without a failure");
}
void requireFailure(Failure failure, std::string_view prefix, std::string_view reason) {
const auto text = emissionFailure(failure);
require(text.starts_with(prefix) && text.find(reason) != std::string::npos, "expected \"" + std::string(prefix) + "...\" with \"" + std::string(reason) + "\", got \"" + text + "\"");
}
}
int main() {
try {
requireFailure(Failure::MissingComputeInput, "SPIR-V emission failed: hash=0x1173ae1000 stage=", " reason=compute input info is missing");
requireFailure(Failure::UnloweredInstruction, "SPIR-V emission failed: hash=0x1173ae1000 stage=", " opcode=Unreachable reason=");
} catch (const std::exception& error) {
std::fprintf(stderr, "SpirvFailureText: %s\n", error.what());
return 1;
}
std::puts("SpirvFailureText: emission failures name the shader hash in hex");
return 0;
}
@@ -0,0 +1,172 @@
#define SPV_ENABLE_UTILITY_CODE
#include <spirv/unified1/spirv.hpp>
#undef SPV_ENABLE_UTILITY_CODE
#include "Recompiler.hpp"
#include "SubvectorLoopFixture.hpp"
#include <array>
#include <cstdint>
#include <cstdio>
#include <exception>
#include <map>
#include <optional>
#include <span>
#include <stdexcept>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
namespace {
using namespace ShaderRecompiler;
void require(bool condition, const std::string& message) {
if (!condition) throw std::runtime_error(message);
}
std::vector<std::uint32_t> recompile(bool robustBufferAccess2) {
const std::array<std::uint32_t, 8> userData{0x10000000u, 0u, 0x100u, 0x31016facu, 0x10001000u, 0u, 0x900u, 0x31016facu};
const std::array<std::string_view, 1> extensions{"SPV_KHR_storage_buffer_storage_class"};
const std::array<std::uint32_t, 3> capabilities{spv::CapabilityShader, spv::CapabilityGroupNonUniform, spv::CapabilityGroupNonUniformBallot};
RecompileRequest request{};
request.shader = {ShaderStage::Compute, 0x20000u, SubvectorLoopFixture::ExecKeptCode, 0, {}};
request.context.waveSize = 64;
request.context.userDataBaseRegister = 0;
request.context.userData = userData;
request.context.compute = ShaderComputeStageInfo{{64u, 1u, 1u}, 0u, {false, false, false}, false, 1u};
request.target.vulkanVersion = 0x00403000u;
request.target.spirvVersion = 0x00010600u;
request.target.subgroupSize = 64;
request.target.bdaAbiVersion = 1;
request.target.supportedCapabilities = capabilities;
request.target.supportedExtensions = extensions;
request.target.maxWorkgroupSize = {1024u, 1024u, 64u};
request.target.maxWorkgroupInvocations = 1024;
request.target.maxWorkgroupSharedMemoryBytes = 49152;
request.target.robustBufferAccess2 = robustBufferAccess2;
request.layout = {0, 0, 0, 128};
request.useCache = false;
auto result = Recompile(request);
require(!result.spirv.empty(), "the shader did not recompile");
return result.spirv.Copy();
}
enum class EmptyExecTest {
Other,
OneCompare,
TwoCompares
};
class BranchConditions {
public:
explicit BranchConditions(std::vector<std::uint32_t> module) : words(std::move(module)) {
require(words.size() >= 5, "the SPIR-V header is missing");
for (std::size_t offset = 5; offset < words.size();) {
const auto count = words[offset] >> spv::WordCountShift;
require(count != 0 && count <= words.size() - offset, "a SPIR-V instruction is truncated");
const auto opcode = static_cast<spv::Op>(words[offset] & spv::OpCodeMask);
bool result = false;
bool type = false;
spv::HasResultAndType(opcode, &result, &type);
if (result) definitions.emplace(words[offset + (type ? 2 : 1)], offset);
if (opcode == spv::OpBranchConditional) conditions.push_back(words[offset + 1]);
offset += count;
}
}
std::vector<EmptyExecTest> Tests() const {
std::vector<EmptyExecTest> tests;
for (const auto condition : conditions) tests.push_back(classify(condition));
return tests;
}
private:
std::vector<std::uint32_t> words;
std::vector<std::uint32_t> conditions;
std::map<std::uint32_t, std::size_t> definitions;
std::span<const std::uint32_t> instruction(std::uint32_t id, spv::Op opcode, std::size_t count) const {
const auto found = definitions.find(id);
if (found == definitions.end()) return {};
const auto offset = found->second;
if ((words[offset] & spv::OpCodeMask) != static_cast<std::uint32_t>(opcode) || (words[offset] >> spv::WordCountShift) != count) return {};
return std::span<const std::uint32_t>(words).subspan(offset, count);
}
bool zero(std::uint32_t id) const {
const auto constant = instruction(id, spv::OpConstant, 4);
return !constant.empty() && constant[3] == 0u;
}
std::optional<std::uint32_t> comparedWithZero(std::uint32_t id) const {
const auto compare = instruction(id, spv::OpIEqual, 5);
if (compare.empty()) return std::nullopt;
if (zero(compare[4])) return compare[3];
if (zero(compare[3])) return compare[4];
return std::nullopt;
}
std::optional<std::pair<std::uint32_t, std::uint32_t>> execWord(std::uint32_t id) const {
const auto extract = instruction(id, spv::OpCompositeExtract, 5);
if (extract.empty() || instruction(extract[3], spv::OpGroupNonUniformBallot, 5).empty()) return std::nullopt;
return std::pair{extract[3], extract[4]};
}
bool execWords(std::uint32_t first, std::uint32_t second) const {
const auto low = execWord(first);
const auto high = execWord(second);
return low && high && low->first == high->first && low->second <= 1u && high->second <= 1u && low->second != high->second;
}
EmptyExecTest classify(std::uint32_t condition) const {
if (const auto negation = instruction(condition, spv::OpLogicalNot, 4); !negation.empty()) return classify(negation[3]);
if (const auto operand = comparedWithZero(condition)) {
const auto combined = instruction(*operand, spv::OpBitwiseOr, 5);
if (!combined.empty() && execWords(combined[3], combined[4])) return EmptyExecTest::OneCompare;
}
const auto both = instruction(condition, spv::OpLogicalAnd, 5);
if (!both.empty()) {
const auto first = comparedWithZero(both[3]);
const auto second = comparedWithZero(both[4]);
if (first && second && execWords(*first, *second)) return EmptyExecTest::TwoCompares;
}
return EmptyExecTest::Other;
}
};
int failures = 0;
void fail(const std::string& message) {
std::fprintf(stderr, "SpirvSubvectorLoopBegin: %s\n", message.c_str());
++failures;
}
void check(bool robustBufferAccess2) {
const std::string mode = robustBufferAccess2 ? "robustBufferAccess2 on" : "robustBufferAccess2 off";
std::uint32_t oneCompare = 0;
std::uint32_t twoCompares = 0;
for (const auto test : BranchConditions(recompile(robustBufferAccess2)).Tests()) {
if (test == EmptyExecTest::OneCompare) ++oneCompare;
if (test == EmptyExecTest::TwoCompares) ++twoCompares;
}
if (twoCompares != 0u) {
fail(mode + ": branches that compare exec_lo and exec_hi with 0 separately: " + std::to_string(twoCompares) + ", expected 0");
}
if (oneCompare != 1u) {
fail(mode + ": branches that compare (exec_lo | exec_hi) with 0: " + std::to_string(oneCompare) + ", expected 1 (s_subvector_loop_begin)");
}
}
}
int main() {
try {
check(false);
check(true);
} catch (const std::exception& error) {
fail(error.what());
}
if (failures != 0) return 1;
std::puts("SpirvSubvectorLoopBegin: s_subvector_loop_begin tests an empty exec mask as (exec_lo | exec_hi) == 0 in both robustBufferAccess2 modes");
return 0;
}
@@ -0,0 +1,21 @@
#ifndef CORE_LIBS_PRX_LIBSCEAGCDRIVER_TESTS_SUBVECTORLOOPFIXTURE_HPP
#define CORE_LIBS_PRX_LIBSCEAGCDRIVER_TESTS_SUBVECTORLOOPFIXTURE_HPP
#include <array>
#include <cstdint>
namespace SubvectorLoopFixture {
alignas(256) constexpr std::array<std::uint32_t, 49> ExecKeptCode{
0x34020082, 0xe0301000, 0x80000401, 0xbf8c3f70, 0x7e140280, 0x7e160280, 0x7e180280, 0x7e1a0280,
0x7e1c0280, 0xbe940380, 0xbea603ff, 0x5a5a5a5a, 0x7daa0880, 0xbda6000a, 0x80148114, 0x4a141481,
0x7e160214, 0x7e180226, 0xbe96047e, 0x7e1a0216, 0x7e1c0217, 0xbf098414, 0xbf850001, 0xbe26fff6,
0xbe98047e, 0xbefe04c1, 0x7e1e0226, 0x7e200218, 0x7e220219, 0x7e240214, 0xe0701000, 0x80010a01,
0xe0701100, 0x80010b01, 0xe0701200, 0x80010c01, 0xe0701300, 0x80010d01, 0xe0701400, 0x80010e01,
0xe0701500, 0x80010f01, 0xe0701600, 0x80011001, 0xe0701700, 0x80011101, 0xe0701800, 0x80011201,
0xbf810000,
};
}
#endif
@@ -20,6 +20,7 @@
#include <stdexcept>
#include <string>
#include <string_view>
#include <system_error>
#include <thread>
#include <vector>
@@ -30,6 +31,7 @@ using namespace AgcDriver::GuestMemory;
constexpr std::size_t Block = 65536;
#ifdef _WIN32
constexpr wchar_t HostHoleMode[] = L"--host-hole";
constexpr wchar_t ResidentRemapMode[] = L"--resident-direct-remap";
#endif
void Require(bool condition, const std::string& message) {
@@ -647,6 +649,82 @@ void CheckRewatch() {
GuestArena::GuestArenaRelease_nid_postfix(memory, 4 * Block);
}
void CheckResidentDirectMappingReuse() {
constexpr std::size_t bytes = 5 * Block;
auto* memory = static_cast<std::uint8_t*>(GuestArena::GuestArenaAllocate_nid_postfix(bytes, Block));
Require(memory != nullptr, "cannot reserve the resident remap fixture");
struct Cleanup {
std::uint8_t* memory;
std::array<HANDLE, 3> sections{};
~Cleanup() {
GuestArena::GuestArenaReset_nid_postfix(memory, bytes);
GuestArena::GuestArenaRelease_nid_postfix(memory, bytes);
for (const auto section : sections) {
if (section == nullptr) continue;
GuestArena::GuestArenaForgetSection_nid_postfix(section);
CloseHandle(section);
}
}
} cleanup{memory};
GuestArena::GuestArenaCommit_nid_postfix(memory, 3 * Block, PAGE_READWRITE, 3 * Block);
GuestArena::GuestArenaCommit_nid_postfix(memory + 3 * Block, 2 * Block, PAGE_READWRITE, 2 * Block);
Require(Watched(reinterpret_cast<std::uint64_t>(memory), bytes), "the resident remap fixture is not watched");
for (auto& section : cleanup.sections) {
section = CreateFileMappingW(INVALID_HANDLE_VALUE, nullptr, PAGE_EXECUTE_READWRITE, 0, 3 * Block, nullptr);
Require(section != nullptr, "cannot create the resident remap backing");
}
const auto base = reinterpret_cast<std::uint64_t>(memory);
const auto nativeType = [&](std::size_t offset) {
MEMORY_BASIC_INFORMATION info{};
Require(VirtualQuery(memory + offset, &info, sizeof(info)) == sizeof(info), "cannot query the resident remap backing");
return info.Type;
};
for (std::size_t use = 0; use < 2; ++use) {
BumpCollectEpoch();
const auto before = CollectWrites(base, 3 * Block);
Require(before != 0, "the backing before a resident remap was not collected");
Unwatch(base + Block, Block);
Require(!Watched(base + Block, Block), "the resident remap exclusion was not established");
const auto outside = CollectWrites(base + 3 * Block, 2 * Block);
const auto mapping = GuestArena::GuestArenaCommitGeneration_nid_postfix();
GuestArena::GuestArenaMapDirect_nid_postfix(memory, 3 * Block, cleanup.sections[use], 0, PAGE_READWRITE);
Require(nativeType(0) == MEM_PRIVATE && nativeType(2 * Block) == MEM_PRIVATE, "the resident remap did not create private memory");
Require(Watched(base, 3 * Block), "a resident direct remap retained an old import exclusion");
Require(GuestArena::GuestArenaCommitGeneration_nid_postfix() > mapping, "a resident direct remap reused the mapping generation");
Require(!UnchangedSince(base, Block, before) && !UnchangedSince(base + 2 * Block, Block, before), "a resident direct remap reused old neighbour stamps");
Require(UnchangedSince(base + 3 * Block, 2 * Block, outside), "a resident direct remap invalidated memory outside its range");
const auto after = CollectWritesUncached(base, 3 * Block);
Require(after > before && UnchangedSince(base, 3 * Block, after), "the resident remap could not establish a current generation");
memory[Block + 9] = static_cast<std::uint8_t>(0x51 + use);
CollectWritesUncached(base, 3 * Block);
Require(!UnchangedSince(base + Block, Block, after), "a CPU store after a resident direct remap was missed");
std::cout << "resident direct remap " << use << ": private, watched, new mapping generation, CPU store collected\n";
}
Unwatch(base, 3 * Block);
GuestArena::GuestArenaMap_nid_postfix(memory, 3 * Block, cleanup.sections[0], 0, PAGE_READWRITE);
Require(nativeType(0) == MEM_MAPPED, "the shared remap did not create mapped memory");
for (std::size_t block = 0; block < 3; ++block) Require(!Watched(base + block * Block, Block), "an ordinary shared remap restored a private import exclusion");
GuestArena::GuestArenaMapDirect_nid_postfix(memory + 3 * Block, 2 * Block, cleanup.sections[2], 0, PAGE_READWRITE);
GuestArena::GuestArenaMapDirect_nid_postfix(memory, 3 * Block, cleanup.sections[2], 0, PAGE_READWRITE);
Require(nativeType(0) == MEM_MAPPED && nativeType(Block) == MEM_MAPPED && nativeType(2 * Block) == MEM_PRIVATE, "the mixed direct remap did not establish shared and private spans");
Require(!Watched(base, Block) && !Watched(base + Block, Block) && Watched(base + 2 * Block, Block), "the mixed direct remap restored an exclusion outside its new private span");
std::cout << "resident direct remap controls: shared exclusions retained, only the new private span restored\n";
Unwatch(base, 3 * Block);
bool refused = false;
try {
GuestArena::GuestArenaMapDirect_nid_postfix(memory, 3 * Block, cleanup.sections[1], 0, 0);
} catch (const std::system_error& error) {
Require(error.code().value() == ERROR_INVALID_PARAMETER, "the failed resident remap reported an unexpected error");
refused = true;
}
std::uintptr_t createdBegin = 0;
std::uintptr_t createdEnd = 0;
Require(refused && GuestArena::GuestArenaResidentSpan_nid_postfix(base, 1, &createdBegin, &createdEnd), "the failing protection change did not leave a new private span");
Require(createdBegin == base && createdEnd <= base + 3 * Block && Watched(base, createdEnd - base), "a failed resident remap did not report its completed private span");
for (auto block = createdEnd; block < base + 3 * Block; block += Block) Require(!Watched(block, Block), "a failed resident remap restored an uncreated span");
std::cout << "resident direct remap failure: completed private span watched, original mapping error retained\n";
}
void CheckUntrackedHint() {
constexpr std::size_t bytes = 4 * Block;
auto* memory = static_cast<volatile std::uint8_t*>(GuestArena::GuestArenaAllocate_nid_postfix(bytes, Block));
@@ -1331,6 +1409,10 @@ int main() {
CheckHostHole();
return 0;
}
if (std::wstring_view(GetCommandLineW()).ends_with(ResidentRemapMode)) {
CheckResidentDirectMappingReuse();
return 0;
}
#endif
CheckSharedBlock();
CheckStoredAllSince();
@@ -629,6 +629,52 @@ void DccFastClearTests(AgcDriver::VulkanDevice& device, std::uint32_t selectedSa
}
}
}
for (const auto samples : {2u, 4u, 8u}) {
if (selectedSamples != 0 && samples != selectedSamples) continue;
blocks.push_back(std::make_unique<Block>(layout.Bytes() * samples));
auto& source = *blocks.back();
std::memset(source.data, 0, source.bytes);
blocks.push_back(std::make_unique<Block>(BlockBytes));
auto& metadata = *blocks.back();
std::memset(metadata.data, 0, BlockBytes);
auto first = DecodeTarget(source, samples);
first.dccAddress = metadata.Address();
first.dccPipeAligned = true;
const Color written{0.25f, 0.25f, 0.25f, 0.25f};
Draw(device, first, false, Float32Export, Program(written, Float32Export, 1u), false, true);
const std::vector<std::byte> ones(AgcDriver::Graphics::MultisampledDccBytes(first), std::byte{0xc0});
AgcDriver::GuestMemory::Write(metadata.Address(), ones);
auto second = first;
second.pipeBankXor = 0x4000u;
Draw(device, second, false, Float32Export, Program(written, Float32Export, 1u), false, true);
blocks.push_back(std::make_unique<Block>(layout.Bytes()));
auto& resolved = *blocks.back();
std::memset(resolved.data, 0, resolved.bytes);
AgcDriver::Graphics::ColorMetadataPass pass{};
pass.mode = AgcDriver::Graphics::ColorMetadataPass::Mode::Resolve;
pass.source = second;
pass.targets = {DecodeTarget(resolved)};
device.ColorMetadataPass(pass);
const auto stored = ReadBack(device, resolved);
for (std::uint32_t y = 0; y < Height; ++y) {
for (std::uint32_t x = 0; x < Width; ++x) {
for (std::uint32_t channel = 0; channel < 4; ++channel) {
const auto expected = x < ScissorWidth ? 1.0f + (written[channel] - 1.0f) / static_cast<float>(samples) : 1.0f;
Require(FromHalf(stored[layout.Offset(x, y) / 2u + channel]) == expected, "a multisampled DCC target recreated with another pipe-bank XOR did not start from its new fast clear");
}
}
}
auto third = second;
third.dccAlphaOnMsb = true;
bool refused = false;
try {
Draw(device, third, false, Float32Export, Program(written, Float32Export, 1u), false, true);
} catch (const std::runtime_error& error) {
refused = std::string_view(error.what()).find("layout or metadata changed") != std::string_view::npos;
}
Require(refused, "a resident multisampled DCC target accepted a use with other DCC metadata");
programCount = 0;
}
for (const auto samples : {2u, 4u, 8u}) {
if (selectedSamples != 0 && samples != selectedSamples) continue;
for (const auto mode : {0u, 1u, 2u, 3u, 4u, 5u, 6u, 7u}) {
@@ -0,0 +1,248 @@
#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp"
#include "prx/libSceAgcDriver/Graphics/include/ColorTargetLayout.hpp"
#include "prx/libSceAgcDriver/Graphics/include/MultisampleTarget.hpp"
#include "prx/libSceAgcDriver/Graphics/include/PlaceholderImages.hpp"
#include "prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp"
#include "prx/libSceAgcDriver/Graphics/include/Texture.hpp"
#include "prx/libc/include/GuestAllocations.hpp"
#include "prx/libc/include/GuestArena.hpp"
#include "prx/libc/include/GuestWriteWatch.hpp"
#include "VulkanTestDevice.hpp"
#include "ImageTableAbi.hpp"
#ifdef _WIN32
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <windows.h>
#else
#include <sys/mman.h>
#endif
#include <array>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <memory>
#include <mutex>
#include <stdexcept>
#include <string_view>
namespace AgcDriver {
struct VulkanDeviceTestAccess {
static Graphics::Context ContextOf(const VulkanDevice& device) { return device.graphicsContext(); }
};
}
namespace {
using namespace AgcDriver::Graphics;
constexpr std::uint32_t Side = 64;
constexpr std::size_t BlockBytes = 65536;
constexpr std::size_t AllocationBytes = 2 * BlockBytes;
class GuestBlock {
public:
GuestBlock() {
#ifdef _WIN32
data = GuestArena::GuestArenaAllocate_nid_postfix(AllocationBytes, BlockBytes);
if (data != nullptr) GuestArena::GuestArenaCommit_nid_postfix(data, AllocationBytes, PAGE_READWRITE, AllocationBytes);
#else
void* mapped = mmap(nullptr, AllocationBytes + BlockBytes, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (mapped != MAP_FAILED) {
const auto begin = reinterpret_cast<std::uintptr_t>(mapped);
const auto aligned = (begin + BlockBytes - 1) & ~(static_cast<std::uintptr_t>(BlockBytes) - 1);
if (aligned != begin) munmap(mapped, aligned - begin);
if (aligned + AllocationBytes != begin + AllocationBytes + BlockBytes) munmap(reinterpret_cast<void*>(aligned + AllocationBytes), begin + BlockBytes - aligned);
data = reinterpret_cast<void*>(aligned);
GuestWriteWatch::GuestWriteWatchRegister_nid_postfix(data, AllocationBytes);
}
#endif
Require(data != nullptr, "cannot allocate tracked image memory");
std::memset(data, 0, AllocationBytes);
GuestAllocations::Mutation().Add(data, AllocationBytes, true, true, true);
}
~GuestBlock() {
GuestAllocations::Mutation().Remove(data);
#ifdef _WIN32
GuestArena::GuestArenaReset_nid_postfix(data, AllocationBytes);
GuestArena::GuestArenaRelease_nid_postfix(data, AllocationBytes);
#else
GuestWriteWatch::GuestWriteWatchUnregister_nid_postfix(data, AllocationBytes);
munmap(data, AllocationBytes);
#endif
}
GuestBlock(const GuestBlock&) = delete;
GuestBlock& operator=(const GuestBlock&) = delete;
std::uint64_t Address() const { return reinterpret_cast<std::uintptr_t>(data); }
private:
void* data = nullptr;
};
template<typename TCall>
void RequireColorGuard(TCall&& call, const char* failure) {
bool guarded = false;
try {
call();
} catch (const std::runtime_error& error) {
guarded = std::string_view(error.what()).find("multisampled color target") != std::string_view::npos;
if (!guarded) throw;
}
Require(guarded, failure);
}
void CheckCachedImage(AgcDriver::VulkanDevice& device, bool sampled, bool draw) {
std::printf("checking %s %s image reuse\n", draw ? "draw" : "compute", sampled ? "sampled" : "storage");
const auto context = AgcDriver::VulkanDeviceTestAccess::ContextOf(device);
GuestBlock block;
struct Cleanup {
VkDevice device;
~Cleanup() {
ClearMultisampleTargets(device);
ClearCachedTextures(device);
}
} cleanup{context.device};
const auto address = block.Address();
ShaderRecompiler::DescriptorBinding binding{};
binding.kind = sampled ? ShaderRecompiler::DescriptorKind::SampledImage : ShaderRecompiler::DescriptorKind::StorageImage;
binding.role = ShaderRecompiler::DescriptorRole::GuestImages;
binding.binding = (sampled ? ShaderRecompiler::RuntimeAbi::FirstImageBinding : ShaderRecompiler::RuntimeAbi::FirstStorageImageBinding) + PlaceholderDimensionsPerClass + 2u;
binding.count = 1;
binding.imageShape = ShaderRecompiler::DescriptorImageShape::Image2D;
binding.imageWritten = {false};
binding.imageSamplers = {0u};
binding.guestDescriptor = {static_cast<std::uint32_t>(address >> 8u), static_cast<std::uint32_t>((address >> 40u) & 0xffu) | (20u << 20u) | (((Side - 1u) & 3u) << 30u), ((Side - 1u) >> 2u) | ((Side - 1u) << 14u), 0xfacu | (9u << 28u), 0u, 0u, 0u, 0u};
ShaderRecompiler::RecompileResult vertex;
ShaderRecompiler::RecompileResult program;
program.bindings.push_back(binding);
const CompiledShader shader{draw ? ShaderRecompiler::ShaderStage::Fragment : ShaderRecompiler::ShaderStage::Compute, &program, 0};
const std::array<CompiledShader, 2> stages{{{ShaderRecompiler::ShaderStage::Vertex, &vertex, 0}, shader}};
const auto build = [&] {
if (draw) return std::make_unique<ShaderResources>(context, stages, ColorTarget{}, 0, 0, 0);
return std::make_unique<ShaderResources>(context, shader);
};
auto resources = build();
Require(resources->Reusable(), "the ordinary image resources are not reusable");
const auto revalidate = [&](ShaderResources::ProofReport* report = nullptr) {
return draw ? resources->Revalidate(stages, report) : resources->Revalidate(shader, report);
};
ShaderResources::ProofReport proof;
Require(revalidate(&proof), "the unchanged image resources failed their initial proof");
if (std::getenv("APS5_NO_FAST_REVALIDATE") == nullptr) {
if (AgcDriver::GuestMemory::Watched(address, Side * Side * 4u)) Require(proof.path == ShaderResources::ProofPath::Fast, "the fixture did not exercise the fast image proof");
else std::puts("image memory is compared, not watched: fast image proof not tested; checking the image guards through the full proof");
}
ColorTarget target{};
target.address = address + BlockBytes;
target.surfaceAddress = target.address;
target.extent = {Side, Side};
target.surfaceExtent = target.extent;
target.format = VK_FORMAT_R8G8B8A8_UNORM;
target.tileMode = ColorTileMode::RenderTarget;
target.elementBytes = 4;
target.bytes = Side * Side * 4u;
target.samples = 4;
Require(MultisampleTargetView(context, target) != VK_NULL_HANDLE, "the disjoint multisample target has no view");
device.SubmitRecorded(true);
device.WaitIdle();
Require(revalidate(), "a disjoint multisample target prevented image reuse");
target.address = address;
target.surfaceAddress = address;
Require(MultisampleTargetView(context, target) != VK_NULL_HANDLE, "the overlapping multisample target has no view");
device.SubmitRecorded(true);
device.WaitIdle();
RequireColorGuard([&] { revalidate(); }, "a cached image access accepted memory owned by a multisampled color target");
RequireColorGuard([&] { build(); }, "a fresh image access accepted memory owned by a multisampled color target");
ClearMultisampleTargets(context.device);
Require(revalidate(), "retiring the multisample targets did not restore image reuse");
}
void CheckMultisampleTable(AgcDriver::VulkanDevice& device) {
namespace Abi = ShaderRecompiler::ImageTableAbi;
const auto context = AgcDriver::VulkanDeviceTestAccess::ContextOf(device);
if (context.imageTableCapacity == 0) {
std::puts("skipped, native multisample table check needs image table support");
return;
}
GuestBlock block;
struct Cleanup {
VkDevice device;
~Cleanup() { ClearMultisampleTargets(device); }
} cleanup{context.device};
ColorTarget target{};
target.address = block.Address();
target.surfaceAddress = target.address;
target.extent = {Side, Side};
target.surfaceExtent = target.extent;
target.format = VK_FORMAT_R8G8B8A8_UNORM;
target.tileMode = ColorTileMode::RenderTarget;
target.elementBytes = 4;
target.bytes = Side * Side * 4u;
target.samples = 4;
Require(MultisampleTargetView(context, target) != VK_NULL_HANDLE, "the native multisample table target has no view");
device.SubmitRecorded(true);
device.WaitIdle();
ShaderRecompiler::RecompileResult program;
ShaderRecompiler::DescriptorBinding map{};
map.kind = ShaderRecompiler::DescriptorKind::StorageBuffer;
map.role = ShaderRecompiler::DescriptorRole::ImageTableMap;
map.binding = static_cast<std::uint32_t>(ShaderRecompiler::RuntimeAbi::Binding::ImageTableMap);
map.count = 1;
map.guestDescriptor.resize(Abi::HeaderWords + 1u);
map.guestDescriptor[Abi::HeaderVersion] = Abi::Version;
map.guestDescriptor[Abi::HeaderElementCount] = 1u;
map.guestDescriptor[Abi::HeaderElementFaults] = Abi::HeaderWords;
map.guestDescriptor[Abi::HeaderSamplerFaults] = Abi::HeaderWords + 1u;
program.bindings.push_back(map);
ShaderRecompiler::DescriptorBinding image{};
image.kind = ShaderRecompiler::DescriptorKind::SampledImage;
image.role = ShaderRecompiler::DescriptorRole::ImageTable;
image.binding = ShaderRecompiler::RuntimeAbi::ImageTableBinding;
image.count = 1;
image.imageShapes = {ShaderRecompiler::DescriptorImageShape::Image2DMultisample};
image.imageDepthCompare = {false};
image.imageWritten = {false};
image.imageSamplers = {0u};
const auto address = block.Address();
image.guestDescriptor = {static_cast<std::uint32_t>(address >> 8u), static_cast<std::uint32_t>((address >> 40u) & 0xffu) | (56u << 20u) | (((Side - 1u) & 3u) << 30u), ((Side - 1u) >> 2u) | ((Side - 1u) << 14u), 0xfacu | (2u << 16u) | (0x1bu << 20u) | (14u << 28u), 0u, 2u << 4u, 0u, 0u};
program.bindings.push_back(image);
const CompiledShader shader{ShaderRecompiler::ShaderStage::Compute, &program, 0};
ShaderResources resources(context, shader);
Require(resources.SampledTextures().size() == 1u, "the native multisample table entry was rejected during construction");
Require(resources.Reusable(), "the native multisample table did not reach resource validation");
static_cast<void>(resources.Revalidate(shader));
device.SubmitRecorded(true);
device.WaitIdle();
std::puts("native multisample table validation did not reject its color target");
}
}
int main() {
try {
const auto device = OpenVulkanTestDevice();
if (!device) return VulkanTestSkipped;
const auto context = AgcDriver::VulkanDeviceTestAccess::ContextOf(*device);
if (!AgcDriver::GuestMemory::WriteWatched() || (context.limits.framebufferColorSampleCounts & VK_SAMPLE_COUNT_4_BIT) == 0) {
Require(std::getenv("ANYPS5_REQUIRE_VULKAN") == nullptr, "cached image guard tests require write tracking and 4x color targets");
std::puts("skipped, cached image guard tests require write tracking and 4x color targets");
return VulkanTestSkipped;
}
std::lock_guard gpu(AgcDriver::GuestMemory::GpuMutex());
for (const bool draw : {false, true}) {
for (const bool sampled : {false, true}) CheckCachedImage(*device, sampled, draw);
}
CheckMultisampleTable(*device);
std::puts("cached image multisample guard tests passed");
return 0;
} catch (const std::exception& error) {
std::cerr << error.what() << '\n';
return 1;
}
}
@@ -167,15 +167,23 @@ void Run(AgcDriver::VulkanDevice& device, const Mode& mode) {
device.WaitIdle();
}
void Check(const Mode& mode) {
std::uint32_t Check(const Mode& mode, bool keepsDenormals) {
constexpr const char* Names[2] = {"v_add_f64", "v_mul_f64"};
const std::array<std::uint32_t, 3> deviceFlushes{mode.field & 2u, mode.field & 1u, 0u};
std::uint32_t skipped = 0;
for (std::uint32_t tid = 0; tid < Threads; ++tid) {
for (std::uint32_t form = 0; form < 2u; ++form) {
const std::uint64_t actual = Output[tid * Results + form * 2u] | (static_cast<std::uint64_t>(Output[tid * Results + form * 2u + 1u]) << 32u);
const std::uint64_t expected = Expected[mode.field][tid][form];
Require(actual == expected, std::string(Names[form]) + " " + mode.name + " of " + Hex(Pairs[tid][0]) + " and " + Hex(Pairs[tid][1]) + " is " + Hex(actual) + ", expected " + Hex(expected));
if (actual == expected) continue;
const std::string message = std::string(Names[form]) + " " + mode.name + " of " + Hex(Pairs[tid][0]) + " and " + Hex(Pairs[tid][1]) + " is " + Hex(actual) + ", expected " + Hex(expected);
const bool deviceDependent = std::any_of(deviceFlushes.begin(), deviceFlushes.end(), [&](std::uint32_t flushed) { return Expected[flushed][tid][form] != expected; });
Require(deviceDependent && !keepsDenormals, message);
std::printf("skipped %s, the device flushes f64 denormals without DenormPreserve support\n", message.c_str());
++skipped;
}
}
return skipped;
}
}
@@ -195,21 +203,15 @@ int main() {
{"f64 denormal mode 3, IEEE mode on", ShaderRecompiler::ShaderFloatMode{0xf0u, true, true, false}, 3u},
{"f64 denormal mode 3, IEEE mode off", ShaderRecompiler::ShaderFloatMode{0xf0u, true, false, false}, 3u},
};
const bool canPreserveDenormals = device->Target().float64DenormPreserve;
bool keepsDenormals = device->Target().float64DenormPreserve;
std::uint32_t skipped = 0;
for (const Mode& mode : modes) {
Run(*device, mode);
const bool keepsDenormals = !mode.value.has_value() || ((mode.value->floatMode >> 6u) & 1u) != 0u;
if (!keepsDenormals || canPreserveDenormals) {
Check(mode);
continue;
}
try {
Check(mode);
} catch (const std::exception& error) {
std::printf("skipped %s, the device flushes f64 denormals without DenormPreserve support: %s\n", mode.name, error.what());
}
const std::uint32_t modeSkipped = Check(mode, keepsDenormals);
keepsDenormals = keepsDenormals || (!mode.value.has_value() && modeSkipped == 0u);
skipped += modeSkipped;
}
std::puts("f64 add and mul denormal mode tests passed");
std::printf("f64 add and mul denormal mode tests passed, %u device-dependent results skipped\n", skipped);
return 0;
} catch (const std::exception& error) {
std::cerr << error.what() << '\n';
@@ -94,28 +94,41 @@ std::uint64_t Bits(double value) {
return std::bit_cast<std::uint64_t>(value);
}
void Check(const Mode& mode) {
std::array<std::uint64_t, 5> Expected(std::uint64_t source, std::uint32_t field) {
const double value = std::bit_cast<double>(Flush(source, (field & 1u) == 0u));
const double below = std::bit_cast<double>(0x3fefffffffffffffull);
int exponent = 0;
const double mantissa = std::frexp(value, &exponent);
return {
Bits(std::floor(value)),
Bits(std::ceil(value)),
Flush(Bits(std::min(value - std::floor(value), below)), (field & 2u) == 0u),
Bits(mantissa),
static_cast<std::uint32_t>(exponent),
};
}
std::uint32_t Check(const Mode& mode, bool keepsDenormals) {
const std::uint32_t field = mode.value.has_value() ? mode.value->floatMode >> 6u : 3u;
const std::array<std::uint32_t, 3> deviceFlushes{field & 2u, field & 1u, 0u};
constexpr const char* Names[5] = {"v_floor_f64", "v_ceil_f64", "v_fract_f64", "v_frexp_mant_f64", "v_frexp_exp_i32_f64"};
std::uint32_t skipped = 0;
for (std::uint32_t tid = 0; tid < Threads; ++tid) {
const std::uint64_t source = ValueAt(tid);
const double value = std::bit_cast<double>(Flush(source, (field & 1u) == 0u));
const double below = std::bit_cast<double>(0x3fefffffffffffffull);
int exponent = 0;
const double mantissa = std::frexp(value, &exponent);
const std::array<std::uint64_t, 4> expected{
Bits(std::floor(value)),
Bits(std::ceil(value)),
Flush(Bits(std::min(value - std::floor(value), below)), (field & 2u) == 0u),
Bits(mantissa),
};
constexpr const char* Names[4] = {"v_floor_f64", "v_ceil_f64", "v_fract_f64", "v_frexp_mant_f64"};
const auto expected = Expected(source, field);
const std::uint32_t* out = &Output[tid * Results];
for (std::uint32_t index = 0; index < expected.size(); ++index) {
const std::uint64_t actual = out[index * 2u] | (static_cast<std::uint64_t>(out[index * 2u + 1u]) << 32u);
Require(actual == expected[index], std::string(Names[index]) + " " + mode.name + " of " + Hex(source) + " is " + Hex(actual) + ", expected " + Hex(expected[index]));
const std::uint64_t actual = index < 4u ? out[index * 2u] | (static_cast<std::uint64_t>(out[index * 2u + 1u]) << 32u) : out[8];
if (actual == expected[index]) continue;
const auto text = [index](std::uint64_t bits) { return index < 4u ? Hex(bits) : std::to_string(static_cast<std::int32_t>(bits)); };
const std::string message = std::string(Names[index]) + " " + mode.name + " of " + Hex(source) + " is " + text(actual) + ", expected " + text(expected[index]);
const bool deviceDependent = std::any_of(deviceFlushes.begin(), deviceFlushes.end(), [&](std::uint32_t flushed) { return Expected(source, flushed)[index] != expected[index]; });
Require(deviceDependent && !keepsDenormals, message);
std::printf("skipped %s, the device flushes f64 denormals without DenormPreserve support\n", message.c_str());
++skipped;
}
Require(out[8] == static_cast<std::uint32_t>(exponent), std::string("v_frexp_exp_i32_f64 ") + mode.name + " of " + Hex(source) + " is " + std::to_string(static_cast<std::int32_t>(out[8])) + ", expected " + std::to_string(exponent));
}
return skipped;
}
}
@@ -135,21 +148,15 @@ int main() {
{"f64 denormal mode 3, IEEE mode on", ShaderRecompiler::ShaderFloatMode{0xf0u, true, true, false}},
{"f64 denormal mode 3, IEEE mode off", ShaderRecompiler::ShaderFloatMode{0xf0u, true, false, false}},
};
const bool canPreserveDenormals = device->Target().float64DenormPreserve;
bool keepsDenormals = device->Target().float64DenormPreserve;
std::uint32_t skipped = 0;
for (const Mode& mode : modes) {
Run(*device, mode);
const bool keepsDenormals = !mode.value.has_value() || ((mode.value->floatMode >> 6u) & 1u) != 0u;
if (!keepsDenormals || canPreserveDenormals) {
Check(mode);
continue;
}
try {
Check(mode);
} catch (const std::exception& error) {
std::printf("skipped %s, the device flushes f64 denormals without DenormPreserve support: %s\n", mode.name, error.what());
}
const std::uint32_t modeSkipped = Check(mode, keepsDenormals);
keepsDenormals = keepsDenormals || (!mode.value.has_value() && modeSkipped == 0u);
skipped += modeSkipped;
}
std::puts("f64 rounding denormal mode tests passed");
std::printf("f64 rounding denormal mode tests passed, %u device-dependent results skipped\n", skipped);
return 0;
} catch (const std::exception& error) {
std::cerr << error.what() << '\n';
@@ -0,0 +1,264 @@
#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp"
#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp"
#include "prx/libSceAgcDriver/Graphics/include/Texture.hpp"
#include "prx/libSceAgcDriver/Graphics/include/TextureTiling.hpp"
#include "prx/libc/include/GuestAllocations.hpp"
#include "Recompiler.hpp"
#include "VulkanTestDevice.hpp"
#ifdef _WIN32
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <windows.h>
#endif
#include <algorithm>
#include <bit>
#include <cmath>
#include <limits>
#include <array>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <span>
#include <string>
#include <vector>
namespace {
using AgcDriver::Graphics::Require;
using ShaderRecompiler::ShaderStage;
constexpr std::uint32_t Threads = 32;
constexpr std::uint32_t Width = 32;
constexpr std::uint32_t Height = 8;
constexpr std::uint32_t Type2D = 9;
constexpr std::uint32_t IdentitySwizzle = 0xfacu;
constexpr std::uint32_t TwoChannelSwizzle = 0x22cu;
constexpr std::size_t TexelBytes = 8192;
constexpr std::size_t BlockBytes = 65536;
alignas(256) std::array<std::uint32_t, Threads * 4> Input{};
alignas(256) constexpr std::array<std::uint32_t, 12> StoreCode{
0x34020084, 0xe0381000, 0x80001401, 0xbf8c3f70, 0x7e3c0300, 0x7e3e0280, 0xf0201f08, 0x0001141e,
0x7e3e0281, 0xf0201508, 0x0001141e, 0xbf810000,
};
class GuestBlock {
public:
GuestBlock() {
#ifdef _WIN32
block = static_cast<std::uint8_t*>(VirtualAlloc(nullptr, BlockBytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE));
#else
block = static_cast<std::uint8_t*>(std::aligned_alloc(BlockBytes, BlockBytes));
#endif
Require(block != nullptr, "image store unorm rounding: cannot allocate the guest block");
GuestAllocations::Mutation().Add(block, BlockBytes, true, true, true);
}
~GuestBlock() {
GuestAllocations::Mutation().Remove(block);
#ifdef _WIN32
VirtualFree(block, 0, MEM_RELEASE);
#else
std::free(block);
#endif
}
GuestBlock(const GuestBlock&) = delete;
GuestBlock& operator=(const GuestBlock&) = delete;
std::uint8_t* Data() { return block; }
private:
std::uint8_t* block = nullptr;
};
struct Format {
const char* name;
std::uint32_t format;
std::uint32_t components;
std::uint32_t bits;
bool snorm;
};
constexpr std::array<Format, 5> Formats{{
{"8_UNORM", 1u, 1u, 8u, false},
{"16_16_UNORM", 23u, 2u, 16u, false},
{"8_8_8_8_UNORM", 56u, 4u, 8u, false},
{"8_8_8_8_SNORM", 57u, 4u, 8u, true},
{"16_16_16_16_UNORM", 65u, 4u, 16u, false},
}};
struct Write {
std::uint32_t row;
std::uint32_t dmask;
};
constexpr std::array<Write, 2> StoreWrites{{{0u, 0xfu}, {1u, 0x5u}}};
std::string Hex(std::uint32_t value) {
char text[16];
std::snprintf(text, sizeof(text), "0x%08x", value);
return text;
}
float Scale(const Format& format) {
return static_cast<float>((1u << (format.snorm ? format.bits - 1u : format.bits)) - 1u);
}
float Value(const Format& format, std::uint32_t tid, std::uint32_t index) {
constexpr std::array<float, 16> Edges{0.0f, -0.0f, 1.0f, 0.5f, -0.25f, 1.5f, -1.5f, 1e-30f, -1e-30f, 0.999f, 0.0019607844f, 0.9980392f, 0.33333334f, -0.6666667f, 2e-5f, 0.75f};
const auto slot = tid * 4u + index;
if (slot < Edges.size()) return Edges[slot];
if (slot == Edges.size()) return std::numeric_limits<float>::quiet_NaN();
if (slot == Edges.size() + 1u) return std::numeric_limits<float>::infinity();
if (slot == Edges.size() + 2u) return -std::numeric_limits<float>::infinity();
const auto scale = Scale(format);
const auto level = static_cast<float>((slot * 37u) % static_cast<std::uint32_t>(scale));
auto value = (level + 0.5f) / scale;
for (std::uint32_t step = 0; step < slot % 5u; ++step) value = std::nextafter(value, slot % 2u == 0u ? 2.0f : -2.0f);
return format.snorm && slot % 3u == 0u ? -value : value;
}
std::uint32_t Level(const Format& format, float value) {
const auto lower = format.snorm ? -1.0f : 0.0f;
const auto clamped = std::isnan(value) ? 0.0f : std::clamp(value, lower, 1.0f);
const auto product = static_cast<double>(clamped) * static_cast<double>(Scale(format));
const auto level = static_cast<std::int32_t>(std::nearbyint(product));
return static_cast<std::uint32_t>(level) & ((1u << format.bits) - 1u);
}
std::uint32_t TexelBytesOf(const Format& format) {
return format.components * format.bits / 8u;
}
std::uint32_t SwizzleOf(const Format& format) {
return format.components == 2u ? TwoChannelSwizzle : IdentitySwizzle;
}
AgcDriver::Graphics::TileMipLayout Mip(const Format& format) {
return AgcDriver::Graphics::ComputeMipLayout(AgcDriver::Graphics::TextureTileMode::kLinear, format.format, Width, Height, 1u).at(0);
}
std::uint64_t TexelOffset(const Format& format, std::uint32_t x, std::uint32_t y) {
const auto mip = Mip(format);
return mip.tiledOffset + static_cast<std::uint64_t>(y) * mip.pitchBytes + static_cast<std::uint64_t>(x) * TexelBytesOf(format);
}
std::vector<std::uint8_t> Initial() {
std::vector<std::uint8_t> bytes(TexelBytes);
for (std::size_t index = 0; index < bytes.size(); ++index) {
bytes[index] = static_cast<std::uint8_t>(0x80u + 37u * index + 11u * (index >> 4u));
}
return bytes;
}
std::uint32_t StoredComponent(const Format& format, std::uint32_t tid, std::uint32_t dmask, std::uint32_t component) {
if (((dmask >> component) & 1u) == 0u) return 0u;
std::uint32_t index = 0;
for (std::uint32_t lower = 0; lower < component; ++lower) index += (dmask >> lower) & 1u;
return Level(format, Value(format, tid, index));
}
std::vector<std::uint8_t> Expected(const Format& format) {
auto bytes = Initial();
for (std::uint32_t tid = 0; tid < Threads; ++tid) {
for (const auto& write : StoreWrites) {
auto* texel = bytes.data() + TexelOffset(format, tid, write.row);
for (std::uint32_t component = 0; component < format.components; ++component) {
const auto value = StoredComponent(format, tid, write.dmask, component);
std::memcpy(texel + component * format.bits / 8u, &value, format.bits / 8u);
}
}
}
return bytes;
}
std::array<std::uint32_t, 4> BufferDescriptor(const void* data, std::uint32_t bytes) {
const auto address = reinterpret_cast<std::uintptr_t>(data);
return {static_cast<std::uint32_t>(address), static_cast<std::uint32_t>((address >> 32u) & 0xffffu), bytes, 0x31016facu};
}
std::array<std::uint32_t, 8> TextureDescriptor(const void* data, std::uint32_t format, std::uint32_t swizzle) {
const auto address = static_cast<std::uint64_t>(reinterpret_cast<std::uintptr_t>(data));
return {
static_cast<std::uint32_t>(address >> 8u),
static_cast<std::uint32_t>((address >> 40u) & 0xffu) | (format << 20u) | (((Width - 1u) & 3u) << 30u),
((Width - 1u) >> 2u) | ((Height - 1u) << 14u),
swizzle | (Type2D << 28u),
0u,
0u,
0u,
0u,
};
}
void Dispatch(AgcDriver::VulkanDevice& device, std::uint8_t* texels, std::span<const std::uint32_t> code, std::uint32_t format, std::uint32_t swizzle, const std::array<std::uint32_t, 4>& buffer) {
std::vector<std::uint32_t> userData(16, 0u);
const auto texture = TextureDescriptor(texels, format, swizzle);
std::copy(buffer.begin(), buffer.end(), userData.begin());
std::copy(texture.begin(), texture.end(), userData.begin() + 4);
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, {}},
{32, 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();
AgcDriver::Graphics::StorageTexture::FlushPending(reinterpret_cast<std::uintptr_t>(texels), TexelBytes, nullptr, "test");
device.WaitIdle();
}
void Store(AgcDriver::VulkanDevice& device, std::uint8_t* texels, const Format& format) {
for (std::uint32_t tid = 0; tid < Threads; ++tid) {
for (std::uint32_t index = 0; index < 4u; ++index) Input[tid * 4u + index] = std::bit_cast<std::uint32_t>(Value(format, tid, index));
}
const auto initial = Initial();
std::copy(initial.begin(), initial.end(), texels);
Dispatch(device, texels, StoreCode, format.format, SwizzleOf(format), BufferDescriptor(Input.data(), static_cast<std::uint32_t>(Input.size() * 4u)));
}
void CheckTexels(const std::uint8_t* texels, const Format& format) {
const auto expected = Expected(format);
for (std::uint32_t y = 0; y < Height; ++y) {
for (std::uint32_t x = 0; x < Width; ++x) {
const auto offset = TexelOffset(format, x, y);
for (std::uint32_t component = 0; component < format.components; ++component) {
std::uint32_t actual = 0;
std::uint32_t wanted = 0;
std::memcpy(&actual, texels + offset + component * format.bits / 8u, format.bits / 8u);
std::memcpy(&wanted, expected.data() + offset + component * format.bits / 8u, format.bits / 8u);
Require(actual == wanted, std::string("image_store of ") + format.name + ": texel (" + std::to_string(x) + ", " + std::to_string(y) + ") component " + std::to_string(component) + " is " + Hex(actual) + ", expected " + Hex(wanted) + " (level nearest to the exact product)");
}
}
}
}
}
int main() {
try {
const auto device = OpenVulkanTestDevice();
if (!device) return VulkanTestSkipped;
GuestBlock block;
auto* texels = block.Data();
for (const auto& format : Formats) {
Store(*device, texels, format);
CheckTexels(texels, format);
}
std::puts("image store unorm rounding tests passed");
return 0;
} catch (const std::exception& error) {
std::cerr << error.what() << '\n';
return 1;
}
}
@@ -131,7 +131,7 @@ Captured Capture(std::byte* table) {
result.memory = std::make_unique<AgcDriver::ShaderMemory>(std::span<const MemoryRegion>{});
result.capture = result.memory->Capture(request);
result.regions = result.memory->TakeRecentRegions();
request.context.memory = result.memory->Regions();
request.context.memory = result.regions;
result.compiled = Recompile(request, *result.capture);
return result;
}
@@ -24,6 +24,13 @@ alignas(256) constexpr std::array<std::uint32_t, 43> ScanCode{
0xe0701000, 0x80030802, 0xbf810000,
};
constexpr auto StoreScanCode = [] {
auto code = ScanCode;
code[16] = 0xe0745000;
code[38] = 0xe0745000;
return code;
}();
constexpr std::uint32_t Lanes = 64;
constexpr std::uint32_t Blocks = 4096;
@@ -41,9 +48,17 @@ std::array<std::uint32_t, 4> BufferDescriptor(const void* data, std::uint32_t by
return {static_cast<std::uint32_t>(address), static_cast<std::uint32_t>((address >> 32u) & 0xffffu), bytes, 0x31016facu};
}
void Run(AgcDriver::VulkanDevice& device, std::uint32_t groups) {
alignas(256) static std::array<std::uint32_t, ScanCode.size()> code{};
code = ScanCode;
struct Variant {
const char* name;
const std::array<std::uint32_t, ScanCode.size()>& words;
};
constexpr std::array<Variant, 2> Variants{{{"buffer_atomic_swap_x2", ScanCode}, {"buffer_store_dwordx2", StoreScanCode}}};
void Run(AgcDriver::VulkanDevice& device, std::size_t variant, std::uint32_t groups) {
alignas(256) static std::array<std::array<std::uint32_t, ScanCode.size()>, Variants.size()> programs{};
auto& code = programs[variant];
code = Variants[variant].words;
std::vector<std::uint32_t> userData;
for (const auto& descriptor : {BufferDescriptor(Counter.data(), sizeof(Counter)), BufferDescriptor(Aggregates.data(), sizeof(Aggregates)), BufferDescriptor(Pairs.data(), sizeof(Pairs)), BufferDescriptor(Prefixes.data(), sizeof(Prefixes))}) {
userData.insert(userData.end(), descriptor.begin(), descriptor.end());
@@ -64,8 +79,8 @@ void Run(AgcDriver::VulkanDevice& device, std::uint32_t groups) {
device.WaitIdle();
}
void CheckLookBack(AgcDriver::VulkanDevice& device, std::uint32_t block) {
const auto name = "look-back from block " + std::to_string(block);
void CheckLookBack(AgcDriver::VulkanDevice& device, std::size_t variant, std::uint32_t block) {
const auto name = std::string(Variants[variant].name) + " look-back from block " + std::to_string(block);
Pairs.fill(0u);
std::uint32_t prefix = 0;
for (std::uint32_t b = 0; b < Blocks; ++b) Aggregates[b] = Value(b, block);
@@ -77,20 +92,20 @@ void CheckLookBack(AgcDriver::VulkanDevice& device, std::uint32_t block) {
Counter.fill(0u);
Counter[0] = block;
Prefixes.fill(0xdeadbeefu);
Run(device, 1);
Run(device, variant, 1);
Require(Counter[0] == block + 1, name + ": the counter is " + std::to_string(Counter[0]));
Require(Prefixes[block] == prefix, name + ": its prefix is " + std::to_string(Prefixes[block]) + ", expected " + std::to_string(prefix));
Require(Pairs[2 * block] == 2u, name + ": its flag is " + std::to_string(Pairs[2 * block]) + ", expected 2");
Require(Pairs[2 * block + 1] == prefix + Aggregates[block], name + ": it published " + std::to_string(Pairs[2 * block + 1]) + ", expected " + std::to_string(prefix + Aggregates[block]));
}
void CheckWhole(AgcDriver::VulkanDevice& device, std::uint32_t seed) {
const auto name = "whole scan " + std::to_string(seed);
void CheckWhole(AgcDriver::VulkanDevice& device, std::size_t variant, std::uint32_t seed) {
const auto name = std::string(Variants[variant].name) + " whole scan " + std::to_string(seed);
for (std::uint32_t b = 0; b < Blocks; ++b) Aggregates[b] = Value(b, seed);
Counter.fill(0u);
Pairs.fill(0u);
Prefixes.fill(0xdeadbeefu);
Run(device, Blocks);
Run(device, variant, Blocks);
std::uint32_t running = 0;
for (std::uint32_t b = 0; b < Blocks; ++b) {
Require(Prefixes[b] == running, name + ": block " + std::to_string(b) + "'s prefix is " + std::to_string(Prefixes[b]) + ", expected " + std::to_string(running));
@@ -113,8 +128,10 @@ int main() {
std::printf("skipped, subgroup size %u cannot hold a wave32\n", device->Target().subgroupSize);
return VulkanTestSkipped;
}
for (const std::uint32_t block : {0u, 1u, 2u, 63u, 64u, 1000u, Blocks - 1}) CheckLookBack(*device, block);
for (std::uint32_t seed = 0; seed < 50; ++seed) CheckWhole(*device, seed);
for (std::size_t variant = 0; variant < Variants.size(); ++variant) {
for (const std::uint32_t block : {0u, 1u, 2u, 63u, 64u, 1000u, Blocks - 1}) CheckLookBack(*device, variant, block);
for (std::uint32_t seed = 0; seed < 50; ++seed) CheckWhole(*device, variant, seed);
}
std::puts("look-back scan tests passed");
return 0;
} catch (const std::exception& error) {
@@ -45,6 +45,8 @@ constexpr std::uint32_t TileR64KBX = 0x1b;
constexpr std::uint32_t Type2D = 9;
constexpr std::size_t BlockBytes = 65536;
constexpr std::size_t SurfaceBytes = std::size_t{Side} * Side * 4u;
constexpr std::uint32_t LargeSide = 512;
constexpr std::size_t LargeSurfaceBytes = std::size_t{LargeSide} * LargeSide * 4u;
constexpr int Skipped = 77;
class Device {
@@ -149,16 +151,16 @@ private:
class ReservedSurface {
public:
ReservedSurface() {
block = static_cast<std::uint8_t*>(GuestArena::GuestArenaAllocate_nid_postfix(SurfaceBytes, BlockBytes));
explicit ReservedSurface(std::size_t bytes) : size(bytes) {
block = static_cast<std::uint8_t*>(GuestArena::GuestArenaAllocate_nid_postfix(size, BlockBytes));
Require(block != nullptr, "partially resident snapshot: cannot reserve the guest surface");
GuestAllocations::Mutation().Add(block, SurfaceBytes, true, true, true);
GuestAllocations::Mutation().Add(block, size, true, true, true);
}
~ReservedSurface() {
GuestAllocations::Mutation().Remove(block);
Decommit(0, SurfaceBytes);
GuestArena::GuestArenaRelease_nid_postfix(block, SurfaceBytes);
Decommit(0, size);
GuestArena::GuestArenaRelease_nid_postfix(block, size);
}
ReservedSurface(const ReservedSurface&) = delete;
@@ -189,6 +191,7 @@ public:
std::uint64_t Address() const { return static_cast<std::uint64_t>(reinterpret_cast<std::uintptr_t>(block)); }
private:
std::size_t size = 0;
std::uint8_t* block = nullptr;
};
@@ -200,11 +203,11 @@ void CountReads(std::uint64_t address, std::size_t bytes) {
if (address < surfaceEnd.load() && address + bytes > surfaceBegin.load()) surfaceReads.fetch_add(1);
}
std::array<std::uint32_t, 8> Descriptor(std::uint64_t address) {
std::array<std::uint32_t, 8> Descriptor(std::uint64_t address, std::uint32_t side = Side) {
return {
static_cast<std::uint32_t>(address >> 8u),
static_cast<std::uint32_t>((address >> 40u) & 0xffu) | (Format32UInt << 20u) | (((Side - 1u) & 3u) << 30u),
((Side - 1u) >> 2u) | ((Side - 1u) << 14u),
static_cast<std::uint32_t>((address >> 40u) & 0xffu) | (Format32UInt << 20u) | (((side - 1u) & 3u) << 30u),
((side - 1u) >> 2u) | ((side - 1u) << 14u),
0xfacu | (TileR64KBX << 20u) | (Type2D << 28u),
0u,
0u,
@@ -263,6 +266,48 @@ void Run(const Context& context, ReservedSurface& surface) {
AgcDriver::GuestMemory::SetFlushHook(nullptr);
}
void LargeTracked(const Context& context, ReservedSurface& surface, std::size_t committedBytes) {
const auto address = surface.Address();
const auto words = Descriptor(address, LargeSide);
const auto layout = committedBytes == LargeSurfaceBytes ? std::string("committed") : std::string("partially resident");
Require(DescribeSurface(DecodeTextureResource(words)).guestBytes == LargeSurfaceBytes, "the large test descriptor does not describe the reserved surface");
surface.Commit(0, committedBytes);
std::memset(surface.Data(), 0x5a, committedBytes);
surfaceBegin = address;
surfaceEnd = address + LargeSurfaceBytes;
std::shared_ptr<Texture> previous;
const auto sample = [&](const std::string& when, bool uploaded) {
AgcDriver::GuestMemory::BumpCollectEpoch();
surfaceReads = 0;
auto texture = CachedSampledTexture(context, words);
Require(texture != nullptr && !texture->ViewsStorageImage(), "the large " + layout + " surface was not sampled through a snapshot " + when);
const auto reads = surfaceReads.load();
if (uploaded && (reads == 0 || texture == previous)) throw std::runtime_error("sampling the large " + layout + " surface " + when + " kept the texture uploaded before it");
if (!uploaded && (reads != 0 || texture != previous)) throw std::runtime_error("sampling the large " + layout + " surface " + when + " uploaded it again although the write tracker saw no write (" + std::to_string(reads) + " reads)");
previous = texture;
};
AgcDriver::GuestMemory::SetFlushHook(&CountReads);
try {
sample("at its first use", true);
sample("again", false);
surface.Data()[0x100] = 0x11;
sample("after a CPU write", true);
sample("after the upload of the written surface", false);
surface.Data()[committedBytes - 0x40] = 0x22;
sample("after a CPU write to its last committed block", true);
sample("after the upload of the written block", false);
AgcDriver::GuestMemory::MarkWritten(address + 0x200, 4);
sample("after a driver store of the bytes already there, which a snapshot it did not keep cannot tell from a change", true);
sample("after the upload that followed the driver store", false);
} catch (...) {
AgcDriver::GuestMemory::SetFlushHook(nullptr);
throw;
}
AgcDriver::GuestMemory::SetFlushHook(nullptr);
ClearCachedTextures(context.device);
surface.Decommit(0, committedBytes);
}
#ifdef _WIN32
void UntrackedHint(const Context& context, ReservedSurface& surface) {
const auto address = surface.Address();
@@ -369,9 +414,13 @@ int main(int argc, char** argv) {
auto context = device->GetContext();
TextureDetiler detiler(context);
context.detiler = &detiler;
ReservedSurface surface;
const bool large = argc == 2 && std::string_view(argv[1]) == "--large-tracked";
ReservedSurface surface(large ? LargeSurfaceBytes : SurfaceBytes);
std::lock_guard gpu(AgcDriver::GuestMemory::GpuMutex());
if (argc == 2 && std::string_view(argv[1]) == "--pending-stores") PendingStores(context, surface);
if (large) {
LargeTracked(context, surface, LargeSurfaceBytes);
LargeTracked(context, surface, BlockBytes);
} else if (argc == 2 && std::string_view(argv[1]) == "--pending-stores") PendingStores(context, surface);
else if (argc == 2 && std::string_view(argv[1]) == "--untracked-hint") {
#ifdef _WIN32
UntrackedHint(context, surface);
@@ -223,7 +223,7 @@ int main() {
std::memcpy(indices, triangle.data(), sizeof(triangle));
const auto variant = std::string(readDepth == nullptr ? "without depth" : writeDepth == nullptr ? "with depth on the reader" : "with a depth target each");
Draw(*device, reader, read, readDepth, indices);
Require(WriterFenced(inputs.Address(), sizeof(triangle)), "pass input hazard " + variant + ": a pass writing the indices an open pass reads in place took no barrier");
if (device->DrawInputsInPlace()) Require(WriterFenced(inputs.Address(), sizeof(triangle)), "pass input hazard " + variant + ": a pass writing the indices an open pass reads in place took no barrier");
Draw(*device, reader, read, readDepth, indices);
Draw(*device, writer, written, writeDepth, nullptr);
Require(Covered(*device, read) == all, "pass input hazard " + variant + ": the draw reading its indices in place did not cover its target, a later pass overwrote them first");
@@ -7,6 +7,7 @@
#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp"
#include "Optimization/ResourceProgram.hpp"
#include "CompiledVariant.hpp"
#include "Optimization/RequestMemoryView.hpp"
#include <spirv/unified1/spirv.hpp>
#include <algorithm>
#include <array>
@@ -481,6 +482,197 @@ void RetainedPixelMatchesNullProgram(AgcDriver::VulkanDevice& device) {
}
}
void CheckMergedHull(AgcDriver::VulkanDevice& device) {
using namespace AgcDriver::DriverDetail;
using namespace ShaderRecompiler;
Fixture merged;
Fixture domain;
Fixture fragment;
merged.Initialize(3u);
domain.Initialize(2u);
fragment.Initialize(1u);
ShaderRegistry registry;
for (const auto* fixture : {&merged, &domain, &fragment}) registry.emplace(fixture->snapshot->codeAddress, std::vector<std::shared_ptr<const ShaderSnapshot>>{fixture->snapshot});
AgcDriver::QueueState queue{};
queue.context[0x8e] = 0xfu;
queue.context[0x8f] = 0xfu;
merged.Bind(queue, 0x148u, 0x10bu);
domain.Bind(queue, 0xc8u, 0x8bu);
fragment.Bind(queue, 0x8u, 0xbu);
const auto unset = queue;
const auto null = [&] {
auto state = queue;
state.shader[0x102u] = 0u;
state.shader[0x103u] = 0u;
return state;
}();
alignas(8) const std::array<std::uint32_t, 2> pointer{};
const auto pointerAddress = reinterpret_cast<std::uintptr_t>(pointer.data());
queue.shader[0x102u] = static_cast<std::uint32_t>(pointerAddress);
queue.shader[0x103u] = static_cast<std::uint32_t>(pointerAddress >> 32u);
DrawDecode prepared{};
prepared.state.stages.path = AgcDriver::Graphics::ShaderPath::Tessellation;
prepared.state.stages.vertexWaveSize = 64u;
prepared.state.stages.fragmentWaveSize = 32u;
prepared.state.stages.tessellation = TessellationConfiguration{3, 4, 1, 2, 2};
prepared.pixel.wave32 = true;
prepared.pixel.targetOutputMode[0] = 9;
prepared.pixel.targetExportMapping.fill(0xe4u);
DecodeGraphicsPrograms(prepared, queue, registry, true, true);
DrawDecode draw{};
draw.state = prepared.state;
draw.state.stages.tessellation->mergedHull = false;
draw.pixel = prepared.pixel;
DecodeGraphicsPrograms(draw, queue, registry, false, true);
for (const auto* decoded : {&prepared, &draw}) {
Require(decoded->state.stages.tessellation->mergedHull, "a registered type-3 hull program was not decoded as a merged hull");
Require(decoded->roles == std::vector<ProgramRole>{ProgramRole::Local, ProgramRole::Hull, ProgramRole::Domain, ProgramRole::Fragment}, "a merged hull program did not give the local and hull roles");
const auto& local = decoded->programs[0];
const auto& hull = decoded->programs[1];
Require(local.snapshot == merged.snapshot && hull.snapshot == merged.snapshot && local.codeOffset == 64u && hull.codeOffset == 64u, "the local and hull stages of a merged hull program refer to another program");
Require(local.binary.stage == ShaderStage::Local && hull.binary.stage == ShaderStage::TessellationControl && local.firstUserSgpr == 8u && hull.firstUserSgpr == 0u && hull.userData.size() == local.userData.size() + 8u, "a merged hull program's stages lost the merged user SGPR layout");
}
Require(draw.programs[1].userData[0] == static_cast<std::uint32_t>(pointerAddress) && draw.programs[1].userData[1] == static_cast<std::uint32_t>(pointerAddress >> 32u), "a merged hull draw did not pass its user-data address");
Require(UserDataWords(draw.programs, draw.roles, 0x10eu) == std::vector<std::pair<std::size_t, std::size_t>>{{0u, 2u}, {1u, 10u}}, "an indirect draw's user-data register does not reach both stages of a merged hull program");
std::vector<MemoryRegion> programMemory;
AppendProgramMemory(programMemory, draw.programs);
Require(programMemory.size() == 6u, "a merged hull draw's program memory does not list each code and header region once");
try {
const RequestMemoryView view(programMemory);
} catch (const std::exception& error) {
throw std::runtime_error(std::string("a merged hull draw's program memory is not a valid request memory: ") + error.what());
}
for (const auto* state : {&unset, &null}) {
DrawDecode without{};
without.state = prepared.state;
without.pixel = prepared.pixel;
DecodeGraphicsPrograms(without, *state, registry, false, true);
Require(without.roles == draw.roles && without.programs[1].userData.size() == draw.programs[1].userData.size() && without.programs[1].userData[0] == 0u && without.programs[1].userData[1] == 0u, "a merged hull draw without a user-data address was not decoded with zero s[0:1]");
}
auto unmapped = queue;
unmapped.shader[0x102u] = 0x10u;
unmapped.shader[0x103u] = 0u;
DrawDecode stale{};
stale.state = prepared.state;
stale.pixel = prepared.pixel;
DecodeGraphicsPrograms(stale, unmapped, registry, false, true);
Require(stale.programs[1].userData[0] == 0x10u && stale.programs[1].userData[1] == 0u, "a merged hull draw did not pass a user-data address it does not need unchecked");
const auto stages = PrepareGraphicsStages(prepared, device.Target());
Require(stages.size() == 4u && stages[0].entry.handle != stages[1].entry.handle, "a merged hull program was not prepared as separate local and hull stages");
for (const auto& stage : stages) stage.snapshot->prepared->entries.push_back(stage.entry);
std::vector<LinkedProgram> linked;
std::vector<MemoryRegion> memory;
AppendProgramMemory(memory, draw.programs);
for (std::size_t index = 0; index < draw.programs.size(); ++index) {
const auto& program = draw.programs[index];
linked.push_back({draw.roles[index], program.binary, program.userDataBase, program.firstUserSgpr, program.userData});
}
const auto predicted = merged.snapshot->prepared->entries.size();
for (std::size_t index = 0; index < 2u; ++index) {
const auto& program = draw.programs[index];
const auto vertex = AgcDriver::Graphics::DecodeVertexStageInfo(program.binary.header, program.binary.headerAddress, program.userData, nullptr, true);
RecompileRequest request{program.binary, {64u, program.firstUserSgpr, program.userData, {}, std::nullopt, vertex, memory}, device.Target(), {0, 0, 0, 128u}, GraphicsCompileContext{program.firstUserSgpr, linked, std::nullopt, draw.state.stages.tessellation, {0, 3, 4, 1}}};
static_cast<void>(InvocationFor(*program.snapshot, program.codeOffset, request));
Require(merged.snapshot->prepared->entries.size() == predicted, "a merged hull stage prepared at ABI resolution was prepared again at its draw");
request.graphics->tessellation->mergedHull = false;
Require(!MatchesPreparedShader(request, *stages[index].entry.handle), "a separate hull configuration matched a merged hull artifact");
}
Fixture front;
front.Initialize(5u);
registry.emplace(front.snapshot->codeAddress, std::vector<std::shared_ptr<const ShaderSnapshot>>{front.snapshot});
Fixture back;
back.Initialize(7u);
registry.emplace(back.snapshot->codeAddress, std::vector<std::shared_ptr<const ShaderSnapshot>>{back.snapshot});
auto separate = queue;
front.Bind(separate, 0x148u, 0x10bu);
back.Bind(separate, 0x108u, 0x10bu);
DrawDecode halves{};
halves.state = prepared.state;
halves.pixel = prepared.pixel;
DecodeGraphicsPrograms(halves, separate, registry, false, true);
Require(!halves.state.stages.tessellation->mergedHull && halves.programs[0].snapshot == front.snapshot && halves.programs[1].snapshot == back.snapshot, "separate hull halves were decoded as a merged hull program");
auto halvesUnset = unset;
front.Bind(halvesUnset, 0x148u, 0x10bu);
back.Bind(halvesUnset, 0x108u, 0x10bu);
DrawDecode halvesWithout{};
halvesWithout.state = prepared.state;
halvesWithout.pixel = prepared.pixel;
Reject([&] { DecodeGraphicsPrograms(halvesWithout, halvesUnset, registry, false, true); }, "merged shader user-data address is null");
}
void CheckMergedHullVertexTables(AgcDriver::VulkanDevice& device) {
using namespace AgcDriver::DriverDetail;
using namespace ShaderRecompiler;
struct Header {
Shader shader{};
ShaderUserData users{};
std::array<std::uint16_t, 11> direct{};
ShaderSemantic semantic{};
} header;
alignas(256) std::array<std::uint32_t, 65> code{};
code.fill(0xffffffffu);
code.back() = 0xbf810000u;
header.shader.type = 3;
header.shader.user_data = &header.users;
header.direct.fill(0xffffu);
header.direct[8] = 10u;
header.direct[10] = 12u;
header.users.direct_resource_offset = header.direct.data();
header.users.direct_resource_count = static_cast<std::uint16_t>(header.direct.size());
header.semantic.size_in_elements = 4u;
header.shader.input_semantics = &header.semantic;
header.shader.num_input_semantics = 1u;
auto merged = std::make_shared<ShaderSnapshot>();
merged->type = 3;
merged->codeAddress = reinterpret_cast<std::uintptr_t>(code.data());
merged->headerAddress = reinterpret_cast<std::uintptr_t>(&header);
merged->code.assign(code.begin(), code.end());
merged->header.resize(sizeof(header));
std::memcpy(merged->header.data(), &header, sizeof(header));
Fixture domain;
Fixture fragment;
domain.Initialize(2u);
fragment.Initialize(1u);
ShaderRegistry registry;
registry.emplace(merged->codeAddress, std::vector<std::shared_ptr<const ShaderSnapshot>>{merged});
for (const auto* fixture : {&domain, &fragment}) registry.emplace(fixture->snapshot->codeAddress, std::vector<std::shared_ptr<const ShaderSnapshot>>{fixture->snapshot});
AgcDriver::QueueState queue{};
queue.context[0x8e] = 0xfu;
queue.context[0x8f] = 0xfu;
const auto entry = merged->codeAddress + 256u;
queue.shader[0x148u] = static_cast<std::uint32_t>(entry >> 8u);
queue.shader[0x149u] = static_cast<std::uint32_t>(entry >> 40u);
queue.shader[0x10bu] = 16u << 1u;
domain.Bind(queue, 0xc8u, 0x8bu);
fragment.Bind(queue, 0x8u, 0xbu);
alignas(16) const std::array<std::uint32_t, 4> bufferTable{};
alignas(16) const std::array<std::uint32_t, 4> attributeTable{};
const auto bufferAddress = reinterpret_cast<std::uintptr_t>(bufferTable.data());
const auto attributeAddress = reinterpret_cast<std::uintptr_t>(attributeTable.data());
for (std::uint32_t slot = 2u; slot < 6u; ++slot) queue.shader[0x10cu + slot] = slot % 2u == 0u ? 0x12345600u : 0x00000077u;
queue.shader[0x10cu + 10u] = static_cast<std::uint32_t>(bufferAddress);
queue.shader[0x10cu + 11u] = static_cast<std::uint32_t>(bufferAddress >> 32u);
queue.shader[0x10cu + 12u] = static_cast<std::uint32_t>(attributeAddress);
queue.shader[0x10cu + 13u] = static_cast<std::uint32_t>(attributeAddress >> 32u);
DrawDecode draw{};
draw.state.stages.path = AgcDriver::Graphics::ShaderPath::Tessellation;
draw.state.stages.vertexWaveSize = 64u;
draw.state.stages.fragmentWaveSize = 32u;
draw.state.stages.tessellation = TessellationConfiguration{3, 3, 1, 2, 2};
draw.pixel.wave32 = true;
draw.pixel.targetOutputMode[0] = 9;
draw.pixel.targetExportMapping.fill(0xe4u);
DecodeGraphicsPrograms(draw, queue, registry, false, true);
Require(draw.state.stages.tessellation->mergedHull && draw.programs[1].firstUserSgpr == 0u, "the merged hull draw with vertex tables was not decoded as a merged hull");
for (std::size_t index = 0; index < 2u; ++index) {
const auto& program = draw.programs[index];
std::vector<AgcDriver::Graphics::DecodeRead> reads;
const auto vertex = AgcDriver::Graphics::DecodeVertexStageInfo(program.binary.header, program.binary.headerAddress, program.userData, &reads, false, 8u - program.firstUserSgpr);
Require(vertex.fetchEmbedded && vertex.fetchAttribReg == 12u && vertex.fetchBufferReg == 10u && vertex.resourcesNum == 1u, "a merged hull stage did not decode its vertex tables");
Require(reads.size() == 2u && reads[0].address == attributeAddress && reads[1].address == bufferAddress, "a merged hull stage read its vertex tables from user-data words other than its slots");
}
}
void NullPixelMatchesRegistration(AgcDriver::VulkanDevice& device) {
using namespace AgcDriver::DriverDetail;
Fixture front;
@@ -541,6 +733,8 @@ int main(int argc, char** argv) {
Check(*device, AgcDriver::Graphics::ShaderPath::Vertex, dump);
Check(*device, AgcDriver::Graphics::ShaderPath::Geometry, dump);
Check(*device, AgcDriver::Graphics::ShaderPath::Tessellation, dump);
CheckMergedHull(*device);
CheckMergedHullVertexTables(*device);
std::cout << "prepared graphics ABI tests passed\n";
return 0;
} catch (const std::exception& error) {
@@ -0,0 +1,112 @@
#include "prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp"
#include "prx/libSceAgcDriver/Execution/include/Driver/Shaders/ShaderRegistry.hpp"
#include <algorithm>
#include <condition_variable>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <functional>
#include <iostream>
#include <mutex>
#include <stdexcept>
#include <string>
#include <thread>
#ifdef _WIN32
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <windows.h>
#else
#include <pthread.h>
#endif
namespace {
constexpr std::uint64_t PoolStackBytes = 64ull << 20u;
constexpr std::uint64_t MostCommittedBytes = 8ull << 20u;
void Require(bool condition, const std::string& message) {
if (!condition) throw std::runtime_error("registration prepare stack: " + message);
}
struct StackUse {
std::thread::id thread;
std::uint64_t reserved = 0;
std::uint64_t committed = 0;
};
StackUse MeasureCurrentStack() {
StackUse use;
use.thread = std::this_thread::get_id();
#ifdef _WIN32
MEMORY_BASIC_INFORMATION here{};
const int local = 0;
Require(VirtualQuery(&local, &here, sizeof(here)) != 0, "VirtualQuery of the stack failed");
auto cursor = reinterpret_cast<std::uintptr_t>(here.AllocationBase);
for (;;) {
MEMORY_BASIC_INFORMATION info{};
if (VirtualQuery(reinterpret_cast<const void*>(cursor), &info, sizeof(info)) == 0 || info.AllocationBase != here.AllocationBase) break;
use.reserved += info.RegionSize;
if (info.State == MEM_COMMIT) use.committed += info.RegionSize;
cursor = reinterpret_cast<std::uintptr_t>(info.BaseAddress) + info.RegionSize;
}
#elif defined(__APPLE__)
use.reserved = pthread_get_stacksize_np(pthread_self());
#else
pthread_attr_t attributes;
Require(pthread_getattr_np(pthread_self(), &attributes) == 0, "pthread_getattr_np failed");
void* base = nullptr;
std::size_t size = 0;
pthread_attr_getstack(&attributes, &base, &size);
pthread_attr_destroy(&attributes);
use.reserved = size;
#endif
return use;
}
StackUse MeasureOn(const std::function<void(std::function<void()>)>& submit) {
std::mutex mutex;
std::condition_variable done;
bool measured = false;
StackUse use;
submit([&] {
const auto result = MeasureCurrentStack();
std::lock_guard lock(mutex);
use = result;
measured = true;
done.notify_all();
});
std::unique_lock lock(mutex);
done.wait(lock, [&] { return measured; });
return use;
}
void CheckPool(const char* name, const std::function<void(std::function<void()>)>& submit) {
const auto use = MeasureOn(submit);
Require(use.thread != std::this_thread::get_id(), std::string(name) + " ran the task on the caller's thread");
Require(use.reserved >= PoolStackBytes, std::string(name) + " thread reserves " + std::to_string(use.reserved >> 20u) + " MiB of stack, less than 64 MiB");
#ifdef _WIN32
Require(use.committed <= MostCommittedBytes, std::string(name) + " thread commits " + std::to_string(use.committed >> 20u) + " MiB of its " + std::to_string(use.reserved >> 20u) + " MiB stack up front");
#endif
}
}
int main() {
try {
AgcDriver::DriverDetail::Driver::Get();
CheckPool("registration preparation", [](std::function<void()> task) { AgcDriver::DriverDetail::SubmitRegistrationPreparation(std::move(task)); });
CheckPool("library prebuild", [](std::function<void()> task) { AgcDriver::DriverDetail::SubmitLibraryPrebuild(std::move(task)); });
const char* prebuildThreads = std::getenv("APS5_PREBUILD_THREADS");
const auto started = std::max(2u, std::thread::hardware_concurrency() / 2u) + (prebuildThreads != nullptr ? std::max(1u, static_cast<unsigned>(std::strtoul(prebuildThreads, nullptr, 10))) : 1u);
const auto live = AgcDriver::DriverDetail::RegistrationPreparationThreads();
Require(live == started, "the preparation and prebuild pools count " + std::to_string(live) + " live threads of the " + std::to_string(started) + " they started");
AgcDriver::DriverDetail::CloseRegistrationPreparation();
Require(AgcDriver::DriverDetail::RegistrationPreparationThreads() == 0, "closing the preparation and prebuild pools left " + std::to_string(AgcDriver::DriverDetail::RegistrationPreparationThreads()) + " live threads");
std::puts("registration prepare stack tests passed");
return 0;
} catch (const std::exception& error) {
std::cerr << error.what() << '\n';
return 1;
}
}
@@ -9,6 +9,8 @@
#include "prx/libSceAgcDriver/Graphics/include/TextureCache.hpp"
#include "prx/libSceAgcDriver/Graphics/include/PlaceholderImages.hpp"
#include "prx/libc/include/GuestAllocations.hpp"
#include "prx/libc/include/GuestArena.hpp"
#include "prx/libc/include/GuestWriteWatch.hpp"
#include "VulkanTestDevice.hpp"
#include <SDL_loadso.h>
#ifdef _WIN32
@@ -16,8 +18,12 @@
#define NOMINMAX
#endif
#include <windows.h>
#else
#include <sys/mman.h>
#endif
#include <algorithm>
#include <array>
#include <cctype>
#include <chrono>
#include <cstdint>
#include <cstdio>
@@ -26,8 +32,11 @@
#include <iostream>
#include <memory>
#include <mutex>
#include <optional>
#include <span>
#include <stdexcept>
#include <string>
#include <string_view>
#include <thread>
#include <unordered_map>
#include <vector>
@@ -48,6 +57,67 @@ constexpr std::size_t AllocationBytes = BlockBytes + RegistrationGapBytes;
constexpr std::uint64_t Budget = 1ull << 20u;
constexpr std::uint64_t Ceiling = Budget + Budget / 4u;
std::optional<std::size_t> selectDevice(std::span<const VkPhysicalDeviceProperties> devices, std::string_view request) {
const auto rankDeviceType = [](VkPhysicalDeviceType type) {
switch (type) {
case VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU: return 3;
case VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU: return 2;
case VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU: return 1;
default: return 0;
}
};
std::optional<std::size_t> selected;
int selectedRank = -1;
for (std::size_t index = 0; index < devices.size(); ++index) {
const auto& candidate = devices[index];
if (candidate.apiVersion < VK_API_VERSION_1_1) continue;
const std::string_view name(candidate.deviceName);
if (!request.empty() && std::search(name.begin(), name.end(), request.begin(), request.end(), [](char left, char right) {
return std::tolower(static_cast<unsigned char>(left)) == std::tolower(static_cast<unsigned char>(right));
}) == name.end()) continue;
const int rank = request.empty() ? rankDeviceType(candidate.deviceType) : 4;
if (rank <= selectedRank) continue;
selected = index;
selectedRank = rank;
}
return selected;
}
void deviceSelectionTests() {
const auto properties = [](const char* name, VkPhysicalDeviceType type) {
VkPhysicalDeviceProperties result{};
result.apiVersion = VK_API_VERSION_1_1;
result.deviceType = type;
std::memcpy(result.deviceName, name, std::strlen(name) + 1u);
return result;
};
std::array devices{
properties("AMD Radeon Graphics", VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU),
properties("NVIDIA GeForce RTX 5080", VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU),
properties("Virtual GPU", VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU),
properties("CPU", VK_PHYSICAL_DEVICE_TYPE_CPU),
};
Require(!selectDevice({}, {}).has_value(), "an empty device list selected a device");
Require(selectDevice(devices, {}) == 1u, "default selection did not prefer the discrete GPU");
Require(selectDevice(devices, "AMD") == 0u, "the AMD request did not select the integrated GPU");
Require(selectDevice(devices, "NVIDIA") == 1u, "the NVIDIA request did not select the discrete GPU");
Require(selectDevice(devices, "aMd rAdEoN") == 0u, "a requested integrated GPU lost to an unrelated discrete GPU");
Require(selectDevice(devices, "rtx 5080") == 1u, "a partial GPU name did not match without regard to case");
Require(selectDevice(devices, "Virtual") == 2u, "a requested virtual GPU was not selected");
Require(selectDevice(devices, "cpu") == 3u, "a requested CPU device was not selected");
Require(!selectDevice(devices, "missing").has_value(), "an unmatched request selected another GPU");
Require(!selectDevice(devices, "NVIDIA GeForce RTX 5080 with a longer name").has_value(), "a request longer than the device name matched");
Require(selectDevice(std::span(devices).subspan(2), {}) == 0u, "default selection did not prefer the virtual GPU to the CPU");
devices[1].apiVersion = VK_API_VERSION_1_0;
Require(selectDevice(devices, {}) == 0u, "default selection did not prefer the integrated GPU to the virtual GPU");
Require(!selectDevice(devices, "NVIDIA").has_value(), "a requested device without Vulkan 1.1 was selected");
devices[1] = devices[0];
Require(selectDevice(devices, {}) == 0u && selectDevice(devices, "AMD") == 0u, "equally ranked matches did not retain enumeration order");
for (auto& device : devices) device.apiVersion = VK_API_VERSION_1_0;
Require(!selectDevice(devices, {}).has_value(), "a list without Vulkan 1.1 selected a device");
std::puts("sampled-cache device selection CPU tests passed");
}
class Device {
public:
explicit Device(bool enableStorageImageMultisample = true) {
@@ -71,7 +141,13 @@ public:
Require(count != 0, "no Vulkan device");
std::vector<VkPhysicalDevice> devices(count);
Check(enumerate(instance, &count, devices.data()), "vkEnumeratePhysicalDevices");
context.physical = devices.front();
const auto physicalProperties = function<PFN_vkGetPhysicalDeviceProperties>("vkGetPhysicalDeviceProperties");
std::vector<VkPhysicalDeviceProperties> deviceProperties(devices.size());
for (std::size_t index = 0; index < devices.size(); ++index) physicalProperties(devices[index], &deviceProperties[index]);
const char* requestedName = std::getenv("ANYPS5_GPU");
const auto selectedIndex = selectDevice(deviceProperties, requestedName != nullptr ? requestedName : "");
Require(selectedIndex.has_value(), requestedName != nullptr && *requestedName != '\0' ? std::string("no Vulkan 1.1 device matching ANYPS5_GPU=\"") + requestedName + "\"" : "no Vulkan 1.1 device");
context.physical = devices[*selectedIndex];
const auto extensions = function<PFN_vkEnumerateDeviceExtensionProperties>("vkEnumerateDeviceExtensionProperties");
Check(extensions(context.physical, nullptr, &count, nullptr), "vkEnumerateDeviceExtensionProperties");
std::vector<VkExtensionProperties> available(count);
@@ -122,6 +198,7 @@ public:
function<PFN_vkGetPhysicalDeviceMemoryProperties>("vkGetPhysicalDeviceMemoryProperties")(context.physical, &context.memory);
VkPhysicalDeviceProperties properties{};
function<PFN_vkGetPhysicalDeviceProperties>("vkGetPhysicalDeviceProperties")(context.physical, &properties);
std::printf("sampled-cache fixture Vulkan device: %s vendor=0x%04x device=0x%04x\n", properties.deviceName, properties.vendorID, properties.deviceID);
context.limits = properties.limits;
context.bufferDeviceAddress = true;
context.formatProperties = function<PFN_vkGetPhysicalDeviceFormatProperties>("vkGetPhysicalDeviceFormatProperties");
@@ -141,6 +218,14 @@ public:
~Device() { release(); }
const Context& GetContext() const { return context; }
void VerifySelection() const {
VkPhysicalDeviceProperties properties{};
function<PFN_vkGetPhysicalDeviceProperties>("vkGetPhysicalDeviceProperties")(context.physical, &properties);
const char* request = std::getenv("ANYPS5_GPU");
Require(selectDevice(std::span(&properties, 1), request != nullptr ? request : "").has_value(), "the sampled-cache fixture physical device does not match ANYPS5_GPU");
std::puts("sampled-cache fixture physical device selection verified");
}
private:
template<typename TFunction>
TFunction function(const char* name) const {
@@ -194,6 +279,53 @@ private:
void* memory = nullptr;
};
class WatchedSurface {
public:
explicit WatchedSurface(const Context& context) : context(context) {
#ifdef _WIN32
memory = static_cast<std::uint8_t*>(GuestArena::GuestArenaAllocate_nid_postfix(SurfaceBytes, 65536));
if (memory != nullptr) GuestArena::GuestArenaCommit_nid_postfix(memory, SurfaceBytes, PAGE_READWRITE, SurfaceBytes);
#else
constexpr std::uintptr_t alignment = 65536;
void* mapped = mmap(nullptr, SurfaceBytes + alignment, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (mapped != MAP_FAILED) {
const auto begin = reinterpret_cast<std::uintptr_t>(mapped);
const auto aligned = (begin + alignment - 1) & ~(alignment - 1);
if (aligned != begin) munmap(mapped, aligned - begin);
if (aligned + SurfaceBytes != begin + SurfaceBytes + alignment) munmap(reinterpret_cast<void*>(aligned + SurfaceBytes), begin + alignment - aligned);
memory = reinterpret_cast<std::uint8_t*>(aligned);
GuestWriteWatch::GuestWriteWatchRegister_nid_postfix(memory, SurfaceBytes);
}
#endif
Require(memory != nullptr, "cannot allocate the watched surface");
Require(AgcDriver::GuestMemory::Watched(Address(), SurfaceBytes), "the watched surface is not write-watched");
std::memset(memory, 0x5a, SurfaceBytes);
GuestAllocations::Mutation().Add(memory, SurfaceBytes, true, true, true);
AgcDriver::GuestMemory::CollectWritesUncached(Address(), SurfaceBytes);
}
~WatchedSurface() {
ClearCachedTextures(context.device);
GuestAllocations::Mutation().Remove(memory);
#ifdef _WIN32
GuestArena::GuestArenaReset_nid_postfix(memory, SurfaceBytes);
GuestArena::GuestArenaRelease_nid_postfix(memory, SurfaceBytes);
#else
munmap(memory, SurfaceBytes);
GuestWriteWatch::GuestWriteWatchUnregister_nid_postfix(memory, SurfaceBytes);
#endif
}
WatchedSurface(const WatchedSurface&) = delete;
WatchedSurface& operator=(const WatchedSurface&) = delete;
std::uint64_t Address() const { return reinterpret_cast<std::uint64_t>(memory); }
private:
const Context& context;
std::uint8_t* memory = nullptr;
};
std::array<std::uint32_t, 8> Descriptor(std::uint64_t address, std::uint32_t side = Side) {
return {
static_cast<std::uint32_t>(address >> 8u),
@@ -399,6 +531,103 @@ void viewTests(const Context& context) {
Require(heldView.expired() && heldStorage.expired(), "the evicted image outlived its last caller without a sampled lookup");
}
ShaderRecompiler::RecompileResult SampledProgram(std::uint64_t address) {
ShaderRecompiler::DescriptorBinding binding{};
binding.kind = ShaderRecompiler::DescriptorKind::SampledImage;
binding.role = ShaderRecompiler::DescriptorRole::GuestImages;
binding.descriptorSet = 0;
binding.binding = ShaderRecompiler::RuntimeAbi::FirstImageBinding + PlaceholderDimensionsPerClass + 2u;
binding.count = 1;
binding.imageShape = ShaderRecompiler::DescriptorImageShape::Image2D;
binding.imageWritten.assign(1, false);
binding.imageSamplers.assign(1, 0u);
const auto words = Descriptor(address);
binding.guestDescriptor.assign(words.begin(), words.end());
ShaderRecompiler::RecompileResult program;
program.bindings.push_back(std::move(binding));
return program;
}
void replacedEntryTests(Context context) {
PlaceholderImages placeholders(context);
context.placeholderImages = &placeholders;
TextureCache textures(context);
context.textureCache = &textures;
WatchedSurface surface(context);
ClearCachedTextures(context.device);
const auto program = SampledProgram(surface.Address());
const CompiledShader shader{ShaderRecompiler::ShaderStage::Compute, &program, 0};
auto first = std::make_shared<ShaderResources>(context, shader);
Require(first->Reusable(), "the sampled snapshot's descriptor set is not eligible for resource caching");
Require(first->SampledTextures().size() == 1, "the sampled descriptor set binds no texture");
const auto bound = first->SampledTextures().front();
Require(!bound->ViewsStorageImage(), "the replaced-entry fixture is not a snapshot");
Require(Sampled(context, surface.Address()) == bound, "the descriptor set does not bind the cache's texture");
ShaderResources::ProofReport report{};
Require(first->Revalidate(shader), "a template over an unchanged, still cached texture did not prove current");
Require(first->Revalidate(shader, &report), "a template over an unchanged, still cached texture did not prove current twice");
Require(report.path == ShaderResources::ProofPath::Fast, "the fixture's template did not take the fast proof");
Require(first->SampledTextures().front() == bound, "the proof changed the bound texture");
ClearCachedTextures(context.device);
Require(TextureCacheUsage().sampledEntries == 0, "clearing the sampled cache left entries");
Require(!first->Revalidate(shader, &report), "a template binding a texture whose cache entry left still proved current");
const auto replacement = Sampled(context, surface.Address());
Require(replacement != bound, "the lookup after the clear handed back the old texture");
Require(!first->Revalidate(shader), "a template binding the replaced texture proved current against the new entry");
auto rebuilt = std::make_shared<ShaderResources>(context, shader);
Require(rebuilt->SampledTextures().front() == replacement, "the rebuilt descriptor set does not bind the cache's new texture");
Require(rebuilt->Revalidate(shader), "the rebuilt template did not prove current over its cached texture");
first.reset();
rebuilt.reset();
ClearCachedTextures(context.device);
std::puts("template over a replaced sampled cache entry passed");
}
bool lookupChangedTests(Context context) {
PlaceholderImages placeholders(context);
context.placeholderImages = &placeholders;
TextureCache textures(context);
context.textureCache = &textures;
if (context.hostImportAlignment == 0) {
Require(std::getenv("ANYPS5_REQUIRE_VULKAN") == nullptr, "the lookup-changed test requires host imports");
std::puts("skipped, host imports are unavailable");
return false;
}
WatchedSurface surface(context);
ClearCachedTextures(context.device);
const auto program = SampledProgram(surface.Address());
const CompiledShader shader{ShaderRecompiler::ShaderStage::Compute, &program, 0};
auto first = std::make_shared<ShaderResources>(context, shader);
Require(first->Reusable() && first->SampledTextures().size() == 1, "the lookup-changed template is not reusable");
const auto bound = first->SampledTextures().front();
Require(!bound->ViewsStorageImage(), "the lookup-changed fixture is not a snapshot before the import");
ShaderResources::ProofReport report{};
Require(first->Revalidate(shader), "the lookup-changed template did not prove current");
Require(first->Revalidate(shader, &report) && report.path == ShaderResources::ProofPath::Fast, "the lookup-changed template did not take the fast proof");
std::this_thread::sleep_for(std::chrono::milliseconds(300));
Require(first->Revalidate(shader, &report), "the audit refused an unchanged template");
Require(report.path == ShaderResources::ProofPath::Full, "a proof past the audit interval did not take the full walk");
Require(first->SampledTextures().front() == bound, "the audit changed the bound texture");
Require(first->Revalidate(shader, &report) && report.path == ShaderResources::ProofPath::Fast, "the proof right after an audit did not take the fast proof");
if (HostImportFor(context, surface.Address(), SurfaceBytes) == nullptr) {
Require(std::getenv("ANYPS5_REQUIRE_VULKAN") == nullptr, "the host import of the watched surface was refused");
std::puts("skipped, host imports are unavailable");
return false;
}
Require(!bound->Released(), "the snapshot left the cache without a lookup");
std::this_thread::sleep_for(std::chrono::milliseconds(300));
Require(!first->Revalidate(shader, &report), "a template binding a snapshot of a surface its lookup now views through a storage image still proved current past the audit interval");
const auto view = Sampled(context, surface.Address());
Require(view != bound && view->ViewsStorageImage(), "the lookup of the imported surface does not view its storage image");
auto rebuilt = std::make_shared<ShaderResources>(context, shader);
Require(rebuilt->SampledTextures().front() == view, "the rebuilt template does not bind the storage view");
first.reset();
rebuilt.reset();
ClearCachedTextures(context.device);
std::puts("template over a changed sampled lookup passed");
return true;
}
void reportedBudgetTests(const Context& context) {
if (context.memoryProperties2 == nullptr) {
std::puts("VK_EXT_memory_budget unavailable: the sampled texture cache keeps a quarter of the device-local heap");
@@ -731,7 +960,7 @@ std::uint64_t ReadBudget(const Context& context) {
return SampledTextureCacheBudget(context);
}
constexpr std::uint64_t PressedUsage = ReportedBudget - 11ull * (1ull << 30u);
constexpr std::uint64_t PressedUsage = ReportedBudget - 9ull * (1ull << 30u);
void VKAPI_PTR ReportPressure(VkPhysicalDevice, VkPhysicalDeviceMemoryProperties2* properties) {
auto* reported = static_cast<VkPhysicalDeviceMemoryBudgetPropertiesEXT*>(properties->pNext);
@@ -772,8 +1001,8 @@ void storageMemoryTests(Context context) {
Require(ReadStorageBudget(context) == quarter, "52 GiB free of a 64 GiB budget does not leave the storage image cache its quarter");
context.memoryProperties2 = &ReportPressure;
const auto pressed = ReadStorageBudget(context);
const auto expected = GiB + SampledTextureMemory() + TextureCacheUsage().storageBytes;
Require(pressed == expected, "53 GiB in use of a 64 GiB budget leaves the storage image cache " + std::to_string(pressed >> 20u) + " MiB, not the " + std::to_string(expected >> 20u) + " MiB left after the use outside the texture caches, the sampled textures' 2 GiB floor and 8 GiB of headroom");
const auto expected = GiB + TextureCacheUsage().storageBytes;
Require(pressed == expected, "55 GiB in use of a 64 GiB budget leaves the storage image cache " + std::to_string(pressed >> 20u) + " MiB, not the " + std::to_string(expected >> 20u) + " MiB left after the use outside the texture caches, the sampled textures and 8 GiB of headroom");
std::printf("reported heap budget %llu MiB with %llu MiB in use: storage image cache budget %llu MiB\n", static_cast<unsigned long long>(ReportedBudget >> 20u), static_cast<unsigned long long>(PressedUsage >> 20u), static_cast<unsigned long long>(pressed >> 20u));
}
@@ -783,21 +1012,32 @@ int main(int argc, char** argv) {
try {
const bool memory = argc > 1 && std::string(argv[1]) == "memory";
const std::string mode = argc > 1 ? argv[1] : "";
if (mode == "device-selection-cpu") {
deviceSelectionTests();
return 0;
}
const bool resources = mode == "resource-admission" || mode == "resource-oversized" || mode == "resource-clear";
const bool disableStorageImageMultisample = mode == "without-storage-image-multisample";
#ifdef _WIN32
if (!memory) _putenv_s("APS5_TEXTURE_CACHE_MIB", "1");
if (std::getenv("APS5_TEMPLATE_AUDIT_MS") == nullptr) _putenv_s("APS5_TEMPLATE_AUDIT_MS", "250");
#else
if (!memory) setenv("APS5_TEXTURE_CACHE_MIB", "1", 1);
setenv("APS5_TEMPLATE_AUDIT_MS", "250", 0);
#endif
std::unique_ptr<Device> device;
try {
device = std::make_unique<Device>(!disableStorageImageMultisample);
} catch (const std::exception& error) {
if (std::getenv("ANYPS5_REQUIRE_VULKAN") != nullptr) throw;
const char* requestedName = std::getenv("ANYPS5_GPU");
if (std::getenv("ANYPS5_REQUIRE_VULKAN") != nullptr || (requestedName != nullptr && *requestedName != '\0')) throw;
std::printf("skipped, no usable Vulkan device: %s\n", error.what());
return VulkanTestSkipped;
}
if (mode == "device-selection") {
device->VerifySelection();
return 0;
}
auto context = device->GetContext();
if (disableStorageImageMultisample) {
PlaceholderImages placeholders(context);
@@ -822,6 +1062,18 @@ int main(int argc, char** argv) {
std::puts("resource cache tests passed");
return 0;
}
if (mode == "resource-replaced") {
std::lock_guard gpu(AgcDriver::GuestMemory::GpuMutex());
replacedEntryTests(context);
std::puts("resource cache replaced entry tests passed");
return 0;
}
if (mode == "resource-lookup-changed") {
std::lock_guard gpu(AgcDriver::GuestMemory::GpuMutex());
if (!lookupChangedTests(context)) return VulkanTestSkipped;
std::puts("resource cache lookup-changed tests passed");
return 0;
}
if (memory) {
std::lock_guard gpu(AgcDriver::GuestMemory::GpuMutex());
memoryTests(context);
@@ -274,6 +274,22 @@ void Run(AgcDriver::VulkanDevice& device, std::span<const std::uint32_t> code, s
device.WaitIdle();
}
std::vector<std::uint32_t> Scheduled(std::span<const std::uint32_t> original) {
std::vector<std::uint32_t> code(original.begin(), original.end());
code.insert(code.begin() + 25, 0x7e3c0280u);
code.insert(code.begin() + 24, 0x7e3e0280u);
const auto relocated = [](std::int32_t word) { return word + (word >= 24 ? 1 : 0) + (word >= 25 ? 1 : 0); };
for (std::int32_t word = 0; word < static_cast<std::int32_t>(original.size()); ++word) {
const auto instruction = original[static_cast<std::size_t>(word)];
const auto opcode = instruction & 0xffff0000u;
if (opcode != 0xbf820000u && opcode != 0xbf850000u) continue;
const auto target = word + 1 + static_cast<std::int16_t>(instruction & 0xffffu);
const auto offset = relocated(target) - relocated(word) - 1;
code[static_cast<std::size_t>(relocated(word))] = opcode | (static_cast<std::uint32_t>(offset) & 0xffffu);
}
return code;
}
void CheckReversedSubtractRefused(AgcDriver::VulkanDevice& device) {
std::string refusal;
try {
@@ -306,6 +322,14 @@ int main() {
Check(Expected64);
Run(*device, Wave64Code, 64, device->ComputeTarget(32));
Check(Expected64);
const auto scheduled32 = Scheduled(Wave32Code);
const auto scheduled64 = Scheduled(Wave64Code);
Run(*device, scheduled32, 32, device->ComputeTarget(32));
Check(Expected32);
Run(*device, scheduled64, 64, device->Target());
Check(Expected64);
Run(*device, scheduled64, 64, device->ComputeTarget(32));
Check(Expected64);
CheckReversedSubtractRefused(*device);
std::puts("setpc tests passed");
return 0;
@@ -1,5 +1,6 @@
#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp"
#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp"
#include "../SubvectorLoopFixture.hpp"
#include "Recompiler.hpp"
#include "VulkanTestDevice.hpp"
#include <algorithm>
@@ -17,6 +18,7 @@ namespace {
using AgcDriver::Graphics::Require;
using ShaderRecompiler::ShaderStage;
using SubvectorLoopFixture::ExecKeptCode;
constexpr std::uint32_t Threads = 64;
constexpr std::uint32_t Columns = 9;
@@ -25,16 +27,6 @@ constexpr std::uint32_t Marker = 0x5a5a5a5au;
alignas(256) std::array<std::uint32_t, Threads> Input{};
alignas(256) std::array<std::uint32_t, Threads * Columns> Output{};
alignas(256) constexpr std::array<std::uint32_t, 49> ExecKeptCode{
0x34020082, 0xe0301000, 0x80000401, 0xbf8c3f70, 0x7e140280, 0x7e160280, 0x7e180280, 0x7e1a0280,
0x7e1c0280, 0xbe940380, 0xbea603ff, 0x5a5a5a5a, 0x7daa0880, 0xbda6000a, 0x80148114, 0x4a141481,
0x7e160214, 0x7e180226, 0xbe96047e, 0x7e1a0216, 0x7e1c0217, 0xbf098414, 0xbf850001, 0xbe26fff6,
0xbe98047e, 0xbefe04c1, 0x7e1e0226, 0x7e200218, 0x7e220219, 0x7e240214, 0xe0701000, 0x80010a01,
0xe0701100, 0x80010b01, 0xe0701200, 0x80010c01, 0xe0701300, 0x80010d01, 0xe0701400, 0x80010e01,
0xe0701500, 0x80010f01, 0xe0701600, 0x80011001, 0xe0701700, 0x80011101, 0xe0701800, 0x80011201,
0xbf810000,
};
alignas(256) constexpr std::array<std::uint32_t, 50> ExecResetCode{
0x34020082, 0xe0301000, 0x80000401, 0xbf8c3f70, 0x7e140280, 0x7e160280, 0x7e180280, 0x7e1a0280,
0x7e1c0280, 0xbe940380, 0xbea603ff, 0x5a5a5a5a, 0x7daa0880, 0xbda6000b, 0x80148114, 0x4a141481,
@@ -326,6 +326,90 @@ alignas(256) constexpr std::array<std::uint32_t, 38> QuadDomainCode{
0x7e3402f2, 0xf80008cf, 0x1a191716, 0xf800020f, 0x1a19181a, 0xbf810000,
};
alignas(256) constexpr std::array<std::uint32_t, 87> MergedHullCode{
0xbefe04c1, 0xd7650000, 0x000100c1, 0xd7660000, 0x000200c1, 0x7da206f9, 0x80060000, 0xbf88001c,
0xd5480004, 0x02090502, 0xd5480005, 0x02090102, 0xd5480006, 0x02110902, 0xd5480007, 0x02111102,
0x7e080d04, 0x7e0a0d05, 0x7e0c0d06, 0x7e0e0d07, 0x10100cff, 0x3f19999a, 0x061010ff, 0xbf666666,
0x3e1008ff, 0x3e800000, 0xd54b0009, 0x03c5e105, 0x161406a0, 0xd9340000, 0x0000080a, 0xd8340008,
0x0000070a, 0xd8340010, 0x0000070a, 0xbf8cc07f, 0xbefe04c1, 0xbf8cc07f, 0xd7650000, 0x000100c1,
0xd7660000, 0x000200c1, 0xbf8a0000, 0x7da206f9, 0x81060000, 0xd5480014, 0x02151101, 0x362402ff,
0x000000ff, 0x160028a0, 0xd5430015, 0x040224ff, 0x00000060, 0xdbfc0000, 0x02000015, 0x162c2890,
0xd5430016, 0x045a24ff, 0x00000030, 0xbf8cc07f, 0xe0781000, 0x02020216, 0x7da82881, 0xbe94047e,
0xb06a0060, 0x162602f9, 0x0086066a, 0xd8d80048, 0x06000013, 0xd8dc1008, 0x07000013, 0xbf8cc07f,
0x7e300306, 0x7e320306, 0x7e340306, 0x7e360306, 0x34142484, 0xbefe0414, 0xe0781000, 0x0403180a,
0x4a162481, 0x16161690, 0x4c1616ff, 0x00008000, 0xe0741000, 0x0202070b, 0xbf810000,
};
alignas(256) constexpr std::array<std::uint32_t, 87> MergedHullRegisters{
0xbefe04c1, 0xd7650000, 0x000100c1, 0xd7660000, 0x000200c1, 0x7da206f9, 0x80060000, 0xbf88001c,
0xd5480004, 0x02090502, 0xd5480005, 0x02090102, 0xd5480006, 0x02110902, 0xd5480007, 0x02111102,
0x7e080d04, 0x7e0a0d05, 0x7e0c0d06, 0x7e0e0d07, 0x10100cff, 0x3f19999a, 0x061010ff, 0xbf666666,
0x3e1008ff, 0x3e800000, 0xd54b0009, 0x03c5e105, 0x161406a0, 0xd9340000, 0x0000080a, 0xd8340008,
0x0000070a, 0xd8340010, 0x0000070a, 0xbf8cc07f, 0xbefe04c1, 0xbf8cc07f, 0xd7650000, 0x000100c1,
0xd7660000, 0x000200c1, 0xbf8a0000, 0x7da206f9, 0x81060000, 0xd5480014, 0x02151101, 0x362402ff,
0x000000ff, 0x160028a0, 0xd5430015, 0x040224ff, 0x00000060, 0xdbfc0000, 0x02000015, 0x162c2890,
0xd5430016, 0x045a24ff, 0x00000030, 0xbf8cc07f, 0xe0741000, 0x02020816, 0x7da82881, 0xbe94047e,
0xb06a0060, 0x162602f9, 0x0086066a, 0xd8d80048, 0x06000013, 0xd8dc1008, 0x07000013, 0xbf8cc07f,
0x7e300306, 0x7e320306, 0x7e340306, 0x7e360306, 0x34142484, 0xbefe0414, 0xe0781000, 0x0403180a,
0x4a162481, 0x16161690, 0x4c1616ff, 0x00008000, 0xe0741000, 0x0202070b, 0xbf810000,
};
alignas(256) constexpr std::array<std::uint32_t, 87> MergedHullForeignRecord{
0xbefe04c1, 0xd7650000, 0x000100c1, 0xd7660000, 0x000200c1, 0x7da206f9, 0x80060000, 0xbf88001c,
0xd5480004, 0x02090502, 0xd5480005, 0x02090102, 0xd5480006, 0x02110902, 0xd5480007, 0x02111102,
0x7e080d04, 0x7e0a0d05, 0x7e0c0d06, 0x7e0e0d07, 0x10100cff, 0x3f19999a, 0x061010ff, 0xbf666666,
0x3e1008ff, 0x3e800000, 0xd54b0009, 0x03c5e105, 0x161406a0, 0xd9340020, 0x0000080a, 0xd8340008,
0x0000070a, 0xd8340010, 0x0000070a, 0xbf8cc07f, 0xbefe04c1, 0xbf8cc07f, 0xd7650000, 0x000100c1,
0xd7660000, 0x000200c1, 0xbf8a0000, 0x7da206f9, 0x81060000, 0xd5480014, 0x02151101, 0x362402ff,
0x000000ff, 0x160028a0, 0xd5430015, 0x040224ff, 0x00000060, 0xdbfc0000, 0x02000015, 0x162c2890,
0xd5430016, 0x045a24ff, 0x00000030, 0xbf8cc07f, 0xe0781000, 0x02020216, 0x7da82881, 0xbe94047e,
0xb06a0060, 0x162602f9, 0x0086066a, 0xd8d80048, 0x06000013, 0xd8dc1008, 0x07000013, 0xbf8cc07f,
0x7e300306, 0x7e320306, 0x7e340306, 0x7e360306, 0x34142484, 0xbefe0414, 0xe0781000, 0x0403180a,
0x4a162481, 0x16161690, 0x4c1616ff, 0x00008000, 0xe0741000, 0x0202070b, 0xbf810000,
};
alignas(256) constexpr std::array<std::uint32_t, 88> MergedHullDivergentBarrier{
0xbefe04c1, 0xd7650000, 0x000100c1, 0xd7660000, 0x000200c1, 0x7da206f9, 0x80060000, 0xbf88001d,
0xd5480004, 0x02090502, 0xd5480005, 0x02090102, 0xd5480006, 0x02110902, 0xd5480007, 0x02111102,
0x7e080d04, 0x7e0a0d05, 0x7e0c0d06, 0x7e0e0d07, 0x10100cff, 0x3f19999a, 0x061010ff, 0xbf666666,
0x3e1008ff, 0x3e800000, 0xd54b0009, 0x03c5e105, 0x161406a0, 0xd9340000, 0x0000080a, 0xd8340008,
0x0000070a, 0xd8340010, 0x0000070a, 0xbf8a0000, 0xbf8cc07f, 0xbefe04c1, 0xbf8cc07f, 0xd7650000,
0x000100c1, 0xd7660000, 0x000200c1, 0xbf8a0000, 0x7da206f9, 0x81060000, 0xd5480014, 0x02151101,
0x362402ff, 0x000000ff, 0x160028a0, 0xd5430015, 0x040224ff, 0x00000060, 0xdbfc0000, 0x02000015,
0x162c2890, 0xd5430016, 0x045a24ff, 0x00000030, 0xbf8cc07f, 0xe0781000, 0x02020216, 0x7da82881,
0xbe94047e, 0xb06a0060, 0x162602f9, 0x0086066a, 0xd8d80048, 0x06000013, 0xd8dc1008, 0x07000013,
0xbf8cc07f, 0x7e300306, 0x7e320306, 0x7e340306, 0x7e360306, 0x34142484, 0xbefe0414, 0xe0781000,
0x0403180a, 0x4a162481, 0x16161690, 0x4c1616ff, 0x00008000, 0xe0741000, 0x0202070b, 0xbf810000,
};
alignas(256) constexpr std::array<std::uint32_t, 87> MergedHullOutsidePatch{
0xbefe04c1, 0xd7650000, 0x000100c1, 0xd7660000, 0x000200c1, 0x7da206f9, 0x80060000, 0xbf88001c,
0xd5480004, 0x02090502, 0xd5480005, 0x02090102, 0xd5480006, 0x02110902, 0xd5480007, 0x02111102,
0x7e080d04, 0x7e0a0d05, 0x7e0c0d06, 0x7e0e0d07, 0x10100cff, 0x3f19999a, 0x061010ff, 0xbf666666,
0x3e1008ff, 0x3e800000, 0xd54b0009, 0x03c5e105, 0x161406a0, 0xd9340000, 0x0000080a, 0xd8340008,
0x0000070a, 0xd8340010, 0x0000070a, 0xbf8cc07f, 0xbefe04c1, 0xbf8cc07f, 0xd7650000, 0x000100c1,
0xd7660000, 0x000200c1, 0xbf8a0000, 0x7da206f9, 0x81060000, 0xd5480014, 0x02151101, 0x362402ff,
0x000000ff, 0x160028a0, 0xd5430015, 0x040224ff, 0x00000060, 0xdbfc0000, 0x02000015, 0x162c2890,
0xd5430016, 0x045a24ff, 0x00000030, 0xbf8cc07f, 0xe0781000, 0x02020216, 0x7da82881, 0xbe94047e,
0xb06a0060, 0x162602f9, 0x0086066a, 0xd8d80060, 0x06000013, 0xd8dc1008, 0x07000013, 0xbf8cc07f,
0x7e300306, 0x7e320306, 0x7e340306, 0x7e360306, 0x34142484, 0xbefe0414, 0xe0781000, 0x0403180a,
0x4a162481, 0x16161690, 0x4c1616ff, 0x00008000, 0xe0741000, 0x0202070b, 0xbf810000,
};
alignas(256) constexpr std::array<std::uint32_t, 87> MergedHullReadLane{
0xbefe04c1, 0xd7650000, 0x000100c1, 0xd7660000, 0x000200c1, 0x7da206f9, 0x80060000, 0xbf88001c,
0xd5480004, 0x02090502, 0xd5480005, 0x02090102, 0xd5480006, 0x02110902, 0xd5480007, 0x02111102,
0x7e080d04, 0x7e0a0d05, 0x7e0c0d06, 0x7e0e0d07, 0x10100cff, 0x3f19999a, 0x061010ff, 0xbf666666,
0x3e1008ff, 0x3e800000, 0xd54b0009, 0x03c5e105, 0x161406a0, 0xd9340000, 0x0000080a, 0xd8340008,
0x0000070a, 0xd8340010, 0x0000070a, 0xbf8cc07f, 0xbefe04c1, 0xbf8cc07f, 0xd7650000, 0x000100c1,
0xd7660000, 0x000200c1, 0xbf8a0000, 0x7da206f9, 0x81060000, 0xd5480014, 0x02151101, 0x362402ff,
0x000000ff, 0x160028a0, 0xd5430015, 0x040224ff, 0x00000060, 0xdbfc0000, 0x02000015, 0x162c2890,
0xd5430016, 0x045a24ff, 0x00000030, 0xbf8cc07f, 0xe0781000, 0x02020216, 0x7da82881, 0xbe94047e,
0xb06a0060, 0x162602f9, 0x0086066a, 0xd8d80048, 0x06000013, 0xd8dc1008, 0x07000013, 0xbf8cc07f,
0xd7600010, 0x02010106, 0x7e300210, 0x7e360306, 0x34142484, 0xbefe0414, 0xe0781000, 0x0403180a,
0x4a162481, 0x16161690, 0x4c1616ff, 0x00008000, 0xe0741000, 0x0202070b, 0xbf810000,
};
alignas(256) constexpr std::array<std::uint32_t, 7> PixelCode{
0xc8020002, 0xc8060102, 0xc80a0202, 0xc80e0302, 0xf800180f, 0x03020100, 0xbf810000,
};
@@ -333,6 +417,7 @@ alignas(256) constexpr std::array<std::uint32_t, 7> PixelCode{
using ShaderRecompiler::TessellationConfiguration;
constexpr TessellationConfiguration Patches{3u, 3u, 1u, 2u, 2u};
constexpr TessellationConfiguration MergedPatches{3u, 3u, 1u, 2u, 2u, 256u, 0u, false, 0u, false, true};
constexpr std::array<std::uint32_t, 3> PatchLevels{1u, 0u, 5u};
constexpr std::uint16_t Corner(std::uint32_t patch, std::uint32_t x, std::uint32_t y) {
@@ -379,6 +464,7 @@ alignas(256) constexpr std::array<std::uint16_t, 12> NextInputIndices{
const PatchDraw Triangles{Patches, Indices};
const PatchDraw NextInputTriangles{{4u, 3u, 1u, 2u, 2u}, NextInputIndices};
const PatchDraw MergedTriangles{MergedPatches, Indices};
struct Stages {
ShaderRecompiler::RecompileResult local;
@@ -842,6 +928,31 @@ void RequireSingleControlPointHulls(const ShaderRecompiler::SpirvTarget& target)
Require(!CompileHull(target, HullSingleOutput, {1u, 1u, 1u, 2u, 2u}).spirv.empty(), "a hull with one output control point and unguarded patch outputs was not compiled");
}
void RequireMergedHullWithoutSubgroups(const ShaderRecompiler::SpirvTarget& target) {
std::uint32_t cursor = 0;
const auto hull = Compile(target, ShaderStage::TessellationControl, MergedHullCode, cursor, MergedTriangles);
const auto words = hull.spirv.Words();
for (std::size_t at = 5; at + 1u < words.size(); at += words[at] >> 16u) {
Require((words[at] >> 16u) != 0u, "the merged hull program compiled to a SPIR-V instruction without words");
Require((words[at] & 0xffffu) != spv::OpCapability || words[at + 1u] != spv::CapabilityGroupNonUniform, "the hull stage of a merged hull program declares subgroup operations, which a device without tessellation control subgroups rejects");
}
}
void RequireRegisteredMergedStages() {
const auto low = [](const auto& code) { return static_cast<std::uint32_t>(reinterpret_cast<std::uintptr_t>(code.data()) >> 8u); };
const auto high = [](const auto& code) { return static_cast<std::uint32_t>(reinterpret_cast<std::uintptr_t>(code.data()) >> 40u); };
RegisteredStage merged;
RegisteredStage domain;
Describe(merged, MergedHullCode, 3u, {{0x148u, low(MergedHullCode)}, {0x149u, high(MergedHullCode)}, {0x10au, 0u}, {0x10bu, 16u}}, {{0x2dbu, 0x40049u}, {0x286u, 0x42800000u}, {0x287u, 0x3f800000u}, {0x2d6u, 0xc355u}}, 0x105u);
Describe(domain, DomainCode, 2u, {{0xc8u, low(DomainCode)}, {0xc9u, high(DomainCode)}, {0x8au, 0u}, {0x8bu, 16u}}, {}, 0x02002008u);
for (const auto* stage : {&merged, &domain}) AgcDriverRegisterShader_nid_postfix(&stage->shader);
const std::array<const Shader*, 2> stages{&merged.shader, &domain.shader};
const std::array<ShaderRegister, 2> context{{{0x2d5u, 0x0200210du}, {0x29bu, 0u}}};
const std::array<ShaderRegister, 3> primitive{{{0x25bu, 0u}, {0x262u, 0u}, {0x242u, 9u}}};
AgcDriverResolveGraphicsStagesAbi_nid_postfix(stages, context, primitive);
AgcDriverResolveGraphicsStagesAbi_nid_postfix(stages, context, primitive);
}
}
int main() {
@@ -877,6 +988,14 @@ int main() {
RequirePatches(*device, target, HullAndSaveexec, "hull program narrowing exec with s_and_saveexec_b64");
RequirePatches(*device, target, HullSaveexecLoop, "hull program restoring exec through loop phis");
RequirePatches(*device, target, HullDescendingRecords, "hull program with descending patch records in 128-patch groups", {{3u, 3u, 1u, 2u, 2u, 128u}, Indices});
ExpectRefusal(target, ShaderStage::TessellationControl, MergedHullCode, "more input than output control points", {{4u, 3u, 1u, 2u, 2u, 256u, 0u, false, 0u, false, true}, Indices});
ExpectRefusal(target, ShaderStage::TessellationControl, MergedHullForeignRecord, "outside its own control point's record", MergedTriangles);
ExpectRefusal(target, ShaderStage::TessellationControl, MergedHullDivergentBarrier, "s_barrier that some invocations do not reach", MergedTriangles);
ExpectRefusal(target, ShaderStage::TessellationControl, MergedHullOutsidePatch, "reads LDS outside its patch's control point records", MergedTriangles);
ExpectRefusal(target, ShaderStage::TessellationControl, MergedHullReadLane, "another lane's register", MergedTriangles);
RequireMergedHullWithoutSubgroups(target);
RequirePatches(*device, target, MergedHullCode, "merged hull program", MergedTriangles, MergedHullCode);
RequirePatches(*device, target, MergedHullRegisters, "merged hull program storing its local part's registers", MergedTriangles, MergedHullRegisters);
RequireCounterClockwise(*device, target);
RequireLevelClamps(*device, target);
RequireIntegerPartitioning(*device, target);
@@ -888,6 +1007,7 @@ int main() {
RequireHullBdaReads(*device, target);
RequireCompareBranches(target);
device.reset();
RequireRegisteredMergedStages();
RequireRegisteredStages();
std::puts("Tessellation tests passed");
return 0;
@@ -155,9 +155,135 @@ constexpr const char* Names[16] = {
"v_cubema_f32 v25, -v4, |v5|, v6 clamp",
};
void Fill(std::uint32_t tid, std::uint32_t* words) {
std::copy(std::begin(Rows[tid]), std::end(Rows[tid]), words);
}
alignas(256) constexpr std::array<std::uint32_t, 46> OmodCode{
0x34020084, 0x34060086, 0xe0381000, 0x80000401, 0xbf8c3f70, 0xd544000a, 0x0c1a0b04, 0xd544000b,
0x1c1a0b04, 0xd545000c, 0x0c1a0b04, 0xd545000d, 0x141a0b04, 0xd545000e, 0x1c1a0b04, 0xd546000f,
0x0c1a0b04, 0xd5460010, 0x141a0b04, 0xd5460011, 0x1c1a0b04, 0xd5470012, 0x0c1a0b04, 0xd5470013,
0x141a0b04, 0xd5470014, 0x1c1a0b04, 0xd5440015, 0xdc1a0b04, 0xd5458216, 0x341a0b04, 0xd5468117,
0x5c1a0b04, 0xd5478018, 0x8c1a0b04, 0xd5440019, 0x141a0b04, 0xe0781000, 0x80010a03, 0xe0781010,
0x80010e03, 0xe0781020, 0x80011203, 0xe0781030, 0x80011603, 0xbf810000,
};
constexpr std::uint32_t OmodRows[32][4] = {
{0x3f800000u, 0x3f000000u, 0x3e800000u, 0x00000000u},
{0xbf800000u, 0x3f000000u, 0x3e800000u, 0x00000000u},
{0x3f000000u, 0x3f800000u, 0x3e800000u, 0x00000000u},
{0x3f000000u, 0xbf800000u, 0xbe800000u, 0x00000000u},
{0x3e800000u, 0x3f000000u, 0x3f800000u, 0x00000000u},
{0x3e800000u, 0xbf000000u, 0xbf800000u, 0x00000000u},
{0x40400000u, 0xc0e00000u, 0x40000000u, 0x00000000u},
{0xc0200000u, 0x3fc00000u, 0x40c00000u, 0x00000000u},
{0x7f7fffffu, 0x7f000000u, 0xff000000u, 0x00000000u},
{0xff7fffffu, 0x7e800000u, 0x7effffffu, 0x00000000u},
{0x3f800000u, 0x00800000u, 0x80ffffffu, 0x00000000u},
{0x3f800000u, 0x01000000u, 0x81000000u, 0x00000000u},
{0x3f800000u, 0x00000007u, 0x80000007u, 0x00000000u},
{0x3f800000u, 0x80000000u, 0x00000000u, 0x00000000u},
{0x00800000u, 0x00000000u, 0x80000000u, 0x00000000u},
{0x00000007u, 0x00000003u, 0x00000001u, 0x00000000u},
{0x7f800000u, 0x3f800000u, 0x40000000u, 0x00000000u},
{0xff800000u, 0x7f800000u, 0x3f800000u, 0x00000000u},
{0x7fc00000u, 0x3f800000u, 0x3f000000u, 0x00000000u},
{0x3f800000u, 0xffc00000u, 0x3f000000u, 0x00000000u},
{0x40000000u, 0x3f800000u, 0x7fc00000u, 0x00000000u},
{0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u},
{0x80000000u, 0x80000000u, 0x80000000u, 0x00000000u},
{0x7149f2cau, 0xf149f2cau, 0x71c9f2cau, 0x00000000u},
{0x0da24260u, 0x0e224260u, 0x8e736390u, 0x00000000u},
{0x7e800000u, 0x7e7fffffu, 0x3f800000u, 0x00000000u},
{0x40000000u, 0x7e800000u, 0x7effffffu, 0x00000000u},
{0x3f000000u, 0xbf000000u, 0x3f000000u, 0x00000000u},
{0xc0c5edadu, 0xc08852a3u, 0xc0c18eacu, 0x00000000u},
{0xbf3d7ad8u, 0xc0737ab3u, 0xc0e5e3c1u, 0x00000000u},
{0x3f000000u, 0x3e800000u, 0xff812345u, 0x00000000u},
{0x7f812345u, 0x3f000000u, 0x3e800000u, 0x00000000u}
};
constexpr std::uint32_t OmodExpected[32][16] = {
{0x00000000u, 0x00000000u, 0xbf000000u, 0xbf800000u, 0xbe000000u, 0xbf800000u, 0xc0000000u, 0xbe800000u, 0x40800000u, 0x41000000u, 0x3f800000u, 0x00000000u, 0x3f800000u, 0x3e800000u, 0x3f800000u, 0x00000000u},
{0x40000000u, 0x3f000000u, 0x3f000000u, 0x3f800000u, 0x3e000000u, 0xbf800000u, 0xc0000000u, 0xbe800000u, 0xc0800000u, 0xc1000000u, 0xbf800000u, 0x3f000000u, 0x00000000u, 0x3e800000u, 0x00000000u, 0x40800000u},
{0x40800000u, 0x3f800000u, 0x3f800000u, 0x40000000u, 0x3e800000u, 0x3f000000u, 0x3f800000u, 0x3e000000u, 0x40800000u, 0x41000000u, 0x3f800000u, 0x3fc00000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x41000000u},
{0x40c00000u, 0x3fc00000u, 0x3f800000u, 0x40000000u, 0x3e800000u, 0x3f000000u, 0x3f800000u, 0x3e000000u, 0xc0800000u, 0xc1000000u, 0xbf800000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x41400000u},
{0x41000000u, 0x40000000u, 0x3f000000u, 0x3f800000u, 0x3e000000u, 0xbf800000u, 0xc0000000u, 0xbe800000u, 0x40800000u, 0x41000000u, 0x3f800000u, 0x40200000u, 0x00000000u, 0x3e800000u, 0x00000000u, 0x41800000u},
{0x41200000u, 0x40200000u, 0xbf000000u, 0xbf800000u, 0xbe000000u, 0x3f800000u, 0x40000000u, 0x3e800000u, 0xc0800000u, 0xc1000000u, 0xbf800000u, 0x40000000u, 0x3f800000u, 0x00000000u, 0x3f800000u, 0x41a00000u},
{0x40c00000u, 0x3fc00000u, 0x40c00000u, 0x41400000u, 0x3fc00000u, 0xc0800000u, 0xc1000000u, 0xbf800000u, 0xc1e00000u, 0xc2600000u, 0xc0e00000u, 0x3f800000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x41400000u},
{0x41000000u, 0x40000000u, 0xc0a00000u, 0xc1200000u, 0xbfa00000u, 0xc0400000u, 0xc0c00000u, 0xbf400000u, 0x41c00000u, 0x42400000u, 0x40c00000u, 0x40200000u, 0x3f800000u, 0x3f400000u, 0x00000000u, 0x41800000u},
{0x00000000u, 0x00000000u, 0x7f800000u, 0x7f800000u, 0x7e800000u, 0xff800000u, 0xff800000u, 0xfe800000u, 0x7f800000u, 0x7f800000u, 0x7f800000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x3f800000u, 0x00000000u},
{0x40000000u, 0x3f000000u, 0x7f7fffffu, 0x7f800000u, 0x7e7fffffu, 0xff000000u, 0xff800000u, 0xfe000000u, 0xff800000u, 0xff800000u, 0xff800000u, 0x3f000000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x40800000u},
{0x00000000u, 0x00000000u, 0x017fffffu, 0x01ffffffu, 0x00000000u, 0x81000000u, 0x81800000u, 0x80000000u, 0x40800000u, 0x41000000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x00000000u},
{0x00000000u, 0x00000000u, 0x01800000u, 0x02000000u, 0x00800000u, 0x81800000u, 0x82000000u, 0x80800000u, 0x40800000u, 0x41000000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x00800000u, 0x3f800000u, 0x00000000u},
{0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x40800000u, 0x41000000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x00000000u},
{0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x40800000u, 0x41000000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x00000000u},
{0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x01800000u, 0x02000000u, 0x00800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x01800000u, 0x00000000u},
{0x41000000u, 0x40000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x40000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x41800000u},
{0x00000000u, 0x00000000u, 0xc0800000u, 0xc1000000u, 0xbf800000u, 0xc0000000u, 0xc0800000u, 0xbf000000u, 0x7f800000u, 0x7f800000u, 0x7f800000u, 0x00000000u, 0x3f800000u, 0x3f000000u, 0x3f800000u, 0x00000000u},
{0x40800000u, 0x3f800000u, 0xff800000u, 0xff800000u, 0xff800000u, 0x40000000u, 0x40800000u, 0x3f000000u, 0x7f800000u, 0x7f800000u, 0x7f800000u, 0x3fc00000u, 0x3f800000u, 0x00000000u, 0x3f800000u, 0x41000000u},
{0x00000000u, 0x00000000u, 0xbf800000u, 0xc0000000u, 0xbe800000u, 0xc0000000u, 0xc0800000u, 0xbf000000u, 0x7fc00000u, 0x7fc00000u, 0x7fc00000u, 0x00000000u, 0x00000000u, 0x3f000000u, 0x00000000u, 0x00000000u},
{0x00000000u, 0x00000000u, 0xbf800000u, 0xc0000000u, 0xbe800000u, 0x7fc00000u, 0x7fc00000u, 0x7fc00000u, 0x40800000u, 0x41000000u, 0x3f800000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x3f800000u, 0x00000000u},
{0x00000000u, 0x00000000u, 0xffc00000u, 0xffc00000u, 0xffc00000u, 0xc0000000u, 0xc0800000u, 0xbf000000u, 0x41000000u, 0x41800000u, 0x40000000u, 0x00000000u, 0x00000000u, 0x3f000000u, 0x3f800000u, 0x00000000u},
{0x41000000u, 0x40000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x40000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x41800000u},
{0x41000000u, 0x40000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x40000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x41800000u},
{0x41000000u, 0x40000000u, 0x71c9f2cau, 0x7249f2cau, 0x70c9f2cau, 0x71c9f2cau, 0x7249f2cau, 0x70c9f2cau, 0x72c9f2cau, 0x7349f2cau, 0x71c9f2cau, 0x40200000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x41800000u},
{0x41200000u, 0x40200000u, 0x8e224260u, 0x8ea24260u, 0x8d224260u, 0x8ea24260u, 0x8f224260u, 0x8da24260u, 0x8f736390u, 0x8ff36390u, 0x8e736390u, 0x40000000u, 0x0ea24260u, 0x0da24260u, 0x0f736390u, 0x41a00000u},
{0x00000000u, 0x00000000u, 0xc0000000u, 0xc0800000u, 0xbf000000u, 0xfeffffffu, 0xff7fffffu, 0xfdffffffu, 0x7f800000u, 0x7f800000u, 0x7e800000u, 0x00000000u, 0x3f800000u, 0x3f800000u, 0x3f800000u, 0x00000000u},
{0x41000000u, 0x40000000u, 0x40800000u, 0x41000000u, 0x3f800000u, 0xff000000u, 0xff800000u, 0xfe000000u, 0x7f800000u, 0x7f800000u, 0x7effffffu, 0x40200000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x41800000u},
{0x41000000u, 0x40000000u, 0x3f800000u, 0x40000000u, 0x3e800000u, 0x3f800000u, 0x40000000u, 0x3e800000u, 0x40000000u, 0x40800000u, 0x3f000000u, 0x40200000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x41800000u},
{0x40000000u, 0x3f000000u, 0xc1418eacu, 0xc1c18eacu, 0xc0418eacu, 0x410852a3u, 0x418852a3u, 0x400852a3u, 0xc1c5edadu, 0xc245edadu, 0xc0c5edadu, 0x3f000000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x40800000u},
{0x41200000u, 0x40200000u, 0x3fbd7ad8u, 0x403d7ad8u, 0x3ebd7ad8u, 0x40f37ab3u, 0x41737ab3u, 0x3ff37ab3u, 0xc1e5e3c1u, 0xc265e3c1u, 0xc0e5e3c1u, 0x40000000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x41a00000u},
{0x00000000u, 0x00000000u, 0x7f812345u, 0x7f812345u, 0x7f812345u, 0xbf000000u, 0xbf800000u, 0xbe000000u, 0x40000000u, 0x40800000u, 0x3f000000u, 0x00000000u, 0x00000000u, 0x3e000000u, 0x3f800000u, 0x00000000u},
{0x00000000u, 0x00000000u, 0xbf000000u, 0xbf800000u, 0xbe000000u, 0xbf800000u, 0xc0000000u, 0xbe800000u, 0x7f812345u, 0x7f812345u, 0x7f812345u, 0x00000000u, 0x00000000u, 0x3e800000u, 0x00000000u, 0x00000000u}
};
constexpr std::uint32_t OmodExpectedIeee[32][16] = {
{0x00000000u, 0x00000000u, 0xbe800000u, 0xbe800000u, 0xbe800000u, 0xbf000000u, 0xbf000000u, 0xbf000000u, 0x40000000u, 0x40000000u, 0x40000000u, 0x00000000u, 0x3e800000u, 0x3f000000u, 0x3f800000u, 0x00000000u},
{0x3f800000u, 0x3f800000u, 0x3e800000u, 0x3e800000u, 0x3e800000u, 0xbf000000u, 0xbf000000u, 0xbf000000u, 0xc0000000u, 0xc0000000u, 0xc0000000u, 0x3f800000u, 0x00000000u, 0x3f000000u, 0x00000000u, 0x3f800000u},
{0x40000000u, 0x40000000u, 0x3f000000u, 0x3f000000u, 0x3f000000u, 0x3e800000u, 0x3e800000u, 0x3e800000u, 0x40000000u, 0x40000000u, 0x40000000u, 0x40400000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x40000000u},
{0x40400000u, 0x40400000u, 0x3f000000u, 0x3f000000u, 0x3f000000u, 0x3e800000u, 0x3e800000u, 0x3e800000u, 0xc0000000u, 0xc0000000u, 0xc0000000u, 0x40000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x40400000u},
{0x40800000u, 0x40800000u, 0x3e800000u, 0x3e800000u, 0x3e800000u, 0xbf000000u, 0xbf000000u, 0xbf000000u, 0x40000000u, 0x40000000u, 0x40000000u, 0x40a00000u, 0x00000000u, 0x3f000000u, 0x00000000u, 0x40800000u},
{0x40a00000u, 0x40a00000u, 0xbe800000u, 0xbe800000u, 0xbe800000u, 0x3f000000u, 0x3f000000u, 0x3f000000u, 0xc0000000u, 0xc0000000u, 0xc0000000u, 0x40800000u, 0x3e800000u, 0x00000000u, 0x3f800000u, 0x40a00000u},
{0x40400000u, 0x40400000u, 0x40400000u, 0x40400000u, 0x40400000u, 0xc0000000u, 0xc0000000u, 0xc0000000u, 0xc1600000u, 0xc1600000u, 0xc1600000u, 0x40000000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x40400000u},
{0x40800000u, 0x40800000u, 0xc0200000u, 0xc0200000u, 0xc0200000u, 0xbfc00000u, 0xbfc00000u, 0xbfc00000u, 0x41400000u, 0x41400000u, 0x41400000u, 0x40a00000u, 0x3f800000u, 0x3f800000u, 0x00000000u, 0x40800000u},
{0x00000000u, 0x00000000u, 0x7f000000u, 0x7f000000u, 0x7f000000u, 0xff000000u, 0xff000000u, 0xff000000u, 0x7f800000u, 0x7f800000u, 0x7f800000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x3f800000u, 0x00000000u},
{0x3f800000u, 0x3f800000u, 0x7effffffu, 0x7effffffu, 0x7effffffu, 0xfe800000u, 0xfe800000u, 0xfe800000u, 0xff800000u, 0xff800000u, 0xff800000u, 0x3f800000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x3f800000u},
{0x00000000u, 0x00000000u, 0x00ffffffu, 0x00ffffffu, 0x00ffffffu, 0x80800000u, 0x80800000u, 0x80800000u, 0x40000000u, 0x40000000u, 0x40000000u, 0x00000000u, 0x00000000u, 0x00800000u, 0x3f800000u, 0x00000000u},
{0x00000000u, 0x00000000u, 0x01000000u, 0x01000000u, 0x01000000u, 0x81000000u, 0x81000000u, 0x81000000u, 0x40000000u, 0x40000000u, 0x40000000u, 0x00000000u, 0x00000000u, 0x01000000u, 0x3f800000u, 0x00000000u},
{0x00000000u, 0x00000000u, 0x00000007u, 0x00000007u, 0x00000007u, 0x80000007u, 0x80000007u, 0x80000007u, 0x40000000u, 0x40000000u, 0x40000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x3f800000u, 0x00000000u},
{0x00000000u, 0x00000000u, 0x80000000u, 0x80000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x40000000u, 0x40000000u, 0x40000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x00000000u},
{0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x80000000u, 0x80000000u, 0x80000000u, 0x01000000u, 0x01000000u, 0x01000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x01000000u, 0x00000000u},
{0x40800000u, 0x40800000u, 0x00000007u, 0x00000007u, 0x00000007u, 0x80000003u, 0x80000003u, 0x80000003u, 0x00000000u, 0x00000000u, 0x00000000u, 0x40800000u, 0x00000000u, 0x00000003u, 0x00000000u, 0x40800000u},
{0x00000000u, 0x00000000u, 0xc0000000u, 0xc0000000u, 0xc0000000u, 0xbf800000u, 0xbf800000u, 0xbf800000u, 0x7f800000u, 0x7f800000u, 0x7f800000u, 0x00000000u, 0x3f800000u, 0x3f800000u, 0x3f800000u, 0x00000000u},
{0x40000000u, 0x40000000u, 0xff800000u, 0xff800000u, 0xff800000u, 0x3f800000u, 0x3f800000u, 0x3f800000u, 0x7f800000u, 0x7f800000u, 0x7f800000u, 0x40400000u, 0x3f800000u, 0x00000000u, 0x3f800000u, 0x40000000u},
{0x00000000u, 0x00000000u, 0xbf000000u, 0xbf000000u, 0xbf000000u, 0xbf800000u, 0xbf800000u, 0xbf800000u, 0x7fc00000u, 0x7fc00000u, 0x7fc00000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x00000000u},
{0x00000000u, 0x00000000u, 0xbf000000u, 0xbf000000u, 0xbf000000u, 0x7fc00000u, 0x7fc00000u, 0x7fc00000u, 0x40000000u, 0x40000000u, 0x40000000u, 0x00000000u, 0x3f000000u, 0x00000000u, 0x3f800000u, 0x00000000u},
{0x00000000u, 0x00000000u, 0xffc00000u, 0xffc00000u, 0xffc00000u, 0xbf800000u, 0xbf800000u, 0xbf800000u, 0x40800000u, 0x40800000u, 0x40800000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x3f800000u, 0x00000000u},
{0x40800000u, 0x40800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x80000000u, 0x80000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x40800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x40800000u},
{0x40800000u, 0x40800000u, 0x80000000u, 0x80000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x40800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x40800000u},
{0x40800000u, 0x40800000u, 0x7149f2cau, 0x7149f2cau, 0x7149f2cau, 0x7149f2cau, 0x7149f2cau, 0x7149f2cau, 0x7249f2cau, 0x7249f2cau, 0x7249f2cau, 0x40a00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x40800000u},
{0x40a00000u, 0x40a00000u, 0x8da24260u, 0x8da24260u, 0x8da24260u, 0x8e224260u, 0x8e224260u, 0x8e224260u, 0x8ef36390u, 0x8ef36390u, 0x8ef36390u, 0x40800000u, 0x0da24260u, 0x0e224260u, 0x0ef36390u, 0x40a00000u},
{0x00000000u, 0x00000000u, 0xbf800000u, 0xbf800000u, 0xbf800000u, 0xfe7fffffu, 0xfe7fffffu, 0xfe7fffffu, 0x7f000000u, 0x7f000000u, 0x7f000000u, 0x00000000u, 0x3f800000u, 0x3f800000u, 0x3f800000u, 0x00000000u},
{0x40800000u, 0x40800000u, 0x40000000u, 0x40000000u, 0x40000000u, 0xfe800000u, 0xfe800000u, 0xfe800000u, 0x7f7fffffu, 0x7f7fffffu, 0x7f7fffffu, 0x40a00000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x40800000u},
{0x40800000u, 0x40800000u, 0x3f000000u, 0x3f000000u, 0x3f000000u, 0x3f000000u, 0x3f000000u, 0x3f000000u, 0x3f800000u, 0x3f800000u, 0x3f800000u, 0x40a00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x40800000u},
{0x3f800000u, 0x3f800000u, 0xc0c18eacu, 0xc0c18eacu, 0xc0c18eacu, 0x408852a3u, 0x408852a3u, 0x408852a3u, 0xc145edadu, 0xc145edadu, 0xc145edadu, 0x3f800000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x3f800000u},
{0x40a00000u, 0x40a00000u, 0x3f3d7ad8u, 0x3f3d7ad8u, 0x3f3d7ad8u, 0x40737ab3u, 0x40737ab3u, 0x40737ab3u, 0xc165e3c1u, 0xc165e3c1u, 0xc165e3c1u, 0x40800000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x40a00000u},
{0x00000000u, 0x00000000u, 0x7fc12345u, 0x7fc12345u, 0x7fc12345u, 0xbe800000u, 0xbe800000u, 0xbe800000u, 0x3f800000u, 0x3f800000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x3e800000u, 0x3f800000u, 0x00000000u},
{0x00000000u, 0x00000000u, 0xbe800000u, 0xbe800000u, 0xbe800000u, 0xbf000000u, 0xbf000000u, 0xbf000000u, 0x7fc12345u, 0x7fc12345u, 0x7fc12345u, 0x00000000u, 0x00000000u, 0x3f000000u, 0x00000000u, 0x00000000u}
};
constexpr const char* OmodNames[16] = {
"v_cubeid_f32 v10, v4, v5, v6 mul:2",
"v_cubeid_f32 v11, v4, v5, v6 div:2",
"v_cubesc_f32 v12, v4, v5, v6 mul:2",
"v_cubesc_f32 v13, v4, v5, v6 mul:4",
"v_cubesc_f32 v14, v4, v5, v6 div:2",
"v_cubetc_f32 v15, v4, v5, v6 mul:2",
"v_cubetc_f32 v16, v4, v5, v6 mul:4",
"v_cubetc_f32 v17, v4, v5, v6 div:2",
"v_cubema_f32 v18, v4, v5, v6 mul:2",
"v_cubema_f32 v19, v4, v5, v6 mul:4",
"v_cubema_f32 v20, v4, v5, v6 div:2",
"v_cubeid_f32 v21, v4, -v5, -v6 div:2",
"v_cubesc_f32 v22, -v4, |v5|, v6 clamp mul:4",
"v_cubetc_f32 v23, |v4|, -v5, v6 clamp div:2",
"v_cubema_f32 v24, v4, v5, -v6 clamp mul:2",
"v_cubeid_f32 v25, v4, v5, v6 mul:4",
};
std::array<std::uint32_t, 4> BufferDescriptor(const void* data, std::uint32_t bytes) {
const auto address = reinterpret_cast<std::uintptr_t>(data);
@@ -193,33 +319,19 @@ ShaderRecompiler::RecompileResult Compile(AgcDriver::VulkanDevice& device, std::
return ShaderRecompiler::Recompile(request);
}
void Run(AgcDriver::VulkanDevice& device, const std::optional<ShaderRecompiler::ShaderFloatMode>& floatMode) {
for (std::uint32_t tid = 0; tid < Threads; ++tid) Fill(tid, &Input[tid * Inputs]);
void Run(AgcDriver::VulkanDevice& device, std::span<const std::uint32_t> code, const std::uint32_t (&rows)[32][4], const std::optional<ShaderRecompiler::ShaderFloatMode>& floatMode) {
for (std::uint32_t tid = 0; tid < Threads; ++tid) std::copy(std::begin(rows[tid]), std::end(rows[tid]), &Input[tid * Inputs]);
Output.fill(0xdeadbeefu);
const std::span<const std::uint32_t> code(Code);
const auto result = Compile(device, code, floatMode);
device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast<std::uintptr_t>(code.data()));
device.WaitIdle();
}
void CheckOmodRefused(AgcDriver::VulkanDevice& device) {
for (const std::uint32_t opcode : {0x144u, 0x145u, 0x146u, 0x147u}) {
alignas(256) const std::array<std::uint32_t, 3> code{0xd400000au | (opcode << 16u), 0x0c1a0b04u, 0xbf810000u};
std::string refusal;
try {
static_cast<void>(Compile(device, code));
} catch (const std::exception& error) {
refusal = error.what();
}
Require(refusal.find("VOP3 source modifiers are not implemented") != std::string::npos, "VOP3 opcode " + Hex(opcode) + " with omod was not refused");
}
}
void Check(const std::uint32_t (&expected)[32][16], const char* mode) {
void Check(const std::uint32_t (&expected)[32][16], const char* const (&names)[16], const char* mode) {
for (std::uint32_t tid = 0; tid < Threads; ++tid) {
const std::uint32_t* out = &Output[tid * Results];
for (std::uint32_t i = 0; i < 16; ++i) {
Expect(tid, out[i], expected[tid][i], (std::string(mode) + " " + Names[i]).c_str());
Expect(tid, out[i], expected[tid][i], (std::string(mode) + " " + names[i]).c_str());
}
}
}
@@ -230,13 +342,22 @@ int main() {
try {
const auto device = OpenVulkanTestDevice();
if (!device) return VulkanTestSkipped;
Run(*device, std::nullopt);
Check(Expected, "no float mode");
Run(*device, ShaderRecompiler::ShaderFloatMode{0xc0u, true, false, false});
Check(Expected, "IEEE=0");
Run(*device, ShaderRecompiler::ShaderFloatMode{0xc0u, true, true, false});
Check(ExpectedIeee, "IEEE=1");
CheckOmodRefused(*device);
Run(*device, Code, Rows, std::nullopt);
Check(Expected, Names, "no float mode");
Run(*device, Code, Rows, ShaderRecompiler::ShaderFloatMode{0xc0u, true, false, false});
Check(Expected, Names, "IEEE=0");
Run(*device, Code, Rows, ShaderRecompiler::ShaderFloatMode{0xc0u, true, true, false});
Check(ExpectedIeee, Names, "IEEE=1");
Run(*device, OmodCode, OmodRows, std::nullopt);
Check(OmodExpected, OmodNames, "no float mode");
Run(*device, OmodCode, OmodRows, ShaderRecompiler::ShaderFloatMode{0xc0u, true, false, false});
Check(OmodExpected, OmodNames, "IEEE=0 f32 denormals flushed");
Run(*device, OmodCode, OmodRows, ShaderRecompiler::ShaderFloatMode{0xf0u, true, false, false});
Check(OmodExpected, OmodNames, "IEEE=0 f32 denormals kept");
Run(*device, OmodCode, OmodRows, ShaderRecompiler::ShaderFloatMode{0xc0u, true, true, false});
Check(OmodExpectedIeee, OmodNames, "IEEE=1 f32 denormals flushed");
Run(*device, OmodCode, OmodRows, ShaderRecompiler::ShaderFloatMode{0xf0u, true, true, false});
Check(OmodExpectedIeee, OmodNames, "IEEE=1 f32 denormals kept");
std::puts("vop3 cube clamp tests passed");
return 0;
} catch (const std::exception& error) {
+4 -110
View File
@@ -226,18 +226,6 @@ static int RecordWriteAddressFromCounterPair(Apr::CommandBufferObject* buffer, v
return AppendCommand(buffer, Apr::Opcode::WriteAddressFromCounterPair, Apr::WriteAddressFromCounterCommand{{}, reinterpret_cast<std::uint64_t>(address), counter, counter + 1u});
}
static constexpr int ThrdSuccess = 0;
static constexpr int ThrdNomem = 1;
static constexpr int ThrdTimedout = 2;
static constexpr int ThrdBusy = 3;
static constexpr int ThrdError = 4;
static constexpr int CppDeviceOrResourceBusy = 0;
static constexpr int CppInvalidArgument = 1;
static constexpr int CppResourceUnavailableTryAgain = 6;
static constexpr int MtxPlain = 0x1;
static std::atomic_flag sharedPtrSpinLock;
struct AmmUsageStatsData {
@@ -252,55 +240,10 @@ static_assert(sizeof(AmmUsageStatsData) == 0x18);
static constexpr std::uint16_t AmmPageTablePoolEntries = 512;
static constexpr std::uint32_t AmmRingIdleFlags = 0x7;
struct GuestPad {
unsigned (APS5_VABI* const* vtable)(GuestPad*);
void* condition;
void* mutex;
bool started;
};
static_assert(offsetof(GuestPad, condition) == 0x8 && offsetof(GuestPad, mutex) == 0x10 && offsetof(GuestPad, started) == 0x18 && sizeof(GuestPad) == 0x20);
extern "C" {
int APS5_VABI pthread_create_nid_postfix(Pthread* thread, const PthreadAttr* attr, PthreadEntry entry, void* arg);
int APS5_VABI pthread_join_nid_postfix(Pthread thread, void** value);
int APS5_VABI _Mtx_init_nid_postfix(void** handle, int type);
void APS5_VABI _Mtx_destroy_nid_postfix(void** handle);
int APS5_VABI _Mtx_lock_nid_postfix(void** handle);
int APS5_VABI _Mtx_unlock_nid_postfix(void** handle);
int APS5_VABI _Cnd_init_nid_postfix(void** handle);
void APS5_VABI _Cnd_destroy_nid_postfix(void** handle);
int APS5_VABI _Cnd_wait_nid_postfix(void** condition, void** mutex);
int APS5_VABI _Cnd_signal_nid_postfix(void** handle);
int APS5_VABI wcsrtombs_s_nid_postfix(std::size_t* result, char* destination, std::size_t capacity, const std::uint16_t** source, std::size_t limit, void* state);
[[noreturn]] void APS5_VABI __cxa_call_terminate_nid_postfix(void* exception);
int APS5_VABI _Cnd_init_nid_postfix(void** handle);
int SignalCondition_nid_no_patch(void** handle);
[[noreturn]] void ThrowCppError_nid_no_patch(int code);
}
static void RequireThrd(int result, const char* what) {
switch (result) {
case ThrdSuccess:
return;
case ThrdNomem:
case ThrdTimedout:
ThrowCppError_nid_no_patch(CppResourceUnavailableTryAgain);
case ThrdBusy:
ThrowCppError_nid_no_patch(CppDeviceOrResourceBusy);
case ThrdError:
ThrowCppError_nid_no_patch(CppInvalidArgument);
}
throw std::runtime_error(std::string(what) + " failed with " + std::to_string(result));
}
static void* APS5_VABI PadEntry_nid_no_patch(void* argument) {
auto* pad = static_cast<GuestPad*>(argument);
const auto go = pad->vtable[0];
return reinterpret_cast<void*>(static_cast<std::uintptr_t>(go(pad)));
}
extern "C" {
int APS5_VABI sceAmprCommandBufferWriteAddressOnCompletion(Apr::CommandBufferObject* buffer, volatile std::uint64_t* address, std::uint64_t value) {
if (!ValidWriteAddress(address)) return SCE_KERNEL_ERROR_EINVAL;
@@ -347,6 +290,7 @@ std::uint64_t APS5_VABI sceAmprMeasureCommandSizeWaitOnCounter() { return sizeof
std::uint64_t APS5_VABI sceAmprMeasureCommandSizeWriteKernelEventQueueOnCompletion() { return sizeof(Apr::WriteKernelEventQueueCommand); }
int APS5_VABI sceAmprAprCommandBufferConstructor(Apr::CommandBufferObject* buffer, uint64_t* gatherState, uint64_t* scatterState) {
if (!buffer || !gatherState || !scatterState) return SCE_KERNEL_ERROR_EINVAL;
buffer->type = Apr::BufferType::Apr;
*gatherState = 0;
*scatterState = 0;
@@ -452,6 +396,7 @@ int APS5_VABI sceAmprCommandBufferConstructNop(Apr::CommandBufferObject* buffer,
}
int APS5_VABI sceAmprCommandBufferConstructor(Apr::CommandBufferObject* buffer) {
if (!buffer) return SCE_KERNEL_ERROR_EINVAL;
*buffer = {Apr::BufferType::Generic, 0, 0, 0, 0, nullptr};
return 0;
}
@@ -506,12 +451,14 @@ int APS5_VABI sceAmprCommandBufferPushMarkerWithColor(Apr::CommandBufferObject*
}
int APS5_VABI sceAmprCommandBufferReset(Apr::CommandBufferObject* buffer) {
if (!buffer) return SCE_KERNEL_ERROR_EINVAL;
buffer->offset = 0;
buffer->numCommands = 0;
return 0;
}
int APS5_VABI sceAmprCommandBufferSetBuffer(Apr::CommandBufferObject* buffer, void* memory, uint32_t size) {
if (!buffer) return SCE_KERNEL_ERROR_EINVAL;
buffer->base = static_cast<uint8_t*>(memory);
buffer->size = size;
buffer->offset = 0;
@@ -901,59 +848,6 @@ int APS5_VABI sceAmprAmmWaitCommandBufferCompletion(std::uint32_t id) {
return AmmSubmitted_nid_no_patch(id) ? 0 : SCE_KERNEL_ERROR_ESRCH;
}
[[noreturn]] void APS5_VABI _ZSt16_Throw_Cpp_errori_nid_postfix(int code) {
ThrowCppError_nid_no_patch(code);
}
int APS5_VABI _Thrd_join_nid_postfix(Pthread thread, int* code) {
void* result = nullptr;
if (pthread_join_nid_postfix(thread, &result) != 0) return ThrdError;
if (code) *code = static_cast<int>(reinterpret_cast<std::intptr_t>(result));
return ThrdSuccess;
}
void APS5_VABI _ZNSt4_Pad8_ReleaseEv_nid_postfix(GuestPad* pad) {
if (!pad) APS5_INVALID_ARG_EX;
RequireThrd(_Mtx_lock_nid_postfix(&pad->mutex), "_Pad::_Release: _Mtx_lock");
pad->started = true;
RequireThrd(_Cnd_signal_nid_postfix(&pad->condition), "_Pad::_Release: _Cnd_signal");
RequireThrd(_Mtx_unlock_nid_postfix(&pad->mutex), "_Pad::_Release: _Mtx_unlock");
}
void APS5_VABI _ZNSt4_PadC2Ev_nid_postfix(GuestPad* pad) {
if (!pad) APS5_INVALID_ARG_EX;
RequireThrd(_Cnd_init_nid_postfix(&pad->condition), "_Pad::_Pad: _Cnd_init");
const int created = _Mtx_init_nid_postfix(&pad->mutex, MtxPlain);
if (created != ThrdSuccess) {
_Cnd_destroy_nid_postfix(&pad->condition);
RequireThrd(created, "_Pad::_Pad: _Mtx_init");
}
pad->started = false;
const int locked = _Mtx_lock_nid_postfix(&pad->mutex);
if (locked != ThrdSuccess) {
_Mtx_destroy_nid_postfix(&pad->mutex);
_Cnd_destroy_nid_postfix(&pad->condition);
RequireThrd(locked, "_Pad::_Pad: _Mtx_lock");
}
}
void APS5_VABI _ZNSt4_PadD2Ev_nid_postfix(GuestPad* pad) {
if (!pad) APS5_INVALID_ARG_EX;
RequireThrd(_Mtx_unlock_nid_postfix(&pad->mutex), "_Pad::~_Pad: _Mtx_unlock");
_Mtx_destroy_nid_postfix(&pad->mutex);
_Cnd_destroy_nid_postfix(&pad->condition);
}
int APS5_VABI _Thrd_id_nid_postfix(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
void APS5_VABI _ZNSt4_Pad7_LaunchEPP7pthread_nid_postfix(GuestPad* pad, Pthread* thread) {
if (!pad || !thread) APS5_INVALID_ARG_EX;
const int created = pthread_create_nid_postfix(thread, nullptr, PadEntry_nid_no_patch, pad);
if (created != 0) ThrowCppError_nid_no_patch(CppResourceUnavailableTryAgain);
while (!pad->started) RequireThrd(_Cnd_wait_nid_postfix(&pad->condition, &pad->mutex), "_Pad::_Launch: _Cnd_wait");
}
int APS5_VABI _ZNSt7num_getIcSt19istreambuf_iteratorIcSt11char_traitsIcEEE2idE_nid_postfix(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
+3 -3
View File
@@ -96,7 +96,7 @@ int APS5_VABI sceAppContentAppParamGetInt(uint32_t param_id, int32_t* value) {
int APS5_VABI sceAppContentDownloadDataFormat(const AppContentMountPoint* mount_point) {
if (!mount_point) return SCE_APP_CONTENT_ERROR_PARAMETER;
if (std::strncmp(mount_point->data, DOWNLOAD_MOUNT_POINT, sizeof(mount_point->data)) != 0) APS5_INVALID_ARG_EX;
if (std::strncmp(mount_point->data, DOWNLOAD_MOUNT_POINT, sizeof(mount_point->data)) != 0) return SCE_APP_CONTENT_ERROR_NOT_MOUNTED;
const auto directory = ResolvePath_nid_no_patch(DOWNLOAD_MOUNT_POINT);
if (std::filesystem::create_directories(directory)) RecordWrittenPath_nid_no_patch(directory);
ClearDirectory(directory);
@@ -104,8 +104,8 @@ int APS5_VABI sceAppContentDownloadDataFormat(const AppContentMountPoint* mount_
}
int APS5_VABI sceAppContentDownloadDataGetAvailableSpaceKb(const AppContentMountPoint* mount_point, size_t* available_space_kb) {
if (!available_space_kb) return SCE_APP_CONTENT_ERROR_PARAMETER;
if (!mount_point || std::strncmp(mount_point->data, DOWNLOAD_MOUNT_POINT, sizeof(mount_point->data)) != 0) APS5_INVALID_ARG_EX;
if (!mount_point || !available_space_kb) return SCE_APP_CONTENT_ERROR_PARAMETER;
if (std::strncmp(mount_point->data, DOWNLOAD_MOUNT_POINT, sizeof(mount_point->data)) != 0) return SCE_APP_CONTENT_ERROR_NOT_MOUNTED;
const std::uint64_t quotaKb = GetAppDownloadDataSizeMiB_nid_postfix() * 1024u;
if (quotaKb == 0) {
*available_space_kb = 0;
+15
View File
@@ -0,0 +1,15 @@
add_library(libSceAt9Enc SHARED EXCLUDE_FROM_ALL Export.cpp)
target_include_directories(libSceAt9Enc PRIVATE ${LIBS_INCLUDE_DIR})
configure_windows_unwind(libSceAt9Enc)
if(BUILD_TESTING)
add_test_executable(guest_at9_enc_tests ${LIBS_INCLUDE_DIR}/tests/GuestAt9Enc.cpp)
target_include_directories(guest_at9_enc_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_at9_enc_tests PRIVATE libSceAt9Enc libc)
configure_windows_unwind(guest_at9_enc_tests)
add_test(NAME guest_at9_enc COMMAND guest_at9_enc_tests)
if(WIN32)
set_tests_properties(guest_at9_enc PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceAt9Enc>")
endif()
endif()
+34
View File
@@ -0,0 +1,34 @@
#include <cstdint>
#include <cstddef>
#include "SceTypes.hpp"
#include "prx/libc/include/General.hpp"
extern "C" {
int32_t APS5_VABI sceAt9EncClearContext(void *pAt9EncWorkArea, int32_t *pInternalResult) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int32_t APS5_VABI sceAt9EncCreateEncoder(void *pAt9EncWorkArea, void *pAt9EncParam, int32_t memSize, int32_t *pInternalResult) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int32_t APS5_VABI sceAt9EncEncode(void *pAt9EncWorkArea, const void *pInputBuf, void *pOutputBuf, uint32_t inputBufSize, uint32_t outputBufSize, uint32_t *pOutputDataByte, uint32_t *pUsedBufSize, int32_t *pInternalResult) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int32_t APS5_VABI sceAt9EncFlush(void *pAt9EncWorkArea, void *pOutputBuf, uint32_t outputBufSize, uint32_t *pOutputDataByte, int32_t *pInternalResult) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int32_t APS5_VABI sceAt9EncQueryMemSize(void *pAt9EncParam, int32_t *pMemSize, int32_t *pInternalResult) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
}
@@ -9,6 +9,7 @@ add_library(libSceAudioOut SHARED EXCLUDE_FROM_ALL
${CMAKE_CURRENT_SOURCE_DIR}/src/AudioOut2User.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/AudioOut2Mastering.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/AudioOut2System.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/Unimplemented.cpp
)
target_include_directories(libSceAudioOut PRIVATE
@@ -0,0 +1,488 @@
#include <cstdint>
#include <cstddef>
#include "SceTypes.hpp"
#include "prx/libc/include/General.hpp"
extern "C" {
int APS5_VABI sceAudioOut2UserCreateEx(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioOut2LoPortDestroy(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioOut2LoContextDestroy(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioOutOpenEx(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioOut2LoPortSetAttributes(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioOut2LoContextAdvance(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioOut2LoContextPush(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioOut2LoContextGetQueueLevel(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioOutSetSystemDebugState(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioOut2LoPortGetState(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioOutGetHandleStatusInfo(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioOutGetSimulatedHandleStatusInfo2(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioOutChangeAppModuleState(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioOutGetSimulatedHandleStatusInfo(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioOutGetSimulatedBusUsableStatusByBusType(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioOutAttachToApplicationByPid(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioOutDetachFromApplicationByPid(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioOutPtOpen(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioOutPtClose(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioOutPtGetLastOutputTime(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioOutExPtOpen(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioOutExPtClose(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioOutExPtGetLastOutputTime(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioOutExGetLastOutputTime(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioOutGetInfoOpenNum(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioOutGetInfo(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioOutMasteringInit(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioOutMasteringSetParam(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioOutMasteringGetState(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioOutMasteringTerm(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioOutSetPadSpkVolume(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioOutSetPadJackVolume(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioOutSetUsbVolume(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioOutSetCaesarVolume(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioOutSetHeadphoneOutMode(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioOutGetFocusEnablePid(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioOutSetMainOutput(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioOutSetVolumeDown(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioOutSetChatBalance(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioOutSetRecMode(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioOutSetAllMute(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioOutSetSparkEQ(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioOutArbitrationInitialize(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioOut2ArbitrationInitialize(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioOutEnable3DAudioByArbitration(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioOutAssignHRTF(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioOutEnable3DAudioForUser(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioOutEnableMonoHpOutput(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioOutEnableTVVirtual(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioOutEnableTVCorrection(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioOutEnableVrControllerLowPowerMode(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioOutEnableCinematicVrRotation(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioOutCustomHrtfIsExist(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioOutExConfigureOutputMode(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioOutExSystemInfoIsSupportedAudioOutExMode(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioOutExGetSystemInfo(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioOutExGetOutputInfo(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioOutSysConfigureOutputMode(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioOutSysGetMonitorInfo(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioOutSysHdmiMonitorInfoIsSupportedAudioOutMode(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioOutSysGetSystemInfo(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioOutSysGetOutputInfo(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioOut2LoContextQueryMemory(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioOut2LoContextCreate(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioOut2LoContextSetAttributes(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioOut2MbusInit(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioOut2MbusSetPortConnections(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioOut2MbusSetConnections(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioOut2MbusSetPortStatuses(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioOut2MbusSetHeadphoneOutMode(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioOut2MbusUserLoginFor3DAudio(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioOut2MbusSetDeviceConnections(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioOut2MbusSetMorpheusParam(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioOut2MbusSetCaesarParam(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioOut2MbusSetSparkParam(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioOut2AllAudioMainToHeadphoneForDiag(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioOut2LoPortCreate(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioOut2LoPortGetStateEx(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioOut2ReportRegisterHandler(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioOut2ReportUnregisterHandler(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioOut2GetTvCorrectionInfo(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioOut2GetIpcHandle(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioOut2GetSystemStateEx(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioOut2MasteringInitEx(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioOut2SetDebugHeadphoneMode(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioOut2DebugStateCtrl(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioOut2LoInit(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioOut2LoTerminate(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioOut2ContextBedWrite(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioOut2PortCreateEx(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioOut2InitializeEx(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioOut2Terminate(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioOut2PortRegister(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioOut2PortUnregister(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioOut2GetAmbisonicsEncodeCoefficients(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioOut2GetHrtfIdForCronos(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
}
@@ -312,7 +312,6 @@ std::int32_t APS5_VABI sceAudioPropagationSourceRender(AudioPropagationHandle sy
require(info.output != nullptr && info.outputSize != 0, __func__);
if (info.format != RenderFormat) throw std::runtime_error(std::string(__func__) + ": render format " + std::to_string(info.format) + " is unknown");
}
for (std::uint32_t index = 0; index < count; ++index) std::memset(infos[index].output, 0, infos[index].outputSize);
return 0;
}
@@ -12,6 +12,11 @@ if(BUILD_TESTING)
file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/guest_avplayer)
add_test(NAME guest_avplayer COMMAND guest_avplayer_tests)
add_test(NAME guest_avplayer_callback_close COMMAND guest_avplayer_tests --callback-close)
add_test(NAME guest_avplayer_paused_first_video COMMAND guest_avplayer_tests --paused-first-video)
set_tests_properties(guest_avplayer_paused_first_video PROPERTIES
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/guest_avplayer_paused_first_video
TIMEOUT 20)
file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/guest_avplayer_paused_first_video)
set_tests_properties(guest_avplayer PROPERTIES
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/guest_avplayer
TIMEOUT 60)
@@ -21,6 +26,8 @@ if(BUILD_TESTING)
if(WIN32)
set_tests_properties(guest_avplayer PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceAvPlayer>")
set_tests_properties(guest_avplayer_paused_first_video PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceAvPlayer>")
set_tests_properties(guest_avplayer_callback_close PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceAvPlayer>")
endif()
+1 -1
View File
@@ -507,7 +507,7 @@ private:
while (!frames.empty() && frames.front().epoch < stale) recycleFront(video);
if (frames.empty()) return false;
const auto& front = frames.front();
if (paused && front.epoch <= clockEpoch) return false;
if (paused && presented && front.epoch <= clockEpoch) return false;
if (front.epoch > clockEpoch) {
if (synced && audioDriving && speed == NormalSpeed) return false;
if (synced && front.seamless && presented && presentationClock.Now() < static_cast<double>(lastPresented + frameDuration())) return false;
@@ -31,6 +31,7 @@ struct FontState {
float scaleH = 16.0f;
std::map<int, int> scriptLanguages;
std::map<int, int> typographicFeatures;
int attribute = 0;
FontState() = default;
FontState(const FontState&) = delete;
@@ -52,6 +53,7 @@ struct GeneratedGlyph {
std::vector<std::uint16_t> outlineContours;
bool metricsInitialized = false;
bool outlineInitialized = false;
int attribute = 0;
};
struct SystemFontFile {
+9
View File
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include <atomic>
#include <cstdio>
#include <cstring>
#include <filesystem>
#include <fstream>
@@ -227,6 +228,13 @@ std::shared_ptr<const std::vector<unsigned char>> SystemFontBytes(const std::fil
return bytes;
}
void WarnMissingSystemFont(std::uint32_t fontSetType) {
static std::once_flag warned;
std::call_once(warned, [&] {
std::fprintf(stderr, "[libSceFont] no file for system font set 0x%08X in %s. Titles that lay out text in system fonts can fail far from here without them: put the console's fonts or the Noto substitutes from docs/user/USAGE.md there, or set ANYPS5_SYSTEM_FONTS to their directory.\n", fontSetType, SystemFontDirectory().string().c_str());
});
}
void CopyStyleToCache(FontHandleNative* font) {
std::memcpy(&font->cached_style.state, &font->style, sizeof(StyleStateBlock));
}
@@ -407,6 +415,7 @@ int APS5_VABI sceFontOpenFontSet(FontLibrary library, std::uint32_t fontSetType,
if (!pFontHandle) return SCE_FONT_ERROR_INVALID_PARAMETER;
const std::optional<SystemFontFile> file = FindSystemFontFile(fontSetType);
APS5_LOG_OUT("fontSetType=0x%08X file=%s", fontSetType, file ? file->path.string().c_str() : "(none)");
if (!file && IsSystemFontSet(fontSetType)) WarnMissingSystemFont(fontSetType);
std::uint32_t libraryLock = 0;
if (!AcquireLibraryLock(lib, libraryLock)) {
*pFontHandle = nullptr;
+13 -3
View File
@@ -419,10 +419,20 @@ int APS5_VABI sceFontDeleteGlyph(const FontMemory* memory, FontGlyph* pGlyph) {
return SCE_FONT_OK;
}
int APS5_VABI sceFontGlyphDefineAttribute(FontGlyph glyph, std::uint32_t attribute, std::uint64_t value) {
int APS5_VABI sceFontGlyphDefineAttribute(FontGlyph glyph, int attribute, int* oldAttribute) {
auto* generated = TryGetGeneratedGlyph(glyph);
if (!generated) return SCE_FONT_ERROR_INVALID_GLYPH;
if (oldAttribute) *oldAttribute = generated->attribute;
generated->attribute = attribute;
return SCE_FONT_OK;
}
int APS5_VABI sceFontGlyphGetAttribute(FontGlyph glyph, int attribute, int* nowAttribute) {
(void)attribute;
(void)value;
if (!glyph || glyph->magic != GLYPH_MAGIC) return SCE_FONT_ERROR_INVALID_GLYPH;
if (!nowAttribute) return SCE_FONT_ERROR_INVALID_PARAMETER;
auto* generated = TryGetGeneratedGlyph(glyph);
if (!generated) return SCE_FONT_ERROR_INVALID_GLYPH;
*nowAttribute = generated->attribute;
return SCE_FONT_OK;
}
+17
View File
@@ -101,6 +101,23 @@ float ClampWeightDelta(float scale) {
extern "C" {
int APS5_VABI sceFontDefineAttribute(FontHandle fontHandle, int attribute, int* oldAttribute) {
return AccessFontSettings(fontHandle, [&](FontState& state) {
if (oldAttribute) *oldAttribute = state.attribute;
state.attribute = attribute;
return SCE_FONT_OK;
});
}
int APS5_VABI sceFontGetAttribute(FontHandle fontHandle, int attribute, int* nowAttribute) {
(void)attribute;
if (!nowAttribute) return SCE_FONT_ERROR_INVALID_PARAMETER;
return AccessFontSettings(fontHandle, [&](FontState& state) {
*nowAttribute = state.attribute;
return SCE_FONT_OK;
});
}
int APS5_VABI sceFontSetScriptLanguage(FontHandle fontHandle, int fontScript, int fontLanguage) {
return SetFontSetting(fontHandle, &FontState::scriptLanguages, fontScript, fontLanguage);
}
@@ -32,11 +32,6 @@ int APS5_VABI sceFontCreateWords() {
return 0;
}
int APS5_VABI sceFontDefineAttribute() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceFontDestroyGraphicsDevice() {
NotImplemented_nid_no_patch(__func__);
return 0;
@@ -53,11 +48,6 @@ int APS5_VABI sceFontDestroyWords() {
}
int APS5_VABI sceFontGetAttribute() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceFontGetFontGlyphsOutlineProfile() {
NotImplemented_nid_no_patch(__func__);
return 0;
@@ -78,11 +68,6 @@ int APS5_VABI sceFontGetGlyphExpandBufferState() {
return 0;
}
int APS5_VABI sceFontGlyphGetAttribute() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceFontGlyphRenderImage() {
NotImplemented_nid_no_patch(__func__);
return 0;
@@ -250,4 +250,9 @@ int APS5_VABI sceGameLiveStreamingStopDebugBroadcast(void) {
return 0;
}
int APS5_VABI sceGameLiveStreamingEnableLiveStreaming(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
}
+95
View File
@@ -377,4 +377,99 @@ int APS5_VABI sceHmd2SetIpdOffset() {
return 0;
}
int APS5_VABI sceHmd2InternalSetPortStatus(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceHmd2InternalLedDetectorStartBySlot(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceHmd2InternalImageStartBySlot(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceHmd2InternalSetPortConnection(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceHmd2InternalSetCameraSeeThroughMode(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceHmd2InternalSetFakeLensSeparationDistance(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceHmd2InternalDfuSend(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceHmd2InternalLedDetectorOpenBySlot(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceHmd2InternalImageOpenBySlot(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceHmd2InternalLedDetectorStopBySlot(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceHmd2InternalSetDeviceConnection(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceHmd2InternalLedDetectorCloseBySlot(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceHmd2InternalSetSidetone(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceHmd2InternalGetDeviceId(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceHmd2InternalSetBrightness(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceHmd2InternalImageStopBySlot(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceHmd2InternalSetVibrationMode(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceHmd2InternalGetVr2dData(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceHmd2InternalImageCloseBySlot(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
}
+10
View File
@@ -23,6 +23,16 @@ if(BUILD_TESTING)
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceIme>")
endif()
add_test_executable(guest_ime_filter_text_tests ${LIBS_INCLUDE_DIR}/tests/GuestImeFilterText.cpp)
target_include_directories(guest_ime_filter_text_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_ime_filter_text_tests PRIVATE libSceIme libc)
configure_windows_unwind(guest_ime_filter_text_tests)
add_test(NAME guest_ime_filter_text COMMAND guest_ime_filter_text_tests)
if(WIN32)
set_tests_properties(guest_ime_filter_text PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceIme>")
endif()
add_test_executable(guest_ime_open_tests ${LIBS_INCLUDE_DIR}/tests/GuestImeOpen.cpp
${CMAKE_CURRENT_SOURCE_DIR}/../libSceVideoOut/src/ImeTextInput.cpp)
target_include_directories(guest_ime_open_tests PRIVATE ${LIBS_INCLUDE_DIR} ${ANYPS5_SDL2_INCLUDE_DIR})
+153 -31
View File
@@ -9,8 +9,10 @@
#include <set>
#include <stdexcept>
#include <string>
#include <vector>
#include "SceTypes.hpp"
#include "prx/libc/include/General.hpp"
#include "prx/libSceIme/include/ImeFilterText.hpp"
#include "prx/libSceIme/include/ImeInput.hpp"
namespace {
@@ -59,9 +61,17 @@ constexpr uint32_t CaretMoveRight = 2;
struct Session {
Param param{};
ImeExtKeyboardFilter extKeyboardFilter = nullptr;
std::u16string text;
std::uint32_t caret = 0;
std::deque<ImeEvent> events;
std::deque<ImeHostInput> hostInputs;
std::uint64_t id = 0;
std::uint64_t revision = 0;
};
struct DeferredFilter {
std::u16string oldText;
};
std::mutex g_keyboardMutex;
@@ -69,6 +79,7 @@ std::set<int32_t> g_openKeyboards;
std::mutex g_imeMutex;
std::optional<Session> g_session;
std::atomic<ImeHostTextSink> g_textSink{nullptr};
std::uint64_t g_nextSessionId = 0;
void PanelSize(const Param& param, uint32_t* width, uint32_t* height) {
const uint32_t scale = (param.option & OptionUseOver2K) != 0 ? 2 : 1;
@@ -157,7 +168,68 @@ void QueueCaretSteps(Session& session, uint32_t direction, std::size_t steps) {
}
}
void Insert(Session& session, std::u16string units) {
struct EditDelta {
std::uint32_t index = 0;
std::int32_t length = 0;
};
EditDelta ComputeEditDelta(const std::u16string& before, const std::u16string& after) {
std::size_t prefix = 0;
while (prefix < before.size() && prefix < after.size() && before[prefix] == after[prefix]) ++prefix;
std::size_t beforeTail = before.size();
std::size_t afterTail = after.size();
while (beforeTail > prefix && afterTail > prefix && before[beforeTail - 1] == after[afterTail - 1]) {
--beforeTail;
--afterTail;
}
return {static_cast<std::uint32_t>(prefix),
static_cast<std::int32_t>(afterTail - prefix) - static_cast<std::int32_t>(beforeTail - prefix)};
}
void NormalizeCaret(Session& session) {
if (session.caret > session.text.size()) session.caret = static_cast<std::uint32_t>(session.text.size());
if (session.caret > 0 && session.caret < session.text.size() &&
IsHighSurrogate(session.text[session.caret - 1]) && IsLowSurrogate(session.text[session.caret])) {
++session.caret;
}
}
bool RunTextFilter(ImeTextFilter filter, const std::u16string& text, std::uint32_t maxLength, bool multiline,
std::u16string* filtered) {
std::vector<char16_t> buffer(maxLength + 1, u'\0');
std::uint32_t length = maxLength;
if (filter(buffer.data(), &length, text.c_str(), static_cast<std::uint32_t>(text.size())) != 0) {
return false;
}
if (length > maxLength || !ValidText(buffer.data(), length, multiline)) {
return false;
}
filtered->assign(buffer.data(), buffer.data() + length);
return true;
}
void CommitFilteredEdit(Session& session, const std::u16string* filtered, const std::u16string& oldText) {
if (filtered) {
session.text = *filtered;
NormalizeCaret(session);
}
if (session.text == oldText) return;
SyncBuffers(session);
const EditDelta edit = ComputeEditDelta(oldText, session.text);
QueueText(session, EventUpdateText, edit.index, edit.length);
}
std::optional<DeferredFilter> CommitEdit(Session& session, const std::u16string& oldText, std::uint32_t plainIndex,
std::int32_t plainDelta) {
if (session.param.filter) {
return DeferredFilter{oldText};
}
SyncBuffers(session);
QueueText(session, EventUpdateText, plainIndex, plainDelta);
return std::nullopt;
}
std::optional<DeferredFilter> Insert(Session& session, std::u16string units) {
const bool multiline = (session.param.option & OptionMultiline) != 0;
std::u16string accepted;
for (const char16_t unit : units) {
@@ -167,12 +239,12 @@ void Insert(Session& session, std::u16string units) {
const std::size_t room = session.param.max_text_length - session.text.size();
std::size_t count = std::min(accepted.size(), room);
if (count > 0 && count < accepted.size() && IsHighSurrogate(accepted[count - 1])) --count;
if (count == 0) return;
if (count == 0) return std::nullopt;
const uint32_t index = session.caret;
const std::u16string oldText = session.text;
session.text.insert(index, accepted, 0, count);
session.caret += static_cast<uint32_t>(count);
SyncBuffers(session);
QueueText(session, EventUpdateText, index, static_cast<int32_t>(count));
return CommitEdit(session, oldText, index, static_cast<int32_t>(count));
}
std::size_t UnitsBefore(const Session& session) {
@@ -187,65 +259,61 @@ std::size_t UnitsAfter(const Session& session) {
return caret + 1 < session.text.size() && IsHighSurrogate(session.text[caret]) && IsLowSurrogate(session.text[caret + 1]) ? 2 : 1;
}
void Apply(Session& session, const ImeHostInput& input) {
std::optional<DeferredFilter> Apply(Session& session, const ImeHostInput& input) {
switch (input.kind) {
case ImeHostInputKind::Text: {
std::size_t size = 0;
while (size < sizeof(input.text) && input.text[size] != '\0') ++size;
Insert(session, DecodeUtf8(input.text, size));
return;
return Insert(session, DecodeUtf8(input.text, size));
}
case ImeHostInputKind::Backspace: {
const std::size_t units = UnitsBefore(session);
if (units == 0) return;
if (units == 0) return std::nullopt;
const std::u16string oldText = session.text;
session.caret -= static_cast<uint32_t>(units);
session.text.erase(session.caret, units);
SyncBuffers(session);
QueueText(session, EventUpdateText, session.caret, -static_cast<int32_t>(units));
return;
return CommitEdit(session, oldText, session.caret, -static_cast<int32_t>(units));
}
case ImeHostInputKind::Delete: {
const std::size_t units = UnitsAfter(session);
if (units == 0) return;
if (units == 0) return std::nullopt;
const std::u16string oldText = session.text;
session.text.erase(session.caret, units);
SyncBuffers(session);
QueueText(session, EventUpdateText, session.caret, -static_cast<int32_t>(units));
return;
return CommitEdit(session, oldText, session.caret, -static_cast<int32_t>(units));
}
case ImeHostInputKind::Left: {
const std::size_t units = UnitsBefore(session);
session.caret -= static_cast<uint32_t>(units);
QueueCaretSteps(session, CaretMoveLeft, units);
return;
return std::nullopt;
}
case ImeHostInputKind::Right: {
const std::size_t units = UnitsAfter(session);
session.caret += static_cast<uint32_t>(units);
QueueCaretSteps(session, CaretMoveRight, units);
return;
return std::nullopt;
}
case ImeHostInputKind::Home: {
const std::size_t units = session.caret;
session.caret = 0;
QueueCaretSteps(session, CaretMoveLeft, units);
return;
return std::nullopt;
}
case ImeHostInputKind::End: {
const std::size_t units = session.text.size() - session.caret;
session.caret = static_cast<uint32_t>(session.text.size());
QueueCaretSteps(session, CaretMoveRight, units);
return;
return std::nullopt;
}
case ImeHostInputKind::Enter:
if ((session.param.option & OptionMultiline) != 0) {
Insert(session, u"\n");
} else {
QueueText(session, EventPressEnter, session.caret, 0);
return Insert(session, u"\n");
}
return;
QueueText(session, EventPressEnter, session.caret, 0);
return std::nullopt;
case ImeHostInputKind::Escape:
QueueText(session, EventPressClose, session.caret, 0);
return;
return std::nullopt;
}
throw std::invalid_argument("IME host input: unknown input kind");
}
@@ -289,7 +357,7 @@ void ImePublishInput_nid_postfix(const ImeHostInput& input) {
{
std::lock_guard lock(g_imeMutex);
if (g_session) {
Apply(*g_session, input);
g_session->hostInputs.push_back(input);
return;
}
}
@@ -317,10 +385,10 @@ int APS5_VABI sceImeOpen_nid_postfix(const Param* param, const ExtendedParam* ex
std::lock_guard lock(g_imeMutex);
if (g_session) return ErrorBusy;
if (const int error = ValidateOpen(param, extended)) return error;
if (param->filter) NotImplemented_nid_no_patch("sceImeOpen: text filter");
if (extended && extended->ext_keyboard_filter) NotImplemented_nid_no_patch("sceImeOpen: external keyboard filter");
Session session;
session.id = ++g_nextSessionId;
session.param = *param;
session.extKeyboardFilter = extended ? extended->ext_keyboard_filter : nullptr;
session.text.assign(param->input_text_buffer, TextLength(param->input_text_buffer, param->max_text_length));
session.caret = static_cast<uint32_t>(session.text.size());
SyncBuffers(session);
@@ -400,6 +468,16 @@ int APS5_VABI sceImeKeyboardSetMode(int32_t user_id, uint32_t mode) {
return (mode & ~ValidKeyboardModes) == 0 ? 0 : ErrorInvalidMode;
}
int APS5_VABI sceImeFilterText(char16_t* dst, int32_t* out_length, uint32_t capacity,
const ime_filter::FilterParam* param, const char16_t* src, uint32_t src_length) {
return static_cast<int>(ime_filter::FilterText(dst, out_length, capacity, param, src, src_length));
}
int APS5_VABI sceImeCheckFilterText(const void* param) {
(void)param;
NotImplemented_nid_no_patch(__func__);
}
void APS5_VABI sceImeParamInit(Param* param) {
if (!param) return;
std::memset(param, 0, sizeof(*param));
@@ -412,6 +490,7 @@ int APS5_VABI sceImeSetCaret(const Caret* caret) {
if (!caret) return ErrorInvalidAddress;
if (caret->index > g_session->text.size()) return ErrorInvalidParam;
g_session->caret = caret->index;
++g_session->revision;
return 0;
}
@@ -429,6 +508,7 @@ int APS5_VABI sceImeSetText(const char16_t* text, uint32_t length) {
const std::size_t count = TextLength(text, std::min(length, session.param.max_text_length));
if (!ValidText(text, count, (session.param.option & OptionMultiline) != 0)) return ErrorInvalidText;
session.text.assign(text, count);
++session.revision;
if (session.caret > session.text.size()) session.caret = static_cast<uint32_t>(session.text.size());
SyncBuffers(session);
return 0;
@@ -447,13 +527,55 @@ int APS5_VABI sceImeUpdate(EventHandler handler) {
for (;;) {
ImeEvent event{};
void* arg = nullptr;
bool deliver = false;
bool runFilter = false;
ImeTextFilter filter = nullptr;
std::u16string filterText;
std::u16string filterOldText;
std::uint32_t filterMaxLength = 0;
bool filterMultiline = false;
std::uint64_t sessionId = 0;
std::uint64_t sessionRevision = 0;
{
std::lock_guard lock(g_imeMutex);
if (!g_session || g_session->param.handler != handler || g_session->events.empty()) return 0;
event = g_session->events.front();
g_session->events.pop_front();
arg = g_session->param.arg;
if (!g_session || g_session->param.handler != handler) return 0;
if (!g_session->hostInputs.empty()) {
const ImeHostInput input = g_session->hostInputs.front();
g_session->hostInputs.pop_front();
if (const std::optional<DeferredFilter> deferred = Apply(*g_session, input)) {
runFilter = true;
filter = g_session->param.filter;
filterText = g_session->text;
filterOldText = deferred->oldText;
filterMaxLength = g_session->param.max_text_length;
filterMultiline = (g_session->param.option & OptionMultiline) != 0;
sessionId = g_session->id;
sessionRevision = g_session->revision;
}
} else if (!g_session->events.empty()) {
event = g_session->events.front();
g_session->events.pop_front();
arg = g_session->param.arg;
deliver = true;
} else {
return 0;
}
}
if (runFilter) {
std::u16string filtered;
const bool accepted = RunTextFilter(filter, filterText, filterMaxLength, filterMultiline, &filtered);
std::lock_guard lock(g_imeMutex);
if (g_session && g_session->id == sessionId) {
if (g_session->revision == sessionRevision) {
CommitFilteredEdit(*g_session, accepted ? &filtered : nullptr, filterOldText);
} else if (g_session->text == filterText) {
g_session->text = filterOldText;
NormalizeCaret(*g_session);
}
}
continue;
}
if (!deliver) continue;
handler(arg, &event);
}
}
@@ -0,0 +1,299 @@
#pragma once
#include <cstdint>
namespace ime_filter {
inline constexpr std::uint64_t kCodepageABitmap[1024] = {
0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffff00000000ull, 0xffffffffffffffffull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0xfffe03fbfffe0000ull, 0x00000000000003fbull,
0xffffffffffff0002ull, 0x000000000002ffffull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x480d006333710000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000080200400008ull, 0x03ff03ff00000000ull, 0x00000000000f0000ull, 0x0000000000140000ull,
0x20305fa1e406098dull, 0x00000cc300040000ull, 0x80000020000000ccull, 0x0000000000000000ull,
0x0000000000040000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x000fffff00000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x999999393999900full, 0x0000000000000804ull, 0x300c000300000000ull, 0x000080000000c8c0ull,
0x0000000000000060ull, 0x0000a40000000005ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x00000000b03fffefull, 0xfffffffffffffffeull, 0xfffffffe780fffffull, 0x787fffffffffffffull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0206000000000000ull, 0x0000000000000000ull, 0x000001f000000000ull, 0x0000000000000000ull,
0x084008cc01102008ull, 0x7800000000822600ull, 0x000000027000c000ull, 0x0000000000002010ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x9b46254243f36f8bull, 0x400a0004e3e0e82cull, 0x04497977db365f65ull, 0x18c5603ae3f0ecd7ull,
0x375180003403e60bull, 0x986982007eebe0c8ull, 0x8060e8032d56ad48ull, 0xc568c03aad93661cull,
0x02403f7ec656aa60ull, 0x21751020146183cdull, 0x40bc300007122021ull, 0x0a3060a84562a624ull,
0x9c84040285740217ull, 0x11e27f3414157ffbull, 0x60ff1f7522efb665ull, 0x676336c338403a70ull,
0x0fc946b020b24dd9ull, 0xa03f86384850bc98ull, 0x5232be4998162388ull, 0xc72c00ddeba422abull,
0x8f0a841b26e1a1e7ull, 0x89bfc241559e27ebull, 0x084d636185480014ull, 0x05cfff3eaad07f0cull,
0x7b407a41a803ff1aull, 0x38eb050080024745ull, 0x710c9b341005dc51ull, 0xa404636601000397ull,
0x430ac000005180d0ull, 0x5800000830c89071ull, 0x00415f80f7000ed9ull, 0x62800018941000b0ull,
0x0156820009d00240ull, 0x05101d1008015004ull, 0x10504025001084c1ull, 0xa60d40094d8a410full,
0x098121c0914cab19ull, 0x800006720203c485ull, 0x0009141d00080b04ull, 0x16900009905c49c9ull,
0x2433841222200c65ull, 0x42250a0447960c03ull, 0x4f0c4900d0880028ull, 0x3e87d830d3aa14a2ull,
0x41867ea41f618e04ull, 0x211857ad2dbbc390ull, 0x4e0411382a48241eull, 0x88400d60161b0a40ull,
0x106082219502020aull, 0x8000144404000243ull, 0x700000000c040000ull, 0x0c00024a00c11a06ull,
0x4045140400401a00ull, 0x052b0a78bdf30029ull, 0x8379407cbfa0bba9ull, 0xc5695bf6e91d12fdull,
0xff022115444aeff6ull, 0x0242d033402bed63ull, 0x5dca1b4200131000ull, 0x2c61a703020000a0ull,
0x000002848ff24880ull, 0x0048b200100d5804ull, 0x3780500420011894ull, 0x68be49ea684d3200ull,
0x21c9a8202e42184cull, 0xff7c001e80b050b9ull, 0x01e028c114e0849aull, 0xdddb130fac49870eull,
0x51b2a2e289fbbe1aull, 0x928b3ec632ca5522ull, 0x32986703438f1dbfull, 0xa923081173c03028ull,
0x04028fe33a65c000ull, 0x00a1bf3da6252c4eull, 0x317c06c98cd43e3aull, 0x0edf018bd52a00e0ull,
0xf09111838c22e34bull, 0x40fbc9aca7287d94ull, 0x44445a9007534484ull, 0xf5d4004800013fc8ull,
0x891dc442ec5f7701ull, 0xd242410949286b83ull, 0x3a22184059fe061dull, 0xc0eaf0033b9fb7e4ull,
0xe400898082021386ull, 0x0cc44b8010a1b200ull, 0x48341faf8944d309ull, 0x0470420a0c458259ull,
0x4450314010c8a040ull, 0x0540828101004004ull, 0x1a056a30642c0108ull, 0x645690cf051460a6ull,
0xcbf09c1831000021ull, 0x01b5104c63e2e120ull, 0x3281b8b29a83538cull, 0x0c0233e70a84987aull,
0x9872e1b1d038d6cdull, 0x0459c3f4e2848a1eull, 0xd314484523c2439aull, 0xffbd024136400292ull,
0xa5d27dc0e8f0eb09ull, 0xd0afa47fd24bc242ull, 0x0bd8824734a11aa0ull, 0xc83ad294651bc453ull,
0x33140e0640c8001eull, 0xc0d00088b21f615full, 0x166ba1c5a898a02aull, 0x0604c08b85b4af50ull,
0xa251056e1e04f933ull, 0x73b8ed0776380400ull, 0xc816408119324406ull, 0xaa04298463097c8aull,
0x27614e0eca9c1c24ull, 0xc10c0846830009d0ull, 0x0908540d10816011ull, 0x0c000514cc0a000eull,
0x6784008ba0440430ull, 0x8b18865e8a195288ull, 0x9cbe8c1041602e59ull, 0x00089800895c6861ull,
0xc1900018089a8100ull, 0x640d8505f4a14007ull, 0xff0a48060e4d314eull, 0x000b852e2ea81632ull,
0x696c0e20ca841810ull, 0x0390d65816000032ull, 0x112490001a6851a0ull, 0x1fae5d52432698e1ull,
0x5700fafbae280fa0ull, 0xc044c88099406408ull, 0xa4c48424b1419005ull, 0xc1949000603a1a34ull,
0xc106180d003a8246ull, 0x1511e05099100022ull, 0x022a041a00824157ull, 0x446ad8138930004full,
0x400511c0ed228aa2ull, 0x3101880801021000ull, 0x0f08f80002044620ull, 0x22020000a2008900ull,
0x1040004216108210ull, 0x200052f4126052c0ull, 0x4202110082308510ull, 0xda2070e180b5430aull,
0xfc65350008012040ull, 0x62140286ab0419c1ull, 0x4246908500440087ull, 0x338032070a85405cull,
0xc0d0ce30b8c00400ull, 0x0da505080080c030ull, 0x280c020000400a90ull, 0x4122642940446705ull,
0x847c4664000002e8ull, 0x4049861dde200002ull, 0x20010084c0000a08ull, 0x01c742cd10108400ull,
0x1d8f9968d52a703aull, 0x81d9aef53e12be50ull, 0x732e08282412cec4ull, 0xd41d020c4b3424acull,
0x0811009780002a02ull, 0x7d451786114411c4ull, 0x879140405e4949ddull, 0x491444bad8c4254cull,
0x15800271c8001b92ull, 0xc200096a0c0000c1ull, 0xba49302140024800ull, 0x1008e2ac1c802080ull,
0x841400e300341004ull, 0x1414981020004020ull, 0x5420868804aa70c2ull, 0x2010918004130c62ull,
0x54011c4002064082ull, 0x84802125e4e90383ull, 0xe60944c02810e433ull, 0x080112da81260a03ull,
0xf886400197906901ull, 0xa6510a0e0081e24dull, 0x8441c60081ec011aull, 0x8741acefb62eadb8ull,
0x026811614b028d54ull, 0x043350a02057bb60ull, 0x01122402f7b4a8c0ull, 0x00c8227120009ad3ull,
0xe1800c8a809e2081ull, 0x402810318151b009ull, 0x620e69b689a52a0eull, 0x4d548085d1444425ull,
0x862dd8071fb12c75ull, 0x226e414e5841d97cull, 0xedb7f80d9e088200ull, 0x0814931375668c80ull,
0x6ea6484ec8040e32ull, 0xba0126c066742c4aull, 0x00000000185dd70cull, 0x0000000000000000ull,
0x0540000000000000ull, 0x03a54f81813370a0ull, 0x2344c31a641055ecull, 0x1a090a4300341462ull,
0xa848010213a5187bull, 0xe2dd8106c5440440ull, 0x0416b6262d481af0ull, 0x311280326e405058ull,
0x420a82080c0007e4ull, 0x87134860803b4840ull, 0xe52903193428850dull, 0x5c1825a9870a2345ull,
0x03e85e00d9c577a6ull, 0x41c6cd54a7000081ull, 0x2b0ab860a2042800ull, 0x0e1a08eada9e0020ull,
0x0376890811c0427eull, 0x98a8000401058621ull, 0x20220d05c44846a0ull, 0x28d78a01914854a2ull,
0x3122160500087898ull, 0x06a2fa4e08804340ull, 0x9b14200292110814ull, 0x9010500016432e52ull,
0x2020304285ba0041ull, 0x40802f0807a84f0bull, 0x0601df501a930591ull, 0x4e8006303021a202ull,
0x8001a00404c80cc4ull, 0x0a020880d4316000ull, 0x00418e1800281c00ull, 0x4b00f210ca106ad0ull,
0x889002201506274dull, 0x8150454982a85a00ull, 0x2c08880480002004ull, 0x4ac48001000508d1ull,
0x0a42008e0062e0a0ull, 0xe0a5090e6a8c3055ull, 0x80b3481442c42906ull, 0x733c0102b330803eull,
0x09400c20700d1494ull, 0xc094a451c040301aull, 0xa40c96c205c88dcaull, 0x011000c834040001ull,
0x1cda2428a9cd550dull, 0x120f7a4d48370142ull, 0xd20531fb452a32b4ull, 0x45ca68d7dc44b894ull,
0x420819432ed15097ull, 0xa09798409d48d202ull, 0x00000000064d5409ull, 0x0000000000000000ull,
0x8480000000000000ull, 0x17001c0604215542ull, 0xb9ddff8761107624ull, 0x3c11245d5c0a659full,
0x00000000005dadb0ull, 0x00db28d000000000ull, 0x4408010802000422ull, 0x90288d0aac409804ull,
0x00310400e0018700ull, 0x1054001982211794ull, 0x40039c02021a2cb2ull, 0x7900080c8804bd60ull,
0xcb088640ba3c1628ull, 0x0000001e90807274ull, 0x9c87e188d8000000ull, 0x2791ae6404124034ull,
0x5366408fe6fbe86bull, 0xb5e4e3ab537feea6ull, 0x012285480002869full, 0x20a0211648004402ull,
0x0005208002240004ull, 0x01ac162c01547e00ull, 0x05308c1410852a84ull, 0x906060fafdc3fbc3ull,
0x9690120040336440ull, 0x418200d44e834b31ull, 0x028020801d6a0129ull, 0x9f0c269102ad8000ull,
0x0c24d96f67018044ull, 0x5021500118d02910ull, 0x0201709004d01000ull, 0x0100013261c30148ull,
0x0562080207190088ull, 0xf0a104054c0e0132ull, 0x0000000000000002ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0080000000000000ull, 0x5a0421bd035e8e8dull, 0x0000002611703488ull,
0x8804c50210000000ull, 0x25ed147cf801b815ull, 0x1bd785893bb0ed60ull, 0x0ac50d0c1a627af3ull,
0x6b0d0490524ae5d1ull, 0x16122b575266a35cull, 0x001829491101a872ull, 0x886c600010080948ull,
0x39903012058f916eull, 0x001b88a049b0f840ull, 0x0042850000000000ull, 0x7014ea0498000058ull,
0x60005193611d1628ull, 0x0000000000a71a24ull, 0x1018712043c00000ull, 0x89066004a9270172ull,
0x40810900020cc022ull, 0x00000e348ca0602dull, 0x1101210000000000ull, 0x0892ec4cd31a8011ull,
0x1806c7ac85000040ull, 0x003480000512e03eull, 0x0a126d0180cec008ull, 0x0027011e08568641ull,
0x4e05e032083d3751ull, 0x01400081048401c0ull, 0x0000000000000000ull, 0x00591aa000000000ull,
0xc8001d48882443c8ull, 0x0405981372030152ull, 0x0d148a1004008280ull, 0x2704a04002088056ull,
0x000000004e000000ull, 0xa320000000000000ull, 0xdf002660a0ae1902ull, 0x3ad081217b17f010ull,
0x4800100300284180ull, 0x00c414cf8014cc00ull, 0x0000000130202000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000020000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000010000000ull,
0x00003fffffffc000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull,
0xfffffffffffffffeull, 0xfffffffe7fffffffull, 0x00000000ffffffffull, 0x0000003f00000000ull,
};
struct CodepageRange {
std::uint16_t first;
std::uint16_t last;
};
inline constexpr CodepageRange kCodepageBRanges[151] = {
{0x0000, 0x00ff}, {0x0101, 0x0101}, {0x0113, 0x0113}, {0x011b, 0x011b}, {0x012b, 0x012b}, {0x0144, 0x0144},
{0x0148, 0x0148}, {0x014d, 0x014d}, {0x016b, 0x016b}, {0x01ce, 0x01ce}, {0x01d0, 0x01d0}, {0x01d2, 0x01d2},
{0x01d4, 0x01d4}, {0x01d6, 0x01d6}, {0x01d8, 0x01d8}, {0x01da, 0x01da}, {0x01dc, 0x01dc}, {0x01f9, 0x01f9},
{0x0251, 0x0251}, {0x0261, 0x0261}, {0x02c7, 0x02c7}, {0x02c9, 0x02cb}, {0x02d9, 0x02d9}, {0x0391, 0x03a1},
{0x03a3, 0x03a9}, {0x03b1, 0x03c1}, {0x03c3, 0x03c9}, {0x0401, 0x0401}, {0x0410, 0x044f}, {0x0451, 0x0451},
{0x2010, 0x2010}, {0x2013, 0x2016}, {0x2018, 0x2019}, {0x201c, 0x201d}, {0x2025, 0x2026}, {0x2030, 0x2030},
{0x2032, 0x2033}, {0x2035, 0x2035}, {0x203b, 0x203b}, {0x20ac, 0x20ac}, {0x2103, 0x2103}, {0x2105, 0x2105},
{0x2109, 0x2109}, {0x2116, 0x2116}, {0x2121, 0x2121}, {0x2160, 0x216b}, {0x2170, 0x2179}, {0x2190, 0x2193},
{0x2196, 0x2199}, {0x2208, 0x2208}, {0x220f, 0x220f}, {0x2211, 0x2211}, {0x2215, 0x2215}, {0x221a, 0x221a},
{0x221d, 0x2220}, {0x2223, 0x2223}, {0x2225, 0x2225}, {0x2227, 0x222b}, {0x222e, 0x222e}, {0x2234, 0x2237},
{0x223d, 0x223d}, {0x2248, 0x2248}, {0x224c, 0x224c}, {0x2252, 0x2252}, {0x2260, 0x2261}, {0x2264, 0x2267},
{0x226e, 0x226f}, {0x2295, 0x2295}, {0x2299, 0x2299}, {0x22a5, 0x22a5}, {0x22bf, 0x22bf}, {0x2312, 0x2312},
{0x2460, 0x2469}, {0x2474, 0x249b}, {0x2500, 0x254b}, {0x2550, 0x2573}, {0x2581, 0x258f}, {0x2593, 0x2595},
{0x25a0, 0x25a1}, {0x25b2, 0x25b3}, {0x25bc, 0x25bd}, {0x25c6, 0x25c7}, {0x25cb, 0x25cb}, {0x25ce, 0x25cf},
{0x25e2, 0x25e5}, {0x2605, 0x2606}, {0x2609, 0x2609}, {0x2640, 0x2640}, {0x2642, 0x2642}, {0x2e84, 0x2e84},
{0x2e88, 0x2e88}, {0x2e8b, 0x2e8c}, {0x2e97, 0x2e97}, {0x2ea7, 0x2ea7}, {0x2eaa, 0x2eaa}, {0x2eb6, 0x2eb7},
{0x2ebb, 0x2ebb}, {0x2ff0, 0x2ffb}, {0x3000, 0x3003}, {0x3005, 0x3017}, {0x301d, 0x301e}, {0x3021, 0x3029},
{0x303e, 0x303e}, {0x3041, 0x3093}, {0x309b, 0x309e}, {0x30a1, 0x30f6}, {0x30fc, 0x30fe}, {0x3105, 0x3129},
{0x3220, 0x3229}, {0x3231, 0x3231}, {0x32a3, 0x32a3}, {0x338e, 0x338f}, {0x339c, 0x339e}, {0x33a1, 0x33a1},
{0x33c4, 0x33c4}, {0x33ce, 0x33ce}, {0x33d1, 0x33d2}, {0x33d5, 0x33d5}, {0x3400, 0x3917}, {0x3919, 0x39ce},
{0x39d0, 0x39de}, {0x39e0, 0x415e}, {0x4160, 0x4336}, {0x4338, 0x4728}, {0x472a, 0x477b}, {0x477d, 0x478c},
{0x478e, 0x4c9e}, {0x4ca1, 0x4ca2}, {0x4ca4, 0x4db5}, {0x4e00, 0x9fa5}, {0xe766, 0xe864}, {0xf92c, 0xf92c},
{0xf979, 0xf979}, {0xf995, 0xf995}, {0xf9e7, 0xf9e7}, {0xf9f1, 0xf9f1}, {0xfa0c, 0xfa0f}, {0xfa11, 0xfa11},
{0xfa13, 0xfa14}, {0xfa18, 0xfa18}, {0xfa1f, 0xfa21}, {0xfa23, 0xfa24}, {0xfa27, 0xfa29}, {0xfe30, 0xfe31},
{0xfe33, 0xfe44}, {0xfe49, 0xfe52}, {0xfe54, 0xfe57}, {0xfe59, 0xfe66}, {0xfe68, 0xfe6b}, {0xff01, 0xff5e},
{0xffe0, 0xffe5},
};
}
@@ -0,0 +1,177 @@
#pragma once
#include <cstddef>
#include <cstdint>
#include "ImeFilterCodepages.hpp"
namespace ime_filter {
struct FilterParam {
std::uint64_t mask;
std::uint32_t type;
std::uint32_t version;
std::uint32_t variant;
};
static_assert(sizeof(FilterParam) == 24);
constexpr std::uint32_t kErrorInvalidParam = 0x80bc0030u;
constexpr std::uint32_t kErrorInvalidAddress = 0x80bc0031u;
constexpr std::uint64_t kClassCyrillic = 0x200000400ull;
constexpr std::uint64_t kClassVietnamese = 0x40000000ull;
constexpr std::uint64_t kClassRomanian = 0x80000000ull;
constexpr std::uint64_t kClassThai = 0x4000000ull;
constexpr std::uint64_t kAbsentMasks[8] = {0x188000000ull, 0x40000000ull, 0x10000000ull, 0x1000000ull,
0x2000ull, 0x18000ull, 0x6000ull, 0x4000ull};
constexpr char16_t kPunctuationLatin[] = {
0xa1, 0xbf, 0xb4, 0x2018, 0x2019, 0x201a, 0x201c, 0x201d, 0x201e, 0xb0, 0x2039, 0x203a,
0xab, 0xbb, 0xaa, 0xba, 0xd7, 0xf7, 0xa4, 0xa2, 0x20ac, 0xa3, 0xa5, 0x20a9,
0xa7, 0xa6, 0xb5, 0xac, 0xb7, 0xb9, 0xb2, 0xb3, 0xbc, 0xbd, 0xbe, 0x2116};
constexpr char16_t kPunctuationFullwidth[] = {
0x3001, 0x3002, 0x30fc, 0x30fb, 0x300c, 0x300d, 0xffe5, 0xff01, 0x201d, 0xff03, 0xff04, 0xff05, 0xff06,
0x2019, 0xff08, 0xff09, 0xff0a, 0xff0b, 0xff0c, 0xff0d, 0xff0e, 0xff0f, 0xff1a, 0xff1b, 0xff1c, 0xff1d,
0xff1e, 0xff1f, 0xff20, 0xff3b, 0xff3c, 0xff3d, 0xff3e, 0xff3f, 0xff40, 0xff5b, 0xff5c, 0xff5d, 0xff5e};
constexpr char16_t kPunctuationFullwidthNoProlong[] = {
0x3001, 0x3002, 0x300c, 0x300d, 0xff01, 0x201d, 0xff03, 0xff04, 0xff05, 0xff06, 0x2019, 0xff08,
0xff09, 0xff0a, 0xff0b, 0xff0c, 0xff0d, 0xff0e, 0xff0f, 0xff1a, 0xff1b, 0xff1c, 0xff1d, 0xff1e,
0xff1f, 0xff20, 0xff3b, 0xff3c, 0xff3d, 0xff3e, 0xff3f, 0xff40, 0xff5b, 0xff5c, 0xff5d, 0xff5e};
constexpr char16_t kVietnameseLetters[] = {0x1a0, 0x1a1, 0x1af, 0x1b0, 0x20ab};
constexpr char16_t kModifierLetters[] = {0x2c7, 0x2d8, 0x2d9, 0x2db, 0x2dd};
constexpr char16_t kNumberSeparators[] = {0x2c, 0x2e, 0x2d, 0x0a};
template <std::size_t N>
constexpr bool In(const char16_t (&set)[N], std::uint32_t c) {
for (char16_t v : set) {
if (v == c) return true;
}
return false;
}
constexpr bool InCodepageA(std::uint32_t c) {
return ((kCodepageABitmap[c >> 6] >> (c & 63)) & 1u) != 0;
}
constexpr bool InCodepageB(std::uint32_t c) {
constexpr std::size_t count = sizeof(kCodepageBRanges) / sizeof(kCodepageBRanges[0]);
std::size_t low = 0;
std::size_t high = count;
while (low < high) {
const std::size_t middle = (low + high) / 2;
if (c > kCodepageBRanges[middle].last) {
low = middle + 1;
} else {
high = middle;
}
}
return low < count && c >= kCodepageBRanges[low].first;
}
constexpr bool IsVietnameseExtension(std::uint32_t c) {
return static_cast<std::uint16_t>(c - 0x300) < 0x24 || (c & 0xff00) == 0x1e00 || In(kVietnameseLetters, c);
}
constexpr std::uint64_t DerivedMask(std::uint32_t version, std::uint32_t variant) {
const std::uint32_t second = variant == 1 ? 0x10000000u : 0x6000000u;
const std::uint64_t flag = (variant == 0 || version >= 0x2000000u) ? (1u << 24) : 0;
std::uint64_t low = flag + 0x1fffffu;
const std::uint64_t high = flag | 0x21fffffu;
if (version >= 0x2500000u || variant == 0) low = high;
const std::uint64_t wide = 0x200000000ull + low - 0x4000000ull;
if (version >= 0x5000000u || variant == 0) low = wide;
return version < second ? low : (low | 0x200000000ull);
}
constexpr bool ValidMask(std::uint64_t mask, std::uint32_t version, std::uint32_t variant, std::uint32_t* limit) {
const bool none = variant == 0;
const bool wide = none || version >= 0x5000000u;
const std::uint64_t shifted = (none || version >= 0x2500000u ? 1u : 0u) << 25;
const std::uint64_t first = wide ? 0x200000000ull + shifted - 0x17e1e401ull : (shifted | 0x1e1bffull);
if ((mask & first) != 0) {
*limit = 0x17f;
return true;
}
*limit = 0x7f;
const std::uint32_t second = variant == 1 ? 0x10000000u : 0x6000000u;
const std::uint64_t flag = static_cast<std::uint64_t>(none || version >= 0x2000000u ? 1u : 0u) << 24;
const std::uint64_t base = (wide ? 0x1401e400ull : 0x1e400ull) | flag;
const std::uint64_t allowed = version < second ? base : (base | 0x200000000ull);
return (mask & allowed) != 0;
}
constexpr bool HighBitSet(std::uint64_t mask) {
return (mask & 0x80000000ull) != 0;
}
constexpr bool KeepClassified(std::uint32_t c, std::uint64_t mask, std::uint32_t limit, const bool (&absent)[8]) {
if (c < limit) return true;
if (In(kPunctuationLatin, c)) return true;
if (!absent[7] && (static_cast<std::uint16_t>(c - 0x3130) < 0x60 || static_cast<std::uint16_t>(c + 0x5400) < 0x2bb0)) {
return true;
}
if ((kClassCyrillic & mask) != 0 && (c & 0xff00) == 0x400) return true;
if (!absent[6] && (static_cast<std::uint16_t>(c + 0xf0) <= 9 || In(kPunctuationFullwidth, c))) return true;
if (!absent[5] && (static_cast<std::uint16_t>(c + 0xf0) <= 9 || In(kPunctuationFullwidthNoProlong, c))) return true;
if (!absent[4] && InCodepageA(c)) return true;
if (!absent[5] && InCodepageB(c)) return true;
if (!absent[3] && ((c & 0xff00) == 0x600 || (c & 0xfffc) == 0x200c)) return true;
if ((mask & kClassThai) != 0 && (c & 0xff80) == 0xe00) return true;
if (!absent[2] && (static_cast<std::uint16_t>(c - 0x370) < 0x72 || (c & 0xfff0) == 0x3f0)) return true;
if (!absent[1] && IsVietnameseExtension(c)) return true;
if (HighBitSet(mask) && (c & 0xfffc) == 0x218) return true;
if (!absent[0] && In(kModifierLetters, c)) return true;
return false;
}
inline std::uint32_t FilterText(char16_t* dst, std::int32_t* out_length, std::uint32_t capacity,
const FilterParam* param, const char16_t* src, std::uint32_t src_length) {
if (dst == nullptr || out_length == nullptr || param == nullptr || src == nullptr) return kErrorInvalidAddress;
if (capacity < 2) return kErrorInvalidParam;
const std::uint32_t type = param->type;
if (type >= 5 && type - 0x100 > 3) return kErrorInvalidParam;
std::uint64_t mask = param->mask;
if (mask == 0) mask = DerivedMask(param->version, param->variant);
std::uint32_t limit = 0;
if (!ValidMask(mask, param->version, param->variant, &limit)) return kErrorInvalidParam;
bool absent[8];
for (int i = 0; i < 8; ++i) absent[i] = (mask & kAbsentMasks[i]) == 0;
std::uint32_t room = capacity - 1;
std::uint32_t count = 0;
for (std::uint64_t i = 0; i < src_length && room != 0; ++i) {
const std::uint32_t c = src[i];
if (c == 0) break;
bool keep;
if (type == 4) {
keep = static_cast<std::uint16_t>(c - 0x30) <= 9 || In(kNumberSeparators, c);
} else if (type == 1 || type == 3 || type == 0x102) {
keep = c <= 0x7e;
} else if (type == 0x100) {
keep = c < 0x17f;
if (!keep && (mask & kClassVietnamese) != 0) {
const bool listed = static_cast<std::uint16_t>(c - 0x300) >= 0x24 && (c & 0xff00) != 0x1e00;
keep = !listed || In(kVietnameseLetters, c);
}
if (!keep && HighBitSet(mask)) keep = (c & 0xfffc) == 0x218;
} else {
keep = KeepClassified(c, mask, limit, absent);
}
if (keep) {
dst[count++] = static_cast<char16_t>(c);
--room;
}
}
dst[count] = 0;
*out_length = static_cast<std::int32_t>(count);
return 0;
}
}
@@ -0,0 +1,15 @@
add_library(libSceM4aacEnc SHARED EXCLUDE_FROM_ALL Export.cpp)
target_include_directories(libSceM4aacEnc PRIVATE ${LIBS_INCLUDE_DIR})
configure_windows_unwind(libSceM4aacEnc)
if(BUILD_TESTING)
add_test_executable(guest_m4aac_enc_tests ${LIBS_INCLUDE_DIR}/tests/GuestM4aacEnc.cpp)
target_include_directories(guest_m4aac_enc_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_m4aac_enc_tests PRIVATE libSceM4aacEnc libc)
configure_windows_unwind(guest_m4aac_enc_tests)
add_test(NAME guest_m4aac_enc COMMAND guest_m4aac_enc_tests)
if(WIN32)
set_tests_properties(guest_m4aac_enc PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceM4aacEnc>")
endif()
endif()
+94
View File
@@ -0,0 +1,94 @@
#include <cstdint>
#include <cstddef>
#include "SceTypes.hpp"
#include "prx/libc/include/General.hpp"
extern "C" {
int32_t APS5_VABI sceM4aacEncClearContext(int32_t m4aacEncHandle) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceM4aacEncClearContextEx() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int32_t APS5_VABI sceM4aacEncCreateEncoder(int32_t channels, int32_t samplingRate, int32_t bitRate, int32_t inputFormat, int32_t outputFormat, int32_t *pInternalError) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceM4aacEncCreateEncoderEx() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int32_t APS5_VABI sceM4aacEncDeleteEncoder(int32_t m4aacEncHandle, int32_t *pInternalError) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceM4aacEncDeleteEncoderEx() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int32_t APS5_VABI sceM4aacEncEncode(int32_t m4aacEncHandle, void *pInput, uint32_t inputByte, void *pOutput, uint32_t *pOutputByte, int32_t *pInternalError) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceM4aacEncEncodeEx() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceM4aacEncEncodeWithPriority() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceM4aacEncEncodeWithPriorityEx() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int32_t APS5_VABI sceM4aacEncFlush(int32_t m4aacEncHandle, void *pOutput, uint32_t *pOutputByte, int32_t *pInternalError) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceM4aacEncFlushEx() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceM4aacEncFlushWithPriority() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceM4aacEncFlushWithPriorityEx() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceM4aacEncGetContext() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceM4aacEncInitialize() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceM4aacEncTerminate() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
}
+9 -5
View File
@@ -64,6 +64,8 @@ static constexpr int NET_CTL_INFO_SECONDARY_DNS = 18;
static constexpr int NET_CTL_INFO_HTTP_PROXY_CONFIG = 19;
static constexpr int NET_CTL_INFO_HTTP_PROXY_SERVER = 20;
static constexpr int NET_CTL_INFO_HTTP_PROXY_PORT = 21;
static constexpr int NET_CTL_INFO_V6_IP_ADDRESS = 1;
static constexpr int NET_CTL_INFO_V6_SECONDARY_DNS = 5;
static constexpr std::uint32_t NET_CTL_DEVICE_WIRED = 0;
static constexpr std::uint32_t NET_CTL_LINK_CONNECTED = 1;
static constexpr std::uint32_t NET_CTL_IP_DHCP = 0;
@@ -456,11 +458,6 @@ int APS5_VABI sceNetCtlGetInfo(int code, NetCtlInfo* info) {
return 0;
}
int APS5_VABI sceNetCtlGetInfoV6() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNetCtlGetNatInfo(NetCtlNatInfo* nat_info) {
if (!nat_info) return SCE_NET_CTL_ERROR_INVALID_ADDR;
return SCE_NET_CTL_ERROR_NOT_CONNECTED;
@@ -497,6 +494,13 @@ int APS5_VABI sceNetCtlRegisterCallbackV6(NetCtlCallback func, void* arg, int* c
return RegisterCallback(g_callbacksV6, func, arg, cid);
}
int APS5_VABI sceNetCtlGetInfoV6(int code, void* info) {
if (!info) return SCE_NET_CTL_ERROR_INVALID_ADDR;
if (code < NET_CTL_INFO_V6_IP_ADDRESS || code > NET_CTL_INFO_V6_SECONDARY_DNS) return SCE_NET_CTL_ERROR_INVALID_CODE;
if (!HostHasAddress(AF_INET6)) return SCE_NET_CTL_ERROR_NOT_CONNECTED;
throw std::runtime_error("sceNetCtlGetInfoV6: info code " + std::to_string(code) + " is not implemented");
}
void APS5_VABI sceNetCtlTerm(void) {
std::lock_guard lock(g_callbackLock);
for (auto& slot : g_callbacks) slot = {};
+2 -2
View File
@@ -177,13 +177,13 @@ int APS5_VABI scePadOutputReport() { NotImplemented_nid_no_patch(__func__); retu
int APS5_VABI scePadRead_nid_postfix(int handle, PadData* data, int num) {
if (handle != PAD_HANDLE) return PAD_ERROR_INVALID_HANDLE;
if (data == nullptr || num <= 0) APS5_INVALID_ARG_EX;
if (data == nullptr || num <= 0) return PAD_ERROR_INVALID_ARG;
return Pad::Read(data, num);
}
int APS5_VABI scePadReadState(int handle, PadData* data) {
if (handle != PAD_HANDLE) return PAD_ERROR_INVALID_HANDLE;
if (data == nullptr) APS5_INVALID_ARG_EX;
if (data == nullptr) return PAD_ERROR_INVALID_ARG;
*data = Pad::ReadState();
@@ -1,13 +1,18 @@
#ifndef CORE_LIBS_PRX_LIBSCEPAD_INPUTCOMMANDFILE_HPP
#define CORE_LIBS_PRX_LIBSCEPAD_INPUTCOMMANDFILE_HPP
#include <algorithm>
#include <array>
#include <charconv>
#include <cstdint>
#include <fstream>
#include <iterator>
#include <limits>
#include <sstream>
#include <filesystem>
#include <string>
#include <utility>
#include <vector>
namespace Pad {
class InputCommandFile {
@@ -29,24 +34,36 @@ public:
Input Poll(std::uint64_t now) {
if (path.empty()) return {};
if (now >= nextPoll) {
nextPoll = now + 100000;
const auto command = read();
nextPoll = now > std::numeric_limits<std::uint64_t>::max() - 100000
? std::numeric_limits<std::uint64_t>::max() : now + 100000;
auto command = read();
if (command.sequence > lastSequence) {
lastSequence = command.sequence;
input = command.input;
until = now + command.durationMs * 1000ull;
events = std::move(command.events);
started = now;
}
}
return now < until ? input : Input{};
if (events.empty() || now < started) return {};
const auto elapsed = now - started;
const auto next = std::upper_bound(events.begin(), events.end(), elapsed,
[](std::uint64_t time, const Event& event) { return time < event.start; });
if (next == events.begin()) return {};
const auto& event = *std::prev(next);
return elapsed < event.end ? event.input : Input{};
}
std::uint32_t Buttons(std::uint64_t now) { return Poll(now).buttons; }
private:
struct Event {
std::uint64_t start = 0;
std::uint64_t end = 0;
Input input;
};
struct Command {
std::uint64_t sequence = 0;
Input input;
std::uint32_t durationMs = 0;
std::vector<Event> events;
};
Command read() const {
@@ -56,6 +73,40 @@ private:
const std::string text(line);
if (text.find('-') != std::string::npos) return {};
std::istringstream fields(text);
std::string first;
if (!(fields >> first)) return {};
if (first == "APS5_PAD_TIMELINE") {
Command command;
std::string extra;
std::uint64_t version = 0, count = 0;
if (!(fields >> version >> command.sequence >> count) || fields >> extra) return {};
if (version != 1 || command.sequence == 0 || count == 0 || count > 4096) return {};
if (command.sequence <= lastSequence) return command;
command.events.reserve(static_cast<std::size_t>(count));
std::uint64_t previousEnd = 0;
for (std::uint64_t index = 0; index < count; ++index) {
if (!file.getline(line, sizeof(line))) return {};
const std::string eventText(line);
if (eventText.find('-') != std::string::npos) return {};
fields.clear();
fields.str(eventText);
std::uint64_t start = 0, duration = 0, buttons = 0;
if (!(fields >> start >> duration >> buttons) || fields >> extra) return {};
if ((buttons & ~(0x10fffeull | TouchLeftBit | TouchRightBit)) != 0 || duration == 0 || duration > 5000 ||
start < previousEnd || start > 3600000 - duration) return {};
previousEnd = start + duration;
Input input;
input.buttons = static_cast<std::uint32_t>(buttons & 0x10fffeull);
input.touchLeft = (buttons & TouchLeftBit) != 0;
input.touchRight = (buttons & TouchRightBit) != 0;
command.events.push_back({start * 1000, previousEnd * 1000, input});
}
file >> std::ws;
if (file.peek() != std::char_traits<char>::eof()) return {};
return command;
}
fields.clear();
fields.str(text);
std::string tokens[8];
int count = 0;
while (count < 8 && fields >> tokens[count]) ++count;
@@ -70,15 +121,16 @@ private:
if (values[0] == 0 || (values[1] & ~(0x10fffeull | TouchLeftBit | TouchRightBit)) != 0 || values[2] == 0 || values[2] > 5000) return {};
Command command;
command.sequence = values[0];
command.input.buttons = static_cast<std::uint32_t>(values[1] & 0x10fffeull);
command.input.touchLeft = (values[1] & TouchLeftBit) != 0;
command.input.touchRight = (values[1] & TouchRightBit) != 0;
command.durationMs = static_cast<std::uint32_t>(values[2]);
Input input;
input.buttons = static_cast<std::uint32_t>(values[1] & 0x10fffeull);
input.touchLeft = (values[1] & TouchLeftBit) != 0;
input.touchRight = (values[1] & TouchRightBit) != 0;
for (int i = 3; i < count; ++i) {
if (values[i] > 255) return {};
command.input.sticks[i - 3] = static_cast<std::uint8_t>(values[i]);
input.sticks[i - 3] = static_cast<std::uint8_t>(values[i]);
}
command.input.hasSticks = count == 7;
input.hasSticks = count == 7;
command.events.push_back({0, values[2] * 1000, input});
return command;
}
@@ -88,8 +140,8 @@ private:
std::filesystem::path path;
std::uint64_t lastSequence = 0;
std::uint64_t nextPoll = 0;
std::uint64_t until = 0;
Input input;
std::uint64_t started = 0;
std::vector<Event> events;
};
}
@@ -0,0 +1,111 @@
#include "prx/libScePad/include/InputCommandFile.hpp"
#include <chrono>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <limits>
#include <stdexcept>
void Check(bool condition) {
if (!condition) throw std::runtime_error("input command assertion failed");
}
int main() {
const auto path = std::filesystem::temp_directory_path() /
("anyps5-pad-" + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count()));
const auto write = [&](const std::string& text) { std::ofstream(path) << text; };
try {
write("1 16384 300\n");
Pad::InputCommandFile input(path);
Check(input.Buttons(0) == 0);
write("APS5_PAD_TIMELINE 1 2 3\n200 100 18432\n500 200 16\n800 100 0\n");
Check(input.Buttons(100000) == 0);
Check(input.Buttons(299999) == 0);
Check(input.Buttons(300000) == 18432);
Check(input.Buttons(399999) == 18432);
Check(input.Buttons(400000) == 0);
Check(input.Buttons(600000) == 16);
Check(input.Buttons(800000) == 0);
Check(input.Buttons(1100000) == 0);
write("APS5_PAD_TIMELINE 1 3 2\n0 500 32768\n300 100 16\n");
Check(input.Buttons(1200000) == 0);
write("APS5_PAD_TIMELINE 1 3 2\n0 500 32768\n600 100 16\n");
Check(input.Buttons(1300000) == 32768);
write("APS5_PAD_TIMELINE 1 4 1\n0 6000 16\n");
Check(input.Buttons(1400000) == 32768);
write("APS5_PAD_TIMELINE 1 4 1\n0 500 16\nextra\n");
Check(input.Buttons(1500000) == 32768);
write("APS5_PAD_TIMELINE 1 4 2\n0 100 16\n");
Check(input.Buttons(1600000) == 32768);
write("APS5_PAD_TIMELINE 2 4 1\n0 100 16\n");
Check(input.Buttons(1700000) == 32768);
Check(input.Buttons(1800000) == 0);
write("4 8 200\n");
Check(input.Buttons(1900000) == 8);
Check(input.Buttons(2100000) == 0);
write("3 16 200\n");
Check(input.Buttons(2200000) == 0);
write("5 0 1\n");
Check(input.Buttons(2300000) == 0);
const std::string bad[] = {
"APS5_PAD_TIMELINE 1 6 0\n", "APS5_PAD_TIMELINE 1 6 4097\n",
"APS5_PAD_TIMELINE 1 6 1\n0 1 1\n", "APS5_PAD_TIMELINE 1 6 1\n-1 1 16\n",
"APS5_PAD_TIMELINE 1 6 1\n3600000 1 16\n",
"APS5_PAD_TIMELINE 1 18446744073709551616 1\n0 1 16\n",
"APS5_PAD_TIMELINE 1 6 1\n18446744073709551615 1 16\n",
"APS5_PAD_TIMELINE 1 6 1\n0 1 4294967296\n"
};
std::uint64_t clock = 2400000;
for (const auto& text : bad) {
write(text);
Check(input.Buttons(clock) == 0);
clock += 100000;
}
write("APS5_PAD_TIMELINE 1 6 1\n3599999 1 16384\n");
Check(input.Buttons(clock) == 0);
Check(input.Buttons(clock + 3599999000ull) == 16384);
Check(input.Buttons(clock + 3600000000ull) == 0);
write("APS5_PAD_TIMELINE 1 7 1\n0 100 16\n");
const auto maximum = std::numeric_limits<std::uint64_t>::max();
Check(input.Buttons(maximum - 10) == 16);
Check(input.Buttons(maximum) == 16);
Pad::InputCommandFile stale(path);
Check(stale.Buttons(0) == 0);
Pad::InputCommandFile disabled(std::filesystem::path{});
Check(disabled.Buttons(0) == 0);
write("10 16384 300 0 255 128 64\n");
Pad::InputCommandFile sticks(path);
Check(sticks.Poll(0) == Pad::InputCommandFile::Input{});
write("11 16 200 0 255 128 64\n");
Check(sticks.Poll(100000) == Pad::InputCommandFile::Input{16, true, {0, 255, 128, 64}});
write("12 16 200 0 256 128 64\n");
Check(sticks.Poll(200000) == Pad::InputCommandFile::Input{16, true, {0, 255, 128, 64}});
Check(sticks.Poll(300000) == Pad::InputCommandFile::Input{});
write("12 16 200 0 255 128 64 1\n");
Check(sticks.Poll(400000) == Pad::InputCommandFile::Input{});
write("APS5_PAD_TIMELINE 1 12 1\n0 500 16384\n");
Check(sticks.Poll(500000) == Pad::InputCommandFile::Input{16384});
write("13 8 200 255 0 64 128\n");
Check(sticks.Poll(600000) == Pad::InputCommandFile::Input{8, true, {255, 0, 64, 128}});
write("14 16 100\n");
Check(sticks.Poll(700000) == Pad::InputCommandFile::Input{16});
Check(sticks.Poll(800000) == Pad::InputCommandFile::Input{});
write("15 2113536 100 0 255 128 64\n");
Check(sticks.Poll(900000) == Pad::InputCommandFile::Input{16384, true, {0, 255, 128, 64}, true, false});
Check(sticks.Poll(1000000) == Pad::InputCommandFile::Input{});
write("APS5_PAD_TIMELINE 1 16 3\n0 100 2113536\n100 100 4194304\n200 100 6291456\n");
Check(sticks.Poll(1100000) == Pad::InputCommandFile::Input{16384, false, {}, true, false});
Check(sticks.Poll(1200000) == Pad::InputCommandFile::Input{0, false, {}, false, true});
Check(sticks.Poll(1300000) == Pad::InputCommandFile::Input{0, false, {}, true, true});
Check(sticks.Poll(1400000) == Pad::InputCommandFile::Input{});
write("APS5_PAD_TIMELINE 1 17 1\n0 100 8388608\n");
Check(sticks.Poll(1500000) == Pad::InputCommandFile::Input{});
std::filesystem::remove(path);
std::cout << "Controller timeline runtime cases passed\n";
return 0;
} catch (const std::exception& error) {
std::filesystem::remove(path);
std::cerr << error.what() << '\n';
return 1;
}
}
@@ -5,7 +5,8 @@ configure_windows_unwind(libScePlayerSelectionDialog)
if(BUILD_TESTING)
add_test_executable(guest_player_selection_dialog_tests ${LIBS_INCLUDE_DIR}/tests/GuestPlayerSelectionDialog.cpp)
target_include_directories(guest_player_selection_dialog_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_player_selection_dialog_tests PRIVATE libScePlayerSelectionDialog)
target_link_libraries(guest_player_selection_dialog_tests PRIVATE libScePlayerSelectionDialog libc)
configure_windows_unwind(guest_player_selection_dialog_tests)
add_test(NAME guest_player_selection_dialog COMMAND guest_player_selection_dialog_tests)
if(WIN32)
set_tests_properties(guest_player_selection_dialog PROPERTIES ENVIRONMENT_MODIFICATION
@@ -63,6 +63,11 @@ int APS5_VABI scePlayerSelectionDialogGetResult(void* result) {
return 0;
}
int APS5_VABI scePlayerSelectionDialogParamInitialize(void* param) {
if (param == nullptr) return COMMON_DIALOG_ERROR_ARG_NULL;
NotImplemented_nid_no_patch(__func__);
}
int APS5_VABI scePlayerSelectionDialogTerminate(void) {
if (g_status.exchange(COMMON_DIALOG_STATUS_NONE) == COMMON_DIALOG_STATUS_NONE) return COMMON_DIALOG_ERROR_NOT_INITIALIZED;
return 0;
@@ -6,6 +6,7 @@ if(BUILD_TESTING)
add_test_executable(guest_razor_cpu_tests ${LIBS_INCLUDE_DIR}/tests/GuestRazorCpu.cpp)
target_include_directories(guest_razor_cpu_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_razor_cpu_tests PRIVATE libSceRazorCpu)
configure_windows_unwind(guest_razor_cpu_tests)
add_test(NAME guest_razor_cpu COMMAND guest_razor_cpu_tests)
if(WIN32)
set_tests_properties(guest_razor_cpu PROPERTIES ENVIRONMENT_MODIFICATION
+8 -9
View File
@@ -69,17 +69,16 @@ int APS5_VABI sceRazorCpuSync(void) {
}
void APS5_VABI sceRazorCpuDisableFiberUserMarkers(void) {
NotImplemented_nid_no_patch(__func__);
}
int APS5_VABI sceRazorCpuInitDataTags(void* buffer, size_t size) {
(void)buffer;
(void)size;
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceRazorCpuShutdownDataTags(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
size_t APS5_VABI sceRazorCpuGetDataTagStorageSize(uint32_t tagCount) {
@@ -93,7 +92,7 @@ int APS5_VABI sceRazorCpuTagArray(const char* label, uint32_t category, void* ar
(void)array;
(void)bufferSize;
(void)elementSize;
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceRazorCpuTagBuffer(const char* label, uint32_t category, void* buffer, size_t bufferSize) {
@@ -101,20 +100,20 @@ int APS5_VABI sceRazorCpuTagBuffer(const char* label, uint32_t category, void* b
(void)category;
(void)buffer;
(void)bufferSize;
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceRazorCpuResizeTaggedBuffer(uint32_t category, void* buffer, size_t newBufferSize) {
(void)category;
(void)buffer;
(void)newBufferSize;
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceRazorCpuUnTagBuffer(uint32_t category, void* buffer) {
(void)category;
(void)buffer;
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceRazorCpuBeginLogicalFileAccess(const char* filename, uint64_t tag, uint64_t size, uint8_t operation) {
@@ -122,11 +121,11 @@ int APS5_VABI sceRazorCpuBeginLogicalFileAccess(const char* filename, uint64_t t
(void)tag;
(void)size;
(void)operation;
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceRazorCpuEndLogicalFileAccess(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
}
+121 -13
View File
@@ -1,10 +1,14 @@
#include <atomic>
#include <chrono>
#include <condition_variable>
#include <cstdint>
#include <cstdio>
#include <cstddef>
#include <cstring>
#include <limits>
#include <map>
#include <mutex>
#include <stdexcept>
#include "SceTypes.hpp"
#include "prx/libc/include/General.hpp"
@@ -20,6 +24,13 @@ constexpr int RUDP_ERROR_MEMORY = static_cast<int>(0x80770007u);
constexpr int RUDP_ERROR_CONN_RESET = static_cast<int>(0x80770009u);
constexpr int RUDP_ERROR_CONN_REFUSED = static_cast<int>(0x8077000Au);
constexpr int RUDP_ERROR_NOT_ACCEPTABLE = static_cast<int>(0x80770011u);
constexpr int RUDP_ERROR_INVALID_POLL_ID = static_cast<int>(0x8077001Fu);
constexpr int RUDP_ERROR_TOO_MANY_CONTEXTS = static_cast<int>(0x80770020u);
constexpr int RUDP_POLL_OP_ADD = 1;
constexpr int RUDP_POLL_OP_MODIFY = 2;
constexpr int RUDP_POLL_OP_REMOVE = 3;
constexpr uint64_t RUDP_POLL_WAIT_MAX_USEC = static_cast<uint64_t>(std::numeric_limits<int64_t>::max() / 2000);
constexpr int RUDP_POOL_MIN_SIZE = 0xC58;
constexpr std::uintptr_t RUDP_POOL_ALIGNMENT = 8;
@@ -36,9 +47,31 @@ std::map<int, bool> g_ctx; // id -> bound
GuestRudpEventHandler g_handler = nullptr;
void* g_handler_arg = nullptr;
struct RudpPoll {
uint32_t capacity;
std::map<int, uint16_t> entries;
};
struct RudpPollEvent {
int ctxId;
uint16_t reqEvents;
uint16_t rtnEvents;
};
static_assert(sizeof(RudpPollEvent) == 8);
int g_next_poll = 1;
std::map<int, RudpPoll> g_polls;
std::condition_variable g_polls_changed;
bool valid_ctx(int ctx) {
return g_ctx.count(ctx) != 0;
}
void remove_ctx_from_polls(int ctx) {
for (auto& [id, poll] : g_polls) {
poll.entries.erase(ctx);
}
}
} // namespace
extern "C" {
@@ -84,6 +117,8 @@ int APS5_VABI sceRudpEnd(void) {
}
g_inited = false;
g_ctx.clear();
g_polls.clear();
g_polls_changed.notify_all();
return 0;
}
@@ -92,7 +127,11 @@ int APS5_VABI sceRudpTerminate(int ctx_id) {
if (!g_inited) {
return RUDP_ERROR_NOT_INITIALIZED;
}
return g_ctx.erase(ctx_id) != 0 ? 0 : RUDP_ERROR_INVALID_CONTEXT_ID;
if (g_ctx.erase(ctx_id) == 0) {
return RUDP_ERROR_INVALID_CONTEXT_ID;
}
remove_ctx_from_polls(ctx_id);
return 0;
}
int APS5_VABI sceRudpEnableInternalIOThread(uint32_t stack_size, uint32_t priority) {
@@ -293,23 +332,92 @@ int APS5_VABI sceRudpGetMaxSegmentSize(uint16_t* size) {
return 0;
}
int APS5_VABI sceRudpPollCreate() {
NotImplemented_nid_no_patch(__func__);
int APS5_VABI sceRudpPollCreate(uint32_t size) {
std::lock_guard<std::mutex> lk(g_mutex);
if (!g_inited) {
return RUDP_ERROR_NOT_INITIALIZED;
}
if (size == 0) {
return RUDP_ERROR_INVALID_ARGUMENT;
}
if (g_next_poll == std::numeric_limits<int>::max()) {
throw std::runtime_error("sceRudpPollCreate: poll ids exhausted");
}
const int id = g_next_poll++;
g_polls[id] = RudpPoll{size, {}};
return id;
}
int APS5_VABI sceRudpPollControl(int pollId, int op, int ctxId, uint16_t events) {
std::lock_guard<std::mutex> lk(g_mutex);
if (!g_inited) {
return RUDP_ERROR_NOT_INITIALIZED;
}
if (pollId < 0 || ctxId < 0 || (op != RUDP_POLL_OP_ADD && op != RUDP_POLL_OP_MODIFY && op != RUDP_POLL_OP_REMOVE)) {
return RUDP_ERROR_INVALID_ARGUMENT;
}
const auto poll = g_polls.find(pollId);
if (poll == g_polls.end()) {
return RUDP_ERROR_INVALID_POLL_ID;
}
if (!valid_ctx(ctxId)) {
return RUDP_ERROR_INVALID_CONTEXT_ID;
}
auto& entries = poll->second.entries;
const auto entry = entries.find(ctxId);
if (op == RUDP_POLL_OP_ADD) {
if (entries.size() >= poll->second.capacity) {
return RUDP_ERROR_TOO_MANY_CONTEXTS;
}
if (entry != entries.end()) {
return RUDP_ERROR_INVALID_CONTEXT_ID;
}
entries.emplace(ctxId, events);
return 0;
}
if (entry == entries.end()) {
return RUDP_ERROR_INVALID_CONTEXT_ID;
}
if (op == RUDP_POLL_OP_MODIFY) {
entry->second = events;
} else {
entries.erase(entry);
}
return 0;
}
int APS5_VABI sceRudpPollControl() {
NotImplemented_nid_no_patch(__func__);
return 0;
int APS5_VABI sceRudpPollWait(int pollId, RudpPollEvent* events, uint32_t eventLen, uint64_t timeout) {
std::unique_lock<std::mutex> lk(g_mutex);
if (!g_inited) {
return RUDP_ERROR_NOT_INITIALIZED;
}
if (pollId < 0 || events == nullptr || eventLen == 0) {
return RUDP_ERROR_INVALID_ARGUMENT;
}
if (g_polls.count(pollId) == 0) {
return RUDP_ERROR_INVALID_POLL_ID;
}
const auto closed = [pollId] { return !g_inited || g_polls.count(pollId) == 0; };
if (timeout > RUDP_POLL_WAIT_MAX_USEC) {
g_polls_changed.wait(lk, closed);
} else if (!g_polls_changed.wait_for(lk, std::chrono::microseconds(timeout), closed)) {
return 0;
}
return g_inited ? RUDP_ERROR_INVALID_POLL_ID : RUDP_ERROR_NOT_INITIALIZED;
}
int APS5_VABI sceRudpPollWait() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceRudpPollDestroy() {
NotImplemented_nid_no_patch(__func__);
int APS5_VABI sceRudpPollDestroy(int pollId) {
std::lock_guard<std::mutex> lk(g_mutex);
if (!g_inited) {
return RUDP_ERROR_NOT_INITIALIZED;
}
if (pollId < 0) {
return RUDP_ERROR_INVALID_ARGUMENT;
}
if (g_polls.erase(pollId) == 0) {
return RUDP_ERROR_INVALID_POLL_ID;
}
g_polls_changed.notify_all();
return 0;
}
@@ -361,7 +361,7 @@ int APS5_VABI sceSaveDataDeleteTransactionResource(int32_t resource) {
return SAVE_DATA_OK;
}
static constexpr std::uint64_t SAVE_DATA_BLOCK_BYTES = 32768;
static constexpr std::uint64_t SAVE_DATA_BLOCK_BYTES = 65536;
static std::uint64_t save_blocks(const std::string& real_path) {
std::ifstream file(blocks_path(real_path));
@@ -591,7 +591,7 @@ int APS5_VABI sceSaveDataInitialize3(const void* init) {
int APS5_VABI sceSaveDataLoadIcon(const SaveDataMountPoint* mount_point, SaveDataIcon* icon) {
(void)icon;
if (mount_point == nullptr) {
throw std::runtime_error("sceSaveDataLoadIcon: null mount_point");
return SAVE_DATA_ERROR_PARAMETER;
}
std::lock_guard lock(g_slots_mutex);
if (find_slot_by_mount_point(mount_point->data) == -1) {
@@ -673,7 +673,7 @@ int APS5_VABI sceSaveDataPrepare(const SaveDataMountPoint* mount_point, const Sa
int APS5_VABI sceSaveDataSaveIcon(const SaveDataMountPoint* mount_point, const SaveDataIcon* icon) {
(void)icon;
if (mount_point == nullptr) {
throw std::runtime_error("sceSaveDataSaveIcon: null mount_point");
return SAVE_DATA_ERROR_PARAMETER;
}
std::lock_guard lock(g_slots_mutex);
if (find_slot_by_mount_point(mount_point->data) == -1) {
@@ -690,7 +690,7 @@ int APS5_VABI sceSaveDataSaveIconByPath(const SaveDataMountPoint* mount_point, c
static int setParam(const SaveDataMountPoint* mount_point, uint32_t param_type, const void* param_buf, size_t param_buf_size) {
if (mount_point == nullptr || param_buf == nullptr) {
throw std::runtime_error("sceSaveDataSetParam: null argument");
return SAVE_DATA_ERROR_PARAMETER;
}
std::lock_guard lock(g_slots_mutex);
const int slot = find_slot_by_mount_point(mount_point->data);
@@ -896,7 +896,7 @@ int APS5_VABI sceSaveDataTransferringMount(const SaveDataTransferringMount* moun
static int umount2(uint32_t mode, const SaveDataMountPoint* mount_point) {
(void)mode;
if (mount_point == nullptr) {
throw std::runtime_error("sceSaveDataUmount2: null mount_point");
return SAVE_DATA_ERROR_PARAMETER;
}
std::lock_guard lock(g_slots_mutex);
int slot = find_slot_by_mount_point(mount_point->data);
@@ -25,7 +25,7 @@ constexpr std::uint32_t SAVE_DATA_MOUNT_MODE_RDWR = 2;
constexpr std::uint32_t SAVE_DATA_MOUNT_MODE_CREATE = 4;
constexpr std::uint32_t SAVE_DATA_MOUNT_MODE_CREATE2 = 32;
constexpr std::uint64_t SAVE_DATA_BLOCKS_MAX = 32768;
constexpr std::uint64_t SAVE_DATA_BLOCKS_MAX = 16384;
constexpr std::uint32_t SAVE_DATA_PARAM_TYPE_ALL = 0;
constexpr std::uint32_t SAVE_DATA_PARAM_TYPE_TITLE = 1;
constexpr std::uint32_t SAVE_DATA_PARAM_TYPE_SUB_TITLE = 2;
@@ -262,6 +262,10 @@ int APS5_VABI sceSystemServiceGetDisplaySafeAreaInfo(SystemServiceDisplaySafeAre
return SYSTEM_SERVICE_OK;
}
int APS5_VABI sceSystemServiceShowDisplaySafeAreaSettings(void) {
return SYSTEM_SERVICE_OK;
}
int APS5_VABI sceSystemServiceGetHdrToneMapLuminance(SystemServiceHdrToneMapLuminance* luminance) {
if (luminance == nullptr) {
return SYSTEM_SERVICE_ERROR_PARAMETER;
@@ -149,6 +149,15 @@ if(BUILD_TESTING)
set_tests_properties(pad_input_mapping PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
add_test_executable(pad_output_pacing_tests tests/PadOutputPacing.cpp)
target_include_directories(pad_output_pacing_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(pad_output_pacing_tests PRIVATE libc)
configure_windows_unwind(pad_output_pacing_tests)
add_test(NAME pad_output_pacing COMMAND pad_output_pacing_tests)
if(WIN32)
set_tests_properties(pad_output_pacing PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
endif()
if(BUILD_TESTING)
@@ -5,6 +5,7 @@
#include "SDL_gamecontroller.h"
#include "prx/libScePad/include/KeyboardMouse.hpp"
#include "prx/libScePad/include/PadState.hpp"
#include "prx/libSceVideoOut/include/PadOutputPacing.hpp"
#include <chrono>
#include "prx/libSceVideoOut/include/Telemetry.hpp"
@@ -37,6 +38,8 @@ private:
bool outputPending = false;
VideoOutTelemetry::PadChangeFilter telemetryFilter;
std::chrono::steady_clock::time_point nextRumbleRefresh{};
LightBarPacer lightBar;
TriggerEffectCache triggerEffect;
};
#endif
@@ -0,0 +1,63 @@
#ifndef CORE_LIBS_PRX_LIBSCEVIDEOOUT_PADOUTPUTPACING_HPP
#define CORE_LIBS_PRX_LIBSCEVIDEOOUT_PADOUTPUTPACING_HPP
#include <array>
#include <chrono>
#include <cstdint>
class LightBarPacer {
public:
using Clock = std::chrono::steady_clock;
using Color = std::array<std::uint8_t, 3>;
static constexpr std::chrono::milliseconds Interval{30};
bool Pending(const Color& color) const {
return !sent || color != last;
}
bool Due(const Color& color, Clock::time_point now) const {
return Pending(color) && now >= next;
}
void Result(const Color& color, Clock::time_point now, bool succeeded) {
next = now + Interval;
if (!succeeded) return;
last = color;
sent = true;
}
void Reset() {
sent = false;
next = {};
}
private:
Color last{};
bool sent = false;
Clock::time_point next{};
};
class TriggerEffectCache {
public:
using Effect = std::array<std::uint8_t, 47>;
bool NeedsSend(const Effect& effect) const {
return !sent || effect != last;
}
void Result(const Effect& effect, bool succeeded) {
if (!succeeded) return;
last = effect;
sent = true;
}
void Reset() {
sent = false;
}
private:
Effect last{};
bool sent = false;
};
#endif
+16 -10
View File
@@ -60,6 +60,8 @@ void PadInput::openController(int deviceIndex) {
SDL_GameControllerGetNumTouchpads(controller), SDL_GameControllerHasLED(controller) == SDL_TRUE, SDL_GameControllerHasRumbleTriggers(controller) == SDL_TRUE);
enableSensors();
outputPending = true;
lightBar.Reset();
triggerEffect.Reset();
}
void PadInput::enableSensors() {
@@ -87,10 +89,16 @@ void PadInput::applyOutput() {
if (controller == nullptr) return;
const auto now = std::chrono::steady_clock::now();
const bool isPs5 = SDL_GameControllerGetType(controller) == SDL_CONTROLLER_TYPE_PS5;
const auto large = isPs5 ? outputState.vibrationLarge : std::max(outputState.vibrationLarge, outputState.audioVibrationLarge);
const auto small = isPs5 ? outputState.vibrationSmall : std::max(outputState.vibrationSmall, outputState.audioVibrationSmall);
const auto large = std::max(outputState.vibrationLarge, outputState.audioVibrationLarge);
const auto small = std::max(outputState.vibrationSmall, outputState.audioVibrationSmall);
const bool rumbling = large != 0 || small != 0;
const bool triggerRumble = outputState.trigger[0].fallback != 0 || outputState.trigger[1].fallback != 0;
if (SDL_GameControllerHasLED(controller) == SDL_TRUE) {
const LightBarPacer::Color color = outputState.lightBarValid
? LightBarPacer::Color{outputState.lightBar[0], outputState.lightBar[1], outputState.lightBar[2]}
: LightBarPacer::Color{0, 64, 255};
if (lightBar.Due(color, now)) lightBar.Result(color, now, SDL_GameControllerSetLED(controller, color[0], color[1], color[2]) == 0);
}
if (!outputPending) {
if ((rumbling || (triggerRumble && !isPs5)) && now >= nextRumbleRefresh) outputPending = true;
else return;
@@ -99,17 +107,15 @@ void PadInput::applyOutput() {
nextRumbleRefresh = now + std::chrono::milliseconds(700);
constexpr Uint32 rumbleMs = 2000;
SDL_GameControllerRumble(controller, static_cast<Uint16>(large * 257), static_cast<Uint16>(small * 257), rumbling ? rumbleMs : 0);
if (SDL_GameControllerHasLED(controller) == SDL_TRUE) {
if (outputState.lightBarValid) SDL_GameControllerSetLED(controller, outputState.lightBar[0], outputState.lightBar[1], outputState.lightBar[2]);
else SDL_GameControllerSetLED(controller, 0, 64, 255);
}
if (outputState.triggerTouched) {
if (isPs5) {
Uint8 effect[47] = {};
TriggerEffectCache::Effect effect{};
effect[0] = 0x04 | 0x08;
std::memcpy(effect + 10, outputState.trigger[1].effect, 11);
std::memcpy(effect + 21, outputState.trigger[0].effect, 11);
SDL_GameControllerSendEffect(controller, effect, sizeof(effect));
std::memcpy(effect.data() + 10, outputState.trigger[1].effect, 11);
std::memcpy(effect.data() + 21, outputState.trigger[0].effect, 11);
if (triggerEffect.NeedsSend(effect)) {
triggerEffect.Result(effect, SDL_GameControllerSendEffect(controller, effect.data(), static_cast<int>(effect.size())) == 0);
}
} else if (SDL_GameControllerHasRumbleTriggers(controller) == SDL_TRUE) {
SDL_GameControllerRumbleTriggers(controller, static_cast<Uint16>(outputState.trigger[0].fallback * 257), static_cast<Uint16>(outputState.trigger[1].fallback * 257), triggerRumble ? rumbleMs : 0);
}
@@ -0,0 +1,89 @@
#include "prx/libSceVideoOut/include/PadOutputPacing.hpp"
#include <chrono>
#include <cstdio>
#include <cstdlib>
namespace {
using namespace std::chrono_literals;
void Require(bool condition, const char* message) {
if (condition) return;
std::fprintf(stderr, "pad_output_pacing: %s\n", message);
std::exit(1);
}
void LightBarSendsChangesOnly() {
LightBarPacer pacer;
const auto start = LightBarPacer::Clock::time_point{} + 1s;
const LightBarPacer::Color blue{0, 64, 255};
Require(pacer.Due(blue, start), "the first colour is sent at once");
pacer.Result(blue, start, true);
Require(!pacer.Pending(blue), "a colour that was sent is not sent again");
Require(!pacer.Due(blue, start + 1s), "an unchanged colour stays quiet");
const LightBarPacer::Color red{255, 0, 0};
Require(pacer.Pending(red) && !pacer.Due(red, start + 10ms), "a new colour waits for the interval");
Require(pacer.Due(red, start + LightBarPacer::Interval), "a new colour goes out once the interval has passed");
}
void LightBarRetriesFailures() {
LightBarPacer pacer;
const auto start = LightBarPacer::Clock::time_point{} + 1s;
const LightBarPacer::Color green{0, 255, 0};
pacer.Result(green, start, false);
Require(pacer.Pending(green), "a colour SDL refused is still pending");
Require(!pacer.Due(green, start + 1ms), "a refused colour is not retried on the next frame");
Require(pacer.Due(green, start + LightBarPacer::Interval), "a refused colour is retried after the interval");
pacer.Result(green, start + LightBarPacer::Interval, true);
Require(!pacer.Pending(green), "a retried colour that was sent is done");
pacer.Reset();
Require(pacer.Due(green, start + LightBarPacer::Interval), "a new controller gets the current colour");
}
void LightBarAnimationIsPaced() {
LightBarPacer pacer;
const auto start = LightBarPacer::Clock::time_point{} + 1s;
constexpr int frames = 120;
int sent = 0;
LightBarPacer::Color color{};
for (int frame = 0; frame < frames; ++frame) {
const auto now = start + std::chrono::microseconds(frame * 8333);
color = {static_cast<std::uint8_t>(frame), 32, 32};
if (!pacer.Due(color, now)) continue;
pacer.Result(color, now, true);
++sent;
}
Require(sent >= 30 && sent <= 34, "a colour that changes every frame at 120 Hz goes out about 33 times a second");
const auto end = start + 1s;
Require(pacer.Pending(color), "the last colour of the animation is still owed");
Require(pacer.Due(color, end + LightBarPacer::Interval), "the last colour of the animation is sent when the interval allows");
}
void TriggerEffectRetriesFailures() {
TriggerEffectCache cache;
TriggerEffectCache::Effect effect{};
effect[0] = 0x0C;
effect[10] = 0x21;
Require(cache.NeedsSend(effect), "the first effect is sent");
cache.Result(effect, false);
Require(cache.NeedsSend(effect), "an effect SDL refused is sent again");
cache.Result(effect, true);
Require(!cache.NeedsSend(effect), "an effect that was sent is not repeated");
auto changed = effect;
changed[21] = 0x26;
Require(cache.NeedsSend(changed), "a changed effect is sent");
cache.Reset();
Require(cache.NeedsSend(effect), "a new controller gets the current effect");
}
}
int main() {
LightBarSendsChangesOnly();
LightBarRetriesFailures();
LightBarAnimationIsPaced();
TriggerEffectRetriesFailures();
std::puts("pad_output_pacing: ok");
return 0;
}
+30 -9
View File
@@ -1,3 +1,4 @@
#include <algorithm>
#include <cstdint>
#include <cstddef>
#include <cstring>
@@ -68,6 +69,22 @@ int SetAttribute(std::map<std::int32_t, Endpoint>& endpoints, std::int32_t id, s
return 0;
}
int GetAttribute(std::map<std::int32_t, Endpoint>& endpoints, std::int32_t id, std::int32_t attribute, void* value, std::int32_t size) {
const auto endpoint = endpoints.find(id);
if (endpoint == endpoints.end() || !value || size <= 0) return SCE_VOICE_ERROR_LIBVOICEQOS_ARGUMENT_INVALID;
const auto attr = endpoint->second.find(attribute);
if (attr != endpoint->second.end() && !attr->second.empty()) {
const auto toCopy = std::min(static_cast<std::size_t>(size), attr->second.size());
std::memcpy(value, attr->second.data(), toCopy);
if (static_cast<std::size_t>(size) > toCopy) {
std::memset(static_cast<std::uint8_t*>(value) + toCopy, 0, static_cast<std::size_t>(size) - toCopy);
}
} else {
std::memset(value, 0, static_cast<std::size_t>(size));
}
return 0;
}
template<typename Body>
int Locked(Body&& body) {
auto& qos = State();
@@ -144,19 +161,23 @@ int APS5_VABI sceVoiceQoSSetRemoteEndpointAttribute(int32_t remoteId, int32_t at
return Locked([&](QoS& qos) { return SetAttribute(qos.remotes, remoteId, attributeId, value, size); });
}
int APS5_VABI sceVoiceQoSReadPacket() {
NotImplemented_nid_no_patch(__func__);
return 0;
int APS5_VABI sceVoiceQoSReadPacket(int32_t connectionId, void* buffer, uint32_t* size) {
return Locked([&](QoS& qos) {
if (!buffer || !size || !qos.connections.contains(connectionId)) return SCE_VOICE_ERROR_LIBVOICEQOS_ARGUMENT_INVALID;
*size = 0;
return 0;
});
}
int APS5_VABI sceVoiceQoSWritePacket() {
NotImplemented_nid_no_patch(__func__);
return 0;
int APS5_VABI sceVoiceQoSWritePacket(int32_t connectionId, const void* buffer, uint32_t* size) {
return Locked([&](QoS& qos) {
if (!buffer || !size || !qos.connections.contains(connectionId)) return SCE_VOICE_ERROR_LIBVOICEQOS_ARGUMENT_INVALID;
return 0;
});
}
int APS5_VABI sceVoiceQoSGetLocalEndpointAttribute() {
NotImplemented_nid_no_patch(__func__);
return 0;
int APS5_VABI sceVoiceQoSGetLocalEndpointAttribute(int32_t localId, int32_t attributeId, void* value, int32_t size) {
return Locked([&](QoS& qos) { return GetAttribute(qos.locals, localId, attributeId, value, size); });
}
int APS5_VABI sceVoiceQoSGetRemoteEndpointAttribute(int32_t remoteId, int32_t attributeId, const void* value, int32_t size) {
Loaded 100 of 307 files, more files were not shown because too many files have changed in this diff. Show more