mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-22 00:22:57 -05:00
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:
commit
43e7a4792e
307 files changed
+10503
-977
No files matched your search
@@ -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}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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})
|
||||
|
||||
@@ -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()
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 = {};
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
Reference in new issue
Block a user